@sema-agent/client-core 0.93.0 → 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.
Files changed (156) hide show
  1. package/CHANGELOG.md +261 -0
  2. package/README.md +31 -18
  3. package/dist/adapt/arms.js +8 -5
  4. package/dist/adapter/downstream/terminalToSdkResult.js +2 -3
  5. package/dist/agentSession/engineSessionBackgroundStop.js +2 -1
  6. package/dist/agentsWireCaps.d.ts +15 -0
  7. package/dist/agentsWireCaps.js +111 -1
  8. package/dist/approvalReachWire.d.ts +18 -0
  9. package/dist/approvalReachWire.js +58 -0
  10. package/dist/approvalsStreamLiveCapability.js +16 -11
  11. package/dist/callerCwdCapability.d.ts +16 -0
  12. package/dist/callerCwdCapability.js +47 -0
  13. package/dist/controlRouter.js +3 -10
  14. package/dist/coreValuePorts.d.ts +64 -1
  15. package/dist/coreValuePorts.js +30 -0
  16. package/dist/detachWire.d.ts +1 -4
  17. package/dist/detachWire.js +1 -1
  18. package/dist/deviceExecutorManagementCapability.js +39 -34
  19. package/dist/deviceFlowPolling.d.ts +5 -0
  20. package/dist/deviceFlowPolling.js +28 -0
  21. package/dist/displayUntrusted.js +652 -21
  22. package/dist/engineErrorCodes.d.ts +5 -1
  23. package/dist/engineErrorCodes.js +9 -12
  24. package/dist/engineNoticeCodes.d.ts +22 -2
  25. package/dist/engineNoticeCodes.js +73 -6
  26. package/dist/engineRefusedToolName.d.ts +1 -0
  27. package/dist/engineRefusedToolName.js +8 -0
  28. package/dist/executionLaneCapability.js +14 -9
  29. package/dist/fileHistoryCaptureCapability.js +14 -9
  30. package/dist/fileHistoryLegNotice.d.ts +11 -0
  31. package/dist/fileHistoryLegNotice.js +50 -13
  32. package/dist/fleet/fleetLedger.js +5 -0
  33. package/dist/gateOutcome.d.ts +2 -1
  34. package/dist/gateOutcome.js +3 -0
  35. package/dist/gateVocabulary.js +1 -1
  36. package/dist/generated/engineAskFaceTables.d.ts +3 -0
  37. package/dist/generated/engineAskFaceTables.js +12 -0
  38. package/dist/generated/engineFactTables.d.ts +4 -2
  39. package/dist/generated/engineFactTables.js +13 -0
  40. package/dist/generated/engineNoticeTables.d.ts +5 -0
  41. package/dist/generated/engineNoticeTables.js +7 -0
  42. package/dist/generated/sdkFactTables.d.ts +3 -0
  43. package/dist/generated/sdkFactTables.js +17 -0
  44. package/dist/generated/settingsSchemaTables.d.ts +3 -0
  45. package/dist/generated/settingsSchemaTables.js +3 -0
  46. package/dist/headlessPermissionModeWire.d.ts +2 -2
  47. package/dist/headlessPermissionModeWire.js +11 -21
  48. package/dist/hitl/approvalHolds.d.ts +36 -0
  49. package/dist/hitl/approvalHolds.js +89 -0
  50. package/dist/hitl/approvalOutcomeNote.js +2 -1
  51. package/dist/hitl/approvalResolution.d.ts +3 -2
  52. package/dist/hitl/approvalResolution.js +21 -0
  53. package/dist/hitl/approvalsFeed.js +5 -0
  54. package/dist/hitl/askGateWire.js +3 -0
  55. package/dist/hitl/automaticAnswerVerdict.d.ts +8 -2
  56. package/dist/hitl/automaticAnswerVerdict.js +69 -21
  57. package/dist/hitl/decisionRefusal.d.ts +19 -0
  58. package/dist/hitl/decisionRefusal.js +63 -0
  59. package/dist/hitl/frameRouter.d.ts +10 -0
  60. package/dist/hitl/frameRouter.js +59 -3
  61. package/dist/hitl/gateLedger.d.ts +3 -0
  62. package/dist/hitl/gateLedger.js +17 -0
  63. package/dist/hitl/hitlBridge.d.ts +2 -2
  64. package/dist/hitl/hitlBridge.js +8 -10
  65. package/dist/hitl/hitlHostSurface.d.ts +6 -0
  66. package/dist/hitl/hitlHostSurface.js +25 -4
  67. package/dist/hitl/localAllowRule.js +2 -1
  68. package/dist/hitl/noAllowCause.d.ts +9 -0
  69. package/dist/hitl/noAllowCause.js +26 -0
  70. package/dist/hitl/parkResolver.js +6 -3
  71. package/dist/hitl/parkRowBirthWait.js +2 -1
  72. package/dist/hitl/persistedRulesWire.d.ts +6 -1
  73. package/dist/hitl/persistedRulesWire.js +12 -4
  74. package/dist/hitl/planReviewWire.d.ts +0 -1
  75. package/dist/hitl/planReviewWire.js +30 -9
  76. package/dist/hitl/sessionPolicyDeliverable.d.ts +1 -1
  77. package/dist/hitl/sessionPolicyDeliverable.js +3 -8
  78. package/dist/hitl/sessionPolicyWire.js +1 -1
  79. package/dist/hitl/toolApprovalWire.d.ts +37 -2
  80. package/dist/hitl/toolApprovalWire.js +392 -13
  81. package/dist/humanTurnRead.d.ts +15 -1
  82. package/dist/humanTurnRead.js +28 -2
  83. package/dist/index.d.ts +18 -6
  84. package/dist/index.js +14 -5
  85. package/dist/limitsWire.d.ts +0 -1
  86. package/dist/limitsWire.js +2 -10
  87. package/dist/liveModelCatalog.d.ts +14 -0
  88. package/dist/liveModelCatalog.js +61 -0
  89. package/dist/mcpProbeCapability.js +14 -9
  90. package/dist/mcpReconnect.js +13 -8
  91. package/dist/memoryComplianceCapability.js +15 -10
  92. package/dist/memoryOriginCapability.js +15 -10
  93. package/dist/modelIdentityResolvability.d.ts +4 -2
  94. package/dist/modelIdentityResolvability.js +31 -4
  95. package/dist/outputStyleSeatCapability.d.ts +16 -0
  96. package/dist/outputStyleSeatCapability.js +47 -0
  97. package/dist/ownKey.d.ts +1 -0
  98. package/dist/ownKey.js +10 -0
  99. package/dist/peerLaneCapability.js +16 -11
  100. package/dist/permissionRuleIssue.js +1 -1
  101. package/dist/permissionRulesWriteCapability.js +16 -11
  102. package/dist/permissionsAllowGrantCapability.d.ts +2 -0
  103. package/dist/permissionsAllowGrantCapability.js +38 -16
  104. package/dist/permissionsDeclaration.d.ts +10 -1
  105. package/dist/permissionsDeclaration.js +97 -15
  106. package/dist/protocolSegment.d.ts +1 -0
  107. package/dist/protocolSegment.js +13 -1
  108. package/dist/request/taskRequest.d.ts +11 -3
  109. package/dist/request/taskRequest.js +13 -1
  110. package/dist/resultFrameKeys.d.ts +4 -0
  111. package/dist/resultFrameKeys.js +13 -0
  112. package/dist/resumeRefusalCopy.js +6 -16
  113. package/dist/retryStatus.js +6 -4
  114. package/dist/rewindArchiveCapability.js +13 -8
  115. package/dist/{hitl/rosterMountedNames.d.ts → rosterMountedNames.d.ts} +1 -1
  116. package/dist/runCancelContext.js +2 -1
  117. package/dist/sealedKeyCapability.js +19 -14
  118. package/dist/seatContract.d.ts +33 -5
  119. package/dist/seatContract.js +143 -2
  120. package/dist/sessionPolicyCapability.js +15 -10
  121. package/dist/sessionReadRedactionCapability.d.ts +16 -0
  122. package/dist/sessionReadRedactionCapability.js +47 -0
  123. package/dist/sessionSyncFileHistoryCapability.js +14 -9
  124. package/dist/skillToolListsCapability.js +14 -9
  125. package/dist/sqlEngineCapability.js +18 -13
  126. package/dist/steerPriorityCapability.js +21 -16
  127. package/dist/subagent/agentStateDetail.d.ts +1 -0
  128. package/dist/subagent/agentStateDetail.js +41 -0
  129. package/dist/subagent/engineCompactWire.d.ts +15 -0
  130. package/dist/subagent/engineCompactWire.js +75 -13
  131. package/dist/supportFloor.js +1 -1
  132. package/dist/taskRequestWords.d.ts +2 -4
  133. package/dist/taskRequestWords.js +3 -2
  134. package/dist/types/engineState.d.ts +1 -1
  135. package/dist/userAttachmentEnvelope.d.ts +22 -1
  136. package/dist/userAttachmentEnvelope.js +90 -2
  137. package/dist/userInvokedSkillsCapability.js +14 -9
  138. package/dist/webSearchBackendCapability.js +21 -16
  139. package/dist/wireErrorTriage.d.ts +11 -0
  140. package/dist/wireErrorTriage.js +35 -0
  141. package/dist/wireFailureShape.d.ts +6 -0
  142. package/dist/wireFailureShape.js +30 -3
  143. package/dist/wireRefusalCopy.d.ts +6 -1
  144. package/dist/wireRefusalCopy.js +22 -8
  145. package/dist/wiringInstructionsAttribution.d.ts +10 -0
  146. package/dist/wiringInstructionsAttribution.js +67 -0
  147. package/dist/workflowClient.js +22 -1
  148. package/dist/workflowsJournalCapability.js +14 -9
  149. package/dist/writeProtectionCapability.js +28 -23
  150. package/docs/INTEGRATION-CLIENTS.md +817 -45
  151. package/package.json +5 -5
  152. package/dist/askSurvivesStandingAnswer.d.ts +0 -13
  153. package/dist/askSurvivesStandingAnswer.js +0 -53
  154. package/dist/hitl/askSurvivesPosture.d.ts +0 -20
  155. package/dist/hitl/askSurvivesPosture.js +0 -48
  156. /package/dist/{hitl/rosterMountedNames.js → rosterMountedNames.js} +0 -0
@@ -28,6 +28,7 @@ export const CORE_ENGINE_NOTICE_CODES = Object.freeze([
28
28
  "permission.ask_released_by_mode",
29
29
  "permission.read_deny_unapplied",
30
30
  "permission.rule_store_unavailable",
31
+ "permission.rule_row_dropped",
31
32
  "permission.allowed_tool_not_granted",
32
33
  "peer.inbound_disposition",
33
34
  "peer.held_settled",
@@ -116,6 +117,7 @@ export const CORE_NOTICE_AUDIENCE = Object.freeze({
116
117
  "permission.ask_released_by_mode": "user",
117
118
  "permission.read_deny_unapplied": "user",
118
119
  "permission.rule_store_unavailable": "user",
120
+ "permission.rule_row_dropped": "user",
119
121
  "permission.allowed_tool_not_granted": "user",
120
122
  "peer.inbound_disposition": "user",
121
123
  "peer.held_settled": "user",
@@ -210,6 +212,7 @@ export const CORE_NOTICE_MINTER = Object.freeze({
210
212
  "permission.ask_released_by_mode": "engine",
211
213
  "permission.read_deny_unapplied": "engine",
212
214
  "permission.rule_store_unavailable": "engine",
215
+ "permission.rule_row_dropped": "engine",
213
216
  "permission.allowed_tool_not_granted": "engine",
214
217
  "peer.inbound_disposition": "engine",
215
218
  "peer.held_settled": "engine",
@@ -278,3 +281,7 @@ export const CORE_TOOL_TURN_INCOMPLETE_CAUSES = Object.freeze([
278
281
  "bad_frame",
279
282
  "missing_result_synthesized",
280
283
  ]);
284
+ export const CORE_USER_INPUT_UNDRAINED_FAMILIES = Object.freeze({
285
+ "steer": Object.freeze({ code: "task.user_steer_undrained", countKey: "steer" }),
286
+ "followUp": Object.freeze({ code: "task.user_followup_undrained", countKey: "followUp" }),
287
+ });
@@ -0,0 +1,3 @@
1
+ export declare const SDK_ABORT_CAUSES: readonly ["client_disconnected", "ask_park_grace_exhausted", "cancel_verb"];
2
+ export declare const SDK_STATUS_DERIVED_COARSE_CODES: readonly ["request.rejected", "auth.unauthorized", "auth.forbidden", "not_found.resource", "request.payload_too_large", "request.unprocessable", "limit.rate_exceeded", "capability.unavailable", "internal.error"];
3
+ export declare const SDK_DENY_REASON_ROUTING_FAILURE: "routing_failure_fail_closed";
@@ -0,0 +1,17 @@
1
+ export const SDK_ABORT_CAUSES = Object.freeze([
2
+ "client_disconnected",
3
+ "ask_park_grace_exhausted",
4
+ "cancel_verb",
5
+ ]);
6
+ export const SDK_STATUS_DERIVED_COARSE_CODES = Object.freeze([
7
+ "request.rejected",
8
+ "auth.unauthorized",
9
+ "auth.forbidden",
10
+ "not_found.resource",
11
+ "request.payload_too_large",
12
+ "request.unprocessable",
13
+ "limit.rate_exceeded",
14
+ "capability.unavailable",
15
+ "internal.error",
16
+ ]);
17
+ export const SDK_DENY_REASON_ROUTING_FAILURE = "routing_failure_fail_closed";
@@ -1,5 +1,8 @@
1
1
  export declare const SETTINGS_ENGINE_OWNED_HOOK_EVENTS: readonly ["PreToolUse", "PostToolUse", "PostToolUseFailure", "PostToolBatch", "PreCompact", "PostCompact", "Stop", "StopFailure", "UserPromptSubmit"];
2
2
  export declare const SETTINGS_SECRET_LITERAL_RE: RegExp;
3
+ export declare const SETTINGS_PRIVATE_KEY_PEM_HEADER: RegExp;
4
+ export declare const SETTINGS_DEVICE_CODE_TTL_SECONDS = 900;
5
+ export declare const SETTINGS_DEVICE_POLL_INTERVAL_SECONDS = 5;
3
6
  export declare const SETTINGS_TIER_GROUP_NAME_RE: RegExp;
4
7
  export declare const SETTINGS_BUDGET_NUMBER_KEYS: readonly ["maxBudgetUsd", "tpmLimit", "rpmLimit", "maxParallelRequests", "maxRunCostUsd", "maxIterations"];
5
8
  export declare const SETTINGS_BUDGET_STRING_KEYS: readonly ["mode", "budgetDuration", "enforcement"];
@@ -10,6 +10,9 @@ export const SETTINGS_ENGINE_OWNED_HOOK_EVENTS = Object.freeze([
10
10
  "UserPromptSubmit",
11
11
  ]);
12
12
  export const SETTINGS_SECRET_LITERAL_RE = Object.freeze(/^(sk-|gh[opus]_|github_pat_|xox[baprse]-|xapp-|bearer\s|basic\s|eyJ[A-Za-z0-9_-]{10,}\.|AKIA[0-9A-Z]{12,}|AIza[0-9A-Za-z_-]{20,}|-----BEGIN[A-Z0-9 ]*PRIVATE KEY)/i);
13
+ export const SETTINGS_PRIVATE_KEY_PEM_HEADER = Object.freeze(/-----BEGIN[A-Z0-9 ]*PRIVATE KEY/i);
14
+ export const SETTINGS_DEVICE_CODE_TTL_SECONDS = 900;
15
+ export const SETTINGS_DEVICE_POLL_INTERVAL_SECONDS = 5;
13
16
  export const SETTINGS_TIER_GROUP_NAME_RE = Object.freeze(/^[a-z0-9][a-z0-9._-]*$/i);
14
17
  export const SETTINGS_BUDGET_NUMBER_KEYS = Object.freeze([
15
18
  "maxBudgetUsd",
@@ -3,10 +3,10 @@ import { type PermissionModesReading } from './permissionModesCapability.js';
3
3
  import { type EnvLike } from './hostEnv.js';
4
4
  export declare const HEADLESS_PERMISSION_MODE_ENV = "SEMA_HEADLESS_PERMISSION_MODE";
5
5
  export declare const HEADLESS_PERMISSION_WIRE_MODES: readonly PermissionMode[];
6
- export declare function normalizeShellPermissionModeForWire(mode: string | undefined, permissionModes?: PermissionModesReading): string | undefined;
6
+ export declare function normalizeShellPermissionModeForWire(mode: string | undefined, _permissionModes?: PermissionModesReading): string | undefined;
7
7
  export type HeadlessPermissionResolution = {
8
8
  fields: PermissionModeField;
9
- source: 'flag' | 'env' | 'default' | 'absent';
9
+ source: 'flag' | 'env' | 'default';
10
10
  warning?: string;
11
11
  };
12
12
  export declare function permissionModeFromArgv(argv: readonly string[]): string | undefined;
@@ -6,15 +6,10 @@ import { PERMISSION_MODE_WORDS } from './taskRequestWords.js';
6
6
  import { readOwnDataValue } from './ownKey.js';
7
7
  export const HEADLESS_PERMISSION_MODE_ENV = 'SEMA_HEADLESS_PERMISSION_MODE';
8
8
  export const HEADLESS_PERMISSION_WIRE_MODES = [...PERMISSION_MODE_WORDS];
9
- const SHELL_INTERNAL_MODE_TO_WIRE = new Map([
10
- ['manual', 'default'],
11
- ['dontAsk', 'default'],
12
- ]);
13
- export function normalizeShellPermissionModeForWire(mode, permissionModes) {
9
+ const SHELL_INTERNAL_MODE_TO_WIRE = new Map([['manual', 'default']]);
10
+ export function normalizeShellPermissionModeForWire(mode, _permissionModes) {
14
11
  if (mode === undefined)
15
12
  return undefined;
16
- if (mode === 'dontAsk' && permissionModeAdmitted(permissionModes, mode))
17
- return mode;
18
13
  return SHELL_INTERNAL_MODE_TO_WIRE.get(mode) ?? mode;
19
14
  }
20
15
  export function permissionModeFromArgv(argv) {
@@ -35,8 +30,9 @@ export function resolveHeadlessPermissionMode(argv, env = hostEnv(), permissionM
35
30
  const flagMode = permissionModeFromArgv(argv);
36
31
  if (flagMode !== undefined) {
37
32
  const snap = flagMode === 'dontAsk' ? dontAskSnapshot(permissionModes) : undefined;
38
- const normalized = normalizeShellPermissionModeForWire(flagMode, snap);
39
- return { fields: permissionModeField(normalized, { stampDefault: true, permissionModes: snap }), source: 'flag' };
33
+ const normalized = normalizeShellPermissionModeForWire(flagMode);
34
+ const projected = permissionModeField(normalized, { stampDefault: true, permissionModes: snap });
35
+ return { fields: projected.permissionMode !== undefined ? projected : permissionModeField('default', { stampDefault: true }), source: 'flag' };
40
36
  }
41
37
  const raw = env[HEADLESS_PERMISSION_MODE_ENV]?.trim();
42
38
  if (raw === undefined || raw === '') {
@@ -44,21 +40,15 @@ export function resolveHeadlessPermissionMode(argv, env = hostEnv(), permissionM
44
40
  }
45
41
  if (HEADLESS_PERMISSION_WIRE_MODES.includes(raw)) {
46
42
  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
- }
58
- return {
43
+ const resolution = {
59
44
  fields: permissionModeField(raw, { stampDefault: true, permissionModes: snap }),
60
45
  source: 'env',
61
46
  };
47
+ if (raw === 'dontAsk' && resolution.fields.permissionMode !== 'dontAsk' && !warnedInvalidEnv) {
48
+ warnedInvalidEnv = true;
49
+ resolution.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"`;
50
+ }
51
+ return resolution;
62
52
  }
63
53
  const resolution = {
64
54
  fields: permissionModeField('default', { stampDefault: true }),
@@ -0,0 +1,36 @@
1
+ import type { FsApprovalOutcome, ToolApprovalFrameOutcome } from './toolApprovalWire.js';
2
+ export interface DurableDecisionHold {
3
+ readonly lane: 'durable';
4
+ readonly outcome: Promise<FsApprovalOutcome>;
5
+ presented: boolean;
6
+ readonly interrupt: () => void;
7
+ readonly cardSignal: AbortSignal;
8
+ trailing?: Promise<unknown>;
9
+ }
10
+ export interface LiveCardHold {
11
+ readonly lane: 'live';
12
+ readonly outcome: Promise<ToolApprovalFrameOutcome>;
13
+ phase: 'card' | 'respond';
14
+ superseded?: {
15
+ readonly parked: boolean;
16
+ };
17
+ readonly cardSignal: AbortSignal;
18
+ readonly interrupt: () => void;
19
+ trailing?: Promise<void>;
20
+ }
21
+ export type ApprovalHold = DurableDecisionHold | LiveCardHold;
22
+ export interface ApprovalDecisionHoldTable {
23
+ readonly inFlight: Map<string, ApprovalHold>;
24
+ readonly settled: Map<string, DurableSettledHold>;
25
+ readonly openReads: Set<number>;
26
+ clock: number;
27
+ }
28
+ export interface DurableSettledHold {
29
+ readonly at: number;
30
+ readonly hold: DurableDecisionHold;
31
+ }
32
+ export declare const approvalDecisionHolds: ApprovalDecisionHoldTable;
33
+ export declare function liveCardHoldOf(key: string): LiveCardHold | undefined;
34
+ export declare function holdLiveCard(key: string, callerSignal: AbortSignal | undefined, present: (hold: LiveCardHold) => Promise<ToolApprovalFrameOutcome>): Promise<ToolApprovalFrameOutcome>;
35
+ export declare function joinLiveCard(hold: LiveCardHold, signal: AbortSignal | undefined): Promise<ToolApprovalFrameOutcome>;
36
+ export declare function supersedeLiveCard(key: string, parked: boolean): boolean;
@@ -0,0 +1,89 @@
1
+ import { hostLog } from '../host.js';
2
+ export const approvalDecisionHolds = { inFlight: new Map(), settled: new Map(), openReads: new Set(), clock: 0 };
3
+ export function liveCardHoldOf(key) {
4
+ const h = approvalDecisionHolds.inFlight.get(key);
5
+ return h !== undefined && h.lane === 'live' ? h : undefined;
6
+ }
7
+ export function holdLiveCard(key, callerSignal, present) {
8
+ const h = approvalDecisionHolds;
9
+ const cut = new AbortController();
10
+ let settled = false;
11
+ const interrupt = () => {
12
+ if (!settled && !cut.signal.aborted)
13
+ cut.abort();
14
+ };
15
+ const unsubscribe = followCallerSignal(callerSignal, interrupt);
16
+ let resolveOutcome = () => { };
17
+ let rejectOutcome = () => { };
18
+ const outcome = new Promise((resolve, reject) => {
19
+ resolveOutcome = resolve;
20
+ rejectOutcome = reject;
21
+ });
22
+ const hold = { lane: 'live', outcome, phase: 'card', cardSignal: cut.signal, interrupt };
23
+ h.inFlight.set(key, hold);
24
+ const release = () => {
25
+ if (h.inFlight.get(key) === hold)
26
+ h.inFlight.delete(key);
27
+ };
28
+ const settle = (give) => {
29
+ settled = true;
30
+ unsubscribe();
31
+ const trailing = hold.trailing;
32
+ if (trailing === undefined) {
33
+ release();
34
+ give();
35
+ return;
36
+ }
37
+ give();
38
+ trailing.then(release, release);
39
+ };
40
+ let run;
41
+ try {
42
+ run = present(hold);
43
+ }
44
+ catch (e) {
45
+ run = Promise.reject(e);
46
+ }
47
+ run.then((o) => settle(() => resolveOutcome(o)), (e) => settle(() => rejectOutcome(e)));
48
+ return outcome;
49
+ }
50
+ export async function joinLiveCard(hold, signal) {
51
+ const unsubscribe = followCallerSignal(signal, hold.interrupt);
52
+ try {
53
+ const o = await hold.outcome;
54
+ return { ...o, joinedInFlight: true };
55
+ }
56
+ finally {
57
+ unsubscribe();
58
+ }
59
+ }
60
+ function followCallerSignal(signal, onAbort) {
61
+ if (signal === undefined || signal === null)
62
+ return () => { };
63
+ try {
64
+ if (signal.aborted === true) {
65
+ onAbort();
66
+ return () => { };
67
+ }
68
+ signal.addEventListener('abort', onAbort, { once: true });
69
+ }
70
+ catch {
71
+ return () => { };
72
+ }
73
+ return () => {
74
+ try {
75
+ signal.removeEventListener('abort', onAbort);
76
+ }
77
+ catch (e) {
78
+ hostLog('debug', `approvalHolds: could not detach from the caller's abort signal (${String(e).slice(0, 120)}) — the listener is one-shot and inert after settle`);
79
+ }
80
+ };
81
+ }
82
+ export function supersedeLiveCard(key, parked) {
83
+ const hold = liveCardHoldOf(key);
84
+ if (hold === undefined || hold.phase !== 'card' || hold.superseded !== undefined)
85
+ return false;
86
+ hold.superseded = Object.freeze({ parked });
87
+ hold.interrupt();
88
+ return true;
89
+ }
@@ -17,7 +17,8 @@ function detailOf(r) {
17
17
  }
18
18
  export function approvalOutcomeNoteOf(frame, outcome) {
19
19
  const r = approvalResolutionOf(outcome);
20
- const retracted = approvalNotSentCause(r) === 'retracted';
20
+ const cause = approvalNotSentCause(r);
21
+ const retracted = cause === 'retracted';
21
22
  const detail = detailOf(r);
22
23
  return {
23
24
  approvalId: frame.approvalId,
@@ -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,31 @@ 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
+ const message = str(pick(v, 'message'));
110
+ return {
111
+ approvalId,
112
+ ...(retryAfterMs !== undefined ? { retryAfterMs } : {}),
113
+ ...(decidePath !== undefined ? { decidePath } : {}),
114
+ ...(message !== undefined ? { message } : {}),
115
+ };
116
+ }
101
117
  function frameLegResolution(outcome) {
102
118
  if (pick(outcome, 'retracted') === true)
103
119
  return { kind: 'not_sent', cause: 'retracted' };
104
120
  if (pick(outcome, 'editRefused') === true)
105
121
  return { kind: 'not_sent', cause: 'edit_refused' };
122
+ if (pick(outcome, 'aborted') === true)
123
+ return { kind: 'unsettled', cause: 'interrupted' };
124
+ const parking = parkingViewOf(pick(outcome, 'parking'));
125
+ if (parking !== undefined)
126
+ return { kind: 'unsettled', cause: 'parking_in_progress', parking };
106
127
  if (pick(outcome, 'decision') === 'unresolved') {
107
128
  const refusal = pick(outcome, 'respondRefusal');
108
129
  return {
@@ -1,6 +1,7 @@
1
1
  import { hostLog } from '../host.js';
2
2
  import { unrefTimer } from '../unrefTimer.js';
3
3
  import { readLivePendingRows } from './livePendingAsk.js';
4
+ import { isInStreamDrainingEnd } from '../wireErrorTriage.js';
4
5
  const MAX_CONSECUTIVE_POLL_ESCAPES = 5;
5
6
  function readDurablePendingRows(raw) {
6
7
  if (!Array.isArray(raw))
@@ -415,6 +416,10 @@ export function startApprovalsFeed(client, onSnapshot, opts) {
415
416
  stats.heartbeats++;
416
417
  continue;
417
418
  }
419
+ if (isInStreamDrainingEnd(ev)) {
420
+ hostLog('debug', 'approvalsFeed: push leg received the drain end frame — the engine is going down; the stream reconnects and re-syncs on its own');
421
+ continue;
422
+ }
418
423
  stats.pushEvents++;
419
424
  await take('push');
420
425
  }
@@ -27,6 +27,9 @@ export async function* bridgeAskUserQuestionGates(source, deps, opts) {
27
27
  while (true) {
28
28
  let park = null;
29
29
  for await (const ev of stream) {
30
+ const answering = led.answeringLiveCards();
31
+ if (answering.length > 0)
32
+ await Promise.all(answering);
30
33
  led.noteSeq(ev);
31
34
  if (isHostProgressFrame(ev))
32
35
  progressedSinceReattach = true;
@@ -1,5 +1,7 @@
1
1
  import { type CoreModeArbitrationRow } from '../generated/engineFactTables.js';
2
- export type AutomaticAnswerLane = 'mode_posture' | 'standing_rule';
2
+ import { CORE_ASK_FACES } from '../generated/engineAskFaceTables.js';
3
+ export type AutomaticAnswerLane = 'mode_posture' | 'standing_rule' | 'permission_request_hook';
4
+ export type AutomaticAnswerAskFace = (typeof CORE_ASK_FACES)[number];
3
5
  export interface AutomaticAnswerFacts {
4
6
  readonly settingsAskRuleMatched?: boolean;
5
7
  }
@@ -17,5 +19,9 @@ export type AutomaticAnswerVerdict = {
17
19
  readonly kind: 'unknown';
18
20
  readonly why: AutomaticAnswerUnknownWhy;
19
21
  };
20
- export declare function foldModeArbitrationRow(classes: Iterable<string>): CoreModeArbitrationRow;
22
+ export declare const MODE_ARBITRATION: Readonly<Record<"hook" | "write_protection" | "mode" | "rule_unreadable" | "write_protection_unresolved" | "content_question" | "ancestor" | "dangerous_delete" | "unverifiable_delete" | "transcript_write" | "org_rule" | "governance_unavailable" | "ask_rule" | "denial_limit_fallback" | "classifier_no_ruling" | "requires_user_interaction" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary", CoreModeArbitrationRow>>;
23
+ export declare function foldModeArbitrationRow(classes: Iterable<string>, askFace?: AutomaticAnswerAskFace): CoreModeArbitrationRow;
21
24
  export declare function automaticAnswerVerdict(card: object | null | undefined, lane: AutomaticAnswerLane, facts?: AutomaticAnswerFacts | null): AutomaticAnswerVerdict;
25
+ export declare function mandatedApprovalDetailOf(card: object | null | undefined): string;
26
+ export type PermissionRequestHookScope = 'allow_or_deny' | 'deny_only' | 'skip';
27
+ export declare function permissionRequestHookScope(verdict: AutomaticAnswerVerdict | null | undefined): PermissionRequestHookScope;
@@ -1,8 +1,11 @@
1
1
  import { CORE_MODE_ARBITRATION, CORE_MODE_RELEASE_REASONS } from '../generated/engineFactTables.js';
2
+ import { CORE_RELEASABLE_ASK_CLASSES_BY_FACE, CORE_SHELL_FACE_TOOL_NAMES } from '../generated/engineAskFaceTables.js';
3
+ import { mandatedApprovalDetail } from '../gateVocabulary.js';
2
4
  import { readOwnNonEmptyString } from '../ownKey.js';
3
5
  import { readAskClassPairState } from './askClassPair.js';
4
6
  import { approvalAllowRuleShadowed } from './allowRuleShadow.js';
5
7
  import { approvalIsMandated, readApprovalMandate } from './toolApprovalWire.js';
8
+ export const MODE_ARBITRATION = CORE_MODE_ARBITRATION;
6
9
  const TABLE = CORE_MODE_ARBITRATION;
7
10
  const ORIGIN_IMPLIES_CLASS = Object.freeze({
8
11
  content_question: 'content_question',
@@ -12,12 +15,26 @@ const ORIGIN_IMPLIES_CLASS = Object.freeze({
12
15
  ask_rule: 'ask_rule',
13
16
  denial_limit_fallback: 'denial_limit_fallback',
14
17
  });
15
- const MANDATE_WORD_IMPLIES_CLASS = Object.freeze({
16
- operator_always: 'shell_floor',
17
- write_protection_unresolved: 'write_protection_unresolved',
18
- });
18
+ function mandateWordClass(word) {
19
+ if (word === 'operator_always')
20
+ return 'shell_floor';
21
+ return Object.hasOwn(TABLE, word) ? word : undefined;
22
+ }
19
23
  const ORDINARY_ROW = CORE_MODE_ARBITRATION.ordinary;
20
- export function foldModeArbitrationRow(classes) {
24
+ function bypassImmuneOn(c, face) {
25
+ const on = face !== undefined && Object.hasOwn(CORE_RELEASABLE_ASK_CLASSES_BY_FACE, face) ? face : 'other';
26
+ return !CORE_RELEASABLE_ASK_CLASSES_BY_FACE[on].includes(c);
27
+ }
28
+ function askFaceOfCard(card) {
29
+ try {
30
+ const d = Object.getOwnPropertyDescriptor(card, 'toolName');
31
+ return d !== undefined && 'value' in d && typeof d.value === 'string' && CORE_SHELL_FACE_TOOL_NAMES.includes(d.value) ? 'shell' : 'other';
32
+ }
33
+ catch {
34
+ return 'other';
35
+ }
36
+ }
37
+ export function foldModeArbitrationRow(classes, askFace) {
21
38
  let any = false;
22
39
  let bypassImmune = false;
23
40
  let classifierEligible = true;
@@ -29,7 +46,7 @@ export function foldModeArbitrationRow(classes) {
29
46
  continue;
30
47
  const row = TABLE[c];
31
48
  any = true;
32
- bypassImmune ||= row.bypassImmune;
49
+ bypassImmune ||= bypassImmuneOn(c, askFace);
33
50
  classifierEligible &&= row.classifierEligible;
34
51
  sandboxAdmission &&= row.sandboxAdmission;
35
52
  ruleMayClear &&= row.ruleMayClear;
@@ -39,8 +56,12 @@ export function foldModeArbitrationRow(classes) {
39
56
  return ORDINARY_ROW;
40
57
  return Object.freeze({ bypassImmune, classifierEligible, sandboxAdmission, ruleMayClear, hookAskFloor });
41
58
  }
42
- function laneBlocks(lane, row) {
43
- return lane === 'mode_posture' ? row.bypassImmune : !row.ruleMayClear;
59
+ function rowBlocks(lane, row) {
60
+ return lane === 'mode_posture' ? row.bypassImmune : lane === 'standing_rule' ? !row.ruleMayClear : row.hookAskFloor;
61
+ }
62
+ function classBlocks(lane, c, face) {
63
+ const row = TABLE[c];
64
+ return lane === 'mode_posture' ? bypassImmuneOn(c, face) : lane === 'standing_rule' ? !row.ruleMayClear : row.hookAskFloor;
44
65
  }
45
66
  function mustAsk(road, extra) {
46
67
  return Object.freeze({ kind: 'must_ask', road, ...(extra?.askClass !== undefined ? { askClass: extra.askClass } : {}), ...(extra?.origin !== undefined ? { origin: extra.origin } : {}) });
@@ -75,34 +96,33 @@ function readHostAskRuleMatched(facts) {
75
96
  }
76
97
  }
77
98
  export function automaticAnswerVerdict(card, lane, facts) {
78
- if (lane !== 'mode_posture' && lane !== 'standing_rule')
99
+ if (lane !== 'mode_posture' && lane !== 'standing_rule' && lane !== 'permission_request_hook')
79
100
  return UNKNOWN.lane_not_recognised;
80
101
  try {
81
102
  if (typeof card !== 'object' || card === null)
82
103
  return UNKNOWN.card_unreadable;
104
+ const face = lane === 'mode_posture' ? askFaceOfCard(card) : undefined;
83
105
  const origin = readOwnNonEmptyString(evidenceView(card, 'origin'), 'origin');
84
106
  if (origin !== undefined && Object.hasOwn(ORIGIN_IMPLIES_CLASS, origin)) {
85
107
  const cls = ORIGIN_IMPLIES_CLASS[origin];
86
- if (laneBlocks(lane, TABLE[cls]))
108
+ if (classBlocks(lane, cls, face))
87
109
  return mustAsk('origin', { origin, askClass: cls });
88
110
  }
89
- if (lane === 'standing_rule' && evidenceView(card, 'requiresRealApproval').requiresRealApproval === true)
111
+ if (lane !== 'mode_posture' && evidenceView(card, 'requiresRealApproval').requiresRealApproval === true)
90
112
  return mustAsk('requires_real_approval');
91
113
  if (evidenceView(card, 'governanceForced').governanceForced === true)
92
114
  return mustAsk('governance');
93
115
  if (lane === 'mode_posture') {
94
116
  const word = readApprovalMandate(evidenceView(card, 'mandate'));
95
- if (word !== undefined && Object.hasOwn(MANDATE_WORD_IMPLIES_CLASS, word)) {
96
- const cls = MANDATE_WORD_IMPLIES_CLASS[word];
97
- if (laneBlocks(lane, TABLE[cls]))
98
- return mustAsk('mandate_word', { askClass: cls });
99
- }
117
+ const cls = word !== undefined ? mandateWordClass(word) : undefined;
118
+ if (cls !== undefined && classBlocks(lane, cls, face))
119
+ return mustAsk('mandate_word', { askClass: cls });
100
120
  }
101
121
  else {
102
122
  const mandateView = evidenceView(card, 'mandated', 'ruleOffersAbsence', 'mandate');
103
123
  if (approvalIsMandated(mandateView))
104
124
  return mustAsk('mandated');
105
- if (approvalAllowRuleShadowed(card) || readOwnNonEmptyString(mandateView, 'ruleOffersAbsence') === 'shadowed')
125
+ if (lane === 'standing_rule' && (approvalAllowRuleShadowed(card) || readOwnNonEmptyString(mandateView, 'ruleOffersAbsence') === 'shadowed'))
106
126
  return mustAsk('allow_rule_shadow');
107
127
  }
108
128
  const pair = readAskClassPairState(card);
@@ -110,7 +130,7 @@ export function automaticAnswerVerdict(card, lane, facts) {
110
130
  return UNKNOWN.classes_malformed;
111
131
  if (pair.state === 'absent')
112
132
  return UNKNOWN.classes_absent;
113
- const folded = foldModeArbitrationRow(pair.classes);
133
+ const folded = foldModeArbitrationRow(pair.classes, face);
114
134
  let anyKnown = false;
115
135
  let firstBlocking;
116
136
  let anyUnknown = false;
@@ -120,14 +140,14 @@ export function automaticAnswerVerdict(card, lane, facts) {
120
140
  continue;
121
141
  }
122
142
  anyKnown = true;
123
- if (firstBlocking === undefined && laneBlocks(lane, TABLE[c]))
143
+ if (firstBlocking === undefined && classBlocks(lane, c, face))
124
144
  firstBlocking = c;
125
145
  }
126
- if (anyKnown && laneBlocks(lane, folded))
146
+ if (anyKnown && rowBlocks(lane, folded))
127
147
  return mustAsk('ask_class', firstBlocking !== undefined ? { askClass: firstBlocking } : undefined);
128
148
  if (anyUnknown)
129
149
  return UNKNOWN.class_not_in_table;
130
- if (lane === 'standing_rule')
150
+ if (lane !== 'mode_posture')
131
151
  return MAY_ANSWER;
132
152
  const matched = readHostAskRuleMatched(facts);
133
153
  if (matched === true)
@@ -141,3 +161,31 @@ export function automaticAnswerVerdict(card, lane, facts) {
141
161
  return UNKNOWN.card_unreadable;
142
162
  }
143
163
  }
164
+ export function mandatedApprovalDetailOf(card) {
165
+ try {
166
+ if (typeof card !== 'object' || card === null)
167
+ return mandatedApprovalDetail();
168
+ const pair = readAskClassPairState(card);
169
+ if (pair.state !== 'pair')
170
+ return mandatedApprovalDetail();
171
+ if (pair.classes.some((c) => !Object.hasOwn(TABLE, c)))
172
+ return mandatedApprovalDetail();
173
+ const row = foldModeArbitrationRow(pair.classes);
174
+ if (row.classifierEligible || row.sandboxAdmission)
175
+ return mandatedApprovalDetail();
176
+ return MANDATED_NO_AUTOMATIC_CHECK_DETAIL;
177
+ }
178
+ catch {
179
+ return mandatedApprovalDetail();
180
+ }
181
+ }
182
+ const MANDATED_NO_AUTOMATIC_CHECK_DETAIL = 'no saved allow rule and no remembered answer can retire this question, and no automatic check answers it; each call is settled on its own, by you here, and an answer covers only that call';
183
+ export function permissionRequestHookScope(verdict) {
184
+ try {
185
+ const kind = typeof verdict === 'object' && verdict !== null ? verdict.kind : undefined;
186
+ return kind === 'may_answer' ? 'allow_or_deny' : kind === 'must_ask' ? 'skip' : 'deny_only';
187
+ }
188
+ catch {
189
+ return 'deny_only';
190
+ }
191
+ }
@@ -0,0 +1,19 @@
1
+ export type DecisionRefusalStanding = 'settled-elsewhere' | 'ambiguous' | 'refused-pending';
2
+ export interface DecisionRefusalClassified {
3
+ readonly standing: DecisionRefusalStanding;
4
+ readonly status: number;
5
+ readonly errorCode?: string;
6
+ readonly transient?: true;
7
+ }
8
+ export declare function classifyDecisionRefusal(err: unknown): DecisionRefusalClassified | undefined;
9
+ export type LiveAskPendingReading = 'pending' | 'not_pending' | 'unknown';
10
+ export declare function liveAskPendingReading(envelope: unknown, approvalId: string): LiveAskPendingReading;
11
+ export declare function decisionRefusalClosesOut(refusal: DecisionRefusalClassified | undefined, pending: LiveAskPendingReading): boolean;
12
+ export type UndeliverableDecisionEnd = 'run-stopped' | 'run-unconfirmed' | 'turn-stopped';
13
+ export interface UndeliverableDecisionFacts {
14
+ readonly status: number;
15
+ readonly errorCode?: string;
16
+ readonly end: UndeliverableDecisionEnd;
17
+ readonly runId?: string;
18
+ }
19
+ export declare function undeliverableDecisionContent(facts: UndeliverableDecisionFacts): string;