@shipers-dev/multi 0.48.0 → 0.51.0
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 +448 -127
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -33133,25 +33133,146 @@ var init_workspace_mutex = __esm(() => {
|
|
|
33133
33133
|
]);
|
|
33134
33134
|
});
|
|
33135
33135
|
|
|
33136
|
+
// src/_impl/adapter-pidfile.ts
|
|
33137
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync5, readdirSync as readdirSync2, readFileSync as readFileSync5, unlinkSync as unlinkSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
33138
|
+
import { homedir } from "os";
|
|
33139
|
+
import { join as join7 } from "path";
|
|
33140
|
+
import { spawnSync } from "child_process";
|
|
33141
|
+
function ensureDir() {
|
|
33142
|
+
if (!existsSync5(ADAPTERS_DIR))
|
|
33143
|
+
mkdirSync5(ADAPTERS_DIR, { recursive: true });
|
|
33144
|
+
}
|
|
33145
|
+
function pidfilePath(pid) {
|
|
33146
|
+
return join7(ADAPTERS_DIR, `${pid}.json`);
|
|
33147
|
+
}
|
|
33148
|
+
function writeAdapterPidfile(entry) {
|
|
33149
|
+
ensureDir();
|
|
33150
|
+
const full = {
|
|
33151
|
+
...entry,
|
|
33152
|
+
daemon_pid: entry.daemon_pid ?? process.pid,
|
|
33153
|
+
started_at: Date.now()
|
|
33154
|
+
};
|
|
33155
|
+
const path = pidfilePath(entry.pid);
|
|
33156
|
+
try {
|
|
33157
|
+
writeFileSync5(path, JSON.stringify(full) + `
|
|
33158
|
+
`, "utf8");
|
|
33159
|
+
} catch {}
|
|
33160
|
+
return path;
|
|
33161
|
+
}
|
|
33162
|
+
function removeAdapterPidfile(pid) {
|
|
33163
|
+
try {
|
|
33164
|
+
unlinkSync3(pidfilePath(pid));
|
|
33165
|
+
} catch {}
|
|
33166
|
+
}
|
|
33167
|
+
function inspectPid(pid) {
|
|
33168
|
+
try {
|
|
33169
|
+
const r = spawnSync("ps", ["-o", "ppid=,command=", "-p", String(pid)], { encoding: "utf8" });
|
|
33170
|
+
if (r.status !== 0)
|
|
33171
|
+
return { alive: false, ppid: 0, command: "" };
|
|
33172
|
+
const line = (r.stdout || "").trim();
|
|
33173
|
+
if (!line)
|
|
33174
|
+
return { alive: false, ppid: 0, command: "" };
|
|
33175
|
+
const m = line.match(/^\s*(\d+)\s+(.*)$/);
|
|
33176
|
+
if (!m)
|
|
33177
|
+
return { alive: false, ppid: 0, command: "" };
|
|
33178
|
+
return { alive: true, ppid: parseInt(m[1], 10), command: m[2] };
|
|
33179
|
+
} catch {
|
|
33180
|
+
return { alive: false, ppid: 0, command: "" };
|
|
33181
|
+
}
|
|
33182
|
+
}
|
|
33183
|
+
function tryKill(pid, signal) {
|
|
33184
|
+
try {
|
|
33185
|
+
process.kill(pid, signal);
|
|
33186
|
+
return true;
|
|
33187
|
+
} catch {
|
|
33188
|
+
return false;
|
|
33189
|
+
}
|
|
33190
|
+
}
|
|
33191
|
+
function isPidAlive(pid) {
|
|
33192
|
+
try {
|
|
33193
|
+
process.kill(pid, 0);
|
|
33194
|
+
return true;
|
|
33195
|
+
} catch {
|
|
33196
|
+
return false;
|
|
33197
|
+
}
|
|
33198
|
+
}
|
|
33199
|
+
function reapStaleAdapters(log3 = () => {}) {
|
|
33200
|
+
ensureDir();
|
|
33201
|
+
let entries2 = [];
|
|
33202
|
+
try {
|
|
33203
|
+
entries2 = readdirSync2(ADAPTERS_DIR);
|
|
33204
|
+
} catch {
|
|
33205
|
+
return 0;
|
|
33206
|
+
}
|
|
33207
|
+
let reaped = 0;
|
|
33208
|
+
for (const name of entries2) {
|
|
33209
|
+
if (!name.endsWith(".json"))
|
|
33210
|
+
continue;
|
|
33211
|
+
const path = join7(ADAPTERS_DIR, name);
|
|
33212
|
+
let parsed = null;
|
|
33213
|
+
try {
|
|
33214
|
+
parsed = JSON.parse(readFileSync5(path, "utf8"));
|
|
33215
|
+
} catch {}
|
|
33216
|
+
if (!parsed || typeof parsed.pid !== "number") {
|
|
33217
|
+
try {
|
|
33218
|
+
unlinkSync3(path);
|
|
33219
|
+
} catch {}
|
|
33220
|
+
continue;
|
|
33221
|
+
}
|
|
33222
|
+
const info = inspectPid(parsed.pid);
|
|
33223
|
+
if (!info.alive) {
|
|
33224
|
+
try {
|
|
33225
|
+
unlinkSync3(path);
|
|
33226
|
+
} catch {}
|
|
33227
|
+
continue;
|
|
33228
|
+
}
|
|
33229
|
+
if (parsed.daemon_pid && isPidAlive(parsed.daemon_pid))
|
|
33230
|
+
continue;
|
|
33231
|
+
if (parsed.bin && !info.command.includes(parsed.bin)) {
|
|
33232
|
+
try {
|
|
33233
|
+
unlinkSync3(path);
|
|
33234
|
+
} catch {}
|
|
33235
|
+
continue;
|
|
33236
|
+
}
|
|
33237
|
+
if (info.ppid !== 1)
|
|
33238
|
+
continue;
|
|
33239
|
+
log3(`[reap] killing orphan ${parsed.kind} pid=${parsed.pid} bin=${parsed.bin} session=${parsed.session_id ?? "?"}`);
|
|
33240
|
+
tryKill(parsed.pid, "SIGTERM");
|
|
33241
|
+
setTimeout(() => {
|
|
33242
|
+
if (isPidAlive(parsed.pid))
|
|
33243
|
+
tryKill(parsed.pid, "SIGKILL");
|
|
33244
|
+
}, 1500).unref?.();
|
|
33245
|
+
try {
|
|
33246
|
+
unlinkSync3(path);
|
|
33247
|
+
} catch {}
|
|
33248
|
+
reaped++;
|
|
33249
|
+
}
|
|
33250
|
+
return reaped;
|
|
33251
|
+
}
|
|
33252
|
+
var ADAPTERS_DIR;
|
|
33253
|
+
var init_adapter_pidfile = __esm(() => {
|
|
33254
|
+
ADAPTERS_DIR = join7(homedir(), ".multi", "adapters");
|
|
33255
|
+
});
|
|
33256
|
+
|
|
33136
33257
|
// src/_impl/acp-runner.ts
|
|
33137
|
-
import { readFileSync as
|
|
33138
|
-
import { dirname as dirname6, join as
|
|
33258
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, existsSync as existsSync6 } from "fs";
|
|
33259
|
+
import { dirname as dirname6, join as join8 } from "path";
|
|
33139
33260
|
function ensureBypassPermissions(cwd) {
|
|
33140
33261
|
try {
|
|
33141
|
-
const dir =
|
|
33142
|
-
if (!
|
|
33143
|
-
|
|
33144
|
-
const path =
|
|
33262
|
+
const dir = join8(cwd, ".claude");
|
|
33263
|
+
if (!existsSync6(dir))
|
|
33264
|
+
mkdirSync6(dir, { recursive: true });
|
|
33265
|
+
const path = join8(dir, "settings.local.json");
|
|
33145
33266
|
let json2 = {};
|
|
33146
|
-
if (
|
|
33267
|
+
if (existsSync6(path)) {
|
|
33147
33268
|
try {
|
|
33148
|
-
json2 = JSON.parse(
|
|
33269
|
+
json2 = JSON.parse(readFileSync6(path, "utf8"));
|
|
33149
33270
|
} catch {
|
|
33150
33271
|
json2 = {};
|
|
33151
33272
|
}
|
|
33152
33273
|
}
|
|
33153
33274
|
json2.permissions = { ...json2.permissions || {}, defaultMode: "bypassPermissions" };
|
|
33154
|
-
|
|
33275
|
+
writeFileSync6(path, JSON.stringify(json2, null, 2) + `
|
|
33155
33276
|
`, "utf8");
|
|
33156
33277
|
} catch {}
|
|
33157
33278
|
}
|
|
@@ -33195,6 +33316,16 @@ async function runAcp(opts) {
|
|
|
33195
33316
|
try {
|
|
33196
33317
|
opts.onSpawn?.(child);
|
|
33197
33318
|
} catch {}
|
|
33319
|
+
const pidfileBin = argv[0]?.split("/").pop() || "claude-code-acp";
|
|
33320
|
+
if (typeof child.pid === "number") {
|
|
33321
|
+
writeAdapterPidfile({
|
|
33322
|
+
pid: child.pid,
|
|
33323
|
+
bin: pidfileBin,
|
|
33324
|
+
kind: "acp",
|
|
33325
|
+
session_id: opts.sessionId ?? null,
|
|
33326
|
+
issue_id: opts.issueId ?? null
|
|
33327
|
+
});
|
|
33328
|
+
}
|
|
33198
33329
|
const output = new WritableStream({
|
|
33199
33330
|
write(chunk2) {
|
|
33200
33331
|
child.stdin.write(chunk2);
|
|
@@ -33210,6 +33341,8 @@ async function runAcp(opts) {
|
|
|
33210
33341
|
let activeSessionId = opts.sessionId || null;
|
|
33211
33342
|
let recording = false;
|
|
33212
33343
|
let chunkCount = 0;
|
|
33344
|
+
let capturingSummary = false;
|
|
33345
|
+
let summaryBuf = "";
|
|
33213
33346
|
const alwaysAllow = new Set;
|
|
33214
33347
|
const client = {
|
|
33215
33348
|
async sessionUpdate(params) {
|
|
@@ -33222,7 +33355,7 @@ async function runAcp(opts) {
|
|
|
33222
33355
|
},
|
|
33223
33356
|
async readTextFile(params) {
|
|
33224
33357
|
try {
|
|
33225
|
-
const content =
|
|
33358
|
+
const content = readFileSync6(params.path, "utf8");
|
|
33226
33359
|
const sliced = typeof params.line === "number" && typeof params.limit === "number" ? content.split(`
|
|
33227
33360
|
`).slice(params.line, params.line + params.limit).join(`
|
|
33228
33361
|
`) : content;
|
|
@@ -33234,9 +33367,9 @@ async function runAcp(opts) {
|
|
|
33234
33367
|
async writeTextFile(params) {
|
|
33235
33368
|
try {
|
|
33236
33369
|
const dir = dirname6(params.path);
|
|
33237
|
-
if (!
|
|
33238
|
-
|
|
33239
|
-
|
|
33370
|
+
if (!existsSync6(dir))
|
|
33371
|
+
mkdirSync6(dir, { recursive: true });
|
|
33372
|
+
writeFileSync6(params.path, params.content, "utf8");
|
|
33240
33373
|
return {};
|
|
33241
33374
|
} catch (e) {
|
|
33242
33375
|
throw new Error(`writeTextFile failed: ${fmtErr(e)}`);
|
|
@@ -33304,15 +33437,41 @@ async function runAcp(opts) {
|
|
|
33304
33437
|
await opts.onEvent({ event_type: "error", payload: { message: `agent produced no output (stopReason=${stopReason})` } });
|
|
33305
33438
|
}
|
|
33306
33439
|
await opts.onEvent({ event_type: "result", payload: { stopReason } });
|
|
33307
|
-
|
|
33440
|
+
let summaryText;
|
|
33441
|
+
if (opts.summaryPrompt && chunkCount > 0) {
|
|
33442
|
+
try {
|
|
33443
|
+
capturingSummary = true;
|
|
33444
|
+
summaryBuf = "";
|
|
33445
|
+
await conn.prompt({
|
|
33446
|
+
sessionId: activeSessionId,
|
|
33447
|
+
prompt: [{ type: "text", text: opts.summaryPrompt }]
|
|
33448
|
+
});
|
|
33449
|
+
summaryText = summaryBuf.trim() || undefined;
|
|
33450
|
+
} catch (e) {
|
|
33451
|
+
await opts.onEvent({ event_type: "progress", payload: { message: `memory summary failed: ${fmtErr(e)}` } });
|
|
33452
|
+
} finally {
|
|
33453
|
+
capturingSummary = false;
|
|
33454
|
+
}
|
|
33455
|
+
}
|
|
33456
|
+
return { stopReason, sessionId: activeSessionId, summaryText };
|
|
33308
33457
|
} finally {
|
|
33309
33458
|
try {
|
|
33310
33459
|
child.kill();
|
|
33311
33460
|
} catch {}
|
|
33461
|
+
if (typeof child.pid === "number")
|
|
33462
|
+
removeAdapterPidfile(child.pid);
|
|
33312
33463
|
}
|
|
33313
33464
|
async function handleSessionUpdate(params, o) {
|
|
33314
33465
|
const u = params.update;
|
|
33315
33466
|
const kind = u.sessionUpdate;
|
|
33467
|
+
if (capturingSummary) {
|
|
33468
|
+
if (kind === "agent_message_chunk") {
|
|
33469
|
+
const text = extractText(u.content);
|
|
33470
|
+
if (text)
|
|
33471
|
+
summaryBuf += text;
|
|
33472
|
+
}
|
|
33473
|
+
return;
|
|
33474
|
+
}
|
|
33316
33475
|
switch (kind) {
|
|
33317
33476
|
case "agent_message_chunk": {
|
|
33318
33477
|
const text = extractText(u.content);
|
|
@@ -33453,17 +33612,111 @@ var init_acp_runner = __esm(() => {
|
|
|
33453
33612
|
init_acp();
|
|
33454
33613
|
init_client();
|
|
33455
33614
|
init_workspace_mutex();
|
|
33615
|
+
init_adapter_pidfile();
|
|
33456
33616
|
});
|
|
33457
33617
|
|
|
33458
33618
|
// src/_impl/acpx-runner.ts
|
|
33459
33619
|
import { appendFileSync as appendFileSync3 } from "fs";
|
|
33460
|
-
import { join as
|
|
33620
|
+
import { join as join9 } from "path";
|
|
33461
33621
|
function dlog(msg) {
|
|
33462
33622
|
try {
|
|
33463
33623
|
appendFileSync3(LOG_PATH2, `[${new Date().toISOString()}] ${msg}
|
|
33464
33624
|
`);
|
|
33465
33625
|
} catch {}
|
|
33466
33626
|
}
|
|
33627
|
+
async function spawnAcpxPrompt(agentType, cwd, sessionName, prompt, collectAssistantText, forward, onPlanUpdate) {
|
|
33628
|
+
const args2 = ["acpx", "--format", "json", "--json-strict", "--approve-all", "--ttl", "0"];
|
|
33629
|
+
if (cwd)
|
|
33630
|
+
args2.push("--cwd", cwd);
|
|
33631
|
+
args2.push(agentType);
|
|
33632
|
+
if (sessionName)
|
|
33633
|
+
args2.push("prompt", "-s", sessionName);
|
|
33634
|
+
else
|
|
33635
|
+
args2.push("prompt");
|
|
33636
|
+
args2.push(prompt);
|
|
33637
|
+
dlog(`[acpx] prompt: ${args2.slice(0, 10).join(" ")} ... (prompt len=${prompt.length})`);
|
|
33638
|
+
const proc = Bun.spawn(args2, { stdout: "pipe", stderr: "pipe", stdin: "ignore" });
|
|
33639
|
+
if (typeof proc.pid === "number") {
|
|
33640
|
+
writeAdapterPidfile({ pid: proc.pid, bin: "acpx", kind: "acpx", session_id: sessionName ?? null });
|
|
33641
|
+
}
|
|
33642
|
+
let stopReason = "end_turn";
|
|
33643
|
+
(async () => {
|
|
33644
|
+
try {
|
|
33645
|
+
const r = proc.stderr.getReader();
|
|
33646
|
+
const d = new TextDecoder;
|
|
33647
|
+
let sb = "";
|
|
33648
|
+
while (true) {
|
|
33649
|
+
const { value: value3, done: done9 } = await r.read();
|
|
33650
|
+
if (done9)
|
|
33651
|
+
break;
|
|
33652
|
+
sb += d.decode(value3, { stream: true });
|
|
33653
|
+
let nl;
|
|
33654
|
+
while ((nl = sb.indexOf(`
|
|
33655
|
+
`)) !== -1) {
|
|
33656
|
+
const ln = sb.slice(0, nl).trim();
|
|
33657
|
+
sb = sb.slice(nl + 1);
|
|
33658
|
+
if (ln)
|
|
33659
|
+
dlog(`[acpx stderr] ${ln.slice(0, 500)}`);
|
|
33660
|
+
}
|
|
33661
|
+
}
|
|
33662
|
+
} catch {}
|
|
33663
|
+
})();
|
|
33664
|
+
const fakeOpts = { agentType, prompt, cwd, sessionName, onEvent: () => {}, onPlanUpdate };
|
|
33665
|
+
const reader = proc.stdout.getReader();
|
|
33666
|
+
const dec = new TextDecoder;
|
|
33667
|
+
let buf = "";
|
|
33668
|
+
while (true) {
|
|
33669
|
+
const { value: value3, done: done9 } = await reader.read();
|
|
33670
|
+
if (done9)
|
|
33671
|
+
break;
|
|
33672
|
+
buf += dec.decode(value3, { stream: true });
|
|
33673
|
+
let nl;
|
|
33674
|
+
while ((nl = buf.indexOf(`
|
|
33675
|
+
`)) !== -1) {
|
|
33676
|
+
const line = buf.slice(0, nl).trim();
|
|
33677
|
+
buf = buf.slice(nl + 1);
|
|
33678
|
+
if (!line)
|
|
33679
|
+
continue;
|
|
33680
|
+
dlog(`[acpx stdout] ${line.slice(0, 500)}`);
|
|
33681
|
+
const events = parseAcpLine(line, (r) => {
|
|
33682
|
+
stopReason = r;
|
|
33683
|
+
}, fakeOpts);
|
|
33684
|
+
for (const ev of events) {
|
|
33685
|
+
if (collectAssistantText && ev.event_type === "assistant_text") {
|
|
33686
|
+
const t = ev.payload?.text;
|
|
33687
|
+
if (typeof t === "string")
|
|
33688
|
+
collectAssistantText.buf += t;
|
|
33689
|
+
} else {
|
|
33690
|
+
await forward(ev);
|
|
33691
|
+
}
|
|
33692
|
+
}
|
|
33693
|
+
}
|
|
33694
|
+
}
|
|
33695
|
+
if (buf.trim()) {
|
|
33696
|
+
const events = parseAcpLine(buf.trim(), (r) => {
|
|
33697
|
+
stopReason = r;
|
|
33698
|
+
}, fakeOpts);
|
|
33699
|
+
for (const ev of events) {
|
|
33700
|
+
if (collectAssistantText && ev.event_type === "assistant_text") {
|
|
33701
|
+
const t = ev.payload?.text;
|
|
33702
|
+
if (typeof t === "string")
|
|
33703
|
+
collectAssistantText.buf += t;
|
|
33704
|
+
} else {
|
|
33705
|
+
await forward(ev);
|
|
33706
|
+
}
|
|
33707
|
+
}
|
|
33708
|
+
}
|
|
33709
|
+
const code = await proc.exited;
|
|
33710
|
+
if (typeof proc.pid === "number")
|
|
33711
|
+
removeAdapterPidfile(proc.pid);
|
|
33712
|
+
if (code !== 0 && code !== 130) {
|
|
33713
|
+
if (!collectAssistantText)
|
|
33714
|
+
await forward({ event_type: "error", payload: { message: `acpx exited with code ${code}` } });
|
|
33715
|
+
else
|
|
33716
|
+
dlog(`[acpx] summary spawn exit=${code}`);
|
|
33717
|
+
}
|
|
33718
|
+
return { stopReason };
|
|
33719
|
+
}
|
|
33467
33720
|
async function runAcpx(opts) {
|
|
33468
33721
|
const args2 = ["acpx", "--format", "json", "--json-strict", "--approve-all", "--ttl", "0"];
|
|
33469
33722
|
if (opts.cwd)
|
|
@@ -33491,6 +33744,9 @@ async function runAcpx(opts) {
|
|
|
33491
33744
|
try {
|
|
33492
33745
|
opts.onSpawn?.(proc);
|
|
33493
33746
|
} catch {}
|
|
33747
|
+
if (typeof proc.pid === "number") {
|
|
33748
|
+
writeAdapterPidfile({ pid: proc.pid, bin: "acpx", kind: "acpx", session_id: opts.sessionName ?? null });
|
|
33749
|
+
}
|
|
33494
33750
|
let stopReason = "end_turn";
|
|
33495
33751
|
(async () => {
|
|
33496
33752
|
try {
|
|
@@ -33547,10 +33803,22 @@ async function runAcpx(opts) {
|
|
|
33547
33803
|
await opts.onEvent(ev);
|
|
33548
33804
|
}
|
|
33549
33805
|
const code = await proc.exited;
|
|
33806
|
+
if (typeof proc.pid === "number")
|
|
33807
|
+
removeAdapterPidfile(proc.pid);
|
|
33550
33808
|
if (code !== 0 && code !== 130) {
|
|
33551
33809
|
await opts.onEvent({ event_type: "error", payload: { message: `acpx exited with code ${code}` } });
|
|
33552
33810
|
}
|
|
33553
|
-
|
|
33811
|
+
let summaryText;
|
|
33812
|
+
if (opts.summaryPrompt && opts.sessionName && (code === 0 || code === 130)) {
|
|
33813
|
+
const collect = { buf: "" };
|
|
33814
|
+
try {
|
|
33815
|
+
await spawnAcpxPrompt(opts.agentType, opts.cwd, opts.sessionName, opts.summaryPrompt, collect, () => {}, opts.onPlanUpdate);
|
|
33816
|
+
summaryText = collect.buf.trim() || undefined;
|
|
33817
|
+
} catch (e) {
|
|
33818
|
+
dlog(`[acpx] summary error: ${String(e)}`);
|
|
33819
|
+
}
|
|
33820
|
+
}
|
|
33821
|
+
return { stopReason, summaryText };
|
|
33554
33822
|
}
|
|
33555
33823
|
function parseAcpLine(line, setStop, opts) {
|
|
33556
33824
|
let msg;
|
|
@@ -33652,8 +33920,9 @@ function extractText2(content) {
|
|
|
33652
33920
|
}
|
|
33653
33921
|
var HOME3, LOG_PATH2;
|
|
33654
33922
|
var init_acpx_runner = __esm(() => {
|
|
33923
|
+
init_adapter_pidfile();
|
|
33655
33924
|
HOME3 = process.env.HOME || process.env.USERPROFILE || ".";
|
|
33656
|
-
LOG_PATH2 =
|
|
33925
|
+
LOG_PATH2 = join9(HOME3, ".multi", "logs", "agent.log");
|
|
33657
33926
|
});
|
|
33658
33927
|
|
|
33659
33928
|
// ../../node_modules/zod/index.js
|
|
@@ -33879,12 +34148,12 @@ function parsePlanBlocks(text) {
|
|
|
33879
34148
|
}
|
|
33880
34149
|
return { actions, errors: errors3 };
|
|
33881
34150
|
}
|
|
33882
|
-
var PLAN_SCHEMA_VERSION =
|
|
34151
|
+
var PLAN_SCHEMA_VERSION = 6, Priority, AssigneeType, IssueStatus, SessionRole, SkillFile, EvalPolicy, PlanActionSchema, PlanEnvelopeSchema, UiBlockSchema, UI_FENCE_RE, FENCE_RE;
|
|
33883
34152
|
var init_plans = __esm(() => {
|
|
33884
34153
|
init_zod();
|
|
33885
34154
|
Priority = exports_external.enum(["low", "medium", "high"]);
|
|
33886
34155
|
AssigneeType = exports_external.enum(["human", "agent"]);
|
|
33887
|
-
IssueStatus = exports_external.enum(["todo", "in_progress", "done", "failed", "stopped", "cancelled"]);
|
|
34156
|
+
IssueStatus = exports_external.enum(["todo", "in_progress", "blocked", "done", "archived", "failed", "stopped", "cancelled"]);
|
|
33888
34157
|
SessionRole = exports_external.enum(["implementer", "reviewer", "test-fixer"]);
|
|
33889
34158
|
SkillFile = exports_external.object({ path: exports_external.string().min(1), content: exports_external.string() });
|
|
33890
34159
|
EvalPolicy = exports_external.object({
|
|
@@ -34085,7 +34354,6 @@ var init_chat = __esm(() => {
|
|
|
34085
34354
|
runtime: exports_external.string().nullable().optional()
|
|
34086
34355
|
});
|
|
34087
34356
|
});
|
|
34088
|
-
|
|
34089
34357
|
// ../lib/index.ts
|
|
34090
34358
|
var init_lib = __esm(() => {
|
|
34091
34359
|
init_streams();
|
|
@@ -34097,8 +34365,8 @@ var init_lib = __esm(() => {
|
|
|
34097
34365
|
|
|
34098
34366
|
// src/_impl/git-enforce.ts
|
|
34099
34367
|
import { spawn as spawn2 } from "node:child_process";
|
|
34100
|
-
import { existsSync as
|
|
34101
|
-
import { join as
|
|
34368
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
34369
|
+
import { join as join10 } from "node:path";
|
|
34102
34370
|
function run4(cwd, cmd, args2) {
|
|
34103
34371
|
return new Promise((resolve2) => {
|
|
34104
34372
|
const p = spawn2(cmd, args2, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -34118,7 +34386,7 @@ function normalizeKey2(issueKey) {
|
|
|
34118
34386
|
return issueKey.toLowerCase().replace(/[^a-z0-9\-_\/]/g, "-");
|
|
34119
34387
|
}
|
|
34120
34388
|
function worktreePath(baseWorkingDir, issueKey) {
|
|
34121
|
-
return
|
|
34389
|
+
return join10(baseWorkingDir, ".multi", "worktrees", normalizeKey2(issueKey));
|
|
34122
34390
|
}
|
|
34123
34391
|
function branchFor(issueKey) {
|
|
34124
34392
|
return `multi/${normalizeKey2(issueKey)}`;
|
|
@@ -34137,7 +34405,7 @@ function enforceCommitAndPush(baseWorkingDir, issueKey, mode = "enforce") {
|
|
|
34137
34405
|
};
|
|
34138
34406
|
if (mode === "off")
|
|
34139
34407
|
return result;
|
|
34140
|
-
if (!
|
|
34408
|
+
if (!existsSync10(wt))
|
|
34141
34409
|
return result;
|
|
34142
34410
|
if (!(yield* isGitRepo2(wt)))
|
|
34143
34411
|
return result;
|
|
@@ -34225,7 +34493,7 @@ var git = (cwd, args2, op) => exports_Effect.tryPromise({
|
|
|
34225
34493
|
try: () => run4(cwd, "git", args2),
|
|
34226
34494
|
catch: (cause3) => new GitError({ op, message: `git ${args2.join(" ")} threw`, cause: cause3 })
|
|
34227
34495
|
}), isGitRepo2 = (dir) => exports_Effect.gen(function* () {
|
|
34228
|
-
if (!
|
|
34496
|
+
if (!existsSync10(dir))
|
|
34229
34497
|
return false;
|
|
34230
34498
|
const r = yield* git(dir, ["rev-parse", "--is-inside-work-tree"], "rev-parse");
|
|
34231
34499
|
return r.code === 0 && r.stdout === "true";
|
|
@@ -34237,14 +34505,14 @@ var init_git_enforce = __esm(() => {
|
|
|
34237
34505
|
|
|
34238
34506
|
// src/_impl/outbox.ts
|
|
34239
34507
|
import { Database as Database2 } from "bun:sqlite";
|
|
34240
|
-
import { existsSync as
|
|
34241
|
-
import { homedir } from "os";
|
|
34242
|
-
import { join as
|
|
34508
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync8 } from "fs";
|
|
34509
|
+
import { homedir as homedir2 } from "os";
|
|
34510
|
+
import { join as join11 } from "path";
|
|
34243
34511
|
function ensureDb() {
|
|
34244
34512
|
if (db)
|
|
34245
34513
|
return db;
|
|
34246
|
-
if (!
|
|
34247
|
-
|
|
34514
|
+
if (!existsSync11(MULTI_DIR3))
|
|
34515
|
+
mkdirSync8(MULTI_DIR3, { recursive: true });
|
|
34248
34516
|
db = new Database2(TASKS_DB_PATH2);
|
|
34249
34517
|
db.exec(`
|
|
34250
34518
|
CREATE TABLE IF NOT EXISTS stream_outbox (
|
|
@@ -34377,17 +34645,17 @@ function startOutboxFlusher(apiUrl, wsId) {
|
|
|
34377
34645
|
var MULTI_DIR3, TASKS_DB_PATH2, BATCH_SIZE = 100, FLUSH_INTERVAL_MS = 2000, MAX_BACKOFF_MS = 30000, db = null;
|
|
34378
34646
|
var init_outbox = __esm(() => {
|
|
34379
34647
|
init_client();
|
|
34380
|
-
MULTI_DIR3 =
|
|
34381
|
-
TASKS_DB_PATH2 =
|
|
34648
|
+
MULTI_DIR3 = join11(homedir2(), ".multi");
|
|
34649
|
+
TASKS_DB_PATH2 = join11(MULTI_DIR3, "tasks.db");
|
|
34382
34650
|
});
|
|
34383
34651
|
|
|
34384
34652
|
// src/_impl/run-task.ts
|
|
34385
|
-
import { mkdirSync as
|
|
34386
|
-
import { join as
|
|
34653
|
+
import { mkdirSync as mkdirSync9, existsSync as existsSync12, writeFileSync as writeFileSync7, readFileSync as readFileSync10, appendFileSync as appendFileSync4, unlinkSync as unlinkSync6, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
|
|
34654
|
+
import { join as join12, dirname as dirname9 } from "path";
|
|
34387
34655
|
function ensureDirs() {
|
|
34388
|
-
for (const d of [MULTI_DIR4,
|
|
34389
|
-
if (!
|
|
34390
|
-
|
|
34656
|
+
for (const d of [MULTI_DIR4, join12(MULTI_DIR4, "logs"), SKILLS_DIR2]) {
|
|
34657
|
+
if (!existsSync12(d))
|
|
34658
|
+
mkdirSync9(d, { recursive: true });
|
|
34391
34659
|
}
|
|
34392
34660
|
}
|
|
34393
34661
|
function log3(msg) {
|
|
@@ -34431,10 +34699,42 @@ function resolveEnforcementMode(task) {
|
|
|
34431
34699
|
return m;
|
|
34432
34700
|
return "enforce";
|
|
34433
34701
|
}
|
|
34702
|
+
function buildMemorySummaryPrompt(task) {
|
|
34703
|
+
const key = task?.key || task?.issue_id || "task";
|
|
34704
|
+
const title = task?.title || "";
|
|
34705
|
+
return [
|
|
34706
|
+
`The task "${key}: ${title}" is now complete.`,
|
|
34707
|
+
`Provide a memory summary for future agents working on this project.`,
|
|
34708
|
+
`Output EXACTLY 5 short bullets (one line each, prefix "- "):`,
|
|
34709
|
+
`1. What changed (the actual outcome)`,
|
|
34710
|
+
`2. Why (motivation, constraints)`,
|
|
34711
|
+
`3. Key decisions / tradeoffs`,
|
|
34712
|
+
`4. Files or modules touched`,
|
|
34713
|
+
`5. Open follow-ups, if any`,
|
|
34714
|
+
``,
|
|
34715
|
+
`Rules: do NOT call any tools. Reply with ONLY the 5 bullets, no preamble, no closing.`
|
|
34716
|
+
].join(`
|
|
34717
|
+
`);
|
|
34718
|
+
}
|
|
34719
|
+
async function writeTaskMemory(apiUrl, task, text) {
|
|
34720
|
+
if (!task?.project_id || !text)
|
|
34721
|
+
return;
|
|
34722
|
+
try {
|
|
34723
|
+
await apiClient.post(`${apiUrl}/api/memory/write`, {
|
|
34724
|
+
project_id: task.project_id,
|
|
34725
|
+
source_kind: "task",
|
|
34726
|
+
source_id: String(task.issue_id || task.id || ""),
|
|
34727
|
+
agent_id: task.agent_id ?? null,
|
|
34728
|
+
text: text.slice(0, 19500)
|
|
34729
|
+
});
|
|
34730
|
+
} catch (e) {
|
|
34731
|
+
log3(`memory write failed: ${String(e)}`);
|
|
34732
|
+
}
|
|
34733
|
+
}
|
|
34434
34734
|
async function handleRunTask(apiUrl, deviceId, task, detected, ctx) {
|
|
34435
34735
|
const issueId = task.issue_id;
|
|
34436
34736
|
const isFollowup = !!task.followup;
|
|
34437
|
-
const baseWorkingDir = task.working_dir &&
|
|
34737
|
+
const baseWorkingDir = task.working_dir && existsSync12(task.working_dir) ? task.working_dir : undefined;
|
|
34438
34738
|
const tenantWsId = task.tenant_workspace_id ?? null;
|
|
34439
34739
|
const projectId = task.project_id ?? null;
|
|
34440
34740
|
const ISSUE_BASE = tenantWsId && projectId ? `${apiUrl}/api/workspaces/${tenantWsId}/projects/${projectId}/issues/${issueId}` : null;
|
|
@@ -34501,18 +34801,19 @@ async function handleRunTask(apiUrl, deviceId, task, detected, ctx) {
|
|
|
34501
34801
|
await postStream(apiUrl, issueId, "progress", { message: `Device ${deviceId} picked up ${isFollowup ? "follow-up" : "task"}` });
|
|
34502
34802
|
let attachmentRefs = [];
|
|
34503
34803
|
if (task.from_comment_id && task.tenant_workspace_id && task.project_id) {
|
|
34504
|
-
const baseDir = workingDir ||
|
|
34505
|
-
const inDir =
|
|
34804
|
+
const baseDir = workingDir || join12(MULTI_DIR4, "tmp", issueId);
|
|
34805
|
+
const inDir = join12(baseDir, ".multi-in", task.from_comment_id);
|
|
34506
34806
|
attachmentRefs = await downloadCommentAttachments(apiUrl, task.tenant_workspace_id, task.project_id, issueId, task.from_comment_id, inDir);
|
|
34507
34807
|
if (attachmentRefs.length)
|
|
34508
34808
|
log3(` fetched ${attachmentRefs.length} attachment(s) → ${inDir}`);
|
|
34509
34809
|
}
|
|
34510
|
-
const outDir =
|
|
34810
|
+
const outDir = join12(workingDir || join12(MULTI_DIR4, "tmp", issueId), ".multi-out");
|
|
34511
34811
|
let liveCommentId;
|
|
34512
34812
|
let liveBody = "";
|
|
34513
34813
|
let hadError = false;
|
|
34514
34814
|
let hasAssistantText = false;
|
|
34515
34815
|
let liveCommentPromise = null;
|
|
34816
|
+
let memorySummary = null;
|
|
34516
34817
|
const ensureLiveComment = () => {
|
|
34517
34818
|
if (liveCommentId)
|
|
34518
34819
|
return Promise.resolve();
|
|
@@ -34821,7 +35122,7 @@ ${userPart}` : userPart;
|
|
|
34821
35122
|
throw new Error(`ACP adapter for ${chosen.type} not found`);
|
|
34822
35123
|
log3(` adapter: ${chosen.type} → ${adapterBin.join(" ")}`);
|
|
34823
35124
|
const startedAt = Date.now();
|
|
34824
|
-
const { sessionId } = await runAcp({
|
|
35125
|
+
const { sessionId, summaryText: acpSummary } = await runAcp({
|
|
34825
35126
|
apiUrl,
|
|
34826
35127
|
issueId,
|
|
34827
35128
|
deviceId,
|
|
@@ -34833,6 +35134,7 @@ ${userPart}` : userPart;
|
|
|
34833
35134
|
adapterBin,
|
|
34834
35135
|
autonomy: task.autonomy_level,
|
|
34835
35136
|
cwd: workingDir,
|
|
35137
|
+
summaryPrompt: buildMemorySummaryPrompt(task),
|
|
34836
35138
|
onEvent: eventHandler,
|
|
34837
35139
|
onSession: async (sid) => {
|
|
34838
35140
|
try {
|
|
@@ -34857,6 +35159,7 @@ ${userPart}` : userPart;
|
|
|
34857
35159
|
});
|
|
34858
35160
|
postStream(apiUrl, issueId, "run_finished", { stopReason: typeof sessionId === "string" ? "ok" : "unknown", duration_ms: Date.now() - startedAt });
|
|
34859
35161
|
log3(` acp session ${sessionId.slice(0, 8)}`);
|
|
35162
|
+
memorySummary = acpSummary || null;
|
|
34860
35163
|
} else if (useAcpx) {
|
|
34861
35164
|
let preamble = "";
|
|
34862
35165
|
try {
|
|
@@ -34926,11 +35229,12 @@ Write generated files to: ${outDir}`;
|
|
|
34926
35229
|
${userPart}` : userPart;
|
|
34927
35230
|
log3(` acpx runner: ${preferType}`);
|
|
34928
35231
|
const acpxStartedAt = Date.now();
|
|
34929
|
-
await runAcpx({
|
|
35232
|
+
const { summaryText: acpxSummary } = await runAcpx({
|
|
34930
35233
|
agentType: preferType,
|
|
34931
35234
|
prompt: full,
|
|
34932
35235
|
cwd: workingDir,
|
|
34933
35236
|
sessionName: `issue-${issueId}`,
|
|
35237
|
+
summaryPrompt: buildMemorySummaryPrompt(task),
|
|
34934
35238
|
onEvent: eventHandler,
|
|
34935
35239
|
onSpawn: (child) => {
|
|
34936
35240
|
if (ctx?.runEntry) {
|
|
@@ -34941,6 +35245,7 @@ ${userPart}` : userPart;
|
|
|
34941
35245
|
}
|
|
34942
35246
|
});
|
|
34943
35247
|
postStream(apiUrl, issueId, "run_finished", { stopReason: "ok", duration_ms: Date.now() - acpxStartedAt });
|
|
35248
|
+
memorySummary = acpxSummary || null;
|
|
34944
35249
|
} else {
|
|
34945
35250
|
const runner = pickRunner(detected, preferType);
|
|
34946
35251
|
for await (const event of runner(task))
|
|
@@ -34984,6 +35289,10 @@ ${userPart}` : userPart;
|
|
|
34984
35289
|
if (ISSUE_BASE)
|
|
34985
35290
|
await apiClient.post(`${ISSUE_BASE}/complete`, {});
|
|
34986
35291
|
log3(` ✓ ${task.key} complete`);
|
|
35292
|
+
if (memorySummary) {
|
|
35293
|
+
await writeTaskMemory(apiUrl, task, memorySummary);
|
|
35294
|
+
log3(` \uD83D\uDCDD memory summary written (${memorySummary.length} chars)`);
|
|
35295
|
+
}
|
|
34987
35296
|
if (baseWorkingDir) {
|
|
34988
35297
|
const mergeTarget = task.merge_to_main === true || task.merge_to_main === "true" ? "main" : typeof task.merge_target === "string" ? task.merge_target : null;
|
|
34989
35298
|
await exports_Effect.runPromise(terminalGitFlowE(apiUrl, issueId, baseWorkingDir, task.key || issueId, resolveEnforcementMode(task), mergeTarget));
|
|
@@ -35008,7 +35317,12 @@ async function buildPlanningPreamble(apiUrl, task, _wsId) {
|
|
|
35008
35317
|
if (depth >= PLANNING_DEPTH_LIMIT) {
|
|
35009
35318
|
return `# Planning (sub-task context)
|
|
35010
35319
|
|
|
35011
|
-
You are acting on a sub-issue spawned by another agent. You MAY emit a \`multi-plan\` block to update your own issue's status (e.g. mark done/failed) but CANNOT create child issues or delegate further.
|
|
35320
|
+
You are acting on a sub-issue spawned by another agent. You MAY emit a \`multi-plan\` block to update your own issue's status (e.g. mark done/blocked/failed) but CANNOT create child issues or delegate further.
|
|
35321
|
+
|
|
35322
|
+
Status guidance:
|
|
35323
|
+
- \`done\` — work is finished; no further human or agent action needed.
|
|
35324
|
+
- \`blocked\` — you need a human decision before continuing (confirmation, choice between approaches, missing info). Do NOT mark \`done\` when waiting on review.
|
|
35325
|
+
- \`failed\` — work could not be completed; explain in your reply.
|
|
35012
35326
|
|
|
35013
35327
|
\`\`\`multi-plan
|
|
35014
35328
|
{"actions":[{"type":"update","id":"<this issue id>","status":"done"}]}
|
|
@@ -35056,6 +35370,8 @@ Issue actions:
|
|
|
35056
35370
|
]}
|
|
35057
35371
|
\`\`\`
|
|
35058
35372
|
|
|
35373
|
+
Status values: \`todo\` | \`in_progress\` | \`blocked\` | \`done\` | \`archived\` | \`failed\`. **Use \`blocked\` (NOT \`done\`) when you are pausing to wait on a human decision** — confirmation, a choice between approaches, or missing context. Marking such an issue \`done\` is wrong: the work isn't finished, you're waiting on review. Use \`archived\` to hide a completed/abandoned issue from default views.
|
|
35374
|
+
|
|
35059
35375
|
Prefer the bulk \`issue.delete_where\` over \`issue.list\` + per-issue \`issue.delete\` when the user's intent matches a filter ("delete all todo issues", "remove anything assigned to agent X"). It runs in one turn instead of two.
|
|
35060
35376
|
|
|
35061
35377
|
Read actions (\`issue.list\`, \`issue.search\`) return the matched rows in the action summary comment. Use them to look up issue ids/keys before \`update\` / \`delegate\` / \`issue.delete\` ONLY when the per-row filter isn't enough (e.g. you need to inspect titles before acting).
|
|
@@ -35174,7 +35490,7 @@ async function executePlanActions(apiUrl, parentTask, actions, ctx, parseErrors
|
|
|
35174
35490
|
}
|
|
35175
35491
|
lines.push(`- [ok] updated ${res.data.key}`);
|
|
35176
35492
|
results.push({ type: "update", status: "ok", issue_id: res.data.id, key: res.data.key, status_to: a.status ?? null, title_to: a.title ?? null });
|
|
35177
|
-
if ((a.status === "done" || a.status === "cancelled") && parentTask.working_dir &&
|
|
35493
|
+
if ((a.status === "done" || a.status === "cancelled") && parentTask.working_dir && existsSync12(parentTask.working_dir)) {
|
|
35178
35494
|
const targetKey = res.data?.key;
|
|
35179
35495
|
if (targetKey) {
|
|
35180
35496
|
const targetIssueId = res.data?.id || a.id;
|
|
@@ -35455,26 +35771,26 @@ function statusIcon(status3) {
|
|
|
35455
35771
|
}
|
|
35456
35772
|
}
|
|
35457
35773
|
async function resolveAcpAdapter(agentType, detectedPath) {
|
|
35458
|
-
if (agentType === "pi" && detectedPath &&
|
|
35774
|
+
if (agentType === "pi" && detectedPath && existsSync12(detectedPath)) {
|
|
35459
35775
|
return [detectedPath, "--mode", "rpc"];
|
|
35460
35776
|
}
|
|
35461
35777
|
const override = process.env.MULTI_ACP_ADAPTER?.trim();
|
|
35462
35778
|
const adapterNames = override ? [override] : ["claude-code-acp", "claude-agent-acp"];
|
|
35463
35779
|
for (const name of adapterNames) {
|
|
35464
|
-
if (name.startsWith("/") &&
|
|
35780
|
+
if (name.startsWith("/") && existsSync12(name))
|
|
35465
35781
|
return [name];
|
|
35466
35782
|
try {
|
|
35467
35783
|
const here = new URL(import.meta.url).pathname;
|
|
35468
35784
|
let dir = here;
|
|
35469
35785
|
for (let i = 0;i < 8; i++) {
|
|
35470
35786
|
dir = dirname9(dir);
|
|
35471
|
-
const bin =
|
|
35472
|
-
if (
|
|
35787
|
+
const bin = join12(dir, "node_modules", ".bin", name);
|
|
35788
|
+
if (existsSync12(bin))
|
|
35473
35789
|
return [bin];
|
|
35474
35790
|
}
|
|
35475
35791
|
} catch {}
|
|
35476
|
-
const global =
|
|
35477
|
-
if (
|
|
35792
|
+
const global = join12(HOME4, ".bun", "install", "global", "node_modules", ".bin", name);
|
|
35793
|
+
if (existsSync12(global))
|
|
35478
35794
|
return [global];
|
|
35479
35795
|
}
|
|
35480
35796
|
return null;
|
|
@@ -35483,7 +35799,7 @@ async function postStream(_apiUrl, issueId, event_type, payload) {
|
|
|
35483
35799
|
try {
|
|
35484
35800
|
ensureDirs();
|
|
35485
35801
|
const date6 = new Date().toISOString().slice(0, 10);
|
|
35486
|
-
const path =
|
|
35802
|
+
const path = join12(MULTI_DIR4, "logs", `events-${date6}.ndjson`);
|
|
35487
35803
|
appendFileSync4(path, JSON.stringify({ ts: Date.now(), issue_id: issueId, event_type, payload }) + `
|
|
35488
35804
|
`);
|
|
35489
35805
|
} catch {}
|
|
@@ -35502,7 +35818,7 @@ async function downloadCommentAttachments(apiUrl, wsId, projectId, issueId, comm
|
|
|
35502
35818
|
const items = list.data?.results || list.data || [];
|
|
35503
35819
|
if (!Array.isArray(items) || items.length === 0)
|
|
35504
35820
|
return [];
|
|
35505
|
-
|
|
35821
|
+
mkdirSync9(destDir, { recursive: true });
|
|
35506
35822
|
const token = authTokenHeader();
|
|
35507
35823
|
const out = [];
|
|
35508
35824
|
for (const it of items) {
|
|
@@ -35511,8 +35827,8 @@ async function downloadCommentAttachments(apiUrl, wsId, projectId, issueId, comm
|
|
|
35511
35827
|
continue;
|
|
35512
35828
|
const buf = new Uint8Array(await res.arrayBuffer());
|
|
35513
35829
|
const safe = it.filename.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
35514
|
-
const p =
|
|
35515
|
-
|
|
35830
|
+
const p = join12(destDir, safe);
|
|
35831
|
+
writeFileSync7(p, buf);
|
|
35516
35832
|
out.push({ filename: it.filename, path: p });
|
|
35517
35833
|
}
|
|
35518
35834
|
return out;
|
|
@@ -35522,9 +35838,9 @@ async function downloadCommentAttachments(apiUrl, wsId, projectId, issueId, comm
|
|
|
35522
35838
|
}
|
|
35523
35839
|
function authTokenHeader() {
|
|
35524
35840
|
try {
|
|
35525
|
-
if (!
|
|
35841
|
+
if (!existsSync12(CONFIG_PATH2))
|
|
35526
35842
|
return null;
|
|
35527
|
-
const raw = JSON.parse(
|
|
35843
|
+
const raw = JSON.parse(readFileSync10(CONFIG_PATH2, "utf8"));
|
|
35528
35844
|
const token = raw.token ?? raw.authToken;
|
|
35529
35845
|
return token ? `Bearer ${token}` : null;
|
|
35530
35846
|
} catch {
|
|
@@ -35532,14 +35848,14 @@ function authTokenHeader() {
|
|
|
35532
35848
|
}
|
|
35533
35849
|
}
|
|
35534
35850
|
async function uploadOutputDir(apiUrl, wsId, projectId, issueId, commentId, dir) {
|
|
35535
|
-
if (!
|
|
35851
|
+
if (!existsSync12(dir))
|
|
35536
35852
|
return 0;
|
|
35537
35853
|
const files = [];
|
|
35538
35854
|
const walk = (d, depth = 0) => {
|
|
35539
35855
|
if (depth > 3)
|
|
35540
35856
|
return;
|
|
35541
|
-
for (const name of
|
|
35542
|
-
const p =
|
|
35857
|
+
for (const name of readdirSync3(d)) {
|
|
35858
|
+
const p = join12(d, name);
|
|
35543
35859
|
try {
|
|
35544
35860
|
const st = statSync2(p);
|
|
35545
35861
|
if (st.isDirectory())
|
|
@@ -35557,7 +35873,7 @@ async function uploadOutputDir(apiUrl, wsId, projectId, issueId, commentId, dir)
|
|
|
35557
35873
|
let uploaded = 0;
|
|
35558
35874
|
for (const f of files) {
|
|
35559
35875
|
try {
|
|
35560
|
-
const data =
|
|
35876
|
+
const data = readFileSync10(f);
|
|
35561
35877
|
const form = new FormData;
|
|
35562
35878
|
const blob = new Blob([data]);
|
|
35563
35879
|
form.append("file", blob, f.split("/").pop() || "file");
|
|
@@ -35572,7 +35888,7 @@ async function uploadOutputDir(apiUrl, wsId, projectId, issueId, commentId, dir)
|
|
|
35572
35888
|
if (res.ok) {
|
|
35573
35889
|
uploaded++;
|
|
35574
35890
|
try {
|
|
35575
|
-
|
|
35891
|
+
unlinkSync6(f);
|
|
35576
35892
|
} catch {}
|
|
35577
35893
|
}
|
|
35578
35894
|
} catch (e) {
|
|
@@ -35848,10 +36164,10 @@ var init_run_task = __esm(() => {
|
|
|
35848
36164
|
init_git_enforce();
|
|
35849
36165
|
init_outbox();
|
|
35850
36166
|
HOME4 = process.env.HOME || process.env.USERPROFILE || ".";
|
|
35851
|
-
MULTI_DIR4 =
|
|
35852
|
-
CONFIG_PATH2 =
|
|
35853
|
-
LOG_PATH3 =
|
|
35854
|
-
SKILLS_DIR2 =
|
|
36167
|
+
MULTI_DIR4 = join12(HOME4, ".multi");
|
|
36168
|
+
CONFIG_PATH2 = join12(MULTI_DIR4, "config.json");
|
|
36169
|
+
LOG_PATH3 = join12(MULTI_DIR4, "logs", "agent.log");
|
|
36170
|
+
SKILLS_DIR2 = join12(MULTI_DIR4, "skills");
|
|
35855
36171
|
});
|
|
35856
36172
|
|
|
35857
36173
|
// ../lib/chat-doc.ts
|
|
@@ -35897,12 +36213,12 @@ function patchMessage(doc2, mapId, patch9) {
|
|
|
35897
36213
|
map20.set(k, v);
|
|
35898
36214
|
}
|
|
35899
36215
|
}
|
|
35900
|
-
function appendText(doc2, mapId,
|
|
36216
|
+
function appendText(doc2, mapId, chunk3) {
|
|
35901
36217
|
const map20 = doc2.getContainerById(mapId);
|
|
35902
36218
|
if (!map20)
|
|
35903
36219
|
return;
|
|
35904
36220
|
const cur = map20.get("text") ?? "";
|
|
35905
|
-
map20.set("text", cur +
|
|
36221
|
+
map20.set("text", cur + chunk3);
|
|
35906
36222
|
}
|
|
35907
36223
|
function finalizeMessage(doc2, mapId) {
|
|
35908
36224
|
const map20 = doc2.getContainerById(mapId);
|
|
@@ -35943,8 +36259,8 @@ var init_chat_doc = __esm(() => {
|
|
|
35943
36259
|
});
|
|
35944
36260
|
|
|
35945
36261
|
// src/_impl/chat-peer.ts
|
|
35946
|
-
import { existsSync as
|
|
35947
|
-
import { dirname as dirname10, join as
|
|
36262
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync10, readFileSync as readFileSync11, writeFileSync as writeFileSync8 } from "fs";
|
|
36263
|
+
import { dirname as dirname10, join as join13 } from "path";
|
|
35948
36264
|
import { LoroDoc as LoroDoc2 } from "loro-crdt";
|
|
35949
36265
|
|
|
35950
36266
|
class ChatPeer {
|
|
@@ -35961,24 +36277,24 @@ class ChatPeer {
|
|
|
35961
36277
|
constructor(opts) {
|
|
35962
36278
|
this.opts = opts;
|
|
35963
36279
|
this.chatId = opts.chatId;
|
|
35964
|
-
this.snapshotPath =
|
|
36280
|
+
this.snapshotPath = join13(MULTI_DIR, "chats", `${opts.chatId}.loro`);
|
|
35965
36281
|
this.doc = this.loadFromDisk();
|
|
35966
36282
|
for (const m of listMessages(this.doc))
|
|
35967
36283
|
this.seenIds.add(m.id);
|
|
35968
36284
|
}
|
|
35969
36285
|
loadFromDisk() {
|
|
35970
36286
|
try {
|
|
35971
|
-
if (
|
|
35972
|
-
const bytes =
|
|
36287
|
+
if (existsSync13(this.snapshotPath)) {
|
|
36288
|
+
const bytes = readFileSync11(this.snapshotPath);
|
|
35973
36289
|
return importSnapshot(new Uint8Array(bytes));
|
|
35974
36290
|
}
|
|
35975
36291
|
} catch {}
|
|
35976
36292
|
return new LoroDoc2;
|
|
35977
36293
|
}
|
|
35978
36294
|
persist() {
|
|
35979
|
-
if (!
|
|
35980
|
-
|
|
35981
|
-
|
|
36295
|
+
if (!existsSync13(dirname10(this.snapshotPath)))
|
|
36296
|
+
mkdirSync10(dirname10(this.snapshotPath), { recursive: true });
|
|
36297
|
+
writeFileSync8(this.snapshotPath, exportSnapshot(this.doc));
|
|
35982
36298
|
this.dirtySinceWrite = 0;
|
|
35983
36299
|
}
|
|
35984
36300
|
appendAndPush(msg) {
|
|
@@ -36013,8 +36329,8 @@ class ChatPeer {
|
|
|
36013
36329
|
this.flush();
|
|
36014
36330
|
return { msgId: msg.id, containerId };
|
|
36015
36331
|
}
|
|
36016
|
-
appendPartialText(containerId,
|
|
36017
|
-
appendText(this.doc, containerId,
|
|
36332
|
+
appendPartialText(containerId, chunk3) {
|
|
36333
|
+
appendText(this.doc, containerId, chunk3);
|
|
36018
36334
|
this.flush();
|
|
36019
36335
|
}
|
|
36020
36336
|
finalizePartialMessage(containerId) {
|
|
@@ -37024,6 +37340,7 @@ Action vocabulary:
|
|
|
37024
37340
|
|
|
37025
37341
|
Rules:
|
|
37026
37342
|
- Omit the block entirely if no actions are needed. Don't emit empty arrays.
|
|
37343
|
+
- Status values for \`update\`: \`todo\` | \`in_progress\` | \`blocked\` | \`done\` | \`archived\` | \`failed\`. **Use \`blocked\` (NOT \`done\`) when the issue is paused waiting on a human decision** — confirmation, choice, or missing context. Marking such an issue \`done\` misrepresents finished work. Use \`archived\` to hide a completed/abandoned issue from default views.
|
|
37027
37344
|
- 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.
|
|
37028
37345
|
- 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.
|
|
37029
37346
|
- 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.
|
|
@@ -37798,7 +38115,7 @@ import { parseArgs } from "util";
|
|
|
37798
38115
|
// package.json
|
|
37799
38116
|
var package_default = {
|
|
37800
38117
|
name: "@shipers-dev/multi",
|
|
37801
|
-
version: "0.
|
|
38118
|
+
version: "0.51.0",
|
|
37802
38119
|
type: "module",
|
|
37803
38120
|
bin: {
|
|
37804
38121
|
"multi-agent": "./dist/index.js"
|
|
@@ -38312,7 +38629,7 @@ class Runners extends exports_Effect.Service()("cli/Runners", {
|
|
|
38312
38629
|
// src/commands/simple.ts
|
|
38313
38630
|
init_esm();
|
|
38314
38631
|
init_paths();
|
|
38315
|
-
import { existsSync as
|
|
38632
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
|
|
38316
38633
|
|
|
38317
38634
|
// src/services/Config.ts
|
|
38318
38635
|
init_esm();
|
|
@@ -38407,7 +38724,7 @@ var statusCmd = exports_Effect.fn("statusCmd")(function* () {
|
|
|
38407
38724
|
process.exit(1);
|
|
38408
38725
|
}
|
|
38409
38726
|
const d = res.data;
|
|
38410
|
-
const pid =
|
|
38727
|
+
const pid = existsSync7(PID_PATH) ? readFileSync7(PID_PATH, "utf8").trim() : null;
|
|
38411
38728
|
const daemon = pid && isRunning3(Number(pid)) ? `running (pid ${pid})` : "stopped";
|
|
38412
38729
|
console.log(`
|
|
38413
38730
|
Device Status
|
|
@@ -38422,11 +38739,11 @@ Daemon: ${daemon}
|
|
|
38422
38739
|
});
|
|
38423
38740
|
var stopCmd = exports_Effect.fn("stopCmd")(function* () {
|
|
38424
38741
|
const fs3 = yield* FileSystem;
|
|
38425
|
-
if (!
|
|
38742
|
+
if (!existsSync7(PID_PATH)) {
|
|
38426
38743
|
console.log("No daemon running.");
|
|
38427
38744
|
return;
|
|
38428
38745
|
}
|
|
38429
|
-
const pid = Number(
|
|
38746
|
+
const pid = Number(readFileSync7(PID_PATH, "utf8").trim());
|
|
38430
38747
|
if (!pid || !isRunning3(pid)) {
|
|
38431
38748
|
yield* fs3.remove(PID_PATH);
|
|
38432
38749
|
console.log("Cleaned stale pidfile.");
|
|
@@ -38439,11 +38756,11 @@ var stopCmd = exports_Effect.fn("stopCmd")(function* () {
|
|
|
38439
38756
|
} catch {}
|
|
38440
38757
|
});
|
|
38441
38758
|
var logsCmd = exports_Effect.fn("logsCmd")(function* () {
|
|
38442
|
-
if (!
|
|
38759
|
+
if (!existsSync7(LOG_PATH)) {
|
|
38443
38760
|
console.log("No logs yet.");
|
|
38444
38761
|
return;
|
|
38445
38762
|
}
|
|
38446
|
-
const content =
|
|
38763
|
+
const content = readFileSync7(LOG_PATH, "utf8");
|
|
38447
38764
|
console.log(content.split(`
|
|
38448
38765
|
`).slice(-100).join(`
|
|
38449
38766
|
`));
|
|
@@ -38452,10 +38769,10 @@ var resetCmd = exports_Effect.fn("resetCmd")(function* (issueId) {
|
|
|
38452
38769
|
if (!issueId) {
|
|
38453
38770
|
return yield* exports_Effect.fail(new UsageError({ message: "reset requires --issue <id>" }));
|
|
38454
38771
|
}
|
|
38455
|
-
if (!
|
|
38772
|
+
if (!existsSync7(PORT_PATH)) {
|
|
38456
38773
|
return yield* exports_Effect.fail(new DaemonError({ message: "Daemon not running (no port file)." }));
|
|
38457
38774
|
}
|
|
38458
|
-
const port = Number(
|
|
38775
|
+
const port = Number(readFileSync7(PORT_PATH, "utf8").trim());
|
|
38459
38776
|
const config2 = yield* Config4;
|
|
38460
38777
|
const cfg = yield* config2.load;
|
|
38461
38778
|
if (!cfg.dispatchSecret) {
|
|
@@ -38616,10 +38933,10 @@ var linkCmd = exports_Effect.fn("linkCmd")(function* (apiUrl, agentId) {
|
|
|
38616
38933
|
// src/commands/restart.ts
|
|
38617
38934
|
init_esm();
|
|
38618
38935
|
init_paths();
|
|
38619
|
-
import { existsSync as
|
|
38936
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9, unlinkSync as unlinkSync5 } from "fs";
|
|
38620
38937
|
|
|
38621
38938
|
// src/_impl/pid-lock.ts
|
|
38622
|
-
import { closeSync, existsSync as
|
|
38939
|
+
import { closeSync, existsSync as existsSync8, mkdirSync as mkdirSync7, openSync, readFileSync as readFileSync8, unlinkSync as unlinkSync4, writeSync } from "fs";
|
|
38623
38940
|
import { dirname as dirname8 } from "path";
|
|
38624
38941
|
var isAlive = (pid) => {
|
|
38625
38942
|
if (!Number.isFinite(pid) || pid <= 0)
|
|
@@ -38632,7 +38949,7 @@ var isAlive = (pid) => {
|
|
|
38632
38949
|
}
|
|
38633
38950
|
};
|
|
38634
38951
|
var writeExclusive = (path, pid) => {
|
|
38635
|
-
|
|
38952
|
+
mkdirSync7(dirname8(path), { recursive: true });
|
|
38636
38953
|
const fd = openSync(path, "wx");
|
|
38637
38954
|
try {
|
|
38638
38955
|
writeSync(fd, String(pid));
|
|
@@ -38651,7 +38968,7 @@ var acquirePidLock = (path) => {
|
|
|
38651
38968
|
}
|
|
38652
38969
|
const raw = (() => {
|
|
38653
38970
|
try {
|
|
38654
|
-
return
|
|
38971
|
+
return readFileSync8(path, "utf8").trim();
|
|
38655
38972
|
} catch {
|
|
38656
38973
|
return "";
|
|
38657
38974
|
}
|
|
@@ -38663,7 +38980,7 @@ var acquirePidLock = (path) => {
|
|
|
38663
38980
|
return { ok: false, existingPid };
|
|
38664
38981
|
}
|
|
38665
38982
|
try {
|
|
38666
|
-
|
|
38983
|
+
unlinkSync4(path);
|
|
38667
38984
|
} catch {}
|
|
38668
38985
|
try {
|
|
38669
38986
|
writeExclusive(path, ourPid);
|
|
@@ -38673,7 +38990,7 @@ var acquirePidLock = (path) => {
|
|
|
38673
38990
|
throw e;
|
|
38674
38991
|
const racingRaw = (() => {
|
|
38675
38992
|
try {
|
|
38676
|
-
return
|
|
38993
|
+
return readFileSync8(path, "utf8").trim();
|
|
38677
38994
|
} catch {
|
|
38678
38995
|
return "";
|
|
38679
38996
|
}
|
|
@@ -38684,12 +39001,12 @@ var acquirePidLock = (path) => {
|
|
|
38684
39001
|
};
|
|
38685
39002
|
var releasePidLock = (path) => {
|
|
38686
39003
|
try {
|
|
38687
|
-
if (!
|
|
39004
|
+
if (!existsSync8(path))
|
|
38688
39005
|
return;
|
|
38689
|
-
const raw =
|
|
39006
|
+
const raw = readFileSync8(path, "utf8").trim();
|
|
38690
39007
|
if (Number(raw) !== process.pid)
|
|
38691
39008
|
return;
|
|
38692
|
-
|
|
39009
|
+
unlinkSync4(path);
|
|
38693
39010
|
} catch {}
|
|
38694
39011
|
};
|
|
38695
39012
|
var killStaleConnects = () => {
|
|
@@ -38754,8 +39071,8 @@ var isRunning4 = (pid) => {
|
|
|
38754
39071
|
var restartCmd = exports_Effect.fn("restartCmd")(function* (apiUrl) {
|
|
38755
39072
|
const fs3 = yield* FileSystem;
|
|
38756
39073
|
yield* (yield* Config4).ensureDirs;
|
|
38757
|
-
if (
|
|
38758
|
-
const pid = Number(
|
|
39074
|
+
if (existsSync9(PID_PATH)) {
|
|
39075
|
+
const pid = Number(readFileSync9(PID_PATH, "utf8").trim());
|
|
38759
39076
|
if (pid && isRunning4(pid)) {
|
|
38760
39077
|
yield* fs3.writeText(STOP_PATH, "1");
|
|
38761
39078
|
try {
|
|
@@ -38774,13 +39091,13 @@ var restartCmd = exports_Effect.fn("restartCmd")(function* (apiUrl) {
|
|
|
38774
39091
|
}
|
|
38775
39092
|
}
|
|
38776
39093
|
try {
|
|
38777
|
-
if (
|
|
38778
|
-
|
|
39094
|
+
if (existsSync9(PID_PATH))
|
|
39095
|
+
unlinkSync5(PID_PATH);
|
|
38779
39096
|
} catch {}
|
|
38780
39097
|
}
|
|
38781
39098
|
try {
|
|
38782
|
-
if (
|
|
38783
|
-
|
|
39099
|
+
if (existsSync9(STOP_PATH))
|
|
39100
|
+
unlinkSync5(STOP_PATH);
|
|
38784
39101
|
} catch {}
|
|
38785
39102
|
const killed = killStaleConnects();
|
|
38786
39103
|
if (killed.length)
|
|
@@ -38876,21 +39193,22 @@ init_client();
|
|
|
38876
39193
|
init_detect();
|
|
38877
39194
|
init_run_task();
|
|
38878
39195
|
import { Database as Database3 } from "bun:sqlite";
|
|
38879
|
-
import { existsSync as
|
|
38880
|
-
import { homedir as
|
|
38881
|
-
import { join as
|
|
39196
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync11, writeFileSync as writeFileSync9, unlinkSync as unlinkSync7 } from "fs";
|
|
39197
|
+
import { homedir as homedir3 } from "os";
|
|
39198
|
+
import { join as join14 } from "path";
|
|
39199
|
+
init_adapter_pidfile();
|
|
38882
39200
|
init_errors();
|
|
38883
|
-
var MULTI_DIR5 =
|
|
38884
|
-
var PID_PATH2 =
|
|
38885
|
-
var PORT_PATH2 =
|
|
38886
|
-
var STOP_PATH2 =
|
|
38887
|
-
var TASKS_DB_PATH3 =
|
|
39201
|
+
var MULTI_DIR5 = join14(homedir3(), ".multi");
|
|
39202
|
+
var PID_PATH2 = join14(MULTI_DIR5, "agent.pid");
|
|
39203
|
+
var PORT_PATH2 = join14(MULTI_DIR5, "agent.port");
|
|
39204
|
+
var STOP_PATH2 = join14(MULTI_DIR5, "stop.flag");
|
|
39205
|
+
var TASKS_DB_PATH3 = join14(MULTI_DIR5, "tasks.db");
|
|
38888
39206
|
function ensureDirs2() {
|
|
38889
|
-
if (!
|
|
38890
|
-
|
|
38891
|
-
const logs =
|
|
38892
|
-
if (!
|
|
38893
|
-
|
|
39207
|
+
if (!existsSync14(MULTI_DIR5))
|
|
39208
|
+
mkdirSync11(MULTI_DIR5, { recursive: true });
|
|
39209
|
+
const logs = join14(MULTI_DIR5, "logs");
|
|
39210
|
+
if (!existsSync14(logs))
|
|
39211
|
+
mkdirSync11(logs, { recursive: true });
|
|
38894
39212
|
}
|
|
38895
39213
|
function isLocalApi(url2) {
|
|
38896
39214
|
try {
|
|
@@ -39182,8 +39500,11 @@ var daemonProgram = ({ cfg, apiUrl }) => exports_Effect.gen(function* () {
|
|
|
39182
39500
|
if (cfg.authToken)
|
|
39183
39501
|
setAuthToken(cfg.authToken);
|
|
39184
39502
|
ensureDirs2();
|
|
39185
|
-
if (
|
|
39186
|
-
|
|
39503
|
+
if (existsSync14(STOP_PATH2))
|
|
39504
|
+
unlinkSync7(STOP_PATH2);
|
|
39505
|
+
const reaped = reapStaleAdapters((m) => log3(m));
|
|
39506
|
+
if (reaped > 0)
|
|
39507
|
+
log3(`reaped ${reaped} stale adapter${reaped === 1 ? "" : "s"}`);
|
|
39187
39508
|
const detected = yield* exports_Effect.promise(() => detectAgents());
|
|
39188
39509
|
const localMode = isLocalApi(apiUrl);
|
|
39189
39510
|
log3(`daemon device=${cfg.deviceId} pid=${process.pid}`);
|
|
@@ -39317,10 +39638,10 @@ var daemonProgram = ({ cfg, apiUrl }) => exports_Effect.gen(function* () {
|
|
|
39317
39638
|
});
|
|
39318
39639
|
log3(`Local server: http://127.0.0.1:${port}`);
|
|
39319
39640
|
try {
|
|
39320
|
-
|
|
39641
|
+
writeFileSync9(PORT_PATH2, String(port));
|
|
39321
39642
|
} catch {}
|
|
39322
39643
|
try {
|
|
39323
|
-
|
|
39644
|
+
writeFileSync9(PID_PATH2, String(process.pid));
|
|
39324
39645
|
} catch {}
|
|
39325
39646
|
let tunnel;
|
|
39326
39647
|
if (localMode) {
|
|
@@ -39384,7 +39705,7 @@ var daemonProgram = ({ cfg, apiUrl }) => exports_Effect.gen(function* () {
|
|
|
39384
39705
|
let probeFailures = 0;
|
|
39385
39706
|
while (true) {
|
|
39386
39707
|
yield* exports_Effect.sleep(exports_Duration.seconds(120));
|
|
39387
|
-
if (
|
|
39708
|
+
if (existsSync14(STOP_PATH2)) {
|
|
39388
39709
|
yield* exports_Deferred.succeed(stopDeferred, "stop flag");
|
|
39389
39710
|
return;
|
|
39390
39711
|
}
|
|
@@ -39409,7 +39730,7 @@ var daemonProgram = ({ cfg, apiUrl }) => exports_Effect.gen(function* () {
|
|
|
39409
39730
|
yield* exports_Effect.forkIn(daemonScope)(exports_Effect.gen(function* () {
|
|
39410
39731
|
while (true) {
|
|
39411
39732
|
yield* exports_Effect.sleep(exports_Duration.seconds(5));
|
|
39412
|
-
if (
|
|
39733
|
+
if (existsSync14(STOP_PATH2)) {
|
|
39413
39734
|
yield* exports_Deferred.succeed(stopDeferred, "stop flag");
|
|
39414
39735
|
return;
|
|
39415
39736
|
}
|
|
@@ -39449,12 +39770,12 @@ var daemonProgram = ({ cfg, apiUrl }) => exports_Effect.gen(function* () {
|
|
|
39449
39770
|
yield* exports_Effect.race(exports_Fiber.interruptAll(inFlight), exports_Effect.sleep(exports_Duration.seconds(10)));
|
|
39450
39771
|
}
|
|
39451
39772
|
yield* exports_Scope.close(daemonScope, exports_Exit.void).pipe(exports_Effect.catchAll(() => exports_Effect.void));
|
|
39452
|
-
if (
|
|
39453
|
-
|
|
39454
|
-
if (
|
|
39455
|
-
|
|
39456
|
-
if (
|
|
39457
|
-
|
|
39773
|
+
if (existsSync14(PID_PATH2))
|
|
39774
|
+
unlinkSync7(PID_PATH2);
|
|
39775
|
+
if (existsSync14(STOP_PATH2))
|
|
39776
|
+
unlinkSync7(STOP_PATH2);
|
|
39777
|
+
if (existsSync14(PORT_PATH2))
|
|
39778
|
+
unlinkSync7(PORT_PATH2);
|
|
39458
39779
|
db2.close();
|
|
39459
39780
|
log3("disconnected");
|
|
39460
39781
|
});
|