@gajae-code/coding-agent 0.12.10 → 0.12.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -2
- package/dist/types/cli/notify-cli.d.ts +42 -1
- package/dist/types/cli/update-cli.d.ts +9 -3
- package/dist/types/commands/notify.d.ts +6 -0
- package/dist/types/config/file-lock.d.ts +3 -0
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/coordinator-mcp/question-state.d.ts +3 -1
- package/dist/types/extensibility/extensions/index.d.ts +1 -0
- package/dist/types/extensibility/extensions/ouroboros-ooo-bridge.d.ts +13 -0
- package/dist/types/extensibility/extensions/prefix-command-bridge.d.ts +0 -1
- package/dist/types/extensibility/extensions/types.d.ts +2 -0
- package/dist/types/main.d.ts +6 -2
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/utils/host-status.d.ts +23 -0
- package/dist/types/runtime-mcp/types.d.ts +2 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +12 -0
- package/dist/types/sdk/bus/chat-daemon-cli.d.ts +3 -0
- package/dist/types/sdk/bus/chat-daemon-command-channel.d.ts +244 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +13 -1
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +7 -0
- package/dist/types/sdk/bus/control-plane-frames.d.ts +43 -0
- package/dist/types/sdk/bus/conversation-store.d.ts +18 -0
- package/dist/types/sdk/bus/existing-thread-readiness.d.ts +71 -0
- package/dist/types/sdk/bus/notification-service.d.ts +10 -0
- package/dist/types/sdk/bus/slack-daemon.d.ts +27 -0
- package/dist/types/sdk/bus/slack-live-provider.d.ts +11 -0
- package/dist/types/sdk/bus/slack-provider.d.ts +14 -0
- package/dist/types/sdk/bus/slack-thread-binding.d.ts +140 -0
- package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +2 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +25 -7
- package/dist/types/sdk/bus/telegram-daemon.d.ts +104 -16
- package/dist/types/sdk/bus/telegram-reference.d.ts +10 -1
- package/dist/types/sdk/bus/topic-registry.d.ts +130 -85
- package/dist/types/sdk/host/host.d.ts +52 -0
- package/dist/types/sdk/lifecycle-session.d.ts +6 -0
- package/dist/types/sdk/session-activation.d.ts +41 -0
- package/dist/types/sdk/session.d.ts +18 -0
- package/dist/types/sdk/startup-capability.d.ts +8 -1
- package/dist/types/session/agent-session.d.ts +7 -0
- package/dist/types/session/artifacts.d.ts +2 -2
- package/dist/types/session/internal/managed-session-storage.d.ts +12 -2
- package/dist/types/session/session-manager.d.ts +40 -5
- package/dist/types/session/session-storage.d.ts +1 -0
- package/dist/types/tools/computer.d.ts +35 -2
- package/dist/types/tools/index.d.ts +8 -0
- package/dist/types/tools/todo-write.d.ts +2 -0
- package/dist/types/utils/npm-registry.d.ts +71 -0
- package/examples/extensions/README.md +38 -4
- package/examples/extensions/ooo-bridge.ts +15 -0
- package/package.json +8 -8
- package/scripts/generate-sdk-operation-inventory.ts +4 -0
- package/src/async/job-manager.ts +9 -2
- package/src/cli/notify-cli.ts +226 -2
- package/src/cli/update-cli.ts +90 -23
- package/src/commands/notify.ts +27 -3
- package/src/commands/sdk.ts +1 -0
- package/src/config/file-lock-gc.ts +18 -0
- package/src/config/file-lock.ts +61 -16
- package/src/config/model-profiles.ts +31 -15
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/question-state.ts +2 -0
- package/src/coordinator-mcp/server.ts +358 -7
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +2 -0
- package/src/extensibility/extensions/index.ts +1 -0
- package/src/extensibility/extensions/ouroboros-ooo-bridge.ts +235 -0
- package/src/extensibility/extensions/prefix-command-bridge.ts +4 -9
- package/src/extensibility/extensions/runner.ts +15 -2
- package/src/extensibility/extensions/types.ts +2 -0
- package/src/gjc-runtime/launch-tmux.ts +53 -1
- package/src/internal-urls/docs-index.generated.ts +13 -12
- package/src/main.ts +203 -67
- package/src/modes/DESIGN.md +8 -5
- package/src/modes/components/footer.ts +10 -18
- package/src/modes/components/session-selector.ts +26 -3
- package/src/modes/components/status-line/gh.ts +23 -2
- package/src/modes/controllers/event-controller.ts +3 -0
- package/src/modes/controllers/extension-ui-controller.ts +2 -0
- package/src/modes/controllers/input-controller.ts +11 -0
- package/src/modes/controllers/selector-controller.ts +8 -2
- package/src/modes/interactive-mode.ts +7 -1
- package/src/modes/utils/host-status.ts +38 -0
- package/src/prompts/tools/computer.md +6 -1
- package/src/runtime-mcp/types.ts +2 -0
- package/src/sdk/broker/lifecycle.ts +60 -3
- package/src/sdk/bus/chat-daemon-cli.ts +42 -0
- package/src/sdk/bus/chat-daemon-command-channel.ts +921 -0
- package/src/sdk/bus/chat-daemon-control.ts +18 -3
- package/src/sdk/bus/chat-daemon-runtime.ts +257 -21
- package/src/sdk/bus/control-plane-frames.ts +51 -0
- package/src/sdk/bus/conversation-store.ts +29 -1
- package/src/sdk/bus/existing-thread-readiness.ts +155 -0
- package/src/sdk/bus/index.ts +70 -0
- package/src/sdk/bus/notification-service.ts +28 -1
- package/src/sdk/bus/rich-render.ts +3 -2
- package/src/sdk/bus/slack-daemon.ts +99 -4
- package/src/sdk/bus/slack-live-provider.ts +32 -2
- package/src/sdk/bus/slack-provider.ts +11 -0
- package/src/sdk/bus/slack-thread-binding.ts +641 -0
- package/src/sdk/bus/telegram-daemon-cli.ts +11 -0
- package/src/sdk/bus/telegram-daemon-contract.ts +25 -7
- package/src/sdk/bus/telegram-daemon.ts +1961 -497
- package/src/sdk/bus/telegram-reference.ts +42 -6
- package/src/sdk/bus/topic-registry.ts +674 -181
- package/src/sdk/host/host.ts +180 -1
- package/src/sdk/lifecycle-session.ts +8 -2
- package/src/sdk/protocol/operation-inventory.generated.json +22 -0
- package/src/sdk/session-activation.ts +127 -0
- package/src/sdk/session.ts +139 -28
- package/src/sdk/startup-capability.ts +10 -1
- package/src/session/agent-session.ts +331 -30
- package/src/session/artifacts.ts +56 -13
- package/src/session/internal/managed-session-storage.ts +440 -243
- package/src/session/session-manager.ts +288 -44
- package/src/session/session-storage.ts +3 -0
- package/src/setup/provider-presets.json +1 -1
- package/src/task/index.ts +92 -44
- package/src/tools/computer.ts +322 -45
- package/src/tools/index.ts +8 -0
- package/src/tools/todo-write.ts +29 -11
- package/src/utils/npm-registry.ts +660 -0
|
@@ -184,7 +184,7 @@ import {
|
|
|
184
184
|
} from "@gajae-code/utils";
|
|
185
185
|
|
|
186
186
|
import { createAppendOnlyContextManager, resolveAppendOnlyMode } from "../append-only-mode";
|
|
187
|
-
import { type AsyncJob, type AsyncJobDeliveryState, AsyncJobManager } from "../async";
|
|
187
|
+
import { type AsyncJob, type AsyncJobDeliveryState, AsyncJobManager, type OwnerSubagentShutdownLease } from "../async";
|
|
188
188
|
import { reset as resetCapabilities } from "../capability";
|
|
189
189
|
import type { Rule } from "../capability/rule";
|
|
190
190
|
import type { CasReceipt } from "../config/atomic-yaml-patch";
|
|
@@ -427,6 +427,7 @@ import {
|
|
|
427
427
|
getLatestCompactionEntry,
|
|
428
428
|
getSessionMessageEntryId,
|
|
429
429
|
getSessionMessageObservationId,
|
|
430
|
+
SessionAppendPersistenceError,
|
|
430
431
|
transferSessionMessageIdentity,
|
|
431
432
|
} from "./session-manager";
|
|
432
433
|
import { getEntriesForInternalRead, getSessionContextForInternalRead } from "./session-manager-internal";
|
|
@@ -609,6 +610,8 @@ export interface AgentSessionConfig {
|
|
|
609
610
|
discoveryMode?: "off" | "mcp-only" | "all";
|
|
610
611
|
/** MCP tool names to activate for the current session when discovery mode is enabled. */
|
|
611
612
|
initialSelectedMCPToolNames?: string[];
|
|
613
|
+
/** Keep persisted MCP names until a deferred exact catalog becomes available. */
|
|
614
|
+
preserveUnavailableInitialMCPToolSelection?: boolean;
|
|
612
615
|
/** Built-in discoverable tool names restored for the current all-discovery session. */
|
|
613
616
|
initialSelectedDiscoveredBuiltinToolNames?: string[];
|
|
614
617
|
/** Discoverable built-ins active for configured or explicit reasons independently of persisted discovery selection. */
|
|
@@ -655,6 +658,8 @@ export interface AgentSessionConfig {
|
|
|
655
658
|
* process-global manager **MUST NOT** dispose it on their own teardown.
|
|
656
659
|
*/
|
|
657
660
|
ownedMcpManager?: MCPManager;
|
|
661
|
+
/** Optional startup dependency that must settle before the first provider turn. */
|
|
662
|
+
startupTurnBarrier?: Promise<void>;
|
|
658
663
|
/** Optional fork-context seed used to initialize a child session before its first prompt. */
|
|
659
664
|
forkContextSeed?: ForkContextSeed;
|
|
660
665
|
/** Optional provider state override. Fork-context children should omit this by default. */
|
|
@@ -1946,6 +1951,7 @@ export class AgentSession {
|
|
|
1946
1951
|
*/
|
|
1947
1952
|
readonly #ownedAsyncJobManager: AsyncJobManager | undefined;
|
|
1948
1953
|
readonly #ownedMcpManager: MCPManager | undefined;
|
|
1954
|
+
#startupTurnBarrier: Promise<void> | undefined;
|
|
1949
1955
|
#pendingPythonMessages: PythonExecutionMessage[] = [];
|
|
1950
1956
|
#activeEvalExecutions = new Set<Promise<unknown>>();
|
|
1951
1957
|
#evalExecutionDisposing = false;
|
|
@@ -1965,6 +1971,8 @@ export class AgentSession {
|
|
|
1965
1971
|
#isDisposed = false;
|
|
1966
1972
|
#disposePromise: Promise<void> | undefined;
|
|
1967
1973
|
readonly #toolSessionCleanups = new Set<() => Promise<void> | void>();
|
|
1974
|
+
readonly #toolSessionTransitionCleanups = new Set<() => Promise<void> | void>();
|
|
1975
|
+
readonly #deferredOwnerShutdownFinalizations = new Set<Promise<void>>();
|
|
1968
1976
|
#newSessionTransition: Promise<boolean> | undefined;
|
|
1969
1977
|
// Extension system
|
|
1970
1978
|
#extensionRunner: ExtensionRunner | undefined = undefined;
|
|
@@ -2268,10 +2276,23 @@ export class AgentSession {
|
|
|
2268
2276
|
next.ready.resolve();
|
|
2269
2277
|
}
|
|
2270
2278
|
|
|
2279
|
+
async #awaitStartupTurnBarrier(): Promise<void> {
|
|
2280
|
+
const barrier = this.#startupTurnBarrier;
|
|
2281
|
+
if (!barrier) return;
|
|
2282
|
+
await barrier;
|
|
2283
|
+
if (this.#startupTurnBarrier === barrier) this.#startupTurnBarrier = undefined;
|
|
2284
|
+
}
|
|
2285
|
+
extendStartupTurnBarrier(barrier: Promise<void>): void {
|
|
2286
|
+
const current = this.#startupTurnBarrier;
|
|
2287
|
+
this.#startupTurnBarrier = current ? Promise.all([current, barrier]).then(() => {}) : barrier;
|
|
2288
|
+
void this.#startupTurnBarrier.catch(() => {});
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2271
2291
|
async #withSessionAdmission<T>(
|
|
2272
2292
|
kind: SessionAdmissionKind,
|
|
2273
2293
|
body: (lease: SessionAdmissionLease) => Promise<T>,
|
|
2274
2294
|
): Promise<T> {
|
|
2295
|
+
if (kind === "prompt") await this.#awaitStartupTurnBarrier();
|
|
2275
2296
|
const owner = this.#sessionAdmissionContext.getStore();
|
|
2276
2297
|
if (owner && !owner.released) throw this.#sessionAdmissionBusyError();
|
|
2277
2298
|
if (this.#sessionAdmissionClosed || this.#isDisposed) throw this.#sessionAdmissionBusyError();
|
|
@@ -2600,6 +2621,7 @@ export class AgentSession {
|
|
|
2600
2621
|
this.#ownedAsyncJobManager = config.ownedAsyncJobManager;
|
|
2601
2622
|
this.#retainedMemorySampler = config.retainedMemorySampler;
|
|
2602
2623
|
this.#ownedMcpManager = config.ownedMcpManager;
|
|
2624
|
+
this.#startupTurnBarrier = config.startupTurnBarrier;
|
|
2603
2625
|
this.#scopedModels = config.scopedModels ?? [];
|
|
2604
2626
|
this.#thinkingLevel = config.thinkingLevel;
|
|
2605
2627
|
this.#promptTemplates = config.promptTemplates ?? [];
|
|
@@ -2660,6 +2682,8 @@ export class AgentSession {
|
|
|
2660
2682
|
injectIdle: async messages => {
|
|
2661
2683
|
const first = messages[0];
|
|
2662
2684
|
if (!first) return;
|
|
2685
|
+
await this.#awaitStartupTurnBarrier();
|
|
2686
|
+
if (this.#isDisposed) return;
|
|
2663
2687
|
if (messages.length === 1) {
|
|
2664
2688
|
await this.agent.prompt(first, this.#managedFallbackPromptOptions());
|
|
2665
2689
|
} else {
|
|
@@ -2722,7 +2746,7 @@ export class AgentSession {
|
|
|
2722
2746
|
config.initialDiscoveredBuiltinToolSelectionIsExplicit === true
|
|
2723
2747
|
? this.#selectRestorableDiscoveredBuiltinToolNames(config.initialPersistedDiscoveredBuiltinToolNames ?? [])
|
|
2724
2748
|
: undefined;
|
|
2725
|
-
this.#pruneSelectedMCPToolNames();
|
|
2749
|
+
if (!config.preserveUnavailableInitialMCPToolSelection) this.#pruneSelectedMCPToolNames();
|
|
2726
2750
|
const persistInitialMCPToolSelection =
|
|
2727
2751
|
config.persistInitialMCPToolSelection === true && config.initialMCPToolSelectionIsExplicit !== false;
|
|
2728
2752
|
const persistInitialDiscoveredBuiltinToolSelection =
|
|
@@ -3376,6 +3400,9 @@ export class AgentSession {
|
|
|
3376
3400
|
getAgentId(): string | undefined {
|
|
3377
3401
|
return this.#agentId;
|
|
3378
3402
|
}
|
|
3403
|
+
get isDisposed(): boolean {
|
|
3404
|
+
return this.#isDisposed;
|
|
3405
|
+
}
|
|
3379
3406
|
|
|
3380
3407
|
registerToolSessionCleanup(cleanup: () => Promise<void> | void): () => void {
|
|
3381
3408
|
if (this.#isDisposed) throw new Error("Cannot register tool cleanup after session disposal has started.");
|
|
@@ -3383,6 +3410,23 @@ export class AgentSession {
|
|
|
3383
3410
|
return () => this.#toolSessionCleanups.delete(cleanup);
|
|
3384
3411
|
}
|
|
3385
3412
|
|
|
3413
|
+
registerToolSessionTransitionCleanup(cleanup: () => Promise<void> | void): () => void {
|
|
3414
|
+
if (this.#isDisposed)
|
|
3415
|
+
throw new Error("Cannot register tool transition cleanup after session disposal has started.");
|
|
3416
|
+
this.#toolSessionTransitionCleanups.add(cleanup);
|
|
3417
|
+
return () => this.#toolSessionTransitionCleanups.delete(cleanup);
|
|
3418
|
+
}
|
|
3419
|
+
|
|
3420
|
+
async #runToolSessionTransitionCleanups(): Promise<void> {
|
|
3421
|
+
const cleanups = Array.from(this.#toolSessionTransitionCleanups);
|
|
3422
|
+
this.#toolSessionTransitionCleanups.clear();
|
|
3423
|
+
const results = await Promise.allSettled(cleanups.map(async cleanup => await cleanup()));
|
|
3424
|
+
for (const result of results) {
|
|
3425
|
+
if (result.status === "rejected")
|
|
3426
|
+
logger.warn("Tool session transition cleanup failed", { error: String(result.reason) });
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3429
|
+
|
|
3386
3430
|
async #runToolSessionCleanups(): Promise<void> {
|
|
3387
3431
|
const cleanups = Array.from(this.#toolSessionCleanups);
|
|
3388
3432
|
this.#toolSessionCleanups.clear();
|
|
@@ -3435,6 +3479,61 @@ export class AgentSession {
|
|
|
3435
3479
|
manager.cancelAll({ ownerId: this.#agentId });
|
|
3436
3480
|
}
|
|
3437
3481
|
|
|
3482
|
+
async #settleOwnAsyncJobsBeforeArtifactRetirement(): Promise<void> {
|
|
3483
|
+
const ownerId = this.#agentId;
|
|
3484
|
+
const manager = AsyncJobManager.instance();
|
|
3485
|
+
if (!ownerId || !manager) return;
|
|
3486
|
+
const lease = manager.beginOwnerSubagentShutdown(ownerId);
|
|
3487
|
+
if (!lease) throw new Error("Owned async cleanup is already in progress before artifact retirement.");
|
|
3488
|
+
let committed = false;
|
|
3489
|
+
try {
|
|
3490
|
+
manager.runOwnerProducerCleanupsStrict({ ownerId });
|
|
3491
|
+
const proof = await manager.cancelAndProveOwnerSubagents(lease);
|
|
3492
|
+
if (!proof.confirmed)
|
|
3493
|
+
throw new Error("Owned subagent cleanup could not be confirmed before artifact retirement.");
|
|
3494
|
+
if (!(await manager.waitForOwnerInFlightDeliveries(ownerId)))
|
|
3495
|
+
throw new Error("Owned async deliveries did not settle before artifact retirement.");
|
|
3496
|
+
if (!(await manager.cancelAndSettleOwnerJobs(ownerId)))
|
|
3497
|
+
throw new Error("Owned async jobs did not settle before artifact retirement.");
|
|
3498
|
+
committed = true;
|
|
3499
|
+
} finally {
|
|
3500
|
+
manager.finishOwnerSubagentShutdown(lease, committed ? "commit" : "release");
|
|
3501
|
+
}
|
|
3502
|
+
}
|
|
3503
|
+
|
|
3504
|
+
#scheduleDeferredOwnerShutdownFinalization(
|
|
3505
|
+
manager: AsyncJobManager,
|
|
3506
|
+
lease: OwnerSubagentShutdownLease,
|
|
3507
|
+
ownerId: string,
|
|
3508
|
+
): void {
|
|
3509
|
+
const finalization = (async () => {
|
|
3510
|
+
while (!this.#isDisposed) {
|
|
3511
|
+
try {
|
|
3512
|
+
manager.runOwnerProducerCleanupsStrict({ ownerId });
|
|
3513
|
+
const proof = await manager.cancelAndProveOwnerSubagents(lease);
|
|
3514
|
+
if (!proof.confirmed) throw new Error(`owner_subagent_${proof.reason}`);
|
|
3515
|
+
if (!(await manager.waitForOwnerInFlightDeliveries(ownerId)))
|
|
3516
|
+
throw new Error("owner_delivery_settlement_timeout");
|
|
3517
|
+
if (!(await manager.cancelAndSettleOwnerJobs(ownerId))) throw new Error("owner_job_settlement_timeout");
|
|
3518
|
+
if (this.#isDisposed) break;
|
|
3519
|
+
this.sessionManager.retireEphemeralArtifactsAfterTransition();
|
|
3520
|
+
await this.#runToolSessionTransitionCleanups();
|
|
3521
|
+
manager.finishOwnerSubagentShutdown(lease, "commit");
|
|
3522
|
+
return;
|
|
3523
|
+
} catch (error) {
|
|
3524
|
+
logger.warn("Deferred owner shutdown finalization retry failed", {
|
|
3525
|
+
ownerId,
|
|
3526
|
+
error: error instanceof Error ? error.message : String(error),
|
|
3527
|
+
});
|
|
3528
|
+
await Bun.sleep(25);
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
manager.finishOwnerSubagentShutdown(lease, "release");
|
|
3532
|
+
})();
|
|
3533
|
+
this.#deferredOwnerShutdownFinalizations.add(finalization);
|
|
3534
|
+
void finalization.finally(() => this.#deferredOwnerShutdownFinalizations.delete(finalization));
|
|
3535
|
+
}
|
|
3536
|
+
|
|
3438
3537
|
#suppressOwnAsyncJobDeliveries(): void {
|
|
3439
3538
|
if (!this.#agentId) return;
|
|
3440
3539
|
const manager = AsyncJobManager.instance();
|
|
@@ -3899,14 +3998,19 @@ export class AgentSession {
|
|
|
3899
3998
|
) {
|
|
3900
3999
|
const isRosterReminder = event.message.role === "custom" && event.message.customType === "irc-peer-roster";
|
|
3901
4000
|
if (!isRosterReminder) {
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
4001
|
+
try {
|
|
4002
|
+
this.#appendCustomMessageEntry(
|
|
4003
|
+
event.message.customType,
|
|
4004
|
+
event.message.content,
|
|
4005
|
+
event.message.display,
|
|
4006
|
+
event.message.details,
|
|
4007
|
+
event.message.attribution ?? "agent",
|
|
4008
|
+
getSessionMessageObservationId(event.message),
|
|
4009
|
+
);
|
|
4010
|
+
} catch (error) {
|
|
4011
|
+
this.agent.abort();
|
|
4012
|
+
throw error;
|
|
4013
|
+
}
|
|
3910
4014
|
}
|
|
3911
4015
|
} else if (
|
|
3912
4016
|
event.message.role === "user" ||
|
|
@@ -3915,7 +4019,73 @@ export class AgentSession {
|
|
|
3915
4019
|
event.message.role === "toolResult" ||
|
|
3916
4020
|
event.message.role === "fileMention"
|
|
3917
4021
|
) {
|
|
3918
|
-
|
|
4022
|
+
try {
|
|
4023
|
+
this.sessionManager.appendMessage(event.message);
|
|
4024
|
+
} catch (error) {
|
|
4025
|
+
if (
|
|
4026
|
+
event.message.role !== "toolResult" ||
|
|
4027
|
+
event.message.toolName !== "todo_write" ||
|
|
4028
|
+
!(error instanceof SessionAppendPersistenceError) ||
|
|
4029
|
+
error.phase !== "current_append"
|
|
4030
|
+
) {
|
|
4031
|
+
this.agent.abort();
|
|
4032
|
+
throw error;
|
|
4033
|
+
}
|
|
4034
|
+
this.agent.abort();
|
|
4035
|
+
|
|
4036
|
+
const failure = error.persistenceError.message;
|
|
4037
|
+
const failedEntryId = error.entryId;
|
|
4038
|
+
this.#syncTodoPhasesFromBranch();
|
|
4039
|
+
event.message.isError = true;
|
|
4040
|
+
event.message.content = [
|
|
4041
|
+
{
|
|
4042
|
+
type: "text",
|
|
4043
|
+
text: `Todo state persistence failed: ${failure}\nDo not change the payload solely because of this failure. The durable outcome is unknown; reconcile the session state before retrying or continuing.`,
|
|
4044
|
+
},
|
|
4045
|
+
];
|
|
4046
|
+
event.message.details = {
|
|
4047
|
+
...(event.message.details && typeof event.message.details === "object" ? event.message.details : {}),
|
|
4048
|
+
phases: this.getTodoPhases(),
|
|
4049
|
+
failureKind: "persistence",
|
|
4050
|
+
};
|
|
4051
|
+
this.agent.touchContext();
|
|
4052
|
+
let recovered = false;
|
|
4053
|
+
try {
|
|
4054
|
+
await this.sessionManager.recoverPersistenceFailure();
|
|
4055
|
+
recovered = true;
|
|
4056
|
+
} catch (recoveryError) {
|
|
4057
|
+
logger.warn("Todo persistence recovery failed", {
|
|
4058
|
+
error: recoveryError instanceof Error ? recoveryError.message : String(recoveryError),
|
|
4059
|
+
});
|
|
4060
|
+
}
|
|
4061
|
+
if (recovered) {
|
|
4062
|
+
const durableTodoResult = this.sessionManager
|
|
4063
|
+
.getBranch()
|
|
4064
|
+
.find(
|
|
4065
|
+
entry =>
|
|
4066
|
+
entry.id === failedEntryId &&
|
|
4067
|
+
entry.type === "message" &&
|
|
4068
|
+
entry.message.role === "toolResult" &&
|
|
4069
|
+
entry.message.toolName === "todo_write",
|
|
4070
|
+
);
|
|
4071
|
+
this.#syncTodoPhasesFromBranch();
|
|
4072
|
+
if (durableTodoResult?.type === "message" && durableTodoResult.message.role === "toolResult") {
|
|
4073
|
+
event.message.content = durableTodoResult.message.content;
|
|
4074
|
+
event.message.details = durableTodoResult.message.details;
|
|
4075
|
+
event.message.isError = durableTodoResult.message.isError;
|
|
4076
|
+
} else {
|
|
4077
|
+
event.message.details = {
|
|
4078
|
+
...(event.message.details && typeof event.message.details === "object"
|
|
4079
|
+
? event.message.details
|
|
4080
|
+
: {}),
|
|
4081
|
+
phases: this.getTodoPhases(),
|
|
4082
|
+
failureKind: "persistence",
|
|
4083
|
+
};
|
|
4084
|
+
this.sessionManager.appendMessage(event.message);
|
|
4085
|
+
this.agent.touchContext();
|
|
4086
|
+
}
|
|
4087
|
+
}
|
|
4088
|
+
}
|
|
3919
4089
|
}
|
|
3920
4090
|
}
|
|
3921
4091
|
|
|
@@ -4189,7 +4359,13 @@ export class AgentSession {
|
|
|
4189
4359
|
if (event.message.role === "toolResult") {
|
|
4190
4360
|
const { toolName, details, isError, content } = event.message as {
|
|
4191
4361
|
toolName?: string;
|
|
4192
|
-
details?: {
|
|
4362
|
+
details?: {
|
|
4363
|
+
path?: string;
|
|
4364
|
+
phases?: TodoPhase[];
|
|
4365
|
+
report?: string;
|
|
4366
|
+
startedAt?: string;
|
|
4367
|
+
failureKind?: "payload_rejected" | "argument_validation" | "execution" | "persistence";
|
|
4368
|
+
};
|
|
4193
4369
|
isError?: boolean;
|
|
4194
4370
|
content?: Array<TextContent | ImageContent>;
|
|
4195
4371
|
};
|
|
@@ -4202,22 +4378,34 @@ export class AgentSession {
|
|
|
4202
4378
|
}
|
|
4203
4379
|
if (toolName === "todo_write" && isError) {
|
|
4204
4380
|
const errorText = content?.find(part => part.type === "text")?.text;
|
|
4381
|
+
const payloadRejected =
|
|
4382
|
+
details?.failureKind === "payload_rejected" || details?.failureKind === "argument_validation";
|
|
4205
4383
|
const reminderText = [
|
|
4206
4384
|
"<system-reminder>",
|
|
4207
|
-
|
|
4385
|
+
payloadRejected
|
|
4386
|
+
? "todo_write rejected its payload. The requested todo update was not applied."
|
|
4387
|
+
: "todo_write did not complete because the turn aborted during runtime execution or persistence.",
|
|
4208
4388
|
errorText ? `Failure: ${errorText}` : "Failure: todo_write returned an error.",
|
|
4209
|
-
|
|
4389
|
+
payloadRejected
|
|
4390
|
+
? "Correct the payload and call todo_write again before continuing."
|
|
4391
|
+
: "Do not change the payload solely because of this failure. The durable outcome is unknown; reconcile the session state before retrying or continuing.",
|
|
4210
4392
|
"</system-reminder>",
|
|
4211
4393
|
].join("\n");
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4394
|
+
try {
|
|
4395
|
+
await this.sendCustomMessage(
|
|
4396
|
+
{
|
|
4397
|
+
customType: "todo-write-error-reminder",
|
|
4398
|
+
content: reminderText,
|
|
4399
|
+
display: false,
|
|
4400
|
+
details: { toolName, errorText },
|
|
4401
|
+
},
|
|
4402
|
+
{ deliverAs: "nextTurn" },
|
|
4403
|
+
);
|
|
4404
|
+
} catch (error) {
|
|
4405
|
+
logger.warn("Todo write error reminder persistence failed", {
|
|
4406
|
+
error: error instanceof Error ? error.message : String(error),
|
|
4407
|
+
});
|
|
4408
|
+
}
|
|
4221
4409
|
}
|
|
4222
4410
|
if (toolName === "checkpoint" && !isError) {
|
|
4223
4411
|
const checkpointEntryId = this.sessionManager.getLeafId();
|
|
@@ -4557,6 +4745,11 @@ export class AgentSession {
|
|
|
4557
4745
|
settleLease();
|
|
4558
4746
|
return;
|
|
4559
4747
|
}
|
|
4748
|
+
await this.#awaitStartupTurnBarrier();
|
|
4749
|
+
if (!canContinue()) {
|
|
4750
|
+
settleLease();
|
|
4751
|
+
return;
|
|
4752
|
+
}
|
|
4560
4753
|
try {
|
|
4561
4754
|
if (!options?.skipCompactionCheck) {
|
|
4562
4755
|
await this.#checkEstimatedContextBeforePrompt();
|
|
@@ -5906,6 +6099,7 @@ export class AgentSession {
|
|
|
5906
6099
|
// the session that owns the manager goes on to dispose it (which itself
|
|
5907
6100
|
// nukes any leftover jobs and pending deliveries).
|
|
5908
6101
|
this.#cancelOwnAsyncJobs();
|
|
6102
|
+
await Promise.allSettled(this.#deferredOwnerShutdownFinalizations);
|
|
5909
6103
|
const ownedAsyncManager = this.#ownedAsyncJobManager;
|
|
5910
6104
|
if (ownedAsyncManager) {
|
|
5911
6105
|
const drained = await ownedAsyncManager.dispose({ timeoutMs: 3_000 });
|
|
@@ -5917,6 +6111,7 @@ export class AgentSession {
|
|
|
5917
6111
|
AsyncJobManager.setInstance(undefined);
|
|
5918
6112
|
}
|
|
5919
6113
|
}
|
|
6114
|
+
await this.#runToolSessionTransitionCleanups();
|
|
5920
6115
|
await this.#runToolSessionCleanups();
|
|
5921
6116
|
// Only disconnect the MCP manager THIS session owns (top-level sessions that
|
|
5922
6117
|
// connected plugin-bundle MCP servers). Subagents and callers that merely
|
|
@@ -6744,6 +6939,12 @@ export class AgentSession {
|
|
|
6744
6939
|
const built = await this.#runAdmittedBaseSystemPromptRebuild(() =>
|
|
6745
6940
|
this.#rebuildSystemPrompt!(validToolNames, this.#toolRegistry),
|
|
6746
6941
|
);
|
|
6942
|
+
if (this.#isDisposed) {
|
|
6943
|
+
if (generation === this.#baseSystemPromptGeneration) {
|
|
6944
|
+
this.#pendingAppliedToolSignature = undefined;
|
|
6945
|
+
}
|
|
6946
|
+
return;
|
|
6947
|
+
}
|
|
6747
6948
|
if (generation === this.#baseSystemPromptGeneration) {
|
|
6748
6949
|
this.#baseSystemPrompt = built.systemPrompt;
|
|
6749
6950
|
this.agent.setSystemPrompt(this.#baseSystemPrompt);
|
|
@@ -7229,6 +7430,8 @@ export class AgentSession {
|
|
|
7229
7430
|
if (!canContinuePersistedHistory(this.agent.state.messages)) {
|
|
7230
7431
|
throw new Error("Cannot continue from persisted message history");
|
|
7231
7432
|
}
|
|
7433
|
+
await this.#awaitStartupTurnBarrier();
|
|
7434
|
+
if (this.#sessionAdmissionClosed || this.#isDisposed) throw this.#sessionAdmissionBusyError();
|
|
7232
7435
|
this.#beginInFlight();
|
|
7233
7436
|
let hindsightRecall: string | undefined;
|
|
7234
7437
|
try {
|
|
@@ -10093,6 +10296,7 @@ export class AgentSession {
|
|
|
10093
10296
|
// Last fallible gate while public getters still show the predecessor (#3138).
|
|
10094
10297
|
await initializeLocalRoot(this.#localProtocolOptions(prepared));
|
|
10095
10298
|
this.sessionManager.commitPreparedNewSession(prepared);
|
|
10299
|
+
await this.#runToolSessionTransitionCleanups();
|
|
10096
10300
|
} catch (error) {
|
|
10097
10301
|
throw await discardPreparedNewSessionAfterFailure(this.sessionManager, prepared, error);
|
|
10098
10302
|
}
|
|
@@ -10162,6 +10366,7 @@ export class AgentSession {
|
|
|
10162
10366
|
// Last fallible gate while public getters still show the predecessor (#3138).
|
|
10163
10367
|
await initializeLocalRoot(this.#localProtocolOptions(prepared));
|
|
10164
10368
|
this.sessionManager.commitPreparedNewSession(prepared);
|
|
10369
|
+
await this.#runToolSessionTransitionCleanups();
|
|
10165
10370
|
} catch (error) {
|
|
10166
10371
|
throw await discardPreparedNewSessionAfterFailure(this.sessionManager, prepared, error);
|
|
10167
10372
|
}
|
|
@@ -10328,7 +10533,9 @@ export class AgentSession {
|
|
|
10328
10533
|
}
|
|
10329
10534
|
try {
|
|
10330
10535
|
await initializeLocalRoot(this.#localProtocolOptions(prepared));
|
|
10536
|
+
await this.#settleOwnAsyncJobsBeforeArtifactRetirement();
|
|
10331
10537
|
this.sessionManager.commitPreparedNewSession(prepared);
|
|
10538
|
+
await this.#runToolSessionTransitionCleanups();
|
|
10332
10539
|
} catch (error) {
|
|
10333
10540
|
throw await discardPreparedNewSessionAfterFailure(this.sessionManager, prepared, error);
|
|
10334
10541
|
}
|
|
@@ -11445,6 +11652,7 @@ export class AgentSession {
|
|
|
11445
11652
|
const artifactManager = this.sessionManager.getArtifactManager();
|
|
11446
11653
|
const prunedArtifacts: Array<{ entryId: string; id: string; toolType: string; originalText: string }> = [];
|
|
11447
11654
|
let reservedArtifactId: string | undefined;
|
|
11655
|
+
let artifactAllocationAvailable = artifactManager !== null;
|
|
11448
11656
|
if (artifactManager) {
|
|
11449
11657
|
try {
|
|
11450
11658
|
reservedArtifactId = (await artifactManager.allocatePath("tool-output")).id;
|
|
@@ -11452,14 +11660,24 @@ export class AgentSession {
|
|
|
11452
11660
|
logger.warn("Failed to reserve artifact ID for pruned tool output", {
|
|
11453
11661
|
error: error instanceof Error ? error.message : String(error),
|
|
11454
11662
|
});
|
|
11663
|
+
artifactAllocationAvailable = false;
|
|
11455
11664
|
}
|
|
11456
11665
|
}
|
|
11457
11666
|
const result = pruneToolOutputs(branchEntries, DEFAULT_PRUNE_CONFIG, {
|
|
11458
11667
|
relaxedMinimum: overThreshold ? 0 : undefined,
|
|
11459
11668
|
artifactRefMaxChars: PRUNED_ARTIFACT_REF_MAX_CHARS,
|
|
11460
11669
|
artifactRef: candidate => {
|
|
11461
|
-
if (!artifactManager) return undefined;
|
|
11462
|
-
|
|
11670
|
+
if (!artifactManager || !artifactAllocationAvailable) return undefined;
|
|
11671
|
+
let id: string;
|
|
11672
|
+
try {
|
|
11673
|
+
id = reservedArtifactId ?? String(artifactManager.allocateId());
|
|
11674
|
+
} catch (error) {
|
|
11675
|
+
artifactAllocationAvailable = false;
|
|
11676
|
+
logger.warn("Failed to allocate artifact ID for pruned tool output", {
|
|
11677
|
+
error: error instanceof Error ? error.message : String(error),
|
|
11678
|
+
});
|
|
11679
|
+
return undefined;
|
|
11680
|
+
}
|
|
11463
11681
|
reservedArtifactId = undefined;
|
|
11464
11682
|
const toolType = (candidate.toolName ?? "tool-output").replace(/[^a-zA-Z0-9_-]/g, "_") || "tool-output";
|
|
11465
11683
|
prunedArtifacts.push({ entryId: candidate.entryId, id, toolType, originalText: candidate.originalText });
|
|
@@ -12071,10 +12289,12 @@ export class AgentSession {
|
|
|
12071
12289
|
const successorGateEmitter = this.#constructWorkflowGateEmitter(prepared.sessionId);
|
|
12072
12290
|
// Last fallible action: verified local:// readiness from immutable staged options.
|
|
12073
12291
|
await initializeLocalRoot(this.#localProtocolOptions(prepared));
|
|
12292
|
+
await this.#settleOwnAsyncJobsBeforeArtifactRetirement();
|
|
12074
12293
|
|
|
12075
12294
|
// --- Commit boundary: synchronous adoption is the sole identity publication.
|
|
12076
12295
|
this.sessionManager.commitPreparedNewSession(prepared);
|
|
12077
12296
|
committed = true;
|
|
12297
|
+
await this.#runToolSessionTransitionCleanups();
|
|
12078
12298
|
this.agent.reset();
|
|
12079
12299
|
this.#syncAgentSessionId();
|
|
12080
12300
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
@@ -12105,7 +12325,6 @@ export class AgentSession {
|
|
|
12105
12325
|
this.#resetIrcRosterDeliveryState();
|
|
12106
12326
|
this.#planReferenceSent = false;
|
|
12107
12327
|
this.#planReferencePath = "local://PLAN.md";
|
|
12108
|
-
this.#cancelOwnAsyncJobs();
|
|
12109
12328
|
// The predecessor's fenced async-job results (queued while the
|
|
12110
12329
|
// transition held the delivery fence) belong to the handed-off session,
|
|
12111
12330
|
// not the successor. Suppress and drop ONLY the async-result kind so they
|
|
@@ -14847,6 +15066,17 @@ export class AgentSession {
|
|
|
14847
15066
|
// before the failure. This bypasses #hasCleanRetryReplaySafety because the
|
|
14848
15067
|
// content-free check is the replay-safety guarantee for credential rotation.
|
|
14849
15068
|
const canReplayRotatedCredential = credentialRotated;
|
|
15069
|
+
// Universal automatic replay-safety gate (#3791): once the failed attempt
|
|
15070
|
+
// carries observable assistant text, thinking, or tool-call content,
|
|
15071
|
+
// automatic session retry must not re-issue the request. This covers
|
|
15072
|
+
// configured legacy retry (which previously only gated first-event
|
|
15073
|
+
// timeouts) as well as bare defaults. Managed fallback keeps its
|
|
15074
|
+
// provisional discard path (events never published to the session).
|
|
15075
|
+
// Content-free credential rotation remains eligible above; first-event
|
|
15076
|
+
// timeout keeps its additional typed/scope checks earlier in this method.
|
|
15077
|
+
if (!managedFallback && assistantMessageHasVisibleOrToolContent(message)) {
|
|
15078
|
+
return false;
|
|
15079
|
+
}
|
|
14850
15080
|
// Bare defaults retain their narrow watchdog and Codex admissions. A
|
|
14851
15081
|
// first-event timeout adds the typed, content-free, current-clean-scope
|
|
14852
15082
|
// requirement above; other transient watchdogs preserve legacy behavior.
|
|
@@ -16210,6 +16440,10 @@ export class AgentSession {
|
|
|
16210
16440
|
onTransitionMutationStarted?: () => void;
|
|
16211
16441
|
},
|
|
16212
16442
|
): Promise<boolean> {
|
|
16443
|
+
let ownerShutdownManager: AsyncJobManager | undefined;
|
|
16444
|
+
let ownerShutdownLease: OwnerSubagentShutdownLease | undefined;
|
|
16445
|
+
let ownerShutdownTransitionCommitted = false;
|
|
16446
|
+
let ownerShutdownFinalizationDeferred = false;
|
|
16213
16447
|
this.#beginSessionTransition("switch-session");
|
|
16214
16448
|
try {
|
|
16215
16449
|
const previousSessionFile = this.sessionManager.getSessionFile();
|
|
@@ -16230,9 +16464,31 @@ export class AgentSession {
|
|
|
16230
16464
|
}
|
|
16231
16465
|
options?.onTransitionMutationStarted?.();
|
|
16232
16466
|
|
|
16233
|
-
|
|
16467
|
+
const asyncManager = AsyncJobManager.instance();
|
|
16468
|
+
const ownerId = this.#agentId;
|
|
16469
|
+
const lease =
|
|
16470
|
+
switchingToDifferentSession && asyncManager && ownerId
|
|
16471
|
+
? asyncManager.beginOwnerSubagentShutdown(ownerId)
|
|
16472
|
+
: undefined;
|
|
16473
|
+
if (switchingToDifferentSession && asyncManager && ownerId && !lease) {
|
|
16474
|
+
this.emitNotice(
|
|
16475
|
+
"error",
|
|
16476
|
+
"Cannot switch sessions while owned subagent cleanup is already in progress.",
|
|
16477
|
+
"switch-session-subagent-cleanup",
|
|
16478
|
+
);
|
|
16479
|
+
return false;
|
|
16480
|
+
}
|
|
16481
|
+
if (lease && asyncManager) {
|
|
16482
|
+
ownerShutdownManager = asyncManager;
|
|
16483
|
+
ownerShutdownLease = lease;
|
|
16484
|
+
}
|
|
16234
16485
|
await this.abort();
|
|
16486
|
+
if (this.isCompacting) {
|
|
16487
|
+
this.abortCompaction();
|
|
16488
|
+
while (this.isCompacting) await Bun.sleep(10);
|
|
16489
|
+
}
|
|
16235
16490
|
|
|
16491
|
+
this.#disconnectFromAgent();
|
|
16236
16492
|
// Flush pending writes before switching so restore snapshots reflect committed state.
|
|
16237
16493
|
await this.sessionManager.flush();
|
|
16238
16494
|
const previousSessionState = this.sessionManager.captureState();
|
|
@@ -16263,9 +16519,13 @@ export class AgentSession {
|
|
|
16263
16519
|
? this.#suspendWorkflowGateEmitter(previousSessionState.sessionId)
|
|
16264
16520
|
: undefined;
|
|
16265
16521
|
let unavailableDefaultChainMessage: string | undefined;
|
|
16522
|
+
let transitionCleanupCommitted = false;
|
|
16266
16523
|
|
|
16267
16524
|
try {
|
|
16268
|
-
await this.sessionManager.setSessionFile(sessionPath
|
|
16525
|
+
await this.sessionManager.setSessionFile(sessionPath, {
|
|
16526
|
+
deferEphemeralArtifactRetirement: switchingToDifferentSession,
|
|
16527
|
+
});
|
|
16528
|
+
if (switchingToDifferentSession) this.sessionManager.stageAdoptedArtifactManagerForTransition();
|
|
16269
16529
|
// The successor identity is already rotated in the manager but not yet
|
|
16270
16530
|
// published; gate its local:// root before publication so the agent,
|
|
16271
16531
|
// workflow-gate emitter, and hooks cannot resolve against an ungated
|
|
@@ -16365,6 +16625,38 @@ export class AgentSession {
|
|
|
16365
16625
|
this.#resetIrcRosterDeliveryState();
|
|
16366
16626
|
}
|
|
16367
16627
|
|
|
16628
|
+
if (switchingToDifferentSession) {
|
|
16629
|
+
let ownerShutdownSettled = true;
|
|
16630
|
+
if (ownerShutdownManager && ownerShutdownLease && ownerId) {
|
|
16631
|
+
try {
|
|
16632
|
+
ownerShutdownManager.runOwnerProducerCleanupsStrict({ ownerId });
|
|
16633
|
+
const proof = await ownerShutdownManager.cancelAndProveOwnerSubagents(ownerShutdownLease);
|
|
16634
|
+
if (!proof.confirmed)
|
|
16635
|
+
throw new Error("Owned subagent cleanup could not be confirmed after successor validation.");
|
|
16636
|
+
if (!(await ownerShutdownManager.waitForOwnerInFlightDeliveries(ownerId)))
|
|
16637
|
+
throw new Error("Owned async deliveries did not settle after successor validation.");
|
|
16638
|
+
if (!(await ownerShutdownManager.cancelAndSettleOwnerJobs(ownerId)))
|
|
16639
|
+
throw new Error("Owned async jobs did not settle after successor validation.");
|
|
16640
|
+
} catch (error) {
|
|
16641
|
+
ownerShutdownSettled = false;
|
|
16642
|
+
ownerShutdownFinalizationDeferred = true;
|
|
16643
|
+
this.#scheduleDeferredOwnerShutdownFinalization(ownerShutdownManager, ownerShutdownLease, ownerId);
|
|
16644
|
+
this.#suppressOwnAsyncJobDeliveries();
|
|
16645
|
+
this.emitNotice(
|
|
16646
|
+
"error",
|
|
16647
|
+
`Successor session is active, but predecessor async cleanup did not settle: ${error instanceof Error ? error.message : String(error)}`,
|
|
16648
|
+
"switch-session-subagent-cleanup",
|
|
16649
|
+
);
|
|
16650
|
+
}
|
|
16651
|
+
}
|
|
16652
|
+
transitionCleanupCommitted = true;
|
|
16653
|
+
// Different files may intentionally carry the same copied session id; pathname transition is the commit signal.
|
|
16654
|
+
ownerShutdownTransitionCommitted = true;
|
|
16655
|
+
if (ownerShutdownSettled) {
|
|
16656
|
+
this.sessionManager.retireEphemeralArtifactsAfterTransition();
|
|
16657
|
+
await this.#runToolSessionTransitionCleanups();
|
|
16658
|
+
}
|
|
16659
|
+
}
|
|
16368
16660
|
this.#reconnectToAgent();
|
|
16369
16661
|
// Fence predecessor continuations before session_switch starts SDK runtime
|
|
16370
16662
|
// teardown. The previous runtime waits for those continuations to settle;
|
|
@@ -16373,7 +16665,8 @@ export class AgentSession {
|
|
|
16373
16665
|
this.#bindWorkflowGateEmitter(previousSessionState.sessionId, suspendedWorkflowGateEmitter);
|
|
16374
16666
|
// session_switch is the post-commit identity signal. SDK authority and
|
|
16375
16667
|
// other identity-bound integrations must not observe the successor until
|
|
16376
|
-
// messages, model state, MCP selections,
|
|
16668
|
+
// messages, model state, MCP selections, the agent subscription, and
|
|
16669
|
+
// session-scoped tool cleanup are complete.
|
|
16377
16670
|
if (this.#extensionRunner) {
|
|
16378
16671
|
await this.#extensionRunner.emit({
|
|
16379
16672
|
type: "session_switch",
|
|
@@ -16384,6 +16677,7 @@ export class AgentSession {
|
|
|
16384
16677
|
}
|
|
16385
16678
|
return true;
|
|
16386
16679
|
} catch (error) {
|
|
16680
|
+
if (transitionCleanupCommitted) throw error;
|
|
16387
16681
|
this.sessionManager.restoreState(previousSessionState);
|
|
16388
16682
|
this.#defaultFallbackController = undefined;
|
|
16389
16683
|
this.#syncAgentSessionId(previousSessionState.sessionId);
|
|
@@ -16442,6 +16736,12 @@ export class AgentSession {
|
|
|
16442
16736
|
throw error;
|
|
16443
16737
|
}
|
|
16444
16738
|
} finally {
|
|
16739
|
+
if (ownerShutdownManager && ownerShutdownLease && !ownerShutdownFinalizationDeferred) {
|
|
16740
|
+
ownerShutdownManager.finishOwnerSubagentShutdown(
|
|
16741
|
+
ownerShutdownLease,
|
|
16742
|
+
ownerShutdownTransitionCommitted ? "commit" : "release",
|
|
16743
|
+
);
|
|
16744
|
+
}
|
|
16445
16745
|
this.#endSessionTransition();
|
|
16446
16746
|
}
|
|
16447
16747
|
}
|
|
@@ -16493,13 +16793,14 @@ export class AgentSession {
|
|
|
16493
16793
|
: await this.sessionManager.prepareNewSession({ parentSession: previousSessionFile });
|
|
16494
16794
|
try {
|
|
16495
16795
|
await initializeLocalRoot(this.#localProtocolOptions(prepared));
|
|
16796
|
+
await this.#settleOwnAsyncJobsBeforeArtifactRetirement();
|
|
16496
16797
|
this.sessionManager.commitPreparedNewSession(prepared);
|
|
16798
|
+
await this.#runToolSessionTransitionCleanups();
|
|
16497
16799
|
} catch (error) {
|
|
16498
16800
|
throw await discardPreparedNewSessionAfterFailure(this.sessionManager, prepared, error);
|
|
16499
16801
|
}
|
|
16500
16802
|
this.#pendingNextTurnMessages = [];
|
|
16501
16803
|
this.#scheduledHiddenNextTurnGeneration = undefined;
|
|
16502
|
-
this.#cancelOwnAsyncJobs();
|
|
16503
16804
|
|
|
16504
16805
|
this.#syncTodoPhasesFromBranch();
|
|
16505
16806
|
this.#syncAgentSessionId();
|