@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.
Files changed (85) 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 +101 -38
  11. package/src/agent/graph.test.js +75 -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 +147 -2
  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 +30 -3
  25. package/src/core/mcp.test.js +69 -1
  26. package/src/core/plan.js +46 -4
  27. package/src/core/plan.test.js +16 -1
  28. package/src/core/planPatch.js +64 -3
  29. package/src/core/planPatch.test.js +110 -1
  30. package/src/core/queueStore.test.js +21 -0
  31. package/src/core/runtimeLog.js +119 -0
  32. package/src/core/runtimeLog.test.js +84 -0
  33. package/src/core/wikirc.js +22 -0
  34. package/src/core/wikirc.test.js +49 -1
  35. package/src/core/workflow.js +14 -3
  36. package/src/graph/graphAggregator.js +5 -0
  37. package/src/graph/graphPatch.js +8 -0
  38. package/src/graph/graphSnapshot.js +14 -0
  39. package/src/graph/graphVisibilityPolicy.js +40 -0
  40. package/src/graph/runGraphProjector.js +87 -0
  41. package/src/graph/runGraphProjector.test.js +111 -0
  42. package/src/orchestrator/agentRegistry.js +172 -0
  43. package/src/orchestrator/agentRegistry.test.js +114 -0
  44. package/src/orchestrator/approvalPolicy.js +126 -0
  45. package/src/orchestrator/approvalPolicy.test.js +78 -0
  46. package/src/orchestrator/assignmentManager.js +80 -0
  47. package/src/orchestrator/attemptManager.js +168 -0
  48. package/src/orchestrator/attemptManager.test.js +160 -0
  49. package/src/orchestrator/budgetManager.js +127 -0
  50. package/src/orchestrator/capabilityRegistry.js +64 -0
  51. package/src/orchestrator/capabilityRegistry.test.js +59 -0
  52. package/src/orchestrator/capabilityResolver.js +151 -0
  53. package/src/orchestrator/capabilityResolver.test.js +127 -0
  54. package/src/orchestrator/dependencyResolver.js +112 -0
  55. package/src/orchestrator/dispatcher.js +302 -0
  56. package/src/orchestrator/lockManager.js +51 -0
  57. package/src/orchestrator/planIntegrator.js +268 -0
  58. package/src/orchestrator/planIntegrator.test.js +213 -0
  59. package/src/orchestrator/planValidator.js +534 -0
  60. package/src/orchestrator/planValidator.test.js +262 -0
  61. package/src/orchestrator/resultAggregator.js +248 -0
  62. package/src/orchestrator/resultAggregator.test.js +211 -0
  63. package/src/orchestrator/scheduler.js +103 -0
  64. package/src/orchestrator/scheduler.test.js +138 -0
  65. package/src/runtime/approvals.js +130 -1
  66. package/src/runtime/controlMessages.js +43 -0
  67. package/src/runtime/controlMessages.test.js +21 -0
  68. package/src/runtime/donna-contract.test.js +296 -33
  69. package/src/runtime/recoveryManager.js +176 -0
  70. package/src/runtime/recoveryManager.test.js +162 -0
  71. package/src/runtime/runner.e2e.test.js +98 -12
  72. package/src/runtime/runner.js +261 -227
  73. package/src/runtime/runner.test.js +249 -452
  74. package/src/runtime/server.js +142 -24
  75. package/src/runtime/server.test.js +192 -62
  76. package/src/runtime/store.js +837 -2
  77. package/src/runtime/store.test.js +269 -28
  78. package/src/runtime/supervisor.js +33 -1
  79. package/src/runtime/supervisor.test.js +47 -1
  80. package/src/shell/RightPane.tsx +8 -5
  81. package/src/shell/repl.js +7 -11
  82. package/src/shell/repl.test.js +26 -5
  83. package/src/shell/tui.tsx +1 -0
  84. package/src/shell/useAgent.ts +5 -1
  85. 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].sort(), [202, 409]);
175
- const accepted = first.status === 202 ? first : second;
176
- acceptedRun = await accepted.json();
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: 'juno', evaluate: false, replans: 1 }),
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, 'juno');
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: 'juno',
235
- session: { workspace: 'juno' },
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=juno`, {
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=juno`);
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, 'juno');
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 [juno, docs] = await Promise.all([
326
- fetch(`${url}?workspace=juno`, {
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([juno.status, docs.status], [202, 202]);
396
+ assert.deepEqual([acme.status, docs.status], [202, 202]);
338
397
 
339
- const conflict = await fetch(`${url}?workspace=juno`, {
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(conflict.status, 409);
345
- assert.deepEqual(runWorkspaces.sort(), ['docs', 'juno']);
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('juno')?.();
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=juno`);
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, 'juno');
392
- assert.equal(stateWorkspace, 'juno');
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=juno`, {
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, 'juno');
540
+ assert.equal(resumedWorkspace, 'acme');
481
541
  assert.deepEqual(await response.json(), {
482
542
  resumed: 1,
483
543
  interrupted: 0,
484
- workspaces: [{ workspace: 'juno', resumed: true }],
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=juno&runId=run-1&itemId=item-1`, {
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: 'juno',
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: 'juno',
537
- controlQueue: [{ id: 'control-1', workspace: 'juno', status: 'queued', input: 'later' }],
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: 'juno',
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=juno`);
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=juno`, {
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: 'juno',
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: 'juno',
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=juno`, {
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: 'juno',
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: 'juno',
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=juno`, {
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: 'juno',
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: 'juno',
846
+ workspace: 'acme',
717
847
  }));
718
848
  dispatchAgentEvent(session, createAgentEvent('plan_set', {
719
849
  origin: 'tool',
720
850
  runId: 'run-mutate',
721
- workspace: 'juno',
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: 'juno', status: 'running' }],
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: 'juno',
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=juno`, {
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, 'mutate');
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=juno`, {
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: 'juno',
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: 'juno',
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=juno`, {
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: 'juno',
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: 'juno',
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=juno`, {
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, 'juno');
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: 'juno',
915
- controlQueue: [{ id: 'control-existing', workspace: 'juno', status: 'queued', input: 'resume queued control' }],
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: 'juno',
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: 'juno',
963
- session: { workspace: 'juno', wikirc: { profile: 'default' } },
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=juno`);
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=juno`, {
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: 'juno',
1026
- session: { workspace: 'juno' },
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=juno`, {
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' }),