@shipers-dev/multi 0.38.5 → 0.39.1
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 +208 -133
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -33834,7 +33834,7 @@ function parsePlanBlocks(text) {
|
|
|
33834
33834
|
}
|
|
33835
33835
|
return { actions, errors: errors3 };
|
|
33836
33836
|
}
|
|
33837
|
-
var PLAN_SCHEMA_VERSION =
|
|
33837
|
+
var PLAN_SCHEMA_VERSION = 2, Priority, AssigneeType, IssueStatus, SessionRole, SkillFile, PlanActionSchema, PlanEnvelopeSchema, UiBlockSchema, UI_FENCE_RE, FENCE_RE;
|
|
33838
33838
|
var init_plans = __esm(() => {
|
|
33839
33839
|
init_zod();
|
|
33840
33840
|
Priority = exports_external.enum(["low", "medium", "high"]);
|
|
@@ -33870,6 +33870,11 @@ var init_plans = __esm(() => {
|
|
|
33870
33870
|
id: exports_external.string().min(1),
|
|
33871
33871
|
assignee_id: exports_external.string().min(1)
|
|
33872
33872
|
}),
|
|
33873
|
+
exports_external.object({
|
|
33874
|
+
type: exports_external.literal("issue.comment"),
|
|
33875
|
+
id: exports_external.string().min(1),
|
|
33876
|
+
body: exports_external.string().min(1).max(8000)
|
|
33877
|
+
}),
|
|
33873
33878
|
exports_external.object({
|
|
33874
33879
|
type: exports_external.literal("issue.delete"),
|
|
33875
33880
|
id: exports_external.string().min(1)
|
|
@@ -34023,7 +34028,7 @@ var init_lib = __esm(() => {
|
|
|
34023
34028
|
|
|
34024
34029
|
// src/_impl/git-enforce.ts
|
|
34025
34030
|
import { spawn as spawn2 } from "node:child_process";
|
|
34026
|
-
import { existsSync as
|
|
34031
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
34027
34032
|
import { join as join9 } from "node:path";
|
|
34028
34033
|
function run4(cwd, cmd, args2) {
|
|
34029
34034
|
return new Promise((resolve2) => {
|
|
@@ -34063,7 +34068,7 @@ function enforceCommitAndPush(baseWorkingDir, issueKey, mode = "enforce") {
|
|
|
34063
34068
|
};
|
|
34064
34069
|
if (mode === "off")
|
|
34065
34070
|
return result;
|
|
34066
|
-
if (!
|
|
34071
|
+
if (!existsSync9(wt))
|
|
34067
34072
|
return result;
|
|
34068
34073
|
if (!(yield* isGitRepo2(wt)))
|
|
34069
34074
|
return result;
|
|
@@ -34151,7 +34156,7 @@ var git = (cwd, args2, op) => exports_Effect.tryPromise({
|
|
|
34151
34156
|
try: () => run4(cwd, "git", args2),
|
|
34152
34157
|
catch: (cause3) => new GitError({ op, message: `git ${args2.join(" ")} threw`, cause: cause3 })
|
|
34153
34158
|
}), isGitRepo2 = (dir) => exports_Effect.gen(function* () {
|
|
34154
|
-
if (!
|
|
34159
|
+
if (!existsSync9(dir))
|
|
34155
34160
|
return false;
|
|
34156
34161
|
const r = yield* git(dir, ["rev-parse", "--is-inside-work-tree"], "rev-parse");
|
|
34157
34162
|
return r.code === 0 && r.stdout === "true";
|
|
@@ -34163,14 +34168,14 @@ var init_git_enforce = __esm(() => {
|
|
|
34163
34168
|
|
|
34164
34169
|
// src/_impl/outbox.ts
|
|
34165
34170
|
import { Database as Database2 } from "bun:sqlite";
|
|
34166
|
-
import { existsSync as
|
|
34171
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync7 } from "fs";
|
|
34167
34172
|
import { homedir } from "os";
|
|
34168
34173
|
import { join as join10 } from "path";
|
|
34169
34174
|
function ensureDb() {
|
|
34170
34175
|
if (db)
|
|
34171
34176
|
return db;
|
|
34172
|
-
if (!
|
|
34173
|
-
|
|
34177
|
+
if (!existsSync10(MULTI_DIR3))
|
|
34178
|
+
mkdirSync7(MULTI_DIR3, { recursive: true });
|
|
34174
34179
|
db = new Database2(TASKS_DB_PATH2);
|
|
34175
34180
|
db.exec(`
|
|
34176
34181
|
CREATE TABLE IF NOT EXISTS stream_outbox (
|
|
@@ -34308,12 +34313,12 @@ var init_outbox = __esm(() => {
|
|
|
34308
34313
|
});
|
|
34309
34314
|
|
|
34310
34315
|
// src/_impl/run-task.ts
|
|
34311
|
-
import { mkdirSync as
|
|
34312
|
-
import { join as join11, dirname as
|
|
34316
|
+
import { mkdirSync as mkdirSync8, existsSync as existsSync11, writeFileSync as writeFileSync6, readFileSync as readFileSync9, appendFileSync as appendFileSync4, unlinkSync as unlinkSync5, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
34317
|
+
import { join as join11, dirname as dirname9 } from "path";
|
|
34313
34318
|
function ensureDirs() {
|
|
34314
34319
|
for (const d of [MULTI_DIR4, join11(MULTI_DIR4, "logs"), SKILLS_DIR2]) {
|
|
34315
|
-
if (!
|
|
34316
|
-
|
|
34320
|
+
if (!existsSync11(d))
|
|
34321
|
+
mkdirSync8(d, { recursive: true });
|
|
34317
34322
|
}
|
|
34318
34323
|
}
|
|
34319
34324
|
function log3(msg) {
|
|
@@ -34360,7 +34365,7 @@ function resolveEnforcementMode(task) {
|
|
|
34360
34365
|
async function handleRunTask(apiUrl, deviceId, task, detected, ctx) {
|
|
34361
34366
|
const issueId = task.issue_id;
|
|
34362
34367
|
const isFollowup = !!task.followup;
|
|
34363
|
-
const baseWorkingDir = task.working_dir &&
|
|
34368
|
+
const baseWorkingDir = task.working_dir && existsSync11(task.working_dir) ? task.working_dir : undefined;
|
|
34364
34369
|
const tenantWsId = task.tenant_workspace_id ?? null;
|
|
34365
34370
|
const projectId = task.project_id ?? null;
|
|
34366
34371
|
const ISSUE_BASE = tenantWsId && projectId ? `${apiUrl}/api/workspaces/${tenantWsId}/projects/${projectId}/issues/${issueId}` : null;
|
|
@@ -35051,7 +35056,7 @@ async function executePlanActions(apiUrl, parentTask, actions, ctx, parseErrors
|
|
|
35051
35056
|
continue;
|
|
35052
35057
|
}
|
|
35053
35058
|
lines.push(`- [ok] updated ${res.data.key}`);
|
|
35054
|
-
if ((a.status === "done" || a.status === "cancelled") && parentTask.working_dir &&
|
|
35059
|
+
if ((a.status === "done" || a.status === "cancelled") && parentTask.working_dir && existsSync11(parentTask.working_dir)) {
|
|
35055
35060
|
const targetKey = res.data?.key;
|
|
35056
35061
|
if (targetKey) {
|
|
35057
35062
|
const targetIssueId = res.data?.id || a.id;
|
|
@@ -35286,26 +35291,26 @@ function statusIcon(status3) {
|
|
|
35286
35291
|
}
|
|
35287
35292
|
}
|
|
35288
35293
|
async function resolveAcpAdapter(agentType, detectedPath) {
|
|
35289
|
-
if (agentType === "pi" && detectedPath &&
|
|
35294
|
+
if (agentType === "pi" && detectedPath && existsSync11(detectedPath)) {
|
|
35290
35295
|
return [detectedPath, "--mode", "rpc"];
|
|
35291
35296
|
}
|
|
35292
35297
|
const override = process.env.MULTI_ACP_ADAPTER?.trim();
|
|
35293
35298
|
const adapterNames = override ? [override] : ["claude-code-acp", "claude-agent-acp"];
|
|
35294
35299
|
for (const name of adapterNames) {
|
|
35295
|
-
if (name.startsWith("/") &&
|
|
35300
|
+
if (name.startsWith("/") && existsSync11(name))
|
|
35296
35301
|
return [name];
|
|
35297
35302
|
try {
|
|
35298
35303
|
const here = new URL(import.meta.url).pathname;
|
|
35299
35304
|
let dir = here;
|
|
35300
35305
|
for (let i = 0;i < 8; i++) {
|
|
35301
|
-
dir =
|
|
35306
|
+
dir = dirname9(dir);
|
|
35302
35307
|
const bin = join11(dir, "node_modules", ".bin", name);
|
|
35303
|
-
if (
|
|
35308
|
+
if (existsSync11(bin))
|
|
35304
35309
|
return [bin];
|
|
35305
35310
|
}
|
|
35306
35311
|
} catch {}
|
|
35307
35312
|
const global2 = join11(HOME4, ".bun", "install", "global", "node_modules", ".bin", name);
|
|
35308
|
-
if (
|
|
35313
|
+
if (existsSync11(global2))
|
|
35309
35314
|
return [global2];
|
|
35310
35315
|
}
|
|
35311
35316
|
return null;
|
|
@@ -35333,7 +35338,7 @@ async function downloadCommentAttachments(apiUrl, wsId, projectId, issueId, comm
|
|
|
35333
35338
|
const items = list.data?.results || list.data || [];
|
|
35334
35339
|
if (!Array.isArray(items) || items.length === 0)
|
|
35335
35340
|
return [];
|
|
35336
|
-
|
|
35341
|
+
mkdirSync8(destDir, { recursive: true });
|
|
35337
35342
|
const token = authTokenHeader();
|
|
35338
35343
|
const out = [];
|
|
35339
35344
|
for (const it of items) {
|
|
@@ -35353,9 +35358,9 @@ async function downloadCommentAttachments(apiUrl, wsId, projectId, issueId, comm
|
|
|
35353
35358
|
}
|
|
35354
35359
|
function authTokenHeader() {
|
|
35355
35360
|
try {
|
|
35356
|
-
if (!
|
|
35361
|
+
if (!existsSync11(CONFIG_PATH2))
|
|
35357
35362
|
return null;
|
|
35358
|
-
const raw = JSON.parse(
|
|
35363
|
+
const raw = JSON.parse(readFileSync9(CONFIG_PATH2, "utf8"));
|
|
35359
35364
|
const token = raw.token ?? raw.authToken;
|
|
35360
35365
|
return token ? `Bearer ${token}` : null;
|
|
35361
35366
|
} catch {
|
|
@@ -35363,7 +35368,7 @@ function authTokenHeader() {
|
|
|
35363
35368
|
}
|
|
35364
35369
|
}
|
|
35365
35370
|
async function uploadOutputDir(apiUrl, wsId, projectId, issueId, commentId, dir) {
|
|
35366
|
-
if (!
|
|
35371
|
+
if (!existsSync11(dir))
|
|
35367
35372
|
return 0;
|
|
35368
35373
|
const files = [];
|
|
35369
35374
|
const walk = (d, depth = 0) => {
|
|
@@ -35388,7 +35393,7 @@ async function uploadOutputDir(apiUrl, wsId, projectId, issueId, commentId, dir)
|
|
|
35388
35393
|
let uploaded = 0;
|
|
35389
35394
|
for (const f of files) {
|
|
35390
35395
|
try {
|
|
35391
|
-
const data =
|
|
35396
|
+
const data = readFileSync9(f);
|
|
35392
35397
|
const form = new FormData;
|
|
35393
35398
|
const blob = new Blob([data]);
|
|
35394
35399
|
form.append("file", blob, f.split("/").pop() || "file");
|
|
@@ -35403,7 +35408,7 @@ async function uploadOutputDir(apiUrl, wsId, projectId, issueId, commentId, dir)
|
|
|
35403
35408
|
if (res.ok) {
|
|
35404
35409
|
uploaded++;
|
|
35405
35410
|
try {
|
|
35406
|
-
|
|
35411
|
+
unlinkSync5(f);
|
|
35407
35412
|
} catch {}
|
|
35408
35413
|
}
|
|
35409
35414
|
} catch (e) {
|
|
@@ -40156,8 +40161,8 @@ var init_chat_doc = __esm(() => {
|
|
|
40156
40161
|
});
|
|
40157
40162
|
|
|
40158
40163
|
// src/_impl/chat-peer.ts
|
|
40159
|
-
import { existsSync as
|
|
40160
|
-
import { dirname as
|
|
40164
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync9, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "fs";
|
|
40165
|
+
import { dirname as dirname10, join as join12 } from "path";
|
|
40161
40166
|
|
|
40162
40167
|
class ChatPeer {
|
|
40163
40168
|
chatId;
|
|
@@ -40179,16 +40184,16 @@ class ChatPeer {
|
|
|
40179
40184
|
}
|
|
40180
40185
|
loadFromDisk() {
|
|
40181
40186
|
try {
|
|
40182
|
-
if (
|
|
40183
|
-
const bytes =
|
|
40187
|
+
if (existsSync12(this.snapshotPath)) {
|
|
40188
|
+
const bytes = readFileSync10(this.snapshotPath);
|
|
40184
40189
|
return importSnapshot(new Uint8Array(bytes));
|
|
40185
40190
|
}
|
|
40186
40191
|
} catch {}
|
|
40187
40192
|
return new import_loro_crdt2.LoroDoc;
|
|
40188
40193
|
}
|
|
40189
40194
|
persist() {
|
|
40190
|
-
if (!
|
|
40191
|
-
|
|
40195
|
+
if (!existsSync12(dirname10(this.snapshotPath)))
|
|
40196
|
+
mkdirSync9(dirname10(this.snapshotPath), { recursive: true });
|
|
40192
40197
|
writeFileSync7(this.snapshotPath, exportSnapshot(this.doc));
|
|
40193
40198
|
this.dirtySinceWrite = 0;
|
|
40194
40199
|
}
|
|
@@ -40572,6 +40577,16 @@ async function executeChatPlanActions(actionsIn, parseErrors, ctx) {
|
|
|
40572
40577
|
}
|
|
40573
40578
|
lines.push(`- [ok] handoff -> ${a.target_agent_id} via **${res.data?.child_key}** expect=${a.expect ?? "summary"}`);
|
|
40574
40579
|
tally(true);
|
|
40580
|
+
} else if (a.type === "issue.comment") {
|
|
40581
|
+
const res = await apiClient.post(mutateUrl, { action: "comment", id: a.id, body: a.body }, { headers });
|
|
40582
|
+
if (!res.success) {
|
|
40583
|
+
lines.push(`- [err] issue.comment ${a.id}: ${res.error || res.status}`);
|
|
40584
|
+
tally(false);
|
|
40585
|
+
continue;
|
|
40586
|
+
}
|
|
40587
|
+
const dispatched = res.data?.dispatched;
|
|
40588
|
+
lines.push(`- [ok] commented on ${a.id}${dispatched ? " -> dispatched agent" : ""}`);
|
|
40589
|
+
tally(true);
|
|
40575
40590
|
} else if (a.type === "issue.delete") {
|
|
40576
40591
|
const res = await apiClient.post(mutateUrl, { action: "delete", id: a.id }, { headers });
|
|
40577
40592
|
if (!res.success) {
|
|
@@ -40727,7 +40742,8 @@ var init_chat_plan_actions = __esm(() => {
|
|
|
40727
40742
|
"skill.attach": 5,
|
|
40728
40743
|
"skill.detach": 5,
|
|
40729
40744
|
"agent.update": 5,
|
|
40730
|
-
"session.create": 3
|
|
40745
|
+
"session.create": 3,
|
|
40746
|
+
"issue.comment": 5
|
|
40731
40747
|
};
|
|
40732
40748
|
});
|
|
40733
40749
|
|
|
@@ -41141,6 +41157,7 @@ Action vocabulary:
|
|
|
41141
41157
|
{"type":"update","id":"<issue id or key>","status":"done"},
|
|
41142
41158
|
{"type":"delegate","id":"<issue id>","assignee_id":"<agent id>"},
|
|
41143
41159
|
{"type":"handoff","target_agent_id":"<agent id>","prompt":"..."},
|
|
41160
|
+
{"type":"issue.comment","id":"<issue id or key>","body":"@<agent name> ..."},
|
|
41144
41161
|
{"type":"issue.delete","id":"<issue id>"},
|
|
41145
41162
|
{"type":"issue.delete_where","status":"todo","limit":50},
|
|
41146
41163
|
{"type":"issue.list","status":"todo","limit":20},
|
|
@@ -41155,8 +41172,9 @@ Action vocabulary:
|
|
|
41155
41172
|
|
|
41156
41173
|
Rules:
|
|
41157
41174
|
- Omit the block entirely if no actions are needed. Don't emit empty arrays.
|
|
41158
|
-
- Max 10 actions per turn. Sub-caps: agent.create=2, skill.create=3, agent.update=5, skill.attach/detach=5, session.create=3.
|
|
41175
|
+
- 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.
|
|
41159
41176
|
- 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.
|
|
41177
|
+
- 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.
|
|
41160
41178
|
- \`allowed_tools\` on a new agent must be a subset of generally available tools.
|
|
41161
41179
|
|
|
41162
41180
|
To draw an inline panel for the user, append a separate fenced \`multi-ui\` block. The host uploads it to a Cloudflare Worker (24h TTL), serves the rendered HTML at a public URL, and embeds it in a sandboxed iframe (\`allow-scripts allow-forms\`). No reactive runtime — write plain HTML with optional inline \`<script>\` for interactivity. \`window.data\` inside your script is the JSON you sent.
|
|
@@ -41745,7 +41763,7 @@ import { parseArgs } from "util";
|
|
|
41745
41763
|
// package.json
|
|
41746
41764
|
var package_default = {
|
|
41747
41765
|
name: "@shipers-dev/multi",
|
|
41748
|
-
version: "0.
|
|
41766
|
+
version: "0.39.1",
|
|
41749
41767
|
type: "module",
|
|
41750
41768
|
bin: {
|
|
41751
41769
|
"multi-agent": "./dist/index.js"
|
|
@@ -42562,7 +42580,133 @@ var linkCmd = exports_Effect.fn("linkCmd")(function* (apiUrl, agentId) {
|
|
|
42562
42580
|
// src/commands/restart.ts
|
|
42563
42581
|
init_esm();
|
|
42564
42582
|
init_paths();
|
|
42565
|
-
import { existsSync as
|
|
42583
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8, unlinkSync as unlinkSync4 } from "fs";
|
|
42584
|
+
|
|
42585
|
+
// src/_impl/pid-lock.ts
|
|
42586
|
+
import { closeSync, existsSync as existsSync7, mkdirSync as mkdirSync6, openSync, readFileSync as readFileSync7, unlinkSync as unlinkSync3, writeSync } from "fs";
|
|
42587
|
+
import { dirname as dirname8 } from "path";
|
|
42588
|
+
var isAlive = (pid) => {
|
|
42589
|
+
if (!Number.isFinite(pid) || pid <= 0)
|
|
42590
|
+
return false;
|
|
42591
|
+
try {
|
|
42592
|
+
process.kill(pid, 0);
|
|
42593
|
+
return true;
|
|
42594
|
+
} catch (e) {
|
|
42595
|
+
return e?.code === "EPERM";
|
|
42596
|
+
}
|
|
42597
|
+
};
|
|
42598
|
+
var writeExclusive = (path, pid) => {
|
|
42599
|
+
mkdirSync6(dirname8(path), { recursive: true });
|
|
42600
|
+
const fd = openSync(path, "wx");
|
|
42601
|
+
try {
|
|
42602
|
+
writeSync(fd, String(pid));
|
|
42603
|
+
} finally {
|
|
42604
|
+
closeSync(fd);
|
|
42605
|
+
}
|
|
42606
|
+
};
|
|
42607
|
+
var acquirePidLock = (path) => {
|
|
42608
|
+
const ourPid = process.pid;
|
|
42609
|
+
try {
|
|
42610
|
+
writeExclusive(path, ourPid);
|
|
42611
|
+
return { ok: true };
|
|
42612
|
+
} catch (e) {
|
|
42613
|
+
if (e?.code !== "EEXIST")
|
|
42614
|
+
throw e;
|
|
42615
|
+
}
|
|
42616
|
+
const raw = (() => {
|
|
42617
|
+
try {
|
|
42618
|
+
return readFileSync7(path, "utf8").trim();
|
|
42619
|
+
} catch {
|
|
42620
|
+
return "";
|
|
42621
|
+
}
|
|
42622
|
+
})();
|
|
42623
|
+
const existingPid = Number(raw);
|
|
42624
|
+
if (existingPid === ourPid)
|
|
42625
|
+
return { ok: true };
|
|
42626
|
+
if (Number.isFinite(existingPid) && existingPid > 0 && isAlive(existingPid)) {
|
|
42627
|
+
return { ok: false, existingPid };
|
|
42628
|
+
}
|
|
42629
|
+
try {
|
|
42630
|
+
unlinkSync3(path);
|
|
42631
|
+
} catch {}
|
|
42632
|
+
try {
|
|
42633
|
+
writeExclusive(path, ourPid);
|
|
42634
|
+
return { ok: true };
|
|
42635
|
+
} catch (e) {
|
|
42636
|
+
if (e?.code !== "EEXIST")
|
|
42637
|
+
throw e;
|
|
42638
|
+
const racingRaw = (() => {
|
|
42639
|
+
try {
|
|
42640
|
+
return readFileSync7(path, "utf8").trim();
|
|
42641
|
+
} catch {
|
|
42642
|
+
return "";
|
|
42643
|
+
}
|
|
42644
|
+
})();
|
|
42645
|
+
const racingPid = Number(racingRaw);
|
|
42646
|
+
return { ok: false, existingPid: Number.isFinite(racingPid) ? racingPid : 0 };
|
|
42647
|
+
}
|
|
42648
|
+
};
|
|
42649
|
+
var releasePidLock = (path) => {
|
|
42650
|
+
try {
|
|
42651
|
+
if (!existsSync7(path))
|
|
42652
|
+
return;
|
|
42653
|
+
const raw = readFileSync7(path, "utf8").trim();
|
|
42654
|
+
if (Number(raw) !== process.pid)
|
|
42655
|
+
return;
|
|
42656
|
+
unlinkSync3(path);
|
|
42657
|
+
} catch {}
|
|
42658
|
+
};
|
|
42659
|
+
var killStaleConnects = () => {
|
|
42660
|
+
const self2 = process.pid;
|
|
42661
|
+
const parent = process.ppid;
|
|
42662
|
+
const out = (() => {
|
|
42663
|
+
try {
|
|
42664
|
+
const r = Bun.spawnSync(["ps", "-A", "-o", "pid=,command="], { stderr: "ignore" });
|
|
42665
|
+
return new TextDecoder().decode(r.stdout);
|
|
42666
|
+
} catch {
|
|
42667
|
+
return "";
|
|
42668
|
+
}
|
|
42669
|
+
})();
|
|
42670
|
+
const victims = [];
|
|
42671
|
+
for (const line of out.split(`
|
|
42672
|
+
`)) {
|
|
42673
|
+
const m = line.trim().match(/^(\d+)\s+(.*)$/);
|
|
42674
|
+
if (!m)
|
|
42675
|
+
continue;
|
|
42676
|
+
const pid = Number(m[1]);
|
|
42677
|
+
const cmd = m[2];
|
|
42678
|
+
if (!Number.isFinite(pid) || pid === self2 || pid === parent)
|
|
42679
|
+
continue;
|
|
42680
|
+
if (!/@shipers-dev\/multi\/dist\/index\.js/.test(cmd) && !/multi-agent/.test(cmd))
|
|
42681
|
+
continue;
|
|
42682
|
+
if (!/\bconnect\b/.test(cmd))
|
|
42683
|
+
continue;
|
|
42684
|
+
victims.push(pid);
|
|
42685
|
+
}
|
|
42686
|
+
for (const pid of victims) {
|
|
42687
|
+
try {
|
|
42688
|
+
process.kill(pid, "SIGTERM");
|
|
42689
|
+
} catch {}
|
|
42690
|
+
}
|
|
42691
|
+
if (victims.length === 0)
|
|
42692
|
+
return victims;
|
|
42693
|
+
const deadline = Date.now() + 1500;
|
|
42694
|
+
while (Date.now() < deadline) {
|
|
42695
|
+
if (!victims.some((p) => isAlive(p)))
|
|
42696
|
+
break;
|
|
42697
|
+
Bun.sleepSync(100);
|
|
42698
|
+
}
|
|
42699
|
+
for (const pid of victims) {
|
|
42700
|
+
if (isAlive(pid)) {
|
|
42701
|
+
try {
|
|
42702
|
+
process.kill(pid, "SIGKILL");
|
|
42703
|
+
} catch {}
|
|
42704
|
+
}
|
|
42705
|
+
}
|
|
42706
|
+
return victims;
|
|
42707
|
+
};
|
|
42708
|
+
|
|
42709
|
+
// src/commands/restart.ts
|
|
42566
42710
|
var isRunning4 = (pid) => {
|
|
42567
42711
|
try {
|
|
42568
42712
|
process.kill(pid, 0);
|
|
@@ -42574,8 +42718,8 @@ var isRunning4 = (pid) => {
|
|
|
42574
42718
|
var restartCmd = exports_Effect.fn("restartCmd")(function* (apiUrl) {
|
|
42575
42719
|
const fs3 = yield* FileSystem;
|
|
42576
42720
|
yield* (yield* Config4).ensureDirs;
|
|
42577
|
-
if (
|
|
42578
|
-
const pid = Number(
|
|
42721
|
+
if (existsSync8(PID_PATH)) {
|
|
42722
|
+
const pid = Number(readFileSync8(PID_PATH, "utf8").trim());
|
|
42579
42723
|
if (pid && isRunning4(pid)) {
|
|
42580
42724
|
yield* fs3.writeText(STOP_PATH, "1");
|
|
42581
42725
|
try {
|
|
@@ -42594,14 +42738,17 @@ var restartCmd = exports_Effect.fn("restartCmd")(function* (apiUrl) {
|
|
|
42594
42738
|
}
|
|
42595
42739
|
}
|
|
42596
42740
|
try {
|
|
42597
|
-
if (
|
|
42598
|
-
|
|
42741
|
+
if (existsSync8(PID_PATH))
|
|
42742
|
+
unlinkSync4(PID_PATH);
|
|
42599
42743
|
} catch {}
|
|
42600
42744
|
}
|
|
42601
42745
|
try {
|
|
42602
|
-
if (
|
|
42603
|
-
|
|
42746
|
+
if (existsSync8(STOP_PATH))
|
|
42747
|
+
unlinkSync4(STOP_PATH);
|
|
42604
42748
|
} catch {}
|
|
42749
|
+
const killed = killStaleConnects();
|
|
42750
|
+
if (killed.length)
|
|
42751
|
+
console.log(`⏹ Killed stale daemons: ${killed.join(",")}`);
|
|
42605
42752
|
console.log("\uD83D\uDD04 Relaunching daemon...");
|
|
42606
42753
|
const args2 = Bun.argv.slice(1).filter((a) => a !== "-d" && a !== "--detach" && a !== "restart");
|
|
42607
42754
|
if (!args2.includes("connect"))
|
|
@@ -42693,7 +42840,7 @@ init_client();
|
|
|
42693
42840
|
init_detect();
|
|
42694
42841
|
init_run_task();
|
|
42695
42842
|
import { Database as Database3 } from "bun:sqlite";
|
|
42696
|
-
import { existsSync as
|
|
42843
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync10, writeFileSync as writeFileSync8, unlinkSync as unlinkSync6 } from "fs";
|
|
42697
42844
|
import { homedir as homedir2 } from "os";
|
|
42698
42845
|
import { join as join13 } from "path";
|
|
42699
42846
|
init_errors();
|
|
@@ -42703,11 +42850,11 @@ var PORT_PATH2 = join13(MULTI_DIR5, "agent.port");
|
|
|
42703
42850
|
var STOP_PATH2 = join13(MULTI_DIR5, "stop.flag");
|
|
42704
42851
|
var TASKS_DB_PATH3 = join13(MULTI_DIR5, "tasks.db");
|
|
42705
42852
|
function ensureDirs2() {
|
|
42706
|
-
if (!
|
|
42707
|
-
|
|
42853
|
+
if (!existsSync13(MULTI_DIR5))
|
|
42854
|
+
mkdirSync10(MULTI_DIR5, { recursive: true });
|
|
42708
42855
|
const logs = join13(MULTI_DIR5, "logs");
|
|
42709
|
-
if (!
|
|
42710
|
-
|
|
42856
|
+
if (!existsSync13(logs))
|
|
42857
|
+
mkdirSync10(logs, { recursive: true });
|
|
42711
42858
|
}
|
|
42712
42859
|
function isLocalApi(url2) {
|
|
42713
42860
|
try {
|
|
@@ -42886,8 +43033,8 @@ async function ackDispatch(apiUrl, wsId, dispatchId, secret) {
|
|
|
42886
43033
|
log3(`ack dispatch ${dispatchId} failed: ${String(e)}`);
|
|
42887
43034
|
}
|
|
42888
43035
|
}
|
|
42889
|
-
async function drainOfflineDispatches(apiUrl, deviceId, wsId, secret, db2, onEnqueued,
|
|
42890
|
-
if (!
|
|
43036
|
+
async function drainOfflineDispatches(apiUrl, deviceId, wsId, secret, db2, onEnqueued, isAlive2) {
|
|
43037
|
+
if (!isAlive2())
|
|
42891
43038
|
return;
|
|
42892
43039
|
let list;
|
|
42893
43040
|
try {
|
|
@@ -42901,7 +43048,7 @@ async function drainOfflineDispatches(apiUrl, deviceId, wsId, secret, db2, onEnq
|
|
|
42901
43048
|
return;
|
|
42902
43049
|
log3(`draining ${rows.length} offline dispatch(es)`);
|
|
42903
43050
|
for (const r of rows) {
|
|
42904
|
-
if (!
|
|
43051
|
+
if (!isAlive2()) {
|
|
42905
43052
|
log3("drain aborted: shutting down");
|
|
42906
43053
|
return;
|
|
42907
43054
|
}
|
|
@@ -42999,8 +43146,8 @@ var daemonProgram = ({ cfg, apiUrl }) => exports_Effect.gen(function* () {
|
|
|
42999
43146
|
if (cfg.authToken)
|
|
43000
43147
|
setAuthToken(cfg.authToken);
|
|
43001
43148
|
ensureDirs2();
|
|
43002
|
-
if (
|
|
43003
|
-
|
|
43149
|
+
if (existsSync13(STOP_PATH2))
|
|
43150
|
+
unlinkSync6(STOP_PATH2);
|
|
43004
43151
|
const detected = yield* exports_Effect.promise(() => detectAgents());
|
|
43005
43152
|
const localMode = isLocalApi(apiUrl);
|
|
43006
43153
|
log3(`daemon device=${cfg.deviceId} pid=${process.pid}`);
|
|
@@ -43162,7 +43309,7 @@ var daemonProgram = ({ cfg, apiUrl }) => exports_Effect.gen(function* () {
|
|
|
43162
43309
|
let probeFailures = 0;
|
|
43163
43310
|
while (true) {
|
|
43164
43311
|
yield* exports_Effect.sleep(exports_Duration.seconds(120));
|
|
43165
|
-
if (
|
|
43312
|
+
if (existsSync13(STOP_PATH2)) {
|
|
43166
43313
|
yield* exports_Deferred.succeed(stopDeferred, "stop flag");
|
|
43167
43314
|
return;
|
|
43168
43315
|
}
|
|
@@ -43187,7 +43334,7 @@ var daemonProgram = ({ cfg, apiUrl }) => exports_Effect.gen(function* () {
|
|
|
43187
43334
|
yield* exports_Effect.forkIn(daemonScope)(exports_Effect.gen(function* () {
|
|
43188
43335
|
while (true) {
|
|
43189
43336
|
yield* exports_Effect.sleep(exports_Duration.seconds(5));
|
|
43190
|
-
if (
|
|
43337
|
+
if (existsSync13(STOP_PATH2)) {
|
|
43191
43338
|
yield* exports_Deferred.succeed(stopDeferred, "stop flag");
|
|
43192
43339
|
return;
|
|
43193
43340
|
}
|
|
@@ -43224,12 +43371,12 @@ var daemonProgram = ({ cfg, apiUrl }) => exports_Effect.gen(function* () {
|
|
|
43224
43371
|
yield* exports_Effect.race(exports_Fiber.interruptAll(inFlight), exports_Effect.sleep(exports_Duration.seconds(10)));
|
|
43225
43372
|
}
|
|
43226
43373
|
yield* exports_Scope.close(daemonScope, exports_Exit.void).pipe(exports_Effect.catchAll(() => exports_Effect.void));
|
|
43227
|
-
if (
|
|
43228
|
-
|
|
43229
|
-
if (
|
|
43230
|
-
|
|
43231
|
-
if (
|
|
43232
|
-
|
|
43374
|
+
if (existsSync13(PID_PATH2))
|
|
43375
|
+
unlinkSync6(PID_PATH2);
|
|
43376
|
+
if (existsSync13(STOP_PATH2))
|
|
43377
|
+
unlinkSync6(STOP_PATH2);
|
|
43378
|
+
if (existsSync13(PORT_PATH2))
|
|
43379
|
+
unlinkSync6(PORT_PATH2);
|
|
43233
43380
|
db2.close();
|
|
43234
43381
|
log3("disconnected");
|
|
43235
43382
|
});
|
|
@@ -43241,81 +43388,6 @@ async function daemonMain(opts) {
|
|
|
43241
43388
|
}
|
|
43242
43389
|
}
|
|
43243
43390
|
|
|
43244
|
-
// src/_impl/pid-lock.ts
|
|
43245
|
-
import { closeSync, existsSync as existsSync13, mkdirSync as mkdirSync10, openSync, readFileSync as readFileSync10, unlinkSync as unlinkSync6, writeSync } from "fs";
|
|
43246
|
-
import { dirname as dirname10 } from "path";
|
|
43247
|
-
var isAlive = (pid) => {
|
|
43248
|
-
if (!Number.isFinite(pid) || pid <= 0)
|
|
43249
|
-
return false;
|
|
43250
|
-
try {
|
|
43251
|
-
process.kill(pid, 0);
|
|
43252
|
-
return true;
|
|
43253
|
-
} catch (e) {
|
|
43254
|
-
return e?.code === "EPERM";
|
|
43255
|
-
}
|
|
43256
|
-
};
|
|
43257
|
-
var writeExclusive = (path, pid) => {
|
|
43258
|
-
mkdirSync10(dirname10(path), { recursive: true });
|
|
43259
|
-
const fd = openSync(path, "wx");
|
|
43260
|
-
try {
|
|
43261
|
-
writeSync(fd, String(pid));
|
|
43262
|
-
} finally {
|
|
43263
|
-
closeSync(fd);
|
|
43264
|
-
}
|
|
43265
|
-
};
|
|
43266
|
-
var acquirePidLock = (path) => {
|
|
43267
|
-
const ourPid = process.pid;
|
|
43268
|
-
try {
|
|
43269
|
-
writeExclusive(path, ourPid);
|
|
43270
|
-
return { ok: true };
|
|
43271
|
-
} catch (e) {
|
|
43272
|
-
if (e?.code !== "EEXIST")
|
|
43273
|
-
throw e;
|
|
43274
|
-
}
|
|
43275
|
-
const raw = (() => {
|
|
43276
|
-
try {
|
|
43277
|
-
return readFileSync10(path, "utf8").trim();
|
|
43278
|
-
} catch {
|
|
43279
|
-
return "";
|
|
43280
|
-
}
|
|
43281
|
-
})();
|
|
43282
|
-
const existingPid = Number(raw);
|
|
43283
|
-
if (existingPid === ourPid)
|
|
43284
|
-
return { ok: true };
|
|
43285
|
-
if (Number.isFinite(existingPid) && existingPid > 0 && isAlive(existingPid)) {
|
|
43286
|
-
return { ok: false, existingPid };
|
|
43287
|
-
}
|
|
43288
|
-
try {
|
|
43289
|
-
unlinkSync6(path);
|
|
43290
|
-
} catch {}
|
|
43291
|
-
try {
|
|
43292
|
-
writeExclusive(path, ourPid);
|
|
43293
|
-
return { ok: true };
|
|
43294
|
-
} catch (e) {
|
|
43295
|
-
if (e?.code !== "EEXIST")
|
|
43296
|
-
throw e;
|
|
43297
|
-
const racingRaw = (() => {
|
|
43298
|
-
try {
|
|
43299
|
-
return readFileSync10(path, "utf8").trim();
|
|
43300
|
-
} catch {
|
|
43301
|
-
return "";
|
|
43302
|
-
}
|
|
43303
|
-
})();
|
|
43304
|
-
const racingPid = Number(racingRaw);
|
|
43305
|
-
return { ok: false, existingPid: Number.isFinite(racingPid) ? racingPid : 0 };
|
|
43306
|
-
}
|
|
43307
|
-
};
|
|
43308
|
-
var releasePidLock = (path) => {
|
|
43309
|
-
try {
|
|
43310
|
-
if (!existsSync13(path))
|
|
43311
|
-
return;
|
|
43312
|
-
const raw = readFileSync10(path, "utf8").trim();
|
|
43313
|
-
if (Number(raw) !== process.pid)
|
|
43314
|
-
return;
|
|
43315
|
-
unlinkSync6(path);
|
|
43316
|
-
} catch {}
|
|
43317
|
-
};
|
|
43318
|
-
|
|
43319
43391
|
// src/commands/connect.ts
|
|
43320
43392
|
init_outbox();
|
|
43321
43393
|
init_paths();
|
|
@@ -43334,6 +43406,9 @@ var connectCmd = exports_Effect.fn("connectCmd")(function* () {
|
|
|
43334
43406
|
}
|
|
43335
43407
|
if (cfg.authToken)
|
|
43336
43408
|
yield* api2.setAuthToken(cfg.authToken);
|
|
43409
|
+
const killed = killStaleConnects();
|
|
43410
|
+
if (killed.length)
|
|
43411
|
+
yield* logger.log(`connect: killed stale daemons ${killed.join(",")}`);
|
|
43337
43412
|
if (process.env.MULTI_FORCE_CONNECT !== "1") {
|
|
43338
43413
|
const lock = acquirePidLock(PID_PATH);
|
|
43339
43414
|
if (!lock.ok) {
|