@dotdrelle/wiki-manager 0.7.3 → 0.9.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/.env.example +20 -0
- package/README.md +50 -1
- package/docker-compose.yml +1 -23
- package/mcp.endpoints.example.json +13 -0
- package/package.json +2 -2
- package/src/agent/graph.js +101 -15
- package/src/agent/graph.test.js +145 -0
- package/src/cli/wiki-manager.js +306 -53
- package/src/commands/slash.js +4 -24
- package/src/core/agentEvents.js +169 -4
- package/src/core/agentEvents.test.js +176 -4
- package/src/core/agentLoop.js +3 -0
- package/src/core/compose.js +1 -2
- package/src/core/dockerCompose.test.js +5 -5
- package/src/core/jobQueue.js +29 -12
- package/src/core/mcp.js +120 -10
- package/src/core/mcp.test.js +121 -1
- package/src/core/plan.js +33 -0
- package/src/core/queueStore.test.js +1 -0
- package/src/core/sessionConfig.js +24 -0
- package/src/core/wikiWorkspace.test.js +24 -0
- package/src/runtime/approvals.js +113 -0
- package/src/runtime/auth.test.js +8 -0
- package/src/runtime/client.js +52 -6
- package/src/runtime/lifecycle.js +27 -3
- package/src/runtime/queueStore.js +3 -3
- package/src/runtime/runner.js +340 -0
- package/src/runtime/runner.test.js +270 -0
- package/src/runtime/server.js +252 -33
- package/src/runtime/server.test.js +577 -0
- package/src/runtime/store.js +181 -39
- package/src/runtime/store.test.js +363 -4
- package/src/runtime/supervisor.js +6 -0
- package/src/runtime/supervisor.test.js +141 -0
- package/src/shell/RightPane.tsx +1 -1
- package/src/shell/repl.js +22 -6
- package/src/shell/useAgent.ts +1 -1
- package/src/shell/useSession.ts +10 -5
- package/wiki-workspace +198 -4
package/src/cli/wiki-manager.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
1
|
import { readFileSync } from 'node:fs';
|
|
3
2
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
4
3
|
import { dirname, join, resolve } from 'node:path';
|
|
@@ -6,9 +5,11 @@ import { fileURLToPath } from 'node:url';
|
|
|
6
5
|
import { loadManagerEnv } from '../core/env.js';
|
|
7
6
|
loadManagerEnv();
|
|
8
7
|
import { createAgentGraph } from '../agent/graph.js';
|
|
9
|
-
import { handleSlashCommand, printHelp, printVersion } from '../commands/slash.js';
|
|
8
|
+
import { handleSlashCommand, printHelp, printVersion, refreshMcpRuntimeStatus } from '../commands/slash.js';
|
|
10
9
|
import { runShell } from '../shell/repl.js';
|
|
11
10
|
import { runChecks } from '../core/startupCheck.js';
|
|
11
|
+
import { applySessionWikircProfile } from '../core/sessionConfig.js';
|
|
12
|
+
import { listWikircProfiles } from '../core/wikirc.js';
|
|
12
13
|
import { callMcpTool, formatMcpToolResult } from '../core/mcp.js';
|
|
13
14
|
import { extractActivity, parseJsonText, sessionActivities, terminalFailures } from '../core/activity.js';
|
|
14
15
|
import { syncActivitiesToPlan, formatPlanStatus } from '../core/plan.js';
|
|
@@ -20,7 +21,7 @@ import { runAgentTurn, runAgenticLoop } from '../core/agentLoop.js';
|
|
|
20
21
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
21
22
|
const packageJsonPath = resolve(__dirname, '../../package.json');
|
|
22
23
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
23
|
-
const SHELL_COMMANDS = ['help', 'version', 'exit', 'workspace', 'new', 'use', 'config', 'status', 'services', 'start', 'stop', 'logs', 'mcp', 'wiki', 'skills', 'clear', 'chat', 'agent'];
|
|
24
|
+
const SHELL_COMMANDS = ['help', 'version', 'exit', 'workspace', 'new', 'use', 'config', 'status', 'services', 'start', 'stop', 'logs', 'mcp', 'wiki', 'skills', 'clear', 'chat', 'agent', 'approve'];
|
|
24
25
|
|
|
25
26
|
function valueAfter(argv, flag) {
|
|
26
27
|
const index = argv.indexOf(flag);
|
|
@@ -286,12 +287,13 @@ async function runRuntime(argv, agent) {
|
|
|
286
287
|
].join('\n'));
|
|
287
288
|
return;
|
|
288
289
|
}
|
|
289
|
-
const { defaultRuntimeStateDir, openRuntimeStore } = await import('../runtime/store.js');
|
|
290
|
+
const { defaultRuntimeStateDir, openRuntimeStore, RECOVERABLE_QUEUE_STATUSES } = await import('../runtime/store.js');
|
|
290
291
|
const { startRuntimeServer } = await import('../runtime/server.js');
|
|
291
292
|
const { emitRuntimeLog, startActivitySupervisor } = await import('../runtime/supervisor.js');
|
|
292
293
|
const { resolveRuntimeAuthToken } = await import('../runtime/auth.js');
|
|
293
294
|
const { createSqliteQueueStore } = await import('../runtime/queueStore.js');
|
|
294
|
-
const {
|
|
295
|
+
const { createApprovalManager } = await import('../runtime/approvals.js');
|
|
296
|
+
const { runRuntimeAgenticWorkflow } = await import('../runtime/runner.js');
|
|
295
297
|
|
|
296
298
|
const host = valueAfter(argv, '--host') ?? process.env.WIKI_MANAGER_RUNTIME_HOST ?? '0.0.0.0';
|
|
297
299
|
const port = Number(valueAfter(argv, '--port') ?? process.env.WIKI_MANAGER_RUNTIME_PORT ?? 7788);
|
|
@@ -302,45 +304,270 @@ async function runRuntime(argv, agent) {
|
|
|
302
304
|
throw new Error(`Invalid runtime port: ${port}`);
|
|
303
305
|
}
|
|
304
306
|
|
|
305
|
-
const session = createSession();
|
|
306
|
-
session.headless = true;
|
|
307
|
-
session.chatMode = false;
|
|
308
|
-
session.packageJson = packageJson;
|
|
309
|
-
|
|
310
307
|
const store = openRuntimeStore({ stateDir });
|
|
311
|
-
store.hydrateSession(session);
|
|
312
|
-
session.queueStore = createSqliteQueueStore(store, session);
|
|
313
|
-
|
|
314
308
|
let serverHandle = null;
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
309
|
+
const contexts = new Map();
|
|
310
|
+
|
|
311
|
+
async function getWorkspaceContext(workspaceName = null) {
|
|
312
|
+
const requestedWorkspace = workspaceName ? String(workspaceName).trim() : null;
|
|
313
|
+
const key = requestedWorkspace ?? '__default__';
|
|
314
|
+
if (contexts.has(key)) return contexts.get(key);
|
|
315
|
+
|
|
316
|
+
const pending = (async () => {
|
|
317
|
+
const session = createSession();
|
|
318
|
+
session.headless = true;
|
|
319
|
+
session.chatMode = false;
|
|
320
|
+
session.packageJson = packageJson;
|
|
321
|
+
|
|
322
|
+
if (requestedWorkspace) {
|
|
323
|
+
const result = await handleSlashCommand(`/use ${requestedWorkspace}`, { packageJson, session });
|
|
324
|
+
if (!session.workspacePath) throw new Error(result.output || `Workspace not loaded: ${requestedWorkspace}`);
|
|
325
|
+
}
|
|
326
|
+
const workspace = session.workspace ?? requestedWorkspace ?? null;
|
|
327
|
+
store.hydrateSession(session, { workspace });
|
|
328
|
+
session.queueStore = createSqliteQueueStore(store, session, { workspace });
|
|
329
|
+
|
|
330
|
+
const context = {
|
|
331
|
+
workspace,
|
|
332
|
+
session,
|
|
333
|
+
supervisor: null,
|
|
334
|
+
running: false,
|
|
335
|
+
currentAbortController: null,
|
|
336
|
+
approvalManager: null,
|
|
337
|
+
};
|
|
338
|
+
session._onAgentEvent = (event) => {
|
|
339
|
+
store.persistEvent(event);
|
|
340
|
+
serverHandle?.publish(event);
|
|
341
|
+
};
|
|
342
|
+
session._onRuntimeError = (err) => {
|
|
343
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
344
|
+
dispatchAgentEvent(session, createAgentEvent('run_error', {
|
|
345
|
+
origin: 'runtime',
|
|
346
|
+
payload: { message, workspace },
|
|
347
|
+
}));
|
|
348
|
+
};
|
|
349
|
+
context.approvalManager = createApprovalManager(session, {
|
|
350
|
+
defaultTimeoutMs: Number.isFinite(Number(process.env.WIKI_MANAGER_APPROVAL_TIMEOUT_MS))
|
|
351
|
+
? Math.max(1, Number(process.env.WIKI_MANAGER_APPROVAL_TIMEOUT_MS))
|
|
352
|
+
: undefined,
|
|
353
|
+
});
|
|
354
|
+
session._requestApproval = (request) => context.approvalManager.requestApproval(request);
|
|
355
|
+
context.supervisor = startActivitySupervisor(session);
|
|
356
|
+
contexts.set(key, context);
|
|
357
|
+
if (workspace && workspace !== key) contexts.set(workspace, context);
|
|
358
|
+
return context;
|
|
359
|
+
})();
|
|
360
|
+
contexts.set(key, pending);
|
|
361
|
+
pending.catch(() => {
|
|
362
|
+
if (contexts.get(key) === pending) contexts.delete(key);
|
|
363
|
+
});
|
|
364
|
+
return pending;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function recoveryMcpGaps(context) {
|
|
368
|
+
const gaps = [];
|
|
369
|
+
const session = context.session;
|
|
370
|
+
for (const activity of sessionActivities(session)) {
|
|
371
|
+
if (activity.terminal || !activity.poll?.server) continue;
|
|
372
|
+
const endpoint = session.mcp?.[activity.poll.server];
|
|
373
|
+
if (endpoint?.status !== 'connected') gaps.push(activity.poll.server);
|
|
374
|
+
}
|
|
375
|
+
for (const item of session.queueStore?.list?.() ?? []) {
|
|
376
|
+
const status = String(item.status ?? '').toLowerCase();
|
|
377
|
+
if (!RECOVERABLE_QUEUE_STATUSES.includes(status)) continue;
|
|
378
|
+
const endpoint = session.mcp?.[item.server ?? 'production'];
|
|
379
|
+
if (endpoint?.status !== 'connected') gaps.push(item.server ?? 'production');
|
|
380
|
+
}
|
|
381
|
+
return [...new Set(gaps)];
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function activeNonTerminalActivities(session) {
|
|
385
|
+
return sessionActivities(session).filter((activity) => !activity.terminal);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function activePollingActivities(session) {
|
|
389
|
+
return activeNonTerminalActivities(session).filter((activity) => activity.poll);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function buildRecoveryPrompt(run, session) {
|
|
393
|
+
const conversation = session.agentProjection?.conversation ?? [];
|
|
394
|
+
const recentConversation = conversation
|
|
395
|
+
.slice(-8)
|
|
396
|
+
.map((message) => `${message.role}: ${message.content}`)
|
|
397
|
+
.join('\n');
|
|
398
|
+
return [
|
|
399
|
+
'Resume an interrupted runtime run.',
|
|
400
|
+
'',
|
|
401
|
+
'Original task:',
|
|
402
|
+
run.input ?? '(unknown)',
|
|
403
|
+
'',
|
|
404
|
+
session.headlessPlan ? `Current plan:\n${formatPlanStatus(session.headlessPlan)}` : null,
|
|
405
|
+
recentConversation ? `Recent conversation:\n${recentConversation}` : null,
|
|
406
|
+
'',
|
|
407
|
+
'Continue from the current plan state. Start only the next pending step.',
|
|
408
|
+
'If the work is already complete, provide a concise final summary.',
|
|
409
|
+
].filter(Boolean).join('\n');
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function startRecoveredAgenticRun(context, run) {
|
|
413
|
+
if (context.running) return false;
|
|
414
|
+
const session = context.session;
|
|
415
|
+
const supervisor = context.supervisor;
|
|
416
|
+
const runId = run.id;
|
|
417
|
+
const input = buildRecoveryPrompt(run, session);
|
|
418
|
+
context.running = true;
|
|
419
|
+
context.currentAbortController = new AbortController();
|
|
420
|
+
session._currentRunIdentity = {
|
|
421
|
+
runId,
|
|
422
|
+
turnId: `${runId}:resume-0`,
|
|
423
|
+
workspace: context.workspace,
|
|
424
|
+
};
|
|
425
|
+
session._abortSignal = context.currentAbortController.signal;
|
|
426
|
+
supervisor?.setRunSignal(context.currentAbortController.signal);
|
|
427
|
+
session._onStep = (message) => emitRuntimeLog(session, message);
|
|
428
|
+
emitRuntimeLog(session, `runtime: resuming interrupted run ${runId}`);
|
|
429
|
+
|
|
430
|
+
runRuntimeAgenticWorkflow(agent, session, run.input ?? input, {
|
|
431
|
+
initialInput: input,
|
|
432
|
+
signal: context.currentAbortController.signal,
|
|
433
|
+
timeoutMs: 3600 * 1000,
|
|
434
|
+
maxTurns: 20,
|
|
435
|
+
runId,
|
|
436
|
+
pollBusy: supervisor?.pollBusy,
|
|
437
|
+
})
|
|
438
|
+
.catch((err) => {
|
|
439
|
+
if (err?.name === 'AbortError') {
|
|
440
|
+
dispatchAgentEvent(session, createAgentEvent('run_cancelled', {
|
|
441
|
+
origin: 'runtime',
|
|
442
|
+
runId,
|
|
443
|
+
payload: { runId, message: 'Recovered runtime run cancelled.' },
|
|
444
|
+
}));
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
dispatchAgentEvent(session, createAgentEvent('run_error', {
|
|
448
|
+
origin: 'runtime',
|
|
449
|
+
runId,
|
|
450
|
+
payload: {
|
|
451
|
+
runId,
|
|
452
|
+
message: err instanceof Error ? err.message : String(err),
|
|
453
|
+
},
|
|
454
|
+
}));
|
|
455
|
+
})
|
|
456
|
+
.finally(() => {
|
|
457
|
+
context.running = false;
|
|
458
|
+
context.currentAbortController = null;
|
|
459
|
+
supervisor?.setRunSignal(null);
|
|
460
|
+
delete session._abortSignal;
|
|
461
|
+
delete session._onStep;
|
|
462
|
+
delete session._currentRunIdentity;
|
|
463
|
+
});
|
|
326
464
|
|
|
327
|
-
|
|
465
|
+
return true;
|
|
466
|
+
}
|
|
328
467
|
|
|
329
|
-
async function
|
|
468
|
+
async function recoverWorkspace(workspace, { manual = false } = {}) {
|
|
469
|
+
try {
|
|
470
|
+
const context = await getWorkspaceContext(workspace);
|
|
471
|
+
await refreshMcpRuntimeStatus(context.session);
|
|
472
|
+
const gaps = recoveryMcpGaps(context);
|
|
473
|
+
if (gaps.length > 0) {
|
|
474
|
+
const interrupted = store.interruptRuns({ workspace: context.workspace });
|
|
475
|
+
return {
|
|
476
|
+
workspace: context.workspace ?? workspace ?? null,
|
|
477
|
+
resumed: false,
|
|
478
|
+
interrupted,
|
|
479
|
+
reason: `MCP unavailable: ${gaps.join(', ')}`,
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
const recoverableRuns = store.listRecoverableRuns({ workspace: context.workspace });
|
|
483
|
+
const runningRun = recoverableRuns.find((run) => run.status === 'running');
|
|
484
|
+
const activeActivities = activeNonTerminalActivities(context.session);
|
|
485
|
+
const pollingActivities = activePollingActivities(context.session);
|
|
486
|
+
if (runningRun && activeActivities.length === 0) {
|
|
487
|
+
if (!runningRun.input) {
|
|
488
|
+
const interrupted = store.interruptRuns({ workspace: context.workspace });
|
|
489
|
+
return {
|
|
490
|
+
workspace: context.workspace ?? workspace ?? null,
|
|
491
|
+
resumed: false,
|
|
492
|
+
interrupted,
|
|
493
|
+
reason: 'Missing original run input.',
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
const started = startRecoveredAgenticRun(context, runningRun);
|
|
497
|
+
return {
|
|
498
|
+
workspace: context.workspace ?? workspace ?? null,
|
|
499
|
+
resumed: started,
|
|
500
|
+
interrupted: 0,
|
|
501
|
+
mode: 'agentic_loop',
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
if (runningRun && runningRun.input && pollingActivities.length > 0) {
|
|
505
|
+
context.session._onActivitiesTerminal = () => {
|
|
506
|
+
startRecoveredAgenticRun(context, runningRun);
|
|
507
|
+
};
|
|
508
|
+
emitRuntimeLog(context.session, `runtime: recovery watching ${pollingActivities.length} active activity(s)`);
|
|
509
|
+
return {
|
|
510
|
+
workspace: context.workspace ?? workspace ?? null,
|
|
511
|
+
resumed: true,
|
|
512
|
+
interrupted: 0,
|
|
513
|
+
mode: 'activity_poll_then_resume',
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
emitRuntimeLog(context.session, manual ? 'runtime: manual resume completed' : 'runtime: recovery completed');
|
|
517
|
+
const controlStarted = pollingActivities.length === 0
|
|
518
|
+
? serverHandle?.drainControl?.(context) === true
|
|
519
|
+
: false;
|
|
520
|
+
return {
|
|
521
|
+
workspace: context.workspace ?? workspace ?? null,
|
|
522
|
+
resumed: true,
|
|
523
|
+
interrupted: 0,
|
|
524
|
+
mode: controlStarted ? 'control_queue' : pollingActivities.length > 0 ? 'activity_poll' : 'context',
|
|
525
|
+
};
|
|
526
|
+
} catch (err) {
|
|
527
|
+
const interrupted = store.interruptRuns({ workspace });
|
|
528
|
+
return {
|
|
529
|
+
workspace: workspace ?? null,
|
|
530
|
+
resumed: false,
|
|
531
|
+
interrupted,
|
|
532
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
async function recoverRuntime({ workspace = null, manual = false } = {}) {
|
|
538
|
+
const workspaces = workspace ? [workspace] : store.listRecoverableWorkspaces();
|
|
539
|
+
const results = await Promise.all(workspaces.map((item) => recoverWorkspace(item, { manual })));
|
|
540
|
+
return {
|
|
541
|
+
resumed: results.filter((result) => result.resumed).length,
|
|
542
|
+
interrupted: results.reduce((sum, result) => sum + Number(result.interrupted ?? 0), 0),
|
|
543
|
+
workspaces: results,
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
async function executeRun(context, body, { signal } = {}) {
|
|
548
|
+
const session = context.session;
|
|
549
|
+
const supervisor = context.supervisor;
|
|
330
550
|
const input = String(body.input ?? body.prompt ?? '').trim();
|
|
331
551
|
const workspace = body.workspace ? String(body.workspace).trim() : null;
|
|
332
552
|
const timeoutMs = (Number.isFinite(Number(body.timeout)) ? Math.max(1, Number(body.timeout)) : 3600) * 1000;
|
|
333
553
|
const maxTurns = Number.isFinite(Number(body.maxTurns)) ? Math.max(1, Number(body.maxTurns)) : 20;
|
|
334
|
-
const
|
|
554
|
+
const maxReplans = Number.isFinite(Number(body.replans)) ? Math.max(0, Math.floor(Number(body.replans))) : undefined;
|
|
555
|
+
const runId = String(body.runId);
|
|
335
556
|
try {
|
|
336
557
|
if (workspace && session.workspace !== workspace) {
|
|
337
558
|
const result = await handleSlashCommand(`/use ${workspace}`, { packageJson, session });
|
|
338
559
|
if (!session.workspacePath) throw new Error(result.output || `Workspace not loaded: ${workspace}`);
|
|
339
560
|
}
|
|
561
|
+
context.workspace = session.workspace ?? workspace ?? context.workspace ?? null;
|
|
562
|
+
session._currentRunIdentity = {
|
|
563
|
+
runId,
|
|
564
|
+
turnId: `${runId}:turn-0`,
|
|
565
|
+
workspace: context.workspace,
|
|
566
|
+
};
|
|
340
567
|
dispatchAgentEvent(session, createAgentEvent('run_started', {
|
|
341
568
|
origin: 'runtime',
|
|
342
569
|
runId,
|
|
343
|
-
payload: { input, workspace },
|
|
570
|
+
payload: { input, workspace: session._currentRunIdentity.workspace },
|
|
344
571
|
}));
|
|
345
572
|
dispatchAgentEvent(session, createAgentEvent('user_message', {
|
|
346
573
|
origin: 'user',
|
|
@@ -348,35 +575,22 @@ async function runRuntime(argv, agent) {
|
|
|
348
575
|
payload: { content: input },
|
|
349
576
|
}));
|
|
350
577
|
session._abortSignal = signal ?? null;
|
|
578
|
+
session._runApprovalRequired = body.requireApproval === true;
|
|
579
|
+
session._runApprovalResolved = false;
|
|
580
|
+
session._approvalTimeoutMs = Number.isFinite(Number(body.approvalTimeoutMs))
|
|
581
|
+
? Math.max(1, Number(body.approvalTimeoutMs))
|
|
582
|
+
: undefined;
|
|
351
583
|
supervisor?.setRunSignal(signal);
|
|
352
584
|
session._onStep = (message) => emitRuntimeLog(session, message);
|
|
353
|
-
|
|
585
|
+
await runRuntimeAgenticWorkflow(agent, session, input, {
|
|
354
586
|
signal,
|
|
355
587
|
timeoutMs,
|
|
356
588
|
maxTurns,
|
|
357
589
|
runId,
|
|
358
590
|
pollBusy: supervisor?.pollBusy,
|
|
591
|
+
evaluate: body.evaluate !== false,
|
|
592
|
+
...(maxReplans === undefined ? {} : { maxReplans }),
|
|
359
593
|
});
|
|
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
594
|
} catch (err) {
|
|
381
595
|
if (err?.name === 'AbortError') {
|
|
382
596
|
dispatchAgentEvent(session, createAgentEvent('run_cancelled', {
|
|
@@ -401,6 +615,10 @@ async function runRuntime(argv, agent) {
|
|
|
401
615
|
supervisor?.setRunSignal(null);
|
|
402
616
|
delete session._abortSignal;
|
|
403
617
|
delete session._onStep;
|
|
618
|
+
delete session._currentRunIdentity;
|
|
619
|
+
delete session._runApprovalRequired;
|
|
620
|
+
delete session._runApprovalResolved;
|
|
621
|
+
delete session._approvalTimeoutMs;
|
|
404
622
|
}
|
|
405
623
|
}
|
|
406
624
|
|
|
@@ -408,19 +626,54 @@ async function runRuntime(argv, agent) {
|
|
|
408
626
|
host,
|
|
409
627
|
port,
|
|
410
628
|
store,
|
|
411
|
-
|
|
629
|
+
getContext: getWorkspaceContext,
|
|
412
630
|
run: executeRun,
|
|
413
|
-
cancel: () => emitRuntimeLog(session, 'runtime: cancel requested'),
|
|
631
|
+
cancel: (context) => emitRuntimeLog(context.session, 'runtime: cancel requested'),
|
|
632
|
+
resume: ({ workspace }) => recoverRuntime({ workspace, manual: true }),
|
|
633
|
+
approve: async ({ workspace, runId, itemId, approvalId }) => {
|
|
634
|
+
const context = await getWorkspaceContext(workspace);
|
|
635
|
+
return context.approvalManager?.approve({ runId, itemId, approvalId }) ?? { approved: false };
|
|
636
|
+
},
|
|
637
|
+
configProfiles: async (context) => {
|
|
638
|
+
const profiles = listWikircProfiles(context.session.workspacePath);
|
|
639
|
+
return {
|
|
640
|
+
profiles: profiles.map((profile) => profile.name),
|
|
641
|
+
active: context.session.wikirc?.profile ?? null,
|
|
642
|
+
items: profiles.map((profile) => ({
|
|
643
|
+
name: profile.name,
|
|
644
|
+
fileName: profile.fileName,
|
|
645
|
+
default: Boolean(profile.default),
|
|
646
|
+
})),
|
|
647
|
+
};
|
|
648
|
+
},
|
|
649
|
+
useConfigProfile: async (context, profile) => {
|
|
650
|
+
const { summary, config } = applySessionWikircProfile(context.session, profile);
|
|
651
|
+
await refreshMcpRuntimeStatus(context.session);
|
|
652
|
+
dispatchAgentEvent(context.session, createAgentEvent('runtime_log', {
|
|
653
|
+
origin: 'runtime',
|
|
654
|
+
payload: { message: `runtime: config profile switched to ${context.session.wikirc?.profile ?? profile}` },
|
|
655
|
+
}));
|
|
656
|
+
return {
|
|
657
|
+
ok: true,
|
|
658
|
+
active: context.session.wikirc?.profile ?? profile,
|
|
659
|
+
fileName: context.session.wikirc?.fileName ?? null,
|
|
660
|
+
summary,
|
|
661
|
+
config,
|
|
662
|
+
};
|
|
663
|
+
},
|
|
414
664
|
token: auth.token,
|
|
415
665
|
});
|
|
416
|
-
|
|
666
|
+
const recovery = await recoverRuntime();
|
|
417
667
|
|
|
418
668
|
console.log(`wiki-manager runtime listening on http://${host}:${port}`);
|
|
419
669
|
console.log(`runtime state: ${store.dbPath}`);
|
|
670
|
+
if (recovery.resumed > 0 || recovery.interrupted > 0) {
|
|
671
|
+
console.log(`runtime recovery: resumed=${recovery.resumed} interrupted=${recovery.interrupted}`);
|
|
672
|
+
}
|
|
420
673
|
if (auth.tokenPath) console.log(`runtime token: ${auth.tokenPath}`);
|
|
421
674
|
|
|
422
675
|
const shutdown = async () => {
|
|
423
|
-
supervisor?.stop();
|
|
676
|
+
await Promise.all([...new Set(contexts.values())].map(async (v) => { (await v).supervisor?.stop(); }));
|
|
424
677
|
await serverHandle.close();
|
|
425
678
|
store.close();
|
|
426
679
|
process.exit(0);
|
package/src/commands/slash.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
3
3
|
import { join, relative } from 'node:path';
|
|
4
|
-
import { createLlmClientFromWikiConfig } from '../agent/llm.js';
|
|
5
4
|
import { composeServices, listServices, runWikiCli, serviceLogs, serviceStates, startService, stopService } from '../core/compose.js';
|
|
6
5
|
import {
|
|
7
6
|
applyMcpRuntimeStatus,
|
|
@@ -26,10 +25,10 @@ import {
|
|
|
26
25
|
} from '../core/jobQueue.js';
|
|
27
26
|
import {
|
|
28
27
|
listWikircProfiles,
|
|
29
|
-
loadWikircProfile,
|
|
30
28
|
resolveWikircProfile,
|
|
31
29
|
summarizeWikircConfig,
|
|
32
30
|
} from '../core/wikirc.js';
|
|
31
|
+
import { applySessionWikircProfile } from '../core/sessionConfig.js';
|
|
33
32
|
import { deleteWorkspaceAndFiles, startAgents, stopAgents } from '../core/wikiSetup.js';
|
|
34
33
|
import {
|
|
35
34
|
cleanDocumentUploads,
|
|
@@ -490,7 +489,7 @@ function publishDocumentActivity(session, activity) {
|
|
|
490
489
|
return publishPayloadActivity(session, { _activity: activity }, { server: 'documents', tool: 'documents_convert_to_markdown' });
|
|
491
490
|
}
|
|
492
491
|
|
|
493
|
-
async function refreshMcpRuntimeStatus(session) {
|
|
492
|
+
export async function refreshMcpRuntimeStatus(session) {
|
|
494
493
|
session.mcp = buildMcpStatus(session);
|
|
495
494
|
if (!session.workspacePath) return null;
|
|
496
495
|
try {
|
|
@@ -546,25 +545,6 @@ function loadWorkspaceSystemPrompt(workspacePath) {
|
|
|
546
545
|
return existsSync(promptPath) ? readFileSync(promptPath, 'utf8').trim() || null : null;
|
|
547
546
|
}
|
|
548
547
|
|
|
549
|
-
function loadSessionWikirc(session, profileName = 'default') {
|
|
550
|
-
if (!session.workspacePath) {
|
|
551
|
-
throw new Error('No workspace loaded. Use /use <workspace>.');
|
|
552
|
-
}
|
|
553
|
-
const loaded = loadWikircProfile(session.workspacePath, profileName);
|
|
554
|
-
session.wikirc = {
|
|
555
|
-
profile: loaded.profile.name,
|
|
556
|
-
fileName: loaded.profile.fileName,
|
|
557
|
-
path: loaded.profile.path,
|
|
558
|
-
};
|
|
559
|
-
session.wikircConfig = loaded.config;
|
|
560
|
-
session.language = loaded.config?.language ?? null;
|
|
561
|
-
session.llm = createLlmClientFromWikiConfig(loaded.config);
|
|
562
|
-
if (session.mcp?.production) {
|
|
563
|
-
session.mcp.production.activeConfigPath = loaded.profile.fileName;
|
|
564
|
-
}
|
|
565
|
-
return summarizeWikircConfig(loaded.profile, loaded.config);
|
|
566
|
-
}
|
|
567
|
-
|
|
568
548
|
function clearWorkspaceSession(session) {
|
|
569
549
|
session.workspace = null;
|
|
570
550
|
session.workspacePath = null;
|
|
@@ -720,7 +700,7 @@ export async function handleSlashCommand(line, context) {
|
|
|
720
700
|
context.session.systemPrompt = loadWorkspaceSystemPrompt(workspace.workspacePath);
|
|
721
701
|
try {
|
|
722
702
|
step(`Workspace: loading ${workspace.name} config…`);
|
|
723
|
-
const summary =
|
|
703
|
+
const { summary } = applySessionWikircProfile(context.session, 'default');
|
|
724
704
|
step(`Workspace: discovering ${workspace.name} MCP tools…`);
|
|
725
705
|
await refreshMcpRuntimeStatus(context.session);
|
|
726
706
|
return {
|
|
@@ -759,7 +739,7 @@ export async function handleSlashCommand(line, context) {
|
|
|
759
739
|
return { output: 'Usage: /config use <default|name>' };
|
|
760
740
|
}
|
|
761
741
|
try {
|
|
762
|
-
const summary =
|
|
742
|
+
const { summary } = applySessionWikircProfile(context.session, profileName);
|
|
763
743
|
await refreshMcpRuntimeStatus(context.session);
|
|
764
744
|
return {
|
|
765
745
|
output: [
|