@shipers-dev/multi 0.49.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 +279 -122
- 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);
|
|
@@ -33224,7 +33355,7 @@ async function runAcp(opts) {
|
|
|
33224
33355
|
},
|
|
33225
33356
|
async readTextFile(params) {
|
|
33226
33357
|
try {
|
|
33227
|
-
const content =
|
|
33358
|
+
const content = readFileSync6(params.path, "utf8");
|
|
33228
33359
|
const sliced = typeof params.line === "number" && typeof params.limit === "number" ? content.split(`
|
|
33229
33360
|
`).slice(params.line, params.line + params.limit).join(`
|
|
33230
33361
|
`) : content;
|
|
@@ -33236,9 +33367,9 @@ async function runAcp(opts) {
|
|
|
33236
33367
|
async writeTextFile(params) {
|
|
33237
33368
|
try {
|
|
33238
33369
|
const dir = dirname6(params.path);
|
|
33239
|
-
if (!
|
|
33240
|
-
|
|
33241
|
-
|
|
33370
|
+
if (!existsSync6(dir))
|
|
33371
|
+
mkdirSync6(dir, { recursive: true });
|
|
33372
|
+
writeFileSync6(params.path, params.content, "utf8");
|
|
33242
33373
|
return {};
|
|
33243
33374
|
} catch (e) {
|
|
33244
33375
|
throw new Error(`writeTextFile failed: ${fmtErr(e)}`);
|
|
@@ -33327,6 +33458,8 @@ async function runAcp(opts) {
|
|
|
33327
33458
|
try {
|
|
33328
33459
|
child.kill();
|
|
33329
33460
|
} catch {}
|
|
33461
|
+
if (typeof child.pid === "number")
|
|
33462
|
+
removeAdapterPidfile(child.pid);
|
|
33330
33463
|
}
|
|
33331
33464
|
async function handleSessionUpdate(params, o) {
|
|
33332
33465
|
const u = params.update;
|
|
@@ -33479,11 +33612,12 @@ var init_acp_runner = __esm(() => {
|
|
|
33479
33612
|
init_acp();
|
|
33480
33613
|
init_client();
|
|
33481
33614
|
init_workspace_mutex();
|
|
33615
|
+
init_adapter_pidfile();
|
|
33482
33616
|
});
|
|
33483
33617
|
|
|
33484
33618
|
// src/_impl/acpx-runner.ts
|
|
33485
33619
|
import { appendFileSync as appendFileSync3 } from "fs";
|
|
33486
|
-
import { join as
|
|
33620
|
+
import { join as join9 } from "path";
|
|
33487
33621
|
function dlog(msg) {
|
|
33488
33622
|
try {
|
|
33489
33623
|
appendFileSync3(LOG_PATH2, `[${new Date().toISOString()}] ${msg}
|
|
@@ -33502,6 +33636,9 @@ async function spawnAcpxPrompt(agentType, cwd, sessionName, prompt, collectAssis
|
|
|
33502
33636
|
args2.push(prompt);
|
|
33503
33637
|
dlog(`[acpx] prompt: ${args2.slice(0, 10).join(" ")} ... (prompt len=${prompt.length})`);
|
|
33504
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
|
+
}
|
|
33505
33642
|
let stopReason = "end_turn";
|
|
33506
33643
|
(async () => {
|
|
33507
33644
|
try {
|
|
@@ -33570,6 +33707,8 @@ async function spawnAcpxPrompt(agentType, cwd, sessionName, prompt, collectAssis
|
|
|
33570
33707
|
}
|
|
33571
33708
|
}
|
|
33572
33709
|
const code = await proc.exited;
|
|
33710
|
+
if (typeof proc.pid === "number")
|
|
33711
|
+
removeAdapterPidfile(proc.pid);
|
|
33573
33712
|
if (code !== 0 && code !== 130) {
|
|
33574
33713
|
if (!collectAssistantText)
|
|
33575
33714
|
await forward({ event_type: "error", payload: { message: `acpx exited with code ${code}` } });
|
|
@@ -33605,6 +33744,9 @@ async function runAcpx(opts) {
|
|
|
33605
33744
|
try {
|
|
33606
33745
|
opts.onSpawn?.(proc);
|
|
33607
33746
|
} catch {}
|
|
33747
|
+
if (typeof proc.pid === "number") {
|
|
33748
|
+
writeAdapterPidfile({ pid: proc.pid, bin: "acpx", kind: "acpx", session_id: opts.sessionName ?? null });
|
|
33749
|
+
}
|
|
33608
33750
|
let stopReason = "end_turn";
|
|
33609
33751
|
(async () => {
|
|
33610
33752
|
try {
|
|
@@ -33661,6 +33803,8 @@ async function runAcpx(opts) {
|
|
|
33661
33803
|
await opts.onEvent(ev);
|
|
33662
33804
|
}
|
|
33663
33805
|
const code = await proc.exited;
|
|
33806
|
+
if (typeof proc.pid === "number")
|
|
33807
|
+
removeAdapterPidfile(proc.pid);
|
|
33664
33808
|
if (code !== 0 && code !== 130) {
|
|
33665
33809
|
await opts.onEvent({ event_type: "error", payload: { message: `acpx exited with code ${code}` } });
|
|
33666
33810
|
}
|
|
@@ -33776,8 +33920,9 @@ function extractText2(content) {
|
|
|
33776
33920
|
}
|
|
33777
33921
|
var HOME3, LOG_PATH2;
|
|
33778
33922
|
var init_acpx_runner = __esm(() => {
|
|
33923
|
+
init_adapter_pidfile();
|
|
33779
33924
|
HOME3 = process.env.HOME || process.env.USERPROFILE || ".";
|
|
33780
|
-
LOG_PATH2 =
|
|
33925
|
+
LOG_PATH2 = join9(HOME3, ".multi", "logs", "agent.log");
|
|
33781
33926
|
});
|
|
33782
33927
|
|
|
33783
33928
|
// ../../node_modules/zod/index.js
|
|
@@ -34003,12 +34148,12 @@ function parsePlanBlocks(text) {
|
|
|
34003
34148
|
}
|
|
34004
34149
|
return { actions, errors: errors3 };
|
|
34005
34150
|
}
|
|
34006
|
-
var PLAN_SCHEMA_VERSION =
|
|
34151
|
+
var PLAN_SCHEMA_VERSION = 6, Priority, AssigneeType, IssueStatus, SessionRole, SkillFile, EvalPolicy, PlanActionSchema, PlanEnvelopeSchema, UiBlockSchema, UI_FENCE_RE, FENCE_RE;
|
|
34007
34152
|
var init_plans = __esm(() => {
|
|
34008
34153
|
init_zod();
|
|
34009
34154
|
Priority = exports_external.enum(["low", "medium", "high"]);
|
|
34010
34155
|
AssigneeType = exports_external.enum(["human", "agent"]);
|
|
34011
|
-
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"]);
|
|
34012
34157
|
SessionRole = exports_external.enum(["implementer", "reviewer", "test-fixer"]);
|
|
34013
34158
|
SkillFile = exports_external.object({ path: exports_external.string().min(1), content: exports_external.string() });
|
|
34014
34159
|
EvalPolicy = exports_external.object({
|
|
@@ -34220,8 +34365,8 @@ var init_lib = __esm(() => {
|
|
|
34220
34365
|
|
|
34221
34366
|
// src/_impl/git-enforce.ts
|
|
34222
34367
|
import { spawn as spawn2 } from "node:child_process";
|
|
34223
|
-
import { existsSync as
|
|
34224
|
-
import { join as
|
|
34368
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
34369
|
+
import { join as join10 } from "node:path";
|
|
34225
34370
|
function run4(cwd, cmd, args2) {
|
|
34226
34371
|
return new Promise((resolve2) => {
|
|
34227
34372
|
const p = spawn2(cmd, args2, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -34241,7 +34386,7 @@ function normalizeKey2(issueKey) {
|
|
|
34241
34386
|
return issueKey.toLowerCase().replace(/[^a-z0-9\-_\/]/g, "-");
|
|
34242
34387
|
}
|
|
34243
34388
|
function worktreePath(baseWorkingDir, issueKey) {
|
|
34244
|
-
return
|
|
34389
|
+
return join10(baseWorkingDir, ".multi", "worktrees", normalizeKey2(issueKey));
|
|
34245
34390
|
}
|
|
34246
34391
|
function branchFor(issueKey) {
|
|
34247
34392
|
return `multi/${normalizeKey2(issueKey)}`;
|
|
@@ -34260,7 +34405,7 @@ function enforceCommitAndPush(baseWorkingDir, issueKey, mode = "enforce") {
|
|
|
34260
34405
|
};
|
|
34261
34406
|
if (mode === "off")
|
|
34262
34407
|
return result;
|
|
34263
|
-
if (!
|
|
34408
|
+
if (!existsSync10(wt))
|
|
34264
34409
|
return result;
|
|
34265
34410
|
if (!(yield* isGitRepo2(wt)))
|
|
34266
34411
|
return result;
|
|
@@ -34348,7 +34493,7 @@ var git = (cwd, args2, op) => exports_Effect.tryPromise({
|
|
|
34348
34493
|
try: () => run4(cwd, "git", args2),
|
|
34349
34494
|
catch: (cause3) => new GitError({ op, message: `git ${args2.join(" ")} threw`, cause: cause3 })
|
|
34350
34495
|
}), isGitRepo2 = (dir) => exports_Effect.gen(function* () {
|
|
34351
|
-
if (!
|
|
34496
|
+
if (!existsSync10(dir))
|
|
34352
34497
|
return false;
|
|
34353
34498
|
const r = yield* git(dir, ["rev-parse", "--is-inside-work-tree"], "rev-parse");
|
|
34354
34499
|
return r.code === 0 && r.stdout === "true";
|
|
@@ -34360,14 +34505,14 @@ var init_git_enforce = __esm(() => {
|
|
|
34360
34505
|
|
|
34361
34506
|
// src/_impl/outbox.ts
|
|
34362
34507
|
import { Database as Database2 } from "bun:sqlite";
|
|
34363
|
-
import { existsSync as
|
|
34364
|
-
import { homedir } from "os";
|
|
34365
|
-
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";
|
|
34366
34511
|
function ensureDb() {
|
|
34367
34512
|
if (db)
|
|
34368
34513
|
return db;
|
|
34369
|
-
if (!
|
|
34370
|
-
|
|
34514
|
+
if (!existsSync11(MULTI_DIR3))
|
|
34515
|
+
mkdirSync8(MULTI_DIR3, { recursive: true });
|
|
34371
34516
|
db = new Database2(TASKS_DB_PATH2);
|
|
34372
34517
|
db.exec(`
|
|
34373
34518
|
CREATE TABLE IF NOT EXISTS stream_outbox (
|
|
@@ -34500,17 +34645,17 @@ function startOutboxFlusher(apiUrl, wsId) {
|
|
|
34500
34645
|
var MULTI_DIR3, TASKS_DB_PATH2, BATCH_SIZE = 100, FLUSH_INTERVAL_MS = 2000, MAX_BACKOFF_MS = 30000, db = null;
|
|
34501
34646
|
var init_outbox = __esm(() => {
|
|
34502
34647
|
init_client();
|
|
34503
|
-
MULTI_DIR3 =
|
|
34504
|
-
TASKS_DB_PATH2 =
|
|
34648
|
+
MULTI_DIR3 = join11(homedir2(), ".multi");
|
|
34649
|
+
TASKS_DB_PATH2 = join11(MULTI_DIR3, "tasks.db");
|
|
34505
34650
|
});
|
|
34506
34651
|
|
|
34507
34652
|
// src/_impl/run-task.ts
|
|
34508
|
-
import { mkdirSync as
|
|
34509
|
-
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";
|
|
34510
34655
|
function ensureDirs() {
|
|
34511
|
-
for (const d of [MULTI_DIR4,
|
|
34512
|
-
if (!
|
|
34513
|
-
|
|
34656
|
+
for (const d of [MULTI_DIR4, join12(MULTI_DIR4, "logs"), SKILLS_DIR2]) {
|
|
34657
|
+
if (!existsSync12(d))
|
|
34658
|
+
mkdirSync9(d, { recursive: true });
|
|
34514
34659
|
}
|
|
34515
34660
|
}
|
|
34516
34661
|
function log3(msg) {
|
|
@@ -34589,7 +34734,7 @@ async function writeTaskMemory(apiUrl, task, text) {
|
|
|
34589
34734
|
async function handleRunTask(apiUrl, deviceId, task, detected, ctx) {
|
|
34590
34735
|
const issueId = task.issue_id;
|
|
34591
34736
|
const isFollowup = !!task.followup;
|
|
34592
|
-
const baseWorkingDir = task.working_dir &&
|
|
34737
|
+
const baseWorkingDir = task.working_dir && existsSync12(task.working_dir) ? task.working_dir : undefined;
|
|
34593
34738
|
const tenantWsId = task.tenant_workspace_id ?? null;
|
|
34594
34739
|
const projectId = task.project_id ?? null;
|
|
34595
34740
|
const ISSUE_BASE = tenantWsId && projectId ? `${apiUrl}/api/workspaces/${tenantWsId}/projects/${projectId}/issues/${issueId}` : null;
|
|
@@ -34656,13 +34801,13 @@ async function handleRunTask(apiUrl, deviceId, task, detected, ctx) {
|
|
|
34656
34801
|
await postStream(apiUrl, issueId, "progress", { message: `Device ${deviceId} picked up ${isFollowup ? "follow-up" : "task"}` });
|
|
34657
34802
|
let attachmentRefs = [];
|
|
34658
34803
|
if (task.from_comment_id && task.tenant_workspace_id && task.project_id) {
|
|
34659
|
-
const baseDir = workingDir ||
|
|
34660
|
-
const inDir =
|
|
34804
|
+
const baseDir = workingDir || join12(MULTI_DIR4, "tmp", issueId);
|
|
34805
|
+
const inDir = join12(baseDir, ".multi-in", task.from_comment_id);
|
|
34661
34806
|
attachmentRefs = await downloadCommentAttachments(apiUrl, task.tenant_workspace_id, task.project_id, issueId, task.from_comment_id, inDir);
|
|
34662
34807
|
if (attachmentRefs.length)
|
|
34663
34808
|
log3(` fetched ${attachmentRefs.length} attachment(s) → ${inDir}`);
|
|
34664
34809
|
}
|
|
34665
|
-
const outDir =
|
|
34810
|
+
const outDir = join12(workingDir || join12(MULTI_DIR4, "tmp", issueId), ".multi-out");
|
|
34666
34811
|
let liveCommentId;
|
|
34667
34812
|
let liveBody = "";
|
|
34668
34813
|
let hadError = false;
|
|
@@ -35172,7 +35317,12 @@ async function buildPlanningPreamble(apiUrl, task, _wsId) {
|
|
|
35172
35317
|
if (depth >= PLANNING_DEPTH_LIMIT) {
|
|
35173
35318
|
return `# Planning (sub-task context)
|
|
35174
35319
|
|
|
35175
|
-
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.
|
|
35176
35326
|
|
|
35177
35327
|
\`\`\`multi-plan
|
|
35178
35328
|
{"actions":[{"type":"update","id":"<this issue id>","status":"done"}]}
|
|
@@ -35220,6 +35370,8 @@ Issue actions:
|
|
|
35220
35370
|
]}
|
|
35221
35371
|
\`\`\`
|
|
35222
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
|
+
|
|
35223
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.
|
|
35224
35376
|
|
|
35225
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).
|
|
@@ -35338,7 +35490,7 @@ async function executePlanActions(apiUrl, parentTask, actions, ctx, parseErrors
|
|
|
35338
35490
|
}
|
|
35339
35491
|
lines.push(`- [ok] updated ${res.data.key}`);
|
|
35340
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 });
|
|
35341
|
-
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)) {
|
|
35342
35494
|
const targetKey = res.data?.key;
|
|
35343
35495
|
if (targetKey) {
|
|
35344
35496
|
const targetIssueId = res.data?.id || a.id;
|
|
@@ -35619,26 +35771,26 @@ function statusIcon(status3) {
|
|
|
35619
35771
|
}
|
|
35620
35772
|
}
|
|
35621
35773
|
async function resolveAcpAdapter(agentType, detectedPath) {
|
|
35622
|
-
if (agentType === "pi" && detectedPath &&
|
|
35774
|
+
if (agentType === "pi" && detectedPath && existsSync12(detectedPath)) {
|
|
35623
35775
|
return [detectedPath, "--mode", "rpc"];
|
|
35624
35776
|
}
|
|
35625
35777
|
const override = process.env.MULTI_ACP_ADAPTER?.trim();
|
|
35626
35778
|
const adapterNames = override ? [override] : ["claude-code-acp", "claude-agent-acp"];
|
|
35627
35779
|
for (const name of adapterNames) {
|
|
35628
|
-
if (name.startsWith("/") &&
|
|
35780
|
+
if (name.startsWith("/") && existsSync12(name))
|
|
35629
35781
|
return [name];
|
|
35630
35782
|
try {
|
|
35631
35783
|
const here = new URL(import.meta.url).pathname;
|
|
35632
35784
|
let dir = here;
|
|
35633
35785
|
for (let i = 0;i < 8; i++) {
|
|
35634
35786
|
dir = dirname9(dir);
|
|
35635
|
-
const bin =
|
|
35636
|
-
if (
|
|
35787
|
+
const bin = join12(dir, "node_modules", ".bin", name);
|
|
35788
|
+
if (existsSync12(bin))
|
|
35637
35789
|
return [bin];
|
|
35638
35790
|
}
|
|
35639
35791
|
} catch {}
|
|
35640
|
-
const global =
|
|
35641
|
-
if (
|
|
35792
|
+
const global = join12(HOME4, ".bun", "install", "global", "node_modules", ".bin", name);
|
|
35793
|
+
if (existsSync12(global))
|
|
35642
35794
|
return [global];
|
|
35643
35795
|
}
|
|
35644
35796
|
return null;
|
|
@@ -35647,7 +35799,7 @@ async function postStream(_apiUrl, issueId, event_type, payload) {
|
|
|
35647
35799
|
try {
|
|
35648
35800
|
ensureDirs();
|
|
35649
35801
|
const date6 = new Date().toISOString().slice(0, 10);
|
|
35650
|
-
const path =
|
|
35802
|
+
const path = join12(MULTI_DIR4, "logs", `events-${date6}.ndjson`);
|
|
35651
35803
|
appendFileSync4(path, JSON.stringify({ ts: Date.now(), issue_id: issueId, event_type, payload }) + `
|
|
35652
35804
|
`);
|
|
35653
35805
|
} catch {}
|
|
@@ -35666,7 +35818,7 @@ async function downloadCommentAttachments(apiUrl, wsId, projectId, issueId, comm
|
|
|
35666
35818
|
const items = list.data?.results || list.data || [];
|
|
35667
35819
|
if (!Array.isArray(items) || items.length === 0)
|
|
35668
35820
|
return [];
|
|
35669
|
-
|
|
35821
|
+
mkdirSync9(destDir, { recursive: true });
|
|
35670
35822
|
const token = authTokenHeader();
|
|
35671
35823
|
const out = [];
|
|
35672
35824
|
for (const it of items) {
|
|
@@ -35675,8 +35827,8 @@ async function downloadCommentAttachments(apiUrl, wsId, projectId, issueId, comm
|
|
|
35675
35827
|
continue;
|
|
35676
35828
|
const buf = new Uint8Array(await res.arrayBuffer());
|
|
35677
35829
|
const safe = it.filename.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
35678
|
-
const p =
|
|
35679
|
-
|
|
35830
|
+
const p = join12(destDir, safe);
|
|
35831
|
+
writeFileSync7(p, buf);
|
|
35680
35832
|
out.push({ filename: it.filename, path: p });
|
|
35681
35833
|
}
|
|
35682
35834
|
return out;
|
|
@@ -35686,9 +35838,9 @@ async function downloadCommentAttachments(apiUrl, wsId, projectId, issueId, comm
|
|
|
35686
35838
|
}
|
|
35687
35839
|
function authTokenHeader() {
|
|
35688
35840
|
try {
|
|
35689
|
-
if (!
|
|
35841
|
+
if (!existsSync12(CONFIG_PATH2))
|
|
35690
35842
|
return null;
|
|
35691
|
-
const raw = JSON.parse(
|
|
35843
|
+
const raw = JSON.parse(readFileSync10(CONFIG_PATH2, "utf8"));
|
|
35692
35844
|
const token = raw.token ?? raw.authToken;
|
|
35693
35845
|
return token ? `Bearer ${token}` : null;
|
|
35694
35846
|
} catch {
|
|
@@ -35696,14 +35848,14 @@ function authTokenHeader() {
|
|
|
35696
35848
|
}
|
|
35697
35849
|
}
|
|
35698
35850
|
async function uploadOutputDir(apiUrl, wsId, projectId, issueId, commentId, dir) {
|
|
35699
|
-
if (!
|
|
35851
|
+
if (!existsSync12(dir))
|
|
35700
35852
|
return 0;
|
|
35701
35853
|
const files = [];
|
|
35702
35854
|
const walk = (d, depth = 0) => {
|
|
35703
35855
|
if (depth > 3)
|
|
35704
35856
|
return;
|
|
35705
|
-
for (const name of
|
|
35706
|
-
const p =
|
|
35857
|
+
for (const name of readdirSync3(d)) {
|
|
35858
|
+
const p = join12(d, name);
|
|
35707
35859
|
try {
|
|
35708
35860
|
const st = statSync2(p);
|
|
35709
35861
|
if (st.isDirectory())
|
|
@@ -35721,7 +35873,7 @@ async function uploadOutputDir(apiUrl, wsId, projectId, issueId, commentId, dir)
|
|
|
35721
35873
|
let uploaded = 0;
|
|
35722
35874
|
for (const f of files) {
|
|
35723
35875
|
try {
|
|
35724
|
-
const data =
|
|
35876
|
+
const data = readFileSync10(f);
|
|
35725
35877
|
const form = new FormData;
|
|
35726
35878
|
const blob = new Blob([data]);
|
|
35727
35879
|
form.append("file", blob, f.split("/").pop() || "file");
|
|
@@ -35736,7 +35888,7 @@ async function uploadOutputDir(apiUrl, wsId, projectId, issueId, commentId, dir)
|
|
|
35736
35888
|
if (res.ok) {
|
|
35737
35889
|
uploaded++;
|
|
35738
35890
|
try {
|
|
35739
|
-
|
|
35891
|
+
unlinkSync6(f);
|
|
35740
35892
|
} catch {}
|
|
35741
35893
|
}
|
|
35742
35894
|
} catch (e) {
|
|
@@ -36012,10 +36164,10 @@ var init_run_task = __esm(() => {
|
|
|
36012
36164
|
init_git_enforce();
|
|
36013
36165
|
init_outbox();
|
|
36014
36166
|
HOME4 = process.env.HOME || process.env.USERPROFILE || ".";
|
|
36015
|
-
MULTI_DIR4 =
|
|
36016
|
-
CONFIG_PATH2 =
|
|
36017
|
-
LOG_PATH3 =
|
|
36018
|
-
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");
|
|
36019
36171
|
});
|
|
36020
36172
|
|
|
36021
36173
|
// ../lib/chat-doc.ts
|
|
@@ -36061,12 +36213,12 @@ function patchMessage(doc2, mapId, patch9) {
|
|
|
36061
36213
|
map20.set(k, v);
|
|
36062
36214
|
}
|
|
36063
36215
|
}
|
|
36064
|
-
function appendText(doc2, mapId,
|
|
36216
|
+
function appendText(doc2, mapId, chunk3) {
|
|
36065
36217
|
const map20 = doc2.getContainerById(mapId);
|
|
36066
36218
|
if (!map20)
|
|
36067
36219
|
return;
|
|
36068
36220
|
const cur = map20.get("text") ?? "";
|
|
36069
|
-
map20.set("text", cur +
|
|
36221
|
+
map20.set("text", cur + chunk3);
|
|
36070
36222
|
}
|
|
36071
36223
|
function finalizeMessage(doc2, mapId) {
|
|
36072
36224
|
const map20 = doc2.getContainerById(mapId);
|
|
@@ -36107,8 +36259,8 @@ var init_chat_doc = __esm(() => {
|
|
|
36107
36259
|
});
|
|
36108
36260
|
|
|
36109
36261
|
// src/_impl/chat-peer.ts
|
|
36110
|
-
import { existsSync as
|
|
36111
|
-
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";
|
|
36112
36264
|
import { LoroDoc as LoroDoc2 } from "loro-crdt";
|
|
36113
36265
|
|
|
36114
36266
|
class ChatPeer {
|
|
@@ -36125,24 +36277,24 @@ class ChatPeer {
|
|
|
36125
36277
|
constructor(opts) {
|
|
36126
36278
|
this.opts = opts;
|
|
36127
36279
|
this.chatId = opts.chatId;
|
|
36128
|
-
this.snapshotPath =
|
|
36280
|
+
this.snapshotPath = join13(MULTI_DIR, "chats", `${opts.chatId}.loro`);
|
|
36129
36281
|
this.doc = this.loadFromDisk();
|
|
36130
36282
|
for (const m of listMessages(this.doc))
|
|
36131
36283
|
this.seenIds.add(m.id);
|
|
36132
36284
|
}
|
|
36133
36285
|
loadFromDisk() {
|
|
36134
36286
|
try {
|
|
36135
|
-
if (
|
|
36136
|
-
const bytes =
|
|
36287
|
+
if (existsSync13(this.snapshotPath)) {
|
|
36288
|
+
const bytes = readFileSync11(this.snapshotPath);
|
|
36137
36289
|
return importSnapshot(new Uint8Array(bytes));
|
|
36138
36290
|
}
|
|
36139
36291
|
} catch {}
|
|
36140
36292
|
return new LoroDoc2;
|
|
36141
36293
|
}
|
|
36142
36294
|
persist() {
|
|
36143
|
-
if (!
|
|
36144
|
-
|
|
36145
|
-
|
|
36295
|
+
if (!existsSync13(dirname10(this.snapshotPath)))
|
|
36296
|
+
mkdirSync10(dirname10(this.snapshotPath), { recursive: true });
|
|
36297
|
+
writeFileSync8(this.snapshotPath, exportSnapshot(this.doc));
|
|
36146
36298
|
this.dirtySinceWrite = 0;
|
|
36147
36299
|
}
|
|
36148
36300
|
appendAndPush(msg) {
|
|
@@ -36177,8 +36329,8 @@ class ChatPeer {
|
|
|
36177
36329
|
this.flush();
|
|
36178
36330
|
return { msgId: msg.id, containerId };
|
|
36179
36331
|
}
|
|
36180
|
-
appendPartialText(containerId,
|
|
36181
|
-
appendText(this.doc, containerId,
|
|
36332
|
+
appendPartialText(containerId, chunk3) {
|
|
36333
|
+
appendText(this.doc, containerId, chunk3);
|
|
36182
36334
|
this.flush();
|
|
36183
36335
|
}
|
|
36184
36336
|
finalizePartialMessage(containerId) {
|
|
@@ -37188,6 +37340,7 @@ Action vocabulary:
|
|
|
37188
37340
|
|
|
37189
37341
|
Rules:
|
|
37190
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.
|
|
37191
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.
|
|
37192
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.
|
|
37193
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.
|
|
@@ -37962,7 +38115,7 @@ import { parseArgs } from "util";
|
|
|
37962
38115
|
// package.json
|
|
37963
38116
|
var package_default = {
|
|
37964
38117
|
name: "@shipers-dev/multi",
|
|
37965
|
-
version: "0.
|
|
38118
|
+
version: "0.51.0",
|
|
37966
38119
|
type: "module",
|
|
37967
38120
|
bin: {
|
|
37968
38121
|
"multi-agent": "./dist/index.js"
|
|
@@ -38476,7 +38629,7 @@ class Runners extends exports_Effect.Service()("cli/Runners", {
|
|
|
38476
38629
|
// src/commands/simple.ts
|
|
38477
38630
|
init_esm();
|
|
38478
38631
|
init_paths();
|
|
38479
|
-
import { existsSync as
|
|
38632
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
|
|
38480
38633
|
|
|
38481
38634
|
// src/services/Config.ts
|
|
38482
38635
|
init_esm();
|
|
@@ -38571,7 +38724,7 @@ var statusCmd = exports_Effect.fn("statusCmd")(function* () {
|
|
|
38571
38724
|
process.exit(1);
|
|
38572
38725
|
}
|
|
38573
38726
|
const d = res.data;
|
|
38574
|
-
const pid =
|
|
38727
|
+
const pid = existsSync7(PID_PATH) ? readFileSync7(PID_PATH, "utf8").trim() : null;
|
|
38575
38728
|
const daemon = pid && isRunning3(Number(pid)) ? `running (pid ${pid})` : "stopped";
|
|
38576
38729
|
console.log(`
|
|
38577
38730
|
Device Status
|
|
@@ -38586,11 +38739,11 @@ Daemon: ${daemon}
|
|
|
38586
38739
|
});
|
|
38587
38740
|
var stopCmd = exports_Effect.fn("stopCmd")(function* () {
|
|
38588
38741
|
const fs3 = yield* FileSystem;
|
|
38589
|
-
if (!
|
|
38742
|
+
if (!existsSync7(PID_PATH)) {
|
|
38590
38743
|
console.log("No daemon running.");
|
|
38591
38744
|
return;
|
|
38592
38745
|
}
|
|
38593
|
-
const pid = Number(
|
|
38746
|
+
const pid = Number(readFileSync7(PID_PATH, "utf8").trim());
|
|
38594
38747
|
if (!pid || !isRunning3(pid)) {
|
|
38595
38748
|
yield* fs3.remove(PID_PATH);
|
|
38596
38749
|
console.log("Cleaned stale pidfile.");
|
|
@@ -38603,11 +38756,11 @@ var stopCmd = exports_Effect.fn("stopCmd")(function* () {
|
|
|
38603
38756
|
} catch {}
|
|
38604
38757
|
});
|
|
38605
38758
|
var logsCmd = exports_Effect.fn("logsCmd")(function* () {
|
|
38606
|
-
if (!
|
|
38759
|
+
if (!existsSync7(LOG_PATH)) {
|
|
38607
38760
|
console.log("No logs yet.");
|
|
38608
38761
|
return;
|
|
38609
38762
|
}
|
|
38610
|
-
const content =
|
|
38763
|
+
const content = readFileSync7(LOG_PATH, "utf8");
|
|
38611
38764
|
console.log(content.split(`
|
|
38612
38765
|
`).slice(-100).join(`
|
|
38613
38766
|
`));
|
|
@@ -38616,10 +38769,10 @@ var resetCmd = exports_Effect.fn("resetCmd")(function* (issueId) {
|
|
|
38616
38769
|
if (!issueId) {
|
|
38617
38770
|
return yield* exports_Effect.fail(new UsageError({ message: "reset requires --issue <id>" }));
|
|
38618
38771
|
}
|
|
38619
|
-
if (!
|
|
38772
|
+
if (!existsSync7(PORT_PATH)) {
|
|
38620
38773
|
return yield* exports_Effect.fail(new DaemonError({ message: "Daemon not running (no port file)." }));
|
|
38621
38774
|
}
|
|
38622
|
-
const port = Number(
|
|
38775
|
+
const port = Number(readFileSync7(PORT_PATH, "utf8").trim());
|
|
38623
38776
|
const config2 = yield* Config4;
|
|
38624
38777
|
const cfg = yield* config2.load;
|
|
38625
38778
|
if (!cfg.dispatchSecret) {
|
|
@@ -38780,10 +38933,10 @@ var linkCmd = exports_Effect.fn("linkCmd")(function* (apiUrl, agentId) {
|
|
|
38780
38933
|
// src/commands/restart.ts
|
|
38781
38934
|
init_esm();
|
|
38782
38935
|
init_paths();
|
|
38783
|
-
import { existsSync as
|
|
38936
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9, unlinkSync as unlinkSync5 } from "fs";
|
|
38784
38937
|
|
|
38785
38938
|
// src/_impl/pid-lock.ts
|
|
38786
|
-
import { closeSync, existsSync as
|
|
38939
|
+
import { closeSync, existsSync as existsSync8, mkdirSync as mkdirSync7, openSync, readFileSync as readFileSync8, unlinkSync as unlinkSync4, writeSync } from "fs";
|
|
38787
38940
|
import { dirname as dirname8 } from "path";
|
|
38788
38941
|
var isAlive = (pid) => {
|
|
38789
38942
|
if (!Number.isFinite(pid) || pid <= 0)
|
|
@@ -38796,7 +38949,7 @@ var isAlive = (pid) => {
|
|
|
38796
38949
|
}
|
|
38797
38950
|
};
|
|
38798
38951
|
var writeExclusive = (path, pid) => {
|
|
38799
|
-
|
|
38952
|
+
mkdirSync7(dirname8(path), { recursive: true });
|
|
38800
38953
|
const fd = openSync(path, "wx");
|
|
38801
38954
|
try {
|
|
38802
38955
|
writeSync(fd, String(pid));
|
|
@@ -38815,7 +38968,7 @@ var acquirePidLock = (path) => {
|
|
|
38815
38968
|
}
|
|
38816
38969
|
const raw = (() => {
|
|
38817
38970
|
try {
|
|
38818
|
-
return
|
|
38971
|
+
return readFileSync8(path, "utf8").trim();
|
|
38819
38972
|
} catch {
|
|
38820
38973
|
return "";
|
|
38821
38974
|
}
|
|
@@ -38827,7 +38980,7 @@ var acquirePidLock = (path) => {
|
|
|
38827
38980
|
return { ok: false, existingPid };
|
|
38828
38981
|
}
|
|
38829
38982
|
try {
|
|
38830
|
-
|
|
38983
|
+
unlinkSync4(path);
|
|
38831
38984
|
} catch {}
|
|
38832
38985
|
try {
|
|
38833
38986
|
writeExclusive(path, ourPid);
|
|
@@ -38837,7 +38990,7 @@ var acquirePidLock = (path) => {
|
|
|
38837
38990
|
throw e;
|
|
38838
38991
|
const racingRaw = (() => {
|
|
38839
38992
|
try {
|
|
38840
|
-
return
|
|
38993
|
+
return readFileSync8(path, "utf8").trim();
|
|
38841
38994
|
} catch {
|
|
38842
38995
|
return "";
|
|
38843
38996
|
}
|
|
@@ -38848,12 +39001,12 @@ var acquirePidLock = (path) => {
|
|
|
38848
39001
|
};
|
|
38849
39002
|
var releasePidLock = (path) => {
|
|
38850
39003
|
try {
|
|
38851
|
-
if (!
|
|
39004
|
+
if (!existsSync8(path))
|
|
38852
39005
|
return;
|
|
38853
|
-
const raw =
|
|
39006
|
+
const raw = readFileSync8(path, "utf8").trim();
|
|
38854
39007
|
if (Number(raw) !== process.pid)
|
|
38855
39008
|
return;
|
|
38856
|
-
|
|
39009
|
+
unlinkSync4(path);
|
|
38857
39010
|
} catch {}
|
|
38858
39011
|
};
|
|
38859
39012
|
var killStaleConnects = () => {
|
|
@@ -38918,8 +39071,8 @@ var isRunning4 = (pid) => {
|
|
|
38918
39071
|
var restartCmd = exports_Effect.fn("restartCmd")(function* (apiUrl) {
|
|
38919
39072
|
const fs3 = yield* FileSystem;
|
|
38920
39073
|
yield* (yield* Config4).ensureDirs;
|
|
38921
|
-
if (
|
|
38922
|
-
const pid = Number(
|
|
39074
|
+
if (existsSync9(PID_PATH)) {
|
|
39075
|
+
const pid = Number(readFileSync9(PID_PATH, "utf8").trim());
|
|
38923
39076
|
if (pid && isRunning4(pid)) {
|
|
38924
39077
|
yield* fs3.writeText(STOP_PATH, "1");
|
|
38925
39078
|
try {
|
|
@@ -38938,13 +39091,13 @@ var restartCmd = exports_Effect.fn("restartCmd")(function* (apiUrl) {
|
|
|
38938
39091
|
}
|
|
38939
39092
|
}
|
|
38940
39093
|
try {
|
|
38941
|
-
if (
|
|
38942
|
-
|
|
39094
|
+
if (existsSync9(PID_PATH))
|
|
39095
|
+
unlinkSync5(PID_PATH);
|
|
38943
39096
|
} catch {}
|
|
38944
39097
|
}
|
|
38945
39098
|
try {
|
|
38946
|
-
if (
|
|
38947
|
-
|
|
39099
|
+
if (existsSync9(STOP_PATH))
|
|
39100
|
+
unlinkSync5(STOP_PATH);
|
|
38948
39101
|
} catch {}
|
|
38949
39102
|
const killed = killStaleConnects();
|
|
38950
39103
|
if (killed.length)
|
|
@@ -39040,21 +39193,22 @@ init_client();
|
|
|
39040
39193
|
init_detect();
|
|
39041
39194
|
init_run_task();
|
|
39042
39195
|
import { Database as Database3 } from "bun:sqlite";
|
|
39043
|
-
import { existsSync as
|
|
39044
|
-
import { homedir as
|
|
39045
|
-
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();
|
|
39046
39200
|
init_errors();
|
|
39047
|
-
var MULTI_DIR5 =
|
|
39048
|
-
var PID_PATH2 =
|
|
39049
|
-
var PORT_PATH2 =
|
|
39050
|
-
var STOP_PATH2 =
|
|
39051
|
-
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");
|
|
39052
39206
|
function ensureDirs2() {
|
|
39053
|
-
if (!
|
|
39054
|
-
|
|
39055
|
-
const logs =
|
|
39056
|
-
if (!
|
|
39057
|
-
|
|
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 });
|
|
39058
39212
|
}
|
|
39059
39213
|
function isLocalApi(url2) {
|
|
39060
39214
|
try {
|
|
@@ -39346,8 +39500,11 @@ var daemonProgram = ({ cfg, apiUrl }) => exports_Effect.gen(function* () {
|
|
|
39346
39500
|
if (cfg.authToken)
|
|
39347
39501
|
setAuthToken(cfg.authToken);
|
|
39348
39502
|
ensureDirs2();
|
|
39349
|
-
if (
|
|
39350
|
-
|
|
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"}`);
|
|
39351
39508
|
const detected = yield* exports_Effect.promise(() => detectAgents());
|
|
39352
39509
|
const localMode = isLocalApi(apiUrl);
|
|
39353
39510
|
log3(`daemon device=${cfg.deviceId} pid=${process.pid}`);
|
|
@@ -39481,10 +39638,10 @@ var daemonProgram = ({ cfg, apiUrl }) => exports_Effect.gen(function* () {
|
|
|
39481
39638
|
});
|
|
39482
39639
|
log3(`Local server: http://127.0.0.1:${port}`);
|
|
39483
39640
|
try {
|
|
39484
|
-
|
|
39641
|
+
writeFileSync9(PORT_PATH2, String(port));
|
|
39485
39642
|
} catch {}
|
|
39486
39643
|
try {
|
|
39487
|
-
|
|
39644
|
+
writeFileSync9(PID_PATH2, String(process.pid));
|
|
39488
39645
|
} catch {}
|
|
39489
39646
|
let tunnel;
|
|
39490
39647
|
if (localMode) {
|
|
@@ -39548,7 +39705,7 @@ var daemonProgram = ({ cfg, apiUrl }) => exports_Effect.gen(function* () {
|
|
|
39548
39705
|
let probeFailures = 0;
|
|
39549
39706
|
while (true) {
|
|
39550
39707
|
yield* exports_Effect.sleep(exports_Duration.seconds(120));
|
|
39551
|
-
if (
|
|
39708
|
+
if (existsSync14(STOP_PATH2)) {
|
|
39552
39709
|
yield* exports_Deferred.succeed(stopDeferred, "stop flag");
|
|
39553
39710
|
return;
|
|
39554
39711
|
}
|
|
@@ -39573,7 +39730,7 @@ var daemonProgram = ({ cfg, apiUrl }) => exports_Effect.gen(function* () {
|
|
|
39573
39730
|
yield* exports_Effect.forkIn(daemonScope)(exports_Effect.gen(function* () {
|
|
39574
39731
|
while (true) {
|
|
39575
39732
|
yield* exports_Effect.sleep(exports_Duration.seconds(5));
|
|
39576
|
-
if (
|
|
39733
|
+
if (existsSync14(STOP_PATH2)) {
|
|
39577
39734
|
yield* exports_Deferred.succeed(stopDeferred, "stop flag");
|
|
39578
39735
|
return;
|
|
39579
39736
|
}
|
|
@@ -39613,12 +39770,12 @@ var daemonProgram = ({ cfg, apiUrl }) => exports_Effect.gen(function* () {
|
|
|
39613
39770
|
yield* exports_Effect.race(exports_Fiber.interruptAll(inFlight), exports_Effect.sleep(exports_Duration.seconds(10)));
|
|
39614
39771
|
}
|
|
39615
39772
|
yield* exports_Scope.close(daemonScope, exports_Exit.void).pipe(exports_Effect.catchAll(() => exports_Effect.void));
|
|
39616
|
-
if (
|
|
39617
|
-
|
|
39618
|
-
if (
|
|
39619
|
-
|
|
39620
|
-
if (
|
|
39621
|
-
|
|
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);
|
|
39622
39779
|
db2.close();
|
|
39623
39780
|
log3("disconnected");
|
|
39624
39781
|
});
|