@dotdrelle/wiki-manager 0.15.48 → 0.15.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -637,9 +637,15 @@ with `"replans": 1` in the `/run` body.
637
637
  Runtime approvals are bounded to a run, plan revision and approval class.
638
638
  Mutating orchestrated tasks **wait for approval by default**, including tasks
639
639
  created by a skill or a directly selected capability such as ingest or
640
- pipeline. Approve them by replying "valide tout", running `/approve`, or
641
- clicking Approve in either UI (Shell right-pane banner, or the `serve` banner
642
- above the composer). `POST /approve` also accepts an explicit run scope.
640
+ pipeline. Approve them by running `/approve`, or clicking Approve in either UI:
641
+ the Shell right-pane banner, or the `serve` banner.
642
+
643
+ In `serve` that banner is a **fixed overlay, visible in every centre view**. It
644
+ used to sit inside the composer, which the layout hides in the wiki, connectors
645
+ and execution views — so a restore launched from `/history` waited on an
646
+ approval nobody could see, and the Execution view, the one meant for monitoring,
647
+ could not show it either. The Shell never had that gap because its plan pane is
648
+ always on screen. `POST /approve` also accepts an explicit run scope.
643
649
 
644
650
  An explicitly launched skill is also approval-gated. For an orchestrated skill,
645
651
  the scheduler blocks each uncovered mutating task; for a `direct` skill, the
@@ -907,10 +913,35 @@ The shared `docker-compose.yml` starts one workspace stack:
907
913
  Use `wiki-workspace` whenever possible so Compose receives the right project
908
914
  name, env file, ports, and volume mounts.
909
915
 
916
+ `PRODUCTION_ALLOWED_STEPS` gates what `production-mcp` will accept, and an
917
+ omission from it is **silent**: `agent_plan` simply leaves the step's task out of
918
+ the fragment instead of failing. `taxonomy` was missing from the shipped default
919
+ for several releases, so every compose-deployed ingest ran without the taxonomy
920
+ barrier and left the published map stale. Keep the variable in step with the
921
+ in-code default of `production_mcp_server.py`: a test here asserts `taxonomy` is
922
+ present, and one in `agent-wiki-production` compares the whole list against that
923
+ in-code reference. Remember that an explicit value in your `.env` overrides the
924
+ default entirely.
925
+
910
926
  Runtime split: the host manager/runtime uses Node.js 22+ for `node:sqlite`; the
911
927
  interactive OpenTUI shell uses Bun 1.2+; workspace Docker services run from the
912
928
  published images and do not depend on host `node_modules`.
913
929
 
930
+ Two consequences worth knowing before debugging anything:
931
+
932
+ - **The runtime is not a container.** `runtime/lifecycle.js` spawns it locally,
933
+ detached, from the manager sources — no Compose file declares it. Changing
934
+ runtime or shell code therefore needs a **restart**, never an image rebuild;
935
+ changing `llm-wiki` or an agent needs the image rebuilt.
936
+ - **The runtime starts before the workspace containers.** Its first agent
937
+ discovery legitimately finds them absent. `agentRegistry` keeps a known
938
+ agent's capabilities when a probe fails — it only refreshes `lastSeenAt`, and
939
+ says so in the runtime log — and the periodic re-scan re-probes the MCP
940
+ endpoints instead of reusing a cached status. Without both, a capability the
941
+ agent really has stayed missing from the registry until the next successful
942
+ discovery, and the only symptom was a run failing much later with
943
+ `No agent provides capability …`.
944
+
914
945
  As of 0.11.4, the host runtime store carries a minimal format guard:
915
946
  `PRAGMA user_version = 1` in SQLite plus `.wiki/meta.json` with
916
947
  `schemaVersion: 1`. Unknown future versions stop startup with a clear error.
@@ -124,7 +124,13 @@ services:
124
124
  - WORKSPACE_NAME=${WORKSPACE_NAME:-workspace}
125
125
  - WIKI_WORKSPACE_PATH=/workspace
126
126
  - WIKI_CONFIG_PATH=${WIKI_CONFIG_PATH:-}
127
- - PRODUCTION_ALLOWED_STEPS=${PRODUCTION_ALLOWED_STEPS:-doctor,ingest,ingest_plan,ingest_apply,build,export,polish,restore,pipeline}
127
+ # `taxonomy` belongs here, and its absence is SILENT: agent_plan drops the
128
+ # taxonomy task from an ingest fragment when the step is not allowed
129
+ # (production_mcp_server.py, "taxonomy" in _ALLOWED_STEPS), without an
130
+ # error. Every compose-deployed ingest then ran without the Lot 4 barrier
131
+ # and left the published map stale — the very defect that work fixed.
132
+ # `copy` stays out on purpose: it is the legacy step, opt-in only.
133
+ - PRODUCTION_ALLOWED_STEPS=${PRODUCTION_ALLOWED_STEPS:-doctor,ingest,ingest_plan,ingest_apply,taxonomy,build,export,polish,restore,pipeline}
128
134
  - PRODUCTION_REQUIRE_CONFIRMATION=${PRODUCTION_REQUIRE_CONFIRMATION:-false}
129
135
  # Parallelism levers — effective concurrency ≈ recommendedConcurrency.
130
136
  # Intermediate defaults (4/8). Low profile 2/4, high profile 8/16.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotdrelle/wiki-manager",
3
- "version": "0.15.48",
3
+ "version": "0.15.50",
4
4
  "description": "Agentic shell and orchestration cockpit for llm-wiki workspaces.",
5
5
  "license": "PolyForm-Noncommercial-1.0.0",
6
6
  "author": "dotrelle",
@@ -11,7 +11,7 @@
11
11
  },
12
12
  "scripts": {
13
13
  "start": "bun ./bin/wiki-manager.js",
14
- "test": "node --test src/core/skillInvocation.test.js src/core/skillCompiler.test.js src/runtime/skillRun.test.js src/runtime/controlDrain.test.js src/runtime/controlCancellation.test.js src/cli/runtimeStartup.test.js src/cli/wiki-manager.test.js src/agent/graph.test.js src/agent/skillRecursion.test.js src/contracts/schemas.test.js src/core/activity.test.js src/core/env.test.js src/core/agentsCompose.test.js src/core/profileServiceStatus.test.js src/core/workspaceProfile.test.js src/core/buildInfo.test.js src/core/agentEvents.test.js src/core/skillChainView.test.js src/core/runtimeLog.test.js src/activity/activityAggregator.test.js src/graph/runGraphProjector.test.js src/core/workflow.test.js src/core/planPatch.test.js src/core/agentLoop.test.js src/core/plan.test.js src/core/mcp.test.js src/core/toolLoop.test.js src/core/documentIntake.test.js src/core/dockerCompose.test.js src/core/otherWorkspacesRunning.test.js src/core/wikiSetup.test.js src/core/wikiWorkspace.test.js src/core/wikirc.test.js src/core/workspaceInherit.test.js src/core/cacert.test.js src/core/composeOverrides.test.js src/core/setEnvValue.test.js src/core/commandFailure.test.js src/core/googleGrants.test.js src/core/modelFetch.test.js src/core/startupCheck.test.js src/core/queueStore.test.js src/orchestrator/agentRegistry.test.js src/orchestrator/capabilityRegistry.test.js src/orchestrator/capabilityResolver.test.js src/orchestrator/planValidator.test.js src/orchestrator/planIntegrator.test.js src/orchestrator/taskStatuses.test.js src/orchestrator/scheduler.test.js src/orchestrator/attemptManager.test.js src/orchestrator/resultAggregator.test.js src/orchestrator/approvalPolicy.test.js src/orchestrator/dispatcher.test.js src/orchestrator/objectiveResolver.test.js src/commands/slash.test.js src/shell/repl.test.js src/shell/setupWizardModality.test.js src/shell/setupWizardPlaceholders.test.js src/shell/setupWizardSuggestions.test.js src/shell/setupWizardDiscovery.test.js src/shell/wrapText.test.js src/runtime/lifecycle.test.js src/runtime/store.test.js src/runtime/workspaceIsolation.test.js src/runtime/controlMessages.test.js src/runtime/recoveryManager.test.js src/runtime/server.test.js src/runtime/supervisor.test.js src/runtime/delegation.test.js src/runtime/runner.test.js src/runtime/runner.e2e.test.js src/runtime/skillChain.e2e.test.js src/runtime/donna-contract.test.js src/runtime/auth.test.js",
14
+ "test": "node --test src/core/skillInvocation.test.js src/core/skillCompiler.test.js src/runtime/skillRun.test.js src/runtime/controlDrain.test.js src/runtime/controlCancellation.test.js src/cli/runtimeStartup.test.js src/cli/wiki-manager.test.js src/agent/graph.test.js src/agent/skillRecursion.test.js src/contracts/schemas.test.js src/core/activity.test.js src/core/env.test.js src/core/agentsCompose.test.js src/core/profileServiceStatus.test.js src/core/workspaceProfile.test.js src/core/buildInfo.test.js src/core/agentEvents.test.js src/core/skillChainView.test.js src/core/runtimeLog.test.js src/activity/activityAggregator.test.js src/graph/runGraphProjector.test.js src/core/workflow.test.js src/core/planPatch.test.js src/core/agentLoop.test.js src/core/plan.test.js src/core/mcp.test.js src/core/toolLoop.test.js src/core/documentIntake.test.js src/core/dockerCompose.test.js src/core/otherWorkspacesRunning.test.js src/core/wikiSetup.test.js src/core/wikiWorkspace.test.js src/core/wikirc.test.js src/core/workspaceInherit.test.js src/core/cacert.test.js src/core/composeOverrides.test.js src/core/setEnvValue.test.js src/core/commandFailure.test.js src/core/googleGrants.test.js src/core/modelFetch.test.js src/core/startupCheck.test.js src/core/queueStore.test.js src/orchestrator/agentRegistry.test.js src/orchestrator/capabilityRegistry.test.js src/orchestrator/capabilityResolver.test.js src/orchestrator/planValidator.test.js src/orchestrator/planIntegrator.test.js src/orchestrator/taskStatuses.test.js src/orchestrator/scheduler.test.js src/orchestrator/attemptManager.test.js src/orchestrator/resultAggregator.test.js src/orchestrator/approvalPolicy.test.js src/orchestrator/dispatcher.test.js src/orchestrator/objectiveResolver.test.js src/commands/slash.test.js src/shell/repl.test.js src/shell/setupWizardModality.test.js src/shell/setupWizardPlaceholders.test.js src/shell/setupWizardSuggestions.test.js src/shell/setupWizardDiscovery.test.js src/shell/wrapText.test.js src/runtime/lifecycle.test.js src/runtime/store.test.js src/runtime/workspaceIsolation.test.js src/runtime/controlMessages.test.js src/runtime/recoveryManager.test.js src/runtime/server.test.js src/runtime/supervisor.test.js src/runtime/delegation.test.js src/runtime/runner.test.js src/runtime/runner.e2e.test.js src/runtime/skillChain.e2e.test.js src/runtime/donna-contract.test.js src/runtime/approvals.test.js src/runtime/auth.test.js",
15
15
  "check-versions": "node scripts/check-versions.js",
16
16
  "prepack": "node scripts/check-versions.js",
17
17
  "prepublishOnly": "node scripts/check-versions.js",
@@ -26,7 +26,7 @@ import { createAgentEvent, dispatchAgentEvent } from '../core/agentEvents.js';
26
26
  import { enqueueProductionJob, ensureJobQueue, formatQueue, productionLockBusy } from '../core/jobQueue.js';
27
27
  import { loadWorkspaceProfile, updateWorkspaceProfilePreference } from '../core/profile.js';
28
28
  import { capabilityRegistryForSession } from '../orchestrator/capabilityRegistry.js';
29
- import { fetchRuntimeState, postRuntimeApprove, postRuntimeCancel, postRuntimeControl, postRuntimeDelegate, postRuntimeKill, postRuntimeSkill } from '../runtime/client.js';
29
+ import { fetchRuntimeState, postRuntimeCancel, postRuntimeControl, postRuntimeDelegate, postRuntimeKill, postRuntimeSkill } from '../runtime/client.js';
30
30
  import { controlLanguage } from '../runtime/controlMessages.js';
31
31
 
32
32
  const MAX_TOOL_ITERATIONS = 80;
@@ -46,7 +46,7 @@ const MAX_SPINNER_ARG_LENGTH = 96;
46
46
  const INTERNAL_TOOL_SERVERS = {
47
47
  wiki: ['plan_set', 'plan_done'],
48
48
  shell: ['run_command', 'read_command', 'profile_update'],
49
- runtime: ['kill', 'cancel', 'status', 'approve', 'enqueue', 'delegate', 'run_skill'],
49
+ runtime: ['kill', 'cancel', 'status', 'enqueue', 'delegate', 'run_skill'],
50
50
  };
51
51
 
52
52
  const AGENT_SLASH_COMMANDS = new Set([
@@ -164,15 +164,6 @@ const RUNTIME_STATUS_TOOL = {
164
164
  },
165
165
  };
166
166
 
167
- const RUNTIME_APPROVE_TOOL = {
168
- type: 'function',
169
- function: {
170
- name: 'runtime__approve',
171
- description: 'Grant the pending approval of the active runtime run (mutating tasks wait on it). Use when the user consents in ANY phrasing: "vas-y", "ok pour l\'export", "approuve", "valide". Confirm what was approved.',
172
- parameters: { type: 'object', additionalProperties: false, properties: {} },
173
- },
174
- };
175
-
176
167
  const RUNTIME_ENQUEUE_TOOL = {
177
168
  type: 'function',
178
169
  function: {
@@ -364,7 +355,6 @@ function toolDefinitionForCall(session, callName) {
364
355
  RUNTIME_STATUS_TOOL,
365
356
  RUNTIME_CANCEL_TOOL,
366
357
  RUNTIME_KILL_TOOL,
367
- RUNTIME_APPROVE_TOOL,
368
358
  RUNTIME_ENQUEUE_TOOL,
369
359
  RUNTIME_DELEGATE_TOOL,
370
360
  RUNTIME_RUN_SKILL_TOOL,
@@ -852,28 +842,6 @@ export async function handleRuntimeControlTool(session, tool, args = {}) {
852
842
  const result = await postRuntimeCancel({ url, workspace });
853
843
  return result.cancelled ? 'Runtime run cancellation requested.' : `No active run to cancel${result.reason ? ` (${result.reason})` : ''}.`;
854
844
  }
855
- if (tool === 'approve') {
856
- const state = await fetchRuntimeState({ url, workspace });
857
- const pending = (Array.isArray(state?.approvals) ? state.approvals : [])
858
- .filter((approval) => approval.status === 'pending_approval');
859
- const runId = state?.runId
860
- ?? state?.runs?.find((run) => ['running', 'pending_approval'].includes(run.status))?.id
861
- ?? null;
862
- if (!runId || pending.length === 0) return 'No pending approval found.';
863
- const approvalClasses = [...new Set(pending.flatMap((approval) => {
864
- const value = approval.approvalClasses ?? approval.approvalClass ?? [];
865
- return Array.isArray(value) ? value : [value];
866
- }).map(String).filter(Boolean))];
867
- const result = await postRuntimeApprove({
868
- url,
869
- workspace,
870
- runId,
871
- scope: 'run',
872
- planRevision: state?.planRevision ?? null,
873
- approvalClasses: approvalClasses.length > 0 ? approvalClasses : ['default'],
874
- });
875
- return result?.approved ? 'Current validated plan approved.' : 'No pending approval found.';
876
- }
877
845
  if (tool === 'delegate') {
878
846
  const objective = String(args.objective ?? '').trim();
879
847
  if (!objective) return 'Delegation rejected: missing objective.';
@@ -1250,7 +1218,7 @@ export function buildAgentSystemPrompt(state) {
1250
1218
  workspaceProfile
1251
1219
  ? `Workspace profile (.wiki/profile.md) — durable user preferences, apply these to every reply (tone, tutoiement/vouvoiement, formatting, etc.):\n${workspaceProfile}`
1252
1220
  : null,
1253
- 'Runtime control: you have runtime__status, runtime__cancel, runtime__kill, runtime__approve and runtime__enqueue. When the user asks to stop, remove, clean or kill the current run, its jobs or the queue ("supprime le job et la queue", "arr\u00eate tout"), call runtime__kill (or runtime__cancel for a soft stop of just the run) and confirm what was stopped. When the user explicitly asks to delete, reset, abandon or replace the current plan, call runtime__kill with purge=true; never set purge=true for a simple stop. For questions about what is running or queued, call runtime__status and answer from its data. When the user consents to a pending approval in any phrasing ("vas-y", "ok pour l\'export"), call runtime__approve. When the user asks for a NEW action while a run is active, do not execute it: propose runtime__enqueue (run it after) or, if they insist it replaces the current work, runtime__kill then the new action.',
1221
+ 'Runtime control: you have runtime__status, runtime__cancel, runtime__kill and runtime__enqueue. When the user asks to stop, remove, clean or kill the current run, its jobs or the queue ("supprime le job et la queue", "arr\u00eate tout"), call runtime__kill (or runtime__cancel for a soft stop of just the run) and confirm what was stopped. When the user explicitly asks to delete, reset, abandon or replace the current plan, call runtime__kill with purge=true; never set purge=true for a simple stop. For questions about what is running or queued, call runtime__status and answer from its data. You have no approval tool: a pending approval is granted only by the user through the approval button or the /approve command. Never grant, claim or report an approval yourself; when the user asks to proceed with pending mutations, tell them to use those controls. When the user asks for a NEW action while a run is active, do not execute it: propose runtime__enqueue (run it after) or, if they insist it replaces the current work, runtime__kill then the new action.',
1254
1222
  'When the user asks to refresh, show, or update the displayed plan or status, call runtime__status. This is a state refresh request, not a new business capability, and must never be delegated.',
1255
1223
  'Report every runtime control outcome exactly as the tool returned it \u2014 never embellish. If runtime__kill reports 0 run(s)/0 task(s)/0 purged, say there was nothing active to stop or purge; do NOT claim a run, plan, pending approval or queue item was removed. If runtime__status returns an error or could not be read, say the runtime state could not be retrieved and do not describe a state you never obtained. Never assert that something was cleaned, cancelled, approved or purged unless that specific tool result confirms it.',
1256
1224
  'Durable profile updates are actions in this stabilized version: delegate them instead of writing directly.',
@@ -1285,7 +1253,7 @@ export function formatLlmUnavailableMessage(reason) {
1285
1253
 
1286
1254
  function toolsForClassification(classification, writeTools, session = null) {
1287
1255
  const controlTools = session?.runtime?.url
1288
- ? [RUNTIME_STATUS_TOOL, RUNTIME_CANCEL_TOOL, RUNTIME_KILL_TOOL, RUNTIME_APPROVE_TOOL, RUNTIME_ENQUEUE_TOOL]
1256
+ ? [RUNTIME_STATUS_TOOL, RUNTIME_CANCEL_TOOL, RUNTIME_KILL_TOOL, RUNTIME_ENQUEUE_TOOL]
1289
1257
  : [];
1290
1258
  // Provider discovery and validation belong to the runtime. Hiding
1291
1259
  // delegation while the shell snapshot is temporarily empty forced Donna
@@ -782,67 +782,25 @@ test('runtime status does not manufacture a plan', async () => {
782
782
  }
783
783
  });
784
784
 
785
- test('one Donna approval grants the complete validated run revision', async () => {
786
- const originalFetch = globalThis.fetch;
787
- const requests = [];
788
- globalThis.fetch = async (url, options = {}) => {
789
- requests.push({ url: String(url), method: options.method ?? 'GET', body: options.body ? JSON.parse(options.body) : null });
790
- if ((options.method ?? 'GET') === 'GET') {
791
- return {
792
- ok: true,
793
- status: 200,
794
- json: async () => ({
795
- running: true,
796
- runId: 'run-approval',
797
- planRevision: 3,
798
- approvals: [
799
- { status: 'pending_approval', approvalClasses: ['workspace'] },
800
- { status: 'pending_approval', approvalClasses: ['workspace'] },
801
- ],
802
- }),
803
- };
804
- }
805
- return { ok: true, status: 202, json: async () => ({ approved: true, runId: 'run-approval' }) };
806
- };
807
- let calls = 0;
785
+ test('Donna has no self-approval tool during an active run', async () => {
786
+ // Donna must never grant her own pending approval (the recurring "spontaneous
787
+ // approval" regression). Approval is the user's action, expressed through the
788
+ // banner button or /approve — never through an LLM tool call.
789
+ let seenTools = [];
808
790
  const session = sessionBase({
809
791
  runtime: { url: 'http://runtime.test' },
810
792
  agentProjection: { status: 'running', conversation: [], activities: [] },
811
793
  llm: {
812
- async completeWithTools() {
813
- calls += 1;
814
- if (calls === 1) {
815
- return {
816
- content: null,
817
- message: { role: 'assistant', content: null },
818
- tool_calls: [{
819
- id: 'approve-run',
820
- type: 'function',
821
- function: { name: 'runtime__approve', arguments: '{}' },
822
- }],
823
- };
824
- }
825
- return { content: 'Plan approuvé.', message: { role: 'assistant', content: 'Plan approuvé.' }, tool_calls: null };
794
+ async completeWithTools({ tools }) {
795
+ seenTools = tools.map((tool) => tool.function.name);
796
+ return { content: 'ok', message: { role: 'assistant', content: 'ok' }, tool_calls: null };
826
797
  },
827
798
  },
828
799
  });
829
800
 
830
- try {
831
- const result = await createAgentGraph().invoke({ input: 'oui', session });
832
- assert.equal(result.response, 'Plan approuvé.');
833
- const approval = requests.find((request) => request.url.includes('/approve'));
834
- assert.deepEqual(approval.body, {
835
- workspace: 'docs',
836
- runId: 'run-approval',
837
- itemId: null,
838
- approvalId: null,
839
- scope: 'run',
840
- planRevision: 3,
841
- approvalClasses: ['workspace'],
842
- });
843
- } finally {
844
- globalThis.fetch = originalFetch;
845
- }
801
+ await createAgentGraph().invoke({ input: 'oui', session });
802
+ assert.ok(seenTools.includes('runtime__status'), 'control tools still bound during an active run');
803
+ assert.ok(!seenTools.includes('runtime__approve'), 'no runtime__approve tool for Donna');
846
804
  });
847
805
 
848
806
  test('agent graph binds the full toolset for a "remember my preference" request, not just read-only tools', async () => {
@@ -1773,7 +1731,7 @@ test('agent graph lets Donna handle ambiguous input during a run with the contro
1773
1731
  assert.match(result.response, /mettre en file/);
1774
1732
  assert.ok(seenTools.includes('runtime__enqueue'));
1775
1733
  assert.ok(seenTools.includes('runtime__status'));
1776
- assert.ok(seenTools.includes('runtime__approve'));
1734
+ assert.ok(!seenTools.includes('runtime__approve'), 'no self-approval tool for Donna');
1777
1735
  assert.ok(!seenTools.includes('production__production_start_job'), 'no write MCP tools during an active run for ambiguous intents');
1778
1736
  });
1779
1737
 
@@ -567,6 +567,7 @@ async function waitForRuntimeRun(session, log, { timeoutMs, pollMs = 1500, autoA
567
567
  scope: 'run',
568
568
  planRevision,
569
569
  approvalClasses: approvalClasses.length > 0 ? approvalClasses : ['default'],
570
+ caller: 'headless-auto-approve',
570
571
  });
571
572
  const line = `runtime-wait: auto-approved run ${currentRun.id} (revision ${planRevision})${result?.approved ? '' : ' [no pending approval matched]'}`;
572
573
  log.push(line); console.log(line);
@@ -845,6 +846,7 @@ export async function waitForRuntimeChain(session, log, {
845
846
  scope: 'run',
846
847
  planRevision,
847
848
  approvalClasses: approvalClasses.length > 0 ? approvalClasses : ['default'],
849
+ caller: 'chain-wait-auto-approve',
848
850
  });
849
851
  const line = `chain-wait: auto-approved run ${active.runId} (revision ${planRevision})`;
850
852
  log.push(line); console.log(line);
@@ -978,11 +980,12 @@ async function runRuntime(argv, agent) {
978
980
  payload: { message: `runtime: expired ${staleControl.length} stale queued control request(s) from a previous session` },
979
981
  }));
980
982
  }
981
- session._onRuntimeError = (err) => {
983
+ session._onRuntimeError = (err, runId = null) => {
982
984
  const message = err instanceof Error ? err.message : String(err);
983
985
  dispatchAgentEvent(session, createAgentEvent('run_error', {
984
986
  origin: 'runtime',
985
- payload: { message, workspace },
987
+ ...(runId ? { runId } : {}),
988
+ payload: { message, workspace, ...(runId ? { runId } : {}) },
986
989
  }));
987
990
  };
988
991
  context.approvalManager = createApprovalManager(session, {
@@ -991,7 +994,9 @@ async function runRuntime(argv, agent) {
991
994
  : undefined,
992
995
  });
993
996
  session._requestApproval = (request) => context.approvalManager.requestApproval(request);
994
- context.supervisor = startActivitySupervisor(session);
997
+ context.supervisor = startActivitySupervisor(session, {
998
+ refreshMcp: () => refreshMcpRuntimeStatus(session),
999
+ });
995
1000
  contexts.set(key, context);
996
1001
  if (workspace && workspace !== key) contexts.set(workspace, context);
997
1002
  return context;
@@ -1422,7 +1427,26 @@ async function runRuntime(argv, agent) {
1422
1427
  const provider = agents.find((item) => (item.description?.capabilities ?? [])
1423
1428
  .some((capability) => capability.id === body.capabilityPlan.capability));
1424
1429
  if (!provider?.serverName) {
1425
- throw new Error(`No agent provides capability ${body.capabilityPlan.capability}.`);
1430
+ /*
1431
+ Say what WAS seen, not only what was missing.
1432
+
1433
+ "No agent provides capability X." leaves three very different causes
1434
+ indistinguishable: no agent registered at all, the right agent
1435
+ registered but advertising a narrower set (an operation excluded by
1436
+ PRODUCTION_ALLOWED_STEPS drops its whole capability from
1437
+ agent_describe, silently), or a name mismatch. Listing the registry
1438
+ turns the next occurrence into its own diagnosis instead of a guess.
1439
+ */
1440
+ const seen = agents.map((item) => {
1441
+ const ids = (item.description?.capabilities ?? []).map((capability) => capability.id);
1442
+ return `${item.serverName ?? '?'}[${ids.join(', ') || 'none'}]`;
1443
+ });
1444
+ throw new Error(
1445
+ `No agent provides capability ${body.capabilityPlan.capability}. `
1446
+ + (seen.length
1447
+ ? `Registered agents: ${seen.join('; ')}.`
1448
+ : 'No agent is registered: none answered agent_describe.'),
1449
+ );
1426
1450
  }
1427
1451
  const fragment = parseJsonText(formatMcpToolResult(await callMcpTool(session.mcp, provider.serverName, 'agent_plan', {
1428
1452
  capability: body.capabilityPlan.capability,
@@ -1402,7 +1402,7 @@ export async function handleSlashCommand(line, context) {
1402
1402
  },
1403
1403
  });
1404
1404
  if (result?.runId) {
1405
- return { output: `▶ Run de capability accepté (${String(result.runId).slice(0, 8)}) — le plan de l'agent sera intégré et dispatché en parallèle ; approbation demandée avant les mutations (« valide tout » ou /approve).` };
1405
+ return { output: `▶ Run de capability accepté (${String(result.runId).slice(0, 8)}) — le plan de l'agent sera intégré et dispatché en parallèle ; approbation demandée avant les mutations (/approve).` };
1406
1406
  }
1407
1407
  return { output: `Run non démarré: ${result?.explanation ?? result?.error ?? JSON.stringify(result)}` };
1408
1408
  }
@@ -561,7 +561,18 @@ function applyEvent(state, event) {
561
561
  return;
562
562
  case 'run_error':
563
563
  state.status = 'error';
564
- state.logs.push(String(event.payload?.message ?? 'Agent run failed.'));
564
+ /*
565
+ "Run failed:" is part of the line, not decoration.
566
+
567
+ The serve journal keeps only the entries matching a keyword list
568
+ (failed, error, done, approval…). A run killed by a message that uses
569
+ none of those words — "No agent provides capability workspace.restore."
570
+ — was therefore filtered out as unimportant, and the panel showed "No
571
+ essential run event yet" over a run that had just died with its reason
572
+ already in hand. What ends a run is essential by construction; the
573
+ prefix states that instead of hoping the wording says so.
574
+ */
575
+ state.logs.push(`Run failed: ${String(event.payload?.message ?? 'Agent run failed.')}`);
565
576
  // A dead run must not leave "pending" plan steps and spinning
566
577
  // activities in the persisted projection: they reappeared as ghosts
567
578
  // at every relaunch ("des trucs dans le plan qui n'existent pas") and
@@ -675,3 +675,24 @@ test('a stream resumed after a discard carries only the final text', () => {
675
675
  assert.equal(projection.conversation[1].content, '12 pages.');
676
676
  assert.equal(projection.conversation[1].streaming, undefined, 'le message doit être figé');
677
677
  });
678
+
679
+ test('run_error names the failure so the essential journal cannot filter it out', () => {
680
+ // La liste de mots-clés du journal serve (failed, error, done, approval…)
681
+ // rejetait un message qui n'en contient aucun — « No agent provides
682
+ // capability workspace.restore. » — et le panneau affichait « No essential
683
+ // run event yet » au-dessus d'un run mort avec sa raison déjà connue.
684
+ const state = reduceAgentEvents([
685
+ createAgentEvent('run_error', {
686
+ origin: 'runtime',
687
+ runId: 'run-1',
688
+ payload: { message: 'No agent provides capability workspace.restore.' },
689
+ }),
690
+ ]);
691
+
692
+ assert.equal(state.status, 'error');
693
+ const line = state.logs.at(-1);
694
+ assert.match(line, /^Run failed: /);
695
+ assert.match(line, /workspace\.restore/);
696
+ // Le mot qui rend l'entrée « essentielle » pour le journal serve.
697
+ assert.match(line, /failed/i);
698
+ });
@@ -1,4 +1,4 @@
1
1
  {
2
- "version": "0.15.48",
3
- "commit": "7290b94"
2
+ "version": "0.15.50",
3
+ "commit": "844cf7e"
4
4
  }
@@ -27,6 +27,24 @@ test('workspace production agent enables restore by default', async () => {
27
27
  assert.match(String(allowed), /(?:^|,)restore(?:,|})/);
28
28
  });
29
29
 
30
+ test('every shipped default allows the taxonomy step', async () => {
31
+ /*
32
+ L'absence de `taxonomy` est SILENCIEUSE.
33
+
34
+ `agent_plan` retire la tâche taxonomique du fragment d'ingestion quand
35
+ l'étape n'est pas autorisée (`"taxonomy" in _ALLOWED_STEPS`), sans erreur ni
36
+ avertissement. Un déploiement par compose ingérait donc sans jamais publier
37
+ de taxonomie, et la carte restait périmée — exactement le défaut que le Lot 4
38
+ avait corrigé côté moteur. Le défaut de Python porte `taxonomy` ; un compose
39
+ POSITIONNE toujours la variable, donc ce défaut ne s'applique jamais là.
40
+ */
41
+ const raw = await readFile(new URL('../../docker-compose.yml', import.meta.url), 'utf8');
42
+ const compose = YAML.parse(raw);
43
+ const allowed = compose.services['production-mcp'].environment
44
+ .find((entry) => String(entry).startsWith('PRODUCTION_ALLOWED_STEPS='));
45
+ assert.match(String(allowed), /(?:^|,)taxonomy(?:,|})/);
46
+ });
47
+
30
48
  test('shipped compose files never carry a build context', async () => {
31
49
  // Ces deux fichiers partent dans le paquet npm, où les dépôts frères
32
50
  // (`../agent-external/…`) n'existent pas : un `build:` y rend toute commande
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.15.48';
4
+ const WIKI_MANAGER_VERSION = '0.15.50';
5
5
 
6
6
  function envValue(key) {
7
7
  const filePath = managerEnvFile();