* fix(cli): replace ✦ (U+2726) with ◆ (U+25C6) and add ∵/∴ thinking icons
- Replace ✦ with ◆ across all TUI components to fix East Asian
Ambiguous width misalignment (string-width reports 1 but terminals
render 2 columns).
- Use ∵ (because) during thinking streaming, ∴ (therefore) when
thinking is complete — matches the mathematical reasoning pair.
Co-Authored-By: Qwen Code <noreply@alibaba-inc.com>
* fix(cli): reduce STATUS_INDICATOR_WIDTH from 3 to 2 after ◆ replacement
◆ (U+25C6) is a consistent width-1 character across all terminals,
so the tool status indicator no longer needs the extra column that
was reserved for the ambiguous-width ✦ (U+2726).
Co-Authored-By: Qwen Code <noreply@alibaba-inc.com>
* fix(cli): catch missed ✦→◆ references in tests, docs, and scenarios
* fix(cli): shorten tmux spinner frames from 3 to 2 chars to match STATUS_INDICATOR_WIDTH=2
TMUX_SPINNER_FRAMES changed from ['. ', '.. ', '...'] to ['· ', '··']
to prevent 1-column overflow in tmux when STATUS_INDICATOR_WIDTH was
reduced from 3 to 2 after the ◆ replacement.
* revert(cli): keep narrow '.' tmux spinner frames instead of ambiguous '·'
'.' (U+002E) is Narrow (always 1 col), giving a guaranteed fixed-width
tmux spinner. '·' (U+00B7) is East Asian Ambiguous, so on ambiguous-width=2
terminals the frames become 3/4 cols and the spinner jitters — the opposite
of the "fixed-width frames" the surrounding comment promises.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
---------
Co-authored-by: Qwen Code <noreply@alibaba-inc.com>
Co-authored-by: pomelo.lcw <pomelo.lcw@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Graduate cron/loop from experimental opt-in to enabled-by-default.
Flip env var polarity from QWEN_CODE_ENABLE_CRON to
QWEN_CODE_DISABLE_CRON for users who want to opt out.
Update integration tests, docs, and VS Code schema accordingly.
* feat(cli,core): /compress accepts custom focus instructions
Extends /compress to take a trailing instruction string (max 2000 chars)
that is passed through tryCompressChat → tryCompress → CompressOptions
and appended to the compression side-query system prompt as an
"Additional Instructions:" block. Mirrors claude-code /compact <text>.
Empty / whitespace-only args fall back to the prior behaviour.
* test(core): cover /compress customInstructions + PreCompact hook merge
* feat(core): restore plan-mode + subagent snapshot after compaction
Adds two optional ComposePostCompactOptions:
- planModeActive: when true, emits a <plan-mode-active> reminder so the
post-compact agent does not forget destructive tools remain gated.
- runningSubagents: when non-empty, emits a <background-tasks> block
listing each running/paused task by id, status, and description.
Both blocks are spliced into the merged user attachment Content before
file/image restorations. XML-significant characters in descriptions are
escaped to prevent an adversarial subagent description from closing the
wrapper tag.
Wiring at the call site arrives in the next commit.
* feat(core): wire plan-mode + subagent snapshot into post-compact attachments
ChatCompressionService.compress now passes:
- planModeActive: derived from config.getApprovalMode() === ApprovalMode.PLAN
- runningSubagents: filtered from BackgroundTaskRegistry to agent-kind tasks
in 'running' or 'paused' state
into composePostCompactHistory. Adds collectActiveSubagents() helper that
returns [] when the registry is absent so older SDK consumers without it
keep working.
* fix(core,test): use HookSystem.getAdditionalContext accessor; smoke-test /compress
- chatCompressionService: read PreCompact hook output via
result.getAdditionalContext() — the wrapper returns DefaultHookOutput
(not the raw AggregatedHookResult), and the accessor sanitises < / >
consistently with every other call-site in the repo.
- Test mocks now return a DefaultHookOutput-shaped stub via a tiny
makeHookOutput() helper rather than the aggregator shape.
- New integration smoke test for `/compress focus on the scientist
mentioned` exercising the args plumbing end-to-end.
* test(core): update client.test.ts to match new tryCompressChat signature
* feat(core): cap subagent snapshot at 30 entries with overflow notice
Code-review follow-up. Pathological sessions with hundreds of
backgrounded agents could otherwise produce a multi-KB block. Newest 30
rows are kept (highest startTime); older ones are summarised on a
trailing line so the model knows the snapshot is partial.
* fix(core,test): flatten subagent description newlines; type-safe ApprovalMode in tests
Second code-review pass found two real issues:
1. Subagent descriptions containing `\n`/`\r`/`\t` would split across
multiple lines inside the `<background-tasks>` bullet list, letting
the second line read as a sibling row (or worse, an orphan paragraph
between two `- [..]` entries). Flatten whitespace before the slice so
each task stays on one line.
2. The plan-mode wiring tests passed `'plan'` / `'auto-edit'` as plain
strings instead of `ApprovalMode.PLAN` / `ApprovalMode.AUTO_EDIT`.
Source code compares against the enum; a future enum value change
would have silently passed the tests. Import and use the enum.
* fix(core): move PreCompact hook fire after length-guard; align plan-mode tool names
Round 3 code review surfaced two issues:
1. PreCompact hook fired BEFORE the curatedHistory.length < 2 guard, so
a single-message session would trigger any hook side effects
(transcript dump, external notification, etc.) and then NOOP. Move
the hook fire below the guard so hooks only run when compression is
actually possible. New regression test asserts the contract.
2. PLAN_MODE_REMINDER_TEXT said "shell mutations" but the real qwen-code
tool is `run_shell_command` (tool-names.ts:26). Use the verbatim
tool names so a future rename is grep-discoverable.
* refactor(core): share escapeXml, drive plan-mode names from ToolNames, extract reminder builder
Code-review follow-ups on post-compact attachments:
- Replace the local 3-char escapeForXmlText with the shared 5-char
escapeXml from utils/xml.ts, and apply it to the subagent id and
status as well as the description. Subagent ids derive from a
user-configurable subagentConfig.name, so an unescaped `<`/`&` there
could close the <background-tasks> wrapper or forge sibling markup.
- PLAN_MODE_REMINDER_TEXT now interpolates ToolNames.WRITE_FILE / .EDIT /
.SHELL instead of retyping the names, so a future rename stays in sync.
- Extract buildStateReminderParts() as the single source of truth for the
plan-mode + subagent reminder blocks, used by both composePostCompactHistory
and (next commit) its catch-fallback so the two paths can't drift.
* fix(core): scope subagent snapshot to backgrounded tasks; restore reminders on fallback; cap hook context
Three code-review fixes in the compaction service:
- collectActiveSubagents now also requires isBackgrounded — foreground
agents are the parent's synchronously-awaited tool call and don't belong
in a <background-tasks> roster. Mirrors getRunningBackgroundCount.
- The composePostCompactHistory catch-fallback re-applies the plan-mode +
subagent reminders via the shared buildStateReminderParts (pure, no I/O),
so a restoration failure no longer silently drops plan-mode enforcement
and the subagent roster.
- The PreCompact hook's additionalContext is capped at
MAX_HOOK_INSTRUCTIONS_CHARS before entering the side-query prompt,
closing the unbounded-input hole the user-text cap was meant to prevent.
The fallback and hook-cap fixes have RED-verified regression tests.
* feat(cli): warn on /compress instruction truncation; fix integration-test pty typing
- /compress now emits an INFO notice (interactive), a stream message (acp),
and a prefixed return message (non-interactive) when the instruction
string exceeds MAX_COMPRESS_INSTRUCTIONS_CHARS, so the silent 2000-char
clip is no longer invisible to the user.
- Annotate the three `ptyProcess.onData((data: string) => ...)` callbacks
in the compress integration test to clear the TS7006 implicit-any the
reviewer's typecheck flagged (fixed all three occurrences, not only the
one inside this PR's diff).
Cron prompts are rendered as `● Cron: …` notifications since the
distinct Cron message type was added; the interactive tests still
waited for the legacy `> …` user-message line and timed out.
- Add IS_SANDBOX environment detection
- Skip cron interactive tests when running in Docker sandbox
- Move timeout options inline with test definitions
- Add comment explaining flaky test workaround
This prevents flaky test failures in the Docker sandbox environment while preserving test coverage in local development.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Move non-interactive tests to cli/, interactive tests to interactive/.
Add cron-interactive.test.ts wrapping terminal-capture E2E in vitest.
Update npm scripts and release workflow for new directory layout.