weir-harness 2.0.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.capability-badge.js +2 -0
- package/lib/client.capability-manager.js +46 -179
- package/lib/client.capability-model.js +428 -3
- package/lib/client.file-lease-tool-model.js +189 -0
- package/lib/client.file-lease-tool-view.js +434 -0
- package/lib/client.js +105 -7
- package/lib/client.mcp-manager.js +563 -0
- package/package.json +1 -1
- package/skills/mcp-server-setup/SKILL.md +124 -0
- package/src/capabilities/apply-engine.js +18 -4
- package/src/capabilities/capability-remote.js +296 -45
- package/src/capabilities/initial-selection.js +3 -2
- package/src/capabilities/lifecycle-preload.js +69 -11
- package/src/capabilities/lifecycle-snapshot.js +127 -39
- package/src/capabilities/mcp-admission.js +2 -2
- package/src/capabilities/mcp-config.js +344 -0
- package/src/capabilities/mcp-credentials.js +180 -0
- package/src/capabilities/mcp-diagnostics.js +169 -0
- package/src/capabilities/mcp-drain.js +18 -4
- package/src/capabilities/mcp-facade.js +40 -9
- package/src/capabilities/mcp-gate.js +51 -52
- package/src/capabilities/mcp-install-tool.js +276 -0
- package/src/capabilities/mcp-manager.js +575 -146
- package/src/capabilities/mcp-mount.js +220 -79
- package/src/capabilities/mcp-operations.js +359 -0
- package/src/capabilities/mcp-process.js +43 -0
- package/src/capabilities/mcp-registry.js +74 -60
- package/src/capabilities/mcp-runtime.js +103 -0
- package/src/capabilities/read-payloads.js +8 -3
- package/src/capabilities/skill-applicability.js +185 -0
- package/src/capabilities/skill-builtin-migration.js +2 -1
- package/src/capabilities/skill-selection-plugin.js +170 -57
- package/src/file-lease/client.js +268 -35
- package/src/file-lease/index.js +4 -0
- package/src/file-lease/plugin.js +239 -15
- package/src/file-lease/tools.js +426 -38
- package/src/file-lease/watch.js +473 -0
- package/src/intent-gate/index.js +29 -12
- package/src/intent-gate/matcher.js +36 -2
- package/src/lease-broker/protocol.js +3 -2
- package/src/lease-broker/server.js +6 -3
- package/src/lease-broker/state.js +68 -11
- package/src/process/index.js +4 -2
- package/src/shared/audit.js +3 -0
- package/src/shared/child-scope.js +2 -0
- package/src/worktree/rules.js +1 -1
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
// Primary-Orchestrator MCP installation. Preview is inert; install executes only
|
|
2
|
+
// the Host-stored plan approved through userQuestions, never post-answer model input.
|
|
3
|
+
import { createHash } from 'node:crypto'
|
|
4
|
+
import { parseMcpDocument, fingerprintMcpEntry } from './mcp-config.js'
|
|
5
|
+
import { mcpDiagnostic, sanitizeMcpPayload } from './mcp-diagnostics.js'
|
|
6
|
+
|
|
7
|
+
export const MCP_INSTALL_TOOL_NAME = 'mcp_install'
|
|
8
|
+
export const MCP_INSTALL_APPROVE_LABEL = 'Install MCP server'
|
|
9
|
+
export const MCP_INSTALL_CANCEL_LABEL = 'Cancel'
|
|
10
|
+
export const MCP_INSTALL_CONFIRMATION_TIMEOUT_MS = 5 * 60 * 1000
|
|
11
|
+
|
|
12
|
+
const CREDENTIAL_REF = {
|
|
13
|
+
type: 'object',
|
|
14
|
+
properties: {
|
|
15
|
+
ref: { type: 'string', pattern: '^[A-Za-z_][A-Za-z0-9_]*$', description: 'Existing DSH credential reference.' },
|
|
16
|
+
prefix: { type: 'string', description: 'Optional non-secret prefix, such as Bearer .' },
|
|
17
|
+
},
|
|
18
|
+
required: ['ref'], additionalProperties: false,
|
|
19
|
+
}
|
|
20
|
+
const REF_MAP = { type: 'object', additionalProperties: CREDENTIAL_REF }
|
|
21
|
+
const RECONNECT = {
|
|
22
|
+
type: 'object',
|
|
23
|
+
properties: {
|
|
24
|
+
enabled: { type: 'boolean' },
|
|
25
|
+
initialDelayMs: { type: 'number', exclusiveMinimum: 0 },
|
|
26
|
+
maxDelayMs: { type: 'number', exclusiveMinimum: 0 },
|
|
27
|
+
maxAttempts: { type: 'integer', minimum: 0 },
|
|
28
|
+
},
|
|
29
|
+
additionalProperties: false,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const MCP_INSTALL_PARAMETERS = {
|
|
33
|
+
type: 'object',
|
|
34
|
+
properties: {
|
|
35
|
+
action: {
|
|
36
|
+
type: 'string', enum: ['preview', 'install'],
|
|
37
|
+
description: 'preview only validates and returns a redacted plan; install requests Host confirmation before any probe or write.',
|
|
38
|
+
},
|
|
39
|
+
plan: {
|
|
40
|
+
type: 'object',
|
|
41
|
+
description: 'One fresh MCP server. Use credential references for every environment/header/secret-argument value and for any URL with userinfo, query, or fragment.',
|
|
42
|
+
properties: {
|
|
43
|
+
identity: { type: 'string', description: 'Fresh stable logical identity; installation never edits or replaces an existing registration.' },
|
|
44
|
+
label: { type: 'string' },
|
|
45
|
+
transport: { type: 'string', enum: ['stdio', 'streamable-http'] },
|
|
46
|
+
serverName: { type: 'string', pattern: '^[A-Za-z0-9_-]{1,32}$' },
|
|
47
|
+
command: { type: 'string', description: 'stdio executable; required for stdio and forbidden for HTTP.' },
|
|
48
|
+
args: {
|
|
49
|
+
type: 'array', maxItems: 128,
|
|
50
|
+
description: 'Non-secret stdio arguments. Use a credential-reference object instead of a string for every sensitive argument.',
|
|
51
|
+
items: { anyOf: [{ type: 'string' }, CREDENTIAL_REF] },
|
|
52
|
+
},
|
|
53
|
+
secretArgs: { type: 'object', description: 'Credential references keyed by sensitive argument index.', additionalProperties: CREDENTIAL_REF },
|
|
54
|
+
env: { ...REF_MAP, description: 'stdio environment values as credential references; literal values are not accepted.' },
|
|
55
|
+
cwd: { type: 'string' },
|
|
56
|
+
url: {
|
|
57
|
+
description: 'Non-sensitive absolute HTTP(S) URL, or a credential reference for any URL containing userinfo, query, or fragment.',
|
|
58
|
+
anyOf: [{ type: 'string', pattern: '^https?://' }, CREDENTIAL_REF],
|
|
59
|
+
},
|
|
60
|
+
headers: { ...REF_MAP, description: 'HTTP header values as credential references; literal values are not accepted.' },
|
|
61
|
+
toolCallTimeoutMs: { type: 'number', exclusiveMinimum: 0 },
|
|
62
|
+
maxInstructionBytes: { type: 'integer', minimum: 1 },
|
|
63
|
+
reconnect: RECONNECT,
|
|
64
|
+
},
|
|
65
|
+
required: ['identity', 'transport'], additionalProperties: false,
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
required: ['action', 'plan'], additionalProperties: false,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function failure(status, code, message, extra = {}) {
|
|
72
|
+
return {
|
|
73
|
+
status, saved: false,
|
|
74
|
+
error: mcpDiagnostic(new Error(message), { code, hint: code === 'DELEGATED_CALLER' ? 'Ask the parent Orchestrator to perform MCP setup.' : undefined }),
|
|
75
|
+
...extra,
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function delegated(agent) {
|
|
80
|
+
return (agent?.session?.header?.delegationDepth ?? 0) >= 1
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function delegatedResult() {
|
|
84
|
+
return failure('unsupported', 'DELEGATED_CALLER', 'MCP installation belongs to the primary Orchestrator; delegate the setup request to the parent.', {
|
|
85
|
+
next: { action: 'delegate-to-parent-orchestrator' },
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function mcpInstallApproved(answer, questionId, approveLabel = MCP_INSTALL_APPROVE_LABEL) {
|
|
90
|
+
if (!Array.isArray(answer?.answers) || answer.answers.length !== 1) return false
|
|
91
|
+
const item = answer.answers[0]
|
|
92
|
+
return item?.id === questionId && Array.isArray(item.selected) && item.selected.length === 1 && item.selected[0] === approveLabel && !item.custom
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function assertMcpInstallStoredPlan(stored) {
|
|
96
|
+
const digest = createHash('sha256').update(JSON.stringify(stored?.plan)).digest('hex')
|
|
97
|
+
if (digest !== stored?.digest || fingerprintMcpEntry(stored?.entry) !== stored?.entryFingerprint) {
|
|
98
|
+
throw Object.assign(new Error('The stored MCP installation plan changed while confirmation was open'), { code: 'plan-changed' })
|
|
99
|
+
}
|
|
100
|
+
return true
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function cancellation(execSignal, timeoutMs, setTimer, clearTimer) {
|
|
104
|
+
const controller = new AbortController()
|
|
105
|
+
const abort = () => controller.abort(execSignal?.reason)
|
|
106
|
+
if (execSignal?.aborted) abort()
|
|
107
|
+
else execSignal?.addEventListener?.('abort', abort, { once: true })
|
|
108
|
+
const timer = setTimer(() => controller.abort(Object.assign(new Error('MCP installation confirmation timed out'), { code: 'confirmation-timeout' })), timeoutMs)
|
|
109
|
+
return {
|
|
110
|
+
signal: controller.signal,
|
|
111
|
+
dispose() {
|
|
112
|
+
clearTimer(timer)
|
|
113
|
+
execSignal?.removeEventListener?.('abort', abort)
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function firstConfirmationSettlement(ask, signal) {
|
|
119
|
+
return new Promise(resolve => {
|
|
120
|
+
let settled = false
|
|
121
|
+
const finish = value => {
|
|
122
|
+
if (settled) return
|
|
123
|
+
settled = true
|
|
124
|
+
signal.removeEventListener('abort', aborted)
|
|
125
|
+
resolve(value)
|
|
126
|
+
}
|
|
127
|
+
const aborted = () => finish({ kind: 'cancelled', cause: signal.reason })
|
|
128
|
+
if (signal.aborted) { aborted(); return }
|
|
129
|
+
signal.addEventListener('abort', aborted, { once: true })
|
|
130
|
+
Promise.resolve().then(ask).then(
|
|
131
|
+
value => finish({ kind: 'answer', value }),
|
|
132
|
+
cause => finish({ kind: 'error', cause }),
|
|
133
|
+
)
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function confirmationFailure(cause) {
|
|
138
|
+
const code = cause?.code ?? 'confirmation-failed'
|
|
139
|
+
if (code === 'NO_PROVIDER') return failure('unsupported', code, 'Host user confirmation is unavailable; MCP installation was not attempted.')
|
|
140
|
+
if (code === 'DELEGATED_CALLER') return delegatedResult()
|
|
141
|
+
if (code === 'CALLER_NOT_LIVE') return failure('owner-changed', code, 'The calling agent is no longer the exact live runtime root; MCP installation was not attempted.')
|
|
142
|
+
return failure('cancelled', code, 'MCP installation confirmation was cancelled, closed, timed out, or failed; no probe or write occurred.')
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function questionFor(stored, execution) {
|
|
146
|
+
const entry = stored.entry
|
|
147
|
+
const transport = entry.transport.kind
|
|
148
|
+
const effect = transport === 'stdio'
|
|
149
|
+
? `Process effect: run ${execution?.requestedCommand ?? entry.client.command} and perform MCP discovery.${execution?.resolvedCommand ? `\nResolved executable: ${execution.resolvedCommand}` : ''}`
|
|
150
|
+
: `Network effect: connect to ${entry.client.url ?? 'the credential-referenced endpoint'} and perform MCP discovery.`
|
|
151
|
+
const detail = [
|
|
152
|
+
`Plan digest: ${stored.digest}`,
|
|
153
|
+
effect,
|
|
154
|
+
...(transport === 'stdio' ? [`Arguments (exact argv; credential-referenced positions show [redacted]): ${JSON.stringify(stored.plan?.[0]?.entry?.client?.args ?? [])}`] : []),
|
|
155
|
+
'Default for new sessions: off. No session selection or Apply is changed.',
|
|
156
|
+
`Exact redacted plan:\n${JSON.stringify(stored.plan, null, 2)}`,
|
|
157
|
+
].join('\n')
|
|
158
|
+
return {
|
|
159
|
+
id: 'mcp-install-confirmation',
|
|
160
|
+
header: 'MCP server setup',
|
|
161
|
+
question: `Install MCP server "${stored.identity}" with exactly this plan?`,
|
|
162
|
+
detail,
|
|
163
|
+
options: [
|
|
164
|
+
{ label: MCP_INSTALL_APPROVE_LABEL, description: 'Probe first; only a successful probe may register and strictly mount this server.' },
|
|
165
|
+
{ label: MCP_INSTALL_CANCEL_LABEL, description: 'Do not probe, write credentials or registry records, or change selection.' },
|
|
166
|
+
],
|
|
167
|
+
multiSelect: false,
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function previewConflict(preview) {
|
|
172
|
+
const target = preview?.targets?.[0]
|
|
173
|
+
return target?.conflict || target?.namespaceConflict || null
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function createMcpInstallTool(dependencies) {
|
|
177
|
+
const timeoutMs = dependencies.timeoutMs ?? MCP_INSTALL_CONFIRMATION_TIMEOUT_MS
|
|
178
|
+
const setTimer = dependencies.setTimeout ?? setTimeout
|
|
179
|
+
const clearTimer = dependencies.clearTimeout ?? clearTimeout
|
|
180
|
+
return {
|
|
181
|
+
name: MCP_INSTALL_TOOL_NAME,
|
|
182
|
+
description: 'Preview or install one fresh MCP server using credential references. Install requires exact-plan Host user confirmation, probes before registration, and never changes defaults or session selection.',
|
|
183
|
+
parameters: MCP_INSTALL_PARAMETERS,
|
|
184
|
+
output: {
|
|
185
|
+
schema: { type: 'object' },
|
|
186
|
+
render: (_args, value) => [{ type: 'text', text: JSON.stringify(value) }],
|
|
187
|
+
},
|
|
188
|
+
async execute(args, exec) {
|
|
189
|
+
const agent = exec?.agent
|
|
190
|
+
const session = agent?.session
|
|
191
|
+
if (delegated(agent)) return delegatedResult()
|
|
192
|
+
if (!session || !['preview', 'install'].includes(args?.action)) return failure('validation-failed', 'invalid-config', 'mcp_install requires a live session and action preview or install.')
|
|
193
|
+
|
|
194
|
+
let mutationStarted = false
|
|
195
|
+
try {
|
|
196
|
+
const operations = dependencies.operationsFor(session)
|
|
197
|
+
const parsed = parseMcpDocument(args.plan, { surface: 'refs' })
|
|
198
|
+
if (parsed.entries.length !== 1) return failure('validation-failed', 'invalid-config', 'mcp_install accepts exactly one server plan.')
|
|
199
|
+
const normalized = parsed.entries[0]
|
|
200
|
+
const identity = normalized.entry.identity
|
|
201
|
+
const request = { action: 'preview', mode: 'add', identity, config: args.plan, refsOnly: true, surface: 'refs' }
|
|
202
|
+
const preview = await operations.preview(request)
|
|
203
|
+
if (preview.digest !== parsed.digest) throw Object.assign(new Error('MCP preview normalization was not stable'), { code: 'plan-changed' })
|
|
204
|
+
if (args.action === 'preview') return sanitizeMcpPayload({ ...preview, next: { action: 'review-plan-then-install' } })
|
|
205
|
+
if (previewConflict(preview)) {
|
|
206
|
+
return failure('conflict', 'name-conflict', `MCP identity or namespace is already registered: ${previewConflict(preview)}`, { identity, registryRevision: preview.registryRevision })
|
|
207
|
+
}
|
|
208
|
+
if (dependencies.isLiveRoot?.(agent) !== true) return confirmationFailure({ code: 'CALLER_NOT_LIVE' })
|
|
209
|
+
const questions = dependencies.userQuestions?.()
|
|
210
|
+
if (typeof questions?.ask !== 'function') return confirmationFailure({ code: 'NO_PROVIDER' })
|
|
211
|
+
if (typeof dependencies.mutate !== 'function' || typeof dependencies.captureOwnership !== 'function') {
|
|
212
|
+
return failure('unsupported', 'ownership-unavailable', 'Exact MCP mutation ownership cannot be verified; installation was not attempted.')
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const ownership = await dependencies.captureOwnership(session.id)
|
|
216
|
+
const execution = await dependencies.describeExecution?.(normalized.entry) ?? null
|
|
217
|
+
const stored = {
|
|
218
|
+
identity,
|
|
219
|
+
plan: structuredClone(preview.plan),
|
|
220
|
+
entry: structuredClone(normalized.entry),
|
|
221
|
+
digest: preview.digest,
|
|
222
|
+
entryFingerprint: fingerprintMcpEntry(normalized.entry),
|
|
223
|
+
registryRevision: preview.registryRevision,
|
|
224
|
+
}
|
|
225
|
+
const retained = dependencies.storePlan?.(stored) ?? stored
|
|
226
|
+
const question = questionFor(retained, execution)
|
|
227
|
+
const combined = cancellation(exec.signal, timeoutMs, setTimer, clearTimer)
|
|
228
|
+
let settlement
|
|
229
|
+
try {
|
|
230
|
+
settlement = await firstConfirmationSettlement(() => questions.ask({ agent, questions: [question], signal: combined.signal }), combined.signal)
|
|
231
|
+
} finally { combined.dispose() }
|
|
232
|
+
if (settlement.kind !== 'answer') return confirmationFailure(settlement.cause)
|
|
233
|
+
if (!mcpInstallApproved(settlement.value, question.id)) {
|
|
234
|
+
return failure('cancelled', 'confirmation-not-approved', 'The exact approval option was not selected alone; no probe or write occurred.')
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (dependencies.isLiveRoot?.(agent) !== true) return confirmationFailure({ code: 'CALLER_NOT_LIVE' })
|
|
238
|
+
assertMcpInstallStoredPlan(retained)
|
|
239
|
+
const currentOwner = await dependencies.captureOwnership(session.id)
|
|
240
|
+
if (!dependencies.sameOwnership(ownership, currentOwner)) {
|
|
241
|
+
return failure('owner-changed', 'owner-changed', 'MCP mutation ownership changed while confirmation was open; no probe or write occurred.', {
|
|
242
|
+
next: { action: 'preview-again' },
|
|
243
|
+
})
|
|
244
|
+
}
|
|
245
|
+
const current = await operations.preview({
|
|
246
|
+
action: 'preview', mode: 'add', identity, config: retained.entry, refsOnly: true, surface: 'refs',
|
|
247
|
+
})
|
|
248
|
+
if (current.digest !== retained.digest || current.registryRevision !== retained.registryRevision) {
|
|
249
|
+
return failure('revision-conflict', 'plan-or-revision-changed', 'The MCP plan or registry revision changed while confirmation was open; preview again.', {
|
|
250
|
+
identity, registryRevision: current.registryRevision, next: { action: 'preview-again' },
|
|
251
|
+
})
|
|
252
|
+
}
|
|
253
|
+
if (previewConflict(current)) {
|
|
254
|
+
return failure('conflict', 'name-conflict', `MCP identity or namespace changed while confirmation was open: ${previewConflict(current)}`, {
|
|
255
|
+
identity, registryRevision: current.registryRevision, next: { action: 'preview-again' },
|
|
256
|
+
})
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
mutationStarted = true
|
|
260
|
+
const outcome = await dependencies.mutate(session.id, {
|
|
261
|
+
action: 'installFromAgent', identity, config: retained.entry, refsOnly: true, surface: 'refs', targetFingerprint: null,
|
|
262
|
+
})
|
|
263
|
+
return sanitizeMcpPayload(outcome)
|
|
264
|
+
} catch (cause) {
|
|
265
|
+
const code = cause?.code ?? (mutationStarted ? 'indeterminate' : 'validation-failed')
|
|
266
|
+
if (code === 'CALLER_NOT_LIVE' || code === 'NO_PROVIDER' || code === 'DELEGATED_CALLER') return confirmationFailure(cause)
|
|
267
|
+
if (code === 'owner-changed') return failure('owner-changed', code, 'MCP mutation ownership changed; inspect actual state before retrying.', { next: { action: 'inspect-actual-state' } })
|
|
268
|
+
if (code === 'plan-changed') return failure('conflict', code, 'The approved MCP plan changed; no probe or write occurred.', { next: { action: 'preview-again' } })
|
|
269
|
+
return failure(mutationStarted ? 'indeterminate' : code === 'invalid-config' || code === 'executable-not-found' ? 'validation-failed' : 'indeterminate', code,
|
|
270
|
+
mutationStarted ? 'The confirmed MCP installation outcome is unavailable; inspect actual registry and mount state.' : 'MCP installation could not be prepared.', {
|
|
271
|
+
...(mutationStarted ? { next: { action: 'reread-registry' } } : {}),
|
|
272
|
+
})
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
}
|
|
276
|
+
}
|