@shipers-dev/multi 0.23.0 → 0.23.2
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 +110 -15
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -15000,7 +15000,7 @@ import { parseArgs as parseArgs2 } from "util";
|
|
|
15000
15000
|
// package.json
|
|
15001
15001
|
var package_default = {
|
|
15002
15002
|
name: "@shipers-dev/multi",
|
|
15003
|
-
version: "0.23.
|
|
15003
|
+
version: "0.23.2",
|
|
15004
15004
|
type: "module",
|
|
15005
15005
|
bin: {
|
|
15006
15006
|
"multi-agent": "./dist/index.js"
|
|
@@ -31195,6 +31195,41 @@ class RequestError extends Error {
|
|
|
31195
31195
|
// src/_impl/acp-runner.ts
|
|
31196
31196
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync4 } from "fs";
|
|
31197
31197
|
import { dirname as dirname5, join as join6 } from "path";
|
|
31198
|
+
|
|
31199
|
+
// src/_impl/workspace-mutex.ts
|
|
31200
|
+
var tails = new Map;
|
|
31201
|
+
var WRITE_TOOLS = new Set([
|
|
31202
|
+
"Edit",
|
|
31203
|
+
"Write",
|
|
31204
|
+
"Bash",
|
|
31205
|
+
"NotebookEdit",
|
|
31206
|
+
"mcp__acp__Edit",
|
|
31207
|
+
"mcp__acp__Write"
|
|
31208
|
+
]);
|
|
31209
|
+
function isWriteTool(name) {
|
|
31210
|
+
if (!name)
|
|
31211
|
+
return false;
|
|
31212
|
+
return WRITE_TOOLS.has(name);
|
|
31213
|
+
}
|
|
31214
|
+
async function withWorkspaceWrite(workspaceId, fn2) {
|
|
31215
|
+
const prev = tails.get(workspaceId) ?? Promise.resolve();
|
|
31216
|
+
let release;
|
|
31217
|
+
const next = new Promise((res) => {
|
|
31218
|
+
release = res;
|
|
31219
|
+
});
|
|
31220
|
+
tails.set(workspaceId, prev.then(() => next));
|
|
31221
|
+
try {
|
|
31222
|
+
await prev;
|
|
31223
|
+
return await fn2();
|
|
31224
|
+
} finally {
|
|
31225
|
+
release();
|
|
31226
|
+
if (tails.get(workspaceId) === prev.then(() => next)) {
|
|
31227
|
+
tails.delete(workspaceId);
|
|
31228
|
+
}
|
|
31229
|
+
}
|
|
31230
|
+
}
|
|
31231
|
+
|
|
31232
|
+
// src/_impl/acp-runner.ts
|
|
31198
31233
|
function ensureBypassPermissions(cwd) {
|
|
31199
31234
|
try {
|
|
31200
31235
|
const dir = join6(cwd, ".claude");
|
|
@@ -31429,8 +31464,13 @@ ${entries2}` } });
|
|
|
31429
31464
|
const alwaysOpt = opts2.find((op) => /always/i.test(op.name || "") || /allow_always/i.test(op.kind || ""));
|
|
31430
31465
|
const allowOpt = opts2.find((op) => /allow/i.test(op.kind || "") || /allow/i.test(op.name || ""));
|
|
31431
31466
|
const chosen = alwaysOpt || allowOpt;
|
|
31432
|
-
if (chosen)
|
|
31467
|
+
if (chosen) {
|
|
31468
|
+
const toolName = tc.title || tc.toolName || "";
|
|
31469
|
+
if (o.workspaceId && isWriteTool(toolName)) {
|
|
31470
|
+
return await withWorkspaceWrite(o.workspaceId, async () => ({ outcome: { outcome: "selected", optionId: chosen.optionId } }));
|
|
31471
|
+
}
|
|
31433
31472
|
return { outcome: { outcome: "selected", optionId: chosen.optionId } };
|
|
31473
|
+
}
|
|
31434
31474
|
}
|
|
31435
31475
|
if (toolKey && allowCache.has(toolKey)) {
|
|
31436
31476
|
const allowOpt = params.options.find((op) => /allow/i.test(op.kind || "") || /allow/i.test(op.name || ""));
|
|
@@ -32137,6 +32177,39 @@ var ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
|
32137
32177
|
var ID_LENGTH = 21;
|
|
32138
32178
|
var generate = customAlphabet(ALPHABET, ID_LENGTH);
|
|
32139
32179
|
var ID_REGEX = new RegExp(`^[${ALPHABET}]+$`);
|
|
32180
|
+
// ../lib/agent-workspaces.ts
|
|
32181
|
+
var AGENT_WORKSPACE_STATUSES = [
|
|
32182
|
+
"initializing",
|
|
32183
|
+
"setup_pending",
|
|
32184
|
+
"ready",
|
|
32185
|
+
"archived"
|
|
32186
|
+
];
|
|
32187
|
+
var AgentWorkspaceStatusSchema = exports_external.enum(AGENT_WORKSPACE_STATUSES);
|
|
32188
|
+
var AgentWorkspaceSchema = exports_external.object({
|
|
32189
|
+
id: exports_external.string(),
|
|
32190
|
+
project_id: exports_external.string(),
|
|
32191
|
+
issue_id: exports_external.string().nullable(),
|
|
32192
|
+
branch: exports_external.string(),
|
|
32193
|
+
worktree_path: exports_external.string().nullable(),
|
|
32194
|
+
device_id: exports_external.string().nullable(),
|
|
32195
|
+
status: AgentWorkspaceStatusSchema,
|
|
32196
|
+
agent_session_id: exports_external.string().nullable(),
|
|
32197
|
+
runtime: exports_external.string().nullable(),
|
|
32198
|
+
created_at: exports_external.number(),
|
|
32199
|
+
archived_at: exports_external.number().nullable()
|
|
32200
|
+
}).meta({ id: "AgentWorkspace" });
|
|
32201
|
+
var CreateAgentWorkspaceSchema = exports_external.object({
|
|
32202
|
+
project_id: exports_external.string(),
|
|
32203
|
+
issue_id: exports_external.string().nullable().optional(),
|
|
32204
|
+
branch: exports_external.string().min(1),
|
|
32205
|
+
device_id: exports_external.string().nullable().optional(),
|
|
32206
|
+
runtime: exports_external.string().nullable().optional()
|
|
32207
|
+
}).meta({ id: "CreateAgentWorkspace" });
|
|
32208
|
+
var TransitionAgentWorkspaceSchema = exports_external.object({
|
|
32209
|
+
status: AgentWorkspaceStatusSchema,
|
|
32210
|
+
worktree_path: exports_external.string().nullable().optional(),
|
|
32211
|
+
agent_session_id: exports_external.string().nullable().optional()
|
|
32212
|
+
}).meta({ id: "TransitionAgentWorkspace" });
|
|
32140
32213
|
// src/_impl/legacy-main.ts
|
|
32141
32214
|
import { parseArgs } from "util";
|
|
32142
32215
|
import { mkdirSync as mkdirSync5, existsSync as existsSync7, writeFileSync as writeFileSync5, readFileSync as readFileSync7, appendFileSync as appendFileSync3, unlinkSync as unlinkSync3, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
@@ -32423,17 +32496,12 @@ async function cmdConnect(apiUrl, config2) {
|
|
|
32423
32496
|
}
|
|
32424
32497
|
function pickNext() {
|
|
32425
32498
|
const busyAgents = Array.from(running3.values()).map((e) => e.agentId).filter((v) => !!v);
|
|
32426
|
-
const busyIssues = Array.from(running3.values()).map((e) => e.issueId).filter((v) => !!v);
|
|
32427
32499
|
const clauses = [];
|
|
32428
32500
|
const binds = [];
|
|
32429
32501
|
if (busyAgents.length) {
|
|
32430
32502
|
clauses.push(`(agent_id IS NULL OR agent_id NOT IN (${busyAgents.map(() => "?").join(",")}))`);
|
|
32431
32503
|
binds.push(...busyAgents);
|
|
32432
32504
|
}
|
|
32433
|
-
if (busyIssues.length) {
|
|
32434
|
-
clauses.push(`(issue_id IS NULL OR issue_id NOT IN (${busyIssues.map(() => "?").join(",")}))`);
|
|
32435
|
-
binds.push(...busyIssues);
|
|
32436
|
-
}
|
|
32437
32505
|
const where = clauses.length ? `AND ${clauses.join(" AND ")}` : "";
|
|
32438
32506
|
const sql = `SELECT id, payload, agent_id, issue_id FROM tasks WHERE status = 'queued' ${where} ORDER BY created_at ASC LIMIT 1`;
|
|
32439
32507
|
return db.query(sql).get(...binds);
|
|
@@ -32446,8 +32514,6 @@ async function cmdConnect(apiUrl, config2) {
|
|
|
32446
32514
|
const ids4 = resolvePayloadIds(row);
|
|
32447
32515
|
if (ids4.agent_id && Array.from(running3.values()).some((e) => e.agentId === ids4.agent_id))
|
|
32448
32516
|
return;
|
|
32449
|
-
if (ids4.issue_id && Array.from(running3.values()).some((e) => e.issueId === ids4.issue_id))
|
|
32450
|
-
return;
|
|
32451
32517
|
db.run("UPDATE tasks SET status = 'running', started_at = unixepoch(), attempts = attempts + 1 WHERE id = ?", [row.id]);
|
|
32452
32518
|
const entry = { agentId: ids4.agent_id || "", issueId: ids4.issue_id, startedAt: Date.now(), child: null, worktreePath: "" };
|
|
32453
32519
|
running3.set(row.id, entry);
|
|
@@ -32632,9 +32698,9 @@ async function cmdConnect(apiUrl, config2) {
|
|
|
32632
32698
|
{
|
|
32633
32699
|
const pending3 = await heartbeat();
|
|
32634
32700
|
if (pending3 > 0)
|
|
32635
|
-
drainOfflineDispatches(apiUrl, config2.deviceId, config2.token, db, () => schedule2());
|
|
32701
|
+
drainOfflineDispatches(apiUrl, config2.deviceId, config2.token, db, () => schedule2(), () => alive);
|
|
32636
32702
|
}
|
|
32637
|
-
drainOfflineDispatches(apiUrl, config2.deviceId, config2.token, db, () => schedule2());
|
|
32703
|
+
drainOfflineDispatches(apiUrl, config2.deviceId, config2.token, db, () => schedule2(), () => alive);
|
|
32638
32704
|
let alive = true;
|
|
32639
32705
|
const shutdown = async (reason) => {
|
|
32640
32706
|
if (!alive)
|
|
@@ -32683,7 +32749,7 @@ async function cmdConnect(apiUrl, config2) {
|
|
|
32683
32749
|
try {
|
|
32684
32750
|
const pending3 = await heartbeat();
|
|
32685
32751
|
if (pending3 > 0)
|
|
32686
|
-
drainOfflineDispatches(apiUrl, config2.deviceId, config2.token, db, () => schedule2());
|
|
32752
|
+
drainOfflineDispatches(apiUrl, config2.deviceId, config2.token, db, () => schedule2(), () => alive);
|
|
32687
32753
|
} catch (e) {
|
|
32688
32754
|
log3(`heartbeat error after tunnel restart: ${String(e)}`);
|
|
32689
32755
|
}
|
|
@@ -32740,7 +32806,7 @@ async function cmdConnect(apiUrl, config2) {
|
|
|
32740
32806
|
try {
|
|
32741
32807
|
const pending3 = await heartbeat();
|
|
32742
32808
|
if (pending3 > 0)
|
|
32743
|
-
drainOfflineDispatches(apiUrl, config2.deviceId, config2.token, db, () => schedule2());
|
|
32809
|
+
drainOfflineDispatches(apiUrl, config2.deviceId, config2.token, db, () => schedule2(), () => alive);
|
|
32744
32810
|
} catch (e) {
|
|
32745
32811
|
log3(`heartbeat error: ${String(e)}`);
|
|
32746
32812
|
}
|
|
@@ -32935,6 +33001,13 @@ async function handleRunTask(apiUrl, deviceId, task, detected, ctx) {
|
|
|
32935
33001
|
workingDir = wt.path;
|
|
32936
33002
|
worktreeBranch = wt.branch;
|
|
32937
33003
|
await postStream(apiUrl, issueId, "worktree_created", { path: wt.path, branch: wt.branch, reused: !wt.created });
|
|
33004
|
+
if (task.workspace_id && task.project_id) {
|
|
33005
|
+
try {
|
|
33006
|
+
await apiClient.post(`${apiUrl}/api/agent-workspaces/${task.workspace_id}/ready?project_id=${encodeURIComponent(task.project_id)}`, { status: "ready", worktree_path: wt.path });
|
|
33007
|
+
} catch (e) {
|
|
33008
|
+
await postStream(apiUrl, issueId, "worktree_error", { message: `agent-workspace ack failed: ${fmtError(e)}` });
|
|
33009
|
+
}
|
|
33010
|
+
}
|
|
32938
33011
|
} catch (e) {
|
|
32939
33012
|
await postStream(apiUrl, issueId, "worktree_error", { message: fmtError(e) });
|
|
32940
33013
|
}
|
|
@@ -33262,6 +33335,8 @@ ${userPart}` : userPart;
|
|
|
33262
33335
|
issueId,
|
|
33263
33336
|
deviceId,
|
|
33264
33337
|
prompt,
|
|
33338
|
+
workspaceId: task.workspace_id || issueId,
|
|
33339
|
+
workspaceSessionId: task.workspace_session_id || null,
|
|
33265
33340
|
sessionId: task.session_id || null,
|
|
33266
33341
|
adapterBin,
|
|
33267
33342
|
autonomy: task.autonomy_level,
|
|
@@ -33271,6 +33346,11 @@ ${userPart}` : userPart;
|
|
|
33271
33346
|
try {
|
|
33272
33347
|
await apiClient.post(`${apiUrl}/api/issues/${issueId}/session`, { session_id: sid });
|
|
33273
33348
|
} catch {}
|
|
33349
|
+
if (task.workspace_session_id) {
|
|
33350
|
+
try {
|
|
33351
|
+
await apiClient.patch(`${apiUrl}/api/sessions/${task.workspace_session_id}?workspace_id=${encodeURIComponent(task.workspace_id || issueId)}`, { provider_session_id: sid, status: "running" });
|
|
33352
|
+
} catch {}
|
|
33353
|
+
}
|
|
33274
33354
|
},
|
|
33275
33355
|
onSpawn: (child) => {
|
|
33276
33356
|
if (ctx?.runEntry) {
|
|
@@ -33569,7 +33649,7 @@ async function executePlanActions(apiUrl, parentTask, actions, ctx) {
|
|
|
33569
33649
|
if (blocked3)
|
|
33570
33650
|
lines.push(`- \u26A0 ${blocked3} non-update action(s) blocked (planning depth limit ${PLANNING_DEPTH_LIMIT})`);
|
|
33571
33651
|
}
|
|
33572
|
-
const SUBCAPS = { "agent.create": 2, "skill.create": 3, "skill.attach": 5, "skill.detach": 5, "agent.update": 5 };
|
|
33652
|
+
const SUBCAPS = { "agent.create": 2, "skill.create": 3, "skill.attach": 5, "skill.detach": 5, "agent.update": 5, "session.create": 3 };
|
|
33573
33653
|
const counts = {};
|
|
33574
33654
|
actions = actions.filter((a) => {
|
|
33575
33655
|
const cap = SUBCAPS[a.type];
|
|
@@ -33660,6 +33740,15 @@ async function executePlanActions(apiUrl, parentTask, actions, ctx) {
|
|
|
33660
33740
|
continue;
|
|
33661
33741
|
}
|
|
33662
33742
|
lines.push(`- \u23F3 skill.create "${a.name}" queued for human review (op ${res.data?.pending_op_id})`);
|
|
33743
|
+
} else if (a.type === "session.create") {
|
|
33744
|
+
const res = await apiClient.post(`${apiUrl}/api/sessions`, { workspace_id: a.workspace_id, agent_id: a.agent_id, role: a.role, device_id: a.device_id }, { headers });
|
|
33745
|
+
if (!res.success) {
|
|
33746
|
+
lines.push(`- \u274C session.create role=${a.role}: ${res.error || res.status}`);
|
|
33747
|
+
continue;
|
|
33748
|
+
}
|
|
33749
|
+
const sess = res.data?.session;
|
|
33750
|
+
const reused = res.data?.reused;
|
|
33751
|
+
lines.push(`- ${reused ? "\u21BA" : "\u2713"} session.${reused ? "reuse" : "create"} ${sess?.id?.slice(0, 8) || "?"} (role=${a.role})`);
|
|
33663
33752
|
} else if (a.type === "skill.attach" || a.type === "skill.detach") {
|
|
33664
33753
|
const action = a.type === "skill.attach" ? "attach_skill" : "detach_skill";
|
|
33665
33754
|
const res = await apiClient.post(`${apiUrl}/api/agent_ops/agents/mutate`, { action, agent_id: a.agent_id, skill_id: a.skill_id }, { headers });
|
|
@@ -33759,7 +33848,9 @@ async function ackDispatch(apiUrl, dispatchId, secret) {
|
|
|
33759
33848
|
log3(`ack dispatch ${dispatchId} failed: ${String(e)}`);
|
|
33760
33849
|
}
|
|
33761
33850
|
}
|
|
33762
|
-
async function drainOfflineDispatches(apiUrl, deviceId, secret, db, onEnqueued) {
|
|
33851
|
+
async function drainOfflineDispatches(apiUrl, deviceId, secret, db, onEnqueued, isAlive) {
|
|
33852
|
+
if (!isAlive())
|
|
33853
|
+
return;
|
|
33763
33854
|
let list;
|
|
33764
33855
|
try {
|
|
33765
33856
|
list = await apiClient.get(`${apiUrl}/api/devices/${deviceId}/dispatches/pending`);
|
|
@@ -33772,6 +33863,10 @@ async function drainOfflineDispatches(apiUrl, deviceId, secret, db, onEnqueued)
|
|
|
33772
33863
|
return;
|
|
33773
33864
|
log3(`\uD83E\uDEA3 draining ${rows.length} offline dispatch(es)`);
|
|
33774
33865
|
for (const r of rows) {
|
|
33866
|
+
if (!isAlive()) {
|
|
33867
|
+
log3("drain aborted: shutting down");
|
|
33868
|
+
return;
|
|
33869
|
+
}
|
|
33775
33870
|
try {
|
|
33776
33871
|
const res = await fetch(`${apiUrl}/api/issues/dispatches/${r.id}/claim`, {
|
|
33777
33872
|
method: "POST",
|