@dotdrelle/wiki-manager 0.12.8 → 0.12.10
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 +6 -12
- package/README.md +41 -9
- package/agents.docker-compose.mailer.example.yml +36 -0
- package/agents.docker-compose.yml +3 -28
- package/bin/wiki-manager +7 -0
- package/docker-compose.yml +0 -2
- package/mcp.endpoints.example.json +0 -6
- package/package.json +2 -1
- package/src/cli/wiki-manager.js +14 -1
- package/src/core/buildInfo.json +2 -2
- package/src/core/dockerCompose.test.js +3 -1
- package/src/core/env.js +12 -5
- 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/src/shell/tui.tsx +7 -4
- package/wiki-workspace +13 -6
package/.env.example
CHANGED
|
@@ -23,33 +23,28 @@ WORKSPACES_ROOT=/path/to/workspaces
|
|
|
23
23
|
#
|
|
24
24
|
# "cme": { "headers": { "Authorization": "Bearer <CME_MCP_AUTH_TOKEN>" } }
|
|
25
25
|
# "documents": { "headers": { "Authorization": "Bearer <DOCUMENTS_MCP_AUTH_TOKEN>" } }
|
|
26
|
-
# "mailer": { "headers": { "Authorization": "Bearer <MAILER_MCP_AUTH_TOKEN>" } }
|
|
27
26
|
#
|
|
28
27
|
# Leave empty to disable authentication on that agent (not recommended in production).
|
|
29
28
|
|
|
30
29
|
CME_MCP_AUTH_TOKEN=
|
|
31
30
|
DOCUMENTS_MCP_AUTH_TOKEN=
|
|
32
|
-
MAILER_MCP_AUTH_TOKEN=
|
|
33
31
|
|
|
34
32
|
# ── Mailer (MailerSend) ────────────────────────────────────────────────────────
|
|
35
33
|
|
|
36
|
-
MAILERSEND_API_KEY=
|
|
37
|
-
MAILERSEND_FROM_EMAIL=no-reply@example.com
|
|
38
|
-
MAILERSEND_FROM_NAME=Donna
|
|
39
34
|
|
|
40
35
|
# ── Documents LLM OCR / Mermaid (optional) ─────────────────────────────────────
|
|
41
36
|
|
|
42
37
|
# All image OCR is delegated to an OpenAI-compatible vision LLM. The default
|
|
43
|
-
#
|
|
44
|
-
# DOCUMENT_LLM_BASE_URL=https://api.
|
|
45
|
-
# DOCUMENT_LLM_MODEL=
|
|
38
|
+
# Default target is the Albert API (etalab); set DOCUMENT_LLM_API_KEY.
|
|
39
|
+
# DOCUMENT_LLM_BASE_URL=https://albert.api.etalab.gouv.fr/v1
|
|
40
|
+
# DOCUMENT_LLM_MODEL=LightOnOCR-2-1B
|
|
46
41
|
# DOCUMENT_LLM_API_KEY=
|
|
47
42
|
# DOCUMENT_LLM_TIMEOUT_SECONDS=120
|
|
48
43
|
|
|
49
44
|
# ── Optional external MCP providers ────────────────────────────────────────────
|
|
50
|
-
|
|
51
|
-
#
|
|
52
|
-
|
|
45
|
+
# Doctrine: every variable an external MCP endpoint needs (API key, token…)
|
|
46
|
+
# lives HERE and is referenced from mcp.endpoints.json as ${VAR_NAME}.
|
|
47
|
+
# Add your own entries when you declare additional endpoints.
|
|
53
48
|
|
|
54
49
|
# ── MCP retry policy (optional) ────────────────────────────────────────────────
|
|
55
50
|
|
|
@@ -75,4 +70,3 @@ EXA_MCP_API_KEY=
|
|
|
75
70
|
|
|
76
71
|
# CME_MCP_PORT=3336
|
|
77
72
|
# DOCUMENTS_MCP_PORT=3337
|
|
78
|
-
# MAILER_MCP_PORT=3335
|
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`).
|
|
@@ -117,7 +125,7 @@ cp .env.example .env
|
|
|
117
125
|
```
|
|
118
126
|
|
|
119
127
|
**3 — Start the shared agents.**
|
|
120
|
-
Start the common toolbox once (Confluence export `cme`, `documents
|
|
128
|
+
Start the common toolbox once (Confluence export `cme`, `documents`).
|
|
121
129
|
They run in the background and serve every workspace.
|
|
122
130
|
|
|
123
131
|
```bash
|
|
@@ -215,7 +223,7 @@ Start the common toolbox once and for all (Confluence, documents, e-mail,
|
|
|
215
223
|
production). These agents run in the background and serve **all your projects**:
|
|
216
224
|
|
|
217
225
|
```bash
|
|
218
|
-
wiki-workspace agents up # start cme, documents
|
|
226
|
+
wiki-workspace agents up # start cme, documents…
|
|
219
227
|
wiki-workspace agents status # check they respond
|
|
220
228
|
```
|
|
221
229
|
|
|
@@ -360,7 +368,7 @@ of you in the browser (create → configure → start the agents → open).
|
|
|
360
368
|
| [`agent-cme`](https://github.com/dotdrelle/agent-cme) | Global Confluence to Markdown MCP exporter; workspace injected automatically by Donna |
|
|
361
369
|
| [`agent-wiki-production`](https://github.com/dotdrelle/agent-wiki-production) | Workspace-scoped production jobs: ingest, build, export, polish, pipeline |
|
|
362
370
|
| [`agent-wiki-documents`](https://github.com/dotdrelle/agent-wiki-documents) | Document conversion MCP: PDF/Office/HTML/images → Markdown (OCR-capable) |
|
|
363
|
-
| [`agent-mailer-api`](https://github.com/dotdrelle/agent-mailer-api) | Optional external mailer MCP endpoint |
|
|
371
|
+
| [`agent-mailer-api`](https://github.com/dotdrelle/agent-mailer-api) | Optional external mailer MCP endpoint (user-side override, not in the default stack) |
|
|
364
372
|
|
|
365
373
|
## Workspace Model
|
|
366
374
|
|
|
@@ -406,7 +414,7 @@ keys** (`apiKey` + `baseUrl` that *reach a model*).
|
|
|
406
414
|
|
|
407
415
|
| File | Owner | Scope | Holds |
|
|
408
416
|
| --- | --- | --- | --- |
|
|
409
|
-
| `.env` | manager | global | shared secrets: agent MCP tokens,
|
|
417
|
+
| `.env` | manager | global | shared secrets: agent MCP tokens, OCR LLM, port overrides, variables for any user-declared external MCP |
|
|
410
418
|
| `mcp.endpoints.json` | manager | global | where each external agent lives + which `Bearer`/header to send |
|
|
411
419
|
| `workspaces/<name>/.env` | manager | per workspace | ports, workspace path, the wiki's own MCP tokens |
|
|
412
420
|
| `workspaces/<name>/.wikirc.yaml` (+ `.wikirc.yaml.<profile>`) | workspace | per workspace | LLM & vector keys (provider/model/apiKey/baseUrl/retrieval) |
|
|
@@ -454,7 +462,7 @@ cp .env.example .env
|
|
|
454
462
|
|
|
455
463
|
The `.env` file is loaded automatically by both `wiki-manager` (Node/Bun process)
|
|
456
464
|
and `wiki-workspace` (Docker Compose). It sets `WORKSPACES_ROOT`, per-agent auth
|
|
457
|
-
tokens
|
|
465
|
+
tokens and optional port overrides; credentials of optional connectors you enable via the user override.
|
|
458
466
|
|
|
459
467
|
### External MCP endpoints
|
|
460
468
|
|
|
@@ -530,16 +538,40 @@ than tracking its own state.
|
|
|
530
538
|
|
|
531
539
|
### Starting external agents
|
|
532
540
|
|
|
533
|
-
Start CME
|
|
541
|
+
Start CME and documents once for all workspaces:
|
|
534
542
|
|
|
535
543
|
```bash
|
|
536
544
|
wiki-workspace agents up
|
|
537
545
|
```
|
|
538
546
|
|
|
539
|
-
This uses the packaged `agents.docker-compose.yml
|
|
547
|
+
This uses the packaged `agents.docker-compose.yml` (it lives inside the npm
|
|
548
|
+
package — never edit it, updates overwrite it). On first run, `agents up`
|
|
549
|
+
generates the missing agent auth tokens into your manager `.env` and seeds
|
|
550
|
+
`mcp.endpoints.json` from the packaged example. `WORKSPACES_ROOT` is resolved
|
|
540
551
|
automatically from the manager workspaces directory. Agent state is stored under
|
|
541
552
|
`./.agents-data/` unless `AGENTS_DATA_DIR` is set.
|
|
542
553
|
|
|
554
|
+
#### Optional agents and user overrides
|
|
555
|
+
|
|
556
|
+
Anything beyond the default stack (for example the MailerSend agent) is an
|
|
557
|
+
external connector operated at the user's charge — built and published, but
|
|
558
|
+
not part of the delivery. To enable one, create a file named
|
|
559
|
+
`agents.docker-compose.override.yml` **next to your `.env`**:
|
|
560
|
+
|
|
561
|
+
```bash
|
|
562
|
+
cp "$(npm root -g)/@dotdrelle/wiki-manager/agents.docker-compose.mailer.example.yml" \
|
|
563
|
+
agents.docker-compose.override.yml
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
`agents up` includes it automatically when present (standard Docker Compose
|
|
567
|
+
merge: new services are added, same-name keys override the defaults — you can
|
|
568
|
+
also use it to pin a port or a variable of a default agent). The file is
|
|
569
|
+
yours: wiki-manager never generates or overwrites it. Complete the setup by
|
|
570
|
+
adding the connector's variables to your `.env` and its endpoint block to
|
|
571
|
+
your `mcp.endpoints.json` — every variable an external MCP endpoint needs
|
|
572
|
+
lives in the `.env` and is referenced as `${VAR_NAME}` from
|
|
573
|
+
`mcp.endpoints.json`. Detailed steps are in the example file header.
|
|
574
|
+
|
|
543
575
|
Workspace-native MCP servers (`llm-wiki`, `production`) stay configured through
|
|
544
576
|
each workspace `.env`. External agents are workspace-agnostic: the active
|
|
545
577
|
`/use <workspace>` is injected automatically on every CME and documents tool
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# agents.docker-compose.mailer.example.yml — optional MailerSend agent
|
|
2
|
+
#
|
|
3
|
+
# The mailer agent is NOT part of the default delivery: it is an external
|
|
4
|
+
# MCP connector operated at the user's charge. To enable it:
|
|
5
|
+
#
|
|
6
|
+
# 1. Copy this file NEXT TO YOUR .env (the directory where you run
|
|
7
|
+
# wiki-manager) under the name: agents.docker-compose.override.yml
|
|
8
|
+
# That file is yours — wiki-manager never generates or overwrites it,
|
|
9
|
+
# and `wiki-workspace agents up` includes it automatically when present.
|
|
10
|
+
# 2. Add to your .env:
|
|
11
|
+
# MAILER_MCP_AUTH_TOKEN=$(openssl rand -hex 32)
|
|
12
|
+
# MAILERSEND_API_KEY=...
|
|
13
|
+
# MAILERSEND_FROM_EMAIL=...
|
|
14
|
+
# 3. Declare the endpoint in your mcp.endpoints.json:
|
|
15
|
+
# "mailer": {
|
|
16
|
+
# "url": "http://host.docker.internal:${MAILER_MCP_PORT:-3335}/mcp/",
|
|
17
|
+
# "headers": { "Authorization": "Bearer ${MAILER_MCP_AUTH_TOKEN}" }
|
|
18
|
+
# }
|
|
19
|
+
#
|
|
20
|
+
services:
|
|
21
|
+
mailer:
|
|
22
|
+
image: dotdrelle/agent-mailer-api:latest
|
|
23
|
+
user: "${UID:-1000}:${GID:-1000}"
|
|
24
|
+
ports:
|
|
25
|
+
- "${MAILER_MCP_PORT:-3335}:8080"
|
|
26
|
+
environment:
|
|
27
|
+
- MAILERSEND_API_KEY=${MAILERSEND_API_KEY:-}
|
|
28
|
+
- MAILERSEND_FROM_EMAIL=${MAILERSEND_FROM_EMAIL:-no-reply@example.com}
|
|
29
|
+
- MAILERSEND_FROM_NAME=${MAILERSEND_FROM_NAME:-Mailer Agent}
|
|
30
|
+
- MAILERSEND_USER_AGENT=${MAILERSEND_USER_AGENT:-curl/8.7.1}
|
|
31
|
+
- MAILERSEND_VERIFY_SSL=${MAILERSEND_VERIFY_SSL:-true}
|
|
32
|
+
- MAILERSEND_CA_CERT=${MAILERSEND_CA_CERT:-}
|
|
33
|
+
- MAILER_REQUIRE_CONFIRMATION=${MAILER_REQUIRE_CONFIRMATION:-true}
|
|
34
|
+
- MAILER_DRY_RUN=${MAILER_DRY_RUN:-false}
|
|
35
|
+
- MCP_AUTH_TOKEN=${MAILER_MCP_AUTH_TOKEN:-}
|
|
36
|
+
restart: unless-stopped
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# agents.docker-compose.yml — external agent stack
|
|
2
2
|
#
|
|
3
|
-
# Starts cme
|
|
3
|
+
# Starts cme and documents as workspace-agnostic global services.
|
|
4
4
|
# All workspace paths are resolved at tool-call time via the `workspace` param.
|
|
5
5
|
#
|
|
6
6
|
# Required:
|
|
@@ -10,18 +10,12 @@
|
|
|
10
10
|
# AGENTS_DATA_DIR — persistent state for stateful agents (default: ./.agents-data)
|
|
11
11
|
# CME_MCP_PORT — 3336
|
|
12
12
|
# DOCUMENTS_MCP_PORT — 3337
|
|
13
|
-
# MAILER_MCP_PORT — 3335
|
|
14
13
|
# CME_MCP_AUTH_TOKEN — bearer token for cme agent (empty = no auth)
|
|
15
14
|
# DOCUMENTS_MCP_AUTH_TOKEN — bearer token for documents agent (empty = no auth)
|
|
16
|
-
# DOCUMENT_LLM_BASE_URL — https://api.
|
|
17
|
-
# DOCUMENT_LLM_MODEL —
|
|
15
|
+
# DOCUMENT_LLM_BASE_URL — https://albert.api.etalab.gouv.fr/v1
|
|
16
|
+
# DOCUMENT_LLM_MODEL — LightOnOCR-2-1B
|
|
18
17
|
# DOCUMENT_LLM_API_KEY — OpenAI/OpenAI-compatible API key for document OCR
|
|
19
18
|
# DOCUMENT_LLM_TIMEOUT_SECONDS — 120
|
|
20
|
-
# MAILER_MCP_AUTH_TOKEN — bearer token for mailer agent (empty = no auth)
|
|
21
|
-
# MAILERSEND_API_KEY
|
|
22
|
-
# MAILERSEND_FROM_EMAIL
|
|
23
|
-
# MAILERSEND_FROM_NAME
|
|
24
|
-
# MAILERSEND_CA_CERT — optional CA bundle path inside the container
|
|
25
19
|
#
|
|
26
20
|
# Set these variables in a .env file at the directory where you run wiki-workspace,
|
|
27
21
|
# or export them in your shell before running wiki-workspace agents up.
|
|
@@ -83,22 +77,3 @@ services:
|
|
|
83
77
|
#- ${AGENTS_DATA_DIR:-./.agents-data}/certs:/certs:ro
|
|
84
78
|
restart: unless-stopped
|
|
85
79
|
|
|
86
|
-
mailer:
|
|
87
|
-
image: dotdrelle/agent-mailer-api:latest
|
|
88
|
-
user: "${UID:-1000}:${GID:-1000}"
|
|
89
|
-
ports:
|
|
90
|
-
- "${MAILER_MCP_PORT:-3335}:8080"
|
|
91
|
-
environment:
|
|
92
|
-
- MAILERSEND_API_KEY=${MAILERSEND_API_KEY:-}
|
|
93
|
-
- MAILERSEND_FROM_EMAIL=${MAILERSEND_FROM_EMAIL:-no-reply@example.com}
|
|
94
|
-
- MAILERSEND_FROM_NAME=${MAILERSEND_FROM_NAME:-Mailer Agent}
|
|
95
|
-
- MAILERSEND_USER_AGENT=${MAILERSEND_USER_AGENT:-curl/8.7.1}
|
|
96
|
-
- MAILERSEND_VERIFY_SSL=${MAILERSEND_VERIFY_SSL:-true}
|
|
97
|
-
- MAILERSEND_CA_CERT=${MAILERSEND_CA_CERT:-}
|
|
98
|
-
- MAILER_REQUIRE_CONFIRMATION=${MAILER_REQUIRE_CONFIRMATION:-true}
|
|
99
|
-
- MAILER_DRY_RUN=${MAILER_DRY_RUN:-false}
|
|
100
|
-
- MCP_AUTH_TOKEN=${MAILER_MCP_AUTH_TOKEN:-}
|
|
101
|
-
# Optional: mount a CA bundle and set MAILERSEND_CA_CERT to its container path.
|
|
102
|
-
# volumes:
|
|
103
|
-
# - ${AGENTS_DATA_DIR:-./.agents-data}/certs:/certs:ro
|
|
104
|
-
restart: unless-stopped
|
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/docker-compose.yml
CHANGED
|
@@ -51,10 +51,8 @@ services:
|
|
|
51
51
|
- PRODUCTION_MCP_PORT
|
|
52
52
|
- CME_MCP_PORT
|
|
53
53
|
- DOCUMENTS_MCP_PORT
|
|
54
|
-
- MAILER_MCP_PORT
|
|
55
54
|
- CME_MCP_AUTH_TOKEN
|
|
56
55
|
- DOCUMENTS_MCP_AUTH_TOKEN
|
|
57
|
-
- MAILER_MCP_AUTH_TOKEN
|
|
58
56
|
- WORKSPACE_NAME
|
|
59
57
|
- DOCUMENT_INPUT_DIR=/documents/input
|
|
60
58
|
- DOCUMENT_UPLOADS_DIR=/documents/uploads
|
|
@@ -24,12 +24,6 @@
|
|
|
24
24
|
"headers": {
|
|
25
25
|
"Authorization": "Bearer ${DOCUMENTS_MCP_AUTH_TOKEN}"
|
|
26
26
|
}
|
|
27
|
-
},
|
|
28
|
-
"mailer": {
|
|
29
|
-
"url": "http://host.docker.internal:${MAILER_MCP_PORT:-3335}/mcp/",
|
|
30
|
-
"headers": {
|
|
31
|
-
"Authorization": "Bearer ${MAILER_MCP_AUTH_TOKEN}"
|
|
32
|
-
}
|
|
33
27
|
}
|
|
34
28
|
}
|
|
35
29
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotdrelle/wiki-manager",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.10",
|
|
4
4
|
"description": "Agentic shell and orchestration cockpit for llm-wiki workspaces.",
|
|
5
5
|
"license": "PolyForm-Noncommercial-1.0.0",
|
|
6
6
|
"author": "dotrelle",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"wiki-workspace",
|
|
28
28
|
"docker-compose.yml",
|
|
29
29
|
"agents.docker-compose.yml",
|
|
30
|
+
"agents.docker-compose.mailer.example.yml",
|
|
30
31
|
"mcp.endpoints.example.json",
|
|
31
32
|
".env.example",
|
|
32
33
|
"tsconfig.json",
|
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
|
@@ -22,5 +22,7 @@ test('agent compose services run as the host uid and gid', async () => {
|
|
|
22
22
|
const agentsCompose = YAML.parse(agentsRaw);
|
|
23
23
|
assert.equal(agentsCompose.services.cme.user, '${UID:-1000}:${GID:-1000}');
|
|
24
24
|
assert.equal(agentsCompose.services.documents.user, '${UID:-1000}:${GID:-1000}');
|
|
25
|
-
|
|
25
|
+
// MailerSend was an external-connector experiment — removed from the
|
|
26
|
+
// default stack (the agent repo still exists for ad-hoc use).
|
|
27
|
+
assert.equal(agentsCompose.services.mailer, undefined);
|
|
26
28
|
});
|
package/src/core/env.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { copyFileSync, existsSync, readFileSync } from 'node:fs';
|
|
1
|
+
import { copyFileSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
|
|
@@ -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();
|
|
@@ -51,11 +51,18 @@ export function ensureManagerScaffold({ log = () => {} } = {}) {
|
|
|
51
51
|
const envFile = managerEnvFile();
|
|
52
52
|
const envExample = join(packageRoot, '.env.example');
|
|
53
53
|
if (!existsSync(envFile) && existsSync(envExample)) {
|
|
54
|
-
|
|
54
|
+
// Substitute the documentation placeholders with real paths: a copied
|
|
55
|
+
// WORKSPACES_ROOT=/path/to/workspaces silently broke agents compose
|
|
56
|
+
// mounts until manually edited.
|
|
57
|
+
const workspacesRoot = join(managerStateDir(), 'workspaces');
|
|
58
|
+
const content = readFileSync(envExample, 'utf8')
|
|
59
|
+
.replace(/^WORKSPACES_ROOT=.*$/m, `WORKSPACES_ROOT=${workspacesRoot}`)
|
|
60
|
+
.replace(/^# WIKI_WORKSPACES_DIR=.*$/m, `WIKI_WORKSPACES_DIR=${workspacesRoot}`);
|
|
61
|
+
writeFileSync(envFile, content);
|
|
55
62
|
created.push('.env');
|
|
56
63
|
}
|
|
57
64
|
if (created.length > 0) {
|
|
58
|
-
log(`
|
|
65
|
+
log(`configuration initialized successfully in ${managerStateDir()} — created ${created.join(' and ')} from packaged defaults. Optional credentials can be added later for external services.`);
|
|
59
66
|
}
|
|
60
67
|
return created;
|
|
61
68
|
}
|
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.10';
|
|
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/src/shell/tui.tsx
CHANGED
|
@@ -393,10 +393,13 @@ export async function runOpenTuiShell({
|
|
|
393
393
|
packageJson: Record<string, unknown>;
|
|
394
394
|
runtime?: any;
|
|
395
395
|
}) {
|
|
396
|
-
await
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
396
|
+
await new Promise<void>((resolve, reject) => {
|
|
397
|
+
render(() => <App agent={agent} packageJson={packageJson} runtime={runtime} />, {
|
|
398
|
+
exitOnCtrlC: false,
|
|
399
|
+
useMouse: true,
|
|
400
|
+
targetFps: 30,
|
|
401
|
+
onDestroy: resolve,
|
|
402
|
+
}).catch(reject);
|
|
400
403
|
});
|
|
401
404
|
return {};
|
|
402
405
|
}
|
package/wiki-workspace
CHANGED
|
@@ -86,7 +86,7 @@ Commands:
|
|
|
86
86
|
up <workspace> [--open] Start workspace stack: wiki + production
|
|
87
87
|
list List configured workspaces
|
|
88
88
|
|
|
89
|
-
agents up [--pull] Start all external agents (cme, documents
|
|
89
|
+
agents up [--pull] Start all external agents (cme, documents)
|
|
90
90
|
agents down Stop all external agents
|
|
91
91
|
agents logs [args...] Follow external agent logs
|
|
92
92
|
agents pull Pull latest external agent images
|
|
@@ -215,9 +215,15 @@ agents_compose() {
|
|
|
215
215
|
local cacert_args=()
|
|
216
216
|
WORKSPACES_ROOT="$workspaces_root" AGENTS_DATA_DIR="$agents_data_dir" \
|
|
217
217
|
read_lines_into_array cacert_args cacert_compose_args "$agents_compose_file" "agents.cacert.compose.yml"
|
|
218
|
+
# User-owned override: optional external agents (e.g. mailer) live in a
|
|
219
|
+
# file the user maintains NEXT TO the .env — never generated, never
|
|
220
|
+
# overwritten by package updates.
|
|
221
|
+
local override_args=()
|
|
222
|
+
local user_override="$MANAGER_STATE_DIR/agents.docker-compose.override.yml"
|
|
223
|
+
[[ -f "$user_override" ]] && override_args+=(-f "$user_override")
|
|
218
224
|
WORKSPACES_ROOT="$workspaces_root" AGENTS_DATA_DIR="$agents_data_dir" \
|
|
219
225
|
docker compose --project-directory "$DEFAULT_MANAGER_DIR" \
|
|
220
|
-
${compose_env_args[@]+"${compose_env_args[@]}"} -f "$agents_compose_file" ${cacert_args[@]+"${cacert_args[@]}"} -p wiki-agents "$@"
|
|
226
|
+
${compose_env_args[@]+"${compose_env_args[@]}"} -f "$agents_compose_file" ${override_args[@]+"${override_args[@]}"} ${cacert_args[@]+"${cacert_args[@]}"} -p wiki-agents "$@"
|
|
221
227
|
}
|
|
222
228
|
|
|
223
229
|
case "$subcommand" in
|
|
@@ -245,8 +251,9 @@ agents_compose() {
|
|
|
245
251
|
printf 'Workspaces root: %s\n' "$workspaces_root"
|
|
246
252
|
printf 'Agents data: %s\n' "$agents_data_dir"
|
|
247
253
|
[[ -z "$CACERT_PATH" ]] || printf 'CA cert: %s -> /wiki-manager-ca.pem\n' "$CACERT_PATH"
|
|
248
|
-
printf 'Agents: cme=:%s documents=:%s
|
|
249
|
-
"${CME_MCP_PORT:-3336}" "${DOCUMENTS_MCP_PORT:-3337}"
|
|
254
|
+
printf 'Agents: cme=:%s documents=:%s\n' \
|
|
255
|
+
"${CME_MCP_PORT:-3336}" "${DOCUMENTS_MCP_PORT:-3337}"
|
|
256
|
+
[[ ! -f "$MANAGER_STATE_DIR/agents.docker-compose.override.yml" ]] || printf 'User override: agents.docker-compose.override.yml included\n'
|
|
250
257
|
;;
|
|
251
258
|
down)
|
|
252
259
|
[[ $# -eq 0 ]] || die "agents down does not take arguments"
|
|
@@ -389,12 +396,12 @@ ensure_runtime_node() {
|
|
|
389
396
|
}
|
|
390
397
|
|
|
391
398
|
ensure_agent_tokens() {
|
|
392
|
-
# The external agents (cme, documents
|
|
399
|
+
# The external agents (cme, documents) authenticate with tokens
|
|
393
400
|
# from the manager .env — generate any missing one, exactly like the
|
|
394
401
|
# workspace and runtime tokens are generated. Leaving them empty made a
|
|
395
402
|
# fresh install start agents nobody could talk to.
|
|
396
403
|
local key token
|
|
397
|
-
for key in CME_MCP_AUTH_TOKEN DOCUMENTS_MCP_AUTH_TOKEN
|
|
404
|
+
for key in CME_MCP_AUTH_TOKEN DOCUMENTS_MCP_AUTH_TOKEN; do
|
|
398
405
|
token="$(env_value "$MANAGER_ENV_FILE" "$key" "")"
|
|
399
406
|
if [[ -z "$token" ]]; then
|
|
400
407
|
token="$(generate_token)"
|