@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
|
@@ -6,6 +6,7 @@ export type ControlInput = Record<string, unknown>;
|
|
|
6
6
|
* AgentSession and its controllers without exposing those concrete types here.
|
|
7
7
|
*/
|
|
8
8
|
export interface ControlSurface {
|
|
9
|
+
authorizeElevationClaim?(sdkId: string, input: ControlInput, capability: string): boolean;
|
|
9
10
|
prompt(text: string, images?: ControlValue, clientRef?: string): Promise<ControlValue> | ControlValue;
|
|
10
11
|
steer(text: string): Promise<ControlValue> | ControlValue;
|
|
11
12
|
followUp(text: string): Promise<ControlValue> | ControlValue;
|
|
@@ -17,8 +18,14 @@ export interface ControlSurface {
|
|
|
17
18
|
response: ControlValue,
|
|
18
19
|
expectedSessionId?: string,
|
|
19
20
|
idempotencyKey?: string,
|
|
21
|
+
elevationRequestId?: string,
|
|
22
|
+
): Promise<ControlValue> | ControlValue;
|
|
23
|
+
approvePlan(
|
|
24
|
+
id: string,
|
|
25
|
+
choice: ControlValue,
|
|
26
|
+
expectedSessionId?: string,
|
|
27
|
+
elevationRequestId?: string,
|
|
20
28
|
): Promise<ControlValue> | ControlValue;
|
|
21
|
-
approvePlan(id: string, choice: ControlValue, expectedSessionId?: string): Promise<ControlValue> | ControlValue;
|
|
22
29
|
invokeSkill(name: string, args: ControlValue, clientRef?: string): Promise<ControlValue> | ControlValue;
|
|
23
30
|
setPlanMode(on: boolean): Promise<ControlValue> | ControlValue;
|
|
24
31
|
operateGoal(op: string, objective?: string): Promise<ControlValue> | ControlValue;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
1
|
+
import { createHmac, randomUUID, timingSafeEqual } from "node:crypto";
|
|
2
2
|
import { MAX_PINNED_REVISIONS, type RevisionStore, RevisionStoreError, SNAPSHOT_TTL_MS } from "./revision-store.js";
|
|
3
3
|
|
|
4
4
|
export const CURSOR_TTL_MS = SNAPSHOT_TTL_MS;
|
|
@@ -10,8 +10,14 @@ export interface CursorEnvelope {
|
|
|
10
10
|
protocolMajor: 3;
|
|
11
11
|
sessionId: string;
|
|
12
12
|
resource: string;
|
|
13
|
+
/** Unique per-grant identity so identical envelopes never alias into one pin. */
|
|
14
|
+
nonce?: string;
|
|
13
15
|
revision: string;
|
|
14
16
|
highWatermark?: unknown;
|
|
17
|
+
/** TTL metadata minted by `CursorRegistry.grant`: wall-clock ms when granted. */
|
|
18
|
+
issuedAt?: number;
|
|
19
|
+
/** TTL metadata minted by `CursorRegistry.grant`: wall-clock ms when the cursor expires. */
|
|
20
|
+
expiresAt?: number;
|
|
15
21
|
position: unknown;
|
|
16
22
|
direction: string;
|
|
17
23
|
pageShape: unknown;
|
|
@@ -139,6 +145,12 @@ export class CursorRegistry {
|
|
|
139
145
|
MAX_CURSORS_PER_CONNECTION
|
|
140
146
|
)
|
|
141
147
|
throw new CursorError("snapshot_capacity_exceeded", false);
|
|
148
|
+
// Mint the authoritative TTL inside the registry so the signed envelope
|
|
149
|
+
// and the active-entry expiry can never diverge; callers read
|
|
150
|
+
// `issuedAt`/`expiresAt` back from the envelope they passed in.
|
|
151
|
+
const issuedAt = this.now();
|
|
152
|
+
envelope.issuedAt = issuedAt;
|
|
153
|
+
envelope.expiresAt = issuedAt + CURSOR_TTL_MS;
|
|
142
154
|
const cursor = signCursor(envelope, this.sessionToken);
|
|
143
155
|
try {
|
|
144
156
|
await this.revisions.pin(cursor, resourceKind, resourceId, envelope.revision);
|
|
@@ -146,15 +158,47 @@ export class CursorRegistry {
|
|
|
146
158
|
if (error instanceof RevisionStoreError) throw new CursorError(error.code, false);
|
|
147
159
|
throw error;
|
|
148
160
|
}
|
|
149
|
-
this.#active.set(cursor, { connectionId, resourceId, expiresAt:
|
|
161
|
+
this.#active.set(cursor, { connectionId, resourceId, expiresAt: envelope.expiresAt });
|
|
150
162
|
return cursor;
|
|
151
163
|
}
|
|
152
164
|
|
|
165
|
+
async exchange(
|
|
166
|
+
cursor: string,
|
|
167
|
+
connectionId: string,
|
|
168
|
+
expected: CursorQueryContext,
|
|
169
|
+
resourceKind: string,
|
|
170
|
+
resourceId: string,
|
|
171
|
+
): Promise<{ cursor: string; envelope: CursorEnvelope }> {
|
|
172
|
+
const envelope = verifyCursor(cursor, this.sessionToken);
|
|
173
|
+
if (!envelope) throw new CursorError("invalid_cursor", false);
|
|
174
|
+
if (envelope.expiresAt === undefined || envelope.expiresAt <= this.now()) throw new CursorError("cursor_expired");
|
|
175
|
+
if (
|
|
176
|
+
envelope.sessionId !== expected.sessionId ||
|
|
177
|
+
(expected.resource !== undefined && envelope.resource !== expected.resource) ||
|
|
178
|
+
envelope.direction !== expected.direction ||
|
|
179
|
+
canonicalJson(envelope.pageShape) !== canonicalJson(expected.pageShape)
|
|
180
|
+
)
|
|
181
|
+
throw new CursorError("invalid_input", false, "cursor does not match query");
|
|
182
|
+
const exchanged: CursorEnvelope = {
|
|
183
|
+
...envelope,
|
|
184
|
+
nonce: randomUUID(),
|
|
185
|
+
issuedAt: undefined,
|
|
186
|
+
expiresAt: undefined,
|
|
187
|
+
};
|
|
188
|
+
const exchangedCursor = await this.grant(connectionId, exchanged, resourceKind, resourceId);
|
|
189
|
+
return { cursor: exchangedCursor, envelope: exchanged };
|
|
190
|
+
}
|
|
191
|
+
|
|
153
192
|
consume(cursor: string, connectionId: string, expected: CursorQueryContext): CursorEnvelope {
|
|
154
193
|
const envelope = verifyCursor(cursor, this.sessionToken);
|
|
155
194
|
if (!envelope) throw new CursorError("invalid_cursor", false);
|
|
156
195
|
const active = this.#active.get(cursor);
|
|
157
|
-
|
|
196
|
+
const expired =
|
|
197
|
+
!active ||
|
|
198
|
+
active.connectionId !== connectionId ||
|
|
199
|
+
active.expiresAt <= this.now() ||
|
|
200
|
+
(envelope.expiresAt !== undefined && envelope.expiresAt <= this.now());
|
|
201
|
+
if (expired) {
|
|
158
202
|
this.release(cursor);
|
|
159
203
|
throw new CursorError("cursor_expired");
|
|
160
204
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import { PROMPT_CLIENT_REF_MAX_LENGTH } from "../../prompt-status.js";
|
|
2
3
|
import type { ActiveProviderDescriptor } from "../../providers.js";
|
|
3
4
|
import { ActiveProviderResolutionError } from "../../providers.js";
|
|
@@ -56,6 +57,12 @@ export interface SessionSurface {
|
|
|
56
57
|
}): unknown | Promise<unknown>;
|
|
57
58
|
/** Q27 effective model-profile catalog from the live session registry. */
|
|
58
59
|
getModelProfiles?(): unknown[] | Promise<unknown[]>;
|
|
60
|
+
/**
|
|
61
|
+
* Q30 atomic checkpoint capture: the live transcript entries and the
|
|
62
|
+
* event-ring watermark read in ONE synchronous call, so the snapshot
|
|
63
|
+
* revision and the subscribe position can never straddle an append.
|
|
64
|
+
*/
|
|
65
|
+
getCheckpointSnapshot?(): { entries: unknown[]; watermark: SdkCheckpointRecord };
|
|
59
66
|
/** Query rows backed by the session's installed binding map. */
|
|
60
67
|
installedQueries?: ReadonlySet<string>;
|
|
61
68
|
}
|
|
@@ -81,7 +88,33 @@ export interface QueryResponse {
|
|
|
81
88
|
result?: unknown;
|
|
82
89
|
error?: { code: string; message: string; restartQuery?: boolean; details?: unknown };
|
|
83
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Host-published durable transcript checkpoint (C9). `revision` is the
|
|
93
|
+
* transcript high-watermark (entry count/offset) at checkpoint time and bounds
|
|
94
|
+
* replay to exactly `[checkpointRevision, live)`; `generation`/`seq` anchor the
|
|
95
|
+
* event-ring subscribe position carried by replay cursors.
|
|
96
|
+
*/
|
|
97
|
+
export interface SdkCheckpointRecord {
|
|
98
|
+
revision: number;
|
|
99
|
+
generation: number;
|
|
100
|
+
seq: number;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function isNonNegativeSafeInteger(value: unknown): value is number {
|
|
104
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
105
|
+
}
|
|
84
106
|
|
|
107
|
+
export function isCheckpointRecord(value: unknown): value is SdkCheckpointRecord {
|
|
108
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
109
|
+
const record = value as Record<string, unknown>;
|
|
110
|
+
const keys = Object.keys(record);
|
|
111
|
+
return (
|
|
112
|
+
keys.length === 3 &&
|
|
113
|
+
isNonNegativeSafeInteger(record.revision) &&
|
|
114
|
+
isNonNegativeSafeInteger(record.generation) &&
|
|
115
|
+
isNonNegativeSafeInteger(record.seq)
|
|
116
|
+
);
|
|
117
|
+
}
|
|
85
118
|
const sources: Record<string, { resource: string; method: keyof SessionSurface; mvcc: boolean }> = {
|
|
86
119
|
Q01: { resource: "transcript", method: "getTranscriptEntries", mvcc: true },
|
|
87
120
|
Q03: { resource: "context", method: "getContextSnapshot", mvcc: false },
|
|
@@ -138,6 +171,7 @@ const names = [
|
|
|
138
171
|
"models.profiles.list",
|
|
139
172
|
"skill.invoke_status",
|
|
140
173
|
"providers.list/active",
|
|
174
|
+
"session.checkpoint",
|
|
141
175
|
];
|
|
142
176
|
|
|
143
177
|
export class QueryHandlers {
|
|
@@ -164,11 +198,32 @@ export class QueryHandlers {
|
|
|
164
198
|
false,
|
|
165
199
|
`${request.query} is not installed for this session.`,
|
|
166
200
|
);
|
|
201
|
+
// D1 strict raw checkpoint/cursor validation (approved authority contract):
|
|
202
|
+
// `checkpointToken` is a signed checkpoint cursor accepted ONLY on Q01
|
|
203
|
+
// (transcript.list), is mutually exclusive with a top-level cursor, and
|
|
204
|
+
// empty cursors are rejected instead of silently dropped.
|
|
205
|
+
if (request.cursor !== undefined && request.cursor === "")
|
|
206
|
+
return this.#error(request, "invalid_input", false, "cursor must be a non-empty string");
|
|
207
|
+
const checkpointToken = request.input?.checkpointToken;
|
|
208
|
+
if (checkpointToken !== undefined) {
|
|
209
|
+
if (typeof checkpointToken !== "string" || checkpointToken.trim() === "")
|
|
210
|
+
return this.#error(request, "invalid_input", false, "checkpointToken must be a non-empty string");
|
|
211
|
+
if (query !== "Q01" && query !== "Q30")
|
|
212
|
+
return this.#error(
|
|
213
|
+
request,
|
|
214
|
+
"invalid_input",
|
|
215
|
+
false,
|
|
216
|
+
"checkpointToken is only supported on transcript.list and session.checkpoint",
|
|
217
|
+
);
|
|
218
|
+
if (request.cursor !== undefined)
|
|
219
|
+
return this.#error(request, "invalid_input", false, "checkpointToken and cursor are mutually exclusive");
|
|
220
|
+
}
|
|
167
221
|
if (query === "Q02") return await this.#transcriptBody(request);
|
|
168
222
|
if (query === "Q23") return await this.#resourceBody(request);
|
|
169
223
|
if (query === "Q26") return await this.#promptStatus(request);
|
|
170
224
|
if (query === "Q28") return await this.#skillInvokeStatus(request);
|
|
171
225
|
if (query === "Q24") return await this.#artifact(request);
|
|
226
|
+
if (query === "Q30") return await this.#checkpoint(request);
|
|
172
227
|
if (query === "Q27" && request.input && Object.keys(request.input).length > 0)
|
|
173
228
|
return this.#error(request, "invalid_request", false, "models.profiles.list does not accept input fields.");
|
|
174
229
|
if (query === "Q27" && typeof this.surface.getModelProfiles !== "function")
|
|
@@ -203,8 +258,16 @@ export class QueryHandlers {
|
|
|
203
258
|
let position = 0;
|
|
204
259
|
let byteOffset = 0;
|
|
205
260
|
let snapshot: unknown;
|
|
206
|
-
|
|
207
|
-
|
|
261
|
+
// The cursor may arrive either as the top-level continuation cursor or,
|
|
262
|
+
// on Q01 only (validated in `dispatch`), as the `checkpointToken` resume
|
|
263
|
+
// seed. Both are signed checkpoint cursors consumed through the same
|
|
264
|
+
// CursorRegistry authority; neither mints a fresh snapshot.
|
|
265
|
+
const rawCursorToken = request.cursor ?? (queryId === "Q01" ? request.input?.checkpointToken : undefined);
|
|
266
|
+
if (rawCursorToken !== undefined && typeof rawCursorToken !== "string")
|
|
267
|
+
return this.#error(request, "invalid_request", false, "checkpointToken must be a non-empty string.");
|
|
268
|
+
const cursorToken = rawCursorToken;
|
|
269
|
+
if (cursorToken !== undefined) {
|
|
270
|
+
const cursor = this.cursors.consume(cursorToken, request.connectionId, {
|
|
208
271
|
sessionId: this.sessionId,
|
|
209
272
|
resource: source.resource,
|
|
210
273
|
direction: "forward",
|
|
@@ -344,6 +407,96 @@ export class QueryHandlers {
|
|
|
344
407
|
source.resource === "transcript" ? { highWatermark: lastId(snapshot) } : {},
|
|
345
408
|
);
|
|
346
409
|
}
|
|
410
|
+
async #checkpoint(request: QueryRequest): Promise<QueryResponse> {
|
|
411
|
+
if (request.cursor)
|
|
412
|
+
return this.#error(
|
|
413
|
+
request,
|
|
414
|
+
"invalid_request",
|
|
415
|
+
false,
|
|
416
|
+
"session.checkpoint does not accept a cursor; issue a fresh checkpoint on reconnect.",
|
|
417
|
+
);
|
|
418
|
+
const input = request.input ?? {};
|
|
419
|
+
const inputKeys = Object.keys(input);
|
|
420
|
+
if (inputKeys.some(key => key !== "checkpointToken"))
|
|
421
|
+
return this.#error(request, "invalid_request", false, "session.checkpoint accepts only checkpointToken.");
|
|
422
|
+
if (input.checkpointToken !== undefined) {
|
|
423
|
+
if (typeof input.checkpointToken !== "string" || input.checkpointToken.length === 0)
|
|
424
|
+
return this.#error(request, "invalid_request", false, "checkpointToken must be a non-empty string.");
|
|
425
|
+
try {
|
|
426
|
+
const exchanged = await this.cursors.exchange(
|
|
427
|
+
input.checkpointToken,
|
|
428
|
+
request.connectionId,
|
|
429
|
+
{
|
|
430
|
+
sessionId: this.sessionId,
|
|
431
|
+
resource: "transcript",
|
|
432
|
+
direction: "forward",
|
|
433
|
+
pageShape: { targetBytes: TARGET_PAGE_BYTES },
|
|
434
|
+
},
|
|
435
|
+
"transcript",
|
|
436
|
+
"default",
|
|
437
|
+
);
|
|
438
|
+
return {
|
|
439
|
+
id: request.id,
|
|
440
|
+
ok: true,
|
|
441
|
+
result: {
|
|
442
|
+
checkpointToken: exchanged.cursor,
|
|
443
|
+
checkpoint: exchanged.envelope.highWatermark,
|
|
444
|
+
revisionId: exchanged.envelope.revision,
|
|
445
|
+
issuedAt: exchanged.envelope.issuedAt,
|
|
446
|
+
expiresAt: exchanged.envelope.expiresAt,
|
|
447
|
+
},
|
|
448
|
+
};
|
|
449
|
+
} catch (error) {
|
|
450
|
+
if (error instanceof CursorError)
|
|
451
|
+
return this.#error(request, error.code, error.restartQuery, error.message);
|
|
452
|
+
throw error;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
// Expired pins must be removed before payload-hash deduplication; otherwise
|
|
456
|
+
// createRevision can return an expired revision that grant() immediately sweeps.
|
|
457
|
+
this.cursors.sweep();
|
|
458
|
+
// Atomic synchronous capture (C9): entries and event-ring watermark come
|
|
459
|
+
// from the same host-owned call, so the pinned snapshot revision and the
|
|
460
|
+
// subscribe position can never straddle a concurrent append.
|
|
461
|
+
const captured =
|
|
462
|
+
typeof this.surface.getCheckpointSnapshot === "function" ? this.surface.getCheckpointSnapshot() : undefined;
|
|
463
|
+
const entries = captured !== undefined ? captured.entries : await this.surface.getTranscriptEntries();
|
|
464
|
+
const head: SdkCheckpointRecord =
|
|
465
|
+
captured !== undefined
|
|
466
|
+
? captured.watermark
|
|
467
|
+
: { revision: Array.isArray(entries) ? entries.length : 0, generation: 0, seq: 0 };
|
|
468
|
+
// Mint the snapshot from the exact entries captured with the watermark;
|
|
469
|
+
// entries appended after this point are excluded from replay by
|
|
470
|
+
// construction (append-during-checkpoint), and the returned token is the
|
|
471
|
+
// per-grant signed cursor pinned to this revision — never an unlocked
|
|
472
|
+
// fresh replay authority.
|
|
473
|
+
const snapshot = await this.revisions.createRevision("transcript", "default", entries);
|
|
474
|
+
const nonce = randomUUID();
|
|
475
|
+
const envelope: CursorEnvelope = {
|
|
476
|
+
cursorVersion: 1,
|
|
477
|
+
protocolMajor: 3,
|
|
478
|
+
sessionId: this.sessionId,
|
|
479
|
+
resource: "transcript",
|
|
480
|
+
revision: snapshot,
|
|
481
|
+
highWatermark: head,
|
|
482
|
+
nonce,
|
|
483
|
+
position: { offset: 0, selector: { queryId: "Q01" } },
|
|
484
|
+
direction: "forward",
|
|
485
|
+
pageShape: { targetBytes: TARGET_PAGE_BYTES },
|
|
486
|
+
};
|
|
487
|
+
const cursor = await this.cursors.grant(request.connectionId, envelope, "transcript", "default");
|
|
488
|
+
return {
|
|
489
|
+
id: request.id,
|
|
490
|
+
ok: true,
|
|
491
|
+
result: {
|
|
492
|
+
checkpointToken: cursor,
|
|
493
|
+
checkpoint: head,
|
|
494
|
+
revisionId: snapshot,
|
|
495
|
+
issuedAt: envelope.issuedAt,
|
|
496
|
+
expiresAt: envelope.expiresAt,
|
|
497
|
+
},
|
|
498
|
+
};
|
|
499
|
+
}
|
|
347
500
|
|
|
348
501
|
async #transcriptBody(request: QueryRequest): Promise<QueryResponse> {
|
|
349
502
|
let selector = selectorFor("Q02", request.input);
|
|
@@ -116,6 +116,7 @@ describe("SDK surface parity", () => {
|
|
|
116
116
|
expect(createSdkCapabilities(nativePolicy, false)).toEqual(createSdkCapabilities(loopbackPolicy, false));
|
|
117
117
|
expect(nativePolicy.installedQueries).toContain("turn.prompt_status");
|
|
118
118
|
expect(nativePolicy.installedQueries).toContain("skill.invoke_status");
|
|
119
|
+
expect(nativePolicy.installedQueries).toContain("session.checkpoint");
|
|
119
120
|
});
|
|
120
121
|
|
|
121
122
|
test("workflow controls are advertised exactly when a durable gate bridge exists", () => {
|