@4pm/cli 1.31.0 → 1.32.0-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.
Files changed (2) hide show
  1. package/dist/index.js +524 -131
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -28708,6 +28708,16 @@ var ErrorCode = {
28708
28708
  WORKER_POOL_NAME_TAKEN: "WORKER_POOL_NAME_TAKEN",
28709
28709
  WORKER_POOL_ATTACHED: "WORKER_POOL_ATTACHED",
28710
28710
  WORKER_POOL_ALREADY_ATTACHED: "WORKER_POOL_ALREADY_ATTACHED",
28711
+ // A pool can't be both a project pool and the org AI pool (ADR-0376 mutual exclusivity).
28712
+ AI_POOL_CONFLICT: "AI_POOL_CONFLICT",
28713
+ // No org AI pool configured (Settings) for a project-less org AI task (ADR-0376).
28714
+ AI_POOL_NOT_CONFIGURED: "AI_POOL_NOT_CONFIGURED",
28715
+ // Change-request (ADR-0379) not found / already resolved.
28716
+ CHANGE_REQUEST_NOT_FOUND: "CHANGE_REQUEST_NOT_FOUND",
28717
+ CHANGE_REQUEST_ALREADY_RESOLVED: "CHANGE_REQUEST_ALREADY_RESOLVED",
28718
+ // org AI Research (ADR-0380): question refused by the content policy / research-guard; row not found.
28719
+ RESEARCH_REFUSED: "RESEARCH_REFUSED",
28720
+ RESEARCH_NOT_FOUND: "RESEARCH_NOT_FOUND",
28711
28721
  MACHINE_ALREADY_IN_POOL: "MACHINE_ALREADY_IN_POOL",
28712
28722
  NOT_MACHINE_USER: "NOT_MACHINE_USER",
28713
28723
  // worker · physic project
@@ -29925,8 +29935,8 @@ async function runLink(serverUrl, explicitProfile, token = null) {
29925
29935
  }
29926
29936
 
29927
29937
  // src/commands/start.ts
29928
- import { spawn as spawn11 } from "child_process";
29929
- import { join as join42 } from "path";
29938
+ import { spawn as spawn12 } from "child_process";
29939
+ import { join as join43 } from "path";
29930
29940
 
29931
29941
  // src/core/update.ts
29932
29942
  import { execSync as execSync2 } from "child_process";
@@ -33145,7 +33155,7 @@ async function checkAndUpdate(serverUrl, autoUpdate) {
33145
33155
  // src/core/ws-client.ts
33146
33156
  init_wrapper();
33147
33157
  import { randomUUID as randomUUID4 } from "crypto";
33148
- import { basename as basename7, join as join37 } from "path";
33158
+ import { basename as basename7, join as join38 } from "path";
33149
33159
  import { existsSync as existsSync19, mkdirSync as mkdirSync9 } from "fs";
33150
33160
 
33151
33161
  // vendor/@4pm/utils/helpers.ts
@@ -39010,6 +39020,20 @@ var WsChannels = {
39010
39020
  // entries, commits, pushes a branch, opens a PR, then wipes the token/clone, and replies with the
39011
39021
  // PR URL. The write token never persists on the worker (leak-safety on release/reassign).
39012
39022
  FAQ_COMPOSE: "faq.compose",
39023
+ // Org-level "AI author checklist items" (ADR-0376): server (picks an idle cli of the org AI pool) →
39024
+ // cli request–reply. The server forwards a ready-built prompt; the worker runs claude one-shot (no
39025
+ // tools, empty dir — like support-answer) and replies with the raw output for the web to parse. No
39026
+ // project is involved (checklists are org-scoped).
39027
+ CHECKLIST_AUTHOR: "checklist.author",
39028
+ // Org-level "AI Research" (ADR-0380): server (picks an idle cli of the org AI pool) → cli
39029
+ // request–reply. The server forwards the user's question; the worker runs a one-shot research agent
39030
+ // (read-only web/search tools) under a content policy + a research-guard post-check, and replies with
39031
+ // the markdown answer (or a structured refusal) plus the run's token usage so the server meters it to
39032
+ // the org (projectId=null). No project is involved.
39033
+ RESEARCH_ASK: "research.ask",
39034
+ // Org AI Research live progress (ADR-0380): cli → server one-way stream of answer chunks / status for
39035
+ // a running research question, keyed by the query id, relayed to the web over SSE.
39036
+ RESEARCH_PROGRESS: "research.progress",
39013
39037
  // Shared AI memory (ADR-0245): cli → server one-way write-back of the compacted rolling memory for
39014
39038
  // the project it serves (persisted per project × machine-user link). The read direction rides
39015
39039
  // `ws_token` (seeds the cli cache on connect); this channel only carries updates.
@@ -39961,7 +39985,8 @@ function planUnifiedRun(prompt, config, workingCredential, resume, oneShot, over
39961
39985
  key,
39962
39986
  args: buildRunArgs(cred, cmd, resume.get(key), oneShot, override, readOnly, bypass, [...allAiProfileDirs(config), dir2]),
39963
39987
  stdin: prompt,
39964
- env: envVar ? { ...baseEnv, [envVar]: dir2, ...overrideEnv(cmd, override) } : { ...baseEnv, ...overrideEnv(cmd, override) }
39988
+ env: envVar ? { ...baseEnv, [envVar]: dir2, ...overrideEnv(cmd, override) } : { ...baseEnv, ...overrideEnv(cmd, override) },
39989
+ ...cred.apiKey ? { apiKey: cred.apiKey } : {}
39965
39990
  }));
39966
39991
  return { cmd: attempts[0]?.cmd ?? DEFAULT_AI_CLI, attempts };
39967
39992
  }
@@ -39991,7 +40016,8 @@ function planLegacyRun(prompt, config, workingDir, resume, oneShot, override, re
39991
40016
  key,
39992
40017
  args: buildRunArgs(profile, cmd, resume.get(key), oneShot, override, readOnly, bypass, [...allAiProfileDirs(config), dir2]),
39993
40018
  stdin: prompt,
39994
- env: { ...baseEnv, [envVar]: dir2, ...overrideEnv(cmd, override) }
40019
+ env: { ...baseEnv, [envVar]: dir2, ...overrideEnv(cmd, override) },
40020
+ ...profile.apiKey ? { apiKey: profile.apiKey } : {}
39995
40021
  };
39996
40022
  });
39997
40023
  return { cmd, attempts };
@@ -40807,6 +40833,16 @@ async function runClaudeWithFailover(prompt, ai2, imageDir, timeoutMs = ANSWER_T
40807
40833
  }
40808
40834
  throw new Error(`claude ${lastReason}`);
40809
40835
  }
40836
+ async function runChecklistAuthor(req, ai2) {
40837
+ try {
40838
+ const { text, usage } = await runClaudeWithFailover(req.prompt, ai2);
40839
+ if (!text) return { reply: { error: "empty output" }, usage };
40840
+ return { reply: { output: text }, usage };
40841
+ } catch (err) {
40842
+ logger.warn("checklist.author.failed", { error: String(err) });
40843
+ return { reply: { error: String(err) }, usage: { ...NO_USAGE } };
40844
+ }
40845
+ }
40810
40846
  async function runSupportAnswer(req, ai2, profileDir2) {
40811
40847
  let imageDir;
40812
40848
  let question = req.question;
@@ -42211,6 +42247,58 @@ async function runCommand(dispatch, emitRaw, opts) {
42211
42247
  });
42212
42248
  }
42213
42249
 
42250
+ // src/core/resolve-api-key.ts
42251
+ var cache = /* @__PURE__ */ new Map();
42252
+ async function loadAws(pkg) {
42253
+ const spec = pkg;
42254
+ return await import(spec);
42255
+ }
42256
+ async function fetchSsm(d) {
42257
+ const aws = await loadAws("@aws-sdk/client-ssm");
42258
+ const SSMClient = aws.SSMClient;
42259
+ const GetParameterCommand = aws.GetParameterCommand;
42260
+ const client = new SSMClient(d.region ? { region: d.region } : {});
42261
+ const res = await client.send(new GetParameterCommand({ Name: d.name, WithDecryption: true }));
42262
+ return res.Parameter?.Value?.trim() || null;
42263
+ }
42264
+ async function fetchSecretsManager(d) {
42265
+ const aws = await loadAws("@aws-sdk/client-secrets-manager");
42266
+ const SecretsManagerClient = aws.SecretsManagerClient;
42267
+ const GetSecretValueCommand = aws.GetSecretValueCommand;
42268
+ const client = new SecretsManagerClient(d.region ? { region: d.region } : {});
42269
+ const res = await client.send(new GetSecretValueCommand({ SecretId: d.secretId }));
42270
+ const raw = (res.SecretString ?? "").trim();
42271
+ if (!raw) return null;
42272
+ if (d.jsonKey) {
42273
+ try {
42274
+ const obj = JSON.parse(raw);
42275
+ const v2 = obj[d.jsonKey];
42276
+ return typeof v2 === "string" && v2.trim() ? v2.trim() : null;
42277
+ } catch {
42278
+ return null;
42279
+ }
42280
+ }
42281
+ return raw;
42282
+ }
42283
+ async function resolveApiKey(desc) {
42284
+ if (!desc || !desc.source) return null;
42285
+ if (desc.source === "inline") return desc.value?.trim() || null;
42286
+ const cacheKey = JSON.stringify(desc);
42287
+ const hit = cache.get(cacheKey);
42288
+ if (hit) return hit;
42289
+ try {
42290
+ const val = desc.source === "ssm" ? await fetchSsm(desc) : desc.source === "secretsManager" ? await fetchSecretsManager(desc) : null;
42291
+ if (val) {
42292
+ cache.set(cacheKey, val);
42293
+ return val;
42294
+ }
42295
+ return null;
42296
+ } catch (err) {
42297
+ logger.warn("ai.apiKey.resolve.failed", { source: desc.source, error: String(err) });
42298
+ return null;
42299
+ }
42300
+ }
42301
+
42214
42302
  // src/core/ai-runner.ts
42215
42303
  var TIMEOUT_EXIT = 124;
42216
42304
  function isAuthFailure(text) {
@@ -42259,7 +42347,9 @@ async function runAiFailover(plan, commandId, cwd2, handlers, timeoutMs = 0, sig
42259
42347
  handlers.onChunk(text);
42260
42348
  }
42261
42349
  };
42262
- finalExit = await runAttempt(commandId, attempt.cmd, attempt.args, attempt.stdin, cwd2, attempt.env, emit, timeoutMs, signal);
42350
+ const resolvedKey = attempt.apiKey ? await resolveApiKey(attempt.apiKey) : null;
42351
+ const attemptEnv = resolvedKey ? { ...attempt.env, ANTHROPIC_API_KEY: resolvedKey } : attempt.env;
42352
+ finalExit = await runAttempt(commandId, attempt.cmd, attempt.args, attempt.stdin, cwd2, attemptEnv, emit, timeoutMs, signal);
42263
42353
  const tail = parser.flush();
42264
42354
  if (tail) {
42265
42355
  captured += tail;
@@ -42941,10 +43031,10 @@ function activeAiRunCount() {
42941
43031
  return activeRuns.size;
42942
43032
  }
42943
43033
  function cancelActiveRun(commandId) {
42944
- const run15 = activeRuns.get(commandId);
42945
- if (!run15) return { ok: false, state: "unknown" };
42946
- run15.controller.abort();
42947
- return { ok: true, state: run15.state };
43034
+ const run16 = activeRuns.get(commandId);
43035
+ if (!run16) return { ok: false, state: "unknown" };
43036
+ run16.controller.abort();
43037
+ return { ok: true, state: run16.state };
42948
43038
  }
42949
43039
  function handleCommandChannels(ctx, message, payload) {
42950
43040
  if (message.channel === WsChannels.COMMAND_CANCEL) {
@@ -43042,16 +43132,16 @@ async function runLocalCommand(ctx, input) {
43042
43132
  }
43043
43133
  var REFUSED = { exitCode: 1, cancelled: false, exhausted: false, neverStarted: true, output: "" };
43044
43134
  async function runAiPrompt(ctx, prompt, commandId, origin, oneShot = false, images, aiConfig, readOnly = false, bypass = false) {
43045
- const run15 = { controller: new AbortController(), state: "queued" };
43046
- activeRuns.set(commandId, run15);
43135
+ const run16 = { controller: new AbortController(), state: "queued" };
43136
+ activeRuns.set(commandId, run16);
43047
43137
  try {
43048
- return await runAiPromptInner(ctx, prompt, commandId, origin, run15, oneShot, images, aiConfig, readOnly, bypass);
43138
+ return await runAiPromptInner(ctx, prompt, commandId, origin, run16, oneShot, images, aiConfig, readOnly, bypass);
43049
43139
  } finally {
43050
43140
  activeRuns.delete(commandId);
43051
43141
  if (!readOnly) requestGitSnapshot("ai-run");
43052
43142
  }
43053
43143
  }
43054
- async function runAiPromptInner(ctx, prompt, commandId, origin, run15, oneShot = false, images, aiConfig, readOnly = false, bypass = false) {
43144
+ async function runAiPromptInner(ctx, prompt, commandId, origin, run16, oneShot = false, images, aiConfig, readOnly = false, bypass = false) {
43055
43145
  const config = readProfileConfig(ctx.profileDir);
43056
43146
  const projectTimeoutSec = config.projectAiRunTimeoutSec ?? 0;
43057
43147
  const machineTimeoutSec = config.aiRunTimeoutSec ?? 0;
@@ -43234,7 +43324,7 @@ ${guardedPrompt}`;
43234
43324
  queue: { position, limit, running: running2 }
43235
43325
  });
43236
43326
  },
43237
- run15.controller.signal
43327
+ run16.controller.signal
43238
43328
  );
43239
43329
  if (slot.kind === "timeout") {
43240
43330
  neverStarted = true;
@@ -43255,9 +43345,9 @@ ${guardedPrompt}`;
43255
43345
  result = { exitCode: COMMAND_CANCELLED_EXIT_CODE, workedDir: null, workedKey: null, workedCmd: null, usage: { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 }, sessionId: "", cancelled: true };
43256
43346
  } else {
43257
43347
  if (slot.queued) ctx.send(WsChannels.COMMAND_OUTPUT, { commandId, seq: seq++, chunk: "", log: true, queue: null });
43258
- run15.state = "running";
43348
+ run16.state = "running";
43259
43349
  try {
43260
- result = await runAiFailover(plan, commandId, cwd2, handlers, aiRunTimeoutMs, run15.controller.signal);
43350
+ result = await runAiFailover(plan, commandId, cwd2, handlers, aiRunTimeoutMs, run16.controller.signal);
43261
43351
  } finally {
43262
43352
  slot.handle.release();
43263
43353
  }
@@ -45509,8 +45599,258 @@ function applyGitAuth(ctx, method, host) {
45509
45599
  );
45510
45600
  }
45511
45601
 
45512
- // src/core/knowledge-compose.ts
45602
+ // src/core/research-run.ts
45513
45603
  import { spawn as spawn8 } from "child_process";
45604
+ import { mkdtempSync as mkdtempSync3, rmSync as rmSync8 } from "fs";
45605
+ import { tmpdir as tmpdir5 } from "os";
45606
+ import { join as join29 } from "path";
45607
+ var RESEARCH_TIMEOUT_MS = 9 * 60 * 1e3;
45608
+ var RESEARCH_MAX_TURNS = 16;
45609
+ var NO_USAGE2 = { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 };
45610
+ var RESEARCH_ALLOWED_TOOLS = "WebSearch,WebFetch";
45611
+ var RESEARCH_DENIED_TOOLS = [
45612
+ "Bash",
45613
+ "Edit",
45614
+ "Write",
45615
+ "Read",
45616
+ "Glob",
45617
+ "Grep",
45618
+ "NotebookEdit",
45619
+ "Task",
45620
+ "SlashCommand",
45621
+ "Skill",
45622
+ "ToolSearch",
45623
+ "TaskCreate",
45624
+ "TaskGet",
45625
+ "TaskList",
45626
+ "TaskOutput",
45627
+ "TaskStop",
45628
+ "TaskUpdate",
45629
+ "Monitor",
45630
+ "DesignSync",
45631
+ "CronCreate",
45632
+ "CronDelete",
45633
+ "CronList",
45634
+ "EnterWorktree",
45635
+ "ExitWorktree",
45636
+ "RemoteTrigger",
45637
+ "ScheduleWakeup",
45638
+ "SendMessage",
45639
+ "PushNotification"
45640
+ ].join(",");
45641
+ function buildResearchPrompt(question) {
45642
+ return [
45643
+ "You are a research assistant for the 4PM platform. Answer the user's question thoroughly and",
45644
+ "accurately, in Markdown. Use the read-only web tools (WebSearch / WebFetch) to find current, factual",
45645
+ "information when helpful, and cite sources inline where relevant.",
45646
+ "",
45647
+ "STRICT CONTENT POLICY \u2014 you MUST refuse, and must not answer, if the question (or a faithful answer",
45648
+ "to it) would involve any of:",
45649
+ "- sexual, abusive, hateful, or illegal content;",
45650
+ "- political persuasion, campaigning, or partisan advocacy;",
45651
+ "- discrimination or demeaning content targeting a race, ethnicity, religion, gender, nationality, or",
45652
+ " other protected group;",
45653
+ "- probing, scanning, exploiting, or attacking the security of ANY system, network, account, device,",
45654
+ " or person you do not own (no vulnerability hunting, no exploit/malware creation, no credential or",
45655
+ " PII harvesting, no evasion of security controls). General defensive-security education is allowed.",
45656
+ "- any other seriously harmful activity.",
45657
+ "",
45658
+ 'When you must refuse, reply with EXACTLY one line starting with "REFUSED:" followed by a short,',
45659
+ "neutral reason, and output nothing else.",
45660
+ "",
45661
+ "You have READ-ONLY web tools only. Never attempt to read local files, run shell commands, or interact",
45662
+ "with other systems.",
45663
+ "",
45664
+ "---",
45665
+ "",
45666
+ "Question:",
45667
+ question
45668
+ ].join("\n");
45669
+ }
45670
+ function runClaudeOnce2(cmd, profile, prompt, extraEnv, onChunk, denyDirs) {
45671
+ return new Promise((resolve13, reject) => {
45672
+ const isClaude = cmd.includes("claude");
45673
+ const args = [
45674
+ "-p",
45675
+ ...profile?.model ? ["--model", profile.model] : [],
45676
+ ...isClaude ? ["--output-format", "stream-json", "--verbose"] : [],
45677
+ // Secret-path deny rules (ADR-0347) — reads are not confined to the working dir.
45678
+ ...denySettingsArgs(cmd, [...denyDirs, profile?.dir]),
45679
+ // Web-only agent: allow WebSearch/WebFetch (auto-approved), deny everything else, bound the loop.
45680
+ // `--permission-mode default` is the non-variadic terminator (so the variadic tool flags consume
45681
+ // only their own comma-token — same trick as the one-shot/read-only runners).
45682
+ ...isClaude ? [
45683
+ `--max-turns=${RESEARCH_MAX_TURNS}`,
45684
+ "--allowedTools",
45685
+ RESEARCH_ALLOWED_TOOLS,
45686
+ "--disallowedTools",
45687
+ RESEARCH_DENIED_TOOLS,
45688
+ "--permission-mode",
45689
+ "default"
45690
+ ] : []
45691
+ ];
45692
+ const env3 = agentEnv(extraEnv, profile ? { CLAUDE_CONFIG_DIR: profile.dir } : void 0);
45693
+ const cwd2 = mkdtempSync3(join29(tmpdir5(), "4pm-research-"));
45694
+ const cleanup = () => {
45695
+ try {
45696
+ rmSync8(cwd2, { recursive: true, force: true });
45697
+ } catch {
45698
+ }
45699
+ };
45700
+ const child = spawn8(cmd, args, { stdio: ["pipe", "pipe", "pipe"], env: env3, cwd: cwd2 });
45701
+ const parser = createAiStreamParser(cmd);
45702
+ let out = "";
45703
+ let err = "";
45704
+ const timer = setTimeout(() => {
45705
+ child.kill("SIGKILL");
45706
+ reject(new Error("research run timed out"));
45707
+ }, RESEARCH_TIMEOUT_MS);
45708
+ timer.unref();
45709
+ child.stdout.on("data", (d) => {
45710
+ const delta = parser.push(d.toString());
45711
+ if (delta) {
45712
+ out += delta;
45713
+ onChunk(delta);
45714
+ }
45715
+ });
45716
+ child.stderr.on("data", (d) => err += d.toString());
45717
+ child.on("error", (e) => {
45718
+ clearTimeout(timer);
45719
+ cleanup();
45720
+ reject(e);
45721
+ });
45722
+ child.on("close", (code) => {
45723
+ clearTimeout(timer);
45724
+ cleanup();
45725
+ const tail = parser.flush();
45726
+ if (tail) {
45727
+ out += tail;
45728
+ onChunk(tail);
45729
+ }
45730
+ resolve13({ code: code ?? -1, out, err, usage: parser.usage() });
45731
+ });
45732
+ child.stdin.write(prompt);
45733
+ child.stdin.end();
45734
+ });
45735
+ }
45736
+ async function runResearch(question, ai2, onChunk) {
45737
+ const prompt = buildResearchPrompt(question);
45738
+ const denyDirs = ai2.profiles.map((p2) => p2.dir);
45739
+ const attempts = ai2.profiles.length > 0 ? ai2.profiles : [null];
45740
+ let lastReason = "no attempt";
45741
+ for (let i = 0; i < attempts.length; i++) {
45742
+ try {
45743
+ const { code, out, err, usage } = await runClaudeOnce2(ai2.cmd, attempts[i], prompt, ai2.env, onChunk, denyDirs);
45744
+ if (code === 0 && out.trim()) {
45745
+ const u2 = usage.tokens > 0 ? usage : { ...NO_USAGE2, tokens: estimateTokens(out) };
45746
+ return { text: out.trim(), usage: u2 };
45747
+ }
45748
+ lastReason = `exited ${code}: ${(err || out).slice(0, 300)}`;
45749
+ } catch (e) {
45750
+ lastReason = e instanceof Error ? e.message : String(e);
45751
+ }
45752
+ }
45753
+ return { text: "", usage: { ...NO_USAGE2 }, error: lastReason };
45754
+ }
45755
+
45756
+ // src/core/research-guard.ts
45757
+ var REFUSAL_PREFIX = "REFUSED:";
45758
+ var ATTACK_INTENT = [
45759
+ /\b(hack|break|breare|breach|compromise|exploit|gain (unauthorized )?access to|take over|take control of|ddos|dos attack)\b[^.?!]{0,60}\b(into|someone|their|his|her|another|the victim'?s|a target|account|server|network|website|wifi|camera|phone|system|database)\b/i,
45760
+ /\b(write|create|build|generate|give me)\b[^.?!]{0,40}\b(malware|ransomware|a virus|a keylogger|a trojan|spyware|a rootkit|a worm|a botnet|an exploit|a phishing (kit|page|email|site))\b/i,
45761
+ /\bbypass\b[^.?!]{0,40}\b(authentication|login|the firewall|a paywall|security|2fa|mfa|licen[sc]e|drm)\b/i,
45762
+ /\b(steal|harvest|exfiltrate|dump|crack)\b[^.?!]{0,40}\b(credentials|passwords|password hashes|credit cards?|personal data|pii|session tokens)\b/i,
45763
+ /\bbrute[- ]?force\b[^.?!]{0,40}\b(login|password|account|credential)/i
45764
+ ];
45765
+ function isModelRefusal(text) {
45766
+ return text.trimStart().toUpperCase().startsWith(REFUSAL_PREFIX);
45767
+ }
45768
+ function refusalReasonOf(text) {
45769
+ const after = text.trim().slice(REFUSAL_PREFIX.length).trim();
45770
+ return (after || "This question is outside what Research can answer.").slice(0, 300);
45771
+ }
45772
+ function screenQuestion(question) {
45773
+ if (ATTACK_INTENT.some((re2) => re2.test(question))) {
45774
+ return { blocked: true, reason: "The question asks to attack or compromise a system, which Research does not do." };
45775
+ }
45776
+ return { blocked: false };
45777
+ }
45778
+ function screenAnswer(answer) {
45779
+ if (isModelRefusal(answer)) return { blocked: true, reason: refusalReasonOf(answer) };
45780
+ if (ATTACK_INTENT.some((re2) => re2.test(answer))) {
45781
+ return { blocked: true, reason: "The answer was withheld by the content policy." };
45782
+ }
45783
+ return { blocked: false };
45784
+ }
45785
+
45786
+ // src/core/ws-client/handlers/research.ts
45787
+ function handleResearchChannels(ctx, message, payload) {
45788
+ if (message.channel !== WsChannels.RESEARCH_ASK) return false;
45789
+ const req = payload;
45790
+ const config = readProfileConfig(ctx.profileDir);
45791
+ const cmd = config.aiCli || "claude";
45792
+ ctx.bus.push({ source: "server", kind: "aireq", text: `${cmd} \u2039 research` });
45793
+ ctx.bus.startBusy("research");
45794
+ void run14(ctx, message, req, config, cmd).finally(() => ctx.bus.endBusy("research"));
45795
+ return true;
45796
+ }
45797
+ async function run14(ctx, message, req, config, cmd) {
45798
+ const emit = (p2) => ctx.send(WsChannels.RESEARCH_PROGRESS, { queryId: req.queryId, ...p2 });
45799
+ const reply = (r) => ctx.send(WsChannels.RESEARCH_ASK, r, message.id);
45800
+ const pre = screenQuestion(req.question);
45801
+ if (pre.blocked) {
45802
+ reply({ refused: true, refusalReason: pre.reason });
45803
+ return;
45804
+ }
45805
+ const slot = await acquireRunSlot(ctx, (info) => emit({ message: `queued #${info.position}` }));
45806
+ if (slot.kind !== "granted") {
45807
+ reply({ error: slot.kind === "denied" ? "storage full" : "no idle run slot" });
45808
+ return;
45809
+ }
45810
+ try {
45811
+ const ai2 = {
45812
+ cmd,
45813
+ profiles: resolveClaudeProfiles(config, getWorkingProfile(ctx.profileDir, cmd)),
45814
+ env: config.aiEnv
45815
+ };
45816
+ const result = await runResearch(req.question, ai2, (delta) => emit({ chunk: delta }));
45817
+ const occurredAt = (/* @__PURE__ */ new Date()).toISOString();
45818
+ const events = [{ metric: UsageMetric.COMMANDS, amount: 1, occurredAt }];
45819
+ if (result.usage.tokens > 0) {
45820
+ events.push({
45821
+ metric: UsageMetric.AI_TOKENS,
45822
+ amount: result.usage.tokens,
45823
+ occurredAt,
45824
+ inputTokens: result.usage.input,
45825
+ outputTokens: result.usage.output,
45826
+ cacheReadTokens: result.usage.cacheRead,
45827
+ cacheCreationTokens: result.usage.cacheCreation,
45828
+ provider: aiProviderOf(cmd),
45829
+ authMode: resolveClaudeAuthMode(config)
45830
+ });
45831
+ }
45832
+ ctx.reportUsage(events, null);
45833
+ if (result.error || !result.text) {
45834
+ ctx.bus.push({ source: "server", kind: "log", text: `research failed: ${result.error ?? "empty"}`, level: "warn" });
45835
+ reply({ error: result.error || "empty answer", tokens: result.usage.tokens });
45836
+ return;
45837
+ }
45838
+ const post2 = screenAnswer(result.text);
45839
+ if (post2.blocked) {
45840
+ reply({ refused: true, refusalReason: post2.reason, tokens: result.usage.tokens });
45841
+ return;
45842
+ }
45843
+ ctx.bus.push({ source: "server", kind: "aires", text: `${cmd} \u203A` });
45844
+ reply({ answer: result.text, tokens: result.usage.tokens });
45845
+ } catch (err) {
45846
+ reply({ error: err instanceof Error ? err.message : String(err) });
45847
+ } finally {
45848
+ slot.handle.release();
45849
+ }
45850
+ }
45851
+
45852
+ // src/core/knowledge-compose.ts
45853
+ import { spawn as spawn9 } from "child_process";
45514
45854
  var COMPOSE_TIMEOUT_MS = 18e4;
45515
45855
  function buildPrompt3(req) {
45516
45856
  const focus = req.prompt ? `
@@ -45536,7 +45876,7 @@ function runOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
45536
45876
  ...extraEnv,
45537
45877
  ...profile ? { CLAUDE_CONFIG_DIR: profile.dir } : {}
45538
45878
  };
45539
- const child = spawn8(cmd, args, { stdio: ["pipe", "pipe", "pipe"], cwd: cwd2, env: env3 });
45879
+ const child = spawn9(cmd, args, { stdio: ["pipe", "pipe", "pipe"], cwd: cwd2, env: env3 });
45540
45880
  let out = "";
45541
45881
  let err = "";
45542
45882
  const timer = setTimeout(() => {
@@ -45582,14 +45922,14 @@ async function runKnowledgeCompose(req, ai2, physicRoot) {
45582
45922
  }
45583
45923
 
45584
45924
  // src/core/faq-compose.ts
45585
- import { spawn as spawn9, execFile as execFile14 } from "child_process";
45586
- import { existsSync as existsSync16, mkdirSync as mkdirSync8, rmSync as rmSync8 } from "fs";
45925
+ import { spawn as spawn10, execFile as execFile14 } from "child_process";
45926
+ import { existsSync as existsSync16, mkdirSync as mkdirSync8, rmSync as rmSync9 } from "fs";
45587
45927
  import { randomUUID as randomUUID3 } from "crypto";
45588
- import { join as join29 } from "path";
45589
- import { tmpdir as tmpdir5 } from "os";
45928
+ import { join as join30 } from "path";
45929
+ import { tmpdir as tmpdir6 } from "os";
45590
45930
  import { promisify as promisify14 } from "util";
45591
45931
  var execFileAsync2 = promisify14(execFile14);
45592
- var NO_USAGE2 = { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 };
45932
+ var NO_USAGE3 = { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 };
45593
45933
  var COMPOSE_TIMEOUT_MS2 = 10 * 60 * 1e3;
45594
45934
  var AGENT_DENIED_TOOLS = ["Bash", "WebFetch", "WebSearch", "NotebookEdit", "Task"];
45595
45935
  function buildPrompt4(tickets, customPrompt) {
@@ -45652,7 +45992,7 @@ function parsePerTicket(text) {
45652
45992
  }
45653
45993
  return [];
45654
45994
  }
45655
- function runClaudeOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
45995
+ function runClaudeOnce3(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
45656
45996
  return new Promise((resolve13, reject) => {
45657
45997
  const isClaude = cmd.includes("claude");
45658
45998
  const args = [
@@ -45668,7 +46008,7 @@ function runClaudeOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
45668
46008
  ...cmd.includes("codex") ? ["--sandbox", "workspace-write"] : []
45669
46009
  ];
45670
46010
  const env3 = agentEnv(extraEnv, profile ? { CLAUDE_CONFIG_DIR: profile.dir } : void 0);
45671
- const child = spawn9(cmd, args, { stdio: ["pipe", "pipe", "pipe"], cwd: cwd2, env: env3 });
46011
+ const child = spawn10(cmd, args, { stdio: ["pipe", "pipe", "pipe"], cwd: cwd2, env: env3 });
45672
46012
  const parser = createAiStreamParser(cmd);
45673
46013
  let out = "";
45674
46014
  let err = "";
@@ -45696,7 +46036,7 @@ async function runClaudeWithFailover2(prompt, cwd2, ai2) {
45696
46036
  const attempts = ai2.profiles.length > 0 ? ai2.profiles : [null];
45697
46037
  let lastReason = "no attempt";
45698
46038
  for (let i = 0; i < attempts.length; i++) {
45699
- const { code, out, err, usage } = await runClaudeOnce2(ai2.cmd, attempts[i], prompt, cwd2, ai2.env, ai2.profiles.map((p2) => p2.dir));
46039
+ const { code, out, err, usage } = await runClaudeOnce3(ai2.cmd, attempts[i], prompt, cwd2, ai2.env, ai2.profiles.map((p2) => p2.dir));
45700
46040
  if (code === 0) return { usage, text: out.trim() };
45701
46041
  lastReason = `exited ${code}: ${(err || out).slice(0, 500)}`;
45702
46042
  if (i === attempts.length - 1) break;
@@ -45706,7 +46046,7 @@ async function runClaudeWithFailover2(prompt, cwd2, ai2) {
45706
46046
  var MAX_OUTPUT_CHARS = 8e3;
45707
46047
  async function runFaqCompose(req, ai2) {
45708
46048
  if (!req.repo.token) return { prUrl: "", error: "no write token" };
45709
- const workDir = join29(tmpdir5(), "4pm-faq-sync", randomUUID3());
46049
+ const workDir = join30(tmpdir6(), "4pm-faq-sync", randomUUID3());
45710
46050
  const gitEnv = { ...process.env, GIT_TERMINAL_PROMPT: "0" };
45711
46051
  const auth = gitAuthArgs("x-access-token", req.repo.token);
45712
46052
  try {
@@ -45736,7 +46076,7 @@ async function runFaqCompose(req, ai2) {
45736
46076
  } catch (err) {
45737
46077
  logger.warn("faq.compose.pr.failed", { error: String(err) });
45738
46078
  }
45739
- const u2 = usage.tokens > 0 ? usage : { ...NO_USAGE2 };
46079
+ const u2 = usage.tokens > 0 ? usage : { ...NO_USAGE3 };
45740
46080
  return {
45741
46081
  prUrl,
45742
46082
  branch: req.headBranch,
@@ -45752,7 +46092,7 @@ async function runFaqCompose(req, ai2) {
45752
46092
  } finally {
45753
46093
  if (existsSync16(workDir)) {
45754
46094
  try {
45755
- rmSync8(workDir, { recursive: true, force: true });
46095
+ rmSync9(workDir, { recursive: true, force: true });
45756
46096
  } catch {
45757
46097
  }
45758
46098
  }
@@ -45837,6 +46177,58 @@ function handleSupportChannels(ctx, message, payload) {
45837
46177
  }).finally(() => ctx.bus.endBusy("faq-compose"));
45838
46178
  return true;
45839
46179
  }
46180
+ case WsChannels.CHECKLIST_AUTHOR: {
46181
+ const creq = payload;
46182
+ const ccfg = readProfileConfig(ctx.profileDir);
46183
+ const ccmd = ccfg.aiCli || "claude";
46184
+ const cai = {
46185
+ cmd: ccmd,
46186
+ profiles: resolveClaudeProfiles(ccfg, getWorkingProfile(ctx.profileDir, ccmd)),
46187
+ env: ccfg.aiEnv
46188
+ };
46189
+ ctx.bus.push({ source: "server", kind: "aireq", text: `${ccmd} \u2039 author checklist items` });
46190
+ ctx.bus.startBusy("checklist-author");
46191
+ void (async () => {
46192
+ const slot = await acquireRunSlot(ctx, () => void 0);
46193
+ if (slot.kind !== "granted") {
46194
+ ctx.send(
46195
+ WsChannels.CHECKLIST_AUTHOR,
46196
+ { error: slot.kind === "denied" ? "storage full" : "no idle run slot" },
46197
+ message.id
46198
+ );
46199
+ return;
46200
+ }
46201
+ try {
46202
+ const { reply, usage } = await runChecklistAuthor(creq, cai);
46203
+ const occurredAt = (/* @__PURE__ */ new Date()).toISOString();
46204
+ const events = [{ metric: UsageMetric.COMMANDS, amount: 1, occurredAt }];
46205
+ if (usage.tokens > 0) {
46206
+ events.push({
46207
+ metric: UsageMetric.AI_TOKENS,
46208
+ amount: usage.tokens,
46209
+ occurredAt,
46210
+ inputTokens: usage.input,
46211
+ outputTokens: usage.output,
46212
+ cacheReadTokens: usage.cacheRead,
46213
+ cacheCreationTokens: usage.cacheCreation,
46214
+ provider: aiProviderOf(ccmd),
46215
+ authMode: resolveClaudeAuthMode(ccfg)
46216
+ });
46217
+ }
46218
+ ctx.reportUsage(events, null);
46219
+ if (reply.error) {
46220
+ ctx.bus.push({ source: "server", kind: "log", text: `checklist-author failed: ${reply.error}`, level: "warn" });
46221
+ } else {
46222
+ ctx.bus.push({ source: "server", kind: "aires", text: `${ccmd} \u203A` });
46223
+ }
46224
+ reportToolResult(ccmd, !reply.error, reply.error);
46225
+ ctx.send(WsChannels.CHECKLIST_AUTHOR, reply, message.id);
46226
+ } finally {
46227
+ slot.handle.release();
46228
+ }
46229
+ })().finally(() => ctx.bus.endBusy("checklist-author"));
46230
+ return true;
46231
+ }
45840
46232
  default:
45841
46233
  return false;
45842
46234
  }
@@ -45935,7 +46327,7 @@ function handleToolsChannels(ctx, message, payload) {
45935
46327
 
45936
46328
  // src/core/agents.ts
45937
46329
  import { readFile as readFile14, writeFile as writeFile10, mkdir as mkdir8, readdir as readdir6, rm as rm7 } from "fs/promises";
45938
- import { join as join30 } from "path";
46330
+ import { join as join31 } from "path";
45939
46331
  var AGENTS_REL = ".claude/agents";
45940
46332
  var SKILLS_REL = ".claude/skills";
45941
46333
  var MEMORY_DIRS = /* @__PURE__ */ new Set(["short-memory", "long-memory"]);
@@ -45959,17 +46351,17 @@ async function listAgents(root) {
45959
46351
  const subagents = [];
45960
46352
  const skills = [];
45961
46353
  try {
45962
- for (const e of await readdir6(join30(root, AGENTS_REL), { withFileTypes: true })) {
46354
+ for (const e of await readdir6(join31(root, AGENTS_REL), { withFileTypes: true })) {
45963
46355
  if (!e.isFile() || !e.name.endsWith(".md") || MEMORY_DIRS.has(e.name)) continue;
45964
- const fm = frontmatter(await readText2(join30(root, AGENTS_REL, e.name)));
46356
+ const fm = frontmatter(await readText2(join31(root, AGENTS_REL, e.name)));
45965
46357
  subagents.push({ name: e.name.replace(/\.md$/, ""), description: fm.description });
45966
46358
  }
45967
46359
  } catch {
45968
46360
  }
45969
46361
  try {
45970
- for (const e of await readdir6(join30(root, SKILLS_REL), { withFileTypes: true })) {
46362
+ for (const e of await readdir6(join31(root, SKILLS_REL), { withFileTypes: true })) {
45971
46363
  if (!e.isDirectory()) continue;
45972
- const fm = frontmatter(await readText2(join30(root, SKILLS_REL, e.name, "SKILL.md")));
46364
+ const fm = frontmatter(await readText2(join31(root, SKILLS_REL, e.name, "SKILL.md")));
45973
46365
  skills.push({ name: e.name, description: fm.description });
45974
46366
  }
45975
46367
  } catch {
@@ -45980,15 +46372,15 @@ async function listAgents(root) {
45980
46372
  }
45981
46373
  function itemPath(root, kind, name) {
45982
46374
  const n = safeName(name);
45983
- return kind === "subagent" ? join30(root, AGENTS_REL, `${n}.md`) : join30(root, SKILLS_REL, n, "SKILL.md");
46375
+ return kind === "subagent" ? join31(root, AGENTS_REL, `${n}.md`) : join31(root, SKILLS_REL, n, "SKILL.md");
45984
46376
  }
45985
46377
  async function readAgent(root, kind, name) {
45986
46378
  const content = await readText2(itemPath(root, kind, name));
45987
46379
  if (kind !== "subagent") return { content };
45988
46380
  const n = safeName(name);
45989
46381
  const [short, long] = await Promise.all([
45990
- readText2(join30(root, AGENTS_REL, "short-memory", `${n}.md`)),
45991
- readText2(join30(root, AGENTS_REL, "long-memory", `${n}.md`))
46382
+ readText2(join31(root, AGENTS_REL, "short-memory", `${n}.md`)),
46383
+ readText2(join31(root, AGENTS_REL, "long-memory", `${n}.md`))
45992
46384
  ]);
45993
46385
  return { content, memory: { short, long } };
45994
46386
  }
@@ -45998,12 +46390,12 @@ async function writeAgent(root, req) {
45998
46390
  try {
45999
46391
  if (req.action === "delete") {
46000
46392
  if (req.kind === "subagent") await rm7(itemPath(root, "subagent", n), { force: true });
46001
- else await rm7(join30(root, SKILLS_REL, n), { recursive: true, force: true });
46393
+ else await rm7(join31(root, SKILLS_REL, n), { recursive: true, force: true });
46002
46394
  return { ok: true };
46003
46395
  }
46004
46396
  if (req.content === void 0) return { ok: false, error: "content is required" };
46005
46397
  const path = itemPath(root, req.kind, n);
46006
- await mkdir8(join30(path, ".."), { recursive: true });
46398
+ await mkdir8(join31(path, ".."), { recursive: true });
46007
46399
  await writeFile10(path, req.content, "utf8");
46008
46400
  return { ok: true };
46009
46401
  } catch (err) {
@@ -46013,7 +46405,7 @@ async function writeAgent(root, req) {
46013
46405
 
46014
46406
  // src/core/secrets.ts
46015
46407
  import { readFile as readFile15, writeFile as writeFile11 } from "fs/promises";
46016
- import { join as join31 } from "path";
46408
+ import { join as join32 } from "path";
46017
46409
  var SECURITY_REL = "AI_SECURITY.md";
46018
46410
  var PLACEHOLDER_REL = "AI_PLACEHOLDER.md";
46019
46411
  var SECRETS_REL = "project.secrets.json";
@@ -46026,7 +46418,7 @@ async function readText3(path, fallback = "") {
46026
46418
  }
46027
46419
  async function readSecretsObject(root) {
46028
46420
  try {
46029
- const v2 = JSON.parse(await readFile15(join31(root, SECRETS_REL), "utf8"));
46421
+ const v2 = JSON.parse(await readFile15(join32(root, SECRETS_REL), "utf8"));
46030
46422
  return v2 && typeof v2 === "object" ? v2 : {};
46031
46423
  } catch {
46032
46424
  return {};
@@ -46045,8 +46437,8 @@ function placeholderKeys(text) {
46045
46437
  }
46046
46438
  async function readSecrets(root) {
46047
46439
  const [security, placeholder, secrets] = await Promise.all([
46048
- readText3(join31(root, SECURITY_REL)),
46049
- readText3(join31(root, PLACEHOLDER_REL)),
46440
+ readText3(join32(root, SECURITY_REL)),
46441
+ readText3(join32(root, PLACEHOLDER_REL)),
46050
46442
  readSecretsObject(root)
46051
46443
  ]);
46052
46444
  const names = /* @__PURE__ */ new Set([...placeholderKeys(placeholder), ...Object.keys(secrets)]);
@@ -46059,14 +46451,14 @@ async function readSecrets(root) {
46059
46451
  async function writeSecrets(root, req) {
46060
46452
  try {
46061
46453
  if (req.kind === "security") {
46062
- await writeFile11(join31(root, SECURITY_REL), req.content, "utf8");
46454
+ await writeFile11(join32(root, SECURITY_REL), req.content, "utf8");
46063
46455
  } else if (req.kind === "placeholder") {
46064
- await writeFile11(join31(root, PLACEHOLDER_REL), req.content, "utf8");
46456
+ await writeFile11(join32(root, PLACEHOLDER_REL), req.content, "utf8");
46065
46457
  } else {
46066
46458
  const secrets = await readSecretsObject(root);
46067
46459
  if (req.kind === "secret") secrets[req.key] = req.value;
46068
46460
  else delete secrets[req.key];
46069
- await writeFile11(join31(root, SECRETS_REL), JSON.stringify(secrets, null, 2) + "\n", "utf8");
46461
+ await writeFile11(join32(root, SECRETS_REL), JSON.stringify(secrets, null, 2) + "\n", "utf8");
46070
46462
  }
46071
46463
  return { ok: true };
46072
46464
  } catch (err) {
@@ -46076,12 +46468,12 @@ async function writeSecrets(root, req) {
46076
46468
 
46077
46469
  // src/core/agent-tools.ts
46078
46470
  import { readFile as readFile16, writeFile as writeFile12, mkdir as mkdir9 } from "fs/promises";
46079
- import { dirname as dirname9, join as join32 } from "path";
46471
+ import { dirname as dirname9, join as join33 } from "path";
46080
46472
  var SHARED_REL = ".claude/settings.json";
46081
46473
  var LOCAL_REL = ".claude/settings.local.json";
46082
46474
  var SECRETS_DENY = "Read(./project.secrets.json)";
46083
46475
  function fileFor(root, scope) {
46084
- return join32(root, scope === "shared" ? SHARED_REL : LOCAL_REL);
46476
+ return join33(root, scope === "shared" ? SHARED_REL : LOCAL_REL);
46085
46477
  }
46086
46478
  async function readSettingsObject(path) {
46087
46479
  try {
@@ -46118,7 +46510,7 @@ async function writeAgentTools(root, scope, permissions) {
46118
46510
  // src/core/packages.ts
46119
46511
  import { createHash as createHash4 } from "crypto";
46120
46512
  import { readFile as readFile17, writeFile as writeFile13, mkdir as mkdir10, readdir as readdir7, rm as rm8, stat as stat5 } from "fs/promises";
46121
- import { dirname as dirname10, join as join33, relative } from "path";
46513
+ import { dirname as dirname10, join as join34, relative } from "path";
46122
46514
  var AGENTS_REL2 = ".claude/agents";
46123
46515
  var SKILLS_REL2 = ".claude/skills";
46124
46516
  var MANIFEST_REL = ".claude/.4pm-packages.json";
@@ -46142,7 +46534,7 @@ async function listFilesRel(dir2) {
46142
46534
  return;
46143
46535
  }
46144
46536
  for (const e of entries2) {
46145
- const full = join33(d, e.name);
46537
+ const full = join34(d, e.name);
46146
46538
  if (e.isDirectory()) await walk2(full);
46147
46539
  else if (e.isFile()) out.push(relative(dir2, full));
46148
46540
  }
@@ -46153,16 +46545,16 @@ async function listFilesRel(dir2) {
46153
46545
  async function readArtifactFiles(root, kind, slug) {
46154
46546
  const files = [];
46155
46547
  if (kind === "subagent") {
46156
- const agent = await readMaybe(join33(root, AGENTS_REL2, `${slug}.md`));
46548
+ const agent = await readMaybe(join34(root, AGENTS_REL2, `${slug}.md`));
46157
46549
  if (agent) files.push({ path: "agent.md", contentBase64: agent.toString("base64") });
46158
- const short = await readMaybe(join33(root, AGENTS_REL2, "short-memory", `${slug}.md`));
46550
+ const short = await readMaybe(join34(root, AGENTS_REL2, "short-memory", `${slug}.md`));
46159
46551
  if (short) files.push({ path: "short-memory.md", contentBase64: short.toString("base64") });
46160
- const long = await readMaybe(join33(root, AGENTS_REL2, "long-memory", `${slug}.md`));
46552
+ const long = await readMaybe(join34(root, AGENTS_REL2, "long-memory", `${slug}.md`));
46161
46553
  if (long) files.push({ path: "long-memory.md", contentBase64: long.toString("base64") });
46162
46554
  } else {
46163
- const base = join33(root, SKILLS_REL2, slug);
46555
+ const base = join34(root, SKILLS_REL2, slug);
46164
46556
  for (const rel of await listFilesRel(base)) {
46165
- const buf = await readMaybe(join33(base, rel));
46557
+ const buf = await readMaybe(join34(base, rel));
46166
46558
  if (buf) files.push({ path: rel.split("\\").join("/"), contentBase64: buf.toString("base64") });
46167
46559
  }
46168
46560
  }
@@ -46180,7 +46572,7 @@ async function artifactSha256(root, kind, slug) {
46180
46572
  return h.digest("hex");
46181
46573
  }
46182
46574
  async function artifactExists(root, kind, slug) {
46183
- const target = kind === "subagent" ? join33(root, AGENTS_REL2, `${slug}.md`) : join33(root, SKILLS_REL2, slug);
46575
+ const target = kind === "subagent" ? join34(root, AGENTS_REL2, `${slug}.md`) : join34(root, SKILLS_REL2, slug);
46184
46576
  try {
46185
46577
  await stat5(target);
46186
46578
  return true;
@@ -46189,7 +46581,7 @@ async function artifactExists(root, kind, slug) {
46189
46581
  }
46190
46582
  }
46191
46583
  async function readManifest(root) {
46192
- const buf = await readMaybe(join33(root, MANIFEST_REL));
46584
+ const buf = await readMaybe(join34(root, MANIFEST_REL));
46193
46585
  if (!buf) return {};
46194
46586
  try {
46195
46587
  return JSON.parse(buf.toString("utf8"));
@@ -46198,7 +46590,7 @@ async function readManifest(root) {
46198
46590
  }
46199
46591
  }
46200
46592
  async function writeManifest(root, manifest) {
46201
- const path = join33(root, MANIFEST_REL);
46593
+ const path = join34(root, MANIFEST_REL);
46202
46594
  await mkdir10(dirname10(path), { recursive: true });
46203
46595
  await writeFile13(path, JSON.stringify(manifest, null, 2), "utf8");
46204
46596
  }
@@ -46207,24 +46599,24 @@ async function writeArtifactFiles(root, kind, slug, files) {
46207
46599
  if (kind === "subagent") {
46208
46600
  const agent = byPath.get("agent.md");
46209
46601
  if (agent) {
46210
- await mkdir10(join33(root, AGENTS_REL2), { recursive: true });
46211
- await writeFile13(join33(root, AGENTS_REL2, `${slug}.md`), agent);
46602
+ await mkdir10(join34(root, AGENTS_REL2), { recursive: true });
46603
+ await writeFile13(join34(root, AGENTS_REL2, `${slug}.md`), agent);
46212
46604
  }
46213
46605
  const short = byPath.get("short-memory.md");
46214
46606
  if (short) {
46215
- await mkdir10(join33(root, AGENTS_REL2, "short-memory"), { recursive: true });
46216
- await writeFile13(join33(root, AGENTS_REL2, "short-memory", `${slug}.md`), short);
46607
+ await mkdir10(join34(root, AGENTS_REL2, "short-memory"), { recursive: true });
46608
+ await writeFile13(join34(root, AGENTS_REL2, "short-memory", `${slug}.md`), short);
46217
46609
  }
46218
46610
  const long = byPath.get("long-memory.md");
46219
46611
  if (long) {
46220
- await mkdir10(join33(root, AGENTS_REL2, "long-memory"), { recursive: true });
46221
- await writeFile13(join33(root, AGENTS_REL2, "long-memory", `${slug}.md`), long);
46612
+ await mkdir10(join34(root, AGENTS_REL2, "long-memory"), { recursive: true });
46613
+ await writeFile13(join34(root, AGENTS_REL2, "long-memory", `${slug}.md`), long);
46222
46614
  }
46223
46615
  } else {
46224
- const base = join33(root, SKILLS_REL2, slug);
46616
+ const base = join34(root, SKILLS_REL2, slug);
46225
46617
  for (const [rel, buf] of byPath) {
46226
46618
  if (rel.includes("..") || rel.startsWith("/")) continue;
46227
- const dest = join33(base, rel);
46619
+ const dest = join34(base, rel);
46228
46620
  await mkdir10(dirname10(dest), { recursive: true });
46229
46621
  await writeFile13(dest, buf);
46230
46622
  }
@@ -46290,11 +46682,11 @@ async function removePackage(root, slug) {
46290
46682
  const entry = manifest[n];
46291
46683
  if (!entry) return { ok: false, error: "not installed" };
46292
46684
  if (entry.kind === "subagent") {
46293
- await rm8(join33(root, AGENTS_REL2, `${n}.md`), { force: true });
46294
- await rm8(join33(root, AGENTS_REL2, "short-memory", `${n}.md`), { force: true });
46295
- await rm8(join33(root, AGENTS_REL2, "long-memory", `${n}.md`), { force: true });
46685
+ await rm8(join34(root, AGENTS_REL2, `${n}.md`), { force: true });
46686
+ await rm8(join34(root, AGENTS_REL2, "short-memory", `${n}.md`), { force: true });
46687
+ await rm8(join34(root, AGENTS_REL2, "long-memory", `${n}.md`), { force: true });
46296
46688
  } else {
46297
- await rm8(join33(root, SKILLS_REL2, n), { recursive: true, force: true });
46689
+ await rm8(join34(root, SKILLS_REL2, n), { recursive: true, force: true });
46298
46690
  }
46299
46691
  delete manifest[n];
46300
46692
  await writeManifest(root, manifest);
@@ -46306,7 +46698,7 @@ async function removePackage(root, slug) {
46306
46698
 
46307
46699
  // src/core/graph.ts
46308
46700
  import { readFile as readFile18, readdir as readdir8 } from "fs/promises";
46309
- import { join as join34, relative as relative2, dirname as dirname11, resolve as resolve12, extname as extname2, basename as basename6 } from "path";
46701
+ import { join as join35, relative as relative2, dirname as dirname11, resolve as resolve12, extname as extname2, basename as basename6 } from "path";
46310
46702
  var MAX_FILES = 2e3;
46311
46703
  var MAX_NODES = 3e3;
46312
46704
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "out", "coverage", ".turbo"]);
@@ -46326,7 +46718,7 @@ async function walk(root, accept) {
46326
46718
  if (e.name.startsWith(".") && e.name !== ".claude") {
46327
46719
  if (e.isDirectory()) continue;
46328
46720
  }
46329
- const full = join34(dir2, e.name);
46721
+ const full = join35(dir2, e.name);
46330
46722
  if (e.isDirectory()) {
46331
46723
  if (!SKIP_DIRS.has(e.name)) stack.push(full);
46332
46724
  } else if (accept(full)) {
@@ -46447,15 +46839,15 @@ async function buildGraph(root, mode) {
46447
46839
  // src/core/rag.ts
46448
46840
  import { readFile as readFile19, writeFile as writeFile14, statfs as statfs2, mkdir as mkdir11 } from "fs/promises";
46449
46841
  import { existsSync as existsSync17 } from "fs";
46450
- import { execFile as execFile15, spawn as spawn10 } from "child_process";
46842
+ import { execFile as execFile15, spawn as spawn11 } from "child_process";
46451
46843
  import { totalmem as totalmem2, cpus as cpus2 } from "os";
46452
- import { join as join35 } from "path";
46844
+ import { join as join36 } from "path";
46453
46845
  import { promisify as promisify15 } from "util";
46454
- var run14 = promisify15(execFile15);
46846
+ var run15 = promisify15(execFile15);
46455
46847
  var RAG_REL = ".claude/rag";
46456
46848
  async function tryRun(cmd, args) {
46457
46849
  try {
46458
- const { stdout } = await run14(cmd, args, { timeout: 8e3 });
46850
+ const { stdout } = await run15(cmd, args, { timeout: 8e3 });
46459
46851
  return { ok: true, out: stdout.trim() };
46460
46852
  } catch {
46461
46853
  return { ok: false, out: "" };
@@ -46470,7 +46862,7 @@ async function detectPython() {
46470
46862
  }
46471
46863
  async function installLog(root) {
46472
46864
  try {
46473
- const text = await readFile19(join35(root, RAG_REL, "install.log"), "utf8");
46865
+ const text = await readFile19(join36(root, RAG_REL, "install.log"), "utf8");
46474
46866
  return text.split("\n").filter((l) => l.length > 0).slice(-200);
46475
46867
  } catch {
46476
46868
  return [];
@@ -46532,14 +46924,14 @@ async function ragStatus(root) {
46532
46924
  };
46533
46925
  let modelName = "";
46534
46926
  try {
46535
- modelName = (await readFile19(join35(root, RAG_REL, ".installed"), "utf8")).trim();
46927
+ modelName = (await readFile19(join36(root, RAG_REL, ".installed"), "utf8")).trim();
46536
46928
  } catch {
46537
46929
  modelName = "";
46538
46930
  }
46539
46931
  const modelPresent = modelName.length > 0;
46540
46932
  return {
46541
46933
  installed: fastembed && sqliteVec && modelPresent,
46542
- installing: existsSync17(join35(root, RAG_REL, ".installing")),
46934
+ installing: existsSync17(join36(root, RAG_REL, ".installing")),
46543
46935
  python: { found: py.found, version: py.version },
46544
46936
  deps: { pip, fastembed, sqliteVec },
46545
46937
  model: { present: modelPresent, name: modelName },
@@ -46550,9 +46942,9 @@ async function ragStatus(root) {
46550
46942
  };
46551
46943
  }
46552
46944
  async function readIndexMeta(root) {
46553
- const indexing = existsSync17(join35(root, RAG_REL, ".indexing"));
46945
+ const indexing = existsSync17(join36(root, RAG_REL, ".indexing"));
46554
46946
  try {
46555
- const m2 = JSON.parse(await readFile19(join35(root, RAG_REL, ".index.json"), "utf8"));
46947
+ const m2 = JSON.parse(await readFile19(join36(root, RAG_REL, ".index.json"), "utf8"));
46556
46948
  return {
46557
46949
  present: true,
46558
46950
  indexing,
@@ -46619,14 +47011,14 @@ if __name__ == "__main__":
46619
47011
  elif cmd == "query": query(sys.argv[2], int(sys.argv[3]) if len(sys.argv) > 3 else 8)
46620
47012
  `;
46621
47013
  async function ensureEngine(root) {
46622
- await mkdir11(join35(root, RAG_REL), { recursive: true }).catch(() => void 0);
46623
- await writeFile14(join35(root, RAG_REL, "rag_engine.py"), RAG_ENGINE_PY, "utf8");
47014
+ await mkdir11(join36(root, RAG_REL), { recursive: true }).catch(() => void 0);
47015
+ await writeFile14(join36(root, RAG_REL, "rag_engine.py"), RAG_ENGINE_PY, "utf8");
46624
47016
  }
46625
47017
  async function ragReindex(root) {
46626
47018
  const py = await detectPython();
46627
47019
  if (!py.found) return { ok: false, started: false, error: "python3 not found" };
46628
- if (!existsSync17(join35(root, RAG_REL, ".installed"))) return { ok: false, started: false, error: "RAG is not installed" };
46629
- if (existsSync17(join35(root, RAG_REL, ".indexing"))) return { ok: true, started: true };
47020
+ if (!existsSync17(join36(root, RAG_REL, ".installed"))) return { ok: false, started: false, error: "RAG is not installed" };
47021
+ if (existsSync17(join36(root, RAG_REL, ".indexing"))) return { ok: true, started: true };
46630
47022
  await ensureEngine(root);
46631
47023
  const script = `
46632
47024
  set +e
@@ -46634,7 +47026,7 @@ touch "${RAG_REL}/.indexing"
46634
47026
  { echo "[reindex] $(date)"; "$RAG_PY" "${RAG_REL}/rag_engine.py" reindex 2>&1; } >> "${RAG_REL}/index.log" 2>&1
46635
47027
  rm -f "${RAG_REL}/.indexing"
46636
47028
  `;
46637
- const child = spawn10("bash", ["-c", script], {
47029
+ const child = spawn11("bash", ["-c", script], {
46638
47030
  cwd: root,
46639
47031
  env: { ...process.env, RAG_PY: py.bin },
46640
47032
  detached: true,
@@ -46646,12 +47038,12 @@ rm -f "${RAG_REL}/.indexing"
46646
47038
  async function ragQuery(root, queryText, k2 = 8) {
46647
47039
  const py = await detectPython();
46648
47040
  if (!py.found) return { results: [], error: "python3 not found" };
46649
- if (!existsSync17(join35(root, RAG_REL, "index.db"))) return { results: [], error: "no index \u2014 reindex first" };
47041
+ if (!existsSync17(join36(root, RAG_REL, "index.db"))) return { results: [], error: "no index \u2014 reindex first" };
46650
47042
  await ensureEngine(root);
46651
47043
  try {
46652
- const { stdout } = await run14(
47044
+ const { stdout } = await run15(
46653
47045
  py.bin,
46654
- [join35(RAG_REL, "rag_engine.py"), "query", queryText, String(Math.min(Math.max(k2, 1), 20))],
47046
+ [join36(RAG_REL, "rag_engine.py"), "query", queryText, String(Math.min(Math.max(k2, 1), 20))],
46655
47047
  { cwd: root, timeout: 3e4, maxBuffer: 4 * 1024 * 1024 }
46656
47048
  );
46657
47049
  const parsed = JSON.parse(stdout.trim() || '{"results":[]}');
@@ -46666,9 +47058,9 @@ async function ragInstall(root, model) {
46666
47058
  }
46667
47059
  const py = await detectPython();
46668
47060
  if (!py.found) return { ok: false, started: false, error: "python3 not found on the worker" };
46669
- if (existsSync17(join35(root, RAG_REL, ".installing"))) return { ok: true, started: true };
47061
+ if (existsSync17(join36(root, RAG_REL, ".installing"))) return { ok: true, started: true };
46670
47062
  try {
46671
- await mkdir11(join35(root, RAG_REL), { recursive: true });
47063
+ await mkdir11(join36(root, RAG_REL), { recursive: true });
46672
47064
  } catch {
46673
47065
  }
46674
47066
  const script = `
@@ -46684,7 +47076,7 @@ rm -f "${RAG_REL}/.installed"
46684
47076
  } >> "${RAG_REL}/install.log" 2>&1
46685
47077
  rm -f "${RAG_REL}/.installing"
46686
47078
  `;
46687
- const child = spawn10("bash", ["-c", script], {
47079
+ const child = spawn11("bash", ["-c", script], {
46688
47080
  cwd: root,
46689
47081
  env: { ...process.env, RAG_PY: py.bin, RAG_MODEL: model },
46690
47082
  detached: true,
@@ -46696,7 +47088,7 @@ rm -f "${RAG_REL}/.installing"
46696
47088
 
46697
47089
  // src/core/config-sync.ts
46698
47090
  import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
46699
- import { join as join36 } from "path";
47091
+ import { join as join37 } from "path";
46700
47092
  var SERVER_MANAGED_KEYS = [
46701
47093
  "physicPath",
46702
47094
  "sessionSwitchPct",
@@ -46713,7 +47105,7 @@ var SERVER_MANAGED_KEYS = [
46713
47105
  "projectAiMemoryBudgetChars"
46714
47106
  ];
46715
47107
  function readConfigText(profileDir2) {
46716
- const path = join36(profileDir2, "config.json");
47108
+ const path = join37(profileDir2, "config.json");
46717
47109
  if (existsSync18(path)) {
46718
47110
  try {
46719
47111
  return readFileSync16(path, "utf8");
@@ -47504,6 +47896,7 @@ var WsClient = class {
47504
47896
  if (handleToolsChannels(this.hctx, message, payload)) return;
47505
47897
  if (handleGitChannels(this.hctx, message, payload)) return;
47506
47898
  if (handleSupportChannels(this.hctx, message, payload)) return;
47899
+ if (handleResearchChannels(this.hctx, message, payload)) return;
47507
47900
  if (handleMiscChannels(this.hctx, message, payload)) return;
47508
47901
  this.bus.log(t("error.unsupportedChannel", { channel: message.channel }), "warn");
47509
47902
  }
@@ -47622,7 +48015,7 @@ var WsClient = class {
47622
48015
  */
47623
48016
  physicFolderPath(projectName) {
47624
48017
  const safe = projectName.replace(/[/\\]/g, "_").replace(/\.\./g, "_").trim();
47625
- return safe ? join37(this.context.profileDir, safe) : null;
48018
+ return safe ? join38(this.context.profileDir, safe) : null;
47626
48019
  }
47627
48020
  /**
47628
48021
  * Report machine.status: version + the state of the physic project being served
@@ -47734,7 +48127,7 @@ var WsClient = class {
47734
48127
  */
47735
48128
  uploadLog() {
47736
48129
  try {
47737
- const upload = readLogUpload(join37(this.context.profileDir, "logs"));
48130
+ const upload = readLogUpload(join38(this.context.profileDir, "logs"));
47738
48131
  if (!upload) return;
47739
48132
  const sig = `${upload.fileName}:${upload.totalBytes}`;
47740
48133
  if (sig === this.lastLogSig) return;
@@ -47896,10 +48289,10 @@ var WsClient = class {
47896
48289
  };
47897
48290
 
47898
48291
  // src/core/instance-lock.ts
47899
- import { readFileSync as readFileSync17, rmSync as rmSync9, writeFileSync as writeFileSync9 } from "fs";
47900
- import { join as join38 } from "path";
48292
+ import { readFileSync as readFileSync17, rmSync as rmSync10, writeFileSync as writeFileSync9 } from "fs";
48293
+ import { join as join39 } from "path";
47901
48294
  function lockPath(profileDir2) {
47902
- return join38(profileDir2, "start.lock");
48295
+ return join39(profileDir2, "start.lock");
47903
48296
  }
47904
48297
  function isAlive(pid) {
47905
48298
  try {
@@ -47925,7 +48318,7 @@ function acquireInstanceLock(profileDir2) {
47925
48318
  function releaseInstanceLock(profileDir2) {
47926
48319
  try {
47927
48320
  const pid = Number.parseInt(readFileSync17(lockPath(profileDir2), "utf8").trim(), 10);
47928
- if (pid === process.pid) rmSync9(lockPath(profileDir2), { force: true });
48321
+ if (pid === process.pid) rmSync10(lockPath(profileDir2), { force: true });
47929
48322
  } catch {
47930
48323
  }
47931
48324
  }
@@ -48220,8 +48613,8 @@ var SessionBus = class {
48220
48613
 
48221
48614
  // src/core/control-server.ts
48222
48615
  import { createServer } from "net";
48223
- import { chmodSync as chmodSync5, rmSync as rmSync10 } from "fs";
48224
- import { join as join40 } from "path";
48616
+ import { chmodSync as chmodSync5, rmSync as rmSync11 } from "fs";
48617
+ import { join as join41 } from "path";
48225
48618
 
48226
48619
  // src/core/control-protocol.ts
48227
48620
  var CONTROL_SOCKET_FILE = "control.sock";
@@ -48252,9 +48645,9 @@ function createFrameParser() {
48252
48645
  // src/core/control-token.ts
48253
48646
  import { chmodSync as chmodSync4, readFileSync as readFileSync18, writeFileSync as writeFileSync10 } from "fs";
48254
48647
  import { randomBytes as randomBytes3, timingSafeEqual } from "crypto";
48255
- import { join as join39 } from "path";
48648
+ import { join as join40 } from "path";
48256
48649
  function controlTokenPath(profileDir2) {
48257
- return join39(profileDir2, CONTROL_TOKEN_FILE);
48650
+ return join40(profileDir2, CONTROL_TOKEN_FILE);
48258
48651
  }
48259
48652
  function writeControlToken(profileDir2) {
48260
48653
  const token = randomBytes3(32).toString("hex");
@@ -48283,9 +48676,9 @@ function tokenMatches(expected, given) {
48283
48676
 
48284
48677
  // src/core/control-server.ts
48285
48678
  function startControlServer(bus, profileDir2, info) {
48286
- const socketPath = join40(profileDir2, CONTROL_SOCKET_FILE);
48679
+ const socketPath = join41(profileDir2, CONTROL_SOCKET_FILE);
48287
48680
  try {
48288
- rmSync10(socketPath, { force: true });
48681
+ rmSync11(socketPath, { force: true });
48289
48682
  } catch {
48290
48683
  }
48291
48684
  const clients = /* @__PURE__ */ new Set();
@@ -48376,8 +48769,8 @@ function startControlServer(bus, profileDir2, info) {
48376
48769
  clients.clear();
48377
48770
  server.close();
48378
48771
  try {
48379
- rmSync10(socketPath, { force: true });
48380
- rmSync10(controlTokenPath(profileDir2), { force: true });
48772
+ rmSync11(socketPath, { force: true });
48773
+ rmSync11(controlTokenPath(profileDir2), { force: true });
48381
48774
  } catch {
48382
48775
  }
48383
48776
  };
@@ -50356,7 +50749,7 @@ var src_default = Yoga;
50356
50749
 
50357
50750
  // ../../node_modules/.pnpm/ink@5.2.1_@types+react@18.3.31_react@18.3.1/node_modules/ink/build/reconciler.js
50358
50751
  var import_react_reconciler = __toESM(require_react_reconciler(), 1);
50359
- var import_constants12 = __toESM(require_constants2(), 1);
50752
+ var import_constants14 = __toESM(require_constants2(), 1);
50360
50753
  import process3 from "process";
50361
50754
 
50362
50755
  // ../../node_modules/.pnpm/ansi-regex@6.2.2/node_modules/ansi-regex/index.js
@@ -50527,7 +50920,7 @@ function widestLine(string) {
50527
50920
  }
50528
50921
 
50529
50922
  // ../../node_modules/.pnpm/ink@5.2.1_@types+react@18.3.31_react@18.3.1/node_modules/ink/build/measure-text.js
50530
- var cache = {};
50923
+ var cache2 = {};
50531
50924
  var measureText = (text) => {
50532
50925
  if (text.length === 0) {
50533
50926
  return {
@@ -50535,13 +50928,13 @@ var measureText = (text) => {
50535
50928
  height: 0
50536
50929
  };
50537
50930
  }
50538
- const cachedDimensions = cache[text];
50931
+ const cachedDimensions = cache2[text];
50539
50932
  if (cachedDimensions) {
50540
50933
  return cachedDimensions;
50541
50934
  }
50542
50935
  const width = widestLine(text);
50543
50936
  const height = text.split("\n").length;
50544
- cache[text] = { width, height };
50937
+ cache2[text] = { width, height };
50545
50938
  return { width, height };
50546
50939
  };
50547
50940
  var measure_text_default = measureText;
@@ -51066,10 +51459,10 @@ function cliTruncate(text, columns, options = {}) {
51066
51459
  }
51067
51460
 
51068
51461
  // ../../node_modules/.pnpm/ink@5.2.1_@types+react@18.3.31_react@18.3.1/node_modules/ink/build/wrap-text.js
51069
- var cache2 = {};
51462
+ var cache3 = {};
51070
51463
  var wrapText = (text, maxWidth, wrapType) => {
51071
51464
  const cacheKey = text + String(maxWidth) + String(wrapType);
51072
- const cachedText = cache2[cacheKey];
51465
+ const cachedText = cache3[cacheKey];
51073
51466
  if (cachedText) {
51074
51467
  return cachedText;
51075
51468
  }
@@ -51090,7 +51483,7 @@ var wrapText = (text, maxWidth, wrapType) => {
51090
51483
  }
51091
51484
  wrappedText = cliTruncate(text, maxWidth, { position });
51092
51485
  }
51093
- cache2[cacheKey] = wrappedText;
51486
+ cache3[cacheKey] = wrappedText;
51094
51487
  return wrappedText;
51095
51488
  };
51096
51489
  var wrap_text_default = wrapText;
@@ -51598,7 +51991,7 @@ var reconciler_default = (0, import_react_reconciler.default)({
51598
51991
  scheduleTimeout: setTimeout,
51599
51992
  cancelTimeout: clearTimeout,
51600
51993
  noTimeout: -1,
51601
- getCurrentEventPriority: () => import_constants12.DefaultEventPriority,
51994
+ getCurrentEventPriority: () => import_constants14.DefaultEventPriority,
51602
51995
  beforeActiveInstanceBlur() {
51603
51996
  },
51604
51997
  afterActiveInstanceBlur() {
@@ -54041,10 +54434,10 @@ var import_react26 = __toESM(require_react(), 1);
54041
54434
 
54042
54435
  // src/core/input-history.ts
54043
54436
  import { existsSync as existsSync21, readFileSync as readFileSync20, writeFileSync as writeFileSync11 } from "fs";
54044
- import { join as join41 } from "path";
54437
+ import { join as join42 } from "path";
54045
54438
  var MAX_HISTORY = 200;
54046
54439
  function historyPath(profileDir2) {
54047
- return join41(profileDir2, "input-history.json");
54440
+ return join42(profileDir2, "input-history.json");
54048
54441
  }
54049
54442
  function loadInputHistory(profileDir2) {
54050
54443
  const path = historyPath(profileDir2);
@@ -54658,7 +55051,7 @@ async function runStart(profileDir2, profileName) {
54658
55051
  return;
54659
55052
  }
54660
55053
  logger.configure({
54661
- dir: join42(profileDir2, "logs"),
55054
+ dir: join43(profileDir2, "logs"),
54662
55055
  level: resolveLogLevel(),
54663
55056
  base: { version: CLI_VERSION, profile: profileName, scope: credential.scope }
54664
55057
  });
@@ -54678,7 +55071,7 @@ async function runStart(profileDir2, profileName) {
54678
55071
  if (result.action === "updated") {
54679
55072
  logger.info("update.updated", { version: result.version });
54680
55073
  console.log(t("start.restarting"));
54681
- const child = spawn11(process.execPath, process.argv.slice(1), {
55074
+ const child = spawn12(process.execPath, process.argv.slice(1), {
54682
55075
  stdio: "inherit",
54683
55076
  env: { ...process.env, FOURPM_NO_UPDATE: "1" }
54684
55077
  });
@@ -54768,7 +55161,7 @@ async function runStart(profileDir2, profileName) {
54768
55161
  }
54769
55162
 
54770
55163
  // src/commands/attach.ts
54771
- import { join as join43 } from "path";
55164
+ import { join as join44 } from "path";
54772
55165
 
54773
55166
  // src/core/control-client.ts
54774
55167
  import { connect as connect2 } from "net";
@@ -54877,7 +55270,7 @@ async function runAttach(profileDir2, profileName) {
54877
55270
  process.exitCode = 1;
54878
55271
  return;
54879
55272
  }
54880
- const socketPath = join43(profileDir2, CONTROL_SOCKET_FILE);
55273
+ const socketPath = join44(profileDir2, CONTROL_SOCKET_FILE);
54881
55274
  const bus = new SessionBus();
54882
55275
  let conn;
54883
55276
  try {
@@ -54908,11 +55301,11 @@ async function runAttach(profileDir2, profileName) {
54908
55301
 
54909
55302
  // src/commands/auto-run.ts
54910
55303
  import { connect as connect3 } from "net";
54911
- import { join as join44 } from "path";
55304
+ import { join as join45 } from "path";
54912
55305
  var AUTO_RUN_TIMEOUT_MS = 3 * 60 * 60 * 1e3;
54913
55306
  async function runAutoRun(profileDir2, profileName) {
54914
55307
  initI18n(readProfileConfig(profileDir2).locale);
54915
- const socketPath = join44(profileDir2, CONTROL_SOCKET_FILE);
55308
+ const socketPath = join45(profileDir2, CONTROL_SOCKET_FILE);
54916
55309
  const token = readControlToken(profileDir2);
54917
55310
  const code = await new Promise((resolve13) => {
54918
55311
  let settled = false;
@@ -54969,7 +55362,7 @@ async function runAutoRun(profileDir2, profileName) {
54969
55362
 
54970
55363
  // src/commands/git-credential.ts
54971
55364
  import { connect as connect4 } from "net";
54972
- import { join as join45 } from "path";
55365
+ import { join as join46 } from "path";
54973
55366
  var REQUEST_TIMEOUT_MS = 2e4;
54974
55367
  function requestToken(host, path) {
54975
55368
  const profileDir2 = process.env[PROFILE_DIR_ENV];
@@ -54984,7 +55377,7 @@ function requestToken(host, path) {
54984
55377
  socket.destroy();
54985
55378
  resolve13(value);
54986
55379
  };
54987
- const socket = connect4(join45(profileDir2, CONTROL_SOCKET_FILE));
55380
+ const socket = connect4(join46(profileDir2, CONTROL_SOCKET_FILE));
54988
55381
  socket.setEncoding("utf8");
54989
55382
  const timer = setTimeout(() => finish(null), REQUEST_TIMEOUT_MS);
54990
55383
  socket.on("connect", () => {
@@ -55038,12 +55431,12 @@ async function runGitToken() {
55038
55431
  }
55039
55432
 
55040
55433
  // src/commands/ai-login.ts
55041
- import { spawn as spawn12 } from "child_process";
55434
+ import { spawn as spawn13 } from "child_process";
55042
55435
  import { mkdirSync as mkdirSync10 } from "fs";
55043
55436
  import { basename as basename8 } from "path";
55044
55437
  function runProviderLogin(cmd, envVar, dir2) {
55045
55438
  return new Promise((resolve13) => {
55046
- const child = spawn12(cmd, [], {
55439
+ const child = spawn13(cmd, [], {
55047
55440
  stdio: "inherit",
55048
55441
  env: { ...process.env, [envVar]: dir2 }
55049
55442
  });
@@ -55088,7 +55481,7 @@ async function runAiLogin(profileDir2, profileName, aiFilter) {
55088
55481
  }
55089
55482
 
55090
55483
  // src/commands/unlink.ts
55091
- import { rmSync as rmSync11 } from "fs";
55484
+ import { rmSync as rmSync12 } from "fs";
55092
55485
  async function runUnlink(profileDir2, profileName) {
55093
55486
  initI18n(readProfileConfig(profileDir2).locale);
55094
55487
  const cred = readCredential(profileDir2, profileName);
@@ -55102,7 +55495,7 @@ async function runUnlink(profileDir2, profileName) {
55102
55495
  } catch (err) {
55103
55496
  console.log(t("unlink.revokeFailed", { message: err.message }));
55104
55497
  }
55105
- rmSync11(profileDir2, { recursive: true, force: true });
55498
+ rmSync12(profileDir2, { recursive: true, force: true });
55106
55499
  clearDefaultProfileIf(profileName);
55107
55500
  console.log(t("unlink.deleted"));
55108
55501
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4pm/cli",
3
- "version": "1.31.0",
3
+ "version": "1.32.0-b",
4
4
  "private": false,
5
5
  "description": "4PM CLI — drives Claude/Codex CLIs on AI worker machines (Node + TypeScript)",
6
6
  "license": "LicenseRef-4PM-Source-Available",