@dotdrelle/wiki-manager 0.12.7 → 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 +57 -8
- package/bin/wiki-manager +30 -12
- package/docker-compose.yml +0 -1
- package/mcp.endpoints.example.json +0 -6
- package/package.json +2 -1
- package/src/cli/wiki-manager.js +23 -2
- package/src/core/buildInfo.json +2 -2
- package/src/core/env.js +30 -1
- 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/wiki-workspace +33 -0
package/README.md
CHANGED
|
@@ -36,6 +36,52 @@ everything — either with the mouse in a browser, or by talking to an assistant
|
|
|
36
36
|
A **workspace** = a project. Each project is isolated: its documents, settings,
|
|
37
37
|
and results never get mixed up with the others.
|
|
38
38
|
|
|
39
|
+
## Installation and command modes
|
|
40
|
+
|
|
41
|
+
The package can be installed either locally in a project or globally. The
|
|
42
|
+
command used afterwards depends on the installation mode.
|
|
43
|
+
|
|
44
|
+
### Local installation
|
|
45
|
+
|
|
46
|
+
Use a local installation when the manager should be pinned in a project's
|
|
47
|
+
`package.json`:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm install @dotdrelle/wiki-manager
|
|
51
|
+
npm approve-scripts bun # only when npm reports that Bun's postinstall is pending
|
|
52
|
+
npx wiki-manager
|
|
53
|
+
npx wiki-workspace --help
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`npm install` does not add `wiki-manager` or `wiki-workspace` to the shell's
|
|
57
|
+
global `PATH`. Run local executables with `npx` (or `npm exec wiki-manager` and
|
|
58
|
+
`npm exec wiki-workspace`). Bun is installed automatically as a package runtime;
|
|
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.
|
|
62
|
+
|
|
63
|
+
### Global installation
|
|
64
|
+
|
|
65
|
+
Use a global installation when the commands should be available directly from
|
|
66
|
+
any directory:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm install --global @dotdrelle/wiki-manager
|
|
70
|
+
wiki-manager
|
|
71
|
+
wiki-workspace --help
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The global installation also installs the required Bun runtime. A separately
|
|
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
|
+
```
|
|
81
|
+
|
|
82
|
+
In both modes, launch the commands from the directory that should hold the
|
|
83
|
+
manager state (`workspaces/`, `.env`, and `mcp.endpoints.json`).
|
|
84
|
+
|
|
39
85
|
## Functional overview
|
|
40
86
|
|
|
41
87
|
The diagram below shows the whole picture at a glance: how **inputs** (external
|
|
@@ -54,14 +100,17 @@ chat** — all on the **shipped example**, with **no external source to
|
|
|
54
100
|
configure**. External agents (Confluence, mail…) come later, only when you plug
|
|
55
101
|
in real sources.
|
|
56
102
|
|
|
57
|
-
> **Prerequisites:** Docker running and Node ≥ 22.
|
|
103
|
+
> **Prerequisites:** Docker running and Node ≥ 22. Bun is installed with the npm
|
|
104
|
+
> package.
|
|
58
105
|
|
|
59
|
-
**1 — Install
|
|
106
|
+
**1 — Install globally, pick a home folder.**
|
|
60
107
|
The manager keeps its state (workspaces, `.env`, endpoints) in the directory
|
|
61
|
-
where you launch it, so give it a home
|
|
108
|
+
where you launch it, so give it a home. This quick start uses the global mode;
|
|
109
|
+
see [Installation and command modes](#installation-and-command-modes) for the
|
|
110
|
+
local `npm install` + `npx` mode.
|
|
62
111
|
|
|
63
112
|
```bash
|
|
64
|
-
npm
|
|
113
|
+
npm install --global @dotdrelle/wiki-manager
|
|
65
114
|
mkdir -p ~/llm-wiki && cd ~/llm-wiki # all manager state lives here
|
|
66
115
|
```
|
|
67
116
|
|
|
@@ -151,7 +200,7 @@ wiki-workspace wiki demo build # or, in the shell: /skills run pipeli
|
|
|
151
200
|
That's the whole loop. Next: the four ways to use it and how to configure the
|
|
152
201
|
external agents (CME & co.) live in [docs/usage.md](https://raw.githubusercontent.com/dotdrelle/llm-wiki-manager/main/docs/usage.md); the detailed
|
|
153
202
|
story is in [The journey](#the-journey-from-first-launch-to-first-result); and
|
|
154
|
-
installing from source is in [
|
|
203
|
+
installing from source is in [Installing from source](#installing-from-source).
|
|
155
204
|
|
|
156
205
|
## The journey: from first launch to first result
|
|
157
206
|
|
|
@@ -379,15 +428,15 @@ root `.env`; the wiki's **LLM keys** live in each workspace `.wikirc.yaml`.
|
|
|
379
428
|
See the full, field-by-field reference in
|
|
380
429
|
**[docs/configuration.md](https://raw.githubusercontent.com/dotdrelle/llm-wiki-manager/main/docs/configuration.md)**.
|
|
381
430
|
|
|
382
|
-
##
|
|
431
|
+
## Installing from source
|
|
383
432
|
|
|
384
433
|
```bash
|
|
385
434
|
corepack enable
|
|
386
435
|
pnpm install
|
|
387
436
|
```
|
|
388
437
|
|
|
389
|
-
|
|
390
|
-
the package, in the directory where the command is launched:
|
|
438
|
+
Whether installed locally, globally, or from source, `wiki-manager` keeps its
|
|
439
|
+
state outside the package, in the directory where the command is launched:
|
|
391
440
|
|
|
392
441
|
```text
|
|
393
442
|
./workspaces/ # workspace registry
|
package/bin/wiki-manager
CHANGED
|
@@ -39,7 +39,26 @@ if [[ -n "$CACERT_PATH" ]]; then
|
|
|
39
39
|
export CURL_CA_BUNDLE="$CACERT_PATH"
|
|
40
40
|
fi
|
|
41
41
|
|
|
42
|
+
SCRIPT_SOURCE="${BASH_SOURCE[0]}"
|
|
43
|
+
while [[ -L "$SCRIPT_SOURCE" ]]; do
|
|
44
|
+
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_SOURCE")" && pwd)"
|
|
45
|
+
SCRIPT_TARGET="$(readlink "$SCRIPT_SOURCE")"
|
|
46
|
+
if [[ "$SCRIPT_TARGET" = /* ]]; then
|
|
47
|
+
SCRIPT_SOURCE="$SCRIPT_TARGET"
|
|
48
|
+
else
|
|
49
|
+
SCRIPT_SOURCE="$SCRIPT_DIR/$SCRIPT_TARGET"
|
|
50
|
+
fi
|
|
51
|
+
done
|
|
52
|
+
BIN_DIR="$(cd "$(dirname "$SCRIPT_SOURCE")" && pwd)"
|
|
53
|
+
PACKAGE_DIR="$(cd "$BIN_DIR/.." && pwd)"
|
|
54
|
+
|
|
42
55
|
BUN_BIN="$(command -v bun 2>/dev/null || true)"
|
|
56
|
+
if [[ -z "$BUN_BIN" && -x "$PACKAGE_DIR/node_modules/.bin/bun" ]]; then
|
|
57
|
+
BUN_BIN="$PACKAGE_DIR/node_modules/.bin/bun"
|
|
58
|
+
fi
|
|
59
|
+
if [[ -z "$BUN_BIN" && -x "$PACKAGE_DIR/../../.bin/bun" ]]; then
|
|
60
|
+
BUN_BIN="$PACKAGE_DIR/../../.bin/bun"
|
|
61
|
+
fi
|
|
43
62
|
if [[ -z "$BUN_BIN" && -n "${BUN_INSTALL:-}" && -x "$BUN_INSTALL/bin/bun" ]]; then
|
|
44
63
|
BUN_BIN="$BUN_INSTALL/bin/bun"
|
|
45
64
|
fi
|
|
@@ -49,20 +68,19 @@ fi
|
|
|
49
68
|
if [[ -z "$BUN_BIN" ]]; then
|
|
50
69
|
printf '%s\n' \
|
|
51
70
|
'wiki-manager requires Bun >= 1.2.' \
|
|
52
|
-
'
|
|
71
|
+
'Reinstall @dotdrelle/wiki-manager or install Bun from https://bun.sh/docs/installation.' >&2
|
|
53
72
|
exit 127
|
|
54
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
|
|
55
81
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
SCRIPT_TARGET="$(readlink "$SCRIPT_SOURCE")"
|
|
60
|
-
if [[ "$SCRIPT_TARGET" = /* ]]; then
|
|
61
|
-
SCRIPT_SOURCE="$SCRIPT_TARGET"
|
|
62
|
-
else
|
|
63
|
-
SCRIPT_SOURCE="$SCRIPT_DIR/$SCRIPT_TARGET"
|
|
64
|
-
fi
|
|
65
|
-
done
|
|
66
|
-
BIN_DIR="$(cd "$(dirname "$SCRIPT_SOURCE")" && pwd)"
|
|
82
|
+
if [[ ${#ARGS[@]} -eq 0 ]]; then
|
|
83
|
+
exec "$BUN_BIN" "$BIN_DIR/wiki-manager.js"
|
|
84
|
+
fi
|
|
67
85
|
|
|
68
86
|
exec "$BUN_BIN" "$BIN_DIR/wiki-manager.js" "${ARGS[@]}"
|
package/docker-compose.yml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotdrelle/wiki-manager",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.9",
|
|
4
4
|
"description": "Agentic shell and orchestration cockpit for llm-wiki workspaces.",
|
|
5
5
|
"license": "PolyForm-Noncommercial-1.0.0",
|
|
6
6
|
"author": "dotrelle",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"@langchain/langgraph": "^1.3.2",
|
|
50
50
|
"@opentui/core": "^0.3.2",
|
|
51
51
|
"@opentui/solid": "^0.3.2",
|
|
52
|
+
"bun": "^1.3.14",
|
|
52
53
|
"marked": "^15.0.12",
|
|
53
54
|
"marked-terminal": "^7.3.0",
|
|
54
55
|
"openai": "^6.42.0",
|
package/src/cli/wiki-manager.js
CHANGED
|
@@ -2,7 +2,7 @@ import { readFileSync } from 'node:fs';
|
|
|
2
2
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { dirname, join, resolve } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import { loadManagerEnv } from '../core/env.js';
|
|
5
|
+
import { ensureManagerScaffold, loadManagerEnv } from '../core/env.js';
|
|
6
6
|
loadManagerEnv();
|
|
7
7
|
import { createAgentGraph } from '../agent/graph.js';
|
|
8
8
|
import { handleSlashCommand, printHelp, printVersion, refreshMcpRuntimeStatus } from '../commands/slash.js';
|
|
@@ -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 }),
|
|
@@ -735,6 +738,8 @@ async function runRuntime(argv, agent) {
|
|
|
735
738
|
|
|
736
739
|
export async function runCli(argv) {
|
|
737
740
|
if (argv[0] === 'runtime') {
|
|
741
|
+
const scaffolded = ensureManagerScaffold({ log: (message) => console.log(`[wiki-manager] ${message}`) });
|
|
742
|
+
if (scaffolded.length > 0) loadManagerEnv();
|
|
738
743
|
const agent = createAgentGraph();
|
|
739
744
|
await runRuntime(argv.slice(1), agent);
|
|
740
745
|
return;
|
|
@@ -788,17 +793,29 @@ export async function runCli(argv) {
|
|
|
788
793
|
throw new Error('Interactive TUI requires Bun. Run: bun ./bin/wiki-manager.js');
|
|
789
794
|
}
|
|
790
795
|
const { runOpenTuiShell, runStartupWizard } = await import('../shell/tui.tsx');
|
|
796
|
+
// Fresh directory → copy mcp.endpoints.json/.env from the packaged
|
|
797
|
+
// examples so external agents (cme, mailer, documents) connect out of
|
|
798
|
+
// the box. Done here (and in `runtime`), NOT at import time: --version
|
|
799
|
+
// in a random cwd must not litter files.
|
|
800
|
+
const scaffolded = ensureManagerScaffold({ log: (message) => console.log(`[wiki-manager] ${message}`) });
|
|
801
|
+
if (scaffolded.length > 0) loadManagerEnv();
|
|
791
802
|
const gaps = await runChecks();
|
|
792
803
|
if (gaps.length > 0) await runStartupWizard(gaps);
|
|
793
804
|
let runtime = null;
|
|
794
805
|
try {
|
|
795
806
|
const { ensureRuntime } = await import('../runtime/lifecycle.js');
|
|
796
|
-
|
|
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 });
|
|
797
810
|
} catch (err) {
|
|
798
811
|
runtime = unavailableRuntime(err);
|
|
799
812
|
console.error(`Runtime unavailable: ${runtime.error}`);
|
|
800
813
|
}
|
|
801
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
|
+
}
|
|
802
819
|
return;
|
|
803
820
|
}
|
|
804
821
|
|
|
@@ -813,4 +830,8 @@ export async function runCli(argv) {
|
|
|
813
830
|
}
|
|
814
831
|
}
|
|
815
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
|
+
}
|
|
816
837
|
}
|
package/src/core/buildInfo.json
CHANGED
package/src/core/env.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
1
|
+
import { copyFileSync, existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
3
4
|
|
|
4
5
|
export function userManagerDir() {
|
|
5
6
|
return process.cwd();
|
|
@@ -31,6 +32,34 @@ export function managerMcpEndpointsFile() {
|
|
|
31
32
|
return join(managerStateDir(), 'mcp.endpoints.json');
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
36
|
+
|
|
37
|
+
// First-run scaffolding: a fresh install directory has neither
|
|
38
|
+
// mcp.endpoints.json nor .env, so the external agents (cme, mailer,
|
|
39
|
+
// documents) silently never connect — /status shows no agents and Donna has
|
|
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. Optional
|
|
42
|
+
// credentials can be added later for the external services the user enables.
|
|
43
|
+
export function ensureManagerScaffold({ log = () => {} } = {}) {
|
|
44
|
+
const created = [];
|
|
45
|
+
const endpointsFile = managerMcpEndpointsFile();
|
|
46
|
+
const endpointsExample = join(packageRoot, 'mcp.endpoints.example.json');
|
|
47
|
+
if (!existsSync(endpointsFile) && existsSync(endpointsExample)) {
|
|
48
|
+
copyFileSync(endpointsExample, endpointsFile);
|
|
49
|
+
created.push('mcp.endpoints.json');
|
|
50
|
+
}
|
|
51
|
+
const envFile = managerEnvFile();
|
|
52
|
+
const envExample = join(packageRoot, '.env.example');
|
|
53
|
+
if (!existsSync(envFile) && existsSync(envExample)) {
|
|
54
|
+
copyFileSync(envExample, envFile);
|
|
55
|
+
created.push('.env');
|
|
56
|
+
}
|
|
57
|
+
if (created.length > 0) {
|
|
58
|
+
log(`configuration initialized successfully in ${managerStateDir()} — created ${created.join(' and ')} from packaged defaults. Optional credentials can be added later for external services.`);
|
|
59
|
+
}
|
|
60
|
+
return created;
|
|
61
|
+
}
|
|
62
|
+
|
|
34
63
|
// Single source of truth for where `.agents-data` lives, shared by the
|
|
35
64
|
// manager's own document intake and by the host path it mounts into agent
|
|
36
65
|
// containers — keeping them in sync avoids the two silently drifting apart.
|
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
|
+
});
|
package/wiki-workspace
CHANGED
|
@@ -228,6 +228,12 @@ agents_compose() {
|
|
|
228
228
|
shift
|
|
229
229
|
fi
|
|
230
230
|
[[ $# -eq 0 ]] || die "agents up accepts only --pull"
|
|
231
|
+
ensure_agent_tokens
|
|
232
|
+
ensure_endpoints_file
|
|
233
|
+
# The .env may have just been created above — recompute the env-file
|
|
234
|
+
# flag so freshly generated tokens reach the containers on first run.
|
|
235
|
+
compose_env_args=()
|
|
236
|
+
[[ -f "$MANAGER_ENV_FILE" ]] && compose_env_args+=(--env-file "$MANAGER_ENV_FILE")
|
|
231
237
|
mkdir -p "$agents_data_dir/cme" "$agents_data_dir/documents/input" "$agents_data_dir/documents/output" "$agents_data_dir/documents/uploads"
|
|
232
238
|
mkdir -p "$workspaces_root"
|
|
233
239
|
$do_pull && _agents_dc pull
|
|
@@ -382,6 +388,33 @@ ensure_runtime_node() {
|
|
|
382
388
|
[[ "$major" =~ ^[0-9]+$ && "$major" -ge 22 ]] || die "runtime requires Node.js 22+ for node:sqlite; $node_bin is Node $version"
|
|
383
389
|
}
|
|
384
390
|
|
|
391
|
+
ensure_agent_tokens() {
|
|
392
|
+
# The external agents (cme, documents, mailer) authenticate with tokens
|
|
393
|
+
# from the manager .env — generate any missing one, exactly like the
|
|
394
|
+
# workspace and runtime tokens are generated. Leaving them empty made a
|
|
395
|
+
# fresh install start agents nobody could talk to.
|
|
396
|
+
local key token
|
|
397
|
+
for key in CME_MCP_AUTH_TOKEN DOCUMENTS_MCP_AUTH_TOKEN MAILER_MCP_AUTH_TOKEN; do
|
|
398
|
+
token="$(env_value "$MANAGER_ENV_FILE" "$key" "")"
|
|
399
|
+
if [[ -z "$token" ]]; then
|
|
400
|
+
token="$(generate_token)"
|
|
401
|
+
mkdir -p "$(dirname "$MANAGER_ENV_FILE")"
|
|
402
|
+
set_env_value "$MANAGER_ENV_FILE" "$key" "$token"
|
|
403
|
+
printf 'Generated %s in %s\n' "$key" "$MANAGER_ENV_FILE"
|
|
404
|
+
fi
|
|
405
|
+
done
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
ensure_endpoints_file() {
|
|
409
|
+
# Without mcp.endpoints.json the shell never connects to the agents it
|
|
410
|
+
# just started (and `agents status` refuses to run). Seed it from the
|
|
411
|
+
# packaged example; the \${VAR} placeholders resolve against the .env.
|
|
412
|
+
if [[ ! -f "$MANAGER_ENDPOINTS_FILE" && -f "$ROOT_DIR/mcp.endpoints.example.json" ]]; then
|
|
413
|
+
cp "$ROOT_DIR/mcp.endpoints.example.json" "$MANAGER_ENDPOINTS_FILE"
|
|
414
|
+
printf 'Created %s from packaged example\n' "$MANAGER_ENDPOINTS_FILE"
|
|
415
|
+
fi
|
|
416
|
+
}
|
|
417
|
+
|
|
385
418
|
ensure_manager_runtime_token() {
|
|
386
419
|
local token
|
|
387
420
|
token="$(env_value "$MANAGER_ENV_FILE" WIKI_MANAGER_RUNTIME_TOKEN "")"
|