@gajae-code/coding-agent 0.15.5 → 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 +26 -1
- 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 +2 -0
- 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 +96 -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 +2 -5
- 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/client/discovery.d.ts +1 -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 +15 -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 -7
- package/dist/types/session/fold-coordinator.d.ts +201 -0
- 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/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 +40 -5
- 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/config/autorouting-tier-map.ts +32 -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 +51 -9
- package/src/config/model-registry.ts +1021 -122
- 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/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 +2 -3
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +1 -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/session-state-lock.ts +1242 -141
- package/src/gjc-runtime/session-state-sidecar.ts +76 -3
- package/src/index.ts +1 -0
- package/src/internal-urls/docs-index.generated.ts +9 -8
- 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/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 +36 -6
- package/src/sdk/bus/prompt-reconciliation.ts +28 -4
- package/src/sdk/bus/slack-conversation.ts +5 -1
- package/src/sdk/cli/session-cli.ts +12 -1
- package/src/sdk/client/discovery.ts +3 -1
- package/src/sdk/host/control/dispatch.ts +3 -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 +371 -37
- package/src/sdk/host/session-runtime.ts +364 -52
- 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 +228 -96
- package/src/sdk/session.ts +83 -12
- package/src/session/account-inventory.ts +27 -11
- package/src/session/agent-session.ts +678 -103
- package/src/session/fold-coordinator.ts +433 -0
- 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 +5 -4
- 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 +772 -67
- package/src/tools/index.ts +7 -5
- 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,
|
|
@@ -181,8 +182,10 @@ export interface ForkContextSeedOptions {
|
|
|
181
182
|
}
|
|
182
183
|
|
|
183
184
|
import type { AuthCredentialSelector } from "@gajae-code/ai/core";
|
|
185
|
+
import { isFoundryEnabled } from "@gajae-code/ai/utils/foundry";
|
|
184
186
|
import type { MacOSPowerAssertion } from "@gajae-code/natives";
|
|
185
187
|
import {
|
|
188
|
+
$pickCredentialEnv,
|
|
186
189
|
extractRetryHint,
|
|
187
190
|
hasFsCode,
|
|
188
191
|
isEacces,
|
|
@@ -314,6 +317,7 @@ import {
|
|
|
314
317
|
type CoordinatorToolObservation,
|
|
315
318
|
ownerTerminalContextFromEnvironment,
|
|
316
319
|
persistCoordinatorRuntimeStateFromEvent,
|
|
320
|
+
persistCoordinatorWorkerIntegrationOutcome,
|
|
317
321
|
registerCoordinatorRuntimeStateFinalizer,
|
|
318
322
|
UNPROVEN_TOOL_LABEL,
|
|
319
323
|
} from "../gjc-runtime/session-state-sidecar";
|
|
@@ -443,6 +447,7 @@ import {
|
|
|
443
447
|
} from "./contribution-prep";
|
|
444
448
|
import { canonicalCoordinatorToolLabel } from "./coordinator-tool-label";
|
|
445
449
|
import { pruneStaleFileMentions } from "./file-mention-pruning";
|
|
450
|
+
import { describeFoldReceipt, FOLD_WAKE_MERGE_WINDOW_MS, type FoldAdapter, FoldCoordinator } from "./fold-coordinator";
|
|
446
451
|
import type { MemoryGuardRestoreResult } from "./memory-guard-checkpoint-participant";
|
|
447
452
|
import {
|
|
448
453
|
type BashExecutionMessage,
|
|
@@ -457,6 +462,7 @@ import {
|
|
|
457
462
|
SKILL_PROMPT_MESSAGE_TYPE,
|
|
458
463
|
} from "./messages";
|
|
459
464
|
import { isLegacyProviderSafetyStopMessage } from "./provider-safety-stop";
|
|
465
|
+
import { readSdkRunCapability } from "./sdk-run-capability";
|
|
460
466
|
import { formatSessionDumpText } from "./session-dump-format";
|
|
461
467
|
import type {
|
|
462
468
|
BranchSummaryEntry,
|
|
@@ -471,7 +477,6 @@ import type {
|
|
|
471
477
|
SessionManagerCloseOutcome,
|
|
472
478
|
SessionMemoryStats,
|
|
473
479
|
} from "./session-manager";
|
|
474
|
-
|
|
475
480
|
import {
|
|
476
481
|
createReadonlySessionManager,
|
|
477
482
|
getLatestCompactionEntry,
|
|
@@ -488,6 +493,8 @@ import {
|
|
|
488
493
|
bindToolLineage,
|
|
489
494
|
classifyOwnedEnvelope,
|
|
490
495
|
isOwnedCompletionEnvelope,
|
|
496
|
+
isOwnedCompletionEnvelopeAllowed,
|
|
497
|
+
lookupOwnedRegistration,
|
|
491
498
|
lookupTerminalScope,
|
|
492
499
|
mintTurnLineageIdHash,
|
|
493
500
|
type OwnedCompletionEnvelope,
|
|
@@ -496,11 +503,35 @@ import {
|
|
|
496
503
|
settleToolLineageRegistrationWindow,
|
|
497
504
|
unregisterOwnedRegistration,
|
|
498
505
|
} from "./terminal-abort";
|
|
499
|
-
|
|
500
506
|
import { ToolChoiceQueue } from "./tool-choice-queue";
|
|
501
507
|
import { pruneSupersededMaintenanceReminders, pruneSupersededVolatileProjectContext } from "./volatile-context-pruning";
|
|
502
508
|
import { YieldQueue } from "./yield-queue";
|
|
503
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
|
+
|
|
504
535
|
/**
|
|
505
536
|
* #4560: structured workflow recovery projection from canonical durable
|
|
506
537
|
* Ralplan/Ultragoal state, consumed by the compaction summary context and
|
|
@@ -1048,10 +1079,23 @@ export interface PromptOptions {
|
|
|
1048
1079
|
onSkillPrepared?: (meta: { name: string; path: string; lineCount?: number; cleanedArgs?: string }) => void;
|
|
1049
1080
|
/** Optional invocation-scoped cancellation fence used before an accepted skill starts execution. */
|
|
1050
1081
|
preflightSignal?: AbortSignal;
|
|
1051
|
-
/** Internal
|
|
1052
|
-
|
|
1082
|
+
/** Internal host capability; ordinary package callers cannot construct the branded value. */
|
|
1083
|
+
sdkRunCapability?: unknown;
|
|
1053
1084
|
}
|
|
1054
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
|
+
|
|
1055
1099
|
function promptPreflightCancelledError(): Error {
|
|
1056
1100
|
const error = Object.assign(new Error("Prompt preflight was cancelled before execution."), { code: "busy" });
|
|
1057
1101
|
error.name = "PromptPreflightCancelledError";
|
|
@@ -1626,14 +1670,44 @@ function buildSessionMetadata(
|
|
|
1626
1670
|
provider: string,
|
|
1627
1671
|
authStorage: AuthStorage | undefined,
|
|
1628
1672
|
credentialSessionId = sessionId,
|
|
1673
|
+
model?: Model,
|
|
1674
|
+
owner?: object,
|
|
1629
1675
|
): Record<string, unknown> {
|
|
1630
1676
|
const userId: Record<string, string> = { session_id: sessionId };
|
|
1631
1677
|
// Only look up account_uuid when the request is going to Anthropic. Injecting
|
|
1632
1678
|
// a Anthropic model OAuth account_uuid into requests bound for other providers (including
|
|
1633
1679
|
// Anthropic-format-compatible proxies like cloudflare-ai-gateway or gitlab-duo)
|
|
1634
1680
|
// would leak the user's Anthropic identity to unrelated third-party APIs.
|
|
1635
|
-
|
|
1636
|
-
|
|
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 });
|
|
1637
1711
|
if (typeof accountUuid === "string" && accountUuid.length > 0) {
|
|
1638
1712
|
userId.account_uuid = accountUuid;
|
|
1639
1713
|
// Derive device_id from account_uuid so the payload matches the real CC
|
|
@@ -1914,9 +1988,24 @@ export type BeforeAgentStartContributor = (event: {
|
|
|
1914
1988
|
}) => Promise<BeforeAgentStartInternalMessage | undefined>;
|
|
1915
1989
|
|
|
1916
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
|
+
}
|
|
1917
2006
|
|
|
1918
2007
|
export class WorkerIntegrationRequestScheduler {
|
|
1919
|
-
#inFlight: Promise<
|
|
2008
|
+
#inFlight: Promise<WorkerIntegrationOutcome> | undefined = undefined;
|
|
1920
2009
|
#pending = false;
|
|
1921
2010
|
|
|
1922
2011
|
constructor(
|
|
@@ -1933,9 +2022,15 @@ export class WorkerIntegrationRequestScheduler {
|
|
|
1933
2022
|
}
|
|
1934
2023
|
|
|
1935
2024
|
async flush(): Promise<void> {
|
|
2025
|
+
await this.flushWithOutcome();
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
async flushWithOutcome(): Promise<WorkerIntegrationOutcome> {
|
|
2029
|
+
let outcome: WorkerIntegrationOutcome = { status: "completed" };
|
|
1936
2030
|
while (this.#inFlight) {
|
|
1937
|
-
await this.#inFlight;
|
|
2031
|
+
outcome = await this.#inFlight;
|
|
1938
2032
|
}
|
|
2033
|
+
return outcome;
|
|
1939
2034
|
}
|
|
1940
2035
|
|
|
1941
2036
|
#start(): void {
|
|
@@ -1945,16 +2040,22 @@ export class WorkerIntegrationRequestScheduler {
|
|
|
1945
2040
|
try {
|
|
1946
2041
|
request = this.request(controller.signal);
|
|
1947
2042
|
} catch {
|
|
1948
|
-
request = Promise.
|
|
2043
|
+
request = Promise.reject(new Error("Worker integration request failed before dispatch."));
|
|
1949
2044
|
}
|
|
1950
2045
|
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
1951
|
-
const deadline = new Promise<
|
|
2046
|
+
const deadline = new Promise<WorkerIntegrationOutcome>(resolve => {
|
|
1952
2047
|
timeout = setTimeout(() => {
|
|
1953
2048
|
controller.abort(new Error("Worker integration request timed out"));
|
|
1954
|
-
resolve();
|
|
2049
|
+
resolve({ status: "timed_out" });
|
|
1955
2050
|
}, this.timeoutMs);
|
|
1956
2051
|
});
|
|
1957
|
-
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(() => {
|
|
1958
2059
|
if (timeout) clearTimeout(timeout);
|
|
1959
2060
|
controller.abort();
|
|
1960
2061
|
this.#inFlight = undefined;
|
|
@@ -2475,6 +2576,22 @@ export class AgentSession {
|
|
|
2475
2576
|
this.#activeAttemptScope = undefined;
|
|
2476
2577
|
this.#activeLogicalRunId = undefined;
|
|
2477
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
|
+
}
|
|
2478
2595
|
/** Messages queued to be included with the next user prompt as context ("asides"). */
|
|
2479
2596
|
#pendingNextTurnMessages: Array<{ message: CustomMessage; origin: "turn" | "external" }> = [];
|
|
2480
2597
|
#scheduledHiddenNextTurnGeneration: number | undefined = undefined;
|
|
@@ -2608,7 +2725,88 @@ export class AgentSession {
|
|
|
2608
2725
|
onPersisted?: () => void;
|
|
2609
2726
|
appendedToAgent: boolean;
|
|
2610
2727
|
}> = [];
|
|
2611
|
-
|
|
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
|
+
});
|
|
2612
2810
|
|
|
2613
2811
|
// Python execution state
|
|
2614
2812
|
#evalAbortControllers = new Set<AbortController>();
|
|
@@ -2658,6 +2856,7 @@ export class AgentSession {
|
|
|
2658
2856
|
#extensionRunner: ExtensionRunner | undefined = undefined;
|
|
2659
2857
|
/** SDK follow-up ownership by queued message and the attempt that dequeues it. */
|
|
2660
2858
|
#sdkRunTokensByQueuedMessage = new WeakMap<AgentMessage, string>();
|
|
2859
|
+
#skipPostPromptRecoveryWaitByAttemptScope = new WeakSet<AttemptScope>();
|
|
2661
2860
|
#sdkRunTokensByAttemptScope = new WeakMap<AttemptScope, string>();
|
|
2662
2861
|
#activeSdkRunToken: string | undefined;
|
|
2663
2862
|
#activeAttemptScope: AttemptScope | undefined;
|
|
@@ -3006,6 +3205,7 @@ export class AgentSession {
|
|
|
3006
3205
|
* orchestrator does not hold it), so there is no self-deadlock.
|
|
3007
3206
|
*/
|
|
3008
3207
|
#sessionTransitionKind: string | undefined;
|
|
3208
|
+
#coordinatorPersistGeneration = 0;
|
|
3009
3209
|
|
|
3010
3210
|
#beginSessionTransition(kind: string): void {
|
|
3011
3211
|
if (this.#sessionTransitionKind !== undefined) {
|
|
@@ -3015,6 +3215,7 @@ export class AgentSession {
|
|
|
3015
3215
|
);
|
|
3016
3216
|
}
|
|
3017
3217
|
this.#sessionTransitionKind = kind;
|
|
3218
|
+
this.#coordinatorPersistGeneration += 1;
|
|
3018
3219
|
}
|
|
3019
3220
|
|
|
3020
3221
|
#endSessionTransition(): void {
|
|
@@ -3614,7 +3815,7 @@ export class AgentSession {
|
|
|
3614
3815
|
// Deferrals behind still-pending selection fences are excluded here: they
|
|
3615
3816
|
// cannot settle before their fence resolves, and the fence owner may be
|
|
3616
3817
|
// waiting on this very settlement (#4519).
|
|
3617
|
-
if (!predecessorPromptStillInFlight) {
|
|
3818
|
+
if (promptWait !== "publication" && !predecessorPromptStillInFlight) {
|
|
3618
3819
|
await this.#waitForSessionSettlement(this.#turnSettlementDeferralFloor());
|
|
3619
3820
|
}
|
|
3620
3821
|
if (flushError) throw flushError;
|
|
@@ -3656,28 +3857,50 @@ export class AgentSession {
|
|
|
3656
3857
|
this.#pendingAgentEndEmit = undefined;
|
|
3657
3858
|
const lease = this.#deferredAgentEndLeases.get(pending);
|
|
3658
3859
|
if (lease) this.#deferredAgentEndLeases.delete(pending);
|
|
3860
|
+
this.#startAgentEndPublication(pending, lease);
|
|
3861
|
+
}
|
|
3862
|
+
#startAgentEndPublication(pending: AgentSessionEvent, lease?: RunResourceProducerLease): void {
|
|
3659
3863
|
this.#agentEndPublicationInFlight++;
|
|
3660
|
-
|
|
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);
|
|
3661
3869
|
void this.#agentEndPublicationPromise;
|
|
3662
3870
|
}
|
|
3663
3871
|
|
|
3664
3872
|
async #publishDeferredAgentEnd(
|
|
3665
3873
|
pending: AgentSessionEvent,
|
|
3666
3874
|
lease: RunResourceProducerLease | undefined,
|
|
3875
|
+
workerIntegrationSettled: boolean,
|
|
3667
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;
|
|
3668
3888
|
let extensionDelivery: Promise<void> | undefined;
|
|
3669
3889
|
const releaseLease = () => {
|
|
3670
3890
|
if (!lease) return;
|
|
3891
|
+
this.#deferredAgentEndLeases.delete(pending);
|
|
3671
3892
|
if (this.#postPromptLeases.get(lease.resourceRunId) === lease) {
|
|
3672
3893
|
this.#postPromptLeases.delete(lease.resourceRunId);
|
|
3673
3894
|
}
|
|
3674
3895
|
lease.closeDiscovery();
|
|
3675
3896
|
};
|
|
3676
3897
|
const publish = async () => {
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3898
|
+
let workerIntegration: Promise<WorkerIntegrationOutcome> | undefined;
|
|
3899
|
+
let workerIntegrationOutcome: WorkerIntegrationOutcome | undefined;
|
|
3900
|
+
if (!workerIntegrationSettled) {
|
|
3901
|
+
workerIntegration = this.#flushWorkerIntegrationForAgentEnd();
|
|
3902
|
+
workerIntegrationOutcome = await workerIntegration;
|
|
3903
|
+
}
|
|
3681
3904
|
// Reserve persistence before notifying synchronous subscribers: a subscriber
|
|
3682
3905
|
// may start a successor prompt from agent_end, whose running state must
|
|
3683
3906
|
// serialize after this terminal boundary rather than be overwritten by it.
|
|
@@ -3686,16 +3909,72 @@ export class AgentSession {
|
|
|
3686
3909
|
// behind this terminal transition, but do not make the interactive terminal
|
|
3687
3910
|
// wait for the filesystem-backed lock/write to settle. On slow or lock-hostile
|
|
3688
3911
|
// mounts such as WSL drvfs, waiting here leaves the foreground activity loader
|
|
3689
|
-
// and busy input state visible after the model has already finished.
|
|
3690
|
-
|
|
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);
|
|
3691
3916
|
this.#emit(pending);
|
|
3692
|
-
|
|
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
|
+
);
|
|
3693
3955
|
extensionDelivery = this.#queueExtensionEvent(
|
|
3694
3956
|
pending,
|
|
3695
3957
|
undefined,
|
|
3696
3958
|
true,
|
|
3697
3959
|
(pending as AgentSessionEvent & { scope?: AttemptScopeRef }).scope,
|
|
3698
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
|
+
}
|
|
3699
3978
|
};
|
|
3700
3979
|
try {
|
|
3701
3980
|
if (lease) await this.#runResourceLeaseContext.run(lease, publish);
|
|
@@ -3891,9 +4170,19 @@ export class AgentSession {
|
|
|
3891
4170
|
this.#resumeFromOwnedCompletion();
|
|
3892
4171
|
try {
|
|
3893
4172
|
if (survivors.length === 1) {
|
|
3894
|
-
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
|
+
});
|
|
3895
4179
|
} else {
|
|
3896
|
-
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
|
+
});
|
|
3897
4186
|
}
|
|
3898
4187
|
} finally {
|
|
3899
4188
|
// The owned completions were delivered OR the prompt attempt
|
|
@@ -3911,11 +4200,32 @@ export class AgentSession {
|
|
|
3911
4200
|
async () => {
|
|
3912
4201
|
await run();
|
|
3913
4202
|
},
|
|
3914
|
-
|
|
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 },
|
|
3915
4206
|
);
|
|
3916
4207
|
},
|
|
3917
4208
|
});
|
|
3918
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
|
+
});
|
|
3919
4229
|
this.agent.setMaintainContext((context, lifecycle) =>
|
|
3920
4230
|
this.#trackMidRunMaintenance(
|
|
3921
4231
|
this.awaitPendingContextTransformations().then(() => this.#runMidRunMaintenance(context, lifecycle)),
|
|
@@ -4054,6 +4364,18 @@ export class AgentSession {
|
|
|
4054
4364
|
if (messages.some(message => ownedCompletionResumeAction(message) === "fresh")) {
|
|
4055
4365
|
this.#resumeFromOwnedCompletion();
|
|
4056
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
|
+
}
|
|
4057
4379
|
// Monitor task-notifications now carry the owned-completion envelope
|
|
4058
4380
|
// (see tools/monitor.ts), so the general drop + fresh-admission paths
|
|
4059
4381
|
// above cover them exactly like async results — no monitor-specific
|
|
@@ -4089,6 +4411,18 @@ export class AgentSession {
|
|
|
4089
4411
|
// submitter to the in-flight run instead of parking the correlation for
|
|
4090
4412
|
// an unrelated later agent_start (#4668).
|
|
4091
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
|
+
}
|
|
4092
4426
|
this.#fireQueuedPromotionHooks(messages, promotion);
|
|
4093
4427
|
};
|
|
4094
4428
|
this.agent.providerSessionState = this.#providerSessionState;
|
|
@@ -5001,6 +5335,10 @@ export class AgentSession {
|
|
|
5001
5335
|
* seen.
|
|
5002
5336
|
*/
|
|
5003
5337
|
#coordinatorToolObservations = new WeakMap<object, CoordinatorToolObservation>();
|
|
5338
|
+
#agentEventAdmission = new WeakMap<
|
|
5339
|
+
object,
|
|
5340
|
+
{ scope?: AttemptScope; sdkRunToken?: string; persistGeneration: number }
|
|
5341
|
+
>();
|
|
5004
5342
|
|
|
5005
5343
|
/**
|
|
5006
5344
|
* Capture what is true at the SYNCHRONOUS agent-event boundary, before any async work.
|
|
@@ -5056,6 +5394,11 @@ export class AgentSession {
|
|
|
5056
5394
|
}
|
|
5057
5395
|
|
|
5058
5396
|
#trackAgentEvent = (event: AgentEvent): Promise<void> => {
|
|
5397
|
+
this.#agentEventAdmission.set(event, {
|
|
5398
|
+
scope: this.#activeAttemptScope,
|
|
5399
|
+
sdkRunToken: this.#activeSdkRunToken,
|
|
5400
|
+
persistGeneration: this.#coordinatorPersistGeneration,
|
|
5401
|
+
});
|
|
5059
5402
|
// First statement of the listener: the observation must precede every claim,
|
|
5060
5403
|
// reservation, and async hop this handler performs.
|
|
5061
5404
|
this.#observeCoordinatorToolEvent(event);
|
|
@@ -5125,10 +5468,10 @@ export class AgentSession {
|
|
|
5125
5468
|
this.#postPromptLeases.set(eventLease.resourceRunId, eventLease);
|
|
5126
5469
|
if (eventLease) {
|
|
5127
5470
|
await this.#runResourceLeaseContext.run(eventLease, () =>
|
|
5128
|
-
this.#handleAgentEvent(event, activePromptHandle, canonicalAdmission),
|
|
5471
|
+
this.#handleAgentEvent(event, activePromptHandle, canonicalAdmission, eventLease),
|
|
5129
5472
|
);
|
|
5130
5473
|
} else {
|
|
5131
|
-
await this.#handleAgentEvent(event, activePromptHandle, canonicalAdmission);
|
|
5474
|
+
await this.#handleAgentEvent(event, activePromptHandle, canonicalAdmission, undefined);
|
|
5132
5475
|
}
|
|
5133
5476
|
} catch (error) {
|
|
5134
5477
|
logger.warn("Agent event handler failed", { event: event.type, error: String(error) });
|
|
@@ -5136,11 +5479,19 @@ export class AgentSession {
|
|
|
5136
5479
|
canonicalAdmission?.release();
|
|
5137
5480
|
if (eventLease) {
|
|
5138
5481
|
const pendingAgentEnd =
|
|
5139
|
-
event.type === "agent_end" &&
|
|
5482
|
+
event.type === "agent_end" &&
|
|
5483
|
+
!maintenanceCheckpoint &&
|
|
5484
|
+
(this.#pendingAgentEndEmit === event || this.#deferredAgentEndLeases.has(event))
|
|
5140
5485
|
? this.#pendingAgentEndEmit
|
|
5141
5486
|
: undefined;
|
|
5142
5487
|
if (pendingAgentEnd) {
|
|
5143
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.
|
|
5144
5495
|
} else {
|
|
5145
5496
|
if (event.type === "agent_end" && this.#postPromptLeases.get(eventLease.resourceRunId) === eventLease)
|
|
5146
5497
|
this.#postPromptLeases.delete(eventLease.resourceRunId);
|
|
@@ -5163,21 +5514,61 @@ export class AgentSession {
|
|
|
5163
5514
|
/** Serializes sidecar writes in publication order, independent of write latency. */
|
|
5164
5515
|
#coordinatorPersistQueue: Promise<void> = Promise.resolve();
|
|
5165
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
|
+
|
|
5166
5547
|
/**
|
|
5167
5548
|
* Reserve this event's place in the sidecar write order.
|
|
5168
5549
|
*
|
|
5169
5550
|
* Called immediately BEFORE local delivery so a synchronous subscriber that re-enters
|
|
5170
5551
|
* the session cannot get its own, later event persisted first. The returned promise is
|
|
5171
|
-
* only
|
|
5552
|
+
* awaited only by callers that explicitly require a durable handoff; interactive
|
|
5553
|
+
* terminal delivery remains independent of this secondary sink.
|
|
5172
5554
|
*
|
|
5173
5555
|
* A pairing-only tool event takes no place in that order at all — neither its start nor
|
|
5174
5556
|
* its end. It describes a call that was never dispatched, and this file is read as the
|
|
5175
5557
|
* answer to "what is this session doing right now".
|
|
5176
5558
|
*/
|
|
5177
|
-
#queueCoordinatorRuntimeStatePersist(event: AgentSessionEvent): Promise<void> {
|
|
5559
|
+
#queueCoordinatorRuntimeStatePersist(event: AgentSessionEvent, propagateFailure = false): Promise<void> {
|
|
5178
5560
|
if (isNonDispatchedToolEvent(event)) return Promise.resolve();
|
|
5179
5561
|
const observation = this.#coordinatorToolObservations.get(event);
|
|
5180
|
-
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();
|
|
5181
5572
|
const queued = this.#coordinatorPersistQueue.then(run, run);
|
|
5182
5573
|
this.#coordinatorPersistQueue = queued.catch(() => {});
|
|
5183
5574
|
return queued;
|
|
@@ -5185,24 +5576,19 @@ export class AgentSession {
|
|
|
5185
5576
|
|
|
5186
5577
|
async #persistRuntimeStateInBackground(
|
|
5187
5578
|
event: AgentSessionEvent,
|
|
5579
|
+
context: { sessionId: string; cwd: string; sessionFile: string | undefined },
|
|
5188
5580
|
observation: CoordinatorToolObservation | undefined,
|
|
5581
|
+
propagateFailure: boolean,
|
|
5189
5582
|
): Promise<void> {
|
|
5190
5583
|
try {
|
|
5191
|
-
await persistCoordinatorRuntimeStateFromEvent(
|
|
5192
|
-
|
|
5193
|
-
{
|
|
5194
|
-
sessionId: this.sessionId,
|
|
5195
|
-
cwd: this.sessionManager.getCwd(),
|
|
5196
|
-
sessionFile: this.sessionManager.getSessionFile(),
|
|
5197
|
-
},
|
|
5198
|
-
observation,
|
|
5199
|
-
);
|
|
5200
|
-
} catch {
|
|
5584
|
+
await persistCoordinatorRuntimeStateFromEvent(event, context, observation);
|
|
5585
|
+
} catch (error) {
|
|
5201
5586
|
logger.warn("Failed to persist coordinator runtime state", { event: event.type });
|
|
5587
|
+
if (propagateFailure) throw error;
|
|
5202
5588
|
}
|
|
5203
5589
|
}
|
|
5204
5590
|
|
|
5205
|
-
async #emitSessionEvent(event: AgentSessionEvent): Promise<void> {
|
|
5591
|
+
async #emitSessionEvent(event: AgentSessionEvent, eventLease?: RunResourceProducerLease): Promise<void> {
|
|
5206
5592
|
const attemptScope = (event as AgentSessionEvent & { scope?: AttemptScope }).scope;
|
|
5207
5593
|
if (event.type === "turn_start") {
|
|
5208
5594
|
this.#extensionTurnGeneration++;
|
|
@@ -5236,6 +5622,18 @@ export class AgentSession {
|
|
|
5236
5622
|
// have unwound. Subscribers treat this event as the ready signal; flushing it
|
|
5237
5623
|
// from abort while either barrier is active permits a successor to race the
|
|
5238
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
|
+
}
|
|
5239
5637
|
if (event.type === "agent_end" && (this.#livePromptsInFlight() > 0 || this.#agentEventHandlersInFlight > 0)) {
|
|
5240
5638
|
this.#pendingAgentEndEmit = event;
|
|
5241
5639
|
return;
|
|
@@ -5244,8 +5642,12 @@ export class AgentSession {
|
|
|
5244
5642
|
if (event.type === "agent_end") {
|
|
5245
5643
|
// Start the durable terminal write before synchronous subscribers can
|
|
5246
5644
|
// re-enter prompt(), so a successor's running transition serializes after it.
|
|
5247
|
-
|
|
5645
|
+
// Do not let a lock-hostile sidecar suppress the terminal event itself.
|
|
5646
|
+
const terminalPersistence = this.#queueCoordinatorRuntimeStatePersist(event);
|
|
5248
5647
|
this.#emit(event);
|
|
5648
|
+
void terminalPersistence.catch(error => {
|
|
5649
|
+
logger.warn("Failed to persist terminal coordinator runtime state", { error: String(error) });
|
|
5650
|
+
});
|
|
5249
5651
|
await this.#emitExtensionEvent(event);
|
|
5250
5652
|
return;
|
|
5251
5653
|
}
|
|
@@ -5341,6 +5743,7 @@ export class AgentSession {
|
|
|
5341
5743
|
event: AgentEvent,
|
|
5342
5744
|
activePromptHandle?: string,
|
|
5343
5745
|
canonicalAdmission?: CanonicalMessageAdmission,
|
|
5746
|
+
eventLease?: RunResourceProducerLease,
|
|
5344
5747
|
): Promise<void> => {
|
|
5345
5748
|
const attemptScope = (event as AgentEvent & { scope?: AttemptScope }).scope;
|
|
5346
5749
|
|
|
@@ -5680,7 +6083,7 @@ export class AgentSession {
|
|
|
5680
6083
|
});
|
|
5681
6084
|
}
|
|
5682
6085
|
|
|
5683
|
-
await this.#emitSessionEvent(displayEvent);
|
|
6086
|
+
await this.#emitSessionEvent(displayEvent, eventLease);
|
|
5684
6087
|
if (event.type === "message_end" && event.message.role === "assistant") {
|
|
5685
6088
|
this.#assistantAttemptScopes.set(event.message, {
|
|
5686
6089
|
scope: attemptScope,
|
|
@@ -6030,6 +6433,7 @@ export class AgentSession {
|
|
|
6030
6433
|
skipCompactionCheck: true,
|
|
6031
6434
|
resourceRunId: activePromptHandle,
|
|
6032
6435
|
maintenanceContinuation: true,
|
|
6436
|
+
sdkRunToken: attemptScope ? this.#sdkRunTokensByAttemptScope.get(attemptScope) : undefined,
|
|
6033
6437
|
});
|
|
6034
6438
|
}
|
|
6035
6439
|
return;
|
|
@@ -6317,8 +6721,11 @@ export class AgentSession {
|
|
|
6317
6721
|
predecessorAgentEndHold?: symbol;
|
|
6318
6722
|
/** Internal predecessor terminal event sequestered while waiting behind selection. */
|
|
6319
6723
|
deferredPredecessorAgentEnd?: AgentSessionEvent;
|
|
6724
|
+
/** Internal causal SDK owner captured when this continuation was scheduled. */
|
|
6725
|
+
sdkRunToken?: string;
|
|
6320
6726
|
}): Promise<void> {
|
|
6321
6727
|
const continuationAdmission = this.#captureScheduledContinuationAdmission();
|
|
6728
|
+
const scheduledSdkRunToken = options?.sdkRunToken;
|
|
6322
6729
|
const selectionFenceGeneration =
|
|
6323
6730
|
options?.selectionFenceGeneration ??
|
|
6324
6731
|
this.#selectionFenceGenerationContext.getStore() ??
|
|
@@ -6340,6 +6747,7 @@ export class AgentSession {
|
|
|
6340
6747
|
generation: deferredPromptGeneration,
|
|
6341
6748
|
selectionFenceGeneration,
|
|
6342
6749
|
deferredPredecessorAgentEnd,
|
|
6750
|
+
sdkRunToken: scheduledSdkRunToken,
|
|
6343
6751
|
});
|
|
6344
6752
|
} finally {
|
|
6345
6753
|
// The recursive call synchronously re-reserved its settlement
|
|
@@ -6475,18 +6883,18 @@ export class AgentSession {
|
|
|
6475
6883
|
// agent_start (review P1); their queued messages are in-run
|
|
6476
6884
|
// consumptions, not own-run promotions.
|
|
6477
6885
|
const startsOwn = options?.maintenanceContinuation !== true;
|
|
6478
|
-
const
|
|
6479
|
-
?
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
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;
|
|
6483
6893
|
this.#fireQueuedPromotionHooks(acceptance.consumedQueuedMessages, {
|
|
6484
6894
|
startsOwnRun: startsOwn,
|
|
6485
6895
|
});
|
|
6486
|
-
if (
|
|
6487
|
-
|
|
6488
|
-
if (startsOwn) this.#activeSdkRunToken = consumedSdkRunToken;
|
|
6489
|
-
this.#acceptRunHandle(handle);
|
|
6896
|
+
if (startsOwn) this.#activeSdkRunToken = sdkRunToken;
|
|
6897
|
+
this.#acceptSdkAttemptRun(handle, sdkRunToken);
|
|
6490
6898
|
options?.onRunAccepted?.(handle);
|
|
6491
6899
|
// Keep the queued token available through the acceptance callback;
|
|
6492
6900
|
// SDK follow-up owners bind it to the new attempt scope there.
|
|
@@ -6649,8 +7057,12 @@ export class AgentSession {
|
|
|
6649
7057
|
resourceRunId?: string,
|
|
6650
7058
|
deferredSelectionFenceGeneration?: number,
|
|
6651
7059
|
deferredPredecessorAgentEnd?: AgentSessionEvent,
|
|
7060
|
+
sdkRunToken?: string,
|
|
6652
7061
|
): void {
|
|
6653
7062
|
const scheduledGeneration = generation;
|
|
7063
|
+
const scheduledSdkRunToken =
|
|
7064
|
+
sdkRunToken ??
|
|
7065
|
+
(this.#activeAttemptScope ? this.#sdkRunTokensByAttemptScope.get(this.#activeAttemptScope) : undefined);
|
|
6654
7066
|
const continuationAuthorized = async (
|
|
6655
7067
|
signal: AbortSignal,
|
|
6656
7068
|
hasPendingNextTurnMessages = false,
|
|
@@ -6703,6 +7115,7 @@ export class AgentSession {
|
|
|
6703
7115
|
resourceRunId,
|
|
6704
7116
|
selectionFenceGeneration,
|
|
6705
7117
|
predecessorAgentEnd,
|
|
7118
|
+
scheduledSdkRunToken,
|
|
6706
7119
|
);
|
|
6707
7120
|
} finally {
|
|
6708
7121
|
this.#endSelectionFenceDeferralTracking(selectionFenceGeneration);
|
|
@@ -6750,6 +7163,7 @@ export class AgentSession {
|
|
|
6750
7163
|
promptText,
|
|
6751
7164
|
{
|
|
6752
7165
|
skipPostPromptRecoveryWait: true,
|
|
7166
|
+
sdkRunToken: scheduledSdkRunToken,
|
|
6753
7167
|
skipCompactionCheck: true,
|
|
6754
7168
|
predecessorAgentEndHold,
|
|
6755
7169
|
onFinalPreflight: ({ hasPendingNextTurnMessages }) =>
|
|
@@ -7527,16 +7941,16 @@ export class AgentSession {
|
|
|
7527
7941
|
this.#workerIntegrationScheduler?.enqueue();
|
|
7528
7942
|
}
|
|
7529
7943
|
|
|
7530
|
-
async #flushWorkerIntegrationAttempt(): Promise<
|
|
7531
|
-
await this.#workerIntegrationScheduler?.
|
|
7944
|
+
async #flushWorkerIntegrationAttempt(): Promise<WorkerIntegrationOutcome> {
|
|
7945
|
+
return (await this.#workerIntegrationScheduler?.flushWithOutcome()) ?? { status: "completed" };
|
|
7532
7946
|
}
|
|
7533
7947
|
|
|
7534
|
-
async #flushWorkerIntegrationForAgentEnd(): Promise<
|
|
7948
|
+
async #flushWorkerIntegrationForAgentEnd(): Promise<WorkerIntegrationOutcome> {
|
|
7535
7949
|
if (!this.#workerIntegrationRequestedForTurn) {
|
|
7536
7950
|
this.#requestWorkerIntegrationAttempt();
|
|
7537
7951
|
}
|
|
7538
7952
|
try {
|
|
7539
|
-
await this.#flushWorkerIntegrationAttempt();
|
|
7953
|
+
return await this.#flushWorkerIntegrationAttempt();
|
|
7540
7954
|
} finally {
|
|
7541
7955
|
this.#workerIntegrationRequestedForTurn = false;
|
|
7542
7956
|
}
|
|
@@ -7549,6 +7963,9 @@ export class AgentSession {
|
|
|
7549
7963
|
workerIntegrationSettled = false,
|
|
7550
7964
|
scope?: AttemptScopeRef,
|
|
7551
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;
|
|
7552
7969
|
if (event.type === "agent_end" && !workerIntegrationSettled) {
|
|
7553
7970
|
await this.#flushWorkerIntegrationForAgentEnd();
|
|
7554
7971
|
}
|
|
@@ -7559,20 +7976,36 @@ export class AgentSession {
|
|
|
7559
7976
|
? eventToken
|
|
7560
7977
|
: deliveryScope
|
|
7561
7978
|
? this.#sdkRunTokensByAttemptScope.get(deliveryScope as AttemptScope)
|
|
7562
|
-
:
|
|
7979
|
+
: undefined;
|
|
7563
7980
|
const isTerminalAgentEnd =
|
|
7564
7981
|
event.type === "agent_end" && !(event.stopReason === "maintenance" && event.maintenanceOutcome !== "aborted");
|
|
7565
7982
|
const finishAttempt = () => {
|
|
7566
7983
|
if (!isTerminalAgentEnd) return;
|
|
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();
|
|
7567
7989
|
const isActiveAttempt =
|
|
7568
|
-
deliveryScope === undefined
|
|
7569
|
-
|
|
7570
|
-
|
|
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
|
+
}
|
|
7571
8000
|
if (deliveryScope) {
|
|
7572
8001
|
this.#attemptRecordStore.retire(deliveryScope as AttemptScope);
|
|
7573
|
-
|
|
8002
|
+
if (
|
|
8003
|
+
activeSdkRunTokenAtEvent !== undefined &&
|
|
8004
|
+
this.#sdkRunTokensByAttemptScope.get(deliveryScope as AttemptScope) === activeSdkRunTokenAtEvent
|
|
8005
|
+
)
|
|
8006
|
+
this.#sdkRunTokensByAttemptScope.delete(deliveryScope as AttemptScope);
|
|
7574
8007
|
}
|
|
7575
|
-
if (isActiveAttempt
|
|
8008
|
+
if (isActiveAttempt) this.#activeSdkRunToken = undefined;
|
|
7576
8009
|
if (isActiveAttempt) {
|
|
7577
8010
|
this.#activeAttemptScope = undefined;
|
|
7578
8011
|
this.#activeLogicalRunId = undefined;
|
|
@@ -7840,8 +8273,18 @@ export class AgentSession {
|
|
|
7840
8273
|
const sid = this.#providerSessionId ?? sessionId ?? this.sessionManager.getSessionId();
|
|
7841
8274
|
this.agent.sessionId = sid;
|
|
7842
8275
|
this.agent.providerSessionId = this.#providerCacheSessionId ?? sid;
|
|
7843
|
-
this.agent.setMetadataResolver(
|
|
7844
|
-
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
|
+
),
|
|
7845
8288
|
);
|
|
7846
8289
|
}
|
|
7847
8290
|
|
|
@@ -8454,13 +8897,39 @@ export class AgentSession {
|
|
|
8454
8897
|
* handler, so Ctrl+B-style folding fails closed instead of aborting or
|
|
8455
8898
|
* shell-suspending arbitrary work.
|
|
8456
8899
|
*/
|
|
8457
|
-
|
|
8458
|
-
this.#
|
|
8459
|
-
|
|
8460
|
-
|
|
8461
|
-
|
|
8462
|
-
|
|
8463
|
-
|
|
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;
|
|
8464
8933
|
}
|
|
8465
8934
|
|
|
8466
8935
|
/**
|
|
@@ -8469,7 +8938,7 @@ export class AgentSession {
|
|
|
8469
8938
|
* no fold target exists.
|
|
8470
8939
|
*/
|
|
8471
8940
|
hasForegroundBashBackgroundRequestHandler(): boolean {
|
|
8472
|
-
return this.#
|
|
8941
|
+
return this.#foldCoordinator.hasFoldableParticipant();
|
|
8473
8942
|
}
|
|
8474
8943
|
|
|
8475
8944
|
/** Set the SDK permission policy used by guarded ACP tool execution. */
|
|
@@ -8505,11 +8974,17 @@ export class AgentSession {
|
|
|
8505
8974
|
* Ask the active managed foreground bash call to return as a background job.
|
|
8506
8975
|
* Returns false when no supported foreground tool is currently backgroundable.
|
|
8507
8976
|
*/
|
|
8508
|
-
requestForegroundBashBackground(): boolean {
|
|
8509
|
-
|
|
8510
|
-
|
|
8511
|
-
|
|
8512
|
-
|
|
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
|
+
}
|
|
8513
8988
|
}
|
|
8514
8989
|
|
|
8515
8990
|
/**
|
|
@@ -9450,6 +9925,7 @@ export class AgentSession {
|
|
|
9450
9925
|
if (this.#sessionAdmissionClosed || this.#isDisposed) throw this.#sessionAdmissionBusyError();
|
|
9451
9926
|
const inFlightPrompt = this.#beginInFlight();
|
|
9452
9927
|
let hindsightRecall: string | undefined;
|
|
9928
|
+
let continuationSdkRunToken: string | undefined;
|
|
9453
9929
|
try {
|
|
9454
9930
|
const volatileProjectContextMessage = await this.#buildVolatileProjectContextMessage();
|
|
9455
9931
|
this.agent.appendMessage(volatileProjectContextMessage);
|
|
@@ -9485,14 +9961,18 @@ export class AgentSession {
|
|
|
9485
9961
|
await this.agent.continue({
|
|
9486
9962
|
...this.#managedFallbackPromptOptions(),
|
|
9487
9963
|
onRunAccepted: (handle: AttemptRunHandle) => {
|
|
9488
|
-
this.#
|
|
9964
|
+
continuationSdkRunToken = this.#activeSdkRunToken;
|
|
9965
|
+
this.#acceptSdkAttemptRun(handle, continuationSdkRunToken);
|
|
9489
9966
|
if (hindsightRecall) hindsightState?.markRecallSnippetInjected(hindsightRecall);
|
|
9490
9967
|
},
|
|
9491
9968
|
});
|
|
9492
|
-
await this.#waitForPostPromptRecovery();
|
|
9969
|
+
if (continuationSdkRunToken === undefined) await this.#waitForPostPromptRecovery();
|
|
9493
9970
|
} finally {
|
|
9494
9971
|
this.#removeEphemeralCustomMessages();
|
|
9495
|
-
await this.#settleEndedInFlight(
|
|
9972
|
+
await this.#settleEndedInFlight(
|
|
9973
|
+
inFlightPrompt,
|
|
9974
|
+
continuationSdkRunToken === undefined ? "full" : "publication",
|
|
9975
|
+
);
|
|
9496
9976
|
}
|
|
9497
9977
|
}
|
|
9498
9978
|
|
|
@@ -9620,8 +10100,9 @@ export class AgentSession {
|
|
|
9620
10100
|
async setCredentialPin(provider: string, selector: AuthCredentialSelector): Promise<void> {
|
|
9621
10101
|
const scopeId = this.credentialSessionId;
|
|
9622
10102
|
const authStorage = this.#modelRegistry.authStorage;
|
|
9623
|
-
const
|
|
9624
|
-
authStorage.
|
|
10103
|
+
const authStorageOwner = this.#modelRegistry.getAuthStorageOwner();
|
|
10104
|
+
const target = authStorage.resolveOAuthPinTarget(provider, selector, authStorageOwner);
|
|
10105
|
+
authStorage.setSessionCredentialSelector(scopeId, provider, target.canonicalSelector, authStorageOwner);
|
|
9625
10106
|
if (target.canonicalSelector.kind === "id" && !this.#credentialStoreIdentity) return;
|
|
9626
10107
|
this.sessionManager.appendCustomEntry("auth-credential-pin", {
|
|
9627
10108
|
v: 1,
|
|
@@ -9701,9 +10182,13 @@ export class AgentSession {
|
|
|
9701
10182
|
args = "",
|
|
9702
10183
|
options?: Pick<
|
|
9703
10184
|
PromptOptions,
|
|
9704
|
-
"onPreflightAccepted" | "onPreflightAcceptCommit" | "onSkillPrepared" | "preflightSignal" | "
|
|
10185
|
+
"onPreflightAccepted" | "onPreflightAcceptCommit" | "onSkillPrepared" | "preflightSignal" | "sdkRunCapability"
|
|
9705
10186
|
>,
|
|
9706
10187
|
): Promise<{ name: string; path: string; args?: string; lineCount?: number }> {
|
|
10188
|
+
const internalOptions = {
|
|
10189
|
+
...options,
|
|
10190
|
+
sdkRunToken: readSdkRunCapability(options?.sdkRunCapability),
|
|
10191
|
+
};
|
|
9707
10192
|
if (options?.preflightSignal?.aborted) throw promptPreflightCancelledError();
|
|
9708
10193
|
const skillName = name.trim();
|
|
9709
10194
|
if (!skillName) throw Object.assign(new Error("skill.invoke requires a skill name."), { code: "invalid_input" });
|
|
@@ -9749,7 +10234,7 @@ export class AgentSession {
|
|
|
9749
10234
|
lineCount: built.details.lineCount,
|
|
9750
10235
|
cleanedArgs: activation.cleanedArgs || undefined,
|
|
9751
10236
|
});
|
|
9752
|
-
await this.promptCustomMessage(skillPromptMessage,
|
|
10237
|
+
await this.promptCustomMessage(skillPromptMessage, internalOptions as InternalCustomMessageOptions);
|
|
9753
10238
|
return {
|
|
9754
10239
|
name: skill.name,
|
|
9755
10240
|
path: skill.filePath,
|
|
@@ -10439,6 +10924,14 @@ export class AgentSession {
|
|
|
10439
10924
|
* @throws Error if no model selected or no API key available (when not streaming)
|
|
10440
10925
|
*/
|
|
10441
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;
|
|
10442
10935
|
this.#assertRecoveryHydrationPromoted();
|
|
10443
10936
|
const owner = this.#sessionAdmissionContext.getStore();
|
|
10444
10937
|
if (owner && !owner.released) throw this.#sessionAdmissionBusyError();
|
|
@@ -10453,14 +10946,19 @@ export class AgentSession {
|
|
|
10453
10946
|
await this.invokeSkill(
|
|
10454
10947
|
invocation.skill.name,
|
|
10455
10948
|
invocation.args,
|
|
10456
|
-
options?.onPreflightAccepted ||
|
|
10949
|
+
options?.onPreflightAccepted ||
|
|
10950
|
+
options?.onPreflightAcceptCommit ||
|
|
10951
|
+
options?.preflightSignal ||
|
|
10952
|
+
internalOptions?.sdkRunToken
|
|
10457
10953
|
? {
|
|
10458
|
-
...(options
|
|
10459
|
-
|
|
10954
|
+
...(options?.onPreflightAccepted
|
|
10955
|
+
? { onPreflightAccepted: options.onPreflightAccepted }
|
|
10956
|
+
: {}),
|
|
10957
|
+
...(options?.onPreflightAcceptCommit
|
|
10460
10958
|
? { onPreflightAcceptCommit: options.onPreflightAcceptCommit }
|
|
10461
10959
|
: {}),
|
|
10462
|
-
...(options
|
|
10463
|
-
...(options
|
|
10960
|
+
...(options?.preflightSignal ? { preflightSignal: options.preflightSignal } : {}),
|
|
10961
|
+
...(options?.sdkRunCapability ? { sdkRunCapability: options.sdkRunCapability } : {}),
|
|
10464
10962
|
}
|
|
10465
10963
|
: undefined,
|
|
10466
10964
|
);
|
|
@@ -10494,6 +10992,10 @@ export class AgentSession {
|
|
|
10494
10992
|
|
|
10495
10993
|
// Expand file-based prompt templates if requested
|
|
10496
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" });
|
|
10497
10999
|
assertImagePlaceholdersHavePayload(expandedText, options?.images);
|
|
10498
11000
|
const workflowIntentDiff = options?.synthetic ? null : buildWorkflowIntentDiff(expandedText);
|
|
10499
11001
|
const claimsGenuineUserIntent = !options?.synthetic && options?.attribution !== "agent";
|
|
@@ -10714,9 +11216,13 @@ export class AgentSession {
|
|
|
10714
11216
|
| "onPreflightAccepted"
|
|
10715
11217
|
| "onPreflightAcceptCommit"
|
|
10716
11218
|
| "preflightSignal"
|
|
10717
|
-
| "
|
|
11219
|
+
| "sdkRunCapability"
|
|
10718
11220
|
>,
|
|
10719
11221
|
): Promise<void> {
|
|
11222
|
+
const sdkRunToken = readSdkRunCapability(options?.sdkRunCapability);
|
|
11223
|
+
const internalOptions: InternalCustomMessageOptions | undefined = options
|
|
11224
|
+
? { ...options, ...(sdkRunToken ? { sdkRunToken } : {}) }
|
|
11225
|
+
: undefined;
|
|
10720
11226
|
if (options?.preflightSignal?.aborted) throw promptPreflightCancelledError();
|
|
10721
11227
|
const textContent =
|
|
10722
11228
|
typeof message.content === "string"
|
|
@@ -10785,7 +11291,7 @@ export class AgentSession {
|
|
|
10785
11291
|
};
|
|
10786
11292
|
try {
|
|
10787
11293
|
await this.#promptWithMessage(customMessage, textContent, {
|
|
10788
|
-
...
|
|
11294
|
+
...internalOptions,
|
|
10789
11295
|
onPreflightAccepted: undefined,
|
|
10790
11296
|
onPreflightAcceptCommit: commitAcceptance,
|
|
10791
11297
|
admissionLease: admission,
|
|
@@ -10816,8 +11322,8 @@ export class AgentSession {
|
|
|
10816
11322
|
| "onPreflightAccepted"
|
|
10817
11323
|
| "onPreflightAcceptCommit"
|
|
10818
11324
|
| "preflightSignal"
|
|
10819
|
-
| "sdkRunToken"
|
|
10820
11325
|
> & {
|
|
11326
|
+
sdkRunToken?: string;
|
|
10821
11327
|
prependMessages?: AgentMessage[];
|
|
10822
11328
|
skipPostPromptRecoveryWait?: boolean;
|
|
10823
11329
|
predecessorAgentEndHold?: symbol;
|
|
@@ -10884,6 +11390,9 @@ export class AgentSession {
|
|
|
10884
11390
|
const rosterClaim = this.#claimIrcRosterCandidate();
|
|
10885
11391
|
let hasPendingNextTurnMessages = false;
|
|
10886
11392
|
let pendingNextTurnMessageCount = 0;
|
|
11393
|
+
let skipPostPromptRecoveryWait =
|
|
11394
|
+
options?.skipPostPromptRecoveryWait === true || options?.sdkRunToken !== undefined;
|
|
11395
|
+
let promptAttemptScope: AttemptScope | undefined;
|
|
10887
11396
|
let hindsightRecall: string | undefined;
|
|
10888
11397
|
try {
|
|
10889
11398
|
this.#throwIfPromptPreflightCancelled(generation, preflightSignal);
|
|
@@ -11152,9 +11661,10 @@ export class AgentSession {
|
|
|
11152
11661
|
...this.#managedFallbackPromptOptions(),
|
|
11153
11662
|
...(options?.sdkRunToken ? { sdkRunToken: options.sdkRunToken } : {}),
|
|
11154
11663
|
onRunAccepted: (handle: AttemptRunHandle) => {
|
|
11155
|
-
|
|
11156
|
-
if (options?.sdkRunToken)
|
|
11157
|
-
|
|
11664
|
+
promptAttemptScope = handle?.scope;
|
|
11665
|
+
if (handle) this.#acceptSdkAttemptRun(handle, options?.sdkRunToken);
|
|
11666
|
+
else this.#acceptRunHandle(handle);
|
|
11667
|
+
if (handle && options?.sdkRunToken) {
|
|
11158
11668
|
this.#activeSdkRunToken = options.sdkRunToken;
|
|
11159
11669
|
}
|
|
11160
11670
|
options?.onRunAccepted?.(handle);
|
|
@@ -11189,6 +11699,20 @@ export class AgentSession {
|
|
|
11189
11699
|
options?.onPreflightAccepted?.();
|
|
11190
11700
|
},
|
|
11191
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
|
+
}
|
|
11192
11716
|
const terminalAssistant = this.#findLastAssistantMessage();
|
|
11193
11717
|
if (
|
|
11194
11718
|
rosterClaim &&
|
|
@@ -11198,7 +11722,7 @@ export class AgentSession {
|
|
|
11198
11722
|
) {
|
|
11199
11723
|
this.#commitIrcRosterClaim(rosterClaim.token, rosterClaim.epoch);
|
|
11200
11724
|
}
|
|
11201
|
-
if (!
|
|
11725
|
+
if (!skipPostPromptRecoveryWait) {
|
|
11202
11726
|
await this.#waitForPostPromptRecovery();
|
|
11203
11727
|
}
|
|
11204
11728
|
} catch (error) {
|
|
@@ -11223,7 +11747,7 @@ export class AgentSession {
|
|
|
11223
11747
|
this.#releaseIrcRosterClaim(rosterClaim.token, rosterClaim.epoch);
|
|
11224
11748
|
}
|
|
11225
11749
|
this.#releaseDeferredAgentEndContinuation(predecessorAgentEndHold);
|
|
11226
|
-
await this.#settleEndedInFlight(inFlightPrompt,
|
|
11750
|
+
await this.#settleEndedInFlight(inFlightPrompt, skipPostPromptRecoveryWait ? "publication" : "full");
|
|
11227
11751
|
}
|
|
11228
11752
|
}
|
|
11229
11753
|
|
|
@@ -11427,12 +11951,18 @@ export class AgentSession {
|
|
|
11427
11951
|
* Queue a steering message to interrupt the agent mid-run.
|
|
11428
11952
|
*/
|
|
11429
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" });
|
|
11430
11958
|
this.#assertRecoveryHydrationPromoted();
|
|
11431
11959
|
if (text.startsWith("/")) {
|
|
11432
11960
|
this.#throwIfExtensionCommand(text);
|
|
11433
11961
|
}
|
|
11434
11962
|
|
|
11435
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" });
|
|
11436
11966
|
assertImagePlaceholdersHavePayload(expandedText, images);
|
|
11437
11967
|
await this.#queueSteer(expandedText, images, { claimsGenuineUserIntent: true });
|
|
11438
11968
|
}
|
|
@@ -11445,12 +11975,18 @@ export class AgentSession {
|
|
|
11445
11975
|
images?: ImageContent[],
|
|
11446
11976
|
options?: Pick<PromptOptions, "followUpQueuePolicy">,
|
|
11447
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" });
|
|
11448
11982
|
this.#assertRecoveryHydrationPromoted();
|
|
11449
11983
|
if (text.startsWith("/")) {
|
|
11450
11984
|
this.#throwIfExtensionCommand(text);
|
|
11451
11985
|
}
|
|
11452
11986
|
|
|
11453
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" });
|
|
11454
11990
|
assertImagePlaceholdersHavePayload(expandedText, images);
|
|
11455
11991
|
await this.#queueFollowUp(expandedText, images, {
|
|
11456
11992
|
forceOneAtATime: options?.followUpQueuePolicy === "sequential",
|
|
@@ -11468,6 +12004,7 @@ export class AgentSession {
|
|
|
11468
12004
|
claimsGenuineUserIntent?: boolean;
|
|
11469
12005
|
onPromoted?: (promotion: { startsOwnRun?: boolean; removed?: boolean }) => void;
|
|
11470
12006
|
external?: boolean;
|
|
12007
|
+
sdkRunToken?: string;
|
|
11471
12008
|
},
|
|
11472
12009
|
): Promise<void> {
|
|
11473
12010
|
this.#assertNoHandoffTransition();
|
|
@@ -11482,6 +12019,7 @@ export class AgentSession {
|
|
|
11482
12019
|
this.#externalSteerAdmissionSeq.set(message, ++this.#steeringAdmissionSeq);
|
|
11483
12020
|
if (options.onPromoted) this.#steerPromotionHooks.set(message, options.onPromoted);
|
|
11484
12021
|
}
|
|
12022
|
+
if (options?.sdkRunToken) this.#sdkRunTokensByQueuedMessage.set(message, options.sdkRunToken);
|
|
11485
12023
|
if (options?.claimsGenuineUserIntent) {
|
|
11486
12024
|
const epoch = this.#claimDeepInterviewUserIntent();
|
|
11487
12025
|
this.#deepInterviewGenuineUserMessageEpochs.set(message, epoch);
|
|
@@ -12064,9 +12602,11 @@ export class AgentSession {
|
|
|
12064
12602
|
/** Internal dispatch disposition used before actual queue consumption. */
|
|
12065
12603
|
onDispatchDisposition?: (promotion: { startsOwnRun: boolean }) => void;
|
|
12066
12604
|
preflightSignal?: AbortSignal;
|
|
12067
|
-
|
|
12605
|
+
sdkRunCapability?: unknown;
|
|
12068
12606
|
},
|
|
12069
12607
|
): Promise<void> {
|
|
12608
|
+
const sdkRunToken = readSdkRunCapability(options?.sdkRunCapability);
|
|
12609
|
+
const internalOptions = options ? { ...options, ...(sdkRunToken ? { sdkRunToken } : {}) } : undefined;
|
|
12070
12610
|
this.#assertRecoveryHydrationPromoted();
|
|
12071
12611
|
const owner = this.#sessionAdmissionContext.getStore();
|
|
12072
12612
|
if (owner && !owner.released) throw this.#sessionAdmissionBusyError();
|
|
@@ -12087,7 +12627,15 @@ export class AgentSession {
|
|
|
12087
12627
|
const textParts: string[] = [];
|
|
12088
12628
|
images = [];
|
|
12089
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
|
+
});
|
|
12090
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
|
+
});
|
|
12091
12639
|
textParts.push(part.text);
|
|
12092
12640
|
} else {
|
|
12093
12641
|
images.push(part);
|
|
@@ -12096,6 +12644,10 @@ export class AgentSession {
|
|
|
12096
12644
|
text = textParts.join("\n");
|
|
12097
12645
|
if (images.length === 0) images = undefined;
|
|
12098
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" });
|
|
12099
12651
|
|
|
12100
12652
|
let admissionSignal = options?.preflightSignal
|
|
12101
12653
|
? AbortSignal.any([this.#promptPreflightAbortController.signal, options.preflightSignal])
|
|
@@ -12189,7 +12741,7 @@ export class AgentSession {
|
|
|
12189
12741
|
claimsGenuineUserIntent: true,
|
|
12190
12742
|
forceOneAtATime: Boolean(options?.preflightSignal || options?.queuedAtDispatch),
|
|
12191
12743
|
onPromoted: options?.onQueuedPromoted,
|
|
12192
|
-
sdkRunToken:
|
|
12744
|
+
sdkRunToken: internalOptions?.sdkRunToken,
|
|
12193
12745
|
});
|
|
12194
12746
|
const cancelQueuedFollowUp = () => queuedFollowUp.cancel();
|
|
12195
12747
|
options?.preflightSignal?.addEventListener("abort", cancelQueuedFollowUp, { once: true });
|
|
@@ -12204,6 +12756,7 @@ export class AgentSession {
|
|
|
12204
12756
|
claimsGenuineUserIntent: true,
|
|
12205
12757
|
onPromoted: options?.onQueuedPromoted,
|
|
12206
12758
|
external: true,
|
|
12759
|
+
sdkRunToken: internalOptions?.sdkRunToken,
|
|
12207
12760
|
});
|
|
12208
12761
|
options?.onPreflightAccepted?.();
|
|
12209
12762
|
return;
|
|
@@ -12223,6 +12776,7 @@ export class AgentSession {
|
|
|
12223
12776
|
claimsGenuineUserIntent: true,
|
|
12224
12777
|
onPromoted: options?.onQueuedPromoted,
|
|
12225
12778
|
external: true,
|
|
12779
|
+
sdkRunToken: internalOptions?.sdkRunToken,
|
|
12226
12780
|
});
|
|
12227
12781
|
// Dispatch-race disposition (#4668 review P1): the SDK snapshot-decided
|
|
12228
12782
|
// this submission starts its own turn (idle at dispatch), but the
|
|
@@ -12248,7 +12802,7 @@ export class AgentSession {
|
|
|
12248
12802
|
await this.prompt(text, {
|
|
12249
12803
|
expandPromptTemplates: false,
|
|
12250
12804
|
images,
|
|
12251
|
-
sdkRunToken:
|
|
12805
|
+
sdkRunToken: internalOptions?.sdkRunToken,
|
|
12252
12806
|
onPreflightAccepted: () => {
|
|
12253
12807
|
options?.onPreflightAccepted?.();
|
|
12254
12808
|
fireQueuedPromotion();
|
|
@@ -12263,7 +12817,7 @@ export class AgentSession {
|
|
|
12263
12817
|
}
|
|
12264
12818
|
: undefined,
|
|
12265
12819
|
preflightSignal: options?.preflightSignal,
|
|
12266
|
-
});
|
|
12820
|
+
} as InternalPromptOptions);
|
|
12267
12821
|
} finally {
|
|
12268
12822
|
releaseFollowUpReservation();
|
|
12269
12823
|
}
|
|
@@ -12772,6 +13326,7 @@ export class AgentSession {
|
|
|
12772
13326
|
| "tool_abort"
|
|
12773
13327
|
| "internal";
|
|
12774
13328
|
silent?: boolean;
|
|
13329
|
+
sdkRunToken?: string;
|
|
12775
13330
|
}): void {
|
|
12776
13331
|
const abortGoalState = this.getGoalModeState();
|
|
12777
13332
|
this.#suppressNextGoalReminderAfterAbortGoalId =
|
|
@@ -13473,6 +14028,9 @@ export class AgentSession {
|
|
|
13473
14028
|
attribution: "user" as const,
|
|
13474
14029
|
timestamp: Date.now(),
|
|
13475
14030
|
};
|
|
14031
|
+
const selectedSdkRunToken = selectedMessage
|
|
14032
|
+
? this.#sdkRunTokensByQueuedMessage.get(selectedMessage)
|
|
14033
|
+
: undefined;
|
|
13476
14034
|
const messageText =
|
|
13477
14035
|
message.role === "custom"
|
|
13478
14036
|
? this.#getCustomMessageTextContent(message)
|
|
@@ -13491,6 +14049,7 @@ export class AgentSession {
|
|
|
13491
14049
|
await this.#promptWithMessage(message, messageText, {
|
|
13492
14050
|
admissionLease: admission,
|
|
13493
14051
|
resetRetryReplaySafety: true,
|
|
14052
|
+
sdkRunToken: selectedSdkRunToken,
|
|
13494
14053
|
onRunAccepted: () => {
|
|
13495
14054
|
runAccepted = true;
|
|
13496
14055
|
if (selected) this.#fireQueuedPromotionHooks([message], { startsOwnRun: true });
|
|
@@ -19052,6 +19611,9 @@ export class AgentSession {
|
|
|
19052
19611
|
const continuation = this.agent.continue({
|
|
19053
19612
|
...this.#managedFallbackPromptOptions(),
|
|
19054
19613
|
transientRecoveryMessage: this.#escapedNonAsciiRecoveryMessage(),
|
|
19614
|
+
onRunAccepted: (handle: AttemptRunHandle) => {
|
|
19615
|
+
this.#acceptSdkAttemptRun(handle, this.#activeSdkRunToken);
|
|
19616
|
+
},
|
|
19055
19617
|
});
|
|
19056
19618
|
if (
|
|
19057
19619
|
cancellationSignal?.aborted ||
|
|
@@ -19090,6 +19652,9 @@ export class AgentSession {
|
|
|
19090
19652
|
await this.agent.continue({
|
|
19091
19653
|
...this.#managedFallbackPromptOptions(),
|
|
19092
19654
|
transientRecoveryMessage: this.#escapedNonAsciiRecoveryMessage(),
|
|
19655
|
+
onRunAccepted: (handle: AttemptRunHandle) => {
|
|
19656
|
+
this.#acceptSdkAttemptRun(handle, this.#activeSdkRunToken);
|
|
19657
|
+
},
|
|
19093
19658
|
});
|
|
19094
19659
|
},
|
|
19095
19660
|
};
|
|
@@ -19519,11 +20084,13 @@ export class AgentSession {
|
|
|
19519
20084
|
if (!isAuthenticated(activeApiKey)) return "unchanged";
|
|
19520
20085
|
remaining = await authStorage.invalidateCredentialMatching(provider, activeApiKey, {
|
|
19521
20086
|
sessionId: credentialSessionId,
|
|
20087
|
+
owner: this.#modelRegistry.getAuthStorageOwner(),
|
|
19522
20088
|
});
|
|
19523
20089
|
if (!remaining) return "unchanged";
|
|
19524
20090
|
} else {
|
|
19525
20091
|
remaining = await authStorage.markUsageLimitReached(provider, credentialSessionId, {
|
|
19526
20092
|
retryAfterMs: trigger.retryAfterMs,
|
|
20093
|
+
owner: this.#modelRegistry.getAuthStorageOwner(),
|
|
19527
20094
|
});
|
|
19528
20095
|
}
|
|
19529
20096
|
|
|
@@ -20031,7 +20598,12 @@ export class AgentSession {
|
|
|
20031
20598
|
this.#resolveRetry();
|
|
20032
20599
|
return;
|
|
20033
20600
|
}
|
|
20034
|
-
await this.agent.continue(
|
|
20601
|
+
await this.agent.continue({
|
|
20602
|
+
...this.#managedFallbackPromptOptions(),
|
|
20603
|
+
onRunAccepted: (handle: AttemptRunHandle) => {
|
|
20604
|
+
this.#acceptSdkAttemptRun(handle, this.#activeSdkRunToken);
|
|
20605
|
+
},
|
|
20606
|
+
});
|
|
20035
20607
|
return;
|
|
20036
20608
|
} catch (error) {
|
|
20037
20609
|
const attempt = this.#retryAttempt;
|
|
@@ -21068,6 +21640,9 @@ export class AgentSession {
|
|
|
21068
21640
|
model.provider,
|
|
21069
21641
|
this.#modelRegistry.authStorage,
|
|
21070
21642
|
this.credentialSessionId,
|
|
21643
|
+
model,
|
|
21644
|
+
// Optional for the same reason as the main metadata resolver above.
|
|
21645
|
+
this.#modelRegistry.getAuthStorageOwner?.(),
|
|
21071
21646
|
),
|
|
21072
21647
|
reasoning: toReasoningEffort(this.thinkingLevel),
|
|
21073
21648
|
hideThinkingSummary: this.agent.hideThinkingSummary,
|