@dotdrelle/wiki-manager 0.15.27 → 0.15.28

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotdrelle/wiki-manager",
3
- "version": "0.15.27",
3
+ "version": "0.15.28",
4
4
  "description": "Agentic shell and orchestration cockpit for llm-wiki workspaces.",
5
5
  "license": "PolyForm-Noncommercial-1.0.0",
6
6
  "author": "dotrelle",
@@ -1435,13 +1435,20 @@ export async function runCli(argv) {
1435
1435
  const scaffolded = ensureManagerScaffold({ log: (message) => console.log(`[wiki-manager] ${message}`) });
1436
1436
  if (scaffolded.length > 0) loadManagerEnv();
1437
1437
  const reportCheck = ({ kind, ok, detail, context, skipped, pending }) => {
1438
+ // "waiting" checks (containers not up yet, MCP not connected yet) are the
1439
+ // normal state at launch — the shell starts them and reports the real
1440
+ // status afterwards, so echoing them here is pure noise. Only what is
1441
+ // ready or actually broken is printed.
1442
+ if (!ok && (pending || skipped)) return;
1438
1443
  const labels = { docker: 'Docker', internet: 'Internet', agents: 'Agent containers', workspace: 'Workspaces', containers: 'Workspace containers', mcp: 'MCP' };
1439
1444
  const label = labels[kind] ?? kind;
1440
1445
  const instruction = !ok && context?.command ? ` — command: ${context.command}` : '';
1441
- const suffix = detail ? ` — ${detail}` : ` — ${context?.error ?? context?.dockerError ?? 'waiting'}`;
1446
+ // On success the detail is diagnostic only (the probe URL for Internet);
1447
+ // the label already says what passed.
1448
+ const suffix = ok ? '' : ` — ${detail || context?.error || context?.dockerError || 'waiting'}`;
1442
1449
  const color = ok ? '\x1b[32m' : '\x1b[33m';
1443
- const state = ok ? 'ready' : pending || skipped ? 'waiting' : 'needs attention';
1444
- const icon = ok ? '✓' : pending || skipped ? '◐' : '✗';
1450
+ const state = ok ? 'ready' : 'needs attention';
1451
+ const icon = ok ? '✓' : '✗';
1445
1452
  console.log(`${color}${icon} configuration: ${label} ${state}${suffix}${instruction}\x1b[0m`);
1446
1453
  };
1447
1454
  let preflight = await runPreflightChecks({ onCheck: reportCheck });
@@ -1,4 +1,4 @@
1
1
  {
2
- "version": "0.15.27",
3
- "commit": "35b5770"
2
+ "version": "0.15.28",
3
+ "commit": "11b99e6"
4
4
  }
package/src/core/mcp.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
2
  import { managerEnvFile, managerMcpEndpointsFile, readEnvFile } from './env.js';
3
3
 
4
- const WIKI_MANAGER_VERSION = '0.15.27';
4
+ const WIKI_MANAGER_VERSION = '0.15.28';
5
5
 
6
6
  function envValue(key) {
7
7
  const filePath = managerEnvFile();
@@ -54,13 +54,29 @@ test('redo is offered on user questions only, next to copy', async () => {
54
54
  const session = await readFile(new URL('./useSession.ts', import.meta.url), 'utf8');
55
55
  // The runtime must be truncated first: its conversation is derived from the
56
56
  // event log and would be merged straight back in otherwise.
57
- const truncateIndex = session.indexOf('postRuntimeConversationTruncate(index');
57
+ const truncateIndex = session.indexOf('postRuntimeConversationTruncate(runtimeIndex');
58
58
  const spliceIndex = session.indexOf('full.splice(index)');
59
59
  const submitIndex = session.indexOf('return submitInput(content)');
60
60
  assert.ok(truncateIndex > 0 && spliceIndex > truncateIndex && submitIndex > spliceIndex);
61
61
  assert.match(session, /const windowOffset = Math\.max\(0, full\.length - messages\(\)\.length\);/);
62
62
  });
63
63
 
64
+ test('redo translates the local thread index into a runtime conversation index', async () => {
65
+ const session = await readFile(new URL('./useSession.ts', import.meta.url), 'utf8');
66
+ const redo = session.slice(
67
+ session.indexOf('async function redoMessage('),
68
+ session.indexOf('function updateInput('),
69
+ );
70
+ // The local thread holds entries the runtime never recorded (slash-command
71
+ // output, help panels). Passing the raw local index to /conversation/truncate
72
+ // is what produced `index_out_of_range`; `backed` is the translation table.
73
+ assert.match(redo, /const backed = runtimeConversationRefsByWorkspace\.get\(workspaceKey\)/);
74
+ assert.match(redo, /let runtimeIndex = backed\.indexOf\(full\[index\]\)/);
75
+ assert.match(redo, /if \(full\.indexOf\(backed\[i\]\) < index\)/);
76
+ assert.match(redo, /props\.runtime\?\.url && runtimeIndex >= 0/);
77
+ assert.doesNotMatch(redo, /postRuntimeConversationTruncate\(index/);
78
+ });
79
+
64
80
  test('ShellUI renders newest-first order in both Runtime and Agent status', async () => {
65
81
  const source = await readFile(new URL('./RightPane.tsx', import.meta.url), 'utf8');
66
82
  const filteredLogs = source.slice(
@@ -667,10 +667,28 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
667
667
  const index = windowOffset + displayIndex;
668
668
  if (index < 0 || index >= full.length) return { exit: false };
669
669
 
670
- if (props.runtime?.url) {
670
+ const workspaceKey = (session as any).workspace || '__global__';
671
+ // The local thread and the runtime conversation are NOT the same list:
672
+ // slash-command output, help panels and any other local-only entry live in
673
+ // `full` but were never recorded as events. Sending a `full` index straight
674
+ // to /conversation/truncate therefore points at the wrong entry — or past
675
+ // the end (`index_out_of_range`) — as soon as one local entry precedes the
676
+ // redone question. `backed` is index-aligned with the runtime conversation,
677
+ // so it is the only correct translation table.
678
+ const backed = runtimeConversationRefsByWorkspace.get(workspaceKey) ?? [];
679
+ let runtimeIndex = backed.indexOf(full[index]);
680
+ if (runtimeIndex === -1) {
681
+ // Local-only entry: truncate after the last runtime-backed entry that
682
+ // still precedes it, so nothing recorded before the question is lost.
683
+ for (let i = backed.length - 1; i >= 0; i -= 1) {
684
+ if (full.indexOf(backed[i]) < index) { runtimeIndex = i; break; }
685
+ }
686
+ }
687
+
688
+ if (props.runtime?.url && runtimeIndex >= 0) {
671
689
  // The runtime derives the conversation from its event log: deleting
672
690
  // locally alone would be undone by the next /state merge.
673
- const result = await postRuntimeConversationTruncate(index, {
691
+ const result = await postRuntimeConversationTruncate(runtimeIndex, {
674
692
  url: props.runtime.url,
675
693
  workspace: (session as any).workspace ?? null,
676
694
  }) as { truncated?: boolean; reason?: string };
@@ -690,8 +708,7 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
690
708
  // truncate above — clearing every workspace's refs here would desync
691
709
  // other workspaces' `backed` arrays from their untouched `target`
692
710
  // arrays and duplicate their history on the next merge.
693
- const currentWorkspace = (session as any).workspace || '__global__';
694
- runtimeConversationRefsByWorkspace.get(currentWorkspace)?.splice(0);
711
+ runtimeConversationRefsByWorkspace.get(workspaceKey)?.splice(0);
695
712
  refresh();
696
713
  return submitInput(content);
697
714
  }