@dotdrelle/wiki-manager 0.12.1 → 0.12.8

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 (43) hide show
  1. package/README.md +49 -8
  2. package/bin/wiki-manager +23 -12
  3. package/docker-compose.yml +0 -1
  4. package/mcp.endpoints.example.json +0 -6
  5. package/package.json +3 -2
  6. package/src/activity/activityAggregator.js +34 -3
  7. package/src/activity/activityAggregator.test.js +32 -0
  8. package/src/agent/graph.js +272 -22
  9. package/src/agent/graph.test.js +357 -1
  10. package/src/cli/wiki-manager.js +33 -2
  11. package/src/commands/slash.js +74 -1
  12. package/src/commands/slash.test.js +36 -0
  13. package/src/core/activity.js +4 -0
  14. package/src/core/activity.test.js +9 -1
  15. package/src/core/agentEvents.js +30 -0
  16. package/src/core/agentEvents.test.js +42 -1
  17. package/src/core/buildInfo.js +58 -0
  18. package/src/core/buildInfo.json +4 -0
  19. package/src/core/buildInfo.test.js +14 -0
  20. package/src/core/env.js +30 -1
  21. package/src/core/mcp.js +21 -1
  22. package/src/core/mcp.test.js +25 -0
  23. package/src/core/runtimeLog.js +6 -1
  24. package/src/core/runtimeLog.test.js +3 -1
  25. package/src/runtime/client.js +22 -0
  26. package/src/runtime/controlMessages.js +2 -2
  27. package/src/runtime/recoveryManager.js +54 -0
  28. package/src/runtime/recoveryManager.test.js +73 -0
  29. package/src/runtime/runner.js +49 -13
  30. package/src/runtime/runner.test.js +196 -0
  31. package/src/runtime/server.js +67 -8
  32. package/src/runtime/server.test.js +223 -6
  33. package/src/runtime/store.js +48 -3
  34. package/src/runtime/store.test.js +32 -0
  35. package/src/runtime/supervisor.js +77 -1
  36. package/src/shell/RightPane.tsx +124 -31
  37. package/src/shell/SetupWizard.tsx +13 -1
  38. package/src/shell/repl.js +78 -9
  39. package/src/shell/repl.test.js +114 -1
  40. package/src/shell/tui.tsx +4 -1
  41. package/src/shell/useAgent.ts +32 -3
  42. package/src/shell/useSession.ts +14 -1
  43. package/wiki-workspace +33 -0
@@ -1,6 +1,6 @@
1
1
  import { createSignal } from 'solid-js';
2
2
  import { postRuntimeCancel } from '../runtime/client.js';
3
- import { conversationMessages, recordRuntimeUnavailableAgentInput, runLine, submitRuntimeRun } from './repl.js';
3
+ import { conversationMessages, recordRuntimeUnavailableAgentInput, runLine, shouldHandleFreeTextLocally, submitRuntimeRun } from './repl.js';
4
4
 
5
5
  export function useAgent(props: { agent: unknown; packageJson: Record<string, unknown>; session: Record<string, any>; chatMode: () => boolean; runtimeUrl?: string | null; runtimeUnavailableReason?: string | null; refresh: () => void; addLog: (line: string) => void; onRuntimeAccepted?: () => void }) {
6
6
  const [busy, setBusy] = createSignal(false);
@@ -18,7 +18,17 @@ export function useAgent(props: { agent: unknown; packageJson: Record<string, un
18
18
  props.addLog(`input: ${trimmed}`);
19
19
 
20
20
  try {
21
- if (props.runtimeUrl && !props.chatMode() && !trimmed.startsWith('/')) {
21
+ // Questions and small talk are answered by the local agent: the reply
22
+ // shows up in the chat immediately and no runtime run (plan, SQLite
23
+ // state, replans) is created for a sentence that only needed an answer.
24
+ // Actions still go to the runtime below.
25
+ const freeTextRouting = (props.runtimeUrl && !props.chatMode() && !trimmed.startsWith('/'))
26
+ ? shouldHandleFreeTextLocally(trimmed, props.session)
27
+ : null;
28
+ if (props.runtimeUrl && !props.chatMode() && !trimmed.startsWith('/') && !freeTextRouting?.local) {
29
+ if (freeTextRouting?.fallbackReason) {
30
+ props.addLog(`runtime: ${freeTextRouting.fallbackReason}, routing to runtime run`);
31
+ }
22
32
  // Marked _pending so mergeRuntimeConversation (useSession.ts) can
23
33
  // confirm this exact entry instead of pushing a second copy once the
24
34
  // same user message comes back from the runtime's own /state.
@@ -29,6 +39,13 @@ export function useAgent(props: { agent: unknown; packageJson: Record<string, un
29
39
  });
30
40
  if (outcome.kind === 'accepted') {
31
41
  props.onRuntimeAccepted?.();
42
+ const runId = (outcome as any).result?.runId ?? null;
43
+ // Light, immediate feedback in the chat: without it an accepted run
44
+ // is invisible until its first activity lands in the side panels.
45
+ conversationMessages(props.session).push({
46
+ role: 'command',
47
+ content: `▶ Run accepté${runId ? ` (${String(runId).slice(0, 8)})` : ''} — progression dans le panneau Activity, réponse ici à la fin du run.`,
48
+ });
32
49
  props.addLog('runtime: run accepted');
33
50
  } else if (outcome.kind === 'queued') {
34
51
  // The server localizes control-lane acknowledgements from the
@@ -37,6 +54,11 @@ export function useAgent(props: { agent: unknown; packageJson: Record<string, un
37
54
  const explanation = (outcome as any).result?.explanation ?? 'Request added to the queue.';
38
55
  conversationMessages(props.session).push({ role: 'command', content: String(explanation) });
39
56
  props.addLog('runtime: control queued');
57
+ } else if ((outcome as any).result?.explanation) {
58
+ // Control-lane kinds (cancel / approve / observe / modify_run…):
59
+ // surface the server's localized explanation instead of an error.
60
+ conversationMessages(props.session).push({ role: 'command', content: String((outcome as any).result.explanation) });
61
+ props.addLog(`runtime: ${outcome.kind}`);
40
62
  } else {
41
63
  conversationMessages(props.session).push({ role: 'command', content: `Runtime error: ${outcome.message}` });
42
64
  props.addLog(`runtime error: ${outcome.message}`);
@@ -44,7 +66,7 @@ export function useAgent(props: { agent: unknown; packageJson: Record<string, un
44
66
  props.refresh();
45
67
  return { exit: false, runtime: true };
46
68
  }
47
- if (!props.chatMode() && !trimmed.startsWith('/')) {
69
+ if (!props.chatMode() && !trimmed.startsWith('/') && !freeTextRouting?.local) {
48
70
  const message = recordRuntimeUnavailableAgentInput(props.session, trimmed, {
49
71
  error: props.runtimeUnavailableReason ?? 'runtime introuvable',
50
72
  });
@@ -52,6 +74,9 @@ export function useAgent(props: { agent: unknown; packageJson: Record<string, un
52
74
  props.refresh();
53
75
  return { exit: false, runtimeUnavailable: true };
54
76
  }
77
+ if (freeTextRouting?.local) {
78
+ props.addLog(`agent: ${freeTextRouting.classification.kind} handled locally`);
79
+ }
55
80
  const result = await runLine(trimmed, {
56
81
  agent: props.agent,
57
82
  packageJson: props.packageJson,
@@ -59,6 +84,10 @@ export function useAgent(props: { agent: unknown; packageJson: Record<string, un
59
84
  onUpdate: props.refresh,
60
85
  onStep: props.addLog,
61
86
  chatMode: props.chatMode(),
87
+ // Without this, slash commands that target the runtime (/run kill,
88
+ // /run cancel, /run status) reported "Runtime unavailable" in the
89
+ // TUI even while the status bar showed "runtime: connected".
90
+ runtime: props.runtimeUrl ? { url: props.runtimeUrl } : null,
62
91
  });
63
92
  props.refresh();
64
93
  return result;
@@ -2,6 +2,7 @@ import { writeFileSync } from 'node:fs';
2
2
  import { isAbsolute, relative, resolve, sep } from 'node:path';
3
3
  import { createEffect, createMemo, createSignal, onCleanup } from 'solid-js';
4
4
  import { formatMcpToolResult, callMcpTool } from '../core/mcp.js';
5
+ import { versionWithBuild } from '../core/buildInfo.js';
5
6
  import { extractActivity, parseJsonText, sessionActivities } from '../core/activity.js';
6
7
  import { formatPlanStatus, formatCompletedActivities, formatPlanStep } from '../core/plan.js';
7
8
  import { createAgentEvent, dispatchAgentEvent } from '../core/agentEvents.js';
@@ -47,6 +48,12 @@ function buildContinuationPrompt(session: any, completed: any[]): string {
47
48
 
48
49
  export function useSession(props: { agent: unknown; packageJson: Record<string, unknown>; runtime?: any }) {
49
50
  const session = createSession();
51
+ // Donna's runtime__kill/cancel/status/approve/enqueue tools (graph.js)
52
+ // read session.runtime.url directly — without this the OpenTUI session
53
+ // never had it set (only the legacy runShell path did), so those tools
54
+ // reported "Runtime not connected" even with the runtime up and the
55
+ // status bar showing "connected".
56
+ (session as any).runtime = props.runtime ?? null;
50
57
  const [version, setVersion] = createSignal(0);
51
58
  const [logs, setLogs] = createSignal<string[]>([]);
52
59
  const [runtimeState, setRuntimeState] = createSignal<any | null>(null);
@@ -139,7 +146,7 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
139
146
  const statusLine = createMemo(() => {
140
147
  version();
141
148
  return [
142
- `wiki-manager ${props.packageJson.version ?? ''}`.trim(),
149
+ `wiki-manager ${versionWithBuild(props.packageJson)}`.trim(),
143
150
  session.workspace ? session.workspace : 'no workspace',
144
151
  session.wikirc?.profile ? session.wikirc.profile : 'no wikirc',
145
152
  session.language ? session.language : 'no language',
@@ -337,6 +344,12 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
337
344
  const target = conversationMessages(session);
338
345
  const backed = runtimeConversationRefsByWorkspace.get(workspace) ?? [];
339
346
  runtimeConversationRefsByWorkspace.set(workspace, backed);
347
+ // The merge is index-aligned with the runtime conversation array. If that
348
+ // array got SHORTER (runtime restart, projection reset), keeping stale
349
+ // refs would make every new runtime entry silently overwrite an old
350
+ // backed entry instead of appearing in the chat. Drop the refs and let
351
+ // the new conversation append cleanly.
352
+ if (runtimeConversation.length < backed.length) backed.length = 0;
340
353
  if (backed.length === 0 && target.length === 0 && !runtimeConversation.some((message: any) => message?._pending)) {
341
354
  backed.push(...runtimeConversation.map((message: any) => ({
342
355
  role: message.role === 'assistant' ? 'donna' : message.role,
package/wiki-workspace CHANGED
@@ -228,6 +228,12 @@ agents_compose() {
228
228
  shift
229
229
  fi
230
230
  [[ $# -eq 0 ]] || die "agents up accepts only --pull"
231
+ ensure_agent_tokens
232
+ ensure_endpoints_file
233
+ # The .env may have just been created above — recompute the env-file
234
+ # flag so freshly generated tokens reach the containers on first run.
235
+ compose_env_args=()
236
+ [[ -f "$MANAGER_ENV_FILE" ]] && compose_env_args+=(--env-file "$MANAGER_ENV_FILE")
231
237
  mkdir -p "$agents_data_dir/cme" "$agents_data_dir/documents/input" "$agents_data_dir/documents/output" "$agents_data_dir/documents/uploads"
232
238
  mkdir -p "$workspaces_root"
233
239
  $do_pull && _agents_dc pull
@@ -382,6 +388,33 @@ ensure_runtime_node() {
382
388
  [[ "$major" =~ ^[0-9]+$ && "$major" -ge 22 ]] || die "runtime requires Node.js 22+ for node:sqlite; $node_bin is Node $version"
383
389
  }
384
390
 
391
+ ensure_agent_tokens() {
392
+ # The external agents (cme, documents, mailer) authenticate with tokens
393
+ # from the manager .env — generate any missing one, exactly like the
394
+ # workspace and runtime tokens are generated. Leaving them empty made a
395
+ # fresh install start agents nobody could talk to.
396
+ local key token
397
+ for key in CME_MCP_AUTH_TOKEN DOCUMENTS_MCP_AUTH_TOKEN MAILER_MCP_AUTH_TOKEN; do
398
+ token="$(env_value "$MANAGER_ENV_FILE" "$key" "")"
399
+ if [[ -z "$token" ]]; then
400
+ token="$(generate_token)"
401
+ mkdir -p "$(dirname "$MANAGER_ENV_FILE")"
402
+ set_env_value "$MANAGER_ENV_FILE" "$key" "$token"
403
+ printf 'Generated %s in %s\n' "$key" "$MANAGER_ENV_FILE"
404
+ fi
405
+ done
406
+ }
407
+
408
+ ensure_endpoints_file() {
409
+ # Without mcp.endpoints.json the shell never connects to the agents it
410
+ # just started (and `agents status` refuses to run). Seed it from the
411
+ # packaged example; the \${VAR} placeholders resolve against the .env.
412
+ if [[ ! -f "$MANAGER_ENDPOINTS_FILE" && -f "$ROOT_DIR/mcp.endpoints.example.json" ]]; then
413
+ cp "$ROOT_DIR/mcp.endpoints.example.json" "$MANAGER_ENDPOINTS_FILE"
414
+ printf 'Created %s from packaged example\n' "$MANAGER_ENDPOINTS_FILE"
415
+ fi
416
+ }
417
+
385
418
  ensure_manager_runtime_token() {
386
419
  local token
387
420
  token="$(env_value "$MANAGER_ENV_FILE" WIKI_MANAGER_RUNTIME_TOKEN "")"