@4pm/cli 1.5.2 → 1.5.11-b
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 +322 -181
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -28834,18 +28834,21 @@ function isValidRoleSet(roles) {
|
|
|
28834
28834
|
|
|
28835
28835
|
// vendor/@4pm/constants/worker-tools.ts
|
|
28836
28836
|
var WORKER_TOOL_CATALOG = [
|
|
28837
|
-
{ id: "git", label: "Git", category: "vcs", versionArg: "--version", installPackage: null, installable: false },
|
|
28838
|
-
{ id: "node", label: "Node.js", category: "runtime", versionArg: "--version", installPackage: null, installable: false },
|
|
28839
|
-
{ id: "npm", label: "npm", category: "runtime", versionArg: "--version", installPackage: null, installable: false },
|
|
28840
|
-
{ id: "pnpm", label: "pnpm", category: "runtime", versionArg: "--version", installPackage: "pnpm", installable: false },
|
|
28841
|
-
{ id: "claude", label: "Claude Code", category: "ai-cli", versionArg: "--version", installPackage: "@anthropic-ai/claude-code", installable: false },
|
|
28842
|
-
{ id: "codex", label: "Codex", category: "ai-cli", versionArg: "--version", installPackage: "@openai/codex", installable: false },
|
|
28843
|
-
{ id: "gh", label: "GitHub CLI", category: "vcs", versionArg: "--version", installPackage: null, installable: false },
|
|
28844
|
-
{ id: "glab", label: "GitLab CLI", category: "vcs", versionArg: "--version", installPackage: null, installable: false }
|
|
28837
|
+
{ id: "git", label: "Git", category: "vcs", versionArg: "--version", installPackage: null, installable: false, updatable: false },
|
|
28838
|
+
{ id: "node", label: "Node.js", category: "runtime", versionArg: "--version", installPackage: null, installable: false, updatable: false },
|
|
28839
|
+
{ id: "npm", label: "npm", category: "runtime", versionArg: "--version", installPackage: null, installable: false, updatable: false },
|
|
28840
|
+
{ id: "pnpm", label: "pnpm", category: "runtime", versionArg: "--version", installPackage: "pnpm", installable: false, updatable: true },
|
|
28841
|
+
{ id: "claude", label: "Claude Code", category: "ai-cli", versionArg: "--version", installPackage: "@anthropic-ai/claude-code", installable: false, updatable: true },
|
|
28842
|
+
{ id: "codex", label: "Codex", category: "ai-cli", versionArg: "--version", installPackage: "@openai/codex", installable: false, updatable: true },
|
|
28843
|
+
{ id: "gh", label: "GitHub CLI", category: "vcs", versionArg: "--version", installPackage: null, installable: false, updatable: false },
|
|
28844
|
+
{ id: "glab", label: "GitLab CLI", category: "vcs", versionArg: "--version", installPackage: null, installable: false, updatable: false }
|
|
28845
28845
|
];
|
|
28846
28846
|
var WORKER_TOOL_PREREQUISITE_IDS = WORKER_TOOL_CATALOG.filter(
|
|
28847
28847
|
(t) => !t.installable
|
|
28848
28848
|
).map((t) => t.id);
|
|
28849
|
+
var WORKER_TOOL_UPDATABLE_IDS = WORKER_TOOL_CATALOG.filter(
|
|
28850
|
+
(t) => t.updatable
|
|
28851
|
+
).map((t) => t.id);
|
|
28849
28852
|
var WORKER_TOOL_BUNDLED_GLOBALS = ["npm", "corepack"];
|
|
28850
28853
|
|
|
28851
28854
|
// vendor/@4pm/constants/ws.ts
|
|
@@ -32909,7 +32912,7 @@ var deletedFilterSchema = external_exports.enum(["true", "false"]).transform((v)
|
|
|
32909
32912
|
|
|
32910
32913
|
// vendor/@4pm/dto/command.ts
|
|
32911
32914
|
var COMMAND_MAX_LEN = 8e3;
|
|
32912
|
-
var AI_PROMPT_MAX_LEN =
|
|
32915
|
+
var AI_PROMPT_MAX_LEN = 5e5;
|
|
32913
32916
|
var dispatchCommandRequestSchema = external_exports.object({
|
|
32914
32917
|
projectId: external_exports.string().uuid(),
|
|
32915
32918
|
// Target cli. Required unless `pick` is set — with `pick:"idle"` the server chooses the
|
|
@@ -33065,12 +33068,24 @@ var listMachineLinksQuerySchema = baseRequestSchema.extend({
|
|
|
33065
33068
|
var updateMachineLinkRequestSchema = external_exports.object({
|
|
33066
33069
|
hashcode3ExpiresAt: external_exports.string().datetime().nullable()
|
|
33067
33070
|
});
|
|
33071
|
+
var workerToolCopySchema = external_exports.object({
|
|
33072
|
+
/** Source machine-link id whose `toolSnapshot` supplies the manifest. */
|
|
33073
|
+
sourceLinkId: external_exports.string().uuid(),
|
|
33074
|
+
/** Target machine-link ids to queue the manifest for (pushed on their next connect). */
|
|
33075
|
+
targetLinkIds: external_exports.array(external_exports.string().uuid()).min(1).max(200)
|
|
33076
|
+
});
|
|
33068
33077
|
var workerToolInstallSchema = external_exports.object({
|
|
33069
33078
|
/** Catalog id or npm package name (validated npm-name shape on the cli). */
|
|
33070
33079
|
name: external_exports.string().trim().min(1).max(214),
|
|
33071
33080
|
/** Package manager to run the global install with. */
|
|
33072
33081
|
manager: external_exports.enum(["npm", "pnpm"])
|
|
33073
33082
|
});
|
|
33083
|
+
var workerToolAutoUpdateSchema = external_exports.object({
|
|
33084
|
+
/** Catalog id or installed extra's npm package name. */
|
|
33085
|
+
name: external_exports.string().trim().min(1).max(214),
|
|
33086
|
+
/** true = flag this tool for auto-update to @latest; false = clear it. */
|
|
33087
|
+
enabled: external_exports.boolean()
|
|
33088
|
+
});
|
|
33074
33089
|
var configWriteRequestSchema = external_exports.object({
|
|
33075
33090
|
config: external_exports.string().max(256 * 1024)
|
|
33076
33091
|
});
|
|
@@ -33675,8 +33690,27 @@ var WsChannels = {
|
|
|
33675
33690
|
TOOLS_LIST: "tools.list",
|
|
33676
33691
|
TOOLS_INSTALL: "tools.install",
|
|
33677
33692
|
TOOLS_UNINSTALL: "tools.uninstall",
|
|
33693
|
+
// update-to-latest of an npm-distributed catalog tool or an extra (ADR-0252): same streamed
|
|
33694
|
+
// shape as install/uninstall — the cli reinstalls the package at `@latest`.
|
|
33695
|
+
TOOLS_UPDATE: "tools.update",
|
|
33678
33696
|
TOOLS_PROGRESS: "tools.progress",
|
|
33679
33697
|
TOOLS_DONE: "tools.done",
|
|
33698
|
+
// toggle per-tool auto-update (ADR-0253): request/reply — the cli persists the flag in
|
|
33699
|
+
// config.json (`autoUpdateTools`); the ADR-0074 idle daily tick later runs the flagged tools.
|
|
33700
|
+
// (ADR-0254: the flag is now persisted to the DB `toolSnapshot`; this channel is repurposed —
|
|
33701
|
+
// machine-0056 forwards it to an ONLINE worker only, to update its local config.json mirror
|
|
33702
|
+
// immediately instead of waiting for the next ws_token seed.)
|
|
33703
|
+
TOOLS_AUTOUPDATE: "tools.autoUpdate",
|
|
33704
|
+
// cli → server one-way (ADR-0254): report the worker's detected tool snapshot — the server
|
|
33705
|
+
// persists it to `MachineLink.toolSnapshot` for the DB-backed Tools panel + restore-on-boot.
|
|
33706
|
+
// Sent on the ADR-0074 daily idle tick, after each install/uninstall/update op, and on boot
|
|
33707
|
+
// AFTER restore completes. The read/restore direction rides `ws_token` (seeds the cli).
|
|
33708
|
+
TOOLS_REPORT: "tools.report",
|
|
33709
|
+
// server → cli (reply, ADR-0254): push a tool manifest to reconcile NOW — used by "copy tools"
|
|
33710
|
+
// to apply immediately to an online target (the offline path rides `ws_token.toolRestore` on the
|
|
33711
|
+
// next connect). The cli installs each missing/mismatched `name@version`, then reports; the reply
|
|
33712
|
+
// says whether it applied so the server can clear the pending manifest.
|
|
33713
|
+
TOOLS_RESTORE: "tools.restore",
|
|
33680
33714
|
// autonomous mode control (ADR-0152): read settings+status+books+approvals, tail logs, and
|
|
33681
33715
|
// write {settings|approvals|userTodo|cron} — the dashboard drives the worker's autonomous engine.
|
|
33682
33716
|
AUTONOMOUS_READ: "autonomous.read",
|
|
@@ -33837,7 +33871,7 @@ import { existsSync as existsSync13, mkdirSync as mkdirSync5, rmSync as rmSync4
|
|
|
33837
33871
|
import { randomUUID as randomCommandId } from "crypto";
|
|
33838
33872
|
|
|
33839
33873
|
// src/core/update-scheduler.ts
|
|
33840
|
-
import { spawn } from "child_process";
|
|
33874
|
+
import { spawn as spawn2 } from "child_process";
|
|
33841
33875
|
|
|
33842
33876
|
// src/common/logger/logger.ts
|
|
33843
33877
|
import { appendFile, mkdir, readdir, unlink } from "fs/promises";
|
|
@@ -33942,17 +33976,191 @@ ${content.slice(-LOG_UPLOAD_CONTENT_MAX)}`;
|
|
|
33942
33976
|
}
|
|
33943
33977
|
}
|
|
33944
33978
|
|
|
33979
|
+
// src/core/worker-tools.ts
|
|
33980
|
+
import { spawn } from "child_process";
|
|
33981
|
+
var NPM_NAME = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
|
|
33982
|
+
function run(cmd, args, onLine, timeoutMs = 18e4) {
|
|
33983
|
+
return new Promise((resolve8) => {
|
|
33984
|
+
let out = "";
|
|
33985
|
+
let buf = "";
|
|
33986
|
+
let child = null;
|
|
33987
|
+
const done = (code) => {
|
|
33988
|
+
clearTimeout(timer);
|
|
33989
|
+
if (onLine && buf.trim()) onLine(buf.trim());
|
|
33990
|
+
resolve8({ code, out });
|
|
33991
|
+
};
|
|
33992
|
+
const timer = setTimeout(() => {
|
|
33993
|
+
child?.kill();
|
|
33994
|
+
if (onLine) onLine(`timed out after ${Math.round(timeoutMs / 1e3)}s`);
|
|
33995
|
+
done(124);
|
|
33996
|
+
}, timeoutMs);
|
|
33997
|
+
timer.unref?.();
|
|
33998
|
+
const feed = (d) => {
|
|
33999
|
+
const text = d.toString("utf8");
|
|
34000
|
+
out += text;
|
|
34001
|
+
if (!onLine) return;
|
|
34002
|
+
buf += text;
|
|
34003
|
+
let nl;
|
|
34004
|
+
while ((nl = buf.indexOf("\n")) >= 0) {
|
|
34005
|
+
const line = buf.slice(0, nl).replace(/\r$/, "");
|
|
34006
|
+
buf = buf.slice(nl + 1);
|
|
34007
|
+
onLine(line);
|
|
34008
|
+
}
|
|
34009
|
+
};
|
|
34010
|
+
try {
|
|
34011
|
+
child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
34012
|
+
child.stdout?.on("data", feed);
|
|
34013
|
+
child.stderr?.on("data", feed);
|
|
34014
|
+
child.on("exit", (code) => done(code ?? 0));
|
|
34015
|
+
child.on("error", () => done(127));
|
|
34016
|
+
} catch {
|
|
34017
|
+
done(127);
|
|
34018
|
+
}
|
|
34019
|
+
});
|
|
34020
|
+
}
|
|
34021
|
+
async function detectOne(cmd, versionArg) {
|
|
34022
|
+
const { code, out } = await run(cmd, [versionArg], void 0, 15e3);
|
|
34023
|
+
if (code !== 0) return null;
|
|
34024
|
+
const first = out.split("\n").map((l) => l.trim()).find(Boolean);
|
|
34025
|
+
return first ?? "";
|
|
34026
|
+
}
|
|
34027
|
+
async function listExtras(autoUpdateSet = /* @__PURE__ */ new Set()) {
|
|
34028
|
+
const { out } = await run("npm", ["ls", "-g", "--depth=0", "--json"], void 0, 2e4);
|
|
34029
|
+
if (!out.trim()) return [];
|
|
34030
|
+
let deps = {};
|
|
34031
|
+
try {
|
|
34032
|
+
deps = JSON.parse(out).dependencies ?? {};
|
|
34033
|
+
} catch {
|
|
34034
|
+
return [];
|
|
34035
|
+
}
|
|
34036
|
+
const catalogPkgs = new Set(WORKER_TOOL_BUNDLED_GLOBALS);
|
|
34037
|
+
for (const t of WORKER_TOOL_CATALOG) {
|
|
34038
|
+
catalogPkgs.add(t.id);
|
|
34039
|
+
if (t.installPackage) catalogPkgs.add(t.installPackage);
|
|
34040
|
+
}
|
|
34041
|
+
return Object.entries(deps).filter(([name]) => !catalogPkgs.has(name)).map(([name, info]) => ({
|
|
34042
|
+
id: name,
|
|
34043
|
+
label: name,
|
|
34044
|
+
category: "runtime",
|
|
34045
|
+
installed: true,
|
|
34046
|
+
version: info.version ?? null,
|
|
34047
|
+
installable: true,
|
|
34048
|
+
updatable: true,
|
|
34049
|
+
autoUpdate: autoUpdateSet.has(name)
|
|
34050
|
+
}));
|
|
34051
|
+
}
|
|
34052
|
+
async function detectWorkerTools(autoUpdateTools = []) {
|
|
34053
|
+
const autoUpdateSet = new Set(autoUpdateTools);
|
|
34054
|
+
const catalog = await Promise.all(
|
|
34055
|
+
WORKER_TOOL_CATALOG.map(async (t) => {
|
|
34056
|
+
const version = await detectOne(t.id, t.versionArg);
|
|
34057
|
+
return {
|
|
34058
|
+
id: t.id,
|
|
34059
|
+
label: t.label,
|
|
34060
|
+
category: t.category,
|
|
34061
|
+
installed: version !== null,
|
|
34062
|
+
version,
|
|
34063
|
+
installable: t.installable,
|
|
34064
|
+
updatable: t.updatable,
|
|
34065
|
+
autoUpdate: autoUpdateSet.has(t.id)
|
|
34066
|
+
};
|
|
34067
|
+
})
|
|
34068
|
+
);
|
|
34069
|
+
const extras = await listExtras(autoUpdateSet).catch(() => []);
|
|
34070
|
+
return { catalog, extras };
|
|
34071
|
+
}
|
|
34072
|
+
function opPast(op) {
|
|
34073
|
+
return op === "install" ? "installed" : op === "uninstall" ? "uninstalled" : "updated";
|
|
34074
|
+
}
|
|
34075
|
+
function resolvePackage(name, op) {
|
|
34076
|
+
const entry = WORKER_TOOL_CATALOG.find((t) => t.id === name);
|
|
34077
|
+
if (entry) {
|
|
34078
|
+
const allowed = op === "update" ? entry.updatable : entry.installable;
|
|
34079
|
+
if (!allowed) {
|
|
34080
|
+
return { error: `"${name}" is a prerequisite and cannot be ${opPast(op)} from here.` };
|
|
34081
|
+
}
|
|
34082
|
+
return { pkg: entry.installPackage ?? name };
|
|
34083
|
+
}
|
|
34084
|
+
if (WORKER_TOOL_PREREQUISITE_IDS.includes(name)) {
|
|
34085
|
+
return { error: `"${name}" is a prerequisite and cannot be ${opPast(op)} from here.` };
|
|
34086
|
+
}
|
|
34087
|
+
if (WORKER_TOOL_BUNDLED_GLOBALS.includes(name)) {
|
|
34088
|
+
return { error: `"${name}" ships with Node and cannot be ${opPast(op)} from here.` };
|
|
34089
|
+
}
|
|
34090
|
+
if (!NPM_NAME.test(name)) return { error: `Invalid package name: "${name}".` };
|
|
34091
|
+
return { pkg: name };
|
|
34092
|
+
}
|
|
34093
|
+
function opArgs(op, manager, pkg) {
|
|
34094
|
+
if (op === "uninstall") return manager === "pnpm" ? ["remove", "-g", pkg] : ["uninstall", "-g", pkg];
|
|
34095
|
+
const target = op === "update" ? `${pkg}@latest` : pkg;
|
|
34096
|
+
return manager === "pnpm" ? ["add", "-g", target] : ["install", "-g", target];
|
|
34097
|
+
}
|
|
34098
|
+
async function runWorkerToolOp(op, name, manager, onLine) {
|
|
34099
|
+
const resolved = resolvePackage(name, op);
|
|
34100
|
+
if (resolved.error || !resolved.pkg) {
|
|
34101
|
+
return { ok: false, exitCode: 1, error: resolved.error ?? "Invalid package." };
|
|
34102
|
+
}
|
|
34103
|
+
onLine(`$ ${manager} ${opArgs(op, manager, resolved.pkg).join(" ")}`);
|
|
34104
|
+
const { code } = await run(manager, opArgs(op, manager, resolved.pkg), onLine);
|
|
34105
|
+
return { ok: code === 0, exitCode: code, error: code === 0 ? void 0 : `${manager} exited ${code}` };
|
|
34106
|
+
}
|
|
34107
|
+
function setToolAutoUpdate(profileDir2, name, enabled) {
|
|
34108
|
+
const resolved = resolvePackage(name, "update");
|
|
34109
|
+
if (resolved.error) return { ok: false, error: resolved.error };
|
|
34110
|
+
const current = readProfileConfig(profileDir2).autoUpdateTools ?? [];
|
|
34111
|
+
const set = new Set(current);
|
|
34112
|
+
if (enabled) set.add(name);
|
|
34113
|
+
else set.delete(name);
|
|
34114
|
+
writeProfileConfig(profileDir2, { autoUpdateTools: [...set] });
|
|
34115
|
+
return { ok: true };
|
|
34116
|
+
}
|
|
34117
|
+
async function autoUpdateFlaggedTools(tools, onLine) {
|
|
34118
|
+
for (const name of tools) {
|
|
34119
|
+
const res = await runWorkerToolOp("update", name, "npm", onLine).catch(
|
|
34120
|
+
(err) => ({ ok: false, exitCode: 1, error: String(err) })
|
|
34121
|
+
);
|
|
34122
|
+
onLine(res.ok ? `\u2713 ${name} updated` : `\u2717 ${name}: ${res.error ?? "failed"}`);
|
|
34123
|
+
}
|
|
34124
|
+
}
|
|
34125
|
+
async function installPinned(name, version, manager, onLine) {
|
|
34126
|
+
const resolved = resolvePackage(name, "update");
|
|
34127
|
+
if (resolved.error || !resolved.pkg) return { ok: false, error: resolved.error ?? "Invalid package." };
|
|
34128
|
+
const target = `${resolved.pkg}@${version}`;
|
|
34129
|
+
const args = manager === "pnpm" ? ["add", "-g", target] : ["install", "-g", target];
|
|
34130
|
+
onLine(`$ ${manager} ${args.join(" ")}`);
|
|
34131
|
+
const { code } = await run(manager, args, onLine);
|
|
34132
|
+
return { ok: code === 0, error: code === 0 ? void 0 : `${manager} exited ${code}` };
|
|
34133
|
+
}
|
|
34134
|
+
async function reconcileTools(manifest, onLine) {
|
|
34135
|
+
if (manifest.length === 0) return;
|
|
34136
|
+
const { catalog, extras } = await detectWorkerTools();
|
|
34137
|
+
const versionByName = new Map(
|
|
34138
|
+
[...catalog, ...extras].filter((r) => r.installed && r.version).map((r) => [r.id, r.version])
|
|
34139
|
+
);
|
|
34140
|
+
for (const entry of manifest) {
|
|
34141
|
+
const current = versionByName.get(entry.name);
|
|
34142
|
+
if (current && current.includes(entry.version)) continue;
|
|
34143
|
+
onLine(`Restoring ${entry.name}@${entry.version}\u2026`);
|
|
34144
|
+
const res = await installPinned(entry.name, entry.version, entry.manager, onLine).catch(
|
|
34145
|
+
(err) => ({ ok: false, error: String(err) })
|
|
34146
|
+
);
|
|
34147
|
+
onLine(res.ok ? `\u2713 ${entry.name}@${entry.version}` : `\u2717 ${entry.name}: ${res.error ?? "failed"}`);
|
|
34148
|
+
}
|
|
34149
|
+
}
|
|
34150
|
+
|
|
33945
34151
|
// src/core/update-scheduler.ts
|
|
33946
34152
|
var TICK_MS = 6e4;
|
|
33947
34153
|
var UpdateScheduler = class {
|
|
33948
|
-
constructor(serverUrl, profileDir2, bus) {
|
|
34154
|
+
constructor(serverUrl, profileDir2, bus, onToolsChanged) {
|
|
33949
34155
|
this.serverUrl = serverUrl;
|
|
33950
34156
|
this.profileDir = profileDir2;
|
|
33951
34157
|
this.bus = bus;
|
|
34158
|
+
this.onToolsChanged = onToolsChanged;
|
|
33952
34159
|
}
|
|
33953
34160
|
serverUrl;
|
|
33954
34161
|
profileDir;
|
|
33955
34162
|
bus;
|
|
34163
|
+
onToolsChanged;
|
|
33956
34164
|
policy = null;
|
|
33957
34165
|
timer = null;
|
|
33958
34166
|
/** Date key (YYYY-MM-DD in org tz) whose update already ran — enforces once per day. */
|
|
@@ -34024,6 +34232,7 @@ var UpdateScheduler = class {
|
|
|
34024
34232
|
async runUpdate() {
|
|
34025
34233
|
this.updating = true;
|
|
34026
34234
|
try {
|
|
34235
|
+
await this.updateFlaggedTools();
|
|
34027
34236
|
this.bus.log("Scheduled auto-update: checking for a new version\u2026");
|
|
34028
34237
|
const result = await updateToLatest(this.serverUrl);
|
|
34029
34238
|
if (result.action === "already-latest") {
|
|
@@ -34037,7 +34246,7 @@ var UpdateScheduler = class {
|
|
|
34037
34246
|
}
|
|
34038
34247
|
logger.info("update.scheduled.updated", { version: result.version });
|
|
34039
34248
|
this.bus.log(`Updated to ${result.version} \u2014 restarting to apply\u2026`);
|
|
34040
|
-
const child =
|
|
34249
|
+
const child = spawn2(process.execPath, process.argv.slice(1), {
|
|
34041
34250
|
stdio: "inherit",
|
|
34042
34251
|
env: { ...process.env, FOURPM_NO_UPDATE: "1" }
|
|
34043
34252
|
});
|
|
@@ -34048,16 +34257,26 @@ var UpdateScheduler = class {
|
|
|
34048
34257
|
this.updating = false;
|
|
34049
34258
|
}
|
|
34050
34259
|
}
|
|
34260
|
+
/** Update each tool flagged for auto-update in config.json to @latest (ADR-0253) — best-effort. */
|
|
34261
|
+
async updateFlaggedTools() {
|
|
34262
|
+
const tools = readProfileConfig(this.profileDir).autoUpdateTools ?? [];
|
|
34263
|
+
if (tools.length === 0) return;
|
|
34264
|
+
this.bus.log(`Scheduled tool auto-update: ${tools.join(", ")}\u2026`);
|
|
34265
|
+
await autoUpdateFlaggedTools(tools, (line) => logger.info("update.tool.line", { line })).catch(
|
|
34266
|
+
(err) => logger.warn("update.tool.error", { error: String(err) })
|
|
34267
|
+
);
|
|
34268
|
+
this.onToolsChanged?.();
|
|
34269
|
+
}
|
|
34051
34270
|
};
|
|
34052
34271
|
|
|
34053
34272
|
// src/core/executor.ts
|
|
34054
|
-
import { spawn as
|
|
34273
|
+
import { spawn as spawn3 } from "child_process";
|
|
34055
34274
|
var KILL_GRACE_MS = 5e3;
|
|
34056
34275
|
async function runCommand(dispatch, emit, opts) {
|
|
34057
34276
|
let seq = 0;
|
|
34058
34277
|
let child;
|
|
34059
34278
|
try {
|
|
34060
|
-
child =
|
|
34279
|
+
child = spawn3(dispatch.cmd, dispatch.args ?? [], {
|
|
34061
34280
|
cwd: dispatch.path,
|
|
34062
34281
|
env: { ...process.env, ...dispatch.env },
|
|
34063
34282
|
shell: false
|
|
@@ -34651,7 +34870,7 @@ function planLegacyRun(prompt, config, workingDir, resume, oneShot) {
|
|
|
34651
34870
|
}
|
|
34652
34871
|
|
|
34653
34872
|
// src/core/claude-usage.ts
|
|
34654
|
-
import { spawn as
|
|
34873
|
+
import { spawn as spawn4 } from "child_process";
|
|
34655
34874
|
import { readFileSync as readFileSync9 } from "fs";
|
|
34656
34875
|
import { join as join9 } from "path";
|
|
34657
34876
|
var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
@@ -34698,7 +34917,7 @@ function runUsageCli(dir2, aiCli2) {
|
|
|
34698
34917
|
}, 3e4);
|
|
34699
34918
|
timer.unref?.();
|
|
34700
34919
|
try {
|
|
34701
|
-
child =
|
|
34920
|
+
child = spawn4(aiCli2, ["/usage"], {
|
|
34702
34921
|
env: { ...process.env, CLAUDE_CONFIG_DIR: dir2 },
|
|
34703
34922
|
stdio: ["ignore", "pipe", "ignore"]
|
|
34704
34923
|
});
|
|
@@ -34768,7 +34987,7 @@ async function checkClaudeUsage(dirs, aiCli2 = "claude") {
|
|
|
34768
34987
|
// src/core/ai-assist.ts
|
|
34769
34988
|
import { execFile } from "child_process";
|
|
34770
34989
|
import { promisify } from "util";
|
|
34771
|
-
var
|
|
34990
|
+
var run2 = promisify(execFile);
|
|
34772
34991
|
function aiCli() {
|
|
34773
34992
|
return process.env.AI_CLI || "claude";
|
|
34774
34993
|
}
|
|
@@ -34811,7 +35030,7 @@ function parseAi(cli, stdout, prompt) {
|
|
|
34811
35030
|
}
|
|
34812
35031
|
async function runAi(prompt) {
|
|
34813
35032
|
const cli = aiCli();
|
|
34814
|
-
const { stdout } = await
|
|
35033
|
+
const { stdout } = await run2(cli, aiArgs(cli, prompt), {
|
|
34815
35034
|
timeout: 12e4,
|
|
34816
35035
|
maxBuffer: 8 * 1024 * 1024
|
|
34817
35036
|
});
|
|
@@ -35154,7 +35373,7 @@ async function writeWorkerFile(root, relPath, content) {
|
|
|
35154
35373
|
}
|
|
35155
35374
|
|
|
35156
35375
|
// src/core/support-answer.ts
|
|
35157
|
-
import { spawn as
|
|
35376
|
+
import { spawn as spawn5 } from "child_process";
|
|
35158
35377
|
import { execFile as execFile2 } from "child_process";
|
|
35159
35378
|
import { createHash as createHash3 } from "crypto";
|
|
35160
35379
|
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readdirSync as readdirSync4, readFileSync as readFileSync12, statSync as statSync3 } from "fs";
|
|
@@ -35289,7 +35508,7 @@ function runClaudeOnce(cmd, profile, prompt, extraEnv) {
|
|
|
35289
35508
|
...extraEnv,
|
|
35290
35509
|
...profile ? { CLAUDE_CONFIG_DIR: profile.dir } : {}
|
|
35291
35510
|
};
|
|
35292
|
-
const child =
|
|
35511
|
+
const child = spawn5(cmd, args, { stdio: ["pipe", "pipe", "pipe"], env: env3 });
|
|
35293
35512
|
const parser = createAiStreamParser(cmd);
|
|
35294
35513
|
let out = "";
|
|
35295
35514
|
let err = "";
|
|
@@ -35359,7 +35578,7 @@ async function runSupportAnswer(req, ai, profileDir2) {
|
|
|
35359
35578
|
}
|
|
35360
35579
|
|
|
35361
35580
|
// src/core/knowledge-compose.ts
|
|
35362
|
-
import { spawn as
|
|
35581
|
+
import { spawn as spawn6 } from "child_process";
|
|
35363
35582
|
var COMPOSE_TIMEOUT_MS = 18e4;
|
|
35364
35583
|
function buildPrompt2(req) {
|
|
35365
35584
|
const focus = req.prompt ? `
|
|
@@ -35385,7 +35604,7 @@ function runOnce(cmd, profile, prompt, cwd2, extraEnv) {
|
|
|
35385
35604
|
...extraEnv,
|
|
35386
35605
|
...profile ? { CLAUDE_CONFIG_DIR: profile.dir } : {}
|
|
35387
35606
|
};
|
|
35388
|
-
const child =
|
|
35607
|
+
const child = spawn6(cmd, args, { stdio: ["pipe", "pipe", "pipe"], cwd: cwd2, env: env3 });
|
|
35389
35608
|
let out = "";
|
|
35390
35609
|
let err = "";
|
|
35391
35610
|
const timer = setTimeout(() => {
|
|
@@ -35433,10 +35652,10 @@ async function runKnowledgeCompose(req, ai, physicRoot) {
|
|
|
35433
35652
|
// src/core/autonomous.ts
|
|
35434
35653
|
import { readFile as readFile3, writeFile as writeFile2, chmod } from "fs/promises";
|
|
35435
35654
|
import { readFileSync as readFileSync13 } from "fs";
|
|
35436
|
-
import { execFile as execFile3, spawn as
|
|
35655
|
+
import { execFile as execFile3, spawn as spawn7 } from "child_process";
|
|
35437
35656
|
import { join as join13 } from "path";
|
|
35438
35657
|
import { promisify as promisify3 } from "util";
|
|
35439
|
-
var
|
|
35658
|
+
var run3 = promisify3(execFile3);
|
|
35440
35659
|
var SETTINGS_REL = ".claude/.autonomous.settings.json";
|
|
35441
35660
|
var HISTORIES_REL = ".claude/.autonomous.histories.json";
|
|
35442
35661
|
var APPROVALS_REL = ".claude/.autonomous.approvals.json";
|
|
@@ -35470,7 +35689,7 @@ function tickScript(root) {
|
|
|
35470
35689
|
}
|
|
35471
35690
|
async function crontabList() {
|
|
35472
35691
|
try {
|
|
35473
|
-
const { stdout } = await
|
|
35692
|
+
const { stdout } = await run3("crontab", ["-l"], { timeout: 1e4 });
|
|
35474
35693
|
return stdout;
|
|
35475
35694
|
} catch {
|
|
35476
35695
|
return "";
|
|
@@ -35478,7 +35697,7 @@ async function crontabList() {
|
|
|
35478
35697
|
}
|
|
35479
35698
|
async function crontabSet(content) {
|
|
35480
35699
|
return new Promise((resolve8) => {
|
|
35481
|
-
const child =
|
|
35700
|
+
const child = spawn7("crontab", ["-"], { stdio: ["pipe", "ignore", "ignore"] });
|
|
35482
35701
|
child.on("error", () => resolve8(false));
|
|
35483
35702
|
child.on("close", (code) => resolve8(code === 0));
|
|
35484
35703
|
child.stdin.end(content.endsWith("\n") ? content : content + "\n");
|
|
@@ -36209,15 +36428,15 @@ async function buildGraph(root, mode) {
|
|
|
36209
36428
|
// src/core/rag.ts
|
|
36210
36429
|
import { readFile as readFile9, writeFile as writeFile7, statfs as statfs2, mkdir as mkdir6 } from "fs/promises";
|
|
36211
36430
|
import { existsSync as existsSync10 } from "fs";
|
|
36212
|
-
import { execFile as execFile4, spawn as
|
|
36431
|
+
import { execFile as execFile4, spawn as spawn8 } from "child_process";
|
|
36213
36432
|
import { totalmem as totalmem2, cpus as cpus2 } from "os";
|
|
36214
36433
|
import { join as join20 } from "path";
|
|
36215
36434
|
import { promisify as promisify4 } from "util";
|
|
36216
|
-
var
|
|
36435
|
+
var run4 = promisify4(execFile4);
|
|
36217
36436
|
var RAG_REL = ".claude/rag";
|
|
36218
36437
|
async function tryRun(cmd, args) {
|
|
36219
36438
|
try {
|
|
36220
|
-
const { stdout } = await
|
|
36439
|
+
const { stdout } = await run4(cmd, args, { timeout: 8e3 });
|
|
36221
36440
|
return { ok: true, out: stdout.trim() };
|
|
36222
36441
|
} catch {
|
|
36223
36442
|
return { ok: false, out: "" };
|
|
@@ -36396,7 +36615,7 @@ touch "${RAG_REL}/.indexing"
|
|
|
36396
36615
|
{ echo "[reindex] $(date)"; "$RAG_PY" "${RAG_REL}/rag_engine.py" reindex 2>&1; } >> "${RAG_REL}/index.log" 2>&1
|
|
36397
36616
|
rm -f "${RAG_REL}/.indexing"
|
|
36398
36617
|
`;
|
|
36399
|
-
const child =
|
|
36618
|
+
const child = spawn8("bash", ["-c", script], {
|
|
36400
36619
|
cwd: root,
|
|
36401
36620
|
env: { ...process.env, RAG_PY: py.bin },
|
|
36402
36621
|
detached: true,
|
|
@@ -36411,7 +36630,7 @@ async function ragQuery(root, queryText, k = 8) {
|
|
|
36411
36630
|
if (!existsSync10(join20(root, RAG_REL, "index.db"))) return { results: [], error: "no index \u2014 reindex first" };
|
|
36412
36631
|
await ensureEngine(root);
|
|
36413
36632
|
try {
|
|
36414
|
-
const { stdout } = await
|
|
36633
|
+
const { stdout } = await run4(
|
|
36415
36634
|
py.bin,
|
|
36416
36635
|
[join20(RAG_REL, "rag_engine.py"), "query", queryText, String(Math.min(Math.max(k, 1), 20))],
|
|
36417
36636
|
{ cwd: root, timeout: 3e4, maxBuffer: 4 * 1024 * 1024 }
|
|
@@ -36446,7 +36665,7 @@ rm -f "${RAG_REL}/.installed"
|
|
|
36446
36665
|
} >> "${RAG_REL}/install.log" 2>&1
|
|
36447
36666
|
rm -f "${RAG_REL}/.installing"
|
|
36448
36667
|
`;
|
|
36449
|
-
const child =
|
|
36668
|
+
const child = spawn8("bash", ["-c", script], {
|
|
36450
36669
|
cwd: root,
|
|
36451
36670
|
env: { ...process.env, RAG_PY: py.bin, RAG_MODEL: model },
|
|
36452
36671
|
detached: true,
|
|
@@ -36461,7 +36680,7 @@ import { readFile as readFile10 } from "fs/promises";
|
|
|
36461
36680
|
import { execFile as execFile5 } from "child_process";
|
|
36462
36681
|
import { basename as basename3, dirname as dirname7, resolve as resolve5 } from "path";
|
|
36463
36682
|
import { promisify as promisify5 } from "util";
|
|
36464
|
-
var
|
|
36683
|
+
var run5 = promisify5(execFile5);
|
|
36465
36684
|
async function gitDiff(path) {
|
|
36466
36685
|
const target = resolve5(path);
|
|
36467
36686
|
const dir2 = dirname7(target);
|
|
@@ -36473,7 +36692,7 @@ async function gitDiff(path) {
|
|
|
36473
36692
|
}
|
|
36474
36693
|
let oldContent = "";
|
|
36475
36694
|
try {
|
|
36476
|
-
const { stdout } = await
|
|
36695
|
+
const { stdout } = await run5("git", ["show", `HEAD:./${base}`], {
|
|
36477
36696
|
cwd: dir2,
|
|
36478
36697
|
timeout: 15e3,
|
|
36479
36698
|
maxBuffer: 4 * 1024 * 1024
|
|
@@ -36487,10 +36706,10 @@ async function gitDiff(path) {
|
|
|
36487
36706
|
// src/core/git-env.ts
|
|
36488
36707
|
import { execFile as execFile6 } from "child_process";
|
|
36489
36708
|
import { promisify as promisify6 } from "util";
|
|
36490
|
-
var
|
|
36709
|
+
var run6 = promisify6(execFile6);
|
|
36491
36710
|
async function hasBinary(bin, args = ["--version"]) {
|
|
36492
36711
|
try {
|
|
36493
|
-
await
|
|
36712
|
+
await run6(bin, args, { timeout: 5e3 });
|
|
36494
36713
|
return true;
|
|
36495
36714
|
} catch {
|
|
36496
36715
|
return false;
|
|
@@ -36503,7 +36722,7 @@ async function checkGitEnv(provider) {
|
|
|
36503
36722
|
let account = null;
|
|
36504
36723
|
if (installed) {
|
|
36505
36724
|
try {
|
|
36506
|
-
const { stdout, stderr } = await
|
|
36725
|
+
const { stdout, stderr } = await run6(bin, ["auth", "status"], { timeout: 8e3 });
|
|
36507
36726
|
const out = `${stdout}
|
|
36508
36727
|
${stderr}`;
|
|
36509
36728
|
authenticated = /logged in|logged into|✓/i.test(out);
|
|
@@ -36520,7 +36739,7 @@ import { execFile as execFile7 } from "child_process";
|
|
|
36520
36739
|
import { readFile as readFile11, rm as rm3, mkdir as mkdir7 } from "fs/promises";
|
|
36521
36740
|
import { join as join21 } from "path";
|
|
36522
36741
|
import { promisify as promisify7 } from "util";
|
|
36523
|
-
var
|
|
36742
|
+
var run7 = promisify7(execFile7);
|
|
36524
36743
|
function keyPaths(profileDir2) {
|
|
36525
36744
|
const dir2 = join21(profileDir2, ".ssh");
|
|
36526
36745
|
const key = join21(dir2, "id_4pm");
|
|
@@ -36535,14 +36754,14 @@ async function readPublicKey(pub) {
|
|
|
36535
36754
|
}
|
|
36536
36755
|
async function fingerprintOf(pub) {
|
|
36537
36756
|
try {
|
|
36538
|
-
const { stdout } = await
|
|
36757
|
+
const { stdout } = await run7("ssh-keygen", ["-lf", pub], { timeout: 5e3 });
|
|
36539
36758
|
return stdout.match(/SHA256:[A-Za-z0-9+/=]+/)?.[0] ?? null;
|
|
36540
36759
|
} catch {
|
|
36541
36760
|
return null;
|
|
36542
36761
|
}
|
|
36543
36762
|
}
|
|
36544
36763
|
async function configureGit(key) {
|
|
36545
|
-
await
|
|
36764
|
+
await run7(
|
|
36546
36765
|
"git",
|
|
36547
36766
|
["config", "--global", "core.sshCommand", `ssh -i ${key} -o IdentitiesOnly=yes`],
|
|
36548
36767
|
{ timeout: 5e3 }
|
|
@@ -36553,7 +36772,7 @@ async function generate(profileDir2) {
|
|
|
36553
36772
|
await mkdir7(dir2, { recursive: true });
|
|
36554
36773
|
await rm3(key, { force: true });
|
|
36555
36774
|
await rm3(pub, { force: true });
|
|
36556
|
-
await
|
|
36775
|
+
await run7("ssh-keygen", ["-t", "ed25519", "-f", key, "-N", "", "-C", "4pm-deploy-key"], {
|
|
36557
36776
|
timeout: 15e3
|
|
36558
36777
|
});
|
|
36559
36778
|
await configureGit(key);
|
|
@@ -36563,7 +36782,7 @@ async function remove(profileDir2) {
|
|
|
36563
36782
|
const { key, pub } = keyPaths(profileDir2);
|
|
36564
36783
|
await rm3(key, { force: true });
|
|
36565
36784
|
await rm3(pub, { force: true });
|
|
36566
|
-
await
|
|
36785
|
+
await run7("git", ["config", "--global", "--unset", "core.sshCommand"], { timeout: 5e3 }).catch(
|
|
36567
36786
|
() => void 0
|
|
36568
36787
|
);
|
|
36569
36788
|
return { publicKey: null, fingerprint: null };
|
|
@@ -36588,7 +36807,7 @@ import { readdir as readdir6 } from "fs/promises";
|
|
|
36588
36807
|
import { execFile as execFile8 } from "child_process";
|
|
36589
36808
|
import { resolve as resolve6, sep as sep4 } from "path";
|
|
36590
36809
|
import { promisify as promisify8 } from "util";
|
|
36591
|
-
var
|
|
36810
|
+
var run8 = promisify8(execFile8);
|
|
36592
36811
|
var FS = "";
|
|
36593
36812
|
var RS = "";
|
|
36594
36813
|
function resolveRepo(root, repo) {
|
|
@@ -36599,7 +36818,7 @@ function resolveRepo(root, repo) {
|
|
|
36599
36818
|
}
|
|
36600
36819
|
async function git(cwd2, args) {
|
|
36601
36820
|
try {
|
|
36602
|
-
const { stdout } = await
|
|
36821
|
+
const { stdout } = await run8("git", args, {
|
|
36603
36822
|
cwd: cwd2,
|
|
36604
36823
|
timeout: 15e3,
|
|
36605
36824
|
maxBuffer: 8 * 1024 * 1024
|
|
@@ -36703,11 +36922,11 @@ async function gitCommitDiff(root, repo, hash, path) {
|
|
|
36703
36922
|
// src/core/git-commit-identity.ts
|
|
36704
36923
|
import { execFile as execFile9 } from "child_process";
|
|
36705
36924
|
import { promisify as promisify9 } from "util";
|
|
36706
|
-
var
|
|
36925
|
+
var run9 = promisify9(execFile9);
|
|
36707
36926
|
async function setCommitAuthor(cwd2, machineUsername) {
|
|
36708
36927
|
if (!machineUsername) return;
|
|
36709
36928
|
try {
|
|
36710
|
-
await
|
|
36929
|
+
await run9("git", ["config", "--local", "user.name", machineUsername], { cwd: cwd2, timeout: 8e3 });
|
|
36711
36930
|
} catch {
|
|
36712
36931
|
}
|
|
36713
36932
|
}
|
|
@@ -36718,7 +36937,7 @@ import { execFile as execFile10 } from "child_process";
|
|
|
36718
36937
|
import { dirname as dirname8, join as join22, resolve as resolve7 } from "path";
|
|
36719
36938
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
36720
36939
|
import { promisify as promisify10 } from "util";
|
|
36721
|
-
var
|
|
36940
|
+
var run10 = promisify10(execFile10);
|
|
36722
36941
|
function reposOf(spec) {
|
|
36723
36942
|
const repos = spec?.repos;
|
|
36724
36943
|
return Array.isArray(repos) ? repos : [];
|
|
@@ -36732,13 +36951,13 @@ async function provisionRepos(target, repos, emit, opts = {}) {
|
|
|
36732
36951
|
if (repo.primary) {
|
|
36733
36952
|
if (opts.clonePrimary && repo.url) {
|
|
36734
36953
|
emit("git", `Cloning ${repo.url}\u2026`);
|
|
36735
|
-
await
|
|
36954
|
+
await run10("git", ["clone", repo.url, target], { timeout: 12e4 });
|
|
36736
36955
|
continue;
|
|
36737
36956
|
}
|
|
36738
36957
|
emit("git", "Initializing the primary repo\u2026");
|
|
36739
|
-
await
|
|
36958
|
+
await run10("git", ["init"], { cwd: target, timeout: 2e4 });
|
|
36740
36959
|
if (repo.url) {
|
|
36741
|
-
await
|
|
36960
|
+
await run10("git", ["remote", "add", "origin", repo.url], { cwd: target, timeout: 2e4 }).catch(
|
|
36742
36961
|
() => void 0
|
|
36743
36962
|
);
|
|
36744
36963
|
}
|
|
@@ -36747,11 +36966,11 @@ async function provisionRepos(target, repos, emit, opts = {}) {
|
|
|
36747
36966
|
const dir2 = join22(target, subDirName(repo));
|
|
36748
36967
|
if (repo.url) {
|
|
36749
36968
|
emit("git", `Cloning sub-repo ${repo.url}\u2026`);
|
|
36750
|
-
await
|
|
36969
|
+
await run10("git", ["clone", repo.url, dir2], { timeout: 12e4 });
|
|
36751
36970
|
} else {
|
|
36752
36971
|
emit("git", `Initializing sub-repo ${subDirName(repo)}\u2026`);
|
|
36753
36972
|
await mkdir8(dir2, { recursive: true });
|
|
36754
|
-
await
|
|
36973
|
+
await run10("git", ["init"], { cwd: dir2, timeout: 2e4 });
|
|
36755
36974
|
}
|
|
36756
36975
|
}
|
|
36757
36976
|
}
|
|
@@ -37229,7 +37448,7 @@ function runSlashCommand(line, base) {
|
|
|
37229
37448
|
}
|
|
37230
37449
|
|
|
37231
37450
|
// src/core/memory-compact.ts
|
|
37232
|
-
import { spawn as
|
|
37451
|
+
import { spawn as spawn9 } from "child_process";
|
|
37233
37452
|
var COMPACT_TIMEOUT_MS = 6e4;
|
|
37234
37453
|
function buildPrompt3(input) {
|
|
37235
37454
|
return [
|
|
@@ -37257,7 +37476,7 @@ function runOnce2(cmd, profile, prompt, cwd2, extraEnv) {
|
|
|
37257
37476
|
...extraEnv,
|
|
37258
37477
|
...profile ? { CLAUDE_CONFIG_DIR: profile.dir } : {}
|
|
37259
37478
|
};
|
|
37260
|
-
const child =
|
|
37479
|
+
const child = spawn9(cmd, args, { stdio: ["pipe", "pipe", "pipe"], cwd: cwd2, env: env3 });
|
|
37261
37480
|
let out = "";
|
|
37262
37481
|
let err = "";
|
|
37263
37482
|
const timer = setTimeout(() => {
|
|
@@ -37345,125 +37564,6 @@ function applyConfigText(profileDir2, text) {
|
|
|
37345
37564
|
return { ok: true };
|
|
37346
37565
|
}
|
|
37347
37566
|
|
|
37348
|
-
// src/core/worker-tools.ts
|
|
37349
|
-
import { spawn as spawn9 } from "child_process";
|
|
37350
|
-
var NPM_NAME = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
|
|
37351
|
-
function run10(cmd, args, onLine, timeoutMs = 18e4) {
|
|
37352
|
-
return new Promise((resolve8) => {
|
|
37353
|
-
let out = "";
|
|
37354
|
-
let buf = "";
|
|
37355
|
-
let child = null;
|
|
37356
|
-
const done = (code) => {
|
|
37357
|
-
clearTimeout(timer);
|
|
37358
|
-
if (onLine && buf.trim()) onLine(buf.trim());
|
|
37359
|
-
resolve8({ code, out });
|
|
37360
|
-
};
|
|
37361
|
-
const timer = setTimeout(() => {
|
|
37362
|
-
child?.kill();
|
|
37363
|
-
if (onLine) onLine(`timed out after ${Math.round(timeoutMs / 1e3)}s`);
|
|
37364
|
-
done(124);
|
|
37365
|
-
}, timeoutMs);
|
|
37366
|
-
timer.unref?.();
|
|
37367
|
-
const feed = (d) => {
|
|
37368
|
-
const text = d.toString("utf8");
|
|
37369
|
-
out += text;
|
|
37370
|
-
if (!onLine) return;
|
|
37371
|
-
buf += text;
|
|
37372
|
-
let nl;
|
|
37373
|
-
while ((nl = buf.indexOf("\n")) >= 0) {
|
|
37374
|
-
const line = buf.slice(0, nl).replace(/\r$/, "");
|
|
37375
|
-
buf = buf.slice(nl + 1);
|
|
37376
|
-
onLine(line);
|
|
37377
|
-
}
|
|
37378
|
-
};
|
|
37379
|
-
try {
|
|
37380
|
-
child = spawn9(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
37381
|
-
child.stdout?.on("data", feed);
|
|
37382
|
-
child.stderr?.on("data", feed);
|
|
37383
|
-
child.on("exit", (code) => done(code ?? 0));
|
|
37384
|
-
child.on("error", () => done(127));
|
|
37385
|
-
} catch {
|
|
37386
|
-
done(127);
|
|
37387
|
-
}
|
|
37388
|
-
});
|
|
37389
|
-
}
|
|
37390
|
-
async function detectOne(cmd, versionArg) {
|
|
37391
|
-
const { code, out } = await run10(cmd, [versionArg], void 0, 15e3);
|
|
37392
|
-
if (code !== 0) return null;
|
|
37393
|
-
const first = out.split("\n").map((l) => l.trim()).find(Boolean);
|
|
37394
|
-
return first ?? "";
|
|
37395
|
-
}
|
|
37396
|
-
async function listExtras() {
|
|
37397
|
-
const { out } = await run10("npm", ["ls", "-g", "--depth=0", "--json"], void 0, 2e4);
|
|
37398
|
-
if (!out.trim()) return [];
|
|
37399
|
-
let deps = {};
|
|
37400
|
-
try {
|
|
37401
|
-
deps = JSON.parse(out).dependencies ?? {};
|
|
37402
|
-
} catch {
|
|
37403
|
-
return [];
|
|
37404
|
-
}
|
|
37405
|
-
const catalogPkgs = new Set(WORKER_TOOL_BUNDLED_GLOBALS);
|
|
37406
|
-
for (const t of WORKER_TOOL_CATALOG) {
|
|
37407
|
-
catalogPkgs.add(t.id);
|
|
37408
|
-
if (t.installPackage) catalogPkgs.add(t.installPackage);
|
|
37409
|
-
}
|
|
37410
|
-
return Object.entries(deps).filter(([name]) => !catalogPkgs.has(name)).map(([name, info]) => ({
|
|
37411
|
-
id: name,
|
|
37412
|
-
label: name,
|
|
37413
|
-
category: "runtime",
|
|
37414
|
-
installed: true,
|
|
37415
|
-
version: info.version ?? null,
|
|
37416
|
-
installable: true
|
|
37417
|
-
}));
|
|
37418
|
-
}
|
|
37419
|
-
async function detectWorkerTools() {
|
|
37420
|
-
const catalog = await Promise.all(
|
|
37421
|
-
WORKER_TOOL_CATALOG.map(async (t) => {
|
|
37422
|
-
const version = await detectOne(t.id, t.versionArg);
|
|
37423
|
-
return {
|
|
37424
|
-
id: t.id,
|
|
37425
|
-
label: t.label,
|
|
37426
|
-
category: t.category,
|
|
37427
|
-
installed: version !== null,
|
|
37428
|
-
version,
|
|
37429
|
-
installable: t.installable
|
|
37430
|
-
};
|
|
37431
|
-
})
|
|
37432
|
-
);
|
|
37433
|
-
const extras = await listExtras().catch(() => []);
|
|
37434
|
-
return { catalog, extras };
|
|
37435
|
-
}
|
|
37436
|
-
function resolvePackage(name, op) {
|
|
37437
|
-
const entry = WORKER_TOOL_CATALOG.find((t) => t.id === name);
|
|
37438
|
-
if (entry) {
|
|
37439
|
-
if (!entry.installable) {
|
|
37440
|
-
return { error: `"${name}" is a prerequisite and cannot be ${op}ed from here.` };
|
|
37441
|
-
}
|
|
37442
|
-
return { pkg: entry.installPackage ?? name };
|
|
37443
|
-
}
|
|
37444
|
-
if (WORKER_TOOL_PREREQUISITE_IDS.includes(name)) {
|
|
37445
|
-
return { error: `"${name}" is a prerequisite and cannot be ${op}ed from here.` };
|
|
37446
|
-
}
|
|
37447
|
-
if (WORKER_TOOL_BUNDLED_GLOBALS.includes(name)) {
|
|
37448
|
-
return { error: `"${name}" ships with Node and cannot be ${op}ed from here.` };
|
|
37449
|
-
}
|
|
37450
|
-
if (!NPM_NAME.test(name)) return { error: `Invalid package name: "${name}".` };
|
|
37451
|
-
return { pkg: name };
|
|
37452
|
-
}
|
|
37453
|
-
function opArgs(op, manager, pkg) {
|
|
37454
|
-
if (manager === "pnpm") return [op === "install" ? "add" : "remove", "-g", pkg];
|
|
37455
|
-
return [op === "install" ? "install" : "uninstall", "-g", pkg];
|
|
37456
|
-
}
|
|
37457
|
-
async function runWorkerToolOp(op, name, manager, onLine) {
|
|
37458
|
-
const resolved = resolvePackage(name, op);
|
|
37459
|
-
if (resolved.error || !resolved.pkg) {
|
|
37460
|
-
return { ok: false, exitCode: 1, error: resolved.error ?? "Invalid package." };
|
|
37461
|
-
}
|
|
37462
|
-
onLine(`$ ${manager} ${opArgs(op, manager, resolved.pkg).join(" ")}`);
|
|
37463
|
-
const { code } = await run10(manager, opArgs(op, manager, resolved.pkg), onLine);
|
|
37464
|
-
return { ok: code === 0, exitCode: code, error: code === 0 ? void 0 : `${manager} exited ${code}` };
|
|
37465
|
-
}
|
|
37466
|
-
|
|
37467
37567
|
// src/core/ws-client.ts
|
|
37468
37568
|
var MEMORY_SEED_HEADER = "CONTEXT MEMORY from earlier in this conversation (may span prior sessions/accounts). Use it as background; do not repeat it back unless relevant:";
|
|
37469
37569
|
var HEARTBEAT_MS = 3e4;
|
|
@@ -37505,7 +37605,9 @@ var WsClient = class {
|
|
|
37505
37605
|
this.updateScheduler = new UpdateScheduler(
|
|
37506
37606
|
context.credential.serverUrl,
|
|
37507
37607
|
context.profileDir,
|
|
37508
|
-
context.bus
|
|
37608
|
+
context.bus,
|
|
37609
|
+
// After the idle daily tick updates flagged tools, report the fresh snapshot (ADR-0254).
|
|
37610
|
+
() => void this.reportWorkerTools()
|
|
37509
37611
|
);
|
|
37510
37612
|
context.bus.onLocalSubmit((input) => void this.runLocalCommand(input));
|
|
37511
37613
|
context.bus.onReconnect(() => this.reconnectNow());
|
|
@@ -37622,6 +37724,20 @@ var WsClient = class {
|
|
|
37622
37724
|
get isStopped() {
|
|
37623
37725
|
return this.stopped;
|
|
37624
37726
|
}
|
|
37727
|
+
/**
|
|
37728
|
+
* Detect the worker's tools and report the snapshot to the server (ADR-0254, `tools.report`) so the
|
|
37729
|
+
* DB-backed Tools panel + restore target stay current. One-way, best-effort — a detect/send failure
|
|
37730
|
+
* never disrupts the session. Called after each tool op, after the daily tick, and after a restore.
|
|
37731
|
+
*/
|
|
37732
|
+
async reportWorkerTools() {
|
|
37733
|
+
try {
|
|
37734
|
+
const autoUpdate = readProfileConfig(this.context.profileDir).autoUpdateTools ?? [];
|
|
37735
|
+
const { catalog, extras } = await detectWorkerTools(autoUpdate);
|
|
37736
|
+
this.send(WsChannels.TOOLS_REPORT, { catalog, extras });
|
|
37737
|
+
} catch (err) {
|
|
37738
|
+
logger.warn("tools.report.error", { error: String(err) });
|
|
37739
|
+
}
|
|
37740
|
+
}
|
|
37625
37741
|
/**
|
|
37626
37742
|
* Force an immediate reconnect: wake the backoff sleep if waiting, else drop the live
|
|
37627
37743
|
* socket so the run() loop reconnects (the next backoff is skipped). No-op if stopped.
|
|
@@ -37772,7 +37888,10 @@ var WsClient = class {
|
|
|
37772
37888
|
projectAutoClearIdleMinutes: token.projectTokens?.autoClearIdleMinutes ?? 0,
|
|
37773
37889
|
// Project shared-AI-memory override mirrors (ADR-0245).
|
|
37774
37890
|
projectAiMemoryMode: token.aiMemory?.mode ?? "inherit",
|
|
37775
|
-
projectAiMemoryBudgetChars: token.aiMemory?.budgetChars ?? 0
|
|
37891
|
+
projectAiMemoryBudgetChars: token.aiMemory?.budgetChars ?? 0,
|
|
37892
|
+
// Per-tool auto-update flags (ADR-0253) are now DB-owned (ADR-0254) — mirror the server's
|
|
37893
|
+
// list locally so the idle daily tick (UpdateScheduler) can read it without a round-trip.
|
|
37894
|
+
autoUpdateTools: token.toolRestore?.autoUpdate ?? []
|
|
37776
37895
|
});
|
|
37777
37896
|
if (token.aiMemory && !this.aiMemory) this.aiMemory = token.aiMemory.text;
|
|
37778
37897
|
this.outboundReviewEnabled = token.outboundReview?.enabled === true;
|
|
@@ -38250,27 +38369,49 @@ var WsClient = class {
|
|
|
38250
38369
|
break;
|
|
38251
38370
|
}
|
|
38252
38371
|
case WsChannels.TOOLS_LIST:
|
|
38253
|
-
void detectWorkerTools().then(
|
|
38372
|
+
void detectWorkerTools(readProfileConfig(this.context.profileDir).autoUpdateTools ?? []).then(
|
|
38254
38373
|
(reply) => this.send(WsChannels.TOOLS_LIST, reply, message.id)
|
|
38255
38374
|
);
|
|
38256
38375
|
break;
|
|
38376
|
+
case WsChannels.TOOLS_AUTOUPDATE: {
|
|
38377
|
+
const req = payload;
|
|
38378
|
+
const res = setToolAutoUpdate(this.context.profileDir, req.name, req.enabled);
|
|
38379
|
+
this.send(WsChannels.TOOLS_AUTOUPDATE, res, message.id);
|
|
38380
|
+
break;
|
|
38381
|
+
}
|
|
38257
38382
|
case WsChannels.TOOLS_INSTALL:
|
|
38258
|
-
case WsChannels.TOOLS_UNINSTALL:
|
|
38383
|
+
case WsChannels.TOOLS_UNINSTALL:
|
|
38384
|
+
case WsChannels.TOOLS_UPDATE: {
|
|
38259
38385
|
const req = payload;
|
|
38260
|
-
const op = message.channel === WsChannels.TOOLS_INSTALL ? "install" : "uninstall";
|
|
38386
|
+
const op = message.channel === WsChannels.TOOLS_INSTALL ? "install" : message.channel === WsChannels.TOOLS_UPDATE ? "update" : "uninstall";
|
|
38261
38387
|
this.send(message.channel, { started: true }, message.id);
|
|
38262
38388
|
void runWorkerToolOp(
|
|
38263
38389
|
op,
|
|
38264
38390
|
req.name,
|
|
38265
38391
|
req.manager,
|
|
38266
38392
|
(line) => this.send(WsChannels.TOOLS_PROGRESS, { opId: req.opId, line })
|
|
38267
|
-
).then(
|
|
38268
|
-
|
|
38393
|
+
).then((res) => {
|
|
38394
|
+
this.send(WsChannels.TOOLS_DONE, {
|
|
38269
38395
|
opId: req.opId,
|
|
38270
38396
|
ok: res.ok,
|
|
38271
38397
|
exitCode: res.exitCode,
|
|
38272
38398
|
error: res.error
|
|
38273
|
-
})
|
|
38399
|
+
});
|
|
38400
|
+
void this.reportWorkerTools();
|
|
38401
|
+
});
|
|
38402
|
+
break;
|
|
38403
|
+
}
|
|
38404
|
+
case WsChannels.TOOLS_RESTORE: {
|
|
38405
|
+
const req = payload;
|
|
38406
|
+
void reconcileTools(
|
|
38407
|
+
req.manifest.map((m) => ({ name: m.name, version: m.version, manager: m.manager })),
|
|
38408
|
+
(line) => this.bus.log(line)
|
|
38409
|
+
).then(() => this.reportWorkerTools()).then(() => this.send(WsChannels.TOOLS_RESTORE, { ok: true }, message.id)).catch(
|
|
38410
|
+
(err) => this.send(
|
|
38411
|
+
WsChannels.TOOLS_RESTORE,
|
|
38412
|
+
{ ok: false, error: String(err) },
|
|
38413
|
+
message.id
|
|
38414
|
+
)
|
|
38274
38415
|
);
|
|
38275
38416
|
break;
|
|
38276
38417
|
}
|