@4pm/cli 1.21.0 → 1.22.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 +424 -178
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5713,14 +5713,14 @@ var require_react_development = __commonJS({
|
|
|
5713
5713
|
var thenableResult = result;
|
|
5714
5714
|
var wasAwaited = false;
|
|
5715
5715
|
var thenable = {
|
|
5716
|
-
then: function(
|
|
5716
|
+
then: function(resolve11, reject) {
|
|
5717
5717
|
wasAwaited = true;
|
|
5718
5718
|
thenableResult.then(function(returnValue2) {
|
|
5719
5719
|
popActScope(prevActScopeDepth);
|
|
5720
5720
|
if (actScopeDepth === 0) {
|
|
5721
|
-
recursivelyFlushAsyncActWork(returnValue2,
|
|
5721
|
+
recursivelyFlushAsyncActWork(returnValue2, resolve11, reject);
|
|
5722
5722
|
} else {
|
|
5723
|
-
|
|
5723
|
+
resolve11(returnValue2);
|
|
5724
5724
|
}
|
|
5725
5725
|
}, function(error2) {
|
|
5726
5726
|
popActScope(prevActScopeDepth);
|
|
@@ -5750,20 +5750,20 @@ var require_react_development = __commonJS({
|
|
|
5750
5750
|
ReactCurrentActQueue.current = null;
|
|
5751
5751
|
}
|
|
5752
5752
|
var _thenable = {
|
|
5753
|
-
then: function(
|
|
5753
|
+
then: function(resolve11, reject) {
|
|
5754
5754
|
if (ReactCurrentActQueue.current === null) {
|
|
5755
5755
|
ReactCurrentActQueue.current = [];
|
|
5756
|
-
recursivelyFlushAsyncActWork(returnValue,
|
|
5756
|
+
recursivelyFlushAsyncActWork(returnValue, resolve11, reject);
|
|
5757
5757
|
} else {
|
|
5758
|
-
|
|
5758
|
+
resolve11(returnValue);
|
|
5759
5759
|
}
|
|
5760
5760
|
}
|
|
5761
5761
|
};
|
|
5762
5762
|
return _thenable;
|
|
5763
5763
|
} else {
|
|
5764
5764
|
var _thenable2 = {
|
|
5765
|
-
then: function(
|
|
5766
|
-
|
|
5765
|
+
then: function(resolve11, reject) {
|
|
5766
|
+
resolve11(returnValue);
|
|
5767
5767
|
}
|
|
5768
5768
|
};
|
|
5769
5769
|
return _thenable2;
|
|
@@ -5779,7 +5779,7 @@ var require_react_development = __commonJS({
|
|
|
5779
5779
|
actScopeDepth = prevActScopeDepth;
|
|
5780
5780
|
}
|
|
5781
5781
|
}
|
|
5782
|
-
function recursivelyFlushAsyncActWork(returnValue,
|
|
5782
|
+
function recursivelyFlushAsyncActWork(returnValue, resolve11, reject) {
|
|
5783
5783
|
{
|
|
5784
5784
|
var queue = ReactCurrentActQueue.current;
|
|
5785
5785
|
if (queue !== null) {
|
|
@@ -5788,16 +5788,16 @@ var require_react_development = __commonJS({
|
|
|
5788
5788
|
enqueueTask(function() {
|
|
5789
5789
|
if (queue.length === 0) {
|
|
5790
5790
|
ReactCurrentActQueue.current = null;
|
|
5791
|
-
|
|
5791
|
+
resolve11(returnValue);
|
|
5792
5792
|
} else {
|
|
5793
|
-
recursivelyFlushAsyncActWork(returnValue,
|
|
5793
|
+
recursivelyFlushAsyncActWork(returnValue, resolve11, reject);
|
|
5794
5794
|
}
|
|
5795
5795
|
});
|
|
5796
5796
|
} catch (error2) {
|
|
5797
5797
|
reject(error2);
|
|
5798
5798
|
}
|
|
5799
5799
|
} else {
|
|
5800
|
-
|
|
5800
|
+
resolve11(returnValue);
|
|
5801
5801
|
}
|
|
5802
5802
|
}
|
|
5803
5803
|
}
|
|
@@ -28329,7 +28329,10 @@ function defaultProfileConfig() {
|
|
|
28329
28329
|
webBlockedCommands: ["quit", "config"],
|
|
28330
28330
|
// Shared AI memory (ADR-0245) — off by default (opt-in; costs a compaction call per turn).
|
|
28331
28331
|
aiMemoryEnabled: false,
|
|
28332
|
-
aiMemoryBudgetChars: 1e3
|
|
28332
|
+
aiMemoryBudgetChars: 1e3,
|
|
28333
|
+
// Bounded native resume (ADR-0339) — resume only a warm (≤5 min), not-too-large session.
|
|
28334
|
+
aiResumeMaxIdleMinutes: 5,
|
|
28335
|
+
aiResumeMaxContextTokens: 1e5
|
|
28333
28336
|
};
|
|
28334
28337
|
}
|
|
28335
28338
|
function resolveMemoryConfig(config) {
|
|
@@ -28339,6 +28342,14 @@ function resolveMemoryConfig(config) {
|
|
|
28339
28342
|
const budgetChars = projectBudget > 0 ? projectBudget : config.aiMemoryBudgetChars ?? 1e3;
|
|
28340
28343
|
return { enabled, budgetChars };
|
|
28341
28344
|
}
|
|
28345
|
+
function resolveResumePolicy(config) {
|
|
28346
|
+
const idleMin = config.aiResumeMaxIdleMinutes ?? 5;
|
|
28347
|
+
const maxCtx = config.aiResumeMaxContextTokens ?? 1e5;
|
|
28348
|
+
return {
|
|
28349
|
+
maxIdleMs: idleMin > 0 ? idleMin * 6e4 : Number.POSITIVE_INFINITY,
|
|
28350
|
+
maxContextTokens: maxCtx > 0 ? maxCtx : Number.POSITIVE_INFINITY
|
|
28351
|
+
};
|
|
28352
|
+
}
|
|
28342
28353
|
function resolveIdleAutoClearMinutes(config) {
|
|
28343
28354
|
const project = config.projectAutoClearIdleMinutes ?? 0;
|
|
28344
28355
|
return project > 0 ? project : config.autoClearIdleMinutes ?? 10;
|
|
@@ -28628,6 +28639,16 @@ var UsageMetric = {
|
|
|
28628
28639
|
COMMANDS: "commands",
|
|
28629
28640
|
AUTONOMOUS_MINUTES: "autonomous_minutes"
|
|
28630
28641
|
};
|
|
28642
|
+
var AiTokenProvider = {
|
|
28643
|
+
CLAUDE: "claude",
|
|
28644
|
+
CODEX: "codex",
|
|
28645
|
+
ANTIGRAVITY: "antigravity"
|
|
28646
|
+
};
|
|
28647
|
+
var AI_TOKEN_PROVIDERS = [
|
|
28648
|
+
AiTokenProvider.CLAUDE,
|
|
28649
|
+
AiTokenProvider.CODEX,
|
|
28650
|
+
AiTokenProvider.ANTIGRAVITY
|
|
28651
|
+
];
|
|
28631
28652
|
|
|
28632
28653
|
// vendor/@4pm/constants/roles.ts
|
|
28633
28654
|
var Role = {
|
|
@@ -32732,7 +32753,7 @@ async function checkAndUpdate(serverUrl, autoUpdate) {
|
|
|
32732
32753
|
|
|
32733
32754
|
// src/core/ws-client.ts
|
|
32734
32755
|
init_wrapper();
|
|
32735
|
-
import { randomUUID as
|
|
32756
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
32736
32757
|
import { basename as basename7, join as join32 } from "path";
|
|
32737
32758
|
import { existsSync as existsSync17, mkdirSync as mkdirSync8 } from "fs";
|
|
32738
32759
|
|
|
@@ -37498,7 +37519,13 @@ var contactChangeConfirmSchema = external_exports.object({
|
|
|
37498
37519
|
token: external_exports.string().min(1)
|
|
37499
37520
|
});
|
|
37500
37521
|
var activityHeartbeatSchema = external_exports.object({
|
|
37501
|
-
projectId: external_exports.string().uuid().optional()
|
|
37522
|
+
projectId: external_exports.string().uuid().optional(),
|
|
37523
|
+
/** ADR-0342 — the client is idle (5 min no input · tab hidden) ⇒ presence `idle`. */
|
|
37524
|
+
idle: external_exports.boolean().optional()
|
|
37525
|
+
});
|
|
37526
|
+
var USER_CARDS_MAX_IDS = 100;
|
|
37527
|
+
var userCardsQuerySchema = external_exports.object({
|
|
37528
|
+
ids: external_exports.string().transform((v2) => [...new Set(v2.split(",").map((s3) => s3.trim()).filter(Boolean))]).pipe(external_exports.array(external_exports.string().uuid()).min(1).max(USER_CARDS_MAX_IDS))
|
|
37502
37529
|
});
|
|
37503
37530
|
|
|
37504
37531
|
// src/core/scaffold.ts
|
|
@@ -37506,19 +37533,49 @@ import { cp, mkdir, mkdtemp, rm, writeFile } from "fs/promises";
|
|
|
37506
37533
|
import { existsSync as existsSync4 } from "fs";
|
|
37507
37534
|
import { execFile as execFile2 } from "child_process";
|
|
37508
37535
|
import { tmpdir as tmpdir2 } from "os";
|
|
37509
|
-
import { dirname as dirname3, join as join6, resolve } from "path";
|
|
37536
|
+
import { dirname as dirname3, join as join6, resolve as resolve2 } from "path";
|
|
37510
37537
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
37511
37538
|
import { promisify as promisify2 } from "util";
|
|
37512
37539
|
|
|
37513
37540
|
// src/core/ai-assist.ts
|
|
37514
37541
|
import { execFile } from "child_process";
|
|
37515
37542
|
import { promisify } from "util";
|
|
37543
|
+
|
|
37544
|
+
// src/utils/agent-deny.ts
|
|
37545
|
+
import { isAbsolute, resolve } from "path";
|
|
37546
|
+
var HOME_SECRET_PATTERNS = [
|
|
37547
|
+
"~/.4pm/**",
|
|
37548
|
+
"~/.ssh/**",
|
|
37549
|
+
"~/.config/gh/**",
|
|
37550
|
+
"~/.git-credentials",
|
|
37551
|
+
"~/.claude*",
|
|
37552
|
+
"~/.claude*/**"
|
|
37553
|
+
];
|
|
37554
|
+
function absDirPattern(dir2) {
|
|
37555
|
+
const abs = (isAbsolute(dir2) ? dir2 : resolve(dir2)).replace(/\\/g, "/").replace(/\/+$/, "");
|
|
37556
|
+
return `/${abs.startsWith("/") ? abs : `/${abs}`}/**`;
|
|
37557
|
+
}
|
|
37558
|
+
function agentDenyRules(profileDirs2 = []) {
|
|
37559
|
+
const patterns = new Set(HOME_SECRET_PATTERNS);
|
|
37560
|
+
for (const dir2 of profileDirs2) {
|
|
37561
|
+
if (dir2 && dir2.trim()) patterns.add(absDirPattern(dir2.trim()));
|
|
37562
|
+
}
|
|
37563
|
+
const rules = [];
|
|
37564
|
+
for (const p2 of patterns) rules.push(`Read(${p2})`, `Edit(${p2})`);
|
|
37565
|
+
return rules;
|
|
37566
|
+
}
|
|
37567
|
+
function denySettingsArgs(cmd, profileDirs2 = []) {
|
|
37568
|
+
if (!cmd.includes("claude")) return [];
|
|
37569
|
+
return ["--settings", JSON.stringify({ permissions: { deny: agentDenyRules(profileDirs2) } })];
|
|
37570
|
+
}
|
|
37571
|
+
|
|
37572
|
+
// src/core/ai-assist.ts
|
|
37516
37573
|
var run = promisify(execFile);
|
|
37517
37574
|
function aiCli() {
|
|
37518
37575
|
return process.env.AI_CLI || "claude";
|
|
37519
37576
|
}
|
|
37520
37577
|
function aiArgs(cli, prompt) {
|
|
37521
|
-
if (cli === "claude") return ["-p", prompt, "--output-format", "json"];
|
|
37578
|
+
if (cli === "claude") return ["-p", prompt, "--output-format", "json", ...denySettingsArgs(cli, [process.env.CLAUDE_CONFIG_DIR])];
|
|
37522
37579
|
if (cli === "codex") return ["exec", "--json", prompt];
|
|
37523
37580
|
return ["-p", prompt];
|
|
37524
37581
|
}
|
|
@@ -37810,8 +37867,8 @@ async function ensureReposCloned(physicRoot, repos, emit) {
|
|
|
37810
37867
|
function sampleDir() {
|
|
37811
37868
|
if (process.env.SCAFFOLD_SAMPLE_DIR) return process.env.SCAFFOLD_SAMPLE_DIR;
|
|
37812
37869
|
const here = dirname3(fileURLToPath3(import.meta.url));
|
|
37813
|
-
const bundled =
|
|
37814
|
-
for (const candidate of [bundled,
|
|
37870
|
+
const bundled = resolve2(here, "project-sample");
|
|
37871
|
+
for (const candidate of [bundled, resolve2(here, "../project-sample"), resolve2(here, "../../project-sample")]) {
|
|
37815
37872
|
if (existsSync4(candidate)) return candidate;
|
|
37816
37873
|
}
|
|
37817
37874
|
return bundled;
|
|
@@ -37823,7 +37880,7 @@ async function scaffoldProject(payload, profileDir2, onProgress) {
|
|
|
37823
37880
|
onProgress?.({ projectId: payload.projectId, step, message });
|
|
37824
37881
|
};
|
|
37825
37882
|
try {
|
|
37826
|
-
const root = process.env.SCAFFOLD_ROOT ?
|
|
37883
|
+
const root = process.env.SCAFFOLD_ROOT ? resolve2(process.env.SCAFFOLD_ROOT) : resolve2(profileDir2);
|
|
37827
37884
|
const target = join6(root, payload.projectName);
|
|
37828
37885
|
await mkdir(target, { recursive: true });
|
|
37829
37886
|
const repo = singleRepo(reposOf(payload.spec));
|
|
@@ -37928,7 +37985,7 @@ async function addProject(payload, profileDir2, onProgress) {
|
|
|
37928
37985
|
onProgress?.({ projectId: payload.projectId, step, message });
|
|
37929
37986
|
};
|
|
37930
37987
|
try {
|
|
37931
|
-
const root = process.env.SCAFFOLD_ROOT ?
|
|
37988
|
+
const root = process.env.SCAFFOLD_ROOT ? resolve2(process.env.SCAFFOLD_ROOT) : resolve2(profileDir2);
|
|
37932
37989
|
const target = join6(root, payload.projectName);
|
|
37933
37990
|
await mkdir(target, { recursive: true });
|
|
37934
37991
|
const repo = singleRepo(payload.repos ?? []);
|
|
@@ -38568,8 +38625,8 @@ function resolveInstallTimeoutMs(sec) {
|
|
|
38568
38625
|
return sec && sec > 0 ? sec * 1e3 : TOOL_INSTALL_TIMEOUT_DEFAULT_MS;
|
|
38569
38626
|
}
|
|
38570
38627
|
function sleep(ms2) {
|
|
38571
|
-
return new Promise((
|
|
38572
|
-
const t2 = setTimeout(
|
|
38628
|
+
return new Promise((resolve11) => {
|
|
38629
|
+
const t2 = setTimeout(resolve11, ms2);
|
|
38573
38630
|
t2.unref?.();
|
|
38574
38631
|
});
|
|
38575
38632
|
}
|
|
@@ -38597,14 +38654,14 @@ function classifyToolFailure(code, out) {
|
|
|
38597
38654
|
return { reason: "other", retryable: false };
|
|
38598
38655
|
}
|
|
38599
38656
|
function run3(cmd, args, onLine, timeoutMs = 18e4) {
|
|
38600
|
-
return new Promise((
|
|
38657
|
+
return new Promise((resolve11) => {
|
|
38601
38658
|
let out = "";
|
|
38602
38659
|
let buf = "";
|
|
38603
38660
|
let child = null;
|
|
38604
38661
|
const done = (code) => {
|
|
38605
38662
|
clearTimeout(timer);
|
|
38606
38663
|
if (onLine && buf.trim()) onLine(buf.trim());
|
|
38607
|
-
|
|
38664
|
+
resolve11({ code, out });
|
|
38608
38665
|
};
|
|
38609
38666
|
const timer = setTimeout(() => {
|
|
38610
38667
|
child?.kill();
|
|
@@ -39034,7 +39091,7 @@ function clearPinnedCredential(profileDir2) {
|
|
|
39034
39091
|
// src/utils/ai-cli.ts
|
|
39035
39092
|
import { readFileSync as readFileSync8 } from "fs";
|
|
39036
39093
|
import { homedir as homedir3 } from "os";
|
|
39037
|
-
import { basename, isAbsolute, join as join9 } from "path";
|
|
39094
|
+
import { basename, isAbsolute as isAbsolute2, join as join9 } from "path";
|
|
39038
39095
|
var REQUIRED_AI_ARGS = {
|
|
39039
39096
|
claude: ["-p", "--output-format", "stream-json", "--verbose"],
|
|
39040
39097
|
codex: ["exec", "--json"],
|
|
@@ -39171,13 +39228,22 @@ function claudeCredentialKeys(config) {
|
|
|
39171
39228
|
}
|
|
39172
39229
|
function resolveHomePath(value) {
|
|
39173
39230
|
if (value.startsWith("~")) return join9(homedir3(), value.slice(1).replace(/^[/\\]/, ""));
|
|
39174
|
-
if (
|
|
39231
|
+
if (isAbsolute2(value)) return value;
|
|
39175
39232
|
return join9(homedir3(), value);
|
|
39176
39233
|
}
|
|
39177
39234
|
function profileDirs(profiles) {
|
|
39178
39235
|
if (!profiles) return [];
|
|
39179
39236
|
return profiles.filter(isUsableProfile).map((p2) => resolveHomePath(p2.profile));
|
|
39180
39237
|
}
|
|
39238
|
+
function allAiProfileDirs(config) {
|
|
39239
|
+
const dirs = [
|
|
39240
|
+
...profileDirs(config.claudeHome),
|
|
39241
|
+
...profileDirs(config.codexHome),
|
|
39242
|
+
...profileDirs(config.antigravityHome),
|
|
39243
|
+
...profileDirs(config.aiProfiles)
|
|
39244
|
+
];
|
|
39245
|
+
return [...new Set(dirs)];
|
|
39246
|
+
}
|
|
39181
39247
|
function folderScopeGuard(folder, prompt) {
|
|
39182
39248
|
return [
|
|
39183
39249
|
`IMPORTANT: You are working inside the project folder \`${folder}\`. Only read, use, and`,
|
|
@@ -39201,7 +39267,7 @@ function overrideEnv(cmd, o) {
|
|
|
39201
39267
|
const budget = THINKING_TOKENS[o.thinking];
|
|
39202
39268
|
return budget ? { MAX_THINKING_TOKENS: String(budget) } : {};
|
|
39203
39269
|
}
|
|
39204
|
-
function buildRunArgs(profile, cmd, resumeId, oneShot = false, override, readOnly = false, bypass = false) {
|
|
39270
|
+
function buildRunArgs(profile, cmd, resumeId, oneShot = false, override, readOnly = false, bypass = false, denyDirs = []) {
|
|
39205
39271
|
const extras = profile.args ?? [];
|
|
39206
39272
|
const model = override?.model?.trim() || profile.model?.trim();
|
|
39207
39273
|
const modelArgs = model ? ["--model", model] : [];
|
|
@@ -39213,6 +39279,10 @@ function buildRunArgs(profile, cmd, resumeId, oneShot = false, override, readOnl
|
|
|
39213
39279
|
...extras,
|
|
39214
39280
|
...modelArgs,
|
|
39215
39281
|
...overrideArgs(cmd, override),
|
|
39282
|
+
// Secret-path deny rules (ADR-0347) — every configured credential dir + ~/.4pm, ~/.ssh, gh, git
|
|
39283
|
+
// credentials, ~/.claude*. A single-value flag, placed before the mode caps so their non-variadic
|
|
39284
|
+
// terminator still bounds `--disallowedTools`.
|
|
39285
|
+
...denySettingsArgs(cmd, denyDirs),
|
|
39216
39286
|
...modeExtra
|
|
39217
39287
|
];
|
|
39218
39288
|
}
|
|
@@ -39265,7 +39335,7 @@ function planUnifiedRun(prompt, config, workingCredential, resume, oneShot, over
|
|
|
39265
39335
|
return {
|
|
39266
39336
|
cmd: active,
|
|
39267
39337
|
attempts: [
|
|
39268
|
-
{ cmd: active, label: "default", dir: null, key: null, args: buildRunArgs({ profile: "" }, active, void 0, oneShot, override, readOnly, bypass), stdin: prompt, env: { ...baseEnv, ...overrideEnv(active, override) } }
|
|
39338
|
+
{ cmd: active, label: "default", dir: null, key: null, args: buildRunArgs({ profile: "" }, active, void 0, oneShot, override, readOnly, bypass, allAiProfileDirs(config)), stdin: prompt, env: { ...baseEnv, ...overrideEnv(active, override) } }
|
|
39269
39339
|
]
|
|
39270
39340
|
};
|
|
39271
39341
|
}
|
|
@@ -39283,7 +39353,7 @@ function planUnifiedRun(prompt, config, workingCredential, resume, oneShot, over
|
|
|
39283
39353
|
label: cred.label?.trim() || profileDisplayLabel(dir2),
|
|
39284
39354
|
dir: dir2,
|
|
39285
39355
|
key,
|
|
39286
|
-
args: buildRunArgs(cred, cmd, resume.get(key), oneShot, override, readOnly, bypass),
|
|
39356
|
+
args: buildRunArgs(cred, cmd, resume.get(key), oneShot, override, readOnly, bypass, [...allAiProfileDirs(config), dir2]),
|
|
39287
39357
|
stdin: prompt,
|
|
39288
39358
|
env: envVar ? { ...baseEnv, [envVar]: dir2, ...overrideEnv(cmd, override) } : { ...baseEnv, ...overrideEnv(cmd, override) }
|
|
39289
39359
|
}));
|
|
@@ -39296,7 +39366,7 @@ function planLegacyRun(prompt, config, workingDir, resume, oneShot, override, re
|
|
|
39296
39366
|
const defaultPlan = {
|
|
39297
39367
|
cmd,
|
|
39298
39368
|
attempts: [
|
|
39299
|
-
{ cmd, label: "default", dir: null, key: null, args: buildRunArgs({ profile: "" }, cmd, void 0, oneShot, override, readOnly, bypass), stdin: prompt, env: { ...baseEnv, ...overrideEnv(cmd, override) } }
|
|
39369
|
+
{ cmd, label: "default", dir: null, key: null, args: buildRunArgs({ profile: "" }, cmd, void 0, oneShot, override, readOnly, bypass, allAiProfileDirs(config)), stdin: prompt, env: { ...baseEnv, ...overrideEnv(cmd, override) } }
|
|
39300
39370
|
]
|
|
39301
39371
|
};
|
|
39302
39372
|
if (!envVar) return defaultPlan;
|
|
@@ -39313,7 +39383,7 @@ function planLegacyRun(prompt, config, workingDir, resume, oneShot, override, re
|
|
|
39313
39383
|
label: profileDisplayLabel(dir2),
|
|
39314
39384
|
dir: dir2,
|
|
39315
39385
|
key,
|
|
39316
|
-
args: buildRunArgs(profile, cmd, resume.get(key), oneShot, override, readOnly, bypass),
|
|
39386
|
+
args: buildRunArgs(profile, cmd, resume.get(key), oneShot, override, readOnly, bypass, [...allAiProfileDirs(config), dir2]),
|
|
39317
39387
|
stdin: prompt,
|
|
39318
39388
|
env: { ...baseEnv, [envVar]: dir2, ...overrideEnv(cmd, override) }
|
|
39319
39389
|
};
|
|
@@ -39356,16 +39426,16 @@ async function fetchUsage(token) {
|
|
|
39356
39426
|
}
|
|
39357
39427
|
}
|
|
39358
39428
|
function runUsageCli(dir2, aiCli2) {
|
|
39359
|
-
return new Promise((
|
|
39429
|
+
return new Promise((resolve11) => {
|
|
39360
39430
|
let out = "";
|
|
39361
39431
|
let child = null;
|
|
39362
39432
|
const finish = () => {
|
|
39363
39433
|
clearTimeout(timer);
|
|
39364
|
-
|
|
39434
|
+
resolve11(out);
|
|
39365
39435
|
};
|
|
39366
39436
|
const timer = setTimeout(() => {
|
|
39367
39437
|
child?.kill();
|
|
39368
|
-
|
|
39438
|
+
resolve11(out);
|
|
39369
39439
|
}, 3e4);
|
|
39370
39440
|
timer.unref?.();
|
|
39371
39441
|
try {
|
|
@@ -39688,9 +39758,9 @@ function pruneCommandOutputByAge(days) {
|
|
|
39688
39758
|
import { spawn as spawn4 } from "child_process";
|
|
39689
39759
|
import { execFile as execFile3 } from "child_process";
|
|
39690
39760
|
import { createHash as createHash3 } from "crypto";
|
|
39691
|
-
import { existsSync as existsSync10, mkdirSync as mkdirSync5, readdirSync as readdirSync5, readFileSync as readFileSync12, rmSync as rmSync5, statSync as statSync4, writeFileSync as writeFileSync7 } from "fs";
|
|
39692
|
-
import { randomUUID } from "crypto";
|
|
39761
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync5, mkdtempSync as mkdtempSync2, readdirSync as readdirSync5, readFileSync as readFileSync12, rmSync as rmSync5, statSync as statSync4, writeFileSync as writeFileSync7 } from "fs";
|
|
39693
39762
|
import { join as join14 } from "path";
|
|
39763
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
39694
39764
|
import { promisify as promisify3 } from "util";
|
|
39695
39765
|
|
|
39696
39766
|
// src/core/ai-stream.ts
|
|
@@ -39720,12 +39790,16 @@ function createAiStreamParser(cli) {
|
|
|
39720
39790
|
acc.output = ev.usage.output_tokens ?? 0;
|
|
39721
39791
|
acc.cacheRead = ev.usage.cache_read_input_tokens ?? 0;
|
|
39722
39792
|
acc.cacheCreation = ev.usage.cache_creation_input_tokens ?? 0;
|
|
39723
|
-
acc.tokens = acc.input + acc.output;
|
|
39793
|
+
acc.tokens = acc.input + acc.output + acc.cacheRead + acc.cacheCreation;
|
|
39724
39794
|
if (typeof ev.total_cost_usd === "number") acc.costUsd = ev.total_cost_usd;
|
|
39725
39795
|
}
|
|
39726
39796
|
return "";
|
|
39727
39797
|
}
|
|
39728
39798
|
if (ev.type === "assistant") {
|
|
39799
|
+
const u2 = ev.message?.usage;
|
|
39800
|
+
if (u2) {
|
|
39801
|
+
acc.contextTokens = (u2.input_tokens ?? 0) + (u2.cache_read_input_tokens ?? 0) + (u2.cache_creation_input_tokens ?? 0) + (u2.output_tokens ?? 0);
|
|
39802
|
+
}
|
|
39729
39803
|
const text = assistantText(ev);
|
|
39730
39804
|
return text ? `${text}
|
|
39731
39805
|
` : "";
|
|
@@ -39734,11 +39808,12 @@ function createAiStreamParser(cli) {
|
|
|
39734
39808
|
}
|
|
39735
39809
|
function handleCodex(ev) {
|
|
39736
39810
|
if (ev.type === "turn.completed" && ev.usage) {
|
|
39737
|
-
|
|
39811
|
+
const cached = ev.usage.cached_input_tokens ?? 0;
|
|
39812
|
+
acc.input = Math.max(0, (ev.usage.input_tokens ?? 0) - cached);
|
|
39738
39813
|
acc.output = (ev.usage.output_tokens ?? 0) + (ev.usage.reasoning_output_tokens ?? 0);
|
|
39739
|
-
acc.cacheRead =
|
|
39814
|
+
acc.cacheRead = cached;
|
|
39740
39815
|
acc.cacheCreation = 0;
|
|
39741
|
-
acc.tokens = acc.input + acc.output;
|
|
39816
|
+
acc.tokens = acc.input + acc.output + acc.cacheRead;
|
|
39742
39817
|
return "";
|
|
39743
39818
|
}
|
|
39744
39819
|
if (ev.type === "item.completed" && ev.item?.type === "agent_message") {
|
|
@@ -39786,10 +39861,65 @@ function createAiStreamParser(cli) {
|
|
|
39786
39861
|
apiError: () => ({ ...apiError })
|
|
39787
39862
|
};
|
|
39788
39863
|
}
|
|
39864
|
+
function aiProviderOf(cmd) {
|
|
39865
|
+
if (cmd.includes("codex")) return "codex";
|
|
39866
|
+
if (cmd.includes("antigravity")) return "antigravity";
|
|
39867
|
+
return "claude";
|
|
39868
|
+
}
|
|
39789
39869
|
function estimateTokens2(text) {
|
|
39790
39870
|
return Math.max(1, Math.ceil(text.length / 4));
|
|
39791
39871
|
}
|
|
39792
39872
|
|
|
39873
|
+
// src/core/agent-sandbox.ts
|
|
39874
|
+
var ENV_ALLOW = /* @__PURE__ */ new Set([
|
|
39875
|
+
"PATH",
|
|
39876
|
+
"HOME",
|
|
39877
|
+
"USER",
|
|
39878
|
+
"LOGNAME",
|
|
39879
|
+
"SHELL",
|
|
39880
|
+
"LANG",
|
|
39881
|
+
"TERM",
|
|
39882
|
+
"TZ",
|
|
39883
|
+
"TMPDIR",
|
|
39884
|
+
"TMP",
|
|
39885
|
+
"TEMP",
|
|
39886
|
+
"HTTP_PROXY",
|
|
39887
|
+
"HTTPS_PROXY",
|
|
39888
|
+
"NO_PROXY",
|
|
39889
|
+
"ALL_PROXY",
|
|
39890
|
+
"http_proxy",
|
|
39891
|
+
"https_proxy",
|
|
39892
|
+
"no_proxy",
|
|
39893
|
+
"all_proxy",
|
|
39894
|
+
"SSL_CERT_FILE",
|
|
39895
|
+
"SSL_CERT_DIR",
|
|
39896
|
+
"NODE_EXTRA_CA_CERTS",
|
|
39897
|
+
"SYSTEMROOT",
|
|
39898
|
+
"SYSTEMDRIVE",
|
|
39899
|
+
"WINDIR",
|
|
39900
|
+
"COMSPEC",
|
|
39901
|
+
"PATHEXT",
|
|
39902
|
+
"USERPROFILE",
|
|
39903
|
+
"APPDATA",
|
|
39904
|
+
"LOCALAPPDATA",
|
|
39905
|
+
"PROGRAMDATA",
|
|
39906
|
+
"PROGRAMFILES"
|
|
39907
|
+
]);
|
|
39908
|
+
var ENV_ALLOW_PREFIXES = ["LC_", "XDG_", "ANTHROPIC_", "CLAUDE_", "CODEX_", "OPENAI_", "AWS_", "GOOGLE_", "CLOUD_ML_", "VERTEX_"];
|
|
39909
|
+
function agentEnv(extraEnv, overrides) {
|
|
39910
|
+
const env3 = {};
|
|
39911
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
39912
|
+
if (value === void 0) continue;
|
|
39913
|
+
if (ENV_ALLOW.has(key) || ENV_ALLOW_PREFIXES.some((p2) => key.startsWith(p2))) env3[key] = value;
|
|
39914
|
+
}
|
|
39915
|
+
return { ...env3, ...extraEnv, ...overrides };
|
|
39916
|
+
}
|
|
39917
|
+
function gitAuthArgs(user, password) {
|
|
39918
|
+
if (!user && !password) return [];
|
|
39919
|
+
const basic = Buffer.from(`${user}:${password}`).toString("base64");
|
|
39920
|
+
return ["-c", `http.extraHeader=Authorization: Basic ${basic}`];
|
|
39921
|
+
}
|
|
39922
|
+
|
|
39793
39923
|
// src/core/support-answer.ts
|
|
39794
39924
|
var NO_USAGE = { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 };
|
|
39795
39925
|
var execFileAsync = promisify3(execFile3);
|
|
@@ -39803,18 +39933,34 @@ function repoDir(profileDir2, url, branch) {
|
|
|
39803
39933
|
const key = createHash3("sha1").update(`${url}#${branch}`).digest("hex").slice(0, 16);
|
|
39804
39934
|
return join14(kbRoot(profileDir2), key);
|
|
39805
39935
|
}
|
|
39806
|
-
|
|
39807
|
-
|
|
39808
|
-
|
|
39809
|
-
|
|
39936
|
+
var DENIED_TOOLS = ["Bash", "Edit", "Write", "NotebookEdit", "WebFetch", "WebSearch", "Task"];
|
|
39937
|
+
var KB_STALE_MS = 24 * 60 * 60 * 1e3;
|
|
39938
|
+
var kbTokens = /* @__PURE__ */ new Map();
|
|
39939
|
+
function kbAuth(token) {
|
|
39940
|
+
return token ? gitAuthArgs(token, "") : [];
|
|
39941
|
+
}
|
|
39942
|
+
async function pullKb(dir2) {
|
|
39943
|
+
const gitEnv = { ...process.env, GIT_TERMINAL_PROMPT: "0" };
|
|
39944
|
+
await execFileAsync("git", [...kbAuth(kbTokens.get(dir2)), "-C", dir2, "pull", "--ff-only"], { env: gitEnv });
|
|
39810
39945
|
}
|
|
39811
39946
|
async function ensureRepo(profileDir2, repo) {
|
|
39812
39947
|
const dir2 = repoDir(profileDir2, repo.url, repo.branch);
|
|
39813
|
-
if (existsSync10(join14(dir2, ".git"))) return dir2;
|
|
39814
|
-
const url = authedUrl(repo.url, repo.token);
|
|
39815
39948
|
const gitEnv = { ...process.env, GIT_TERMINAL_PROMPT: "0" };
|
|
39949
|
+
if (repo.token) kbTokens.set(dir2, repo.token);
|
|
39950
|
+
else kbTokens.delete(dir2);
|
|
39951
|
+
if (existsSync10(join14(dir2, ".git"))) {
|
|
39952
|
+
const current2 = await execFileAsync("git", ["-C", dir2, "remote", "get-url", "origin"], { env: gitEnv }).then((r) => r.stdout.trim()).catch(() => repo.url);
|
|
39953
|
+
if (current2 !== repo.url) {
|
|
39954
|
+
await execFileAsync("git", ["-C", dir2, "remote", "set-url", "origin", repo.url], { env: gitEnv }).catch(() => void 0);
|
|
39955
|
+
}
|
|
39956
|
+
const marker = existsSync10(join14(dir2, ".git", "FETCH_HEAD")) ? join14(dir2, ".git", "FETCH_HEAD") : join14(dir2, ".git", "HEAD");
|
|
39957
|
+
if (Date.now() - statSync4(marker).mtimeMs > KB_STALE_MS) {
|
|
39958
|
+
void pullKb(dir2).catch((err) => logger.warn("support.kb.refresh.failed", { dir: dir2, error: String(err) }));
|
|
39959
|
+
}
|
|
39960
|
+
return dir2;
|
|
39961
|
+
}
|
|
39816
39962
|
mkdirSync5(kbRoot(profileDir2), { recursive: true });
|
|
39817
|
-
await execFileAsync("git", ["clone", "--depth", "1", "--branch", repo.branch, url, dir2], {
|
|
39963
|
+
await execFileAsync("git", [...kbAuth(repo.token), "clone", "--depth", "1", "--branch", repo.branch, repo.url, dir2], {
|
|
39818
39964
|
env: gitEnv
|
|
39819
39965
|
});
|
|
39820
39966
|
return dir2;
|
|
@@ -39822,12 +39968,11 @@ async function ensureRepo(profileDir2, repo) {
|
|
|
39822
39968
|
async function refreshSupportKb(profileDir2) {
|
|
39823
39969
|
const root = kbRoot(profileDir2);
|
|
39824
39970
|
if (!existsSync10(root)) return;
|
|
39825
|
-
const gitEnv = { ...process.env, GIT_TERMINAL_PROMPT: "0" };
|
|
39826
39971
|
for (const entry of readdirSync5(root)) {
|
|
39827
39972
|
const dir2 = join14(root, entry);
|
|
39828
39973
|
if (!existsSync10(join14(dir2, ".git"))) continue;
|
|
39829
39974
|
try {
|
|
39830
|
-
await
|
|
39975
|
+
await pullKb(dir2);
|
|
39831
39976
|
} catch (err) {
|
|
39832
39977
|
logger.warn("support.kb.refresh.failed", { dir: dir2, error: String(err) });
|
|
39833
39978
|
}
|
|
@@ -39890,6 +40035,33 @@ function buildPrompt(docs, question, askerRole) {
|
|
|
39890
40035
|
question
|
|
39891
40036
|
].join("\n");
|
|
39892
40037
|
}
|
|
40038
|
+
function buildDraftPrompt(docs, context, task) {
|
|
40039
|
+
const goal = task === "reply_draft" ? [
|
|
40040
|
+
"You are drafting a reply FROM the 4PM support team TO the customer in the support ticket below.",
|
|
40041
|
+
"Address the customer's latest message, using the whole conversation for context. Be accurate,",
|
|
40042
|
+
"friendly and concise. Write in the language the customer uses in the ticket."
|
|
40043
|
+
] : [
|
|
40044
|
+
"You are drafting an outreach message FROM the 4PM platform team TO the customer organizations.",
|
|
40045
|
+
"Use the subject and any current draft below as the starting point. Be clear, friendly and concise.",
|
|
40046
|
+
"Write in the language of the subject/draft (or of the admin's instructions when those are empty)."
|
|
40047
|
+
];
|
|
40048
|
+
return [
|
|
40049
|
+
...goal,
|
|
40050
|
+
"Use the documentation and FAQ below for any product facts; never invent features, prices or",
|
|
40051
|
+
"commitments that are not in the docs \u2014 leave a clear [placeholder] for the admin to fill instead.",
|
|
40052
|
+
"Follow the admin's instructions when given. A human admin reviews and edits your draft before",
|
|
40053
|
+
"sending it.",
|
|
40054
|
+
"",
|
|
40055
|
+
"Output ONLY the message body in markdown \u2014 no subject line, no preamble, no explanation, no code",
|
|
40056
|
+
"fences around the whole message.",
|
|
40057
|
+
"",
|
|
40058
|
+
"===== DOCUMENTATION =====",
|
|
40059
|
+
docs,
|
|
40060
|
+
"",
|
|
40061
|
+
"===== CONTEXT =====",
|
|
40062
|
+
context
|
|
40063
|
+
].join("\n");
|
|
40064
|
+
}
|
|
39893
40065
|
function imageExt(mime) {
|
|
39894
40066
|
switch (mime) {
|
|
39895
40067
|
case "image/png":
|
|
@@ -39904,9 +40076,8 @@ function imageExt(mime) {
|
|
|
39904
40076
|
return "png";
|
|
39905
40077
|
}
|
|
39906
40078
|
}
|
|
39907
|
-
function materializeImages2(
|
|
39908
|
-
const dir2 = join14(
|
|
39909
|
-
mkdirSync5(dir2, { recursive: true });
|
|
40079
|
+
function materializeImages2(images, question) {
|
|
40080
|
+
const dir2 = mkdtempSync2(join14(tmpdir3(), "4pm-help-images-"));
|
|
39910
40081
|
let rewritten = question;
|
|
39911
40082
|
images.forEach((img, i) => {
|
|
39912
40083
|
const file = join14(dir2, `image-${i + 1}.${imageExt(img.mime)}`);
|
|
@@ -39932,23 +40103,32 @@ function parseModeratedAnswer(text) {
|
|
|
39932
40103
|
}
|
|
39933
40104
|
return { body: text.trim() };
|
|
39934
40105
|
}
|
|
39935
|
-
function runClaudeOnce(cmd, profile, prompt, extraEnv, imageDir) {
|
|
39936
|
-
return new Promise((
|
|
40106
|
+
function runClaudeOnce(cmd, profile, prompt, extraEnv, imageDir, denyDirs = []) {
|
|
40107
|
+
return new Promise((resolve11, reject) => {
|
|
39937
40108
|
const isClaude = cmd.includes("claude");
|
|
39938
40109
|
const args = [
|
|
39939
40110
|
"-p",
|
|
39940
40111
|
...profile?.model ? ["--model", profile.model] : [],
|
|
39941
40112
|
...isClaude ? ["--output-format", "stream-json", "--verbose"] : [],
|
|
39942
|
-
//
|
|
39943
|
-
//
|
|
39944
|
-
|
|
40113
|
+
// No shell / edits / web for an untrusted question (ADR-0346). Reads need no grant inside the
|
|
40114
|
+
// (empty) working dir and any added dir; anywhere else they would need an approval a headless
|
|
40115
|
+
// run never gets.
|
|
40116
|
+
// Secret-path deny rules (ADR-0347) — reads are not confined to the working dir.
|
|
40117
|
+
...denySettingsArgs(cmd, [...denyDirs, profile?.dir]),
|
|
40118
|
+
...isClaude ? ["--disallowedTools", ...DENIED_TOOLS] : [],
|
|
40119
|
+
// When the question references pasted images (ADR-0273), add the materialized folder so the
|
|
40120
|
+
// agent can Read them (the question already carries their absolute paths) — scoped to it only.
|
|
40121
|
+
...isClaude && imageDir ? ["--add-dir", imageDir] : []
|
|
39945
40122
|
];
|
|
39946
|
-
const env3 = {
|
|
39947
|
-
|
|
39948
|
-
|
|
39949
|
-
|
|
40123
|
+
const env3 = agentEnv(extraEnv, profile ? { CLAUDE_CONFIG_DIR: profile.dir } : void 0);
|
|
40124
|
+
const cwd2 = mkdtempSync2(join14(tmpdir3(), "4pm-support-"));
|
|
40125
|
+
const cleanup = () => {
|
|
40126
|
+
try {
|
|
40127
|
+
rmSync5(cwd2, { recursive: true, force: true });
|
|
40128
|
+
} catch {
|
|
40129
|
+
}
|
|
39950
40130
|
};
|
|
39951
|
-
const child = spawn4(cmd, args, { stdio: ["pipe", "pipe", "pipe"], env: env3 });
|
|
40131
|
+
const child = spawn4(cmd, args, { stdio: ["pipe", "pipe", "pipe"], env: env3, cwd: cwd2 });
|
|
39952
40132
|
const parser = createAiStreamParser(cmd);
|
|
39953
40133
|
let out = "";
|
|
39954
40134
|
let err = "";
|
|
@@ -39961,12 +40141,14 @@ function runClaudeOnce(cmd, profile, prompt, extraEnv, imageDir) {
|
|
|
39961
40141
|
child.stderr.on("data", (d) => err += d.toString());
|
|
39962
40142
|
child.on("error", (e) => {
|
|
39963
40143
|
clearTimeout(timer);
|
|
40144
|
+
cleanup();
|
|
39964
40145
|
reject(e);
|
|
39965
40146
|
});
|
|
39966
40147
|
child.on("close", (code) => {
|
|
39967
40148
|
clearTimeout(timer);
|
|
40149
|
+
cleanup();
|
|
39968
40150
|
out += parser.flush();
|
|
39969
|
-
|
|
40151
|
+
resolve11({ code: code ?? -1, out, err, usage: parser.usage() });
|
|
39970
40152
|
});
|
|
39971
40153
|
child.stdin.write(prompt);
|
|
39972
40154
|
child.stdin.end();
|
|
@@ -39976,7 +40158,7 @@ async function runClaudeWithFailover(prompt, ai2, imageDir) {
|
|
|
39976
40158
|
const attempts = ai2.profiles.length > 0 ? ai2.profiles : [null];
|
|
39977
40159
|
let lastReason = "no attempt";
|
|
39978
40160
|
for (let i = 0; i < attempts.length; i++) {
|
|
39979
|
-
const { code, out, err, usage } = await runClaudeOnce(ai2.cmd, attempts[i], prompt, ai2.env, imageDir);
|
|
40161
|
+
const { code, out, err, usage } = await runClaudeOnce(ai2.cmd, attempts[i], prompt, ai2.env, imageDir, ai2.profiles.map((p2) => p2.dir));
|
|
39980
40162
|
if (code === 0 && out.trim()) return { text: out.trim(), usage };
|
|
39981
40163
|
const combined = err || out;
|
|
39982
40164
|
lastReason = `exited ${code}: ${combined.slice(0, 500)}`;
|
|
@@ -39988,7 +40170,7 @@ async function runSupportAnswer(req, ai2, profileDir2) {
|
|
|
39988
40170
|
let imageDir;
|
|
39989
40171
|
let question = req.question;
|
|
39990
40172
|
if (req.images?.length) {
|
|
39991
|
-
const m2 = materializeImages2(
|
|
40173
|
+
const m2 = materializeImages2(req.images, req.question);
|
|
39992
40174
|
imageDir = m2.dir;
|
|
39993
40175
|
question = m2.question;
|
|
39994
40176
|
}
|
|
@@ -39996,13 +40178,10 @@ async function runSupportAnswer(req, ai2, profileDir2) {
|
|
|
39996
40178
|
const dir2 = await ensureRepo(profileDir2, req.repo);
|
|
39997
40179
|
const docs = collectDocs(dir2);
|
|
39998
40180
|
if (!docs.trim()) return { body: "", error: "KB repo has no documentation" };
|
|
39999
|
-
const
|
|
40000
|
-
|
|
40001
|
-
ai2,
|
|
40002
|
-
imageDir
|
|
40003
|
-
);
|
|
40181
|
+
const prompt = req.task ? buildDraftPrompt(docs, question, req.task) : buildPrompt(docs, question, req.askerRole);
|
|
40182
|
+
const { text, usage } = await runClaudeWithFailover(prompt, ai2, imageDir);
|
|
40004
40183
|
if (!text) return { body: "", error: "empty answer" };
|
|
40005
|
-
const { body, moderation } = parseModeratedAnswer(text);
|
|
40184
|
+
const { body, moderation } = req.task ? { body: text.trim(), moderation: void 0 } : parseModeratedAnswer(text);
|
|
40006
40185
|
if (!body) return { body: "", error: "empty answer" };
|
|
40007
40186
|
const u2 = usage.tokens > 0 ? usage : { ...NO_USAGE, tokens: estimateTokens2(body) };
|
|
40008
40187
|
return {
|
|
@@ -40237,10 +40416,10 @@ async function crontabList() {
|
|
|
40237
40416
|
}
|
|
40238
40417
|
}
|
|
40239
40418
|
async function crontabSet(content) {
|
|
40240
|
-
return new Promise((
|
|
40419
|
+
return new Promise((resolve11) => {
|
|
40241
40420
|
const child = spawn5("crontab", ["-"], { stdio: ["pipe", "ignore", "ignore"] });
|
|
40242
|
-
child.on("error", () =>
|
|
40243
|
-
child.on("close", (code) =>
|
|
40421
|
+
child.on("error", () => resolve11(false));
|
|
40422
|
+
child.on("close", (code) => resolve11(code === 0));
|
|
40244
40423
|
child.stdin.end(content.endsWith("\n") ? content : content + "\n");
|
|
40245
40424
|
});
|
|
40246
40425
|
}
|
|
@@ -40409,14 +40588,14 @@ function detectContainerized() {
|
|
|
40409
40588
|
}
|
|
40410
40589
|
}
|
|
40411
40590
|
function canReach(host, port) {
|
|
40412
|
-
return new Promise((
|
|
40591
|
+
return new Promise((resolve11) => {
|
|
40413
40592
|
const socket = connect({ host, port });
|
|
40414
40593
|
let done = false;
|
|
40415
40594
|
const finish = (ok) => {
|
|
40416
40595
|
if (done) return;
|
|
40417
40596
|
done = true;
|
|
40418
40597
|
socket.destroy();
|
|
40419
|
-
|
|
40598
|
+
resolve11(ok);
|
|
40420
40599
|
};
|
|
40421
40600
|
socket.setTimeout(PROBE_TIMEOUT_MS);
|
|
40422
40601
|
socket.once("connect", () => finish(true));
|
|
@@ -40588,7 +40767,7 @@ async function runCommand(dispatch, emit, opts) {
|
|
|
40588
40767
|
});
|
|
40589
40768
|
child.stdout?.on("data", (data) => batcher.push(data.toString("utf8")));
|
|
40590
40769
|
child.stderr?.on("data", (data) => batcher.push(data.toString("utf8")));
|
|
40591
|
-
await new Promise((
|
|
40770
|
+
await new Promise((resolve11) => {
|
|
40592
40771
|
child.on("close", (exitCode) => {
|
|
40593
40772
|
clearTimers();
|
|
40594
40773
|
batcher.flush();
|
|
@@ -40601,7 +40780,7 @@ async function runCommand(dispatch, emit, opts) {
|
|
|
40601
40780
|
// server settles the command as failed and the web surfaces the note instead of spinning.
|
|
40602
40781
|
exitCode: timedOut ? 124 : exitCode ?? -1
|
|
40603
40782
|
});
|
|
40604
|
-
|
|
40783
|
+
resolve11();
|
|
40605
40784
|
});
|
|
40606
40785
|
child.on("error", (err) => {
|
|
40607
40786
|
clearTimers();
|
|
@@ -40613,7 +40792,7 @@ async function runCommand(dispatch, emit, opts) {
|
|
|
40613
40792
|
done: true,
|
|
40614
40793
|
exitCode: -1
|
|
40615
40794
|
});
|
|
40616
|
-
|
|
40795
|
+
resolve11();
|
|
40617
40796
|
});
|
|
40618
40797
|
});
|
|
40619
40798
|
}
|
|
@@ -40636,15 +40815,15 @@ function isOutOfCredits(text) {
|
|
|
40636
40815
|
);
|
|
40637
40816
|
}
|
|
40638
40817
|
function runAttempt(commandId, cmd, args, stdin, cwd2, env3, onChunk, timeoutMs) {
|
|
40639
|
-
return new Promise((
|
|
40818
|
+
return new Promise((resolve11) => {
|
|
40640
40819
|
runCommand(
|
|
40641
40820
|
{ commandId, cmd, args, path: cwd2, env: env3 },
|
|
40642
40821
|
(out) => {
|
|
40643
40822
|
if (out.chunk) onChunk(out.chunk);
|
|
40644
|
-
if (out.done)
|
|
40823
|
+
if (out.done) resolve11(out.exitCode ?? -1);
|
|
40645
40824
|
},
|
|
40646
40825
|
{ stdin, ...timeoutMs > 0 ? { timeoutMs } : {} }
|
|
40647
|
-
).catch(() =>
|
|
40826
|
+
).catch(() => resolve11(-1));
|
|
40648
40827
|
});
|
|
40649
40828
|
}
|
|
40650
40829
|
async function runAiFailover(plan, commandId, cwd2, handlers, timeoutMs = 0) {
|
|
@@ -41122,6 +41301,24 @@ function runSlashCommand(line, base) {
|
|
|
41122
41301
|
|
|
41123
41302
|
// src/core/memory-compact.ts
|
|
41124
41303
|
import { spawn as spawn7 } from "child_process";
|
|
41304
|
+
function parseJsonResult(out) {
|
|
41305
|
+
let parsed;
|
|
41306
|
+
try {
|
|
41307
|
+
parsed = JSON.parse(out.trim());
|
|
41308
|
+
} catch {
|
|
41309
|
+
return null;
|
|
41310
|
+
}
|
|
41311
|
+
if (parsed.is_error || typeof parsed.result !== "string") return null;
|
|
41312
|
+
const u2 = parsed.usage;
|
|
41313
|
+
const usage = u2 ? {
|
|
41314
|
+
input: u2.input_tokens ?? 0,
|
|
41315
|
+
output: u2.output_tokens ?? 0,
|
|
41316
|
+
cacheRead: u2.cache_read_input_tokens ?? 0,
|
|
41317
|
+
cacheCreation: u2.cache_creation_input_tokens ?? 0,
|
|
41318
|
+
tokens: (u2.input_tokens ?? 0) + (u2.output_tokens ?? 0) + (u2.cache_read_input_tokens ?? 0) + (u2.cache_creation_input_tokens ?? 0)
|
|
41319
|
+
} : null;
|
|
41320
|
+
return { text: parsed.result, usage };
|
|
41321
|
+
}
|
|
41125
41322
|
var COMPACT_TIMEOUT_MS = 6e4;
|
|
41126
41323
|
function buildPrompt2(input) {
|
|
41127
41324
|
return [
|
|
@@ -41141,9 +41338,21 @@ function buildPrompt2(input) {
|
|
|
41141
41338
|
input.answer
|
|
41142
41339
|
].join("\n");
|
|
41143
41340
|
}
|
|
41144
|
-
function runOnce(cmd, profile, prompt, cwd2, extraEnv) {
|
|
41145
|
-
return new Promise((
|
|
41146
|
-
const args = [
|
|
41341
|
+
function runOnce(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
|
|
41342
|
+
return new Promise((resolve11, reject) => {
|
|
41343
|
+
const args = [
|
|
41344
|
+
"-p",
|
|
41345
|
+
"--output-format",
|
|
41346
|
+
"json",
|
|
41347
|
+
...profile?.model ? ["--model", profile.model] : [],
|
|
41348
|
+
// Secret-path deny rules (ADR-0347) — before the variadic `--disallowedTools`.
|
|
41349
|
+
...denySettingsArgs(cmd, [...denyDirs, profile?.dir]),
|
|
41350
|
+
"--max-turns=1",
|
|
41351
|
+
"--disallowedTools",
|
|
41352
|
+
ONE_SHOT_DISALLOWED_CLAUDE_TOOLS,
|
|
41353
|
+
"--permission-mode",
|
|
41354
|
+
"default"
|
|
41355
|
+
];
|
|
41147
41356
|
const env3 = {
|
|
41148
41357
|
...process.env,
|
|
41149
41358
|
...extraEnv,
|
|
@@ -41165,7 +41374,7 @@ function runOnce(cmd, profile, prompt, cwd2, extraEnv) {
|
|
|
41165
41374
|
});
|
|
41166
41375
|
child.on("close", (code) => {
|
|
41167
41376
|
clearTimeout(timer);
|
|
41168
|
-
|
|
41377
|
+
resolve11({ code: code ?? -1, out, err });
|
|
41169
41378
|
});
|
|
41170
41379
|
child.stdin.write(prompt);
|
|
41171
41380
|
child.stdin.end();
|
|
@@ -41176,17 +41385,19 @@ async function runMemoryCompaction(ai2, cwd2, input) {
|
|
|
41176
41385
|
const attempts = ai2.profiles.length > 0 ? ai2.profiles : [null];
|
|
41177
41386
|
for (let i = 0; i < attempts.length; i++) {
|
|
41178
41387
|
try {
|
|
41179
|
-
const
|
|
41180
|
-
|
|
41388
|
+
const profile = attempts[i];
|
|
41389
|
+
const { code, out } = await runOnce(ai2.cmd, profile, prompt, cwd2, ai2.env, ai2.profiles.map((p2) => p2.dir));
|
|
41390
|
+
const parsed = code === 0 ? parseJsonResult(out) : null;
|
|
41391
|
+
if (parsed && parsed.text.trim()) {
|
|
41181
41392
|
reportToolResult(ai2.cmd, true);
|
|
41182
|
-
return
|
|
41393
|
+
return { memory: parsed.text.trim().slice(0, input.budgetChars), usage: parsed.usage, dir: profile?.dir ?? null };
|
|
41183
41394
|
}
|
|
41184
41395
|
if (i === attempts.length - 1) break;
|
|
41185
41396
|
} catch {
|
|
41186
41397
|
break;
|
|
41187
41398
|
}
|
|
41188
41399
|
}
|
|
41189
|
-
return "";
|
|
41400
|
+
return { memory: "", usage: null, dir: null };
|
|
41190
41401
|
}
|
|
41191
41402
|
|
|
41192
41403
|
// src/core/git-commit-identity.ts
|
|
@@ -41361,6 +41572,7 @@ async function runAiPrompt(ctx, prompt, commandId, origin, oneShot = false, imag
|
|
|
41361
41572
|
const guardedPrompt = ctx.restrictToFolder && ctx.physicRoot ? folderScopeGuard(ctx.physicRoot, aiBodyPrompt) : aiBodyPrompt;
|
|
41362
41573
|
const hint = unified ? { credential: startCred ?? workingCred } : { dir: startDir ?? workingDir };
|
|
41363
41574
|
const memCfg = resolveMemoryConfig(config);
|
|
41575
|
+
pruneResumableSessions(ctx, resolveResumePolicy(config), Date.now());
|
|
41364
41576
|
const firstAttempt = planAiRun(guardedPrompt, config, hint, /* @__PURE__ */ new Map(), oneShot, aiConfig, readOnly, bypass).attempts[0];
|
|
41365
41577
|
const resumeId = memCfg.enabled && firstAttempt?.key && firstAttempt.cmd === "claude" ? ctx.sessionIdByKey.get(firstAttempt.key) : void 0;
|
|
41366
41578
|
let effectivePrompt = guardedPrompt;
|
|
@@ -41508,6 +41720,8 @@ ${guardedPrompt}`;
|
|
|
41508
41720
|
outputTokens: result.usage.output,
|
|
41509
41721
|
cacheReadTokens: result.usage.cacheRead,
|
|
41510
41722
|
cacheCreationTokens: result.usage.cacheCreation,
|
|
41723
|
+
// Provider → the server's per-provider quota-token weights (ADR-0340).
|
|
41724
|
+
provider: aiProviderOf(result.workedCmd ?? plan.cmd),
|
|
41511
41725
|
// Auth mode for billing split (ADR-0192 §5): subscription (OAuth) vs api-key (API-billed).
|
|
41512
41726
|
authMode: resolveClaudeAuthMode(config)
|
|
41513
41727
|
});
|
|
@@ -41525,9 +41739,23 @@ ${guardedPrompt}`;
|
|
|
41525
41739
|
if (memCfg.enabled && result.exitCode === 0) {
|
|
41526
41740
|
if (result.workedKey && result.sessionId) {
|
|
41527
41741
|
ctx.sessionIdByKey.set(result.workedKey, result.sessionId);
|
|
41742
|
+
ctx.sessionMetaByKey.set(result.workedKey, { at: Date.now(), contextTokens: result.usage.contextTokens ?? 0 });
|
|
41528
41743
|
}
|
|
41529
41744
|
void updateSharedMemory(ctx, config, prompt, answerText, memCfg.budgetChars).catch(() => {
|
|
41530
41745
|
});
|
|
41746
|
+
} else if (result.exitCode !== 0) {
|
|
41747
|
+
ctx.sessionIdByKey.clear();
|
|
41748
|
+
ctx.sessionMetaByKey.clear();
|
|
41749
|
+
}
|
|
41750
|
+
}
|
|
41751
|
+
function pruneResumableSessions(ctx, policy, now) {
|
|
41752
|
+
for (const key of [...ctx.sessionIdByKey.keys()]) {
|
|
41753
|
+
const meta = ctx.sessionMetaByKey.get(key);
|
|
41754
|
+
const stale = !meta || now - meta.at > policy.maxIdleMs || meta.contextTokens > policy.maxContextTokens;
|
|
41755
|
+
if (stale) {
|
|
41756
|
+
ctx.sessionIdByKey.delete(key);
|
|
41757
|
+
ctx.sessionMetaByKey.delete(key);
|
|
41758
|
+
}
|
|
41531
41759
|
}
|
|
41532
41760
|
}
|
|
41533
41761
|
async function runWebSlashCommand(ctx, line, commandId) {
|
|
@@ -41635,11 +41863,29 @@ async function updateSharedMemory(ctx, config, userPrompt, answer, budgetChars)
|
|
|
41635
41863
|
if (!answer.trim()) return;
|
|
41636
41864
|
const profiles = resolveClaudeProfiles(config, ctx.physicRoot);
|
|
41637
41865
|
const cwd2 = ctx.physicRoot ?? process.cwd();
|
|
41638
|
-
const newMemory = await runMemoryCompaction(
|
|
41866
|
+
const { memory: newMemory, usage, dir: dir2 } = await runMemoryCompaction(
|
|
41639
41867
|
{ cmd: "claude", profiles, env: config.aiEnv },
|
|
41640
41868
|
cwd2,
|
|
41641
41869
|
{ oldMemory: ctx.aiMemory, prompt: userPrompt, answer, budgetChars }
|
|
41642
41870
|
);
|
|
41871
|
+
if (usage && usage.tokens > 0) {
|
|
41872
|
+
ctx.reportUsage(
|
|
41873
|
+
[
|
|
41874
|
+
{
|
|
41875
|
+
metric: UsageMetric.AI_TOKENS,
|
|
41876
|
+
amount: usage.tokens,
|
|
41877
|
+
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
41878
|
+
inputTokens: usage.input,
|
|
41879
|
+
outputTokens: usage.output,
|
|
41880
|
+
cacheReadTokens: usage.cacheRead,
|
|
41881
|
+
cacheCreationTokens: usage.cacheCreation,
|
|
41882
|
+
provider: "claude",
|
|
41883
|
+
authMode: resolveClaudeAuthMode(config)
|
|
41884
|
+
}
|
|
41885
|
+
],
|
|
41886
|
+
dir2 ? profileDisplayLabel(dir2) : null
|
|
41887
|
+
);
|
|
41888
|
+
}
|
|
41643
41889
|
if (!newMemory) return;
|
|
41644
41890
|
ctx.aiMemory = newMemory;
|
|
41645
41891
|
ctx.send(WsChannels.MEMORY_UPDATE, { text: newMemory });
|
|
@@ -41647,6 +41893,7 @@ async function updateSharedMemory(ctx, config, userPrompt, answer, budgetChars)
|
|
|
41647
41893
|
function resetMemorySession(ctx) {
|
|
41648
41894
|
ctx.aiMemory = "";
|
|
41649
41895
|
ctx.sessionIdByKey.clear();
|
|
41896
|
+
ctx.sessionMetaByKey.clear();
|
|
41650
41897
|
ctx.send(WsChannels.MEMORY_UPDATE, { text: "" });
|
|
41651
41898
|
}
|
|
41652
41899
|
function profileUnderSessionPressure(ctx, config, cmd, workingDir) {
|
|
@@ -41719,7 +41966,7 @@ function rejectByReview(ctx, commandId, origin, prompt, reasons) {
|
|
|
41719
41966
|
}
|
|
41720
41967
|
|
|
41721
41968
|
// src/core/autonomous-cycle.ts
|
|
41722
|
-
import { randomUUID
|
|
41969
|
+
import { randomUUID } from "crypto";
|
|
41723
41970
|
import { appendFile as appendFile2, mkdir as mkdir3, readFile as readFile5, readdir as readdir2, rm as rm2, stat } from "fs/promises";
|
|
41724
41971
|
import { join as join20 } from "path";
|
|
41725
41972
|
function buildAutonomousCyclePrompt() {
|
|
@@ -41859,7 +42106,7 @@ async function runAutonomousCycle(ctx) {
|
|
|
41859
42106
|
hist.ticks = { day: today, count: todayTickCount(hist, today) + 1 };
|
|
41860
42107
|
await writeHistories(root, hist);
|
|
41861
42108
|
await log(`[run] cycle (tick ${hist.ticks.count}/${today})`);
|
|
41862
|
-
const commandId =
|
|
42109
|
+
const commandId = randomUUID();
|
|
41863
42110
|
const override = cfg.model ? { model: cfg.model } : void 0;
|
|
41864
42111
|
try {
|
|
41865
42112
|
await runAiPrompt(ctx, buildAutonomousCyclePrompt(), commandId, "local", false, void 0, override, false, true);
|
|
@@ -41963,11 +42210,11 @@ async function allAiProfilesExhausted(ctx, cfg) {
|
|
|
41963
42210
|
|
|
41964
42211
|
// src/core/fs-browse.ts
|
|
41965
42212
|
import { readdir as readdir3 } from "fs/promises";
|
|
41966
|
-
import { resolve as
|
|
42213
|
+
import { resolve as resolve3, sep as sep2 } from "path";
|
|
41967
42214
|
async function listDir(path, root) {
|
|
41968
42215
|
if (!root) return { path: "", entries: [] };
|
|
41969
|
-
const base =
|
|
41970
|
-
const requested =
|
|
42216
|
+
const base = resolve3(root);
|
|
42217
|
+
const requested = resolve3(base, path && path.trim() ? path : ".");
|
|
41971
42218
|
const target = requested === base || requested.startsWith(base + sep2) ? requested : base;
|
|
41972
42219
|
try {
|
|
41973
42220
|
const dirents = await readdir3(target, { withFileTypes: true });
|
|
@@ -41986,12 +42233,12 @@ async function listDir(path, root) {
|
|
|
41986
42233
|
|
|
41987
42234
|
// src/core/fs-read.ts
|
|
41988
42235
|
import { readFile as readFile6 } from "fs/promises";
|
|
41989
|
-
import { resolve as
|
|
42236
|
+
import { resolve as resolve4, sep as sep3 } from "path";
|
|
41990
42237
|
var MAX_BYTES = 256 * 1024;
|
|
41991
42238
|
function resolveInRoot(root, relPath) {
|
|
41992
42239
|
if (!root) return null;
|
|
41993
|
-
const base =
|
|
41994
|
-
const target =
|
|
42240
|
+
const base = resolve4(root);
|
|
42241
|
+
const target = resolve4(base, relPath || ".");
|
|
41995
42242
|
return target === base || target.startsWith(base + sep3) ? target : null;
|
|
41996
42243
|
}
|
|
41997
42244
|
async function readWorkerFile(root, path) {
|
|
@@ -42011,12 +42258,12 @@ async function readWorkerFile(root, path) {
|
|
|
42011
42258
|
|
|
42012
42259
|
// src/core/fs-write.ts
|
|
42013
42260
|
import { mkdir as mkdir4, writeFile as writeFile5 } from "fs/promises";
|
|
42014
|
-
import { dirname as dirname4, resolve as
|
|
42261
|
+
import { dirname as dirname4, resolve as resolve5, sep as sep4 } from "path";
|
|
42015
42262
|
var MAX_BYTES2 = 2 * 1024 * 1024;
|
|
42016
42263
|
function resolveInRoot2(root, relPath) {
|
|
42017
42264
|
if (!root) return null;
|
|
42018
|
-
const base =
|
|
42019
|
-
const target =
|
|
42265
|
+
const base = resolve5(root);
|
|
42266
|
+
const target = resolve5(base, relPath || ".");
|
|
42020
42267
|
return target === base || target.startsWith(base + sep4) ? target : null;
|
|
42021
42268
|
}
|
|
42022
42269
|
async function writeWorkerFile(root, relPath, content) {
|
|
@@ -42039,11 +42286,11 @@ async function writeWorkerFile(root, relPath, content) {
|
|
|
42039
42286
|
|
|
42040
42287
|
// src/core/fs-mutate.ts
|
|
42041
42288
|
import { mkdir as mkdir5, writeFile as writeFile6, rename, rm as rm3, stat as stat2 } from "fs/promises";
|
|
42042
|
-
import { basename as basename3, dirname as dirname5, resolve as
|
|
42289
|
+
import { basename as basename3, dirname as dirname5, resolve as resolve6, sep as sep5 } from "path";
|
|
42043
42290
|
var MAX_BYTES3 = 2 * 1024 * 1024;
|
|
42044
42291
|
function resolveInRoot3(root, rel) {
|
|
42045
|
-
const base =
|
|
42046
|
-
const target =
|
|
42292
|
+
const base = resolve6(root);
|
|
42293
|
+
const target = resolve6(base, rel || ".");
|
|
42047
42294
|
return target === base || target.startsWith(base + sep5) ? target : null;
|
|
42048
42295
|
}
|
|
42049
42296
|
async function isDir(p2) {
|
|
@@ -42068,13 +42315,13 @@ async function mutateFs(root, req) {
|
|
|
42068
42315
|
switch (req.op) {
|
|
42069
42316
|
case "mkdir": {
|
|
42070
42317
|
const target = resolveInRoot3(root, req.path ?? "");
|
|
42071
|
-
if (!target || target ===
|
|
42318
|
+
if (!target || target === resolve6(root)) return escaped(req.path ?? "");
|
|
42072
42319
|
await mkdir5(target, { recursive: true });
|
|
42073
42320
|
return { ok: true, path: target };
|
|
42074
42321
|
}
|
|
42075
42322
|
case "create": {
|
|
42076
42323
|
const target = resolveInRoot3(root, req.path ?? "");
|
|
42077
|
-
if (!target || target ===
|
|
42324
|
+
if (!target || target === resolve6(root)) return escaped(req.path ?? "");
|
|
42078
42325
|
if (await exists(target)) return { ok: false, path: target, error: "already exists" };
|
|
42079
42326
|
const content = req.content ?? "";
|
|
42080
42327
|
if (Buffer.byteLength(content, "utf8") > MAX_BYTES3) {
|
|
@@ -42088,9 +42335,9 @@ async function mutateFs(root, req) {
|
|
|
42088
42335
|
const from = resolveInRoot3(root, req.from ?? "");
|
|
42089
42336
|
let to2 = resolveInRoot3(root, req.to ?? "");
|
|
42090
42337
|
if (!from || !to2) return escaped(req.from ?? req.to ?? "");
|
|
42091
|
-
if (from ===
|
|
42092
|
-
if (await isDir(to2)) to2 =
|
|
42093
|
-
if (to2 !==
|
|
42338
|
+
if (from === resolve6(root)) return { ok: false, path: from, error: "cannot move the project root" };
|
|
42339
|
+
if (await isDir(to2)) to2 = resolve6(to2, basename3(from));
|
|
42340
|
+
if (to2 !== resolve6(root) && !to2.startsWith(resolve6(root) + sep5)) return escaped(req.to ?? "");
|
|
42094
42341
|
if (await exists(to2)) return { ok: false, path: to2, error: "destination already exists" };
|
|
42095
42342
|
await mkdir5(dirname5(to2), { recursive: true });
|
|
42096
42343
|
await rename(from, to2);
|
|
@@ -42099,7 +42346,7 @@ async function mutateFs(root, req) {
|
|
|
42099
42346
|
case "delete": {
|
|
42100
42347
|
const target = resolveInRoot3(root, req.path ?? "");
|
|
42101
42348
|
if (!target) return escaped(req.path ?? "");
|
|
42102
|
-
if (target ===
|
|
42349
|
+
if (target === resolve6(root)) return { ok: false, path: target, error: "cannot delete the project root" };
|
|
42103
42350
|
await rm3(target, { recursive: true, force: true });
|
|
42104
42351
|
return { ok: true, path: target };
|
|
42105
42352
|
}
|
|
@@ -42113,11 +42360,11 @@ async function mutateFs(root, req) {
|
|
|
42113
42360
|
|
|
42114
42361
|
// src/core/fs-transfer.ts
|
|
42115
42362
|
import { mkdir as mkdir6, readFile as readFile7, stat as stat3, writeFile as writeFile7 } from "fs/promises";
|
|
42116
|
-
import { basename as basename4, dirname as dirname6, extname, resolve as
|
|
42363
|
+
import { basename as basename4, dirname as dirname6, extname, resolve as resolve7, sep as sep6 } from "path";
|
|
42117
42364
|
function resolveInRoot4(root, relPath) {
|
|
42118
42365
|
if (!root) return null;
|
|
42119
|
-
const base =
|
|
42120
|
-
const target =
|
|
42366
|
+
const base = resolve7(root);
|
|
42367
|
+
const target = resolve7(base, relPath || ".");
|
|
42121
42368
|
return target === base || target.startsWith(base + sep6) ? target : null;
|
|
42122
42369
|
}
|
|
42123
42370
|
var MIME_BY_EXT = {
|
|
@@ -42233,13 +42480,13 @@ function handleFsChannels(ctx, message, payload) {
|
|
|
42233
42480
|
// src/core/git-diff.ts
|
|
42234
42481
|
import { readFile as readFile8 } from "fs/promises";
|
|
42235
42482
|
import { execFile as execFile6 } from "child_process";
|
|
42236
|
-
import { basename as basename5, dirname as dirname7, resolve as
|
|
42483
|
+
import { basename as basename5, dirname as dirname7, resolve as resolve8, sep as sep7 } from "path";
|
|
42237
42484
|
import { promisify as promisify6 } from "util";
|
|
42238
42485
|
var run6 = promisify6(execFile6);
|
|
42239
42486
|
function resolveInRoot5(root, relPath) {
|
|
42240
42487
|
if (!root) return null;
|
|
42241
|
-
const base =
|
|
42242
|
-
const target =
|
|
42488
|
+
const base = resolve8(root);
|
|
42489
|
+
const target = resolve8(base, relPath || ".");
|
|
42243
42490
|
return target === base || target.startsWith(base + sep7) ? target : null;
|
|
42244
42491
|
}
|
|
42245
42492
|
function looksBinary(buf) {
|
|
@@ -42393,15 +42640,15 @@ async function manageSshKey(op, profileDir2) {
|
|
|
42393
42640
|
|
|
42394
42641
|
// src/core/git-history.ts
|
|
42395
42642
|
import { execFile as execFile9 } from "child_process";
|
|
42396
|
-
import { resolve as
|
|
42643
|
+
import { resolve as resolve9, sep as sep8 } from "path";
|
|
42397
42644
|
import { promisify as promisify9 } from "util";
|
|
42398
42645
|
var run9 = promisify9(execFile9);
|
|
42399
42646
|
var FS = "";
|
|
42400
42647
|
var RS = "";
|
|
42401
42648
|
function resolveRepo(root, repo) {
|
|
42402
42649
|
if (!root) return null;
|
|
42403
|
-
const base =
|
|
42404
|
-
const requested =
|
|
42650
|
+
const base = resolve9(root);
|
|
42651
|
+
const requested = resolve9(base, repo && repo.trim() ? repo : ".");
|
|
42405
42652
|
return requested === base || requested.startsWith(base + sep8) ? requested : base;
|
|
42406
42653
|
}
|
|
42407
42654
|
async function git(cwd2, args) {
|
|
@@ -42418,7 +42665,7 @@ async function git(cwd2, args) {
|
|
|
42418
42665
|
}
|
|
42419
42666
|
async function gitRepos(root) {
|
|
42420
42667
|
if (!root) return { repos: [] };
|
|
42421
|
-
const base =
|
|
42668
|
+
const base = resolve9(root);
|
|
42422
42669
|
const top = (await git(base, ["rev-parse", "--show-toplevel"])).trim();
|
|
42423
42670
|
if (!top || !(top === base || top.startsWith(base + sep8))) return { repos: [] };
|
|
42424
42671
|
const repos = [];
|
|
@@ -42430,7 +42677,7 @@ async function gitRepos(root) {
|
|
|
42430
42677
|
const m2 = line.trim().match(/^[-+U ]?[0-9a-f]{7,40}\s+(\S+)/);
|
|
42431
42678
|
const subPath = m2?.[1];
|
|
42432
42679
|
if (!subPath) continue;
|
|
42433
|
-
const abs =
|
|
42680
|
+
const abs = resolve9(base, subPath);
|
|
42434
42681
|
if (!(abs === base || abs.startsWith(base + sep8))) continue;
|
|
42435
42682
|
const remote = (await git(abs, ["remote", "get-url", "origin"])).trim();
|
|
42436
42683
|
const branch = (await git(abs, ["rev-parse", "--abbrev-ref", "HEAD"])).trim();
|
|
@@ -42752,9 +42999,9 @@ Extra focus from the author: ${req.prompt}` : "";
|
|
|
42752
42999
|
"Output ONLY the Markdown article, no preamble or code fences around the whole thing." + focus
|
|
42753
43000
|
].join("\n");
|
|
42754
43001
|
}
|
|
42755
|
-
function runOnce2(cmd, profile, prompt, cwd2, extraEnv) {
|
|
42756
|
-
return new Promise((
|
|
42757
|
-
const args = ["-p", ...profile?.model ? ["--model", profile.model] : []];
|
|
43002
|
+
function runOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
|
|
43003
|
+
return new Promise((resolve11, reject) => {
|
|
43004
|
+
const args = ["-p", ...profile?.model ? ["--model", profile.model] : [], ...denySettingsArgs(cmd, [...denyDirs, profile?.dir])];
|
|
42758
43005
|
const env3 = {
|
|
42759
43006
|
...process.env,
|
|
42760
43007
|
...extraEnv,
|
|
@@ -42776,7 +43023,7 @@ function runOnce2(cmd, profile, prompt, cwd2, extraEnv) {
|
|
|
42776
43023
|
});
|
|
42777
43024
|
child.on("close", (code) => {
|
|
42778
43025
|
clearTimeout(timer);
|
|
42779
|
-
|
|
43026
|
+
resolve11({ code: code ?? -1, out, err });
|
|
42780
43027
|
});
|
|
42781
43028
|
child.stdin.write(prompt);
|
|
42782
43029
|
child.stdin.end();
|
|
@@ -42788,7 +43035,7 @@ async function runKnowledgeCompose(req, ai2, physicRoot) {
|
|
|
42788
43035
|
let lastReason = "no attempt";
|
|
42789
43036
|
for (let i = 0; i < attempts.length; i++) {
|
|
42790
43037
|
try {
|
|
42791
|
-
const { code, out, err } = await runOnce2(ai2.cmd, attempts[i], prompt, physicRoot, ai2.env);
|
|
43038
|
+
const { code, out, err } = await runOnce2(ai2.cmd, attempts[i], prompt, physicRoot, ai2.env, ai2.profiles.map((p2) => p2.dir));
|
|
42792
43039
|
if (code === 0 && out.trim()) {
|
|
42793
43040
|
reportToolResult(ai2.cmd, true);
|
|
42794
43041
|
return { bodyMarkdown: out.trim() };
|
|
@@ -42808,17 +43055,14 @@ async function runKnowledgeCompose(req, ai2, physicRoot) {
|
|
|
42808
43055
|
// src/core/faq-compose.ts
|
|
42809
43056
|
import { spawn as spawn9, execFile as execFile10 } from "child_process";
|
|
42810
43057
|
import { existsSync as existsSync14, mkdirSync as mkdirSync7, rmSync as rmSync7 } from "fs";
|
|
42811
|
-
import { randomUUID as
|
|
43058
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
42812
43059
|
import { join as join24 } from "path";
|
|
42813
|
-
import { tmpdir as
|
|
43060
|
+
import { tmpdir as tmpdir4 } from "os";
|
|
42814
43061
|
import { promisify as promisify10 } from "util";
|
|
42815
43062
|
var execFileAsync2 = promisify10(execFile10);
|
|
42816
43063
|
var NO_USAGE2 = { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 };
|
|
42817
43064
|
var COMPOSE_TIMEOUT_MS2 = 10 * 60 * 1e3;
|
|
42818
|
-
|
|
42819
|
-
if (url.startsWith("https://")) return url.replace("https://", `https://x-access-token:${token}@`);
|
|
42820
|
-
return url;
|
|
42821
|
-
}
|
|
43065
|
+
var AGENT_DENIED_TOOLS = ["Bash", "WebFetch", "WebSearch", "NotebookEdit", "Task"];
|
|
42822
43066
|
function buildPrompt4(tickets, customPrompt) {
|
|
42823
43067
|
const transcript = tickets.map((t2) => {
|
|
42824
43068
|
const thread = t2.messages.map((m2) => ` [${m2.author}] ${m2.body}`).join("\n");
|
|
@@ -42879,22 +43123,22 @@ function parsePerTicket(text) {
|
|
|
42879
43123
|
}
|
|
42880
43124
|
return [];
|
|
42881
43125
|
}
|
|
42882
|
-
function runClaudeOnce2(cmd, profile, prompt, cwd2, extraEnv) {
|
|
42883
|
-
return new Promise((
|
|
43126
|
+
function runClaudeOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
|
|
43127
|
+
return new Promise((resolve11, reject) => {
|
|
42884
43128
|
const isClaude = cmd.includes("claude");
|
|
42885
43129
|
const args = [
|
|
42886
43130
|
"-p",
|
|
42887
43131
|
...profile?.model ? ["--model", profile.model] : [],
|
|
42888
43132
|
...isClaude ? ["--output-format", "stream-json", "--verbose"] : [],
|
|
42889
|
-
//
|
|
42890
|
-
|
|
42891
|
-
...cmd
|
|
43133
|
+
// Secret-path deny rules (ADR-0347): reads are not confined to the clone, so the credential
|
|
43134
|
+
// dirs / cli profile / ssh / gh / git credentials are explicitly denied.
|
|
43135
|
+
...denySettingsArgs(cmd, [...denyDirs, profile?.dir]),
|
|
43136
|
+
// Edit-only headless agent (ADR-0346): file edits are auto-accepted, everything else (Bash, web)
|
|
43137
|
+
// is denied, since the prompt carries untrusted ticket text. git/PR are done by this module.
|
|
43138
|
+
...isClaude ? ["--permission-mode", "acceptEdits", "--disallowedTools", ...AGENT_DENIED_TOOLS] : [],
|
|
43139
|
+
...cmd.includes("codex") ? ["--sandbox", "workspace-write"] : []
|
|
42892
43140
|
];
|
|
42893
|
-
const env3 = {
|
|
42894
|
-
...process.env,
|
|
42895
|
-
...extraEnv,
|
|
42896
|
-
...profile ? { CLAUDE_CONFIG_DIR: profile.dir } : {}
|
|
42897
|
-
};
|
|
43141
|
+
const env3 = agentEnv(extraEnv, profile ? { CLAUDE_CONFIG_DIR: profile.dir } : void 0);
|
|
42898
43142
|
const child = spawn9(cmd, args, { stdio: ["pipe", "pipe", "pipe"], cwd: cwd2, env: env3 });
|
|
42899
43143
|
const parser = createAiStreamParser(cmd);
|
|
42900
43144
|
let out = "";
|
|
@@ -42913,7 +43157,7 @@ function runClaudeOnce2(cmd, profile, prompt, cwd2, extraEnv) {
|
|
|
42913
43157
|
child.on("close", (code) => {
|
|
42914
43158
|
clearTimeout(timer);
|
|
42915
43159
|
out += parser.flush();
|
|
42916
|
-
|
|
43160
|
+
resolve11({ code: code ?? -1, out, err, usage: parser.usage() });
|
|
42917
43161
|
});
|
|
42918
43162
|
child.stdin.write(prompt);
|
|
42919
43163
|
child.stdin.end();
|
|
@@ -42923,7 +43167,7 @@ async function runClaudeWithFailover2(prompt, cwd2, ai2) {
|
|
|
42923
43167
|
const attempts = ai2.profiles.length > 0 ? ai2.profiles : [null];
|
|
42924
43168
|
let lastReason = "no attempt";
|
|
42925
43169
|
for (let i = 0; i < attempts.length; i++) {
|
|
42926
|
-
const { code, out, err, usage } = await runClaudeOnce2(ai2.cmd, attempts[i], prompt, cwd2, ai2.env);
|
|
43170
|
+
const { code, out, err, usage } = await runClaudeOnce2(ai2.cmd, attempts[i], prompt, cwd2, ai2.env, ai2.profiles.map((p2) => p2.dir));
|
|
42927
43171
|
if (code === 0) return { usage, text: out.trim() };
|
|
42928
43172
|
lastReason = `exited ${code}: ${(err || out).slice(0, 500)}`;
|
|
42929
43173
|
if (i === attempts.length - 1) break;
|
|
@@ -42933,12 +43177,12 @@ async function runClaudeWithFailover2(prompt, cwd2, ai2) {
|
|
|
42933
43177
|
var MAX_OUTPUT_CHARS = 8e3;
|
|
42934
43178
|
async function runFaqCompose(req, ai2) {
|
|
42935
43179
|
if (!req.repo.token) return { prUrl: "", error: "no write token" };
|
|
42936
|
-
const workDir = join24(
|
|
43180
|
+
const workDir = join24(tmpdir4(), "4pm-faq-sync", randomUUID2());
|
|
42937
43181
|
const gitEnv = { ...process.env, GIT_TERMINAL_PROMPT: "0" };
|
|
42938
|
-
const
|
|
43182
|
+
const auth = gitAuthArgs("x-access-token", req.repo.token);
|
|
42939
43183
|
try {
|
|
42940
43184
|
mkdirSync7(workDir, { recursive: true });
|
|
42941
|
-
await execFileAsync2("git", ["clone", "--depth", "1", "--branch", req.repo.branch,
|
|
43185
|
+
await execFileAsync2("git", [...auth, "clone", "--depth", "1", "--branch", req.repo.branch, req.repo.url, workDir], { env: gitEnv });
|
|
42942
43186
|
await execFileAsync2("git", ["-C", workDir, "config", "user.name", "4PM FAQ Bot"], { env: gitEnv });
|
|
42943
43187
|
await execFileAsync2("git", ["-C", workDir, "config", "user.email", "faq-bot@4pm.app"], { env: gitEnv });
|
|
42944
43188
|
await execFileAsync2("git", ["-C", workDir, "checkout", "-b", req.headBranch], { env: gitEnv });
|
|
@@ -42951,7 +43195,7 @@ async function runFaqCompose(req, ai2) {
|
|
|
42951
43195
|
return { prUrl: "", output, perTicket, tokens: usage.tokens || estimateTokens2(text), finishedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
42952
43196
|
}
|
|
42953
43197
|
await execFileAsync2("git", ["-C", workDir, "commit", "-m", "docs(faq): synthesize from support tickets (4PM)"], { env: gitEnv });
|
|
42954
|
-
await execFileAsync2("git", ["-C", workDir, "push", "-u", "origin", req.headBranch], { env: gitEnv });
|
|
43198
|
+
await execFileAsync2("git", [...auth, "-C", workDir, "push", "-u", "origin", req.headBranch], { env: gitEnv });
|
|
42955
43199
|
let prUrl = "";
|
|
42956
43200
|
try {
|
|
42957
43201
|
const pr2 = await execFileAsync2(
|
|
@@ -43533,7 +43777,7 @@ async function removePackage(root, slug) {
|
|
|
43533
43777
|
|
|
43534
43778
|
// src/core/graph.ts
|
|
43535
43779
|
import { readFile as readFile14, readdir as readdir6 } from "fs/promises";
|
|
43536
|
-
import { join as join29, relative as relative2, dirname as dirname10, resolve as
|
|
43780
|
+
import { join as join29, relative as relative2, dirname as dirname10, resolve as resolve10, extname as extname2, basename as basename6 } from "path";
|
|
43537
43781
|
var MAX_FILES = 2e3;
|
|
43538
43782
|
var MAX_NODES = 3e3;
|
|
43539
43783
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "out", "coverage", ".turbo"]);
|
|
@@ -43588,7 +43832,7 @@ async function buildDocs(root) {
|
|
|
43588
43832
|
for (const m2 of text.matchAll(/\[[^\]]*\]\(([^)]+)\)/g)) {
|
|
43589
43833
|
const href = (m2[1] ?? "").split("#")[0].trim();
|
|
43590
43834
|
if (!href || !href.endsWith(".md") || href.startsWith("http")) continue;
|
|
43591
|
-
const target = relative2(root,
|
|
43835
|
+
const target = relative2(root, resolve10(dirname10(f2), href)).split("\\").join("/");
|
|
43592
43836
|
if (ids.has(target) && target !== rel) {
|
|
43593
43837
|
const key = `${rel}->${target}`;
|
|
43594
43838
|
if (!seen.has(key)) {
|
|
@@ -44220,6 +44464,7 @@ var WsClient = class {
|
|
|
44220
44464
|
*/
|
|
44221
44465
|
aiMemory = "";
|
|
44222
44466
|
sessionIdByKey = /* @__PURE__ */ new Map();
|
|
44467
|
+
sessionMetaByKey = /* @__PURE__ */ new Map();
|
|
44223
44468
|
stopped = false;
|
|
44224
44469
|
/** Resolver to wake the backoff sleep early (set while waiting) — for /reconnect. */
|
|
44225
44470
|
wakeReconnect = null;
|
|
@@ -44313,6 +44558,7 @@ var WsClient = class {
|
|
|
44313
44558
|
serverUrl: this.context.credential.serverUrl,
|
|
44314
44559
|
startedAtMs: this.startedAtMs,
|
|
44315
44560
|
sessionIdByKey: this.sessionIdByKey,
|
|
44561
|
+
sessionMetaByKey: this.sessionMetaByKey,
|
|
44316
44562
|
get machineUsername() {
|
|
44317
44563
|
return self.machineUsername;
|
|
44318
44564
|
},
|
|
@@ -44425,14 +44671,14 @@ var WsClient = class {
|
|
|
44425
44671
|
*/
|
|
44426
44672
|
awaitConnected(timeoutMs) {
|
|
44427
44673
|
if (this.sessionKey && this.bus.status === "connected") return Promise.resolve(true);
|
|
44428
|
-
return new Promise((
|
|
44674
|
+
return new Promise((resolve11) => {
|
|
44429
44675
|
const waiter = () => {
|
|
44430
44676
|
clearTimeout(timer);
|
|
44431
|
-
|
|
44677
|
+
resolve11(true);
|
|
44432
44678
|
};
|
|
44433
44679
|
const timer = setTimeout(() => {
|
|
44434
44680
|
this.connectWaiters = this.connectWaiters.filter((w2) => w2 !== waiter);
|
|
44435
|
-
|
|
44681
|
+
resolve11(false);
|
|
44436
44682
|
}, timeoutMs);
|
|
44437
44683
|
this.connectWaiters.push(waiter);
|
|
44438
44684
|
});
|
|
@@ -44459,16 +44705,16 @@ var WsClient = class {
|
|
|
44459
44705
|
this.forceReconnect = false;
|
|
44460
44706
|
return Promise.resolve();
|
|
44461
44707
|
}
|
|
44462
|
-
return new Promise((
|
|
44708
|
+
return new Promise((resolve11) => {
|
|
44463
44709
|
const timer = setTimeout(() => {
|
|
44464
44710
|
this.wakeReconnect = null;
|
|
44465
|
-
|
|
44711
|
+
resolve11();
|
|
44466
44712
|
}, ms2);
|
|
44467
44713
|
this.wakeReconnect = () => {
|
|
44468
44714
|
clearTimeout(timer);
|
|
44469
44715
|
this.wakeReconnect = null;
|
|
44470
44716
|
this.forceReconnect = false;
|
|
44471
|
-
|
|
44717
|
+
resolve11();
|
|
44472
44718
|
};
|
|
44473
44719
|
});
|
|
44474
44720
|
}
|
|
@@ -44522,7 +44768,7 @@ var WsClient = class {
|
|
|
44522
44768
|
* One WS session: token → connect → handshake → handle messages until close.
|
|
44523
44769
|
*/
|
|
44524
44770
|
connectOnce() {
|
|
44525
|
-
return new Promise((
|
|
44771
|
+
return new Promise((resolve11, reject) => {
|
|
44526
44772
|
void (async () => {
|
|
44527
44773
|
this.sessionEstablished = false;
|
|
44528
44774
|
this.bus.setStatus("connecting");
|
|
@@ -44618,7 +44864,7 @@ var WsClient = class {
|
|
|
44618
44864
|
if (code === CLOSE_DRAINING) {
|
|
44619
44865
|
this.bus.log(t("error.serverDraining"));
|
|
44620
44866
|
}
|
|
44621
|
-
|
|
44867
|
+
resolve11();
|
|
44622
44868
|
});
|
|
44623
44869
|
socket.on("error", (err) => {
|
|
44624
44870
|
this.clearHeartbeat();
|
|
@@ -44730,7 +44976,7 @@ var WsClient = class {
|
|
|
44730
44976
|
}
|
|
44731
44977
|
const { payload, nonce } = encryptPayload(this.sessionKey, data);
|
|
44732
44978
|
const envelope = {
|
|
44733
|
-
id:
|
|
44979
|
+
id: randomUUID3(),
|
|
44734
44980
|
replyTo,
|
|
44735
44981
|
channel,
|
|
44736
44982
|
wsToken: this.wsToken,
|
|
@@ -44744,14 +44990,14 @@ var WsClient = class {
|
|
|
44744
44990
|
* Used for quota.check before spawning an AI cli (ADR-0020).
|
|
44745
44991
|
*/
|
|
44746
44992
|
request(channel, data) {
|
|
44747
|
-
return new Promise((
|
|
44993
|
+
return new Promise((resolve11, reject) => {
|
|
44748
44994
|
if (!this.socket || !this.sessionKey) {
|
|
44749
44995
|
reject(new Error("Not connected to the server."));
|
|
44750
44996
|
return;
|
|
44751
44997
|
}
|
|
44752
44998
|
const { payload, nonce } = encryptPayload(this.sessionKey, data);
|
|
44753
44999
|
const envelope = {
|
|
44754
|
-
id:
|
|
45000
|
+
id: randomUUID3(),
|
|
44755
45001
|
replyTo: null,
|
|
44756
45002
|
channel,
|
|
44757
45003
|
wsToken: this.wsToken,
|
|
@@ -44764,7 +45010,7 @@ var WsClient = class {
|
|
|
44764
45010
|
}, 15e3);
|
|
44765
45011
|
timer.unref?.();
|
|
44766
45012
|
this.pending.set(envelope.id, {
|
|
44767
|
-
resolve: (value) =>
|
|
45013
|
+
resolve: (value) => resolve11(value),
|
|
44768
45014
|
timer
|
|
44769
45015
|
});
|
|
44770
45016
|
this.socket.send(JSON.stringify(envelope));
|
|
@@ -45140,7 +45386,7 @@ function releaseInstanceLock(profileDir2) {
|
|
|
45140
45386
|
|
|
45141
45387
|
// src/core/session-bus.ts
|
|
45142
45388
|
import { EventEmitter } from "events";
|
|
45143
|
-
import { randomUUID as
|
|
45389
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
45144
45390
|
var REPLAY_CAP = 500;
|
|
45145
45391
|
var SessionBus = class {
|
|
45146
45392
|
emitter = new EventEmitter();
|
|
@@ -45171,7 +45417,7 @@ var SessionBus = class {
|
|
|
45171
45417
|
/** Producer → consumers: push one transcript entry. Returns its id (for `updateEntry`). */
|
|
45172
45418
|
push(input) {
|
|
45173
45419
|
const entry = {
|
|
45174
|
-
id:
|
|
45420
|
+
id: randomUUID4(),
|
|
45175
45421
|
level: "info",
|
|
45176
45422
|
...input,
|
|
45177
45423
|
ts: Date.now()
|
|
@@ -50875,8 +51121,8 @@ var Ink = class {
|
|
|
50875
51121
|
}
|
|
50876
51122
|
}
|
|
50877
51123
|
async waitUntilExit() {
|
|
50878
|
-
this.exitPromise ||= new Promise((
|
|
50879
|
-
this.resolveExitPromise =
|
|
51124
|
+
this.exitPromise ||= new Promise((resolve11, reject) => {
|
|
51125
|
+
this.resolveExitPromise = resolve11;
|
|
50880
51126
|
this.rejectExitPromise = reject;
|
|
50881
51127
|
});
|
|
50882
51128
|
return this.exitPromise;
|
|
@@ -51929,7 +52175,7 @@ async function runStart(profileDir2, profileName) {
|
|
|
51929
52175
|
logger.error("ws.run.crash", { message: e.message, stack: e.stack });
|
|
51930
52176
|
if (client.isStopped) return;
|
|
51931
52177
|
bus.log(t("start.recovered", { message: e.message }), "error");
|
|
51932
|
-
await new Promise((
|
|
52178
|
+
await new Promise((resolve11) => setTimeout(resolve11, 1e3));
|
|
51933
52179
|
}
|
|
51934
52180
|
}
|
|
51935
52181
|
};
|
|
@@ -51975,7 +52221,7 @@ import { join as join38 } from "path";
|
|
|
51975
52221
|
// src/core/control-client.ts
|
|
51976
52222
|
import { connect as connect2 } from "net";
|
|
51977
52223
|
function connectControl(socketPath, bus, token) {
|
|
51978
|
-
return new Promise((
|
|
52224
|
+
return new Promise((resolve11, reject) => {
|
|
51979
52225
|
const socket = connect2(socketPath);
|
|
51980
52226
|
socket.setEncoding("utf8");
|
|
51981
52227
|
let ready = false;
|
|
@@ -52028,7 +52274,7 @@ function connectControl(socketPath, bus, token) {
|
|
|
52028
52274
|
bus.setTokens(frame.tokens);
|
|
52029
52275
|
for (const entry of frame.transcript) bus.pushEntry(entry);
|
|
52030
52276
|
ready = true;
|
|
52031
|
-
|
|
52277
|
+
resolve11({ info: frame.info, close });
|
|
52032
52278
|
break;
|
|
52033
52279
|
case "transcript":
|
|
52034
52280
|
bus.pushEntry(frame.entry);
|
|
@@ -52116,7 +52362,7 @@ async function runAutoRun(profileDir2, profileName) {
|
|
|
52116
52362
|
initI18n(readProfileConfig(profileDir2).locale);
|
|
52117
52363
|
const socketPath = join39(profileDir2, CONTROL_SOCKET_FILE);
|
|
52118
52364
|
const token = readControlToken(profileDir2);
|
|
52119
|
-
const code = await new Promise((
|
|
52365
|
+
const code = await new Promise((resolve11) => {
|
|
52120
52366
|
let settled = false;
|
|
52121
52367
|
const done = (c) => {
|
|
52122
52368
|
if (settled) return;
|
|
@@ -52126,7 +52372,7 @@ async function runAutoRun(profileDir2, profileName) {
|
|
|
52126
52372
|
socket.destroy();
|
|
52127
52373
|
} catch {
|
|
52128
52374
|
}
|
|
52129
|
-
|
|
52375
|
+
resolve11(c);
|
|
52130
52376
|
};
|
|
52131
52377
|
const socket = connect3(socketPath);
|
|
52132
52378
|
socket.setEncoding("utf8");
|
|
@@ -52174,16 +52420,16 @@ import { spawn as spawn12 } from "child_process";
|
|
|
52174
52420
|
import { mkdirSync as mkdirSync9 } from "fs";
|
|
52175
52421
|
import { basename as basename8 } from "path";
|
|
52176
52422
|
function runProviderLogin(cmd, envVar, dir2) {
|
|
52177
|
-
return new Promise((
|
|
52423
|
+
return new Promise((resolve11) => {
|
|
52178
52424
|
const child = spawn12(cmd, [], {
|
|
52179
52425
|
stdio: "inherit",
|
|
52180
52426
|
env: { ...process.env, [envVar]: dir2 }
|
|
52181
52427
|
});
|
|
52182
52428
|
child.on("error", (err) => {
|
|
52183
52429
|
console.error(t("aiLogin.launchFailed", { cmd, message: err.message }));
|
|
52184
|
-
|
|
52430
|
+
resolve11(1);
|
|
52185
52431
|
});
|
|
52186
|
-
child.on("exit", (code) =>
|
|
52432
|
+
child.on("exit", (code) => resolve11(code ?? 0));
|
|
52187
52433
|
});
|
|
52188
52434
|
}
|
|
52189
52435
|
async function runAiLogin(profileDir2, profileName, aiFilter) {
|
|
@@ -52323,10 +52569,10 @@ function Picker({
|
|
|
52323
52569
|
] });
|
|
52324
52570
|
}
|
|
52325
52571
|
function selectFromList(items, title) {
|
|
52326
|
-
return new Promise((
|
|
52572
|
+
return new Promise((resolve11) => {
|
|
52327
52573
|
let chosen = null;
|
|
52328
52574
|
const app = render_default(/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Picker, { items, title, onDone: (v2) => chosen = v2 }));
|
|
52329
|
-
void app.waitUntilExit().then(() =>
|
|
52575
|
+
void app.waitUntilExit().then(() => resolve11(chosen));
|
|
52330
52576
|
});
|
|
52331
52577
|
}
|
|
52332
52578
|
|