@sema-agent/client-core 0.82.7 → 0.83.1

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 (58) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/README.md +15 -7
  3. package/dist/adapt/arms.d.ts +3 -2
  4. package/dist/adapt/arms.js +17 -11
  5. package/dist/adapt/ids.js +12 -8
  6. package/dist/adapt/textStream.d.ts +3 -2
  7. package/dist/adapt/toolCards.d.ts +2 -3
  8. package/dist/adapt/toolCards.js +1 -1
  9. package/dist/adapt.d.ts +1 -1
  10. package/dist/adapt.js +13 -6
  11. package/dist/adapter/activeRunSelfHeal.js +34 -17
  12. package/dist/adapter/downstream/eventToSdkMessage.js +1 -1
  13. package/dist/adapter/downstream/terminalToSdkResult.d.ts +2 -4
  14. package/dist/adapter/downstream/terminalToSdkResult.js +46 -8
  15. package/dist/adapter/runStream.js +25 -8
  16. package/dist/clientSlice.d.ts +0 -5
  17. package/dist/displayUntrusted.d.ts +24 -0
  18. package/dist/displayUntrusted.js +1437 -0
  19. package/dist/engineNoticeCodes.js +4 -0
  20. package/dist/fleet/fleetRowAgentType.d.ts +0 -1
  21. package/dist/fleet/fleetRowAgentType.js +0 -3
  22. package/dist/fleetTaskDesc.js +3 -3
  23. package/dist/gateVocabulary.js +1 -1
  24. package/dist/hitl/approvalDecisionNoteAudit.js +4 -2
  25. package/dist/hitl/approvalResolution.js +2 -0
  26. package/dist/hitl/frameRouter.d.ts +3 -2
  27. package/dist/hitl/sessionPolicyDeliverable.d.ts +14 -0
  28. package/dist/hitl/sessionPolicyDeliverable.js +80 -0
  29. package/dist/hitl/toolApprovalWire.d.ts +1 -0
  30. package/dist/hitl/toolApprovalWire.js +56 -10
  31. package/dist/hooksWireCaps.d.ts +6 -1
  32. package/dist/hooksWireCaps.js +220 -13
  33. package/dist/host.d.ts +25 -0
  34. package/dist/index.d.ts +4 -0
  35. package/dist/index.js +3 -0
  36. package/dist/liveInitToolFace.d.ts +4 -2
  37. package/dist/liveInitToolFace.js +2 -2
  38. package/dist/model/catalogLoader.d.ts +1 -6
  39. package/dist/panelRunningHistory.d.ts +3 -2
  40. package/dist/peerFrames.d.ts +0 -1
  41. package/dist/peerFrames.js +2 -1
  42. package/dist/pluginHooksWire.d.ts +92 -0
  43. package/dist/pluginHooksWire.js +429 -0
  44. package/dist/printInitToolFace.js +1 -1
  45. package/dist/request/printNotification.js +26 -26
  46. package/dist/seam.d.ts +8 -2
  47. package/dist/seam.js +18 -7
  48. package/dist/subagent/engineSubagentResume.js +2 -1
  49. package/dist/systemReminderTag.d.ts +0 -1
  50. package/dist/systemReminderTag.js +0 -3
  51. package/dist/toolResult.js +1 -0
  52. package/dist/uuidV5.d.ts +3 -0
  53. package/dist/uuidV5.js +178 -0
  54. package/dist/webSearchWireCaps.d.ts +0 -3
  55. package/dist/webSearchWireCaps.js +21 -51
  56. package/dist/wireFailureShape.d.ts +2 -1
  57. package/docs/INTEGRATION-CLIENTS.md +794 -13
  58. package/package.json +2 -2
@@ -0,0 +1,429 @@
1
+ import { displayUntrusted } from './displayUntrusted.js';
2
+ export const ENGINE_FIRED_HOOK_EVENTS = [
3
+ 'PreToolUse',
4
+ 'PostToolUse',
5
+ 'PostToolUseFailure',
6
+ 'PostToolBatch',
7
+ 'PreCompact',
8
+ 'PostCompact',
9
+ 'Stop',
10
+ 'StopFailure',
11
+ 'UserPromptSubmit',
12
+ ];
13
+ export const PLUGIN_HOOK_DISPOSITIONS = ['engine', 'shell', 'not_run'];
14
+ export const PLUGIN_HOOK_REASONS = [
15
+ 'projected',
16
+ 'shell_event',
17
+ 'no_executor',
18
+ 'engine_not_local',
19
+ 'engine_locality_unknown',
20
+ 'engine_capability_absent',
21
+ 'engine_capability_unknown',
22
+ 'not_command',
23
+ 'sensitive_options',
24
+ 'engine_limit',
25
+ 'shell_form_user_config',
26
+ 'user_config_unset',
27
+ 'malformed_entry',
28
+ ];
29
+ export const PLUGIN_HOOK_EXCLUSION_REASONS = [
30
+ 'workspace_untrusted',
31
+ 'governance_unreadable',
32
+ 'policy_disable_all',
33
+ 'startup_mode',
34
+ 'managed_plugins_only',
35
+ ];
36
+ export const SETTINGS_HOOK_DROP_REASONS = ['exec_form_unsupported', 'mcp_tool_unsupported'];
37
+ export const ENGINE_HOOK_LIMITS = { maxGroupsPerEvent: 16, maxEntriesPerEvent: 32, maxCommandChars: 8192 };
38
+ const ENGINE_EVENTS = new Set(ENGINE_FIRED_HOOK_EVENTS);
39
+ const USER_CONFIG_REF_ONE = /\$\{user_config\.[^}]+\}/;
40
+ const USER_CONFIG_REF_ALL = /\$\{user_config\.([^}]+)\}/g;
41
+ function isRecord(v) {
42
+ return typeof v === 'object' && v !== null && !Array.isArray(v);
43
+ }
44
+ function nonEmptyString(v) {
45
+ return typeof v === 'string' && v.length > 0;
46
+ }
47
+ export function isEngineFiredHookEvent(event) {
48
+ return ENGINE_EVENTS.has(event);
49
+ }
50
+ function declaresSensitive(options) {
51
+ if (options === undefined)
52
+ return false;
53
+ if (!isRecord(options))
54
+ return true;
55
+ return Object.values(options).some((o) => !isRecord(o) || o.sensitive !== false);
56
+ }
57
+ function savedPlainValue(options, key) {
58
+ if (!isRecord(options) || !Object.hasOwn(options, key))
59
+ return undefined;
60
+ const o = options[key];
61
+ if (!isRecord(o) || o.sensitive !== false)
62
+ return undefined;
63
+ const v = o.value;
64
+ return v === undefined || v === null ? undefined : String(v);
65
+ }
66
+ function declaredSensitiveKey(options, key) {
67
+ if (!isRecord(options) || !Object.hasOwn(options, key))
68
+ return false;
69
+ const o = options[key];
70
+ return !isRecord(o) || o.sensitive !== false;
71
+ }
72
+ function optionEnvKey(key) {
73
+ return key.replace(/[^A-Za-z0-9_]/g, '_').toUpperCase();
74
+ }
75
+ function projectedOptions(options) {
76
+ if (!isRecord(options))
77
+ return undefined;
78
+ const out = {};
79
+ let any = false;
80
+ for (const key of Object.keys(options)) {
81
+ const v = savedPlainValue(options, key);
82
+ if (v === undefined)
83
+ continue;
84
+ out[optionEnvKey(key)] = v;
85
+ any = true;
86
+ }
87
+ return any ? out : undefined;
88
+ }
89
+ function substituteUserConfig(s, options) {
90
+ return s.replace(USER_CONFIG_REF_ALL, (whole, key) => savedPlainValue(options, key) ?? whole);
91
+ }
92
+ function classifyEntry(entry, options) {
93
+ if (!isRecord(entry) || typeof entry.type !== 'string')
94
+ return { kind: 'malformed' };
95
+ if (entry.type !== 'command')
96
+ return { kind: 'other' };
97
+ const command = entry.command;
98
+ if (!nonEmptyString(command))
99
+ return { kind: 'malformed' };
100
+ const args = entry.args;
101
+ if (args === undefined)
102
+ return { kind: 'shell_form', refusesUserConfig: USER_CONFIG_REF_ONE.test(command) };
103
+ if (!Array.isArray(args) || !args.every((a) => typeof a === 'string'))
104
+ return { kind: 'malformed' };
105
+ const unset = [command, ...args].some((s) => [...s.matchAll(USER_CONFIG_REF_ALL)].some((mm) => mm[1] !== undefined && !declaredSensitiveKey(options, mm[1]) && savedPlainValue(options, mm[1]) === undefined));
106
+ return { kind: 'exec_form', command, args, unsetUserConfig: unset };
107
+ }
108
+ export function occupancyOf(wire) {
109
+ const out = new Map();
110
+ for (const [event, groups] of Object.entries(wire)) {
111
+ if (!Array.isArray(groups))
112
+ continue;
113
+ let entries = 0;
114
+ for (const g of groups)
115
+ if (isRecord(g) && Array.isArray(g.hooks))
116
+ entries += g.hooks.length;
117
+ out.set(event, { groups: groups.length, entries });
118
+ }
119
+ return out;
120
+ }
121
+ export function decidePluginHooks(input) {
122
+ const verdicts = [];
123
+ const excluded = [];
124
+ const candidates = new Map();
125
+ let unreadable = 0;
126
+ const fallback = (event, reason) => input.shellHookEvents.has(event) ? { disposition: 'shell', reason } : { disposition: 'not_run', reason };
127
+ input.plugins.forEach((p, pluginIndex) => {
128
+ if (!isRecord(p) || !isRecord(p.hooks)) {
129
+ unreadable++;
130
+ return;
131
+ }
132
+ const src = p;
133
+ const sourceOk = nonEmptyString(p.id) && nonEmptyString(p.name) && nonEmptyString(p.root) && nonEmptyString(p.dataDir);
134
+ const pluginId = typeof p.id === 'string' ? p.id : '';
135
+ const pluginName = typeof p.name === 'string' ? p.name : '';
136
+ for (const [event, groups] of Object.entries(p.hooks)) {
137
+ if (!Array.isArray(groups)) {
138
+ unreadable++;
139
+ continue;
140
+ }
141
+ ;
142
+ groups.forEach((group, groupIndex) => {
143
+ if (!isRecord(group) || !Array.isArray(group.hooks)) {
144
+ unreadable++;
145
+ return;
146
+ }
147
+ ;
148
+ group.hooks.forEach((entry, hookIndex) => {
149
+ const key = { pluginId, pluginName, event, groupIndex, hookIndex };
150
+ if (input.exclusion !== undefined) {
151
+ excluded.push({ ...key, reason: input.exclusion });
152
+ return;
153
+ }
154
+ if (input.managedOnly && p.managed !== true) {
155
+ excluded.push({ ...key, reason: 'managed_plugins_only' });
156
+ return;
157
+ }
158
+ const shape = sourceOk ? classifyEntry(entry, src.options) : { kind: 'malformed' };
159
+ if (shape.kind === 'malformed') {
160
+ verdicts.push({ ...key, disposition: 'not_run', reason: 'malformed_entry' });
161
+ return;
162
+ }
163
+ if (shape.kind === 'shell_form' && shape.refusesUserConfig) {
164
+ verdicts.push({ ...key, disposition: 'not_run', reason: 'shell_form_user_config' });
165
+ return;
166
+ }
167
+ if (shape.kind === 'exec_form' && shape.unsetUserConfig) {
168
+ verdicts.push({ ...key, disposition: 'not_run', reason: 'user_config_unset' });
169
+ return;
170
+ }
171
+ if (!ENGINE_EVENTS.has(event)) {
172
+ verdicts.push({
173
+ ...key,
174
+ ...(input.shellHookEvents.has(event)
175
+ ? { disposition: 'shell', reason: 'shell_event' }
176
+ : { disposition: 'not_run', reason: 'no_executor' }),
177
+ });
178
+ return;
179
+ }
180
+ const blocked = input.engine.ownedByThisShell === null
181
+ ? 'engine_locality_unknown'
182
+ : !input.engine.ownedByThisShell
183
+ ? 'engine_not_local'
184
+ : input.engine.pluginHooksCapable === null
185
+ ? 'engine_capability_unknown'
186
+ : !input.engine.pluginHooksCapable
187
+ ? 'engine_capability_absent'
188
+ : shape.kind === 'other'
189
+ ? 'not_command'
190
+ : declaresSensitive(src.options)
191
+ ? 'sensitive_options'
192
+ : undefined;
193
+ if (blocked !== undefined) {
194
+ verdicts.push({ ...key, ...fallback(event, blocked) });
195
+ return;
196
+ }
197
+ const plugin = { name: src.name, root: src.root, dataDir: src.dataDir };
198
+ const options = projectedOptions(src.options);
199
+ if (options !== undefined)
200
+ plugin.options = options;
201
+ const wireEntry = { ...entry, plugin };
202
+ if (shape.kind === 'exec_form') {
203
+ wireEntry.command = substituteUserConfig(shape.command, src.options);
204
+ wireEntry.args = shape.args.map((a) => substituteUserConfig(a, src.options));
205
+ }
206
+ const verdict = { ...key, disposition: 'engine', reason: 'projected' };
207
+ verdicts.push(verdict);
208
+ const list = candidates.get(event) ?? [];
209
+ list.push({ verdict, pluginIndex, group, wireEntry });
210
+ candidates.set(event, list);
211
+ });
212
+ });
213
+ }
214
+ });
215
+ const projected = new Map();
216
+ for (const [event, list] of candidates) {
217
+ const fits = list.filter((c) => {
218
+ const cmd = c.wireEntry.command;
219
+ if (typeof cmd === 'string' && cmd.length <= ENGINE_HOOK_LIMITS.maxCommandChars)
220
+ return true;
221
+ Object.assign(c.verdict, fallback(event, 'engine_limit'));
222
+ return false;
223
+ });
224
+ if (fits.length === 0)
225
+ continue;
226
+ const groupKeys = [];
227
+ for (const c of fits) {
228
+ const k = `${c.pluginIndex}\u0000${c.verdict.groupIndex}`;
229
+ if (!groupKeys.includes(k))
230
+ groupKeys.push(k);
231
+ }
232
+ const occ = input.occupied.get(event) ?? { groups: 0, entries: 0 };
233
+ if (occ.groups + groupKeys.length > ENGINE_HOOK_LIMITS.maxGroupsPerEvent ||
234
+ occ.entries + fits.length > ENGINE_HOOK_LIMITS.maxEntriesPerEvent) {
235
+ for (const c of fits)
236
+ Object.assign(c.verdict, fallback(event, 'engine_limit'));
237
+ continue;
238
+ }
239
+ const groupsOut = [];
240
+ for (const k of groupKeys) {
241
+ const members = fits.filter((c) => `${c.pluginIndex}\u0000${c.verdict.groupIndex}` === k);
242
+ const first = members[0];
243
+ if (first === undefined)
244
+ continue;
245
+ groupsOut.push({ ...first.group, hooks: members.map((c) => c.wireEntry) });
246
+ }
247
+ projected.set(event, groupsOut);
248
+ }
249
+ return { verdicts, excluded, projected, unreadable };
250
+ }
251
+ const SHELL_INERT_COMMAND = /^[A-Za-z0-9_./:+-]+$/;
252
+ function carriesArgs(entry) {
253
+ const args = entry.args;
254
+ if (args === undefined || args === null)
255
+ return false;
256
+ if (Array.isArray(args) && args.length === 0) {
257
+ const command = entry.command;
258
+ return !(typeof command === 'string' && SHELL_INERT_COMMAND.test(command));
259
+ }
260
+ return true;
261
+ }
262
+ export function filterSettingsHookGroups(source, event, groups, execFormCapable, hostFires, failLoudMcpTool) {
263
+ const drops = [];
264
+ const out = [];
265
+ let stripped = 0;
266
+ const engineEvent = ENGINE_EVENTS.has(event);
267
+ const notRun = engineEvent && !hostFires;
268
+ const keepMcpTool = failLoudMcpTool && engineEvent;
269
+ groups.forEach((group, groupIndex) => {
270
+ if (!isRecord(group) || !Array.isArray(group.hooks)) {
271
+ out.push(group);
272
+ return;
273
+ }
274
+ const kept = [];
275
+ let changed = false;
276
+ group.hooks.forEach((entry, hookIndex) => {
277
+ const reason = !isRecord(entry)
278
+ ? undefined
279
+ : entry.type === 'mcp_tool'
280
+ ? keepMcpTool
281
+ ? undefined
282
+ : 'mcp_tool_unsupported'
283
+ : entry.type === 'command' && carriesArgs(entry) && !execFormCapable
284
+ ? 'exec_form_unsupported'
285
+ : undefined;
286
+ if (reason !== undefined) {
287
+ drops.push({ source, event, groupIndex, hookIndex, reason, notRun });
288
+ changed = true;
289
+ return;
290
+ }
291
+ if (isRecord(entry) && Object.hasOwn(entry, 'plugin')) {
292
+ const copy = { ...entry };
293
+ delete copy.plugin;
294
+ kept.push(copy);
295
+ stripped++;
296
+ changed = true;
297
+ return;
298
+ }
299
+ kept.push(entry);
300
+ });
301
+ if (!changed)
302
+ out.push(group);
303
+ else if (kept.length > 0)
304
+ out.push({ ...group, hooks: kept });
305
+ });
306
+ return { groups: out, drops, stripped };
307
+ }
308
+ export function pluginHookVerdictOf(plan, pluginId, event, groupIndex, hookIndex) {
309
+ return plan.verdicts.find((v) => v.pluginId === pluginId && v.event === event && v.groupIndex === groupIndex && v.hookIndex === hookIndex);
310
+ }
311
+ const REASON_TEXT = {
312
+ projected: 'it is sent to the engine',
313
+ shell_event: 'this client runs this event itself',
314
+ no_executor: 'neither the engine nor this client runs this event',
315
+ engine_not_local: 'the engine was not started by this client on this machine, so it cannot reach the plugin files',
316
+ 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',
317
+ engine_capability_absent: 'the connected engine does not support plugin hooks',
318
+ engine_capability_unknown: 'it is not known yet whether the connected engine supports plugin hooks, so they are not sent to it',
319
+ not_command: 'only command hooks from plugins can run in the engine',
320
+ sensitive_options: 'the plugin has sensitive options, and those are never sent to the engine',
321
+ engine_limit: "sending it would exceed the engine's hook limits for this event",
322
+ shell_form_user_config: 'its command uses ${user_config.…} in shell form, which is refused (use exec form with "args", or read $CLAUDE_PLUGIN_OPTION_<KEY>)',
323
+ user_config_unset: 'its command uses a plugin option that has not been set',
324
+ malformed_entry: 'the hook entry is malformed',
325
+ };
326
+ const DROP_TEXT = {
327
+ exec_form_unsupported: 'exec-form hooks (with "args") are sent to the engine only once it reports support for them',
328
+ mcp_tool_unsupported: 'MCP tool hooks are not supported by the engine',
329
+ };
330
+ const SOURCE_TEXT = {
331
+ policySettings: 'Managed settings',
332
+ userSettings: 'User settings',
333
+ projectSettings: 'Project settings',
334
+ localSettings: 'Local settings',
335
+ };
336
+ export function pluginHookReasonText(reason) {
337
+ return REASON_TEXT[reason];
338
+ }
339
+ export function settingsHookDropText(reason) {
340
+ return DROP_TEXT[reason];
341
+ }
342
+ export function pluginHookExecutorLabel(v) {
343
+ switch (v.disposition) {
344
+ case 'engine':
345
+ return 'runs in the engine';
346
+ case 'shell':
347
+ return 'runs in this client';
348
+ case 'not_run':
349
+ return `does not run: ${pluginHookReasonText(v.reason)}`;
350
+ }
351
+ }
352
+ const NOTICE_LABEL_MAX = 120;
353
+ const noticeLabel = (s) => capNoticeLabel(escapeNoticeQuotes(displayUntrusted(s, { credentialUrls: false, credentialWords: false })));
354
+ function escapeNoticeQuotes(s) {
355
+ const budget = NOTICE_LABEL_MAX + 16;
356
+ const out = [];
357
+ let len = 0;
358
+ let i = 0;
359
+ while (i < s.length && len <= budget) {
360
+ const c = s.charCodeAt(i);
361
+ if (c === 0x5c) {
362
+ let j = i;
363
+ while (j < s.length && s.charCodeAt(j) === 0x5c)
364
+ j++;
365
+ const want = s.charCodeAt(j) === 0x22 ? (j - i) * 2 : j - i;
366
+ const take = Math.min(want, budget + 1 - len);
367
+ out.push('\\'.repeat(take));
368
+ len += take;
369
+ i = j;
370
+ continue;
371
+ }
372
+ if (c === 0x22) {
373
+ out.push('\\"');
374
+ len += 2;
375
+ }
376
+ else {
377
+ out.push(s.charAt(i));
378
+ len++;
379
+ }
380
+ i++;
381
+ }
382
+ return out.join('');
383
+ }
384
+ function capNoticeLabel(s) {
385
+ if (s.length <= NOTICE_LABEL_MAX)
386
+ return s;
387
+ const token = /\\u\{[0-9A-F]{1,6}\}|\\u[0-9A-F]{4}|\\[\\"]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[^]/gy;
388
+ let end = 0;
389
+ for (let t = token.exec(s); t !== null; t = token.exec(s)) {
390
+ if (t.index + t[0].length > NOTICE_LABEL_MAX - 1)
391
+ break;
392
+ end = t.index + t[0].length;
393
+ }
394
+ return `${s.slice(0, end)}…`;
395
+ }
396
+ export function hooksNotRunNotice(plan) {
397
+ const lines = [];
398
+ const byPlugin = new Map();
399
+ for (const v of plan.verdicts) {
400
+ if (v.disposition !== 'not_run')
401
+ continue;
402
+ const k = `${v.pluginId}\u0000${v.reason}`;
403
+ const row = byPlugin.get(k) ?? { name: v.pluginName, reason: v.reason, events: [], count: 0 };
404
+ if (!row.events.includes(v.event))
405
+ row.events.push(v.event);
406
+ row.count++;
407
+ byPlugin.set(k, row);
408
+ }
409
+ for (const row of byPlugin.values()) {
410
+ const events = row.events.map((e) => noticeLabel(e)).join(', ');
411
+ lines.push(`Plugin "${noticeLabel(row.name) || '(unnamed)'}": ${events} ${row.count === 1 ? 'hook' : 'hooks'} will not run — ${pluginHookReasonText(row.reason)}.`);
412
+ }
413
+ const bySource = new Map();
414
+ for (const d of plan.settingsDropped) {
415
+ if (!d.notRun)
416
+ continue;
417
+ const k = `${d.source}\u0000${d.reason}`;
418
+ const row = bySource.get(k) ?? { source: d.source, reason: d.reason, events: [], count: 0 };
419
+ if (!row.events.includes(d.event))
420
+ row.events.push(d.event);
421
+ row.count++;
422
+ bySource.set(k, row);
423
+ }
424
+ for (const row of bySource.values()) {
425
+ const events = row.events.map((e) => noticeLabel(e)).join(', ');
426
+ lines.push(`${SOURCE_TEXT[row.source]}: ${events} ${row.count === 1 ? 'hook' : 'hooks'} will not run — ${settingsHookDropText(row.reason)}.`);
427
+ }
428
+ return lines;
429
+ }
@@ -19,7 +19,7 @@ export function printInitRosterSignal(message) {
19
19
  if (type === 'result')
20
20
  return { kind: 'no-roster', reason: 'run-ended' };
21
21
  if (type === 'assistant') {
22
- return rec.isApiErrorMessage === true ? { kind: 'no-roster', reason: 'run-ended' } : { kind: 'no-roster', reason: 'model-activity-first' };
22
+ return rec._sema_api_error_message === true ? { kind: 'no-roster', reason: 'run-ended' } : { kind: 'no-roster', reason: 'model-activity-first' };
23
23
  }
24
24
  if (type === 'stream_event' || type === 'retry_status')
25
25
  return { kind: 'no-roster', reason: 'model-activity-first' };
@@ -10,19 +10,19 @@ export const PRINT_NOTIFICATION_FIELD_MATRIX = [
10
10
  { field: 'tool_use_id', from: 'toolUseId', cc: true, why: 'CC 逐字 snake 名(wire 是 camel);把通知钉回发起它的那次工具调用' },
11
11
  { field: 'output_file', from: 'output_file', cc: true, why: 'CC 逐字;后台输出落盘路径' },
12
12
  { field: 'usage', from: 'usage', cc: true, why: 'CC 逐字 optional;计量面(交互面 XML 反而不读它 —— 那是渲染面口径)' },
13
- { field: 'stoppedBy', from: 'stoppedBy', cc: false, why: '🔴 残局归因核心位:谁停的(用户/上限/引擎)。修前 `-p` 出口零携带' },
14
- { field: 'resumable', from: 'resumable', cc: false, why: '🔴 残局归因:还能不能 resume —— 自动化要靠它决定重试还是放弃' },
15
- { field: 'partial', from: 'partial', cc: false, why: 'server 1.258:被终止子代的残果(消费方须渲成不完整,不得当完整结果)' },
16
- { field: 'exitCode', from: 'exitCode', cc: false, why: '后台 bash 退出码(137=OOM 这类归因只在这一位上)' },
17
- { field: 'diagnostics', from: 'diagnostics', cc: false, why: '引擎侧诊断串(UNTRUSTED,service 已 redact)' },
18
- { field: 'result', from: 'result', cc: false, why: '完成结果正文(#49 C1:帧带 result 必入 XML,print 出口同理不得独漏)' },
19
- { field: 'lines', from: 'lines', cc: false, why: '尾部输出行(UNTRUSTED);交互面折进 <output-lines>' },
20
- { field: 'recentSteps', from: 'recentSteps', cc: false, why: '结构化残局步骤 {tool,target,outcome}[] —— **原样透传**,不降级成交互面那份显示串(SDK 消费方要结构)' },
21
- { field: 'editedFiles', from: 'editedFiles', cc: false, why: '结构化改档清单 {path,edits}[];同上原样透传' },
22
- { field: 'task_type', from: 'task_type', cc: false, why: 'lane 位(external/background_agent/…)—— 去重键的分域依据,消费方要能自己算同一把键' },
23
- { field: 'source', from: 'source', cc: false, why: 'external lane 的来源标识(交互面渲成开标签 from= 属性)' },
24
- { field: 'seq', from: 'seq', cc: false, why: '停机周期/批次计数([508]②)—— 与 task_id+status 一起构成去重键' },
25
- { field: 'injected', from: 'injected', cc: false, why: '[1818]§四:本帧**不**证明 XML 已进模型上下文;消费方须按此位判,不得按帧到达判' },
13
+ { field: '_sema_stopped_by', from: 'stoppedBy', cc: false, why: '🔴 残局归因核心位:谁停的(用户/上限/引擎)。修前 `-p` 出口零携带' },
14
+ { field: '_sema_resumable', from: 'resumable', cc: false, why: '🔴 残局归因:还能不能 resume —— 自动化要靠它决定重试还是放弃' },
15
+ { field: '_sema_partial', from: 'partial', cc: false, why: 'server 1.258:被终止子代的残果(消费方须渲成不完整,不得当完整结果)' },
16
+ { field: '_sema_exit_code', from: 'exitCode', cc: false, why: '后台 bash 退出码(137=OOM 这类归因只在这一位上)' },
17
+ { field: '_sema_diagnostics', from: 'diagnostics', cc: false, why: '引擎侧诊断串(UNTRUSTED,service 已 redact)' },
18
+ { field: '_sema_result', from: 'result', cc: false, why: '完成结果正文(#49 C1:帧带 result 必入 XML,print 出口同理不得独漏)' },
19
+ { field: '_sema_lines', from: 'lines', cc: false, why: '尾部输出行(UNTRUSTED);交互面折进 <output-lines>' },
20
+ { field: '_sema_recent_steps', from: 'recentSteps', cc: false, why: '结构化残局步骤 {tool,target,outcome}[] —— **原样透传**,不降级成交互面那份显示串(SDK 消费方要结构)' },
21
+ { field: '_sema_edited_files', from: 'editedFiles', cc: false, why: '结构化改档清单 {path,edits}[];同上原样透传' },
22
+ { field: '_sema_task_type', from: 'task_type', cc: false, why: 'lane 位(external/background_agent/…)—— 去重键的分域依据,消费方要能自己算同一把键;CC 的 task_type 只在 task_started / task_progress 子型上且词表不同,故不占 CC 名' },
23
+ { field: '_sema_source', from: 'source', cc: false, why: 'external lane 的来源标识(交互面渲成开标签 from= 属性)' },
24
+ { field: '_sema_seq', from: 'seq', cc: false, why: '停机周期/批次计数([508]②)—— 与 task_id+status 一起构成去重键' },
25
+ { field: '_sema_injected', from: 'injected', cc: false, why: '[1818]§四:本帧**不**证明 XML 已进模型上下文;消费方须按此位判,不得按帧到达判' },
26
26
  ];
27
27
  const PRINT_FRAME_KEYS = new Set(PRINT_NOTIFICATION_FIELD_MATRIX.map(r => r.field));
28
28
  export function taskNotificationToPrintFrame(n, identity = {}) {
@@ -39,19 +39,19 @@ export function taskNotificationToPrintFrame(n, identity = {}) {
39
39
  ...(f.toolUseId !== undefined ? { tool_use_id: f.toolUseId } : {}),
40
40
  ...(f.outputFile !== undefined ? { output_file: f.outputFile } : {}),
41
41
  ...(n.usage !== undefined ? { usage: n.usage } : {}),
42
- ...(f.stoppedBy !== undefined ? { stoppedBy: f.stoppedBy } : {}),
43
- ...(f.resumable !== undefined ? { resumable: f.resumable } : {}),
44
- ...(f.partial !== undefined ? { partial: f.partial } : {}),
45
- ...(f.exitCode !== undefined ? { exitCode: f.exitCode } : {}),
46
- ...(f.diagnostics !== undefined ? { diagnostics: f.diagnostics } : {}),
47
- ...(f.result !== undefined ? { result: f.result } : {}),
48
- ...(f.lines !== undefined ? { lines: f.lines } : {}),
49
- ...(nonEmptyList(n.recentSteps) ? { recentSteps: n.recentSteps } : {}),
50
- ...(nonEmptyList(n.editedFiles) ? { editedFiles: n.editedFiles } : {}),
51
- ...(f.taskType !== undefined ? { task_type: f.taskType } : {}),
52
- ...(f.externalSource !== undefined ? { source: f.externalSource } : {}),
53
- ...(typeof n.seq === 'number' || typeof n.seq === 'string' ? { seq: n.seq } : {}),
54
- ...(typeof n.injected === 'boolean' ? { injected: n.injected } : {}),
42
+ ...(f.stoppedBy !== undefined ? { _sema_stopped_by: f.stoppedBy } : {}),
43
+ ...(f.resumable !== undefined ? { _sema_resumable: f.resumable } : {}),
44
+ ...(f.partial !== undefined ? { _sema_partial: f.partial } : {}),
45
+ ...(f.exitCode !== undefined ? { _sema_exit_code: f.exitCode } : {}),
46
+ ...(f.diagnostics !== undefined ? { _sema_diagnostics: f.diagnostics } : {}),
47
+ ...(f.result !== undefined ? { _sema_result: f.result } : {}),
48
+ ...(f.lines !== undefined ? { _sema_lines: f.lines } : {}),
49
+ ...(nonEmptyList(n.recentSteps) ? { _sema_recent_steps: n.recentSteps } : {}),
50
+ ...(nonEmptyList(n.editedFiles) ? { _sema_edited_files: n.editedFiles } : {}),
51
+ ...(f.taskType !== undefined ? { _sema_task_type: f.taskType } : {}),
52
+ ...(f.externalSource !== undefined ? { _sema_source: f.externalSource } : {}),
53
+ ...(typeof n.seq === 'number' || typeof n.seq === 'string' ? { _sema_seq: n.seq } : {}),
54
+ ...(typeof n.injected === 'boolean' ? { _sema_injected: n.injected } : {}),
55
55
  ...(identity.uuid !== undefined ? { uuid: identity.uuid } : {}),
56
56
  ...(identity.session_id !== undefined ? { session_id: identity.session_id } : {}),
57
57
  };
package/dist/seam.d.ts CHANGED
@@ -149,7 +149,7 @@ export type ChromeEvent = {
149
149
  } | ({
150
150
  kind: 'run_cost_reconciled';
151
151
  laneProof: LaneProof;
152
- } & RunCostReconcile) | HumanInputChromeEvent | MessageCommittedChromeEvent | EngineNoticeChromeEvent | TextSegmentEndChromeEvent | ThinkingSegmentEndChromeEvent | ToolDisclosureChromeEvent | ToolProgressChromeEvent | ToolRosterDeltaChromeEvent | ContextUsageChromeEvent | CompactionOutcomeChromeEvent | PromptAssembledChromeEvent | WiringManifestChromeEvent | ResultTextDivergedChromeEvent | ApprovalRequestChromeEvent | ApprovalRevokeChromeEvent;
152
+ } & RunCostReconcile) | HumanInputChromeEvent | MessageCommittedChromeEvent | EngineNoticeChromeEvent | TextSegmentEndChromeEvent | ThinkingSegmentEndChromeEvent | ToolDisclosureChromeEvent | ToolProgressChromeEvent | ToolRosterDeltaChromeEvent | ContextUsageChromeEvent | CompactionOutcomeChromeEvent | PromptAssembledChromeEvent | MemorySavedChromeEvent | WiringManifestChromeEvent | ResultTextDivergedChromeEvent | ApprovalRequestChromeEvent | ApprovalRevokeChromeEvent;
153
153
  export interface WiringManifestChromeEvent {
154
154
  kind: 'wiring_manifest';
155
155
  laneProof: LaneProof;
@@ -204,6 +204,12 @@ export interface CompactionOutcomeChromeEvent {
204
204
  reason?: string;
205
205
  eventId?: string;
206
206
  }
207
+ export interface MemorySavedChromeEvent {
208
+ kind: 'memory_saved';
209
+ laneProof: LaneProof;
210
+ id: string;
211
+ notes: string[];
212
+ }
207
213
  export interface PromptAssembledChromeEvent {
208
214
  kind: 'prompt_assembled';
209
215
  laneProof: LaneProof;
@@ -330,4 +336,4 @@ export declare function deriveTranscriptId(frame: {
330
336
  id?: unknown;
331
337
  seq?: unknown;
332
338
  toolCallId?: unknown;
333
- }, ctx: Pick<AdapterContext, 'uuid'>): string;
339
+ }, ctx: Pick<AdapterContext, 'uuid'>, suffix?: string): string;
package/dist/seam.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { TRANSCRIPT_ID_NAMESPACE, uuidV5 } from './uuidV5.js';
1
2
  const CHROME_ARM_TABLE = {
2
3
  retry_status: { required: false, duty: '渲/清 spinner 的重试覆盖层' },
3
4
  panel_task: { required: true, duty: 'publishEngineAgentPanelEvent(event) 原样转发' },
@@ -63,6 +64,13 @@ const CHROME_ARM_TABLE = {
63
64
  duty: 'CC-08(0.71.0):每个压缩边界一条,按**最后一条**取当前值;`usedTokens > compactAtTokens` 是引擎的 shouldCompact 谓词,别重算;`windowTokens` 是 autocompact 窗,' +
64
65
  '别渲成模型上下文大小;🔴 `sections[].tokens` 与 `usedTokens` 不同坐标系,**禁相减**、禁拿一段比 `compactAtTokens`;`sections` 整键缺席 = 没装配出段 IR,不是各段为零。',
65
66
  },
67
+ memory_saved: {
68
+ required: true,
69
+ duty: '0.83.0 起记忆写入不再是转录行,只发本臂。`notes` 是记忆**笔记原文、不是路径**(别拼链接 / 判文件存在);' +
70
+ '要保留「Saved N memories」那一行的宿主把本臂还原成一条转录行:行 uuid 用 `id`(由这次写入的 wire 稳定键派生、重投同值,按它幂等;与 0.82.x 旧行 uuid 不相等),N = `notes.length`,时间戳在接收口取;' +
71
+ '落成 CC 形行时不许再造 `writtenPaths` / `isMeta` 这类 CC 在该臂上不声明的键;每次写入一条事件、本包不合并;' +
72
+ '没收到本臂不等于没写记忆(回执不成形时不发,工具卡照常在);laneProof 为 subagent = 子代写的,别渲成主会话的。',
73
+ },
66
74
  compaction_outcome: {
67
75
  required: false,
68
76
  duty: 'CC-89(0.75.0):一次压缩的非 compacted 结局(作废 / 失败等),给用户一条可见提示。`outcome` / `trigger` 是引擎开集原词,按成员渲染必带 default 臂、认不得照原词显示;' +
@@ -139,12 +147,15 @@ const CHROME_ARM_TABLE = {
139
147
  },
140
148
  };
141
149
  export const CHROME_ARMS = Object.entries(CHROME_ARM_TABLE).map(([kind, d]) => ({ kind, required: d.required, duty: d.duty }));
142
- export function deriveTranscriptId(frame, ctx) {
143
- if (typeof frame.id === 'string' && frame.id.length > 0)
144
- return `wid_${frame.id}`;
145
- if (typeof frame.seq === 'number')
146
- return `wseq_${frame.seq}`;
147
- if (typeof frame.toolCallId === 'string' && frame.toolCallId.length > 0)
148
- return `wtc_${frame.toolCallId}`;
150
+ export function deriveTranscriptId(frame, ctx, suffix) {
151
+ if (typeof frame.id === 'string' && frame.id.length > 0) {
152
+ return uuidV5(TRANSCRIPT_ID_NAMESPACE, suffix !== undefined ? `wid_${frame.id}#${suffix}` : `wid_${frame.id}`);
153
+ }
154
+ if (typeof frame.seq === 'number') {
155
+ return uuidV5(TRANSCRIPT_ID_NAMESPACE, suffix !== undefined ? `wseq#${suffix}_${frame.seq}` : `wseq_${frame.seq}`);
156
+ }
157
+ if (typeof frame.toolCallId === 'string' && frame.toolCallId.length > 0) {
158
+ return uuidV5(TRANSCRIPT_ID_NAMESPACE, suffix !== undefined ? `wtc#${suffix}_${frame.toolCallId}` : `wtc_${frame.toolCallId}`);
159
+ }
149
160
  return ctx.uuid();
150
161
  }
@@ -1,5 +1,6 @@
1
1
  import { hostLog } from '../host.js';
2
2
  import { resumeRetryLaterFromError } from '../wireErrorTriage.js';
3
+ import { displayUntrusted } from '../displayUntrusted.js';
3
4
  import { engineCapTrue } from '../engineCapsCache.js';
4
5
  import { makeEngineWireClient } from '../engineWireSdk.js';
5
6
  import { engineWireTargetFor } from '../engineWireTarget.js';
@@ -99,7 +100,7 @@ export function resolveSubagentResumeContext(childTaskId) {
99
100
  return { facade: client.runs, runId: resolveOwnerRunId(childTaskId), session };
100
101
  }
101
102
  function clean(v, max = 200) {
102
- return String(v ?? '').replace(/[\x00-\x1f\x7f]/g, '.').slice(0, max);
103
+ return displayUntrusted(String(v ?? ''), { credentialUrls: false, credentialWords: false, foldLines: false, mark: 'dot', max });
103
104
  }
104
105
  export async function resumeSettledSubagent(facade, runId, target, content, opts) {
105
106
  if (facade === null)
@@ -1,3 +1,2 @@
1
1
  export declare function unwrapSystemReminder(text: string): string;
2
2
  export declare function stripSystemReminderBlocks(text: string): string;
3
- export declare function startsWithSystemReminderOpenTag(text: string): boolean;
@@ -7,6 +7,3 @@ export function unwrapSystemReminder(text) {
7
7
  export function stripSystemReminderBlocks(text) {
8
8
  return text.replace(new RegExp(`${OPEN_TAG_SOURCE}[\\s\\S]*?<\\/system-reminder>`, 'g'), '');
9
9
  }
10
- export function startsWithSystemReminderOpenTag(text) {
11
- return new RegExp(`^\\s*${OPEN_TAG_SOURCE}`).test(text);
12
- }
@@ -47,6 +47,7 @@ export const STRUCTURED_DETAIL_TYPES = new Set([
47
47
  'read_path_denied',
48
48
  'list-agents',
49
49
  'artifact',
50
+ 'read-inbox',
50
51
  ]);
51
52
  export function structuredDetailType(structured) {
52
53
  if (typeof structured !== 'object' || structured === null)
@@ -0,0 +1,3 @@
1
+ export declare const TRANSCRIPT_ID_NAMESPACE = "5935225a-0f39-4c72-b409-4893196112ab";
2
+ export declare function utf8Encode(s: string): Uint8Array;
3
+ export declare function uuidV5(namespace: string, name: string): string;