@gajae-code/coding-agent 0.11.11 → 0.12.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 +49 -3
- package/dist/types/cli/read-cli.d.ts +1 -0
- package/dist/types/commands/deep-interview.d.ts +9 -0
- package/dist/types/commands/harness.d.ts +22 -0
- package/dist/types/commands/read.d.ts +6 -0
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/provider-auth-health.d.ts +14 -0
- package/dist/types/config/provider-ranking.d.ts +59 -0
- package/dist/types/config/settings-schema.d.ts +29 -1
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/extensibility/extensions/types.d.ts +22 -2
- package/dist/types/extensibility/shared-events.d.ts +6 -0
- package/dist/types/gjc-runtime/deep-interview-ambiguity.d.ts +5 -12
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +17 -32
- package/dist/types/gjc-runtime/deep-interview-stage.d.ts +37 -0
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +4 -135
- package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
- package/dist/types/gjc-runtime/memory-guard-owner-claims.d.ts +47 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +52 -1
- package/dist/types/gjc-runtime/state-writer.d.ts +5 -46
- package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
- package/dist/types/gjc-runtime/team-runtime.d.ts +2 -1
- package/dist/types/gjc-runtime/team-store.d.ts +12 -0
- package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +92 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +7 -0
- package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +8 -0
- package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -5
- package/dist/types/internal-urls/local-root-gc.d.ts +2 -0
- package/dist/types/main.d.ts +1 -1
- package/dist/types/memories/index.d.ts +2 -0
- package/dist/types/modes/acp/acp-agent.d.ts +8 -0
- package/dist/types/modes/components/assistant-message.d.ts +3 -1
- package/dist/types/modes/components/irc-sidebar.d.ts +41 -0
- package/dist/types/modes/components/read-tool-group.d.ts +4 -4
- package/dist/types/modes/components/session-observer-overlay.d.ts +4 -0
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/tool-execution.d.ts +8 -0
- package/dist/types/modes/controllers/event-controller.d.ts +3 -0
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/irc-observation-ledger.d.ts +17 -1
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +16 -0
- package/dist/types/runtime-mcp/smithery-auth.d.ts +5 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +5 -0
- package/dist/types/sdk/bus/control-drain-lease.d.ts +52 -0
- package/dist/types/sdk/bus/index.d.ts +15 -1
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +37 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +2 -1
- package/dist/types/sdk/bus/reconciliation-store.d.ts +65 -0
- package/dist/types/sdk/host/control/operations.d.ts +1 -1
- package/dist/types/sdk/host/host.d.ts +2 -0
- package/dist/types/sdk/host/query/handlers.d.ts +5 -0
- package/dist/types/session/agent-session.d.ts +44 -10
- package/dist/types/session/memory-guard-checkpoint-participant.d.ts +69 -0
- package/dist/types/session/session-manager.d.ts +25 -13
- package/dist/types/session/streaming-output.d.ts +60 -5
- package/dist/types/skill-state/active-state.d.ts +0 -4
- package/dist/types/skill-state/workflow-hud.d.ts +3 -0
- package/dist/types/tools/browser/launch.d.ts +7 -0
- package/dist/types/tools/fetch.d.ts +17 -1
- package/dist/types/tools/output-meta.d.ts +21 -1
- package/dist/types/tools/read-internals.d.ts +12 -0
- package/dist/types/tools/read.d.ts +45 -1
- package/dist/types/tools/resource-gc.d.ts +4 -0
- package/dist/types/tools/tool-result.d.ts +7 -1
- package/package.json +9 -13
- package/scripts/benchmark-sticky-viewport-pr1.ts +239 -0
- package/scripts/capture-sticky-viewport-showcase.ts +292 -0
- package/scripts/generate-sdk-operation-inventory.ts +2 -0
- package/scripts/verify-gjc-sdk-canonicalization.ts +122 -18
- package/scripts/verify-sticky-viewport-showcase.ts +611 -0
- package/src/capability/index.ts +8 -0
- package/src/cli/read-cli.ts +2 -1
- package/src/cli.ts +1 -0
- package/src/commands/deep-interview.ts +29 -9
- package/src/commands/harness.ts +35 -12
- package/src/commands/read.ts +11 -2
- package/src/commands/team.ts +1 -1
- package/src/config/model-registry.ts +21 -2
- package/src/config/provider-auth-health.ts +42 -0
- package/src/config/provider-ranking.ts +119 -0
- package/src/config/settings-schema.ts +23 -1
- package/src/config/settings.ts +246 -108
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +229 -82
- package/src/defaults/gjc/skills/ralplan/SKILL.md +58 -25
- package/src/defaults/gjc/skills/team/SKILL.md +1 -1
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +25 -15
- package/src/eval/py/executor.ts +7 -2
- package/src/extensibility/extensions/types.ts +28 -4
- package/src/extensibility/shared-events.ts +5 -0
- package/src/gjc-runtime/deep-interview-ambiguity.ts +47 -82
- package/src/gjc-runtime/deep-interview-recorder.ts +227 -310
- package/src/gjc-runtime/deep-interview-runtime.ts +109 -270
- package/src/gjc-runtime/deep-interview-stage.ts +1016 -0
- package/src/gjc-runtime/deep-interview-state.ts +12 -943
- package/src/gjc-runtime/gc-render.ts +1 -0
- package/src/gjc-runtime/gc-runtime.ts +11 -1
- package/src/gjc-runtime/memory-guard-owner-claims.ts +389 -0
- package/src/gjc-runtime/ralplan-runtime.ts +742 -85
- package/src/gjc-runtime/state-runtime.ts +2 -1
- package/src/gjc-runtime/state-writer.ts +70 -222
- package/src/gjc-runtime/team-launch.ts +34 -5
- package/src/gjc-runtime/team-runtime.ts +970 -33
- package/src/gjc-runtime/team-store.ts +32 -0
- package/src/gjc-runtime/team-worker-memory-guard.ts +363 -0
- package/src/gjc-runtime/team-workers.ts +1 -0
- package/src/gjc-runtime/tmux-owner-isolation.ts +18 -0
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +20 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +13 -7
- package/src/gjc-runtime/workflow-manifest.generated.json +97 -350
- package/src/gjc-runtime/workflow-manifest.ts +34 -179
- package/src/hooks/native-skill-hook.ts +17 -4
- package/src/hooks/skill-state.ts +1 -23
- package/src/internal-urls/docs-index.generated.ts +8 -9
- package/src/internal-urls/local-root-gc.ts +127 -0
- package/src/main.ts +9 -5
- package/src/memories/index.ts +10 -0
- package/src/modes/DESIGN.md +47 -0
- package/src/modes/acp/acp-agent.ts +43 -7
- package/src/modes/acp/acp-event-mapper.ts +14 -7
- package/src/modes/acp/acp-mode.ts +39 -3
- package/src/modes/components/assistant-message.ts +123 -22
- package/src/modes/components/irc-sidebar.ts +176 -62
- package/src/modes/components/model-selector.ts +61 -4
- package/src/modes/components/oauth-selector.ts +42 -12
- package/src/modes/components/read-tool-group.ts +82 -14
- package/src/modes/components/session-observer-overlay.ts +187 -32
- package/src/modes/components/settings-selector.ts +60 -18
- package/src/modes/components/tool-execution.ts +138 -27
- package/src/modes/controllers/event-controller.ts +109 -27
- package/src/modes/controllers/extension-ui-controller.ts +19 -2
- package/src/modes/controllers/input-controller.ts +18 -3
- package/src/modes/controllers/selector-controller.ts +83 -37
- package/src/modes/interactive-mode.ts +62 -3
- package/src/modes/irc-observation-ledger.ts +71 -15
- package/src/modes/runtime-init.ts +19 -2
- package/src/modes/types.ts +2 -0
- package/src/modes/utils/ui-helpers.ts +94 -1
- package/src/prompts/agents/architect.md +9 -1
- package/src/prompts/agents/critic.md +9 -0
- package/src/prompts/system/system-prompt.md +0 -1
- package/src/prompts/tools/read.md +4 -2
- package/src/runtime-mcp/smithery-auth.ts +17 -2
- package/src/runtime-mcp/smithery-connect.ts +11 -1
- package/src/sdk/broker/lifecycle.ts +25 -6
- package/src/sdk/bus/control-drain-lease.ts +104 -0
- package/src/sdk/bus/index.ts +341 -38
- package/src/sdk/bus/kind-aware-reconciliation.ts +242 -0
- package/src/sdk/bus/prompt-reconciliation.ts +2 -1
- package/src/sdk/bus/reconciliation-store.ts +224 -0
- package/src/sdk/host/control/dispatch.ts +5 -1
- package/src/sdk/host/control/operations.ts +1 -1
- package/src/sdk/host/host.ts +23 -1
- package/src/sdk/host/query/handlers.ts +55 -0
- package/src/sdk/mcp/server.ts +46 -18
- package/src/sdk/protocol/operation-inventory.generated.json +29 -0
- package/src/sdk/protocol/operation-registry.ts +7 -2
- package/src/sdk/session.ts +13 -1
- package/src/session/agent-session.ts +466 -50
- package/src/session/memory-guard-checkpoint-participant.ts +131 -0
- package/src/session/session-manager.ts +764 -17
- package/src/session/streaming-output.ts +262 -43
- package/src/setup/provider-onboarding.ts +19 -4
- package/src/skill-state/active-state.ts +0 -10
- package/src/skill-state/workflow-hud.ts +25 -2
- package/src/skill-state/workflow-mutation-guard.ts +65 -7
- package/src/tools/ask.ts +89 -69
- package/src/tools/browser/launch.ts +41 -6
- package/src/tools/fetch.ts +114 -23
- package/src/tools/output-meta.ts +176 -9
- package/src/tools/path-utils.ts +5 -1
- package/src/tools/read-internals.ts +44 -0
- package/src/tools/read.ts +1542 -310
- package/src/tools/resource-gc.ts +182 -18
- package/src/tools/tool-result.ts +14 -1
- package/src/web/search/providers/codex.ts +1 -1
- package/dist/types/gjc-runtime/deep-interview-draft.d.ts +0 -8
- package/dist/types/gjc-runtime/deep-interview-payload.d.ts +0 -41
- package/dist/types/gjc-runtime/deep-interview-repair.d.ts +0 -17
- package/src/gjc-runtime/deep-interview-draft.ts +0 -843
- package/src/gjc-runtime/deep-interview-payload.ts +0 -262
- package/src/gjc-runtime/deep-interview-repair.ts +0 -1674
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kind-aware invocation reconciliation (prompt | skill) with optional durable store.
|
|
3
|
+
* Preserves Q26 admit/first-terminal/capacity/TTL semantics; indexes and caps are per-kind.
|
|
4
|
+
*/
|
|
5
|
+
import {
|
|
6
|
+
PROMPT_RECONCILIATION_ACTIVE_CAPACITY,
|
|
7
|
+
PROMPT_RECONCILIATION_TERMINAL_CAPACITY,
|
|
8
|
+
PROMPT_RECONCILIATION_TERMINAL_TTL_MS,
|
|
9
|
+
type PromptCorrelation,
|
|
10
|
+
type PromptReconciliationStatus,
|
|
11
|
+
sanitizePromptFailure,
|
|
12
|
+
type TurnPromptReconciliation,
|
|
13
|
+
} from "./prompt-reconciliation";
|
|
14
|
+
import type { DurableReconciliationRecord, ReconciliationKind, ReconciliationStore } from "./reconciliation-store";
|
|
15
|
+
|
|
16
|
+
export type { ReconciliationKind };
|
|
17
|
+
|
|
18
|
+
export interface KindCorrelation extends PromptCorrelation {
|
|
19
|
+
kind: ReconciliationKind;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface KindAwareReconciliation {
|
|
23
|
+
admit(kind: ReconciliationKind, clientRef?: string): void;
|
|
24
|
+
releaseAdmission(kind: ReconciliationKind, clientRef?: string): void;
|
|
25
|
+
noteAccepted(
|
|
26
|
+
kind: ReconciliationKind,
|
|
27
|
+
correlation: PromptCorrelation,
|
|
28
|
+
clientRef?: string,
|
|
29
|
+
extra?: { skillName?: string },
|
|
30
|
+
): Promise<void>;
|
|
31
|
+
noteTransition(
|
|
32
|
+
kind: ReconciliationKind,
|
|
33
|
+
correlation: PromptCorrelation | undefined,
|
|
34
|
+
frame: { type: "agent_start" | "agent_end" } | { type: "agent_failed"; error: unknown },
|
|
35
|
+
): Promise<void>;
|
|
36
|
+
lookup(
|
|
37
|
+
kind: ReconciliationKind,
|
|
38
|
+
selector: { commandId?: string; turnId?: string; clientRef?: string },
|
|
39
|
+
): TurnPromptReconciliation;
|
|
40
|
+
cleanup(): void;
|
|
41
|
+
activeCount(kind: ReconciliationKind): number;
|
|
42
|
+
/** Hydrate from durable store (call once at session host start). */
|
|
43
|
+
hydrateFromStore(): Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function createKindAwareReconciliation(
|
|
47
|
+
options: { now?: () => number; store?: ReconciliationStore | null } = {},
|
|
48
|
+
): KindAwareReconciliation {
|
|
49
|
+
const now = options.now ?? Date.now;
|
|
50
|
+
const store = options.store ?? null;
|
|
51
|
+
const records = new Map<string, DurableReconciliationRecord>();
|
|
52
|
+
const clientRefIndex = new Map<string, string>(); // `${kind}\0${clientRef}` -> key
|
|
53
|
+
const reservedClientRefs = new Map<ReconciliationKind, Set<string>>();
|
|
54
|
+
const reservations: Array<{ kind: ReconciliationKind; clientRef?: string }> = [];
|
|
55
|
+
|
|
56
|
+
const keyOf = (kind: ReconciliationKind, correlation: PromptCorrelation) =>
|
|
57
|
+
`${kind}:${correlation.commandId}:${correlation.turnId}`;
|
|
58
|
+
const refKey = (kind: ReconciliationKind, clientRef: string) => `${kind}\0${clientRef}`;
|
|
59
|
+
|
|
60
|
+
const reservedFor = (kind: ReconciliationKind) => {
|
|
61
|
+
let set = reservedClientRefs.get(kind);
|
|
62
|
+
if (!set) {
|
|
63
|
+
set = new Set();
|
|
64
|
+
reservedClientRefs.set(kind, set);
|
|
65
|
+
}
|
|
66
|
+
return set;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const remove = (key: string) => {
|
|
70
|
+
const record = records.get(key);
|
|
71
|
+
if (!record) return;
|
|
72
|
+
records.delete(key);
|
|
73
|
+
if (record.clientRef !== undefined) {
|
|
74
|
+
const rk = refKey(record.kind, record.clientRef);
|
|
75
|
+
if (clientRefIndex.get(rk) === key) clientRefIndex.delete(rk);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const cleanup = () => {
|
|
80
|
+
const at = now();
|
|
81
|
+
for (const [key, record] of records)
|
|
82
|
+
if (record.terminalAt !== undefined && record.terminalAt + PROMPT_RECONCILIATION_TERMINAL_TTL_MS <= at)
|
|
83
|
+
remove(key);
|
|
84
|
+
for (const kind of ["prompt", "skill"] as const) {
|
|
85
|
+
const terminalEntries = [...records.entries()].filter(
|
|
86
|
+
([, record]) => record.kind === kind && record.terminalAt !== undefined,
|
|
87
|
+
);
|
|
88
|
+
if (terminalEntries.length <= PROMPT_RECONCILIATION_TERMINAL_CAPACITY) continue;
|
|
89
|
+
terminalEntries.sort((a, b) => (a[1].terminalAt as number) - (b[1].terminalAt as number));
|
|
90
|
+
for (const [key] of terminalEntries.slice(0, terminalEntries.length - PROMPT_RECONCILIATION_TERMINAL_CAPACITY))
|
|
91
|
+
remove(key);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const activeCount = (kind: ReconciliationKind) => {
|
|
96
|
+
let count = 0;
|
|
97
|
+
for (const record of records.values()) if (record.kind === kind && record.terminalAt === undefined) count++;
|
|
98
|
+
return count;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const reservationCount = (kind: ReconciliationKind) => reservations.filter(r => r.kind === kind).length;
|
|
102
|
+
|
|
103
|
+
const consumeReservation = (kind: ReconciliationKind, clientRef?: string) => {
|
|
104
|
+
const index = reservations.findIndex(r => r.kind === kind && r.clientRef === clientRef);
|
|
105
|
+
if (index === -1) return;
|
|
106
|
+
reservations.splice(index, 1);
|
|
107
|
+
if (clientRef !== undefined && !reservations.some(r => r.kind === kind && r.clientRef === clientRef))
|
|
108
|
+
reservedFor(kind).delete(clientRef);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const persist = async () => {
|
|
112
|
+
if (!store) return;
|
|
113
|
+
await store.transact(() => [...records.values()].map(r => ({ ...r })));
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const admit = (kind: ReconciliationKind, clientRef?: string) => {
|
|
117
|
+
cleanup();
|
|
118
|
+
const reserved = reservedFor(kind);
|
|
119
|
+
if (clientRef !== undefined && (clientRefIndex.has(refKey(kind, clientRef)) || reserved.has(clientRef)))
|
|
120
|
+
throw Object.assign(
|
|
121
|
+
new Error("A submission with this clientRef is already retained; never reuse a clientRef for retry."),
|
|
122
|
+
{ code: "client_ref_conflict" },
|
|
123
|
+
);
|
|
124
|
+
if (activeCount(kind) + reservationCount(kind) >= PROMPT_RECONCILIATION_ACTIVE_CAPACITY)
|
|
125
|
+
throw Object.assign(new Error("Too many active submissions; reconcile or await terminal state."), {
|
|
126
|
+
code: "reconciliation_capacity",
|
|
127
|
+
});
|
|
128
|
+
reservations.push({ kind, clientRef });
|
|
129
|
+
if (clientRef !== undefined) reserved.add(clientRef);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const releaseAdmission = (kind: ReconciliationKind, clientRef?: string) => {
|
|
133
|
+
consumeReservation(kind, clientRef);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const noteAccepted = async (
|
|
137
|
+
kind: ReconciliationKind,
|
|
138
|
+
correlation: PromptCorrelation,
|
|
139
|
+
clientRef?: string,
|
|
140
|
+
extra?: { skillName?: string },
|
|
141
|
+
) => {
|
|
142
|
+
cleanup();
|
|
143
|
+
consumeReservation(kind, clientRef);
|
|
144
|
+
const at = now();
|
|
145
|
+
const key = keyOf(kind, correlation);
|
|
146
|
+
const record: DurableReconciliationRecord = {
|
|
147
|
+
kind,
|
|
148
|
+
commandId: correlation.commandId,
|
|
149
|
+
turnId: correlation.turnId,
|
|
150
|
+
...(clientRef !== undefined ? { clientRef } : {}),
|
|
151
|
+
status: "accepted",
|
|
152
|
+
acceptedAt: at,
|
|
153
|
+
...(extra?.skillName ? { skillName: extra.skillName } : {}),
|
|
154
|
+
};
|
|
155
|
+
records.set(key, record);
|
|
156
|
+
if (clientRef !== undefined) clientRefIndex.set(refKey(kind, clientRef), key);
|
|
157
|
+
await persist();
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const noteTransition = async (
|
|
161
|
+
kind: ReconciliationKind,
|
|
162
|
+
correlation: PromptCorrelation | undefined,
|
|
163
|
+
frame: { type: "agent_start" | "agent_end" } | { type: "agent_failed"; error: unknown },
|
|
164
|
+
) => {
|
|
165
|
+
if (!correlation) return;
|
|
166
|
+
const record = records.get(keyOf(kind, correlation));
|
|
167
|
+
if (!record || record.terminalAt !== undefined) return;
|
|
168
|
+
if (frame.type === "agent_start") {
|
|
169
|
+
if (record.status === "accepted") {
|
|
170
|
+
record.status = "in_flight";
|
|
171
|
+
record.startedAt = now();
|
|
172
|
+
await persist();
|
|
173
|
+
}
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
record.terminalAt = now();
|
|
177
|
+
if (frame.type === "agent_failed") {
|
|
178
|
+
record.status = "failed";
|
|
179
|
+
record.error = sanitizePromptFailure(frame.error);
|
|
180
|
+
} else {
|
|
181
|
+
record.status = "terminal_ok";
|
|
182
|
+
}
|
|
183
|
+
cleanup();
|
|
184
|
+
await persist();
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const lookup = (
|
|
188
|
+
kind: ReconciliationKind,
|
|
189
|
+
selector: { commandId?: string; turnId?: string; clientRef?: string },
|
|
190
|
+
): TurnPromptReconciliation => {
|
|
191
|
+
cleanup();
|
|
192
|
+
const key =
|
|
193
|
+
selector.clientRef !== undefined
|
|
194
|
+
? clientRefIndex.get(refKey(kind, selector.clientRef))
|
|
195
|
+
: selector.commandId !== undefined && selector.turnId !== undefined
|
|
196
|
+
? keyOf(kind, { commandId: selector.commandId, turnId: selector.turnId })
|
|
197
|
+
: undefined;
|
|
198
|
+
const record = key === undefined ? undefined : records.get(key);
|
|
199
|
+
if (!record) return { status: "unknown" };
|
|
200
|
+
const identity = {
|
|
201
|
+
commandId: record.commandId,
|
|
202
|
+
turnId: record.turnId,
|
|
203
|
+
...(record.clientRef !== undefined ? { clientRef: record.clientRef } : {}),
|
|
204
|
+
acceptedAt: record.acceptedAt,
|
|
205
|
+
};
|
|
206
|
+
if (record.status === "accepted") return { status: "accepted", ...identity };
|
|
207
|
+
if (record.status === "in_flight")
|
|
208
|
+
return { status: "in_flight", ...identity, startedAt: record.startedAt as number };
|
|
209
|
+
const terminal = {
|
|
210
|
+
...identity,
|
|
211
|
+
...(record.startedAt !== undefined ? { startedAt: record.startedAt } : {}),
|
|
212
|
+
terminalAt: record.terminalAt as number,
|
|
213
|
+
};
|
|
214
|
+
if (record.status === "terminal_ok") return { status: "terminal_ok", ...terminal };
|
|
215
|
+
return { status: "failed", ...terminal, error: record.error ?? sanitizePromptFailure(undefined) };
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const hydrateFromStore = async () => {
|
|
219
|
+
if (!store) return;
|
|
220
|
+
const loaded = await store.load();
|
|
221
|
+
records.clear();
|
|
222
|
+
clientRefIndex.clear();
|
|
223
|
+
for (const record of loaded) {
|
|
224
|
+
const key = keyOf(record.kind, record);
|
|
225
|
+
records.set(key, record);
|
|
226
|
+
if (record.clientRef !== undefined) clientRefIndex.set(refKey(record.kind, record.clientRef), key);
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
admit,
|
|
232
|
+
releaseAdmission,
|
|
233
|
+
noteAccepted,
|
|
234
|
+
noteTransition,
|
|
235
|
+
lookup,
|
|
236
|
+
cleanup,
|
|
237
|
+
activeCount,
|
|
238
|
+
hydrateFromStore,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export type { PromptReconciliationStatus };
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
* - Terminal records are retained for TERMINAL_TTL_MS and bounded to
|
|
13
13
|
* TERMINAL_CAPACITY, evicted oldest-terminal-first by terminalAt; only then
|
|
14
14
|
* does a lookup honestly report `unknown`.
|
|
15
|
-
* -
|
|
15
|
+
* - Process-local durability floor is the live session process: restart means `unknown`.
|
|
16
|
+
* Session-scoped durable retention (kind-aware store) is provided by reconciliation-store.ts (#3032).
|
|
16
17
|
* - The clientRef index is session-runtime scoped; a ref conflicts only while
|
|
17
18
|
* retained and must never be reused as a retry mechanism.
|
|
18
19
|
* - Terminal transitions settle once: the first terminal outcome wins.
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-scoped durable store for kind-aware invocation reconciliation (#3032/#3035).
|
|
3
|
+
*
|
|
4
|
+
* Path is always a private sibling of the transcript, never under artifactsDir:
|
|
5
|
+
* <dirname(sessionFile)>/.sdk-reconciliation/<safeSessionId>.json
|
|
6
|
+
*
|
|
7
|
+
* Safe session ids only: /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/
|
|
8
|
+
* Atomic write: temp + fsync + rename + 0600. Corrupt → quarantine + empty.
|
|
9
|
+
* Non-terminal records settle to failed/process_restart on bootstrap (recon incomplete).
|
|
10
|
+
*/
|
|
11
|
+
import * as fs from "node:fs/promises";
|
|
12
|
+
import * as path from "node:path";
|
|
13
|
+
import type { PromptCorrelation, PromptReconciliationStatus } from "./prompt-reconciliation";
|
|
14
|
+
|
|
15
|
+
export const RECONCILIATION_STORE_VERSION = 1;
|
|
16
|
+
export const RECONCILIATION_SESSION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
17
|
+
export const RECONCILIATION_DIR_NAME = ".sdk-reconciliation";
|
|
18
|
+
|
|
19
|
+
export type ReconciliationKind = "prompt" | "skill";
|
|
20
|
+
|
|
21
|
+
export interface DurableReconciliationRecord extends PromptCorrelation {
|
|
22
|
+
kind: ReconciliationKind;
|
|
23
|
+
clientRef?: string;
|
|
24
|
+
status: PromptReconciliationStatus;
|
|
25
|
+
error?: { code: string; message: string };
|
|
26
|
+
acceptedAt: number;
|
|
27
|
+
startedAt?: number;
|
|
28
|
+
terminalAt?: number;
|
|
29
|
+
/** Skill-only safe token; never skill args bodies. */
|
|
30
|
+
skillName?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ReconciliationStoreDocument {
|
|
34
|
+
version: typeof RECONCILIATION_STORE_VERSION;
|
|
35
|
+
sessionId: string;
|
|
36
|
+
records: DurableReconciliationRecord[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ReconciliationStoreFs {
|
|
40
|
+
mkdir(directory: string, options: { recursive: true; mode: number }): Promise<unknown>;
|
|
41
|
+
readFile(file: string, encoding: "utf8"): Promise<string>;
|
|
42
|
+
writeFile(file: string, data: string, options: { mode: number }): Promise<void>;
|
|
43
|
+
rename(from: string, to: string): Promise<void>;
|
|
44
|
+
unlink(file: string): Promise<void>;
|
|
45
|
+
open(
|
|
46
|
+
file: string,
|
|
47
|
+
flags: string,
|
|
48
|
+
): Promise<{
|
|
49
|
+
sync(): Promise<void>;
|
|
50
|
+
close(): Promise<void>;
|
|
51
|
+
writeFile(data: string, encoding: "utf8"): Promise<void>;
|
|
52
|
+
}>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const nodeFs: ReconciliationStoreFs = {
|
|
56
|
+
mkdir: fs.mkdir,
|
|
57
|
+
readFile: fs.readFile,
|
|
58
|
+
writeFile: fs.writeFile,
|
|
59
|
+
rename: fs.rename,
|
|
60
|
+
unlink: fs.unlink,
|
|
61
|
+
open: fs.open as ReconciliationStoreFs["open"],
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export function isSafeReconciliationSessionId(sessionId: string): boolean {
|
|
65
|
+
return RECONCILIATION_SESSION_ID_PATTERN.test(sessionId);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Derive private store path; throws if sessionId is unsafe (path escape). */
|
|
69
|
+
export function reconciliationStorePath(sessionFile: string, sessionId: string): string {
|
|
70
|
+
if (!isSafeReconciliationSessionId(sessionId))
|
|
71
|
+
throw Object.assign(new Error("Unsafe session id for reconciliation store path."), {
|
|
72
|
+
code: "invalid_input",
|
|
73
|
+
});
|
|
74
|
+
return path.join(path.dirname(sessionFile), RECONCILIATION_DIR_NAME, `${sessionId}.json`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
78
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function parseDocument(raw: string, expectedSessionId: string): ReconciliationStoreDocument {
|
|
82
|
+
const value = JSON.parse(raw) as unknown;
|
|
83
|
+
if (!isRecord(value) || value.version !== RECONCILIATION_STORE_VERSION)
|
|
84
|
+
throw new Error("invalid reconciliation store version");
|
|
85
|
+
if (value.sessionId !== expectedSessionId) throw new Error("session id mismatch");
|
|
86
|
+
if (!Array.isArray(value.records)) throw new Error("invalid records");
|
|
87
|
+
return value as unknown as ReconciliationStoreDocument;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Settle non-terminal durable records after process death: failed/process_restart.
|
|
92
|
+
* Meaning: reconciliation incomplete — not proof of agent failure.
|
|
93
|
+
*/
|
|
94
|
+
export function settleProcessRestart(
|
|
95
|
+
records: DurableReconciliationRecord[],
|
|
96
|
+
now: number,
|
|
97
|
+
): DurableReconciliationRecord[] {
|
|
98
|
+
return records.map(record => {
|
|
99
|
+
if (record.terminalAt !== undefined) return record;
|
|
100
|
+
return {
|
|
101
|
+
...record,
|
|
102
|
+
status: "failed",
|
|
103
|
+
terminalAt: now,
|
|
104
|
+
error: { code: "process_restart", message: "Reconciliation incomplete after process restart." },
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface ReconciliationStore {
|
|
110
|
+
readonly path: string | null;
|
|
111
|
+
readonly sessionId: string;
|
|
112
|
+
/** Serialize mutations; reload not required for single-process host (in-memory + write). */
|
|
113
|
+
transact(mutator: (records: DurableReconciliationRecord[]) => DurableReconciliationRecord[]): Promise<void>;
|
|
114
|
+
load(): Promise<DurableReconciliationRecord[]>;
|
|
115
|
+
/** Snapshot currently held in memory after last load/transact. */
|
|
116
|
+
snapshot(): DurableReconciliationRecord[];
|
|
117
|
+
delete(): Promise<void>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function createReconciliationStore(options: {
|
|
121
|
+
sessionFile: string | null | undefined;
|
|
122
|
+
sessionId: string;
|
|
123
|
+
fs?: ReconciliationStoreFs;
|
|
124
|
+
now?: () => number;
|
|
125
|
+
}): ReconciliationStore {
|
|
126
|
+
const fileFs = options.fs ?? nodeFs;
|
|
127
|
+
const now = options.now ?? Date.now;
|
|
128
|
+
const sessionId = options.sessionId;
|
|
129
|
+
const filePath =
|
|
130
|
+
options.sessionFile && isSafeReconciliationSessionId(sessionId)
|
|
131
|
+
? reconciliationStorePath(options.sessionFile, sessionId)
|
|
132
|
+
: null;
|
|
133
|
+
|
|
134
|
+
let memory: DurableReconciliationRecord[] = [];
|
|
135
|
+
let chain: Promise<void> = Promise.resolve();
|
|
136
|
+
|
|
137
|
+
const writeAtomic = async (document: ReconciliationStoreDocument): Promise<void> => {
|
|
138
|
+
if (!filePath) return;
|
|
139
|
+
const directory = path.dirname(filePath);
|
|
140
|
+
await fileFs.mkdir(directory, { recursive: true, mode: 0o700 });
|
|
141
|
+
const temporary = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
142
|
+
try {
|
|
143
|
+
await fileFs.writeFile(temporary, `${JSON.stringify(document)}\n`, { mode: 0o600 });
|
|
144
|
+
try {
|
|
145
|
+
const handle = await fileFs.open(temporary, "r+");
|
|
146
|
+
try {
|
|
147
|
+
await handle.sync();
|
|
148
|
+
} finally {
|
|
149
|
+
await handle.close();
|
|
150
|
+
}
|
|
151
|
+
} catch {
|
|
152
|
+
// fsync optional on some fs seams
|
|
153
|
+
}
|
|
154
|
+
await fileFs.rename(temporary, filePath);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
await fileFs.unlink(temporary).catch(() => {});
|
|
157
|
+
throw Object.assign(error instanceof Error ? error : new Error("reconciliation persist failed"), {
|
|
158
|
+
code: "reconciliation_persist_failed",
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const load = async (): Promise<DurableReconciliationRecord[]> => {
|
|
164
|
+
if (!filePath) {
|
|
165
|
+
memory = [];
|
|
166
|
+
return memory;
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
const raw = await fileFs.readFile(filePath, "utf8");
|
|
170
|
+
const document = parseDocument(raw, sessionId);
|
|
171
|
+
memory = settleProcessRestart(document.records, now());
|
|
172
|
+
// Persist settled state so restart is sticky.
|
|
173
|
+
if (memory.some((r, i) => r !== document.records[i]))
|
|
174
|
+
await writeAtomic({ version: RECONCILIATION_STORE_VERSION, sessionId, records: memory });
|
|
175
|
+
return memory;
|
|
176
|
+
} catch (error) {
|
|
177
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
178
|
+
memory = [];
|
|
179
|
+
return memory;
|
|
180
|
+
}
|
|
181
|
+
// Corrupt → quarantine
|
|
182
|
+
try {
|
|
183
|
+
await fileFs.rename(filePath, `${filePath}.corrupt.${now()}`);
|
|
184
|
+
} catch {
|
|
185
|
+
// ignore
|
|
186
|
+
}
|
|
187
|
+
memory = [];
|
|
188
|
+
return memory;
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const transact = async (
|
|
193
|
+
mutator: (records: DurableReconciliationRecord[]) => DurableReconciliationRecord[],
|
|
194
|
+
): Promise<void> => {
|
|
195
|
+
const run = async () => {
|
|
196
|
+
const next = mutator(memory.map(r => ({ ...r })));
|
|
197
|
+
memory = next;
|
|
198
|
+
await writeAtomic({ version: RECONCILIATION_STORE_VERSION, sessionId, records: memory });
|
|
199
|
+
};
|
|
200
|
+
const pending = chain.then(run, run);
|
|
201
|
+
chain = pending.then(
|
|
202
|
+
() => undefined,
|
|
203
|
+
() => undefined,
|
|
204
|
+
);
|
|
205
|
+
await pending;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const deleteStore = async (): Promise<void> => {
|
|
209
|
+
memory = [];
|
|
210
|
+
if (!filePath) return;
|
|
211
|
+
await fileFs.unlink(filePath).catch(error => {
|
|
212
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
213
|
+
});
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
path: filePath,
|
|
218
|
+
sessionId,
|
|
219
|
+
transact,
|
|
220
|
+
load,
|
|
221
|
+
snapshot: () => memory.map(r => ({ ...r })),
|
|
222
|
+
delete: deleteStore,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
@@ -158,7 +158,11 @@ function invoke(
|
|
|
158
158
|
case "workflow.plan_approve":
|
|
159
159
|
return surface.approvePlan(text(input, "id"), input.choice, input.expectedSessionId as string | undefined);
|
|
160
160
|
case "skill.invoke":
|
|
161
|
-
return surface.invokeSkill(
|
|
161
|
+
return surface.invokeSkill(
|
|
162
|
+
text(input, "name"),
|
|
163
|
+
input.args,
|
|
164
|
+
typeof input.clientRef === "string" ? input.clientRef : undefined,
|
|
165
|
+
);
|
|
162
166
|
case "mode.plan.set":
|
|
163
167
|
return surface.setPlanMode(input.on as boolean);
|
|
164
168
|
case "mode.goal.operate":
|
|
@@ -19,7 +19,7 @@ export interface ControlSurface {
|
|
|
19
19
|
idempotencyKey?: string,
|
|
20
20
|
): Promise<ControlValue> | ControlValue;
|
|
21
21
|
approvePlan(id: string, choice: ControlValue, expectedSessionId?: string): Promise<ControlValue> | ControlValue;
|
|
22
|
-
invokeSkill(name: string, args: ControlValue): Promise<ControlValue> | ControlValue;
|
|
22
|
+
invokeSkill(name: string, args: ControlValue, clientRef?: string): Promise<ControlValue> | ControlValue;
|
|
23
23
|
setPlanMode(on: boolean): Promise<ControlValue> | ControlValue;
|
|
24
24
|
operateGoal(op: string, objective?: string): Promise<ControlValue> | ControlValue;
|
|
25
25
|
replaceTodo(items: ControlValue): Promise<ControlValue> | ControlValue;
|
package/src/sdk/host/host.ts
CHANGED
|
@@ -9,6 +9,13 @@ export interface SessionSdkHostOptions extends HostEndpointAdapters {
|
|
|
9
9
|
query?: (connectionId: string, frame: SdkFrame) => unknown | Promise<unknown>;
|
|
10
10
|
/** Best-effort diagnostic observation of accepted control/query frames. */
|
|
11
11
|
onRequest?: SdkRequestObserver;
|
|
12
|
+
/** Runs before a control response is sent; identity transitions use sendTerminal. */
|
|
13
|
+
beforeControlResponse?: (
|
|
14
|
+
connectionId: string,
|
|
15
|
+
request: SdkFrame,
|
|
16
|
+
response: SdkFrame,
|
|
17
|
+
sendTerminal: () => Promise<void>,
|
|
18
|
+
) => void | Promise<void>;
|
|
12
19
|
/** Runs only after a successful control response has been sent to the client. */
|
|
13
20
|
afterControlResponse?: (connectionId: string, request: SdkFrame, response: SdkFrame) => void | Promise<void>;
|
|
14
21
|
installProviderDefinitions?: (capability: string, definitions: unknown) => void;
|
|
@@ -36,6 +43,14 @@ function errorFrame(connectionId: string, frame: SdkFrame, error: unknown): SdkF
|
|
|
36
43
|
? candidate.code
|
|
37
44
|
: "internal";
|
|
38
45
|
const message = typeof candidate?.message === "string" ? candidate.message : "SDK host operation failed.";
|
|
46
|
+
if (frame.type === "control_request") {
|
|
47
|
+
return {
|
|
48
|
+
type: "control_response",
|
|
49
|
+
id: typeof frame.id === "string" ? frame.id : "",
|
|
50
|
+
ok: false,
|
|
51
|
+
error: { code, message },
|
|
52
|
+
};
|
|
53
|
+
}
|
|
39
54
|
return {
|
|
40
55
|
type: "reverse_response",
|
|
41
56
|
id: typeof frame.id === "string" ? frame.id : "",
|
|
@@ -214,7 +229,14 @@ export class SessionSdkHost {
|
|
|
214
229
|
const result = await this.#options.control?.(connectionId, frame);
|
|
215
230
|
if (result !== undefined) {
|
|
216
231
|
const response = { type: "control_response", ...(result as SdkFrame) };
|
|
217
|
-
|
|
232
|
+
let terminalSent = false;
|
|
233
|
+
const sendTerminal = async (): Promise<void> => {
|
|
234
|
+
if (terminalSent) return;
|
|
235
|
+
terminalSent = true;
|
|
236
|
+
await this.#send(connectionId, response);
|
|
237
|
+
};
|
|
238
|
+
await this.#options.beforeControlResponse?.(connectionId, frame, response, sendTerminal);
|
|
239
|
+
await sendTerminal();
|
|
218
240
|
await this.#options.afterControlResponse?.(connectionId, frame, response);
|
|
219
241
|
}
|
|
220
242
|
break;
|
|
@@ -46,6 +46,11 @@ export interface SessionSurface {
|
|
|
46
46
|
getJobs(): unknown | Promise<unknown>;
|
|
47
47
|
/** Q26 keyed lookup of a submitted prompt's authoritative reconciliation status. */
|
|
48
48
|
getPromptStatus?(selector: { commandId?: string; turnId?: string; clientRef?: string }): unknown | Promise<unknown>;
|
|
49
|
+
getSkillInvokeStatus?(selector: {
|
|
50
|
+
commandId?: string;
|
|
51
|
+
turnId?: string;
|
|
52
|
+
clientRef?: string;
|
|
53
|
+
}): unknown | Promise<unknown>;
|
|
49
54
|
/** Q27 effective model-profile catalog from the live session registry. */
|
|
50
55
|
getModelProfiles?(): unknown[] | Promise<unknown[]>;
|
|
51
56
|
/** Query rows backed by the session's installed binding map. */
|
|
@@ -127,6 +132,7 @@ const names = [
|
|
|
127
132
|
"runtime.jobs.list",
|
|
128
133
|
"turn.prompt_status",
|
|
129
134
|
"models.profiles.list",
|
|
135
|
+
"skill.invoke_status",
|
|
130
136
|
];
|
|
131
137
|
|
|
132
138
|
export class QueryHandlers {
|
|
@@ -156,6 +162,7 @@ export class QueryHandlers {
|
|
|
156
162
|
if (query === "Q02") return await this.#transcriptBody(request);
|
|
157
163
|
if (query === "Q23") return await this.#resourceBody(request);
|
|
158
164
|
if (query === "Q26") return await this.#promptStatus(request);
|
|
165
|
+
if (query === "Q28") return await this.#skillInvokeStatus(request);
|
|
159
166
|
if (query === "Q24") return await this.#artifact(request);
|
|
160
167
|
if (query === "Q27" && request.input && Object.keys(request.input).length > 0)
|
|
161
168
|
return this.#error(request, "invalid_request", false, "models.profiles.list does not accept input fields.");
|
|
@@ -517,6 +524,54 @@ export class QueryHandlers {
|
|
|
517
524
|
return { id: request.id, ok: true, page };
|
|
518
525
|
}
|
|
519
526
|
|
|
527
|
+
async #skillInvokeStatus(request: QueryRequest): Promise<QueryResponse> {
|
|
528
|
+
if (request.cursor)
|
|
529
|
+
return this.#error(request, "invalid_request", false, "skill.invoke_status does not support cursors.");
|
|
530
|
+
const input = request.input ?? {};
|
|
531
|
+
for (const key of Object.keys(input))
|
|
532
|
+
if (key !== "commandId" && key !== "turnId" && key !== "clientRef")
|
|
533
|
+
return this.#error(
|
|
534
|
+
request,
|
|
535
|
+
"invalid_request",
|
|
536
|
+
false,
|
|
537
|
+
`skill.invoke_status does not accept selector field "${key}".`,
|
|
538
|
+
);
|
|
539
|
+
const commandId = typeof input.commandId === "string" && input.commandId ? input.commandId : undefined;
|
|
540
|
+
const turnId = typeof input.turnId === "string" && input.turnId ? input.turnId : undefined;
|
|
541
|
+
const rawClientRef = typeof input.clientRef === "string" ? input.clientRef : undefined;
|
|
542
|
+
const trimmedClientRef = rawClientRef?.trim();
|
|
543
|
+
if (rawClientRef !== undefined && (!trimmedClientRef || trimmedClientRef.length > PROMPT_CLIENT_REF_MAX_LENGTH))
|
|
544
|
+
return this.#error(
|
|
545
|
+
request,
|
|
546
|
+
"invalid_request",
|
|
547
|
+
false,
|
|
548
|
+
"clientRef must be a non-empty string of at most 128 characters.",
|
|
549
|
+
);
|
|
550
|
+
const clientRef = trimmedClientRef || undefined;
|
|
551
|
+
if ((commandId === undefined) !== (turnId === undefined))
|
|
552
|
+
return this.#error(request, "invalid_request", false, "commandId and turnId must be provided together.");
|
|
553
|
+
if (commandId !== undefined && clientRef !== undefined)
|
|
554
|
+
return this.#error(
|
|
555
|
+
request,
|
|
556
|
+
"invalid_request",
|
|
557
|
+
false,
|
|
558
|
+
"Provide exactly one selector: a commandId/turnId pair or a clientRef.",
|
|
559
|
+
);
|
|
560
|
+
if (commandId === undefined && clientRef === undefined)
|
|
561
|
+
return this.#error(
|
|
562
|
+
request,
|
|
563
|
+
"invalid_request",
|
|
564
|
+
false,
|
|
565
|
+
"skill.invoke_status requires a commandId/turnId pair or a clientRef.",
|
|
566
|
+
);
|
|
567
|
+
if (typeof this.surface.getSkillInvokeStatus !== "function")
|
|
568
|
+
return this.#error(request, "unavailable", false, "skill.invoke_status is unavailable for this session.");
|
|
569
|
+
const result = await this.surface.getSkillInvokeStatus(
|
|
570
|
+
clientRef !== undefined ? { clientRef } : { commandId: commandId!, turnId: turnId! },
|
|
571
|
+
);
|
|
572
|
+
return { id: request.id, ok: true, result };
|
|
573
|
+
}
|
|
574
|
+
|
|
520
575
|
async #promptStatus(request: QueryRequest): Promise<QueryResponse> {
|
|
521
576
|
if (request.cursor)
|
|
522
577
|
return this.#error(request, "invalid_request", false, "turn.prompt_status does not support cursors.");
|