@dotdrelle/wiki-manager 0.14.20 → 0.15.25
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 +28 -0
- package/README.md +137 -11
- package/agents.docker-compose.yml +41 -0
- package/docker-compose.yml +11 -1
- package/mcp.endpoints.example.json +3 -3
- package/package.json +1 -2
- package/src/agent/graph.js +92 -12
- package/src/agent/graph.test.js +108 -6
- package/src/cli/runtimeStartup.test.js +14 -0
- package/src/cli/wiki-manager.js +190 -29
- package/src/cli/wiki-manager.test.js +91 -0
- package/src/commands/slash.js +58 -0
- package/src/commands/slash.test.js +18 -0
- package/src/core/activity.js +1 -1
- package/src/core/activity.test.js +5 -0
- package/src/core/buildInfo.json +2 -2
- package/src/core/compose.js +7 -1
- package/src/core/dockerCompose.test.js +27 -3
- package/src/core/env.js +16 -3
- package/src/core/env.test.js +8 -3
- package/src/core/mcp.js +21 -8
- package/src/core/mcp.test.js +55 -0
- package/src/core/wikiSetup.js +26 -2
- package/src/core/wikiWorkspace.test.js +25 -0
- package/src/orchestrator/dispatcher.js +42 -17
- package/src/orchestrator/dispatcher.test.js +127 -0
- package/src/runtime/client.js +23 -0
- package/src/runtime/runner.js +3 -1
- package/src/shell/LeftPane.tsx +20 -1
- package/src/shell/repl.js +134 -36
- package/src/shell/repl.test.js +187 -15
- package/src/shell/useAgent.ts +8 -12
- package/tsconfig.json +2 -1
- package/wiki-workspace +110 -18
- package/agents.docker-compose.mailer.example.yml +0 -36
package/.env.example
CHANGED
|
@@ -42,11 +42,39 @@ WORKSPACES_ROOT=/path/to/workspaces
|
|
|
42
42
|
|
|
43
43
|
CME_MCP_AUTH_TOKEN=
|
|
44
44
|
DOCUMENTS_MCP_AUTH_TOKEN=
|
|
45
|
+
# Generated by `wiki-workspace agents up` when missing.
|
|
46
|
+
CONNECTORS_MCP_AUTH_TOKEN=
|
|
45
47
|
|
|
46
48
|
# ── Agent ports (optional, change only if defaults conflict) ───────────────────
|
|
47
49
|
|
|
48
50
|
# CME_MCP_PORT=3336
|
|
49
51
|
# DOCUMENTS_MCP_PORT=3337
|
|
52
|
+
# CONNECTORS_MCP_PORT=3338
|
|
53
|
+
|
|
54
|
+
# ── SaaS connectors (optional) ────────────────────────────────────────────────
|
|
55
|
+
#
|
|
56
|
+
# Enable the opt-in agent-connectors service:
|
|
57
|
+
CONNECTORS_ENABLED=false
|
|
58
|
+
#
|
|
59
|
+
# The public wikiLLM Desktop/PKCE Client ID is built into agent-connectors.
|
|
60
|
+
# Optional advanced/self-hosted override:
|
|
61
|
+
# GOOGLE_OAUTH_CLIENT_ID=
|
|
62
|
+
# Optional confidential-client compatibility override:
|
|
63
|
+
# GOOGLE_OAUTH_CLIENT_SECRET=
|
|
64
|
+
#
|
|
65
|
+
# The callback URL is generated automatically
|
|
66
|
+
# by `wiki-workspace agents up` when empty. Register the resulting exact URL in
|
|
67
|
+
# Google Cloud Console. Override it only for a remote/public HTTPS deployment.
|
|
68
|
+
# GOOGLE_OAUTH_CALLBACK_URL=http://127.0.0.1:${CONNECTORS_MCP_PORT}/oauth/google/callback
|
|
69
|
+
#
|
|
70
|
+
# Generated automatically when missing. Keep the two secrets distinct.
|
|
71
|
+
# OAUTH_STATE_SECRET=
|
|
72
|
+
# OAUTH_START_TOKEN=
|
|
73
|
+
# OAUTH_STATE_TTL_SECONDS=600
|
|
74
|
+
#
|
|
75
|
+
# Collection concurrency advertised to Donna:
|
|
76
|
+
# CONNECTORS_RECOMMENDED_CONCURRENCY=2
|
|
77
|
+
# CONNECTORS_MAX_CONCURRENCY=4
|
|
50
78
|
|
|
51
79
|
# ── Documents LLM OCR / Mermaid (optional) ─────────────────────────────────────
|
|
52
80
|
|
package/README.md
CHANGED
|
@@ -393,7 +393,6 @@ of you in the browser (create → configure → start the agents → open).
|
|
|
393
393
|
| [`agent-cme`](https://github.com/dotdrelle/agent-cme) | Global Confluence to Markdown MCP exporter; workspace injected automatically by Donna |
|
|
394
394
|
| [`agent-wiki-production`](https://github.com/dotdrelle/agent-wiki-production) | Workspace-scoped production jobs: ingest, build, export, polish, pipeline |
|
|
395
395
|
| [`agent-wiki-documents`](https://github.com/dotdrelle/agent-wiki-documents) | Document conversion MCP: PDF/Office/HTML/images → Markdown (OCR-capable) |
|
|
396
|
-
| [`agent-mailer-api`](https://github.com/dotdrelle/agent-mailer-api) | Optional external mailer MCP endpoint (user-side override, not in the default stack) |
|
|
397
396
|
|
|
398
397
|
## Workspace Model
|
|
399
398
|
|
|
@@ -487,7 +486,7 @@ cp .env.example .env
|
|
|
487
486
|
|
|
488
487
|
The `.env` file is loaded automatically by both `wiki-manager` (Node/Bun process)
|
|
489
488
|
and `wiki-workspace` (Docker Compose). It sets `WORKSPACES_ROOT`, per-agent auth
|
|
490
|
-
tokens
|
|
489
|
+
tokens, optional port overrides, and credentials for enabled connectors.
|
|
491
490
|
|
|
492
491
|
### External MCP endpoints
|
|
493
492
|
|
|
@@ -518,6 +517,45 @@ process environment (including the `.env` loaded at startup):
|
|
|
518
517
|
Copy `mcp.endpoints.example.json` to `mcp.endpoints.json` and set the matching
|
|
519
518
|
token variables in `.env`.
|
|
520
519
|
|
|
520
|
+
### `chatAccess`: which tools chat may use
|
|
521
|
+
|
|
522
|
+
Declaring a server above makes its tools available to **`/agent`** — no further
|
|
523
|
+
declaration, ever. Plug in a new MCP and Donna discovers and uses its tools
|
|
524
|
+
immediately.
|
|
525
|
+
|
|
526
|
+
The optional `chatAccess` block is the authorization layer for **`/chat`**,
|
|
527
|
+
which is closed by default. It decides which tools chat may use, and nothing
|
|
528
|
+
else:
|
|
529
|
+
|
|
530
|
+
```json
|
|
531
|
+
"chatAccess": {
|
|
532
|
+
"maxToolIterations": 8,
|
|
533
|
+
"servers": {
|
|
534
|
+
"cme": { "allow": ["cme_status", "cme_sources_list", "cme_export_status"] },
|
|
535
|
+
"exa": { "allow": ["*"] }
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
| entry | effect in `/chat` |
|
|
541
|
+
| --- | --- |
|
|
542
|
+
| server absent | none of its tools — agent-only |
|
|
543
|
+
| `"allow": ["*"]` | every tool the server exposes |
|
|
544
|
+
| `"allow": [names]` | exactly those tools |
|
|
545
|
+
|
|
546
|
+
Every server uses this one shape. The list is authoritative: naming a tool is
|
|
547
|
+
the decision, whatever the tool is called. No name heuristic filters it — a
|
|
548
|
+
tool name is not a contract, and a third-party MCP is free to name its tools
|
|
549
|
+
however it likes.
|
|
550
|
+
|
|
551
|
+
`/chat` carries no plan: it performs direct unitary actions only. The
|
|
552
|
+
orchestration entry points (`agent_plan`, `agent_execute`,
|
|
553
|
+
`production_start_job`, and plan mutation) are therefore never offered to it,
|
|
554
|
+
including under `"*"`. Multi-step work belongs to `/agent`.
|
|
555
|
+
|
|
556
|
+
An `allowActions` key written by an older manager is folded into `allow` on
|
|
557
|
+
read and removed on the next `agents up`.
|
|
558
|
+
|
|
521
559
|
MCP `tools/call` requests retry transient HTTP/MCP failures before the run fails.
|
|
522
560
|
They also share a per-endpoint outbound control budget (45 RPM by default,
|
|
523
561
|
configurable with `WIKI_MANAGER_MCP_REQUESTS_PER_MINUTE`). This budget is
|
|
@@ -599,18 +637,105 @@ generates the missing agent auth tokens into your manager `.env` and seeds
|
|
|
599
637
|
automatically from the manager workspaces directory. Agent state is stored under
|
|
600
638
|
`./.agents-data/` unless `AGENTS_DATA_DIR` is set.
|
|
601
639
|
|
|
602
|
-
|
|
640
|
+
An `npm -g update @dotdrelle/wiki-manager` replaces the packaged Compose files
|
|
641
|
+
but preserves the operator-owned `.env`, `mcp.endpoints.json`, workspaces, agent
|
|
642
|
+
data, and runtime database. Missing standard endpoint definitions are migrated
|
|
643
|
+
additively; existing endpoint definitions are never overwritten.
|
|
603
644
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
645
|
+
The Gmail connector agent is packaged but opt-in. Enable it in the manager
|
|
646
|
+
`.env`:
|
|
647
|
+
|
|
648
|
+
```dotenv
|
|
649
|
+
CONNECTORS_ENABLED=true
|
|
650
|
+
# Optional locally; generated from CONNECTORS_MCP_PORT when empty:
|
|
651
|
+
GOOGLE_OAUTH_CALLBACK_URL=
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
For a local installation, `wiki-workspace agents up` fills an empty callback
|
|
655
|
+
with:
|
|
656
|
+
|
|
657
|
+
```text
|
|
658
|
+
http://127.0.0.1:<CONNECTORS_MCP_PORT>/oauth/google/callback
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
With the default port, register this exact redirect URI in Google Cloud
|
|
662
|
+
Console:
|
|
663
|
+
|
|
664
|
+
```text
|
|
665
|
+
http://127.0.0.1:3338/oauth/google/callback
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
The browser resolves `127.0.0.1`; Docker forwards the published host port to
|
|
669
|
+
the connectors container. For a remote deployment, set an explicit public
|
|
670
|
+
HTTPS callback instead. In both cases the configured URL must match the Google
|
|
671
|
+
Cloud redirect URI exactly.
|
|
672
|
+
|
|
673
|
+
The local flow uses the public wikiLLM Desktop OAuth Client ID with PKCE and
|
|
674
|
+
does not require a Client Secret. Normal users set neither Google credential.
|
|
675
|
+
`GOOGLE_OAUTH_CLIENT_ID` remains an advanced override for private/internal
|
|
676
|
+
Google projects, and `GOOGLE_OAUTH_CLIENT_SECRET` is an optional compatibility
|
|
677
|
+
override for administrators using a confidential web client.
|
|
678
|
+
|
|
679
|
+
`agents up` also generates the connectors MCP token plus distinct OAuth
|
|
680
|
+
start/state secrets when missing. It adds a regular, standard MCP `connectors`
|
|
681
|
+
entry to `mcp.endpoints.json`. Setting `CONNECTORS_ENABLED=false` and running
|
|
682
|
+
`agents up` removes that entry again, so disabled services are not probed. No
|
|
683
|
+
non-standard `enabled` property is written to MCP configuration files.
|
|
684
|
+
The matching `chatAccess.connectors` policy is managed at the same time, in the
|
|
685
|
+
same shape as every other server — a single `allow` list holding
|
|
686
|
+
`connectors_google_status` and `connectors_google_oauth_start`, so chat can
|
|
687
|
+
report the authorization state and start it. See
|
|
688
|
+
[`chatAccess`](#chataccess-which-tools-chat-may-use).
|
|
689
|
+
|
|
690
|
+
Connector authorization is also available without asking the LLM. These two
|
|
691
|
+
commands work in both the Shell UI and the `llm-wiki serve` chat:
|
|
692
|
+
|
|
693
|
+
```text
|
|
694
|
+
/connector list
|
|
695
|
+
/connector auth google
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
The first reports the Gmail read-only authorization state for the active
|
|
699
|
+
workspace. The second opens Google's OAuth page in the browser. Asking Donna
|
|
700
|
+
to configure or check Google remains supported through the direct connector
|
|
701
|
+
tools above.
|
|
702
|
+
|
|
703
|
+
The Compose profile is an internal implementation detail. Do not set
|
|
704
|
+
`COMPOSE_PROFILES` and do not add provider names such as Gmail or Slack to it:
|
|
705
|
+
one `agent-connectors` service hosts all connector providers.
|
|
706
|
+
|
|
707
|
+
For a public serve deployment, authorization can be started through the
|
|
708
|
+
same-origin proxy:
|
|
608
709
|
|
|
609
710
|
```bash
|
|
610
|
-
|
|
611
|
-
|
|
711
|
+
curl -X POST https://wiki.example.com/api/connectors/google/oauth/start \
|
|
712
|
+
-H 'Origin: https://wiki.example.com' \
|
|
713
|
+
-H 'X-LLM-WIKI-OAUTH: 1' \
|
|
714
|
+
-H 'Content-Type: application/json' \
|
|
715
|
+
-d '{"instanceId":"google-1"}'
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
Open the returned `authorizationUrl`. The workspace is injected by serve and
|
|
719
|
+
cannot be selected by the browser request.
|
|
720
|
+
|
|
721
|
+
Donna discovers the agent contract automatically from the `connectors` MCP
|
|
722
|
+
endpoint. With only one provider, no routing entry is required. To pin it
|
|
723
|
+
explicitly in a workspace profile:
|
|
724
|
+
|
|
725
|
+
```yaml
|
|
726
|
+
capabilityRouting:
|
|
727
|
+
external-source.collect:
|
|
728
|
+
preferredAgents: [connectors]
|
|
729
|
+
allowedAgents: [connectors]
|
|
612
730
|
```
|
|
613
731
|
|
|
732
|
+
#### Optional agents and user overrides
|
|
733
|
+
|
|
734
|
+
Anything beyond the packaged stack is an external connector operated and
|
|
735
|
+
configured independently by the user. To run one alongside the packaged
|
|
736
|
+
agents, create a file named
|
|
737
|
+
`agents.docker-compose.override.yml` **next to your `.env`**:
|
|
738
|
+
|
|
614
739
|
`agents up` includes it automatically when present (standard Docker Compose
|
|
615
740
|
merge: new services are added, same-name keys override the defaults — you can
|
|
616
741
|
also use it to pin a port or a variable of a default agent). The file is
|
|
@@ -618,7 +743,8 @@ yours: wiki-manager never generates or overwrites it. Complete the setup by
|
|
|
618
743
|
adding the connector's variables to your `.env` and its endpoint block to
|
|
619
744
|
your `mcp.endpoints.json` — every variable an external MCP endpoint needs
|
|
620
745
|
lives in the `.env` and is referenced as `${VAR_NAME}` from
|
|
621
|
-
`mcp.endpoints.json`.
|
|
746
|
+
`mcp.endpoints.json`. A connector running outside the manager Compose stack
|
|
747
|
+
only needs an entry in `mcp.endpoints.json`.
|
|
622
748
|
|
|
623
749
|
Workspace-native MCP servers (`llm-wiki`, `production`) stay configured through
|
|
624
750
|
each workspace `.env`. External agents are workspace-agnostic: the active
|
|
@@ -1029,7 +1155,7 @@ llm-wiki-manager/
|
|
|
1029
1155
|
│ ├── useAgent.ts # agent call wrapper (drives the @langchain/langgraph run)
|
|
1030
1156
|
│ └── renderer.ts # markdown stripping and line coloring
|
|
1031
1157
|
├── docker-compose.yml # workspace-scoped stack (serve, mcp-http, production-mcp)
|
|
1032
|
-
├── agents.docker-compose.yml # global external agents
|
|
1158
|
+
├── agents.docker-compose.yml # packaged global external agents
|
|
1033
1159
|
├── wiki-workspace
|
|
1034
1160
|
├── .env.example # template for local .env (WORKSPACES_ROOT, agent tokens, …)
|
|
1035
1161
|
├── mcp.endpoints.example.json
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# agents.docker-compose.yml — external agent stack
|
|
2
2
|
#
|
|
3
3
|
# Starts cme and documents as workspace-agnostic global services.
|
|
4
|
+
# The connectors service is opt-in with CONNECTORS_ENABLED=true; wiki-workspace
|
|
5
|
+
# translates that setting to the internal Compose profile.
|
|
4
6
|
# All workspace paths are resolved at tool-call time via the `workspace` param.
|
|
5
7
|
#
|
|
6
8
|
# Required:
|
|
@@ -12,6 +14,11 @@
|
|
|
12
14
|
# DOCUMENTS_MCP_PORT — 3337
|
|
13
15
|
# CME_MCP_AUTH_TOKEN — bearer token for cme agent (empty = no auth)
|
|
14
16
|
# DOCUMENTS_MCP_AUTH_TOKEN — bearer token for documents agent (empty = no auth)
|
|
17
|
+
# CONNECTORS_MCP_PORT — 3338
|
|
18
|
+
# CONNECTORS_MCP_AUTH_TOKEN — bearer token for connectors MCP
|
|
19
|
+
# GOOGLE_OAUTH_CLIENT_ID / GOOGLE_OAUTH_CLIENT_SECRET — OAuth application
|
|
20
|
+
# GOOGLE_OAUTH_CALLBACK_URL — exact public post-proxy callback URL
|
|
21
|
+
# OAUTH_STATE_SECRET / OAUTH_START_TOKEN — distinct 32+ byte secrets
|
|
15
22
|
# DOCUMENT_LLM_BASE_URL — https://albert.api.etalab.gouv.fr/v1
|
|
16
23
|
# DOCUMENT_LLM_MODEL — lightonai/LightOnOCR-2-1B
|
|
17
24
|
# DOCUMENT_LLM_API_KEY — OpenAI/OpenAI-compatible API key for document OCR
|
|
@@ -76,3 +83,37 @@ services:
|
|
|
76
83
|
- ${WORKSPACES_ROOT:?Set WORKSPACES_ROOT to the directory containing all workspace folders}:/workspaces
|
|
77
84
|
#- ${AGENTS_DATA_DIR:-./.agents-data}/certs:/certs:ro
|
|
78
85
|
restart: unless-stopped
|
|
86
|
+
|
|
87
|
+
connectors:
|
|
88
|
+
profiles: [connectors]
|
|
89
|
+
# Local iteration: build the image from the connectors repo instead of
|
|
90
|
+
# pulling the published tag. `image:` is kept so the build is tagged with the
|
|
91
|
+
# same name; restore image-only before publishing a release.
|
|
92
|
+
build:
|
|
93
|
+
context: ../agent-external/agent-connectors
|
|
94
|
+
dockerfile: Dockerfile
|
|
95
|
+
image: dotdrelle/agent-connectors:latest
|
|
96
|
+
user: "${UID:-1000}:${GID:-1000}"
|
|
97
|
+
ports:
|
|
98
|
+
- "${CONNECTORS_MCP_PORT:-3338}:3338"
|
|
99
|
+
environment:
|
|
100
|
+
- CONNECTORS_PORT=3338
|
|
101
|
+
- MCP_AUTH_TOKEN=${CONNECTORS_MCP_AUTH_TOKEN:-}
|
|
102
|
+
- WORKSPACES_ROOT=/workspaces
|
|
103
|
+
- AGENT_DATA_DIR=/data
|
|
104
|
+
- GOOGLE_OAUTH_CLIENT_ID=${GOOGLE_OAUTH_CLIENT_ID:-}
|
|
105
|
+
- GOOGLE_OAUTH_CLIENT_SECRET=${GOOGLE_OAUTH_CLIENT_SECRET:-}
|
|
106
|
+
- GOOGLE_OAUTH_CALLBACK_URL=${GOOGLE_OAUTH_CALLBACK_URL:-}
|
|
107
|
+
- OAUTH_STATE_SECRET=${OAUTH_STATE_SECRET:-}
|
|
108
|
+
- OAUTH_START_TOKEN=${OAUTH_START_TOKEN:-}
|
|
109
|
+
- OAUTH_STATE_TTL_SECONDS=${OAUTH_STATE_TTL_SECONDS:-600}
|
|
110
|
+
- CONNECTORS_RECOMMENDED_CONCURRENCY=${CONNECTORS_RECOMMENDED_CONCURRENCY:-2}
|
|
111
|
+
- CONNECTORS_MAX_CONCURRENCY=${CONNECTORS_MAX_CONCURRENCY:-4}
|
|
112
|
+
- NODE_USE_ENV_PROXY=${NODE_USE_ENV_PROXY:-}
|
|
113
|
+
- HTTPS_PROXY=${HTTPS_PROXY:-}
|
|
114
|
+
- HTTP_PROXY=${HTTP_PROXY:-}
|
|
115
|
+
- NO_PROXY=${NO_PROXY:-localhost,127.0.0.1,host.docker.internal}
|
|
116
|
+
volumes:
|
|
117
|
+
- ${AGENTS_DATA_DIR:-./.agents-data}/connectors:/data
|
|
118
|
+
- ${WORKSPACES_ROOT:?Set WORKSPACES_ROOT to the directory containing all workspace folders}:/workspaces
|
|
119
|
+
restart: unless-stopped
|
package/docker-compose.yml
CHANGED
|
@@ -37,7 +37,10 @@ services:
|
|
|
37
37
|
stop_grace_period: 10s
|
|
38
38
|
volumes:
|
|
39
39
|
- ${WIKI_WORKSPACE_PATH:-/tmp/llm-wiki-workspace-not-set}:/workspace
|
|
40
|
-
|
|
40
|
+
# Absolute path to the running instance's endpoints file (set by the
|
|
41
|
+
# manager to managerStateDir/mcp.endpoints.json). Falls back to the
|
|
42
|
+
# package-relative file only when launched without the manager.
|
|
43
|
+
- ${WIKI_MANAGER_MCP_ENDPOINTS_FILE:-./mcp.endpoints.json}:/mcp.endpoints.json:ro
|
|
41
44
|
- ${AGENTS_DATA_DIR:-./.agents-data}/documents/input:/documents/input
|
|
42
45
|
- ${AGENTS_DATA_DIR:-./.agents-data}/documents/uploads:/documents/uploads
|
|
43
46
|
# TLS certificates — uncomment if WIKI_SERVE_TLS_CERT_PATH is set
|
|
@@ -53,6 +56,12 @@ services:
|
|
|
53
56
|
- DOCUMENTS_MCP_PORT
|
|
54
57
|
- CME_MCP_AUTH_TOKEN
|
|
55
58
|
- DOCUMENTS_MCP_AUTH_TOKEN
|
|
59
|
+
# Connectors MCP: the serve panel resolves ${CONNECTORS_MCP_AUTH_TOKEN} in
|
|
60
|
+
# /mcp.endpoints.json from process.env only, so it must be forwarded here
|
|
61
|
+
# (symmetric with cme/documents) or the panel sends an empty Bearer → 401.
|
|
62
|
+
- CONNECTORS_MCP_PORT
|
|
63
|
+
- CONNECTORS_MCP_AUTH_TOKEN
|
|
64
|
+
- CONNECTORS_OAUTH_START_TOKEN=${OAUTH_START_TOKEN:-}
|
|
56
65
|
- WORKSPACE_NAME
|
|
57
66
|
- DOCUMENT_INPUT_DIR=/documents/input
|
|
58
67
|
- DOCUMENT_UPLOADS_DIR=/documents/uploads
|
|
@@ -61,6 +70,7 @@ services:
|
|
|
61
70
|
- PRODUCTION_MCP_PROXY_URL=http://host.docker.internal:${PRODUCTION_MCP_PORT:-3102}/mcp/
|
|
62
71
|
- WIKI_MANAGER_RUNTIME_URL=http://host.docker.internal:${WIKI_MANAGER_RUNTIME_PORT:-7788}
|
|
63
72
|
- WIKI_MANAGER_RUNTIME_TOKEN=${WIKI_MANAGER_RUNTIME_TOKEN:-}
|
|
73
|
+
- CONNECTORS_AGENT_URL=http://host.docker.internal:${CONNECTORS_MCP_PORT:-3338}
|
|
64
74
|
# HTTPS — set paths inside the container (e.g. /certs/server.crt) and uncomment the volume above
|
|
65
75
|
#- WIKI_SERVE_TLS_CERT_PATH=/certs/server.crt
|
|
66
76
|
#- WIKI_SERVE_TLS_KEY_PATH=/certs/server.key
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
"chatAccess": {
|
|
30
30
|
"maxToolIterations": 8,
|
|
31
31
|
"servers": {
|
|
32
|
-
"wiki":
|
|
33
|
-
"production":
|
|
34
|
-
"cme":
|
|
32
|
+
"llm-wiki": { "allow": ["help_list", "help_read", "wiki_workspace_status", "wiki_list_pages", "wiki_read_page", "wiki_read_pages", "wiki_search_context", "wiki_collect_context", "wiki_read_ingested_source"] },
|
|
33
|
+
"wiki-production": { "allow": ["production_job_status", "production_jobs_list"] },
|
|
34
|
+
"cme": { "allow": ["cme_status", "cme_sources_list", "cme_export_status"] }
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotdrelle/wiki-manager",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.25",
|
|
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,7 +27,6 @@
|
|
|
27
27
|
"wiki-workspace",
|
|
28
28
|
"docker-compose.yml",
|
|
29
29
|
"agents.docker-compose.yml",
|
|
30
|
-
"agents.docker-compose.mailer.example.yml",
|
|
31
30
|
"mcp.endpoints.example.json",
|
|
32
31
|
".env.example",
|
|
33
32
|
"tsconfig.json",
|
package/src/agent/graph.js
CHANGED
|
@@ -562,7 +562,8 @@ function assertAgentReadSlashCommandAllowed(commandLine) {
|
|
|
562
562
|
function withActiveWorkspaceForExternalTool(session, server, tool, args) {
|
|
563
563
|
const needsWorkspace =
|
|
564
564
|
(server === 'documents' && tool.startsWith('documents_') && tool !== 'documents_status') ||
|
|
565
|
-
(server === 'cme' && tool.startsWith('cme_') && tool !== 'cme_export_cancel' && !(tool === 'cme_export_status' && args.job_id))
|
|
565
|
+
(server === 'cme' && tool.startsWith('cme_') && tool !== 'cme_export_cancel' && !(tool === 'cme_export_status' && args.job_id)) ||
|
|
566
|
+
(server === 'connectors' && tool.startsWith('connectors_'));
|
|
566
567
|
if (!needsWorkspace) return args;
|
|
567
568
|
if (!session.workspace) {
|
|
568
569
|
throw new Error(`No active workspace available for ${server}.${tool}. Use /use <workspace> first.`);
|
|
@@ -820,7 +821,16 @@ function connectorConfigurationTarget(session, objective) {
|
|
|
820
821
|
if (!/(?:configur|connect|authent|oauth|setup|sign[ -]?in)/i.test(text)) return null;
|
|
821
822
|
for (const [serverName, server] of Object.entries(session?.mcp ?? {})) {
|
|
822
823
|
if (server?.status !== 'connected' || !Array.isArray(server.tools) || server.tools.length === 0) continue;
|
|
823
|
-
const
|
|
824
|
+
const genericAliasParts = new Set([
|
|
825
|
+
'auth', 'authenticate', 'connect', 'connector', 'connectors',
|
|
826
|
+
'configure', 'oauth', 'setup', 'start', 'status',
|
|
827
|
+
]);
|
|
828
|
+
const aliases = [
|
|
829
|
+
serverName,
|
|
830
|
+
...server.tools.map((tool) => tool?.name ?? ''),
|
|
831
|
+
]
|
|
832
|
+
.flatMap((value) => String(value).toLowerCase().split(/[^a-z0-9]+/))
|
|
833
|
+
.filter((part) => part.length >= 3 && !genericAliasParts.has(part));
|
|
824
834
|
if (!aliases.some((alias) => text.includes(alias))) continue;
|
|
825
835
|
const setupTool = server.tools.find((tool) => {
|
|
826
836
|
const name = String(tool?.name ?? '').toLowerCase();
|
|
@@ -953,20 +963,20 @@ export function buildAgentSystemPrompt(state) {
|
|
|
953
963
|
`Current wikirc profile: ${wikirc}.`,
|
|
954
964
|
`Available primitives: ${commandList(state.session)}.`,
|
|
955
965
|
'Only announce or call slash commands that appear exactly in Available primitives. Do not invent command names, subcommands, or arguments.',
|
|
956
|
-
'Connected MCP tools you may call directly (server__tool naming convention)
|
|
966
|
+
'Connected MCP tools you may call directly (server__tool naming convention). Everything listed below is directly callable. When the requested action has no matching direct tool, call runtime__delegate with the original objective: the runtime resolves it against the discovered agent capability contracts, including executor-only single-task capabilities.',
|
|
957
967
|
mcpTools,
|
|
958
968
|
'Current local MCP job queue:',
|
|
959
969
|
formatQueue(state.session),
|
|
960
970
|
'Available skills:',
|
|
961
971
|
skills,
|
|
962
|
-
'In interactive agent mode
|
|
972
|
+
'In interactive agent mode, call only tools actually provided to you. Any directly offered tool stays direct; never substitute an orchestration-contract tool yourself.',
|
|
963
973
|
'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.',
|
|
964
974
|
'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.',
|
|
965
975
|
'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. Never invent results, interpret generated content beyond what the tool returned, or fabricate a verification checklist.',
|
|
966
976
|
'You are in AGENT mode, so you can actually act. You MAY close with ONE short, natural follow-up — a single sentence phrased as an offer, and only when it genuinely helps and is an action you can perform right here (delegate it or call a tool), e.g. "Want me to start ingesting these pages?" (phrased in the reply language). This is what makes you feel like an assistant rather than a readout. Only offer what you can truly do in agent mode — never an offer that would require another mode. Keep it to that one line: never produce a "Next steps"/"Prochaines étapes"/"À suivre" list, a checklist, an options menu, or commands for the user to type. If nothing useful naturally follows, simply stop after the answer — do not pad.',
|
|
967
977
|
'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.',
|
|
968
978
|
'Write the way a thoughtful colleague speaks: warm, plain, and to the point. For a simple factual question, 1 to 3 sentences is the sweet spot. Stay synthetic and information-dense — use only the lines needed, and never exceed roughly 15 to 20 short lines even for a detailed answer. Never expose internal reasoning, repeated checks, tool-selection commentary, or a chronological diary. Prioritize the result, essential facts, concrete errors, and actual outputs — but say them in human language, not as a field dump.',
|
|
969
|
-
'
|
|
979
|
+
'Call a matching direct tool when one is offered. Otherwise, for an action backed by a discovered agent capability, call runtime__delegate with the original objective. This applies to both planner agents and executor-only single-task agents. Never call an agent orchestration-contract or plan tool directly.',
|
|
970
980
|
'For any question about the current workspace inventory or what is waiting there, call wiki__wiki_workspace_status first and answer only from its result. This is the canonical read-only workspace state; do not reconstruct it from upload, connector, or production tools.',
|
|
971
981
|
'Tool identifiers are private implementation details. Never print MCP tool names such as server__tool in a user-facing answer. Describe the human result instead.',
|
|
972
982
|
'Internal data shapes are private too. Never quote raw JSON field names (e.g. pendingSources.files), internal directory paths (e.g. raw/untracked/), or config keys in a user-facing answer — translate them into plain language. Say "36 pages sources sont en attente d\'ingestion", not the field or path they came from.',
|
|
@@ -975,10 +985,10 @@ export function buildAgentSystemPrompt(state) {
|
|
|
975
985
|
'For service actions, recommend only available service primitives from Available primitives, with the exact service name when the primitive supports one.',
|
|
976
986
|
'Scope discipline: execute ONLY the action(s) the user explicitly requested. Never chain additional mutating operations (ingest, build, export, polish, delete, send…) that the user did not ask for — even when diagnostics or recommendations suggest them. Finish with the requested result and stop. Example: "applique les recommandations de config" means apply the config; it does NOT authorize launching the ingest those recommendations mention.',
|
|
977
987
|
state.session.runtime?.url
|
|
978
|
-
? 'The runtime is connected and runtime__delegate is
|
|
988
|
+
? 'The runtime is connected and runtime__delegate is available for any requested capability action that has no matching direct tool. When a matching direct tool is offered, call it directly; otherwise let the runtime resolve the objective from discovered capability contracts.'
|
|
979
989
|
: 'No runtime is connected, so you cannot execute actions. State that plainly and name the runtime connection as the missing capability — do not invent a workaround.',
|
|
980
990
|
'If the connector or service needed for a requested read or action is absent from the Connected MCP tools above (its service is not running — e.g. CME, documents, or production), say plainly that this service is not connected and name it as the missing capability. Never redirect a simple read (e.g. "give me the CME config") to an "agent action", never invent its result, and never propose a workaround. Only requests you can actually serve with a listed tool are answered with data.',
|
|
981
|
-
'For
|
|
991
|
+
'For an action with no matching direct tool, call runtime__delegate with the user objective only. The runtime chooses the capability, operation, agent and plan, including a validated single task for executor-only agents. Never choose those identifiers yourself. Never call <provider>__agent_plan, <provider>__agent_execute, legacy production__production_start_job, wiki__plan_set, or wiki__plan_done from interactive chat.',
|
|
982
992
|
'Do not ask the user which sources, files, connectors, or templates to use for an ingest, build, or export: the specialized agent discovers them from the workspace. When the objective is clear (e.g. "lance une ingestion"), delegate it as stated, without a clarifying question.',
|
|
983
993
|
'Promise only what the resolved capability actually exposes in its declared contract (the input schema the specialized agent publishes for that capability). When the user requests an execution parameter — a batch or chunk size, a count "N at a time", concurrency, ordering, priority, or any tuning knob — apply it only if that parameter exists in the target capability\'s published input schema. Otherwise do not confirm or promise it: delegate the objective, and if the user explicitly asked for that parameter, say plainly in one line that you started the work but do not control that aspect (the runtime and the specialized agent decide it). Never state or imply a parameter was applied when the agent contract cannot enforce it.',
|
|
984
994
|
'If runtime__delegate returns a blocker or no specialized provider is available, report only that concrete blocker concisely. Never replace the missing execution path with a suggested slash command, skill, MCP tool name, manual file move, administrator escalation, or alternative workflow unless the user explicitly asks for alternatives.',
|
|
@@ -1073,7 +1083,7 @@ export function isDonnaReadTool(item) {
|
|
|
1073
1083
|
// production agent) is delegated for its DAG/parallelism; plain single-step
|
|
1074
1084
|
// tools are called directly. This is a blocklist, not a whitelist, so adding a
|
|
1075
1085
|
// new MCP never silently disables its tools.
|
|
1076
|
-
function isOrchestrationBypassTool(name) {
|
|
1086
|
+
export function isOrchestrationBypassTool(name) {
|
|
1077
1087
|
const full = String(name ?? '');
|
|
1078
1088
|
if (!full) return true;
|
|
1079
1089
|
if (full === 'wiki__plan_set' || full === 'wiki__plan_done') return true;
|
|
@@ -1087,10 +1097,48 @@ function isReadOnlyMcpCall(session, server, tool) {
|
|
|
1087
1097
|
.find((item) => String(item?.name ?? '') === tool || String(item?.name ?? '').endsWith(`__${tool}`));
|
|
1088
1098
|
return isDonnaReadTool({
|
|
1089
1099
|
function: { name: `${server}__${tool}` },
|
|
1090
|
-
readOnly: descriptor?.readOnly === true,
|
|
1100
|
+
readOnly: descriptor?.annotations?.readOnlyHint === true || descriptor?.readOnly === true,
|
|
1091
1101
|
});
|
|
1092
1102
|
}
|
|
1093
1103
|
|
|
1104
|
+
function hasExecutedActionTool(messages, session) {
|
|
1105
|
+
for (const message of messages ?? []) {
|
|
1106
|
+
for (const call of message?.tool_calls ?? []) {
|
|
1107
|
+
const resolved = resolveToolCallName(session?.mcp, call?.function?.name ?? '', INTERNAL_TOOL_SERVERS);
|
|
1108
|
+
const { server, tool } = resolved;
|
|
1109
|
+
if (!server) continue;
|
|
1110
|
+
if (server === 'runtime') {
|
|
1111
|
+
if (tool !== 'status') return true;
|
|
1112
|
+
continue;
|
|
1113
|
+
}
|
|
1114
|
+
if (server === 'shell') {
|
|
1115
|
+
if (tool !== 'read_command') return true;
|
|
1116
|
+
continue;
|
|
1117
|
+
}
|
|
1118
|
+
if (server === 'wiki' && (tool === 'plan_set' || tool === 'plan_done')) return true;
|
|
1119
|
+
if (!isReadOnlyMcpCall(session, server, tool)) return true;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
return false;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
function hasExecutedReadOnlyTool(messages, session) {
|
|
1126
|
+
for (const message of messages ?? []) {
|
|
1127
|
+
for (const call of message?.tool_calls ?? []) {
|
|
1128
|
+
const { server, tool } = resolveToolCallName(
|
|
1129
|
+
session?.mcp,
|
|
1130
|
+
call?.function?.name ?? '',
|
|
1131
|
+
INTERNAL_TOOL_SERVERS,
|
|
1132
|
+
);
|
|
1133
|
+
if (server === 'runtime' && tool === 'status') return true;
|
|
1134
|
+
if (server === 'shell' && tool === 'read_command') return true;
|
|
1135
|
+
if (server && !['runtime', 'shell'].includes(server)
|
|
1136
|
+
&& isReadOnlyMcpCall(session, server, tool)) return true;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
return false;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1094
1142
|
export function createAgentGraph(options = {}) {
|
|
1095
1143
|
async function orchestratorNode(state) {
|
|
1096
1144
|
const llm = state.session.llm ?? options.llm ?? null;
|
|
@@ -1216,9 +1264,17 @@ export function createAgentGraph(options = {}) {
|
|
|
1216
1264
|
emitAgentEvent(state.session, 'assistant_message', 'llm', { content: '' });
|
|
1217
1265
|
state.session._onStep?.(`[${iterations + 1}/${MAX_TOOL_ITERATIONS}] ${result.tool_calls.length} MCP action${result.tool_calls.length > 1 ? 's' : ''} queued…`);
|
|
1218
1266
|
// On iteration 0 persist the user message too so it survives the loop.
|
|
1267
|
+
// Some OpenAI-compatible providers return tool_calls only at the
|
|
1268
|
+
// top-level result and omit them from `message`. Preserve the calls in
|
|
1269
|
+
// the transcript so follow-up guards can distinguish a status check
|
|
1270
|
+
// from an action that was actually executed.
|
|
1271
|
+
const assistantToolMessage = {
|
|
1272
|
+
...(result.message ?? { role: 'assistant', content: result.content ?? null }),
|
|
1273
|
+
tool_calls: result.tool_calls,
|
|
1274
|
+
};
|
|
1219
1275
|
const newMessages = iterations === 0
|
|
1220
|
-
? [{ role: 'user', content: state.input },
|
|
1221
|
-
: [
|
|
1276
|
+
? [{ role: 'user', content: state.input }, assistantToolMessage]
|
|
1277
|
+
: [assistantToolMessage];
|
|
1222
1278
|
return {
|
|
1223
1279
|
pendingToolCalls: result.tool_calls,
|
|
1224
1280
|
allowedToolNames: tools.map((item) => item?.function?.name).filter(Boolean),
|
|
@@ -1240,7 +1296,7 @@ export function createAgentGraph(options = {}) {
|
|
|
1240
1296
|
result.message ?? { role: 'assistant', content: result.content ?? '' },
|
|
1241
1297
|
{
|
|
1242
1298
|
role: 'user',
|
|
1243
|
-
content: 'Your previous response did not execute the requested action because it called no tool. Do not narrate or simulate execution. Call the
|
|
1299
|
+
content: 'Your previous response did not execute the requested action because it called no tool. Do not narrate or simulate execution. Call the matching direct tool now; if no matching direct tool is offered, call runtime__delegate with the original objective. Never invent results.',
|
|
1244
1300
|
},
|
|
1245
1301
|
],
|
|
1246
1302
|
toolIterations: 1,
|
|
@@ -1251,6 +1307,28 @@ export function createAgentGraph(options = {}) {
|
|
|
1251
1307
|
}
|
|
1252
1308
|
|
|
1253
1309
|
const canDelegate = tools.some((item) => item?.function?.name === 'runtime__delegate');
|
|
1310
|
+
if (iterations > 0 && canDelegate && !state.forceDelegation
|
|
1311
|
+
&& hasExecutedReadOnlyTool(conversationMessages, state.session)
|
|
1312
|
+
&& !hasExecutedActionTool(conversationMessages, state.session)
|
|
1313
|
+
&& await classifyRequestedAction(llm, state.input, state.session._abortSignal)) {
|
|
1314
|
+
state.session._onStreamReset?.();
|
|
1315
|
+
state.session._onStep?.('Agent: read-only checks completed but requested action not executed — delegating…');
|
|
1316
|
+
return {
|
|
1317
|
+
pendingToolCalls: null,
|
|
1318
|
+
messages: [
|
|
1319
|
+
result.message ?? { role: 'assistant', content: result.content ?? '' },
|
|
1320
|
+
{
|
|
1321
|
+
role: 'user',
|
|
1322
|
+
content: 'You only performed read-only/status checks and did not execute the requested action. Call runtime__delegate now with the original objective only.',
|
|
1323
|
+
},
|
|
1324
|
+
],
|
|
1325
|
+
toolIterations: iterations + 1,
|
|
1326
|
+
readyToStream: false,
|
|
1327
|
+
inputClassification: classification,
|
|
1328
|
+
retryWithoutTool: true,
|
|
1329
|
+
forceDelegation: true,
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1254
1332
|
if (!runtimeExecution && iterations === 0 && canDelegate && !state.retryWithoutTool
|
|
1255
1333
|
&& await classifyRequestedAction(llm, state.input, state.session._abortSignal)) {
|
|
1256
1334
|
state.session._onStreamReset?.();
|
|
@@ -1570,6 +1648,7 @@ export function createAgentGraph(options = {}) {
|
|
|
1570
1648
|
messages: toolResultMessages,
|
|
1571
1649
|
pendingToolCalls: null,
|
|
1572
1650
|
forceDelegation: false,
|
|
1651
|
+
retryWithoutTool: false,
|
|
1573
1652
|
terminalToolFailure: true,
|
|
1574
1653
|
invalidToolCallRetries: 0,
|
|
1575
1654
|
invalidResponseRetries: 0,
|
|
@@ -1579,6 +1658,7 @@ export function createAgentGraph(options = {}) {
|
|
|
1579
1658
|
messages: toolResultMessages,
|
|
1580
1659
|
pendingToolCalls: null,
|
|
1581
1660
|
forceDelegation: false,
|
|
1661
|
+
retryWithoutTool: false,
|
|
1582
1662
|
invalidToolCallRetries: 0,
|
|
1583
1663
|
invalidResponseRetries: 0,
|
|
1584
1664
|
terminalToolFailure: false,
|