@gajae-code/coding-agent 0.16.0 → 0.16.1
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 +41 -1
- package/dist/types/async/job-manager.d.ts +32 -1
- package/dist/types/browser-backend.d.ts +12 -0
- package/dist/types/cli/update-cli.d.ts +1 -0
- package/dist/types/commands/sdk.d.ts +2 -0
- package/dist/types/config/billing-path.d.ts +25 -0
- package/dist/types/config/config-schema-version.d.ts +13 -1
- package/dist/types/config/model-registry.d.ts +16 -0
- package/dist/types/config/settings-schema.d.ts +17 -7
- package/dist/types/extensibility/extensions/types.d.ts +8 -3
- package/dist/types/gjc-runtime/launch-worktree.d.ts +25 -1
- package/dist/types/gjc-runtime/session-state-lock.d.ts +22 -0
- package/dist/types/main.d.ts +1 -0
- package/dist/types/modes/acp/acp-agent.d.ts +21 -21
- package/dist/types/modes/components/model-selector.d.ts +4 -0
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/components/sessions-dashboard.d.ts +2 -1
- package/dist/types/modes/interactive-mode.d.ts +16 -2
- package/dist/types/modes/theme/theme.d.ts +2 -1
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/sdk/broker/broker.d.ts +2 -1
- package/dist/types/sdk/broker/ensure.d.ts +3 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +6 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +14 -0
- package/dist/types/sdk/broker/startup-budget.d.ts +52 -0
- package/dist/types/sdk/broker/state-version.d.ts +17 -0
- package/dist/types/sdk/bus/discord-daemon.d.ts +1 -1
- package/dist/types/sdk/bus/index.d.ts +3 -1
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +1 -1
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +1 -1
- package/dist/types/sdk/bus/slack-conversation.d.ts +1 -1
- package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
- package/dist/types/sdk/cli/rows.d.ts +8 -2
- package/dist/types/sdk/cli/session-cli.d.ts +2 -1
- package/dist/types/sdk/host/events.d.ts +1 -0
- package/dist/types/sdk/host/query/cursor.d.ts +4 -2
- package/dist/types/sdk/host/query/handlers.d.ts +1 -0
- package/dist/types/sdk/host/query/revision-store.d.ts +1 -0
- package/dist/types/sdk/host/session-runtime.d.ts +18 -17
- package/dist/types/sdk/lifecycle-session.d.ts +1 -1
- package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -5
- package/dist/types/sdk/receipt-state.d.ts +2 -0
- package/dist/types/sdk/router/session-router.d.ts +4 -0
- package/dist/types/sdk/turn-result.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +47 -7
- package/dist/types/session/fold-coordinator.d.ts +29 -2
- package/dist/types/session/session-manager.d.ts +14 -2
- package/dist/types/tools/ask.d.ts +1 -0
- package/dist/types/tools/bash.d.ts +12 -0
- package/dist/types/tools/browser/launch.d.ts +10 -0
- package/dist/types/tools/browser/registry.d.ts +8 -0
- package/dist/types/tools/index.d.ts +6 -10
- package/dist/types/tools/job.d.ts +3 -1
- package/dist/types/utils/external-editor.d.ts +7 -0
- package/examples/extensions/README.md +1 -1
- package/examples/extensions/pirate.ts +7 -4
- package/package.json +7 -7
- package/scripts/generate-sdk-operation-inventory.ts +7 -1
- package/src/async/job-manager.ts +107 -9
- package/src/browser-backend.ts +29 -0
- package/src/cli/plugin-cli.ts +29 -0
- package/src/cli/update-cli.ts +63 -12
- package/src/commands/sdk.ts +22 -7
- package/src/config/billing-path.ts +42 -0
- package/src/config/config-schema-version.ts +18 -1
- package/src/config/model-preset-registry.ts +177 -18
- package/src/config/model-registry.ts +268 -42
- package/src/config/settings-schema.ts +19 -7
- package/src/config/settings.ts +46 -15
- package/src/coordinator-mcp/projection-scan.ts +6 -6
- package/src/coordinator-mcp/server.ts +21 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
- package/src/extensibility/extensions/runner.ts +27 -0
- package/src/extensibility/extensions/types.ts +8 -3
- package/src/extensibility/gjc-plugins/installer.ts +51 -11
- package/src/extensibility/runtime-skill-discovery.ts +110 -3
- package/src/extensibility/skills.ts +22 -12
- package/src/gjc-runtime/launch-worktree.ts +285 -4
- package/src/gjc-runtime/session-state-lock.ts +154 -28
- package/src/gjc-runtime/session-state-sidecar.ts +10 -17
- package/src/gjc-runtime/tmux-owner-isolation.ts +16 -1
- package/src/internal-urls/docs-index.generated.ts +10 -10
- package/src/main.ts +4 -1
- package/src/modes/acp/acp-agent.ts +5 -5
- package/src/modes/components/model-selector.ts +76 -8
- package/src/modes/components/session-selector.ts +23 -15
- package/src/modes/components/sessions-dashboard.ts +8 -5
- package/src/modes/controllers/command-controller.ts +1 -1
- package/src/modes/controllers/event-controller.ts +30 -4
- package/src/modes/controllers/extension-ui-controller.ts +13 -13
- package/src/modes/controllers/input-controller.ts +2 -2
- package/src/modes/controllers/selector-controller.ts +10 -3
- package/src/modes/interactive-mode.ts +36 -12
- package/src/modes/prompt-action-autocomplete.ts +30 -4
- package/src/modes/runtime-init.ts +12 -9
- package/src/modes/theme/theme.ts +5 -0
- package/src/modes/types.ts +2 -0
- package/src/modes/utils/ui-helpers.ts +22 -3
- package/src/prompts/agent-fragments/browser-aside.md +22 -0
- package/src/prompts/tools/ask.md +1 -1
- package/src/prompts/tools/bash.md +1 -0
- package/src/prompts/tools/job.md +1 -1
- package/src/sdk/acp/adapter.ts +5 -0
- package/src/sdk/broker/broker.ts +10 -1
- package/src/sdk/broker/ensure.ts +76 -3
- package/src/sdk/broker/lifecycle-ledger.ts +6 -0
- package/src/sdk/broker/lifecycle.ts +254 -34
- package/src/sdk/broker/spawn-substrate.ts +45 -2
- package/src/sdk/broker/startup-budget.ts +144 -2
- package/src/sdk/broker/state-version.ts +17 -0
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +111 -24
- package/src/sdk/bus/discord-daemon.ts +50 -10
- package/src/sdk/bus/index.ts +33 -3
- package/src/sdk/bus/kind-aware-reconciliation.ts +296 -48
- package/src/sdk/bus/lifecycle-commands.ts +4 -0
- package/src/sdk/bus/notification-service.ts +22 -5
- package/src/sdk/bus/prompt-reconciliation.ts +7 -2
- package/src/sdk/bus/reconciliation-store.ts +55 -31
- package/src/sdk/bus/slack-conversation.ts +1 -1
- package/src/sdk/bus/slack-daemon.ts +72 -38
- package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
- package/src/sdk/cli/rows.ts +30 -7
- package/src/sdk/cli/session-cli.ts +33 -21
- package/src/sdk/host/control/dispatch.ts +2 -2
- package/src/sdk/host/events.ts +1 -0
- package/src/sdk/host/host.ts +2 -0
- package/src/sdk/host/query/cursor.ts +52 -7
- package/src/sdk/host/query/handlers.ts +33 -12
- package/src/sdk/host/query/revision-store.ts +12 -0
- package/src/sdk/host/session-runtime.test.ts +463 -8
- package/src/sdk/host/session-runtime.ts +383 -54
- package/src/sdk/host/surface-policy.ts +3 -3
- package/src/sdk/lifecycle-session.ts +7 -2
- package/src/sdk/prompt-deadline-manager.ts +2 -1
- package/src/sdk/protocol/operation-inventory.generated.json +54 -3
- package/src/sdk/protocol/operation-registry.ts +1 -1
- package/src/sdk/receipt-state.ts +10 -0
- package/src/sdk/router/session-router.ts +152 -59
- package/src/sdk/session.ts +60 -41
- package/src/sdk/turn-result.ts +22 -0
- package/src/session/agent-session.ts +1033 -260
- package/src/session/fold-coordinator.ts +40 -3
- package/src/session/session-manager.ts +517 -77
- package/src/slash-commands/builtin-registry.ts +41 -0
- package/src/task/executor.ts +9 -4
- package/src/tools/ask-contract.test.ts +64 -0
- package/src/tools/ask-contract.ts +36 -1
- package/src/tools/ask.ts +10 -8
- package/src/tools/bash-interactive.ts +11 -2
- package/src/tools/bash.ts +156 -33
- package/src/tools/browser/launch.ts +37 -0
- package/src/tools/browser/registry.ts +99 -9
- package/src/tools/cron.ts +6 -4
- package/src/tools/descriptors.test.ts +15 -0
- package/src/tools/descriptors.ts +5 -1
- package/src/tools/index.ts +6 -6
- package/src/tools/job.ts +4 -1
- package/src/tools/monitor.ts +12 -9
- package/src/tools/resolve.ts +30 -10
- package/src/tools/tool-catalog.generated.ts +2 -2
- package/src/utils/external-editor.ts +72 -1
|
@@ -56,15 +56,22 @@ import {
|
|
|
56
56
|
import { projectQ10Models } from "../models.js";
|
|
57
57
|
import { PromptDeadlineManager, type PromptTerminalTransitionEvidence } from "../prompt-deadline-manager";
|
|
58
58
|
import { formatPromptFailureForLocalLog, sanitizePromptFailure } from "../prompt-failure";
|
|
59
|
+
import type { SdkPromptTerminalOutcome } from "../prompt-status";
|
|
59
60
|
import { validateRequiredPromptText } from "../protocol/adapter-validation";
|
|
60
61
|
import { OPERATIONS } from "../protocol/operation-registry";
|
|
62
|
+
import { type ReceiptState, reduceReceiptState } from "../receipt-state";
|
|
61
63
|
import {
|
|
62
64
|
createKindAwareReconciliation,
|
|
63
65
|
createReconciliationStore,
|
|
64
66
|
type KindAwareReconciliation,
|
|
65
67
|
resolveReconciliationSessionFile,
|
|
66
68
|
} from "../reconciliation-extensions";
|
|
67
|
-
import {
|
|
69
|
+
import {
|
|
70
|
+
reduceTurnResultContent,
|
|
71
|
+
reportableTurnResultContent,
|
|
72
|
+
sanitizeTurnResultContent,
|
|
73
|
+
type TurnResultContent,
|
|
74
|
+
} from "../turn-result";
|
|
68
75
|
import { type ControlSurface, controlRequestFromFrame, dispatchControl, terminalAbortIdentity } from "./control";
|
|
69
76
|
import {
|
|
70
77
|
BROKER_RUNTIME_ABORT_CAPABILITY_FIELD,
|
|
@@ -224,6 +231,8 @@ export interface SdkOnlyInvocationRecord extends InvocationCorrelation {
|
|
|
224
231
|
startedAt?: number;
|
|
225
232
|
terminalAt?: number;
|
|
226
233
|
error?: { code: string; message: string };
|
|
234
|
+
content?: TurnResultContent;
|
|
235
|
+
receiptState?: Exclude<ReceiptState, "absent">;
|
|
227
236
|
outcome?: unknown;
|
|
228
237
|
pendingOutcome?: unknown;
|
|
229
238
|
skillName?: string;
|
|
@@ -499,6 +508,8 @@ export interface CreateSdkSessionRuntimeOptions {
|
|
|
499
508
|
onLifecycleDrainTimeoutForTests?: () => void;
|
|
500
509
|
/** Test-only observation of bounded failure-diagnostic deduplication state. */
|
|
501
510
|
onFailureDiagnosticKeyCountForTests?: (count: number) => void;
|
|
511
|
+
/** Test-only observation after a resolved invocation completion is reconciled. */
|
|
512
|
+
onInvocationCompletionReconciledForTests?: (kind: InvocationKind, correlation: InvocationCorrelation) => void;
|
|
502
513
|
}
|
|
503
514
|
|
|
504
515
|
function unavailable(operation: string): () => never {
|
|
@@ -516,7 +527,67 @@ export interface InvocationCorrelation {
|
|
|
516
527
|
|
|
517
528
|
export type InvocationKind = "prompt" | "skill" | "steer";
|
|
518
529
|
type InvocationStatus = "accepted" | "in_flight" | "terminal_ok" | "failed" | "uncertain";
|
|
530
|
+
type InvocationOutcome = SdkPromptTerminalOutcome;
|
|
531
|
+
type InvocationOutcomeInput = {
|
|
532
|
+
kind: string;
|
|
533
|
+
reason?: unknown;
|
|
534
|
+
code?: unknown;
|
|
535
|
+
message?: unknown;
|
|
536
|
+
provenance?: unknown;
|
|
537
|
+
};
|
|
519
538
|
const EMPTY_PROMPT_FAILURE = { code: "prompt_failed", message: "Prompt submission failed." } as const;
|
|
539
|
+
const PRESERVED_PROVIDER_FAILURE_CODES = new Set([
|
|
540
|
+
"provider_down",
|
|
541
|
+
"provider_unavailable",
|
|
542
|
+
"provider_rejected",
|
|
543
|
+
"upstream_stream_interrupted",
|
|
544
|
+
"argument_validation",
|
|
545
|
+
"execution",
|
|
546
|
+
"upstream_error",
|
|
547
|
+
"transport_reset",
|
|
548
|
+
]);
|
|
549
|
+
const isPreservedProviderFailure = (code: string | undefined): boolean =>
|
|
550
|
+
code !== undefined && (PRESERVED_PROVIDER_FAILURE_CODES.has(code) || code.startsWith("provider_http_"));
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Durable reconciliation accepts only the bounded outcome vocabulary from
|
|
554
|
+
* reconciliation-store.ts. Provider diagnostics keep their specific safe code
|
|
555
|
+
* in `error`, while the durable outcome is normalized to the public failure
|
|
556
|
+
* classifier so a failed terminal can never be quarantined on reload.
|
|
557
|
+
*/
|
|
558
|
+
function canonicalFailedOutcome(
|
|
559
|
+
failure?: { code?: unknown; message?: unknown },
|
|
560
|
+
provenance: "agent_failed" | "deadline" = "agent_failed",
|
|
561
|
+
): InvocationOutcome {
|
|
562
|
+
const deadline = provenance === "deadline" || failure?.code === "prompt_deadline_exceeded";
|
|
563
|
+
return {
|
|
564
|
+
kind: "failed",
|
|
565
|
+
code: deadline ? "prompt_deadline_exceeded" : "prompt_failed",
|
|
566
|
+
message: deadline ? "Prompt deadline exceeded." : EMPTY_PROMPT_FAILURE.message,
|
|
567
|
+
provenance: deadline ? "deadline" : "agent_failed",
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function canonicalTerminalOutcome(
|
|
572
|
+
outcome: unknown,
|
|
573
|
+
failure?: { code?: unknown; message?: unknown },
|
|
574
|
+
): InvocationOutcome | undefined {
|
|
575
|
+
if (outcome && typeof outcome === "object") {
|
|
576
|
+
const candidate = outcome as { kind?: unknown; reason?: unknown; provenance?: unknown; code?: unknown };
|
|
577
|
+
if (candidate.kind === "stopped") {
|
|
578
|
+
if (candidate.reason === "cancelled" && candidate.provenance === "client_cancel")
|
|
579
|
+
return { kind: "stopped", reason: "cancelled", provenance: "client_cancel" };
|
|
580
|
+
return undefined;
|
|
581
|
+
}
|
|
582
|
+
if (candidate.kind === "failed")
|
|
583
|
+
return canonicalFailedOutcome(
|
|
584
|
+
{ code: candidate.code, message: (outcome as { message?: unknown }).message },
|
|
585
|
+
candidate.provenance === "deadline" ? "deadline" : "agent_failed",
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
if (failure !== undefined) return canonicalFailedOutcome(failure);
|
|
589
|
+
return undefined;
|
|
590
|
+
}
|
|
520
591
|
interface InvocationRecord extends InvocationCorrelation {
|
|
521
592
|
kind: InvocationKind;
|
|
522
593
|
revision: number;
|
|
@@ -527,6 +598,9 @@ interface InvocationRecord extends InvocationCorrelation {
|
|
|
527
598
|
startedAt?: number;
|
|
528
599
|
terminalAt?: number;
|
|
529
600
|
error?: { code: string; message: string };
|
|
601
|
+
content?: TurnResultContent;
|
|
602
|
+
receiptState?: Exclude<ReceiptState, "absent">;
|
|
603
|
+
outcome?: unknown;
|
|
530
604
|
}
|
|
531
605
|
export interface InvocationReconciliation {
|
|
532
606
|
/** Shared v2 reconciliation owner; present for durable terminal admission. */
|
|
@@ -542,7 +616,7 @@ export interface InvocationReconciliation {
|
|
|
542
616
|
type: "agent_start" | "agent_end";
|
|
543
617
|
content?: TurnResultContent;
|
|
544
618
|
hasActivity?: boolean;
|
|
545
|
-
outcome?:
|
|
619
|
+
outcome?: InvocationOutcome;
|
|
546
620
|
}
|
|
547
621
|
| { type: "agent_failed"; error: unknown; content?: TurnResultContent; hasActivity?: boolean },
|
|
548
622
|
): Promise<void>;
|
|
@@ -557,8 +631,8 @@ export interface InvocationReconciliation {
|
|
|
557
631
|
claimPendingOutcome(
|
|
558
632
|
kind: InvocationKind,
|
|
559
633
|
correlation: InvocationCorrelation,
|
|
560
|
-
outcome:
|
|
561
|
-
): Promise<
|
|
634
|
+
outcome: InvocationOutcomeInput,
|
|
635
|
+
): Promise<InvocationOutcome>;
|
|
562
636
|
// Positional compatibility (exact-head review P1): the 4th argument is either
|
|
563
637
|
// the generation-fence isCurrent callback or the legacy recordError object;
|
|
564
638
|
// both shapes are normalized in the implementation so a legacy object can
|
|
@@ -566,7 +640,7 @@ export interface InvocationReconciliation {
|
|
|
566
640
|
finalizeOutcome(
|
|
567
641
|
kind: InvocationKind,
|
|
568
642
|
correlation: InvocationCorrelation,
|
|
569
|
-
outcome?:
|
|
643
|
+
outcome?: InvocationOutcomeInput,
|
|
570
644
|
arg4?: (() => boolean) | { code: string; message: string },
|
|
571
645
|
// Terminal evidence accepted by finalizeOutcome (exact-head review P1/P2):
|
|
572
646
|
// when an explicit outcome is absent, a content-bearing, activity-bearing,
|
|
@@ -809,25 +883,71 @@ export function createInvocationReconciliation(
|
|
|
809
883
|
// so a real lifecycle event is never swallowed.
|
|
810
884
|
}
|
|
811
885
|
const current = records.get(recordKey);
|
|
886
|
+
const incomingOutcome = frame.type === "agent_end" ? canonicalTerminalOutcome(frame.outcome) : undefined;
|
|
887
|
+
const providerFailure =
|
|
888
|
+
current?.error !== undefined && current.error.code !== "prompt_deadline_exceeded";
|
|
889
|
+
if (
|
|
890
|
+
current !== undefined &&
|
|
891
|
+
frame.type === "agent_end" &&
|
|
892
|
+
incomingOutcome?.kind === "failed" &&
|
|
893
|
+
incomingOutcome.provenance !== "deadline"
|
|
894
|
+
) {
|
|
895
|
+
const content = reduceTurnResultContent(current.content, frame.content);
|
|
896
|
+
const failed = {
|
|
897
|
+
...current,
|
|
898
|
+
revision: ++mutationRevision,
|
|
899
|
+
status: "failed" as const,
|
|
900
|
+
outcome: incomingOutcome,
|
|
901
|
+
error:
|
|
902
|
+
current.error?.code !== "prompt_deadline_exceeded" && current.error !== undefined
|
|
903
|
+
? current.error
|
|
904
|
+
: { code: incomingOutcome.code, message: incomingOutcome.message },
|
|
905
|
+
content,
|
|
906
|
+
receiptState: reduceReceiptState(
|
|
907
|
+
current.receiptState,
|
|
908
|
+
reportableTurnResultContent(content) ? "present" : undefined,
|
|
909
|
+
),
|
|
910
|
+
};
|
|
911
|
+
delete (failed as unknown as { pendingOutcome?: unknown }).pendingOutcome;
|
|
912
|
+
delete (failed as unknown as { pendingReceiptState?: unknown }).pendingReceiptState;
|
|
913
|
+
records.set(recordKey, failed);
|
|
914
|
+
try {
|
|
915
|
+
await persist();
|
|
916
|
+
} catch (error) {
|
|
917
|
+
if (records.get(recordKey) === failed) records.set(recordKey, current);
|
|
918
|
+
throw error;
|
|
919
|
+
}
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
812
922
|
if (
|
|
813
923
|
frame.type === "agent_end" &&
|
|
814
924
|
current === pending.finalizedRecord &&
|
|
925
|
+
incomingOutcome?.kind !== "failed" &&
|
|
926
|
+
!providerFailure &&
|
|
815
927
|
(kind !== "prompt" ||
|
|
816
|
-
|
|
928
|
+
incomingOutcome?.kind === "stopped" ||
|
|
817
929
|
frame.hasActivity === true ||
|
|
818
930
|
frame.content?.text.trim())
|
|
819
931
|
) {
|
|
820
932
|
// A real lifecycle end that arrived during deadline finalization is
|
|
821
933
|
// stronger evidence than the synthetic deadline outcome. Upgrade the
|
|
822
|
-
// durable terminal instead of treating
|
|
934
|
+
// durable terminal instead of treating it as a duplicate.
|
|
935
|
+
const content = reduceTurnResultContent(current.content, frame.content);
|
|
823
936
|
const upgraded = {
|
|
824
937
|
...current,
|
|
825
938
|
revision: ++mutationRevision,
|
|
826
939
|
status: "terminal_ok" as const,
|
|
940
|
+
content,
|
|
941
|
+
...(incomingOutcome?.kind === "stopped" ? { outcome: incomingOutcome } : {}),
|
|
942
|
+
receiptState: reduceReceiptState(
|
|
943
|
+
current.receiptState,
|
|
944
|
+
reportableTurnResultContent(content) ? "present" : undefined,
|
|
945
|
+
),
|
|
827
946
|
};
|
|
828
947
|
// Drop the superseded synthetic deadline error so the surfaced payload is
|
|
829
948
|
// not the contradictory {status: terminal_ok, error: prompt_deadline_exceeded}.
|
|
830
949
|
delete (upgraded as Partial<InvocationRecord>).error;
|
|
950
|
+
if (incomingOutcome?.kind !== "stopped") delete (upgraded as Partial<InvocationRecord>).outcome;
|
|
831
951
|
records.set(recordKey, upgraded);
|
|
832
952
|
try {
|
|
833
953
|
await persist();
|
|
@@ -851,18 +971,115 @@ export function createInvocationReconciliation(
|
|
|
851
971
|
}
|
|
852
972
|
}
|
|
853
973
|
if (record.terminalAt !== undefined) {
|
|
974
|
+
if (frame.type === "agent_end") {
|
|
975
|
+
const incomingOutcome = canonicalTerminalOutcome(frame.outcome);
|
|
976
|
+
if (incomingOutcome?.kind === "failed" && incomingOutcome.provenance !== "deadline") {
|
|
977
|
+
const content = reduceTurnResultContent(record.content, frame.content);
|
|
978
|
+
const failed = {
|
|
979
|
+
...record,
|
|
980
|
+
revision: ++mutationRevision,
|
|
981
|
+
status: "failed" as const,
|
|
982
|
+
outcome: incomingOutcome,
|
|
983
|
+
error:
|
|
984
|
+
record.error?.code !== "prompt_deadline_exceeded" && record.error !== undefined
|
|
985
|
+
? record.error
|
|
986
|
+
: { code: incomingOutcome.code, message: incomingOutcome.message },
|
|
987
|
+
content,
|
|
988
|
+
receiptState: reduceReceiptState(
|
|
989
|
+
record.receiptState,
|
|
990
|
+
reportableTurnResultContent(content) ? "present" : undefined,
|
|
991
|
+
),
|
|
992
|
+
};
|
|
993
|
+
delete (failed as unknown as { pendingOutcome?: unknown }).pendingOutcome;
|
|
994
|
+
delete (failed as unknown as { pendingReceiptState?: unknown }).pendingReceiptState;
|
|
995
|
+
records.set(recordKey, failed);
|
|
996
|
+
try {
|
|
997
|
+
await persist();
|
|
998
|
+
} catch (error) {
|
|
999
|
+
if (records.get(recordKey) === failed) records.set(recordKey, record);
|
|
1000
|
+
throw error;
|
|
1001
|
+
}
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
if (frame.type === "agent_failed" && record.error?.code === "prompt_deadline_exceeded") {
|
|
1006
|
+
const failure = sanitizePromptFailure(frame.error);
|
|
1007
|
+
const next = {
|
|
1008
|
+
...record,
|
|
1009
|
+
revision: ++mutationRevision,
|
|
1010
|
+
status: "failed" as const,
|
|
1011
|
+
error: failure,
|
|
1012
|
+
outcome: canonicalFailedOutcome(failure),
|
|
1013
|
+
};
|
|
1014
|
+
delete (next as unknown as { pendingOutcome?: unknown }).pendingOutcome;
|
|
1015
|
+
delete (next as unknown as { pendingReceiptState?: unknown }).pendingReceiptState;
|
|
1016
|
+
records.set(recordKey, next);
|
|
1017
|
+
try {
|
|
1018
|
+
await persist();
|
|
1019
|
+
} catch (error) {
|
|
1020
|
+
if (records.get(recordKey) === next) records.set(recordKey, record);
|
|
1021
|
+
throw error;
|
|
1022
|
+
}
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
if (frame.type === "agent_end" && record.error?.code !== "prompt_deadline_exceeded") {
|
|
1026
|
+
const content = reduceTurnResultContent(record.content, frame.content);
|
|
1027
|
+
const receiptState = reduceReceiptState(
|
|
1028
|
+
record.receiptState,
|
|
1029
|
+
reportableTurnResultContent(content) ? "present" : undefined,
|
|
1030
|
+
);
|
|
1031
|
+
let outcome = record.outcome;
|
|
1032
|
+
const incomingOutcome = canonicalTerminalOutcome(frame.outcome);
|
|
1033
|
+
if (outcome === undefined) {
|
|
1034
|
+
if (record.status === "failed" && record.error !== undefined)
|
|
1035
|
+
outcome = canonicalFailedOutcome(record.error);
|
|
1036
|
+
else if (record.status === "terminal_ok" && incomingOutcome?.kind === "stopped")
|
|
1037
|
+
outcome = incomingOutcome;
|
|
1038
|
+
}
|
|
1039
|
+
if (content !== record.content || receiptState !== record.receiptState || outcome !== record.outcome) {
|
|
1040
|
+
const enriched = {
|
|
1041
|
+
...record,
|
|
1042
|
+
revision: ++mutationRevision,
|
|
1043
|
+
content,
|
|
1044
|
+
receiptState,
|
|
1045
|
+
...(outcome === undefined ? {} : { outcome }),
|
|
1046
|
+
};
|
|
1047
|
+
records.set(recordKey, enriched);
|
|
1048
|
+
try {
|
|
1049
|
+
await persist();
|
|
1050
|
+
} catch (error) {
|
|
1051
|
+
if (records.get(recordKey) === enriched) records.set(recordKey, record);
|
|
1052
|
+
throw error;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
854
1057
|
if (
|
|
855
1058
|
frame.type === "agent_end" &&
|
|
856
1059
|
record.error?.code === "prompt_deadline_exceeded" &&
|
|
1060
|
+
canonicalTerminalOutcome(frame.outcome)?.kind !== "failed" &&
|
|
857
1061
|
(kind !== "prompt" ||
|
|
858
|
-
frame.outcome?.kind === "stopped" ||
|
|
1062
|
+
canonicalTerminalOutcome(frame.outcome)?.kind === "stopped" ||
|
|
859
1063
|
frame.hasActivity === true ||
|
|
860
1064
|
frame.content?.text.trim())
|
|
861
1065
|
) {
|
|
862
|
-
const
|
|
1066
|
+
const content = reduceTurnResultContent(record.content, frame.content);
|
|
1067
|
+
const incomingOutcome = canonicalTerminalOutcome(frame.outcome);
|
|
1068
|
+
const upgraded = {
|
|
1069
|
+
...record,
|
|
1070
|
+
revision: ++mutationRevision,
|
|
1071
|
+
status: "terminal_ok" as const,
|
|
1072
|
+
content,
|
|
1073
|
+
...(incomingOutcome?.kind === "stopped" ? { outcome: incomingOutcome } : {}),
|
|
1074
|
+
receiptState: reduceReceiptState(
|
|
1075
|
+
record.receiptState,
|
|
1076
|
+
reportableTurnResultContent(content) ? "present" : undefined,
|
|
1077
|
+
),
|
|
1078
|
+
};
|
|
863
1079
|
// The synthetic deadline error is superseded evidence; terminal_ok must not
|
|
864
1080
|
// surface a deadline failure for a prompt that actually completed.
|
|
865
1081
|
delete (upgraded as Partial<InvocationRecord>).error;
|
|
1082
|
+
if (incomingOutcome?.kind !== "stopped") delete (upgraded as Partial<InvocationRecord>).outcome;
|
|
866
1083
|
records.set(recordKey, upgraded);
|
|
867
1084
|
try {
|
|
868
1085
|
await persist();
|
|
@@ -919,19 +1136,43 @@ export function createInvocationReconciliation(
|
|
|
919
1136
|
if (next.error === undefined || (next.error.code === "agent_failed" && failure.code !== "agent_failed"))
|
|
920
1137
|
next.error = failure;
|
|
921
1138
|
} else {
|
|
1139
|
+
const pendingOutcome = (next as unknown as { pendingOutcome?: unknown }).pendingOutcome;
|
|
1140
|
+
delete (next as unknown as { pendingOutcome?: unknown }).pendingOutcome;
|
|
1141
|
+
delete (next as unknown as { pendingReceiptState?: unknown }).pendingReceiptState;
|
|
1142
|
+
next.content = reduceTurnResultContent(next.content, frame.content);
|
|
1143
|
+
const incomingOutcome = canonicalTerminalOutcome(frame.outcome) ?? canonicalTerminalOutcome(pendingOutcome);
|
|
922
1144
|
if (
|
|
1145
|
+
incomingOutcome?.kind === "stopped" &&
|
|
1146
|
+
(next.error === undefined || next.error.code === "prompt_deadline_exceeded")
|
|
1147
|
+
) {
|
|
1148
|
+
next.outcome = incomingOutcome;
|
|
1149
|
+
if (next.error?.code === "prompt_deadline_exceeded") delete next.error;
|
|
1150
|
+
next.status = "terminal_ok";
|
|
1151
|
+
} else if (incomingOutcome?.kind === "failed") {
|
|
1152
|
+
next.outcome = incomingOutcome;
|
|
1153
|
+
if (next.error === undefined)
|
|
1154
|
+
next.error = { code: incomingOutcome.code, message: incomingOutcome.message };
|
|
1155
|
+
next.status = "failed";
|
|
1156
|
+
} else if (next.error !== undefined) {
|
|
1157
|
+
next.outcome = canonicalFailedOutcome(next.error);
|
|
1158
|
+
next.status = "failed";
|
|
1159
|
+
} else if (
|
|
923
1160
|
kind === "prompt" &&
|
|
924
|
-
next.error === undefined &&
|
|
925
1161
|
frame.type === "agent_end" &&
|
|
926
|
-
frame.outcome?.kind !== "stopped" &&
|
|
927
1162
|
!frame.content?.text.trim() &&
|
|
928
1163
|
!frame.hasActivity
|
|
929
1164
|
) {
|
|
930
1165
|
next.status = "failed";
|
|
931
1166
|
next.error = EMPTY_PROMPT_FAILURE;
|
|
932
|
-
|
|
1167
|
+
next.outcome = canonicalFailedOutcome(EMPTY_PROMPT_FAILURE);
|
|
1168
|
+
} else next.status = "terminal_ok";
|
|
1169
|
+
next.receiptState = reduceReceiptState(
|
|
1170
|
+
next.receiptState,
|
|
1171
|
+
reportableTurnResultContent(next.content) ? "present" : "missing",
|
|
1172
|
+
);
|
|
933
1173
|
next.terminalAt = Date.now();
|
|
934
1174
|
}
|
|
1175
|
+
|
|
935
1176
|
records.set(recordKey, next);
|
|
936
1177
|
try {
|
|
937
1178
|
await persist();
|
|
@@ -942,20 +1183,24 @@ export function createInvocationReconciliation(
|
|
|
942
1183
|
},
|
|
943
1184
|
lookup(kind, selector) {
|
|
944
1185
|
const record = find(kind, selector);
|
|
945
|
-
if (!record) return { status: "unknown" };
|
|
1186
|
+
if (!record) return { status: "unknown", receiptState: "unknown" };
|
|
946
1187
|
const identity = {
|
|
947
1188
|
commandId: record.commandId,
|
|
948
1189
|
turnId: record.turnId,
|
|
949
1190
|
...(record.clientRef === undefined ? {} : { clientRef: record.clientRef }),
|
|
950
1191
|
acceptedAt: record.acceptedAt,
|
|
951
1192
|
};
|
|
952
|
-
if (record.status === "accepted") return { status: "accepted", ...identity };
|
|
953
|
-
if (record.status === "in_flight")
|
|
1193
|
+
if (record.status === "accepted") return { status: "accepted", receiptState: "absent", ...identity };
|
|
1194
|
+
if (record.status === "in_flight")
|
|
1195
|
+
return { status: "in_flight", receiptState: "absent", ...identity, startedAt: record.startedAt };
|
|
954
1196
|
return {
|
|
955
1197
|
status: record.status,
|
|
956
1198
|
...identity,
|
|
957
1199
|
...(record.startedAt === undefined ? {} : { startedAt: record.startedAt }),
|
|
958
1200
|
terminalAt: record.terminalAt,
|
|
1201
|
+
receiptState: record.receiptState ?? "unknown",
|
|
1202
|
+
...(record.outcome === undefined ? {} : { outcome: record.outcome }),
|
|
1203
|
+
...(reportableTurnResultContent(record.content) ? { content: record.content } : {}),
|
|
959
1204
|
...(record.error === undefined ? {} : { error: record.error }),
|
|
960
1205
|
};
|
|
961
1206
|
},
|
|
@@ -982,11 +1227,13 @@ export function createInvocationReconciliation(
|
|
|
982
1227
|
hydrate,
|
|
983
1228
|
async claimPendingOutcome(kind, correlation, outcome) {
|
|
984
1229
|
const record = records.get(key(kind, correlation));
|
|
985
|
-
|
|
1230
|
+
const normalized = canonicalTerminalOutcome(outcome);
|
|
1231
|
+
if (normalized === undefined) throw new Error("Invalid terminal outcome.");
|
|
1232
|
+
if (!record || record.terminalAt !== undefined || record.kind !== kind) return normalized;
|
|
986
1233
|
const pending = (record as unknown as { pendingOutcome?: unknown }).pendingOutcome;
|
|
987
|
-
if (pending !== undefined) return pending;
|
|
1234
|
+
if (pending !== undefined) return canonicalTerminalOutcome(pending) ?? normalized;
|
|
988
1235
|
const next = { ...record, revision: ++mutationRevision } as InvocationRecord & { pendingOutcome?: unknown };
|
|
989
|
-
next.pendingOutcome =
|
|
1236
|
+
next.pendingOutcome = normalized;
|
|
990
1237
|
records.set(key(kind, correlation), next);
|
|
991
1238
|
try {
|
|
992
1239
|
await persist();
|
|
@@ -994,7 +1241,7 @@ export function createInvocationReconciliation(
|
|
|
994
1241
|
if (records.get(key(kind, correlation)) === next) records.set(key(kind, correlation), record);
|
|
995
1242
|
throw error;
|
|
996
1243
|
}
|
|
997
|
-
return
|
|
1244
|
+
return normalized;
|
|
998
1245
|
},
|
|
999
1246
|
async finalizeOutcome(
|
|
1000
1247
|
kind,
|
|
@@ -1026,36 +1273,64 @@ export function createInvocationReconciliation(
|
|
|
1026
1273
|
const recordKey = key(kind, correlation);
|
|
1027
1274
|
const record = records.get(recordKey);
|
|
1028
1275
|
if (!record || record.terminalAt !== undefined || record.kind !== kind) return;
|
|
1029
|
-
const
|
|
1030
|
-
(record as unknown as { pendingOutcome?:
|
|
1031
|
-
|
|
1276
|
+
const requestedOutcome = canonicalTerminalOutcome(
|
|
1277
|
+
outcome ?? (record as unknown as { pendingOutcome?: unknown }).pendingOutcome,
|
|
1278
|
+
);
|
|
1279
|
+
const priorFailure = record.error;
|
|
1280
|
+
// A provider diagnostic recorded before a deadline finalization is stronger
|
|
1281
|
+
// than the synthetic deadline claim. Preserve it as the terminal failure
|
|
1282
|
+
// intent instead of allowing a later agent_end to look successful.
|
|
1283
|
+
const finalOutcome =
|
|
1284
|
+
priorFailure !== undefined && priorFailure.code !== "prompt_deadline_exceeded"
|
|
1285
|
+
? canonicalFailedOutcome(priorFailure)
|
|
1286
|
+
: requestedOutcome?.kind === "stopped"
|
|
1287
|
+
? requestedOutcome
|
|
1288
|
+
: priorFailure !== undefined &&
|
|
1289
|
+
(requestedOutcome === undefined ||
|
|
1290
|
+
(requestedOutcome.kind === "failed" &&
|
|
1291
|
+
(requestedOutcome.code === "prompt_deadline_exceeded" ||
|
|
1292
|
+
priorFailure.code !== "prompt_deadline_exceeded")))
|
|
1293
|
+
? canonicalFailedOutcome(priorFailure)
|
|
1294
|
+
: requestedOutcome;
|
|
1032
1295
|
const previousRecord = { ...record };
|
|
1033
1296
|
const finalizedRecord: InvocationRecord = { ...record, revision: ++mutationRevision, terminalAt: Date.now() };
|
|
1034
|
-
|
|
1297
|
+
finalizedRecord.content = reduceTurnResultContent(finalizedRecord.content, evidence?.content);
|
|
1298
|
+
let resolvedOutcome = finalOutcome;
|
|
1299
|
+
if (resolvedOutcome?.kind === "stopped") {
|
|
1300
|
+
// Stopped is a successful terminal boundary: it must never coexist with
|
|
1301
|
+
// an earlier diagnostic failure.
|
|
1302
|
+
finalizedRecord.status = "terminal_ok";
|
|
1303
|
+
delete finalizedRecord.error;
|
|
1304
|
+
} else if (resolvedOutcome?.kind === "failed") {
|
|
1035
1305
|
finalizedRecord.status = "failed";
|
|
1036
|
-
// Legacy positional recordError overrides the recorded cause; the
|
|
1037
|
-
// outcome's own code/message is the default (exact-head review P1).
|
|
1038
1306
|
finalizedRecord.error =
|
|
1039
1307
|
recordError !== undefined
|
|
1040
1308
|
? { code: recordError.code, message: recordError.message }
|
|
1041
|
-
:
|
|
1042
|
-
|
|
1309
|
+
: priorFailure !== undefined &&
|
|
1310
|
+
(resolvedOutcome.code === "prompt_deadline_exceeded" ||
|
|
1311
|
+
priorFailure.code !== "prompt_deadline_exceeded")
|
|
1312
|
+
? priorFailure
|
|
1313
|
+
: { code: resolvedOutcome.code, message: resolvedOutcome.message };
|
|
1314
|
+
} else if (kind === "prompt") {
|
|
1043
1315
|
// Evidence-based empty predicate (exact-head review P1/P2): the same
|
|
1044
1316
|
// semantics as the noteTransition agent_end classifier. A
|
|
1045
|
-
// content-bearing
|
|
1046
|
-
// finalization without an explicit outcome is a successful terminal;
|
|
1317
|
+
// content-bearing or activity-bearing finalization is successful;
|
|
1047
1318
|
// only a genuinely empty, no-activity completion fails closed.
|
|
1048
1319
|
const terminalText = evidence?.content?.text.trim() ?? "";
|
|
1049
1320
|
if (terminalText === "" && !evidence?.hasActivity && evidence?.outcomeKind !== "stopped") {
|
|
1321
|
+
resolvedOutcome = canonicalFailedOutcome(EMPTY_PROMPT_FAILURE);
|
|
1050
1322
|
finalizedRecord.status = "failed";
|
|
1051
1323
|
finalizedRecord.error = EMPTY_PROMPT_FAILURE;
|
|
1052
|
-
} else
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
finalizedRecord.
|
|
1057
|
-
|
|
1058
|
-
(
|
|
1324
|
+
} else finalizedRecord.status = "terminal_ok";
|
|
1325
|
+
} else finalizedRecord.status = "terminal_ok";
|
|
1326
|
+
finalizedRecord.receiptState = reduceReceiptState(
|
|
1327
|
+
finalizedRecord.receiptState,
|
|
1328
|
+
reportableTurnResultContent(finalizedRecord.content) ? "present" : "unknown",
|
|
1329
|
+
);
|
|
1330
|
+
if (resolvedOutcome === undefined) delete finalizedRecord.outcome;
|
|
1331
|
+
else finalizedRecord.outcome = resolvedOutcome;
|
|
1332
|
+
delete (finalizedRecord as unknown as Record<string, unknown>).pendingOutcome;
|
|
1333
|
+
delete (finalizedRecord as unknown as Record<string, unknown>).pendingReceiptState;
|
|
1059
1334
|
if (isCurrent !== undefined && !isCurrent()) return;
|
|
1060
1335
|
const commit = Promise.withResolvers<void>();
|
|
1061
1336
|
// This promise is an internal coordination signal for lifecycle transitions
|
|
@@ -1105,8 +1380,13 @@ export function createInvocationReconciliation(
|
|
|
1105
1380
|
};
|
|
1106
1381
|
if (isCurrent !== undefined && !isCurrent()) return;
|
|
1107
1382
|
delete next.terminalAt;
|
|
1108
|
-
|
|
1109
|
-
|
|
1383
|
+
if (next.error?.code === "prompt_deadline_exceeded" || !isPreservedProviderFailure(next.error?.code))
|
|
1384
|
+
delete next.error;
|
|
1385
|
+
delete next.receiptState;
|
|
1386
|
+
delete (next as unknown as { outcome?: unknown; pendingOutcome?: unknown; pendingReceiptState?: unknown })
|
|
1387
|
+
.outcome;
|
|
1388
|
+
delete (next as unknown as { pendingOutcome?: unknown }).pendingOutcome;
|
|
1389
|
+
delete (next as unknown as { pendingReceiptState?: unknown }).pendingReceiptState;
|
|
1110
1390
|
(next as unknown as { deadlineRecoveryPending?: boolean }).deadlineRecoveryPending = true;
|
|
1111
1391
|
if (deadlineMaxAt !== undefined) (next as unknown as { deadlineMaxAt?: number }).deadlineMaxAt = deadlineMaxAt;
|
|
1112
1392
|
records.set(recordKey, next);
|
|
@@ -1145,6 +1425,7 @@ export interface SdkSurfaceFactoryOptions {
|
|
|
1145
1425
|
}) => unknown;
|
|
1146
1426
|
steerStatusLookup?: (selector: { commandId?: string; turnId?: string; clientRef?: string }) => unknown;
|
|
1147
1427
|
hostTools?: boolean | (() => boolean);
|
|
1428
|
+
getRuntimeHost?: () => SessionSdkHost | undefined;
|
|
1148
1429
|
}
|
|
1149
1430
|
|
|
1150
1431
|
/** Shared policy, capability, and query-surface factory for every SDK transport. */
|
|
@@ -1174,6 +1455,7 @@ function createQuerySurface(
|
|
|
1174
1455
|
}) => unknown;
|
|
1175
1456
|
steerStatusLookup?: (selector: { commandId?: string; turnId?: string; clientRef?: string }) => unknown;
|
|
1176
1457
|
hostTools?: boolean | (() => boolean);
|
|
1458
|
+
getRuntimeHost?: () => SessionSdkHost | undefined;
|
|
1177
1459
|
} = {},
|
|
1178
1460
|
): SessionSurface {
|
|
1179
1461
|
const policy =
|
|
@@ -1577,6 +1859,28 @@ function createQuerySurface(
|
|
|
1577
1859
|
turnId?: string;
|
|
1578
1860
|
clientRef?: string;
|
|
1579
1861
|
}) => (options.turnResultLookup ?? (value => reconciliation.lookupResult(value.kind, value)))(selector),
|
|
1862
|
+
getCheckpointSnapshot: () => {
|
|
1863
|
+
const host = options.getRuntimeHost?.();
|
|
1864
|
+
if (!host)
|
|
1865
|
+
throw Object.assign(new Error("Atomic session checkpoint capture is unavailable."), {
|
|
1866
|
+
code: "unavailable",
|
|
1867
|
+
});
|
|
1868
|
+
const entries =
|
|
1869
|
+
typeof (ctx as Partial<ExtensionContext>).getTranscript === "function" ? ctx.getTranscript() : undefined;
|
|
1870
|
+
if (!Array.isArray(entries))
|
|
1871
|
+
throw Object.assign(new Error("Atomic session checkpoint capture is unavailable."), {
|
|
1872
|
+
code: "unavailable",
|
|
1873
|
+
});
|
|
1874
|
+
return {
|
|
1875
|
+
entries,
|
|
1876
|
+
watermark: {
|
|
1877
|
+
revision: entries.length,
|
|
1878
|
+
generation: host.events.generation,
|
|
1879
|
+
seq: host.events.sequence,
|
|
1880
|
+
idle: ctx.isIdle(),
|
|
1881
|
+
},
|
|
1882
|
+
};
|
|
1883
|
+
},
|
|
1580
1884
|
getSteerStatus: (selector: { commandId?: string; turnId?: string; clientRef?: string }) =>
|
|
1581
1885
|
(options.steerStatusLookup ?? (value => reconciliation.lookup("steer", value)))(selector),
|
|
1582
1886
|
getModelProfiles: async () => {
|
|
@@ -1626,6 +1930,7 @@ export function createSdkSurfaceFactory(
|
|
|
1626
1930
|
turnResultLookup: options.turnResultLookup,
|
|
1627
1931
|
steerStatusLookup: options.steerStatusLookup,
|
|
1628
1932
|
hostTools: options.hostTools,
|
|
1933
|
+
getRuntimeHost: options.getRuntimeHost,
|
|
1629
1934
|
});
|
|
1630
1935
|
return {
|
|
1631
1936
|
policy,
|
|
@@ -1933,6 +2238,7 @@ function createControlSurface(
|
|
|
1933
2238
|
) => void,
|
|
1934
2239
|
canResolveGate: () => boolean = () => true,
|
|
1935
2240
|
trackGateResolution: <T>(resolution: Promise<T>) => Promise<T> = async resolution => await resolution,
|
|
2241
|
+
onInvocationCompletionReconciledForTests?: (kind: InvocationKind, correlation: InvocationCorrelation) => void,
|
|
1936
2242
|
): ControlSurface {
|
|
1937
2243
|
const normalizePromptImages = (value: unknown): ImageContent[] => {
|
|
1938
2244
|
if (!Array.isArray(value)) return [];
|
|
@@ -2180,18 +2486,9 @@ function createControlSurface(
|
|
|
2180
2486
|
try {
|
|
2181
2487
|
await reconciliation.noteTransition(kind, correlation, {
|
|
2182
2488
|
type: "agent_end",
|
|
2183
|
-
...(typeof result === "string"
|
|
2184
|
-
? {
|
|
2185
|
-
content: {
|
|
2186
|
-
version: 1,
|
|
2187
|
-
type: "text",
|
|
2188
|
-
text: result,
|
|
2189
|
-
byteLength: 0,
|
|
2190
|
-
truncated: false,
|
|
2191
|
-
},
|
|
2192
|
-
}
|
|
2193
|
-
: {}),
|
|
2489
|
+
...(typeof result === "string" ? { content: sanitizeTurnResultContent(result) } : {}),
|
|
2194
2490
|
});
|
|
2491
|
+
onInvocationCompletionReconciledForTests?.(kind, correlation);
|
|
2195
2492
|
retirePendingOwner?.(kind, correlation, "always");
|
|
2196
2493
|
} catch (transitionError) {
|
|
2197
2494
|
if (kind === "prompt" || kind === "skill") {
|
|
@@ -3844,7 +4141,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
3844
4141
|
lifecycleOwner?: LifecycleOwner,
|
|
3845
4142
|
terminalContent?: TurnResultContent,
|
|
3846
4143
|
terminalHasActivity = false,
|
|
3847
|
-
terminalOutcome?:
|
|
4144
|
+
terminalOutcome?: InvocationOutcome,
|
|
3848
4145
|
): Promise<void> => {
|
|
3849
4146
|
const current = lifecycleOwner?.state ?? active;
|
|
3850
4147
|
if (!current) return;
|
|
@@ -4109,7 +4406,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
4109
4406
|
? ({
|
|
4110
4407
|
content: terminalContent,
|
|
4111
4408
|
hasActivity: terminalHasActivity,
|
|
4112
|
-
outcome: terminalOutcome,
|
|
4409
|
+
...(terminalOutcome?.kind === "stopped" ? { outcome: terminalOutcome } : {}),
|
|
4113
4410
|
} satisfies PromptTerminalTransitionEvidence)
|
|
4114
4411
|
: undefined,
|
|
4115
4412
|
);
|
|
@@ -4313,7 +4610,9 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
4313
4610
|
const terminalOutcome =
|
|
4314
4611
|
event.stopReason === "cancelled"
|
|
4315
4612
|
? ({ kind: "stopped", reason: "cancelled", provenance: "client_cancel" } as const)
|
|
4316
|
-
: undefined
|
|
4613
|
+
: failure === undefined
|
|
4614
|
+
? undefined
|
|
4615
|
+
: canonicalFailedOutcome(failure);
|
|
4317
4616
|
return trackLifecycle(async () => {
|
|
4318
4617
|
if (failure && !failureAlreadyPublished) {
|
|
4319
4618
|
for (const key of genericFailureKeys) owner?.failureDiagnosticKeys.delete(key);
|
|
@@ -4502,6 +4801,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
4502
4801
|
});
|
|
4503
4802
|
await Promise.race([settled, timeout]);
|
|
4504
4803
|
};
|
|
4804
|
+
let runtime: SessionSdkSessionRuntime;
|
|
4505
4805
|
const surfaceFactory = createSdkSurfaceFactory({
|
|
4506
4806
|
ctx,
|
|
4507
4807
|
id: sessionId,
|
|
@@ -4511,6 +4811,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
4511
4811
|
steerStatusLookup: selector => steerReconciliation.lookupSteer(selector),
|
|
4512
4812
|
configOverrides: options.configOverrides,
|
|
4513
4813
|
settings: options.settings,
|
|
4814
|
+
getRuntimeHost: () => runtime?.host,
|
|
4514
4815
|
});
|
|
4515
4816
|
const queryHandlers = new QueryHandlers(surfaceFactory.query, sessionId, revisions, cursors);
|
|
4516
4817
|
const inputGate = { quiescing: false };
|
|
@@ -4567,7 +4868,6 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
4567
4868
|
skillRecoveryControllers.delete(key);
|
|
4568
4869
|
});
|
|
4569
4870
|
};
|
|
4570
|
-
let runtime: SessionSdkSessionRuntime;
|
|
4571
4871
|
// Durable-first bounded terminalization for accepted submissions that leave
|
|
4572
4872
|
// their queue or race a run WITHOUT consumption (exact-head review: clearing
|
|
4573
4873
|
// the lease before the durable writes strands the row accepted with no
|
|
@@ -4856,6 +5156,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
4856
5156
|
},
|
|
4857
5157
|
() => acceptingGateResolutions,
|
|
4858
5158
|
trackGateResolution,
|
|
5159
|
+
options.onInvocationCompletionReconciledForTests,
|
|
4859
5160
|
);
|
|
4860
5161
|
const installProviderDefinitions = (capability: string, definitions: unknown): void => {
|
|
4861
5162
|
if (capability === "permission") {
|
|
@@ -5034,9 +5335,37 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
5034
5335
|
},
|
|
5035
5336
|
});
|
|
5036
5337
|
if (isAutoroutingInactive(api)) markAutoroutingInactive(runtime.host);
|
|
5037
|
-
const
|
|
5338
|
+
const disposeWorkflowGate = ctx.workflowGate?.onGateEmitted?.(gate =>
|
|
5038
5339
|
runtime.emitEvent({ kind: "workflow_gate", payload: gate }),
|
|
5039
5340
|
);
|
|
5341
|
+
// Every fold (steer, chord, auto-background timer, `bash.background`)
|
|
5342
|
+
// publishes one replayable `bash_folded` frame; `runtime.jobs.list`
|
|
5343
|
+
// `foldReason` remains the source of truth clients reconcile against after
|
|
5344
|
+
// a replay, since a replayed frame may describe a job that has since ended.
|
|
5345
|
+
const disposeFold = ctx.onJobFold?.(event => {
|
|
5346
|
+
try {
|
|
5347
|
+
runtime.emitEvent({
|
|
5348
|
+
type: "bash_folded",
|
|
5349
|
+
sessionId,
|
|
5350
|
+
jobId: event.jobId,
|
|
5351
|
+
generation: event.generation,
|
|
5352
|
+
reason: event.reason,
|
|
5353
|
+
});
|
|
5354
|
+
} catch (error) {
|
|
5355
|
+
// The fold already happened; publication failure must not affect it,
|
|
5356
|
+
// but it must leave evidence because a client relying on the event
|
|
5357
|
+
// instead of runtime.jobs.list would otherwise never learn of the fold.
|
|
5358
|
+
logger.warn("sdk: bash_folded publication failed", {
|
|
5359
|
+
jobId: event.jobId,
|
|
5360
|
+
reason: event.reason,
|
|
5361
|
+
error: error instanceof Error ? error.message : String(error),
|
|
5362
|
+
});
|
|
5363
|
+
}
|
|
5364
|
+
});
|
|
5365
|
+
const disposeGate = (): void => {
|
|
5366
|
+
disposeWorkflowGate?.();
|
|
5367
|
+
disposeFold?.();
|
|
5368
|
+
};
|
|
5040
5369
|
let publishedEndpointUrl: string | undefined;
|
|
5041
5370
|
let brokerRegistered = false;
|
|
5042
5371
|
const registerBroker = async (): Promise<void> => {
|