@dotdrelle/wiki-manager 0.11.4 → 0.11.7
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/agents.docker-compose.yml +3 -3
- package/package.json +2 -2
- package/src/agent/graph.js +118 -5
- package/src/agent/graph.test.js +158 -1
- package/src/cli/wiki-manager.js +9 -2
- package/src/commands/slash.js +36 -11
- package/src/core/agentLoop.js +20 -5
- package/src/core/agentLoop.test.js +19 -1
- package/src/core/mcp.js +1 -1
- package/src/core/planPatch.js +39 -1
- package/src/core/planPatch.test.js +23 -1
- package/src/core/profile.js +111 -0
- package/src/core/skills.js +1 -1
- package/src/runtime/client.js +12 -0
- package/src/runtime/donna-contract.test.js +359 -0
- package/src/runtime/runner.js +72 -3
- package/src/runtime/runner.test.js +113 -3
- package/src/runtime/server.js +27 -1
- package/src/runtime/server.test.js +38 -0
- package/src/shell/LeftPane.tsx +2 -1
- package/src/shell/StartupScreen.tsx +118 -23
- package/src/shell/repl.js +95 -42
- package/src/shell/repl.test.js +59 -1
- package/src/shell/tui.tsx +90 -75
- package/src/shell/useAgent.ts +14 -4
- package/src/shell/useSession.ts +92 -17
package/src/shell/useSession.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { writeFileSync } from 'node:fs';
|
|
2
2
|
import { isAbsolute, relative, resolve, sep } from 'node:path';
|
|
3
|
-
import { createMemo, createSignal, onCleanup } from 'solid-js';
|
|
3
|
+
import { createEffect, createMemo, createSignal, onCleanup } from 'solid-js';
|
|
4
4
|
import { formatMcpToolResult, callMcpTool } from '../core/mcp.js';
|
|
5
5
|
import { extractActivity, parseJsonText, sessionActivities } from '../core/activity.js';
|
|
6
6
|
import { formatPlanStatus, formatCompletedActivities } from '../core/plan.js';
|
|
@@ -14,12 +14,13 @@ import {
|
|
|
14
14
|
completionDescription,
|
|
15
15
|
conversationMessages,
|
|
16
16
|
createSession,
|
|
17
|
+
runtimeUnavailableAgentMessage,
|
|
17
18
|
} from './repl.js';
|
|
18
19
|
import { useAgent } from './useAgent';
|
|
19
20
|
|
|
20
|
-
function runtimeStatusText(status: 'disabled' | 'connected' | 'disconnected', runStatus: string): string {
|
|
21
|
-
if (status === 'connected') return `runtime ${runStatus}`;
|
|
22
|
-
if (status === 'disconnected') return 'runtime offline';
|
|
21
|
+
function runtimeStatusText(status: 'disabled' | 'connected' | 'disconnected', runStatus: string, reason: string | null, workspace: string | null): string {
|
|
22
|
+
if (status === 'connected') return `runtime: connected (${workspace ?? 'no workspace'}) ${runStatus}`;
|
|
23
|
+
if (status === 'disconnected') return reason ? `runtime: disconnected: ${reason}` : 'runtime offline';
|
|
23
24
|
return 'runtime off';
|
|
24
25
|
}
|
|
25
26
|
|
|
@@ -74,13 +75,21 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
|
|
|
74
75
|
const addLog = (line: string) => {
|
|
75
76
|
setLogs((items) => [...items, `${new Date().toLocaleTimeString()} ${line}`].slice(-200));
|
|
76
77
|
};
|
|
78
|
+
const runtimeUnavailableReason = createMemo(() => {
|
|
79
|
+
if (props.runtime?.url) return null;
|
|
80
|
+
const reason = props.runtime?.error ?? props.runtime?.unavailableReason ?? props.runtime?.reason ?? null;
|
|
81
|
+
return reason ? String(reason) : null;
|
|
82
|
+
});
|
|
77
83
|
if (props.runtime?.url) addLog(`runtime: connected ${props.runtime.url}${props.runtime.started ? ' (started)' : ''}`);
|
|
84
|
+
else if (props.runtime?.error) addLog(`runtime: unavailable — ${props.runtime.error}`);
|
|
85
|
+
else if (runtimeUnavailableReason()) addLog(`runtime: disconnected: ${runtimeUnavailableReason()}`);
|
|
78
86
|
const agent = useAgent({
|
|
79
87
|
agent: props.agent,
|
|
80
88
|
packageJson: props.packageJson,
|
|
81
89
|
session,
|
|
82
90
|
chatMode,
|
|
83
91
|
runtimeUrl: props.runtime?.url ?? null,
|
|
92
|
+
runtimeUnavailableReason: runtimeUnavailableReason(),
|
|
84
93
|
refresh,
|
|
85
94
|
addLog,
|
|
86
95
|
onRuntimeAccepted: () => {
|
|
@@ -97,23 +106,22 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
|
|
|
97
106
|
);
|
|
98
107
|
const localFallbackActive = createMemo(() => !props.runtime?.url || runtimeStatus() === 'disconnected');
|
|
99
108
|
|
|
109
|
+
// Runtime conversation entries are merged into conversationMessages(session)
|
|
110
|
+
// (see mergeRuntimeConversation below) as they arrive, so they land in true
|
|
111
|
+
// chronological order alongside local command output (/status, /use, ...)
|
|
112
|
+
// instead of being reconstructed as two separately-ordered blocks at render
|
|
113
|
+
// time — that used to put every local command before the whole runtime
|
|
114
|
+
// history regardless of when each one actually happened.
|
|
100
115
|
const messages = createMemo(() => {
|
|
101
116
|
version();
|
|
117
|
+
return [...conversationMessages(session)].slice(-200);
|
|
118
|
+
});
|
|
119
|
+
createEffect(() => {
|
|
102
120
|
const runtimeConversation = runtimeState()?.conversation;
|
|
103
|
-
const localCommands = conversationMessages(session)
|
|
104
|
-
.filter((message: any) => message.role === 'command')
|
|
105
|
-
.map((message: any) => ({
|
|
106
|
-
role: 'command',
|
|
107
|
-
content: String(message.content ?? ''),
|
|
108
|
-
}));
|
|
109
121
|
if (Array.isArray(runtimeConversation) && runtimeConversation.length > 0) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
content: String(message.content ?? ''),
|
|
113
|
-
}));
|
|
114
|
-
return [...runtimeMessages, ...localCommands].slice(-200);
|
|
122
|
+
setShowWelcome(false);
|
|
123
|
+
setConversationScroll(0);
|
|
115
124
|
}
|
|
116
|
-
return [...conversationMessages(session)];
|
|
117
125
|
});
|
|
118
126
|
const prompt = createMemo(() => {
|
|
119
127
|
version();
|
|
@@ -133,9 +141,14 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
|
|
|
133
141
|
session.wikirc?.profile ? session.wikirc.profile : 'no wikirc',
|
|
134
142
|
session.language ? session.language : 'no language',
|
|
135
143
|
session.llm ? 'llm ready' : 'llm limited',
|
|
136
|
-
runtimeStatusText(runtimeStatus(), runtimeRunStatus()),
|
|
144
|
+
runtimeStatusText(runtimeStatus(), runtimeRunStatus(), runtimeUnavailableReason(), session.workspace),
|
|
137
145
|
].join(' ');
|
|
138
146
|
});
|
|
147
|
+
const runtimeHint = createMemo(() => {
|
|
148
|
+
version();
|
|
149
|
+
if (props.runtime?.url) return null;
|
|
150
|
+
return runtimeUnavailableAgentMessage({ error: runtimeUnavailableReason() ?? 'runtime introuvable' });
|
|
151
|
+
});
|
|
139
152
|
const matchContext = createMemo(() => {
|
|
140
153
|
if (input() === dismissedSlashInput()) return null;
|
|
141
154
|
return completionContext(input(), session);
|
|
@@ -263,10 +276,54 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
|
|
|
263
276
|
return [...logs(), ...tagged].slice(-200);
|
|
264
277
|
});
|
|
265
278
|
|
|
279
|
+
// Index-aligned with each workspace's runtimeConversation array — not just
|
|
280
|
+
// a length count. The server sometimes finalizes a streaming placeholder
|
|
281
|
+
// (queued during a tool-calling turn, never closed out because that turn's
|
|
282
|
+
// reply had no final text) by overwriting the *same* conversation entry's
|
|
283
|
+
// content on a later turn, without the array growing. A length-based diff
|
|
284
|
+
// would miss that update entirely and leave the stale placeholder text
|
|
285
|
+
// shown forever; tracking local entry references per index picks it up.
|
|
286
|
+
const runtimeConversationRefsByWorkspace = new Map<string, any[]>();
|
|
287
|
+
function mergeRuntimeConversation(state: any) {
|
|
288
|
+
const workspace = (session as any).workspace || '__global__';
|
|
289
|
+
const runtimeConversation = Array.isArray(state?.conversation) ? state.conversation : [];
|
|
290
|
+
if (runtimeConversation.length === 0) return;
|
|
291
|
+
const target = conversationMessages(session);
|
|
292
|
+
const backed = runtimeConversationRefsByWorkspace.get(workspace) ?? [];
|
|
293
|
+
runtimeConversationRefsByWorkspace.set(workspace, backed);
|
|
294
|
+
for (let i = 0; i < runtimeConversation.length; i += 1) {
|
|
295
|
+
const message = runtimeConversation[i];
|
|
296
|
+
const content = String(message.content ?? '');
|
|
297
|
+
const role = message.role === 'assistant' ? 'donna' : message.role;
|
|
298
|
+
if (i < backed.length) {
|
|
299
|
+
const entry = backed[i];
|
|
300
|
+
if (entry.content !== content) entry.content = content;
|
|
301
|
+
if (entry.role !== role) entry.role = role;
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if (role === 'user') {
|
|
305
|
+
// The user's own message was already pushed optimistically (see
|
|
306
|
+
// useAgent.ts) and marked _pending — confirm that entry instead of
|
|
307
|
+
// adding a second copy. No matching pending entry (e.g. history
|
|
308
|
+
// restored after a fresh reconnect) falls through to a normal push.
|
|
309
|
+
const pendingIndex = target.findIndex((entry: any) => entry._pending && entry.role === 'user' && entry.content === content);
|
|
310
|
+
if (pendingIndex !== -1) {
|
|
311
|
+
delete target[pendingIndex]._pending;
|
|
312
|
+
backed.push(target[pendingIndex]);
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
const entry = { role, content };
|
|
317
|
+
target.push(entry);
|
|
318
|
+
backed.push(entry);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
266
322
|
function syncRuntimeState() {
|
|
267
323
|
void fetchRuntimeState({ url: props.runtime.url, workspace: (session as any).workspace ?? null })
|
|
268
324
|
.then((state) => {
|
|
269
325
|
setRuntimeState(state);
|
|
326
|
+
mergeRuntimeConversation(state);
|
|
270
327
|
setRuntimeStatus('connected');
|
|
271
328
|
refresh();
|
|
272
329
|
})
|
|
@@ -310,6 +367,22 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
|
|
|
310
367
|
syncRuntimeState();
|
|
311
368
|
void subscribeRuntimeEvents();
|
|
312
369
|
}
|
|
370
|
+
|
|
371
|
+
let currentRuntimeWorkspace = (session as any).workspace ?? null;
|
|
372
|
+
function resyncRuntimeWorkspaceIfChanged() {
|
|
373
|
+
const workspace = (session as any).workspace ?? null;
|
|
374
|
+
if (workspace === currentRuntimeWorkspace) return;
|
|
375
|
+
currentRuntimeWorkspace = workspace;
|
|
376
|
+
if (!props.runtime?.url) return;
|
|
377
|
+
if (runtimeReconnectTimer) {
|
|
378
|
+
clearTimeout(runtimeReconnectTimer);
|
|
379
|
+
runtimeReconnectTimer = null;
|
|
380
|
+
}
|
|
381
|
+
runtimeStreamAbort?.abort();
|
|
382
|
+
syncRuntimeState();
|
|
383
|
+
void subscribeRuntimeEvents();
|
|
384
|
+
}
|
|
385
|
+
|
|
313
386
|
onCleanup(() => {
|
|
314
387
|
runtimeStreamStopped = true;
|
|
315
388
|
runtimeStreamAbort?.abort();
|
|
@@ -408,6 +481,7 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
|
|
|
408
481
|
lastActivityLines.clear();
|
|
409
482
|
lastActivityDetails.clear();
|
|
410
483
|
const result = await agent.submit(line);
|
|
484
|
+
resyncRuntimeWorkspaceIfChanged();
|
|
411
485
|
if ((result as any)?.setMode === 'chat') {
|
|
412
486
|
setChatMode(true);
|
|
413
487
|
(session as any).chatMode = true;
|
|
@@ -523,6 +597,7 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
|
|
|
523
597
|
setInput: updateInput,
|
|
524
598
|
title,
|
|
525
599
|
statusLine,
|
|
600
|
+
runtimeHint,
|
|
526
601
|
chatMode,
|
|
527
602
|
showWelcome,
|
|
528
603
|
activeEditor,
|