@sema-agent/client-core 0.94.0 → 0.95.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 (255) hide show
  1. package/CHANGELOG.md +341 -0
  2. package/README.md +70 -47
  3. package/dist/adapt/arms.js +5 -6
  4. package/dist/adapt/panelTasks.js +4 -10
  5. package/dist/adapt/toolCards.js +2 -2
  6. package/dist/adapt/wireShapes.js +3 -8
  7. package/dist/adapter/activeRunSelfHeal.d.ts +8 -3
  8. package/dist/adapter/activeRunSelfHeal.js +20 -12
  9. package/dist/adapter/downstream/eventToSdkMessage.js +24 -17
  10. package/dist/adapter/downstream/terminalToSdkResult.js +11 -3
  11. package/dist/adapter/runStream.js +2 -1
  12. package/dist/adapter/types.d.ts +7 -0
  13. package/dist/adapter/types.js +15 -0
  14. package/dist/agentSession/backgroundView.js +5 -1
  15. package/dist/agentSession/engineAgentRegistryRead.js +2 -1
  16. package/dist/agentSession/engineSessionBackgroundStop.js +3 -2
  17. package/dist/agentsWireCaps.d.ts +2 -0
  18. package/dist/approvalDecidedByCapability.d.ts +0 -1
  19. package/dist/approvalDecidedByCapability.js +2 -7
  20. package/dist/approvalsStreamLiveCapability.d.ts +0 -1
  21. package/dist/approvalsStreamLiveCapability.js +2 -6
  22. package/dist/atMostOnce.d.ts +8 -0
  23. package/dist/atMostOnce.js +36 -0
  24. package/dist/attachmentSeatCapability.d.ts +0 -1
  25. package/dist/attachmentSeatCapability.js +0 -3
  26. package/dist/attachmentsWireCaps.js +11 -6
  27. package/dist/bashExitReading.d.ts +10 -0
  28. package/dist/bashExitReading.js +12 -0
  29. package/dist/callerCwdCapability.d.ts +0 -1
  30. package/dist/callerCwdCapability.js +2 -7
  31. package/dist/centerWiringCapability.d.ts +1 -3
  32. package/dist/centerWiringCapability.js +18 -21
  33. package/dist/classifierStatus.js +4 -4
  34. package/dist/cloudConfigWireCaps.js +43 -21
  35. package/dist/compensations.js +1 -1
  36. package/dist/cycleOrder.d.ts +15 -0
  37. package/dist/cycleOrder.js +45 -0
  38. package/dist/decideFailureNote.js +4 -1
  39. package/dist/decideReceipt.d.ts +2 -1
  40. package/dist/decideReceipt.js +41 -13
  41. package/dist/declared/supportFloorDeclared.d.ts +1 -0
  42. package/dist/declared/supportFloorDeclared.js +5 -0
  43. package/dist/deviceExecutorManagementCapability.d.ts +0 -1
  44. package/dist/deviceExecutorManagementCapability.js +7 -16
  45. package/dist/deviceRebind.d.ts +88 -0
  46. package/dist/deviceRebind.js +242 -0
  47. package/dist/displayUntrusted.d.ts +1 -0
  48. package/dist/displayUntrusted.js +313 -11
  49. package/dist/engineAgentAbsence.d.ts +2 -2
  50. package/dist/engineAgentPanelStore.d.ts +8 -18
  51. package/dist/engineAgentPanelStore.js +19 -537
  52. package/dist/engineCapReader.d.ts +6 -6
  53. package/dist/engineCapReader.js +30 -43
  54. package/dist/engineCapsCache.d.ts +7 -2
  55. package/dist/engineCapsCache.js +64 -26
  56. package/dist/engineCredentialRequirement.d.ts +9 -0
  57. package/dist/engineCredentialRequirement.js +23 -0
  58. package/dist/engineEnvSourcing.d.ts +53 -0
  59. package/dist/engineEnvSourcing.js +81 -0
  60. package/dist/engineErrorCodes.d.ts +14 -3
  61. package/dist/engineErrorCodes.js +31 -10
  62. package/dist/engineIdentity.js +2 -0
  63. package/dist/engineWireSdk.js +2 -2
  64. package/dist/executionLaneCapability.d.ts +2 -3
  65. package/dist/executionLaneCapability.js +18 -23
  66. package/dist/fileHistoryCaptureCapability.d.ts +0 -1
  67. package/dist/fileHistoryCaptureCapability.js +2 -6
  68. package/dist/fileHistoryLegNotice.d.ts +3 -2
  69. package/dist/fileHistoryLegNotice.js +51 -2
  70. package/dist/fleet/fleetLedger.d.ts +2 -0
  71. package/dist/fleet/fleetLedger.js +69 -52
  72. package/dist/fleet/fleetProjection.d.ts +0 -3
  73. package/dist/fleet/fleetProjection.js +9 -16
  74. package/dist/fleetAgentPanelProjection.d.ts +4 -17
  75. package/dist/fleetAgentPanelProjection.js +7 -197
  76. package/dist/fleetAgentRowLike.d.ts +16 -0
  77. package/dist/fleetAgentRowLike.js +1 -0
  78. package/dist/gateOutcome.js +30 -18
  79. package/dist/generated/sdkFactTables.d.ts +2 -0
  80. package/dist/generated/sdkFactTables.js +17 -0
  81. package/dist/generated/settingsSchemaFns.d.ts +10 -0
  82. package/dist/generated/settingsSchemaFns.js +22 -0
  83. package/dist/headlessPermissionModeWire.d.ts +1 -1
  84. package/dist/headlessPermissionModeWire.js +1 -1
  85. package/dist/headlessReconnectWire.js +49 -20
  86. package/dist/hitl/allowRuleShadow.d.ts +1 -1
  87. package/dist/hitl/allowRuleShadow.js +49 -8
  88. package/dist/hitl/approvalCompleteParked.d.ts +1 -0
  89. package/dist/hitl/approvalCompleteParked.js +14 -0
  90. package/dist/hitl/approvalEndingCause.d.ts +5 -0
  91. package/dist/hitl/approvalEndingCause.js +61 -0
  92. package/dist/hitl/approvalHolds.d.ts +12 -2
  93. package/dist/hitl/approvalHolds.js +53 -2
  94. package/dist/hitl/approvalsFeed.js +23 -11
  95. package/dist/hitl/askGateWire.d.ts +2 -1
  96. package/dist/hitl/askGateWire.js +26 -1
  97. package/dist/hitl/automaticAnswerVerdict.js +2 -2
  98. package/dist/hitl/crashConverged.js +2 -0
  99. package/dist/hitl/decisionConflict.d.ts +3 -0
  100. package/dist/hitl/decisionConflict.js +43 -0
  101. package/dist/hitl/decisionRefusal.d.ts +4 -0
  102. package/dist/hitl/decisionRefusal.js +26 -1
  103. package/dist/hitl/frameRouter.js +26 -43
  104. package/dist/hitl/gateLedger.d.ts +5 -0
  105. package/dist/hitl/gateLedger.js +21 -0
  106. package/dist/hitl/hitlBridge.d.ts +12 -1
  107. package/dist/hitl/hitlBridge.js +68 -53
  108. package/dist/hitl/hitlHostSurface.js +3 -11
  109. package/dist/hitl/livePendingAsk.d.ts +16 -6
  110. package/dist/hitl/livePendingAsk.js +36 -4
  111. package/dist/hitl/parkExpiryJudgements.d.ts +19 -0
  112. package/dist/hitl/parkExpiryJudgements.js +46 -0
  113. package/dist/hitl/parkResolver.d.ts +3 -7
  114. package/dist/hitl/parkResolver.js +50 -26
  115. package/dist/hitl/persistedRulesWire.js +5 -1
  116. package/dist/hitl/planReviewWire.d.ts +2 -2
  117. package/dist/hitl/planReviewWire.js +56 -30
  118. package/dist/hitl/replayedApproval.d.ts +8 -0
  119. package/dist/hitl/replayedApproval.js +125 -10
  120. package/dist/hitl/resumeRunningCard.d.ts +2 -0
  121. package/dist/hitl/resumeRunningCard.js +15 -4
  122. package/dist/hitl/toolApprovalWire.d.ts +24 -4
  123. package/dist/hitl/toolApprovalWire.js +115 -38
  124. package/dist/hooksWireCaps.js +38 -6
  125. package/dist/host.d.ts +5 -0
  126. package/dist/host.js +3 -1
  127. package/dist/hostCallbackGuard.d.ts +1 -0
  128. package/dist/hostCallbackGuard.js +25 -0
  129. package/dist/index.d.ts +30 -11
  130. package/dist/index.js +20 -8
  131. package/dist/interactiveInterrupt.d.ts +8 -0
  132. package/dist/interactiveInterrupt.js +54 -0
  133. package/dist/limitsWire.d.ts +5 -0
  134. package/dist/limitsWire.js +49 -3
  135. package/dist/liveModelCatalog.d.ts +95 -0
  136. package/dist/liveModelCatalog.js +297 -32
  137. package/dist/mcpEngineLeg.d.ts +25 -0
  138. package/dist/mcpEngineLeg.js +71 -15
  139. package/dist/mcpPanel.js +2 -1
  140. package/dist/mcpProbeCapability.d.ts +0 -1
  141. package/dist/mcpProbeCapability.js +2 -6
  142. package/dist/mcpReconnect.d.ts +1 -3
  143. package/dist/mcpReconnect.js +10 -5
  144. package/dist/memoryComplianceCapability.d.ts +0 -1
  145. package/dist/memoryComplianceCapability.js +2 -6
  146. package/dist/memoryOriginCapability.d.ts +0 -1
  147. package/dist/memoryOriginCapability.js +2 -6
  148. package/dist/memoryVerbsWire.d.ts +1 -1
  149. package/dist/memoryVerbsWire.js +4 -0
  150. package/dist/model/liveCatalogShape.d.ts +14 -0
  151. package/dist/model/liveCatalogShape.js +71 -0
  152. package/dist/model/modelSupplyRules.d.ts +1 -0
  153. package/dist/model/modelSupplyRules.js +30 -9
  154. package/dist/model/modelsPolicy.d.ts +21 -0
  155. package/dist/model/modelsPolicy.js +90 -0
  156. package/dist/modelIdentityResolvability.d.ts +1 -1
  157. package/dist/modelIdentityResolvability.js +5 -12
  158. package/dist/modelSwitchFallback.d.ts +69 -0
  159. package/dist/modelSwitchFallback.js +206 -0
  160. package/dist/notifications.js +12 -11
  161. package/dist/outputStyleSeatCapability.d.ts +0 -1
  162. package/dist/outputStyleSeatCapability.js +2 -7
  163. package/dist/ownKey.d.ts +6 -0
  164. package/dist/ownKey.js +48 -0
  165. package/dist/panelReduce.d.ts +114 -0
  166. package/dist/panelReduce.js +1135 -0
  167. package/dist/panelStateHolder.d.ts +17 -0
  168. package/dist/panelStateHolder.js +201 -0
  169. package/dist/peerLaneCapability.d.ts +0 -1
  170. package/dist/peerLaneCapability.js +2 -6
  171. package/dist/permissionModesCapability.d.ts +0 -1
  172. package/dist/permissionModesCapability.js +5 -10
  173. package/dist/permissionRulesWriteCapability.d.ts +0 -1
  174. package/dist/permissionRulesWriteCapability.js +2 -6
  175. package/dist/permissionsAllowGrantCapability.d.ts +0 -1
  176. package/dist/permissionsAllowGrantCapability.js +3 -9
  177. package/dist/permissionsDeclaration.d.ts +6 -0
  178. package/dist/permissionsDeclaration.js +45 -0
  179. package/dist/printToolResultFrame.js +2 -10
  180. package/dist/promptDelivery.js +0 -8
  181. package/dist/resumeRefusalCopy.d.ts +18 -1
  182. package/dist/resumeRefusalCopy.js +126 -15
  183. package/dist/rewindArchiveCapability.d.ts +2 -1
  184. package/dist/rewindArchiveCapability.js +15 -7
  185. package/dist/rewindEngineAction.d.ts +21 -0
  186. package/dist/rewindEngineAction.js +36 -0
  187. package/dist/rewindWireCaps.d.ts +2 -1
  188. package/dist/rewindWireCaps.js +1 -0
  189. package/dist/runTerminal.d.ts +0 -11
  190. package/dist/runTerminal.js +4 -27
  191. package/dist/sdkRegistryTransit.d.ts +2 -1
  192. package/dist/sdkRegistryTransit.js +1 -0
  193. package/dist/sealedKeyCapability.d.ts +0 -1
  194. package/dist/sealedKeyCapability.js +3 -9
  195. package/dist/seatContract.d.ts +54 -25
  196. package/dist/seatContract.js +394 -171
  197. package/dist/sessionIdSegment.d.ts +1 -0
  198. package/dist/sessionIdSegment.js +11 -0
  199. package/dist/sessionMemoryErase.d.ts +1 -1
  200. package/dist/sessionMemoryErase.js +6 -1
  201. package/dist/sessionMemoryStatus.js +3 -2
  202. package/dist/sessionPolicyCapability.d.ts +0 -1
  203. package/dist/sessionPolicyCapability.js +2 -6
  204. package/dist/sessionReadRedactionCapability.d.ts +0 -1
  205. package/dist/sessionReadRedactionCapability.js +2 -7
  206. package/dist/sessionSyncFileHistoryCapability.d.ts +0 -1
  207. package/dist/sessionSyncFileHistoryCapability.js +2 -6
  208. package/dist/skillToolListsCapability.d.ts +0 -1
  209. package/dist/skillToolListsCapability.js +2 -6
  210. package/dist/skillsWireCaps.d.ts +4 -0
  211. package/dist/skillsWireCaps.js +66 -1
  212. package/dist/sqlEngineCapability.d.ts +0 -1
  213. package/dist/sqlEngineCapability.js +9 -14
  214. package/dist/steerPriorityCapability.d.ts +0 -1
  215. package/dist/steerPriorityCapability.js +3 -9
  216. package/dist/storePostureCapability.d.ts +1 -1
  217. package/dist/storePostureCapability.js +16 -16
  218. package/dist/streamDrainingLegs.d.ts +6 -0
  219. package/dist/streamDrainingLegs.js +23 -0
  220. package/dist/subagent/engineCompactWire.js +13 -38
  221. package/dist/subagent/engineDelegatedPrompt.js +10 -6
  222. package/dist/subagent/engineSubagentOutput.d.ts +0 -1
  223. package/dist/subagent/engineSubagentOutput.js +5 -20
  224. package/dist/subagent/engineSubagentTail.d.ts +0 -1
  225. package/dist/subagent/engineSubagentTail.js +7 -32
  226. package/dist/subagentContentStore.js +3 -3
  227. package/dist/supportFloor.js +2 -5
  228. package/dist/terminalStatuses.d.ts +30 -0
  229. package/dist/terminalStatuses.js +67 -0
  230. package/dist/toolResult.d.ts +5 -0
  231. package/dist/toolResult.js +88 -36
  232. package/dist/types/engineState.d.ts +2 -1
  233. package/dist/usageAnalytics.d.ts +146 -0
  234. package/dist/usageAnalytics.js +311 -0
  235. package/dist/userInvokedSkillsCapability.d.ts +0 -1
  236. package/dist/userInvokedSkillsCapability.js +2 -6
  237. package/dist/webSearchBackendCapability.d.ts +0 -1
  238. package/dist/webSearchBackendCapability.js +3 -7
  239. package/dist/webSearchWireCaps.js +3 -11
  240. package/dist/wireFailureShape.d.ts +4 -0
  241. package/dist/wireFailureShape.js +42 -2
  242. package/dist/wireRefusalCopy.js +2 -18
  243. package/dist/workflowClient.js +7 -11
  244. package/dist/workflowsJournalCapability.d.ts +0 -1
  245. package/dist/workflowsJournalCapability.js +2 -6
  246. package/dist/writeProtectionCapability.d.ts +0 -1
  247. package/dist/writeProtectionCapability.js +5 -10
  248. package/docs/INTEGRATION-CLIENTS.md +1033 -26
  249. package/package.json +5 -5
  250. package/dist/engineCapsGenerationGuard.d.ts +0 -4
  251. package/dist/engineCapsGenerationGuard.js +0 -19
  252. package/dist/panelRunningHistory.d.ts +0 -22
  253. package/dist/panelRunningHistory.js +0 -82
  254. package/dist/registryConflict.d.ts +0 -3
  255. package/dist/registryConflict.js +0 -44
@@ -1,23 +1,25 @@
1
1
  import { stripSystemReminderBlocks } from '../systemReminderTag.js';
2
2
  import { hostLog } from '../host.js';
3
+ import { isSessionIdPathSegment } from '../sessionIdSegment.js';
3
4
  import { clientHasVerbs, engineWireFor, INJECTED_WIRE_SETTLE_MS, settleInjected } from '../engineWireFor.js';
4
5
  import { resolveOwnerContext } from './engineSubagentResume.js';
6
+ import { panelAnchorOf } from '../panelReduce.js';
7
+ import { clearEnginePanelAnchors, panelStateNow, stepEnginePanel } from '../panelStateHolder.js';
5
8
  function usableDelegatedPromptClient(client) {
6
9
  return clientHasVerbs(client, [['sessions', 'get']]);
7
10
  }
8
11
  const TAIL_WINDOW = 200;
9
12
  const BLOB_CAP = 8 * 1024;
10
- const transcriptIdByTask = new Map();
11
13
  export function recordEngineTranscriptId(taskId, transcriptId) {
12
14
  if (!taskId || !transcriptId)
13
15
  return;
14
- transcriptIdByTask.set(taskId, transcriptId);
16
+ stepEnginePanel({ kind: 'anchor', taskId, transcriptId });
15
17
  }
16
18
  export function getEngineTranscriptId(taskId) {
17
- return transcriptIdByTask.get(taskId);
19
+ return panelAnchorOf(panelStateNow(), taskId);
18
20
  }
19
21
  export function __resetEngineDelegatedPromptForTests() {
20
- transcriptIdByTask.clear();
22
+ clearEnginePanelAnchors();
21
23
  promptHydrateInFlight.clear();
22
24
  }
23
25
  const promptHydrateInFlight = new Set();
@@ -94,6 +96,8 @@ export function __setTranscriptWindowReaderForTests(r) {
94
96
  async function readTranscriptWindow(transcriptId, sessionKey, injectedWire) {
95
97
  if (transcriptWindowReader)
96
98
  return transcriptWindowReader(transcriptId);
99
+ if (!isSessionIdPathSegment(transcriptId))
100
+ return null;
97
101
  const wire = engineWireFor(injectedWire, sessionKey, usableDelegatedPromptClient);
98
102
  if (wire.kind === 'no-target' || wire.kind === 'injected-unusable')
99
103
  return null;
@@ -112,8 +116,8 @@ async function readTranscriptWindow(transcriptId, sessionKey, injectedWire) {
112
116
  export async function fetchDelegatedPrompt(taskId, opts) {
113
117
  if (promptHydrateInFlight.has(taskId))
114
118
  return { outcome: 'skipped' };
115
- const transcriptId = transcriptIdByTask.get(taskId);
116
- if (!transcriptId)
119
+ const transcriptId = getEngineTranscriptId(taskId);
120
+ if (!isSessionIdPathSegment(transcriptId))
117
121
  return { outcome: 'skipped' };
118
122
  promptHydrateInFlight.add(taskId);
119
123
  let msgs = null;
@@ -5,7 +5,6 @@ export type SubagentReportWireClient = {
5
5
  runs: Pick<AgentClient['runs'], 'subagentOutput'>;
6
6
  };
7
7
  export type SubagentReportWire = EngineWireInjection<SubagentReportWireClient>;
8
- export declare function __resetSubagentOutputCapCacheForTests(): void;
9
8
  export interface EngineSubagentReport {
10
9
  content: string;
11
10
  status?: string;
@@ -1,32 +1,17 @@
1
1
  import { hostLog } from '../host.js';
2
2
  import { clientHasVerbs, engineWireFor, INJECTED_WIRE_SETTLE_MS, settleInjected } from '../engineWireFor.js';
3
+ import { engineCapStateVia } from '../engineCapsCache.js';
3
4
  import { engineSessionParamFor } from '../engineSessionParam.js';
4
5
  import { resolveOwnerContext } from './engineSubagentResume.js';
5
6
  import { noteBgOwnerAbsence } from './subagentOwnerAbsence.js';
6
7
  function usableReportClient(client) {
7
8
  return clientHasVerbs(client, [['capabilities'], ['runs', 'subagentOutput']]);
8
9
  }
9
- const capByBase = new Map();
10
- const CAP_FALSE_RETRY_TTL_MS = 5 * 60_000;
11
10
  async function subagentOutputCapable(client, cacheKey, signal, deadlineMs) {
12
- const hit = cacheKey !== undefined ? capByBase.get(cacheKey) : undefined;
13
- if (hit && (hit.ok || Date.now() - hit.at < CAP_FALSE_RETRY_TTL_MS))
14
- return hit.ok;
15
- try {
16
- const caps = deadlineMs !== undefined
17
- ? await settleInjected(deadlineMs, signal, (s) => client.capabilities({ signal: s }))
18
- : await client.capabilities({ ...(signal ? { signal } : {}) });
19
- const ok = caps.subagentOutput === true;
20
- if (cacheKey !== undefined)
21
- capByBase.set(cacheKey, { ok, at: Date.now() });
22
- return ok;
23
- }
24
- catch {
25
- return false;
26
- }
27
- }
28
- export function __resetSubagentOutputCapCacheForTests() {
29
- capByBase.clear();
11
+ const state = await engineCapStateVia(cacheKey, 'subagentOutput', (s) => deadlineMs !== undefined
12
+ ? settleInjected(deadlineMs, s, (x) => client.capabilities({ signal: x }))
13
+ : client.capabilities({ ...(s ? { signal: s } : {}) }), { withinMs: deadlineMs ?? INJECTED_WIRE_SETTLE_MS, ...(signal ? { signal } : {}) });
14
+ return state === 'true';
30
15
  }
31
16
  export async function fetchEngineSubagentReport(taskId, opts) {
32
17
  const { record: owner, runId, key } = resolveOwnerContext(taskId);
@@ -5,7 +5,6 @@ export type SubagentTailWireClient = {
5
5
  runs: Pick<AgentClient['runs'], 'subagentStream'>;
6
6
  };
7
7
  export type SubagentTailWire = EngineWireInjection<SubagentTailWireClient>;
8
- export declare function __resetSubagentStreamCapCacheForTests(): void;
9
8
  export interface SubagentActivity {
10
9
  taskId: string;
11
10
  toolName: string;
@@ -1,5 +1,7 @@
1
1
  import { hostLog } from '../host.js';
2
+ import { invokeHostCallbackIsolated } from '../hostCallbackGuard.js';
2
3
  import { clientHasVerbs, engineWireFor, INJECTED_WIRE_SETTLE_MS, settleInjected } from '../engineWireFor.js';
4
+ import { engineCapStateVia } from '../engineCapsCache.js';
3
5
  import { engineSessionParamFor } from '../engineSessionParam.js';
4
6
  import { resolveOwnerContext } from './engineSubagentResume.js';
5
7
  import { coerceOutput, parentToolCallIdOf, publishSubagentContentEvent, settleSubagentContent, } from '../subagentContentStore.js';
@@ -7,27 +9,11 @@ import { noteBgOwnerAbsence } from './subagentOwnerAbsence.js';
7
9
  function usableTailClient(client) {
8
10
  return clientHasVerbs(client, [['capabilities'], ['runs', 'subagentStream']]);
9
11
  }
10
- const capByBase = new Map();
11
- const CAP_FALSE_RETRY_TTL_MS = 5 * 60_000;
12
12
  async function subagentStreamCapable(client, cacheKey, signal, deadlineMs) {
13
- const hit = cacheKey !== undefined ? capByBase.get(cacheKey) : undefined;
14
- if (hit && (hit.ok || Date.now() - hit.at < CAP_FALSE_RETRY_TTL_MS))
15
- return hit.ok;
16
- try {
17
- const caps = deadlineMs !== undefined
18
- ? await settleInjected(deadlineMs, signal, (s) => client.capabilities({ signal: s }))
19
- : await client.capabilities({ ...(signal ? { signal } : {}) });
20
- const ok = caps.subagentStream === true;
21
- if (cacheKey !== undefined)
22
- capByBase.set(cacheKey, { ok, at: Date.now() });
23
- return ok;
24
- }
25
- catch {
26
- return false;
27
- }
28
- }
29
- export function __resetSubagentStreamCapCacheForTests() {
30
- capByBase.clear();
13
+ const state = await engineCapStateVia(cacheKey, 'subagentStream', (s) => deadlineMs !== undefined
14
+ ? settleInjected(deadlineMs, s, (x) => client.capabilities({ signal: x }))
15
+ : client.capabilities({ ...(s ? { signal: s } : {}) }), { withinMs: deadlineMs ?? INJECTED_WIRE_SETTLE_MS, ...(signal ? { signal } : {}) });
16
+ return state === 'true';
31
17
  }
32
18
  const activeTails = new Map();
33
19
  let activitySink = null;
@@ -106,18 +92,7 @@ export function tailEngineSubagent(taskId, opts) {
106
92
  }
107
93
  switch (ev.event) {
108
94
  case 'meta':
109
- try {
110
- const out = tailMetaSink?.({ taskId, meta: ev.data });
111
- if (out !== null && typeof out === 'object' && typeof out.then === 'function') {
112
- ;
113
- out.then(undefined, (e) => {
114
- hostLog('debug', `[subagent-tail] ${taskId} meta sink rejected: ${String(e).slice(0, 160)}`);
115
- });
116
- }
117
- }
118
- catch (e) {
119
- hostLog('debug', `[subagent-tail] ${taskId} meta sink threw: ${String(e).slice(0, 160)}`);
120
- }
95
+ invokeHostCallbackIsolated(`[subagent-tail] ${taskId} meta sink`, () => tailMetaSink?.({ taskId, meta: ev.data }));
121
96
  continue;
122
97
  case 'forward':
123
98
  break;
@@ -1,3 +1,4 @@
1
+ import { DEFAULT_SESSION_KEY } from './sessionSlot.js';
1
2
  export function coerceOutput(v) {
2
3
  if (v === undefined || v === null)
3
4
  return undefined;
@@ -648,7 +649,6 @@ export function getBgTerminalFacts(taskId) {
648
649
  }
649
650
  const MAX_BG_PARENT_RUNS = 320;
650
651
  const bgParentRun = new Map();
651
- const DEFAULT_OWNER_SLOT = '__default__';
652
652
  let ownerSessionConflictCount = 0;
653
653
  let lastOwnerSessionConflict;
654
654
  const conflictQuarantine = new Map();
@@ -703,8 +703,8 @@ export function recordSubagentOwner(taskId, runId, sessionId, sessionKey) {
703
703
  },
704
704
  next: { ...nextPair },
705
705
  };
706
- const priorSlot = prior?.sessionKey ?? DEFAULT_OWNER_SLOT;
707
- const nextSlot = nextPair.sessionKey ?? DEFAULT_OWNER_SLOT;
706
+ const priorSlot = prior?.sessionKey ?? DEFAULT_SESSION_KEY;
707
+ const nextSlot = nextPair.sessionKey ?? DEFAULT_SESSION_KEY;
708
708
  if (priorSlot !== nextSlot) {
709
709
  bgParentRun.delete(taskId);
710
710
  if (conflictQuarantine.size >= MAX_BG_PARENT_RUNS && !conflictQuarantine.has(taskId)) {
@@ -1,8 +1,5 @@
1
- export const SUPPORT_FLOOR = Object.freeze({
2
- server: '7.106.0',
3
- core: '7.35.2',
4
- sdk: '18.1.0',
5
- });
1
+ import { DECLARED_SUPPORT_FLOOR } from './declared/supportFloorDeclared.js';
2
+ export const SUPPORT_FLOOR = DECLARED_SUPPORT_FLOOR;
6
3
  export function supportFloorOf(component) {
7
4
  if (component === 'server' || component === 'core' || component === 'sdk')
8
5
  return SUPPORT_FLOOR[component];
@@ -0,0 +1,30 @@
1
+ import type { SDKTaskUpdatedMessage } from '@sema-agent/agent-types';
2
+ import { CORE_TERMINAL_CAUSE_KINDS } from './generated/engineFactTables.js';
3
+ export { CORE_TERMINAL_CAUSE_KINDS };
4
+ export type CoreTerminalCauseKind = (typeof CORE_TERMINAL_CAUSE_KINDS)[number];
5
+ export declare function isCoreTerminalCauseKind(kind: unknown): kind is CoreTerminalCauseKind;
6
+ export declare const SDK_RUN_TERMINAL_STATUSES: readonly ["completed", "failed", "blocked", "timeout"];
7
+ export type SdkRunTerminalStatus = (typeof SDK_RUN_TERMINAL_STATUSES)[number];
8
+ export declare function isSdkRunTerminal(status: unknown): status is SdkRunTerminalStatus;
9
+ export declare function isSdkRunTerminalNotSuccess(status: unknown): status is Exclude<SdkRunTerminalStatus, 'completed'>;
10
+ export declare const SDK_PARKED_RUN_STATUSES: readonly ["suspended", "needs_review"];
11
+ export declare function isSdkRunParked(status: unknown): status is (typeof SDK_PARKED_RUN_STATUSES)[number];
12
+ export declare function isSdkRunFrameSynthesizable(status: unknown): boolean;
13
+ export declare const SDK_FLEET_TASK_TERMINAL_STATUSES: readonly ["completed", "failed", "killed"];
14
+ export type SdkFleetTaskTerminalStatus = (typeof SDK_FLEET_TASK_TERMINAL_STATUSES)[number];
15
+ export declare function isSdkFleetTaskTerminal(status: unknown): status is SdkFleetTaskTerminalStatus;
16
+ export declare function isSdkFleetTaskTerminalNotSuccess(status: unknown): status is Exclude<SdkFleetTaskTerminalStatus, 'completed'>;
17
+ export declare const CORE_TASK_NOTIFICATION_TERMINAL_STATUSES: readonly ["completed", "failed", "killed", "cancelled"];
18
+ export type CoreTaskNotificationTerminalStatus = (typeof CORE_TASK_NOTIFICATION_TERMINAL_STATUSES)[number];
19
+ export declare function isCoreTaskNotificationTerminal(status: unknown): status is CoreTaskNotificationTerminalStatus;
20
+ export declare function isCoreTaskNotificationTerminalNotSuccess(status: unknown): status is Exclude<CoreTaskNotificationTerminalStatus, 'completed'>;
21
+ export declare const CORE_SEMA_TASK_TERMINAL_STATUSES: readonly ["completed", "failed", "killed", "cancelled"];
22
+ export type CoreSemaTaskTerminalStatus = (typeof CORE_SEMA_TASK_TERMINAL_STATUSES)[number];
23
+ export declare function isCoreSemaTaskTerminal(status: unknown): status is CoreSemaTaskTerminalStatus;
24
+ export declare const CORE_WORKFLOW_RUN_TERMINAL_STATUSES: readonly ["completed", "failed"];
25
+ export declare function isCoreWorkflowRunTerminal(status: unknown): status is (typeof CORE_WORKFLOW_RUN_TERMINAL_STATUSES)[number];
26
+ export declare const CORE_TASK_PROGRESS_TERMINAL_STATUSES: readonly ["completed", "failed"];
27
+ export declare function isCoreTaskProgressTerminal(status: unknown): status is (typeof CORE_TASK_PROGRESS_TERMINAL_STATUSES)[number];
28
+ export declare function isCoreTaskProgressTerminalNotSuccess(status: unknown): status is 'failed';
29
+ export type CcTaskTerminalStatus = Extract<NonNullable<SDKTaskUpdatedMessage['patch']['status']>, 'completed' | 'failed' | 'killed'>;
30
+ export declare function ccTaskTerminalStatusOf(status: unknown): CcTaskTerminalStatus | undefined;
@@ -0,0 +1,67 @@
1
+ import { CORE_TERMINAL_CAUSE_KINDS } from './generated/engineFactTables.js';
2
+ function memberOf(table, word) {
3
+ return typeof word === 'string' && table.includes(word);
4
+ }
5
+ function terminalNotSuccessOf(table, word) {
6
+ return word !== 'completed' && memberOf(table, word);
7
+ }
8
+ export { CORE_TERMINAL_CAUSE_KINDS };
9
+ export function isCoreTerminalCauseKind(kind) {
10
+ return memberOf(CORE_TERMINAL_CAUSE_KINDS, kind);
11
+ }
12
+ export const SDK_RUN_TERMINAL_STATUSES = Object.freeze(['completed', 'failed', 'blocked', 'timeout']);
13
+ export function isSdkRunTerminal(status) {
14
+ return memberOf(SDK_RUN_TERMINAL_STATUSES, status);
15
+ }
16
+ export function isSdkRunTerminalNotSuccess(status) {
17
+ return terminalNotSuccessOf(SDK_RUN_TERMINAL_STATUSES, status);
18
+ }
19
+ export const SDK_PARKED_RUN_STATUSES = Object.freeze(['suspended', 'needs_review']);
20
+ export function isSdkRunParked(status) {
21
+ return memberOf(SDK_PARKED_RUN_STATUSES, status);
22
+ }
23
+ export function isSdkRunFrameSynthesizable(status) {
24
+ return isSdkRunTerminal(status) || isSdkRunParked(status);
25
+ }
26
+ export const SDK_FLEET_TASK_TERMINAL_STATUSES = Object.freeze(['completed', 'failed', 'killed']);
27
+ export function isSdkFleetTaskTerminal(status) {
28
+ return memberOf(SDK_FLEET_TASK_TERMINAL_STATUSES, status);
29
+ }
30
+ export function isSdkFleetTaskTerminalNotSuccess(status) {
31
+ return terminalNotSuccessOf(SDK_FLEET_TASK_TERMINAL_STATUSES, status);
32
+ }
33
+ export const CORE_TASK_NOTIFICATION_TERMINAL_STATUSES = Object.freeze(['completed', 'failed', 'killed', 'cancelled']);
34
+ export function isCoreTaskNotificationTerminal(status) {
35
+ return memberOf(CORE_TASK_NOTIFICATION_TERMINAL_STATUSES, status);
36
+ }
37
+ export function isCoreTaskNotificationTerminalNotSuccess(status) {
38
+ return terminalNotSuccessOf(CORE_TASK_NOTIFICATION_TERMINAL_STATUSES, status);
39
+ }
40
+ export const CORE_SEMA_TASK_TERMINAL_STATUSES = Object.freeze(['completed', 'failed', 'killed', 'cancelled']);
41
+ export function isCoreSemaTaskTerminal(status) {
42
+ return memberOf(CORE_SEMA_TASK_TERMINAL_STATUSES, status);
43
+ }
44
+ export const CORE_WORKFLOW_RUN_TERMINAL_STATUSES = Object.freeze(['completed', 'failed']);
45
+ export function isCoreWorkflowRunTerminal(status) {
46
+ return memberOf(CORE_WORKFLOW_RUN_TERMINAL_STATUSES, status);
47
+ }
48
+ export const CORE_TASK_PROGRESS_TERMINAL_STATUSES = Object.freeze(['completed', 'failed']);
49
+ export function isCoreTaskProgressTerminal(status) {
50
+ return memberOf(CORE_TASK_PROGRESS_TERMINAL_STATUSES, status);
51
+ }
52
+ export function isCoreTaskProgressTerminalNotSuccess(status) {
53
+ return terminalNotSuccessOf(CORE_TASK_PROGRESS_TERMINAL_STATUSES, status);
54
+ }
55
+ const CC_TASK_TERMINAL_STATUS_OF = Object.freeze({
56
+ completed: 'completed',
57
+ failed: 'failed',
58
+ killed: 'killed',
59
+ cancelled: 'killed',
60
+ blocked: 'failed',
61
+ timeout: 'failed',
62
+ });
63
+ export function ccTaskTerminalStatusOf(status) {
64
+ return typeof status === 'string' && Object.hasOwn(CC_TASK_TERMINAL_STATUS_OF, status)
65
+ ? CC_TASK_TERMINAL_STATUS_OF[status]
66
+ : undefined;
67
+ }
@@ -13,6 +13,7 @@ export declare const PROTOCOL_MARKERS: {
13
13
  readonly delegationNoTextResult: "(no text result)";
14
14
  readonly delegationTrailer: "Verify this result before concluding. If it leaves more to do, continue or delegate again.";
15
15
  readonly delegationTrailerFork: "Verify this result before concluding. If it leaves more to do, continue the work yourself or fork again.";
16
+ readonly delegationFieldLine: RegExp;
16
17
  readonly taskOutputLabel: RegExp;
17
18
  readonly taskOutputStatus: RegExp;
18
19
  readonly taskOutputHeadSlots: RegExp;
@@ -52,6 +53,10 @@ export type DelegationReportFrame = {
52
53
  before: string;
53
54
  after: string;
54
55
  section: 'result' | 'partial_findings' | 'no_text_result';
56
+ status?: string;
57
+ error?: string;
58
+ stats?: string;
59
+ headRest?: string;
55
60
  };
56
61
  export declare function displayBody(text: unknown, origin?: DisplayBodyOrigin): DisplayBody | undefined;
57
62
  export type ModelFacingTaskOutput = {
@@ -3,6 +3,8 @@ import { markEngineWorkflowNotified, registerOutstandingBgTask, registerOutstand
3
3
  import { parseWorkflowPollEnvelope, projectWorkflowTaskOutput } from './workflow.js';
4
4
  import { asModelFallbackReason } from './engineErrorCodes.js';
5
5
  import { frozenSet } from './frozenSet.js';
6
+ import { bashExitOf, bashExitReadingFailed } from './bashExitReading.js';
7
+ import { isCoreSemaTaskTerminal } from './terminalStatuses.js';
6
8
  import { CORE_ENGINE_CARD_TYPES } from './generated/engineFactTables.js';
7
9
  export const STRUCTURED_DETAIL_TYPES = frozenSet(CORE_ENGINE_CARD_TYPES);
8
10
  export function structuredDetailType(structured) {
@@ -12,11 +14,6 @@ export function structuredDetailType(structured) {
12
14
  return typeof t === 'string' && STRUCTURED_DETAIL_TYPES.has(t) ? t : undefined;
13
15
  }
14
16
  const MAX_STRUCTURED_FIELD_CHARS = 1_000_000;
15
- const WF_TERMINAL_STATUSES = new Set([
16
- 'completed',
17
- 'failed',
18
- 'cancelled',
19
- ]);
20
17
  export const PROTOCOL_MARKERS = {
21
18
  bashHead: /^exit code: (-?\d+)(?: \(([^)\n]*)\))?\n(--- stdout ---\n)?/,
22
19
  bashStderrSep: '\n--- stderr ---\n',
@@ -28,6 +25,7 @@ export const PROTOCOL_MARKERS = {
28
25
  delegationNoTextResult: '(no text result)',
29
26
  delegationTrailer: 'Verify this result before concluding. If it leaves more to do, continue or delegate again.',
30
27
  delegationTrailerFork: 'Verify this result before concluding. If it leaves more to do, continue the work yourself or fork again.',
28
+ delegationFieldLine: /^(status|error|error_kind|observer|stats|worktree):(?: ([^\r\u2028\u2029\u0085]*))?/,
31
29
  taskOutputLabel: /^TaskOutput\s+(\S+)$/,
32
30
  taskOutputStatus: /^status:\s*(.+)$/m,
33
31
  taskOutputHeadSlots: /^(\r?\n)(?:(state: ([^\r\n\u2028\u2029\u0085]*)(?:\r?\n|$)))?(?:(error: ([^\r\n\u2028\u2029\u0085]*)(?:\r?\n|$)))?/,
@@ -35,7 +33,7 @@ export const PROTOCOL_MARKERS = {
35
33
  taskOutputNoneSentinel: '(none)',
36
34
  taskOutputStdoutLine: /^--- (?:new )?stdout[^\n]*---\n?/m,
37
35
  taskOutputStderrLine: /^--- (?:new )?stderr[^\n]*---\n?/m,
38
- taskOutputResultLine: /^--- result(?: \([^)\n]*\))? ---\n?/m,
36
+ taskOutputResultLine: /^--- result(?: \((?:[^)\n\r\u2028\u2029]|[\r\u2028\u2029](?!--- result))*\))? ---\n?/m,
39
37
  spoolStdoutFull: '--- stdout (full, re-readable) ---',
40
38
  spoolStdoutIncremental: '--- new stdout ---',
41
39
  };
@@ -78,6 +76,59 @@ export function stripUntrustedFence(text) {
78
76
  return null;
79
77
  return { label: m[1] ?? '', body: m[2] ?? '' };
80
78
  }
79
+ function delegationFrameSlots(lines, headAt, labelAt, fromAt) {
80
+ const read = (i) => {
81
+ const line = lines[i] ?? '';
82
+ const hit = PROTOCOL_MARKERS.delegationFieldLine.exec(line);
83
+ if (hit === null)
84
+ return null;
85
+ const value = hit[2];
86
+ return { key: hit[1] ?? '', value: value !== undefined && hit[0].length === line.length && value.trim() !== '' ? value : undefined };
87
+ };
88
+ const fields = [];
89
+ for (let i = headAt + 1; i < labelAt; i++)
90
+ fields.push(read(i));
91
+ const errorFollowedByEngineField = (at) => {
92
+ const next = fields[at + 1];
93
+ return next !== null && next !== undefined && next.value !== undefined && (next.key === 'error_kind' || next.key === 'observer' || next.key === 'stats');
94
+ };
95
+ const worktreeBefore = (at) => fields.slice(0, at).some((f) => f?.key === 'worktree');
96
+ const soleAt = (key) => {
97
+ let at = -1;
98
+ for (let i = 0; i < fields.length; i++) {
99
+ if (fields[i]?.key !== key)
100
+ continue;
101
+ if (at >= 0)
102
+ return -1;
103
+ at = i;
104
+ }
105
+ return at;
106
+ };
107
+ const claimed = new Set([headAt, labelAt]);
108
+ if (fromAt > labelAt + 1)
109
+ claimed.add(labelAt + 1);
110
+ const slots = {};
111
+ const first = fields[0] ?? null;
112
+ if (first !== null && first.key === 'status' && first.value !== undefined && soleAt('status') === 0) {
113
+ slots.status = first.value.trim();
114
+ claimed.add(headAt + 1);
115
+ for (const key of ['error', 'stats']) {
116
+ const at = soleAt(key);
117
+ const value = at >= 0 ? fields[at]?.value : undefined;
118
+ if (value === undefined || (key === 'error' && (!errorFollowedByEngineField(at) || worktreeBefore(at))))
119
+ continue;
120
+ slots[key] = value;
121
+ claimed.add(headAt + 1 + at);
122
+ }
123
+ }
124
+ let rest = '';
125
+ for (let i = 0; i < fromAt; i++)
126
+ if (!claimed.has(i))
127
+ rest += `${lines[i] ?? ''}\n`;
128
+ if (rest.trim() !== '')
129
+ slots.headRest = rest;
130
+ return slots;
131
+ }
81
132
  function locateDelegationResultSection(text) {
82
133
  const lines = text.split('\n');
83
134
  const soleLineAt = (hit) => {
@@ -107,14 +158,14 @@ function locateDelegationResultSection(text) {
107
158
  };
108
159
  if (trailerAt === labelAt + 2 && lines[labelAt + 1] === PROTOCOL_MARKERS.delegationNoTextResult) {
109
160
  const c = cut(labelAt + 1);
110
- return { section: 'no_text_result', before: c.before, after: c.after };
161
+ return { section: 'no_text_result', before: c.before, after: c.after, slots: delegationFrameSlots(lines, headAt, labelAt, labelAt + 1) };
111
162
  }
112
163
  const partial = lines[labelAt + 1] === PROTOCOL_MARKERS.delegationPartialFindingsLabel;
113
164
  const from = partial ? labelAt + 2 : labelAt + 1;
114
165
  if (from >= trailerAt)
115
166
  return null;
116
167
  const c = cut(from);
117
- return { section: partial ? 'partial_findings' : 'result', before: c.before, fenceText: c.middle, after: c.after };
168
+ return { section: partial ? 'partial_findings' : 'result', before: c.before, fenceText: c.middle, after: c.after, slots: delegationFrameSlots(lines, headAt, labelAt, from) };
118
169
  }
119
170
  export function displayBody(text, origin = 'any_tool_result') {
120
171
  if (typeof text !== 'string')
@@ -128,14 +179,14 @@ export function displayBody(text, origin = 'any_tool_result') {
128
179
  if (located === null)
129
180
  return { fenced: false, body: text };
130
181
  if (located.section === 'no_text_result') {
131
- const { before, after, section } = located;
132
- return { fenced: false, body: '', frame: { before, after, section } };
182
+ const { before, after, section, slots } = located;
183
+ return { fenced: false, body: '', frame: { before, after, section, ...slots } };
133
184
  }
134
185
  const inner = stripUntrustedFence(located.fenceText);
135
186
  if (inner === null)
136
187
  return { fenced: false, body: text };
137
- const { before, after, section } = located;
138
- return { fenced: true, label: inner.label, body: inner.body, frame: { before, after, section } };
188
+ const { before, after, section, slots } = located;
189
+ return { fenced: true, label: inner.label, body: inner.body, frame: { before, after, section, ...slots } };
139
190
  }
140
191
  export function parseModelFacingTaskOutput(text) {
141
192
  modelFacingParseCount++;
@@ -241,7 +292,7 @@ export function wireOutputToBody(toolName, output, isError, truncated, flatten,
241
292
  : typeof bgBlock?.task_id === 'string'
242
293
  ? bgBlock.task_id
243
294
  : undefined;
244
- if (s?.detached === true)
295
+ if (readBashDetachView(structured)?.handoffTaskId !== undefined)
245
296
  bgTaskId = undefined;
246
297
  }
247
298
  else {
@@ -255,20 +306,23 @@ export function wireOutputToBody(toolName, output, isError, truncated, flatten,
255
306
  };
256
307
  }
257
308
  }
258
- const slots = sType === 'bash' && (typeof s?.stdout === 'string' || typeof s?.stderr === 'string')
309
+ let parsedMemo;
310
+ const parseOnce = () => (parsedMemo === undefined ? (parsedMemo = parseModelFacingBash(text)) : parsedMemo);
311
+ const exit = bashExitOf(structured, parseOnce);
312
+ const structuredSlots = sType === 'bash' && (typeof s?.stdout === 'string' || typeof s?.stderr === 'string');
313
+ const textSlots = structuredSlots ? null : parseOnce();
314
+ const slots = structuredSlots
259
315
  ? {
260
316
  stdout: typeof s?.stdout === 'string' ? s.stdout : '',
261
317
  stderr: typeof s?.stderr === 'string' ? s.stderr : '',
262
- exitCode: typeof s?.exitCode === 'number' ? s.exitCode : null,
318
+ exitCode: exit.exitCode,
263
319
  }
264
- : parseModelFacingBash(text);
320
+ : textSlots !== null
321
+ ? { ...textSlots, exitCode: exit.exitCode }
322
+ : null;
265
323
  if (slots) {
266
- const benignInterp = typeof s?.returnCodeInterpretation === 'string' && s.returnCodeInterpretation.length > 0
267
- ? s.returnCodeInterpretation
268
- : 'note' in slots && typeof slots.note === 'string' && slots.note.length > 0
269
- ? slots.note
270
- : undefined;
271
- const failed = isError || (slots.exitCode != null && slots.exitCode !== 0 && benignInterp === undefined);
324
+ const benignInterp = exit.benignNote;
325
+ const failed = isError || bashExitReadingFailed(exit);
272
326
  if (failed) {
273
327
  const reason = slots.stderr.length > 0 ? slots.stderr : slots.stdout;
274
328
  let body = slots.exitCode != null ? `Exit code ${slots.exitCode}\n${reason}` : reason;
@@ -293,7 +347,7 @@ export function wireOutputToBody(toolName, output, isError, truncated, flatten,
293
347
  };
294
348
  }
295
349
  const stdout = truncated ? `${text}\n…(truncated)` : text;
296
- if (isError)
350
+ if (isError || bashExitReadingFailed(exit))
297
351
  return { content: stdout, isError: true };
298
352
  return { content: stdout, isError, toolUseResult: { stdout, stderr: '', interrupted: false } };
299
353
  }
@@ -316,7 +370,7 @@ export function wireOutputToBody(toolName, output, isError, truncated, flatten,
316
370
  }
317
371
  const wfEnvelope = parseWorkflowPollEnvelope(text);
318
372
  if (wfEnvelope) {
319
- if (leaderLedger && WF_TERMINAL_STATUSES.has(wfEnvelope.status))
373
+ if (leaderLedger && isCoreSemaTaskTerminal(wfEnvelope.status))
320
374
  markEngineWorkflowNotified(wfEnvelope.taskId);
321
375
  return wrap({
322
376
  task_id: wfEnvelope.taskId,
@@ -362,23 +416,22 @@ export function structuredToToolUseResult(structured, modelText, leaderLedger =
362
416
  };
363
417
  }
364
418
  case 'bash': {
365
- if (s.detached === true && typeof s.task_id === 'string') {
419
+ const handoffTaskId = readBashDetachView(structured)?.handoffTaskId;
420
+ if (handoffTaskId !== undefined) {
366
421
  return {
367
422
  toolUseResult: {
368
423
  stdout: '',
369
424
  stderr: '',
370
425
  interrupted: false,
371
- backgroundTaskId: s.task_id,
426
+ backgroundTaskId: handoffTaskId,
372
427
  backgroundedByUser: true,
373
428
  },
374
429
  };
375
430
  }
376
431
  if (typeof s.stdout !== 'string' && typeof s.stderr !== 'string')
377
432
  return null;
378
- const exitCode = typeof s.exitCode === 'number' ? s.exitCode : null;
379
- const benignInterp = typeof s.returnCodeInterpretation === 'string' && s.returnCodeInterpretation.length > 0
380
- ? s.returnCodeInterpretation
381
- : undefined;
433
+ const exit = bashExitOf(structured, () => null);
434
+ const benignInterp = exit.benignNote;
382
435
  return {
383
436
  toolUseResult: {
384
437
  stdout: typeof s.stdout === 'string' ? s.stdout : '',
@@ -386,7 +439,7 @@ export function structuredToToolUseResult(structured, modelText, leaderLedger =
386
439
  interrupted: false,
387
440
  ...(benignInterp !== undefined ? { returnCodeInterpretation: benignInterp } : {}),
388
441
  },
389
- ...(exitCode !== null && exitCode !== 0 && benignInterp === undefined ? { isError: true } : {}),
442
+ ...(bashExitReadingFailed(exit) ? { isError: true } : {}),
390
443
  };
391
444
  }
392
445
  case 'edit': {
@@ -650,7 +703,7 @@ export function structuredToToolUseResult(structured, modelText, leaderLedger =
650
703
  const runErrorCode = typeof s.errorCode === 'string' && s.errorCode.length > 0 ? s.errorCode : undefined;
651
704
  let output = bodyText ?? '';
652
705
  if (taskType === 'workflow') {
653
- if (leaderLedger && WF_TERMINAL_STATUSES.has(status))
706
+ if (leaderLedger && isCoreSemaTaskTerminal(status))
654
707
  markEngineWorkflowNotified(taskId);
655
708
  const jsonSrc = output.length > 0 ? output : (modelText ?? '');
656
709
  output = projectWorkflowTaskOutput(jsonSrc);
@@ -716,9 +769,9 @@ export function detectEngineBgShellDetach(toolName, rawInput, structured, output
716
769
  if (toolName !== 'Bash')
717
770
  return null;
718
771
  const view = readBashDetachView(structured);
719
- if (view === null || !view.detached || view.registerFailed || view.taskId === undefined)
772
+ if (view === null || view.registerFailed || view.handoffTaskId === undefined)
720
773
  return null;
721
- return engineBgShellRegistrationBody(view.taskId, rawInput, typeof outputText === 'string' ? outputText : '', 'bash');
774
+ return engineBgShellRegistrationBody(view.handoffTaskId, rawInput, typeof outputText === 'string' ? outputText : '', 'bash');
722
775
  }
723
776
  function readBashDetachView(structured) {
724
777
  if (typeof structured !== 'object' || structured === null)
@@ -731,9 +784,8 @@ function readBashDetachView(structured) {
731
784
  const registerFailed = s.registerFailed;
732
785
  const taskId = s.task_id;
733
786
  return {
734
- detached: detached === true,
787
+ handoffTaskId: detached === true && typeof taskId === 'string' && taskId.length > 0 ? taskId : undefined,
735
788
  registerFailed: registerFailed === true,
736
- taskId: typeof taskId === 'string' && taskId.length > 0 ? taskId : undefined,
737
789
  };
738
790
  }
739
791
  export function todoWriteToolUseResult(rawInput, oldTodos, isError) {
@@ -1,5 +1,5 @@
1
1
  import type { EngineHealth } from '@sema-agent/sdk';
2
- export interface HealthResponse extends Pick<EngineHealth, 'status' | 'model' | 'sessionBackend' | 'sessions' | 'instanceId' | 'dataRoot' | 'configHash' | 'pid' | 'port' | 'restartRequired' | 'version' | 'restart'> {
2
+ export interface HealthResponse extends Pick<EngineHealth, 'status' | 'model' | 'sessionBackend' | 'sessions' | 'instanceId' | 'dataRoot' | 'configHash' | 'pid' | 'port' | 'restartRequired' | 'version' | 'startedAt'> {
3
3
  }
4
4
  export interface EnginePortFile {
5
5
  port: number;
@@ -19,6 +19,7 @@ export interface EngineInstance {
19
19
  instanceId?: string;
20
20
  dataRoot?: string;
21
21
  configHash?: string;
22
+ startedAt?: number;
22
23
  logPath?: string;
23
24
  ownedByThisShell: boolean;
24
25
  child?: unknown;