@sema-agent/client-core 0.107.0 → 0.109.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 (165) hide show
  1. package/CHANGELOG.md +239 -0
  2. package/README.md +54 -36
  3. package/dist/adapt/arms.js +23 -8
  4. package/dist/adapt/textStream.d.ts +2 -0
  5. package/dist/adapt/textStream.js +37 -16
  6. package/dist/adapter/activeRunSelfHeal.d.ts +2 -0
  7. package/dist/adapter/activeRunSelfHeal.js +21 -4
  8. package/dist/adapter/downstream/eventToSdkMessage.js +2 -1
  9. package/dist/agentSession/engineAgentRegistryRead.js +2 -0
  10. package/dist/bashExitReading.d.ts +12 -0
  11. package/dist/bashExitReading.js +44 -0
  12. package/dist/checkpointRowVerdict.d.ts +6 -1
  13. package/dist/checkpointRowVerdict.js +30 -2
  14. package/dist/cloudConfigWireCaps.d.ts +10 -0
  15. package/dist/cloudConfigWireCaps.js +33 -0
  16. package/dist/compensations.js +1 -1
  17. package/dist/coreValuePorts.d.ts +17 -1
  18. package/dist/coreValuePorts.js +5 -0
  19. package/dist/decideReceipt.js +2 -1
  20. package/dist/engineAgentAbsence.d.ts +2 -0
  21. package/dist/engineAgentAbsence.js +28 -0
  22. package/dist/engineErrorCodes.d.ts +9 -0
  23. package/dist/engineErrorCodes.js +9 -0
  24. package/dist/engineHealthFacts.d.ts +17 -0
  25. package/dist/engineHealthFacts.js +42 -3
  26. package/dist/engineNoticeCodes.d.ts +147 -1
  27. package/dist/engineNoticeCodes.js +694 -57
  28. package/dist/engineTargetSeat.js +1 -0
  29. package/dist/errorSupplementRoles.js +6 -1
  30. package/dist/eventLeg.js +1 -1
  31. package/dist/executionLaneWords.js +2 -1
  32. package/dist/fleetTaskDesc.js +3 -3
  33. package/dist/gateOutcome.d.ts +10 -1
  34. package/dist/gateOutcome.js +48 -1
  35. package/dist/generated/engineFactTables.d.ts +2 -0
  36. package/dist/generated/engineFactTables.js +11 -0
  37. package/dist/generated/engineNoticeTables.js +6 -0
  38. package/dist/generated/settingsSchemaFns.d.ts +4 -2
  39. package/dist/generated/settingsSchemaFns.js +9 -3
  40. package/dist/generated/settingsSchemaMcpRequestEntry.d.ts +1 -1
  41. package/dist/generated/settingsSchemaMcpRequestEntry.js +9 -18
  42. package/dist/generated/settingsSchemaReservedMemberName.d.ts +1 -0
  43. package/dist/generated/settingsSchemaReservedMemberName.js +7 -0
  44. package/dist/generated/settingsSchemaTables.d.ts +2 -0
  45. package/dist/generated/settingsSchemaTables.js +10 -0
  46. package/dist/generated/settingsSchemaWireName.d.ts +1 -0
  47. package/dist/generated/settingsSchemaWireName.js +8 -0
  48. package/dist/headlessReconnectWire.js +12 -7
  49. package/dist/hitl/approvalAskers.d.ts +13 -0
  50. package/dist/hitl/approvalAskers.js +79 -0
  51. package/dist/hitl/approvalCardReasons.js +22 -4
  52. package/dist/hitl/approvalRequestFrameReading.d.ts +14 -0
  53. package/dist/hitl/approvalRequestFrameReading.js +79 -0
  54. package/dist/hitl/approvalResolution.d.ts +3 -0
  55. package/dist/hitl/approvalResolution.js +22 -4
  56. package/dist/hitl/approvalsFeed.js +4 -13
  57. package/dist/hitl/askParkRowRouting.js +10 -3
  58. package/dist/hitl/automaticAnswerVerdict.d.ts +5 -2
  59. package/dist/hitl/automaticAnswerVerdict.js +16 -2
  60. package/dist/hitl/decisionConflict.d.ts +2 -1
  61. package/dist/hitl/decisionConflict.js +4 -1
  62. package/dist/hitl/decisionRefusal.d.ts +1 -0
  63. package/dist/hitl/decisionRefusal.js +5 -2
  64. package/dist/hitl/decisionSend.js +2 -6
  65. package/dist/hitl/frameRouter.d.ts +3 -2
  66. package/dist/hitl/frameRouter.js +37 -7
  67. package/dist/hitl/gateLedger.d.ts +2 -0
  68. package/dist/hitl/gateLedger.js +3 -1
  69. package/dist/hitl/hitlBridge.d.ts +4 -0
  70. package/dist/hitl/hitlBridge.js +11 -8
  71. package/dist/hitl/livePendingAsk.js +17 -16
  72. package/dist/hitl/localAllowRule.js +2 -1
  73. package/dist/hitl/noAllowCause.d.ts +1 -1
  74. package/dist/hitl/noAllowCause.js +4 -2
  75. package/dist/hitl/parkExpiryJudgements.js +26 -1
  76. package/dist/hitl/parkOwnership.d.ts +2 -0
  77. package/dist/hitl/parkOwnership.js +48 -8
  78. package/dist/hitl/parkResolver.js +39 -30
  79. package/dist/hitl/parkRowBirthWait.d.ts +2 -0
  80. package/dist/hitl/parkRowBirthWait.js +8 -1
  81. package/dist/hitl/parkedUnresumable.d.ts +13 -0
  82. package/dist/hitl/parkedUnresumable.js +80 -0
  83. package/dist/hitl/permissionRuleScope.d.ts +4 -0
  84. package/dist/hitl/permissionRuleScope.js +31 -0
  85. package/dist/hitl/persistedRulesWire.d.ts +7 -1
  86. package/dist/hitl/persistedRulesWire.js +25 -8
  87. package/dist/hitl/planReviewWire.js +12 -16
  88. package/dist/hitl/policyDenyCause.d.ts +3 -0
  89. package/dist/hitl/policyDenyCause.js +16 -0
  90. package/dist/hitl/sessionAllowRoute.js +5 -4
  91. package/dist/hitl/sessionPolicyDeliverable.js +1 -1
  92. package/dist/hitl/standingRows.d.ts +1 -5
  93. package/dist/hitl/standingRows.js +15 -14
  94. package/dist/hitl/toolApprovalWire.d.ts +16 -4
  95. package/dist/hitl/toolApprovalWire.js +57 -17
  96. package/dist/hooksReaderGenerations.d.ts +1 -0
  97. package/dist/hooksReaderGenerations.js +12 -0
  98. package/dist/hooksWireCaps.js +12 -7
  99. package/dist/hostSettingsSource.js +2 -0
  100. package/dist/i18n/messages.en.d.ts +206 -1
  101. package/dist/i18n/messages.en.js +209 -4
  102. package/dist/i18n/messages.zh.js +214 -4
  103. package/dist/index.d.ts +35 -8
  104. package/dist/index.js +20 -5
  105. package/dist/liveModelCatalog.d.ts +4 -2
  106. package/dist/liveModelCatalog.js +164 -26
  107. package/dist/localEngineJudge.d.ts +75 -0
  108. package/dist/localEngineJudge.js +242 -0
  109. package/dist/localEngineOccupancy.d.ts +88 -0
  110. package/dist/localEngineOccupancy.js +269 -0
  111. package/dist/localEngineSeatReading.d.ts +29 -0
  112. package/dist/localEngineSeatReading.js +294 -0
  113. package/dist/localEngineSpawn.d.ts +113 -0
  114. package/dist/localEngineSpawn.js +486 -0
  115. package/dist/localEngineStep.d.ts +286 -0
  116. package/dist/localEngineStep.js +1086 -0
  117. package/dist/localEngineStopLadder.d.ts +29 -0
  118. package/dist/localEngineStopLadder.js +139 -0
  119. package/dist/localEngineSwapCard.d.ts +43 -0
  120. package/dist/localEngineSwapCard.js +205 -0
  121. package/dist/localEngineTiming.d.ts +8 -0
  122. package/dist/localEngineTiming.js +8 -0
  123. package/dist/mcpReconnect.js +12 -5
  124. package/dist/mcpWireCaps.js +2 -2
  125. package/dist/memoryEntriesWire.d.ts +1 -1
  126. package/dist/memoryEntriesWire.js +5 -0
  127. package/dist/model/catalogDefault.d.ts +25 -0
  128. package/dist/model/catalogDefault.js +44 -0
  129. package/dist/noticePresentation.d.ts +2 -0
  130. package/dist/noticePresentation.js +79 -1
  131. package/dist/ownKey.d.ts +10 -0
  132. package/dist/ownKey.js +15 -1
  133. package/dist/permissionWireCaps.d.ts +1 -0
  134. package/dist/permissionWireCaps.js +11 -0
  135. package/dist/pluginHooksWire.d.ts +5 -3
  136. package/dist/pluginHooksWire.js +116 -7
  137. package/dist/queuedDelivery.d.ts +1 -0
  138. package/dist/queuedDelivery.js +12 -0
  139. package/dist/resultFrameKeys.js +4 -2
  140. package/dist/resumeRefusalCopy.js +15 -8
  141. package/dist/ruleReadings.js +1 -1
  142. package/dist/runPermissionModeSwitch.d.ts +2 -1
  143. package/dist/runPermissionModeSwitch.js +16 -6
  144. package/dist/seam.d.ts +13 -2
  145. package/dist/seam.js +8 -1
  146. package/dist/seatContract.d.ts +103 -12
  147. package/dist/seatContract.js +344 -14
  148. package/dist/sessionBackground.js +2 -0
  149. package/dist/sessionEngine.d.ts +2 -1
  150. package/dist/sessionEngine.js +5 -0
  151. package/dist/settingsHooksSalvage.d.ts +5 -0
  152. package/dist/settingsHooksSalvage.js +54 -0
  153. package/dist/subagentContentStore.d.ts +12 -0
  154. package/dist/subagentContentStore.js +73 -12
  155. package/dist/supportFloor.js +2 -0
  156. package/dist/taskNotificationAdmission.d.ts +19 -0
  157. package/dist/taskNotificationAdmission.js +84 -0
  158. package/dist/toolResult.js +20 -6
  159. package/dist/webSearchWireCaps.js +9 -4
  160. package/dist/wireFailureShape.d.ts +20 -0
  161. package/dist/wireFailureShape.js +37 -6
  162. package/dist/wireRefusalCopy.d.ts +23 -2
  163. package/dist/wireRefusalCopy.js +93 -4
  164. package/docs/INTEGRATION-CLIENTS.md +1959 -15
  165. package/package.json +4 -4
@@ -69,6 +69,14 @@ function pickCut(src, limit, floor, boundary) {
69
69
  return clause;
70
70
  return word >= floor ? word : -1;
71
71
  }
72
+ export function pairSafeCut(text, cut) {
73
+ if (cut <= 0 || cut >= text.length || text.charAt(cut) !== '\\' || text.charAt(cut - 1) !== '\\')
74
+ return cut;
75
+ let start = cut - 1;
76
+ while (start > 0 && text.charAt(start - 1) === '\\')
77
+ start--;
78
+ return (cut - start) % 2 === 1 ? cut - 1 : cut;
79
+ }
72
80
  export function cutForDisplay(text, budget, opts) {
73
81
  const src = typeof text === 'string' ? text : (guardedRead(() => String(text ?? '')) ?? '');
74
82
  if (typeof budget !== 'number' || !Number.isFinite(budget))
@@ -83,9 +91,79 @@ export function cutForDisplay(text, budget, opts) {
83
91
  const head = capDisplay(src, room, 'escape');
84
92
  const floor = Math.ceil(head.length / CUT_KEEP_DIVISOR);
85
93
  const at = pickCut(src, head.length, floor, boundary);
86
- const kept = (at === -1 ? head : src.slice(0, at)).trimEnd();
94
+ const kept = src.slice(0, pairSafeCut(src, at === -1 ? head.length : at)).trimEnd();
87
95
  return { shown: `${kept}${mark}`, truncated: true };
88
96
  }
97
+ const ELISION_MARK = '…';
98
+ const ELISION_MIN_ROOM = 8;
99
+ const ELISION_SOFT_BACKOFF = 24;
100
+ const CLUSTER_EXTENDER = /^[\p{M}‍︀-️\u{1F3FB}-\u{1F3FF}\u{E0020}-\u{E007F}]/u;
101
+ const REGIONAL_INDICATOR = /^[\u{1F1E6}-\u{1F1FF}]$/u;
102
+ const RTL_LETTER = /^[\p{Script=Arabic}\p{Script=Hebrew}\p{Script=Syriac}\p{Script=Thaana}\p{Script=Nko}\p{Script=Samaritan}\p{Script=Mandaic}]$/u;
103
+ function codePointBefore(text, i) {
104
+ return text.slice(Math.max(0, i - 2), i).match(/[\s\S]$/u)?.[0] ?? '';
105
+ }
106
+ function codePointAfter(text, i) {
107
+ const cp = i < text.length ? text.codePointAt(i) : undefined;
108
+ return cp === undefined ? '' : String.fromCodePoint(cp);
109
+ }
110
+ function hardSafeBoundary(text, i) {
111
+ return i <= 0 || i >= text.length || (capDisplay(text, i, 'escape').length === i && pairSafeCut(text, i) === i);
112
+ }
113
+ function calmBoundary(text, i) {
114
+ const before = codePointBefore(text, i);
115
+ const after = codePointAfter(text, i);
116
+ if (before === '' || after === '')
117
+ return true;
118
+ if (before === '‍' || CLUSTER_EXTENDER.test(after))
119
+ return false;
120
+ if (RTL_LETTER.test(before) || RTL_LETTER.test(after))
121
+ return false;
122
+ if (REGIONAL_INDICATOR.test(before) && REGIONAL_INDICATOR.test(after)) {
123
+ let run = 0;
124
+ for (let j = i; j > 0;) {
125
+ const cp = codePointBefore(text, j);
126
+ if (!REGIONAL_INDICATOR.test(cp))
127
+ break;
128
+ run++;
129
+ j -= cp.length;
130
+ }
131
+ return run % 2 === 0;
132
+ }
133
+ return true;
134
+ }
135
+ function settleOnCalm(text, at, step) {
136
+ for (let k = 0; k <= ELISION_SOFT_BACKOFF; k++) {
137
+ const i = at + step * k;
138
+ if (i < 0 || i > text.length)
139
+ break;
140
+ if (hardSafeBoundary(text, i) && calmBoundary(text, i))
141
+ return i;
142
+ }
143
+ return at;
144
+ }
145
+ export function elideMiddleForDisplay(text, budget) {
146
+ const src = typeof text === 'string' ? text : (guardedRead(() => String(text ?? '')) ?? '');
147
+ if (typeof budget !== 'number' || !Number.isFinite(budget))
148
+ return src;
149
+ const limit = Math.max(0, Math.floor(budget));
150
+ if (src.length <= limit)
151
+ return src;
152
+ const room = limit - ELISION_MARK.length;
153
+ if (room < ELISION_MIN_ROOM)
154
+ return cutForDisplay(src, limit).shown;
155
+ const headRoom = Math.ceil(room / 2);
156
+ const tailRoom = room - headRoom;
157
+ let head = headRoom;
158
+ while (head > 0 && !hardSafeBoundary(src, head))
159
+ head--;
160
+ let tail = src.length - tailRoom;
161
+ while (tail < src.length && !hardSafeBoundary(src, tail))
162
+ tail++;
163
+ head = settleOnCalm(src, head, -1);
164
+ tail = settleOnCalm(src, tail, 1);
165
+ return `${src.slice(0, head).trimEnd()}${ELISION_MARK}${src.slice(tail)}`;
166
+ }
89
167
  export const NOTICE_SEAT_CLASS = Object.freeze({
90
168
  user: Object.freeze({ present: 'persistent+hint', unknown: 'persistent+hint', absent: 'persistent' }),
91
169
  operator: Object.freeze({ present: 'transient', unknown: 'transient', absent: 'diagnostic' }),
package/dist/ownKey.d.ts CHANGED
@@ -32,8 +32,18 @@ export interface HumanInputAttachmentView {
32
32
  kind?: string;
33
33
  outcome: string;
34
34
  label?: string;
35
+ declinedBy?: HumanInputAttachmentDeclinedByView;
36
+ }
37
+ export interface HumanInputAttachmentDeclinedByView {
38
+ kind: 'extension';
39
+ name: string;
35
40
  }
36
41
  export declare function humanInputAttachmentsOf(frame: {} | null | undefined): HumanInputAttachmentView[] | undefined;
42
+ export interface HumanInputAuthorKeys {
43
+ author?: string;
44
+ extension?: string;
45
+ }
46
+ export declare function humanInputAuthorKeys(frame: {} | null | undefined): HumanInputAuthorKeys;
37
47
  export declare function markMcpSectionDroppedNamedRows(rows: object, dropped: ReadonlyMap<string, number>): void;
38
48
  export declare function mcpSectionDroppedNamedRowsOf(rows: object | null | undefined, name: string): number;
39
49
  export interface PromptManifestSectionRow {
package/dist/ownKey.js CHANGED
@@ -175,7 +175,14 @@ export function humanInputAttachmentsOf(frame) {
175
175
  return undefined;
176
176
  const kind = readOwnDataNonEmptyString(r, 'kind');
177
177
  const label = readOwnDataNonEmptyString(r, 'label');
178
- return { ordinal, ...(kind !== undefined ? { kind } : {}), outcome, ...(label !== undefined ? { label } : {}) };
178
+ const declinedBy = guardedRead(() => {
179
+ const d = readOwnDataValue(r, 'declinedBy');
180
+ if (typeof d !== 'object' || d === null || Array.isArray(d))
181
+ return undefined;
182
+ const name = readOwnDataNonEmptyString(d, 'name');
183
+ return readOwnDataValue(d, 'kind') === 'extension' && name !== undefined ? { kind: 'extension', name } : undefined;
184
+ });
185
+ return { ordinal, ...(kind !== undefined ? { kind } : {}), outcome, ...(label !== undefined ? { label } : {}), ...(declinedBy !== undefined ? { declinedBy } : {}) };
179
186
  });
180
187
  if (row !== undefined)
181
188
  appendOwnElement(rows, row);
@@ -186,6 +193,13 @@ export function humanInputAttachmentsOf(frame) {
186
193
  return undefined;
187
194
  }
188
195
  }
196
+ export function humanInputAuthorKeys(frame) {
197
+ if (typeof frame !== 'object' || frame === null || Array.isArray(frame))
198
+ return {};
199
+ const author = guardedRead(() => readOwnDataNonEmptyString(frame, 'author'));
200
+ const extension = guardedRead(() => readOwnDataNonEmptyString(frame, 'extension'));
201
+ return { ...(author !== undefined ? { author } : {}), ...(extension !== undefined ? { extension } : {}) };
202
+ }
189
203
  const DROPPED_NAMED_ROWS = Symbol.for('@sema-agent/client-core/mcpSectionDroppedNamedRows');
190
204
  export function markMcpSectionDroppedNamedRows(rows, dropped) {
191
205
  if (dropped.size === 0)
@@ -4,6 +4,7 @@ export type { PermissionMode };
4
4
  export interface PermissionModeField {
5
5
  permissionMode?: PermissionMode;
6
6
  }
7
+ export declare function permissionModeTitle(word: string): string | undefined;
7
8
  export declare function permissionModeField(mode: string | undefined, opts?: {
8
9
  stampDefault?: boolean;
9
10
  permissionModes?: PermissionModesReading | undefined;
@@ -2,6 +2,17 @@ import { PERMISSION_MODE_WORDS } from './taskRequestWords.js';
2
2
  import { permissionModeAdmitted } from './permissionModesCapability.js';
3
3
  const _permissionModePin = true;
4
4
  void _permissionModePin;
5
+ const PERMISSION_MODE_TITLES = {
6
+ default: 'Manual',
7
+ acceptEdits: 'Accept edits',
8
+ plan: 'Plan',
9
+ auto: 'Auto',
10
+ bypassPermissions: 'Bypass Permissions',
11
+ dontAsk: "Don't Ask",
12
+ };
13
+ export function permissionModeTitle(word) {
14
+ return typeof word === 'string' && Object.hasOwn(PERMISSION_MODE_TITLES, word) ? PERMISSION_MODE_TITLES[word] : undefined;
15
+ }
5
16
  const BIT_GATED_MODE = 'dontAsk';
6
17
  const KNOWN = new Set(PERMISSION_MODE_WORDS.filter((w) => w !== BIT_GATED_MODE));
7
18
  export function permissionModeField(mode, opts) {
@@ -8,11 +8,11 @@ export declare const ENGINE_FIRED_HOOK_EVENTS: readonly ["PreToolUse", "PostTool
8
8
  export type EngineFiredHookEvent = (typeof ENGINE_FIRED_HOOK_EVENTS)[number];
9
9
  export declare const PLUGIN_HOOK_DISPOSITIONS: readonly ["engine", "shell", "not_run"];
10
10
  export type PluginHookDisposition = (typeof PLUGIN_HOOK_DISPOSITIONS)[number];
11
- export declare const PLUGIN_HOOK_REASONS: readonly ["projected", "shell_event", "no_executor", "engine_not_local", "engine_locality_unknown", "engine_capability_absent", "engine_capability_unknown", "not_command", "sensitive_options", "engine_limit", "shell_form_user_config", "user_config_unset", "malformed_entry", "engine_hooks_off", "engine_hooks_unknown", "engine_type_skipped", "engine_llm_limit", "engine_unconfirmed", "engine_elsewhere", "hooks_module", "scope_unsupported", "source_incomplete", "compaction_local_fallback"];
11
+ export declare const PLUGIN_HOOK_REASONS: readonly ["projected", "shell_event", "no_executor", "engine_not_local", "engine_locality_unknown", "engine_capability_absent", "engine_capability_unknown", "not_command", "sensitive_options", "engine_limit", "shell_form_user_config", "user_config_unset", "malformed_entry", "engine_hooks_off", "engine_hooks_unknown", "engine_type_skipped", "engine_llm_limit", "engine_unconfirmed", "engine_elsewhere", "hooks_module", "scope_unsupported", "source_incomplete", "compaction_local_fallback", "key_not_read", "source_not_authored"];
12
12
  export type PluginHookReason = (typeof PLUGIN_HOOK_REASONS)[number];
13
13
  export declare const PLUGIN_HOOK_EXCLUSION_REASONS: readonly ["workspace_untrusted", "governance_unreadable", "policy_disable_all", "startup_mode", "managed_plugins_only", "prompt_not_user_authored"];
14
14
  export type PluginHookExclusionReason = (typeof PLUGIN_HOOK_EXCLUSION_REASONS)[number];
15
- export declare const SETTINGS_HOOK_DROP_REASONS: readonly ["exec_form_unsupported", "mcp_tool_unsupported"];
15
+ export declare const SETTINGS_HOOK_DROP_REASONS: readonly ["exec_form_unsupported", "mcp_tool_unsupported", "key_not_read", "source_not_authored"];
16
16
  export type SettingsHookDropReason = (typeof SETTINGS_HOOK_DROP_REASONS)[number];
17
17
  export type HookSettingsSource = 'policySettings' | 'userSettings' | 'projectSettings' | 'localSettings' | 'flagSettings';
18
18
  export interface PluginHookKey {
@@ -36,6 +36,7 @@ export interface SettingsHookDrop {
36
36
  readonly hookIndex: number;
37
37
  readonly reason: SettingsHookDropReason;
38
38
  readonly notRun: boolean;
39
+ readonly unreadKeys?: readonly (readonly (string | number)[])[];
39
40
  }
40
41
  export interface SettingsHookVerdict {
41
42
  readonly source: HookSettingsSource;
@@ -147,6 +148,7 @@ export interface PluginHookDecisionInput {
147
148
  readonly promptAuthoredByUser: boolean;
148
149
  readonly occupied: ReadonlyMap<string, EventOccupancy>;
149
150
  readonly suppressedLocally?: ReadonlySet<string>;
151
+ readonly readerNotRead: readonly string[];
150
152
  }
151
153
  export interface PluginHookDecision {
152
154
  readonly verdicts: readonly PluginHookVerdict[];
@@ -166,7 +168,7 @@ export declare function decideSessionHooks(input: SessionHookDecisionInput): {
166
168
  readonly verdicts: readonly SessionHookVerdict[];
167
169
  readonly unreadable: number;
168
170
  };
169
- export declare function filterSettingsHookGroups(source: HookSettingsSource, event: string, groups: readonly WireHookGroup[], execFormCapable: boolean, shellRunsType: (type: string | undefined) => boolean, judge: SettingsHookJudge, mask?: HooksSalvageMask): SettingsHookFilterResult;
171
+ export declare function filterSettingsHookGroups(source: HookSettingsSource, event: string, groups: readonly WireHookGroup[], execFormCapable: boolean, shellRunsType: (type: string | undefined) => boolean, judge: SettingsHookJudge, readerNotRead: readonly string[], mask?: HooksSalvageMask): SettingsHookFilterResult;
170
172
  export interface SettingsHookFilterResult {
171
173
  readonly groups: WireHookGroup[];
172
174
  readonly drops: SettingsHookDrop[];
@@ -1,8 +1,8 @@
1
1
  import { displayUntrusted } from './displayUntrusted.js';
2
- import { appendOwnElement } from './ownKey.js';
2
+ import { appendOwnElement, readOwnDataValue } from './ownKey.js';
3
3
  import { msg, renderMessage } from './i18n/translate.js';
4
4
  import { SETTINGS_ENGINE_OWNED_HOOK_EVENTS } from './generated/settingsSchemaTables.js';
5
- import { HOOK_SETTINGS_SOURCE_TEXT, hooksSalvageMaskOf, salvageSettingsHooksEntries, } from './settingsHooksSalvage.js';
5
+ import { HOOK_SETTINGS_SOURCE_TEXT, hooksEntryUnreadKeys, hooksGroupAuthoredSource, hooksGroupUnreadKeys, hooksKeyCoordinateText, hooksSalvageMaskOf, salvageSettingsHooksEntries, } from './settingsHooksSalvage.js';
6
6
  export const ENGINE_FIRED_HOOK_EVENTS = SETTINGS_ENGINE_OWNED_HOOK_EVENTS;
7
7
  export const PLUGIN_HOOK_DISPOSITIONS = ['engine', 'shell', 'not_run'];
8
8
  export const PLUGIN_HOOK_REASONS = [
@@ -29,6 +29,8 @@ export const PLUGIN_HOOK_REASONS = [
29
29
  'scope_unsupported',
30
30
  'source_incomplete',
31
31
  'compaction_local_fallback',
32
+ 'key_not_read',
33
+ 'source_not_authored',
32
34
  ];
33
35
  export const PLUGIN_HOOK_EXCLUSION_REASONS = [
34
36
  'workspace_untrusted',
@@ -38,7 +40,7 @@ export const PLUGIN_HOOK_EXCLUSION_REASONS = [
38
40
  'managed_plugins_only',
39
41
  'prompt_not_user_authored',
40
42
  ];
41
- export const SETTINGS_HOOK_DROP_REASONS = ['exec_form_unsupported', 'mcp_tool_unsupported'];
43
+ export const SETTINGS_HOOK_DROP_REASONS = ['exec_form_unsupported', 'mcp_tool_unsupported', 'key_not_read', 'source_not_authored'];
42
44
  export function hooksWirePlanShapeOf(v) {
43
45
  try {
44
46
  if (typeof v !== 'object' || v === null || Array.isArray(v))
@@ -257,7 +259,9 @@ export function decidePluginHooks(input) {
257
259
  unreadable++;
258
260
  return;
259
261
  }
260
- ;
262
+ const authored = hooksGroupAuthoredSource(group);
263
+ const groupUnread = authored === 'unreadable' ? undefined : hooksGroupUnreadKeys(event, group);
264
+ const groupSourceAuthored = authored === 'unreadable' || (authored === 'present' && groupUnread !== undefined);
261
265
  group.hooks.forEach((entry, hookIndex) => {
262
266
  const key = { pluginId, pluginName, event, groupIndex, hookIndex };
263
267
  if (input.exclusion !== undefined) {
@@ -317,6 +321,15 @@ export function decidePluginHooks(input) {
317
321
  appendOwnElement(verdicts, { ...key, disposition: ex.disposition, reason: ex.reason });
318
322
  return;
319
323
  }
324
+ const entryUnread = isRecord(entry) ? hooksEntryUnreadKeys(event, entry, input.readerNotRead) : undefined;
325
+ if (groupSourceAuthored && (authored === 'unreadable' || entryUnread !== undefined)) {
326
+ appendOwnElement(verdicts, { ...key, ...fallback(event, entryType, 'source_not_authored') });
327
+ return;
328
+ }
329
+ if (entryUnread !== undefined && ((groupUnread?.length ?? 0) > 0 || entryUnread.length > 0)) {
330
+ appendOwnElement(verdicts, { ...key, ...fallback(event, entryType, 'key_not_read') });
331
+ return;
332
+ }
320
333
  const plugin = { name: src.name, root: src.root, dataDir: src.dataDir };
321
334
  const options = projectedOptions(src.options);
322
335
  if (options !== undefined)
@@ -454,7 +467,18 @@ function carriesArgs(entry) {
454
467
  }
455
468
  return true;
456
469
  }
457
- export function filterSettingsHookGroups(source, event, groups, execFormCapable, shellRunsType, judge, mask) {
470
+ function unreadKeyPathsOf(event, groupIndex, hookIndex, groupUnread, entryUnread) {
471
+ if (entryUnread === undefined)
472
+ return Object.freeze([]);
473
+ const out = [];
474
+ for (const k of groupUnread)
475
+ appendOwnElement(out, Object.freeze(['hooks', event, groupIndex, k]));
476
+ for (const k of entryUnread)
477
+ if (k !== 'plugin')
478
+ appendOwnElement(out, Object.freeze(['hooks', event, groupIndex, 'hooks', hookIndex, k]));
479
+ return Object.freeze(out);
480
+ }
481
+ export function filterSettingsHookGroups(source, event, groups, execFormCapable, shellRunsType, judge, readerNotRead, mask) {
458
482
  const drops = [];
459
483
  const verdicts = [];
460
484
  const excluded = [];
@@ -470,20 +494,35 @@ export function filterSettingsHookGroups(source, event, groups, execFormCapable,
470
494
  }
471
495
  const kept = [];
472
496
  let changed = false;
497
+ const authored = hooksGroupAuthoredSource(group);
498
+ const groupUnread = authored === 'unreadable' ? undefined : hooksGroupUnreadKeys(event, group);
499
+ const groupSourceAuthored = authored === 'unreadable' || (authored === 'present' && groupUnread !== undefined);
473
500
  group.hooks.forEach((entry, hookIndex) => {
474
501
  if (mask !== undefined && mask.entryDropped(event, groupIndex, hookIndex)) {
475
502
  changed = true;
476
503
  return;
477
504
  }
478
- const reason = !isRecord(entry)
505
+ const earlier = !isRecord(entry)
479
506
  ? undefined
480
507
  : entry.type === 'mcp_tool'
481
508
  ? 'mcp_tool_unsupported'
482
509
  : entry.type === 'command' && carriesArgs(entry) && !execFormCapable
483
510
  ? 'exec_form_unsupported'
484
511
  : undefined;
512
+ const entryUnread = earlier === undefined && isRecord(entry) ? hooksEntryUnreadKeys(event, entry, readerNotRead) : undefined;
513
+ const sourceNotAuthored = earlier === undefined && groupSourceAuthored && (authored === 'unreadable' || entryUnread !== undefined);
514
+ const unreadKeys = earlier === undefined && !sourceNotAuthored ? unreadKeyPathsOf(event, groupIndex, hookIndex, groupUnread ?? [], entryUnread) : [];
515
+ const reason = earlier ?? (sourceNotAuthored ? 'source_not_authored' : unreadKeys.length > 0 ? 'key_not_read' : undefined);
485
516
  if (reason !== undefined) {
486
- appendOwnElement(drops, { source, event, groupIndex, hookIndex, reason, notRun: engineEvent && !shellRunsType(isRecord(entry) && typeof entry.type === 'string' ? entry.type : undefined) });
517
+ appendOwnElement(drops, {
518
+ source,
519
+ event,
520
+ groupIndex,
521
+ hookIndex,
522
+ reason,
523
+ notRun: engineEvent && !shellRunsType(isRecord(entry) && typeof entry.type === 'string' ? entry.type : undefined),
524
+ ...(reason === 'key_not_read' ? { unreadKeys } : {}),
525
+ });
487
526
  changed = true;
488
527
  return;
489
528
  }
@@ -510,6 +549,8 @@ export function filterSettingsHookGroups(source, event, groups, execFormCapable,
510
549
  }
511
550
  appendOwnElement(kept, entry);
512
551
  });
552
+ if (((groupUnread?.length ?? 0) > 0 || groupSourceAuthored) && kept.length === 0)
553
+ changed = true;
513
554
  if (!changed)
514
555
  appendOwnElement(out, group);
515
556
  else if (kept.length > 0)
@@ -832,10 +873,14 @@ const REASON_TEXT = {
832
873
  scope_unsupported: 'hooks registered for a skill, an agent or by the host application are not sent to the engine, and this client does not run this event',
833
874
  source_incomplete: 'the plugin was reported without its id, name, root folder or data folder',
834
875
  compaction_local_fallback: renderMessage(msg('config.hooks.plan.reason.compactionLocalFallback')),
876
+ key_not_read: renderMessage(msg('config.hooks.plan.reason.keyNotRead')),
877
+ source_not_authored: renderMessage(msg('config.hooks.plan.reason.sourceNotAuthored')),
835
878
  };
836
879
  const DROP_TEXT = {
837
880
  exec_form_unsupported: 'exec-form hooks (with "args") are sent to the engine only once it reports support for them',
838
881
  mcp_tool_unsupported: 'MCP tool hooks are not supported by the engine',
882
+ key_not_read: REASON_TEXT.key_not_read,
883
+ source_not_authored: REASON_TEXT.source_not_authored,
839
884
  };
840
885
  const SOURCE_TEXT = HOOK_SETTINGS_SOURCE_TEXT;
841
886
  export function pluginHookReasonText(reason) {
@@ -898,8 +943,52 @@ function capNoticeLabel(s) {
898
943
  }
899
944
  return `${s.slice(0, end)}…`;
900
945
  }
946
+ const KEY_NOT_READ_SHOWN = 3;
947
+ function unreadPathsOfRow(d) {
948
+ const raw = d.unreadKeys;
949
+ if (!Array.isArray(raw))
950
+ return [];
951
+ return raw.filter((p) => Array.isArray(p) && p.length > 0 && p.every((x) => typeof x === 'string' || typeof x === 'number'));
952
+ }
953
+ function namedPathsOfRow(d) {
954
+ switch (d.reason) {
955
+ case 'key_not_read':
956
+ return unreadPathsOfRow(d);
957
+ case 'source_not_authored':
958
+ return typeof d.event === 'string' && Number.isInteger(d.groupIndex) && d.groupIndex >= 0 ? [['hooks', d.event, d.groupIndex, 'source']] : [];
959
+ case 'exec_form_unsupported':
960
+ case 'mcp_tool_unsupported':
961
+ return [];
962
+ }
963
+ }
964
+ function namedCoordinatesText(coordinates) {
965
+ const shown = coordinates.slice(0, KEY_NOT_READ_SHOWN).join(', ');
966
+ return coordinates.length > KEY_NOT_READ_SHOWN ? renderMessage(msg('config.hooks.plan.keyNotRead.more', { shown, rest: coordinates.length - KEY_NOT_READ_SHOWN })) : shown;
967
+ }
968
+ function keyNotReadLine(source, row) {
969
+ const events = row.events.map((e) => noticeLabel(e)).join(', ');
970
+ const hooks = `${row.count} ${events} ${row.count === 1 ? 'hook' : 'hooks'}`;
971
+ const coordinates = namedCoordinatesText(row.coordinates);
972
+ const line = renderMessage(msg(row.coordinates.length === 1 ? 'config.hooks.plan.keyNotRead.oneKey' : 'config.hooks.plan.keyNotRead.manyKeys', { layer: SOURCE_TEXT[source], hooks, coordinates }));
973
+ return row.onFailure ? renderMessage(msg('config.hooks.plan.keyNotRead.withOnFailure', { line })) : line;
974
+ }
975
+ function sourceNotAuthoredLine(source, row) {
976
+ const events = row.events.map((e) => noticeLabel(e)).join(', ');
977
+ const hooks = `${row.count} ${events} ${row.count === 1 ? 'hook' : 'hooks'}`;
978
+ const coordinates = namedCoordinatesText(row.coordinates);
979
+ return renderMessage(msg(row.coordinates.length === 1 ? 'config.hooks.plan.sourceNotAuthored.oneKey' : 'config.hooks.plan.sourceNotAuthored.manyKeys', { layer: SOURCE_TEXT[source], hooks, coordinates }));
980
+ }
901
981
  export function hooksNotRunNotice(plan) {
902
982
  const lines = [];
983
+ let closedBy;
984
+ try {
985
+ closedBy = isRecord(plan) ? readOwnDataValue(plan, 'closedBy') : undefined;
986
+ }
987
+ catch {
988
+ closedBy = undefined;
989
+ }
990
+ if (closedBy === 'policy_disable_all')
991
+ appendOwnElement(lines, renderMessage(msg('config.hooks.plan.disableAllHooks')));
903
992
  const byPlugin = new Map();
904
993
  for (const v of plan.verdicts) {
905
994
  if (v.disposition !== 'not_run')
@@ -916,9 +1005,27 @@ export function hooksNotRunNotice(plan) {
916
1005
  appendOwnElement(lines, `Plugin "${noticeLabel(row.name) || '(unnamed)'}": ${events} ${row.count === 1 ? 'hook' : 'hooks'} will not run — ${pluginHookReasonText(row.reason)}.`);
917
1006
  }
918
1007
  const bySource = new Map();
1008
+ const namedBySource = new Map();
919
1009
  for (const d of plan.settingsDropped) {
920
1010
  if (!d.notRun)
921
1011
  continue;
1012
+ const named = d.reason === 'key_not_read' || d.reason === 'source_not_authored' ? namedPathsOfRow(d) : [];
1013
+ if (named.length > 0 && (d.reason === 'key_not_read' || d.reason === 'source_not_authored')) {
1014
+ const nk = `${d.source}\u0000${d.reason}`;
1015
+ const row = namedBySource.get(nk) ?? { source: d.source, reason: d.reason, events: [], count: 0, coordinates: [], onFailure: false };
1016
+ if (!row.events.includes(d.event))
1017
+ appendOwnElement(row.events, d.event);
1018
+ row.count++;
1019
+ for (const path of named) {
1020
+ const c = hooksKeyCoordinateText(path);
1021
+ if (!row.coordinates.includes(c))
1022
+ appendOwnElement(row.coordinates, c);
1023
+ if (d.reason === 'key_not_read' && path[path.length - 1] === 'onFailure')
1024
+ row.onFailure = true;
1025
+ }
1026
+ namedBySource.set(nk, row);
1027
+ continue;
1028
+ }
922
1029
  const k = `${d.source}\u0000${d.reason}`;
923
1030
  const row = bySource.get(k) ?? { source: d.source, reason: d.reason, events: [], count: 0 };
924
1031
  if (!row.events.includes(d.event))
@@ -930,6 +1037,8 @@ export function hooksNotRunNotice(plan) {
930
1037
  const events = row.events.map((e) => noticeLabel(e)).join(', ');
931
1038
  appendOwnElement(lines, `${SOURCE_TEXT[row.source]}: ${events} ${row.count === 1 ? 'hook' : 'hooks'} will not run — ${settingsHookDropText(row.reason)}.`);
932
1039
  }
1040
+ for (const row of namedBySource.values())
1041
+ appendOwnElement(lines, row.reason === 'key_not_read' ? keyNotReadLine(row.source, row) : sourceNotAuthoredLine(row.source, row));
933
1042
  const settingsVerdicts = plan.settingsVerdicts;
934
1043
  const byVerdict = new Map();
935
1044
  for (const raw of Array.isArray(settingsVerdicts) ? settingsVerdicts : []) {
@@ -15,6 +15,7 @@ export interface QueueEntry {
15
15
  readonly intentKey?: string;
16
16
  readonly screening?: 'pending' | 'result';
17
17
  readonly upsExecutor?: 'shell' | 'engine' | 'unknown' | 'none';
18
+ readonly _sema_task_notification?: {} | null | undefined;
18
19
  }
19
20
  export type QueuedHoldReason = 'barrier' | 'share_incomplete' | 'not_steerable' | 'in_flight' | 'attempted' | 'no_run';
20
21
  export interface QueuedDeliveryPlan {
@@ -3,6 +3,7 @@ import { imageVisionOf } from './liveModelCatalog.js';
3
3
  import { userPromptSubmitExecutorOf } from './pluginHooksWire.js';
4
4
  import { observedUserAttachmentSeat } from './userAttachmentSeat.js';
5
5
  import { guardedRead, readOwnDataValue } from './ownKey.js';
6
+ import { taskNotificationOf } from './notifications.js';
6
7
  const own = (o, key) => guardedRead(() => readOwnDataValue(o, key));
7
8
  const str = (o, key) => {
8
9
  const v = own(o, key);
@@ -43,6 +44,8 @@ export function batchCompatible(head, follower, plan) {
43
44
  return false;
44
45
  if (typeof head !== 'object' || head === null || typeof follower !== 'object' || follower === null)
45
46
  return false;
47
+ if (carriesCompletionIdentity(head) || carriesCompletionIdentity(follower))
48
+ return false;
46
49
  const executor = userPromptSubmitExecutorOf(plan);
47
50
  if (executor === 'engine' || executor === 'unknown')
48
51
  return false;
@@ -84,6 +87,15 @@ export function batchCompatible(head, follower, plan) {
84
87
  return false;
85
88
  }
86
89
  }
90
+ function carriesCompletionIdentity(e) {
91
+ const slot = peek(e, '_sema_task_notification');
92
+ if (slot === undefined || slot === null)
93
+ return false;
94
+ if (slot === UNREADABLE)
95
+ return true;
96
+ const fact = taskNotificationOf({ _sema_task_notification: slot });
97
+ return fact === undefined || fact.completionId !== undefined;
98
+ }
87
99
  function upsExecutorWordOk(v) {
88
100
  return v === undefined || v === 'shell' || v === 'engine' || v === 'unknown' || v === 'none';
89
101
  }
@@ -1,4 +1,4 @@
1
- import { readFrameStringKey, readFrameTrueMark } from './ownKey.js';
1
+ import { guardedRead, readFrameStringKey, readFrameTrueMark, readOwnDataValue } from './ownKey.js';
2
2
  export function resultErrorCodeOf(frame) {
3
3
  return readFrameStringKey(frame, '_sema_error_code');
4
4
  }
@@ -12,5 +12,7 @@ export function resultParkFailedReasonOf(frame) {
12
12
  return readFrameStringKey(frame, '_sema_park_failed_reason');
13
13
  }
14
14
  export function resultErrorCodeInTextOf(frame) {
15
- return readFrameTrueMark(frame, '_sema_error_code_in_text') ? true : undefined;
15
+ if (!readFrameTrueMark(frame, '_sema_error_code_in_text'))
16
+ return undefined;
17
+ return guardedRead(() => (Array.isArray(readOwnDataValue(frame, 'errors')) ? true : undefined));
16
18
  }
@@ -2,21 +2,27 @@ import { msg, renderMessage } from './i18n/translate.js';
2
2
  import { readBlockedBy, readErrorNumber, readErrorString, readThrownSlot } from './wireFailureShape.js';
3
3
  import { guardedRead, ownDataField, readOwnDataNonEmptyString, readOwnDataValue } from './ownKey.js';
4
4
  import { CORE_RESUME_STARTUP_CAUSES } from './generated/engineFactTables.js';
5
- import { RESUME_CONTEXT_UNAVAILABLE, RESUME_PLACEMENT_MISMATCH, RESUME_PREFLIGHT_REJECTED, RESUME_POLICY_FOLD_CODE, PARKED_RESUME_STARTUP_FAILED, isStatusDerivedCoarseCode, RESUME_SESSION_NOT_FOUND, REQUEST_FIELD_CONFLICT, CHECKPOINT_UNSUPPORTED_VERSION, CHECKPOINT_WALLTIME_AXIS_RETIRED, CHECKPOINT_CONFIG_REFUSED, isRefusedParkCode, refusalSaysStillPending, refusalRetriableReading, } from './engineErrorCodes.js';
5
+ import { RESUME_CONTEXT_UNAVAILABLE, RESUME_PLACEMENT_MISMATCH, RESUME_PREFLIGHT_REJECTED, RESUME_PRINCIPAL_MISMATCH, RESUME_COMPOSITION_MISMATCH, RESUME_PARENT_CONSTRAINT_MISSING, RESUME_PARENT_CONSTRAINT_MISMATCH, RESUME_CONSTRAINT_UNPROJECTABLE, RESUME_POLICY_FOLD_CODE, PARKED_RESUME_STARTUP_FAILED, isStatusDerivedCoarseCode, RESUME_SESSION_NOT_FOUND, REQUEST_FIELD_CONFLICT, CHECKPOINT_UNSUPPORTED_VERSION, CHECKPOINT_WALLTIME_AXIS_RETIRED, CHECKPOINT_CONFIG_REFUSED, isRefusedParkCode, refusalSaysStillPending, refusalRetriableReading, } from './engineErrorCodes.js';
6
6
  import { capForDisplay } from './fleetTaskDesc.js';
7
7
  import { engineVersionTier } from './supportFloor.js';
8
8
  import { resumeRetryLaterFromError } from './wireErrorTriage.js';
9
- export const RESUME_REFUSAL_CODES = Object.freeze([
10
- RESUME_PLACEMENT_MISMATCH,
11
- RESUME_PREFLIGHT_REJECTED,
12
- ]);
9
+ const REFUSAL_COPY_KEYS = Object.freeze({
10
+ [RESUME_PLACEMENT_MISMATCH]: 'resume.refusal.placementMismatch',
11
+ [RESUME_PREFLIGHT_REJECTED]: null,
12
+ [RESUME_PRINCIPAL_MISMATCH]: 'resume.refusal.principalMismatch',
13
+ [RESUME_COMPOSITION_MISMATCH]: 'resume.refusal.compositionMismatch',
14
+ [RESUME_PARENT_CONSTRAINT_MISSING]: 'resume.refusal.parentConstraintMissing',
15
+ [RESUME_PARENT_CONSTRAINT_MISMATCH]: 'resume.refusal.parentConstraintMismatch',
16
+ [RESUME_CONSTRAINT_UNPROJECTABLE]: 'resume.refusal.constraintUnprojectable',
17
+ });
18
+ export const RESUME_REFUSAL_CODES = Object.freeze(Object.keys(REFUSAL_COPY_KEYS));
13
19
  export function resumeRefusalFromError(err) {
14
20
  if (typeof err !== 'object' || err === null)
15
21
  return null;
16
22
  const code = guardedRead(() => ownDataField(err, 'errorCode'));
17
23
  if (typeof code !== 'string' || !RESUME_REFUSAL_CODES.includes(code))
18
24
  return null;
19
- if (code === RESUME_PLACEMENT_MISMATCH) {
25
+ if (code !== RESUME_PREFLIGHT_REJECTED) {
20
26
  return { code, waitable: false };
21
27
  }
22
28
  const timed = resumeRetryLaterFromError(err);
@@ -29,8 +35,9 @@ export function resumeRefusalFromError(err) {
29
35
  };
30
36
  }
31
37
  export function resumeRefusalContent(detail, lang) {
32
- if (detail.code === RESUME_PLACEMENT_MISMATCH)
33
- return renderMessage(msg('resume.refusal.placementMismatch'), lang);
38
+ const copyKey = typeof detail.code === 'string' && Object.hasOwn(REFUSAL_COPY_KEYS, detail.code) ? REFUSAL_COPY_KEYS[detail.code] : null;
39
+ if (copyKey !== null && copyKey !== undefined)
40
+ return renderMessage(msg(copyKey, { safe: msg('resume.refusal.untouched') }), lang);
34
41
  return renderMessage(msg(detail.waitable ? 'resume.refusal.preflightWaitable' : 'resume.refusal.preflightNoWait'), lang);
35
42
  }
36
43
  export function resumePolicyBlockFromError(err) {
@@ -122,7 +122,7 @@ export function ruleNearMissSentence(rows, lang) {
122
122
  const named = shown
123
123
  .map((it) => renderMessage(msg('rules.refusal.item', {
124
124
  rule: `"${capForDisplay(it.rule, NEAR_MISS_RULE_MAX)}"`,
125
- reason: renderMessage(msg('rules.nearMiss.reason', { mounted: capForDisplay(displayUntrusted(it.mounted), NEAR_MISS_NAME_MAX) }), lang),
125
+ reason: renderMessage(msg('rules.nearMiss.reason', { mounted: displayUntrusted(it.mounted, { max: NEAR_MISS_NAME_MAX, escapeBackslashes: 'auto' }) }), lang),
126
126
  }), lang))
127
127
  .join(separator);
128
128
  const rest = items.length - shown.length;
@@ -2,7 +2,7 @@ import type { UiLanguage } from './env/uiLanguage.js';
2
2
  import { type PermissionModeReceiptView } from './adapter/downstream/wiringManifestView.js';
3
3
  import { SDK_PERMISSION_MODE_ADMISSION_FOLDS, SDK_PERMISSION_MODE_PERSIST_MISSES } from './generated/sdkFactTables.js';
4
4
  import { type PermissionModeWriteReading } from './permissionModeWriteCapability.js';
5
- import type { PermissionMode } from './permissionWireCaps.js';
5
+ import { type PermissionMode } from './permissionWireCaps.js';
6
6
  export type { PermissionModeReceiptView };
7
7
  type AdmissionFold = (typeof SDK_PERMISSION_MODE_ADMISSION_FOLDS)[number];
8
8
  type PersistMiss = (typeof SDK_PERMISSION_MODE_PERSIST_MISSES)[number];
@@ -40,6 +40,7 @@ export type RunPermissionModeSwitchOutcome = {
40
40
  readonly status?: number;
41
41
  } | {
42
42
  readonly kind: 'unavailable';
43
+ readonly code?: string;
43
44
  };
44
45
  export interface RunPermissionModeSwitchRuns {
45
46
  setPermissionMode(taskId: string, mode: PermissionMode, opts?: {
@@ -3,8 +3,9 @@ import { readPermissionModeReceiptSegment } from './adapter/downstream/wiringMan
3
3
  import { displayUntrusted } from './displayUntrusted.js';
4
4
  import { SDK_PERMISSION_MODE_ADMISSION_FOLDS, SDK_PERMISSION_MODE_PERSIST_MISSES } from './generated/sdkFactTables.js';
5
5
  import { msg, renderMessage } from './i18n/translate.js';
6
- import { ownDataField } from './ownKey.js';
6
+ import { ownDataField, readOwnDataValue } from './ownKey.js';
7
7
  import { permissionModeWriteAvailable } from './permissionModeWriteCapability.js';
8
+ import { permissionModeTitle } from './permissionWireCaps.js';
8
9
  import { isDisplayableWireCode } from './wireFailureShape.js';
9
10
  export const PERMISSION_MODE_SWITCH_RESULT_KEYS = Object.freeze(['taskId', 'sessionId', 'status', 'permissionMode', 'admission', 'persisted', 'persistMiss']);
10
11
  const NOT_RUNNING_CODE = 'steering.not_running';
@@ -19,7 +20,9 @@ function guardedSlot(o, key) {
19
20
  return undefined;
20
21
  }
21
22
  }
22
- const unavailable = () => Object.freeze({ kind: 'unavailable' });
23
+ const unavailable = (code) => Object.freeze(code !== undefined ? { kind: 'unavailable', code } : { kind: 'unavailable' });
24
+ const RUN_STORE_REQUIRED_CODE = 'capability.run_store_required';
25
+ const CHECKPOINT_STORE_REQUIRED_CODE = 'capability.checkpoint_store_required';
23
26
  const unreadableAnswer = () => Object.freeze({ kind: 'unconfirmed', why: 'unreadable-answer' });
24
27
  function admissionOf(raw) {
25
28
  if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
@@ -72,7 +75,7 @@ function switchOutcomeOf(taskId, sent, body) {
72
75
  function failureOutcomeOf(e) {
73
76
  const v = atMostOnceVerdictOf(e);
74
77
  if (v.status === 501)
75
- return unavailable();
78
+ return unavailable(v.code);
76
79
  if (v.delivery === 'rejected' && v.status !== undefined) {
77
80
  if (v.status === 404)
78
81
  return Object.freeze({ kind: 'not-found' });
@@ -110,7 +113,8 @@ export async function setRunPermissionMode(runs, taskId, mode, opts = {}) {
110
113
  export function runPermissionModeSwitchLine(outcome, lang) {
111
114
  switch (outcome.kind) {
112
115
  case 'switched': {
113
- const mode = displayUntrusted(outcome.permissionMode.effective, { max: 64 });
116
+ const effective = outcome.permissionMode.effective;
117
+ const mode = permissionModeTitle(effective) ?? displayUntrusted(effective, { max: 64 });
114
118
  const reason = outcome.admission !== undefined
115
119
  ? msg(outcome.admission.foldedBy === 'entry_floor' ? 'session.permissionModeSwitch.reason.entryFloor' : 'session.permissionModeSwitch.reason.disableAutoMode')
116
120
  : outcome.permissionMode.foldedBy !== undefined
@@ -129,7 +133,13 @@ export function runPermissionModeSwitchLine(outcome, lang) {
129
133
  : renderMessage(msg('session.permissionModeSwitch.refused', { status: String(outcome.status) }), lang);
130
134
  case 'unconfirmed':
131
135
  return renderMessage(msg('session.permissionModeSwitch.unconfirmed'), lang);
132
- case 'unavailable':
133
- return renderMessage(msg('session.permissionModeSwitch.unavailable'), lang);
136
+ case 'unavailable': {
137
+ const code = readOwnDataValue(outcome, 'code');
138
+ return renderMessage(msg(code === RUN_STORE_REQUIRED_CODE
139
+ ? 'session.permissionModeSwitch.unavailableRunStore'
140
+ : code === CHECKPOINT_STORE_REQUIRED_CODE
141
+ ? 'session.permissionModeSwitch.unavailableCheckpointStore'
142
+ : 'session.permissionModeSwitch.unavailable'), lang);
143
+ }
134
144
  }
135
145
  }