@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.
- package/README.md +32 -7
- package/agents.docker-compose.yml +3 -0
- package/docker-compose.yml +1 -0
- package/package.json +3 -2
- package/src/activity/activityAggregator.js +109 -0
- package/src/activity/activityAggregator.test.js +60 -0
- package/src/activity/activityDeduplicator.js +50 -0
- package/src/activity/progressCalculator.js +61 -0
- package/src/activity/runSynthesis.js +15 -0
- package/src/agent/graph.js +65 -30
- package/src/agent/graph.test.js +28 -5
- package/src/cli/wiki-manager.js +22 -0
- package/src/contracts/schemas.js +249 -13
- package/src/contracts/schemas.test.js +145 -0
- package/src/core/activity.js +8 -5
- package/src/core/agentEvents.js +260 -23
- package/src/core/agentEvents.test.js +53 -0
- package/src/core/compose.js +6 -1
- package/src/core/dockerCompose.test.js +12 -0
- package/src/core/documentIntake.js +33 -38
- package/src/core/documentIntake.test.js +25 -0
- package/src/core/env.js +11 -1
- package/src/core/jobQueue.js +26 -2
- package/src/core/mcp.js +1 -1
- package/src/core/plan.js +46 -4
- package/src/core/plan.test.js +16 -1
- package/src/core/planPatch.js +64 -3
- package/src/core/planPatch.test.js +110 -1
- package/src/core/queueStore.test.js +21 -0
- package/src/core/runtimeLog.js +119 -0
- package/src/core/runtimeLog.test.js +84 -0
- package/src/core/wikirc.js +22 -0
- package/src/core/wikirc.test.js +49 -1
- package/src/core/workflow.js +14 -3
- package/src/graph/graphAggregator.js +5 -0
- package/src/graph/graphPatch.js +8 -0
- package/src/graph/graphSnapshot.js +14 -0
- package/src/graph/graphVisibilityPolicy.js +40 -0
- package/src/graph/runGraphProjector.js +87 -0
- package/src/graph/runGraphProjector.test.js +111 -0
- package/src/orchestrator/agentRegistry.js +172 -0
- package/src/orchestrator/agentRegistry.test.js +114 -0
- package/src/orchestrator/approvalPolicy.js +126 -0
- package/src/orchestrator/approvalPolicy.test.js +78 -0
- package/src/orchestrator/assignmentManager.js +80 -0
- package/src/orchestrator/attemptManager.js +168 -0
- package/src/orchestrator/attemptManager.test.js +160 -0
- package/src/orchestrator/budgetManager.js +127 -0
- package/src/orchestrator/capabilityRegistry.js +64 -0
- package/src/orchestrator/capabilityRegistry.test.js +59 -0
- package/src/orchestrator/capabilityResolver.js +151 -0
- package/src/orchestrator/capabilityResolver.test.js +127 -0
- package/src/orchestrator/dependencyResolver.js +112 -0
- package/src/orchestrator/dispatcher.js +302 -0
- package/src/orchestrator/lockManager.js +51 -0
- package/src/orchestrator/planIntegrator.js +268 -0
- package/src/orchestrator/planIntegrator.test.js +213 -0
- package/src/orchestrator/planValidator.js +534 -0
- package/src/orchestrator/planValidator.test.js +262 -0
- package/src/orchestrator/resultAggregator.js +248 -0
- package/src/orchestrator/resultAggregator.test.js +211 -0
- package/src/orchestrator/scheduler.js +103 -0
- package/src/orchestrator/scheduler.test.js +138 -0
- package/src/runtime/approvals.js +130 -1
- package/src/runtime/donna-contract.test.js +293 -30
- package/src/runtime/recoveryManager.js +176 -0
- package/src/runtime/recoveryManager.test.js +162 -0
- package/src/runtime/runner.e2e.test.js +98 -12
- package/src/runtime/runner.js +261 -227
- package/src/runtime/runner.test.js +249 -452
- package/src/runtime/server.js +137 -20
- package/src/runtime/server.test.js +137 -7
- package/src/runtime/store.js +837 -2
- package/src/runtime/store.test.js +245 -4
- package/src/runtime/supervisor.js +33 -1
- package/src/runtime/supervisor.test.js +47 -1
- package/src/shell/RightPane.tsx +8 -5
- package/src/shell/repl.js +4 -9
- package/src/shell/repl.test.js +25 -4
- package/src/shell/tui.tsx +1 -0
- package/src/shell/useSession.ts +38 -10
package/src/agent/graph.test.js
CHANGED
|
@@ -300,7 +300,7 @@ test('agent graph waits for tool-level approval configured on endpoint', async (
|
|
|
300
300
|
}
|
|
301
301
|
});
|
|
302
302
|
|
|
303
|
-
test('agent graph accepts structured wiki plan steps
|
|
303
|
+
test('agent graph accepts structured wiki plan steps without selecting MCP executors implicitly', async () => {
|
|
304
304
|
let calls = 0;
|
|
305
305
|
const session = sessionBase({
|
|
306
306
|
mcp: {
|
|
@@ -337,8 +337,20 @@ test('agent graph accepts structured wiki plan steps and selects MCP executors',
|
|
|
337
337
|
name: 'wiki__plan_set',
|
|
338
338
|
arguments: JSON.stringify({
|
|
339
339
|
steps: [
|
|
340
|
-
{
|
|
341
|
-
|
|
340
|
+
{
|
|
341
|
+
id: 'cme-export',
|
|
342
|
+
description: 'Export CME pages',
|
|
343
|
+
requiredCapability: 'external-source.export',
|
|
344
|
+
executor: 'cme.cme_export_run',
|
|
345
|
+
executorQuery: { capability: 'legacy export' },
|
|
346
|
+
outputRefs: ['raw/untracked'],
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
id: 'build',
|
|
350
|
+
description: 'Run production build',
|
|
351
|
+
requiredCapability: 'knowledge.pipeline',
|
|
352
|
+
dependsOn: ['cme-export'],
|
|
353
|
+
},
|
|
342
354
|
],
|
|
343
355
|
}),
|
|
344
356
|
},
|
|
@@ -359,8 +371,19 @@ test('agent graph accepts structured wiki plan steps and selects MCP executors',
|
|
|
359
371
|
|
|
360
372
|
assert.equal(result.response, 'Plan ready.');
|
|
361
373
|
assert.deepEqual(session.headlessPlan.map((step) => step.id), ['cme-export', 'build']);
|
|
362
|
-
assert.
|
|
363
|
-
assert.equal(session.headlessPlan[
|
|
374
|
+
assert.deepEqual(session.headlessPlan.map((step) => step.requiredCapability), ['external-source.export', 'knowledge.pipeline']);
|
|
375
|
+
assert.equal(session.headlessPlan[0].executor, null);
|
|
376
|
+
assert.equal(session.headlessPlan[0].executorQuery, null);
|
|
377
|
+
assert.equal(session.headlessPlan[1].executor, null);
|
|
364
378
|
assert.deepEqual(session.headlessPlan[1].dependsOn, ['cme-export']);
|
|
365
379
|
assert.deepEqual(session.headlessPlan[0].outputRefs, ['raw/untracked']);
|
|
366
380
|
});
|
|
381
|
+
|
|
382
|
+
test('buildAgentSystemPrompt forbids inventing slash commands or arguments', () => {
|
|
383
|
+
const prompt = buildAgentSystemPrompt({ session: sessionBase({ commands: ['status', 'services'] }) });
|
|
384
|
+
assert.match(prompt, /Available primitives: \/status, \/services\./);
|
|
385
|
+
assert.match(prompt, /Do not invent command names, subcommands, or arguments/);
|
|
386
|
+
assert.doesNotMatch(prompt, /\/restart serve/);
|
|
387
|
+
assert.doesNotMatch(prompt, /executorQuery/);
|
|
388
|
+
assert.doesNotMatch(prompt, /executor:"/);
|
|
389
|
+
});
|
package/src/cli/wiki-manager.js
CHANGED
|
@@ -294,6 +294,7 @@ async function runRuntime(argv, agent) {
|
|
|
294
294
|
}
|
|
295
295
|
const { defaultRuntimeStateDir, openRuntimeStore, RECOVERABLE_QUEUE_STATUSES } = await import('../runtime/store.js');
|
|
296
296
|
const { startRuntimeServer } = await import('../runtime/server.js');
|
|
297
|
+
const { recoverActiveRuns } = await import('../runtime/recoveryManager.js');
|
|
297
298
|
const { emitRuntimeLog, startActivitySupervisor } = await import('../runtime/supervisor.js');
|
|
298
299
|
const { resolveRuntimeAuthToken } = await import('../runtime/auth.js');
|
|
299
300
|
const { createSqliteQueueStore } = await import('../runtime/queueStore.js');
|
|
@@ -484,6 +485,27 @@ async function runRuntime(argv, agent) {
|
|
|
484
485
|
reason: `MCP unavailable: ${gaps.join(', ')}`,
|
|
485
486
|
};
|
|
486
487
|
}
|
|
488
|
+
const taskRecovery = await recoverActiveRuns({
|
|
489
|
+
store,
|
|
490
|
+
session: context.session,
|
|
491
|
+
workspace: context.workspace,
|
|
492
|
+
callTool: callMcpTool,
|
|
493
|
+
});
|
|
494
|
+
if (!taskRecovery.ok) {
|
|
495
|
+
const interrupted = store.interruptRuns({ workspace: context.workspace });
|
|
496
|
+
return {
|
|
497
|
+
workspace: context.workspace ?? workspace ?? null,
|
|
498
|
+
resumed: false,
|
|
499
|
+
interrupted,
|
|
500
|
+
reason: `Task recovery failed: ${taskRecovery.errors.map((item) => `${item.taskId}: ${item.error}`).join('; ')}`,
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
if (taskRecovery.recovered.length > 0 || taskRecovery.rescheduled.length > 0) {
|
|
504
|
+
emitRuntimeLog(
|
|
505
|
+
context.session,
|
|
506
|
+
`runtime: recovery attached ${taskRecovery.recovered.length} job(s), requeued ${taskRecovery.rescheduled.length} task(s)`,
|
|
507
|
+
);
|
|
508
|
+
}
|
|
487
509
|
const recoverableRuns = store.listRecoverableRuns({ workspace: context.workspace });
|
|
488
510
|
const runningRun = recoverableRuns.find((run) => run.status === 'running');
|
|
489
511
|
const activeActivities = activeNonTerminalActivities(context.session);
|
package/src/contracts/schemas.js
CHANGED
|
@@ -3,6 +3,7 @@ const STATUS_VALUES = [
|
|
|
3
3
|
'queued',
|
|
4
4
|
'running',
|
|
5
5
|
'waiting',
|
|
6
|
+
'waiting_approval',
|
|
6
7
|
'pending_approval',
|
|
7
8
|
'done',
|
|
8
9
|
'failed',
|
|
@@ -18,6 +19,7 @@ const STATUS_VALUES = [
|
|
|
18
19
|
|
|
19
20
|
const nullableString = { type: ['string', 'null'] };
|
|
20
21
|
const nullableObject = { type: ['object', 'null'], additionalProperties: true };
|
|
22
|
+
const stringArraySchema = { type: 'array', items: { type: 'string' } };
|
|
21
23
|
const outputReferenceSchema = {
|
|
22
24
|
$id: 'https://dotdrelle.dev/wiki-manager/contracts/output-reference/v1',
|
|
23
25
|
title: 'OutputReference',
|
|
@@ -38,6 +40,241 @@ const outputReferenceSchema = {
|
|
|
38
40
|
],
|
|
39
41
|
};
|
|
40
42
|
|
|
43
|
+
const retryPolicySchema = {
|
|
44
|
+
$id: 'https://dotdrelle.dev/wiki-manager/contracts/retry-policy/v1',
|
|
45
|
+
title: 'RetryPolicy',
|
|
46
|
+
schemaVersion: '1',
|
|
47
|
+
type: 'object',
|
|
48
|
+
required: ['maxAttempts', 'retryableErrors', 'allowAgentFallback'],
|
|
49
|
+
additionalProperties: false,
|
|
50
|
+
properties: {
|
|
51
|
+
maxAttempts: { type: 'integer', minimum: 1 },
|
|
52
|
+
retryableErrors: stringArraySchema,
|
|
53
|
+
allowAgentFallback: { type: 'boolean' },
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const jsonSchemaSchema = { type: 'object', additionalProperties: true };
|
|
58
|
+
const capabilityDescriptionSchema = {
|
|
59
|
+
$id: 'https://dotdrelle.dev/wiki-manager/contracts/capability-description/v1',
|
|
60
|
+
title: 'CapabilityDescription',
|
|
61
|
+
schemaVersion: '1',
|
|
62
|
+
type: 'object',
|
|
63
|
+
required: ['id', 'version', 'description', 'inputSchema', 'outputSchema', 'supportedOperations'],
|
|
64
|
+
additionalProperties: true,
|
|
65
|
+
properties: {
|
|
66
|
+
id: { type: 'string', minLength: 1 },
|
|
67
|
+
version: { type: 'string', minLength: 1 },
|
|
68
|
+
description: { type: 'string' },
|
|
69
|
+
inputSchema: jsonSchemaSchema,
|
|
70
|
+
outputSchema: jsonSchemaSchema,
|
|
71
|
+
supportedOperations: stringArraySchema,
|
|
72
|
+
mutationClass: { type: 'string' },
|
|
73
|
+
defaultRequiresApproval: { type: 'boolean' },
|
|
74
|
+
estimatedCost: {
|
|
75
|
+
type: 'object',
|
|
76
|
+
additionalProperties: false,
|
|
77
|
+
properties: {
|
|
78
|
+
llmCalls: { type: 'number', minimum: 0 },
|
|
79
|
+
tokenRange: { type: 'array', items: { type: 'number' } },
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const agentDescriptionSchema = {
|
|
86
|
+
$id: 'https://dotdrelle.dev/wiki-manager/contracts/agent-description/v1',
|
|
87
|
+
title: 'AgentDescription',
|
|
88
|
+
schemaVersion: '1',
|
|
89
|
+
type: 'object',
|
|
90
|
+
required: ['contractVersion', 'agentType', 'agentInstanceId', 'displayName', 'capabilities', 'orchestration', 'limits', 'health'],
|
|
91
|
+
additionalProperties: true,
|
|
92
|
+
properties: {
|
|
93
|
+
contractVersion: { type: 'string', minLength: 1 },
|
|
94
|
+
agentType: { type: 'string', minLength: 1 },
|
|
95
|
+
agentInstanceId: { type: 'string', minLength: 1 },
|
|
96
|
+
displayName: { type: 'string', minLength: 1 },
|
|
97
|
+
capabilities: { type: 'array', items: capabilityDescriptionSchema },
|
|
98
|
+
orchestration: {
|
|
99
|
+
type: 'object',
|
|
100
|
+
required: ['canPlan', 'canExpandPlan', 'canExecute', 'canCancel', 'canResume', 'supportsIdempotency', 'supportsParallelWorkers'],
|
|
101
|
+
additionalProperties: true,
|
|
102
|
+
properties: {
|
|
103
|
+
canPlan: { type: 'boolean' },
|
|
104
|
+
canExpandPlan: { type: 'boolean' },
|
|
105
|
+
canExecute: { type: 'boolean' },
|
|
106
|
+
canCancel: { type: 'boolean' },
|
|
107
|
+
canResume: { type: 'boolean' },
|
|
108
|
+
supportsIdempotency: { type: 'boolean' },
|
|
109
|
+
supportsParallelWorkers: { type: 'boolean' },
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
limits: {
|
|
113
|
+
type: 'object',
|
|
114
|
+
required: ['recommendedConcurrency', 'maxConcurrency'],
|
|
115
|
+
additionalProperties: true,
|
|
116
|
+
properties: {
|
|
117
|
+
recommendedConcurrency: { type: 'number', minimum: 0 },
|
|
118
|
+
maxConcurrency: { type: 'number', minimum: 0 },
|
|
119
|
+
maxTasksPerPlan: { type: 'number', minimum: 0 },
|
|
120
|
+
maxTaskDurationMs: { type: 'number', minimum: 0 },
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
health: {
|
|
124
|
+
type: 'object',
|
|
125
|
+
required: ['status'],
|
|
126
|
+
additionalProperties: false,
|
|
127
|
+
properties: {
|
|
128
|
+
status: { type: 'string', enum: ['available', 'degraded', 'unavailable'] },
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const taskGroupSchema = {
|
|
135
|
+
$id: 'https://dotdrelle.dev/wiki-manager/contracts/task-group/v1',
|
|
136
|
+
title: 'TaskGroup',
|
|
137
|
+
schemaVersion: '1',
|
|
138
|
+
type: 'object',
|
|
139
|
+
required: ['id', 'label'],
|
|
140
|
+
additionalProperties: false,
|
|
141
|
+
properties: {
|
|
142
|
+
id: { type: 'string', minLength: 1 },
|
|
143
|
+
label: { type: 'string', minLength: 1 },
|
|
144
|
+
recommendedConcurrency: { type: 'integer', minimum: 1 },
|
|
145
|
+
progressWeight: { type: 'number', minimum: 0 },
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const plannedTaskSchema = {
|
|
150
|
+
$id: 'https://dotdrelle.dev/wiki-manager/contracts/planned-task/v1',
|
|
151
|
+
title: 'PlannedTask',
|
|
152
|
+
schemaVersion: '1',
|
|
153
|
+
type: 'object',
|
|
154
|
+
required: [
|
|
155
|
+
'id',
|
|
156
|
+
'label',
|
|
157
|
+
'requiredCapability',
|
|
158
|
+
'operation',
|
|
159
|
+
'dependsOn',
|
|
160
|
+
'parallelizable',
|
|
161
|
+
'inputRefs',
|
|
162
|
+
'locks',
|
|
163
|
+
'requiresApproval',
|
|
164
|
+
'idempotencyKey',
|
|
165
|
+
'progressWeight',
|
|
166
|
+
],
|
|
167
|
+
additionalProperties: false,
|
|
168
|
+
properties: {
|
|
169
|
+
id: { type: 'string', minLength: 1 },
|
|
170
|
+
label: { type: 'string', minLength: 1 },
|
|
171
|
+
requiredCapability: { type: 'string', minLength: 1 },
|
|
172
|
+
operation: { type: 'string', minLength: 1 },
|
|
173
|
+
arguments: { type: 'object', additionalProperties: true },
|
|
174
|
+
groupId: { type: 'string' },
|
|
175
|
+
dependsOn: stringArraySchema,
|
|
176
|
+
dependsOnGroup: { type: 'string' },
|
|
177
|
+
barrier: { type: 'boolean' },
|
|
178
|
+
parallelizable: { type: 'boolean' },
|
|
179
|
+
recommendedConcurrency: { type: 'integer', minimum: 1 },
|
|
180
|
+
inputRefs: { type: 'array', items: outputReferenceSchema },
|
|
181
|
+
expectedOutputRefs: { type: 'array', items: outputReferenceSchema },
|
|
182
|
+
locks: stringArraySchema,
|
|
183
|
+
requiresApproval: { type: 'boolean' },
|
|
184
|
+
approvalClass: { type: 'string' },
|
|
185
|
+
approvalSummary: { type: 'string' },
|
|
186
|
+
idempotencyKey: { type: ['string', 'null'] },
|
|
187
|
+
progressWeight: { type: 'number', minimum: 0 },
|
|
188
|
+
priority: { type: 'number' },
|
|
189
|
+
retryPolicy: retryPolicySchema,
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const taskGraphFragmentSchema = {
|
|
194
|
+
$id: 'https://dotdrelle.dev/wiki-manager/contracts/task-graph-fragment/v1',
|
|
195
|
+
title: 'TaskGraphFragment',
|
|
196
|
+
schemaVersion: '1',
|
|
197
|
+
type: 'object',
|
|
198
|
+
required: ['contractVersion', 'agentInstanceId', 'capability', 'summary', 'groups', 'tasks'],
|
|
199
|
+
additionalProperties: false,
|
|
200
|
+
properties: {
|
|
201
|
+
contractVersion: { type: 'string', minLength: 1 },
|
|
202
|
+
agentInstanceId: { type: 'string', minLength: 1 },
|
|
203
|
+
capability: { type: 'string', minLength: 1 },
|
|
204
|
+
summary: {
|
|
205
|
+
type: 'object',
|
|
206
|
+
required: ['label', 'initialSynthesis', 'estimatedTasks'],
|
|
207
|
+
additionalProperties: false,
|
|
208
|
+
properties: {
|
|
209
|
+
label: { type: 'string', minLength: 1 },
|
|
210
|
+
initialSynthesis: stringArraySchema,
|
|
211
|
+
estimatedTasks: { type: 'integer', minimum: 0 },
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
groups: { type: 'array', items: taskGroupSchema },
|
|
215
|
+
tasks: { type: 'array', items: plannedTaskSchema },
|
|
216
|
+
expectedOutputs: { type: 'array', items: outputReferenceSchema },
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const planExpansionRequestSchema = {
|
|
221
|
+
$id: 'https://dotdrelle.dev/wiki-manager/contracts/plan-expansion-request/v1',
|
|
222
|
+
title: 'PlanExpansionRequest',
|
|
223
|
+
schemaVersion: '1',
|
|
224
|
+
type: 'object',
|
|
225
|
+
required: ['capability'],
|
|
226
|
+
additionalProperties: true,
|
|
227
|
+
properties: {
|
|
228
|
+
capability: { type: 'string', minLength: 1 },
|
|
229
|
+
operation: nullableString,
|
|
230
|
+
objective: nullableString,
|
|
231
|
+
reason: nullableString,
|
|
232
|
+
arguments: { type: 'object', additionalProperties: true },
|
|
233
|
+
workspace: { type: 'object', additionalProperties: true },
|
|
234
|
+
constraints: {
|
|
235
|
+
type: 'object',
|
|
236
|
+
additionalProperties: true,
|
|
237
|
+
properties: {
|
|
238
|
+
maxTasks: { type: 'integer', minimum: 1 },
|
|
239
|
+
maxConcurrency: { type: 'integer', minimum: 1 },
|
|
240
|
+
maxDepth: { type: 'integer', minimum: 1 },
|
|
241
|
+
requireApprovalForMutations: { type: 'boolean' },
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
insertBeforeTasks: stringArraySchema,
|
|
245
|
+
insertAfterTasks: stringArraySchema,
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// Shared by planStepSchema and patchTaskSchema, which differ only in their
|
|
250
|
+
// `id`/`description` requiredness — every other field is identical.
|
|
251
|
+
const taskFieldsSchema = {
|
|
252
|
+
label: { type: 'string' },
|
|
253
|
+
requiredCapability: { type: ['string', 'null'] },
|
|
254
|
+
operation: { type: ['string', 'null'] },
|
|
255
|
+
arguments: { type: 'object', additionalProperties: true },
|
|
256
|
+
groupId: nullableString,
|
|
257
|
+
status: { type: 'string' },
|
|
258
|
+
dependsOn: { type: 'array', items: { type: 'string' } },
|
|
259
|
+
dependsOnGroup: nullableString,
|
|
260
|
+
barrier: { type: 'boolean' },
|
|
261
|
+
parallelizable: { type: 'boolean' },
|
|
262
|
+
recommendedConcurrency: { type: 'integer', minimum: 1 },
|
|
263
|
+
executor: nullableString,
|
|
264
|
+
executorQuery: nullableObject,
|
|
265
|
+
inputRefs: { type: 'array', items: outputReferenceSchema },
|
|
266
|
+
expectedOutputRefs: { type: 'array', items: outputReferenceSchema },
|
|
267
|
+
locks: stringArraySchema,
|
|
268
|
+
requiresApproval: { type: 'boolean' },
|
|
269
|
+
approvalClass: nullableString,
|
|
270
|
+
approvalSummary: nullableString,
|
|
271
|
+
idempotencyKey: { type: ['string', 'null'] },
|
|
272
|
+
progressWeight: { type: 'number', minimum: 0 },
|
|
273
|
+
priority: { type: 'number' },
|
|
274
|
+
retryPolicy: retryPolicySchema,
|
|
275
|
+
outputRefs: { type: 'array', items: outputReferenceSchema },
|
|
276
|
+
};
|
|
277
|
+
|
|
41
278
|
const planStepSchema = {
|
|
42
279
|
type: 'object',
|
|
43
280
|
required: ['description', 'status', 'dependsOn', 'outputRefs'],
|
|
@@ -46,11 +283,7 @@ const planStepSchema = {
|
|
|
46
283
|
step: { type: 'number' },
|
|
47
284
|
id: nullableString,
|
|
48
285
|
description: { type: 'string' },
|
|
49
|
-
|
|
50
|
-
dependsOn: { type: 'array', items: { type: 'string' } },
|
|
51
|
-
executor: nullableString,
|
|
52
|
-
executorQuery: nullableObject,
|
|
53
|
-
outputRefs: { type: 'array', items: outputReferenceSchema },
|
|
286
|
+
...taskFieldsSchema,
|
|
54
287
|
},
|
|
55
288
|
};
|
|
56
289
|
|
|
@@ -61,11 +294,7 @@ const patchTaskSchema = {
|
|
|
61
294
|
properties: {
|
|
62
295
|
id: { type: 'string' },
|
|
63
296
|
description: { type: 'string' },
|
|
64
|
-
|
|
65
|
-
dependsOn: { type: 'array', items: { type: 'string' } },
|
|
66
|
-
executor: nullableString,
|
|
67
|
-
executorQuery: nullableObject,
|
|
68
|
-
outputRefs: { type: 'array', items: outputReferenceSchema },
|
|
297
|
+
...taskFieldsSchema,
|
|
69
298
|
},
|
|
70
299
|
};
|
|
71
300
|
|
|
@@ -145,7 +374,7 @@ export const contractSchemas = {
|
|
|
145
374
|
message: { type: 'string' },
|
|
146
375
|
prompt: { type: 'string' },
|
|
147
376
|
request: { type: 'string' },
|
|
148
|
-
intent: { type: 'string', enum: ['observe', 'converse', 'mutate', 'enqueue'] },
|
|
377
|
+
intent: { type: 'string', enum: ['observe', 'converse', 'mutate', 'enqueue', 'approve', 'modify_run', 'enqueue_run', 'cancel', 'ambiguous'] },
|
|
149
378
|
workspace: nullableString,
|
|
150
379
|
patchId: { type: 'string' },
|
|
151
380
|
id: { type: 'string' },
|
|
@@ -157,7 +386,7 @@ export const contractSchemas = {
|
|
|
157
386
|
title: 'StructuredPlan',
|
|
158
387
|
schemaVersion: '1',
|
|
159
388
|
type: 'array',
|
|
160
|
-
items: planStepSchema,
|
|
389
|
+
items: { anyOf: [planStepSchema, plannedTaskSchema] },
|
|
161
390
|
},
|
|
162
391
|
planPatch: {
|
|
163
392
|
$id: 'https://dotdrelle.dev/wiki-manager/contracts/plan-patch/v1',
|
|
@@ -179,7 +408,7 @@ export const contractSchemas = {
|
|
|
179
408
|
additionalProperties: true,
|
|
180
409
|
properties: {
|
|
181
410
|
op: { type: 'string', enum: ['add_task', 'add_dependency', 'remove_dependency', 'cancel_task', 'replace_executor', 'request_approval'] },
|
|
182
|
-
task: patchTaskSchema,
|
|
411
|
+
task: { anyOf: [patchTaskSchema, plannedTaskSchema] },
|
|
183
412
|
taskId: { type: 'string' },
|
|
184
413
|
targetTaskId: { type: 'string' },
|
|
185
414
|
dependencyId: { type: 'string' },
|
|
@@ -193,6 +422,13 @@ export const contractSchemas = {
|
|
|
193
422
|
},
|
|
194
423
|
},
|
|
195
424
|
outputReference: outputReferenceSchema,
|
|
425
|
+
capabilityDescription: capabilityDescriptionSchema,
|
|
426
|
+
agentDescription: agentDescriptionSchema,
|
|
427
|
+
retryPolicy: retryPolicySchema,
|
|
428
|
+
taskGroup: taskGroupSchema,
|
|
429
|
+
plannedTask: plannedTaskSchema,
|
|
430
|
+
taskGraphFragment: taskGraphFragmentSchema,
|
|
431
|
+
planExpansionRequest: planExpansionRequestSchema,
|
|
196
432
|
};
|
|
197
433
|
|
|
198
434
|
export function validateContract(name, value) {
|
|
@@ -91,3 +91,148 @@ test('plan and patch contracts cover structured dependencies and output refs', (
|
|
|
91
91
|
assert.equal(validateContract('plan', plan).ok, true);
|
|
92
92
|
assert.equal(validateContract('planPatch', patch).ok, true);
|
|
93
93
|
});
|
|
94
|
+
|
|
95
|
+
test('task graph fragment contract carries planned tasks and groups round-trip', () => {
|
|
96
|
+
const fragment = {
|
|
97
|
+
contractVersion: '1',
|
|
98
|
+
agentInstanceId: 'production-main',
|
|
99
|
+
capability: 'knowledge.update',
|
|
100
|
+
summary: {
|
|
101
|
+
label: 'Update knowledge',
|
|
102
|
+
initialSynthesis: ['Two files will be ingested.'],
|
|
103
|
+
estimatedTasks: 1,
|
|
104
|
+
},
|
|
105
|
+
groups: [{
|
|
106
|
+
id: 'ingest',
|
|
107
|
+
label: 'Ingest sources',
|
|
108
|
+
recommendedConcurrency: 2,
|
|
109
|
+
progressWeight: 2,
|
|
110
|
+
}],
|
|
111
|
+
tasks: [{
|
|
112
|
+
id: 'ingest-a',
|
|
113
|
+
label: 'Ingest source A',
|
|
114
|
+
requiredCapability: 'knowledge.update',
|
|
115
|
+
operation: 'ingest',
|
|
116
|
+
arguments: { files: ['raw/untracked/a.md'] },
|
|
117
|
+
groupId: 'ingest',
|
|
118
|
+
dependsOn: [],
|
|
119
|
+
parallelizable: true,
|
|
120
|
+
recommendedConcurrency: 2,
|
|
121
|
+
inputRefs: [{ type: 'file', ref: 'raw/untracked/a.md', label: 'A' }],
|
|
122
|
+
expectedOutputRefs: ['wiki/a.md'],
|
|
123
|
+
locks: ['workspace:wiki'],
|
|
124
|
+
requiresApproval: false,
|
|
125
|
+
idempotencyKey: null,
|
|
126
|
+
progressWeight: 1,
|
|
127
|
+
priority: 10,
|
|
128
|
+
retryPolicy: {
|
|
129
|
+
maxAttempts: 2,
|
|
130
|
+
retryableErrors: ['timeout'],
|
|
131
|
+
allowAgentFallback: false,
|
|
132
|
+
},
|
|
133
|
+
}],
|
|
134
|
+
expectedOutputs: ['wiki/a.md'],
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const roundTrip = JSON.parse(JSON.stringify(fragment));
|
|
138
|
+
assert.deepEqual(roundTrip, fragment);
|
|
139
|
+
assert.equal(validateContract('taskGraphFragment', roundTrip).ok, true);
|
|
140
|
+
assert.equal(validateContract('plannedTask', roundTrip.tasks[0]).ok, true);
|
|
141
|
+
assert.equal(validateContract('taskGroup', roundTrip.groups[0]).ok, true);
|
|
142
|
+
assert.equal(validateContract('retryPolicy', roundTrip.tasks[0].retryPolicy).ok, true);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('strict planned task contract rejects invalid fields and types', () => {
|
|
146
|
+
const invalid = {
|
|
147
|
+
id: 'bad',
|
|
148
|
+
label: 'Bad task',
|
|
149
|
+
requiredCapability: 'knowledge.update',
|
|
150
|
+
operation: 'ingest',
|
|
151
|
+
dependsOn: [],
|
|
152
|
+
parallelizable: false,
|
|
153
|
+
inputRefs: [],
|
|
154
|
+
locks: [],
|
|
155
|
+
requiresApproval: false,
|
|
156
|
+
idempotencyKey: null,
|
|
157
|
+
progressWeight: 1,
|
|
158
|
+
executor: 'legacy-tool',
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const result = validateContract('plannedTask', invalid);
|
|
162
|
+
assert.equal(result.ok, false);
|
|
163
|
+
assert.ok(result.errors.some((error) => error.includes('executor is not allowed')));
|
|
164
|
+
assert.throws(() => assertContract('plannedTask', invalid), /executor is not allowed/);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('plan and plan patch contracts accept new planned task fields without rejecting legacy tasks', () => {
|
|
168
|
+
const plannedTask = {
|
|
169
|
+
id: 'publish',
|
|
170
|
+
label: 'Publish deliverable',
|
|
171
|
+
requiredCapability: 'document.publish',
|
|
172
|
+
operation: 'publish',
|
|
173
|
+
arguments: { target: 'deliverables/report.md' },
|
|
174
|
+
dependsOn: ['build'],
|
|
175
|
+
parallelizable: false,
|
|
176
|
+
inputRefs: ['deliverables/report.md'],
|
|
177
|
+
locks: ['deliverable:report.md'],
|
|
178
|
+
requiresApproval: true,
|
|
179
|
+
approvalClass: 'mutation',
|
|
180
|
+
approvalSummary: 'Publish report',
|
|
181
|
+
idempotencyKey: 'idem-1',
|
|
182
|
+
progressWeight: 1,
|
|
183
|
+
};
|
|
184
|
+
const legacyTask = {
|
|
185
|
+
step: 1,
|
|
186
|
+
id: 'build',
|
|
187
|
+
description: 'Build report',
|
|
188
|
+
status: 'done',
|
|
189
|
+
dependsOn: [],
|
|
190
|
+
outputRefs: ['deliverables/report.md'],
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
assert.equal(validateContract('plan', [legacyTask, plannedTask]).ok, true);
|
|
194
|
+
assert.equal(validateContract('planPatch', {
|
|
195
|
+
basePlanRevision: 1,
|
|
196
|
+
operations: [{ op: 'add_task', task: plannedTask }],
|
|
197
|
+
}).ok, true);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test('agent description contract validates orchestrable agent capabilities', () => {
|
|
201
|
+
const description = {
|
|
202
|
+
contractVersion: '1',
|
|
203
|
+
agentType: 'production',
|
|
204
|
+
agentInstanceId: 'production-main',
|
|
205
|
+
displayName: 'Production',
|
|
206
|
+
capabilities: [{
|
|
207
|
+
id: 'knowledge.update',
|
|
208
|
+
version: '1',
|
|
209
|
+
description: 'Update wiki knowledge',
|
|
210
|
+
inputSchema: { type: 'object' },
|
|
211
|
+
outputSchema: { type: 'object' },
|
|
212
|
+
supportedOperations: ['ingest', 'pipeline'],
|
|
213
|
+
mutationClass: 'workspace',
|
|
214
|
+
defaultRequiresApproval: true,
|
|
215
|
+
estimatedCost: { llmCalls: 2, tokenRange: [1000, 3000] },
|
|
216
|
+
}],
|
|
217
|
+
orchestration: {
|
|
218
|
+
canPlan: true,
|
|
219
|
+
canExpandPlan: false,
|
|
220
|
+
canExecute: true,
|
|
221
|
+
canCancel: true,
|
|
222
|
+
canResume: false,
|
|
223
|
+
supportsIdempotency: false,
|
|
224
|
+
supportsParallelWorkers: true,
|
|
225
|
+
},
|
|
226
|
+
limits: {
|
|
227
|
+
recommendedConcurrency: 2,
|
|
228
|
+
maxConcurrency: 4,
|
|
229
|
+
maxTasksPerPlan: 50,
|
|
230
|
+
maxTaskDurationMs: 600000,
|
|
231
|
+
},
|
|
232
|
+
health: { status: 'available' },
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
assert.equal(validateContract('agentDescription', description).ok, true);
|
|
236
|
+
assert.equal(validateContract('capabilityDescription', description.capabilities[0]).ok, true);
|
|
237
|
+
assert.equal(validateContract('agentDescription', { ...description, health: { status: 'offline' } }).ok, false);
|
|
238
|
+
});
|
package/src/core/activity.js
CHANGED
|
@@ -100,11 +100,12 @@ export function normalizeActivity(activity, fallback = {}) {
|
|
|
100
100
|
return normalized;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
function productionActivityFromPayload(payload) {
|
|
103
|
+
function productionActivityFromPayload(payload, context = {}) {
|
|
104
104
|
const progress = payload?.progress;
|
|
105
105
|
const job = payload?.job;
|
|
106
106
|
const jobId = payload?.jobId ?? job?.jobId;
|
|
107
107
|
if (!progress && !job && !jobId) return null;
|
|
108
|
+
const result = payload?.result && typeof payload.result === 'object' ? payload.result : null;
|
|
108
109
|
const status = job?.status ?? payload?.status ?? progress?.status ?? 'running';
|
|
109
110
|
const percent = Number.isFinite(Number(progress?.percent)) ? Number(progress.percent) : null;
|
|
110
111
|
const sourceCount = Number(progress?.sourceCount);
|
|
@@ -139,21 +140,23 @@ function productionActivityFromPayload(payload) {
|
|
|
139
140
|
return normalizeActivity({
|
|
140
141
|
id: jobId,
|
|
141
142
|
source: 'production',
|
|
142
|
-
kind: job?.type ?? payload?.type ?? progress?.phase ?? progress?.currentStep ?? 'job',
|
|
143
|
+
kind: job?.type ?? payload?.operation ?? payload?.type ?? progress?.phase ?? progress?.currentStep ?? 'job',
|
|
143
144
|
label: detail ? `Production: ${detail}` : `Production: ${status}`,
|
|
144
145
|
status,
|
|
145
146
|
progress: {
|
|
146
147
|
...(progress ?? {}),
|
|
147
148
|
...(percent !== null ? { percent } : {}),
|
|
148
149
|
step,
|
|
150
|
+
...(payload?.taskId ? { stepId: String(payload.taskId) } : {}),
|
|
149
151
|
},
|
|
150
152
|
poll: jobId ? {
|
|
151
153
|
server: 'production',
|
|
152
|
-
tool: 'production_job_status',
|
|
154
|
+
tool: context.tool === 'agent_status' ? 'agent_status' : 'production_job_status',
|
|
153
155
|
args: { jobId },
|
|
154
156
|
intervalMs: 2500,
|
|
155
157
|
} : null,
|
|
156
|
-
|
|
158
|
+
outputRefs: Array.isArray(result?.outputRefs) ? result.outputRefs : [],
|
|
159
|
+
error: job?.error ?? result?.error?.message ?? payload?.error ?? null,
|
|
157
160
|
});
|
|
158
161
|
}
|
|
159
162
|
|
|
@@ -163,7 +166,7 @@ export function extractActivity(payload, context = {}) {
|
|
|
163
166
|
return normalizeActivity(payload._activity, { source: context.server });
|
|
164
167
|
}
|
|
165
168
|
if (context.server === 'production') {
|
|
166
|
-
return productionActivityFromPayload(payload);
|
|
169
|
+
return productionActivityFromPayload(payload, context);
|
|
167
170
|
}
|
|
168
171
|
return null;
|
|
169
172
|
}
|