@dotdrelle/wiki-manager 0.11.10 → 0.12.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/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 +101 -38
- package/src/agent/graph.test.js +75 -5
- package/src/cli/wiki-manager.js +22 -0
- package/src/contracts/schemas.js +249 -13
- package/src/contracts/schemas.test.js +147 -2
- 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 +30 -3
- package/src/core/mcp.test.js +69 -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/controlMessages.js +43 -0
- package/src/runtime/controlMessages.test.js +21 -0
- package/src/runtime/donna-contract.test.js +296 -33
- 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 +142 -24
- package/src/runtime/server.test.js +192 -62
- package/src/runtime/store.js +837 -2
- package/src/runtime/store.test.js +269 -28
- 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 +7 -11
- package/src/shell/repl.test.js +26 -5
- package/src/shell/tui.tsx +1 -0
- package/src/shell/useAgent.ts +5 -1
- package/src/shell/useSession.ts +75 -19
|
@@ -128,6 +128,64 @@ test('runtime server shutdown endpoint closes the listener', async (t) => {
|
|
|
128
128
|
}));
|
|
129
129
|
});
|
|
130
130
|
|
|
131
|
+
test('runtime server exposes task assignment attempt and result read endpoints', async (t) => {
|
|
132
|
+
const taskId = 'run-1:task-a';
|
|
133
|
+
let handle;
|
|
134
|
+
try {
|
|
135
|
+
handle = await startRuntimeServer({
|
|
136
|
+
host: '127.0.0.1',
|
|
137
|
+
port: 0,
|
|
138
|
+
token: 'runtime-secret',
|
|
139
|
+
store: {
|
|
140
|
+
dbPath: ':memory:',
|
|
141
|
+
getState: () => ({ status: 'idle' }),
|
|
142
|
+
listEvents: () => [],
|
|
143
|
+
listTasks: ({ runId }) => runId === 'run-1' ? [{ id: taskId, runId, status: 'done' }] : [],
|
|
144
|
+
listTaskAttempts: ({ taskId: requested }) => requested === taskId ? [{
|
|
145
|
+
attemptId: 'attempt-1',
|
|
146
|
+
taskId,
|
|
147
|
+
runId: 'run-1',
|
|
148
|
+
status: 'done',
|
|
149
|
+
jobId: 'job-1',
|
|
150
|
+
}] : [],
|
|
151
|
+
getTaskResult: ({ taskId: requested }) => requested === taskId ? {
|
|
152
|
+
attemptId: 'attempt-1',
|
|
153
|
+
taskId,
|
|
154
|
+
status: 'succeeded',
|
|
155
|
+
outputRefs: [{ type: 'file', ref: 'deliverables/a.md' }],
|
|
156
|
+
metrics: { durationMs: 7 },
|
|
157
|
+
} : null,
|
|
158
|
+
},
|
|
159
|
+
session: {},
|
|
160
|
+
run: async () => {},
|
|
161
|
+
});
|
|
162
|
+
} catch (err) {
|
|
163
|
+
if (err?.code === 'EPERM') {
|
|
164
|
+
t.skip('network listen is not permitted in this sandbox');
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
throw err;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
const headers = { authorization: 'Bearer runtime-secret' };
|
|
172
|
+
const tasksResponse = await fetch(`http://127.0.0.1:${handle.port}/runs/run-1/tasks`, { headers });
|
|
173
|
+
assert.equal(tasksResponse.status, 200);
|
|
174
|
+
assert.deepEqual((await tasksResponse.json()).tasks, [{ id: taskId, runId: 'run-1', status: 'done' }]);
|
|
175
|
+
|
|
176
|
+
const encodedTaskId = encodeURIComponent(taskId);
|
|
177
|
+
const attemptsResponse = await fetch(`http://127.0.0.1:${handle.port}/tasks/${encodedTaskId}/attempts`, { headers });
|
|
178
|
+
assert.equal(attemptsResponse.status, 200);
|
|
179
|
+
assert.equal((await attemptsResponse.json()).attempts[0].attemptId, 'attempt-1');
|
|
180
|
+
|
|
181
|
+
const resultResponse = await fetch(`http://127.0.0.1:${handle.port}/tasks/${encodedTaskId}/result`, { headers });
|
|
182
|
+
assert.equal(resultResponse.status, 200);
|
|
183
|
+
assert.equal((await resultResponse.json()).result.metrics.durationMs, 7);
|
|
184
|
+
} finally {
|
|
185
|
+
await handle.close();
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
131
189
|
test('runtime server accepts only one active run', async (t) => {
|
|
132
190
|
let releaseRun;
|
|
133
191
|
let runCount = 0;
|
|
@@ -157,7 +215,6 @@ test('runtime server accepts only one active run', async (t) => {
|
|
|
157
215
|
|
|
158
216
|
try {
|
|
159
217
|
const url = `http://127.0.0.1:${handle.port}/run`;
|
|
160
|
-
let acceptedRun = null;
|
|
161
218
|
const [first, second] = await Promise.all([
|
|
162
219
|
fetch(url, {
|
|
163
220
|
method: 'POST',
|
|
@@ -171,9 +228,11 @@ test('runtime server accepts only one active run', async (t) => {
|
|
|
171
228
|
}),
|
|
172
229
|
]);
|
|
173
230
|
|
|
174
|
-
assert.deepEqual([first.status, second.status]
|
|
175
|
-
const
|
|
176
|
-
acceptedRun =
|
|
231
|
+
assert.deepEqual([first.status, second.status], [202, 202]);
|
|
232
|
+
const bodies = [await first.json(), await second.json()];
|
|
233
|
+
const acceptedRun = bodies.find((body) => body.accepted && !body.queued);
|
|
234
|
+
const queuedRun = bodies.find((body) => body.queued);
|
|
235
|
+
assert.equal(queuedRun.kind, 'enqueue_run');
|
|
177
236
|
assert.equal(acceptedRun.accepted, true);
|
|
178
237
|
assert.match(acceptedRun.runId, /^[0-9a-f-]{36}$/);
|
|
179
238
|
assert.equal(runCount, 1);
|
|
@@ -212,7 +271,7 @@ test('runtime server returns the accepted run id and passes it to the runner', a
|
|
|
212
271
|
const response = await fetch(`http://127.0.0.1:${handle.port}/run`, {
|
|
213
272
|
method: 'POST',
|
|
214
273
|
headers: { 'Content-Type': 'application/json' },
|
|
215
|
-
body: JSON.stringify({ input: 'build', workspace: '
|
|
274
|
+
body: JSON.stringify({ input: 'build', workspace: 'acme', evaluate: false, replans: 1 }),
|
|
216
275
|
});
|
|
217
276
|
assert.equal(response.status, 202);
|
|
218
277
|
const body = await response.json();
|
|
@@ -220,7 +279,7 @@ test('runtime server returns the accepted run id and passes it to the runner', a
|
|
|
220
279
|
assert.match(body.runId, /^[0-9a-f-]{36}$/);
|
|
221
280
|
await new Promise((resolve) => setImmediate(resolve));
|
|
222
281
|
assert.equal(receivedBody.runId, body.runId);
|
|
223
|
-
assert.equal(receivedBody.workspace, '
|
|
282
|
+
assert.equal(receivedBody.workspace, 'acme');
|
|
224
283
|
assert.equal(receivedBody.evaluate, false);
|
|
225
284
|
assert.equal(receivedBody.replans, 1);
|
|
226
285
|
} finally {
|
|
@@ -231,8 +290,8 @@ test('runtime server returns the accepted run id and passes it to the runner', a
|
|
|
231
290
|
test('runtime server state exposes active run identity while running', async (t) => {
|
|
232
291
|
let releaseRun;
|
|
233
292
|
const context = {
|
|
234
|
-
workspace: '
|
|
235
|
-
session: { workspace: '
|
|
293
|
+
workspace: 'acme',
|
|
294
|
+
session: { workspace: 'acme' },
|
|
236
295
|
running: false,
|
|
237
296
|
currentAbortController: null,
|
|
238
297
|
currentRunId: null,
|
|
@@ -262,18 +321,18 @@ test('runtime server state exposes active run identity while running', async (t)
|
|
|
262
321
|
}
|
|
263
322
|
|
|
264
323
|
try {
|
|
265
|
-
const runResponse = await fetch(`http://127.0.0.1:${handle.port}/run?workspace=
|
|
324
|
+
const runResponse = await fetch(`http://127.0.0.1:${handle.port}/run?workspace=acme`, {
|
|
266
325
|
method: 'POST',
|
|
267
326
|
headers: { 'Content-Type': 'application/json' },
|
|
268
327
|
body: JSON.stringify({ input: 'build' }),
|
|
269
328
|
});
|
|
270
329
|
acceptedRun = await runResponse.json();
|
|
271
|
-
const stateResponse = await fetch(`http://127.0.0.1:${handle.port}/state?workspace=
|
|
330
|
+
const stateResponse = await fetch(`http://127.0.0.1:${handle.port}/state?workspace=acme`);
|
|
272
331
|
const state = await stateResponse.json();
|
|
273
332
|
assert.equal(state.status, 'running');
|
|
274
333
|
assert.equal(state.running, true);
|
|
275
334
|
assert.equal(state.runId, acceptedRun.runId);
|
|
276
|
-
assert.equal(state.workspace, '
|
|
335
|
+
assert.equal(state.workspace, 'acme');
|
|
277
336
|
} finally {
|
|
278
337
|
releaseRun?.();
|
|
279
338
|
await handle.close();
|
|
@@ -322,8 +381,8 @@ test('runtime server isolates active runs by workspace', async (t) => {
|
|
|
322
381
|
|
|
323
382
|
try {
|
|
324
383
|
const url = `http://127.0.0.1:${handle.port}/run`;
|
|
325
|
-
const [
|
|
326
|
-
fetch(`${url}?workspace=
|
|
384
|
+
const [acme, docs] = await Promise.all([
|
|
385
|
+
fetch(`${url}?workspace=acme`, {
|
|
327
386
|
method: 'POST',
|
|
328
387
|
headers: { 'Content-Type': 'application/json' },
|
|
329
388
|
body: JSON.stringify({ input: 'first' }),
|
|
@@ -334,17 +393,18 @@ test('runtime server isolates active runs by workspace', async (t) => {
|
|
|
334
393
|
body: JSON.stringify({ input: 'second' }),
|
|
335
394
|
}),
|
|
336
395
|
]);
|
|
337
|
-
assert.deepEqual([
|
|
396
|
+
assert.deepEqual([acme.status, docs.status], [202, 202]);
|
|
338
397
|
|
|
339
|
-
const
|
|
398
|
+
const queued = await fetch(`${url}?workspace=acme`, {
|
|
340
399
|
method: 'POST',
|
|
341
400
|
headers: { 'Content-Type': 'application/json' },
|
|
342
401
|
body: JSON.stringify({ input: 'third' }),
|
|
343
402
|
});
|
|
344
|
-
assert.equal(
|
|
345
|
-
assert.
|
|
403
|
+
assert.equal(queued.status, 202);
|
|
404
|
+
assert.equal((await queued.json()).queued, true);
|
|
405
|
+
assert.deepEqual(runWorkspaces.sort(), ['acme', 'docs']);
|
|
346
406
|
} finally {
|
|
347
|
-
releases.get('
|
|
407
|
+
releases.get('acme')?.();
|
|
348
408
|
releases.get('docs')?.();
|
|
349
409
|
await handle.close();
|
|
350
410
|
}
|
|
@@ -386,10 +446,10 @@ test('runtime server filters state and events by workspace', async (t) => {
|
|
|
386
446
|
}
|
|
387
447
|
|
|
388
448
|
try {
|
|
389
|
-
const state = await fetch(`http://127.0.0.1:${handle.port}/state?workspace=
|
|
449
|
+
const state = await fetch(`http://127.0.0.1:${handle.port}/state?workspace=acme`);
|
|
390
450
|
assert.equal(state.status, 200);
|
|
391
|
-
assert.equal((await state.json()).workspace, '
|
|
392
|
-
assert.equal(stateWorkspace, '
|
|
451
|
+
assert.equal((await state.json()).workspace, 'acme');
|
|
452
|
+
assert.equal(stateWorkspace, 'acme');
|
|
393
453
|
|
|
394
454
|
const events = await fetch(`http://127.0.0.1:${handle.port}/events?workspace=docs`);
|
|
395
455
|
assert.equal(events.status, 200);
|
|
@@ -473,15 +533,15 @@ test('runtime server exposes manual resume endpoint', async (t) => {
|
|
|
473
533
|
}
|
|
474
534
|
|
|
475
535
|
try {
|
|
476
|
-
const response = await fetch(`http://127.0.0.1:${handle.port}/resume?workspace=
|
|
536
|
+
const response = await fetch(`http://127.0.0.1:${handle.port}/resume?workspace=acme`, {
|
|
477
537
|
method: 'POST',
|
|
478
538
|
});
|
|
479
539
|
assert.equal(response.status, 202);
|
|
480
|
-
assert.equal(resumedWorkspace, '
|
|
540
|
+
assert.equal(resumedWorkspace, 'acme');
|
|
481
541
|
assert.deepEqual(await response.json(), {
|
|
482
542
|
resumed: 1,
|
|
483
543
|
interrupted: 0,
|
|
484
|
-
workspaces: [{ workspace: '
|
|
544
|
+
workspaces: [{ workspace: 'acme', resumed: true }],
|
|
485
545
|
});
|
|
486
546
|
} finally {
|
|
487
547
|
await handle.close();
|
|
@@ -515,15 +575,21 @@ test('runtime server exposes approval endpoint', async (t) => {
|
|
|
515
575
|
}
|
|
516
576
|
|
|
517
577
|
try {
|
|
518
|
-
const response = await fetch(`http://127.0.0.1:${handle.port}/approve?workspace=
|
|
578
|
+
const response = await fetch(`http://127.0.0.1:${handle.port}/approve?workspace=acme&runId=run-1&itemId=item-1`, {
|
|
519
579
|
method: 'POST',
|
|
520
580
|
});
|
|
521
581
|
assert.equal(response.status, 202);
|
|
522
582
|
assert.deepEqual(approved, {
|
|
523
|
-
workspace: '
|
|
583
|
+
workspace: 'acme',
|
|
584
|
+
workspaceId: 'acme',
|
|
524
585
|
runId: 'run-1',
|
|
525
586
|
itemId: 'item-1',
|
|
526
587
|
approvalId: null,
|
|
588
|
+
scope: null,
|
|
589
|
+
taskId: null,
|
|
590
|
+
groupId: null,
|
|
591
|
+
planRevision: null,
|
|
592
|
+
approvalClasses: [],
|
|
527
593
|
});
|
|
528
594
|
assert.deepEqual(await response.json(), { approved: true, runId: 'run-1', itemId: 'item-1' });
|
|
529
595
|
} finally {
|
|
@@ -533,8 +599,8 @@ test('runtime server exposes approval endpoint', async (t) => {
|
|
|
533
599
|
|
|
534
600
|
test('runtime server exposes control status and explanation', async (t) => {
|
|
535
601
|
const session = {
|
|
536
|
-
workspace: '
|
|
537
|
-
controlQueue: [{ id: 'control-1', workspace: '
|
|
602
|
+
workspace: 'acme',
|
|
603
|
+
controlQueue: [{ id: 'control-1', workspace: 'acme', status: 'queued', input: 'later' }],
|
|
538
604
|
};
|
|
539
605
|
let handle;
|
|
540
606
|
try {
|
|
@@ -554,7 +620,7 @@ test('runtime server exposes control status and explanation', async (t) => {
|
|
|
554
620
|
listEvents: () => [],
|
|
555
621
|
},
|
|
556
622
|
getContext: async () => ({
|
|
557
|
-
workspace: '
|
|
623
|
+
workspace: 'acme',
|
|
558
624
|
session,
|
|
559
625
|
running: false,
|
|
560
626
|
currentAbortController: null,
|
|
@@ -570,14 +636,14 @@ test('runtime server exposes control status and explanation', async (t) => {
|
|
|
570
636
|
}
|
|
571
637
|
|
|
572
638
|
try {
|
|
573
|
-
const status = await fetch(`http://127.0.0.1:${handle.port}/control?workspace=
|
|
639
|
+
const status = await fetch(`http://127.0.0.1:${handle.port}/control?workspace=acme`);
|
|
574
640
|
assert.equal(status.status, 200);
|
|
575
641
|
const statusBody = await status.json();
|
|
576
642
|
assert.equal(statusBody.status, 'idle');
|
|
577
643
|
assert.equal(statusBody.running, false);
|
|
578
644
|
assert.equal(statusBody.controlQueue[0].id, 'control-1');
|
|
579
645
|
|
|
580
|
-
const explain = await fetch(`http://127.0.0.1:${handle.port}/control?workspace=
|
|
646
|
+
const explain = await fetch(`http://127.0.0.1:${handle.port}/control?workspace=acme`, {
|
|
581
647
|
method: 'POST',
|
|
582
648
|
headers: { 'Content-Type': 'application/json' },
|
|
583
649
|
body: JSON.stringify({ action: 'explain' }),
|
|
@@ -591,7 +657,7 @@ test('runtime server exposes control status and explanation', async (t) => {
|
|
|
591
657
|
|
|
592
658
|
test('runtime server control enqueue emits events but does not patch an active plan or start a run', async (t) => {
|
|
593
659
|
const session = {
|
|
594
|
-
workspace: '
|
|
660
|
+
workspace: 'acme',
|
|
595
661
|
controlQueue: [],
|
|
596
662
|
};
|
|
597
663
|
const events = [];
|
|
@@ -615,7 +681,7 @@ test('runtime server control enqueue emits events but does not patch an active p
|
|
|
615
681
|
listEvents: () => [],
|
|
616
682
|
},
|
|
617
683
|
getContext: async () => ({
|
|
618
|
-
workspace: '
|
|
684
|
+
workspace: 'acme',
|
|
619
685
|
session,
|
|
620
686
|
running: true,
|
|
621
687
|
currentAbortController: new AbortController(),
|
|
@@ -631,7 +697,7 @@ test('runtime server control enqueue emits events but does not patch an active p
|
|
|
631
697
|
}
|
|
632
698
|
|
|
633
699
|
try {
|
|
634
|
-
const response = await fetch(`http://127.0.0.1:${handle.port}/control?workspace=
|
|
700
|
+
const response = await fetch(`http://127.0.0.1:${handle.port}/control?workspace=acme`, {
|
|
635
701
|
method: 'POST',
|
|
636
702
|
headers: { 'Content-Type': 'application/json' },
|
|
637
703
|
body: JSON.stringify({ action: 'enqueue', input: 'run this after current work' }),
|
|
@@ -652,7 +718,7 @@ test('runtime server control enqueue emits events but does not patch an active p
|
|
|
652
718
|
|
|
653
719
|
test('runtime server control message observes an active run without enqueueing', async (t) => {
|
|
654
720
|
const session = {
|
|
655
|
-
workspace: '
|
|
721
|
+
workspace: 'acme',
|
|
656
722
|
controlQueue: [],
|
|
657
723
|
};
|
|
658
724
|
let runCount = 0;
|
|
@@ -673,7 +739,7 @@ test('runtime server control message observes an active run without enqueueing',
|
|
|
673
739
|
listEvents: () => [],
|
|
674
740
|
},
|
|
675
741
|
getContext: async () => ({
|
|
676
|
-
workspace: '
|
|
742
|
+
workspace: 'acme',
|
|
677
743
|
session,
|
|
678
744
|
running: true,
|
|
679
745
|
currentAbortController: new AbortController(),
|
|
@@ -689,7 +755,7 @@ test('runtime server control message observes an active run without enqueueing',
|
|
|
689
755
|
}
|
|
690
756
|
|
|
691
757
|
try {
|
|
692
|
-
const response = await fetch(`http://127.0.0.1:${handle.port}/control?workspace=
|
|
758
|
+
const response = await fetch(`http://127.0.0.1:${handle.port}/control?workspace=acme`, {
|
|
693
759
|
method: 'POST',
|
|
694
760
|
headers: { 'Content-Type': 'application/json' },
|
|
695
761
|
body: JSON.stringify({ action: 'message', input: 'Où en est le build ?' }),
|
|
@@ -705,20 +771,84 @@ test('runtime server control message observes an active run without enqueueing',
|
|
|
705
771
|
}
|
|
706
772
|
});
|
|
707
773
|
|
|
774
|
+
test('runtime server control message handles approve and cancel intents during an active run', async (t) => {
|
|
775
|
+
const session = {
|
|
776
|
+
workspace: 'acme',
|
|
777
|
+
controlQueue: [],
|
|
778
|
+
};
|
|
779
|
+
const abortController = new AbortController();
|
|
780
|
+
let cancelled = false;
|
|
781
|
+
let handle;
|
|
782
|
+
try {
|
|
783
|
+
handle = await startRuntimeServer({
|
|
784
|
+
host: '127.0.0.1',
|
|
785
|
+
port: 0,
|
|
786
|
+
store: {
|
|
787
|
+
dbPath: ':memory:',
|
|
788
|
+
getState: () => ({
|
|
789
|
+
status: 'running',
|
|
790
|
+
plan: [{ step: 1, description: 'Build documents', status: 'running' }],
|
|
791
|
+
queue: [],
|
|
792
|
+
approvals: [],
|
|
793
|
+
summary: null,
|
|
794
|
+
}),
|
|
795
|
+
listEvents: () => [],
|
|
796
|
+
},
|
|
797
|
+
getContext: async () => ({
|
|
798
|
+
workspace: 'acme',
|
|
799
|
+
session,
|
|
800
|
+
running: true,
|
|
801
|
+
currentAbortController: abortController,
|
|
802
|
+
}),
|
|
803
|
+
cancel: async () => { cancelled = true; },
|
|
804
|
+
run: async () => {},
|
|
805
|
+
});
|
|
806
|
+
} catch (err) {
|
|
807
|
+
if (err?.code === 'EPERM') {
|
|
808
|
+
t.skip('network listen is not permitted in this sandbox');
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
throw err;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
try {
|
|
815
|
+
const approveResponse = await fetch(`http://127.0.0.1:${handle.port}/control?workspace=acme`, {
|
|
816
|
+
method: 'POST',
|
|
817
|
+
headers: { 'Content-Type': 'application/json' },
|
|
818
|
+
body: JSON.stringify({ action: 'message', input: 'valide tout' }),
|
|
819
|
+
});
|
|
820
|
+
assert.equal(approveResponse.status, 200);
|
|
821
|
+
assert.equal((await approveResponse.json()).kind, 'approve');
|
|
822
|
+
|
|
823
|
+
const cancelResponse = await fetch(`http://127.0.0.1:${handle.port}/control?workspace=acme`, {
|
|
824
|
+
method: 'POST',
|
|
825
|
+
headers: { 'Content-Type': 'application/json' },
|
|
826
|
+
body: JSON.stringify({ action: 'message', input: 'annule le run' }),
|
|
827
|
+
});
|
|
828
|
+
assert.equal(cancelResponse.status, 200);
|
|
829
|
+
assert.equal((await cancelResponse.json()).kind, 'cancel');
|
|
830
|
+
assert.equal(cancelled, true);
|
|
831
|
+
assert.equal(abortController.signal.aborted, true);
|
|
832
|
+
assert.equal(session.controlQueue.length, 0);
|
|
833
|
+
} finally {
|
|
834
|
+
await handle.close();
|
|
835
|
+
}
|
|
836
|
+
});
|
|
837
|
+
|
|
708
838
|
test('runtime server control message records active plan mutation as a proposal', async (t) => {
|
|
709
839
|
const session = {
|
|
710
|
-
workspace: '
|
|
840
|
+
workspace: 'acme',
|
|
711
841
|
controlQueue: [],
|
|
712
842
|
};
|
|
713
843
|
dispatchAgentEvent(session, createAgentEvent('run_started', {
|
|
714
844
|
origin: 'runtime',
|
|
715
845
|
runId: 'run-mutate',
|
|
716
|
-
workspace: '
|
|
846
|
+
workspace: 'acme',
|
|
717
847
|
}));
|
|
718
848
|
dispatchAgentEvent(session, createAgentEvent('plan_set', {
|
|
719
849
|
origin: 'tool',
|
|
720
850
|
runId: 'run-mutate',
|
|
721
|
-
workspace: '
|
|
851
|
+
workspace: 'acme',
|
|
722
852
|
payload: {
|
|
723
853
|
steps: [{ step: 1, id: 'generate', description: 'Generate', status: 'running' }],
|
|
724
854
|
},
|
|
@@ -735,13 +865,13 @@ test('runtime server control message records active plan mutation as a proposal'
|
|
|
735
865
|
...session.agentProjection,
|
|
736
866
|
status: session.agentProjection?.status ?? 'running',
|
|
737
867
|
queue: [],
|
|
738
|
-
runs: [{ id: 'run-mutate', workspace: '
|
|
868
|
+
runs: [{ id: 'run-mutate', workspace: 'acme', status: 'running' }],
|
|
739
869
|
runId: 'run-mutate',
|
|
740
870
|
}),
|
|
741
871
|
listEvents: () => [],
|
|
742
872
|
},
|
|
743
873
|
getContext: async () => ({
|
|
744
|
-
workspace: '
|
|
874
|
+
workspace: 'acme',
|
|
745
875
|
session,
|
|
746
876
|
running: true,
|
|
747
877
|
currentAbortController: new AbortController(),
|
|
@@ -758,20 +888,20 @@ test('runtime server control message records active plan mutation as a proposal'
|
|
|
758
888
|
}
|
|
759
889
|
|
|
760
890
|
try {
|
|
761
|
-
const response = await fetch(`http://127.0.0.1:${handle.port}/control?workspace=
|
|
891
|
+
const response = await fetch(`http://127.0.0.1:${handle.port}/control?workspace=acme`, {
|
|
762
892
|
method: 'POST',
|
|
763
893
|
headers: { 'Content-Type': 'application/json' },
|
|
764
894
|
body: JSON.stringify({ action: 'message', input: 'Ajoute un envoi après chaque génération' }),
|
|
765
895
|
});
|
|
766
896
|
assert.equal(response.status, 202);
|
|
767
897
|
const body = await response.json();
|
|
768
|
-
assert.equal(body.kind, '
|
|
898
|
+
assert.equal(body.kind, 'modify_run');
|
|
769
899
|
assert.equal(body.proposal.status, 'proposed');
|
|
770
900
|
assert.equal(body.proposal.input, 'Ajoute un envoi après chaque génération');
|
|
771
901
|
assert.equal(session.agentProjection.planPatches[0].status, 'proposed');
|
|
772
902
|
assert.ok(session.agentEvents.some((event) => event.type === 'control_message_received'));
|
|
773
903
|
assert.ok(session.agentEvents.some((event) => event.type === 'plan_patch_proposed'));
|
|
774
|
-
const approveResponse = await fetch(`http://127.0.0.1:${handle.port}/control?workspace=
|
|
904
|
+
const approveResponse = await fetch(`http://127.0.0.1:${handle.port}/control?workspace=acme`, {
|
|
775
905
|
method: 'POST',
|
|
776
906
|
headers: { 'Content-Type': 'application/json' },
|
|
777
907
|
body: JSON.stringify({ action: 'approve_patch', patchId: body.proposal.id }),
|
|
@@ -794,7 +924,7 @@ test('runtime server control message records active plan mutation as a proposal'
|
|
|
794
924
|
|
|
795
925
|
test('runtime server control message reports ambiguity without starting a run', async (t) => {
|
|
796
926
|
const session = {
|
|
797
|
-
workspace: '
|
|
927
|
+
workspace: 'acme',
|
|
798
928
|
controlQueue: [],
|
|
799
929
|
};
|
|
800
930
|
let runCount = 0;
|
|
@@ -815,7 +945,7 @@ test('runtime server control message reports ambiguity without starting a run',
|
|
|
815
945
|
listEvents: () => [],
|
|
816
946
|
},
|
|
817
947
|
getContext: async () => ({
|
|
818
|
-
workspace: '
|
|
948
|
+
workspace: 'acme',
|
|
819
949
|
session,
|
|
820
950
|
running: true,
|
|
821
951
|
currentAbortController: new AbortController(),
|
|
@@ -831,7 +961,7 @@ test('runtime server control message reports ambiguity without starting a run',
|
|
|
831
961
|
}
|
|
832
962
|
|
|
833
963
|
try {
|
|
834
|
-
const response = await fetch(`http://127.0.0.1:${handle.port}/control?workspace=
|
|
964
|
+
const response = await fetch(`http://127.0.0.1:${handle.port}/control?workspace=acme`, {
|
|
835
965
|
method: 'POST',
|
|
836
966
|
headers: { 'Content-Type': 'application/json' },
|
|
837
967
|
body: JSON.stringify({ action: 'message', input: 'Lance aussi la publication' }),
|
|
@@ -849,7 +979,7 @@ test('runtime server control message reports ambiguity without starting a run',
|
|
|
849
979
|
|
|
850
980
|
test('runtime server drains queued control requests when idle', async (t) => {
|
|
851
981
|
const session = {
|
|
852
|
-
workspace: '
|
|
982
|
+
workspace: 'acme',
|
|
853
983
|
controlQueue: [],
|
|
854
984
|
};
|
|
855
985
|
const events = [];
|
|
@@ -873,7 +1003,7 @@ test('runtime server drains queued control requests when idle', async (t) => {
|
|
|
873
1003
|
listEvents: () => [],
|
|
874
1004
|
},
|
|
875
1005
|
getContext: async () => ({
|
|
876
|
-
workspace: '
|
|
1006
|
+
workspace: 'acme',
|
|
877
1007
|
session,
|
|
878
1008
|
running: false,
|
|
879
1009
|
currentAbortController: null,
|
|
@@ -891,7 +1021,7 @@ test('runtime server drains queued control requests when idle', async (t) => {
|
|
|
891
1021
|
}
|
|
892
1022
|
|
|
893
1023
|
try {
|
|
894
|
-
const response = await fetch(`http://127.0.0.1:${handle.port}/control?workspace=
|
|
1024
|
+
const response = await fetch(`http://127.0.0.1:${handle.port}/control?workspace=acme`, {
|
|
895
1025
|
method: 'POST',
|
|
896
1026
|
headers: { 'Content-Type': 'application/json' },
|
|
897
1027
|
body: JSON.stringify({ action: 'enqueue', input: 'run from control queue' }),
|
|
@@ -899,7 +1029,7 @@ test('runtime server drains queued control requests when idle', async (t) => {
|
|
|
899
1029
|
assert.equal(response.status, 202);
|
|
900
1030
|
await new Promise((resolve) => setImmediate(resolve));
|
|
901
1031
|
assert.equal(receivedBody.input, 'run from control queue');
|
|
902
|
-
assert.equal(receivedBody.workspace, '
|
|
1032
|
+
assert.equal(receivedBody.workspace, 'acme');
|
|
903
1033
|
assert.match(receivedBody.runId, /^[0-9a-f-]{36}$/);
|
|
904
1034
|
assert.equal(session.controlQueue[0].status, 'running');
|
|
905
1035
|
assert.equal(session.controlQueue[0].runId, receivedBody.runId);
|
|
@@ -911,13 +1041,13 @@ test('runtime server drains queued control requests when idle', async (t) => {
|
|
|
911
1041
|
|
|
912
1042
|
test('runtime server handle drains a pre-existing hydrated control request', async (t) => {
|
|
913
1043
|
const session = {
|
|
914
|
-
workspace: '
|
|
915
|
-
controlQueue: [{ id: 'control-existing', workspace: '
|
|
1044
|
+
workspace: 'acme',
|
|
1045
|
+
controlQueue: [{ id: 'control-existing', workspace: 'acme', status: 'queued', input: 'resume queued control' }],
|
|
916
1046
|
};
|
|
917
1047
|
const events = [];
|
|
918
1048
|
session._onAgentEvent = (event) => events.push(event);
|
|
919
1049
|
const context = {
|
|
920
|
-
workspace: '
|
|
1050
|
+
workspace: 'acme',
|
|
921
1051
|
session,
|
|
922
1052
|
running: false,
|
|
923
1053
|
currentAbortController: null,
|
|
@@ -959,8 +1089,8 @@ test('runtime server handle drains a pre-existing hydrated control request', asy
|
|
|
959
1089
|
|
|
960
1090
|
test('runtime server exposes config profile list and switch endpoints', async (t) => {
|
|
961
1091
|
const context = {
|
|
962
|
-
workspace: '
|
|
963
|
-
session: { workspace: '
|
|
1092
|
+
workspace: 'acme',
|
|
1093
|
+
session: { workspace: 'acme', wikirc: { profile: 'default' } },
|
|
964
1094
|
running: false,
|
|
965
1095
|
currentAbortController: null,
|
|
966
1096
|
};
|
|
@@ -993,11 +1123,11 @@ test('runtime server exposes config profile list and switch endpoints', async (t
|
|
|
993
1123
|
}
|
|
994
1124
|
|
|
995
1125
|
try {
|
|
996
|
-
const profiles = await fetch(`http://127.0.0.1:${handle.port}/config/profiles?workspace=
|
|
1126
|
+
const profiles = await fetch(`http://127.0.0.1:${handle.port}/config/profiles?workspace=acme`);
|
|
997
1127
|
assert.equal(profiles.status, 200);
|
|
998
1128
|
assert.deepEqual(await profiles.json(), { profiles: ['default', 'vpn'], active: 'default' });
|
|
999
1129
|
|
|
1000
|
-
const use = await fetch(`http://127.0.0.1:${handle.port}/config/use?workspace=
|
|
1130
|
+
const use = await fetch(`http://127.0.0.1:${handle.port}/config/use?workspace=acme`, {
|
|
1001
1131
|
method: 'POST',
|
|
1002
1132
|
headers: { 'Content-Type': 'application/json' },
|
|
1003
1133
|
body: JSON.stringify({ profile: 'vpn' }),
|
|
@@ -1022,8 +1152,8 @@ test('runtime server rejects config switching while a run is active', async (t)
|
|
|
1022
1152
|
listEvents: () => [],
|
|
1023
1153
|
},
|
|
1024
1154
|
getContext: async () => ({
|
|
1025
|
-
workspace: '
|
|
1026
|
-
session: { workspace: '
|
|
1155
|
+
workspace: 'acme',
|
|
1156
|
+
session: { workspace: 'acme' },
|
|
1027
1157
|
running: true,
|
|
1028
1158
|
currentAbortController: null,
|
|
1029
1159
|
}),
|
|
@@ -1039,7 +1169,7 @@ test('runtime server rejects config switching while a run is active', async (t)
|
|
|
1039
1169
|
}
|
|
1040
1170
|
|
|
1041
1171
|
try {
|
|
1042
|
-
const response = await fetch(`http://127.0.0.1:${handle.port}/config/use?workspace=
|
|
1172
|
+
const response = await fetch(`http://127.0.0.1:${handle.port}/config/use?workspace=acme`, {
|
|
1043
1173
|
method: 'POST',
|
|
1044
1174
|
headers: { 'Content-Type': 'application/json' },
|
|
1045
1175
|
body: JSON.stringify({ profile: 'vpn' }),
|