@sema-agent/client-core 0.104.0 → 0.105.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 +99 -0
- package/README.md +13 -5
- package/dist/adapt/arms.js +14 -6
- package/dist/adapter/activeRunSelfHeal.d.ts +1 -0
- package/dist/adapter/activeRunSelfHeal.js +10 -5
- package/dist/adapter/downstream/eventToSdkMessage.js +4 -4
- package/dist/adapter/downstream/wiringManifestView.d.ts +6 -0
- package/dist/adapter/downstream/wiringManifestView.js +25 -0
- package/dist/adapter/runStream.js +5 -1
- package/dist/cloudConfigWireCaps.js +3 -0
- package/dist/compensations.js +6 -6
- package/dist/coreValuePorts.d.ts +3 -1
- package/dist/coreValuePorts.js +4 -0
- package/dist/displayUntrusted.d.ts +4 -2
- package/dist/displayUntrusted.js +45 -22
- package/dist/engineGenerationPort.d.ts +11 -2
- package/dist/engineGenerationPort.js +63 -9
- package/dist/engineHealthFacts.d.ts +5 -0
- package/dist/engineHealthFacts.js +11 -0
- package/dist/eventLeg.d.ts +1 -1
- package/dist/eventLeg.js +23 -3
- package/dist/generated/engineFactTables.d.ts +1 -0
- package/dist/generated/engineFactTables.js +1 -0
- package/dist/generated/sdkFactTables.d.ts +2 -0
- package/dist/generated/sdkFactTables.js +9 -0
- package/dist/headlessReconnectWire.js +31 -4
- package/dist/hitl/askGateWire.js +26 -2
- package/dist/hitl/frameRouter.d.ts +4 -0
- package/dist/hitl/frameRouter.js +14 -0
- package/dist/hitl/gateIdentity.d.ts +1 -0
- package/dist/hitl/gateIdentity.js +3 -0
- package/dist/hitl/livePendingAsk.d.ts +2 -1
- package/dist/hitl/livePendingAsk.js +7 -1
- package/dist/hitl/planReviewCard.d.ts +21 -0
- package/dist/hitl/planReviewCard.js +229 -0
- package/dist/hitl/planReviewWire.d.ts +38 -13
- package/dist/hitl/planReviewWire.js +355 -133
- package/dist/hitl/toolApprovalWire.d.ts +2 -2
- package/dist/hitl/toolApprovalWire.js +53 -32
- package/dist/hostSettingsSource.d.ts +1 -1
- package/dist/hostSettingsSource.js +8 -4
- package/dist/hostTurnSettings.d.ts +1 -1
- package/dist/hostTurnSettings.js +29 -10
- package/dist/i18n/messages.en.d.ts +50 -0
- package/dist/i18n/messages.en.js +50 -0
- package/dist/i18n/messages.zh.js +51 -0
- package/dist/index.d.ts +15 -3
- package/dist/index.js +9 -1
- package/dist/mcpTransportKindsCapability.d.ts +18 -0
- package/dist/mcpTransportKindsCapability.js +75 -0
- package/dist/mcpWireCaps.d.ts +4 -1
- package/dist/mcpWireCaps.js +24 -13
- package/dist/notifications.d.ts +9 -0
- package/dist/notifications.js +11 -2
- package/dist/ownKey.d.ts +4 -0
- package/dist/ownKey.js +8 -0
- package/dist/permissionModeWriteCapability.d.ts +13 -0
- package/dist/permissionModeWriteCapability.js +35 -0
- package/dist/permissionsAllowGrantCapability.d.ts +3 -0
- package/dist/permissionsAllowGrantCapability.js +26 -3
- package/dist/permissionsDeclaration.d.ts +23 -1
- package/dist/permissionsDeclaration.js +60 -30
- package/dist/planReviewCardFacts.d.ts +116 -0
- package/dist/planReviewCardFacts.js +469 -0
- package/dist/reattachWindow.d.ts +8 -1
- package/dist/reattachWindow.js +44 -12
- package/dist/runFollow.d.ts +30 -0
- package/dist/runFollow.js +117 -0
- package/dist/runLedgerSnapshot.d.ts +20 -0
- package/dist/runLedgerSnapshot.js +272 -0
- package/dist/runPermissionModeSwitch.d.ts +57 -0
- package/dist/runPermissionModeSwitch.js +135 -0
- package/dist/runReattachPolicy.d.ts +13 -5
- package/dist/runReattachPolicy.js +58 -23
- package/dist/seam.d.ts +3 -0
- package/dist/seam.js +1 -1
- package/dist/seatContract.js +1 -0
- package/dist/sessionMemoryErase.d.ts +1 -1
- package/dist/sessionMemoryErase.js +5 -0
- package/dist/sessionMemoryStatus.d.ts +27 -1
- package/dist/sessionMemoryStatus.js +117 -0
- package/dist/sseIdleTriage.d.ts +3 -1
- package/dist/sseIdleTriage.js +19 -4
- package/dist/steering.d.ts +1 -0
- package/dist/steering.js +34 -26
- package/dist/subagent/engineSubagentTail.js +2 -0
- package/dist/subagentContentStore.d.ts +3 -0
- package/dist/subagentContentStore.js +10 -1
- package/dist/turnFailurePresentation.d.ts +5 -2
- package/dist/turnFailurePresentation.js +12 -4
- package/docs/INTEGRATION-CLIENTS.md +587 -10
- package/package.json +2 -2
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
import { displayUntrusted, displayUntrustedMarks, DISPLAY_DECISION_BODY } from './displayUntrusted.js';
|
|
2
|
+
import { readRunTerminal, runTerminalGateKind } from './runTerminal.js';
|
|
3
|
+
import { SDK_PLAN_REVIEW_GATE_KINDS } from './generated/sdkFactTables.js';
|
|
4
|
+
import { CORE_PRESENT_PLAN_TOOL_NAME } from './generated/engineFactTables.js';
|
|
5
|
+
import { DEFAULT_SESSION_KEY } from './sessionSlot.js';
|
|
6
|
+
const PLAN_BODY_MAX_LENGTH = 200_000;
|
|
7
|
+
const PLAN_BODY_UNKNOWN_CAUSES = Object.freeze([
|
|
8
|
+
'not_observed', 'gate_unidentified', 'gate_moved', 'unreadable', 'ambiguous_call', 'malformed_frame', 'conflicting_claims', 'not_presented', 'not_carried',
|
|
9
|
+
]);
|
|
10
|
+
const TEMPORARY_CAUSES = new Set(['unreadable', 'gate_moved', 'not_observed', 'gate_unidentified']);
|
|
11
|
+
const BODY_JUDGE_READING = Object.freeze({ ...DISPLAY_DECISION_BODY, credentialUrls: false, credentialWords: false });
|
|
12
|
+
export function planReviewBodyOf(text) {
|
|
13
|
+
try {
|
|
14
|
+
if (typeof text !== 'string')
|
|
15
|
+
return Object.freeze({ kind: 'marked', literal: '' });
|
|
16
|
+
if (text.length > PLAN_BODY_MAX_LENGTH)
|
|
17
|
+
return Object.freeze({ kind: 'too_large', length: text.length });
|
|
18
|
+
const laidOut = text.split('\r\n').join('\n');
|
|
19
|
+
if (displayUntrustedMarks(laidOut, BODY_JUDGE_READING).marks.length > 0) {
|
|
20
|
+
return Object.freeze({ kind: 'marked', literal: displayUntrusted(laidOut, { ...DISPLAY_DECISION_BODY, escapeBackslashes: true }) });
|
|
21
|
+
}
|
|
22
|
+
return Object.freeze({ kind: 'shown', markdown: laidOut });
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return Object.freeze({ kind: 'marked', literal: '' });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function mintPlanReviewCardFacts(taskId, checkpointId, body) {
|
|
29
|
+
const b = checkpointId === undefined ? unknownBody('gate_unidentified') : body;
|
|
30
|
+
return Object.freeze({ taskId, ...(checkpointId !== undefined ? { checkpointId } : {}), body: Object.isFrozen(b) ? b : Object.freeze({ ...b }) });
|
|
31
|
+
}
|
|
32
|
+
export function unknownBody(cause) {
|
|
33
|
+
return Object.freeze({ kind: 'unknown', cause });
|
|
34
|
+
}
|
|
35
|
+
function ownData(v, key) {
|
|
36
|
+
if (typeof v !== 'object' || v === null)
|
|
37
|
+
return { present: false };
|
|
38
|
+
const d = Object.getOwnPropertyDescriptor(v, key);
|
|
39
|
+
if (d === undefined)
|
|
40
|
+
return { present: false };
|
|
41
|
+
if (!Object.hasOwn(d, 'value'))
|
|
42
|
+
return { present: true };
|
|
43
|
+
return { present: true, value: d.value };
|
|
44
|
+
}
|
|
45
|
+
const nonEmpty = (v) => typeof v === 'string' && v.length > 0;
|
|
46
|
+
export function planReviewCardFactsFrom(v) {
|
|
47
|
+
try {
|
|
48
|
+
if (typeof v !== 'object' || v === null || Array.isArray(v))
|
|
49
|
+
return undefined;
|
|
50
|
+
const taskId = ownData(v, 'taskId').value;
|
|
51
|
+
if (!nonEmpty(taskId))
|
|
52
|
+
return undefined;
|
|
53
|
+
const ck = ownData(v, 'checkpointId');
|
|
54
|
+
if (ck.present && !nonEmpty(ck.value))
|
|
55
|
+
return undefined;
|
|
56
|
+
const body = readBody(ownData(v, 'body').value);
|
|
57
|
+
if (body === undefined)
|
|
58
|
+
return undefined;
|
|
59
|
+
const checkpointId = ck.present ? ck.value : undefined;
|
|
60
|
+
if (checkpointId === undefined && !(body.kind === 'unknown' && body.cause === 'gate_unidentified'))
|
|
61
|
+
return undefined;
|
|
62
|
+
return mintPlanReviewCardFacts(taskId, checkpointId, body);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function readBody(v) {
|
|
69
|
+
if (typeof v !== 'object' || v === null || Array.isArray(v))
|
|
70
|
+
return undefined;
|
|
71
|
+
const kind = ownData(v, 'kind').value;
|
|
72
|
+
if (kind === 'shown') {
|
|
73
|
+
const markdown = ownData(v, 'markdown').value;
|
|
74
|
+
return typeof markdown === 'string' ? Object.freeze({ kind, markdown }) : undefined;
|
|
75
|
+
}
|
|
76
|
+
if (kind === 'marked') {
|
|
77
|
+
const literal = ownData(v, 'literal').value;
|
|
78
|
+
return typeof literal === 'string' ? Object.freeze({ kind, literal }) : undefined;
|
|
79
|
+
}
|
|
80
|
+
if (kind === 'too_large') {
|
|
81
|
+
const length = ownData(v, 'length').value;
|
|
82
|
+
return typeof length === 'number' && Number.isInteger(length) && length > PLAN_BODY_MAX_LENGTH ? Object.freeze({ kind, length }) : undefined;
|
|
83
|
+
}
|
|
84
|
+
if (kind === 'unknown') {
|
|
85
|
+
const cause = ownData(v, 'cause').value;
|
|
86
|
+
return typeof cause === 'string' && PLAN_BODY_UNKNOWN_CAUSES.includes(cause) ? unknownBody(cause) : undefined;
|
|
87
|
+
}
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
export function samePlanReviewCardFacts(a, b) {
|
|
91
|
+
if (a === b)
|
|
92
|
+
return true;
|
|
93
|
+
if (a.taskId !== b.taskId || a.checkpointId !== b.checkpointId || a.body.kind !== b.body.kind)
|
|
94
|
+
return false;
|
|
95
|
+
const x = a.body;
|
|
96
|
+
const y = b.body;
|
|
97
|
+
return x.markdown === y.markdown && x.literal === y.literal && x.length === y.length && x.cause === y.cause;
|
|
98
|
+
}
|
|
99
|
+
function isPlanGateKind(kind) {
|
|
100
|
+
return typeof kind === 'string' && SDK_PLAN_REVIEW_GATE_KINDS.includes(kind);
|
|
101
|
+
}
|
|
102
|
+
export function isPlanReviewPark(result) {
|
|
103
|
+
const read = readRunTerminal(result);
|
|
104
|
+
if (read === null || read.kind !== 'paused')
|
|
105
|
+
return false;
|
|
106
|
+
if (!isPlanGateKind(runTerminalGateKind(read)))
|
|
107
|
+
return false;
|
|
108
|
+
const taskId = result?.taskId;
|
|
109
|
+
return typeof taskId === 'string' && taskId.length > 0;
|
|
110
|
+
}
|
|
111
|
+
export function pausedCheckpointIdOf(carrier) {
|
|
112
|
+
try {
|
|
113
|
+
const read = readRunTerminal(carrier);
|
|
114
|
+
if (read === null || read.kind !== 'paused')
|
|
115
|
+
return undefined;
|
|
116
|
+
const id = read.checkpointId;
|
|
117
|
+
return typeof id === 'string' && id.length > 0 ? id : undefined;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const SETTLED_CALLS_MAX = 10_000;
|
|
124
|
+
const PLAN_REVIEW_RUNS_MAX = 64;
|
|
125
|
+
const planReviewRuns = new Map();
|
|
126
|
+
const runKey = (sessionKey, taskId) => `${sessionKey}\u0000${taskId}`;
|
|
127
|
+
function emptyWindow() {
|
|
128
|
+
return { presented: [], malformed: false, ambiguous: false, unreadable: false };
|
|
129
|
+
}
|
|
130
|
+
function recordFor(sessionKey, taskId) {
|
|
131
|
+
const key = runKey(sessionKey, taskId);
|
|
132
|
+
let rec = planReviewRuns.get(key);
|
|
133
|
+
if (rec !== undefined) {
|
|
134
|
+
planReviewRuns.delete(key);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
rec = { open: new Set(), settled: new Set(), window: emptyWindow(), sawPlanFrame: false, dirty: false, generation: 0, cardIssued: false };
|
|
138
|
+
}
|
|
139
|
+
planReviewRuns.set(key, rec);
|
|
140
|
+
while (planReviewRuns.size > PLAN_REVIEW_RUNS_MAX) {
|
|
141
|
+
const oldest = planReviewRuns.keys().next();
|
|
142
|
+
if (oldest.done === true)
|
|
143
|
+
break;
|
|
144
|
+
planReviewRuns.delete(oldest.value);
|
|
145
|
+
}
|
|
146
|
+
return rec;
|
|
147
|
+
}
|
|
148
|
+
export function _resetPlanReviewRunsForTest() {
|
|
149
|
+
planReviewRuns.clear();
|
|
150
|
+
}
|
|
151
|
+
export function beginPlanReviewFactsRead(sessionKey, taskId) {
|
|
152
|
+
const rec = recordFor(sessionKey, taskId);
|
|
153
|
+
rec.generation += 1;
|
|
154
|
+
return { sessionKey, taskId, generation: rec.generation };
|
|
155
|
+
}
|
|
156
|
+
export function commitPlanReviewFacts(ticket, facts, claim, highWater) {
|
|
157
|
+
const rec = planReviewRuns.get(runKey(ticket.sessionKey, ticket.taskId));
|
|
158
|
+
if (rec === undefined || rec.generation !== ticket.generation)
|
|
159
|
+
return undefined;
|
|
160
|
+
const checkpointId = facts.checkpointId;
|
|
161
|
+
if (checkpointId === undefined)
|
|
162
|
+
return facts;
|
|
163
|
+
if (facts.body.kind === 'unknown' && TEMPORARY_CAUSES.has(facts.body.cause))
|
|
164
|
+
return facts;
|
|
165
|
+
const prev = rec.gate;
|
|
166
|
+
let out = facts;
|
|
167
|
+
if (prev !== undefined && prev.checkpointId === checkpointId && (isConflict(prev.facts) || !samePlanClaim(prev.claim, claim))) {
|
|
168
|
+
out = mintPlanReviewCardFacts(facts.taskId, checkpointId, unknownBody('conflicting_claims'));
|
|
169
|
+
}
|
|
170
|
+
rec.gate = { checkpointId, facts: out, claim, generation: ticket.generation, ...(highWater !== undefined ? { highWater } : {}) };
|
|
171
|
+
return out;
|
|
172
|
+
}
|
|
173
|
+
function isConflict(facts) {
|
|
174
|
+
return facts.body.kind === 'unknown' && facts.body.cause === 'conflicting_claims';
|
|
175
|
+
}
|
|
176
|
+
export function samePlanClaim(a, b) {
|
|
177
|
+
if (a.kind === 'text' && b.kind === 'text')
|
|
178
|
+
return a.text === b.text;
|
|
179
|
+
if (a.kind === 'cause' && b.kind === 'cause')
|
|
180
|
+
return a.cause === b.cause;
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
export function registeredPlanReviewCardFacts(sessionKey, taskId, checkpointId) {
|
|
184
|
+
const gate = currentPlanReviewGateOf(sessionKey, taskId);
|
|
185
|
+
return gate !== undefined && gate.checkpointId === checkpointId ? gate.facts : undefined;
|
|
186
|
+
}
|
|
187
|
+
export function currentPlanReviewGateOf(sessionKey, taskId) {
|
|
188
|
+
const rec = planReviewRuns.get(runKey(sessionKey, taskId));
|
|
189
|
+
const gate = rec?.gate;
|
|
190
|
+
return rec !== undefined && gate !== undefined && gate.generation === rec.generation
|
|
191
|
+
? { checkpointId: gate.checkpointId, facts: gate.facts, generation: gate.generation }
|
|
192
|
+
: undefined;
|
|
193
|
+
}
|
|
194
|
+
export function planReviewRunGeneration(sessionKey, taskId) {
|
|
195
|
+
return recordFor(sessionKey, taskId).generation;
|
|
196
|
+
}
|
|
197
|
+
export function notePlanReviewCardIssued(sessionKey, taskId) {
|
|
198
|
+
const rec = recordFor(sessionKey, taskId);
|
|
199
|
+
const before = rec.cardIssued;
|
|
200
|
+
rec.cardIssued = true;
|
|
201
|
+
return before;
|
|
202
|
+
}
|
|
203
|
+
export function planReviewGateHighWaterOf(sessionKey, taskId, checkpointId) {
|
|
204
|
+
const gate = planReviewRuns.get(runKey(sessionKey, taskId))?.gate;
|
|
205
|
+
return gate !== undefined && gate.checkpointId === checkpointId ? gate.highWater : undefined;
|
|
206
|
+
}
|
|
207
|
+
function claimOf(rec) {
|
|
208
|
+
const w = rec.window;
|
|
209
|
+
if (w.unreadable)
|
|
210
|
+
return { kind: 'cause', cause: 'unreadable' };
|
|
211
|
+
if (w.malformed)
|
|
212
|
+
return { kind: 'cause', cause: 'malformed_frame' };
|
|
213
|
+
if (rec.open.size > 0 || w.ambiguous || w.presented.length > 1)
|
|
214
|
+
return { kind: 'cause', cause: 'ambiguous_call' };
|
|
215
|
+
const only = w.presented[0];
|
|
216
|
+
if (only === undefined)
|
|
217
|
+
return { kind: 'cause', cause: rec.sawPlanFrame ? 'not_presented' : 'not_observed' };
|
|
218
|
+
return only.plan === undefined ? { kind: 'cause', cause: 'not_carried' } : { kind: 'text', text: only.plan };
|
|
219
|
+
}
|
|
220
|
+
function planMarker(rec, gate) {
|
|
221
|
+
if (!rec.dirty && rec.claimAtMarker !== undefined && (gate === undefined || rec.claimGate === undefined || rec.claimGate === gate)) {
|
|
222
|
+
if (gate !== undefined)
|
|
223
|
+
rec.claimGate = gate;
|
|
224
|
+
return rec.claimAtMarker;
|
|
225
|
+
}
|
|
226
|
+
const claim = claimOf(rec);
|
|
227
|
+
rec.claimAtMarker = claim;
|
|
228
|
+
rec.claimGate = gate;
|
|
229
|
+
rec.dirty = false;
|
|
230
|
+
rec.window = emptyWindow();
|
|
231
|
+
return claim;
|
|
232
|
+
}
|
|
233
|
+
function otherMarker(rec) {
|
|
234
|
+
rec.claimAtMarker = undefined;
|
|
235
|
+
rec.claimGate = undefined;
|
|
236
|
+
rec.dirty = false;
|
|
237
|
+
rec.window = emptyWindow();
|
|
238
|
+
}
|
|
239
|
+
function settle(rec, id) {
|
|
240
|
+
rec.settled.add(id);
|
|
241
|
+
while (rec.settled.size > SETTLED_CALLS_MAX) {
|
|
242
|
+
const oldest = rec.settled.values().next();
|
|
243
|
+
if (oldest.done === true)
|
|
244
|
+
break;
|
|
245
|
+
rec.settled.delete(oldest.value);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
function classOf(frame) {
|
|
249
|
+
const type = ownData(frame, 'type').value;
|
|
250
|
+
if (type === 'tool_start' || type === 'tool_end')
|
|
251
|
+
return ownData(frame, 'toolName').value === CORE_PRESENT_PLAN_TOOL_NAME ? 'plan_call' : 'other';
|
|
252
|
+
if (type === 'needs_review' || type === 'suspended')
|
|
253
|
+
return 'marker';
|
|
254
|
+
if (type === 'done')
|
|
255
|
+
return 'done';
|
|
256
|
+
if (type === 'meta')
|
|
257
|
+
return 'meta';
|
|
258
|
+
return 'other';
|
|
259
|
+
}
|
|
260
|
+
function foldPlanCall(rec, frame) {
|
|
261
|
+
rec.sawPlanFrame = true;
|
|
262
|
+
const id = ownData(frame, 'toolCallId').value;
|
|
263
|
+
if (!nonEmpty(id)) {
|
|
264
|
+
rec.window.malformed = true;
|
|
265
|
+
rec.dirty = true;
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (rec.settled.has(id))
|
|
269
|
+
return;
|
|
270
|
+
rec.dirty = true;
|
|
271
|
+
if (ownData(frame, 'type').value === 'tool_start') {
|
|
272
|
+
if (ownData(frame, 'recovered').value === true)
|
|
273
|
+
rec.window.ambiguous = true;
|
|
274
|
+
rec.open.add(id);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const started = rec.open.delete(id);
|
|
278
|
+
settle(rec, id);
|
|
279
|
+
if (!started) {
|
|
280
|
+
rec.window.ambiguous = true;
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
if (ownData(frame, 'isError').value !== false || ownData(frame, 'notExecuted').value === true)
|
|
284
|
+
return;
|
|
285
|
+
const s = ownData(frame, 'structured');
|
|
286
|
+
if (!s.present || s.value === undefined) {
|
|
287
|
+
rec.window.presented.push({});
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
const sv = s.value;
|
|
291
|
+
if (typeof sv !== 'object' || sv === null || Array.isArray(sv) || ownData(sv, 'type').value !== 'exit-plan-mode') {
|
|
292
|
+
rec.window.malformed = true;
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const plan = ownData(sv, 'plan').value;
|
|
296
|
+
if (typeof plan !== 'string') {
|
|
297
|
+
rec.window.malformed = true;
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
rec.window.presented.push({ plan });
|
|
301
|
+
}
|
|
302
|
+
function markerGateOf(frame) {
|
|
303
|
+
const v = ownData(frame, 'checkpointId').value;
|
|
304
|
+
return nonEmpty(v) ? v : undefined;
|
|
305
|
+
}
|
|
306
|
+
function foldMarker(rec, frame) {
|
|
307
|
+
const type = ownData(frame, 'type').value;
|
|
308
|
+
const gate = ownData(frame, 'gate');
|
|
309
|
+
if (type === 'needs_review') {
|
|
310
|
+
if (!gate.present || (gate.value !== null && (typeof gate.value !== 'object' || Array.isArray(gate.value)))) {
|
|
311
|
+
rec.window.malformed = true;
|
|
312
|
+
rec.dirty = true;
|
|
313
|
+
planMarker(rec);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (gate.value === null) {
|
|
317
|
+
planMarker(rec, markerGateOf(frame));
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
const kind = ownData(gate.value, 'kind').value;
|
|
321
|
+
if (typeof kind !== 'string') {
|
|
322
|
+
rec.window.malformed = true;
|
|
323
|
+
rec.dirty = true;
|
|
324
|
+
planMarker(rec);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (isPlanGateKind(kind))
|
|
328
|
+
planMarker(rec, markerGateOf(frame));
|
|
329
|
+
else
|
|
330
|
+
otherMarker(rec);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
const kind = gate.present && typeof gate.value === 'object' && gate.value !== null ? ownData(gate.value, 'kind').value : undefined;
|
|
334
|
+
if (isPlanGateKind(kind))
|
|
335
|
+
planMarker(rec, markerGateOf(frame));
|
|
336
|
+
else
|
|
337
|
+
otherMarker(rec);
|
|
338
|
+
}
|
|
339
|
+
const LEG_BUFFER_MAX = 256;
|
|
340
|
+
export function openPlanBodyClaimLeg() {
|
|
341
|
+
return { buffer: [], overflow: false, unreadable: false };
|
|
342
|
+
}
|
|
343
|
+
function bindLeg(leg, taskId) {
|
|
344
|
+
const rec = recordFor(DEFAULT_SESSION_KEY, taskId);
|
|
345
|
+
if (leg.taskId === taskId)
|
|
346
|
+
return rec;
|
|
347
|
+
const pending = leg.buffer.splice(0);
|
|
348
|
+
const lost = leg.overflow || leg.unreadable;
|
|
349
|
+
leg.taskId = taskId;
|
|
350
|
+
leg.overflow = false;
|
|
351
|
+
leg.unreadable = false;
|
|
352
|
+
if (lost) {
|
|
353
|
+
rec.window.unreadable = true;
|
|
354
|
+
rec.dirty = true;
|
|
355
|
+
}
|
|
356
|
+
for (const p of pending)
|
|
357
|
+
foldInto(rec, p.frame, p.subFlow);
|
|
358
|
+
return rec;
|
|
359
|
+
}
|
|
360
|
+
function foldInto(rec, frame, subFlow) {
|
|
361
|
+
try {
|
|
362
|
+
const c = classOf(frame);
|
|
363
|
+
if (c === 'plan_call') {
|
|
364
|
+
if (!subFlow)
|
|
365
|
+
foldPlanCall(rec, frame);
|
|
366
|
+
}
|
|
367
|
+
else if (c === 'marker') {
|
|
368
|
+
if (!subFlow)
|
|
369
|
+
foldMarker(rec, frame);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
catch {
|
|
373
|
+
rec.window.unreadable = true;
|
|
374
|
+
rec.dirty = true;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
export function stepPlanBodyClaim(leg, frame, subFlow) {
|
|
378
|
+
try {
|
|
379
|
+
const c = classOf(frame);
|
|
380
|
+
if (c === 'meta') {
|
|
381
|
+
const taskId = ownData(frame, 'taskId').value;
|
|
382
|
+
if (nonEmpty(taskId))
|
|
383
|
+
bindLeg(leg, taskId);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (c !== 'plan_call' && c !== 'marker')
|
|
387
|
+
return;
|
|
388
|
+
if (leg.taskId === undefined) {
|
|
389
|
+
if (leg.buffer.length >= LEG_BUFFER_MAX)
|
|
390
|
+
leg.overflow = true;
|
|
391
|
+
else
|
|
392
|
+
leg.buffer.push({ frame, subFlow });
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
foldInto(recordFor(DEFAULT_SESSION_KEY, leg.taskId), frame, subFlow);
|
|
396
|
+
}
|
|
397
|
+
catch {
|
|
398
|
+
leg.unreadable = true;
|
|
399
|
+
if (leg.taskId !== undefined) {
|
|
400
|
+
const rec = recordFor(DEFAULT_SESSION_KEY, leg.taskId);
|
|
401
|
+
rec.window.unreadable = true;
|
|
402
|
+
rec.dirty = true;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
export function planReviewParkFacts(leg, result) {
|
|
407
|
+
let taskId;
|
|
408
|
+
let checkpointId;
|
|
409
|
+
try {
|
|
410
|
+
if (!isPlanReviewPark(result)) {
|
|
411
|
+
const read = readRunTerminal(result);
|
|
412
|
+
const tid = ownData(result, 'taskId').value;
|
|
413
|
+
if (read !== null && read.kind === 'paused' && nonEmpty(tid))
|
|
414
|
+
otherMarker(legRecord(leg, tid));
|
|
415
|
+
return undefined;
|
|
416
|
+
}
|
|
417
|
+
taskId = result.taskId;
|
|
418
|
+
checkpointId = pausedCheckpointIdOf(result);
|
|
419
|
+
const claim = planMarker(legRecord(leg, taskId), checkpointId);
|
|
420
|
+
const body = claim.kind === 'text' ? planReviewBodyOf(claim.text) : unknownBody(claim.cause);
|
|
421
|
+
const ticket = beginPlanReviewFactsRead(DEFAULT_SESSION_KEY, taskId);
|
|
422
|
+
return commitPlanReviewFacts(ticket, mintPlanReviewCardFacts(taskId, checkpointId, body), claim);
|
|
423
|
+
}
|
|
424
|
+
catch {
|
|
425
|
+
return taskId === undefined ? undefined : mintPlanReviewCardFacts(taskId, checkpointId, unknownBody('unreadable'));
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
function legRecord(leg, taskId) {
|
|
429
|
+
return leg.taskId === undefined || leg.taskId === taskId ? bindLeg(leg, taskId) : recordFor(DEFAULT_SESSION_KEY, taskId);
|
|
430
|
+
}
|
|
431
|
+
export function planReviewFactsCurrent(taken) {
|
|
432
|
+
const rec = planReviewRuns.get(runKey(taken.sessionKey, taken.taskId));
|
|
433
|
+
return rec !== undefined && rec.generation === taken.generation;
|
|
434
|
+
}
|
|
435
|
+
export function planGateRowReadOf(row) {
|
|
436
|
+
try {
|
|
437
|
+
const result = ownData(row, 'result').value;
|
|
438
|
+
const read = readRunTerminal(result);
|
|
439
|
+
const atPlanGate = read !== null && read.kind === 'paused' && isPlanGateKind(runTerminalGateKind(read));
|
|
440
|
+
const checkpointId = pausedCheckpointIdOf(result);
|
|
441
|
+
return { ok: true, atPlanGate, ...(checkpointId !== undefined ? { checkpointId } : {}) };
|
|
442
|
+
}
|
|
443
|
+
catch {
|
|
444
|
+
return { ok: false, atPlanGate: false };
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
export function openPlanReviewLedgerFold() {
|
|
448
|
+
return { open: new Set(), settled: new Set(), window: emptyWindow(), sawPlanFrame: true, dirty: false, generation: 0, cardIssued: false };
|
|
449
|
+
}
|
|
450
|
+
export function stepPlanReviewLedgerFold(fold, frame, subFlow) {
|
|
451
|
+
if (typeof frame === 'object' && frame !== null)
|
|
452
|
+
foldInto(fold, frame, subFlow);
|
|
453
|
+
return fold;
|
|
454
|
+
}
|
|
455
|
+
export function planReviewLedgerClaimOf(fold) {
|
|
456
|
+
return fold.dirty ? undefined : fold.claimAtMarker;
|
|
457
|
+
}
|
|
458
|
+
export function isPlanReviewClaimFrame(frame) {
|
|
459
|
+
if (typeof frame !== 'object' || frame === null)
|
|
460
|
+
return false;
|
|
461
|
+
let c;
|
|
462
|
+
try {
|
|
463
|
+
c = classOf(frame);
|
|
464
|
+
}
|
|
465
|
+
catch {
|
|
466
|
+
c = 'unreadable';
|
|
467
|
+
}
|
|
468
|
+
return c === 'plan_call' || c === 'marker' || c === 'unreadable';
|
|
469
|
+
}
|
package/dist/reattachWindow.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ReattachBreak, type RunNotFollowed } from './runFollow.js';
|
|
1
2
|
export declare const REATTACH_WINDOW_MS = 300000;
|
|
2
3
|
export declare const REATTACH_BACKOFF_START_MS = 1000;
|
|
3
4
|
export declare const REATTACH_BACKOFF_CAP_MS = 30000;
|
|
@@ -25,8 +26,10 @@ export interface ReattachWindowState {
|
|
|
25
26
|
readonly consecutiveNotFound: number;
|
|
26
27
|
readonly notFoundConfirmed: boolean;
|
|
27
28
|
readonly tuning: Readonly<Tuned>;
|
|
29
|
+
readonly runId?: string;
|
|
30
|
+
readonly lastBreak?: ReattachBreak;
|
|
28
31
|
}
|
|
29
|
-
export declare function reattachWindowOpen(nowMs: number, tuning?: ReattachWindowTuning): ReattachWindowState;
|
|
32
|
+
export declare function reattachWindowOpen(nowMs: number, tuning?: ReattachWindowTuning, runId?: string): ReattachWindowState;
|
|
30
33
|
export type ReattachProbeResult = 'readable' | 'unreachable' | 'not-found';
|
|
31
34
|
export type ReattachWindowEvent = {
|
|
32
35
|
readonly type: 'attempt';
|
|
@@ -40,6 +43,8 @@ export type ReattachWindowEvent = {
|
|
|
40
43
|
} | {
|
|
41
44
|
readonly type: 'backoff';
|
|
42
45
|
readonly serverRetryAfterMs?: number;
|
|
46
|
+
readonly break?: ReattachBreak;
|
|
47
|
+
readonly runId?: string;
|
|
43
48
|
} | {
|
|
44
49
|
readonly type: 'new-segment';
|
|
45
50
|
};
|
|
@@ -51,9 +56,11 @@ export type ReattachWindowDecision = {
|
|
|
51
56
|
readonly afterMs: number;
|
|
52
57
|
} | {
|
|
53
58
|
readonly action: 'window-exhausted';
|
|
59
|
+
readonly notFollowed: RunNotFollowed<'reattach_exhausted' | 'bad_input'>;
|
|
54
60
|
} | {
|
|
55
61
|
readonly action: 'run-unknown';
|
|
56
62
|
readonly confirmations: number;
|
|
63
|
+
readonly notFollowed: RunNotFollowed<'run_unknown'>;
|
|
57
64
|
};
|
|
58
65
|
export interface ReattachWindowStep {
|
|
59
66
|
readonly state: ReattachWindowState;
|
package/dist/reattachWindow.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { usableClockMs } from './clockReading.js';
|
|
2
2
|
import { readThrownSlot, THROWN_SLOT_UNREADABLE } from './wireFailureShape.js';
|
|
3
|
+
import { isReattachBreak, mintRunNotFollowed } from './runFollow.js';
|
|
3
4
|
export const REATTACH_WINDOW_MS = 300_000;
|
|
4
5
|
export const REATTACH_BACKOFF_START_MS = 1_000;
|
|
5
6
|
export const REATTACH_BACKOFF_CAP_MS = 30_000;
|
|
@@ -25,15 +26,37 @@ export function reattachBackoffMs(attempt, tuning) {
|
|
|
25
26
|
const raw = startMs * 2 ** (attempt - 1);
|
|
26
27
|
return Number.isFinite(raw) ? Math.min(raw, capMs) : capMs;
|
|
27
28
|
}
|
|
28
|
-
|
|
29
|
+
const isRunHandle = (v) => typeof v === 'string' && v !== '';
|
|
30
|
+
export function reattachWindowOpen(nowMs, tuning, runId) {
|
|
29
31
|
const t = tuned(tuning);
|
|
30
32
|
const start = usableClockMs(nowMs) ?? Number.NaN;
|
|
31
|
-
return Object.freeze({
|
|
33
|
+
return Object.freeze({
|
|
34
|
+
enteredAtMs: start,
|
|
35
|
+
deadlineMs: start + t.windowMs,
|
|
36
|
+
attempt: 0,
|
|
37
|
+
consecutiveNotFound: 0,
|
|
38
|
+
notFoundConfirmed: false,
|
|
39
|
+
tuning: Object.freeze(t),
|
|
40
|
+
...(isRunHandle(runId) ? { runId } : {}),
|
|
41
|
+
});
|
|
32
42
|
}
|
|
33
43
|
const step = (state, decision) => Object.freeze({ state, decision });
|
|
34
|
-
const
|
|
44
|
+
const runOf = (state) => {
|
|
45
|
+
const v = readThrownSlot(state, 'runId', 'data');
|
|
46
|
+
return isRunHandle(v) ? v : undefined;
|
|
47
|
+
};
|
|
48
|
+
const exhausted = (state) => {
|
|
49
|
+
const brk = readThrownSlot(state, 'lastBreak', 'data');
|
|
50
|
+
return step(state, Object.freeze({ action: 'window-exhausted', notFollowed: mintRunNotFollowed('reattach_exhausted', runOf(state), isReattachBreak(brk) ? brk : undefined) }));
|
|
51
|
+
};
|
|
52
|
+
const badInput = (state, runId) => step(state, Object.freeze({ action: 'window-exhausted', notFollowed: mintRunNotFollowed('bad_input', runId) }));
|
|
35
53
|
const cont = (state, notFoundConfirmed = false) => step(state, Object.freeze({ action: 'continue', notFoundConfirmed }));
|
|
36
|
-
const next = (state, patch) =>
|
|
54
|
+
const next = (state, patch, lastBreak) => {
|
|
55
|
+
if (lastBreak === undefined)
|
|
56
|
+
return Object.freeze({ ...state, ...patch });
|
|
57
|
+
const { lastBreak: _previous, ...rest } = state;
|
|
58
|
+
return Object.freeze(lastBreak.to === undefined ? { ...rest, ...patch } : { ...rest, ...patch, lastBreak: lastBreak.to });
|
|
59
|
+
};
|
|
37
60
|
const isCount = (v) => typeof v === 'number' && Number.isSafeInteger(v) && v >= 0;
|
|
38
61
|
function validState(s) {
|
|
39
62
|
if (typeof s !== 'object' || s === null)
|
|
@@ -42,18 +65,20 @@ function validState(s) {
|
|
|
42
65
|
return typeof o.enteredAtMs === 'number' && typeof o.deadlineMs === 'number' && isCount(o.attempt) && isCount(o.consecutiveNotFound) && typeof o.notFoundConfirmed === 'boolean' && typeof o.tuning === 'object' && o.tuning !== null;
|
|
43
66
|
}
|
|
44
67
|
export function reattachWindowStep(state, event, nowMs) {
|
|
68
|
+
let windowRun;
|
|
45
69
|
try {
|
|
70
|
+
windowRun = runOf(state);
|
|
46
71
|
if (!validState(state))
|
|
47
|
-
return
|
|
72
|
+
return badInput(reattachWindowOpen(Number.NaN, undefined, windowRun), windowRun);
|
|
48
73
|
if (typeof event !== 'object' || event === null)
|
|
49
74
|
return cont(state);
|
|
50
75
|
const type = event.type;
|
|
51
76
|
const result = event.result;
|
|
52
77
|
const serverRetryAfterMs = event.serverRetryAfterMs;
|
|
53
78
|
if (type === 'new-segment')
|
|
54
|
-
return cont(reattachWindowOpen(nowMs, state.tuning));
|
|
55
|
-
if (usableClockMs(nowMs) === null || !Number.isFinite(state.deadlineMs))
|
|
56
|
-
return
|
|
79
|
+
return cont(reattachWindowOpen(nowMs, state.tuning, windowRun));
|
|
80
|
+
if (usableClockMs(nowMs) === null || usableClockMs(state.enteredAtMs) === null || !Number.isFinite(state.deadlineMs))
|
|
81
|
+
return badInput(state, windowRun);
|
|
57
82
|
switch (type) {
|
|
58
83
|
case 'attempt':
|
|
59
84
|
return nowMs < state.deadlineMs ? cont(next(state, { attempt: state.attempt + 1 })) : exhausted(state);
|
|
@@ -70,18 +95,25 @@ export function reattachWindowStep(state, event, nowMs) {
|
|
|
70
95
|
case 'tail-frame':
|
|
71
96
|
return cont(next(state, { consecutiveNotFound: 0, notFoundConfirmed: false }));
|
|
72
97
|
case 'tail-refused-404':
|
|
73
|
-
return state.notFoundConfirmed
|
|
98
|
+
return state.notFoundConfirmed
|
|
99
|
+
? step(state, Object.freeze({ action: 'run-unknown', confirmations: state.consecutiveNotFound, notFollowed: mintRunNotFollowed('run_unknown', windowRun) }))
|
|
100
|
+
: cont(state);
|
|
74
101
|
case 'backoff': {
|
|
75
|
-
const
|
|
102
|
+
const brkRead = readThrownSlot(event, 'break');
|
|
103
|
+
const brk = isReattachBreak(brkRead) ? brkRead : undefined;
|
|
104
|
+
const handoffRead = readThrownSlot(event, 'runId');
|
|
105
|
+
const handoff = isRunHandle(handoffRead) && handoffRead !== windowRun ? handoffRead : undefined;
|
|
106
|
+
const moved = next(state, handoff !== undefined ? { runId: handoff, consecutiveNotFound: 0, notFoundConfirmed: false } : {}, { to: brk });
|
|
107
|
+
const own = reattachBackoffMs(moved.attempt, moved.tuning);
|
|
76
108
|
const delay = typeof serverRetryAfterMs === 'number' && Number.isFinite(serverRetryAfterMs) && serverRetryAfterMs > 0 ? Math.max(own, serverRetryAfterMs) : own;
|
|
77
|
-
return nowMs + delay >=
|
|
109
|
+
return nowMs + delay >= moved.deadlineMs ? exhausted(moved) : step(moved, Object.freeze({ action: 'wait', afterMs: delay }));
|
|
78
110
|
}
|
|
79
111
|
default:
|
|
80
112
|
return cont(state);
|
|
81
113
|
}
|
|
82
114
|
}
|
|
83
115
|
catch {
|
|
84
|
-
return
|
|
116
|
+
return badInput(reattachWindowOpen(Number.NaN, undefined, windowRun), windowRun);
|
|
85
117
|
}
|
|
86
118
|
}
|
|
87
119
|
export function poisonFrameInitial() {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { UiLanguage } from './env/uiLanguage.js';
|
|
2
|
+
import { type MessageRef } from './i18n/translate.js';
|
|
3
|
+
import type { ReattachEvent } from './runReattachPolicy.js';
|
|
4
|
+
export type RunNotFollowedCause = 'reattach_exhausted' | 'no_run_id' | 'bad_input' | 'run_unknown' | 'target_unconfirmed' | 'cursor_unknown';
|
|
5
|
+
export type ReattachBreak = ReattachEvent['cause'];
|
|
6
|
+
export type RunNotFollowed<C extends RunNotFollowedCause = RunNotFollowedCause> = C extends 'reattach_exhausted' ? RunNotFollowedExhausted : RunNotFollowedFact<C>;
|
|
7
|
+
interface RunNotFollowedFact<C extends RunNotFollowedCause> {
|
|
8
|
+
readonly kind: 'not_followable';
|
|
9
|
+
readonly cause: C;
|
|
10
|
+
readonly taskId?: string;
|
|
11
|
+
}
|
|
12
|
+
interface RunNotFollowedExhausted extends RunNotFollowedFact<'reattach_exhausted'> {
|
|
13
|
+
readonly lastBreak?: ReattachBreak;
|
|
14
|
+
}
|
|
15
|
+
export declare const isReattachBreak: (v: {} | null | undefined) => v is ReattachBreak;
|
|
16
|
+
export declare function mintRunNotFollowed<C extends RunNotFollowedCause>(cause: C, taskId?: string, lastBreak?: ReattachBreak): RunNotFollowed<C>;
|
|
17
|
+
export declare function registerRunNotFollowed(thrown: object, fact: RunNotFollowed): void;
|
|
18
|
+
export declare function runNotFollowedOf(err: {} | null | undefined): RunNotFollowed | undefined;
|
|
19
|
+
interface NoticeParts {
|
|
20
|
+
readonly reason: MessageRef;
|
|
21
|
+
readonly ending: MessageRef | '';
|
|
22
|
+
readonly exit: MessageRef | '';
|
|
23
|
+
}
|
|
24
|
+
export declare function runNotFollowedHeadRef(taskId: string | undefined, parts: NoticeParts): MessageRef;
|
|
25
|
+
export declare function runNotFollowedReasonRef(cause: RunNotFollowedCause, lastBreak?: ReattachBreak): MessageRef;
|
|
26
|
+
export declare function runNotFollowedEndingUnknownRef(): MessageRef;
|
|
27
|
+
export declare function runNotFollowedExitRef(cause: RunNotFollowedCause): MessageRef | '';
|
|
28
|
+
export declare function runNotFollowedNoticeRef(fact: RunNotFollowed): MessageRef;
|
|
29
|
+
export declare function runNotFollowedNotice(fact: RunNotFollowed, lang?: UiLanguage): string;
|
|
30
|
+
export {};
|