@sema-agent/client-core 0.98.0 → 0.99.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 (192) hide show
  1. package/CHANGELOG.md +199 -0
  2. package/README.md +35 -22
  3. package/dist/adapt/arms.js +61 -20
  4. package/dist/adapt/panelTasks.d.ts +1 -1
  5. package/dist/adapt/panelTasks.js +2 -2
  6. package/dist/adapter/activeRunSelfHeal.d.ts +4 -3
  7. package/dist/adapter/activeRunSelfHeal.js +162 -293
  8. package/dist/adapter/downstream/eventToSdkMessage.js +52 -4
  9. package/dist/adapter/runStream.js +52 -37
  10. package/dist/adapter/types.js +2 -1
  11. package/dist/agentSession/engineSessionBackgroundStop.js +16 -8
  12. package/dist/agentsWireCaps.d.ts +0 -3
  13. package/dist/agentsWireCaps.js +33 -24
  14. package/dist/cloudConfigWireCaps.d.ts +1 -1
  15. package/dist/cloudConfigWireCaps.js +26 -4
  16. package/dist/controlRouter.d.ts +4 -2
  17. package/dist/controlRouter.js +39 -51
  18. package/dist/coreValuePorts.d.ts +26 -1
  19. package/dist/coreValuePorts.js +10 -0
  20. package/dist/corruptSessionCopy.d.ts +2 -1
  21. package/dist/corruptSessionCopy.js +3 -3
  22. package/dist/declared/supportFloorDeclared.js +1 -1
  23. package/dist/detachWire.js +3 -2
  24. package/dist/devicesFace.d.ts +34 -0
  25. package/dist/devicesFace.js +90 -0
  26. package/dist/displayUntrusted.d.ts +1 -0
  27. package/dist/displayUntrusted.js +3 -0
  28. package/dist/engineAgentPanelStore.d.ts +7 -0
  29. package/dist/engineAgentPanelStore.js +14 -0
  30. package/dist/engineCapReader.d.ts +2 -1
  31. package/dist/engineCapReader.js +7 -5
  32. package/dist/engineCapsCache.d.ts +8 -0
  33. package/dist/engineCapsCache.js +59 -6
  34. package/dist/engineCapsChange.d.ts +2 -0
  35. package/dist/engineCapsChange.js +9 -0
  36. package/dist/engineErrorCodes.d.ts +2 -1
  37. package/dist/engineErrorCodes.js +2 -7
  38. package/dist/engineRowRef.js +3 -0
  39. package/dist/engineSessionMessages.d.ts +1 -0
  40. package/dist/engineSessionMessages.js +7 -1
  41. package/dist/engineTargetSeat.d.ts +27 -0
  42. package/dist/engineTargetSeat.js +199 -0
  43. package/dist/engineWireFor.js +3 -8
  44. package/dist/env/uiLanguage.d.ts +2 -1
  45. package/dist/env/uiLanguage.js +3 -2
  46. package/dist/eventLeg.d.ts +8 -0
  47. package/dist/eventLeg.js +61 -41
  48. package/dist/fileHistoryCaptureCapability.d.ts +2 -1
  49. package/dist/fileHistoryCaptureCapability.js +12 -18
  50. package/dist/fileHistoryLegNotice.d.ts +2 -1
  51. package/dist/fileHistoryLegNotice.js +3 -3
  52. package/dist/frameBinding.d.ts +3 -0
  53. package/dist/frameBinding.js +12 -0
  54. package/dist/gateOutcome.js +16 -2
  55. package/dist/gateVocabulary.d.ts +8 -7
  56. package/dist/gateVocabulary.js +71 -73
  57. package/dist/generated/engineFactTables.d.ts +17 -1
  58. package/dist/generated/engineFactTables.js +29 -0
  59. package/dist/generated/engineNoticeTables.js +9 -3
  60. package/dist/generated/settingsSchemaFns.d.ts +6 -0
  61. package/dist/generated/settingsSchemaFns.js +57 -0
  62. package/dist/generated/settingsSchemaHooksClassifier.d.ts +2 -0
  63. package/dist/generated/settingsSchemaHooksClassifier.js +254 -0
  64. package/dist/generated/settingsSchemaTables.d.ts +1 -0
  65. package/dist/generated/settingsSchemaTables.js +4 -0
  66. package/dist/generated/settingsSchemaTypes.d.ts +105 -0
  67. package/dist/generated/settingsSchemaTypes.js +1 -0
  68. package/dist/hitl/allowRuleShadow.d.ts +2 -1
  69. package/dist/hitl/allowRuleShadow.js +17 -14
  70. package/dist/hitl/approvalCardReasons.d.ts +3 -2
  71. package/dist/hitl/approvalCardReasons.js +27 -28
  72. package/dist/hitl/approvalHolds.d.ts +3 -0
  73. package/dist/hitl/approvalHolds.js +73 -29
  74. package/dist/hitl/askClassPair.d.ts +1 -1
  75. package/dist/hitl/askClassPair.js +16 -0
  76. package/dist/hitl/askGateWire.js +2 -1
  77. package/dist/hitl/askSettlementLine.d.ts +14 -4
  78. package/dist/hitl/askSettlementLine.js +67 -47
  79. package/dist/hitl/automaticAnswerVerdict.d.ts +2 -1
  80. package/dist/hitl/automaticAnswerVerdict.js +8 -8
  81. package/dist/hitl/crashConvergedNotice.d.ts +5 -4
  82. package/dist/hitl/crashConvergedNotice.js +28 -41
  83. package/dist/hitl/frameRouter.js +7 -8
  84. package/dist/hitl/hitlBridge.d.ts +1 -0
  85. package/dist/hitl/hitlBridge.js +3 -3
  86. package/dist/hitl/persistedRulesWire.d.ts +6 -5
  87. package/dist/hitl/persistedRulesWire.js +55 -73
  88. package/dist/hitl/planReviewWire.d.ts +5 -0
  89. package/dist/hitl/planReviewWire.js +8 -0
  90. package/dist/hitl/sessionPolicyDeliverable.d.ts +2 -0
  91. package/dist/hitl/sessionPolicyDeliverable.js +46 -3
  92. package/dist/hitl/sessionPolicyWire.d.ts +2 -1
  93. package/dist/hitl/sessionPolicyWire.js +23 -24
  94. package/dist/hitl/suspendedReopen.d.ts +2 -1
  95. package/dist/hitl/suspendedReopen.js +7 -10
  96. package/dist/hitl/toolApprovalWire.d.ts +17 -5
  97. package/dist/hitl/toolApprovalWire.js +79 -40
  98. package/dist/hooksWireCaps.js +145 -56
  99. package/dist/host.d.ts +21 -0
  100. package/dist/hostTurnSettings.d.ts +71 -0
  101. package/dist/hostTurnSettings.js +291 -0
  102. package/dist/humanTurn.d.ts +14 -1
  103. package/dist/humanTurn.js +100 -12
  104. package/dist/humanTurnRead.d.ts +2 -0
  105. package/dist/humanTurnRead.js +22 -1
  106. package/dist/i18n/messages.en.d.ts +582 -0
  107. package/dist/i18n/messages.en.js +582 -0
  108. package/dist/i18n/messages.zh.js +718 -1
  109. package/dist/index.d.ts +29 -13
  110. package/dist/index.js +18 -9
  111. package/dist/inheritEnvCapability.d.ts +3 -2
  112. package/dist/inheritEnvCapability.js +3 -3
  113. package/dist/inheritEnvWire.d.ts +3 -2
  114. package/dist/inheritEnvWire.js +12 -14
  115. package/dist/interactiveHalt.js +17 -6
  116. package/dist/liveModelCatalog.d.ts +2 -1
  117. package/dist/liveModelCatalog.js +14 -8
  118. package/dist/mcpPanel.d.ts +10 -1
  119. package/dist/mcpPanel.js +40 -13
  120. package/dist/mcpProbeCapability.d.ts +2 -1
  121. package/dist/mcpProbeCapability.js +7 -11
  122. package/dist/mcpProbeWire.d.ts +3 -1
  123. package/dist/mcpProbeWire.js +5 -0
  124. package/dist/mcpWireCaps.d.ts +34 -0
  125. package/dist/mcpWireCaps.js +117 -17
  126. package/dist/memoryEntriesWire.js +5 -10
  127. package/dist/model/onboardJudgement.d.ts +93 -0
  128. package/dist/model/onboardJudgement.js +415 -0
  129. package/dist/model/tierVocabulary.d.ts +2 -0
  130. package/dist/model/tierVocabulary.js +17 -5
  131. package/dist/modelCapabilityProbe.d.ts +64 -0
  132. package/dist/modelCapabilityProbe.js +600 -0
  133. package/dist/modelIdentityResolvability.d.ts +3 -0
  134. package/dist/modelIdentityResolvability.js +26 -17
  135. package/dist/ownKey.d.ts +7 -0
  136. package/dist/ownKey.js +34 -0
  137. package/dist/panelReduce.d.ts +6 -1
  138. package/dist/panelReduce.js +50 -3
  139. package/dist/permissionRuleIssue.js +2 -0
  140. package/dist/permissionRulesWriteCapability.d.ts +2 -1
  141. package/dist/permissionRulesWriteCapability.js +8 -6
  142. package/dist/pluginHooksWire.d.ts +46 -3
  143. package/dist/pluginHooksWire.js +217 -11
  144. package/dist/printModelStatus.d.ts +63 -0
  145. package/dist/printModelStatus.js +155 -0
  146. package/dist/request/taskRequest.d.ts +11 -5
  147. package/dist/request/taskRequest.js +112 -16
  148. package/dist/resumeRefusalCopy.d.ts +12 -9
  149. package/dist/resumeRefusalCopy.js +70 -143
  150. package/dist/retryStatus.d.ts +37 -0
  151. package/dist/retryStatus.js +79 -0
  152. package/dist/scenarioRefusal.d.ts +9 -0
  153. package/dist/scenarioRefusal.js +111 -0
  154. package/dist/seam.d.ts +25 -1
  155. package/dist/seam.js +10 -1
  156. package/dist/seatContract.d.ts +260 -10
  157. package/dist/seatContract.js +692 -19
  158. package/dist/selfOrchestrationDenial.js +7 -19
  159. package/dist/sessionIdSegment.d.ts +6 -0
  160. package/dist/sessionIdSegment.js +24 -0
  161. package/dist/sessionMemoryStatus.js +6 -11
  162. package/dist/sessionPolicyCapability.d.ts +2 -1
  163. package/dist/sessionPolicyCapability.js +8 -6
  164. package/dist/settingsHooksSalvage.d.ts +44 -0
  165. package/dist/settingsHooksSalvage.js +211 -0
  166. package/dist/skillToolListsCapability.d.ts +2 -1
  167. package/dist/skillToolListsCapability.js +8 -6
  168. package/dist/sseIdleTriage.js +3 -2
  169. package/dist/subagent/engineSubagentResume.js +15 -9
  170. package/dist/subagentContentStore.d.ts +3 -0
  171. package/dist/subagentContentStore.js +22 -0
  172. package/dist/toolHistoryMismatch.js +3 -2
  173. package/dist/toolRoster.d.ts +3 -2
  174. package/dist/toolRoster.js +21 -20
  175. package/dist/turnFailurePresentation.d.ts +12 -1
  176. package/dist/turnFailurePresentation.js +212 -15
  177. package/dist/usageAnalytics.js +6 -11
  178. package/dist/userAttachmentEnvelope.d.ts +1 -0
  179. package/dist/userAttachmentEnvelope.js +37 -7
  180. package/dist/userAttachmentSeat.d.ts +21 -0
  181. package/dist/userAttachmentSeat.js +75 -0
  182. package/dist/userInvokedSkillsCapability.d.ts +11 -1
  183. package/dist/userInvokedSkillsCapability.js +31 -7
  184. package/dist/wireErrorTriage.d.ts +7 -0
  185. package/dist/wireErrorTriage.js +23 -1
  186. package/dist/wireFailureShape.js +2 -1
  187. package/dist/wireRefusalCopy.d.ts +13 -1
  188. package/dist/wireRefusalCopy.js +24 -28
  189. package/dist/workflowsJournalCapability.d.ts +2 -1
  190. package/dist/workflowsJournalCapability.js +8 -6
  191. package/docs/INTEGRATION-CLIENTS.md +542 -30
  192. package/package.json +5 -5
@@ -1,4 +1,5 @@
1
1
  import { displayUntrusted } from './displayUntrusted.js';
2
+ import { msg, renderMessage } from './i18n/translate.js';
2
3
  import { HANDS_REASON_NO_EXECUTION_ENV, projectHandsSection } from './handsSeam.js';
3
4
  const str = (v) => typeof v === 'string' && v.length > 0 ? v : undefined;
4
5
  const strArr = (v) => Array.isArray(v) ? v.filter((x) => typeof x === 'string') : undefined;
@@ -144,55 +145,55 @@ export function handsSeamReadingOf(view) {
144
145
  return { kind: 'mounted' };
145
146
  return seam.reason !== undefined ? { kind: 'not_mounted', reason: seam.reason } : { kind: 'not_mounted' };
146
147
  }
147
- const HANDS_NONE_STEM = 'this leg of the run was assembled with NO built-in file-and-shell tools';
148
- const HANDS_NONE_TAIL = "so on this leg the model had no tool of the engine's own for editing files or running commands (tools mounted by other conditions, such as external servers, and sub-runs delegated to other legs are not covered by this reading)";
149
148
  const HANDS_REASON_DISPLAY_MAX = 64;
150
- const HANDS_NOT_REPORTED_SENTENCE = 'whether this leg has built-in file-and-shell tools is not stated here — the engine did not report its hands seam (older engines and servers, and the static half of a manifest, never do); the tool roster may still answer it';
151
- export function handsSeamDetail(reading) {
149
+ function handsNotReported(lang) {
150
+ return renderMessage(msg('notice.capability.hands.notReported'), lang);
151
+ }
152
+ export function handsSeamDetail(reading, lang) {
152
153
  const kind = typeof reading === 'object' && reading !== null ? reading.kind : undefined;
153
154
  switch (kind) {
154
155
  case 'mounted':
155
- return 'this leg of the run was assembled with its built-in file tools mounted — the engine says so; whether a shell is reachable is answered by the tool roster, not by this statement';
156
+ return renderMessage(msg('notice.capability.hands.seamMounted'), lang);
156
157
  case 'not_mounted': {
157
158
  const raw = reading.reason;
158
159
  const reason = typeof raw === 'string' && raw.length > 0 ? raw : undefined;
159
160
  const why = reason === undefined
160
- ? 'the engine states it mounted no hands on this leg (it gave no reason)'
161
+ ? msg('notice.capability.hands.whyNoReason')
161
162
  : reason === HANDS_REASON_NO_EXECUTION_ENV
162
- ? 'the engine states it mounted no hands on this leg because no execution environment was supplied'
163
- : `the engine states it mounted no hands on this leg (reason: ${displayUntrusted(reason, { max: HANDS_REASON_DISPLAY_MAX })}, a word this build does not know)`;
164
- return `${HANDS_NONE_STEM} — ${why}, ${HANDS_NONE_TAIL}`;
163
+ ? msg('notice.capability.hands.whyNoExecutionEnv')
164
+ : msg('notice.capability.hands.whyUnknownReason', { reason: displayUntrusted(reason, { max: HANDS_REASON_DISPLAY_MAX }) });
165
+ return renderMessage(msg('notice.capability.hands.noneLine', { stem: msg('notice.capability.hands.noneStem'), why, tail: msg('notice.capability.hands.noneTail') }), lang);
165
166
  }
166
167
  default:
167
- return HANDS_NOT_REPORTED_SENTENCE;
168
+ return handsNotReported(lang);
168
169
  }
169
170
  }
170
- export function handsMountedDetail(reading) {
171
+ export function handsMountedDetail(reading, lang) {
171
172
  const r = reading;
172
173
  const kind = typeof r === 'object' && r !== null ? r.kind : undefined;
173
174
  switch (kind) {
174
175
  case 'mounted': {
175
176
  const rows = r.rows;
176
177
  if (typeof rows === 'number' && Number.isInteger(rows) && rows > 0)
177
- return `this run was assembled with file-and-shell tools mounted (${rows} of them)`;
178
- return HANDS_NOT_REPORTED_SENTENCE;
178
+ return renderMessage(msg('notice.capability.hands.rosterMounted', { rows }), lang);
179
+ return handsNotReported(lang);
179
180
  }
180
181
  case 'not-mounted':
181
- return `${HANDS_NONE_STEM} — the engine reports a roster and not one row was mounted by that condition, ${HANDS_NONE_TAIL}`;
182
+ return renderMessage(msg('notice.capability.hands.rosterNotMounted', { stem: msg('notice.capability.hands.noneStem'), tail: msg('notice.capability.hands.noneTail') }), lang);
182
183
  case 'unknown':
183
184
  switch (r.why) {
184
185
  case 'no-manifest':
185
- return 'whether this run has file-and-shell tools is unknown — no assembly manifest was observed for it';
186
+ return renderMessage(msg('notice.capability.hands.unknownNoManifest'), lang);
186
187
  case 'no-roster':
187
- return 'whether this run has file-and-shell tools is unknown — the manifest carries no tool roster (the static half never does; only an engine-minted effective manifest reports one)';
188
+ return renderMessage(msg('notice.capability.hands.unknownNoRoster'), lang);
188
189
  case 'roster-unreadable':
189
- return 'whether this run has file-and-shell tools is unknown — the tool roster could not be read in the shape this reader knows';
190
+ return renderMessage(msg('notice.capability.hands.unknownRosterUnreadable'), lang);
190
191
  case 'mount-condition-absent':
191
- return 'whether this run has file-and-shell tools is unknown — this engine does not report which condition mounted each tool, so an empty count here would be a fact about the reader, not about the run';
192
+ return renderMessage(msg('notice.capability.hands.unknownMountConditionAbsent'), lang);
192
193
  }
193
- return HANDS_NOT_REPORTED_SENTENCE;
194
+ return handsNotReported(lang);
194
195
  }
195
- return HANDS_NOT_REPORTED_SENTENCE;
196
+ return handsNotReported(lang);
196
197
  }
197
198
  export function toolRosterNames(roster) {
198
199
  return roster?.entries.map((e) => e.name) ?? [];
@@ -1,4 +1,7 @@
1
+ import type { UiLanguage } from './env/uiLanguage.js';
1
2
  import { type TurnWireErrorVerdict } from './wireErrorTriage.js';
3
+ import { type LiveModelCatalog } from './liveModelCatalog.js';
4
+ import type { TaskRequestLike } from './request/taskRequest.js';
2
5
  export type TurnFailureCategory = 'auth' | 'billing' | 'rate_limited' | 'invalid_request' | 'capability_unavailable' | 'timeout' | 'server_error' | 'connection_lost' | 'parked' | 'internal';
3
6
  export declare const TURN_FAILURE_CATEGORIES: readonly TurnFailureCategory[];
4
7
  export interface TurnFailureRetry {
@@ -15,11 +18,19 @@ export interface TurnFailurePresentation {
15
18
  readonly name?: string;
16
19
  readonly retry: TurnFailureRetry;
17
20
  readonly waitSec?: number;
21
+ readonly notes: readonly string[];
22
+ readonly waitNote?: string;
23
+ }
24
+ export interface TurnFailureNotesContext {
25
+ readonly requestedScenario?: string;
26
+ readonly sentRequest?: TaskRequestLike;
27
+ readonly modelCatalog?: LiveModelCatalog | null;
28
+ readonly nowMs?: number;
18
29
  }
19
30
  export declare function turnFailurePresentation(err: {} | null | undefined, evidence?: {
20
31
  readonly bound?: boolean;
21
32
  readonly reopened?: boolean;
22
- }): TurnFailurePresentation;
33
+ }, notesCtx?: TurnFailureNotesContext, lang?: UiLanguage): TurnFailurePresentation;
23
34
  export type SeatTurnFailure = Pick<TurnFailurePresentation, 'category' | 'retry' | 'waitSec' | 'headline' | 'errorCode'>;
24
35
  export declare function seatTurnFailureOf(err: {} | null | undefined, evidence?: {
25
36
  readonly bound?: boolean;
@@ -1,10 +1,18 @@
1
+ import { msg, renderMessage } from './i18n/translate.js';
1
2
  import { classifyTurnWireError, effectiveWireErrorCode, isPreStreamDrainingReject } from './wireErrorTriage.js';
2
3
  import { isSseIdleError } from './sseIdleTriage.js';
3
4
  import { isCodeClassifiedGateFailure } from './gateFailureCodes.js';
4
5
  import { CORRUPT_ROW_ERROR_CODE, CORRUPT_SESSION_ERROR_CODE, MEMORY_CONTROL_PLANE_CORRUPT, SCENARIO_ASSIGNMENT_UNKNOWN_ERROR_CODE } from './engineErrorCodes.js';
5
- import { toolDeclarationRefusalFromError } from './wireRefusalCopy.js';
6
+ import { bodyShapeRefusalFromError, bodyShapeRefusalTouchesSettings, fleetLeaseUnavailableContent, fleetLeaseUnavailableFromError, hostPostureRefusalContent, hostPostureRefusalFromError, settingsPermissionBucketRefusalContent, settingsPermissionBucketRefusalFromError, toolDeclarationRefusalContent, toolDeclarationRefusalFromError, usageWindowExhaustedContent, usageWindowExhaustedFromError, } from './wireRefusalCopy.js';
6
7
  import { THROWN_SLOT_UNREADABLE, readErrorNumber, readThrownSlot, wireFailureTextsOf } from './wireFailureShape.js';
7
8
  import { provablyNotSent } from './engineWireFor.js';
9
+ import { corruptStoredRowContent, scenarioDenyFromError, scenarioNotAllowedContent } from './wireErrorTriage.js';
10
+ import { checkpointUnsupportedVersionContent, checkpointUnsupportedVersionFromError, requireExistingSessionRefusalContent, requireExistingSessionRefusalFromError, resumeContextUnavailableContent, resumeContextUnavailableFromError, resumePolicyBlockContent, resumePolicyBlockFromError, resumeRefusalContent, resumeRefusalFromError, } from './resumeRefusalCopy.js';
11
+ import { resumeReopenContent, resumeReopenFromError } from './hitl/suspendedReopen.js';
12
+ import { isCorruptSessionError } from './corruptSession.js';
13
+ import { corruptSessionContent } from './corruptSessionCopy.js';
14
+ import { inheritEnvRefusalContent, inheritEnvRefusalFromError } from './inheritEnvWire.js';
15
+ import { modelCandidateRejectedContent, modelCandidateRejectedOf } from './liveModelCatalog.js';
8
16
  const CATEGORY_TABLE = Object.freeze({
9
17
  auth: true,
10
18
  billing: true,
@@ -18,12 +26,12 @@ const CATEGORY_TABLE = Object.freeze({
18
26
  internal: true,
19
27
  });
20
28
  export const TURN_FAILURE_CATEGORIES = Object.freeze(Object.keys(CATEGORY_TABLE));
21
- const TURN_FAILURE_HEADLINES = Object.freeze({
22
- connection_lost_not_sent: 'The engine is unreachable — your message did not run. Check that the engine is up, then send it again.',
23
- connection_lost: 'The connection to the engine was lost — it is not known whether your message ran. Check the run’s status, or reattach to it, before sending it again.',
24
- timeout: 'The event stream went silent — the run may still be going on the engine. Check its status before sending again.',
25
- parked: 'The event stream ended while the run had not reached a terminal state — the run is likely parked waiting for a decision, not crashed. Check its status and decide anything pending; the turn did not complete here.',
26
- internal: 'This client hit an internal error — it is a bug in the client, not an engine outage, and the turn did not complete.',
29
+ const TURN_FAILURE_HEADLINE_KEYS = Object.freeze({
30
+ connection_lost_not_sent: 'notice.failure.headline.connectionLostNotSent',
31
+ connection_lost: 'notice.failure.headline.connectionLost',
32
+ timeout: 'notice.failure.headline.timeout',
33
+ parked: 'notice.failure.headline.parked',
34
+ internal: 'notice.failure.headline.internal',
27
35
  });
28
36
  function waitSecOf(err) {
29
37
  const ms = readThrownSlot(err, 'retryAfterMs', 'data');
@@ -88,43 +96,43 @@ function connectionLostBeforeSend(err, evidence) {
88
96
  return false;
89
97
  return typeof err === 'object' && err !== null && provablyNotSent(err);
90
98
  }
91
- export function turnFailurePresentation(err, evidence) {
99
+ export function turnFailurePresentation(err, evidence, notesCtx, lang) {
92
100
  const verdict = classifyTurnWireError(err);
93
101
  let shape;
94
- let headline;
102
+ let headlineKey;
95
103
  let detail;
96
104
  let name;
97
105
  if (verdict.kind === 'http') {
98
106
  shape = httpShapeOf(err, verdict);
99
107
  detail = verdict.message;
100
108
  if (shape.category === 'internal')
101
- headline = TURN_FAILURE_HEADLINES.internal;
109
+ headlineKey = TURN_FAILURE_HEADLINE_KEYS.internal;
102
110
  }
103
111
  else if (verdict.kind === 'stream-ended-without-terminal') {
104
112
  shape = { category: 'parked', offer: false };
105
- headline = TURN_FAILURE_HEADLINES.parked;
113
+ headlineKey = TURN_FAILURE_HEADLINE_KEYS.parked;
106
114
  detail = wireFailureTextsOf(err).reason;
107
115
  }
108
116
  else if (verdict.kind === 'transport') {
109
117
  const notSent = connectionLostBeforeSend(err, evidence);
110
118
  shape = { category: 'connection_lost', offer: notSent };
111
- headline = notSent ? TURN_FAILURE_HEADLINES.connection_lost_not_sent : TURN_FAILURE_HEADLINES.connection_lost;
119
+ headlineKey = notSent ? TURN_FAILURE_HEADLINE_KEYS.connection_lost_not_sent : TURN_FAILURE_HEADLINE_KEYS.connection_lost;
112
120
  detail = verdict.detail;
113
121
  }
114
122
  else if (isIdleWatchdog(err)) {
115
123
  shape = { category: 'timeout', offer: true };
116
- headline = TURN_FAILURE_HEADLINES.timeout;
124
+ headlineKey = TURN_FAILURE_HEADLINE_KEYS.timeout;
117
125
  detail = verdict.detail;
118
126
  }
119
127
  else if (BROWSER_FETCH_FAILURE.test(verdict.detail)) {
120
128
  const notSent = connectionLostBeforeSend(err, evidence);
121
129
  shape = { category: 'connection_lost', offer: notSent };
122
- headline = notSent ? TURN_FAILURE_HEADLINES.connection_lost_not_sent : TURN_FAILURE_HEADLINES.connection_lost;
130
+ headlineKey = notSent ? TURN_FAILURE_HEADLINE_KEYS.connection_lost_not_sent : TURN_FAILURE_HEADLINE_KEYS.connection_lost;
123
131
  detail = verdict.detail;
124
132
  }
125
133
  else {
126
134
  shape = { category: 'internal', offer: false };
127
- headline = TURN_FAILURE_HEADLINES.internal;
135
+ headlineKey = TURN_FAILURE_HEADLINE_KEYS.internal;
128
136
  detail = verdict.detail;
129
137
  name = verdict.name;
130
138
  }
@@ -133,6 +141,8 @@ export function turnFailurePresentation(err, evidence) {
133
141
  const wireRetriable = readThrownSlot(err, 'retriable', 'data') === true && !isCodeClassifiedGateFailure(errorCode, true);
134
142
  const retry = wireRetriable && shape.category !== 'parked' ? { offer: true, basis: 'wire' } : { offer: shape.offer, basis: 'category' };
135
143
  const waitSec = waitSecOf(err);
144
+ const chain = turnFailureNotesOf(err, verdict, notesCtx, waitSec, lang);
145
+ const headline = headlineKey !== undefined ? renderMessage(msg(headlineKey), lang) : undefined;
136
146
  return Object.freeze({
137
147
  category: shape.category,
138
148
  kind: verdict.kind,
@@ -143,8 +153,195 @@ export function turnFailurePresentation(err, evidence) {
143
153
  ...(name !== undefined ? { name } : {}),
144
154
  retry: Object.freeze(retry),
145
155
  ...(waitSec !== undefined ? { waitSec } : {}),
156
+ notes: chain.notes,
157
+ ...(chain.waitNote !== undefined ? { waitNote: chain.waitNote } : {}),
146
158
  });
147
159
  }
160
+ function quotaDetailOf(err) {
161
+ if (readThrownSlot(err, 'status', 'data') !== 429 || readThrownSlot(err, 'errorCode', 'data') !== 'quota_exhausted')
162
+ return null;
163
+ const detail = readThrownSlot(err, 'detail', 'data');
164
+ const slot = (key) => (typeof detail === 'object' && detail !== null ? readThrownSlot(detail, key, 'data') : undefined);
165
+ const windowType = slot('windowType');
166
+ const remaining = slot('remaining');
167
+ const resetAt = slot('resetAt');
168
+ const isEstimate = slot('isEstimate');
169
+ const pool = slot('pool');
170
+ return {
171
+ ...(windowType === 'fiveHour' || windowType === 'weekly' || windowType === 'budget' ? { windowType } : {}),
172
+ ...(typeof remaining === 'number' && Number.isFinite(remaining) ? { remaining } : {}),
173
+ ...(typeof resetAt === 'string' || resetAt === null ? { resetAt } : {}),
174
+ ...(typeof isEstimate === 'boolean' ? { isEstimate } : {}),
175
+ ...(typeof pool === 'string' && pool.length > 0 ? { pool } : {}),
176
+ };
177
+ }
178
+ const MONTH_KEYS = [
179
+ 'notice.failure.month.jan',
180
+ 'notice.failure.month.feb',
181
+ 'notice.failure.month.mar',
182
+ 'notice.failure.month.apr',
183
+ 'notice.failure.month.may',
184
+ 'notice.failure.month.jun',
185
+ 'notice.failure.month.jul',
186
+ 'notice.failure.month.aug',
187
+ 'notice.failure.month.sep',
188
+ 'notice.failure.month.oct',
189
+ 'notice.failure.month.nov',
190
+ 'notice.failure.month.dec',
191
+ ];
192
+ function quotaResetInstant(resetAtIso, nowMs, lang) {
193
+ const t = Date.parse(resetAtIso);
194
+ if (!Number.isFinite(t))
195
+ return undefined;
196
+ const d = new Date(t);
197
+ const now = new Date(nowMs);
198
+ const hm = `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
199
+ const sameDay = d.getFullYear() === now.getFullYear() && d.getMonth() === now.getMonth() && d.getDate() === now.getDate();
200
+ if (sameDay)
201
+ return hm;
202
+ const monthKey = MONTH_KEYS[d.getMonth()];
203
+ return renderMessage(msg('notice.failure.quota.instantOtherDay', { month: monthKey === undefined ? '' : msg(monthKey), day: d.getDate(), time: hm }), lang);
204
+ }
205
+ function quotaExhaustedLine(detail, nowMs, lang) {
206
+ const part = (key, params) => renderMessage(msg(key, params), lang);
207
+ const parts = [];
208
+ if (detail.windowType === 'fiveHour')
209
+ parts.push(part('notice.failure.quota.fiveHour'));
210
+ else if (detail.windowType === 'weekly')
211
+ parts.push(part('notice.failure.quota.weekly'));
212
+ else if (detail.windowType === 'budget') {
213
+ if (detail.pool === 'personal')
214
+ parts.push(part('notice.failure.quota.budgetPersonal'));
215
+ else if (detail.pool)
216
+ parts.push(part('notice.failure.quota.budgetPool', { pool: detail.pool }));
217
+ else
218
+ parts.push(part('notice.failure.quota.budget'));
219
+ }
220
+ else
221
+ parts.push(part('notice.failure.quota.usage'));
222
+ if (typeof detail.remaining === 'number' && Number.isFinite(detail.remaining)) {
223
+ parts.push(detail.windowType === 'budget'
224
+ ? part('notice.failure.quota.remainingBudget', { amount: `$${(detail.remaining / 1_000_000).toFixed(2)}` })
225
+ : part('notice.failure.quota.remainingTokens', { count: detail.remaining.toLocaleString('en-US') }));
226
+ }
227
+ const resetText = typeof detail.resetAt === 'string' ? quotaResetInstant(detail.resetAt, nowMs, lang) : undefined;
228
+ if (resetText)
229
+ parts.push(detail.isEstimate === false ? part('notice.failure.quota.resetAt', { time: resetText }) : part('notice.failure.quota.resetAround', { time: resetText }));
230
+ parts.push(part('notice.failure.quota.refresh'));
231
+ parts.push(part('notice.failure.quota.noBypass'));
232
+ return parts.join(part('notice.failure.quota.join'));
233
+ }
234
+ function protectiveSettingsAtRisk(sent, lang) {
235
+ const out = [];
236
+ const permsRaw = sent.permissions;
237
+ const perms = typeof permsRaw === 'object' && permsRaw !== null ? permsRaw : {};
238
+ const deny = Array.isArray(perms.deny) ? perms.deny.length : 0;
239
+ const ask = Array.isArray(perms.ask) ? perms.ask.length : 0;
240
+ if (deny > 0)
241
+ out.push(renderMessage(msg(deny === 1 ? 'notice.failure.settings.denyRules.one' : 'notice.failure.settings.denyRules.other', { count: deny }), lang));
242
+ if (ask > 0)
243
+ out.push(renderMessage(msg(ask === 1 ? 'notice.failure.settings.askRules.one' : 'notice.failure.settings.askRules.other', { count: ask }), lang));
244
+ if (perms.disableBypassPermissionsMode !== undefined)
245
+ out.push('permissions.disableBypassPermissionsMode');
246
+ if (perms.disableAutoMode !== undefined)
247
+ out.push('permissions.disableAutoMode');
248
+ if (typeof perms.defaultMode === 'string')
249
+ out.push('permissions.defaultMode');
250
+ return out;
251
+ }
252
+ function settingsBodyShapeNote(err, sentRequest, lang) {
253
+ if (readThrownSlot(err, 'status', 'data') !== 400 || readThrownSlot(err, 'errorCode', 'data') !== 'request.body_shape')
254
+ return undefined;
255
+ const touches = bodyShapeRefusalTouchesSettings(bodyShapeRefusalFromError(err), sentRequest);
256
+ if (touches === false)
257
+ return undefined;
258
+ const rawSettings = sentRequest === undefined ? undefined : readThrownSlot(sentRequest, 'settings', 'data');
259
+ if (typeof rawSettings !== 'object' || rawSettings === null || Array.isArray(rawSettings))
260
+ return undefined;
261
+ const sentSettings = rawSettings;
262
+ if (Object.keys(sentSettings).length === 0)
263
+ return undefined;
264
+ const lines = [renderMessage(msg('notice.failure.settings.lead'), lang)];
265
+ const atRisk = protectiveSettingsAtRisk(sentSettings, lang);
266
+ if (atRisk.length > 0) {
267
+ lines.push(renderMessage(msg('notice.failure.settings.safety', { risks: atRisk.join(renderMessage(msg('notice.failure.settings.riskJoin'), lang)) }), lang));
268
+ lines.push(renderMessage(msg('notice.failure.settings.stillLocal'), lang));
269
+ }
270
+ return lines.join('\n');
271
+ }
272
+ function isRefusedRequest(err) {
273
+ const status = readThrownSlot(err, 'status', 'data');
274
+ return typeof status === 'number' && status >= 400 && status < 500;
275
+ }
276
+ function waitSentence(sec, lang) {
277
+ return renderMessage(sec === 1 ? msg('notice.failure.wait.one') : msg('notice.failure.wait.other', { seconds: sec }), lang);
278
+ }
279
+ function turnFailureNotesOf(err, verdict, ctx, waitSec, lang) {
280
+ const lines = [];
281
+ try {
282
+ const sentRequest = ctx === undefined ? undefined : ctx.sentRequest;
283
+ const nowMs = ctx !== undefined && typeof ctx.nowMs === 'number' && Number.isFinite(ctx.nowMs) ? ctx.nowMs : Date.now();
284
+ const quota = quotaDetailOf(err);
285
+ if (quota)
286
+ lines.push(quotaExhaustedLine(quota, nowMs, lang));
287
+ const scenarioDeny = quota ? null : scenarioDenyFromError(err);
288
+ if (scenarioDeny)
289
+ lines.push(scenarioNotAllowedContent(scenarioDeny, ctx?.requestedScenario));
290
+ const resumeRefusal = quota || scenarioDeny ? null : resumeRefusalFromError(err);
291
+ if (resumeRefusal)
292
+ lines.push(resumeRefusalContent(resumeRefusal, lang));
293
+ const requireExisting = quota || scenarioDeny || resumeRefusal ? null : requireExistingSessionRefusalFromError(err);
294
+ if (requireExisting)
295
+ lines.push(requireExistingSessionRefusalContent(requireExisting, lang));
296
+ const resumeReopen = quota || scenarioDeny || resumeRefusal || requireExisting ? null : resumeReopenFromError(err);
297
+ if (resumeReopen)
298
+ lines.push(resumeReopenContent(resumeReopen, lang));
299
+ const resumeGone = quota || scenarioDeny || resumeRefusal || requireExisting || resumeReopen ? null : resumeContextUnavailableFromError(err);
300
+ if (resumeGone)
301
+ lines.push(resumeContextUnavailableContent(resumeGone, lang));
302
+ const checkpointUnsupported = quota || scenarioDeny || resumeRefusal || requireExisting || resumeReopen || resumeGone ? null : checkpointUnsupportedVersionFromError(err);
303
+ if (checkpointUnsupported)
304
+ lines.push(checkpointUnsupportedVersionContent(checkpointUnsupported, undefined, lang));
305
+ const storedRowWhere = quota || scenarioDeny || resumeRefusal || resumeReopen || resumeGone ? undefined : verdict.kind === 'http' ? verdict.storedRowWhere : undefined;
306
+ if (storedRowWhere !== undefined)
307
+ lines.push(corruptStoredRowContent(storedRowWhere));
308
+ if (storedRowWhere === undefined && isCorruptSessionError(err))
309
+ lines.push(corruptSessionContent(lang));
310
+ const usageWindow = quota || scenarioDeny || resumeRefusal || resumeReopen || resumeGone ? null : usageWindowExhaustedFromError(err);
311
+ if (usageWindow)
312
+ lines.push(usageWindowExhaustedContent(usageWindow));
313
+ const hostPosture = quota || scenarioDeny || resumeRefusal || resumeReopen || resumeGone || storedRowWhere !== undefined ? null : hostPostureRefusalFromError(err, sentRequest);
314
+ if (hostPosture)
315
+ lines.push(hostPostureRefusalContent(hostPosture));
316
+ const inheritEnv = hostPosture ? null : inheritEnvRefusalFromError(err, sentRequest);
317
+ if (inheritEnv)
318
+ lines.push(inheritEnvRefusalContent(inheritEnv, lang));
319
+ const bucket = inheritEnv ? null : settingsPermissionBucketRefusalFromError(err);
320
+ const toolDecl = toolDeclarationRefusalFromError(err);
321
+ if (toolDecl)
322
+ lines.push(toolDeclarationRefusalContent(toolDecl));
323
+ const lease = fleetLeaseUnavailableFromError(err);
324
+ if (lease)
325
+ lines.push(fleetLeaseUnavailableContent(lease));
326
+ const policyBlock = quota || scenarioDeny || resumeRefusal || resumeReopen || resumeGone || usageWindow || hostPosture ? null : resumePolicyBlockFromError(err);
327
+ if (policyBlock)
328
+ lines.push(resumePolicyBlockContent(policyBlock, lang));
329
+ if (bucket)
330
+ lines.push(settingsPermissionBucketRefusalContent(bucket));
331
+ const settingsRefusal = settingsBodyShapeNote(err, sentRequest, lang);
332
+ if (settingsRefusal !== undefined)
333
+ lines.push(settingsRefusal);
334
+ if (isRefusedRequest(err) && ctx !== undefined && ctx.modelCatalog !== undefined) {
335
+ const rejected = modelCandidateRejectedContent(modelCandidateRejectedOf(ctx.modelCatalog), lang);
336
+ if (rejected !== undefined)
337
+ lines.push(rejected);
338
+ }
339
+ return Object.freeze({ notes: Object.freeze(lines), ...(waitSec !== undefined && !lease && !usageWindow ? { waitNote: waitSentence(waitSec, lang) } : {}) });
340
+ }
341
+ catch {
342
+ return Object.freeze({ notes: Object.freeze(lines) });
343
+ }
344
+ }
148
345
  export function seatTurnFailureOf(err, evidence) {
149
346
  const p = turnFailurePresentation(err, evidence);
150
347
  return {
@@ -1,3 +1,4 @@
1
+ import { readThrownSlot, THROWN_SLOT_UNREADABLE } from './wireFailureShape.js';
1
2
  export const USAGE_SENTENCES = Object.freeze({
2
3
  analyticsUnavailable: 'Usage analytics aren’t available on this backend.',
3
4
  analyticsUnread: 'Usage analytics couldn’t be read just now.',
@@ -10,18 +11,12 @@ const isRecord = (v) => typeof v === 'object' && v !== null && !Array.isArray(v)
10
11
  const nonNeg = (v) => typeof v === 'number' && Number.isFinite(v) && v >= 0;
11
12
  const nonEmpty = (v) => typeof v === 'string' && v.length > 0;
12
13
  export function classifyUsageFailure(e) {
13
- let code;
14
- let status;
15
- try {
16
- const o = e;
17
- const rawCode = o?.errorCode;
18
- const rawStatus = o?.status;
19
- code = typeof rawCode === 'string' ? rawCode : undefined;
20
- status = typeof rawStatus === 'number' ? rawStatus : undefined;
21
- }
22
- catch {
14
+ const rawCode = readThrownSlot(e, 'errorCode', 'data');
15
+ const rawStatus = readThrownSlot(e, 'status', 'data');
16
+ if (rawCode === THROWN_SLOT_UNREADABLE || rawStatus === THROWN_SLOT_UNREADABLE)
23
17
  return { kind: 'failed', error: e };
24
- }
18
+ const code = typeof rawCode === 'string' ? rawCode : undefined;
19
+ const status = typeof rawStatus === 'number' ? rawStatus : undefined;
25
20
  if (code !== undefined) {
26
21
  if (code.startsWith('capability.'))
27
22
  return { kind: 'unavailable', why: 'capability' };
@@ -78,5 +78,6 @@ export type UserAttachmentDescriptor = {
78
78
  };
79
79
  export declare function showAttachmentName(s: string): string;
80
80
  export declare function mintEnvelopeOf(d: DescriptorView, imageDelivery: 'attached' | 'not-attached'): string;
81
+ export declare function seatTextOf(d: DescriptorView): string | undefined;
81
82
  export declare function mintReferenceOf(d: DescriptorView): string;
82
83
  export declare function mintUserAttachmentEnvelope(descriptor: UserAttachmentDescriptor): string;
@@ -83,11 +83,7 @@ function mintText(d) {
83
83
  : `Full path: ${path} (lines ${d.startLine}–${d.startLine + n - 1} of ${d.totalLines}; the other lines are not included). The client read it from disk and copied those lines between the two fence lines below. ${dataNote(d.source, "the file's contents")}`;
84
84
  return fenced(anyEscaped ? [l1, l2, ESCAPE_NOTE] : [l1, l2], body);
85
85
  }
86
- function mintNotebook(d) {
87
- const path = showAttachmentName(d.path);
88
- const l1 = firstLine(d.source, 'a Jupyter notebook', d.name);
89
- if (d.cells.length === 0)
90
- return `${l1}\nFull path: ${path} (0 cells). The client read it from disk; it has no cells.`;
86
+ function notebookBody(d) {
91
87
  let cellsWithImages = 0;
92
88
  let cellsWithOversized = 0;
93
89
  const parts = [];
@@ -113,9 +109,16 @@ function mintNotebook(d) {
113
109
  if (hasImage)
114
110
  cellsWithImages++;
115
111
  }
112
+ return { text: parts.join('\n'), cellsWithImages, cellsWithOversized };
113
+ }
114
+ function mintNotebook(d) {
115
+ const path = showAttachmentName(d.path);
116
+ const l1 = firstLine(d.source, 'a Jupyter notebook', d.name);
117
+ if (d.cells.length === 0)
118
+ return `${l1}\nFull path: ${path} (0 cells). The client read it from disk; it has no cells.`;
119
+ const { text, cellsWithImages, cellsWithOversized } = notebookBody(d);
116
120
  let anyEscaped = false;
117
- const body = parts
118
- .join('\n')
121
+ const body = text
119
122
  .split('\n')
120
123
  .map((line, i) => {
121
124
  const shown = showBodyLine(line);
@@ -281,6 +284,33 @@ function referenceSentence(d) {
281
284
  }
282
285
  return `The user mentioned the ${what} ${name} (${path}) in this message; its contents are not included here.`;
283
286
  }
287
+ export function seatTextOf(d) {
288
+ let text;
289
+ switch (d.kind) {
290
+ case 'text':
291
+ if (d.source !== 'at-mention' || !d.complete || d.lines.length === 0)
292
+ return undefined;
293
+ text = d.lines.join('\n');
294
+ break;
295
+ case 'notebook': {
296
+ if (d.source !== 'at-mention' || d.cells.length === 0)
297
+ return undefined;
298
+ const body = notebookBody(d);
299
+ if (body.cellsWithImages > 0 || body.cellsWithOversized > 0)
300
+ return undefined;
301
+ text = body.text;
302
+ break;
303
+ }
304
+ case 'directory':
305
+ if (d.entries.length === 0 || d.totalEntries > d.entries.length)
306
+ return undefined;
307
+ text = d.entries.map(showAttachmentName).join('\n');
308
+ break;
309
+ default:
310
+ return undefined;
311
+ }
312
+ return text.length > 0 ? text : undefined;
313
+ }
284
314
  export function mintReferenceOf(d) {
285
315
  return `${TAG_OPEN}${tagAttributes(d)} />\n${referenceSentence(d)}`;
286
316
  }
@@ -0,0 +1,21 @@
1
+ export interface UserAttachmentSeatLimits {
2
+ readonly maxEntries: number;
3
+ readonly maxRenderedTextChars: number;
4
+ }
5
+ export interface UserAttachmentSeatPresent {
6
+ readonly kind: 'present';
7
+ readonly entries: readonly string[];
8
+ readonly kinds: readonly string[];
9
+ readonly supplies: readonly string[];
10
+ readonly limits: UserAttachmentSeatLimits;
11
+ }
12
+ export type UserAttachmentSeatReading = {
13
+ kind: 'unobserved';
14
+ } | {
15
+ kind: 'unreadable';
16
+ } | {
17
+ kind: 'not_reported';
18
+ } | UserAttachmentSeatPresent;
19
+ export declare function projectUserAttachmentSeatCapability(caps: {} | null | undefined): UserAttachmentSeatReading | undefined;
20
+ export declare function observedUserAttachmentSeat(baseUrl?: string | undefined): UserAttachmentSeatReading;
21
+ export declare function userAttachmentSeatAdmits(reading: UserAttachmentSeatReading | undefined): 'yes' | 'no' | 'unknown';
@@ -0,0 +1,75 @@
1
+ import { engineWireTarget } from './engineWireTarget.js';
2
+ import { createEngineCapReader } from './engineCapReader.js';
3
+ import { ownDataField, readOwnDataValue } from './ownKey.js';
4
+ const _seatPin = [true, true];
5
+ void _seatPin;
6
+ function stringList(v) {
7
+ if (!Array.isArray(v))
8
+ return undefined;
9
+ const n = ownDataField(v, 'length');
10
+ if (typeof n !== 'number' || !Number.isSafeInteger(n) || n < 0)
11
+ return undefined;
12
+ const out = [];
13
+ for (let i = 0; i < n; i += 1) {
14
+ const d = Object.getOwnPropertyDescriptor(v, i);
15
+ if (d === undefined || !('value' in d) || typeof d.value !== 'string')
16
+ return undefined;
17
+ out.push(d.value);
18
+ }
19
+ return out;
20
+ }
21
+ const positiveInt = (v) => (typeof v === 'number' && Number.isSafeInteger(v) && v >= 1 ? v : undefined);
22
+ const isPlainRecord = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
23
+ export function projectUserAttachmentSeatCapability(caps) {
24
+ try {
25
+ if (!isPlainRecord(caps))
26
+ return undefined;
27
+ const seat = ownDataField(caps, 'userAttachments');
28
+ if (seat === undefined)
29
+ return { kind: 'not_reported' };
30
+ if (!isPlainRecord(seat))
31
+ return undefined;
32
+ const entries = stringList(ownDataField(seat, 'entries'));
33
+ const kinds = stringList(ownDataField(seat, 'kinds'));
34
+ const supplies = stringList(ownDataField(seat, 'supplies'));
35
+ const limits = ownDataField(seat, 'limits');
36
+ if (entries === undefined || kinds === undefined || supplies === undefined || !isPlainRecord(limits))
37
+ return undefined;
38
+ const maxEntries = positiveInt(ownDataField(limits, 'maxEntries'));
39
+ const maxRenderedTextChars = positiveInt(ownDataField(limits, 'maxRenderedTextChars'));
40
+ if (maxEntries === undefined || maxRenderedTextChars === undefined)
41
+ return undefined;
42
+ return { kind: 'present', entries, kinds, supplies, limits: { maxEntries, maxRenderedTextChars } };
43
+ }
44
+ catch {
45
+ return undefined;
46
+ }
47
+ }
48
+ const reader = createEngineCapReader({ name: 'userAttachments', project: projectUserAttachmentSeatCapability });
49
+ export function observedUserAttachmentSeat(baseUrl = engineWireTarget()?.baseUrl) {
50
+ return reader.observed(baseUrl);
51
+ }
52
+ export function userAttachmentSeatAdmits(reading) {
53
+ try {
54
+ if (!isPlainRecord(reading))
55
+ return 'unknown';
56
+ const kind = readOwnDataValue(reading, 'kind');
57
+ if (kind === 'not_reported')
58
+ return 'no';
59
+ if (kind !== 'present')
60
+ return 'unknown';
61
+ const has = (key, word) => {
62
+ const list = stringList(readOwnDataValue(reading, key));
63
+ return list === undefined ? undefined : list.includes(word);
64
+ };
65
+ const objective = has('entries', 'objective');
66
+ const text = has('supplies', 'content_text');
67
+ const client = has('kinds', 'client_text');
68
+ if (objective === undefined || text === undefined || client === undefined)
69
+ return 'unknown';
70
+ return objective && text && client ? 'yes' : 'no';
71
+ }
72
+ catch {
73
+ return 'unknown';
74
+ }
75
+ }
@@ -1,3 +1,4 @@
1
+ import type { UiLanguage } from './env/uiLanguage.js';
1
2
  export type UserInvokedSkillsReading = {
2
3
  kind: 'unobserved';
3
4
  } | {
@@ -12,4 +13,13 @@ export type UserInvokedSkillsReading = {
12
13
  export declare function projectUserInvokedSkillsCapability(caps: unknown): UserInvokedSkillsReading | undefined;
13
14
  export declare function observedUserInvokedSkills(baseUrl?: string | undefined): UserInvokedSkillsReading;
14
15
  export declare function userInvokedSkillsAvailable(reading: UserInvokedSkillsReading): 'yes' | 'no' | 'unknown';
15
- export declare function userInvokedSkillsDoctorDetail(reading: UserInvokedSkillsReading): string;
16
+ export declare function userInvokedSkillsDoctorDetail(reading: UserInvokedSkillsReading, lang?: UiLanguage): string;
17
+ export interface UserInvokedSkillsDeclaration {
18
+ readonly field: {
19
+ readonly userInvokedSkills?: readonly string[];
20
+ };
21
+ readonly withheld?: {
22
+ readonly cause: Exclude<UserInvokedSkillsReading['kind'], 'present'>;
23
+ };
24
+ }
25
+ export declare function userInvokedSkillsDeclaration(names: readonly string[] | null | undefined, reading: UserInvokedSkillsReading): UserInvokedSkillsDeclaration;