@sema-agent/client-core 0.92.0 → 0.93.1
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 +227 -0
- package/README.md +25 -16
- package/dist/adapt/arms.js +2 -0
- package/dist/adapt/toolCards.js +6 -4
- package/dist/adapt.d.ts +1 -1
- package/dist/adapt.js +1 -1
- package/dist/adapter/runStream.js +3 -1
- package/dist/agentsWireCaps.js +1 -1
- package/dist/approvalDecidedByCapability.d.ts +16 -0
- package/dist/approvalDecidedByCapability.js +47 -0
- package/dist/approvalsStreamLiveCapability.js +3 -3
- package/dist/askSurvivesStandingAnswer.d.ts +1 -1
- package/dist/centerWiringCapability.js +1 -1
- package/dist/cloudConfigWireCaps.js +5 -5
- package/dist/compensations.js +2 -2
- package/dist/decideReceipt.d.ts +2 -0
- package/dist/decideReceipt.js +34 -9
- package/dist/detachWire.d.ts +2 -14
- package/dist/detachWire.js +1 -47
- package/dist/deviceExecutorManagementCapability.d.ts +6 -2
- package/dist/deviceExecutorManagementCapability.js +61 -15
- package/dist/engineConfigDrift.d.ts +20 -0
- package/dist/engineConfigDrift.js +54 -0
- package/dist/engineErrorCodes.d.ts +3 -0
- package/dist/engineErrorCodes.js +12 -4
- package/dist/engineInlineTaskStats.d.ts +1 -0
- package/dist/engineNoticeCodes.d.ts +2 -2
- package/dist/engineWireFor.d.ts +22 -0
- package/dist/engineWireFor.js +154 -9
- package/dist/engineWireSdk.d.ts +0 -1
- package/dist/engineWireSdk.js +0 -3
- package/dist/executionLaneCapability.js +2 -2
- package/dist/fileHistoryCaptureCapability.d.ts +1 -1
- package/dist/fileHistoryCaptureCapability.js +1 -6
- package/dist/fileHistoryLegNotice.d.ts +1 -1
- package/dist/fileHistoryLegNotice.js +1 -6
- package/dist/gateVocabulary.d.ts +1 -5
- package/dist/gateVocabulary.js +4 -22
- package/dist/generated/engineFactTables.d.ts +4 -3
- package/dist/generated/engineFactTables.js +10 -0
- package/dist/generated/engineNoticeTables.d.ts +1 -1
- package/dist/generated/engineNoticeTables.js +1 -1
- package/dist/goalStopHook.d.ts +1 -8
- package/dist/goalStopHook.js +2 -58
- package/dist/headlessPermissionModeWire.d.ts +3 -2
- package/dist/headlessPermissionModeWire.js +46 -5
- package/dist/hitl/approvalResolution.d.ts +3 -2
- package/dist/hitl/approvalResolution.js +13 -0
- package/dist/hitl/askParkRowRouting.js +2 -2
- package/dist/hitl/frameRouter.js +7 -2
- package/dist/hitl/gateLedger.d.ts +1 -0
- package/dist/hitl/gateLedger.js +3 -0
- package/dist/hitl/hitlBridge.d.ts +1 -0
- package/dist/hitl/hitlBridge.js +6 -1
- package/dist/hitl/hitlHostSurface.d.ts +6 -2
- package/dist/hitl/hitlHostSurface.js +29 -3
- package/dist/hitl/parkResolver.js +22 -4
- package/dist/hitl/persistedRulesWire.js +29 -17
- package/dist/hitl/planReviewWire.d.ts +0 -2
- package/dist/hitl/planReviewWire.js +1 -18
- package/dist/hitl/sessionPolicyDeliverable.js +3 -3
- package/dist/hitl/sessionPolicyWire.d.ts +2 -0
- package/dist/hitl/sessionPolicyWire.js +7 -4
- package/dist/hitl/toolApprovalWire.d.ts +32 -7
- package/dist/hitl/toolApprovalWire.js +282 -59
- package/dist/hooksWireCaps.d.ts +1 -1
- package/dist/hooksWireCaps.js +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.js +6 -2
- package/dist/inheritEnvWire.d.ts +1 -4
- package/dist/inheritEnvWire.js +0 -18
- package/dist/limitsWire.d.ts +0 -6
- package/dist/limitsWire.js +0 -38
- package/dist/mcpProbeCapability.js +1 -1
- package/dist/mcpProbeWire.js +10 -12
- package/dist/mcpReconnect.d.ts +5 -0
- package/dist/mcpReconnect.js +28 -0
- package/dist/memoryComplianceCapability.js +2 -2
- package/dist/memoryOriginCapability.js +2 -2
- package/dist/memoryVerbsWire.js +14 -13
- package/dist/ownKey.d.ts +2 -0
- package/dist/ownKey.js +15 -0
- package/dist/peerLaneCapability.js +2 -2
- package/dist/permissionModesCapability.d.ts +16 -0
- package/dist/permissionModesCapability.js +71 -0
- package/dist/permissionRuleIssue.js +1 -1
- package/dist/permissionRulesWriteCapability.js +2 -2
- package/dist/permissionWireCaps.d.ts +2 -0
- package/dist/permissionWireCaps.js +8 -1
- package/dist/permissionsAllowGrantCapability.js +7 -2
- package/dist/postureKnob.js +1 -1
- package/dist/protocolSegment.d.ts +2 -0
- package/dist/protocolSegment.js +3 -0
- package/dist/readFacePosture.js +1 -1
- package/dist/resumeRefusalCopy.d.ts +1 -0
- package/dist/resumeRefusalCopy.js +26 -3
- package/dist/retryStatus.js +2 -1
- package/dist/rewindArchiveCapability.js +2 -2
- package/dist/runTerminal.d.ts +16 -0
- package/dist/runTerminal.js +32 -3
- package/dist/seam.d.ts +1 -0
- package/dist/sessionPolicyCapability.js +1 -1
- package/dist/sessionSyncFileHistoryCapability.js +2 -2
- package/dist/skillToolListsCapability.js +1 -1
- package/dist/sqlEngineCapability.js +1 -1
- package/dist/steerPriorityCapability.js +1 -1
- package/dist/storePostureCapability.js +2 -2
- package/dist/supportFloor.d.ts +1 -0
- package/dist/supportFloor.js +13 -3
- package/dist/taskRequestWords.d.ts +4 -2
- package/dist/taskRequestWords.js +1 -1
- package/dist/toolHistoryBadTurn.js +1 -2
- package/dist/toolResult.d.ts +2 -1
- package/dist/toolResult.js +33 -3
- package/dist/userInvokedSkillsCapability.js +1 -1
- package/dist/webSearchBackendCapability.js +2 -2
- package/dist/webSearchWireCaps.d.ts +1 -1
- package/dist/webSearchWireCaps.js +40 -2
- package/dist/wireFailureShape.js +2 -2
- package/dist/wireRefusalCopy.d.ts +9 -3
- package/dist/wireRefusalCopy.js +41 -4
- package/dist/workflowsJournalCapability.js +1 -1
- package/dist/writeProtectionCapability.js +1 -1
- package/docs/INTEGRATION-CLIENTS.md +999 -15
- package/package.json +2 -2
package/dist/goalStopHook.js
CHANGED
|
@@ -1,80 +1,24 @@
|
|
|
1
1
|
import { hostEnv } from './hostEnv.js';
|
|
2
2
|
import { envFlagOff } from './envFlag.js';
|
|
3
|
-
import { engineCapString } from './engineCapsCache.js';
|
|
4
3
|
let wireSessionStopHookPrompt = null;
|
|
5
|
-
let wireSessionStopHookCcSemantics = false;
|
|
6
4
|
export const GOAL_STOP_HOOK_WIRE_ENV = 'SEMA_GOAL_STOP_HOOK_WIRE';
|
|
7
|
-
export const CC_STOP_SEMANTICS_MIN_SERVER = '1.279.3';
|
|
8
|
-
function parseSemverCore(v) {
|
|
9
|
-
const m = /^(\d+)\.(\d+)\.(\d+)$/.exec(v.trim());
|
|
10
|
-
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
11
|
-
}
|
|
12
|
-
export function ccStopSemanticsFromVersion(version) {
|
|
13
|
-
if (typeof version !== 'string' || version.length === 0)
|
|
14
|
-
return false;
|
|
15
|
-
const got = parseSemverCore(version);
|
|
16
|
-
const min = parseSemverCore(CC_STOP_SEMANTICS_MIN_SERVER);
|
|
17
|
-
if (got === null || min === null)
|
|
18
|
-
return false;
|
|
19
|
-
const [gotMajor, gotMinor, gotPatch] = got;
|
|
20
|
-
const [minMajor, minMinor, minPatch] = min;
|
|
21
|
-
if (gotMajor !== minMajor)
|
|
22
|
-
return gotMajor > minMajor;
|
|
23
|
-
if (gotMinor !== minMinor)
|
|
24
|
-
return gotMinor > minMinor;
|
|
25
|
-
if (gotPatch !== minPatch)
|
|
26
|
-
return gotPatch > minPatch;
|
|
27
|
-
return true;
|
|
28
|
-
}
|
|
29
|
-
export function engineCcStopSemantics(env = hostEnv()) {
|
|
30
|
-
const base = env.SEMA_LIVE_BASEURL;
|
|
31
|
-
if (!base)
|
|
32
|
-
return false;
|
|
33
|
-
return ccStopSemanticsFromVersion(engineCapString(base, 'version'));
|
|
34
|
-
}
|
|
35
5
|
export function isGoalStopHookWireArmed(env = hostEnv()) {
|
|
36
6
|
return !envFlagOff(env[GOAL_STOP_HOOK_WIRE_ENV]);
|
|
37
7
|
}
|
|
38
|
-
export function setWireSessionStopHook(prompt
|
|
8
|
+
export function setWireSessionStopHook(prompt) {
|
|
39
9
|
wireSessionStopHookPrompt = prompt && prompt.length > 0 ? prompt : null;
|
|
40
|
-
wireSessionStopHookCcSemantics = wireSessionStopHookPrompt === null ? false : opts?.ccSemantics === true;
|
|
41
10
|
}
|
|
42
11
|
export function getWireSessionStopHook() {
|
|
43
12
|
return wireSessionStopHookPrompt;
|
|
44
13
|
}
|
|
45
|
-
export function getWireSessionStopHookCcSemantics() {
|
|
46
|
-
return wireSessionStopHookCcSemantics;
|
|
47
|
-
}
|
|
48
|
-
export function buildGoalStopHookPrompt(condition) {
|
|
49
|
-
const reason = `[session goal] Before you stop: verify that this goal actually holds — ${condition}. ` +
|
|
50
|
-
`If it does hold, say so plainly and finish. If it does not, keep working toward it and do not ` +
|
|
51
|
-
`pause to ask the user what to do.`;
|
|
52
|
-
return ('You are the Stop gate for an autonomous coding agent session. The user set this session goal, ' +
|
|
53
|
-
'which the agent is supposed to satisfy before it stops:\n' +
|
|
54
|
-
`<goal>\n${condition}\n</goal>\n` +
|
|
55
|
-
'Stop-hook input JSON: $ARGUMENTS\n' +
|
|
56
|
-
'Reply with RAW JSON only — no prose, no explanation, no code fences.\n' +
|
|
57
|
-
'If the input JSON has "stop_hook_active" set to true, this gate already fired once for this turn: ' +
|
|
58
|
-
'reply with exactly {"continue":true} so the agent may stop.\n' +
|
|
59
|
-
'Otherwise reply with exactly:\n' +
|
|
60
|
-
JSON.stringify({ decision: 'block', reason }));
|
|
61
|
-
}
|
|
62
14
|
export function goalStopHookMatcher(managedOnly) {
|
|
63
15
|
if (wireSessionStopHookPrompt === null || managedOnly || !isGoalStopHookWireArmed())
|
|
64
16
|
return null;
|
|
65
17
|
return {
|
|
66
18
|
matcher: '',
|
|
67
|
-
hooks: [
|
|
68
|
-
{
|
|
69
|
-
type: 'prompt',
|
|
70
|
-
prompt: wireSessionStopHookCcSemantics
|
|
71
|
-
? wireSessionStopHookPrompt
|
|
72
|
-
: buildGoalStopHookPrompt(wireSessionStopHookPrompt),
|
|
73
|
-
},
|
|
74
|
-
],
|
|
19
|
+
hooks: [{ type: 'prompt', prompt: wireSessionStopHookPrompt }],
|
|
75
20
|
};
|
|
76
21
|
}
|
|
77
22
|
export function __resetGoalStopHookForTests() {
|
|
78
23
|
wireSessionStopHookPrompt = null;
|
|
79
|
-
wireSessionStopHookCcSemantics = false;
|
|
80
24
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { type PermissionMode, type PermissionModeField } from './permissionWireCaps.js';
|
|
2
|
+
import { type PermissionModesReading } from './permissionModesCapability.js';
|
|
2
3
|
import { type EnvLike } from './hostEnv.js';
|
|
3
4
|
export declare const HEADLESS_PERMISSION_MODE_ENV = "SEMA_HEADLESS_PERMISSION_MODE";
|
|
4
5
|
export declare const HEADLESS_PERMISSION_WIRE_MODES: readonly PermissionMode[];
|
|
5
|
-
export declare function normalizeShellPermissionModeForWire(mode: string | undefined): string | undefined;
|
|
6
|
+
export declare function normalizeShellPermissionModeForWire(mode: string | undefined, permissionModes?: PermissionModesReading): string | undefined;
|
|
6
7
|
export type HeadlessPermissionResolution = {
|
|
7
8
|
fields: PermissionModeField;
|
|
8
9
|
source: 'flag' | 'env' | 'default' | 'absent';
|
|
9
10
|
warning?: string;
|
|
10
11
|
};
|
|
11
12
|
export declare function permissionModeFromArgv(argv: readonly string[]): string | undefined;
|
|
12
|
-
export declare function resolveHeadlessPermissionMode(argv: readonly string[], env?: EnvLike): HeadlessPermissionResolution;
|
|
13
|
+
export declare function resolveHeadlessPermissionMode(argv: readonly string[], env?: EnvLike, permissionModes?: PermissionModesReading): HeadlessPermissionResolution;
|
|
13
14
|
export declare function _resetHeadlessPermissionModeWireForTest(): void;
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import { permissionModeField } from './permissionWireCaps.js';
|
|
2
|
+
import { permissionModeAdmitted } from './permissionModesCapability.js';
|
|
2
3
|
import { hostEnv } from './hostEnv.js';
|
|
3
4
|
import { lastFlagValue } from './argvFlagValue.js';
|
|
4
5
|
import { PERMISSION_MODE_WORDS } from './taskRequestWords.js';
|
|
6
|
+
import { readOwnDataValue } from './ownKey.js';
|
|
5
7
|
export const HEADLESS_PERMISSION_MODE_ENV = 'SEMA_HEADLESS_PERMISSION_MODE';
|
|
6
8
|
export const HEADLESS_PERMISSION_WIRE_MODES = [...PERMISSION_MODE_WORDS];
|
|
7
9
|
const SHELL_INTERNAL_MODE_TO_WIRE = new Map([
|
|
8
10
|
['manual', 'default'],
|
|
9
11
|
['dontAsk', 'default'],
|
|
10
12
|
]);
|
|
11
|
-
export function normalizeShellPermissionModeForWire(mode) {
|
|
13
|
+
export function normalizeShellPermissionModeForWire(mode, permissionModes) {
|
|
12
14
|
if (mode === undefined)
|
|
13
15
|
return undefined;
|
|
16
|
+
if (mode === 'dontAsk' && permissionModeAdmitted(permissionModes, mode))
|
|
17
|
+
return mode;
|
|
14
18
|
return SHELL_INTERNAL_MODE_TO_WIRE.get(mode) ?? mode;
|
|
15
19
|
}
|
|
16
20
|
export function permissionModeFromArgv(argv) {
|
|
@@ -27,19 +31,32 @@ export function permissionModeFromArgv(argv) {
|
|
|
27
31
|
return undefined;
|
|
28
32
|
}
|
|
29
33
|
let warnedInvalidEnv = false;
|
|
30
|
-
export function resolveHeadlessPermissionMode(argv, env = hostEnv()) {
|
|
34
|
+
export function resolveHeadlessPermissionMode(argv, env = hostEnv(), permissionModes) {
|
|
31
35
|
const flagMode = permissionModeFromArgv(argv);
|
|
32
36
|
if (flagMode !== undefined) {
|
|
33
|
-
const
|
|
34
|
-
|
|
37
|
+
const snap = flagMode === 'dontAsk' ? dontAskSnapshot(permissionModes) : undefined;
|
|
38
|
+
const normalized = normalizeShellPermissionModeForWire(flagMode, snap);
|
|
39
|
+
return { fields: permissionModeField(normalized, { stampDefault: true, permissionModes: snap }), source: 'flag' };
|
|
35
40
|
}
|
|
36
41
|
const raw = env[HEADLESS_PERMISSION_MODE_ENV]?.trim();
|
|
37
42
|
if (raw === undefined || raw === '') {
|
|
38
43
|
return { fields: permissionModeField('default', { stampDefault: true }), source: 'default' };
|
|
39
44
|
}
|
|
40
45
|
if (HEADLESS_PERMISSION_WIRE_MODES.includes(raw)) {
|
|
46
|
+
const snap = raw === 'dontAsk' ? dontAskSnapshot(permissionModes) : undefined;
|
|
47
|
+
if (raw === 'dontAsk' && snap === undefined) {
|
|
48
|
+
const downgraded = {
|
|
49
|
+
fields: permissionModeField('default', { stampDefault: true }),
|
|
50
|
+
source: 'env',
|
|
51
|
+
};
|
|
52
|
+
if (!warnedInvalidEnv) {
|
|
53
|
+
warnedInvalidEnv = true;
|
|
54
|
+
downgraded.warning = `sema: ${HEADLESS_PERMISSION_MODE_ENV}="dontAsk" is only sent once this client knows the engine accepts that mode, and ${dontAskWithheldClause(permissionModes)} — this run uses "default"`;
|
|
55
|
+
}
|
|
56
|
+
return downgraded;
|
|
57
|
+
}
|
|
41
58
|
return {
|
|
42
|
-
fields: permissionModeField(raw, { stampDefault: true }),
|
|
59
|
+
fields: permissionModeField(raw, { stampDefault: true, permissionModes: snap }),
|
|
43
60
|
source: 'env',
|
|
44
61
|
};
|
|
45
62
|
}
|
|
@@ -53,6 +70,30 @@ export function resolveHeadlessPermissionMode(argv, env = hostEnv()) {
|
|
|
53
70
|
}
|
|
54
71
|
return resolution;
|
|
55
72
|
}
|
|
73
|
+
function dontAskSnapshot(reading) {
|
|
74
|
+
return permissionModeAdmitted(reading, 'dontAsk') ? { kind: 'present', modes: Object.freeze(['dontAsk']) } : undefined;
|
|
75
|
+
}
|
|
76
|
+
function dontAskWithheldClause(reading) {
|
|
77
|
+
if (reading === undefined)
|
|
78
|
+
return 'no such reading was supplied for this run';
|
|
79
|
+
let kind;
|
|
80
|
+
try {
|
|
81
|
+
kind = typeof reading === 'object' && reading !== null ? readOwnDataValue(reading, 'kind') : undefined;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
kind = undefined;
|
|
85
|
+
}
|
|
86
|
+
switch (kind) {
|
|
87
|
+
case 'unobserved':
|
|
88
|
+
return "this engine's capabilities have not been read yet (wait for them before the first request)";
|
|
89
|
+
case 'not_reported':
|
|
90
|
+
return 'this engine does not report which modes it accepts';
|
|
91
|
+
case 'present':
|
|
92
|
+
return "this engine's list of accepted modes does not include it";
|
|
93
|
+
default:
|
|
94
|
+
return 'the reading supplied for it could not be read';
|
|
95
|
+
}
|
|
96
|
+
}
|
|
56
97
|
export function _resetHeadlessPermissionModeWireForTest() {
|
|
57
98
|
warnedInvalidEnv = false;
|
|
58
99
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { DecideReceiptView } from '../decideReceipt.js';
|
|
2
2
|
import type { HitlSafetyCode } from './hitlBridge.js';
|
|
3
|
-
import type { FsApprovalOutcome, ToolApprovalFrameOutcome, ToolApprovalRespondAckView, ToolApprovalRespondDecision } from './toolApprovalWire.js';
|
|
3
|
+
import type { FsApprovalOutcome, ToolApprovalFrameOutcome, ToolApprovalRespondAckView, ToolApprovalRespondDecision, ToolApprovalRespondParkingView } from './toolApprovalWire.js';
|
|
4
4
|
export declare const APPROVAL_NOT_SENT_CAUSES: readonly ["retracted", "edit_refused", "no_pending", "fetch_failed", "no_input", "card_unavailable"];
|
|
5
5
|
export type ApprovalNotSentCause = (typeof APPROVAL_NOT_SENT_CAUSES)[number];
|
|
6
|
-
export declare const APPROVAL_UNSETTLED_CAUSES: readonly ["engine_refused", "decide_failed", "transport_exhausted", "safety_stop", "respond_failed", "interrupted", "orchestration", "unreadable"];
|
|
6
|
+
export declare const APPROVAL_UNSETTLED_CAUSES: readonly ["engine_refused", "decide_failed", "transport_exhausted", "safety_stop", "respond_failed", "parking_in_progress", "interrupted", "orchestration", "unreadable"];
|
|
7
7
|
export type ApprovalUnsettledCause = (typeof APPROVAL_UNSETTLED_CAUSES)[number];
|
|
8
8
|
export interface ApprovalResolutionDecided {
|
|
9
9
|
kind: 'decided';
|
|
@@ -26,6 +26,7 @@ export interface ApprovalResolutionUnsettled {
|
|
|
26
26
|
errorCode?: string;
|
|
27
27
|
status?: number;
|
|
28
28
|
detail?: string;
|
|
29
|
+
parking?: ToolApprovalRespondParkingView;
|
|
29
30
|
}
|
|
30
31
|
export type ApprovalResolution = ApprovalResolutionDecided | ApprovalResolutionNotSent | ApprovalResolutionUnsettled;
|
|
31
32
|
export type ApprovalLegOutcome = FsApprovalOutcome | ToolApprovalFrameOutcome;
|
|
@@ -12,6 +12,7 @@ export const APPROVAL_UNSETTLED_CAUSES = Object.freeze([
|
|
|
12
12
|
'transport_exhausted',
|
|
13
13
|
'safety_stop',
|
|
14
14
|
'respond_failed',
|
|
15
|
+
'parking_in_progress',
|
|
15
16
|
'interrupted',
|
|
16
17
|
'orchestration',
|
|
17
18
|
'unreadable',
|
|
@@ -98,11 +99,23 @@ function fsLegResolution(outcome, kind) {
|
|
|
98
99
|
return { kind: 'unsettled', cause: 'orchestration', ...call, ...detail };
|
|
99
100
|
return { kind: 'unsettled', cause: 'unreadable', ...call, ...detail };
|
|
100
101
|
}
|
|
102
|
+
function parkingViewOf(v) {
|
|
103
|
+
const approvalId = str(pick(v, 'approvalId'));
|
|
104
|
+
if (approvalId === undefined)
|
|
105
|
+
return undefined;
|
|
106
|
+
const hint = pick(v, 'retryAfterMs');
|
|
107
|
+
const retryAfterMs = typeof hint === 'number' && Number.isFinite(hint) && hint >= 0 ? hint : undefined;
|
|
108
|
+
const decidePath = str(pick(v, 'decidePath'));
|
|
109
|
+
return { approvalId, ...(retryAfterMs !== undefined ? { retryAfterMs } : {}), ...(decidePath !== undefined ? { decidePath } : {}) };
|
|
110
|
+
}
|
|
101
111
|
function frameLegResolution(outcome) {
|
|
102
112
|
if (pick(outcome, 'retracted') === true)
|
|
103
113
|
return { kind: 'not_sent', cause: 'retracted' };
|
|
104
114
|
if (pick(outcome, 'editRefused') === true)
|
|
105
115
|
return { kind: 'not_sent', cause: 'edit_refused' };
|
|
116
|
+
const parking = parkingViewOf(pick(outcome, 'parking'));
|
|
117
|
+
if (parking !== undefined)
|
|
118
|
+
return { kind: 'unsettled', cause: 'parking_in_progress', parking };
|
|
106
119
|
if (pick(outcome, 'decision') === 'unresolved') {
|
|
107
120
|
const refusal = pick(outcome, 'respondRefusal');
|
|
108
121
|
return {
|
|
@@ -15,9 +15,9 @@ export function classifyAskParkRows(rows, taskId, deps) {
|
|
|
15
15
|
kind: 'unborn',
|
|
16
16
|
reason: rows.length === 0
|
|
17
17
|
?
|
|
18
|
-
`no pending row at all for task ${taskId} — either an orphan park (a seat held with no decision outstanding) or the next gate's row is not minted yet`
|
|
18
|
+
`no pending row at all for task ${taskId} — either an orphan park (a seat held with no decision outstanding) or the next gate's row is not minted yet; this client cannot tell which`
|
|
19
19
|
: soleUnowned
|
|
20
|
-
? `the only pending row (task ${String(sole?.taskId)} / session ${String(sole?.sessionId)}) could not be proven to belong to this client (not in its own-run ledger, session id absent or different) — refusing to surface an approval that may be another session's`
|
|
20
|
+
? `the only pending row (task ${String(sole?.taskId)} / session ${String(sole?.sessionId)}) could not be proven to belong to this client (not in its own-run ledger, session id absent or different; this client cannot tell which) — refusing to surface an approval that may be another session's`
|
|
21
21
|
: `${String(rows.length)} pending rows but none for task ${taskId} — ambiguous queue, refusing to surface an unrelated row`,
|
|
22
22
|
};
|
|
23
23
|
}
|
package/dist/hitl/frameRouter.js
CHANGED
|
@@ -66,6 +66,11 @@ function isGatedToolName(name) {
|
|
|
66
66
|
function isFsOrShellToolName(name) {
|
|
67
67
|
return typeof name === 'string' && (toolNameIsFsWrite(name) || toolNameIsShellExec(name));
|
|
68
68
|
}
|
|
69
|
+
function parkFamilyEvidence(led, gateToolName, gatedCallId) {
|
|
70
|
+
if (!isFsOrShellToolName(gateToolName))
|
|
71
|
+
return false;
|
|
72
|
+
return gatedCallId === undefined || led.isFsOrShellGatedCall(gatedCallId);
|
|
73
|
+
}
|
|
69
74
|
const ENGINE_GATE_PARKED_ERROR_CODE = GATE_PARKED_ERROR_CODE;
|
|
70
75
|
function isEngineAbortToolEnd(ev) {
|
|
71
76
|
if (ev.errorCode === ENGINE_GATE_PARKED_ERROR_CODE)
|
|
@@ -353,7 +358,7 @@ function routeSuspended(ev, ctx) {
|
|
|
353
358
|
if (ev.gate &&
|
|
354
359
|
isToolApprovalGate(ev.gate) &&
|
|
355
360
|
ctx.taskId.current) {
|
|
356
|
-
ctx.led.noteParkGate({ fsOrShellFamily:
|
|
361
|
+
ctx.led.noteParkGate({ fsOrShellFamily: parkFamilyEvidence(ctx.led, ev.gate.toolName, gatedCallId) });
|
|
357
362
|
return { kind: 'park', gate: 'fs', ...(gatedCallId !== undefined ? { gatedCallId } : {}) };
|
|
358
363
|
}
|
|
359
364
|
return { kind: 'yield', events: [ev] };
|
|
@@ -364,7 +369,7 @@ function routeDone(ev, ctx) {
|
|
|
364
369
|
const park = parkGateOf(ev.result);
|
|
365
370
|
if (park !== null) {
|
|
366
371
|
ctx.taskId.current = ctx.taskId.current ?? park.taskId;
|
|
367
|
-
led.noteParkGate({ fsOrShellFamily: park.gate === 'fs' &&
|
|
372
|
+
led.noteParkGate({ fsOrShellFamily: park.gate === 'fs' && parkFamilyEvidence(led, park.toolName, gatedCallId) });
|
|
368
373
|
return { kind: 'park', gate: park.gate, pendingDone: ev, ...(gatedCallId !== undefined ? { gatedCallId } : {}) };
|
|
369
374
|
}
|
|
370
375
|
const events = [...flushHeldWithInterruptRewrite(led, { terminal: ev, signal: ctx.signal })];
|
|
@@ -46,6 +46,7 @@ export interface GateLedger {
|
|
|
46
46
|
lastPendingFsCall(): string | undefined;
|
|
47
47
|
noteFsOrShellGate(callId: string): void;
|
|
48
48
|
lastFsOrShellGatedCallId(): string | undefined;
|
|
49
|
+
isFsOrShellGatedCall(callId: string): boolean;
|
|
49
50
|
noteParkGate(evidence: {
|
|
50
51
|
fsOrShellFamily: boolean;
|
|
51
52
|
}): void;
|
package/dist/hitl/gateLedger.js
CHANGED
|
@@ -235,6 +235,9 @@ export function createGateLedger() {
|
|
|
235
235
|
lastFsOrShellGatedCallId() {
|
|
236
236
|
return lastFsOrShellGatedCall;
|
|
237
237
|
},
|
|
238
|
+
isFsOrShellGatedCall(callId) {
|
|
239
|
+
return fsOrShellGatedCalls.has(callId);
|
|
240
|
+
},
|
|
238
241
|
noteParkGate(evidence) {
|
|
239
242
|
if (batchParkCount > 0 && !heldSinceLastPark)
|
|
240
243
|
return;
|
|
@@ -56,6 +56,7 @@ export interface HitlDecideDenyOutcome {
|
|
|
56
56
|
decision: 'deny';
|
|
57
57
|
reason?: string | undefined;
|
|
58
58
|
settledBy?: 'policy' | undefined;
|
|
59
|
+
decidedBy?: 'policy' | undefined;
|
|
59
60
|
}
|
|
60
61
|
export declare class HitlSafetyError extends Error {
|
|
61
62
|
readonly code: HitlSafetyCode;
|
package/dist/hitl/hitlBridge.js
CHANGED
|
@@ -2,6 +2,7 @@ import { askParkForeignGateKind } from '../adapter/activeRunSelfHeal.js';
|
|
|
2
2
|
import { eventSeq } from '../adapter/types.js';
|
|
3
3
|
import { hostLog } from '../host.js';
|
|
4
4
|
import { abortableSleep } from '../abortableSleep.js';
|
|
5
|
+
import { readOwnDataValue } from '../ownKey.js';
|
|
5
6
|
import { suspendedReopenOf } from './suspendedReopen.js';
|
|
6
7
|
export const DEFAULT_DENY_REASON = 'The user rejected this tool use';
|
|
7
8
|
const MAX_DENY_REASON_CHARS = 4096;
|
|
@@ -291,7 +292,11 @@ export class HitlBridge {
|
|
|
291
292
|
decision: 'deny',
|
|
292
293
|
...this.bindingOf(pending),
|
|
293
294
|
...(outcome.reason !== undefined ? { reason: outcome.reason } : {}),
|
|
294
|
-
...(outcome
|
|
295
|
+
...(readOwnDataValue(outcome, 'decidedBy') === 'policy'
|
|
296
|
+
? { decidedBy: 'policy' }
|
|
297
|
+
: readOwnDataValue(outcome, 'settledBy') === 'policy'
|
|
298
|
+
? { settledBy: 'policy' }
|
|
299
|
+
: {}),
|
|
295
300
|
};
|
|
296
301
|
return this.decideRaw(pending.sessionId, decision, opts);
|
|
297
302
|
}
|
|
@@ -28,12 +28,16 @@ export declare const REMEMBER_NOT_APPLIED_WARN_TEXT = "this session-wide allow w
|
|
|
28
28
|
export declare const EDIT_NOT_FORWARDED_WARN_TEXT = "your edited tool input was NOT forwarded \u2014 the tool is running with the ORIGINAL input you saw before editing";
|
|
29
29
|
export declare function surfaceRememberNotApplied(): void;
|
|
30
30
|
export declare function surfaceEditNotForwarded(): void;
|
|
31
|
+
export declare const APPROVAL_PARKING_WARN_TEXT = "This approval is being parked; answer it again on the parked card when it appears.";
|
|
32
|
+
export declare function approvalParkingNoticeText(retryAfterMs?: number): string;
|
|
33
|
+
export declare function surfaceApprovalParking(retryAfterMs?: number): void;
|
|
31
34
|
export declare const REPLAYED_APPROVAL_LIST_UNREADABLE_WARN_TEXT: string;
|
|
32
35
|
export declare function surfaceReplayedApprovalListUnreadable(): void;
|
|
33
36
|
export declare const EDIT_REFUSED_ON_BLIND_ASK_WARN_TEXT: string;
|
|
34
37
|
export declare function surfaceEditRefusedOnBlindAsk(): void;
|
|
35
38
|
export declare const RULE_NOT_SENT_WARN_TEXT = "your \"don't ask again\" choice was NOT sent to this engine \u2014 support for that rule form could not be confirmed. The approval itself went through, but you will be asked again";
|
|
36
39
|
export declare function surfaceRuleArmNotSent(): void;
|
|
37
|
-
export
|
|
38
|
-
export declare
|
|
40
|
+
export type RuleArmRejectedWhy = 'both_arms' | 'index_not_a_batch_offer' | 'text_not_offered';
|
|
41
|
+
export declare const RULE_NOT_SENT_REJECTED_WARN_TEXT: string;
|
|
42
|
+
export declare function surfaceRuleArmRejected(why?: RuleArmRejectedWhy): void;
|
|
39
43
|
export declare function observeCancelByDeny(settle: Promise<unknown>, taskId: string): void;
|
|
@@ -72,6 +72,17 @@ export function surfaceRememberNotApplied() {
|
|
|
72
72
|
export function surfaceEditNotForwarded() {
|
|
73
73
|
surfaceSelfClearingWarn(EDIT_NOT_FORWARDED_KEY, EDIT_NOT_FORWARDED_WARN_TEXT, EDIT_NOT_FORWARDED_TIMEOUT_MS);
|
|
74
74
|
}
|
|
75
|
+
export const APPROVAL_PARKING_WARN_TEXT = 'This approval is being parked; answer it again on the parked card when it appears.';
|
|
76
|
+
const APPROVAL_PARKING_KEY = 'hitl-approval-parking';
|
|
77
|
+
const APPROVAL_PARKING_TIMEOUT_MS = 15_000;
|
|
78
|
+
export function approvalParkingNoticeText(retryAfterMs) {
|
|
79
|
+
if (retryAfterMs === undefined)
|
|
80
|
+
return APPROVAL_PARKING_WARN_TEXT;
|
|
81
|
+
return `${APPROVAL_PARKING_WARN_TEXT} (the engine suggested waiting about ${Math.ceil(retryAfterMs / 100) / 10} s)`;
|
|
82
|
+
}
|
|
83
|
+
export function surfaceApprovalParking(retryAfterMs) {
|
|
84
|
+
surfaceSelfClearingWarn(APPROVAL_PARKING_KEY, approvalParkingNoticeText(retryAfterMs), APPROVAL_PARKING_TIMEOUT_MS);
|
|
85
|
+
}
|
|
75
86
|
export const REPLAYED_APPROVAL_LIST_UNREADABLE_WARN_TEXT = "Could not read the pending-approval list, so an approval request read back from this run's event log was not raised as a question, " +
|
|
76
87
|
'and no decision was sent for it.';
|
|
77
88
|
const REPLAYED_APPROVAL_LIST_UNREADABLE_KEY = 'hitl-replayed-approval-list-unreadable';
|
|
@@ -91,11 +102,26 @@ const RULE_NOT_SENT_TIMEOUT_MS = 10_000;
|
|
|
91
102
|
export function surfaceRuleArmNotSent() {
|
|
92
103
|
surfaceSelfClearingWarn(RULE_NOT_SENT_KEY, RULE_NOT_SENT_WARN_TEXT, RULE_NOT_SENT_TIMEOUT_MS);
|
|
93
104
|
}
|
|
94
|
-
|
|
105
|
+
function ruleNotSentRejectedReason(why) {
|
|
106
|
+
switch (why) {
|
|
107
|
+
case 'both_arms':
|
|
108
|
+
return 'the card returned two conflicting choices at once, a rule text and a batch index';
|
|
109
|
+
case 'index_not_a_batch_offer':
|
|
110
|
+
return 'the card returned an index that is not a batch offer';
|
|
111
|
+
case 'text_not_offered':
|
|
112
|
+
return 'the card returned a rule text that is not one of the single rules the engine offered';
|
|
113
|
+
default:
|
|
114
|
+
return 'the card returned a rule text that is not one of the single rules the engine offered, an index that is not a batch offer, or two conflicting choices at once; this client cannot tell which';
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function ruleNotSentRejectedText(why) {
|
|
118
|
+
return `your "don't ask again" choice was NOT sent — it did not pass the client-side check (${ruleNotSentRejectedReason(why)}). The approval itself went through, but you will be asked again`;
|
|
119
|
+
}
|
|
120
|
+
export const RULE_NOT_SENT_REJECTED_WARN_TEXT = ruleNotSentRejectedText();
|
|
95
121
|
const RULE_NOT_SENT_REJECTED_KEY = 'hitl-rule-arm-rejected';
|
|
96
122
|
const RULE_NOT_SENT_REJECTED_TIMEOUT_MS = 10_000;
|
|
97
|
-
export function surfaceRuleArmRejected() {
|
|
98
|
-
surfaceSelfClearingWarn(RULE_NOT_SENT_REJECTED_KEY,
|
|
123
|
+
export function surfaceRuleArmRejected(why) {
|
|
124
|
+
surfaceSelfClearingWarn(RULE_NOT_SENT_REJECTED_KEY, ruleNotSentRejectedText(why), RULE_NOT_SENT_REJECTED_TIMEOUT_MS);
|
|
99
125
|
}
|
|
100
126
|
function isNoPendingError(e) {
|
|
101
127
|
if (e instanceof HitlSafetyError)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { putOwnKey } from '../ownKey.js';
|
|
1
|
+
import { putOwnKey, readOwnDataValue } from '../ownKey.js';
|
|
2
2
|
import { DecideTransportRetryExhaustedError, HitlBridge, HitlSafetyError, NO_QUESTION_OVERLAY_REASON, findPendingForTask, isNoDecisionSurfaceReason, readDecideCurrentPending, readWireErrorCode, } from './hitlBridge.js';
|
|
3
|
-
import { ASK_GONE, CHECKPOINT_UNSUPPORTED_VERSION, PARKED_RESUME_STARTUP_FAILED, RESUME_POLICY_FOLD_CODE, isDecideParkMovedCode, refusalSaysStillPending } from '../engineErrorCodes.js';
|
|
3
|
+
import { ASK_GONE, CHECKPOINT_UNSUPPORTED_VERSION, DECIDE_WORKFLOW_PARK_FORKED, DECIDE_WORKFLOW_PARKS_UNREADABLE, PARKED_RESUME_STARTUP_FAILED, RESUME_POLICY_FOLD_CODE, isDecideParkMovedCode, refusalSaysStillPending } from '../engineErrorCodes.js';
|
|
4
4
|
import { publishQuestionFrame, registerLocalQuestionResponder, hasQuestionOverlay, } from '../liveQuestionStore.js';
|
|
5
5
|
import { hostLog } from '../host.js';
|
|
6
6
|
import { surfaceFsApprovalAndDecide } from './toolApprovalWire.js';
|
|
@@ -63,7 +63,11 @@ function alreadyResolvedFailureVerdict(outcome) {
|
|
|
63
63
|
return isAlreadyResolvedGateReason(outcome.reason);
|
|
64
64
|
}
|
|
65
65
|
export function isGateStandingErrorCode(code, retriable) {
|
|
66
|
-
return code === RESUME_POLICY_FOLD_CODE ||
|
|
66
|
+
return (code === RESUME_POLICY_FOLD_CODE ||
|
|
67
|
+
code === 'corrupt' ||
|
|
68
|
+
(code === CHECKPOINT_UNSUPPORTED_VERSION && retriable === true) ||
|
|
69
|
+
code === DECIDE_WORKFLOW_PARKS_UNREADABLE ||
|
|
70
|
+
code === DECIDE_WORKFLOW_PARK_FORKED);
|
|
67
71
|
}
|
|
68
72
|
export function isCodeClassifiedGateFailure(code, retriable) {
|
|
69
73
|
return isGateStandingErrorCode(code, retriable) || code === PARKED_RESUME_STARTUP_FAILED;
|
|
@@ -192,10 +196,18 @@ function isFetchStepNoPending(outcome) {
|
|
|
192
196
|
async function surfaceParkGate(park, ctx, gatedCallId, onPresented) {
|
|
193
197
|
const { deps, led, taskId } = ctx;
|
|
194
198
|
if (park.gate === 'fs') {
|
|
199
|
+
let decidedByReading;
|
|
200
|
+
try {
|
|
201
|
+
decidedByReading = readOwnDataValue(deps.approvalLane, 'approvalDecidedBy');
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
decidedByReading = undefined;
|
|
205
|
+
}
|
|
195
206
|
const outcome = await surfaceFsApprovalAndDecide({
|
|
196
207
|
client: deps.client,
|
|
197
208
|
...(onPresented ? { onPresented } : {}),
|
|
198
209
|
...(deps.onDecideOperation ? { onDecideOperation: deps.onDecideOperation } : {}),
|
|
210
|
+
...(decidedByReading !== undefined ? { approvalDecidedBy: decidedByReading } : {}),
|
|
199
211
|
}, taskId, argsByCallOf(led), ctx.signal, gatedCallId);
|
|
200
212
|
const outcomeDenied = 'denied' in outcome ? outcome.denied : undefined;
|
|
201
213
|
if (outcome.kind === 'decided' && outcomeDenied === true && outcome.gatedCallId) {
|
|
@@ -210,6 +222,12 @@ async function surfaceParkGate(park, ctx, gatedCallId, onPresented) {
|
|
|
210
222
|
}
|
|
211
223
|
return surfaceGateAndDecide(deps, taskId, argsByCallOf(led), ctx.signal, gatedCallId, onPresented);
|
|
212
224
|
}
|
|
225
|
+
function decidedIdentityCandidate(led, park) {
|
|
226
|
+
const id = park.gatedCallId;
|
|
227
|
+
if (id !== undefined)
|
|
228
|
+
return id;
|
|
229
|
+
return led.lastFsOrShellGatedCallId();
|
|
230
|
+
}
|
|
213
231
|
export async function resolvePark(park, ctx) {
|
|
214
232
|
const { led, taskId } = ctx;
|
|
215
233
|
let outcome;
|
|
@@ -245,7 +263,7 @@ export async function resolvePark(park, ctx) {
|
|
|
245
263
|
}
|
|
246
264
|
else {
|
|
247
265
|
if (park.gate === 'fs')
|
|
248
|
-
candidateGatedCallId = led
|
|
266
|
+
candidateGatedCallId = decidedIdentityCandidate(led, park);
|
|
249
267
|
outcome = await surfaceParkGate(park, ctx, park.gatedCallId, witness);
|
|
250
268
|
}
|
|
251
269
|
const alreadyDecidedById = outcome.kind === 'failed' &&
|
|
@@ -88,6 +88,10 @@ function rowStr(row, key) {
|
|
|
88
88
|
function persistedRuleIdentityRowReadable(row) {
|
|
89
89
|
return rowStr(row, 'rule') !== undefined && rowStr(row, 'scope') !== undefined;
|
|
90
90
|
}
|
|
91
|
+
function persistedRuleIdentityRowWhy(row) {
|
|
92
|
+
const bad = ['rule', 'scope'].filter((k) => rowStr(row, k) === undefined);
|
|
93
|
+
return `${bad.join(' and ')} ${bad.length === 1 ? 'is' : 'are'} missing or not a non-empty string`;
|
|
94
|
+
}
|
|
91
95
|
export function persistedRuleBehaviorOf(rule) {
|
|
92
96
|
const b = rowStr(rule, 'behavior');
|
|
93
97
|
return b !== undefined && BEHAVIOR_SET.has(b) ? b : undefined;
|
|
@@ -148,16 +152,22 @@ export async function listAllPersistedRules(facade, params = {}, opts) {
|
|
|
148
152
|
}
|
|
149
153
|
return { ok: false, failure };
|
|
150
154
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
155
|
+
const pageProblems = [];
|
|
156
|
+
if (typeof res !== 'object' || res === null) {
|
|
157
|
+
pageProblems.push('the page is not an object');
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
if (!Array.isArray(res.rules))
|
|
161
|
+
pageProblems.push('rules is missing or not an array');
|
|
162
|
+
if (typeof res.rev !== 'number' || !Number.isFinite(res.rev))
|
|
163
|
+
pageProblems.push('rev is missing or not a finite number');
|
|
164
|
+
}
|
|
165
|
+
if (pageProblems.length > 0) {
|
|
156
166
|
return {
|
|
157
167
|
ok: false,
|
|
158
168
|
failure: {
|
|
159
169
|
kind: 'error',
|
|
160
|
-
message:
|
|
170
|
+
message: `rule list page was malformed (${pageProblems.join('; ')}) on a 2xx — refusing to certify it as a complete governance list`,
|
|
161
171
|
},
|
|
162
172
|
};
|
|
163
173
|
}
|
|
@@ -176,7 +186,7 @@ export async function listAllPersistedRules(facade, params = {}, opts) {
|
|
|
176
186
|
ok: false,
|
|
177
187
|
failure: {
|
|
178
188
|
kind: 'error',
|
|
179
|
-
message:
|
|
189
|
+
message: `rule list page carried a malformed rule row (${persistedRuleIdentityRowWhy(r)}) — refusing to present a list that hides or garbles live rules`,
|
|
180
190
|
},
|
|
181
191
|
};
|
|
182
192
|
}
|
|
@@ -348,24 +358,26 @@ export async function writePersistedRule(facade, input, opts) {
|
|
|
348
358
|
catch (e) {
|
|
349
359
|
return writeOutcomeFromFailure(classifyRulesFailure(e));
|
|
350
360
|
}
|
|
351
|
-
const malformed = () => ({
|
|
361
|
+
const malformed = (what) => ({
|
|
352
362
|
status: 'unknown',
|
|
353
363
|
why: 'malformed_result',
|
|
354
|
-
message:
|
|
364
|
+
message: `the rule write answered 2xx with a body this client cannot read (${what}) — refusing to certify it as a standing rule; re-list the rules to reconcile`,
|
|
355
365
|
});
|
|
356
366
|
if (res === null || typeof res !== 'object' || Array.isArray(res))
|
|
357
|
-
return malformed();
|
|
367
|
+
return malformed('the body is not an object');
|
|
358
368
|
const snap = certificationSnapshot(res);
|
|
359
369
|
if (snap === null)
|
|
360
|
-
return malformed();
|
|
370
|
+
return malformed('a field of the body is an inherited or accessor property, or threw when read');
|
|
361
371
|
const { status, rev, hasLiveness, liveness, hasRow, row } = snap;
|
|
362
|
-
|
|
363
|
-
|
|
372
|
+
const statusOk = status === 'persisted' || status === 'no-op';
|
|
373
|
+
const revOk = typeof rev === 'number' && Number.isFinite(rev);
|
|
374
|
+
if (!statusOk || !revOk)
|
|
375
|
+
return malformed([...(statusOk ? [] : ['status is not persisted or no-op']), ...(revOk ? [] : ['rev is not a finite number'])].join('; '));
|
|
364
376
|
const wrote = status === 'persisted' ? 'persisted' : 'no_op';
|
|
365
377
|
if (hasLiveness) {
|
|
366
378
|
if (liveness === 'no') {
|
|
367
379
|
if (hasRow)
|
|
368
|
-
return malformed();
|
|
380
|
+
return malformed('liveness and the written row contradict each other');
|
|
369
381
|
return {
|
|
370
382
|
status: 'revoked',
|
|
371
383
|
wrote,
|
|
@@ -375,7 +387,7 @@ export async function writePersistedRule(facade, input, opts) {
|
|
|
375
387
|
}
|
|
376
388
|
if (liveness === 'unknown') {
|
|
377
389
|
if (hasRow)
|
|
378
|
-
return malformed();
|
|
390
|
+
return malformed('liveness and the written row contradict each other');
|
|
379
391
|
return {
|
|
380
392
|
status: 'unverified',
|
|
381
393
|
wrote,
|
|
@@ -384,10 +396,10 @@ export async function writePersistedRule(facade, input, opts) {
|
|
|
384
396
|
};
|
|
385
397
|
}
|
|
386
398
|
if (liveness !== 'yes')
|
|
387
|
-
return malformed();
|
|
399
|
+
return malformed('liveness is not one of yes, no or unknown');
|
|
388
400
|
}
|
|
389
401
|
if (!persistedRuleIdentityRowReadable(row))
|
|
390
|
-
return malformed();
|
|
402
|
+
return malformed(`the written row is unreadable (${persistedRuleIdentityRowWhy(row)})`);
|
|
391
403
|
const landed = row;
|
|
392
404
|
if (landed.behavior !== behavior || landed.rule !== rule || landed.scope !== scope) {
|
|
393
405
|
return {
|
|
@@ -6,8 +6,6 @@ export declare const PLAN_REVIEW_APPROVE_LABEL = "Yes, approve and run the plan"
|
|
|
6
6
|
export declare const PLAN_REVIEW_REJECT_LABEL = "No, reject it (keep planning)";
|
|
7
7
|
export declare const PLAN_REVIEW_APPROVE_AUTO_LABEL = "Yes, and auto-accept edits";
|
|
8
8
|
export declare const PLAN_REVIEW_APPROVE_MANUAL_LABEL = "Yes, and manually approve edits";
|
|
9
|
-
export declare const PLAN_REVIEW_MODE_AFTER_MIN_ENGINE: readonly [number, number, number];
|
|
10
|
-
export declare function versionSupportsPlanReviewModeAfter(v: string | undefined): 'supported' | 'unsupported' | 'unknown';
|
|
11
9
|
export declare function planReviewDecisionFromAnswer(answer: QuestionAnswer | null | undefined, shownLabels?: readonly string[]): 'approve' | 'reject' | 'dismissed';
|
|
12
10
|
export type PlanReviewChoice = {
|
|
13
11
|
decision: 'approve';
|
|
@@ -9,7 +9,6 @@ import { decideFailureNote } from '../decideFailureNote.js';
|
|
|
9
9
|
import { checkpointUnsupportedVersionContent, checkpointUnsupportedVersionFromError } from '../resumeRefusalCopy.js';
|
|
10
10
|
import { REQUEST_FIELD_CONFLICT, isUnavailableFaceCode } from '../engineErrorCodes.js';
|
|
11
11
|
import { denyReasonForWire, isPlanReviewModeAfter } from './hitlBridge.js';
|
|
12
|
-
import { engineCapString } from '../engineCapsCache.js';
|
|
13
12
|
import { planReviewQuestionId, REOPEN_ID_TAIL, taskIdFromPlanReviewQuestionId } from './gateIdentity.js';
|
|
14
13
|
import { notePlanReviewAnswered, notePlanReviewAnsweredFor, planReviewArmedKeyFor, registerArmedGateFor, waitForGateArmedFor, wasGateArmedFor, } from './armedGateRegistry.js';
|
|
15
14
|
import { createSessionSlot, DEFAULT_SESSION_KEY } from '../sessionSlot.js';
|
|
@@ -20,20 +19,6 @@ export const PLAN_REVIEW_APPROVE_LABEL = 'Yes, approve and run the plan';
|
|
|
20
19
|
export const PLAN_REVIEW_REJECT_LABEL = 'No, reject it (keep planning)';
|
|
21
20
|
export const PLAN_REVIEW_APPROVE_AUTO_LABEL = 'Yes, and auto-accept edits';
|
|
22
21
|
export const PLAN_REVIEW_APPROVE_MANUAL_LABEL = 'Yes, and manually approve edits';
|
|
23
|
-
export const PLAN_REVIEW_MODE_AFTER_MIN_ENGINE = Object.freeze([7, 86, 0]);
|
|
24
|
-
export function versionSupportsPlanReviewModeAfter(v) {
|
|
25
|
-
const mm = v ? /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.exec(v) : null;
|
|
26
|
-
if (!mm)
|
|
27
|
-
return 'unknown';
|
|
28
|
-
const got = [Number(mm[1]), Number(mm[2]), Number(mm[3])];
|
|
29
|
-
for (let i = 0; i < 3; i++) {
|
|
30
|
-
const g = got[i];
|
|
31
|
-
const need = PLAN_REVIEW_MODE_AFTER_MIN_ENGINE[i];
|
|
32
|
-
if (g !== need)
|
|
33
|
-
return g > need ? 'supported' : 'unsupported';
|
|
34
|
-
}
|
|
35
|
-
return 'supported';
|
|
36
|
-
}
|
|
37
22
|
const APPROVE_LABEL = PLAN_REVIEW_APPROVE_LABEL;
|
|
38
23
|
const REJECT_LABEL = PLAN_REVIEW_REJECT_LABEL;
|
|
39
24
|
const LEGACY_TWO_CHOICE_LABELS = Object.freeze([PLAN_REVIEW_APPROVE_LABEL, PLAN_REVIEW_REJECT_LABEL]);
|
|
@@ -266,9 +251,7 @@ export function armPlanReviewApproval(result, sessionKey, opts) {
|
|
|
266
251
|
}
|
|
267
252
|
armedPlanReviewTaskIds.add(taskId);
|
|
268
253
|
armedTaskId = taskId;
|
|
269
|
-
const
|
|
270
|
-
const offerModeAfter = opts?.submittedInPlanMode === true &&
|
|
271
|
-
versionSupportsPlanReviewModeAfter(engineCapString(capsBaseUrl, 'version')) === 'supported';
|
|
254
|
+
const offerModeAfter = opts?.submittedInPlanMode === true;
|
|
272
255
|
if (offerModeAfter)
|
|
273
256
|
offerModeAfterByTask.set(taskId, true);
|
|
274
257
|
else
|
|
@@ -109,10 +109,10 @@ export function sessionPolicyDeliverable(behavior, rules, opts) {
|
|
|
109
109
|
}
|
|
110
110
|
if (why === undefined && COVERING_NAMESPACE_PREFIXES.some((p) => r.startsWith(p))) {
|
|
111
111
|
const segment = protocolSegmentOf(r);
|
|
112
|
-
if (segment.kind === '
|
|
113
|
-
return unreadable();
|
|
114
|
-
if (segment.kind === 'unminted')
|
|
112
|
+
if (segment.kind === 'empty' || segment.kind === 'unminted')
|
|
115
113
|
why = 'not_a_tool_name';
|
|
114
|
+
else if (segment.kind === 'unknown')
|
|
115
|
+
return unreadable();
|
|
116
116
|
}
|
|
117
117
|
if (why === undefined && mounted !== undefined && !mounted.has(r))
|
|
118
118
|
why = 'not_in_roster';
|