@dotdrelle/wiki-manager 0.12.9 → 0.12.11
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 +9 -18
- package/README.md +34 -10
- package/agents.docker-compose.mailer.example.yml +36 -0
- package/agents.docker-compose.yml +3 -28
- package/docker-compose.yml +0 -2
- package/mcp.endpoints.example.json +0 -6
- package/package.json +2 -1
- package/src/agent/graph.js +40 -0
- package/src/agent/graph.test.js +55 -0
- package/src/commands/slash.js +1 -7
- package/src/core/agentLoop.js +6 -0
- package/src/core/agentLoop.test.js +50 -2
- package/src/core/buildInfo.json +2 -2
- package/src/core/dockerCompose.test.js +3 -1
- package/src/core/env.js +9 -2
- package/src/core/mcp.js +1 -1
- package/src/core/plan.js +11 -4
- package/src/runtime/runner.js +9 -0
- package/src/shell/LeftPane.tsx +4 -1
- package/src/shell/tui.tsx +7 -4
- package/wiki-workspace +13 -6
package/.env.example
CHANGED
|
@@ -23,33 +23,30 @@ 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
|
+
# ── Agent ports (optional, change only if defaults conflict) ───────────────────
|
|
35
33
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
MAILERSEND_FROM_NAME=Donna
|
|
34
|
+
# CME_MCP_PORT=3336
|
|
35
|
+
# DOCUMENTS_MCP_PORT=3337
|
|
39
36
|
|
|
40
37
|
# ── Documents LLM OCR / Mermaid (optional) ─────────────────────────────────────
|
|
41
38
|
|
|
42
39
|
# 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=
|
|
40
|
+
# Default target is the Albert API (etalab); set DOCUMENT_LLM_API_KEY.
|
|
41
|
+
# DOCUMENT_LLM_BASE_URL=https://albert.api.etalab.gouv.fr/v1
|
|
42
|
+
# DOCUMENT_LLM_MODEL=LightOnOCR-2-1B
|
|
46
43
|
# DOCUMENT_LLM_API_KEY=
|
|
47
44
|
# DOCUMENT_LLM_TIMEOUT_SECONDS=120
|
|
48
45
|
|
|
49
46
|
# ── Optional external MCP providers ────────────────────────────────────────────
|
|
50
|
-
|
|
51
|
-
#
|
|
52
|
-
|
|
47
|
+
# Doctrine: every variable an external MCP endpoint needs (API key, token…)
|
|
48
|
+
# lives HERE and is referenced from mcp.endpoints.json as ${VAR_NAME}.
|
|
49
|
+
# Add your own entries when you declare additional endpoints.
|
|
53
50
|
|
|
54
51
|
# ── MCP retry policy (optional) ────────────────────────────────────────────────
|
|
55
52
|
|
|
@@ -70,9 +67,3 @@ EXA_MCP_API_KEY=
|
|
|
70
67
|
# Runtime approvals can pause runs or protected tools until /approve is called.
|
|
71
68
|
# WIKI_MANAGER_APPROVAL_TIMEOUT_MS=600000
|
|
72
69
|
# WIKI_MANAGER_REQUIRE_APPROVAL_TOOLS=production.production_start_job
|
|
73
|
-
|
|
74
|
-
# ── Agent ports (optional, change only if defaults conflict) ───────────────────
|
|
75
|
-
|
|
76
|
-
# CME_MCP_PORT=3336
|
|
77
|
-
# DOCUMENTS_MCP_PORT=3337
|
|
78
|
-
# MAILER_MCP_PORT=3335
|
package/README.md
CHANGED
|
@@ -48,7 +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
|
+
npm approve-scripts bun@1.3.14 # only when npm reports that Bun's postinstall is pending
|
|
52
52
|
npx wiki-manager
|
|
53
53
|
npx wiki-workspace --help
|
|
54
54
|
```
|
|
@@ -57,8 +57,8 @@ npx wiki-workspace --help
|
|
|
57
57
|
global `PATH`. Run local executables with `npx` (or `npm exec wiki-manager` and
|
|
58
58
|
`npm exec wiki-workspace`). Bun is installed automatically as a package runtime;
|
|
59
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
|
|
61
|
-
first launch.
|
|
60
|
+
the explicit `npm approve-scripts bun@1.3.14` security approval shown above
|
|
61
|
+
before the first launch.
|
|
62
62
|
|
|
63
63
|
### Global installation
|
|
64
64
|
|
|
@@ -125,7 +125,7 @@ cp .env.example .env
|
|
|
125
125
|
```
|
|
126
126
|
|
|
127
127
|
**3 — Start the shared agents.**
|
|
128
|
-
Start the common toolbox once (Confluence export `cme`, `documents
|
|
128
|
+
Start the common toolbox once (Confluence export `cme`, `documents`).
|
|
129
129
|
They run in the background and serve every workspace.
|
|
130
130
|
|
|
131
131
|
```bash
|
|
@@ -223,7 +223,7 @@ Start the common toolbox once and for all (Confluence, documents, e-mail,
|
|
|
223
223
|
production). These agents run in the background and serve **all your projects**:
|
|
224
224
|
|
|
225
225
|
```bash
|
|
226
|
-
wiki-workspace agents up # start cme, documents
|
|
226
|
+
wiki-workspace agents up # start cme, documents…
|
|
227
227
|
wiki-workspace agents status # check they respond
|
|
228
228
|
```
|
|
229
229
|
|
|
@@ -368,7 +368,7 @@ of you in the browser (create → configure → start the agents → open).
|
|
|
368
368
|
| [`agent-cme`](https://github.com/dotdrelle/agent-cme) | Global Confluence to Markdown MCP exporter; workspace injected automatically by Donna |
|
|
369
369
|
| [`agent-wiki-production`](https://github.com/dotdrelle/agent-wiki-production) | Workspace-scoped production jobs: ingest, build, export, polish, pipeline |
|
|
370
370
|
| [`agent-wiki-documents`](https://github.com/dotdrelle/agent-wiki-documents) | Document conversion MCP: PDF/Office/HTML/images → Markdown (OCR-capable) |
|
|
371
|
-
| [`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) |
|
|
372
372
|
|
|
373
373
|
## Workspace Model
|
|
374
374
|
|
|
@@ -414,7 +414,7 @@ keys** (`apiKey` + `baseUrl` that *reach a model*).
|
|
|
414
414
|
|
|
415
415
|
| File | Owner | Scope | Holds |
|
|
416
416
|
| --- | --- | --- | --- |
|
|
417
|
-
| `.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 |
|
|
418
418
|
| `mcp.endpoints.json` | manager | global | where each external agent lives + which `Bearer`/header to send |
|
|
419
419
|
| `workspaces/<name>/.env` | manager | per workspace | ports, workspace path, the wiki's own MCP tokens |
|
|
420
420
|
| `workspaces/<name>/.wikirc.yaml` (+ `.wikirc.yaml.<profile>`) | workspace | per workspace | LLM & vector keys (provider/model/apiKey/baseUrl/retrieval) |
|
|
@@ -462,7 +462,7 @@ cp .env.example .env
|
|
|
462
462
|
|
|
463
463
|
The `.env` file is loaded automatically by both `wiki-manager` (Node/Bun process)
|
|
464
464
|
and `wiki-workspace` (Docker Compose). It sets `WORKSPACES_ROOT`, per-agent auth
|
|
465
|
-
tokens
|
|
465
|
+
tokens and optional port overrides; credentials of optional connectors you enable via the user override.
|
|
466
466
|
|
|
467
467
|
### External MCP endpoints
|
|
468
468
|
|
|
@@ -538,16 +538,40 @@ than tracking its own state.
|
|
|
538
538
|
|
|
539
539
|
### Starting external agents
|
|
540
540
|
|
|
541
|
-
Start CME
|
|
541
|
+
Start CME and documents once for all workspaces:
|
|
542
542
|
|
|
543
543
|
```bash
|
|
544
544
|
wiki-workspace agents up
|
|
545
545
|
```
|
|
546
546
|
|
|
547
|
-
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
|
|
548
551
|
automatically from the manager workspaces directory. Agent state is stored under
|
|
549
552
|
`./.agents-data/` unless `AGENTS_DATA_DIR` is set.
|
|
550
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
|
+
|
|
551
575
|
Workspace-native MCP servers (`llm-wiki`, `production`) stay configured through
|
|
552
576
|
each workspace `.env`. External agents are workspace-agnostic: the active
|
|
553
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/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.11",
|
|
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/agent/graph.js
CHANGED
|
@@ -250,6 +250,7 @@ const AgentState = Annotation.Root({
|
|
|
250
250
|
readyToStream: Annotation(),
|
|
251
251
|
streamContext: Annotation(),
|
|
252
252
|
streamedInline: Annotation(),
|
|
253
|
+
retryWithoutTool: Annotation({ default: () => false }),
|
|
253
254
|
});
|
|
254
255
|
|
|
255
256
|
function commandList(session) {
|
|
@@ -688,6 +689,9 @@ export function buildAgentSystemPrompt(state) {
|
|
|
688
689
|
skills,
|
|
689
690
|
'You can call MCP tools directly using the provided tool functions.',
|
|
690
691
|
'When the user asks for an action that can be performed with connected MCP tools or safe primitives, do not answer with future intent such as "I will call...", "I am going to run...", or "launching..." unless you also call the tool in the same turn. Either call the tool now, ask for the exact missing required arguments, or explain the concrete blocker.',
|
|
692
|
+
'Execution truthfulness: never invent a job id, status, percentage, duration, generated file, file content, URL, command, or tool result. An action is executed only when you call an available tool and receive its result. Examples and placeholders are forbidden in execution reports.',
|
|
693
|
+
'After any completed action, give a short factual summary based only on the tool result: outcome and concrete outputs or references actually returned. Mention a viewing primitive only when it exists in Available primitives and is relevant. Do not interpret generated content, propose verification checklists, invent next steps, or suggest commands unless the user explicitly asks.',
|
|
694
|
+
'When calling a tool, emit no preliminary narration. Call it directly; the PLAN and Activity panels show progress. After completion, keep the final response concise and proportional to the result.',
|
|
691
695
|
'For connector configuration/setup/update requests, if a matching setup/configuration tool is connected and the required arguments are known, call it immediately. If the connector or tool is not connected, say which concrete capability is missing and recommend the exact service/status primitive to inspect it. Do not invent a pending connector action in plain text.',
|
|
692
696
|
'For workspace-scoped external MCP tools, the orchestrator enforces workspace injection. Use the active workspace for configuration, source, import, export, conversion, and generation tools unless a tool is explicitly job-scoped and only requires a job id.',
|
|
693
697
|
'You can call shell__run_command for safe manager slash commands such as /workspace list, /workspace init <name> [path], /use <workspace>, /config, /status, /services, /skills, /skills show <name>, and /skills run <name>.',
|
|
@@ -892,12 +896,14 @@ export function createAgentGraph(options = {}) {
|
|
|
892
896
|
|
|
893
897
|
try {
|
|
894
898
|
const useStreamWithTools = typeof llm.streamWithTools === 'function';
|
|
899
|
+
const suppressExecutionNarration = runtimeExecution && (iterations === 0 || state.retryWithoutTool);
|
|
895
900
|
const result = useStreamWithTools
|
|
896
901
|
? await llm.streamWithTools({
|
|
897
902
|
system,
|
|
898
903
|
tools,
|
|
899
904
|
messages: conversationMessages,
|
|
900
905
|
onTextDelta: (delta) => {
|
|
906
|
+
if (suppressExecutionNarration) return;
|
|
901
907
|
emitAgentEvent(state.session, 'assistant_delta', 'llm', { delta });
|
|
902
908
|
state.session._onStream?.(delta);
|
|
903
909
|
},
|
|
@@ -930,6 +936,39 @@ export function createAgentGraph(options = {}) {
|
|
|
930
936
|
toolIterations: iterations + 1,
|
|
931
937
|
readyToStream: false,
|
|
932
938
|
inputClassification: classification,
|
|
939
|
+
retryWithoutTool: false,
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
if (runtimeExecution && iterations === 0 && !state.retryWithoutTool) {
|
|
944
|
+
state.session._onStreamReset?.();
|
|
945
|
+
state.session._onStep?.('Agent: action response rejected — no tool was called; retrying…');
|
|
946
|
+
return {
|
|
947
|
+
pendingToolCalls: null,
|
|
948
|
+
messages: [
|
|
949
|
+
{ role: 'user', content: state.input },
|
|
950
|
+
result.message ?? { role: 'assistant', content: result.content ?? '' },
|
|
951
|
+
{
|
|
952
|
+
role: 'user',
|
|
953
|
+
content: 'Your previous response did not execute the requested action because it called no tool. Do not narrate or simulate execution. Call the appropriate available tool now. Never invent results.',
|
|
954
|
+
},
|
|
955
|
+
],
|
|
956
|
+
toolIterations: 1,
|
|
957
|
+
readyToStream: false,
|
|
958
|
+
inputClassification: classification,
|
|
959
|
+
retryWithoutTool: true,
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
if (runtimeExecution && state.retryWithoutTool) {
|
|
964
|
+
state.session._onStreamReset?.();
|
|
965
|
+
const failure = 'Action non exécutée : Donna n’a appelé aucun outil disponible. Aucun job ni résultat n’a été créé.';
|
|
966
|
+
emitAgentEvent(state.session, 'assistant_message', 'agent_guard', { content: failure });
|
|
967
|
+
return {
|
|
968
|
+
response: failure,
|
|
969
|
+
pendingToolCalls: null,
|
|
970
|
+
readyToStream: false,
|
|
971
|
+
retryWithoutTool: false,
|
|
933
972
|
};
|
|
934
973
|
}
|
|
935
974
|
|
|
@@ -1163,6 +1202,7 @@ export function createAgentGraph(options = {}) {
|
|
|
1163
1202
|
|
|
1164
1203
|
function routeOrchestrator(state) {
|
|
1165
1204
|
if (state.pendingToolCalls?.length > 0) return 'tool_executor';
|
|
1205
|
+
if (state.retryWithoutTool) return 'orchestrator';
|
|
1166
1206
|
return END;
|
|
1167
1207
|
}
|
|
1168
1208
|
|
package/src/agent/graph.test.js
CHANGED
|
@@ -686,6 +686,61 @@ test('agent graph survives more than 12 tool iterations (recursion limit)', asyn
|
|
|
686
686
|
}
|
|
687
687
|
});
|
|
688
688
|
|
|
689
|
+
test('runtime action retries a text-only hallucination and requires a real tool call', async () => {
|
|
690
|
+
const originalFetch = globalThis.fetch;
|
|
691
|
+
globalThis.fetch = async () => ({
|
|
692
|
+
ok: true,
|
|
693
|
+
status: 200,
|
|
694
|
+
headers: { get: () => null },
|
|
695
|
+
text: async () => JSON.stringify({ result: { content: [{ type: 'text', text: '{"ok":true,"outputs":["deliverables/result.md"]}' }] } }),
|
|
696
|
+
});
|
|
697
|
+
let calls = 0;
|
|
698
|
+
let retryMessages = [];
|
|
699
|
+
const session = sessionBase({
|
|
700
|
+
_currentRunIdentity: { runId: 'run-build', turnId: 'run-build:turn-1', workspace: 'docs' },
|
|
701
|
+
llm: {
|
|
702
|
+
async completeWithTools({ messages }) {
|
|
703
|
+
calls += 1;
|
|
704
|
+
if (calls === 1) {
|
|
705
|
+
return {
|
|
706
|
+
content: 'Build terminé, faux-job-123, rapport.pdf.',
|
|
707
|
+
message: { role: 'assistant', content: 'Build terminé, faux-job-123, rapport.pdf.' },
|
|
708
|
+
tool_calls: null,
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
if (calls === 2) {
|
|
712
|
+
retryMessages = messages;
|
|
713
|
+
return {
|
|
714
|
+
content: null,
|
|
715
|
+
message: { role: 'assistant', content: null },
|
|
716
|
+
tool_calls: [{
|
|
717
|
+
id: 'build-call',
|
|
718
|
+
type: 'function',
|
|
719
|
+
function: { name: 'production__production_start_job', arguments: '{"type":"build"}' },
|
|
720
|
+
}],
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
return {
|
|
724
|
+
content: 'Build terminé. Résultat : deliverables/result.md. Disponible dans /openui.',
|
|
725
|
+
message: { role: 'assistant', content: 'Build terminé. Résultat : deliverables/result.md. Disponible dans /openui.' },
|
|
726
|
+
tool_calls: null,
|
|
727
|
+
};
|
|
728
|
+
},
|
|
729
|
+
},
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
try {
|
|
733
|
+
const result = await createAgentGraph().invoke({ input: 'lance le build', session });
|
|
734
|
+
assert.equal(calls, 3);
|
|
735
|
+
assert.match(retryMessages.at(-1).content, /called no tool/);
|
|
736
|
+
assert.doesNotMatch(result.response, /faux-job-123|rapport\.pdf/);
|
|
737
|
+
assert.match(result.response, /deliverables\/result\.md/);
|
|
738
|
+
assert.equal(session.headlessPlan?.[0]?.status, 'done');
|
|
739
|
+
} finally {
|
|
740
|
+
globalThis.fetch = originalFetch;
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
|
|
689
744
|
test('agent graph auto-declares the plan from an agent_plan task-graph fragment', async () => {
|
|
690
745
|
// The bridge that makes parallel ingestion real: when the LLM calls
|
|
691
746
|
// production__agent_plan, the shell integrates the fragment as the plan
|
package/src/commands/slash.js
CHANGED
|
@@ -513,9 +513,6 @@ export async function refreshMcpRuntimeStatus(session) {
|
|
|
513
513
|
|
|
514
514
|
async function statusText(session) {
|
|
515
515
|
const states = await refreshMcpRuntimeStatus(session);
|
|
516
|
-
const services = session.workspacePath
|
|
517
|
-
? await composeServices(session).catch(() => [])
|
|
518
|
-
: [];
|
|
519
516
|
const workspaceStats = collectWorkspaceStats(session);
|
|
520
517
|
const workspaceColumn = sectionBlock('Workspace', [
|
|
521
518
|
`workspace: ${session.workspace ?? '-'}`,
|
|
@@ -530,9 +527,6 @@ async function statusText(session) {
|
|
|
530
527
|
`model: ${session.wikircConfig?.llm?.model ?? '-'}`,
|
|
531
528
|
`baseUrl: ${session.wikircConfig?.llm?.baseUrl ?? '-'}`,
|
|
532
529
|
]);
|
|
533
|
-
const servicesColumn = sectionBlock('Services', services.length > 0
|
|
534
|
-
? services.map((service) => `- ${service}`)
|
|
535
|
-
: ['No workspace loaded.']);
|
|
536
530
|
const runtimeColumn = sectionBlock('Runtime', (states ? serviceStatesText(states) : 'Docker runtime not available or no workspace loaded.').split('\n'));
|
|
537
531
|
const mcpColumn = sectionBlock('MCP', formatMcpStatus(session.mcp).split('\n'));
|
|
538
532
|
const mcpToolsColumn = sectionBlock('MCP tool summary', formatMcpToolSummary(session.mcp).split('\n'));
|
|
@@ -542,7 +536,7 @@ async function statusText(session) {
|
|
|
542
536
|
'',
|
|
543
537
|
workspaceStatsText(workspaceStats),
|
|
544
538
|
'',
|
|
545
|
-
|
|
539
|
+
runtimeColumn,
|
|
546
540
|
'',
|
|
547
541
|
twoColumns(mcpColumn, mcpToolsColumn),
|
|
548
542
|
].join('\n');
|
package/src/core/agentLoop.js
CHANGED
|
@@ -66,6 +66,7 @@ export async function runAgenticLoop(agent, session, initialInput, {
|
|
|
66
66
|
onPendingSteps = null,
|
|
67
67
|
onActivitiesStarted = null,
|
|
68
68
|
onActivitiesCompleted = null,
|
|
69
|
+
deterministicTerminalSummary = false,
|
|
69
70
|
onMaxTurns = null,
|
|
70
71
|
abortMessage = 'Agent run cancelled.',
|
|
71
72
|
parallelHandoff = false,
|
|
@@ -149,6 +150,11 @@ export async function runAgenticLoop(agent, session, initialInput, {
|
|
|
149
150
|
const completed = waitResult.completed ?? [];
|
|
150
151
|
const summary = formatCompletedActivities(completed);
|
|
151
152
|
onActivitiesCompleted?.({ completed, summary });
|
|
153
|
+
const unfinished = (session.headlessPlan ?? []).some((step) =>
|
|
154
|
+
['pending', 'pending_approval', 'running', 'starting', 'queued'].includes(String(step.status ?? '').toLowerCase()));
|
|
155
|
+
if (deterministicTerminalSummary && !unfinished) {
|
|
156
|
+
return { ok: true, completed, summary, deterministicSummary: true };
|
|
157
|
+
}
|
|
152
158
|
if (parallelHandoff && readyPlanTasks(session.headlessPlan).length > 1) {
|
|
153
159
|
return { ok: true, handoff: true };
|
|
154
160
|
}
|
|
@@ -77,8 +77,56 @@ test('runAgenticLoop waits for new activities and continues with a completion su
|
|
|
77
77
|
assert.equal(result.ok, true);
|
|
78
78
|
assert.equal(inputs.length, 2);
|
|
79
79
|
assert.match(inputs[1], /Completed activities:/);
|
|
80
|
-
assert.match(inputs[1],
|
|
81
|
-
assert.deepEqual(callbacks, ['started:1', '-
|
|
80
|
+
assert.match(inputs[1], /- job: done/);
|
|
81
|
+
assert.deepEqual(callbacks, ['started:1', '- job: done']);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('runAgenticLoop can finish from terminal activity facts without another LLM turn', async () => {
|
|
85
|
+
const session = { activities: {}, headlessPlan: null };
|
|
86
|
+
let turns = 0;
|
|
87
|
+
const result = await runAgenticLoop({
|
|
88
|
+
async invoke({ session: turnSession }) {
|
|
89
|
+
turns += 1;
|
|
90
|
+
dispatchAgentEvent(turnSession, createAgentEvent('activity_upserted', {
|
|
91
|
+
payload: {
|
|
92
|
+
activity: {
|
|
93
|
+
id: 'job-build',
|
|
94
|
+
source: 'production',
|
|
95
|
+
kind: 'build',
|
|
96
|
+
label: 'Build workspace',
|
|
97
|
+
status: 'running',
|
|
98
|
+
terminal: false,
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
}));
|
|
102
|
+
return { response: 'Job started.' };
|
|
103
|
+
},
|
|
104
|
+
}, session, 'Build workspace', {
|
|
105
|
+
maxTurns: 3,
|
|
106
|
+
timeoutMs: 1000,
|
|
107
|
+
deterministicTerminalSummary: true,
|
|
108
|
+
waitForActivities: async (turnSession) => {
|
|
109
|
+
dispatchAgentEvent(turnSession, createAgentEvent('activity_upserted', {
|
|
110
|
+
payload: {
|
|
111
|
+
activity: {
|
|
112
|
+
id: 'job-build',
|
|
113
|
+
source: 'production',
|
|
114
|
+
kind: 'build',
|
|
115
|
+
label: 'Build workspace',
|
|
116
|
+
status: 'done',
|
|
117
|
+
terminal: true,
|
|
118
|
+
outputRefs: ['deliverables/result.md'],
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
}));
|
|
122
|
+
return { ok: true, completed: Object.values(turnSession.activities) };
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
assert.equal(turns, 1);
|
|
127
|
+
assert.equal(result.deterministicSummary, true);
|
|
128
|
+
assert.match(result.summary, /build: done/);
|
|
129
|
+
assert.match(result.summary, /output: deliverables\/result\.md/);
|
|
82
130
|
});
|
|
83
131
|
|
|
84
132
|
test('runAgenticLoop extracts a fallback numbered plan from first response', async () => {
|
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
|
|
|
@@ -51,7 +51,14 @@ 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) {
|
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.11';
|
|
5
5
|
|
|
6
6
|
function envValue(key) {
|
|
7
7
|
const filePath = managerEnvFile();
|
package/src/core/plan.js
CHANGED
|
@@ -141,10 +141,17 @@ export function formatConfigValue(value) {
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
export function formatCompletedActivities(activities) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
.
|
|
144
|
+
const terminal = activities.filter((activity) => activity.terminal);
|
|
145
|
+
const lines = terminal.map((activity) => {
|
|
146
|
+
const label = activity.kind ?? activity.label ?? `${activity.source} ${activity.id ?? 'activity'}`;
|
|
147
|
+
return `- ${label}: ${activity.status}${activity.error ? ` (${activity.error})` : ''}`;
|
|
148
|
+
});
|
|
149
|
+
const outputs = [...new Set(terminal.flatMap((activity) => activity.outputRefs ?? []).map((ref) => {
|
|
150
|
+
if (ref && typeof ref === 'object') return String(ref.ref ?? ref.path ?? ref.url ?? '').trim();
|
|
151
|
+
return String(ref ?? '').trim();
|
|
152
|
+
}).filter(Boolean))];
|
|
153
|
+
if (outputs.length > 0) lines.push(...outputs.map((output) => `- output: ${output}`));
|
|
154
|
+
return lines.join('\n');
|
|
148
155
|
}
|
|
149
156
|
|
|
150
157
|
function findMatchingPlanStepByStructure(plan, activity) {
|
package/src/runtime/runner.js
CHANGED
|
@@ -50,6 +50,7 @@ export async function runRuntimeAgenticLoop(agent, session, initialInput, { sign
|
|
|
50
50
|
maxTurns,
|
|
51
51
|
runId,
|
|
52
52
|
parallelHandoff,
|
|
53
|
+
deterministicTerminalSummary: true,
|
|
53
54
|
abortMessage: 'Runtime run cancelled.',
|
|
54
55
|
waitForActivities: (turnSession, startedActivities, waitOptions) =>
|
|
55
56
|
waitForRuntimeActivities(turnSession, startedActivities, { ...waitOptions, pollBusy }),
|
|
@@ -78,6 +79,14 @@ export async function runRuntimeAgenticLoop(agent, session, initialInput, { sign
|
|
|
78
79
|
onActivitiesStarted: ({ activities }) => {
|
|
79
80
|
emitRuntimeLog(session, `agentic-loop: ${activities.length} new activity(s), waiting`);
|
|
80
81
|
},
|
|
82
|
+
onActivitiesCompleted: ({ summary }) => {
|
|
83
|
+
emitRuntimeLog(session, `agentic-loop: completed activities:\n${summary}`);
|
|
84
|
+
dispatchAgentEvent(session, createAgentEvent('assistant_message', {
|
|
85
|
+
origin: 'runtime',
|
|
86
|
+
runId,
|
|
87
|
+
payload: { content: summary || 'Action terminée.' },
|
|
88
|
+
}));
|
|
89
|
+
},
|
|
81
90
|
onMaxTurns: ({ maxTurns: totalTurns }) => {
|
|
82
91
|
emitRuntimeLog(session, `agentic-loop: max turns (${totalTurns}) reached`);
|
|
83
92
|
},
|
package/src/shell/LeftPane.tsx
CHANGED
|
@@ -375,7 +375,10 @@ function conversationLines(messages: Array<{ role: string; content: string }>, c
|
|
|
375
375
|
let inFence = false;
|
|
376
376
|
const lines: Array<{ text: string; isCode: boolean }> = [];
|
|
377
377
|
for (const line of raw.split('\n')) {
|
|
378
|
-
|
|
378
|
+
// LLM responses often indent fenced blocks as part of a list. Accept
|
|
379
|
+
// leading whitespace so ```bash / ~~~ fences are rendered as code
|
|
380
|
+
// instead of leaking their Markdown markers into the conversation.
|
|
381
|
+
if (/^\s*(`{3,}|~{3,})/.test(line)) { inFence = !inFence; continue; }
|
|
379
382
|
lines.push({ text: line, isCode: inFence });
|
|
380
383
|
}
|
|
381
384
|
return [
|
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)"
|