@dotdrelle/wiki-manager 0.11.9 → 0.12.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 (81) hide show
  1. package/README.md +32 -7
  2. package/agents.docker-compose.yml +3 -0
  3. package/docker-compose.yml +1 -0
  4. package/package.json +3 -2
  5. package/src/activity/activityAggregator.js +109 -0
  6. package/src/activity/activityAggregator.test.js +60 -0
  7. package/src/activity/activityDeduplicator.js +50 -0
  8. package/src/activity/progressCalculator.js +61 -0
  9. package/src/activity/runSynthesis.js +15 -0
  10. package/src/agent/graph.js +65 -30
  11. package/src/agent/graph.test.js +28 -5
  12. package/src/cli/wiki-manager.js +22 -0
  13. package/src/contracts/schemas.js +249 -13
  14. package/src/contracts/schemas.test.js +145 -0
  15. package/src/core/activity.js +8 -5
  16. package/src/core/agentEvents.js +260 -23
  17. package/src/core/agentEvents.test.js +53 -0
  18. package/src/core/compose.js +6 -1
  19. package/src/core/dockerCompose.test.js +12 -0
  20. package/src/core/documentIntake.js +33 -38
  21. package/src/core/documentIntake.test.js +25 -0
  22. package/src/core/env.js +11 -1
  23. package/src/core/jobQueue.js +26 -2
  24. package/src/core/mcp.js +1 -1
  25. package/src/core/plan.js +46 -4
  26. package/src/core/plan.test.js +16 -1
  27. package/src/core/planPatch.js +64 -3
  28. package/src/core/planPatch.test.js +110 -1
  29. package/src/core/queueStore.test.js +21 -0
  30. package/src/core/runtimeLog.js +119 -0
  31. package/src/core/runtimeLog.test.js +84 -0
  32. package/src/core/wikirc.js +22 -0
  33. package/src/core/wikirc.test.js +49 -1
  34. package/src/core/workflow.js +14 -3
  35. package/src/graph/graphAggregator.js +5 -0
  36. package/src/graph/graphPatch.js +8 -0
  37. package/src/graph/graphSnapshot.js +14 -0
  38. package/src/graph/graphVisibilityPolicy.js +40 -0
  39. package/src/graph/runGraphProjector.js +87 -0
  40. package/src/graph/runGraphProjector.test.js +111 -0
  41. package/src/orchestrator/agentRegistry.js +172 -0
  42. package/src/orchestrator/agentRegistry.test.js +114 -0
  43. package/src/orchestrator/approvalPolicy.js +126 -0
  44. package/src/orchestrator/approvalPolicy.test.js +78 -0
  45. package/src/orchestrator/assignmentManager.js +80 -0
  46. package/src/orchestrator/attemptManager.js +168 -0
  47. package/src/orchestrator/attemptManager.test.js +160 -0
  48. package/src/orchestrator/budgetManager.js +127 -0
  49. package/src/orchestrator/capabilityRegistry.js +64 -0
  50. package/src/orchestrator/capabilityRegistry.test.js +59 -0
  51. package/src/orchestrator/capabilityResolver.js +151 -0
  52. package/src/orchestrator/capabilityResolver.test.js +127 -0
  53. package/src/orchestrator/dependencyResolver.js +112 -0
  54. package/src/orchestrator/dispatcher.js +302 -0
  55. package/src/orchestrator/lockManager.js +51 -0
  56. package/src/orchestrator/planIntegrator.js +268 -0
  57. package/src/orchestrator/planIntegrator.test.js +213 -0
  58. package/src/orchestrator/planValidator.js +534 -0
  59. package/src/orchestrator/planValidator.test.js +262 -0
  60. package/src/orchestrator/resultAggregator.js +248 -0
  61. package/src/orchestrator/resultAggregator.test.js +211 -0
  62. package/src/orchestrator/scheduler.js +103 -0
  63. package/src/orchestrator/scheduler.test.js +138 -0
  64. package/src/runtime/approvals.js +130 -1
  65. package/src/runtime/donna-contract.test.js +293 -30
  66. package/src/runtime/recoveryManager.js +176 -0
  67. package/src/runtime/recoveryManager.test.js +162 -0
  68. package/src/runtime/runner.e2e.test.js +98 -12
  69. package/src/runtime/runner.js +261 -227
  70. package/src/runtime/runner.test.js +249 -452
  71. package/src/runtime/server.js +137 -20
  72. package/src/runtime/server.test.js +137 -7
  73. package/src/runtime/store.js +837 -2
  74. package/src/runtime/store.test.js +245 -4
  75. package/src/runtime/supervisor.js +33 -1
  76. package/src/runtime/supervisor.test.js +47 -1
  77. package/src/shell/RightPane.tsx +8 -5
  78. package/src/shell/repl.js +4 -9
  79. package/src/shell/repl.test.js +25 -4
  80. package/src/shell/tui.tsx +1 -0
  81. package/src/shell/useSession.ts +38 -10
@@ -0,0 +1,262 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+ import {
4
+ PlanValidationError,
5
+ assertValidatedFragment,
6
+ isValidatedFragment,
7
+ validateFragment,
8
+ } from './planValidator.js';
9
+
10
+ function provider(capabilityId = 'knowledge.update', {
11
+ operations = ['ingest'],
12
+ health = 'available',
13
+ contractVersion = '1',
14
+ inputSchema = {
15
+ type: 'object',
16
+ required: ['inputs'],
17
+ additionalProperties: true,
18
+ properties: {
19
+ inputs: { type: 'array', items: { type: 'string' } },
20
+ },
21
+ },
22
+ mutation = true,
23
+ } = {}) {
24
+ return {
25
+ agentInstanceId: 'production-main',
26
+ health,
27
+ capability: {
28
+ id: capabilityId,
29
+ version: '1',
30
+ description: capabilityId,
31
+ inputSchema,
32
+ outputSchema: {},
33
+ supportedOperations: operations,
34
+ ...(mutation ? { mutationClass: 'workspace', defaultRequiresApproval: true } : {}),
35
+ },
36
+ description: {
37
+ contractVersion,
38
+ agentType: 'production',
39
+ agentInstanceId: 'production-main',
40
+ displayName: 'Production',
41
+ capabilities: [],
42
+ },
43
+ };
44
+ }
45
+
46
+ function registry(providers = [provider()]) {
47
+ return {
48
+ providersFor(capability) {
49
+ const id = String(capability).split('@')[0];
50
+ return providers.filter((item) => item.capability.id === id);
51
+ },
52
+ isCompatible(contractVersion) {
53
+ return String(contractVersion) === '1';
54
+ },
55
+ };
56
+ }
57
+
58
+ function fragment(overrides = {}) {
59
+ return {
60
+ contractVersion: '1',
61
+ agentInstanceId: 'production-main',
62
+ capability: 'knowledge.update',
63
+ summary: {
64
+ label: 'Update knowledge',
65
+ initialSynthesis: ['One source file will be ingested.'],
66
+ estimatedTasks: 1,
67
+ },
68
+ groups: [{
69
+ id: 'Ingest Group',
70
+ label: 'Ingest sources',
71
+ recommendedConcurrency: 2,
72
+ progressWeight: 1,
73
+ }],
74
+ tasks: [{
75
+ id: 'Ingest A',
76
+ label: 'Ingest A',
77
+ requiredCapability: 'knowledge.update',
78
+ operation: 'ingest',
79
+ arguments: { inputs: ['raw/untracked/a.md'] },
80
+ groupId: 'Ingest Group',
81
+ dependsOn: [],
82
+ parallelizable: true,
83
+ inputRefs: [{ type: 'file', ref: 'raw/untracked/a.md' }],
84
+ expectedOutputRefs: [{ type: 'file', ref: '.wiki/ingest-plans/a.json' }],
85
+ locks: ['workspace-write'],
86
+ requiresApproval: true,
87
+ idempotencyKey: 'idem-a',
88
+ progressWeight: 1,
89
+ }],
90
+ expectedOutputs: [{ type: 'directory', ref: 'wiki' }],
91
+ ...overrides,
92
+ };
93
+ }
94
+
95
+ function firstErrorCode(result) {
96
+ return result.errors[0]?.code;
97
+ }
98
+
99
+ test('planValidator normalizes a valid fragment and marks it as validated', () => {
100
+ const result = validateFragment(fragment(), {
101
+ registry: registry(),
102
+ run: { plannerAgentInstanceId: 'production-main' },
103
+ budgets: { maxTasks: 2, maxConcurrency: 2, maxDepth: 1, maxProgressWeight: 2 },
104
+ });
105
+
106
+ assert.equal(result.ok, true);
107
+ assert.equal(result.errors.length, 0);
108
+ assert.equal(result.normalizedFragment.tasks[0].id, 'ingest-a');
109
+ assert.equal(result.normalizedFragment.tasks[0].groupId, 'ingest-group');
110
+ assert.equal(result.normalizedFragment.groups[0].id, 'ingest-group');
111
+ assert.equal(isValidatedFragment(result.normalizedFragment), true);
112
+ assert.equal(assertValidatedFragment(result.normalizedFragment), result.normalizedFragment);
113
+ });
114
+
115
+ test('planValidator rejects invalid contract shape', () => {
116
+ const result = validateFragment(fragment({ extra: true }), { registry: registry() });
117
+ assert.equal(firstErrorCode(result), 'invalid_contract_shape');
118
+ });
119
+
120
+ test('planValidator rejects unsupported contract version', () => {
121
+ const result = validateFragment(fragment({ contractVersion: '99' }), { registry: registry() });
122
+ assert.equal(firstErrorCode(result), 'unsupported_contract_version');
123
+ });
124
+
125
+ test('planValidator rejects agent instance mismatch', () => {
126
+ const result = validateFragment(fragment(), {
127
+ registry: registry(),
128
+ run: { plannerAgentInstanceId: 'production-secondary' },
129
+ });
130
+ assert.equal(firstErrorCode(result), 'agent_instance_mismatch');
131
+ });
132
+
133
+ test('planValidator rejects estimated task mismatch', () => {
134
+ const result = validateFragment(fragment({
135
+ summary: {
136
+ label: 'Update knowledge',
137
+ initialSynthesis: [],
138
+ estimatedTasks: 2,
139
+ },
140
+ }), { registry: registry() });
141
+ assert.equal(firstErrorCode(result), 'estimated_tasks_mismatch');
142
+ });
143
+
144
+ test('planValidator rejects duplicate normalized task ids', () => {
145
+ const base = fragment();
146
+ const result = validateFragment(fragment({
147
+ summary: { ...base.summary, estimatedTasks: 2 },
148
+ tasks: [
149
+ base.tasks[0],
150
+ { ...base.tasks[0], id: 'ingest-a', label: 'Duplicate ingest' },
151
+ ],
152
+ }), { registry: registry() });
153
+ assert.equal(firstErrorCode(result), 'duplicate_task_id');
154
+ });
155
+
156
+ test('planValidator rejects unknown task groups', () => {
157
+ const base = fragment();
158
+ const result = validateFragment(fragment({
159
+ tasks: [{ ...base.tasks[0], groupId: 'missing-group' }],
160
+ }), { registry: registry() });
161
+ assert.equal(firstErrorCode(result), 'unknown_group_id');
162
+ });
163
+
164
+ test('planValidator rejects unknown dependencies', () => {
165
+ const base = fragment();
166
+ const result = validateFragment(fragment({
167
+ tasks: [{ ...base.tasks[0], dependsOn: ['missing-task'] }],
168
+ }), { registry: registry() });
169
+ assert.equal(firstErrorCode(result), 'unknown_dependency');
170
+ });
171
+
172
+ test('planValidator rejects self dependencies', () => {
173
+ const base = fragment();
174
+ const result = validateFragment(fragment({
175
+ tasks: [{ ...base.tasks[0], dependsOn: ['Ingest A'] }],
176
+ }), { registry: registry() });
177
+ assert.equal(firstErrorCode(result), 'self_dependency');
178
+ });
179
+
180
+ test('planValidator rejects dependency cycles with Kahn topological sort', () => {
181
+ const base = fragment();
182
+ const second = {
183
+ ...base.tasks[0],
184
+ id: 'Ingest B',
185
+ label: 'Ingest B',
186
+ dependsOn: ['Ingest A'],
187
+ arguments: { inputs: ['raw/untracked/b.md'] },
188
+ inputRefs: [{ type: 'file', ref: 'raw/untracked/b.md' }],
189
+ idempotencyKey: 'idem-b',
190
+ };
191
+ const first = { ...base.tasks[0], dependsOn: ['Ingest B'] };
192
+ const result = validateFragment(fragment({
193
+ summary: { ...base.summary, estimatedTasks: 2 },
194
+ tasks: [first, second],
195
+ }), { registry: registry() });
196
+ assert.equal(firstErrorCode(result), 'dependency_cycle');
197
+ });
198
+
199
+ test('planValidator rejects non concrete input refs', () => {
200
+ const base = fragment();
201
+ const result = validateFragment(fragment({
202
+ tasks: [{
203
+ ...base.tasks[0],
204
+ inputRefs: [{ type: 'file', ref: 'raw/untracked/*.md' }],
205
+ }],
206
+ }), { registry: registry() });
207
+ assert.equal(firstErrorCode(result), 'non_concrete_input_ref');
208
+ });
209
+
210
+ test('planValidator rejects unavailable capabilities', () => {
211
+ const result = validateFragment(fragment(), { registry: registry([]) });
212
+ assert.equal(firstErrorCode(result), 'capability_unavailable');
213
+ });
214
+
215
+ test('planValidator rejects unsupported operations', () => {
216
+ const result = validateFragment(fragment(), {
217
+ registry: registry([provider('knowledge.update', { operations: ['export'] })]),
218
+ });
219
+ assert.equal(firstErrorCode(result), 'operation_unsupported');
220
+ });
221
+
222
+ test('planValidator rejects arguments that do not match provider inputSchema', () => {
223
+ const base = fragment();
224
+ const result = validateFragment(fragment({
225
+ tasks: [{
226
+ ...base.tasks[0],
227
+ arguments: { files: ['raw/untracked/a.md'] },
228
+ }],
229
+ }), { registry: registry() });
230
+ assert.equal(firstErrorCode(result), 'invalid_arguments');
231
+ });
232
+
233
+ test('planValidator rejects mutating tasks without idempotency key', () => {
234
+ const base = fragment();
235
+ const result = validateFragment(fragment({
236
+ tasks: [{ ...base.tasks[0], idempotencyKey: null }],
237
+ }), { registry: registry() });
238
+ assert.equal(firstErrorCode(result), 'missing_idempotency_key');
239
+ });
240
+
241
+ test('planValidator rejects mutating tasks without locks', () => {
242
+ const base = fragment();
243
+ const result = validateFragment(fragment({
244
+ tasks: [{ ...base.tasks[0], locks: [] }],
245
+ }), { registry: registry() });
246
+ assert.equal(firstErrorCode(result), 'missing_locks');
247
+ });
248
+
249
+ test('planValidator rejects budget violations', () => {
250
+ const result = validateFragment(fragment(), {
251
+ registry: registry(),
252
+ budgets: { maxTasks: 0, maxProgressWeight: 0.5 },
253
+ });
254
+ assert.equal(firstErrorCode(result), 'budget_progress_weight_exceeded');
255
+ });
256
+
257
+ test('scheduler assertion rejects fragments not returned by validateFragment', () => {
258
+ assert.throws(
259
+ () => assertValidatedFragment(fragment()),
260
+ (error) => error instanceof PlanValidationError && error.errors[0].code === 'fragment_not_validated',
261
+ );
262
+ });
@@ -0,0 +1,248 @@
1
+ import { validateContract } from '../contracts/schemas.js';
2
+ import { parseJsonText } from '../core/activity.js';
3
+ import { createAgentEvent, dispatchAgentEvent } from '../core/agentEvents.js';
4
+ import { callMcpTool, formatMcpToolResult } from '../core/mcp.js';
5
+ import { resolve as resolveCapability } from './capabilityResolver.js';
6
+ import { integrate } from './planIntegrator.js';
7
+ import { validateFragment } from './planValidator.js';
8
+
9
+ export function createResultAggregator({
10
+ session = null,
11
+ runId = null,
12
+ store = null,
13
+ registry = null,
14
+ workspaceConfig = null,
15
+ budgets = {},
16
+ callTool = callMcpTool,
17
+ } = {}) {
18
+ return {
19
+ accept(result, options = {}) {
20
+ return accept(result, {
21
+ session,
22
+ runId,
23
+ store,
24
+ registry,
25
+ workspaceConfig,
26
+ budgets,
27
+ callTool,
28
+ ...options,
29
+ });
30
+ },
31
+ };
32
+ }
33
+
34
+ export async function accept(result, {
35
+ session,
36
+ runId = null,
37
+ task = null,
38
+ assignment = null,
39
+ store = null,
40
+ registry = null,
41
+ workspaceConfig = null,
42
+ budgets = {},
43
+ callTool = callMcpTool,
44
+ } = {}) {
45
+ if (!session) throw new Error('resultAggregator.accept requires session.');
46
+ const taskId = String(result?.taskId ?? task?.id ?? task?.step ?? '');
47
+ const ok = resultOk(result);
48
+ const status = cancelled(result) ? 'cancelled' : ok ? 'done' : 'failed';
49
+ const payload = {
50
+ runId,
51
+ taskId,
52
+ result,
53
+ assignment: assignment ? {
54
+ agentInstanceId: assignment.agentInstanceId,
55
+ serverName: assignment.serverName ?? null,
56
+ } : null,
57
+ };
58
+ persistDispatch(store, dispatchAgentEvent(session, createAgentEvent('task.result_returned', {
59
+ origin: 'result_aggregator',
60
+ runId,
61
+ taskId,
62
+ payload,
63
+ })));
64
+ persistDispatch(store, dispatchAgentEvent(session, createAgentEvent('plan_step_updated', {
65
+ origin: 'result_aggregator',
66
+ runId,
67
+ taskId,
68
+ payload: {
69
+ taskId,
70
+ status,
71
+ outputRefs: normalizeOutputRefs(result?.outputRefs ?? result?.result?.outputRefs),
72
+ result,
73
+ },
74
+ })));
75
+ persistDispatch(store, dispatchAgentEvent(session, createAgentEvent(ok ? 'task.completed' : 'task.failed', {
76
+ origin: 'result_aggregator',
77
+ runId,
78
+ taskId,
79
+ payload,
80
+ })));
81
+ const expansion = await maybeExpandPlan(result, {
82
+ session,
83
+ runId,
84
+ task,
85
+ taskId,
86
+ store,
87
+ registry,
88
+ workspaceConfig,
89
+ budgets,
90
+ callTool,
91
+ });
92
+ return { ok, status, expansion };
93
+ }
94
+
95
+ async function maybeExpandPlan(result, {
96
+ session,
97
+ runId,
98
+ task,
99
+ taskId,
100
+ store,
101
+ registry,
102
+ workspaceConfig,
103
+ budgets,
104
+ callTool,
105
+ }) {
106
+ const request = result?.planExpansionRequest ?? result?.result?.planExpansionRequest;
107
+ if (!request) return null;
108
+
109
+ const requestValidation = validateContract('planExpansionRequest', request);
110
+ if (!requestValidation.ok) {
111
+ return rejectExpansion({ session, runId, taskId, store, errors: requestValidation.errors.map((message) => ({ code: 'invalid_plan_expansion_request', message })) });
112
+ }
113
+
114
+ const effectiveRegistry = registry ?? session.capabilityRegistry;
115
+ let resolved;
116
+ try {
117
+ resolved = resolveCapability(request.capability, {
118
+ workspaceConfig: workspaceConfig ?? session.wikircConfig ?? session.wikirc?.config ?? {},
119
+ registry: effectiveRegistry,
120
+ });
121
+ } catch (err) {
122
+ return rejectExpansion({
123
+ session,
124
+ runId,
125
+ taskId,
126
+ store,
127
+ errors: [{
128
+ code: 'capability_unavailable',
129
+ message: err instanceof Error ? err.message : String(err),
130
+ details: {
131
+ capability: request.capability,
132
+ reason: err?.reason ?? null,
133
+ },
134
+ }],
135
+ });
136
+ }
137
+
138
+ const provider = providerFor(effectiveRegistry, request.capability, resolved.agentInstanceId);
139
+ const serverName = provider?.serverName ?? agentFor(session, resolved.agentInstanceId)?.serverName ?? null;
140
+ if (!serverName) {
141
+ return rejectExpansion({
142
+ session,
143
+ runId,
144
+ taskId,
145
+ store,
146
+ errors: [{ code: 'agent_server_unavailable', message: `No MCP server found for expansion agent ${resolved.agentInstanceId}.` }],
147
+ });
148
+ }
149
+
150
+ const toolName = toolNameFor(session, serverName, 'agent_plan');
151
+ const planRequest = agentPlanRequest(request, session);
152
+ const fragment = parseToolPayload(await callTool(session.mcp, serverName, toolName, planRequest));
153
+ const validation = validateFragment(fragment, {
154
+ registry: effectiveRegistry,
155
+ run: { plannerAgentInstanceId: resolved.agentInstanceId },
156
+ budgets,
157
+ });
158
+ if (!validation.ok) {
159
+ return rejectExpansion({ session, runId, taskId, store, errors: validation.errors });
160
+ }
161
+
162
+ return integrate(runId, validation.normalizedFragment, {
163
+ registry: effectiveRegistry,
164
+ budgets,
165
+ session,
166
+ store,
167
+ workspace: session.workspace ?? session._currentRunIdentity?.workspace ?? null,
168
+ insertBeforeTasks: request.insertBeforeTasks ?? [],
169
+ insertAfterTasks: request.insertAfterTasks ?? (taskId ? [taskId] : []),
170
+ enforceApprovalCoverage: true,
171
+ });
172
+ }
173
+
174
+ function rejectExpansion({ session, runId, taskId, store, errors }) {
175
+ const event = dispatchAgentEvent(session, createAgentEvent('plan.rejected', {
176
+ origin: 'result_aggregator',
177
+ runId,
178
+ taskId,
179
+ payload: {
180
+ runId,
181
+ taskId,
182
+ errors,
183
+ },
184
+ }));
185
+ persistDispatch(store, event);
186
+ return { ok: false, errors };
187
+ }
188
+
189
+ function resultOk(result) {
190
+ const status = String(result?.status ?? result?.result?.status ?? '').toLowerCase();
191
+ return result?.ok === true || ['succeeded', 'success', 'done', 'complete', 'completed'].includes(status);
192
+ }
193
+
194
+ function cancelled(result) {
195
+ return ['cancelled', 'canceled'].includes(String(result?.status ?? result?.result?.status ?? '').toLowerCase());
196
+ }
197
+
198
+ function normalizeOutputRefs(value) {
199
+ return Array.isArray(value) ? value.map((ref) => (ref && typeof ref === 'object' ? { ...ref } : String(ref))) : [];
200
+ }
201
+
202
+ function persistDispatch(store, event) {
203
+ store?.persistEvent?.(event);
204
+ }
205
+
206
+ function agentPlanRequest(request, session) {
207
+ return {
208
+ capability: request.capability,
209
+ operation: request.operation ?? undefined,
210
+ objective: request.objective ?? request.reason ?? undefined,
211
+ workspace: request.workspace ?? workspaceRequest(session),
212
+ arguments: request.arguments && typeof request.arguments === 'object' ? request.arguments : {},
213
+ constraints: request.constraints && typeof request.constraints === 'object' ? request.constraints : {},
214
+ };
215
+ }
216
+
217
+ function workspaceRequest(session) {
218
+ const workspace = session.workspace ?? session._currentRunIdentity?.workspace;
219
+ if (workspace && typeof workspace === 'object' && !Array.isArray(workspace)) return { ...workspace };
220
+ return { name: String(workspace ?? 'workspace') };
221
+ }
222
+
223
+ function providerFor(registry, capability, agentInstanceId) {
224
+ if (typeof registry?.providersFor !== 'function') return null;
225
+ return (registry.providersFor(capability) ?? [])
226
+ .find((provider) => provider.agentInstanceId === agentInstanceId) ?? null;
227
+ }
228
+
229
+ function agentFor(session, agentInstanceId) {
230
+ return [
231
+ ...(session?.agentRegistrySnapshot ?? []),
232
+ ...(session?.agents ?? []),
233
+ ].find((agent) => agent?.agentInstanceId === agentInstanceId) ?? null;
234
+ }
235
+
236
+ function toolNameFor(session, serverName, baseName) {
237
+ const tools = session.mcp?.[serverName]?.tools ?? [];
238
+ const names = tools.map((tool) => String(tool.name ?? '')).filter(Boolean);
239
+ return names.find((name) => name === baseName)
240
+ ?? names.find((name) => name === `${serverName}__${baseName}`)
241
+ ?? names.find((name) => name.endsWith(`__${baseName}`))
242
+ ?? baseName;
243
+ }
244
+
245
+ function parseToolPayload(result) {
246
+ if (result && typeof result === 'object' && !Array.isArray(result) && !Array.isArray(result.content)) return result;
247
+ return parseJsonText(formatMcpToolResult(result)) ?? {};
248
+ }