@sema-agent/client-core 0.84.1 → 0.85.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 +149 -0
- package/README.md +18 -11
- package/dist/adapt/arms.js +1 -1
- package/dist/adapt.d.ts +1 -1
- package/dist/adapt.js +1 -1
- package/dist/adapter/downstream/terminalToSdkResult.js +9 -5
- package/dist/adapter/runStream.js +6 -2
- package/dist/agentSession/engineSessionBackgroundStop.d.ts +50 -0
- package/dist/agentSession/engineSessionBackgroundStop.js +291 -0
- package/dist/agentsWireCaps.d.ts +2 -1
- package/dist/cloudConfigWireCaps.js +2 -1
- package/dist/corruptSession.d.ts +3 -0
- package/dist/corruptSession.js +29 -0
- package/dist/corruptSessionCopy.d.ts +1 -0
- package/dist/corruptSessionCopy.js +4 -0
- package/dist/decideFailureNote.js +4 -1
- package/dist/decideReceipt.d.ts +6 -0
- package/dist/decideReceipt.js +30 -1
- package/dist/effortWire.d.ts +2 -1
- package/dist/effortWire.js +2 -4
- package/dist/engineErrorCodes.d.ts +9 -0
- package/dist/engineErrorCodes.js +29 -0
- package/dist/engineNoticeCodes.d.ts +9 -0
- package/dist/engineNoticeCodes.js +44 -156
- package/dist/engineWireFor.d.ts +43 -0
- package/dist/engineWireFor.js +399 -0
- package/dist/engineWireSdk.d.ts +4 -0
- package/dist/fileHistoryCaptureCapability.d.ts +4 -1
- package/dist/fileHistoryCaptureCapability.js +43 -9
- package/dist/gateVocabulary.d.ts +1 -2
- package/dist/gateVocabulary.js +1 -22
- package/dist/generated/engineNoticeTables.d.ts +3 -0
- package/dist/generated/engineNoticeTables.js +156 -0
- package/dist/generated/toolNameTables.d.ts +2 -0
- package/dist/generated/toolNameTables.js +43 -0
- package/dist/headlessPermissionModeWire.js +2 -4
- package/dist/hitl/frameRouter.js +4 -2
- package/dist/hitl/parkResolver.js +3 -1
- package/dist/hitl/planReviewWire.d.ts +11 -0
- package/dist/hitl/planReviewWire.js +177 -69
- package/dist/hitl/rosterMountedNames.d.ts +2 -0
- package/dist/hitl/rosterMountedNames.js +38 -0
- package/dist/hitl/sessionPolicyDeliverable.d.ts +6 -2
- package/dist/hitl/sessionPolicyDeliverable.js +25 -45
- package/dist/hitl/sessionPolicyWire.js +1 -38
- package/dist/hitl/toolApprovalWire.d.ts +4 -2
- package/dist/hitl/toolApprovalWire.js +4 -2
- package/dist/index.d.ts +9 -3
- package/dist/index.js +6 -2
- package/dist/permissionWireCaps.d.ts +2 -1
- package/dist/permissionWireCaps.js +2 -7
- package/dist/request/taskRequest.d.ts +2 -0
- package/dist/request/taskRequest.js +18 -1
- package/dist/resumeRefusalCopy.js +4 -2
- package/dist/runTerminal.js +4 -0
- package/dist/sandboxWire.js +1 -1
- package/dist/sealedKeyCapability.d.ts +17 -0
- package/dist/sealedKeyCapability.js +42 -0
- package/dist/seatContract.d.ts +2 -1
- package/dist/seatContract.js +5 -21
- package/dist/subagent/engineCompactWire.d.ts +13 -2
- package/dist/subagent/engineCompactWire.js +48 -37
- package/dist/subagent/engineDelegatedPrompt.d.ts +9 -1
- package/dist/subagent/engineDelegatedPrompt.js +16 -16
- package/dist/subagent/engineRowStopGate.d.ts +2 -2
- package/dist/subagent/engineRowStopGate.js +4 -4
- package/dist/subagent/engineSubagentOutput.d.ts +8 -0
- package/dist/subagent/engineSubagentOutput.js +37 -18
- package/dist/subagent/engineSubagentResume.d.ts +11 -2
- package/dist/subagent/engineSubagentResume.js +28 -14
- package/dist/subagent/engineSubagentSteer.d.ts +10 -1
- package/dist/subagent/engineSubagentSteer.js +16 -11
- package/dist/subagent/engineSubagentTail.d.ts +10 -1
- package/dist/subagent/engineSubagentTail.js +35 -17
- package/dist/subagent/engineTaskHandleWire.d.ts +13 -0
- package/dist/subagent/engineTaskHandleWire.js +41 -20
- package/dist/taskRequestWords.d.ts +5 -0
- package/dist/taskRequestWords.js +4 -0
- package/dist/wireErrorTriage.d.ts +1 -0
- package/dist/wireErrorTriage.js +3 -1
- package/dist/wireRefusalCopy.d.ts +10 -0
- package/dist/wireRefusalCopy.js +62 -2
- package/docs/INTEGRATION-CLIENTS.md +679 -18
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { publishQuestionFrame, publishQuestionFrameFor, registerLocalQuestionResponder, unregisterLocalQuestionResponder, hasLocalQuestionResponder, hasQuestionOverlayFor, } from '../liveQuestionStore.js';
|
|
2
2
|
import { hostLog } from '../host.js';
|
|
3
3
|
import { abortableSleep, settleWithinDeadline } from '../abortableSleep.js';
|
|
4
|
-
import {
|
|
4
|
+
import { clientHasVerbs, engineWireFor, noAnswerKindOf, sendOnce } from '../engineWireFor.js';
|
|
5
5
|
import { engineWireTarget } from '../engineWireTarget.js';
|
|
6
6
|
import { enqueuePlanReviewOutcome } from '../notifications.js';
|
|
7
7
|
import { corruptStoredRowContent, corruptStoredRowWhere } from '../wireErrorTriage.js';
|
|
@@ -102,6 +102,18 @@ export function isPlanReviewPark(result) {
|
|
|
102
102
|
const taskId = result?.taskId;
|
|
103
103
|
return typeof taskId === 'string' && taskId.length > 0;
|
|
104
104
|
}
|
|
105
|
+
function pausedCheckpointIdOf(carrier) {
|
|
106
|
+
try {
|
|
107
|
+
const read = readRunTerminal(carrier);
|
|
108
|
+
if (read === null || read.kind !== 'paused')
|
|
109
|
+
return undefined;
|
|
110
|
+
const id = read.checkpointId;
|
|
111
|
+
return typeof id === 'string' && id.length > 0 ? id : undefined;
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
105
117
|
const armedPlanReviewTaskIds = new Set();
|
|
106
118
|
const offerModeAfterByTask = new Map();
|
|
107
119
|
export function _resetArmedPlanReviewsForTest() {
|
|
@@ -239,6 +251,7 @@ export function armPlanReviewApproval(result, sessionKey, opts) {
|
|
|
239
251
|
? { ...(wire !== undefined ? { wire } : {}), ...(opts?.onOutcome !== undefined ? { onOutcome: opts.onOutcome } : {}) }
|
|
240
252
|
: undefined;
|
|
241
253
|
const { taskId } = result;
|
|
254
|
+
const gateBefore = pausedCheckpointIdOf(result);
|
|
242
255
|
retireActiveReopen(DEFAULT_SESSION_KEY, planReviewQuestionId(taskId));
|
|
243
256
|
if (armedPlanReviewTaskIds.has(taskId)) {
|
|
244
257
|
if (hasLocalQuestionResponder(planReviewQuestionId(taskId))) {
|
|
@@ -274,7 +287,7 @@ export function armPlanReviewApproval(result, sessionKey, opts) {
|
|
|
274
287
|
offerModeAfterByTask.delete(taskId);
|
|
275
288
|
notePlanReviewAnswered(questionId);
|
|
276
289
|
hostLog('debug', `planReviewWire: arm responder delivering ${decided} for task ${taskId} (path=arm)`);
|
|
277
|
-
void (
|
|
290
|
+
void decidePlanReviewFor(taskId, decided, modeAfter, delivery, gateBefore);
|
|
278
291
|
return { ok: true };
|
|
279
292
|
});
|
|
280
293
|
publishPlanReviewCard(taskId);
|
|
@@ -312,108 +325,186 @@ function planReviewDecisionTimedOutOutcome(decision) {
|
|
|
312
325
|
return (`The plan_review ${decision} was sent, but no answer came back from the engine within the time limit this host allows for it — ` +
|
|
313
326
|
`it may have taken effect (${decision === 'approve' ? 'an approval keeps the engine busy until the resumed work finishes' : 'the engine may still be applying it'}).`);
|
|
314
327
|
}
|
|
315
|
-
function
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
328
|
+
function planReviewDecisionConnectionLostOutcome(decision) {
|
|
329
|
+
return (`The plan_review ${decision} got no answer from the engine: the connection failed before an answer came back, and the request may already have reached the engine — ` +
|
|
330
|
+
`it may have taken effect (${decision === 'approve' ? 'an approval keeps the engine busy until the resumed work finishes' : 'the engine may still be applying it'}).`);
|
|
331
|
+
}
|
|
332
|
+
function planReviewAdvancedOutcome(decision, postStatus) {
|
|
333
|
+
return (`The plan was ${decision === 'approve' ? 'approved' : 'rejected'} and the task advanced to a NEW approval gate ` +
|
|
334
|
+
`(post-decide status: ${postStatus}) — the next approval card will surface it; this is not a completion yet.`);
|
|
322
335
|
}
|
|
323
336
|
function usablePlanReviewWireClient(client) {
|
|
324
337
|
if (client === null || typeof client !== 'object')
|
|
325
338
|
return false;
|
|
326
|
-
|
|
327
|
-
return typeof c.assistant?.planReview === 'function' && typeof c.runs?.get === 'function';
|
|
339
|
+
return clientHasVerbs(client, [['assistant', 'planReview'], ['runs', 'get']]);
|
|
328
340
|
}
|
|
329
341
|
const PLAN_REVIEW_INJECTED_REPULL_TIMEOUT_MS = 15_000;
|
|
330
|
-
const
|
|
342
|
+
const planReviewDecisionOps = [];
|
|
343
|
+
const MAX_PLAN_REVIEW_DECISION_OPS = 32;
|
|
344
|
+
let planReviewLedgerClock = 0;
|
|
331
345
|
let decideDispatchSeq = 0;
|
|
346
|
+
function planReviewOpsOf(taskId) {
|
|
347
|
+
return planReviewDecisionOps.filter((op) => op.taskId === taskId);
|
|
348
|
+
}
|
|
349
|
+
function settlePlanReviewDecisionOp(op) {
|
|
350
|
+
if (op.settledAt === null)
|
|
351
|
+
op.settledAt = ++planReviewLedgerClock;
|
|
352
|
+
}
|
|
353
|
+
function pushPlanReviewDecisionOp(op) {
|
|
354
|
+
planReviewDecisionOps.push(op);
|
|
355
|
+
while (planReviewDecisionOps.length > MAX_PLAN_REVIEW_DECISION_OPS) {
|
|
356
|
+
let victim = planReviewDecisionOps.findIndex((o) => o.settledAt !== null && !o.ownCallOpen);
|
|
357
|
+
if (victim < 0)
|
|
358
|
+
victim = planReviewDecisionOps.findIndex((o) => o !== op && o.phase === 'handed');
|
|
359
|
+
if (victim < 0)
|
|
360
|
+
break;
|
|
361
|
+
planReviewDecisionOps.splice(victim, 1);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
function dropPlanReviewDecisionOp(op) {
|
|
365
|
+
const i = planReviewDecisionOps.indexOf(op);
|
|
366
|
+
if (i >= 0)
|
|
367
|
+
planReviewDecisionOps.splice(i, 1);
|
|
368
|
+
}
|
|
369
|
+
export function notePlanReviewDecisionHandedOver(taskId) {
|
|
370
|
+
if (typeof taskId !== 'string' || taskId === '')
|
|
371
|
+
return null;
|
|
372
|
+
if (planReviewDecisionOps.some((op) => op.taskId === taskId && op.settledAt === null))
|
|
373
|
+
return null;
|
|
374
|
+
const op = { taskId, handedAt: ++planReviewLedgerClock, settledAt: null, phase: 'handed', notApplied: false, ownCallOpen: false };
|
|
375
|
+
pushPlanReviewDecisionOp(op);
|
|
376
|
+
return {
|
|
377
|
+
settle: () => {
|
|
378
|
+
if (op.phase !== 'handed')
|
|
379
|
+
return;
|
|
380
|
+
settlePlanReviewDecisionOp(op);
|
|
381
|
+
},
|
|
382
|
+
revoke: () => {
|
|
383
|
+
if (op.phase !== 'handed' || op.settledAt !== null)
|
|
384
|
+
return;
|
|
385
|
+
dropPlanReviewDecisionOp(op);
|
|
386
|
+
},
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
export function planReviewPendingReadMark() {
|
|
390
|
+
return ++planReviewLedgerClock;
|
|
391
|
+
}
|
|
392
|
+
export function planReviewDecisionInFlight(taskId, pendingReadStartedAt) {
|
|
393
|
+
if (typeof taskId !== 'string' || taskId === '')
|
|
394
|
+
return false;
|
|
395
|
+
const mark = typeof pendingReadStartedAt === 'number' && Number.isFinite(pendingReadStartedAt) ? pendingReadStartedAt : undefined;
|
|
396
|
+
return planReviewDecisionOps.some((op) => op.taskId === taskId && (op.settledAt === null || (mark !== undefined && op.settledAt > mark && !op.notApplied)));
|
|
397
|
+
}
|
|
398
|
+
export function notePlanReviewDecisionNotApplied(taskId) {
|
|
399
|
+
if (typeof taskId !== 'string' || taskId === '')
|
|
400
|
+
return;
|
|
401
|
+
const ops = planReviewOpsOf(taskId);
|
|
402
|
+
if (ops.some((op) => op.settledAt === null))
|
|
403
|
+
return;
|
|
404
|
+
let latest;
|
|
405
|
+
for (const op of ops)
|
|
406
|
+
if (latest === undefined || op.handedAt > latest.handedAt)
|
|
407
|
+
latest = op;
|
|
408
|
+
if (latest !== undefined)
|
|
409
|
+
latest.notApplied = true;
|
|
410
|
+
}
|
|
411
|
+
export function notePlanReviewGateLeft(taskId) {
|
|
412
|
+
if (typeof taskId !== 'string' || taskId === '')
|
|
413
|
+
return;
|
|
414
|
+
for (const op of planReviewOpsOf(taskId))
|
|
415
|
+
settlePlanReviewDecisionOp(op);
|
|
416
|
+
}
|
|
417
|
+
export function latestPlanReviewDecisionMark(taskId) {
|
|
418
|
+
if (typeof taskId !== 'string' || taskId === '')
|
|
419
|
+
return null;
|
|
420
|
+
let mark = null;
|
|
421
|
+
for (const op of planReviewOpsOf(taskId))
|
|
422
|
+
if (mark === null || op.handedAt > mark)
|
|
423
|
+
mark = op.handedAt;
|
|
424
|
+
return mark;
|
|
425
|
+
}
|
|
332
426
|
export function __resetPlanReviewDecisionLatchForTests() {
|
|
333
|
-
|
|
427
|
+
planReviewDecisionOps.length = 0;
|
|
334
428
|
}
|
|
335
429
|
export async function decidePlanReview(taskId, decision, permissionModeAfter, opts) {
|
|
336
|
-
|
|
430
|
+
return decidePlanReviewFor(taskId, decision, permissionModeAfter, opts, undefined);
|
|
431
|
+
}
|
|
432
|
+
async function decidePlanReviewFor(taskId, decision, permissionModeAfter, opts, gateBefore) {
|
|
433
|
+
if (typeof taskId === 'string' && planReviewDecisionOps.some((op) => op.taskId === taskId && op.phase === 'posting')) {
|
|
337
434
|
hostLog('error', `planReviewWire: decidePlanReview(${decision}) for task ${taskId} REFUSED — a decision for this task is already in flight (duplicate delivery latched)`);
|
|
338
435
|
return;
|
|
339
436
|
}
|
|
340
|
-
|
|
341
|
-
|
|
437
|
+
let op = planReviewDecisionOps.find((o) => o.taskId === taskId && o.phase === 'handed' && o.settledAt === null);
|
|
438
|
+
if (op === undefined) {
|
|
439
|
+
op = { taskId: typeof taskId === 'string' ? taskId : String(taskId), handedAt: ++planReviewLedgerClock, settledAt: null, phase: 'posting', notApplied: false, ownCallOpen: true };
|
|
440
|
+
pushPlanReviewDecisionOp(op);
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
op.phase = 'posting';
|
|
444
|
+
op.ownCallOpen = true;
|
|
445
|
+
}
|
|
446
|
+
const mine = op;
|
|
342
447
|
const releaseLatch = () => {
|
|
343
|
-
if (
|
|
344
|
-
|
|
448
|
+
if (mine.phase === 'posting')
|
|
449
|
+
mine.phase = 'landed';
|
|
450
|
+
};
|
|
451
|
+
const settle = (notApplied) => {
|
|
452
|
+
if (notApplied)
|
|
453
|
+
mine.notApplied = true;
|
|
454
|
+
settlePlanReviewDecisionOp(mine);
|
|
345
455
|
};
|
|
346
456
|
const dispatchNo = ++decideDispatchSeq;
|
|
347
457
|
hostLog('debug', `planReviewWire: decidePlanReview dispatch #${dispatchNo} task=${taskId} decision=${decision} mode=${permissionModeAfter ?? '-'}`);
|
|
348
458
|
try {
|
|
349
|
-
await decidePlanReviewInner(taskId, decision, permissionModeAfter, dispatchNo, releaseLatch, opts);
|
|
459
|
+
await decidePlanReviewInner(taskId, decision, permissionModeAfter, dispatchNo, releaseLatch, settle, opts, gateBefore);
|
|
350
460
|
}
|
|
351
461
|
finally {
|
|
352
462
|
releaseLatch();
|
|
463
|
+
settlePlanReviewDecisionOp(mine);
|
|
464
|
+
mine.ownCallOpen = false;
|
|
353
465
|
}
|
|
354
466
|
}
|
|
355
|
-
function planReviewClientsFor(
|
|
356
|
-
if (
|
|
357
|
-
|
|
358
|
-
try {
|
|
359
|
-
injected = typeof wire === 'object' && wire !== null ? wire.client : undefined;
|
|
360
|
-
}
|
|
361
|
-
catch {
|
|
362
|
-
injected = undefined;
|
|
363
|
-
}
|
|
364
|
-
if (!usablePlanReviewWireClient(injected))
|
|
365
|
-
return null;
|
|
366
|
-
const client = injected;
|
|
467
|
+
function planReviewClientsFor(resolved) {
|
|
468
|
+
if (resolved.kind === 'injected') {
|
|
469
|
+
const client = resolved.client;
|
|
367
470
|
return { decide: client, repull: () => client };
|
|
368
471
|
}
|
|
369
|
-
|
|
370
|
-
return null;
|
|
371
|
-
const decide = makeEngineWireClient({
|
|
372
|
-
baseUrl: cfg.baseUrl,
|
|
373
|
-
...(cfg.token ? { token: cfg.token } : {}),
|
|
374
|
-
principal: cfg.principal,
|
|
375
|
-
timeoutMs: PLAN_REVIEW_RESUME_TIMEOUT_MS,
|
|
376
|
-
});
|
|
472
|
+
const decide = resolved.make({ timeoutMs: PLAN_REVIEW_RESUME_TIMEOUT_MS });
|
|
377
473
|
if (!decide)
|
|
378
474
|
return null;
|
|
379
475
|
return {
|
|
380
476
|
decide,
|
|
381
|
-
repull: () =>
|
|
382
|
-
baseUrl: cfg.baseUrl,
|
|
383
|
-
...(cfg.token ? { token: cfg.token } : {}),
|
|
384
|
-
principal: cfg.principal,
|
|
385
|
-
timeoutMs: 15_000,
|
|
386
|
-
}),
|
|
477
|
+
repull: () => resolved.make({ timeoutMs: 15_000 }),
|
|
387
478
|
};
|
|
388
479
|
}
|
|
389
|
-
async function decidePlanReviewInner(taskId, decision, permissionModeAfter, dispatchNo, releaseLatch, opts) {
|
|
480
|
+
async function decidePlanReviewInner(taskId, decision, permissionModeAfter, dispatchNo, releaseLatch, settle, opts, gateBefore) {
|
|
390
481
|
let outcome;
|
|
391
482
|
let effect = 'unconfirmed';
|
|
483
|
+
let stillParkedProven = false;
|
|
392
484
|
let statusSeen;
|
|
393
485
|
const wire = opts?.wire;
|
|
394
|
-
const
|
|
486
|
+
const resolved = engineWireFor(wire, DEFAULT_SESSION_KEY, usablePlanReviewWireClient);
|
|
395
487
|
const badMode = permissionModeAfter !== undefined && (decision !== 'approve' || !isPlanReviewModeAfter(permissionModeAfter));
|
|
396
488
|
if (badMode) {
|
|
397
489
|
hostLog('error', `planReviewWire: ${decision} NOT sent — permissionModeAfter is only valid with approve and must be "default" or "acceptEdits"`);
|
|
398
490
|
effect = 'not_sent';
|
|
399
491
|
outcome = `The plan_review ${decision} could NOT be sent: the post-approval permission mode given with it is not valid for this decision (it only applies to an approval, and must be "default" or "acceptEdits"). Tell the user plainly that the decision did not go through; the plan is still waiting.`;
|
|
400
492
|
}
|
|
401
|
-
else if (
|
|
493
|
+
else if (resolved.kind === 'no-target') {
|
|
402
494
|
hostLog('error', `planReviewWire: ${decision} NOT sent — engineWireTarget() unavailable`);
|
|
403
495
|
effect = 'not_sent';
|
|
404
496
|
outcome = `The plan_review ${decision} could NOT be sent: no engine connection is configured on this host. Tell the user plainly that the decision did not go through.`;
|
|
405
497
|
}
|
|
498
|
+
else if (resolved.kind === 'injected-unusable') {
|
|
499
|
+
hostLog('error', `planReviewWire: ${decision} NOT sent — the engine client supplied by the host is not usable (needs assistant.planReview and runs.get)`);
|
|
500
|
+
effect = 'not_sent';
|
|
501
|
+
outcome = `The plan_review ${decision} could NOT be sent: the engine client supplied by the host cannot send it (it lacks the plan-review verb). Tell the user plainly that the decision did not go through.`;
|
|
502
|
+
}
|
|
406
503
|
else {
|
|
407
|
-
const clients = planReviewClientsFor(
|
|
504
|
+
const clients = planReviewClientsFor(resolved);
|
|
408
505
|
if (!clients) {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
outcome = `The plan_review ${decision} could NOT be sent: the engine client supplied by the host cannot send it (it lacks the plan-review verb). Tell the user plainly that the decision did not go through.`;
|
|
412
|
-
}
|
|
413
|
-
else {
|
|
414
|
-
hostLog('error', `planReviewWire: ${decision} NOT sent — makeEngineWireClient() returned null`);
|
|
415
|
-
outcome = `The plan_review ${decision} could NOT be sent: the engine client could not be constructed. Tell the user plainly that the decision did not go through.`;
|
|
416
|
-
}
|
|
506
|
+
hostLog('error', `planReviewWire: ${decision} NOT sent — makeEngineWireClient() returned null`);
|
|
507
|
+
outcome = `The plan_review ${decision} could NOT be sent: the engine client could not be constructed. Tell the user plainly that the decision did not go through.`;
|
|
417
508
|
effect = 'not_sent';
|
|
418
509
|
}
|
|
419
510
|
else {
|
|
@@ -428,13 +519,13 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
428
519
|
reqBody.permissionModeAfter = mode;
|
|
429
520
|
let body;
|
|
430
521
|
try {
|
|
431
|
-
body = await client.assistant.planReview(taskId, reqBody);
|
|
522
|
+
body = await sendOnce(undefined, () => client.assistant.planReview(taskId, reqBody));
|
|
432
523
|
}
|
|
433
524
|
catch (e1) {
|
|
434
525
|
const conflict = e1;
|
|
435
526
|
if (mode === 'default' && conflict?.status === 400 && conflict?.errorCode === 'request.field_conflict') {
|
|
436
527
|
hostLog('debug', 'planReviewWire: permissionModeAfter "default" refused as not applicable — re-sending the approval without the key (same meaning)');
|
|
437
|
-
body = await client.assistant.planReview(taskId, reason !== undefined ? { decision, reason } : { decision });
|
|
528
|
+
body = await sendOnce(undefined, () => client.assistant.planReview(taskId, reason !== undefined ? { decision, reason } : { decision }));
|
|
438
529
|
}
|
|
439
530
|
else {
|
|
440
531
|
throw e1;
|
|
@@ -445,6 +536,7 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
445
536
|
}
|
|
446
537
|
hostLog('debug', `planReviewWire: ${decision} → ok (dispatch #${dispatchNo}) ${JSON.stringify(body).slice(0, 200)}`);
|
|
447
538
|
let postStatus;
|
|
539
|
+
let gateAfter;
|
|
448
540
|
try {
|
|
449
541
|
const probe = clients.repull();
|
|
450
542
|
if (probe) {
|
|
@@ -461,6 +553,7 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
461
553
|
post = (await probe.runs.get(taskId));
|
|
462
554
|
}
|
|
463
555
|
postStatus = typeof post?.status === 'string' ? post.status : undefined;
|
|
556
|
+
gateAfter = pausedCheckpointIdOf(post?.result);
|
|
464
557
|
}
|
|
465
558
|
}
|
|
466
559
|
catch (e) {
|
|
@@ -468,8 +561,14 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
468
561
|
}
|
|
469
562
|
const effective = postStatus ?? (typeof body?.status === 'string' ? body.status : undefined);
|
|
470
563
|
statusSeen = effective;
|
|
471
|
-
|
|
564
|
+
const gateMoved = postStatus === 'needs_review' && gateBefore !== undefined && gateAfter !== undefined && gateAfter !== gateBefore;
|
|
565
|
+
if (effective === 'needs_review' && gateMoved) {
|
|
566
|
+
effect = 'advanced';
|
|
567
|
+
outcome = planReviewAdvancedOutcome(decision, 'needs_review');
|
|
568
|
+
}
|
|
569
|
+
else if (effective === 'needs_review') {
|
|
472
570
|
effect = 'still_parked';
|
|
571
|
+
stillParkedProven = postStatus === 'needs_review' && gateBefore !== undefined && gateAfter !== undefined && gateAfter === gateBefore;
|
|
473
572
|
outcome =
|
|
474
573
|
`The plan_review ${decision} returned HTTP 200 but the session is STILL locked on the same review gate ` +
|
|
475
574
|
`(post-decide status: needs_review). The decision did NOT take effect (known engine issue, RB-471 family). ` +
|
|
@@ -477,9 +576,7 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
477
576
|
}
|
|
478
577
|
else if (effective === 'suspended') {
|
|
479
578
|
effect = 'advanced';
|
|
480
|
-
outcome =
|
|
481
|
-
`The plan was ${decision === 'approve' ? 'approved' : 'rejected'} and the task advanced to a NEW approval gate ` +
|
|
482
|
-
`(post-decide status: suspended) — the next approval card will surface it; this is not a completion yet.`;
|
|
579
|
+
outcome = planReviewAdvancedOutcome(decision, 'suspended');
|
|
483
580
|
}
|
|
484
581
|
else if (effective !== undefined) {
|
|
485
582
|
effect = 'took_effect';
|
|
@@ -492,9 +589,11 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
492
589
|
catch (e) {
|
|
493
590
|
const status = e?.status;
|
|
494
591
|
const refusedCode = e?.errorCode;
|
|
592
|
+
const noAnswer = typeof status === 'number' || typeof e !== 'object' ? null : noAnswerKindOf(e);
|
|
495
593
|
if (status === 400 && refusedCode === 'request.field_conflict' && permissionModeAfter === 'acceptEdits') {
|
|
496
594
|
hostLog('debug', 'planReviewWire: approve + acceptEdits refused (request.field_conflict) — NOT re-sent with a different mode');
|
|
497
595
|
effect = 'still_parked';
|
|
596
|
+
stillParkedProven = true;
|
|
498
597
|
outcome =
|
|
499
598
|
'The plan approval was NOT applied: the engine refused "auto-accept edits" for this task (it was not started read-only in plan mode, so that option does not apply). ' +
|
|
500
599
|
'Nothing changed engine-side — the plan is still waiting for review. Tell the user plainly, and that approving with "manually approve edits" will go through.';
|
|
@@ -518,10 +617,14 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
518
617
|
effect = status >= 400 && status < 500 ? 'not_applied' : 'unconfirmed';
|
|
519
618
|
outcome = `${`The plan_review decision failed: HTTP ${status} ${msg}`.trim()}${decideFailureNote(e)}`;
|
|
520
619
|
}
|
|
521
|
-
else if (
|
|
620
|
+
else if (noAnswer === 'time-limit' || noAnswer === 'deadline') {
|
|
522
621
|
hostLog('debug', `planReviewWire: ${decision} sent but no answer within the request time limit (task ${taskId})`);
|
|
523
622
|
outcome = planReviewDecisionTimedOutOutcome(decision);
|
|
524
623
|
}
|
|
624
|
+
else if (noAnswer === 'connection-lost') {
|
|
625
|
+
hostLog('debug', `planReviewWire: ${decision} got no answer — the connection failed and it cannot be told whether the request reached the engine (task ${taskId})`);
|
|
626
|
+
outcome = planReviewDecisionConnectionLostOutcome(decision);
|
|
627
|
+
}
|
|
525
628
|
else {
|
|
526
629
|
outcome = `The plan_review decision could not reach the engine: ${String(e)}`;
|
|
527
630
|
}
|
|
@@ -531,6 +634,7 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
531
634
|
const tail = planReviewOutcomeTail(taskId, decision, effect);
|
|
532
635
|
const prompt = `<plan-review-outcome>\n${outcome}\n${tail}\n</plan-review-outcome>`;
|
|
533
636
|
const onOutcome = opts?.onOutcome;
|
|
637
|
+
settle(effect === 'not_sent' || (effect === 'still_parked' && stillParkedProven));
|
|
534
638
|
try {
|
|
535
639
|
const delivered = enqueuePlanReviewOutcome(prompt, { taskId, dispatchNo, decision, effect });
|
|
536
640
|
if (!delivered) {
|
|
@@ -723,7 +827,7 @@ function mintPlanReviewReopenCard(taskId, opts, sessionKey, receiptMs) {
|
|
|
723
827
|
revokePlanReviewUserDismissal(sessionKey, taskId, markBefore);
|
|
724
828
|
return { reopened: true, firstSight };
|
|
725
829
|
}
|
|
726
|
-
async function reopenAfterDismissalCheck(taskId, opts, sessionKey, receiptMs, mark) {
|
|
830
|
+
async function reopenAfterDismissalCheck(taskId, opts, sessionKey, receiptMs, mark, recheckReadStart) {
|
|
727
831
|
const refusedByUser = { reopened: false, dismissedByUser: true };
|
|
728
832
|
try {
|
|
729
833
|
let fresh = false;
|
|
@@ -740,7 +844,8 @@ async function reopenAfterDismissalCheck(taskId, opts, sessionKey, receiptMs, ma
|
|
|
740
844
|
}
|
|
741
845
|
}
|
|
742
846
|
let handedOver = false;
|
|
743
|
-
|
|
847
|
+
const ledgerSaysInFlight = planReviewDecisionInFlight(taskId, recheckReadStart);
|
|
848
|
+
if (!ledgerSaysInFlight) {
|
|
744
849
|
try {
|
|
745
850
|
handedOver = opts.decisionHandedOver?.(taskId) === true;
|
|
746
851
|
}
|
|
@@ -749,7 +854,7 @@ async function reopenAfterDismissalCheck(taskId, opts, sessionKey, receiptMs, ma
|
|
|
749
854
|
return { reopened: false };
|
|
750
855
|
}
|
|
751
856
|
}
|
|
752
|
-
if (
|
|
857
|
+
if (ledgerSaysInFlight || handedOver) {
|
|
753
858
|
hostLog('debug', `planReviewWire: reopen for task ${taskId} refused after the dismissal check — a decision for this task went out while the check was reading`);
|
|
754
859
|
return { reopened: false, _sema_decisionInFlight: true };
|
|
755
860
|
}
|
|
@@ -822,11 +927,14 @@ export function reopenPlanReviewCard(taskId, opts) {
|
|
|
822
927
|
if (typeof taskId !== 'string' || taskId.length === 0) {
|
|
823
928
|
return receiptMs === undefined ? { reopened: false } : Promise.resolve({ reopened: false });
|
|
824
929
|
}
|
|
825
|
-
|
|
930
|
+
const readStart = typeof opts?.pendingReadStartedAt === 'number' && Number.isFinite(opts.pendingReadStartedAt) ? opts.pendingReadStartedAt : undefined;
|
|
931
|
+
if (planReviewDecisionInFlight(taskId, readStart)) {
|
|
826
932
|
hostLog('debug', `planReviewWire: reopen for task ${taskId} refused — a decision for this task is in flight (its answer is still being delivered)`);
|
|
827
933
|
const refused = { reopened: false, _sema_decisionInFlight: true };
|
|
828
934
|
return receiptMs === undefined ? refused : Promise.resolve(refused);
|
|
829
935
|
}
|
|
936
|
+
const guardStartedAt = planReviewPendingReadMark();
|
|
937
|
+
const recheckReadStart = readStart === undefined ? guardStartedAt : Math.min(readStart, guardStartedAt);
|
|
830
938
|
const sessionKey = opts?.sessionKey ?? DEFAULT_SESSION_KEY;
|
|
831
939
|
if (!hasQuestionOverlayFor(sessionKey)) {
|
|
832
940
|
return receiptMs === undefined ? { reopened: false } : Promise.resolve({ reopened: false });
|
|
@@ -846,7 +954,7 @@ export function reopenPlanReviewCard(taskId, opts) {
|
|
|
846
954
|
hostLog('debug', `planReviewWire: automatic reopen for task ${taskId} refused — the user closed this gate's card`);
|
|
847
955
|
return { reopened: false, dismissedByUser: true };
|
|
848
956
|
}
|
|
849
|
-
return reopenAfterDismissalCheck(taskId, opts, sessionKey, receiptMs, mark);
|
|
957
|
+
return reopenAfterDismissalCheck(taskId, opts, sessionKey, receiptMs, mark, recheckReadStart);
|
|
850
958
|
}
|
|
851
959
|
}
|
|
852
960
|
return mintPlanReviewReopen(taskId, opts, sessionKey, receiptMs);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export function rosterMountedNames(roster) {
|
|
2
|
+
if (roster === undefined || roster === null)
|
|
3
|
+
return 'absent';
|
|
4
|
+
try {
|
|
5
|
+
if (typeof roster !== 'object' || Array.isArray(roster))
|
|
6
|
+
return 'unreadable';
|
|
7
|
+
const entries = roster.entries;
|
|
8
|
+
if (!Array.isArray(entries))
|
|
9
|
+
return 'unreadable';
|
|
10
|
+
const names = new Set();
|
|
11
|
+
const n = entries.length;
|
|
12
|
+
for (let i = 0; i < n; i++) {
|
|
13
|
+
if (!Object.hasOwn(entries, i))
|
|
14
|
+
return 'unreadable';
|
|
15
|
+
const row = entries[i];
|
|
16
|
+
if (typeof row !== 'object' || row === null)
|
|
17
|
+
return 'unreadable';
|
|
18
|
+
const name = row.name;
|
|
19
|
+
if (typeof name !== 'string' || name.length === 0)
|
|
20
|
+
return 'unreadable';
|
|
21
|
+
names.add(name);
|
|
22
|
+
const aliases = row.aliases;
|
|
23
|
+
if (aliases === undefined)
|
|
24
|
+
continue;
|
|
25
|
+
if (!Array.isArray(aliases))
|
|
26
|
+
return 'unreadable';
|
|
27
|
+
for (const a of aliases) {
|
|
28
|
+
if (typeof a !== 'string')
|
|
29
|
+
return 'unreadable';
|
|
30
|
+
names.add(a);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return names;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return 'unreadable';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { SessionPermissionRules } from '@sema-agent/sdk';
|
|
2
|
-
|
|
2
|
+
import type { ToolRosterView } from '../toolRoster.js';
|
|
3
|
+
export type SessionPolicyWithheldWhy = 'ask_no_bucket' | 'qualified_deny' | 'peer_wide' | 'wildcard' | 'not_a_tool_name' | 'legacy_tool_name' | 'not_in_roster';
|
|
3
4
|
export declare const SESSION_POLICY_WITHHELD_WHY: readonly SessionPolicyWithheldWhy[];
|
|
4
5
|
export interface SessionPolicyWithheldRule {
|
|
5
6
|
readonly rule: string;
|
|
@@ -11,7 +12,10 @@ export interface SessionPolicyDeliverability {
|
|
|
11
12
|
readonly withheld: readonly SessionPolicyWithheldRule[];
|
|
12
13
|
readonly unreadable?: true;
|
|
13
14
|
}
|
|
15
|
+
export interface SessionPolicyDeliverableOptions {
|
|
16
|
+
readonly roster?: ToolRosterView;
|
|
17
|
+
}
|
|
14
18
|
export declare function isEngineRefusedToolName(name: string): boolean;
|
|
15
|
-
export declare function sessionPolicyDeliverable(behavior: 'deny' | 'ask', rules: readonly string[]): SessionPolicyDeliverability;
|
|
19
|
+
export declare function sessionPolicyDeliverable(behavior: 'deny' | 'ask', rules: readonly string[], opts?: SessionPolicyDeliverableOptions): SessionPolicyDeliverability;
|
|
16
20
|
export declare function sessionPolicyWithheldNotice(why: SessionPolicyWithheldWhy): string;
|
|
17
21
|
export declare function legacyToolNameFailureNoteOf(code: string | undefined): string | undefined;
|
|
@@ -1,47 +1,8 @@
|
|
|
1
1
|
import { CONFIG_LEGACY_TOOL_NAME } from '../engineErrorCodes.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
['write_file', 'Write'],
|
|
7
|
-
['bash', 'Bash'],
|
|
8
|
-
['bash_readonly', null],
|
|
9
|
-
['glob', 'Glob'],
|
|
10
|
-
['grep', 'Grep'],
|
|
11
|
-
['web_fetch', 'WebFetch'],
|
|
12
|
-
['tool_search', 'ToolSearch'],
|
|
13
|
-
['skill', 'Skill'],
|
|
14
|
-
['present_plan', 'ExitPlanMode'],
|
|
15
|
-
['enter_plan_mode', 'EnterPlanMode'],
|
|
16
|
-
['spawn_subagent', 'Agent'],
|
|
17
|
-
['CronCancel', 'CronDelete'],
|
|
18
|
-
['lsp', 'LSP'],
|
|
19
|
-
['submit_output', 'StructuredOutput'],
|
|
20
|
-
['repo_map', 'RepoMap'],
|
|
21
|
-
['read_tool_result', 'ReadToolResult'],
|
|
22
|
-
['report_blocked', 'ReportBlocked'],
|
|
23
|
-
['declare_done', 'DeclareDone'],
|
|
24
|
-
['run_workflow', 'Workflow'],
|
|
25
|
-
['RunWorkflow', 'Workflow'],
|
|
26
|
-
['Task', 'Agent'],
|
|
27
|
-
['BashOutput', 'TaskOutput'],
|
|
28
|
-
['AgentOutputTool', 'TaskOutput'],
|
|
29
|
-
['BashOutputTool', 'TaskOutput'],
|
|
30
|
-
['AgentOutput', 'TaskOutput'],
|
|
31
|
-
['WorkflowStatus', null],
|
|
32
|
-
['KillShell', 'TaskStop'],
|
|
33
|
-
['KillBash', 'TaskStop'],
|
|
34
|
-
['ListMcpResources', 'ListMcpResourcesTool'],
|
|
35
|
-
['ReadMcpResource', 'ReadMcpResourceTool'],
|
|
36
|
-
['ReadMcpResourceDir', 'ReadMcpResourceDirTool'],
|
|
37
|
-
['MultiEdit', null],
|
|
38
|
-
['Fork', null],
|
|
39
|
-
['Sleep', null],
|
|
40
|
-
['Remember', null],
|
|
41
|
-
['remember', null],
|
|
42
|
-
['Recall', null],
|
|
43
|
-
['recall', null],
|
|
44
|
-
]);
|
|
2
|
+
import { rosterMountedNames } from './rosterMountedNames.js';
|
|
3
|
+
import { CORE_PROTOCOL_PREFIXES, CORE_RETIRED_TOOL_CURRENT_NAMES } from '../generated/toolNameTables.js';
|
|
4
|
+
const COVERING_NAMESPACE_PREFIXES = CORE_PROTOCOL_PREFIXES;
|
|
5
|
+
const ENGINE_RETIRED_TOOL_NAMES = CORE_RETIRED_TOOL_CURRENT_NAMES;
|
|
45
6
|
const PREFIXES_IN_PROSE = COVERING_NAMESPACE_PREFIXES.map((prefix) => `"${prefix}"`).join(' or ');
|
|
46
7
|
const WITHHELD_NOTICE = {
|
|
47
8
|
ask_no_bucket: 'Not written to this session’s rule record: that record only holds rules that block outright, and this rule asks before running instead — recording it as a block would change what it means.',
|
|
@@ -50,6 +11,7 @@ const WITHHELD_NOTICE = {
|
|
|
50
11
|
wildcard: 'Not written to this session’s rule record: this rule uses a wildcard (*), and an engine that matches that record by exact tool name would block none of the tools it covers.',
|
|
51
12
|
not_a_tool_name: 'Not written to this session’s rule record: this entry is not exactly one tool name — it is empty, not text, or has spaces around the name.',
|
|
52
13
|
legacy_tool_name: `Not written to this session’s rule record: unless a tool by exactly this name is available, the engine refuses it — it is a retired tool name, or it contains "__" but does not start with ${PREFIXES_IN_PROSE} — and with it in that record every later run of this session would fail at startup until that entry is removed from the record. If the tool still exists, use its current name; a server or agent-peer tool name starts with ${PREFIXES_IN_PROSE}.`,
|
|
14
|
+
not_in_roster: 'Not written to this session’s rule record: the tool roster supplied for this check has no tool by exactly this name, as a name or an alias, with letter case counted — it may be misspelled, or name a tool this deployment does not provide — so recorded as it is it would block nothing here.',
|
|
53
15
|
};
|
|
54
16
|
export const SESSION_POLICY_WITHHELD_WHY = Object.freeze(Object.keys(WITHHELD_NOTICE));
|
|
55
17
|
const MAX_RULES = 10_000;
|
|
@@ -83,7 +45,21 @@ export function isEngineRefusedToolName(name) {
|
|
|
83
45
|
return true;
|
|
84
46
|
return name.includes('__') && !COVERING_NAMESPACE_PREFIXES.some((prefix) => name.startsWith(prefix));
|
|
85
47
|
}
|
|
86
|
-
|
|
48
|
+
function suppliedRosterNames(opts) {
|
|
49
|
+
if (opts === undefined || opts === null)
|
|
50
|
+
return 'absent';
|
|
51
|
+
let roster;
|
|
52
|
+
try {
|
|
53
|
+
if (typeof opts !== 'object')
|
|
54
|
+
return 'unreadable';
|
|
55
|
+
roster = opts.roster;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return 'unreadable';
|
|
59
|
+
}
|
|
60
|
+
return rosterMountedNames(roster);
|
|
61
|
+
}
|
|
62
|
+
export function sessionPolicyDeliverable(behavior, rules, opts) {
|
|
87
63
|
const entries = [];
|
|
88
64
|
try {
|
|
89
65
|
if (behavior !== 'deny' && behavior !== 'ask')
|
|
@@ -105,6 +81,10 @@ export function sessionPolicyDeliverable(behavior, rules) {
|
|
|
105
81
|
catch {
|
|
106
82
|
return unreadable();
|
|
107
83
|
}
|
|
84
|
+
const known = suppliedRosterNames(opts);
|
|
85
|
+
if (known === 'unreadable')
|
|
86
|
+
return unreadable();
|
|
87
|
+
const mounted = known === 'absent' ? undefined : known;
|
|
108
88
|
const withheld = [];
|
|
109
89
|
if (behavior === 'ask') {
|
|
110
90
|
for (const r of entries)
|
|
@@ -117,7 +97,7 @@ export function sessionPolicyDeliverable(behavior, rules) {
|
|
|
117
97
|
withheld.push({ rule: '', why: 'not_a_tool_name' });
|
|
118
98
|
continue;
|
|
119
99
|
}
|
|
120
|
-
const why = withheldWhyOf(r);
|
|
100
|
+
const why = withheldWhyOf(r) ?? (mounted !== undefined && !mounted.has(r) ? 'not_in_roster' : undefined);
|
|
121
101
|
if (why === undefined)
|
|
122
102
|
exact.push(r);
|
|
123
103
|
else {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { wireFailureShapeOf } from '../wireFailureShape.js';
|
|
2
2
|
import { hostLog } from '../host.js';
|
|
3
3
|
import { isEngineRefusedToolName } from './sessionPolicyDeliverable.js';
|
|
4
|
+
import { rosterMountedNames } from './rosterMountedNames.js';
|
|
4
5
|
const RULE_FIELD_PRESENCE = {
|
|
5
6
|
toolAllow: true,
|
|
6
7
|
toolDeny: true,
|
|
@@ -307,44 +308,6 @@ export function sessionPolicyTightenNotice(outcome) {
|
|
|
307
308
|
}
|
|
308
309
|
}
|
|
309
310
|
}
|
|
310
|
-
function rosterMountedNames(roster) {
|
|
311
|
-
if (roster === undefined || roster === null)
|
|
312
|
-
return 'absent';
|
|
313
|
-
try {
|
|
314
|
-
if (typeof roster !== 'object' || Array.isArray(roster))
|
|
315
|
-
return 'unreadable';
|
|
316
|
-
const entries = roster.entries;
|
|
317
|
-
if (!Array.isArray(entries))
|
|
318
|
-
return 'unreadable';
|
|
319
|
-
const names = new Set();
|
|
320
|
-
const n = entries.length;
|
|
321
|
-
for (let i = 0; i < n; i++) {
|
|
322
|
-
if (!Object.hasOwn(entries, i))
|
|
323
|
-
return 'unreadable';
|
|
324
|
-
const row = entries[i];
|
|
325
|
-
if (typeof row !== 'object' || row === null)
|
|
326
|
-
return 'unreadable';
|
|
327
|
-
const name = row.name;
|
|
328
|
-
if (typeof name !== 'string' || name.length === 0)
|
|
329
|
-
return 'unreadable';
|
|
330
|
-
names.add(name);
|
|
331
|
-
const aliases = row.aliases;
|
|
332
|
-
if (aliases === undefined)
|
|
333
|
-
continue;
|
|
334
|
-
if (!Array.isArray(aliases))
|
|
335
|
-
return 'unreadable';
|
|
336
|
-
for (const a of aliases) {
|
|
337
|
-
if (typeof a !== 'string')
|
|
338
|
-
return 'unreadable';
|
|
339
|
-
names.add(a);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
return names;
|
|
343
|
-
}
|
|
344
|
-
catch {
|
|
345
|
-
return 'unreadable';
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
311
|
function withoutRefusedEntries(prior, mounted) {
|
|
349
312
|
const REFUSABLE_NAME_FIELDS = ['toolDeny', 'toolAllow'];
|
|
350
313
|
const isRefusable = (f) => REFUSABLE_NAME_FIELDS.includes(f);
|