wogiflow 2.29.0 → 2.29.1
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/lib/wogi-claude
CHANGED
|
@@ -107,13 +107,78 @@ if [ "$__wogi_is_worker" -eq 1 ]; then
|
|
|
107
107
|
fi
|
|
108
108
|
fi
|
|
109
109
|
|
|
110
|
-
# Resolve the
|
|
111
|
-
#
|
|
112
|
-
# alongside other worker state.
|
|
110
|
+
# Resolve the channel-only MCP config used for stripping. Persistent path
|
|
111
|
+
# so we don't regenerate on every restart; living in .workflow/state/ keeps
|
|
112
|
+
# it alongside other worker state.
|
|
113
|
+
#
|
|
114
|
+
# IMPORTANT — REGRESSION FIX (audit-channel-transport-001):
|
|
115
|
+
#
|
|
116
|
+
# The original Story A (wf-8294d960) wrote `{"mcpServers":{}}` — fully
|
|
117
|
+
# empty — under the (unverified) assumption that workers don't need any
|
|
118
|
+
# MCP servers in worker mode. That assumption was WRONG: it stripped
|
|
119
|
+
# `wogi-workspace-channel` which IS the transport that the manager uses
|
|
120
|
+
# to dispatch tasks to workers via `workspace_send_message` (the manager
|
|
121
|
+
# HTTP-POSTs to the worker's channel-server port; with no MCP server,
|
|
122
|
+
# there's no listener, so dispatches silently fail with "connection
|
|
123
|
+
# refused"). Tier-3 evidence (end-to-end manager→worker dispatch) was
|
|
124
|
+
# never collected; only boot-latency was measured. Story B
|
|
125
|
+
# (wf-ab59f0e4) layered COMPLETION-SUMMARY routing on top of this
|
|
126
|
+
# broken transport without auditing the dependency.
|
|
127
|
+
#
|
|
128
|
+
# The proper fix: extract ONLY the `wogi-workspace-channel` entry from
|
|
129
|
+
# the worker's real `.mcp.json` and write a channel-only config. This
|
|
130
|
+
# preserves Story A's boot-speed win (claude.ai MCP integrations stay
|
|
131
|
+
# stripped) while keeping the workspace transport active. If the
|
|
132
|
+
# worker's `.mcp.json` doesn't define `wogi-workspace-channel` (e.g.
|
|
133
|
+
# this is not a workspace member), fall back to the empty MCP config
|
|
134
|
+
# (the strip is harmless in non-workspace contexts).
|
|
113
135
|
__wogi_empty_mcp_config=""
|
|
114
136
|
if [ "$__wogi_strip_mcp" -eq 1 ]; then
|
|
115
|
-
__wogi_empty_mcp_config="${WOGI_WORKSPACE_ROOT:-$(pwd)}/.workflow/state/worker-
|
|
116
|
-
|
|
137
|
+
__wogi_empty_mcp_config="${WOGI_WORKSPACE_ROOT:-$(pwd)}/.workflow/state/worker-channel-only-mcp.json"
|
|
138
|
+
__wogi_member_mcp_path="$(pwd)/.mcp.json"
|
|
139
|
+
if command -v node >/dev/null 2>&1; then
|
|
140
|
+
# Use the dedicated helper (testable; see tests/flow-worker-mcp-strip.test.js).
|
|
141
|
+
# Extracts ONLY the wogi-workspace-channel entry from the worker's real
|
|
142
|
+
# .mcp.json so manager-side workspace_send_message dispatch keeps working.
|
|
143
|
+
__wogi_strip_helper=""
|
|
144
|
+
for __wogi_candidate in \
|
|
145
|
+
"$(dirname "$0")/../scripts/flow-worker-mcp-strip.js" \
|
|
146
|
+
"$(npm root -g 2>/dev/null)/wogiflow/scripts/flow-worker-mcp-strip.js" \
|
|
147
|
+
"$(pwd)/node_modules/wogiflow/scripts/flow-worker-mcp-strip.js"; do
|
|
148
|
+
if [ -f "$__wogi_candidate" ]; then
|
|
149
|
+
__wogi_strip_helper="$__wogi_candidate"
|
|
150
|
+
break
|
|
151
|
+
fi
|
|
152
|
+
done
|
|
153
|
+
if [ -n "$__wogi_strip_helper" ]; then
|
|
154
|
+
node "$__wogi_strip_helper" "$__wogi_member_mcp_path" "$__wogi_empty_mcp_config" 2>/dev/null || __wogi_strip_mcp=0
|
|
155
|
+
else
|
|
156
|
+
# Helper not found — fall back to inline extraction (legacy code path
|
|
157
|
+
# for installs that pre-date the helper script).
|
|
158
|
+
node -e '
|
|
159
|
+
const fs = require("fs");
|
|
160
|
+
const path = require("path");
|
|
161
|
+
const [src, out] = process.argv.slice(1);
|
|
162
|
+
let channelEntry = null;
|
|
163
|
+
try {
|
|
164
|
+
if (fs.existsSync(src)) {
|
|
165
|
+
const cfg = JSON.parse(fs.readFileSync(src, "utf-8"));
|
|
166
|
+
const ws = cfg && cfg.mcpServers && cfg.mcpServers["wogi-workspace-channel"];
|
|
167
|
+
if (ws) channelEntry = ws;
|
|
168
|
+
}
|
|
169
|
+
} catch (_e) {}
|
|
170
|
+
const payload = channelEntry
|
|
171
|
+
? { mcpServers: { "wogi-workspace-channel": channelEntry } }
|
|
172
|
+
: { mcpServers: {} };
|
|
173
|
+
fs.mkdirSync(path.dirname(out), { recursive: true });
|
|
174
|
+
const tmp = out + ".tmp." + process.pid;
|
|
175
|
+
fs.writeFileSync(tmp, JSON.stringify(payload, null, 2) + "\n");
|
|
176
|
+
fs.renameSync(tmp, out);
|
|
177
|
+
' "$__wogi_member_mcp_path" "$__wogi_empty_mcp_config" 2>/dev/null || __wogi_strip_mcp=0
|
|
178
|
+
fi
|
|
179
|
+
else
|
|
180
|
+
# node missing — last-resort fallback. Empty config = manager dispatch
|
|
181
|
+
# will fail, but worker boot will still succeed.
|
|
117
182
|
mkdir -p "$(dirname "$__wogi_empty_mcp_config")" 2>/dev/null
|
|
118
183
|
printf '{"mcpServers":{}}\n' > "$__wogi_empty_mcp_config" 2>/dev/null || __wogi_strip_mcp=0
|
|
119
184
|
fi
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wogiflow",
|
|
3
|
-
"version": "2.29.
|
|
3
|
+
"version": "2.29.1",
|
|
4
4
|
"description": "AI-powered development workflow management system with multi-model support",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
12
|
"flow": "./scripts/flow",
|
|
13
|
-
"test": "NODE_ENV=test node --test tests/auto-compact-prompt.test.js tests/flow-paths.test.js tests/flow-io.test.js tests/flow-config-loader.test.js tests/flow-damage-control.test.js tests/flow-output.test.js tests/flow-constants.test.js tests/flow-session-state.test.js tests/flow-hooks-integration.test.js tests/flow-utils.test.js tests/flow-security.test.js tests/flow-memory-db.test.js tests/flow-durable-session.test.js tests/flow-skill-matcher.test.js tests/flow-bridge.test.js tests/flow-proactive-compact.test.js tests/flow-cascade-completion.test.js tests/flow-capture-gate.test.js tests/flow-correction-detector-hybrid.test.js tests/flow-promote.test.js tests/flow-archive-runs.test.js tests/flow-memory.test.js tests/flow-hooks-pre-tool-helpers.test.js tests/flow-hooks-bugfix-scope-gate.test.js tests/flow-hooks-routing-gate.test.js tests/flow-hooks-phase-read-gate.test.js tests/flow-hooks-commit-log-gate.test.js tests/flow-hooks-deploy-gate.test.js tests/flow-hooks-todowrite-gate.test.js tests/flow-hooks-git-safety-gate.test.js tests/flow-hooks-scope-mutation-gate.test.js tests/flow-hooks-strike-gate.test.js tests/flow-hooks-component-check.test.js tests/flow-hooks-scope-gate.test.js tests/flow-hooks-implementation-gate.test.js tests/flow-hooks-research-gate.test.js tests/flow-hooks-loop-check.test.js tests/flow-hooks-manager-boundary-gate.test.js tests/flow-hooks-phase-gate.test.js tests/flow-hooks-pre-tool-orchestrator.test.js tests/flow-hooks-observation-capture.test.js tests/flow-hooks-task-gate.test.js tests/flow-durable-session-suspension.test.js tests/flow-health-mcp-scopes.test.js tests/flow-lean-config.test.js tests/flow-workspace-autopickup.test.js tests/flow-worker-boundary-gate.test.js tests/flow-worker-question-classifier.test.js tests/flow-completion-truth-gate-contradictions.test.js tests/flow-structure-sensor.test.js tests/flow-workspace-dispatch-tracking.test.js tests/workspace-ipc-sqlite.test.js tests/workspace-ipc-multi-worker.test.js tests/flow-story-gates.test.js tests/flow-workspace-restart-handoff.test.js tests/flow-wogi-claude-wrapper.test.js tests/flow-wave1-integrations.test.js tests/flow-wave2-integrations.test.js tests/flow-wave3-integrations.test.js tests/flow-commit-claims-gate.test.js tests/auto-review.test.js tests/gate-telemetry-surface.test.js tests/agents-md-alias.test.js tests/flow-skill-manage.test.js tests/fuzzy-patch.test.js tests/mode-schema.test.js tests/flow-feature-dossier.test.js && NODE_ENV=test node tests/run-quality-gates.test.js",
|
|
13
|
+
"test": "NODE_ENV=test node --test tests/auto-compact-prompt.test.js tests/flow-paths.test.js tests/flow-io.test.js tests/flow-config-loader.test.js tests/flow-damage-control.test.js tests/flow-output.test.js tests/flow-constants.test.js tests/flow-session-state.test.js tests/flow-hooks-integration.test.js tests/flow-utils.test.js tests/flow-security.test.js tests/flow-memory-db.test.js tests/flow-durable-session.test.js tests/flow-skill-matcher.test.js tests/flow-bridge.test.js tests/flow-proactive-compact.test.js tests/flow-cascade-completion.test.js tests/flow-capture-gate.test.js tests/flow-correction-detector-hybrid.test.js tests/flow-promote.test.js tests/flow-archive-runs.test.js tests/flow-memory.test.js tests/flow-hooks-pre-tool-helpers.test.js tests/flow-hooks-bugfix-scope-gate.test.js tests/flow-hooks-routing-gate.test.js tests/flow-hooks-phase-read-gate.test.js tests/flow-hooks-commit-log-gate.test.js tests/flow-hooks-deploy-gate.test.js tests/flow-hooks-todowrite-gate.test.js tests/flow-hooks-git-safety-gate.test.js tests/flow-hooks-scope-mutation-gate.test.js tests/flow-hooks-strike-gate.test.js tests/flow-hooks-component-check.test.js tests/flow-hooks-scope-gate.test.js tests/flow-hooks-implementation-gate.test.js tests/flow-hooks-research-gate.test.js tests/flow-hooks-loop-check.test.js tests/flow-hooks-manager-boundary-gate.test.js tests/flow-hooks-phase-gate.test.js tests/flow-hooks-pre-tool-orchestrator.test.js tests/flow-hooks-observation-capture.test.js tests/flow-hooks-task-gate.test.js tests/flow-durable-session-suspension.test.js tests/flow-health-mcp-scopes.test.js tests/flow-lean-config.test.js tests/flow-workspace-autopickup.test.js tests/flow-worker-boundary-gate.test.js tests/flow-worker-question-classifier.test.js tests/flow-completion-truth-gate-contradictions.test.js tests/flow-structure-sensor.test.js tests/flow-workspace-dispatch-tracking.test.js tests/workspace-ipc-sqlite.test.js tests/workspace-ipc-multi-worker.test.js tests/flow-story-gates.test.js tests/flow-workspace-restart-handoff.test.js tests/flow-wogi-claude-wrapper.test.js tests/flow-wave1-integrations.test.js tests/flow-wave2-integrations.test.js tests/flow-wave3-integrations.test.js tests/flow-commit-claims-gate.test.js tests/auto-review.test.js tests/gate-telemetry-surface.test.js tests/agents-md-alias.test.js tests/flow-skill-manage.test.js tests/fuzzy-patch.test.js tests/mode-schema.test.js tests/flow-feature-dossier.test.js tests/flow-autonomous-mode.test.js tests/flow-epic-cascade.test.js tests/flow-workspace-summary.test.js tests/flow-hooks-research-evidence-gate.test.js tests/flow-worker-mcp-strip.test.js && NODE_ENV=test node tests/run-quality-gates.test.js",
|
|
14
14
|
"test:syntax": "find scripts/ lib/ -name '*.js' -not -path '*/node_modules/*' -exec node --check {} +",
|
|
15
15
|
"lint": "eslint scripts/ lib/ tests/",
|
|
16
16
|
"lint:ci": "eslint scripts/ lib/ tests/ --max-warnings 0",
|
|
@@ -368,8 +368,9 @@ function invalidateConfigCache() {
|
|
|
368
368
|
// Config Value Access
|
|
369
369
|
// ============================================================
|
|
370
370
|
|
|
371
|
-
// Dangerous property names that could lead to prototype pollution
|
|
372
|
-
|
|
371
|
+
// Dangerous property names that could lead to prototype pollution.
|
|
372
|
+
// Consolidated to flow-io canonical (audit dup-002 / wf-9fc4970b).
|
|
373
|
+
const { DANGEROUS_KEYS: DANGEROUS_CONFIG_PROPS } = require('./flow-io');
|
|
373
374
|
|
|
374
375
|
/**
|
|
375
376
|
* Validate config path doesn't contain dangerous property names
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
const path = require('node:path');
|
|
19
|
+
const { DANGEROUS_KEYS } = require('./flow-io');
|
|
19
20
|
const {
|
|
20
21
|
PATHS,
|
|
21
22
|
safeJsonParse,
|
|
@@ -695,11 +696,11 @@ function loadPendingCorrections() {
|
|
|
695
696
|
// SEC-005 fix (2026-04-13): recursive prototype-pollution check for
|
|
696
697
|
// array-rooted JSON. Returns true if __proto__/constructor/prototype found.
|
|
697
698
|
function hasDangerousKeys(value) {
|
|
698
|
-
|
|
699
|
+
// Consolidated to flow-io canonical DANGEROUS_KEYS (audit dup-002 / wf-9fc4970b).
|
|
699
700
|
const visit = (node, depth) => {
|
|
700
701
|
if (depth > 8 || node === null || typeof node !== 'object') return false;
|
|
701
702
|
for (const key of Object.getOwnPropertyNames(node)) {
|
|
702
|
-
if (
|
|
703
|
+
if (DANGEROUS_KEYS.has(key)) return true;
|
|
703
704
|
if (visit(node[key], depth + 1)) return true;
|
|
704
705
|
}
|
|
705
706
|
return false;
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
const fs = require('node:fs');
|
|
18
18
|
const path = require('node:path');
|
|
19
|
+
const { DANGEROUS_KEYS } = require('./flow-io');
|
|
19
20
|
const {
|
|
20
21
|
PROJECT_ROOT,
|
|
21
22
|
parseFlags,
|
|
@@ -341,7 +342,7 @@ function applyTemplate(template, data) {
|
|
|
341
342
|
}
|
|
342
343
|
|
|
343
344
|
// Forbidden keys to prevent prototype pollution (case-insensitive)
|
|
344
|
-
|
|
345
|
+
// Consolidated to flow-io canonical (audit dup-002 / wf-9fc4970b).
|
|
345
346
|
|
|
346
347
|
// Simple substitution: {{key}} or {{object.key}}
|
|
347
348
|
return template.replace(/\{\{([^}]+)\}\}/g, (match, path) => {
|
|
@@ -351,7 +352,7 @@ function applyTemplate(template, data) {
|
|
|
351
352
|
for (const key of keys) {
|
|
352
353
|
// Prevent prototype pollution attacks (case-insensitive check)
|
|
353
354
|
const keyLower = key.toLowerCase();
|
|
354
|
-
if (
|
|
355
|
+
if (DANGEROUS_KEYS.has(keyLower)) return match;
|
|
355
356
|
if (value === undefined || value === null) return match;
|
|
356
357
|
// Only access own properties
|
|
357
358
|
if (!Object.hasOwn(value, key)) return match;
|
|
@@ -38,8 +38,9 @@ const MODEL_TEMPLATE_MAP = {
|
|
|
38
38
|
'gemini-2-flash': 'gemini-flash.yaml'
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
-
// Blocked keys for security (prototype pollution prevention)
|
|
42
|
-
|
|
41
|
+
// Blocked keys for security (prototype pollution prevention).
|
|
42
|
+
// Consolidated to flow-io canonical (audit dup-002 / wf-9fc4970b).
|
|
43
|
+
const { DANGEROUS_KEYS: BLOCKED_KEYS } = require('./flow-io');
|
|
43
44
|
|
|
44
45
|
// ============================================================
|
|
45
46
|
// YAML Parser (lightweight, no dependency)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow — Worker MCP Strip Helper
|
|
5
|
+
*
|
|
6
|
+
* Generates a channel-only MCP config for worker boot. This is the proper
|
|
7
|
+
* fix for the audit-channel-transport-001 regression: Story A originally
|
|
8
|
+
* wrote `{"mcpServers":{}}` (fully empty) for boot speed, which silently
|
|
9
|
+
* stripped the `wogi-workspace-channel` MCP server — leaving manager-side
|
|
10
|
+
* `workspace_send_message` HTTP-POSTs unable to reach the worker.
|
|
11
|
+
*
|
|
12
|
+
* This script reads the worker member-repo's real `.mcp.json`, extracts
|
|
13
|
+
* ONLY the `wogi-workspace-channel` entry, and writes a channel-only
|
|
14
|
+
* config to a destination path. Result:
|
|
15
|
+
* - claude.ai MCP integrations remain stripped (Story A's boot-speed win)
|
|
16
|
+
* - The workspace transport remains active (manager dispatch works)
|
|
17
|
+
*
|
|
18
|
+
* Fallback: if the source `.mcp.json` doesn't define
|
|
19
|
+
* `wogi-workspace-channel` (e.g. the worker isn't a workspace member),
|
|
20
|
+
* the destination is written with `{"mcpServers":{}}` — harmless in
|
|
21
|
+
* non-workspace contexts.
|
|
22
|
+
*
|
|
23
|
+
* Usage:
|
|
24
|
+
* node flow-worker-mcp-strip.js <source-mcp.json> <dest-mcp.json>
|
|
25
|
+
*
|
|
26
|
+
* Programmatic:
|
|
27
|
+
* const { extractChannelOnlyConfig, writeChannelOnlyConfig } =
|
|
28
|
+
* require('./flow-worker-mcp-strip');
|
|
29
|
+
* const cfg = extractChannelOnlyConfig(srcPath);
|
|
30
|
+
* writeChannelOnlyConfig(destPath, cfg);
|
|
31
|
+
*
|
|
32
|
+
* Exit codes:
|
|
33
|
+
* 0 — success (channel-only or empty config written)
|
|
34
|
+
* 1 — write failure (caller should fall back to no-strip)
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
'use strict';
|
|
38
|
+
|
|
39
|
+
const fs = require('node:fs');
|
|
40
|
+
const path = require('node:path');
|
|
41
|
+
|
|
42
|
+
const CHANNEL_SERVER_NAME = 'wogi-workspace-channel';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Read the source `.mcp.json` and return the channel-only config object.
|
|
46
|
+
* Never throws; returns the empty-config fallback on any failure.
|
|
47
|
+
*/
|
|
48
|
+
function extractChannelOnlyConfig(sourcePath) {
|
|
49
|
+
const empty = { mcpServers: {} };
|
|
50
|
+
if (!sourcePath || typeof sourcePath !== 'string') return empty;
|
|
51
|
+
try {
|
|
52
|
+
if (!fs.existsSync(sourcePath)) return empty;
|
|
53
|
+
const raw = fs.readFileSync(sourcePath, 'utf-8');
|
|
54
|
+
const parsed = JSON.parse(raw);
|
|
55
|
+
if (!parsed || typeof parsed !== 'object' || !parsed.mcpServers) return empty;
|
|
56
|
+
const entry = parsed.mcpServers[CHANNEL_SERVER_NAME];
|
|
57
|
+
if (!entry || typeof entry !== 'object') return empty;
|
|
58
|
+
return { mcpServers: { [CHANNEL_SERVER_NAME]: entry } };
|
|
59
|
+
} catch (_err) {
|
|
60
|
+
return empty;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Atomically write the channel-only config to destPath. Returns true on
|
|
66
|
+
* success, false on failure (caller should fall back).
|
|
67
|
+
*/
|
|
68
|
+
function writeChannelOnlyConfig(destPath, config) {
|
|
69
|
+
if (!destPath || typeof destPath !== 'string') return false;
|
|
70
|
+
try {
|
|
71
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
72
|
+
const tmp = `${destPath}.tmp.${process.pid}.${Math.random().toString(36).slice(2, 8)}`;
|
|
73
|
+
fs.writeFileSync(tmp, JSON.stringify(config, null, 2) + '\n');
|
|
74
|
+
fs.renameSync(tmp, destPath);
|
|
75
|
+
return true;
|
|
76
|
+
} catch (_err) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Whether the resulting config preserves the channel transport (i.e. the
|
|
83
|
+
* worker will be reachable from the manager). Useful for callers that want
|
|
84
|
+
* to log a warning if dispatch will silently fail.
|
|
85
|
+
*/
|
|
86
|
+
function preservesChannelTransport(config) {
|
|
87
|
+
return Boolean(
|
|
88
|
+
config &&
|
|
89
|
+
config.mcpServers &&
|
|
90
|
+
config.mcpServers[CHANNEL_SERVER_NAME] &&
|
|
91
|
+
typeof config.mcpServers[CHANNEL_SERVER_NAME] === 'object'
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
module.exports = {
|
|
96
|
+
CHANNEL_SERVER_NAME,
|
|
97
|
+
extractChannelOnlyConfig,
|
|
98
|
+
writeChannelOnlyConfig,
|
|
99
|
+
preservesChannelTransport
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
if (require.main === module) {
|
|
103
|
+
const [src, dest] = process.argv.slice(2);
|
|
104
|
+
if (!src || !dest) {
|
|
105
|
+
process.stderr.write('Usage: flow-worker-mcp-strip <source-mcp.json> <dest-mcp.json>\n');
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
const cfg = extractChannelOnlyConfig(src);
|
|
109
|
+
const ok = writeChannelOnlyConfig(dest, cfg);
|
|
110
|
+
if (!ok) {
|
|
111
|
+
process.stderr.write(`[flow-worker-mcp-strip] failed to write ${dest}\n`);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
if (!preservesChannelTransport(cfg)) {
|
|
115
|
+
process.stderr.write(
|
|
116
|
+
`[flow-worker-mcp-strip] WARNING: ${src} did not define ${CHANNEL_SERVER_NAME} — ` +
|
|
117
|
+
`worker will boot but manager dispatch will fail. ` +
|
|
118
|
+
`Run "flow workspace init" in the workspace root to regenerate .mcp.json.\n`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
process.exit(0);
|
|
122
|
+
}
|