@sema-agent/client-core 0.106.0 → 0.108.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 +199 -0
- package/README.md +35 -26
- package/dist/adapter/activeRunSelfHeal.d.ts +10 -0
- package/dist/adapter/activeRunSelfHeal.js +68 -23
- package/dist/checkpointRowVerdict.d.ts +11 -0
- package/dist/checkpointRowVerdict.js +35 -0
- package/dist/cloudConfigWireCaps.d.ts +10 -0
- package/dist/cloudConfigWireCaps.js +33 -0
- package/dist/coreValuePorts.d.ts +17 -1
- package/dist/coreValuePorts.js +5 -0
- package/dist/decideReceipt.js +2 -1
- package/dist/displayUntrusted.d.ts +5 -3
- package/dist/displayUntrusted.js +50 -41
- package/dist/engineErrorCodes.d.ts +5 -0
- package/dist/engineErrorCodes.js +5 -0
- package/dist/engineNoticeCodes.d.ts +31 -1
- package/dist/engineNoticeCodes.js +182 -9
- package/dist/engineTargetSeat.js +1 -0
- package/dist/errorSupplementRoles.js +4 -1
- package/dist/eventLeg.js +1 -1
- package/dist/executionLaneCapability.js +3 -2
- package/dist/executionLaneWords.d.ts +1 -0
- package/dist/executionLaneWords.js +4 -0
- package/dist/gateOutcome.d.ts +10 -1
- package/dist/gateOutcome.js +48 -1
- package/dist/generated/engineFactTables.d.ts +1 -0
- package/dist/generated/engineFactTables.js +5 -0
- package/dist/generated/engineNoticeTables.js +12 -3
- package/dist/generated/settingsSchemaFns.d.ts +4 -2
- package/dist/generated/settingsSchemaFns.js +9 -3
- package/dist/generated/settingsSchemaMcpRequestEntry.d.ts +1 -1
- package/dist/generated/settingsSchemaMcpRequestEntry.js +9 -18
- package/dist/generated/settingsSchemaReservedMemberName.d.ts +1 -0
- package/dist/generated/settingsSchemaReservedMemberName.js +7 -0
- package/dist/generated/settingsSchemaTables.d.ts +2 -0
- package/dist/generated/settingsSchemaTables.js +10 -0
- package/dist/generated/settingsSchemaWireName.d.ts +1 -0
- package/dist/generated/settingsSchemaWireName.js +8 -0
- package/dist/headlessReconnectWire.js +12 -7
- package/dist/hitl/approvalAskers.d.ts +13 -0
- package/dist/hitl/approvalAskers.js +79 -0
- package/dist/hitl/approvalResolution.d.ts +8 -1
- package/dist/hitl/approvalResolution.js +37 -2
- package/dist/hitl/approvalsFeed.js +5 -13
- package/dist/hitl/askParkRowRouting.js +17 -1
- package/dist/hitl/decisionRefusal.d.ts +1 -0
- package/dist/hitl/decisionRefusal.js +5 -2
- package/dist/hitl/decisionSend.d.ts +2 -0
- package/dist/hitl/decisionSend.js +22 -12
- package/dist/hitl/frameRouter.d.ts +3 -2
- package/dist/hitl/frameRouter.js +28 -6
- package/dist/hitl/gateLedger.d.ts +2 -0
- package/dist/hitl/gateLedger.js +3 -1
- package/dist/hitl/hitlBridge.d.ts +5 -1
- package/dist/hitl/hitlBridge.js +26 -2
- package/dist/hitl/livePendingAsk.js +4 -2
- package/dist/hitl/parkOwnership.js +4 -5
- package/dist/hitl/parkResolver.js +34 -21
- package/dist/hitl/parkRowBirthWait.d.ts +9 -0
- package/dist/hitl/parkRowBirthWait.js +8 -1
- package/dist/hitl/parkRowDelegation.d.ts +6 -0
- package/dist/hitl/parkRowDelegation.js +33 -0
- package/dist/hitl/persistedRulesWire.d.ts +19 -3
- package/dist/hitl/persistedRulesWire.js +118 -20
- package/dist/hitl/planReviewWire.js +16 -18
- package/dist/hitl/policyDenyCause.d.ts +3 -0
- package/dist/hitl/policyDenyCause.js +16 -0
- package/dist/hitl/sessionAllowRoute.d.ts +5 -0
- package/dist/hitl/sessionAllowRoute.js +42 -0
- package/dist/hitl/sessionPolicyDeliverable.js +1 -1
- package/dist/hitl/standingRows.js +10 -10
- package/dist/hitl/toolApprovalWire.d.ts +8 -2
- package/dist/hitl/toolApprovalWire.js +44 -11
- package/dist/hooksReaderGenerations.d.ts +1 -0
- package/dist/hooksReaderGenerations.js +12 -0
- package/dist/hooksWireCaps.js +8 -4
- package/dist/i18n/messages.en.d.ts +43 -0
- package/dist/i18n/messages.en.js +47 -4
- package/dist/i18n/messages.zh.js +53 -4
- package/dist/index.d.ts +7 -1
- package/dist/index.js +5 -1
- package/dist/injectionLaneNotice.js +2 -1
- package/dist/liveModelCatalog.d.ts +4 -2
- package/dist/liveModelCatalog.js +164 -26
- package/dist/model/catalogDefault.d.ts +25 -0
- package/dist/model/catalogDefault.js +44 -0
- package/dist/noticePresentation.js +2 -0
- package/dist/permissionWireCaps.d.ts +1 -0
- package/dist/permissionWireCaps.js +11 -0
- package/dist/pluginHooksWire.d.ts +5 -3
- package/dist/pluginHooksWire.js +116 -7
- package/dist/queuedDelivery.d.ts +1 -0
- package/dist/queuedDelivery.js +12 -0
- package/dist/resultFrameKeys.js +4 -2
- package/dist/resumeRefusalCopy.js +15 -8
- package/dist/runPermissionModeSwitch.d.ts +2 -1
- package/dist/runPermissionModeSwitch.js +16 -6
- package/dist/sdkWireTransit.d.ts +1 -1
- package/dist/seatContract.d.ts +64 -10
- package/dist/seatContract.js +167 -12
- package/dist/sessionEngine.d.ts +2 -1
- package/dist/sessionEngine.js +5 -0
- package/dist/settingsHooksSalvage.d.ts +5 -0
- package/dist/settingsHooksSalvage.js +54 -0
- package/dist/subagentContentStore.js +1 -1
- package/dist/supportFloor.js +2 -0
- package/dist/taskNotificationAdmission.d.ts +19 -0
- package/dist/taskNotificationAdmission.js +84 -0
- package/dist/toolResult.js +2 -1
- package/dist/webSearchWireCaps.js +9 -4
- package/dist/wireRefusalCopy.d.ts +7 -1
- package/dist/wireRefusalCopy.js +7 -1
- package/docs/INTEGRATION-CLIENTS.md +1189 -18
- package/package.json +5 -5
package/dist/hitl/frameRouter.js
CHANGED
|
@@ -13,6 +13,8 @@ import { surfaceForCurrentSession, surfaceReplayedApprovalListUnreadable } from
|
|
|
13
13
|
import { liveCardHoldOf, noteEngineClosedApproval, settleEngineClosedApproval } from './approvalHolds.js';
|
|
14
14
|
import { approvalCompleteParked } from './approvalCompleteParked.js';
|
|
15
15
|
import { liveFrameCallKey } from './gateIdentity.js';
|
|
16
|
+
import { readPolicyDenyCause } from './policyDenyCause.js';
|
|
17
|
+
import { msg, renderMessage } from '../i18n/translate.js';
|
|
16
18
|
import { SEMA_DENIAL_KIND_KEY, hasLedgerPositionKey } from './gateLedger.js';
|
|
17
19
|
import { replayedApprovalClosesCardFrame, replayedApprovalDisposition, settledApprovalDisposition } from './replayedApproval.js';
|
|
18
20
|
import { settleWithinDeadline } from '../abortableSleep.js';
|
|
@@ -64,10 +66,33 @@ export function policyDenyReason(facts) {
|
|
|
64
66
|
function denyOutputForRender(attribution) {
|
|
65
67
|
if (attribution?.settledBy !== 'policy')
|
|
66
68
|
return HITL_REJECT_MESSAGE;
|
|
69
|
+
const head = policyDenyHeadFor(readPolicyDenyCause(attribution));
|
|
67
70
|
const reason = attribution.reason;
|
|
68
71
|
return reason !== undefined && reason.trim() !== ''
|
|
69
|
-
? `${
|
|
70
|
-
:
|
|
72
|
+
? `${head}\n${reason}`
|
|
73
|
+
: head;
|
|
74
|
+
}
|
|
75
|
+
function policyDenyHeadFor(cause) {
|
|
76
|
+
const tail = msg('approval.policyDeny.tail');
|
|
77
|
+
switch (cause) {
|
|
78
|
+
case 'dont_ask':
|
|
79
|
+
return renderMessage(msg('approval.policyDeny.dontAsk', { tail }));
|
|
80
|
+
case 'deny_rule':
|
|
81
|
+
return renderMessage(msg('approval.policyDeny.denyRule', { tail }));
|
|
82
|
+
case 'engine_floor':
|
|
83
|
+
return renderMessage(msg('approval.policyDeny.engineFloor', { tail }));
|
|
84
|
+
case 'no_approval_ui':
|
|
85
|
+
case undefined:
|
|
86
|
+
return HITL_POLICY_DENY_MESSAGE;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export function denyAttributionOf(deny) {
|
|
90
|
+
const policyCause = readPolicyDenyCause(deny);
|
|
91
|
+
return {
|
|
92
|
+
...(deny?.settledBy !== undefined ? { settledBy: deny.settledBy } : {}),
|
|
93
|
+
...(deny?.reason !== undefined ? { reason: deny.reason } : {}),
|
|
94
|
+
...(policyCause !== undefined ? { policyCause } : {}),
|
|
95
|
+
};
|
|
71
96
|
}
|
|
72
97
|
function denialKindForTranscript(attribution) {
|
|
73
98
|
return ccToolDenialKindForSettledBy(attribution?.settledBy);
|
|
@@ -354,10 +379,7 @@ function settleLiveCardOutcome(ev, outcome, ctx, fromSubagent, gatedCallId) {
|
|
|
354
379
|
invokeHostCallbackIsolated('liveHitlAskWire: onToolApprovalOutcome', () => deps.onToolApprovalOutcome?.(ev, outcome));
|
|
355
380
|
}
|
|
356
381
|
if (outcome.kind === 'decided' && outcome.decision === 'deny' && !fromSubagent) {
|
|
357
|
-
const attribution =
|
|
358
|
-
...(outcome.deny?.settledBy !== undefined ? { settledBy: outcome.deny.settledBy } : {}),
|
|
359
|
-
...(outcome.deny?.reason !== undefined ? { reason: outcome.deny.reason } : {}),
|
|
360
|
-
};
|
|
382
|
+
const attribution = denyAttributionOf(outcome.deny);
|
|
361
383
|
if (gatedCallId !== undefined)
|
|
362
384
|
led.markDenied(gatedCallId, attribution);
|
|
363
385
|
else
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { AgentEvent } from '@sema-agent/sdk';
|
|
2
|
+
import { type PolicyDenyCause } from './policyDenyCause.js';
|
|
2
3
|
export declare const SEMA_COLLATERAL_ABORT_KEY = "_sema_collateral_abort";
|
|
3
4
|
export declare const SEMA_DENIAL_KIND_KEY = "_sema_denial_kind";
|
|
4
5
|
export interface GateDenyAttribution {
|
|
5
6
|
settledBy?: 'human' | 'policy';
|
|
6
7
|
reason?: string;
|
|
8
|
+
policyCause?: PolicyDenyCause;
|
|
7
9
|
}
|
|
8
10
|
export interface AskAnsweredOutput {
|
|
9
11
|
type: 'ask-user-question';
|
package/dist/hitl/gateLedger.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parkedToolEndRoleOf } from '../engineRowRef.js';
|
|
2
2
|
import { inheritFrameBinding } from '../frameBinding.js';
|
|
3
|
+
import { readPolicyDenyCause } from './policyDenyCause.js';
|
|
3
4
|
export const SEMA_COLLATERAL_ABORT_KEY = '_sema_collateral_abort';
|
|
4
5
|
export const SEMA_DENIAL_KIND_KEY = '_sema_denial_kind';
|
|
5
6
|
export function durableSeqOf(ev) {
|
|
@@ -27,9 +28,10 @@ function normalizeDenyAttribution(a) {
|
|
|
27
28
|
return undefined;
|
|
28
29
|
const settledBy = a.settledBy === 'human' || a.settledBy === 'policy' ? a.settledBy : undefined;
|
|
29
30
|
const reason = typeof a.reason === 'string' && a.reason.trim() !== '' ? a.reason : undefined;
|
|
31
|
+
const policyCause = settledBy === 'policy' ? readPolicyDenyCause(a) : undefined;
|
|
30
32
|
if (settledBy === undefined && reason === undefined)
|
|
31
33
|
return undefined;
|
|
32
|
-
return { ...(settledBy !== undefined ? { settledBy } : {}), ...(reason !== undefined ? { reason } : {}) };
|
|
34
|
+
return { ...(settledBy !== undefined ? { settledBy } : {}), ...(reason !== undefined ? { reason } : {}), ...(policyCause !== undefined ? { policyCause } : {}) };
|
|
33
35
|
}
|
|
34
36
|
export function createGateLedger() {
|
|
35
37
|
const startedCalls = new Set();
|
|
@@ -3,6 +3,7 @@ import type { PermissionResult } from '@sema-agent/agent-types';
|
|
|
3
3
|
import { type CapturedLocalCode, type CauseSlotRead } from '../engineWireFor.js';
|
|
4
4
|
import type { ApprovalsListEnvelope } from './crashConverged.js';
|
|
5
5
|
import { type SuspendedReopenReading } from './suspendedReopen.js';
|
|
6
|
+
import type { CheckpointRowVerdictReading } from '../checkpointRowVerdict.js';
|
|
6
7
|
export declare const DEFAULT_DENY_REASON = "The user rejected this tool use";
|
|
7
8
|
export declare const MAX_DENY_REASON_CHARS = 4096;
|
|
8
9
|
export declare function denyReasonForWire(reason: string | undefined, tag: string): string | undefined;
|
|
@@ -132,7 +133,10 @@ export type FindPendingOutcome = {
|
|
|
132
133
|
} | {
|
|
133
134
|
ok: false;
|
|
134
135
|
reason: string;
|
|
135
|
-
code?: 'no_pending' | 'wrong_gate';
|
|
136
|
+
code?: 'no_pending' | 'wrong_gate' | 'row_unresumable';
|
|
137
|
+
rowVerdict?: Exclude<CheckpointRowVerdictReading, {
|
|
138
|
+
readonly kind: 'ok';
|
|
139
|
+
}>;
|
|
136
140
|
};
|
|
137
141
|
export declare function findPendingForTask(client: HitlClientLike, taskId: string, matches: (toolName: string | undefined) => boolean, opts?: {
|
|
138
142
|
signal?: AbortSignal;
|
package/dist/hitl/hitlBridge.js
CHANGED
|
@@ -11,6 +11,7 @@ import { suspendedReopenOf } from './suspendedReopen.js';
|
|
|
11
11
|
import { closeDecisionOp, decisionLedgerKeysOf, openDecisionOp } from './decisionLedger.js';
|
|
12
12
|
import { askParkBlockGeneration, noteAskParkDecideSettled } from './askParkAutoReopenBlock.js';
|
|
13
13
|
import { checkpointQueueRowSortOf, QUEUE_ROW_FAMILY_UNREADABLE_GATE } from './approvalQueueRow.js';
|
|
14
|
+
import { checkpointDeadlineStateOf, checkpointRowBarOf, checkpointRowBarSentence, unboundWayOutContent } from '../checkpointRowVerdict.js';
|
|
14
15
|
export const DEFAULT_DENY_REASON = 'The user rejected this tool use';
|
|
15
16
|
export const MAX_DENY_REASON_CHARS = 4096;
|
|
16
17
|
export function denyReasonForWire(reason, tag) {
|
|
@@ -283,6 +284,9 @@ export async function findPendingForTask(client, taskId, matches, opts, callId,
|
|
|
283
284
|
? readings.find((x) => x.r.taskId === taskId && (x.r.boundCallId === callId || x.r.toolCallId === callId) && x.foreign !== null)
|
|
284
285
|
: undefined;
|
|
285
286
|
if (identityForeign !== undefined) {
|
|
287
|
+
const identityBar = identityForeign.foreign === QUEUE_ROW_FAMILY_UNREADABLE_GATE ? checkpointRowBarOf(identityForeign.r) : undefined;
|
|
288
|
+
if (identityBar !== undefined)
|
|
289
|
+
return { ok: false, reason: checkpointRowBarSentence(identityBar), code: 'row_unresumable', rowVerdict: identityBar };
|
|
286
290
|
return {
|
|
287
291
|
ok: false,
|
|
288
292
|
reason: `the pending row this park names (call ${callId}) is parked on a '${String(identityForeign.foreign)}' gate — not an approval this leg decides; refusing to decide a different row`,
|
|
@@ -294,8 +298,19 @@ export async function findPendingForTask(client, taskId, matches, opts, callId,
|
|
|
294
298
|
const pending = (identityConflicts ? undefined : identityRow) ??
|
|
295
299
|
pool.find((r) => r.taskId === taskId && matches(typeof r.toolName === 'string' ? r.toolName : undefined)) ??
|
|
296
300
|
pool.find((r) => r.taskId === taskId);
|
|
297
|
-
if (!pending)
|
|
301
|
+
if (!pending) {
|
|
302
|
+
for (const x of readings) {
|
|
303
|
+
if (x.r.taskId !== taskId || x.foreign !== QUEUE_ROW_FAMILY_UNREADABLE_GATE)
|
|
304
|
+
continue;
|
|
305
|
+
const unreadableBar = checkpointRowBarOf(x.r);
|
|
306
|
+
if (unreadableBar !== undefined)
|
|
307
|
+
return { ok: false, reason: checkpointRowBarSentence(unreadableBar), code: 'row_unresumable', rowVerdict: unreadableBar };
|
|
308
|
+
}
|
|
298
309
|
return { ok: false, reason: 'no pending checkpoint for this run (resolved/expired?)', code: 'no_pending' };
|
|
310
|
+
}
|
|
311
|
+
const bar = checkpointRowBarOf(pending);
|
|
312
|
+
if (bar !== undefined)
|
|
313
|
+
return { ok: false, reason: checkpointRowBarSentence(bar), code: 'row_unresumable', rowVerdict: bar };
|
|
299
314
|
return { ok: true, pending, gatedCallId: pending.toolCallId ?? pending.boundCallId ?? undefined };
|
|
300
315
|
}
|
|
301
316
|
export class HitlBridge {
|
|
@@ -349,7 +364,7 @@ export class HitlBridge {
|
|
|
349
364
|
if (typeof boundCallId !== 'string' || boundCallId.length === 0 || typeof boundInputHash !== 'string' || boundInputHash.length === 0) {
|
|
350
365
|
throw new HitlSafetyError(`pending checkpoint ${p.id ?? '<no-id>'} is missing D-1 binding fields ` +
|
|
351
366
|
`(boundCallId=${String(boundCallId)}, boundInputHash=${String(boundInputHash)}) — ` +
|
|
352
|
-
|
|
367
|
+
`the row itself carries no binding to echo, so ${unboundWayOutContent(checkpointDeadlineStateOf(p))}`, 'binding_missing');
|
|
353
368
|
}
|
|
354
369
|
return { boundCallId, boundInputHash };
|
|
355
370
|
}
|
|
@@ -358,6 +373,8 @@ export class HitlBridge {
|
|
|
358
373
|
if (!pending) {
|
|
359
374
|
throw new HitlSafetyError('no pending checkpoint to decide (resolved/expired under the human) — refetch + re-present', 'no_pending');
|
|
360
375
|
}
|
|
376
|
+
if (preResolvedPending !== undefined)
|
|
377
|
+
assertPendingRowResumable(preResolvedPending);
|
|
361
378
|
const decision = outcome.decision === 'approve'
|
|
362
379
|
? {
|
|
363
380
|
decision: 'approve',
|
|
@@ -383,6 +400,8 @@ export class HitlBridge {
|
|
|
383
400
|
if (!pending) {
|
|
384
401
|
throw new HitlSafetyError('no pending AskUserQuestion checkpoint to answer — refetch + re-present', 'no_pending');
|
|
385
402
|
}
|
|
403
|
+
if (preResolvedPending !== undefined)
|
|
404
|
+
assertPendingRowResumable(preResolvedPending);
|
|
386
405
|
const decision = {
|
|
387
406
|
decision: 'approve',
|
|
388
407
|
answer: { answers: answers.map((a) => marshalAnswer(a)) },
|
|
@@ -541,6 +560,11 @@ export function makeHitlCanUseTool(bridge, prompt) {
|
|
|
541
560
|
return decision;
|
|
542
561
|
};
|
|
543
562
|
}
|
|
563
|
+
function assertPendingRowResumable(pending) {
|
|
564
|
+
const bar = checkpointRowBarOf(pending);
|
|
565
|
+
if (bar !== undefined)
|
|
566
|
+
throw new Error(checkpointRowBarSentence(bar));
|
|
567
|
+
}
|
|
544
568
|
function assertAnswersSendable(answers) {
|
|
545
569
|
if (!Array.isArray(answers) || answers.length === 0) {
|
|
546
570
|
throw new HitlSafetyError('refusing to send an EMPTY AskUserQuestion answer set — `{answers:[]}` is the wire\'s DENY/NO_HUMAN form, ' +
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isApprovalRequestFrameV1 } from '@sema-agent/sdk';
|
|
2
2
|
import { appendOwnElement } from '../ownKey.js';
|
|
3
3
|
import { readRequiresRealApproval } from './approvalCardReading.js';
|
|
4
|
+
import { pendingRowIsOfSession } from './parkRowDelegation.js';
|
|
4
5
|
import { approvalLiveFaceOf, liveFaceCarried } from './approvalLiveFace.js';
|
|
5
6
|
import { THROWN_SLOT_UNREADABLE, readThrownSlot } from '../wireFailureShape.js';
|
|
6
7
|
import { isToolApprovalFrame, surfaceToolApprovalFrameAndRespond, } from './toolApprovalWire.js';
|
|
@@ -124,7 +125,8 @@ export function countApprovalsAwaitingDecision(snapshot, filter) {
|
|
|
124
125
|
if (snapshot.kind === 'unknown')
|
|
125
126
|
return { durable: null, live: null, suspendedSubagent: null };
|
|
126
127
|
const bySession = (rows) => scope.kind === 'session' ? rows.filter(r => r.sessionId === scope.sessionId) : [...rows];
|
|
127
|
-
const
|
|
128
|
+
const durableOfSession = (rows) => scope.kind === 'session' ? rows.filter(r => pendingRowIsOfSession(r, scope.sessionId)).length : rows.length;
|
|
129
|
+
const durable = (snapshot.pendingDropped ?? 0) > 0 ? null : Array.isArray(snapshot.pending) ? durableOfSession(snapshot.pending) : null;
|
|
128
130
|
if (!Array.isArray(snapshot.livePending))
|
|
129
131
|
return { durable, live: null, suspendedSubagent: null };
|
|
130
132
|
if ((snapshot.livePendingDropped ?? 0) > 0)
|
|
@@ -138,7 +140,7 @@ export function livePendingRowToApprovalFrame(row) {
|
|
|
138
140
|
approvalId: row.approvalId,
|
|
139
141
|
toolName: row.toolName,
|
|
140
142
|
...(row.fromSubagent === true ? { fromSubagent: true } : {}),
|
|
141
|
-
...(row.originTaskId !== undefined ? {
|
|
143
|
+
...(row.originTaskId !== undefined ? { sourceSessionId: row.originTaskId } : {}),
|
|
142
144
|
...(row.governanceForced === true ? { governanceForced: true } : {}),
|
|
143
145
|
...(readRequiresRealApproval(row) ? { requiresRealApproval: true } : {}),
|
|
144
146
|
...liveFaceCarried(approvalLiveFaceOf(row)),
|
|
@@ -2,6 +2,7 @@ import { isOwnEngineRun } from '../subagentContentStore.js';
|
|
|
2
2
|
import { rowIdTail } from '../workflow.js';
|
|
3
3
|
import { hostSessionFor } from '../host.js';
|
|
4
4
|
import { DEFAULT_SESSION_KEY } from '../sessionSlot.js';
|
|
5
|
+
import { pendingRowIsOfSession } from './parkRowDelegation.js';
|
|
5
6
|
export function pendingRowIsOwnedByThisSession(row, deps) {
|
|
6
7
|
const sessionKey = deps?.sessionKey ?? DEFAULT_SESSION_KEY;
|
|
7
8
|
const rowTaskId = typeof row.taskId === 'string' ? row.taskId : '';
|
|
@@ -14,11 +15,9 @@ export function pendingRowIsOwnedByThisSession(row, deps) {
|
|
|
14
15
|
}
|
|
15
16
|
const currentSessionId = deps?.currentSessionId ?? (() => hostSessionFor(sessionKey)?.currentSessionId());
|
|
16
17
|
const sessionId = currentSessionId();
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
rowSessionId.length > 0 &&
|
|
21
|
-
rowSessionId === sessionId);
|
|
18
|
+
if (typeof sessionId !== 'string' || sessionId.length === 0)
|
|
19
|
+
return false;
|
|
20
|
+
return pendingRowIsOfSession(row, sessionId);
|
|
22
21
|
}
|
|
23
22
|
export function askQuestionsFromPending(pending) {
|
|
24
23
|
const input = pending.input;
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import { putOwnKey, readOwnDataValue } from '../ownKey.js';
|
|
2
2
|
import { HitlBridge, NO_QUESTION_OVERLAY_REASON, findPendingForTask, isNoDecisionSurfaceReason, } from './hitlBridge.js';
|
|
3
|
-
import { approvalResolutionDetail, directSafetyStopOf, resolutionOfDecisionFailure } from './approvalResolution.js';
|
|
3
|
+
import { approvalResolutionDetail, approvalResolutionStatusTag, directSafetyStopOf, resolutionOfDecisionFailure } from './approvalResolution.js';
|
|
4
4
|
import { readDecisionRefusalAsk } from './decisionRefusal.js';
|
|
5
5
|
import { publishQuestionFrame, registerLocalQuestionResponder, hasQuestionOverlay, } from '../liveQuestionStore.js';
|
|
6
6
|
import { hostLog } from '../host.js';
|
|
7
7
|
import { surfaceFsApprovalAndDecide } from './toolApprovalWire.js';
|
|
8
8
|
import { INTERRUPT_DENY_REASON, observeCancelByDeny } from './hitlHostSurface.js';
|
|
9
|
-
import { flushHeldWithInterruptRewrite, isAskTool } from './frameRouter.js';
|
|
9
|
+
import { denyAttributionOf, flushHeldWithInterruptRewrite, isAskTool } from './frameRouter.js';
|
|
10
|
+
import { invokeHostCallbackIsolated } from '../hostCallbackGuard.js';
|
|
10
11
|
import { inheritFrameBinding } from '../frameBinding.js';
|
|
11
12
|
import { approvalCallKey, askGateQuestionId } from './gateIdentity.js';
|
|
12
13
|
import { readDecideReceipt } from '../decideReceipt.js';
|
|
13
14
|
import { decideFailureNote } from '../decideFailureNote.js';
|
|
14
15
|
import { wireFailureTextsOf } from '../wireFailureShape.js';
|
|
16
|
+
import { msg, renderMessage } from '../i18n/translate.js';
|
|
17
|
+
import { refusedParkFromError } from '../resumeRefusalCopy.js';
|
|
15
18
|
export const MAX_GATE_HOPS = 2;
|
|
16
19
|
export function nextHopBudget(prev, round) {
|
|
17
20
|
const total = (prev?.total ?? 0) + 1;
|
|
@@ -63,7 +66,7 @@ async function surfaceGateAndDecide(deps, taskId, askArgsByCall, signal, parkGat
|
|
|
63
66
|
}
|
|
64
67
|
const found = await findPendingForTask(deps.client, taskId, isAskTool, signal ? { signal } : undefined, parkGatedCallId, (row) => isAskTool(typeof row.toolName === 'string' ? row.toolName : undefined));
|
|
65
68
|
if (!found.ok)
|
|
66
|
-
return { kind: 'not_sent', lane: 'ask', cause: found.code ?? 'fetch_failed', ...(found.reason !== '' ? { detail: found.reason } : {}) };
|
|
69
|
+
return { kind: 'not_sent', lane: 'ask', cause: found.code ?? 'fetch_failed', ...(found.reason !== '' ? { detail: found.reason } : {}), ...(found.rowVerdict !== undefined ? { rowVerdict: found.rowVerdict } : {}) };
|
|
67
70
|
const { pending, gatedCallId } = found;
|
|
68
71
|
const at = { lane: 'ask', ...(gatedCallId !== undefined ? { gatedCallId } : {}) };
|
|
69
72
|
const fromArgs = gatedCallId
|
|
@@ -159,6 +162,10 @@ function failureReasonOf(r) {
|
|
|
159
162
|
return '';
|
|
160
163
|
return r.detail ?? approvalResolutionDetail(r) ?? r.cause;
|
|
161
164
|
}
|
|
165
|
+
function failsoftReasonOf(r) {
|
|
166
|
+
const tag = approvalResolutionStatusTag(r);
|
|
167
|
+
return tag === '' ? failureReasonOf(r) : `${failureReasonOf(r)} ${tag}`;
|
|
168
|
+
}
|
|
162
169
|
function orchestrationEnd(park, gatedCallId, detail) {
|
|
163
170
|
return { kind: 'unsettled', lane: park.gate === 'fs' ? 'decide' : 'ask', cause: 'orchestration', ...(gatedCallId !== undefined ? { gatedCallId } : {}), detail };
|
|
164
171
|
}
|
|
@@ -179,10 +186,7 @@ async function surfaceParkGate(park, ctx, gatedCallId, onPresented) {
|
|
|
179
186
|
...(decidedByReading !== undefined ? { approvalDecidedBy: decidedByReading } : {}),
|
|
180
187
|
}, taskId, argsByCallOf(led), ctx.signal, gatedCallId, park.toolName);
|
|
181
188
|
if (outcome.kind === 'decided' && outcome.decision === 'deny' && outcome.gatedCallId) {
|
|
182
|
-
led.markDenied(outcome.gatedCallId,
|
|
183
|
-
...(outcome.deny?.settledBy !== undefined ? { settledBy: outcome.deny.settledBy } : {}),
|
|
184
|
-
...(outcome.deny?.reason !== undefined ? { reason: outcome.deny.reason } : {}),
|
|
185
|
-
});
|
|
189
|
+
led.markDenied(outcome.gatedCallId, denyAttributionOf(outcome.deny));
|
|
186
190
|
}
|
|
187
191
|
return outcome;
|
|
188
192
|
}
|
|
@@ -282,9 +286,9 @@ export async function resolvePark(park, ctx) {
|
|
|
282
286
|
? undefined
|
|
283
287
|
: outcome.kind === 'not_sent' && outcome.cause === 'retracted'
|
|
284
288
|
? 'the approval card was retracted by the host without a decision (nothing was sent; the run stays parked and remains decidable elsewhere)'
|
|
285
|
-
:
|
|
289
|
+
: failsoftReasonOf(outcome);
|
|
286
290
|
const noDecisionSurface = outcome.kind === 'not_sent' && outcome.cause === 'card_unavailable' && outcome.detail !== undefined && isNoDecisionSurfaceReason(outcome.detail);
|
|
287
|
-
return { kind: 'failsoft', events: failsoftEvents(park, ctx, interrupted, failedReason, undefined, noDecisionSurface) };
|
|
291
|
+
return { kind: 'failsoft', events: failsoftEvents(park, ctx, interrupted, failedReason, undefined, noDecisionSurface, failsoftWayOutOf(outcome)) };
|
|
288
292
|
}
|
|
289
293
|
led.dropHeldForDecidedPark(outcome.gatedCallId, park.gatedCallId);
|
|
290
294
|
if (outcome.gatedCallId) {
|
|
@@ -307,15 +311,27 @@ export async function resolvePark(park, ctx) {
|
|
|
307
311
|
...(handoffTaskId !== undefined ? { handoffTaskId } : {}),
|
|
308
312
|
};
|
|
309
313
|
}
|
|
310
|
-
function
|
|
314
|
+
function failsoftWayOutOf(outcome) {
|
|
315
|
+
if (outcome.kind === 'not_sent' && (outcome.cause === 'row_unresumable' || (outcome.cause === 'safety_stop' && outcome.safetyCode === 'binding_missing')))
|
|
316
|
+
return 'reason';
|
|
317
|
+
if (outcome.kind === 'unsettled' && outcome.cause === 'engine_refused' && refusedParkFromError(outcome.refusal) !== null)
|
|
318
|
+
return 'cancel';
|
|
319
|
+
return 'card';
|
|
320
|
+
}
|
|
321
|
+
function failsoftReasonSentence(park, failedReason, terminal, wayOut = 'card') {
|
|
311
322
|
if (terminal !== undefined)
|
|
312
323
|
return terminal.errorMessage;
|
|
313
324
|
if (failedReason === undefined)
|
|
314
325
|
return undefined;
|
|
326
|
+
const gate = park.gate === 'fs' ? 'Tool approval' : 'AskUserQuestion';
|
|
327
|
+
if (wayOut === 'reason')
|
|
328
|
+
return renderMessage(msg('approval.park.stillParked', { gate, reason: failedReason }));
|
|
329
|
+
if (wayOut === 'cancel')
|
|
330
|
+
return renderMessage(msg('approval.park.refusedNotResumable', { gate, reason: failedReason }));
|
|
315
331
|
return (`${park.gate === 'fs' ? 'Tool approval' : 'AskUserQuestion'} gate could not be answered: ${failedReason}` +
|
|
316
332
|
' — the run is still parked on this approval: decide it on the card when it is shown again, or cancel the run.');
|
|
317
333
|
}
|
|
318
|
-
function failsoftEvents(park, ctx, gateAbortedByUser, failedReason, terminal, noDecisionSurface = false) {
|
|
334
|
+
function failsoftEvents(park, ctx, gateAbortedByUser, failedReason, terminal, noDecisionSurface = false, wayOut = 'card') {
|
|
319
335
|
const events = [
|
|
320
336
|
...flushHeldWithInterruptRewrite(ctx.led, {
|
|
321
337
|
terminal: park.pendingDone,
|
|
@@ -323,7 +339,7 @@ function failsoftEvents(park, ctx, gateAbortedByUser, failedReason, terminal, no
|
|
|
323
339
|
gateAbortedByUser,
|
|
324
340
|
}),
|
|
325
341
|
];
|
|
326
|
-
const reason = failsoftReasonSentence(park, failedReason, terminal);
|
|
342
|
+
const reason = failsoftReasonSentence(park, failedReason, terminal, wayOut);
|
|
327
343
|
if (park.pendingDone) {
|
|
328
344
|
events.push(reason === undefined || noDecisionSurface ? park.pendingDone : inheritFrameBinding(park.pendingDone, { ...park.pendingDone, _sema_park_failed_reason: reason }));
|
|
329
345
|
}
|
|
@@ -352,15 +368,12 @@ export function stalledTerminal(park, ctx, stalledRounds, lastStallReason, decid
|
|
|
352
368
|
return { kind: 'failsoft', events: failsoftEvents(park, ctx, false, reason) };
|
|
353
369
|
}
|
|
354
370
|
function notifyParkReattach(ctx, attempt, max, reason) {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
cb({ attempt, max, reason });
|
|
360
|
-
}
|
|
361
|
-
catch (e) {
|
|
362
|
-
hostLog('debug', `liveHitlAskWire: onParkReattach threw (${String(e)}) — ignored`);
|
|
363
|
-
}
|
|
371
|
+
invokeHostCallbackIsolated('liveHitlAskWire: onParkReattach', () => {
|
|
372
|
+
const cb = ctx.deps.onParkReattach;
|
|
373
|
+
if (typeof cb !== 'function' || ctx.signal?.aborted === true)
|
|
374
|
+
return;
|
|
375
|
+
return cb.call(ctx.deps, { attempt, max, reason });
|
|
376
|
+
});
|
|
364
377
|
}
|
|
365
378
|
function transportReasonToken(reason) {
|
|
366
379
|
const m = /(ECONNREFUSED|ECONNRESET|ETIMEDOUT|EPIPE|EAI_AGAIN|TimeoutError|AbortError|fetch failed|network)/i.exec(reason);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CheckpointRowVerdictReading } from '../checkpointRowVerdict.js';
|
|
1
2
|
export type ParkRowProbe<Row> = {
|
|
2
3
|
kind: 'row';
|
|
3
4
|
row: Row;
|
|
@@ -7,6 +8,10 @@ export type ParkRowProbe<Row> = {
|
|
|
7
8
|
} | {
|
|
8
9
|
kind: 'settled';
|
|
9
10
|
reason: string;
|
|
11
|
+
rowVerdict?: Exclude<CheckpointRowVerdictReading, {
|
|
12
|
+
readonly kind: 'ok';
|
|
13
|
+
}>;
|
|
14
|
+
rowTaskId?: string;
|
|
10
15
|
};
|
|
11
16
|
export type ParkRowWaitVerdict<Row> = {
|
|
12
17
|
kind: 'row';
|
|
@@ -23,6 +28,10 @@ export type ParkRowWaitVerdict<Row> = {
|
|
|
23
28
|
reason: string;
|
|
24
29
|
waitedMs: number;
|
|
25
30
|
probes: number;
|
|
31
|
+
rowVerdict?: Exclude<CheckpointRowVerdictReading, {
|
|
32
|
+
readonly kind: 'ok';
|
|
33
|
+
}>;
|
|
34
|
+
rowTaskId?: string;
|
|
26
35
|
} | {
|
|
27
36
|
kind: 'aborted';
|
|
28
37
|
waitedMs: number;
|
|
@@ -103,7 +103,14 @@ export async function waitForParkRowBirth(deps) {
|
|
|
103
103
|
if (probed.kind === 'row')
|
|
104
104
|
return { kind: 'row', row: probed.row, waitedMs: now() - startedAt, probes };
|
|
105
105
|
if (probed.kind === 'settled') {
|
|
106
|
-
return {
|
|
106
|
+
return {
|
|
107
|
+
kind: 'settled',
|
|
108
|
+
reason: probed.reason,
|
|
109
|
+
waitedMs: now() - startedAt,
|
|
110
|
+
probes,
|
|
111
|
+
...(probed.rowVerdict !== undefined ? { rowVerdict: probed.rowVerdict } : {}),
|
|
112
|
+
...(probed.rowTaskId !== undefined ? { rowTaskId: probed.rowTaskId } : {}),
|
|
113
|
+
};
|
|
107
114
|
}
|
|
108
115
|
lastReason = probed.reason;
|
|
109
116
|
const remainingMs = budgetMs - (now() - startedAt);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export interface ParkRowDelegationLinks {
|
|
2
|
+
readonly parentSessionId?: string;
|
|
3
|
+
readonly rootSessionId?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function pendingRowIsOfSession(row: object, sessionId: string): boolean;
|
|
6
|
+
export declare function parkRowDelegationLinksOf(row: object | null | undefined): ParkRowDelegationLinks | undefined;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { slotReadOf } from '../wireFailureShape.js';
|
|
2
|
+
export function pendingRowIsOfSession(row, sessionId) {
|
|
3
|
+
if (sessionId.length === 0)
|
|
4
|
+
return false;
|
|
5
|
+
const rowSessionId = typeof row.sessionId === 'string' ? row.sessionId : '';
|
|
6
|
+
if (rowSessionId.length > 0 && rowSessionId === sessionId)
|
|
7
|
+
return true;
|
|
8
|
+
const links = parkRowDelegationLinksOf(row);
|
|
9
|
+
return links !== undefined && (links.parentSessionId === sessionId || links.rootSessionId === sessionId);
|
|
10
|
+
}
|
|
11
|
+
export function parkRowDelegationLinksOf(row) {
|
|
12
|
+
try {
|
|
13
|
+
if (typeof row !== 'object' || row === null)
|
|
14
|
+
return undefined;
|
|
15
|
+
const a = slotReadOf(row, 'agent');
|
|
16
|
+
if (a.presence !== 'present' || typeof a.value !== 'object' || a.value === null)
|
|
17
|
+
return undefined;
|
|
18
|
+
const agent = a.value;
|
|
19
|
+
const link = (key) => {
|
|
20
|
+
const r = slotReadOf(agent, key);
|
|
21
|
+
return r.presence === 'present' && typeof r.value === 'string' && r.value !== '' ? r.value : undefined;
|
|
22
|
+
};
|
|
23
|
+
const parentSessionId = link('parentSessionId');
|
|
24
|
+
const rootSessionId = link('rootSessionId');
|
|
25
|
+
return Object.freeze({
|
|
26
|
+
...(parentSessionId !== undefined ? { parentSessionId } : {}),
|
|
27
|
+
...(rootSessionId !== undefined ? { rootSessionId } : {}),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CcImportLayer, CcImportPrepareResult,
|
|
1
|
+
import type { CcImportLayer, CcImportPrepareResult, PersistedRule, RuleBehavior, RuleListParams, RuleListResult, RuleRevokeRequest, RemovalLiveness, RuleRevokeResult, RuleWriteRequest, RuleWriteResult, RulesResource } from '@sema-agent/sdk';
|
|
2
2
|
import type { UiLanguage } from '../env/uiLanguage.js';
|
|
3
3
|
export interface RulesFacade {
|
|
4
4
|
list(params?: RuleListParams, opts?: {
|
|
@@ -15,17 +15,33 @@ export interface RulesFacade {
|
|
|
15
15
|
}): Promise<CcImportPrepareResult>;
|
|
16
16
|
ccImportRedeem(ticket: string, opts?: {
|
|
17
17
|
signal?: AbortSignal;
|
|
18
|
-
}):
|
|
18
|
+
}): ReturnType<RulesResource['ccImportRedeem']>;
|
|
19
19
|
}
|
|
20
20
|
export type RulesListFacade = Pick<RulesFacade, 'list'>;
|
|
21
21
|
export declare function persistedRulesLaneAvailable(baseUrl: string | undefined): boolean;
|
|
22
22
|
export declare function persistedRulesGovernanceAvailable(baseUrl: string | undefined): boolean;
|
|
23
23
|
export declare function persistedRulesWriteAvailable(baseUrl: string | undefined): boolean;
|
|
24
|
+
export interface CcImportRedeemIndeterminate {
|
|
25
|
+
readonly key?: CcImportRedeemRuleKey;
|
|
26
|
+
readonly reason?: string;
|
|
27
|
+
readonly detail?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface CcImportRedeemRuleKey {
|
|
30
|
+
readonly behavior: string;
|
|
31
|
+
readonly rule: string;
|
|
32
|
+
readonly scope: string;
|
|
33
|
+
}
|
|
34
|
+
export interface CcImportRedeemRefused {
|
|
35
|
+
readonly key?: CcImportRedeemRuleKey;
|
|
36
|
+
}
|
|
24
37
|
export interface CcImportRedeemCounts {
|
|
25
38
|
persisted: number;
|
|
26
39
|
deduped: number;
|
|
40
|
+
indeterminate: readonly CcImportRedeemIndeterminate[];
|
|
41
|
+
refused?: readonly CcImportRedeemRefused[];
|
|
27
42
|
}
|
|
28
|
-
export
|
|
43
|
+
export type CcImportReceiptOutcome = 'persisted' | 'no-op' | 'refused' | 'indeterminate';
|
|
44
|
+
export declare function readCcImportRedeemCounts(body: unknown): CcImportRedeemCounts | null;
|
|
29
45
|
export type RulesFailure = {
|
|
30
46
|
kind: 'lane-unavailable';
|
|
31
47
|
message: string;
|