mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-07-10 01:29:17 +00:00
* feat(memory): add a git-shared team memory tier
Add an opt-in third auto-memory tier — TEAM — stored in the repository at
`.qwen/team-memory/` and shared with collaborators through git. The private
USER and PROJECT tiers are unchanged.
- Enabled via the `memory.enableTeamMemory` setting (default off) or the
`QWEN_CODE_MEMORY_TEAM` env override; inactive otherwise.
- The model is taught a three-tier prompt and when to route saves to the
shared tier (project-wide conventions, team-wide references).
- Writes to the team directory are scanned for secrets (a curated
gitleaks-style ruleset) and hard-blocked, since the directory is committed
to the repo and visible to everyone with repo access.
- Team writes default to 'ask' permission — not auto-allowed like the private
tiers — so they are proposed for confirmation in the default approval mode
and surface in the git diff for review.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(vscode): update settings schema for team memory
* fix(memory): harden team memory review gaps
* fix(memory): address team-memory PR review feedback
- secret-scanner: AWS key suffix is base62 ([A-Z0-9]), not base32
([A-Z2-7]) — old class missed IDs with digits 0/1/8/9; add a
positive test for such a key.
- paths: document that isAnyAutoMemPath deliberately excludes team
memory (security-load-bearing — team writes must stay 'ask').
- extractionAgentPlanner: name the team-memory dir in the scoped deny
message so a denied team write is debuggable.
- prompt: spell the tier count via a number-word lookup so a future
4th tier never silently reads "two".
- team-memory-secret-guard: debug-log matched rule IDs only (never the
secret content) when a write is blocked.
- edit: when a blocked team write's secret already exists in the
on-disk file, tell the user to delete the committed secret.
- docs: caveat that a directory-form gitignore (.qwen/) makes the
!-reinclude a no-op; use the file-glob form.
Co-Authored-By: Qwen-Coder <noreply@qwen.ai>
* fix(memory): harden team-memory secret guard on blocked writes and notebooks
- write-file: blocked team-memory secret write now returns an `error`
field (INVALID_TOOL_PARAMS) so the framework treats it as a failure
instead of a silent success, mirroring edit.ts.
- notebook-edit: scan the serialized .ipynb that hits disk through the
team-memory secret guard before writing, blocking with the same
`error` field; behavior unchanged for non-team-memory paths.
- store: log non-ENOENT errors in readTeamAutoMemoryIndex before
re-throwing, so EACCES/EIO/ELOOP no longer vanish via config.ts's
best-effort `.catch(() => null)`.
- config: emit a debug diagnostic when team memory is enabled but
suppressed by the untrusted-workspace trust gate.
Co-Authored-By: Qwen-Coder <noreply@qwen.ai>
* feat(memory): auto-generate the team memory index
Generate the team `MEMORY.md` index by scanning the saved memory files
instead of having the model hand-maintain it. The index is derived and
ordered by path, so the committed file is deterministic across machines.
This removes the git merge-conflict surface a hand-edited shared index would
have, keeps the index consistent with the files, and frees the model from the
two-step save for the team tier.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* feat(memory): opt-in git sync for team memory
Add an opt-in, best-effort git sync of team memory at session start so a
daemon can share team memory through the remote without manual git. Off by
default; enabled with `QWEN_CODE_MEMORY_TEAM_SYNC=1`.
It commits the team directory (only that path), pulls fast-forward-only, then
pushes. `--ff-only` is deliberate: it never creates a merge commit or a
conflict — a diverged branch is skipped rather than touching the working tree.
All git is best-effort and never throws, so a failure cannot break a session.
Commits can be attributed to the acting user via an optional author, for
cooperative per-user attribution on a shared daemon.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(core): close team-memory secret-guard regex + test gaps
- secret-scanner: add `_` to the two T3BlbkFJ-marker char classes so legacy
base64url OpenAI keys with underscores in the body no longer bypass the
scanner; extend the OpenAI positive samples with an underscore key body.
- edit: cover the pre-existing-secret branch — seed the on-disk file with a
full detectable token, assert the distinct "already exists" message and that
the blocked edit leaves the file untouched.
- team-paths: cover the first-ever write before .qwen/team-memory exists,
exercising realpathNearestExisting walking up to an existing ancestor.
Co-Authored-By: Qwen-Coder <noreply@qwen.ai>
* feat(core): add Google OAuth client secret and SendGrid secret-scanner rules
Extend the team-memory secret scanner with two more high-confidence,
distinctive-prefix credential patterns (both gitleaks rules):
- gcp-oauth-client-secret: GOCSPX- prefix + 24 base64url chars,
placed next to the existing GCP AIza API-key rule.
- sendgrid-api-token: SG. + 22-char id + . + 43-char secret.
Both have a near-zero false-positive bar, matching the curated set's
existing criteria. Add a load-bearing positive sample per rule.
Co-Authored-By: Qwen-Coder <noreply@qwen.ai>
* fix(memory): warn when team memory is enabled but not git-shareable
When the team tier is active, emit a one-time startup warning if its
directory is git-ignored (e.g. a directory-form `.qwen/` ignore that a
`!`-reinclude cannot escape) or there is no git root — so the tier never
silently shares nothing. Gated on the active (trusted + enabled) tier;
probes a representative file path with `git check-ignore`, and is
latched so refreshHierarchicalMemory re-runs do not re-emit it.
Closes the last open review thread (wenshao + qwen-code-ci-bot both
flagged the silent-inert case); the docs caveat already shipped.
Co-Authored-By: Qwen-Coder <noreply@qwen.ai>
* feat(memory): dedup team index by description; fix empty-frontmatter parse
The team index now collapses entries that share a (normalized) description
into one line — listing the other files via "(also: …)" — so two people
saving the same shared fact don't surface as duplicates. Nothing is dropped;
every file path is preserved and the grouping is deterministic (input is
path-sorted).
Also fix a pre-existing frontmatter bug: parseFrontmatterValue used `\s*`,
which crosses newlines, so an empty value (`description:`) captured the next
frontmatter line. Bound it to horizontal whitespace.
Co-Authored-By: Qwen-Coder <noreply@qwen.ai>
* feat(memory): add memory.enableTeamMemorySync setting
Team-memory git sync was env-only (QWEN_CODE_MEMORY_TEAM_SYNC). Add a
first-class `memory.enableTeamMemorySync` setting mirroring the
enableTeamMemory plumbing (settingsSchema, cli/core config, acpAgent
type/schema/default/keys/normalizer, desktop mirror), with the env var kept
as a 0/1 override. Off by default. Docs updated.
Co-Authored-By: Qwen-Coder <noreply@qwen.ai>
* fix(memory): audit hardening — resilient scan, deterministic team index
From a multi-round adversarial audit of the team-memory tier:
- scan: a single unreadable memory file (EACCES, or a TOCTOU delete during
`git pull`) no longer rejects the whole scan and wipe every memory from the
index — per-file failures are caught and skipped (debug-logged).
- indexer: order the team index by code unit, not `localeCompare()`, so the
committed MEMORY.md is byte-identical across machines/locales and the
ff-only sync can't wedge on index churn.
- scan: escape the frontmatter key before building its RegExp (latent hardening).
- config: surface a skipped/failed team-memory sync at debug level instead of
silently swallowing it.
Co-Authored-By: Qwen-Coder <noreply@qwen.ai>
* fix(memory): deterministic team index cap + surface diverged sync
Round-2 audit follow-ups:
- scan: the >200-file cap selected its subset by mtime + locale basename, which
differs per machine/locale — so a large team's committed index churned and
wedged the ff-only sync. Team scans now cap by code-unit path (deterministic);
private tiers keep mtime-recency (never shared, so it's fine).
- sync: a diverged branch made `pull --ff-only` (and the push) fail with no
`skippedReason` — a silent no-op for a user who opted into sync. Now reported
as `pull-failed` / `push-failed` and logged. Docs note pull/push are
whole-branch, not path-scoped.
Co-Authored-By: Qwen-Coder <noreply@qwen.ai>
* fix(memory): regenerate settings schema for enableTeamMemorySync
Co-Authored-By: Qwen-Coder <noreply@qwen.ai>
* fix(memory): harden team-memory git sync (reconcile-first, scoped push, safe failure)
The opt-in team-memory git sync could publish unrelated local commits, wedge a
two-writer branch, leave team files staged on commit failure, and hang session
start on a credential prompt. Harden all four maintainer-flagged criticals:
- Reconcile first: run `git pull --ff-only` BEFORE committing so the local team
commit lands on top of upstream instead of diverging; a diverged branch is
skipped cleanly as `pull-failed` rather than committing then wedging `--ff-only`.
- Scoped push: push only when THIS sync created the commit and the branch was
not already ahead of `@{u}` (pre-sync), via an explicit single-branch refspec
(`HEAD:<merge-ref>`) — never an unqualified `git push`. New `local-ahead` skip.
- Safe failure: unstage the team path when the commit fails (hook/GPG/identity)
so a user's next manual `git commit` cannot sweep team files in.
- Non-interactive git: force `GIT_TERMINAL_PROMPT=0` + batch-mode SSH, SIGKILL on
timeout, and a shorter timeout so sync cannot block session start on a prompt.
config refresh: rebuild the team index BEFORE syncing (so a fresh MEMORY.md is
committed/pushed, with a re-build after a pull that brought new files) and key
the shareability latch by projectRoot so a newly entered repo is re-checked.
Co-Authored-By: Qwen-Coder <noreply@qwen.ai>
* fix(memory): harden team-memory index/scan/path security
Address the security/correctness criticals from PR #5886 review. Team
memory is committed to the repo and loaded into every collaborator's
system prompt, so its write/scan/classification paths are trust
boundaries.
- indexer: pass noFollow on the team MEMORY.md write and reject a
symlinked team root, so a committed `.qwen/team-memory` (or index)
symlink can't redirect the generated index outside the repo.
- indexer: sanitize attacker-controlled frontmatter title/description
(strip control/zero-width/bidi chars, collapse newlines, defang
code/link markdown, cap length) before embedding into MEMORY.md, to
prevent prompt injection into the shared context.
- indexer: skip the rewrite when the generated index is byte-identical,
avoiding mtime churn and no-op commit cycles.
- scan: normalize CRLF before parsing so a Windows checkout's team files
don't silently drop out of the shared index.
- paths: resolve a dangling final-component symlink via lstat/readlink in
realpathNearestExisting, so a `decoy -> team-memory/leak.md` (missing
target) is classified as team and the secret scanner can't be bypassed.
- secret-scanner: bound both quantifiers in the openai-api-key T3BlbkFJ
rule ({20,512}) to remove O(n^2) ReDoS backtracking; real keys still
match.
- team-memory-git-status: also probe a representative topic path so a
repo that re-includes the index but ignores topic files is flagged as
non-shareable.
- notebook-edit: run the team-memory secret scan in validateToolParamValues
too, for parity with edit/write-file.
Adds load-bearing tests for each fix.
Co-Authored-By: Qwen-Coder <noreply@qwen.ai>
* fix(memory): sanitize team index paths and verify whole-path containment
Refines the earlier team-memory security hardening with two fixes the
first pass left open:
- sanitizeIndexPath now defangs the attacker-controlled relativePath
rendered into the committed MEMORY.md — at BOTH the `](path)` link
target and the "(also: ...)" dedup suffix. A git filename may legally
contain newlines and markdown delimiters, so a raw path could inject a
second physical line (e.g. "- SYSTEM:") and reopen the prompt-injection
structure the patch was closing for title/description.
- rebuildTeamAutoMemoryIndex now realpath-resolves the whole team root
and requires it to equal repoRoot/.qwen/team-memory. The prior lstat
only inspected the leaf, so a symlinked PARENT component (e.g.
`.qwen -> /tmp/out`) let scan/write escape the repo undetected while
the guard believed it had rejected symlinks.
Also adds a regression test pinning the security-load-bearing invariant
that isAnyAutoMemPath excludes team paths (so team writes stay ask-gated).
Co-Authored-By: Qwen-Coder <noreply@qwen.ai>
* fix(memory): keep team index link targets addressable, not just safe
The earlier injection fix rewrote legal filename chars (`(` `)` `[` `]` and
backticks) to `_` and used that destroyed value as the ACTUAL MEMORY.md link
target. A real file `feedback/a(b).md` was emitted as `](feedback/a_b_.md)`,
which does not exist (or, worse, collides with a different real file) — the
link was injection-safe but no longer addressable.
Replace `sanitizeIndexPath` with `encodeIndexPathTarget`, a reversible
percent-encoder: every char outside an addressable allowlist (alphanumerics
and `/ . - _ ~`) is percent-encoded, so breakout chars become inert ASCII
(newline->%0A, `(`->%28, `)`->%29, space->%20, backtick->%60, ...). The target
is a single line with no `](`/`)` breakout, yet `decodeURIComponent` recovers
the exact original path so the link resolves to the real file. `/` is kept
literal so the path still works as a relative reference. Applied at both index
sites (the main `](path)` link and the `(also: ...)` suffix). The display label
(title) stays non-reversibly sanitized as before — it is not addressable.
Update the path tests to assert the emitted target percent-decodes back to the
real relative path, and add a `feedback/a(b).md` case. The newline+`]`/`)`
injection assertions remain (mutation-verified: they fail if the encoding is
dropped).
Co-Authored-By: Qwen-Coder <noreply@qwen.ai>
* fix(memory): gate team-memory sync on the index safety check
rebuildTeamAutoMemoryIndex throws when the team root is a symlink that
could redirect the committed index outside the repo, but the surrounding
.catch swallowed the error and syncTeamMemory ran independently — so a
refused symlink/escape still got git add/commit/push'd on the out-of-repo
dir, defeating the indexer's refusal. Capture whether the rebuild
succeeded and only sync when it did, so a failed safety check skips sync
entirely.
Co-Authored-By: Qwen-Coder <noreply@qwen.ai>
* fix(memory): harden team-memory git sync and cover execute-time secret scan
Several robustness fixes to the team-memory git sync, plus a missing test:
- Respect a user's GIT_SSH_COMMAND: append our non-interactive batch guards
(-oBatchMode=yes -oConnectTimeout=5) onto it instead of clobbering it, so a
custom identity (-i), proxy jump (-J), or non-standard port is preserved.
- Skip cleanly on a detached HEAD (new `detached-head` skippedReason) instead
of creating an orphaned commit that can never be pushed. A branch with no
upstream still commits locally as before.
- Use SIGTERM (not SIGKILL) as the git kill signal so a timeout mid-commit/-pull
lets git release index.lock and clean up; the non-interactive ssh guards
already bound the network-hang case.
- docs(memory): correct the sync description to match the implementation
(rebuild index, ff-pull BEFORE commit, push only the scoped sync commit,
skip on divergence/no-upstream).
- test(notebook-edit): cover the execute-time team-memory secret scan (the
full-notebook backstop), distinct from the existing validate-time test.
Co-Authored-By: Qwen-Coder <noreply@qwen.ai>
* fix(memory): split security vs operational team-sync failures; per-op git kill signal
Refine the recent team-memory sync changes per review:
- indexer: throw a typed TeamMemoryRootSecurityError for the symlink/escape
rejection so the sync gate can tell a SECURITY refusal apart from an
OPERATIONAL IO failure. config now blocks sync ONLY on the security class;
an operational rebuild failure (EACCES/ENOSPC/EPERM) is logged but
self-corrects on the next rebuild and no longer permanently gates legitimate
team sync. The security invariant (never add/commit/push an escaping root)
is unchanged.
- team-memory-sync: pass a per-op kill signal to tryGit — SIGKILL for the
hang-prone network/ref ops (pull/push/rev-parse/symbolic-ref; no index.lock
to corrupt), SIGTERM for mutating ops (add/commit) so git can release locks
and clean up. Node's execFile timeout does not escalate to SIGKILL on its
own, so a child trapping SIGTERM could hang past the timeout.
- team-memory-sync: thread the resolved branch into resolvePushTarget so it no
longer re-spawns `git symbolic-ref` after the detached-HEAD check.
- docs: fix a broken sentence in the team-memory git-sync section.
- tests: cover both rebuild failure classes (security -> sync skipped;
operational -> sync still runs) and a positive gate (rebuild ok + sync
enabled -> sync runs); assert the typed error on the indexer symlink tests.
Co-Authored-By: Qwen-Coder <noreply@qwen.ai>
---------
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <noreply@qwen.ai>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
|
||
|---|---|---|
| .. | ||
| channels | ||
| _meta.ts | ||
| approval-mode.md | ||
| arena.md | ||
| auto-mode.md | ||
| code-review.md | ||
| commands.md | ||
| dual-output.md | ||
| followup-suggestions.md | ||
| headless.md | ||
| hooks.md | ||
| language.md | ||
| lsp.md | ||
| markdown-rendering.md | ||
| mcp.md | ||
| memory.md | ||
| sandbox.md | ||
| scheduled-tasks.md | ||
| skills.md | ||
| status-line.md | ||
| structured-output.md | ||
| sub-agents.md | ||
| tips.md | ||
| token-caching.md | ||
| tool-use-summaries.md | ||
| worktree.md | ||