@dotdrelle/wiki-manager 0.6.34 → 0.7.3
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/bin/wiki-manager.js +2 -2
- package/docker-compose.yml +25 -1
- package/package.json +2 -2
- package/src/agent/graph.js +2 -19
- package/src/cli/wiki-manager.js +236 -129
- package/src/commands/slash.js +17 -1
- package/src/commands/slash.test.js +78 -0
- package/src/core/activity.js +14 -0
- package/src/core/agentEvents.js +24 -2
- package/src/core/agentLoop.js +164 -0
- package/src/core/agentLoop.test.js +85 -0
- package/src/core/cacert.js +4 -3
- package/src/core/compose.js +4 -3
- package/src/core/dockerCompose.test.js +14 -0
- package/src/core/documentIntake.test.js +7 -7
- package/src/core/env.js +6 -0
- package/src/core/jobQueue.js +18 -4
- package/src/core/mcp.js +1 -1
- package/src/core/queueStore.js +27 -0
- package/src/core/queueStore.test.js +31 -0
- package/src/core/startupCheck.js +1 -1
- package/src/core/startupCheck.test.js +66 -0
- package/src/core/wikirc.js +2 -2
- package/src/core/workspaces.js +8 -1
- package/src/runtime/auth.js +35 -0
- package/src/runtime/auth.test.js +21 -0
- package/src/runtime/client.js +108 -0
- package/src/runtime/lifecycle.js +60 -0
- package/src/runtime/queueStore.js +6 -0
- package/src/runtime/runner.js +73 -0
- package/src/runtime/server.js +160 -0
- package/src/runtime/server.test.js +53 -0
- package/src/runtime/store.js +292 -0
- package/src/runtime/store.test.js +103 -0
- package/src/runtime/supervisor.js +98 -0
- package/src/runtime/supervisor.test.js +99 -0
- package/src/shell/repl.js +132 -17
- package/src/shell/repl.test.js +38 -0
- package/src/shell/tui.tsx +4 -1
- package/src/shell/useAgent.ts +20 -2
- package/src/shell/useSession.ts +146 -11
- package/wiki-workspace +39 -30
package/bin/wiki-manager.js
CHANGED
|
@@ -80,8 +80,8 @@ async function main() {
|
|
|
80
80
|
// Fallback for already-bootstrapped direct invocations; the shell wrapper
|
|
81
81
|
// exports these before Bun starts.
|
|
82
82
|
if (parsed.cacert) Object.assign(process.env, cacertEnvVars(parsed.cacert));
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
const interactive = process.stdout.isTTY && process.stdin.isTTY && argv[0] !== 'runtime' && !argv.includes('--setup-wizard') && !argv.includes('--headless') && !argv.includes('--once') && !argv.includes('--version') && !argv.includes('-v') && !argv.includes('--help') && !argv.includes('-h');
|
|
84
|
+
if (interactive || argv.includes('--setup-wizard')) await import('@opentui/solid/preload');
|
|
85
85
|
if (interactive) process.stdout.write('Starting wiki-manager…\r');
|
|
86
86
|
const { runCli } = await import('../src/cli/wiki-manager.js');
|
|
87
87
|
await runCli(argv);
|
package/docker-compose.yml
CHANGED
|
@@ -62,6 +62,8 @@ services:
|
|
|
62
62
|
- DOCUMENT_MAX_UPLOAD_BYTES=${DOCUMENT_MAX_UPLOAD_BYTES:-52428800}
|
|
63
63
|
- WIKI_MCP_PROXY_URL=http://host.docker.internal:${WIKI_MCP_PORT:-3101}/mcp
|
|
64
64
|
- PRODUCTION_MCP_PROXY_URL=http://host.docker.internal:${PRODUCTION_MCP_PORT:-3102}/mcp/
|
|
65
|
+
- WIKI_MANAGER_RUNTIME_URL=http://host.docker.internal:7788
|
|
66
|
+
- WIKI_MANAGER_RUNTIME_TOKEN=${WIKI_MANAGER_RUNTIME_TOKEN:-}
|
|
65
67
|
# HTTPS — set paths inside the container (e.g. /certs/server.crt) and uncomment the volume above
|
|
66
68
|
#- WIKI_SERVE_TLS_CERT_PATH=/certs/server.crt
|
|
67
69
|
#- WIKI_SERVE_TLS_KEY_PATH=/certs/server.key
|
|
@@ -95,6 +97,25 @@ services:
|
|
|
95
97
|
- host.docker.internal:host-gateway
|
|
96
98
|
restart: unless-stopped
|
|
97
99
|
|
|
100
|
+
# ── wiki-manager runtime ─────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
agent-runtime:
|
|
103
|
+
image: dotdrelle/llm-wiki-manager:latest
|
|
104
|
+
labels:
|
|
105
|
+
wiki-manager.description: "Agentic runtime — runs, plan, activities, queue."
|
|
106
|
+
command: runtime --host 0.0.0.0 --port 7788 --state-dir /state
|
|
107
|
+
volumes:
|
|
108
|
+
- ./.wiki-manager:/state
|
|
109
|
+
- ${WIKI_WORKSPACES_DIR:-./workspaces}:/workspaces
|
|
110
|
+
environment:
|
|
111
|
+
- WIKI_WORKSPACES_DIR=/workspaces
|
|
112
|
+
- WIKI_MANAGER_RUNTIME_TOKEN=${WIKI_MANAGER_RUNTIME_TOKEN:-}
|
|
113
|
+
ports:
|
|
114
|
+
- '127.0.0.1:7788:7788'
|
|
115
|
+
extra_hosts:
|
|
116
|
+
- host.docker.internal:host-gateway
|
|
117
|
+
restart: unless-stopped
|
|
118
|
+
|
|
98
119
|
# ── agent-wiki-production ─────────────────────────────────────────────────
|
|
99
120
|
|
|
100
121
|
production-mcp:
|
|
@@ -119,7 +140,7 @@ services:
|
|
|
119
140
|
x-wiki-manager:
|
|
120
141
|
service-aliases:
|
|
121
142
|
all:
|
|
122
|
-
targets: [serve, mcp-http, production-mcp]
|
|
143
|
+
targets: [serve, mcp-http, agent-runtime, production-mcp]
|
|
123
144
|
description: "Full workspace service set."
|
|
124
145
|
ui:
|
|
125
146
|
targets: [serve]
|
|
@@ -130,6 +151,9 @@ x-wiki-manager:
|
|
|
130
151
|
mcp:
|
|
131
152
|
targets: [mcp-http]
|
|
132
153
|
description: "Alias for mcp-http: wiki MCP server."
|
|
154
|
+
runtime:
|
|
155
|
+
targets: [agent-runtime]
|
|
156
|
+
description: "Alias for agent-runtime: agentic runtime."
|
|
133
157
|
production:
|
|
134
158
|
targets: [production-mcp]
|
|
135
159
|
description: "Alias for production-mcp: production jobs."
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotdrelle/wiki-manager",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.3",
|
|
4
4
|
"description": "Agentic shell and orchestration cockpit for llm-wiki workspaces.",
|
|
5
5
|
"license": "PolyForm-Noncommercial-1.0.0",
|
|
6
6
|
"author": "dotrelle",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
13
|
"start": "bun ./bin/wiki-manager.js",
|
|
14
|
-
"test": "node --test src/core/activity.test.js src/core/agentEvents.test.js src/core/plan.test.js src/core/mcp.test.js src/core/documentIntake.test.js src/core/wikirc.test.js src/core/modelFetch.test.js src/commands/slash.test.js",
|
|
14
|
+
"test": "node --test src/core/activity.test.js src/core/agentEvents.test.js src/core/agentLoop.test.js src/core/plan.test.js src/core/mcp.test.js src/core/documentIntake.test.js src/core/dockerCompose.test.js src/core/wikirc.test.js src/core/modelFetch.test.js src/core/startupCheck.test.js src/core/queueStore.test.js src/commands/slash.test.js src/shell/repl.test.js src/runtime/store.test.js src/runtime/server.test.js src/runtime/supervisor.test.js src/runtime/auth.test.js",
|
|
15
15
|
"check": "bun ./bin/wiki-manager.js --version && bun ./bin/wiki-manager.js --help && bun ./bin/wiki-manager.js --once \"verifie le mode agent\""
|
|
16
16
|
},
|
|
17
17
|
"engines": {
|
package/src/agent/graph.js
CHANGED
|
@@ -43,7 +43,7 @@ const SHELL_RUN_COMMAND_TOOL = {
|
|
|
43
43
|
properties: {
|
|
44
44
|
command: {
|
|
45
45
|
type: 'string',
|
|
46
|
-
description: 'Slash command to run, for example "/workspace list", "/workspace init
|
|
46
|
+
description: 'Slash command to run, for example "/workspace list", "/workspace init <name>", or "/use <workspace>".',
|
|
47
47
|
},
|
|
48
48
|
},
|
|
49
49
|
required: ['command'],
|
|
@@ -366,23 +366,6 @@ export function buildAgentSystemPrompt(state) {
|
|
|
366
366
|
export function buildLimitedAgentResponse(state, reason = 'no workspace loaded with .wikirc.yaml') {
|
|
367
367
|
const workspace = state.session.workspace ?? 'no workspace selected';
|
|
368
368
|
const wikirc = state.session.wikirc?.profile ?? 'no profile loaded';
|
|
369
|
-
const language = state.session.language ?? 'en-US';
|
|
370
|
-
if (language.toLowerCase().startsWith('fr')) {
|
|
371
|
-
return [
|
|
372
|
-
`Donna est active. Workspace courant: ${workspace}.`,
|
|
373
|
-
`Profil wikirc courant: ${wikirc}.`,
|
|
374
|
-
'',
|
|
375
|
-
"Je suis le mode agent du shell: utilise `/agent` pour router les entrees libres vers ce graphe LangGraph, ou `/chat` pour revenir au chat direct.",
|
|
376
|
-
`Connexion LLM: mode limite (${reason}).`,
|
|
377
|
-
`Primitives disponibles maintenant: ${commandList(state.session)}.`,
|
|
378
|
-
'',
|
|
379
|
-
'Outils MCP connectes:',
|
|
380
|
-
formatMcpToolsForAgent(state.session.mcp),
|
|
381
|
-
'',
|
|
382
|
-
'Mode limite: workspace, Docker Compose, appels MCP, echappatoire /wiki, decouverte skills et mode headless sont branches.',
|
|
383
|
-
"Utilise `/help` pour voir les commandes deterministes disponibles.",
|
|
384
|
-
].join('\n');
|
|
385
|
-
}
|
|
386
369
|
return [
|
|
387
370
|
`Donna is active. Current workspace: ${workspace}.`,
|
|
388
371
|
`Current wikirc profile: ${wikirc}.`,
|
|
@@ -506,7 +489,7 @@ export function createAgentGraph(options = {}) {
|
|
|
506
489
|
} catch (err) {
|
|
507
490
|
if (err.name === 'AbortError') throw err;
|
|
508
491
|
const message = err instanceof Error ? err.message : String(err);
|
|
509
|
-
return { response: buildLimitedAgentResponse(state, `LLM
|
|
492
|
+
return { response: buildLimitedAgentResponse(state, `LLM unavailable: ${message}`), pendingToolCalls: null, readyToStream: false };
|
|
510
493
|
}
|
|
511
494
|
}
|
|
512
495
|
|
package/src/cli/wiki-manager.js
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
1
2
|
import { readFileSync } from 'node:fs';
|
|
2
3
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
3
4
|
import { dirname, join, resolve } from 'node:path';
|
|
4
5
|
import { fileURLToPath } from 'node:url';
|
|
5
6
|
import { loadManagerEnv } from '../core/env.js';
|
|
6
7
|
loadManagerEnv();
|
|
7
|
-
import { createAgentGraph
|
|
8
|
+
import { createAgentGraph } from '../agent/graph.js';
|
|
8
9
|
import { handleSlashCommand, printHelp, printVersion } from '../commands/slash.js';
|
|
9
10
|
import { runShell } from '../shell/repl.js';
|
|
10
11
|
import { runChecks } from '../core/startupCheck.js';
|
|
11
12
|
import { callMcpTool, formatMcpToolResult } from '../core/mcp.js';
|
|
12
|
-
import { extractActivity, parseJsonText, sessionActivities } from '../core/activity.js';
|
|
13
|
-
import {
|
|
13
|
+
import { extractActivity, parseJsonText, sessionActivities, terminalFailures } from '../core/activity.js';
|
|
14
|
+
import { syncActivitiesToPlan, formatPlanStatus } from '../core/plan.js';
|
|
14
15
|
import { createAgentEvent, dispatchAgentEvent } from '../core/agentEvents.js';
|
|
16
|
+
import { runAgentTurn, runAgenticLoop } from '../core/agentLoop.js';
|
|
17
|
+
// Runtime modules use node:sqlite (Node.js built-in unavailable in Bun).
|
|
18
|
+
// They are imported dynamically so the shell / TUI path never loads them.
|
|
15
19
|
|
|
16
20
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
21
|
const packageJsonPath = resolve(__dirname, '../../package.json');
|
|
@@ -39,6 +43,7 @@ function createSession() {
|
|
|
39
43
|
packageJson,
|
|
40
44
|
conversations: { __global__: [] },
|
|
41
45
|
activities: {},
|
|
46
|
+
jobQueue: [],
|
|
42
47
|
productionActivity: null,
|
|
43
48
|
headlessPlan: null,
|
|
44
49
|
};
|
|
@@ -57,20 +62,6 @@ async function writeHeadlessLog(session, lines, explicitPath) {
|
|
|
57
62
|
return logPath;
|
|
58
63
|
}
|
|
59
64
|
|
|
60
|
-
function activitySnapshot(session) {
|
|
61
|
-
return new Set(sessionActivities(session).map((a) => a.key));
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function newNonTerminalActivities(snapshotBefore, session) {
|
|
65
|
-
return sessionActivities(session).filter((a) => !snapshotBefore.has(a.key) && !a.terminal);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function terminalFailures(activities) {
|
|
69
|
-
return activities.filter(
|
|
70
|
-
(a) => a.terminal && ['failed', 'error', 'cancelled', 'canceled'].includes(String(a.status).toLowerCase()),
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
65
|
|
|
75
66
|
async function runHeadlessActivityLoop(session, log, { wait, timeoutMs }) {
|
|
76
67
|
if (!wait) return { exitCode: 0, completed: [], timedOut: false };
|
|
@@ -150,118 +141,54 @@ async function runHeadlessActivityLoop(session, log, { wait, timeoutMs }) {
|
|
|
150
141
|
return { exitCode: 1, completed, timedOut: true };
|
|
151
142
|
}
|
|
152
143
|
|
|
153
|
-
async function runHeadlessAgentTurn(agent, session, input, log, messages = []) {
|
|
154
|
-
session.packageJson = packageJson;
|
|
155
|
-
let streamedContent = '';
|
|
156
|
-
session._onStream = (delta) => { streamedContent += delta; };
|
|
157
|
-
session._onStreamReset = () => { streamedContent = ''; };
|
|
158
|
-
let result;
|
|
159
|
-
try {
|
|
160
|
-
result = await agent.invoke({ input, session, messages });
|
|
161
|
-
} finally {
|
|
162
|
-
delete session._onStream;
|
|
163
|
-
delete session._onStreamReset;
|
|
164
|
-
}
|
|
165
|
-
if (result.streamedInline) {
|
|
166
|
-
return streamedContent.trim() || buildLimitedAgentResponse({ input, session }, 'LLM stream ended without content');
|
|
167
|
-
}
|
|
168
|
-
if (result.response != null) return result.response;
|
|
169
|
-
if (result.readyToStream && session.llm?.stream) {
|
|
170
|
-
const { system, messages: streamMessages = [] } = result.streamContext ?? {};
|
|
171
|
-
let content = '';
|
|
172
|
-
for await (const delta of session.llm.stream({
|
|
173
|
-
system: system ?? buildAgentSystemPrompt({ input, session }),
|
|
174
|
-
messages: streamMessages,
|
|
175
|
-
})) {
|
|
176
|
-
content += delta;
|
|
177
|
-
}
|
|
178
|
-
return content.trim() || buildLimitedAgentResponse({ input, session }, 'LLM stream ended without content');
|
|
179
|
-
}
|
|
180
|
-
return buildLimitedAgentResponse({ input, session });
|
|
181
|
-
}
|
|
182
|
-
|
|
183
144
|
async function runHeadlessAgenticLoop(agent, session, initialInput, log, { timeoutMs, maxTurns }) {
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
} else if (turn === 1 && session.headlessPlan) {
|
|
215
|
-
log.push(`agentic-loop: plan set via tool (${session.headlessPlan.length} steps)`);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const newPending = newNonTerminalActivities(snapshot, session);
|
|
219
|
-
if (newPending.length === 0) {
|
|
220
|
-
const pendingSteps = (session.headlessPlan ?? []).filter((s) => s.status === 'pending');
|
|
221
|
-
if (pendingSteps.length === 0) {
|
|
222
|
-
log.push('agentic-loop: no new non-terminal activities — plan complete');
|
|
223
|
-
console.log('[headless] Plan complete.');
|
|
224
|
-
return { exitCode: 0 };
|
|
225
|
-
}
|
|
145
|
+
const result = await runAgenticLoop(agent, session, initialInput, {
|
|
146
|
+
timeoutMs,
|
|
147
|
+
maxTurns,
|
|
148
|
+
waitForActivities: async (turnSession, _startedActivities, waitOptions) => {
|
|
149
|
+
const waitResult = await runHeadlessActivityLoop(turnSession, log, { wait: true, timeoutMs: waitOptions.timeoutMs });
|
|
150
|
+
return {
|
|
151
|
+
ok: waitResult.exitCode === 0 && !waitResult.timedOut,
|
|
152
|
+
...waitResult,
|
|
153
|
+
};
|
|
154
|
+
},
|
|
155
|
+
onTurnStart: ({ turn, maxTurns: totalTurns }) => {
|
|
156
|
+
log.push(`agentic-loop: turn ${turn}/${totalTurns}`);
|
|
157
|
+
console.log(`[headless] Agent turn ${turn}/${totalTurns}…`);
|
|
158
|
+
},
|
|
159
|
+
onTurnResponse: ({ turn, response }) => {
|
|
160
|
+
log.push(`agentic-loop: turn ${turn} response:`);
|
|
161
|
+
log.push(response);
|
|
162
|
+
console.log(response);
|
|
163
|
+
},
|
|
164
|
+
onPlanExtracted: ({ steps }) => {
|
|
165
|
+
log.push(`agentic-loop: plan extracted from text (${steps.length} steps, fallback)`);
|
|
166
|
+
},
|
|
167
|
+
onPlanAlreadySet: ({ steps }) => {
|
|
168
|
+
log.push(`agentic-loop: plan set via tool (${steps.length} steps)`);
|
|
169
|
+
},
|
|
170
|
+
onComplete: () => {
|
|
171
|
+
log.push('agentic-loop: no new non-terminal activities — plan complete');
|
|
172
|
+
console.log('[headless] Plan complete.');
|
|
173
|
+
},
|
|
174
|
+
onPendingSteps: ({ pendingSteps }) => {
|
|
226
175
|
log.push(`agentic-loop: no new async activity, ${pendingSteps.length} pending step(s) remain`);
|
|
227
176
|
if (session.headlessPlan) log.push(`agentic-loop: plan status:\n${formatPlanStatus(session.headlessPlan)}`);
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
if (timedOut || exitCode !== 0) return { exitCode };
|
|
245
|
-
|
|
246
|
-
const summary = formatCompletedActivities(completed);
|
|
247
|
-
log.push(`agentic-loop: completed activities:\n${summary}`);
|
|
248
|
-
if (session.headlessPlan) log.push(`agentic-loop: plan status:\n${formatPlanStatus(session.headlessPlan)}`);
|
|
249
|
-
currentInput = [
|
|
250
|
-
'Original task:',
|
|
251
|
-
initialInput,
|
|
252
|
-
'',
|
|
253
|
-
session.headlessPlan ? `Plan status:\n${formatPlanStatus(session.headlessPlan)}\n` : null,
|
|
254
|
-
'Completed activities:',
|
|
255
|
-
summary,
|
|
256
|
-
'',
|
|
257
|
-
'Continue the original plan. Start the next required step only.',
|
|
258
|
-
'If all steps are complete, provide the final summary.',
|
|
259
|
-
].filter(Boolean).join('\n');
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
log.push(`agentic-loop: max turns (${maxTurns}) reached without completing`);
|
|
263
|
-
console.error(`[headless] Max agent turns (${maxTurns}) reached.`);
|
|
264
|
-
return { exitCode: 1 };
|
|
177
|
+
},
|
|
178
|
+
onActivitiesStarted: ({ activities }) => {
|
|
179
|
+
log.push(`agentic-loop: ${activities.length} new job(s) started, waiting…`);
|
|
180
|
+
},
|
|
181
|
+
onActivitiesCompleted: ({ summary }) => {
|
|
182
|
+
log.push(`agentic-loop: completed activities:\n${summary}`);
|
|
183
|
+
if (session.headlessPlan) log.push(`agentic-loop: plan status:\n${formatPlanStatus(session.headlessPlan)}`);
|
|
184
|
+
},
|
|
185
|
+
onMaxTurns: ({ maxTurns: totalTurns }) => {
|
|
186
|
+
log.push(`agentic-loop: max turns (${totalTurns}) reached without completing`);
|
|
187
|
+
console.error(`[headless] Max agent turns (${totalTurns}) reached.`);
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
return { exitCode: result.ok ? 0 : (result.waitResult?.exitCode ?? 1) };
|
|
265
192
|
}
|
|
266
193
|
|
|
267
194
|
async function runHeadless(argv, agent) {
|
|
@@ -326,7 +253,7 @@ async function runHeadless(argv, agent) {
|
|
|
326
253
|
if (useAgenticLoop) {
|
|
327
254
|
({ exitCode } = await runHeadlessAgenticLoop(agent, session, input, log, { timeoutMs, maxTurns }));
|
|
328
255
|
} else {
|
|
329
|
-
const response = await
|
|
256
|
+
const response = await runAgentTurn(agent, session, input);
|
|
330
257
|
log.push('response:');
|
|
331
258
|
log.push(response);
|
|
332
259
|
console.log(response);
|
|
@@ -345,7 +272,171 @@ async function runHeadless(argv, agent) {
|
|
|
345
272
|
}
|
|
346
273
|
}
|
|
347
274
|
|
|
275
|
+
async function runRuntime(argv, agent) {
|
|
276
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
277
|
+
console.log([
|
|
278
|
+
'Usage: wiki-manager runtime [--host <host>] [--port <port>] [--state-dir <dir>]',
|
|
279
|
+
'',
|
|
280
|
+
'Runs the local agentic runtime used by wiki-manager Shell and llm-wiki serve.',
|
|
281
|
+
'',
|
|
282
|
+
'Defaults:',
|
|
283
|
+
' --host 0.0.0.0',
|
|
284
|
+
' --port 7788',
|
|
285
|
+
' --state-dir .wiki-manager',
|
|
286
|
+
].join('\n'));
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const { defaultRuntimeStateDir, openRuntimeStore } = await import('../runtime/store.js');
|
|
290
|
+
const { startRuntimeServer } = await import('../runtime/server.js');
|
|
291
|
+
const { emitRuntimeLog, startActivitySupervisor } = await import('../runtime/supervisor.js');
|
|
292
|
+
const { resolveRuntimeAuthToken } = await import('../runtime/auth.js');
|
|
293
|
+
const { createSqliteQueueStore } = await import('../runtime/queueStore.js');
|
|
294
|
+
const { runRuntimeAgenticLoop } = await import('../runtime/runner.js');
|
|
295
|
+
|
|
296
|
+
const host = valueAfter(argv, '--host') ?? process.env.WIKI_MANAGER_RUNTIME_HOST ?? '0.0.0.0';
|
|
297
|
+
const port = Number(valueAfter(argv, '--port') ?? process.env.WIKI_MANAGER_RUNTIME_PORT ?? 7788);
|
|
298
|
+
const stateDir = valueAfter(argv, '--state-dir') ?? defaultRuntimeStateDir();
|
|
299
|
+
const auth = resolveRuntimeAuthToken({ host, stateDir });
|
|
300
|
+
if (auth.token) process.env.WIKI_MANAGER_RUNTIME_TOKEN = auth.token;
|
|
301
|
+
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
|
|
302
|
+
throw new Error(`Invalid runtime port: ${port}`);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const session = createSession();
|
|
306
|
+
session.headless = true;
|
|
307
|
+
session.chatMode = false;
|
|
308
|
+
session.packageJson = packageJson;
|
|
309
|
+
|
|
310
|
+
const store = openRuntimeStore({ stateDir });
|
|
311
|
+
store.hydrateSession(session);
|
|
312
|
+
session.queueStore = createSqliteQueueStore(store, session);
|
|
313
|
+
|
|
314
|
+
let serverHandle = null;
|
|
315
|
+
session._onAgentEvent = (event) => {
|
|
316
|
+
store.persistEvent(event);
|
|
317
|
+
serverHandle?.publish(event);
|
|
318
|
+
};
|
|
319
|
+
session._onRuntimeError = (err) => {
|
|
320
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
321
|
+
dispatchAgentEvent(session, createAgentEvent('run_error', {
|
|
322
|
+
origin: 'runtime',
|
|
323
|
+
payload: { message },
|
|
324
|
+
}));
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
let supervisor = null;
|
|
328
|
+
|
|
329
|
+
async function executeRun(body, { signal } = {}) {
|
|
330
|
+
const input = String(body.input ?? body.prompt ?? '').trim();
|
|
331
|
+
const workspace = body.workspace ? String(body.workspace).trim() : null;
|
|
332
|
+
const timeoutMs = (Number.isFinite(Number(body.timeout)) ? Math.max(1, Number(body.timeout)) : 3600) * 1000;
|
|
333
|
+
const maxTurns = Number.isFinite(Number(body.maxTurns)) ? Math.max(1, Number(body.maxTurns)) : 20;
|
|
334
|
+
const runId = randomUUID();
|
|
335
|
+
try {
|
|
336
|
+
if (workspace && session.workspace !== workspace) {
|
|
337
|
+
const result = await handleSlashCommand(`/use ${workspace}`, { packageJson, session });
|
|
338
|
+
if (!session.workspacePath) throw new Error(result.output || `Workspace not loaded: ${workspace}`);
|
|
339
|
+
}
|
|
340
|
+
dispatchAgentEvent(session, createAgentEvent('run_started', {
|
|
341
|
+
origin: 'runtime',
|
|
342
|
+
runId,
|
|
343
|
+
payload: { input, workspace },
|
|
344
|
+
}));
|
|
345
|
+
dispatchAgentEvent(session, createAgentEvent('user_message', {
|
|
346
|
+
origin: 'user',
|
|
347
|
+
runId,
|
|
348
|
+
payload: { content: input },
|
|
349
|
+
}));
|
|
350
|
+
session._abortSignal = signal ?? null;
|
|
351
|
+
supervisor?.setRunSignal(signal);
|
|
352
|
+
session._onStep = (message) => emitRuntimeLog(session, message);
|
|
353
|
+
const result = await runRuntimeAgenticLoop(agent, session, input, {
|
|
354
|
+
signal,
|
|
355
|
+
timeoutMs,
|
|
356
|
+
maxTurns,
|
|
357
|
+
runId,
|
|
358
|
+
pollBusy: supervisor?.pollBusy,
|
|
359
|
+
});
|
|
360
|
+
if (!result.ok) {
|
|
361
|
+
dispatchAgentEvent(session, createAgentEvent('run_error', {
|
|
362
|
+
origin: 'runtime',
|
|
363
|
+
runId,
|
|
364
|
+
payload: {
|
|
365
|
+
runId,
|
|
366
|
+
message: result.timedOut
|
|
367
|
+
? 'Runtime agentic loop timed out.'
|
|
368
|
+
: result.maxTurns
|
|
369
|
+
? `Runtime agentic loop reached max turns (${maxTurns}).`
|
|
370
|
+
: 'Runtime agentic loop failed.',
|
|
371
|
+
},
|
|
372
|
+
}));
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
dispatchAgentEvent(session, createAgentEvent('run_done', {
|
|
376
|
+
origin: 'runtime',
|
|
377
|
+
runId,
|
|
378
|
+
payload: { runId },
|
|
379
|
+
}));
|
|
380
|
+
} catch (err) {
|
|
381
|
+
if (err?.name === 'AbortError') {
|
|
382
|
+
dispatchAgentEvent(session, createAgentEvent('run_cancelled', {
|
|
383
|
+
origin: 'runtime',
|
|
384
|
+
runId,
|
|
385
|
+
payload: {
|
|
386
|
+
runId,
|
|
387
|
+
message: 'Agent run cancelled.',
|
|
388
|
+
},
|
|
389
|
+
}));
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
dispatchAgentEvent(session, createAgentEvent('run_error', {
|
|
393
|
+
origin: 'runtime',
|
|
394
|
+
runId,
|
|
395
|
+
payload: {
|
|
396
|
+
runId,
|
|
397
|
+
message: err instanceof Error ? err.message : String(err),
|
|
398
|
+
},
|
|
399
|
+
}));
|
|
400
|
+
} finally {
|
|
401
|
+
supervisor?.setRunSignal(null);
|
|
402
|
+
delete session._abortSignal;
|
|
403
|
+
delete session._onStep;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
serverHandle = await startRuntimeServer({
|
|
408
|
+
host,
|
|
409
|
+
port,
|
|
410
|
+
store,
|
|
411
|
+
session,
|
|
412
|
+
run: executeRun,
|
|
413
|
+
cancel: () => emitRuntimeLog(session, 'runtime: cancel requested'),
|
|
414
|
+
token: auth.token,
|
|
415
|
+
});
|
|
416
|
+
supervisor = startActivitySupervisor(session);
|
|
417
|
+
|
|
418
|
+
console.log(`wiki-manager runtime listening on http://${host}:${port}`);
|
|
419
|
+
console.log(`runtime state: ${store.dbPath}`);
|
|
420
|
+
if (auth.tokenPath) console.log(`runtime token: ${auth.tokenPath}`);
|
|
421
|
+
|
|
422
|
+
const shutdown = async () => {
|
|
423
|
+
supervisor?.stop();
|
|
424
|
+
await serverHandle.close();
|
|
425
|
+
store.close();
|
|
426
|
+
process.exit(0);
|
|
427
|
+
};
|
|
428
|
+
process.once('SIGINT', shutdown);
|
|
429
|
+
process.once('SIGTERM', shutdown);
|
|
430
|
+
await new Promise(() => {});
|
|
431
|
+
}
|
|
432
|
+
|
|
348
433
|
export async function runCli(argv) {
|
|
434
|
+
if (argv[0] === 'runtime') {
|
|
435
|
+
const agent = createAgentGraph();
|
|
436
|
+
await runRuntime(argv.slice(1), agent);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
|
|
349
440
|
if (argv.includes('--setup-wizard')) {
|
|
350
441
|
if (!process.versions.bun) {
|
|
351
442
|
throw new Error('Setup wizard requires Bun. Run: bun ./bin/wiki-manager.js --setup-wizard');
|
|
@@ -396,9 +487,25 @@ export async function runCli(argv) {
|
|
|
396
487
|
const { runOpenTuiShell, runStartupWizard } = await import('../shell/tui.tsx');
|
|
397
488
|
const gaps = await runChecks();
|
|
398
489
|
if (gaps.length > 0) await runStartupWizard(gaps);
|
|
399
|
-
|
|
490
|
+
let runtime = null;
|
|
491
|
+
try {
|
|
492
|
+
const { ensureRuntime } = await import('../runtime/lifecycle.js');
|
|
493
|
+
runtime = await ensureRuntime();
|
|
494
|
+
} catch (err) {
|
|
495
|
+
console.error(`Runtime unavailable: ${err instanceof Error ? err.message : String(err)}`);
|
|
496
|
+
}
|
|
497
|
+
await runOpenTuiShell({ agent, packageJson, runtime });
|
|
400
498
|
return;
|
|
401
499
|
}
|
|
402
500
|
|
|
403
|
-
|
|
501
|
+
let runtime = null;
|
|
502
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
503
|
+
try {
|
|
504
|
+
const { ensureRuntime } = await import('../runtime/lifecycle.js');
|
|
505
|
+
runtime = await ensureRuntime();
|
|
506
|
+
} catch (err) {
|
|
507
|
+
console.error(`Runtime unavailable: ${err instanceof Error ? err.message : String(err)}`);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
await runShell({ agent, packageJson, runtime });
|
|
404
511
|
}
|
package/src/commands/slash.js
CHANGED
|
@@ -282,6 +282,13 @@ function workspaceStatsText(stats) {
|
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
function workspaceLoadedText(workspace, summary, session) {
|
|
285
|
+
const profiles = listWikircProfiles(workspace.workspacePath);
|
|
286
|
+
const profileLines = profiles.length > 0
|
|
287
|
+
? profiles.map((profile) => {
|
|
288
|
+
const marker = profile.name === summary.profile ? '*' : ' ';
|
|
289
|
+
return `${marker} ${profile.name}\t${profile.fileName}`;
|
|
290
|
+
})
|
|
291
|
+
: ['No .wikirc.yaml profile found.'];
|
|
285
292
|
return [
|
|
286
293
|
`Workspace: ${workspace.name}`,
|
|
287
294
|
'',
|
|
@@ -300,6 +307,12 @@ function workspaceLoadedText(workspace, summary, session) {
|
|
|
300
307
|
`vector: ${summary.vectorEnabled ? 'enabled' : 'disabled'}`,
|
|
301
308
|
`embedding: ${summary.embeddingModel ?? '-'}`,
|
|
302
309
|
'',
|
|
310
|
+
'Available configs',
|
|
311
|
+
'',
|
|
312
|
+
...profileLines,
|
|
313
|
+
'',
|
|
314
|
+
`Switch config: /config use <profile>`,
|
|
315
|
+
'',
|
|
303
316
|
'Session',
|
|
304
317
|
'',
|
|
305
318
|
`llm: ${session.llm ? 'configured' : 'missing config'}`,
|
|
@@ -647,7 +660,7 @@ Modes:
|
|
|
647
660
|
Status:
|
|
648
661
|
Agent-first shell is installed with workspace services, MCP calls, wiki CLI, skill discovery, and headless runs.
|
|
649
662
|
Shell UI is English. Agent exchange language is read from the active .wikirc.yaml.
|
|
650
|
-
LLM config is intentionally workspace-scoped and
|
|
663
|
+
LLM config is intentionally workspace-scoped and is read from .wikirc.yaml after /use <workspace>.
|
|
651
664
|
Headless mode supports one-shot workspace prompts and skill runs with log output.
|
|
652
665
|
`;
|
|
653
666
|
}
|
|
@@ -690,6 +703,9 @@ export async function handleSlashCommand(line, context) {
|
|
|
690
703
|
case 'use': {
|
|
691
704
|
const workspaceName = args[1];
|
|
692
705
|
if (!workspaceName) {
|
|
706
|
+
return { output: formatWorkspaceList(listWorkspaces(), context.session) };
|
|
707
|
+
}
|
|
708
|
+
if (args[2]) {
|
|
693
709
|
return { output: 'Usage: /use <workspace>' };
|
|
694
710
|
}
|
|
695
711
|
const workspace = findWorkspace(workspaceName);
|