gsd-pi 2.80.0-dev.e146beb20 → 2.80.0-dev.e6c48c3af
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/README.md +4 -2
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/phases.js +59 -21
- package/dist/resources/extensions/gsd/auto/resolve.js +17 -0
- package/dist/resources/extensions/gsd/auto/run-unit.js +17 -2
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +1 -1
- package/dist/resources/extensions/gsd/auto-prompts.js +13 -1
- package/dist/resources/extensions/gsd/auto-recovery.js +43 -1
- package/dist/resources/extensions/gsd/auto-supervisor.js +8 -1
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +2 -2
- package/dist/resources/extensions/gsd/auto.js +84 -5
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +21 -2
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +27 -20
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +75 -4
- package/dist/resources/extensions/gsd/clean-root-preflight.js +24 -6
- package/dist/resources/extensions/gsd/context-budget.js +37 -2
- package/dist/resources/extensions/gsd/db/unit-dispatches.js +39 -0
- package/dist/resources/extensions/gsd/db-base-schema.js +4 -2
- package/dist/resources/extensions/gsd/db-migration-steps.js +6 -0
- package/dist/resources/extensions/gsd/git-service.js +36 -4
- package/dist/resources/extensions/gsd/gsd-db.js +46 -13
- package/dist/resources/extensions/gsd/guided-flow.js +33 -4
- package/dist/resources/extensions/gsd/memory-store.js +69 -12
- package/dist/resources/extensions/gsd/migrate/command.js +40 -1
- package/dist/resources/extensions/gsd/migration-auto-check.js +87 -0
- package/dist/resources/extensions/gsd/pre-execution-checks.js +7 -0
- package/dist/resources/extensions/gsd/prompt-loader.js +28 -2
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +16 -13
- package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
- package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -5
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
- package/dist/resources/extensions/gsd/quick.js +34 -2
- package/dist/resources/extensions/gsd/tools/context-mode-tool-result.js +15 -0
- package/dist/resources/extensions/gsd/tools/exec-search-tool.js +5 -0
- package/dist/resources/extensions/gsd/tools/exec-tool.js +3 -15
- package/dist/resources/extensions/gsd/tools/memory-tools.js +1 -0
- package/dist/resources/extensions/gsd/tools/resume-tool.js +5 -0
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +1 -1
- package/dist/resources/extensions/gsd/unit-context-composer.js +12 -3
- package/dist/resources/extensions/gsd/unit-runtime.js +11 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +33 -17
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +16 -16
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +16 -16
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +3 -3
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +22 -17
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/workflow-tools.test.ts +75 -2
- package/packages/mcp-server/src/workflow-tools.ts +30 -16
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/native/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +32 -0
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +15 -0
- package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +12 -3
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +3 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +11 -0
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js +9 -0
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js +103 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.js +3 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +12 -0
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +20 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +25 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +13 -5
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +53 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +36 -0
- package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +18 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +14 -3
- package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +3 -1
- package/packages/pi-coding-agent/src/core/compaction/compaction.ts +18 -0
- package/packages/pi-coding-agent/src/core/compaction-threshold.test.ts +121 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +2 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.ts +5 -0
- package/packages/pi-coding-agent/src/core/extensions/types.ts +12 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +39 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +4 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +56 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +22 -7
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +3 -0
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +18 -8
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/src/tui.ts +20 -8
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -2
- package/src/resources/extensions/gsd/auto/phases.ts +85 -35
- package/src/resources/extensions/gsd/auto/resolve.ts +23 -1
- package/src/resources/extensions/gsd/auto/run-unit.ts +22 -2
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +1 -1
- package/src/resources/extensions/gsd/auto-prompts.ts +17 -1
- package/src/resources/extensions/gsd/auto-recovery.ts +54 -0
- package/src/resources/extensions/gsd/auto-supervisor.ts +7 -0
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +2 -2
- package/src/resources/extensions/gsd/auto.ts +96 -4
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +21 -1
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +27 -19
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +88 -4
- package/src/resources/extensions/gsd/clean-root-preflight.ts +32 -7
- package/src/resources/extensions/gsd/context-budget.ts +44 -2
- package/src/resources/extensions/gsd/db/unit-dispatches.ts +41 -0
- package/src/resources/extensions/gsd/db-base-schema.ts +4 -2
- package/src/resources/extensions/gsd/db-migration-steps.ts +8 -0
- package/src/resources/extensions/gsd/git-service.ts +46 -8
- package/src/resources/extensions/gsd/gsd-db.ts +50 -13
- package/src/resources/extensions/gsd/guided-flow.ts +49 -4
- package/src/resources/extensions/gsd/memory-store.ts +77 -12
- package/src/resources/extensions/gsd/migrate/command.ts +47 -1
- package/src/resources/extensions/gsd/migration-auto-check.ts +129 -0
- package/src/resources/extensions/gsd/pre-execution-checks.ts +7 -0
- package/src/resources/extensions/gsd/preferences-types.ts +1 -1
- package/src/resources/extensions/gsd/prompt-loader.ts +27 -2
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +16 -13
- package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
- package/src/resources/extensions/gsd/prompts/quick-task.md +1 -5
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
- package/src/resources/extensions/gsd/quick.ts +37 -2
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +215 -1
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +56 -13
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +14 -1
- package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +166 -4
- package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +15 -6
- package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +14 -1
- package/src/resources/extensions/gsd/tests/context-budget.test.ts +10 -1
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +5 -1
- package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +313 -0
- package/src/resources/extensions/gsd/tests/exec-history.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +65 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +239 -1
- package/src/resources/extensions/gsd/tests/memory-decay-factor.test.ts +90 -0
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +127 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/prompt-path-audit.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/quick-external-gsd.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +156 -0
- package/src/resources/extensions/gsd/tests/signal-handlers.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +49 -1
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/status-db-open.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +136 -4
- package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +63 -1
- package/src/resources/extensions/gsd/tools/context-mode-tool-result.ts +25 -0
- package/src/resources/extensions/gsd/tools/exec-search-tool.ts +7 -7
- package/src/resources/extensions/gsd/tools/exec-tool.ts +4 -23
- package/src/resources/extensions/gsd/tools/memory-tools.ts +1 -0
- package/src/resources/extensions/gsd/tools/resume-tool.ts +7 -7
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +1 -1
- package/src/resources/extensions/gsd/unit-context-composer.ts +19 -4
- package/src/resources/extensions/gsd/unit-runtime.ts +11 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +36 -15
- /package/dist/web/standalone/.next/static/{y73quA-XdLo9n41nxphjW → 4dQ9NTZJ8pEvFwBgDUX93}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{y73quA-XdLo9n41nxphjW → 4dQ9NTZJ8pEvFwBgDUX93}/_ssgManifest.js +0 -0
|
@@ -120,6 +120,7 @@ describe("#4243 — abort() must run before _disconnectFromAgent()", () => {
|
|
|
120
120
|
|
|
121
121
|
it("newSession() invokes abort() before _disconnectFromAgent()", async () => {
|
|
122
122
|
const session = await createSession();
|
|
123
|
+
(session as any).agent.state.isStreaming = true;
|
|
123
124
|
const order = recordCallOrder(session as any, ["abort", "_disconnectFromAgent"]);
|
|
124
125
|
|
|
125
126
|
const ok = await session.newSession();
|
|
@@ -138,6 +139,40 @@ describe("#4243 — abort() must run before _disconnectFromAgent()", () => {
|
|
|
138
139
|
);
|
|
139
140
|
});
|
|
140
141
|
|
|
142
|
+
it("newSession() waits instead of aborting when the prior turn is idle but not settled", async () => {
|
|
143
|
+
const session = await createSession();
|
|
144
|
+
const order: string[] = [];
|
|
145
|
+
let releaseIdle!: () => void;
|
|
146
|
+
const idle = new Promise<void>((resolve) => {
|
|
147
|
+
releaseIdle = resolve;
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
(session as any).agent.state.isStreaming = false;
|
|
151
|
+
(session as any).agent.waitForIdle = () => {
|
|
152
|
+
order.push("waitForIdle");
|
|
153
|
+
return idle;
|
|
154
|
+
};
|
|
155
|
+
(session as any).abort = async () => {
|
|
156
|
+
order.push("abort");
|
|
157
|
+
};
|
|
158
|
+
const originalDisconnect = (session as any)._disconnectFromAgent.bind(session);
|
|
159
|
+
(session as any)._disconnectFromAgent = () => {
|
|
160
|
+
order.push("_disconnectFromAgent");
|
|
161
|
+
originalDisconnect();
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const pendingNewSession = session.newSession();
|
|
165
|
+
await Promise.resolve();
|
|
166
|
+
assert.deepEqual(order, ["waitForIdle"]);
|
|
167
|
+
assert.equal(order.includes("abort"), false);
|
|
168
|
+
|
|
169
|
+
releaseIdle();
|
|
170
|
+
const ok = await pendingNewSession;
|
|
171
|
+
assert.equal(ok, true);
|
|
172
|
+
assert.deepEqual(order, ["waitForIdle", "_disconnectFromAgent"]);
|
|
173
|
+
assert.equal(order.includes("abort"), false);
|
|
174
|
+
});
|
|
175
|
+
|
|
141
176
|
it("newSession() waits instead of aborting while agent_end processing is still streaming", async () => {
|
|
142
177
|
const session = await createSession();
|
|
143
178
|
const order: string[] = [];
|
|
@@ -432,6 +467,7 @@ describe("#4243 — abort() must run before _disconnectFromAgent()", () => {
|
|
|
432
467
|
const sessionFile = session.sessionFile;
|
|
433
468
|
assert.ok(typeof sessionFile === "string" && sessionFile.length > 0, "need a session file to switch to");
|
|
434
469
|
|
|
470
|
+
(session as any).agent.state.isStreaming = true;
|
|
435
471
|
const order = recordCallOrder(session as any, ["abort", "_disconnectFromAgent"]);
|
|
436
472
|
|
|
437
473
|
const ok = await session.switchSession(sessionFile);
|
|
@@ -130,4 +130,22 @@ describe("#3616 — newSession() restores narrowed tool set when cwd unchanged",
|
|
|
130
130
|
"cwd-changed branch must rebuild with includeAllExtensionTools: true",
|
|
131
131
|
);
|
|
132
132
|
});
|
|
133
|
+
|
|
134
|
+
it("uses explicit cwd option instead of process.cwd() when rebuilding runtime", async () => {
|
|
135
|
+
const session = await createSession();
|
|
136
|
+
const explicitCwd = mkdtempSync(join(testDir, "explicit-cwd-"));
|
|
137
|
+
(session as any)._cwd = process.cwd();
|
|
138
|
+
|
|
139
|
+
let buildRuntimeCalled = false;
|
|
140
|
+
const originalBuild = (session as any)._buildRuntime.bind(session);
|
|
141
|
+
(session as any)._buildRuntime = (options?: { includeAllExtensionTools?: boolean }) => {
|
|
142
|
+
buildRuntimeCalled = true;
|
|
143
|
+
return originalBuild(options);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const ok = await session.newSession({ cwd: explicitCwd });
|
|
147
|
+
assert.equal(ok, true);
|
|
148
|
+
assert.equal((session as any)._cwd, explicitCwd);
|
|
149
|
+
assert.ok(buildRuntimeCalled, "explicit cwd differing from prior cwd must rebuild runtime");
|
|
150
|
+
});
|
|
133
151
|
});
|
|
@@ -1626,6 +1626,11 @@ export class AgentSession {
|
|
|
1626
1626
|
// message_end/agent_end events fire while listeners are still connected.
|
|
1627
1627
|
// During agent_end handling the turn is already ending; aborting there can
|
|
1628
1628
|
// convert a successful auto-mode handoff into an aborted provider message.
|
|
1629
|
+
if (!this.agent.state.isStreaming) {
|
|
1630
|
+
this._retryHandler.abortRetry();
|
|
1631
|
+
await this.agent.waitForIdle();
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1629
1634
|
await this.abort();
|
|
1630
1635
|
}
|
|
1631
1636
|
|
|
@@ -1640,6 +1645,8 @@ export class AgentSession {
|
|
|
1640
1645
|
async newSession(options?: {
|
|
1641
1646
|
parentSession?: string;
|
|
1642
1647
|
setup?: (sessionManager: SessionManager) => Promise<void>;
|
|
1648
|
+
/** Explicit working directory for the new session/tool runtime. */
|
|
1649
|
+
cwd?: string;
|
|
1643
1650
|
/** See ExtensionCommandContext.newSession for docs (#3731). */
|
|
1644
1651
|
abortSignal?: AbortSignal;
|
|
1645
1652
|
}): Promise<boolean> {
|
|
@@ -1674,10 +1681,11 @@ export class AgentSession {
|
|
|
1674
1681
|
} finally {
|
|
1675
1682
|
this._sessionSwitchPending = false;
|
|
1676
1683
|
}
|
|
1677
|
-
// Update cwd
|
|
1678
|
-
//
|
|
1684
|
+
// Update cwd for the new tool runtime. Auto-mode passes an explicit cwd
|
|
1685
|
+
// so session routing does not depend on global process.cwd() after
|
|
1686
|
+
// worktree merge/teardown. Other callers keep the historical behavior.
|
|
1679
1687
|
const previousCwd = this._cwd;
|
|
1680
|
-
this._cwd = process.cwd();
|
|
1688
|
+
this._cwd = options?.cwd ?? process.cwd();
|
|
1681
1689
|
this.sessionManager.newSession({ parentSession: options?.parentSession });
|
|
1682
1690
|
this.agent.sessionId = this.sessionManager.getSessionId();
|
|
1683
1691
|
this._steeringMessages = [];
|
|
@@ -2211,6 +2219,9 @@ export class AgentSession {
|
|
|
2211
2219
|
})();
|
|
2212
2220
|
},
|
|
2213
2221
|
getSystemPrompt: () => this.systemPrompt,
|
|
2222
|
+
setCompactionThresholdOverride: (percent) => {
|
|
2223
|
+
this.settingsManager.setCompactionThresholdOverride(percent);
|
|
2224
|
+
},
|
|
2214
2225
|
},
|
|
2215
2226
|
);
|
|
2216
2227
|
}
|
|
@@ -1080,7 +1080,9 @@ test("chat-controller rolls up low-signal direct tool execution events on agent_
|
|
|
1080
1080
|
} as any);
|
|
1081
1081
|
}
|
|
1082
1082
|
|
|
1083
|
-
assert.equal(host.chatContainer.children.length,
|
|
1083
|
+
assert.equal(host.chatContainer.children.length, 1, "direct tool events roll up as they finish");
|
|
1084
|
+
assert.equal(host.chatContainer.children[0]?.constructor?.name, "ToolPhaseSummaryComponent");
|
|
1085
|
+
assert.match(host.chatContainer.children[0].render(120).join("\n"), /Setup \/ shell 3 actions/);
|
|
1084
1086
|
|
|
1085
1087
|
await handleAgentEvent(host, { type: "agent_end" } as any);
|
|
1086
1088
|
|
|
@@ -86,6 +86,13 @@ export interface CompactionSettings {
|
|
|
86
86
|
enabled: boolean;
|
|
87
87
|
reserveTokens: number;
|
|
88
88
|
keepRecentTokens: number;
|
|
89
|
+
/**
|
|
90
|
+
* Optional percent-of-context-window threshold (0 < value < 1). When set,
|
|
91
|
+
* `shouldCompact()` fires once `contextTokens > contextWindow * thresholdPercent`,
|
|
92
|
+
* overriding the absolute `reserveTokens` calculation. Lets host integrations
|
|
93
|
+
* (e.g. GSD) express compaction policy as a fraction independent of model size.
|
|
94
|
+
*/
|
|
95
|
+
thresholdPercent?: number;
|
|
89
96
|
}
|
|
90
97
|
|
|
91
98
|
export const DEFAULT_COMPACTION_SETTINGS: CompactionSettings = {
|
|
@@ -185,9 +192,20 @@ export function estimateContextTokens(messages: AgentMessage[]): ContextUsageEst
|
|
|
185
192
|
|
|
186
193
|
/**
|
|
187
194
|
* Check if compaction should trigger based on context usage.
|
|
195
|
+
*
|
|
196
|
+
* When `thresholdPercent` is set (and within (0, 1)), it overrides the absolute
|
|
197
|
+
* `reserveTokens` calculation: compaction fires at `contextWindow * thresholdPercent`.
|
|
198
|
+
* Otherwise the legacy `contextWindow - reserveTokens` headroom is used.
|
|
188
199
|
*/
|
|
189
200
|
export function shouldCompact(contextTokens: number, contextWindow: number, settings: CompactionSettings): boolean {
|
|
190
201
|
if (!settings.enabled) return false;
|
|
202
|
+
if (
|
|
203
|
+
settings.thresholdPercent !== undefined &&
|
|
204
|
+
settings.thresholdPercent > 0 &&
|
|
205
|
+
settings.thresholdPercent < 1
|
|
206
|
+
) {
|
|
207
|
+
return contextTokens > contextWindow * settings.thresholdPercent;
|
|
208
|
+
}
|
|
191
209
|
return contextTokens > contextWindow - settings.reserveTokens;
|
|
192
210
|
}
|
|
193
211
|
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// pi-coding-agent / Regression tests for compaction threshold percent (#5475)
|
|
2
|
+
|
|
3
|
+
import assert from "node:assert/strict";
|
|
4
|
+
import { describe, it } from "node:test";
|
|
5
|
+
|
|
6
|
+
import { shouldCompact, type CompactionSettings } from "./compaction/compaction.js";
|
|
7
|
+
import { SettingsManager } from "./settings-manager.js";
|
|
8
|
+
|
|
9
|
+
const REGISTRY_DEFAULTS: CompactionSettings = {
|
|
10
|
+
enabled: true,
|
|
11
|
+
reserveTokens: 16_384,
|
|
12
|
+
keepRecentTokens: 20_000,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
describe("shouldCompact — thresholdPercent (#5475)", () => {
|
|
16
|
+
it("uses absolute reserveTokens when thresholdPercent is unset (legacy behavior)", () => {
|
|
17
|
+
// 200K window, 16384 reserve → fires at 183_617 tokens
|
|
18
|
+
assert.equal(shouldCompact(183_616, 200_000, REGISTRY_DEFAULTS), false);
|
|
19
|
+
assert.equal(shouldCompact(183_617, 200_000, REGISTRY_DEFAULTS), true);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("uses thresholdPercent when set, ignoring reserveTokens", () => {
|
|
23
|
+
const settings: CompactionSettings = { ...REGISTRY_DEFAULTS, thresholdPercent: 0.7 };
|
|
24
|
+
// 200K * 0.7 = 140_000 → fires above that
|
|
25
|
+
assert.equal(shouldCompact(140_000, 200_000, settings), false);
|
|
26
|
+
assert.equal(shouldCompact(140_001, 200_000, settings), true);
|
|
27
|
+
// reserveTokens-based math would have said false at 183_616 — the percent override changes that
|
|
28
|
+
assert.equal(shouldCompact(150_000, 200_000, settings), true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("falls back to reserveTokens when thresholdPercent is out of range", () => {
|
|
32
|
+
// Defense in depth: reject 0, 1, negative, NaN, Infinity
|
|
33
|
+
for (const bad of [0, 1, -0.1, 1.5, Number.NaN, Number.POSITIVE_INFINITY]) {
|
|
34
|
+
const settings: CompactionSettings = { ...REGISTRY_DEFAULTS, thresholdPercent: bad };
|
|
35
|
+
assert.equal(
|
|
36
|
+
shouldCompact(183_616, 200_000, settings),
|
|
37
|
+
false,
|
|
38
|
+
`bad=${bad} should fall back to reserveTokens math`,
|
|
39
|
+
);
|
|
40
|
+
assert.equal(shouldCompact(183_617, 200_000, settings), true, `bad=${bad}`);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("respects enabled=false regardless of thresholdPercent", () => {
|
|
45
|
+
const settings: CompactionSettings = {
|
|
46
|
+
...REGISTRY_DEFAULTS,
|
|
47
|
+
enabled: false,
|
|
48
|
+
thresholdPercent: 0.5,
|
|
49
|
+
};
|
|
50
|
+
assert.equal(shouldCompact(199_999, 200_000, settings), false);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("scales with contextWindow — same percent, different windows", () => {
|
|
54
|
+
const settings: CompactionSettings = { ...REGISTRY_DEFAULTS, thresholdPercent: 0.8 };
|
|
55
|
+
// 100K window: fires above 80_000
|
|
56
|
+
assert.equal(shouldCompact(80_000, 100_000, settings), false);
|
|
57
|
+
assert.equal(shouldCompact(80_001, 100_000, settings), true);
|
|
58
|
+
// 1M window: fires above 800_000
|
|
59
|
+
assert.equal(shouldCompact(800_000, 1_000_000, settings), false);
|
|
60
|
+
assert.equal(shouldCompact(800_001, 1_000_000, settings), true);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("SettingsManager — compaction threshold override (#5475)", () => {
|
|
65
|
+
it("getCompactionThresholdPercent returns undefined by default", () => {
|
|
66
|
+
const sm = SettingsManager.inMemory({});
|
|
67
|
+
assert.equal(sm.getCompactionThresholdPercent(), undefined);
|
|
68
|
+
assert.equal(sm.getCompactionSettings().thresholdPercent, undefined);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("setCompactionThresholdOverride applies in-memory and is exposed via getCompactionSettings", () => {
|
|
72
|
+
const sm = SettingsManager.inMemory({});
|
|
73
|
+
sm.setCompactionThresholdOverride(0.7);
|
|
74
|
+
assert.equal(sm.getCompactionThresholdPercent(), 0.7);
|
|
75
|
+
assert.equal(sm.getCompactionSettings().thresholdPercent, 0.7);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("setCompactionThresholdOverride(undefined) clears a prior override", () => {
|
|
79
|
+
const sm = SettingsManager.inMemory({});
|
|
80
|
+
sm.setCompactionThresholdOverride(0.7);
|
|
81
|
+
sm.setCompactionThresholdOverride(undefined);
|
|
82
|
+
assert.equal(sm.getCompactionThresholdPercent(), undefined);
|
|
83
|
+
assert.equal(sm.getCompactionSettings().thresholdPercent, undefined);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("setCompactionThresholdOverride preserves other compaction fields (enabled, reserveTokens)", () => {
|
|
87
|
+
const sm = SettingsManager.inMemory({
|
|
88
|
+
compaction: { enabled: true, reserveTokens: 30_000, keepRecentTokens: 25_000 },
|
|
89
|
+
});
|
|
90
|
+
sm.setCompactionThresholdOverride(0.6);
|
|
91
|
+
const settings = sm.getCompactionSettings();
|
|
92
|
+
assert.equal(settings.enabled, true);
|
|
93
|
+
assert.equal(settings.reserveTokens, 30_000);
|
|
94
|
+
assert.equal(settings.keepRecentTokens, 25_000);
|
|
95
|
+
assert.equal(settings.thresholdPercent, 0.6);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("setCompactionThresholdOverride works when no compaction config exists yet", () => {
|
|
99
|
+
const sm = SettingsManager.inMemory({});
|
|
100
|
+
sm.setCompactionThresholdOverride(0.85);
|
|
101
|
+
assert.equal(sm.getCompactionThresholdPercent(), 0.85);
|
|
102
|
+
// Other compaction fields fall back to their defaults
|
|
103
|
+
const settings = sm.getCompactionSettings();
|
|
104
|
+
assert.equal(settings.enabled, true);
|
|
105
|
+
assert.equal(typeof settings.reserveTokens, "number");
|
|
106
|
+
assert.equal(typeof settings.keepRecentTokens, "number");
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe("end-to-end — getCompactionSettings + shouldCompact (#5475)", () => {
|
|
111
|
+
it("70% threshold on a 200K window fires at the documented bug-report value (140_001 not 183_617)", () => {
|
|
112
|
+
const sm = SettingsManager.inMemory({});
|
|
113
|
+
sm.setCompactionThresholdOverride(0.7);
|
|
114
|
+
const settings = sm.getCompactionSettings();
|
|
115
|
+
|
|
116
|
+
assert.equal(shouldCompact(140_000, 200_000, settings), false);
|
|
117
|
+
assert.equal(shouldCompact(140_001, 200_000, settings), true);
|
|
118
|
+
// Pre-fix behavior would have required 183_617 — verify we no longer wait that long
|
|
119
|
+
assert.equal(shouldCompact(150_000, 200_000, settings), true);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -141,6 +141,7 @@ describe("ExtensionRunner.emitToolCall", () => {
|
|
|
141
141
|
getContextUsage: () => undefined,
|
|
142
142
|
compact: () => {},
|
|
143
143
|
getSystemPrompt: () => "",
|
|
144
|
+
setCompactionThresholdOverride: () => {},
|
|
144
145
|
});
|
|
145
146
|
|
|
146
147
|
const errors: any[] = [];
|
|
@@ -220,6 +221,7 @@ describe("ExtensionRunner.createContext", () => {
|
|
|
220
221
|
getContextUsage: () => undefined,
|
|
221
222
|
compact: () => {},
|
|
222
223
|
getSystemPrompt: () => "",
|
|
224
|
+
setCompactionThresholdOverride: () => {},
|
|
223
225
|
});
|
|
224
226
|
|
|
225
227
|
const errors: any[] = [];
|
|
@@ -173,6 +173,8 @@ export type ExtensionErrorListener = (error: ExtensionError) => void;
|
|
|
173
173
|
export type NewSessionHandler = (options?: {
|
|
174
174
|
parentSession?: string;
|
|
175
175
|
setup?: (sessionManager: SessionManager) => Promise<void>;
|
|
176
|
+
/** Explicit working directory for the new session/tool runtime. */
|
|
177
|
+
cwd?: string;
|
|
176
178
|
/** See ExtensionCommandContext.newSession for docs (#3731). */
|
|
177
179
|
abortSignal?: AbortSignal;
|
|
178
180
|
}) => Promise<{ cancelled: boolean }>;
|
|
@@ -235,6 +237,7 @@ export class ExtensionRunner {
|
|
|
235
237
|
private getContextUsageFn: () => ContextUsage | undefined = () => undefined;
|
|
236
238
|
private compactFn: (options?: CompactOptions) => void = () => {};
|
|
237
239
|
private getSystemPromptFn: () => string = () => "";
|
|
240
|
+
private setCompactionThresholdOverrideFn: (percent: number | undefined) => void = () => {};
|
|
238
241
|
private newSessionHandler: NewSessionHandler = async () => {
|
|
239
242
|
throw new Error("Command context not yet bound: newSession is unavailable during early lifecycle");
|
|
240
243
|
};
|
|
@@ -428,6 +431,7 @@ export class ExtensionRunner {
|
|
|
428
431
|
this.getContextUsageFn = contextActions.getContextUsage;
|
|
429
432
|
this.compactFn = contextActions.compact;
|
|
430
433
|
this.getSystemPromptFn = contextActions.getSystemPrompt;
|
|
434
|
+
this.setCompactionThresholdOverrideFn = contextActions.setCompactionThresholdOverride;
|
|
431
435
|
|
|
432
436
|
// Flush provider registrations queued during extension loading
|
|
433
437
|
for (const { name, config } of this.runtime.pendingProviderRegistrations) {
|
|
@@ -714,6 +718,7 @@ export class ExtensionRunner {
|
|
|
714
718
|
getContextUsage: () => this.getContextUsageFn(),
|
|
715
719
|
compact: (options) => this.compactFn(options),
|
|
716
720
|
getSystemPrompt: () => this.getSystemPromptFn(),
|
|
721
|
+
setCompactionThresholdOverride: (percent) => this.setCompactionThresholdOverrideFn(percent),
|
|
717
722
|
};
|
|
718
723
|
}
|
|
719
724
|
|
|
@@ -289,6 +289,12 @@ export interface ExtensionContext {
|
|
|
289
289
|
compact(options?: CompactOptions): void;
|
|
290
290
|
/** Get the current effective system prompt. */
|
|
291
291
|
getSystemPrompt(): string;
|
|
292
|
+
/**
|
|
293
|
+
* Set or clear an in-memory compaction threshold-percent override (0 < value < 1).
|
|
294
|
+
* Pass `undefined` to clear. The override is not persisted; host integrations
|
|
295
|
+
* are expected to re-apply on each session_start.
|
|
296
|
+
*/
|
|
297
|
+
setCompactionThresholdOverride(percent: number | undefined): void;
|
|
292
298
|
}
|
|
293
299
|
|
|
294
300
|
/**
|
|
@@ -303,6 +309,9 @@ export interface ExtensionCommandContext extends ExtensionContext {
|
|
|
303
309
|
newSession(options?: {
|
|
304
310
|
parentSession?: string;
|
|
305
311
|
setup?: (sessionManager: SessionManager) => Promise<void>;
|
|
312
|
+
/** Explicit working directory for the new session/tool runtime.
|
|
313
|
+
* When omitted, newSession() captures process.cwd() for backwards compatibility. */
|
|
314
|
+
cwd?: string;
|
|
306
315
|
/** When aborted before the session is fully configured, newSession() returns
|
|
307
316
|
* early without rebuilding the tool runtime. Used by runUnit() to discard
|
|
308
317
|
* a late-resolving newSession() after the session-creation timeout fires,
|
|
@@ -1741,6 +1750,7 @@ export interface ExtensionContextActions {
|
|
|
1741
1750
|
getContextUsage: () => ContextUsage | undefined;
|
|
1742
1751
|
compact: (options?: CompactOptions) => void;
|
|
1743
1752
|
getSystemPrompt: () => string;
|
|
1753
|
+
setCompactionThresholdOverride: (percent: number | undefined) => void;
|
|
1744
1754
|
}
|
|
1745
1755
|
|
|
1746
1756
|
/**
|
|
@@ -1752,6 +1762,8 @@ export interface ExtensionCommandContextActions {
|
|
|
1752
1762
|
newSession: (options?: {
|
|
1753
1763
|
parentSession?: string;
|
|
1754
1764
|
setup?: (sessionManager: SessionManager) => Promise<void>;
|
|
1765
|
+
/** See ExtensionCommandContext.newSession for docs. */
|
|
1766
|
+
cwd?: string;
|
|
1755
1767
|
/** See ExtensionCommandContext.newSession for docs (#3731). */
|
|
1756
1768
|
abortSignal?: AbortSignal;
|
|
1757
1769
|
}) => Promise<{ cancelled: boolean }>;
|
|
@@ -15,6 +15,13 @@ export interface CompactionSettings {
|
|
|
15
15
|
enabled?: boolean; // default: true
|
|
16
16
|
reserveTokens?: number; // default: 16384
|
|
17
17
|
keepRecentTokens?: number; // default: 20000
|
|
18
|
+
/**
|
|
19
|
+
* Optional percent-of-context-window trigger (0 < value < 1). When set,
|
|
20
|
+
* compaction fires at `contextWindow * thresholdPercent` and overrides
|
|
21
|
+
* `reserveTokens`. Typically set as a runtime override by host integrations
|
|
22
|
+
* (see `setCompactionThresholdOverride`) and not persisted by users directly.
|
|
23
|
+
*/
|
|
24
|
+
thresholdPercent?: number;
|
|
18
25
|
}
|
|
19
26
|
|
|
20
27
|
export interface BranchSummarySettings {
|
|
@@ -812,11 +819,42 @@ export class SettingsManager {
|
|
|
812
819
|
return this.settings.compaction?.keepRecentTokens ?? COMPACTION_KEEP_RECENT_TOKENS;
|
|
813
820
|
}
|
|
814
821
|
|
|
815
|
-
|
|
822
|
+
getCompactionThresholdPercent(): number | undefined {
|
|
823
|
+
return this.settings.compaction?.thresholdPercent;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* Set or clear an in-memory compaction threshold-percent override.
|
|
828
|
+
*
|
|
829
|
+
* Applied to `this.settings` only; never persisted to disk. Pass `undefined`
|
|
830
|
+
* to clear a previously set override (necessary for idempotent re-sync from
|
|
831
|
+
* host integrations whose preference may have been removed).
|
|
832
|
+
*
|
|
833
|
+
* Direct mutation is used instead of `applyOverrides()` because deep-merge
|
|
834
|
+
* semantics skip `undefined` values, which would prevent clearing.
|
|
835
|
+
*/
|
|
836
|
+
setCompactionThresholdOverride(percent: number | undefined): void {
|
|
837
|
+
if (!this.settings.compaction) {
|
|
838
|
+
this.settings.compaction = {};
|
|
839
|
+
}
|
|
840
|
+
if (percent === undefined) {
|
|
841
|
+
delete this.settings.compaction.thresholdPercent;
|
|
842
|
+
} else {
|
|
843
|
+
this.settings.compaction.thresholdPercent = percent;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
getCompactionSettings(): {
|
|
848
|
+
enabled: boolean;
|
|
849
|
+
reserveTokens: number;
|
|
850
|
+
keepRecentTokens: number;
|
|
851
|
+
thresholdPercent?: number;
|
|
852
|
+
} {
|
|
816
853
|
return {
|
|
817
854
|
enabled: this.getCompactionEnabled(),
|
|
818
855
|
reserveTokens: this.getCompactionReserveTokens(),
|
|
819
856
|
keepRecentTokens: this.getCompactionKeepRecentTokens(),
|
|
857
|
+
thresholdPercent: this.getCompactionThresholdPercent(),
|
|
820
858
|
};
|
|
821
859
|
}
|
|
822
860
|
|
|
@@ -1287,6 +1287,10 @@ export class ToolPhaseSummaryComponent extends Container {
|
|
|
1287
1287
|
super();
|
|
1288
1288
|
}
|
|
1289
1289
|
|
|
1290
|
+
getPhases(): ToolExecutionPhase[] {
|
|
1291
|
+
return this.phases.map((phase) => ({ ...phase }));
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1290
1294
|
override render(width: number): string[] {
|
|
1291
1295
|
const frameWidth = Math.max(20, width);
|
|
1292
1296
|
const rows = this.phases.map((phase) => {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
|
+
import { Container } from "@gsd/pi-tui";
|
|
4
|
+
import stripAnsi from "strip-ansi";
|
|
3
5
|
|
|
4
6
|
import { findLatestPinnableText, handleAgentEvent } from "./chat-controller.js";
|
|
5
7
|
import { initTheme } from "../theme/theme.js";
|
|
@@ -111,3 +113,57 @@ test("handleAgentEvent: agent_start clears stale adaptive blocking error", async
|
|
|
111
113
|
assert.equal(cleared, true);
|
|
112
114
|
assert.equal(requestedRender, true);
|
|
113
115
|
});
|
|
116
|
+
|
|
117
|
+
test("handleAgentEvent: standalone completed tool events roll up incrementally", async () => {
|
|
118
|
+
initTheme("dark", false);
|
|
119
|
+
const chatContainer = new Container();
|
|
120
|
+
let renderCount = 0;
|
|
121
|
+
const host = {
|
|
122
|
+
isInitialized: true,
|
|
123
|
+
footer: { invalidate() {} },
|
|
124
|
+
settingsManager: {
|
|
125
|
+
getTimestampFormat() {
|
|
126
|
+
return "date-time-iso";
|
|
127
|
+
},
|
|
128
|
+
getShowImages() {
|
|
129
|
+
return false;
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
getRegisteredToolDefinition() {
|
|
133
|
+
return undefined;
|
|
134
|
+
},
|
|
135
|
+
chatContainer,
|
|
136
|
+
pendingTools: new Map(),
|
|
137
|
+
ui: {
|
|
138
|
+
requestRender() {
|
|
139
|
+
renderCount++;
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
} as any;
|
|
143
|
+
|
|
144
|
+
for (const [toolCallId, toolName] of [
|
|
145
|
+
["read-1", "read"],
|
|
146
|
+
["read-2", "read"],
|
|
147
|
+
["edit-1", "edit"],
|
|
148
|
+
] as const) {
|
|
149
|
+
await handleAgentEvent(host, {
|
|
150
|
+
type: "tool_execution_start",
|
|
151
|
+
toolCallId,
|
|
152
|
+
toolName,
|
|
153
|
+
args: { path: `/tmp/${toolCallId}.txt` },
|
|
154
|
+
} as any);
|
|
155
|
+
await handleAgentEvent(host, {
|
|
156
|
+
type: "tool_execution_end",
|
|
157
|
+
toolCallId,
|
|
158
|
+
toolName,
|
|
159
|
+
result: { content: [], isError: false },
|
|
160
|
+
isError: false,
|
|
161
|
+
} as any);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const rendered = stripAnsi(chatContainer.render(100).join("\n"));
|
|
165
|
+
assert.match(rendered, /Context reads 2 actions\s+success · \d+(ms|s)/);
|
|
166
|
+
assert.match(rendered, /File changes 1 action\s+success · \d+(ms|s)/);
|
|
167
|
+
assert.doesNotMatch(rendered, /\bread\s+success ·/);
|
|
168
|
+
assert.ok(renderCount > 0);
|
|
169
|
+
});
|
|
@@ -28,7 +28,8 @@ type RenderedSegment =
|
|
|
28
28
|
contentType: "text" | "thinking";
|
|
29
29
|
component: AssistantMessageComponent;
|
|
30
30
|
}
|
|
31
|
-
| { kind: "tool"; contentIndex: number; component: ToolExecutionComponent }
|
|
31
|
+
| { kind: "tool"; contentIndex: number; component: ToolExecutionComponent }
|
|
32
|
+
| { kind: "tool-summary"; component: ToolPhaseSummaryComponent; phases: ToolExecutionPhase[] };
|
|
32
33
|
|
|
33
34
|
let renderedSegments: RenderedSegment[] = [];
|
|
34
35
|
// When providers reuse one assistant lifecycle across internal sub-turns,
|
|
@@ -124,17 +125,25 @@ function replaceCompactToolRowsWithPhaseSummary(
|
|
|
124
125
|
): void {
|
|
125
126
|
let changed = false;
|
|
126
127
|
const nextRenderedSegments: RenderedSegment[] = [];
|
|
127
|
-
let rollupRun: Array<{
|
|
128
|
+
let rollupRun: Array<{
|
|
129
|
+
seg: Extract<RenderedSegment, { kind: "tool" | "tool-summary" }>;
|
|
130
|
+
phases: ToolExecutionPhase[];
|
|
131
|
+
}> = [];
|
|
128
132
|
|
|
129
133
|
const flushRollupRun = () => {
|
|
130
|
-
|
|
134
|
+
const actionCount = rollupRun.reduce(
|
|
135
|
+
(total, item) => total + item.phases.reduce((sum, phase) => sum + phase.count, 0),
|
|
136
|
+
0,
|
|
137
|
+
);
|
|
138
|
+
if (actionCount < 2) {
|
|
131
139
|
nextRenderedSegments.push(...rollupRun.map((item) => item.seg));
|
|
132
140
|
rollupRun = [];
|
|
133
141
|
return;
|
|
134
142
|
}
|
|
135
143
|
|
|
136
144
|
const firstIndex = Math.max(0, host.chatContainer.children.indexOf(rollupRun[0].seg.component));
|
|
137
|
-
const
|
|
145
|
+
const phases = mergeToolPhases(rollupRun.flatMap((item) => item.phases));
|
|
146
|
+
const summary = new ToolPhaseSummaryComponent(phases);
|
|
138
147
|
|
|
139
148
|
for (const { seg } of rollupRun) {
|
|
140
149
|
host.chatContainer.removeChild(seg.component);
|
|
@@ -149,13 +158,18 @@ function replaceCompactToolRowsWithPhaseSummary(
|
|
|
149
158
|
}
|
|
150
159
|
|
|
151
160
|
changed = true;
|
|
161
|
+
nextRenderedSegments.push({ kind: "tool-summary", component: summary, phases });
|
|
152
162
|
rollupRun = [];
|
|
153
163
|
};
|
|
154
164
|
|
|
155
165
|
for (const seg of renderedSegments) {
|
|
156
166
|
const phase = seg.kind === "tool" ? seg.component.getRollupPhase() : null;
|
|
157
167
|
if (seg.kind === "tool" && phase) {
|
|
158
|
-
rollupRun.push({ seg, phase });
|
|
168
|
+
rollupRun.push({ seg, phases: [phase] });
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (seg.kind === "tool-summary") {
|
|
172
|
+
rollupRun.push({ seg, phases: seg.component.getPhases() });
|
|
159
173
|
continue;
|
|
160
174
|
}
|
|
161
175
|
|
|
@@ -409,6 +423,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
409
423
|
details: externalToolResult.details,
|
|
410
424
|
isError: externalToolResult.isError,
|
|
411
425
|
});
|
|
426
|
+
replaceCompactToolRowsWithPhaseSummary(host);
|
|
412
427
|
}
|
|
413
428
|
}
|
|
414
429
|
|
|
@@ -843,7 +858,6 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
843
858
|
host.streamingComponent.setShowMetadata(true);
|
|
844
859
|
host.streamingComponent.updateContent(host.streamingMessage);
|
|
845
860
|
}
|
|
846
|
-
replaceCompactToolRowsWithPhaseSummary(host);
|
|
847
861
|
|
|
848
862
|
if (host.streamingMessage.stopReason === "aborted" || host.streamingMessage.stopReason === "error") {
|
|
849
863
|
if (!errorMessage) {
|
|
@@ -862,6 +876,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
862
876
|
for (const [, component] of host.pendingTools.entries()) {
|
|
863
877
|
component.setArgsComplete();
|
|
864
878
|
}
|
|
879
|
+
replaceCompactToolRowsWithPhaseSummary(host);
|
|
865
880
|
}
|
|
866
881
|
host.streamingComponent = undefined;
|
|
867
882
|
host.streamingMessage = undefined;
|
|
@@ -912,7 +927,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
|
|
|
912
927
|
const component = host.pendingTools.get(event.toolCallId);
|
|
913
928
|
if (component) {
|
|
914
929
|
component.updateResult({ ...event.result, isError: event.isError });
|
|
915
|
-
host
|
|
930
|
+
replaceCompactToolRowsWithPhaseSummary(host);
|
|
916
931
|
host.ui.requestRender();
|
|
917
932
|
}
|
|
918
933
|
break;
|
|
@@ -1378,6 +1378,9 @@ export class InteractiveMode {
|
|
|
1378
1378
|
})();
|
|
1379
1379
|
},
|
|
1380
1380
|
getSystemPrompt: () => this.session.systemPrompt,
|
|
1381
|
+
setCompactionThresholdOverride: (percent) => {
|
|
1382
|
+
this.session.settingsManager.setCompactionThresholdOverride(percent);
|
|
1383
|
+
},
|
|
1381
1384
|
});
|
|
1382
1385
|
|
|
1383
1386
|
// Set up the extension shortcut handler on the default editor
|