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 (written to ${msgPath}).` }]
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 (appears as a channel notification), 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
+ 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
- Use the \`workspace_send_message\` MCP tool:
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
- This is NON-OPTIONAL. Every channel-dispatched task MUST end with a workspace_send_message to "manager".
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 using the same tool:
1007
- ${peerNames.map(p => `- \`workspace_send_message(to: "${p}", message: "...")\``).join('\n')}
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: \`workspace_send_message(to: "manager", message: "## Need Decision\\n\\n[describe the choice and options]")\`
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wogiflow",
3
- "version": "2.5.10",
3
+ "version": "2.6.1",
4
4
  "description": "AI-powered development workflow management system with multi-model support",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -61,22 +61,19 @@ runHook('Stop', async ({ parsedInput }) => {
61
61
  };
62
62
  }
63
63
 
64
- // Workspace worker: auto-write results back to manager when stopping
65
- // This runs in the hook (not the AI), so it's guaranteed to execute.
66
- // The AI can't be relied on to call workspace_send_message the stop
67
- // hook fires before the AI gets a chance, or the AI forgets.
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 messagesDir = path.join(workspaceRoot, '.workspace', 'messages');
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 summary = [];
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
- summary.push(`**Task**: ${task.title || task.id}`);
90
- if (task.type) summary.push(`**Type**: ${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 last request-log entry
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
- summary.push(`**Files changed**: ${allChanged.join(', ')}`);
96
+ summaryParts.push(`**Files changed**: ${allChanged.join(', ')}`);
119
97
  }
120
98
  } catch (_err) { /* non-critical */ }
121
99
 
122
- const msgId = 'msg-' + crypto.randomBytes(4).toString('hex');
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
- if (process.env.DEBUG) {
140
- console.error(`[Stop] Workspace message written: ${msgId}`);
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 message failed: ${err.message}`);
119
+ console.error(`[Stop] Workspace notification failed: ${err.message}`);
146
120
  }
147
121
  }
148
122
  }