@gajae-code/coding-agent 0.18.10 → 0.18.12
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 +295 -0
- package/dist/types/async/job-manager.d.ts +7 -3
- package/dist/types/async/result-formatting.d.ts +2 -0
- package/dist/types/autoresearch/git.d.ts +1 -1
- package/dist/types/capability/fs.d.ts +2 -2
- package/dist/types/cli/accounts-cli.d.ts +2 -2
- package/dist/types/cli/doctor/install-repairs.d.ts +4 -1
- package/dist/types/cli/install-activation.d.ts +10 -4
- package/dist/types/cli/public-command-errors.d.ts +1 -0
- package/dist/types/cli/update-cli.d.ts +14 -1
- package/dist/types/cli-ordinary.d.ts +2 -2
- package/dist/types/commands/accounts.d.ts +2 -1
- package/dist/types/commands/migrate.d.ts +1 -1
- package/dist/types/commands/sdk.d.ts +5 -0
- package/dist/types/commit/changelog/index.d.ts +1 -0
- package/dist/types/config/autorouting-tier-map.d.ts +30 -0
- package/dist/types/config/config-hot-reload-trust.d.ts +12 -0
- package/dist/types/config/config-hot-reload.d.ts +39 -0
- package/dist/types/config/file-lock.d.ts +8 -1
- package/dist/types/config/model-discovery-manager.d.ts +2 -0
- package/dist/types/config/model-profile-activation.d.ts +62 -6
- package/dist/types/config/model-registry.d.ts +56 -5
- package/dist/types/config/model-resolver.d.ts +2 -0
- package/dist/types/config/models-config-schema.d.ts +10 -0
- package/dist/types/config/settings-schema.d.ts +46 -3
- package/dist/types/config/settings.d.ts +33 -1
- package/dist/types/dap/config.d.ts +8 -4
- package/dist/types/debug/system-info.d.ts +6 -1
- package/dist/types/discovery/mcp-compat.d.ts +3 -1
- package/dist/types/edit/modes/patch.d.ts +5 -0
- package/dist/types/edit/modes/replace.d.ts +3 -0
- package/dist/types/exec/bash-executor.d.ts +12 -0
- package/dist/types/gjc-runtime/managed-owner-admission.d.ts +3 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +2 -0
- package/dist/types/gjc-runtime/state-runtime.d.ts +0 -2
- package/dist/types/migrate/adapters/codex.d.ts +2 -1
- package/dist/types/migrate/adapters/index.d.ts +10 -1
- package/dist/types/migrate/executor.d.ts +3 -1
- package/dist/types/migrate/types.d.ts +15 -3
- package/dist/types/modes/components/settings-selector.d.ts +6 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -0
- package/dist/types/modes/theme/theme.d.ts +1 -0
- package/dist/types/runtime-mcp/content-limits.d.ts +4 -0
- package/dist/types/runtime-mcp/manager.d.ts +7 -0
- package/dist/types/runtime-mcp/plugin-mcp-name-filter.d.ts +22 -0
- package/dist/types/runtime-mcp/tool-bridge.d.ts +4 -0
- package/dist/types/sdk/broker/broker-exit.d.ts +2 -2
- package/dist/types/sdk/broker/broker.d.ts +7 -1
- package/dist/types/sdk/broker/daemon-entry.d.ts +11 -1
- package/dist/types/sdk/broker/doctor-restart.d.ts +1 -1
- package/dist/types/sdk/broker/ensure.d.ts +136 -7
- package/dist/types/sdk/broker/hop.d.ts +36 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +18 -10
- package/dist/types/sdk/broker/lock-host-identity.d.ts +5 -0
- package/dist/types/sdk/broker/session-index.d.ts +4 -1
- package/dist/types/sdk/bus/chat-command-policy.d.ts +2 -1
- package/dist/types/sdk/bus/index.d.ts +23 -0
- package/dist/types/sdk/bus/reconciliation-store.d.ts +10 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +3 -1
- package/dist/types/sdk/cli/session-cli.d.ts +0 -1
- package/dist/types/sdk/host/session-runtime.d.ts +32 -0
- package/dist/types/sdk/prompt-deadline-flush.d.ts +42 -4
- package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -0
- package/dist/types/sdk/protocol/adapter-validation.d.ts +10 -4
- package/dist/types/sdk/reconciliation-extensions.d.ts +1 -1
- package/dist/types/sdk/session-list.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +58 -2
- package/dist/types/session/internal/managed-session-scope.d.ts +2 -2
- package/dist/types/session/internal/managed-session-storage.d.ts +1 -1
- package/dist/types/session/internal/native-publish-outcome.d.ts +1 -1
- package/dist/types/session/messages.d.ts +5 -1
- package/dist/types/session/session-manager.d.ts +13 -3
- package/dist/types/session/startup-auth-config.d.ts +24 -2
- package/dist/types/session/streaming-output.d.ts +11 -0
- package/dist/types/session/terminal-abort.d.ts +3 -1
- package/dist/types/session-import/service.d.ts +1 -1
- package/dist/types/skill-state/workflow-mutation-guard.d.ts +7 -3
- package/dist/types/slash-commands/helpers/provider-auth-status.d.ts +8 -0
- package/dist/types/task/executor.d.ts +19 -2
- package/dist/types/tools/ast-edit-write-path.d.ts +10 -0
- package/dist/types/tools/atomic-file-write.d.ts +9 -7
- package/dist/types/tools/bash-allowed-prefixes.d.ts +11 -0
- package/dist/types/tools/descriptors.d.ts +2 -0
- package/dist/types/tools/fetch.d.ts +1 -1
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tools/python.d.ts +1 -1
- package/dist/types/tools/report-issue.d.ts +41 -0
- package/dist/types/tools/subagent.d.ts +23 -0
- package/dist/types/web/scrapers/types.d.ts +8 -1
- package/dist/types/web/search/providers/insane.d.ts +7 -1
- package/dist/types/web/search/providers/searxng.d.ts +5 -0
- package/package.json +8 -8
- package/scripts/build-binary.ts +2 -1
- package/scripts/capture-subagent-compact-panel-showcase.ts +105 -0
- package/scripts/generate-docs-index.ts +8 -1
- package/scripts/generate-sdk-operation-inventory.ts +22 -4
- package/src/async/job-manager.ts +356 -56
- package/src/async/result-formatting.ts +40 -0
- package/src/autoresearch/git.ts +9 -2
- package/src/autoresearch/prompts/prompt-setup.md +1 -1
- package/src/capability/fs.ts +27 -19
- package/src/capability/index.ts +2 -2
- package/src/cli/accounts-cli.ts +176 -8
- package/src/cli/auth-broker-cli.ts +21 -11
- package/src/cli/doctor/install-repairs.ts +35 -5
- package/src/cli/fast-help.ts +1 -1
- package/src/cli/install-activation.ts +47 -4
- package/src/cli/migrate-cli.ts +7 -5
- package/src/cli/public-command-errors.ts +4 -0
- package/src/cli/root-flags.ts +2 -2
- package/src/cli/stats-cli.ts +9 -5
- package/src/cli/update-cli.ts +224 -7
- package/src/cli-ordinary.ts +7 -2
- package/src/commands/accounts.ts +10 -5
- package/src/commands/migrate.ts +3 -3
- package/src/commands/sdk.ts +199 -42
- package/src/commands/ultragoal.ts +16 -8
- package/src/commit/changelog/index.ts +39 -1
- package/src/commit/message.ts +10 -3
- package/src/config/autorouting-tier-map.ts +30 -0
- package/src/config/config-hot-reload-trust.ts +148 -0
- package/src/config/config-hot-reload.ts +734 -0
- package/src/config/file-lock.ts +216 -56
- package/src/config/model-bindings-applier.ts +85 -47
- package/src/config/model-discovery-manager.ts +10 -0
- package/src/config/model-profile-activation.ts +716 -113
- package/src/config/model-registry.ts +1334 -158
- package/src/config/model-resolver.ts +3 -1
- package/src/config/models-config-schema.ts +6 -0
- package/src/config/settings-schema.ts +60 -3
- package/src/config/settings.ts +458 -24
- package/src/coordinator-mcp/policy.ts +26 -1
- package/src/crash/sanitize.ts +4 -0
- package/src/customization/import.ts +3 -1
- package/src/dap/config.ts +31 -11
- package/src/dap/defaults.json +1 -1
- package/src/debug/crash-diagnostics.ts +2 -1
- package/src/debug/report-bundle.ts +19 -0
- package/src/debug/system-info.ts +8 -3
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.ts +21 -7
- package/src/discovery/agents-md.ts +1 -1
- package/src/discovery/helpers.ts +1 -2
- package/src/discovery/mcp-compat.ts +120 -17
- package/src/edit/apply-patch/index.ts +12 -1
- package/src/edit/index.ts +13 -17
- package/src/edit/modes/patch.ts +187 -8
- package/src/edit/modes/replace.ts +136 -0
- package/src/eval/js/context-manager.ts +29 -3
- package/src/eval/js/worker-entry.ts +51 -18
- package/src/eval/py/executor.ts +14 -4
- package/src/eval/py/runner.py +144 -5
- package/src/exec/bash-executor.ts +81 -4
- package/src/exec/bash-shell-guardian.ts +4 -1
- package/src/exec/bash-shell-supervisor.ts +8 -4
- package/src/exec/isolated-shell.ts +5 -1
- package/src/export/html/index.ts +46 -6
- package/src/extensibility/custom-tools/wrapper.ts +3 -1
- package/src/extensibility/extensions/loader.ts +8 -1
- package/src/extensibility/plugins/marketplace/fetcher.ts +2 -2
- package/src/gjc-runtime/gc-runtime.ts +33 -6
- package/src/gjc-runtime/launch-tmux.ts +7 -2
- package/src/gjc-runtime/launch-worktree.test.ts +148 -0
- package/src/gjc-runtime/launch-worktree.ts +101 -14
- package/src/gjc-runtime/managed-owner-admission.ts +46 -2
- package/src/gjc-runtime/session-resolution.ts +38 -6
- package/src/gjc-runtime/session-state-lock.ts +59 -14
- package/src/gjc-runtime/state-runtime.ts +23 -19
- package/src/hooks/native-skill-hook.ts +14 -11
- package/src/internal-urls/docs-index.generated.ts +14 -13
- package/src/internal-urls/local-protocol.ts +142 -8
- package/src/lsp/index.ts +29 -0
- package/src/lsp/workspace-path.ts +23 -0
- package/src/migrate/action-planner.ts +57 -0
- package/src/migrate/adapters/claude-code.ts +23 -4
- package/src/migrate/adapters/codex.ts +22 -4
- package/src/migrate/adapters/index.ts +22 -4
- package/src/migrate/adapters/opencode.ts +6 -1
- package/src/migrate/executor.ts +84 -7
- package/src/migrate/report.ts +19 -2
- package/src/migrate/types.ts +14 -4
- package/src/modes/DESIGN.md +51 -0
- package/src/modes/acp/permission-mode.ts +18 -1
- package/src/modes/components/assistant-message.ts +55 -1
- package/src/modes/components/settings-selector.ts +103 -37
- package/src/modes/controllers/command-controller.ts +38 -2
- package/src/modes/controllers/plan-mode-controller.ts +17 -7
- package/src/modes/controllers/selector-controller.ts +62 -6
- package/src/modes/interactive-mode.ts +68 -0
- package/src/modes/theme/theme.ts +15 -0
- package/src/prompts/tools/debug.md +1 -1
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/report-issue.md +13 -0
- package/src/prompts/tools/task.md +1 -1
- package/src/runtime-credential-selector.ts +2 -2
- package/src/runtime-mcp/content-limits.ts +7 -0
- package/src/runtime-mcp/manager-pool.test.ts +5 -4
- package/src/runtime-mcp/manager.ts +32 -0
- package/src/runtime-mcp/oauth-flow.ts +32 -6
- package/src/runtime-mcp/plugin-mcp-name-filter.ts +62 -0
- package/src/runtime-mcp/render.ts +16 -1
- package/src/runtime-mcp/tool-bridge.ts +126 -17
- package/src/sdk/broker/broker-exit.ts +8 -5
- package/src/sdk/broker/broker.ts +342 -82
- package/src/sdk/broker/daemon-entry.ts +338 -116
- package/src/sdk/broker/doctor-restart.ts +2 -1
- package/src/sdk/broker/ensure.ts +1143 -265
- package/src/sdk/broker/hop.ts +274 -0
- package/src/sdk/broker/lifecycle-ledger.ts +35 -11
- package/src/sdk/broker/lock-host-identity.ts +29 -0
- package/src/sdk/broker/session-index.ts +62 -12
- package/src/sdk/bus/chat-command-policy.ts +16 -3
- package/src/sdk/bus/index.ts +52 -21
- package/src/sdk/bus/reconciliation-store.ts +201 -8
- package/src/sdk/bus/telegram-daemon-contract.ts +3 -1
- package/src/sdk/cli/session-cli.ts +21 -6
- package/src/sdk/host/session-runtime.test.ts +118 -23
- package/src/sdk/host/session-runtime.ts +352 -47
- package/src/sdk/lifecycle/service.ts +1 -0
- package/src/sdk/prompt-deadline-flush.ts +78 -5
- package/src/sdk/prompt-deadline-manager.ts +68 -14
- package/src/sdk/protocol/adapter-validation.ts +53 -11
- package/src/sdk/protocol/operation-inventory.generated.json +101 -2
- package/src/sdk/reconciliation-extensions.ts +1 -0
- package/src/sdk/session-list.ts +1 -0
- package/src/sdk/session.ts +114 -61
- package/src/session/agent-session.ts +1043 -81
- package/src/session/internal/managed-session-scope.ts +126 -39
- package/src/session/internal/managed-session-storage.ts +14 -11
- package/src/session/internal/native-publish-outcome.ts +5 -1
- package/src/session/messages.ts +47 -6
- package/src/session/session-manager.ts +236 -14
- package/src/session/startup-auth-config.ts +116 -8
- package/src/session/streaming-output.ts +262 -4
- package/src/session/terminal-abort.ts +14 -2
- package/src/session-import/service.ts +1 -0
- package/src/skill-state/workflow-mutation-guard.ts +491 -32
- package/src/slash-commands/builtin-registry.ts +15 -3
- package/src/slash-commands/helpers/credential-switch.ts +10 -5
- package/src/slash-commands/helpers/provider-auth-status.ts +456 -0
- package/src/task/executor.ts +103 -12
- package/src/task/index.ts +389 -175
- package/src/tools/ast-edit-write-path.ts +64 -0
- package/src/tools/ast-edit.ts +18 -1
- package/src/tools/atomic-file-write.ts +46 -15
- package/src/tools/auto-generated-guard.ts +3 -0
- package/src/tools/bash-allowed-prefixes.ts +153 -5
- package/src/tools/bash-interactive.ts +14 -2
- package/src/tools/bash.ts +14 -0
- package/src/tools/debug.ts +7 -6
- package/src/tools/descriptors.test.ts +44 -0
- package/src/tools/descriptors.ts +34 -2
- package/src/tools/fetch.ts +3 -2
- package/src/tools/index.ts +2 -0
- package/src/tools/output-meta.ts +23 -2
- package/src/tools/python.ts +1 -1
- package/src/tools/read.ts +17 -35
- package/src/tools/report-issue.ts +184 -0
- package/src/tools/subagent-render.ts +114 -12
- package/src/tools/subagent.ts +108 -1
- package/src/tools/todo-write.ts +41 -3
- package/src/tools/tool-catalog.generated.ts +31 -3
- package/src/tools/write.ts +35 -3
- package/src/utils/commit-message-generator.ts +9 -2
- package/src/utils/file-mentions.ts +29 -1
- package/src/utils/title-generator.ts +7 -2
- package/src/web/scrapers/index.ts +3 -1
- package/src/web/scrapers/types.ts +8 -0
- package/src/web/scrapers/youtube.ts +3 -2
- package/src/web/search/providers/insane.ts +91 -28
- package/src/web/search/providers/searxng.ts +62 -10
package/src/tools/descriptors.ts
CHANGED
|
@@ -84,6 +84,9 @@ export class LazyAgentTool implements AgentTool<any, any, any> {
|
|
|
84
84
|
#loader?: () => ToolDescriptorLoadResult;
|
|
85
85
|
#session?: ToolSession;
|
|
86
86
|
#loadPromise?: Promise<AgentTool<any, any, any>>;
|
|
87
|
+
#loadGeneration = 0;
|
|
88
|
+
#loadToken?: object;
|
|
89
|
+
#expectedCwd?: string;
|
|
87
90
|
|
|
88
91
|
constructor(
|
|
89
92
|
descriptor: ToolDescriptor,
|
|
@@ -97,9 +100,31 @@ export class LazyAgentTool implements AgentTool<any, any, any> {
|
|
|
97
100
|
this.#session = session;
|
|
98
101
|
}
|
|
99
102
|
|
|
103
|
+
/** Clear a session-bound materialization after its owning session moves. */
|
|
104
|
+
invalidateForSessionMove(expectedCwd?: string): void {
|
|
105
|
+
this.#loadGeneration++;
|
|
106
|
+
this.#tool = undefined;
|
|
107
|
+
this.#loadPromise = undefined;
|
|
108
|
+
this.#loadToken = undefined;
|
|
109
|
+
this.#expectedCwd = expectedCwd;
|
|
110
|
+
if (!this.#loader && this.#session) {
|
|
111
|
+
const session = this.#session;
|
|
112
|
+
this.#loader = () => this.descriptor.load(session);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
100
116
|
async #get(): Promise<AgentTool<any, any, any>> {
|
|
117
|
+
const sessionCwd = this.#session?.cwd;
|
|
118
|
+
if (this.#expectedCwd !== undefined && sessionCwd !== this.#expectedCwd) {
|
|
119
|
+
throw new ToolError(
|
|
120
|
+
`Tool "${this.descriptor.metadata.name}" session cwd changed outside a committed move (expected ${this.#expectedCwd}, got ${sessionCwd ?? "unknown"})`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
101
123
|
if (this.#tool) return this.#tool;
|
|
102
124
|
if (!this.#loadPromise) {
|
|
125
|
+
const generation = this.#loadGeneration;
|
|
126
|
+
const token = {};
|
|
127
|
+
this.#loadToken = token;
|
|
103
128
|
const load = this.#loader;
|
|
104
129
|
this.#loadPromise = Promise.resolve()
|
|
105
130
|
.then(() => {
|
|
@@ -108,11 +133,14 @@ export class LazyAgentTool implements AgentTool<any, any, any> {
|
|
|
108
133
|
})
|
|
109
134
|
.then(tool => {
|
|
110
135
|
if (!tool) throw new ToolError(`Tool "${this.descriptor.metadata.name}" failed to load`);
|
|
111
|
-
this.#tool = tool;
|
|
136
|
+
if (generation === this.#loadGeneration && this.#loadToken === token) this.#tool = tool;
|
|
112
137
|
return tool;
|
|
113
138
|
})
|
|
114
139
|
.catch(error => {
|
|
115
|
-
this.#
|
|
140
|
+
if (generation === this.#loadGeneration && this.#loadToken === token) {
|
|
141
|
+
this.#loadPromise = undefined;
|
|
142
|
+
this.#loadToken = undefined;
|
|
143
|
+
}
|
|
116
144
|
if (error instanceof ToolError) throw error;
|
|
117
145
|
throw new ToolError(
|
|
118
146
|
`Tool "${this.descriptor.metadata.name}" failed to load implementation: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -270,6 +298,7 @@ function availableFor(name: string, session: ToolSession, context = defaultAvail
|
|
|
270
298
|
if (name === "ast_grep") return Boolean(session.settings.get("astGrep.enabled"));
|
|
271
299
|
if (name === "ast_edit") return Boolean(session.settings.get("astEdit.enabled"));
|
|
272
300
|
if (name === "render_mermaid") return Boolean(session.settings.get("renderMermaid.enabled"));
|
|
301
|
+
if (name === "report_issue") return Boolean(session.settings.get("reportIssue.enabled"));
|
|
273
302
|
if (name === "web_search") return Boolean(session.settings.get("web_search.enabled"));
|
|
274
303
|
if (name === "search_tool_bm25") return context.discoveryActive ?? resolveDiscoveryActive(session);
|
|
275
304
|
if (name === "calc") return Boolean(session.settings.get("calc.enabled"));
|
|
@@ -389,6 +418,8 @@ const loaders: Record<string, Loader> = {
|
|
|
389
418
|
cached("goal", () => import("../goals/tools/goal-tool")).then(module => new module.GoalTool(session)),
|
|
390
419
|
move_session: session =>
|
|
391
420
|
cached("move_session", () => import("./move-session")).then(module => new module.MoveSessionTool(session)),
|
|
421
|
+
report_issue: session =>
|
|
422
|
+
cached("report_issue", () => import("./report-issue")).then(module => new module.ReportIssueTool(session)),
|
|
392
423
|
yield: session => cached("yield", () => import("./yield")).then(module => new module.YieldTool(session)),
|
|
393
424
|
report_finding: _session => cached("review", () => import("./review")).then(module => module.reportFindingTool),
|
|
394
425
|
resolve: session => cached("resolve", () => import("./resolve")).then(module => new module.ResolveTool(session)),
|
|
@@ -481,6 +512,7 @@ const names: Array<[name: string, label: string, summary: string | undefined, lo
|
|
|
481
512
|
["skill", "Skill", "Chain into another available skill", "essential"],
|
|
482
513
|
["goal", "Goal", undefined, "essential"],
|
|
483
514
|
["move_session", "Move Session", undefined, "essential"],
|
|
515
|
+
["report_issue", "Report Issue", "Record a tool that behaved inconsistently with its description", "essential"],
|
|
484
516
|
];
|
|
485
517
|
|
|
486
518
|
const sessionDescriptions: Readonly<Record<string, ToolDescriptorMetadata["descriptionForSession"]>> = {
|
package/src/tools/fetch.ts
CHANGED
|
@@ -677,12 +677,13 @@ async function handleSpecialUrls(
|
|
|
677
677
|
timeout: number,
|
|
678
678
|
signal: AbortSignal | undefined,
|
|
679
679
|
storage: AgentStorage | null,
|
|
680
|
+
settings: Settings,
|
|
680
681
|
): Promise<FetchRenderResult | null> {
|
|
681
682
|
for (const handler of getSpecialHandlers()) {
|
|
682
683
|
if (signal?.aborted) {
|
|
683
684
|
throw new ToolAbortError();
|
|
684
685
|
}
|
|
685
|
-
const result = await handler(url, timeout, signal, storage);
|
|
686
|
+
const result = await handler(url, timeout, signal, storage, settings);
|
|
686
687
|
if (result) return result;
|
|
687
688
|
}
|
|
688
689
|
return null;
|
|
@@ -766,7 +767,7 @@ async function renderUrl(
|
|
|
766
767
|
|
|
767
768
|
// Step 1: Try special handlers for known sites (unless raw mode)
|
|
768
769
|
if (!raw) {
|
|
769
|
-
const specialResult = await handleSpecialUrls(url, timeout, signal, storage);
|
|
770
|
+
const specialResult = await handleSpecialUrls(url, timeout, signal, storage, settings);
|
|
770
771
|
if (specialResult) return specialResult;
|
|
771
772
|
}
|
|
772
773
|
|
package/src/tools/index.ts
CHANGED
|
@@ -364,6 +364,8 @@ export interface ToolSession {
|
|
|
364
364
|
* directory, or escapes the current scope.
|
|
365
365
|
*/
|
|
366
366
|
rescopeSessionCwd?: (path: string) => Promise<{ from: string; to: string }>;
|
|
367
|
+
/** Live ACP/SDK approval mode. Task children copy this at spawn; absent means `allow`. */
|
|
368
|
+
getSdkPermissionMode?: () => "prompt" | "allow" | "deny";
|
|
367
369
|
/** Bridge to the connected client (e.g. ACP editor host). Tools should route fs/terminal/permission requests through this when available. */
|
|
368
370
|
getClientBridge?: () => ClientBridge | undefined;
|
|
369
371
|
/** Get compact conversation context for subagents (excludes tool results, system prompts) */
|
package/src/tools/output-meta.ts
CHANGED
|
@@ -20,13 +20,14 @@ import {
|
|
|
20
20
|
formatMiddleElisionMarker,
|
|
21
21
|
type OutputSummary,
|
|
22
22
|
type ReadWindow,
|
|
23
|
+
runWithOutputRedaction,
|
|
23
24
|
type TruncationResult,
|
|
24
25
|
truncateMiddle,
|
|
25
26
|
truncateTail,
|
|
26
27
|
} from "../session/streaming-output";
|
|
27
28
|
|
|
28
29
|
import { formatBytes, wrapBrackets } from "./render-utils";
|
|
29
|
-
import { renderError, ToolError } from "./tool-errors";
|
|
30
|
+
import { renderError, ToolError, throwIfAborted } from "./tool-errors";
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
33
|
* Truncation metadata for the output notice.
|
|
@@ -1155,6 +1156,19 @@ async function enforceInlineResultBackstop(
|
|
|
1155
1156
|
// Tool wrapper
|
|
1156
1157
|
// =============================================================================
|
|
1157
1158
|
|
|
1159
|
+
async function outputRedactionFor(settings: Settings): Promise<readonly string[] | undefined> {
|
|
1160
|
+
const { collectEnvSecrets, loadSecrets } = await import("../secrets");
|
|
1161
|
+
const entries = [...(await loadSecrets(settings.getCwd(), settings.getAgentDir())), ...collectEnvSecrets()];
|
|
1162
|
+
const plain: string[] = [];
|
|
1163
|
+
const seen = new Set<string>();
|
|
1164
|
+
for (const entry of entries) {
|
|
1165
|
+
if (entry.type !== "plain" || entry.content.length === 0 || seen.has(entry.content)) continue;
|
|
1166
|
+
seen.add(entry.content);
|
|
1167
|
+
plain.push(entry.content);
|
|
1168
|
+
}
|
|
1169
|
+
return plain.length > 0 ? plain : undefined;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1158
1172
|
async function wrappedExecute(
|
|
1159
1173
|
this: AgentTool & { [kUnwrappedExecute]: AgentToolExecFn },
|
|
1160
1174
|
toolCallId: string,
|
|
@@ -1166,7 +1180,14 @@ async function wrappedExecute(
|
|
|
1166
1180
|
const originalExecute = this[kUnwrappedExecute];
|
|
1167
1181
|
|
|
1168
1182
|
try {
|
|
1169
|
-
|
|
1183
|
+
const settings = context?.settings;
|
|
1184
|
+
let plainSecrets: readonly string[] | undefined;
|
|
1185
|
+
if (settings?.get("secrets.enabled") === true) {
|
|
1186
|
+
plainSecrets = await outputRedactionFor(settings);
|
|
1187
|
+
throwIfAborted(signal);
|
|
1188
|
+
}
|
|
1189
|
+
const run = () => originalExecute.call(this, toolCallId, params, signal, onUpdate, context);
|
|
1190
|
+
let result = plainSecrets ? await runWithOutputRedaction(plainSecrets, run) : await run();
|
|
1170
1191
|
|
|
1171
1192
|
// Spill large results to artifact, truncate to tail
|
|
1172
1193
|
result = await spillLargeResultToArtifact(result, this.name, context);
|
package/src/tools/python.ts
CHANGED
|
@@ -31,7 +31,7 @@ export interface SessionPythonToolInput {
|
|
|
31
31
|
/** Resolve the GJC session id used for the kernel owner and transcript paths. */
|
|
32
32
|
getSessionId: () => string | null;
|
|
33
33
|
/** Register cleanup with the current logical session lifecycle. */
|
|
34
|
-
registerSessionCleanup: (cleanup: () => Promise<void> | void) => (() => void) |
|
|
34
|
+
registerSessionCleanup: (cleanup: () => Promise<void> | void) => (() => void) | undefined;
|
|
35
35
|
/** Reject execution after the owning session has begun disposal. */
|
|
36
36
|
assertEvalExecutionAllowed?: () => void;
|
|
37
37
|
/** Track this whole invocation through its transcript append. */
|
package/src/tools/read.ts
CHANGED
|
@@ -26,9 +26,9 @@ import {
|
|
|
26
26
|
formatMiddleElisionMarker,
|
|
27
27
|
truncateHead,
|
|
28
28
|
truncateHeadBytes,
|
|
29
|
-
truncateLine,
|
|
30
29
|
truncateMiddleWindows,
|
|
31
30
|
truncateTailBytes,
|
|
31
|
+
truncateTextWithSecretAwareness,
|
|
32
32
|
} from "../session/streaming-output";
|
|
33
33
|
import { fileHyperlink, renderCodeCell, renderMarkdownCell, renderStatusLine, tryResolveInternalUrlSync } from "../tui";
|
|
34
34
|
import { CachedOutputBlock } from "../tui/output-block";
|
|
@@ -253,12 +253,7 @@ function renderReadWindow(
|
|
|
253
253
|
const hash = shouldAddHashLines && segment.kind === "lines";
|
|
254
254
|
const numbers = !hash && shouldAddLineNumbers;
|
|
255
255
|
const renderContent =
|
|
256
|
-
!hash && maxColumns > 0
|
|
257
|
-
? segment.content
|
|
258
|
-
.split("\n")
|
|
259
|
-
.map(line => truncateLine(line, maxColumns).text)
|
|
260
|
-
.join("\n")
|
|
261
|
-
: segment.content;
|
|
256
|
+
!hash && maxColumns > 0 ? truncateTextWithSecretAwareness(segment.content, maxColumns).text : segment.content;
|
|
262
257
|
return formatTextWithMode(renderContent, segment.origin.startLine, hash, numbers);
|
|
263
258
|
};
|
|
264
259
|
const marker = formatMiddleElisionMarker(window.elidedLines, window.elidedBytes);
|
|
@@ -1906,17 +1901,12 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1906
1901
|
// A partial line is a byte suffix, not a complete source line. Never
|
|
1907
1902
|
// give it an anchor that could make the preview look fully readable.
|
|
1908
1903
|
const numbers = !hash && shouldAddLineNumbers && segment.kind === "lines";
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
if (result.wasTruncated) columnTruncated = maxColumns;
|
|
1916
|
-
return result.text;
|
|
1917
|
-
})
|
|
1918
|
-
.join("\n")
|
|
1919
|
-
: segment.content;
|
|
1904
|
+
let renderContent = segment.content;
|
|
1905
|
+
if (!options.raw && !hash && maxColumns > 0) {
|
|
1906
|
+
const result = truncateTextWithSecretAwareness(segment.content, maxColumns);
|
|
1907
|
+
if (result.wasTruncated) columnTruncated = maxColumns;
|
|
1908
|
+
renderContent = result.text;
|
|
1909
|
+
}
|
|
1920
1910
|
return formatTextWithMode(renderContent, segment.origin.startLine, hash, numbers);
|
|
1921
1911
|
};
|
|
1922
1912
|
let text = "";
|
|
@@ -2097,12 +2087,10 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
2097
2087
|
const collectedLines = streamResult.lines;
|
|
2098
2088
|
const cacheLines = collectedLines.slice();
|
|
2099
2089
|
if (!rawSelector && !shouldAddHashLines && maxColumns > 0) {
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
columnTruncated = maxColumns;
|
|
2105
|
-
}
|
|
2090
|
+
const clipped = truncateTextWithSecretAwareness(collectedLines.join("\n"), maxColumns);
|
|
2091
|
+
if (clipped.wasTruncated) {
|
|
2092
|
+
collectedLines.splice(0, collectedLines.length, ...clipped.text.split("\n"));
|
|
2093
|
+
columnTruncated = maxColumns;
|
|
2106
2094
|
}
|
|
2107
2095
|
}
|
|
2108
2096
|
|
|
@@ -3136,11 +3124,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
3136
3124
|
const directionalMaxColumns = rawSelector ? 0 : resolveOutputMaxColumns(this.session.settings);
|
|
3137
3125
|
if (!rawSelector && !shouldAddDirectionalHashLines && directionalMaxColumns > 0) {
|
|
3138
3126
|
for (const segment of windowSegments(window)) {
|
|
3139
|
-
if (
|
|
3140
|
-
segment.content
|
|
3141
|
-
.split("\n")
|
|
3142
|
-
.some(line => truncateLine(line, directionalMaxColumns).wasTruncated)
|
|
3143
|
-
) {
|
|
3127
|
+
if (truncateTextWithSecretAwareness(segment.content, directionalMaxColumns).wasTruncated) {
|
|
3144
3128
|
columnTruncated = directionalMaxColumns;
|
|
3145
3129
|
break;
|
|
3146
3130
|
}
|
|
@@ -3268,12 +3252,10 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
3268
3252
|
const shouldAddHashLines = !rawSelector && displayMode.hashLines;
|
|
3269
3253
|
const maxColumns = resolveOutputMaxColumns(this.session.settings);
|
|
3270
3254
|
if (!rawSelector && !shouldAddHashLines && maxColumns > 0) {
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
columnTruncated = maxColumns;
|
|
3276
|
-
}
|
|
3255
|
+
const clipped = truncateTextWithSecretAwareness(collectedLines.join("\n"), maxColumns);
|
|
3256
|
+
if (clipped.wasTruncated) {
|
|
3257
|
+
collectedLines.splice(0, collectedLines.length, ...clipped.text.split("\n"));
|
|
3258
|
+
columnTruncated = maxColumns;
|
|
3277
3259
|
}
|
|
3278
3260
|
}
|
|
3279
3261
|
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `report_issue` — local auto-QA for tool behavior.
|
|
3
|
+
*
|
|
4
|
+
* The model records a tool whose output contradicts that tool's own
|
|
5
|
+
* description. Reports are appended to a local SQLite database
|
|
6
|
+
* (`~/.gjc/tool-issues.db`); nothing leaves the machine. Inspect them with the
|
|
7
|
+
* read tool's SQLite support, e.g.
|
|
8
|
+
* `gjc read '~/.gjc/tool-issues.db?q=SELECT tool, count(*) FROM tool_issues GROUP BY tool'`.
|
|
9
|
+
*
|
|
10
|
+
* Inspired by oh-my-pi's `xd://report_issue` auto-QA device; collector push
|
|
11
|
+
* and consent prompts are intentionally not part of this local-only port.
|
|
12
|
+
*/
|
|
13
|
+
import { Database } from "bun:sqlite";
|
|
14
|
+
import * as fs from "node:fs/promises";
|
|
15
|
+
import * as path from "node:path";
|
|
16
|
+
import type { AgentTool, AgentToolResult } from "@gajae-code/agent-core";
|
|
17
|
+
import { getToolIssuesDbPath, logger, prompt, VERSION } from "@gajae-code/utils";
|
|
18
|
+
import * as z from "zod/v4";
|
|
19
|
+
import reportIssueDescription from "../prompts/tools/report-issue.md" with { type: "text" };
|
|
20
|
+
import type { ToolSession } from "./index";
|
|
21
|
+
import { ToolError } from "./tool-errors";
|
|
22
|
+
|
|
23
|
+
/** Upper bound on the stored tool name, in UTF-8 bytes. */
|
|
24
|
+
const MAX_TOOL_BYTES = 128;
|
|
25
|
+
/** Upper bound on the stored report body, in characters. */
|
|
26
|
+
const MAX_REPORT_CHARS = 4_000;
|
|
27
|
+
|
|
28
|
+
const reportIssueSchema = z.object({
|
|
29
|
+
tool: z.string().describe("name of the tool that misbehaved"),
|
|
30
|
+
report: z.string().describe("what was called, what was expected, what happened"),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export type ReportIssueToolInput = z.infer<typeof reportIssueSchema>;
|
|
34
|
+
|
|
35
|
+
export interface ReportIssueToolDetails {
|
|
36
|
+
recorded: boolean;
|
|
37
|
+
tool: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ToolIssueEntry {
|
|
41
|
+
tool: string;
|
|
42
|
+
report: string;
|
|
43
|
+
model: string | null;
|
|
44
|
+
sessionId: string | null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Open the database for a single report. Reports are rare, so no connection is cached: a file the
|
|
49
|
+
* user deleted is recreated on the next report, and no handle keeps the file locked between reports.
|
|
50
|
+
*/
|
|
51
|
+
async function openToolIssueDb(dbPath: string): Promise<Database> {
|
|
52
|
+
// Only a directory created here gets 0700; an existing shared config/data root keeps its mode.
|
|
53
|
+
await fs.mkdir(path.dirname(dbPath), { recursive: true, mode: 0o700 });
|
|
54
|
+
// Create (or tighten) the database file before SQLite opens it. SQLite's unix VFS creates the
|
|
55
|
+
// WAL/SHM sidecars with the main file's permissions, so they are private from their first byte.
|
|
56
|
+
const handle = await fs.open(dbPath, "a", 0o600);
|
|
57
|
+
try {
|
|
58
|
+
if (process.platform !== "win32") await handle.chmod(0o600);
|
|
59
|
+
} finally {
|
|
60
|
+
await handle.close();
|
|
61
|
+
}
|
|
62
|
+
const db = new Database(dbPath);
|
|
63
|
+
try {
|
|
64
|
+
db.run(`
|
|
65
|
+
PRAGMA busy_timeout=5000;
|
|
66
|
+
PRAGMA journal_mode=WAL;
|
|
67
|
+
CREATE TABLE IF NOT EXISTS tool_issues (
|
|
68
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
69
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
70
|
+
tool TEXT NOT NULL,
|
|
71
|
+
report TEXT NOT NULL,
|
|
72
|
+
model TEXT,
|
|
73
|
+
gjc_version TEXT NOT NULL,
|
|
74
|
+
session_id TEXT
|
|
75
|
+
);
|
|
76
|
+
CREATE INDEX IF NOT EXISTS idx_tool_issues_tool_created ON tool_issues(tool, created_at);
|
|
77
|
+
`);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
db.close();
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
return db;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function truncateUtf8(text: string, maxBytes: number): string {
|
|
86
|
+
const bytes = Buffer.from(text, "utf8");
|
|
87
|
+
if (bytes.length <= maxBytes) return text;
|
|
88
|
+
// Decoding a cut multi-byte sequence yields U+FFFD; drop it.
|
|
89
|
+
return bytes
|
|
90
|
+
.subarray(0, maxBytes)
|
|
91
|
+
.toString("utf8")
|
|
92
|
+
.replace(/\uFFFD+$/u, "");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Truncate text by complete Unicode code points, avoiding incomplete surrogate pairs.
|
|
97
|
+
* JavaScript strings use UTF-16 encoding, where astral characters use two code units
|
|
98
|
+
* (high surrogate + low surrogate). We must count actual code points, not UTF-16 code units.
|
|
99
|
+
*/
|
|
100
|
+
function truncateByCodePoint(text: string, maxChars: number): string {
|
|
101
|
+
let codePointCount = 0;
|
|
102
|
+
let result = "";
|
|
103
|
+
// Iterate through actual code points using for-of, which handles surrogate pairs correctly
|
|
104
|
+
for (const codePoint of text) {
|
|
105
|
+
if (codePointCount >= maxChars) break;
|
|
106
|
+
result += codePoint;
|
|
107
|
+
codePointCount++;
|
|
108
|
+
}
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function codePointLength(text: string): number {
|
|
113
|
+
let count = 0;
|
|
114
|
+
for (const _ of text) count++;
|
|
115
|
+
return count;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Normalize and clamp a report to the stored field limits. */
|
|
119
|
+
export function normalizeToolIssue(tool: string, report: string): { tool: string; report: string } {
|
|
120
|
+
const normalizedTool = truncateUtf8(tool.trim(), MAX_TOOL_BYTES);
|
|
121
|
+
const trimmedReport = report.trim();
|
|
122
|
+
const normalizedReport =
|
|
123
|
+
codePointLength(trimmedReport) > MAX_REPORT_CHARS
|
|
124
|
+
? `${truncateByCodePoint(trimmedReport, MAX_REPORT_CHARS - 1)}…`
|
|
125
|
+
: trimmedReport;
|
|
126
|
+
return { tool: normalizedTool, report: normalizedReport };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Append one report to the tool-issue database at `dbPath`. */
|
|
130
|
+
export async function recordToolIssue(dbPath: string, entry: ToolIssueEntry): Promise<void> {
|
|
131
|
+
const db = await openToolIssueDb(dbPath);
|
|
132
|
+
try {
|
|
133
|
+
db.run("INSERT INTO tool_issues (tool, report, model, gjc_version, session_id) VALUES (?, ?, ?, ?, ?)", [
|
|
134
|
+
entry.tool,
|
|
135
|
+
entry.report,
|
|
136
|
+
entry.model,
|
|
137
|
+
VERSION,
|
|
138
|
+
entry.sessionId,
|
|
139
|
+
]);
|
|
140
|
+
} finally {
|
|
141
|
+
db.close();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export class ReportIssueTool implements AgentTool<typeof reportIssueSchema, ReportIssueToolDetails> {
|
|
146
|
+
readonly name = "report_issue";
|
|
147
|
+
readonly label = "Report Issue";
|
|
148
|
+
readonly loadMode = "essential" as const;
|
|
149
|
+
readonly description = prompt.render(reportIssueDescription);
|
|
150
|
+
readonly parameters = reportIssueSchema;
|
|
151
|
+
readonly strict = true;
|
|
152
|
+
readonly intent = "omit" as const;
|
|
153
|
+
readonly #session: ToolSession;
|
|
154
|
+
readonly #dbPath: string;
|
|
155
|
+
|
|
156
|
+
constructor(session: ToolSession, dbPath: string = getToolIssuesDbPath()) {
|
|
157
|
+
this.#session = session;
|
|
158
|
+
this.#dbPath = dbPath;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async execute(_toolCallId: string, params: ReportIssueToolInput): Promise<AgentToolResult<ReportIssueToolDetails>> {
|
|
162
|
+
const { tool, report } = normalizeToolIssue(params.tool, params.report);
|
|
163
|
+
if (!tool) throw new ToolError("tool must name the tool that misbehaved.");
|
|
164
|
+
if (!report) throw new ToolError("report must describe the unexpected behavior.");
|
|
165
|
+
try {
|
|
166
|
+
await recordToolIssue(this.#dbPath, {
|
|
167
|
+
tool,
|
|
168
|
+
report,
|
|
169
|
+
model: this.#session.getActiveModelString?.() ?? null,
|
|
170
|
+
sessionId: this.#session.getSessionId?.() ?? null,
|
|
171
|
+
});
|
|
172
|
+
} catch (error) {
|
|
173
|
+
logger.warn("report_issue: failed to record tool issue", { error: String(error), dbPath: this.#dbPath });
|
|
174
|
+
return {
|
|
175
|
+
content: [{ type: "text", text: "Report could not be stored locally; continue the task." }],
|
|
176
|
+
details: { recorded: false, tool },
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
content: [{ type: "text", text: "Recorded. Continue the task." }],
|
|
181
|
+
details: { recorded: true, tool },
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
* producer is available (resumed-from-disk or backward-compat records).
|
|
9
9
|
*/
|
|
10
10
|
import type { Component } from "@gajae-code/tui";
|
|
11
|
-
import { Text } from "@gajae-code/tui";
|
|
11
|
+
import { getKeybindings, Text } from "@gajae-code/tui";
|
|
12
12
|
import { formatNumber } from "@gajae-code/utils";
|
|
13
|
+
import { isSettingsInitialized, settings } from "../config/settings";
|
|
13
14
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
14
15
|
import type { Theme } from "../modes/theme/theme";
|
|
15
16
|
import { providerRetryPhaseLabel } from "../task/provider-retry-status";
|
|
@@ -18,21 +19,26 @@ import {
|
|
|
18
19
|
formatDuration,
|
|
19
20
|
formatStatusIcon,
|
|
20
21
|
getPreviewLines,
|
|
22
|
+
pluralize,
|
|
21
23
|
replaceTabs,
|
|
22
24
|
type ToolUIStatus,
|
|
23
25
|
truncateToWidth,
|
|
26
|
+
wrapTextWithAnsi,
|
|
24
27
|
} from "./render-utils";
|
|
25
28
|
import {
|
|
26
29
|
SUBAGENT_ACTIVITY_BUCKET_MS,
|
|
30
|
+
SUBAGENT_COMPACT_IDLE_MS,
|
|
27
31
|
type SubagentLiveProgress,
|
|
28
32
|
type SubagentSnapshot,
|
|
29
33
|
type SubagentToolDetails,
|
|
30
34
|
subagentAwaitRenderedStateSignature,
|
|
35
|
+
summarizeSubagentGroup,
|
|
31
36
|
} from "./subagent";
|
|
32
37
|
|
|
33
38
|
export { subagentAwaitRenderedStateSignature } from "./subagent";
|
|
34
39
|
|
|
35
40
|
const PREVIEW_LINES_COLLAPSED = 1;
|
|
41
|
+
const GUIDANCE_MAX_LINES = 12;
|
|
36
42
|
const PREVIEW_LINES_EXPANDED = 4;
|
|
37
43
|
const PREVIEW_LINE_WIDTH = 80;
|
|
38
44
|
|
|
@@ -114,10 +120,90 @@ function collectProviderDegradationGroups(
|
|
|
114
120
|
.sort((a, b) => b.count - a.count || a.provider.localeCompare(b.provider));
|
|
115
121
|
}
|
|
116
122
|
|
|
123
|
+
function compactSubagentPanelEnabled(): boolean {
|
|
124
|
+
return isSettingsInitialized() && settings.get("task.compactSubagentPanel") === true;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// One summary line for the whole group. Counts only, so the line stays O(1) in the
|
|
128
|
+
// number of subagents; each subagent is still one expand toggle away.
|
|
129
|
+
function renderCompactSubagentSummary(subagents: readonly SubagentSnapshot[], theme: Theme): string {
|
|
130
|
+
const summary = summarizeSubagentGroup(subagents, Date.now());
|
|
131
|
+
const parts: string[] = [];
|
|
132
|
+
if (summary.running > 0) parts.push(`${summary.running} ${pluralize("subagent", summary.running)} running`);
|
|
133
|
+
// Attention-worthy counts come first so a narrow terminal truncates the rest.
|
|
134
|
+
if (summary.failed > 0) parts.push(`${summary.failed} failed`);
|
|
135
|
+
if (summary.retrying > 0) parts.push(`${summary.retrying} retrying`);
|
|
136
|
+
if (summary.idle > 0) parts.push(`${summary.idle} idle >${SUBAGENT_COMPACT_IDLE_MS / 1000}s`);
|
|
137
|
+
if (summary.queued > 0) parts.push(`${summary.queued} queued`);
|
|
138
|
+
if (summary.paused > 0) parts.push(`${summary.paused} paused`);
|
|
139
|
+
if (summary.completed > 0) parts.push(`${summary.completed} done`);
|
|
140
|
+
if (summary.cancelled > 0) parts.push(`${summary.cancelled} cancelled`);
|
|
141
|
+
return ` ${theme.fg("dim", parts.join(" · "))}`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// At most one warning row in compact mode, however many providers degrade, so the
|
|
145
|
+
// collapsed height stays independent of the subagent count. The expanded view keeps
|
|
146
|
+
// one row per provider.
|
|
147
|
+
function renderCompactDegradationLine(
|
|
148
|
+
groups: ReadonlyArray<{ provider: string; count: number }>,
|
|
149
|
+
theme: Theme,
|
|
150
|
+
): string | undefined {
|
|
151
|
+
const [worst] = groups;
|
|
152
|
+
if (!worst) return undefined;
|
|
153
|
+
const more = groups.length > 1 ? ` (+${groups.length - 1} more ${pluralize("provider", groups.length - 1)})` : "";
|
|
154
|
+
return ` ${theme.fg("warning", `provider degraded: ${worst.count} subagents retrying on ${worst.provider}${more}`)}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// The expand hint gets its own line so a long summary never truncates it away.
|
|
158
|
+
function renderCompactSubagentHint(running: boolean, theme: Theme): string {
|
|
159
|
+
const expandKey = getKeybindings().getKeys("app.tools.expand")[0];
|
|
160
|
+
const hints = [expandKey ? `${expandKey} to list each` : "expand tool output to list each"];
|
|
161
|
+
if (running) hints.push("ctrl+s to observe sessions");
|
|
162
|
+
return ` ${theme.fg("dim", `(${hints.join(" · ")})`)}`;
|
|
163
|
+
}
|
|
164
|
+
|
|
117
165
|
function boundSubagentBodyLines(lines: string[], width: number): string[] {
|
|
118
166
|
return lines.map(line => (line.length > 0 ? truncateToWidth(replaceTabs(line), width, Ellipsis.Omit) : ""));
|
|
119
167
|
}
|
|
120
168
|
|
|
169
|
+
// Guidance is an instruction the caller must act on (e.g. "inspect before
|
|
170
|
+
// cancelling" after an await timeout), so it wraps to the terminal width instead
|
|
171
|
+
// of being cut to one preview line.
|
|
172
|
+
function renderSubagentGuidanceLines(snapshot: SubagentSnapshot, width: number, theme: Theme): string[] {
|
|
173
|
+
const guidance = snapshot.guidance?.trim();
|
|
174
|
+
if (!guidance) return [];
|
|
175
|
+
const indent = " ";
|
|
176
|
+
const wrapWidth = Math.max(1, width - indent.length);
|
|
177
|
+
const wrapped = guidance.split("\n").flatMap(segment => wrapTextWithAnsi(replaceTabs(segment), wrapWidth));
|
|
178
|
+
// Still bounded: producer guidance is a short fixed sentence, so the cap only
|
|
179
|
+
// bites on pathologically narrow terminals.
|
|
180
|
+
if (wrapped.length > GUIDANCE_MAX_LINES) {
|
|
181
|
+
wrapped.length = GUIDANCE_MAX_LINES;
|
|
182
|
+
wrapped[GUIDANCE_MAX_LINES - 1] = truncateToWidth(
|
|
183
|
+
`${wrapped[GUIDANCE_MAX_LINES - 1]!}…`,
|
|
184
|
+
wrapWidth,
|
|
185
|
+
Ellipsis.Unicode,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
return wrapped.map(line => `${indent}${theme.fg("dim", line)}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function renderBoundSubagentBody(
|
|
192
|
+
snapshot: SubagentSnapshot,
|
|
193
|
+
expanded: boolean,
|
|
194
|
+
width: number,
|
|
195
|
+
theme: Theme,
|
|
196
|
+
staticTime: boolean,
|
|
197
|
+
): string[] {
|
|
198
|
+
return boundSubagentBodyLines(
|
|
199
|
+
[
|
|
200
|
+
...renderSubagentSnapshotBody(snapshot, expanded, theme, staticTime),
|
|
201
|
+
...renderSubagentGuidanceLines(snapshot, width, theme),
|
|
202
|
+
],
|
|
203
|
+
width,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
121
207
|
function renderCachedSubagentBody(
|
|
122
208
|
snapshot: SubagentSnapshot,
|
|
123
209
|
signature: string,
|
|
@@ -133,7 +219,7 @@ function renderCachedSubagentBody(
|
|
|
133
219
|
subagentBodyCache.set(key, hit);
|
|
134
220
|
return hit;
|
|
135
221
|
}
|
|
136
|
-
const lines =
|
|
222
|
+
const lines = renderBoundSubagentBody(snapshot, expanded, width, theme, true);
|
|
137
223
|
subagentBodyRenderCount += 1;
|
|
138
224
|
subagentBodyCache.set(key, lines);
|
|
139
225
|
if (subagentBodyCache.size > SUBAGENT_BODY_CACHE_MAX) {
|
|
@@ -150,7 +236,7 @@ function renderDynamicSubagentBody(
|
|
|
150
236
|
theme: Theme,
|
|
151
237
|
): string[] {
|
|
152
238
|
subagentBodyRenderCount += 1;
|
|
153
|
-
return
|
|
239
|
+
return renderBoundSubagentBody(snapshot, expanded, width, theme, false);
|
|
154
240
|
}
|
|
155
241
|
|
|
156
242
|
function statusIconKind(status: SubagentSnapshot["status"]): ToolUIStatus {
|
|
@@ -363,11 +449,7 @@ function renderSubagentSnapshotBody(
|
|
|
363
449
|
}
|
|
364
450
|
}
|
|
365
451
|
|
|
366
|
-
|
|
367
|
-
for (const line of getPreviewLines(snapshot.guidance, 1, PREVIEW_LINE_WIDTH, Ellipsis.Unicode)) {
|
|
368
|
-
lines.push(` ${theme.fg("dim", replaceTabs(line))}`);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
452
|
+
// `snapshot.guidance` is appended by renderBoundSubagentBody, wrapped to width.
|
|
371
453
|
|
|
372
454
|
return lines;
|
|
373
455
|
}
|
|
@@ -421,6 +503,29 @@ export const subagentToolRenderer = {
|
|
|
421
503
|
theme,
|
|
422
504
|
);
|
|
423
505
|
const out: string[] = [truncateToWidth(replaceTabs(header), width, Ellipsis.Omit)];
|
|
506
|
+
const liveProgress = subagents.flatMap(snapshot =>
|
|
507
|
+
snapshot.progress && snapshot.liveProgressAvailable !== false ? [snapshot.progress] : [],
|
|
508
|
+
);
|
|
509
|
+
const degradationGroups = collectProviderDegradationGroups(liveProgress);
|
|
510
|
+
// Compact applies only to live await progress. Final results (timeout
|
|
511
|
+
// guidance, failure and completion receipts, status/steer replies) keep
|
|
512
|
+
// the full per-subagent rendering.
|
|
513
|
+
if (!expanded && options.isPartial && compactSubagentPanelEnabled()) {
|
|
514
|
+
out.push(
|
|
515
|
+
truncateToWidth(replaceTabs(renderCompactSubagentSummary(subagents, theme)), width, Ellipsis.Unicode),
|
|
516
|
+
);
|
|
517
|
+
const compactDegradation = renderCompactDegradationLine(degradationGroups, theme);
|
|
518
|
+
if (compactDegradation)
|
|
519
|
+
out.push(truncateToWidth(replaceTabs(compactDegradation), width, Ellipsis.Unicode));
|
|
520
|
+
out.push(
|
|
521
|
+
truncateToWidth(
|
|
522
|
+
replaceTabs(renderCompactSubagentHint(runningCount > 0, theme)),
|
|
523
|
+
width,
|
|
524
|
+
Ellipsis.Unicode,
|
|
525
|
+
),
|
|
526
|
+
);
|
|
527
|
+
return out;
|
|
528
|
+
}
|
|
424
529
|
// Discoverability: the inline panel is a bounded preview; the session
|
|
425
530
|
// observer (ctrl+s) streams the full per-subagent message history.
|
|
426
531
|
if (runningCount > 0) {
|
|
@@ -432,10 +537,7 @@ export const subagentToolRenderer = {
|
|
|
432
537
|
),
|
|
433
538
|
);
|
|
434
539
|
}
|
|
435
|
-
const
|
|
436
|
-
snapshot.progress && snapshot.liveProgressAvailable !== false ? [snapshot.progress] : [],
|
|
437
|
-
);
|
|
438
|
-
for (const group of collectProviderDegradationGroups(liveProgress)) {
|
|
540
|
+
for (const group of degradationGroups) {
|
|
439
541
|
out.push(
|
|
440
542
|
truncateToWidth(
|
|
441
543
|
replaceTabs(
|