@dotdrelle/wiki-manager 0.14.13 → 0.14.20
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/.env.example +29 -4
- package/README.md +19 -0
- package/docker-compose.yml +6 -1
- package/package.json +1 -1
- package/src/activity/activityAggregator.js +50 -16
- package/src/activity/activityAggregator.test.js +67 -4
- package/src/agent/graph.js +79 -11
- package/src/agent/graph.test.js +43 -3
- package/src/cli/wiki-manager.js +74 -11
- package/src/cli/wiki-manager.test.js +40 -1
- package/src/commands/slash.js +10 -3
- package/src/commands/slash.test.js +24 -0
- package/src/core/buildInfo.json +2 -2
- package/src/core/dockerCompose.test.js +4 -0
- package/src/core/env.test.js +3 -0
- package/src/core/mcp.js +1 -1
- package/src/core/wikiSetup.js +35 -0
- package/src/core/wikiWorkspace.test.js +20 -0
- package/src/core/workflow.js +72 -0
- package/src/core/workflow.test.js +57 -0
- package/src/core/workspaces.js +10 -2
- package/src/orchestrator/dependencyResolver.js +19 -1
- package/src/orchestrator/objectiveResolver.js +24 -0
- package/src/orchestrator/objectiveResolver.test.js +23 -1
- package/src/orchestrator/scheduler.js +27 -7
- package/src/orchestrator/scheduler.test.js +45 -1
- package/src/runtime/auth.test.js +65 -1
- package/src/runtime/client.js +4 -0
- package/src/runtime/donna-contract.test.js +2 -0
- package/src/runtime/lifecycle.js +21 -12
- package/src/runtime/runner.js +141 -18
- package/src/runtime/runner.test.js +30 -0
- package/src/runtime/server.js +13 -2
- package/src/runtime/server.test.js +30 -1
- package/src/runtime/store.js +54 -0
- package/src/runtime/store.test.js +42 -0
- package/src/shell/FileEditorDialog.tsx +2 -2
- package/src/shell/LeftPane.tsx +60 -15
- package/src/shell/RightPane.tsx +168 -56
- package/src/shell/StartupScreen.tsx +3 -7
- package/src/shell/renderer.ts +1 -0
- package/src/shell/repl.js +7 -81
- package/src/shell/repl.test.js +141 -38
- package/src/shell/tui.tsx +65 -65
- package/src/shell/useSession.ts +92 -6
- package/wiki-workspace +28 -0
package/src/shell/useSession.ts
CHANGED
|
@@ -321,6 +321,52 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
|
|
|
321
321
|
? lastVisiblePlan.map((step) => ({ ...step }))
|
|
322
322
|
: current;
|
|
323
323
|
});
|
|
324
|
+
const runSummary = createMemo(() => {
|
|
325
|
+
version();
|
|
326
|
+
const workflow = runtimeState()?.workflow;
|
|
327
|
+
const tasks = Array.isArray(workflow?.nodes)
|
|
328
|
+
? workflow.nodes.filter((node: any) => node.type === 'task')
|
|
329
|
+
: [];
|
|
330
|
+
if (tasks.length === 0) return null;
|
|
331
|
+
|
|
332
|
+
const graphNodes = Array.isArray(workflow?.graph?.nodes) ? workflow.graph.nodes : [];
|
|
333
|
+
const graphEdges = Array.isArray(workflow?.graph?.edges) ? workflow.graph.edges : [];
|
|
334
|
+
const agents = new Set<string>();
|
|
335
|
+
for (const task of tasks) {
|
|
336
|
+
if (task.executor) agents.add(String(task.executor));
|
|
337
|
+
const assignmentIds = graphEdges
|
|
338
|
+
.filter((edge: any) => edge.type === 'assigned_to' && edge.from === task.id)
|
|
339
|
+
.map((edge: any) => edge.to);
|
|
340
|
+
for (const assignmentId of assignmentIds) {
|
|
341
|
+
graphEdges
|
|
342
|
+
.filter((edge: any) => edge.type === 'uses_agent' && edge.from === assignmentId)
|
|
343
|
+
.forEach((edge: any) => agents.add(String(edge.to).replace(/^agent:/, '')));
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const activeParallel = tasks.filter((task: any) => String(task.status) === 'running').length;
|
|
348
|
+
const groupConcurrency = graphNodes
|
|
349
|
+
.filter((node: any) => node.type === 'task_group')
|
|
350
|
+
.map((node: any) => Number(node.raw?.recommendedConcurrency))
|
|
351
|
+
.filter((value: number) => Number.isFinite(value) && value > 0);
|
|
352
|
+
// Authoritative resolved concurrency published by the runtime; fall back to
|
|
353
|
+
// the plan-derived value for replayed/historical runs.
|
|
354
|
+
const resolved = runtimeState()?.concurrency;
|
|
355
|
+
const maxParallel = Number.isFinite(Number(resolved?.limit))
|
|
356
|
+
? Number(resolved.limit)
|
|
357
|
+
: Math.max(1, activeParallel, ...groupConcurrency);
|
|
358
|
+
const done = tasks.filter((task: any) => String(task.status) === 'done').length;
|
|
359
|
+
const usage = workflow?.usage ?? {};
|
|
360
|
+
const tokens = (known: unknown, value: unknown) =>
|
|
361
|
+
known ? new Intl.NumberFormat().format(Number(value) || 0) : '—';
|
|
362
|
+
|
|
363
|
+
return [
|
|
364
|
+
`${agents.size} agent${agents.size === 1 ? '' : 's'}`,
|
|
365
|
+
`parallel ${activeParallel}/×${maxParallel}${resolved?.cappedByCeiling ? ' (ceiling)' : ''}`,
|
|
366
|
+
`${done}/${tasks.length} tasks`,
|
|
367
|
+
`${tokens(usage.inputKnown, usage.inputTokens)} in · ${tokens(usage.outputKnown, usage.outputTokens)} out`,
|
|
368
|
+
].join(' · ');
|
|
369
|
+
});
|
|
324
370
|
const visibleLogs = createMemo(() => {
|
|
325
371
|
version();
|
|
326
372
|
const runtimeLogs = runtimeState()?.logs;
|
|
@@ -329,6 +375,11 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
|
|
|
329
375
|
const tagged = filterRuntimeLogs(runtimeLogs.map((line: any) => `runtime ${String(line)}`), filter);
|
|
330
376
|
return [...logs(), ...tagged];
|
|
331
377
|
});
|
|
378
|
+
const pendingApprovals = createMemo(() => {
|
|
379
|
+
version();
|
|
380
|
+
return (Array.isArray(runtimeState()?.approvals) ? runtimeState().approvals : [])
|
|
381
|
+
.filter((approval: any) => approval.status === 'pending_approval');
|
|
382
|
+
});
|
|
332
383
|
|
|
333
384
|
// Index-aligned with each workspace's runtimeConversation array — not just
|
|
334
385
|
// a length count. The server sometimes finalizes a streaming placeholder
|
|
@@ -379,16 +430,23 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
|
|
|
379
430
|
continue;
|
|
380
431
|
}
|
|
381
432
|
if (role === 'user') {
|
|
382
|
-
// The user's own message was already pushed optimistically
|
|
383
|
-
//
|
|
384
|
-
//
|
|
385
|
-
//
|
|
433
|
+
// The user's own message was already pushed optimistically — either
|
|
434
|
+
// marked _pending (web / runtime-submit path, useAgent.ts) OR as a plain
|
|
435
|
+
// entry by the interactive agent turn (repl.js runAgentTurn) that then
|
|
436
|
+
// delegated. Adopt whichever already exists instead of appending a
|
|
437
|
+
// second identical copy (the delegation seeds the run with the same
|
|
438
|
+
// objective, which comes back through this merge).
|
|
386
439
|
const pendingIndex = target.findIndex((entry: any) => entry._pending && entry.role === 'user' && entry.content === content);
|
|
387
440
|
if (pendingIndex !== -1) {
|
|
388
441
|
delete target[pendingIndex]._pending;
|
|
389
442
|
backed.push(target[pendingIndex]);
|
|
390
443
|
continue;
|
|
391
444
|
}
|
|
445
|
+
const localIndex = target.findIndex((entry: any) => entry.role === 'user' && entry.content === content && !backed.includes(entry));
|
|
446
|
+
if (localIndex !== -1) {
|
|
447
|
+
backed.push(target[localIndex]);
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
392
450
|
}
|
|
393
451
|
const entry = { role, content };
|
|
394
452
|
target.push(entry);
|
|
@@ -397,19 +455,37 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
|
|
|
397
455
|
}
|
|
398
456
|
|
|
399
457
|
function syncRuntimeState() {
|
|
400
|
-
|
|
458
|
+
const workspace = (session as any).workspace ?? null;
|
|
459
|
+
const workspaceKey = workspace ?? '__global__';
|
|
460
|
+
if (runtimeSyncInFlight.has(workspaceKey)) return;
|
|
461
|
+
runtimeSyncInFlight.add(workspaceKey);
|
|
462
|
+
void fetchRuntimeState({ url: props.runtime.url, workspace })
|
|
401
463
|
.then((state) => {
|
|
464
|
+
if (((session as any).workspace ?? null) !== workspace) return;
|
|
402
465
|
const displayState = sanitizeRuntimeStateForDisplay(state);
|
|
403
466
|
setRuntimeState(displayState);
|
|
467
|
+
// The per-job "Job terminé" lines and the canned plan summary are gone:
|
|
468
|
+
// the runtime now emits ONE natural-language Donna message at completion
|
|
469
|
+
// (see announceRunOutcome in runner.js), which arrives through the
|
|
470
|
+
// conversation merge below. Detect that the chat grew to keep the
|
|
471
|
+
// welcome-hidden / scroll-to-latest behavior.
|
|
472
|
+
const beforeLength = conversationMessages(session).length;
|
|
404
473
|
mergeRuntimeConversation(displayState);
|
|
474
|
+
if (conversationMessages(session).length > beforeLength) {
|
|
475
|
+
setShowWelcome(false);
|
|
476
|
+
setConversationScroll(0);
|
|
477
|
+
}
|
|
405
478
|
setRuntimeStatus('connected');
|
|
406
479
|
refresh();
|
|
407
480
|
})
|
|
408
481
|
.catch(() => {
|
|
482
|
+
if (((session as any).workspace ?? null) !== workspace) return;
|
|
409
483
|
setRuntimeStatus('disconnected');
|
|
410
484
|
refresh();
|
|
411
|
-
})
|
|
485
|
+
})
|
|
486
|
+
.finally(() => { runtimeSyncInFlight.delete(workspaceKey); });
|
|
412
487
|
}
|
|
488
|
+
const runtimeSyncInFlight = new Set<string>();
|
|
413
489
|
let runtimeStreamAbort: AbortController | null = null;
|
|
414
490
|
let runtimeSyncDebounce: ReturnType<typeof setTimeout> | null = null;
|
|
415
491
|
let runtimeReconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
|
@@ -446,6 +522,13 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
|
|
|
446
522
|
void subscribeRuntimeEvents();
|
|
447
523
|
}
|
|
448
524
|
|
|
525
|
+
// Safety net only — the SSE stream (subscribeRuntimeEvents) is the primary
|
|
526
|
+
// sync path and already debounces on every agent_event. This just catches
|
|
527
|
+
// a missed/dropped event, so it can poll far less often than that.
|
|
528
|
+
const runtimeCompletionWatchdog = setInterval(() => {
|
|
529
|
+
if (props.runtime?.url && runtimeRunStatus() === 'running') syncRuntimeState();
|
|
530
|
+
}, 10000);
|
|
531
|
+
|
|
449
532
|
let currentRuntimeWorkspace = (session as any).workspace ?? null;
|
|
450
533
|
function resyncRuntimeWorkspaceIfChanged() {
|
|
451
534
|
const workspace = (session as any).workspace ?? null;
|
|
@@ -466,6 +549,7 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
|
|
|
466
549
|
runtimeStreamAbort?.abort();
|
|
467
550
|
if (runtimeSyncDebounce) clearTimeout(runtimeSyncDebounce);
|
|
468
551
|
if (runtimeReconnectTimer) clearTimeout(runtimeReconnectTimer);
|
|
552
|
+
clearInterval(runtimeCompletionWatchdog);
|
|
469
553
|
});
|
|
470
554
|
|
|
471
555
|
const activityPollTimer = setInterval(() => {
|
|
@@ -690,6 +774,8 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
|
|
|
690
774
|
toggleRightTab,
|
|
691
775
|
selectRightTab,
|
|
692
776
|
plan,
|
|
777
|
+
runSummary,
|
|
778
|
+
pendingApprovals,
|
|
693
779
|
conversationScroll,
|
|
694
780
|
scrollConversation,
|
|
695
781
|
conversationBusy,
|
package/wiki-workspace
CHANGED
|
@@ -95,6 +95,7 @@ Commands:
|
|
|
95
95
|
agents down Stop all external agents
|
|
96
96
|
agents logs [args...] Follow external agent logs
|
|
97
97
|
agents pull Pull latest external agent images
|
|
98
|
+
agents refresh Update only external agents currently running
|
|
98
99
|
agents status Check reachability of configured MCP endpoints
|
|
99
100
|
|
|
100
101
|
runtime up Start the single host agent-runtime on Node.js 22
|
|
@@ -276,6 +277,10 @@ agents_compose() {
|
|
|
276
277
|
[[ $# -eq 0 ]] || die "agents pull does not take arguments"
|
|
277
278
|
_agents_dc pull
|
|
278
279
|
;;
|
|
280
|
+
refresh)
|
|
281
|
+
[[ $# -eq 0 ]] || die "agents refresh does not take arguments"
|
|
282
|
+
refresh_running_services 'No running external agent containers to refresh' 'Refreshed running agents: %s' _agents_dc
|
|
283
|
+
;;
|
|
279
284
|
status)
|
|
280
285
|
[[ $# -eq 0 ]] || die "agents status does not take arguments"
|
|
281
286
|
if [[ ! -f "$MANAGER_ENDPOINTS_FILE" ]]; then
|
|
@@ -751,10 +756,30 @@ read_lines_into_array() {
|
|
|
751
756
|
eval "$array_name=()"
|
|
752
757
|
local line
|
|
753
758
|
while IFS= read -r line; do
|
|
759
|
+
[[ -n "${line//[[:space:]]/}" ]] || continue
|
|
754
760
|
eval "$array_name+=(\"\$line\")"
|
|
755
761
|
done < <("$@")
|
|
756
762
|
}
|
|
757
763
|
|
|
764
|
+
# Shared by the "agents refresh" and per-workspace "refresh" subcommands:
|
|
765
|
+
# pull and recreate only the compose services already running, skipping the
|
|
766
|
+
# rest. "$@" is the compose invocation (e.g. `_agents_dc` or
|
|
767
|
+
# `compose_for_workspace "$workspace"`) to run ps/pull/up through.
|
|
768
|
+
refresh_running_services() {
|
|
769
|
+
local no_running_msg="$1"
|
|
770
|
+
local refreshed_msg_fmt="$2"
|
|
771
|
+
shift 2
|
|
772
|
+
local running_services=()
|
|
773
|
+
read_lines_into_array running_services "$@" ps --status running --services
|
|
774
|
+
if [[ ${#running_services[@]} -eq 0 ]]; then
|
|
775
|
+
printf '%s\n' "$no_running_msg"
|
|
776
|
+
return
|
|
777
|
+
fi
|
|
778
|
+
"$@" pull "${running_services[@]}"
|
|
779
|
+
"$@" up -d "${running_services[@]}"
|
|
780
|
+
printf "$refreshed_msg_fmt\n" "${running_services[*]}"
|
|
781
|
+
}
|
|
782
|
+
|
|
758
783
|
cacert_compose_args() {
|
|
759
784
|
local compose_file="$1"
|
|
760
785
|
local override_name="$2"
|
|
@@ -1074,6 +1099,9 @@ main() {
|
|
|
1074
1099
|
down)
|
|
1075
1100
|
compose_for_workspace "$workspace" down
|
|
1076
1101
|
;;
|
|
1102
|
+
refresh)
|
|
1103
|
+
refresh_running_services "No running workspace containers to refresh: $workspace" "Refreshed running workspace containers: $workspace (%s)" compose_for_workspace "$workspace"
|
|
1104
|
+
;;
|
|
1077
1105
|
logs)
|
|
1078
1106
|
local log_args=()
|
|
1079
1107
|
read_lines_into_array log_args logs_args "$@"
|