@sema-agent/client-core 0.92.0 → 0.93.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 +179 -0
- package/README.md +23 -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/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 +3 -2
- package/dist/hitl/hitlHostSurface.js +18 -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 +22 -7
- package/dist/hitl/toolApprovalWire.js +133 -54
- 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 +901 -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
|
}
|
|
@@ -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
|
}
|
|
@@ -34,6 +34,7 @@ export declare const EDIT_REFUSED_ON_BLIND_ASK_WARN_TEXT: string;
|
|
|
34
34
|
export declare function surfaceEditRefusedOnBlindAsk(): void;
|
|
35
35
|
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
36
|
export declare function surfaceRuleArmNotSent(): void;
|
|
37
|
-
export
|
|
38
|
-
export declare
|
|
37
|
+
export type RuleArmRejectedWhy = 'both_arms' | 'index_not_a_batch_offer' | 'text_not_offered';
|
|
38
|
+
export declare const RULE_NOT_SENT_REJECTED_WARN_TEXT: string;
|
|
39
|
+
export declare function surfaceRuleArmRejected(why?: RuleArmRejectedWhy): void;
|
|
39
40
|
export declare function observeCancelByDeny(settle: Promise<unknown>, taskId: string): void;
|
|
@@ -91,11 +91,26 @@ const RULE_NOT_SENT_TIMEOUT_MS = 10_000;
|
|
|
91
91
|
export function surfaceRuleArmNotSent() {
|
|
92
92
|
surfaceSelfClearingWarn(RULE_NOT_SENT_KEY, RULE_NOT_SENT_WARN_TEXT, RULE_NOT_SENT_TIMEOUT_MS);
|
|
93
93
|
}
|
|
94
|
-
|
|
94
|
+
function ruleNotSentRejectedReason(why) {
|
|
95
|
+
switch (why) {
|
|
96
|
+
case 'both_arms':
|
|
97
|
+
return 'the card returned two conflicting choices at once, a rule text and a batch index';
|
|
98
|
+
case 'index_not_a_batch_offer':
|
|
99
|
+
return 'the card returned an index that is not a batch offer';
|
|
100
|
+
case 'text_not_offered':
|
|
101
|
+
return 'the card returned a rule text that is not one of the single rules the engine offered';
|
|
102
|
+
default:
|
|
103
|
+
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';
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function ruleNotSentRejectedText(why) {
|
|
107
|
+
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`;
|
|
108
|
+
}
|
|
109
|
+
export const RULE_NOT_SENT_REJECTED_WARN_TEXT = ruleNotSentRejectedText();
|
|
95
110
|
const RULE_NOT_SENT_REJECTED_KEY = 'hitl-rule-arm-rejected';
|
|
96
111
|
const RULE_NOT_SENT_REJECTED_TIMEOUT_MS = 10_000;
|
|
97
|
-
export function surfaceRuleArmRejected() {
|
|
98
|
-
surfaceSelfClearingWarn(RULE_NOT_SENT_REJECTED_KEY,
|
|
112
|
+
export function surfaceRuleArmRejected(why) {
|
|
113
|
+
surfaceSelfClearingWarn(RULE_NOT_SENT_REJECTED_KEY, ruleNotSentRejectedText(why), RULE_NOT_SENT_REJECTED_TIMEOUT_MS);
|
|
99
114
|
}
|
|
100
115
|
function isNoPendingError(e) {
|
|
101
116
|
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';
|
|
@@ -115,6 +115,8 @@ export declare function sessionPolicyReadNotice(outcome: Exclude<SessionPolicyRe
|
|
|
115
115
|
kind: 'present';
|
|
116
116
|
}> | {
|
|
117
117
|
kind: 'no_target';
|
|
118
|
+
} | {
|
|
119
|
+
kind: 'not_started';
|
|
118
120
|
}): string;
|
|
119
121
|
export interface SessionPolicyRemovedEntry {
|
|
120
122
|
readonly field: 'toolDeny' | 'toolAllow';
|
|
@@ -328,7 +328,7 @@ export function sessionPolicyTightenNotice(outcome) {
|
|
|
328
328
|
case 'face_unavailable':
|
|
329
329
|
return 'This deployment does not keep per-session tool rules, so nothing was written.';
|
|
330
330
|
case 'session_not_found':
|
|
331
|
-
return 'This session is not readable from here — it may not exist, or it may belong to someone else; nothing was written.';
|
|
331
|
+
return 'This session is not readable from here — it may not exist, or it may belong to someone else (the engine answers both the same way, so this client cannot tell which); nothing was written.';
|
|
332
332
|
case 'session_ownerless':
|
|
333
333
|
return 'This session has no owner, so a rule written for it could never be read back; nothing was written. Attach an identity to the session and try again.';
|
|
334
334
|
case 'unauthorized':
|
|
@@ -354,6 +354,7 @@ export function sessionPolicyTightenNotice(outcome) {
|
|
|
354
354
|
}
|
|
355
355
|
const READ_NOTICE_GENERIC = 'This session’s rule record could not be shown, so what it holds is not known. Try reading it again.';
|
|
356
356
|
const READ_NOTICE_NO_TARGET = 'There is no engine connection for this session, so its rule record cannot be read and what it holds is not known. Look again once it is connected.';
|
|
357
|
+
const READ_NOTICE_NOT_STARTED = 'This session has not begun on the engine yet — nothing has been sent in it — so it has no rule record to read. Send a first message, then look again.';
|
|
357
358
|
function readFailureSentence(kind) {
|
|
358
359
|
switch (kind) {
|
|
359
360
|
case 'face-unavailable':
|
|
@@ -367,7 +368,7 @@ function readFailureSentence(kind) {
|
|
|
367
368
|
case 'session-ownerless':
|
|
368
369
|
return 'This session has no owner, so it has no rule record to read.';
|
|
369
370
|
case 'session-not-found':
|
|
370
|
-
return 'This session is not readable from here — it may not exist, or it may belong to someone else — so its rule record cannot be shown. Check that you are connected to the engine and the session you meant, or continue in a new session.';
|
|
371
|
+
return 'This session is not readable from here — it may not exist, or it may belong to someone else (the engine answers both the same way, so this client cannot tell which) — so its rule record cannot be shown. Check that you are connected to the engine and the session you meant, or continue in a new session.';
|
|
371
372
|
case 'request-rejected':
|
|
372
373
|
return 'The engine rejected the request to read this session’s rule record as malformed, so it could not be read. Sending the same request again will not change that; check which session was asked for.';
|
|
373
374
|
case 'legacy-tool-name':
|
|
@@ -409,6 +410,8 @@ export function sessionPolicyReadNotice(outcome) {
|
|
|
409
410
|
return readMalformedSentence(ownRead(outcome, 'why')) ?? READ_NOTICE_GENERIC;
|
|
410
411
|
if (arm === 'no_target')
|
|
411
412
|
return READ_NOTICE_NO_TARGET;
|
|
413
|
+
if (arm === 'not_started')
|
|
414
|
+
return READ_NOTICE_NOT_STARTED;
|
|
412
415
|
return READ_NOTICE_GENERIC;
|
|
413
416
|
}
|
|
414
417
|
catch {
|
|
@@ -569,7 +572,7 @@ export function sessionPolicyRemovalNotice(outcome) {
|
|
|
569
572
|
case 'face_unavailable':
|
|
570
573
|
return 'This deployment does not keep per-session tool rules, so there is no session rule record to remove entries from — nothing was changed.';
|
|
571
574
|
case 'session_not_found':
|
|
572
|
-
return 'This session is not readable from here — it may not exist, or it may belong to someone else; nothing was changed.';
|
|
575
|
+
return 'This session is not readable from here — it may not exist, or it may belong to someone else (the engine answers both the same way, so this client cannot tell which); nothing was changed.';
|
|
573
576
|
case 'session_ownerless':
|
|
574
577
|
return 'This session has no owner, so it has no session rule record to change; nothing was changed.';
|
|
575
578
|
case 'unauthorized':
|
|
@@ -586,7 +589,7 @@ export function sessionPolicyRemovalNotice(outcome) {
|
|
|
586
589
|
case 'store_corrupt':
|
|
587
590
|
return 'The engine could not establish which version this session’s rules are at and refused to stamp a new one, so nothing was changed. This needs an operator to look at the rule store.';
|
|
588
591
|
case 'session_not_found':
|
|
589
|
-
return 'This session is not writable from here — it may not exist, or it may belong to someone else; nothing was changed.';
|
|
592
|
+
return 'This session is not writable from here — it may not exist, or it may belong to someone else (the engine answers both the same way, so this client cannot tell which); nothing was changed.';
|
|
590
593
|
case 'unauthorized':
|
|
591
594
|
return 'This call could not present a verifiable identity, so nothing was changed.';
|
|
592
595
|
default:
|