mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-10-23 07:19:52 +00:00
|
Some checks are pending
E2E Tests / E2E Test - macOS - shard 1/2 (push) Blocked by required conditions
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Lint & Static (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / web-shell E2E Smoke (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Integration Tests (no-AK, No Sandbox) (push) Blocked by required conditions
Qwen Code CI / Integration Tests (CLI, No Sandbox) (push) Blocked by required conditions
E2E Tests / Build for E2E (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 2/2 (push) Blocked by required conditions
E2E Tests / E2E Interactive - OpenTUI renderer (bun) (push) Blocked by required conditions
E2E Tests / channel-plugin E2E (nightly) (push) Blocked by required conditions
E2E Tests / cron-interactive E2E (nightly) (push) Blocked by required conditions
E2E Tests / web-shell Browser Regression (push) Waiting to run
Managed Agent gate / Select the Managed Agent tier (push) Waiting to run
Managed Agent gate / SDK Java () (push) Blocked by required conditions
Managed Agent gate / Managed Agent gate (push) Blocked by required conditions
SDK Java Contract Open PRs / Re-check open PRs against main (push) Waiting to run
SDK Java Flyway Open PRs / Re-check open PRs against main (push) Waiting to run
Verify Landlock helper / Native Linux CLI boundary (push) Waiting to run
Verify Landlock helper / Native Windows settings and streams (push) Waiting to run
Verify Landlock helper / arm64 (push) Waiting to run
Verify Landlock helper / x64 (push) Waiting to run
* feat(serve): carry a scheduled-task gate through the daemon and Web Shell
A gated scheduled task (PR 1a) could only be created from the TUI, and the
TUI refused a gate on a `sessionMode: "current"` task because nothing on the
daemon side knew the field. This threads `gate` through every path a task can
take in the daemon and surfaces it in the Web Shell.
- `cron_create` accepts a gate on a current-session task; the request reaches
the daemon over the ACP bridge (`gate` must be a non-empty string within the
2048-char cap, else `invalidParams`) and the daemon wraps the task with
`withTaskGate`, so the downgrade fuse (`condition`) stays in step.
- REST: POST accepts `gate` (400 `invalid_gate`, 400 `gate_requires_recurring`
on a one-shot), PATCH sets, replaces or clears it (`null`/`''`) and resets
the gate status when the command changed; a gate edit on a legacy guarded
task is refused with 409 because its `condition` is its own fail-closed
marker, not a fuse. Task views carry `gate` and `gateStatus`.
- A manual run of a gated task has no scheduler in the loop, so the run prompt
asks the model to run the gate itself first, under the usual tool approval,
and stop when it prints nothing (`buildGatedManualRunPrompt`); per-run
manual runs and the Web Shell's persistent-task "run now" both use it.
- Capability `scheduled_task_gate` (`{ since: 'v1' }`) advertises the field;
the Web Shell shows the gate editor, card line and status only when the
daemon lists it.
- `gateCommandError` moves the shape check into core so the tool, the bridge
and the routes agree on what a gate may be. Its `&` check is
`backgroundsAnyPart`, so the routes refuse a backgrounded wrapper script
(`nohup bash -c './probe.sh &'`) exactly as `cron_create` does.
Tests cover the tool, the bridge, the ACP session, the routes (POST/PATCH/run
and the existing-session creator), the run prompt and the dialog. Docs: the
protocol capability table, the user guide's daemon section and the design
doc's status.
* test(serve): list scheduled_task_gate in the integration capability envelope
* fix(core): keep a current-session gate confirmation and reset a replaced gate's streak
A gate confirmed through the dialog for a `sessionMode: "current"` task was
blocked at its first fire: the daemon writes the task file and this
session's scheduler installs the job on its next reload, with nothing to
inherit the confirmation from. `cron_create` now hands it to the scheduler
(`approveGate`), which marks the job when it is loaded or when the reload
installs it, and only while the task's gate is that command.
A reload also kept the in-memory gate status when the file had none, even
after the gate command changed, so a replaced gate continued the old
failure streak and its first failure was counted without a notice. The
in-memory status now carries over only for an unchanged gate.
* refactor(serve): single-source the gate cap in the bridge and drop barrel churn
* fix(core): keep a replaced gate's late result off the new command's status
The gate status write matched the task by id only. When a PATCH replaced
the gate and dropped its status, the old command could still settle before
this session's watcher reloaded the task, and its write put the old
command's streak back onto the new gate: the new command's first failure
was then counted without a notice. The status is now written only while
the task's gate on disk is the command that produced it; the run record of
that fire is still appended.
* fix(core): keep the tick's gate status write on its own command
The tick persists a gated fire's stamp together with the job's in-memory
gate status, matched to the task by id only. When a PATCH replaced the
gate and dropped its status, a tick landing before this session's watcher
reload wrote the old command's streak back onto the new gate, so the new
command's first failure was counted without a notice. The tick now keeps
the command with the status and applies it only while the task's gate on
disk is still that command.
* fix(serve): send the gate only when edited and tighten what a gate may be
Web Shell:
- An edit sends `gate` only when the field changed it. The route reads any
`gate` key as a gate edit, which a legacy guarded task refuses (409), so an
untouched field made prompt and cron edits fail on those tasks.
- A one-shot edit shows no gate field and sends no gate (the daemon refuses
a gate there and would discard the whole save).
- The field is a textarea, so a stored multi-line gate is not flattened on
the next save.
- The card's status line leads with the outcome and reason while the gate
fails or is blocked, and the hint states what the runner really does: exit
0 with output delivers, empty stdout skips, a non-zero exit reports stderr,
and unattended runs need a rule, a read-only command or auto/yolo.
Core: `gateCommandError` refuses a command of only comments (it runs as
nothing, so every fire would be quiet) and reads a trailing `\&` as a
literal while `\\&` still backgrounds.
Docs: `scheduled_task_gate` is unconditional, so it leaves the conditional
capability table (the docs contract test counted it there) for its own
paragraph with the recurring and legacy refusals, the `blocked` outcome and
its preconditions, and the per-method behaviour of an older daemon.
Tests cover each change, plus the shared validator's length branch, a gate
on a create bound to a caller session, and `approveGate` refusing a loaded
job whose command differs.
* fix(core): read a gate's comment scope from its shell structure
The comment-only check ran on the permission text, which joins a
wrapper's script with whatever follows the wrapper: `bash -c '# x' &&
printf 'ready\n'` read as one comment line and was refused, though the
command after the wrapper runs and prints. `runsOnlyComments` now checks a
plain command line by line and a wrapper by its own script, recursively,
counting anything after the script as a command.
* fix(serve): close audit gaps in gate validation, confirmation and edits
- A daemon that predates gated tasks drops a requested gate on a
current-session create. The daemon now echoes the stored gate through
the bridge to the child, and `cron_create` removes a task that came back
without its gate and refuses, instead of leaving a task that runs at
every slot.
- `stop()` keeps pending gate confirmations: a turn cancelled before the
reload that installs the task stops and re-enables the scheduler.
- PATCH refuses a gated one-shot only when the edit makes it; a task
already in that state keeps its other edits and can clear the gate.
- `backgroundsAnyPart` ignores comments and counts the backslashes before a
trailing `&` at every level, wrapper scripts included: an `&` in a
comment no longer refuses a gate, a quote in a comment no longer hides a
real `&`, and `bash -c 'echo \\&'` is refused like its top-level form.
- Docs: PATCH clears a gate with null or "", the one-shot refusal is per
edit, the `&` check is lexical, and binaries up to v0.19.8 ignore the
downgrade fuse.
* fix: read gate scripts with and without comments and close audit edge cases
- Read every level of a gate twice, with comments stripped and raw, and
refuse an `&` either reading finds: a `#` misread inside `$(...)`,
`$'...'` or an escaped wrapper quote can no longer hide a real `&`.
The check errs toward refusing, documented in the design, protocol and
user docs.
- Treat plain words after a wrapper script as its positional parameters
when checking for a comment-only gate.
- Name the ungated task left behind when removing it after a daemon
dropped the gate fails.
- Re-sending an unchanged gate or recurring flag no longer counts as
making a new one-shot gate.
- Web Shell: allow clearing the gate of a one-shot gated task, refuse
to submit a typed gate once the daemon stops advertising gates, drop
the client-side length cap, and fix the gate field comment.
* fix(core): fail a quiet gate whose background part still holds its output
The lexical `&` check cannot follow every spelling: a quote it misreads
inside "$(...)" or $'...', a script handed to eval, piped into a shell,
passed as `bash -c -- ...` or put together from several quoted pieces
all get past it, and such a gate settles quiet at every fire.
Back it with the runner: the shell service now marks a streamStdout
result whose output pipes were still open when the post-exit drain ran
out (`outputHeldAfterExit`), and a gate that exits 0 with nothing on
stdout in that state is reported as failed instead of quiet. A gate
that printed in the foreground is still delivered.
The docs now call the lexical check best-effort and describe the
backstop. Also moves approveGate's doc comment back onto it.
* fix(core): judge a gate's backstop on stdout alone and stop what it left behind
- The shell service now reads the stdout end on streamStdout runs, and
reports `stdoutHeldAfterExit` only when stdout itself was still open as
the post-exit drain ran out. A helper that silenced its stdout but kept
stderr no longer turns every quiet fire into a failure.
- When a gate's stdout is still held, the runner kills the gate's process
group: the gate is over by contract, and each fire would leave one more
process behind.
- Quiet is judged on the stdout the session would see: output of escape
sequences alone (`tput sgr0`) no longer wakes the session with an empty
gate-output block.
- The manual-run instructions tell the model that a command which puts
part of itself in the background counts as a failure, matching the
scheduled fire.
- Docs: the backstop's stdout-only rule, the stopped process group, and
the bwrap shell execution sandbox, whose PID namespace ends a background
part with the command so only the lexical check applies there.
* fix(core): stop a gate's leftovers gently, keep cut and escaped output, align manual runs
- Leftovers: the gate's process group gets SIGTERM first, so a part that
holds a lock can release it, then SIGKILL 200 ms later. Docs and the
comment now say what is out of reach: a part in its own process group
or session, and Windows.
- Quiet: a cut output is never quiet, since what was kept may be noise in
front of what was cut. Sanitizing strips escape sequences line by line,
so a title sequence left open cannot swallow the lines after it.
- Durations run on the monotonic clock.
- Manual runs: a gate that printed nothing because it put part of itself
in the background is the failure; one that printed is followed, as the
scheduled fire delivers it.
- Tests: the leftover-stop test keeps process.kill stubbed and its timer
faked for the whole case, so no run signals a real process group.
* fix(core): judge manual gate runs on stdout, defang open structural tags, kill leftovers at exit
- Manual runs: the model runs the gate as `{ <gate>\n} 2>/dev/null`,
since a scheduled fire reads only stdout; a warning on stderr no longer
makes a manual run follow the task where the scheduled fire is quiet.
- Defang: a structural tag name is defanged however the tag goes on,
`</gate-output >`, a tab, attributes or the line's end, all of which
can still close the data block in XML.
- Leftovers: a process that exits before the SIGKILL is due sends it on
its way out, so a part that ignored SIGTERM cannot outlive a headless
run that ends with its last gate.
- A held stdout with nothing visible fails even when the output was cut.
- The leftover test restores process.kill and real timers in `finally`.
* fix(core): refuse a gate that carries a NUL character
A process argument cannot carry a NUL, so such a gate passed every check
and then failed to start at every fire. gateCommandError now refuses it,
which answers 400 invalid_gate on the routes and refuses it in
cron_create.
* fix: send a manual gate run as written, and defang only what parses as a tag
- Manual runs send the gate exactly as written again: the `{ … } 2>/dev/null`
wrapper kept the session's permission rules from matching it (allow
rules asked again, deny rules no longer applied, the read-only path
refused it) and broke under cmd.exe and PowerShell. The prompt instead
says only standard output counts and warnings on standard error do not.
- The structural tag defang matches a name followed by `>`, `/>`, an
attribute or the end of the line, so `</gate-output >` is still defanged
while `Promise<Result | null>` and `a <result && b` in monitored text no
longer gain an invisible character.
- A held stdout whose cut output showed nothing says so, instead of
claiming nothing was printed.
* fix(core): defang the gate's own wrappers in every tag form, keep Monitor's defang as it was
The gate block is not XML-escaped, so its defang is the whole defence, and
the shared Monitor regex only matched `<name>`: `</gate-output x>`,
`</gate-output/ >` or a name split by an invisible character still closed
the block. Widening the shared regex either missed forms or defanged
ordinary Monitor text (`Promise<Result>`), so monitor.ts is back to its
base, and the gate's sanitizer strips the invisible characters that could
hide a wrapper and defangs `gate-output` / `gate-stderr` whatever follows
the name.
Docs: a manual run applies `Bash(...)` rules and the read-only check to
the command as written; the scheduled fire checks it as a `Monitor`
command, and a `Monitor(...)` rule does not cover the shell call.
* fix(core): write every < in gate output as < instead of chasing tag spellings
Defanging the wrappers' names kept missing forms: a joiner or bidi mark in
the name, attributes, a tag spelled in tag characters, and a strip list of
invisible characters both leaked and damaged ordinary text (Persian ZWNJ).
The gate block is data the model reads, so every `<` in it is now written
`<` and its tag characters (U+E0000–E007F) are dropped: no tag can open
or close the block however it is spelled. The fire text and the failure
notice say so, and the docs describe it.
* fix(core): read a newline after a wrapper's script as a command separator
A gate such as `bash -c '# setup'` followed by a newline and a command was
refused as only a comment: the wrapper parse trimmed the newline away, so
the next command read as the script's positional words. The suffix keeps
its leading newline now (other readers trim it themselves).
* fix: let gate rules see a line after a wrapper script, and refuse any gate edit the daemon would drop
- Security: a gate's permission check read `bash -c 'echo hi'` followed by
a newline and `rm …` as `echo hi rm …` (the script and its positional
words), while the gate runs the text as written and the shell runs `rm`
as the next command; deny rules missed it and the read-only check
allowed it. The Monitor normalization now keeps that line on a line of
its own in the safety command (only stricter for Monitor, whose spawned
command is unchanged).
- A wrapper comment followed by a line of only comments is refused as
only a comment again.
- Web Shell: with gates no longer advertised, any change to a task's gate
(clearing one included) blocks the save with an error, instead of a
request without the `gate` key that kept the old command.
- Docs: the rule fix and the §4.2 example texts.
* fix(core): refuse a command after a shell wrapper's script in a gate
The permission rules read a wrapper's script and what follows it as one
command line, which is what Monitor spawns, while a gate runs the text as
written: a command after a newline, `;`, `&&` or `|` runs on its own.
Joining the next line with a newline in the safety command still let a
script ending in `\`, an open quote, a here-document start, or a blank
other than space before the newline hide that command from deny rules and
the read-only check. A gate now keeps every command inside the wrapper's
script; the Monitor normalization is back to its base.
Web Shell: a gate change made before the daemon stopped advertising gates
stays visible, so the change the save refuses can be undone.
* test(core): type the gate refusal table so the spread rows check
* fix(core): refuse a gate the Monitor normalization would rewrite
The permission rules check a gate as the Monitor normalization rebuilds
it: a shell wrapper's script unquoted and joined into one line, which is
what Monitor spawns. A gate runs as written, and the two kept differing in
new ways: adjacent quotes end the script early, a separator glued to it
hides the next command, a trailing `\`, an open quote or a command after
it does the same, and deny rules and the read-only check then judged a
command other than the one that ran.
Instead of chasing each spelling, a gate must now be a command the
normalization leaves exactly as written: a shell wrapper (`bash -c '…'`,
also behind env assignments) is refused. A gate already runs in a shell,
so the wrapper adds nothing. This replaces the narrower check on commands
after the script.
* fix(core): name the command the rules would check when refusing a gate, and drop stale wrapper notes
A plain command the Monitor normalization misreads as a wrapper
(`bash /opt/probe.sh -c x` reads as `x`) was refused as "wrapped in a
shell" with nothing to say why; the error now shows what the permission
rules would check. The design docs no longer list wrapper spellings that
are refused at creation as getting past the background check, the zh-CN
paragraph loses the leftover from the earlier rule, and the risks name
the PermissionManager's own blind spots that a gate inherits from the
shell tool.
* fix(core): keep the refused gate's checked command whole when cut, and word the refusal as a reading, not a mismatch
The command named in a wrapper refusal is cut at 80 units without
splitting a surrogate pair, and the error says the rules would check it
"not the command as written" rather than "instead of what runs", which
was untrue for a plain wrapper. The gateCommandError doc lists every
refusal again. In the design docs a gate gets the decision of a monitor
call (not of a shell command), and the acceptance criterion on older
binaries matches the fuse: v0.19.9 on never fire a gated task.
* fix(core): report a gate again when its failure streak changes kind
Blocked and failed fires share one streak, and only its first fire was
reported. A gate created over REST is blocked until its rule is added;
the blocked notice says further blocked fires are counted silently until
the gate runs again, yet when it then ran and failed nothing was said.
A notice now opens a streak and follows each change between blocked and
failed. The envelope comment and design docs also stop claiming more
than `<` escaping gives: look-alike brackets stay as text in the block.
* fix(core): report a gate blocked for another reason, and store the quiet fire that ends a failure streak
A gate blocked by a deny rule and then, once the rule was removed, only
unapproved was never told the rule to add: both fires were 'blocked', so
the second counted silently. A blocked fire now notifies when its reason
changes too.
A quiet fire's status only reached the file with the next tick persist,
so a restart in between reloaded the old failure streak and counted the
next failure into it without a notice. The quiet fire that ends a streak
is now written at once.
The tool descriptions, the Web Shell hint, the protocol and user docs and
the design docs say when a failure is reported in the same terms.
* fix(core): book a gated fire at the slot it ran for
The run record and the job handed to the consumer re-read the live job's
lastFiredAt when the gate settled; a manual run or a later slot stamped
while the gate ran moved it on, so the run landed under another fire's
timestamp and a per-run session's annotation matched the wrong entry.
Both now use the fire's own slot, which the status already did.
Also corrects the design docs on how often a gated task rewrites the
tasks file, and two scheduler comments the new notice and persistence
rules had made stale.
* fix: title a gated per-run session from its task, book runs in time order, call a gated slot checked
A gated fire hands the session the gate's delivered text as its prompt,
and a per-run fire titled its fresh session from that text instead of
the task prompt a manual run uses; the fire now carries the task prompt
too. A gated run is booked when its gate settles, under its own slot,
which can be older than a manual run booked meanwhile, so the run ring
now keeps time order. And the Web Shell card read a gated task's slot as
its last run, though a quiet gate ran no prompt: it now reads as checked.
* fix: refuse a gate holding an unpaired surrogate, and say "1 failure"
Bash receives the gate as UTF-8, which cannot carry an unpaired UTF-16
surrogate, so such a gate ran as U+FFFD rather than the text the
permission rules checked; it is now refused with the other malformed
gates, as main refuses it for a hosted shell command. The Web Shell gate
line also said "1 failures" on a gate's first failure.
* docs(core): name where a session drops a delivered gate output with its fire
* fix(core): never start a gate's stderr tail with half a character, and name where a session drops a gate output
The failure notice keeps the last 2048 UTF-16 units of stderr, a cut that
could leave the low half of a surrogate pair at its start; the notice is
a model prompt, and an unpaired surrogate breaks its serialization. The
cut now drops that half. The design docs describe the places a session
drops a fire, and a delivered gate output with it, as examples rather
than a closed list: a user prompt, a cancel or the token-limit breaker
clear queued fires too.
* fix(core): drop half a character at the stderr tail's start on a short tail too
The runner keeps a rolling stderr buffer cut in UTF-16 units, which can
also leave the low half of a pair at its start; the previous fix only
dropped that half when the tail itself was cut. It is now dropped either
way.
* docs: say the rule a blocked gate names may not cover a compound command
The rule is the one Monitor's "always allow" would save, which for a
compound command (a test of a command substitution, an if, a heredoc)
may leave a part unmatched, so the gate stays blocked; such a probe
belongs in a script.
|
||
|---|---|---|
| .. | ||
| _daemon-benchmark-helpers.ts | ||
| _daemon-harness.ts | ||
| _daemon-perf-report.ts | ||
| _first-output-benchmark.test.ts | ||
| _first-output-benchmark.ts | ||
| _prompt-latency-policy.test.ts | ||
| _prompt-latency-policy.ts | ||
| acp-cron.test.ts | ||
| acp-integration.test.ts | ||
| advisor-tool.test.ts | ||
| cron-tools.test.ts | ||
| daemon-invocation-context.test.ts | ||
| daemon-workflow-ancestor-trust.test.ts | ||
| daemon-workflow-intent.test.ts | ||
| edit.test.ts | ||
| extensions-install.test.ts | ||
| external-context-mem0-daemon-delete.test.ts | ||
| external-context-mem0-daemon-write.test.ts | ||
| file-system.test.ts | ||
| gen-ai-telemetry.test.ts | ||
| headless-workflow-skill.test.ts | ||
| hosted-harness-process.test.ts | ||
| hosted-latency-baseline.test.ts | ||
| json-output.test.ts | ||
| json-schema.test.ts | ||
| list_directory.test.ts | ||
| mcp_server_cyclic_schema.test.ts | ||
| mock-acp-typecheck.test.ts | ||
| monitor.test.ts | ||
| notebook-edit.test.ts | ||
| qwen-config-dir.test.ts | ||
| qwen-daemon-first-output-benchmark.test.ts | ||
| qwen-daemon-loadtest.test.ts | ||
| qwen-daemon-startup-benchmark.test.ts | ||
| qwen-daemon-vs-cli-benchmark.test.ts | ||
| qwen-serve-baseline.test.ts | ||
| qwen-serve-channel-workers.test.ts | ||
| qwen-serve-client-mcp.test.ts | ||
| qwen-serve-live-journal-recovery.test.ts | ||
| qwen-serve-routes.test.ts | ||
| qwen-serve-standalone-concurrency.test.ts | ||
| qwen-serve-streaming.test.ts | ||
| qwen-serve-web-shell-live-journal-recovery.test.ts | ||
| read_many_files.test.ts | ||
| run_shell_command.test.ts | ||
| save_memory.test.ts | ||
| settings-migration.test.ts | ||
| simple-mcp-server.test.ts | ||
| sleep-interception.test.ts | ||
| stdin-context.test.ts | ||
| subagent-hook-owner.test.ts | ||
| telemetry.test.ts | ||
| todo_write.test.ts | ||
| tool-hook-context.test.ts | ||
| tool-hook-updated-input.test.ts | ||
| tool-search.test.ts | ||
| utf-bom-encoding.test.ts | ||
| workflow-ancestor-discovery.test.ts | ||
| workflow-intent.test.ts | ||
| write_file.test.ts | ||