@dotdrelle/wiki-manager 0.14.16 → 0.14.23
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 +46 -4
- package/README.md +116 -11
- package/agents.docker-compose.yml +41 -0
- package/docker-compose.yml +16 -1
- package/mcp.endpoints.example.json +3 -3
- package/package.json +1 -2
- package/src/activity/activityAggregator.js +7 -3
- package/src/activity/activityAggregator.test.js +16 -2
- package/src/agent/graph.js +91 -11
- package/src/agent/graph.test.js +108 -6
- package/src/cli/runtimeStartup.test.js +14 -0
- package/src/cli/wiki-manager.js +220 -37
- package/src/cli/wiki-manager.test.js +131 -1
- 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 +7 -2
- 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/core/workflow.js +72 -0
- package/src/core/workflow.test.js +57 -0
- package/src/orchestrator/dispatcher.js +1 -1
- package/src/orchestrator/dispatcher.test.js +48 -0
- package/src/orchestrator/scheduler.js +27 -7
- package/src/orchestrator/scheduler.test.js +23 -0
- package/src/runtime/client.js +23 -0
- package/src/runtime/runner.js +30 -3
- package/src/runtime/store.js +54 -0
- package/src/runtime/store.test.js +42 -0
- package/src/shell/LeftPane.tsx +20 -1
- package/src/shell/RightPane.tsx +22 -3
- package/src/shell/repl.js +86 -9
- package/src/shell/repl.test.js +103 -2
- package/src/shell/tui.tsx +6 -1
- package/src/shell/useAgent.ts +8 -12
- package/src/shell/useSession.ts +47 -0
- 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
|
|
|
@@ -74,10 +102,24 @@ DOCUMENTS_MCP_AUTH_TOKEN=
|
|
|
74
102
|
# WIKI_MANAGER_RUNTIME_HOST=0.0.0.0
|
|
75
103
|
|
|
76
104
|
|
|
77
|
-
#
|
|
78
|
-
#
|
|
79
|
-
#
|
|
80
|
-
#
|
|
105
|
+
# ── Parallelism & throughput ───────────────────────────────────────────────────
|
|
106
|
+
# Effective concurrency = MIN(agent recommendedConcurrency, agent maxConcurrency,
|
|
107
|
+
# this ceiling, per-task limits). So the PRIMARY levers live on the production
|
|
108
|
+
# agent (PRODUCTION_RECOMMENDED_CONCURRENCY / PRODUCTION_MAX_CONCURRENCY); this
|
|
109
|
+
# manager variable can only LOWER the result, never raise it. Full explanation
|
|
110
|
+
# and low/high profiles in docs/configuration.md § "Parallelism & throughput".
|
|
111
|
+
#
|
|
112
|
+
# Manager ceiling — leave unset to let the agent decide. Set to constrain:
|
|
113
|
+
# WIKI_MANAGER_CAPABILITY_CONCURRENCY=4
|
|
114
|
+
#
|
|
115
|
+
# Production agent capacity (passed through by docker-compose). Intermediate
|
|
116
|
+
# defaults are 4/8 (effective ≈ 4 parallel). Profiles:
|
|
117
|
+
# low → PRODUCTION_RECOMMENDED_CONCURRENCY=2 PRODUCTION_MAX_CONCURRENCY=4
|
|
118
|
+
# high → PRODUCTION_RECOMMENDED_CONCURRENCY=8 PRODUCTION_MAX_CONCURRENCY=16
|
|
119
|
+
# The wiki LLM backend must accept this many concurrent requests, and
|
|
120
|
+
# ingest_apply stays serialized regardless (global workspace-write lock).
|
|
121
|
+
# PRODUCTION_RECOMMENDED_CONCURRENCY=4
|
|
122
|
+
# PRODUCTION_MAX_CONCURRENCY=8
|
|
81
123
|
|
|
82
124
|
# ── MCP retry policy (optional) ────────────────────────────────────────────────
|
|
83
125
|
|
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
|
|
|
@@ -550,6 +549,25 @@ or the shell command `/approve item <id>`. The approval timeout defaults to 10
|
|
|
550
549
|
minutes and can be changed with `WIKI_MANAGER_APPROVAL_TIMEOUT_MS` or
|
|
551
550
|
`approvalTimeoutMs` in the `/run` body.
|
|
552
551
|
|
|
552
|
+
Directly-launched capability runs (ingest, pipeline) now **wait for approval by
|
|
553
|
+
default** before their mutating tasks: reply "valide tout", run `/approve`, or
|
|
554
|
+
click Approve in either UI (Shell right-pane banner, or the `serve` banner above
|
|
555
|
+
the composer). Auto-approval only happens when the run is started with
|
|
556
|
+
`autoApprove: true` (headless/CI).
|
|
557
|
+
|
|
558
|
+
### Parallelism & throughput
|
|
559
|
+
|
|
560
|
+
The number of tasks that run at once is `MIN(agent recommendedConcurrency, agent
|
|
561
|
+
maxConcurrency, WIKI_MANAGER_CAPABILITY_CONCURRENCY, per-task limits)` — a
|
|
562
|
+
minimum, so the manager ceiling can only lower it. The production agent ships
|
|
563
|
+
intermediate defaults (`PRODUCTION_RECOMMENDED_CONCURRENCY=4` /
|
|
564
|
+
`PRODUCTION_MAX_CONCURRENCY=8`, ≈ 4 parallel); locks then cap real parallelism
|
|
565
|
+
per phase (`ingest_apply` stays serial). The resolved value is shown in both
|
|
566
|
+
UIs' run summary and on the run node of the execution graph, with an amber
|
|
567
|
+
"(ceiling)" marker when the manager ceiling binds. Low/high profiles, the lock
|
|
568
|
+
model and the LLM-backend caveat are in
|
|
569
|
+
[docs/configuration.md § "Parallelism & throughput"](docs/configuration.md).
|
|
570
|
+
|
|
553
571
|
While a run is active, `GET`/`POST /control` still answers without waiting for
|
|
554
572
|
it to finish: `{"action":"status"}` returns the current run/plan/queue state,
|
|
555
573
|
`{"action":"explain"}` adds a one-line plain-language summary, and
|
|
@@ -580,18 +598,104 @@ generates the missing agent auth tokens into your manager `.env` and seeds
|
|
|
580
598
|
automatically from the manager workspaces directory. Agent state is stored under
|
|
581
599
|
`./.agents-data/` unless `AGENTS_DATA_DIR` is set.
|
|
582
600
|
|
|
583
|
-
|
|
601
|
+
An `npm -g update @dotdrelle/wiki-manager` replaces the packaged Compose files
|
|
602
|
+
but preserves the operator-owned `.env`, `mcp.endpoints.json`, workspaces, agent
|
|
603
|
+
data, and runtime database. Missing standard endpoint definitions are migrated
|
|
604
|
+
additively; existing endpoint definitions are never overwritten.
|
|
584
605
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
606
|
+
The Gmail connector agent is packaged but opt-in. Enable it in the manager
|
|
607
|
+
`.env`:
|
|
608
|
+
|
|
609
|
+
```dotenv
|
|
610
|
+
CONNECTORS_ENABLED=true
|
|
611
|
+
# Optional locally; generated from CONNECTORS_MCP_PORT when empty:
|
|
612
|
+
GOOGLE_OAUTH_CALLBACK_URL=
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
For a local installation, `wiki-workspace agents up` fills an empty callback
|
|
616
|
+
with:
|
|
617
|
+
|
|
618
|
+
```text
|
|
619
|
+
http://127.0.0.1:<CONNECTORS_MCP_PORT>/oauth/google/callback
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
With the default port, register this exact redirect URI in Google Cloud
|
|
623
|
+
Console:
|
|
624
|
+
|
|
625
|
+
```text
|
|
626
|
+
http://127.0.0.1:3338/oauth/google/callback
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
The browser resolves `127.0.0.1`; Docker forwards the published host port to
|
|
630
|
+
the connectors container. For a remote deployment, set an explicit public
|
|
631
|
+
HTTPS callback instead. In both cases the configured URL must match the Google
|
|
632
|
+
Cloud redirect URI exactly.
|
|
633
|
+
|
|
634
|
+
The local flow uses the public wikiLLM Desktop OAuth Client ID with PKCE and
|
|
635
|
+
does not require a Client Secret. Normal users set neither Google credential.
|
|
636
|
+
`GOOGLE_OAUTH_CLIENT_ID` remains an advanced override for private/internal
|
|
637
|
+
Google projects, and `GOOGLE_OAUTH_CLIENT_SECRET` is an optional compatibility
|
|
638
|
+
override for administrators using a confidential web client.
|
|
639
|
+
|
|
640
|
+
`agents up` also generates the connectors MCP token plus distinct OAuth
|
|
641
|
+
start/state secrets when missing. It adds a regular, standard MCP `connectors`
|
|
642
|
+
entry to `mcp.endpoints.json`. Setting `CONNECTORS_ENABLED=false` and running
|
|
643
|
+
`agents up` removes that entry again, so disabled services are not probed. No
|
|
644
|
+
non-standard `enabled` property is written to MCP configuration files.
|
|
645
|
+
The matching `chatAccess.connectors` policy is managed at the same time. Its
|
|
646
|
+
read-only `allow` list exposes `connectors_google_status`, while the explicit
|
|
647
|
+
`allowActions` list exposes only `connectors_google_oauth_start`. Orchestration
|
|
648
|
+
tools such as `agent_execute` are never exposed directly to served chat.
|
|
649
|
+
|
|
650
|
+
Connector authorization is also available without asking the LLM. These two
|
|
651
|
+
commands work in both the Shell UI and the `llm-wiki serve` chat:
|
|
652
|
+
|
|
653
|
+
```text
|
|
654
|
+
/connector list
|
|
655
|
+
/connector auth google
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
The first reports the Gmail read-only authorization state for the active
|
|
659
|
+
workspace. The second opens Google's OAuth page in the browser. Asking Donna
|
|
660
|
+
to configure or check Google remains supported through the direct connector
|
|
661
|
+
tools above.
|
|
662
|
+
|
|
663
|
+
The Compose profile is an internal implementation detail. Do not set
|
|
664
|
+
`COMPOSE_PROFILES` and do not add provider names such as Gmail or Slack to it:
|
|
665
|
+
one `agent-connectors` service hosts all connector providers.
|
|
666
|
+
|
|
667
|
+
For a public serve deployment, authorization can be started through the
|
|
668
|
+
same-origin proxy:
|
|
589
669
|
|
|
590
670
|
```bash
|
|
591
|
-
|
|
592
|
-
|
|
671
|
+
curl -X POST https://wiki.example.com/api/connectors/google/oauth/start \
|
|
672
|
+
-H 'Origin: https://wiki.example.com' \
|
|
673
|
+
-H 'X-LLM-WIKI-OAUTH: 1' \
|
|
674
|
+
-H 'Content-Type: application/json' \
|
|
675
|
+
-d '{"instanceId":"google-1"}'
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
Open the returned `authorizationUrl`. The workspace is injected by serve and
|
|
679
|
+
cannot be selected by the browser request.
|
|
680
|
+
|
|
681
|
+
Donna discovers the agent contract automatically from the `connectors` MCP
|
|
682
|
+
endpoint. With only one provider, no routing entry is required. To pin it
|
|
683
|
+
explicitly in a workspace profile:
|
|
684
|
+
|
|
685
|
+
```yaml
|
|
686
|
+
capabilityRouting:
|
|
687
|
+
external-source.collect:
|
|
688
|
+
preferredAgents: [connectors]
|
|
689
|
+
allowedAgents: [connectors]
|
|
593
690
|
```
|
|
594
691
|
|
|
692
|
+
#### Optional agents and user overrides
|
|
693
|
+
|
|
694
|
+
Anything beyond the packaged stack is an external connector operated and
|
|
695
|
+
configured independently by the user. To run one alongside the packaged
|
|
696
|
+
agents, create a file named
|
|
697
|
+
`agents.docker-compose.override.yml` **next to your `.env`**:
|
|
698
|
+
|
|
595
699
|
`agents up` includes it automatically when present (standard Docker Compose
|
|
596
700
|
merge: new services are added, same-name keys override the defaults — you can
|
|
597
701
|
also use it to pin a port or a variable of a default agent). The file is
|
|
@@ -599,7 +703,8 @@ yours: wiki-manager never generates or overwrites it. Complete the setup by
|
|
|
599
703
|
adding the connector's variables to your `.env` and its endpoint block to
|
|
600
704
|
your `mcp.endpoints.json` — every variable an external MCP endpoint needs
|
|
601
705
|
lives in the `.env` and is referenced as `${VAR_NAME}` from
|
|
602
|
-
`mcp.endpoints.json`.
|
|
706
|
+
`mcp.endpoints.json`. A connector running outside the manager Compose stack
|
|
707
|
+
only needs an entry in `mcp.endpoints.json`.
|
|
603
708
|
|
|
604
709
|
Workspace-native MCP servers (`llm-wiki`, `production`) stay configured through
|
|
605
710
|
each workspace `.env`. External agents are workspace-agnostic: the active
|
|
@@ -1010,7 +1115,7 @@ llm-wiki-manager/
|
|
|
1010
1115
|
│ ├── useAgent.ts # agent call wrapper (drives the @langchain/langgraph run)
|
|
1011
1116
|
│ └── renderer.ts # markdown stripping and line coloring
|
|
1012
1117
|
├── docker-compose.yml # workspace-scoped stack (serve, mcp-http, production-mcp)
|
|
1013
|
-
├── agents.docker-compose.yml # global external agents
|
|
1118
|
+
├── agents.docker-compose.yml # packaged global external agents
|
|
1014
1119
|
├── wiki-workspace
|
|
1015
1120
|
├── .env.example # template for local .env (WORKSPACES_ROOT, agent tokens, …)
|
|
1016
1121
|
├── 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
|
|
@@ -110,6 +120,11 @@ services:
|
|
|
110
120
|
- WIKI_CONFIG_PATH=${WIKI_CONFIG_PATH:-}
|
|
111
121
|
- PRODUCTION_ALLOWED_STEPS=${PRODUCTION_ALLOWED_STEPS:-doctor,ingest,ingest_plan,ingest_apply,build,export,polish,pipeline}
|
|
112
122
|
- PRODUCTION_REQUIRE_CONFIRMATION=${PRODUCTION_REQUIRE_CONFIRMATION:-false}
|
|
123
|
+
# Parallelism levers — effective concurrency ≈ recommendedConcurrency.
|
|
124
|
+
# Intermediate defaults (4/8). Low profile 2/4, high profile 8/16.
|
|
125
|
+
# See docs/configuration.md § "Parallelism & throughput".
|
|
126
|
+
- PRODUCTION_RECOMMENDED_CONCURRENCY=${PRODUCTION_RECOMMENDED_CONCURRENCY:-4}
|
|
127
|
+
- PRODUCTION_MAX_CONCURRENCY=${PRODUCTION_MAX_CONCURRENCY:-8}
|
|
113
128
|
- PRODUCTION_JOBS_DIR=${PRODUCTION_JOBS_DIR:-/workspace/.wiki/production-jobs}
|
|
114
129
|
- PRODUCTION_LOCKS_DIR=${PRODUCTION_LOCKS_DIR:-/workspace/.wiki/production-jobs/locks}
|
|
115
130
|
ports:
|
|
@@ -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.14.
|
|
3
|
+
"version": "0.14.23",
|
|
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",
|
|
@@ -93,17 +93,21 @@ function groupLine(group, activities) {
|
|
|
93
93
|
// still progressing. Show the live worker as running; surface the group
|
|
94
94
|
// failure once no work remains. Otherwise its business label was rendered
|
|
95
95
|
// red even though that exact task was healthy and advancing.
|
|
96
|
+
// Glyphs mirror the Shell PlanPanel so the two never disagree: done is a
|
|
97
|
+
// check (not a cross — "[x]" read as a failure X), failure is a distinct
|
|
98
|
+
// cross, and an approval wait gets its own pause glyph instead of reusing the
|
|
99
|
+
// failure "[!]".
|
|
96
100
|
if (running.length > 0 || activeActivity) {
|
|
97
101
|
icon = '[...]';
|
|
98
102
|
status = activeProgress != null ? `${Math.round(activeProgress)} %` : `${done}/${total}`;
|
|
99
103
|
} else if (failed) {
|
|
100
|
-
icon = '[
|
|
104
|
+
icon = '[✗]';
|
|
101
105
|
status = 'error';
|
|
102
106
|
} else if (done === total) {
|
|
103
|
-
icon = '[
|
|
107
|
+
icon = '[✓]';
|
|
104
108
|
status = 'done';
|
|
105
109
|
} else if (waitingApproval) {
|
|
106
|
-
icon = '[
|
|
110
|
+
icon = '[⏸]';
|
|
107
111
|
status = 'validation';
|
|
108
112
|
} else if (done > 0) {
|
|
109
113
|
icon = '[...]';
|
|
@@ -5,6 +5,20 @@ import { aggregateActivity } from './activityAggregator.js';
|
|
|
5
5
|
import { visibleActivityEvents } from './activityDeduplicator.js';
|
|
6
6
|
import { calculateWeightedProgress } from './progressCalculator.js';
|
|
7
7
|
|
|
8
|
+
test('a group awaiting approval renders a distinct pause glyph, not a failure', () => {
|
|
9
|
+
const aggregated = aggregateActivity({
|
|
10
|
+
plan: [
|
|
11
|
+
{ id: 'publish', label: 'Publication', groupId: 'publish', status: 'pending_approval', progressWeight: 1 },
|
|
12
|
+
],
|
|
13
|
+
activities: [],
|
|
14
|
+
});
|
|
15
|
+
const line = aggregated.lines.find((item) => /publish|Publication/.test(item.label));
|
|
16
|
+
assert.ok(line, 'the awaiting-approval group is present');
|
|
17
|
+
assert.match(line.label, /\[⏸\]/, 'uses the pause glyph');
|
|
18
|
+
assert.doesNotMatch(line.label, /\[!\]|\[✗\]/, 'never reuses the failure glyph');
|
|
19
|
+
assert.equal(line.status, 'validation');
|
|
20
|
+
});
|
|
21
|
+
|
|
8
22
|
test('activityDeduplicator keeps one visible entry for repeated 2 percent polls', () => {
|
|
9
23
|
const events = Array.from({ length: 50 }, () => ({
|
|
10
24
|
type: 'activity_upserted',
|
|
@@ -54,7 +68,7 @@ test('aggregateActivity exposes initial synthesis and grouped display lines', ()
|
|
|
54
68
|
|
|
55
69
|
assert.deepEqual(activity.initialSynthesis, ['120 sources detectees', '6 traitements simultanes recommandes']);
|
|
56
70
|
assert.equal(activity.progress.percent, 54);
|
|
57
|
-
assert.ok(activity.lines.some((line) => /\[
|
|
71
|
+
assert.ok(activity.lines.some((line) => /\[✓\] collect - done/.test(line.label)));
|
|
58
72
|
assert.ok(activity.lines.some((line) => /\[\.\.\.\] customer-data\.enrich - 63 %/.test(line.label)));
|
|
59
73
|
const enrichLine = activity.lines.find((line) => /customer-data\.enrich/.test(line.label));
|
|
60
74
|
assert.equal(enrichLine.progress.label, 'Export rapport.md');
|
|
@@ -92,7 +106,7 @@ test('aggregateActivity keeps activities not attached to any plan task visible',
|
|
|
92
106
|
|
|
93
107
|
const aggregated = aggregateActivity(state, []);
|
|
94
108
|
const labels = aggregated.lines.map((line) => line.label).join('\n');
|
|
95
|
-
assert.match(labels, /\[
|
|
109
|
+
assert.match(labels, /\[✓\] .* done/, 'the done plan group stays visible');
|
|
96
110
|
assert.match(labels, /Ingest b87acaf6/, 'the unattached running ingest must appear');
|
|
97
111
|
const ingestLine = aggregated.lines.find((line) => /Ingest/.test(line.label));
|
|
98
112
|
assert.equal(ingestLine.status, 'running');
|
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.',
|
|
@@ -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,
|