Skip to content

Inbox: connect Qwen Code and tag its sessions - #2271

Merged
backnotprop merged 16 commits into
mainfrom
feat/inbox-qwen-connect
Oct 28, 2026
Merged

backnotprop merged 16 commits into
mainfrom
feat/inbox-qwen-connect

Conversation

@backnotprop

@backnotprop backnotprop commented Oct 28, 2026 •

Copy link
Copy Markdown
Owner

What

Qwen Code joins the Inbox the same way Gemini CLI does, and its sessions get a real identity.

  • Connect. plannotator inbox adds a plannotator-inbox entry to Qwen Code's settings.json itself. It does not run qwen mcp add: that command writes every MCP server back with its ${VAR} values filled in, so a token the person keeps in their environment would end up in the file. A qwen on PATH is still asked its --version, so a program that only shares the name is left alone. It runs in the home folder, so Qwen never touches the working folder's .qwen/settings.json. Qwen Code shows up in Settings → Agents, in the Connected band and in the "Connect an agent" picker, with its own icon.
  • QWEN_HOME. Read as Qwen reads it: the environment first, then ~/.qwen/.env or ~/.env. ~ is expanded, and the value is not trimmed. A relative QWEN_HOME names a different folder in each project, so Qwen Code is then left to connect by hand. A ~/.qwen folder without the CLI is also listed as "connect it by hand".
  • Settings stay safe. Qwen's settings.json may hold comments. Plannotator keeps every comment and adds or removes only its own entry. A file that is not valid JSON, or is read-only, is left byte for byte as it was, and the terminal says why. Add then remove gives the file back exactly. Uninstall uses the same edit. Every write is a new file renamed over the old one, keeping the file's mode. A CRLF file keeps CRLF, also next to a comment.
  • The file it was written in. Uninstall, Settings off and Settings → Agents itself use the file Plannotator recorded, not the one QWEN_HOME names now (QWEN_HOME set in one shell and not in another).
  • A different QWEN_HOME. A run that starts with a different QWEN_HOME touches neither file. It says once per change of home, in the terminal and the band, where Qwen Code is connected. A run from the matching shell does not reset that, and it removes the other shell's line from its band. Settings → Agents shows Qwen Code off there: "Connected in ~/.qwen/settings.json. Turn on to move it here." Only that switch moves it: the entry is added in the new home first, then removed from the old file. A relative QWEN_HOME, another program named qwen, or a new settings.json that does not parse or is read-only is refused, and nothing changes. If the old entry cannot be removed, the new one is taken back out, so only one entry is ever left. If even that fails, the message says so and the new entry is recorded too, so uninstall removes both. That second entry stays recorded when Settings later turns Qwen Code on or off, and uninstall keeps the record until every entry is gone. Once Settings has moved it, the band stops saying to move it. One folder spelled two ways (a symlink, /tmp and /private/tmp, a trailing slash) counts as one place. Plannotator never ignores case itself: on a disk that ignores case, the real path already gives the case on disk, and on a case-sensitive disk (Linux) qwen and Qwen are two folders. An entry removed by hand counts as removed outside, never as "connected in …".
  • Note. The Inbox note goes in ~/.qwen/QWEN.md, or in the file named by context.fileName. This reuses the Gemini logic.
  • Session identity. The stdio shim knows it runs inside Qwen Code from its MCP client name (qwen-cli-mcp-client-<server name>, or the older qwen-code-mcp-client) or from QWEN_CODE_SESSION_ID. On every call it reads Qwen's session record for its parent process, so after /clear or /resume the messages carry the new session id, not the stale one from the environment. With no record, the environment id is used. Qwen sessions can now be tagged and addressed. Replies do not wake Qwen yet; Inbox: replies go into running Qwen Code sessions #2274 adds that.

Why

The owner asked for full Qwen Code support in the Inbox (2026-10-27). This is the first half. The wake comes in #2274, stacked on this one.

Changes for existing users

  • People with qwen on PATH get a plannotator-inbox entry in ~/.qwen/settings.json (or $QWEN_HOME/settings.json) on their next plannotator inbox. PLANNOTATOR_INBOX_CONNECT=0 turns this off, and plannotator uninstall removes it.
  • Qwen Code appears in the Connected band and in the note dialog.
  • Known gap, older than this PR and the same for every agent: a connect run reads agents.json at its start and writes it at its end (up to 30 s later), so a Settings switch made in that window can be overwritten. Left as is.
  • Known gaps, left as is because each needs a failed rollback first (rare):
    • After an uninstall that removed the main entry but not the second one, the record still names the main entry, so Settings shows it as "removed in Qwen Code" and a reinstall does not reconnect it.
    • Settings on can record the second entry at the agent's own path when the person had removed it by hand. This does no harm: turning it off or uninstalling removes it once.

Credit

The "Connect an agent" panel for Qwen Code, its spaced-path test, and the idea of writing the settings.json entry instead of using qwen mcp add come from NonoElRobot's fork (commit c6a02a0), adapted to the current code. Thank you, NonoElRobot.

How it was tested

  • packages/server/inbox-agents-qwen.test.ts (40 tests) uses a fake qwen and a scratch home. It covers:
    • connecting, and keeping the person's other settings and every comment;
    • another server with ${GITHUB_TOKEN} keeps that text, not the token (this test fails on the old qwen mcp add code);
    • QWEN_HOME from the environment and from .env, and a relative one;
    • foreign and nightly versions, and qwen always run in the home folder;
    • an entry that is turned off and never added again;
    • a malformed settings.json and a read-only one, both left byte-identical;
    • uninstall removal, and a foreign entry left alone.
    • a QWEN_HOME that differs from the add: a run changes nothing and says it once; uninstall and Settings off act on the recorded file; Settings on moves it, and the old file comes back byte for byte;
    • a new home that cannot take the entry (trailing comma, read-only, relative QWEN_HOME, another qwen), and an old file that is broken or read-only: both files and the record stay as they were;
    • said once per change of home, said again after Settings acts, a symlinked or differently spelled home, and a hand removal;
    • homes that differ only by case: one folder when the disk ignores case, two on a case-sensitive disk. The test checks the disk and runs the matching case, so Linux CI runs the case-sensitive one. Both were run here, the second on a case-sensitive disk image;
    • a move whose rollback really fails (the new file turns read-only just before the rollback, through a test-only hook): said, and both entries recorded. That entry is kept through a later Settings on or off, and uninstall removes it. A second entry the person replaced with their own is theirs: it is never touched, uninstall names it, and it is no longer recorded. One they removed is nothing to do. A move out of a file whose entry the person changed never removes the second entry instead. uninstall.test.ts checks that a record is kept while one of its entries is left. It makes the removal fail with a duplicate key, which works on every platform, including Windows.
  • inbox-agents-settings.test.ts goes through the same HTTP route the UI uses. The row reads "elsewhere" and is off. Turning it on into a settings.json with a trailing comma is refused, and the old entry survives byte for byte. Once that file is fixed, turning it on moves the entry. agentsSettings.test.tsx (DOM) checks the line, and that the switch sends reach: true.
  • json-edit.test.ts covers JSON with comments: insert after a trailing comment (also in a CRLF file), cut next to one, a comment in a one-line empty object, refuse trailing commas.
  • Each new check was reverted once to confirm its test fails.
  • inbox-agents.test.ts checks that the shared JSON writer renames a new file over the old one and keeps its mode (an in-place write fails it).
  • inbox-agent-notes.test.ts covers the QWEN.md target, context.fileName and QWEN_HOME.
  • inbox-cli.test.ts covers the shim: the real client name, environment ids, a record swapped by /clear, bad and shared records, no record, precedence, and an end-to-end shim that follows /clear.
  • Typecheck is clean. The related suites and the Inbox DOM tests pass after merging main.
  • The real qwen was never run, and the real ~/.qwen was never read.
  • Not verified without a real Qwen: the exact --version line, and that the shim's parent process really is the Qwen process.

backnotprop and others added 2 commits October 27, 2026 20:26
Qwen Code joins the agents plannotator inbox connects: qwen mcp add -s user
(0.0.8 or later, QWEN_HOME honoured), its settings.json guarded so a run
never leaves it broken, the note in QWEN.md (or its context.fileName), the
harness panel and a glyph. The stdio shim sends as Qwen's current session,
read from Qwen's session registry on every call so /clear and /resume move it.

The connect snippet follows NonoElRobot's fork (c6a02a0).

Co-Authored-By: Claude <noreply@anthropic.com>
# Conflicts:
#	apps/hook/server/inbox-mcp-shim.ts
#	packages/core/inbox-types.ts
qwen mcp add and remove write every MCP server back with $VAR and ${VAR}
filled in, so a token kept in the environment landed in settings.json and
the old guard did not notice. Plannotator now edits the file itself, as
JSON with comments: only its own entry changes, every comment stays, and a
file that does not parse or is read-only is left as it is.

Qwen's session client is named qwen-cli-mcp-client-<server>, so a Qwen
session with no registry record now keeps its session id. qwen runs in the
home folder, QWEN_HOME is read from Qwen's .env files too and is not
trimmed, a relative QWEN_HOME is left to the person, and a record with a
malformed kind is read as a terminal session, as Qwen reads it.
- Uninstall, Settings off and the connect run check and remove the entry in
  the file the record names, not the one QWEN_HOME names now. A run or
  Settings on under a new QWEN_HOME moves the entry (out of the old file
  first); an old file that cannot be written keeps the entry and its record,
  said once. No false "removed outside".
- json-edit: a CRLF line comment keeps its \r on the line ending; a one-line
  empty object keeps its comment on add.
- Test: the shared JSON writer renames a new file over the old one and keeps
  its mode.
# Conflicts:
#	apps/hook/server/inbox-command.ts
A run that finds QWEN_HOME differs from the recorded home touches neither
settings file: it says once where Qwen Code is connected and that turning it
on in Settings → Agents moves it (report.elsewhere, also in the band).
Settings → Agents shows the recorded file.

Turning Qwen Code on in Settings under the new home adds the entry there
first (a relative QWEN_HOME, another program named qwen, a settings.json
that does not parse or is read-only: refused, nothing changed), then removes
the recorded one; when the old one cannot be removed (read-only, no longer
parses) the new entry is taken back out, so only the recorded one is left.
- Settings → Agents shows Qwen Code off, state "elsewhere", when its entry
  is in another QWEN_HOME: "Connected in <file>. Turn on to move it here."
  The switch sends on, which runs the add-first move with rollback.
- The "connected in another folder" line is said once per pair of places
  (a run from the matching shell keeps it said; Settings clears it), and a
  run whose QWEN_HOME matches drops the other shell's line from the band.
- Places compare by real path, case-folded on macOS and Windows; an
  absolute QWEN_HOME is normalized (a trailing slash).
- A run reads the recorded file first: an entry removed by hand there is
  removed outside, never "connected in …".
- A rollback that fails is said, and its entry kept on the record as a
  stray that uninstall removes.
…ugh Settings and uninstall

Two homes that differ only by case are two folders on a case-sensitive volume;
the real path already gives the case on disk where case is ignored. A stray
entry stays recorded when Settings turns Qwen Code on or off, uninstall keeps
an agent's record while one of its entries is left, and the band's move line
goes once Settings acts.
…d test runs on Windows

A second entry the person replaced with their own is theirs: named once, never
touched, no longer recorded; one they removed is nothing to do. The uninstall
test makes the main removal fail with a duplicate key instead of a read-only
folder, which Windows ignores. A move out of a changed file never takes the
stray entry instead (tested).
@backnotprop
backnotprop merged commit 15d047e into main Oct 28, 2026
32 checks passed
backnotprop added a commit to utpalsinghdev/plannotator that referenced this pull request Oct 31, 2026
Keeps both the host-recovery checks and the Qwen Code uninstall work
(backnotprop#2271-backnotprop#2275). The Qwen extension action uses the new references form.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant