@shipers-dev/multi 0.23.0 → 0.23.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 (2) hide show
  1. package/dist/index.js +99 -10
  2. 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.0",
15003
+ version: "0.23.1",
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);
@@ -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 });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipers-dev/multi",
3
- "version": "0.23.0",
3
+ "version": "0.23.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "multi-agent": "./dist/index.js"