Merge branch 'master' into worktree-ws-more

This commit is contained in:
imccyu
2026-07-26 11:20:05 +08:00
committed by GitHub
114 changed files with 593 additions and 1499 deletions

View File

@@ -3,8 +3,7 @@
"compilerOptions": {
"composite": true,
"rootDir": "src",
"outDir": "lib/types",
"tsBuildInfoFile": "../../../.typecheck/cli-demo.tsbuildinfo"
"outDir": "lib/types"
},
"include": ["src/**/*.ts"],
"references": [

View File

@@ -6,4 +6,4 @@ Plan mode is one logged, per-agent collaboration state. It is a single **product
|---|---|---|
| `plan-mode/` | `plan/mode` vocabulary + fold, boundary-applied state, the `plan:policy` guidance section, `/plan [message]` entry and `/plan off` exit, and the model-facing `exit_plan_mode` review tool | `ctx.planMode` |
The active state is a pure function of the session log, so resume and fork restore it without extra machinery. The deployment supplies plan instructions through Cordis config, while `exit_plan_mode` stays registered when planning is inactive to keep the request tool catalog stable. Interactive adapters use the plugin-owned `/plan` command; sandbox mode and approval policy remain independent enforcement settings. Design: [plan-mode Agent Note](../../.agents/notes/implemented/feature/2026-07-07-plan-mode.md) and [plan-specific state simplification](../../.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md).
The active state is a pure function of the session log, so resume and fork restore it without extra machinery. The deployment supplies plan instructions through Cordis config, while `exit_plan_mode` stays registered when planning is inactive to keep the request tool catalog stable. Interactive adapters use the plugin-owned `/plan` command; sandbox mode and approval policy remain independent enforcement settings. Design: [plan-specific collaboration state](../../.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md).

View File

@@ -29,7 +29,7 @@ The TUI consumes the plugin-owned `/plan` command; other front doors may drive t
`section` is required and non-empty. Unknown keys fail at load. The package does not accept arbitrary named modes, tool filters, sandbox settings, or approval policy.
Design: [plan-mode Agent Note](../../../.agents/notes/implemented/feature/2026-07-07-plan-mode.md) and [plan-specific state simplification](../../../.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md).
Design: [plan-specific collaboration state](../../../.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md).
## Model Experience

View File

@@ -15,8 +15,7 @@
* The exit tool remains registered while plan mode is inactive so crossing a
* boundary changes only the prompt section, not the request tool catalog.
*
* Agent Notes:
* - .agents/notes/implemented/feature/2026-07-07-plan-mode.md
* Agent Note:
* - .agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md
*
* @module @deepseek-ai/dsh-plan-mode

View File

@@ -6,7 +6,7 @@ Local Linux/macOS `node-pty` backend for `ctx.pty`; loading it on another platfo
The plugin injects `pty`, `sandbox`, and `sandboxPolicy`, then registers the configured backend type (`shell`). `danger-full-access` starts the shell directly; confined modes wrap the exact shell argv through `ctx.sandbox`. The effective session mode is resolved at spawn. A change to a different effective mode is rejected before its `sandbox/mode` event commits while that owner has an open PTY or a spawn in progress; the fence is attached to the exact owner and therefore outlives a local-provider reload that retains existing sessions. Wait for creation to settle and close the sessions before changing modes, so a terminal opened with wider access cannot survive a downgrade.
Linux readiness combines a foreground-verified private bash prompt marker, foreground-process-group syscall inspection, silence fallback, and absolute timeout. macOS uses the verified prompt marker plus silence/timeout because it has no `/proc` syscall surface. A marker is not ready until printable prompt text arrives, including when the OSC marker and `PS1` are split across data callbacks; when bash prints the marker before the kernel publishes its return to the foreground process group, polling retains the candidate until bash ownership is observable or the ordinary silence bound expires. An interactive child that inherits `PROMPT_COMMAND` therefore cannot suppress inferred-idle readiness until the absolute timeout. Unrecognized or unreadable process state is never a positive exact-idle signal. During unpublished startup, a fallback requires observed output; zero-output silence cannot publish an empty session, and timeout rejects the spawn. Cancellation closes the unpublished shell and rejects with the caller's exact abort reason even when its foreground process group is not observable yet; if that close fails, `PtyBackendCleanupError` separately preserves the cleanup failure for registry disposal. Incomplete terminal-control sequences are bounded by `maxReadBytes` and discarded through their terminator after crossing that limit; a trailing carriage return is carried across callbacks so split CRLF becomes one newline.
Linux readiness combines a foreground-verified private bash prompt marker, foreground-process-group syscall inspection, silence fallback, and absolute timeout. macOS uses the verified prompt marker plus silence/timeout because it has no `/proc` syscall surface. A marker is not ready until printable prompt text arrives, including when the OSC marker and `PS1` are split across data callbacks; when bash prints the marker before the kernel publishes its return to the foreground process group, polling retains the candidate through one final poll after the ordinary silence bound so a coincident handoff can win. An interactive child that inherits `PROMPT_COMMAND` therefore cannot suppress inferred-idle readiness until the absolute timeout. Unrecognized or unreadable process state is never a positive exact-idle signal. During unpublished startup, a fallback requires observed output; zero-output silence cannot publish an empty session, and timeout rejects the spawn. Cancellation closes the unpublished shell and rejects with the caller's exact abort reason even when its foreground process group is not observable yet; if that close fails, `PtyBackendCleanupError` separately preserves the cleanup failure for registry disposal. Incomplete terminal-control sequences are bounded by `maxReadBytes` and discarded through their terminator after crossing that limit; a trailing carriage return is carried across callbacks so split CRLF becomes one newline.
Send cancellation resolves the current foreground process group and delivers a real `SIGINT`; it never emulates interruption by writing `\x03`, so raw-mode programs remain cancellable. Close sends `SIGTERM` to descendants, waits, then sends `SIGKILL` to the union of captured survivors and newly scanned descendants so reparenting cannot hide a process from teardown. It verifies that every retained identity is gone or, on Linux, a non-executing zombie before stopping the shell; zombie entries are quiescent and are reaped as the shell exits. A survivor failure does not cache a permanently rejected close; a later close retries the teardown.

View File

@@ -331,8 +331,11 @@ export class LocalPtySession implements PtyBackendSession {
// A prompt candidate can race bash's foreground handoff, but an interactive
// child also inherits PROMPT_COMMAND. Silence therefore remains the bound
// on waiting for shell ownership instead of letting a child marker suppress
// readiness until the absolute timeout.
if (startupHasOutput && Date.now() - this.lastOutputAt >= this.config.idleSilenceMs) {
// readiness until the absolute timeout. One final poll lets a foreground
// handoff coincident with that boundary win before the fallback settles.
const idleFor = Date.now() - this.lastOutputAt
const handoffGrace = this.promptSeen ? this.config.pollIntervalMs : 0
if (startupHasOutput && idleFor >= this.config.idleSilenceMs && idleFor - this.config.idleSilenceMs >= handoffGrace) {
this.settleActive('inferred_idle')
return
}

View File

@@ -298,7 +298,7 @@ describe('LocalPtySession readiness and output', () => {
void operation.done.then(() => { settled = true })
inspector.pgid = 789
terminal.emitData('\x1b]133;D;0\x07dsh> ')
await vi.advanceTimersByTimeAsync(40)
await vi.advanceTimersByTimeAsync(50)
expect(settled).toBe(false)
inspector.pgid = 456