@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
@@ -5,6 +5,7 @@ import { activeCacertPath } from '../core/cacert.js';
5
5
  import { normalizePlanPatch, rebasePlanPatch } from '../core/planPatch.js';
6
6
  import { validateContractInDev } from '../contracts/schemas.js';
7
7
  import { runtimeTokenFromEnv } from './auth.js';
8
+ import { controlMessage } from './controlMessages.js';
8
9
 
9
10
  export function startRuntimeServer({
10
11
  host = '127.0.0.1',
@@ -85,6 +86,35 @@ export function startRuntimeServer({
85
86
  });
86
87
  return;
87
88
  }
89
+ if (request.method === 'GET') {
90
+ const taskRead = readTaskEndpoint(url);
91
+ if (taskRead) {
92
+ if (taskRead.kind === 'run_tasks') {
93
+ sendJson(response, 200, {
94
+ ok: true,
95
+ runId: taskRead.runId,
96
+ tasks: typeof store.listTasks === 'function' ? store.listTasks({ runId: taskRead.runId }) : [],
97
+ });
98
+ return;
99
+ }
100
+ if (taskRead.kind === 'task_attempts') {
101
+ sendJson(response, 200, {
102
+ ok: true,
103
+ taskId: taskRead.taskId,
104
+ attempts: typeof store.listTaskAttempts === 'function' ? store.listTaskAttempts({ taskId: taskRead.taskId }) : [],
105
+ });
106
+ return;
107
+ }
108
+ if (taskRead.kind === 'task_result') {
109
+ sendJson(response, 200, {
110
+ ok: true,
111
+ taskId: taskRead.taskId,
112
+ result: typeof store.getTaskResult === 'function' ? store.getTaskResult({ taskId: taskRead.taskId }) : null,
113
+ });
114
+ return;
115
+ }
116
+ }
117
+ }
88
118
  if (request.method === 'GET' && url.pathname === '/control') {
89
119
  const workspace = workspaceFromUrl(url);
90
120
  const context = await resolveContext({ workspace });
@@ -112,9 +142,11 @@ export function startRuntimeServer({
112
142
  return;
113
143
  }
114
144
  validateContractInDev('controlMessage', { ...body, action, input });
115
- const result = handleControlMessage(context, store, input, {
145
+ const result = await handleControlMessage(context, store, input, {
116
146
  intent: body.intent,
117
147
  startNextControlRequest,
148
+ cancel,
149
+ approve,
118
150
  });
119
151
  sendJson(response, result.statusCode, result.body);
120
152
  return;
@@ -202,10 +234,6 @@ export function startRuntimeServer({
202
234
  }
203
235
  if (request.method === 'POST' && url.pathname === '/run') {
204
236
  const { body, context } = await resolveBodyContext(request, url);
205
- if (context.running) {
206
- sendJson(response, 409, { error: 'A runtime run is already active.' });
207
- return;
208
- }
209
237
  try {
210
238
  const input = String(body.input ?? body.prompt ?? '').trim();
211
239
  if (!input) {
@@ -213,6 +241,18 @@ export function startRuntimeServer({
213
241
  return;
214
242
  }
215
243
  validateContractInDev('runRequest', { ...body, input });
244
+ if (context.running) {
245
+ const item = enqueueControlRequest(context, input);
246
+ sendJson(response, 202, {
247
+ accepted: true,
248
+ queued: true,
249
+ kind: 'enqueue_run',
250
+ item,
251
+ ...controlStatus(context, store),
252
+ explanation: controlMessage(context?.session, 'queued_for_future_run'),
253
+ });
254
+ return;
255
+ }
216
256
  const accepted = startRuntimeRun(context, body);
217
257
  sendJson(response, 202, accepted);
218
258
  } catch (err) {
@@ -262,9 +302,15 @@ export function startRuntimeServer({
262
302
  const workspace = workspaceFromBody(body) ?? workspaceFromUrl(url);
263
303
  const result = await approve?.({
264
304
  workspace,
305
+ workspaceId: workspace,
265
306
  runId: url.searchParams.get('runId') ?? body.runId ?? null,
266
307
  itemId: url.searchParams.get('itemId') ?? body.itemId ?? null,
267
308
  approvalId: url.searchParams.get('approvalId') ?? body.approvalId ?? null,
309
+ scope: url.searchParams.get('scope') ?? body.scope ?? null,
310
+ taskId: url.searchParams.get('taskId') ?? body.taskId ?? null,
311
+ groupId: url.searchParams.get('groupId') ?? body.groupId ?? null,
312
+ planRevision: readOptionalNumber(url.searchParams.get('planRevision') ?? body.planRevision),
313
+ approvalClasses: readOptionalList(body.approvalClasses ?? body.approvalClass ?? url.searchParams.get('approvalClass')),
268
314
  });
269
315
  sendJson(response, result?.approved ? 202 : 404, result ?? { approved: false });
270
316
  return;
@@ -363,6 +409,20 @@ function workspaceFromBody(body) {
363
409
  return workspace == null ? null : String(workspace).trim() || null;
364
410
  }
365
411
 
412
+ function readTaskEndpoint(url) {
413
+ const parts = url.pathname.split('/').filter(Boolean).map((part) => decodeURIComponent(part));
414
+ if (parts.length === 3 && parts[0] === 'runs' && parts[2] === 'tasks') {
415
+ return { kind: 'run_tasks', runId: parts[1] };
416
+ }
417
+ if (parts.length === 3 && parts[0] === 'tasks' && parts[2] === 'attempts') {
418
+ return { kind: 'task_attempts', taskId: parts[1] };
419
+ }
420
+ if (parts.length === 3 && parts[0] === 'tasks' && parts[2] === 'result') {
421
+ return { kind: 'task_result', taskId: parts[1] };
422
+ }
423
+ return null;
424
+ }
425
+
366
426
  function controlStatus(context, store) {
367
427
  const workspace = context?.workspace ?? context?.session?.workspace ?? null;
368
428
  const state = runtimeState(context, store, { workspace });
@@ -419,29 +479,57 @@ function readOnlyControlResponse(kind, classification, status, explanation, { ac
419
479
  };
420
480
  }
421
481
 
422
- function handleControlMessage(context, store, input, { intent = null, startNextControlRequest = () => false } = {}) {
482
+ function approvalRequestFromStatus(status) {
483
+ const runId = status.runId ?? status.runs?.find((run) => run.status === 'running' || run.status === 'pending_approval')?.id ?? null;
484
+ const pending = (status.approvals ?? []).filter((approval) => approval.status === 'pending_approval');
485
+ const classes = [...new Set(pending.flatMap((approval) => readOptionalList(approval.approvalClasses ?? approval.approvalClass)))];
486
+ return {
487
+ workspace: status.workspace ?? null,
488
+ workspaceId: status.workspace ?? null,
489
+ runId,
490
+ scope: 'run',
491
+ planRevision: status.planRevision ?? null,
492
+ approvalClasses: classes.length > 0 ? classes : ['default'],
493
+ };
494
+ }
495
+
496
+ async function handleControlMessage(context, store, input, { intent = null, startNextControlRequest = () => false, cancel = null, approve = null } = {}) {
423
497
  const status = controlStatus(context, store);
424
498
  const classification = classifyControlMessage(input, status, intent);
425
499
  if (classification.kind === 'observe') {
426
500
  return readOnlyControlResponse('observe', classification, status, explainControlState(status));
427
501
  }
428
- if (classification.kind === 'mutate') {
502
+ if (classification.kind === 'cancel') {
503
+ if (context.running && context.currentAbortController) {
504
+ context.currentAbortController.abort();
505
+ await cancel?.(context);
506
+ return readOnlyControlResponse('cancel', classification, controlStatus(context, store), 'Runtime cancellation requested.', { accepted: true });
507
+ }
508
+ return readOnlyControlResponse('cancel', classification, status, 'No active run to cancel.', { accepted: false });
509
+ }
510
+ if (classification.kind === 'approve') {
511
+ const result = await approve?.(approvalRequestFromStatus(status));
512
+ return readOnlyControlResponse('approve', classification, controlStatus(context, store), result?.approved
513
+ ? 'Approval grant recorded for the current run revision.'
514
+ : 'Approval intent recorded; no pending approval was available to grant.', { accepted: result?.approved === true, extra: { approval: result ?? null } });
515
+ }
516
+ if (classification.kind === 'modify_run') {
429
517
  const proposal = storeControlProposal(context, input, classification, status);
430
518
  return {
431
519
  statusCode: 202,
432
520
  body: {
433
521
  accepted: true,
434
- kind: 'mutate',
522
+ kind: 'modify_run',
435
523
  classification,
436
524
  proposal,
437
525
  ...controlStatus(context, store),
438
- explanation: 'Plan patch proposed. Approve it explicitly to apply it to the active plan.',
526
+ explanation: controlMessage(context?.session, 'plan_patch_proposed'),
439
527
  },
440
528
  };
441
529
  }
442
- if (classification.kind === 'enqueue') {
530
+ if (classification.kind === 'enqueue_run') {
443
531
  const item = enqueueControlRequest(context, input);
444
- // Unlike `mutate`, this may synchronously start a queued run (see
532
+ // Unlike `modify_run`, this may synchronously start a queued run (see
445
533
  // startNextControlRequest), which can change running/plan/status — a full
446
534
  // controlStatus() recompute is required here, not just controlQueue.
447
535
  void startNextControlRequest(context);
@@ -449,28 +537,29 @@ function handleControlMessage(context, store, input, { intent = null, startNextC
449
537
  statusCode: 202,
450
538
  body: {
451
539
  accepted: true,
452
- kind: 'enqueue',
540
+ kind: 'enqueue_run',
453
541
  classification,
454
542
  item,
455
543
  ...controlStatus(context, store),
544
+ explanation: controlMessage(context?.session, 'queued_for_future_run'),
456
545
  },
457
546
  };
458
547
  }
459
548
  if (classification.kind === 'ambiguous') {
460
- return readOnlyControlResponse('ambiguous', classification, status, 'The runtime cannot safely classify this message.', {
549
+ return readOnlyControlResponse('ambiguous', classification, status, controlMessage(context?.session, 'ambiguous_control'), {
461
550
  accepted: false,
462
551
  extra: {
463
552
  choices: [
464
553
  { action: 'message', intent: 'observe', label: 'Ask about this run' },
465
- { action: 'message', intent: 'mutate', label: 'Propose a change to this run' },
466
- { action: 'enqueue', intent: 'enqueue', label: 'Queue as a future run' },
554
+ { action: 'message', intent: 'modify_run', label: 'Propose a change to this run' },
555
+ { action: 'enqueue', intent: 'enqueue_run', label: 'Queue as a future run' },
467
556
  ],
468
557
  },
469
558
  });
470
559
  }
471
560
  return readOnlyControlResponse('converse', classification, status, status.running
472
- ? 'Runtime run is still active. This message was treated as conversation and did not create a queued run.'
473
- : 'Runtime is idle. This message was treated as conversation and did not create a run.');
561
+ ? controlMessage(context?.session, 'converse_while_running')
562
+ : controlMessage(context?.session, 'converse_while_idle'));
474
563
  }
475
564
 
476
565
  function enqueueControlRequest(context, input) {
@@ -510,7 +599,7 @@ function storeControlProposal(context, input, classification, status) {
510
599
  origin: 'runtime',
511
600
  runId: context.currentRunId ?? status.runId ?? null,
512
601
  workspace: proposal.workspace,
513
- payload: { input, intent: 'mutate', classification },
602
+ payload: { input, intent: 'modify_run', classification },
514
603
  }));
515
604
  dispatchAgentEvent(context.session, createAgentEvent('plan_patch_proposed', {
516
605
  origin: 'runtime',
@@ -636,17 +725,34 @@ function classifyControlMessage(input, status, forcedIntent = null) {
636
725
  // Caller (the /control message route) already trims and rejects empty input.
637
726
  const lower = String(input ?? '').toLowerCase();
638
727
  const intent = forcedIntent ? String(forcedIntent).toLowerCase() : null;
639
- if (['observe', 'converse', 'mutate', 'enqueue'].includes(intent)) {
640
- return { kind: intent, confidence: 1, reason: 'explicit_intent' };
728
+ const explicit = {
729
+ observe: 'observe',
730
+ converse: 'converse',
731
+ approve: 'approve',
732
+ modify_run: 'modify_run',
733
+ mutate: 'modify_run',
734
+ enqueue_run: 'enqueue_run',
735
+ enqueue: 'enqueue_run',
736
+ cancel: 'cancel',
737
+ ambiguous: 'ambiguous',
738
+ }[intent];
739
+ if (explicit) {
740
+ return { kind: explicit, confidence: 1, reason: 'explicit_intent' };
741
+ }
742
+ if (/\b(valide tout|approve all|approve|approuve|valid[eé]|ok pour tout|go pour tout)\b/i.test(lower)) {
743
+ return { kind: 'approve', confidence: 0.86, reason: 'approval_request' };
744
+ }
745
+ if (/\b(cancel|annule|stop|arr[eê]te|interromps|abort)\b/i.test(lower)) {
746
+ return { kind: 'cancel', confidence: 0.86, reason: 'cancel_request' };
747
+ }
748
+ if (/\b(plus tard|later|ensuite|apr[eè]s ce run|enqueue|mets en file|met en file|futur|next run|future run)\b/i.test(lower)) {
749
+ return { kind: 'enqueue_run', confidence: 0.8, reason: 'future_run_request' };
641
750
  }
642
751
  if (/\b(o[uù] en es[t-]|status|statut|progress|progression|build|run|job|queue|file|logs?|explique|explain|inspect|show|montre|quoi de neuf)\b/i.test(lower)) {
643
752
  return { kind: 'observe', confidence: 0.86, reason: 'status_or_explanation_request' };
644
753
  }
645
754
  if (status.running && /\b(ajoute|add|change|modifie|modify|remplace|replace|retire|remove|skip|ignore|apr[eè]s|before|after|chaque|each|plan|step|t[aâ]che)\b/i.test(lower)) {
646
- return { kind: 'mutate', confidence: 0.78, reason: 'active_run_change_request' };
647
- }
648
- if (/\b(plus tard|later|ensuite|apr[eè]s ce run|apr[eè]s|enqueue|queue|mets en file|met en file|futur|next run|future run)\b/i.test(lower)) {
649
- return { kind: 'enqueue', confidence: 0.8, reason: 'future_run_request' };
755
+ return { kind: 'modify_run', confidence: 0.78, reason: 'active_run_change_request' };
650
756
  }
651
757
  if (status.running && /\b(lance|run|g[eé]n[eè]re|build|export|cr[eé]e|create|send|envoie|ingest|convert|importe|import)\b/i.test(lower)) {
652
758
  return { kind: 'ambiguous', confidence: 0.45, reason: 'active_run_action_is_ambiguous' };
@@ -688,6 +794,18 @@ function readRequiredPatchId(body, response) {
688
794
  return patchId;
689
795
  }
690
796
 
797
+ function readOptionalNumber(value) {
798
+ if (value == null || value === '') return null;
799
+ const number = Number(value);
800
+ return Number.isFinite(number) ? number : null;
801
+ }
802
+
803
+ function readOptionalList(value) {
804
+ if (value == null || value === '') return [];
805
+ if (Array.isArray(value)) return value.map(String).map((item) => item.trim()).filter(Boolean);
806
+ return String(value).split(',').map((item) => item.trim()).filter(Boolean);
807
+ }
808
+
691
809
  function readJson(request) {
692
810
  return new Promise((resolve, reject) => {
693
811
  let raw = '';