@gajae-code/coding-agent 0.12.21 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +67 -2
- package/dist/types/cli/args.d.ts +5 -2
- package/dist/types/cli/root-flags.d.ts +246 -0
- package/dist/types/cli.d.ts +11 -19
- package/dist/types/commands/daemon.d.ts +0 -30
- package/dist/types/commands/launch.d.ts +11 -19
- package/dist/types/commands/quick-lane.d.ts +32 -0
- package/dist/types/commands/sdk.d.ts +21 -15
- package/dist/types/config/file-lock-gc.d.ts +2 -0
- package/dist/types/config/model-equivalence.d.ts +20 -0
- package/dist/types/config/model-profile-activation.d.ts +62 -17
- package/dist/types/config/model-profiles.d.ts +10 -0
- package/dist/types/config/model-registry.d.ts +68 -1
- package/dist/types/config/model-resolver.d.ts +15 -4
- package/dist/types/config/models-config-schema.d.ts +7 -2
- package/dist/types/config/provider-selection-policy.d.ts +62 -0
- package/dist/types/config/settings-schema.d.ts +36 -78
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/edit/renderer.d.ts +5 -2
- package/dist/types/edit/streaming.d.ts +13 -0
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -0
- package/dist/types/extensibility/extensions/compact-handler.d.ts +2 -1
- package/dist/types/extensibility/extensions/types.d.ts +3 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +19 -5
- package/dist/types/internal-urls/local-protocol.d.ts +1 -0
- package/dist/types/modes/components/model-selector.d.ts +0 -2
- package/dist/types/modes/components/provider-order-context.d.ts +45 -0
- package/dist/types/modes/components/provider-order-editor.d.ts +24 -0
- package/dist/types/modes/components/settings-defs.d.ts +5 -1
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/quick-lane/classify.d.ts +40 -0
- package/dist/types/runtime/stt-settings-setup.d.ts +18 -0
- package/dist/types/sdk/broker/broker.d.ts +56 -2
- package/dist/types/sdk/broker/operation-receipt-ledger.d.ts +25 -0
- package/dist/types/sdk/broker/session-index.d.ts +83 -2
- package/dist/types/sdk/broker/state-version.d.ts +1 -1
- package/dist/types/sdk/bus/notification-service.d.ts +6 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +8 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +4 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +26 -0
- package/dist/types/sdk/cli/rows.d.ts +98 -0
- package/dist/types/sdk/cli/session-cli.d.ts +35 -3
- package/dist/types/sdk/elevation/allowlist.d.ts +20 -0
- package/dist/types/sdk/elevation/audit.d.ts +93 -0
- package/dist/types/sdk/elevation/capability.d.ts +3 -0
- package/dist/types/sdk/elevation/digest.d.ts +72 -0
- package/dist/types/sdk/elevation/dispatch-receipt.d.ts +85 -0
- package/dist/types/sdk/elevation/grant-ledger.d.ts +163 -0
- package/dist/types/sdk/elevation/owner.d.ts +48 -0
- package/dist/types/sdk/guides/cache.d.ts +98 -0
- package/dist/types/sdk/guides/catalog.d.ts +156 -0
- package/dist/types/sdk/guides/cli.d.ts +39 -0
- package/dist/types/sdk/guides/manifest.d.ts +66 -0
- package/dist/types/sdk/guides/verify.d.ts +66 -0
- package/dist/types/sdk/host/control/dispatch.d.ts +8 -0
- package/dist/types/sdk/host/control/index.d.ts +1 -1
- package/dist/types/sdk/host/control/operations.d.ts +3 -2
- package/dist/types/sdk/host/query/cursor.d.ts +10 -0
- package/dist/types/sdk/host/query/handlers.d.ts +21 -0
- package/dist/types/sdk/host/session-runtime.d.ts +9 -0
- package/dist/types/sdk/mcp/server.d.ts +2 -2
- package/dist/types/sdk/prompt-status.d.ts +11 -0
- package/dist/types/sdk/session.d.ts +4 -0
- package/dist/types/sdk/transport/index.d.ts +1 -0
- package/dist/types/sdk/transport/relay.d.ts +1 -0
- package/dist/types/sdk/transport/serve-cli.d.ts +20 -1
- package/dist/types/session/agent-session.d.ts +32 -18
- package/dist/types/session/blob-store.d.ts +13 -7
- package/dist/types/session/fallback-chain-controller.d.ts +15 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +25 -1
- package/dist/types/session/internal/session-memory-sidecar.d.ts +5 -5
- package/dist/types/session/session-manager.d.ts +139 -10
- package/dist/types/session/session-storage.d.ts +48 -0
- package/dist/types/setup/provider-onboarding.d.ts +7 -1
- package/dist/types/slash-commands/builtin-registry.d.ts +14 -0
- package/dist/types/stt/setup.d.ts +2 -0
- package/dist/types/task/executor.d.ts +10 -2
- package/dist/types/tools/browser/runtime-diagnostics.d.ts +65 -0
- package/dist/types/tools/browser/tab-protocol.d.ts +4 -0
- package/dist/types/tools/browser/tab-supervisor.d.ts +2 -0
- package/dist/types/tools/browser/tab-worker.d.ts +2 -0
- package/dist/types/tools/browser.d.ts +2 -0
- package/dist/types/tools/image-gen.d.ts +14 -18
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tools/resource-gc.d.ts +2 -0
- package/dist/types/utils/pasted-image-path.d.ts +6 -0
- package/package.json +9 -9
- package/scripts/generate-sdk-operation-inventory.ts +8 -0
- package/scripts/run-test-manifest.ts +61 -21
- package/scripts/verify-gjc-sdk-canonicalization.ts +9 -4
- package/src/cli/args.ts +62 -9
- package/src/cli/completion-cli.ts +1 -0
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/root-flags.ts +100 -0
- package/src/cli/setup-cli.ts +6 -2
- package/src/cli.ts +42 -65
- package/src/commands/acp.ts +1 -1
- package/src/commands/daemon.ts +2 -31
- package/src/commands/gc.ts +1 -1
- package/src/commands/launch.ts +8 -129
- package/src/commands/quick-lane.ts +51 -0
- package/src/commands/sdk.ts +200 -26
- package/src/commands/setup.ts +4 -2
- package/src/config/file-lock-gc.ts +4 -1
- package/src/config/model-equivalence.ts +76 -7
- package/src/config/model-profile-activation.ts +1115 -265
- package/src/config/model-profiles.ts +134 -2
- package/src/config/model-registry.ts +352 -50
- package/src/config/model-resolver.ts +289 -38
- package/src/config/models-config-schema.ts +11 -4
- package/src/config/provider-selection-policy.ts +146 -0
- package/src/config/settings-schema.ts +50 -77
- package/src/config/settings.ts +40 -0
- package/src/coordinator-mcp/server.ts +32 -2
- package/src/edit/renderer.ts +175 -223
- package/src/edit/streaming.ts +108 -1
- package/src/export/html/index.ts +57 -7
- package/src/extensibility/custom-tools/types.ts +2 -0
- package/src/extensibility/extensions/compact-handler.ts +3 -2
- package/src/extensibility/extensions/runner.ts +6 -0
- package/src/extensibility/extensions/types.ts +3 -0
- package/src/gjc-runtime/gc-runtime.ts +379 -20
- package/src/gjc-runtime/launch-worktree.ts +5 -1
- package/src/gjc-runtime/team-runtime.ts +59 -2
- package/src/gjc-runtime/tmux-owner-isolation.ts +19 -0
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +16 -1
- package/src/internal-urls/docs-index.generated.ts +17 -13
- package/src/internal-urls/local-protocol.ts +1 -0
- package/src/main.ts +40 -3
- package/src/memories/index.ts +4 -2
- package/src/modes/acp/acp-agent.ts +235 -91
- package/src/modes/components/model-selector.ts +206 -42
- package/src/modes/components/provider-order-context.ts +197 -0
- package/src/modes/components/provider-order-editor.ts +441 -0
- package/src/modes/components/settings-defs.ts +21 -1
- package/src/modes/components/settings-selector.ts +45 -2
- package/src/modes/components/tool-execution.ts +47 -15
- package/src/modes/controllers/command-controller.ts +6 -3
- package/src/modes/controllers/extension-ui-controller.ts +2 -0
- package/src/modes/controllers/input-controller.ts +73 -2
- package/src/modes/controllers/selector-controller.ts +260 -153
- package/src/modes/prompt-suggestion-controller.ts +1 -1
- package/src/modes/runtime-init.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +18 -0
- package/src/prompts/tools/browser.md +1 -0
- package/src/quick-lane/classify.ts +257 -0
- package/src/rlm/index.ts +1 -1
- package/src/runtime/stt-settings-setup.ts +57 -0
- package/src/sdk/acp/adapter.ts +2 -1
- package/src/sdk/broker/broker.ts +780 -12
- package/src/sdk/broker/lifecycle.ts +70 -29
- package/src/sdk/broker/operation-receipt-ledger.ts +144 -0
- package/src/sdk/broker/session-index.ts +463 -48
- package/src/sdk/broker/state-version.ts +13 -9
- package/src/sdk/broker/transport.ts +14 -1
- package/src/sdk/bus/index.ts +34 -13
- package/src/sdk/bus/kind-aware-reconciliation.ts +21 -2
- package/src/sdk/bus/notification-service.ts +47 -1
- package/src/sdk/bus/prompt-reconciliation.ts +22 -3
- package/src/sdk/bus/telegram-daemon-contract.ts +4 -1
- package/src/sdk/bus/telegram-daemon.ts +54 -3
- package/src/sdk/cli/rows.ts +247 -0
- package/src/sdk/cli/session-cli.ts +930 -79
- package/src/sdk/elevation/allowlist.ts +54 -0
- package/src/sdk/elevation/audit.ts +208 -0
- package/src/sdk/elevation/capability.ts +41 -0
- package/src/sdk/elevation/digest.ts +129 -0
- package/src/sdk/elevation/dispatch-receipt.ts +184 -0
- package/src/sdk/elevation/grant-ledger.ts +1142 -0
- package/src/sdk/elevation/owner.ts +109 -0
- package/src/sdk/guides/cache.ts +363 -0
- package/src/sdk/guides/catalog.ts +554 -0
- package/src/sdk/guides/cli.ts +235 -0
- package/src/sdk/guides/manifest.ts +152 -0
- package/src/sdk/guides/verify.ts +178 -0
- package/src/sdk/host/control/dispatch.ts +49 -1
- package/src/sdk/host/control/index.ts +1 -0
- package/src/sdk/host/control/operations.ts +8 -1
- package/src/sdk/host/query/cursor.ts +47 -3
- package/src/sdk/host/query/handlers.ts +155 -2
- package/src/sdk/host/sdk-surface-parity.test.ts +1 -0
- package/src/sdk/host/session-runtime.test.ts +391 -1
- package/src/sdk/host/session-runtime.ts +308 -73
- package/src/sdk/host/surface-policy.ts +3 -0
- package/src/sdk/host/websocket-transport.ts +7 -1
- package/src/sdk/mcp/server.ts +88 -15
- package/src/sdk/prompt-status.ts +8 -0
- package/src/sdk/protocol/operation-inventory.generated.json +62 -0
- package/src/sdk/protocol/operation-registry.ts +3 -2
- package/src/sdk/session.ts +87 -39
- package/src/sdk/transport/index.ts +1 -0
- package/src/sdk/transport/relay.ts +3 -0
- package/src/sdk/transport/serve-cli.ts +111 -24
- package/src/secrets/obfuscator.ts +92 -12
- package/src/session/agent-session.ts +322 -93
- package/src/session/blob-store.ts +60 -8
- package/src/session/fallback-chain-controller.ts +35 -0
- package/src/session/internal/managed-session-scope.ts +25 -3
- package/src/session/internal/managed-session-storage.ts +574 -73
- package/src/session/internal/session-memory-sidecar.ts +19 -29
- package/src/session/session-manager.ts +2166 -743
- package/src/session/session-storage.ts +715 -48
- package/src/setup/provider-onboarding.ts +19 -3
- package/src/setup/provider-presets.json +3 -1
- package/src/slash-commands/builtin-registry.ts +85 -3
- package/src/stt/downloader.ts +2 -3
- package/src/stt/setup.ts +29 -4
- package/src/task/executor.ts +22 -18
- package/src/task/index.ts +24 -1
- package/src/tools/archive-reader.ts +11 -2
- package/src/tools/browser/runtime-diagnostics.ts +249 -0
- package/src/tools/browser/tab-protocol.ts +4 -0
- package/src/tools/browser/tab-supervisor.ts +5 -0
- package/src/tools/browser/tab-worker.ts +36 -1
- package/src/tools/browser.ts +7 -0
- package/src/tools/image-gen.ts +110 -166
- package/src/tools/index.ts +2 -0
- package/src/tools/read.ts +1 -1
- package/src/tools/resource-gc.ts +6 -1
- package/src/tools/tool-catalog.generated.ts +5 -1
- package/src/utils/pasted-image-path.ts +93 -0
- package/src/web/search/index.ts +3 -3
|
@@ -8,11 +8,14 @@ import type { Api, Model } from "@gajae-code/ai/core";
|
|
|
8
8
|
import { logger } from "@gajae-code/utils";
|
|
9
9
|
import { isModelProfileProviderAvailable, projectModelProfileCatalog } from "../../config/model-profile-contract";
|
|
10
10
|
import { type ModelProfileDefinition, resolveProfileBindings } from "../../config/model-profiles";
|
|
11
|
-
import { resolveModelChainWithAuth } from "../../config/model-resolver";
|
|
12
|
-
import { normalizeModelSelectorValue } from "../../config/model-selector-value";
|
|
11
|
+
import { resolveModelChainWithAuth, splitSelectorThinkingSuffix } from "../../config/model-resolver";
|
|
12
|
+
import { type ModelSelectorValue, normalizeModelSelectorValue } from "../../config/model-selector-value";
|
|
13
13
|
import { type Settings, validateSettingPatch } from "../../config/settings";
|
|
14
14
|
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "../../extensibility/extensions";
|
|
15
15
|
import { parseThinkingLevel } from "../../thinking";
|
|
16
|
+
import { ensureBroker } from "../broker/ensure";
|
|
17
|
+
import { SessionIndex } from "../broker/session-index";
|
|
18
|
+
import { elevationAuthorityPath, verifyElevationCapability } from "../elevation/capability";
|
|
16
19
|
import {
|
|
17
20
|
collectAuthenticatedProfileProviders,
|
|
18
21
|
parseSyntheticModelId,
|
|
@@ -24,9 +27,9 @@ import {
|
|
|
24
27
|
import { projectQ10Models } from "../models.js";
|
|
25
28
|
import { formatPromptFailureForLocalLog, sanitizePromptFailure } from "../prompt-failure";
|
|
26
29
|
import { OPERATIONS } from "../protocol/operation-registry";
|
|
27
|
-
import { type ControlSurface, dispatchControl } from "./control";
|
|
30
|
+
import { type ControlSurface, controlRequestFromFrame, dispatchControl } from "./control";
|
|
28
31
|
import { SessionSdkHost, type SessionSdkHostOptions } from "./host";
|
|
29
|
-
import { CursorRegistry, QueryHandlers, RevisionStore, type SessionSurface } from "./query";
|
|
32
|
+
import { CursorRegistry, QueryHandlers, RevisionStore, type SdkCheckpointRecord, type SessionSurface } from "./query";
|
|
30
33
|
import {
|
|
31
34
|
createSdkCapabilities,
|
|
32
35
|
createSdkSurfacePolicyForContext,
|
|
@@ -217,6 +220,10 @@ export class SessionSdkSessionRuntime {
|
|
|
217
220
|
|
|
218
221
|
/** Narrow extension-facing factory for the SDK-only session path. */
|
|
219
222
|
export interface CreateSdkSessionRuntimeOptions {
|
|
223
|
+
/** Authoritative broker state root for this session's endpoint lifecycle. */
|
|
224
|
+
agentDir: string;
|
|
225
|
+
/** Lifecycle-owned sessions require broker publication before they become usable. */
|
|
226
|
+
brokerRegistrationRequired?: boolean;
|
|
220
227
|
createTransport(input: {
|
|
221
228
|
sessionId: string;
|
|
222
229
|
stateRoot: string;
|
|
@@ -411,7 +418,24 @@ export function createInvocationReconciliation(
|
|
|
411
418
|
async noteTransition(kind, correlation, frame) {
|
|
412
419
|
if (!correlation) return;
|
|
413
420
|
const record = records.get(key(kind, correlation));
|
|
414
|
-
if (!record
|
|
421
|
+
if (!record) return;
|
|
422
|
+
if (record.terminalAt !== undefined) {
|
|
423
|
+
// Same late agent_failed enrichment as the kind-aware bus reconciler: a
|
|
424
|
+
// failure reason may arrive on a different delivery path than the one that
|
|
425
|
+
// claimed the terminal. Enrich the settled record instead of dropping it;
|
|
426
|
+
// never resurrect (status/terminalAt untouched), and first reason wins.
|
|
427
|
+
if (frame.type === "agent_failed" && record.error === undefined) {
|
|
428
|
+
logger.error("SDK invocation failed (late)", {
|
|
429
|
+
kind,
|
|
430
|
+
commandId: correlation.commandId,
|
|
431
|
+
turnId: correlation.turnId,
|
|
432
|
+
error: formatPromptFailureForLocalLog(frame.error),
|
|
433
|
+
});
|
|
434
|
+
record.error = sanitizePromptFailure(frame.error);
|
|
435
|
+
await persist();
|
|
436
|
+
}
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
415
439
|
if (frame.type === "agent_start") {
|
|
416
440
|
record.status = "in_flight";
|
|
417
441
|
record.startedAt = Date.now();
|
|
@@ -466,6 +490,8 @@ export interface SdkSurfaceFactoryOptions {
|
|
|
466
490
|
promptStatusLookup?: (selector: { commandId?: string; turnId?: string; clientRef?: string }) => unknown;
|
|
467
491
|
skillStatusLookup?: (selector: { commandId?: string; turnId?: string; clientRef?: string }) => unknown;
|
|
468
492
|
hostTools?: boolean | (() => boolean);
|
|
493
|
+
/** Q30 event-ring watermark source (the live session event stream); synchronous. */
|
|
494
|
+
getEventWatermark?: () => { generation: number; seq: number };
|
|
469
495
|
}
|
|
470
496
|
|
|
471
497
|
/** Shared policy, capability, and query-surface factory for every SDK transport. */
|
|
@@ -490,6 +516,8 @@ function createQuerySurface(
|
|
|
490
516
|
promptStatusLookup?: (selector: { commandId?: string; turnId?: string; clientRef?: string }) => unknown;
|
|
491
517
|
skillStatusLookup?: (selector: { commandId?: string; turnId?: string; clientRef?: string }) => unknown;
|
|
492
518
|
hostTools?: boolean | (() => boolean);
|
|
519
|
+
/** Q30 event-ring watermark source (the live session event stream); synchronous. */
|
|
520
|
+
getEventWatermark?: () => { generation: number; seq: number };
|
|
493
521
|
} = {},
|
|
494
522
|
): SessionSurface {
|
|
495
523
|
const policy =
|
|
@@ -528,6 +556,63 @@ function createQuerySurface(
|
|
|
528
556
|
}
|
|
529
557
|
return undefined;
|
|
530
558
|
};
|
|
559
|
+
const getProfileCredentialSessionId = () => ctx.credentialSessionId ?? id;
|
|
560
|
+
const resolveProfileAvailability = async (
|
|
561
|
+
profile: ModelProfileDefinition,
|
|
562
|
+
authenticatedProviders: ReadonlySet<string>,
|
|
563
|
+
): Promise<{ available: boolean; defaultModel?: Model<Api> }> => {
|
|
564
|
+
const rewriteSelectorProvider = (selector: string): string => {
|
|
565
|
+
const slash = selector.indexOf("/");
|
|
566
|
+
if (slash < 0) return selector;
|
|
567
|
+
const provider = selector.slice(0, slash);
|
|
568
|
+
if (authenticatedProviders.has(provider)) return selector;
|
|
569
|
+
const group = (profile.alternativeProviderGroups ?? []).find(candidates => candidates.includes(provider));
|
|
570
|
+
if (!group) return selector;
|
|
571
|
+
const replacement = group.find(candidate => authenticatedProviders.has(candidate));
|
|
572
|
+
return replacement ? replacement + selector.slice(slash) : selector;
|
|
573
|
+
};
|
|
574
|
+
try {
|
|
575
|
+
const bindings = resolveProfileBindings(profile);
|
|
576
|
+
const assignments: Array<{ value: ModelSelectorValue; isDefault: boolean }> = [];
|
|
577
|
+
if (bindings.defaultSelector !== undefined) {
|
|
578
|
+
assignments.push({ value: bindings.defaultSelector, isDefault: true });
|
|
579
|
+
}
|
|
580
|
+
for (const value of Object.values(bindings.modelRoles)) assignments.push({ value, isDefault: false });
|
|
581
|
+
for (const value of Object.values(bindings.agentModelOverrides)) assignments.push({ value, isDefault: false });
|
|
582
|
+
let defaultModel: Model<Api> | undefined;
|
|
583
|
+
for (const assignment of assignments) {
|
|
584
|
+
const selectors = normalizeModelSelectorValue(assignment.value).map(rewriteSelectorProvider);
|
|
585
|
+
const hasBareSelector = selectors.some(selector => {
|
|
586
|
+
const suffix = splitSelectorThinkingSuffix(selector);
|
|
587
|
+
const identity = suffix.thinkingLevel ? suffix.selector : selector;
|
|
588
|
+
return !identity.includes("/");
|
|
589
|
+
});
|
|
590
|
+
if (!assignment.isDefault && !hasBareSelector) continue;
|
|
591
|
+
const resolution = await resolveModelChainWithAuth(
|
|
592
|
+
selectors,
|
|
593
|
+
{
|
|
594
|
+
...ctx.modelRegistry,
|
|
595
|
+
getAvailable: () => ctx.modelRegistry.getAvailable(),
|
|
596
|
+
getApiKey: (model: Model<Api>, sessionId?: string) =>
|
|
597
|
+
ctx.modelRegistry.getApiKeyForProvider(model.provider, sessionId, model.baseUrl),
|
|
598
|
+
},
|
|
599
|
+
options.settings,
|
|
600
|
+
getProfileCredentialSessionId(),
|
|
601
|
+
{
|
|
602
|
+
managedFallback: true,
|
|
603
|
+
aliasIntent: "preset-equivalent",
|
|
604
|
+
canonicalSessionId: null,
|
|
605
|
+
credentialSessionId: getProfileCredentialSessionId(),
|
|
606
|
+
},
|
|
607
|
+
);
|
|
608
|
+
if (!resolution.model) return { available: false };
|
|
609
|
+
if (assignment.isDefault) defaultModel = resolution.model;
|
|
610
|
+
}
|
|
611
|
+
return { available: true, defaultModel };
|
|
612
|
+
} catch {
|
|
613
|
+
return { available: false };
|
|
614
|
+
}
|
|
615
|
+
};
|
|
531
616
|
const getDiff = async () => {
|
|
532
617
|
try {
|
|
533
618
|
const { stdout } = await execFileAsync("git", ["diff", "--no-ext-diff"], {
|
|
@@ -620,61 +705,27 @@ function createQuerySurface(
|
|
|
620
705
|
let authenticatedProviders: ReadonlySet<string>;
|
|
621
706
|
try {
|
|
622
707
|
authenticatedProviders = await collectAuthenticatedProfileProviders(profiles, provider =>
|
|
623
|
-
ctx.modelRegistry.getApiKeyForProvider(provider,
|
|
708
|
+
ctx.modelRegistry.getApiKeyForProvider(provider, getProfileCredentialSessionId()),
|
|
624
709
|
);
|
|
625
710
|
} catch {
|
|
626
711
|
// Availability join failed: degrade only the synthetic facade,
|
|
627
712
|
// retain concrete rows and the active marker readback.
|
|
628
713
|
return degraded();
|
|
629
714
|
}
|
|
630
|
-
// Resolve each profile's default model exactly like profile activation:
|
|
631
|
-
// walk the default mapping chain, rewrite alternative-group providers
|
|
632
|
-
// to their authenticated member, and use the same pattern-aware,
|
|
633
|
-
// managed-fallback-eligible resolver so Q10 reports the limits of the
|
|
634
|
-
// model the profile will actually activate (glob defaults such as
|
|
635
|
-
// `provider/gpt-*` and Cursor-managed-fallback skips included).
|
|
636
715
|
const resolvedDefaultModels = new Map<string, Model<Api>>();
|
|
637
|
-
const
|
|
638
|
-
const slash = selector.indexOf("/");
|
|
639
|
-
if (slash < 0) return selector;
|
|
640
|
-
const provider = selector.slice(0, slash);
|
|
641
|
-
if (authenticatedProviders.has(provider)) return selector;
|
|
642
|
-
const group = (profile.alternativeProviderGroups ?? []).find(candidates => candidates.includes(provider));
|
|
643
|
-
if (!group) return selector;
|
|
644
|
-
const replacement = group.find(candidate => authenticatedProviders.has(candidate));
|
|
645
|
-
return replacement ? replacement + selector.slice(slash) : selector;
|
|
646
|
-
};
|
|
716
|
+
const fullyResolvedProfiles = new Set<string>();
|
|
647
717
|
await Promise.all(
|
|
648
718
|
[...profiles.entries()].map(async ([name, profile]) => {
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
rewriteSelectorProvider(selector, profile),
|
|
654
|
-
);
|
|
655
|
-
const resolution = await resolveModelChainWithAuth(
|
|
656
|
-
selectors,
|
|
657
|
-
{
|
|
658
|
-
...ctx.modelRegistry,
|
|
659
|
-
getAvailable: () => ctx.modelRegistry.getAll(),
|
|
660
|
-
getApiKey: (model: Model<Api>, sessionId?: string) =>
|
|
661
|
-
ctx.modelRegistry.getApiKeyForProvider(model.provider, sessionId, model.baseUrl),
|
|
662
|
-
},
|
|
663
|
-
options.settings,
|
|
664
|
-
id,
|
|
665
|
-
{ managedFallback: true },
|
|
666
|
-
);
|
|
667
|
-
if (resolution.model) resolvedDefaultModels.set(name, resolution.model);
|
|
668
|
-
} catch {
|
|
669
|
-
// A provider whose credential state cannot be read must not
|
|
670
|
-
// fail the whole Q10 query: skip this profile's metadata
|
|
671
|
-
// resolution and degrade only its synthetic row.
|
|
672
|
-
}
|
|
719
|
+
const result = await resolveProfileAvailability(profile, authenticatedProviders);
|
|
720
|
+
if (!result.available) return;
|
|
721
|
+
fullyResolvedProfiles.add(name);
|
|
722
|
+
if (result.defaultModel) resolvedDefaultModels.set(name, result.defaultModel);
|
|
673
723
|
}),
|
|
674
724
|
);
|
|
675
725
|
const availableProfileIds = new Set<string>();
|
|
676
726
|
for (const [name, profile] of profiles) {
|
|
677
727
|
if (!isModelProfileProviderAvailable(profile, authenticatedProviders)) continue;
|
|
728
|
+
if (!fullyResolvedProfiles.has(name)) continue;
|
|
678
729
|
// A profile with a default mapping is selectable only when its
|
|
679
730
|
// default chain actually resolves to an authenticated model:
|
|
680
731
|
// activation rejects unresolvable defaults even when the
|
|
@@ -737,12 +788,35 @@ function createQuerySurface(
|
|
|
737
788
|
getModelProfiles: async () => {
|
|
738
789
|
const profiles = ctx.modelRegistry.getModelProfiles();
|
|
739
790
|
const authenticatedProviders = await collectAuthenticatedProfileProviders(profiles, provider =>
|
|
740
|
-
ctx.modelRegistry.getApiKeyForProvider(provider,
|
|
791
|
+
ctx.modelRegistry.getApiKeyForProvider(provider, getProfileCredentialSessionId()),
|
|
741
792
|
);
|
|
742
|
-
return
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
793
|
+
return (await Promise.all(
|
|
794
|
+
projectModelProfileCatalog(profiles, ctx.modelRegistry.getError()).map(async item => ({
|
|
795
|
+
...item,
|
|
796
|
+
available:
|
|
797
|
+
isModelProfileProviderAvailable(profiles.get(item.id)!, authenticatedProviders) &&
|
|
798
|
+
(
|
|
799
|
+
await resolveProfileAvailability(profiles.get(item.id)!, authenticatedProviders)
|
|
800
|
+
).available,
|
|
801
|
+
})),
|
|
802
|
+
)) as unknown[];
|
|
803
|
+
},
|
|
804
|
+
getCheckpointSnapshot: (): { entries: unknown[]; watermark: SdkCheckpointRecord } => {
|
|
805
|
+
// Q30 atomic checkpoint capture (C9): the transcript entries and the
|
|
806
|
+
// event-ring watermark are read in one synchronous call from the host
|
|
807
|
+
// event stream, so the snapshot revision and the subscribe position
|
|
808
|
+
// can never straddle a concurrent append.
|
|
809
|
+
const entries =
|
|
810
|
+
typeof (ctx as Partial<ExtensionContext>).getTranscript === "function" ? ctx.getTranscript() : [];
|
|
811
|
+
const watermark = options.getEventWatermark?.() ?? { generation: 0, seq: 0 };
|
|
812
|
+
return {
|
|
813
|
+
entries,
|
|
814
|
+
watermark: {
|
|
815
|
+
revision: Array.isArray(entries) ? entries.length : 0,
|
|
816
|
+
generation: watermark.generation,
|
|
817
|
+
seq: watermark.seq,
|
|
818
|
+
},
|
|
819
|
+
};
|
|
746
820
|
},
|
|
747
821
|
installedQueries: policy.installedQueries,
|
|
748
822
|
};
|
|
@@ -774,6 +848,7 @@ export function createSdkSurfaceFactory(
|
|
|
774
848
|
promptStatusLookup: options.promptStatusLookup,
|
|
775
849
|
skillStatusLookup: options.skillStatusLookup,
|
|
776
850
|
hostTools: options.hostTools,
|
|
851
|
+
getEventWatermark: options.getEventWatermark,
|
|
777
852
|
});
|
|
778
853
|
return {
|
|
779
854
|
policy,
|
|
@@ -837,6 +912,52 @@ function containsSecretConfigKey(value: unknown, seen = new Set<object>()): bool
|
|
|
837
912
|
containsSecretConfigKey(nested, seen),
|
|
838
913
|
);
|
|
839
914
|
}
|
|
915
|
+
async function resolveSdkWorkflowGate(
|
|
916
|
+
ctx: ExtensionContext,
|
|
917
|
+
operation: "workflow.gate_answer" | "workflow.plan_approve",
|
|
918
|
+
id: string,
|
|
919
|
+
answer: unknown,
|
|
920
|
+
expectedSessionId: string | undefined,
|
|
921
|
+
idempotencyKey: string,
|
|
922
|
+
canResolve: () => boolean,
|
|
923
|
+
): Promise<unknown> {
|
|
924
|
+
if (!canResolve())
|
|
925
|
+
throw Object.assign(new Error("Workflow gate is no longer answerable."), { code: "resource_gone" });
|
|
926
|
+
if (expectedSessionId !== undefined && expectedSessionId !== ctx.sessionManager.getSessionId())
|
|
927
|
+
throw Object.assign(new Error("Workflow gate session does not match this endpoint."), { code: "resource_gone" });
|
|
928
|
+
if (expectedSessionId === undefined) logger.warn("workflow_control_missing_expected_session_id", { operation });
|
|
929
|
+
const workflowGate = ctx.workflowGate;
|
|
930
|
+
if (
|
|
931
|
+
typeof workflowGate?.resolveGate !== "function" ||
|
|
932
|
+
typeof workflowGate.recoverAcceptedGates !== "function" ||
|
|
933
|
+
typeof workflowGate.lookupCompletedResolution !== "function" ||
|
|
934
|
+
typeof workflowGate.prepareTerminalization !== "function" ||
|
|
935
|
+
typeof workflowGate.clearPreparedTerminalization !== "function"
|
|
936
|
+
)
|
|
937
|
+
throw Object.assign(new Error("Workflow gates are unavailable for this session."), { code: "resource_gone" });
|
|
938
|
+
const response = { gate_id: id, answer, idempotency_key: idempotencyKey };
|
|
939
|
+
const completed = workflowGate.lookupCompletedResolution(response);
|
|
940
|
+
if (completed.kind === "completed") return completed.resolution;
|
|
941
|
+
if (completed.kind === "accepted_incomplete") {
|
|
942
|
+
await workflowGate.recoverAcceptedGates();
|
|
943
|
+
const recovered = workflowGate.lookupCompletedResolution(response);
|
|
944
|
+
if (recovered.kind === "completed") return recovered.resolution;
|
|
945
|
+
throw Object.assign(new Error("Workflow gate resolution outcome is uncertain."), { code: "terminal_uncertain" });
|
|
946
|
+
}
|
|
947
|
+
if (!workflowGate.prepareTerminalization(id, "not_published"))
|
|
948
|
+
throw Object.assign(new Error("Workflow gate is no longer answerable."), { code: "resource_gone" });
|
|
949
|
+
try {
|
|
950
|
+
const resolution = await workflowGate.resolveGate(response);
|
|
951
|
+
if ((resolution as { status?: unknown }).status === "rejected") workflowGate.clearPreparedTerminalization(id);
|
|
952
|
+
return resolution;
|
|
953
|
+
} catch (error) {
|
|
954
|
+
const stillPending = workflowGate.listPendingGates?.().some(gate => gate.gate_id === id) === true;
|
|
955
|
+
if (stillPending) workflowGate.clearPreparedTerminalization(id);
|
|
956
|
+
else workflowGate.quarantineGate?.(id);
|
|
957
|
+
throw error;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
840
961
|
function createControlSurface(
|
|
841
962
|
ctx: ExtensionContext,
|
|
842
963
|
api: ExtensionAPI,
|
|
@@ -846,6 +967,9 @@ function createControlSurface(
|
|
|
846
967
|
settings?: Settings,
|
|
847
968
|
configOverrides?: Map<string, unknown>,
|
|
848
969
|
configRevision: { current: number } = { current: 0 },
|
|
970
|
+
elevationAuthorityToken?: string,
|
|
971
|
+
canResolveGate: () => boolean = () => true,
|
|
972
|
+
trackGateResolution: <T>(resolution: Promise<T>) => Promise<T> = async resolution => await resolution,
|
|
849
973
|
): ControlSurface {
|
|
850
974
|
const surfacePolicy =
|
|
851
975
|
policy ?? createSdkSurfacePolicyForContext(ctx, hasSdkWorkflowGateCapability(ctx.workflowGate));
|
|
@@ -942,7 +1066,7 @@ function createControlSurface(
|
|
|
942
1066
|
try {
|
|
943
1067
|
const submission = Promise.resolve(
|
|
944
1068
|
run({
|
|
945
|
-
onPreflightAccepted: () => void accept().catch(
|
|
1069
|
+
onPreflightAccepted: () => void accept().catch(error => preflight.reject(error)),
|
|
946
1070
|
onPreflightAcceptCommit: accept,
|
|
947
1071
|
}),
|
|
948
1072
|
);
|
|
@@ -953,7 +1077,7 @@ function createControlSurface(
|
|
|
953
1077
|
return;
|
|
954
1078
|
}
|
|
955
1079
|
if (allowCompletionFallback) {
|
|
956
|
-
void accept().catch(
|
|
1080
|
+
void accept().catch(error => preflight.reject(error));
|
|
957
1081
|
return;
|
|
958
1082
|
}
|
|
959
1083
|
settled = true;
|
|
@@ -965,8 +1089,11 @@ function createControlSurface(
|
|
|
965
1089
|
},
|
|
966
1090
|
error => {
|
|
967
1091
|
if (settled) {
|
|
968
|
-
|
|
969
|
-
|
|
1092
|
+
// The submission promise rejects after preflight acceptance only when the
|
|
1093
|
+
// work itself is over (provider stream interrupt, abort, queue failure).
|
|
1094
|
+
// Every kind must terminalize here or the record stays non-terminal
|
|
1095
|
+
// forever; `noteTransition` ignores an already-terminal record.
|
|
1096
|
+
void reconciliation.noteTransition(kind, correlation, { type: "agent_failed", error });
|
|
970
1097
|
return;
|
|
971
1098
|
}
|
|
972
1099
|
settled = true;
|
|
@@ -986,6 +1113,9 @@ function createControlSurface(
|
|
|
986
1113
|
}
|
|
987
1114
|
};
|
|
988
1115
|
return {
|
|
1116
|
+
authorizeElevationClaim: (sdkId, input, capability) =>
|
|
1117
|
+
elevationAuthorityToken !== undefined &&
|
|
1118
|
+
verifyElevationCapability(elevationAuthorityToken, capability, sdkId, input),
|
|
989
1119
|
prompt: async (text, images, clientRef) =>
|
|
990
1120
|
submit("prompt", clientRef, options =>
|
|
991
1121
|
api.sendUserMessage(
|
|
@@ -1008,8 +1138,22 @@ function createControlSurface(
|
|
|
1008
1138
|
return await submit("prompt", undefined, options => api.sendUserMessage(text, options));
|
|
1009
1139
|
},
|
|
1010
1140
|
answerAsk: unavailable("ask.answer"),
|
|
1011
|
-
answerGate:
|
|
1012
|
-
|
|
1141
|
+
answerGate: async (id, response, expectedSessionId, idempotencyKey) =>
|
|
1142
|
+
await trackGateResolution(
|
|
1143
|
+
resolveSdkWorkflowGate(
|
|
1144
|
+
ctx,
|
|
1145
|
+
"workflow.gate_answer",
|
|
1146
|
+
id,
|
|
1147
|
+
response,
|
|
1148
|
+
expectedSessionId,
|
|
1149
|
+
idempotencyKey ?? id,
|
|
1150
|
+
canResolveGate,
|
|
1151
|
+
),
|
|
1152
|
+
),
|
|
1153
|
+
approvePlan: async (id, choice, expectedSessionId) =>
|
|
1154
|
+
await trackGateResolution(
|
|
1155
|
+
resolveSdkWorkflowGate(ctx, "workflow.plan_approve", id, choice, expectedSessionId, id, canResolveGate),
|
|
1156
|
+
),
|
|
1013
1157
|
invokeSkill: async (name, args, clientRef) => {
|
|
1014
1158
|
if (!ctx.invokeSkill) return unavailable("skill.invoke")();
|
|
1015
1159
|
if (args !== undefined && typeof args !== "string")
|
|
@@ -1191,6 +1335,9 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
1191
1335
|
cursors: CursorRegistry;
|
|
1192
1336
|
reconciliation: InvocationReconciliation;
|
|
1193
1337
|
pending: Array<{ kind: InvocationKind; correlation: InvocationCorrelation }>;
|
|
1338
|
+
registerBroker: () => Promise<void>;
|
|
1339
|
+
fenceGateResolutions: () => void;
|
|
1340
|
+
waitForGateResolutionQuiescence: () => Promise<void>;
|
|
1194
1341
|
activeInvocation?: { kind: InvocationKind; correlation: InvocationCorrelation };
|
|
1195
1342
|
disposeGate?: () => void;
|
|
1196
1343
|
}
|
|
@@ -1209,9 +1356,12 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
1209
1356
|
};
|
|
1210
1357
|
api.on("agent_start", async (_event, ctx) => await emitLifecycle("agent_start", ctx));
|
|
1211
1358
|
api.on("agent_end", async (_event, ctx) => await emitLifecycle("agent_end", ctx));
|
|
1212
|
-
api.on("turn_start", (_event, ctx) =>
|
|
1213
|
-
|
|
1214
|
-
|
|
1359
|
+
api.on("turn_start", async (_event, ctx) => {
|
|
1360
|
+
const current = active;
|
|
1361
|
+
if (!current) return;
|
|
1362
|
+
await current.registerBroker();
|
|
1363
|
+
current.runtime.emitEvent({ type: "turn_start", sessionId: ctx.sessionManager.getSessionId() });
|
|
1364
|
+
});
|
|
1215
1365
|
api.on("turn_end", (_event, ctx) =>
|
|
1216
1366
|
active?.runtime.emitEvent({ type: "turn_end", sessionId: ctx.sessionManager.getSessionId() }),
|
|
1217
1367
|
);
|
|
@@ -1227,6 +1377,11 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
1227
1377
|
const sessionId = ctx.sessionManager.getSessionId();
|
|
1228
1378
|
const stateRoot = path.join(ctx.cwd, ".gjc", "state");
|
|
1229
1379
|
const token = crypto.randomBytes(24).toString("base64url");
|
|
1380
|
+
const elevationAuthorityToken = crypto.randomBytes(32).toString("base64url");
|
|
1381
|
+
const elevationAuthorityFile = elevationAuthorityPath(stateRoot, sessionId);
|
|
1382
|
+
await fs.mkdir(path.dirname(elevationAuthorityFile), { recursive: true, mode: 0o700 });
|
|
1383
|
+
await Bun.write(elevationAuthorityFile, `${JSON.stringify({ version: 1, token: elevationAuthorityToken })}\n`);
|
|
1384
|
+
await fs.chmod(elevationAuthorityFile, 0o600);
|
|
1230
1385
|
const transport = await options.createTransport({ sessionId, stateRoot, token });
|
|
1231
1386
|
const revisions = new RevisionStore(sessionId, Date.now, { storageDir: stateRoot });
|
|
1232
1387
|
const cursors = new CursorRegistry(token, revisions);
|
|
@@ -1234,6 +1389,25 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
1234
1389
|
await reconciliation.hydrate();
|
|
1235
1390
|
const pending: Array<{ kind: InvocationKind; correlation: InvocationCorrelation }> = [];
|
|
1236
1391
|
const configRevision = { current: 0 };
|
|
1392
|
+
// The event-ring watermark lives on the runtime host, which is constructed
|
|
1393
|
+
// after the surface factory; the lazy source indirection lets the surface
|
|
1394
|
+
// read the live generation/seq once the runtime exists (Q30 atomic
|
|
1395
|
+
// capture, C9).
|
|
1396
|
+
let eventWatermarkSource: () => { generation: number; seq: number } = () => ({ generation: 0, seq: 0 });
|
|
1397
|
+
let acceptingGateResolutions = true;
|
|
1398
|
+
const inFlightGateResolutions = new Set<Promise<unknown>>();
|
|
1399
|
+
const trackGateResolution = <T>(resolution: Promise<T>): Promise<T> => {
|
|
1400
|
+
const tracked = resolution.finally(() => inFlightGateResolutions.delete(tracked));
|
|
1401
|
+
inFlightGateResolutions.add(tracked);
|
|
1402
|
+
return tracked;
|
|
1403
|
+
};
|
|
1404
|
+
const waitForGateResolutionQuiescence = async (): Promise<void> => {
|
|
1405
|
+
const settled = Promise.allSettled(inFlightGateResolutions);
|
|
1406
|
+
const timeout = Bun.sleep(5_000).then(() => {
|
|
1407
|
+
throw new Error("Timed out waiting for SDK workflow gate resolutions to settle.");
|
|
1408
|
+
});
|
|
1409
|
+
await Promise.race([settled, timeout]);
|
|
1410
|
+
};
|
|
1237
1411
|
const surfaceFactory = createSdkSurfaceFactory({
|
|
1238
1412
|
ctx,
|
|
1239
1413
|
id: sessionId,
|
|
@@ -1243,6 +1417,7 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
1243
1417
|
skillStatusLookup: selector => reconciliation.lookup("skill", selector),
|
|
1244
1418
|
configOverrides: options.configOverrides,
|
|
1245
1419
|
settings: options.settings,
|
|
1420
|
+
getEventWatermark: () => eventWatermarkSource(),
|
|
1246
1421
|
});
|
|
1247
1422
|
const queryHandlers = new QueryHandlers(surfaceFactory.query, sessionId, revisions, cursors);
|
|
1248
1423
|
const controlSurface = createControlSurface(
|
|
@@ -1256,6 +1431,9 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
1256
1431
|
options.settings,
|
|
1257
1432
|
options.configOverrides,
|
|
1258
1433
|
configRevision,
|
|
1434
|
+
elevationAuthorityToken,
|
|
1435
|
+
() => acceptingGateResolutions,
|
|
1436
|
+
trackGateResolution,
|
|
1259
1437
|
);
|
|
1260
1438
|
let runtime: SessionSdkSessionRuntime;
|
|
1261
1439
|
const installProviderDefinitions = (capability: string, definitions: unknown): void => {
|
|
@@ -1325,22 +1503,23 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
1325
1503
|
runtime = new SessionSdkSessionRuntime({
|
|
1326
1504
|
transport,
|
|
1327
1505
|
control: async (_connectionId, frame) => {
|
|
1328
|
-
const request = frame as Record<string, unknown
|
|
1506
|
+
const request = controlRequestFromFrame(frame as Record<string, unknown>);
|
|
1329
1507
|
return dispatchControl(
|
|
1330
1508
|
controlSurface,
|
|
1331
1509
|
OPERATIONS.find(operation => operation.kind === "control" && operation.sdkId === request.operation),
|
|
1332
|
-
|
|
1333
|
-
id: typeof request.id === "string" ? request.id : "",
|
|
1334
|
-
operation: typeof request.operation === "string" ? request.operation : "",
|
|
1335
|
-
input: request.input,
|
|
1336
|
-
expectedRevision: typeof request.expectedRevision === "string" ? request.expectedRevision : undefined,
|
|
1337
|
-
idempotencyKey: typeof request.idempotencyKey === "string" ? request.idempotencyKey : undefined,
|
|
1338
|
-
confirm: request.confirm === true,
|
|
1339
|
-
},
|
|
1510
|
+
request,
|
|
1340
1511
|
);
|
|
1341
1512
|
},
|
|
1342
1513
|
query: async (connectionId, frame) => {
|
|
1343
1514
|
const request = frame as Record<string, unknown>;
|
|
1515
|
+
// D1: a non-string top-level cursor is rejected, never silently
|
|
1516
|
+
// dropped into a fresh-snapshot query.
|
|
1517
|
+
if (request.cursor !== undefined && typeof request.cursor !== "string")
|
|
1518
|
+
return {
|
|
1519
|
+
id: typeof request.id === "string" ? request.id : undefined,
|
|
1520
|
+
ok: false,
|
|
1521
|
+
error: { code: "invalid_input", message: "cursor must be a non-empty string" },
|
|
1522
|
+
};
|
|
1344
1523
|
return queryHandlers.dispatch({
|
|
1345
1524
|
id: typeof request.id === "string" ? request.id : undefined,
|
|
1346
1525
|
query: typeof request.query === "string" ? request.query : "",
|
|
@@ -1359,12 +1538,54 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
1359
1538
|
if (request.operation === "session.close" && response.ok === true) ctx.shutdown();
|
|
1360
1539
|
},
|
|
1361
1540
|
});
|
|
1541
|
+
// The runtime host now owns the live event stream; point the surface's
|
|
1542
|
+
// checkpoint watermark at the real generation/seq (C9).
|
|
1543
|
+
eventWatermarkSource = () => ({
|
|
1544
|
+
generation: runtime.host.events.generation,
|
|
1545
|
+
seq: runtime.host.events.sequence,
|
|
1546
|
+
});
|
|
1362
1547
|
const disposeGate = ctx.workflowGate?.onGateEmitted?.(gate =>
|
|
1363
1548
|
runtime.emitEvent({ kind: "workflow_gate", payload: gate }),
|
|
1364
1549
|
);
|
|
1365
|
-
|
|
1550
|
+
let brokerRegistered = false;
|
|
1551
|
+
const registerBroker = async (): Promise<void> => {
|
|
1552
|
+
if (brokerRegistered) return;
|
|
1553
|
+
try {
|
|
1554
|
+
await ensureBroker({ agentDir: options.agentDir });
|
|
1555
|
+
const index = await new SessionIndex(options.agentDir).open();
|
|
1556
|
+
const locator = { repo: path.resolve(ctx.cwd), stateRoot };
|
|
1557
|
+
await runtime.registerWithBroker({
|
|
1558
|
+
register: async input => {
|
|
1559
|
+
const endpointMtimeMs = (await fs.stat(path.join(input.stateRoot, "sdk", `${input.sessionId}.json`)))
|
|
1560
|
+
.mtimeMs;
|
|
1561
|
+
await index.append({ type: "host_registered", ...input, locator, pid: process.pid, endpointMtimeMs });
|
|
1562
|
+
},
|
|
1563
|
+
unregister: async input => {
|
|
1564
|
+
await index.append({ type: "host_unregistered", ...input, locator, pid: process.pid });
|
|
1565
|
+
},
|
|
1566
|
+
});
|
|
1567
|
+
brokerRegistered = true;
|
|
1568
|
+
} catch (error) {
|
|
1569
|
+
if (options.brokerRegistrationRequired) throw error;
|
|
1570
|
+
logger.warn(`sdk broker registration unavailable: ${String(error)}`);
|
|
1571
|
+
}
|
|
1572
|
+
};
|
|
1573
|
+
active = {
|
|
1574
|
+
runtime,
|
|
1575
|
+
revisions,
|
|
1576
|
+
cursors,
|
|
1577
|
+
reconciliation,
|
|
1578
|
+
pending,
|
|
1579
|
+
registerBroker,
|
|
1580
|
+
fenceGateResolutions: () => {
|
|
1581
|
+
acceptingGateResolutions = false;
|
|
1582
|
+
},
|
|
1583
|
+
waitForGateResolutionQuiescence,
|
|
1584
|
+
disposeGate,
|
|
1585
|
+
};
|
|
1366
1586
|
try {
|
|
1367
1587
|
await runtime.start();
|
|
1588
|
+
await registerBroker();
|
|
1368
1589
|
} catch (error) {
|
|
1369
1590
|
active = undefined;
|
|
1370
1591
|
disposeGate?.();
|
|
@@ -1375,7 +1596,19 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
1375
1596
|
code: errorCode(cleanupError),
|
|
1376
1597
|
error: String(cleanupError),
|
|
1377
1598
|
});
|
|
1378
|
-
active = {
|
|
1599
|
+
active = {
|
|
1600
|
+
runtime,
|
|
1601
|
+
revisions,
|
|
1602
|
+
cursors,
|
|
1603
|
+
reconciliation,
|
|
1604
|
+
pending,
|
|
1605
|
+
registerBroker,
|
|
1606
|
+
fenceGateResolutions: () => {
|
|
1607
|
+
acceptingGateResolutions = false;
|
|
1608
|
+
},
|
|
1609
|
+
waitForGateResolutionQuiescence,
|
|
1610
|
+
disposeGate,
|
|
1611
|
+
};
|
|
1379
1612
|
throw new AggregateError([error, cleanupError], "SDK runtime startup failed and cleanup failed.");
|
|
1380
1613
|
}
|
|
1381
1614
|
cursors.close();
|
|
@@ -1385,10 +1618,12 @@ export function createSdkSessionRuntimeExtension(api: ExtensionAPI, options: Cre
|
|
|
1385
1618
|
};
|
|
1386
1619
|
const stopActive = async (): Promise<void> => {
|
|
1387
1620
|
const current = active;
|
|
1388
|
-
active = undefined;
|
|
1389
1621
|
if (!current) return;
|
|
1390
|
-
current.
|
|
1622
|
+
current.fenceGateResolutions();
|
|
1391
1623
|
try {
|
|
1624
|
+
await current.waitForGateResolutionQuiescence();
|
|
1625
|
+
active = undefined;
|
|
1626
|
+
current.disposeGate?.();
|
|
1392
1627
|
await current.runtime.stop();
|
|
1393
1628
|
} catch (error) {
|
|
1394
1629
|
logger.error("sdk runtime stop failed", { code: errorCode(error), error: String(error) });
|
|
@@ -58,6 +58,9 @@ const CONTROL_BINDINGS: Readonly<Record<string, string | undefined>> = {
|
|
|
58
58
|
|
|
59
59
|
// Resource queries (`artifact.read`, `runtime.jobs.list`) remain dispatchable when their
|
|
60
60
|
// backing session resource is absent so their handlers can return `resource_gone`.
|
|
61
|
+
// `session.checkpoint` (Q30) is likewise unconditionally installed: it is an SDK-native
|
|
62
|
+
// replay authority that degrades to the live transcript head when the host publishes no
|
|
63
|
+
// durable checkpoint, so it must never be hidden behind a binding gate.
|
|
61
64
|
const QUERY_BINDINGS: Readonly<Record<string, string | undefined>> = {
|
|
62
65
|
"skill.list/state": "getSkillState",
|
|
63
66
|
"config.list/get": "getConfigItems",
|
|
@@ -198,7 +198,13 @@ export async function createSdkWebSocketTransport(
|
|
|
198
198
|
try {
|
|
199
199
|
await filesystem.writeFile(
|
|
200
200
|
endpointFile,
|
|
201
|
-
JSON.stringify({
|
|
201
|
+
JSON.stringify({
|
|
202
|
+
version: 1,
|
|
203
|
+
sessionId: input.sessionId,
|
|
204
|
+
url,
|
|
205
|
+
token: input.token,
|
|
206
|
+
pid: process.pid,
|
|
207
|
+
}),
|
|
202
208
|
"utf8",
|
|
203
209
|
);
|
|
204
210
|
} catch (error) {
|