alanbox 0.1.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.
Files changed (41) hide show
  1. package/0commondflowv1/AGENTS.md +51 -0
  2. package/0commondflowv1/res/three-lens-review.js +124 -0
  3. package/0commondflowv1/src/AGENTS.md +26 -0
  4. package/0commondflowv1/src/args.js +75 -0
  5. package/0commondflowv1/src/cli.js +121 -0
  6. package/0commondflowv1/src/commands/AGENTS.md +29 -0
  7. package/0commondflowv1/src/commands/doctor.js +17 -0
  8. package/0commondflowv1/src/commands/info.js +33 -0
  9. package/0commondflowv1/src/commands/install.js +247 -0
  10. package/0commondflowv1/src/commands/swarm/auto.js +270 -0
  11. package/0commondflowv1/src/commands/swarm/custom.js +60 -0
  12. package/0commondflowv1/src/commands/swarm/index.js +20 -0
  13. package/0commondflowv1/src/core/AGENTS.md +31 -0
  14. package/0commondflowv1/src/core/handoff.js +100 -0
  15. package/0commondflowv1/src/core/prompt-builder.js +93 -0
  16. package/0commondflowv1/src/core/prompt-templates.js +92 -0
  17. package/0commondflowv1/src/core/storage.js +98 -0
  18. package/0commondflowv1/src/core/swarm-executor.js +167 -0
  19. package/0commondflowv1/src/core/workers.js +172 -0
  20. package/0commondflowv1/src/core/workflow-planner.js +366 -0
  21. package/0commondflowv1/src/core/workflow-storage.js +123 -0
  22. package/0commondflowv1/src/prompt/AGENTS.md +16 -0
  23. package/0commondflowv1/src/prompt/default.md +30 -0
  24. package/0commondflowv1/src/prompt/reviewer.md +59 -0
  25. package/0commondflowv1/src/prompt/synthesizer.md +31 -0
  26. package/0commondflowv1/src/prompt/verifier.md +31 -0
  27. package/0commondflowv1/src/runner/AGENTS.md +24 -0
  28. package/0commondflowv1/src/runner/codex-runner.js +519 -0
  29. package/0commondflowv1/src/runner/config.json +16 -0
  30. package/README.md +31 -0
  31. package/bin/multirunagent.js +15 -0
  32. package/hooks/hooks.json +18 -0
  33. package/mcp/README.md +5 -0
  34. package/package.json +45 -0
  35. package/plugin/AGENTS.md +14 -0
  36. package/plugin/plugin.json +36 -0
  37. package/scripts/sub-codex-hook.ps1 +15 -0
  38. package/skills/AGENTS.md +17 -0
  39. package/skills/aibox-swam/SKILL.md +77 -0
  40. package/skills/sub-codex-doctor/SKILL.md +27 -0
  41. package/skills/sub-codex-swarm/SKILL.md +56 -0
@@ -0,0 +1,247 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const readline = require('readline');
4
+
5
+ const INSTALL_TARGETS = new Set(['codex', 'claude', 'both']);
6
+
7
+ async function runInstall(context = {}) {
8
+ const packageRoot = path.resolve(__dirname, '..', '..', '..');
9
+ const home = process.env.USERPROFILE || process.env.HOME;
10
+ if (!home) {
11
+ throw new Error('cannot resolve user home directory');
12
+ }
13
+
14
+ const target = await resolveInstallTarget(context.args || {});
15
+ const installed = [];
16
+
17
+ if (target === 'codex' || target === 'both') {
18
+ installed.push(installCodex({ home, packageRoot }));
19
+ }
20
+
21
+ if (target === 'claude' || target === 'both') {
22
+ installed.push(installClaude({ home, packageRoot }));
23
+ }
24
+
25
+ console.log('');
26
+ for (const result of installed) {
27
+ console.log(`[${result.name}]`);
28
+ for (const line of result.lines) {
29
+ console.log(` ${line}`);
30
+ }
31
+ }
32
+ console.log('');
33
+ console.log('Restart or refresh the target agent so it can discover the installed skills.');
34
+ }
35
+
36
+ function installCodex({ home, packageRoot }) {
37
+ const pluginName = 'alanbox';
38
+ const pluginRoot = path.join(home, 'plugins', pluginName);
39
+ const marketplaceRoot = path.join(home, '.agents', 'plugins');
40
+ const marketplacePath = path.join(marketplaceRoot, 'marketplace.json');
41
+ const codexHome = process.env.CODEX_HOME || path.join(home, '.codex');
42
+ const codexSkillsRoot = path.join(codexHome, 'skills');
43
+ const packageSkillsRoot = path.join(packageRoot, 'skills');
44
+ const packageHooksRoot = path.join(packageRoot, 'hooks');
45
+ const packageScriptsRoot = path.join(packageRoot, 'scripts');
46
+ const packageMcpRoot = path.join(packageRoot, 'mcp');
47
+
48
+ replaceDir(path.join(packageRoot, 'plugin'), path.join(pluginRoot, '.codex-plugin'));
49
+ replaceDir(packageSkillsRoot, path.join(pluginRoot, 'skills'));
50
+ replaceDir(packageHooksRoot, path.join(pluginRoot, 'hooks'));
51
+ replaceDir(packageScriptsRoot, path.join(pluginRoot, 'scripts'));
52
+ replaceDir(packageMcpRoot, path.join(pluginRoot, 'mcp'));
53
+ copyOptionalFile(path.join(packageRoot, 'README.md'), path.join(pluginRoot, 'README.md'));
54
+ installSkills(packageSkillsRoot, codexSkillsRoot);
55
+ installResourceDir(packageHooksRoot, path.join(codexHome, 'hooks', pluginName));
56
+ installResourceDir(packageMcpRoot, path.join(codexHome, 'mcp', pluginName));
57
+ ensureMarketplace({ marketplaceRoot, marketplacePath, pluginName });
58
+
59
+ return {
60
+ name: 'codex',
61
+ lines: [
62
+ `plugin: ${pluginRoot}`,
63
+ `skills: ${codexSkillsRoot}`,
64
+ `hooks: ${path.join(codexHome, 'hooks', pluginName)}`,
65
+ `mcp: ${path.join(codexHome, 'mcp', pluginName)}`,
66
+ `marketplace: ${marketplacePath}`,
67
+ ],
68
+ };
69
+ }
70
+
71
+ function installClaude({ home, packageRoot }) {
72
+ const pluginName = 'alanbox';
73
+ const claudeHome = process.env.CLAUDE_HOME || path.join(home, '.claude');
74
+ const pluginRoot = path.join(claudeHome, 'plugins', pluginName);
75
+ const claudeSkillsRoot = path.join(claudeHome, 'skill');
76
+ const packageSkillsRoot = path.join(packageRoot, 'skills');
77
+ const packageHooksRoot = path.join(packageRoot, 'hooks');
78
+ const packageScriptsRoot = path.join(packageRoot, 'scripts');
79
+ const packageMcpRoot = path.join(packageRoot, 'mcp');
80
+
81
+ replaceDir(packageSkillsRoot, path.join(pluginRoot, 'skills'));
82
+ replaceDir(packageHooksRoot, path.join(pluginRoot, 'hooks'));
83
+ replaceDir(packageScriptsRoot, path.join(pluginRoot, 'scripts'));
84
+ replaceDir(packageMcpRoot, path.join(pluginRoot, 'mcp'));
85
+ copyOptionalFile(path.join(packageRoot, 'README.md'), path.join(pluginRoot, 'README.md'));
86
+ installSkills(packageSkillsRoot, claudeSkillsRoot);
87
+ installResourceDir(packageHooksRoot, path.join(claudeHome, 'hooks', pluginName));
88
+ installResourceDir(packageMcpRoot, path.join(claudeHome, 'mcp', pluginName));
89
+
90
+ return {
91
+ name: 'claude',
92
+ lines: [
93
+ `plugin resources: ${pluginRoot}`,
94
+ `skills: ${claudeSkillsRoot}`,
95
+ `hooks: ${path.join(claudeHome, 'hooks', pluginName)}`,
96
+ `mcp: ${path.join(claudeHome, 'mcp', pluginName)}`,
97
+ ],
98
+ };
99
+ }
100
+
101
+ async function resolveInstallTarget(args) {
102
+ const explicit = normalizeTarget(args.target || args.to || args.platform);
103
+ if (explicit) return explicit;
104
+
105
+ if (args.both) return 'both';
106
+ if (args.codex && args.claude) return 'both';
107
+ if (args.codex) return 'codex';
108
+ if (args.claude) return 'claude';
109
+
110
+ return promptInstallTarget();
111
+ }
112
+
113
+ function normalizeTarget(value) {
114
+ if (!value) return '';
115
+ const normalized = String(value).trim().toLowerCase();
116
+ if (normalized === 'all') return 'both';
117
+ if (!INSTALL_TARGETS.has(normalized)) {
118
+ throw new Error('invalid install target. Use --target codex, --target claude, or --target both.');
119
+ }
120
+ return normalized;
121
+ }
122
+
123
+ async function promptInstallTarget() {
124
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
125
+ throw new Error('install target is required in non-interactive mode. Use --target codex, --target claude, or --target both.');
126
+ }
127
+
128
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
129
+ const question = (text) => new Promise((resolve) => rl.question(text, resolve));
130
+
131
+ try {
132
+ for (;;) {
133
+ const answer = String(await question('Install alanbox to [1] Codex, [2] Claude, [3] both? (default: 3) ')).trim().toLowerCase();
134
+ if (!answer || answer === '3' || answer === 'both' || answer === 'b' || answer === 'all') return 'both';
135
+ if (answer === '1' || answer === 'codex' || answer === 'c') return 'codex';
136
+ if (answer === '2' || answer === 'claude' || answer === 'cl') return 'claude';
137
+ console.log('Please choose 1, 2, 3, codex, claude, or both.');
138
+ }
139
+ } finally {
140
+ rl.close();
141
+ }
142
+ }
143
+
144
+ function copyDir(src, dst) {
145
+ if (!fs.existsSync(src)) return false;
146
+ fs.mkdirSync(dst, { recursive: true });
147
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
148
+ const from = path.join(src, entry.name);
149
+ const to = path.join(dst, entry.name);
150
+ if (entry.isDirectory()) {
151
+ copyDir(from, to);
152
+ } else {
153
+ copyFile(from, to);
154
+ }
155
+ }
156
+ return true;
157
+ }
158
+
159
+ function replaceDir(src, dst) {
160
+ if (!fs.existsSync(src)) return false;
161
+ removeDir(dst);
162
+ return copyDir(src, dst);
163
+ }
164
+
165
+ function installResourceDir(src, dst) {
166
+ if (!fs.existsSync(src)) return false;
167
+ removeDir(dst);
168
+ return copyDir(src, dst);
169
+ }
170
+
171
+ function copyFile(src, dst) {
172
+ fs.mkdirSync(path.dirname(dst), { recursive: true });
173
+ fs.copyFileSync(src, dst);
174
+ }
175
+
176
+ function copyOptionalFile(src, dst) {
177
+ if (!fs.existsSync(src)) return false;
178
+ copyFile(src, dst);
179
+ return true;
180
+ }
181
+
182
+ function installSkills(srcRoot, dstRoot) {
183
+ if (!fs.existsSync(srcRoot)) return;
184
+ fs.mkdirSync(dstRoot, { recursive: true });
185
+
186
+ for (const entry of fs.readdirSync(srcRoot, { withFileTypes: true })) {
187
+ if (!entry.isDirectory()) continue;
188
+
189
+ const srcSkill = path.join(srcRoot, entry.name);
190
+ const dstSkill = path.join(dstRoot, entry.name);
191
+ const skillFile = path.join(srcSkill, 'SKILL.md');
192
+ if (!fs.existsSync(skillFile)) continue;
193
+
194
+ removeDir(dstSkill);
195
+ copyDir(srcSkill, dstSkill);
196
+ }
197
+ }
198
+
199
+ function removeDir(target) {
200
+ if (!fs.existsSync(target)) return;
201
+ fs.rmSync(target, { recursive: true, force: true });
202
+ }
203
+
204
+ function ensureMarketplace({ marketplaceRoot, marketplacePath, pluginName }) {
205
+ fs.mkdirSync(marketplaceRoot, { recursive: true });
206
+ const marketplace = fs.existsSync(marketplacePath)
207
+ ? readJson(marketplacePath)
208
+ : { name: 'personal', interface: { displayName: 'Personal' }, plugins: [] };
209
+
210
+ marketplace.name = marketplace.name || 'personal';
211
+ marketplace.interface = marketplace.interface || { displayName: 'Personal' };
212
+ marketplace.plugins = Array.isArray(marketplace.plugins) ? marketplace.plugins : [];
213
+
214
+ const entry = {
215
+ name: pluginName,
216
+ source: {
217
+ source: 'local',
218
+ path: './plugins/alanbox',
219
+ },
220
+ policy: {
221
+ installation: 'AVAILABLE',
222
+ authentication: 'ON_INSTALL',
223
+ },
224
+ category: 'Productivity',
225
+ };
226
+
227
+ const index = marketplace.plugins.findIndex((item) => item.name === pluginName);
228
+ if (index >= 0) {
229
+ marketplace.plugins[index] = entry;
230
+ } else {
231
+ marketplace.plugins.push(entry);
232
+ }
233
+
234
+ fs.writeFileSync(marketplacePath, JSON.stringify(marketplace, null, 2), 'utf8');
235
+ }
236
+
237
+ function readJson(filePath) {
238
+ try {
239
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
240
+ } catch {
241
+ return { name: 'personal', interface: { displayName: 'Personal' }, plugins: [] };
242
+ }
243
+ }
244
+
245
+ module.exports = {
246
+ runInstall,
247
+ };
@@ -0,0 +1,270 @@
1
+ /**
2
+ * swarm --auto 命令处理器。
3
+ * 根据自然语言目标生成本地 GOAP workflow,先推荐/等待确认,再复用 swarm executor 执行。
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const readline = require('readline/promises');
8
+ const { stdin: input, stdout: output } = require('process');
9
+ const { buildContext } = require('../../core/prompt-builder');
10
+ const { planWorkflow, workflowToTasks, replanWorkflow, hashPlan } = require('../../core/workflow-planner');
11
+ const {
12
+ createWorkflowRun,
13
+ loadWorkflowRun,
14
+ saveRunMetadata,
15
+ savePlan,
16
+ saveState,
17
+ appendStep,
18
+ readPlan,
19
+ readState,
20
+ } = require('../../core/workflow-storage');
21
+ const { executeTaskGraph } = require('../../core/swarm-executor');
22
+
23
+ async function runAutoSwarm({ args, config, cwd, timeoutMs }) {
24
+ const namespace = args.namespace || config.defaultNamespace;
25
+ const maxReplans = args['max-replans'] === undefined ? 1 : Number(args['max-replans']);
26
+
27
+ if (args.resume) {
28
+ await resumeWorkflow({ args, config, cwd, timeoutMs, namespace, maxReplans });
29
+ return;
30
+ }
31
+
32
+ const goal = args._.join(' ').trim();
33
+ if (!goal) {
34
+ throw new Error('missing auto goal. Example: alanbox swarm --auto "帮我检查项目" -a "codex:reviewer:复核代码"');
35
+ }
36
+
37
+ const run = createWorkflowRun({ namespace, goal, cwd });
38
+ const workflow = planWorkflow({
39
+ goal,
40
+ cwd,
41
+ namespace,
42
+ agentSpecs: args.agent || [],
43
+ planHint: args.planHint || '',
44
+ config,
45
+ });
46
+
47
+ const approved = await ensureApproved({ args, run, workflow, status: 'awaiting_confirmation' });
48
+ if (!approved) return;
49
+
50
+ await executeWorkflow({ args, config, cwd, timeoutMs, run, workflow, maxReplans });
51
+ }
52
+
53
+ async function resumeWorkflow({ args, config, cwd, timeoutMs, namespace, maxReplans }) {
54
+ const run = loadWorkflowRun(namespace, args.resume);
55
+ const workflow = readPlan(run);
56
+ const state = readState(run);
57
+ const actualHash = hashPlan(workflow);
58
+ if (workflow.planHash && workflow.planHash !== actualHash) {
59
+ throw new Error(`plan hash mismatch for ${run.runId}; refuse to resume a modified plan`);
60
+ }
61
+
62
+ const approved = await ensureApproved({ args, run, workflow, status: state.status || 'awaiting_confirmation', resumed: true });
63
+ if (!approved) return;
64
+
65
+ await executeWorkflow({ args, config, cwd: run.cwd || cwd, timeoutMs, run, workflow, maxReplans });
66
+ }
67
+
68
+ async function ensureApproved({ args, run, workflow, status, resumed = false }) {
69
+ savePendingRun({ run, workflow, status, resumed });
70
+
71
+ if (args.y) {
72
+ appendStep(run, { type: 'approval', result: 'approved', source: '-y' });
73
+ saveState(run, { status: 'approved', approved: true, planHash: workflow.planHash });
74
+ saveRunMetadata(run, { status: 'approved', planHash: workflow.planHash });
75
+ return true;
76
+ }
77
+
78
+ if (!isInteractive()) {
79
+ appendStep(run, { type: 'approval_required', reason: 'non_interactive' });
80
+ writePlanSummary({ run, workflow, status: 'awaiting_confirmation', stream: process.stderr });
81
+ process.exitCode = 2;
82
+ return false;
83
+ }
84
+
85
+ writePlanSummary({ run, workflow, status: resumed ? 'resume_waiting_confirmation' : 'awaiting_confirmation', stream: process.stdout });
86
+ const rl = readline.createInterface({ input, output });
87
+ try {
88
+ const answer = (await rl.question('输入 yes/approve 执行 workflow,其他输入取消:')).trim().toLowerCase();
89
+ if (!['yes', 'y', 'approve', 'approved'].includes(answer)) {
90
+ appendStep(run, { type: 'approval', result: 'denied', answer });
91
+ saveState(run, { status: 'cancelled', approved: false, planHash: workflow.planHash });
92
+ saveRunMetadata(run, { status: 'cancelled', planHash: workflow.planHash });
93
+ process.exitCode = 2;
94
+ return false;
95
+ }
96
+ } finally {
97
+ rl.close();
98
+ }
99
+
100
+ appendStep(run, { type: 'approval', result: 'approved', source: 'tty' });
101
+ saveState(run, { status: 'approved', approved: true, planHash: workflow.planHash });
102
+ saveRunMetadata(run, { status: 'approved', planHash: workflow.planHash });
103
+ return true;
104
+ }
105
+
106
+ function savePendingRun({ run, workflow, status, resumed }) {
107
+ savePlan(run, workflow);
108
+ saveState(run, {
109
+ status,
110
+ approved: false,
111
+ planHash: workflow.planHash,
112
+ worldState: workflow.worldState,
113
+ resumeCommand: `alanbox swarm --resume ${run.runId} --namespace ${run.namespace} -y`,
114
+ });
115
+ saveRunMetadata(run, { status, planHash: workflow.planHash });
116
+ appendStep(run, { type: resumed ? 'resume_plan_loaded' : 'plan_created', status, planHash: workflow.planHash });
117
+ }
118
+
119
+ async function executeWorkflow({ args, config, cwd, timeoutMs, run, workflow, maxReplans }) {
120
+ const injected = buildContext({
121
+ context: args.context,
122
+ contextFile: args['context-file'],
123
+ skillFile: args['skill-file'],
124
+ cwd: process.cwd(),
125
+ });
126
+ const tasks = workflowToTasks(workflow, run.namespace);
127
+ appendStep(run, { type: 'execution_started', actionIds: tasks.map((task) => task.id) });
128
+ saveState(run, {
129
+ status: 'running',
130
+ approved: true,
131
+ planHash: workflow.planHash,
132
+ worldState: { ...workflow.worldState, approved: true },
133
+ });
134
+ saveRunMetadata(run, { status: 'running', planHash: workflow.planHash });
135
+
136
+ const result = await executeTaskGraph({
137
+ args: { ...args, 'parallel-workers': true },
138
+ config,
139
+ cwd,
140
+ timeoutMs,
141
+ tasks,
142
+ namespace: run.namespace,
143
+ runDir: run.runDir,
144
+ resultDir: run.resultDir,
145
+ memoryDir: run.memoryDir,
146
+ injected,
147
+ failOnHandoffStatus: true,
148
+ onTaskStart: (task) => appendStep(run, { type: 'action_started', actionId: task.id, beforeState: workflow.worldState }),
149
+ onTaskFinish: (task, entry) => appendStep(run, {
150
+ type: 'action_finished',
151
+ actionId: task?.id,
152
+ ok: entry.ok,
153
+ code: entry.code,
154
+ error: entry.error,
155
+ handoffStatus: entry.handoff?.status,
156
+ outputPath: entry.outputPath,
157
+ }),
158
+ });
159
+
160
+ if (result.success) {
161
+ saveState(run, {
162
+ status: 'completed',
163
+ approved: true,
164
+ planHash: workflow.planHash,
165
+ result,
166
+ worldState: { ...workflow.worldState, executedActions: tasks.map((task) => task.id), goalVerified: true },
167
+ });
168
+ saveRunMetadata(run, { status: 'completed', planHash: workflow.planHash });
169
+ emitExecutionResult(args, run, workflow, result);
170
+ return;
171
+ }
172
+
173
+ const failures = result.results
174
+ .filter((item) => !item.ok)
175
+ .map((item) => ({ id: item.id, error: item.error || item.handoff?.summary || `exit ${item.code}` }));
176
+ appendStep(run, { type: 'execution_failed', failures });
177
+
178
+ if (maxReplans > 0) {
179
+ const replanned = replanWorkflow({
180
+ workflow,
181
+ failures,
182
+ cwd,
183
+ namespace: run.namespace,
184
+ config,
185
+ planHint: args.planHint || '',
186
+ });
187
+ savePlan(run, replanned);
188
+ saveState(run, {
189
+ status: 'awaiting_confirmation',
190
+ approved: false,
191
+ planHash: replanned.planHash,
192
+ previousPlanHash: workflow.planHash,
193
+ failures,
194
+ replanRemaining: maxReplans - 1,
195
+ });
196
+ saveRunMetadata(run, { status: 'awaiting_confirmation', planHash: replanned.planHash });
197
+ appendStep(run, { type: 'replan_created', previousPlanHash: workflow.planHash, planHash: replanned.planHash });
198
+ if (args.y && isRetryOnly(workflow, replanned)) {
199
+ await executeWorkflow({ args: { ...args, y: true }, config, cwd, timeoutMs, run, workflow: replanned, maxReplans: maxReplans - 1 });
200
+ return;
201
+ }
202
+ writePlanSummary({ run, workflow: replanned, status: 'awaiting_confirmation', stream: process.stderr });
203
+ process.exitCode = 2;
204
+ return;
205
+ }
206
+
207
+ saveState(run, {
208
+ status: 'failed',
209
+ approved: true,
210
+ planHash: workflow.planHash,
211
+ failures,
212
+ result,
213
+ });
214
+ saveRunMetadata(run, { status: 'failed', planHash: workflow.planHash });
215
+ emitExecutionResult(args, run, workflow, result);
216
+ process.exitCode = 1;
217
+ }
218
+
219
+ function isRetryOnly(previous, next) {
220
+ const previousTargets = new Set(previous.actions.map((action) => `${action.type}:${action.target || ''}:${action.provider || 'codex'}`));
221
+ return next.actions
222
+ .filter((action) => action.type === 'review')
223
+ .every((action) => previousTargets.has(`${action.type}:${action.target || ''}:${action.provider || 'codex'}`));
224
+ }
225
+
226
+ function emitExecutionResult(args, run, workflow, result) {
227
+ const stream = result.success ? process.stdout : process.stderr;
228
+ stream.write(`\n[auto-swarm] ${result.success ? 'completed' : 'failed'}\n`);
229
+ stream.write(`runId: ${run.runId}\n`);
230
+ stream.write(`runDir: ${run.runDir}\n`);
231
+ if (result.errors.length) {
232
+ stream.write(`errors:\n${result.errors.map((item) => `- ${item}`).join('\n')}\n`);
233
+ }
234
+ }
235
+
236
+ function writePlanSummary({ run, workflow, status, stream }) {
237
+ const lines = [
238
+ '',
239
+ `# auto workflow 推荐 (${status})`,
240
+ '',
241
+ `runId: ${run.runId}`,
242
+ `runDir: ${run.runDir}`,
243
+ `目标: ${workflow.goal}`,
244
+ workflow.planHint ? `补充描述: ${workflow.planHint}` : '',
245
+ '',
246
+ '## Action 顺序',
247
+ ...workflow.phases.map((phase) => {
248
+ const actionText = phase.actionIds.join(', ');
249
+ return `- ${phase.title}${phase.parallel ? '(并行)' : ''}: ${actionText}`;
250
+ }),
251
+ '',
252
+ '## 预期 WorldState 变化',
253
+ ...workflow.expectedWorldStateChanges.map((item) => `- ${item}`),
254
+ '',
255
+ '## 风险',
256
+ ...workflow.risks.map((item) => `- ${item}`),
257
+ '',
258
+ `确认执行: alanbox swarm --resume ${run.runId} --namespace ${run.namespace} -y`,
259
+ '',
260
+ ].filter((line) => line !== '');
261
+ stream.write(`${lines.join('\n')}\n`);
262
+ }
263
+
264
+ function isInteractive() {
265
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
266
+ }
267
+
268
+ module.exports = {
269
+ runAutoSwarm,
270
+ };
@@ -0,0 +1,60 @@
1
+ /**
2
+ * 手动 swarm 命令处理器。
3
+ * 规划并运行多个 provider worker(Codex / Claude / 已配置 worker),支持默认串行交接与并行执行。
4
+ *
5
+ * Worker 规格通过 --worker(或兼容别名 --task)传入,可重复多次:
6
+ * "<platform-or-worker>:<role>:<prompt>"
7
+ */
8
+
9
+ const path = require('path');
10
+ const { buildContext } = require('../../core/prompt-builder');
11
+ const { parseWorkerSpecs } = require('../../core/workers');
12
+ const { resolveRunDir } = require('../../core/storage');
13
+ const { executeTaskGraph } = require('../../core/swarm-executor');
14
+
15
+ async function runCustomSwarm({ args, config, cwd, timeoutMs }) {
16
+ const specs = args.task || [];
17
+ if (specs.length === 0) {
18
+ throw new Error('missing --worker. Example: node index.js swarm --worker "codex-e7:lead:..." --worker "codex-19b:reviewer:..."');
19
+ }
20
+
21
+ const injected = buildContext({
22
+ context: args.context || args._.join(' ').trim(),
23
+ contextFile: args['context-file'],
24
+ skillFile: args['skill-file'],
25
+ cwd: process.cwd(),
26
+ });
27
+ const namespace = args.namespace || config.defaultNamespace;
28
+ const runDir = resolveRunDir(namespace);
29
+ const resultDir = path.join(runDir, 'results');
30
+ const memoryDir = path.join(runDir, 'memory');
31
+ const tasks = parseWorkerSpecs(specs, Boolean(args['parallel-workers'] || args.parallel), config)
32
+ .map((task) => ({ ...task, namespace }));
33
+
34
+ const result = await executeTaskGraph({
35
+ args,
36
+ config,
37
+ cwd,
38
+ timeoutMs,
39
+ tasks,
40
+ namespace,
41
+ runDir,
42
+ resultDir,
43
+ memoryDir,
44
+ injected,
45
+ });
46
+
47
+ for (const item of result.results) {
48
+ if (item.error) {
49
+ console.error(`[swarm][问题] ${item.error}`);
50
+ }
51
+ }
52
+
53
+ if (result.errors.length > 0) {
54
+ process.exitCode = 1;
55
+ }
56
+ }
57
+
58
+ module.exports = {
59
+ runCustomSwarm,
60
+ };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * swarm 命令入口与路由。
3
+ * `--auto` / `--resume` 委托 auto workflow(auto.js),否则走手动 worker 编排(custom.js)。
4
+ */
5
+
6
+ const { runAutoSwarm } = require('./auto');
7
+ const { runCustomSwarm } = require('./custom');
8
+
9
+ async function runSwarm({ args, config, cwd, timeoutMs }) {
10
+ if (args.auto || args.resume) {
11
+ await runAutoSwarm({ args, config, cwd, timeoutMs });
12
+ return;
13
+ }
14
+
15
+ await runCustomSwarm({ args, config, cwd, timeoutMs });
16
+ }
17
+
18
+ module.exports = {
19
+ runSwarm,
20
+ };
@@ -0,0 +1,31 @@
1
+ ## core
2
+
3
+ `0commondflowv1/src/core` 存放 worker 编排协议、handoff 协议、prompt 构造和本地结果存储辅助函数,是 `swarm` 使用的核心逻辑。
4
+
5
+ **Important:** 这里的输出格式、prompt 协议和路径会被命令、skills、README 和用户历史 run 依赖。改协议或存储结构前,要考虑旧结果读取兼容,并同步更新说明文档。
6
+
7
+ ### Important files
8
+
9
+ - `workers.js` — 解析 worker spec、解析 `--worker "[spec1,spec2]"` 并行阶段、归一化 worker 名称、构建依赖层。
10
+ - `swarm-executor.js` — 普通 swarm 和 auto workflow 共用的 task graph 执行器;负责 runner 调用、结果写入和 handoff 汇总,`swarm --parallel-workers` 也走这里。
11
+ - `workflow-planner.js` — 本地最小 GOAP planner;把自然语言目标、`-a` agent spec 和 `-p` plan hint 转成 phases/actions/dependencies。
12
+ - `workflow-storage.js` — auto workflow 的 AgentDB-lite 文件存储;写入 run/plan/state/steps JSON,不替代旧 swarm results/memory。
13
+ - `prompt-templates.js` — 从 `0commondflowv1/src/prompt/<role>.md` 读取 auto workflow role 模板并安全 fallback。
14
+ - `handoff.js` — 构造 prior results context,并解析 / 生成 `HANDOFF_JSON`。
15
+ - `prompt-builder.js` — 构造 role、worker、platform、namespace、context、skill 和 task 的最终 prompt。
16
+ - `storage.js` — 写入 worker markdown 输出和 memory JSON;默认根目录是 `%USERPROFILE%\.multirunagent`。
17
+
18
+ ### Implementation notes
19
+
20
+ - worker spec 应兼容 `codex:role:prompt`、`claude:role:prompt` 和未来 worker 名称形式;普通 swarm 的 `--worker "[spec1,spec2]"` 表示同一阶段并行,阶段之间仍按依赖顺序串行。
21
+ - `HANDOFF_JSON` 稳定字段要保持兼容:`status`、`summary`、`files`、`findings`、`nextSteps`、`artifacts`;`verification` 是可选扩展字段,缺失时不得破坏旧 worker 输出。
22
+ - 子 worker 最终 prompt 协议和 `core/handoff.js` 必须同步维护;artifact 示例应指向 `%USERPROFILE%\.multirunagent\<namespace>`。
23
+ - auto workflow 启用 `failOnHandoffStatus` 时必须要求有效解析到 `HANDOFF_JSON` 且 status 为 `completed`;缺失结构化 handoff 不能被 fallback 误判为成功。
24
+ - 路径片段必须经过清洗,避免 namespace、worker、role、runId 或模板 role 中的特殊字符写出预期目录。
25
+ - 不要把用户级结果存储改回项目内 `.sub-codex`;旧 `.sub-codex` 只作为历史位置看待。
26
+ - auto workflow 没有 dry-run 预览;非交互(无 TTY)且未 `-y` 的真实 auto 请求会保存 pending run 并返回退出码 2,等待 `swarm --resume <runId> --namespace <ns> -y`。
27
+
28
+ ### Verification
29
+
30
+ - 从包根目录运行:`npm run validate`
31
+ - 存储路径或 prompt 改动后,需实际运行一次 swarm(已无 dry-run 预览)并检查 `%USERPROFILE%\.multirunagent\<namespace>` 下的 `runDir`、`memoryDir` 和注入 prompt。