@shipers-dev/multi 0.38.0 → 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 +304 -214
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -33220,7 +33220,11 @@ async function runAcp(opts) {
33220
33220
  } catch (e) {
33221
33221
  throw new Error(`writeTextFile failed: ${fmtErr(e)}`);
33222
33222
  }
33223
- }
33223
+ },
33224
+ async extMethod(_method, _params) {
33225
+ return {};
33226
+ },
33227
+ async extNotification(_method, _params) {}
33224
33228
  };
33225
33229
  const conn = new ClientSideConnection(() => client, stream2);
33226
33230
  try {
@@ -33289,8 +33293,7 @@ async function runAcp(opts) {
33289
33293
  const u = params.update;
33290
33294
  const kind = u.sessionUpdate;
33291
33295
  switch (kind) {
33292
- case "agent_message_chunk":
33293
- case "agent_thought_chunk": {
33296
+ case "agent_message_chunk": {
33294
33297
  const text = extractText(u.content);
33295
33298
  if (text) {
33296
33299
  chunkCount++;
@@ -33298,6 +33301,9 @@ async function runAcp(opts) {
33298
33301
  }
33299
33302
  break;
33300
33303
  }
33304
+ case "agent_thought_chunk": {
33305
+ break;
33306
+ }
33301
33307
  case "tool_call":
33302
33308
  case "tool_call_update": {
33303
33309
  chunkCount++;
@@ -33540,13 +33546,15 @@ function handleSessionUpdate(params) {
33540
33546
  const kind = u.sessionUpdate;
33541
33547
  const out = [];
33542
33548
  switch (kind) {
33543
- case "agent_message_chunk":
33544
- case "agent_thought_chunk": {
33549
+ case "agent_message_chunk": {
33545
33550
  const text = extractText2(u.content);
33546
33551
  if (text)
33547
33552
  out.push({ event_type: "assistant_text", payload: { text } });
33548
33553
  break;
33549
33554
  }
33555
+ case "agent_thought_chunk": {
33556
+ break;
33557
+ }
33550
33558
  case "tool_call":
33551
33559
  case "tool_call_update": {
33552
33560
  out.push({ event_type: "tool_call", payload: {
@@ -33758,6 +33766,25 @@ var init_agent_workspaces = __esm(() => {
33758
33766
  });
33759
33767
 
33760
33768
  // ../lib/plans.ts
33769
+ function parseUiBlocks(text) {
33770
+ const blocks = [];
33771
+ const errors3 = [];
33772
+ if (!text)
33773
+ return { blocks, errors: errors3 };
33774
+ let i = 0;
33775
+ let m;
33776
+ UI_FENCE_RE.lastIndex = 0;
33777
+ while ((m = UI_FENCE_RE.exec(text)) !== null) {
33778
+ i++;
33779
+ try {
33780
+ const parsed = UiBlockSchema.parse(JSON.parse(m[1]));
33781
+ blocks.push(parsed);
33782
+ } catch (e) {
33783
+ errors3.push({ index: i, type: "multi-ui", message: e?.message || "invalid ui block" });
33784
+ }
33785
+ }
33786
+ return { blocks, errors: errors3 };
33787
+ }
33761
33788
  function parsePlanBlocks(text) {
33762
33789
  const actions = [];
33763
33790
  const errors3 = [];
@@ -33807,52 +33834,13 @@ function parsePlanBlocks(text) {
33807
33834
  }
33808
33835
  return { actions, errors: errors3 };
33809
33836
  }
33810
- function parseUiBlocks(text) {
33811
- const blocks = [];
33812
- const spans = [];
33813
- const errors3 = [];
33814
- if (!text)
33815
- return { blocks, spans, errors: errors3 };
33816
- let i = 0;
33817
- let m;
33818
- UI_FENCE_RE.lastIndex = 0;
33819
- while ((m = UI_FENCE_RE.exec(text)) !== null) {
33820
- i += 1;
33821
- const start3 = m.index;
33822
- const end3 = m.index + m[0].length;
33823
- let parsed;
33824
- try {
33825
- parsed = JSON.parse(m[1]);
33826
- } catch (e) {
33827
- errors3.push({ index: i, type: "multi-ui", message: `block ${i}: invalid JSON (${e.message})` });
33828
- continue;
33829
- }
33830
- const r = UiBlockSchema.safeParse(parsed);
33831
- if (!r.success) {
33832
- const issues = r.error.issues.map((iss) => `${iss.path.join(".") || "<root>"}: ${iss.message}`);
33833
- errors3.push({ index: i, type: "multi-ui", message: `block ${i}: ${issues.join("; ")}` });
33834
- continue;
33835
- }
33836
- blocks.push(r.data);
33837
- spans.push({ block: r.data, start: start3, end: end3 });
33838
- }
33839
- return { blocks, spans, errors: errors3 };
33840
- }
33841
- var PLAN_SCHEMA_VERSION = 4, EvalPolicy, Priority, AssigneeType, IssueStatus, SessionRole, Capabilities, HandoffExpect, SkillFile, PlanActionSchema, PlanEnvelopeSchema, FENCE_RE, UiBlockSchema, UI_FENCE_RE;
33837
+ var PLAN_SCHEMA_VERSION = 2, Priority, AssigneeType, IssueStatus, SessionRole, SkillFile, PlanActionSchema, PlanEnvelopeSchema, UiBlockSchema, UI_FENCE_RE, FENCE_RE;
33842
33838
  var init_plans = __esm(() => {
33843
33839
  init_zod();
33844
- EvalPolicy = exports_external.object({
33845
- evaluator_agent_id: exports_external.string().min(1),
33846
- threshold: exports_external.number().min(0).max(1),
33847
- on_fail: exports_external.enum(["retry", "reroute", "escalate"]).optional(),
33848
- max_retries: exports_external.number().int().min(0).max(10).optional()
33849
- });
33850
33840
  Priority = exports_external.enum(["low", "medium", "high"]);
33851
33841
  AssigneeType = exports_external.enum(["human", "agent"]);
33852
33842
  IssueStatus = exports_external.enum(["todo", "in_progress", "done", "failed", "stopped", "cancelled"]);
33853
33843
  SessionRole = exports_external.enum(["implementer", "reviewer", "test-fixer"]);
33854
- Capabilities = exports_external.record(exports_external.string(), exports_external.unknown());
33855
- HandoffExpect = exports_external.enum(["summary", "json", "none"]);
33856
33844
  SkillFile = exports_external.object({ path: exports_external.string().min(1), content: exports_external.string() });
33857
33845
  PlanActionSchema = exports_external.discriminatedUnion("type", [
33858
33846
  exports_external.object({
@@ -33864,9 +33852,7 @@ var init_plans = __esm(() => {
33864
33852
  assignee_type: AssigneeType.optional(),
33865
33853
  assignee_id: exports_external.string().optional(),
33866
33854
  parent_id: exports_external.string().optional(),
33867
- blocked_by: exports_external.array(exports_external.string().min(1)).optional(),
33868
- eval_policy: EvalPolicy.optional(),
33869
- await_children: exports_external.boolean().optional()
33855
+ blocked_by: exports_external.array(exports_external.string().min(1)).optional()
33870
33856
  }),
33871
33857
  exports_external.object({
33872
33858
  type: exports_external.literal("update"),
@@ -33877,8 +33863,7 @@ var init_plans = __esm(() => {
33877
33863
  priority: Priority.optional(),
33878
33864
  assignee_type: AssigneeType.optional(),
33879
33865
  assignee_id: exports_external.string().optional(),
33880
- blocked_by: exports_external.array(exports_external.string().min(1)).optional(),
33881
- eval_policy: EvalPolicy.nullable().optional()
33866
+ blocked_by: exports_external.array(exports_external.string().min(1)).optional()
33882
33867
  }),
33883
33868
  exports_external.object({
33884
33869
  type: exports_external.literal("delegate"),
@@ -33886,12 +33871,9 @@ var init_plans = __esm(() => {
33886
33871
  assignee_id: exports_external.string().min(1)
33887
33872
  }),
33888
33873
  exports_external.object({
33889
- type: exports_external.literal("handoff"),
33890
- target_agent_id: exports_external.string().min(1),
33891
- prompt: exports_external.string().min(1),
33892
- expect: HandoffExpect.optional(),
33893
- return_to: exports_external.string().optional(),
33894
- title: exports_external.string().min(1).max(500).optional()
33874
+ type: exports_external.literal("issue.comment"),
33875
+ id: exports_external.string().min(1),
33876
+ body: exports_external.string().min(1).max(8000)
33895
33877
  }),
33896
33878
  exports_external.object({
33897
33879
  type: exports_external.literal("issue.delete"),
@@ -33923,16 +33905,14 @@ var init_plans = __esm(() => {
33923
33905
  agent_type: exports_external.string().min(1),
33924
33906
  prompt: exports_external.string().optional(),
33925
33907
  skill_ids: exports_external.array(exports_external.string()).optional(),
33926
- allowed_tools: exports_external.array(exports_external.string()).optional(),
33927
- capabilities: Capabilities.optional()
33908
+ allowed_tools: exports_external.array(exports_external.string()).optional()
33928
33909
  }),
33929
33910
  exports_external.object({
33930
33911
  type: exports_external.literal("agent.update"),
33931
33912
  id: exports_external.string().min(1),
33932
33913
  name: exports_external.string().min(1).max(120).optional(),
33933
33914
  prompt: exports_external.string().nullable().optional(),
33934
- allowed_tools: exports_external.array(exports_external.string()).nullable().optional(),
33935
- capabilities: Capabilities.nullable().optional()
33915
+ allowed_tools: exports_external.array(exports_external.string()).nullable().optional()
33936
33916
  }),
33937
33917
  exports_external.object({
33938
33918
  type: exports_external.literal("skill.create"),
@@ -33958,25 +33938,18 @@ var init_plans = __esm(() => {
33958
33938
  agent_id: exports_external.string().min(1),
33959
33939
  role: SessionRole,
33960
33940
  device_id: exports_external.string().optional()
33961
- }),
33962
- exports_external.object({
33963
- type: exports_external.literal("eval.submit"),
33964
- id: exports_external.string().min(1).optional(),
33965
- score: exports_external.number().min(0).max(1),
33966
- feedback: exports_external.string().min(1).max(8000),
33967
- scores: exports_external.record(exports_external.string(), exports_external.number().min(0).max(1)).optional()
33968
33941
  })
33969
33942
  ]);
33970
33943
  PlanEnvelopeSchema = exports_external.object({
33971
33944
  actions: exports_external.array(exports_external.unknown())
33972
33945
  });
33973
- FENCE_RE = /```multi-plan\s*\n([\s\S]*?)\n```/g;
33974
33946
  UiBlockSchema = exports_external.object({
33975
33947
  template: exports_external.string().min(1).max(20000),
33976
33948
  data: exports_external.unknown().optional(),
33977
33949
  height: exports_external.number().int().min(40).max(1200).optional()
33978
33950
  });
33979
33951
  UI_FENCE_RE = /```multi-ui\s*\n([\s\S]*?)\n```/g;
33952
+ FENCE_RE = /```multi-plan\s*\n([\s\S]*?)\n```/g;
33980
33953
  });
33981
33954
 
33982
33955
  // ../lib/chat.ts
@@ -33991,6 +33964,7 @@ var init_chat = __esm(() => {
33991
33964
  title: exports_external.string(),
33992
33965
  primary_agent_id: exports_external.string().nullable(),
33993
33966
  device_id: exports_external.string().nullable(),
33967
+ runtime: exports_external.string().nullable(),
33994
33968
  created_at: exports_external.number(),
33995
33969
  updated_at: exports_external.number()
33996
33970
  });
@@ -34032,12 +34006,14 @@ var init_chat = __esm(() => {
34032
34006
  CreateChatBodySchema = exports_external.object({
34033
34007
  title: exports_external.string().min(1).max(200),
34034
34008
  primary_agent_id: exports_external.string().nullable().optional(),
34035
- device_id: exports_external.string().nullable().optional()
34009
+ device_id: exports_external.string().nullable().optional(),
34010
+ runtime: exports_external.string().nullable().optional()
34036
34011
  });
34037
34012
  UpdateChatBodySchema = exports_external.object({
34038
34013
  title: exports_external.string().min(1).max(200).optional(),
34039
34014
  primary_agent_id: exports_external.string().nullable().optional(),
34040
- device_id: exports_external.string().nullable().optional()
34015
+ device_id: exports_external.string().nullable().optional(),
34016
+ runtime: exports_external.string().nullable().optional()
34041
34017
  });
34042
34018
  });
34043
34019
 
@@ -34052,7 +34028,7 @@ var init_lib = __esm(() => {
34052
34028
 
34053
34029
  // src/_impl/git-enforce.ts
34054
34030
  import { spawn as spawn2 } from "node:child_process";
34055
- import { existsSync as existsSync8 } from "node:fs";
34031
+ import { existsSync as existsSync9 } from "node:fs";
34056
34032
  import { join as join9 } from "node:path";
34057
34033
  function run4(cwd, cmd, args2) {
34058
34034
  return new Promise((resolve2) => {
@@ -34092,7 +34068,7 @@ function enforceCommitAndPush(baseWorkingDir, issueKey, mode = "enforce") {
34092
34068
  };
34093
34069
  if (mode === "off")
34094
34070
  return result;
34095
- if (!existsSync8(wt))
34071
+ if (!existsSync9(wt))
34096
34072
  return result;
34097
34073
  if (!(yield* isGitRepo2(wt)))
34098
34074
  return result;
@@ -34180,7 +34156,7 @@ var git = (cwd, args2, op) => exports_Effect.tryPromise({
34180
34156
  try: () => run4(cwd, "git", args2),
34181
34157
  catch: (cause3) => new GitError({ op, message: `git ${args2.join(" ")} threw`, cause: cause3 })
34182
34158
  }), isGitRepo2 = (dir) => exports_Effect.gen(function* () {
34183
- if (!existsSync8(dir))
34159
+ if (!existsSync9(dir))
34184
34160
  return false;
34185
34161
  const r = yield* git(dir, ["rev-parse", "--is-inside-work-tree"], "rev-parse");
34186
34162
  return r.code === 0 && r.stdout === "true";
@@ -34192,14 +34168,14 @@ var init_git_enforce = __esm(() => {
34192
34168
 
34193
34169
  // src/_impl/outbox.ts
34194
34170
  import { Database as Database2 } from "bun:sqlite";
34195
- import { existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
34171
+ import { existsSync as existsSync10, mkdirSync as mkdirSync7 } from "fs";
34196
34172
  import { homedir } from "os";
34197
34173
  import { join as join10 } from "path";
34198
34174
  function ensureDb() {
34199
34175
  if (db)
34200
34176
  return db;
34201
- if (!existsSync9(MULTI_DIR3))
34202
- mkdirSync6(MULTI_DIR3, { recursive: true });
34177
+ if (!existsSync10(MULTI_DIR3))
34178
+ mkdirSync7(MULTI_DIR3, { recursive: true });
34203
34179
  db = new Database2(TASKS_DB_PATH2);
34204
34180
  db.exec(`
34205
34181
  CREATE TABLE IF NOT EXISTS stream_outbox (
@@ -34337,12 +34313,12 @@ var init_outbox = __esm(() => {
34337
34313
  });
34338
34314
 
34339
34315
  // src/_impl/run-task.ts
34340
- 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";
34341
- 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";
34342
34318
  function ensureDirs() {
34343
34319
  for (const d of [MULTI_DIR4, join11(MULTI_DIR4, "logs"), SKILLS_DIR2]) {
34344
- if (!existsSync10(d))
34345
- mkdirSync7(d, { recursive: true });
34320
+ if (!existsSync11(d))
34321
+ mkdirSync8(d, { recursive: true });
34346
34322
  }
34347
34323
  }
34348
34324
  function log3(msg) {
@@ -34389,7 +34365,7 @@ function resolveEnforcementMode(task) {
34389
34365
  async function handleRunTask(apiUrl, deviceId, task, detected, ctx) {
34390
34366
  const issueId = task.issue_id;
34391
34367
  const isFollowup = !!task.followup;
34392
- 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;
34393
34369
  const tenantWsId = task.tenant_workspace_id ?? null;
34394
34370
  const projectId = task.project_id ?? null;
34395
34371
  const ISSUE_BASE = tenantWsId && projectId ? `${apiUrl}/api/workspaces/${tenantWsId}/projects/${projectId}/issues/${issueId}` : null;
@@ -35080,7 +35056,7 @@ async function executePlanActions(apiUrl, parentTask, actions, ctx, parseErrors
35080
35056
  continue;
35081
35057
  }
35082
35058
  lines.push(`- [ok] updated ${res.data.key}`);
35083
- 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)) {
35084
35060
  const targetKey = res.data?.key;
35085
35061
  if (targetKey) {
35086
35062
  const targetIssueId = res.data?.id || a.id;
@@ -35315,26 +35291,26 @@ function statusIcon(status3) {
35315
35291
  }
35316
35292
  }
35317
35293
  async function resolveAcpAdapter(agentType, detectedPath) {
35318
- if (agentType === "pi" && detectedPath && existsSync10(detectedPath)) {
35294
+ if (agentType === "pi" && detectedPath && existsSync11(detectedPath)) {
35319
35295
  return [detectedPath, "--mode", "rpc"];
35320
35296
  }
35321
35297
  const override = process.env.MULTI_ACP_ADAPTER?.trim();
35322
35298
  const adapterNames = override ? [override] : ["claude-code-acp", "claude-agent-acp"];
35323
35299
  for (const name of adapterNames) {
35324
- if (name.startsWith("/") && existsSync10(name))
35300
+ if (name.startsWith("/") && existsSync11(name))
35325
35301
  return [name];
35326
35302
  try {
35327
35303
  const here = new URL(import.meta.url).pathname;
35328
35304
  let dir = here;
35329
35305
  for (let i = 0;i < 8; i++) {
35330
- dir = dirname8(dir);
35306
+ dir = dirname9(dir);
35331
35307
  const bin = join11(dir, "node_modules", ".bin", name);
35332
- if (existsSync10(bin))
35308
+ if (existsSync11(bin))
35333
35309
  return [bin];
35334
35310
  }
35335
35311
  } catch {}
35336
35312
  const global2 = join11(HOME4, ".bun", "install", "global", "node_modules", ".bin", name);
35337
- if (existsSync10(global2))
35313
+ if (existsSync11(global2))
35338
35314
  return [global2];
35339
35315
  }
35340
35316
  return null;
@@ -35362,7 +35338,7 @@ async function downloadCommentAttachments(apiUrl, wsId, projectId, issueId, comm
35362
35338
  const items = list.data?.results || list.data || [];
35363
35339
  if (!Array.isArray(items) || items.length === 0)
35364
35340
  return [];
35365
- mkdirSync7(destDir, { recursive: true });
35341
+ mkdirSync8(destDir, { recursive: true });
35366
35342
  const token = authTokenHeader();
35367
35343
  const out = [];
35368
35344
  for (const it of items) {
@@ -35382,9 +35358,9 @@ async function downloadCommentAttachments(apiUrl, wsId, projectId, issueId, comm
35382
35358
  }
35383
35359
  function authTokenHeader() {
35384
35360
  try {
35385
- if (!existsSync10(CONFIG_PATH2))
35361
+ if (!existsSync11(CONFIG_PATH2))
35386
35362
  return null;
35387
- const raw = JSON.parse(readFileSync8(CONFIG_PATH2, "utf8"));
35363
+ const raw = JSON.parse(readFileSync9(CONFIG_PATH2, "utf8"));
35388
35364
  const token = raw.token ?? raw.authToken;
35389
35365
  return token ? `Bearer ${token}` : null;
35390
35366
  } catch {
@@ -35392,7 +35368,7 @@ function authTokenHeader() {
35392
35368
  }
35393
35369
  }
35394
35370
  async function uploadOutputDir(apiUrl, wsId, projectId, issueId, commentId, dir) {
35395
- if (!existsSync10(dir))
35371
+ if (!existsSync11(dir))
35396
35372
  return 0;
35397
35373
  const files = [];
35398
35374
  const walk = (d, depth = 0) => {
@@ -35417,7 +35393,7 @@ async function uploadOutputDir(apiUrl, wsId, projectId, issueId, commentId, dir)
35417
35393
  let uploaded = 0;
35418
35394
  for (const f of files) {
35419
35395
  try {
35420
- const data = readFileSync8(f);
35396
+ const data = readFileSync9(f);
35421
35397
  const form = new FormData;
35422
35398
  const blob = new Blob([data]);
35423
35399
  form.append("file", blob, f.split("/").pop() || "file");
@@ -35432,7 +35408,7 @@ async function uploadOutputDir(apiUrl, wsId, projectId, issueId, commentId, dir)
35432
35408
  if (res.ok) {
35433
35409
  uploaded++;
35434
35410
  try {
35435
- unlinkSync4(f);
35411
+ unlinkSync5(f);
35436
35412
  } catch {}
35437
35413
  }
35438
35414
  } catch (e) {
@@ -40185,8 +40161,8 @@ var init_chat_doc = __esm(() => {
40185
40161
  });
40186
40162
 
40187
40163
  // src/_impl/chat-peer.ts
40188
- import { existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
40189
- 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";
40190
40166
 
40191
40167
  class ChatPeer {
40192
40168
  chatId;
@@ -40208,16 +40184,16 @@ class ChatPeer {
40208
40184
  }
40209
40185
  loadFromDisk() {
40210
40186
  try {
40211
- if (existsSync11(this.snapshotPath)) {
40212
- const bytes = readFileSync9(this.snapshotPath);
40187
+ if (existsSync12(this.snapshotPath)) {
40188
+ const bytes = readFileSync10(this.snapshotPath);
40213
40189
  return importSnapshot(new Uint8Array(bytes));
40214
40190
  }
40215
40191
  } catch {}
40216
40192
  return new import_loro_crdt2.LoroDoc;
40217
40193
  }
40218
40194
  persist() {
40219
- if (!existsSync11(dirname9(this.snapshotPath)))
40220
- mkdirSync8(dirname9(this.snapshotPath), { recursive: true });
40195
+ if (!existsSync12(dirname10(this.snapshotPath)))
40196
+ mkdirSync9(dirname10(this.snapshotPath), { recursive: true });
40221
40197
  writeFileSync7(this.snapshotPath, exportSnapshot(this.doc));
40222
40198
  this.dirtySinceWrite = 0;
40223
40199
  }
@@ -40384,12 +40360,6 @@ async function handleChatTurn(opts) {
40384
40360
  return;
40385
40361
  }
40386
40362
  const chosen = opts.preferredRuntime && detected.find((d) => d.type === opts.preferredRuntime) || detected.find((d) => d.type === "claude-code") || detected[0];
40387
- const adapter = await resolveAcpAdapter(chosen.type, chosen.path);
40388
- if (!adapter) {
40389
- opts.onDone(`error: no ACP adapter for ${chosen.type}`);
40390
- return;
40391
- }
40392
- const sessionId = chatSessions.get(opts.chatId) || null;
40393
40363
  let prompt = opts.prompt;
40394
40364
  if (opts.systemPreamble && !chatPreambleSent.has(opts.chatId)) {
40395
40365
  prompt = `${opts.systemPreamble}
@@ -40399,6 +40369,52 @@ async function handleChatTurn(opts) {
40399
40369
  ${opts.prompt}`;
40400
40370
  chatPreambleSent.add(opts.chatId);
40401
40371
  }
40372
+ if (ACPX_RUNTIMES.has(chosen.type)) {
40373
+ try {
40374
+ const { stopReason } = await runAcpx({
40375
+ agentType: chosen.type,
40376
+ prompt,
40377
+ cwd: opts.cwd,
40378
+ sessionName: `chat-${opts.chatId}`,
40379
+ onEvent: (ev) => {
40380
+ if (ev.event_type === "assistant_text") {
40381
+ const text = ev.payload?.text;
40382
+ if (typeof text === "string" && text.length)
40383
+ opts.onChunk(text);
40384
+ } else if (ev.event_type === "tool_call") {
40385
+ const p = ev.payload;
40386
+ opts.onToolCall?.({
40387
+ id: String(p?.id ?? ""),
40388
+ tool: String(p?.tool ?? "tool"),
40389
+ kind: p?.kind ? String(p.kind) : undefined,
40390
+ status: p?.status ? String(p.status) : undefined,
40391
+ input: p?.input
40392
+ });
40393
+ } else if (ev.event_type === "tool_result") {
40394
+ const p = ev.payload;
40395
+ opts.onToolResult?.({
40396
+ tool_call_id: String(p?.tool_use_id ?? p?.id ?? ""),
40397
+ content: typeof p?.content === "string" ? p.content : JSON.stringify(p?.content ?? "")
40398
+ });
40399
+ } else if (ev.event_type === "error") {
40400
+ const m = String(ev.payload?.message ?? "agent error");
40401
+ opts.onDone(`error: ${m}`);
40402
+ }
40403
+ }
40404
+ });
40405
+ opts.onDone(stopReason || "ok");
40406
+ } catch (e) {
40407
+ opts.log(`[chat-turn] ${opts.chatId} acpx crash: ${e.message}`);
40408
+ opts.onDone(`error: ${e.message}`);
40409
+ }
40410
+ return;
40411
+ }
40412
+ const adapter = await resolveAcpAdapter(chosen.type, chosen.path);
40413
+ if (!adapter) {
40414
+ opts.onDone(`error: no ACP adapter for ${chosen.type}`);
40415
+ return;
40416
+ }
40417
+ const sessionId = chatSessions.get(opts.chatId) || null;
40402
40418
  try {
40403
40419
  await runAcp({
40404
40420
  apiUrl: "",
@@ -40445,11 +40461,13 @@ ${opts.prompt}`;
40445
40461
  opts.onDone(`error: ${e.message}`);
40446
40462
  }
40447
40463
  }
40448
- var chatSessions, chatPreambleSent;
40464
+ var ACPX_RUNTIMES, chatSessions, chatPreambleSent;
40449
40465
  var init_chat_turn = __esm(() => {
40450
40466
  init_acp_runner();
40467
+ init_acpx_runner();
40451
40468
  init_run_task();
40452
40469
  init_detect();
40470
+ ACPX_RUNTIMES = new Set(["pi", "codex", "openclaw"]);
40453
40471
  chatSessions = new Map;
40454
40472
  chatPreambleSent = new Set;
40455
40473
  });
@@ -40559,6 +40577,16 @@ async function executeChatPlanActions(actionsIn, parseErrors, ctx) {
40559
40577
  }
40560
40578
  lines.push(`- [ok] handoff -> ${a.target_agent_id} via **${res.data?.child_key}** expect=${a.expect ?? "summary"}`);
40561
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);
40562
40590
  } else if (a.type === "issue.delete") {
40563
40591
  const res = await apiClient.post(mutateUrl, { action: "delete", id: a.id }, { headers });
40564
40592
  if (!res.success) {
@@ -40714,7 +40742,8 @@ var init_chat_plan_actions = __esm(() => {
40714
40742
  "skill.attach": 5,
40715
40743
  "skill.detach": 5,
40716
40744
  "agent.update": 5,
40717
- "session.create": 3
40745
+ "session.create": 3,
40746
+ "issue.comment": 5
40718
40747
  };
40719
40748
  });
40720
40749
 
@@ -40826,8 +40855,10 @@ class ChatSupervisor {
40826
40855
  return;
40827
40856
  }
40828
40857
  }
40829
- async resolveAgentRuntime(agentId) {
40830
- if (!agentId)
40858
+ async resolveAgentRuntime(chat2, agentId) {
40859
+ if (chat2.runtime)
40860
+ return chat2.runtime;
40861
+ if (!agentId || typeof agentId !== "string")
40831
40862
  return null;
40832
40863
  const headers = { authorization: `Bearer ${this.opts.authToken}` };
40833
40864
  try {
@@ -40866,7 +40897,7 @@ class ChatSupervisor {
40866
40897
  const mentionAgent = this.resolveAgentFromMention(allAgents, mentioned);
40867
40898
  const resolvedAgentId = mentionAgent?.id ?? chat2.primary_agent_id ?? null;
40868
40899
  const agentAuthorId = resolvedAgentId || mentioned || "agent";
40869
- const runtime4 = await this.resolveAgentRuntime(resolvedAgentId);
40900
+ const runtime4 = await this.resolveAgentRuntime(chat2, resolvedAgentId);
40870
40901
  const cwd = await this.resolveCwd(chat2);
40871
40902
  if (cwd)
40872
40903
  this.opts.log(`[chat ${chat2.id}] cwd=${cwd}`);
@@ -40894,7 +40925,7 @@ class ChatSupervisor {
40894
40925
  peer.finalizePartialMessage(textContainerId);
40895
40926
  textContainerId = null;
40896
40927
  };
40897
- const agentDisplayName = mentionAgent?.name ?? allAgents.find((a) => a.id === resolvedAgentId)?.name ?? undefined;
40928
+ const agentDisplayName = mentionAgent?.name ?? allAgents.find((a) => a.id === resolvedAgentId)?.name ?? runtime4 ?? undefined;
40898
40929
  const ensureTextOpen = () => {
40899
40930
  if (textContainerId == null) {
40900
40931
  textContainerId = peer.beginPartialAgentMessage(agentAuthorId, agentDisplayName).containerId;
@@ -41064,7 +41095,7 @@ class ChatSupervisor {
41064
41095
  }
41065
41096
  }
41066
41097
  async autoTitle(chat2, userMsg, agentReply) {
41067
- const runtime4 = await this.resolveAgentRuntime(chat2);
41098
+ const runtime4 = await this.resolveAgentRuntime(chat2, chat2.primary_agent_id);
41068
41099
  const trimUser = userMsg.slice(0, 800);
41069
41100
  const trimAgent = agentReply.slice(0, 1200);
41070
41101
  const prompt = `Reply with ONLY a 3-6 word title for this conversation. No quotes, no punctuation, no explanation. Capitalize each word.
@@ -41126,6 +41157,7 @@ Action vocabulary:
41126
41157
  {"type":"update","id":"<issue id or key>","status":"done"},
41127
41158
  {"type":"delegate","id":"<issue id>","assignee_id":"<agent id>"},
41128
41159
  {"type":"handoff","target_agent_id":"<agent id>","prompt":"..."},
41160
+ {"type":"issue.comment","id":"<issue id or key>","body":"@<agent name> ..."},
41129
41161
  {"type":"issue.delete","id":"<issue id>"},
41130
41162
  {"type":"issue.delete_where","status":"todo","limit":50},
41131
41163
  {"type":"issue.list","status":"todo","limit":20},
@@ -41140,8 +41172,9 @@ Action vocabulary:
41140
41172
 
41141
41173
  Rules:
41142
41174
  - Omit the block entirely if no actions are needed. Don't emit empty arrays.
41143
- - 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.
41144
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.
41145
41178
  - \`allowed_tools\` on a new agent must be a subset of generally available tools.
41146
41179
 
41147
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.
@@ -41730,7 +41763,7 @@ import { parseArgs } from "util";
41730
41763
  // package.json
41731
41764
  var package_default = {
41732
41765
  name: "@shipers-dev/multi",
41733
- version: "0.38.0",
41766
+ version: "0.39.1",
41734
41767
  type: "module",
41735
41768
  bin: {
41736
41769
  "multi-agent": "./dist/index.js"
@@ -42547,7 +42580,133 @@ var linkCmd = exports_Effect.fn("linkCmd")(function* (apiUrl, agentId) {
42547
42580
  // src/commands/restart.ts
42548
42581
  init_esm();
42549
42582
  init_paths();
42550
- 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
42551
42710
  var isRunning4 = (pid) => {
42552
42711
  try {
42553
42712
  process.kill(pid, 0);
@@ -42559,8 +42718,8 @@ var isRunning4 = (pid) => {
42559
42718
  var restartCmd = exports_Effect.fn("restartCmd")(function* (apiUrl) {
42560
42719
  const fs3 = yield* FileSystem;
42561
42720
  yield* (yield* Config4).ensureDirs;
42562
- if (existsSync7(PID_PATH)) {
42563
- const pid = Number(readFileSync7(PID_PATH, "utf8").trim());
42721
+ if (existsSync8(PID_PATH)) {
42722
+ const pid = Number(readFileSync8(PID_PATH, "utf8").trim());
42564
42723
  if (pid && isRunning4(pid)) {
42565
42724
  yield* fs3.writeText(STOP_PATH, "1");
42566
42725
  try {
@@ -42579,14 +42738,17 @@ var restartCmd = exports_Effect.fn("restartCmd")(function* (apiUrl) {
42579
42738
  }
42580
42739
  }
42581
42740
  try {
42582
- if (existsSync7(PID_PATH))
42583
- unlinkSync3(PID_PATH);
42741
+ if (existsSync8(PID_PATH))
42742
+ unlinkSync4(PID_PATH);
42584
42743
  } catch {}
42585
42744
  }
42586
42745
  try {
42587
- if (existsSync7(STOP_PATH))
42588
- unlinkSync3(STOP_PATH);
42746
+ if (existsSync8(STOP_PATH))
42747
+ unlinkSync4(STOP_PATH);
42589
42748
  } catch {}
42749
+ const killed = killStaleConnects();
42750
+ if (killed.length)
42751
+ console.log(`⏹ Killed stale daemons: ${killed.join(",")}`);
42590
42752
  console.log("\uD83D\uDD04 Relaunching daemon...");
42591
42753
  const args2 = Bun.argv.slice(1).filter((a) => a !== "-d" && a !== "--detach" && a !== "restart");
42592
42754
  if (!args2.includes("connect"))
@@ -42678,7 +42840,7 @@ init_client();
42678
42840
  init_detect();
42679
42841
  init_run_task();
42680
42842
  import { Database as Database3 } from "bun:sqlite";
42681
- 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";
42682
42844
  import { homedir as homedir2 } from "os";
42683
42845
  import { join as join13 } from "path";
42684
42846
  init_errors();
@@ -42688,11 +42850,11 @@ var PORT_PATH2 = join13(MULTI_DIR5, "agent.port");
42688
42850
  var STOP_PATH2 = join13(MULTI_DIR5, "stop.flag");
42689
42851
  var TASKS_DB_PATH3 = join13(MULTI_DIR5, "tasks.db");
42690
42852
  function ensureDirs2() {
42691
- if (!existsSync12(MULTI_DIR5))
42692
- mkdirSync9(MULTI_DIR5, { recursive: true });
42853
+ if (!existsSync13(MULTI_DIR5))
42854
+ mkdirSync10(MULTI_DIR5, { recursive: true });
42693
42855
  const logs = join13(MULTI_DIR5, "logs");
42694
- if (!existsSync12(logs))
42695
- mkdirSync9(logs, { recursive: true });
42856
+ if (!existsSync13(logs))
42857
+ mkdirSync10(logs, { recursive: true });
42696
42858
  }
42697
42859
  function isLocalApi(url2) {
42698
42860
  try {
@@ -42871,8 +43033,8 @@ async function ackDispatch(apiUrl, wsId, dispatchId, secret) {
42871
43033
  log3(`ack dispatch ${dispatchId} failed: ${String(e)}`);
42872
43034
  }
42873
43035
  }
42874
- async function drainOfflineDispatches(apiUrl, deviceId, wsId, secret, db2, onEnqueued, isAlive) {
42875
- if (!isAlive())
43036
+ async function drainOfflineDispatches(apiUrl, deviceId, wsId, secret, db2, onEnqueued, isAlive2) {
43037
+ if (!isAlive2())
42876
43038
  return;
42877
43039
  let list;
42878
43040
  try {
@@ -42886,7 +43048,7 @@ async function drainOfflineDispatches(apiUrl, deviceId, wsId, secret, db2, onEnq
42886
43048
  return;
42887
43049
  log3(`draining ${rows.length} offline dispatch(es)`);
42888
43050
  for (const r of rows) {
42889
- if (!isAlive()) {
43051
+ if (!isAlive2()) {
42890
43052
  log3("drain aborted: shutting down");
42891
43053
  return;
42892
43054
  }
@@ -42984,8 +43146,8 @@ var daemonProgram = ({ cfg, apiUrl }) => exports_Effect.gen(function* () {
42984
43146
  if (cfg.authToken)
42985
43147
  setAuthToken(cfg.authToken);
42986
43148
  ensureDirs2();
42987
- if (existsSync12(STOP_PATH2))
42988
- unlinkSync5(STOP_PATH2);
43149
+ if (existsSync13(STOP_PATH2))
43150
+ unlinkSync6(STOP_PATH2);
42989
43151
  const detected = yield* exports_Effect.promise(() => detectAgents());
42990
43152
  const localMode = isLocalApi(apiUrl);
42991
43153
  log3(`daemon device=${cfg.deviceId} pid=${process.pid}`);
@@ -43147,7 +43309,7 @@ var daemonProgram = ({ cfg, apiUrl }) => exports_Effect.gen(function* () {
43147
43309
  let probeFailures = 0;
43148
43310
  while (true) {
43149
43311
  yield* exports_Effect.sleep(exports_Duration.seconds(120));
43150
- if (existsSync12(STOP_PATH2)) {
43312
+ if (existsSync13(STOP_PATH2)) {
43151
43313
  yield* exports_Deferred.succeed(stopDeferred, "stop flag");
43152
43314
  return;
43153
43315
  }
@@ -43172,7 +43334,7 @@ var daemonProgram = ({ cfg, apiUrl }) => exports_Effect.gen(function* () {
43172
43334
  yield* exports_Effect.forkIn(daemonScope)(exports_Effect.gen(function* () {
43173
43335
  while (true) {
43174
43336
  yield* exports_Effect.sleep(exports_Duration.seconds(5));
43175
- if (existsSync12(STOP_PATH2)) {
43337
+ if (existsSync13(STOP_PATH2)) {
43176
43338
  yield* exports_Deferred.succeed(stopDeferred, "stop flag");
43177
43339
  return;
43178
43340
  }
@@ -43209,12 +43371,12 @@ var daemonProgram = ({ cfg, apiUrl }) => exports_Effect.gen(function* () {
43209
43371
  yield* exports_Effect.race(exports_Fiber.interruptAll(inFlight), exports_Effect.sleep(exports_Duration.seconds(10)));
43210
43372
  }
43211
43373
  yield* exports_Scope.close(daemonScope, exports_Exit.void).pipe(exports_Effect.catchAll(() => exports_Effect.void));
43212
- if (existsSync12(PID_PATH2))
43213
- unlinkSync5(PID_PATH2);
43214
- if (existsSync12(STOP_PATH2))
43215
- unlinkSync5(STOP_PATH2);
43216
- if (existsSync12(PORT_PATH2))
43217
- 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);
43218
43380
  db2.close();
43219
43381
  log3("disconnected");
43220
43382
  });
@@ -43226,81 +43388,6 @@ async function daemonMain(opts) {
43226
43388
  }
43227
43389
  }
43228
43390
 
43229
- // src/_impl/pid-lock.ts
43230
- import { closeSync, existsSync as existsSync13, mkdirSync as mkdirSync10, openSync, readFileSync as readFileSync10, unlinkSync as unlinkSync6, writeSync } from "fs";
43231
- import { dirname as dirname10 } from "path";
43232
- var isAlive = (pid) => {
43233
- if (!Number.isFinite(pid) || pid <= 0)
43234
- return false;
43235
- try {
43236
- process.kill(pid, 0);
43237
- return true;
43238
- } catch (e) {
43239
- return e?.code === "EPERM";
43240
- }
43241
- };
43242
- var writeExclusive = (path, pid) => {
43243
- mkdirSync10(dirname10(path), { recursive: true });
43244
- const fd = openSync(path, "wx");
43245
- try {
43246
- writeSync(fd, String(pid));
43247
- } finally {
43248
- closeSync(fd);
43249
- }
43250
- };
43251
- var acquirePidLock = (path) => {
43252
- const ourPid = process.pid;
43253
- try {
43254
- writeExclusive(path, ourPid);
43255
- return { ok: true };
43256
- } catch (e) {
43257
- if (e?.code !== "EEXIST")
43258
- throw e;
43259
- }
43260
- const raw = (() => {
43261
- try {
43262
- return readFileSync10(path, "utf8").trim();
43263
- } catch {
43264
- return "";
43265
- }
43266
- })();
43267
- const existingPid = Number(raw);
43268
- if (existingPid === ourPid)
43269
- return { ok: true };
43270
- if (Number.isFinite(existingPid) && existingPid > 0 && isAlive(existingPid)) {
43271
- return { ok: false, existingPid };
43272
- }
43273
- try {
43274
- unlinkSync6(path);
43275
- } catch {}
43276
- try {
43277
- writeExclusive(path, ourPid);
43278
- return { ok: true };
43279
- } catch (e) {
43280
- if (e?.code !== "EEXIST")
43281
- throw e;
43282
- const racingRaw = (() => {
43283
- try {
43284
- return readFileSync10(path, "utf8").trim();
43285
- } catch {
43286
- return "";
43287
- }
43288
- })();
43289
- const racingPid = Number(racingRaw);
43290
- return { ok: false, existingPid: Number.isFinite(racingPid) ? racingPid : 0 };
43291
- }
43292
- };
43293
- var releasePidLock = (path) => {
43294
- try {
43295
- if (!existsSync13(path))
43296
- return;
43297
- const raw = readFileSync10(path, "utf8").trim();
43298
- if (Number(raw) !== process.pid)
43299
- return;
43300
- unlinkSync6(path);
43301
- } catch {}
43302
- };
43303
-
43304
43391
  // src/commands/connect.ts
43305
43392
  init_outbox();
43306
43393
  init_paths();
@@ -43319,6 +43406,9 @@ var connectCmd = exports_Effect.fn("connectCmd")(function* () {
43319
43406
  }
43320
43407
  if (cfg.authToken)
43321
43408
  yield* api2.setAuthToken(cfg.authToken);
43409
+ const killed = killStaleConnects();
43410
+ if (killed.length)
43411
+ yield* logger.log(`connect: killed stale daemons ${killed.join(",")}`);
43322
43412
  if (process.env.MULTI_FORCE_CONNECT !== "1") {
43323
43413
  const lock = acquirePidLock(PID_PATH);
43324
43414
  if (!lock.ok) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipers-dev/multi",
3
- "version": "0.38.0",
3
+ "version": "0.39.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "multi-agent": "./dist/index.js"