@4pm/cli 1.19.7 → 1.20.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 +344 -120
- package/dist/project-sample/.claude/settings.json +0 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -29519,8 +29519,8 @@ async function runLink(serverUrl, explicitProfile, token = null) {
|
|
|
29519
29519
|
}
|
|
29520
29520
|
|
|
29521
29521
|
// src/commands/start.ts
|
|
29522
|
-
import { spawn as
|
|
29523
|
-
import { join as
|
|
29522
|
+
import { spawn as spawn11 } from "child_process";
|
|
29523
|
+
import { join as join37 } from "path";
|
|
29524
29524
|
|
|
29525
29525
|
// src/core/update.ts
|
|
29526
29526
|
import { execSync as execSync2 } from "child_process";
|
|
@@ -32732,9 +32732,9 @@ async function checkAndUpdate(serverUrl, autoUpdate) {
|
|
|
32732
32732
|
|
|
32733
32733
|
// src/core/ws-client.ts
|
|
32734
32734
|
init_wrapper();
|
|
32735
|
-
import { randomUUID as
|
|
32736
|
-
import { basename as basename7, join as
|
|
32737
|
-
import { existsSync as
|
|
32735
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
32736
|
+
import { basename as basename7, join as join32 } from "path";
|
|
32737
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync8 } from "fs";
|
|
32738
32738
|
|
|
32739
32739
|
// vendor/@4pm/utils/helpers.ts
|
|
32740
32740
|
function backoffJitterMs(attempt, baseMs = 500, maxMs = 1e4) {
|
|
@@ -37191,7 +37191,6 @@ var toolRuleSchema = external_exports.string().min(1).max(500);
|
|
|
37191
37191
|
var agentToolsPermissionsSchema = external_exports.object({
|
|
37192
37192
|
defaultMode: external_exports.enum(["default", "acceptEdits", "plan", "bypassPermissions"]),
|
|
37193
37193
|
allow: external_exports.array(toolRuleSchema).max(500),
|
|
37194
|
-
ask: external_exports.array(toolRuleSchema).max(500),
|
|
37195
37194
|
deny: external_exports.array(toolRuleSchema).max(500)
|
|
37196
37195
|
});
|
|
37197
37196
|
var agentToolsReadQuerySchema = external_exports.object({
|
|
@@ -37681,6 +37680,50 @@ async function commitAndPushSubmodules(root, submodules, emit) {
|
|
|
37681
37680
|
emit("submodule-push-failed", `Submodules committed locally but the push failed: ${msg}`);
|
|
37682
37681
|
}
|
|
37683
37682
|
}
|
|
37683
|
+
function providerSlug(url) {
|
|
37684
|
+
const provider = /github\.com/i.test(url) ? "gh" : /gitlab\.com/i.test(url) ? "glab" : null;
|
|
37685
|
+
const m2 = /(?:github|gitlab)\.com[/:]+([^/]+\/.+?)(?:\.git)?\/?$/i.exec(url);
|
|
37686
|
+
return { provider, slug: m2?.[1] ?? "" };
|
|
37687
|
+
}
|
|
37688
|
+
async function commitAndOpenPr(root, repo, emit) {
|
|
37689
|
+
emit("commit", "Committing the scaffolded project\u2026");
|
|
37690
|
+
await run2("git", ["add", "-A"], { cwd: root, timeout: 6e4 });
|
|
37691
|
+
const staged = (await run2("git", ["diff", "--cached", "--name-only"], { cwd: root, timeout: 3e4 })).stdout.trim();
|
|
37692
|
+
if (!staged) {
|
|
37693
|
+
emit("commit", "Nothing to commit \u2014 skipping the pull request.");
|
|
37694
|
+
return;
|
|
37695
|
+
}
|
|
37696
|
+
const commitArgs = ["commit", "-m", "chore: scaffold project (4PM)"];
|
|
37697
|
+
try {
|
|
37698
|
+
await run2("git", commitArgs, { cwd: root, timeout: 6e4 });
|
|
37699
|
+
} catch {
|
|
37700
|
+
await run2("git", ["-c", "user.name=4PM", "-c", "user.email=noreply@4pm.app", ...commitArgs], { cwd: root, timeout: 6e4 });
|
|
37701
|
+
}
|
|
37702
|
+
const branch = (repo.branch ?? "").trim();
|
|
37703
|
+
emit("push", "Pushing the scaffold branch\u2026");
|
|
37704
|
+
try {
|
|
37705
|
+
await run2("git", ["push", "-u", "origin", ...branch ? [branch] : ["HEAD"]], { cwd: root, timeout: 12e4 });
|
|
37706
|
+
} catch (err) {
|
|
37707
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
37708
|
+
emit("push-failed", `Scaffold committed locally but the push failed: ${msg} \u2014 push it from the Git tab when credentials are available.`);
|
|
37709
|
+
return;
|
|
37710
|
+
}
|
|
37711
|
+
const { provider, slug } = providerSlug(repo.url ?? "");
|
|
37712
|
+
if (!provider) {
|
|
37713
|
+
emit("pr", "Unknown git host \u2014 skipping the pull request.");
|
|
37714
|
+
return;
|
|
37715
|
+
}
|
|
37716
|
+
emit("pr", `Opening a pull request via ${provider}\u2026`);
|
|
37717
|
+
try {
|
|
37718
|
+
const args = provider === "glab" ? ["mr", "create", "--fill", ...slug ? ["-R", slug] : []] : ["pr", "create", "--fill", ...slug ? ["-R", slug] : []];
|
|
37719
|
+
const { stdout } = await run2(provider, args, { cwd: root, timeout: 12e4 });
|
|
37720
|
+
const link2 = stdout.trim().split(/\s+/).find((s3) => /^https?:\/\//.test(s3)) ?? stdout.trim().split("\n").pop() ?? "";
|
|
37721
|
+
emit("pr", `Pull request opened${link2 ? `: ${link2}` : "."}`);
|
|
37722
|
+
} catch (err) {
|
|
37723
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
37724
|
+
emit("pr-failed", `Could not open a pull request (${provider}): ${msg}`);
|
|
37725
|
+
}
|
|
37726
|
+
}
|
|
37684
37727
|
function cloneArgs(url, dest, branch) {
|
|
37685
37728
|
const b2 = (branch ?? "").trim();
|
|
37686
37729
|
return b2 ? ["clone", "-b", b2, url, dest] : ["clone", url, dest];
|
|
@@ -37787,18 +37830,20 @@ async function scaffoldProject(payload, profileDir2, onProgress) {
|
|
|
37787
37830
|
if (!repo) throw new Error("A project must declare one repo (ADR-0314).");
|
|
37788
37831
|
emit("git", "Cloning repository\u2026");
|
|
37789
37832
|
await provisionRepo(target, repo, emit, { createMissingBranch: true });
|
|
37790
|
-
await scaffoldRepo(target, payload.projectName, payload.spec, emit);
|
|
37833
|
+
await scaffoldRepo(target, payload.projectName, payload.spec, emit, { resetClaude: true });
|
|
37791
37834
|
await attachSubmodules(target, submodulesOf(reposOf(payload.spec)), emit, true);
|
|
37792
37835
|
emit("version", "Writing .4pm/.4pm.json\u2026");
|
|
37793
37836
|
await writeTemplateMarker(target);
|
|
37837
|
+
await commitAndOpenPr(target, repo, emit);
|
|
37794
37838
|
onProgress?.({ projectId: payload.projectId, step: "done", message: "Scaffold complete.", done: true });
|
|
37795
37839
|
return { ok: true, path: target };
|
|
37796
37840
|
} catch (err) {
|
|
37797
37841
|
return { ok: false, error: err instanceof Error ? err.message : String(err), step: lastStep };
|
|
37798
37842
|
}
|
|
37799
37843
|
}
|
|
37800
|
-
async function scaffoldRepo(dir2, label, spec, emit) {
|
|
37844
|
+
async function scaffoldRepo(dir2, label, spec, emit, opts = {}) {
|
|
37801
37845
|
await mkdir(dir2, { recursive: true });
|
|
37846
|
+
if (opts.resetClaude) await resetTemplateManagedClaude(dir2, label, emit);
|
|
37802
37847
|
emit("copy", `Copying the sample template into ${label}\u2026`);
|
|
37803
37848
|
await cp(sampleDir(), dir2, { recursive: true, force: false, errorOnExist: false });
|
|
37804
37849
|
if (spec) {
|
|
@@ -37807,6 +37852,13 @@ async function scaffoldRepo(dir2, label, spec, emit) {
|
|
|
37807
37852
|
await aiInit(dir2, spec, emit);
|
|
37808
37853
|
}
|
|
37809
37854
|
}
|
|
37855
|
+
async function resetTemplateManagedClaude(dir2, label, emit) {
|
|
37856
|
+
const claude = join6(dir2, ".claude");
|
|
37857
|
+
if (!existsSync4(claude)) return;
|
|
37858
|
+
emit("copy", `Resetting template .claude config in ${label}\u2026`);
|
|
37859
|
+
await rm(join6(claude, "settings.json"), { force: true });
|
|
37860
|
+
await rm(join6(claude, "skills"), { recursive: true, force: true });
|
|
37861
|
+
}
|
|
37810
37862
|
async function writeTemplateMarker(target) {
|
|
37811
37863
|
const dir2 = join6(target, ".4pm");
|
|
37812
37864
|
await mkdir(dir2, { recursive: true });
|
|
@@ -37854,15 +37906,20 @@ ${specJson}`
|
|
|
37854
37906
|
${specJson}` + (guideInstructions ? `
|
|
37855
37907
|
Also incorporate these additional instructions/content:
|
|
37856
37908
|
${guideInstructions}
|
|
37857
|
-
` : "")
|
|
37909
|
+
` : ""),
|
|
37910
|
+
guideInstructions
|
|
37858
37911
|
);
|
|
37859
37912
|
}
|
|
37860
|
-
async function generateFile(path, prompt) {
|
|
37913
|
+
async function generateFile(path, prompt, fallback) {
|
|
37861
37914
|
try {
|
|
37862
37915
|
const text = await aiGenerate(prompt);
|
|
37863
|
-
if (text.trim())
|
|
37916
|
+
if (text.trim()) {
|
|
37917
|
+
await writeFile(path, text.trim() + "\n", "utf8");
|
|
37918
|
+
return;
|
|
37919
|
+
}
|
|
37864
37920
|
} catch {
|
|
37865
37921
|
}
|
|
37922
|
+
if (fallback && fallback.trim()) await writeFile(path, fallback.trim() + "\n", "utf8");
|
|
37866
37923
|
}
|
|
37867
37924
|
async function addProject(payload, profileDir2, onProgress) {
|
|
37868
37925
|
let lastStep = "start";
|
|
@@ -38332,6 +38389,12 @@ var WsChannels = {
|
|
|
38332
38389
|
// AI knowledge distill (ADR-0190): server → project cli request–reply. The worker runs the AI CLI
|
|
38333
38390
|
// in the project's working dir with a distillation prompt and replies with the composed markdown.
|
|
38334
38391
|
KNOWLEDGE_COMPOSE: "knowledge.compose",
|
|
38392
|
+
// Ticket → FAQ synthesis (ADR-0333): server (platform-pool dispatcher) → platform-pool cli
|
|
38393
|
+
// request–reply. The dispatcher sends the selected support tickets + the 4pm-faq repo with a
|
|
38394
|
+
// short-lived WRITE token; the worker runs an agent-write that distils the tickets into FAQ
|
|
38395
|
+
// entries, commits, pushes a branch, opens a PR, then wipes the token/clone, and replies with the
|
|
38396
|
+
// PR URL. The write token never persists on the worker (leak-safety on release/reassign).
|
|
38397
|
+
FAQ_COMPOSE: "faq.compose",
|
|
38335
38398
|
// Shared AI memory (ADR-0245): cli → server one-way write-back of the compacted rolling memory for
|
|
38336
38399
|
// the project it serves (persisted per project × machine-user link). The read direction rides
|
|
38337
38400
|
// `ws_token` (seeds the cli cache on connect); this channel only carries updates.
|
|
@@ -42360,7 +42423,8 @@ async function gitRepos(root) {
|
|
|
42360
42423
|
if (!top || !(top === base || top.startsWith(base + sep8))) return { repos: [] };
|
|
42361
42424
|
const repos = [];
|
|
42362
42425
|
const rootRemote = (await git(base, ["remote", "get-url", "origin"])).trim();
|
|
42363
|
-
|
|
42426
|
+
const rootBranch = (await git(base, ["rev-parse", "--abbrev-ref", "HEAD"])).trim();
|
|
42427
|
+
repos.push({ subdir: "", name: "", remote: rootRemote || null, branch: rootBranch || null });
|
|
42364
42428
|
const subOut = await git(base, ["submodule", "status"]);
|
|
42365
42429
|
for (const line of subOut.split("\n")) {
|
|
42366
42430
|
const m2 = line.trim().match(/^[-+U ]?[0-9a-f]{7,40}\s+(\S+)/);
|
|
@@ -42369,7 +42433,8 @@ async function gitRepos(root) {
|
|
|
42369
42433
|
const abs = resolve8(base, subPath);
|
|
42370
42434
|
if (!(abs === base || abs.startsWith(base + sep8))) continue;
|
|
42371
42435
|
const remote = (await git(abs, ["remote", "get-url", "origin"])).trim();
|
|
42372
|
-
|
|
42436
|
+
const branch = (await git(abs, ["rev-parse", "--abbrev-ref", "HEAD"])).trim();
|
|
42437
|
+
repos.push({ subdir: subPath, name: subPath, remote: remote || null, branch: branch || null });
|
|
42373
42438
|
}
|
|
42374
42439
|
return { repos };
|
|
42375
42440
|
}
|
|
@@ -42740,6 +42805,145 @@ async function runKnowledgeCompose(req, ai2, physicRoot) {
|
|
|
42740
42805
|
return { bodyMarkdown: "", error: lastReason };
|
|
42741
42806
|
}
|
|
42742
42807
|
|
|
42808
|
+
// src/core/faq-compose.ts
|
|
42809
|
+
import { spawn as spawn9, execFile as execFile10 } from "child_process";
|
|
42810
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync7, rmSync as rmSync7 } from "fs";
|
|
42811
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
42812
|
+
import { join as join24 } from "path";
|
|
42813
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
42814
|
+
import { promisify as promisify10 } from "util";
|
|
42815
|
+
var execFileAsync2 = promisify10(execFile10);
|
|
42816
|
+
var NO_USAGE2 = { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 };
|
|
42817
|
+
var COMPOSE_TIMEOUT_MS2 = 10 * 60 * 1e3;
|
|
42818
|
+
function authedUrl2(url, token) {
|
|
42819
|
+
if (url.startsWith("https://")) return url.replace("https://", `https://x-access-token:${token}@`);
|
|
42820
|
+
return url;
|
|
42821
|
+
}
|
|
42822
|
+
function buildPrompt4(tickets) {
|
|
42823
|
+
const transcript = tickets.map((t2) => {
|
|
42824
|
+
const thread = t2.messages.map((m2) => ` [${m2.author}] ${m2.body}`).join("\n");
|
|
42825
|
+
return `### Ticket ${t2.id} \u2014 ${t2.subject} (${t2.category})
|
|
42826
|
+
${thread}`;
|
|
42827
|
+
}).join("\n\n");
|
|
42828
|
+
return [
|
|
42829
|
+
"You are maintaining the 4PM product FAQ, stored as markdown in THIS git repository (your CWD).",
|
|
42830
|
+
"From the resolved support tickets below, distil reusable, product-general FAQ entries and add or",
|
|
42831
|
+
"update the appropriate markdown file(s) in this repo (e.g. an FAQ.md, or the closest existing",
|
|
42832
|
+
"topic file \u2014 inspect the repo first and match its structure/style).",
|
|
42833
|
+
"",
|
|
42834
|
+
"Rules:",
|
|
42835
|
+
"- Write generalized Q&A, NOT ticket-specific replies; never include customer names, emails, ids,",
|
|
42836
|
+
" org names or any personal data from the tickets.",
|
|
42837
|
+
"- Merge with existing entries instead of duplicating; keep the existing formatting and headings.",
|
|
42838
|
+
"- If a ticket yields no reusable FAQ value, skip it. If nothing is worth adding, make NO changes.",
|
|
42839
|
+
"- Only edit markdown documentation files; do not touch code, CI, or unrelated files.",
|
|
42840
|
+
"- Do NOT run git commit/push or open a PR yourself \u2014 just leave the edited files in the working",
|
|
42841
|
+
" tree; the surrounding tooling handles commit + PR.",
|
|
42842
|
+
"",
|
|
42843
|
+
"===== RESOLVED TICKETS =====",
|
|
42844
|
+
transcript
|
|
42845
|
+
].join("\n");
|
|
42846
|
+
}
|
|
42847
|
+
function runClaudeOnce2(cmd, profile, prompt, cwd2, extraEnv) {
|
|
42848
|
+
return new Promise((resolve10, reject) => {
|
|
42849
|
+
const isClaude = cmd.includes("claude");
|
|
42850
|
+
const args = [
|
|
42851
|
+
"-p",
|
|
42852
|
+
...profile?.model ? ["--model", profile.model] : [],
|
|
42853
|
+
...isClaude ? ["--output-format", "stream-json", "--verbose"] : [],
|
|
42854
|
+
// Write-capable headless agent (ADR-0271): auto-approve tools so it never stalls on a prompt.
|
|
42855
|
+
...isClaude ? ["--permission-mode", "bypassPermissions"] : [],
|
|
42856
|
+
...cmd.includes("codex") ? ["--dangerously-bypass-approvals-and-sandbox"] : []
|
|
42857
|
+
];
|
|
42858
|
+
const env3 = {
|
|
42859
|
+
...process.env,
|
|
42860
|
+
...extraEnv,
|
|
42861
|
+
...profile ? { CLAUDE_CONFIG_DIR: profile.dir } : {}
|
|
42862
|
+
};
|
|
42863
|
+
const child = spawn9(cmd, args, { stdio: ["pipe", "pipe", "pipe"], cwd: cwd2, env: env3 });
|
|
42864
|
+
const parser = createAiStreamParser(cmd);
|
|
42865
|
+
let out = "";
|
|
42866
|
+
let err = "";
|
|
42867
|
+
const timer = setTimeout(() => {
|
|
42868
|
+
child.kill("SIGKILL");
|
|
42869
|
+
reject(new Error("faq-compose agent timed out"));
|
|
42870
|
+
}, COMPOSE_TIMEOUT_MS2);
|
|
42871
|
+
timer.unref();
|
|
42872
|
+
child.stdout.on("data", (d) => out += parser.push(d.toString()));
|
|
42873
|
+
child.stderr.on("data", (d) => err += d.toString());
|
|
42874
|
+
child.on("error", (e) => {
|
|
42875
|
+
clearTimeout(timer);
|
|
42876
|
+
reject(e);
|
|
42877
|
+
});
|
|
42878
|
+
child.on("close", (code) => {
|
|
42879
|
+
clearTimeout(timer);
|
|
42880
|
+
out += parser.flush();
|
|
42881
|
+
resolve10({ code: code ?? -1, out, err, usage: parser.usage() });
|
|
42882
|
+
});
|
|
42883
|
+
child.stdin.write(prompt);
|
|
42884
|
+
child.stdin.end();
|
|
42885
|
+
});
|
|
42886
|
+
}
|
|
42887
|
+
async function runClaudeWithFailover2(prompt, cwd2, ai2) {
|
|
42888
|
+
const attempts = ai2.profiles.length > 0 ? ai2.profiles : [null];
|
|
42889
|
+
let lastReason = "no attempt";
|
|
42890
|
+
for (let i = 0; i < attempts.length; i++) {
|
|
42891
|
+
const { code, out, err, usage } = await runClaudeOnce2(ai2.cmd, attempts[i], prompt, cwd2, ai2.env);
|
|
42892
|
+
if (code === 0) return usage;
|
|
42893
|
+
lastReason = `exited ${code}: ${(err || out).slice(0, 500)}`;
|
|
42894
|
+
if (i === attempts.length - 1) break;
|
|
42895
|
+
}
|
|
42896
|
+
throw new Error(`claude ${lastReason}`);
|
|
42897
|
+
}
|
|
42898
|
+
async function runFaqCompose(req, ai2) {
|
|
42899
|
+
if (!req.repo.token) return { prUrl: "", error: "no write token" };
|
|
42900
|
+
const workDir = join24(tmpdir3(), "4pm-faq-sync", randomUUID3());
|
|
42901
|
+
const gitEnv = { ...process.env, GIT_TERMINAL_PROMPT: "0" };
|
|
42902
|
+
const authed = authedUrl2(req.repo.url, req.repo.token);
|
|
42903
|
+
try {
|
|
42904
|
+
mkdirSync7(workDir, { recursive: true });
|
|
42905
|
+
await execFileAsync2("git", ["clone", "--depth", "1", "--branch", req.repo.branch, authed, workDir], { env: gitEnv });
|
|
42906
|
+
await execFileAsync2("git", ["-C", workDir, "config", "user.name", "4PM FAQ Bot"], { env: gitEnv });
|
|
42907
|
+
await execFileAsync2("git", ["-C", workDir, "config", "user.email", "faq-bot@4pm.app"], { env: gitEnv });
|
|
42908
|
+
await execFileAsync2("git", ["-C", workDir, "checkout", "-b", req.headBranch], { env: gitEnv });
|
|
42909
|
+
const usage = await runClaudeWithFailover2(buildPrompt4(req.tickets), workDir, ai2);
|
|
42910
|
+
await execFileAsync2("git", ["-C", workDir, "add", "-A"], { env: gitEnv });
|
|
42911
|
+
const staged = await execFileAsync2("git", ["-C", workDir, "diff", "--cached", "--name-only"], { env: gitEnv });
|
|
42912
|
+
if (!staged.stdout.trim()) return { prUrl: "", tokens: usage.tokens || estimateTokens2(""), finishedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
42913
|
+
await execFileAsync2("git", ["-C", workDir, "commit", "-m", "docs(faq): synthesize from support tickets (4PM)"], { env: gitEnv });
|
|
42914
|
+
await execFileAsync2("git", ["-C", workDir, "push", "-u", "origin", req.headBranch], { env: gitEnv });
|
|
42915
|
+
let prUrl = "";
|
|
42916
|
+
try {
|
|
42917
|
+
const pr2 = await execFileAsync2(
|
|
42918
|
+
"gh",
|
|
42919
|
+
["pr", "create", "--fill", "--head", req.headBranch, "--base", req.repo.branch],
|
|
42920
|
+
{ cwd: workDir, env: { ...gitEnv, GH_TOKEN: req.repo.token } }
|
|
42921
|
+
);
|
|
42922
|
+
prUrl = pr2.stdout.trim().split("\n").pop() ?? "";
|
|
42923
|
+
} catch (err) {
|
|
42924
|
+
logger.warn("faq.compose.pr.failed", { error: String(err) });
|
|
42925
|
+
}
|
|
42926
|
+
const u2 = usage.tokens > 0 ? usage : { ...NO_USAGE2 };
|
|
42927
|
+
return {
|
|
42928
|
+
prUrl,
|
|
42929
|
+
branch: req.headBranch,
|
|
42930
|
+
tokens: u2.tokens,
|
|
42931
|
+
...usage.tokens > 0 ? { tokensBreakdown: { input: usage.input, output: usage.output, cacheRead: usage.cacheRead, cacheCreation: usage.cacheCreation } } : {},
|
|
42932
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
42933
|
+
};
|
|
42934
|
+
} catch (err) {
|
|
42935
|
+
logger.warn("faq.compose.failed", { error: String(err) });
|
|
42936
|
+
return { prUrl: "", error: String(err) };
|
|
42937
|
+
} finally {
|
|
42938
|
+
if (existsSync14(workDir)) {
|
|
42939
|
+
try {
|
|
42940
|
+
rmSync7(workDir, { recursive: true, force: true });
|
|
42941
|
+
} catch {
|
|
42942
|
+
}
|
|
42943
|
+
}
|
|
42944
|
+
}
|
|
42945
|
+
}
|
|
42946
|
+
|
|
42743
42947
|
// src/core/ws-client/handlers/support.ts
|
|
42744
42948
|
function handleSupportChannels(ctx, message, payload) {
|
|
42745
42949
|
switch (message.channel) {
|
|
@@ -42796,6 +43000,28 @@ function handleSupportChannels(ctx, message, payload) {
|
|
|
42796
43000
|
}).finally(() => ctx.bus.endBusy("knowledge-compose"));
|
|
42797
43001
|
return true;
|
|
42798
43002
|
}
|
|
43003
|
+
case WsChannels.FAQ_COMPOSE: {
|
|
43004
|
+
const freq = payload;
|
|
43005
|
+
const fcfg = readProfileConfig(ctx.profileDir);
|
|
43006
|
+
const fcmd = fcfg.aiCli || "claude";
|
|
43007
|
+
const fai = {
|
|
43008
|
+
cmd: fcmd,
|
|
43009
|
+
profiles: resolveClaudeProfiles(fcfg, getWorkingProfile(ctx.profileDir, fcmd)),
|
|
43010
|
+
env: fcfg.aiEnv
|
|
43011
|
+
};
|
|
43012
|
+
ctx.bus.push({ source: "server", kind: "aireq", text: `${fcmd} \u2039 synthesize FAQ (${freq.tickets.length} tickets)` });
|
|
43013
|
+
ctx.bus.startBusy("faq-compose");
|
|
43014
|
+
void runFaqCompose(freq, fai).then((reply) => {
|
|
43015
|
+
if (reply.error) {
|
|
43016
|
+
ctx.bus.push({ source: "server", kind: "log", text: `faq-compose failed: ${reply.error}`, level: "warn" });
|
|
43017
|
+
} else {
|
|
43018
|
+
ctx.bus.push({ source: "server", kind: "aires", text: `${fcmd} \u203A ${reply.prUrl || "no changes"}` });
|
|
43019
|
+
}
|
|
43020
|
+
reportToolResult(fcmd, !reply.error, reply.error);
|
|
43021
|
+
ctx.send(WsChannels.FAQ_COMPOSE, reply, message.id);
|
|
43022
|
+
}).finally(() => ctx.bus.endBusy("faq-compose"));
|
|
43023
|
+
return true;
|
|
43024
|
+
}
|
|
42799
43025
|
default:
|
|
42800
43026
|
return false;
|
|
42801
43027
|
}
|
|
@@ -42894,7 +43120,7 @@ function handleToolsChannels(ctx, message, payload) {
|
|
|
42894
43120
|
|
|
42895
43121
|
// src/core/agents.ts
|
|
42896
43122
|
import { readFile as readFile10, writeFile as writeFile8, mkdir as mkdir8, readdir as readdir4, rm as rm5 } from "fs/promises";
|
|
42897
|
-
import { join as
|
|
43123
|
+
import { join as join25 } from "path";
|
|
42898
43124
|
var AGENTS_REL = ".claude/agents";
|
|
42899
43125
|
var SKILLS_REL = ".claude/skills";
|
|
42900
43126
|
var MEMORY_DIRS = /* @__PURE__ */ new Set(["short-memory", "long-memory"]);
|
|
@@ -42918,17 +43144,17 @@ async function listAgents(root) {
|
|
|
42918
43144
|
const subagents = [];
|
|
42919
43145
|
const skills = [];
|
|
42920
43146
|
try {
|
|
42921
|
-
for (const e of await readdir4(
|
|
43147
|
+
for (const e of await readdir4(join25(root, AGENTS_REL), { withFileTypes: true })) {
|
|
42922
43148
|
if (!e.isFile() || !e.name.endsWith(".md") || MEMORY_DIRS.has(e.name)) continue;
|
|
42923
|
-
const fm = frontmatter(await readText2(
|
|
43149
|
+
const fm = frontmatter(await readText2(join25(root, AGENTS_REL, e.name)));
|
|
42924
43150
|
subagents.push({ name: e.name.replace(/\.md$/, ""), description: fm.description });
|
|
42925
43151
|
}
|
|
42926
43152
|
} catch {
|
|
42927
43153
|
}
|
|
42928
43154
|
try {
|
|
42929
|
-
for (const e of await readdir4(
|
|
43155
|
+
for (const e of await readdir4(join25(root, SKILLS_REL), { withFileTypes: true })) {
|
|
42930
43156
|
if (!e.isDirectory()) continue;
|
|
42931
|
-
const fm = frontmatter(await readText2(
|
|
43157
|
+
const fm = frontmatter(await readText2(join25(root, SKILLS_REL, e.name, "SKILL.md")));
|
|
42932
43158
|
skills.push({ name: e.name, description: fm.description });
|
|
42933
43159
|
}
|
|
42934
43160
|
} catch {
|
|
@@ -42939,15 +43165,15 @@ async function listAgents(root) {
|
|
|
42939
43165
|
}
|
|
42940
43166
|
function itemPath(root, kind, name) {
|
|
42941
43167
|
const n = safeName(name);
|
|
42942
|
-
return kind === "subagent" ?
|
|
43168
|
+
return kind === "subagent" ? join25(root, AGENTS_REL, `${n}.md`) : join25(root, SKILLS_REL, n, "SKILL.md");
|
|
42943
43169
|
}
|
|
42944
43170
|
async function readAgent(root, kind, name) {
|
|
42945
43171
|
const content = await readText2(itemPath(root, kind, name));
|
|
42946
43172
|
if (kind !== "subagent") return { content };
|
|
42947
43173
|
const n = safeName(name);
|
|
42948
43174
|
const [short, long] = await Promise.all([
|
|
42949
|
-
readText2(
|
|
42950
|
-
readText2(
|
|
43175
|
+
readText2(join25(root, AGENTS_REL, "short-memory", `${n}.md`)),
|
|
43176
|
+
readText2(join25(root, AGENTS_REL, "long-memory", `${n}.md`))
|
|
42951
43177
|
]);
|
|
42952
43178
|
return { content, memory: { short, long } };
|
|
42953
43179
|
}
|
|
@@ -42957,12 +43183,12 @@ async function writeAgent(root, req) {
|
|
|
42957
43183
|
try {
|
|
42958
43184
|
if (req.action === "delete") {
|
|
42959
43185
|
if (req.kind === "subagent") await rm5(itemPath(root, "subagent", n), { force: true });
|
|
42960
|
-
else await rm5(
|
|
43186
|
+
else await rm5(join25(root, SKILLS_REL, n), { recursive: true, force: true });
|
|
42961
43187
|
return { ok: true };
|
|
42962
43188
|
}
|
|
42963
43189
|
if (req.content === void 0) return { ok: false, error: "content is required" };
|
|
42964
43190
|
const path = itemPath(root, req.kind, n);
|
|
42965
|
-
await mkdir8(
|
|
43191
|
+
await mkdir8(join25(path, ".."), { recursive: true });
|
|
42966
43192
|
await writeFile8(path, req.content, "utf8");
|
|
42967
43193
|
return { ok: true };
|
|
42968
43194
|
} catch (err) {
|
|
@@ -42972,7 +43198,7 @@ async function writeAgent(root, req) {
|
|
|
42972
43198
|
|
|
42973
43199
|
// src/core/secrets.ts
|
|
42974
43200
|
import { readFile as readFile11, writeFile as writeFile9 } from "fs/promises";
|
|
42975
|
-
import { join as
|
|
43201
|
+
import { join as join26 } from "path";
|
|
42976
43202
|
var SECURITY_REL = "AI_SECURITY.md";
|
|
42977
43203
|
var PLACEHOLDER_REL = "AI_PLACEHOLDER.md";
|
|
42978
43204
|
var SECRETS_REL = "project.secrets.json";
|
|
@@ -42985,7 +43211,7 @@ async function readText3(path, fallback = "") {
|
|
|
42985
43211
|
}
|
|
42986
43212
|
async function readSecretsObject(root) {
|
|
42987
43213
|
try {
|
|
42988
|
-
const v2 = JSON.parse(await readFile11(
|
|
43214
|
+
const v2 = JSON.parse(await readFile11(join26(root, SECRETS_REL), "utf8"));
|
|
42989
43215
|
return v2 && typeof v2 === "object" ? v2 : {};
|
|
42990
43216
|
} catch {
|
|
42991
43217
|
return {};
|
|
@@ -43004,8 +43230,8 @@ function placeholderKeys(text) {
|
|
|
43004
43230
|
}
|
|
43005
43231
|
async function readSecrets(root) {
|
|
43006
43232
|
const [security, placeholder, secrets] = await Promise.all([
|
|
43007
|
-
readText3(
|
|
43008
|
-
readText3(
|
|
43233
|
+
readText3(join26(root, SECURITY_REL)),
|
|
43234
|
+
readText3(join26(root, PLACEHOLDER_REL)),
|
|
43009
43235
|
readSecretsObject(root)
|
|
43010
43236
|
]);
|
|
43011
43237
|
const names = /* @__PURE__ */ new Set([...placeholderKeys(placeholder), ...Object.keys(secrets)]);
|
|
@@ -43018,14 +43244,14 @@ async function readSecrets(root) {
|
|
|
43018
43244
|
async function writeSecrets(root, req) {
|
|
43019
43245
|
try {
|
|
43020
43246
|
if (req.kind === "security") {
|
|
43021
|
-
await writeFile9(
|
|
43247
|
+
await writeFile9(join26(root, SECURITY_REL), req.content, "utf8");
|
|
43022
43248
|
} else if (req.kind === "placeholder") {
|
|
43023
|
-
await writeFile9(
|
|
43249
|
+
await writeFile9(join26(root, PLACEHOLDER_REL), req.content, "utf8");
|
|
43024
43250
|
} else {
|
|
43025
43251
|
const secrets = await readSecretsObject(root);
|
|
43026
43252
|
if (req.kind === "secret") secrets[req.key] = req.value;
|
|
43027
43253
|
else delete secrets[req.key];
|
|
43028
|
-
await writeFile9(
|
|
43254
|
+
await writeFile9(join26(root, SECRETS_REL), JSON.stringify(secrets, null, 2) + "\n", "utf8");
|
|
43029
43255
|
}
|
|
43030
43256
|
return { ok: true };
|
|
43031
43257
|
} catch (err) {
|
|
@@ -43035,13 +43261,12 @@ async function writeSecrets(root, req) {
|
|
|
43035
43261
|
|
|
43036
43262
|
// src/core/agent-tools.ts
|
|
43037
43263
|
import { readFile as readFile12, writeFile as writeFile10, mkdir as mkdir9 } from "fs/promises";
|
|
43038
|
-
import { dirname as dirname8, join as
|
|
43264
|
+
import { dirname as dirname8, join as join27 } from "path";
|
|
43039
43265
|
var SHARED_REL = ".claude/settings.json";
|
|
43040
43266
|
var LOCAL_REL = ".claude/settings.local.json";
|
|
43041
43267
|
var SECRETS_DENY = "Read(./project.secrets.json)";
|
|
43042
|
-
var MODES = ["default", "acceptEdits", "plan", "bypassPermissions"];
|
|
43043
43268
|
function fileFor(root, scope) {
|
|
43044
|
-
return
|
|
43269
|
+
return join27(root, scope === "shared" ? SHARED_REL : LOCAL_REL);
|
|
43045
43270
|
}
|
|
43046
43271
|
async function readSettingsObject(path) {
|
|
43047
43272
|
try {
|
|
@@ -43053,9 +43278,8 @@ async function readSettingsObject(path) {
|
|
|
43053
43278
|
}
|
|
43054
43279
|
function normalizePermissions(raw) {
|
|
43055
43280
|
const p2 = raw && typeof raw === "object" ? raw : {};
|
|
43056
|
-
const mode = MODES.includes(p2.defaultMode) ? p2.defaultMode : "default";
|
|
43057
43281
|
const list = (v2) => Array.isArray(v2) ? v2.filter((x) => typeof x === "string") : [];
|
|
43058
|
-
return { defaultMode:
|
|
43282
|
+
return { defaultMode: "bypassPermissions", allow: list(p2.allow), deny: list(p2.deny) };
|
|
43059
43283
|
}
|
|
43060
43284
|
async function readAgentTools(root, scope) {
|
|
43061
43285
|
const obj = await readSettingsObject(fileFor(root, scope));
|
|
@@ -43079,7 +43303,7 @@ async function writeAgentTools(root, scope, permissions) {
|
|
|
43079
43303
|
// src/core/packages.ts
|
|
43080
43304
|
import { createHash as createHash4 } from "crypto";
|
|
43081
43305
|
import { readFile as readFile13, writeFile as writeFile11, mkdir as mkdir10, readdir as readdir5, rm as rm6, stat as stat4 } from "fs/promises";
|
|
43082
|
-
import { dirname as dirname9, join as
|
|
43306
|
+
import { dirname as dirname9, join as join28, relative } from "path";
|
|
43083
43307
|
var AGENTS_REL2 = ".claude/agents";
|
|
43084
43308
|
var SKILLS_REL2 = ".claude/skills";
|
|
43085
43309
|
var MANIFEST_REL = ".claude/.4pm-packages.json";
|
|
@@ -43103,7 +43327,7 @@ async function listFilesRel(dir2) {
|
|
|
43103
43327
|
return;
|
|
43104
43328
|
}
|
|
43105
43329
|
for (const e of entries2) {
|
|
43106
|
-
const full =
|
|
43330
|
+
const full = join28(d, e.name);
|
|
43107
43331
|
if (e.isDirectory()) await walk2(full);
|
|
43108
43332
|
else if (e.isFile()) out.push(relative(dir2, full));
|
|
43109
43333
|
}
|
|
@@ -43114,16 +43338,16 @@ async function listFilesRel(dir2) {
|
|
|
43114
43338
|
async function readArtifactFiles(root, kind, slug) {
|
|
43115
43339
|
const files = [];
|
|
43116
43340
|
if (kind === "subagent") {
|
|
43117
|
-
const agent = await readMaybe(
|
|
43341
|
+
const agent = await readMaybe(join28(root, AGENTS_REL2, `${slug}.md`));
|
|
43118
43342
|
if (agent) files.push({ path: "agent.md", contentBase64: agent.toString("base64") });
|
|
43119
|
-
const short = await readMaybe(
|
|
43343
|
+
const short = await readMaybe(join28(root, AGENTS_REL2, "short-memory", `${slug}.md`));
|
|
43120
43344
|
if (short) files.push({ path: "short-memory.md", contentBase64: short.toString("base64") });
|
|
43121
|
-
const long = await readMaybe(
|
|
43345
|
+
const long = await readMaybe(join28(root, AGENTS_REL2, "long-memory", `${slug}.md`));
|
|
43122
43346
|
if (long) files.push({ path: "long-memory.md", contentBase64: long.toString("base64") });
|
|
43123
43347
|
} else {
|
|
43124
|
-
const base =
|
|
43348
|
+
const base = join28(root, SKILLS_REL2, slug);
|
|
43125
43349
|
for (const rel of await listFilesRel(base)) {
|
|
43126
|
-
const buf = await readMaybe(
|
|
43350
|
+
const buf = await readMaybe(join28(base, rel));
|
|
43127
43351
|
if (buf) files.push({ path: rel.split("\\").join("/"), contentBase64: buf.toString("base64") });
|
|
43128
43352
|
}
|
|
43129
43353
|
}
|
|
@@ -43141,7 +43365,7 @@ async function artifactSha256(root, kind, slug) {
|
|
|
43141
43365
|
return h.digest("hex");
|
|
43142
43366
|
}
|
|
43143
43367
|
async function artifactExists(root, kind, slug) {
|
|
43144
|
-
const target = kind === "subagent" ?
|
|
43368
|
+
const target = kind === "subagent" ? join28(root, AGENTS_REL2, `${slug}.md`) : join28(root, SKILLS_REL2, slug);
|
|
43145
43369
|
try {
|
|
43146
43370
|
await stat4(target);
|
|
43147
43371
|
return true;
|
|
@@ -43150,7 +43374,7 @@ async function artifactExists(root, kind, slug) {
|
|
|
43150
43374
|
}
|
|
43151
43375
|
}
|
|
43152
43376
|
async function readManifest(root) {
|
|
43153
|
-
const buf = await readMaybe(
|
|
43377
|
+
const buf = await readMaybe(join28(root, MANIFEST_REL));
|
|
43154
43378
|
if (!buf) return {};
|
|
43155
43379
|
try {
|
|
43156
43380
|
return JSON.parse(buf.toString("utf8"));
|
|
@@ -43159,7 +43383,7 @@ async function readManifest(root) {
|
|
|
43159
43383
|
}
|
|
43160
43384
|
}
|
|
43161
43385
|
async function writeManifest(root, manifest) {
|
|
43162
|
-
const path =
|
|
43386
|
+
const path = join28(root, MANIFEST_REL);
|
|
43163
43387
|
await mkdir10(dirname9(path), { recursive: true });
|
|
43164
43388
|
await writeFile11(path, JSON.stringify(manifest, null, 2), "utf8");
|
|
43165
43389
|
}
|
|
@@ -43168,24 +43392,24 @@ async function writeArtifactFiles(root, kind, slug, files) {
|
|
|
43168
43392
|
if (kind === "subagent") {
|
|
43169
43393
|
const agent = byPath.get("agent.md");
|
|
43170
43394
|
if (agent) {
|
|
43171
|
-
await mkdir10(
|
|
43172
|
-
await writeFile11(
|
|
43395
|
+
await mkdir10(join28(root, AGENTS_REL2), { recursive: true });
|
|
43396
|
+
await writeFile11(join28(root, AGENTS_REL2, `${slug}.md`), agent);
|
|
43173
43397
|
}
|
|
43174
43398
|
const short = byPath.get("short-memory.md");
|
|
43175
43399
|
if (short) {
|
|
43176
|
-
await mkdir10(
|
|
43177
|
-
await writeFile11(
|
|
43400
|
+
await mkdir10(join28(root, AGENTS_REL2, "short-memory"), { recursive: true });
|
|
43401
|
+
await writeFile11(join28(root, AGENTS_REL2, "short-memory", `${slug}.md`), short);
|
|
43178
43402
|
}
|
|
43179
43403
|
const long = byPath.get("long-memory.md");
|
|
43180
43404
|
if (long) {
|
|
43181
|
-
await mkdir10(
|
|
43182
|
-
await writeFile11(
|
|
43405
|
+
await mkdir10(join28(root, AGENTS_REL2, "long-memory"), { recursive: true });
|
|
43406
|
+
await writeFile11(join28(root, AGENTS_REL2, "long-memory", `${slug}.md`), long);
|
|
43183
43407
|
}
|
|
43184
43408
|
} else {
|
|
43185
|
-
const base =
|
|
43409
|
+
const base = join28(root, SKILLS_REL2, slug);
|
|
43186
43410
|
for (const [rel, buf] of byPath) {
|
|
43187
43411
|
if (rel.includes("..") || rel.startsWith("/")) continue;
|
|
43188
|
-
const dest =
|
|
43412
|
+
const dest = join28(base, rel);
|
|
43189
43413
|
await mkdir10(dirname9(dest), { recursive: true });
|
|
43190
43414
|
await writeFile11(dest, buf);
|
|
43191
43415
|
}
|
|
@@ -43251,11 +43475,11 @@ async function removePackage(root, slug) {
|
|
|
43251
43475
|
const entry = manifest[n];
|
|
43252
43476
|
if (!entry) return { ok: false, error: "not installed" };
|
|
43253
43477
|
if (entry.kind === "subagent") {
|
|
43254
|
-
await rm6(
|
|
43255
|
-
await rm6(
|
|
43256
|
-
await rm6(
|
|
43478
|
+
await rm6(join28(root, AGENTS_REL2, `${n}.md`), { force: true });
|
|
43479
|
+
await rm6(join28(root, AGENTS_REL2, "short-memory", `${n}.md`), { force: true });
|
|
43480
|
+
await rm6(join28(root, AGENTS_REL2, "long-memory", `${n}.md`), { force: true });
|
|
43257
43481
|
} else {
|
|
43258
|
-
await rm6(
|
|
43482
|
+
await rm6(join28(root, SKILLS_REL2, n), { recursive: true, force: true });
|
|
43259
43483
|
}
|
|
43260
43484
|
delete manifest[n];
|
|
43261
43485
|
await writeManifest(root, manifest);
|
|
@@ -43267,7 +43491,7 @@ async function removePackage(root, slug) {
|
|
|
43267
43491
|
|
|
43268
43492
|
// src/core/graph.ts
|
|
43269
43493
|
import { readFile as readFile14, readdir as readdir6 } from "fs/promises";
|
|
43270
|
-
import { join as
|
|
43494
|
+
import { join as join29, relative as relative2, dirname as dirname10, resolve as resolve9, extname as extname2, basename as basename6 } from "path";
|
|
43271
43495
|
var MAX_FILES = 2e3;
|
|
43272
43496
|
var MAX_NODES = 3e3;
|
|
43273
43497
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "out", "coverage", ".turbo"]);
|
|
@@ -43287,7 +43511,7 @@ async function walk(root, accept) {
|
|
|
43287
43511
|
if (e.name.startsWith(".") && e.name !== ".claude") {
|
|
43288
43512
|
if (e.isDirectory()) continue;
|
|
43289
43513
|
}
|
|
43290
|
-
const full =
|
|
43514
|
+
const full = join29(dir2, e.name);
|
|
43291
43515
|
if (e.isDirectory()) {
|
|
43292
43516
|
if (!SKIP_DIRS.has(e.name)) stack.push(full);
|
|
43293
43517
|
} else if (accept(full)) {
|
|
@@ -43407,12 +43631,12 @@ async function buildGraph(root, mode) {
|
|
|
43407
43631
|
|
|
43408
43632
|
// src/core/rag.ts
|
|
43409
43633
|
import { readFile as readFile15, writeFile as writeFile12, statfs as statfs2, mkdir as mkdir11 } from "fs/promises";
|
|
43410
|
-
import { existsSync as
|
|
43411
|
-
import { execFile as
|
|
43634
|
+
import { existsSync as existsSync15 } from "fs";
|
|
43635
|
+
import { execFile as execFile11, spawn as spawn10 } from "child_process";
|
|
43412
43636
|
import { totalmem as totalmem2, cpus as cpus2 } from "os";
|
|
43413
|
-
import { join as
|
|
43414
|
-
import { promisify as
|
|
43415
|
-
var run10 =
|
|
43637
|
+
import { join as join30 } from "path";
|
|
43638
|
+
import { promisify as promisify11 } from "util";
|
|
43639
|
+
var run10 = promisify11(execFile11);
|
|
43416
43640
|
var RAG_REL = ".claude/rag";
|
|
43417
43641
|
async function tryRun(cmd, args) {
|
|
43418
43642
|
try {
|
|
@@ -43431,7 +43655,7 @@ async function detectPython() {
|
|
|
43431
43655
|
}
|
|
43432
43656
|
async function installLog(root) {
|
|
43433
43657
|
try {
|
|
43434
|
-
const text = await readFile15(
|
|
43658
|
+
const text = await readFile15(join30(root, RAG_REL, "install.log"), "utf8");
|
|
43435
43659
|
return text.split("\n").filter((l) => l.length > 0).slice(-200);
|
|
43436
43660
|
} catch {
|
|
43437
43661
|
return [];
|
|
@@ -43493,14 +43717,14 @@ async function ragStatus(root) {
|
|
|
43493
43717
|
};
|
|
43494
43718
|
let modelName = "";
|
|
43495
43719
|
try {
|
|
43496
|
-
modelName = (await readFile15(
|
|
43720
|
+
modelName = (await readFile15(join30(root, RAG_REL, ".installed"), "utf8")).trim();
|
|
43497
43721
|
} catch {
|
|
43498
43722
|
modelName = "";
|
|
43499
43723
|
}
|
|
43500
43724
|
const modelPresent = modelName.length > 0;
|
|
43501
43725
|
return {
|
|
43502
43726
|
installed: fastembed && sqliteVec && modelPresent,
|
|
43503
|
-
installing:
|
|
43727
|
+
installing: existsSync15(join30(root, RAG_REL, ".installing")),
|
|
43504
43728
|
python: { found: py.found, version: py.version },
|
|
43505
43729
|
deps: { pip, fastembed, sqliteVec },
|
|
43506
43730
|
model: { present: modelPresent, name: modelName },
|
|
@@ -43511,9 +43735,9 @@ async function ragStatus(root) {
|
|
|
43511
43735
|
};
|
|
43512
43736
|
}
|
|
43513
43737
|
async function readIndexMeta(root) {
|
|
43514
|
-
const indexing =
|
|
43738
|
+
const indexing = existsSync15(join30(root, RAG_REL, ".indexing"));
|
|
43515
43739
|
try {
|
|
43516
|
-
const m2 = JSON.parse(await readFile15(
|
|
43740
|
+
const m2 = JSON.parse(await readFile15(join30(root, RAG_REL, ".index.json"), "utf8"));
|
|
43517
43741
|
return {
|
|
43518
43742
|
present: true,
|
|
43519
43743
|
indexing,
|
|
@@ -43580,14 +43804,14 @@ if __name__ == "__main__":
|
|
|
43580
43804
|
elif cmd == "query": query(sys.argv[2], int(sys.argv[3]) if len(sys.argv) > 3 else 8)
|
|
43581
43805
|
`;
|
|
43582
43806
|
async function ensureEngine(root) {
|
|
43583
|
-
await mkdir11(
|
|
43584
|
-
await writeFile12(
|
|
43807
|
+
await mkdir11(join30(root, RAG_REL), { recursive: true }).catch(() => void 0);
|
|
43808
|
+
await writeFile12(join30(root, RAG_REL, "rag_engine.py"), RAG_ENGINE_PY, "utf8");
|
|
43585
43809
|
}
|
|
43586
43810
|
async function ragReindex(root) {
|
|
43587
43811
|
const py = await detectPython();
|
|
43588
43812
|
if (!py.found) return { ok: false, started: false, error: "python3 not found" };
|
|
43589
|
-
if (!
|
|
43590
|
-
if (
|
|
43813
|
+
if (!existsSync15(join30(root, RAG_REL, ".installed"))) return { ok: false, started: false, error: "RAG is not installed" };
|
|
43814
|
+
if (existsSync15(join30(root, RAG_REL, ".indexing"))) return { ok: true, started: true };
|
|
43591
43815
|
await ensureEngine(root);
|
|
43592
43816
|
const script = `
|
|
43593
43817
|
set +e
|
|
@@ -43595,7 +43819,7 @@ touch "${RAG_REL}/.indexing"
|
|
|
43595
43819
|
{ echo "[reindex] $(date)"; "$RAG_PY" "${RAG_REL}/rag_engine.py" reindex 2>&1; } >> "${RAG_REL}/index.log" 2>&1
|
|
43596
43820
|
rm -f "${RAG_REL}/.indexing"
|
|
43597
43821
|
`;
|
|
43598
|
-
const child =
|
|
43822
|
+
const child = spawn10("bash", ["-c", script], {
|
|
43599
43823
|
cwd: root,
|
|
43600
43824
|
env: { ...process.env, RAG_PY: py.bin },
|
|
43601
43825
|
detached: true,
|
|
@@ -43607,12 +43831,12 @@ rm -f "${RAG_REL}/.indexing"
|
|
|
43607
43831
|
async function ragQuery(root, queryText, k2 = 8) {
|
|
43608
43832
|
const py = await detectPython();
|
|
43609
43833
|
if (!py.found) return { results: [], error: "python3 not found" };
|
|
43610
|
-
if (!
|
|
43834
|
+
if (!existsSync15(join30(root, RAG_REL, "index.db"))) return { results: [], error: "no index \u2014 reindex first" };
|
|
43611
43835
|
await ensureEngine(root);
|
|
43612
43836
|
try {
|
|
43613
43837
|
const { stdout } = await run10(
|
|
43614
43838
|
py.bin,
|
|
43615
|
-
[
|
|
43839
|
+
[join30(RAG_REL, "rag_engine.py"), "query", queryText, String(Math.min(Math.max(k2, 1), 20))],
|
|
43616
43840
|
{ cwd: root, timeout: 3e4, maxBuffer: 4 * 1024 * 1024 }
|
|
43617
43841
|
);
|
|
43618
43842
|
const parsed = JSON.parse(stdout.trim() || '{"results":[]}');
|
|
@@ -43627,9 +43851,9 @@ async function ragInstall(root, model) {
|
|
|
43627
43851
|
}
|
|
43628
43852
|
const py = await detectPython();
|
|
43629
43853
|
if (!py.found) return { ok: false, started: false, error: "python3 not found on the worker" };
|
|
43630
|
-
if (
|
|
43854
|
+
if (existsSync15(join30(root, RAG_REL, ".installing"))) return { ok: true, started: true };
|
|
43631
43855
|
try {
|
|
43632
|
-
await mkdir11(
|
|
43856
|
+
await mkdir11(join30(root, RAG_REL), { recursive: true });
|
|
43633
43857
|
} catch {
|
|
43634
43858
|
}
|
|
43635
43859
|
const script = `
|
|
@@ -43645,7 +43869,7 @@ rm -f "${RAG_REL}/.installed"
|
|
|
43645
43869
|
} >> "${RAG_REL}/install.log" 2>&1
|
|
43646
43870
|
rm -f "${RAG_REL}/.installing"
|
|
43647
43871
|
`;
|
|
43648
|
-
const child =
|
|
43872
|
+
const child = spawn10("bash", ["-c", script], {
|
|
43649
43873
|
cwd: root,
|
|
43650
43874
|
env: { ...process.env, RAG_PY: py.bin, RAG_MODEL: model },
|
|
43651
43875
|
detached: true,
|
|
@@ -43656,8 +43880,8 @@ rm -f "${RAG_REL}/.installing"
|
|
|
43656
43880
|
}
|
|
43657
43881
|
|
|
43658
43882
|
// src/core/config-sync.ts
|
|
43659
|
-
import { existsSync as
|
|
43660
|
-
import { join as
|
|
43883
|
+
import { existsSync as existsSync16, readFileSync as readFileSync16 } from "fs";
|
|
43884
|
+
import { join as join31 } from "path";
|
|
43661
43885
|
var SERVER_MANAGED_KEYS = [
|
|
43662
43886
|
"physicPath",
|
|
43663
43887
|
"sessionSwitchPct",
|
|
@@ -43674,8 +43898,8 @@ var SERVER_MANAGED_KEYS = [
|
|
|
43674
43898
|
"projectAiMemoryBudgetChars"
|
|
43675
43899
|
];
|
|
43676
43900
|
function readConfigText(profileDir2) {
|
|
43677
|
-
const path =
|
|
43678
|
-
if (
|
|
43901
|
+
const path = join31(profileDir2, "config.json");
|
|
43902
|
+
if (existsSync16(path)) {
|
|
43679
43903
|
try {
|
|
43680
43904
|
return readFileSync16(path, "utf8");
|
|
43681
43905
|
} catch {
|
|
@@ -44464,7 +44688,7 @@ var WsClient = class {
|
|
|
44464
44688
|
}
|
|
44465
44689
|
const { payload, nonce } = encryptPayload(this.sessionKey, data);
|
|
44466
44690
|
const envelope = {
|
|
44467
|
-
id:
|
|
44691
|
+
id: randomUUID4(),
|
|
44468
44692
|
replyTo,
|
|
44469
44693
|
channel,
|
|
44470
44694
|
wsToken: this.wsToken,
|
|
@@ -44485,7 +44709,7 @@ var WsClient = class {
|
|
|
44485
44709
|
}
|
|
44486
44710
|
const { payload, nonce } = encryptPayload(this.sessionKey, data);
|
|
44487
44711
|
const envelope = {
|
|
44488
|
-
id:
|
|
44712
|
+
id: randomUUID4(),
|
|
44489
44713
|
replyTo: null,
|
|
44490
44714
|
channel,
|
|
44491
44715
|
wsToken: this.wsToken,
|
|
@@ -44552,8 +44776,8 @@ var WsClient = class {
|
|
|
44552
44776
|
/** Create the physic folder at an absolute path if missing (best-effort). */
|
|
44553
44777
|
ensurePhysicFolderPath(folder) {
|
|
44554
44778
|
try {
|
|
44555
|
-
if (!
|
|
44556
|
-
|
|
44779
|
+
if (!existsSync17(folder)) {
|
|
44780
|
+
mkdirSync8(folder, { recursive: true });
|
|
44557
44781
|
this.bus.log(t("project.createdFolder", { folder }));
|
|
44558
44782
|
}
|
|
44559
44783
|
} catch (err) {
|
|
@@ -44567,7 +44791,7 @@ var WsClient = class {
|
|
|
44567
44791
|
*/
|
|
44568
44792
|
physicFolderPath(projectName) {
|
|
44569
44793
|
const safe = projectName.replace(/[/\\]/g, "_").replace(/\.\./g, "_").trim();
|
|
44570
|
-
return safe ?
|
|
44794
|
+
return safe ? join32(this.context.profileDir, safe) : null;
|
|
44571
44795
|
}
|
|
44572
44796
|
/**
|
|
44573
44797
|
* Report machine.status: version + the state of the physic project being served
|
|
@@ -44582,7 +44806,7 @@ var WsClient = class {
|
|
|
44582
44806
|
projects: [],
|
|
44583
44807
|
version: CLI_VERSION,
|
|
44584
44808
|
physicPath,
|
|
44585
|
-
physicPathExists: physicPath ?
|
|
44809
|
+
physicPathExists: physicPath ? existsSync17(physicPath) : void 0,
|
|
44586
44810
|
// Real coarse state (ADR-0152): !paused and a recent tick (was hard-coded false).
|
|
44587
44811
|
autonomousRunning: physicPath ? isAutonomousRunning(physicPath, this.context.profileDir) : false,
|
|
44588
44812
|
// The machine-user AI-run limit (0 = unlimited) + profile count so a dispatch can resolve the
|
|
@@ -44676,7 +44900,7 @@ var WsClient = class {
|
|
|
44676
44900
|
*/
|
|
44677
44901
|
uploadLog() {
|
|
44678
44902
|
try {
|
|
44679
|
-
const upload = readLogUpload(
|
|
44903
|
+
const upload = readLogUpload(join32(this.context.profileDir, "logs"));
|
|
44680
44904
|
if (!upload) return;
|
|
44681
44905
|
const sig = `${upload.fileName}:${upload.totalBytes}`;
|
|
44682
44906
|
if (sig === this.lastLogSig) return;
|
|
@@ -44838,10 +45062,10 @@ var WsClient = class {
|
|
|
44838
45062
|
};
|
|
44839
45063
|
|
|
44840
45064
|
// src/core/instance-lock.ts
|
|
44841
|
-
import { readFileSync as readFileSync17, rmSync as
|
|
44842
|
-
import { join as
|
|
45065
|
+
import { readFileSync as readFileSync17, rmSync as rmSync8, writeFileSync as writeFileSync9 } from "fs";
|
|
45066
|
+
import { join as join33 } from "path";
|
|
44843
45067
|
function lockPath(profileDir2) {
|
|
44844
|
-
return
|
|
45068
|
+
return join33(profileDir2, "start.lock");
|
|
44845
45069
|
}
|
|
44846
45070
|
function isAlive(pid) {
|
|
44847
45071
|
try {
|
|
@@ -44867,14 +45091,14 @@ function acquireInstanceLock(profileDir2) {
|
|
|
44867
45091
|
function releaseInstanceLock(profileDir2) {
|
|
44868
45092
|
try {
|
|
44869
45093
|
const pid = Number.parseInt(readFileSync17(lockPath(profileDir2), "utf8").trim(), 10);
|
|
44870
|
-
if (pid === process.pid)
|
|
45094
|
+
if (pid === process.pid) rmSync8(lockPath(profileDir2), { force: true });
|
|
44871
45095
|
} catch {
|
|
44872
45096
|
}
|
|
44873
45097
|
}
|
|
44874
45098
|
|
|
44875
45099
|
// src/core/session-bus.ts
|
|
44876
45100
|
import { EventEmitter } from "events";
|
|
44877
|
-
import { randomUUID as
|
|
45101
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
44878
45102
|
var REPLAY_CAP = 500;
|
|
44879
45103
|
var SessionBus = class {
|
|
44880
45104
|
emitter = new EventEmitter();
|
|
@@ -44905,7 +45129,7 @@ var SessionBus = class {
|
|
|
44905
45129
|
/** Producer → consumers: push one transcript entry. Returns its id (for `updateEntry`). */
|
|
44906
45130
|
push(input) {
|
|
44907
45131
|
const entry = {
|
|
44908
|
-
id:
|
|
45132
|
+
id: randomUUID5(),
|
|
44909
45133
|
level: "info",
|
|
44910
45134
|
...input,
|
|
44911
45135
|
ts: Date.now()
|
|
@@ -45162,8 +45386,8 @@ var SessionBus = class {
|
|
|
45162
45386
|
|
|
45163
45387
|
// src/core/control-server.ts
|
|
45164
45388
|
import { createServer } from "net";
|
|
45165
|
-
import { chmodSync as chmodSync4, rmSync as
|
|
45166
|
-
import { join as
|
|
45389
|
+
import { chmodSync as chmodSync4, rmSync as rmSync9 } from "fs";
|
|
45390
|
+
import { join as join35 } from "path";
|
|
45167
45391
|
|
|
45168
45392
|
// src/core/control-protocol.ts
|
|
45169
45393
|
var CONTROL_SOCKET_FILE = "control.sock";
|
|
@@ -45194,9 +45418,9 @@ function createFrameParser() {
|
|
|
45194
45418
|
// src/core/control-token.ts
|
|
45195
45419
|
import { chmodSync as chmodSync3, readFileSync as readFileSync18, writeFileSync as writeFileSync10 } from "fs";
|
|
45196
45420
|
import { randomBytes as randomBytes3, timingSafeEqual } from "crypto";
|
|
45197
|
-
import { join as
|
|
45421
|
+
import { join as join34 } from "path";
|
|
45198
45422
|
function controlTokenPath(profileDir2) {
|
|
45199
|
-
return
|
|
45423
|
+
return join34(profileDir2, CONTROL_TOKEN_FILE);
|
|
45200
45424
|
}
|
|
45201
45425
|
function writeControlToken(profileDir2) {
|
|
45202
45426
|
const token = randomBytes3(32).toString("hex");
|
|
@@ -45225,9 +45449,9 @@ function tokenMatches(expected, given) {
|
|
|
45225
45449
|
|
|
45226
45450
|
// src/core/control-server.ts
|
|
45227
45451
|
function startControlServer(bus, profileDir2, info) {
|
|
45228
|
-
const socketPath =
|
|
45452
|
+
const socketPath = join35(profileDir2, CONTROL_SOCKET_FILE);
|
|
45229
45453
|
try {
|
|
45230
|
-
|
|
45454
|
+
rmSync9(socketPath, { force: true });
|
|
45231
45455
|
} catch {
|
|
45232
45456
|
}
|
|
45233
45457
|
const clients = /* @__PURE__ */ new Set();
|
|
@@ -45313,8 +45537,8 @@ function startControlServer(bus, profileDir2, info) {
|
|
|
45313
45537
|
clients.clear();
|
|
45314
45538
|
server.close();
|
|
45315
45539
|
try {
|
|
45316
|
-
|
|
45317
|
-
|
|
45540
|
+
rmSync9(socketPath, { force: true });
|
|
45541
|
+
rmSync9(controlTokenPath(profileDir2), { force: true });
|
|
45318
45542
|
} catch {
|
|
45319
45543
|
}
|
|
45320
45544
|
};
|
|
@@ -50977,15 +51201,15 @@ var import_react21 = __toESM(require_react(), 1);
|
|
|
50977
51201
|
var import_react26 = __toESM(require_react(), 1);
|
|
50978
51202
|
|
|
50979
51203
|
// src/core/input-history.ts
|
|
50980
|
-
import { existsSync as
|
|
50981
|
-
import { join as
|
|
51204
|
+
import { existsSync as existsSync19, readFileSync as readFileSync20, writeFileSync as writeFileSync11 } from "fs";
|
|
51205
|
+
import { join as join36 } from "path";
|
|
50982
51206
|
var MAX_HISTORY = 200;
|
|
50983
51207
|
function historyPath(profileDir2) {
|
|
50984
|
-
return
|
|
51208
|
+
return join36(profileDir2, "input-history.json");
|
|
50985
51209
|
}
|
|
50986
51210
|
function loadInputHistory(profileDir2) {
|
|
50987
51211
|
const path = historyPath(profileDir2);
|
|
50988
|
-
if (!
|
|
51212
|
+
if (!existsSync19(path)) return [];
|
|
50989
51213
|
try {
|
|
50990
51214
|
const data = JSON.parse(readFileSync20(path, "utf8"));
|
|
50991
51215
|
return Array.isArray(data) ? data.filter((x) => typeof x === "string") : [];
|
|
@@ -51595,7 +51819,7 @@ async function runStart(profileDir2, profileName) {
|
|
|
51595
51819
|
return;
|
|
51596
51820
|
}
|
|
51597
51821
|
logger.configure({
|
|
51598
|
-
dir:
|
|
51822
|
+
dir: join37(profileDir2, "logs"),
|
|
51599
51823
|
level: resolveLogLevel(),
|
|
51600
51824
|
base: { version: CLI_VERSION, profile: profileName, scope: credential.scope }
|
|
51601
51825
|
});
|
|
@@ -51615,7 +51839,7 @@ async function runStart(profileDir2, profileName) {
|
|
|
51615
51839
|
if (result.action === "updated") {
|
|
51616
51840
|
logger.info("update.updated", { version: result.version });
|
|
51617
51841
|
console.log(t("start.restarting"));
|
|
51618
|
-
const child =
|
|
51842
|
+
const child = spawn11(process.execPath, process.argv.slice(1), {
|
|
51619
51843
|
stdio: "inherit",
|
|
51620
51844
|
env: { ...process.env, FOURPM_NO_UPDATE: "1" }
|
|
51621
51845
|
});
|
|
@@ -51704,7 +51928,7 @@ async function runStart(profileDir2, profileName) {
|
|
|
51704
51928
|
}
|
|
51705
51929
|
|
|
51706
51930
|
// src/commands/attach.ts
|
|
51707
|
-
import { join as
|
|
51931
|
+
import { join as join38 } from "path";
|
|
51708
51932
|
|
|
51709
51933
|
// src/core/control-client.ts
|
|
51710
51934
|
import { connect as connect2 } from "net";
|
|
@@ -51813,7 +52037,7 @@ async function runAttach(profileDir2, profileName) {
|
|
|
51813
52037
|
process.exitCode = 1;
|
|
51814
52038
|
return;
|
|
51815
52039
|
}
|
|
51816
|
-
const socketPath =
|
|
52040
|
+
const socketPath = join38(profileDir2, CONTROL_SOCKET_FILE);
|
|
51817
52041
|
const bus = new SessionBus();
|
|
51818
52042
|
let conn;
|
|
51819
52043
|
try {
|
|
@@ -51844,11 +52068,11 @@ async function runAttach(profileDir2, profileName) {
|
|
|
51844
52068
|
|
|
51845
52069
|
// src/commands/auto-run.ts
|
|
51846
52070
|
import { connect as connect3 } from "net";
|
|
51847
|
-
import { join as
|
|
52071
|
+
import { join as join39 } from "path";
|
|
51848
52072
|
var AUTO_RUN_TIMEOUT_MS = 3 * 60 * 60 * 1e3;
|
|
51849
52073
|
async function runAutoRun(profileDir2, profileName) {
|
|
51850
52074
|
initI18n(readProfileConfig(profileDir2).locale);
|
|
51851
|
-
const socketPath =
|
|
52075
|
+
const socketPath = join39(profileDir2, CONTROL_SOCKET_FILE);
|
|
51852
52076
|
const token = readControlToken(profileDir2);
|
|
51853
52077
|
const code = await new Promise((resolve10) => {
|
|
51854
52078
|
let settled = false;
|
|
@@ -51904,12 +52128,12 @@ async function runAutoRun(profileDir2, profileName) {
|
|
|
51904
52128
|
}
|
|
51905
52129
|
|
|
51906
52130
|
// src/commands/ai-login.ts
|
|
51907
|
-
import { spawn as
|
|
51908
|
-
import { mkdirSync as
|
|
52131
|
+
import { spawn as spawn12 } from "child_process";
|
|
52132
|
+
import { mkdirSync as mkdirSync9 } from "fs";
|
|
51909
52133
|
import { basename as basename8 } from "path";
|
|
51910
52134
|
function runProviderLogin(cmd, envVar, dir2) {
|
|
51911
52135
|
return new Promise((resolve10) => {
|
|
51912
|
-
const child =
|
|
52136
|
+
const child = spawn12(cmd, [], {
|
|
51913
52137
|
stdio: "inherit",
|
|
51914
52138
|
env: { ...process.env, [envVar]: dir2 }
|
|
51915
52139
|
});
|
|
@@ -51942,7 +52166,7 @@ async function runAiLogin(profileDir2, profileName, aiFilter) {
|
|
|
51942
52166
|
continue;
|
|
51943
52167
|
}
|
|
51944
52168
|
const dir2 = resolveHomePath(cred.profile);
|
|
51945
|
-
|
|
52169
|
+
mkdirSync9(dir2, { recursive: true });
|
|
51946
52170
|
console.log(t("aiLogin.loggingIn", { provider: cred.provider, profile: cred.profile, envVar, dir: dir2 }));
|
|
51947
52171
|
console.log(t("aiLogin.completeSignIn"));
|
|
51948
52172
|
const code = await runProviderLogin(cred.provider, envVar, dir2);
|
|
@@ -51954,7 +52178,7 @@ async function runAiLogin(profileDir2, profileName, aiFilter) {
|
|
|
51954
52178
|
}
|
|
51955
52179
|
|
|
51956
52180
|
// src/commands/unlink.ts
|
|
51957
|
-
import { rmSync as
|
|
52181
|
+
import { rmSync as rmSync10 } from "fs";
|
|
51958
52182
|
async function runUnlink(profileDir2, profileName) {
|
|
51959
52183
|
initI18n(readProfileConfig(profileDir2).locale);
|
|
51960
52184
|
const cred = readCredential(profileDir2, profileName);
|
|
@@ -51968,7 +52192,7 @@ async function runUnlink(profileDir2, profileName) {
|
|
|
51968
52192
|
} catch (err) {
|
|
51969
52193
|
console.log(t("unlink.revokeFailed", { message: err.message }));
|
|
51970
52194
|
}
|
|
51971
|
-
|
|
52195
|
+
rmSync10(profileDir2, { recursive: true, force: true });
|
|
51972
52196
|
clearDefaultProfileIf(profileName);
|
|
51973
52197
|
console.log(t("unlink.deleted"));
|
|
51974
52198
|
}
|