@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 +50 -11
- package/package.json +1 -1
- package/src/acp-runner.ts +18 -4
- package/src/index.ts +25 -7
- package/.claude/settings.local.json +0 -11
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: ${
|
|
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: ${
|
|
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. ${
|
|
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: ${
|
|
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.
|
|
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:
|
|
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 (${
|
|
6658
|
+
log(` \u23F9 ${task.key} stopped (${msg})`);
|
|
6639
6659
|
} else {
|
|
6640
|
-
await postStream(apiUrl, issueId, "error", { message:
|
|
6641
|
-
await postComment(`\u274C spawn error: ${
|
|
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: ${
|
|
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: ${
|
|
6998
|
+
yield { event_type: "error", payload: { message: `spawn failed: ${fmtError(e)}` } };
|
|
6960
6999
|
return;
|
|
6961
7000
|
}
|
|
6962
7001
|
const queue = [];
|
package/package.json
CHANGED
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: ${
|
|
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: ${
|
|
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. ${
|
|
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: ${
|
|
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.
|
|
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:
|
|
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 (${
|
|
881
|
+
log(` ⏹ ${task.key} stopped (${msg})`);
|
|
881
882
|
} else {
|
|
882
|
-
await postStream(apiUrl, issueId, 'error', { message:
|
|
883
|
-
await postComment(`❌ spawn error: ${
|
|
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: ${
|
|
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: ${
|
|
1204
|
+
yield { event_type: 'error', payload: { message: `spawn failed: ${fmtError(e)}` } };
|
|
1187
1205
|
return;
|
|
1188
1206
|
}
|
|
1189
1207
|
|