@sema-agent/client-core 0.88.0 → 0.90.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 (61) hide show
  1. package/CHANGELOG.md +208 -0
  2. package/README.md +27 -21
  3. package/dist/adapter/activeRunSelfHeal.js +4 -2
  4. package/dist/adapter/downstream/eventToSdkMessage.js +4 -2
  5. package/dist/adapter/downstream/wiringManifestView.d.ts +9 -0
  6. package/dist/adapter/downstream/wiringManifestView.js +27 -0
  7. package/dist/adapter/runStream.d.ts +2 -0
  8. package/dist/adapter/runStream.js +157 -32
  9. package/dist/attachmentsWireCaps.d.ts +13 -2
  10. package/dist/attachmentsWireCaps.js +25 -5
  11. package/dist/controlRouter.d.ts +7 -1
  12. package/dist/controlRouter.js +43 -1
  13. package/dist/coreValuePorts.d.ts +1 -0
  14. package/dist/disableSkillsWire.d.ts +17 -0
  15. package/dist/disableSkillsWire.js +52 -0
  16. package/dist/displayUntrusted.js +122 -55
  17. package/dist/engineCapReader.js +12 -1
  18. package/dist/engineNoticeCodes.d.ts +25 -9
  19. package/dist/engineNoticeCodes.js +161 -68
  20. package/dist/fileHistoryLegNotice.d.ts +20 -0
  21. package/dist/fileHistoryLegNotice.js +122 -0
  22. package/dist/gateOutcome.d.ts +1 -1
  23. package/dist/gateOutcome.js +1 -0
  24. package/dist/generated/engineFactTables.d.ts +1 -0
  25. package/dist/generated/engineFactTables.js +3 -0
  26. package/dist/generated/engineNoticeTables.d.ts +3 -0
  27. package/dist/generated/engineNoticeTables.js +107 -2
  28. package/dist/hitl/toolApprovalWire.d.ts +6 -2
  29. package/dist/hitl/toolApprovalWire.js +30 -0
  30. package/dist/hookToolAliases.d.ts +3 -0
  31. package/dist/hookToolAliases.js +10 -0
  32. package/dist/hooksWireCaps.js +80 -12
  33. package/dist/index.d.ts +14 -3
  34. package/dist/index.js +10 -3
  35. package/dist/permissionsAllowGrantCapability.d.ts +17 -0
  36. package/dist/permissionsAllowGrantCapability.js +75 -0
  37. package/dist/permissionsDeclaration.d.ts +25 -0
  38. package/dist/permissionsDeclaration.js +227 -0
  39. package/dist/pluginHooksWire.d.ts +46 -4
  40. package/dist/pluginHooksWire.js +277 -24
  41. package/dist/promptDelivery.d.ts +5 -2
  42. package/dist/promptDelivery.js +8 -3
  43. package/dist/providerRejection.d.ts +7 -0
  44. package/dist/providerRejection.js +58 -0
  45. package/dist/request/taskRequest.d.ts +2 -0
  46. package/dist/request/taskRequest.js +67 -9
  47. package/dist/runTerminal.d.ts +6 -0
  48. package/dist/runTerminal.js +23 -0
  49. package/dist/sdkRegistryTransit.d.ts +1 -0
  50. package/dist/sdkRegistryTransit.js +1 -0
  51. package/dist/seam.d.ts +3 -0
  52. package/dist/seatContract.d.ts +9 -1
  53. package/dist/seatContract.js +10 -0
  54. package/dist/subagent/engineSubagentSteer.d.ts +8 -1
  55. package/dist/subagent/engineSubagentSteer.js +26 -3
  56. package/dist/supportFloor.d.ts +10 -0
  57. package/dist/supportFloor.js +42 -0
  58. package/dist/taskRequestWords.d.ts +1 -0
  59. package/dist/taskRequestWords.js +14 -0
  60. package/docs/INTEGRATION-CLIENTS.md +896 -14
  61. package/package.json +3 -3
@@ -1,3 +1,6 @@
1
+ import { refuseBadDisableSkills, refuseDisableSkillsConflict } from '../disableSkillsWire.js';
2
+ import { ATTACHMENTS_SPEC_KEYS, AttachmentsSpecRefused } from '../attachmentsWireCaps.js';
3
+ import { retiredAttachmentsKeyWhy } from '../taskRequestWords.js';
1
4
  export const REQUEST_FIELD_MATRIX = [
2
5
  { field: 'objective', lanes: ['interactive', 'print', 'utility'], live: false, why: '本 turn 的真实输入,每条车道都必需', whyUtilityLane: '恒座:三条车道都必须带上本次提交的模型面输入' },
3
6
  { field: 'sessionId', lanes: ['interactive', 'print', 'utility'], live: false, why: '单命名空间(TP-A Step 2):壳 id 即引擎 session id;print 在非 live 下保留 mock 常量', whyUtilityLane: '恒座:side-channel 提交走**独立**会话 id(壳另铸一个,绝不复用用户会话那一个 —— 复用会把这次提交的转录混进用户会话)' },
@@ -25,6 +28,7 @@ export const REQUEST_FIELD_MATRIX = [
25
28
  { field: 'fileHistory', lanes: ['interactive', 'print'], live: true, why: '「这一次运行别捕获文件历史」的**请求级声明**(server 7.104.0 提交体键,单成员闭集 "disabled";`POST /v1/tasks` · `/v1/tasks/stream` · `/v1/runs` 同收,子代继承)。两条用户车道都是提交面、都跑在用户的文件上 ⇒ 都能声明;「本会话不捕获」= 每个提交都带(续跑腿重放同一个体)。🔴 只在能力读数 `on-optional` 时发(老 server 对表外键 400),值由片段口 `fileHistoryDeclarationField` 唯一铸出。与 rewind 还原键同交 ⇒ 上游 400 `request.field_conflict`', whyUtilityLane: '无座:side-channel 强制零工具面(`excludeAllTools`),不会改任何文件 ⇒ 没有可捕获的东西;带上它只会在不认识这一键的老 server 上给这条提交多一种 400' },
26
29
  { field: 'inheritEnv', lanes: ['interactive', 'print'], live: true, why: '模型驱动的宿主 shell「继承多少用户环境」的**请求级声明**(server 7.106.0 提交体键,两词闭集 "all" / "scrub";`POST /v1/tasks` · `/v1/tasks/stream` · `/v1/runs` 同收,续跑腿重放同判)。两条用户车道都是提交面、都可能在用户机器上跑 shell ⇒ 都能声明。本层两词原样透传、不按车道 / 部署预判(非 host 车道 × "all" 400、托管 × "all" 422 归上游判)。🔴 缺席 = 缺省清洗且清掉本会话上一条声明 ⇒ 要「本会话整份继承」就每个提交都带。🔴 只在服务端版本 ≥ 7.106.0 时发(老 server 对表外键整条 400),值由片段口 `inheritEnvDeclaration` 唯一铸出', whyUtilityLane: '无座:side-channel 强制零工具面(`excludeAllTools`),没有模型驱动的 shell 去继承环境;带上它只会在不认这一键的老服务端上给这条提交多一种 400' },
27
30
  { field: 'requireExistingSession', lanes: ['interactive', 'print'], live: true, why: '「只续已有会话」的**请求级断言**(sdk `TaskRequest.requireExistingSession`):`true` 且会话不在 ⇒ 服务端在任何东西跑起来之前拒(会话不在 / 没给会话 id 两形),而**不是**静默新开一个同 id 的空会话;缺席 / `false` = 历史的「找不到就新开」。两条用户车道都有「续某个会话」的入口 ⇒ 都能声明。本层只认字面 `true`(`false` 与缺席同义、不发);非布尔坏值响亮拒(上游对它 400);`true` 而入参没有可续的会话 id ⇒ 装配期按名响亮拒,不留给上游。上游两形的拒绝经 `requireExistingSessionRefusalFromError` 归成一句', whyUtilityLane: '无座:side-channel 每一次提交都走壳另铸的**独立**会话 id(见 sessionId 那一行),从不续任何已有会话 ⇒「必须已存在」对它没有意义,带上去只会让每一次提交都被拒' },
31
+ { field: 'disableSkills', lanes: ['interactive', 'print'], live: true, why: '「这一发一只技能都不装」的**请求级声明**(出厂技能、调用方给的技能、子代定义里的技能一个都不进引擎;其余工具照挂 —— 与 excludeAllTools 是两件事,两键同带各自生效)。两条用户车道都是提交面 ⇒ 都能声明。本层只认字面 true(false 与缺席同义、不发);非布尔坏值响亮拒(上游对它 400);与非空 skills 或任一非空 agents[].skills 同在 ⇒ 装配期按名响亮拒(上游对这一组合 400),兜底层落 agents 之后同判。🔴 只在能力位读数为真时发(老服务端对表外键整条 400),值由片段口 disableSkillsDeclaration 唯一铸出', whyUtilityLane: '无座:同 skills 一行 —— side-channel 是零工具面,装不装技能都无从谈起' },
28
32
  { field: 'finalVerification', lanes: ['print'], live: true, why: 'P1-1 终验:无人值守车道才需要引擎自证;交互 REPL 由人当场看结果。#106 裁 B(让位+告知)后交互面默认关', whyUtilityLane: '无座:终验让引擎再跑一轮自证,而 side-channel 要的是一次小提交的即时答案;「无人值守才需要引擎自证」这条理由在本车道上让位给「小预算优先」' },
29
33
  { field: 'limits', lanes: ['print', 'utility'], live: true, why: 'P2-3-b:`-p` 的预算护栏(--max-* flag 族),交互 REPL 由人随时 Esc', whyUtilityLane: '有座:side-channel 必须带预算护栏(墙钟 / 输出 token / 轮数)—— 它同样没有人看着,而且没有后续回合可以中断' },
30
34
  { field: 'interactiveTools', lanes: ['print', 'utility'], live: true, why: '[909]B 件3:无人值守 stamp false,从 roster 源头灭掉 AskUserQuestion/plan 门。交互车道 stamp false 等于自废武功', whyUtilityLane: '有座:side-channel 绝不该弹交互问答 / plan 门(没有人在看这条提交)。它与 excludeAllTools 是**两条独立**的收紧声明、在这条车道上**并存**(本车道强制带卸载声明,见 `refuseUtilityWithoutToolUnload`;这一位是它旁边那条「连问都别问」的声明,不是它的降级替身)' },
@@ -76,18 +80,40 @@ export function taskRequestOmissionCauseDetail(cause) {
76
80
  const word = typeof cause === 'string' && cause.length > 0 ? cause : '(none)';
77
81
  return `a cause this build does not know (${word}) — this build does not know this cause: receipt causes come from this build's own frozen vocabulary, so a word outside it was not produced here; check whatever supplied it`;
78
82
  }
79
- export const TASK_REQUEST_OMISSION_CAUSE_NOTICES = Object.freeze({
80
- upstream_absent: 'Not sent with this request: this version has no way to send this setting in any request, so giving it or leaving it out makes no difference to what is sent.',
81
- other_channel: 'Not sent from here: that part of the request belongs to a separate step, so what goes out for it, if anything, is decided there rather than by the value given here.',
82
- off_lane: 'Not sent with this request: this kind of request does not carry this setting, so giving it or leaving it out makes no difference to what this request sends.',
83
- not_live: 'Not sent with this request: the request was marked as not going to a live engine (for example an offline or test run), and this setting is only sent on requests that go to one.',
83
+ const TASK_REQUEST_OMISSION_CAUSE_NOTICE_PARTS = Object.freeze({
84
+ upstream_absent: {
85
+ lead: 'Not sent with this request',
86
+ reason: 'this version has no way to send this setting in any request, so giving it or leaving it out makes no difference to what is sent',
87
+ },
88
+ other_channel: {
89
+ lead: 'Not sent from here',
90
+ reason: 'that part of the request belongs to a separate step, so what goes out for it, if anything, is decided there rather than by the value given here',
91
+ },
92
+ off_lane: {
93
+ lead: 'Not sent with this request',
94
+ reason: 'this kind of request does not carry this setting, so giving it or leaving it out makes no difference to what this request sends',
95
+ },
96
+ not_live: {
97
+ lead: 'Not sent with this request',
98
+ reason: 'the request was marked as not going to a live engine (for example an offline or test run), and this setting is only sent on requests that go to one',
99
+ },
84
100
  });
85
- const TASK_REQUEST_OMISSION_UNKNOWN_CAUSE_NOTICE = 'Not sent with this request, for a reason this version does not recognize.';
101
+ const TASK_REQUEST_OMISSION_UNKNOWN_CAUSE_LEAD = 'Not sent with this request';
102
+ const TASK_REQUEST_OMISSION_UNKNOWN_CAUSE_REASON = 'for a reason this version does not recognize';
103
+ const wholeOmissionNotice = (p) => `${p.lead}: ${p.reason}.`;
104
+ export const TASK_REQUEST_OMISSION_CAUSE_NOTICES = Object.freeze(Object.fromEntries(TASK_REQUEST_OMISSION_CAUSES.map((cause) => [cause, wholeOmissionNotice(TASK_REQUEST_OMISSION_CAUSE_NOTICE_PARTS[cause])])));
105
+ const TASK_REQUEST_OMISSION_UNKNOWN_CAUSE_NOTICE = `${TASK_REQUEST_OMISSION_UNKNOWN_CAUSE_LEAD}, ${TASK_REQUEST_OMISSION_UNKNOWN_CAUSE_REASON}.`;
106
+ const omissionNoticePartsOf = (cause) => typeof cause === 'string' && Object.hasOwn(TASK_REQUEST_OMISSION_CAUSE_NOTICE_PARTS, cause)
107
+ ? TASK_REQUEST_OMISSION_CAUSE_NOTICE_PARTS[cause]
108
+ : undefined;
86
109
  export function taskRequestOmissionCauseNotice(cause) {
87
- return typeof cause === 'string' && Object.hasOwn(TASK_REQUEST_OMISSION_CAUSE_NOTICES, cause)
110
+ return omissionNoticePartsOf(cause) !== undefined
88
111
  ? TASK_REQUEST_OMISSION_CAUSE_NOTICES[cause]
89
112
  : TASK_REQUEST_OMISSION_UNKNOWN_CAUSE_NOTICE;
90
113
  }
114
+ export function taskRequestOmissionCausePhrase(cause) {
115
+ return omissionNoticePartsOf(cause)?.reason ?? TASK_REQUEST_OMISSION_UNKNOWN_CAUSE_REASON;
116
+ }
91
117
  const row = (field) => ({ via: 'matrix', field });
92
118
  const INPUT_KEY_ROUTES = {
93
119
  objective: { via: 'always' },
@@ -141,6 +167,7 @@ const INPUT_KEY_ROUTES = {
141
167
  fileHistory: row('fileHistory'),
142
168
  inheritEnv: row('inheritEnv'),
143
169
  requireExistingSession: row('requireExistingSession'),
170
+ disableSkills: row('disableSkills'),
144
171
  rewind: row('resumeAt/resumeAtMode/restoreFiles/rewindFilesTo'),
145
172
  repo: row('repo'),
146
173
  council: row('council'),
@@ -214,6 +241,7 @@ const VALUE_LEVEL_IN_PLAY = Object.freeze({
214
241
  memory: (v) => v === 'off',
215
242
  fileHistory: (v) => v === FILE_HISTORY_DISABLED,
216
243
  requireExistingSession: (v) => v === true,
244
+ disableSkills: (v) => v === true,
217
245
  rewind: (v) => typeof v === 'object' && v !== null && rewindSubKeys().some((k) => hasOwn(v, k) && present(v[k])),
218
246
  });
219
247
  const inPlay = (key, v) => present(v) && (VALUE_LEVEL_IN_PLAY[key]?.(v) ?? true);
@@ -425,6 +453,29 @@ const refuseBadMemoryOff = (v) => {
425
453
  '`"on"`,`{ enabled: false }` 对象形也不是 wire 拼法)且对坏拼写**响亮拒**(server 400 `request.field_invalid`)。' +
426
454
  '本层若静默删键,这一次运行会照常挂上记忆面而声明人不会知道 —— 与 memoryCapture 同一条纪律。错误文本只报形状,不回显值。');
427
455
  };
456
+ const withoutRetiredAttachmentsKeys = (v) => {
457
+ if (typeof v !== 'object' || v === null)
458
+ return v;
459
+ let drop;
460
+ for (const key of Object.keys(v)) {
461
+ const why = retiredAttachmentsKeyWhy(key, 'key');
462
+ if (why === undefined)
463
+ continue;
464
+ const d = Object.getOwnPropertyDescriptor(v, key);
465
+ if (d !== undefined && Object.prototype.hasOwnProperty.call(d, 'value') && d.value === undefined) {
466
+ ;
467
+ (drop ??= []).push(key);
468
+ continue;
469
+ }
470
+ throw new AttachmentsSpecRefused(`buildTaskRequest: refusing attachments key ${JSON.stringify(key)} — ${why}`, { token: key, why, allowed: ATTACHMENTS_SPEC_KEYS, key });
471
+ }
472
+ if (drop === undefined)
473
+ return v;
474
+ const copy = { ...v };
475
+ for (const key of drop)
476
+ delete copy[key];
477
+ return copy;
478
+ };
428
479
  const refuseBadExcludeAllTools = (v) => {
429
480
  if (v === undefined || v === null || v === true || v === false)
430
481
  return;
@@ -542,7 +593,7 @@ export function parseMaxCostUsd(text) {
542
593
  return { ok: false, why: 'not_positive' };
543
594
  return { ok: true, value };
544
595
  }
545
- const ACCESSOR_READ_ORDER = ['live', 'memoryCapture', 'memory', 'settings', 'maxCostUsd', 'excludeAllTools', 'maxTokens', 'outputRetries', 'fileHistory', 'inheritEnv', 'requireExistingSession'];
596
+ const ACCESSOR_READ_ORDER = ['live', 'memoryCapture', 'memory', 'settings', 'maxCostUsd', 'excludeAllTools', 'maxTokens', 'outputRetries', 'fileHistory', 'inheritEnv', 'requireExistingSession', 'disableSkills'];
546
597
  function dataFirstSnapshot(input, afterData) {
547
598
  let descs;
548
599
  try {
@@ -612,6 +663,7 @@ export function assembleTaskRequest(input, lane) {
612
663
  const fileHistoryIn = src.fileHistory;
613
664
  const inheritEnvIn = src.inheritEnv;
614
665
  const requireExistingSessionIn = src.requireExistingSession;
666
+ const disableSkillsIn = src.disableSkills;
615
667
  const INPUT_KEYS = Object.keys(INPUT_KEY_ROUTES);
616
668
  const i = snapshotByName(src, INPUT_KEYS, {
617
669
  live,
@@ -624,6 +676,7 @@ export function assembleTaskRequest(input, lane) {
624
676
  fileHistory: fileHistoryIn,
625
677
  inheritEnv: inheritEnvIn,
626
678
  requireExistingSession: requireExistingSessionIn,
679
+ disableSkills: disableSkillsIn,
627
680
  settings: settingsIn,
628
681
  });
629
682
  const SETTINGS_KEYS = Object.keys(SETTINGS_KEY_ROUTES);
@@ -637,6 +690,7 @@ export function assembleTaskRequest(input, lane) {
637
690
  refuseBadFileHistory(fileHistoryIn);
638
691
  refuseBadInheritEnv(inheritEnvIn);
639
692
  refuseBadRequireExistingSession(requireExistingSessionIn);
693
+ refuseBadDisableSkills(disableSkillsIn, 'buildTaskRequest');
640
694
  const rewindRoute = INPUT_KEY_ROUTES.rewind;
641
695
  const RETIRED_REWIND_FILES_KEY = 'rewindFiles';
642
696
  const REWIND_KEYS = rewindRoute.via === 'matrix' ? rewindRoute.field.split('/') : [];
@@ -721,6 +775,7 @@ export function assembleTaskRequest(input, lane) {
721
775
  '错误文本只报键名,不回显值。');
722
776
  }
723
777
  refuseRequireExistingWithoutSession(requireExistingSessionIn, i.sessionId);
778
+ refuseDisableSkillsConflict(disableSkillsIn, i.skills, Reflect.get(src, 'agents'), 'buildTaskRequest');
724
779
  refuseUtilityWithoutToolUnload(lane, excludeAllToolsIn);
725
780
  const carries = (route, value, key) => {
726
781
  if (route.via === 'always')
@@ -754,6 +809,7 @@ export function assembleTaskRequest(input, lane) {
754
809
  ...(onSetting('hooks') ? { hooks: s.hooks } : {}),
755
810
  ...(s.outputStyle !== undefined ? { outputStyle: s.outputStyle } : {}),
756
811
  };
812
+ const attachmentsForWire = on('attachments') ? withoutRetiredAttachmentsKeys(i.attachments) : undefined;
757
813
  const out = {
758
814
  objective: i.objective,
759
815
  sessionId: i.sessionId,
@@ -772,7 +828,7 @@ export function assembleTaskRequest(input, lane) {
772
828
  ...(on('retainBackgroundProcesses') ? { retainBackgroundProcesses: i.retainBackgroundProcesses } : {}),
773
829
  ...(on('promptProfile') ? { promptProfile: i.promptProfile } : {}),
774
830
  ...(on('enableFork') ? { enableFork: i.enableFork } : {}),
775
- ...(on('attachments') ? { attachments: i.attachments } : {}),
831
+ ...(on('attachments') ? { attachments: attachmentsForWire } : {}),
776
832
  ...(on('repo') ? { repo: i.repo } : {}),
777
833
  ...(on('council') ? { council: true } : {}),
778
834
  ...(on('debate') ? { debate: true } : {}),
@@ -796,6 +852,7 @@ export function assembleTaskRequest(input, lane) {
796
852
  ...(on('fileHistory') ? { fileHistory: FILE_HISTORY_DISABLED } : {}),
797
853
  ...(on('inheritEnv') ? { inheritEnv: inheritEnvIn } : {}),
798
854
  ...(on('requireExistingSession') ? { requireExistingSession: true } : {}),
855
+ ...(on('disableSkills') ? { disableSkills: true } : {}),
799
856
  ...(Object.keys(settingsOut).length > 0 ? { settings: settingsOut } : {}),
800
857
  };
801
858
  return Object.freeze({ request: out, omitted: Object.freeze([...omitted, ...lateOmitted]) });
@@ -819,6 +876,7 @@ export function applyLiveRequestDefaults(req, host) {
819
876
  out.retainSubagentSessions = true;
820
877
  if (out.agents === undefined && host.agents !== undefined)
821
878
  out.agents = host.agents;
879
+ refuseDisableSkillsConflict(out.disableSkills, out.skills, out.agents, 'applyLiveRequestDefaults');
822
880
  if (typeof out.scenario === 'string' && host.mockScenarioKeys?.has(out.scenario)) {
823
881
  delete out.scenario;
824
882
  }
@@ -11,6 +11,11 @@ export interface RunTerminalPausedRead extends RunTerminalPause {
11
11
  code?: string;
12
12
  flatStatus?: string;
13
13
  }
14
+ export interface RunTerminalFailureDetail {
15
+ kind: string;
16
+ status?: number;
17
+ code?: string;
18
+ }
14
19
  export type RunTerminalRead = {
15
20
  kind: 'completed';
16
21
  plane: RunTerminalPlane;
@@ -20,6 +25,7 @@ export type RunTerminalRead = {
20
25
  code?: string;
21
26
  message?: string;
22
27
  nestedPause?: RunTerminalPause;
28
+ detail?: RunTerminalFailureDetail;
23
29
  } | {
24
30
  kind: 'blocked';
25
31
  plane: RunTerminalPlane;
@@ -20,6 +20,27 @@ function pauseOf(v) {
20
20
  ...(restoreMode !== undefined ? { restoreMode } : {}),
21
21
  };
22
22
  }
23
+ const PROVIDER_REJECTED_DETAIL_KIND = 'provider_rejected';
24
+ function failureDetailOf(cause) {
25
+ try {
26
+ const own = (o, k) => (Object.prototype.hasOwnProperty.call(o, k) ? o[k] : undefined);
27
+ const v = own(cause, 'detail');
28
+ if (typeof v !== 'object' || v === null || Array.isArray(v))
29
+ return undefined;
30
+ const kind = str(own(v, 'kind'));
31
+ if (kind === undefined)
32
+ return undefined;
33
+ const rawStatus = own(v, 'status');
34
+ const status = typeof rawStatus === 'number' && Number.isInteger(rawStatus) && rawStatus >= 100 && rawStatus <= 599 ? rawStatus : undefined;
35
+ if (kind === PROVIDER_REJECTED_DETAIL_KIND && (status === undefined || status < 400))
36
+ return undefined;
37
+ const code = str(own(v, 'code'));
38
+ return { kind, ...(status !== undefined ? { status } : {}), ...(code !== undefined ? { code } : {}) };
39
+ }
40
+ catch {
41
+ return undefined;
42
+ }
43
+ }
23
44
  const FLAT_PAUSED_STATUSES = ['suspended', 'needs_review'];
24
45
  export function readRunTerminal(result) {
25
46
  if (typeof result !== 'object' || result === null || Array.isArray(result))
@@ -44,12 +65,14 @@ export function readRunTerminal(result) {
44
65
  const code = str(c.code);
45
66
  const message = str(c.message);
46
67
  const nestedPause = c.nestedPause !== undefined ? pauseOf(c.nestedPause) : undefined;
68
+ const detail = failureDetailOf(c);
47
69
  return {
48
70
  kind: 'failed',
49
71
  plane: 'cause',
50
72
  ...(code !== undefined ? { code } : {}),
51
73
  ...(message !== undefined ? { message } : {}),
52
74
  ...(nestedPause !== undefined ? { nestedPause } : {}),
75
+ ...(detail !== undefined ? { detail } : {}),
53
76
  };
54
77
  }
55
78
  case 'blocked': {
@@ -2,5 +2,6 @@ export { RegistryClient } from '@sema-agent/sdk/registry';
2
2
  export { getMeConfig, getScopeConfigDraft, getEffective } from '@sema-agent/sdk/registry';
3
3
  export { probeRegistryHealth, postFeedback } from '@sema-agent/sdk/registry';
4
4
  export { REGISTRY_AUTH_PATHS, skillContentAddress } from '@sema-agent/sdk/registry';
5
+ export { requestDeviceCode, pollUntilApproved, refreshTokens, listScopes, switchScope } from '@sema-agent/sdk/registry';
5
6
  export { RegistryApiError, OAuthFlowError } from '@sema-agent/sdk/registry';
6
7
  export type { RegistryTokens, TokenProvider, } from '@sema-agent/sdk/registry';
@@ -2,4 +2,5 @@ export { RegistryClient } from '@sema-agent/sdk/registry';
2
2
  export { getMeConfig, getScopeConfigDraft, getEffective } from '@sema-agent/sdk/registry';
3
3
  export { probeRegistryHealth, postFeedback } from '@sema-agent/sdk/registry';
4
4
  export { REGISTRY_AUTH_PATHS, skillContentAddress } from '@sema-agent/sdk/registry';
5
+ export { requestDeviceCode, pollUntilApproved, refreshTokens, listScopes, switchScope } from '@sema-agent/sdk/registry';
5
6
  export { RegistryApiError, OAuthFlowError } from '@sema-agent/sdk/registry';
package/dist/seam.d.ts CHANGED
@@ -174,6 +174,9 @@ export interface WiringManifestChromeEvent {
174
174
  readDeny?: WiringManifestReadDenyView;
175
175
  hands?: WiringManifestHandsView;
176
176
  fileHistory?: string;
177
+ instructions?: {
178
+ readonly selected: readonly string[];
179
+ };
177
180
  eventId?: string;
178
181
  }
179
182
  export interface TextSegmentCommittedRow {
@@ -61,6 +61,8 @@ export interface ToolPermissionRequest {
61
61
  mandate?: ApprovalMandateWord;
62
62
  origin?: string;
63
63
  ruleOffersAbsence?: string;
64
+ askClass?: string;
65
+ classes?: readonly string[];
64
66
  }
65
67
  declare const SEAT_EVENT_TYPE_TUPLE: readonly ["start", "close", "session_updated", "message", "stream_event", "chrome_event", "error", "tool_permission_resolved"];
66
68
  export type SeatEventType = (typeof SEAT_EVENT_TYPE_TUPLE)[number];
@@ -261,7 +263,7 @@ export declare const SEAT_METHOD_NAMES: readonly ["start", "sendMessage", "inter
261
263
  export declare const SEAT_STORE_PROPS: readonly ["sessionsStateStore", "focusedSessionStateStore", "settingsStateStore"];
262
264
  export declare const SEAT_EVENT_METHOD_NAMES: readonly ["onEvent", "onToolPermissionRequest"];
263
265
  export declare const LOCAL_SESSION_EVENT_KEYS: readonly ["type", "sessionId", "message", "messages", "error", "errorCategory", "code", "request", "data", "sources", "permissionMode", "session", "userMessageUuid", "hasPreClearSession", "requestId", "outcome", "chrome"];
264
- export declare const TOOL_PERMISSION_REQUEST_KEYS: readonly ["requestId", "sessionId", "toolName", "toolCallId", "input", "message", "toctouBinding", "createdAtMs", "governanceForced", "delegation", "mandated", "mandate", "origin", "ruleOffersAbsence"];
266
+ export declare const TOOL_PERMISSION_REQUEST_KEYS: readonly ["requestId", "sessionId", "toolName", "toolCallId", "input", "message", "toctouBinding", "createdAtMs", "governanceForced", "delegation", "mandated", "mandate", "origin", "ruleOffersAbsence", "askClass", "classes"];
265
267
  export declare const LOCAL_SESSION_RECORD_KEYS: readonly ["sessionId", "engineSessionId", "model", "title", "isRunning", "createdAt", "lastActivityAt", "queuedMessages", "parked"];
266
268
  export declare const SEND_MESSAGE_REQUEST_KEYS: readonly ["sessionId", "text", "model", "userMessageUuid", "permissionMode", "attachmentIds", "reasoningEffort", "retainBackgroundProcesses", "forwardSubagentEvents", "retainSubagentSessions", "settings", "scenario"];
267
269
  export declare const SEAT_SEND_MESSAGE_KEY_ORIGINS: {
@@ -333,6 +335,12 @@ export declare const SEAT_VALIDATOR_KEY_COVERAGE: {
333
335
  }, {
334
336
  readonly key: "origin";
335
337
  readonly why: "喂姿态谓词 askSurvivesPosture 的安全位(CC-203),按开集读(非空串原文字节、词表不校);权威窄读在卡口与过境口 toolPermissionRequestAskBits,座位门不设第二判官(为一个位拒掉整条 request = 把安全 ask 丢给引擎 TTL deny)。坏形的两种失效方向:非串 / 空串读成缺席 ⇒ 宿主报了没命中时姿态谓词可答可以放(放宽方向:真出身若是必问词就丢了);表外串读成不认识的词 ⇒ 答 unknown,宿主退回自己的规则(不放宽)。不经过境口自铸座位请求的宿主要自己按同一把窄读。";
338
+ }, {
339
+ readonly key: "askClass";
340
+ readonly why: "展示位(CC-284),权威成对窄读在卡口与过境口 toolPermissionRequestAskBits(与 classes 同在同缺、首员等于 askClass、不判成员、不重排);座位门不设第二判官,理由同 mandated(为一个展示位拒掉整条 request = 把安全 ask 丢给引擎超时拒)。坏形读成缺席 ⇒ 卡上不显示问类,不改决断;缺席不是「普通问」。不经过境口自铸座位请求的宿主要自己按同一把成对窄读。";
341
+ }, {
342
+ readonly key: "classes";
343
+ readonly why: "与 askClass 同一对(CC-284),同在同缺、同一只成对窄读(非空、每员非空串、无重复、首员等于 askClass;开集、原样顺序);座位门不设第二判官,理由同 askClass。";
336
344
  }, {
337
345
  readonly key: "ruleOffersAbsence";
338
346
  readonly why: "喂强制判据口 approvalIsMandated 的位(CC-203;寄存行那条腿的强制只以缺席词 mandated 表达),按开集读(非空串原文字节、词表不校);权威窄读在卡口与过境口,座位门不设第二判官,理由同 mandated。坏形读成缺席 ⇒ 这一路不判强制(放宽方向:寄存行上的强制就丢了;另两路 mandated / mandate 照判);表外串只是不等于 mandated。不经过境口自铸座位请求的宿主要自己按同一把窄读。";
@@ -478,6 +478,8 @@ export const TOOL_PERMISSION_REQUEST_KEYS = [
478
478
  "mandate",
479
479
  "origin",
480
480
  "ruleOffersAbsence",
481
+ "askClass",
482
+ "classes",
481
483
  ];
482
484
  export const LOCAL_SESSION_RECORD_KEYS = [
483
485
  "sessionId",
@@ -563,6 +565,14 @@ export const SEAT_VALIDATOR_KEY_COVERAGE = {
563
565
  key: "origin",
564
566
  why: "喂姿态谓词 askSurvivesPosture 的安全位(CC-203),按开集读(非空串原文字节、词表不校);权威窄读在卡口与过境口 toolPermissionRequestAskBits,座位门不设第二判官(为一个位拒掉整条 request = 把安全 ask 丢给引擎 TTL deny)。坏形的两种失效方向:非串 / 空串读成缺席 ⇒ 宿主报了没命中时姿态谓词可答可以放(放宽方向:真出身若是必问词就丢了);表外串读成不认识的词 ⇒ 答 unknown,宿主退回自己的规则(不放宽)。不经过境口自铸座位请求的宿主要自己按同一把窄读。",
565
567
  },
568
+ {
569
+ key: "askClass",
570
+ why: "展示位(CC-284),权威成对窄读在卡口与过境口 toolPermissionRequestAskBits(与 classes 同在同缺、首员等于 askClass、不判成员、不重排);座位门不设第二判官,理由同 mandated(为一个展示位拒掉整条 request = 把安全 ask 丢给引擎超时拒)。坏形读成缺席 ⇒ 卡上不显示问类,不改决断;缺席不是「普通问」。不经过境口自铸座位请求的宿主要自己按同一把成对窄读。",
571
+ },
572
+ {
573
+ key: "classes",
574
+ why: "与 askClass 同一对(CC-284),同在同缺、同一只成对窄读(非空、每员非空串、无重复、首员等于 askClass;开集、原样顺序);座位门不设第二判官,理由同 askClass。",
575
+ },
566
576
  {
567
577
  key: "ruleOffersAbsence",
568
578
  why: "喂强制判据口 approvalIsMandated 的位(CC-203;寄存行那条腿的强制只以缺席词 mandated 表达),按开集读(非空串原文字节、词表不校);权威窄读在卡口与过境口,座位门不设第二判官,理由同 mandated。坏形读成缺席 ⇒ 这一路不判强制(放宽方向:寄存行上的强制就丢了;另两路 mandated / mandate 照判);表外串只是不等于 mandated。不经过境口自铸座位请求的宿主要自己按同一把窄读。",
@@ -4,10 +4,17 @@ export type SubagentSteerWireClient = {
4
4
  runs: Pick<AgentClient['runs'], 'steerSubagent'>;
5
5
  };
6
6
  export type SubagentSteerWire = EngineWireInjection<SubagentSteerWireClient>;
7
+ export interface SubagentSteerNotDelivered {
8
+ ok: false;
9
+ reason: 'not-delivered';
10
+ safety: 'ledger_unavailable';
11
+ detail: string;
12
+ }
7
13
  export type SubagentSteerOutcome = {
8
14
  ok: true;
9
15
  receipt: string;
10
- } | {
16
+ messageId?: string;
17
+ } | SubagentSteerNotDelivered | {
11
18
  ok: false;
12
19
  reason: 'no-wire' | 'no-run' | 'not-running' | 'error';
13
20
  detail?: string;
@@ -3,9 +3,21 @@ import { clientHasVerbs, engineWireFor, INJECTED_WIRE_SETTLE_MS, noAnswerDetailF
3
3
  import { engineSessionParamFor } from '../engineSessionParam.js';
4
4
  import { resolveOwnerContext, resolveOwnerRunId } from './engineSubagentResume.js';
5
5
  import { noteBgOwnerAbsence } from './subagentOwnerAbsence.js';
6
+ import { errCodes, steeringRefusedBeforeEngine } from '../controlRouter.js';
6
7
  function usableSteerClient(client) {
7
8
  return clientHasVerbs(client, [['runs', 'steerSubagent']]);
8
9
  }
10
+ function steerReceiptMessageId(body) {
11
+ try {
12
+ if (typeof body !== 'object' || body === null || !Object.hasOwn(body, 'messageId'))
13
+ return undefined;
14
+ const v = body.messageId;
15
+ return typeof v === 'string' && v.length > 0 ? v : undefined;
16
+ }
17
+ catch {
18
+ return undefined;
19
+ }
20
+ }
9
21
  export async function steerEngineSubagent(target, text, childTaskId, opts) {
10
22
  const named = typeof childTaskId === 'string' && childTaskId !== '' ? childTaskId : undefined;
11
23
  const { record: owner, key } = resolveOwnerContext(named);
@@ -36,13 +48,24 @@ export async function steerEngineSubagent(target, text, childTaskId, opts) {
36
48
  const copy = body?.note ?? body?.message;
37
49
  if (typeof copy === 'string' && copy.length > 0)
38
50
  receipt = copy;
39
- return { ok: true, receipt };
51
+ const messageId = steerReceiptMessageId(body);
52
+ return messageId === undefined ? { ok: true, receipt } : { ok: true, receipt, messageId };
40
53
  }
41
54
  catch (e) {
42
- const status = e?.status;
43
- if (typeof status === 'number') {
55
+ let status;
56
+ try {
57
+ status = errCodes(e).status;
58
+ }
59
+ catch {
60
+ status = undefined;
61
+ }
62
+ if (status !== undefined) {
44
63
  const detail = e instanceof Error ? e.message : String(e);
45
64
  hostLog('debug', `engineSubagentSteer: ${status} ${detail.slice(0, 200)}`);
65
+ const refused = typeof e === 'object' && e !== null ? steeringRefusedBeforeEngine(e) : undefined;
66
+ if (refused !== undefined && refused.safety === 'ledger_unavailable') {
67
+ return { ok: false, reason: 'not-delivered', safety: refused.safety, detail: refused.advice };
68
+ }
46
69
  if (status === 409 || status === 404) {
47
70
  return { ok: false, reason: 'not-running', detail };
48
71
  }
@@ -0,0 +1,10 @@
1
+ export type SupportFloorComponent = 'server' | 'core' | 'sdk';
2
+ export interface SupportFloor {
3
+ readonly server: string;
4
+ readonly core: string;
5
+ readonly sdk: string;
6
+ }
7
+ export declare const SUPPORT_FLOOR: Readonly<SupportFloor>;
8
+ export declare function supportFloorOf(component: unknown): string | undefined;
9
+ export type EngineVersionTier = 'engine' | 'legacy' | 'unknown';
10
+ export declare function engineVersionTier(version: unknown, floor?: string): EngineVersionTier;
@@ -0,0 +1,42 @@
1
+ export const SUPPORT_FLOOR = Object.freeze({
2
+ server: '7.104.0',
3
+ core: '7.33.8',
4
+ sdk: '12.0.1',
5
+ });
6
+ export function supportFloorOf(component) {
7
+ if (component === 'server' || component === 'core' || component === 'sdk')
8
+ return SUPPORT_FLOOR[component];
9
+ return undefined;
10
+ }
11
+ function releaseTriple(v) {
12
+ if (typeof v !== 'string')
13
+ return undefined;
14
+ const m = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.exec(v);
15
+ if (m === null)
16
+ return undefined;
17
+ return [Number(m[1]), Number(m[2]), Number(m[3])];
18
+ }
19
+ function compareTriple(a, b) {
20
+ for (let i = 0; i < 3; i++) {
21
+ const x = a[i];
22
+ const y = b[i];
23
+ if (x !== y)
24
+ return x < y ? -1 : 1;
25
+ }
26
+ return 0;
27
+ }
28
+ export function engineVersionTier(version, floor) {
29
+ let need = releaseTriple(SUPPORT_FLOOR.server);
30
+ if (floor !== undefined) {
31
+ const own = releaseTriple(floor);
32
+ if (own === undefined) {
33
+ throw new TypeError(`engineVersionTier: floor 只接受纯 x.y.z 版本串(整串、无前导零、无前后缀)或缺席 —— 收到 typeof=${floor === null ? 'null' : typeof floor}。错误文本只报形状,不回显值。`);
34
+ }
35
+ if (compareTriple(own, need) > 0)
36
+ need = own;
37
+ }
38
+ const got = releaseTriple(version);
39
+ if (got === undefined)
40
+ return 'unknown';
41
+ return compareTriple(got, need) >= 0 ? 'engine' : 'legacy';
42
+ }
@@ -3,3 +3,4 @@ export type TaskRequestReasoningEffort = NonNullable<TaskRequest['reasoningEffor
3
3
  export declare const REASONING_EFFORT_WORDS: readonly ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
4
4
  export type TaskRequestPermissionMode = NonNullable<TaskRequest['permissionMode']>;
5
5
  export declare const PERMISSION_MODE_WORDS: readonly ["default", "plan", "acceptEdits", "bypassPermissions", "auto"];
6
+ export declare function retiredAttachmentsKeyWhy(nameOrToken: string, form?: 'token' | 'key'): string | undefined;
@@ -2,3 +2,17 @@ export const REASONING_EFFORT_WORDS = ['off', 'minimal', 'low', 'medium', 'high'
2
2
  export const PERMISSION_MODE_WORDS = ['default', 'plan', 'acceptEdits', 'bypassPermissions', 'auto'];
3
3
  const _taskRequestWordPins = [true, true, true, true];
4
4
  void _taskRequestWordPins;
5
+ export function retiredAttachmentsKeyWhy(nameOrToken, form = 'token') {
6
+ const lower = nameOrToken.trim().toLowerCase();
7
+ const eq = lower.indexOf('=');
8
+ const lhs = (eq < 0 ? lower : lower.slice(0, eq)).trim();
9
+ const bare = lhs.startsWith('+') || lhs.startsWith('-') ? lhs.slice(1).trim() : lhs;
10
+ if (bare !== 'planmodereminder')
11
+ return undefined;
12
+ const head = '"planModeReminder" was retired with core 7.39.0 / server 7.113.0 — the engine now states the plan posture itself whenever the permission mode is "plan"';
13
+ if (form === 'token')
14
+ return `${head}, and a request carrying the key is answered 400 request.field_invalid; remove the token (send permissionMode "plan" for the plan posture)`;
15
+ return nameOrToken === 'planModeReminder'
16
+ ? `${head}, and a request carrying this key is answered 400 request.field_invalid; remove the key (send permissionMode "plan" for the plan posture)`
17
+ : `${head}; the server ignores this spelling of it, but it is refused here so the retired key is not sent under any spelling; remove the key (send permissionMode "plan" for the plan posture)`;
18
+ }