@gajae-code/coding-agent 0.15.6 → 0.16.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 +59 -0
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/root-flags.d.ts +14 -0
- package/dist/types/cli/update-cli.d.ts +13 -0
- package/dist/types/cli.d.ts +7 -0
- package/dist/types/commands/launch.d.ts +10 -0
- package/dist/types/config/autorouting-tier-map.d.ts +24 -0
- package/dist/types/config/file-lock.d.ts +5 -0
- package/dist/types/config/model-registry.d.ts +6 -0
- package/dist/types/config/settings-schema.d.ts +10 -1
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/coordinator-mcp/projection-scan.d.ts +8 -0
- package/dist/types/crash/record-loader.d.ts +15 -0
- package/dist/types/gjc-runtime/launch-worktree-reservation.d.ts +21 -0
- package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +2 -0
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +66 -1
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +1 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +27 -0
- package/dist/types/master-mode/context.d.ts +21 -0
- package/dist/types/master-mode/first-request.d.ts +34 -0
- package/dist/types/master-mode/snapshot.d.ts +32 -0
- package/dist/types/modes/components/frictionless-onboarding-selector.d.ts +10 -0
- package/dist/types/modes/components/oauth-selector.d.ts +1 -0
- package/dist/types/modes/components/status-line/model-name.d.ts +18 -0
- package/dist/types/modes/components/status-line/priority-row.d.ts +34 -0
- package/dist/types/modes/components/status-line/segments.d.ts +10 -0
- package/dist/types/modes/print-mode.d.ts +17 -0
- package/dist/types/modes/ui-language.d.ts +7 -1
- package/dist/types/sdk/broker/broker.d.ts +77 -1
- package/dist/types/sdk/broker/endpoint-authority.d.ts +24 -0
- package/dist/types/sdk/broker/index.d.ts +3 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +26 -0
- package/dist/types/sdk/broker/master-capability.d.ts +10 -0
- package/dist/types/sdk/broker/session-index.d.ts +40 -11
- package/dist/types/sdk/broker/session-scope.d.ts +72 -0
- package/dist/types/sdk/broker/spawn-authority.d.ts +201 -0
- package/dist/types/sdk/broker/spawn-substrate.d.ts +30 -0
- package/dist/types/sdk/broker/state-version.d.ts +4 -1
- package/dist/types/sdk/broker/worktree-occupancy.d.ts +28 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +3 -0
- package/dist/types/sdk/bus/index.d.ts +6 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
- package/dist/types/sdk/cli/index.d.ts +1 -0
- package/dist/types/sdk/cli/master-cli.d.ts +63 -0
- package/dist/types/sdk/cli/rows.d.ts +4 -3
- package/dist/types/sdk/cli/session-cli.d.ts +18 -2
- package/dist/types/sdk/client/client.d.ts +3 -2
- package/dist/types/sdk/host/control/dispatch.d.ts +10 -0
- package/dist/types/sdk/host/control/index.d.ts +1 -1
- package/dist/types/sdk/host/control/operations.d.ts +2 -0
- package/dist/types/sdk/host/control/runtime-gate.d.ts +21 -7
- package/dist/types/sdk/host/host.d.ts +10 -0
- package/dist/types/sdk/host/session-runtime.d.ts +52 -0
- package/dist/types/sdk/lifecycle/broker-client.d.ts +6 -0
- package/dist/types/sdk/lifecycle/service.d.ts +15 -3
- package/dist/types/sdk/lifecycle-session.d.ts +2 -0
- package/dist/types/sdk/router/session-router.d.ts +2 -0
- package/dist/types/sdk/session.d.ts +16 -0
- package/dist/types/sdk/startup-capability.d.ts +2 -1
- package/dist/types/session/agent-session.d.ts +5 -0
- package/dist/types/session/credential-store-errors.d.ts +1 -0
- package/dist/types/session/session-manager.d.ts +65 -0
- package/dist/types/session/yield-queue.d.ts +5 -4
- package/dist/types/setup/frictionless-onboarding.d.ts +1 -0
- package/dist/types/tools/bash.d.ts +4 -0
- package/dist/types/tools/gh.d.ts +8 -0
- package/dist/types/tools/index.d.ts +4 -0
- package/package.json +7 -7
- package/scripts/generate-sdk-operation-inventory.ts +6 -0
- package/scripts/verify-gjc-sdk-canonicalization.ts +35 -1
- package/src/cli/accounts-cli.ts +9 -5
- package/src/cli/args.ts +17 -0
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/root-flags.ts +5 -0
- package/src/cli/update-cli.ts +77 -11
- package/src/commands/launch.ts +86 -18
- package/src/commands/sdk.ts +113 -21
- package/src/config/autorouting-tier-map.ts +12 -0
- package/src/config/file-lock.ts +158 -83
- package/src/config/model-preset-registry.ts +1 -5
- package/src/config/model-profiles.ts +5 -5
- package/src/config/model-registry.ts +23 -0
- package/src/config/model-resolver.ts +5 -0
- package/src/config/settings-schema.ts +8 -1
- package/src/config/settings.ts +5 -0
- package/src/coordinator-mcp/projection-scan.ts +21 -0
- package/src/coordinator-mcp/question-state.ts +9 -11
- package/src/coordinator-mcp/server.ts +94 -20
- package/src/crash/index-store.ts +2 -0
- package/src/crash/record-loader.ts +10 -0
- package/src/gjc-runtime/launch-worktree-reservation.ts +242 -0
- package/src/gjc-runtime/managed-owner-supervisor.ts +63 -51
- package/src/gjc-runtime/session-state-lock.ts +79 -6
- package/src/gjc-runtime/session-state-sidecar.ts +929 -23
- package/src/gjc-runtime/tmux-owner-isolation-cli.ts +4 -7
- package/src/gjc-runtime/tmux-owner-isolation.ts +2 -2
- package/src/gjc-runtime/tmux-sessions.ts +252 -13
- package/src/hooks/skill-state.ts +6 -0
- package/src/internal-urls/docs-index.generated.ts +7 -7
- package/src/internal-urls/issue-pr-protocol.ts +4 -2
- package/src/main.ts +57 -2
- package/src/master-mode/context.ts +50 -0
- package/src/master-mode/first-request.ts +65 -0
- package/src/master-mode/snapshot.ts +205 -0
- package/src/modes/acp/acp-agent.ts +605 -156
- package/src/modes/components/footer.ts +4 -2
- package/src/modes/components/frictionless-onboarding-selector.ts +48 -2
- package/src/modes/components/oauth-selector.ts +6 -1
- package/src/modes/components/settings-selector.ts +1 -0
- package/src/modes/components/status-line/model-name.ts +53 -0
- package/src/modes/components/status-line/priority-row.ts +107 -0
- package/src/modes/components/status-line/segments.ts +23 -9
- package/src/modes/components/tool-status-header.ts +151 -15
- package/src/modes/controllers/input-controller.ts +28 -0
- package/src/modes/controllers/selector-controller.ts +191 -96
- package/src/modes/print-mode.ts +64 -0
- package/src/modes/ui-language.ts +49 -2
- package/src/prompts/system/master-mode.md +28 -0
- package/src/sdk/broker/broker.ts +1636 -26
- package/src/sdk/broker/endpoint-authority.ts +86 -0
- package/src/sdk/broker/ensure.ts +5 -0
- package/src/sdk/broker/index.ts +3 -0
- package/src/sdk/broker/lifecycle.ts +186 -41
- package/src/sdk/broker/master-capability.ts +232 -0
- package/src/sdk/broker/session-index.ts +121 -17
- package/src/sdk/broker/session-scope.ts +272 -0
- package/src/sdk/broker/spawn-authority.ts +1031 -0
- package/src/sdk/broker/spawn-substrate.ts +548 -0
- package/src/sdk/broker/state-version.ts +26 -14
- package/src/sdk/broker/transport.ts +1 -0
- package/src/sdk/broker/worktree-occupancy.ts +90 -0
- package/src/sdk/bus/chat-daemon-control.ts +5 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +35 -4
- package/src/sdk/bus/index.ts +51 -4
- package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
- package/src/sdk/cli/index.ts +1 -0
- package/src/sdk/cli/master-cli.ts +259 -0
- package/src/sdk/cli/rows.ts +11 -8
- package/src/sdk/cli/session-cli.ts +264 -5
- package/src/sdk/client/client.ts +14 -8
- package/src/sdk/host/control/dispatch.ts +99 -38
- package/src/sdk/host/control/index.ts +2 -0
- package/src/sdk/host/control/operations.ts +2 -0
- package/src/sdk/host/control/runtime-gate.ts +203 -11
- package/src/sdk/host/host.ts +27 -2
- package/src/sdk/host/session-runtime.test.ts +210 -6
- package/src/sdk/host/session-runtime.ts +291 -49
- package/src/sdk/lifecycle/broker-client.ts +28 -0
- package/src/sdk/lifecycle/service.ts +279 -20
- package/src/sdk/lifecycle-session.ts +10 -2
- package/src/sdk/protocol/operation-inventory.generated.json +51 -0
- package/src/sdk/protocol/operation-registry.ts +13 -3
- package/src/sdk/router/session-router.ts +67 -59
- package/src/sdk/session.ts +357 -155
- package/src/sdk/startup-capability.ts +1 -0
- package/src/session/agent-session.ts +640 -103
- package/src/session/blob-store.ts +15 -5
- package/src/session/credential-store-errors.ts +2 -0
- package/src/session/internal/managed-session-storage.ts +153 -78
- package/src/session/session-manager.ts +337 -24
- package/src/session/yield-queue.ts +24 -10
- package/src/setup/frictionless-onboarding.ts +2 -0
- package/src/telemetry/events.ts +1138 -12
- package/src/tools/bash.ts +256 -42
- package/src/tools/gh.ts +104 -48
- package/src/tools/github-cache.ts +2 -2
- package/src/tools/index.ts +4 -0
|
@@ -315,10 +315,16 @@ import {
|
|
|
315
315
|
} from "../gjc-runtime/session-layout";
|
|
316
316
|
import {
|
|
317
317
|
type CoordinatorToolObservation,
|
|
318
|
+
clearCoordinatorRuntimeStateRescope,
|
|
319
|
+
hasCoordinatorRuntimeStateRescopeJournal,
|
|
320
|
+
markCoordinatorRuntimeStateRescopePublishing,
|
|
318
321
|
ownerTerminalContextFromEnvironment,
|
|
319
322
|
persistCoordinatorRuntimeStateFromEvent,
|
|
320
323
|
persistCoordinatorWorkerIntegrationOutcome,
|
|
324
|
+
prepareCoordinatorRuntimeStateRescope,
|
|
325
|
+
recoverCoordinatorRuntimeStateRescope,
|
|
321
326
|
registerCoordinatorRuntimeStateFinalizer,
|
|
327
|
+
relocateCoordinatorRuntimeStateForRescope,
|
|
322
328
|
UNPROVEN_TOOL_LABEL,
|
|
323
329
|
} from "../gjc-runtime/session-state-sidecar";
|
|
324
330
|
import {
|
|
@@ -2386,6 +2392,7 @@ export class AgentSession {
|
|
|
2386
2392
|
#preProfileModel: Model | undefined;
|
|
2387
2393
|
#sessionAdmissionQueue: SessionAdmissionEntry[] = [];
|
|
2388
2394
|
#activeSessionAdmission: SessionAdmissionEntry | undefined;
|
|
2395
|
+
#startupPromptWaiters = new Set<Promise<void>>();
|
|
2389
2396
|
#sessionAdmissionClosing = false;
|
|
2390
2397
|
#sessionAdmissionClosed = false;
|
|
2391
2398
|
#sessionAdmissionContext = new AsyncLocalStorage<SessionAdmissionEntry>();
|
|
@@ -2814,6 +2821,27 @@ export class AgentSession {
|
|
|
2814
2821
|
/** Idempotent unregister handle for this session's resource-GC registration. */
|
|
2815
2822
|
#unregisterResourceGc?: () => void;
|
|
2816
2823
|
#unregisterRuntimeStateFinalizer?: () => void;
|
|
2824
|
+
#unregisterBeforeMoveListener?: () => void;
|
|
2825
|
+
#unregisterMoveAbortListener?: () => void;
|
|
2826
|
+
#unregisterMovePublicationListener?: () => void;
|
|
2827
|
+
#unregisterAfterMoveListener?: () => void;
|
|
2828
|
+
/**
|
|
2829
|
+
* (Re)register the postmortem finalizer bound to the CURRENT cwd/session file. Called at
|
|
2830
|
+
* construction and after every committed rescope, so an unexpected exit writes terminal
|
|
2831
|
+
* state to the session's live location rather than the abandoned launch root.
|
|
2832
|
+
*/
|
|
2833
|
+
#registerRuntimeStateFinalizer(): void {
|
|
2834
|
+
this.#unregisterRuntimeStateFinalizer?.();
|
|
2835
|
+
const currentContext = () => ({
|
|
2836
|
+
sessionId: this.sessionId,
|
|
2837
|
+
cwd: this.sessionManager.getCwd(),
|
|
2838
|
+
sessionFile: this.sessionManager.getSessionFile(),
|
|
2839
|
+
});
|
|
2840
|
+
this.#unregisterRuntimeStateFinalizer = registerCoordinatorRuntimeStateFinalizer(
|
|
2841
|
+
currentContext(),
|
|
2842
|
+
currentContext,
|
|
2843
|
+
);
|
|
2844
|
+
}
|
|
2817
2845
|
#unregisterSessionMemorySettings?: () => void;
|
|
2818
2846
|
/**
|
|
2819
2847
|
* AsyncJobManager owned by this session (top-level only). Subagents leave
|
|
@@ -2823,6 +2851,7 @@ export class AgentSession {
|
|
|
2823
2851
|
readonly #disposeAsyncJobManager: boolean;
|
|
2824
2852
|
#ownedMcpManager: MCPManager | undefined;
|
|
2825
2853
|
#startupTurnBarrier: Promise<void> | undefined;
|
|
2854
|
+
#startupTurnBarrierPending = false;
|
|
2826
2855
|
#pendingPythonMessages: Array<{
|
|
2827
2856
|
message: PythonExecutionMessage;
|
|
2828
2857
|
onPersisted?: () => void;
|
|
@@ -2848,6 +2877,9 @@ export class AgentSession {
|
|
|
2848
2877
|
readonly #asyncJobProviderSessionId: string | undefined;
|
|
2849
2878
|
#isDisposed = false;
|
|
2850
2879
|
#disposePromise: Promise<void> | undefined;
|
|
2880
|
+
readonly #disposeAbortController = new AbortController();
|
|
2881
|
+
#disposeAdmissionClosed: Promise<void> | undefined;
|
|
2882
|
+
#disposePostPromptDrain: Promise<void> | undefined;
|
|
2851
2883
|
readonly #toolSessionCleanups = new Set<() => Promise<void> | void>();
|
|
2852
2884
|
readonly #toolSessionTransitionCleanups = new Set<() => Promise<void> | void>();
|
|
2853
2885
|
readonly #deferredOwnerShutdownFinalizations = new Set<Promise<void>>();
|
|
@@ -3019,6 +3051,7 @@ export class AgentSession {
|
|
|
3019
3051
|
#cancelAndSubmitAbortOutcomeProviderForTests: (() => Promise<AbortOutcome>) | undefined = undefined;
|
|
3020
3052
|
#postPromptTasks = new Set<Promise<void>>();
|
|
3021
3053
|
#postPromptTaskSelectionFenceGenerations = new Map<Promise<void>, number>();
|
|
3054
|
+
#postPromptTaskRecoveryExcluded = new Set<Promise<void>>();
|
|
3022
3055
|
#postPromptTasksPromise: Promise<void> | undefined = undefined;
|
|
3023
3056
|
#postPromptTasksResolve: (() => void) | undefined = undefined;
|
|
3024
3057
|
#postPromptTasksAbortController = new AbortController();
|
|
@@ -3206,6 +3239,65 @@ export class AgentSession {
|
|
|
3206
3239
|
*/
|
|
3207
3240
|
#sessionTransitionKind: string | undefined;
|
|
3208
3241
|
#coordinatorPersistGeneration = 0;
|
|
3242
|
+
#coordinatorRescopeBarrier: Promise<void> | undefined;
|
|
3243
|
+
#releaseCoordinatorRescopeBarrier: (() => void) | undefined;
|
|
3244
|
+
#coordinatorRescopeMoveId: string | undefined;
|
|
3245
|
+
#coordinatorEventHandlers = new Set<Promise<void>>();
|
|
3246
|
+
#coordinatorUnbarrieredPersists = new Set<Promise<void>>();
|
|
3247
|
+
#coordinatorReleasedBarrierPersists = new Set<Promise<void>>();
|
|
3248
|
+
|
|
3249
|
+
#beginCoordinatorRescopeBarrier(): {
|
|
3250
|
+
eventHandlers: Promise<void>[];
|
|
3251
|
+
releasedBarrierPersists: Promise<void>[];
|
|
3252
|
+
} {
|
|
3253
|
+
if (this.#coordinatorRescopeBarrier) throw new Error("Coordinator rescope barrier is already active.");
|
|
3254
|
+
const releasedBarrierPersists = [...this.#coordinatorReleasedBarrierPersists];
|
|
3255
|
+
const barrier = Promise.withResolvers<void>();
|
|
3256
|
+
this.#coordinatorRescopeBarrier = barrier.promise;
|
|
3257
|
+
this.#releaseCoordinatorRescopeBarrier = barrier.resolve;
|
|
3258
|
+
this.#bindPendingAgentEndToRescopeBarrier();
|
|
3259
|
+
return { eventHandlers: [...this.#coordinatorEventHandlers], releasedBarrierPersists };
|
|
3260
|
+
}
|
|
3261
|
+
|
|
3262
|
+
#trackReleasedBarrierPersist(persist: Promise<void>): void {
|
|
3263
|
+
this.#coordinatorReleasedBarrierPersists.add(persist);
|
|
3264
|
+
void persist.then(
|
|
3265
|
+
() => this.#coordinatorReleasedBarrierPersists.delete(persist),
|
|
3266
|
+
() => this.#coordinatorReleasedBarrierPersists.delete(persist),
|
|
3267
|
+
);
|
|
3268
|
+
}
|
|
3269
|
+
|
|
3270
|
+
#bindPendingAgentEndToRescopeBarrier(): void {
|
|
3271
|
+
if (!this.#coordinatorRescopeBarrier || !this.#pendingAgentEndEmit) return;
|
|
3272
|
+
const admission = this.#agentEventAdmission.get(this.#pendingAgentEndEmit);
|
|
3273
|
+
if (admission) admission.persistBarrier = this.#coordinatorRescopeBarrier;
|
|
3274
|
+
}
|
|
3275
|
+
|
|
3276
|
+
#appendCoordinatorPersist(run: () => Promise<void>): Promise<void> {
|
|
3277
|
+
const queued = this.#coordinatorPersistQueue.then(run, run);
|
|
3278
|
+
this.#coordinatorPersistQueue = queued.catch(() => {});
|
|
3279
|
+
return queued;
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3282
|
+
#trackUnbarrieredCoordinatorPersist(persist: Promise<void>): void {
|
|
3283
|
+
this.#coordinatorUnbarrieredPersists.add(persist);
|
|
3284
|
+
void persist.then(
|
|
3285
|
+
() => this.#coordinatorUnbarrieredPersists.delete(persist),
|
|
3286
|
+
() => this.#coordinatorUnbarrieredPersists.delete(persist),
|
|
3287
|
+
);
|
|
3288
|
+
}
|
|
3289
|
+
|
|
3290
|
+
async #drainUnbarrieredCoordinatorPersists(): Promise<void> {
|
|
3291
|
+
while (this.#coordinatorUnbarrieredPersists.size > 0) {
|
|
3292
|
+
await Promise.allSettled([...this.#coordinatorUnbarrieredPersists]);
|
|
3293
|
+
}
|
|
3294
|
+
}
|
|
3295
|
+
|
|
3296
|
+
#endCoordinatorRescopeBarrier(): void {
|
|
3297
|
+
this.#releaseCoordinatorRescopeBarrier?.();
|
|
3298
|
+
this.#releaseCoordinatorRescopeBarrier = undefined;
|
|
3299
|
+
this.#coordinatorRescopeBarrier = undefined;
|
|
3300
|
+
}
|
|
3209
3301
|
|
|
3210
3302
|
#beginSessionTransition(kind: string): void {
|
|
3211
3303
|
if (this.#sessionTransitionKind !== undefined) {
|
|
@@ -3242,16 +3334,61 @@ export class AgentSession {
|
|
|
3242
3334
|
return { entry, capability: entry.continuationCapability };
|
|
3243
3335
|
}
|
|
3244
3336
|
|
|
3245
|
-
|
|
3246
|
-
|
|
3337
|
+
#setStartupTurnBarrier(barrier: Promise<void> | undefined): void {
|
|
3338
|
+
this.#startupTurnBarrier = barrier;
|
|
3339
|
+
this.#startupTurnBarrierPending = barrier !== undefined;
|
|
3247
3340
|
if (!barrier) return;
|
|
3248
|
-
|
|
3249
|
-
|
|
3341
|
+
void barrier.then(
|
|
3342
|
+
() => {
|
|
3343
|
+
if (this.#startupTurnBarrier === barrier) this.#startupTurnBarrierPending = false;
|
|
3344
|
+
},
|
|
3345
|
+
() => {
|
|
3346
|
+
if (this.#startupTurnBarrier === barrier) this.#startupTurnBarrierPending = false;
|
|
3347
|
+
},
|
|
3348
|
+
);
|
|
3349
|
+
}
|
|
3350
|
+
async #awaitStartupTurnBarrier(signal?: AbortSignal): Promise<void> {
|
|
3351
|
+
while (true) {
|
|
3352
|
+
const barrier = this.#startupTurnBarrier;
|
|
3353
|
+
if (!barrier) return;
|
|
3354
|
+
await awaitPromptInvocationPreflight(barrier, signal);
|
|
3355
|
+
// Cross one task boundary before clearing readiness. Promise reaction
|
|
3356
|
+
// chains fully drain before timers, so every extension published by the
|
|
3357
|
+
// captured barrier's settlement becomes visible without an arbitrary
|
|
3358
|
+
// microtask-depth cap.
|
|
3359
|
+
const publicationTurn = Promise.withResolvers<void>();
|
|
3360
|
+
const timer = setTimeout(publicationTurn.resolve, 0);
|
|
3361
|
+
try {
|
|
3362
|
+
await awaitPromptInvocationPreflight(publicationTurn.promise, signal);
|
|
3363
|
+
} finally {
|
|
3364
|
+
clearTimeout(timer);
|
|
3365
|
+
}
|
|
3366
|
+
if (this.#startupTurnBarrier !== barrier) continue;
|
|
3367
|
+
this.#setStartupTurnBarrier(undefined);
|
|
3368
|
+
return;
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
async #awaitStartupPromptWaiters(signal?: AbortSignal): Promise<void> {
|
|
3372
|
+
while (this.#startupPromptWaiters.size > 0) {
|
|
3373
|
+
await awaitPromptInvocationPreflight(Promise.all([...this.#startupPromptWaiters]), signal);
|
|
3374
|
+
await Promise.resolve();
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
3377
|
+
#reserveStartupPromptWaiter(): () => void {
|
|
3378
|
+
const waiter = Promise.withResolvers<void>();
|
|
3379
|
+
this.#startupPromptWaiters.add(waiter.promise);
|
|
3380
|
+
let released = false;
|
|
3381
|
+
return () => {
|
|
3382
|
+
if (released) return;
|
|
3383
|
+
released = true;
|
|
3384
|
+
this.#startupPromptWaiters.delete(waiter.promise);
|
|
3385
|
+
waiter.resolve();
|
|
3386
|
+
};
|
|
3250
3387
|
}
|
|
3251
3388
|
extendStartupTurnBarrier(barrier: Promise<void>): void {
|
|
3252
3389
|
const current = this.#startupTurnBarrier;
|
|
3253
|
-
this.#
|
|
3254
|
-
void this.#startupTurnBarrier
|
|
3390
|
+
this.#setStartupTurnBarrier(current ? Promise.all([current, barrier]).then(() => {}) : barrier);
|
|
3391
|
+
void this.#startupTurnBarrier?.catch(() => {});
|
|
3255
3392
|
}
|
|
3256
3393
|
|
|
3257
3394
|
async #withSessionAdmission<T>(
|
|
@@ -3263,6 +3400,8 @@ export class AgentSession {
|
|
|
3263
3400
|
allowDuringClosing?: boolean;
|
|
3264
3401
|
bypassSelectionFenceGeneration?: number;
|
|
3265
3402
|
allowPromptContinuationReentry?: boolean;
|
|
3403
|
+
idleDelivery?: boolean;
|
|
3404
|
+
startupPromptWaiterRelease?: () => void;
|
|
3266
3405
|
},
|
|
3267
3406
|
): Promise<T> {
|
|
3268
3407
|
const owner = this.#sessionAdmissionContext.getStore();
|
|
@@ -3277,7 +3416,21 @@ export class AgentSession {
|
|
|
3277
3416
|
}
|
|
3278
3417
|
throw this.#sessionAdmissionBusyError();
|
|
3279
3418
|
}
|
|
3280
|
-
|
|
3419
|
+
const releaseStartupPromptWaiter =
|
|
3420
|
+
kind === "prompt" && options?.idleDelivery !== true
|
|
3421
|
+
? (options?.startupPromptWaiterRelease ?? this.#reserveStartupPromptWaiter())
|
|
3422
|
+
: undefined;
|
|
3423
|
+
if (kind === "prompt") {
|
|
3424
|
+
const startupSignal = signal
|
|
3425
|
+
? AbortSignal.any([signal, this.#disposeAbortController.signal])
|
|
3426
|
+
: this.#disposeAbortController.signal;
|
|
3427
|
+
try {
|
|
3428
|
+
await awaitPromptInvocationPreflight(this.#awaitStartupTurnBarrier(startupSignal), startupSignal);
|
|
3429
|
+
} finally {
|
|
3430
|
+
releaseStartupPromptWaiter?.();
|
|
3431
|
+
}
|
|
3432
|
+
if (options?.idleDelivery === true) await this.#awaitStartupPromptWaiters(startupSignal);
|
|
3433
|
+
}
|
|
3281
3434
|
const bypassesSelectionFence =
|
|
3282
3435
|
options?.bypassSelectionFenceGeneration !== undefined &&
|
|
3283
3436
|
options.bypassSelectionFenceGeneration < this.#selectionFenceGeneration;
|
|
@@ -4039,10 +4192,96 @@ export class AgentSession {
|
|
|
4039
4192
|
cwd: () => this.sessionManager.getCwd(),
|
|
4040
4193
|
});
|
|
4041
4194
|
}
|
|
4042
|
-
this.#
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4195
|
+
this.#registerRuntimeStateFinalizer();
|
|
4196
|
+
this.#unregisterBeforeMoveListener = this.sessionManager.registerBeforeMoveListener(async move => {
|
|
4197
|
+
const admittedBeforeBarrier = this.#beginCoordinatorRescopeBarrier();
|
|
4198
|
+
await Promise.allSettled([
|
|
4199
|
+
...admittedBeforeBarrier.eventHandlers,
|
|
4200
|
+
...admittedBeforeBarrier.releasedBarrierPersists,
|
|
4201
|
+
]);
|
|
4202
|
+
this.#bindPendingAgentEndToRescopeBarrier();
|
|
4203
|
+
await this.#drainUnbarrieredCoordinatorPersists();
|
|
4204
|
+
this.#coordinatorPersistGeneration += 1;
|
|
4205
|
+
const moveId = crypto.randomUUID();
|
|
4206
|
+
this.#coordinatorRescopeMoveId = moveId;
|
|
4207
|
+
await prepareCoordinatorRuntimeStateRescope({
|
|
4208
|
+
moveId,
|
|
4209
|
+
sessionId: this.sessionId,
|
|
4210
|
+
previousCwd: move.previousCwd,
|
|
4211
|
+
newCwd: move.newCwd,
|
|
4212
|
+
previousCwdIdentity: move.previousCwdIdentity,
|
|
4213
|
+
newCwdIdentity: move.newCwdIdentity,
|
|
4214
|
+
previousSessionFile: move.previousSessionFile ?? null,
|
|
4215
|
+
newSessionFile: move.newSessionFile ?? null,
|
|
4216
|
+
});
|
|
4217
|
+
});
|
|
4218
|
+
this.#unregisterMoveAbortListener = this.sessionManager.registerMoveAbortListener(async move => {
|
|
4219
|
+
const moveId = this.#coordinatorRescopeMoveId;
|
|
4220
|
+
try {
|
|
4221
|
+
if (moveId && !move.preserveRecoveryJournal) {
|
|
4222
|
+
await clearCoordinatorRuntimeStateRescope(
|
|
4223
|
+
{
|
|
4224
|
+
sessionId: this.sessionId,
|
|
4225
|
+
cwd: move.newCwd,
|
|
4226
|
+
sessionFile: move.newSessionFile ?? null,
|
|
4227
|
+
},
|
|
4228
|
+
moveId,
|
|
4229
|
+
move.previousCwd,
|
|
4230
|
+
);
|
|
4231
|
+
}
|
|
4232
|
+
} finally {
|
|
4233
|
+
this.#coordinatorRescopeMoveId = undefined;
|
|
4234
|
+
this.#endCoordinatorRescopeBarrier();
|
|
4235
|
+
}
|
|
4236
|
+
});
|
|
4237
|
+
this.#unregisterMovePublicationListener = this.sessionManager.registerMovePublicationListener(async move => {
|
|
4238
|
+
const moveId = this.#coordinatorRescopeMoveId;
|
|
4239
|
+
if (!moveId) throw new Error("Coordinator rescope publication has no prepared move identity.");
|
|
4240
|
+
await markCoordinatorRuntimeStateRescopePublishing(
|
|
4241
|
+
{
|
|
4242
|
+
sessionId: this.sessionId,
|
|
4243
|
+
cwd: move.newCwd,
|
|
4244
|
+
sessionFile: move.newSessionFile ?? null,
|
|
4245
|
+
},
|
|
4246
|
+
move.previousCwd,
|
|
4247
|
+
moveId,
|
|
4248
|
+
);
|
|
4249
|
+
});
|
|
4250
|
+
// Every committed rescope (`move_session`, `/move`, SDK/ACP `session.cwd.move`) funnels
|
|
4251
|
+
// through SessionManager.moveTo, so one after-move listener covers all surfaces: it
|
|
4252
|
+
// relocates the coordinator-shared runtime state to the new cwd (otherwise the identity
|
|
4253
|
+
// fence refuses every later persist) and rebinds the postmortem finalizer, which
|
|
4254
|
+
// otherwise keeps writing terminal state to the launch root's cwd/session file.
|
|
4255
|
+
this.#unregisterAfterMoveListener = this.sessionManager.registerAfterMoveListener(async move => {
|
|
4256
|
+
let completed = false;
|
|
4257
|
+
try {
|
|
4258
|
+
const relocated = await relocateCoordinatorRuntimeStateForRescope(
|
|
4259
|
+
{
|
|
4260
|
+
sessionId: this.sessionId,
|
|
4261
|
+
cwd: move.newCwd,
|
|
4262
|
+
sessionFile: this.sessionManager.getSessionFile() ?? null,
|
|
4263
|
+
previousSessionFile: move.previousSessionFile ?? null,
|
|
4264
|
+
},
|
|
4265
|
+
move.previousCwd,
|
|
4266
|
+
);
|
|
4267
|
+
if (!relocated) throw new Error("Coordinator runtime state rescope was refused.");
|
|
4268
|
+
await clearCoordinatorRuntimeStateRescope(
|
|
4269
|
+
{
|
|
4270
|
+
sessionId: this.sessionId,
|
|
4271
|
+
cwd: move.newCwd,
|
|
4272
|
+
sessionFile: this.sessionManager.getSessionFile() ?? null,
|
|
4273
|
+
},
|
|
4274
|
+
this.#coordinatorRescopeMoveId,
|
|
4275
|
+
move.previousCwd,
|
|
4276
|
+
);
|
|
4277
|
+
completed = true;
|
|
4278
|
+
} finally {
|
|
4279
|
+
this.#registerRuntimeStateFinalizer();
|
|
4280
|
+
if (completed) {
|
|
4281
|
+
this.#coordinatorRescopeMoveId = undefined;
|
|
4282
|
+
this.#endCoordinatorRescopeBarrier();
|
|
4283
|
+
}
|
|
4284
|
+
}
|
|
4046
4285
|
});
|
|
4047
4286
|
// Power assertions are taken per turn (see #beginInFlight); nothing acquired here.
|
|
4048
4287
|
this.#evalKernelOwnerId = config.evalKernelOwnerId ?? `agent-session:${Snowflake.next()}`;
|
|
@@ -4051,7 +4290,19 @@ export class AgentSession {
|
|
|
4051
4290
|
this.#disposeAsyncJobManager = config.disposeAsyncJobManager ?? true;
|
|
4052
4291
|
this.#retainedMemorySampler = config.retainedMemorySampler;
|
|
4053
4292
|
this.#ownedMcpManager = config.ownedMcpManager;
|
|
4054
|
-
this.#
|
|
4293
|
+
this.#setStartupTurnBarrier(config.startupTurnBarrier);
|
|
4294
|
+
// Only arm the recovery barrier when a rescope journal is actually pending. Arming it
|
|
4295
|
+
// unconditionally flips #startupTurnBarrier from absent to a (resolved) promise for
|
|
4296
|
+
// every session, which makes barrier-gated post-turn work (e.g. the hidden-next-turn
|
|
4297
|
+
// drain) take an extra scheduler hop and reorder against terminal-abort requeue.
|
|
4298
|
+
const rescopeRecoveryContext = {
|
|
4299
|
+
sessionId: this.sessionId,
|
|
4300
|
+
cwd: this.sessionManager.getCwd(),
|
|
4301
|
+
sessionFile: this.sessionManager.getSessionFile() ?? null,
|
|
4302
|
+
};
|
|
4303
|
+
if (hasCoordinatorRuntimeStateRescopeJournal(rescopeRecoveryContext)) {
|
|
4304
|
+
this.extendStartupTurnBarrier(recoverCoordinatorRuntimeStateRescope(rescopeRecoveryContext));
|
|
4305
|
+
}
|
|
4055
4306
|
this.#scopedModels = config.scopedModels ?? [];
|
|
4056
4307
|
this.#thinkingLevel = config.thinkingLevel;
|
|
4057
4308
|
this.#promptTemplates = config.promptTemplates ?? [];
|
|
@@ -4139,7 +4390,7 @@ export class AgentSession {
|
|
|
4139
4390
|
// agent.prompt directly, allocates right before admission.
|
|
4140
4391
|
this.agent.followUp(message);
|
|
4141
4392
|
},
|
|
4142
|
-
injectIdle: async messages => {
|
|
4393
|
+
injectIdle: async (messages, signal) => {
|
|
4143
4394
|
// Mandated boundary comment (corrected turn semantics): same origin
|
|
4144
4395
|
// split as the streaming injector — an allowed owned-completion
|
|
4145
4396
|
// delivery starts a fresh turn attempt/lineage and is not a
|
|
@@ -4153,37 +4404,43 @@ export class AgentSession {
|
|
|
4153
4404
|
if (dropped.length > 0) this.#settleDeliveredOwnedRegistrations(dropped);
|
|
4154
4405
|
const first = survivors[0];
|
|
4155
4406
|
if (!first) return;
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
// completion). Route the survivors through followUp — the
|
|
4163
|
-
// streaming injector's path — which allocates the fresh resume
|
|
4164
|
-
// lineage at actual admission (review thread P1).
|
|
4165
|
-
if (this.isStreaming) {
|
|
4166
|
-
for (const message of survivors) this.agent.followUp(message);
|
|
4167
|
-
return;
|
|
4168
|
-
}
|
|
4169
|
-
if (survivors.some(message => ownedCompletionResumeAction(message) === "fresh"))
|
|
4170
|
-
this.#resumeFromOwnedCompletion();
|
|
4407
|
+
const settleIfDisposing = (): boolean => {
|
|
4408
|
+
if (!this.#isDisposed && !this.#sessionAdmissionClosing && !this.#disposeAbortController.signal.aborted)
|
|
4409
|
+
return false;
|
|
4410
|
+
this.#settleDeliveredOwnedRegistrations(survivors);
|
|
4411
|
+
return true;
|
|
4412
|
+
};
|
|
4171
4413
|
try {
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4414
|
+
await this.#withSessionAdmission(
|
|
4415
|
+
"prompt",
|
|
4416
|
+
async () => {
|
|
4417
|
+
if (settleIfDisposing()) return;
|
|
4418
|
+
if (this.isStreaming) {
|
|
4419
|
+
await awaitPromptInvocationPreflight(this.agent.waitForIdle(), signal);
|
|
4420
|
+
if (settleIfDisposing()) return;
|
|
4421
|
+
}
|
|
4422
|
+
if (survivors.some(message => ownedCompletionResumeAction(message) === "fresh"))
|
|
4423
|
+
this.#resumeFromOwnedCompletion();
|
|
4424
|
+
if (survivors.length === 1) {
|
|
4425
|
+
await this.agent.prompt(first, {
|
|
4426
|
+
...this.#managedFallbackPromptOptions(),
|
|
4427
|
+
onRunAccepted: (handle: AttemptRunHandle) => {
|
|
4428
|
+
if (handle) this.#acceptSdkAttemptRun(handle);
|
|
4429
|
+
},
|
|
4430
|
+
});
|
|
4431
|
+
} else {
|
|
4432
|
+
await this.agent.prompt(survivors, {
|
|
4433
|
+
...this.#managedFallbackPromptOptions(),
|
|
4434
|
+
onRunAccepted: (handle: AttemptRunHandle) => {
|
|
4435
|
+
if (handle) this.#acceptSdkAttemptRun(handle);
|
|
4436
|
+
},
|
|
4437
|
+
});
|
|
4438
|
+
}
|
|
4439
|
+
},
|
|
4440
|
+
signal,
|
|
4441
|
+
undefined,
|
|
4442
|
+
{ idleDelivery: true },
|
|
4443
|
+
);
|
|
4187
4444
|
} finally {
|
|
4188
4445
|
// The owned completions were delivered OR the prompt attempt
|
|
4189
4446
|
// failed (e.g. provider rejection): either way the yield
|
|
@@ -4195,16 +4452,19 @@ export class AgentSession {
|
|
|
4195
4452
|
this.#settleDeliveredOwnedRegistrations(survivors);
|
|
4196
4453
|
}
|
|
4197
4454
|
},
|
|
4198
|
-
scheduleIdleFlush: run => {
|
|
4455
|
+
scheduleIdleFlush: (run, onSkip) => {
|
|
4456
|
+
// The startup barrier already gates injectIdle, so begin waiting on a
|
|
4457
|
+
// pending barrier immediately. Once readiness has settled, ordinary
|
|
4458
|
+
// idle wakes retain the fixed merge window.
|
|
4459
|
+
const delayMs = this.#startupTurnBarrierPending ? 0 : FOLD_WAKE_MERGE_WINDOW_MS;
|
|
4199
4460
|
this.#schedulePostPromptTask(
|
|
4200
|
-
async
|
|
4201
|
-
await run();
|
|
4461
|
+
async signal => {
|
|
4462
|
+
await run(signal);
|
|
4202
4463
|
},
|
|
4203
|
-
|
|
4204
|
-
// turn instead of each buying its own.
|
|
4205
|
-
{ delayMs: FOLD_WAKE_MERGE_WINDOW_MS },
|
|
4464
|
+
{ delayMs, onSkip, excludeFromPostPromptRecovery: true },
|
|
4206
4465
|
);
|
|
4207
4466
|
},
|
|
4467
|
+
getIdleFlushSignal: () => this.#postPromptTasksAbortController.signal,
|
|
4208
4468
|
});
|
|
4209
4469
|
this.agent.setOnBeforeYield(() => this.yieldQueue.flush("streaming"));
|
|
4210
4470
|
// Stop-after-result, never abort: a fold arms this once and the loop ends the
|
|
@@ -5337,7 +5597,7 @@ export class AgentSession {
|
|
|
5337
5597
|
#coordinatorToolObservations = new WeakMap<object, CoordinatorToolObservation>();
|
|
5338
5598
|
#agentEventAdmission = new WeakMap<
|
|
5339
5599
|
object,
|
|
5340
|
-
{ scope?: AttemptScope; sdkRunToken?: string; persistGeneration: number }
|
|
5600
|
+
{ scope?: AttemptScope; sdkRunToken?: string; persistGeneration: number; persistBarrier?: Promise<void> }
|
|
5341
5601
|
>();
|
|
5342
5602
|
|
|
5343
5603
|
/**
|
|
@@ -5398,6 +5658,7 @@ export class AgentSession {
|
|
|
5398
5658
|
scope: this.#activeAttemptScope,
|
|
5399
5659
|
sdkRunToken: this.#activeSdkRunToken,
|
|
5400
5660
|
persistGeneration: this.#coordinatorPersistGeneration,
|
|
5661
|
+
persistBarrier: this.#coordinatorRescopeBarrier,
|
|
5401
5662
|
});
|
|
5402
5663
|
// First statement of the listener: the observation must precede every claim,
|
|
5403
5664
|
// reservation, and async hop this handler performs.
|
|
@@ -5507,6 +5768,11 @@ export class AgentSession {
|
|
|
5507
5768
|
this.#resolveSessionSettlement();
|
|
5508
5769
|
}
|
|
5509
5770
|
})();
|
|
5771
|
+
this.#coordinatorEventHandlers.add(handler);
|
|
5772
|
+
void handler.then(
|
|
5773
|
+
() => this.#coordinatorEventHandlers.delete(handler),
|
|
5774
|
+
() => this.#coordinatorEventHandlers.delete(handler),
|
|
5775
|
+
);
|
|
5510
5776
|
if (eventLease) eventLease.track("post_prompt", "agent-session-event", handler);
|
|
5511
5777
|
return handler;
|
|
5512
5778
|
};
|
|
@@ -5530,15 +5796,39 @@ export class AgentSession {
|
|
|
5530
5796
|
message: `${kind} ${outcome.status} for ${correlation}${safeError ? `: ${safeError}` : ""}`,
|
|
5531
5797
|
});
|
|
5532
5798
|
}
|
|
5533
|
-
const
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5799
|
+
const generation = this.#coordinatorPersistGeneration;
|
|
5800
|
+
const barrier = this.#coordinatorRescopeBarrier;
|
|
5801
|
+
const persist = async () => {
|
|
5802
|
+
if (barrier) {
|
|
5803
|
+
await barrier;
|
|
5804
|
+
return await persistCoordinatorWorkerIntegrationOutcome(
|
|
5805
|
+
{
|
|
5806
|
+
sessionId: this.sessionId,
|
|
5807
|
+
cwd: this.sessionManager.getCwd(),
|
|
5808
|
+
sessionFile: this.sessionManager.getSessionFile(),
|
|
5809
|
+
},
|
|
5810
|
+
{
|
|
5811
|
+
...outcome,
|
|
5812
|
+
kind,
|
|
5813
|
+
correlationId,
|
|
5814
|
+
error: sanitizePostPublicationError(outcome.error),
|
|
5815
|
+
},
|
|
5816
|
+
);
|
|
5817
|
+
}
|
|
5818
|
+
return generation === this.#coordinatorPersistGeneration
|
|
5819
|
+
? await persistCoordinatorWorkerIntegrationOutcome(context, {
|
|
5820
|
+
...outcome,
|
|
5821
|
+
kind,
|
|
5822
|
+
correlationId,
|
|
5823
|
+
error: sanitizePostPublicationError(outcome.error),
|
|
5824
|
+
})
|
|
5825
|
+
: undefined;
|
|
5826
|
+
};
|
|
5827
|
+
const queued = barrier
|
|
5828
|
+
? barrier.then(() => this.#appendCoordinatorPersist(persist))
|
|
5829
|
+
: this.#appendCoordinatorPersist(persist);
|
|
5830
|
+
if (barrier) this.#trackReleasedBarrierPersist(queued);
|
|
5831
|
+
else this.#trackUnbarrieredCoordinatorPersist(queued);
|
|
5542
5832
|
void queued.catch(error =>
|
|
5543
5833
|
logger.warn("Failed to persist terminal reconciliation outcome", { error: String(error) }),
|
|
5544
5834
|
);
|
|
@@ -5559,18 +5849,38 @@ export class AgentSession {
|
|
|
5559
5849
|
#queueCoordinatorRuntimeStatePersist(event: AgentSessionEvent, propagateFailure = false): Promise<void> {
|
|
5560
5850
|
if (isNonDispatchedToolEvent(event)) return Promise.resolve();
|
|
5561
5851
|
const observation = this.#coordinatorToolObservations.get(event);
|
|
5852
|
+
const admission = this.#agentEventAdmission.get(event);
|
|
5853
|
+
const barrier = admission?.persistBarrier;
|
|
5854
|
+
if (barrier) {
|
|
5855
|
+
const run = async () => {
|
|
5856
|
+
await barrier;
|
|
5857
|
+
await this.#persistRuntimeStateInBackground(
|
|
5858
|
+
event,
|
|
5859
|
+
{
|
|
5860
|
+
sessionId: this.sessionId,
|
|
5861
|
+
cwd: this.sessionManager.getCwd(),
|
|
5862
|
+
sessionFile: this.sessionManager.getSessionFile(),
|
|
5863
|
+
},
|
|
5864
|
+
observation,
|
|
5865
|
+
propagateFailure,
|
|
5866
|
+
);
|
|
5867
|
+
};
|
|
5868
|
+
const queued = barrier.then(() => this.#appendCoordinatorPersist(run));
|
|
5869
|
+
this.#trackReleasedBarrierPersist(queued);
|
|
5870
|
+
return queued;
|
|
5871
|
+
}
|
|
5562
5872
|
const context = {
|
|
5563
5873
|
sessionId: this.sessionId,
|
|
5564
5874
|
cwd: this.sessionManager.getCwd(),
|
|
5565
5875
|
sessionFile: this.sessionManager.getSessionFile(),
|
|
5566
5876
|
};
|
|
5567
|
-
const generation =
|
|
5877
|
+
const generation = admission?.persistGeneration ?? this.#coordinatorPersistGeneration;
|
|
5568
5878
|
const run = () =>
|
|
5569
5879
|
generation === this.#coordinatorPersistGeneration
|
|
5570
5880
|
? this.#persistRuntimeStateInBackground(event, context, observation, propagateFailure)
|
|
5571
5881
|
: Promise.resolve();
|
|
5572
|
-
const queued = this.#
|
|
5573
|
-
this.#
|
|
5882
|
+
const queued = this.#appendCoordinatorPersist(run);
|
|
5883
|
+
this.#trackUnbarrieredCoordinatorPersist(queued);
|
|
5574
5884
|
return queued;
|
|
5575
5885
|
}
|
|
5576
5886
|
|
|
@@ -6530,9 +6840,11 @@ export class AgentSession {
|
|
|
6530
6840
|
});
|
|
6531
6841
|
}
|
|
6532
6842
|
this.#resolveRetry();
|
|
6843
|
+
if (this.#isDisposed || this.#sessionAdmissionClosing) return;
|
|
6533
6844
|
|
|
6534
6845
|
const compactionTask = this.#schedulePostPromptTask(
|
|
6535
6846
|
async () => {
|
|
6847
|
+
if (this.#isDisposed || this.#sessionAdmissionClosing) return;
|
|
6536
6848
|
await this.#checkCompaction(msg, true, undefined, activePromptHandle);
|
|
6537
6849
|
},
|
|
6538
6850
|
{ resourceRunId: activePromptHandle },
|
|
@@ -6605,9 +6917,11 @@ export class AgentSession {
|
|
|
6605
6917
|
selectionFenceGeneration: number,
|
|
6606
6918
|
lease?: RunResourceProducerLease,
|
|
6607
6919
|
leaseTask: Promise<void> = task,
|
|
6920
|
+
excludeFromRecovery = false,
|
|
6608
6921
|
): void {
|
|
6609
6922
|
this.#postPromptTasks.add(task);
|
|
6610
6923
|
this.#postPromptTaskSelectionFenceGenerations.set(task, selectionFenceGeneration);
|
|
6924
|
+
if (excludeFromRecovery) this.#postPromptTaskRecoveryExcluded.add(task);
|
|
6611
6925
|
this.#ensurePostPromptTasksPromise();
|
|
6612
6926
|
if (lease) {
|
|
6613
6927
|
lease.track("post_prompt", "agent-session", leaseTask);
|
|
@@ -6618,6 +6932,7 @@ export class AgentSession {
|
|
|
6618
6932
|
.finally(() => {
|
|
6619
6933
|
this.#postPromptTasks.delete(task);
|
|
6620
6934
|
this.#postPromptTaskSelectionFenceGenerations.delete(task);
|
|
6935
|
+
this.#postPromptTaskRecoveryExcluded.delete(task);
|
|
6621
6936
|
if (this.#postPromptTasks.size === 0) this.#resolvePostPromptTasks();
|
|
6622
6937
|
});
|
|
6623
6938
|
}
|
|
@@ -6631,6 +6946,7 @@ export class AgentSession {
|
|
|
6631
6946
|
resourceRunId?: string;
|
|
6632
6947
|
leaseTask?: Promise<void>;
|
|
6633
6948
|
selectionFenceGeneration?: number;
|
|
6949
|
+
excludeFromPostPromptRecovery?: boolean;
|
|
6634
6950
|
},
|
|
6635
6951
|
): Promise<void> {
|
|
6636
6952
|
const selectionFenceGeneration =
|
|
@@ -6657,7 +6973,11 @@ export class AgentSession {
|
|
|
6657
6973
|
options?.onSkip?.();
|
|
6658
6974
|
return Promise.resolve();
|
|
6659
6975
|
}
|
|
6660
|
-
const
|
|
6976
|
+
const taskSignal = reservation?.ok ? reservation.lease.signal : this.#postPromptTasksAbortController.signal;
|
|
6977
|
+
// Disposal is a session-wide terminal fence. Compose it with the selected
|
|
6978
|
+
// logical-resource signal so stale leases from earlier runs cannot keep a
|
|
6979
|
+
// post-prompt task alive after the current run was quarantined.
|
|
6980
|
+
const signal = AbortSignal.any([taskSignal, this.#disposeAbortController.signal]);
|
|
6661
6981
|
const runScheduled = async () => {
|
|
6662
6982
|
if (delayMs > 0) {
|
|
6663
6983
|
try {
|
|
@@ -6685,6 +7005,7 @@ export class AgentSession {
|
|
|
6685
7005
|
selectionFenceGeneration,
|
|
6686
7006
|
reservation?.ok ? reservation.lease : undefined,
|
|
6687
7007
|
options?.leaseTask,
|
|
7008
|
+
options?.excludeFromPostPromptRecovery,
|
|
6688
7009
|
);
|
|
6689
7010
|
return scheduled;
|
|
6690
7011
|
}
|
|
@@ -6818,7 +7139,7 @@ export class AgentSession {
|
|
|
6818
7139
|
settleLease();
|
|
6819
7140
|
return;
|
|
6820
7141
|
}
|
|
6821
|
-
await this.#awaitStartupTurnBarrier();
|
|
7142
|
+
await this.#awaitStartupTurnBarrier(scheduledSignal);
|
|
6822
7143
|
if (!canContinue()) {
|
|
6823
7144
|
settleLease();
|
|
6824
7145
|
return;
|
|
@@ -7222,6 +7543,7 @@ export class AgentSession {
|
|
|
7222
7543
|
this.#postPromptTasksAbortController = new AbortController();
|
|
7223
7544
|
this.#postPromptTasks.clear();
|
|
7224
7545
|
this.#postPromptTaskSelectionFenceGenerations.clear();
|
|
7546
|
+
this.#postPromptTaskRecoveryExcluded.clear();
|
|
7225
7547
|
this.#releaseDeferredAgentEndContinuations();
|
|
7226
7548
|
this.#resolveTtsrResume();
|
|
7227
7549
|
this.#resolvePostPromptTasks();
|
|
@@ -7243,8 +7565,11 @@ export class AgentSession {
|
|
|
7243
7565
|
await this.#ttsrResumePromise;
|
|
7244
7566
|
continue;
|
|
7245
7567
|
}
|
|
7246
|
-
|
|
7247
|
-
|
|
7568
|
+
const recoveryTasks = [...this.#postPromptTasks].filter(
|
|
7569
|
+
task => !this.#postPromptTaskRecoveryExcluded.has(task),
|
|
7570
|
+
);
|
|
7571
|
+
if (recoveryTasks.length > 0) {
|
|
7572
|
+
await Promise.allSettled(recoveryTasks);
|
|
7248
7573
|
continue;
|
|
7249
7574
|
}
|
|
7250
7575
|
// Tracked post-prompt tasks cover deferred continuations scheduled from
|
|
@@ -7266,7 +7591,9 @@ export class AgentSession {
|
|
|
7266
7591
|
*/
|
|
7267
7592
|
async #waitForPostPromptTasksBeforeSelectionFence(selectionFenceGeneration: number): Promise<void> {
|
|
7268
7593
|
const precedingTasks = [...this.#postPromptTasks].filter(
|
|
7269
|
-
task =>
|
|
7594
|
+
task =>
|
|
7595
|
+
!this.#postPromptTaskRecoveryExcluded.has(task) &&
|
|
7596
|
+
(this.#postPromptTaskSelectionFenceGenerations.get(task) ?? 0) < selectionFenceGeneration,
|
|
7270
7597
|
);
|
|
7271
7598
|
if (precedingTasks.length > 0) await Promise.allSettled(precedingTasks);
|
|
7272
7599
|
}
|
|
@@ -8388,13 +8715,35 @@ export class AgentSession {
|
|
|
8388
8715
|
if (this.#disposePromise) return this.#disposePromise;
|
|
8389
8716
|
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
8390
8717
|
this.#disposePromise = promise;
|
|
8718
|
+
this.#abortAdmissionEpoch++;
|
|
8719
|
+
this.#isDisposed = true;
|
|
8720
|
+
this.#disposeAbortController.abort();
|
|
8721
|
+
this.#disposeAdmissionClosed = this.#closeSessionAdmission();
|
|
8722
|
+
this.#disposePostPromptDrain = this.#cancelPostPromptTasks();
|
|
8723
|
+
this.#settleDeliveredOwnedRegistrations(this.#pendingNextTurnMessages.map(entry => entry.message));
|
|
8724
|
+
this.#pendingNextTurnMessages = [];
|
|
8725
|
+
this.#scheduledHiddenNextTurnGeneration = undefined;
|
|
8726
|
+
this.#abortActiveMidRunBarriers();
|
|
8727
|
+
this.abortCompaction();
|
|
8728
|
+
this.abortRetry();
|
|
8729
|
+
this.#quarantineAgentRunResources();
|
|
8730
|
+
this.agent.abort();
|
|
8731
|
+
this.agent.setMainAttemptScopeObserver(undefined);
|
|
8732
|
+
this.#disconnectFromAgent();
|
|
8391
8733
|
void this.#dispose().then(resolve, reject);
|
|
8392
8734
|
return promise;
|
|
8393
8735
|
}
|
|
8394
8736
|
|
|
8737
|
+
/** Cancel the active logical run domain, including managed continuations detached from Agent's active attempt. */
|
|
8738
|
+
#quarantineAgentRunResources(): void {
|
|
8739
|
+
const resourceRunId = this.agent.currentManagedLogicalRunId ?? this.agent.activeResourceRunId;
|
|
8740
|
+
if (resourceRunId !== undefined) this.agent.resourceLedger.quarantine(String(resourceRunId));
|
|
8741
|
+
}
|
|
8742
|
+
|
|
8395
8743
|
async #dispose(): Promise<void> {
|
|
8396
|
-
|
|
8397
|
-
|
|
8744
|
+
const admissionClosed = this.#disposeAdmissionClosed ?? this.#closeSessionAdmission();
|
|
8745
|
+
await admissionClosed;
|
|
8746
|
+
await this.sessionManager.closeCwdMoveAdmission();
|
|
8398
8747
|
this.#isDisposed = true;
|
|
8399
8748
|
// Reject new direct Python starts as soon as disposal begins (synchronously,
|
|
8400
8749
|
// before any await) so callers cannot race a start against teardown.
|
|
@@ -8432,18 +8781,22 @@ export class AgentSession {
|
|
|
8432
8781
|
this.agent.forceAbort("Session disposed");
|
|
8433
8782
|
}
|
|
8434
8783
|
}
|
|
8435
|
-
await admissionClosed;
|
|
8436
8784
|
await this.#agentEndPublicationPromise;
|
|
8437
8785
|
await this.#queuedExtensionEvents;
|
|
8786
|
+
await this.#agentEndHandlingPromise;
|
|
8438
8787
|
// Drain the sidecar write order for the same reason the two queues above are
|
|
8439
8788
|
// drained: each entry writes under the native identity-bound state-file lock, so a
|
|
8440
|
-
// still-queued write would run after the session that owns it is gone — releasing
|
|
8789
|
+
// a still-queued write would run after the session that owns it is gone — releasing
|
|
8441
8790
|
// a lock whose owner no longer exists, and under `bun test --isolate` calling into
|
|
8442
8791
|
// the addon after the runtime tore down the context it was scheduled in. The chain
|
|
8443
8792
|
// is already failure-absorbing, so this only waits.
|
|
8444
8793
|
await this.#coordinatorPersistQueue;
|
|
8794
|
+
// Terminal publication records its reconciliation from a promise reaction, which
|
|
8795
|
+
// can append after the queue snapshot above was awaited. Drain the tracked writes
|
|
8796
|
+
// again so disposal cannot return while that late reconciliation still owns a lock.
|
|
8797
|
+
await this.#drainUnbarrieredCoordinatorPersists();
|
|
8445
8798
|
this.#pendingBackgroundExchanges = [];
|
|
8446
|
-
this
|
|
8799
|
+
this.#drainTerminalOwnedYieldEntries();
|
|
8447
8800
|
|
|
8448
8801
|
this.agent.setOnBeforeYield(undefined);
|
|
8449
8802
|
try {
|
|
@@ -8457,12 +8810,25 @@ export class AgentSession {
|
|
|
8457
8810
|
this.#workflowGateEmitter = undefined;
|
|
8458
8811
|
this.#notifyWorkflowGateEmitterChanged(this.sessionId, undefined);
|
|
8459
8812
|
await this.#flushWorkerIntegrationAttempt();
|
|
8460
|
-
|
|
8813
|
+
// Worker integration completion can enqueue terminal reconciliation while the
|
|
8814
|
+
// flush above is pending; drain that late producer before disposal resolves.
|
|
8815
|
+
await this.#coordinatorPersistQueue;
|
|
8816
|
+
await this.#drainUnbarrieredCoordinatorPersists();
|
|
8817
|
+
await (this.#disposePostPromptDrain ?? this.#cancelPostPromptTasks());
|
|
8461
8818
|
// Cancel jobs this agent registered so a subagent's teardown doesn't
|
|
8462
8819
|
// leak its background bash/task work into the parent's manager. Only
|
|
8463
8820
|
// the session that owns the manager goes on to dispose it (which itself
|
|
8464
8821
|
// nukes any leftover jobs and pending deliveries).
|
|
8465
8822
|
this.#cancelOwnAsyncJobs();
|
|
8823
|
+
// Final drain (Codex review P2 on #5088): an async-job completion that
|
|
8824
|
+
// enqueued during the awaited session_shutdown window above survived the
|
|
8825
|
+
// first drain with a retained delivery claim, and the already-aborted
|
|
8826
|
+
// disposal signal makes its idle-flush task exit before YieldQueue.flush
|
|
8827
|
+
// can run. Producers are now cancelled, so drain once more to settle any
|
|
8828
|
+
// straggler envelope and release its claim before owned-manager teardown
|
|
8829
|
+
// — a shared manager would otherwise keep the claim past this session's
|
|
8830
|
+
// disposal.
|
|
8831
|
+
this.#drainTerminalOwnedYieldEntries();
|
|
8466
8832
|
await Promise.allSettled(this.#deferredOwnerShutdownFinalizations);
|
|
8467
8833
|
const ownedAsyncManager = this.#ownedAsyncJobManager;
|
|
8468
8834
|
if (ownedAsyncManager && this.#disposeAsyncJobManager) {
|
|
@@ -8499,6 +8865,14 @@ export class AgentSession {
|
|
|
8499
8865
|
this.#unregisterSessionMemorySettings = undefined;
|
|
8500
8866
|
if (ownerTerminalContextFromEnvironment() === null) this.#unregisterRuntimeStateFinalizer?.();
|
|
8501
8867
|
this.#unregisterRuntimeStateFinalizer = undefined;
|
|
8868
|
+
this.#unregisterBeforeMoveListener?.();
|
|
8869
|
+
this.#unregisterBeforeMoveListener = undefined;
|
|
8870
|
+
this.#unregisterMoveAbortListener?.();
|
|
8871
|
+
this.#unregisterMoveAbortListener = undefined;
|
|
8872
|
+
this.#unregisterMovePublicationListener?.();
|
|
8873
|
+
this.#unregisterMovePublicationListener = undefined;
|
|
8874
|
+
this.#unregisterAfterMoveListener?.();
|
|
8875
|
+
this.#unregisterAfterMoveListener = undefined;
|
|
8502
8876
|
await releaseTabsForOwner(this.sessionManager.getSessionId()).catch((error: unknown) =>
|
|
8503
8877
|
logger.warn("session dispose: releaseTabsForOwner failed", { error }),
|
|
8504
8878
|
);
|
|
@@ -8663,14 +9037,24 @@ export class AgentSession {
|
|
|
8663
9037
|
await this.#waitForPostPromptTasksBeforeSelectionFence(ignoreSelectionFenceGeneration);
|
|
8664
9038
|
}
|
|
8665
9039
|
await this.#waitForSessionSettlement(ignoreSelectionFenceGeneration);
|
|
9040
|
+
const hasBlockingPostPromptTasks =
|
|
9041
|
+
ignoreSelectionFenceGeneration === undefined
|
|
9042
|
+
? this.#postPromptTasks.size > 0
|
|
9043
|
+
: [...this.#postPromptTasks].some(
|
|
9044
|
+
task =>
|
|
9045
|
+
!this.#postPromptTaskRecoveryExcluded.has(task) &&
|
|
9046
|
+
(this.#postPromptTaskSelectionFenceGenerations.get(task) ?? 0) < ignoreSelectionFenceGeneration,
|
|
9047
|
+
);
|
|
8666
9048
|
if (
|
|
8667
9049
|
!this.agent.state.isStreaming &&
|
|
8668
9050
|
!this.#retryPromise &&
|
|
8669
9051
|
!this.#ttsrResumePromise &&
|
|
8670
|
-
!
|
|
9052
|
+
!hasBlockingPostPromptTasks &&
|
|
8671
9053
|
!this.#isSessionSettlementPending(ignoreSelectionFenceGeneration)
|
|
8672
9054
|
)
|
|
8673
9055
|
return;
|
|
9056
|
+
if (ignoreSelectionFenceGeneration === undefined && hasBlockingPostPromptTasks && this.#postPromptTasksPromise)
|
|
9057
|
+
await this.#postPromptTasksPromise;
|
|
8674
9058
|
}
|
|
8675
9059
|
}
|
|
8676
9060
|
|
|
@@ -8695,6 +9079,35 @@ export class AgentSession {
|
|
|
8695
9079
|
await this.sessionManager.flush();
|
|
8696
9080
|
}
|
|
8697
9081
|
|
|
9082
|
+
queueCoordinatorRuntimeStatePersistForTests(event: AgentSessionEvent, gate: Promise<void>): Promise<void> {
|
|
9083
|
+
this.#agentEventAdmission.set(event, {
|
|
9084
|
+
persistGeneration: this.#coordinatorPersistGeneration,
|
|
9085
|
+
persistBarrier: this.#coordinatorRescopeBarrier,
|
|
9086
|
+
});
|
|
9087
|
+
const task = (async () => {
|
|
9088
|
+
await gate;
|
|
9089
|
+
await this.#queueCoordinatorRuntimeStatePersist(event, true);
|
|
9090
|
+
})();
|
|
9091
|
+
this.#coordinatorEventHandlers.add(task);
|
|
9092
|
+
void task.then(
|
|
9093
|
+
() => this.#coordinatorEventHandlers.delete(task),
|
|
9094
|
+
() => this.#coordinatorEventHandlers.delete(task),
|
|
9095
|
+
);
|
|
9096
|
+
return task;
|
|
9097
|
+
}
|
|
9098
|
+
|
|
9099
|
+
parkAgentEndForCoordinatorPersistForTests(event: Extract<AgentSessionEvent, { type: "agent_end" }>): void {
|
|
9100
|
+
this.#agentEventAdmission.set(event, {
|
|
9101
|
+
persistGeneration: this.#coordinatorPersistGeneration,
|
|
9102
|
+
persistBarrier: this.#coordinatorRescopeBarrier,
|
|
9103
|
+
});
|
|
9104
|
+
this.#pendingAgentEndEmit = event;
|
|
9105
|
+
}
|
|
9106
|
+
|
|
9107
|
+
flushParkedAgentEndForCoordinatorPersistForTests(): void {
|
|
9108
|
+
this.#flushPendingAgentEnd();
|
|
9109
|
+
}
|
|
9110
|
+
|
|
8698
9111
|
async drainAsyncJobDeliveriesForAcp(options?: { timeoutMs?: number }): Promise<boolean> {
|
|
8699
9112
|
const manager = this.#ownedAsyncJobManager ?? AsyncJobManager.instance();
|
|
8700
9113
|
if (!manager) return false;
|
|
@@ -10924,6 +11337,19 @@ export class AgentSession {
|
|
|
10924
11337
|
* @throws Error if no model selected or no API key available (when not streaming)
|
|
10925
11338
|
*/
|
|
10926
11339
|
async prompt(text: string, options?: PromptOptions): Promise<void> {
|
|
11340
|
+
const releaseStartupPromptWaiter = this.#reserveStartupPromptWaiter();
|
|
11341
|
+
try {
|
|
11342
|
+
await this.#promptInternal(text, options, releaseStartupPromptWaiter);
|
|
11343
|
+
} finally {
|
|
11344
|
+
releaseStartupPromptWaiter();
|
|
11345
|
+
}
|
|
11346
|
+
}
|
|
11347
|
+
|
|
11348
|
+
async #promptInternal(
|
|
11349
|
+
text: string,
|
|
11350
|
+
options: PromptOptions | undefined,
|
|
11351
|
+
releaseStartupPromptWaiter: () => void,
|
|
11352
|
+
): Promise<void> {
|
|
10927
11353
|
const hasUsableImage =
|
|
10928
11354
|
options?.images?.some(image => typeof image?.data === "string" && image.data.trim().length > 0) === true;
|
|
10929
11355
|
if (typeof text !== "string" || (text.trim().length === 0 && !hasUsableImage))
|
|
@@ -11069,7 +11495,12 @@ export class AgentSession {
|
|
|
11069
11495
|
attribution: promptAttribution,
|
|
11070
11496
|
timestamp: Date.now(),
|
|
11071
11497
|
}
|
|
11072
|
-
: {
|
|
11498
|
+
: {
|
|
11499
|
+
role: "user" as const,
|
|
11500
|
+
content: userContent,
|
|
11501
|
+
attribution: promptAttribution,
|
|
11502
|
+
timestamp: Date.now(),
|
|
11503
|
+
};
|
|
11073
11504
|
if (deepInterviewUserIntentEpoch !== undefined)
|
|
11074
11505
|
this.#deepInterviewGenuineUserMessageEpochs.set(message, deepInterviewUserIntentEpoch);
|
|
11075
11506
|
await this.refreshGjcSubskillTools();
|
|
@@ -11097,6 +11528,8 @@ export class AgentSession {
|
|
|
11097
11528
|
}
|
|
11098
11529
|
},
|
|
11099
11530
|
options?.preflightSignal,
|
|
11531
|
+
undefined,
|
|
11532
|
+
{ startupPromptWaiterRelease: releaseStartupPromptWaiter },
|
|
11100
11533
|
);
|
|
11101
11534
|
}
|
|
11102
11535
|
|
|
@@ -12220,7 +12653,16 @@ export class AgentSession {
|
|
|
12220
12653
|
}
|
|
12221
12654
|
this.#scheduledHiddenNextTurnGeneration = generation;
|
|
12222
12655
|
this.#schedulePostPromptTask(
|
|
12223
|
-
async
|
|
12656
|
+
async signal => {
|
|
12657
|
+
if (signal.aborted || this.#isDisposed || this.#sessionAdmissionClosing) return;
|
|
12658
|
+
if (this.#startupTurnBarrier) {
|
|
12659
|
+
try {
|
|
12660
|
+
await this.#awaitStartupTurnBarrier(signal);
|
|
12661
|
+
} catch {
|
|
12662
|
+
return;
|
|
12663
|
+
}
|
|
12664
|
+
}
|
|
12665
|
+
if (signal.aborted || this.#isDisposed || this.#sessionAdmissionClosing) return;
|
|
12224
12666
|
if (this.#scheduledHiddenNextTurnGeneration === generation) {
|
|
12225
12667
|
this.#scheduledHiddenNextTurnGeneration = undefined;
|
|
12226
12668
|
}
|
|
@@ -12249,8 +12691,12 @@ export class AgentSession {
|
|
|
12249
12691
|
return;
|
|
12250
12692
|
}
|
|
12251
12693
|
try {
|
|
12252
|
-
await this.#promptQueuedHiddenNextTurnMessages();
|
|
12694
|
+
await this.#promptQueuedHiddenNextTurnMessages(signal);
|
|
12253
12695
|
} catch {
|
|
12696
|
+
if (this.#isDisposed || this.#sessionAdmissionClosing || this.#disposeAbortController.signal.aborted) {
|
|
12697
|
+
this.#pendingNextTurnMessages = [];
|
|
12698
|
+
return;
|
|
12699
|
+
}
|
|
12254
12700
|
// Leave the hidden next-turn messages queued for the next explicit prompt.
|
|
12255
12701
|
}
|
|
12256
12702
|
},
|
|
@@ -12265,7 +12711,7 @@ export class AgentSession {
|
|
|
12265
12711
|
);
|
|
12266
12712
|
}
|
|
12267
12713
|
|
|
12268
|
-
async #promptQueuedHiddenNextTurnMessages(): Promise<void> {
|
|
12714
|
+
async #promptQueuedHiddenNextTurnMessages(signal?: AbortSignal): Promise<void> {
|
|
12269
12715
|
if (this.#pendingNextTurnMessages.length === 0) {
|
|
12270
12716
|
return;
|
|
12271
12717
|
}
|
|
@@ -12295,6 +12741,14 @@ export class AgentSession {
|
|
|
12295
12741
|
if (reclassified.length === 0) {
|
|
12296
12742
|
return;
|
|
12297
12743
|
}
|
|
12744
|
+
if (this.#isDisposed || this.#sessionAdmissionClosing || this.#disposeAbortController.signal.aborted) {
|
|
12745
|
+
this.#settleDeliveredOwnedRegistrations(reclassified.map(entry => entry.message));
|
|
12746
|
+
return;
|
|
12747
|
+
}
|
|
12748
|
+
if (signal?.aborted) {
|
|
12749
|
+
this.#pendingNextTurnMessages = [...reclassified, ...this.#pendingNextTurnMessages];
|
|
12750
|
+
return;
|
|
12751
|
+
}
|
|
12298
12752
|
// Allocate a fresh root-turn lineage when the drained batch contains a
|
|
12299
12753
|
// fresh owned envelope OR an EXTERNAL hidden trigger: an external
|
|
12300
12754
|
// nextTurn message has no owned envelope, so without this it would
|
|
@@ -12316,11 +12770,24 @@ export class AgentSession {
|
|
|
12316
12770
|
const textContent = this.#getCustomMessageTextContent(message);
|
|
12317
12771
|
await this.#syncSkillPromptActiveStateSafely(message, true);
|
|
12318
12772
|
try {
|
|
12773
|
+
if (this.#isDisposed || this.#sessionAdmissionClosing || this.#disposeAbortController.signal.aborted) {
|
|
12774
|
+
this.#settleDeliveredOwnedRegistrations(reclassified.map(entry => entry.message));
|
|
12775
|
+
return;
|
|
12776
|
+
}
|
|
12777
|
+
if (signal?.aborted) {
|
|
12778
|
+
this.#pendingNextTurnMessages = [...reclassified, ...this.#pendingNextTurnMessages];
|
|
12779
|
+
return;
|
|
12780
|
+
}
|
|
12319
12781
|
await this.#promptWithMessage(message, textContent, {
|
|
12320
12782
|
prependMessages,
|
|
12321
12783
|
skipPostPromptRecoveryWait: true,
|
|
12784
|
+
preflightSignal: signal,
|
|
12322
12785
|
});
|
|
12323
12786
|
} catch (error) {
|
|
12787
|
+
if (this.#isDisposed || this.#sessionAdmissionClosing || this.#disposeAbortController.signal.aborted) {
|
|
12788
|
+
this.#settleDeliveredOwnedRegistrations(reclassified.map(entry => entry.message));
|
|
12789
|
+
return;
|
|
12790
|
+
}
|
|
12324
12791
|
// Requeue only the SURVIVING reclassified entries: a completion
|
|
12325
12792
|
// denied by scope:"owned" was settled (dropped) above and must
|
|
12326
12793
|
// never reach a later prompt — once its scope and now-unoccupied
|
|
@@ -12347,19 +12814,55 @@ export class AgentSession {
|
|
|
12347
12814
|
// another session's manager (which lacks the same local job id) would
|
|
12348
12815
|
// see job === undefined and remove a still-live registration (review
|
|
12349
12816
|
// thread P1).
|
|
12350
|
-
const manager = this.#ownedAsyncJobManager ?? AsyncJobManager.instance();
|
|
12351
12817
|
for (const message of messages) {
|
|
12352
12818
|
const details = (message as { details?: { ownedCompletions?: OwnedCompletionEnvelope[] } }).details;
|
|
12353
|
-
for (const envelope of details?.ownedCompletions ?? [])
|
|
12354
|
-
|
|
12355
|
-
|
|
12356
|
-
|
|
12357
|
-
|
|
12358
|
-
|
|
12359
|
-
|
|
12819
|
+
for (const envelope of details?.ownedCompletions ?? []) this.#settleOwnedCompletionEnvelope(envelope);
|
|
12820
|
+
}
|
|
12821
|
+
}
|
|
12822
|
+
|
|
12823
|
+
#settleOwnedCompletionEnvelope(envelope: OwnedCompletionEnvelope): void {
|
|
12824
|
+
const manager = this.#ownedAsyncJobManager ?? AsyncJobManager.instance();
|
|
12825
|
+
const job = manager?.getJob(envelope.registration.jobId);
|
|
12826
|
+
const status = job?.generation === envelope.registration.jobGeneration ? job?.status : undefined;
|
|
12827
|
+
// Evicted jobs have no live record (job === undefined); terminal statuses
|
|
12828
|
+
// settle the registration.
|
|
12829
|
+
if (job === undefined || status === "completed" || status === "cancelled" || status === "failed") {
|
|
12830
|
+
unregisterOwnedRegistration(envelope.registration);
|
|
12831
|
+
}
|
|
12832
|
+
}
|
|
12833
|
+
|
|
12834
|
+
/**
|
|
12835
|
+
* Drop every queued yield entry, releasing its retained delivery claim and
|
|
12836
|
+
* settling terminal owned-completion envelopes so neither outlives this
|
|
12837
|
+
* session's disposal. Claims resolve to the manager that actually retained
|
|
12838
|
+
* them: owned entries via their registration endpoint (subagents inherit a
|
|
12839
|
+
* parent's manager, and the process-global instance may belong to a
|
|
12840
|
+
* different concurrent session), ordinary entries only on the session-OWNED
|
|
12841
|
+
* manager. Disposal runs this twice: before the awaited session_shutdown
|
|
12842
|
+
* emit, and again after producer cancellation — a completion enqueued
|
|
12843
|
+
* inside that window would otherwise survive disposal, its claim retained
|
|
12844
|
+
* on a shared manager and interfering with later delivery teardown (Codex
|
|
12845
|
+
* review P2 on #5088).
|
|
12846
|
+
*/
|
|
12847
|
+
#drainTerminalOwnedYieldEntries(): void {
|
|
12848
|
+
this.yieldQueue.clear((kind, entries) => {
|
|
12849
|
+
if (kind !== "async-result") return;
|
|
12850
|
+
const ownedManager = this.#ownedAsyncJobManager;
|
|
12851
|
+
for (const entry of entries) {
|
|
12852
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
12853
|
+
const record = entry as { generation?: unknown; ownedCompletion?: unknown };
|
|
12854
|
+
if (typeof record.generation === "string" && record.generation !== "") {
|
|
12855
|
+
const envelope = isOwnedCompletionEnvelope(record.ownedCompletion) ? record.ownedCompletion : undefined;
|
|
12856
|
+
const endpointId = envelope?.registration.endpointId;
|
|
12857
|
+
const manager =
|
|
12858
|
+
(endpointId !== undefined ? AsyncJobManager.forEndpoint(endpointId) : undefined) ?? ownedManager;
|
|
12859
|
+
manager?.releaseDeliveryClaim(record.generation);
|
|
12860
|
+
}
|
|
12861
|
+
if (isOwnedCompletionEnvelope(record.ownedCompletion)) {
|
|
12862
|
+
this.#settleOwnedCompletionEnvelope(record.ownedCompletion);
|
|
12360
12863
|
}
|
|
12361
12864
|
}
|
|
12362
|
-
}
|
|
12865
|
+
});
|
|
12363
12866
|
}
|
|
12364
12867
|
|
|
12365
12868
|
#getCustomMessageTextContent(message: Pick<CustomMessage, "content">): string {
|
|
@@ -13415,12 +13918,14 @@ export class AgentSession {
|
|
|
13415
13918
|
this.#promptPreflightCancellationGeneration++;
|
|
13416
13919
|
this.#promptPreflightAbortController.abort();
|
|
13417
13920
|
this.#promptPreflightAbortController = new AbortController();
|
|
13921
|
+
this.#drainTerminalOwnedYieldEntries();
|
|
13922
|
+
const overlappingPostPromptDrain = this.#cancelPostPromptTasks();
|
|
13418
13923
|
// Abort visibility is per-request: a later real abort must not inherit an
|
|
13419
13924
|
// earlier silent abort's suppression and swallow the user-visible notice.
|
|
13420
13925
|
if (options?.silent !== true) this.#silentAbortPending = false;
|
|
13421
13926
|
// Capture the unwind: the field clears once the first abort settles, and
|
|
13422
13927
|
// the awaits below must keep watching THIS unwind, not a successor's.
|
|
13423
|
-
const sharedUnwind = this.#abortUnwind;
|
|
13928
|
+
const sharedUnwind = Promise.all([this.#abortUnwind, overlappingPostPromptDrain]).then(() => {});
|
|
13424
13929
|
if (options?.timeoutMs !== undefined) {
|
|
13425
13930
|
const timeoutMs = Math.max(0, options.timeoutMs);
|
|
13426
13931
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -13451,6 +13956,7 @@ export class AgentSession {
|
|
|
13451
13956
|
this.#abortUnwind = unwind.promise;
|
|
13452
13957
|
try {
|
|
13453
13958
|
this.#abortOptions(options);
|
|
13959
|
+
this.#drainTerminalOwnedYieldEntries();
|
|
13454
13960
|
const postPromptDrain = this.#cancelPostPromptTasks();
|
|
13455
13961
|
const managedLogicalRunId =
|
|
13456
13962
|
this.#defaultFallbackChain().chain.entries.length > 1 ? this.agent.currentManagedLogicalRunId : undefined;
|
|
@@ -19493,11 +19999,15 @@ export class AgentSession {
|
|
|
19493
19999
|
}
|
|
19494
20000
|
|
|
19495
20001
|
async #handleManagedAttemptOutcome(outcome: ManagedAttemptOutcome): Promise<ManagedAttemptDecision> {
|
|
20002
|
+
const attemptAbortEpoch = this.#abortAdmissionEpoch;
|
|
20003
|
+
const attemptCancelled = () =>
|
|
20004
|
+
this.#isDisposed || this.#sessionAdmissionClosing || this.#abortAdmissionEpoch !== attemptAbortEpoch;
|
|
19496
20005
|
const activePromptHandle = this.activePromptHandle;
|
|
19497
20006
|
const cancellationSignal = activePromptHandle
|
|
19498
20007
|
? this.#runCancellationDomains.lookup(activePromptHandle)?.signal
|
|
19499
20008
|
: undefined;
|
|
19500
|
-
if (cancellationSignal?.aborted)
|
|
20009
|
+
if (attemptCancelled() || cancellationSignal?.aborted)
|
|
20010
|
+
return { type: "terminal", terminal: { stopReason: "cancelled" } };
|
|
19501
20011
|
if (outcome.type === "run_terminal") {
|
|
19502
20012
|
this.#defaultFallbackChain().resetAttemptBudget();
|
|
19503
20013
|
return { type: "terminal", terminal: { stopReason: outcome.reason } };
|
|
@@ -19533,7 +20043,6 @@ export class AgentSession {
|
|
|
19533
20043
|
const controllerStateBeforeFailure = controller.snapshotRuntimeState();
|
|
19534
20044
|
const advanced = controller.recordEscapedArgumentsFailure(errorMessage, false);
|
|
19535
20045
|
this.#escapedNonAsciiManagedRetries = 0;
|
|
19536
|
-
const abortEpoch = this.#abortAdmissionEpoch;
|
|
19537
20046
|
if (advanced) {
|
|
19538
20047
|
const transitionGeneration = ++this.#fallbackTransitionGeneration;
|
|
19539
20048
|
return {
|
|
@@ -19554,7 +20063,9 @@ export class AgentSession {
|
|
|
19554
20063
|
!ownership.isCurrent() ||
|
|
19555
20064
|
ownership.lease.signal.aborted ||
|
|
19556
20065
|
cancellationSignal?.aborted ||
|
|
19557
|
-
this.#
|
|
20066
|
+
this.#isDisposed ||
|
|
20067
|
+
this.#sessionAdmissionClosing ||
|
|
20068
|
+
this.#abortAdmissionEpoch !== attemptAbortEpoch ||
|
|
19558
20069
|
this.#fallbackTransitionGeneration !== transitionGeneration;
|
|
19559
20070
|
if (continuationCancelled) {
|
|
19560
20071
|
await restoreOwnedTransition();
|
|
@@ -19579,7 +20090,7 @@ export class AgentSession {
|
|
|
19579
20090
|
"escaped_non_ascii",
|
|
19580
20091
|
1,
|
|
19581
20092
|
cancellationSignal,
|
|
19582
|
-
|
|
20093
|
+
attemptAbortEpoch,
|
|
19583
20094
|
controllerStateBeforeFailure,
|
|
19584
20095
|
previousModel,
|
|
19585
20096
|
previousThinkingLevel,
|
|
@@ -19588,7 +20099,9 @@ export class AgentSession {
|
|
|
19588
20099
|
);
|
|
19589
20100
|
if (!switched) {
|
|
19590
20101
|
if (
|
|
19591
|
-
this.#
|
|
20102
|
+
this.#isDisposed ||
|
|
20103
|
+
this.#sessionAdmissionClosing ||
|
|
20104
|
+
this.#abortAdmissionEpoch !== attemptAbortEpoch ||
|
|
19592
20105
|
cancellationSignal?.aborted ||
|
|
19593
20106
|
this.#fallbackTransitionGeneration !== transitionGeneration
|
|
19594
20107
|
) {
|
|
@@ -19608,6 +20121,15 @@ export class AgentSession {
|
|
|
19608
20121
|
});
|
|
19609
20122
|
return;
|
|
19610
20123
|
}
|
|
20124
|
+
if (
|
|
20125
|
+
attemptCancelled() ||
|
|
20126
|
+
!ownership.isCurrent() ||
|
|
20127
|
+
ownership.lease.signal.aborted ||
|
|
20128
|
+
this.#fallbackTransitionGeneration !== transitionGeneration
|
|
20129
|
+
) {
|
|
20130
|
+
await restoreOwnedTransition();
|
|
20131
|
+
return;
|
|
20132
|
+
}
|
|
19611
20133
|
const continuation = this.agent.continue({
|
|
19612
20134
|
...this.#managedFallbackPromptOptions(),
|
|
19613
20135
|
transientRecoveryMessage: this.#escapedNonAsciiRecoveryMessage(),
|
|
@@ -19617,7 +20139,9 @@ export class AgentSession {
|
|
|
19617
20139
|
});
|
|
19618
20140
|
if (
|
|
19619
20141
|
cancellationSignal?.aborted ||
|
|
19620
|
-
this.#
|
|
20142
|
+
this.#isDisposed ||
|
|
20143
|
+
this.#sessionAdmissionClosing ||
|
|
20144
|
+
this.#abortAdmissionEpoch !== attemptAbortEpoch ||
|
|
19621
20145
|
this.#fallbackTransitionGeneration !== transitionGeneration
|
|
19622
20146
|
) {
|
|
19623
20147
|
await restoreOwnedTransition();
|
|
@@ -19636,7 +20160,7 @@ export class AgentSession {
|
|
|
19636
20160
|
};
|
|
19637
20161
|
}
|
|
19638
20162
|
controller.advance();
|
|
19639
|
-
if (
|
|
20163
|
+
if (attemptCancelled() || cancellationSignal?.aborted)
|
|
19640
20164
|
return { type: "terminal", terminal: { stopReason: "cancelled" } };
|
|
19641
20165
|
this.#defaultFallbackExhaustedLastTurn = true;
|
|
19642
20166
|
return this.#managedFallbackExhaustionDecision(
|
|
@@ -19648,7 +20172,7 @@ export class AgentSession {
|
|
|
19648
20172
|
return {
|
|
19649
20173
|
type: "retry",
|
|
19650
20174
|
continuation: async ownership => {
|
|
19651
|
-
if (!ownership.isCurrent() || ownership.lease.signal.aborted) return;
|
|
20175
|
+
if (attemptCancelled() || !ownership.isCurrent() || ownership.lease.signal.aborted) return;
|
|
19652
20176
|
await this.agent.continue({
|
|
19653
20177
|
...this.#managedFallbackPromptOptions(),
|
|
19654
20178
|
transientRecoveryMessage: this.#escapedNonAsciiRecoveryMessage(),
|
|
@@ -19667,7 +20191,7 @@ export class AgentSession {
|
|
|
19667
20191
|
return {
|
|
19668
20192
|
type: "maintenance",
|
|
19669
20193
|
continuation: async ownership => {
|
|
19670
|
-
if (!ownership.isCurrent() || ownership.lease.signal.aborted) return;
|
|
20194
|
+
if (attemptCancelled() || !ownership.isCurrent() || ownership.lease.signal.aborted) return;
|
|
19671
20195
|
const resourceRunId = String(ownership.logicalRunId);
|
|
19672
20196
|
const previousLease = this.#postPromptLeases.get(resourceRunId);
|
|
19673
20197
|
this.#postPromptLeases.set(resourceRunId, ownership.lease);
|
|
@@ -19686,7 +20210,13 @@ export class AgentSession {
|
|
|
19686
20210
|
resourceRunId,
|
|
19687
20211
|
ownership.lease.signal,
|
|
19688
20212
|
);
|
|
19689
|
-
if (
|
|
20213
|
+
if (
|
|
20214
|
+
terminalized ||
|
|
20215
|
+
successorScheduled ||
|
|
20216
|
+
attemptCancelled() ||
|
|
20217
|
+
!ownership.isCurrent() ||
|
|
20218
|
+
ownership.lease.signal.aborted
|
|
20219
|
+
)
|
|
19690
20220
|
return;
|
|
19691
20221
|
this.agent.requestRunTerminal(ownership.handle.logicalRunId, {
|
|
19692
20222
|
stopReason: "error",
|
|
@@ -20129,6 +20659,9 @@ export class AgentSession {
|
|
|
20129
20659
|
scope?: AttemptScope,
|
|
20130
20660
|
scopeWasClean = this.#isRetryScopeClean(scope),
|
|
20131
20661
|
): Promise<boolean | ManagedAttemptDecision> {
|
|
20662
|
+
const retryAbortEpoch = this.#abortAdmissionEpoch;
|
|
20663
|
+
const retryCancelled = () =>
|
|
20664
|
+
this.#isDisposed || this.#sessionAdmissionClosing || this.#abortAdmissionEpoch !== retryAbortEpoch;
|
|
20132
20665
|
const controller = this.#defaultFallbackChain();
|
|
20133
20666
|
const managedFallback = controller.chain.entries.length > 1;
|
|
20134
20667
|
const retrySettings = this.settings.getGroup("retry");
|
|
@@ -20140,6 +20673,9 @@ export class AgentSession {
|
|
|
20140
20673
|
const classification = this.#classifyErrorForRetry(message);
|
|
20141
20674
|
const localSnapshot = classification === "local_snapshot";
|
|
20142
20675
|
const localBufferOverflow = classification === "local_buffer_overflow";
|
|
20676
|
+
if (retryCancelled()) {
|
|
20677
|
+
return managedOutcome ? { type: "terminal", terminal: { stopReason: "cancelled" } } : false;
|
|
20678
|
+
}
|
|
20143
20679
|
// A local machinery failure must never stay charged against the provider
|
|
20144
20680
|
// fallback budget, no matter which local exit follows (disabled retry,
|
|
20145
20681
|
// visible-content surface, bounded retry, exhaustion, or the immediate
|
|
@@ -20402,7 +20938,7 @@ export class AgentSession {
|
|
|
20402
20938
|
ownership?.domain.signal ??
|
|
20403
20939
|
(activePromptHandle ? this.#runCancellationDomains.lookup(activePromptHandle)?.signal : undefined);
|
|
20404
20940
|
if (ownership && (!ownership.isCurrent() || cancellationSignal?.aborted)) return;
|
|
20405
|
-
|
|
20941
|
+
if (retryCancelled()) return;
|
|
20406
20942
|
let quotaPoolExhausted = false;
|
|
20407
20943
|
if (managedFallback && !credentialRotated && !providerRetryCeilingReached) {
|
|
20408
20944
|
const mark = await this.#markFailedCredential(trigger);
|
|
@@ -20436,7 +20972,7 @@ export class AgentSession {
|
|
|
20436
20972
|
trigger.class,
|
|
20437
20973
|
attemptsUsed,
|
|
20438
20974
|
cancellationSignal,
|
|
20439
|
-
|
|
20975
|
+
retryAbortEpoch,
|
|
20440
20976
|
controllerStateBeforeAdvance,
|
|
20441
20977
|
previousModel,
|
|
20442
20978
|
previousThinkingLevel,
|
|
@@ -20446,7 +20982,7 @@ export class AgentSession {
|
|
|
20446
20982
|
}
|
|
20447
20983
|
}
|
|
20448
20984
|
if (ownership && (!ownership.isCurrent() || cancellationSignal?.aborted)) {
|
|
20449
|
-
if (
|
|
20985
|
+
if (!retryCancelled()) {
|
|
20450
20986
|
await this.#restoreDefaultFallbackTransition(
|
|
20451
20987
|
controller,
|
|
20452
20988
|
controllerStateBeforeAdvance,
|
|
@@ -20456,7 +20992,7 @@ export class AgentSession {
|
|
|
20456
20992
|
}
|
|
20457
20993
|
return;
|
|
20458
20994
|
}
|
|
20459
|
-
if (
|
|
20995
|
+
if (retryCancelled()) {
|
|
20460
20996
|
controller.resetForNewTurn();
|
|
20461
20997
|
return;
|
|
20462
20998
|
}
|
|
@@ -20585,8 +21121,9 @@ export class AgentSession {
|
|
|
20585
21121
|
|
|
20586
21122
|
if (managedOutcome) {
|
|
20587
21123
|
try {
|
|
21124
|
+
if (retryCancelled() || ownershipCancelled()) return;
|
|
20588
21125
|
await this.#checkEstimatedContextBeforePrompt();
|
|
20589
|
-
if (ownershipCancelled()) {
|
|
21126
|
+
if (retryCancelled() || ownershipCancelled()) {
|
|
20590
21127
|
const attempt = this.#retryAttempt;
|
|
20591
21128
|
this.#retryAttempt = 0;
|
|
20592
21129
|
await this.#emitSessionEvent({
|