@shipers-dev/multi 0.6.6 → 0.6.8
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 +58 -14
- package/package.json +1 -1
- package/src/acp-runner.ts +34 -7
- package/src/index.ts +22 -7
package/dist/index.js
CHANGED
|
@@ -5199,6 +5199,7 @@ async function runAcp(opts) {
|
|
|
5199
5199
|
const stream2 = ndJsonStream(output, input);
|
|
5200
5200
|
let activeSessionId = opts.sessionId || null;
|
|
5201
5201
|
let recording = false;
|
|
5202
|
+
let chunkCount = 0;
|
|
5202
5203
|
const alwaysAllow = new Set;
|
|
5203
5204
|
const client = {
|
|
5204
5205
|
async sessionUpdate(params) {
|
|
@@ -5263,12 +5264,33 @@ async function runAcp(opts) {
|
|
|
5263
5264
|
}
|
|
5264
5265
|
}
|
|
5265
5266
|
recording = true;
|
|
5266
|
-
const
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5267
|
+
const runPrompt = async () => {
|
|
5268
|
+
chunkCount = 0;
|
|
5269
|
+
return await conn.prompt({
|
|
5270
|
+
sessionId: activeSessionId,
|
|
5271
|
+
prompt: [{ type: "text", text: opts.prompt }]
|
|
5272
|
+
});
|
|
5273
|
+
};
|
|
5274
|
+
let res = await runPrompt();
|
|
5275
|
+
let stopReason = res.stopReason;
|
|
5276
|
+
if (chunkCount === 0 && opts.sessionId) {
|
|
5277
|
+
await opts.onEvent({ event_type: "progress", payload: { message: `resumed session produced no output (stopReason=${stopReason}); retrying with fresh session` } });
|
|
5278
|
+
try {
|
|
5279
|
+
const fresh = await conn.newSession({ cwd: opts.cwd || process.cwd(), mcpServers: [] });
|
|
5280
|
+
activeSessionId = fresh.sessionId;
|
|
5281
|
+
if (opts.onSession)
|
|
5282
|
+
await opts.onSession(fresh.sessionId);
|
|
5283
|
+
res = await runPrompt();
|
|
5284
|
+
stopReason = res.stopReason;
|
|
5285
|
+
} catch (e) {
|
|
5286
|
+
await opts.onEvent({ event_type: "error", payload: { message: `retry with fresh session failed: ${String(e)}` } });
|
|
5287
|
+
}
|
|
5288
|
+
}
|
|
5289
|
+
if (chunkCount === 0) {
|
|
5290
|
+
await opts.onEvent({ event_type: "error", payload: { message: `agent produced no output (stopReason=${stopReason})` } });
|
|
5291
|
+
}
|
|
5292
|
+
await opts.onEvent({ event_type: "result", payload: { stopReason } });
|
|
5293
|
+
return { stopReason, sessionId: activeSessionId };
|
|
5272
5294
|
} finally {
|
|
5273
5295
|
try {
|
|
5274
5296
|
child.kill();
|
|
@@ -5281,12 +5303,15 @@ async function runAcp(opts) {
|
|
|
5281
5303
|
case "agent_message_chunk":
|
|
5282
5304
|
case "agent_thought_chunk": {
|
|
5283
5305
|
const text = extractText(u.content);
|
|
5284
|
-
if (text)
|
|
5306
|
+
if (text) {
|
|
5307
|
+
chunkCount++;
|
|
5285
5308
|
await o.onEvent({ event_type: "assistant_text", payload: { text } });
|
|
5309
|
+
}
|
|
5286
5310
|
break;
|
|
5287
5311
|
}
|
|
5288
5312
|
case "tool_call":
|
|
5289
5313
|
case "tool_call_update": {
|
|
5314
|
+
chunkCount++;
|
|
5290
5315
|
await o.onEvent({ event_type: "tool_call", payload: {
|
|
5291
5316
|
id: u.toolCallId || u.id,
|
|
5292
5317
|
tool: u.title || u.toolName || "tool",
|
|
@@ -5574,7 +5599,7 @@ var LOG_PATH2 = join2(MULTI_DIR, "logs", "agent.log");
|
|
|
5574
5599
|
var SKILLS_DIR = join2(MULTI_DIR, "skills");
|
|
5575
5600
|
var STOP_PATH = join2(MULTI_DIR, "stop.flag");
|
|
5576
5601
|
var TASKS_DB_PATH = join2(MULTI_DIR, "tasks.db");
|
|
5577
|
-
var VERSION = "0.6.
|
|
5602
|
+
var VERSION = "0.6.7";
|
|
5578
5603
|
var COMMANDS = {
|
|
5579
5604
|
setup: "Register this device with a workspace",
|
|
5580
5605
|
connect: "Connect device to realtime hub and execute assigned tasks",
|
|
@@ -6225,11 +6250,18 @@ _${bits.join(" \xB7 ")}_`);
|
|
|
6225
6250
|
const base = `${task.title}
|
|
6226
6251
|
|
|
6227
6252
|
${task.description || ""}`.trim();
|
|
6228
|
-
let userPart
|
|
6253
|
+
let userPart;
|
|
6254
|
+
if (task.followup) {
|
|
6255
|
+
const cleanFollowup = stripSelfMention(task.followup, preferType);
|
|
6256
|
+
userPart = `# New user message \u2014 THIS is the current request
|
|
6257
|
+
|
|
6258
|
+
${cleanFollowup}
|
|
6229
6259
|
|
|
6230
6260
|
---
|
|
6231
|
-
|
|
6232
|
-
|
|
6261
|
+
*Earlier thread (${task.key} "${task.title}") is only background context. Do not re-address the original task unless the new message explicitly refers back to it.*`;
|
|
6262
|
+
} else {
|
|
6263
|
+
userPart = stripSelfMention(base || task.title, preferType);
|
|
6264
|
+
}
|
|
6233
6265
|
if (attachmentRefs.length) {
|
|
6234
6266
|
const lines = attachmentRefs.map((a) => `- ${a.filename}: ${a.path}`).join(`
|
|
6235
6267
|
`);
|
|
@@ -6338,11 +6370,18 @@ ${body}
|
|
|
6338
6370
|
const base = `${task.title}
|
|
6339
6371
|
|
|
6340
6372
|
${task.description || ""}`.trim();
|
|
6341
|
-
let userPart
|
|
6373
|
+
let userPart;
|
|
6374
|
+
if (task.followup) {
|
|
6375
|
+
const cleanFollowup = stripSelfMention(task.followup, preferType);
|
|
6376
|
+
userPart = `# New user message \u2014 THIS is the current request
|
|
6377
|
+
|
|
6378
|
+
${cleanFollowup}
|
|
6342
6379
|
|
|
6343
6380
|
---
|
|
6344
|
-
|
|
6345
|
-
|
|
6381
|
+
*Earlier thread (${task.key} "${task.title}") is only background context. Do not re-address the original task unless the new message explicitly refers back to it.*`;
|
|
6382
|
+
} else {
|
|
6383
|
+
userPart = stripSelfMention(base || task.title, preferType);
|
|
6384
|
+
}
|
|
6346
6385
|
if (attachmentRefs.length) {
|
|
6347
6386
|
const lines = attachmentRefs.map((a) => `- ${a.filename}: ${a.path}`).join(`
|
|
6348
6387
|
`);
|
|
@@ -6381,6 +6420,11 @@ ${userPart}` : userPart;
|
|
|
6381
6420
|
if (n > 0)
|
|
6382
6421
|
log(` \uD83D\uDCCE uploaded ${n} output file(s)`);
|
|
6383
6422
|
}
|
|
6423
|
+
if (!hasAssistantText && !hadError) {
|
|
6424
|
+
const stopReason = turn.result?.stopReason || "unknown";
|
|
6425
|
+
await postComment(`\u26A0\uFE0F Agent returned no output (stopReason=${stopReason}). Adapter may be stuck on a stale session \u2014 try starting a new issue or clearing session_id.`);
|
|
6426
|
+
log(` \u26A0 ${task.key} produced no assistant output (stopReason=${stopReason})`);
|
|
6427
|
+
}
|
|
6384
6428
|
if (hadError) {
|
|
6385
6429
|
await apiClient.post(`${apiUrl}/api/issues/${issueId}/fail`, {});
|
|
6386
6430
|
log(` \u2717 ${task.key} failed`);
|
package/package.json
CHANGED
package/src/acp-runner.ts
CHANGED
|
@@ -57,6 +57,7 @@ export async function runAcp(opts: AcpRunOpts): Promise<{ stopReason: string; se
|
|
|
57
57
|
|
|
58
58
|
let activeSessionId: string | null = opts.sessionId || null;
|
|
59
59
|
let recording = false; // only forward events after prompt() starts
|
|
60
|
+
let chunkCount = 0; // assistant_text + tool_call chunks seen during prompt()
|
|
60
61
|
const alwaysAllow = new Set<string>(); // keys: toolName|kind that user said "always allow"
|
|
61
62
|
|
|
62
63
|
const client: Client = {
|
|
@@ -122,13 +123,38 @@ export async function runAcp(opts: AcpRunOpts): Promise<{ stopReason: string; se
|
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
recording = true;
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
const runPrompt = async () => {
|
|
127
|
+
chunkCount = 0;
|
|
128
|
+
return await conn.prompt({
|
|
129
|
+
sessionId: activeSessionId!,
|
|
130
|
+
prompt: [{ type: 'text', text: opts.prompt }],
|
|
131
|
+
} as any);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
let res = await runPrompt();
|
|
135
|
+
let stopReason = (res as any).stopReason;
|
|
136
|
+
|
|
137
|
+
// Resumed session that returned zero chunks → session is likely stale in the adapter.
|
|
138
|
+
// Retry once with a fresh session so user gets an actual response.
|
|
139
|
+
if (chunkCount === 0 && opts.sessionId) {
|
|
140
|
+
await opts.onEvent({ event_type: 'progress', payload: { message: `resumed session produced no output (stopReason=${stopReason}); retrying with fresh session` } });
|
|
141
|
+
try {
|
|
142
|
+
const fresh = await conn.newSession({ cwd: opts.cwd || process.cwd(), mcpServers: [] } as any);
|
|
143
|
+
activeSessionId = fresh.sessionId;
|
|
144
|
+
if (opts.onSession) await opts.onSession(fresh.sessionId);
|
|
145
|
+
res = await runPrompt();
|
|
146
|
+
stopReason = (res as any).stopReason;
|
|
147
|
+
} catch (e) {
|
|
148
|
+
await opts.onEvent({ event_type: 'error', payload: { message: `retry with fresh session failed: ${String(e)}` } });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (chunkCount === 0) {
|
|
153
|
+
await opts.onEvent({ event_type: 'error', payload: { message: `agent produced no output (stopReason=${stopReason})` } });
|
|
154
|
+
}
|
|
129
155
|
|
|
130
|
-
await opts.onEvent({ event_type: 'result', payload: { stopReason
|
|
131
|
-
return { stopReason
|
|
156
|
+
await opts.onEvent({ event_type: 'result', payload: { stopReason } });
|
|
157
|
+
return { stopReason, sessionId: activeSessionId! };
|
|
132
158
|
} finally {
|
|
133
159
|
try { child.kill(); } catch {}
|
|
134
160
|
}
|
|
@@ -140,11 +166,12 @@ export async function runAcp(opts: AcpRunOpts): Promise<{ stopReason: string; se
|
|
|
140
166
|
case 'agent_message_chunk':
|
|
141
167
|
case 'agent_thought_chunk': {
|
|
142
168
|
const text = extractText(u.content);
|
|
143
|
-
if (text) await o.onEvent({ event_type: 'assistant_text', payload: { text } });
|
|
169
|
+
if (text) { chunkCount++; await o.onEvent({ event_type: 'assistant_text', payload: { text } }); }
|
|
144
170
|
break;
|
|
145
171
|
}
|
|
146
172
|
case 'tool_call':
|
|
147
173
|
case 'tool_call_update': {
|
|
174
|
+
chunkCount++;
|
|
148
175
|
await o.onEvent({ event_type: 'tool_call', payload: {
|
|
149
176
|
id: u.toolCallId || u.id, tool: u.title || u.toolName || 'tool', kind: u.kind, status: u.status, input: u.rawInput, locations: u.locations,
|
|
150
177
|
}});
|
package/src/index.ts
CHANGED
|
@@ -17,7 +17,7 @@ const LOG_PATH = join(MULTI_DIR, 'logs', 'agent.log');
|
|
|
17
17
|
const SKILLS_DIR = join(MULTI_DIR, 'skills');
|
|
18
18
|
const STOP_PATH = join(MULTI_DIR, 'stop.flag');
|
|
19
19
|
const TASKS_DB_PATH = join(MULTI_DIR, 'tasks.db');
|
|
20
|
-
const VERSION = '0.6.
|
|
20
|
+
const VERSION = '0.6.7';
|
|
21
21
|
|
|
22
22
|
const COMMANDS = {
|
|
23
23
|
setup: 'Register this device with a workspace',
|
|
@@ -623,10 +623,13 @@ async function handleRunTask(apiUrl: string, deviceId: string, task: any, detect
|
|
|
623
623
|
try {
|
|
624
624
|
if (useAcp) {
|
|
625
625
|
const base = `${task.title}\n\n${task.description || ''}`.trim();
|
|
626
|
-
let userPart
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
626
|
+
let userPart: string;
|
|
627
|
+
if (task.followup) {
|
|
628
|
+
const cleanFollowup = stripSelfMention(task.followup, preferType);
|
|
629
|
+
userPart = `# New user message — THIS is the current request\n\n${cleanFollowup}\n\n---\n*Earlier thread (${task.key} "${task.title}") is only background context. Do not re-address the original task unless the new message explicitly refers back to it.*`;
|
|
630
|
+
} else {
|
|
631
|
+
userPart = stripSelfMention(base || task.title, preferType);
|
|
632
|
+
}
|
|
630
633
|
if (attachmentRefs.length) {
|
|
631
634
|
const lines = attachmentRefs.map(a => `- ${a.filename}: ${a.path}`).join('\n');
|
|
632
635
|
userPart += `\n\n---\nAttached input files (read them with your tools if useful):\n${lines}\n\nNote: if (and only if) you produce binary or large artifact outputs (screenshots, data exports, generated source files), write them under ${outDir}. Always put your human-facing reply in the chat response itself — do NOT write your answer as a file.`;
|
|
@@ -697,8 +700,13 @@ async function handleRunTask(apiUrl: string, deviceId: string, task: any, detect
|
|
|
697
700
|
}
|
|
698
701
|
} catch {}
|
|
699
702
|
const base = `${task.title}\n\n${task.description || ''}`.trim();
|
|
700
|
-
let userPart
|
|
701
|
-
|
|
703
|
+
let userPart: string;
|
|
704
|
+
if (task.followup) {
|
|
705
|
+
const cleanFollowup = stripSelfMention(task.followup, preferType);
|
|
706
|
+
userPart = `# New user message — THIS is the current request\n\n${cleanFollowup}\n\n---\n*Earlier thread (${task.key} "${task.title}") is only background context. Do not re-address the original task unless the new message explicitly refers back to it.*`;
|
|
707
|
+
} else {
|
|
708
|
+
userPart = stripSelfMention(base || task.title, preferType);
|
|
709
|
+
}
|
|
702
710
|
if (attachmentRefs.length) {
|
|
703
711
|
const lines = attachmentRefs.map(a => `- ${a.filename}: ${a.path}`).join('\n');
|
|
704
712
|
userPart += `\n\n---\nAttached files:\n${lines}\n\nWrite generated files to: ${outDir}`;
|
|
@@ -725,6 +733,13 @@ async function handleRunTask(apiUrl: string, deviceId: string, task: any, detect
|
|
|
725
733
|
if (n > 0) log(` 📎 uploaded ${n} output file(s)`);
|
|
726
734
|
}
|
|
727
735
|
|
|
736
|
+
// Visible fallback: agent ran but emitted nothing user-facing
|
|
737
|
+
if (!hasAssistantText && !hadError) {
|
|
738
|
+
const stopReason = turn.result?.stopReason || 'unknown';
|
|
739
|
+
await postComment(`⚠️ Agent returned no output (stopReason=${stopReason}). Adapter may be stuck on a stale session — try starting a new issue or clearing session_id.`);
|
|
740
|
+
log(` ⚠ ${task.key} produced no assistant output (stopReason=${stopReason})`);
|
|
741
|
+
}
|
|
742
|
+
|
|
728
743
|
if (hadError) { await apiClient.post(`${apiUrl}/api/issues/${issueId}/fail`, {}); log(` ✗ ${task.key} failed`); }
|
|
729
744
|
else { await apiClient.post(`${apiUrl}/api/issues/${issueId}/complete`, {}); log(` ✓ ${task.key} complete`); }
|
|
730
745
|
} catch (e) {
|