@sema-agent/client-core 0.85.0 → 0.85.2
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 +91 -0
- package/README.md +5 -5
- package/dist/adapter/activeRunSelfHeal.d.ts +6 -0
- package/dist/adapter/activeRunSelfHeal.js +272 -50
- package/dist/hitl/armedGateRegistry.d.ts +1 -0
- package/dist/hitl/armedGateRegistry.js +1 -1
- package/dist/hitl/planReviewWire.js +14 -3
- package/dist/host.d.ts +1 -1
- package/dist/host.js +11 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/request/taskRequest.d.ts +2 -0
- package/dist/request/taskRequest.js +12 -0
- package/dist/subagent/subagentOwnerAbsence.js +2 -5
- package/docs/INTEGRATION-CLIENTS.md +335 -14
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { abortableSleep } from '../abortableSleep.js';
|
|
2
|
-
import { corruptStoredRowContent, corruptStoredRowWhere } from '../wireErrorTriage.js';
|
|
2
|
+
import { corruptStoredRowContent, corruptStoredRowWhere, effectiveWireErrorCode } from '../wireErrorTriage.js';
|
|
3
3
|
export const PLAN_REVIEW_GATE_KIND = 'plan_review';
|
|
4
4
|
export const PLAN_REVIEW_GATE_KINDS = Object.freeze([PLAN_REVIEW_GATE_KIND, 'dry_run_review']);
|
|
5
5
|
export const ASK_PARK_GATE_KINDS = Object.freeze(['human', 'irreversible_ask', 'policy_ask', 'tool_approval']);
|
|
@@ -45,20 +45,42 @@ function sessionOpts(deps) {
|
|
|
45
45
|
const sessionId = deps?.sessionId;
|
|
46
46
|
return typeof sessionId === 'string' && sessionId.length > 0 ? { session: sessionId } : {};
|
|
47
47
|
}
|
|
48
|
+
function errorSlot(e, key) {
|
|
49
|
+
if (e === null || e === undefined)
|
|
50
|
+
return undefined;
|
|
51
|
+
try {
|
|
52
|
+
return e[key];
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
48
58
|
function describeFailure(e) {
|
|
49
59
|
const where = corruptStoredRowWhere(e);
|
|
50
60
|
const rowNote = where === undefined ? '' : ` — ${corruptStoredRowContent(where)}`;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
return `${m.length > 200 ? `${m.slice(0, 200)}…` : m}${rowNote}`;
|
|
61
|
+
let m;
|
|
62
|
+
try {
|
|
63
|
+
m = typeof e === 'object' && e !== null && 'message' in e ? e.message : undefined;
|
|
55
64
|
}
|
|
65
|
+
catch {
|
|
66
|
+
m = undefined;
|
|
67
|
+
}
|
|
68
|
+
if (typeof m === 'string' && m.length > 0)
|
|
69
|
+
return `${m.length > 200 ? `${m.slice(0, 200)}…` : m}${rowNote}`;
|
|
56
70
|
return `the engine gave no detail${rowNote}`;
|
|
57
71
|
}
|
|
58
72
|
function readStatus(record) {
|
|
59
73
|
const st = record?.status;
|
|
60
74
|
return typeof st === 'string' && st.length > 0 ? st : null;
|
|
61
75
|
}
|
|
76
|
+
function readStatusGuarded(record) {
|
|
77
|
+
try {
|
|
78
|
+
return readStatus(record);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
62
84
|
function readClaimHolder(record) {
|
|
63
85
|
if (record === null || typeof record !== 'object')
|
|
64
86
|
return 'unknown';
|
|
@@ -79,11 +101,129 @@ export function readCancelRequested(body) {
|
|
|
79
101
|
const v = body.cancelRequested;
|
|
80
102
|
return typeof v === 'boolean' ? v : undefined;
|
|
81
103
|
}
|
|
104
|
+
const REOPEN_VERDICT_KEYS = [
|
|
105
|
+
'reopened',
|
|
106
|
+
'firstSight',
|
|
107
|
+
'presented',
|
|
108
|
+
'decidedWithoutCard',
|
|
109
|
+
'pendingRowGone',
|
|
110
|
+
'_sema_decisionInFlight',
|
|
111
|
+
'dismissedByUser',
|
|
112
|
+
'_sema_noPresentationSurface',
|
|
113
|
+
'_sema_errorCode',
|
|
114
|
+
];
|
|
115
|
+
function snapshotReopenVerdict(raw) {
|
|
116
|
+
if (raw === null || raw === undefined)
|
|
117
|
+
return { reopened: false };
|
|
118
|
+
if (typeof raw !== 'object' && typeof raw !== 'function')
|
|
119
|
+
return raw;
|
|
120
|
+
const src = raw;
|
|
121
|
+
let nullProto = false;
|
|
122
|
+
try {
|
|
123
|
+
nullProto = Object.getPrototypeOf(raw) === null;
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
nullProto = false;
|
|
127
|
+
}
|
|
128
|
+
const snap = nullProto ? Object.create(null) : {};
|
|
129
|
+
const put = (k, v) => {
|
|
130
|
+
Object.defineProperty(snap, k, { value: v, enumerable: true, writable: true, configurable: true });
|
|
131
|
+
};
|
|
132
|
+
let own = [];
|
|
133
|
+
try {
|
|
134
|
+
own = Reflect.ownKeys(raw);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
own = [];
|
|
138
|
+
}
|
|
139
|
+
const copied = new Set();
|
|
140
|
+
for (const k of own) {
|
|
141
|
+
let enumerable = false;
|
|
142
|
+
try {
|
|
143
|
+
enumerable = Object.prototype.propertyIsEnumerable.call(raw, k);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
enumerable = false;
|
|
147
|
+
}
|
|
148
|
+
if (!enumerable)
|
|
149
|
+
continue;
|
|
150
|
+
let v;
|
|
151
|
+
let readable = true;
|
|
152
|
+
try {
|
|
153
|
+
v = src[k];
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
readable = false;
|
|
157
|
+
}
|
|
158
|
+
if (!readable)
|
|
159
|
+
continue;
|
|
160
|
+
put(k, v);
|
|
161
|
+
copied.add(k);
|
|
162
|
+
}
|
|
163
|
+
for (const k of REOPEN_VERDICT_KEYS) {
|
|
164
|
+
if (copied.has(k))
|
|
165
|
+
continue;
|
|
166
|
+
let v;
|
|
167
|
+
let readable = true;
|
|
168
|
+
try {
|
|
169
|
+
v = src[k];
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
readable = false;
|
|
173
|
+
}
|
|
174
|
+
if (readable && v !== undefined)
|
|
175
|
+
put(k, v);
|
|
176
|
+
}
|
|
177
|
+
return snap;
|
|
178
|
+
}
|
|
82
179
|
function reopenRefusedForDecisionInFlight(verdict) {
|
|
83
|
-
|
|
180
|
+
try {
|
|
181
|
+
return verdict !== null && verdict.reopened === false && verdict._sema_decisionInFlight === true;
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
84
186
|
}
|
|
85
187
|
function reopenRefusedForUserDismissal(verdict) {
|
|
86
|
-
|
|
188
|
+
try {
|
|
189
|
+
return verdict !== null && verdict.reopened === false && verdict.dismissedByUser === true;
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function reopenRefusedForNoPresentationSurface(verdict) {
|
|
196
|
+
try {
|
|
197
|
+
return (verdict !== null &&
|
|
198
|
+
verdict.reopened === false &&
|
|
199
|
+
verdict._sema_noPresentationSurface === true);
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function reopenErrorCodeOf(verdict) {
|
|
206
|
+
try {
|
|
207
|
+
if (verdict.reopened !== false)
|
|
208
|
+
return undefined;
|
|
209
|
+
const v = verdict._sema_errorCode;
|
|
210
|
+
return typeof v === 'string' && v.length > 0 ? v : undefined;
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function failureWireCode(e) {
|
|
217
|
+
try {
|
|
218
|
+
return effectiveWireErrorCode(e);
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
function thrownReopenVerdict(e) {
|
|
225
|
+
const code = failureWireCode(e);
|
|
226
|
+
return code !== undefined ? { reopened: false, _sema_errorCode: code } : { reopened: false };
|
|
87
227
|
}
|
|
88
228
|
function planReviewReopenTriggerFor(origin) {
|
|
89
229
|
if (origin === 'user')
|
|
@@ -93,23 +233,57 @@ function planReviewReopenTriggerFor(origin) {
|
|
|
93
233
|
return undefined;
|
|
94
234
|
}
|
|
95
235
|
function reopenDelivered(verdict) {
|
|
96
|
-
|
|
236
|
+
let reopened;
|
|
237
|
+
let presented;
|
|
238
|
+
try {
|
|
239
|
+
reopened = verdict.reopened;
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
if (reopened !== true)
|
|
245
|
+
return false;
|
|
246
|
+
try {
|
|
247
|
+
presented = verdict.presented;
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
presented = undefined;
|
|
251
|
+
}
|
|
252
|
+
return presented !== false;
|
|
253
|
+
}
|
|
254
|
+
function reopenedOfOutcome(outcome) {
|
|
255
|
+
try {
|
|
256
|
+
return outcome.reopened ?? null;
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
97
261
|
}
|
|
98
262
|
export function atMostOnceFailureClass(e) {
|
|
99
|
-
const status = e
|
|
263
|
+
const status = errorSlot(e, 'status');
|
|
100
264
|
return typeof status === 'number' && status >= 400 && status < 500 ? 'rejected' : 'unknown';
|
|
101
265
|
}
|
|
102
266
|
export function readSteerDelivery(receipt) {
|
|
103
|
-
|
|
267
|
+
try {
|
|
268
|
+
if (typeof receipt !== 'object' || receipt === null || !('delivery' in receipt))
|
|
269
|
+
return null;
|
|
270
|
+
const d = receipt.delivery;
|
|
271
|
+
return typeof d === 'string' && d.length > 0 ? d : null;
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
104
274
|
return null;
|
|
105
|
-
|
|
106
|
-
return typeof d === 'string' && d.length > 0 ? d : null;
|
|
275
|
+
}
|
|
107
276
|
}
|
|
108
277
|
export function readSteerReceiptStatus(receipt) {
|
|
109
|
-
|
|
278
|
+
try {
|
|
279
|
+
if (typeof receipt !== 'object' || receipt === null || !('status' in receipt))
|
|
280
|
+
return null;
|
|
281
|
+
const s = receipt.status;
|
|
282
|
+
return typeof s === 'string' && s.length > 0 ? s : null;
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
110
285
|
return null;
|
|
111
|
-
|
|
112
|
-
return typeof s === 'string' && s.length > 0 ? s : null;
|
|
286
|
+
}
|
|
113
287
|
}
|
|
114
288
|
const CANCEL_RELEASE_WAIT_MS = 10_000;
|
|
115
289
|
const RUNNING_LIVENESS_RECHECK_TIMEOUT_MS = 4_000;
|
|
@@ -227,11 +401,14 @@ async function readOwnedPendingCount(read, deps) {
|
|
|
227
401
|
claimPollSleep(OWNED_PENDING_RECHECK_TIMEOUT_MS, recheckCtl.signal).then(() => null),
|
|
228
402
|
]);
|
|
229
403
|
if (deps?.signal?.aborted === true)
|
|
230
|
-
return null;
|
|
231
|
-
return typeof count === 'number' ? count : null;
|
|
404
|
+
return { count: null };
|
|
405
|
+
return { count: typeof count === 'number' ? count : null };
|
|
232
406
|
}
|
|
233
|
-
catch {
|
|
234
|
-
|
|
407
|
+
catch (e) {
|
|
408
|
+
if (deps?.signal?.aborted === true)
|
|
409
|
+
return { count: null };
|
|
410
|
+
const errorCode = failureWireCode(e);
|
|
411
|
+
return errorCode !== undefined ? { count: null, errorCode } : { count: null };
|
|
235
412
|
}
|
|
236
413
|
finally {
|
|
237
414
|
deps?.signal?.removeEventListener('abort', onCallerAbort);
|
|
@@ -248,7 +425,7 @@ export async function attemptActiveRunSelfHeal(signal, runs, deps) {
|
|
|
248
425
|
}
|
|
249
426
|
const callerAborted = () => deps?.signal?.aborted === true;
|
|
250
427
|
if (pending && typeof deps?.listOwnedPendingApprovals === 'function' && !callerAborted()) {
|
|
251
|
-
const count = await readOwnedPendingCount(deps.listOwnedPendingApprovals, deps);
|
|
428
|
+
const { count } = await readOwnedPendingCount(deps.listOwnedPendingApprovals, deps);
|
|
252
429
|
if (count === 0 && !callerAborted())
|
|
253
430
|
pending = false;
|
|
254
431
|
}
|
|
@@ -279,7 +456,7 @@ export async function attemptActiveRunSelfHeal(signal, runs, deps) {
|
|
|
279
456
|
catch (e) {
|
|
280
457
|
return { kind: 'state-unknown', taskId, detail: describeFailure(e) };
|
|
281
458
|
}
|
|
282
|
-
status =
|
|
459
|
+
status = readStatusGuarded(record);
|
|
283
460
|
}
|
|
284
461
|
if (status === null) {
|
|
285
462
|
return { kind: 'state-unknown', taskId, detail: 'the engine reported no status for that run' };
|
|
@@ -299,31 +476,41 @@ async function planVerdict(taskId, deps) {
|
|
|
299
476
|
try {
|
|
300
477
|
const trigger = planReviewReopenTriggerFor(deps?.submissionOrigin);
|
|
301
478
|
const verdict = trigger === undefined ? deps?.reopenPlanReview?.(taskId) : deps?.reopenPlanReview?.(taskId, { trigger });
|
|
302
|
-
return (await verdict)
|
|
479
|
+
return snapshotReopenVerdict(await verdict);
|
|
303
480
|
}
|
|
304
|
-
catch {
|
|
305
|
-
|
|
481
|
+
catch (e) {
|
|
482
|
+
if (deps?.signal?.aborted === true)
|
|
483
|
+
return { reopened: false };
|
|
484
|
+
return thrownReopenVerdict(e);
|
|
306
485
|
}
|
|
307
486
|
}
|
|
308
487
|
async function askVerdict(taskId, deps) {
|
|
309
488
|
try {
|
|
310
|
-
return (await deps?.reopenAskPark?.(taskId))
|
|
489
|
+
return snapshotReopenVerdict(await deps?.reopenAskPark?.(taskId));
|
|
311
490
|
}
|
|
312
|
-
catch {
|
|
313
|
-
|
|
491
|
+
catch (e) {
|
|
492
|
+
if (deps?.signal?.aborted === true)
|
|
493
|
+
return { reopened: false };
|
|
494
|
+
return thrownReopenVerdict(e);
|
|
314
495
|
}
|
|
315
496
|
}
|
|
316
497
|
async function planReviewArm(taskId, signal, deps) {
|
|
317
498
|
const verdict = await planVerdict(taskId, deps);
|
|
318
499
|
if (reopenDelivered(verdict))
|
|
319
500
|
return { kind: 'plan-review-reopened', taskId, firstSight: verdict.firstSight === true };
|
|
501
|
+
const inFlight = reopenRefusedForDecisionInFlight(verdict);
|
|
502
|
+
const dismissed = reopenRefusedForUserDismissal(verdict);
|
|
503
|
+
const noView = reopenRefusedForNoPresentationSurface(verdict);
|
|
504
|
+
const errorCode = inFlight || dismissed || noView ? undefined : reopenErrorCodeOf(verdict);
|
|
320
505
|
return {
|
|
321
506
|
kind: 'plan-review-reopen-failed',
|
|
322
507
|
taskId,
|
|
323
508
|
decidePath: signal.pendingGate?.decidePath ?? null,
|
|
324
|
-
...(
|
|
509
|
+
...(inFlight ? { decisionInFlight: true } : {}),
|
|
325
510
|
...(verdict.pendingRowGone === true ? { pendingRowGone: true } : {}),
|
|
326
|
-
...(
|
|
511
|
+
...(dismissed ? { dismissedByUser: true } : {}),
|
|
512
|
+
...(noView ? { noPresentationSurface: true } : {}),
|
|
513
|
+
...(errorCode !== undefined ? { errorCode } : {}),
|
|
327
514
|
};
|
|
328
515
|
}
|
|
329
516
|
async function askParkArm(taskId, signal, runs, deps) {
|
|
@@ -358,29 +545,41 @@ async function askParkArm(taskId, signal, runs, deps) {
|
|
|
358
545
|
if (verdict.reopened === false && verdict.decidedWithoutCard !== true && verdict.pendingRowGone === true && !inFlight) {
|
|
359
546
|
return staleParkArm(taskId, signal, runs, deps);
|
|
360
547
|
}
|
|
548
|
+
const noView = reopenRefusedForNoPresentationSurface(verdict);
|
|
549
|
+
const errorCode = inFlight || noView || reopenRefusedForUserDismissal(verdict) ? undefined : reopenErrorCodeOf(verdict);
|
|
361
550
|
return {
|
|
362
551
|
kind: 'ask-reopen-failed',
|
|
363
552
|
taskId,
|
|
364
553
|
decidePath: signal.pendingGate?.decidePath ?? null,
|
|
365
554
|
...(inFlight ? { decisionInFlight: true } : {}),
|
|
366
555
|
...(verdict.pendingRowGone === true ? { pendingRowGone: true } : {}),
|
|
556
|
+
...(noView ? { noPresentationSurface: true } : {}),
|
|
557
|
+
...(errorCode !== undefined ? { errorCode } : {}),
|
|
367
558
|
};
|
|
368
559
|
}
|
|
369
560
|
async function probeRunStatusOnce(taskId, durable, durableGet, deps) {
|
|
370
561
|
let status = null;
|
|
371
562
|
let ghost = false;
|
|
563
|
+
let errorCode;
|
|
564
|
+
let record = null;
|
|
565
|
+
let answered = false;
|
|
372
566
|
const lease = claimProbeLease(deps?.signal, RUNNING_LIVENESS_RECHECK_TIMEOUT_MS);
|
|
373
567
|
try {
|
|
374
|
-
|
|
568
|
+
record = await durableGet.call(durable, taskId, { signal: lease.signal, ...sessionOpts(deps) });
|
|
569
|
+
answered = true;
|
|
375
570
|
}
|
|
376
571
|
catch (e) {
|
|
377
|
-
if (e
|
|
572
|
+
if (errorSlot(e, 'status') === 404)
|
|
378
573
|
ghost = true;
|
|
574
|
+
else
|
|
575
|
+
errorCode = failureWireCode(e);
|
|
379
576
|
}
|
|
380
577
|
finally {
|
|
381
578
|
lease.release();
|
|
382
579
|
}
|
|
383
|
-
|
|
580
|
+
if (answered)
|
|
581
|
+
status = readStatusGuarded(record);
|
|
582
|
+
return errorCode !== undefined ? { status, ghost, errorCode } : { status, ghost };
|
|
384
583
|
}
|
|
385
584
|
async function staleParkArm(taskId, busy, runs, deps) {
|
|
386
585
|
const reopenFailed = {
|
|
@@ -394,6 +593,7 @@ async function staleParkArm(taskId, busy, runs, deps) {
|
|
|
394
593
|
taskId,
|
|
395
594
|
decidePath: busy.pendingGate?.decidePath ?? null,
|
|
396
595
|
};
|
|
596
|
+
const withFailureCode = (base, code) => code !== undefined ? { ...base, errorCode: code } : base;
|
|
397
597
|
if (runs === undefined || typeof runs.get !== 'function')
|
|
398
598
|
return reopenFailed;
|
|
399
599
|
const durableGet = runs.get;
|
|
@@ -407,7 +607,7 @@ async function staleParkArm(taskId, busy, runs, deps) {
|
|
|
407
607
|
return reopenFailed;
|
|
408
608
|
const fresh = first.status;
|
|
409
609
|
if (fresh === null)
|
|
410
|
-
return reopenFailed;
|
|
610
|
+
return withFailureCode(reopenFailed, first.errorCode);
|
|
411
611
|
if (RUNNING_STATES.includes(fresh))
|
|
412
612
|
return runningChoiceArm(taskId, fresh, busy, false, runs, deps);
|
|
413
613
|
if (PLAN_REVIEW_STATES.includes(fresh))
|
|
@@ -446,12 +646,14 @@ async function staleParkArm(taskId, busy, runs, deps) {
|
|
|
446
646
|
return { kind: 'ask-run-not-found', taskId };
|
|
447
647
|
if (callerAborted())
|
|
448
648
|
return reopenFailedAfterCard;
|
|
449
|
-
if (afterCard.status === null
|
|
450
|
-
return
|
|
649
|
+
if (afterCard.status === null)
|
|
650
|
+
return withFailureCode(reopenFailedAfterCard, afterCard.errorCode);
|
|
651
|
+
if (!ASK_PARK_STATES.includes(afterCard.status)) {
|
|
652
|
+
return !CLAIM_HELD_STATES.includes(afterCard.status) ? reopenFailed : reopenFailedAfterCard;
|
|
451
653
|
}
|
|
452
|
-
const
|
|
453
|
-
if (
|
|
454
|
-
return reopenFailedAfterCard;
|
|
654
|
+
const recheck = await readOwnedPendingCount(recheckOwnedPending, deps);
|
|
655
|
+
if (recheck.count !== 0)
|
|
656
|
+
return withFailureCode(reopenFailedAfterCard, recheck.errorCode);
|
|
455
657
|
const verdict = await cancelAndConfirmRelease(taskId, runs, durableGet, deps);
|
|
456
658
|
if (verdict.outcome === 'released') {
|
|
457
659
|
return { kind: 'stale-park-cancelled', taskId, ...(verdict.engineStatedRelease ? { releaseConfirmedByEngine: true } : {}) };
|
|
@@ -655,6 +857,7 @@ export function activeRunSelfHealRow(outcome, signal, copy, origin, follow) {
|
|
|
655
857
|
}
|
|
656
858
|
const INJECTED_LEAD = 'A follow-up message sema sent on its own (not one you typed)';
|
|
657
859
|
const USER_CLOSED_CARD_NOT_PUT_BACK = 'sema does not put a card you closed back on screen by itself';
|
|
860
|
+
const NO_CARD_VIEW_ATTACHED = 'this session has no view attached that can show decision cards';
|
|
658
861
|
function injectedSubmissionRow(outcome, following = false) {
|
|
659
862
|
const lead = INJECTED_LEAD;
|
|
660
863
|
const id = 'taskId' in outcome && outcome.taskId ? ` (id ${outcome.taskId})` : '';
|
|
@@ -677,22 +880,32 @@ function injectedSubmissionRow(outcome, following = false) {
|
|
|
677
880
|
}
|
|
678
881
|
if (outcome.kind === 'running-steered' && outcome.delivery === 'queued') {
|
|
679
882
|
const queuedOn = `${lead} is queued on an earlier reply${id} that is paused waiting for a decision`;
|
|
680
|
-
|
|
883
|
+
const reopened = reopenedOfOutcome(outcome);
|
|
884
|
+
if (reopened !== null && reopenDelivered(reopened)) {
|
|
681
885
|
return `${queuedOn}; sema has shown that decision card, and the message is picked up once you answer it.`;
|
|
682
886
|
}
|
|
683
|
-
if (reopenRefusedForDecisionInFlight(
|
|
887
|
+
if (reopenRefusedForDecisionInFlight(reopened)) {
|
|
684
888
|
return `${queuedOn}; your answer to it is still on its way, and the message is picked up once that answer is applied.`;
|
|
685
889
|
}
|
|
686
|
-
if (reopenRefusedForUserDismissal(
|
|
890
|
+
if (reopenRefusedForUserDismissal(reopened)) {
|
|
687
891
|
return (`${queuedOn}; you closed that card yourself, so sema did not put it back on screen — send a message to bring it ` +
|
|
688
892
|
`back, and the message is picked up once that decision is made.`);
|
|
689
893
|
}
|
|
690
|
-
|
|
894
|
+
const noView = reopenRefusedForNoPresentationSurface(reopened) ? ` (${NO_CARD_VIEW_ATTACHED})` : '';
|
|
895
|
+
return `${queuedOn} sema could not show here${noView}; the message is picked up only once that decision is made.`;
|
|
691
896
|
}
|
|
692
897
|
if (outcome.kind === 'running-steered' && outcome.delivery === 'parked_for_wake') {
|
|
693
898
|
return (`${lead} has not reached the model: the earlier reply${id} had already finished, so the message was set aside on it ` +
|
|
694
899
|
`instead of starting a new reply, and sema did not retry it.`);
|
|
695
900
|
}
|
|
901
|
+
if (outcome.kind === 'running-steered' && outcome.delivery !== 'applied') {
|
|
902
|
+
const unknown = `${lead} was accepted for the reply already in progress${id}, but how it will be delivered was not reported, ` +
|
|
903
|
+
`so sema cannot tell whether that reply will pick it up`;
|
|
904
|
+
return following
|
|
905
|
+
? `${unknown}. sema is following that reply and will show whatever it asks for next; check what it does ` +
|
|
906
|
+
`before deciding whether anything needs to be sent again.`
|
|
907
|
+
: `${unknown} — watch that reply before deciding whether anything needs to be sent again.`;
|
|
908
|
+
}
|
|
696
909
|
switch (selfHealSubmissionDisposition(outcome)) {
|
|
697
910
|
case 'held-for-decision':
|
|
698
911
|
return (`${lead} is on hold: this session is busy with an earlier reply${id} that is waiting on a decision. ` +
|
|
@@ -710,9 +923,13 @@ function injectedSubmissionRow(outcome, following = false) {
|
|
|
710
923
|
`sema is following that reply and will show whatever it asks for next; nothing for you to do now.`
|
|
711
924
|
: `${lead} was passed to the reply already in progress${id} instead of starting a new one — ` +
|
|
712
925
|
`watch that reply for what it does with it.`;
|
|
713
|
-
case 'not-delivered':
|
|
926
|
+
case 'not-delivered': {
|
|
927
|
+
const noView = (outcome.kind === 'ask-reopen-failed' || outcome.kind === 'plan-review-reopen-failed') && outcome.noPresentationSurface === true
|
|
928
|
+
? ` (${NO_CARD_VIEW_ATTACHED})`
|
|
929
|
+
: '';
|
|
714
930
|
return (`${lead} was not delivered: this session was still busy with an earlier reply${id}, and there was no open card ` +
|
|
715
|
-
`you could answer to free it. sema did not retry, so the model has not seen it.`);
|
|
931
|
+
`you could answer to free it${noView}. sema did not retry, so the model has not seen it.`);
|
|
932
|
+
}
|
|
716
933
|
}
|
|
717
934
|
}
|
|
718
935
|
function decisionInFlightRow(parkedOn) {
|
|
@@ -780,8 +997,9 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false, ke
|
|
|
780
997
|
const viaEngine = engineDecide && outcome.decidePath
|
|
781
998
|
? ` You can also decide it on the engine directly: POST ${outcome.decidePath}.`
|
|
782
999
|
: '';
|
|
1000
|
+
const noView = outcome.noPresentationSurface === true ? `: ${NO_CARD_VIEW_ATTACHED}` : '';
|
|
783
1001
|
return (`The previous turn is parked waiting for your decision (run ${outcome.taskId}) and sema could ` +
|
|
784
|
-
`not reopen that card here. It did NOT cancel the run — that would have decided it for you. ` +
|
|
1002
|
+
`not reopen that card here${noView}. It did NOT cancel the run — that would have decided it for you. ` +
|
|
785
1003
|
(keepSessionWayOut !== null
|
|
786
1004
|
? `Your message was NOT sent. To keep this conversation, ${keepSessionWayOut}: the card comes back when the session resumes, if it is still waiting. Or ${wayOut} if you no longer want it.${viaEngine}`
|
|
787
1005
|
: `Your message was NOT sent; ${wayOut} if you no longer want it.${viaEngine}`));
|
|
@@ -797,8 +1015,9 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false, ke
|
|
|
797
1015
|
const viaEngine = engineDecide && outcome.decidePath
|
|
798
1016
|
? ` You can also decide it on the engine directly: POST ${outcome.decidePath}.`
|
|
799
1017
|
: '';
|
|
1018
|
+
const noView = outcome.noPresentationSurface === true ? `: ${NO_CARD_VIEW_ATTACHED}` : '';
|
|
800
1019
|
return (`The previous turn is parked waiting for a plan review (run ${outcome.taskId}) and sema could not ` +
|
|
801
|
-
`reopen that approval card. It did NOT cancel the run — that would have discarded the plan for ` +
|
|
1020
|
+
`reopen that approval card${noView}. It did NOT cancel the run — that would have discarded the plan for ` +
|
|
802
1021
|
(keepSessionWayOut !== null
|
|
803
1022
|
? `you. Your message was NOT sent. To keep this conversation, ${keepSessionWayOut}: the card comes back when the session resumes, if it is still waiting. Or ${wayOut} if you no longer want that plan.${viaEngine}`
|
|
804
1023
|
: `you. Your message was NOT sent; ${wayOut} if you no longer want that plan.${viaEngine}`));
|
|
@@ -816,15 +1035,18 @@ function activeRunSelfHealBaseRow(outcome, signal, wayOut, following = false, ke
|
|
|
816
1035
|
}
|
|
817
1036
|
if (outcome.delivery === 'queued') {
|
|
818
1037
|
const parked = outcome.status ? `parked (status ${outcome.status})` : 'parked';
|
|
819
|
-
const
|
|
1038
|
+
const reopened = reopenedOfOutcome(outcome);
|
|
1039
|
+
const next = reopened !== null && reopenDelivered(reopened)
|
|
820
1040
|
? `sema has surfaced that decision card — answering it is what lets that run resume and pick your message up.`
|
|
821
|
-
: reopenRefusedForDecisionInFlight(
|
|
1041
|
+
: reopenRefusedForDecisionInFlight(reopened)
|
|
822
1042
|
? `Your answer to that decision is still on its way to the engine, so sema did not show the card again; ` +
|
|
823
1043
|
`that run moves on once the engine applies it.`
|
|
824
|
-
: reopenRefusedForUserDismissal(
|
|
1044
|
+
: reopenRefusedForUserDismissal(reopened)
|
|
825
1045
|
? `You closed that decision card yourself, so sema did not put it back on screen; nothing will resume that ` +
|
|
826
1046
|
`run until that decision is made — send a message to bring the card back.`
|
|
827
|
-
: `sema could not surface that decision card here
|
|
1047
|
+
: `sema could not surface that decision card here` +
|
|
1048
|
+
(reopenRefusedForNoPresentationSurface(reopened) ? ` (${NO_CARD_VIEW_ATTACHED})` : '') +
|
|
1049
|
+
`, so nothing will resume that run until that decision is made; ` +
|
|
828
1050
|
`${wayOut} if you no longer want it.`;
|
|
829
1051
|
return (`${handle} is not actually working right now: the engine reports it ${parked} on a decision it needs ` +
|
|
830
1052
|
`from you, so it queued your message on that park instead of running it. ${next} ${textOnly} ` +
|
|
@@ -9,6 +9,7 @@ export declare function gateArmedWaitMs(): number;
|
|
|
9
9
|
export declare function waitForGateArmed(keys: readonly string[], timeoutMs: number): Promise<boolean>;
|
|
10
10
|
export declare function waitForGateArmedFor(sessionKey: string, keys: readonly string[], timeoutMs: number): Promise<boolean>;
|
|
11
11
|
export declare function registerArmedGateFromQuestionId(questionId: unknown): void;
|
|
12
|
+
export declare function registerArmedGateFromQuestionIdFor(sessionKey: string, questionId: unknown): void;
|
|
12
13
|
export declare function planReviewArmedKey(taskId: string): string;
|
|
13
14
|
export declare function planReviewArmedKeyFor(sessionKey: string, taskId: string): string;
|
|
14
15
|
export declare function notePlanReviewAnswered(questionId: unknown): void;
|
|
@@ -120,7 +120,7 @@ function armedKeyForQuestionIdFor(sessionKey, questionId) {
|
|
|
120
120
|
export function registerArmedGateFromQuestionId(questionId) {
|
|
121
121
|
registerArmedGateFromQuestionIdFor(DEFAULT_SESSION_KEY, questionId);
|
|
122
122
|
}
|
|
123
|
-
function registerArmedGateFromQuestionIdFor(sessionKey, questionId) {
|
|
123
|
+
export function registerArmedGateFromQuestionIdFor(sessionKey, questionId) {
|
|
124
124
|
if (typeof questionId !== 'string' || questionId.length === 0)
|
|
125
125
|
return;
|
|
126
126
|
if (questionId.startsWith(PLAN_REVIEW_QUESTION_ID_PREFIX) && !questionId.includes(REOPEN_ID_TAIL)) {
|
|
@@ -827,6 +827,12 @@ function mintPlanReviewReopenCard(taskId, opts, sessionKey, receiptMs) {
|
|
|
827
827
|
revokePlanReviewUserDismissal(sessionKey, taskId, markBefore);
|
|
828
828
|
return { reopened: true, firstSight };
|
|
829
829
|
}
|
|
830
|
+
function noPresentationSurfaceVerdict(taskId, sessionKey) {
|
|
831
|
+
const verdict = { reopened: false, _sema_noPresentationSurface: true };
|
|
832
|
+
hostLog('debug', `planReviewWire: reopen for task ${taskId} refused — nothing is subscribed to question frames for session ` +
|
|
833
|
+
`${JSON.stringify(sessionKey)} (onQuestionFrameFor), so the card has nowhere to render`);
|
|
834
|
+
return verdict;
|
|
835
|
+
}
|
|
830
836
|
async function reopenAfterDismissalCheck(taskId, opts, sessionKey, receiptMs, mark, recheckReadStart) {
|
|
831
837
|
const refusedByUser = { reopened: false, dismissedByUser: true };
|
|
832
838
|
try {
|
|
@@ -863,6 +869,11 @@ async function reopenAfterDismissalCheck(taskId, opts, sessionKey, receiptMs, ma
|
|
|
863
869
|
hostLog('debug', `planReviewWire: reopen for task ${taskId} not minted — the dismissal record changed while it was being checked (stale evaluation)`);
|
|
864
870
|
return await staleDismissalVerdict(current, sessionKey, taskId, receiptMs);
|
|
865
871
|
}
|
|
872
|
+
if (!hasQuestionOverlayFor(sessionKey)) {
|
|
873
|
+
if (fresh)
|
|
874
|
+
dropDismissalEntry(sessionKey, taskId, mark);
|
|
875
|
+
return noPresentationSurfaceVerdict(taskId, sessionKey);
|
|
876
|
+
}
|
|
866
877
|
if (!fresh) {
|
|
867
878
|
hostLog('debug', `planReviewWire: automatic reopen for task ${taskId} refused — the user closed this gate's card (no newer gate instance proven)`);
|
|
868
879
|
return refusedByUser;
|
|
@@ -878,8 +889,7 @@ async function reopenAfterDismissalCheck(taskId, opts, sessionKey, receiptMs, ma
|
|
|
878
889
|
putDismissalEntry(sessionKey, taskId, reopening);
|
|
879
890
|
let verdict = { reopened: false };
|
|
880
891
|
try {
|
|
881
|
-
|
|
882
|
-
verdict = await mintPlanReviewReopen(taskId, opts, sessionKey, receiptMs);
|
|
892
|
+
verdict = await mintPlanReviewReopen(taskId, opts, sessionKey, receiptMs);
|
|
883
893
|
}
|
|
884
894
|
finally {
|
|
885
895
|
const presented = verdict.reopened === true;
|
|
@@ -937,7 +947,8 @@ export function reopenPlanReviewCard(taskId, opts) {
|
|
|
937
947
|
const recheckReadStart = readStart === undefined ? guardStartedAt : Math.min(readStart, guardStartedAt);
|
|
938
948
|
const sessionKey = opts?.sessionKey ?? DEFAULT_SESSION_KEY;
|
|
939
949
|
if (!hasQuestionOverlayFor(sessionKey)) {
|
|
940
|
-
|
|
950
|
+
const noSurface = noPresentationSurfaceVerdict(taskId, sessionKey);
|
|
951
|
+
return receiptMs === undefined ? noSurface : Promise.resolve(noSurface);
|
|
941
952
|
}
|
|
942
953
|
if (sessionKey !== DEFAULT_SESSION_KEY && opts?.deliverDecision === undefined && opts?.wire === undefined) {
|
|
943
954
|
hostLog('error', `planReviewWire: plan_review 卡未重开 —— sessionKey=${JSON.stringify(sessionKey)} 是非默认槽,` +
|
package/dist/host.d.ts
CHANGED
|
@@ -56,7 +56,7 @@ export interface HostPorts {
|
|
|
56
56
|
}
|
|
57
57
|
export declare function installHost(ports: HostPorts): () => void;
|
|
58
58
|
export declare function installHostFor(sessionKey: string, ports: Omit<HostPorts, 'queue'>): () => void;
|
|
59
|
-
export declare function hostLog(level: AdapterLogLevel, msg: string, data?: unknown):
|
|
59
|
+
export declare function hostLog(level: AdapterLogLevel, msg: string, data?: unknown): boolean;
|
|
60
60
|
export declare function hostProbe(channel: string, line: string): void;
|
|
61
61
|
export declare function hostSettings(): SettingsPort | undefined;
|
|
62
62
|
export declare function hostSettingsFor(sessionKey: string): SettingsPort | undefined;
|
package/dist/host.js
CHANGED
|
@@ -46,11 +46,20 @@ export function installHostFor(sessionKey, ports) {
|
|
|
46
46
|
slot.state = prev;
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
|
+
function callDiagnosticPort(call) {
|
|
50
|
+
try {
|
|
51
|
+
call();
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
49
58
|
export function hostLog(level, msg, data) {
|
|
50
|
-
hostSlot(DEFAULT_SESSION_KEY).state.log?.(level, msg, data);
|
|
59
|
+
return callDiagnosticPort(() => hostSlot(DEFAULT_SESSION_KEY).state.log?.(level, msg, data));
|
|
51
60
|
}
|
|
52
61
|
export function hostProbe(channel, line) {
|
|
53
|
-
hostSlot(DEFAULT_SESSION_KEY).state.probe?.(channel, line);
|
|
62
|
+
callDiagnosticPort(() => hostSlot(DEFAULT_SESSION_KEY).state.probe?.(channel, line));
|
|
54
63
|
}
|
|
55
64
|
export function hostSettings() {
|
|
56
65
|
return hostSettingsFor(DEFAULT_SESSION_KEY);
|
package/dist/index.d.ts
CHANGED
|
@@ -169,7 +169,7 @@ export * from './coreValuePorts.js';
|
|
|
169
169
|
export * from './hitl/askGateWire.js';
|
|
170
170
|
export * from './hitl/planReviewWire.js';
|
|
171
171
|
export { PLAN_REVIEW_QUESTION_ID_PREFIX, REOPEN_ID_TAIL, approvalCallKey, askGateQuestionId, planReviewQuestionId, liveFrameCallKey, armedKeyFromQuestionId, } from './hitl/gateIdentity.js';
|
|
172
|
-
export { _resetArmedGateRegistryForTest, _setGateArmedWaitMsForTest, clearArmedGate, gateArmedWaitMs, notePlanReviewAnswered, onGateArmed, planReviewArmedKey, registerArmedGate, registerArmedGateFor, registerArmedGateFromQuestionId, waitForGateArmed, wasGateArmed, wasGateArmedFor, } from './hitl/armedGateRegistry.js';
|
|
172
|
+
export { _resetArmedGateRegistryForTest, _setGateArmedWaitMsForTest, clearArmedGate, gateArmedWaitMs, notePlanReviewAnswered, onGateArmed, planReviewArmedKey, registerArmedGate, registerArmedGateFor, registerArmedGateFromQuestionId, registerArmedGateFromQuestionIdFor, waitForGateArmed, wasGateArmed, wasGateArmedFor, } from './hitl/armedGateRegistry.js';
|
|
173
173
|
export * from './hitl/parkOwnership.js';
|
|
174
174
|
export * from './hitl/parkRowBirthWait.js';
|
|
175
175
|
export * from './hitl/approvalDecisionNoteAudit.js';
|
package/dist/index.js
CHANGED
|
@@ -155,7 +155,7 @@ export * from './coreValuePorts.js';
|
|
|
155
155
|
export * from './hitl/askGateWire.js';
|
|
156
156
|
export * from './hitl/planReviewWire.js';
|
|
157
157
|
export { PLAN_REVIEW_QUESTION_ID_PREFIX, REOPEN_ID_TAIL, approvalCallKey, askGateQuestionId, planReviewQuestionId, liveFrameCallKey, armedKeyFromQuestionId, } from './hitl/gateIdentity.js';
|
|
158
|
-
export { _resetArmedGateRegistryForTest, _setGateArmedWaitMsForTest, clearArmedGate, gateArmedWaitMs, notePlanReviewAnswered, onGateArmed, planReviewArmedKey, registerArmedGate, registerArmedGateFor, registerArmedGateFromQuestionId, waitForGateArmed, wasGateArmed, wasGateArmedFor, } from './hitl/armedGateRegistry.js';
|
|
158
|
+
export { _resetArmedGateRegistryForTest, _setGateArmedWaitMsForTest, clearArmedGate, gateArmedWaitMs, notePlanReviewAnswered, onGateArmed, planReviewArmedKey, registerArmedGate, registerArmedGateFor, registerArmedGateFromQuestionId, registerArmedGateFromQuestionIdFor, waitForGateArmed, wasGateArmed, wasGateArmedFor, } from './hitl/armedGateRegistry.js';
|
|
159
159
|
export * from './hitl/parkOwnership.js';
|
|
160
160
|
export * from './hitl/parkRowBirthWait.js';
|
|
161
161
|
export * from './hitl/approvalDecisionNoteAudit.js';
|
|
@@ -71,6 +71,8 @@ export interface TaskRequestOmission {
|
|
|
71
71
|
readonly cause: TaskRequestOmissionCause;
|
|
72
72
|
}
|
|
73
73
|
export declare function taskRequestOmissionCauseDetail(cause: unknown): string;
|
|
74
|
+
export declare const TASK_REQUEST_OMISSION_CAUSE_NOTICES: Readonly<Record<TaskRequestOmissionCause, string>>;
|
|
75
|
+
export declare function taskRequestOmissionCauseNotice(cause: unknown): string;
|
|
74
76
|
export interface TaskRequestAssembly {
|
|
75
77
|
readonly request: TaskRequestLike;
|
|
76
78
|
readonly omitted: readonly TaskRequestOmission[];
|