@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.
- package/CHANGELOG.md +177 -0
- package/README.md +32 -19
- package/dist/adapt/arms.js +60 -36
- package/dist/adapt/panelTasks.d.ts +2 -1
- package/dist/adapt/panelTasks.js +32 -9
- package/dist/adapt/textStream.d.ts +1 -1
- package/dist/adapt/textStream.js +51 -28
- package/dist/adapter/activeRunSelfHeal.d.ts +7 -0
- package/dist/adapter/activeRunSelfHeal.js +58 -11
- package/dist/adapter/downstream/eventToSdkMessage.d.ts +25 -6
- package/dist/adapter/downstream/eventToSdkMessage.js +125 -9
- package/dist/adapter/downstream/terminalToSdkResult.js +10 -0
- package/dist/adapter/downstream/wiringManifestView.d.ts +3 -1
- package/dist/adapter/downstream/wiringManifestView.js +1 -0
- package/dist/adapter/runStream.js +32 -5
- package/dist/adapter/types.d.ts +2 -0
- package/dist/agentsWireCaps.d.ts +7 -0
- package/dist/agentsWireCaps.js +100 -15
- package/dist/cloudConfigWireCaps.d.ts +10 -1
- package/dist/cloudConfigWireCaps.js +156 -62
- package/dist/compactionStatus.d.ts +34 -0
- package/dist/compactionStatus.js +196 -0
- package/dist/decideFailureNote.js +3 -3
- package/dist/displayUntrusted.js +150 -5
- package/dist/engineAgentPanelStore.d.ts +7 -4
- package/dist/engineErrorCodes.d.ts +9 -1
- package/dist/engineErrorCodes.js +9 -5
- package/dist/engineRowRef.js +1 -0
- package/dist/engineToolDirectory.d.ts +3 -0
- package/dist/engineToolDirectory.js +4 -2
- package/dist/eventLeg.d.ts +1 -7
- package/dist/eventLeg.js +7 -8
- package/dist/fileHistoryCaptureCapability.d.ts +0 -3
- package/dist/fileHistoryCaptureCapability.js +0 -4
- package/dist/generated/engineFactTables.d.ts +1 -0
- package/dist/generated/engineFactTables.js +27 -0
- package/dist/generated/engineNoticeTables.js +3 -0
- package/dist/generated/settingsSchemaFns.d.ts +12 -0
- package/dist/generated/settingsSchemaFns.js +13 -0
- package/dist/generated/settingsSchemaMcpRequestEntry.d.ts +1 -0
- package/dist/generated/settingsSchemaMcpRequestEntry.js +162 -0
- package/dist/generated/settingsSchemaTables.d.ts +1 -0
- package/dist/generated/settingsSchemaTables.js +5 -0
- package/dist/generated/settingsSchemaTypes.d.ts +28 -0
- package/dist/hitl/allowRuleShadow.js +2 -2
- package/dist/hitl/approvalCardReasons.js +2 -2
- package/dist/hitl/approvalHolds.d.ts +14 -0
- package/dist/hitl/approvalHolds.js +41 -0
- package/dist/hitl/askClassPair.d.ts +2 -2
- package/dist/hitl/askClassPair.js +2 -2
- package/dist/hitl/attributionLadder.d.ts +23 -0
- package/dist/hitl/attributionLadder.js +67 -0
- package/dist/hitl/decisionLedger.d.ts +55 -0
- package/dist/hitl/decisionLedger.js +156 -0
- package/dist/hitl/decisionRefusal.d.ts +1 -0
- package/dist/hitl/decisionRefusal.js +1 -1
- package/dist/hitl/decisionSend.d.ts +113 -0
- package/dist/hitl/decisionSend.js +579 -0
- package/dist/hitl/hitlBridge.js +18 -6
- package/dist/hitl/planReviewWire.js +45 -76
- package/dist/hitl/sessionPolicyDeliverable.js +3 -6
- package/dist/hitl/toolApprovalWire.js +43 -109
- package/dist/hostTurnSettings.d.ts +46 -7
- package/dist/hostTurnSettings.js +408 -7
- package/dist/humanTurn.d.ts +2 -1
- package/dist/humanTurn.js +3 -3
- package/dist/i18n/messages.en.d.ts +131 -0
- package/dist/i18n/messages.en.js +143 -12
- package/dist/i18n/messages.zh.js +148 -12
- package/dist/index.d.ts +18 -2
- package/dist/index.js +10 -1
- package/dist/inheritEnvCapability.d.ts +1 -0
- package/dist/inheritEnvCapability.js +9 -2
- package/dist/inheritEnvWire.js +2 -2
- package/dist/injectionLaneNotice.d.ts +5 -0
- package/dist/injectionLaneNotice.js +46 -0
- package/dist/mcpPanel.js +2 -0
- package/dist/mcpReconnect.js +2 -2
- package/dist/mcpToolToggles.d.ts +6 -0
- package/dist/mcpToolToggles.js +46 -0
- package/dist/mcpWireCaps.d.ts +10 -4
- package/dist/mcpWireCaps.js +106 -71
- package/dist/mcpWireTransport.d.ts +4 -0
- package/dist/mcpWireTransport.js +18 -0
- package/dist/modelCapabilityProbe.d.ts +2 -1
- package/dist/modelCapabilityProbe.js +10 -9
- package/dist/modelIdentityResolvability.d.ts +2 -0
- package/dist/modelIdentityResolvability.js +23 -5
- package/dist/ownKey.d.ts +26 -0
- package/dist/ownKey.js +75 -0
- package/dist/panelReduce.d.ts +5 -0
- package/dist/panelReduce.js +45 -13
- package/dist/panelStateHolder.d.ts +1 -0
- package/dist/panelStateHolder.js +10 -1
- package/dist/permissionRuleIssue.d.ts +2 -1
- package/dist/permissionRuleIssue.js +22 -21
- package/dist/pluginHooksWire.js +12 -10
- package/dist/prepareRefusal.d.ts +8 -0
- package/dist/prepareRefusal.js +72 -0
- package/dist/request/taskRequest.d.ts +10 -10
- package/dist/request/taskRequest.js +106 -65
- package/dist/resumeRefusalCopy.d.ts +3 -1
- package/dist/resumeRefusalCopy.js +22 -4
- package/dist/retryStatus.d.ts +18 -0
- package/dist/retryStatus.js +76 -19
- package/dist/ruleRefusalSentence.d.ts +4 -0
- package/dist/ruleRefusalSentence.js +58 -0
- package/dist/sandboxWire.d.ts +2 -1
- package/dist/sandboxWire.js +55 -21
- package/dist/scenarioRefusal.d.ts +2 -1
- package/dist/scenarioRefusal.js +5 -5
- package/dist/seam.d.ts +24 -9
- package/dist/seam.js +44 -4
- package/dist/seatContract.d.ts +16 -8
- package/dist/seatContract.js +21 -6
- package/dist/settingsCredentialKeyPaths.d.ts +8 -0
- package/dist/settingsCredentialKeyPaths.js +219 -0
- package/dist/skillsWireCaps.d.ts +0 -3
- package/dist/skillsWireCaps.js +1 -2
- package/dist/subagentContentStore.d.ts +1 -0
- package/dist/subagentContentStore.js +135 -27
- package/dist/toolsFlagNames.d.ts +8 -0
- package/dist/toolsFlagNames.js +110 -0
- package/dist/turnFailurePresentation.js +15 -7
- package/dist/wireRefusalCopy.d.ts +51 -17
- package/dist/wireRefusalCopy.js +298 -135
- package/docs/INTEGRATION-CLIENTS.md +423 -26
- package/package.json +4 -4
package/dist/agentsWireCaps.js
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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
|
-
|
|
349
|
-
warnOnce(w);
|
|
350
|
-
payload = projected.agents;
|
|
430
|
+
setPayload(projected.agents, projected.warnings);
|
|
351
431
|
}
|
|
352
432
|
else {
|
|
353
|
-
|
|
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
|
-
|
|
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
|
-
|
|
375
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
586
|
+
clearPayload();
|
|
506
587
|
return {};
|
|
507
588
|
}
|
|
508
|
-
const
|
|
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
|
-
|
|
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' | '
|
|
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 {
|
|
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
|
|
18
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
51
|
+
missingHere.push(refName);
|
|
51
52
|
else
|
|
52
|
-
|
|
53
|
+
out.push([target, v]);
|
|
53
54
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
93
|
-
drop(name, '
|
|
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
|
-
|
|
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) =>
|
|
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(
|
|
692
|
-
const distinct = [...new Set(
|
|
693
|
-
const parts = distinct.slice(0, MCP_DROP_NAMES_SHOWN)
|
|
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 (
|
|
779
|
+
if (label === undefined)
|
|
749
780
|
bucket.unnamed++;
|
|
750
781
|
else
|
|
751
|
-
bucket.names.push(
|
|
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;
|