@shipers-dev/multi 0.9.1 → 0.9.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 CHANGED
@@ -5169,6 +5169,25 @@ class RequestError extends Error {
5169
5169
  // src/acp-runner.ts
5170
5170
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
5171
5171
  import { dirname } from "path";
5172
+ function fmtErr(e) {
5173
+ if (e == null)
5174
+ return "unknown error";
5175
+ if (typeof e === "string")
5176
+ return e;
5177
+ if (e instanceof Error)
5178
+ return e.message;
5179
+ if (typeof e === "object") {
5180
+ const inner = e.error ?? e.cause ?? e;
5181
+ const msg = inner?.message ?? e.message ?? e.reason ?? e.statusText;
5182
+ const code = inner?.code ?? e.code ?? e.status;
5183
+ if (msg)
5184
+ return code ? `${msg} (code ${code})` : String(msg);
5185
+ try {
5186
+ return JSON.stringify(e).slice(0, 500);
5187
+ } catch {}
5188
+ }
5189
+ return String(e);
5190
+ }
5172
5191
  async function runAcp(opts) {
5173
5192
  const cleanEnv = {};
5174
5193
  for (const [k, v] of Object.entries(process.env)) {
@@ -5221,7 +5240,7 @@ async function runAcp(opts) {
5221
5240
  `) : content;
5222
5241
  return { content: sliced };
5223
5242
  } catch (e) {
5224
- throw new Error(`readTextFile failed: ${String(e)}`);
5243
+ throw new Error(`readTextFile failed: ${fmtErr(e)}`);
5225
5244
  }
5226
5245
  },
5227
5246
  async writeTextFile(params) {
@@ -5232,7 +5251,7 @@ async function runAcp(opts) {
5232
5251
  writeFileSync(params.path, params.content, "utf8");
5233
5252
  return {};
5234
5253
  } catch (e) {
5235
- throw new Error(`writeTextFile failed: ${String(e)}`);
5254
+ throw new Error(`writeTextFile failed: ${fmtErr(e)}`);
5236
5255
  }
5237
5256
  }
5238
5257
  };
@@ -5259,7 +5278,7 @@ async function runAcp(opts) {
5259
5278
  await conn.loadSession?.({ sessionId: activeSessionId, cwd: opts.cwd || process.cwd(), mcpServers: [] });
5260
5279
  await opts.onEvent({ event_type: "progress", payload: { message: `ACP session ${activeSessionId.slice(0, 8)} resumed` } });
5261
5280
  } catch (e) {
5262
- await opts.onEvent({ event_type: "progress", payload: { message: `load_session failed; starting new. ${String(e)}` } });
5281
+ await opts.onEvent({ event_type: "progress", payload: { message: `load_session failed; starting new. ${fmtErr(e)}` } });
5263
5282
  const { sessionId } = await conn.newSession({ cwd: opts.cwd || process.cwd(), mcpServers: [] });
5264
5283
  activeSessionId = sessionId;
5265
5284
  if (opts.onSession)
@@ -5286,7 +5305,7 @@ async function runAcp(opts) {
5286
5305
  res = await runPrompt();
5287
5306
  stopReason = res.stopReason;
5288
5307
  } catch (e) {
5289
- await opts.onEvent({ event_type: "error", payload: { message: `retry with fresh session failed: ${String(e)}` } });
5308
+ await opts.onEvent({ event_type: "error", payload: { message: `retry with fresh session failed: ${fmtErr(e)}` } });
5290
5309
  }
5291
5310
  }
5292
5311
  if (chunkCount === 0) {
@@ -5689,7 +5708,7 @@ var LOG_PATH2 = join3(MULTI_DIR, "logs", "agent.log");
5689
5708
  var SKILLS_DIR = join3(MULTI_DIR, "skills");
5690
5709
  var STOP_PATH = join3(MULTI_DIR, "stop.flag");
5691
5710
  var TASKS_DB_PATH = join3(MULTI_DIR, "tasks.db");
5692
- var VERSION = "0.9.1";
5711
+ var VERSION = "0.9.2";
5693
5712
  var COMMANDS = {
5694
5713
  setup: "Register this device with a workspace",
5695
5714
  connect: "Connect device to realtime hub and execute assigned tasks",
@@ -6164,7 +6183,7 @@ async function handleRunTask(apiUrl, deviceId, task, detected, ctx) {
6164
6183
  worktreeBranch = wt.branch;
6165
6184
  await postStream(apiUrl, issueId, "worktree_created", { path: wt.path, branch: wt.branch, reused: !wt.created });
6166
6185
  } catch (e) {
6167
- await postStream(apiUrl, issueId, "worktree_error", { message: String(e) });
6186
+ await postStream(apiUrl, issueId, "worktree_error", { message: fmtError(e) });
6168
6187
  }
6169
6188
  }
6170
6189
  log(`\u25B6 run_task ${task.key}: ${isFollowup ? "(follow-up) " : ""}${task.title}${workingDir ? ` [cwd: ${workingDir}${worktreeBranch ? ` @${worktreeBranch}` : ""}]` : ""}`);
@@ -6633,14 +6652,15 @@ ${userPart}` : userPart;
6633
6652
  log(` \u2713 ${task.key} complete`);
6634
6653
  }
6635
6654
  } catch (e) {
6655
+ const msg = fmtError(e);
6636
6656
  if (ctx?.runEntry?.stopped) {
6637
6657
  await markStopped(apiUrl, issueId, ctx.runEntry.stopReason || "stopped");
6638
- log(` \u23F9 ${task.key} stopped (${String(e)})`);
6658
+ log(` \u23F9 ${task.key} stopped (${msg})`);
6639
6659
  } else {
6640
- await postStream(apiUrl, issueId, "error", { message: String(e) });
6641
- await postComment(`\u274C spawn error: ${String(e)}`);
6660
+ await postStream(apiUrl, issueId, "error", { message: msg });
6661
+ await postComment(`\u274C spawn error: ${msg}`);
6642
6662
  await apiClient.post(`${apiUrl}/api/issues/${issueId}/fail`, {});
6643
- log(` \u2717 ${task.key} failed: ${String(e)}`);
6663
+ log(` \u2717 ${task.key} failed: ${msg}`);
6644
6664
  }
6645
6665
  }
6646
6666
  }
@@ -6802,6 +6822,25 @@ function stripMd(s) {
6802
6822
  function stripSelfMention(prompt, _agentType) {
6803
6823
  return prompt.replace(/^@[A-Za-z0-9_\-]+\s*/, "").trim();
6804
6824
  }
6825
+ function fmtError(e) {
6826
+ if (e == null)
6827
+ return "unknown error";
6828
+ if (typeof e === "string")
6829
+ return e;
6830
+ if (e instanceof Error)
6831
+ return e.stack ? `${e.message}` : String(e);
6832
+ if (typeof e === "object") {
6833
+ const inner = e.error ?? e.cause ?? e;
6834
+ const msg = inner?.message ?? e.message ?? e.reason ?? e.statusText;
6835
+ const code = inner?.code ?? e.code ?? e.status;
6836
+ if (msg)
6837
+ return code ? `${msg} (code ${code})` : String(msg);
6838
+ try {
6839
+ return JSON.stringify(e).slice(0, 500);
6840
+ } catch {}
6841
+ }
6842
+ return String(e);
6843
+ }
6805
6844
  function statusIcon(status) {
6806
6845
  switch (status) {
6807
6846
  case "pending":
@@ -6956,7 +6995,7 @@ Context (original task ${task.key}): ${task.title}` : base || task.title;
6956
6995
  try {
6957
6996
  proc = Bun.spawn([agent.path, ...args], { stdout: "pipe", stderr: "pipe", stdin: "ignore", cwd: task?.working_dir || undefined });
6958
6997
  } catch (e) {
6959
- yield { event_type: "error", payload: { message: `spawn failed: ${String(e)}` } };
6998
+ yield { event_type: "error", payload: { message: `spawn failed: ${fmtError(e)}` } };
6960
6999
  return;
6961
7000
  }
6962
7001
  const queue = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipers-dev/multi",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "multi-agent": "./dist/index.js"
package/src/acp-runner.ts CHANGED
@@ -8,6 +8,20 @@ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
8
8
  import { dirname } from 'path';
9
9
  import { apiClient } from './client';
10
10
 
11
+ function fmtErr(e: any): string {
12
+ if (e == null) return 'unknown error';
13
+ if (typeof e === 'string') return e;
14
+ if (e instanceof Error) return e.message;
15
+ if (typeof e === 'object') {
16
+ const inner = e.error ?? e.cause ?? e;
17
+ const msg = inner?.message ?? e.message ?? e.reason ?? e.statusText;
18
+ const code = inner?.code ?? e.code ?? e.status;
19
+ if (msg) return code ? `${msg} (code ${code})` : String(msg);
20
+ try { return JSON.stringify(e).slice(0, 500); } catch {}
21
+ }
22
+ return String(e);
23
+ }
24
+
11
25
  export type AcpEvent =
12
26
  | { event_type: 'progress'; payload: any }
13
27
  | { event_type: 'assistant_text'; payload: { text: string } }
@@ -79,7 +93,7 @@ export async function runAcp(opts: AcpRunOpts): Promise<{ stopReason: string; se
79
93
  : content;
80
94
  return { content: sliced };
81
95
  } catch (e) {
82
- throw new Error(`readTextFile failed: ${String(e)}`);
96
+ throw new Error(`readTextFile failed: ${fmtErr(e)}`);
83
97
  }
84
98
  },
85
99
  async writeTextFile(params: WriteTextFileRequest): Promise<WriteTextFileResponse> {
@@ -89,7 +103,7 @@ export async function runAcp(opts: AcpRunOpts): Promise<{ stopReason: string; se
89
103
  writeFileSync(params.path, params.content, 'utf8');
90
104
  return {};
91
105
  } catch (e) {
92
- throw new Error(`writeTextFile failed: ${String(e)}`);
106
+ throw new Error(`writeTextFile failed: ${fmtErr(e)}`);
93
107
  }
94
108
  },
95
109
  };
@@ -118,7 +132,7 @@ export async function runAcp(opts: AcpRunOpts): Promise<{ stopReason: string; se
118
132
  await conn.loadSession?.({ sessionId: activeSessionId, cwd: opts.cwd || process.cwd(), mcpServers: [] } as any);
119
133
  await opts.onEvent({ event_type: 'progress', payload: { message: `ACP session ${activeSessionId.slice(0, 8)} resumed` } });
120
134
  } catch (e) {
121
- await opts.onEvent({ event_type: 'progress', payload: { message: `load_session failed; starting new. ${String(e)}` } });
135
+ await opts.onEvent({ event_type: 'progress', payload: { message: `load_session failed; starting new. ${fmtErr(e)}` } });
122
136
  const { sessionId } = await conn.newSession({ cwd: opts.cwd || process.cwd(), mcpServers: [] } as any);
123
137
  activeSessionId = sessionId;
124
138
  if (opts.onSession) await opts.onSession(sessionId);
@@ -148,7 +162,7 @@ export async function runAcp(opts: AcpRunOpts): Promise<{ stopReason: string; se
148
162
  res = await runPrompt();
149
163
  stopReason = (res as any).stopReason;
150
164
  } catch (e) {
151
- await opts.onEvent({ event_type: 'error', payload: { message: `retry with fresh session failed: ${String(e)}` } });
165
+ await opts.onEvent({ event_type: 'error', payload: { message: `retry with fresh session failed: ${fmtErr(e)}` } });
152
166
  }
153
167
  }
154
168
 
package/src/index.ts CHANGED
@@ -18,7 +18,7 @@ const LOG_PATH = join(MULTI_DIR, 'logs', 'agent.log');
18
18
  const SKILLS_DIR = join(MULTI_DIR, 'skills');
19
19
  const STOP_PATH = join(MULTI_DIR, 'stop.flag');
20
20
  const TASKS_DB_PATH = join(MULTI_DIR, 'tasks.db');
21
- const VERSION = '0.9.1';
21
+ const VERSION = '0.9.2';
22
22
 
23
23
  const COMMANDS = {
24
24
  setup: 'Register this device with a workspace',
@@ -501,7 +501,7 @@ async function handleRunTask(apiUrl: string, deviceId: string, task: any, detect
501
501
  worktreeBranch = wt.branch;
502
502
  await postStream(apiUrl, issueId, 'worktree_created', { path: wt.path, branch: wt.branch, reused: !wt.created });
503
503
  } catch (e) {
504
- await postStream(apiUrl, issueId, 'worktree_error', { message: String(e) });
504
+ await postStream(apiUrl, issueId, 'worktree_error', { message: fmtError(e) });
505
505
  }
506
506
  }
507
507
 
@@ -875,14 +875,15 @@ async function handleRunTask(apiUrl: string, deviceId: string, task: any, detect
875
875
  } else if (hadError) { await apiClient.post(`${apiUrl}/api/issues/${issueId}/fail`, {}); log(` ✗ ${task.key} failed`); }
876
876
  else { await apiClient.post(`${apiUrl}/api/issues/${issueId}/complete`, {}); log(` ✓ ${task.key} complete`); }
877
877
  } catch (e) {
878
+ const msg = fmtError(e);
878
879
  if (ctx?.runEntry?.stopped) {
879
880
  await markStopped(apiUrl, issueId, ctx.runEntry.stopReason || 'stopped');
880
- log(` ⏹ ${task.key} stopped (${String(e)})`);
881
+ log(` ⏹ ${task.key} stopped (${msg})`);
881
882
  } else {
882
- await postStream(apiUrl, issueId, 'error', { message: String(e) });
883
- await postComment(`❌ spawn error: ${String(e)}`);
883
+ await postStream(apiUrl, issueId, 'error', { message: msg });
884
+ await postComment(`❌ spawn error: ${msg}`);
884
885
  await apiClient.post(`${apiUrl}/api/issues/${issueId}/fail`, {});
885
- log(` ✗ ${task.key} failed: ${String(e)}`);
886
+ log(` ✗ ${task.key} failed: ${msg}`);
886
887
  }
887
888
  }
888
889
  }
@@ -1040,6 +1041,23 @@ function stripSelfMention(prompt: string, _agentType?: string): string {
1040
1041
  return prompt.replace(/^@[A-Za-z0-9_\-]+\s*/, '').trim();
1041
1042
  }
1042
1043
 
1044
+ // Best-effort error → string. Plain Error, ACP error envelopes ({error:{message,code,data}}),
1045
+ // fetch Response-shaped rejections, and arbitrary objects all stringify to something readable
1046
+ // instead of "[object Object]".
1047
+ function fmtError(e: any): string {
1048
+ if (e == null) return 'unknown error';
1049
+ if (typeof e === 'string') return e;
1050
+ if (e instanceof Error) return e.stack ? `${e.message}` : String(e);
1051
+ if (typeof e === 'object') {
1052
+ const inner = e.error ?? e.cause ?? e;
1053
+ const msg = inner?.message ?? e.message ?? e.reason ?? e.statusText;
1054
+ const code = inner?.code ?? e.code ?? e.status;
1055
+ if (msg) return code ? `${msg} (code ${code})` : String(msg);
1056
+ try { return JSON.stringify(e).slice(0, 500); } catch {}
1057
+ }
1058
+ return String(e);
1059
+ }
1060
+
1043
1061
  function statusIcon(status?: string): string {
1044
1062
  switch (status) {
1045
1063
  case 'pending': return '⏳';
@@ -1183,7 +1201,7 @@ function makeCliRunner(agent: { type: string; path: string }): Runner {
1183
1201
  try {
1184
1202
  proc = Bun.spawn([agent.path, ...args], { stdout: 'pipe', stderr: 'pipe', stdin: 'ignore', cwd: (task as any)?.working_dir || undefined });
1185
1203
  } catch (e) {
1186
- yield { event_type: 'error', payload: { message: `spawn failed: ${String(e)}` } };
1204
+ yield { event_type: 'error', payload: { message: `spawn failed: ${fmtError(e)}` } };
1187
1205
  return;
1188
1206
  }
1189
1207
 
@@ -1,11 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "mcp__acp__Write",
5
- "Bash(npm install:*)",
6
- "Bash(npx tsc:*)",
7
- "Bash(npx vite build:*)",
8
- "mcp__acp__Edit"
9
- ]
10
- }
11
- }