@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
@@ -8,6 +8,8 @@ import { CORE_PROTOCOL_PREFIXES } from './generated/toolNameTables.js';
8
8
  import { rosterMountedNames } from './rosterMountedNames.js';
9
9
  import { capForDisplay } from './fleetTaskDesc.js';
10
10
  import { deniedEntryToolName } from './engineToolDirectory.js';
11
+ import { hasCoreValuePort, mcpNamespace, mintNamespacePrefix } from './coreValuePorts.js';
12
+ import { msg, renderMessage } from './i18n/translate.js';
11
13
  import { assemblyRefusal, withAssemblyRefusal } from './wireFailureShape.js';
12
14
  export const MAX_TASK_AGENTS = 32;
13
15
  export const TASK_AGENT_WIRE_FIELDS = [
@@ -28,6 +30,7 @@ export const TASK_AGENT_WIRE_FIELDS = [
28
30
  'observerMessage',
29
31
  ];
30
32
  export const AGENT_MEMORY_WORDS = frozenSet(['user', 'project', 'local']);
33
+ export const REFUSED_PROJECT_SERVER_CAUSES = Object.freeze(['rejected', 'not_approved', 'disabled', 'policy_blocked', 'plugin_only', 'malformed_config']);
31
34
  const SOURCE_RANK = {
32
35
  policySettings: 0,
33
36
  flagSettings: 1,
@@ -113,8 +116,58 @@ function toolListShapeRefusal(agent, key, v) {
113
116
  return withAssemblyRefusal(new TypeError(`projectAgentsForWire: agent "${capForDisplay(agent, 80)}" has ${key} written as ${listShapeWord(v)}, but it must be an array of tool-name strings — ` +
114
117
  'refusing to assemble the agents list (a list dropped silently would give the agent every tool, or remove a deny rule). Fix the definition.'), 'input_malformed');
115
118
  }
119
+ const REFUSED_SERVER_PHRASE_KEYS = Object.freeze({
120
+ rejected: ['config.agentsWire.refusedServer.cause.rejected', 'config.agentsWire.refusedServer.way.rejected'],
121
+ not_approved: ['config.agentsWire.refusedServer.cause.notApproved', 'config.agentsWire.refusedServer.way.notApproved'],
122
+ disabled: ['config.agentsWire.refusedServer.cause.disabled', 'config.agentsWire.refusedServer.way.disabled'],
123
+ policy_blocked: ['config.agentsWire.refusedServer.cause.policyBlocked', 'config.agentsWire.refusedServer.way.policyBlocked'],
124
+ plugin_only: ['config.agentsWire.refusedServer.cause.pluginOnly', 'config.agentsWire.refusedServer.way.pluginOnly'],
125
+ malformed_config: ['config.agentsWire.refusedServer.cause.malformedConfig', 'config.agentsWire.refusedServer.way.malformedConfig'],
126
+ });
127
+ const REFUSED_SERVER_UNKNOWN_PHRASE = ['config.agentsWire.refusedServer.cause.unknown', 'config.agentsWire.refusedServer.way.unknown'];
128
+ const REFUSED_SERVER_NAME_MAX = 80;
129
+ function refusedServerIndexOf(list) {
130
+ try {
131
+ if (!Array.isArray(list) || list.length === 0)
132
+ return [];
133
+ if (!hasCoreValuePort('protocolOf') || !hasCoreValuePort('mintNamespacePrefix'))
134
+ return [];
135
+ const ns = mcpNamespace();
136
+ if (ns === undefined)
137
+ return [];
138
+ const out = [];
139
+ const seen = new Set();
140
+ for (const row of list) {
141
+ try {
142
+ if (row === null || typeof row !== 'object')
143
+ continue;
144
+ const name = row.name;
145
+ const cause = row.cause;
146
+ if (typeof name !== 'string' || name === '' || seen.has(name))
147
+ continue;
148
+ const prefix = mintNamespacePrefix(ns, name);
149
+ if (typeof prefix !== 'string' || prefix.length === 0)
150
+ continue;
151
+ seen.add(name);
152
+ const known = typeof cause === 'string' && Object.hasOwn(REFUSED_SERVER_PHRASE_KEYS, cause) ? REFUSED_SERVER_PHRASE_KEYS[cause] : undefined;
153
+ out.push({ name, prefix, phrase: known ?? REFUSED_SERVER_UNKNOWN_PHRASE });
154
+ }
155
+ catch {
156
+ continue;
157
+ }
158
+ }
159
+ return out;
160
+ }
161
+ catch {
162
+ return [];
163
+ }
164
+ }
165
+ function entryNamesServer(entry, prefix) {
166
+ return entry.startsWith(prefix) || entry === prefix.slice(0, -2);
167
+ }
116
168
  export function projectAgentsForWire(defs, deps) {
117
169
  const warnings = [];
170
+ const refusedServers = refusedServerIndexOf(deps.refusedProjectServers);
118
171
  const customs = defs
119
172
  .filter(d => d.source !== 'built-in')
120
173
  .slice()
@@ -191,7 +244,30 @@ export function projectAgentsForWire(defs, deps) {
191
244
  for (const f of flaws) {
192
245
  if (f.list !== 'tools')
193
246
  continue;
194
- warnings.push(`agents wire: ${who}: its tools entry ${toolListFlawPhrase(f)} — sent as written; tool names are matched exactly, so this entry gives the agent nothing unless this run has a tool with exactly that name. Fix the entry in the agent definition.`);
247
+ warnings.push(renderMessage(msg('config.agentsWire.toolEntry.sentence', { who, phrase: toolListFlawPhrase(f) })));
248
+ }
249
+ }
250
+ if (refusedServers.length > 0 && item.allowTools !== undefined) {
251
+ const told = new Set();
252
+ const mounted = rosterMountedNames(deps.toolRoster);
253
+ const mountedNames = typeof mounted === 'object' ? mounted : undefined;
254
+ for (const entry of item.allowTools) {
255
+ if (mountedNames?.has(entry) === true)
256
+ continue;
257
+ const hit = refusedServers.find((r) => entryNamesServer(entry, r.prefix));
258
+ if (hit === undefined)
259
+ continue;
260
+ const sentence = renderMessage(msg('config.agentsWire.refusedServer.sentence', {
261
+ who: whoOf(),
262
+ entry: JSON.stringify(entry),
263
+ server: JSON.stringify(capForDisplay(hit.name, REFUSED_SERVER_NAME_MAX)),
264
+ cause: msg(hit.phrase[0]),
265
+ way: msg(hit.phrase[1]),
266
+ }));
267
+ if (told.has(sentence))
268
+ continue;
269
+ told.add(sentence);
270
+ warnings.push(sentence);
195
271
  }
196
272
  }
197
273
  if (def.memory !== undefined) {
@@ -300,6 +376,12 @@ let preparedKey = null;
300
376
  let preparedCapsGen = null;
301
377
  let preparedPromise = null;
302
378
  let payload;
379
+ function setPayload(agents, warnings) {
380
+ payload = { agents, warnings: Object.freeze([...warnings]) };
381
+ }
382
+ function clearPayload() {
383
+ payload = undefined;
384
+ }
303
385
  let currentGeneration = null;
304
386
  function concludeGeneration(generation, outcome) {
305
387
  if (generation !== null && preparedPromise === generation && currentGeneration !== null)
@@ -335,7 +417,7 @@ function watchCapsVerdict(baseUrl) {
335
417
  void engineCapsSettled(baseUrl).then(() => {
336
418
  if (preparedKey !== baseUrl || capsVerdictAt(baseUrl) !== false)
337
419
  return;
338
- payload = undefined;
420
+ clearPayload();
339
421
  concludeGeneration(preparedPromise, 'ok');
340
422
  currentGeneration?.settle();
341
423
  });
@@ -345,12 +427,10 @@ function applyProjectionIfCurrent(generation, projected) {
345
427
  return false;
346
428
  const accepted = capsVerdictAt(preparedKey) !== false;
347
429
  if (accepted) {
348
- for (const w of projected.warnings)
349
- warnOnce(w);
350
- payload = projected.agents;
430
+ setPayload(projected.agents, projected.warnings);
351
431
  }
352
432
  else {
353
- payload = undefined;
433
+ clearPayload();
354
434
  }
355
435
  concludeGeneration(generation, 'ok');
356
436
  currentGeneration?.settle();
@@ -361,7 +441,7 @@ async function runPrepare(cfg, projector, generation) {
361
441
  const [, projected] = await Promise.all([capsSettledAt(cfg.baseUrl), projector()]);
362
442
  if (capsVerdictAt(cfg.baseUrl) === false) {
363
443
  if (preparedKey === cfg.baseUrl) {
364
- payload = undefined;
444
+ clearPayload();
365
445
  concludeGeneration(generation, 'ok');
366
446
  if (projected.agents) {
367
447
  warnOnce(`agents wire: this engine does not support per-task custom agents (capabilities.taskAgents is not true: the engine does not report it, or this deployment turns it off, for example a multi-tenant one; this client cannot tell which) — ${projected.agents.length} custom agent definition(s) will NOT be sent`);
@@ -371,13 +451,14 @@ async function runPrepare(cfg, projector, generation) {
371
451
  }
372
452
  if (!applyProjectionIfCurrent(generation, projected))
373
453
  return;
374
- if (payload && hostEnv().SEMA_DEBUG) {
375
- console.error(`[sema] agents wire: ${payload.length} custom agent(s) → TaskRequest.agents (${payload.map(a => a.name).join(', ')})`);
454
+ const sent = payload?.agents;
455
+ if (sent && hostEnv().SEMA_DEBUG) {
456
+ console.error(`[sema] agents wire: ${sent.length} custom agent(s) → TaskRequest.agents (${sent.map(a => a.name).join(', ')})`);
376
457
  }
377
458
  }
378
459
  catch (e) {
379
460
  if (preparedPromise === generation)
380
- payload = undefined;
461
+ clearPayload();
381
462
  concludeGeneration(generation, 'failed');
382
463
  if (hostEnv().SEMA_DEBUG) {
383
464
  console.error('[sema] agents wire prepare failed (the readiness gate refuses this turn):', e);
@@ -412,7 +493,7 @@ export function invalidateTaskAgentsWire() {
412
493
  const projector = activeProjector;
413
494
  if (!preparedPromise || !projector)
414
495
  return Promise.resolve();
415
- payload = undefined;
496
+ clearPayload();
416
497
  return startGeneration(generation => runReproject(projector, generation));
417
498
  }
418
499
  async function runReproject(projector, generation) {
@@ -443,7 +524,7 @@ export async function awaitTaskAgentsWire(timeoutMs = 4000) {
443
524
  return taskAgentsReady();
444
525
  const projector = activeProjector;
445
526
  if (currentGeneration?.outcome === 'failed' && projector !== null) {
446
- payload = undefined;
527
+ clearPayload();
447
528
  void startGeneration(generation => runReproject(projector, generation));
448
529
  }
449
530
  let timeoutHandle;
@@ -502,10 +583,14 @@ function kickTaskAgentsRefresh() {
502
583
  export function taskAgentsField() {
503
584
  const base = preparedKey;
504
585
  if (capsVerdictAt(base) === false) {
505
- payload = undefined;
586
+ clearPayload();
506
587
  return {};
507
588
  }
508
- const current = payload;
589
+ const held = payload;
590
+ const current = held?.agents;
591
+ if (held !== undefined)
592
+ for (const w of held.warnings)
593
+ warnOnce(w);
509
594
  kickTaskAgentsRefresh();
510
595
  return current ? { agents: current } : {};
511
596
  }
@@ -515,6 +600,6 @@ export function _resetAgentsWireForTest() {
515
600
  preparedCapsGen = null;
516
601
  preparedPromise = null;
517
602
  currentGeneration = null;
518
- payload = undefined;
603
+ clearPayload();
519
604
  emittedWarnings.clear();
520
605
  }
@@ -1,18 +1,25 @@
1
1
  import type { SkillSpec } from '@sema-agent/sdk';
2
2
  import type { McpServerSpec } from './mcpWireCaps.js';
3
3
  import type { EnvLike } from './hostEnv.js';
4
+ import type { McpEntryKeyIssue } from './generated/settingsSchemaTypes.js';
4
5
  export type { McpServerSpec };
5
6
  type Rec = Record<string, unknown>;
6
7
  export type { EnvLike };
7
- export type CloudMcpDropReason = 'disabled' | 'missing-field' | 'unknown-transport' | 'capped' | 'principal-and-assertion-header' | 'tool-axes-invalid' | 'elicitation-invalid';
8
+ export type CloudMcpDropReason = 'disabled' | 'missing-field' | 'unknown-transport' | 'principal-and-assertion-header' | 'tool-axes-invalid' | 'elicitation-invalid' | 'name-unusable' | 'transport-invalid';
8
9
  export interface CloudMcpDroppedServer {
9
10
  name?: string;
10
11
  reason: CloudMcpDropReason;
12
+ path?: string;
13
+ }
14
+ export interface CloudMcpDegradedServer {
15
+ name: string;
16
+ lost: readonly McpEntryKeyIssue[];
11
17
  }
12
18
  export interface CloudMcpProjection {
13
19
  specs: McpServerSpec[];
14
20
  missingEnv: string[];
15
21
  droppedServers: CloudMcpDroppedServer[];
22
+ degradedServers: CloudMcpDegradedServer[];
16
23
  }
17
24
  export declare function cloudMcpToSpecs(servers: unknown, env: EnvLike): CloudMcpProjection;
18
25
  export declare function cloudSkillsToSpecs(manifestSkills: unknown, resolveBody: (contentHash: string) => string | null): SkillSpec[];
@@ -111,6 +118,7 @@ export interface CloudProjection {
111
118
  scope: string;
112
119
  skillsMissingBody: string[];
113
120
  droppedMcpServers: CloudMcpDroppedServer[];
121
+ degradedMcpServers: CloudMcpDegradedServer[];
114
122
  droppedModels: CloudDroppedModel[];
115
123
  execution: CloudExecutionProjection;
116
124
  effectiveWarnings: CloudEffectiveWarningsReading;
@@ -125,4 +133,5 @@ export interface CloudEffectiveNotice {
125
133
  }
126
134
  export declare function cloudEffectiveNotices(p: Pick<CloudProjection, 'effectiveWarnings' | 'budget' | 'runtimeCaps' | 'modelRefIssues' | 'droppedMcpServers'> & {
127
135
  readonly mcp?: readonly McpServerSpec[];
136
+ readonly degradedMcpServers?: readonly CloudMcpDegradedServer[];
128
137
  }): CloudEffectiveNotice[];
@@ -1,11 +1,14 @@
1
- import { MCP_CAPS, mcpAssertionHeaderNotSentNotice, mcpElicitationVerdict, mcpToolAxesVerdict, readMcpHttpIdentityHeaders } from './mcpWireCaps.js';
1
+ import { mcpAssertionHeaderNotSentNotice, mcpElicitationVerdict, mcpToolAxesVerdict, readMcpHttpIdentityHeaders } from './mcpWireCaps.js';
2
2
  import { toCappedSpec } from './skillsWireCaps.js';
3
+ import { SETTINGS_DESCRIBE_BAD_WIRE_NAME, SETTINGS_IS_USABLE_WIRE_NAME, SETTINGS_READ_REQUEST_MCP_TRANSPORT } from './generated/settingsSchemaFns.js';
4
+ import { snapshotArgs, wireTransportOf } from './mcpWireTransport.js';
3
5
  import { msg, renderMessage } from './i18n/translate.js';
4
6
  import { TIER_ORDER } from './model/tierVocabulary.js';
5
7
  import { SETTINGS_NARROW_AT_MENTION } from './generated/settingsSchemaFns.js';
6
8
  import { modelsPolicyOf } from './model/modelsPolicy.js';
7
9
  import { normalizeEngineBaseUrl } from './engineUrl.js';
8
10
  import { guardedRead, readOwnDataNonEmptyString, readOwnDataValue } from './ownKey.js';
11
+ import { capForDisplay } from './fleetTaskDesc.js';
9
12
  import { SETTINGS_BUDGET_NUMBER_KEYS, SETTINGS_BUDGET_PER_MODEL_NUMBER_KEYS, SETTINGS_BUDGET_STRING_KEYS, SETTINGS_TIER_GROUP_NAME_RE, } from './generated/settingsSchemaTables.js';
10
13
  const asRec = (v) => (typeof v === 'object' && v !== null && !Array.isArray(v) ? v : undefined);
11
14
  const asArr = (v) => (Array.isArray(v) ? v : []);
@@ -14,16 +17,13 @@ export function cloudMcpToSpecs(servers, env) {
14
17
  const specs = [];
15
18
  const missingEnv = [];
16
19
  const droppedServers = [];
17
- const drop = (name, reason) => {
18
- droppedServers.push({ ...(name ? { name } : {}), reason });
20
+ const degradedServers = [];
21
+ const drop = (name, reason, path) => {
22
+ droppedServers.push({ ...(name ? { name } : {}), reason, ...(path !== undefined ? { path } : {}) });
19
23
  };
20
24
  for (const raw of asArr(servers)) {
21
25
  const s = asRec(raw);
22
26
  const name = s ? asStr(s.name) : undefined;
23
- if (specs.length >= MCP_CAPS.items) {
24
- drop(name, 'capped');
25
- continue;
26
- }
27
27
  if (!s || s.enabled === false) {
28
28
  drop(name, s?.enabled === false ? 'disabled' : 'missing-field');
29
29
  continue;
@@ -33,66 +33,79 @@ export function cloudMcpToSpecs(servers, env) {
33
33
  drop(name, 'missing-field');
34
34
  continue;
35
35
  }
36
- let transport = null;
37
- if (t.kind === 'stdio') {
38
- const command = asStr(t.command);
39
- if (!command) {
40
- drop(name, 'missing-field');
41
- continue;
42
- }
43
- const envOut = {};
44
- for (const [target, ref] of Object.entries(asRec(t.envRefs) ?? {})) {
36
+ if (!SETTINGS_IS_USABLE_WIRE_NAME(name)) {
37
+ drop(name, 'name-unusable');
38
+ continue;
39
+ }
40
+ let candidate;
41
+ let requiredRaw;
42
+ const missingHere = [];
43
+ const resolveRefs = (refs) => {
44
+ const out = [];
45
+ for (const [target, ref] of Object.entries(asRec(refs) ?? {})) {
45
46
  const refName = asStr(ref);
46
47
  if (!refName)
47
48
  continue;
48
49
  const v = env[refName];
49
50
  if (v === undefined || v === '')
50
- missingEnv.push(refName);
51
+ missingHere.push(refName);
51
52
  else
52
- envOut[target] = v;
53
+ out.push([target, v]);
53
54
  }
54
- const args = asArr(t.args).filter((a) => typeof a === 'string');
55
- transport = {
56
- kind: 'stdio',
57
- command,
58
- ...(args.length > 0 ? { args } : {}),
59
- ...(Object.keys(envOut).length > 0 ? { env: envOut } : {}),
60
- };
61
- }
62
- else if (t.kind === 'http') {
63
- const url = asStr(t.url);
64
- if (!url) {
55
+ return out;
56
+ };
57
+ const dropHard = (issue, raw) => {
58
+ const missing = (issue.path === 'transport.command' || issue.path === 'transport.url') && asStr(raw) === undefined;
59
+ if (missing)
65
60
  drop(name, 'missing-field');
66
- continue;
67
- }
68
- const identity = readMcpHttpIdentityHeaders(t);
69
- if (identity.kind === 'both_set') {
70
- drop(name, 'principal-and-assertion-header');
71
- continue;
61
+ else
62
+ drop(name, 'transport-invalid', issue.path);
63
+ };
64
+ try {
65
+ if (t.kind === 'stdio') {
66
+ const command = t.command;
67
+ requiredRaw = command;
68
+ const head = SETTINGS_READ_REQUEST_MCP_TRANSPORT({ kind: 'stdio', command });
69
+ if (!head.ok) {
70
+ dropHard(head.issue, command);
71
+ continue;
72
+ }
73
+ const envEntries = resolveRefs(t.envRefs);
74
+ candidate = { kind: 'stdio', command, args: snapshotArgs(t.args), env: envEntries.length > 0 ? Object.fromEntries(envEntries) : undefined };
72
75
  }
73
- const headers = {};
74
- for (const [headerName, ref] of Object.entries(asRec(t.headerRefs) ?? {})) {
75
- const refName = asStr(ref);
76
- if (!refName)
76
+ else if (t.kind === 'http') {
77
+ const url = t.url;
78
+ requiredRaw = url;
79
+ const head = SETTINGS_READ_REQUEST_MCP_TRANSPORT({ kind: 'http', url });
80
+ if (!head.ok) {
81
+ dropHard(head.issue, url);
77
82
  continue;
78
- const v = env[refName];
79
- if (v === undefined || v === '')
80
- missingEnv.push(refName);
81
- else
82
- headers[headerName] = v;
83
+ }
84
+ const principalHeader = t.principalHeader;
85
+ const assertionHeader = t.assertionHeader;
86
+ if (readMcpHttpIdentityHeaders({ principalHeader, assertionHeader }).kind === 'both_set') {
87
+ drop(name, 'principal-and-assertion-header');
88
+ continue;
89
+ }
90
+ const headerEntries = resolveRefs(t.headerRefs);
91
+ candidate = { kind: 'http', url, headers: headerEntries.length > 0 ? Object.fromEntries(headerEntries) : undefined, principalHeader, assertionHeader };
92
+ }
93
+ else {
94
+ drop(name, 'unknown-transport');
95
+ continue;
83
96
  }
84
- transport = {
85
- kind: 'http',
86
- url,
87
- ...(Object.keys(headers).length > 0 ? { headers } : {}),
88
- ...(identity.principalHeader !== undefined ? { principalHeader: identity.principalHeader } : {}),
89
- ...(identity.assertionHeader !== undefined ? { assertionHeader: identity.assertionHeader } : {}),
90
- };
91
97
  }
92
- else {
93
- drop(name, 'unknown-transport');
98
+ catch {
99
+ drop(name, 'transport-invalid', 'transport');
94
100
  continue;
95
101
  }
102
+ const read = SETTINGS_READ_REQUEST_MCP_TRANSPORT(candidate);
103
+ if (!read.ok) {
104
+ dropHard(read.issue, requiredRaw);
105
+ continue;
106
+ }
107
+ missingEnv.push(...missingHere);
108
+ const transport = wireTransportOf(read.transport);
96
109
  const axesDeclared = s.toolAxes;
97
110
  const axesVerdict = mcpToolAxesVerdict(axesDeclared);
98
111
  if (axesVerdict.kind === 'bad') {
@@ -107,6 +120,8 @@ export function cloudMcpToSpecs(servers, env) {
107
120
  }
108
121
  const allowToolsWritten = Array.isArray(s.allowTools);
109
122
  const allowTools = asArr(s.allowTools).filter((a) => typeof a === 'string');
123
+ if (read.lost.length > 0)
124
+ degradedServers.push({ name, lost: Object.freeze([...read.lost]) });
110
125
  specs.push({
111
126
  name,
112
127
  transport,
@@ -116,7 +131,7 @@ export function cloudMcpToSpecs(servers, env) {
116
131
  ...(axesShipped !== undefined ? { toolAxes: axesShipped } : {}),
117
132
  });
118
133
  }
119
- return { specs, missingEnv: [...new Set(missingEnv)], droppedServers };
134
+ return { specs, missingEnv: [...new Set(missingEnv)], droppedServers, degradedServers };
120
135
  }
121
136
  export function cloudSkillsToSpecs(manifestSkills, resolveBody) {
122
137
  const specs = [];
@@ -567,7 +582,7 @@ export function projectEffectiveBody(body, skillBodies, env, rostersValue, opts)
567
582
  const config = asRec(b.config) ?? {};
568
583
  const mcpDomain = asRec(config.mcp);
569
584
  const skillsDomain = asRec(config.skills);
570
- const { specs: mcp, missingEnv: mcpMissing, droppedServers: droppedMcpServers } = cloudMcpToSpecs(mcpDomain?.servers, env);
585
+ const { specs: mcp, missingEnv: mcpMissing, droppedServers: droppedMcpServers, degradedServers: degradedMcpServers } = cloudMcpToSpecs(mcpDomain?.servers, env);
571
586
  const manifest = asArr(skillsDomain?.skills);
572
587
  const skills = cloudSkillsToSpecs(manifest, (hash) => skillBodies[hash] ?? null);
573
588
  const skillsMissingBody = manifest
@@ -592,6 +607,7 @@ export function projectEffectiveBody(body, skillBodies, env, rostersValue, opts)
592
607
  scope: asStr(b.scope) ?? 'global',
593
608
  skillsMissingBody,
594
609
  droppedMcpServers,
610
+ degradedMcpServers,
595
611
  droppedModels: entriesRead === undefined ? [] : cloudModelsDropped({ models: entriesRead }),
596
612
  execution: cloudExecutionToPolicy(config.execution),
597
613
  effectiveWarnings: effectiveWarningsOf(b),
@@ -676,9 +692,13 @@ const MCP_DROP_SENTENCES = {
676
692
  level: 'warn',
677
693
  text: (who) => renderMessage(msg('config.mcp.cloudElicitationInvalid', { who })),
678
694
  },
679
- capped: {
695
+ 'name-unusable': {
696
+ level: 'warn',
697
+ text: (who) => renderMessage(msg('config.mcp.cloudNameUnusable', { who })),
698
+ },
699
+ 'transport-invalid': {
680
700
  level: 'warn',
681
- text: (who) => `cloud config: MCP server entries left out because more than ${MCP_CAPS.items} were served to you and this client passes at most ${MCP_CAPS.items} to the engine: ${who}`,
701
+ text: (who) => renderMessage(msg('config.mcp.cloudTransportInvalid', { who })),
682
702
  },
683
703
  disabled: {
684
704
  level: 'info',
@@ -688,9 +708,9 @@ const MCP_DROP_SENTENCES = {
688
708
  const MCP_DROP_UNRECOGNISED = (who) => `cloud config: MCP server entries left out for a reason this client does not recognise: ${who}`;
689
709
  const MCP_DROP_LIST_UNREADABLE = 'cloud config: the list of MCP server entries left out could not be read by this client — servers missing from the local tool list may have been left out without notice';
690
710
  const MCP_DROP_NAMES_SHOWN = 5;
691
- function mcpDropWho(names, unnamed) {
692
- const distinct = [...new Set(names)];
693
- const parts = distinct.slice(0, MCP_DROP_NAMES_SHOWN).map((n) => `"${n}"`);
711
+ function mcpDropWho(labels, unnamed) {
712
+ const distinct = [...new Set(labels)];
713
+ const parts = distinct.slice(0, MCP_DROP_NAMES_SHOWN);
694
714
  if (distinct.length > MCP_DROP_NAMES_SHOWN)
695
715
  parts.push(`+${distinct.length - MCP_DROP_NAMES_SHOWN} more`);
696
716
  if (unnamed > 0)
@@ -728,6 +748,8 @@ function droppedMcpNotices(carrier) {
728
748
  catch {
729
749
  return listUnreadable;
730
750
  }
751
+ if (typeof count !== 'number' || !Number.isSafeInteger(count) || count < 0)
752
+ return listUnreadable;
731
753
  const known = new Map();
732
754
  const unrecognised = { names: [], unnamed: 0 };
733
755
  for (let i = 0; i < count; i++) {
@@ -738,6 +760,15 @@ function droppedMcpNotices(carrier) {
738
760
  return typeof raw === 'string' && Object.hasOwn(MCP_DROP_SENTENCES, raw) ? raw : undefined;
739
761
  });
740
762
  const name = guarded(() => readOwnDataNonEmptyString(rowObj, 'name'));
763
+ const path = reason === 'transport-invalid' ? guarded(() => readOwnDataNonEmptyString(rowObj, 'path')) : undefined;
764
+ const pathLabel = path !== undefined && /^transport(\.[A-Za-z]{1,32})?$/.test(path) ? ` (${path})` : '';
765
+ const label = name === undefined
766
+ ? undefined
767
+ : reason === 'name-unusable'
768
+ ? SETTINGS_DESCRIBE_BAD_WIRE_NAME(name)
769
+ : reason === 'transport-invalid'
770
+ ? `"${capForDisplay(name, DEGRADED_NAME_SHOWN)}"${pathLabel}`
771
+ : `"${name}"${pathLabel}`;
741
772
  let bucket = unrecognised;
742
773
  if (reason !== undefined) {
743
774
  let b = known.get(reason);
@@ -745,10 +776,10 @@ function droppedMcpNotices(carrier) {
745
776
  known.set(reason, (b = { names: [], unnamed: 0 }));
746
777
  bucket = b;
747
778
  }
748
- if (name === undefined)
779
+ if (label === undefined)
749
780
  bucket.unnamed++;
750
781
  else
751
- bucket.names.push(name);
782
+ bucket.names.push(label);
752
783
  }
753
784
  const out = [];
754
785
  for (const reason of Object.keys(MCP_DROP_SENTENCES)) {
@@ -761,6 +792,68 @@ function droppedMcpNotices(carrier) {
761
792
  }
762
793
  return out;
763
794
  }
795
+ const DEGRADED_LOST_SHOWN = 80;
796
+ const DEGRADED_NAME_SHOWN = 128;
797
+ const LOST_PATH_SHAPE = /^transport(\.[A-Za-z_][A-Za-z0-9_]{0,31}){1,2}$/;
798
+ function degradedLostLabels(rowObj) {
799
+ const lost = readOwnDataValue(rowObj, 'lost');
800
+ const out = [];
801
+ if (!Array.isArray(lost))
802
+ return out;
803
+ const n = guarded(() => lost.length) ?? 0;
804
+ for (let i = 0; i < n; i++) {
805
+ const one = guarded(() => readOwnDataValue(lost, String(i)));
806
+ const issue = typeof one === 'object' && one !== null ? one : undefined;
807
+ const path = guarded(() => readOwnDataNonEmptyString(issue, 'path'));
808
+ if (path === undefined || !LOST_PATH_SHAPE.test(path))
809
+ continue;
810
+ const member = guarded(() => readOwnDataNonEmptyString(issue, 'member'));
811
+ out.push(member !== undefined && !path.endsWith(`.${member}`) ? `${path} (member "${capForDisplay(member, DEGRADED_LOST_SHOWN)}")` : path);
812
+ }
813
+ return [...new Set(out)];
814
+ }
815
+ function degradedMcpNotices(carrier) {
816
+ const unreadable = [{ level: 'warn', text: renderMessage(msg('config.mcp.cloudTransportDegradedUnreadable')) }];
817
+ let desc;
818
+ try {
819
+ desc = Object.getOwnPropertyDescriptor(carrier, 'degradedMcpServers');
820
+ }
821
+ catch {
822
+ return unreadable;
823
+ }
824
+ if (desc === undefined)
825
+ return [];
826
+ if (!Object.hasOwn(desc, 'value'))
827
+ return unreadable;
828
+ const rows = desc.value;
829
+ let count = 0;
830
+ try {
831
+ if (!Array.isArray(rows))
832
+ return unreadable;
833
+ count = rows.length;
834
+ }
835
+ catch {
836
+ return unreadable;
837
+ }
838
+ if (typeof count !== 'number' || !Number.isSafeInteger(count) || count < 0)
839
+ return unreadable;
840
+ const labels = [];
841
+ let unnamed = 0;
842
+ for (let i = 0; i < count; i++) {
843
+ const row = guarded(() => readOwnDataValue(rows, String(i)));
844
+ const rowObj = typeof row === 'object' && row !== null ? row : undefined;
845
+ const name = guarded(() => readOwnDataNonEmptyString(rowObj, 'name'));
846
+ if (name === undefined) {
847
+ unnamed++;
848
+ continue;
849
+ }
850
+ const paths = guarded(() => degradedLostLabels(rowObj)) ?? [];
851
+ labels.push(`"${capForDisplay(name, DEGRADED_NAME_SHOWN)}"${paths.length > 0 ? ` (${paths.join(', ')})` : ''}`);
852
+ }
853
+ if (labels.length + unnamed === 0)
854
+ return [];
855
+ return [{ level: 'warn', text: renderMessage(msg('config.mcp.cloudTransportDegraded', { who: mcpDropWho(labels, unnamed) })) }];
856
+ }
764
857
  function budgetNotice(r) {
765
858
  switch (r.kind) {
766
859
  case 'configured': {
@@ -829,6 +922,7 @@ export function cloudEffectiveNotices(p) {
829
922
  });
830
923
  }
831
924
  out.push(...droppedMcpNotices(p));
925
+ out.push(...degradedMcpNotices(p));
832
926
  const assertionLine = mcpAssertionHeaderNotSentNotice(guarded(() => readOwnDataValue(p, 'mcp')) ?? []);
833
927
  if (assertionLine !== undefined)
834
928
  out.push({ level: 'warn', text: `cloud config: ${assertionLine}` });
@@ -0,0 +1,34 @@
1
+ export interface CompactionStatusReading {
2
+ readonly compacting: boolean;
3
+ readonly since?: number;
4
+ }
5
+ export type CompactionStatusChange = {
6
+ readonly kind: 'opened';
7
+ readonly pass: string;
8
+ readonly since?: number;
9
+ } | {
10
+ readonly kind: 'closed';
11
+ readonly pass: string;
12
+ readonly result?: 'success' | 'failed';
13
+ readonly error?: string;
14
+ };
15
+ export interface CompactionStatusTracker {
16
+ take(input: {} | null | undefined): CompactionStatusChange | null;
17
+ streamEnded(): CompactionStatusChange | null;
18
+ reading(): CompactionStatusReading;
19
+ }
20
+ export interface PrintCompactionStatusFrame {
21
+ readonly type: 'system';
22
+ readonly subtype: 'status';
23
+ readonly status: 'compacting' | null;
24
+ readonly compact_result?: 'success' | 'failed';
25
+ readonly compact_error?: string;
26
+ readonly uuid: string;
27
+ readonly session_id: string;
28
+ }
29
+ export interface PrintCompactionStatusCtx {
30
+ readonly sessionId: string;
31
+ readonly mintUuid: () => string;
32
+ }
33
+ export declare function createCompactionStatusTracker(): CompactionStatusTracker;
34
+ export declare function compactionStatusFrameOf(change: CompactionStatusChange | null | undefined, ctx: PrintCompactionStatusCtx): PrintCompactionStatusFrame | null;