@gajae-code/coding-agent 0.15.4 → 0.15.6
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 +46 -2
- package/dist/types/async/job-manager.d.ts +83 -0
- package/dist/types/cli/auth-gateway-cli.d.ts +17 -0
- package/dist/types/cli/model-presets-cli.d.ts +9 -0
- package/dist/types/cli/update-cli.d.ts +27 -5
- package/dist/types/commands/auth-gateway.d.ts +3 -0
- package/dist/types/commands/gc.d.ts +13 -0
- package/dist/types/commands/model-presets.d.ts +21 -0
- package/dist/types/config/autorouting-tier-map.d.ts +99 -0
- package/dist/types/config/file-lock.d.ts +49 -2
- package/dist/types/config/internal/model-preset-registry-test-state.d.ts +4 -0
- package/dist/types/config/internal/model-preset-registry-test-support.d.ts +3 -0
- package/dist/types/config/keybindings.d.ts +3 -2
- package/dist/types/config/model-preset-registry.d.ts +368 -0
- package/dist/types/config/model-profile-activation.d.ts +39 -0
- package/dist/types/config/model-profiles.d.ts +5 -3
- package/dist/types/config/model-registry.d.ts +11 -5
- package/dist/types/config/settings-schema.d.ts +9 -0
- package/dist/types/coordinator-mcp/model-preset.d.ts +7 -5
- package/dist/types/coordinator-mcp/projection-scan.d.ts +46 -0
- package/dist/types/extensibility/extensions/types.d.ts +4 -4
- package/dist/types/extensibility/shared-events.d.ts +2 -0
- package/dist/types/gjc-runtime/empty-delete-gc.d.ts +58 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +2 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +35 -6
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +6 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/main.d.ts +3 -1
- package/dist/types/modes/components/jobs-overlay-model.d.ts +4 -2
- package/dist/types/modes/jobs-observer.d.ts +36 -0
- package/dist/types/sdk/broker/broker.d.ts +3 -0
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +3 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +4 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +8 -1
- package/dist/types/sdk/cli/session-cli.d.ts +42 -1
- package/dist/types/sdk/client/discovery.d.ts +1 -0
- package/dist/types/sdk/host/host.d.ts +2 -0
- package/dist/types/sdk/host/model-pin.d.ts +7 -2
- package/dist/types/sdk/host/sdk-run-capability.d.ts +1 -0
- package/dist/types/sdk/host/session-runtime.d.ts +19 -1
- package/dist/types/sdk/prompt-deadline-manager.d.ts +11 -1
- package/dist/types/sdk/protocol/adapter-validation.d.ts +2 -0
- package/dist/types/sdk/router/session-router.d.ts +9 -0
- package/dist/types/session/account-inventory.d.ts +1 -0
- package/dist/types/session/agent-session.d.ts +19 -5
- package/dist/types/session/fold-coordinator.d.ts +201 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +4 -3
- package/dist/types/session/sdk-run-capability-internal.d.ts +7 -0
- package/dist/types/session/sdk-run-capability.d.ts +2 -0
- package/dist/types/telemetry/control.d.ts +11 -0
- package/dist/types/telemetry/events.d.ts +20 -0
- package/dist/types/telemetry/index.d.ts +3 -0
- package/dist/types/telemetry/transport.d.ts +22 -0
- package/dist/types/tools/bash-interactive.d.ts +23 -0
- package/dist/types/tools/index.d.ts +6 -5
- package/dist/types/tools/path-utils.d.ts +5 -6
- package/dist/types/tools/tmux-self-injection-guard.d.ts +15 -0
- package/dist/types/web/search/providers/base.d.ts +9 -4
- package/dist/types/web/search/providers/xai.d.ts +2 -1
- package/dist/types/web/search/types.d.ts +14 -0
- package/package.json +10 -7
- package/scripts/generate-hotkeys-docs.ts +4 -0
- package/scripts/generate-sdk-operation-inventory.ts +3 -3
- package/src/async/job-manager.ts +671 -39
- package/src/cli/auth-broker-cli.ts +40 -12
- package/src/cli/auth-gateway-cli.ts +273 -97
- package/src/cli/config-cli.ts +2 -2
- package/src/cli/fast-help.ts +3 -0
- package/src/cli/model-presets-cli.ts +100 -0
- package/src/cli/update-cli.ts +142 -6
- package/src/cli.ts +6 -2
- package/src/commands/auth-gateway.ts +12 -8
- package/src/commands/gc.ts +19 -0
- package/src/commands/model-presets.ts +24 -0
- package/src/commands/sdk.ts +5 -0
- package/src/config/autorouting-tier-map.ts +33 -0
- package/src/config/file-lock-gc.ts +9 -4
- package/src/config/file-lock.ts +775 -59
- package/src/config/internal/model-preset-registry-test-state.ts +28 -0
- package/src/config/internal/model-preset-registry-test-support.ts +11 -0
- package/src/config/keybindings.ts +6 -2
- package/src/config/model-preset-registry.ts +2799 -0
- package/src/config/model-profile-activation.ts +115 -28
- package/src/config/model-profile-contract.ts +4 -1
- package/src/config/model-profiles.ts +56 -14
- package/src/config/model-registry.ts +1028 -123
- package/src/config/settings-schema.ts +11 -0
- package/src/config/settings.ts +6 -1
- package/src/coordinator-mcp/model-preset.ts +32 -17
- package/src/coordinator-mcp/policy.ts +1 -1
- package/src/coordinator-mcp/projection-scan.ts +571 -0
- package/src/coordinator-mcp/server.ts +19 -24
- package/src/coordinator-mcp/session-reaper.ts +9 -3
- package/src/extensibility/extensions/loader.ts +0 -1
- package/src/extensibility/extensions/types.ts +4 -2
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +1 -0
- package/src/extensibility/shared-events.ts +2 -0
- package/src/gjc-runtime/empty-delete-gc.ts +301 -0
- package/src/gjc-runtime/gc-render.ts +29 -0
- package/src/gjc-runtime/gc-runtime.ts +174 -11
- package/src/gjc-runtime/launch-worktree.ts +44 -7
- package/src/gjc-runtime/ralplan-runtime.ts +14 -1
- package/src/gjc-runtime/session-state-lock.ts +1242 -141
- package/src/gjc-runtime/session-state-sidecar.ts +76 -3
- package/src/gjc-runtime/tmux-sessions.ts +145 -48
- package/src/index.ts +1 -0
- package/src/internal-urls/docs-index.generated.ts +14 -13
- package/src/lsp/config.ts +4 -3
- package/src/lsp/path-trust.ts +12 -6
- package/src/main.ts +9 -2
- package/src/modes/acp/acp-agent.ts +11 -5
- package/src/modes/components/jobs-overlay-model.ts +99 -16
- package/src/modes/components/model-selector.ts +158 -18
- package/src/modes/components/status-line/segments.ts +6 -1
- package/src/modes/controllers/extension-ui-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +23 -6
- package/src/modes/interactive-mode.ts +14 -1
- package/src/modes/jobs-observer.ts +149 -16
- package/src/modes/prompt-action-autocomplete.ts +26 -47
- package/src/modes/runtime-init.ts +1 -1
- package/src/modes/utils/injected-user-submission.ts +15 -1
- package/src/prompts/tools/bash.md +2 -2
- package/src/prompts/tools/fold-receipt.md +3 -0
- package/src/prompts/tools/job.md +1 -1
- package/src/sdk/acp/adapter.ts +17 -2
- package/src/sdk/broker/broker.ts +48 -21
- package/src/sdk/broker/lifecycle.ts +32 -6
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +11 -1
- package/src/sdk/bus/kind-aware-reconciliation.ts +46 -8
- package/src/sdk/bus/prompt-reconciliation.ts +33 -6
- package/src/sdk/bus/slack-conversation.ts +5 -1
- package/src/sdk/bus/telegram-daemon-contract.ts +8 -1
- package/src/sdk/cli/session-cli.ts +161 -5
- package/src/sdk/client/discovery.ts +3 -1
- package/src/sdk/host/control/dispatch.ts +3 -0
- package/src/sdk/host/host.ts +7 -0
- package/src/sdk/host/model-pin.ts +185 -25
- package/src/sdk/host/sdk-run-capability.ts +1 -0
- package/src/sdk/host/session-runtime.test.ts +1240 -107
- package/src/sdk/host/session-runtime.ts +1029 -115
- package/src/sdk/prompt-deadline-manager.ts +16 -1
- package/src/sdk/protocol/adapter-validation.ts +27 -0
- package/src/sdk/protocol/operation-inventory.generated.json +2 -2
- package/src/sdk/protocol/operation-registry.ts +4 -1
- package/src/sdk/router/session-router.ts +297 -111
- package/src/sdk/session.ts +83 -12
- package/src/session/account-inventory.ts +27 -11
- package/src/session/agent-session.ts +887 -95
- package/src/session/fold-coordinator.ts +433 -0
- package/src/session/internal/managed-session-storage.ts +64 -26
- package/src/session/sdk-run-capability-internal.ts +15 -0
- package/src/session/sdk-run-capability.ts +2 -0
- package/src/session/session-manager.ts +29 -10
- package/src/setup/provider-presets.json +1 -1
- package/src/slash-commands/helpers/credential-switch.ts +6 -1
- package/src/task/executor.ts +19 -3
- package/src/telemetry/control.ts +26 -0
- package/src/telemetry/events.ts +142 -0
- package/src/telemetry/index.ts +3 -0
- package/src/telemetry/transport.ts +87 -0
- package/src/tools/bash-interactive.ts +182 -85
- package/src/tools/bash.ts +786 -67
- package/src/tools/index.ts +7 -5
- package/src/tools/path-utils.ts +77 -29
- package/src/tools/tmux-self-injection-guard.ts +359 -0
- package/src/tools/tool-catalog.generated.ts +1 -1
- package/src/web/search/provider.ts +120 -11
- package/src/web/search/providers/anthropic.ts +89 -17
- package/src/web/search/providers/base.ts +9 -4
- package/src/web/search/providers/gemini.ts +87 -15
- package/src/web/search/providers/openai-compatible.ts +23 -8
- package/src/web/search/providers/xai.ts +98 -12
- package/src/web/search/types.ts +16 -0
|
@@ -117,6 +117,7 @@ import {
|
|
|
117
117
|
modelsAreEqual,
|
|
118
118
|
streamSimple,
|
|
119
119
|
} from "@gajae-code/ai/core";
|
|
120
|
+
import { normalizeAnthropicBaseUrl } from "@gajae-code/ai/providers/anthropic";
|
|
120
121
|
import {
|
|
121
122
|
type AuthDisposition,
|
|
122
123
|
beginAttempt,
|
|
@@ -124,6 +125,7 @@ import {
|
|
|
124
125
|
EMPTY_RESPONSE_PROVIDER_CODE,
|
|
125
126
|
type FallbackAttemptToken,
|
|
126
127
|
type FallbackTriggerClass,
|
|
128
|
+
SERVER_OVERLOADED_PROVIDER_CODE,
|
|
127
129
|
STREAM_FIRST_EVENT_TIMEOUT_PROVIDER_CODE,
|
|
128
130
|
} from "@gajae-code/ai/utils/fallback-transport";
|
|
129
131
|
import { AttemptRecordStore } from "./attempt-record-store";
|
|
@@ -180,8 +182,10 @@ export interface ForkContextSeedOptions {
|
|
|
180
182
|
}
|
|
181
183
|
|
|
182
184
|
import type { AuthCredentialSelector } from "@gajae-code/ai/core";
|
|
185
|
+
import { isFoundryEnabled } from "@gajae-code/ai/utils/foundry";
|
|
183
186
|
import type { MacOSPowerAssertion } from "@gajae-code/natives";
|
|
184
187
|
import {
|
|
188
|
+
$pickCredentialEnv,
|
|
185
189
|
extractRetryHint,
|
|
186
190
|
hasFsCode,
|
|
187
191
|
isEacces,
|
|
@@ -313,6 +317,7 @@ import {
|
|
|
313
317
|
type CoordinatorToolObservation,
|
|
314
318
|
ownerTerminalContextFromEnvironment,
|
|
315
319
|
persistCoordinatorRuntimeStateFromEvent,
|
|
320
|
+
persistCoordinatorWorkerIntegrationOutcome,
|
|
316
321
|
registerCoordinatorRuntimeStateFinalizer,
|
|
317
322
|
UNPROVEN_TOOL_LABEL,
|
|
318
323
|
} from "../gjc-runtime/session-state-sidecar";
|
|
@@ -442,6 +447,7 @@ import {
|
|
|
442
447
|
} from "./contribution-prep";
|
|
443
448
|
import { canonicalCoordinatorToolLabel } from "./coordinator-tool-label";
|
|
444
449
|
import { pruneStaleFileMentions } from "./file-mention-pruning";
|
|
450
|
+
import { describeFoldReceipt, FOLD_WAKE_MERGE_WINDOW_MS, type FoldAdapter, FoldCoordinator } from "./fold-coordinator";
|
|
445
451
|
import type { MemoryGuardRestoreResult } from "./memory-guard-checkpoint-participant";
|
|
446
452
|
import {
|
|
447
453
|
type BashExecutionMessage,
|
|
@@ -456,6 +462,7 @@ import {
|
|
|
456
462
|
SKILL_PROMPT_MESSAGE_TYPE,
|
|
457
463
|
} from "./messages";
|
|
458
464
|
import { isLegacyProviderSafetyStopMessage } from "./provider-safety-stop";
|
|
465
|
+
import { readSdkRunCapability } from "./sdk-run-capability";
|
|
459
466
|
import { formatSessionDumpText } from "./session-dump-format";
|
|
460
467
|
import type {
|
|
461
468
|
BranchSummaryEntry,
|
|
@@ -470,7 +477,6 @@ import type {
|
|
|
470
477
|
SessionManagerCloseOutcome,
|
|
471
478
|
SessionMemoryStats,
|
|
472
479
|
} from "./session-manager";
|
|
473
|
-
|
|
474
480
|
import {
|
|
475
481
|
createReadonlySessionManager,
|
|
476
482
|
getLatestCompactionEntry,
|
|
@@ -487,6 +493,8 @@ import {
|
|
|
487
493
|
bindToolLineage,
|
|
488
494
|
classifyOwnedEnvelope,
|
|
489
495
|
isOwnedCompletionEnvelope,
|
|
496
|
+
isOwnedCompletionEnvelopeAllowed,
|
|
497
|
+
lookupOwnedRegistration,
|
|
490
498
|
lookupTerminalScope,
|
|
491
499
|
mintTurnLineageIdHash,
|
|
492
500
|
type OwnedCompletionEnvelope,
|
|
@@ -495,11 +503,35 @@ import {
|
|
|
495
503
|
settleToolLineageRegistrationWindow,
|
|
496
504
|
unregisterOwnedRegistration,
|
|
497
505
|
} from "./terminal-abort";
|
|
498
|
-
|
|
499
506
|
import { ToolChoiceQueue } from "./tool-choice-queue";
|
|
500
507
|
import { pruneSupersededMaintenanceReminders, pruneSupersededVolatileProjectContext } from "./volatile-context-pruning";
|
|
501
508
|
import { YieldQueue } from "./yield-queue";
|
|
502
509
|
|
|
510
|
+
const ASYNC_INLINE_RESULT_MAX_CHARS = 12_000;
|
|
511
|
+
const ASYNC_PREVIEW_MAX_CHARS = 4_000;
|
|
512
|
+
|
|
513
|
+
async function formatParkedAsyncResult(
|
|
514
|
+
sessionManager: SessionManager,
|
|
515
|
+
result: string,
|
|
516
|
+
allowArtifact = true,
|
|
517
|
+
): Promise<string> {
|
|
518
|
+
if (result.length <= ASYNC_INLINE_RESULT_MAX_CHARS) return result;
|
|
519
|
+
const preview = `${result.slice(0, ASYNC_PREVIEW_MAX_CHARS)}\n\n[Output truncated. Showing first ${ASYNC_PREVIEW_MAX_CHARS.toLocaleString()} characters.]`;
|
|
520
|
+
try {
|
|
521
|
+
if (!allowArtifact) return preview;
|
|
522
|
+
const { path: artifactPath, id: artifactId } = await sessionManager.allocateArtifactPath("async");
|
|
523
|
+
if (artifactPath && artifactId) {
|
|
524
|
+
await Bun.write(artifactPath, result);
|
|
525
|
+
return `${preview}\nFull output: artifact://${artifactId}`;
|
|
526
|
+
}
|
|
527
|
+
} catch (error) {
|
|
528
|
+
logger.warn("Failed to persist parked async follow-up artifact", {
|
|
529
|
+
error: error instanceof Error ? error.message : String(error),
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
return preview;
|
|
533
|
+
}
|
|
534
|
+
|
|
503
535
|
/**
|
|
504
536
|
* #4560: structured workflow recovery projection from canonical durable
|
|
505
537
|
* Ralplan/Ultragoal state, consumed by the compaction summary context and
|
|
@@ -1047,8 +1079,23 @@ export interface PromptOptions {
|
|
|
1047
1079
|
onSkillPrepared?: (meta: { name: string; path: string; lineCount?: number; cleanedArgs?: string }) => void;
|
|
1048
1080
|
/** Optional invocation-scoped cancellation fence used before an accepted skill starts execution. */
|
|
1049
1081
|
preflightSignal?: AbortSignal;
|
|
1082
|
+
/** Internal host capability; ordinary package callers cannot construct the branded value. */
|
|
1083
|
+
sdkRunCapability?: unknown;
|
|
1050
1084
|
}
|
|
1051
1085
|
|
|
1086
|
+
type InternalPromptOptions = PromptOptions & { sdkRunToken?: string };
|
|
1087
|
+
type InternalCustomMessageOptions = Pick<
|
|
1088
|
+
PromptOptions,
|
|
1089
|
+
| "streamingBehavior"
|
|
1090
|
+
| "toolChoice"
|
|
1091
|
+
| "followUpQueuePolicy"
|
|
1092
|
+
| "onPreflightAccepted"
|
|
1093
|
+
| "onPreflightAcceptCommit"
|
|
1094
|
+
| "preflightSignal"
|
|
1095
|
+
> & {
|
|
1096
|
+
sdkRunToken?: string;
|
|
1097
|
+
};
|
|
1098
|
+
|
|
1052
1099
|
function promptPreflightCancelledError(): Error {
|
|
1053
1100
|
const error = Object.assign(new Error("Prompt preflight was cancelled before execution."), { code: "busy" });
|
|
1054
1101
|
error.name = "PromptPreflightCancelledError";
|
|
@@ -1297,11 +1344,74 @@ function isBareDefaultMessageOnlyFirstEventTimeout(message: AssistantMessage): b
|
|
|
1297
1344
|
BARE_DEFAULT_WATCHDOG_ERROR.test(message.errorMessage ?? ""))
|
|
1298
1345
|
);
|
|
1299
1346
|
}
|
|
1347
|
+
/**
|
|
1348
|
+
* True when the attempt's structured evidence is exactly the provider's typed
|
|
1349
|
+
* capacity-overload code and nothing else: no status, no retained retry header,
|
|
1350
|
+
* no second typed code, no error kind, and none of the transport's other
|
|
1351
|
+
* observations (request size, first-event timing, endpoint class, provider retry
|
|
1352
|
+
* ceiling). The code arrives statuslessly (a Codex error event, an HTTP 200
|
|
1353
|
+
* terminal Responses envelope), so these facts ARE the overload rather than
|
|
1354
|
+
* evidence that the attempt did observable work; any additional fact means the
|
|
1355
|
+
* attempt is no longer provably that bare capacity rejection. The code is
|
|
1356
|
+
* compared case-sensitively, matching the parser and the transport gate.
|
|
1357
|
+
*/
|
|
1358
|
+
function isExactTypedOverloadFacts(message: AssistantMessage): boolean {
|
|
1359
|
+
if (message.errorKind !== undefined || message.errorStatus !== undefined) return false;
|
|
1360
|
+
const facts = message.transportFailure;
|
|
1361
|
+
if (!facts) return false;
|
|
1362
|
+
return (
|
|
1363
|
+
facts.providerCode === SERVER_OVERLOADED_PROVIDER_CODE &&
|
|
1364
|
+
facts.status === undefined &&
|
|
1365
|
+
facts.anthropicErrorType === undefined &&
|
|
1366
|
+
(facts.openaiErrorCode === undefined || facts.openaiErrorCode === SERVER_OVERLOADED_PROVIDER_CODE) &&
|
|
1367
|
+
(facts.headers === undefined || Object.keys(facts.headers).length === 0) &&
|
|
1368
|
+
facts.requestBytes === undefined &&
|
|
1369
|
+
facts.firstEventElapsedMs === undefined &&
|
|
1370
|
+
facts.firstEventTimeoutMs === undefined &&
|
|
1371
|
+
facts.endpointClass === undefined &&
|
|
1372
|
+
facts.retryMaxAttempts === undefined
|
|
1373
|
+
);
|
|
1374
|
+
}
|
|
1375
|
+
/**
|
|
1376
|
+
* True when transport facts are exactly the provider's statusless typed
|
|
1377
|
+
* capacity-overload code (the provider code verbatim and, when present, the
|
|
1378
|
+
* OpenAI code verbatim, with no status). The agent loop must not treat the new
|
|
1379
|
+
* facts as managed transaction authority, and managed session fallback must
|
|
1380
|
+
* not gain retry/advance authority from them, so this predicate keeps both
|
|
1381
|
+
* guards reading only typed facts, never error prose.
|
|
1382
|
+
*/
|
|
1383
|
+
function isStatuslessTypedOverloadFacts(facts: TransportFailureFacts | undefined): boolean {
|
|
1384
|
+
if (!facts) return false;
|
|
1385
|
+
return (
|
|
1386
|
+
facts.status === undefined &&
|
|
1387
|
+
facts.providerCode === SERVER_OVERLOADED_PROVIDER_CODE &&
|
|
1388
|
+
(facts.openaiErrorCode === undefined || facts.openaiErrorCode === SERVER_OVERLOADED_PROVIDER_CODE)
|
|
1389
|
+
);
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1300
1392
|
function isBareDefaultCodexOverload(message: AssistantMessage): boolean {
|
|
1301
1393
|
return (
|
|
1302
1394
|
message.api === "openai-codex-responses" &&
|
|
1303
1395
|
BARE_DEFAULT_CODEX_OVERLOAD_ERROR.test(message.errorMessage ?? "") &&
|
|
1304
|
-
!hasBareDefaultRetryDisqualifyingFacts(message) &&
|
|
1396
|
+
(isExactTypedOverloadFacts(message) || !hasBareDefaultRetryDisqualifyingFacts(message)) &&
|
|
1397
|
+
!assistantMessageHasVisibleOrToolContent(message)
|
|
1398
|
+
);
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
/**
|
|
1402
|
+
* True when a generic OpenAI Responses turn failed with exactly OpenAI's typed
|
|
1403
|
+
* capacity-overload code. Unlike the Codex and Anthropic admissions this reads
|
|
1404
|
+
* no prose at all: the terminal Responses envelope carries the code in typed
|
|
1405
|
+
* transport facts, so the code plus a content-free attempt is the whole
|
|
1406
|
+
* admission. Both `openaiErrorCode` and the derived `providerCode` must be the
|
|
1407
|
+
* exact code, so a different transport's overload facts cannot enter through
|
|
1408
|
+
* this path.
|
|
1409
|
+
*/
|
|
1410
|
+
function isBareDefaultOpenAIResponsesOverload(message: AssistantMessage): boolean {
|
|
1411
|
+
return (
|
|
1412
|
+
message.api === "openai-responses" &&
|
|
1413
|
+
message.transportFailure?.openaiErrorCode === SERVER_OVERLOADED_PROVIDER_CODE &&
|
|
1414
|
+
isExactTypedOverloadFacts(message) &&
|
|
1305
1415
|
!assistantMessageHasVisibleOrToolContent(message)
|
|
1306
1416
|
);
|
|
1307
1417
|
}
|
|
@@ -1560,14 +1670,44 @@ function buildSessionMetadata(
|
|
|
1560
1670
|
provider: string,
|
|
1561
1671
|
authStorage: AuthStorage | undefined,
|
|
1562
1672
|
credentialSessionId = sessionId,
|
|
1673
|
+
model?: Model,
|
|
1674
|
+
owner?: object,
|
|
1563
1675
|
): Record<string, unknown> {
|
|
1564
1676
|
const userId: Record<string, string> = { session_id: sessionId };
|
|
1565
1677
|
// Only look up account_uuid when the request is going to Anthropic. Injecting
|
|
1566
1678
|
// a Anthropic model OAuth account_uuid into requests bound for other providers (including
|
|
1567
1679
|
// Anthropic-format-compatible proxies like cloudflare-ai-gateway or gitlab-duo)
|
|
1568
1680
|
// would leak the user's Anthropic identity to unrelated third-party APIs.
|
|
1569
|
-
|
|
1570
|
-
|
|
1681
|
+
const effectiveBaseUrl =
|
|
1682
|
+
model?.provider === "anthropic"
|
|
1683
|
+
? (normalizeAnthropicBaseUrl(
|
|
1684
|
+
(isFoundryEnabled() ? $pickCredentialEnv("FOUNDRY_BASE_URL") : undefined) ?? model.baseUrl,
|
|
1685
|
+
) ?? "https://api.anthropic.com")
|
|
1686
|
+
: undefined;
|
|
1687
|
+
let officialAnthropicEndpoint = false;
|
|
1688
|
+
if (provider === "anthropic" && model?.api === "anthropic-messages") {
|
|
1689
|
+
try {
|
|
1690
|
+
const candidate = effectiveBaseUrl ?? "https://api.anthropic.com";
|
|
1691
|
+
const url = new URL(candidate);
|
|
1692
|
+
const authority = candidate.match(/^[a-z][a-z\d+.-]*:\/\/([^/?#]*)/iu)?.[1];
|
|
1693
|
+
officialAnthropicEndpoint =
|
|
1694
|
+
url.protocol === "https:" &&
|
|
1695
|
+
url.hostname === "api.anthropic.com" &&
|
|
1696
|
+
authority?.toLowerCase() === "api.anthropic.com" &&
|
|
1697
|
+
!url.username &&
|
|
1698
|
+
!url.password &&
|
|
1699
|
+
!url.search &&
|
|
1700
|
+
!url.hash &&
|
|
1701
|
+
(url.pathname === "/" || url.pathname === "/v1" || url.pathname === "/v1/");
|
|
1702
|
+
} catch {
|
|
1703
|
+
officialAnthropicEndpoint = false;
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
// Without the registry owner, do not query shared auth storage: an unscoped
|
|
1707
|
+
// lookup can attribute a registry-scoped API-key request to another OAuth
|
|
1708
|
+
// account. Compatibility facades therefore retain session_id-only metadata.
|
|
1709
|
+
if (officialAnthropicEndpoint && owner) {
|
|
1710
|
+
const accountUuid = authStorage?.getOAuthAccountId("anthropic", credentialSessionId, { owner });
|
|
1571
1711
|
if (typeof accountUuid === "string" && accountUuid.length > 0) {
|
|
1572
1712
|
userId.account_uuid = accountUuid;
|
|
1573
1713
|
// Derive device_id from account_uuid so the payload matches the real CC
|
|
@@ -1848,9 +1988,24 @@ export type BeforeAgentStartContributor = (event: {
|
|
|
1848
1988
|
}) => Promise<BeforeAgentStartInternalMessage | undefined>;
|
|
1849
1989
|
|
|
1850
1990
|
const AGENT_END_WORKER_INTEGRATION_TIMEOUT_MS = 5_000;
|
|
1991
|
+
const POST_PUBLICATION_ERROR_MAX_BYTES = 512;
|
|
1992
|
+
|
|
1993
|
+
export type WorkerIntegrationOutcome =
|
|
1994
|
+
| { status: "completed" }
|
|
1995
|
+
| { status: "failed"; error: string }
|
|
1996
|
+
| { status: "timed_out" };
|
|
1997
|
+
|
|
1998
|
+
function sanitizePostPublicationError(error: string | undefined): string | undefined {
|
|
1999
|
+
if (error === undefined) return undefined;
|
|
2000
|
+
const normalized = error
|
|
2001
|
+
.replace(/[\u0000-\u001f\u007f]/gu, " ")
|
|
2002
|
+
.replace(/\s+/gu, " ")
|
|
2003
|
+
.trim();
|
|
2004
|
+
return truncateHeadBytes(normalized || "unknown worker integration error", POST_PUBLICATION_ERROR_MAX_BYTES).text;
|
|
2005
|
+
}
|
|
1851
2006
|
|
|
1852
2007
|
export class WorkerIntegrationRequestScheduler {
|
|
1853
|
-
#inFlight: Promise<
|
|
2008
|
+
#inFlight: Promise<WorkerIntegrationOutcome> | undefined = undefined;
|
|
1854
2009
|
#pending = false;
|
|
1855
2010
|
|
|
1856
2011
|
constructor(
|
|
@@ -1867,9 +2022,15 @@ export class WorkerIntegrationRequestScheduler {
|
|
|
1867
2022
|
}
|
|
1868
2023
|
|
|
1869
2024
|
async flush(): Promise<void> {
|
|
2025
|
+
await this.flushWithOutcome();
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
async flushWithOutcome(): Promise<WorkerIntegrationOutcome> {
|
|
2029
|
+
let outcome: WorkerIntegrationOutcome = { status: "completed" };
|
|
1870
2030
|
while (this.#inFlight) {
|
|
1871
|
-
await this.#inFlight;
|
|
2031
|
+
outcome = await this.#inFlight;
|
|
1872
2032
|
}
|
|
2033
|
+
return outcome;
|
|
1873
2034
|
}
|
|
1874
2035
|
|
|
1875
2036
|
#start(): void {
|
|
@@ -1879,16 +2040,22 @@ export class WorkerIntegrationRequestScheduler {
|
|
|
1879
2040
|
try {
|
|
1880
2041
|
request = this.request(controller.signal);
|
|
1881
2042
|
} catch {
|
|
1882
|
-
request = Promise.
|
|
2043
|
+
request = Promise.reject(new Error("Worker integration request failed before dispatch."));
|
|
1883
2044
|
}
|
|
1884
2045
|
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
1885
|
-
const deadline = new Promise<
|
|
2046
|
+
const deadline = new Promise<WorkerIntegrationOutcome>(resolve => {
|
|
1886
2047
|
timeout = setTimeout(() => {
|
|
1887
2048
|
controller.abort(new Error("Worker integration request timed out"));
|
|
1888
|
-
resolve();
|
|
2049
|
+
resolve({ status: "timed_out" });
|
|
1889
2050
|
}, this.timeoutMs);
|
|
1890
2051
|
});
|
|
1891
|
-
this.#inFlight = Promise.race([
|
|
2052
|
+
this.#inFlight = Promise.race([
|
|
2053
|
+
request.then(
|
|
2054
|
+
() => ({ status: "completed" as const }),
|
|
2055
|
+
error => ({ status: "failed" as const, error: String(error) }),
|
|
2056
|
+
),
|
|
2057
|
+
deadline,
|
|
2058
|
+
]).finally(() => {
|
|
1892
2059
|
if (timeout) clearTimeout(timeout);
|
|
1893
2060
|
controller.abort();
|
|
1894
2061
|
this.#inFlight = undefined;
|
|
@@ -2365,6 +2532,8 @@ export class AgentSession {
|
|
|
2365
2532
|
this.#followUpPromotionHooks.delete(message);
|
|
2366
2533
|
followUpHook({ startsOwnRun: true, removed: true });
|
|
2367
2534
|
}
|
|
2535
|
+
this.#sdkRunTokensByQueuedMessage.delete(message);
|
|
2536
|
+
this.#deepInterviewGenuineUserMessageEpochs.delete(message);
|
|
2368
2537
|
}
|
|
2369
2538
|
}
|
|
2370
2539
|
#fireQueuedPromotionHooks(messages: readonly AgentMessage[], promotion?: { startsOwnRun?: boolean }): void {
|
|
@@ -2384,6 +2553,45 @@ export class AgentSession {
|
|
|
2384
2553
|
}
|
|
2385
2554
|
}
|
|
2386
2555
|
}
|
|
2556
|
+
#queuedMessagesForSessionTransition(): AgentMessage[] {
|
|
2557
|
+
return [
|
|
2558
|
+
...new Set([
|
|
2559
|
+
...this.agent.snapshotSteering(),
|
|
2560
|
+
...this.agent.snapshotFollowUp(),
|
|
2561
|
+
...this.#deferredSdkFollowUps,
|
|
2562
|
+
]),
|
|
2563
|
+
];
|
|
2564
|
+
}
|
|
2565
|
+
/** Drop queued SDK work when the session identity is replaced. The old
|
|
2566
|
+
* promotion hooks belong to the predecessor runtime; retaining the message
|
|
2567
|
+
* would let it execute later under the successor without an owner. */
|
|
2568
|
+
#terminalizeQueuedSdkWorkForSessionTransition(messages: readonly AgentMessage[]): void {
|
|
2569
|
+
if (messages.length === 0) return;
|
|
2570
|
+
this.#fireQueuedRemovalHooks(messages);
|
|
2571
|
+
for (const message of messages) this.#sdkRunTokensByQueuedMessage.delete(message);
|
|
2572
|
+
this.#deferredSdkFollowUps = this.#deferredSdkFollowUps.filter(message => !messages.includes(message));
|
|
2573
|
+
}
|
|
2574
|
+
#resetActiveSdkRunOwnership(): void {
|
|
2575
|
+
this.#activeSdkRunToken = undefined;
|
|
2576
|
+
this.#activeAttemptScope = undefined;
|
|
2577
|
+
this.#activeLogicalRunId = undefined;
|
|
2578
|
+
}
|
|
2579
|
+
#acceptSdkAttemptRun(handle: AttemptRunHandle, sdkRunToken?: string): void {
|
|
2580
|
+
const predecessorScope = this.#activeAttemptScope;
|
|
2581
|
+
const predecessorSdkRunToken =
|
|
2582
|
+
predecessorScope === undefined ? undefined : this.#sdkRunTokensByAttemptScope.get(predecessorScope);
|
|
2583
|
+
const carryRecoverySkip =
|
|
2584
|
+
sdkRunToken !== undefined &&
|
|
2585
|
+
predecessorSdkRunToken === sdkRunToken &&
|
|
2586
|
+
predecessorScope !== undefined &&
|
|
2587
|
+
this.#skipPostPromptRecoveryWaitByAttemptScope.delete(predecessorScope);
|
|
2588
|
+
this.#acceptRunHandle(handle);
|
|
2589
|
+
if (sdkRunToken !== undefined) {
|
|
2590
|
+
this.#activeSdkRunToken = sdkRunToken;
|
|
2591
|
+
this.#sdkRunTokensByAttemptScope.set(handle.scope, sdkRunToken);
|
|
2592
|
+
}
|
|
2593
|
+
if (carryRecoverySkip) this.#skipPostPromptRecoveryWaitByAttemptScope.add(handle.scope);
|
|
2594
|
+
}
|
|
2387
2595
|
/** Messages queued to be included with the next user prompt as context ("asides"). */
|
|
2388
2596
|
#pendingNextTurnMessages: Array<{ message: CustomMessage; origin: "turn" | "external" }> = [];
|
|
2389
2597
|
#scheduledHiddenNextTurnGeneration: number | undefined = undefined;
|
|
@@ -2517,7 +2725,88 @@ export class AgentSession {
|
|
|
2517
2725
|
onPersisted?: () => void;
|
|
2518
2726
|
appendedToAgent: boolean;
|
|
2519
2727
|
}> = [];
|
|
2520
|
-
|
|
2728
|
+
/**
|
|
2729
|
+
* Set by a fold and consumed once by the pause checkpoint, so a fold ends its
|
|
2730
|
+
* own turn without pausing any later turn.
|
|
2731
|
+
*/
|
|
2732
|
+
#foldStopRequested = false;
|
|
2733
|
+
/**
|
|
2734
|
+
* The single linearization point for folding a foreground wait. Arrow deps
|
|
2735
|
+
* capture `this` but are only invoked after construction.
|
|
2736
|
+
*/
|
|
2737
|
+
readonly #foldCoordinator = new FoldCoordinator({
|
|
2738
|
+
hasActiveTurn: () => this.activePromptHandle !== undefined,
|
|
2739
|
+
armSteeringFence: () => {
|
|
2740
|
+
this.agent.setSteeringAdmissionFence(() => true);
|
|
2741
|
+
return () => this.agent.setSteeringAdmissionFence(undefined);
|
|
2742
|
+
},
|
|
2743
|
+
requestStop: () => {
|
|
2744
|
+
this.#foldStopRequested = true;
|
|
2745
|
+
},
|
|
2746
|
+
captureRemainingIntent: () => this.#captureRemainingIntentForFold(),
|
|
2747
|
+
// A parked completion replayed after the fold must schedule its own wake.
|
|
2748
|
+
// This mirrors exactly what the sdk delivery seam does for a live receipt
|
|
2749
|
+
// delivery, so the parked path cannot rely on an unrelated idle rearm.
|
|
2750
|
+
deliverParked: (job, disposition) => {
|
|
2751
|
+
const endpointId = this.#ownedAsyncJobManager
|
|
2752
|
+
? AsyncJobManager.endpointIdOf(this.#ownedAsyncJobManager)
|
|
2753
|
+
: undefined;
|
|
2754
|
+
const registration = lookupOwnedRegistration(job.id, job.generation, endpointId);
|
|
2755
|
+
if (this.#ownedAsyncJobManager?.isDeliverySuppressed(job.id, job.generation)) {
|
|
2756
|
+
this.#ownedAsyncJobManager.clearParkedDelivery(job.generation);
|
|
2757
|
+
return;
|
|
2758
|
+
}
|
|
2759
|
+
if (this.#foldCoordinator.claimCompletionNotice(job)) {
|
|
2760
|
+
this.emitNotice(
|
|
2761
|
+
"info",
|
|
2762
|
+
`Folded job ${disposition.receipt.jobId} (${disposition.receipt.label}) finished.`,
|
|
2763
|
+
"fold",
|
|
2764
|
+
);
|
|
2765
|
+
}
|
|
2766
|
+
const ownedCompletion = registration
|
|
2767
|
+
? {
|
|
2768
|
+
lineageIdHash: registration.lineageIdHash,
|
|
2769
|
+
promptAttemptEpoch: registration.promptAttemptEpoch,
|
|
2770
|
+
registration,
|
|
2771
|
+
}
|
|
2772
|
+
: undefined;
|
|
2773
|
+
const allowArtifact = ownedCompletion === undefined || isOwnedCompletionEnvelopeAllowed(ownedCompletion);
|
|
2774
|
+
void formatParkedAsyncResult(this.sessionManager, disposition.text, allowArtifact)
|
|
2775
|
+
.then(formattedResult => {
|
|
2776
|
+
const manager = this.#ownedAsyncJobManager;
|
|
2777
|
+
if (manager?.isDeliverySuppressed(job.id, job.generation)) {
|
|
2778
|
+
manager.clearParkedDelivery(job.generation);
|
|
2779
|
+
return;
|
|
2780
|
+
}
|
|
2781
|
+
if (!this.#foldCoordinator.claimCompletionDelivery(job)) {
|
|
2782
|
+
manager?.clearParkedDelivery(job.generation);
|
|
2783
|
+
return;
|
|
2784
|
+
}
|
|
2785
|
+
try {
|
|
2786
|
+
manager?.retainDeliveryClaim(job);
|
|
2787
|
+
if (manager?.isDeliverySuppressed(job.id, job.generation)) {
|
|
2788
|
+
manager.releaseDeliveryClaim(job.generation);
|
|
2789
|
+
manager.clearParkedDelivery(job.generation);
|
|
2790
|
+
return;
|
|
2791
|
+
}
|
|
2792
|
+
this.yieldQueue.enqueue("async-result", {
|
|
2793
|
+
jobId: disposition.receipt.jobId,
|
|
2794
|
+
generation: disposition.receipt.jobGeneration,
|
|
2795
|
+
result: `${formattedResult}\n\n${describeFoldReceipt(disposition.receipt)}`,
|
|
2796
|
+
job,
|
|
2797
|
+
durationMs: undefined,
|
|
2798
|
+
ownedCompletion,
|
|
2799
|
+
});
|
|
2800
|
+
} finally {
|
|
2801
|
+
manager?.clearParkedDelivery(job.generation);
|
|
2802
|
+
}
|
|
2803
|
+
})
|
|
2804
|
+
.catch(error => {
|
|
2805
|
+
this.#ownedAsyncJobManager?.clearParkedDelivery(job.generation);
|
|
2806
|
+
logger.warn("Parked folded delivery formatting failed", { error: String(error) });
|
|
2807
|
+
});
|
|
2808
|
+
},
|
|
2809
|
+
});
|
|
2521
2810
|
|
|
2522
2811
|
// Python execution state
|
|
2523
2812
|
#evalAbortControllers = new Set<AbortController>();
|
|
@@ -2567,7 +2856,10 @@ export class AgentSession {
|
|
|
2567
2856
|
#extensionRunner: ExtensionRunner | undefined = undefined;
|
|
2568
2857
|
/** SDK follow-up ownership by queued message and the attempt that dequeues it. */
|
|
2569
2858
|
#sdkRunTokensByQueuedMessage = new WeakMap<AgentMessage, string>();
|
|
2859
|
+
#skipPostPromptRecoveryWaitByAttemptScope = new WeakSet<AttemptScope>();
|
|
2570
2860
|
#sdkRunTokensByAttemptScope = new WeakMap<AttemptScope, string>();
|
|
2861
|
+
#activeSdkRunToken: string | undefined;
|
|
2862
|
+
#activeAttemptScope: AttemptScope | undefined;
|
|
2571
2863
|
#attemptAuthority!: AttemptScopeAuthority;
|
|
2572
2864
|
#attemptRecordStore!: AttemptRecordStore;
|
|
2573
2865
|
#activeLogicalRunId: AttemptRunHandle["logicalRunId"] | undefined;
|
|
@@ -2576,6 +2868,7 @@ export class AgentSession {
|
|
|
2576
2868
|
// previously recorded run id rather than throwing inside the callback.
|
|
2577
2869
|
if (!handle) return;
|
|
2578
2870
|
this.#activeLogicalRunId = handle.logicalRunId;
|
|
2871
|
+
this.#activeAttemptScope = handle.scope;
|
|
2579
2872
|
}
|
|
2580
2873
|
|
|
2581
2874
|
#turnIndex = 0;
|
|
@@ -2912,6 +3205,7 @@ export class AgentSession {
|
|
|
2912
3205
|
* orchestrator does not hold it), so there is no self-deadlock.
|
|
2913
3206
|
*/
|
|
2914
3207
|
#sessionTransitionKind: string | undefined;
|
|
3208
|
+
#coordinatorPersistGeneration = 0;
|
|
2915
3209
|
|
|
2916
3210
|
#beginSessionTransition(kind: string): void {
|
|
2917
3211
|
if (this.#sessionTransitionKind !== undefined) {
|
|
@@ -2921,6 +3215,7 @@ export class AgentSession {
|
|
|
2921
3215
|
);
|
|
2922
3216
|
}
|
|
2923
3217
|
this.#sessionTransitionKind = kind;
|
|
3218
|
+
this.#coordinatorPersistGeneration += 1;
|
|
2924
3219
|
}
|
|
2925
3220
|
|
|
2926
3221
|
#endSessionTransition(): void {
|
|
@@ -3520,7 +3815,7 @@ export class AgentSession {
|
|
|
3520
3815
|
// Deferrals behind still-pending selection fences are excluded here: they
|
|
3521
3816
|
// cannot settle before their fence resolves, and the fence owner may be
|
|
3522
3817
|
// waiting on this very settlement (#4519).
|
|
3523
|
-
if (!predecessorPromptStillInFlight) {
|
|
3818
|
+
if (promptWait !== "publication" && !predecessorPromptStillInFlight) {
|
|
3524
3819
|
await this.#waitForSessionSettlement(this.#turnSettlementDeferralFloor());
|
|
3525
3820
|
}
|
|
3526
3821
|
if (flushError) throw flushError;
|
|
@@ -3562,28 +3857,50 @@ export class AgentSession {
|
|
|
3562
3857
|
this.#pendingAgentEndEmit = undefined;
|
|
3563
3858
|
const lease = this.#deferredAgentEndLeases.get(pending);
|
|
3564
3859
|
if (lease) this.#deferredAgentEndLeases.delete(pending);
|
|
3860
|
+
this.#startAgentEndPublication(pending, lease);
|
|
3861
|
+
}
|
|
3862
|
+
#startAgentEndPublication(pending: AgentSessionEvent, lease?: RunResourceProducerLease): void {
|
|
3565
3863
|
this.#agentEndPublicationInFlight++;
|
|
3566
|
-
|
|
3864
|
+
const pendingScope = (pending as AgentSessionEvent & { scope?: AttemptScopeRef }).scope as
|
|
3865
|
+
| AttemptScope
|
|
3866
|
+
| undefined;
|
|
3867
|
+
const sdkTerminal = pendingScope !== undefined && this.#sdkRunTokensByAttemptScope.has(pendingScope);
|
|
3868
|
+
this.#agentEndPublicationPromise = this.#publishDeferredAgentEnd(pending, lease, sdkTerminal);
|
|
3567
3869
|
void this.#agentEndPublicationPromise;
|
|
3568
3870
|
}
|
|
3569
3871
|
|
|
3570
3872
|
async #publishDeferredAgentEnd(
|
|
3571
3873
|
pending: AgentSessionEvent,
|
|
3572
3874
|
lease: RunResourceProducerLease | undefined,
|
|
3875
|
+
workerIntegrationSettled: boolean,
|
|
3573
3876
|
): Promise<void> {
|
|
3877
|
+
const publicationContext = {
|
|
3878
|
+
sessionId: this.sessionId,
|
|
3879
|
+
cwd: this.sessionManager.getCwd(),
|
|
3880
|
+
sessionFile: this.sessionManager.getSessionFile(),
|
|
3881
|
+
};
|
|
3882
|
+
const publicationScope = (pending as AgentSessionEvent & { scope?: AttemptScopeRef }).scope as
|
|
3883
|
+
| AttemptScope
|
|
3884
|
+
| undefined;
|
|
3885
|
+
const publicationCorrelationId = publicationScope
|
|
3886
|
+
? this.#sdkRunTokensByAttemptScope.get(publicationScope)
|
|
3887
|
+
: undefined;
|
|
3574
3888
|
let extensionDelivery: Promise<void> | undefined;
|
|
3575
3889
|
const releaseLease = () => {
|
|
3576
3890
|
if (!lease) return;
|
|
3891
|
+
this.#deferredAgentEndLeases.delete(pending);
|
|
3577
3892
|
if (this.#postPromptLeases.get(lease.resourceRunId) === lease) {
|
|
3578
3893
|
this.#postPromptLeases.delete(lease.resourceRunId);
|
|
3579
3894
|
}
|
|
3580
3895
|
lease.closeDiscovery();
|
|
3581
3896
|
};
|
|
3582
3897
|
const publish = async () => {
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3898
|
+
let workerIntegration: Promise<WorkerIntegrationOutcome> | undefined;
|
|
3899
|
+
let workerIntegrationOutcome: WorkerIntegrationOutcome | undefined;
|
|
3900
|
+
if (!workerIntegrationSettled) {
|
|
3901
|
+
workerIntegration = this.#flushWorkerIntegrationForAgentEnd();
|
|
3902
|
+
workerIntegrationOutcome = await workerIntegration;
|
|
3903
|
+
}
|
|
3587
3904
|
// Reserve persistence before notifying synchronous subscribers: a subscriber
|
|
3588
3905
|
// may start a successor prompt from agent_end, whose running state must
|
|
3589
3906
|
// serialize after this terminal boundary rather than be overwritten by it.
|
|
@@ -3592,16 +3909,72 @@ export class AgentSession {
|
|
|
3592
3909
|
// behind this terminal transition, but do not make the interactive terminal
|
|
3593
3910
|
// wait for the filesystem-backed lock/write to settle. On slow or lock-hostile
|
|
3594
3911
|
// mounts such as WSL drvfs, waiting here leaves the foreground activity loader
|
|
3595
|
-
// and busy input state visible after the model has already finished.
|
|
3596
|
-
|
|
3912
|
+
// and busy input state visible after the model has already finished. The queue
|
|
3913
|
+
// preserves transaction order; terminal publication is the user-visible
|
|
3914
|
+
// authority and must not be suppressed by a secondary persistence failure.
|
|
3915
|
+
const terminalPersistence = this.#queueCoordinatorRuntimeStatePersist(pending, true);
|
|
3597
3916
|
this.#emit(pending);
|
|
3598
|
-
|
|
3917
|
+
void terminalPersistence.then(
|
|
3918
|
+
() => {
|
|
3919
|
+
if (workerIntegrationOutcome) {
|
|
3920
|
+
this.#recordPostPublicationOutcome(
|
|
3921
|
+
publicationContext,
|
|
3922
|
+
publicationCorrelationId,
|
|
3923
|
+
"worker_integration",
|
|
3924
|
+
workerIntegrationOutcome,
|
|
3925
|
+
);
|
|
3926
|
+
}
|
|
3927
|
+
this.#recordPostPublicationOutcome(
|
|
3928
|
+
publicationContext,
|
|
3929
|
+
publicationCorrelationId,
|
|
3930
|
+
"terminal_persistence",
|
|
3931
|
+
{ status: "completed" },
|
|
3932
|
+
);
|
|
3933
|
+
},
|
|
3934
|
+
error => {
|
|
3935
|
+
if (workerIntegrationOutcome) {
|
|
3936
|
+
this.#recordPostPublicationOutcome(
|
|
3937
|
+
publicationContext,
|
|
3938
|
+
publicationCorrelationId,
|
|
3939
|
+
"worker_integration",
|
|
3940
|
+
workerIntegrationOutcome,
|
|
3941
|
+
);
|
|
3942
|
+
}
|
|
3943
|
+
this.#recordPostPublicationOutcome(
|
|
3944
|
+
publicationContext,
|
|
3945
|
+
publicationCorrelationId,
|
|
3946
|
+
"terminal_persistence",
|
|
3947
|
+
{
|
|
3948
|
+
status: "failed",
|
|
3949
|
+
error: String(error),
|
|
3950
|
+
},
|
|
3951
|
+
);
|
|
3952
|
+
logger.warn("Failed to persist terminal coordinator runtime state", { error: String(error) });
|
|
3953
|
+
},
|
|
3954
|
+
);
|
|
3599
3955
|
extensionDelivery = this.#queueExtensionEvent(
|
|
3600
3956
|
pending,
|
|
3601
3957
|
undefined,
|
|
3602
3958
|
true,
|
|
3603
3959
|
(pending as AgentSessionEvent & { scope?: AttemptScopeRef }).scope,
|
|
3604
3960
|
);
|
|
3961
|
+
if (workerIntegrationSettled) {
|
|
3962
|
+
workerIntegration = this.#flushWorkerIntegrationForAgentEnd();
|
|
3963
|
+
void workerIntegration.then(outcome =>
|
|
3964
|
+
this.#recordPostPublicationOutcome(
|
|
3965
|
+
publicationContext,
|
|
3966
|
+
publicationCorrelationId,
|
|
3967
|
+
"worker_integration",
|
|
3968
|
+
outcome,
|
|
3969
|
+
),
|
|
3970
|
+
);
|
|
3971
|
+
await extensionDelivery;
|
|
3972
|
+
void terminalPersistence.catch(error =>
|
|
3973
|
+
logger.warn("Terminal persistence continued after SDK publication", { error }),
|
|
3974
|
+
);
|
|
3975
|
+
} else {
|
|
3976
|
+
// Non-SDK/public publication has already awaited worker integration above.
|
|
3977
|
+
}
|
|
3605
3978
|
};
|
|
3606
3979
|
try {
|
|
3607
3980
|
if (lease) await this.#runResourceLeaseContext.run(lease, publish);
|
|
@@ -3797,9 +4170,19 @@ export class AgentSession {
|
|
|
3797
4170
|
this.#resumeFromOwnedCompletion();
|
|
3798
4171
|
try {
|
|
3799
4172
|
if (survivors.length === 1) {
|
|
3800
|
-
await this.agent.prompt(first,
|
|
4173
|
+
await this.agent.prompt(first, {
|
|
4174
|
+
...this.#managedFallbackPromptOptions(),
|
|
4175
|
+
onRunAccepted: (handle: AttemptRunHandle) => {
|
|
4176
|
+
if (handle) this.#acceptSdkAttemptRun(handle);
|
|
4177
|
+
},
|
|
4178
|
+
});
|
|
3801
4179
|
} else {
|
|
3802
|
-
await this.agent.prompt(survivors,
|
|
4180
|
+
await this.agent.prompt(survivors, {
|
|
4181
|
+
...this.#managedFallbackPromptOptions(),
|
|
4182
|
+
onRunAccepted: (handle: AttemptRunHandle) => {
|
|
4183
|
+
if (handle) this.#acceptSdkAttemptRun(handle);
|
|
4184
|
+
},
|
|
4185
|
+
});
|
|
3803
4186
|
}
|
|
3804
4187
|
} finally {
|
|
3805
4188
|
// The owned completions were delivered OR the prompt attempt
|
|
@@ -3817,11 +4200,32 @@ export class AgentSession {
|
|
|
3817
4200
|
async () => {
|
|
3818
4201
|
await run();
|
|
3819
4202
|
},
|
|
3820
|
-
|
|
4203
|
+
// One merge window, so staggered completions share a single wake
|
|
4204
|
+
// turn instead of each buying its own.
|
|
4205
|
+
{ delayMs: FOLD_WAKE_MERGE_WINDOW_MS },
|
|
3821
4206
|
);
|
|
3822
4207
|
},
|
|
3823
4208
|
});
|
|
3824
4209
|
this.agent.setOnBeforeYield(() => this.yieldQueue.flush("streaming"));
|
|
4210
|
+
// Stop-after-result, never abort: a fold arms this once and the loop ends the
|
|
4211
|
+
// turn at its next checkpoint. Consuming the flag here keeps the pause scoped
|
|
4212
|
+
// to the folded turn instead of pausing everything that follows. The
|
|
4213
|
+
// config-provided checkpoint (subagent requestPause, RLM research) is OR-ed
|
|
4214
|
+
// in, never replaced.
|
|
4215
|
+
// sdk/session.ts threads the config's shouldPause into new Agent, so the
|
|
4216
|
+
// Agent's current checkpoint IS the config-provided one (subagent
|
|
4217
|
+
// requestPause, RLM research). Capture and OR it rather than replace it.
|
|
4218
|
+
const configuredShouldPause = this.agent.shouldPause;
|
|
4219
|
+
this.agent.setShouldPause(() => {
|
|
4220
|
+
if (configuredShouldPause?.() === true) return true;
|
|
4221
|
+
if (!this.#foldStopRequested) return false;
|
|
4222
|
+
this.#foldStopRequested = false;
|
|
4223
|
+
// The folded turn has now actually stopped: steering admission must be
|
|
4224
|
+
// restored so later turns drain their queues normally (the fence stays
|
|
4225
|
+
// armed from the fold until exactly this point).
|
|
4226
|
+
this.agent.setSteeringAdmissionFence(undefined);
|
|
4227
|
+
return true;
|
|
4228
|
+
});
|
|
3825
4229
|
this.agent.setMaintainContext((context, lifecycle) =>
|
|
3826
4230
|
this.#trackMidRunMaintenance(
|
|
3827
4231
|
this.awaitPendingContextTransformations().then(() => this.#runMidRunMaintenance(context, lifecycle)),
|
|
@@ -3960,6 +4364,18 @@ export class AgentSession {
|
|
|
3960
4364
|
if (messages.some(message => ownedCompletionResumeAction(message) === "fresh")) {
|
|
3961
4365
|
this.#resumeFromOwnedCompletion();
|
|
3962
4366
|
}
|
|
4367
|
+
const consumedSdkRunTokenForCurrentRun = messages
|
|
4368
|
+
.map(message => this.#sdkRunTokensByQueuedMessage.get(message))
|
|
4369
|
+
.find((token): token is string => token !== undefined);
|
|
4370
|
+
if (
|
|
4371
|
+
consumedSdkRunTokenForCurrentRun !== undefined &&
|
|
4372
|
+
this.#activeAttemptScope !== undefined &&
|
|
4373
|
+
promotion.startsOwnRun !== true
|
|
4374
|
+
) {
|
|
4375
|
+
this.#sdkRunTokensByAttemptScope.set(this.#activeAttemptScope, consumedSdkRunTokenForCurrentRun);
|
|
4376
|
+
this.#skipPostPromptRecoveryWaitByAttemptScope.add(this.#activeAttemptScope);
|
|
4377
|
+
this.#activeSdkRunToken = consumedSdkRunTokenForCurrentRun;
|
|
4378
|
+
}
|
|
3963
4379
|
// Monitor task-notifications now carry the owned-completion envelope
|
|
3964
4380
|
// (see tools/monitor.ts), so the general drop + fresh-admission paths
|
|
3965
4381
|
// above cover them exactly like async results — no monitor-specific
|
|
@@ -3975,13 +4391,38 @@ export class AgentSession {
|
|
|
3975
4391
|
// when the batch is drained by a continuation the message did not
|
|
3976
4392
|
// schedule (a skipped continuation must never discard the correlation
|
|
3977
4393
|
// of work that is still consumed; review thread P2).
|
|
4394
|
+
const consumedSdkRunToken =
|
|
4395
|
+
promotion.startsOwnRun === true
|
|
4396
|
+
? [...messages, ...dropped]
|
|
4397
|
+
.map(message => this.#sdkRunTokensByQueuedMessage.get(message))
|
|
4398
|
+
.find((token): token is string => token !== undefined)
|
|
4399
|
+
: undefined;
|
|
3978
4400
|
this.#fireQueuedPromotionHooks(messages, promotion);
|
|
4401
|
+
if (consumedSdkRunToken !== undefined) this.#activeSdkRunToken = consumedSdkRunToken;
|
|
4402
|
+
// Dropped or in-run follow-ups have no later own-run acceptance callback;
|
|
4403
|
+
// delete those tokens now. Own-run messages stay mapped until
|
|
4404
|
+
// #scheduleAgentContinue.onRunAccepted binds the new attempt scope.
|
|
4405
|
+
for (const message of dropped) this.#sdkRunTokensByQueuedMessage.delete(message);
|
|
4406
|
+
if (promotion.startsOwnRun !== true)
|
|
4407
|
+
for (const message of messages) this.#sdkRunTokensByQueuedMessage.delete(message);
|
|
3979
4408
|
};
|
|
3980
4409
|
// Steering consumed mid-run never starts its own run: fire the stored
|
|
3981
4410
|
// promotion hook at the REAL dequeue boundary so the SDK attaches the
|
|
3982
4411
|
// submitter to the in-flight run instead of parking the correlation for
|
|
3983
4412
|
// an unrelated later agent_start (#4668).
|
|
3984
4413
|
this.agent.onSteeringConsumed = (messages, promotion = { startsOwnRun: false }) => {
|
|
4414
|
+
const consumedSdkRunToken = messages
|
|
4415
|
+
.map(message => this.#sdkRunTokensByQueuedMessage.get(message))
|
|
4416
|
+
.find((token): token is string => token !== undefined);
|
|
4417
|
+
if (
|
|
4418
|
+
consumedSdkRunToken !== undefined &&
|
|
4419
|
+
this.#activeAttemptScope !== undefined &&
|
|
4420
|
+
promotion.startsOwnRun !== true
|
|
4421
|
+
) {
|
|
4422
|
+
this.#sdkRunTokensByAttemptScope.set(this.#activeAttemptScope, consumedSdkRunToken);
|
|
4423
|
+
this.#skipPostPromptRecoveryWaitByAttemptScope.add(this.#activeAttemptScope);
|
|
4424
|
+
this.#activeSdkRunToken = consumedSdkRunToken;
|
|
4425
|
+
}
|
|
3985
4426
|
this.#fireQueuedPromotionHooks(messages, promotion);
|
|
3986
4427
|
};
|
|
3987
4428
|
this.agent.providerSessionState = this.#providerSessionState;
|
|
@@ -4894,6 +5335,10 @@ export class AgentSession {
|
|
|
4894
5335
|
* seen.
|
|
4895
5336
|
*/
|
|
4896
5337
|
#coordinatorToolObservations = new WeakMap<object, CoordinatorToolObservation>();
|
|
5338
|
+
#agentEventAdmission = new WeakMap<
|
|
5339
|
+
object,
|
|
5340
|
+
{ scope?: AttemptScope; sdkRunToken?: string; persistGeneration: number }
|
|
5341
|
+
>();
|
|
4897
5342
|
|
|
4898
5343
|
/**
|
|
4899
5344
|
* Capture what is true at the SYNCHRONOUS agent-event boundary, before any async work.
|
|
@@ -4949,6 +5394,11 @@ export class AgentSession {
|
|
|
4949
5394
|
}
|
|
4950
5395
|
|
|
4951
5396
|
#trackAgentEvent = (event: AgentEvent): Promise<void> => {
|
|
5397
|
+
this.#agentEventAdmission.set(event, {
|
|
5398
|
+
scope: this.#activeAttemptScope,
|
|
5399
|
+
sdkRunToken: this.#activeSdkRunToken,
|
|
5400
|
+
persistGeneration: this.#coordinatorPersistGeneration,
|
|
5401
|
+
});
|
|
4952
5402
|
// First statement of the listener: the observation must precede every claim,
|
|
4953
5403
|
// reservation, and async hop this handler performs.
|
|
4954
5404
|
this.#observeCoordinatorToolEvent(event);
|
|
@@ -5018,10 +5468,10 @@ export class AgentSession {
|
|
|
5018
5468
|
this.#postPromptLeases.set(eventLease.resourceRunId, eventLease);
|
|
5019
5469
|
if (eventLease) {
|
|
5020
5470
|
await this.#runResourceLeaseContext.run(eventLease, () =>
|
|
5021
|
-
this.#handleAgentEvent(event, activePromptHandle, canonicalAdmission),
|
|
5471
|
+
this.#handleAgentEvent(event, activePromptHandle, canonicalAdmission, eventLease),
|
|
5022
5472
|
);
|
|
5023
5473
|
} else {
|
|
5024
|
-
await this.#handleAgentEvent(event, activePromptHandle, canonicalAdmission);
|
|
5474
|
+
await this.#handleAgentEvent(event, activePromptHandle, canonicalAdmission, undefined);
|
|
5025
5475
|
}
|
|
5026
5476
|
} catch (error) {
|
|
5027
5477
|
logger.warn("Agent event handler failed", { event: event.type, error: String(error) });
|
|
@@ -5029,11 +5479,19 @@ export class AgentSession {
|
|
|
5029
5479
|
canonicalAdmission?.release();
|
|
5030
5480
|
if (eventLease) {
|
|
5031
5481
|
const pendingAgentEnd =
|
|
5032
|
-
event.type === "agent_end" &&
|
|
5482
|
+
event.type === "agent_end" &&
|
|
5483
|
+
!maintenanceCheckpoint &&
|
|
5484
|
+
(this.#pendingAgentEndEmit === event || this.#deferredAgentEndLeases.has(event))
|
|
5033
5485
|
? this.#pendingAgentEndEmit
|
|
5034
5486
|
: undefined;
|
|
5035
5487
|
if (pendingAgentEnd) {
|
|
5036
5488
|
this.#deferredAgentEndLeases.set(pendingAgentEnd, eventLease);
|
|
5489
|
+
} else if (
|
|
5490
|
+
event.type === "agent_end" &&
|
|
5491
|
+
!maintenanceCheckpoint &&
|
|
5492
|
+
this.#deferredAgentEndLeases.has(event)
|
|
5493
|
+
) {
|
|
5494
|
+
// Fast publication took ownership of the lease before the handler unwound.
|
|
5037
5495
|
} else {
|
|
5038
5496
|
if (event.type === "agent_end" && this.#postPromptLeases.get(eventLease.resourceRunId) === eventLease)
|
|
5039
5497
|
this.#postPromptLeases.delete(eventLease.resourceRunId);
|
|
@@ -5056,21 +5514,61 @@ export class AgentSession {
|
|
|
5056
5514
|
/** Serializes sidecar writes in publication order, independent of write latency. */
|
|
5057
5515
|
#coordinatorPersistQueue: Promise<void> = Promise.resolve();
|
|
5058
5516
|
|
|
5517
|
+
#recordPostPublicationOutcome(
|
|
5518
|
+
context: { sessionId: string; cwd: string; sessionFile: string | undefined },
|
|
5519
|
+
correlationId: string | undefined,
|
|
5520
|
+
kind: "worker_integration" | "terminal_persistence",
|
|
5521
|
+
outcome: { status: "completed" | "failed" | "timed_out"; error?: string },
|
|
5522
|
+
): void {
|
|
5523
|
+
if (outcome.status !== "completed") {
|
|
5524
|
+
const correlation = correlationId ?? context.sessionId;
|
|
5525
|
+
const safeError = sanitizePostPublicationError(outcome.error);
|
|
5526
|
+
this.#emit({
|
|
5527
|
+
type: "notice",
|
|
5528
|
+
level: "error",
|
|
5529
|
+
source: kind,
|
|
5530
|
+
message: `${kind} ${outcome.status} for ${correlation}${safeError ? `: ${safeError}` : ""}`,
|
|
5531
|
+
});
|
|
5532
|
+
}
|
|
5533
|
+
const persist = () =>
|
|
5534
|
+
persistCoordinatorWorkerIntegrationOutcome(context, {
|
|
5535
|
+
...outcome,
|
|
5536
|
+
kind,
|
|
5537
|
+
correlationId,
|
|
5538
|
+
error: sanitizePostPublicationError(outcome.error),
|
|
5539
|
+
});
|
|
5540
|
+
const queued = this.#coordinatorPersistQueue.then(persist, persist);
|
|
5541
|
+
this.#coordinatorPersistQueue = queued.catch(() => {});
|
|
5542
|
+
void queued.catch(error =>
|
|
5543
|
+
logger.warn("Failed to persist terminal reconciliation outcome", { error: String(error) }),
|
|
5544
|
+
);
|
|
5545
|
+
}
|
|
5546
|
+
|
|
5059
5547
|
/**
|
|
5060
5548
|
* Reserve this event's place in the sidecar write order.
|
|
5061
5549
|
*
|
|
5062
5550
|
* Called immediately BEFORE local delivery so a synchronous subscriber that re-enters
|
|
5063
5551
|
* the session cannot get its own, later event persisted first. The returned promise is
|
|
5064
|
-
* only
|
|
5552
|
+
* awaited only by callers that explicitly require a durable handoff; interactive
|
|
5553
|
+
* terminal delivery remains independent of this secondary sink.
|
|
5065
5554
|
*
|
|
5066
5555
|
* A pairing-only tool event takes no place in that order at all — neither its start nor
|
|
5067
5556
|
* its end. It describes a call that was never dispatched, and this file is read as the
|
|
5068
5557
|
* answer to "what is this session doing right now".
|
|
5069
5558
|
*/
|
|
5070
|
-
#queueCoordinatorRuntimeStatePersist(event: AgentSessionEvent): Promise<void> {
|
|
5559
|
+
#queueCoordinatorRuntimeStatePersist(event: AgentSessionEvent, propagateFailure = false): Promise<void> {
|
|
5071
5560
|
if (isNonDispatchedToolEvent(event)) return Promise.resolve();
|
|
5072
5561
|
const observation = this.#coordinatorToolObservations.get(event);
|
|
5073
|
-
const
|
|
5562
|
+
const context = {
|
|
5563
|
+
sessionId: this.sessionId,
|
|
5564
|
+
cwd: this.sessionManager.getCwd(),
|
|
5565
|
+
sessionFile: this.sessionManager.getSessionFile(),
|
|
5566
|
+
};
|
|
5567
|
+
const generation = this.#agentEventAdmission.get(event)?.persistGeneration ?? this.#coordinatorPersistGeneration;
|
|
5568
|
+
const run = () =>
|
|
5569
|
+
generation === this.#coordinatorPersistGeneration
|
|
5570
|
+
? this.#persistRuntimeStateInBackground(event, context, observation, propagateFailure)
|
|
5571
|
+
: Promise.resolve();
|
|
5074
5572
|
const queued = this.#coordinatorPersistQueue.then(run, run);
|
|
5075
5573
|
this.#coordinatorPersistQueue = queued.catch(() => {});
|
|
5076
5574
|
return queued;
|
|
@@ -5078,24 +5576,19 @@ export class AgentSession {
|
|
|
5078
5576
|
|
|
5079
5577
|
async #persistRuntimeStateInBackground(
|
|
5080
5578
|
event: AgentSessionEvent,
|
|
5579
|
+
context: { sessionId: string; cwd: string; sessionFile: string | undefined },
|
|
5081
5580
|
observation: CoordinatorToolObservation | undefined,
|
|
5581
|
+
propagateFailure: boolean,
|
|
5082
5582
|
): Promise<void> {
|
|
5083
5583
|
try {
|
|
5084
|
-
await persistCoordinatorRuntimeStateFromEvent(
|
|
5085
|
-
|
|
5086
|
-
{
|
|
5087
|
-
sessionId: this.sessionId,
|
|
5088
|
-
cwd: this.sessionManager.getCwd(),
|
|
5089
|
-
sessionFile: this.sessionManager.getSessionFile(),
|
|
5090
|
-
},
|
|
5091
|
-
observation,
|
|
5092
|
-
);
|
|
5093
|
-
} catch {
|
|
5584
|
+
await persistCoordinatorRuntimeStateFromEvent(event, context, observation);
|
|
5585
|
+
} catch (error) {
|
|
5094
5586
|
logger.warn("Failed to persist coordinator runtime state", { event: event.type });
|
|
5587
|
+
if (propagateFailure) throw error;
|
|
5095
5588
|
}
|
|
5096
5589
|
}
|
|
5097
5590
|
|
|
5098
|
-
async #emitSessionEvent(event: AgentSessionEvent): Promise<void> {
|
|
5591
|
+
async #emitSessionEvent(event: AgentSessionEvent, eventLease?: RunResourceProducerLease): Promise<void> {
|
|
5099
5592
|
const attemptScope = (event as AgentSessionEvent & { scope?: AttemptScope }).scope;
|
|
5100
5593
|
if (event.type === "turn_start") {
|
|
5101
5594
|
this.#extensionTurnGeneration++;
|
|
@@ -5129,6 +5622,18 @@ export class AgentSession {
|
|
|
5129
5622
|
// have unwound. Subscribers treat this event as the ready signal; flushing it
|
|
5130
5623
|
// from abort while either barrier is active permits a successor to race the
|
|
5131
5624
|
// prior prompt's cleanup.
|
|
5625
|
+
const sdkTerminal =
|
|
5626
|
+
event.type === "agent_end" && attemptScope !== undefined && this.#sdkRunTokensByAttemptScope.has(attemptScope);
|
|
5627
|
+
if (
|
|
5628
|
+
event.type === "agent_end" &&
|
|
5629
|
+
sdkTerminal &&
|
|
5630
|
+
this.#pendingAgentEndContinuationHolds.size === 0 &&
|
|
5631
|
+
this.#pendingAgentEndEmit === undefined
|
|
5632
|
+
) {
|
|
5633
|
+
if (eventLease) this.#deferredAgentEndLeases.set(event, eventLease);
|
|
5634
|
+
this.#startAgentEndPublication(event, eventLease);
|
|
5635
|
+
return;
|
|
5636
|
+
}
|
|
5132
5637
|
if (event.type === "agent_end" && (this.#livePromptsInFlight() > 0 || this.#agentEventHandlersInFlight > 0)) {
|
|
5133
5638
|
this.#pendingAgentEndEmit = event;
|
|
5134
5639
|
return;
|
|
@@ -5137,8 +5642,12 @@ export class AgentSession {
|
|
|
5137
5642
|
if (event.type === "agent_end") {
|
|
5138
5643
|
// Start the durable terminal write before synchronous subscribers can
|
|
5139
5644
|
// re-enter prompt(), so a successor's running transition serializes after it.
|
|
5140
|
-
|
|
5645
|
+
// Do not let a lock-hostile sidecar suppress the terminal event itself.
|
|
5646
|
+
const terminalPersistence = this.#queueCoordinatorRuntimeStatePersist(event);
|
|
5141
5647
|
this.#emit(event);
|
|
5648
|
+
void terminalPersistence.catch(error => {
|
|
5649
|
+
logger.warn("Failed to persist terminal coordinator runtime state", { error: String(error) });
|
|
5650
|
+
});
|
|
5142
5651
|
await this.#emitExtensionEvent(event);
|
|
5143
5652
|
return;
|
|
5144
5653
|
}
|
|
@@ -5234,6 +5743,7 @@ export class AgentSession {
|
|
|
5234
5743
|
event: AgentEvent,
|
|
5235
5744
|
activePromptHandle?: string,
|
|
5236
5745
|
canonicalAdmission?: CanonicalMessageAdmission,
|
|
5746
|
+
eventLease?: RunResourceProducerLease,
|
|
5237
5747
|
): Promise<void> => {
|
|
5238
5748
|
const attemptScope = (event as AgentEvent & { scope?: AttemptScope }).scope;
|
|
5239
5749
|
|
|
@@ -5573,7 +6083,7 @@ export class AgentSession {
|
|
|
5573
6083
|
});
|
|
5574
6084
|
}
|
|
5575
6085
|
|
|
5576
|
-
await this.#emitSessionEvent(displayEvent);
|
|
6086
|
+
await this.#emitSessionEvent(displayEvent, eventLease);
|
|
5577
6087
|
if (event.type === "message_end" && event.message.role === "assistant") {
|
|
5578
6088
|
this.#assistantAttemptScopes.set(event.message, {
|
|
5579
6089
|
scope: attemptScope,
|
|
@@ -5923,6 +6433,7 @@ export class AgentSession {
|
|
|
5923
6433
|
skipCompactionCheck: true,
|
|
5924
6434
|
resourceRunId: activePromptHandle,
|
|
5925
6435
|
maintenanceContinuation: true,
|
|
6436
|
+
sdkRunToken: attemptScope ? this.#sdkRunTokensByAttemptScope.get(attemptScope) : undefined,
|
|
5926
6437
|
});
|
|
5927
6438
|
}
|
|
5928
6439
|
return;
|
|
@@ -6210,8 +6721,11 @@ export class AgentSession {
|
|
|
6210
6721
|
predecessorAgentEndHold?: symbol;
|
|
6211
6722
|
/** Internal predecessor terminal event sequestered while waiting behind selection. */
|
|
6212
6723
|
deferredPredecessorAgentEnd?: AgentSessionEvent;
|
|
6724
|
+
/** Internal causal SDK owner captured when this continuation was scheduled. */
|
|
6725
|
+
sdkRunToken?: string;
|
|
6213
6726
|
}): Promise<void> {
|
|
6214
6727
|
const continuationAdmission = this.#captureScheduledContinuationAdmission();
|
|
6728
|
+
const scheduledSdkRunToken = options?.sdkRunToken;
|
|
6215
6729
|
const selectionFenceGeneration =
|
|
6216
6730
|
options?.selectionFenceGeneration ??
|
|
6217
6731
|
this.#selectionFenceGenerationContext.getStore() ??
|
|
@@ -6233,6 +6747,7 @@ export class AgentSession {
|
|
|
6233
6747
|
generation: deferredPromptGeneration,
|
|
6234
6748
|
selectionFenceGeneration,
|
|
6235
6749
|
deferredPredecessorAgentEnd,
|
|
6750
|
+
sdkRunToken: scheduledSdkRunToken,
|
|
6236
6751
|
});
|
|
6237
6752
|
} finally {
|
|
6238
6753
|
// The recursive call synchronously re-reserved its settlement
|
|
@@ -6368,18 +6883,23 @@ export class AgentSession {
|
|
|
6368
6883
|
// agent_start (review P1); their queued messages are in-run
|
|
6369
6884
|
// consumptions, not own-run promotions.
|
|
6370
6885
|
const startsOwn = options?.maintenanceContinuation !== true;
|
|
6886
|
+
const inheritedSdkRunToken = options?.continueQueuedOnly
|
|
6887
|
+
? undefined
|
|
6888
|
+
: (options?.sdkRunToken ?? scheduledSdkRunToken);
|
|
6889
|
+
const consumedSdkRunToken = acceptance.consumedQueuedMessages
|
|
6890
|
+
.map(message => this.#sdkRunTokensByQueuedMessage.get(message))
|
|
6891
|
+
.find((token): token is string => token !== undefined);
|
|
6892
|
+
const sdkRunToken = consumedSdkRunToken ?? inheritedSdkRunToken;
|
|
6371
6893
|
this.#fireQueuedPromotionHooks(acceptance.consumedQueuedMessages, {
|
|
6372
6894
|
startsOwnRun: startsOwn,
|
|
6373
6895
|
});
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
if (sdkRunToken) {
|
|
6377
|
-
this.#sdkRunTokensByAttemptScope.set(handle.scope, sdkRunToken);
|
|
6378
|
-
break;
|
|
6379
|
-
}
|
|
6380
|
-
}
|
|
6381
|
-
this.#acceptRunHandle(handle);
|
|
6896
|
+
if (startsOwn) this.#activeSdkRunToken = sdkRunToken;
|
|
6897
|
+
this.#acceptSdkAttemptRun(handle, sdkRunToken);
|
|
6382
6898
|
options?.onRunAccepted?.(handle);
|
|
6899
|
+
// Keep the queued token available through the acceptance callback;
|
|
6900
|
+
// SDK follow-up owners bind it to the new attempt scope there.
|
|
6901
|
+
for (const message of acceptance.consumedQueuedMessages)
|
|
6902
|
+
this.#sdkRunTokensByQueuedMessage.delete(message);
|
|
6383
6903
|
settleLease();
|
|
6384
6904
|
releasePredecessor();
|
|
6385
6905
|
if (startsQueuedSuccessor) {
|
|
@@ -6537,8 +7057,12 @@ export class AgentSession {
|
|
|
6537
7057
|
resourceRunId?: string,
|
|
6538
7058
|
deferredSelectionFenceGeneration?: number,
|
|
6539
7059
|
deferredPredecessorAgentEnd?: AgentSessionEvent,
|
|
7060
|
+
sdkRunToken?: string,
|
|
6540
7061
|
): void {
|
|
6541
7062
|
const scheduledGeneration = generation;
|
|
7063
|
+
const scheduledSdkRunToken =
|
|
7064
|
+
sdkRunToken ??
|
|
7065
|
+
(this.#activeAttemptScope ? this.#sdkRunTokensByAttemptScope.get(this.#activeAttemptScope) : undefined);
|
|
6542
7066
|
const continuationAuthorized = async (
|
|
6543
7067
|
signal: AbortSignal,
|
|
6544
7068
|
hasPendingNextTurnMessages = false,
|
|
@@ -6591,6 +7115,7 @@ export class AgentSession {
|
|
|
6591
7115
|
resourceRunId,
|
|
6592
7116
|
selectionFenceGeneration,
|
|
6593
7117
|
predecessorAgentEnd,
|
|
7118
|
+
scheduledSdkRunToken,
|
|
6594
7119
|
);
|
|
6595
7120
|
} finally {
|
|
6596
7121
|
this.#endSelectionFenceDeferralTracking(selectionFenceGeneration);
|
|
@@ -6638,6 +7163,7 @@ export class AgentSession {
|
|
|
6638
7163
|
promptText,
|
|
6639
7164
|
{
|
|
6640
7165
|
skipPostPromptRecoveryWait: true,
|
|
7166
|
+
sdkRunToken: scheduledSdkRunToken,
|
|
6641
7167
|
skipCompactionCheck: true,
|
|
6642
7168
|
predecessorAgentEndHold,
|
|
6643
7169
|
onFinalPreflight: ({ hasPendingNextTurnMessages }) =>
|
|
@@ -7415,16 +7941,16 @@ export class AgentSession {
|
|
|
7415
7941
|
this.#workerIntegrationScheduler?.enqueue();
|
|
7416
7942
|
}
|
|
7417
7943
|
|
|
7418
|
-
async #flushWorkerIntegrationAttempt(): Promise<
|
|
7419
|
-
await this.#workerIntegrationScheduler?.
|
|
7944
|
+
async #flushWorkerIntegrationAttempt(): Promise<WorkerIntegrationOutcome> {
|
|
7945
|
+
return (await this.#workerIntegrationScheduler?.flushWithOutcome()) ?? { status: "completed" };
|
|
7420
7946
|
}
|
|
7421
7947
|
|
|
7422
|
-
async #flushWorkerIntegrationForAgentEnd(): Promise<
|
|
7948
|
+
async #flushWorkerIntegrationForAgentEnd(): Promise<WorkerIntegrationOutcome> {
|
|
7423
7949
|
if (!this.#workerIntegrationRequestedForTurn) {
|
|
7424
7950
|
this.#requestWorkerIntegrationAttempt();
|
|
7425
7951
|
}
|
|
7426
7952
|
try {
|
|
7427
|
-
await this.#flushWorkerIntegrationAttempt();
|
|
7953
|
+
return await this.#flushWorkerIntegrationAttempt();
|
|
7428
7954
|
} finally {
|
|
7429
7955
|
this.#workerIntegrationRequestedForTurn = false;
|
|
7430
7956
|
}
|
|
@@ -7437,16 +7963,53 @@ export class AgentSession {
|
|
|
7437
7963
|
workerIntegrationSettled = false,
|
|
7438
7964
|
scope?: AttemptScopeRef,
|
|
7439
7965
|
): Promise<void> {
|
|
7966
|
+
const admission = this.#agentEventAdmission.get(event);
|
|
7967
|
+
const activeAttemptScopeAtEvent = admission === undefined ? this.#activeAttemptScope : admission.scope;
|
|
7968
|
+
const activeSdkRunTokenAtEvent = admission === undefined ? this.#activeSdkRunToken : admission.sdkRunToken;
|
|
7440
7969
|
if (event.type === "agent_end" && !workerIntegrationSettled) {
|
|
7441
7970
|
await this.#flushWorkerIntegrationForAgentEnd();
|
|
7442
7971
|
}
|
|
7443
7972
|
const deliveryScope = scope ?? (event as AgentSessionEvent & { scope?: AttemptScopeRef }).scope;
|
|
7973
|
+
const eventToken = (event as AgentSessionEvent & { sdkRunToken?: unknown }).sdkRunToken;
|
|
7974
|
+
const sdkRunToken =
|
|
7975
|
+
typeof eventToken === "string"
|
|
7976
|
+
? eventToken
|
|
7977
|
+
: deliveryScope
|
|
7978
|
+
? this.#sdkRunTokensByAttemptScope.get(deliveryScope as AttemptScope)
|
|
7979
|
+
: undefined;
|
|
7444
7980
|
const isTerminalAgentEnd =
|
|
7445
7981
|
event.type === "agent_end" && !(event.stopReason === "maintenance" && event.maintenanceOutcome !== "aborted");
|
|
7446
7982
|
const finishAttempt = () => {
|
|
7447
7983
|
if (!isTerminalAgentEnd) return;
|
|
7448
|
-
|
|
7449
|
-
|
|
7984
|
+
// A merged wake batch whose flush fired while still streaming cleared its
|
|
7985
|
+
// pending flag without rescheduling. Returning to idle is exactly when it
|
|
7986
|
+
// becomes deliverable again, so rearm here rather than waiting for an
|
|
7987
|
+
// unrelated enqueue.
|
|
7988
|
+
this.yieldQueue.rearmIdle();
|
|
7989
|
+
const isActiveAttempt =
|
|
7990
|
+
deliveryScope === undefined
|
|
7991
|
+
? this.#activeAttemptScope === activeAttemptScopeAtEvent &&
|
|
7992
|
+
this.#activeSdkRunToken === activeSdkRunTokenAtEvent
|
|
7993
|
+
: deliveryScope === activeAttemptScopeAtEvent &&
|
|
7994
|
+
deliveryScope === this.#activeAttemptScope &&
|
|
7995
|
+
this.#activeSdkRunToken === activeSdkRunTokenAtEvent;
|
|
7996
|
+
if (this.#foldStopRequested) {
|
|
7997
|
+
this.#foldStopRequested = false;
|
|
7998
|
+
this.agent.setSteeringAdmissionFence(undefined);
|
|
7999
|
+
}
|
|
8000
|
+
if (deliveryScope) {
|
|
8001
|
+
this.#attemptRecordStore.retire(deliveryScope as AttemptScope);
|
|
8002
|
+
if (
|
|
8003
|
+
activeSdkRunTokenAtEvent !== undefined &&
|
|
8004
|
+
this.#sdkRunTokensByAttemptScope.get(deliveryScope as AttemptScope) === activeSdkRunTokenAtEvent
|
|
8005
|
+
)
|
|
8006
|
+
this.#sdkRunTokensByAttemptScope.delete(deliveryScope as AttemptScope);
|
|
8007
|
+
}
|
|
8008
|
+
if (isActiveAttempt) this.#activeSdkRunToken = undefined;
|
|
8009
|
+
if (isActiveAttempt) {
|
|
8010
|
+
this.#activeAttemptScope = undefined;
|
|
8011
|
+
this.#activeLogicalRunId = undefined;
|
|
8012
|
+
}
|
|
7450
8013
|
};
|
|
7451
8014
|
if (!this.#extensionRunner) {
|
|
7452
8015
|
finishAttempt();
|
|
@@ -7459,9 +8022,7 @@ export class AgentSession {
|
|
|
7459
8022
|
await this.#extensionRunner.emit(
|
|
7460
8023
|
{
|
|
7461
8024
|
type: "agent_start",
|
|
7462
|
-
...(
|
|
7463
|
-
? { sdkRunToken: this.#sdkRunTokensByAttemptScope.get(deliveryScope as AttemptScope) }
|
|
7464
|
-
: {}),
|
|
8025
|
+
...(sdkRunToken ? { sdkRunToken } : {}),
|
|
7465
8026
|
},
|
|
7466
8027
|
undefined,
|
|
7467
8028
|
deliveryScope,
|
|
@@ -7472,6 +8033,7 @@ export class AgentSession {
|
|
|
7472
8033
|
type: "agent_failed",
|
|
7473
8034
|
error: sanitizePromptFailure(event.error),
|
|
7474
8035
|
scope: event.scope,
|
|
8036
|
+
...(sdkRunToken ? { sdkRunToken } : {}),
|
|
7475
8037
|
},
|
|
7476
8038
|
undefined,
|
|
7477
8039
|
deliveryScope,
|
|
@@ -7483,6 +8045,7 @@ export class AgentSession {
|
|
|
7483
8045
|
messages: event.messages,
|
|
7484
8046
|
stopReason: event.stopReason,
|
|
7485
8047
|
maintenanceOutcome: event.maintenanceOutcome,
|
|
8048
|
+
...(sdkRunToken ? { sdkRunToken } : {}),
|
|
7486
8049
|
},
|
|
7487
8050
|
undefined,
|
|
7488
8051
|
deliveryScope,
|
|
@@ -7710,8 +8273,18 @@ export class AgentSession {
|
|
|
7710
8273
|
const sid = this.#providerSessionId ?? sessionId ?? this.sessionManager.getSessionId();
|
|
7711
8274
|
this.agent.sessionId = sid;
|
|
7712
8275
|
this.agent.providerSessionId = this.#providerCacheSessionId ?? sid;
|
|
7713
|
-
this.agent.setMetadataResolver(
|
|
7714
|
-
buildSessionMetadata(
|
|
8276
|
+
this.agent.setMetadataResolver(context =>
|
|
8277
|
+
buildSessionMetadata(
|
|
8278
|
+
sid,
|
|
8279
|
+
context.provider,
|
|
8280
|
+
this.#modelRegistry.authStorage,
|
|
8281
|
+
this.credentialSessionId,
|
|
8282
|
+
context.model ?? this.model,
|
|
8283
|
+
// Optional call: the metadata resolver runs on every prompt, and `owner` is an
|
|
8284
|
+
// optional `buildSessionMetadata` argument. The auth-credential paths keep the
|
|
8285
|
+
// strict call because they genuinely require a real registry owner.
|
|
8286
|
+
this.#modelRegistry.getAuthStorageOwner?.(),
|
|
8287
|
+
),
|
|
7715
8288
|
);
|
|
7716
8289
|
}
|
|
7717
8290
|
|
|
@@ -8324,13 +8897,39 @@ export class AgentSession {
|
|
|
8324
8897
|
* handler, so Ctrl+B-style folding fails closed instead of aborting or
|
|
8325
8898
|
* shell-suspending arbitrary work.
|
|
8326
8899
|
*/
|
|
8327
|
-
|
|
8328
|
-
this.#
|
|
8329
|
-
|
|
8330
|
-
|
|
8331
|
-
|
|
8332
|
-
|
|
8333
|
-
|
|
8900
|
+
registerForegroundFoldParticipant(adapter: FoldAdapter): () => void {
|
|
8901
|
+
return this.#foldCoordinator.registerParticipant(adapter);
|
|
8902
|
+
}
|
|
8903
|
+
|
|
8904
|
+
/** The session-owned fold coordinator, for delivery-side receipt lookup. */
|
|
8905
|
+
get foldCoordinator(): FoldCoordinator {
|
|
8906
|
+
return this.#foldCoordinator;
|
|
8907
|
+
}
|
|
8908
|
+
|
|
8909
|
+
/**
|
|
8910
|
+
* Best-effort capture of what the interrupted turn still intended to do, so
|
|
8911
|
+
* the wake turn can finish the original task instead of only reporting output.
|
|
8912
|
+
*/
|
|
8913
|
+
#captureRemainingIntentForFold(): string | undefined {
|
|
8914
|
+
const messages = this.agent.state.messages;
|
|
8915
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
8916
|
+
const message = messages[index];
|
|
8917
|
+
if (message?.role !== "user") continue;
|
|
8918
|
+
const content = message.content;
|
|
8919
|
+
const text =
|
|
8920
|
+
typeof content === "string"
|
|
8921
|
+
? content
|
|
8922
|
+
: Array.isArray(content)
|
|
8923
|
+
? content
|
|
8924
|
+
.map(block => (typeof block === "object" && block && "text" in block ? String(block.text) : ""))
|
|
8925
|
+
.filter(Boolean)
|
|
8926
|
+
.join("\n")
|
|
8927
|
+
: "";
|
|
8928
|
+
const trimmed = text.trim();
|
|
8929
|
+
if (trimmed.length === 0) return undefined;
|
|
8930
|
+
return trimmed.length > 2000 ? `${trimmed.slice(0, 2000)}…` : trimmed;
|
|
8931
|
+
}
|
|
8932
|
+
return undefined;
|
|
8334
8933
|
}
|
|
8335
8934
|
|
|
8336
8935
|
/**
|
|
@@ -8339,7 +8938,7 @@ export class AgentSession {
|
|
|
8339
8938
|
* no fold target exists.
|
|
8340
8939
|
*/
|
|
8341
8940
|
hasForegroundBashBackgroundRequestHandler(): boolean {
|
|
8342
|
-
return this.#
|
|
8941
|
+
return this.#foldCoordinator.hasFoldableParticipant();
|
|
8343
8942
|
}
|
|
8344
8943
|
|
|
8345
8944
|
/** Set the SDK permission policy used by guarded ACP tool execution. */
|
|
@@ -8375,11 +8974,17 @@ export class AgentSession {
|
|
|
8375
8974
|
* Ask the active managed foreground bash call to return as a background job.
|
|
8376
8975
|
* Returns false when no supported foreground tool is currently backgroundable.
|
|
8377
8976
|
*/
|
|
8378
|
-
requestForegroundBashBackground(): boolean {
|
|
8379
|
-
|
|
8380
|
-
|
|
8381
|
-
|
|
8382
|
-
|
|
8977
|
+
async requestForegroundBashBackground(): Promise<boolean> {
|
|
8978
|
+
if (!this.#foldCoordinator.hasFoldableParticipant()) return false;
|
|
8979
|
+
try {
|
|
8980
|
+
const result = await this.#foldCoordinator.requestFold();
|
|
8981
|
+
return result.status === "folded";
|
|
8982
|
+
} catch (error) {
|
|
8983
|
+
logger.warn("Foreground fold request failed", {
|
|
8984
|
+
error: error instanceof Error ? error.message : String(error),
|
|
8985
|
+
});
|
|
8986
|
+
return false;
|
|
8987
|
+
}
|
|
8383
8988
|
}
|
|
8384
8989
|
|
|
8385
8990
|
/**
|
|
@@ -9320,6 +9925,7 @@ export class AgentSession {
|
|
|
9320
9925
|
if (this.#sessionAdmissionClosed || this.#isDisposed) throw this.#sessionAdmissionBusyError();
|
|
9321
9926
|
const inFlightPrompt = this.#beginInFlight();
|
|
9322
9927
|
let hindsightRecall: string | undefined;
|
|
9928
|
+
let continuationSdkRunToken: string | undefined;
|
|
9323
9929
|
try {
|
|
9324
9930
|
const volatileProjectContextMessage = await this.#buildVolatileProjectContextMessage();
|
|
9325
9931
|
this.agent.appendMessage(volatileProjectContextMessage);
|
|
@@ -9355,14 +9961,18 @@ export class AgentSession {
|
|
|
9355
9961
|
await this.agent.continue({
|
|
9356
9962
|
...this.#managedFallbackPromptOptions(),
|
|
9357
9963
|
onRunAccepted: (handle: AttemptRunHandle) => {
|
|
9358
|
-
this.#
|
|
9964
|
+
continuationSdkRunToken = this.#activeSdkRunToken;
|
|
9965
|
+
this.#acceptSdkAttemptRun(handle, continuationSdkRunToken);
|
|
9359
9966
|
if (hindsightRecall) hindsightState?.markRecallSnippetInjected(hindsightRecall);
|
|
9360
9967
|
},
|
|
9361
9968
|
});
|
|
9362
|
-
await this.#waitForPostPromptRecovery();
|
|
9969
|
+
if (continuationSdkRunToken === undefined) await this.#waitForPostPromptRecovery();
|
|
9363
9970
|
} finally {
|
|
9364
9971
|
this.#removeEphemeralCustomMessages();
|
|
9365
|
-
await this.#settleEndedInFlight(
|
|
9972
|
+
await this.#settleEndedInFlight(
|
|
9973
|
+
inFlightPrompt,
|
|
9974
|
+
continuationSdkRunToken === undefined ? "full" : "publication",
|
|
9975
|
+
);
|
|
9366
9976
|
}
|
|
9367
9977
|
}
|
|
9368
9978
|
|
|
@@ -9490,8 +10100,9 @@ export class AgentSession {
|
|
|
9490
10100
|
async setCredentialPin(provider: string, selector: AuthCredentialSelector): Promise<void> {
|
|
9491
10101
|
const scopeId = this.credentialSessionId;
|
|
9492
10102
|
const authStorage = this.#modelRegistry.authStorage;
|
|
9493
|
-
const
|
|
9494
|
-
authStorage.
|
|
10103
|
+
const authStorageOwner = this.#modelRegistry.getAuthStorageOwner();
|
|
10104
|
+
const target = authStorage.resolveOAuthPinTarget(provider, selector, authStorageOwner);
|
|
10105
|
+
authStorage.setSessionCredentialSelector(scopeId, provider, target.canonicalSelector, authStorageOwner);
|
|
9495
10106
|
if (target.canonicalSelector.kind === "id" && !this.#credentialStoreIdentity) return;
|
|
9496
10107
|
this.sessionManager.appendCustomEntry("auth-credential-pin", {
|
|
9497
10108
|
v: 1,
|
|
@@ -9571,9 +10182,13 @@ export class AgentSession {
|
|
|
9571
10182
|
args = "",
|
|
9572
10183
|
options?: Pick<
|
|
9573
10184
|
PromptOptions,
|
|
9574
|
-
"onPreflightAccepted" | "onPreflightAcceptCommit" | "onSkillPrepared" | "preflightSignal"
|
|
10185
|
+
"onPreflightAccepted" | "onPreflightAcceptCommit" | "onSkillPrepared" | "preflightSignal" | "sdkRunCapability"
|
|
9575
10186
|
>,
|
|
9576
10187
|
): Promise<{ name: string; path: string; args?: string; lineCount?: number }> {
|
|
10188
|
+
const internalOptions = {
|
|
10189
|
+
...options,
|
|
10190
|
+
sdkRunToken: readSdkRunCapability(options?.sdkRunCapability),
|
|
10191
|
+
};
|
|
9577
10192
|
if (options?.preflightSignal?.aborted) throw promptPreflightCancelledError();
|
|
9578
10193
|
const skillName = name.trim();
|
|
9579
10194
|
if (!skillName) throw Object.assign(new Error("skill.invoke requires a skill name."), { code: "invalid_input" });
|
|
@@ -9619,7 +10234,7 @@ export class AgentSession {
|
|
|
9619
10234
|
lineCount: built.details.lineCount,
|
|
9620
10235
|
cleanedArgs: activation.cleanedArgs || undefined,
|
|
9621
10236
|
});
|
|
9622
|
-
await this.promptCustomMessage(skillPromptMessage,
|
|
10237
|
+
await this.promptCustomMessage(skillPromptMessage, internalOptions as InternalCustomMessageOptions);
|
|
9623
10238
|
return {
|
|
9624
10239
|
name: skill.name,
|
|
9625
10240
|
path: skill.filePath,
|
|
@@ -10309,6 +10924,14 @@ export class AgentSession {
|
|
|
10309
10924
|
* @throws Error if no model selected or no API key available (when not streaming)
|
|
10310
10925
|
*/
|
|
10311
10926
|
async prompt(text: string, options?: PromptOptions): Promise<void> {
|
|
10927
|
+
const hasUsableImage =
|
|
10928
|
+
options?.images?.some(image => typeof image?.data === "string" && image.data.trim().length > 0) === true;
|
|
10929
|
+
if (typeof text !== "string" || (text.trim().length === 0 && !hasUsableImage))
|
|
10930
|
+
throw Object.assign(new Error("Prompt must not be empty."), { code: "invalid_input" });
|
|
10931
|
+
const sdkRunToken = readSdkRunCapability(options?.sdkRunCapability);
|
|
10932
|
+
const internalOptions: InternalPromptOptions | undefined = options
|
|
10933
|
+
? { ...options, ...(sdkRunToken ? { sdkRunToken } : {}) }
|
|
10934
|
+
: undefined;
|
|
10312
10935
|
this.#assertRecoveryHydrationPromoted();
|
|
10313
10936
|
const owner = this.#sessionAdmissionContext.getStore();
|
|
10314
10937
|
if (owner && !owner.released) throw this.#sessionAdmissionBusyError();
|
|
@@ -10323,13 +10946,19 @@ export class AgentSession {
|
|
|
10323
10946
|
await this.invokeSkill(
|
|
10324
10947
|
invocation.skill.name,
|
|
10325
10948
|
invocation.args,
|
|
10326
|
-
options?.onPreflightAccepted ||
|
|
10949
|
+
options?.onPreflightAccepted ||
|
|
10950
|
+
options?.onPreflightAcceptCommit ||
|
|
10951
|
+
options?.preflightSignal ||
|
|
10952
|
+
internalOptions?.sdkRunToken
|
|
10327
10953
|
? {
|
|
10328
|
-
...(options
|
|
10329
|
-
|
|
10954
|
+
...(options?.onPreflightAccepted
|
|
10955
|
+
? { onPreflightAccepted: options.onPreflightAccepted }
|
|
10956
|
+
: {}),
|
|
10957
|
+
...(options?.onPreflightAcceptCommit
|
|
10330
10958
|
? { onPreflightAcceptCommit: options.onPreflightAcceptCommit }
|
|
10331
10959
|
: {}),
|
|
10332
|
-
...(options
|
|
10960
|
+
...(options?.preflightSignal ? { preflightSignal: options.preflightSignal } : {}),
|
|
10961
|
+
...(options?.sdkRunCapability ? { sdkRunCapability: options.sdkRunCapability } : {}),
|
|
10333
10962
|
}
|
|
10334
10963
|
: undefined,
|
|
10335
10964
|
);
|
|
@@ -10363,6 +10992,10 @@ export class AgentSession {
|
|
|
10363
10992
|
|
|
10364
10993
|
// Expand file-based prompt templates if requested
|
|
10365
10994
|
const expandedText = expandPromptTemplates ? expandPromptTemplate(text, [...this.#promptTemplates]) : text;
|
|
10995
|
+
const expandedHasUsableImage =
|
|
10996
|
+
options?.images?.some(image => typeof image?.data === "string" && image.data.trim().length > 0) === true;
|
|
10997
|
+
if (expandedText.trim().length === 0 && !expandedHasUsableImage)
|
|
10998
|
+
throw Object.assign(new Error("Prompt must not be empty."), { code: "invalid_input" });
|
|
10366
10999
|
assertImagePlaceholdersHavePayload(expandedText, options?.images);
|
|
10367
11000
|
const workflowIntentDiff = options?.synthetic ? null : buildWorkflowIntentDiff(expandedText);
|
|
10368
11001
|
const claimsGenuineUserIntent = !options?.synthetic && options?.attribution !== "agent";
|
|
@@ -10583,8 +11216,13 @@ export class AgentSession {
|
|
|
10583
11216
|
| "onPreflightAccepted"
|
|
10584
11217
|
| "onPreflightAcceptCommit"
|
|
10585
11218
|
| "preflightSignal"
|
|
11219
|
+
| "sdkRunCapability"
|
|
10586
11220
|
>,
|
|
10587
11221
|
): Promise<void> {
|
|
11222
|
+
const sdkRunToken = readSdkRunCapability(options?.sdkRunCapability);
|
|
11223
|
+
const internalOptions: InternalCustomMessageOptions | undefined = options
|
|
11224
|
+
? { ...options, ...(sdkRunToken ? { sdkRunToken } : {}) }
|
|
11225
|
+
: undefined;
|
|
10588
11226
|
if (options?.preflightSignal?.aborted) throw promptPreflightCancelledError();
|
|
10589
11227
|
const textContent =
|
|
10590
11228
|
typeof message.content === "string"
|
|
@@ -10653,7 +11291,7 @@ export class AgentSession {
|
|
|
10653
11291
|
};
|
|
10654
11292
|
try {
|
|
10655
11293
|
await this.#promptWithMessage(customMessage, textContent, {
|
|
10656
|
-
...
|
|
11294
|
+
...internalOptions,
|
|
10657
11295
|
onPreflightAccepted: undefined,
|
|
10658
11296
|
onPreflightAcceptCommit: commitAcceptance,
|
|
10659
11297
|
admissionLease: admission,
|
|
@@ -10685,6 +11323,7 @@ export class AgentSession {
|
|
|
10685
11323
|
| "onPreflightAcceptCommit"
|
|
10686
11324
|
| "preflightSignal"
|
|
10687
11325
|
> & {
|
|
11326
|
+
sdkRunToken?: string;
|
|
10688
11327
|
prependMessages?: AgentMessage[];
|
|
10689
11328
|
skipPostPromptRecoveryWait?: boolean;
|
|
10690
11329
|
predecessorAgentEndHold?: symbol;
|
|
@@ -10751,6 +11390,9 @@ export class AgentSession {
|
|
|
10751
11390
|
const rosterClaim = this.#claimIrcRosterCandidate();
|
|
10752
11391
|
let hasPendingNextTurnMessages = false;
|
|
10753
11392
|
let pendingNextTurnMessageCount = 0;
|
|
11393
|
+
let skipPostPromptRecoveryWait =
|
|
11394
|
+
options?.skipPostPromptRecoveryWait === true || options?.sdkRunToken !== undefined;
|
|
11395
|
+
let promptAttemptScope: AttemptScope | undefined;
|
|
10754
11396
|
let hindsightRecall: string | undefined;
|
|
10755
11397
|
try {
|
|
10756
11398
|
this.#throwIfPromptPreflightCancelled(generation, preflightSignal);
|
|
@@ -11017,8 +11659,14 @@ export class AgentSession {
|
|
|
11017
11659
|
const agentPromptOptions = {
|
|
11018
11660
|
...(options?.toolChoice ? { toolChoice: options.toolChoice } : undefined),
|
|
11019
11661
|
...this.#managedFallbackPromptOptions(),
|
|
11662
|
+
...(options?.sdkRunToken ? { sdkRunToken: options.sdkRunToken } : {}),
|
|
11020
11663
|
onRunAccepted: (handle: AttemptRunHandle) => {
|
|
11021
|
-
|
|
11664
|
+
promptAttemptScope = handle?.scope;
|
|
11665
|
+
if (handle) this.#acceptSdkAttemptRun(handle, options?.sdkRunToken);
|
|
11666
|
+
else this.#acceptRunHandle(handle);
|
|
11667
|
+
if (handle && options?.sdkRunToken) {
|
|
11668
|
+
this.#activeSdkRunToken = options.sdkRunToken;
|
|
11669
|
+
}
|
|
11022
11670
|
options?.onRunAccepted?.(handle);
|
|
11023
11671
|
options?.admissionLease?.release();
|
|
11024
11672
|
// R3.3: the accepted-run wrapper is the exact acceptance boundary —
|
|
@@ -11051,6 +11699,20 @@ export class AgentSession {
|
|
|
11051
11699
|
options?.onPreflightAccepted?.();
|
|
11052
11700
|
},
|
|
11053
11701
|
});
|
|
11702
|
+
const activeTerminalScope = this.#activeAttemptScope;
|
|
11703
|
+
const terminalAttemptScope =
|
|
11704
|
+
activeTerminalScope !== undefined &&
|
|
11705
|
+
(this.#skipPostPromptRecoveryWaitByAttemptScope.has(activeTerminalScope) ||
|
|
11706
|
+
(options?.sdkRunToken !== undefined &&
|
|
11707
|
+
this.#sdkRunTokensByAttemptScope.get(activeTerminalScope) === options.sdkRunToken))
|
|
11708
|
+
? activeTerminalScope
|
|
11709
|
+
: promptAttemptScope;
|
|
11710
|
+
if (
|
|
11711
|
+
terminalAttemptScope !== undefined &&
|
|
11712
|
+
this.#skipPostPromptRecoveryWaitByAttemptScope.delete(terminalAttemptScope)
|
|
11713
|
+
) {
|
|
11714
|
+
skipPostPromptRecoveryWait = true;
|
|
11715
|
+
}
|
|
11054
11716
|
const terminalAssistant = this.#findLastAssistantMessage();
|
|
11055
11717
|
if (
|
|
11056
11718
|
rosterClaim &&
|
|
@@ -11060,7 +11722,7 @@ export class AgentSession {
|
|
|
11060
11722
|
) {
|
|
11061
11723
|
this.#commitIrcRosterClaim(rosterClaim.token, rosterClaim.epoch);
|
|
11062
11724
|
}
|
|
11063
|
-
if (!
|
|
11725
|
+
if (!skipPostPromptRecoveryWait) {
|
|
11064
11726
|
await this.#waitForPostPromptRecovery();
|
|
11065
11727
|
}
|
|
11066
11728
|
} catch (error) {
|
|
@@ -11085,7 +11747,7 @@ export class AgentSession {
|
|
|
11085
11747
|
this.#releaseIrcRosterClaim(rosterClaim.token, rosterClaim.epoch);
|
|
11086
11748
|
}
|
|
11087
11749
|
this.#releaseDeferredAgentEndContinuation(predecessorAgentEndHold);
|
|
11088
|
-
await this.#settleEndedInFlight(inFlightPrompt,
|
|
11750
|
+
await this.#settleEndedInFlight(inFlightPrompt, skipPostPromptRecoveryWait ? "publication" : "full");
|
|
11089
11751
|
}
|
|
11090
11752
|
}
|
|
11091
11753
|
|
|
@@ -11289,12 +11951,18 @@ export class AgentSession {
|
|
|
11289
11951
|
* Queue a steering message to interrupt the agent mid-run.
|
|
11290
11952
|
*/
|
|
11291
11953
|
async steer(text: string, images?: ImageContent[]): Promise<void> {
|
|
11954
|
+
const hasUsableImage =
|
|
11955
|
+
images?.some(image => typeof image?.data === "string" && image.data.trim().length > 0) === true;
|
|
11956
|
+
if (typeof text !== "string" || (text.trim().length === 0 && !hasUsableImage))
|
|
11957
|
+
throw Object.assign(new Error("Prompt must not be empty."), { code: "invalid_input" });
|
|
11292
11958
|
this.#assertRecoveryHydrationPromoted();
|
|
11293
11959
|
if (text.startsWith("/")) {
|
|
11294
11960
|
this.#throwIfExtensionCommand(text);
|
|
11295
11961
|
}
|
|
11296
11962
|
|
|
11297
11963
|
const expandedText = expandPromptTemplate(text, [...this.#promptTemplates]);
|
|
11964
|
+
if (expandedText.trim().length === 0 && !hasUsableImage)
|
|
11965
|
+
throw Object.assign(new Error("Prompt must not be empty."), { code: "invalid_input" });
|
|
11298
11966
|
assertImagePlaceholdersHavePayload(expandedText, images);
|
|
11299
11967
|
await this.#queueSteer(expandedText, images, { claimsGenuineUserIntent: true });
|
|
11300
11968
|
}
|
|
@@ -11307,12 +11975,18 @@ export class AgentSession {
|
|
|
11307
11975
|
images?: ImageContent[],
|
|
11308
11976
|
options?: Pick<PromptOptions, "followUpQueuePolicy">,
|
|
11309
11977
|
): Promise<void> {
|
|
11978
|
+
const hasUsableImage =
|
|
11979
|
+
images?.some(image => typeof image?.data === "string" && image.data.trim().length > 0) === true;
|
|
11980
|
+
if (typeof text !== "string" || (text.trim().length === 0 && !hasUsableImage))
|
|
11981
|
+
throw Object.assign(new Error("Prompt must not be empty."), { code: "invalid_input" });
|
|
11310
11982
|
this.#assertRecoveryHydrationPromoted();
|
|
11311
11983
|
if (text.startsWith("/")) {
|
|
11312
11984
|
this.#throwIfExtensionCommand(text);
|
|
11313
11985
|
}
|
|
11314
11986
|
|
|
11315
11987
|
const expandedText = expandPromptTemplate(text, [...this.#promptTemplates]);
|
|
11988
|
+
if (expandedText.trim().length === 0 && !hasUsableImage)
|
|
11989
|
+
throw Object.assign(new Error("Prompt must not be empty."), { code: "invalid_input" });
|
|
11316
11990
|
assertImagePlaceholdersHavePayload(expandedText, images);
|
|
11317
11991
|
await this.#queueFollowUp(expandedText, images, {
|
|
11318
11992
|
forceOneAtATime: options?.followUpQueuePolicy === "sequential",
|
|
@@ -11330,6 +12004,7 @@ export class AgentSession {
|
|
|
11330
12004
|
claimsGenuineUserIntent?: boolean;
|
|
11331
12005
|
onPromoted?: (promotion: { startsOwnRun?: boolean; removed?: boolean }) => void;
|
|
11332
12006
|
external?: boolean;
|
|
12007
|
+
sdkRunToken?: string;
|
|
11333
12008
|
},
|
|
11334
12009
|
): Promise<void> {
|
|
11335
12010
|
this.#assertNoHandoffTransition();
|
|
@@ -11344,6 +12019,7 @@ export class AgentSession {
|
|
|
11344
12019
|
this.#externalSteerAdmissionSeq.set(message, ++this.#steeringAdmissionSeq);
|
|
11345
12020
|
if (options.onPromoted) this.#steerPromotionHooks.set(message, options.onPromoted);
|
|
11346
12021
|
}
|
|
12022
|
+
if (options?.sdkRunToken) this.#sdkRunTokensByQueuedMessage.set(message, options.sdkRunToken);
|
|
11347
12023
|
if (options?.claimsGenuineUserIntent) {
|
|
11348
12024
|
const epoch = this.#claimDeepInterviewUserIntent();
|
|
11349
12025
|
this.#deepInterviewGenuineUserMessageEpochs.set(message, epoch);
|
|
@@ -11926,9 +12602,11 @@ export class AgentSession {
|
|
|
11926
12602
|
/** Internal dispatch disposition used before actual queue consumption. */
|
|
11927
12603
|
onDispatchDisposition?: (promotion: { startsOwnRun: boolean }) => void;
|
|
11928
12604
|
preflightSignal?: AbortSignal;
|
|
11929
|
-
|
|
12605
|
+
sdkRunCapability?: unknown;
|
|
11930
12606
|
},
|
|
11931
12607
|
): Promise<void> {
|
|
12608
|
+
const sdkRunToken = readSdkRunCapability(options?.sdkRunCapability);
|
|
12609
|
+
const internalOptions = options ? { ...options, ...(sdkRunToken ? { sdkRunToken } : {}) } : undefined;
|
|
11932
12610
|
this.#assertRecoveryHydrationPromoted();
|
|
11933
12611
|
const owner = this.#sessionAdmissionContext.getStore();
|
|
11934
12612
|
if (owner && !owner.released) throw this.#sessionAdmissionBusyError();
|
|
@@ -11949,7 +12627,15 @@ export class AgentSession {
|
|
|
11949
12627
|
const textParts: string[] = [];
|
|
11950
12628
|
images = [];
|
|
11951
12629
|
for (const part of content) {
|
|
12630
|
+
if (!part || typeof part !== "object" || Array.isArray(part))
|
|
12631
|
+
throw Object.assign(new Error("sendUserMessage content blocks must be objects."), {
|
|
12632
|
+
code: "invalid_input",
|
|
12633
|
+
});
|
|
11952
12634
|
if (part.type === "text") {
|
|
12635
|
+
if (typeof part.text !== "string")
|
|
12636
|
+
throw Object.assign(new Error("sendUserMessage text blocks must contain string text."), {
|
|
12637
|
+
code: "invalid_input",
|
|
12638
|
+
});
|
|
11953
12639
|
textParts.push(part.text);
|
|
11954
12640
|
} else {
|
|
11955
12641
|
images.push(part);
|
|
@@ -11958,6 +12644,10 @@ export class AgentSession {
|
|
|
11958
12644
|
text = textParts.join("\n");
|
|
11959
12645
|
if (images.length === 0) images = undefined;
|
|
11960
12646
|
}
|
|
12647
|
+
const hasUsableImage =
|
|
12648
|
+
images?.some(image => typeof image?.data === "string" && image.data.trim().length > 0) === true;
|
|
12649
|
+
if (text.trim().length === 0 && !hasUsableImage)
|
|
12650
|
+
throw Object.assign(new Error("Prompt must not be empty."), { code: "invalid_input" });
|
|
11961
12651
|
|
|
11962
12652
|
let admissionSignal = options?.preflightSignal
|
|
11963
12653
|
? AbortSignal.any([this.#promptPreflightAbortController.signal, options.preflightSignal])
|
|
@@ -12051,7 +12741,7 @@ export class AgentSession {
|
|
|
12051
12741
|
claimsGenuineUserIntent: true,
|
|
12052
12742
|
forceOneAtATime: Boolean(options?.preflightSignal || options?.queuedAtDispatch),
|
|
12053
12743
|
onPromoted: options?.onQueuedPromoted,
|
|
12054
|
-
sdkRunToken:
|
|
12744
|
+
sdkRunToken: internalOptions?.sdkRunToken,
|
|
12055
12745
|
});
|
|
12056
12746
|
const cancelQueuedFollowUp = () => queuedFollowUp.cancel();
|
|
12057
12747
|
options?.preflightSignal?.addEventListener("abort", cancelQueuedFollowUp, { once: true });
|
|
@@ -12066,6 +12756,7 @@ export class AgentSession {
|
|
|
12066
12756
|
claimsGenuineUserIntent: true,
|
|
12067
12757
|
onPromoted: options?.onQueuedPromoted,
|
|
12068
12758
|
external: true,
|
|
12759
|
+
sdkRunToken: internalOptions?.sdkRunToken,
|
|
12069
12760
|
});
|
|
12070
12761
|
options?.onPreflightAccepted?.();
|
|
12071
12762
|
return;
|
|
@@ -12085,6 +12776,7 @@ export class AgentSession {
|
|
|
12085
12776
|
claimsGenuineUserIntent: true,
|
|
12086
12777
|
onPromoted: options?.onQueuedPromoted,
|
|
12087
12778
|
external: true,
|
|
12779
|
+
sdkRunToken: internalOptions?.sdkRunToken,
|
|
12088
12780
|
});
|
|
12089
12781
|
// Dispatch-race disposition (#4668 review P1): the SDK snapshot-decided
|
|
12090
12782
|
// this submission starts its own turn (idle at dispatch), but the
|
|
@@ -12110,6 +12802,7 @@ export class AgentSession {
|
|
|
12110
12802
|
await this.prompt(text, {
|
|
12111
12803
|
expandPromptTemplates: false,
|
|
12112
12804
|
images,
|
|
12805
|
+
sdkRunToken: internalOptions?.sdkRunToken,
|
|
12113
12806
|
onPreflightAccepted: () => {
|
|
12114
12807
|
options?.onPreflightAccepted?.();
|
|
12115
12808
|
fireQueuedPromotion();
|
|
@@ -12124,7 +12817,7 @@ export class AgentSession {
|
|
|
12124
12817
|
}
|
|
12125
12818
|
: undefined,
|
|
12126
12819
|
preflightSignal: options?.preflightSignal,
|
|
12127
|
-
});
|
|
12820
|
+
} as InternalPromptOptions);
|
|
12128
12821
|
} finally {
|
|
12129
12822
|
releaseFollowUpReservation();
|
|
12130
12823
|
}
|
|
@@ -12633,6 +13326,7 @@ export class AgentSession {
|
|
|
12633
13326
|
| "tool_abort"
|
|
12634
13327
|
| "internal";
|
|
12635
13328
|
silent?: boolean;
|
|
13329
|
+
sdkRunToken?: string;
|
|
12636
13330
|
}): void {
|
|
12637
13331
|
const abortGoalState = this.getGoalModeState();
|
|
12638
13332
|
this.#suppressNextGoalReminderAfterAbortGoalId =
|
|
@@ -13334,6 +14028,9 @@ export class AgentSession {
|
|
|
13334
14028
|
attribution: "user" as const,
|
|
13335
14029
|
timestamp: Date.now(),
|
|
13336
14030
|
};
|
|
14031
|
+
const selectedSdkRunToken = selectedMessage
|
|
14032
|
+
? this.#sdkRunTokensByQueuedMessage.get(selectedMessage)
|
|
14033
|
+
: undefined;
|
|
13337
14034
|
const messageText =
|
|
13338
14035
|
message.role === "custom"
|
|
13339
14036
|
? this.#getCustomMessageTextContent(message)
|
|
@@ -13352,6 +14049,7 @@ export class AgentSession {
|
|
|
13352
14049
|
await this.#promptWithMessage(message, messageText, {
|
|
13353
14050
|
admissionLease: admission,
|
|
13354
14051
|
resetRetryReplaySafety: true,
|
|
14052
|
+
sdkRunToken: selectedSdkRunToken,
|
|
13355
14053
|
onRunAccepted: () => {
|
|
13356
14054
|
runAccepted = true;
|
|
13357
14055
|
if (selected) this.#fireQueuedPromotionHooks([message], { startsOwnRun: true });
|
|
@@ -13461,6 +14159,8 @@ export class AgentSession {
|
|
|
13461
14159
|
this.#cancelOwnAsyncJobs();
|
|
13462
14160
|
this.#closeAllProviderSessions("new session");
|
|
13463
14161
|
this.#rebindProviderSessionState(new Map());
|
|
14162
|
+
this.#terminalizeQueuedSdkWorkForSessionTransition(this.#queuedMessagesForSessionTransition());
|
|
14163
|
+
this.#resetActiveSdkRunOwnership();
|
|
13464
14164
|
this.agent.reset();
|
|
13465
14165
|
if (!options?.drop) await this.sessionManager.flush();
|
|
13466
14166
|
const noLeasePreviousSessionIdentity = this.sessionManager.getSessionId();
|
|
@@ -13558,6 +14258,8 @@ export class AgentSession {
|
|
|
13558
14258
|
this.#disconnectFromAgent();
|
|
13559
14259
|
this.#closeAllProviderSessions("new session");
|
|
13560
14260
|
this.#rebindProviderSessionState(new Map());
|
|
14261
|
+
this.#terminalizeQueuedSdkWorkForSessionTransition(this.#queuedMessagesForSessionTransition());
|
|
14262
|
+
this.#resetActiveSdkRunOwnership();
|
|
13561
14263
|
this.agent.reset();
|
|
13562
14264
|
this.setTodoPhases([]);
|
|
13563
14265
|
this.#syncAgentSessionId();
|
|
@@ -13680,6 +14382,8 @@ export class AgentSession {
|
|
|
13680
14382
|
this.yieldQueue.clear();
|
|
13681
14383
|
this.#pendingBackgroundExchanges = [];
|
|
13682
14384
|
this.#closeAllProviderSessions("context clear");
|
|
14385
|
+
this.#terminalizeQueuedSdkWorkForSessionTransition(this.#queuedMessagesForSessionTransition());
|
|
14386
|
+
this.#resetActiveSdkRunOwnership();
|
|
13683
14387
|
this.agent.reset();
|
|
13684
14388
|
await this.sessionManager.flush();
|
|
13685
14389
|
this.sessionManager.appendContextClearEntry({ sessionId });
|
|
@@ -16104,6 +16808,7 @@ export class AgentSession {
|
|
|
16104
16808
|
const rollbackPendingNextTurnMessages = [...this.#pendingNextTurnMessages];
|
|
16105
16809
|
const rollbackScheduledHiddenNextTurnGeneration = this.#scheduledHiddenNextTurnGeneration;
|
|
16106
16810
|
const rollbackTodoReminderCount = this.#todoReminderCount;
|
|
16811
|
+
const rollbackDeferredSdkFollowUps = [...this.#deferredSdkFollowUps];
|
|
16107
16812
|
// Snapshot the agent's executable queues so a rollback restores queued
|
|
16108
16813
|
// user work that agent.reset() would otherwise clear.
|
|
16109
16814
|
const rollbackAgentSteeringQueue = this.agent.snapshotSteering();
|
|
@@ -16144,6 +16849,12 @@ export class AgentSession {
|
|
|
16144
16849
|
this.#rekeyJobManagerForSessionIdentity(rollbackSessionState.sessionId, rollbackSessionState.sessionFile);
|
|
16145
16850
|
committed = true;
|
|
16146
16851
|
await this.#runToolSessionTransitionCleanups();
|
|
16852
|
+
this.#terminalizeQueuedSdkWorkForSessionTransition([
|
|
16853
|
+
...rollbackAgentSteeringQueue,
|
|
16854
|
+
...rollbackAgentFollowUpQueue,
|
|
16855
|
+
...rollbackDeferredSdkFollowUps,
|
|
16856
|
+
]);
|
|
16857
|
+
this.#resetActiveSdkRunOwnership();
|
|
16147
16858
|
this.agent.reset();
|
|
16148
16859
|
this.#syncAgentSessionId();
|
|
16149
16860
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
@@ -16227,6 +16938,7 @@ export class AgentSession {
|
|
|
16227
16938
|
this.#pendingNextTurnMessages = rollbackPendingNextTurnMessages;
|
|
16228
16939
|
this.#scheduledHiddenNextTurnGeneration = rollbackScheduledHiddenNextTurnGeneration;
|
|
16229
16940
|
this.#todoReminderCount = rollbackTodoReminderCount;
|
|
16941
|
+
this.#deferredSdkFollowUps = rollbackDeferredSdkFollowUps;
|
|
16230
16942
|
this.#syncTodoPhasesFromBranch();
|
|
16231
16943
|
// Exact-discard only the staged successor; predecessor state was never adopted.
|
|
16232
16944
|
const rollbackError = prepared
|
|
@@ -18899,6 +19611,9 @@ export class AgentSession {
|
|
|
18899
19611
|
const continuation = this.agent.continue({
|
|
18900
19612
|
...this.#managedFallbackPromptOptions(),
|
|
18901
19613
|
transientRecoveryMessage: this.#escapedNonAsciiRecoveryMessage(),
|
|
19614
|
+
onRunAccepted: (handle: AttemptRunHandle) => {
|
|
19615
|
+
this.#acceptSdkAttemptRun(handle, this.#activeSdkRunToken);
|
|
19616
|
+
},
|
|
18902
19617
|
});
|
|
18903
19618
|
if (
|
|
18904
19619
|
cancellationSignal?.aborted ||
|
|
@@ -18937,6 +19652,9 @@ export class AgentSession {
|
|
|
18937
19652
|
await this.agent.continue({
|
|
18938
19653
|
...this.#managedFallbackPromptOptions(),
|
|
18939
19654
|
transientRecoveryMessage: this.#escapedNonAsciiRecoveryMessage(),
|
|
19655
|
+
onRunAccepted: (handle: AttemptRunHandle) => {
|
|
19656
|
+
this.#acceptSdkAttemptRun(handle, this.#activeSdkRunToken);
|
|
19657
|
+
},
|
|
18940
19658
|
});
|
|
18941
19659
|
},
|
|
18942
19660
|
};
|
|
@@ -18995,6 +19713,18 @@ export class AgentSession {
|
|
|
18995
19713
|
},
|
|
18996
19714
|
};
|
|
18997
19715
|
}
|
|
19716
|
+
// Issue #5018 preserves managed behavior for the typed statusless
|
|
19717
|
+
// Responses overload: before the code survived transport, this failure
|
|
19718
|
+
// reached the session as an ordinary committed error, so the chain never
|
|
19719
|
+
// discarded or advanced on it. Route it to the exhaustion decision
|
|
19720
|
+
// directly, before the retryable path can classify its new facts.
|
|
19721
|
+
if (isStatuslessTypedOverloadFacts(outcome.failure.transportFailure)) {
|
|
19722
|
+
this.#defaultFallbackChain().resetAttemptBudget();
|
|
19723
|
+
return this.#managedFallbackExhaustionDecision(
|
|
19724
|
+
outcome.failure.message,
|
|
19725
|
+
outcome.failure.message.errorMessage || "Model fallback attempt failed",
|
|
19726
|
+
);
|
|
19727
|
+
}
|
|
18998
19728
|
return this.#handleRetryableError(
|
|
18999
19729
|
outcome.failure.message,
|
|
19000
19730
|
true,
|
|
@@ -19354,11 +20084,13 @@ export class AgentSession {
|
|
|
19354
20084
|
if (!isAuthenticated(activeApiKey)) return "unchanged";
|
|
19355
20085
|
remaining = await authStorage.invalidateCredentialMatching(provider, activeApiKey, {
|
|
19356
20086
|
sessionId: credentialSessionId,
|
|
20087
|
+
owner: this.#modelRegistry.getAuthStorageOwner(),
|
|
19357
20088
|
});
|
|
19358
20089
|
if (!remaining) return "unchanged";
|
|
19359
20090
|
} else {
|
|
19360
20091
|
remaining = await authStorage.markUsageLimitReached(provider, credentialSessionId, {
|
|
19361
20092
|
retryAfterMs: trigger.retryAfterMs,
|
|
20093
|
+
owner: this.#modelRegistry.getAuthStorageOwner(),
|
|
19362
20094
|
});
|
|
19363
20095
|
}
|
|
19364
20096
|
|
|
@@ -19453,7 +20185,10 @@ export class AgentSession {
|
|
|
19453
20185
|
}
|
|
19454
20186
|
const firstEventTimeout = classification === "first_event_timeout";
|
|
19455
20187
|
const emptyResponse = classification === "empty_response";
|
|
19456
|
-
const canReplayProviderOverload =
|
|
20188
|
+
const canReplayProviderOverload =
|
|
20189
|
+
isBareDefaultCodexOverload(message) ||
|
|
20190
|
+
isBareDefaultAnthropicOverload(message) ||
|
|
20191
|
+
isBareDefaultOpenAIResponsesOverload(message);
|
|
19457
20192
|
const reportedRetryMaxAttempts = transportFailure?.retryMaxAttempts;
|
|
19458
20193
|
if (reportedRetryMaxAttempts !== undefined) {
|
|
19459
20194
|
this.#providerRetryMaxAttempts = Math.min(
|
|
@@ -19496,6 +20231,17 @@ export class AgentSession {
|
|
|
19496
20231
|
const trigger:
|
|
19497
20232
|
| { class: FallbackTriggerClass; retryAfterMs?: number; authDisposition?: AuthDisposition }
|
|
19498
20233
|
| undefined = this.#fallbackTriggerFor(message, !managedFallback, transportFailure);
|
|
20234
|
+
// OpenAI's typed statusless capacity-overload code (issue #5018) must not
|
|
20235
|
+
// gain managed-chain retry/advance authority from its new facts. Before
|
|
20236
|
+
// the code survived transport, this failure reached the session as an
|
|
20237
|
+
// ordinary committed error and surfaced immediately, so mirror that
|
|
20238
|
+
// behavior with the existing exhaustion decision.
|
|
20239
|
+
if (managedFallback && isStatuslessTypedOverloadFacts(transportFailure)) {
|
|
20240
|
+
return this.#managedFallbackExhaustionDecision(
|
|
20241
|
+
message,
|
|
20242
|
+
message.errorMessage || "Model fallback attempt failed",
|
|
20243
|
+
);
|
|
20244
|
+
}
|
|
19499
20245
|
if (!trigger) {
|
|
19500
20246
|
return managedOutcome
|
|
19501
20247
|
? this.#managedFallbackExhaustionDecision(message, message.errorMessage || "Model fallback attempt failed")
|
|
@@ -19852,7 +20598,12 @@ export class AgentSession {
|
|
|
19852
20598
|
this.#resolveRetry();
|
|
19853
20599
|
return;
|
|
19854
20600
|
}
|
|
19855
|
-
await this.agent.continue(
|
|
20601
|
+
await this.agent.continue({
|
|
20602
|
+
...this.#managedFallbackPromptOptions(),
|
|
20603
|
+
onRunAccepted: (handle: AttemptRunHandle) => {
|
|
20604
|
+
this.#acceptSdkAttemptRun(handle, this.#activeSdkRunToken);
|
|
20605
|
+
},
|
|
20606
|
+
});
|
|
19856
20607
|
return;
|
|
19857
20608
|
} catch (error) {
|
|
19858
20609
|
const attempt = this.#retryAttempt;
|
|
@@ -19934,6 +20685,7 @@ export class AgentSession {
|
|
|
19934
20685
|
preSubmit: PreSubmitBuilder,
|
|
19935
20686
|
options?: {
|
|
19936
20687
|
toolChoice?: ToolChoice;
|
|
20688
|
+
sdkRunToken?: string;
|
|
19937
20689
|
fallbackManaged?: boolean;
|
|
19938
20690
|
onRunAccepted?: (handle: AttemptRunHandle) => void;
|
|
19939
20691
|
},
|
|
@@ -20888,6 +21640,9 @@ export class AgentSession {
|
|
|
20888
21640
|
model.provider,
|
|
20889
21641
|
this.#modelRegistry.authStorage,
|
|
20890
21642
|
this.credentialSessionId,
|
|
21643
|
+
model,
|
|
21644
|
+
// Optional for the same reason as the main metadata resolver above.
|
|
21645
|
+
this.#modelRegistry.getAuthStorageOwner?.(),
|
|
20891
21646
|
),
|
|
20892
21647
|
reasoning: toReasoningEffort(this.thinkingLevel),
|
|
20893
21648
|
hideThinkingSummary: this.agent.hideThinkingSummary,
|
|
@@ -21233,6 +21988,10 @@ export class AgentSession {
|
|
|
21233
21988
|
const previousSystemPrompt = this.agent.state.systemPrompt;
|
|
21234
21989
|
const previousAgentSteeringQueue = this.agent.snapshotSteering();
|
|
21235
21990
|
const previousAgentFollowUpQueue = this.agent.snapshotFollowUp();
|
|
21991
|
+
const previousDeferredSdkFollowUps = [...this.#deferredSdkFollowUps];
|
|
21992
|
+
const previousActiveSdkRunToken = this.#activeSdkRunToken;
|
|
21993
|
+
const previousActiveAttemptScope = this.#activeAttemptScope;
|
|
21994
|
+
const previousActiveLogicalRunId = this.#activeLogicalRunId;
|
|
21236
21995
|
|
|
21237
21996
|
this.#steeringMessages = [];
|
|
21238
21997
|
this.#followUpMessages = [];
|
|
@@ -21280,6 +22039,7 @@ export class AgentSession {
|
|
|
21280
22039
|
// The target session is loaded and MCP selections are restored: discard
|
|
21281
22040
|
// pre-switch delivery queues before completing the restored agent state.
|
|
21282
22041
|
this.agent.clearAllQueues();
|
|
22042
|
+
this.#resetActiveSdkRunOwnership();
|
|
21283
22043
|
|
|
21284
22044
|
if (historyRewriteReason) {
|
|
21285
22045
|
this.agent.replaceMessages(sessionContext.messages, {
|
|
@@ -21464,6 +22224,12 @@ export class AgentSession {
|
|
|
21464
22224
|
...(options?.transition ? { transition: options.transition } : {}),
|
|
21465
22225
|
});
|
|
21466
22226
|
}
|
|
22227
|
+
this.#terminalizeQueuedSdkWorkForSessionTransition([
|
|
22228
|
+
...previousAgentSteeringQueue,
|
|
22229
|
+
...previousAgentFollowUpQueue,
|
|
22230
|
+
...previousDeferredSdkFollowUps,
|
|
22231
|
+
]);
|
|
22232
|
+
this.#deferredSdkFollowUps = [];
|
|
21467
22233
|
return true;
|
|
21468
22234
|
} catch (error) {
|
|
21469
22235
|
if (transitionCleanupCommitted) throw error;
|
|
@@ -21529,6 +22295,10 @@ export class AgentSession {
|
|
|
21529
22295
|
this.#followUpMessages = previousFollowUpMessages;
|
|
21530
22296
|
this.#pendingNextTurnMessages = previousPendingNextTurnMessages;
|
|
21531
22297
|
this.#scheduledHiddenNextTurnGeneration = previousScheduledHiddenNextTurnGeneration;
|
|
22298
|
+
this.#deferredSdkFollowUps = previousDeferredSdkFollowUps;
|
|
22299
|
+
this.#activeSdkRunToken = previousActiveSdkRunToken;
|
|
22300
|
+
this.#activeAttemptScope = previousActiveAttemptScope;
|
|
22301
|
+
this.#activeLogicalRunId = previousActiveLogicalRunId;
|
|
21532
22302
|
this.agent.clearAllQueues();
|
|
21533
22303
|
this.agent.restoreSteering(previousAgentSteeringQueue);
|
|
21534
22304
|
this.agent.restoreFollowUp(previousAgentFollowUpQueue);
|
|
@@ -21595,6 +22365,9 @@ export class AgentSession {
|
|
|
21595
22365
|
}
|
|
21596
22366
|
|
|
21597
22367
|
const selectedText = this.#extractUserMessageText(selectedEntry.message.content);
|
|
22368
|
+
const previousAgentSteeringQueue = this.agent.snapshotSteering();
|
|
22369
|
+
const previousAgentFollowUpQueue = this.agent.snapshotFollowUp();
|
|
22370
|
+
const previousDeferredSdkFollowUps = [...this.#deferredSdkFollowUps];
|
|
21598
22371
|
|
|
21599
22372
|
let skipConversationRestore = false;
|
|
21600
22373
|
|
|
@@ -21630,6 +22403,16 @@ export class AgentSession {
|
|
|
21630
22403
|
}
|
|
21631
22404
|
this.#pendingNextTurnMessages = [];
|
|
21632
22405
|
this.#scheduledHiddenNextTurnGeneration = undefined;
|
|
22406
|
+
this.#terminalizeQueuedSdkWorkForSessionTransition([
|
|
22407
|
+
...previousAgentSteeringQueue,
|
|
22408
|
+
...previousAgentFollowUpQueue,
|
|
22409
|
+
...previousDeferredSdkFollowUps,
|
|
22410
|
+
]);
|
|
22411
|
+
this.#deferredSdkFollowUps = [];
|
|
22412
|
+
this.#resetActiveSdkRunOwnership();
|
|
22413
|
+
this.agent.clearAllQueues();
|
|
22414
|
+
this.#steeringMessages = [];
|
|
22415
|
+
this.#followUpMessages = [];
|
|
21633
22416
|
|
|
21634
22417
|
this.#syncTodoPhasesFromBranch();
|
|
21635
22418
|
this.#syncAgentSessionId();
|
|
@@ -21835,6 +22618,15 @@ export class AgentSession {
|
|
|
21835
22618
|
this.sessionManager.branch(newLeafId);
|
|
21836
22619
|
}
|
|
21837
22620
|
|
|
22621
|
+
// The history rewrite is now committed. Drop predecessor-owned queued SDK
|
|
22622
|
+
// work at this boundary; cancelled or failed preparation above preserves it.
|
|
22623
|
+
const queuedSdkWork = this.#queuedMessagesForSessionTransition();
|
|
22624
|
+
this.#terminalizeQueuedSdkWorkForSessionTransition(queuedSdkWork);
|
|
22625
|
+
this.#deferredSdkFollowUps = [];
|
|
22626
|
+
this.agent.clearAllQueues();
|
|
22627
|
+
this.#steeringMessages = [];
|
|
22628
|
+
this.#followUpMessages = [];
|
|
22629
|
+
|
|
21838
22630
|
// Update agent state through the canonical filtered display context so legacy
|
|
21839
22631
|
// request-scoped entries cannot re-enter live history after tree navigation.
|
|
21840
22632
|
const displayContext = this.buildDisplaySessionContext();
|