@sema-agent/client-core 0.106.0 → 0.107.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 +81 -0
- package/README.md +20 -16
- package/dist/adapter/activeRunSelfHeal.d.ts +8 -0
- package/dist/adapter/activeRunSelfHeal.js +50 -22
- package/dist/checkpointRowVerdict.d.ts +6 -0
- package/dist/checkpointRowVerdict.js +9 -0
- package/dist/decideReceipt.js +1 -1
- package/dist/displayUntrusted.d.ts +5 -3
- package/dist/displayUntrusted.js +50 -41
- package/dist/engineNoticeCodes.d.ts +12 -0
- package/dist/engineNoticeCodes.js +117 -6
- package/dist/errorSupplementRoles.js +2 -1
- package/dist/executionLaneCapability.js +3 -2
- package/dist/executionLaneWords.d.ts +1 -0
- package/dist/executionLaneWords.js +3 -0
- package/dist/generated/engineNoticeTables.js +9 -3
- package/dist/hitl/approvalResolution.d.ts +5 -1
- package/dist/hitl/approvalResolution.js +18 -1
- package/dist/hitl/approvalsFeed.js +2 -1
- package/dist/hitl/askParkRowRouting.js +12 -0
- package/dist/hitl/decisionSend.d.ts +2 -0
- package/dist/hitl/decisionSend.js +20 -6
- package/dist/hitl/hitlBridge.d.ts +5 -1
- package/dist/hitl/hitlBridge.js +27 -2
- package/dist/hitl/livePendingAsk.js +4 -2
- package/dist/hitl/parkOwnership.js +4 -5
- package/dist/hitl/parkResolver.js +9 -5
- package/dist/hitl/parkRowBirthWait.d.ts +7 -0
- package/dist/hitl/parkRowBirthWait.js +1 -1
- package/dist/hitl/parkRowDelegation.d.ts +6 -0
- package/dist/hitl/parkRowDelegation.js +33 -0
- package/dist/hitl/persistedRulesWire.d.ts +15 -3
- package/dist/hitl/persistedRulesWire.js +102 -20
- package/dist/hitl/planReviewWire.js +4 -2
- package/dist/hitl/sessionAllowRoute.d.ts +5 -0
- package/dist/hitl/sessionAllowRoute.js +42 -0
- package/dist/hitl/standingRows.js +10 -10
- package/dist/hitl/toolApprovalWire.js +22 -8
- package/dist/i18n/messages.en.d.ts +6 -0
- package/dist/i18n/messages.en.js +8 -2
- package/dist/i18n/messages.zh.js +9 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/injectionLaneNotice.js +2 -1
- package/dist/noticePresentation.js +2 -0
- package/dist/sdkWireTransit.d.ts +1 -1
- package/dist/seatContract.d.ts +1 -0
- package/dist/seatContract.js +3 -1
- package/dist/wireRefusalCopy.d.ts +6 -1
- package/dist/wireRefusalCopy.js +7 -1
- package/docs/INTEGRATION-CLIENTS.md +382 -13
- package/package.json +5 -5
|
@@ -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;
|
|
@@ -12,6 +12,7 @@ import { approvalCallKey, askGateQuestionId } from './gateIdentity.js';
|
|
|
12
12
|
import { readDecideReceipt } from '../decideReceipt.js';
|
|
13
13
|
import { decideFailureNote } from '../decideFailureNote.js';
|
|
14
14
|
import { wireFailureTextsOf } from '../wireFailureShape.js';
|
|
15
|
+
import { msg, renderMessage } from '../i18n/translate.js';
|
|
15
16
|
export const MAX_GATE_HOPS = 2;
|
|
16
17
|
export function nextHopBudget(prev, round) {
|
|
17
18
|
const total = (prev?.total ?? 0) + 1;
|
|
@@ -63,7 +64,7 @@ async function surfaceGateAndDecide(deps, taskId, askArgsByCall, signal, parkGat
|
|
|
63
64
|
}
|
|
64
65
|
const found = await findPendingForTask(deps.client, taskId, isAskTool, signal ? { signal } : undefined, parkGatedCallId, (row) => isAskTool(typeof row.toolName === 'string' ? row.toolName : undefined));
|
|
65
66
|
if (!found.ok)
|
|
66
|
-
return { kind: 'not_sent', lane: 'ask', cause: found.code ?? 'fetch_failed', ...(found.reason !== '' ? { detail: found.reason } : {}) };
|
|
67
|
+
return { kind: 'not_sent', lane: 'ask', cause: found.code ?? 'fetch_failed', ...(found.reason !== '' ? { detail: found.reason } : {}), ...(found.rowVerdict !== undefined ? { rowVerdict: found.rowVerdict } : {}) };
|
|
67
68
|
const { pending, gatedCallId } = found;
|
|
68
69
|
const at = { lane: 'ask', ...(gatedCallId !== undefined ? { gatedCallId } : {}) };
|
|
69
70
|
const fromArgs = gatedCallId
|
|
@@ -284,7 +285,8 @@ export async function resolvePark(park, ctx) {
|
|
|
284
285
|
? 'the approval card was retracted by the host without a decision (nothing was sent; the run stays parked and remains decidable elsewhere)'
|
|
285
286
|
: failureReasonOf(outcome);
|
|
286
287
|
const noDecisionSurface = outcome.kind === 'not_sent' && outcome.cause === 'card_unavailable' && outcome.detail !== undefined && isNoDecisionSurfaceReason(outcome.detail);
|
|
287
|
-
|
|
288
|
+
const undecidableHere = outcome.kind === 'not_sent' && (outcome.cause === 'row_unresumable' || (outcome.cause === 'safety_stop' && outcome.safetyCode === 'binding_missing'));
|
|
289
|
+
return { kind: 'failsoft', events: failsoftEvents(park, ctx, interrupted, failedReason, undefined, noDecisionSurface, undecidableHere) };
|
|
288
290
|
}
|
|
289
291
|
led.dropHeldForDecidedPark(outcome.gatedCallId, park.gatedCallId);
|
|
290
292
|
if (outcome.gatedCallId) {
|
|
@@ -307,15 +309,17 @@ export async function resolvePark(park, ctx) {
|
|
|
307
309
|
...(handoffTaskId !== undefined ? { handoffTaskId } : {}),
|
|
308
310
|
};
|
|
309
311
|
}
|
|
310
|
-
function failsoftReasonSentence(park, failedReason, terminal) {
|
|
312
|
+
function failsoftReasonSentence(park, failedReason, terminal, reasonCarriesWayOut = false) {
|
|
311
313
|
if (terminal !== undefined)
|
|
312
314
|
return terminal.errorMessage;
|
|
313
315
|
if (failedReason === undefined)
|
|
314
316
|
return undefined;
|
|
317
|
+
if (reasonCarriesWayOut)
|
|
318
|
+
return renderMessage(msg('approval.park.stillParked', { gate: park.gate === 'fs' ? 'Tool approval' : 'AskUserQuestion', reason: failedReason }));
|
|
315
319
|
return (`${park.gate === 'fs' ? 'Tool approval' : 'AskUserQuestion'} gate could not be answered: ${failedReason}` +
|
|
316
320
|
' — the run is still parked on this approval: decide it on the card when it is shown again, or cancel the run.');
|
|
317
321
|
}
|
|
318
|
-
function failsoftEvents(park, ctx, gateAbortedByUser, failedReason, terminal, noDecisionSurface = false) {
|
|
322
|
+
function failsoftEvents(park, ctx, gateAbortedByUser, failedReason, terminal, noDecisionSurface = false, reasonCarriesWayOut = false) {
|
|
319
323
|
const events = [
|
|
320
324
|
...flushHeldWithInterruptRewrite(ctx.led, {
|
|
321
325
|
terminal: park.pendingDone,
|
|
@@ -323,7 +327,7 @@ function failsoftEvents(park, ctx, gateAbortedByUser, failedReason, terminal, no
|
|
|
323
327
|
gateAbortedByUser,
|
|
324
328
|
}),
|
|
325
329
|
];
|
|
326
|
-
const reason = failsoftReasonSentence(park, failedReason, terminal);
|
|
330
|
+
const reason = failsoftReasonSentence(park, failedReason, terminal, reasonCarriesWayOut);
|
|
327
331
|
if (park.pendingDone) {
|
|
328
332
|
events.push(reason === undefined || noDecisionSurface ? park.pendingDone : inheritFrameBinding(park.pendingDone, { ...park.pendingDone, _sema_park_failed_reason: reason }));
|
|
329
333
|
}
|
|
@@ -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,9 @@ export type ParkRowProbe<Row> = {
|
|
|
7
8
|
} | {
|
|
8
9
|
kind: 'settled';
|
|
9
10
|
reason: string;
|
|
11
|
+
rowVerdict?: Exclude<CheckpointRowVerdictReading, {
|
|
12
|
+
readonly kind: 'ok';
|
|
13
|
+
}>;
|
|
10
14
|
};
|
|
11
15
|
export type ParkRowWaitVerdict<Row> = {
|
|
12
16
|
kind: 'row';
|
|
@@ -23,6 +27,9 @@ export type ParkRowWaitVerdict<Row> = {
|
|
|
23
27
|
reason: string;
|
|
24
28
|
waitedMs: number;
|
|
25
29
|
probes: number;
|
|
30
|
+
rowVerdict?: Exclude<CheckpointRowVerdictReading, {
|
|
31
|
+
readonly kind: 'ok';
|
|
32
|
+
}>;
|
|
26
33
|
} | {
|
|
27
34
|
kind: 'aborted';
|
|
28
35
|
waitedMs: number;
|
|
@@ -103,7 +103,7 @@ 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 { kind: 'settled', reason: probed.reason, waitedMs: now() - startedAt, probes };
|
|
106
|
+
return { kind: 'settled', reason: probed.reason, waitedMs: now() - startedAt, probes, ...(probed.rowVerdict !== undefined ? { rowVerdict: probed.rowVerdict } : {}) };
|
|
107
107
|
}
|
|
108
108
|
lastReason = probed.reason;
|
|
109
109
|
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,29 @@ 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
|
+
}
|
|
24
34
|
export interface CcImportRedeemCounts {
|
|
25
35
|
persisted: number;
|
|
26
36
|
deduped: number;
|
|
37
|
+
indeterminate: readonly CcImportRedeemIndeterminate[];
|
|
27
38
|
}
|
|
28
|
-
export
|
|
39
|
+
export type CcImportReceiptOutcome = 'persisted' | 'no-op' | 'indeterminate';
|
|
40
|
+
export declare function readCcImportRedeemCounts(body: unknown): CcImportRedeemCounts | null;
|
|
29
41
|
export type RulesFailure = {
|
|
30
42
|
kind: 'lane-unavailable';
|
|
31
43
|
message: string;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { displayUntrusted, quoteUntrustedForSentence } from '../displayUntrusted.js';
|
|
2
2
|
import { engineCapTrue } from '../engineCapsCache.js';
|
|
3
3
|
import { CORE_RULE_BEHAVIORS } from '../generated/engineFactTables.js';
|
|
4
|
-
import { wireFailureShapeOf } from '../wireFailureShape.js';
|
|
4
|
+
import { slotReadOf, wireFailureShapeOf } from '../wireFailureShape.js';
|
|
5
5
|
import { CAPABILITY_OPERATOR_PRINCIPALS_REQUIRED } from '../engineErrorCodes.js';
|
|
6
6
|
import { hostLog } from '../host.js';
|
|
7
7
|
import { msg, renderMessage } from '../i18n/translate.js';
|
|
8
|
-
import { guardedRead, readOwnDataValue } from '../ownKey.js';
|
|
8
|
+
import { appendOwnElement, guardedRead, readOwnDataValue } from '../ownKey.js';
|
|
9
9
|
import { observedPermissionRulesWrite, permissionRulesWriteAvailable } from '../permissionRulesWriteCapability.js';
|
|
10
10
|
import { isToolApprovalRespondReplayed, readToolApprovalRespondAck } from './toolApprovalWire.js';
|
|
11
11
|
export function persistedRulesLaneAvailable(baseUrl) {
|
|
@@ -17,27 +17,109 @@ export function persistedRulesGovernanceAvailable(baseUrl) {
|
|
|
17
17
|
export function persistedRulesWriteAvailable(baseUrl) {
|
|
18
18
|
return permissionRulesWriteAvailable(observedPermissionRulesWrite(baseUrl ?? ''));
|
|
19
19
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
20
|
+
const NO_INDETERMINATE = Object.freeze([]);
|
|
21
|
+
const INDETERMINATE_DETAIL_SHOWN_MAX = 400;
|
|
22
|
+
function ownArrayItems(v) {
|
|
23
|
+
try {
|
|
24
|
+
if (typeof v !== 'object' || v === null || !Array.isArray(v))
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
const len = slotReadOf(v, 'length');
|
|
31
|
+
if (len.presence !== 'present' || typeof len.value !== 'number' || !Number.isSafeInteger(len.value) || len.value < 0)
|
|
32
|
+
return undefined;
|
|
33
|
+
const out = [];
|
|
34
|
+
for (let i = 0; i < len.value; i++) {
|
|
35
|
+
const item = slotReadOf(v, String(i));
|
|
36
|
+
if (item.presence !== 'present')
|
|
37
|
+
return undefined;
|
|
38
|
+
appendOwnElement(out, item.value);
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
function ownNonEmpty(o, key) {
|
|
43
|
+
const r = slotReadOf(o, key);
|
|
44
|
+
return r.presence === 'present' && typeof r.value === 'string' && r.value !== '' ? r.value : undefined;
|
|
45
|
+
}
|
|
46
|
+
function indeterminateRowOf(row) {
|
|
47
|
+
const k = slotReadOf(row, 'key');
|
|
48
|
+
const keyObj = k.presence === 'present' && typeof k.value === 'object' && k.value !== null ? k.value : undefined;
|
|
49
|
+
const behavior = keyObj === undefined ? undefined : ownNonEmpty(keyObj, 'behavior');
|
|
50
|
+
const rule = keyObj === undefined ? undefined : ownNonEmpty(keyObj, 'rule');
|
|
51
|
+
const scope = keyObj === undefined ? undefined : ownNonEmpty(keyObj, 'scope');
|
|
52
|
+
const reason = ownNonEmpty(row, 'reason');
|
|
53
|
+
const d = slotReadOf(row, 'detail');
|
|
54
|
+
const shown = d.presence === 'present' && typeof d.value === 'string' ? displayUntrusted(d.value, { max: INDETERMINATE_DETAIL_SHOWN_MAX }) : undefined;
|
|
55
|
+
return Object.freeze({
|
|
56
|
+
...(behavior !== undefined && rule !== undefined && scope !== undefined ? { key: Object.freeze({ behavior, rule, scope }) } : {}),
|
|
57
|
+
...(reason !== undefined ? { reason } : {}),
|
|
58
|
+
...(shown !== undefined && shown.trim() !== '' ? { detail: shown } : {}),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
const RECEIPT_ROW_ARMS = {
|
|
62
|
+
persisted: (acc) => {
|
|
63
|
+
acc.persisted += 1;
|
|
64
|
+
},
|
|
65
|
+
'no-op': (acc) => {
|
|
66
|
+
acc.deduped += 1;
|
|
67
|
+
},
|
|
68
|
+
indeterminate: (acc, row) => {
|
|
69
|
+
appendOwnElement(acc.indeterminate, indeterminateRowOf(row));
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
function receiptCountsOf(rows) {
|
|
73
|
+
const acc = { persisted: 0, deduped: 0, indeterminate: [] };
|
|
74
|
+
for (const row of rows) {
|
|
75
|
+
if (typeof row !== 'object' || row === null)
|
|
76
|
+
return null;
|
|
77
|
+
const o = slotReadOf(row, 'outcome');
|
|
78
|
+
const word = o.presence === 'present' ? o.value : undefined;
|
|
79
|
+
if (typeof word !== 'string' || !Object.hasOwn(RECEIPT_ROW_ARMS, word))
|
|
80
|
+
return null;
|
|
81
|
+
RECEIPT_ROW_ARMS[word](acc, row);
|
|
82
|
+
}
|
|
83
|
+
return { persisted: acc.persisted, deduped: acc.deduped, indeterminate: acc.indeterminate.length === 0 ? NO_INDETERMINATE : Object.freeze(acc.indeterminate) };
|
|
84
|
+
}
|
|
85
|
+
function legacyEnvelopeCountsOf(result) {
|
|
86
|
+
if (typeof result !== 'object' || result === null)
|
|
87
|
+
return null;
|
|
88
|
+
const m = slotReadOf(result, 'members');
|
|
89
|
+
const rows = m.presence === 'present' ? ownArrayItems(m.value) : undefined;
|
|
90
|
+
if (rows === undefined)
|
|
91
|
+
return null;
|
|
92
|
+
let persisted = 0;
|
|
93
|
+
let deduped = 0;
|
|
94
|
+
for (const row of rows) {
|
|
95
|
+
if (typeof row !== 'object' || row === null)
|
|
96
|
+
continue;
|
|
97
|
+
const st = slotReadOf(row, 'status');
|
|
98
|
+
const status = st.presence === 'present' ? st.value : undefined;
|
|
99
|
+
if (status === 'persisted')
|
|
100
|
+
persisted += 1;
|
|
101
|
+
else if (status === 'deduped')
|
|
102
|
+
deduped += 1;
|
|
103
|
+
}
|
|
104
|
+
return { persisted, deduped, indeterminate: NO_INDETERMINATE };
|
|
105
|
+
}
|
|
106
|
+
export function readCcImportRedeemCounts(body) {
|
|
107
|
+
try {
|
|
108
|
+
if (typeof body !== 'object' || body === null)
|
|
109
|
+
return null;
|
|
110
|
+
const top = slotReadOf(body, 'members');
|
|
111
|
+
if (top.presence !== 'absent') {
|
|
112
|
+
if (top.presence !== 'present')
|
|
113
|
+
return null;
|
|
114
|
+
const rows = ownArrayItems(top.value);
|
|
115
|
+
return rows === undefined ? null : receiptCountsOf(rows);
|
|
31
116
|
}
|
|
32
|
-
|
|
117
|
+
const legacy = slotReadOf(body, 'result');
|
|
118
|
+
return legacy.presence === 'present' ? legacyEnvelopeCountsOf(legacy.value) : null;
|
|
33
119
|
}
|
|
34
|
-
|
|
35
|
-
return
|
|
36
|
-
persisted: Array.isArray(o.persisted) ? o.persisted.length : 0,
|
|
37
|
-
deduped: Array.isArray(o.deduped) ? o.deduped.length : 0,
|
|
38
|
-
};
|
|
120
|
+
catch {
|
|
121
|
+
return null;
|
|
39
122
|
}
|
|
40
|
-
return null;
|
|
41
123
|
}
|
|
42
124
|
export function classifyRulesFailure(e, context) {
|
|
43
125
|
const { status, errorCode, message, retryAfterSec, committed } = wireFailureShapeOf(e);
|
|
@@ -677,6 +677,8 @@ function planReviewSettledVerdict(r) {
|
|
|
677
677
|
return status !== 'completed' && !gateMoved ? 'still_parked' : 'unconfirmed';
|
|
678
678
|
if (status === 'completed')
|
|
679
679
|
return 'took_effect';
|
|
680
|
+
if (status === 'running' && r.decision === 'reject')
|
|
681
|
+
return 'took_effect';
|
|
680
682
|
const newGateHasCard = () => {
|
|
681
683
|
const leg = durableDecideLegOf({ gateKind: r.gateKindAfter ?? null });
|
|
682
684
|
return leg === 'approval' || leg === 'plan_review';
|
|
@@ -874,7 +876,7 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
874
876
|
const bodyStatus = bodyWordOf(body, 'status');
|
|
875
877
|
const effective = postStatus ?? bodyStatus;
|
|
876
878
|
statusSeen = effective;
|
|
877
|
-
effect = planReviewSettledVerdict({ postStatus, bodyStatus, reparked: bodySaysReparked(body), gateBefore, gateAfter, gateKindAfter });
|
|
879
|
+
effect = planReviewSettledVerdict({ decision, postStatus, bodyStatus, reparked: bodySaysReparked(body), gateBefore, gateAfter, gateKindAfter });
|
|
878
880
|
const code = bodyWordOf(body, 'errorCode');
|
|
879
881
|
if (effect === 'still_parked' && postStatus === 'needs_review' && !bodySaysReparked(body)) {
|
|
880
882
|
outcome =
|
|
@@ -892,7 +894,7 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
892
894
|
}
|
|
893
895
|
else if (effect === 'took_effect') {
|
|
894
896
|
outcome =
|
|
895
|
-
`The plan was ${decision === 'approve' ? 'approved and the parked task resumed to completion' : 'rejected (plan discarded)'} — final status: ${effective} ` +
|
|
897
|
+
`The plan was ${decision === 'approve' ? 'approved and the parked task resumed to completion' : 'rejected (plan discarded)'} — ${effective === 'completed' ? 'final status' : 'status'}: ${effective} ` +
|
|
896
898
|
`(${postStatus !== undefined ? 'post-decide re-checked: task left the review gate' : "from the decision's own reply; the post-decide re-check was unavailable"}).`;
|
|
897
899
|
}
|
|
898
900
|
else if (effective === 'needs_review' && (gateBefore === undefined || gateAfter === undefined || postStatus !== 'needs_review')) {
|
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
export type SessionAllowLeg = 'frame' | 'v1' | 'generic';
|
|
2
2
|
export declare function sessionAllowCarried(leg: SessionAllowLeg, approvalId: unknown): boolean;
|
|
3
|
+
export type SessionAllowWhy = 'given' | 'route_cannot_carry' | 'evidence_unreadable' | 'card_unreadable';
|
|
4
|
+
export type CardUnreadableEvidence = readonly string[] | 'absent' | 'unreadable';
|
|
5
|
+
export declare function readCardUnreadableEvidence(card: object): CardUnreadableEvidence;
|
|
6
|
+
export declare function sessionAllowCardReadable(card: object | null | undefined): Exclude<SessionAllowWhy, 'route_cannot_carry'>;
|
|
7
|
+
export declare function sessionAllowEligible(card: object | null | undefined): SessionAllowWhy;
|
|
@@ -5,3 +5,45 @@ export function sessionAllowCarried(leg, approvalId) {
|
|
|
5
5
|
return typeof approvalId === 'string' && approvalId !== '';
|
|
6
6
|
return false;
|
|
7
7
|
}
|
|
8
|
+
export function readCardUnreadableEvidence(card) {
|
|
9
|
+
try {
|
|
10
|
+
if (!('unreadableEvidence' in card))
|
|
11
|
+
return 'absent';
|
|
12
|
+
const d = Object.getOwnPropertyDescriptor(card, 'unreadableEvidence');
|
|
13
|
+
const list = d !== undefined && Object.hasOwn(d, 'value') ? d.value : undefined;
|
|
14
|
+
return Array.isArray(list) && list.every((k) => typeof k === 'string') ? list : 'unreadable';
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return 'unreadable';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function sessionAllowCardReadable(card) {
|
|
21
|
+
if (typeof card !== 'object' || card === null)
|
|
22
|
+
return 'card_unreadable';
|
|
23
|
+
try {
|
|
24
|
+
if ('cardPayloadUnreadable' in card)
|
|
25
|
+
return 'card_unreadable';
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return 'card_unreadable';
|
|
29
|
+
}
|
|
30
|
+
const evidence = readCardUnreadableEvidence(card);
|
|
31
|
+
if (evidence === 'unreadable')
|
|
32
|
+
return 'card_unreadable';
|
|
33
|
+
return evidence !== 'absent' && evidence.length > 0 ? 'evidence_unreadable' : 'given';
|
|
34
|
+
}
|
|
35
|
+
export function sessionAllowEligible(card) {
|
|
36
|
+
if (typeof card !== 'object' || card === null)
|
|
37
|
+
return 'card_unreadable';
|
|
38
|
+
let carried;
|
|
39
|
+
try {
|
|
40
|
+
const d = Object.getOwnPropertyDescriptor(card, 'allowSessionCarried');
|
|
41
|
+
carried = d !== undefined && Object.hasOwn(d, 'value') ? d.value : undefined;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return 'card_unreadable';
|
|
45
|
+
}
|
|
46
|
+
if (carried !== true)
|
|
47
|
+
return 'route_cannot_carry';
|
|
48
|
+
return sessionAllowCardReadable(card);
|
|
49
|
+
}
|
|
@@ -4,6 +4,7 @@ import { readApprovalCardWord, readRuleOffers } from './toolApprovalWire.js';
|
|
|
4
4
|
import { CORE_SKILL_TOOL_NAME } from '../generated/toolNameTables.js';
|
|
5
5
|
import { toolPermissionRequestIdDomain } from '../seatContract.js';
|
|
6
6
|
import { permissionsDeclarationReport } from '../permissionsDeclaration.js';
|
|
7
|
+
import { readCardUnreadableEvidence, sessionAllowCardReadable, sessionAllowEligible } from './sessionAllowRoute.js';
|
|
7
8
|
const UNREADABLE = Symbol('unreadable');
|
|
8
9
|
function ownData(v, key) {
|
|
9
10
|
try {
|
|
@@ -90,18 +91,17 @@ function enginePremiseOf(card) {
|
|
|
90
91
|
return 'card_unreadable';
|
|
91
92
|
if (refused.inCard)
|
|
92
93
|
return refused.value === true ? 'remember_refused' : 'remember_unreadable';
|
|
93
|
-
const
|
|
94
|
-
if (
|
|
94
|
+
const list = readCardUnreadableEvidence(card);
|
|
95
|
+
if (list === 'unreadable')
|
|
95
96
|
return 'card_unreadable';
|
|
96
|
-
if (
|
|
97
|
+
if (list === 'absent')
|
|
97
98
|
return { evidenceUnreadable: false };
|
|
98
|
-
const list = ownData(card, 'unreadableEvidence');
|
|
99
|
-
if (!Array.isArray(list) || !list.every((k) => typeof k === 'string'))
|
|
100
|
-
return 'card_unreadable';
|
|
101
99
|
if (list.includes('rememberRefused'))
|
|
102
100
|
return 'remember_unreadable';
|
|
103
101
|
return { evidenceUnreadable: list.length > 0 };
|
|
104
102
|
}
|
|
103
|
+
const _sessionWhyPin = true;
|
|
104
|
+
void _sessionWhyPin;
|
|
105
105
|
export function standingRowsOf(card, host) {
|
|
106
106
|
if (typeof card !== 'object' || card === null)
|
|
107
107
|
return allClosed('card_unreadable');
|
|
@@ -110,11 +110,11 @@ export function standingRowsOf(card, host) {
|
|
|
110
110
|
if (typeof premise === 'string')
|
|
111
111
|
return allClosed(premise);
|
|
112
112
|
const evidenceUnreadable = premise.evidenceUnreadable;
|
|
113
|
-
const
|
|
114
|
-
const
|
|
113
|
+
const sessionWhy = sessionAllowEligible(card);
|
|
114
|
+
const sessionRow = sessionWhy === 'given';
|
|
115
115
|
let deploymentList;
|
|
116
116
|
if (!sessionRow) {
|
|
117
|
-
deploymentList =
|
|
117
|
+
deploymentList = sessionWhy;
|
|
118
118
|
}
|
|
119
119
|
else {
|
|
120
120
|
const pair = readAskClassPairState(card);
|
|
@@ -143,7 +143,7 @@ export function seatSessionRowOf(card, requestId) {
|
|
|
143
143
|
const domain = toolPermissionRequestIdDomain(requestId);
|
|
144
144
|
if (domain !== 'live:' && domain !== 'durable:')
|
|
145
145
|
return false;
|
|
146
|
-
return typeof enginePremiseOf(card) !== 'string';
|
|
146
|
+
return typeof enginePremiseOf(card) !== 'string' && sessionAllowCardReadable(card) === 'given';
|
|
147
147
|
}
|
|
148
148
|
catch {
|
|
149
149
|
return false;
|
|
@@ -15,7 +15,7 @@ import { readEngineActiveBgTasks } from '../fleet/fleetLedger.js';
|
|
|
15
15
|
import { CORE_SKILL_TOOL_NAME } from '../generated/toolNameTables.js';
|
|
16
16
|
import { CORE_ALLOW_ENTRY_INERT_REASONS, CORE_PERSISTED_RULE_MATCHES } from '../generated/engineFactTables.js';
|
|
17
17
|
import { msg, renderMessage } from '../i18n/translate.js';
|
|
18
|
-
import { sessionAllowCarried } from './sessionAllowRoute.js';
|
|
18
|
+
import { sessionAllowCarried, sessionAllowEligible } from './sessionAllowRoute.js';
|
|
19
19
|
import { INTERRUPT_DENY_REASON, observeCancelByDeny, surfaceApprovalParking, surfaceRememberNotApplied, surfaceEditNotForwarded, surfaceEditRefusedOnBlindAsk, surfaceRuleArmNotSent, surfaceRuleArmRejected } from './hitlHostSurface.js';
|
|
20
20
|
import { approvalCallKey, liveFrameCallKey } from './gateIdentity.js';
|
|
21
21
|
import { displayUntrusted, quoteUntrustedForSentence } from '../displayUntrusted.js';
|
|
@@ -449,7 +449,7 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
|
|
|
449
449
|
closeDurableRead(readMark);
|
|
450
450
|
}
|
|
451
451
|
if (!found.ok)
|
|
452
|
-
return { kind: 'not_sent', lane: 'decide', cause: found.code ?? 'fetch_failed', ...(found.reason !== '' ? { detail: found.reason } : {}) };
|
|
452
|
+
return { kind: 'not_sent', lane: 'decide', cause: found.code ?? 'fetch_failed', ...(found.reason !== '' ? { detail: found.reason } : {}), ...(found.rowVerdict !== undefined ? { rowVerdict: found.rowVerdict } : {}) };
|
|
453
453
|
if (held !== undefined)
|
|
454
454
|
return joinDurableDecision(held, deps, signal);
|
|
455
455
|
const rowIdentity = parkRowHolderIdentity(found.pending, found.gatedCallId);
|
|
@@ -500,7 +500,7 @@ async function presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, foun
|
|
|
500
500
|
const durableRuleOffersAbsence = pending.ruleOffersAbsence;
|
|
501
501
|
hold.presented = true;
|
|
502
502
|
deps.onPresented?.();
|
|
503
|
-
const
|
|
503
|
+
const cardRequest = withUnreadableEvidence(pending, 'row', {
|
|
504
504
|
toolName,
|
|
505
505
|
args,
|
|
506
506
|
callKey,
|
|
@@ -533,7 +533,8 @@ async function presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, foun
|
|
|
533
533
|
allowSessionCarried: true,
|
|
534
534
|
grantBinding: readApprovalGrantBinding(pending),
|
|
535
535
|
parkRow: parkRowOf(taskId, pending, gatedCallId),
|
|
536
|
-
})
|
|
536
|
+
});
|
|
537
|
+
const card = await surfaceCallTrackedCard(gatedCallId, undefined, ledgerSlot, cardRequest);
|
|
537
538
|
hold.phase = 'respond';
|
|
538
539
|
if (rowArgsGap !== undefined && card.kind === 'allow' && card.updatedInput !== undefined) {
|
|
539
540
|
hostLog('error', `liveToolApprovalWire: approval card for ${gatedCallId ?? `task ${taskId}`} returned an edited approval, but the tool input is ${rowArgsGap === 'truncated' ? 'a size marker on the pending row' : 'missing from both the stream and the pending row'} — nothing sent (the park stays pending)`);
|
|
@@ -560,10 +561,15 @@ async function presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, foun
|
|
|
560
561
|
let rememberStop;
|
|
561
562
|
try {
|
|
562
563
|
let rememberRefused = false;
|
|
563
|
-
const
|
|
564
|
+
const sessionAllowWhy = sessionAllowEligible(cardRequest);
|
|
565
|
+
const sessionDowngraded = card.allowSession === true && sessionAllowWhy !== 'given';
|
|
566
|
+
if (sessionDowngraded)
|
|
567
|
+
hostLog('debug', `liveToolApprovalWire: ${gatedCallId ?? `task ${taskId}`} session-wide allow taken as allow-once — the card is not eligible (${sessionAllowWhy})`);
|
|
568
|
+
const answered = sessionDowngraded ? { ...card, allowSession: false } : card;
|
|
569
|
+
const approveBody = answerPayloadFor(answered, 'decide');
|
|
564
570
|
const { remember: _remember, ...plainApproveBody } = approveBody;
|
|
565
571
|
void _remember;
|
|
566
|
-
emitAnswerCommitted(callKey, 'decide',
|
|
572
|
+
emitAnswerCommitted(callKey, 'decide', answered, approveBody);
|
|
567
573
|
if (approveBody.remember === 'session') {
|
|
568
574
|
let sessionReceipt;
|
|
569
575
|
let sessionDecided = false;
|
|
@@ -591,7 +597,7 @@ async function presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, foun
|
|
|
591
597
|
}
|
|
592
598
|
const raw = await bridge.decideTool(plainApproveBody, gatedCallId, decideOpts, pending);
|
|
593
599
|
const receipt = readDecideReceipt(raw);
|
|
594
|
-
if (rememberRefused)
|
|
600
|
+
if (rememberRefused || sessionDowngraded)
|
|
595
601
|
surfaceRememberNotApplied();
|
|
596
602
|
return { kind: 'decided', ...at, decision: 'allow', ...(receipt !== undefined ? { receipt } : {}) };
|
|
597
603
|
}
|
|
@@ -2038,9 +2044,15 @@ async function presentToolApprovalFrameAndRespond(frame, carriers, approvalId, r
|
|
|
2038
2044
|
enterLiveRespondPhase(hold);
|
|
2039
2045
|
const noteCapable = lane?.approvalDecisionNoteCapable === true;
|
|
2040
2046
|
const denyReasonRead = card.kind === 'deny' ? ((r) => (typeof r === 'string' ? r : undefined))(card.reason) : undefined;
|
|
2047
|
+
const sessionAllowWhy = sessionAllowEligible(facts);
|
|
2048
|
+
const sessionDowngraded = card.kind === 'allow' && card.allowSession === true && sessionAllowWhy !== 'given';
|
|
2049
|
+
if (sessionDowngraded)
|
|
2050
|
+
hostLog('debug', `liveToolApprovalWire: ${approvalId} session-wide allow taken as allow-once — the card is not eligible (${sessionAllowWhy})`);
|
|
2041
2051
|
const answerSnapshot = card.kind === 'deny'
|
|
2042
2052
|
? ((by) => ({ kind: 'deny', ...(denyReasonRead !== undefined ? { reason: denyReasonRead } : {}), ...(by !== undefined ? { settledBy: by } : {}) }))(readApprovalDenySettledBy(card))
|
|
2043
|
-
: card
|
|
2053
|
+
: sessionDowngraded && card.kind === 'allow'
|
|
2054
|
+
? { ...card, allowSession: false }
|
|
2055
|
+
: card;
|
|
2044
2056
|
const basePayload = answerPayloadFor(answerSnapshot, 'respond', { approvalDecisionNoteCapable: noteCapable });
|
|
2045
2057
|
const decision = basePayload?.decision ?? 'deny';
|
|
2046
2058
|
if (card.kind === 'allow' && typeof card.note === 'string' && card.note.trim() !== '' && answerCarriage({ callKey: liveFrameCallKey(approvalId) }).allowNote === 'none') {
|
|
@@ -2273,6 +2285,8 @@ async function presentToolApprovalFrameAndRespond(frame, carriers, approvalId, r
|
|
|
2273
2285
|
logAfterDecided(`liveToolApprovalWire: ${approvalId} allow_session ack rememberApplied=false — grant not stored, surfacing honest notice`);
|
|
2274
2286
|
surfaceRememberNotApplied();
|
|
2275
2287
|
}
|
|
2288
|
+
if (replayed === undefined && sessionDowngraded)
|
|
2289
|
+
surfaceRememberNotApplied();
|
|
2276
2290
|
if (respondOpts.updatedInput !== undefined && applied?.updatedInputForwarded === false) {
|
|
2277
2291
|
logAfterDecided(`liveToolApprovalWire: ${approvalId} edited args were NOT forwarded (ack.updatedInputForwarded=false) — the tool runs on the ORIGINAL input`);
|
|
2278
2292
|
surfaceEditNotForwarded();
|
|
@@ -247,6 +247,10 @@ declare const EN: {
|
|
|
247
247
|
'approval.settle.cause.streamDisconnected': string;
|
|
248
248
|
'approval.settle.cause.undeliverable': string;
|
|
249
249
|
'approval.decide.requiresBinding': string;
|
|
250
|
+
'approval.decide.unboundWayOut': string;
|
|
251
|
+
'approval.rowUnresumable.fallback': string;
|
|
252
|
+
'approval.rowUnresumable.askNotSurfaced': string;
|
|
253
|
+
'approval.park.stillParked': string;
|
|
250
254
|
'approval.decision.grantChanged': string;
|
|
251
255
|
'approval.decision.settledElsewhereContrary.deniedFirst': string;
|
|
252
256
|
'approval.decision.settledElsewhereContrary.approvedFirst': string;
|
|
@@ -422,6 +426,7 @@ declare const EN: {
|
|
|
422
426
|
'session.selfHeal.row.askDecided.inferred': string;
|
|
423
427
|
'session.selfHeal.row.askDecided.releaseUnknown': string;
|
|
424
428
|
'session.selfHeal.row.askReopenFailed.withKeep': string;
|
|
429
|
+
'session.selfHeal.row.askReopenFailed.rowUnresumable': string;
|
|
425
430
|
'session.selfHeal.row.askReopenFailed.noKeep': string;
|
|
426
431
|
'session.selfHeal.row.planReopenFailed.dismissed': string;
|
|
427
432
|
'session.selfHeal.row.planReopenFailed.withKeep': string;
|
|
@@ -983,6 +988,7 @@ declare const EN: {
|
|
|
983
988
|
'notice.wireRefusal.hostPosture.both': string;
|
|
984
989
|
'notice.wireRefusal.hostPosture.unattributed': string;
|
|
985
990
|
'notice.wireRefusal.autonomyLane': string;
|
|
991
|
+
'notice.wireRefusal.sessionArchiveStamped': string;
|
|
986
992
|
'notice.wireRefusal.unsupportedRule': string;
|
|
987
993
|
'notice.wireRefusal.unsupportedRule.other': string;
|
|
988
994
|
'notice.wireRefusal.unsupportedRule.stored': string;
|