@oh-my-pi/pi-coding-agent 17.1.7 → 17.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +127 -0
- package/dist/{CHANGELOG-5k4dq4g6.md → CHANGELOG-be1f2t8h.md} +127 -0
- package/dist/cli.js +9554 -12379
- package/dist/template-8vdv6xb4.js +1671 -0
- package/dist/template-f8wx9vfn.css +1355 -0
- package/dist/template-qat058wr.html +55 -0
- package/dist/tool-views.generated-jdfmzwmn.js +35 -0
- package/dist/types/advisor/advise-tool.d.ts +0 -7
- package/dist/types/advisor/runtime.d.ts +8 -11
- package/dist/types/advisor/transcript-recorder.d.ts +15 -0
- package/dist/types/async/job-manager.d.ts +11 -0
- package/dist/types/auto-thinking/classifier.d.ts +9 -2
- package/dist/types/cleanse/agent.d.ts +19 -0
- package/dist/types/cleanse/balance.d.ts +7 -0
- package/dist/types/cleanse/checkers.d.ts +13 -0
- package/dist/types/cleanse/index.d.ts +16 -0
- package/dist/types/cleanse/loop.d.ts +16 -0
- package/dist/types/cleanse/parsers.d.ts +13 -0
- package/dist/types/cleanse/progress.d.ts +14 -0
- package/dist/types/cleanse/types.d.ts +64 -0
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/session-picker.d.ts +14 -7
- package/dist/types/collab/guest.d.ts +21 -2
- package/dist/types/commands/cleanse.d.ts +23 -0
- package/dist/types/commands/launch.d.ts +6 -0
- package/dist/types/config/settings-schema.d.ts +63 -8
- package/dist/types/cursor-bridge-tools.d.ts +54 -0
- package/dist/types/cursor.d.ts +193 -8
- package/dist/types/edit/edit-clipboard.d.ts +19 -0
- package/dist/types/edit/hashline/diff.d.ts +9 -1
- package/dist/types/edit/index.d.ts +8 -1
- package/dist/types/edit/streaming.d.ts +8 -1
- package/dist/types/export/html/index.d.ts +2 -0
- package/dist/types/extensibility/extensions/runner.d.ts +19 -2
- package/dist/types/extensibility/extensions/types.d.ts +25 -1
- package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +27 -1
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +2 -0
- package/dist/types/internal-urls/local-protocol.d.ts +3 -2
- package/dist/types/launch/terminal-output-worker-client.d.ts +5 -0
- package/dist/types/launch/terminal-output-worker-protocol.d.ts +14 -0
- package/dist/types/launch/terminal-output-worker.d.ts +1 -0
- package/dist/types/lsp/client.d.ts +2 -0
- package/dist/types/main.d.ts +2 -0
- package/dist/types/mcp/manager.d.ts +33 -4
- package/dist/types/modes/components/codex-reset-fireworks.d.ts +51 -0
- package/dist/types/modes/components/read-tool-group.d.ts +12 -0
- package/dist/types/modes/components/session-selector.d.ts +6 -0
- package/dist/types/modes/components/status-line/component.d.ts +23 -0
- package/dist/types/modes/components/tool-execution.d.ts +16 -7
- package/dist/types/modes/components/usage-row.d.ts +2 -0
- package/dist/types/modes/controllers/mcp-command-controller.d.ts +26 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
- package/dist/types/modes/interactive-mode.d.ts +8 -5
- package/dist/types/modes/rpc/rpc-client.d.ts +7 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +16 -0
- package/dist/types/modes/theme/theme.d.ts +12 -1
- package/dist/types/modes/types.d.ts +2 -1
- package/dist/types/sdk.d.ts +1 -0
- package/dist/types/session/agent-session-events.d.ts +2 -0
- package/dist/types/session/agent-session-types.d.ts +36 -1
- package/dist/types/session/agent-session.d.ts +4 -2
- package/dist/types/session/async-job-delivery.d.ts +8 -0
- package/dist/types/session/claude-session-store.d.ts +13 -0
- package/dist/types/session/codex-session-store.d.ts +14 -0
- package/dist/types/session/credential-pin.d.ts +58 -0
- package/dist/types/session/foreign-session-import.d.ts +15 -0
- package/dist/types/session/foreign-session-jsonl.d.ts +9 -0
- package/dist/types/session/foreign-session-store.d.ts +23 -0
- package/dist/types/session/model-controls.d.ts +3 -3
- package/dist/types/session/session-advisors.d.ts +47 -2
- package/dist/types/session/session-entries.d.ts +19 -1
- package/dist/types/session/session-handoff.d.ts +4 -1
- package/dist/types/session/session-loader.d.ts +2 -0
- package/dist/types/session/session-maintenance.d.ts +1 -1
- package/dist/types/session/session-manager.d.ts +21 -0
- package/dist/types/session/session-tools.d.ts +25 -1
- package/dist/types/session/turn-recovery.d.ts +10 -4
- package/dist/types/system-prompt.d.ts +2 -1
- package/dist/types/task/executor.d.ts +11 -2
- package/dist/types/task/isolation-ownership.d.ts +34 -0
- package/dist/types/task/render.d.ts +7 -0
- package/dist/types/thinking.d.ts +18 -6
- package/dist/types/tools/acp-bridge.d.ts +28 -5
- package/dist/types/tools/browser/cmux/cmux-tab.d.ts +1 -2
- package/dist/types/tools/browser/tab-worker.d.ts +1 -4
- package/dist/types/tools/grep.d.ts +17 -1
- package/dist/types/tools/hub/launch.d.ts +5 -1
- package/dist/types/tools/index.d.ts +4 -1
- package/dist/types/tools/path-utils.d.ts +19 -0
- package/dist/types/tools/read.d.ts +2 -0
- package/dist/types/tools/xdev.d.ts +2 -1
- package/dist/types/tui/output-block.d.ts +5 -5
- package/dist/types/utils/changelog.d.ts +19 -0
- package/dist/types/utils/git.d.ts +10 -0
- package/dist/types/utils/jj.d.ts +11 -4
- package/dist/types/web/search/types.d.ts +2 -2
- package/package.json +16 -12
- package/scripts/bundle-dist.ts +3 -1
- package/src/advisor/advise-tool.ts +0 -11
- package/src/advisor/runtime.ts +138 -24
- package/src/advisor/transcript-recorder.ts +56 -0
- package/src/async/job-manager.ts +30 -7
- package/src/auto-thinking/classifier.ts +48 -10
- package/src/cleanse/agent.ts +226 -0
- package/src/cleanse/balance.ts +79 -0
- package/src/cleanse/checkers.ts +996 -0
- package/src/cleanse/index.ts +190 -0
- package/src/cleanse/loop.ts +51 -0
- package/src/cleanse/parsers.ts +726 -0
- package/src/cleanse/progress.ts +50 -0
- package/src/cleanse/prompts/assignment.md +47 -0
- package/src/cleanse/types.ts +72 -0
- package/src/cli/args.ts +6 -0
- package/src/cli/flag-tables.ts +2 -0
- package/src/cli/gallery-fixtures/fs.ts +25 -0
- package/src/cli/session-picker.ts +34 -17
- package/src/cli/ttsr-cli.ts +19 -1
- package/src/cli/update-cli.ts +3 -0
- package/src/cli/usage-cli.ts +29 -4
- package/src/cli/worktree-cli.ts +28 -11
- package/src/cli-commands.ts +1 -0
- package/src/cli.ts +11 -1
- package/src/collab/guest.ts +37 -4
- package/src/collab/host.ts +1 -0
- package/src/commands/cleanse.ts +45 -0
- package/src/commands/launch.ts +6 -0
- package/src/config/model-registry.ts +103 -25
- package/src/config/settings-schema.ts +65 -6
- package/src/config/settings.ts +25 -0
- package/src/cursor-bridge-tools.ts +81 -0
- package/src/cursor.ts +463 -12
- package/src/discovery/claude-plugins.ts +144 -34
- package/src/edit/edit-clipboard.ts +23 -0
- package/src/edit/hashline/diff.ts +49 -10
- package/src/edit/hashline/execute.ts +38 -6
- package/src/edit/hashline/filesystem.ts +27 -3
- package/src/edit/index.ts +12 -2
- package/src/edit/renderer.ts +4 -4
- package/src/edit/streaming.ts +17 -2
- package/src/export/html/index.ts +17 -10
- package/src/export/html/template.js +1 -1
- package/src/extensibility/extensions/runner.ts +70 -0
- package/src/extensibility/extensions/types.ts +30 -0
- package/src/extensibility/legacy-pi-ai-shim.ts +46 -1
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +12 -13
- package/src/internal-urls/local-protocol.ts +3 -2
- package/src/internal-urls/mcp-protocol.ts +4 -1
- package/src/launch/broker.ts +14 -4
- package/src/launch/terminal-output-worker-client.ts +53 -0
- package/src/launch/terminal-output-worker-protocol.ts +11 -0
- package/src/launch/terminal-output-worker.ts +23 -0
- package/src/lsp/client.ts +18 -3
- package/src/lsp/index.ts +4 -0
- package/src/lsp/render.ts +1 -1
- package/src/main.ts +127 -56
- package/src/mcp/manager.ts +120 -18
- package/src/modes/acp/acp-agent.ts +27 -18
- package/src/modes/acp/acp-event-mapper.ts +38 -4
- package/src/modes/components/chat-transcript-builder.ts +30 -11
- package/src/modes/components/codex-reset-fireworks.ts +369 -0
- package/src/modes/components/read-tool-group.ts +159 -17
- package/src/modes/components/session-selector.ts +19 -3
- package/src/modes/components/settings-selector.ts +9 -1
- package/src/modes/components/status-line/component.ts +381 -54
- package/src/modes/components/tool-execution.ts +78 -31
- package/src/modes/components/usage-row.ts +10 -5
- package/src/modes/controllers/event-controller.ts +26 -4
- package/src/modes/controllers/mcp-command-controller.ts +68 -3
- package/src/modes/controllers/selector-controller.ts +94 -36
- package/src/modes/controllers/tan-command-controller.ts +14 -0
- package/src/modes/interactive-mode.ts +67 -64
- package/src/modes/prompt-action-autocomplete.ts +5 -3
- package/src/modes/rpc/rpc-client.ts +18 -0
- package/src/modes/rpc/rpc-mode.ts +16 -1
- package/src/modes/rpc/rpc-types.ts +11 -0
- package/src/modes/theme/theme.ts +151 -6
- package/src/modes/types.ts +2 -1
- package/src/modes/utils/ui-helpers.ts +38 -21
- package/src/prompts/goals/guided-goal-interview.md +41 -6
- package/src/prompts/system/auto-thinking-difficulty.md +4 -2
- package/src/prompts/system/plan-mode-active.md +2 -2
- package/src/prompts/system/system-prompt.md +3 -0
- package/src/prompts/system/vibe-mode-active.md +4 -1
- package/src/prompts/system/xdev-mount-notice.md +1 -1
- package/src/prompts/tools/bash.md +15 -17
- package/src/prompts/tools/browser.md +1 -0
- package/src/prompts/tools/checkpoint.md +1 -1
- package/src/prompts/tools/glob.md +7 -6
- package/src/prompts/tools/goal.md +1 -1
- package/src/prompts/tools/grep.md +7 -6
- package/src/sdk.ts +178 -15
- package/src/session/agent-session-events.ts +1 -0
- package/src/session/agent-session-types.ts +40 -1
- package/src/session/agent-session.ts +330 -179
- package/src/session/async-job-delivery.ts +8 -0
- package/src/session/claude-session-store.ts +426 -0
- package/src/session/codex-session-store.ts +673 -0
- package/src/session/credential-pin.ts +93 -0
- package/src/session/foreign-session-import.ts +52 -0
- package/src/session/foreign-session-jsonl.ts +29 -0
- package/src/session/foreign-session-store.ts +26 -0
- package/src/session/model-controls.ts +15 -7
- package/src/session/session-advisors.ts +142 -23
- package/src/session/session-entries.ts +21 -1
- package/src/session/session-handoff.ts +20 -3
- package/src/session/session-history-format.ts +31 -6
- package/src/session/session-loader.ts +25 -9
- package/src/session/session-maintenance.ts +59 -11
- package/src/session/session-manager.ts +61 -0
- package/src/session/session-tools.ts +107 -5
- package/src/session/settings-stream-fn.ts +1 -0
- package/src/session/turn-recovery.ts +97 -51
- package/src/slash-commands/builtin-registry.ts +136 -5
- package/src/system-prompt.ts +3 -2
- package/src/task/executor.ts +17 -4
- package/src/task/isolation-ownership.ts +106 -0
- package/src/task/render.ts +20 -4
- package/src/task/worktree.ts +8 -0
- package/src/thinking.ts +29 -10
- package/src/tools/acp-bridge.ts +52 -8
- package/src/tools/ask.ts +3 -3
- package/src/tools/ast-edit.ts +9 -2
- package/src/tools/browser/cmux/cmux-tab.ts +9 -14
- package/src/tools/browser/tab-worker.ts +12 -35
- package/src/tools/browser.ts +7 -2
- package/src/tools/checkpoint.ts +0 -13
- package/src/tools/gh-renderer.ts +3 -3
- package/src/tools/grep.ts +64 -8
- package/src/tools/hub/launch.ts +10 -15
- package/src/tools/index.ts +38 -5
- package/src/tools/path-utils.ts +88 -0
- package/src/tools/read.ts +3 -0
- package/src/tools/write.ts +44 -3
- package/src/tools/xdev.ts +39 -14
- package/src/tui/code-cell.ts +4 -4
- package/src/tui/output-block.ts +25 -8
- package/src/utils/changelog.ts +144 -4
- package/src/utils/git.ts +61 -14
- package/src/utils/jj.ts +19 -13
- package/src/utils/shell-snapshot-fn-env.sh +5 -2
- package/src/web/search/providers/codex.ts +34 -0
- package/src/web/search/render.ts +2 -2
- package/src/web/search/types.ts +1 -1
- package/dist/types/goals/guided-setup.d.ts +0 -30
- package/src/goals/guided-setup.ts +0 -171
- package/src/prompts/goals/guided-goal-system.md +0 -33
|
@@ -19,7 +19,7 @@ import * as path from "node:path";
|
|
|
19
19
|
import { scheduler } from "node:timers/promises";
|
|
20
20
|
import { isPromise } from "node:util/types";
|
|
21
21
|
|
|
22
|
-
import type { InMemorySnapshotStore } from "@oh-my-pi/hashline";
|
|
22
|
+
import type { Clipboard, InMemorySnapshotStore } from "@oh-my-pi/hashline";
|
|
23
23
|
import {
|
|
24
24
|
type AfterToolCallContext,
|
|
25
25
|
type AfterToolCallResult,
|
|
@@ -95,7 +95,7 @@ import {
|
|
|
95
95
|
stringProperty,
|
|
96
96
|
withTimeout,
|
|
97
97
|
} from "@oh-my-pi/pi-utils";
|
|
98
|
-
import type
|
|
98
|
+
import { type AdvisorConfig, type AdvisorRuntimeStatus, loadAdvisorTranscriptCosts } from "../advisor";
|
|
99
99
|
import { type AsyncJob, AsyncJobManager } from "../async";
|
|
100
100
|
import { shouldEnableAppendOnlyContext } from "../config/append-only-context-mode";
|
|
101
101
|
import type { ModelRegistry } from "../config/model-registry";
|
|
@@ -249,6 +249,7 @@ import {
|
|
|
249
249
|
shouldEvaluateCodexAutoRedeem,
|
|
250
250
|
shouldPromptCodexAutoRedeem,
|
|
251
251
|
} from "./codex-auto-reset";
|
|
252
|
+
import { recordCredentialPin, seedCredentialPins } from "./credential-pin";
|
|
252
253
|
import { EvalRunner, type EvalRunnerHost } from "./eval-runner";
|
|
253
254
|
import {
|
|
254
255
|
collectPendingToolCalls,
|
|
@@ -398,6 +399,9 @@ type SetSessionNameWithTrigger = (
|
|
|
398
399
|
trigger?: SessionNameTrigger,
|
|
399
400
|
) => Promise<boolean>;
|
|
400
401
|
|
|
402
|
+
const kPersistedSessionEntryId = Symbol("persistedSessionEntryId");
|
|
403
|
+
type PersistedAssistantMessage = AssistantMessage & { [kPersistedSessionEntryId]?: string };
|
|
404
|
+
|
|
401
405
|
export class AgentSession {
|
|
402
406
|
readonly agent: Agent;
|
|
403
407
|
readonly sessionManager: SessionManager;
|
|
@@ -406,6 +410,8 @@ export class AgentSession {
|
|
|
406
410
|
getXdevToolEntries: () => Array<{ name: string; summary: string }>;
|
|
407
411
|
readonly yieldQueue: YieldQueue;
|
|
408
412
|
fileSnapshotStore?: InMemorySnapshotStore;
|
|
413
|
+
/** Per-session `CUT`/`PASTE` clipboard register shared across edit calls. */
|
|
414
|
+
editClipboard?: Clipboard;
|
|
409
415
|
|
|
410
416
|
#powerAssertion: MacOSPowerAssertion | undefined;
|
|
411
417
|
|
|
@@ -487,6 +493,13 @@ export class AgentSession {
|
|
|
487
493
|
readonly #asyncJobManager: AsyncJobManager | undefined;
|
|
488
494
|
/** Clears this session's owner delivery sink registration; set when a manager + agent id exist. */
|
|
489
495
|
#unregisterAsyncDeliverySink: (() => void) | undefined;
|
|
496
|
+
/**
|
|
497
|
+
* Async-delivery generation, bumped on every session transition that evicts
|
|
498
|
+
* this owner's jobs (see {@link AgentSession.#cancelOwnAsyncJobs}). Stamped
|
|
499
|
+
* onto each queued async-result follow-up so a delivery formatted or drained
|
|
500
|
+
* across a `/new` is dropped regardless of job-id reuse.
|
|
501
|
+
*/
|
|
502
|
+
#asyncDeliveryEpoch = 0;
|
|
490
503
|
|
|
491
504
|
readonly #irc: IrcBridge;
|
|
492
505
|
// Agent identity (registry id) used for IRC routing and job ownership.
|
|
@@ -949,6 +962,7 @@ export class AgentSession {
|
|
|
949
962
|
scheduleAgentContinue: options => this.#scheduleAgentContinue(options),
|
|
950
963
|
waitForSessionMessagePersistence: message => this.#waitForSessionMessagePersistence(message),
|
|
951
964
|
appendSessionMessage: message => this.#appendSessionMessage(message),
|
|
965
|
+
persistedAssistantEntryId: message => (message as PersistedAssistantMessage)[kPersistedSessionEntryId],
|
|
952
966
|
sessionMessageAlreadyPersisted: message => this.#sessionMessageAlreadyPersisted(message),
|
|
953
967
|
setModelWithProviderSessionReset: model => this.#setModelWithProviderSessionReset(model),
|
|
954
968
|
resetCurrentResponsesProviderSession: reason => this.#resetCurrentResponsesProviderSession(reason),
|
|
@@ -1173,7 +1187,7 @@ export class AgentSession {
|
|
|
1173
1187
|
this.#deliverAsyncJobResult(manager, jobId, text, job),
|
|
1174
1188
|
);
|
|
1175
1189
|
this.yieldQueue.register<AsyncResultEntry>("async-result", {
|
|
1176
|
-
isStale: entry => manager.isDeliverySuppressed(entry.jobId),
|
|
1190
|
+
isStale: entry => entry.epoch !== this.#asyncDeliveryEpoch || manager.isDeliverySuppressed(entry.jobId),
|
|
1177
1191
|
build: buildAsyncResultBatchMessage,
|
|
1178
1192
|
});
|
|
1179
1193
|
}
|
|
@@ -1267,8 +1281,8 @@ export class AgentSession {
|
|
|
1267
1281
|
hasPendingNextTurnMessages: () => this.#pendingNextTurnMessages.length > 0,
|
|
1268
1282
|
convertToLlmForSideRequest: messages => this.#convertToLlmForSideRequest(messages),
|
|
1269
1283
|
effectiveServiceTier: model => this.#models.effectiveServiceTier(model),
|
|
1270
|
-
resolveContextPromotionTarget: (model, contextWindow) =>
|
|
1271
|
-
this.#maintenance.resolveContextPromotionTarget(model, contextWindow),
|
|
1284
|
+
resolveContextPromotionTarget: (model, contextWindow, signal) =>
|
|
1285
|
+
this.#maintenance.resolveContextPromotionTarget(model, contextWindow, signal),
|
|
1272
1286
|
resolveCompactionModelCandidates: (model, availableModels) =>
|
|
1273
1287
|
this.#maintenance.resolveCompactionModelCandidates(model, availableModels),
|
|
1274
1288
|
resolveRetryFallbackRole: (selector, model) => this.#recovery.resolveRetryFallbackRole(selector, model),
|
|
@@ -1283,12 +1297,17 @@ export class AgentSession {
|
|
|
1283
1297
|
this.#advisors = new SessionAdvisors(advisorsHost, {
|
|
1284
1298
|
enabled: this.settings.get("advisor.enabled"),
|
|
1285
1299
|
tools: config.advisorTools,
|
|
1300
|
+
createGrepTool: config.advisorCreateGrepTool,
|
|
1301
|
+
createEditTool: config.advisorCreateEditTool,
|
|
1302
|
+
getToolContext: config.advisorGetToolContext,
|
|
1303
|
+
mcpResources: config.advisorMcpResources,
|
|
1286
1304
|
watchdogPrompt: config.advisorWatchdogPrompt,
|
|
1287
1305
|
sharedInstructions: config.advisorSharedInstructions,
|
|
1288
1306
|
contextPrompt: config.advisorContextPrompt,
|
|
1289
1307
|
configs: config.advisorConfigs,
|
|
1290
1308
|
streamFn: config.advisorStreamFn,
|
|
1291
1309
|
transformProviderContext: config.transformProviderContext,
|
|
1310
|
+
initialCosts: config.initialAdvisorCosts,
|
|
1292
1311
|
});
|
|
1293
1312
|
|
|
1294
1313
|
const maintenanceHost: SessionMaintenanceHost = {
|
|
@@ -1394,7 +1413,10 @@ export class AgentSession {
|
|
|
1394
1413
|
},
|
|
1395
1414
|
resetTodoCycle: () => this.#todo.resetCycle(),
|
|
1396
1415
|
buildDisplaySessionContext: () => this.buildDisplaySessionContext(),
|
|
1397
|
-
|
|
1416
|
+
resetAdvisorSessionState: () => this.#advisors.resetSessionState(),
|
|
1417
|
+
drainAndDetachAdvisorRecorders: () => this.#advisors.drainAndDetachRecorders(),
|
|
1418
|
+
reattachAdvisorRecorderFeeds: () => this.#advisors.reattachRecorderFeeds(),
|
|
1419
|
+
clearAdvisorCost: () => this.#advisors.clearCost(),
|
|
1398
1420
|
syncTodoPhasesFromBranch: () => this.#todo.syncFromBranch(),
|
|
1399
1421
|
};
|
|
1400
1422
|
this.#handoff = new SessionHandoff(handoffHost);
|
|
@@ -1612,7 +1634,9 @@ export class AgentSession {
|
|
|
1612
1634
|
* transitions (newSession, switchSession, handoff, dispose) so a subagent
|
|
1613
1635
|
* cleans up its own background work without touching its parent's jobs.
|
|
1614
1636
|
*
|
|
1615
|
-
*
|
|
1637
|
+
* Cleanup runs against this session's scoped manager: running jobs are
|
|
1638
|
+
* cancelled, finished rows are evicted with their pending deliveries, and any
|
|
1639
|
+
* async-result follow-up already queued for injection is dropped. Subagents have
|
|
1616
1640
|
* unique agent ids and inherit the parent's manager to clean up their own
|
|
1617
1641
|
* jobs. A secondary in-process top-level session gets no scoped manager,
|
|
1618
1642
|
* because it defaults to `MAIN_AGENT_ID`; reaching through the global
|
|
@@ -1625,6 +1649,12 @@ export class AgentSession {
|
|
|
1625
1649
|
if (!this.#agentId) return;
|
|
1626
1650
|
const manager = this.#asyncJobManager;
|
|
1627
1651
|
manager?.cancelAll({ ownerId: this.#agentId });
|
|
1652
|
+
manager?.evictCompletedJobs({ ownerId: this.#agentId });
|
|
1653
|
+
// Invalidate this owner's in-flight/drained deliveries against the new
|
|
1654
|
+
// generation, then drop any async-result follow-up already queued, so a
|
|
1655
|
+
// prior session's background result cannot inject into the next transcript.
|
|
1656
|
+
this.#asyncDeliveryEpoch += 1;
|
|
1657
|
+
this.yieldQueue.clear("async-result");
|
|
1628
1658
|
}
|
|
1629
1659
|
|
|
1630
1660
|
/**
|
|
@@ -1690,10 +1720,17 @@ export class AgentSession {
|
|
|
1690
1720
|
async #deliverAsyncJobResult(manager: AsyncJobManager, jobId: string, text: string, job?: AsyncJob): Promise<void> {
|
|
1691
1721
|
if (this.#isDisposed) return;
|
|
1692
1722
|
if (manager.isDeliverySuppressed(jobId)) return;
|
|
1723
|
+
// Snapshot the generation before the async format step: a `/new` during it
|
|
1724
|
+
// bumps the epoch, so this delivery belongs to the replaced session and
|
|
1725
|
+
// must not enqueue — the suppression marker alone is unreliable because
|
|
1726
|
+
// job-id reuse clears it.
|
|
1727
|
+
const epoch = this.#asyncDeliveryEpoch;
|
|
1693
1728
|
const formatted = await this.#formatAsyncResultForFollowUp(text);
|
|
1729
|
+
if (this.#isDisposed) return;
|
|
1730
|
+
if (epoch !== this.#asyncDeliveryEpoch) return;
|
|
1694
1731
|
if (manager.isDeliverySuppressed(jobId)) return;
|
|
1695
1732
|
const durationMs = job ? Math.max(0, Date.now() - job.startTime) : undefined;
|
|
1696
|
-
this.yieldQueue.enqueue<AsyncResultEntry>("async-result", { jobId, result: formatted, job, durationMs });
|
|
1733
|
+
this.yieldQueue.enqueue<AsyncResultEntry>("async-result", { jobId, result: formatted, job, durationMs, epoch });
|
|
1697
1734
|
}
|
|
1698
1735
|
|
|
1699
1736
|
async #formatAsyncResultForFollowUp(result: string): Promise<string> {
|
|
@@ -2039,6 +2076,9 @@ export class AgentSession {
|
|
|
2039
2076
|
const cache = this.#persistedMessageKeys;
|
|
2040
2077
|
const wasFresh = cache !== undefined && cache.anchor === this.#persistedMessageKeysAnchor();
|
|
2041
2078
|
const entryId = this.sessionManager.appendMessage(message);
|
|
2079
|
+
if (message.role === "assistant") {
|
|
2080
|
+
(message as PersistedAssistantMessage)[kPersistedSessionEntryId] = entryId;
|
|
2081
|
+
}
|
|
2042
2082
|
const key = sessionMessagePersistenceKey(message);
|
|
2043
2083
|
if (wasFresh && cache && key) {
|
|
2044
2084
|
cache.keys.add(key);
|
|
@@ -2391,6 +2431,15 @@ export class AgentSession {
|
|
|
2391
2431
|
},
|
|
2392
2432
|
costUsd: assistantMsg.usage.cost.total,
|
|
2393
2433
|
});
|
|
2434
|
+
// Persist which account served this turn so a resumed process can
|
|
2435
|
+
// re-pin it and keep the provider's account-scoped prompt cache
|
|
2436
|
+
// warm (broker-mode sticky routing is process-local).
|
|
2437
|
+
recordCredentialPin(
|
|
2438
|
+
this.#modelRegistry.authStorage,
|
|
2439
|
+
this.sessionManager,
|
|
2440
|
+
this.sessionId,
|
|
2441
|
+
assistantMsg.provider,
|
|
2442
|
+
);
|
|
2394
2443
|
}
|
|
2395
2444
|
if (event.message.role === "toolResult") {
|
|
2396
2445
|
const { toolName, toolCallId, isError, content } = event.message;
|
|
@@ -2574,6 +2623,11 @@ export class AgentSession {
|
|
|
2574
2623
|
return;
|
|
2575
2624
|
}
|
|
2576
2625
|
|
|
2626
|
+
// Record quota exhaustion before deciding whether this failed turn may be
|
|
2627
|
+
// replayed. Visible/side-effecting output then remains terminal while its
|
|
2628
|
+
// credential is still blocked or rotated exactly once.
|
|
2629
|
+
await this.#recovery.recordUsageLimitOutcome(msg);
|
|
2630
|
+
|
|
2577
2631
|
let compactionResult = COMPACTION_CHECK_NONE;
|
|
2578
2632
|
let checkedCompaction = false;
|
|
2579
2633
|
if (activeGoal) {
|
|
@@ -3358,6 +3412,14 @@ export class AgentSession {
|
|
|
3358
3412
|
this.agent.setMetadataResolver((provider: string) =>
|
|
3359
3413
|
buildSessionMetadata(sid, provider, this.#modelRegistry.authStorage),
|
|
3360
3414
|
);
|
|
3415
|
+
// Restore the session's recorded provider accounts before the first
|
|
3416
|
+
// request routes: sticky rows are process-local under a remote auth
|
|
3417
|
+
// broker, and losing them re-ranks onto a different account, cold-missing
|
|
3418
|
+
// the account-scoped prompt cache. Skipped for fresh provider sessions —
|
|
3419
|
+
// those explicitly want new routing identity.
|
|
3420
|
+
if (!this.#freshProviderSessionId) {
|
|
3421
|
+
seedCredentialPins(this.#modelRegistry.authStorage, this.sessionManager, sid);
|
|
3422
|
+
}
|
|
3361
3423
|
// Keep every live advisor's provider identity in lockstep with the primary's
|
|
3362
3424
|
// across every session-boundary transition — including branch paths that
|
|
3363
3425
|
// skip conversation restore — so advisors never emit the previous
|
|
@@ -4315,6 +4377,7 @@ export class AgentSession {
|
|
|
4315
4377
|
this.agent.clearDeferredToolDirectives();
|
|
4316
4378
|
this.#toolChoiceQueue.clear();
|
|
4317
4379
|
this.#tools.clearAcpPermissionDecisions();
|
|
4380
|
+
this.#tools.resetAnnouncedMounts();
|
|
4318
4381
|
}
|
|
4319
4382
|
|
|
4320
4383
|
/**
|
|
@@ -4565,7 +4628,9 @@ export class AgentSession {
|
|
|
4565
4628
|
return {
|
|
4566
4629
|
role: "custom",
|
|
4567
4630
|
customType: "vibe-mode-context",
|
|
4568
|
-
content: prompt.render(vibeModeActivePrompt
|
|
4631
|
+
content: prompt.render(vibeModeActivePrompt, {
|
|
4632
|
+
todoAvailable: this.getActiveToolNames().includes("todo"),
|
|
4633
|
+
}),
|
|
4569
4634
|
display: false,
|
|
4570
4635
|
attribution: "agent",
|
|
4571
4636
|
timestamp: Date.now(),
|
|
@@ -5146,6 +5211,7 @@ export class AgentSession {
|
|
|
5146
5211
|
void this.dispose().finally(() => process.exit(0));
|
|
5147
5212
|
},
|
|
5148
5213
|
getContextUsage: () => this.getContextUsage(),
|
|
5214
|
+
getAsyncJobSnapshot: () => this.getAsyncJobSnapshot(),
|
|
5149
5215
|
waitForIdle: () => this.waitForIdle(),
|
|
5150
5216
|
newSession: async options => {
|
|
5151
5217
|
const success = await this.newSession({ parentSession: options?.parentSession });
|
|
@@ -5964,6 +6030,7 @@ export class AgentSession {
|
|
|
5964
6030
|
}
|
|
5965
6031
|
|
|
5966
6032
|
this.#disconnectFromAgent();
|
|
6033
|
+
let advisorRecordersDetached = false;
|
|
5967
6034
|
await this.abort();
|
|
5968
6035
|
this.#cancelOwnAsyncJobs();
|
|
5969
6036
|
this.#closeAllProviderSessions("new session");
|
|
@@ -5971,69 +6038,73 @@ export class AgentSession {
|
|
|
5971
6038
|
const bashTransition = this.#bash.beginSessionTransition({ persistDetached: options?.drop !== true });
|
|
5972
6039
|
let sessionTransitioned = false;
|
|
5973
6040
|
try {
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
}
|
|
5985
|
-
|
|
6041
|
+
advisorRecordersDetached = true;
|
|
6042
|
+
await this.#advisors.drainAndDetachRecorders();
|
|
6043
|
+
try {
|
|
6044
|
+
this.agent.reset();
|
|
6045
|
+
if (options?.drop && previousSessionFile) {
|
|
6046
|
+
try {
|
|
6047
|
+
await this.sessionManager.dropSession(previousSessionFile);
|
|
6048
|
+
} catch (err) {
|
|
6049
|
+
logger.error("Failed to delete session during /drop", { err });
|
|
6050
|
+
}
|
|
6051
|
+
} else {
|
|
6052
|
+
await this.sessionManager.flush();
|
|
5986
6053
|
}
|
|
5987
|
-
|
|
5988
|
-
|
|
6054
|
+
await this.sessionManager.newSession({
|
|
6055
|
+
...options,
|
|
6056
|
+
additionalDirectories: this.settings.get("workspace.additionalDirectories"),
|
|
6057
|
+
});
|
|
6058
|
+
this.#bash.markSessionTransition(bashTransition);
|
|
6059
|
+
// The new session owns the transcript from here, so the previous
|
|
6060
|
+
// conversation's advisor spend is retired with it. Clearing at the commit
|
|
6061
|
+
// point keeps the status line honest even if a later step below throws.
|
|
6062
|
+
this.#advisors.clearCost();
|
|
6063
|
+
sessionTransitioned = true;
|
|
6064
|
+
} finally {
|
|
6065
|
+
this.#bash.finishSessionTransition(bashTransition, sessionTransitioned);
|
|
5989
6066
|
}
|
|
5990
|
-
await this.sessionManager.newSession({
|
|
5991
|
-
...options,
|
|
5992
|
-
additionalDirectories: this.settings.get("workspace.additionalDirectories"),
|
|
5993
|
-
});
|
|
5994
|
-
this.#bash.markSessionTransition(bashTransition);
|
|
5995
|
-
// The new session owns the transcript from here, so the previous
|
|
5996
|
-
// conversation's advisor spend is retired with it. Clearing at the commit
|
|
5997
|
-
// point keeps the status line honest even if a later step below throws.
|
|
5998
|
-
this.#advisors.clearCost();
|
|
5999
|
-
sessionTransitioned = true;
|
|
6000
|
-
} finally {
|
|
6001
|
-
this.#bash.finishSessionTransition(bashTransition, sessionTransitioned);
|
|
6002
|
-
}
|
|
6003
6067
|
|
|
6004
|
-
|
|
6005
|
-
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6068
|
+
this.#clearSessionScopedToolState();
|
|
6069
|
+
this.#clearCheckpointRuntimeState();
|
|
6070
|
+
this.setTodoPhases([]);
|
|
6071
|
+
this.#freshProviderSessionId = undefined;
|
|
6072
|
+
this.#clearInheritedProviderPromptCacheKey();
|
|
6073
|
+
this.#syncAgentSessionId();
|
|
6074
|
+
this.#memory.rekeyForCurrentSessionId();
|
|
6075
|
+
await this.#memory.resetContextForNewTranscript();
|
|
6076
|
+
this.#pendingNextTurnMessages = [];
|
|
6077
|
+
this.#scheduledHiddenNextTurnGeneration = undefined;
|
|
6014
6078
|
|
|
6015
|
-
|
|
6016
|
-
|
|
6079
|
+
this.sessionManager.appendThinkingLevelChange(this.thinkingLevel, this.configuredThinkingLevel());
|
|
6080
|
+
this.sessionManager.appendServiceTierChange(this.#models.serviceTierEntry());
|
|
6017
6081
|
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6082
|
+
this.#todo.resetCycle();
|
|
6083
|
+
this.#planReferenceSent = false;
|
|
6084
|
+
this.#planReferencePath = "local://PLAN.md";
|
|
6085
|
+
this.#advisors.resetSessionState();
|
|
6086
|
+
advisorRecordersDetached = false;
|
|
6087
|
+
this.#reconnectToAgent();
|
|
6088
|
+
// The workspace-roots block must reflect the new session's directory set,
|
|
6089
|
+
// not the previous session's — refresh before the next turn goes out.
|
|
6090
|
+
await this.refreshBaseSystemPrompt();
|
|
6026
6091
|
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6092
|
+
// Emit session_switch event with reason "new" to hooks
|
|
6093
|
+
if (this.#extensionRunner) {
|
|
6094
|
+
await this.#extensionRunner.emit({
|
|
6095
|
+
type: "session_switch",
|
|
6096
|
+
reason: "new",
|
|
6097
|
+
previousSessionFile,
|
|
6098
|
+
});
|
|
6099
|
+
}
|
|
6035
6100
|
|
|
6036
|
-
|
|
6101
|
+
return true;
|
|
6102
|
+
} finally {
|
|
6103
|
+
if (advisorRecordersDetached) {
|
|
6104
|
+
if (sessionTransitioned) this.#advisors.resetSessionState();
|
|
6105
|
+
else this.#advisors.reattachRecorderFeeds();
|
|
6106
|
+
}
|
|
6107
|
+
}
|
|
6037
6108
|
}
|
|
6038
6109
|
|
|
6039
6110
|
/**
|
|
@@ -6069,59 +6140,70 @@ export class AgentSession {
|
|
|
6069
6140
|
await this.#bash.flushPending();
|
|
6070
6141
|
// Flush current session to ensure all entries are written
|
|
6071
6142
|
await this.sessionManager.flush();
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
// Fork the session (creates new session file with same entries)
|
|
6075
|
-
let forkResult: { oldSessionFile: string; newSessionFile: string } | undefined;
|
|
6143
|
+
let advisorRecordersDetached = false;
|
|
6076
6144
|
try {
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6145
|
+
advisorRecordersDetached = true;
|
|
6146
|
+
// Fork keeps the conversation, but still needs a quiet artifact boundary:
|
|
6147
|
+
// stop and settle in-flight advisors before muting their feeds.
|
|
6148
|
+
await this.#advisors.drainAndDetachRecorders();
|
|
6149
|
+
const bashTransition = this.#bash.beginSessionTransition();
|
|
6150
|
+
|
|
6151
|
+
// Fork the session (creates new session file with same entries)
|
|
6152
|
+
let forkResult: { oldSessionFile: string; newSessionFile: string } | undefined;
|
|
6153
|
+
try {
|
|
6154
|
+
forkResult = await this.sessionManager.fork();
|
|
6155
|
+
} catch (error) {
|
|
6156
|
+
this.#bash.finishSessionTransition(bashTransition, false);
|
|
6157
|
+
throw error;
|
|
6158
|
+
}
|
|
6159
|
+
if (!forkResult) {
|
|
6160
|
+
this.#bash.finishSessionTransition(bashTransition, false);
|
|
6161
|
+
return false;
|
|
6162
|
+
}
|
|
6163
|
+
this.#bash.markSessionTransition(bashTransition);
|
|
6164
|
+
this.#bash.finishSessionTransition(bashTransition, true);
|
|
6088
6165
|
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6166
|
+
// Copy artifacts directory if it exists
|
|
6167
|
+
const oldArtifactDir = forkResult.oldSessionFile.slice(0, -6);
|
|
6168
|
+
const newArtifactDir = forkResult.newSessionFile.slice(0, -6);
|
|
6092
6169
|
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6170
|
+
try {
|
|
6171
|
+
const oldDirStat = await fs.promises.stat(oldArtifactDir);
|
|
6172
|
+
if (oldDirStat.isDirectory()) {
|
|
6173
|
+
await fs.promises.cp(oldArtifactDir, newArtifactDir, { recursive: true });
|
|
6174
|
+
}
|
|
6175
|
+
} catch (err) {
|
|
6176
|
+
if (!isEnoent(err)) {
|
|
6177
|
+
logger.warn("Failed to copy artifacts during fork", {
|
|
6178
|
+
oldArtifactDir,
|
|
6179
|
+
newArtifactDir,
|
|
6180
|
+
error: err instanceof Error ? err.message : String(err),
|
|
6181
|
+
});
|
|
6182
|
+
}
|
|
6097
6183
|
}
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
6184
|
+
|
|
6185
|
+
// Update agent session ID
|
|
6186
|
+
this.#freshProviderSessionId = undefined;
|
|
6187
|
+
this.#adoptInheritedProviderPromptCacheKey();
|
|
6188
|
+
this.#syncAgentSessionId();
|
|
6189
|
+
this.#memory.rekeyForCurrentSessionId();
|
|
6190
|
+
this.#advisors.reattachRecorderFeeds();
|
|
6191
|
+
advisorRecordersDetached = false;
|
|
6192
|
+
await this.#memory.resetContextForNewTranscript();
|
|
6193
|
+
|
|
6194
|
+
// Emit session_switch event with reason "fork" to hooks
|
|
6195
|
+
if (this.#extensionRunner) {
|
|
6196
|
+
await this.#extensionRunner.emit({
|
|
6197
|
+
type: "session_switch",
|
|
6198
|
+
reason: "fork",
|
|
6199
|
+
previousSessionFile,
|
|
6104
6200
|
});
|
|
6105
6201
|
}
|
|
6106
|
-
}
|
|
6107
|
-
|
|
6108
|
-
// Update agent session ID
|
|
6109
|
-
this.#freshProviderSessionId = undefined;
|
|
6110
|
-
this.#adoptInheritedProviderPromptCacheKey();
|
|
6111
|
-
this.#syncAgentSessionId();
|
|
6112
|
-
this.#memory.rekeyForCurrentSessionId();
|
|
6113
|
-
await this.#memory.resetContextForNewTranscript();
|
|
6114
6202
|
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
type: "session_switch",
|
|
6119
|
-
reason: "fork",
|
|
6120
|
-
previousSessionFile,
|
|
6121
|
-
});
|
|
6203
|
+
return true;
|
|
6204
|
+
} finally {
|
|
6205
|
+
if (advisorRecordersDetached) this.#advisors.reattachRecorderFeeds();
|
|
6122
6206
|
}
|
|
6123
|
-
|
|
6124
|
-
return true;
|
|
6125
6207
|
}
|
|
6126
6208
|
|
|
6127
6209
|
/** Move the active session and artifacts after enforcing mode transition invariants. */
|
|
@@ -6495,13 +6577,30 @@ export class AgentSession {
|
|
|
6495
6577
|
|
|
6496
6578
|
async #setModelWithProviderSessionReset(model: Model): Promise<void> {
|
|
6497
6579
|
const currentModel = this.model;
|
|
6580
|
+
const isChanging = !currentModel || !modelsAreEqual(currentModel, model);
|
|
6498
6581
|
if (currentModel) {
|
|
6499
6582
|
this.#closeProviderSessionsForModelSwitch(currentModel, model);
|
|
6500
|
-
if (
|
|
6583
|
+
if (isChanging) {
|
|
6501
6584
|
this.#clearInheritedProviderPromptCacheKey();
|
|
6502
6585
|
}
|
|
6503
6586
|
}
|
|
6504
6587
|
this.agent.setModel(model);
|
|
6588
|
+
// Model mutations driven through ModelControls (explicit /model, prewalk
|
|
6589
|
+
// hand-offs, retry-fallback, model cycling) funnel through this method,
|
|
6590
|
+
// so this is the single point that notifies subscribers (ACP config
|
|
6591
|
+
// sync, RPC, TUI status line) — callers that bypass ModelControls never
|
|
6592
|
+
// need to remember to notify separately. `switchSession`'s rollback
|
|
6593
|
+
// restores via `agent.setModel` directly and emits its own corrective
|
|
6594
|
+
// event.
|
|
6595
|
+
//
|
|
6596
|
+
// Fan-out uses the synchronous `#emit`, matching `thinking_level_changed`:
|
|
6597
|
+
// `model_changed` has no extension-facing hook (`#emitExtensionEvent`
|
|
6598
|
+
// never maps it), so routing it through `#emitSessionEvent` would only
|
|
6599
|
+
// add an extension-delivery await inside every model switch — including
|
|
6600
|
+
// retry-fallback on the error path.
|
|
6601
|
+
if (isChanging) {
|
|
6602
|
+
this.#emit({ type: "model_changed" });
|
|
6603
|
+
}
|
|
6505
6604
|
|
|
6506
6605
|
// Re-evaluate append-only context mode — provider or setting may have changed
|
|
6507
6606
|
this.#syncAppendOnlyContext(model);
|
|
@@ -7020,6 +7119,11 @@ export class AgentSession {
|
|
|
7020
7119
|
this.#scheduledHiddenNextTurnGeneration = undefined;
|
|
7021
7120
|
|
|
7022
7121
|
try {
|
|
7122
|
+
if (switchingToDifferentSession) {
|
|
7123
|
+
// Stop and settle in-flight advisors while the old-session feeds can
|
|
7124
|
+
// still observe message_end, then mute before swapping files.
|
|
7125
|
+
await this.#advisors.drainAndDetachRecorders();
|
|
7126
|
+
}
|
|
7023
7127
|
await this.sessionManager.setSessionFile(sessionPath);
|
|
7024
7128
|
this.#bash.markSessionTransition(bashTransition);
|
|
7025
7129
|
if (switchingToDifferentSession) {
|
|
@@ -7132,7 +7236,7 @@ export class AgentSession {
|
|
|
7132
7236
|
if (switchingToDifferentSession) {
|
|
7133
7237
|
await this.#memory.resetContextForNewTranscript();
|
|
7134
7238
|
}
|
|
7135
|
-
if (switchingToDifferentSession) {
|
|
7239
|
+
if (switchingToDifferentSession || didReloadConversationChange) {
|
|
7136
7240
|
this.#clearSessionScopedToolState();
|
|
7137
7241
|
}
|
|
7138
7242
|
this.#reconnectToAgent();
|
|
@@ -7155,9 +7259,14 @@ export class AgentSession {
|
|
|
7155
7259
|
error: String(refreshErr),
|
|
7156
7260
|
});
|
|
7157
7261
|
}
|
|
7158
|
-
//
|
|
7159
|
-
// an earlier
|
|
7160
|
-
|
|
7262
|
+
// Hand the ledger over to the session that just took over, and only once the
|
|
7263
|
+
// switch has committed: an earlier swap would be lost work if any step above
|
|
7264
|
+
// rolled it back. The target's own advisor transcripts are the record of what
|
|
7265
|
+
// it already spent, so a session with history resumes with its total instead
|
|
7266
|
+
// of restarting at zero.
|
|
7267
|
+
if (switchingToDifferentSession) {
|
|
7268
|
+
this.#advisors.restoreCost(await loadAdvisorTranscriptCosts(this.sessionFile));
|
|
7269
|
+
}
|
|
7161
7270
|
this.#bash.finishSessionTransition(bashTransition, true);
|
|
7162
7271
|
return true;
|
|
7163
7272
|
} catch (error) {
|
|
@@ -7178,13 +7287,32 @@ export class AgentSession {
|
|
|
7178
7287
|
this.#pendingRewindReport = previousPendingRewindReport;
|
|
7179
7288
|
this.#lastCompletedRewind = previousLastCompletedRewind;
|
|
7180
7289
|
this.#rewoundToolResultIds = previousRewoundToolResultIds;
|
|
7290
|
+
// The try block may have already reached #setModelWithProviderSessionReset
|
|
7291
|
+
// for the target session's model, which emits `model_changed` for it.
|
|
7292
|
+
// Restoring here bypasses that method (it also resets provider-session
|
|
7293
|
+
// state we're already unwinding above), so if the rollback actually
|
|
7294
|
+
// changes the model back, emit the corrective event ourselves —
|
|
7295
|
+
// otherwise ACP/RPC/TUI keep advertising the never-committed target.
|
|
7296
|
+
// Deferred until after restoreThinkingSnapshot below: #emit's listeners
|
|
7297
|
+
// (ACP's #handleLifetimeEvent -> #pushConfigOptionUpdate) read
|
|
7298
|
+
// session state synchronously before their first await, so emitting
|
|
7299
|
+
// here — before the target session's thinking level is unwound —
|
|
7300
|
+
// would push a { previousModel, target-session-thinking } config that
|
|
7301
|
+
// was never a real session state.
|
|
7302
|
+
let modelRolledBack = false;
|
|
7181
7303
|
if (previousModel) {
|
|
7304
|
+
const rolledBackModel = this.model;
|
|
7182
7305
|
this.agent.setModel(previousModel);
|
|
7306
|
+
modelRolledBack = !modelsAreEqual(rolledBackModel, previousModel);
|
|
7183
7307
|
}
|
|
7184
7308
|
this.#models.restoreThinkingSnapshot(previousThinkingLevel, previousAutoThinking, previousAutoResolvedLevel);
|
|
7185
7309
|
this.#models.restoreServiceTiers(previousServiceTierByFamily);
|
|
7310
|
+
if (modelRolledBack) {
|
|
7311
|
+
this.#emit({ type: "model_changed" });
|
|
7312
|
+
}
|
|
7186
7313
|
this.#todo.syncFromBranch();
|
|
7187
7314
|
this.#advisors.resetAllRuntimes();
|
|
7315
|
+
this.#advisors.reattachRecorderFeeds();
|
|
7188
7316
|
this.#reconnectToAgent();
|
|
7189
7317
|
try {
|
|
7190
7318
|
await this.#sessionSwitchReconciler?.();
|
|
@@ -7252,45 +7380,57 @@ export class AgentSession {
|
|
|
7252
7380
|
await this.#drainAutolearnCapture();
|
|
7253
7381
|
|
|
7254
7382
|
let sessionTransitioned = false;
|
|
7383
|
+
let advisorRecordersDetached = false;
|
|
7255
7384
|
try {
|
|
7256
|
-
|
|
7257
|
-
|
|
7258
|
-
|
|
7259
|
-
|
|
7385
|
+
advisorRecordersDetached = true;
|
|
7386
|
+
await this.#advisors.drainAndDetachRecorders();
|
|
7387
|
+
try {
|
|
7388
|
+
if (!selectedEntry.parentId) {
|
|
7389
|
+
await this.sessionManager.newSession({ parentSession: previousSessionFile });
|
|
7390
|
+
} else {
|
|
7391
|
+
this.sessionManager.createBranchedSession(selectedEntry.parentId);
|
|
7392
|
+
}
|
|
7393
|
+
this.#bash.markSessionTransition(bashTransition);
|
|
7394
|
+
this.#advisors.clearCost();
|
|
7395
|
+
sessionTransitioned = true;
|
|
7396
|
+
} finally {
|
|
7397
|
+
this.#bash.finishSessionTransition(bashTransition, sessionTransitioned);
|
|
7260
7398
|
}
|
|
7261
|
-
this.#
|
|
7262
|
-
this.#
|
|
7263
|
-
|
|
7264
|
-
|
|
7265
|
-
this.#
|
|
7266
|
-
|
|
7267
|
-
|
|
7268
|
-
|
|
7269
|
-
this.#todo.syncFromBranch();
|
|
7270
|
-
this.#freshProviderSessionId = undefined;
|
|
7271
|
-
this.#clearInheritedProviderPromptCacheKey();
|
|
7272
|
-
this.#syncAgentSessionId();
|
|
7273
|
-
this.#memory.rekeyForCurrentSessionId();
|
|
7274
|
-
await this.#memory.resetContextForNewTranscript();
|
|
7399
|
+
this.#clearSessionScopedToolState();
|
|
7400
|
+
this.#rehydrateCheckpointRewindState();
|
|
7401
|
+
this.#todo.syncFromBranch();
|
|
7402
|
+
this.#freshProviderSessionId = undefined;
|
|
7403
|
+
this.#clearInheritedProviderPromptCacheKey();
|
|
7404
|
+
this.#syncAgentSessionId();
|
|
7405
|
+
this.#memory.rekeyForCurrentSessionId();
|
|
7406
|
+
await this.#memory.resetContextForNewTranscript();
|
|
7275
7407
|
|
|
7276
|
-
|
|
7277
|
-
|
|
7408
|
+
// Reload messages from entries (works for both file and in-memory mode)
|
|
7409
|
+
const sessionContext = this.buildDisplaySessionContext();
|
|
7278
7410
|
|
|
7279
|
-
|
|
7280
|
-
|
|
7281
|
-
|
|
7282
|
-
|
|
7283
|
-
|
|
7284
|
-
|
|
7285
|
-
|
|
7411
|
+
// Emit session_branch event to hooks (after branch completes)
|
|
7412
|
+
if (this.#extensionRunner) {
|
|
7413
|
+
await this.#extensionRunner.emit({
|
|
7414
|
+
type: "session_branch",
|
|
7415
|
+
previousSessionFile,
|
|
7416
|
+
});
|
|
7417
|
+
}
|
|
7286
7418
|
|
|
7287
|
-
|
|
7288
|
-
|
|
7289
|
-
|
|
7290
|
-
|
|
7291
|
-
|
|
7419
|
+
if (!skipConversationRestore) {
|
|
7420
|
+
this.agent.replaceMessages(sessionContext.messages);
|
|
7421
|
+
this.#advisors.resetSessionState();
|
|
7422
|
+
this.#closeCodexProviderSessionsForHistoryRewrite();
|
|
7423
|
+
}
|
|
7292
7424
|
|
|
7293
|
-
|
|
7425
|
+
this.#advisors.reattachRecorderFeeds();
|
|
7426
|
+
advisorRecordersDetached = false;
|
|
7427
|
+
return { selectedText, selectedImages, cancelled: false };
|
|
7428
|
+
} finally {
|
|
7429
|
+
if (advisorRecordersDetached) {
|
|
7430
|
+
if (sessionTransitioned) this.#advisors.resetSessionState();
|
|
7431
|
+
else this.#advisors.reattachRecorderFeeds();
|
|
7432
|
+
}
|
|
7433
|
+
}
|
|
7294
7434
|
}
|
|
7295
7435
|
|
|
7296
7436
|
async branchFromBtw(
|
|
@@ -7354,44 +7494,55 @@ export class AgentSession {
|
|
|
7354
7494
|
await this.#drainAutolearnCapture();
|
|
7355
7495
|
|
|
7356
7496
|
let sessionTransitioned = false;
|
|
7497
|
+
let advisorRecordersDetached = false;
|
|
7357
7498
|
try {
|
|
7358
|
-
|
|
7359
|
-
this.#
|
|
7360
|
-
|
|
7361
|
-
|
|
7362
|
-
|
|
7363
|
-
|
|
7364
|
-
|
|
7499
|
+
advisorRecordersDetached = true;
|
|
7500
|
+
await this.#advisors.drainAndDetachRecorders();
|
|
7501
|
+
try {
|
|
7502
|
+
this.sessionManager.createBranchedSession(leafId);
|
|
7503
|
+
this.#bash.markSessionTransition(bashTransition);
|
|
7504
|
+
this.#advisors.clearCost();
|
|
7505
|
+
sessionTransitioned = true;
|
|
7506
|
+
} finally {
|
|
7507
|
+
this.#bash.finishSessionTransition(bashTransition, sessionTransitioned);
|
|
7508
|
+
}
|
|
7365
7509
|
|
|
7366
|
-
|
|
7510
|
+
this.#clearSessionScopedToolState();
|
|
7367
7511
|
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
7372
|
-
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
|
|
7377
|
-
|
|
7378
|
-
|
|
7379
|
-
|
|
7512
|
+
this.#rehydrateCheckpointRewindState();
|
|
7513
|
+
this.sessionManager.appendMessage({
|
|
7514
|
+
role: "user",
|
|
7515
|
+
content: [{ type: "text", text: question }],
|
|
7516
|
+
timestamp: Date.now(),
|
|
7517
|
+
});
|
|
7518
|
+
this.sessionManager.appendMessage(sanitizeAssistantForReparentedHistory(assistantMessage));
|
|
7519
|
+
this.#todo.syncFromBranch();
|
|
7520
|
+
this.#freshProviderSessionId = undefined;
|
|
7521
|
+
this.#syncAgentSessionId();
|
|
7522
|
+
this.#memory.rekeyForCurrentSessionId();
|
|
7523
|
+
await this.#memory.resetContextForNewTranscript();
|
|
7380
7524
|
|
|
7381
|
-
|
|
7525
|
+
const sessionContext = this.buildDisplaySessionContext();
|
|
7382
7526
|
|
|
7383
|
-
|
|
7384
|
-
|
|
7385
|
-
|
|
7386
|
-
|
|
7387
|
-
|
|
7388
|
-
|
|
7527
|
+
if (this.#extensionRunner) {
|
|
7528
|
+
await this.#extensionRunner.emit({
|
|
7529
|
+
type: "session_branch",
|
|
7530
|
+
previousSessionFile,
|
|
7531
|
+
});
|
|
7532
|
+
}
|
|
7389
7533
|
|
|
7390
|
-
|
|
7391
|
-
|
|
7392
|
-
|
|
7534
|
+
this.agent.replaceMessages(sessionContext.messages);
|
|
7535
|
+
this.#advisors.resetSessionState();
|
|
7536
|
+
this.#closeCodexProviderSessionsForHistoryRewrite();
|
|
7537
|
+
advisorRecordersDetached = false;
|
|
7393
7538
|
|
|
7394
|
-
|
|
7539
|
+
return { cancelled: false, sessionFile: this.sessionFile };
|
|
7540
|
+
} finally {
|
|
7541
|
+
if (advisorRecordersDetached) {
|
|
7542
|
+
if (sessionTransitioned) this.#advisors.resetSessionState();
|
|
7543
|
+
else this.#advisors.reattachRecorderFeeds();
|
|
7544
|
+
}
|
|
7545
|
+
}
|
|
7395
7546
|
}
|
|
7396
7547
|
|
|
7397
7548
|
// =========================================================================
|