@sema-agent/client-core 0.109.0 → 0.110.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 (123) hide show
  1. package/CHANGELOG.md +173 -0
  2. package/README.md +46 -22
  3. package/dist/adapt/arms.js +20 -7
  4. package/dist/adapter/downstream/eventToSdkMessage.js +11 -6
  5. package/dist/declared/supportFloorDeclared.js +1 -1
  6. package/dist/displayUntrusted.js +132 -14
  7. package/dist/engineErrorCodes.d.ts +3 -0
  8. package/dist/engineErrorCodes.js +3 -0
  9. package/dist/engineHealthFacts.d.ts +9 -0
  10. package/dist/engineHealthFacts.js +21 -1
  11. package/dist/engineInlineTaskStats.d.ts +4 -3
  12. package/dist/engineInlineTaskStats.js +18 -2
  13. package/dist/engineNoticeCodes.d.ts +10 -0
  14. package/dist/engineNoticeCodes.js +33 -1
  15. package/dist/engineRepoint.d.ts +140 -0
  16. package/dist/engineRepoint.js +285 -0
  17. package/dist/engineRowRef.js +2 -0
  18. package/dist/engineSessionMessages.d.ts +2 -1
  19. package/dist/engineSessionMessages.js +5 -0
  20. package/dist/errorSupplementRoles.js +2 -1
  21. package/dist/fleet/fleetProjection.js +3 -2
  22. package/dist/generated/engineFactTables.d.ts +2 -0
  23. package/dist/generated/engineFactTables.js +12 -0
  24. package/dist/generated/sdkFactTables.d.ts +1 -0
  25. package/dist/generated/sdkFactTables.js +4 -0
  26. package/dist/hitl/approvalAskers.d.ts +6 -0
  27. package/dist/hitl/approvalAskers.js +30 -0
  28. package/dist/hitl/approvalCardReasons.js +6 -3
  29. package/dist/hitl/approvalRequestFrameReading.d.ts +7 -0
  30. package/dist/hitl/approvalRequestFrameReading.js +11 -0
  31. package/dist/hitl/approvalResolution.d.ts +8 -1
  32. package/dist/hitl/approvalResolution.js +4 -0
  33. package/dist/hitl/askGateWire.js +0 -2
  34. package/dist/hitl/automaticAnswerVerdict.js +2 -2
  35. package/dist/hitl/decisionConflict.js +6 -3
  36. package/dist/hitl/decisionRefusal.js +1 -0
  37. package/dist/hitl/decisionSend.d.ts +3 -0
  38. package/dist/hitl/decisionSend.js +29 -11
  39. package/dist/hitl/editedApprovalAcceptance.d.ts +25 -0
  40. package/dist/hitl/editedApprovalAcceptance.js +26 -0
  41. package/dist/hitl/frameRouter.js +4 -18
  42. package/dist/hitl/gateLedger.d.ts +0 -4
  43. package/dist/hitl/gateLedger.js +0 -19
  44. package/dist/hitl/hitlBridge.d.ts +1 -0
  45. package/dist/hitl/hitlBridge.js +4 -4
  46. package/dist/hitl/hitlHostSurface.d.ts +1 -0
  47. package/dist/hitl/hitlHostSurface.js +7 -2
  48. package/dist/hitl/hostWithdrawal.d.ts +15 -0
  49. package/dist/hitl/hostWithdrawal.js +38 -0
  50. package/dist/hitl/parkResolver.js +25 -9
  51. package/dist/hitl/persistedRulesWire.js +2 -2
  52. package/dist/hitl/planReviewWire.js +14 -4
  53. package/dist/hitl/sessionPolicyWire.js +6 -4
  54. package/dist/hitl/standingRows.d.ts +1 -0
  55. package/dist/hitl/standingRows.js +15 -1
  56. package/dist/hitl/toolApprovalWire.d.ts +35 -8
  57. package/dist/hitl/toolApprovalWire.js +235 -33
  58. package/dist/hooksWireCaps.js +31 -1
  59. package/dist/hostTurnSettings.d.ts +10 -4
  60. package/dist/hostTurnSettings.js +112 -5
  61. package/dist/i18n/messages.en.d.ts +83 -0
  62. package/dist/i18n/messages.en.js +86 -2
  63. package/dist/i18n/messages.zh.js +96 -2
  64. package/dist/index.d.ts +21 -7
  65. package/dist/index.js +14 -4
  66. package/dist/inheritEnvCapability.js +2 -0
  67. package/dist/inputAuthor.d.ts +16 -0
  68. package/dist/inputAuthor.js +44 -0
  69. package/dist/interruptRefusalNote.d.ts +2 -0
  70. package/dist/interruptRefusalNote.js +17 -0
  71. package/dist/liveModelCatalog.d.ts +2 -0
  72. package/dist/liveModelCatalog.js +21 -7
  73. package/dist/liveQuestionStore.d.ts +4 -1
  74. package/dist/liveQuestionStore.js +12 -3
  75. package/dist/mcpEngineLeg.d.ts +3 -0
  76. package/dist/mcpEngineLeg.js +38 -1
  77. package/dist/mcpPanel.js +2 -0
  78. package/dist/mcpReconnect.d.ts +9 -1
  79. package/dist/mcpReconnect.js +121 -16
  80. package/dist/modelCapabilityProbe.js +4 -3
  81. package/dist/modsFace.d.ts +8 -1
  82. package/dist/modsFace.js +27 -0
  83. package/dist/notifications.d.ts +17 -6
  84. package/dist/notifications.js +67 -19
  85. package/dist/ownKey.d.ts +0 -5
  86. package/dist/ownKey.js +0 -7
  87. package/dist/peerFrames.d.ts +40 -2
  88. package/dist/peerFrames.js +158 -3
  89. package/dist/pluginHooksWire.d.ts +2 -1
  90. package/dist/pluginHooksWire.js +56 -1
  91. package/dist/replayBody.js +10 -2
  92. package/dist/request/taskRequest.d.ts +5 -0
  93. package/dist/request/taskRequest.js +138 -21
  94. package/dist/resumeRefusalCopy.d.ts +1 -3
  95. package/dist/resumeRefusalCopy.js +2 -1
  96. package/dist/retryStatus.d.ts +2 -2
  97. package/dist/retryStatus.js +2 -0
  98. package/dist/seam.d.ts +12 -1
  99. package/dist/seam.js +7 -1
  100. package/dist/seatContract.d.ts +86 -9
  101. package/dist/seatContract.js +196 -7
  102. package/dist/sessionBackground.js +2 -1
  103. package/dist/sessionBackgroundHandoff.js +2 -1
  104. package/dist/sessionEngine.d.ts +2 -0
  105. package/dist/sessionEngine.js +111 -0
  106. package/dist/sessionMemoryStatus.d.ts +2 -1
  107. package/dist/sessionSyncFileHistory.d.ts +4 -0
  108. package/dist/sessionSyncFileHistory.js +28 -0
  109. package/dist/sseIdleTriage.d.ts +13 -1
  110. package/dist/sseIdleTriage.js +24 -6
  111. package/dist/subagent/engineSubagentTail.js +6 -0
  112. package/dist/terminalStatuses.d.ts +1 -0
  113. package/dist/terminalStatuses.js +4 -1
  114. package/dist/turnFailurePresentation.js +9 -2
  115. package/dist/typePins.d.ts +11 -0
  116. package/dist/usageAnalytics.d.ts +17 -0
  117. package/dist/usageAnalytics.js +44 -2
  118. package/dist/wireFailureShape.d.ts +2 -5
  119. package/dist/wireFailureShape.js +10 -20
  120. package/dist/wireRefusalCopy.d.ts +10 -0
  121. package/dist/wireRefusalCopy.js +76 -0
  122. package/docs/INTEGRATION-CLIENTS.md +2126 -20
  123. package/package.json +3 -3
@@ -1,13 +1,13 @@
1
1
  import { capForDisplay } from './fleetTaskDesc.js';
2
2
  import { ownDataField } from './ownKey.js';
3
3
  import { isSessionIdPathSegment } from './sessionIdSegment.js';
4
- import { inSlotReadOf, inValueOf } from './wireFailureShape.js';
4
+ import { inSlotReadOf, inValueOf, slotReadOf } from './wireFailureShape.js';
5
5
  import { readMcpAuthorization } from './mcpLiveness.js';
6
+ import { msg, renderMessage } from './i18n/translate.js';
6
7
  export const MCP_RECONNECT_OUTCOMES = Object.freeze(['accepted', 'refused', 'unsupported']);
7
8
  export const MCP_RECONNECT_TRANSACTION_NOTICE = 'Reconnecting closes the current connection before dialing again. On a healthy server this is a transaction, ' +
8
9
  'not a refresh: a failed re-dial leaves the server down and withdraws its tools from the model until a later re-dial succeeds.';
9
- export const MCP_RECONNECT_UNSETTLED_NOTICE = 'The MCP re-dial did not settle within the time this request waits, so there is no result yet: the re-dial was not cancelled and may still be running. ' +
10
- "Check the server's state before re-dialing it again — a second request that arrives after the dial has settled closes the connection and dials once more.";
10
+ export const MCP_RECONNECT_UNSETTLED_NOTICE = renderMessage(msg('session.mcpReconnect.unsettled'));
11
11
  const UNSETTLED_CODE = 'state.mcp_reconnect_unsettled';
12
12
  const _sdkClientIsAnInjection = [true];
13
13
  void _sdkClientIsAnInjection;
@@ -196,23 +196,128 @@ export function projectMcpReconnectCapability(caps) {
196
196
  return undefined;
197
197
  }
198
198
  }
199
- export function mcpReconnectOutcomeDetail(view) {
199
+ function reasonTailOf(reason, lang) {
200
+ return reason === undefined ? '' : renderMessage(msg('session.mcpReconnect.reasonTail', { reason }), lang);
201
+ }
202
+ function outcomeSentence(f, lang) {
203
+ switch (f.outcome) {
204
+ case 'accepted':
205
+ return renderMessage(msg(f.toolCount === 1 ? 'session.mcpReconnect.accepted.one' : 'session.mcpReconnect.accepted.other', { server: f.name, toolCount: f.toolCount, added: f.added, removed: f.removed }), lang);
206
+ case 'refused': {
207
+ const why = reasonTailOf(f.reason, lang);
208
+ if (f.roster === undefined)
209
+ return renderMessage(msg('session.mcpReconnect.refused.untouched', { server: f.name, why }), lang);
210
+ if (f.roster === 0)
211
+ return renderMessage(msg('session.mcpReconnect.refused.noTools', { server: f.name, why }), lang);
212
+ return renderMessage(msg(f.roster === 1 ? 'session.mcpReconnect.refused.listed.one' : 'session.mcpReconnect.refused.listed.other', { server: f.name, toolCount: f.roster, why }), lang);
213
+ }
214
+ case 'unsupported':
215
+ return renderMessage(msg('session.mcpReconnect.undeclared', { server: f.name, why: reasonTailOf(f.reason, lang) }), lang);
216
+ }
217
+ }
218
+ export function mcpReconnectOutcomeDetail(view, lang) {
200
219
  const name = capForDisplay(view.server, RECONNECT_WORD_MAX);
201
220
  switch (view.outcome) {
202
221
  case 'accepted':
203
- return `MCP server ${name} was re-dialed: ${view.toolCount} tool${view.toolCount === 1 ? '' : 's'} mounted for this run (${view.added.length} added, ${view.removed.length} removed)`;
222
+ return outcomeSentence({ outcome: 'accepted', name, toolCount: view.toolCount, added: view.added.length, removed: view.removed.length }, lang);
223
+ case 'refused':
224
+ return outcomeSentence({ outcome: 'refused', name, roster: view.toolNames?.length, reason: view.reason }, lang);
225
+ case 'unsupported':
226
+ return outcomeSentence({ outcome: 'unsupported', name, reason: view.reason }, lang);
227
+ }
228
+ }
229
+ function verdictDetailKey(kind) {
230
+ switch (kind) {
231
+ case 'not_running':
232
+ return 'session.mcpReconnect.notRunning';
233
+ case 'unsupported':
234
+ return 'session.mcpReconnect.unsupported';
235
+ case 'not_found':
236
+ return 'session.mcpReconnect.notFound';
237
+ case 'invalid':
238
+ return 'session.mcpReconnect.invalid';
239
+ case 'failed':
240
+ return 'session.mcpReconnect.failed';
241
+ case 'no_session':
242
+ return 'session.mcpReconnect.noSession';
243
+ case 'unavailable':
244
+ return 'session.mcpReconnect.unavailable';
245
+ case 'unsettled':
246
+ return 'session.mcpReconnect.unsettled';
247
+ default:
248
+ return undefined;
249
+ }
250
+ }
251
+ function fieldOf(o, key) {
252
+ const s = slotReadOf(o, key);
253
+ return s.presence === 'present' ? { kind: 'value', value: s.value } : s.presence === 'absent' ? { kind: 'absent' } : { kind: 'unreadable' };
254
+ }
255
+ function okOutcomeFactsOf(verdict, server) {
256
+ const slot = fieldOf(verdict, 'result');
257
+ if (slot.kind !== 'value')
258
+ return undefined;
259
+ const r = slot.value;
260
+ if (r === null || typeof r !== 'object' || Array.isArray(r))
261
+ return undefined;
262
+ const outcome = fieldOf(r, 'outcome');
263
+ const echoed = fieldOf(r, 'server');
264
+ if (outcome.kind !== 'value' || echoed.kind !== 'value' || echoed.value !== server)
265
+ return undefined;
266
+ const rs = fieldOf(r, 'reason');
267
+ if (rs.kind === 'unreadable' || (rs.kind === 'value' && typeof rs.value !== 'string'))
268
+ return undefined;
269
+ const reason = rs.kind === 'value' ? rs.value : undefined;
270
+ const name = capForDisplay(server, RECONNECT_WORD_MAX);
271
+ switch (outcome.value) {
272
+ case 'unsupported':
273
+ return { outcome: 'unsupported', name, reason };
274
+ case 'accepted': {
275
+ const toolCount = fieldOf(r, 'toolCount');
276
+ const added = fieldOf(r, 'added');
277
+ const removed = fieldOf(r, 'removed');
278
+ if (toolCount.kind !== 'value' || added.kind !== 'value' || removed.kind !== 'value')
279
+ return undefined;
280
+ const n = toolCount.value;
281
+ const addedList = stringList(added.value);
282
+ const removedList = stringList(removed.value);
283
+ if (typeof n !== 'number' || !Number.isInteger(n) || n < 0 || addedList === undefined || removedList === undefined)
284
+ return undefined;
285
+ return { outcome: 'accepted', name, toolCount: n, added: addedList.length, removed: removedList.length };
286
+ }
204
287
  case 'refused': {
205
- const why = view.reason !== undefined ? ` — ${view.reason}` : '';
206
- if (view.toolNames === undefined) {
207
- return `MCP server ${name} was NOT re-dialed; its connection and tools were left untouched${why}`;
208
- }
209
- if (view.toolNames.length === 0) {
210
- return `MCP server ${name} was NOT re-dialed; this run's tool list has no tools from it right now (the dial failed, or this run's tool settings let none of its tools through)${why}`;
211
- }
212
- return `MCP server ${name} was NOT re-dialed; the engine reports ${view.toolNames.length} tool${view.toolNames.length === 1 ? '' : 's'} for it after this attempt${why}`;
288
+ const names = fieldOf(r, 'toolNames');
289
+ if (names.kind === 'unreadable')
290
+ return undefined;
291
+ if (names.kind === 'absent')
292
+ return { outcome: 'refused', name, roster: undefined, reason };
293
+ const roster = stringList(names.value);
294
+ if (roster === undefined)
295
+ return undefined;
296
+ return { outcome: 'refused', name, roster: roster.length, reason };
213
297
  }
214
- case 'unsupported':
215
- return (`This run declared no MCP servers, so there is nothing to re-dial (${name} is not part of this run)` +
216
- (view.reason !== undefined ? ` — ${view.reason}` : ''));
298
+ default:
299
+ return undefined;
300
+ }
301
+ }
302
+ export function mcpReconnectVerdictDetail(verdict, server, lang) {
303
+ try {
304
+ if (verdict === null || typeof verdict !== 'object')
305
+ return undefined;
306
+ const kindSlot = slotReadOf(verdict, 'kind');
307
+ if (kindSlot.presence !== 'present' || typeof kindSlot.value !== 'string')
308
+ return undefined;
309
+ if (!nonEmpty(server))
310
+ return undefined;
311
+ if (kindSlot.value === 'ok') {
312
+ const facts = okOutcomeFactsOf(verdict, server);
313
+ return facts === undefined ? undefined : outcomeSentence(facts, lang);
314
+ }
315
+ const key = verdictDetailKey(kindSlot.value);
316
+ if (key === undefined)
317
+ return undefined;
318
+ return renderMessage(msg(key, { server: capForDisplay(server, RECONNECT_WORD_MAX) }), lang);
319
+ }
320
+ catch {
321
+ return undefined;
217
322
  }
218
323
  }
@@ -128,15 +128,16 @@ export async function probeModelCapability(entry, send) {
128
128
  evidence: { baseline, attempts, lowEffortAccepted: !low.refused },
129
129
  };
130
130
  }
131
+ const _capabilityBitsPin = true;
132
+ void _capabilityBitsPin;
131
133
  export function applyProbeToEntry(entry, result) {
132
134
  if (result.verdict === 'inconclusive')
133
135
  return entry;
134
136
  const compatIn = typeof entry.compat === 'object' && entry.compat !== null && !Array.isArray(entry.compat)
135
137
  ? entry.compat
136
138
  : undefined;
137
- if (result.verdict === 'no_reasoning') {
138
- return { ...entry, reasoning: false };
139
- }
139
+ if (result.verdict === 'no_reasoning')
140
+ return entry;
140
141
  if (result.verdict === 'disabled_via' && typeof result.thinkingFormat === 'string') {
141
142
  return { ...entry, reasoning: true, compat: { ...compatIn, thinkingFormat: result.thinkingFormat } };
142
143
  }
@@ -1,8 +1,15 @@
1
- import type { WiringManifestModRefusedView } from './adapter/downstream/eventToSdkMessage.js';
1
+ import type { WiringManifestModRefusedView, WiringManifestModView } from './adapter/downstream/eventToSdkMessage.js';
2
+ import type { SessionModsView } from './seatContract.js';
3
+ import type { DeviceUnreadable } from './deviceRebind.js';
2
4
  import type { EngineNoticeChromeEvent } from './seam.js';
3
5
  import type { UiLanguage } from './env/uiLanguage.js';
6
+ import { type ApprovalRequestedBy } from './hitl/approvalAskers.js';
4
7
  export declare const MOD_LOADER_VERBS: readonly ["list", "load", "unload", "status"];
5
8
  export type ModLoaderVerb = (typeof MOD_LOADER_VERBS)[number];
6
9
  export declare function readModRegistrationRefusedNotice(ev: EngineNoticeChromeEvent): WiringManifestModRefusedView | undefined;
10
+ export declare function sessionModsOf(manifest: {
11
+ readonly mods?: readonly WiringManifestModView[];
12
+ } | null | undefined): SessionModsView | DeviceUnreadable;
7
13
  export declare function modCardSentence(input: object | null | undefined, lang?: UiLanguage): string | undefined;
14
+ export declare function approvalRequestedBySentence(by: ApprovalRequestedBy | null | undefined, lang?: UiLanguage): string | undefined;
8
15
  export declare function modLoaderFailureSentence(name: string, error: unknown, lang?: UiLanguage): string;
package/dist/modsFace.js CHANGED
@@ -4,6 +4,7 @@ import { displayUntrusted } from './displayUntrusted.js';
4
4
  import { WIRE_FAILURE_UNRENDERABLE_TEXT, wireFailureTextsOf } from './wireFailureShape.js';
5
5
  import { gateOutcomeOf, isDeniedGate, rewriteExtensionNamesOf, toolInputRewriteOf } from './gateOutcome.js';
6
6
  import { guardedRead, readOwnDataNonEmptyString, readOwnDataValue, toolResultBlockOf } from './ownKey.js';
7
+ import { requestedByViewOf } from './hitl/approvalAskers.js';
7
8
  const isCardRecordObject = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
8
9
  export const MOD_LOADER_VERBS = Object.freeze(['list', 'load', 'unload', 'status']);
9
10
  const modRowOf = (r) => modRowsOf([r])?.[0];
@@ -21,6 +22,18 @@ export function readModRegistrationRefusedNotice(ev) {
21
22
  return undefined;
22
23
  }
23
24
  }
25
+ export function sessionModsOf(manifest) {
26
+ try {
27
+ if (!isCardRecordObject(manifest))
28
+ return sessionModsUnreadable();
29
+ const rows = modRowsOf(readOwnDataValue(manifest, 'mods'));
30
+ return rows === undefined ? sessionModsUnreadable() : Object.freeze({ rows: Object.freeze(rows.slice()) });
31
+ }
32
+ catch {
33
+ return sessionModsUnreadable();
34
+ }
35
+ }
36
+ const sessionModsUnreadable = () => Object.freeze({ kind: 'unreadable', why: renderMessage(msg('config.mods.session.unreadable')) });
24
37
  const MOD_NAME_SHOWN_MAX = 64;
25
38
  const MOD_REASON_SHOWN_MAX = 160;
26
39
  const shownName = (s) => displayUntrusted(s, { blanks: true, max: MOD_NAME_SHOWN_MAX });
@@ -54,6 +67,20 @@ export function modCardSentence(input, lang) {
54
67
  return undefined;
55
68
  }
56
69
  }
70
+ export function approvalRequestedBySentence(by, lang) {
71
+ try {
72
+ const view = requestedByViewOf(by);
73
+ if (view === undefined)
74
+ return undefined;
75
+ const name = shownName(view.name);
76
+ if (name.trim() === '')
77
+ return undefined;
78
+ return renderMessage(msg('config.mods.request.by', { by: name }), lang);
79
+ }
80
+ catch {
81
+ return undefined;
82
+ }
83
+ }
57
84
  export function modLoaderFailureSentence(name, error, lang) {
58
85
  const shown = guardedRead(() => (typeof name === 'string' ? shownName(name) : undefined)) ?? '';
59
86
  const { reason } = wireFailureTextsOf(error);
@@ -9,6 +9,7 @@ export interface TaskNotificationFields {
9
9
  outputFile?: string;
10
10
  diagnostics?: string;
11
11
  result?: string;
12
+ error?: string;
12
13
  stoppedBy?: string;
13
14
  partial?: boolean;
14
15
  exitCode?: number;
@@ -93,11 +94,21 @@ export declare function noteWorkflowCompletionCardEnqueued(runId: string): void;
93
94
  export declare function listNotifiedRuns(): string[];
94
95
  export declare function listWorkflowCompletionCardsEnqueued(): string[];
95
96
  export declare function isWorkflowCompletionCardEnqueued(runId: string): boolean;
96
- type WorkflowStatusProbe = (runId: string) => Promise<{
97
- terminal: boolean;
98
- status: 'completed' | 'failed';
99
- summary: string;
100
- } | null>;
97
+ export type WorkflowRunReader = (runId: string) => Promise<unknown>;
98
+ export type WorkflowRunTerminalReading = {
99
+ readonly state: 'running';
100
+ } | {
101
+ readonly state: 'awaiting approval';
102
+ } | {
103
+ readonly state: 'completed' | 'failed';
104
+ readonly result?: string;
105
+ readonly error?: string;
106
+ readonly pointer: 'absent';
107
+ } | {
108
+ readonly state: 'unknown';
109
+ readonly why: 'body_unreadable' | 'status_unreadable' | 'status_unrecognized';
110
+ };
111
+ export declare function workflowRunTerminalReadingOf(body: unknown): WorkflowRunTerminalReading;
101
112
  export declare function outstandingWorkflowCount(): number;
102
113
  export declare function outstandingDeliverableWorkflowCount(): number;
103
114
  export declare function subscribeOutstandingWorkflows(listener: () => void): () => void;
@@ -111,7 +122,7 @@ export interface NotificationDropCounters {
111
122
  bgWatchCollectedUnprobed: number;
112
123
  }
113
124
  export declare function notificationDropCounters(): NotificationDropCounters;
114
- export declare function installWorkflowStatusProbe(probe: WorkflowStatusProbe): void;
125
+ export declare function installWorkflowRunReader(reader: WorkflowRunReader | null): void;
115
126
  type BgTaskStatusProbe = (taskId: string) => Promise<{
116
127
  terminal: boolean;
117
128
  status: string;
@@ -2,10 +2,11 @@ import { hostEnv } from './hostEnv.js';
2
2
  import { unrefTimer } from './unrefTimer.js';
3
3
  import { clearEnginePanelTaskResidentByWire, publishEngineAgentPanelEvent, } from './engineAgentPanelStore.js';
4
4
  import { panelEndFact } from './panelReduce.js';
5
- import { isCoreTaskNotificationTerminal, isCoreTaskNotificationTerminalNotSuccess } from './terminalStatuses.js';
5
+ import { isCoreTaskNotificationTerminal, isCoreTaskNotificationTerminalNotSuccess, isCoreWorkflowRunParkedEnd, isCoreWorkflowRunTerminal } from './terminalStatuses.js';
6
6
  import { cycleSeqValue } from './cycleOrder.js';
7
7
  import { CORE_ENGINE_AUTHORITY_ENVELOPE_TAGS } from './generated/engineFactTables.js';
8
8
  import { guardedRead, ownSnapshot, ownSnapshotRecord, ownSnapshotTyped, readOwnDataValue, workflowCompletionExtrasOf } from './ownKey.js';
9
+ import { displayUntrusted } from './displayUntrusted.js';
9
10
  function escapeXml(v) {
10
11
  return v.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
11
12
  }
@@ -102,6 +103,7 @@ export function freezeTaskNotificationFields(n) {
102
103
  ...(n.outputFile !== undefined ? { outputFile: n.outputFile } : {}),
103
104
  ...(n.diagnostics !== undefined ? { diagnostics: n.diagnostics } : {}),
104
105
  ...(n.result !== undefined ? { result: n.result } : {}),
106
+ ...(n.error !== undefined ? { error: n.error } : {}),
105
107
  ...(n.stoppedBy !== undefined ? { stoppedBy: n.stoppedBy } : {}),
106
108
  ...(n.partial !== undefined ? { partial: n.partial } : {}),
107
109
  ...(n.exitCode !== undefined ? { exitCode: n.exitCode } : {}),
@@ -128,7 +130,7 @@ export function taskNotificationOf(carrier) {
128
130
  if (typeof taskId !== 'string' || taskId.length === 0 || typeof status !== 'string' || typeof summary !== 'string')
129
131
  return undefined;
130
132
  const out = { taskId, status, summary };
131
- for (const k of ['taskType', 'toolUseId', 'outputFile', 'diagnostics', 'result', 'stoppedBy', 'externalSource']) {
133
+ for (const k of ['taskType', 'toolUseId', 'outputFile', 'diagnostics', 'result', 'error', 'stoppedBy', 'externalSource']) {
132
134
  const v = slot(k);
133
135
  if (typeof v === 'string')
134
136
  out[k] = v;
@@ -478,6 +480,50 @@ const cardEnqueuedRunIds = new Set();
478
480
  export function isWorkflowCompletionCardEnqueued(runId) {
479
481
  return cardEnqueuedRunIds.has(runId);
480
482
  }
483
+ export function workflowRunTerminalReadingOf(body) {
484
+ const snap = probeAnswerSnapshot(body);
485
+ if (snap === null)
486
+ return Object.freeze({ state: 'unknown', why: 'body_unreadable' });
487
+ const status = guardedRead(() => readOwnDataValue(snap, 'status'));
488
+ if (typeof status !== 'string')
489
+ return Object.freeze({ state: 'unknown', why: 'status_unreadable' });
490
+ if (status === 'running')
491
+ return Object.freeze({ state: 'running' });
492
+ if (!isCoreWorkflowRunTerminal(status))
493
+ return Object.freeze({ state: 'unknown', why: 'status_unrecognized' });
494
+ if (isCoreWorkflowRunParkedEnd(status, guardedRead(() => readOwnDataValue(snap, 'errorCode'))))
495
+ return Object.freeze({ state: 'awaiting approval' });
496
+ const result = guardedRead(() => readOwnDataValue(snap, 'result'));
497
+ const error = status === 'failed' ? guardedRead(() => readOwnDataValue(snap, 'error')) : undefined;
498
+ const shownError = typeof error === 'string' && error.length > 0 ? shownRunError(error) : '';
499
+ return Object.freeze({
500
+ state: status,
501
+ ...(typeof result === 'string' && result.length > 0 ? { result } : {}),
502
+ ...(shownError.length > 0 ? { error: shownError } : {}),
503
+ pointer: 'absent',
504
+ });
505
+ }
506
+ const WORKFLOW_RUN_ERROR_MAX = 200;
507
+ function shownRunError(raw) {
508
+ const opts = { foldLines: false, escapeBackslashes: 'auto' };
509
+ const whole = displayUntrusted(raw, opts);
510
+ return whole.length <= WORKFLOW_RUN_ERROR_MAX ? whole : `${displayUntrusted(raw, { ...opts, max: WORKFLOW_RUN_ERROR_MAX - 1 })}…`;
511
+ }
512
+ function workflowPollDecisionOf(delivered, reading) {
513
+ if (delivered)
514
+ return { act: 'stop', why: 'delivered' };
515
+ if (reading.state === 'running')
516
+ return { act: 'wait', why: 'running' };
517
+ if (reading.state === 'awaiting approval')
518
+ return { act: 'wait', why: 'awaiting approval' };
519
+ if (reading.state === 'unknown')
520
+ return { act: 'wait', why: 'unknown' };
521
+ return { act: 'emit', status: reading.state, ...(reading.result !== undefined ? { result: reading.result } : {}), ...(reading.error !== undefined ? { error: reading.error } : {}) };
522
+ }
523
+ function workflowPollCompletionSummary(status, error) {
524
+ const head = `workflow ${status} (read from its run record; no handle to the full record is available)`;
525
+ return error === undefined ? head : `${head} — failure reason from the run record: ${error}`;
526
+ }
481
527
  const outstandingRuns = new Map();
482
528
  const outstandingListeners = new Set();
483
529
  function notifyOutstanding() {
@@ -493,7 +539,7 @@ export function outstandingWorkflowCount() {
493
539
  return outstandingRuns.size;
494
540
  }
495
541
  export function outstandingDeliverableWorkflowCount() {
496
- return statusProbe ? outstandingRuns.size : 0;
542
+ return workflowRunReader ? outstandingRuns.size : 0;
497
543
  }
498
544
  export function subscribeOutstandingWorkflows(listener) {
499
545
  outstandingListeners.add(listener);
@@ -531,12 +577,12 @@ export function notificationDropCounters() {
531
577
  bgWatchCollectedUnprobed,
532
578
  };
533
579
  }
534
- let statusProbe = null;
580
+ let workflowRunReader = null;
535
581
  let watchTimer = null;
536
582
  const WATCH_INTERVAL_MS = 5000;
537
583
  const WATCH_TTL_MS = 2 * 60 * 60 * 1000;
538
- export function installWorkflowStatusProbe(probe) {
539
- statusProbe = probe;
584
+ export function installWorkflowRunReader(reader) {
585
+ workflowRunReader = reader;
540
586
  }
541
587
  const BG_FIRST_SEQ = 1;
542
588
  function normalizeBgSeq(seq) {
@@ -762,26 +808,25 @@ async function tickWatchInner(now) {
762
808
  bgProbeInFlight.delete(key);
763
809
  }
764
810
  }
765
- const probe = statusProbe;
811
+ const reader = workflowRunReader;
766
812
  for (const runId of [...outstandingRuns.keys()]) {
767
813
  if (notifiedRunIds.has(runId)) {
768
814
  if (outstandingRuns.delete(runId))
769
815
  notifyOutstanding();
770
816
  continue;
771
817
  }
772
- if (!probe)
818
+ if (!reader)
773
819
  continue;
774
820
  runProbeInFlight.set(runId, now);
775
821
  try {
776
- const res = probeAnswerSnapshot(await withProbeDeadline(probe(runId), probeTimeoutMs()));
777
- if (res?.terminal) {
778
- if (outstandingRuns.delete(runId))
779
- notifyOutstanding();
780
- enqueueEngineWorkflowNotification({
781
- runId,
782
- status: res.status,
783
- summary: res.summary,
784
- });
822
+ const reading = workflowRunTerminalReadingOf(await withProbeDeadline(reader(runId), probeTimeoutMs()));
823
+ const decision = workflowPollDecisionOf(notifiedRunIds.has(runId), reading);
824
+ if (decision.act === 'wait')
825
+ continue;
826
+ if (outstandingRuns.delete(runId))
827
+ notifyOutstanding();
828
+ if (decision.act === 'emit') {
829
+ enqueueWorkflowCompletion({ runId, status: decision.status, summary: workflowPollCompletionSummary(decision.status, decision.error) }, { ...(decision.result !== undefined ? { result: decision.result } : {}), ...(decision.error !== undefined ? { error: decision.error } : {}) });
785
830
  }
786
831
  }
787
832
  catch {
@@ -845,6 +890,9 @@ export function enqueueEngineWorkflowNotification(raw) {
845
890
  traceNotif('workflow-notification dropped:payload-unreadable');
846
891
  return;
847
892
  }
893
+ enqueueWorkflowCompletion(c, completionIdOnly(workflowCompletionExtrasOf(c)));
894
+ }
895
+ function enqueueWorkflowCompletion(c, body) {
848
896
  if (notifiedRunIds.has(c.runId)) {
849
897
  workflowDedupDropped++;
850
898
  traceNotif(`workflow notification suppressed (run already notified) runId=${c.runId} status=${c.status}`);
@@ -852,7 +900,7 @@ export function enqueueEngineWorkflowNotification(raw) {
852
900
  }
853
901
  markRunNotified(c.runId);
854
902
  cardEnqueuedRunIds.add(c.runId);
855
- port()?.enqueuePendingNotification(taskNotificationQueueCommand({ taskId: c.runId, taskType: 'workflow', status: c.status, summary: c.summary, ...completionIdOnly(workflowCompletionExtrasOf(c)) }));
903
+ port()?.enqueuePendingNotification(taskNotificationQueueCommand({ taskId: c.runId, taskType: 'workflow', status: c.status, summary: c.summary, ...body }));
856
904
  }
857
905
  export function _resetEngineTaskNotificationForTest() {
858
906
  notifiedRunIds.clear();
@@ -867,7 +915,7 @@ export function _resetEngineTaskNotificationForTest() {
867
915
  bgProbeInFlight.clear();
868
916
  runProbeInFlight.clear();
869
917
  tickGeneration = 0;
870
- statusProbe = null;
918
+ workflowRunReader = null;
871
919
  bgStatusProbe = null;
872
920
  tickStartedAtMs = null;
873
921
  abandonedCount = 0;
package/dist/ownKey.d.ts CHANGED
@@ -39,11 +39,6 @@ export interface HumanInputAttachmentDeclinedByView {
39
39
  name: string;
40
40
  }
41
41
  export declare function humanInputAttachmentsOf(frame: {} | null | undefined): HumanInputAttachmentView[] | undefined;
42
- export interface HumanInputAuthorKeys {
43
- author?: string;
44
- extension?: string;
45
- }
46
- export declare function humanInputAuthorKeys(frame: {} | null | undefined): HumanInputAuthorKeys;
47
42
  export declare function markMcpSectionDroppedNamedRows(rows: object, dropped: ReadonlyMap<string, number>): void;
48
43
  export declare function mcpSectionDroppedNamedRowsOf(rows: object | null | undefined, name: string): number;
49
44
  export interface PromptManifestSectionRow {
package/dist/ownKey.js CHANGED
@@ -193,13 +193,6 @@ export function humanInputAttachmentsOf(frame) {
193
193
  return undefined;
194
194
  }
195
195
  }
196
- export function humanInputAuthorKeys(frame) {
197
- if (typeof frame !== 'object' || frame === null || Array.isArray(frame))
198
- return {};
199
- const author = guardedRead(() => readOwnDataNonEmptyString(frame, 'author'));
200
- const extension = guardedRead(() => readOwnDataNonEmptyString(frame, 'extension'));
201
- return { ...(author !== undefined ? { author } : {}), ...(extension !== undefined ? { extension } : {}) };
202
- }
203
196
  const DROPPED_NAMED_ROWS = Symbol.for('@sema-agent/client-core/mcpSectionDroppedNamedRows');
204
197
  export function markMcpSectionDroppedNamedRows(rows, dropped) {
205
198
  if (dropped.size === 0)
@@ -1,6 +1,7 @@
1
+ import type { UiLanguage } from './env/uiLanguage.js';
1
2
  export declare const AGENT_MESSAGE_TAG = "agent-message";
2
3
  export declare const CROSS_SESSION_MESSAGE_TAG = "cross-session-message";
3
- export declare const PEER_FRAME_LANES: readonly ["agent_message", "cross_session_message", "cross_session_notice"];
4
+ export declare const PEER_FRAME_LANES: readonly ["agent_message", "cross_session_message", "cross_session_notice", "inbox_notice"];
4
5
  export declare const CROSS_SESSION_NOTICE_KINDS: readonly ["delivery_notice", "idle_notice"];
5
6
  export type CrossSessionNoticeKind = (typeof CROSS_SESSION_NOTICE_KINDS)[number];
6
7
  export declare const PEER_MODE_CLASSES: readonly ["bypass", "prompting"];
@@ -22,13 +23,50 @@ export type CrossSessionMessageFrame = {
22
23
  fromScope?: string;
23
24
  seq?: number;
24
25
  };
26
+ export type PeerNoticeFacts = {
27
+ state: string;
28
+ recipient?: string;
29
+ target?: string;
30
+ detail?: string;
31
+ dropReason?: string;
32
+ refSeq?: number;
33
+ finishedAt?: number;
34
+ };
25
35
  export type CrossSessionNoticeFrame = {
26
36
  lane: 'cross_session_notice';
27
37
  kind: CrossSessionNoticeKind;
28
38
  text: string;
39
+ notice?: PeerNoticeFacts;
40
+ };
41
+ export type InboxNoticeFrame = {
42
+ lane: 'inbox_notice';
43
+ pending: number;
44
+ delivered: number;
45
+ urgent?: number;
46
+ resupplied?: number;
29
47
  };
30
- export type PeerFrameProjection = AgentMessageFrame | CrossSessionMessageFrame | CrossSessionNoticeFrame;
48
+ export type PeerFrameProjection = AgentMessageFrame | CrossSessionMessageFrame | CrossSessionNoticeFrame | InboxNoticeFrame;
31
49
  export declare function classifyPeerNotification(raw: Record<string, unknown>): PeerFrameProjection | null;
50
+ export type SessionBoxNoticeFacts = {
51
+ noticeKind: 'inbox';
52
+ pending: number;
53
+ delivered: number;
54
+ urgent?: number;
55
+ resupplied?: number;
56
+ } | {
57
+ noticeKind: 'delivery' | 'idle';
58
+ state: string;
59
+ peerLabel?: string;
60
+ detail?: string;
61
+ dropReason?: string;
62
+ refSeq?: number;
63
+ finishedAt?: number;
64
+ };
65
+ export type SessionBoxNoticeBody = SessionBoxNoticeFacts & {
66
+ text: string;
67
+ };
68
+ export declare function sessionBoxNoticeSentence(facts: SessionBoxNoticeFacts, lang?: UiLanguage): string | undefined;
69
+ export declare function sessionBoxNoticeOf(p: PeerFrameProjection): SessionBoxNoticeBody | undefined;
32
70
  export declare function renderPeerFrameTranscriptText(p: PeerFrameProjection): string;
33
71
  export declare function parsePeerFrameText(text: string): AgentMessageFrame | CrossSessionMessageFrame | null;
34
72
  export declare function peerFrameDisplayName(p: AgentMessageFrame | CrossSessionMessageFrame): string;