@oh-my-pi/pi-coding-agent 17.2.12 → 17.2.14
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 +63 -0
- package/dist/{CHANGELOG-k9ghy5sn.md → CHANGELOG-za420td8.md} +63 -0
- package/dist/cli.js +3658 -3627
- package/dist/types/advisor/delta-split.d.ts +24 -0
- package/dist/types/advisor/runtime.d.ts +2 -2
- package/dist/types/async/job-manager.d.ts +8 -1
- package/dist/types/cli/update-cli.d.ts +53 -1
- package/dist/types/config/keybindings.d.ts +10 -0
- package/dist/types/config/model-resolver.d.ts +15 -2
- package/dist/types/config/settings-schema.d.ts +14 -0
- package/dist/types/discovery/agents-md.d.ts +10 -1
- package/dist/types/eval/runner-cache.d.ts +12 -0
- package/dist/types/extensibility/extensions/runner.d.ts +12 -3
- package/dist/types/extensibility/extensions/types.d.ts +15 -4
- package/dist/types/extensibility/plugins/marketplace/manager.d.ts +4 -1
- package/dist/types/lib/xai-http.d.ts +0 -1
- package/dist/types/mcp/tool-bridge.d.ts +8 -5
- package/dist/types/modes/components/agent-hub-renderer.d.ts +6 -1
- package/dist/types/modes/components/status-line/types.d.ts +4 -0
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +2 -4
- package/dist/types/modes/interactive-mode.d.ts +17 -8
- package/dist/types/modes/types.d.ts +7 -10
- package/dist/types/modes/utils/hotkeys-markdown.d.ts +1 -1
- package/dist/types/session/agent-session-types.d.ts +4 -0
- package/dist/types/session/agent-session.d.ts +24 -3
- package/dist/types/session/messages.d.ts +20 -0
- package/dist/types/session/retry-fallback-chains.d.ts +13 -0
- package/dist/types/session/session-advisors.d.ts +1 -1
- package/dist/types/session/session-history-format.d.ts +10 -0
- package/dist/types/session/session-maintenance.d.ts +1 -1
- package/dist/types/session/session-tools.d.ts +35 -5
- package/dist/types/session/turn-recovery.d.ts +34 -5
- package/dist/types/slash-commands/types.d.ts +5 -1
- package/dist/types/task/executor.d.ts +1 -1
- package/dist/types/tools/approval.d.ts +7 -0
- package/dist/types/tools/builtin-names.d.ts +1 -2
- package/dist/types/tools/index.d.ts +1 -0
- package/dist/types/tools/think.d.ts +38 -0
- package/dist/types/tools/todo.d.ts +14 -15
- package/dist/types/tools/write.d.ts +2 -2
- package/dist/types/utils/local-date.d.ts +2 -0
- package/dist/types/vibe/runtime.d.ts +1 -1
- package/dist/types/web/parallel.d.ts +1 -0
- package/dist/types/web/search/providers/brave.d.ts +8 -3
- package/dist/types/web/search/providers/codex.d.ts +6 -0
- package/dist/types/web/search/providers/firecrawl.d.ts +3 -2
- package/dist/types/web/search/providers/jina.d.ts +3 -3
- package/dist/types/web/search/providers/parallel.d.ts +1 -0
- package/dist/types/web/search/providers/perplexity.d.ts +4 -0
- package/dist/types/web/search/providers/tinyfish.d.ts +2 -0
- package/package.json +13 -13
- package/src/advisor/delta-split.ts +98 -0
- package/src/advisor/runtime.ts +321 -69
- package/src/async/job-manager.ts +14 -3
- package/src/cli/gallery-fixtures/agentic.ts +16 -0
- package/src/cli/plugin-cli.ts +30 -2
- package/src/cli/update-cli.ts +259 -24
- package/src/config/keybindings.ts +52 -9
- package/src/config/model-resolver.ts +19 -3
- package/src/config/settings-schema.ts +16 -0
- package/src/cursor.ts +10 -5
- package/src/discovery/agents-md.ts +61 -23
- package/src/eval/jl/kernel.ts +2 -20
- package/src/eval/py/kernel.ts +2 -20
- package/src/eval/rb/kernel.ts +2 -20
- package/src/eval/runner-cache.ts +41 -0
- package/src/exec/non-interactive-env.ts +14 -3
- package/src/extensibility/extensions/loader.ts +5 -2
- package/src/extensibility/extensions/runner.ts +184 -66
- package/src/extensibility/extensions/types.ts +26 -2
- package/src/extensibility/extensions/wrapper.ts +13 -7
- package/src/extensibility/plugins/marketplace/manager.ts +6 -2
- package/src/hindsight/client.ts +1 -1
- package/src/lib/xai-http.ts +0 -4
- package/src/lsp/client.ts +2 -0
- package/src/lsp/servers.ts +1 -1
- package/src/mcp/tool-bridge.ts +15 -6
- package/src/modes/components/agent-hub-renderer.ts +9 -3
- package/src/modes/components/agent-hub.ts +2 -1
- package/src/modes/components/status-line/component.ts +58 -6
- package/src/modes/components/status-line/segments.ts +12 -1
- package/src/modes/components/status-line/types.ts +1 -0
- package/src/modes/components/user-message.ts +20 -5
- package/src/modes/controllers/event-controller.ts +48 -0
- package/src/modes/controllers/extension-ui-controller.ts +14 -7
- package/src/modes/controllers/input-controller.ts +25 -6
- package/src/modes/controllers/selector-controller.ts +5 -0
- package/src/modes/interactive-mode.ts +315 -129
- package/src/modes/rpc/rpc-frame.ts +13 -5
- package/src/modes/theme/tui-adapters.ts +4 -5
- package/src/modes/types.ts +13 -7
- package/src/modes/utils/hotkeys-markdown.ts +10 -6
- package/src/prompts/system/system-prompt.md +10 -1
- package/src/registry/persisted-agents.ts +43 -8
- package/src/sdk.ts +146 -9
- package/src/session/agent-session-types.ts +4 -0
- package/src/session/agent-session.ts +91 -10
- package/src/session/messages.ts +98 -28
- package/src/session/retry-fallback-chains.ts +14 -0
- package/src/session/session-advisors.ts +32 -15
- package/src/session/session-history-format.ts +15 -1
- package/src/session/session-maintenance.ts +8 -8
- package/src/session/session-manager.ts +6 -2
- package/src/session/session-tools.ts +355 -183
- package/src/session/turn-recovery.ts +225 -47
- package/src/slash-commands/builtin-modes.ts +41 -12
- package/src/slash-commands/types.ts +5 -1
- package/src/task/executor.ts +92 -45
- package/src/task/structured-subagent.ts +5 -5
- package/src/tools/approval.ts +44 -10
- package/src/tools/builtin-names.ts +1 -2
- package/src/tools/fetch.ts +21 -2
- package/src/tools/image-gen.ts +6 -8
- package/src/tools/index.ts +8 -0
- package/src/tools/renderers.ts +2 -0
- package/src/tools/think.ts +62 -0
- package/src/tools/todo.ts +70 -26
- package/src/tools/tts.ts +3 -2
- package/src/tools/write.ts +7 -3
- package/src/utils/local-date.ts +13 -0
- package/src/utils/tools-manager.ts +2 -2
- package/src/vibe/runtime.ts +22 -14
- package/src/web/kagi.ts +91 -34
- package/src/web/parallel.ts +11 -2
- package/src/web/scrapers/crates-io.ts +2 -2
- package/src/web/scrapers/discogs.ts +2 -2
- package/src/web/scrapers/docs-rs.ts +2 -2
- package/src/web/scrapers/github.ts +2 -2
- package/src/web/scrapers/musicbrainz.ts +1 -2
- package/src/web/scrapers/pubmed.ts +2 -2
- package/src/web/scrapers/sec-edgar.ts +2 -2
- package/src/web/search/providers/brave.ts +121 -46
- package/src/web/search/providers/codex.ts +88 -12
- package/src/web/search/providers/exa.ts +45 -10
- package/src/web/search/providers/firecrawl.ts +53 -11
- package/src/web/search/providers/gemini.ts +139 -27
- package/src/web/search/providers/jina.ts +48 -25
- package/src/web/search/providers/parallel.ts +23 -9
- package/src/web/search/providers/perplexity.ts +24 -7
- package/src/web/search/providers/searxng.ts +77 -1
- package/src/web/search/providers/tavily.ts +23 -22
- package/src/web/search/providers/tinyfish.ts +44 -10
- package/src/web/search/providers/xai.ts +85 -14
|
@@ -98,7 +98,7 @@ import {
|
|
|
98
98
|
withTimeout,
|
|
99
99
|
} from "@oh-my-pi/pi-utils";
|
|
100
100
|
import { type AdvisorConfig, type AdvisorRuntimeStatus, loadAdvisorTranscriptCosts } from "../advisor";
|
|
101
|
-
import { type AsyncJob, AsyncJobManager } from "../async";
|
|
101
|
+
import { ASYNC_JOB_MANAGER_SHUTDOWN_REASON, type AsyncJob, AsyncJobManager } from "../async";
|
|
102
102
|
import { shouldEnableAppendOnlyContext } from "../config/append-only-context-mode";
|
|
103
103
|
import type { ModelRegistry } from "../config/model-registry";
|
|
104
104
|
import type { ResolvedModelRoleValue } from "../config/model-resolver";
|
|
@@ -313,6 +313,7 @@ import {
|
|
|
313
313
|
queueChipText,
|
|
314
314
|
toRestoredQueuedMessage,
|
|
315
315
|
} from "./queued-messages";
|
|
316
|
+
import type { ServingModel } from "./retry-fallback-chains";
|
|
316
317
|
import { type AdvisorStats, SessionAdvisors, type SessionAdvisorsHost } from "./session-advisors";
|
|
317
318
|
import type { BuildSessionContextOptions, SessionContext } from "./session-context";
|
|
318
319
|
import { getRestorableSessionModels } from "./session-context";
|
|
@@ -1240,8 +1241,10 @@ export class AgentSession {
|
|
|
1240
1241
|
toolRegistry: config.toolRegistry,
|
|
1241
1242
|
createVibeTools: config.createVibeTools,
|
|
1242
1243
|
createComputerTool: config.createComputerTool,
|
|
1244
|
+
createThinkTool: config.createThinkTool,
|
|
1243
1245
|
createInspectImageTool: config.createInspectImageTool,
|
|
1244
1246
|
builtInToolNames: config.builtInToolNames,
|
|
1247
|
+
mcpManagerToolNames: config.mcpManagerToolNames,
|
|
1245
1248
|
presentationPinnedToolNames: config.presentationPinnedToolNames,
|
|
1246
1249
|
ensureWriteRegistered: config.ensureWriteRegistered,
|
|
1247
1250
|
rebuildSystemPrompt: config.rebuildSystemPrompt,
|
|
@@ -1488,7 +1491,7 @@ export class AgentSession {
|
|
|
1488
1491
|
this.#planReferenceSent = false;
|
|
1489
1492
|
},
|
|
1490
1493
|
syncTodoPhasesFromBranch: () => this.#todo.syncFromBranch(),
|
|
1491
|
-
resetAdvisorRuntimes: () => this.#advisors.resetAllRuntimes(),
|
|
1494
|
+
resetAdvisorRuntimes: (reason?: string) => this.#advisors.resetAllRuntimes(reason),
|
|
1492
1495
|
rebaseAfterCompaction: () => this.#stats.rebaseAfterCompaction(),
|
|
1493
1496
|
recordAnchoredHistoryRewrite: tokensRemoved => this.#stats.recordAnchoredHistoryRewrite(tokensRemoved),
|
|
1494
1497
|
getContextBreakdown: options => this.getContextBreakdown(options),
|
|
@@ -1783,10 +1786,10 @@ export class AgentSession {
|
|
|
1783
1786
|
*
|
|
1784
1787
|
* No-op when no manager is reachable or this session has no agent id.
|
|
1785
1788
|
*/
|
|
1786
|
-
#cancelOwnAsyncJobs(): void {
|
|
1789
|
+
#cancelOwnAsyncJobs(reason?: unknown): void {
|
|
1787
1790
|
if (!this.#agentId) return;
|
|
1788
1791
|
const manager = this.#asyncJobManager;
|
|
1789
|
-
manager?.cancelAll({ ownerId: this.#agentId });
|
|
1792
|
+
manager?.cancelAll({ ownerId: this.#agentId }, reason);
|
|
1790
1793
|
manager?.evictCompletedJobs({ ownerId: this.#agentId });
|
|
1791
1794
|
// Invalidate this owner's in-flight/drained deliveries against the new
|
|
1792
1795
|
// generation, then drop any async-result follow-up already queued, so a
|
|
@@ -3743,8 +3746,14 @@ export class AgentSession {
|
|
|
3743
3746
|
// dead-letter rather than enqueue a follow-up into a disposing session.
|
|
3744
3747
|
this.#unregisterAsyncDeliverySink?.();
|
|
3745
3748
|
this.#unregisterAsyncDeliverySink = undefined;
|
|
3746
|
-
this.#cancelOwnAsyncJobs();
|
|
3747
3749
|
const manager = this.#ownedAsyncJobManager;
|
|
3750
|
+
// The shutdown reason is reserved for the top-level session that OWNS the
|
|
3751
|
+
// manager — the genuine process/handled-shutdown path — so the task
|
|
3752
|
+
// executor parks (rather than tombstones) interrupted subagents. A
|
|
3753
|
+
// subagent session dispose (e.g. `release({ tombstone: true })` during an
|
|
3754
|
+
// explicit hard kill) leaves `#ownedAsyncJobManager` undefined and must
|
|
3755
|
+
// propagate a generic cancellation so its nested children stay terminal.
|
|
3756
|
+
this.#cancelOwnAsyncJobs(manager ? ASYNC_JOB_MANAGER_SHUTDOWN_REASON : undefined);
|
|
3748
3757
|
if (!manager) return;
|
|
3749
3758
|
|
|
3750
3759
|
try {
|
|
@@ -4097,9 +4106,13 @@ export class AgentSession {
|
|
|
4097
4106
|
return this.agent.state.model;
|
|
4098
4107
|
}
|
|
4099
4108
|
|
|
4100
|
-
/**
|
|
4101
|
-
|
|
4102
|
-
|
|
4109
|
+
/**
|
|
4110
|
+
* Model this session's produced work is attributed to. Holds the last model
|
|
4111
|
+
* that actually served while a fallback is armed but unproven, so observers
|
|
4112
|
+
* never credit a run to a candidate that produced nothing.
|
|
4113
|
+
*/
|
|
4114
|
+
get servingModel(): ServingModel | undefined {
|
|
4115
|
+
return this.#recovery.servingModel;
|
|
4103
4116
|
}
|
|
4104
4117
|
|
|
4105
4118
|
/** Install the interactive decision surface for reserve-triggered model changes. */
|
|
@@ -4296,6 +4309,41 @@ export class AgentSession {
|
|
|
4296
4309
|
return this.#tools.hasBuiltInTool(name);
|
|
4297
4310
|
}
|
|
4298
4311
|
|
|
4312
|
+
/** Updates source provenance when a live registry entry is replaced or restored. */
|
|
4313
|
+
setToolBuiltIn(name: string, builtIn: boolean): void {
|
|
4314
|
+
this.#tools.setToolBuiltIn(name, builtIn);
|
|
4315
|
+
}
|
|
4316
|
+
|
|
4317
|
+
/** Whether the live registry entry is owned by the RPC host. */
|
|
4318
|
+
hasRpcHostTool(name: string): boolean {
|
|
4319
|
+
return this.#tools.hasRpcHostTool(name);
|
|
4320
|
+
}
|
|
4321
|
+
|
|
4322
|
+
/** Whether the current MCP entry came from the manager snapshot. */
|
|
4323
|
+
hasMCPManagerTool(name: string): boolean {
|
|
4324
|
+
return this.#tools.hasMCPManagerTool(name);
|
|
4325
|
+
}
|
|
4326
|
+
|
|
4327
|
+
/** Restores manager ownership after a lifecycle registration rollback. */
|
|
4328
|
+
setMCPManagerTool(name: string, managerOwned: boolean): void {
|
|
4329
|
+
this.#tools.setMCPManagerTool(name, managerOwned);
|
|
4330
|
+
}
|
|
4331
|
+
|
|
4332
|
+
/** Current extension-owned MCP entry retained across manager refreshes. */
|
|
4333
|
+
getExtensionMCPTool(name: string): AgentTool | undefined {
|
|
4334
|
+
return this.#tools.getExtensionMCPTool(name);
|
|
4335
|
+
}
|
|
4336
|
+
|
|
4337
|
+
/** Updates extension MCP ownership after a lifecycle registration commit or rollback. */
|
|
4338
|
+
setExtensionMCPTool(name: string, tool: AgentTool | undefined): void {
|
|
4339
|
+
this.#tools.setExtensionMCPTool(name, tool);
|
|
4340
|
+
}
|
|
4341
|
+
|
|
4342
|
+
/** Runs a registry/presentation mutation in this session's shared queue. */
|
|
4343
|
+
runToolRegistryMutation<T>(mutation: () => Promise<T>, signal?: AbortSignal): Promise<T> {
|
|
4344
|
+
return this.#tools.runToolRegistryMutation(mutation, signal);
|
|
4345
|
+
}
|
|
4346
|
+
|
|
4299
4347
|
/** Names of every registered tool. */
|
|
4300
4348
|
getAllToolNames(): string[] {
|
|
4301
4349
|
return this.#tools.getAllToolNames();
|
|
@@ -4349,8 +4397,13 @@ export class AgentSession {
|
|
|
4349
4397
|
}
|
|
4350
4398
|
|
|
4351
4399
|
/** Restores an exact top-level versus `xd://` tool partition. */
|
|
4352
|
-
setActiveToolPresentation(
|
|
4353
|
-
|
|
4400
|
+
setActiveToolPresentation(
|
|
4401
|
+
toolNames: string[],
|
|
4402
|
+
mountedToolNames: string[],
|
|
4403
|
+
forcePromptRefresh = false,
|
|
4404
|
+
signal?: AbortSignal,
|
|
4405
|
+
): Promise<void> {
|
|
4406
|
+
return this.#tools.setActiveToolPresentation(toolNames, mountedToolNames, forcePromptRefresh, signal);
|
|
4354
4407
|
}
|
|
4355
4408
|
|
|
4356
4409
|
/**
|
|
@@ -4367,6 +4420,11 @@ export class AgentSession {
|
|
|
4367
4420
|
return this.#tools.setComputerToolEnabled(enabled);
|
|
4368
4421
|
}
|
|
4369
4422
|
|
|
4423
|
+
/** Applies the external-thinking setting to the private scratchpad tool immediately. */
|
|
4424
|
+
setThinkToolEnabled(enabled: boolean): Promise<boolean> {
|
|
4425
|
+
return this.#tools.setThinkToolEnabled(enabled);
|
|
4426
|
+
}
|
|
4427
|
+
|
|
4370
4428
|
/**
|
|
4371
4429
|
* Session-scoped inspect_image mode (`/vision`). `auto` clears the override
|
|
4372
4430
|
* and returns to the persisted `inspect_image.mode` setting; `on`/`off`
|
|
@@ -5124,6 +5182,18 @@ export class AgentSession {
|
|
|
5124
5182
|
|
|
5125
5183
|
// Skip eager preludes when the user has already queued a directive
|
|
5126
5184
|
const hasPendingUserDirective = this.#toolChoiceQueue.inspect().includes("user-force");
|
|
5185
|
+
const activeModel = this.agent.state.model;
|
|
5186
|
+
const externalThinkingToolChoice =
|
|
5187
|
+
!options?.synthetic &&
|
|
5188
|
+
!hasPendingUserDirective &&
|
|
5189
|
+
this.settings.get("externalThinking") &&
|
|
5190
|
+
this.getEnabledToolNames().includes("think") &&
|
|
5191
|
+
activeModel &&
|
|
5192
|
+
(activeModel.api === "openai-responses" ||
|
|
5193
|
+
activeModel.api === "azure-openai-responses" ||
|
|
5194
|
+
activeModel.api === "openai-codex-responses")
|
|
5195
|
+
? buildNamedToolChoice("think", activeModel)
|
|
5196
|
+
: undefined;
|
|
5127
5197
|
const eagerTodoPrelude =
|
|
5128
5198
|
!options?.synthetic && !hasPendingUserDirective ? this.#todo.createEagerTodoPrelude(expandedText) : undefined;
|
|
5129
5199
|
const eagerTaskPrelude =
|
|
@@ -5141,6 +5211,12 @@ export class AgentSession {
|
|
|
5141
5211
|
: undefined;
|
|
5142
5212
|
|
|
5143
5213
|
const promptAttribution = options?.attribution ?? (options?.synthetic ? "agent" : "user");
|
|
5214
|
+
if (externalThinkingToolChoice) {
|
|
5215
|
+
this.#toolChoiceQueue.pushOnce(externalThinkingToolChoice, {
|
|
5216
|
+
label: "external-thinking",
|
|
5217
|
+
now: true,
|
|
5218
|
+
});
|
|
5219
|
+
}
|
|
5144
5220
|
const message = options?.synthetic
|
|
5145
5221
|
? { role: "developer" as const, content: userContent, attribution: promptAttribution, timestamp: Date.now() }
|
|
5146
5222
|
: { role: "user" as const, content: userContent, attribution: promptAttribution, timestamp: Date.now() };
|
|
@@ -5171,6 +5247,7 @@ export class AgentSession {
|
|
|
5171
5247
|
// Clean up residual eager-todo directive if the prompt never consumed it
|
|
5172
5248
|
// (e.g., compaction aborted, validation failed).
|
|
5173
5249
|
this.#toolChoiceQueue.removeByLabel("eager-todo");
|
|
5250
|
+
this.#toolChoiceQueue.removeByLabel("external-thinking");
|
|
5174
5251
|
}
|
|
5175
5252
|
return true;
|
|
5176
5253
|
}
|
|
@@ -6497,6 +6574,7 @@ export class AgentSession {
|
|
|
6497
6574
|
async fork(): Promise<boolean> {
|
|
6498
6575
|
this.#assertVibeSessionTransitionAllowed("fork the session");
|
|
6499
6576
|
const previousSessionFile = this.sessionFile;
|
|
6577
|
+
const previousSessionId = this.sessionManager.getSessionId();
|
|
6500
6578
|
|
|
6501
6579
|
// Emit session_before_switch event with reason "fork" (can be cancelled)
|
|
6502
6580
|
if (this.#extensionRunner?.hasHandlers("session_before_switch")) {
|
|
@@ -6535,6 +6613,9 @@ export class AgentSession {
|
|
|
6535
6613
|
}
|
|
6536
6614
|
this.#bash.markSessionTransition(bashTransition);
|
|
6537
6615
|
this.#bash.finishSessionTransition(bashTransition, true);
|
|
6616
|
+
// The fork clones the transcript and keeps this recovery state running
|
|
6617
|
+
// under a fresh id, so the work already produced is still this session's.
|
|
6618
|
+
this.#recovery.reanchorServedAttribution(previousSessionId);
|
|
6538
6619
|
|
|
6539
6620
|
// Copy artifacts directory if it exists
|
|
6540
6621
|
const oldArtifactDir = forkResult.oldSessionFile.slice(0, -6);
|
package/src/session/messages.ts
CHANGED
|
@@ -498,6 +498,76 @@ export function isEmptyErrorTurn(message: Pick<AssistantMessage, "stopReason" |
|
|
|
498
498
|
});
|
|
499
499
|
}
|
|
500
500
|
|
|
501
|
+
/** Non-whitespace text. Tolerates malformed blocks: transcripts replayed off
|
|
502
|
+
* disk predate current shapes, and a missing field must not throw. */
|
|
503
|
+
function hasText(content: { text?: unknown }): boolean {
|
|
504
|
+
return typeof content.text === "string" && content.text.trim().length > 0;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* A block that is real output from the model.
|
|
509
|
+
*
|
|
510
|
+
* Everything the assistant can emit counts except two: unsigned thinking, which
|
|
511
|
+
* is not provider-authenticated and not actionable, and Anthropic's `fallback`
|
|
512
|
+
* marker, which records that the request was routed elsewhere rather than
|
|
513
|
+
* carrying any output. A native image response often arrives with no text and
|
|
514
|
+
* no tool call at all, so recognising only those would call it nothing.
|
|
515
|
+
*/
|
|
516
|
+
function isActionableContent(content: AssistantMessage["content"][number] | undefined): boolean {
|
|
517
|
+
switch (content?.type) {
|
|
518
|
+
case "toolCall":
|
|
519
|
+
case "image":
|
|
520
|
+
case "redactedThinking":
|
|
521
|
+
case "anthropicServerTool":
|
|
522
|
+
return true;
|
|
523
|
+
case "text":
|
|
524
|
+
return hasText(content);
|
|
525
|
+
case "thinking":
|
|
526
|
+
return typeof content.thinkingSignature === "string" && content.thinkingSignature.trim().length > 0;
|
|
527
|
+
default:
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/** A `stop`/`toolUse` turn that produced nothing actionable. Any other stop
|
|
533
|
+
* reason is not an "empty stop": an `error`/`aborted` turn is a failure rather
|
|
534
|
+
* than an empty completion, and a `length` stop was cut off mid-output. */
|
|
535
|
+
export function isEmptyAssistantStop(message: Pick<AssistantMessage, "stopReason" | "content">): boolean {
|
|
536
|
+
switch (message.stopReason) {
|
|
537
|
+
case "stop":
|
|
538
|
+
return !message.content.some(isActionableContent);
|
|
539
|
+
case "toolUse":
|
|
540
|
+
// An orphaned toolUse stop (no tool_use block) corrupts Anthropic history:
|
|
541
|
+
// a later tool_result has nothing to anchor to. Thinking alone cannot anchor
|
|
542
|
+
// a tool_result, so it does not rescue a toolUse stop here.
|
|
543
|
+
return !message.content.some(
|
|
544
|
+
content => content?.type === "toolCall" || (content?.type === "text" && hasText(content)),
|
|
545
|
+
);
|
|
546
|
+
default:
|
|
547
|
+
return false;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* True when this assistant turn actually produced output, making its model the
|
|
553
|
+
* one that served the run.
|
|
554
|
+
*
|
|
555
|
+
* Attribution asks this from two places that MUST reach the same verdict: the
|
|
556
|
+
* live session, which flips a fallback to "served", and the offline walk
|
|
557
|
+
* replaying a transcript. `error` and `aborted` are both failures — a stalled or
|
|
558
|
+
* dropped stream is finalized as `aborted` with its partial block still
|
|
559
|
+
* attached, so a stop reason alone is not proof.
|
|
560
|
+
*
|
|
561
|
+
* Actionable content is required on top of the empty-stop rule, which only
|
|
562
|
+
* inspects `stop`/`toolUse`. A `length` stop burns the whole output budget
|
|
563
|
+
* without necessarily emitting anything usable, and every other stop reason
|
|
564
|
+
* bypasses that rule entirely.
|
|
565
|
+
*/
|
|
566
|
+
export function assistantTurnProducedOutput(message: Pick<AssistantMessage, "stopReason" | "content">): boolean {
|
|
567
|
+
if (message.stopReason === "error" || message.stopReason === "aborted") return false;
|
|
568
|
+
return !isEmptyAssistantStop(message) && message.content.some(isActionableContent);
|
|
569
|
+
}
|
|
570
|
+
|
|
501
571
|
/** Sentinel `errorMessage` the agent stamps on any abort that carried no custom
|
|
502
572
|
* reason (bare `abort()`). Renderers treat it as "no specific reason given". */
|
|
503
573
|
export const GENERIC_ABORT_SENTINEL = "Request was aborted";
|
|
@@ -1081,17 +1151,16 @@ const convertCache = new WeakMap<AgentMessage, ConvertMemoEntry>();
|
|
|
1081
1151
|
// The tail-identity guard on exact-repeat catches the streaming snapshot swap
|
|
1082
1152
|
// (partial → trailing is a fresh identity), so a settled tail is never served
|
|
1083
1153
|
// from a stale mid-stream fragment.
|
|
1154
|
+
interface ConvertArrayMemo {
|
|
1155
|
+
generation: number;
|
|
1156
|
+
length: number;
|
|
1157
|
+
output: Message[];
|
|
1158
|
+
tail: AgentMessage | undefined;
|
|
1159
|
+
prefixOutputLen: number;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1084
1162
|
let convertGeneration = 0;
|
|
1085
|
-
|
|
1086
|
-
let lastConvertLength = 0;
|
|
1087
|
-
let lastConvertOutput: Message[] | undefined;
|
|
1088
|
-
let lastConvertGeneration = -1;
|
|
1089
|
-
let lastConvertTail: AgentMessage | undefined;
|
|
1090
|
-
// Output-message count contributed by messages[0 .. lastConvertLength-1), i.e.
|
|
1091
|
-
// every message except the last. The last message is neighbor-sensitive (its LLM
|
|
1092
|
-
// view drops the trailing thinking run only while an interrupted-thinking marker
|
|
1093
|
-
// follows), so growth reconverts it rather than reusing its old fragment.
|
|
1094
|
-
let lastConvertPrefixOutputLen = 0;
|
|
1163
|
+
const convertArrayCache = new WeakMap<AgentMessage[], ConvertArrayMemo>();
|
|
1095
1164
|
|
|
1096
1165
|
registerMessageCacheInvalidator(message => {
|
|
1097
1166
|
convertCache.delete(message);
|
|
@@ -1246,15 +1315,16 @@ function convertOneCached(m: AgentMessage, interruptedNext: boolean): Message[]
|
|
|
1246
1315
|
*/
|
|
1247
1316
|
export function convertToLlm(messages: AgentMessage[]): Message[] {
|
|
1248
1317
|
const len = messages.length;
|
|
1249
|
-
const
|
|
1318
|
+
const memo = convertArrayCache.get(messages);
|
|
1319
|
+
const sameGeneration = memo !== undefined && memo.generation === convertGeneration;
|
|
1250
1320
|
const tail = len > 0 ? messages[len - 1] : undefined;
|
|
1251
1321
|
|
|
1252
1322
|
// Exact-repeat: same array, same length, same trailing identity → reuse the
|
|
1253
1323
|
// outer array. The tail-identity check rejects the streaming snapshot swap
|
|
1254
1324
|
// (partial → settled trailing keeps array identity/length but mints a fresh
|
|
1255
1325
|
// tail), so a settled tail never reads a stale mid-stream fragment.
|
|
1256
|
-
if (
|
|
1257
|
-
return
|
|
1326
|
+
if (sameGeneration && memo.length === len && tail === memo.tail) {
|
|
1327
|
+
return memo.output;
|
|
1258
1328
|
}
|
|
1259
1329
|
|
|
1260
1330
|
// Slice-on-growth: same array grew by append. Every interior message is
|
|
@@ -1267,15 +1337,14 @@ export function convertToLlm(messages: AgentMessage[]): Message[] {
|
|
|
1267
1337
|
let out: Message[];
|
|
1268
1338
|
let start: number;
|
|
1269
1339
|
if (
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
lastConvertPrefixOutputLen <= lastConvertOutput.length
|
|
1340
|
+
sameGeneration &&
|
|
1341
|
+
len > memo.length &&
|
|
1342
|
+
memo.length > 0 &&
|
|
1343
|
+
messages[memo.length - 1] === memo.tail &&
|
|
1344
|
+
memo.prefixOutputLen <= memo.output.length
|
|
1276
1345
|
) {
|
|
1277
|
-
out =
|
|
1278
|
-
start =
|
|
1346
|
+
out = memo.output.slice(0, memo.prefixOutputLen);
|
|
1347
|
+
start = memo.length - 1;
|
|
1279
1348
|
} else {
|
|
1280
1349
|
out = [];
|
|
1281
1350
|
start = 0;
|
|
@@ -1294,12 +1363,13 @@ export function convertToLlm(messages: AgentMessage[]): Message[] {
|
|
|
1294
1363
|
if (len === 0) prefixOutputLen = 0;
|
|
1295
1364
|
|
|
1296
1365
|
// Record for the next call's shortcuts. `out` is a fresh array (slice or new),
|
|
1297
|
-
// so a prior caller holding the previous
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1366
|
+
// so a prior caller holding the previous memo output never sees it grow.
|
|
1367
|
+
convertArrayCache.set(messages, {
|
|
1368
|
+
generation: convertGeneration,
|
|
1369
|
+
length: len,
|
|
1370
|
+
output: out,
|
|
1371
|
+
tail,
|
|
1372
|
+
prefixOutputLen,
|
|
1373
|
+
});
|
|
1304
1374
|
return out;
|
|
1305
1375
|
}
|
|
@@ -50,6 +50,20 @@ export interface ActiveRetryFallbackState {
|
|
|
50
50
|
originalThinkingLevel: ConfiguredThinkingLevel | undefined;
|
|
51
51
|
lastAppliedFallbackThinkingLevel: ConfiguredThinkingLevel | undefined;
|
|
52
52
|
pinned: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Set once a turn on the fallback target settles successfully. Until then the
|
|
55
|
+
* switch is only a routing decision — nothing has been produced by the new
|
|
56
|
+
* model, so no observer may report the run as having used it.
|
|
57
|
+
*/
|
|
58
|
+
served?: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Model a session's produced work is attributed to. */
|
|
62
|
+
export interface ServingModel {
|
|
63
|
+
/** Full selector including routing and thinking level. */
|
|
64
|
+
selector: string;
|
|
65
|
+
/** Whether fallback routing, rather than the configured primary, owns it. */
|
|
66
|
+
isFallback: boolean;
|
|
53
67
|
}
|
|
54
68
|
|
|
55
69
|
const RETRY_BACKOFF_MAX_DELAY_MS = 8_000;
|
|
@@ -418,8 +418,8 @@ export class SessionAdvisors {
|
|
|
418
418
|
}
|
|
419
419
|
|
|
420
420
|
/** Re-primes advisor transcript views after an in-conversation history rewrite. */
|
|
421
|
-
resetAllRuntimes(): void {
|
|
422
|
-
this.#resetAllAdvisorRuntimes();
|
|
421
|
+
resetAllRuntimes(reason?: string): void {
|
|
422
|
+
this.#resetAllAdvisorRuntimes(reason);
|
|
423
423
|
}
|
|
424
424
|
|
|
425
425
|
/** Whether live runtimes still match the resolved advisor configuration. */
|
|
@@ -535,7 +535,7 @@ export class SessionAdvisors {
|
|
|
535
535
|
for (const a of this.#advisors) {
|
|
536
536
|
a.agentUnsubscribe?.();
|
|
537
537
|
a.agentUnsubscribe = undefined;
|
|
538
|
-
a.runtime.reset();
|
|
538
|
+
a.runtime.reset("conversation-boundary");
|
|
539
539
|
a.adviseTool.resetDeliveredNotes();
|
|
540
540
|
a.emissionGuard.reset();
|
|
541
541
|
this.#attachAdvisorRecorderFeed(a);
|
|
@@ -785,14 +785,22 @@ export class SessionAdvisors {
|
|
|
785
785
|
mcpResources: this.#advisorMcpResources,
|
|
786
786
|
});
|
|
787
787
|
const baseAdvisorStreamFn = this.#advisorStreamFn ?? streamSimple;
|
|
788
|
-
const advisorStreamFn: StreamFn = (requestModel, context, options) =>
|
|
789
|
-
|
|
790
|
-
requestModel,
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
788
|
+
const advisorStreamFn: StreamFn = (requestModel, context, options) => {
|
|
789
|
+
if (requestModel.api === "openai-codex-responses") {
|
|
790
|
+
return baseAdvisorStreamFn(requestModel, context, {
|
|
791
|
+
...options,
|
|
792
|
+
codexSseMaxAttempts: ADVISOR_CODEX_SSE_MAX_ATTEMPTS,
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
if (
|
|
796
|
+
requestModel.api === "google-generative-ai" ||
|
|
797
|
+
requestModel.api === "google-gemini-cli" ||
|
|
798
|
+
requestModel.api === "google-vertex"
|
|
799
|
+
) {
|
|
800
|
+
return baseAdvisorStreamFn(requestModel, context, { ...options, acceptEmptyResponse: true });
|
|
801
|
+
}
|
|
802
|
+
return baseAdvisorStreamFn(requestModel, context, options);
|
|
803
|
+
};
|
|
796
804
|
const advisorAgent = new Agent({
|
|
797
805
|
initialState: {
|
|
798
806
|
systemPrompt,
|
|
@@ -832,8 +840,17 @@ export class SessionAdvisors {
|
|
|
832
840
|
let quarantined: string | undefined;
|
|
833
841
|
try {
|
|
834
842
|
quarantinedAdvisorOutput = undefined;
|
|
835
|
-
|
|
836
|
-
|
|
843
|
+
// Multi-message input (candidate 4) must serialize deterministically
|
|
844
|
+
// for quarantine source text; reuse the session history formatter
|
|
845
|
+
// rather than ad-hoc joins so all message kinds (text/tool/
|
|
846
|
+
// custom/structured) are preserved exactly as rendered.
|
|
847
|
+
currentAdvisorInput = Array.isArray(input)
|
|
848
|
+
? formatSessionHistoryMarkdown(input, { watchedRoles: true })
|
|
849
|
+
: input;
|
|
850
|
+
// Agent.prompt's overloads accept string OR AgentMessage[] but not
|
|
851
|
+
// the union, so narrow first; both branches intentionally identical.
|
|
852
|
+
if (Array.isArray(input)) await advisorAgent.prompt(input);
|
|
853
|
+
else await advisorAgent.prompt(input);
|
|
837
854
|
quarantined = quarantinedAdvisorOutput;
|
|
838
855
|
} finally {
|
|
839
856
|
quarantinedAdvisorOutput = undefined;
|
|
@@ -1057,8 +1074,8 @@ export class SessionAdvisors {
|
|
|
1057
1074
|
}
|
|
1058
1075
|
|
|
1059
1076
|
/** Re-prime every advisor's transcript view after an in-conversation history rewrite. */
|
|
1060
|
-
#resetAllAdvisorRuntimes(): void {
|
|
1061
|
-
for (const a of this.#advisors) a.runtime.reset();
|
|
1077
|
+
#resetAllAdvisorRuntimes(reason?: string): void {
|
|
1078
|
+
for (const a of this.#advisors) a.runtime.reset(reason);
|
|
1062
1079
|
}
|
|
1063
1080
|
|
|
1064
1081
|
#stopAdvisorRuntime(): void {
|
|
@@ -55,6 +55,14 @@ export interface HistoryFormatOptions {
|
|
|
55
55
|
*/
|
|
56
56
|
toolResultIndex?: ReadonlyMap<string, ToolResultMessage>;
|
|
57
57
|
consumedToolCallIds?: Set<string>;
|
|
58
|
+
/**
|
|
59
|
+
* Chunked rendering state: a mutable holder for the watched-role label
|
|
60
|
+
* (`**user**:` / `**agent**:`) that ended the previous chunk. Lets a caller
|
|
61
|
+
* formatting one logical transcript across several calls (advisor
|
|
62
|
+
* multi-message split) keep consecutive same-role collapsing byte-identical
|
|
63
|
+
* to the single-block render: pass one object across all chunk calls.
|
|
64
|
+
*/
|
|
65
|
+
watchedRoleState?: { lastLabel: string | undefined };
|
|
58
66
|
}
|
|
59
67
|
|
|
60
68
|
/** Max length of the primary-arg summary inside `→ tool(...)` lines. */
|
|
@@ -313,7 +321,9 @@ export function formatSessionHistoryMarkdown(messages: unknown[], opts?: History
|
|
|
313
321
|
// (the watched agent emits one assistant message per tool call, so otherwise
|
|
314
322
|
// every call repeats `**agent**:`). Cleared whenever a
|
|
315
323
|
// non-role-labeled line is emitted so the next turn re-labels.
|
|
316
|
-
|
|
324
|
+
// Chunked callers seed the previous chunk's trailing label so collapsing
|
|
325
|
+
// stays byte-identical to the single-block render.
|
|
326
|
+
let lastWatchedLabel: string | undefined = opts?.watchedRoleState?.lastLabel;
|
|
317
327
|
// Emit a watched-mode role label, collapsing consecutive same-role turns
|
|
318
328
|
// under one label (matching the user/assistant paths). Used for the
|
|
319
329
|
// user-attributed `!`/`$` execution lines so the advisor never reads them
|
|
@@ -455,5 +465,9 @@ export function formatSessionHistoryMarkdown(messages: unknown[], opts?: History
|
|
|
455
465
|
}
|
|
456
466
|
}
|
|
457
467
|
|
|
468
|
+
if (opts?.watchedRoleState) {
|
|
469
|
+
opts.watchedRoleState.lastLabel = lastWatchedLabel;
|
|
470
|
+
}
|
|
471
|
+
|
|
458
472
|
return `${lines.join("\n").trim()}\n`;
|
|
459
473
|
}
|
|
@@ -235,7 +235,7 @@ export interface SessionMaintenanceHost {
|
|
|
235
235
|
resetCodexProviderAfterCompaction(compaction: CodexCompactionContext): void;
|
|
236
236
|
resetPlanReference(): void;
|
|
237
237
|
syncTodoPhasesFromBranch(): void;
|
|
238
|
-
resetAdvisorRuntimes(): void;
|
|
238
|
+
resetAdvisorRuntimes(reason?: string): void;
|
|
239
239
|
rebaseAfterCompaction(): void;
|
|
240
240
|
recordAnchoredHistoryRewrite(tokensRemoved: number): void;
|
|
241
241
|
getContextBreakdown(options?: {
|
|
@@ -345,7 +345,7 @@ export class SessionMaintenance {
|
|
|
345
345
|
await this.#host.sessionManager.rewriteEntries();
|
|
346
346
|
const sessionContext = this.#host.buildDisplaySessionContext();
|
|
347
347
|
this.#host.agent.replaceMessages(sessionContext.messages);
|
|
348
|
-
this.#host.resetAdvisorRuntimes();
|
|
348
|
+
this.#host.resetAdvisorRuntimes("prune-tool-outputs");
|
|
349
349
|
this.#host.syncTodoPhasesFromBranch();
|
|
350
350
|
this.#host.closeCodexProviderSessionsForHistoryRewrite();
|
|
351
351
|
return result;
|
|
@@ -388,7 +388,7 @@ export class SessionMaintenance {
|
|
|
388
388
|
await this.#host.sessionManager.rewriteEntries();
|
|
389
389
|
const sessionContext = this.#host.buildDisplaySessionContext();
|
|
390
390
|
this.#host.agent.replaceMessages(sessionContext.messages);
|
|
391
|
-
this.#host.resetAdvisorRuntimes();
|
|
391
|
+
this.#host.resetAdvisorRuntimes("prune-stale-tool-results");
|
|
392
392
|
this.#host.syncTodoPhasesFromBranch();
|
|
393
393
|
this.#host.closeCodexProviderSessionsForHistoryRewrite();
|
|
394
394
|
return result;
|
|
@@ -439,7 +439,7 @@ export class SessionMaintenance {
|
|
|
439
439
|
await this.#host.sessionManager.rewriteEntries();
|
|
440
440
|
const sessionContext = this.#host.buildDisplaySessionContext();
|
|
441
441
|
this.#host.agent.replaceMessages(sessionContext.messages);
|
|
442
|
-
this.#host.resetAdvisorRuntimes();
|
|
442
|
+
this.#host.resetAdvisorRuntimes("drop-images");
|
|
443
443
|
this.#host.closeCodexProviderSessionsForHistoryRewrite();
|
|
444
444
|
return { removed };
|
|
445
445
|
}
|
|
@@ -527,7 +527,7 @@ export class SessionMaintenance {
|
|
|
527
527
|
await this.#host.sessionManager.rewriteEntries();
|
|
528
528
|
const sessionContext = this.#host.buildDisplaySessionContext();
|
|
529
529
|
this.#host.agent.replaceMessages(sessionContext.messages);
|
|
530
|
-
this.#host.resetAdvisorRuntimes();
|
|
530
|
+
this.#host.resetAdvisorRuntimes("shake");
|
|
531
531
|
this.#host.closeCodexProviderSessionsForHistoryRewrite();
|
|
532
532
|
|
|
533
533
|
return {
|
|
@@ -879,7 +879,7 @@ export class SessionMaintenance {
|
|
|
879
879
|
// plan reference. Clear the sent-flag so #buildPlanReferenceMessage re-reads
|
|
880
880
|
// the plan from disk and re-injects it on the next turn (issue #1246).
|
|
881
881
|
this.#host.resetPlanReference();
|
|
882
|
-
this.#host.resetAdvisorRuntimes();
|
|
882
|
+
this.#host.resetAdvisorRuntimes("compact");
|
|
883
883
|
this.#host.syncTodoPhasesFromBranch();
|
|
884
884
|
if (codexCompaction) {
|
|
885
885
|
this.#host.resetCodexProviderAfterCompaction(codexCompaction);
|
|
@@ -2094,7 +2094,7 @@ export class SessionMaintenance {
|
|
|
2094
2094
|
// and advisor cursors / todo phases were derived from the replaced
|
|
2095
2095
|
// history.
|
|
2096
2096
|
this.#host.resetPlanReference();
|
|
2097
|
-
this.#host.resetAdvisorRuntimes();
|
|
2097
|
+
this.#host.resetAdvisorRuntimes("compaction-rescue");
|
|
2098
2098
|
this.#host.syncTodoPhasesFromBranch();
|
|
2099
2099
|
this.#host.closeCodexProviderSessionsForHistoryRewrite();
|
|
2100
2100
|
// Extensions must see the entry that is now active, not (only) the one
|
|
@@ -2763,7 +2763,7 @@ export class SessionMaintenance {
|
|
|
2763
2763
|
// plan reference. Clear the sent-flag so #buildPlanReferenceMessage re-reads
|
|
2764
2764
|
// the plan from disk and re-injects it on the next turn (issue #1246).
|
|
2765
2765
|
this.#host.resetPlanReference();
|
|
2766
|
-
this.#host.resetAdvisorRuntimes();
|
|
2766
|
+
this.#host.resetAdvisorRuntimes("auto-compaction");
|
|
2767
2767
|
this.#host.syncTodoPhasesFromBranch();
|
|
2768
2768
|
if (codexCompaction) {
|
|
2769
2769
|
this.#host.resetCodexProviderAfterCompaction(codexCompaction);
|
|
@@ -1285,6 +1285,10 @@ export class SessionManager {
|
|
|
1285
1285
|
|
|
1286
1286
|
/** Switch to a different session file (resume / branch). */
|
|
1287
1287
|
async setSessionFile(sessionFile: string): Promise<void> {
|
|
1288
|
+
await this.#setSessionFile(sessionFile);
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
async #setSessionFile(sessionFile: string, loadedEntries?: FileEntry[]): Promise<void> {
|
|
1288
1292
|
await this.#drainAndCloseWriter();
|
|
1289
1293
|
this.#clearDiskError();
|
|
1290
1294
|
this.#draftOnlySessionCleanupArmed = false;
|
|
@@ -1294,7 +1298,7 @@ export class SessionManager {
|
|
|
1294
1298
|
this.#rememberBreadcrumb(this.#cwd, resolvedSessionFile);
|
|
1295
1299
|
|
|
1296
1300
|
const titleSlot = await readTitleSlotFromFile(resolvedSessionFile, this.#storage);
|
|
1297
|
-
const fileEntries = await loadEntriesFromFile(resolvedSessionFile, this.#storage);
|
|
1301
|
+
const fileEntries = loadedEntries ?? (await loadEntriesFromFile(resolvedSessionFile, this.#storage));
|
|
1298
1302
|
if (fileEntries.length === 0) {
|
|
1299
1303
|
// Explicit but empty/missing path (e.g. --session flag): start fresh but
|
|
1300
1304
|
// keep the requested path and materialize the header immediately.
|
|
@@ -2601,7 +2605,7 @@ export class SessionManager {
|
|
|
2601
2605
|
: path.dirname(path.resolve(filePath)));
|
|
2602
2606
|
const manager = new SessionManager(cwd, dir, true, storage);
|
|
2603
2607
|
manager.#suppressBreadcrumb = options?.suppressBreadcrumb === true;
|
|
2604
|
-
await manager
|
|
2608
|
+
await manager.#setSessionFile(filePath, loaded);
|
|
2605
2609
|
return manager;
|
|
2606
2610
|
}
|
|
2607
2611
|
|