@sema-agent/client-core 0.93.1 → 0.94.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 +213 -0
- package/README.md +30 -19
- package/dist/adapt/arms.js +8 -5
- package/dist/adapter/downstream/terminalToSdkResult.js +2 -3
- package/dist/agentSession/engineSessionBackgroundStop.js +2 -1
- package/dist/agentsWireCaps.d.ts +15 -0
- package/dist/agentsWireCaps.js +111 -1
- package/dist/approvalReachWire.d.ts +18 -0
- package/dist/approvalReachWire.js +58 -0
- package/dist/approvalsStreamLiveCapability.js +16 -11
- package/dist/callerCwdCapability.d.ts +16 -0
- package/dist/callerCwdCapability.js +47 -0
- package/dist/controlRouter.js +3 -10
- package/dist/coreValuePorts.d.ts +64 -1
- package/dist/coreValuePorts.js +30 -0
- package/dist/detachWire.d.ts +1 -4
- package/dist/detachWire.js +1 -1
- package/dist/deviceExecutorManagementCapability.js +39 -34
- package/dist/deviceFlowPolling.d.ts +5 -0
- package/dist/deviceFlowPolling.js +28 -0
- package/dist/displayUntrusted.js +652 -21
- package/dist/engineErrorCodes.d.ts +5 -1
- package/dist/engineErrorCodes.js +9 -12
- package/dist/engineNoticeCodes.d.ts +22 -2
- package/dist/engineNoticeCodes.js +73 -6
- package/dist/engineRefusedToolName.d.ts +1 -0
- package/dist/engineRefusedToolName.js +8 -0
- package/dist/executionLaneCapability.js +14 -9
- package/dist/fileHistoryCaptureCapability.js +14 -9
- package/dist/fileHistoryLegNotice.d.ts +11 -0
- package/dist/fileHistoryLegNotice.js +50 -13
- package/dist/fleet/fleetLedger.js +5 -0
- package/dist/gateOutcome.d.ts +2 -1
- package/dist/gateOutcome.js +3 -0
- package/dist/gateVocabulary.js +1 -1
- package/dist/generated/engineAskFaceTables.d.ts +3 -0
- package/dist/generated/engineAskFaceTables.js +12 -0
- package/dist/generated/engineFactTables.d.ts +4 -2
- package/dist/generated/engineFactTables.js +13 -0
- package/dist/generated/engineNoticeTables.d.ts +5 -0
- package/dist/generated/engineNoticeTables.js +7 -0
- package/dist/generated/sdkFactTables.d.ts +3 -0
- package/dist/generated/sdkFactTables.js +17 -0
- package/dist/generated/settingsSchemaTables.d.ts +3 -0
- package/dist/generated/settingsSchemaTables.js +3 -0
- package/dist/headlessPermissionModeWire.d.ts +2 -2
- package/dist/headlessPermissionModeWire.js +11 -21
- package/dist/hitl/approvalHolds.d.ts +36 -0
- package/dist/hitl/approvalHolds.js +89 -0
- package/dist/hitl/approvalOutcomeNote.js +2 -1
- package/dist/hitl/approvalResolution.js +9 -1
- package/dist/hitl/approvalsFeed.js +5 -0
- package/dist/hitl/askGateWire.js +3 -0
- package/dist/hitl/automaticAnswerVerdict.d.ts +8 -2
- package/dist/hitl/automaticAnswerVerdict.js +69 -21
- package/dist/hitl/decisionRefusal.d.ts +19 -0
- package/dist/hitl/decisionRefusal.js +63 -0
- package/dist/hitl/frameRouter.d.ts +10 -0
- package/dist/hitl/frameRouter.js +59 -3
- package/dist/hitl/gateLedger.d.ts +3 -0
- package/dist/hitl/gateLedger.js +17 -0
- package/dist/hitl/hitlBridge.d.ts +2 -2
- package/dist/hitl/hitlBridge.js +8 -10
- package/dist/hitl/hitlHostSurface.d.ts +3 -0
- package/dist/hitl/hitlHostSurface.js +14 -4
- package/dist/hitl/localAllowRule.js +2 -1
- package/dist/hitl/noAllowCause.d.ts +9 -0
- package/dist/hitl/noAllowCause.js +26 -0
- package/dist/hitl/parkResolver.js +6 -3
- package/dist/hitl/parkRowBirthWait.js +2 -1
- package/dist/hitl/persistedRulesWire.d.ts +6 -1
- package/dist/hitl/persistedRulesWire.js +12 -4
- package/dist/hitl/planReviewWire.d.ts +0 -1
- package/dist/hitl/planReviewWire.js +30 -9
- package/dist/hitl/sessionPolicyDeliverable.d.ts +1 -1
- package/dist/hitl/sessionPolicyDeliverable.js +3 -8
- package/dist/hitl/sessionPolicyWire.js +1 -1
- package/dist/hitl/toolApprovalWire.d.ts +27 -2
- package/dist/hitl/toolApprovalWire.js +258 -23
- package/dist/humanTurnRead.d.ts +15 -1
- package/dist/humanTurnRead.js +28 -2
- package/dist/index.d.ts +18 -6
- package/dist/index.js +14 -5
- package/dist/limitsWire.d.ts +0 -1
- package/dist/limitsWire.js +2 -10
- package/dist/liveModelCatalog.d.ts +14 -0
- package/dist/liveModelCatalog.js +61 -0
- package/dist/mcpProbeCapability.js +14 -9
- package/dist/mcpReconnect.js +13 -8
- package/dist/memoryComplianceCapability.js +15 -10
- package/dist/memoryOriginCapability.js +15 -10
- package/dist/modelIdentityResolvability.d.ts +4 -2
- package/dist/modelIdentityResolvability.js +31 -4
- package/dist/outputStyleSeatCapability.d.ts +16 -0
- package/dist/outputStyleSeatCapability.js +47 -0
- package/dist/ownKey.d.ts +1 -0
- package/dist/ownKey.js +10 -0
- package/dist/peerLaneCapability.js +16 -11
- package/dist/permissionRuleIssue.js +1 -1
- package/dist/permissionRulesWriteCapability.js +16 -11
- package/dist/permissionsAllowGrantCapability.d.ts +2 -0
- package/dist/permissionsAllowGrantCapability.js +38 -16
- package/dist/permissionsDeclaration.d.ts +10 -1
- package/dist/permissionsDeclaration.js +97 -15
- package/dist/protocolSegment.d.ts +1 -0
- package/dist/protocolSegment.js +13 -1
- package/dist/request/taskRequest.d.ts +11 -3
- package/dist/request/taskRequest.js +13 -1
- package/dist/resultFrameKeys.d.ts +4 -0
- package/dist/resultFrameKeys.js +13 -0
- package/dist/resumeRefusalCopy.js +6 -16
- package/dist/retryStatus.js +6 -4
- package/dist/rewindArchiveCapability.js +13 -8
- package/dist/{hitl/rosterMountedNames.d.ts → rosterMountedNames.d.ts} +1 -1
- package/dist/runCancelContext.js +2 -1
- package/dist/sealedKeyCapability.js +19 -14
- package/dist/seatContract.d.ts +33 -5
- package/dist/seatContract.js +143 -2
- package/dist/sessionPolicyCapability.js +15 -10
- package/dist/sessionReadRedactionCapability.d.ts +16 -0
- package/dist/sessionReadRedactionCapability.js +47 -0
- package/dist/sessionSyncFileHistoryCapability.js +14 -9
- package/dist/skillToolListsCapability.js +14 -9
- package/dist/sqlEngineCapability.js +18 -13
- package/dist/steerPriorityCapability.js +21 -16
- package/dist/subagent/agentStateDetail.d.ts +1 -0
- package/dist/subagent/agentStateDetail.js +41 -0
- package/dist/subagent/engineCompactWire.d.ts +15 -0
- package/dist/subagent/engineCompactWire.js +75 -13
- package/dist/supportFloor.js +1 -1
- package/dist/taskRequestWords.d.ts +2 -4
- package/dist/taskRequestWords.js +3 -2
- package/dist/types/engineState.d.ts +1 -1
- package/dist/userAttachmentEnvelope.d.ts +22 -1
- package/dist/userAttachmentEnvelope.js +90 -2
- package/dist/userInvokedSkillsCapability.js +14 -9
- package/dist/webSearchBackendCapability.js +21 -16
- package/dist/wireErrorTriage.d.ts +11 -0
- package/dist/wireErrorTriage.js +35 -0
- package/dist/wireFailureShape.d.ts +6 -0
- package/dist/wireFailureShape.js +30 -3
- package/dist/wireRefusalCopy.d.ts +6 -1
- package/dist/wireRefusalCopy.js +22 -8
- package/dist/wiringInstructionsAttribution.d.ts +10 -0
- package/dist/wiringInstructionsAttribution.js +67 -0
- package/dist/workflowClient.js +22 -1
- package/dist/workflowsJournalCapability.js +14 -9
- package/dist/writeProtectionCapability.js +28 -23
- package/docs/INTEGRATION-CLIENTS.md +719 -45
- package/package.json +5 -5
- package/dist/askSurvivesStandingAnswer.d.ts +0 -13
- package/dist/askSurvivesStandingAnswer.js +0 -53
- package/dist/hitl/askSurvivesPosture.d.ts +0 -20
- package/dist/hitl/askSurvivesPosture.js +0 -48
- /package/dist/{hitl/rosterMountedNames.js → rosterMountedNames.js} +0 -0
package/dist/hitl/frameRouter.js
CHANGED
|
@@ -4,6 +4,8 @@ import { classifierDenyFromToolEnd } from '../classifierVerdictWire.js';
|
|
|
4
4
|
import { GATE_PARKED_ERROR_CODE } from '../engineErrorCodes.js';
|
|
5
5
|
import { readRunTerminal, runTerminalCode, runTerminalGateKind, runTerminalGateToolName } from '../runTerminal.js';
|
|
6
6
|
import { surfaceForCurrentSession, surfaceReplayedApprovalListUnreadable } from './hitlHostSurface.js';
|
|
7
|
+
import { liveCardHoldOf, supersedeLiveCard } from './approvalHolds.js';
|
|
8
|
+
import { liveFrameCallKey } from './gateIdentity.js';
|
|
7
9
|
import { SEMA_DENIAL_KIND_KEY, hasLedgerPositionKey } from './gateLedger.js';
|
|
8
10
|
import { replayedApprovalDisposition } from './replayedApproval.js';
|
|
9
11
|
import { settleWithinDeadline } from '../abortableSleep.js';
|
|
@@ -14,6 +16,38 @@ export const HITL_REJECT_MESSAGE = REJECT_MESSAGE;
|
|
|
14
16
|
export const HITL_POLICY_DENY_MESSAGE = 'Permission for this tool use was denied: it requires interactive approval, and permission prompts are ' +
|
|
15
17
|
'not available in this session. The action was NOT performed. Do not claim it succeeded, and do not retry ' +
|
|
16
18
|
'it in this session \u2014 report the limitation to the user, or suggest an alternative.';
|
|
19
|
+
const SENTENCE_TOOL_NAME = /^[A-Za-z0-9_][A-Za-z0-9_.:@-]{0,199}$/;
|
|
20
|
+
export function policyDenyReason(facts) {
|
|
21
|
+
const one = typeof facts.toolName === 'string' && SENTENCE_TOOL_NAME.test(facts.toolName) ? `this one "${facts.toolName}" call` : 'this one call';
|
|
22
|
+
const flag = typeof facts.allowRuleFlag === 'string' && SENTENCE_TOOL_NAME.test(facts.allowRuleFlag.replace(/^--?/, '')) ? ` (or ${facts.allowRuleFlag})` : '';
|
|
23
|
+
const budgetKnob = typeof facts.budgetSetting === 'string' && SENTENCE_TOOL_NAME.test(facts.budgetSetting) ? facts.budgetSetting : undefined;
|
|
24
|
+
const cause = facts.cause === 'no_allow_rule' || facts.cause === 'ask_rule' || facts.cause === 'budget' ? facts.cause : 'other';
|
|
25
|
+
if (facts.lane === 'dont_ask') {
|
|
26
|
+
const mode = 'denied by the dontAsk permission mode';
|
|
27
|
+
switch (cause) {
|
|
28
|
+
case 'no_allow_rule':
|
|
29
|
+
return `${mode}: ${one} needed approval, no permission rule allowed it, and this mode refuses anything that would ask — calls that need no approval still run; an allow rule in settings${flag} can approve calls like this ahead of time`;
|
|
30
|
+
case 'ask_rule':
|
|
31
|
+
return `${mode}: an ask rule matched ${one}, and this mode refuses anything that would ask (an ask rule outranks allow rules) — narrow or remove that ask rule, or use another permission mode to answer it`;
|
|
32
|
+
case 'budget':
|
|
33
|
+
return `${mode}: ${one} needed approval, and this session's automatic-approval budget is used up, so saved rules no longer approve calls${budgetKnob !== undefined ? ` — raise ${budgetKnob} before launch to change the cap` : ''}`;
|
|
34
|
+
default:
|
|
35
|
+
return `${mode}: ${one} needed a person's approval that saved permission rules cannot approve here (the engine or your deployment requires a person for it, or it could not be checked against your rules), and this mode refuses anything that would ask — use another permission mode to answer it`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const lane = 'no approval UI on this lane';
|
|
39
|
+
const tail = 'nobody can be asked here, so only this call was refused — calls that need no approval still run';
|
|
40
|
+
switch (cause) {
|
|
41
|
+
case 'no_allow_rule':
|
|
42
|
+
return `${lane}: ${one} needed a person's approval, no permission rule answered it, and ${tail}; a matching allow rule in settings${flag} can approve calls like this ahead of time`;
|
|
43
|
+
case 'ask_rule':
|
|
44
|
+
return `${lane}: an ask rule matched ${one}, so it needed a person's answer, and ${tail}; an ask rule outranks allow rules, so narrow or remove that ask rule, or run the task interactively to answer it`;
|
|
45
|
+
case 'budget':
|
|
46
|
+
return `${lane}: ${one} needed a person's approval, and this session's automatic-approval budget is used up, so saved rules no longer approve calls; ${tail}${budgetKnob !== undefined ? `; raise ${budgetKnob} before launch to change the cap` : ''}`;
|
|
47
|
+
default:
|
|
48
|
+
return `${lane}: ${one} needed a person's approval, and saved permission rules cannot approve it here (the engine or your deployment requires a person for it, or it could not be checked against your rules); ${tail}; run the task interactively to answer it`;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
17
51
|
function denyOutputForRender(attribution) {
|
|
18
52
|
if (attribution?.settledBy !== 'policy')
|
|
19
53
|
return HITL_REJECT_MESSAGE;
|
|
@@ -232,10 +266,24 @@ const TOOL_END_ARMS = [
|
|
|
232
266
|
'改顺序必须同批改 TOOL_END_ARM_ORDER 并说明为什么新顺序仍然对。');
|
|
233
267
|
}
|
|
234
268
|
}
|
|
269
|
+
function completeFrameParked(ev) {
|
|
270
|
+
try {
|
|
271
|
+
const d = Object.getOwnPropertyDescriptor(ev, 'parked');
|
|
272
|
+
return d !== undefined && 'value' in d && d.value === true;
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
235
278
|
async function routeToolApprovalFrame(ev, ctx) {
|
|
236
279
|
const { led, deps } = ctx;
|
|
237
|
-
if (ev.type === 'tool_approval_complete')
|
|
280
|
+
if (ev.type === 'tool_approval_complete') {
|
|
281
|
+
const id = ev.approvalId;
|
|
282
|
+
if (typeof id === 'string' && id !== '' && supersedeLiveCard(liveFrameCallKey(id), completeFrameParked(ev))) {
|
|
283
|
+
hostLog('debug', `liveHitlAskWire: tool_approval_complete for ${id} arrived while its card was waiting — card withdrawn (nothing will be sent from it)`);
|
|
284
|
+
}
|
|
238
285
|
return { kind: 'skip' };
|
|
286
|
+
}
|
|
239
287
|
if (!deps.respondToolApproval) {
|
|
240
288
|
hostLog('debug', `liveHitlAskWire: tool_approval frame ${ev.approvalId} but no respond wire — engine self-settles (TTL deny)`);
|
|
241
289
|
return { kind: 'skip' };
|
|
@@ -269,7 +317,16 @@ async function routeToolApprovalFrame(ev, ctx) {
|
|
|
269
317
|
const frameCallId = !fromSubagent && typeof ev.toolCallId === 'string' && ev.toolCallId !== '' ? ev.toolCallId : undefined;
|
|
270
318
|
const gatedCallId = fromSubagent ? undefined : frameCallId ?? led.lastPendingFsCall();
|
|
271
319
|
const argsCallId = frameCallId === undefined ? gatedCallId : typeof ev.toolName === 'string' && toolNameIsFsWrite(ev.toolName) ? frameCallId : undefined;
|
|
272
|
-
const
|
|
320
|
+
const launched = surfaceToolApprovalFrameAndRespond(ev, deps.respondToolApproval, argsOfGatedStart(led, argsCallId), ctx.signal, deps.approvalLane);
|
|
321
|
+
const hold = liveCardHoldOf(liveFrameCallKey(ev.approvalId));
|
|
322
|
+
const handled = launched.then((outcome) => settleLiveCardOutcome(ev, outcome, ctx, fromSubagent, gatedCallId), (e) => {
|
|
323
|
+
hostLog('debug', `liveHitlAskWire: tool_approval card for ${ev.approvalId} failed: ${String(e).slice(0, 160)}`);
|
|
324
|
+
});
|
|
325
|
+
led.trackLiveCard(() => hold?.phase ?? 'card', handled);
|
|
326
|
+
return { kind: 'skip' };
|
|
327
|
+
}
|
|
328
|
+
function settleLiveCardOutcome(ev, outcome, ctx, fromSubagent, gatedCallId) {
|
|
329
|
+
const { led, deps } = ctx;
|
|
273
330
|
const { decision } = outcome;
|
|
274
331
|
if (deps.onToolApprovalOutcome !== undefined) {
|
|
275
332
|
try {
|
|
@@ -294,7 +351,6 @@ async function routeToolApprovalFrame(ev, ctx) {
|
|
|
294
351
|
else
|
|
295
352
|
led.armDenyStamp(attribution);
|
|
296
353
|
}
|
|
297
|
-
return { kind: 'skip' };
|
|
298
354
|
}
|
|
299
355
|
const REPLAYED_APPROVAL_LIST_DEADLINE_MS = 3000;
|
|
300
356
|
async function judgeLedgerApprovalFrame(ev, ctx) {
|
|
@@ -23,6 +23,9 @@ export interface GateLedger {
|
|
|
23
23
|
notePreambleApproval(approvalId: string): void;
|
|
24
24
|
preambleApprovals(): ReadonlySet<string>;
|
|
25
25
|
beginStreamSegment(): void;
|
|
26
|
+
trackLiveCard(phaseOf: () => 'card' | 'respond', handled: Promise<void>): void;
|
|
27
|
+
answeringLiveCards(): Promise<void>[];
|
|
28
|
+
liveCardsSettled(): Promise<void>;
|
|
26
29
|
markStarted(callId: string): boolean;
|
|
27
30
|
noteGatedStart(callId: string, ev: AgentEvent): void;
|
|
28
31
|
gatedStartArgs(): ReadonlyMap<string, unknown>;
|
package/dist/hitl/gateLedger.js
CHANGED
|
@@ -88,6 +88,7 @@ export function createGateLedger() {
|
|
|
88
88
|
let denyStampAttribution;
|
|
89
89
|
let seq;
|
|
90
90
|
const preambleApprovalIds = new Set();
|
|
91
|
+
const liveCards = new Set();
|
|
91
92
|
return {
|
|
92
93
|
noteSeq(ev) {
|
|
93
94
|
const s = durableSeqOf(ev);
|
|
@@ -120,6 +121,22 @@ export function createGateLedger() {
|
|
|
120
121
|
beginStreamSegment() {
|
|
121
122
|
preambleApprovalIds.clear();
|
|
122
123
|
},
|
|
124
|
+
trackLiveCard(phaseOf, handled) {
|
|
125
|
+
const entry = { phaseOf, handled };
|
|
126
|
+
liveCards.add(entry);
|
|
127
|
+
void handled.then(() => { liveCards.delete(entry); }, () => { liveCards.delete(entry); });
|
|
128
|
+
},
|
|
129
|
+
answeringLiveCards() {
|
|
130
|
+
const out = [];
|
|
131
|
+
for (const e of liveCards)
|
|
132
|
+
if (e.phaseOf() === 'respond')
|
|
133
|
+
out.push(e.handled);
|
|
134
|
+
return out;
|
|
135
|
+
},
|
|
136
|
+
async liveCardsSettled() {
|
|
137
|
+
while (liveCards.size > 0)
|
|
138
|
+
await Promise.allSettled([...liveCards].map((e) => e.handled));
|
|
139
|
+
},
|
|
123
140
|
markStarted(callId) {
|
|
124
141
|
if (startedCalls.has(callId))
|
|
125
142
|
return false;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentEvent, ApprovalDecision, ApprovalStaleError, PendingCheckpoint, CheckpointGate, PlanReviewRequest, AssistantTaskStatus } from '@sema-agent/sdk';
|
|
1
|
+
import type { AgentEvent, ApprovalDecidedBy, ApprovalDecision, ApprovalStaleError, PendingCheckpoint, CheckpointGate, PlanReviewRequest, AssistantTaskStatus } from '@sema-agent/sdk';
|
|
2
2
|
import type { PermissionResult } from '@sema-agent/agent-types';
|
|
3
3
|
import type { ApprovalsListEnvelope } from './crashConverged.js';
|
|
4
4
|
import { type SuspendedReopenReading } from './suspendedReopen.js';
|
|
@@ -56,7 +56,7 @@ export interface HitlDecideDenyOutcome {
|
|
|
56
56
|
decision: 'deny';
|
|
57
57
|
reason?: string | undefined;
|
|
58
58
|
settledBy?: 'policy' | undefined;
|
|
59
|
-
decidedBy?: 'policy' | undefined;
|
|
59
|
+
decidedBy?: Extract<ApprovalDecidedBy, 'policy'> | undefined;
|
|
60
60
|
}
|
|
61
61
|
export declare class HitlSafetyError extends Error {
|
|
62
62
|
readonly code: HitlSafetyCode;
|
package/dist/hitl/hitlBridge.js
CHANGED
|
@@ -3,6 +3,7 @@ import { eventSeq } from '../adapter/types.js';
|
|
|
3
3
|
import { hostLog } from '../host.js';
|
|
4
4
|
import { abortableSleep } from '../abortableSleep.js';
|
|
5
5
|
import { readOwnDataValue } from '../ownKey.js';
|
|
6
|
+
import { wireFailureTextsOf } from '../wireFailureShape.js';
|
|
6
7
|
import { suspendedReopenOf } from './suspendedReopen.js';
|
|
7
8
|
export const DEFAULT_DENY_REASON = 'The user rejected this tool use';
|
|
8
9
|
const MAX_DENY_REASON_CHARS = 4096;
|
|
@@ -208,7 +209,9 @@ export async function findPendingForTask(client, taskId, matches, opts, callId,
|
|
|
208
209
|
rows = (await client.approvals.list(opts)).pending;
|
|
209
210
|
}
|
|
210
211
|
catch (e) {
|
|
211
|
-
|
|
212
|
+
const t = wireFailureTextsOf(e);
|
|
213
|
+
hostLog('debug', `hitlBridge: approvals.list failed: ${t.log}`);
|
|
214
|
+
return { ok: false, reason: `approvals.list failed: ${t.reason}` };
|
|
212
215
|
}
|
|
213
216
|
const decidable = rows.filter((r) => askParkForeignGateKind(r) === null);
|
|
214
217
|
const identityRow = callId !== undefined
|
|
@@ -327,13 +330,8 @@ export class HitlBridge {
|
|
|
327
330
|
throw new HitlSafetyError(`plan-review "${outcome.decision}" must NOT carry editedPlan`, 'bad_plan_edit');
|
|
328
331
|
}
|
|
329
332
|
const modeAfter = outcome.permissionModeAfter;
|
|
330
|
-
if (modeAfter !== undefined) {
|
|
331
|
-
|
|
332
|
-
throw new HitlSafetyError(`plan-review "${outcome.decision}" must NOT carry permissionModeAfter (it only applies to an approval)`, 'bad_plan_mode');
|
|
333
|
-
}
|
|
334
|
-
if (!isPlanReviewModeAfter(modeAfter)) {
|
|
335
|
-
throw new HitlSafetyError('plan-review permissionModeAfter must be "default" or "acceptEdits"', 'bad_plan_mode');
|
|
336
|
-
}
|
|
333
|
+
if (modeAfter !== undefined && !isPlanReviewModeAfter(modeAfter)) {
|
|
334
|
+
throw new HitlSafetyError('plan-review permissionModeAfter must be "default" or "acceptEdits"', 'bad_plan_mode');
|
|
337
335
|
}
|
|
338
336
|
const req = { decision: outcome.decision };
|
|
339
337
|
if (modeAfter !== undefined && isPlanReviewModeAfter(modeAfter))
|
|
@@ -416,11 +414,11 @@ export class HitlBridge {
|
|
|
416
414
|
throw e;
|
|
417
415
|
if (isDecideAttemptTimeout(e)) {
|
|
418
416
|
if (attempts >= DECIDE_TIMEOUT_MIN_ATTEMPTS && Date.now() - startedAt >= decideTimeoutRetryBudgetMs()) {
|
|
419
|
-
throw new DecideTransportRetryExhaustedError(attempts,
|
|
417
|
+
throw new DecideTransportRetryExhaustedError(attempts, wireFailureTextsOf(e).reason);
|
|
420
418
|
}
|
|
421
419
|
}
|
|
422
420
|
else if (netRetriesUsed >= 1) {
|
|
423
|
-
throw new DecideTransportRetryExhaustedError(attempts,
|
|
421
|
+
throw new DecideTransportRetryExhaustedError(attempts, wireFailureTextsOf(e).reason);
|
|
424
422
|
}
|
|
425
423
|
else {
|
|
426
424
|
netRetriesUsed++;
|
|
@@ -23,6 +23,7 @@ export declare function hitlHostSurfaceFor(sessionKey: string): HitlHostSurface
|
|
|
23
23
|
export declare function _resetHitlHostSurfaceForTest(): void;
|
|
24
24
|
export declare function surfaceForCurrentSession(): HitlHostSurface | null;
|
|
25
25
|
export declare const CANCEL_DENY_BUDGET_MS = 2000;
|
|
26
|
+
export declare const INTERRUPT_DENY_REASON = "Interrupted by user";
|
|
26
27
|
export declare const CANCEL_DENY_WARN_TEXT = "could not cancel the pending question \u2014 the session may stay locked; the run may need engine-side recovery";
|
|
27
28
|
export declare const REMEMBER_NOT_APPLIED_WARN_TEXT = "this session-wide allow was not remembered by the worker \u2014 you will be asked again for this tool";
|
|
28
29
|
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";
|
|
@@ -31,6 +32,8 @@ export declare function surfaceEditNotForwarded(): void;
|
|
|
31
32
|
export declare const APPROVAL_PARKING_WARN_TEXT = "This approval is being parked; answer it again on the parked card when it appears.";
|
|
32
33
|
export declare function approvalParkingNoticeText(retryAfterMs?: number): string;
|
|
33
34
|
export declare function surfaceApprovalParking(retryAfterMs?: number): void;
|
|
35
|
+
export declare const COMPACT_NOT_RUNNING_WARN_TEXT = "engine run finished before compaction was applied; nothing was compacted";
|
|
36
|
+
export declare function surfaceCompactNotRunning(sessionKey?: string): void;
|
|
34
37
|
export declare const REPLAYED_APPROVAL_LIST_UNREADABLE_WARN_TEXT: string;
|
|
35
38
|
export declare function surfaceReplayedApprovalListUnreadable(): void;
|
|
36
39
|
export declare const EDIT_REFUSED_ON_BLIND_ASK_WARN_TEXT: string;
|
|
@@ -29,22 +29,26 @@ export function _resetHitlHostSurfaceForTest() {
|
|
|
29
29
|
hostSurfaceMissesByKey.clear();
|
|
30
30
|
}
|
|
31
31
|
export function surfaceForCurrentSession() {
|
|
32
|
-
|
|
32
|
+
return surfaceForSession(DEFAULT_SESSION_KEY);
|
|
33
|
+
}
|
|
34
|
+
function surfaceForSession(sessionKey) {
|
|
35
|
+
const hostSurface = hitlHostSurfaceFor(sessionKey);
|
|
33
36
|
if (hostSurface === null) {
|
|
34
|
-
hostSurfaceMissesByKey.set(
|
|
37
|
+
hostSurfaceMissesByKey.set(sessionKey, (hostSurfaceMissesByKey.get(sessionKey) ?? 0) + 1);
|
|
35
38
|
}
|
|
36
39
|
return hostSurface;
|
|
37
40
|
}
|
|
38
41
|
export const CANCEL_DENY_BUDGET_MS = 2000;
|
|
42
|
+
export const INTERRUPT_DENY_REASON = 'Interrupted by user';
|
|
39
43
|
export const CANCEL_DENY_WARN_TEXT = 'could not cancel the pending question — the session may stay locked; the run may need engine-side recovery';
|
|
40
44
|
const CANCEL_DENY_WARN_KEY = 'hitl-cancel-deny-warn';
|
|
41
45
|
const CANCEL_DENY_WARN_TIMEOUT_MS = 10_000;
|
|
42
46
|
function surfaceCancelDenyWarn(text = CANCEL_DENY_WARN_TEXT) {
|
|
43
47
|
surfaceSelfClearingWarn(CANCEL_DENY_WARN_KEY, text, CANCEL_DENY_WARN_TIMEOUT_MS);
|
|
44
48
|
}
|
|
45
|
-
function surfaceSelfClearingWarn(key, text, timeoutMs) {
|
|
49
|
+
function surfaceSelfClearingWarn(key, text, timeoutMs, sessionKey = DEFAULT_SESSION_KEY) {
|
|
46
50
|
try {
|
|
47
|
-
const s =
|
|
51
|
+
const s = surfaceForSession(sessionKey);
|
|
48
52
|
if (!s)
|
|
49
53
|
return;
|
|
50
54
|
s.showNotice({ key, text, color: 'warning', priority: 'immediate', timeoutMs });
|
|
@@ -83,6 +87,12 @@ export function approvalParkingNoticeText(retryAfterMs) {
|
|
|
83
87
|
export function surfaceApprovalParking(retryAfterMs) {
|
|
84
88
|
surfaceSelfClearingWarn(APPROVAL_PARKING_KEY, approvalParkingNoticeText(retryAfterMs), APPROVAL_PARKING_TIMEOUT_MS);
|
|
85
89
|
}
|
|
90
|
+
export const COMPACT_NOT_RUNNING_WARN_TEXT = 'engine run finished before compaction was applied; nothing was compacted';
|
|
91
|
+
const COMPACT_NOT_RUNNING_KEY = 'engine-compact-not-running';
|
|
92
|
+
const COMPACT_NOT_RUNNING_TIMEOUT_MS = 10_000;
|
|
93
|
+
export function surfaceCompactNotRunning(sessionKey = DEFAULT_SESSION_KEY) {
|
|
94
|
+
surfaceSelfClearingWarn(COMPACT_NOT_RUNNING_KEY, COMPACT_NOT_RUNNING_WARN_TEXT, COMPACT_NOT_RUNNING_TIMEOUT_MS, sessionKey);
|
|
95
|
+
}
|
|
86
96
|
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, " +
|
|
87
97
|
'and no decision was sent for it.';
|
|
88
98
|
const REPLAYED_APPROVAL_LIST_UNREADABLE_KEY = 'hitl-replayed-approval-list-unreadable';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { wireFailureTextsOf } from '../wireFailureShape.js';
|
|
1
2
|
const BASH_TOOL_NAME_FOR_RULES = 'Bash';
|
|
2
3
|
export function parseLocalAllowRule(rule, expectedToolName, deps) {
|
|
3
4
|
if (typeof rule !== 'string' || rule === '') {
|
|
@@ -8,7 +9,7 @@ export function parseLocalAllowRule(rule, expectedToolName, deps) {
|
|
|
8
9
|
value = deps.parseRule(rule);
|
|
9
10
|
}
|
|
10
11
|
catch (e) {
|
|
11
|
-
return { ok: false, error: `rule text did not parse (${
|
|
12
|
+
return { ok: false, error: `rule text did not parse (${wireFailureTextsOf(e).reason})` };
|
|
12
13
|
}
|
|
13
14
|
if (typeof value.toolName !== 'string' || value.toolName === '') {
|
|
14
15
|
return { ok: false, error: 'rule text carries no tool name' };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type NoAllowCause = 'no_allow_rule' | 'ask_rule' | 'budget' | 'other';
|
|
2
|
+
export interface RuleLadderNoAllowFacts {
|
|
3
|
+
readonly allowed?: boolean;
|
|
4
|
+
readonly denied?: boolean;
|
|
5
|
+
readonly askRuleMatched?: boolean;
|
|
6
|
+
readonly budgetExhausted?: boolean;
|
|
7
|
+
readonly noRuleMatched?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function noAllowCauseOf(card: object | null | undefined, ladder: RuleLadderNoAllowFacts | null | undefined): NoAllowCause | undefined;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { automaticAnswerVerdict } from './automaticAnswerVerdict.js';
|
|
2
|
+
function ladderSays(ladder, key) {
|
|
3
|
+
if (typeof ladder !== 'object' || ladder === null)
|
|
4
|
+
return false;
|
|
5
|
+
try {
|
|
6
|
+
const d = Object.getOwnPropertyDescriptor(ladder, key);
|
|
7
|
+
return d !== undefined && 'value' in d && d.value === true;
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function noAllowCauseOf(card, ladder) {
|
|
14
|
+
if (ladderSays(ladder, 'allowed') || ladderSays(ladder, 'denied'))
|
|
15
|
+
return undefined;
|
|
16
|
+
if (ladderSays(ladder, 'askRuleMatched'))
|
|
17
|
+
return 'ask_rule';
|
|
18
|
+
if (ladderSays(ladder, 'budgetExhausted'))
|
|
19
|
+
return 'budget';
|
|
20
|
+
const standing = automaticAnswerVerdict(card, 'standing_rule');
|
|
21
|
+
if (standing.kind === 'must_ask' && (standing.askClass === 'ask_rule' || standing.origin === 'ask_rule'))
|
|
22
|
+
return 'ask_rule';
|
|
23
|
+
if (ladderSays(ladder, 'noRuleMatched') && standing.kind === 'may_answer')
|
|
24
|
+
return 'no_allow_rule';
|
|
25
|
+
return 'other';
|
|
26
|
+
}
|
|
@@ -4,11 +4,12 @@ import { ASK_GONE, CHECKPOINT_UNSUPPORTED_VERSION, DECIDE_WORKFLOW_PARK_FORKED,
|
|
|
4
4
|
import { publishQuestionFrame, registerLocalQuestionResponder, hasQuestionOverlay, } from '../liveQuestionStore.js';
|
|
5
5
|
import { hostLog } from '../host.js';
|
|
6
6
|
import { surfaceFsApprovalAndDecide } from './toolApprovalWire.js';
|
|
7
|
-
import { observeCancelByDeny } from './hitlHostSurface.js';
|
|
7
|
+
import { INTERRUPT_DENY_REASON, observeCancelByDeny } from './hitlHostSurface.js';
|
|
8
8
|
import { flushHeldWithInterruptRewrite, isAskTool } from './frameRouter.js';
|
|
9
9
|
import { approvalCallKey, askGateQuestionId } from './gateIdentity.js';
|
|
10
10
|
import { readDecideReceipt } from '../decideReceipt.js';
|
|
11
11
|
import { decideFailureNote } from '../decideFailureNote.js';
|
|
12
|
+
import { wireFailureTextsOf } from '../wireFailureShape.js';
|
|
12
13
|
export const MAX_GATE_HOPS = 2;
|
|
13
14
|
export function nextHopBudget(prev, round) {
|
|
14
15
|
const total = (prev?.total ?? 0) + 1;
|
|
@@ -139,7 +140,7 @@ async function surfaceGateAndDecide(deps, taskId, askArgsByCall, signal, parkGat
|
|
|
139
140
|
});
|
|
140
141
|
const bridge = new HitlBridge(deps.client, taskId, deps.onDecideOperation ? { onDecideOperation: deps.onDecideOperation } : undefined);
|
|
141
142
|
if (answer === null) {
|
|
142
|
-
observeCancelByDeny(bridge.decideTool({ decision: 'deny', reason:
|
|
143
|
+
observeCancelByDeny(bridge.decideTool({ decision: 'deny', reason: INTERRUPT_DENY_REASON }, gatedCallId, undefined, pending), taskId);
|
|
143
144
|
return { kind: 'aborted', gatedCallId };
|
|
144
145
|
}
|
|
145
146
|
let declinedReceipt;
|
|
@@ -170,11 +171,13 @@ async function surfaceGateAndDecide(deps, taskId, askArgsByCall, signal, parkGat
|
|
|
170
171
|
const currentPending = readDecideCurrentPending(e);
|
|
171
172
|
const wireCode = readWireErrorCode(e);
|
|
172
173
|
const wireRetriable = typeof e === 'object' && e !== null && refusalSaysStillPending(e);
|
|
174
|
+
const failureTexts = wireFailureTextsOf(e);
|
|
175
|
+
hostLog('debug', `liveHitlAskWire: decide failed: ${failureTexts.log}`);
|
|
173
176
|
return {
|
|
174
177
|
kind: 'failed',
|
|
175
178
|
stage: 'decide',
|
|
176
179
|
gatedCallId,
|
|
177
|
-
reason: `decide failed: ${
|
|
180
|
+
reason: `decide failed: ${failureTexts.reason}${decideFailureNote(e, attemptedDecision)}`,
|
|
178
181
|
...(code !== undefined ? { code } : {}),
|
|
179
182
|
...(e instanceof DecideTransportRetryExhaustedError ? { retryExhausted: true } : {}),
|
|
180
183
|
...(currentPending !== undefined ? { currentPending } : {}),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { abortableSleep } from '../abortableSleep.js';
|
|
2
|
+
import { wireFailureTextsOf } from '../wireFailureShape.js';
|
|
2
3
|
function parkRowPollDelay(ms, signal) {
|
|
3
4
|
return abortableSleep(ms, signal ?? new AbortController().signal);
|
|
4
5
|
}
|
|
@@ -16,7 +17,7 @@ export async function waitForParkRowBirth(deps) {
|
|
|
16
17
|
return await deps.probe(attempt, signal);
|
|
17
18
|
}
|
|
18
19
|
catch (e) {
|
|
19
|
-
return { kind: 'unborn', reason: `probe threw: ${
|
|
20
|
+
return { kind: 'unborn', reason: `probe threw: ${wireFailureTextsOf(e).reason}` };
|
|
20
21
|
}
|
|
21
22
|
};
|
|
22
23
|
const probeWithinRemaining = async (attempt, remainingMs) => {
|
|
@@ -44,6 +44,9 @@ export type RulesFailure = {
|
|
|
44
44
|
} | {
|
|
45
45
|
kind: 'cursor-stale';
|
|
46
46
|
message: string;
|
|
47
|
+
} | {
|
|
48
|
+
kind: 'query-refused';
|
|
49
|
+
message: string;
|
|
47
50
|
} | {
|
|
48
51
|
kind: 'too-many-candidates';
|
|
49
52
|
message: string;
|
|
@@ -78,7 +81,9 @@ export type RulesFailure = {
|
|
|
78
81
|
kind: 'error';
|
|
79
82
|
message: string;
|
|
80
83
|
};
|
|
81
|
-
export declare function classifyRulesFailure(e: unknown
|
|
84
|
+
export declare function classifyRulesFailure(e: unknown, context?: {
|
|
85
|
+
readonly cursorSent?: boolean;
|
|
86
|
+
}): RulesFailure;
|
|
82
87
|
export declare const PERSISTED_RULE_BEHAVIORS: readonly RuleBehavior[];
|
|
83
88
|
export declare function persistedRuleBehaviorOf(rule: unknown): RuleBehavior | undefined;
|
|
84
89
|
export declare const PERSISTED_RULE_BEHAVIOR_UNKNOWN = "(unknown)";
|
|
@@ -36,7 +36,7 @@ export function readCcImportRedeemCounts(result) {
|
|
|
36
36
|
}
|
|
37
37
|
return null;
|
|
38
38
|
}
|
|
39
|
-
export function classifyRulesFailure(e) {
|
|
39
|
+
export function classifyRulesFailure(e, context) {
|
|
40
40
|
const { status, errorCode, message, retryAfterSec, committed } = wireFailureShapeOf(e);
|
|
41
41
|
if (errorCode === 'capability.rule_store_required') {
|
|
42
42
|
return { kind: 'lane-unavailable', message };
|
|
@@ -58,8 +58,16 @@ export function classifyRulesFailure(e) {
|
|
|
58
58
|
if (errorCode === 'state.rule_write_failed') {
|
|
59
59
|
return committed === 'no' ? { kind: 'write-not-committed', message } : { kind: 'write-indeterminate', message };
|
|
60
60
|
}
|
|
61
|
-
if (status === 400 && errorCode === 'request.query_invalid')
|
|
62
|
-
|
|
61
|
+
if (status === 400 && errorCode === 'request.query_invalid') {
|
|
62
|
+
let cursorSent;
|
|
63
|
+
try {
|
|
64
|
+
cursorSent = context?.cursorSent;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
cursorSent = undefined;
|
|
68
|
+
}
|
|
69
|
+
return cursorSent === false ? { kind: 'query-refused', message } : { kind: 'cursor-stale', message };
|
|
70
|
+
}
|
|
63
71
|
if (status === 400 && errorCode === 'request.field_invalid')
|
|
64
72
|
return { kind: 'field-refused', message };
|
|
65
73
|
if (status === 400 && errorCode === 'request.body_shape')
|
|
@@ -144,7 +152,7 @@ export async function listAllPersistedRules(facade, params = {}, opts) {
|
|
|
144
152
|
res = await facade.list({ limit: PAGE_LIMIT, ...cleanParams, ...(cursor !== undefined ? { cursor } : {}) }, opts);
|
|
145
153
|
}
|
|
146
154
|
catch (e) {
|
|
147
|
-
const failure = classifyRulesFailure(e);
|
|
155
|
+
const failure = classifyRulesFailure(e, { cursorSent: cursor !== undefined });
|
|
148
156
|
if (failure.kind === 'cursor-stale' && cursor !== undefined && attempt === 0) {
|
|
149
157
|
hostLog('debug', 'persistedRulesWire: rule list cursor was minted on another rev/principal/scope — dropping it and re-listing from the top (never resuming a stale keyset)');
|
|
150
158
|
restart = true;
|
|
@@ -6,7 +6,8 @@ import { engineWireTarget } from '../engineWireTarget.js';
|
|
|
6
6
|
import { enqueuePlanReviewOutcome } from '../notifications.js';
|
|
7
7
|
import { corruptStoredRowContent, corruptStoredRowWhere } from '../wireErrorTriage.js';
|
|
8
8
|
import { decideFailureNote } from '../decideFailureNote.js';
|
|
9
|
-
import {
|
|
9
|
+
import { wireFailureTextsOf } from '../wireFailureShape.js';
|
|
10
|
+
import { checkpointUnsupportedVersionContent, checkpointUnsupportedVersionFromError, resumeContextUnavailableContent, resumeContextUnavailableFromError } from '../resumeRefusalCopy.js';
|
|
10
11
|
import { REQUEST_FIELD_CONFLICT, isUnavailableFaceCode } from '../engineErrorCodes.js';
|
|
11
12
|
import { denyReasonForWire, isPlanReviewModeAfter } from './hitlBridge.js';
|
|
12
13
|
import { planReviewQuestionId, REOPEN_ID_TAIL, taskIdFromPlanReviewQuestionId } from './gateIdentity.js';
|
|
@@ -309,6 +310,15 @@ export function planReviewOutcomeTail(taskId, decision, effect, cause) {
|
|
|
309
310
|
return `Do not state the result as fact: tell the user the ${decision} was submitted but its effect could not be confirmed; TaskOutput("${taskId}") shows where the task stands.`;
|
|
310
311
|
}
|
|
311
312
|
}
|
|
313
|
+
function clientOnlyView(wire) {
|
|
314
|
+
if (wire === undefined || wire === null || typeof wire !== 'object')
|
|
315
|
+
return wire;
|
|
316
|
+
return {
|
|
317
|
+
get client() {
|
|
318
|
+
return wire.client;
|
|
319
|
+
},
|
|
320
|
+
};
|
|
321
|
+
}
|
|
312
322
|
function planReviewDecisionTimedOutOutcome(decision) {
|
|
313
323
|
return (`The plan_review ${decision} was sent, but no answer came back from the engine within the time limit this host allows for it — ` +
|
|
314
324
|
`it may have taken effect (${decision === 'approve' ? 'an approval keeps the engine busy until the resumed work finishes' : 'the engine may still be applying it'}).`);
|
|
@@ -492,12 +502,12 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
492
502
|
let stillParkedProven = false;
|
|
493
503
|
let statusSeen;
|
|
494
504
|
const wire = opts?.wire;
|
|
495
|
-
const resolved = engineWireFor(wire, DEFAULT_SESSION_KEY, usablePlanReviewWireClient);
|
|
496
|
-
const badMode = permissionModeAfter !== undefined &&
|
|
505
|
+
const resolved = engineWireFor(clientOnlyView(wire), DEFAULT_SESSION_KEY, usablePlanReviewWireClient);
|
|
506
|
+
const badMode = permissionModeAfter !== undefined && !isPlanReviewModeAfter(permissionModeAfter);
|
|
497
507
|
if (badMode) {
|
|
498
|
-
hostLog('error', `planReviewWire: ${decision} NOT sent — permissionModeAfter
|
|
508
|
+
hostLog('error', `planReviewWire: ${decision} NOT sent — permissionModeAfter must be "default" or "acceptEdits"`);
|
|
499
509
|
effect = 'not_sent';
|
|
500
|
-
outcome = `The plan_review ${decision} could NOT be sent: the post-approval permission mode given with it is not valid
|
|
510
|
+
outcome = `The plan_review ${decision} could NOT be sent: the post-approval permission mode given with it is not valid (it must be "default" or "acceptEdits"). Tell the user plainly that the decision did not go through; the plan is still waiting.`;
|
|
501
511
|
}
|
|
502
512
|
else if (resolved.kind === 'no-target') {
|
|
503
513
|
hostLog('error', `planReviewWire: ${decision} NOT sent — engineWireTarget() unavailable`);
|
|
@@ -600,12 +610,15 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
600
610
|
const refusedCode = e?.errorCode;
|
|
601
611
|
const noAnswer = typeof status === 'number' || typeof e !== 'object' ? null : noAnswerKindOf(e);
|
|
602
612
|
if (status === 400 && refusedCode === REQUEST_FIELD_CONFLICT && permissionModeAfter === 'acceptEdits') {
|
|
603
|
-
hostLog('debug',
|
|
613
|
+
hostLog('debug', `planReviewWire: ${decision} + acceptEdits refused (request.field_conflict) — NOT re-sent with a different mode`);
|
|
604
614
|
effect = 'still_parked';
|
|
605
615
|
stillParkedProven = true;
|
|
606
616
|
outcome =
|
|
607
|
-
|
|
608
|
-
'
|
|
617
|
+
decision === 'approve'
|
|
618
|
+
? 'The plan approval was NOT applied: the engine refused "auto-accept edits" for this task (it was not started read-only in plan mode, so that option does not apply). ' +
|
|
619
|
+
'Nothing changed engine-side — the plan is still waiting for review. Tell the user plainly, and that approving with "manually approve edits" will go through.'
|
|
620
|
+
: `The plan ${decision} was NOT applied: the engine refused the post-approval permission mode that was sent with it (request.field_conflict). ` +
|
|
621
|
+
'Nothing changed engine-side — the plan is still waiting for review. Tell the user plainly that the decision did not go through.';
|
|
609
622
|
}
|
|
610
623
|
else if (status === 404 && refusedCode === 'not_found.plan_review') {
|
|
611
624
|
hostLog('debug', `planReviewWire: ${decision} → 404 not_found.plan_review (no pending plan review any more — an earlier decision likely settled it)`);
|
|
@@ -622,6 +635,12 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
622
635
|
stillParkedCause = 'stored_form_unsupported';
|
|
623
636
|
outcome = `This ${decision} was not applied. ${checkpointUnsupportedVersionContent(cu, { parkKind: 'plan_review', runId: taskId })}`;
|
|
624
637
|
}
|
|
638
|
+
else if (resumeContextUnavailableFromError(e) !== null) {
|
|
639
|
+
const ru = resumeContextUnavailableFromError(e);
|
|
640
|
+
hostLog('debug', `planReviewWire: ${decision} → 409 conflict.resume_context_unavailable (the session context needed to continue this run is not available)`);
|
|
641
|
+
effect = 'not_applied';
|
|
642
|
+
outcome = `This ${decision} was not applied. ${resumeContextUnavailableContent(ru)}`;
|
|
643
|
+
}
|
|
625
644
|
else if (typeof status === 'number' && corruptStoredRowWhere(e) !== undefined) {
|
|
626
645
|
const where = corruptStoredRowWhere(e);
|
|
627
646
|
hostLog('debug', `planReviewWire: ${decision} → ${String(status)} corrupt (stored resume context unreadable)`);
|
|
@@ -643,7 +662,9 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
|
|
|
643
662
|
outcome = planReviewDecisionConnectionLostOutcome(decision);
|
|
644
663
|
}
|
|
645
664
|
else {
|
|
646
|
-
|
|
665
|
+
const failureTexts = wireFailureTextsOf(e);
|
|
666
|
+
hostLog('debug', `planReviewWire: ${decision} could not reach the engine: ${failureTexts.log}`);
|
|
667
|
+
outcome = `The plan_review decision could not reach the engine: ${failureTexts.reason}`;
|
|
647
668
|
}
|
|
648
669
|
}
|
|
649
670
|
}
|
|
@@ -15,7 +15,7 @@ export interface SessionPolicyDeliverability {
|
|
|
15
15
|
export interface SessionPolicyDeliverableOptions {
|
|
16
16
|
readonly roster?: ToolRosterView;
|
|
17
17
|
}
|
|
18
|
-
export
|
|
18
|
+
export { isEngineRefusedToolName } from '../engineRefusedToolName.js';
|
|
19
19
|
export declare function sessionPolicyDeliverable(behavior: 'deny' | 'ask', rules: readonly string[], opts?: SessionPolicyDeliverableOptions): SessionPolicyDeliverability;
|
|
20
20
|
export declare function sessionPolicyWithheldNotice(why: SessionPolicyWithheldWhy): string;
|
|
21
21
|
export declare function legacyToolNameFailureNoteOf(code: string | undefined): string | undefined;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { CONFIG_LEGACY_TOOL_NAME, RULES_LEGACY_TOOL_NAME } from '../engineErrorCodes.js';
|
|
2
|
-
import { rosterMountedNames } from '
|
|
2
|
+
import { rosterMountedNames } from '../rosterMountedNames.js';
|
|
3
3
|
import { ruleCompileVerdict } from '../ruleCompileVerdict.js';
|
|
4
4
|
import { protocolSegmentOf } from '../protocolSegment.js';
|
|
5
5
|
import { PREFIXES_IN_PROSE, REFUSED_NAME_CLASSES, REFUSED_NAME_FIX, RULES_LEGACY_TOOL_NAME_WRITE_NOTE } from './refusedToolNameProse.js';
|
|
6
6
|
import { CORE_PROTOCOL_PREFIXES, CORE_RETIRED_TOOL_CURRENT_NAMES } from '../generated/toolNameTables.js';
|
|
7
|
+
import { isEngineRefusedToolName } from '../engineRefusedToolName.js';
|
|
7
8
|
const COVERING_NAMESPACE_PREFIXES = CORE_PROTOCOL_PREFIXES;
|
|
8
9
|
const ENGINE_RETIRED_TOOL_NAMES = CORE_RETIRED_TOOL_CURRENT_NAMES;
|
|
9
10
|
const WITHHELD_NOTICE = {
|
|
@@ -40,13 +41,7 @@ function withheldWhyOf(rule) {
|
|
|
40
41
|
return 'legacy_tool_name';
|
|
41
42
|
return undefined;
|
|
42
43
|
}
|
|
43
|
-
export
|
|
44
|
-
if (typeof name !== 'string')
|
|
45
|
-
return false;
|
|
46
|
-
if (ENGINE_RETIRED_TOOL_NAMES.has(name))
|
|
47
|
-
return true;
|
|
48
|
-
return name.includes('__') && !COVERING_NAMESPACE_PREFIXES.some((prefix) => name.startsWith(prefix));
|
|
49
|
-
}
|
|
44
|
+
export { isEngineRefusedToolName } from '../engineRefusedToolName.js';
|
|
50
45
|
function suppliedRosterNames(opts) {
|
|
51
46
|
if (opts === undefined || opts === null)
|
|
52
47
|
return 'absent';
|
|
@@ -3,7 +3,7 @@ import { hostLog } from '../host.js';
|
|
|
3
3
|
import { isEngineRefusedToolName } from './sessionPolicyDeliverable.js';
|
|
4
4
|
import { RULES_LEGACY_TOOL_NAME_WRITE_NOTE } from './refusedToolNameProse.js';
|
|
5
5
|
import { RULES_LEGACY_TOOL_NAME, isRulesRefusalCode } from '../engineErrorCodes.js';
|
|
6
|
-
import { rosterMountedNames } from '
|
|
6
|
+
import { rosterMountedNames } from '../rosterMountedNames.js';
|
|
7
7
|
import { CORE_RULE_TOOL_NAME_VERDICTS } from '../generated/engineFactTables.js';
|
|
8
8
|
function isLegacyToolNameVerdict(v) {
|
|
9
9
|
return typeof v === 'string' && v !== 'current' && CORE_RULE_TOOL_NAME_VERDICTS.includes(v);
|