@dotdrelle/wiki-manager 0.8.2 → 0.10.4

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
@@ -460,6 +460,21 @@ or the shell command `/approve item <id>`. The approval timeout defaults to 10
460
460
  minutes and can be changed with `WIKI_MANAGER_APPROVAL_TIMEOUT_MS` or
461
461
  `approvalTimeoutMs` in the `/run` body.
462
462
 
463
+ While a run is active, `GET`/`POST /control` still answers without waiting for
464
+ it to finish: `{"action":"status"}` returns the current run/plan/queue state,
465
+ `{"action":"explain"}` adds a one-line plain-language summary, and
466
+ `{"action":"enqueue","input":"..."}` accepts a new request without touching the
467
+ active plan. A queued request starts automatically as soon as the workspace
468
+ goes idle — either because the enqueue call itself found the workspace free,
469
+ or because the run in progress finished and drained the next queued item.
470
+
471
+ `GET /config/profiles` lists the `.wikirc` profiles for a workspace and
472
+ `POST /config/use {"profile":"..."}` switches the active one — the same
473
+ switch as the shell's `/config use`, rejected with 409 while a run is active.
474
+ The manager is the source of truth for which profile is active; `llm-wiki
475
+ serve`'s config-profile picker mirrors whatever the manager reports rather
476
+ than tracking its own state.
477
+
463
478
  ### Starting external agents
464
479
 
465
480
  Start CME, documents, and mailer once for all workspaces:
@@ -512,13 +527,17 @@ The shared `docker-compose.yml` starts one workspace stack:
512
527
 
513
528
  | Service | Role | Port variable |
514
529
  | --- | --- | --- |
515
- | `serve` | Wiki web UI and browser chat | `WIKI_SERVE_PORT` |
516
- | `mcp-http` | llm-wiki MCP endpoint | `WIKI_MCP_PORT` |
517
- | `production-mcp` | Production job MCP endpoint | `PRODUCTION_MCP_PORT` |
530
+ | `serve` | Wiki web UI and browser chat, container port `3000` | `WIKI_SERVE_PORT` |
531
+ | `mcp-http` | llm-wiki MCP endpoint, container port `3333` | `WIKI_MCP_PORT` |
532
+ | `production-mcp` | Production job MCP endpoint, container port `8080` | `PRODUCTION_MCP_PORT` |
518
533
 
519
534
  Use `wiki-workspace` whenever possible so Compose receives the right project
520
535
  name, env file, ports, and volume mounts.
521
536
 
537
+ Runtime split: the host manager/runtime uses Node.js 22+ for `node:sqlite`; the
538
+ interactive OpenTUI shell uses Bun 1.2+; workspace Docker services run from the
539
+ published images and do not depend on host `node_modules`.
540
+
522
541
  ```bash
523
542
  wiki-workspace list
524
543
  wiki-workspace agents up
@@ -784,12 +803,23 @@ Files matching `docker-compose*.local.yml` are ignored by Git.
784
803
  ```bash
785
804
  pnpm install
786
805
  pnpm start
806
+ pnpm run check-versions
787
807
  pnpm run check
788
808
  ```
789
809
 
790
- When bumping the package version, update both `package.json` and the Streamable
791
- HTTP MCP `clientInfo.version` in `src/core/mcp.js`. They are kept explicit so
792
- remote MCP server logs show the manager build that initiated the handshake.
810
+ When bumping a coordinated release, keep `llm-wiki`, `llm-wiki-manager`, Python
811
+ agent `_AGENT_VERSION` values, MCP `clientInfo.version` / server versions, Git
812
+ tags, and Docker image tags aligned. Run:
813
+
814
+ ```bash
815
+ pnpm run check-versions
816
+ CHECK_GIT_TAG=1 pnpm run check-versions # pre-release tag check
817
+ CHECK_DOCKER_IMAGES=1 pnpm run check-versions # after local image build
818
+ ```
819
+
820
+ `build-and-push.sh` synchronizes the coordinated version, runs
821
+ `pnpm run check-versions`, builds images tagged with that version, and can push
822
+ the matching `latest` tags.
793
823
 
794
824
  `pnpm run check` verifies the CLI version, help output, and limited `--once` mode.
795
825
  For headless changes, also test a controlled error path, for example:
@@ -21,6 +21,7 @@
21
21
  # MAILERSEND_API_KEY
22
22
  # MAILERSEND_FROM_EMAIL
23
23
  # MAILERSEND_FROM_NAME
24
+ # MAILERSEND_CA_CERT — optional CA bundle path inside the container
24
25
  #
25
26
  # Set these variables in a .env file at the directory where you run wiki-workspace,
26
27
  # or export them in your shell before running wiki-workspace agents up.
@@ -90,7 +91,11 @@ services:
90
91
  - MAILERSEND_FROM_NAME=${MAILERSEND_FROM_NAME:-Mailer Agent}
91
92
  - MAILERSEND_USER_AGENT=${MAILERSEND_USER_AGENT:-curl/8.7.1}
92
93
  - MAILERSEND_VERIFY_SSL=${MAILERSEND_VERIFY_SSL:-true}
94
+ - MAILERSEND_CA_CERT=${MAILERSEND_CA_CERT:-}
93
95
  - MAILER_REQUIRE_CONFIRMATION=${MAILER_REQUIRE_CONFIRMATION:-true}
94
96
  - MAILER_DRY_RUN=${MAILER_DRY_RUN:-false}
95
97
  - MCP_AUTH_TOKEN=${MAILER_MCP_AUTH_TOKEN:-}
98
+ volumes:
99
+ # Optional: mount a CA bundle and set MAILERSEND_CA_CERT to its container path.
100
+ #- ${AGENTS_DATA_DIR:-./.agents-data}/certs:/certs:ro
96
101
  restart: unless-stopped
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotdrelle/wiki-manager",
3
- "version": "0.8.2",
3
+ "version": "0.10.4",
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,10 @@
11
11
  },
12
12
  "scripts": {
13
13
  "start": "bun ./bin/wiki-manager.js",
14
- "test": "node --test src/agent/graph.test.js src/core/activity.test.js src/core/agentEvents.test.js src/core/agentLoop.test.js src/core/plan.test.js src/core/mcp.test.js src/core/documentIntake.test.js src/core/dockerCompose.test.js src/core/wikiWorkspace.test.js src/core/wikirc.test.js src/core/modelFetch.test.js src/core/startupCheck.test.js src/core/queueStore.test.js src/commands/slash.test.js src/shell/repl.test.js src/runtime/store.test.js src/runtime/server.test.js src/runtime/supervisor.test.js src/runtime/runner.test.js src/runtime/auth.test.js",
14
+ "test": "node --test src/agent/graph.test.js src/contracts/schemas.test.js src/core/activity.test.js src/core/agentEvents.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/documentIntake.test.js src/core/dockerCompose.test.js src/core/wikiWorkspace.test.js src/core/wikirc.test.js src/core/modelFetch.test.js src/core/startupCheck.test.js src/core/queueStore.test.js src/commands/slash.test.js src/shell/repl.test.js src/runtime/store.test.js src/runtime/server.test.js src/runtime/supervisor.test.js src/runtime/runner.test.js src/runtime/auth.test.js",
15
+ "check-versions": "node scripts/check-versions.js",
16
+ "prepack": "node scripts/check-versions.js",
17
+ "prepublishOnly": "node scripts/check-versions.js",
15
18
  "check": "bun ./bin/wiki-manager.js --version && bun ./bin/wiki-manager.js --help && bun ./bin/wiki-manager.js --once \"verifie le mode agent\""
16
19
  },
17
20
  "engines": {
@@ -66,8 +66,26 @@ const WIKI_PLAN_SET_TOOL = {
66
66
  properties: {
67
67
  steps: {
68
68
  type: 'array',
69
- items: { type: 'string' },
70
- description: 'Ordered step descriptions, e.g. ["CME export", "Production ingest", "Build", "Polish", "Email report"].',
69
+ items: {
70
+ anyOf: [
71
+ { type: 'string' },
72
+ {
73
+ type: 'object',
74
+ additionalProperties: false,
75
+ properties: {
76
+ id: { type: 'string' },
77
+ description: { type: 'string' },
78
+ status: { type: 'string', enum: ['pending', 'queued', 'running', 'waiting', 'pending_approval', 'done', 'failed', 'cancelled', 'stalled', 'added_during_run'] },
79
+ dependsOn: { type: 'array', items: { type: 'string' } },
80
+ executor: { type: ['string', 'null'] },
81
+ executorQuery: { type: ['object', 'null'], additionalProperties: true },
82
+ outputRefs: { type: 'array', items: { type: 'string' } },
83
+ },
84
+ required: ['description'],
85
+ },
86
+ ],
87
+ },
88
+ description: 'Ordered steps. Backward-compatible strings are accepted; structured steps may include id, dependsOn, executor, executorQuery, outputRefs.',
71
89
  },
72
90
  },
73
91
  required: ['steps'],
@@ -344,11 +362,7 @@ function handleWikiTool(session, tool, args) {
344
362
  if (tool === 'plan_set') {
345
363
  const steps = Array.isArray(args.steps) ? args.steps : [];
346
364
  emitAgentEvent(session, 'plan_set', 'tool', {
347
- steps: steps.map((description, i) => ({
348
- step: i + 1,
349
- description: String(description),
350
- status: 'pending',
351
- })),
365
+ steps: steps.map((raw, i) => normalizeDeclaredPlanStep(raw, i, session)),
352
366
  });
353
367
  return `Plan registered: ${steps.length} step${steps.length !== 1 ? 's' : ''}.`;
354
368
  }
@@ -364,6 +378,51 @@ function handleWikiTool(session, tool, args) {
364
378
  return `Unknown wiki tool: ${tool}`;
365
379
  }
366
380
 
381
+ function normalizeDeclaredPlanStep(raw, index, session) {
382
+ const item = raw && typeof raw === 'object' && !Array.isArray(raw)
383
+ ? raw
384
+ : { description: String(raw) };
385
+ const description = String(item.description ?? item.label ?? item.name ?? item.id ?? `Step ${index + 1}`);
386
+ return {
387
+ step: Number(item.step ?? index + 1),
388
+ id: item.id ? String(item.id) : slugStepId(description, index),
389
+ description,
390
+ status: item.status ?? 'pending',
391
+ dependsOn: Array.isArray(item.dependsOn) ? item.dependsOn.map(String) : [],
392
+ executor: item.executor ?? selectExecutorForStep(description, session),
393
+ executorQuery: item.executorQuery ?? null,
394
+ outputRefs: Array.isArray(item.outputRefs) ? item.outputRefs.map(String) : [],
395
+ };
396
+ }
397
+
398
+ function slugStepId(description, index) {
399
+ const slug = String(description)
400
+ .toLowerCase()
401
+ .normalize('NFD')
402
+ .replace(/[\u0300-\u036f]/g, '')
403
+ .replace(/[^a-z0-9]+/g, '-')
404
+ .replace(/^-+|-+$/g, '')
405
+ .slice(0, 48);
406
+ return slug || `task-${index + 1}`;
407
+ }
408
+
409
+ function selectExecutorForStep(description, session) {
410
+ const text = String(description ?? '').toLowerCase();
411
+ let fallback = null;
412
+ for (const [serverName, value] of Object.entries(session.mcp ?? {})) {
413
+ if (value.status !== 'connected') continue;
414
+ for (const tool of value.tools ?? []) {
415
+ const executor = `${serverName}.${tool.name}`;
416
+ fallback ??= executor;
417
+ const haystack = `${serverName} ${tool.name} ${tool.description ?? ''}`.toLowerCase();
418
+ if (text.split(/[^a-z0-9]+/).filter((token) => token.length >= 4).some((token) => haystack.includes(token))) {
419
+ return executor;
420
+ }
421
+ }
422
+ }
423
+ return fallback;
424
+ }
425
+
367
426
  export function buildAgentSystemPrompt(state) {
368
427
  const workspace = state.session.workspace ?? 'no workspace selected';
369
428
  const wikirc = state.session.wikirc?.profile ?? 'no profile loaded';
@@ -401,8 +460,8 @@ export function buildAgentSystemPrompt(state) {
401
460
  '',
402
461
  'Task startup:',
403
462
  ' 1. If the next MCP tool returns _activity.plan.steps, call that tool directly; the shell will create the visible plan from the returned activity.',
404
- ' 2. If the tool cannot declare its own plan, call wiki__plan_set(steps=["Step description", ...]) before executing the first step.',
405
- ' Multi-tool example: wiki__plan_set(steps=["CME export", "Production pipeline", "Email report"])',
463
+ ' 2. If the tool cannot declare its own plan, call wiki__plan_set before executing the first step. Prefer structured steps: {id, description, dependsOn, executor, executorQuery, outputRefs}; a legacy list of strings is still accepted.',
464
+ ' Multi-tool example: wiki__plan_set(steps=[{id:"cme-export",description:"CME export",dependsOn:[],executor:"cme.cme_export_run",outputRefs:["raw/untracked"]},{id:"production",description:"Production pipeline",dependsOn:["cme-export"],executor:"production.production_start_job",outputRefs:["deliverables"]}])',
406
465
  ' 3. Immediately execute the first step using the appropriate MCP tool. Do not start step 2 in the same turn unless one async pipeline tool owns and declares the whole sequence.',
407
466
  ' For synchronous steps (result is immediate, no _activity polling), call wiki__plan_done(step=1) after confirming success.',
408
467
  ' For async MCP jobs (returns _activity with poll), the orchestrator tracks completion automatically.',
@@ -143,3 +143,67 @@ test('agent graph waits for tool-level approval configured on endpoint', async (
143
143
  }
144
144
  });
145
145
 
146
+ test('agent graph accepts structured wiki plan steps and selects MCP executors', async () => {
147
+ let calls = 0;
148
+ const session = sessionBase({
149
+ mcp: {
150
+ cme: {
151
+ status: 'connected',
152
+ url: 'http://127.0.0.1:3001/mcp/',
153
+ tools: [{
154
+ name: 'cme_export_run',
155
+ description: 'Export CME pages',
156
+ inputSchema: { type: 'object', properties: {} },
157
+ }],
158
+ },
159
+ production: {
160
+ status: 'connected',
161
+ url: 'http://127.0.0.1:3000/mcp/',
162
+ tools: [{
163
+ name: 'production_start_job',
164
+ description: 'Start production job',
165
+ inputSchema: { type: 'object', properties: { type: { type: 'string' } } },
166
+ }],
167
+ },
168
+ },
169
+ llm: {
170
+ async completeWithTools() {
171
+ calls += 1;
172
+ if (calls === 1) {
173
+ return {
174
+ content: null,
175
+ message: { role: 'assistant', content: null },
176
+ tool_calls: [{
177
+ id: 'plan-call',
178
+ type: 'function',
179
+ function: {
180
+ name: 'wiki__plan_set',
181
+ arguments: JSON.stringify({
182
+ steps: [
183
+ { id: 'cme-export', description: 'Export CME pages', outputRefs: ['raw/untracked'] },
184
+ { id: 'build', description: 'Run production build', dependsOn: ['cme-export'] },
185
+ ],
186
+ }),
187
+ },
188
+ }],
189
+ };
190
+ }
191
+ return {
192
+ content: 'Plan ready.',
193
+ message: { role: 'assistant', content: 'Plan ready.' },
194
+ tool_calls: null,
195
+ };
196
+ },
197
+ },
198
+ });
199
+
200
+ const agent = createAgentGraph();
201
+ const result = await agent.invoke({ input: 'Plan export then build', session });
202
+
203
+ assert.equal(result.response, 'Plan ready.');
204
+ assert.deepEqual(session.headlessPlan.map((step) => step.id), ['cme-export', 'build']);
205
+ assert.equal(session.headlessPlan[0].executor, 'cme.cme_export_run');
206
+ assert.equal(session.headlessPlan[1].executor, 'production.production_start_job');
207
+ assert.deepEqual(session.headlessPlan[1].dependsOn, ['cme-export']);
208
+ assert.deepEqual(session.headlessPlan[0].outputRefs, ['raw/untracked']);
209
+ });
@@ -8,6 +8,8 @@ import { createAgentGraph } from '../agent/graph.js';
8
8
  import { handleSlashCommand, printHelp, printVersion, refreshMcpRuntimeStatus } from '../commands/slash.js';
9
9
  import { runShell } from '../shell/repl.js';
10
10
  import { runChecks } from '../core/startupCheck.js';
11
+ import { applySessionWikircProfile } from '../core/sessionConfig.js';
12
+ import { listWikircProfiles } from '../core/wikirc.js';
11
13
  import { callMcpTool, formatMcpToolResult } from '../core/mcp.js';
12
14
  import { extractActivity, parseJsonText, sessionActivities, terminalFailures } from '../core/activity.js';
13
15
  import { syncActivitiesToPlan, formatPlanStatus } from '../core/plan.js';
@@ -161,7 +163,7 @@ async function runHeadlessAgenticLoop(agent, session, initialInput, log, { timeo
161
163
  console.log(response);
162
164
  },
163
165
  onPlanExtracted: ({ steps }) => {
164
- log.push(`agentic-loop: plan extracted from text (${steps.length} steps, fallback)`);
166
+ log.push(`agentic-loop: plan extracted from text (${steps.length} steps, deprecated fallback)`);
165
167
  },
166
168
  onPlanAlreadySet: ({ steps }) => {
167
169
  log.push(`agentic-loop: plan set via tool (${steps.length} steps)`);
@@ -512,11 +514,14 @@ async function runRuntime(argv, agent) {
512
514
  };
513
515
  }
514
516
  emitRuntimeLog(context.session, manual ? 'runtime: manual resume completed' : 'runtime: recovery completed');
517
+ const controlStarted = pollingActivities.length === 0
518
+ ? serverHandle?.drainControl?.(context) === true
519
+ : false;
515
520
  return {
516
521
  workspace: context.workspace ?? workspace ?? null,
517
522
  resumed: true,
518
523
  interrupted: 0,
519
- mode: pollingActivities.length > 0 ? 'activity_poll' : 'context',
524
+ mode: controlStarted ? 'control_queue' : pollingActivities.length > 0 ? 'activity_poll' : 'context',
520
525
  };
521
526
  } catch (err) {
522
527
  const interrupted = store.interruptRuns({ workspace });
@@ -629,6 +634,33 @@ async function runRuntime(argv, agent) {
629
634
  const context = await getWorkspaceContext(workspace);
630
635
  return context.approvalManager?.approve({ runId, itemId, approvalId }) ?? { approved: false };
631
636
  },
637
+ configProfiles: async (context) => {
638
+ const profiles = listWikircProfiles(context.session.workspacePath);
639
+ return {
640
+ profiles: profiles.map((profile) => profile.name),
641
+ active: context.session.wikirc?.profile ?? null,
642
+ items: profiles.map((profile) => ({
643
+ name: profile.name,
644
+ fileName: profile.fileName,
645
+ default: Boolean(profile.default),
646
+ })),
647
+ };
648
+ },
649
+ useConfigProfile: async (context, profile) => {
650
+ const { summary, config } = applySessionWikircProfile(context.session, profile);
651
+ await refreshMcpRuntimeStatus(context.session);
652
+ dispatchAgentEvent(context.session, createAgentEvent('runtime_log', {
653
+ origin: 'runtime',
654
+ payload: { message: `runtime: config profile switched to ${context.session.wikirc?.profile ?? profile}` },
655
+ }));
656
+ return {
657
+ ok: true,
658
+ active: context.session.wikirc?.profile ?? profile,
659
+ fileName: context.session.wikirc?.fileName ?? null,
660
+ summary,
661
+ config,
662
+ };
663
+ },
632
664
  token: auth.token,
633
665
  });
634
666
  const recovery = await recoverRuntime();
@@ -1,7 +1,6 @@
1
1
  import { execFileSync } from 'node:child_process';
2
2
  import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
3
3
  import { join, relative } from 'node:path';
4
- import { createLlmClientFromWikiConfig } from '../agent/llm.js';
5
4
  import { composeServices, listServices, runWikiCli, serviceLogs, serviceStates, startService, stopService } from '../core/compose.js';
6
5
  import {
7
6
  applyMcpRuntimeStatus,
@@ -26,10 +25,10 @@ import {
26
25
  } from '../core/jobQueue.js';
27
26
  import {
28
27
  listWikircProfiles,
29
- loadWikircProfile,
30
28
  resolveWikircProfile,
31
29
  summarizeWikircConfig,
32
30
  } from '../core/wikirc.js';
31
+ import { applySessionWikircProfile } from '../core/sessionConfig.js';
33
32
  import { deleteWorkspaceAndFiles, startAgents, stopAgents } from '../core/wikiSetup.js';
34
33
  import {
35
34
  cleanDocumentUploads,
@@ -546,25 +545,6 @@ function loadWorkspaceSystemPrompt(workspacePath) {
546
545
  return existsSync(promptPath) ? readFileSync(promptPath, 'utf8').trim() || null : null;
547
546
  }
548
547
 
549
- function loadSessionWikirc(session, profileName = 'default') {
550
- if (!session.workspacePath) {
551
- throw new Error('No workspace loaded. Use /use <workspace>.');
552
- }
553
- const loaded = loadWikircProfile(session.workspacePath, profileName);
554
- session.wikirc = {
555
- profile: loaded.profile.name,
556
- fileName: loaded.profile.fileName,
557
- path: loaded.profile.path,
558
- };
559
- session.wikircConfig = loaded.config;
560
- session.language = loaded.config?.language ?? null;
561
- session.llm = createLlmClientFromWikiConfig(loaded.config);
562
- if (session.mcp?.production) {
563
- session.mcp.production.activeConfigPath = loaded.profile.fileName;
564
- }
565
- return summarizeWikircConfig(loaded.profile, loaded.config);
566
- }
567
-
568
548
  function clearWorkspaceSession(session) {
569
549
  session.workspace = null;
570
550
  session.workspacePath = null;
@@ -635,8 +615,8 @@ ${helpPair('/workspace list', 'Workspaces', '/new <n> [path]', 'New workspace')}
635
615
  ${helpPair('/use <workspace>', 'Use workspace', '/status', 'Session status')}
636
616
  ${helpPair('/config list', 'Config profiles', '/config use <n>', 'Use config')}
637
617
  ${helpPair('/config edit <n>', 'Edit config', '/workspace delete <n>', 'Delete workspace')}
638
- ${helpPair('/services', 'Services', '/start [service|agents]', 'Start service(s)')}
639
- ${helpPair('/stop [service|agents]', 'Stop service(s)', '/logs <service>', 'Service logs')}
618
+ ${helpPair('/services', 'Services', '/start [all|service|agents]', 'Start service(s)')}
619
+ ${helpPair('/stop [all|service|agents]', 'Stop service(s)', '/logs <service>', 'Service logs')}
640
620
  ${helpPair('/skills', 'List skills', '/skills show <n>', 'Show skill')}
641
621
  ${helpPair('/skills run <n>', 'Run skill guide', '/skills edit <n>', 'Edit skill')}
642
622
  ${helpPair('/mcp status', 'MCP status', '/mcp endpoints', 'MCP endpoints')}
@@ -720,7 +700,7 @@ export async function handleSlashCommand(line, context) {
720
700
  context.session.systemPrompt = loadWorkspaceSystemPrompt(workspace.workspacePath);
721
701
  try {
722
702
  step(`Workspace: loading ${workspace.name} config…`);
723
- const summary = loadSessionWikirc(context.session, 'default');
703
+ const { summary } = applySessionWikircProfile(context.session, 'default');
724
704
  step(`Workspace: discovering ${workspace.name} MCP tools…`);
725
705
  await refreshMcpRuntimeStatus(context.session);
726
706
  return {
@@ -759,7 +739,7 @@ export async function handleSlashCommand(line, context) {
759
739
  return { output: 'Usage: /config use <default|name>' };
760
740
  }
761
741
  try {
762
- const summary = loadSessionWikirc(context.session, profileName);
742
+ const { summary } = applySessionWikircProfile(context.session, profileName);
763
743
  await refreshMcpRuntimeStatus(context.session);
764
744
  return {
765
745
  output: [
@@ -830,6 +810,10 @@ export async function handleSlashCommand(line, context) {
830
810
  }
831
811
  }
832
812
  case 'start': {
813
+ // 'all' already resolves correctly through serviceAliases() (DEFAULT_SERVICE_ALIASES.all
814
+ // = COMPOSE_SERVICES, overridable via docker-compose.yml's service-aliases.all.targets) —
815
+ // do not remap it to undefined, that bypasses any custom "all" target list and always
816
+ // falls back to the hardcoded COMPOSE_SERVICES constant instead.
833
817
  const service = args[1];
834
818
  if (service === 'agents') return runAgentCommand(startAgents, 'start');
835
819
  try {
@@ -0,0 +1,16 @@
1
+ # Runtime Contracts
2
+
3
+ Versioned JSON Schema-like contracts live in `schemas.js`.
4
+
5
+ Current schema version: `1`.
6
+
7
+ Validated boundaries:
8
+
9
+ - `_activity` after normalization in `core/activity.js`
10
+ - `AgentRunEvent` creation and dispatch in `core/agentEvents.js`
11
+ - structured plan and plan patch normalization
12
+ - runtime `/run` and `/control` request payloads
13
+
14
+ Validation is enabled when `WIKI_MANAGER_VALIDATE_CONTRACTS=1`, `CI=true`, or
15
+ `NODE_ENV` is set to a non-production value. Schemas tolerate additional fields
16
+ so agents can extend payloads without breaking older consumers.