@shipers-dev/multi 0.38.5 → 0.39.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 +208 -133
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -33834,7 +33834,7 @@ function parsePlanBlocks(text) {
33834
33834
  }
33835
33835
  return { actions, errors: errors3 };
33836
33836
  }
33837
- var PLAN_SCHEMA_VERSION = 1, Priority, AssigneeType, IssueStatus, SessionRole, SkillFile, PlanActionSchema, PlanEnvelopeSchema, UiBlockSchema, UI_FENCE_RE, FENCE_RE;
33837
+ var PLAN_SCHEMA_VERSION = 2, Priority, AssigneeType, IssueStatus, SessionRole, SkillFile, PlanActionSchema, PlanEnvelopeSchema, UiBlockSchema, UI_FENCE_RE, FENCE_RE;
33838
33838
  var init_plans = __esm(() => {
33839
33839
  init_zod();
33840
33840
  Priority = exports_external.enum(["low", "medium", "high"]);
@@ -33870,6 +33870,11 @@ var init_plans = __esm(() => {
33870
33870
  id: exports_external.string().min(1),
33871
33871
  assignee_id: exports_external.string().min(1)
33872
33872
  }),
33873
+ exports_external.object({
33874
+ type: exports_external.literal("issue.comment"),
33875
+ id: exports_external.string().min(1),
33876
+ body: exports_external.string().min(1).max(8000)
33877
+ }),
33873
33878
  exports_external.object({
33874
33879
  type: exports_external.literal("issue.delete"),
33875
33880
  id: exports_external.string().min(1)
@@ -34023,7 +34028,7 @@ var init_lib = __esm(() => {
34023
34028
 
34024
34029
  // src/_impl/git-enforce.ts
34025
34030
  import { spawn as spawn2 } from "node:child_process";
34026
- import { existsSync as existsSync8 } from "node:fs";
34031
+ import { existsSync as existsSync9 } from "node:fs";
34027
34032
  import { join as join9 } from "node:path";
34028
34033
  function run4(cwd, cmd, args2) {
34029
34034
  return new Promise((resolve2) => {
@@ -34063,7 +34068,7 @@ function enforceCommitAndPush(baseWorkingDir, issueKey, mode = "enforce") {
34063
34068
  };
34064
34069
  if (mode === "off")
34065
34070
  return result;
34066
- if (!existsSync8(wt))
34071
+ if (!existsSync9(wt))
34067
34072
  return result;
34068
34073
  if (!(yield* isGitRepo2(wt)))
34069
34074
  return result;
@@ -34151,7 +34156,7 @@ var git = (cwd, args2, op) => exports_Effect.tryPromise({
34151
34156
  try: () => run4(cwd, "git", args2),
34152
34157
  catch: (cause3) => new GitError({ op, message: `git ${args2.join(" ")} threw`, cause: cause3 })
34153
34158
  }), isGitRepo2 = (dir) => exports_Effect.gen(function* () {
34154
- if (!existsSync8(dir))
34159
+ if (!existsSync9(dir))
34155
34160
  return false;
34156
34161
  const r = yield* git(dir, ["rev-parse", "--is-inside-work-tree"], "rev-parse");
34157
34162
  return r.code === 0 && r.stdout === "true";
@@ -34163,14 +34168,14 @@ var init_git_enforce = __esm(() => {
34163
34168
 
34164
34169
  // src/_impl/outbox.ts
34165
34170
  import { Database as Database2 } from "bun:sqlite";
34166
- import { existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
34171
+ import { existsSync as existsSync10, mkdirSync as mkdirSync7 } from "fs";
34167
34172
  import { homedir } from "os";
34168
34173
  import { join as join10 } from "path";
34169
34174
  function ensureDb() {
34170
34175
  if (db)
34171
34176
  return db;
34172
- if (!existsSync9(MULTI_DIR3))
34173
- mkdirSync6(MULTI_DIR3, { recursive: true });
34177
+ if (!existsSync10(MULTI_DIR3))
34178
+ mkdirSync7(MULTI_DIR3, { recursive: true });
34174
34179
  db = new Database2(TASKS_DB_PATH2);
34175
34180
  db.exec(`
34176
34181
  CREATE TABLE IF NOT EXISTS stream_outbox (
@@ -34308,12 +34313,12 @@ var init_outbox = __esm(() => {
34308
34313
  });
34309
34314
 
34310
34315
  // src/_impl/run-task.ts
34311
- import { mkdirSync as mkdirSync7, existsSync as existsSync10, writeFileSync as writeFileSync6, readFileSync as readFileSync8, appendFileSync as appendFileSync4, unlinkSync as unlinkSync4, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
34312
- import { join as join11, dirname as dirname8 } from "path";
34316
+ import { mkdirSync as mkdirSync8, existsSync as existsSync11, writeFileSync as writeFileSync6, readFileSync as readFileSync9, appendFileSync as appendFileSync4, unlinkSync as unlinkSync5, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
34317
+ import { join as join11, dirname as dirname9 } from "path";
34313
34318
  function ensureDirs() {
34314
34319
  for (const d of [MULTI_DIR4, join11(MULTI_DIR4, "logs"), SKILLS_DIR2]) {
34315
- if (!existsSync10(d))
34316
- mkdirSync7(d, { recursive: true });
34320
+ if (!existsSync11(d))
34321
+ mkdirSync8(d, { recursive: true });
34317
34322
  }
34318
34323
  }
34319
34324
  function log3(msg) {
@@ -34360,7 +34365,7 @@ function resolveEnforcementMode(task) {
34360
34365
  async function handleRunTask(apiUrl, deviceId, task, detected, ctx) {
34361
34366
  const issueId = task.issue_id;
34362
34367
  const isFollowup = !!task.followup;
34363
- const baseWorkingDir = task.working_dir && existsSync10(task.working_dir) ? task.working_dir : undefined;
34368
+ const baseWorkingDir = task.working_dir && existsSync11(task.working_dir) ? task.working_dir : undefined;
34364
34369
  const tenantWsId = task.tenant_workspace_id ?? null;
34365
34370
  const projectId = task.project_id ?? null;
34366
34371
  const ISSUE_BASE = tenantWsId && projectId ? `${apiUrl}/api/workspaces/${tenantWsId}/projects/${projectId}/issues/${issueId}` : null;
@@ -35051,7 +35056,7 @@ async function executePlanActions(apiUrl, parentTask, actions, ctx, parseErrors
35051
35056
  continue;
35052
35057
  }
35053
35058
  lines.push(`- [ok] updated ${res.data.key}`);
35054
- if ((a.status === "done" || a.status === "cancelled") && parentTask.working_dir && existsSync10(parentTask.working_dir)) {
35059
+ if ((a.status === "done" || a.status === "cancelled") && parentTask.working_dir && existsSync11(parentTask.working_dir)) {
35055
35060
  const targetKey = res.data?.key;
35056
35061
  if (targetKey) {
35057
35062
  const targetIssueId = res.data?.id || a.id;
@@ -35286,26 +35291,26 @@ function statusIcon(status3) {
35286
35291
  }
35287
35292
  }
35288
35293
  async function resolveAcpAdapter(agentType, detectedPath) {
35289
- if (agentType === "pi" && detectedPath && existsSync10(detectedPath)) {
35294
+ if (agentType === "pi" && detectedPath && existsSync11(detectedPath)) {
35290
35295
  return [detectedPath, "--mode", "rpc"];
35291
35296
  }
35292
35297
  const override = process.env.MULTI_ACP_ADAPTER?.trim();
35293
35298
  const adapterNames = override ? [override] : ["claude-code-acp", "claude-agent-acp"];
35294
35299
  for (const name of adapterNames) {
35295
- if (name.startsWith("/") && existsSync10(name))
35300
+ if (name.startsWith("/") && existsSync11(name))
35296
35301
  return [name];
35297
35302
  try {
35298
35303
  const here = new URL(import.meta.url).pathname;
35299
35304
  let dir = here;
35300
35305
  for (let i = 0;i < 8; i++) {
35301
- dir = dirname8(dir);
35306
+ dir = dirname9(dir);
35302
35307
  const bin = join11(dir, "node_modules", ".bin", name);
35303
- if (existsSync10(bin))
35308
+ if (existsSync11(bin))
35304
35309
  return [bin];
35305
35310
  }
35306
35311
  } catch {}
35307
35312
  const global2 = join11(HOME4, ".bun", "install", "global", "node_modules", ".bin", name);
35308
- if (existsSync10(global2))
35313
+ if (existsSync11(global2))
35309
35314
  return [global2];
35310
35315
  }
35311
35316
  return null;
@@ -35333,7 +35338,7 @@ async function downloadCommentAttachments(apiUrl, wsId, projectId, issueId, comm
35333
35338
  const items = list.data?.results || list.data || [];
35334
35339
  if (!Array.isArray(items) || items.length === 0)
35335
35340
  return [];
35336
- mkdirSync7(destDir, { recursive: true });
35341
+ mkdirSync8(destDir, { recursive: true });
35337
35342
  const token = authTokenHeader();
35338
35343
  const out = [];
35339
35344
  for (const it of items) {
@@ -35353,9 +35358,9 @@ async function downloadCommentAttachments(apiUrl, wsId, projectId, issueId, comm
35353
35358
  }
35354
35359
  function authTokenHeader() {
35355
35360
  try {
35356
- if (!existsSync10(CONFIG_PATH2))
35361
+ if (!existsSync11(CONFIG_PATH2))
35357
35362
  return null;
35358
- const raw = JSON.parse(readFileSync8(CONFIG_PATH2, "utf8"));
35363
+ const raw = JSON.parse(readFileSync9(CONFIG_PATH2, "utf8"));
35359
35364
  const token = raw.token ?? raw.authToken;
35360
35365
  return token ? `Bearer ${token}` : null;
35361
35366
  } catch {
@@ -35363,7 +35368,7 @@ function authTokenHeader() {
35363
35368
  }
35364
35369
  }
35365
35370
  async function uploadOutputDir(apiUrl, wsId, projectId, issueId, commentId, dir) {
35366
- if (!existsSync10(dir))
35371
+ if (!existsSync11(dir))
35367
35372
  return 0;
35368
35373
  const files = [];
35369
35374
  const walk = (d, depth = 0) => {
@@ -35388,7 +35393,7 @@ async function uploadOutputDir(apiUrl, wsId, projectId, issueId, commentId, dir)
35388
35393
  let uploaded = 0;
35389
35394
  for (const f of files) {
35390
35395
  try {
35391
- const data = readFileSync8(f);
35396
+ const data = readFileSync9(f);
35392
35397
  const form = new FormData;
35393
35398
  const blob = new Blob([data]);
35394
35399
  form.append("file", blob, f.split("/").pop() || "file");
@@ -35403,7 +35408,7 @@ async function uploadOutputDir(apiUrl, wsId, projectId, issueId, commentId, dir)
35403
35408
  if (res.ok) {
35404
35409
  uploaded++;
35405
35410
  try {
35406
- unlinkSync4(f);
35411
+ unlinkSync5(f);
35407
35412
  } catch {}
35408
35413
  }
35409
35414
  } catch (e) {
@@ -40156,8 +40161,8 @@ var init_chat_doc = __esm(() => {
40156
40161
  });
40157
40162
 
40158
40163
  // src/_impl/chat-peer.ts
40159
- import { existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
40160
- import { dirname as dirname9, join as join12 } from "path";
40164
+ import { existsSync as existsSync12, mkdirSync as mkdirSync9, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "fs";
40165
+ import { dirname as dirname10, join as join12 } from "path";
40161
40166
 
40162
40167
  class ChatPeer {
40163
40168
  chatId;
@@ -40179,16 +40184,16 @@ class ChatPeer {
40179
40184
  }
40180
40185
  loadFromDisk() {
40181
40186
  try {
40182
- if (existsSync11(this.snapshotPath)) {
40183
- const bytes = readFileSync9(this.snapshotPath);
40187
+ if (existsSync12(this.snapshotPath)) {
40188
+ const bytes = readFileSync10(this.snapshotPath);
40184
40189
  return importSnapshot(new Uint8Array(bytes));
40185
40190
  }
40186
40191
  } catch {}
40187
40192
  return new import_loro_crdt2.LoroDoc;
40188
40193
  }
40189
40194
  persist() {
40190
- if (!existsSync11(dirname9(this.snapshotPath)))
40191
- mkdirSync8(dirname9(this.snapshotPath), { recursive: true });
40195
+ if (!existsSync12(dirname10(this.snapshotPath)))
40196
+ mkdirSync9(dirname10(this.snapshotPath), { recursive: true });
40192
40197
  writeFileSync7(this.snapshotPath, exportSnapshot(this.doc));
40193
40198
  this.dirtySinceWrite = 0;
40194
40199
  }
@@ -40572,6 +40577,16 @@ async function executeChatPlanActions(actionsIn, parseErrors, ctx) {
40572
40577
  }
40573
40578
  lines.push(`- [ok] handoff -> ${a.target_agent_id} via **${res.data?.child_key}** expect=${a.expect ?? "summary"}`);
40574
40579
  tally(true);
40580
+ } else if (a.type === "issue.comment") {
40581
+ const res = await apiClient.post(mutateUrl, { action: "comment", id: a.id, body: a.body }, { headers });
40582
+ if (!res.success) {
40583
+ lines.push(`- [err] issue.comment ${a.id}: ${res.error || res.status}`);
40584
+ tally(false);
40585
+ continue;
40586
+ }
40587
+ const dispatched = res.data?.dispatched;
40588
+ lines.push(`- [ok] commented on ${a.id}${dispatched ? " -> dispatched agent" : ""}`);
40589
+ tally(true);
40575
40590
  } else if (a.type === "issue.delete") {
40576
40591
  const res = await apiClient.post(mutateUrl, { action: "delete", id: a.id }, { headers });
40577
40592
  if (!res.success) {
@@ -40727,7 +40742,8 @@ var init_chat_plan_actions = __esm(() => {
40727
40742
  "skill.attach": 5,
40728
40743
  "skill.detach": 5,
40729
40744
  "agent.update": 5,
40730
- "session.create": 3
40745
+ "session.create": 3,
40746
+ "issue.comment": 5
40731
40747
  };
40732
40748
  });
40733
40749
 
@@ -41141,6 +41157,7 @@ Action vocabulary:
41141
41157
  {"type":"update","id":"<issue id or key>","status":"done"},
41142
41158
  {"type":"delegate","id":"<issue id>","assignee_id":"<agent id>"},
41143
41159
  {"type":"handoff","target_agent_id":"<agent id>","prompt":"..."},
41160
+ {"type":"issue.comment","id":"<issue id or key>","body":"@<agent name> ..."},
41144
41161
  {"type":"issue.delete","id":"<issue id>"},
41145
41162
  {"type":"issue.delete_where","status":"todo","limit":50},
41146
41163
  {"type":"issue.list","status":"todo","limit":20},
@@ -41155,8 +41172,9 @@ Action vocabulary:
41155
41172
 
41156
41173
  Rules:
41157
41174
  - Omit the block entirely if no actions are needed. Don't emit empty arrays.
41158
- - Max 10 actions per turn. Sub-caps: agent.create=2, skill.create=3, agent.update=5, skill.attach/detach=5, session.create=3.
41175
+ - Max 10 actions per turn. Sub-caps: agent.create=2, skill.create=3, agent.update=5, skill.attach/detach=5, session.create=3, issue.comment=5.
41159
41176
  - Chat-initiated agent.create / agent.update / skill.attach are auto-approved (the user is reading this reply right now). skill.create still queues for human review.
41177
+ - Use \`issue.comment\` with \`@<agent name>\` mention to dispatch an agent on an existing issue. Plain comments without an @mention are recorded but do not trigger a run. Issues whose autonomy is \`manual\` will not dispatch.
41160
41178
  - \`allowed_tools\` on a new agent must be a subset of generally available tools.
41161
41179
 
41162
41180
  To draw an inline panel for the user, append a separate fenced \`multi-ui\` block. The host uploads it to a Cloudflare Worker (24h TTL), serves the rendered HTML at a public URL, and embeds it in a sandboxed iframe (\`allow-scripts allow-forms\`). No reactive runtime — write plain HTML with optional inline \`<script>\` for interactivity. \`window.data\` inside your script is the JSON you sent.
@@ -41745,7 +41763,7 @@ import { parseArgs } from "util";
41745
41763
  // package.json
41746
41764
  var package_default = {
41747
41765
  name: "@shipers-dev/multi",
41748
- version: "0.38.5",
41766
+ version: "0.39.1",
41749
41767
  type: "module",
41750
41768
  bin: {
41751
41769
  "multi-agent": "./dist/index.js"
@@ -42562,7 +42580,133 @@ var linkCmd = exports_Effect.fn("linkCmd")(function* (apiUrl, agentId) {
42562
42580
  // src/commands/restart.ts
42563
42581
  init_esm();
42564
42582
  init_paths();
42565
- import { existsSync as existsSync7, readFileSync as readFileSync7, unlinkSync as unlinkSync3 } from "fs";
42583
+ import { existsSync as existsSync8, readFileSync as readFileSync8, unlinkSync as unlinkSync4 } from "fs";
42584
+
42585
+ // src/_impl/pid-lock.ts
42586
+ import { closeSync, existsSync as existsSync7, mkdirSync as mkdirSync6, openSync, readFileSync as readFileSync7, unlinkSync as unlinkSync3, writeSync } from "fs";
42587
+ import { dirname as dirname8 } from "path";
42588
+ var isAlive = (pid) => {
42589
+ if (!Number.isFinite(pid) || pid <= 0)
42590
+ return false;
42591
+ try {
42592
+ process.kill(pid, 0);
42593
+ return true;
42594
+ } catch (e) {
42595
+ return e?.code === "EPERM";
42596
+ }
42597
+ };
42598
+ var writeExclusive = (path, pid) => {
42599
+ mkdirSync6(dirname8(path), { recursive: true });
42600
+ const fd = openSync(path, "wx");
42601
+ try {
42602
+ writeSync(fd, String(pid));
42603
+ } finally {
42604
+ closeSync(fd);
42605
+ }
42606
+ };
42607
+ var acquirePidLock = (path) => {
42608
+ const ourPid = process.pid;
42609
+ try {
42610
+ writeExclusive(path, ourPid);
42611
+ return { ok: true };
42612
+ } catch (e) {
42613
+ if (e?.code !== "EEXIST")
42614
+ throw e;
42615
+ }
42616
+ const raw = (() => {
42617
+ try {
42618
+ return readFileSync7(path, "utf8").trim();
42619
+ } catch {
42620
+ return "";
42621
+ }
42622
+ })();
42623
+ const existingPid = Number(raw);
42624
+ if (existingPid === ourPid)
42625
+ return { ok: true };
42626
+ if (Number.isFinite(existingPid) && existingPid > 0 && isAlive(existingPid)) {
42627
+ return { ok: false, existingPid };
42628
+ }
42629
+ try {
42630
+ unlinkSync3(path);
42631
+ } catch {}
42632
+ try {
42633
+ writeExclusive(path, ourPid);
42634
+ return { ok: true };
42635
+ } catch (e) {
42636
+ if (e?.code !== "EEXIST")
42637
+ throw e;
42638
+ const racingRaw = (() => {
42639
+ try {
42640
+ return readFileSync7(path, "utf8").trim();
42641
+ } catch {
42642
+ return "";
42643
+ }
42644
+ })();
42645
+ const racingPid = Number(racingRaw);
42646
+ return { ok: false, existingPid: Number.isFinite(racingPid) ? racingPid : 0 };
42647
+ }
42648
+ };
42649
+ var releasePidLock = (path) => {
42650
+ try {
42651
+ if (!existsSync7(path))
42652
+ return;
42653
+ const raw = readFileSync7(path, "utf8").trim();
42654
+ if (Number(raw) !== process.pid)
42655
+ return;
42656
+ unlinkSync3(path);
42657
+ } catch {}
42658
+ };
42659
+ var killStaleConnects = () => {
42660
+ const self2 = process.pid;
42661
+ const parent = process.ppid;
42662
+ const out = (() => {
42663
+ try {
42664
+ const r = Bun.spawnSync(["ps", "-A", "-o", "pid=,command="], { stderr: "ignore" });
42665
+ return new TextDecoder().decode(r.stdout);
42666
+ } catch {
42667
+ return "";
42668
+ }
42669
+ })();
42670
+ const victims = [];
42671
+ for (const line of out.split(`
42672
+ `)) {
42673
+ const m = line.trim().match(/^(\d+)\s+(.*)$/);
42674
+ if (!m)
42675
+ continue;
42676
+ const pid = Number(m[1]);
42677
+ const cmd = m[2];
42678
+ if (!Number.isFinite(pid) || pid === self2 || pid === parent)
42679
+ continue;
42680
+ if (!/@shipers-dev\/multi\/dist\/index\.js/.test(cmd) && !/multi-agent/.test(cmd))
42681
+ continue;
42682
+ if (!/\bconnect\b/.test(cmd))
42683
+ continue;
42684
+ victims.push(pid);
42685
+ }
42686
+ for (const pid of victims) {
42687
+ try {
42688
+ process.kill(pid, "SIGTERM");
42689
+ } catch {}
42690
+ }
42691
+ if (victims.length === 0)
42692
+ return victims;
42693
+ const deadline = Date.now() + 1500;
42694
+ while (Date.now() < deadline) {
42695
+ if (!victims.some((p) => isAlive(p)))
42696
+ break;
42697
+ Bun.sleepSync(100);
42698
+ }
42699
+ for (const pid of victims) {
42700
+ if (isAlive(pid)) {
42701
+ try {
42702
+ process.kill(pid, "SIGKILL");
42703
+ } catch {}
42704
+ }
42705
+ }
42706
+ return victims;
42707
+ };
42708
+
42709
+ // src/commands/restart.ts
42566
42710
  var isRunning4 = (pid) => {
42567
42711
  try {
42568
42712
  process.kill(pid, 0);
@@ -42574,8 +42718,8 @@ var isRunning4 = (pid) => {
42574
42718
  var restartCmd = exports_Effect.fn("restartCmd")(function* (apiUrl) {
42575
42719
  const fs3 = yield* FileSystem;
42576
42720
  yield* (yield* Config4).ensureDirs;
42577
- if (existsSync7(PID_PATH)) {
42578
- const pid = Number(readFileSync7(PID_PATH, "utf8").trim());
42721
+ if (existsSync8(PID_PATH)) {
42722
+ const pid = Number(readFileSync8(PID_PATH, "utf8").trim());
42579
42723
  if (pid && isRunning4(pid)) {
42580
42724
  yield* fs3.writeText(STOP_PATH, "1");
42581
42725
  try {
@@ -42594,14 +42738,17 @@ var restartCmd = exports_Effect.fn("restartCmd")(function* (apiUrl) {
42594
42738
  }
42595
42739
  }
42596
42740
  try {
42597
- if (existsSync7(PID_PATH))
42598
- unlinkSync3(PID_PATH);
42741
+ if (existsSync8(PID_PATH))
42742
+ unlinkSync4(PID_PATH);
42599
42743
  } catch {}
42600
42744
  }
42601
42745
  try {
42602
- if (existsSync7(STOP_PATH))
42603
- unlinkSync3(STOP_PATH);
42746
+ if (existsSync8(STOP_PATH))
42747
+ unlinkSync4(STOP_PATH);
42604
42748
  } catch {}
42749
+ const killed = killStaleConnects();
42750
+ if (killed.length)
42751
+ console.log(`⏹ Killed stale daemons: ${killed.join(",")}`);
42605
42752
  console.log("\uD83D\uDD04 Relaunching daemon...");
42606
42753
  const args2 = Bun.argv.slice(1).filter((a) => a !== "-d" && a !== "--detach" && a !== "restart");
42607
42754
  if (!args2.includes("connect"))
@@ -42693,7 +42840,7 @@ init_client();
42693
42840
  init_detect();
42694
42841
  init_run_task();
42695
42842
  import { Database as Database3 } from "bun:sqlite";
42696
- import { existsSync as existsSync12, mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, unlinkSync as unlinkSync5 } from "fs";
42843
+ import { existsSync as existsSync13, mkdirSync as mkdirSync10, writeFileSync as writeFileSync8, unlinkSync as unlinkSync6 } from "fs";
42697
42844
  import { homedir as homedir2 } from "os";
42698
42845
  import { join as join13 } from "path";
42699
42846
  init_errors();
@@ -42703,11 +42850,11 @@ var PORT_PATH2 = join13(MULTI_DIR5, "agent.port");
42703
42850
  var STOP_PATH2 = join13(MULTI_DIR5, "stop.flag");
42704
42851
  var TASKS_DB_PATH3 = join13(MULTI_DIR5, "tasks.db");
42705
42852
  function ensureDirs2() {
42706
- if (!existsSync12(MULTI_DIR5))
42707
- mkdirSync9(MULTI_DIR5, { recursive: true });
42853
+ if (!existsSync13(MULTI_DIR5))
42854
+ mkdirSync10(MULTI_DIR5, { recursive: true });
42708
42855
  const logs = join13(MULTI_DIR5, "logs");
42709
- if (!existsSync12(logs))
42710
- mkdirSync9(logs, { recursive: true });
42856
+ if (!existsSync13(logs))
42857
+ mkdirSync10(logs, { recursive: true });
42711
42858
  }
42712
42859
  function isLocalApi(url2) {
42713
42860
  try {
@@ -42886,8 +43033,8 @@ async function ackDispatch(apiUrl, wsId, dispatchId, secret) {
42886
43033
  log3(`ack dispatch ${dispatchId} failed: ${String(e)}`);
42887
43034
  }
42888
43035
  }
42889
- async function drainOfflineDispatches(apiUrl, deviceId, wsId, secret, db2, onEnqueued, isAlive) {
42890
- if (!isAlive())
43036
+ async function drainOfflineDispatches(apiUrl, deviceId, wsId, secret, db2, onEnqueued, isAlive2) {
43037
+ if (!isAlive2())
42891
43038
  return;
42892
43039
  let list;
42893
43040
  try {
@@ -42901,7 +43048,7 @@ async function drainOfflineDispatches(apiUrl, deviceId, wsId, secret, db2, onEnq
42901
43048
  return;
42902
43049
  log3(`draining ${rows.length} offline dispatch(es)`);
42903
43050
  for (const r of rows) {
42904
- if (!isAlive()) {
43051
+ if (!isAlive2()) {
42905
43052
  log3("drain aborted: shutting down");
42906
43053
  return;
42907
43054
  }
@@ -42999,8 +43146,8 @@ var daemonProgram = ({ cfg, apiUrl }) => exports_Effect.gen(function* () {
42999
43146
  if (cfg.authToken)
43000
43147
  setAuthToken(cfg.authToken);
43001
43148
  ensureDirs2();
43002
- if (existsSync12(STOP_PATH2))
43003
- unlinkSync5(STOP_PATH2);
43149
+ if (existsSync13(STOP_PATH2))
43150
+ unlinkSync6(STOP_PATH2);
43004
43151
  const detected = yield* exports_Effect.promise(() => detectAgents());
43005
43152
  const localMode = isLocalApi(apiUrl);
43006
43153
  log3(`daemon device=${cfg.deviceId} pid=${process.pid}`);
@@ -43162,7 +43309,7 @@ var daemonProgram = ({ cfg, apiUrl }) => exports_Effect.gen(function* () {
43162
43309
  let probeFailures = 0;
43163
43310
  while (true) {
43164
43311
  yield* exports_Effect.sleep(exports_Duration.seconds(120));
43165
- if (existsSync12(STOP_PATH2)) {
43312
+ if (existsSync13(STOP_PATH2)) {
43166
43313
  yield* exports_Deferred.succeed(stopDeferred, "stop flag");
43167
43314
  return;
43168
43315
  }
@@ -43187,7 +43334,7 @@ var daemonProgram = ({ cfg, apiUrl }) => exports_Effect.gen(function* () {
43187
43334
  yield* exports_Effect.forkIn(daemonScope)(exports_Effect.gen(function* () {
43188
43335
  while (true) {
43189
43336
  yield* exports_Effect.sleep(exports_Duration.seconds(5));
43190
- if (existsSync12(STOP_PATH2)) {
43337
+ if (existsSync13(STOP_PATH2)) {
43191
43338
  yield* exports_Deferred.succeed(stopDeferred, "stop flag");
43192
43339
  return;
43193
43340
  }
@@ -43224,12 +43371,12 @@ var daemonProgram = ({ cfg, apiUrl }) => exports_Effect.gen(function* () {
43224
43371
  yield* exports_Effect.race(exports_Fiber.interruptAll(inFlight), exports_Effect.sleep(exports_Duration.seconds(10)));
43225
43372
  }
43226
43373
  yield* exports_Scope.close(daemonScope, exports_Exit.void).pipe(exports_Effect.catchAll(() => exports_Effect.void));
43227
- if (existsSync12(PID_PATH2))
43228
- unlinkSync5(PID_PATH2);
43229
- if (existsSync12(STOP_PATH2))
43230
- unlinkSync5(STOP_PATH2);
43231
- if (existsSync12(PORT_PATH2))
43232
- unlinkSync5(PORT_PATH2);
43374
+ if (existsSync13(PID_PATH2))
43375
+ unlinkSync6(PID_PATH2);
43376
+ if (existsSync13(STOP_PATH2))
43377
+ unlinkSync6(STOP_PATH2);
43378
+ if (existsSync13(PORT_PATH2))
43379
+ unlinkSync6(PORT_PATH2);
43233
43380
  db2.close();
43234
43381
  log3("disconnected");
43235
43382
  });
@@ -43241,81 +43388,6 @@ async function daemonMain(opts) {
43241
43388
  }
43242
43389
  }
43243
43390
 
43244
- // src/_impl/pid-lock.ts
43245
- import { closeSync, existsSync as existsSync13, mkdirSync as mkdirSync10, openSync, readFileSync as readFileSync10, unlinkSync as unlinkSync6, writeSync } from "fs";
43246
- import { dirname as dirname10 } from "path";
43247
- var isAlive = (pid) => {
43248
- if (!Number.isFinite(pid) || pid <= 0)
43249
- return false;
43250
- try {
43251
- process.kill(pid, 0);
43252
- return true;
43253
- } catch (e) {
43254
- return e?.code === "EPERM";
43255
- }
43256
- };
43257
- var writeExclusive = (path, pid) => {
43258
- mkdirSync10(dirname10(path), { recursive: true });
43259
- const fd = openSync(path, "wx");
43260
- try {
43261
- writeSync(fd, String(pid));
43262
- } finally {
43263
- closeSync(fd);
43264
- }
43265
- };
43266
- var acquirePidLock = (path) => {
43267
- const ourPid = process.pid;
43268
- try {
43269
- writeExclusive(path, ourPid);
43270
- return { ok: true };
43271
- } catch (e) {
43272
- if (e?.code !== "EEXIST")
43273
- throw e;
43274
- }
43275
- const raw = (() => {
43276
- try {
43277
- return readFileSync10(path, "utf8").trim();
43278
- } catch {
43279
- return "";
43280
- }
43281
- })();
43282
- const existingPid = Number(raw);
43283
- if (existingPid === ourPid)
43284
- return { ok: true };
43285
- if (Number.isFinite(existingPid) && existingPid > 0 && isAlive(existingPid)) {
43286
- return { ok: false, existingPid };
43287
- }
43288
- try {
43289
- unlinkSync6(path);
43290
- } catch {}
43291
- try {
43292
- writeExclusive(path, ourPid);
43293
- return { ok: true };
43294
- } catch (e) {
43295
- if (e?.code !== "EEXIST")
43296
- throw e;
43297
- const racingRaw = (() => {
43298
- try {
43299
- return readFileSync10(path, "utf8").trim();
43300
- } catch {
43301
- return "";
43302
- }
43303
- })();
43304
- const racingPid = Number(racingRaw);
43305
- return { ok: false, existingPid: Number.isFinite(racingPid) ? racingPid : 0 };
43306
- }
43307
- };
43308
- var releasePidLock = (path) => {
43309
- try {
43310
- if (!existsSync13(path))
43311
- return;
43312
- const raw = readFileSync10(path, "utf8").trim();
43313
- if (Number(raw) !== process.pid)
43314
- return;
43315
- unlinkSync6(path);
43316
- } catch {}
43317
- };
43318
-
43319
43391
  // src/commands/connect.ts
43320
43392
  init_outbox();
43321
43393
  init_paths();
@@ -43334,6 +43406,9 @@ var connectCmd = exports_Effect.fn("connectCmd")(function* () {
43334
43406
  }
43335
43407
  if (cfg.authToken)
43336
43408
  yield* api2.setAuthToken(cfg.authToken);
43409
+ const killed = killStaleConnects();
43410
+ if (killed.length)
43411
+ yield* logger.log(`connect: killed stale daemons ${killed.join(",")}`);
43337
43412
  if (process.env.MULTI_FORCE_CONNECT !== "1") {
43338
43413
  const lock = acquirePidLock(PID_PATH);
43339
43414
  if (!lock.ok) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipers-dev/multi",
3
- "version": "0.38.5",
3
+ "version": "0.39.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "multi-agent": "./dist/index.js"