@oh-my-pi/pi-coding-agent 17.1.7 → 17.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +127 -0
- package/dist/{CHANGELOG-5k4dq4g6.md → CHANGELOG-be1f2t8h.md} +127 -0
- package/dist/cli.js +9554 -12379
- package/dist/template-8vdv6xb4.js +1671 -0
- package/dist/template-f8wx9vfn.css +1355 -0
- package/dist/template-qat058wr.html +55 -0
- package/dist/tool-views.generated-jdfmzwmn.js +35 -0
- package/dist/types/advisor/advise-tool.d.ts +0 -7
- package/dist/types/advisor/runtime.d.ts +8 -11
- package/dist/types/advisor/transcript-recorder.d.ts +15 -0
- package/dist/types/async/job-manager.d.ts +11 -0
- package/dist/types/auto-thinking/classifier.d.ts +9 -2
- package/dist/types/cleanse/agent.d.ts +19 -0
- package/dist/types/cleanse/balance.d.ts +7 -0
- package/dist/types/cleanse/checkers.d.ts +13 -0
- package/dist/types/cleanse/index.d.ts +16 -0
- package/dist/types/cleanse/loop.d.ts +16 -0
- package/dist/types/cleanse/parsers.d.ts +13 -0
- package/dist/types/cleanse/progress.d.ts +14 -0
- package/dist/types/cleanse/types.d.ts +64 -0
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/session-picker.d.ts +14 -7
- package/dist/types/collab/guest.d.ts +21 -2
- package/dist/types/commands/cleanse.d.ts +23 -0
- package/dist/types/commands/launch.d.ts +6 -0
- package/dist/types/config/settings-schema.d.ts +63 -8
- package/dist/types/cursor-bridge-tools.d.ts +54 -0
- package/dist/types/cursor.d.ts +193 -8
- package/dist/types/edit/edit-clipboard.d.ts +19 -0
- package/dist/types/edit/hashline/diff.d.ts +9 -1
- package/dist/types/edit/index.d.ts +8 -1
- package/dist/types/edit/streaming.d.ts +8 -1
- package/dist/types/export/html/index.d.ts +2 -0
- package/dist/types/extensibility/extensions/runner.d.ts +19 -2
- package/dist/types/extensibility/extensions/types.d.ts +25 -1
- package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +27 -1
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +2 -0
- package/dist/types/internal-urls/local-protocol.d.ts +3 -2
- package/dist/types/launch/terminal-output-worker-client.d.ts +5 -0
- package/dist/types/launch/terminal-output-worker-protocol.d.ts +14 -0
- package/dist/types/launch/terminal-output-worker.d.ts +1 -0
- package/dist/types/lsp/client.d.ts +2 -0
- package/dist/types/main.d.ts +2 -0
- package/dist/types/mcp/manager.d.ts +33 -4
- package/dist/types/modes/components/codex-reset-fireworks.d.ts +51 -0
- package/dist/types/modes/components/read-tool-group.d.ts +12 -0
- package/dist/types/modes/components/session-selector.d.ts +6 -0
- package/dist/types/modes/components/status-line/component.d.ts +23 -0
- package/dist/types/modes/components/tool-execution.d.ts +16 -7
- package/dist/types/modes/components/usage-row.d.ts +2 -0
- package/dist/types/modes/controllers/mcp-command-controller.d.ts +26 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
- package/dist/types/modes/interactive-mode.d.ts +8 -5
- package/dist/types/modes/rpc/rpc-client.d.ts +7 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +16 -0
- package/dist/types/modes/theme/theme.d.ts +12 -1
- package/dist/types/modes/types.d.ts +2 -1
- package/dist/types/sdk.d.ts +1 -0
- package/dist/types/session/agent-session-events.d.ts +2 -0
- package/dist/types/session/agent-session-types.d.ts +36 -1
- package/dist/types/session/agent-session.d.ts +4 -2
- package/dist/types/session/async-job-delivery.d.ts +8 -0
- package/dist/types/session/claude-session-store.d.ts +13 -0
- package/dist/types/session/codex-session-store.d.ts +14 -0
- package/dist/types/session/credential-pin.d.ts +58 -0
- package/dist/types/session/foreign-session-import.d.ts +15 -0
- package/dist/types/session/foreign-session-jsonl.d.ts +9 -0
- package/dist/types/session/foreign-session-store.d.ts +23 -0
- package/dist/types/session/model-controls.d.ts +3 -3
- package/dist/types/session/session-advisors.d.ts +47 -2
- package/dist/types/session/session-entries.d.ts +19 -1
- package/dist/types/session/session-handoff.d.ts +4 -1
- package/dist/types/session/session-loader.d.ts +2 -0
- package/dist/types/session/session-maintenance.d.ts +1 -1
- package/dist/types/session/session-manager.d.ts +21 -0
- package/dist/types/session/session-tools.d.ts +25 -1
- package/dist/types/session/turn-recovery.d.ts +10 -4
- package/dist/types/system-prompt.d.ts +2 -1
- package/dist/types/task/executor.d.ts +11 -2
- package/dist/types/task/isolation-ownership.d.ts +34 -0
- package/dist/types/task/render.d.ts +7 -0
- package/dist/types/thinking.d.ts +18 -6
- package/dist/types/tools/acp-bridge.d.ts +28 -5
- package/dist/types/tools/browser/cmux/cmux-tab.d.ts +1 -2
- package/dist/types/tools/browser/tab-worker.d.ts +1 -4
- package/dist/types/tools/grep.d.ts +17 -1
- package/dist/types/tools/hub/launch.d.ts +5 -1
- package/dist/types/tools/index.d.ts +4 -1
- package/dist/types/tools/path-utils.d.ts +19 -0
- package/dist/types/tools/read.d.ts +2 -0
- package/dist/types/tools/xdev.d.ts +2 -1
- package/dist/types/tui/output-block.d.ts +5 -5
- package/dist/types/utils/changelog.d.ts +19 -0
- package/dist/types/utils/git.d.ts +10 -0
- package/dist/types/utils/jj.d.ts +11 -4
- package/dist/types/web/search/types.d.ts +2 -2
- package/package.json +16 -12
- package/scripts/bundle-dist.ts +3 -1
- package/src/advisor/advise-tool.ts +0 -11
- package/src/advisor/runtime.ts +138 -24
- package/src/advisor/transcript-recorder.ts +56 -0
- package/src/async/job-manager.ts +30 -7
- package/src/auto-thinking/classifier.ts +48 -10
- package/src/cleanse/agent.ts +226 -0
- package/src/cleanse/balance.ts +79 -0
- package/src/cleanse/checkers.ts +996 -0
- package/src/cleanse/index.ts +190 -0
- package/src/cleanse/loop.ts +51 -0
- package/src/cleanse/parsers.ts +726 -0
- package/src/cleanse/progress.ts +50 -0
- package/src/cleanse/prompts/assignment.md +47 -0
- package/src/cleanse/types.ts +72 -0
- package/src/cli/args.ts +6 -0
- package/src/cli/flag-tables.ts +2 -0
- package/src/cli/gallery-fixtures/fs.ts +25 -0
- package/src/cli/session-picker.ts +34 -17
- package/src/cli/ttsr-cli.ts +19 -1
- package/src/cli/update-cli.ts +3 -0
- package/src/cli/usage-cli.ts +29 -4
- package/src/cli/worktree-cli.ts +28 -11
- package/src/cli-commands.ts +1 -0
- package/src/cli.ts +11 -1
- package/src/collab/guest.ts +37 -4
- package/src/collab/host.ts +1 -0
- package/src/commands/cleanse.ts +45 -0
- package/src/commands/launch.ts +6 -0
- package/src/config/model-registry.ts +103 -25
- package/src/config/settings-schema.ts +65 -6
- package/src/config/settings.ts +25 -0
- package/src/cursor-bridge-tools.ts +81 -0
- package/src/cursor.ts +463 -12
- package/src/discovery/claude-plugins.ts +144 -34
- package/src/edit/edit-clipboard.ts +23 -0
- package/src/edit/hashline/diff.ts +49 -10
- package/src/edit/hashline/execute.ts +38 -6
- package/src/edit/hashline/filesystem.ts +27 -3
- package/src/edit/index.ts +12 -2
- package/src/edit/renderer.ts +4 -4
- package/src/edit/streaming.ts +17 -2
- package/src/export/html/index.ts +17 -10
- package/src/export/html/template.js +1 -1
- package/src/extensibility/extensions/runner.ts +70 -0
- package/src/extensibility/extensions/types.ts +30 -0
- package/src/extensibility/legacy-pi-ai-shim.ts +46 -1
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +12 -13
- package/src/internal-urls/local-protocol.ts +3 -2
- package/src/internal-urls/mcp-protocol.ts +4 -1
- package/src/launch/broker.ts +14 -4
- package/src/launch/terminal-output-worker-client.ts +53 -0
- package/src/launch/terminal-output-worker-protocol.ts +11 -0
- package/src/launch/terminal-output-worker.ts +23 -0
- package/src/lsp/client.ts +18 -3
- package/src/lsp/index.ts +4 -0
- package/src/lsp/render.ts +1 -1
- package/src/main.ts +127 -56
- package/src/mcp/manager.ts +120 -18
- package/src/modes/acp/acp-agent.ts +27 -18
- package/src/modes/acp/acp-event-mapper.ts +38 -4
- package/src/modes/components/chat-transcript-builder.ts +30 -11
- package/src/modes/components/codex-reset-fireworks.ts +369 -0
- package/src/modes/components/read-tool-group.ts +159 -17
- package/src/modes/components/session-selector.ts +19 -3
- package/src/modes/components/settings-selector.ts +9 -1
- package/src/modes/components/status-line/component.ts +381 -54
- package/src/modes/components/tool-execution.ts +78 -31
- package/src/modes/components/usage-row.ts +10 -5
- package/src/modes/controllers/event-controller.ts +26 -4
- package/src/modes/controllers/mcp-command-controller.ts +68 -3
- package/src/modes/controllers/selector-controller.ts +94 -36
- package/src/modes/controllers/tan-command-controller.ts +14 -0
- package/src/modes/interactive-mode.ts +67 -64
- package/src/modes/prompt-action-autocomplete.ts +5 -3
- package/src/modes/rpc/rpc-client.ts +18 -0
- package/src/modes/rpc/rpc-mode.ts +16 -1
- package/src/modes/rpc/rpc-types.ts +11 -0
- package/src/modes/theme/theme.ts +151 -6
- package/src/modes/types.ts +2 -1
- package/src/modes/utils/ui-helpers.ts +38 -21
- package/src/prompts/goals/guided-goal-interview.md +41 -6
- package/src/prompts/system/auto-thinking-difficulty.md +4 -2
- package/src/prompts/system/plan-mode-active.md +2 -2
- package/src/prompts/system/system-prompt.md +3 -0
- package/src/prompts/system/vibe-mode-active.md +4 -1
- package/src/prompts/system/xdev-mount-notice.md +1 -1
- package/src/prompts/tools/bash.md +15 -17
- package/src/prompts/tools/browser.md +1 -0
- package/src/prompts/tools/checkpoint.md +1 -1
- package/src/prompts/tools/glob.md +7 -6
- package/src/prompts/tools/goal.md +1 -1
- package/src/prompts/tools/grep.md +7 -6
- package/src/sdk.ts +178 -15
- package/src/session/agent-session-events.ts +1 -0
- package/src/session/agent-session-types.ts +40 -1
- package/src/session/agent-session.ts +330 -179
- package/src/session/async-job-delivery.ts +8 -0
- package/src/session/claude-session-store.ts +426 -0
- package/src/session/codex-session-store.ts +673 -0
- package/src/session/credential-pin.ts +93 -0
- package/src/session/foreign-session-import.ts +52 -0
- package/src/session/foreign-session-jsonl.ts +29 -0
- package/src/session/foreign-session-store.ts +26 -0
- package/src/session/model-controls.ts +15 -7
- package/src/session/session-advisors.ts +142 -23
- package/src/session/session-entries.ts +21 -1
- package/src/session/session-handoff.ts +20 -3
- package/src/session/session-history-format.ts +31 -6
- package/src/session/session-loader.ts +25 -9
- package/src/session/session-maintenance.ts +59 -11
- package/src/session/session-manager.ts +61 -0
- package/src/session/session-tools.ts +107 -5
- package/src/session/settings-stream-fn.ts +1 -0
- package/src/session/turn-recovery.ts +97 -51
- package/src/slash-commands/builtin-registry.ts +136 -5
- package/src/system-prompt.ts +3 -2
- package/src/task/executor.ts +17 -4
- package/src/task/isolation-ownership.ts +106 -0
- package/src/task/render.ts +20 -4
- package/src/task/worktree.ts +8 -0
- package/src/thinking.ts +29 -10
- package/src/tools/acp-bridge.ts +52 -8
- package/src/tools/ask.ts +3 -3
- package/src/tools/ast-edit.ts +9 -2
- package/src/tools/browser/cmux/cmux-tab.ts +9 -14
- package/src/tools/browser/tab-worker.ts +12 -35
- package/src/tools/browser.ts +7 -2
- package/src/tools/checkpoint.ts +0 -13
- package/src/tools/gh-renderer.ts +3 -3
- package/src/tools/grep.ts +64 -8
- package/src/tools/hub/launch.ts +10 -15
- package/src/tools/index.ts +38 -5
- package/src/tools/path-utils.ts +88 -0
- package/src/tools/read.ts +3 -0
- package/src/tools/write.ts +44 -3
- package/src/tools/xdev.ts +39 -14
- package/src/tui/code-cell.ts +4 -4
- package/src/tui/output-block.ts +25 -8
- package/src/utils/changelog.ts +144 -4
- package/src/utils/git.ts +61 -14
- package/src/utils/jj.ts +19 -13
- package/src/utils/shell-snapshot-fn-env.sh +5 -2
- package/src/web/search/providers/codex.ts +34 -0
- package/src/web/search/render.ts +2 -2
- package/src/web/search/types.ts +1 -1
- package/dist/types/goals/guided-setup.d.ts +0 -30
- package/src/goals/guided-setup.ts +0 -171
- package/src/prompts/goals/guided-goal-system.md +0 -33
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const BAR_WIDTH = 16;
|
|
2
|
+
|
|
3
|
+
/** Minimal output contract used by the interactive cleanse progress reporter. */
|
|
4
|
+
export interface CleanseProgressOutput {
|
|
5
|
+
isTTY?: boolean;
|
|
6
|
+
write(text: string): boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Renders completed repair workers on one transient terminal line. */
|
|
10
|
+
export interface CleanseProgressReporter {
|
|
11
|
+
readonly interactive: boolean;
|
|
12
|
+
start(total: number): void;
|
|
13
|
+
complete(): void;
|
|
14
|
+
finish(): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Create the TTY-only worker completion reporter used by `omp cleanse`. */
|
|
18
|
+
export function createCleanseProgressReporter(output: CleanseProgressOutput = process.stdout): CleanseProgressReporter {
|
|
19
|
+
const interactive = output.isTTY === true;
|
|
20
|
+
let total = 0;
|
|
21
|
+
let completed = 0;
|
|
22
|
+
let rendered = false;
|
|
23
|
+
|
|
24
|
+
const render = (): void => {
|
|
25
|
+
if (!interactive || total === 0) return;
|
|
26
|
+
const ratio = Math.min(completed / total, 1);
|
|
27
|
+
const filled = Math.round(ratio * BAR_WIDTH);
|
|
28
|
+
const bar = `${"█".repeat(filled)}${"░".repeat(BAR_WIDTH - filled)}`;
|
|
29
|
+
output.write(`\rRepairing [${bar}] ${completed}/${total}\x1b[K`);
|
|
30
|
+
rendered = true;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
interactive,
|
|
35
|
+
start(nextTotal) {
|
|
36
|
+
total = Math.max(nextTotal, 0);
|
|
37
|
+
completed = 0;
|
|
38
|
+
render();
|
|
39
|
+
},
|
|
40
|
+
complete() {
|
|
41
|
+
completed = Math.min(completed + 1, total);
|
|
42
|
+
render();
|
|
43
|
+
},
|
|
44
|
+
finish() {
|
|
45
|
+
if (!rendered) return;
|
|
46
|
+
output.write("\n");
|
|
47
|
+
rendered = false;
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<critical>
|
|
2
|
+
- You MUST fix every assigned diagnostic at its root cause.
|
|
3
|
+
- You MUST stay inside the write scope below.
|
|
4
|
+
- You NEVER suppress valid diagnostics to make checks pass.
|
|
5
|
+
- You NEVER delegate or spawn another agent.
|
|
6
|
+
- You NEVER run project-wide checks or formatters; the orchestrator reruns them.
|
|
7
|
+
</critical>
|
|
8
|
+
|
|
9
|
+
# Assignment
|
|
10
|
+
|
|
11
|
+
Repair wave {{wave}}, worker {{worker}}.
|
|
12
|
+
|
|
13
|
+
## Write scope
|
|
14
|
+
|
|
15
|
+
{{write_scope}}
|
|
16
|
+
|
|
17
|
+
Read related code freely. Project-level diagnostics MAY require the smallest necessary edit outside named files; use `hub` before touching a peer-owned file.
|
|
18
|
+
|
|
19
|
+
## Diagnostics
|
|
20
|
+
|
|
21
|
+
{{diagnostics}}
|
|
22
|
+
|
|
23
|
+
## Checker commands
|
|
24
|
+
|
|
25
|
+
{{checker_commands}}
|
|
26
|
+
|
|
27
|
+
## Concurrent peer ownership
|
|
28
|
+
|
|
29
|
+
{{peer_assignments}}
|
|
30
|
+
|
|
31
|
+
<workflow>
|
|
32
|
+
1. Inspect every assigned location and its local context.
|
|
33
|
+
2. Fix causes, not emitted text or checker configuration.
|
|
34
|
+
3. Preserve behavior unless a diagnostic proves it wrong.
|
|
35
|
+
4. Run only targeted checks for assigned files when available.
|
|
36
|
+
5. Re-read changed sections and resolve every assigned item.
|
|
37
|
+
</workflow>
|
|
38
|
+
|
|
39
|
+
<completeness>
|
|
40
|
+
- Done means every assigned diagnostic has a concrete fix.
|
|
41
|
+
- Blocked items MUST name the missing prerequisite in the final result.
|
|
42
|
+
- Final output MUST list changed files and unresolved items.
|
|
43
|
+
</completeness>
|
|
44
|
+
|
|
45
|
+
<critical>
|
|
46
|
+
You MUST leave peer-owned files untouched and complete the full assignment.
|
|
47
|
+
</critical>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/** Severity normalized across checker-specific output formats. */
|
|
2
|
+
export type CleanseSeverity = "error" | "warning" | "info";
|
|
3
|
+
|
|
4
|
+
/** One actionable problem reported by a project checker. */
|
|
5
|
+
export interface CleanseDiagnostic {
|
|
6
|
+
checker: string;
|
|
7
|
+
file?: string;
|
|
8
|
+
line?: number;
|
|
9
|
+
column?: number;
|
|
10
|
+
endLine?: number;
|
|
11
|
+
endColumn?: number;
|
|
12
|
+
code?: string;
|
|
13
|
+
severity: CleanseSeverity;
|
|
14
|
+
message: string;
|
|
15
|
+
suggestion?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Result metadata for one checker invocation. */
|
|
19
|
+
export interface CleanseCheckResult {
|
|
20
|
+
id: string;
|
|
21
|
+
label: string;
|
|
22
|
+
language: string;
|
|
23
|
+
cwd: string;
|
|
24
|
+
command: string;
|
|
25
|
+
exitCode: number | null;
|
|
26
|
+
diagnostics: CleanseDiagnostic[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Checker discovery result omitted because its required executable was unavailable. */
|
|
30
|
+
export interface SkippedCleanseCheck {
|
|
31
|
+
label: string;
|
|
32
|
+
language: string;
|
|
33
|
+
reason: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Aggregate diagnostics from every checker selected for a project. */
|
|
37
|
+
export interface CleanseDiagnosticReport {
|
|
38
|
+
checks: CleanseCheckResult[];
|
|
39
|
+
diagnostics: CleanseDiagnostic[];
|
|
40
|
+
skipped: SkippedCleanseCheck[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** All diagnostics attached to one file, which scheduling never splits across agents. */
|
|
44
|
+
export interface CleanseFileIssues {
|
|
45
|
+
file?: string;
|
|
46
|
+
diagnostics: CleanseDiagnostic[];
|
|
47
|
+
weight: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** One weighted, file-disjoint workload handed to a subagent. */
|
|
51
|
+
export interface CleanseAssignment {
|
|
52
|
+
index: number;
|
|
53
|
+
groups: CleanseFileIssues[];
|
|
54
|
+
weight: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Settled result from one cleanse subagent. */
|
|
58
|
+
export interface CleanseAgentOutcome {
|
|
59
|
+
name: string;
|
|
60
|
+
success: boolean;
|
|
61
|
+
output: string;
|
|
62
|
+
error?: string;
|
|
63
|
+
resolvedModel?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Final state after one bounded repair wave and verification pass. */
|
|
67
|
+
export interface CleanseLoopResult {
|
|
68
|
+
status: "clean" | "stalled" | "cancelled";
|
|
69
|
+
waves: number;
|
|
70
|
+
report: CleanseDiagnosticReport;
|
|
71
|
+
outcomes: CleanseAgentOutcome[];
|
|
72
|
+
}
|
package/src/cli/args.ts
CHANGED
|
@@ -44,6 +44,8 @@ export interface Args {
|
|
|
44
44
|
advisor?: boolean;
|
|
45
45
|
continue?: boolean;
|
|
46
46
|
resume?: string | true;
|
|
47
|
+
fromClaude?: boolean;
|
|
48
|
+
fromCodex?: boolean;
|
|
47
49
|
help?: boolean;
|
|
48
50
|
version?: boolean;
|
|
49
51
|
mode?: Mode;
|
|
@@ -227,6 +229,10 @@ export function parseArgs(inputArgs: string[], extensionFlags?: Map<string, { ty
|
|
|
227
229
|
result.alias = arg.slice("--alias=".length);
|
|
228
230
|
} else if (arg === "--continue" || arg === "-c") {
|
|
229
231
|
result.continue = true;
|
|
232
|
+
} else if (arg === "--from-claude") {
|
|
233
|
+
result.fromClaude = true;
|
|
234
|
+
} else if (arg === "--from-codex") {
|
|
235
|
+
result.fromCodex = true;
|
|
230
236
|
} else if (arg === "--no-session") {
|
|
231
237
|
result.noSession = true;
|
|
232
238
|
} else if (arg === "--no-tools") {
|
package/src/cli/flag-tables.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// biome-ignore-all lint/suspicious/noTemplateCurlyInString: sample source-code strings (read fixtures) intentionally contain literal ${...}.
|
|
2
2
|
// Gallery fixtures for the filesystem tools (read, write, glob).
|
|
3
|
+
import type { Usage } from "@oh-my-pi/pi-ai";
|
|
3
4
|
import { ReadToolGroupComponent } from "../../modes/components/read-tool-group";
|
|
4
5
|
import type { GalleryFixture, GalleryFixtureState, GalleryResult } from "./types";
|
|
5
6
|
|
|
@@ -47,6 +48,15 @@ const groupedReadDelimitedPath = groupedReadTargets.join(",");
|
|
|
47
48
|
const groupedReadRepeatedFile = "packages/coding-agent/src/task/render.ts";
|
|
48
49
|
const groupedReadRepeatedRanges = `${groupedReadRepeatedFile}:507-605,1070-1194,1210-1240,1270-1274`;
|
|
49
50
|
|
|
51
|
+
const GROUPED_READ_USAGE: Usage = {
|
|
52
|
+
input: 2400,
|
|
53
|
+
output: 113,
|
|
54
|
+
cacheRead: 103_000,
|
|
55
|
+
cacheWrite: 0,
|
|
56
|
+
totalTokens: 105_513,
|
|
57
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
58
|
+
};
|
|
59
|
+
|
|
50
60
|
function textResult(text: string, details?: unknown, isError?: boolean): GalleryResult {
|
|
51
61
|
return { content: [{ type: "text", text }], details, isError };
|
|
52
62
|
}
|
|
@@ -81,6 +91,13 @@ function renderReadGroupFixtureState(state: GalleryFixtureState, width: number,
|
|
|
81
91
|
false,
|
|
82
92
|
"read-delimited",
|
|
83
93
|
);
|
|
94
|
+
component.attachUsage(
|
|
95
|
+
["read-delimited"],
|
|
96
|
+
GROUPED_READ_USAGE,
|
|
97
|
+
5300,
|
|
98
|
+
2200,
|
|
99
|
+
new Date(2026, 6, 28, 21, 5, 47).getTime(),
|
|
100
|
+
);
|
|
84
101
|
|
|
85
102
|
if (state === "error") {
|
|
86
103
|
component.updateResult(
|
|
@@ -88,10 +105,18 @@ function renderReadGroupFixtureState(state: GalleryFixtureState, width: number,
|
|
|
88
105
|
false,
|
|
89
106
|
"read-ranges",
|
|
90
107
|
);
|
|
108
|
+
component.attachUsage(
|
|
109
|
+
["read-ranges"],
|
|
110
|
+
GROUPED_READ_USAGE,
|
|
111
|
+
4700,
|
|
112
|
+
1900,
|
|
113
|
+
new Date(2026, 6, 28, 21, 5, 52).getTime(),
|
|
114
|
+
);
|
|
91
115
|
return component.render(width);
|
|
92
116
|
}
|
|
93
117
|
|
|
94
118
|
component.updateResult(textResult("Read four render.ts ranges."), false, "read-ranges");
|
|
119
|
+
component.attachUsage(["read-ranges"], GROUPED_READ_USAGE, 4700, 1900, new Date(2026, 6, 28, 21, 5, 52).getTime());
|
|
95
120
|
return component.render(width);
|
|
96
121
|
}
|
|
97
122
|
|
|
@@ -6,16 +6,26 @@ import type { SessionInfo } from "../session/session-listing";
|
|
|
6
6
|
import { SessionManager } from "../session/session-manager";
|
|
7
7
|
import { FileSessionStorage } from "../session/session-storage";
|
|
8
8
|
|
|
9
|
+
/** Presentation and capability controls for the standalone session picker. */
|
|
10
|
+
export interface SessionPickerOptions {
|
|
11
|
+
allSessions?: SessionInfo[];
|
|
12
|
+
title?: string;
|
|
13
|
+
scopeLabel?: string | false;
|
|
14
|
+
showCwd?: boolean;
|
|
15
|
+
allowDelete?: boolean;
|
|
16
|
+
allowGlobalScope?: boolean;
|
|
17
|
+
historySearch?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
9
20
|
/**
|
|
10
21
|
* Show the TUI session selector and return the selected session, or null if
|
|
11
|
-
* cancelled.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* is loaded lazily via `SessionManager.listAll`.
|
|
22
|
+
* cancelled. The default OMP picker supports deletion, transcript-history
|
|
23
|
+
* search, and an all-projects scope; foreign import pickers disable those
|
|
24
|
+
* source-owned capabilities.
|
|
15
25
|
*/
|
|
16
26
|
export async function selectSession(
|
|
17
27
|
sessions: SessionInfo[],
|
|
18
|
-
options
|
|
28
|
+
options: SessionPickerOptions = {},
|
|
19
29
|
): Promise<SessionInfo | null> {
|
|
20
30
|
const { promise, resolve } = Promise.withResolvers<SessionInfo | null>();
|
|
21
31
|
const ui = new TUI(new ProcessTerminal());
|
|
@@ -26,11 +36,13 @@ export async function selectSession(
|
|
|
26
36
|
// session-list prefix never sees. Best-effort: a missing/locked history.db
|
|
27
37
|
// must not break the picker.
|
|
28
38
|
let historyMatcher: ((query: string) => string[]) | undefined;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
39
|
+
if (options.historySearch !== false) {
|
|
40
|
+
try {
|
|
41
|
+
const history = HistoryStorage.open();
|
|
42
|
+
historyMatcher = (query: string) => history.matchingSessionIds(query);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
logger.warn("History storage unavailable for session ranking", { error: String(error) });
|
|
45
|
+
}
|
|
34
46
|
}
|
|
35
47
|
|
|
36
48
|
const showSelector = () => {
|
|
@@ -58,16 +70,21 @@ export async function selectSession(
|
|
|
58
70
|
}
|
|
59
71
|
},
|
|
60
72
|
{
|
|
61
|
-
onDelete:
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
73
|
+
onDelete:
|
|
74
|
+
options.allowDelete === false
|
|
75
|
+
? undefined
|
|
76
|
+
: async (session: SessionInfo) => {
|
|
77
|
+
await storage.deleteSessionWithArtifacts(session.path);
|
|
78
|
+
return true;
|
|
79
|
+
},
|
|
66
80
|
historyMatcher,
|
|
67
|
-
loadAllSessions: () => SessionManager.listAll(storage),
|
|
68
|
-
allSessions: options
|
|
81
|
+
loadAllSessions: options.allowGlobalScope === false ? undefined : () => SessionManager.listAll(storage),
|
|
82
|
+
allSessions: options.allSessions,
|
|
69
83
|
getTerminalRows: () => ui.terminal.rows,
|
|
70
84
|
fillHeight: true,
|
|
85
|
+
title: options.title,
|
|
86
|
+
scopeLabel: options.scopeLabel,
|
|
87
|
+
showCwd: options.showCwd,
|
|
71
88
|
},
|
|
72
89
|
);
|
|
73
90
|
return selector;
|
package/src/cli/ttsr-cli.ts
CHANGED
|
@@ -94,12 +94,18 @@ interface TestReport {
|
|
|
94
94
|
evaluated: number;
|
|
95
95
|
triggered: RuleMatchDetail[];
|
|
96
96
|
notTriggered: RuleMatchDetail[];
|
|
97
|
+
/**
|
|
98
|
+
* Set when a file path was supplied but the match source was inferred as
|
|
99
|
+
* `text` (extension absent from {@link SOURCE_FILE_EXT}), so callers can
|
|
100
|
+
* surface why a source file was evaluated against a prose context.
|
|
101
|
+
*/
|
|
102
|
+
inferenceNote?: string;
|
|
97
103
|
}
|
|
98
104
|
|
|
99
105
|
const STDIN_MARKER = "-";
|
|
100
106
|
/** Extensions treated as source files for default tool-context inference. */
|
|
101
107
|
const SOURCE_FILE_EXT =
|
|
102
|
-
/^\.(ts|tsx|js|jsx|mjs|cjs|rs|py|go|java|kt|swift|c|cc|cpp|h|hpp|rb|php|lua|css|scss|html|json|ya?ml|toml|md|mdc)$/i;
|
|
108
|
+
/^\.(ts|tsx|js|jsx|mjs|cjs|rs|py|go|java|kt|swift|c|cc|cpp|h|hpp|rb|php|lua|css|scss|html|json|ya?ml|toml|md|mdc|cs|razor|cshtml|fs|fsx|vb|sh|bash|sql|zig|dart|scala|ex|exs|proto|tf)$/i;
|
|
103
109
|
|
|
104
110
|
const BINARY_PROBE_BYTES = 8192;
|
|
105
111
|
const DEFAULT_MAX_SCAN_BYTES = 5 * 1024 * 1024;
|
|
@@ -342,6 +348,14 @@ async function runTest(args: TtsrTestArgs, json: boolean, cwd: string): Promise<
|
|
|
342
348
|
args.source ?? (filePath && SOURCE_FILE_EXT.test(path.extname(filePath)) ? "tool" : "text");
|
|
343
349
|
const tool = args.tool ?? (source === "tool" ? "edit" : undefined);
|
|
344
350
|
|
|
351
|
+
// A supplied source file whose extension is unknown falls through to the
|
|
352
|
+
// text (prose) context, where tool-scoped rules can never match. Surface
|
|
353
|
+
// that so a false negative reads as a context mismatch, not a bad regex.
|
|
354
|
+
const inferenceNote =
|
|
355
|
+
!args.source && filePath && source === "text"
|
|
356
|
+
? `inferred --source text from '${path.extname(filePath) || filePath}' (not in the source-file extension set); pass --source tool --tool edit to evaluate tool-scoped rules`
|
|
357
|
+
: undefined;
|
|
358
|
+
|
|
345
359
|
const context: TtsrMatchContext = {
|
|
346
360
|
source,
|
|
347
361
|
toolName: tool,
|
|
@@ -373,6 +387,7 @@ async function runTest(args: TtsrTestArgs, json: boolean, cwd: string): Promise<
|
|
|
373
387
|
evaluated: rules.length,
|
|
374
388
|
triggered,
|
|
375
389
|
notTriggered,
|
|
390
|
+
inferenceNote,
|
|
376
391
|
};
|
|
377
392
|
|
|
378
393
|
if (json) {
|
|
@@ -390,6 +405,9 @@ function renderTestReport(report: TestReport, verbose: boolean, isolated: boolea
|
|
|
390
405
|
`${chalk.bold("TTSR test")} — source=${chalk.cyan(ctxLabel)}${pathLabel} snippet=${chalk.dim(`${report.snippetBytes}b`)}\n`,
|
|
391
406
|
);
|
|
392
407
|
process.stdout.write(`${chalk.dim(` "${report.snippetPreview}"`)}\n\n`);
|
|
408
|
+
if (report.inferenceNote) {
|
|
409
|
+
process.stdout.write(`${chalk.yellow(`note: ${report.inferenceNote}`)}\n\n`);
|
|
410
|
+
}
|
|
393
411
|
|
|
394
412
|
if (report.triggered.length === 0) {
|
|
395
413
|
process.stdout.write(`${chalk.red("No rules triggered.")} (evaluated ${report.evaluated})\n`);
|
package/src/cli/update-cli.ts
CHANGED
|
@@ -231,6 +231,9 @@ export async function downloadVerifiedBinary(options: VerifiedBinaryDownloadOpti
|
|
|
231
231
|
await fs.promises.chmod(options.targetPath, 0o755);
|
|
232
232
|
} catch (err) {
|
|
233
233
|
await unlinkIfExists(options.targetPath);
|
|
234
|
+
if (isTimeoutError(err)) {
|
|
235
|
+
throw new Error("Timed out downloading release binary after 15 minutes", { cause: err });
|
|
236
|
+
}
|
|
234
237
|
throw err;
|
|
235
238
|
}
|
|
236
239
|
}
|
package/src/cli/usage-cli.ts
CHANGED
|
@@ -560,9 +560,34 @@ function formatReloginDeadline(
|
|
|
560
560
|
* automatically (refresh failure, upstream invalidation). Rows the user
|
|
561
561
|
* replaced or deleted deliberately are lifecycle noise, not lost capacity.
|
|
562
562
|
*/
|
|
563
|
-
function isActionableDisable(summary: DisabledCredentialSummary): boolean {
|
|
563
|
+
function isActionableDisable(summary: DisabledCredentialSummary, activeAccounts: UsageAccountIdentity[] = []): boolean {
|
|
564
564
|
if (summary.type !== "oauth") return false;
|
|
565
|
-
|
|
565
|
+
if (/^(replaced by|deleted by user)/i.test(summary.cause)) return false;
|
|
566
|
+
|
|
567
|
+
// Do not display tombstone if there is an active account for the same provider
|
|
568
|
+
// matching the same identity (email, accountId, or org).
|
|
569
|
+
const summaryEmail = summary.email?.toLowerCase();
|
|
570
|
+
const summaryAccountId = summary.accountId?.toLowerCase();
|
|
571
|
+
const summaryOrgId = summary.orgId?.toLowerCase();
|
|
572
|
+
|
|
573
|
+
const matchesActive = activeAccounts.some(account => {
|
|
574
|
+
if (account.provider !== summary.provider) return false;
|
|
575
|
+
|
|
576
|
+
const accountEmail = account.email?.toLowerCase();
|
|
577
|
+
const accountAccountId = account.accountId?.toLowerCase();
|
|
578
|
+
const accountOrgId = account.orgId?.toLowerCase();
|
|
579
|
+
|
|
580
|
+
// If email or accountId match, it's the same identity
|
|
581
|
+
if (summaryEmail && accountEmail && summaryEmail === accountEmail) return true;
|
|
582
|
+
if (summaryAccountId && accountAccountId && summaryAccountId === accountAccountId) return true;
|
|
583
|
+
|
|
584
|
+
// Fallback: if orgId matches and neither email nor accountId contradicts
|
|
585
|
+
if (summaryOrgId && accountOrgId && summaryOrgId === accountOrgId) return true;
|
|
586
|
+
|
|
587
|
+
return false;
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
return !matchesActive;
|
|
566
591
|
}
|
|
567
592
|
|
|
568
593
|
/** Human-sized disable cause: the upstream `error_description` when embedded, else the first clause. */
|
|
@@ -610,7 +635,7 @@ export function formatUsageBreakdown(
|
|
|
610
635
|
}
|
|
611
636
|
const disabledByProvider = new Map<string, DisabledCredentialSummary[]>();
|
|
612
637
|
for (const summary of disabled) {
|
|
613
|
-
if (!isActionableDisable(summary)) continue;
|
|
638
|
+
if (!isActionableDisable(summary, accounts)) continue;
|
|
614
639
|
const list = disabledByProvider.get(summary.provider) ?? [];
|
|
615
640
|
list.push(summary);
|
|
616
641
|
disabledByProvider.set(summary.provider, list);
|
|
@@ -1018,7 +1043,7 @@ export async function runUsageCommand(cmd: UsageCommandArgs): Promise<void> {
|
|
|
1018
1043
|
const stats = computeProviderWindowStats(filteredReports.filter(peer => peer.provider === report.provider));
|
|
1019
1044
|
if (stats.length > 0) capacity[report.provider] = stats;
|
|
1020
1045
|
}
|
|
1021
|
-
let disabledForJson = disabled.filter(isActionableDisable);
|
|
1046
|
+
let disabledForJson = disabled.filter(summary => isActionableDisable(summary, accounts));
|
|
1022
1047
|
if (redaction) {
|
|
1023
1048
|
disabledForJson = disabledForJson.map(summary => ({
|
|
1024
1049
|
...summary,
|
package/src/cli/worktree-cli.ts
CHANGED
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
* `<parent-repo>/.git/worktrees/<name>/`.
|
|
9
9
|
* - **Task-isolation dirs** (`task/worktree.ts`): a wrapper dir with a
|
|
10
10
|
* compact `m` subdir mounted/cloned by `natives.isoStart`. Legacy `merged`
|
|
11
|
-
* subdirs are still recognized.
|
|
12
|
-
*
|
|
11
|
+
* subdirs are still recognized. `ensureIsolation` writes an ownership
|
|
12
|
+
* marker naming the live omp process; a
|
|
13
|
+
* sandbox whose owner is still running is reported `live` and never
|
|
14
|
+
* removed without `--all`, so `clear` reclaims only crashed leftovers.
|
|
13
15
|
*
|
|
14
16
|
* Legacy entries from before the encoding change keep working because git still
|
|
15
17
|
* tracks them by branch name. This command exists to GC them on demand.
|
|
@@ -18,6 +20,7 @@ import * as fs from "node:fs/promises";
|
|
|
18
20
|
import * as path from "node:path";
|
|
19
21
|
import { getWorktreesDir, isEnoent } from "@oh-my-pi/pi-utils";
|
|
20
22
|
import chalk from "chalk";
|
|
23
|
+
import { hasLiveIsolationOwner, ISOLATION_OWNER_FILE } from "../task/isolation-ownership";
|
|
21
24
|
import * as git from "../utils/git";
|
|
22
25
|
|
|
23
26
|
type WorktreeKind = "pr-checkout" | "task-isolation" | "empty" | "stray";
|
|
@@ -211,16 +214,30 @@ async function classifyDir(dir: string): Promise<WorktreeEntry | null> {
|
|
|
211
214
|
if (gitStat?.isFile()) {
|
|
212
215
|
return classifyPrCheckout(dir, gitEntry);
|
|
213
216
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
217
|
+
// A task-isolation sandbox is identified by its ownership marker — written
|
|
218
|
+
// before the backend materialises the mount — or by the `m`/`merged` mount
|
|
219
|
+
// dir itself (legacy dirs and crashed pre-marker runs). Recognizing the
|
|
220
|
+
// marker alone keeps an in-progress sandbox from being mistaken for a stray
|
|
221
|
+
// during the window between marker creation and mount materialisation.
|
|
222
|
+
let isIsolation = await Bun.file(path.join(dir, ISOLATION_OWNER_FILE)).exists();
|
|
223
|
+
if (!isIsolation) {
|
|
224
|
+
for (const mountDir of TASK_ISOLATION_MOUNT_DIRS) {
|
|
225
|
+
const mountStat = await fs.stat(path.join(dir, mountDir)).catch(() => null);
|
|
226
|
+
if (mountStat?.isDirectory()) {
|
|
227
|
+
isIsolation = true;
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
222
231
|
}
|
|
223
|
-
return null;
|
|
232
|
+
if (!isIsolation) return null;
|
|
233
|
+
const live = await hasLiveIsolationOwner(dir);
|
|
234
|
+
return {
|
|
235
|
+
path: dir,
|
|
236
|
+
kind: "task-isolation",
|
|
237
|
+
// Only after confirming no live owner is the "no live task" claim true.
|
|
238
|
+
// A running subagent's sandbox stays live so `clear` won't delete it.
|
|
239
|
+
orphanReason: live ? undefined : "task-isolation leftover (no live task owns it)",
|
|
240
|
+
};
|
|
224
241
|
}
|
|
225
242
|
|
|
226
243
|
async function classifyPrCheckout(dir: string, gitEntry: string): Promise<WorktreeEntry> {
|
package/src/cli-commands.ts
CHANGED
|
@@ -18,6 +18,7 @@ export const commands: CommandEntry[] = [
|
|
|
18
18
|
{ name: "auth-gateway", load: () => import("./commands/auth-gateway").then(m => m.default) },
|
|
19
19
|
{ name: "agents", load: () => import("./commands/agents").then(m => m.default) },
|
|
20
20
|
{ name: "bench", load: () => import("./commands/bench").then(m => m.default) },
|
|
21
|
+
{ name: "cleanse", load: () => import("./commands/cleanse").then(m => m.default) },
|
|
21
22
|
{ name: "commit", load: () => import("./commands/commit").then(m => m.default) },
|
|
22
23
|
{ name: "completions", load: () => import("./commands/completions").then(m => m.default) },
|
|
23
24
|
{ name: "__complete", load: () => import("./commands/complete").then(m => m.default) },
|
package/src/cli.ts
CHANGED
|
@@ -25,12 +25,14 @@ import {
|
|
|
25
25
|
VERSION,
|
|
26
26
|
} from "@oh-my-pi/pi-utils/dirs";
|
|
27
27
|
import { interceptUnhandledRejections } from "@oh-my-pi/pi-utils/postmortem";
|
|
28
|
+
import { setProcessName } from "@oh-my-pi/pi-utils/process-name";
|
|
28
29
|
import { declareWorkerHostEntry, installWorkerInbox, isWorkerHostSelector } from "@oh-my-pi/pi-utils/worker-host";
|
|
29
30
|
import { installProfileAlias, resolveProfileAliasCommandFromProcess } from "./cli/profile-alias";
|
|
30
31
|
import { extractProfileFlags } from "./cli/profile-bootstrap";
|
|
31
32
|
import { startJsEvalProcess } from "./eval/js/process-entry";
|
|
32
33
|
import type { WorkerInbound as JsWorkerInbound, WorkerOutbound as JsWorkerOutbound } from "./eval/js/worker-protocol";
|
|
33
34
|
import { DAEMON_BROKER_WORKER_ARG } from "./launch/protocol";
|
|
35
|
+
import { TERMINAL_OUTPUT_WORKER_ARG } from "./launch/terminal-output-worker-protocol";
|
|
34
36
|
import { COMPUTER_WORKER_ARG } from "./tools/computer/protocol";
|
|
35
37
|
import { smokeTestComputerWorker } from "./tools/computer/supervisor";
|
|
36
38
|
import { startComputerWorker } from "./tools/computer/worker-entry";
|
|
@@ -42,7 +44,7 @@ if (Bun.semver.order(Bun.version, MIN_BUN_VERSION) < 0) {
|
|
|
42
44
|
process.exit(1);
|
|
43
45
|
}
|
|
44
46
|
|
|
45
|
-
|
|
47
|
+
setProcessName(APP_NAME);
|
|
46
48
|
|
|
47
49
|
// `Bun.build`-API compiled Windows executables report `import.meta.main ===
|
|
48
50
|
// false`: the standalone loader keys the entry module with native backslashes
|
|
@@ -87,6 +89,7 @@ async function runSmokeTest(): Promise<void> {
|
|
|
87
89
|
const { smokeTestJsEvalWorker } = await import("./eval/js/context-manager");
|
|
88
90
|
// Other smoke dependencies stay lazy so normal CLI startup does not load their worker clients.
|
|
89
91
|
const { smokeTestDaemonBroker } = await import("./launch/client");
|
|
92
|
+
const { smokeTestTerminalOutputWorker } = await import("./launch/terminal-output-worker-client");
|
|
90
93
|
await smokeTestSyncWorker();
|
|
91
94
|
|
|
92
95
|
const statsServer = await startServer(0);
|
|
@@ -108,6 +111,7 @@ async function runSmokeTest(): Promise<void> {
|
|
|
108
111
|
await smokeTestTtsWorker();
|
|
109
112
|
await smokeTestMnemopiEmbedWorker();
|
|
110
113
|
await smokeTestDaemonBroker();
|
|
114
|
+
await smokeTestTerminalOutputWorker();
|
|
111
115
|
process.stdout.write("smoke-test: ok\n");
|
|
112
116
|
}
|
|
113
117
|
|
|
@@ -194,6 +198,12 @@ async function runWorkerEntrypoint(arg: string | undefined): Promise<boolean> {
|
|
|
194
198
|
await runIpcSubprocessWorker(startMnemopiEmbedWorker);
|
|
195
199
|
return true;
|
|
196
200
|
}
|
|
201
|
+
if (arg === TERMINAL_OUTPUT_WORKER_ARG) {
|
|
202
|
+
if (parentPort) installWorkerInbox(parentPort);
|
|
203
|
+
// This selector is the isolation boundary; a static import would evaluate xterm in normal CLI startup.
|
|
204
|
+
await import("./launch/terminal-output-worker");
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
197
207
|
if (arg === DAEMON_BROKER_WORKER_ARG) {
|
|
198
208
|
// Worker selectors must dispatch before the normal command graph loads.
|
|
199
209
|
const { startDaemonBrokerFromEnvironment } = await import("./launch/broker");
|
package/src/collab/guest.ts
CHANGED
|
@@ -85,14 +85,15 @@ interface PendingSnapshot {
|
|
|
85
85
|
/** Minimal context surface the idle-state reconciler mutates. */
|
|
86
86
|
export interface GuestIdleReconcilerCtx {
|
|
87
87
|
statusLine: { markActivityEnd: () => void };
|
|
88
|
+
statusContainer: Pick<InteractiveModeContext["statusContainer"], "disposeChildren">;
|
|
88
89
|
loadingAnimation: { stop: () => void } | undefined;
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
/**
|
|
92
93
|
* Close the guest UI state held open by an earlier `agent_start` whose
|
|
93
94
|
* matching `agent_end` never reached us — most often because a reconnect
|
|
94
|
-
* dropped the event mid-stream.
|
|
95
|
-
* `state
|
|
95
|
+
* dropped the event mid-stream. Reached via {@link reconcileGuestSnapshotHostState}
|
|
96
|
+
* (the live `state`-frame and welcome/resync reconciler) when the host reports `isStreaming === false`:
|
|
96
97
|
* folds the in-flight active-time window into the per-session meter (so
|
|
97
98
|
* `time_spent` stops ticking) and stops the `Working…` loader if one is
|
|
98
99
|
* still animating. No-op when the host is still streaming.
|
|
@@ -106,17 +107,49 @@ export function reconcileGuestIdleHostState(ctx: GuestIdleReconcilerCtx, isStrea
|
|
|
106
107
|
if (ctx.loadingAnimation) {
|
|
107
108
|
ctx.loadingAnimation.stop();
|
|
108
109
|
ctx.loadingAnimation = undefined;
|
|
110
|
+
ctx.statusContainer.disposeChildren();
|
|
109
111
|
}
|
|
110
112
|
}
|
|
111
113
|
|
|
112
114
|
/** Reconcile a welcome/resync snapshot's host activity state into the guest meter. */
|
|
113
115
|
export interface GuestSnapshotActivityReconcilerCtx extends GuestIdleReconcilerCtx {
|
|
114
116
|
statusLine: GuestIdleReconcilerCtx["statusLine"] & { markActivityStart: () => void };
|
|
117
|
+
/**
|
|
118
|
+
* Start (or re-attach) the live "Working…" loader. Mirrors
|
|
119
|
+
* `InteractiveModeContext.ensureLoadingAnimation`, which is what
|
|
120
|
+
* `EventController` calls on `agent_start`. Required so a guest that
|
|
121
|
+
* missed an earlier `agent_start` (a reconnect dropped it mid-stream)
|
|
122
|
+
* starts its spinner when the host later reports it is streaming.
|
|
123
|
+
*/
|
|
124
|
+
ensureLoadingAnimation: InteractiveModeContext["ensureLoadingAnimation"];
|
|
125
|
+
autoCompactionLoader: InteractiveModeContext["autoCompactionLoader"];
|
|
126
|
+
retryLoader: InteractiveModeContext["retryLoader"];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Status-area state which cannot outlive removal of its child components. */
|
|
130
|
+
export interface GuestTransientStatusCtx {
|
|
131
|
+
statusContainer: Pick<InteractiveModeContext["statusContainer"], "clear">;
|
|
132
|
+
autoCompactionLoader: InteractiveModeContext["autoCompactionLoader"];
|
|
133
|
+
retryLoader: InteractiveModeContext["retryLoader"];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Stop and forget status-area loaders before detaching their components. */
|
|
137
|
+
export function clearGuestTransientStatus(ctx: GuestTransientStatusCtx): void {
|
|
138
|
+
if (ctx.autoCompactionLoader) {
|
|
139
|
+
ctx.autoCompactionLoader.stop();
|
|
140
|
+
ctx.autoCompactionLoader = undefined;
|
|
141
|
+
}
|
|
142
|
+
if (ctx.retryLoader) {
|
|
143
|
+
ctx.retryLoader.stop();
|
|
144
|
+
ctx.retryLoader = undefined;
|
|
145
|
+
}
|
|
146
|
+
ctx.statusContainer.clear();
|
|
115
147
|
}
|
|
116
148
|
|
|
117
149
|
export function reconcileGuestSnapshotHostState(ctx: GuestSnapshotActivityReconcilerCtx, isStreaming: boolean): void {
|
|
118
150
|
if (isStreaming) {
|
|
119
151
|
ctx.statusLine.markActivityStart();
|
|
152
|
+
if (!ctx.autoCompactionLoader && !ctx.retryLoader) ctx.ensureLoadingAnimation();
|
|
120
153
|
return;
|
|
121
154
|
}
|
|
122
155
|
reconcileGuestIdleHostState(ctx, false);
|
|
@@ -482,7 +515,7 @@ export class CollabGuestLink {
|
|
|
482
515
|
this.#applyHostState(frame.state);
|
|
483
516
|
setSessionTerminalTitle(frame.state.sessionName, frame.state.cwd);
|
|
484
517
|
this.#updateStatusSegment();
|
|
485
|
-
|
|
518
|
+
reconcileGuestSnapshotHostState(this.#ctx, frame.state.isStreaming);
|
|
486
519
|
this.#ctx.statusLine.invalidate();
|
|
487
520
|
this.#ctx.ui.requestRender();
|
|
488
521
|
break;
|
|
@@ -681,7 +714,7 @@ export class CollabGuestLink {
|
|
|
681
714
|
|
|
682
715
|
#clearTransientUi(): void {
|
|
683
716
|
this.#clearUiRequests();
|
|
684
|
-
this.#ctx
|
|
717
|
+
clearGuestTransientStatus(this.#ctx);
|
|
685
718
|
this.#ctx.pendingMessagesContainer.clear();
|
|
686
719
|
this.#ctx.compactionQueuedMessages = [];
|
|
687
720
|
this.#ctx.streamingComponent = undefined;
|