@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
|
@@ -5,16 +5,30 @@ import * as fs from "node:fs/promises";
|
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
7
|
import { ThinkingLevel } from "@gajae-code/agent-core";
|
|
8
|
-
import type { Api, Model } from "@gajae-code/ai/core";
|
|
8
|
+
import type { Api, ImageContent, Model } from "@gajae-code/ai/core";
|
|
9
9
|
import { logger } from "@gajae-code/utils";
|
|
10
10
|
import { AsyncJobManager } from "../../async";
|
|
11
|
+
import {
|
|
12
|
+
getProxyRoutableProviders,
|
|
13
|
+
inspectProxyProviderId,
|
|
14
|
+
requiresQualifiedModelProfileRoleResolution,
|
|
15
|
+
resolveProxyMode,
|
|
16
|
+
rewriteSelectorForProxy,
|
|
17
|
+
tryResolveProxyProviderId,
|
|
18
|
+
} from "../../config/model-profile-activation";
|
|
11
19
|
import { isModelProfileProviderAvailable, projectModelProfileCatalog } from "../../config/model-profile-contract";
|
|
12
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
deriveModelProfileMappedProviders,
|
|
22
|
+
type ModelProfileDefinition,
|
|
23
|
+
resolveProfileBindings,
|
|
24
|
+
} from "../../config/model-profiles";
|
|
25
|
+
import { isAuthenticated, kNoAuth } from "../../config/model-registry";
|
|
13
26
|
import { resolveModelChainWithAuth, splitSelectorThinkingSuffix } from "../../config/model-resolver";
|
|
14
27
|
import { type ModelSelectorValue, normalizeModelSelectorValue } from "../../config/model-selector-value";
|
|
15
28
|
import { type Settings, validateSettingPatch } from "../../config/settings";
|
|
16
29
|
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "../../extensibility/extensions";
|
|
17
30
|
import { normalizeGoal } from "../../goals/state";
|
|
31
|
+
import type { SdkRunCapability } from "../../session/sdk-run-capability";
|
|
18
32
|
import {
|
|
19
33
|
boundTerminalRetentionState,
|
|
20
34
|
findOwnedRegistrationsForTurn,
|
|
@@ -26,7 +40,6 @@ import { parseThinkingLevel } from "../../thinking";
|
|
|
26
40
|
import { ensureBroker } from "../broker/ensure";
|
|
27
41
|
import { SessionIndex } from "../broker/session-index";
|
|
28
42
|
import {
|
|
29
|
-
collectAuthenticatedProfileProviders,
|
|
30
43
|
parseSyntheticModelId,
|
|
31
44
|
resolveSyntheticModelSelection,
|
|
32
45
|
SYNTHETIC_PROVIDER_ID,
|
|
@@ -34,8 +47,9 @@ import {
|
|
|
34
47
|
syntheticNamespaceCollision,
|
|
35
48
|
} from "../model-profile-model";
|
|
36
49
|
import { projectQ10Models } from "../models.js";
|
|
37
|
-
import { PromptDeadlineManager } from "../prompt-deadline-manager";
|
|
50
|
+
import { PromptDeadlineManager, type PromptTerminalTransitionEvidence } from "../prompt-deadline-manager";
|
|
38
51
|
import { formatPromptFailureForLocalLog, sanitizePromptFailure } from "../prompt-failure";
|
|
52
|
+
import { validateRequiredPromptText } from "../protocol/adapter-validation";
|
|
39
53
|
import { OPERATIONS } from "../protocol/operation-registry";
|
|
40
54
|
import {
|
|
41
55
|
createKindAwareReconciliation,
|
|
@@ -43,11 +57,13 @@ import {
|
|
|
43
57
|
type KindAwareReconciliation,
|
|
44
58
|
resolveReconciliationSessionFile,
|
|
45
59
|
} from "../reconciliation-extensions";
|
|
60
|
+
import { sanitizeTurnResultContent, type TurnResultContent } from "../turn-result";
|
|
46
61
|
import { type ControlSurface, controlRequestFromFrame, dispatchControl } from "./control";
|
|
47
62
|
import { BROKER_RUNTIME_CLOSE_CAPABILITY_FIELD } from "./control/runtime-gate";
|
|
48
63
|
import { SessionSdkHost, type SessionSdkHostOptions } from "./host";
|
|
49
64
|
import { clearAutoroutingInactive, isAutoroutingInactive, markAutoroutingInactive } from "./internal-autorouting-state";
|
|
50
65
|
import { CursorRegistry, QueryHandlers, RevisionStore, type SessionSurface } from "./query";
|
|
66
|
+
import { createSdkRunCapability } from "./sdk-run-capability";
|
|
51
67
|
import {
|
|
52
68
|
createSdkCapabilities,
|
|
53
69
|
createSdkSurfacePolicyForContext,
|
|
@@ -420,6 +436,7 @@ export interface InvocationCorrelation {
|
|
|
420
436
|
|
|
421
437
|
export type InvocationKind = "prompt" | "skill" | "steer";
|
|
422
438
|
type InvocationStatus = "accepted" | "in_flight" | "terminal_ok" | "failed" | "uncertain";
|
|
439
|
+
const EMPTY_PROMPT_FAILURE = { code: "prompt_failed", message: "Prompt submission failed." } as const;
|
|
423
440
|
interface InvocationRecord extends InvocationCorrelation {
|
|
424
441
|
kind: InvocationKind;
|
|
425
442
|
revision: number;
|
|
@@ -440,7 +457,14 @@ export interface InvocationReconciliation {
|
|
|
440
457
|
noteTransition(
|
|
441
458
|
kind: InvocationKind,
|
|
442
459
|
correlation: InvocationCorrelation | undefined,
|
|
443
|
-
frame:
|
|
460
|
+
frame:
|
|
461
|
+
| {
|
|
462
|
+
type: "agent_start" | "agent_end";
|
|
463
|
+
content?: TurnResultContent;
|
|
464
|
+
hasActivity?: boolean;
|
|
465
|
+
outcome?: { kind: "stopped"; reason: "cancelled"; provenance: "client_cancel" };
|
|
466
|
+
}
|
|
467
|
+
| { type: "agent_failed"; error: unknown; content?: TurnResultContent; hasActivity?: boolean },
|
|
444
468
|
): Promise<void>;
|
|
445
469
|
lookup(kind: InvocationKind, selector: { commandId?: string; turnId?: string; clientRef?: string }): unknown;
|
|
446
470
|
lookupResult(kind: InvocationKind, selector: { commandId?: string; turnId?: string; clientRef?: string }): unknown;
|
|
@@ -464,7 +488,13 @@ export interface InvocationReconciliation {
|
|
|
464
488
|
correlation: InvocationCorrelation,
|
|
465
489
|
outcome?: { kind: string; code: string; message: string; provenance?: string },
|
|
466
490
|
arg4?: (() => boolean) | { code: string; message: string },
|
|
491
|
+
// Terminal evidence accepted by finalizeOutcome (exact-head review P1/P2):
|
|
492
|
+
// when an explicit outcome is absent, a content-bearing, activity-bearing,
|
|
493
|
+
// or explicitly stopped finalization is still a successful terminal; only a
|
|
494
|
+
// genuinely empty, no-activity completion fails closed with the sanitized
|
|
495
|
+
// empty-prompt failure. Mirrors the noteTransition evidence predicate.
|
|
467
496
|
arg5?: unknown,
|
|
497
|
+
arg6?: { content?: TurnResultContent; hasActivity?: boolean; outcomeKind?: string },
|
|
468
498
|
): Promise<void>;
|
|
469
499
|
markUncertain(
|
|
470
500
|
kind: InvocationKind,
|
|
@@ -699,7 +729,14 @@ export function createInvocationReconciliation(
|
|
|
699
729
|
// so a real lifecycle event is never swallowed.
|
|
700
730
|
}
|
|
701
731
|
const current = records.get(recordKey);
|
|
702
|
-
if (
|
|
732
|
+
if (
|
|
733
|
+
frame.type === "agent_end" &&
|
|
734
|
+
current === pending.finalizedRecord &&
|
|
735
|
+
(kind !== "prompt" ||
|
|
736
|
+
frame.outcome?.kind === "stopped" ||
|
|
737
|
+
frame.hasActivity === true ||
|
|
738
|
+
frame.content?.text.trim())
|
|
739
|
+
) {
|
|
703
740
|
// A real lifecycle end that arrived during deadline finalization is
|
|
704
741
|
// stronger evidence than the synthetic deadline outcome. Upgrade the
|
|
705
742
|
// durable terminal instead of treating the event as a duplicate.
|
|
@@ -734,7 +771,14 @@ export function createInvocationReconciliation(
|
|
|
734
771
|
}
|
|
735
772
|
}
|
|
736
773
|
if (record.terminalAt !== undefined) {
|
|
737
|
-
if (
|
|
774
|
+
if (
|
|
775
|
+
frame.type === "agent_end" &&
|
|
776
|
+
record.error?.code === "prompt_deadline_exceeded" &&
|
|
777
|
+
(kind !== "prompt" ||
|
|
778
|
+
frame.outcome?.kind === "stopped" ||
|
|
779
|
+
frame.hasActivity === true ||
|
|
780
|
+
frame.content?.text.trim())
|
|
781
|
+
) {
|
|
738
782
|
const upgraded = { ...record, revision: ++mutationRevision, status: "terminal_ok" as const };
|
|
739
783
|
// The synthetic deadline error is superseded evidence; terminal_ok must not
|
|
740
784
|
// surface a deadline failure for a prompt that actually completed.
|
|
@@ -795,7 +839,17 @@ export function createInvocationReconciliation(
|
|
|
795
839
|
if (next.error === undefined || (next.error.code === "agent_failed" && failure.code !== "agent_failed"))
|
|
796
840
|
next.error = failure;
|
|
797
841
|
} else {
|
|
798
|
-
|
|
842
|
+
if (
|
|
843
|
+
kind === "prompt" &&
|
|
844
|
+
next.error === undefined &&
|
|
845
|
+
frame.type === "agent_end" &&
|
|
846
|
+
frame.outcome?.kind !== "stopped" &&
|
|
847
|
+
!frame.content?.text.trim() &&
|
|
848
|
+
!frame.hasActivity
|
|
849
|
+
) {
|
|
850
|
+
next.status = "failed";
|
|
851
|
+
next.error = EMPTY_PROMPT_FAILURE;
|
|
852
|
+
} else next.status = next.error === undefined ? "terminal_ok" : "failed";
|
|
799
853
|
next.terminalAt = Date.now();
|
|
800
854
|
}
|
|
801
855
|
records.set(recordKey, next);
|
|
@@ -868,6 +922,7 @@ export function createInvocationReconciliation(
|
|
|
868
922
|
outcome,
|
|
869
923
|
arg4?: (() => boolean) | { code: string; message: string },
|
|
870
924
|
arg5?: unknown,
|
|
925
|
+
arg6?: { content?: TurnResultContent; hasActivity?: boolean; outcomeKind?: string },
|
|
871
926
|
) {
|
|
872
927
|
// Normalize every supported positional form (exact-head review P1):
|
|
873
928
|
// (…, isCurrent) | (…, recordError) | (…, undefined, recordError) |
|
|
@@ -880,6 +935,14 @@ export function createInvocationReconciliation(
|
|
|
880
935
|
? (arg5 as { code: string; message: string })
|
|
881
936
|
: undefined;
|
|
882
937
|
const recordError = recordErrorCandidate;
|
|
938
|
+
// Terminal evidence may also arrive positionally through arg5 (legacy
|
|
939
|
+
// callers) when it does not carry a recordError shape; normalize it so
|
|
940
|
+
// the empty predicate below sees every evidence channel consistently.
|
|
941
|
+
const legacyEvidenceCandidate =
|
|
942
|
+
typeof arg5 === "object" && arg5 !== null && !("code" in arg5)
|
|
943
|
+
? (arg5 as { content?: TurnResultContent; hasActivity?: boolean; outcomeKind?: string })
|
|
944
|
+
: undefined;
|
|
945
|
+
const evidence = arg6 ?? legacyEvidenceCandidate;
|
|
883
946
|
const recordKey = key(kind, correlation);
|
|
884
947
|
const record = records.get(recordKey);
|
|
885
948
|
if (!record || record.terminalAt !== undefined || record.kind !== kind) return;
|
|
@@ -896,6 +959,19 @@ export function createInvocationReconciliation(
|
|
|
896
959
|
recordError !== undefined
|
|
897
960
|
? { code: recordError.code, message: recordError.message }
|
|
898
961
|
: { code: finalOutcome.code, message: finalOutcome.message };
|
|
962
|
+
} else if (kind === "prompt" && finalOutcome === undefined) {
|
|
963
|
+
// Evidence-based empty predicate (exact-head review P1/P2): the same
|
|
964
|
+
// semantics as the noteTransition agent_end classifier. A
|
|
965
|
+
// content-bearing, activity-bearing, or explicitly stopped
|
|
966
|
+
// finalization without an explicit outcome is a successful terminal;
|
|
967
|
+
// only a genuinely empty, no-activity completion fails closed.
|
|
968
|
+
const terminalText = evidence?.content?.text.trim() ?? "";
|
|
969
|
+
if (terminalText === "" && !evidence?.hasActivity && evidence?.outcomeKind !== "stopped") {
|
|
970
|
+
finalizedRecord.status = "failed";
|
|
971
|
+
finalizedRecord.error = EMPTY_PROMPT_FAILURE;
|
|
972
|
+
} else {
|
|
973
|
+
finalizedRecord.status = "terminal_ok";
|
|
974
|
+
}
|
|
899
975
|
} else {
|
|
900
976
|
finalizedRecord.status = "terminal_ok";
|
|
901
977
|
}
|
|
@@ -1057,21 +1133,50 @@ function createQuerySurface(
|
|
|
1057
1133
|
return undefined;
|
|
1058
1134
|
};
|
|
1059
1135
|
const getProfileCredentialSessionId = () => ctx.credentialSessionId ?? id;
|
|
1136
|
+
const profileSettings = (options.settings ?? ctx.settings) as Pick<Settings, "get"> | undefined;
|
|
1137
|
+
const getProfileAvailableModels = (): Model<Api>[] => {
|
|
1138
|
+
const getAvailableForProfileActivation = ctx.modelRegistry.getAvailableForProfileActivation;
|
|
1139
|
+
return typeof getAvailableForProfileActivation === "function"
|
|
1140
|
+
? getAvailableForProfileActivation.call(ctx.modelRegistry)
|
|
1141
|
+
: ctx.modelRegistry.getAvailable();
|
|
1142
|
+
};
|
|
1060
1143
|
const resolveProfileAvailability = async (
|
|
1061
1144
|
profile: ModelProfileDefinition,
|
|
1062
1145
|
authenticatedProviders: ReadonlySet<string>,
|
|
1063
1146
|
): Promise<{ available: boolean; defaultModel?: Model<Api> }> => {
|
|
1147
|
+
if (profile.source !== "user" && inspectProxyProviderId(profileSettings).status === "invalid")
|
|
1148
|
+
return { available: false };
|
|
1149
|
+
const proxyProvider = profile.source === "user" ? undefined : tryResolveProxyProviderId(profileSettings);
|
|
1150
|
+
const proxyAuthenticated = proxyProvider !== undefined && authenticatedProviders.has(proxyProvider);
|
|
1151
|
+
const profileAuthenticated = new Set(authenticatedProviders);
|
|
1152
|
+
if (proxyAuthenticated) {
|
|
1153
|
+
for (const provider of getProxyRoutableProviders(profile)) profileAuthenticated.add(provider);
|
|
1154
|
+
}
|
|
1064
1155
|
const rewriteSelectorProvider = (selector: string): string => {
|
|
1065
1156
|
const slash = selector.indexOf("/");
|
|
1066
1157
|
if (slash < 0) return selector;
|
|
1067
1158
|
const provider = selector.slice(0, slash);
|
|
1068
|
-
if (
|
|
1159
|
+
if (profileAuthenticated.has(provider)) return selector;
|
|
1069
1160
|
const group = (profile.alternativeProviderGroups ?? []).find(candidates => candidates.includes(provider));
|
|
1070
1161
|
if (!group) return selector;
|
|
1071
|
-
const replacement = group.find(candidate =>
|
|
1162
|
+
const replacement = group.find(candidate => profileAuthenticated.has(candidate));
|
|
1072
1163
|
return replacement ? replacement + selector.slice(slash) : selector;
|
|
1073
1164
|
};
|
|
1074
1165
|
try {
|
|
1166
|
+
const proxyMode = profile.source === "user" ? "fallback" : resolveProxyMode(profileSettings);
|
|
1167
|
+
if (profile.source !== "user") {
|
|
1168
|
+
const configuredProviders = ctx.modelRegistry.getConfiguredProviderIds?.() ?? [];
|
|
1169
|
+
if (proxyProvider !== undefined && !configuredProviders.includes(proxyProvider))
|
|
1170
|
+
return { available: false };
|
|
1171
|
+
}
|
|
1172
|
+
if (profile.source !== "user" && proxyMode === "always") {
|
|
1173
|
+
if (
|
|
1174
|
+
proxyProvider === undefined ||
|
|
1175
|
+
!proxyAuthenticated ||
|
|
1176
|
+
!(ctx.modelRegistry.getConfiguredProviderIds?.() ?? []).includes(proxyProvider)
|
|
1177
|
+
)
|
|
1178
|
+
return { available: false };
|
|
1179
|
+
}
|
|
1075
1180
|
const bindings = resolveProfileBindings(profile);
|
|
1076
1181
|
const assignments: Array<{ value: ModelSelectorValue; isDefault: boolean }> = [];
|
|
1077
1182
|
if (bindings.defaultSelector !== undefined) {
|
|
@@ -1079,23 +1184,44 @@ function createQuerySurface(
|
|
|
1079
1184
|
}
|
|
1080
1185
|
for (const value of Object.values(bindings.modelRoles)) assignments.push({ value, isDefault: false });
|
|
1081
1186
|
for (const value of Object.values(bindings.agentModelOverrides)) assignments.push({ value, isDefault: false });
|
|
1187
|
+
const availableModels = getProfileAvailableModels();
|
|
1188
|
+
const resolutionRegistry = {
|
|
1189
|
+
...ctx.modelRegistry,
|
|
1190
|
+
getAvailable: () => availableModels,
|
|
1191
|
+
getApiKey: (model: Model<Api>, sessionId?: string) =>
|
|
1192
|
+
ctx.modelRegistry.getApiKeyForProvider(model.provider, sessionId, model.baseUrl),
|
|
1193
|
+
resolveCanonicalModel: ctx.modelRegistry.resolveCanonicalModel?.bind(ctx.modelRegistry),
|
|
1194
|
+
getCanonicalVariants: ctx.modelRegistry.getCanonicalVariants?.bind(ctx.modelRegistry),
|
|
1195
|
+
getCanonicalId: ctx.modelRegistry.getCanonicalId?.bind(ctx.modelRegistry),
|
|
1196
|
+
resolveModelByLookupAlias: ctx.modelRegistry.resolveModelByLookupAlias?.bind(ctx.modelRegistry),
|
|
1197
|
+
lookupAliasExists: ctx.modelRegistry.lookupAliasExists?.bind(ctx.modelRegistry),
|
|
1198
|
+
clearCanonicalVariant: ctx.modelRegistry.clearCanonicalVariant?.bind(ctx.modelRegistry),
|
|
1199
|
+
};
|
|
1082
1200
|
let defaultModel: Model<Api> | undefined;
|
|
1083
1201
|
for (const assignment of assignments) {
|
|
1084
|
-
|
|
1202
|
+
let selectors = normalizeModelSelectorValue(assignment.value).map(rewriteSelectorProvider);
|
|
1203
|
+
if (proxyProvider !== undefined && proxyAuthenticated && profile.source !== "user") {
|
|
1204
|
+
selectors = selectors.map(selector =>
|
|
1205
|
+
rewriteSelectorForProxy(
|
|
1206
|
+
selector,
|
|
1207
|
+
proxyProvider,
|
|
1208
|
+
proxyMode,
|
|
1209
|
+
availableModels,
|
|
1210
|
+
new Set(authenticatedProviders),
|
|
1211
|
+
getProxyRoutableProviders(profile),
|
|
1212
|
+
),
|
|
1213
|
+
);
|
|
1214
|
+
}
|
|
1085
1215
|
const hasBareSelector = selectors.some(selector => {
|
|
1086
1216
|
const suffix = splitSelectorThinkingSuffix(selector);
|
|
1087
1217
|
const identity = suffix.thinkingLevel ? suffix.selector : selector;
|
|
1088
1218
|
return !identity.includes("/");
|
|
1089
1219
|
});
|
|
1090
|
-
if (!assignment.isDefault && !hasBareSelector)
|
|
1220
|
+
if (!assignment.isDefault && !requiresQualifiedModelProfileRoleResolution(profile) && !hasBareSelector)
|
|
1221
|
+
continue;
|
|
1091
1222
|
const resolution = await resolveModelChainWithAuth(
|
|
1092
1223
|
selectors,
|
|
1093
|
-
|
|
1094
|
-
...ctx.modelRegistry,
|
|
1095
|
-
getAvailable: () => ctx.modelRegistry.getAvailable(),
|
|
1096
|
-
getApiKey: (model: Model<Api>, sessionId?: string) =>
|
|
1097
|
-
ctx.modelRegistry.getApiKeyForProvider(model.provider, sessionId, model.baseUrl),
|
|
1098
|
-
},
|
|
1224
|
+
resolutionRegistry,
|
|
1099
1225
|
options.settings,
|
|
1100
1226
|
getProfileCredentialSessionId(),
|
|
1101
1227
|
{
|
|
@@ -1113,6 +1239,45 @@ function createQuerySurface(
|
|
|
1113
1239
|
return { available: false };
|
|
1114
1240
|
}
|
|
1115
1241
|
};
|
|
1242
|
+
const collectProfileAuthentication = async (
|
|
1243
|
+
profiles: ReadonlyMap<string, ModelProfileDefinition>,
|
|
1244
|
+
): Promise<Set<string>> => {
|
|
1245
|
+
const providers = new Set<string>();
|
|
1246
|
+
for (const profile of profiles.values()) {
|
|
1247
|
+
for (const provider of profile.requiredProviders) providers.add(provider);
|
|
1248
|
+
for (const group of profile.alternativeProviderGroups ?? []) {
|
|
1249
|
+
for (const provider of group) providers.add(provider);
|
|
1250
|
+
}
|
|
1251
|
+
for (const provider of deriveModelProfileMappedProviders(profile)) providers.add(provider);
|
|
1252
|
+
}
|
|
1253
|
+
const authenticated = new Set<string>();
|
|
1254
|
+
await Promise.all(
|
|
1255
|
+
[...providers].map(async provider => {
|
|
1256
|
+
try {
|
|
1257
|
+
const apiKey = await ctx.modelRegistry.getApiKeyForProvider(provider, getProfileCredentialSessionId());
|
|
1258
|
+
if (apiKey === kNoAuth || isAuthenticated(apiKey)) authenticated.add(provider);
|
|
1259
|
+
} catch {
|
|
1260
|
+
// A provider whose credential state cannot be read is not currently configurable.
|
|
1261
|
+
}
|
|
1262
|
+
}),
|
|
1263
|
+
);
|
|
1264
|
+
const proxyProviders = new Set<string>();
|
|
1265
|
+
for (const profile of profiles.values()) {
|
|
1266
|
+
if (profile.source === "user") continue;
|
|
1267
|
+
const proxyProvider = tryResolveProxyProviderId(profileSettings);
|
|
1268
|
+
if (proxyProvider !== undefined) proxyProviders.add(proxyProvider);
|
|
1269
|
+
}
|
|
1270
|
+
for (const proxyProvider of proxyProviders) {
|
|
1271
|
+
try {
|
|
1272
|
+
const apiKey = await ctx.modelRegistry.getApiKeyForProvider(proxyProvider, getProfileCredentialSessionId());
|
|
1273
|
+
if (apiKey === kNoAuth || isAuthenticated(apiKey)) authenticated.add(proxyProvider);
|
|
1274
|
+
} catch {
|
|
1275
|
+
// Passive availability must degrade to unavailable when proxy credential
|
|
1276
|
+
// refresh/storage fails; explicit activation retains its diagnostics.
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
return authenticated;
|
|
1280
|
+
};
|
|
1116
1281
|
const getDiff = async () => {
|
|
1117
1282
|
try {
|
|
1118
1283
|
const { stdout } = await execFileAsync("git", ["diff", "--no-ext-diff"], {
|
|
@@ -1245,9 +1410,7 @@ function createQuerySurface(
|
|
|
1245
1410
|
if (collision) return degraded();
|
|
1246
1411
|
let authenticatedProviders: ReadonlySet<string>;
|
|
1247
1412
|
try {
|
|
1248
|
-
authenticatedProviders = await
|
|
1249
|
-
ctx.modelRegistry.getApiKeyForProvider(provider, getProfileCredentialSessionId()),
|
|
1250
|
-
);
|
|
1413
|
+
authenticatedProviders = await collectProfileAuthentication(profiles);
|
|
1251
1414
|
} catch {
|
|
1252
1415
|
// Availability join failed: degrade only the synthetic facade,
|
|
1253
1416
|
// retain concrete rows and the active marker readback.
|
|
@@ -1265,7 +1428,12 @@ function createQuerySurface(
|
|
|
1265
1428
|
);
|
|
1266
1429
|
const availableProfileIds = new Set<string>();
|
|
1267
1430
|
for (const [name, profile] of profiles) {
|
|
1268
|
-
if (
|
|
1431
|
+
if (profile.source !== "user" && inspectProxyProviderId(profileSettings).status === "invalid") continue;
|
|
1432
|
+
const profileAuthenticated = new Set(authenticatedProviders);
|
|
1433
|
+
const proxyProvider = profile.source === "user" ? undefined : tryResolveProxyProviderId(profileSettings);
|
|
1434
|
+
if (proxyProvider !== undefined && profileAuthenticated.has(proxyProvider))
|
|
1435
|
+
for (const provider of getProxyRoutableProviders(profile)) profileAuthenticated.add(provider);
|
|
1436
|
+
if (!isModelProfileProviderAvailable(profile, profileAuthenticated)) continue;
|
|
1269
1437
|
if (!fullyResolvedProfiles.has(name)) continue;
|
|
1270
1438
|
// A profile with a default mapping is selectable only when its
|
|
1271
1439
|
// default chain actually resolves to an authenticated model:
|
|
@@ -1336,18 +1504,19 @@ function createQuerySurface(
|
|
|
1336
1504
|
(options.steerStatusLookup ?? (value => reconciliation.lookup("steer", value)))(selector),
|
|
1337
1505
|
getModelProfiles: async () => {
|
|
1338
1506
|
const profiles = ctx.modelRegistry.getModelProfiles();
|
|
1339
|
-
const authenticatedProviders = await
|
|
1340
|
-
ctx.modelRegistry.getApiKeyForProvider(provider, getProfileCredentialSessionId()),
|
|
1341
|
-
);
|
|
1507
|
+
const authenticatedProviders = await collectProfileAuthentication(profiles);
|
|
1342
1508
|
return (await Promise.all(
|
|
1343
|
-
projectModelProfileCatalog(profiles, ctx.modelRegistry.getError()).map(async item =>
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1509
|
+
projectModelProfileCatalog(profiles, ctx.modelRegistry.getError()).map(async item => {
|
|
1510
|
+
const profile = profiles.get(item.id)!;
|
|
1511
|
+
const profileAuthenticated = new Set(authenticatedProviders);
|
|
1512
|
+
const proxyProvider = profile.source === "user" ? undefined : tryResolveProxyProviderId(profileSettings);
|
|
1513
|
+
if (proxyProvider !== undefined && profileAuthenticated.has(proxyProvider))
|
|
1514
|
+
for (const provider of getProxyRoutableProviders(profile)) profileAuthenticated.add(provider);
|
|
1515
|
+
const available =
|
|
1516
|
+
isModelProfileProviderAvailable(profile, profileAuthenticated) &&
|
|
1517
|
+
(await resolveProfileAvailability(profile, authenticatedProviders)).available;
|
|
1518
|
+
return { ...item, available };
|
|
1519
|
+
}),
|
|
1351
1520
|
)) as unknown[];
|
|
1352
1521
|
},
|
|
1353
1522
|
installedQueries: policy.installedQueries,
|
|
@@ -1600,6 +1769,55 @@ function providerFailureFromAgentEnd(event: unknown): { code: string; message: s
|
|
|
1600
1769
|
}
|
|
1601
1770
|
}
|
|
1602
1771
|
|
|
1772
|
+
interface PromptTerminalEvidence {
|
|
1773
|
+
content?: TurnResultContent;
|
|
1774
|
+
hasActivity: boolean;
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
function promptTerminalEvidenceFromAgentEnd(event: unknown): PromptTerminalEvidence {
|
|
1778
|
+
try {
|
|
1779
|
+
if (!event || typeof event !== "object") return { hasActivity: false };
|
|
1780
|
+
const messages = (event as { messages?: unknown }).messages;
|
|
1781
|
+
if (!Array.isArray(messages)) return { hasActivity: false };
|
|
1782
|
+
const assistant = [...messages]
|
|
1783
|
+
.reverse()
|
|
1784
|
+
.find(
|
|
1785
|
+
message => message && typeof message === "object" && (message as { role?: unknown }).role === "assistant",
|
|
1786
|
+
);
|
|
1787
|
+
if (!assistant || typeof assistant !== "object") return { hasActivity: false };
|
|
1788
|
+
const content = (assistant as { content?: unknown }).content;
|
|
1789
|
+
if (typeof content === "string") {
|
|
1790
|
+
const bounded = sanitizeTurnResultContent(content);
|
|
1791
|
+
return { content: bounded, hasActivity: content.trim().length > 0 };
|
|
1792
|
+
}
|
|
1793
|
+
if (Array.isArray(content)) {
|
|
1794
|
+
const hasActivity = content.some(block => {
|
|
1795
|
+
if (block === null || typeof block !== "object") return false;
|
|
1796
|
+
const type = (block as { type?: unknown }).type;
|
|
1797
|
+
if (type === "text") {
|
|
1798
|
+
const text = (block as { text?: unknown }).text;
|
|
1799
|
+
return typeof text === "string" && text.trim().length > 0;
|
|
1800
|
+
}
|
|
1801
|
+
return typeof type === "string" && type.length > 0;
|
|
1802
|
+
});
|
|
1803
|
+
const text = content
|
|
1804
|
+
.filter(
|
|
1805
|
+
(block): block is { type: "text"; text: string } =>
|
|
1806
|
+
block !== null &&
|
|
1807
|
+
typeof block === "object" &&
|
|
1808
|
+
(block as { type?: unknown }).type === "text" &&
|
|
1809
|
+
typeof (block as { text?: unknown }).text === "string",
|
|
1810
|
+
)
|
|
1811
|
+
.map(block => block.text)
|
|
1812
|
+
.join("");
|
|
1813
|
+
return { content: sanitizeTurnResultContent(text), hasActivity };
|
|
1814
|
+
}
|
|
1815
|
+
return { content: sanitizeTurnResultContent(""), hasActivity: false };
|
|
1816
|
+
} catch {
|
|
1817
|
+
return { hasActivity: false };
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1603
1821
|
function createControlSurface(
|
|
1604
1822
|
ctx: ExtensionContext,
|
|
1605
1823
|
api: ExtensionAPI,
|
|
@@ -1611,6 +1829,7 @@ function createControlSurface(
|
|
|
1611
1829
|
startsOwnTurn: boolean,
|
|
1612
1830
|
sdkRunToken: string,
|
|
1613
1831
|
) => void,
|
|
1832
|
+
armPromptDeadline: (correlation: InvocationCorrelation) => void,
|
|
1614
1833
|
steerReconciliation: KindAwareReconciliation,
|
|
1615
1834
|
onPromotedTurn?: (
|
|
1616
1835
|
kind: InvocationKind,
|
|
@@ -1638,6 +1857,36 @@ function createControlSurface(
|
|
|
1638
1857
|
canResolveGate: () => boolean = () => true,
|
|
1639
1858
|
trackGateResolution: <T>(resolution: Promise<T>) => Promise<T> = async resolution => await resolution,
|
|
1640
1859
|
): ControlSurface {
|
|
1860
|
+
const normalizePromptImages = (value: unknown): ImageContent[] => {
|
|
1861
|
+
if (!Array.isArray(value)) return [];
|
|
1862
|
+
const normalized: ImageContent[] = [];
|
|
1863
|
+
for (const candidate of value) {
|
|
1864
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) continue;
|
|
1865
|
+
const image = candidate as Record<string, unknown>;
|
|
1866
|
+
if (typeof image.data !== "string" || image.data.trim().length === 0) continue;
|
|
1867
|
+
if (image.mimeType !== undefined && typeof image.mimeType !== "string") continue;
|
|
1868
|
+
normalized.push({
|
|
1869
|
+
type: "image",
|
|
1870
|
+
data: image.data,
|
|
1871
|
+
mimeType: typeof image.mimeType === "string" && image.mimeType.length > 0 ? image.mimeType : "image/png",
|
|
1872
|
+
});
|
|
1873
|
+
}
|
|
1874
|
+
return normalized;
|
|
1875
|
+
};
|
|
1876
|
+
type InternalSendOptions = NonNullable<Parameters<ExtensionAPI["sendUserMessage"]>[1]> & {
|
|
1877
|
+
sdkRunCapability?: SdkRunCapability;
|
|
1878
|
+
};
|
|
1879
|
+
type InternalSdkApi = Omit<ExtensionAPI, "sendUserMessage"> & {
|
|
1880
|
+
sendUserMessage: (
|
|
1881
|
+
content: Parameters<ExtensionAPI["sendUserMessage"]>[0],
|
|
1882
|
+
options?: InternalSendOptions,
|
|
1883
|
+
) => Promise<void>;
|
|
1884
|
+
};
|
|
1885
|
+
const internalApi = api as InternalSdkApi;
|
|
1886
|
+
const sendSdkUserMessage = (
|
|
1887
|
+
content: Parameters<ExtensionAPI["sendUserMessage"]>[0],
|
|
1888
|
+
options?: Record<string, unknown>,
|
|
1889
|
+
): Promise<void> => internalApi.sendUserMessage(content, options);
|
|
1641
1890
|
const surfacePolicy =
|
|
1642
1891
|
policy ?? createSdkSurfacePolicyForContext(ctx, hasSdkWorkflowGateCapability(ctx.workflowGate));
|
|
1643
1892
|
const typed = (operation: string, input: Record<string, unknown> = {}) =>
|
|
@@ -1722,7 +1971,7 @@ function createControlSurface(
|
|
|
1722
1971
|
kind: InvocationKind,
|
|
1723
1972
|
clientRef: string | undefined,
|
|
1724
1973
|
run: (options: {
|
|
1725
|
-
|
|
1974
|
+
sdkRunCapability: SdkRunCapability;
|
|
1726
1975
|
onPreflightAccepted: () => void;
|
|
1727
1976
|
onPreflightAcceptCommit: () => Promise<void>;
|
|
1728
1977
|
/** Internal disposition before a queued submission is actually consumed. */
|
|
@@ -1743,6 +1992,7 @@ function createControlSurface(
|
|
|
1743
1992
|
reconciliation.admit(kind, retainedClientRef);
|
|
1744
1993
|
const correlation = newCorrelation();
|
|
1745
1994
|
const sdkRunToken = `${correlation.commandId}:${correlation.turnId}`;
|
|
1995
|
+
const sdkRunCapability = createSdkRunCapability(sdkRunToken);
|
|
1746
1996
|
const preflight = Promise.withResolvers<void>();
|
|
1747
1997
|
let accepted = false;
|
|
1748
1998
|
let settled = false;
|
|
@@ -1766,6 +2016,7 @@ function createControlSurface(
|
|
|
1766
2016
|
await reconciliation.noteAccepted(kind, correlation, retainedClientRef);
|
|
1767
2017
|
accepted = true;
|
|
1768
2018
|
settled = true;
|
|
2019
|
+
if (kind === "prompt" && startsOwnTurn) armPromptDeadline(correlation);
|
|
1769
2020
|
// The accepted submission does NOT own the active turn until its run
|
|
1770
2021
|
// actually STARTS: the connection is carried on the pending entry and
|
|
1771
2022
|
// associated at agent_start instead (review thread P1).
|
|
@@ -1801,7 +2052,7 @@ function createControlSurface(
|
|
|
1801
2052
|
const submission = Promise.resolve(
|
|
1802
2053
|
run({
|
|
1803
2054
|
onPreflightAccepted: () => void accept().catch(() => undefined),
|
|
1804
|
-
|
|
2055
|
+
sdkRunCapability,
|
|
1805
2056
|
onPreflightAcceptCommit: accept,
|
|
1806
2057
|
onDispatchDisposition: promotion => {
|
|
1807
2058
|
promotionStartsOwnRun = promotion.startsOwnRun;
|
|
@@ -2881,52 +3132,77 @@ function createControlSurface(
|
|
|
2881
3132
|
}
|
|
2882
3133
|
};
|
|
2883
3134
|
return {
|
|
2884
|
-
prompt: async (text, images, clientRef) =>
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
3135
|
+
prompt: async (text, images, clientRef) => {
|
|
3136
|
+
const invalid = validateRequiredPromptText("turn.prompt", { text, images });
|
|
3137
|
+
if (invalid) throw Object.assign(new Error(invalid.message), { code: invalid.code });
|
|
3138
|
+
return await submit("prompt", clientRef, ({ queuedAtDispatch, sdkRunCapability, ...options }) =>
|
|
3139
|
+
sendSdkUserMessage(
|
|
3140
|
+
typeof images === "undefined"
|
|
3141
|
+
? text
|
|
3142
|
+
: ([{ type: "text", text }, ...normalizePromptImages(images)] as [
|
|
3143
|
+
{ type: "text"; text: string },
|
|
3144
|
+
...ImageContent[],
|
|
3145
|
+
]),
|
|
2888
3146
|
{
|
|
2889
3147
|
...options,
|
|
2890
|
-
|
|
3148
|
+
sdkRunCapability,
|
|
3149
|
+
// ACP terminal settlement is owned by the correlated agent_end
|
|
3150
|
+
// publication. Post-prompt recovery may include independent
|
|
3151
|
+
// subagent work and must not hold that client-facing boundary.
|
|
2891
3152
|
...(queuedAtDispatch ? { queuedAtDispatch: true } : {}),
|
|
2892
3153
|
},
|
|
2893
3154
|
),
|
|
2894
|
-
)
|
|
3155
|
+
);
|
|
3156
|
+
},
|
|
2895
3157
|
steer: async (text, clientRef) => {
|
|
3158
|
+
const invalid = validateRequiredPromptText("turn.steer", { text });
|
|
3159
|
+
if (invalid) throw Object.assign(new Error(invalid.message), { code: invalid.code });
|
|
2896
3160
|
const retainedClientRef = normalizeClientRef(clientRef);
|
|
2897
3161
|
if (retainedClientRef === undefined) {
|
|
2898
3162
|
const correlation = newCorrelation();
|
|
2899
|
-
await
|
|
3163
|
+
await sendSdkUserMessage(text, { deliverAs: "steer" });
|
|
2900
3164
|
return { accepted: true, ...correlation };
|
|
2901
3165
|
}
|
|
2902
3166
|
const durable = steerReconciliation;
|
|
2903
3167
|
const reservation = await durable.reserveSteer(retainedClientRef, text);
|
|
2904
3168
|
if (reservation.replay) return { accepted: reservation.result.status === "accepted", ...reservation.result };
|
|
2905
3169
|
try {
|
|
2906
|
-
await
|
|
3170
|
+
await sendSdkUserMessage(text, { deliverAs: "steer" });
|
|
2907
3171
|
return { accepted: true, ...(await durable.settleSteer(retainedClientRef, "accepted")) };
|
|
2908
3172
|
} catch (error) {
|
|
2909
3173
|
return { accepted: false, ...(await durable.settleSteer(retainedClientRef, "rejected", error)) };
|
|
2910
3174
|
}
|
|
2911
3175
|
},
|
|
2912
|
-
followUp: async text =>
|
|
2913
|
-
|
|
3176
|
+
followUp: async text => {
|
|
3177
|
+
const invalid = validateRequiredPromptText("turn.follow_up", { text });
|
|
3178
|
+
if (invalid) throw Object.assign(new Error(invalid.message), { code: invalid.code });
|
|
3179
|
+
return await submit(
|
|
2914
3180
|
"prompt",
|
|
2915
3181
|
undefined,
|
|
2916
|
-
|
|
3182
|
+
({ sdkRunCapability, ...options }) =>
|
|
3183
|
+
sendSdkUserMessage(text, {
|
|
3184
|
+
...options,
|
|
3185
|
+
sdkRunCapability,
|
|
3186
|
+
deliverAs: "followUp",
|
|
3187
|
+
}),
|
|
2917
3188
|
undefined,
|
|
2918
3189
|
false,
|
|
2919
3190
|
// Follow-ups never start inline; ownership correlates at promotion.
|
|
2920
3191
|
true,
|
|
2921
|
-
)
|
|
3192
|
+
);
|
|
3193
|
+
},
|
|
2922
3194
|
abort: async () => {
|
|
2923
3195
|
await Promise.resolve(ctx.abort()).catch(() => undefined);
|
|
2924
3196
|
return { aborted: true };
|
|
2925
3197
|
},
|
|
2926
3198
|
abortTerminal: terminalAbort,
|
|
2927
3199
|
abortAndPrompt: async text => {
|
|
3200
|
+
const invalid = validateRequiredPromptText("turn.abort_and_prompt", { text });
|
|
3201
|
+
if (invalid) throw Object.assign(new Error(invalid.message), { code: invalid.code });
|
|
2928
3202
|
await ctx.abort();
|
|
2929
|
-
return await submit("prompt", undefined,
|
|
3203
|
+
return await submit("prompt", undefined, ({ sdkRunCapability, ...options }) =>
|
|
3204
|
+
sendSdkUserMessage(text, { ...options, sdkRunCapability }),
|
|
3205
|
+
);
|
|
2930
3206
|
},
|
|
2931
3207
|
answerAsk: unavailable("ask.answer"),
|
|
2932
3208
|
answerGate: async (id, response, expectedSessionId, idempotencyKey) =>
|
|
@@ -2953,9 +3229,10 @@ function createControlSurface(
|
|
|
2953
3229
|
return await submit(
|
|
2954
3230
|
"skill",
|
|
2955
3231
|
clientRef,
|
|
2956
|
-
options =>
|
|
3232
|
+
({ sdkRunCapability, ...options }) =>
|
|
2957
3233
|
ctx.invokeSkill!(name, args, {
|
|
2958
3234
|
...options,
|
|
3235
|
+
sdkRunCapability,
|
|
2959
3236
|
onSkillPrepared: meta => {
|
|
2960
3237
|
prepared = meta;
|
|
2961
3238
|
},
|
|
@@ -3374,6 +3651,9 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
3374
3651
|
failureCause?: unknown,
|
|
3375
3652
|
maintenanceOutcome?: string,
|
|
3376
3653
|
lifecycleOwner?: LifecycleOwner,
|
|
3654
|
+
terminalContent?: TurnResultContent,
|
|
3655
|
+
terminalHasActivity = false,
|
|
3656
|
+
terminalOutcome?: { kind: "stopped"; reason: "cancelled"; provenance: "client_cancel" },
|
|
3377
3657
|
): Promise<void> => {
|
|
3378
3658
|
const current = lifecycleOwner?.state ?? active;
|
|
3379
3659
|
if (!current) return;
|
|
@@ -3461,6 +3741,11 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
3461
3741
|
if (current.activeInvocation?.kind === "prompt") {
|
|
3462
3742
|
current.deadlineManager.onAccepted(current.activeInvocation.correlation);
|
|
3463
3743
|
}
|
|
3744
|
+
} else {
|
|
3745
|
+
// An empty drain is an agent-initiated successor run. Clear the
|
|
3746
|
+
// predecessor's SDK owner and active invocation so abort ownership and
|
|
3747
|
+
// tool-progress renewal cannot leak into the successor.
|
|
3748
|
+
adoptLifecycleBatch(undefined);
|
|
3464
3749
|
}
|
|
3465
3750
|
transitions = drained.map(({ kind, correlation }) => ({ kind, correlation }));
|
|
3466
3751
|
} else {
|
|
@@ -3626,7 +3911,17 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
3626
3911
|
const reasonKey = `${invocation.correlation.commandId}:${invocation.correlation.turnId}`;
|
|
3627
3912
|
const unrecorded = type === "agent_end" ? current.unrecordedFailureReasons?.get(reasonKey) : undefined;
|
|
3628
3913
|
if (type === "agent_end" && invocation.kind === "prompt")
|
|
3629
|
-
current.deadlineManager.noteTerminalTransition(
|
|
3914
|
+
current.deadlineManager.noteTerminalTransition(
|
|
3915
|
+
invocation.correlation,
|
|
3916
|
+
unrecorded,
|
|
3917
|
+
terminalContent !== undefined || terminalHasActivity || terminalOutcome !== undefined
|
|
3918
|
+
? ({
|
|
3919
|
+
content: terminalContent,
|
|
3920
|
+
hasActivity: terminalHasActivity,
|
|
3921
|
+
outcome: terminalOutcome,
|
|
3922
|
+
} satisfies PromptTerminalTransitionEvidence)
|
|
3923
|
+
: undefined,
|
|
3924
|
+
);
|
|
3630
3925
|
if (type === "agent_end") {
|
|
3631
3926
|
// Compound recovery (exact-head review P1): if this run's
|
|
3632
3927
|
// agent_failed write failed, re-record the sanitized reason
|
|
@@ -3650,7 +3945,16 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
3650
3945
|
error:
|
|
3651
3946
|
failureCause ?? Object.assign(new Error("agent run failed"), { code: "agent_failed" }),
|
|
3652
3947
|
}
|
|
3653
|
-
: {
|
|
3948
|
+
: {
|
|
3949
|
+
type,
|
|
3950
|
+
...(type === "agent_end" && terminalContent !== undefined
|
|
3951
|
+
? { content: terminalContent }
|
|
3952
|
+
: {}),
|
|
3953
|
+
...(type === "agent_end" && terminalHasActivity ? { hasActivity: true } : {}),
|
|
3954
|
+
...(type === "agent_end" && terminalOutcome !== undefined
|
|
3955
|
+
? { outcome: terminalOutcome }
|
|
3956
|
+
: {}),
|
|
3957
|
+
};
|
|
3654
3958
|
await current.reconciliation.noteTransition(invocation.kind, invocation.correlation, frame as never);
|
|
3655
3959
|
if ((type as string) === "agent_end") {
|
|
3656
3960
|
if (invocation.kind === "prompt") current.deadlineManager.clear(invocation.correlation);
|
|
@@ -3814,6 +4118,11 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
3814
4118
|
owner?.failureDiagnosticKeys.has(lifecycleCorrelationKey(correlation)),
|
|
3815
4119
|
);
|
|
3816
4120
|
const failureAlreadyPublished = failure === undefined || (hasExistingFailure && genericFailureKeys.length === 0);
|
|
4121
|
+
const terminalEvidence = promptTerminalEvidenceFromAgentEnd(event);
|
|
4122
|
+
const terminalOutcome =
|
|
4123
|
+
event.stopReason === "cancelled"
|
|
4124
|
+
? ({ kind: "stopped", reason: "cancelled", provenance: "client_cancel" } as const)
|
|
4125
|
+
: undefined;
|
|
3817
4126
|
return trackLifecycle(async () => {
|
|
3818
4127
|
if (failure && !failureAlreadyPublished) {
|
|
3819
4128
|
for (const key of genericFailureKeys) owner?.failureDiagnosticKeys.delete(key);
|
|
@@ -3825,6 +4134,9 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
3825
4134
|
undefined,
|
|
3826
4135
|
event.stopReason === "maintenance" ? event.maintenanceOutcome : undefined,
|
|
3827
4136
|
lifecycleOwner,
|
|
4137
|
+
terminalEvidence.content,
|
|
4138
|
+
terminalEvidence.hasActivity,
|
|
4139
|
+
terminalOutcome,
|
|
3828
4140
|
);
|
|
3829
4141
|
}, owner).finally(() => {
|
|
3830
4142
|
if (typeof event.sdkRunToken === "string" && lifecycleRunOwners.get(event.sdkRunToken)?.state === owner)
|
|
@@ -4159,8 +4471,8 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
4159
4471
|
// promotion/agent_start instead, so a prompt waiting behind a
|
|
4160
4472
|
// legitimately long turn never false-fires. The agent_start
|
|
4161
4473
|
// re-entry in emitLifecycle is a no-op for an existing lease.
|
|
4162
|
-
if (startsOwnTurn && kind === "prompt") deadlineManager.onAccepted(correlation);
|
|
4163
4474
|
},
|
|
4475
|
+
correlation => deadlineManager.onAccepted(correlation),
|
|
4164
4476
|
steerReconciliation,
|
|
4165
4477
|
(kind, correlation, connectionId, sdkRunToken, promotion) => {
|
|
4166
4478
|
const bindPromotedToken = (batch?: LifecycleBatch): void => {
|