wogiflow 2.5.10 → 2.6.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.
|
@@ -270,8 +270,27 @@ function handleRequest(msg) {
|
|
|
270
270
|
};
|
|
271
271
|
const msgPath = require('node:path').join(messagesDir, `${msgId}.json`);
|
|
272
272
|
fs.writeFileSync(msgPath, JSON.stringify(msgObj, null, 2));
|
|
273
|
+
|
|
274
|
+
// Also POST to manager's channel port for real-time notification
|
|
275
|
+
const managerPort = process.env.WOGI_MANAGER_PORT;
|
|
276
|
+
if (managerPort) {
|
|
277
|
+
try {
|
|
278
|
+
const buf = Buffer.from(message, 'utf-8');
|
|
279
|
+
const req = http.request({
|
|
280
|
+
hostname: '127.0.0.1',
|
|
281
|
+
port: parseInt(managerPort, 10),
|
|
282
|
+
path: '/',
|
|
283
|
+
method: 'POST',
|
|
284
|
+
headers: { 'Content-Type': 'text/plain', 'Content-Length': buf.byteLength, 'X-Wogi-From': REPO_NAME }
|
|
285
|
+
});
|
|
286
|
+
req.on('error', () => { /* best effort */ });
|
|
287
|
+
req.write(buf);
|
|
288
|
+
req.end();
|
|
289
|
+
} catch (_err) { /* fallback is file */ }
|
|
290
|
+
}
|
|
291
|
+
|
|
273
292
|
sendResponse(msg.id, {
|
|
274
|
-
content: [{ type: 'text', text: `Message sent to manager (
|
|
293
|
+
content: [{ type: 'text', text: `Message sent to manager${managerPort ? ' (file + channel notification)' : ' (file only)'}.` }]
|
|
275
294
|
});
|
|
276
295
|
} catch (err) {
|
|
277
296
|
sendResponse(msg.id, {
|
package/lib/workspace.js
CHANGED
|
@@ -945,7 +945,8 @@ function generateMemberMcpConfigs(workspaceRoot, config) {
|
|
|
945
945
|
WOGI_CHANNEL_PORT: String(channelConfig.port),
|
|
946
946
|
WOGI_REPO_NAME: name,
|
|
947
947
|
WOGI_PEERS: peers,
|
|
948
|
-
WOGI_WORKSPACE_ROOT: workspaceRoot
|
|
948
|
+
WOGI_WORKSPACE_ROOT: workspaceRoot,
|
|
949
|
+
WOGI_MANAGER_PORT: String(config.channels.managerPort || (config.channels.basePort - 1))
|
|
949
950
|
}
|
|
950
951
|
}
|
|
951
952
|
}
|
|
@@ -989,22 +990,32 @@ description: "Workspace worker rules — reply to manager after every channel ta
|
|
|
989
990
|
|
|
990
991
|
You are **${name}** in a Wogi Workspace managed by an orchestrator.
|
|
991
992
|
|
|
992
|
-
## CRITICAL: Always Reply to the Manager
|
|
993
|
+
## CRITICAL: Always Reply to the Manager (port ${config.channels.managerPort || (config.channels.basePort - 1)})
|
|
993
994
|
|
|
994
|
-
When you receive a message via the workspace channel
|
|
995
|
+
When you receive a message via the workspace channel, you MUST send results back to the manager after completing the work. **The user only sees the manager terminal — if you don't reply, they never see your results.**
|
|
995
996
|
|
|
996
|
-
|
|
997
|
+
**PRIMARY method — curl to manager port (always works, even if MCP tools haven't loaded yet):**
|
|
998
|
+
|
|
999
|
+
\`\`\`bash
|
|
1000
|
+
curl -s -X POST http://localhost:${config.channels.managerPort || (config.channels.basePort - 1)} -H "Content-Type: text/plain" -H "X-Wogi-From: ${name}" -d "## Results
|
|
1001
|
+
|
|
1002
|
+
[your findings/summary here]"
|
|
1003
|
+
\`\`\`
|
|
1004
|
+
|
|
1005
|
+
**ALTERNATIVE — workspace_send_message MCP tool (if available):**
|
|
997
1006
|
|
|
998
1007
|
\`\`\`
|
|
999
1008
|
workspace_send_message(to: "manager", message: "## Results\\n\\n[your findings/summary here]")
|
|
1000
1009
|
\`\`\`
|
|
1001
1010
|
|
|
1002
|
-
|
|
1011
|
+
If the MCP tool is not available (shows "MCP servers still connecting"), use the curl command. **Do NOT write JSON files to .workspace/messages/ directly — that doesn't notify the manager.**
|
|
1012
|
+
|
|
1013
|
+
This is NON-OPTIONAL. Every channel-dispatched task MUST end with a reply to the manager.
|
|
1003
1014
|
|
|
1004
1015
|
## Peer Communication
|
|
1005
1016
|
|
|
1006
|
-
You can talk to peer repos
|
|
1007
|
-
${peerNames.map(p => `- \`
|
|
1017
|
+
You can talk to peer repos via curl or the MCP tool:
|
|
1018
|
+
${peerNames.map(p => `- \`curl -s -X POST http://localhost:${channelMembers[p]?.port || '???'} -H "X-Wogi-From: ${name}" -d "your message"\``).join('\n')}
|
|
1008
1019
|
|
|
1009
1020
|
## Autonomous Mode — Auto-Approve Everything
|
|
1010
1021
|
|
|
@@ -1023,7 +1034,7 @@ Only send a question to the manager (instead of results) when:
|
|
|
1023
1034
|
- The task requires **deleting user data** or making irreversible changes
|
|
1024
1035
|
- You are genuinely **stuck** and cannot proceed
|
|
1025
1036
|
|
|
1026
|
-
To escalate: \`
|
|
1037
|
+
To escalate: \`curl -s -X POST http://localhost:${config.channels.managerPort || (config.channels.basePort - 1)} -H "X-Wogi-From: ${name}" -d "## Need Decision: [describe the choice and options]"\`
|
|
1027
1038
|
|
|
1028
1039
|
For everything else — just do the work and report results.
|
|
1029
1040
|
|
|
@@ -1039,6 +1050,50 @@ For everything else — just do the work and report results.
|
|
|
1039
1050
|
fs.writeFileSync(path.join(rulesDir, 'worker-rules.md'), workerRule);
|
|
1040
1051
|
console.log(` ✓ ${name}/.claude/rules/workspace/worker-rules.md`);
|
|
1041
1052
|
}
|
|
1053
|
+
|
|
1054
|
+
// Generate .mcp.json at workspace root for the manager's channel server
|
|
1055
|
+
const managerPort = config.channels.managerPort || (config.channels.basePort - 1);
|
|
1056
|
+
let channelServerPath;
|
|
1057
|
+
try {
|
|
1058
|
+
channelServerPath = require.resolve('wogiflow/lib/workspace-channel-server.js');
|
|
1059
|
+
} catch (_err) {
|
|
1060
|
+
channelServerPath = path.join(__dirname, 'workspace-channel-server.js');
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
const allMembers = memberNames.map(n => `${n}:${channelMembers[n].port}`).join(',');
|
|
1064
|
+
const managerMcpConfig = {
|
|
1065
|
+
mcpServers: {
|
|
1066
|
+
'wogi-workspace-channel': {
|
|
1067
|
+
command: 'node',
|
|
1068
|
+
args: [channelServerPath],
|
|
1069
|
+
env: {
|
|
1070
|
+
WOGI_CHANNEL_PORT: String(managerPort),
|
|
1071
|
+
WOGI_REPO_NAME: 'manager',
|
|
1072
|
+
WOGI_PEERS: allMembers,
|
|
1073
|
+
WOGI_WORKSPACE_ROOT: workspaceRoot
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
};
|
|
1078
|
+
|
|
1079
|
+
// Merge with existing .mcp.json at workspace root
|
|
1080
|
+
const managerMcpPath = path.join(workspaceRoot, '.mcp.json');
|
|
1081
|
+
let existingManagerMcp = {};
|
|
1082
|
+
try {
|
|
1083
|
+
if (fs.existsSync(managerMcpPath)) {
|
|
1084
|
+
existingManagerMcp = JSON.parse(fs.readFileSync(managerMcpPath, 'utf-8'));
|
|
1085
|
+
}
|
|
1086
|
+
} catch (_err) { /* ignore */ }
|
|
1087
|
+
|
|
1088
|
+
const mergedManager = {
|
|
1089
|
+
...existingManagerMcp,
|
|
1090
|
+
mcpServers: {
|
|
1091
|
+
...(existingManagerMcp.mcpServers || {}),
|
|
1092
|
+
...managerMcpConfig.mcpServers
|
|
1093
|
+
}
|
|
1094
|
+
};
|
|
1095
|
+
fs.writeFileSync(managerMcpPath, JSON.stringify(mergedManager, null, 2));
|
|
1096
|
+
console.log(` ✓ .mcp.json (manager channel port ${managerPort}, workers: ${allMembers})`);
|
|
1042
1097
|
}
|
|
1043
1098
|
|
|
1044
1099
|
// ============================================================
|
|
@@ -1375,13 +1430,50 @@ function startWorkerSession(cwd) {
|
|
|
1375
1430
|
}
|
|
1376
1431
|
}
|
|
1377
1432
|
|
|
1433
|
+
// Manager mode: CWD is the workspace root itself (not a member)
|
|
1378
1434
|
if (!memberName || !memberChannel) {
|
|
1435
|
+
if (relativePath === '' || relativePath === '.') {
|
|
1436
|
+
// Start as workspace manager
|
|
1437
|
+
const managerPort = config.channels.managerPort || (config.channels.basePort - 1);
|
|
1438
|
+
const allMembers = Object.entries(config.channels.members)
|
|
1439
|
+
.map(([n, ch]) => `${n}:${ch.port}`)
|
|
1440
|
+
.join(',');
|
|
1441
|
+
|
|
1442
|
+
console.log(`Starting manager session (port ${managerPort})`);
|
|
1443
|
+
console.log(`Workers: ${allMembers}`);
|
|
1444
|
+
console.log('');
|
|
1445
|
+
|
|
1446
|
+
const env = {
|
|
1447
|
+
...process.env,
|
|
1448
|
+
WOGI_CHANNEL_PORT: String(managerPort),
|
|
1449
|
+
WOGI_REPO_NAME: 'manager',
|
|
1450
|
+
WOGI_PEERS: allMembers,
|
|
1451
|
+
WOGI_WORKSPACE_ROOT: workspaceRoot
|
|
1452
|
+
};
|
|
1453
|
+
|
|
1454
|
+
try {
|
|
1455
|
+
execSync('claude --dangerously-skip-permissions --dangerously-load-development-channels server:wogi-workspace-channel', {
|
|
1456
|
+
cwd,
|
|
1457
|
+
env,
|
|
1458
|
+
stdio: 'inherit'
|
|
1459
|
+
});
|
|
1460
|
+
} catch (err) {
|
|
1461
|
+
if (err.status && err.status !== 0 && err.signal !== 'SIGINT') {
|
|
1462
|
+
console.error(`Manager session exited with error (code ${err.status}): ${err.message}`);
|
|
1463
|
+
process.exit(err.status);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1379
1469
|
console.error(`Error: Current directory "${relativePath}" does not match any workspace member.`);
|
|
1380
1470
|
console.error('Members:', Object.keys(config.members).join(', '));
|
|
1471
|
+
console.error('Or run from the workspace root to start as manager.');
|
|
1381
1472
|
process.exit(1);
|
|
1382
1473
|
}
|
|
1383
1474
|
|
|
1384
|
-
// Build peer list
|
|
1475
|
+
// Build peer list (include manager port)
|
|
1476
|
+
const managerPort = config.channels.managerPort || (config.channels.basePort - 1);
|
|
1385
1477
|
const peers = Object.entries(config.channels.members)
|
|
1386
1478
|
.filter(([n]) => n !== memberName)
|
|
1387
1479
|
.map(([n, ch]) => `${n}:${ch.port}`)
|
|
@@ -1415,6 +1507,7 @@ function startWorkerSession(cwd) {
|
|
|
1415
1507
|
WOGI_CHANNEL_PORT: String(memberChannel.port),
|
|
1416
1508
|
WOGI_REPO_NAME: memberName,
|
|
1417
1509
|
WOGI_PEERS: peers,
|
|
1510
|
+
WOGI_MANAGER_PORT: String(managerPort),
|
|
1418
1511
|
WOGI_WORKSPACE_ROOT: workspaceRoot
|
|
1419
1512
|
};
|
|
1420
1513
|
|
package/package.json
CHANGED
|
@@ -61,22 +61,19 @@ runHook('Stop', async ({ parsedInput }) => {
|
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
// Workspace worker:
|
|
65
|
-
//
|
|
66
|
-
// The
|
|
67
|
-
|
|
68
|
-
if (process.env.WOGI_WORKSPACE_ROOT && process.env.WOGI_REPO_NAME) {
|
|
64
|
+
// Workspace worker: send results to manager via HTTP when stopping.
|
|
65
|
+
// Uses synchronous curl to guarantee delivery before the hook process exits.
|
|
66
|
+
// The async http.request approach was unreliable — process exited before request completed.
|
|
67
|
+
if (process.env.WOGI_MANAGER_PORT && process.env.WOGI_REPO_NAME && process.env.WOGI_REPO_NAME !== 'manager') {
|
|
69
68
|
try {
|
|
69
|
+
const { execSync } = require('node:child_process');
|
|
70
70
|
const fs = require('node:fs');
|
|
71
71
|
const path = require('node:path');
|
|
72
|
-
const crypto = require('node:crypto');
|
|
73
|
-
const workspaceRoot = process.env.WOGI_WORKSPACE_ROOT;
|
|
74
72
|
const repoName = process.env.WOGI_REPO_NAME;
|
|
75
|
-
const
|
|
76
|
-
fs.mkdirSync(messagesDir, { recursive: true });
|
|
73
|
+
const managerPort = process.env.WOGI_MANAGER_PORT;
|
|
77
74
|
|
|
78
75
|
// Build summary from available state
|
|
79
|
-
const
|
|
76
|
+
const summaryParts = [];
|
|
80
77
|
const { PATHS, safeJsonParse } = require('../../flow-utils');
|
|
81
78
|
|
|
82
79
|
// Get current/recently completed task info
|
|
@@ -86,63 +83,40 @@ runHook('Stop', async ({ parsedInput }) => {
|
|
|
86
83
|
const task = recentTask || inProgressTask;
|
|
87
84
|
|
|
88
85
|
if (task) {
|
|
89
|
-
|
|
90
|
-
if (task.type)
|
|
86
|
+
summaryParts.push(`**Task**: ${task.title || task.id}`);
|
|
87
|
+
if (task.type) summaryParts.push(`**Type**: ${task.type}`);
|
|
91
88
|
}
|
|
92
89
|
|
|
93
|
-
// Get
|
|
90
|
+
// Get changed files
|
|
94
91
|
try {
|
|
95
|
-
const logPath = path.join(PATHS.root, 'request-log.md');
|
|
96
|
-
if (fs.existsSync(logPath)) {
|
|
97
|
-
const stat = fs.statSync(logPath);
|
|
98
|
-
if (stat.size < 200 * 1024) {
|
|
99
|
-
const logContent = fs.readFileSync(logPath, 'utf-8');
|
|
100
|
-
const parts = logContent.split(/^### R-/m);
|
|
101
|
-
if (parts.length > 1) {
|
|
102
|
-
const lastEntry = parts[parts.length - 1];
|
|
103
|
-
if (lastEntry.length < 2000) {
|
|
104
|
-
summary.push(`**Log entry**:\n### R-${lastEntry.trim()}`);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
} catch (_err) { /* non-critical */ }
|
|
110
|
-
|
|
111
|
-
// Get git status for changed files
|
|
112
|
-
try {
|
|
113
|
-
const { execSync } = require('node:child_process');
|
|
114
92
|
const diff = execSync('git diff --name-only HEAD 2>/dev/null || true', { cwd: PATHS.root, encoding: 'utf-8' }).trim();
|
|
115
93
|
const staged = execSync('git diff --name-only --staged 2>/dev/null || true', { cwd: PATHS.root, encoding: 'utf-8' }).trim();
|
|
116
94
|
const allChanged = [...new Set([...diff.split('\n'), ...staged.split('\n')].filter(Boolean))];
|
|
117
95
|
if (allChanged.length > 0) {
|
|
118
|
-
|
|
96
|
+
summaryParts.push(`**Files changed**: ${allChanged.join(', ')}`);
|
|
119
97
|
}
|
|
120
98
|
} catch (_err) { /* non-critical */ }
|
|
121
99
|
|
|
122
|
-
const
|
|
123
|
-
const message = {
|
|
124
|
-
id: msgId,
|
|
125
|
-
from: repoName,
|
|
126
|
-
to: 'manager',
|
|
127
|
-
type: 'task-complete',
|
|
128
|
-
priority: 'medium',
|
|
129
|
-
timestamp: new Date().toISOString(),
|
|
130
|
-
subject: task ? `Completed: ${task.title || task.id}` : `Work completed by ${repoName}`,
|
|
131
|
-
body: summary.join('\n') || `${repoName} finished processing.`,
|
|
132
|
-
taskId: task?.id || null,
|
|
133
|
-
actionRequired: false,
|
|
134
|
-
status: 'pending'
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
fs.writeFileSync(path.join(messagesDir, `${msgId}.json`), JSON.stringify(message, null, 2));
|
|
100
|
+
const body = summaryParts.join('\n') || `${repoName} finished processing.`;
|
|
138
101
|
|
|
139
|
-
|
|
140
|
-
|
|
102
|
+
// PRIMARY: Synchronous curl to manager port — guaranteed to complete before exit
|
|
103
|
+
try {
|
|
104
|
+
execSync(
|
|
105
|
+
`curl -s -X POST http://127.0.0.1:${managerPort} -H "Content-Type: text/plain" -H "X-Wogi-From: ${repoName}" --data-binary @-`,
|
|
106
|
+
{ input: body, timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }
|
|
107
|
+
);
|
|
108
|
+
if (process.env.DEBUG) {
|
|
109
|
+
console.error(`[Stop] Sent results to manager via curl :${managerPort}`);
|
|
110
|
+
}
|
|
111
|
+
} catch (_err) {
|
|
112
|
+
// Manager might be offline — that's OK
|
|
113
|
+
if (process.env.DEBUG) {
|
|
114
|
+
console.error(`[Stop] curl to manager:${managerPort} failed: ${_err.message}`);
|
|
115
|
+
}
|
|
141
116
|
}
|
|
142
117
|
} catch (err) {
|
|
143
|
-
// Non-critical — best effort
|
|
144
118
|
if (process.env.DEBUG) {
|
|
145
|
-
console.error(`[Stop] Workspace
|
|
119
|
+
console.error(`[Stop] Workspace notification failed: ${err.message}`);
|
|
146
120
|
}
|
|
147
121
|
}
|
|
148
122
|
}
|