@shipers-dev/multi 0.9.3 → 0.9.5
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/dist/index.js +112 -7
- package/package.json +1 -1
- package/src/index.ts +72 -5
package/dist/index.js
CHANGED
|
@@ -5700,15 +5700,35 @@ async function ensureWorktree(workingDir, issueKey) {
|
|
|
5700
5700
|
import { parseArgs } from "util";
|
|
5701
5701
|
import { mkdirSync as mkdirSync3, existsSync as existsSync3, writeFileSync as writeFileSync3, readFileSync as readFileSync3, appendFileSync as appendFileSync2, unlinkSync, readdirSync, statSync } from "fs";
|
|
5702
5702
|
import { join as join3, dirname as dirname2 } from "path";
|
|
5703
|
+
// package.json
|
|
5704
|
+
var package_default = {
|
|
5705
|
+
name: "@shipers-dev/multi",
|
|
5706
|
+
version: "0.9.5",
|
|
5707
|
+
type: "module",
|
|
5708
|
+
bin: {
|
|
5709
|
+
"multi-agent": "./dist/index.js"
|
|
5710
|
+
},
|
|
5711
|
+
scripts: {
|
|
5712
|
+
dev: "bun run src/index.ts",
|
|
5713
|
+
build: "bun build src/index.ts --outdir=dist --target=bun"
|
|
5714
|
+
},
|
|
5715
|
+
dependencies: {
|
|
5716
|
+
"@zed-industries/agent-client-protocol": "^0.4.5",
|
|
5717
|
+
"@zed-industries/claude-code-acp": "^0.16.2"
|
|
5718
|
+
}
|
|
5719
|
+
};
|
|
5720
|
+
|
|
5721
|
+
// src/index.ts
|
|
5703
5722
|
var HOME2 = process.env.HOME || process.env.USERPROFILE || ".";
|
|
5704
5723
|
var MULTI_DIR = join3(HOME2, ".multi");
|
|
5705
5724
|
var CONFIG_PATH = join3(MULTI_DIR, "config.json");
|
|
5706
5725
|
var PID_PATH = join3(MULTI_DIR, "agent.pid");
|
|
5726
|
+
var PORT_PATH = join3(MULTI_DIR, "agent.port");
|
|
5707
5727
|
var LOG_PATH2 = join3(MULTI_DIR, "logs", "agent.log");
|
|
5708
5728
|
var SKILLS_DIR = join3(MULTI_DIR, "skills");
|
|
5709
5729
|
var STOP_PATH = join3(MULTI_DIR, "stop.flag");
|
|
5710
5730
|
var TASKS_DB_PATH = join3(MULTI_DIR, "tasks.db");
|
|
5711
|
-
var VERSION =
|
|
5731
|
+
var VERSION = package_default.version;
|
|
5712
5732
|
var COMMANDS = {
|
|
5713
5733
|
setup: "Register this device with a workspace",
|
|
5714
5734
|
connect: "Connect device to realtime hub and execute assigned tasks",
|
|
@@ -5716,7 +5736,8 @@ var COMMANDS = {
|
|
|
5716
5736
|
status: "Show current status",
|
|
5717
5737
|
stop: "Stop the running daemon",
|
|
5718
5738
|
restart: "Stop and relaunch the daemon in background",
|
|
5719
|
-
logs: "View execution logs"
|
|
5739
|
+
logs: "View execution logs",
|
|
5740
|
+
reset: "Reset acpx session for an issue (--issue <id>)"
|
|
5720
5741
|
};
|
|
5721
5742
|
function ensureDirs() {
|
|
5722
5743
|
for (const d of [MULTI_DIR, join3(MULTI_DIR, "logs"), SKILLS_DIR]) {
|
|
@@ -5746,7 +5767,8 @@ async function main() {
|
|
|
5746
5767
|
name: { type: "string" },
|
|
5747
5768
|
workspace: { type: "string" },
|
|
5748
5769
|
agent: { type: "string" },
|
|
5749
|
-
api: { type: "string" }
|
|
5770
|
+
api: { type: "string" },
|
|
5771
|
+
issue: { type: "string" }
|
|
5750
5772
|
},
|
|
5751
5773
|
allowPositionals: true,
|
|
5752
5774
|
strict: false
|
|
@@ -5787,6 +5809,9 @@ async function main() {
|
|
|
5787
5809
|
case "logs":
|
|
5788
5810
|
await cmdLogs();
|
|
5789
5811
|
break;
|
|
5812
|
+
case "reset":
|
|
5813
|
+
await cmdReset(args.values.issue);
|
|
5814
|
+
break;
|
|
5790
5815
|
default:
|
|
5791
5816
|
console.error(`Unknown command: ${command}`);
|
|
5792
5817
|
printHelp();
|
|
@@ -5807,6 +5832,7 @@ Commands:
|
|
|
5807
5832
|
stop ${COMMANDS.stop}
|
|
5808
5833
|
restart ${COMMANDS.restart}
|
|
5809
5834
|
logs ${COMMANDS.logs}
|
|
5835
|
+
reset ${COMMANDS.reset}
|
|
5810
5836
|
|
|
5811
5837
|
Options:
|
|
5812
5838
|
--name <name> Device name
|
|
@@ -6033,6 +6059,52 @@ async function cmdConnect(apiUrl, config) {
|
|
|
6033
6059
|
}
|
|
6034
6060
|
})();
|
|
6035
6061
|
}
|
|
6062
|
+
if (url.pathname === "/reset_session" && req.method === "POST") {
|
|
6063
|
+
if (req.headers.get("authorization") !== expectedAuth)
|
|
6064
|
+
return new Response("unauthorized", { status: 401 });
|
|
6065
|
+
return (async () => {
|
|
6066
|
+
try {
|
|
6067
|
+
const { issue_id, agent_type } = await req.json();
|
|
6068
|
+
if (!issue_id)
|
|
6069
|
+
return Response.json({ error: "issue_id required" }, { status: 400 });
|
|
6070
|
+
const entries = Array.from(running.values()).filter((e) => e.issueId === issue_id);
|
|
6071
|
+
for (const entry of entries) {
|
|
6072
|
+
entry.stopped = true;
|
|
6073
|
+
entry.stopReason = "session reset";
|
|
6074
|
+
try {
|
|
6075
|
+
entry.child?.kill("SIGTERM");
|
|
6076
|
+
} catch {}
|
|
6077
|
+
setTimeout(() => {
|
|
6078
|
+
try {
|
|
6079
|
+
entry.child?.kill("SIGKILL");
|
|
6080
|
+
} catch {}
|
|
6081
|
+
}, 3000);
|
|
6082
|
+
}
|
|
6083
|
+
const acpxTypes = agent_type ? [agent_type] : ["pi", "codex", "openclaw"];
|
|
6084
|
+
const sessionName = `issue-${issue_id}`;
|
|
6085
|
+
const cwd = process.env.HOME || process.cwd();
|
|
6086
|
+
const results = [];
|
|
6087
|
+
for (const t of acpxTypes) {
|
|
6088
|
+
try {
|
|
6089
|
+
const rm = Bun.spawn(["acpx", "--ttl", "0", "--cwd", cwd, t, "sessions", "close", sessionName], {
|
|
6090
|
+
stdout: "pipe",
|
|
6091
|
+
stderr: "pipe",
|
|
6092
|
+
stdin: "ignore"
|
|
6093
|
+
});
|
|
6094
|
+
const err = await new Response(rm.stderr).text();
|
|
6095
|
+
const exit = await rm.exited;
|
|
6096
|
+
results.push({ agent: t, exit, stderr: err.slice(0, 200) });
|
|
6097
|
+
} catch (e) {
|
|
6098
|
+
results.push({ agent: t, exit: -1, stderr: String(e).slice(0, 200) });
|
|
6099
|
+
}
|
|
6100
|
+
}
|
|
6101
|
+
log(`\uD83D\uDD04 reset_session ${issue_id} (killed=${entries.length}) ${results.map((r) => `${r.agent}:${r.exit}`).join(" ")}`);
|
|
6102
|
+
return Response.json({ ok: true, killed: entries.length, sessions: results });
|
|
6103
|
+
} catch (e) {
|
|
6104
|
+
return Response.json({ error: String(e) }, { status: 400 });
|
|
6105
|
+
}
|
|
6106
|
+
})();
|
|
6107
|
+
}
|
|
6036
6108
|
if (url.pathname === "/stop" && req.method === "POST") {
|
|
6037
6109
|
if (req.headers.get("authorization") !== expectedAuth)
|
|
6038
6110
|
return new Response("unauthorized", { status: 401 });
|
|
@@ -6069,6 +6141,9 @@ async function cmdConnect(apiUrl, config) {
|
|
|
6069
6141
|
}
|
|
6070
6142
|
});
|
|
6071
6143
|
log(`\uD83C\uDF10 Local server: http://127.0.0.1:${port}`);
|
|
6144
|
+
try {
|
|
6145
|
+
writeFileSync3(PORT_PATH, String(port));
|
|
6146
|
+
} catch {}
|
|
6072
6147
|
const cf = Bun.spawn(["cloudflared", "tunnel", "--no-autoupdate", "--url", `http://127.0.0.1:${port}`], {
|
|
6073
6148
|
stdout: "pipe",
|
|
6074
6149
|
stderr: "pipe",
|
|
@@ -6106,6 +6181,8 @@ async function cmdConnect(apiUrl, config) {
|
|
|
6106
6181
|
unlinkSync(PID_PATH);
|
|
6107
6182
|
if (existsSync3(STOP_PATH))
|
|
6108
6183
|
unlinkSync(STOP_PATH);
|
|
6184
|
+
if (existsSync3(PORT_PATH))
|
|
6185
|
+
unlinkSync(PORT_PATH);
|
|
6109
6186
|
db.close();
|
|
6110
6187
|
log("\uD83D\uDC4B Disconnected");
|
|
6111
6188
|
process.exit(0);
|
|
@@ -6695,7 +6772,7 @@ ${userPart}` : userPart;
|
|
|
6695
6772
|
}
|
|
6696
6773
|
async function buildPlanningPreamble(apiUrl, task) {
|
|
6697
6774
|
const depth = typeof task.planning_depth === "number" ? task.planning_depth : 0;
|
|
6698
|
-
if (depth >=
|
|
6775
|
+
if (depth >= PLANNING_DEPTH_LIMIT) {
|
|
6699
6776
|
return `# Planning (sub-task context)
|
|
6700
6777
|
|
|
6701
6778
|
You are acting on a sub-issue spawned by another agent. You MAY emit a \`multi-plan\` block to update your own issue's status (e.g. mark done/failed) but CANNOT create child issues or delegate further.
|
|
@@ -6740,7 +6817,7 @@ Syntax:
|
|
|
6740
6817
|
Rules:
|
|
6741
6818
|
- Omit the block entirely if no actions are needed.
|
|
6742
6819
|
- Max 10 actions per turn; additional actions are dropped.
|
|
6743
|
-
-
|
|
6820
|
+
- Planning depth is capped at ${PLANNING_DEPTH_LIMIT}: descendants beyond that depth may only \`update\` their own issue.
|
|
6744
6821
|
- \`create\` defaults \`project_id\` to the current project and \`parent_id\` to the current issue.
|
|
6745
6822
|
- \`update\` may change title, description, status (todo/in_progress/done/failed), priority, assignee_type, assignee_id.
|
|
6746
6823
|
- \`delegate\` is shorthand for reassigning and resetting status to todo.
|
|
@@ -6768,6 +6845,7 @@ function extractPlanActions(text) {
|
|
|
6768
6845
|
return out;
|
|
6769
6846
|
}
|
|
6770
6847
|
var PLAN_ACTION_LIMIT = 10;
|
|
6848
|
+
var PLANNING_DEPTH_LIMIT = 10;
|
|
6771
6849
|
async function executePlanActions(apiUrl, parentTask, actions, ctx) {
|
|
6772
6850
|
const lines = [];
|
|
6773
6851
|
let truncated = false;
|
|
@@ -6776,11 +6854,11 @@ async function executePlanActions(apiUrl, parentTask, actions, ctx) {
|
|
|
6776
6854
|
actions = actions.slice(0, PLAN_ACTION_LIMIT);
|
|
6777
6855
|
}
|
|
6778
6856
|
const depth = typeof parentTask.planning_depth === "number" ? parentTask.planning_depth : 0;
|
|
6779
|
-
if (depth >=
|
|
6857
|
+
if (depth >= PLANNING_DEPTH_LIMIT) {
|
|
6780
6858
|
const blocked = actions.filter((a) => a.type === "create" || a.type === "delegate").length;
|
|
6781
6859
|
actions = actions.filter((a) => a.type === "update");
|
|
6782
6860
|
if (blocked)
|
|
6783
|
-
lines.push(`- \u26A0 ${blocked} create/delegate action(s) blocked (planning depth limit)`);
|
|
6861
|
+
lines.push(`- \u26A0 ${blocked} create/delegate action(s) blocked (planning depth limit ${PLANNING_DEPTH_LIMIT})`);
|
|
6784
6862
|
}
|
|
6785
6863
|
const parentId = parentTask.issue_id;
|
|
6786
6864
|
const parentProjectId = await (async () => {
|
|
@@ -7228,6 +7306,33 @@ async function cmdStop() {
|
|
|
7228
7306
|
console.log(`Sent SIGTERM to ${pid}`);
|
|
7229
7307
|
} catch {}
|
|
7230
7308
|
}
|
|
7309
|
+
async function cmdReset(issueId) {
|
|
7310
|
+
if (!issueId) {
|
|
7311
|
+
console.error("Usage: multi-agent reset --issue <issue_id>");
|
|
7312
|
+
process.exit(2);
|
|
7313
|
+
}
|
|
7314
|
+
if (!existsSync3(PORT_PATH)) {
|
|
7315
|
+
console.error("Daemon not running (no port file).");
|
|
7316
|
+
process.exit(1);
|
|
7317
|
+
}
|
|
7318
|
+
const port = Number(readFileSync3(PORT_PATH, "utf8").trim());
|
|
7319
|
+
const config = loadConfig();
|
|
7320
|
+
if (!config.dispatchSecret) {
|
|
7321
|
+
console.error("No dispatchSecret in config \u2014 run `multi-agent setup` first.");
|
|
7322
|
+
process.exit(1);
|
|
7323
|
+
}
|
|
7324
|
+
const res = await fetch(`http://127.0.0.1:${port}/reset_session`, {
|
|
7325
|
+
method: "POST",
|
|
7326
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${config.dispatchSecret}` },
|
|
7327
|
+
body: JSON.stringify({ issue_id: issueId })
|
|
7328
|
+
});
|
|
7329
|
+
const body = await res.text();
|
|
7330
|
+
if (!res.ok) {
|
|
7331
|
+
console.error(`Reset failed (${res.status}): ${body}`);
|
|
7332
|
+
process.exit(1);
|
|
7333
|
+
}
|
|
7334
|
+
console.log(body);
|
|
7335
|
+
}
|
|
7231
7336
|
async function cmdRestart(apiUrl) {
|
|
7232
7337
|
if (existsSync3(PID_PATH)) {
|
|
7233
7338
|
const pid = Number(readFileSync3(PID_PATH, "utf8").trim());
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -9,16 +9,18 @@ import { ensureWorktree } from './worktree';
|
|
|
9
9
|
import { parseArgs } from 'util';
|
|
10
10
|
import { mkdirSync, existsSync, writeFileSync, readFileSync, appendFileSync, unlinkSync, readdirSync, statSync } from 'fs';
|
|
11
11
|
import { join, dirname } from 'path';
|
|
12
|
+
import pkg from '../package.json' with { type: 'json' };
|
|
12
13
|
|
|
13
14
|
const HOME = process.env.HOME || process.env.USERPROFILE || '.';
|
|
14
15
|
const MULTI_DIR = join(HOME, '.multi');
|
|
15
16
|
const CONFIG_PATH = join(MULTI_DIR, 'config.json');
|
|
16
17
|
const PID_PATH = join(MULTI_DIR, 'agent.pid');
|
|
18
|
+
const PORT_PATH = join(MULTI_DIR, 'agent.port');
|
|
17
19
|
const LOG_PATH = join(MULTI_DIR, 'logs', 'agent.log');
|
|
18
20
|
const SKILLS_DIR = join(MULTI_DIR, 'skills');
|
|
19
21
|
const STOP_PATH = join(MULTI_DIR, 'stop.flag');
|
|
20
22
|
const TASKS_DB_PATH = join(MULTI_DIR, 'tasks.db');
|
|
21
|
-
const VERSION =
|
|
23
|
+
const VERSION = (pkg as { version: string }).version;
|
|
22
24
|
|
|
23
25
|
const COMMANDS = {
|
|
24
26
|
setup: 'Register this device with a workspace',
|
|
@@ -28,6 +30,7 @@ const COMMANDS = {
|
|
|
28
30
|
stop: 'Stop the running daemon',
|
|
29
31
|
restart: 'Stop and relaunch the daemon in background',
|
|
30
32
|
logs: 'View execution logs',
|
|
33
|
+
reset: 'Reset acpx session for an issue (--issue <id>)',
|
|
31
34
|
} as const;
|
|
32
35
|
|
|
33
36
|
type Command = keyof typeof COMMANDS;
|
|
@@ -62,6 +65,7 @@ async function main() {
|
|
|
62
65
|
workspace: { type: 'string' },
|
|
63
66
|
agent: { type: 'string' },
|
|
64
67
|
api: { type: 'string' },
|
|
68
|
+
issue: { type: 'string' },
|
|
65
69
|
},
|
|
66
70
|
allowPositionals: true,
|
|
67
71
|
strict: false,
|
|
@@ -105,6 +109,9 @@ async function main() {
|
|
|
105
109
|
case 'logs':
|
|
106
110
|
await cmdLogs();
|
|
107
111
|
break;
|
|
112
|
+
case 'reset':
|
|
113
|
+
await cmdReset(args.values.issue);
|
|
114
|
+
break;
|
|
108
115
|
default:
|
|
109
116
|
console.error(`Unknown command: ${command}`);
|
|
110
117
|
printHelp();
|
|
@@ -126,6 +133,7 @@ Commands:
|
|
|
126
133
|
stop ${COMMANDS.stop}
|
|
127
134
|
restart ${COMMANDS.restart}
|
|
128
135
|
logs ${COMMANDS.logs}
|
|
136
|
+
reset ${COMMANDS.reset}
|
|
129
137
|
|
|
130
138
|
Options:
|
|
131
139
|
--name <name> Device name
|
|
@@ -354,6 +362,46 @@ async function cmdConnect(apiUrl: string, config: Config) {
|
|
|
354
362
|
}
|
|
355
363
|
})();
|
|
356
364
|
}
|
|
365
|
+
if (url.pathname === '/reset_session' && req.method === 'POST') {
|
|
366
|
+
if (req.headers.get('authorization') !== expectedAuth) return new Response('unauthorized', { status: 401 });
|
|
367
|
+
return (async () => {
|
|
368
|
+
try {
|
|
369
|
+
const { issue_id, agent_type } = await req.json() as { issue_id: string; agent_type?: string };
|
|
370
|
+
if (!issue_id) return Response.json({ error: 'issue_id required' }, { status: 400 });
|
|
371
|
+
|
|
372
|
+
// Kill any running acpx child for this issue.
|
|
373
|
+
const entries = Array.from(running.values()).filter((e) => e.issueId === issue_id);
|
|
374
|
+
for (const entry of entries) {
|
|
375
|
+
entry.stopped = true;
|
|
376
|
+
entry.stopReason = 'session reset';
|
|
377
|
+
try { entry.child?.kill('SIGTERM'); } catch {}
|
|
378
|
+
setTimeout(() => { try { entry.child?.kill('SIGKILL'); } catch {} }, 3000);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Close acpx session so next dispatch creates fresh. Only known acpx types.
|
|
382
|
+
const acpxTypes = agent_type ? [agent_type] : ['pi', 'codex', 'openclaw'];
|
|
383
|
+
const sessionName = `issue-${issue_id}`;
|
|
384
|
+
const cwd = process.env.HOME || process.cwd();
|
|
385
|
+
const results: { agent: string; exit: number; stderr: string }[] = [];
|
|
386
|
+
for (const t of acpxTypes) {
|
|
387
|
+
try {
|
|
388
|
+
const rm = Bun.spawn(['acpx', '--ttl', '0', '--cwd', cwd, t, 'sessions', 'close', sessionName], {
|
|
389
|
+
stdout: 'pipe', stderr: 'pipe', stdin: 'ignore',
|
|
390
|
+
});
|
|
391
|
+
const err = await new Response(rm.stderr as any).text();
|
|
392
|
+
const exit = await rm.exited;
|
|
393
|
+
results.push({ agent: t, exit, stderr: err.slice(0, 200) });
|
|
394
|
+
} catch (e) {
|
|
395
|
+
results.push({ agent: t, exit: -1, stderr: String(e).slice(0, 200) });
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
log(`🔄 reset_session ${issue_id} (killed=${entries.length}) ${results.map(r => `${r.agent}:${r.exit}`).join(' ')}`);
|
|
399
|
+
return Response.json({ ok: true, killed: entries.length, sessions: results });
|
|
400
|
+
} catch (e) {
|
|
401
|
+
return Response.json({ error: String(e) }, { status: 400 });
|
|
402
|
+
}
|
|
403
|
+
})();
|
|
404
|
+
}
|
|
357
405
|
if (url.pathname === '/stop' && req.method === 'POST') {
|
|
358
406
|
if (req.headers.get('authorization') !== expectedAuth) return new Response('unauthorized', { status: 401 });
|
|
359
407
|
return (async () => {
|
|
@@ -382,6 +430,7 @@ async function cmdConnect(apiUrl: string, config: Config) {
|
|
|
382
430
|
},
|
|
383
431
|
});
|
|
384
432
|
log(`🌐 Local server: http://127.0.0.1:${port}`);
|
|
433
|
+
try { writeFileSync(PORT_PATH, String(port)); } catch {}
|
|
385
434
|
|
|
386
435
|
// Spawn cloudflared quick tunnel
|
|
387
436
|
const cf = Bun.spawn(['cloudflared', 'tunnel', '--no-autoupdate', '--url', `http://127.0.0.1:${port}`], {
|
|
@@ -409,6 +458,7 @@ async function cmdConnect(apiUrl: string, config: Config) {
|
|
|
409
458
|
try { await apiClient.post(`${apiUrl}/api/devices/${config.deviceId}/heartbeat`, { status: 'offline', tunnel_url: null }); } catch {}
|
|
410
459
|
if (existsSync(PID_PATH)) unlinkSync(PID_PATH);
|
|
411
460
|
if (existsSync(STOP_PATH)) unlinkSync(STOP_PATH);
|
|
461
|
+
if (existsSync(PORT_PATH)) unlinkSync(PORT_PATH);
|
|
412
462
|
db.close();
|
|
413
463
|
log('👋 Disconnected');
|
|
414
464
|
process.exit(0);
|
|
@@ -922,7 +972,7 @@ async function handleRunTask(apiUrl: string, deviceId: string, task: any, detect
|
|
|
922
972
|
// the project, or delegate to other agents.
|
|
923
973
|
async function buildPlanningPreamble(apiUrl: string, task: any): Promise<string> {
|
|
924
974
|
const depth = typeof task.planning_depth === 'number' ? task.planning_depth : 0;
|
|
925
|
-
if (depth >=
|
|
975
|
+
if (depth >= PLANNING_DEPTH_LIMIT) {
|
|
926
976
|
return `# Planning (sub-task context)
|
|
927
977
|
|
|
928
978
|
You are acting on a sub-issue spawned by another agent. You MAY emit a \`multi-plan\` block to update your own issue's status (e.g. mark done/failed) but CANNOT create child issues or delegate further.
|
|
@@ -967,7 +1017,7 @@ Syntax:
|
|
|
967
1017
|
Rules:
|
|
968
1018
|
- Omit the block entirely if no actions are needed.
|
|
969
1019
|
- Max 10 actions per turn; additional actions are dropped.
|
|
970
|
-
-
|
|
1020
|
+
- Planning depth is capped at ${PLANNING_DEPTH_LIMIT}: descendants beyond that depth may only \`update\` their own issue.
|
|
971
1021
|
- \`create\` defaults \`project_id\` to the current project and \`parent_id\` to the current issue.
|
|
972
1022
|
- \`update\` may change title, description, status (todo/in_progress/done/failed), priority, assignee_type, assignee_id.
|
|
973
1023
|
- \`delegate\` is shorthand for reassigning and resetting status to todo.
|
|
@@ -998,6 +1048,7 @@ function extractPlanActions(text: string): PlanAction[] {
|
|
|
998
1048
|
}
|
|
999
1049
|
|
|
1000
1050
|
const PLAN_ACTION_LIMIT = 10;
|
|
1051
|
+
const PLANNING_DEPTH_LIMIT = 10;
|
|
1001
1052
|
|
|
1002
1053
|
async function executePlanActions(apiUrl: string, parentTask: any, actions: PlanAction[], ctx: RuntimeCtx): Promise<string> {
|
|
1003
1054
|
const lines: string[] = [];
|
|
@@ -1009,10 +1060,10 @@ async function executePlanActions(apiUrl: string, parentTask: any, actions: Plan
|
|
|
1009
1060
|
// Prevent agent recursion: a child turn's plan cannot itself spawn more children.
|
|
1010
1061
|
// `planning_depth` is carried on each dispatched task (set server-side from issue row).
|
|
1011
1062
|
const depth = typeof parentTask.planning_depth === 'number' ? parentTask.planning_depth : 0;
|
|
1012
|
-
if (depth >=
|
|
1063
|
+
if (depth >= PLANNING_DEPTH_LIMIT) {
|
|
1013
1064
|
const blocked = actions.filter(a => a.type === 'create' || a.type === 'delegate').length;
|
|
1014
1065
|
actions = actions.filter(a => a.type === 'update');
|
|
1015
|
-
if (blocked) lines.push(`- ⚠ ${blocked} create/delegate action(s) blocked (planning depth limit)`);
|
|
1066
|
+
if (blocked) lines.push(`- ⚠ ${blocked} create/delegate action(s) blocked (planning depth limit ${PLANNING_DEPTH_LIMIT})`);
|
|
1016
1067
|
}
|
|
1017
1068
|
const parentId = parentTask.issue_id;
|
|
1018
1069
|
const parentProjectId = await (async () => {
|
|
@@ -1406,6 +1457,22 @@ async function cmdStop() {
|
|
|
1406
1457
|
try { process.kill(pid, 'SIGTERM'); console.log(`Sent SIGTERM to ${pid}`); } catch {}
|
|
1407
1458
|
}
|
|
1408
1459
|
|
|
1460
|
+
async function cmdReset(issueId?: string) {
|
|
1461
|
+
if (!issueId) { console.error('Usage: multi-agent reset --issue <issue_id>'); process.exit(2); }
|
|
1462
|
+
if (!existsSync(PORT_PATH)) { console.error('Daemon not running (no port file).'); process.exit(1); }
|
|
1463
|
+
const port = Number(readFileSync(PORT_PATH, 'utf8').trim());
|
|
1464
|
+
const config = loadConfig();
|
|
1465
|
+
if (!config.dispatchSecret) { console.error('No dispatchSecret in config — run `multi-agent setup` first.'); process.exit(1); }
|
|
1466
|
+
const res = await fetch(`http://127.0.0.1:${port}/reset_session`, {
|
|
1467
|
+
method: 'POST',
|
|
1468
|
+
headers: { 'content-type': 'application/json', 'authorization': `Bearer ${config.dispatchSecret}` },
|
|
1469
|
+
body: JSON.stringify({ issue_id: issueId }),
|
|
1470
|
+
});
|
|
1471
|
+
const body = await res.text();
|
|
1472
|
+
if (!res.ok) { console.error(`Reset failed (${res.status}): ${body}`); process.exit(1); }
|
|
1473
|
+
console.log(body);
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1409
1476
|
async function cmdRestart(apiUrl: string) {
|
|
1410
1477
|
if (existsSync(PID_PATH)) {
|
|
1411
1478
|
const pid = Number(readFileSync(PID_PATH, 'utf8').trim());
|