@sema-agent/client-core 0.99.0 → 0.100.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 (128) hide show
  1. package/CHANGELOG.md +177 -0
  2. package/README.md +32 -19
  3. package/dist/adapt/arms.js +60 -36
  4. package/dist/adapt/panelTasks.d.ts +2 -1
  5. package/dist/adapt/panelTasks.js +32 -9
  6. package/dist/adapt/textStream.d.ts +1 -1
  7. package/dist/adapt/textStream.js +51 -28
  8. package/dist/adapter/activeRunSelfHeal.d.ts +7 -0
  9. package/dist/adapter/activeRunSelfHeal.js +58 -11
  10. package/dist/adapter/downstream/eventToSdkMessage.d.ts +25 -6
  11. package/dist/adapter/downstream/eventToSdkMessage.js +125 -9
  12. package/dist/adapter/downstream/terminalToSdkResult.js +10 -0
  13. package/dist/adapter/downstream/wiringManifestView.d.ts +3 -1
  14. package/dist/adapter/downstream/wiringManifestView.js +1 -0
  15. package/dist/adapter/runStream.js +32 -5
  16. package/dist/adapter/types.d.ts +2 -0
  17. package/dist/agentsWireCaps.d.ts +7 -0
  18. package/dist/agentsWireCaps.js +100 -15
  19. package/dist/cloudConfigWireCaps.d.ts +10 -1
  20. package/dist/cloudConfigWireCaps.js +156 -62
  21. package/dist/compactionStatus.d.ts +34 -0
  22. package/dist/compactionStatus.js +196 -0
  23. package/dist/decideFailureNote.js +3 -3
  24. package/dist/displayUntrusted.js +150 -5
  25. package/dist/engineAgentPanelStore.d.ts +7 -4
  26. package/dist/engineErrorCodes.d.ts +9 -1
  27. package/dist/engineErrorCodes.js +9 -5
  28. package/dist/engineRowRef.js +1 -0
  29. package/dist/engineToolDirectory.d.ts +3 -0
  30. package/dist/engineToolDirectory.js +4 -2
  31. package/dist/eventLeg.d.ts +1 -7
  32. package/dist/eventLeg.js +7 -8
  33. package/dist/fileHistoryCaptureCapability.d.ts +0 -3
  34. package/dist/fileHistoryCaptureCapability.js +0 -4
  35. package/dist/generated/engineFactTables.d.ts +1 -0
  36. package/dist/generated/engineFactTables.js +27 -0
  37. package/dist/generated/engineNoticeTables.js +3 -0
  38. package/dist/generated/settingsSchemaFns.d.ts +12 -0
  39. package/dist/generated/settingsSchemaFns.js +13 -0
  40. package/dist/generated/settingsSchemaMcpRequestEntry.d.ts +1 -0
  41. package/dist/generated/settingsSchemaMcpRequestEntry.js +162 -0
  42. package/dist/generated/settingsSchemaTables.d.ts +1 -0
  43. package/dist/generated/settingsSchemaTables.js +5 -0
  44. package/dist/generated/settingsSchemaTypes.d.ts +28 -0
  45. package/dist/hitl/allowRuleShadow.js +2 -2
  46. package/dist/hitl/approvalCardReasons.js +2 -2
  47. package/dist/hitl/approvalHolds.d.ts +14 -0
  48. package/dist/hitl/approvalHolds.js +41 -0
  49. package/dist/hitl/askClassPair.d.ts +2 -2
  50. package/dist/hitl/askClassPair.js +2 -2
  51. package/dist/hitl/attributionLadder.d.ts +23 -0
  52. package/dist/hitl/attributionLadder.js +67 -0
  53. package/dist/hitl/decisionLedger.d.ts +55 -0
  54. package/dist/hitl/decisionLedger.js +156 -0
  55. package/dist/hitl/decisionRefusal.d.ts +1 -0
  56. package/dist/hitl/decisionRefusal.js +1 -1
  57. package/dist/hitl/decisionSend.d.ts +113 -0
  58. package/dist/hitl/decisionSend.js +579 -0
  59. package/dist/hitl/hitlBridge.js +18 -6
  60. package/dist/hitl/planReviewWire.js +45 -76
  61. package/dist/hitl/sessionPolicyDeliverable.js +3 -6
  62. package/dist/hitl/toolApprovalWire.js +43 -109
  63. package/dist/hostTurnSettings.d.ts +46 -7
  64. package/dist/hostTurnSettings.js +408 -7
  65. package/dist/humanTurn.d.ts +2 -1
  66. package/dist/humanTurn.js +3 -3
  67. package/dist/i18n/messages.en.d.ts +131 -0
  68. package/dist/i18n/messages.en.js +143 -12
  69. package/dist/i18n/messages.zh.js +148 -12
  70. package/dist/index.d.ts +18 -2
  71. package/dist/index.js +10 -1
  72. package/dist/inheritEnvCapability.d.ts +1 -0
  73. package/dist/inheritEnvCapability.js +9 -2
  74. package/dist/inheritEnvWire.js +2 -2
  75. package/dist/injectionLaneNotice.d.ts +5 -0
  76. package/dist/injectionLaneNotice.js +46 -0
  77. package/dist/mcpPanel.js +2 -0
  78. package/dist/mcpReconnect.js +2 -2
  79. package/dist/mcpToolToggles.d.ts +6 -0
  80. package/dist/mcpToolToggles.js +46 -0
  81. package/dist/mcpWireCaps.d.ts +10 -4
  82. package/dist/mcpWireCaps.js +106 -71
  83. package/dist/mcpWireTransport.d.ts +4 -0
  84. package/dist/mcpWireTransport.js +18 -0
  85. package/dist/modelCapabilityProbe.d.ts +2 -1
  86. package/dist/modelCapabilityProbe.js +10 -9
  87. package/dist/modelIdentityResolvability.d.ts +2 -0
  88. package/dist/modelIdentityResolvability.js +23 -5
  89. package/dist/ownKey.d.ts +26 -0
  90. package/dist/ownKey.js +75 -0
  91. package/dist/panelReduce.d.ts +5 -0
  92. package/dist/panelReduce.js +45 -13
  93. package/dist/panelStateHolder.d.ts +1 -0
  94. package/dist/panelStateHolder.js +10 -1
  95. package/dist/permissionRuleIssue.d.ts +2 -1
  96. package/dist/permissionRuleIssue.js +22 -21
  97. package/dist/pluginHooksWire.js +12 -10
  98. package/dist/prepareRefusal.d.ts +8 -0
  99. package/dist/prepareRefusal.js +72 -0
  100. package/dist/request/taskRequest.d.ts +10 -10
  101. package/dist/request/taskRequest.js +106 -65
  102. package/dist/resumeRefusalCopy.d.ts +3 -1
  103. package/dist/resumeRefusalCopy.js +22 -4
  104. package/dist/retryStatus.d.ts +18 -0
  105. package/dist/retryStatus.js +76 -19
  106. package/dist/ruleRefusalSentence.d.ts +4 -0
  107. package/dist/ruleRefusalSentence.js +58 -0
  108. package/dist/sandboxWire.d.ts +2 -1
  109. package/dist/sandboxWire.js +55 -21
  110. package/dist/scenarioRefusal.d.ts +2 -1
  111. package/dist/scenarioRefusal.js +5 -5
  112. package/dist/seam.d.ts +24 -9
  113. package/dist/seam.js +44 -4
  114. package/dist/seatContract.d.ts +16 -8
  115. package/dist/seatContract.js +21 -6
  116. package/dist/settingsCredentialKeyPaths.d.ts +8 -0
  117. package/dist/settingsCredentialKeyPaths.js +219 -0
  118. package/dist/skillsWireCaps.d.ts +0 -3
  119. package/dist/skillsWireCaps.js +1 -2
  120. package/dist/subagentContentStore.d.ts +1 -0
  121. package/dist/subagentContentStore.js +135 -27
  122. package/dist/toolsFlagNames.d.ts +8 -0
  123. package/dist/toolsFlagNames.js +110 -0
  124. package/dist/turnFailurePresentation.js +15 -7
  125. package/dist/wireRefusalCopy.d.ts +51 -17
  126. package/dist/wireRefusalCopy.js +298 -135
  127. package/docs/INTEGRATION-CLIENTS.md +423 -26
  128. package/package.json +4 -4
@@ -0,0 +1,46 @@
1
+ import { checkMcpToolToggleKey } from './seatContract.js';
2
+ function keyOf(key, index) {
3
+ const refuse = (why) => new TypeError(`mcpToolTogglesToExcludeTools: keys[${index}] 不是逐工具开关键 { server, tool }(${why})—— 值不回显。` +
4
+ '宿主存档读坏了请在读档处用 checkMcpToolToggleKey 判,不要交进来让它在每一回合被静默丢掉。');
5
+ const shape = checkMcpToolToggleKey(key);
6
+ if (shape !== true)
7
+ throw refuse(`${shape.reason}${shape.field !== undefined ? ` @ ${shape.field}` : ''}`);
8
+ let snap;
9
+ try {
10
+ snap = { server: Object.getOwnPropertyDescriptor(key, 'server')?.value, tool: Object.getOwnPropertyDescriptor(key, 'tool')?.value };
11
+ }
12
+ catch {
13
+ throw refuse('读不出');
14
+ }
15
+ const again = checkMcpToolToggleKey(snap);
16
+ if (again !== true)
17
+ throw refuse(`${again.reason}${again.field !== undefined ? ` @ ${again.field}` : ''};判形之后读到的值与判过的不一致`);
18
+ return Object.freeze({ server: snap.server, tool: snap.tool });
19
+ }
20
+ function mountedSetOf(mountedServers) {
21
+ if (mountedServers === 'unknown')
22
+ return 'unknown';
23
+ if (!Array.isArray(mountedServers) || !mountedServers.every((s) => typeof s === 'string')) {
24
+ throw new TypeError("mcpToolTogglesToExcludeTools: mountedServers 只接受服务器名(串)数组,或宿主判不出时交 'unknown' —— 收到别的形(值不回显)。" +
25
+ "不知道本回合挂了哪些时请交 'unknown'(全部出),不要交 [](那会把用户关掉的工具静默放回)。");
26
+ }
27
+ return new Set(mountedServers);
28
+ }
29
+ export function mcpToolTogglesToExcludeTools(keys, mountedServers) {
30
+ if (!Array.isArray(keys)) {
31
+ throw new TypeError('mcpToolTogglesToExcludeTools: keys 必须是逐工具开关键数组(值不回显)。');
32
+ }
33
+ const mounted = mountedSetOf(mountedServers);
34
+ const parts = keys.map((k, i) => keyOf(k, i));
35
+ const out = [];
36
+ const seen = new Set();
37
+ for (const { server, tool } of parts) {
38
+ if (mounted !== 'unknown' && !mounted.has(server))
39
+ continue;
40
+ if (seen.has(tool))
41
+ continue;
42
+ seen.add(tool);
43
+ out.push(tool);
44
+ }
45
+ return out;
46
+ }
@@ -1,9 +1,7 @@
1
1
  import type { McpServerSpec } from '@sema-agent/sdk';
2
+ import type { McpEntryKeyIssue } from './generated/settingsSchemaTypes.js';
2
3
  import type { UiLanguage } from './env/uiLanguage.js';
3
4
  export type { McpServerSpec };
4
- export declare const MCP_CAPS: {
5
- readonly items: 32;
6
- };
7
5
  export interface McpConfigLike {
8
6
  type?: string;
9
7
  command?: string;
@@ -17,6 +15,7 @@ export interface McpConfigLike {
17
15
  elicitation?: {} | null | undefined;
18
16
  toolFaces?: unknown;
19
17
  toolAxes?: unknown;
18
+ startupTimeoutMs?: number;
20
19
  }
21
20
  export type McpHttpIdentityHeaders = {
22
21
  readonly kind: 'ok';
@@ -44,16 +43,23 @@ export declare function mcpElicitationVerdict(declared: {} | null | undefined):
44
43
  } | {
45
44
  readonly kind: 'bad';
46
45
  };
47
- export type McpConfigDropReason = 'name_blank' | 'name_edge_whitespace' | 'name_collides_after_trim' | 'tool_axes_invalid' | 'elicitation_invalid';
46
+ export type McpConfigDropReason = 'name_blank' | 'name_edge_whitespace' | 'name_collides_after_trim' | 'name_too_long' | 'transport_invalid' | 'identity_headers_both_set' | 'tool_axes_invalid' | 'elicitation_invalid';
48
47
  export interface McpConfigDropped {
49
48
  readonly name: string;
50
49
  readonly reason: McpConfigDropReason;
51
50
  readonly problem?: string;
51
+ readonly path?: string;
52
+ readonly notice: string;
53
+ }
54
+ export interface McpConfigDegraded {
55
+ readonly name: string;
56
+ readonly lost: readonly McpEntryKeyIssue[];
52
57
  readonly notice: string;
53
58
  }
54
59
  export interface McpConfigProjection {
55
60
  readonly specs: McpServerSpec[];
56
61
  readonly dropped: readonly McpConfigDropped[];
62
+ readonly degraded: readonly McpConfigDegraded[];
57
63
  }
58
64
  export interface ProjectMcpConfigsOptions {
59
65
  readonly sourceLabel?: (serverName: string) => string | undefined;
@@ -1,37 +1,32 @@
1
+ import { SETTINGS_DESCRIBE_BAD_WIRE_NAME, SETTINGS_IS_USABLE_WIRE_NAME, SETTINGS_READ_MCP_IDENTITY_HEADERS, SETTINGS_READ_REQUEST_MCP_TRANSPORT, } from './generated/settingsSchemaFns.js';
2
+ import { snapshotArgs, wireTransportOf } from './mcpWireTransport.js';
1
3
  import { guardedRead } from './ownKey.js';
2
4
  import { readMcpToolAxes } from './coreValuePorts.js';
3
5
  import { capForDisplay } from './fleetTaskDesc.js';
4
6
  import { msg, renderMessage } from './i18n/translate.js';
5
7
  const _mcpServerSpecPin = true;
6
8
  void _mcpServerSpecPin;
7
- export const MCP_CAPS = { items: 32 };
8
9
  function transparentKeys(c, axes, elicitation) {
9
10
  return {
10
11
  ...(c.toolFaces !== undefined ? { toolFaces: c.toolFaces } : {}),
11
12
  ...(axes !== undefined ? { toolAxes: axes } : {}),
12
13
  ...(elicitation !== undefined ? { elicitation } : {}),
14
+ ...(c.startupTimeoutMs !== undefined ? { startupTimeoutMs: c.startupTimeoutMs } : {}),
13
15
  };
14
16
  }
15
- function presentHeaderName(v) {
16
- return typeof v === 'string' && v.length > 0 ? v : undefined;
17
- }
18
17
  export function readMcpHttpIdentityHeaders(transport) {
19
18
  const t = transport;
20
- const principalHeader = presentHeaderName(t.principalHeader);
21
- const assertionHeader = presentHeaderName(t.assertionHeader);
19
+ const principalHeader = t.principalHeader;
20
+ const assertionHeader = t.assertionHeader;
22
21
  if (principalHeader !== undefined && assertionHeader !== undefined)
23
22
  return { kind: 'both_set' };
24
- return {
25
- kind: 'ok',
26
- ...(principalHeader !== undefined ? { principalHeader } : {}),
27
- ...(assertionHeader !== undefined ? { assertionHeader } : {}),
28
- };
23
+ return { kind: 'ok', ...SETTINGS_READ_MCP_IDENTITY_HEADERS({ principalHeader, assertionHeader }) };
29
24
  }
30
25
  export function mcpConfigToSpec(name, c) {
31
26
  return mcpConfigEntry(name, c).spec;
32
27
  }
33
28
  function mcpConfigEntry(name, c) {
34
- if (mcpNameFlaw(name) !== undefined)
29
+ if (!nameUsable(name))
35
30
  return { spec: null };
36
31
  const declared = c.toolAxes;
37
32
  const verdict = mcpToolAxesVerdict(declared);
@@ -39,49 +34,62 @@ function mcpConfigEntry(name, c) {
39
34
  const elVerdict = mcpElicitationVerdict(elDeclared);
40
35
  if (verdict.kind === 'bad' || elVerdict.kind === 'bad') {
41
36
  const rest = specOf(name, c, undefined, undefined);
42
- if (rest === null)
37
+ if (rest.kind === 'skip')
43
38
  return { spec: null };
39
+ if (rest.kind === 'both_set')
40
+ return { spec: null, identityBothSet: true };
41
+ if (rest.kind === 'bad')
42
+ return { spec: null, transportBad: rest.issue };
44
43
  return verdict.kind === 'bad' ? { spec: null, axesProblem: verdict.problem } : { spec: null, elicitationInvalid: true };
45
44
  }
46
- return { spec: specOf(name, c, verdict.kind === 'ok' ? verdict.toolAxes : declared, elVerdict.elicitation) };
45
+ const read = specOf(name, c, verdict.kind === 'ok' ? verdict.toolAxes : declared, elVerdict.elicitation);
46
+ if (read.kind === 'skip')
47
+ return { spec: null };
48
+ if (read.kind === 'both_set')
49
+ return { spec: null, identityBothSet: true };
50
+ if (read.kind === 'bad')
51
+ return { spec: null, transportBad: read.issue };
52
+ return { spec: read.spec, ...(read.lost.length > 0 ? { transportLost: read.lost } : {}) };
47
53
  }
48
- function specOf(n, c, axes, elicitation) {
49
- const kind = c.type ?? 'stdio';
50
- if (kind === 'stdio') {
51
- if (!c.command)
52
- return null;
53
- return {
54
- name: n,
55
- transport: {
56
- kind: 'stdio',
57
- command: c.command,
58
- ...(c.args && c.args.length > 0 ? { args: c.args } : {}),
59
- ...(c.env ? { env: c.env } : {}),
60
- },
61
- ...(Array.isArray(c.allowTools) ? { allowTools: c.allowTools } : {}),
62
- ...transparentKeys(c, axes, elicitation),
63
- };
54
+ function specOf(name, c, axes, elicitation) {
55
+ let candidate;
56
+ try {
57
+ const kind = c.type ?? 'stdio';
58
+ if (kind === 'stdio') {
59
+ const command = c.command;
60
+ const head = SETTINGS_READ_REQUEST_MCP_TRANSPORT({ kind: 'stdio', command });
61
+ if (!head.ok)
62
+ return { kind: 'bad', issue: head.issue };
63
+ candidate = { kind: 'stdio', command, args: snapshotArgs(c.args), env: c.env };
64
+ }
65
+ else if (kind === 'http') {
66
+ const url = c.url;
67
+ const head = SETTINGS_READ_REQUEST_MCP_TRANSPORT({ kind: 'http', url });
68
+ if (!head.ok)
69
+ return { kind: 'bad', issue: head.issue };
70
+ const principalHeader = c.principalHeader;
71
+ const assertionHeader = c.assertionHeader;
72
+ if (readMcpHttpIdentityHeaders({ principalHeader, assertionHeader }).kind === 'both_set')
73
+ return { kind: 'both_set' };
74
+ candidate = { kind: 'http', url, headers: c.headers, principalHeader, assertionHeader };
75
+ }
76
+ else {
77
+ return { kind: 'skip' };
78
+ }
64
79
  }
65
- if (kind === 'http') {
66
- if (!c.url)
67
- return null;
68
- const id = readMcpHttpIdentityHeaders(c);
69
- if (id.kind === 'both_set')
70
- return null;
71
- return {
72
- name: n,
73
- transport: {
74
- kind: 'http',
75
- url: c.url,
76
- ...(c.headers ? { headers: c.headers } : {}),
77
- ...(id.principalHeader !== undefined ? { principalHeader: id.principalHeader } : {}),
78
- ...(id.assertionHeader !== undefined ? { assertionHeader: id.assertionHeader } : {}),
79
- },
80
- ...(Array.isArray(c.allowTools) ? { allowTools: c.allowTools } : {}),
81
- ...transparentKeys(c, axes, elicitation),
82
- };
80
+ catch {
81
+ return { kind: 'bad', issue: { path: 'transport', reason: renderMessage(msg('config.mcp.transportUnreadable')) } };
83
82
  }
84
- return null;
83
+ const read = SETTINGS_READ_REQUEST_MCP_TRANSPORT(candidate);
84
+ if (!read.ok)
85
+ return { kind: 'bad', issue: read.issue };
86
+ const spec = {
87
+ name,
88
+ transport: wireTransportOf(read.transport),
89
+ ...(Array.isArray(c.allowTools) ? { allowTools: c.allowTools } : {}),
90
+ ...transparentKeys(c, axes, elicitation),
91
+ };
92
+ return { kind: 'ok', spec, lost: read.lost };
85
93
  }
86
94
  export function mcpConfigsToSpecs(servers) {
87
95
  return projectMcpConfigs(servers).specs;
@@ -107,18 +115,18 @@ export function mcpElicitationVerdict(declared) {
107
115
  return Object.freeze({ kind: 'ok' });
108
116
  return typeof declared === 'boolean' ? Object.freeze({ kind: 'ok', elicitation: declared }) : Object.freeze({ kind: 'bad' });
109
117
  }
110
- function mcpNameFlaw(name) {
111
- if (typeof name !== 'string')
112
- return 'name_blank';
113
- const t = name.trim();
114
- if (t.length === 0)
118
+ const nameUsable = (name) => SETTINGS_IS_USABLE_WIRE_NAME(name);
119
+ function mcpNameDropReasonOf(name) {
120
+ if (typeof name !== 'string' || name.trim().length === 0)
115
121
  return 'name_blank';
116
- return t === name ? undefined : 'name_edge_whitespace';
122
+ return name.trim() !== name ? 'name_edge_whitespace' : 'name_too_long';
117
123
  }
118
124
  const DROP_NAME_SHOWN = 80;
119
125
  const DROP_LABEL_SHOWN = 140;
120
126
  const DROP_PROBLEM_SHOWN = 300;
121
- function dropNotice(name, reason, problem, opts) {
127
+ const DROP_BAD_NAME_SHOWN = 160;
128
+ const DEGRADED_LOST_SHOWN = 400;
129
+ function dropNotice(name, reason, problem, opts, path) {
122
130
  let label;
123
131
  try {
124
132
  label = opts?.sourceLabel?.(name);
@@ -128,35 +136,53 @@ function dropNotice(name, reason, problem, opts) {
128
136
  }
129
137
  const where = typeof label === 'string' && label.length > 0 ? ` (${capForDisplay(label, DROP_LABEL_SHOWN)})` : '';
130
138
  const quoted = `"${capForDisplay(name, DROP_NAME_SHOWN)}"`;
139
+ const badName = () => capForDisplay(SETTINGS_DESCRIBE_BAD_WIRE_NAME(name), DROP_BAD_NAME_SHOWN);
131
140
  const ref = reason === 'tool_axes_invalid'
132
141
  ? msg('config.mcp.toolAxesInvalid', { name: quoted, where, problem: capForDisplay(problem ?? renderMessage(msg('config.mcp.toolAxesFallbackProblem')), DROP_PROBLEM_SHOWN) })
133
142
  : reason === 'elicitation_invalid'
134
143
  ? msg('config.mcp.elicitationInvalid', { name: quoted, where })
135
- : reason === 'name_blank'
136
- ? msg('config.mcp.nameBlank', { name: quoted, where })
137
- : reason === 'name_collides_after_trim'
138
- ? msg('config.mcp.nameCollides', { name: quoted, where })
139
- : msg('config.mcp.nameEdgeWhitespace', { name: quoted, where });
144
+ : reason === 'identity_headers_both_set'
145
+ ? msg('config.mcp.identityHeadersBothSet', { name: quoted, where })
146
+ : reason === 'transport_invalid'
147
+ ? msg('config.mcp.transportInvalid', { name: quoted, where, path: capForDisplay(path ?? 'transport', DROP_NAME_SHOWN), problem: capForDisplay(problem ?? '', DROP_PROBLEM_SHOWN) })
148
+ : reason === 'name_blank'
149
+ ? msg('config.mcp.nameBlank', { name: badName(), where })
150
+ : reason === 'name_collides_after_trim'
151
+ ? msg('config.mcp.nameCollides', { name: badName(), where })
152
+ : reason === 'name_too_long'
153
+ ? msg('config.mcp.nameTooLong', { name: badName(), where })
154
+ : msg('config.mcp.nameEdgeWhitespace', { name: badName(), where });
140
155
  return renderMessage(ref, opts?.lang);
141
156
  }
157
+ function degradedNotice(name, lost, opts) {
158
+ let label;
159
+ try {
160
+ label = opts?.sourceLabel?.(name);
161
+ }
162
+ catch {
163
+ label = undefined;
164
+ }
165
+ const where = typeof label === 'string' && label.length > 0 ? ` (${capForDisplay(label, DROP_LABEL_SHOWN)})` : '';
166
+ const quoted = `"${capForDisplay(name, DROP_NAME_SHOWN)}"`;
167
+ const what = capForDisplay(lost.map((l) => `${l.path}: ${l.reason}`).join('; '), DEGRADED_LOST_SHOWN);
168
+ return renderMessage(msg('config.mcp.transportDegraded', { name: quoted, where, lost: what }), opts?.lang);
169
+ }
142
170
  export function projectMcpConfigs(servers, opts) {
143
171
  const specs = [];
144
172
  const dropped = [];
173
+ const degraded = [];
145
174
  const entries = Object.entries(servers ?? {});
146
175
  const exactNames = new Set();
147
176
  const trimmedCount = new Map();
148
177
  for (const [name] of entries) {
149
- const flaw = mcpNameFlaw(name);
150
- if (flaw === undefined)
178
+ if (nameUsable(name))
151
179
  exactNames.add(name);
152
- else if (flaw === 'name_edge_whitespace')
180
+ else if (mcpNameDropReasonOf(name) === 'name_edge_whitespace')
153
181
  trimmedCount.set(name.trim(), (trimmedCount.get(name.trim()) ?? 0) + 1);
154
182
  }
155
183
  for (const [name, c] of entries) {
156
- if (specs.length >= MCP_CAPS.items)
157
- break;
158
- const flaw = mcpNameFlaw(name);
159
- if (flaw !== undefined) {
184
+ if (!nameUsable(name)) {
185
+ const flaw = mcpNameDropReasonOf(name);
160
186
  const reason = flaw === 'name_edge_whitespace' && (exactNames.has(name.trim()) || (trimmedCount.get(name.trim()) ?? 0) > 1) ? 'name_collides_after_trim' : flaw;
161
187
  dropped.push({ name, reason, notice: dropNotice(name, reason, undefined, opts) });
162
188
  continue;
@@ -164,16 +190,25 @@ export function projectMcpConfigs(servers, opts) {
164
190
  const out = mcpConfigEntry(name, c);
165
191
  if (out.spec !== null) {
166
192
  specs.push(out.spec);
193
+ if (out.transportLost !== undefined)
194
+ degraded.push({ name, lost: Object.freeze([...out.transportLost]), notice: degradedNotice(name, out.transportLost, opts) });
167
195
  continue;
168
196
  }
169
- if (out.axesProblem !== undefined) {
197
+ if (out.identityBothSet === true) {
198
+ dropped.push({ name, reason: 'identity_headers_both_set', notice: dropNotice(name, 'identity_headers_both_set', undefined, opts) });
199
+ }
200
+ else if (out.transportBad !== undefined) {
201
+ const { path, reason: problem } = out.transportBad;
202
+ dropped.push({ name, reason: 'transport_invalid', problem, path, notice: dropNotice(name, 'transport_invalid', problem, opts, path) });
203
+ }
204
+ else if (out.axesProblem !== undefined) {
170
205
  dropped.push({ name, reason: 'tool_axes_invalid', problem: out.axesProblem, notice: dropNotice(name, 'tool_axes_invalid', out.axesProblem, opts) });
171
206
  }
172
207
  else if (out.elicitationInvalid === true) {
173
208
  dropped.push({ name, reason: 'elicitation_invalid', notice: dropNotice(name, 'elicitation_invalid', undefined, opts) });
174
209
  }
175
210
  }
176
- return { specs, dropped: Object.freeze(dropped) };
211
+ return { specs, dropped: Object.freeze(dropped), degraded: Object.freeze(degraded) };
177
212
  }
178
213
  function assertionDeclaringServerName(spec) {
179
214
  const t = spec?.transport;
@@ -197,6 +232,6 @@ export function mcpAssertionHeaderNotSentNotice(specs) {
197
232
  const parts = distinct.slice(0, ASSERTION_NOTICE_NAMES_SHOWN).map((x) => `"${x}"`);
198
233
  if (distinct.length > ASSERTION_NOTICE_NAMES_SHOWN)
199
234
  parts.push(`+${distinct.length - ASSERTION_NOTICE_NAMES_SHOWN} more`);
200
- return (`MCP servers that declare an assertion header (assertionHeader): ${parts.join(', ')} — server versions up to 7.131.0 accept this setting but do not send the header, ` +
235
+ return (`MCP servers that declare an assertion header (assertionHeader): ${parts.join(', ')} — server versions up to 7.132.0 accept this setting but do not send the header, ` +
201
236
  'and this client cannot tell whether the engine it uses sends it; a connector that requires the header will refuse requests from these servers');
202
237
  }
@@ -0,0 +1,4 @@
1
+ import type { McpServerSpec } from '@sema-agent/sdk';
2
+ import type { RequestMcpTransport } from './generated/settingsSchemaTypes.js';
3
+ export declare function snapshotArgs(v: {} | null | undefined): {} | null | undefined;
4
+ export declare function wireTransportOf(t: RequestMcpTransport): McpServerSpec['transport'];
@@ -0,0 +1,18 @@
1
+ const ARGS_SNAPSHOT_MAX = 65_536;
2
+ export function snapshotArgs(v) {
3
+ if (!Array.isArray(v))
4
+ return v;
5
+ const n = v.length;
6
+ if (typeof n !== 'number' || !Number.isSafeInteger(n) || n < 0 || n > ARGS_SNAPSHOT_MAX)
7
+ return {};
8
+ const out = [];
9
+ for (let i = 0; i < n; i++)
10
+ out.push(v[i]);
11
+ return out;
12
+ }
13
+ export function wireTransportOf(t) {
14
+ if (t.kind === 'stdio') {
15
+ return { kind: 'stdio', command: t.command, ...(t.args !== undefined && t.args.length > 0 ? { args: t.args } : {}), ...(t.env !== undefined ? { env: t.env } : {}) };
16
+ }
17
+ return t;
18
+ }
@@ -1,4 +1,5 @@
1
1
  import type { ModelApiWord } from './model/providerPresets.js';
2
+ import type { UiLanguage } from './env/uiLanguage.js';
2
3
  export interface OpenAICompletionsProbeBody {
3
4
  model: string;
4
5
  messages: ReadonlyArray<{
@@ -129,5 +130,5 @@ export interface ProbeVerdictFacts {
129
130
  }
130
131
  export declare function probeVerdictNotice(facts: ProbeVerdictFacts | null | undefined, ctx?: {
131
132
  readonly modelName?: string;
132
- }): ProbeVerdictNotice;
133
+ }, lang?: UiLanguage): ProbeVerdictNotice;
133
134
  export {};
@@ -2,6 +2,7 @@ import { displayUntrusted } from './displayUntrusted.js';
2
2
  import { abortableSleep } from './abortableSleep.js';
3
3
  import { readThrownSlot } from './wireFailureShape.js';
4
4
  import { guardedRead, readOwnDataValue } from './ownKey.js';
5
+ import { msg, renderMessage } from './i18n/translate.js';
5
6
  export const THINKING_FORMATS = Object.freeze([
6
7
  'openai',
7
8
  'openrouter',
@@ -698,7 +699,7 @@ function probeSeconds(ms) {
698
699
  return `${Number.isInteger(sec) ? sec : sec.toFixed(1)}s`;
699
700
  }
700
701
  const readFacts = (o, key) => guardedRead(() => readOwnDataValue(o, key));
701
- export function probeVerdictNotice(facts, ctx) {
702
+ export function probeVerdictNotice(facts, ctx, lang) {
702
703
  const rawWord = readFacts(facts, 'verdict');
703
704
  const word = typeof rawWord === 'string' && PROBE_VERDICTS.includes(rawWord) ? rawWord : 'other';
704
705
  const rawStatus = readFacts(facts, 'httpStatus');
@@ -713,31 +714,31 @@ export function probeVerdictNotice(facts, ctx) {
713
714
  let recourse;
714
715
  switch (word) {
715
716
  case 'ok':
716
- line = `The model answered${waited === undefined ? '' : ` — first token after ${probeSeconds(waited)}`}`;
717
+ line = renderMessage(waited === undefined ? msg('model.probe.verdict.ok') : msg('model.probe.verdict.okWaited', { time: probeSeconds(waited) }), lang);
717
718
  break;
718
719
  case 'auth':
719
- line = `The model service rejected the API key${http}`;
720
+ line = renderMessage(msg('model.probe.verdict.auth', { http }), lang);
720
721
  recourse = 'check_credentials';
721
722
  break;
722
723
  case 'unreachable':
723
- line = `Could not reach the model service${waited === undefined ? '' : ` — no response within ${probeSeconds(waited)}`}`;
724
+ line = renderMessage(waited === undefined ? msg('model.probe.verdict.unreachable') : msg('model.probe.verdict.unreachableWaited', { time: probeSeconds(waited) }), lang);
724
725
  break;
725
726
  case 'model':
726
- line = `The model service does not recognise this model id${http}`;
727
+ line = renderMessage(msg('model.probe.verdict.model', { http }), lang);
727
728
  break;
728
729
  case 'shape':
729
- line = `This address does not offer the expected interface${http} — check the API shape`;
730
+ line = renderMessage(msg('model.probe.verdict.shape', { http }), lang);
730
731
  break;
731
732
  case 'busy':
732
- line = `The model service is busy right now${http} — retry later or switch model`;
733
+ line = renderMessage(msg('model.probe.verdict.busy', { http }), lang);
733
734
  recourse = 'switch_model';
734
735
  break;
735
736
  case 'no_token':
736
- line = `The model service answered but sent no token${waited === undefined ? '' : ` within ${probeSeconds(waited)}`} — it may be overloaded`;
737
+ line = renderMessage(waited === undefined ? msg('model.probe.verdict.noToken') : msg('model.probe.verdict.noTokenWaited', { time: probeSeconds(waited) }), lang);
737
738
  recourse = 'switch_model';
738
739
  break;
739
740
  case 'other':
740
- line = `The model service gave an unexpected answer${http}`;
741
+ line = renderMessage(msg('model.probe.verdict.other', { http }), lang);
741
742
  break;
742
743
  }
743
744
  return Object.freeze(recourse === undefined ? { line: `${prefix}${line}` } : { line: `${prefix}${line}`, recourse });
@@ -12,11 +12,13 @@ export type ModelIdentityLanes = {
12
12
  [L in ModelIdentityLane]: ModelIdentityLaneReading;
13
13
  };
14
14
  export type EngineAnswerObservation = 'answered' | 'not_observed' | 'unreachable';
15
+ export type EngineCatalogAdoptionReading = 'absent' | 'refused' | 'undetermined';
15
16
  export interface LocalEngineModelIdentityReading {
16
17
  engine: 'local';
17
18
  lanes: ModelIdentityLanes;
18
19
  engineAnswer?: EngineAnswerObservation;
19
20
  engineAcceptsCatalogDefault?: boolean;
21
+ catalogAdoption?: EngineCatalogAdoptionReading;
20
22
  routeGap?: ModelRouteGapReading;
21
23
  }
22
24
  export type ModelRouteGapReading = 'endpoint_missing' | 'lane_credential_missing' | 'none';
@@ -31,6 +31,10 @@ function routeGapOf(reading) {
31
31
  const v = own(reading, 'routeGap')?.v;
32
32
  return v === 'endpoint_missing' || v === 'lane_credential_missing' || v === 'none' ? v : undefined;
33
33
  }
34
+ function catalogAdoptionOf(reading) {
35
+ const v = own(reading, 'catalogAdoption')?.v;
36
+ return v === 'absent' || v === 'refused' || v === 'undetermined' ? v : undefined;
37
+ }
34
38
  function analyse(reading) {
35
39
  try {
36
40
  if (!isPlainObject(reading))
@@ -47,25 +51,30 @@ function analyse(reading) {
47
51
  const lanesHit = own(reading, 'lanes')?.v;
48
52
  const order = laneOrder();
49
53
  if (!isPlainObject(lanesHit)) {
50
- return { verdict: 'unknown', why: 'lanes', unreadable: [], unreported: [...order], unusable: [], catalogUnconfirmed: null, catalogRefused: false };
54
+ return { verdict: 'unknown', why: 'lanes', unreadable: [], unreported: [...order], unusable: [], catalogUnconfirmed: null, catalogRefused: null };
51
55
  }
52
56
  const partial = [];
53
57
  const unreadable = [];
54
58
  const unreported = [];
55
59
  const unusable = [];
56
- let catalogRefused = false;
60
+ let catalogRefused = null;
57
61
  let catalogUnconfirmed = null;
58
62
  for (const lane of order) {
59
63
  const s = laneState(lanesHit, lane);
60
64
  if (lane === 'modelCatalogFile') {
61
65
  if (s === 'complete') {
66
+ if (catalogAdoptionOf(reading) === 'refused') {
67
+ partial.push(lane);
68
+ catalogRefused = 'adoption';
69
+ continue;
70
+ }
62
71
  const bit = own(reading, 'engineAcceptsCatalogDefault');
63
72
  const evidence = catalogDefaultEvidence(s, bit?.v);
64
73
  if (evidence !== undefined)
65
74
  return { verdict: 'resolvable', why: 'catalog_default', evidence };
66
75
  if (bit !== undefined && bit.v === false) {
67
76
  partial.push(lane);
68
- catalogRefused = true;
77
+ catalogRefused = 'engine';
69
78
  }
70
79
  else {
71
80
  catalogUnconfirmed = bit === undefined || bit.v === undefined ? 'not_reported' : 'unusable';
@@ -151,7 +160,13 @@ export function modelIdentityDetail(reading) {
151
160
  const partialClause = others.length > 0
152
161
  ? `${joinPhrases(others.map(laneLabel))} ${others.length > 1 ? 'carry' : 'carries'} part of a model configuration (such as a gateway address or a credential) but no model name`
153
162
  : '';
154
- if (a.catalogRefused) {
163
+ if (a.catalogRefused === 'adoption') {
164
+ const clause = msg('model.identity.catalogAdoption.refused', { lane: msg(LANE_LABEL_KEYS.modelCatalogFile) });
165
+ return partialClause !== ''
166
+ ? renderMessage(msg('model.identity.catalogAdoption.notResolvableWithPartial', { clause, partial: partialClause }))
167
+ : renderMessage(msg('model.identity.catalogAdoption.notResolvable', { clause }));
168
+ }
169
+ if (a.catalogRefused === 'engine') {
155
170
  return (`no model name is configured for the local engine to start with — ${laneLabel('modelCatalogFile')} names a default model, but the local engine is reported not to accept a catalog default in place of a startup model name` +
156
171
  (partialClause !== '' ? `, and ${partialClause}` : '') +
157
172
  ', and the engine does not start without one');
@@ -179,7 +194,10 @@ export function modelIdentityDetail(reading) {
179
194
  else if (a.catalogUnconfirmed === 'unusable') {
180
195
  parts.push(`${laneLabel('modelCatalogFile')} names a default model, but the report of whether the local engine accepts a catalog default in place of a startup model name gave no usable reading`);
181
196
  }
182
- else if (a.catalogRefused) {
197
+ else if (a.catalogRefused === 'adoption') {
198
+ parts.push(renderMessage(msg('model.identity.catalogAdoption.refused', { lane: msg(LANE_LABEL_KEYS.modelCatalogFile) })));
199
+ }
200
+ else if (a.catalogRefused === 'engine') {
183
201
  parts.push(`${laneLabel('modelCatalogFile')} names a default model, but the local engine is reported not to accept a catalog default in place of a startup model name`);
184
202
  }
185
203
  return `whether the local engine has a model name is unknown — ${joinPhrases(parts)}, and no other place this client reads provides one`;
package/dist/ownKey.d.ts CHANGED
@@ -33,3 +33,29 @@ export interface HumanInputAttachmentView {
33
33
  export declare function humanInputAttachmentsOf(frame: {} | null | undefined): HumanInputAttachmentView[] | undefined;
34
34
  export declare function markMcpSectionDroppedNamedRows(rows: object, dropped: ReadonlyMap<string, number>): void;
35
35
  export declare function mcpSectionDroppedNamedRowsOf(rows: object | null | undefined, name: string): number;
36
+ export interface PromptManifestSectionRow {
37
+ id: string;
38
+ chars: number;
39
+ band?: string;
40
+ locale?: string;
41
+ variant?: string;
42
+ contentHash?: string;
43
+ slot?: string;
44
+ }
45
+ export interface PromptManifestIdentityRow {
46
+ readonly artifact: string;
47
+ readonly layers: PromptIdentityLayersRow;
48
+ readonly epoch: number;
49
+ readonly activatedBy: string;
50
+ }
51
+ export interface PromptIdentityLayersRow {
52
+ readonly catalog: string;
53
+ readonly renderers: Readonly<Record<string, number>>;
54
+ readonly center?: string;
55
+ readonly deploy?: string;
56
+ readonly task?: string;
57
+ readonly binding: string;
58
+ }
59
+ export declare function promptManifestSectionViewOf(row: object): PromptManifestSectionRow | undefined;
60
+ export declare function promptManifestIdentityOfFrame(frame: object): PromptManifestIdentityRow | undefined;
61
+ export declare function promptManifestIdentityOf(raw: {} | null | undefined): PromptManifestIdentityRow | undefined;