@sema-agent/client-core 0.109.0 → 0.111.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 (236) hide show
  1. package/CHANGELOG.md +342 -0
  2. package/README.md +88 -32
  3. package/dist/abortableSleep.d.ts +7 -0
  4. package/dist/abortableSleep.js +37 -0
  5. package/dist/adapt/arms.js +45 -11
  6. package/dist/adapt/toolCards.d.ts +3 -1
  7. package/dist/adapt/toolCards.js +4 -2
  8. package/dist/adapter/activeRunSelfHeal.d.ts +39 -0
  9. package/dist/adapter/activeRunSelfHeal.js +171 -3
  10. package/dist/adapter/downstream/eventToSdkMessage.js +20 -15
  11. package/dist/adapter/downstream/terminalToSdkResult.d.ts +2 -0
  12. package/dist/adapter/downstream/terminalToSdkResult.js +11 -0
  13. package/dist/adapter/runStream.d.ts +5 -0
  14. package/dist/adapter/runStream.js +15 -3
  15. package/dist/attachmentRoute.d.ts +20 -0
  16. package/dist/attachmentRoute.js +39 -0
  17. package/dist/attachmentsWireCaps.js +1 -1
  18. package/dist/builtinOutputStyles.d.ts +12 -0
  19. package/dist/builtinOutputStyles.js +147 -0
  20. package/dist/compactionStatus.d.ts +9 -0
  21. package/dist/compactionStatus.js +29 -5
  22. package/dist/controlRouter.js +2 -1
  23. package/dist/coreValuePorts.d.ts +39 -1
  24. package/dist/coreValuePorts.js +10 -0
  25. package/dist/declared/supportFloorDeclared.js +1 -1
  26. package/dist/displayUntrusted.d.ts +1 -0
  27. package/dist/displayUntrusted.js +138 -14
  28. package/dist/engineErrorCodes.d.ts +3 -0
  29. package/dist/engineErrorCodes.js +3 -0
  30. package/dist/engineHealthFacts.d.ts +20 -0
  31. package/dist/engineHealthFacts.js +42 -1
  32. package/dist/engineHealthProbeVerdict.d.ts +23 -0
  33. package/dist/engineHealthProbeVerdict.js +115 -0
  34. package/dist/engineInlineTaskStats.d.ts +4 -3
  35. package/dist/engineInlineTaskStats.js +18 -2
  36. package/dist/engineNoticeCodes.d.ts +10 -0
  37. package/dist/engineNoticeCodes.js +36 -6
  38. package/dist/engineRepoint.d.ts +147 -0
  39. package/dist/engineRepoint.js +310 -0
  40. package/dist/engineRowRef.d.ts +3 -0
  41. package/dist/engineRowRef.js +13 -1
  42. package/dist/engineSessionMessages.d.ts +2 -1
  43. package/dist/engineSessionMessages.js +5 -0
  44. package/dist/engineSwapCrossing.d.ts +57 -0
  45. package/dist/engineSwapCrossing.js +297 -0
  46. package/dist/engineUnavailable.d.ts +44 -0
  47. package/dist/engineUnavailable.js +492 -0
  48. package/dist/errorSupplementRoles.js +2 -1
  49. package/dist/eventLeg.js +3 -3
  50. package/dist/fleet/fleetProjection.js +3 -2
  51. package/dist/generated/engineFactTables.d.ts +2 -0
  52. package/dist/generated/engineFactTables.js +12 -0
  53. package/dist/generated/sdkFactTables.d.ts +2 -1
  54. package/dist/generated/sdkFactTables.js +5 -0
  55. package/dist/generated/settingsSchemaTables.d.ts +4 -0
  56. package/dist/generated/settingsSchemaTables.js +1 -0
  57. package/dist/hitl/approvalAskers.d.ts +6 -0
  58. package/dist/hitl/approvalAskers.js +30 -0
  59. package/dist/hitl/approvalCardReasons.js +6 -3
  60. package/dist/hitl/approvalDelivery.d.ts +1 -0
  61. package/dist/hitl/approvalDelivery.js +1 -1
  62. package/dist/hitl/approvalRequestFrameReading.d.ts +7 -0
  63. package/dist/hitl/approvalRequestFrameReading.js +11 -0
  64. package/dist/hitl/approvalResolution.d.ts +9 -2
  65. package/dist/hitl/approvalResolution.js +6 -1
  66. package/dist/hitl/askGateWire.js +5 -8
  67. package/dist/hitl/automaticAnswerVerdict.js +2 -2
  68. package/dist/hitl/decisionConflict.js +6 -3
  69. package/dist/hitl/decisionRefusal.d.ts +2 -0
  70. package/dist/hitl/decisionRefusal.js +8 -8
  71. package/dist/hitl/decisionRefusalCarrier.d.ts +3 -0
  72. package/dist/hitl/decisionRefusalCarrier.js +348 -0
  73. package/dist/hitl/decisionSend.d.ts +10 -0
  74. package/dist/hitl/decisionSend.js +57 -17
  75. package/dist/hitl/editedApprovalAcceptance.d.ts +25 -0
  76. package/dist/hitl/editedApprovalAcceptance.js +26 -0
  77. package/dist/hitl/elicitationRefusal.d.ts +4 -0
  78. package/dist/hitl/elicitationRefusal.js +23 -0
  79. package/dist/hitl/frameRouter.d.ts +2 -2
  80. package/dist/hitl/frameRouter.js +12 -33
  81. package/dist/hitl/gateIdentity.d.ts +5 -0
  82. package/dist/hitl/gateIdentity.js +23 -0
  83. package/dist/hitl/gateLedger.d.ts +0 -4
  84. package/dist/hitl/gateLedger.js +0 -19
  85. package/dist/hitl/hitlBridge.d.ts +7 -0
  86. package/dist/hitl/hitlBridge.js +14 -8
  87. package/dist/hitl/hitlHostSurface.d.ts +3 -1
  88. package/dist/hitl/hitlHostSurface.js +14 -4
  89. package/dist/hitl/hostWithdrawal.d.ts +15 -0
  90. package/dist/hitl/hostWithdrawal.js +38 -0
  91. package/dist/hitl/localAllowRedemption.d.ts +67 -0
  92. package/dist/hitl/localAllowRedemption.js +256 -0
  93. package/dist/hitl/parkResolver.js +32 -13
  94. package/dist/hitl/persistedRulesWire.d.ts +16 -0
  95. package/dist/hitl/persistedRulesWire.js +72 -2
  96. package/dist/hitl/planReviewWire.d.ts +3 -0
  97. package/dist/hitl/planReviewWire.js +37 -10
  98. package/dist/hitl/policyDenyCause.d.ts +1 -1
  99. package/dist/hitl/policyDenyCause.js +1 -1
  100. package/dist/hitl/resumeRunningCard.d.ts +1 -0
  101. package/dist/hitl/resumeRunningCard.js +1 -0
  102. package/dist/hitl/screenCardCarriers.d.ts +15 -0
  103. package/dist/hitl/screenCardCarriers.js +41 -0
  104. package/dist/hitl/screenDecisionCards.d.ts +98 -0
  105. package/dist/hitl/screenDecisionCards.js +362 -0
  106. package/dist/hitl/sessionPolicyWire.js +6 -4
  107. package/dist/hitl/standingRows.d.ts +4 -1
  108. package/dist/hitl/standingRows.js +33 -1
  109. package/dist/hitl/toolApprovalWire.d.ts +64 -15
  110. package/dist/hitl/toolApprovalWire.js +470 -125
  111. package/dist/hooksWireCaps.js +31 -1
  112. package/dist/hostSettingNoticeArm.d.ts +3 -0
  113. package/dist/hostSettingNoticeArm.js +12 -0
  114. package/dist/hostTurnSettings.d.ts +13 -4
  115. package/dist/hostTurnSettings.js +150 -10
  116. package/dist/humanTurnRead.js +11 -1
  117. package/dist/i18n/messages.en.d.ts +212 -0
  118. package/dist/i18n/messages.en.js +217 -5
  119. package/dist/i18n/messages.zh.js +235 -5
  120. package/dist/i18n/translate.d.ts +3 -0
  121. package/dist/i18n/translate.js +78 -10
  122. package/dist/i18n/zh.d.ts +1 -0
  123. package/dist/i18n/zh.js +1 -0
  124. package/dist/index.d.ts +56 -16
  125. package/dist/index.js +40 -12
  126. package/dist/inheritEnvCapability.js +2 -0
  127. package/dist/inputAuthor.d.ts +16 -0
  128. package/dist/inputAuthor.js +44 -0
  129. package/dist/interactiveHalt.js +2 -1
  130. package/dist/interactiveInterrupt.js +20 -2
  131. package/dist/interruptRefusalNote.d.ts +2 -0
  132. package/dist/interruptRefusalNote.js +17 -0
  133. package/dist/liveModelCatalog.d.ts +2 -0
  134. package/dist/liveModelCatalog.js +23 -9
  135. package/dist/liveQuestionStore.d.ts +4 -1
  136. package/dist/liveQuestionStore.js +12 -3
  137. package/dist/localEngineSeatReading.d.ts +2 -2
  138. package/dist/localEngineSeatReading.js +5 -3
  139. package/dist/localEngineSpawn.d.ts +0 -7
  140. package/dist/localEngineSpawn.js +0 -92
  141. package/dist/localEngineStep.d.ts +2 -2
  142. package/dist/localEngineStep.js +2 -0
  143. package/dist/mcpEngineLeg.d.ts +3 -0
  144. package/dist/mcpEngineLeg.js +38 -1
  145. package/dist/mcpPanel.js +2 -0
  146. package/dist/mcpReconnect.d.ts +9 -1
  147. package/dist/mcpReconnect.js +124 -19
  148. package/dist/mcpWireCaps.js +1 -1
  149. package/dist/model/engineModelWords.d.ts +3 -0
  150. package/dist/model/engineModelWords.js +13 -0
  151. package/dist/model/subagentModelWord.d.ts +21 -2
  152. package/dist/model/subagentModelWord.js +100 -29
  153. package/dist/modelCapabilityProbe.js +7 -43
  154. package/dist/modsFace.d.ts +8 -1
  155. package/dist/modsFace.js +27 -0
  156. package/dist/notifications.d.ts +36 -6
  157. package/dist/notifications.js +170 -20
  158. package/dist/ownKey.d.ts +6 -5
  159. package/dist/ownKey.js +31 -7
  160. package/dist/peerFrames.d.ts +40 -2
  161. package/dist/peerFrames.js +158 -3
  162. package/dist/pendingUserRow.d.ts +4 -0
  163. package/dist/pendingUserRow.js +16 -0
  164. package/dist/permissionsDeclaration.d.ts +1 -0
  165. package/dist/permissionsDeclaration.js +11 -1
  166. package/dist/planReviewCardFacts.d.ts +3 -0
  167. package/dist/planReviewCardFacts.js +52 -2
  168. package/dist/pluginHooksWire.d.ts +10 -1
  169. package/dist/pluginHooksWire.js +96 -7
  170. package/dist/printToolResultFrame.js +4 -2
  171. package/dist/promptBlockedCopy.js +11 -6
  172. package/dist/promptSuggestionBatch.d.ts +7 -0
  173. package/dist/promptSuggestionBatch.js +9 -0
  174. package/dist/promptSuggestionsTail.d.ts +39 -0
  175. package/dist/promptSuggestionsTail.js +116 -0
  176. package/dist/replayBody.js +10 -2
  177. package/dist/request/taskRequest.d.ts +5 -0
  178. package/dist/request/taskRequest.js +142 -21
  179. package/dist/requestCoordinate.d.ts +1 -0
  180. package/dist/requestCoordinate.js +13 -0
  181. package/dist/resumeRefusalCopy.d.ts +1 -3
  182. package/dist/resumeRefusalCopy.js +2 -1
  183. package/dist/retryStatus.d.ts +2 -2
  184. package/dist/retryStatus.js +2 -0
  185. package/dist/rewindArchiveReading.js +4 -4
  186. package/dist/runCancelContext.d.ts +2 -1
  187. package/dist/runCancelContext.js +1 -0
  188. package/dist/runPermissionModeSwitch.js +2 -2
  189. package/dist/scenarioRefusal.js +11 -13
  190. package/dist/scenarioSkills.js +11 -6
  191. package/dist/seam.d.ts +27 -2
  192. package/dist/seam.js +17 -1
  193. package/dist/seatContract.d.ts +189 -12
  194. package/dist/seatContract.js +401 -9
  195. package/dist/sessionBackground.js +2 -1
  196. package/dist/sessionBackgroundHandoff.js +2 -1
  197. package/dist/sessionCostReading.d.ts +70 -0
  198. package/dist/sessionCostReading.js +150 -0
  199. package/dist/sessionEngine.d.ts +2 -0
  200. package/dist/sessionEngine.js +111 -0
  201. package/dist/sessionHooksRoster.d.ts +4 -0
  202. package/dist/sessionHooksRoster.js +158 -0
  203. package/dist/sessionMemoryStatus.d.ts +2 -1
  204. package/dist/sessionMemoryStatus.js +2 -2
  205. package/dist/sessionReadRedaction.d.ts +7 -0
  206. package/dist/sessionReadRedaction.js +26 -0
  207. package/dist/sessionSyncFileHistory.d.ts +4 -0
  208. package/dist/sessionSyncFileHistory.js +27 -0
  209. package/dist/settingsHooksSalvage.js +2 -1
  210. package/dist/sseIdleTriage.d.ts +13 -1
  211. package/dist/sseIdleTriage.js +25 -6
  212. package/dist/storeDurabilityWord.d.ts +15 -0
  213. package/dist/storeDurabilityWord.js +18 -0
  214. package/dist/storePostureCapability.d.ts +7 -3
  215. package/dist/storePostureCapability.js +69 -39
  216. package/dist/subagent/engineSubagentResume.js +2 -2
  217. package/dist/subagent/engineSubagentTail.js +8 -1
  218. package/dist/subagentContentStore.d.ts +11 -0
  219. package/dist/subagentContentStore.js +28 -2
  220. package/dist/terminalStatuses.d.ts +1 -0
  221. package/dist/terminalStatuses.js +4 -1
  222. package/dist/toolResult.d.ts +1 -0
  223. package/dist/toolResult.js +2 -0
  224. package/dist/turnFailurePresentation.js +9 -2
  225. package/dist/typePins.d.ts +11 -0
  226. package/dist/usageAnalytics.d.ts +17 -0
  227. package/dist/usageAnalytics.js +44 -2
  228. package/dist/userAttachmentEnvelope.d.ts +2 -1
  229. package/dist/userAttachmentEnvelope.js +10 -0
  230. package/dist/wireErrorTriage.js +2 -1
  231. package/dist/wireFailureShape.d.ts +6 -5
  232. package/dist/wireFailureShape.js +150 -38
  233. package/dist/wireRefusalCopy.d.ts +16 -5
  234. package/dist/wireRefusalCopy.js +119 -86
  235. package/docs/INTEGRATION-CLIENTS.md +4228 -21
  236. package/package.json +7 -3
@@ -115,6 +115,7 @@ export declare function assembledRuleRows(input: PermissionsDeclarationInput, ca
115
115
  export interface PermissionsAllowKeptFacts {
116
116
  readonly problemCount?: number;
117
117
  readonly managedPolicyPending?: true;
118
+ readonly managedPolicyFetch?: 'in_progress' | 'failed' | 'unknown';
118
119
  readonly where?: string;
119
120
  }
120
121
  export declare function permissionsAllowKeptNotice(report: Pick<PermissionsDeclarationReport, 'kept'>, settingsRead: 'clean' | 'partial' | 'unreadable', facts?: PermissionsAllowKeptFacts): string | undefined;
@@ -543,11 +543,21 @@ export function permissionsAllowKeptNotice(report, settingsRead, facts) {
543
543
  const rawCount = readFact('problemCount');
544
544
  const rawWhere = readFact('where');
545
545
  const managedPending = readFact('managedPolicyPending') === true;
546
+ const rawFetch = readFact('managedPolicyFetch');
547
+ const fetchState = rawFetch === 'in_progress' || rawFetch === 'failed' ? rawFetch : 'unknown';
546
548
  const count = typeof rawCount === 'number' && Number.isInteger(rawCount) && rawCount >= 1 ? rawCount : undefined;
547
549
  const where = typeof rawWhere === 'string' && rawWhere.length >= 1 && rawWhere.length <= 200 && !/\p{Cc}/u.test(rawWhere) ? rawWhere : undefined;
548
550
  const head = `${String(held)} permissions.allow rule${held === 1 ? ' was' : 's were'} not sent to the engine for this run because `;
549
551
  if (settingsRead === 'partial' && managedPending && count === 1) {
550
- return (`${head}the managed policy for your account has not been received yet (sema keeps fetching it; allow rules apply once it arrives). ` +
552
+ if (fetchState === 'in_progress') {
553
+ return (`${head}the managed policy for your account has not been received yet (sema keeps fetching it; allow rules apply once it arrives). ` +
554
+ 'Run again once it has been received (deny and ask rules, and rules given with --allowedTools, are still sent).');
555
+ }
556
+ if (fetchState === 'failed') {
557
+ return (`${head}the managed policy for your account has not been received yet (this run will not fetch again; the next run retries). ` +
558
+ 'Run again to retry fetching it (deny and ask rules, and rules given with --allowedTools, are still sent).');
559
+ }
560
+ return (`${head}the managed policy for your account has not been received yet. ` +
551
561
  'Run again once it has been received (deny and ask rules, and rules given with --allowedTools, are still sent).');
552
562
  }
553
563
  const why = settingsRead === 'partial'
@@ -16,6 +16,7 @@ export interface PlanReviewCardFacts {
16
16
  readonly taskId: string;
17
17
  readonly checkpointId?: string;
18
18
  readonly body: PlanReviewBody;
19
+ readonly evidenceRevision?: number;
19
20
  }
20
21
  export declare function planReviewBodyOf(text: string): Exclude<PlanReviewBody, {
21
22
  kind: 'unknown';
@@ -91,10 +92,12 @@ export interface PlanBodyClaimLeg {
91
92
  }>;
92
93
  overflow: boolean;
93
94
  unreadable: boolean;
95
+ readonly seen: Set<string>;
94
96
  }
95
97
  export declare function openPlanBodyClaimLeg(): PlanBodyClaimLeg;
96
98
  export declare function stepPlanBodyClaim(leg: PlanBodyClaimLeg, frame: object, subFlow: boolean): void;
97
99
  export declare function planReviewParkFacts(leg: PlanBodyClaimLeg, result: {} | null | undefined): PlanReviewCardFacts | undefined;
100
+ export declare function planReviewClaimStep(leg: PlanBodyClaimLeg, frame: object, subFlow: boolean, seq: string | undefined): PlanReviewCardFacts | undefined;
98
101
  export interface PlanReviewFactsTaken {
99
102
  readonly sessionKey: string;
100
103
  readonly taskId: string;
@@ -3,6 +3,7 @@ import { readRunTerminal, runTerminalGateKind } from './runTerminal.js';
3
3
  import { SDK_PLAN_REVIEW_GATE_KINDS } from './generated/sdkFactTables.js';
4
4
  import { CORE_PRESENT_PLAN_TOOL_NAME } from './generated/engineFactTables.js';
5
5
  import { DEFAULT_SESSION_KEY } from './sessionSlot.js';
6
+ import { doneResultView } from './replayBody.js';
6
7
  const PLAN_BODY_MAX_LENGTH = 200_000;
7
8
  const PLAN_BODY_UNKNOWN_CAUSES = Object.freeze([
8
9
  'not_observed', 'gate_unidentified', 'gate_moved', 'unreadable', 'ambiguous_call', 'malformed_frame', 'conflicting_claims', 'not_presented', 'not_carried',
@@ -29,6 +30,13 @@ export function mintPlanReviewCardFacts(taskId, checkpointId, body) {
29
30
  const b = checkpointId === undefined ? unknownBody('gate_unidentified') : body;
30
31
  return Object.freeze({ taskId, ...(checkpointId !== undefined ? { checkpointId } : {}), body: Object.isFrozen(b) ? b : Object.freeze({ ...b }) });
31
32
  }
33
+ let evidenceRevisionSeq = 0;
34
+ function withEvidenceRevision(facts, evidenceRevision) {
35
+ return Object.freeze({ taskId: facts.taskId, ...(facts.checkpointId !== undefined ? { checkpointId: facts.checkpointId } : {}), body: facts.body, evidenceRevision });
36
+ }
37
+ function isEvidenceRevision(v) {
38
+ return typeof v === 'number' && Number.isSafeInteger(v) && v > 0;
39
+ }
32
40
  export function unknownBody(cause) {
33
41
  return Object.freeze({ kind: 'unknown', cause });
34
42
  }
@@ -59,7 +67,11 @@ export function planReviewCardFactsFrom(v) {
59
67
  const checkpointId = ck.present ? ck.value : undefined;
60
68
  if (checkpointId === undefined && !(body.kind === 'unknown' && body.cause === 'gate_unidentified'))
61
69
  return undefined;
62
- return mintPlanReviewCardFacts(taskId, checkpointId, body);
70
+ const eg = ownData(v, 'evidenceRevision');
71
+ if (eg.present && !isEvidenceRevision(eg.value))
72
+ return undefined;
73
+ const minted = mintPlanReviewCardFacts(taskId, checkpointId, body);
74
+ return eg.present ? withEvidenceRevision(minted, eg.value) : minted;
63
75
  }
64
76
  catch {
65
77
  return undefined;
@@ -167,6 +179,10 @@ export function commitPlanReviewFacts(ticket, facts, claim, highWater) {
167
179
  if (prev !== undefined && prev.checkpointId === checkpointId && (isConflict(prev.facts) || !samePlanClaim(prev.claim, claim))) {
168
180
  out = mintPlanReviewCardFacts(facts.taskId, checkpointId, unknownBody('conflicting_claims'));
169
181
  }
182
+ const reused = prev !== undefined && prev.checkpointId === checkpointId && prev.facts.evidenceRevision !== undefined && samePlanReviewCardFacts(prev.facts, out)
183
+ ? prev.facts.evidenceRevision
184
+ : undefined;
185
+ out = withEvidenceRevision(out, reused ?? ++evidenceRevisionSeq);
170
186
  rec.gate = { checkpointId, facts: out, claim, generation: ticket.generation, ...(highWater !== undefined ? { highWater } : {}) };
171
187
  return out;
172
188
  }
@@ -338,7 +354,7 @@ function foldMarker(rec, frame) {
338
354
  }
339
355
  const LEG_BUFFER_MAX = 256;
340
356
  export function openPlanBodyClaimLeg() {
341
- return { buffer: [], overflow: false, unreadable: false };
357
+ return { buffer: [], overflow: false, unreadable: false, seen: new Set() };
342
358
  }
343
359
  function bindLeg(leg, taskId) {
344
360
  const rec = recordFor(DEFAULT_SESSION_KEY, taskId);
@@ -425,6 +441,40 @@ export function planReviewParkFacts(leg, result) {
425
441
  return taskId === undefined ? undefined : mintPlanReviewCardFacts(taskId, checkpointId, unknownBody('unreadable'));
426
442
  }
427
443
  }
444
+ export function planReviewClaimStep(leg, frame, subFlow, seq) {
445
+ if (claimSeqDedupe(leg, frame, seq) === 'duplicate')
446
+ return undefined;
447
+ stepPlanBodyClaim(leg, frame, subFlow);
448
+ let result;
449
+ try {
450
+ if (frame.type !== 'done')
451
+ return undefined;
452
+ result = frame.result;
453
+ }
454
+ catch {
455
+ return undefined;
456
+ }
457
+ return planReviewParkFacts(leg, doneResultView(result).body);
458
+ }
459
+ function claimSeqDedupe(leg, frame, seq) {
460
+ try {
461
+ if (seq !== undefined && classOf(frame) !== 'other') {
462
+ if (leg.seen.has(seq))
463
+ return 'duplicate';
464
+ leg.seen.add(seq);
465
+ while (leg.seen.size > SETTLED_CALLS_MAX) {
466
+ const oldest = leg.seen.values().next();
467
+ if (oldest.done === true)
468
+ break;
469
+ leg.seen.delete(oldest.value);
470
+ }
471
+ }
472
+ return 'fresh';
473
+ }
474
+ catch {
475
+ return 'class_unreadable';
476
+ }
477
+ }
428
478
  function legRecord(leg, taskId) {
429
479
  return leg.taskId === undefined || leg.taskId === taskId ? bindLeg(leg, taskId) : recordFor(DEFAULT_SESSION_KEY, taskId);
430
480
  }
@@ -12,8 +12,10 @@ export declare const PLUGIN_HOOK_REASONS: readonly ["projected", "shell_event",
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", "key_not_read", "source_not_authored"];
15
+ export declare const SETTINGS_HOOK_DROP_REASONS: readonly ["exec_form_unsupported", "mcp_tool_unsupported", "key_not_read", "source_not_authored", "workspace_untrusted", "governance_unreadable", "policy_disable_all", "no_executor"];
16
16
  export type SettingsHookDropReason = (typeof SETTINGS_HOOK_DROP_REASONS)[number];
17
+ export type HookEntryTypeWord = 'command' | 'prompt' | 'agent' | 'http' | 'mcp_tool';
18
+ export declare const HOOK_ENTRY_TYPE_WORDS: readonly ["command", "prompt", "agent", "http", "mcp_tool"];
17
19
  export type HookSettingsSource = 'policySettings' | 'userSettings' | 'projectSettings' | 'localSettings' | 'flagSettings';
18
20
  export interface PluginHookKey {
19
21
  readonly pluginId: string;
@@ -34,6 +36,7 @@ export interface SettingsHookDrop {
34
36
  readonly event: string;
35
37
  readonly groupIndex: number;
36
38
  readonly hookIndex: number;
39
+ readonly type?: HookEntryTypeWord;
37
40
  readonly reason: SettingsHookDropReason;
38
41
  readonly notRun: boolean;
39
42
  readonly unreadKeys?: readonly (readonly (string | number)[])[];
@@ -43,6 +46,7 @@ export interface SettingsHookVerdict {
43
46
  readonly event: string;
44
47
  readonly groupIndex: number;
45
48
  readonly hookIndex: number;
49
+ readonly type?: HookEntryTypeWord;
46
50
  readonly disposition: PluginHookDisposition;
47
51
  readonly reason: PluginHookReason;
48
52
  }
@@ -51,6 +55,7 @@ export interface SettingsHookExclusion {
51
55
  readonly event: string;
52
56
  readonly groupIndex: number;
53
57
  readonly hookIndex: number;
58
+ readonly type?: HookEntryTypeWord;
54
59
  readonly reason: PluginHookExclusionReason;
55
60
  }
56
61
  export interface SettingsHooksSalvageRow {
@@ -169,6 +174,9 @@ export declare function decideSessionHooks(input: SessionHookDecisionInput): {
169
174
  readonly unreadable: number;
170
175
  };
171
176
  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;
177
+ export declare function hookEntryTypeWordOf(entry: {} | null | undefined): {
178
+ readonly type?: HookEntryTypeWord;
179
+ };
172
180
  export interface SettingsHookFilterResult {
173
181
  readonly groups: WireHookGroup[];
174
182
  readonly drops: SettingsHookDrop[];
@@ -196,6 +204,7 @@ export interface SettingsHooksLocalGroup<T> {
196
204
  export declare function settingsHooksKeptLocally<T>(plan: HooksWirePlan, event: string, group: SettingsHooksLocalGroup<T>, opts?: {
197
205
  readonly engineCompaction?: 'performed' | 'not_performed';
198
206
  }): readonly T[];
207
+ export declare function pluginHooksKeptLocally<T>(plan: HooksWirePlan, pluginId: string, event: string, groupIndex: number, hooks: readonly T[]): readonly T[];
199
208
  export declare function pluginHookReasonText(reason: PluginHookReason): string;
200
209
  export declare function settingsHookDropText(reason: SettingsHookDropReason): string;
201
210
  export declare function pluginHookExecutorLabel(v: PluginHookVerdict): string;
@@ -1,7 +1,9 @@
1
1
  import { displayUntrusted } from './displayUntrusted.js';
2
2
  import { appendOwnElement, readOwnDataValue } from './ownKey.js';
3
+ import { inSlotReadOf } from './wireFailureShape.js';
3
4
  import { msg, renderMessage } from './i18n/translate.js';
4
5
  import { SETTINGS_ENGINE_OWNED_HOOK_EVENTS } from './generated/settingsSchemaTables.js';
6
+ import { SETTINGS_HOOK_EVENTS } from './generated/settingsSchemaFns.js';
5
7
  import { HOOK_SETTINGS_SOURCE_TEXT, hooksEntryUnreadKeys, hooksGroupAuthoredSource, hooksGroupUnreadKeys, hooksKeyCoordinateText, hooksSalvageMaskOf, salvageSettingsHooksEntries, } from './settingsHooksSalvage.js';
6
8
  export const ENGINE_FIRED_HOOK_EVENTS = SETTINGS_ENGINE_OWNED_HOOK_EVENTS;
7
9
  export const PLUGIN_HOOK_DISPOSITIONS = ['engine', 'shell', 'not_run'];
@@ -40,7 +42,19 @@ export const PLUGIN_HOOK_EXCLUSION_REASONS = [
40
42
  'managed_plugins_only',
41
43
  'prompt_not_user_authored',
42
44
  ];
43
- export const SETTINGS_HOOK_DROP_REASONS = ['exec_form_unsupported', 'mcp_tool_unsupported', 'key_not_read', 'source_not_authored'];
45
+ export const SETTINGS_HOOK_DROP_REASONS = [
46
+ 'exec_form_unsupported',
47
+ 'mcp_tool_unsupported',
48
+ 'key_not_read',
49
+ 'source_not_authored',
50
+ 'workspace_untrusted',
51
+ 'governance_unreadable',
52
+ 'policy_disable_all',
53
+ 'no_executor',
54
+ ];
55
+ export const HOOK_ENTRY_TYPE_WORDS = Object.freeze(['command', 'prompt', 'agent', 'http', 'mcp_tool']);
56
+ const _hookEntryTypePins = [true, true, true, true];
57
+ void _hookEntryTypePins;
44
58
  export function hooksWirePlanShapeOf(v) {
45
59
  try {
46
60
  if (typeof v !== 'object' || v === null || Array.isArray(v))
@@ -135,6 +149,7 @@ function hookExecutorOf(x) {
135
149
  return judged(x.shellRuns ? 'shell' : 'not_run', why);
136
150
  }
137
151
  const ENGINE_EVENTS = new Set(ENGINE_FIRED_HOOK_EVENTS);
152
+ const SHELL_OWNED_EVENTS = new Set(SETTINGS_HOOK_EVENTS.filter((e) => !ENGINE_EVENTS.has(e)));
138
153
  const USER_CONFIG_REF_ONE = /\$\{user_config\.[^}]+\}/;
139
154
  const USER_CONFIG_REF_ALL = /\$\{user_config\.([^}]+)\}/g;
140
155
  function isRecord(v) {
@@ -484,10 +499,23 @@ export function filterSettingsHookGroups(source, event, groups, execFormCapable,
484
499
  const excluded = [];
485
500
  const out = [];
486
501
  let stripped = 0;
487
- const engineEvent = ENGINE_EVENTS.has(event);
502
+ const engineEvent = isEngineFiredHookEvent(event);
503
+ const shellOwned = !engineEvent && SHELL_OWNED_EVENTS.has(event);
488
504
  groups.forEach((group, groupIndex) => {
489
505
  if (mask !== undefined && mask.groupDropped(event, groupIndex))
490
506
  return;
507
+ if (!engineEvent) {
508
+ if (!shellOwned || !isRecord(group) || !Array.isArray(group.hooks))
509
+ return;
510
+ group.hooks.forEach((entry, hookIndex) => {
511
+ if (mask !== undefined && mask.entryDropped(event, groupIndex, hookIndex))
512
+ return;
513
+ if (shellRunsType(isRecord(entry) && typeof entry.type === 'string' ? entry.type : undefined))
514
+ return;
515
+ appendOwnElement(drops, { source, event, groupIndex, hookIndex, reason: 'no_executor', notRun: true });
516
+ });
517
+ return;
518
+ }
491
519
  if (!isRecord(group) || !Array.isArray(group.hooks)) {
492
520
  appendOwnElement(out, group);
493
521
  return;
@@ -513,27 +541,29 @@ export function filterSettingsHookGroups(source, event, groups, execFormCapable,
513
541
  const sourceNotAuthored = earlier === undefined && groupSourceAuthored && (authored === 'unreadable' || entryUnread !== undefined);
514
542
  const unreadKeys = earlier === undefined && !sourceNotAuthored ? unreadKeyPathsOf(event, groupIndex, hookIndex, groupUnread ?? [], entryUnread) : [];
515
543
  const reason = earlier ?? (sourceNotAuthored ? 'source_not_authored' : unreadKeys.length > 0 ? 'key_not_read' : undefined);
544
+ const typed = hookEntryTypeWordOf(entry);
516
545
  if (reason !== undefined) {
517
546
  appendOwnElement(drops, {
518
547
  source,
519
548
  event,
520
549
  groupIndex,
521
550
  hookIndex,
551
+ ...typed,
522
552
  reason,
523
- notRun: engineEvent && !shellRunsType(isRecord(entry) && typeof entry.type === 'string' ? entry.type : undefined),
553
+ notRun: !shellRunsType(isRecord(entry) && typeof entry.type === 'string' ? entry.type : undefined),
524
554
  ...(reason === 'key_not_read' ? { unreadKeys } : {}),
525
555
  });
526
556
  changed = true;
527
557
  return;
528
558
  }
529
- if (engineEvent && isRecord(entry) && judgeableSettingsEntry(entry)) {
559
+ if (isRecord(entry) && judgeableSettingsEntry(entry)) {
530
560
  const ex = judge(event, group, entry);
531
561
  if (ex.kind === 'excluded') {
532
- appendOwnElement(excluded, { source, event, groupIndex, hookIndex, reason: ex.reason });
562
+ appendOwnElement(excluded, { source, event, groupIndex, hookIndex, ...typed, reason: ex.reason });
533
563
  changed = true;
534
564
  return;
535
565
  }
536
- appendOwnElement(verdicts, { source, event, groupIndex, hookIndex, disposition: ex.disposition, reason: ex.reason });
566
+ appendOwnElement(verdicts, { source, event, groupIndex, hookIndex, ...typed, disposition: ex.disposition, reason: ex.reason });
537
567
  if (ex.disposition === 'shell') {
538
568
  changed = true;
539
569
  return;
@@ -558,6 +588,17 @@ export function filterSettingsHookGroups(source, event, groups, execFormCapable,
558
588
  });
559
589
  return { groups: out, drops, stripped, verdicts, excluded };
560
590
  }
591
+ export function hookEntryTypeWordOf(entry) {
592
+ try {
593
+ if (!isRecord(entry))
594
+ return {};
595
+ const t = readOwnDataValue(entry, 'type');
596
+ return typeof t === 'string' && HOOK_ENTRY_TYPE_WORDS.includes(t) ? { type: t } : {};
597
+ }
598
+ catch {
599
+ return {};
600
+ }
601
+ }
561
602
  function judgeableSettingsEntry(entry) {
562
603
  const type = entry.type;
563
604
  if (typeof type !== 'string' || !ENGINE_HOOK_TYPE_RUNS.has(type))
@@ -729,6 +770,8 @@ function skipMatcherKey(matcher) {
729
770
  return matcher === undefined || matcher === null || matcher === '' || matcher === '*' ? 'null' : JSON.stringify(matcher);
730
771
  }
731
772
  export function settingsHooksKeptLocally(plan, event, group, opts) {
773
+ if (planClosedLocally(plan))
774
+ return [];
732
775
  let raw;
733
776
  try {
734
777
  raw = group?.hooks;
@@ -802,6 +845,42 @@ export function settingsHooksKeptLocally(plan, event, group, opts) {
802
845
  return hooks;
803
846
  }
804
847
  }
848
+ function planClosedLocally(plan) {
849
+ if (typeof plan !== 'object' || plan === null)
850
+ return false;
851
+ let slot;
852
+ try {
853
+ slot = inSlotReadOf(plan, 'closedBy');
854
+ }
855
+ catch {
856
+ return true;
857
+ }
858
+ if (slot.state === 'absent')
859
+ return false;
860
+ if (slot.state !== 'present')
861
+ return true;
862
+ return slot.value !== null && slot.value !== undefined;
863
+ }
864
+ export function pluginHooksKeptLocally(plan, pluginId, event, groupIndex, hooks) {
865
+ if (planClosedLocally(plan))
866
+ return [];
867
+ if (!Array.isArray(hooks))
868
+ return hooks;
869
+ try {
870
+ let kept = null;
871
+ hooks.forEach((h, i) => {
872
+ const engineRuns = pluginHookVerdictOf(plan, pluginId, event, groupIndex, i)?.disposition === 'engine';
873
+ if (engineRuns)
874
+ kept ??= hooks.slice(0, i);
875
+ else if (kept !== null)
876
+ appendOwnElement(kept, h);
877
+ });
878
+ return kept ?? hooks;
879
+ }
880
+ catch {
881
+ return hooks;
882
+ }
883
+ }
805
884
  function settingsRowBaseOf(v) {
806
885
  if (!isRecord(v))
807
886
  return undefined;
@@ -852,7 +931,7 @@ function settingsExclusionRowOf(v) {
852
931
  const REASON_TEXT = {
853
932
  projected: 'it is sent to the engine',
854
933
  shell_event: 'this client runs this event itself',
855
- no_executor: 'neither the engine nor this client runs this event',
934
+ no_executor: renderMessage(msg('config.hooks.plan.reason.noExecutor')),
856
935
  engine_not_local: 'the engine was not started by this client on this machine, so it cannot reach the plugin files',
857
936
  engine_locality_unknown: 'it is not known whether the engine was started by this client on this machine, so plugin hooks are not sent to it',
858
937
  engine_capability_absent: 'the connected engine does not support plugin hooks',
@@ -881,6 +960,10 @@ const DROP_TEXT = {
881
960
  mcp_tool_unsupported: 'MCP tool hooks are not supported by the engine',
882
961
  key_not_read: REASON_TEXT.key_not_read,
883
962
  source_not_authored: REASON_TEXT.source_not_authored,
963
+ workspace_untrusted: renderMessage(msg('config.hooks.plan.reason.closedUntrusted')),
964
+ governance_unreadable: renderMessage(msg('config.hooks.plan.reason.closedGovernanceUnreadable')),
965
+ policy_disable_all: renderMessage(msg('config.hooks.plan.reason.closedDisableAll')),
966
+ no_executor: REASON_TEXT.no_executor,
884
967
  };
885
968
  const SOURCE_TEXT = HOOK_SETTINGS_SOURCE_TEXT;
886
969
  export function pluginHookReasonText(reason) {
@@ -958,6 +1041,10 @@ function namedPathsOfRow(d) {
958
1041
  return typeof d.event === 'string' && Number.isInteger(d.groupIndex) && d.groupIndex >= 0 ? [['hooks', d.event, d.groupIndex, 'source']] : [];
959
1042
  case 'exec_form_unsupported':
960
1043
  case 'mcp_tool_unsupported':
1044
+ case 'workspace_untrusted':
1045
+ case 'governance_unreadable':
1046
+ case 'policy_disable_all':
1047
+ case 'no_executor':
961
1048
  return [];
962
1049
  }
963
1050
  }
@@ -1009,6 +1096,8 @@ export function hooksNotRunNotice(plan) {
1009
1096
  for (const d of plan.settingsDropped) {
1010
1097
  if (!d.notRun)
1011
1098
  continue;
1099
+ if (d.reason === 'workspace_untrusted' || d.reason === 'policy_disable_all')
1100
+ continue;
1012
1101
  const named = d.reason === 'key_not_read' || d.reason === 'source_not_authored' ? namedPathsOfRow(d) : [];
1013
1102
  if (named.length > 0 && (d.reason === 'key_not_read' || d.reason === 'source_not_authored')) {
1014
1103
  const nk = `${d.source}\u0000${d.reason}`;
@@ -24,6 +24,8 @@ export function toolEndResultToUserFrame(arm) {
24
24
  }, undefined, parent === null ? 'leader' : 'subagent');
25
25
  const envelope = toolResultEnvelopeParts(outcome);
26
26
  const gateView = guardedRead(() => gateOutcomeOf(arm));
27
+ const denialKindRaw = arm._sema_denial_kind;
28
+ const denialKind = typeof denialKindRaw === 'string' ? denialKindRaw : undefined;
27
29
  const content = Array.isArray(outcome.body.content) ? outcome.body.content : arm.truncated === true ? `${text}\n…(truncated)` : text;
28
30
  return {
29
31
  type: 'user',
@@ -39,9 +41,9 @@ export function toolEndResultToUserFrame(arm) {
39
41
  ],
40
42
  },
41
43
  parent_tool_use_id: parent,
42
- ...(isCcToolDenialKind(arm._sema_denial_kind) ? { _sema_denial_kind: arm._sema_denial_kind } : {}),
44
+ ...(isCcToolDenialKind(denialKind) ? { _sema_denial_kind: denialKind } : {}),
43
45
  ...(gateView !== undefined ? { _sema_gate: gateView } : {}),
44
- ...toolResultRowFlagParts(arm, gateView),
46
+ ...toolResultRowFlagParts(arm, gateView, denialKind),
45
47
  ...(Object.hasOwn(envelope, 'tool_use_result') ? { tool_use_result: envelope.tool_use_result } : {}),
46
48
  ...(Object.hasOwn(envelope, '_sema_degraded') ? { _sema_degraded: envelope._sema_degraded } : {}),
47
49
  uuid: rowIdentity.uuid,
@@ -4,7 +4,10 @@ import { PROMPT_BLOCKED_BY_HOOK_ROW_LEAD } from './rowKind.js';
4
4
  export { PROMPT_BLOCKED_BY_HOOK_ROW_LEAD };
5
5
  const RUN_BLOCKED_REASONED = 'Run blocked';
6
6
  const RUN_BLOCKED_NO_REASON = 'Run blocked (the agent reported it cannot proceed)';
7
- const HOOK_WORDS_SHOWN_MAX = 1000;
7
+ const WORDS_SHOWN_MAX = 1000;
8
+ function shownWords(words) {
9
+ return displayUntrusted(words, { max: WORDS_SHOWN_MAX }).trim();
10
+ }
8
11
  const CAUSE_CLAUSE = Object.freeze({
9
12
  crashed: 'the hook failed before it answered; the prompt was not submitted',
10
13
  timeout: 'the hook did not answer in time; the prompt was not submitted',
@@ -13,7 +16,7 @@ const CAUSE_CLAUSE = Object.freeze({
13
16
  function shownHookWords(words) {
14
17
  if (words === undefined)
15
18
  return undefined;
16
- const shown = stripSystemReminderTagTokens(displayUntrusted(stripSystemReminderTagTokens(words), { max: HOOK_WORDS_SHOWN_MAX })).trim();
19
+ const shown = stripSystemReminderTagTokens(shownWords(stripSystemReminderTagTokens(words))).trim();
17
20
  return shown === '' ? undefined : shown;
18
21
  }
19
22
  export function runBlockedText(blocked) {
@@ -29,8 +32,10 @@ export function runBlockedText(blocked) {
29
32
  if (reason === undefined)
30
33
  return RUN_BLOCKED_NO_REASON;
31
34
  const unwrapped = unwrapSystemReminder(reason);
32
- if (unwrapped === reason)
33
- return `${RUN_BLOCKED_REASONED}: ${reason}`;
34
- const body = stripSystemReminderTagTokens(unwrapped).trim();
35
- return body === '' ? RUN_BLOCKED_NO_REASON : `${RUN_BLOCKED_REASONED}: ${body}`;
35
+ if (unwrapped === reason) {
36
+ const shown = shownWords(reason);
37
+ return shown === '' ? RUN_BLOCKED_NO_REASON : `${RUN_BLOCKED_REASONED}: ${shown}`;
38
+ }
39
+ const body = shownHookWords(unwrapped);
40
+ return body === undefined ? RUN_BLOCKED_NO_REASON : `${RUN_BLOCKED_REASONED}: ${body}`;
36
41
  }
@@ -0,0 +1,7 @@
1
+ export declare const SUGGESTION_BATCH_CAP = 8;
2
+ export declare const SUGGESTION_CHARS_CAP = 200;
3
+ export interface PromptSuggestionBatch {
4
+ readonly list: readonly string[];
5
+ readonly truncatedCount?: number;
6
+ }
7
+ export declare function promptSuggestionBatchOf(raw: {} | null | undefined): PromptSuggestionBatch | undefined;
@@ -0,0 +1,9 @@
1
+ export const SUGGESTION_BATCH_CAP = 8;
2
+ export const SUGGESTION_CHARS_CAP = 200;
3
+ export function promptSuggestionBatchOf(raw) {
4
+ const filtered = (Array.isArray(raw) ? raw : []).filter((s) => typeof s === 'string' && s.trim().length > 0);
5
+ const list = filtered.slice(0, SUGGESTION_BATCH_CAP).map((s) => (s.length > SUGGESTION_CHARS_CAP ? `${s.slice(0, SUGGESTION_CHARS_CAP)}…` : s));
6
+ if (list.length === 0)
7
+ return undefined;
8
+ return { list, ...(filtered.length > list.length ? { truncatedCount: filtered.length - list.length } : {}) };
9
+ }
@@ -0,0 +1,39 @@
1
+ import type { AgentClient } from '@sema-agent/sdk';
2
+ import type { LaneProof } from './seam.js';
3
+ export declare const PROMPT_SUGGESTIONS_TAIL_MAX_ATTEMPTS = 6;
4
+ export declare const PROMPT_SUGGESTIONS_TAIL_BUDGET_MS = 12000;
5
+ export interface PromptSuggestionsTailEvent {
6
+ readonly kind: 'prompt_suggestions';
7
+ readonly laneProof: LaneProof;
8
+ readonly suggestions: readonly string[];
9
+ }
10
+ export type PromptSuggestionsStreamSeen = 'frame' | 'done' | 'cut';
11
+ export interface PromptSuggestionsTailInput {
12
+ readonly runs: Pick<AgentClient['runs'], 'get'>;
13
+ readonly runId: string;
14
+ readonly sessionId?: string | undefined;
15
+ readonly requested: boolean;
16
+ readonly stream: PromptSuggestionsStreamSeen;
17
+ readonly baseUrl: string | undefined;
18
+ readonly signal?: AbortSignal | undefined;
19
+ readonly sleep?: ((ms: number) => Promise<void>) | undefined;
20
+ readonly now?: (() => number) | undefined;
21
+ }
22
+ export type PromptSuggestionsTailOutcome = {
23
+ readonly kind: 'skipped';
24
+ readonly why: 'not_requested' | 'frame_on_stream' | 'settled_on_stream';
25
+ } | {
26
+ readonly kind: 'settled';
27
+ readonly event?: PromptSuggestionsTailEvent;
28
+ } | {
29
+ readonly kind: 'not_completed';
30
+ readonly attempts: number;
31
+ } | {
32
+ readonly kind: 'gave_up';
33
+ readonly attempts: number;
34
+ } | {
35
+ readonly kind: 'unavailable';
36
+ readonly attempts: number;
37
+ };
38
+ export declare function promptSuggestionsOnStream(baseUrl: string | undefined): boolean;
39
+ export declare function followUpPromptSuggestions(input: PromptSuggestionsTailInput): Promise<PromptSuggestionsTailOutcome>;
@@ -0,0 +1,116 @@
1
+ import { abortableSleep, requestDeadline } from './abortableSleep.js';
2
+ import { engineCapString, engineCapValue } from './engineCapsCache.js';
3
+ import { readOwnDataValue } from './ownKey.js';
4
+ import { promptSuggestionBatchOf } from './promptSuggestionBatch.js';
5
+ import { engineVersionTier } from './supportFloor.js';
6
+ export const PROMPT_SUGGESTIONS_TAIL_MAX_ATTEMPTS = 6;
7
+ export const PROMPT_SUGGESTIONS_TAIL_BUDGET_MS = 12_000;
8
+ const RETRY_STEP_MS = 750;
9
+ const RETRY_CAP_MS = 3_000;
10
+ const ON_STREAM_SERVER_FLOOR = '7.149.0';
11
+ const MAY_STILL_SUGGEST = Object.freeze(['completed', 'running']);
12
+ export function promptSuggestionsOnStream(baseUrl) {
13
+ try {
14
+ const bit = engineCapValue(baseUrl, 'promptSuggestions');
15
+ if (bit.kind !== 'value' || bit.value !== true)
16
+ return false;
17
+ return engineVersionTier(engineCapString(baseUrl, 'version'), ON_STREAM_SERVER_FLOOR) === 'engine';
18
+ }
19
+ catch {
20
+ return false;
21
+ }
22
+ }
23
+ const freeze = (o) => Object.freeze(o);
24
+ const own = (o, key) => {
25
+ try {
26
+ return typeof o === 'object' && o !== null ? readOwnDataValue(o, key) : undefined;
27
+ }
28
+ catch {
29
+ return undefined;
30
+ }
31
+ };
32
+ function eventOf(raw) {
33
+ const batch = promptSuggestionBatchOf(raw);
34
+ return batch === undefined ? undefined : freeze({ kind: 'prompt_suggestions', laneProof: freeze({ lane: 'main' }), suggestions: freeze([...batch.list]) });
35
+ }
36
+ function skipWhy(input) {
37
+ if (own(input, 'requested') !== true)
38
+ return 'not_requested';
39
+ const seen = own(input, 'stream');
40
+ if (seen === 'frame')
41
+ return 'frame_on_stream';
42
+ if (seen === 'done' && promptSuggestionsOnStream(own(input, 'baseUrl')))
43
+ return 'settled_on_stream';
44
+ return undefined;
45
+ }
46
+ export async function followUpPromptSuggestions(input) {
47
+ let attempts = 0;
48
+ try {
49
+ const why = skipWhy(input);
50
+ if (why !== undefined)
51
+ return freeze({ kind: 'skipped', why });
52
+ const runs = own(input, 'runs');
53
+ const runId = own(input, 'runId');
54
+ const get = typeof runs === 'object' && runs !== null ? runs.get : undefined;
55
+ if (typeof get !== 'function' || typeof runId !== 'string' || runId.length === 0)
56
+ return freeze({ kind: 'unavailable', attempts });
57
+ const sessionId = own(input, 'sessionId');
58
+ const signal = own(input, 'signal');
59
+ const sleepIn = own(input, 'sleep');
60
+ const nowIn = own(input, 'now');
61
+ const sleep = typeof sleepIn === 'function' ? sleepIn : (ms) => abortableSleep(ms, signal ?? new AbortController().signal);
62
+ const now = typeof nowIn === 'function' ? nowIn : Date.now;
63
+ const signalState = () => {
64
+ if (signal === undefined || signal === null)
65
+ return 'live';
66
+ try {
67
+ return signal.aborted === true ? 'aborted' : 'live';
68
+ }
69
+ catch {
70
+ return 'unreadable';
71
+ }
72
+ };
73
+ const t0 = now();
74
+ for (;;) {
75
+ if (signalState() !== 'live')
76
+ return freeze({ kind: 'unavailable', attempts });
77
+ const left = PROMPT_SUGGESTIONS_TAIL_BUDGET_MS - (now() - t0);
78
+ if (!(left > 0))
79
+ return freeze({ kind: 'gave_up', attempts });
80
+ attempts++;
81
+ const d = requestDeadline(left, signal);
82
+ let rec;
83
+ try {
84
+ rec = await d.race(Promise.resolve(get.call(runs, runId, {
85
+ ...(typeof sessionId === 'string' && sessionId.length > 0 ? { session: sessionId } : {}),
86
+ signal: d.signal,
87
+ })));
88
+ }
89
+ catch {
90
+ return freeze({ kind: d.expired() ? 'gave_up' : 'unavailable', attempts });
91
+ }
92
+ finally {
93
+ d.abort();
94
+ }
95
+ const suggestions = own(rec, 'suggestions');
96
+ if (Array.isArray(suggestions)) {
97
+ const event = eventOf(suggestions);
98
+ return freeze({ kind: 'settled', ...(event !== undefined ? { event } : {}) });
99
+ }
100
+ const status = own(rec, 'status');
101
+ if (typeof status === 'string' && !MAY_STILL_SUGGEST.includes(status))
102
+ return freeze({ kind: 'not_completed', attempts });
103
+ if (attempts >= PROMPT_SUGGESTIONS_TAIL_MAX_ATTEMPTS)
104
+ return freeze({ kind: 'gave_up', attempts });
105
+ const rest = PROMPT_SUGGESTIONS_TAIL_BUDGET_MS - (now() - t0);
106
+ if (!(rest > 0))
107
+ return freeze({ kind: 'gave_up', attempts });
108
+ if (signalState() !== 'live')
109
+ return freeze({ kind: 'unavailable', attempts });
110
+ await sleep(Math.min(RETRY_STEP_MS * attempts, RETRY_CAP_MS, rest));
111
+ }
112
+ }
113
+ catch {
114
+ return freeze({ kind: 'unavailable', attempts });
115
+ }
116
+ }