@dotdrelle/wiki-manager 0.12.8 → 0.12.9
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/README.md +10 -2
- package/bin/wiki-manager +7 -0
- package/package.json +1 -1
- package/src/cli/wiki-manager.js +14 -1
- package/src/core/buildInfo.json +2 -2
- package/src/core/env.js +3 -3
- package/src/core/mcp.js +1 -1
- package/src/core/wikiSetup.js +8 -0
- package/src/runtime/lifecycle.js +31 -1
- package/src/runtime/server.js +5 -0
- package/src/runtime/server.test.js +28 -0
package/README.md
CHANGED
|
@@ -48,6 +48,7 @@ Use a local installation when the manager should be pinned in a project's
|
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
50
|
npm install @dotdrelle/wiki-manager
|
|
51
|
+
npm approve-scripts bun # only when npm reports that Bun's postinstall is pending
|
|
51
52
|
npx wiki-manager
|
|
52
53
|
npx wiki-workspace --help
|
|
53
54
|
```
|
|
@@ -55,7 +56,9 @@ npx wiki-workspace --help
|
|
|
55
56
|
`npm install` does not add `wiki-manager` or `wiki-workspace` to the shell's
|
|
56
57
|
global `PATH`. Run local executables with `npx` (or `npm exec wiki-manager` and
|
|
57
58
|
`npm exec wiki-workspace`). Bun is installed automatically as a package runtime;
|
|
58
|
-
you do not need to add `~/.bun/bin` to `PATH`.
|
|
59
|
+
you do not need to add `~/.bun/bin` to `PATH`. Recent npm versions may require
|
|
60
|
+
the explicit `npm approve-scripts bun` security approval shown above before the
|
|
61
|
+
first launch.
|
|
59
62
|
|
|
60
63
|
### Global installation
|
|
61
64
|
|
|
@@ -69,7 +72,12 @@ wiki-workspace --help
|
|
|
69
72
|
```
|
|
70
73
|
|
|
71
74
|
The global installation also installs the required Bun runtime. A separately
|
|
72
|
-
installed Bun remains supported, but is not required.
|
|
75
|
+
installed Bun remains supported, but is not required. If npm blocks Bun's
|
|
76
|
+
installation script, reinstall with:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npm install --global --allow-scripts=bun @dotdrelle/wiki-manager
|
|
80
|
+
```
|
|
73
81
|
|
|
74
82
|
In both modes, launch the commands from the directory that should hold the
|
|
75
83
|
manager state (`workspaces/`, `.env`, and `mcp.endpoints.json`).
|
package/bin/wiki-manager
CHANGED
|
@@ -71,6 +71,13 @@ if [[ -z "$BUN_BIN" ]]; then
|
|
|
71
71
|
'Reinstall @dotdrelle/wiki-manager or install Bun from https://bun.sh/docs/installation.' >&2
|
|
72
72
|
exit 127
|
|
73
73
|
fi
|
|
74
|
+
if ! "$BUN_BIN" --version >/dev/null 2>&1; then
|
|
75
|
+
printf '%s\n' \
|
|
76
|
+
'The bundled Bun runtime is installed but not initialized.' \
|
|
77
|
+
'Local install: npm approve-scripts bun' \
|
|
78
|
+
'Global install: npm install -g --allow-scripts=bun @dotdrelle/wiki-manager' >&2
|
|
79
|
+
exit 126
|
|
80
|
+
fi
|
|
74
81
|
|
|
75
82
|
if [[ ${#ARGS[@]} -eq 0 ]]; then
|
|
76
83
|
exec "$BUN_BIN" "$BIN_DIR/wiki-manager.js"
|
package/package.json
CHANGED
package/src/cli/wiki-manager.js
CHANGED
|
@@ -677,6 +677,9 @@ async function runRuntime(argv, agent) {
|
|
|
677
677
|
port,
|
|
678
678
|
store,
|
|
679
679
|
getContext: getWorkspaceContext,
|
|
680
|
+
listActiveRuns: () => [...contexts.values()]
|
|
681
|
+
.filter((context) => context?.running)
|
|
682
|
+
.map((context) => ({ workspace: context.workspace ?? null, runId: context.currentRunId ?? null })),
|
|
680
683
|
run: executeRun,
|
|
681
684
|
cancel: (context) => emitRuntimeLog(context.session, 'runtime: cancel requested'),
|
|
682
685
|
resume: ({ workspace }) => recoverRuntime({ workspace, manual: true }),
|
|
@@ -801,12 +804,18 @@ export async function runCli(argv) {
|
|
|
801
804
|
let runtime = null;
|
|
802
805
|
try {
|
|
803
806
|
const { ensureRuntime } = await import('../runtime/lifecycle.js');
|
|
804
|
-
|
|
807
|
+
// If the manager files were just scaffolded, a pre-existing runtime
|
|
808
|
+
// predates them: restart it so its sessions load the new endpoints.
|
|
809
|
+
runtime = await ensureRuntime({ forceRestart: scaffolded.length > 0 });
|
|
805
810
|
} catch (err) {
|
|
806
811
|
runtime = unavailableRuntime(err);
|
|
807
812
|
console.error(`Runtime unavailable: ${runtime.error}`);
|
|
808
813
|
}
|
|
809
814
|
await runOpenTuiShell({ agent, packageJson, runtime });
|
|
815
|
+
if (runtime?.url) {
|
|
816
|
+
const { shutdownOwnedRuntime } = await import('../runtime/lifecycle.js');
|
|
817
|
+
await shutdownOwnedRuntime(runtime, { log: (message) => console.log(`[wiki-manager] ${message}`) });
|
|
818
|
+
}
|
|
810
819
|
return;
|
|
811
820
|
}
|
|
812
821
|
|
|
@@ -821,4 +830,8 @@ export async function runCli(argv) {
|
|
|
821
830
|
}
|
|
822
831
|
}
|
|
823
832
|
await runShell({ agent, packageJson, runtime });
|
|
833
|
+
if (runtime?.url) {
|
|
834
|
+
const { shutdownOwnedRuntime } = await import('../runtime/lifecycle.js');
|
|
835
|
+
await shutdownOwnedRuntime(runtime, { log: (message) => console.log(`[wiki-manager] ${message}`) });
|
|
836
|
+
}
|
|
824
837
|
}
|
package/src/core/buildInfo.json
CHANGED
package/src/core/env.js
CHANGED
|
@@ -38,8 +38,8 @@ const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..')
|
|
|
38
38
|
// mcp.endpoints.json nor .env, so the external agents (cme, mailer,
|
|
39
39
|
// documents) silently never connect — /status shows no agents and Donna has
|
|
40
40
|
// no CME tools to configure anything with. Copy the packaged examples so a
|
|
41
|
-
// fresh directory works out of the box with the default agent ports
|
|
42
|
-
//
|
|
41
|
+
// fresh directory works out of the box with the default agent ports. Optional
|
|
42
|
+
// credentials can be added later for the external services the user enables.
|
|
43
43
|
export function ensureManagerScaffold({ log = () => {} } = {}) {
|
|
44
44
|
const created = [];
|
|
45
45
|
const endpointsFile = managerMcpEndpointsFile();
|
|
@@ -55,7 +55,7 @@ export function ensureManagerScaffold({ log = () => {} } = {}) {
|
|
|
55
55
|
created.push('.env');
|
|
56
56
|
}
|
|
57
57
|
if (created.length > 0) {
|
|
58
|
-
log(`
|
|
58
|
+
log(`configuration initialized successfully in ${managerStateDir()} — created ${created.join(' and ')} from packaged defaults. Optional credentials can be added later for external services.`);
|
|
59
59
|
}
|
|
60
60
|
return created;
|
|
61
61
|
}
|
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.12.
|
|
4
|
+
const WIKI_MANAGER_VERSION = '0.12.9';
|
|
5
5
|
|
|
6
6
|
function envValue(key) {
|
|
7
7
|
const filePath = managerEnvFile();
|
package/src/core/wikiSetup.js
CHANGED
|
@@ -3,6 +3,7 @@ import { readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { promisify } from 'node:util';
|
|
5
5
|
import { patchWikircProfile } from './wikirc.js';
|
|
6
|
+
import { managerEnvFile, managerMcpEndpointsFile } from './env.js';
|
|
6
7
|
import { createWorkspace, findWorkspace, isValidWorkspaceName, listWorkspaces, managerRoot, workspacesDir } from './workspaces.js';
|
|
7
8
|
|
|
8
9
|
const execFileAsync = promisify(execFile);
|
|
@@ -39,6 +40,13 @@ export async function startAgents(options = {}) {
|
|
|
39
40
|
env: {
|
|
40
41
|
...process.env,
|
|
41
42
|
WIKI_WORKSPACES_DIR: workspacesDir(),
|
|
43
|
+
// cwd is the npm package root (the script and compose files live
|
|
44
|
+
// there), so the manager files MUST be pinned explicitly: without
|
|
45
|
+
// these, wiki-workspace resolved .env and mcp.endpoints.json against
|
|
46
|
+
// $PWD and generated agent tokens into the PACKAGE directory — the
|
|
47
|
+
// user's .env stayed empty on a fresh install.
|
|
48
|
+
WIKI_MANAGER_ENV_FILE: managerEnvFile(),
|
|
49
|
+
WIKI_MANAGER_ENDPOINTS_FILE: managerMcpEndpointsFile(),
|
|
42
50
|
},
|
|
43
51
|
timeout: options.timeout ?? 180_000,
|
|
44
52
|
maxBuffer: options.maxBuffer ?? 1024 * 1024 * 8,
|
package/src/runtime/lifecycle.js
CHANGED
|
@@ -39,6 +39,7 @@ export async function ensureRuntime({
|
|
|
39
39
|
stateDir = process.env.WIKI_MANAGER_STATE_DIR ?? defaultRuntimeStateDir(),
|
|
40
40
|
url = process.env.WIKI_MANAGER_RUNTIME_URL ?? `http://127.0.0.1:${port}`,
|
|
41
41
|
timeoutMs = 5000,
|
|
42
|
+
forceRestart = false,
|
|
42
43
|
} = {}) {
|
|
43
44
|
const auth = resolveRuntimeAuthToken({ host, stateDir });
|
|
44
45
|
if (auth.token) process.env.WIKI_MANAGER_RUNTIME_TOKEN = auth.token;
|
|
@@ -46,7 +47,11 @@ export async function ensureRuntime({
|
|
|
46
47
|
if (existing) {
|
|
47
48
|
const expectedCacertPath = activeCacertPath();
|
|
48
49
|
const actualCacertPath = existing.cacertPath ? resolve(existing.cacertPath) : null;
|
|
49
|
-
|
|
50
|
+
// forceRestart: the caller knows the manager configuration just changed
|
|
51
|
+
// (e.g. mcp.endpoints.json scaffolded on first run) — a runtime started
|
|
52
|
+
// BEFORE that only knows the old endpoints and would keep answering
|
|
53
|
+
// without the agents until manually restarted.
|
|
54
|
+
if (!forceRestart && actualCacertPath === expectedCacertPath) {
|
|
50
55
|
return { url, started: false, health: existing, token: auth.token, tokenPath: auth.tokenPath };
|
|
51
56
|
}
|
|
52
57
|
await postRuntimeShutdown({ url, token: auth.token });
|
|
@@ -99,3 +104,28 @@ export async function runtimeHealthOrNull(url = runtimeUrlFromEnv(), token = run
|
|
|
99
104
|
return null;
|
|
100
105
|
}
|
|
101
106
|
}
|
|
107
|
+
|
|
108
|
+
// Contract with the user: the shell OWNS the runtime it started — leaving it
|
|
109
|
+
// alive after exit produced zombie runtimes running yesterday's code and
|
|
110
|
+
// yesterday's endpoints. Nuance preserved: if a run is active anywhere, the
|
|
111
|
+
// runtime is left alive so the run survives the shell (that promise stays).
|
|
112
|
+
export async function shutdownOwnedRuntime(runtime, { log = () => {} } = {}) {
|
|
113
|
+
if (!runtime?.url || !runtime?.started) return { action: 'kept', reason: 'not_owned' };
|
|
114
|
+
try {
|
|
115
|
+
const health = await runtimeHealthOrNull(runtime.url, runtime.token);
|
|
116
|
+
if (!health) return { action: 'kept', reason: 'unreachable' };
|
|
117
|
+
const activeRuns = Array.isArray(health.activeRuns) ? health.activeRuns : [];
|
|
118
|
+
if (activeRuns.length > 0) {
|
|
119
|
+
const labels = activeRuns
|
|
120
|
+
.map((run) => [run.workspace, run.runId ? String(run.runId).slice(0, 8) : null].filter(Boolean).join('/'))
|
|
121
|
+
.join(', ');
|
|
122
|
+
log(`runtime laissé actif : run en cours (${labels}) — il survivra à ce shell ; relance wiki-manager pour le retrouver.`);
|
|
123
|
+
return { action: 'kept', reason: 'run_active', activeRuns };
|
|
124
|
+
}
|
|
125
|
+
await postRuntimeShutdown({ url: runtime.url, token: runtime.token });
|
|
126
|
+
log('runtime arrêté (démarré par ce shell, aucun run en cours).');
|
|
127
|
+
return { action: 'shutdown' };
|
|
128
|
+
} catch (err) {
|
|
129
|
+
return { action: 'error', reason: err instanceof Error ? err.message : String(err) };
|
|
130
|
+
}
|
|
131
|
+
}
|
package/src/runtime/server.js
CHANGED
|
@@ -20,6 +20,7 @@ export function startRuntimeServer({
|
|
|
20
20
|
approve,
|
|
21
21
|
configProfiles,
|
|
22
22
|
useConfigProfile,
|
|
23
|
+
listActiveRuns = null,
|
|
23
24
|
exitOnShutdown = process.env.WIKI_MANAGER_RUNTIME_CHILD === '1',
|
|
24
25
|
} = {}) {
|
|
25
26
|
const clients = new Set();
|
|
@@ -52,10 +53,14 @@ export function startRuntimeServer({
|
|
|
52
53
|
if (request.method === 'GET' && url.pathname === '/health') {
|
|
53
54
|
const workspace = workspaceFromUrl(url);
|
|
54
55
|
const context = workspace ? await resolveContext({ workspace }) : null;
|
|
56
|
+
// activeRuns spans ALL workspace contexts: the shell uses it at exit
|
|
57
|
+
// to decide whether shutting down its own runtime would kill work.
|
|
58
|
+
const activeRuns = typeof listActiveRuns === 'function' ? listActiveRuns() : [];
|
|
55
59
|
sendJson(response, 200, {
|
|
56
60
|
ok: true,
|
|
57
61
|
status: context?.running ? 'running' : 'idle',
|
|
58
62
|
workspace: context?.workspace ?? workspace ?? null,
|
|
63
|
+
activeRuns,
|
|
59
64
|
dbPath: store.dbPath,
|
|
60
65
|
cacertPath: activeCacertPath(),
|
|
61
66
|
nodeExtraCaCerts: process.env.NODE_EXTRA_CA_CERTS ?? null,
|
|
@@ -1396,3 +1396,31 @@ test('runtime server answers control messages posted to /run during an active ru
|
|
|
1396
1396
|
await handle.close();
|
|
1397
1397
|
}
|
|
1398
1398
|
});
|
|
1399
|
+
|
|
1400
|
+
test('runtime health reports active runs across workspaces', async (t) => {
|
|
1401
|
+
let handle;
|
|
1402
|
+
try {
|
|
1403
|
+
handle = await startRuntimeServer({
|
|
1404
|
+
host: '127.0.0.1',
|
|
1405
|
+
port: 0,
|
|
1406
|
+
store: { dbPath: ':memory:', getState: () => ({ status: 'idle' }), listEvents: () => [] },
|
|
1407
|
+
session: {},
|
|
1408
|
+
// The shell reads this at exit: shutting down its own runtime must not
|
|
1409
|
+
// kill a run that is supposed to survive the shell.
|
|
1410
|
+
listActiveRuns: () => [{ workspace: 'juno', runId: 'run-1234abcd' }],
|
|
1411
|
+
});
|
|
1412
|
+
} catch (err) {
|
|
1413
|
+
if (err?.code === 'EPERM') {
|
|
1414
|
+
t.skip('network listen is not permitted in this sandbox');
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
throw err;
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
try {
|
|
1421
|
+
const health = await (await fetch(`http://127.0.0.1:${handle.port}/health`)).json();
|
|
1422
|
+
assert.deepEqual(health.activeRuns, [{ workspace: 'juno', runId: 'run-1234abcd' }]);
|
|
1423
|
+
} finally {
|
|
1424
|
+
await handle.close();
|
|
1425
|
+
}
|
|
1426
|
+
});
|