@oh-my-pi/pi-coding-agent 15.7.5 → 15.8.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 +159 -182
- package/dist/types/async/job-manager.d.ts +3 -3
- package/dist/types/cli/args.d.ts +1 -0
- package/dist/types/cli/claude-trace-cli.d.ts +54 -0
- package/dist/types/cli/session-picker.d.ts +10 -3
- package/dist/types/cli/update-cli.d.ts +17 -0
- package/dist/types/commands/launch.d.ts +3 -0
- package/dist/types/config/keybindings.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +11 -2
- package/dist/types/config/settings.d.ts +13 -0
- package/dist/types/eval/__tests__/heartbeat.test.d.ts +1 -0
- package/dist/types/eval/concurrency-bridge.d.ts +26 -0
- package/dist/types/eval/heartbeat.d.ts +45 -0
- package/dist/types/eval/js/tool-bridge.d.ts +2 -1
- package/dist/types/extensibility/extensions/loader.d.ts +2 -2
- package/dist/types/extensibility/extensions/runner.d.ts +2 -1
- package/dist/types/extensibility/extensions/types.d.ts +24 -5
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +11 -0
- package/dist/types/lsp/diagnostics-ledger.d.ts +10 -0
- package/dist/types/lsp/index.d.ts +2 -0
- package/dist/types/lsp/utils.d.ts +4 -0
- package/dist/types/main.d.ts +5 -0
- package/dist/types/modes/acp/acp-agent.d.ts +1 -1
- package/dist/types/modes/components/assistant-message.d.ts +3 -1
- package/dist/types/modes/components/custom-editor.d.ts +3 -2
- package/dist/types/modes/components/hook-selector.d.ts +10 -1
- package/dist/types/modes/components/session-selector.d.ts +32 -5
- package/dist/types/modes/components/tool-execution.d.ts +8 -0
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +3 -3
- package/dist/types/modes/controllers/input-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +10 -3
- package/dist/types/modes/rpc/rpc-mode.d.ts +1 -1
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +5 -3
- package/dist/types/registry/agent-registry.d.ts +1 -1
- package/dist/types/sdk.d.ts +2 -2
- package/dist/types/session/agent-session.d.ts +12 -3
- package/dist/types/session/history-storage.d.ts +16 -1
- package/dist/types/session/session-manager.d.ts +4 -0
- package/dist/types/task/output-manager.d.ts +6 -15
- package/dist/types/tools/ask.d.ts +8 -6
- package/dist/types/tools/eval-backends.d.ts +12 -0
- package/dist/types/tools/eval-render.d.ts +52 -0
- package/dist/types/tools/eval.d.ts +2 -35
- package/dist/types/tools/find.d.ts +0 -9
- package/dist/types/tools/index.d.ts +5 -12
- package/dist/types/tools/path-utils.d.ts +16 -0
- package/dist/types/tools/sqlite-reader.d.ts +25 -8
- package/dist/types/tui/output-block.d.ts +4 -3
- package/dist/types/utils/clipboard.d.ts +4 -0
- package/dist/types/web/kagi.d.ts +76 -0
- package/dist/types/web/search/providers/exa.d.ts +7 -1
- package/dist/types/web/search/providers/kagi.d.ts +1 -0
- package/examples/extensions/README.md +1 -0
- package/examples/extensions/thinking-note.ts +13 -0
- package/package.json +9 -9
- package/src/async/job-manager.ts +3 -3
- package/src/cli/args.ts +6 -2
- package/src/cli/claude-trace-cli.ts +783 -0
- package/src/cli/session-picker.ts +36 -10
- package/src/cli/update-cli.ts +35 -2
- package/src/commands/launch.ts +3 -0
- package/src/config/keybindings.ts +6 -0
- package/src/config/model-registry.ts +33 -4
- package/src/config/settings-schema.ts +12 -2
- package/src/config/settings.ts +23 -0
- package/src/discovery/claude-plugins.ts +7 -9
- package/src/discovery/claude.ts +41 -22
- package/src/edit/index.ts +23 -3
- package/src/eval/__tests__/agent-bridge.test.ts +148 -4
- package/src/eval/__tests__/heartbeat.test.ts +84 -0
- package/src/eval/__tests__/llm-bridge.test.ts +30 -0
- package/src/eval/agent-bridge.ts +44 -38
- package/src/eval/concurrency-bridge.ts +34 -0
- package/src/eval/heartbeat.ts +74 -0
- package/src/eval/js/executor.ts +13 -9
- package/src/eval/js/shared/prelude.txt +20 -17
- package/src/eval/js/tool-bridge.ts +5 -0
- package/src/eval/llm-bridge.ts +20 -14
- package/src/eval/py/executor.ts +14 -18
- package/src/eval/py/prelude.py +23 -15
- package/src/exec/bash-executor.ts +31 -5
- package/src/extensibility/extensions/loader.ts +16 -18
- package/src/extensibility/extensions/runner.ts +22 -17
- package/src/extensibility/extensions/types.ts +39 -5
- package/src/extensibility/plugins/legacy-pi-compat.ts +115 -131
- package/src/extensibility/skills.ts +0 -1
- package/src/internal-urls/docs-index.generated.ts +14 -13
- package/src/lsp/diagnostics-ledger.ts +51 -0
- package/src/lsp/index.ts +9 -22
- package/src/lsp/utils.ts +21 -0
- package/src/main.ts +92 -24
- package/src/modes/acp/acp-agent.ts +8 -4
- package/src/modes/acp/acp-event-mapper.ts +54 -4
- package/src/modes/components/assistant-message.ts +28 -1
- package/src/modes/components/custom-editor.ts +19 -7
- package/src/modes/components/hook-selector.ts +229 -44
- package/src/modes/components/oauth-selector.ts +12 -6
- package/src/modes/components/session-selector.ts +179 -24
- package/src/modes/components/tool-execution.ts +36 -7
- package/src/modes/controllers/command-controller.ts +2 -11
- package/src/modes/controllers/event-controller.ts +5 -2
- package/src/modes/controllers/extension-ui-controller.ts +6 -4
- package/src/modes/controllers/input-controller.ts +19 -16
- package/src/modes/controllers/selector-controller.ts +61 -21
- package/src/modes/interactive-mode.ts +127 -16
- package/src/modes/rpc/rpc-mode.ts +17 -6
- package/src/modes/theme/theme-schema.json +30 -0
- package/src/modes/theme/theme.ts +39 -2
- package/src/modes/types.ts +7 -3
- package/src/modes/utils/ui-helpers.ts +5 -2
- package/src/prompts/system/orchestrate-notice.md +5 -3
- package/src/prompts/system/workflow-notice.md +2 -2
- package/src/prompts/tools/ask.md +2 -1
- package/src/prompts/tools/eval.md +6 -6
- package/src/prompts/tools/find.md +1 -1
- package/src/prompts/tools/irc.md +6 -6
- package/src/prompts/tools/search.md +1 -1
- package/src/prompts/tools/task.md +1 -1
- package/src/registry/agent-registry.ts +1 -1
- package/src/sdk.ts +85 -31
- package/src/session/agent-session.ts +127 -57
- package/src/session/history-storage.ts +56 -12
- package/src/session/session-manager.ts +34 -0
- package/src/task/output-manager.ts +40 -48
- package/src/task/render.ts +3 -8
- package/src/tools/ask.ts +74 -32
- package/src/tools/browser/tab-worker.ts +8 -5
- package/src/tools/eval-backends.ts +38 -0
- package/src/tools/eval-render.ts +750 -0
- package/src/tools/eval.ts +27 -754
- package/src/tools/find.ts +5 -29
- package/src/tools/index.ts +8 -38
- package/src/tools/path-utils.ts +144 -1
- package/src/tools/read.ts +47 -0
- package/src/tools/renderers.ts +1 -1
- package/src/tools/search.ts +2 -27
- package/src/tools/sqlite-reader.ts +92 -9
- package/src/tools/write.ts +9 -1
- package/src/tui/output-block.ts +5 -4
- package/src/utils/clipboard.ts +38 -1
- package/src/utils/open.ts +37 -2
- package/src/web/kagi.ts +168 -49
- package/src/web/search/providers/anthropic.ts +1 -1
- package/src/web/search/providers/exa.ts +20 -86
- package/src/web/search/providers/kagi.ts +4 -0
|
@@ -62,6 +62,7 @@ import type {
|
|
|
62
62
|
Message,
|
|
63
63
|
MessageAttribution,
|
|
64
64
|
Model,
|
|
65
|
+
ProviderResponseMetadata,
|
|
65
66
|
ProviderSessionState,
|
|
66
67
|
ServiceTier,
|
|
67
68
|
SimpleStreamOptions,
|
|
@@ -87,6 +88,7 @@ import { countTokens, MacOSPowerAssertion } from "@oh-my-pi/pi-natives";
|
|
|
87
88
|
import {
|
|
88
89
|
extractRetryHint,
|
|
89
90
|
getAgentDbPath,
|
|
91
|
+
getInstallId,
|
|
90
92
|
isEnoent,
|
|
91
93
|
isUnexpectedSocketCloseMessage,
|
|
92
94
|
logger,
|
|
@@ -159,6 +161,7 @@ import { containsOrchestrate, ORCHESTRATE_NOTICE } from "../modes/orchestrate";
|
|
|
159
161
|
import { getCurrentThemeName, theme } from "../modes/theme/theme";
|
|
160
162
|
import { parseTurnBudget } from "../modes/turn-budget";
|
|
161
163
|
import { containsUltrathink, ULTRATHINK_NOTICE } from "../modes/ultrathink";
|
|
164
|
+
import { computeNonMessageTokens } from "../modes/utils/context-usage";
|
|
162
165
|
import { containsWorkflow, WORKFLOW_NOTICE } from "../modes/workflow";
|
|
163
166
|
import type { PlanModeState } from "../plan-mode/state";
|
|
164
167
|
import autoContinuePrompt from "../prompts/system/auto-continue.md" with { type: "text" };
|
|
@@ -226,7 +229,7 @@ import type {
|
|
|
226
229
|
SessionContext,
|
|
227
230
|
SessionManager,
|
|
228
231
|
} from "./session-manager";
|
|
229
|
-
import { getLatestCompactionEntry } from "./session-manager";
|
|
232
|
+
import { getLatestCompactionEntry, getRestorableSessionModels } from "./session-manager";
|
|
230
233
|
import type { ShakeMode, ShakeResult } from "./shake-types";
|
|
231
234
|
import { ToolChoiceQueue } from "./tool-choice-queue";
|
|
232
235
|
import { YieldQueue } from "./yield-queue";
|
|
@@ -357,7 +360,7 @@ export interface AgentSessionConfig {
|
|
|
357
360
|
* **MUST NOT** dispose it on their own teardown.
|
|
358
361
|
*/
|
|
359
362
|
ownedAsyncJobManager?: AsyncJobManager;
|
|
360
|
-
/** Agent identity (registry id like "
|
|
363
|
+
/** Agent identity (registry id like "Main" or "Alice") used for IRC routing. */
|
|
361
364
|
agentId?: string;
|
|
362
365
|
/** Shared agent registry (for forwarding IRC observations to the main session UI). */
|
|
363
366
|
agentRegistry?: AgentRegistry;
|
|
@@ -580,15 +583,14 @@ function buildSessionMetadata(
|
|
|
580
583
|
const accountUuid = authStorage?.getOAuthAccountId("anthropic", sessionId);
|
|
581
584
|
if (typeof accountUuid === "string" && accountUuid.length > 0) {
|
|
582
585
|
userId.account_uuid = accountUuid;
|
|
583
|
-
//
|
|
584
|
-
//
|
|
585
|
-
//
|
|
586
|
-
//
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
userId.device_id = crypto.createHash("sha256").update(`omp-device-id-v1:${accountUuid}`).digest("hex");
|
|
586
|
+
// Claude Code's `device_id` is a stable 64-hex install identifier. Use
|
|
587
|
+
// omp's persistent install id as the root instead of deriving it from
|
|
588
|
+
// `account_uuid`: logging into a different Claude account on the same
|
|
589
|
+
// install should not make the device look new.
|
|
590
|
+
userId.device_id = crypto
|
|
591
|
+
.createHash("sha256")
|
|
592
|
+
.update(`omp-claude-device-id-v1:${getInstallId()}`)
|
|
593
|
+
.digest("hex");
|
|
592
594
|
}
|
|
593
595
|
}
|
|
594
596
|
return { user_id: JSON.stringify(userId) };
|
|
@@ -1103,10 +1105,12 @@ export class AgentSession {
|
|
|
1103
1105
|
this.#onResponse = configuredOnResponse
|
|
1104
1106
|
? async (response, model) => {
|
|
1105
1107
|
this.rawSseDebugBuffer.recordResponse(response, model);
|
|
1108
|
+
this.#ingestProviderUsageHeaders(response, model);
|
|
1106
1109
|
await configuredOnResponse(response, model);
|
|
1107
1110
|
}
|
|
1108
1111
|
: (response, model) => {
|
|
1109
1112
|
this.rawSseDebugBuffer.recordResponse(response, model);
|
|
1113
|
+
this.#ingestProviderUsageHeaders(response, model);
|
|
1110
1114
|
};
|
|
1111
1115
|
const configuredOnSseEvent = config.onSseEvent;
|
|
1112
1116
|
this.#onSseEvent = configuredOnSseEvent
|
|
@@ -2826,13 +2830,14 @@ export class AgentSession {
|
|
|
2826
2830
|
|
|
2827
2831
|
/**
|
|
2828
2832
|
* Set agent.sessionId from the session manager and install a dynamic
|
|
2829
|
-
* metadata resolver so every API request carries
|
|
2830
|
-
* like real Claude Code's `getAPIMetadata` output:
|
|
2831
|
-
*
|
|
2832
|
-
* a known account UUID is loaded
|
|
2833
|
-
*
|
|
2834
|
-
*
|
|
2835
|
-
*
|
|
2833
|
+
* metadata resolver so every Anthropic API request carries
|
|
2834
|
+
* `metadata.user_id` shaped like real Claude Code's `getAPIMetadata` output:
|
|
2835
|
+
* `{ session_id, account_uuid, device_id }`. `account_uuid` is included only
|
|
2836
|
+
* when an Anthropic OAuth credential with a known account UUID is loaded;
|
|
2837
|
+
* `device_id` is derived from the persistent omp install id. Resolving live
|
|
2838
|
+
* keeps the value in sync with auth-state changes (login/logout, token
|
|
2839
|
+
* refresh that surfaces a new account uuid) without needing to re-call
|
|
2840
|
+
* `#syncAgentSessionId()` on every such event.
|
|
2836
2841
|
*/
|
|
2837
2842
|
#syncAgentSessionId(sessionId?: string): void {
|
|
2838
2843
|
const sid = this.#providerSessionId ?? sessionId ?? this.sessionManager.getSessionId();
|
|
@@ -3617,8 +3622,13 @@ export class AgentSession {
|
|
|
3617
3622
|
/**
|
|
3618
3623
|
* Replace MCP tools in the registry and recompute the visible MCP tool set immediately.
|
|
3619
3624
|
* This allows /mcp add/remove/reauth to take effect without restarting the session.
|
|
3625
|
+
*
|
|
3626
|
+
* @param mcpTools The new MCP tools to register.
|
|
3627
|
+
* @param options.activateAll When true, force-activates every newly registered MCP tool
|
|
3628
|
+
* regardless of prior selection state. Used when an ACP client provisions MCP servers
|
|
3629
|
+
* for a session where MCP discovery is disabled.
|
|
3620
3630
|
*/
|
|
3621
|
-
async refreshMCPTools(mcpTools: CustomTool[]): Promise<void> {
|
|
3631
|
+
async refreshMCPTools(mcpTools: CustomTool[], options?: { activateAll?: boolean }): Promise<void> {
|
|
3622
3632
|
const previousSelectedMCPToolNames = this.getSelectedMCPToolNames();
|
|
3623
3633
|
const existingNames = Array.from(this.#toolRegistry.keys());
|
|
3624
3634
|
for (const name of existingNames) {
|
|
@@ -3659,6 +3669,18 @@ export class AgentSession {
|
|
|
3659
3669
|
this.#getConfiguredDefaultSelectedMCPToolNames(),
|
|
3660
3670
|
);
|
|
3661
3671
|
|
|
3672
|
+
if (options?.activateAll) {
|
|
3673
|
+
// Force-activate every newly registered MCP tool. This path is used
|
|
3674
|
+
// when an ACP client provisions MCP servers for a session where MCP
|
|
3675
|
+
// discovery is disabled — without it, getSelectedMCPToolNames()
|
|
3676
|
+
// returns only already-active tools (circular deadlock: tools can
|
|
3677
|
+
// only become active if they're already active).
|
|
3678
|
+
const newMcpNames = mcpTools.map(t => t.name);
|
|
3679
|
+
const nextActive = [...new Set([...this.#getActiveNonMCPToolNames(), ...newMcpNames])];
|
|
3680
|
+
await this.#applyActiveToolsByName(nextActive, { previousSelectedMCPToolNames });
|
|
3681
|
+
return;
|
|
3682
|
+
}
|
|
3683
|
+
|
|
3662
3684
|
const nextActive = [...this.#getActiveNonMCPToolNames(), ...this.getSelectedMCPToolNames()];
|
|
3663
3685
|
await this.#applyActiveToolsByName(nextActive, { previousSelectedMCPToolNames });
|
|
3664
3686
|
}
|
|
@@ -4279,7 +4301,7 @@ export class AgentSession {
|
|
|
4279
4301
|
// next microtask alongside the new turn.
|
|
4280
4302
|
const lastAssistant = this.#findLastAssistantMessage();
|
|
4281
4303
|
if (lastAssistant && !options?.skipCompactionCheck) {
|
|
4282
|
-
await this.#checkCompaction(lastAssistant, false, false);
|
|
4304
|
+
await this.#checkCompaction(lastAssistant, false, false, false);
|
|
4283
4305
|
}
|
|
4284
4306
|
|
|
4285
4307
|
// Build messages array (session context, eager todo prelude, then active prompt message)
|
|
@@ -4381,6 +4403,11 @@ export class AgentSession {
|
|
|
4381
4403
|
}
|
|
4382
4404
|
}
|
|
4383
4405
|
|
|
4406
|
+
await this.#runPrePromptCompactionIfNeeded(messages);
|
|
4407
|
+
if (this.#promptGeneration !== generation) {
|
|
4408
|
+
return;
|
|
4409
|
+
}
|
|
4410
|
+
|
|
4384
4411
|
const agentPromptOptions = options?.toolChoice ? { toolChoice: options.toolChoice } : undefined;
|
|
4385
4412
|
await this.#promptAgentWithIdleRetry(messages, agentPromptOptions);
|
|
4386
4413
|
if (!options?.skipPostPromptRecoveryWait) {
|
|
@@ -6091,6 +6118,34 @@ export class AgentSession {
|
|
|
6091
6118
|
}
|
|
6092
6119
|
}
|
|
6093
6120
|
|
|
6121
|
+
#estimatePendingPromptTokens(messages: AgentMessage[]): number {
|
|
6122
|
+
let tokens = computeNonMessageTokens(this);
|
|
6123
|
+
for (const message of this.messages) {
|
|
6124
|
+
tokens += estimateTokens(message);
|
|
6125
|
+
}
|
|
6126
|
+
for (const message of messages) {
|
|
6127
|
+
tokens += estimateTokens(message);
|
|
6128
|
+
}
|
|
6129
|
+
return tokens;
|
|
6130
|
+
}
|
|
6131
|
+
|
|
6132
|
+
async #runPrePromptCompactionIfNeeded(messages: AgentMessage[]): Promise<void> {
|
|
6133
|
+
const model = this.model;
|
|
6134
|
+
if (!model) return;
|
|
6135
|
+
const contextWindow = model.contextWindow ?? 0;
|
|
6136
|
+
if (contextWindow <= 0) return;
|
|
6137
|
+
const compactionSettings = this.settings.getGroup("compaction");
|
|
6138
|
+
const contextTokens = this.#estimatePendingPromptTokens(messages);
|
|
6139
|
+
if (!shouldCompact(contextTokens, contextWindow, compactionSettings)) return;
|
|
6140
|
+
|
|
6141
|
+
logger.debug("Pre-prompt context maintenance triggered by pending prompt size", {
|
|
6142
|
+
contextTokens,
|
|
6143
|
+
contextWindow,
|
|
6144
|
+
model: `${model.provider}/${model.id}`,
|
|
6145
|
+
});
|
|
6146
|
+
await this.#runAutoCompaction("threshold", false, false, false, { autoContinue: false });
|
|
6147
|
+
}
|
|
6148
|
+
|
|
6094
6149
|
/**
|
|
6095
6150
|
* Check if context maintenance or promotion is needed and run it.
|
|
6096
6151
|
* Called after agent_end and before prompt submission.
|
|
@@ -6111,6 +6166,7 @@ export class AgentSession {
|
|
|
6111
6166
|
* `agent_end` handler set this to true so `session.prompt()` resolves cleanly; callers
|
|
6112
6167
|
* on the pre-prompt path (where the next agent turn is about to start) set it to false
|
|
6113
6168
|
* to avoid racing the deferred handoff against the new turn.
|
|
6169
|
+
* @param autoContinue Whether maintenance may schedule the agent-authored continuation prompt.
|
|
6114
6170
|
* @returns true when a deferred handoff was scheduled. Callers MUST then skip any
|
|
6115
6171
|
* subsequent `#scheduleAgentContinue` / reminder appends for this turn — the
|
|
6116
6172
|
* handoff will replace session state and a concurrent `agent.continue()` would
|
|
@@ -6120,6 +6176,7 @@ export class AgentSession {
|
|
|
6120
6176
|
assistantMessage: AssistantMessage,
|
|
6121
6177
|
skipAbortedCheck = true,
|
|
6122
6178
|
allowDefer = true,
|
|
6179
|
+
autoContinue = true,
|
|
6123
6180
|
): Promise<boolean> {
|
|
6124
6181
|
// Skip if message was aborted (user cancelled) - unless skipAbortedCheck is false
|
|
6125
6182
|
if (skipAbortedCheck && assistantMessage.stopReason === "aborted") return false;
|
|
@@ -6157,7 +6214,7 @@ export class AgentSession {
|
|
|
6157
6214
|
// No promotion target available fall through to compaction
|
|
6158
6215
|
const compactionSettings = this.settings.getGroup("compaction");
|
|
6159
6216
|
if (compactionSettings.enabled && compactionSettings.strategy !== "off") {
|
|
6160
|
-
await this.#runAutoCompaction("overflow", true, false, allowDefer);
|
|
6217
|
+
await this.#runAutoCompaction("overflow", true, false, allowDefer, { autoContinue });
|
|
6161
6218
|
}
|
|
6162
6219
|
return false;
|
|
6163
6220
|
}
|
|
@@ -6189,7 +6246,7 @@ export class AgentSession {
|
|
|
6189
6246
|
model: `${assistantMessage.provider}/${assistantMessage.model}`,
|
|
6190
6247
|
strategy: incompleteCompactionSettings.strategy,
|
|
6191
6248
|
});
|
|
6192
|
-
await this.#runAutoCompaction("incomplete", true, false, allowDefer);
|
|
6249
|
+
await this.#runAutoCompaction("incomplete", true, false, allowDefer, { autoContinue });
|
|
6193
6250
|
} else {
|
|
6194
6251
|
// Neither promotion nor compaction is available — surface the dead-end so
|
|
6195
6252
|
// the user understands why the turn yielded with nothing.
|
|
@@ -6215,7 +6272,7 @@ export class AgentSession {
|
|
|
6215
6272
|
// Try promotion first — if a larger model is available, switch instead of compacting
|
|
6216
6273
|
const promoted = await this.#tryContextPromotion(assistantMessage);
|
|
6217
6274
|
if (!promoted) {
|
|
6218
|
-
return await this.#runAutoCompaction("threshold", false, false, allowDefer);
|
|
6275
|
+
return await this.#runAutoCompaction("threshold", false, false, allowDefer, { autoContinue });
|
|
6219
6276
|
}
|
|
6220
6277
|
}
|
|
6221
6278
|
return false;
|
|
@@ -6939,17 +6996,18 @@ export class AgentSession {
|
|
|
6939
6996
|
willRetry: boolean,
|
|
6940
6997
|
deferred = false,
|
|
6941
6998
|
allowDefer = true,
|
|
6999
|
+
options: { autoContinue?: boolean } = {},
|
|
6942
7000
|
): Promise<boolean> {
|
|
6943
7001
|
const compactionSettings = this.settings.getGroup("compaction");
|
|
6944
7002
|
if (compactionSettings.strategy === "off") return false;
|
|
6945
7003
|
if (reason !== "idle" && !compactionSettings.enabled) return false;
|
|
6946
7004
|
const generation = this.#promptGeneration;
|
|
6947
|
-
|
|
7005
|
+
const shouldAutoContinue = options.autoContinue !== false && compactionSettings.autoContinue !== false;
|
|
6948
7006
|
// Shake runs inline (cheap, no remote LLM). On overflow recovery, if shake
|
|
6949
7007
|
// reclaims nothing we fall through to the summary-compaction body below so
|
|
6950
7008
|
// the oversized input still gets resolved.
|
|
6951
7009
|
if (compactionSettings.strategy === "shake") {
|
|
6952
|
-
const outcome = await this.#runAutoShake(reason, willRetry, generation);
|
|
7010
|
+
const outcome = await this.#runAutoShake(reason, willRetry, generation, shouldAutoContinue);
|
|
6953
7011
|
if (outcome !== "fallback") return false;
|
|
6954
7012
|
}
|
|
6955
7013
|
// "overflow" and "incomplete" force inline execution because they are recovery
|
|
@@ -7018,7 +7076,7 @@ export class AgentSession {
|
|
|
7018
7076
|
aborted: false,
|
|
7019
7077
|
willRetry: false,
|
|
7020
7078
|
});
|
|
7021
|
-
if (!autoCompactionSignal.aborted && reason !== "idle" &&
|
|
7079
|
+
if (!autoCompactionSignal.aborted && reason !== "idle" && shouldAutoContinue) {
|
|
7022
7080
|
this.#scheduleAutoContinuePrompt(generation);
|
|
7023
7081
|
}
|
|
7024
7082
|
return false;
|
|
@@ -7270,7 +7328,7 @@ export class AgentSession {
|
|
|
7270
7328
|
};
|
|
7271
7329
|
await this.#emitSessionEvent({ type: "auto_compaction_end", action, result, aborted: false, willRetry });
|
|
7272
7330
|
|
|
7273
|
-
if (!willRetry && reason !== "idle" &&
|
|
7331
|
+
if (!willRetry && reason !== "idle" && shouldAutoContinue) {
|
|
7274
7332
|
this.#scheduleAutoContinuePrompt(generation);
|
|
7275
7333
|
}
|
|
7276
7334
|
|
|
@@ -7348,6 +7406,7 @@ export class AgentSession {
|
|
|
7348
7406
|
reason: "overflow" | "threshold" | "idle" | "incomplete",
|
|
7349
7407
|
willRetry: boolean,
|
|
7350
7408
|
generation: number,
|
|
7409
|
+
autoContinue: boolean,
|
|
7351
7410
|
): Promise<"handled" | "fallback"> {
|
|
7352
7411
|
const action = "shake";
|
|
7353
7412
|
await this.#emitSessionEvent({ type: "auto_compaction_start", reason, action });
|
|
@@ -7355,7 +7414,6 @@ export class AgentSession {
|
|
|
7355
7414
|
const controller = new AbortController();
|
|
7356
7415
|
this.#autoCompactionAbortController = controller;
|
|
7357
7416
|
const signal = controller.signal;
|
|
7358
|
-
const compactionSettings = this.settings.getGroup("compaction");
|
|
7359
7417
|
try {
|
|
7360
7418
|
const result = await this.shake("elide", { config: DEFAULT_SHAKE_CONFIG, signal });
|
|
7361
7419
|
if (signal.aborted) {
|
|
@@ -7391,7 +7449,7 @@ export class AgentSession {
|
|
|
7391
7449
|
skipped: !reclaimed,
|
|
7392
7450
|
});
|
|
7393
7451
|
|
|
7394
|
-
if (!willRetry && reason !== "idle" &&
|
|
7452
|
+
if (!willRetry && reason !== "idle" && autoContinue) {
|
|
7395
7453
|
this.#scheduleAutoContinuePrompt(generation);
|
|
7396
7454
|
}
|
|
7397
7455
|
if (willRetry) {
|
|
@@ -8669,28 +8727,34 @@ export class AgentSession {
|
|
|
8669
8727
|
}
|
|
8670
8728
|
|
|
8671
8729
|
// Restore model if saved
|
|
8672
|
-
const
|
|
8673
|
-
|
|
8674
|
-
|
|
8675
|
-
|
|
8676
|
-
|
|
8677
|
-
|
|
8678
|
-
|
|
8679
|
-
|
|
8680
|
-
|
|
8681
|
-
|
|
8682
|
-
|
|
8683
|
-
|
|
8684
|
-
|
|
8685
|
-
|
|
8686
|
-
|
|
8687
|
-
|
|
8688
|
-
|
|
8689
|
-
|
|
8690
|
-
|
|
8691
|
-
|
|
8692
|
-
|
|
8693
|
-
|
|
8730
|
+
const targetModelStrings = getRestorableSessionModels(
|
|
8731
|
+
sessionContext.models,
|
|
8732
|
+
this.sessionManager.getLastModelChangeRole(),
|
|
8733
|
+
);
|
|
8734
|
+
if (targetModelStrings.length > 0) {
|
|
8735
|
+
const availableModels = this.#modelRegistry.getAvailable();
|
|
8736
|
+
let match: Model | undefined;
|
|
8737
|
+
for (const targetModelStr of targetModelStrings) {
|
|
8738
|
+
const slashIdx = targetModelStr.indexOf("/");
|
|
8739
|
+
if (slashIdx <= 0) continue;
|
|
8740
|
+
const provider = targetModelStr.slice(0, slashIdx);
|
|
8741
|
+
const modelId = targetModelStr.slice(slashIdx + 1);
|
|
8742
|
+
match = availableModels.find(m => m.provider === provider && m.id === modelId);
|
|
8743
|
+
if (match) break;
|
|
8744
|
+
}
|
|
8745
|
+
if (match) {
|
|
8746
|
+
const currentModel = this.model;
|
|
8747
|
+
const shouldResetProviderState =
|
|
8748
|
+
switchingToDifferentSession ||
|
|
8749
|
+
(currentModel !== undefined &&
|
|
8750
|
+
(currentModel.provider !== match.provider ||
|
|
8751
|
+
currentModel.id !== match.id ||
|
|
8752
|
+
currentModel.api !== match.api));
|
|
8753
|
+
if (shouldResetProviderState) {
|
|
8754
|
+
this.#setModelWithProviderSessionReset(match);
|
|
8755
|
+
} else {
|
|
8756
|
+
this.agent.setModel(match);
|
|
8757
|
+
this.#syncToolCallBatchCap(match);
|
|
8694
8758
|
}
|
|
8695
8759
|
}
|
|
8696
8760
|
}
|
|
@@ -9148,12 +9212,10 @@ export class AgentSession {
|
|
|
9148
9212
|
* Uses the last assistant message's usage data when available,
|
|
9149
9213
|
* otherwise estimates tokens for all messages.
|
|
9150
9214
|
*/
|
|
9151
|
-
getContextUsage(): ContextUsage | undefined {
|
|
9215
|
+
getContextUsage(options?: { contextWindow?: number }): ContextUsage | undefined {
|
|
9152
9216
|
const model = this.model;
|
|
9153
|
-
|
|
9154
|
-
|
|
9155
|
-
const contextWindow = model.contextWindow ?? 0;
|
|
9156
|
-
if (contextWindow <= 0) return undefined;
|
|
9217
|
+
const contextWindow = options?.contextWindow ?? model?.contextWindow ?? 0;
|
|
9218
|
+
if (!Number.isFinite(contextWindow) || contextWindow <= 0) return undefined;
|
|
9157
9219
|
|
|
9158
9220
|
// After compaction, the last assistant usage reflects pre-compaction context size.
|
|
9159
9221
|
// We can only trust usage from an assistant that responded after the latest compaction.
|
|
@@ -9194,6 +9256,14 @@ export class AgentSession {
|
|
|
9194
9256
|
};
|
|
9195
9257
|
}
|
|
9196
9258
|
|
|
9259
|
+
#ingestProviderUsageHeaders(response: ProviderResponseMetadata, model?: Model): void {
|
|
9260
|
+
if (model?.provider !== "anthropic") return;
|
|
9261
|
+
this.#modelRegistry.authStorage.ingestUsageHeaders("anthropic", response.headers, {
|
|
9262
|
+
sessionId: this.agent.sessionId,
|
|
9263
|
+
baseUrl: this.#modelRegistry.getProviderBaseUrl?.("anthropic"),
|
|
9264
|
+
});
|
|
9265
|
+
}
|
|
9266
|
+
|
|
9197
9267
|
async fetchUsageReports(signal?: AbortSignal): Promise<UsageReport[] | null> {
|
|
9198
9268
|
const authStorage = this.#modelRegistry.authStorage;
|
|
9199
9269
|
if (!authStorage.fetchUsageReports) return null;
|
|
@@ -9211,7 +9281,7 @@ export class AgentSession {
|
|
|
9211
9281
|
} {
|
|
9212
9282
|
const messages = this.messages;
|
|
9213
9283
|
|
|
9214
|
-
// Find last assistant message with usage
|
|
9284
|
+
// Find last assistant message with valid usage.
|
|
9215
9285
|
let lastUsageIndex: number | null = null;
|
|
9216
9286
|
let lastUsage: Usage | undefined;
|
|
9217
9287
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -8,6 +8,8 @@ export interface HistoryEntry {
|
|
|
8
8
|
prompt: string;
|
|
9
9
|
created_at: number;
|
|
10
10
|
cwd?: string;
|
|
11
|
+
/** ID of the session the prompt was submitted from, if known. */
|
|
12
|
+
sessionId?: string;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
type HistoryRow = {
|
|
@@ -15,6 +17,7 @@ type HistoryRow = {
|
|
|
15
17
|
prompt: string;
|
|
16
18
|
created_at: number;
|
|
17
19
|
cwd: string | null;
|
|
20
|
+
session_id: string | null;
|
|
18
21
|
};
|
|
19
22
|
|
|
20
23
|
const SQLITE_NOW_EPOCH = "CAST(strftime('%s','now') AS INTEGER)";
|
|
@@ -62,7 +65,8 @@ class AsyncDrain<T> {
|
|
|
62
65
|
export class HistoryStorage {
|
|
63
66
|
#db: Database;
|
|
64
67
|
static #instance?: HistoryStorage;
|
|
65
|
-
#drain = new AsyncDrain<Pick<HistoryEntry, "prompt" | "cwd">>(100);
|
|
68
|
+
#drain = new AsyncDrain<Pick<HistoryEntry, "prompt" | "cwd" | "sessionId">>(100);
|
|
69
|
+
#sessionResolver?: () => string | undefined;
|
|
66
70
|
|
|
67
71
|
// Prepared statements
|
|
68
72
|
#insertRowStmt: Statement;
|
|
@@ -91,7 +95,8 @@ CREATE TABLE IF NOT EXISTS history (
|
|
|
91
95
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
92
96
|
prompt TEXT NOT NULL,
|
|
93
97
|
created_at INTEGER NOT NULL DEFAULT (${SQLITE_NOW_EPOCH}),
|
|
94
|
-
cwd TEXT
|
|
98
|
+
cwd TEXT,
|
|
99
|
+
session_id TEXT
|
|
95
100
|
);
|
|
96
101
|
CREATE INDEX IF NOT EXISTS idx_history_created_at ON history(created_at DESC);
|
|
97
102
|
|
|
@@ -106,6 +111,10 @@ CREATE TRIGGER IF NOT EXISTS history_ai AFTER INSERT ON history BEGIN
|
|
|
106
111
|
this.#migrateHistorySchema();
|
|
107
112
|
}
|
|
108
113
|
|
|
114
|
+
if (!this.#historySchemaHasColumn("session_id")) {
|
|
115
|
+
this.#db.run("ALTER TABLE history ADD COLUMN session_id TEXT");
|
|
116
|
+
}
|
|
117
|
+
|
|
109
118
|
if (!hasFts) {
|
|
110
119
|
try {
|
|
111
120
|
this.#db.run("INSERT INTO history_fts(history_fts) VALUES('rebuild')");
|
|
@@ -115,14 +124,14 @@ CREATE TRIGGER IF NOT EXISTS history_ai AFTER INSERT ON history BEGIN
|
|
|
115
124
|
}
|
|
116
125
|
|
|
117
126
|
this.#recentStmt = this.#db.prepare(
|
|
118
|
-
"SELECT id, prompt, created_at, cwd FROM history ORDER BY created_at DESC, id DESC LIMIT ?",
|
|
127
|
+
"SELECT id, prompt, created_at, cwd, session_id FROM history ORDER BY created_at DESC, id DESC LIMIT ?",
|
|
119
128
|
);
|
|
120
129
|
this.#searchStmt = this.#db.prepare(
|
|
121
|
-
"SELECT h.id, h.prompt, h.created_at, h.cwd FROM history_fts f JOIN history h ON h.id = f.rowid WHERE history_fts MATCH ? ORDER BY h.created_at DESC, h.id DESC LIMIT ?",
|
|
130
|
+
"SELECT h.id, h.prompt, h.created_at, h.cwd, h.session_id FROM history_fts f JOIN history h ON h.id = f.rowid WHERE history_fts MATCH ? ORDER BY h.created_at DESC, h.id DESC LIMIT ?",
|
|
122
131
|
);
|
|
123
132
|
this.#lastPromptStmt = this.#db.prepare("SELECT prompt FROM history ORDER BY id DESC LIMIT 1");
|
|
124
133
|
|
|
125
|
-
this.#insertRowStmt = this.#db.prepare("INSERT INTO history (prompt, cwd) VALUES (?, ?)");
|
|
134
|
+
this.#insertRowStmt = this.#db.prepare("INSERT INTO history (prompt, cwd, session_id) VALUES (?, ?, ?)");
|
|
126
135
|
|
|
127
136
|
const last = this.#lastPromptStmt.get() as { prompt?: string } | undefined;
|
|
128
137
|
this.#lastPromptCache = last?.prompt ?? null;
|
|
@@ -140,20 +149,30 @@ CREATE TRIGGER IF NOT EXISTS history_ai AFTER INSERT ON history BEGIN
|
|
|
140
149
|
HistoryStorage.#instance = undefined;
|
|
141
150
|
}
|
|
142
151
|
|
|
143
|
-
#insertBatch(rows: Array<Pick<HistoryEntry, "prompt" | "cwd">>): void {
|
|
144
|
-
this.#db.transaction((rows: Array<Pick<HistoryEntry, "prompt" | "cwd">>) => {
|
|
152
|
+
#insertBatch(rows: Array<Pick<HistoryEntry, "prompt" | "cwd" | "sessionId">>): void {
|
|
153
|
+
this.#db.transaction((rows: Array<Pick<HistoryEntry, "prompt" | "cwd" | "sessionId">>) => {
|
|
145
154
|
for (const row of rows) {
|
|
146
|
-
this.#insertRowStmt.run(row.prompt, row.cwd ?? null);
|
|
155
|
+
this.#insertRowStmt.run(row.prompt, row.cwd ?? null, row.sessionId ?? null);
|
|
147
156
|
}
|
|
148
157
|
})(rows);
|
|
149
158
|
}
|
|
150
159
|
|
|
151
|
-
|
|
160
|
+
/**
|
|
161
|
+
* Register a resolver that supplies the current session ID for prompts added
|
|
162
|
+
* without an explicit `sessionId`. Evaluated synchronously at `add()` time so
|
|
163
|
+
* batched writes capture the session active when the prompt was submitted.
|
|
164
|
+
*/
|
|
165
|
+
setSessionResolver(resolver: () => string | undefined): void {
|
|
166
|
+
this.#sessionResolver = resolver;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
add(prompt: string, cwd?: string, sessionId?: string): Promise<void> {
|
|
152
170
|
const trimmed = prompt.trim();
|
|
153
171
|
if (!trimmed) return Promise.resolve();
|
|
154
172
|
if (this.#lastPromptCache === trimmed) return Promise.resolve();
|
|
155
173
|
this.#lastPromptCache = trimmed;
|
|
156
|
-
|
|
174
|
+
const session = sessionId ?? this.#sessionResolver?.();
|
|
175
|
+
return this.#drain.push({ prompt: trimmed, cwd: cwd ?? undefined, sessionId: session || undefined }, rows => {
|
|
157
176
|
this.#insertBatch(rows);
|
|
158
177
|
});
|
|
159
178
|
}
|
|
@@ -224,6 +243,24 @@ CREATE TRIGGER IF NOT EXISTS history_ai AFTER INSERT ON history BEGIN
|
|
|
224
243
|
return merged;
|
|
225
244
|
}
|
|
226
245
|
|
|
246
|
+
/**
|
|
247
|
+
* IDs of the sessions whose stored prompts match `query`, ordered by match
|
|
248
|
+
* relevance (most relevant/recent first) and de-duplicated. Prompts with no
|
|
249
|
+
* recorded session are skipped. Used to augment session ranking in the
|
|
250
|
+
* resume picker with prompts that the 4KB session-list prefix never sees.
|
|
251
|
+
*/
|
|
252
|
+
matchingSessionIds(query: string, limit = 500): string[] {
|
|
253
|
+
const seen = new Set<string>();
|
|
254
|
+
const ids: string[] = [];
|
|
255
|
+
for (const entry of this.search(query, limit)) {
|
|
256
|
+
const id = entry.sessionId;
|
|
257
|
+
if (!id || seen.has(id)) continue;
|
|
258
|
+
seen.add(id);
|
|
259
|
+
ids.push(id);
|
|
260
|
+
}
|
|
261
|
+
return ids;
|
|
262
|
+
}
|
|
263
|
+
|
|
227
264
|
#ensureDir(dbPath: string): void {
|
|
228
265
|
const dir = path.dirname(dbPath);
|
|
229
266
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -236,6 +273,11 @@ CREATE TRIGGER IF NOT EXISTS history_ai AFTER INSERT ON history BEGIN
|
|
|
236
273
|
return row?.sql?.includes("unixepoch(") ?? false;
|
|
237
274
|
}
|
|
238
275
|
|
|
276
|
+
#historySchemaHasColumn(column: string): boolean {
|
|
277
|
+
const columns = this.#db.prepare("PRAGMA table_info(history)").all() as Array<{ name: string }>;
|
|
278
|
+
return columns.some(col => col.name === column);
|
|
279
|
+
}
|
|
280
|
+
|
|
239
281
|
#migrateHistorySchema(): void {
|
|
240
282
|
const migrate = this.#db.transaction(() => {
|
|
241
283
|
this.#db.run("ALTER TABLE history RENAME TO history_legacy");
|
|
@@ -247,7 +289,8 @@ CREATE TABLE history (
|
|
|
247
289
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
248
290
|
prompt TEXT NOT NULL,
|
|
249
291
|
created_at INTEGER NOT NULL DEFAULT (${SQLITE_NOW_EPOCH}),
|
|
250
|
-
cwd TEXT
|
|
292
|
+
cwd TEXT,
|
|
293
|
+
session_id TEXT
|
|
251
294
|
);
|
|
252
295
|
CREATE INDEX IF NOT EXISTS idx_history_created_at ON history(created_at DESC);
|
|
253
296
|
INSERT INTO history (id, prompt, created_at, cwd)
|
|
@@ -294,7 +337,7 @@ END;
|
|
|
294
337
|
if (stmt) return stmt;
|
|
295
338
|
const whereClause = Array(tokenCount).fill("prompt LIKE ? ESCAPE '\\' COLLATE NOCASE").join(" AND ");
|
|
296
339
|
stmt = this.#db.prepare(
|
|
297
|
-
`SELECT id, prompt, created_at, cwd FROM history WHERE ${whereClause} ORDER BY created_at DESC, id DESC LIMIT ?`,
|
|
340
|
+
`SELECT id, prompt, created_at, cwd, session_id FROM history WHERE ${whereClause} ORDER BY created_at DESC, id DESC LIMIT ?`,
|
|
298
341
|
);
|
|
299
342
|
this.#substringStmts.set(tokenCount, stmt);
|
|
300
343
|
return stmt;
|
|
@@ -306,6 +349,7 @@ END;
|
|
|
306
349
|
prompt: row.prompt,
|
|
307
350
|
created_at: row.created_at,
|
|
308
351
|
cwd: row.cwd ?? undefined,
|
|
352
|
+
sessionId: row.session_id ?? undefined,
|
|
309
353
|
};
|
|
310
354
|
}
|
|
311
355
|
}
|
|
@@ -254,6 +254,22 @@ export interface SessionContext {
|
|
|
254
254
|
modeData?: Record<string, unknown>;
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
+
/** Lists session model strings to try when restoring, in fallback order. */
|
|
258
|
+
export function getRestorableSessionModels(
|
|
259
|
+
models: Readonly<Record<string, string>>,
|
|
260
|
+
lastModelChangeRole: string | undefined,
|
|
261
|
+
): string[] {
|
|
262
|
+
const defaultModel = models.default;
|
|
263
|
+
if (!lastModelChangeRole || lastModelChangeRole === "default" || lastModelChangeRole === "temporary") {
|
|
264
|
+
return defaultModel ? [defaultModel] : [];
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const roleModel = models[lastModelChangeRole];
|
|
268
|
+
if (!roleModel) return defaultModel ? [defaultModel] : [];
|
|
269
|
+
if (!defaultModel || roleModel === defaultModel) return [roleModel];
|
|
270
|
+
return [roleModel, defaultModel];
|
|
271
|
+
}
|
|
272
|
+
|
|
257
273
|
export interface SessionInfo {
|
|
258
274
|
path: string;
|
|
259
275
|
id: string;
|
|
@@ -1808,6 +1824,8 @@ export async function resolveResumableSession(
|
|
|
1808
1824
|
return { session: globalMatch, scope: "global" };
|
|
1809
1825
|
}
|
|
1810
1826
|
interface SessionManagerStateSnapshot {
|
|
1827
|
+
cwd: string;
|
|
1828
|
+
sessionDir: string;
|
|
1811
1829
|
sessionId: string;
|
|
1812
1830
|
sessionName: string | undefined;
|
|
1813
1831
|
titleSource: "auto" | "user" | undefined;
|
|
@@ -1880,6 +1898,8 @@ export class SessionManager {
|
|
|
1880
1898
|
|
|
1881
1899
|
captureState(): SessionManagerStateSnapshot {
|
|
1882
1900
|
return {
|
|
1901
|
+
cwd: this.cwd,
|
|
1902
|
+
sessionDir: this.sessionDir,
|
|
1883
1903
|
sessionId: this.#sessionId,
|
|
1884
1904
|
sessionName: this.#sessionName,
|
|
1885
1905
|
titleSource: this.#titleSource,
|
|
@@ -1893,6 +1913,8 @@ export class SessionManager {
|
|
|
1893
1913
|
}
|
|
1894
1914
|
|
|
1895
1915
|
restoreState(snapshot: SessionManagerStateSnapshot): void {
|
|
1916
|
+
this.cwd = snapshot.cwd;
|
|
1917
|
+
this.sessionDir = snapshot.sessionDir;
|
|
1896
1918
|
this.#sessionId = snapshot.sessionId;
|
|
1897
1919
|
this.#sessionName = snapshot.sessionName;
|
|
1898
1920
|
this.#titleSource = snapshot.titleSource;
|
|
@@ -1938,6 +1960,18 @@ export class SessionManager {
|
|
|
1938
1960
|
this.#sessionName = header?.title;
|
|
1939
1961
|
this.#titleSource = header?.titleSource;
|
|
1940
1962
|
|
|
1963
|
+
// Adopt the loaded session's own working directory. Sessions are stored in
|
|
1964
|
+
// a directory keyed by their cwd, so resuming a session from another
|
|
1965
|
+
// project (e.g. global review in the picker) must re-point cwd/sessionDir
|
|
1966
|
+
// at that project. Same-cwd resumes and in-place reloads are a no-op; old
|
|
1967
|
+
// sessions with no recorded cwd keep the current cwd.
|
|
1968
|
+
const headerCwd = header?.cwd ? path.resolve(header.cwd) : undefined;
|
|
1969
|
+
if (headerCwd && headerCwd !== this.cwd) {
|
|
1970
|
+
this.cwd = headerCwd;
|
|
1971
|
+
this.sessionDir = path.resolve(this.#sessionFile, "..");
|
|
1972
|
+
writeTerminalBreadcrumb(this.cwd, this.#sessionFile);
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1941
1975
|
this.#needsFullRewriteOnNextPersist = migrateToCurrentVersion(this.#fileEntries);
|
|
1942
1976
|
|
|
1943
1977
|
await resolveBlobRefsInEntries(this.#fileEntries, this.#blobStore);
|