@4pm/cli 1.21.0 → 1.23.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 +443 -182
- 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
|
}
|
|
@@ -39221,13 +39291,25 @@ function claudeHomeDirs(config) {
|
|
|
39221
39291
|
return dirs.length > 0 ? dirs : [join9(homedir3(), ".claude")];
|
|
39222
39292
|
}
|
|
39223
39293
|
function profileDisplayLabel(dir2) {
|
|
39294
|
+
return profileAccountEmail(dir2) ?? basename(dir2);
|
|
39295
|
+
}
|
|
39296
|
+
function profileAccountEmail(dir2) {
|
|
39224
39297
|
try {
|
|
39225
39298
|
const raw = JSON.parse(readFileSync8(join9(dir2, ".claude.json"), "utf8"));
|
|
39226
|
-
const email = raw.oauthAccount?.emailAddress;
|
|
39227
|
-
|
|
39299
|
+
const email = raw.oauthAccount?.emailAddress?.trim();
|
|
39300
|
+
return email ? email : null;
|
|
39228
39301
|
} catch {
|
|
39302
|
+
return null;
|
|
39229
39303
|
}
|
|
39230
|
-
|
|
39304
|
+
}
|
|
39305
|
+
function aiAccountLabels(config) {
|
|
39306
|
+
const labels = isUnifiedConfig(config) ? config.aiProfiles.filter(isUsableCredential).map((c) => {
|
|
39307
|
+
const dir2 = resolveHomePath(c.profile);
|
|
39308
|
+
return profileAccountEmail(dir2) ?? (c.label?.trim() || basename(dir2));
|
|
39309
|
+
}) : [...profileDirs(config.claudeHome), ...profileDirs(config.codexHome), ...profileDirs(config.antigravityHome)].map(
|
|
39310
|
+
(dir2) => profileDisplayLabel(dir2)
|
|
39311
|
+
);
|
|
39312
|
+
return [...new Set(labels)];
|
|
39231
39313
|
}
|
|
39232
39314
|
function profileEnvVar(cmd) {
|
|
39233
39315
|
if (cmd.includes("claude")) return "CLAUDE_CONFIG_DIR";
|
|
@@ -39265,7 +39347,7 @@ function planUnifiedRun(prompt, config, workingCredential, resume, oneShot, over
|
|
|
39265
39347
|
return {
|
|
39266
39348
|
cmd: active,
|
|
39267
39349
|
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) } }
|
|
39350
|
+
{ 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
39351
|
]
|
|
39270
39352
|
};
|
|
39271
39353
|
}
|
|
@@ -39283,7 +39365,7 @@ function planUnifiedRun(prompt, config, workingCredential, resume, oneShot, over
|
|
|
39283
39365
|
label: cred.label?.trim() || profileDisplayLabel(dir2),
|
|
39284
39366
|
dir: dir2,
|
|
39285
39367
|
key,
|
|
39286
|
-
args: buildRunArgs(cred, cmd, resume.get(key), oneShot, override, readOnly, bypass),
|
|
39368
|
+
args: buildRunArgs(cred, cmd, resume.get(key), oneShot, override, readOnly, bypass, [...allAiProfileDirs(config), dir2]),
|
|
39287
39369
|
stdin: prompt,
|
|
39288
39370
|
env: envVar ? { ...baseEnv, [envVar]: dir2, ...overrideEnv(cmd, override) } : { ...baseEnv, ...overrideEnv(cmd, override) }
|
|
39289
39371
|
}));
|
|
@@ -39296,7 +39378,7 @@ function planLegacyRun(prompt, config, workingDir, resume, oneShot, override, re
|
|
|
39296
39378
|
const defaultPlan = {
|
|
39297
39379
|
cmd,
|
|
39298
39380
|
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) } }
|
|
39381
|
+
{ 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
39382
|
]
|
|
39301
39383
|
};
|
|
39302
39384
|
if (!envVar) return defaultPlan;
|
|
@@ -39313,7 +39395,7 @@ function planLegacyRun(prompt, config, workingDir, resume, oneShot, override, re
|
|
|
39313
39395
|
label: profileDisplayLabel(dir2),
|
|
39314
39396
|
dir: dir2,
|
|
39315
39397
|
key,
|
|
39316
|
-
args: buildRunArgs(profile, cmd, resume.get(key), oneShot, override, readOnly, bypass),
|
|
39398
|
+
args: buildRunArgs(profile, cmd, resume.get(key), oneShot, override, readOnly, bypass, [...allAiProfileDirs(config), dir2]),
|
|
39317
39399
|
stdin: prompt,
|
|
39318
39400
|
env: { ...baseEnv, [envVar]: dir2, ...overrideEnv(cmd, override) }
|
|
39319
39401
|
};
|
|
@@ -39356,16 +39438,16 @@ async function fetchUsage(token) {
|
|
|
39356
39438
|
}
|
|
39357
39439
|
}
|
|
39358
39440
|
function runUsageCli(dir2, aiCli2) {
|
|
39359
|
-
return new Promise((
|
|
39441
|
+
return new Promise((resolve11) => {
|
|
39360
39442
|
let out = "";
|
|
39361
39443
|
let child = null;
|
|
39362
39444
|
const finish = () => {
|
|
39363
39445
|
clearTimeout(timer);
|
|
39364
|
-
|
|
39446
|
+
resolve11(out);
|
|
39365
39447
|
};
|
|
39366
39448
|
const timer = setTimeout(() => {
|
|
39367
39449
|
child?.kill();
|
|
39368
|
-
|
|
39450
|
+
resolve11(out);
|
|
39369
39451
|
}, 3e4);
|
|
39370
39452
|
timer.unref?.();
|
|
39371
39453
|
try {
|
|
@@ -39688,9 +39770,9 @@ function pruneCommandOutputByAge(days) {
|
|
|
39688
39770
|
import { spawn as spawn4 } from "child_process";
|
|
39689
39771
|
import { execFile as execFile3 } from "child_process";
|
|
39690
39772
|
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";
|
|
39773
|
+
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
39774
|
import { join as join14 } from "path";
|
|
39775
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
39694
39776
|
import { promisify as promisify3 } from "util";
|
|
39695
39777
|
|
|
39696
39778
|
// src/core/ai-stream.ts
|
|
@@ -39720,12 +39802,16 @@ function createAiStreamParser(cli) {
|
|
|
39720
39802
|
acc.output = ev.usage.output_tokens ?? 0;
|
|
39721
39803
|
acc.cacheRead = ev.usage.cache_read_input_tokens ?? 0;
|
|
39722
39804
|
acc.cacheCreation = ev.usage.cache_creation_input_tokens ?? 0;
|
|
39723
|
-
acc.tokens = acc.input + acc.output;
|
|
39805
|
+
acc.tokens = acc.input + acc.output + acc.cacheRead + acc.cacheCreation;
|
|
39724
39806
|
if (typeof ev.total_cost_usd === "number") acc.costUsd = ev.total_cost_usd;
|
|
39725
39807
|
}
|
|
39726
39808
|
return "";
|
|
39727
39809
|
}
|
|
39728
39810
|
if (ev.type === "assistant") {
|
|
39811
|
+
const u2 = ev.message?.usage;
|
|
39812
|
+
if (u2) {
|
|
39813
|
+
acc.contextTokens = (u2.input_tokens ?? 0) + (u2.cache_read_input_tokens ?? 0) + (u2.cache_creation_input_tokens ?? 0) + (u2.output_tokens ?? 0);
|
|
39814
|
+
}
|
|
39729
39815
|
const text = assistantText(ev);
|
|
39730
39816
|
return text ? `${text}
|
|
39731
39817
|
` : "";
|
|
@@ -39734,11 +39820,12 @@ function createAiStreamParser(cli) {
|
|
|
39734
39820
|
}
|
|
39735
39821
|
function handleCodex(ev) {
|
|
39736
39822
|
if (ev.type === "turn.completed" && ev.usage) {
|
|
39737
|
-
|
|
39823
|
+
const cached = ev.usage.cached_input_tokens ?? 0;
|
|
39824
|
+
acc.input = Math.max(0, (ev.usage.input_tokens ?? 0) - cached);
|
|
39738
39825
|
acc.output = (ev.usage.output_tokens ?? 0) + (ev.usage.reasoning_output_tokens ?? 0);
|
|
39739
|
-
acc.cacheRead =
|
|
39826
|
+
acc.cacheRead = cached;
|
|
39740
39827
|
acc.cacheCreation = 0;
|
|
39741
|
-
acc.tokens = acc.input + acc.output;
|
|
39828
|
+
acc.tokens = acc.input + acc.output + acc.cacheRead;
|
|
39742
39829
|
return "";
|
|
39743
39830
|
}
|
|
39744
39831
|
if (ev.type === "item.completed" && ev.item?.type === "agent_message") {
|
|
@@ -39786,10 +39873,65 @@ function createAiStreamParser(cli) {
|
|
|
39786
39873
|
apiError: () => ({ ...apiError })
|
|
39787
39874
|
};
|
|
39788
39875
|
}
|
|
39876
|
+
function aiProviderOf(cmd) {
|
|
39877
|
+
if (cmd.includes("codex")) return "codex";
|
|
39878
|
+
if (cmd.includes("antigravity")) return "antigravity";
|
|
39879
|
+
return "claude";
|
|
39880
|
+
}
|
|
39789
39881
|
function estimateTokens2(text) {
|
|
39790
39882
|
return Math.max(1, Math.ceil(text.length / 4));
|
|
39791
39883
|
}
|
|
39792
39884
|
|
|
39885
|
+
// src/core/agent-sandbox.ts
|
|
39886
|
+
var ENV_ALLOW = /* @__PURE__ */ new Set([
|
|
39887
|
+
"PATH",
|
|
39888
|
+
"HOME",
|
|
39889
|
+
"USER",
|
|
39890
|
+
"LOGNAME",
|
|
39891
|
+
"SHELL",
|
|
39892
|
+
"LANG",
|
|
39893
|
+
"TERM",
|
|
39894
|
+
"TZ",
|
|
39895
|
+
"TMPDIR",
|
|
39896
|
+
"TMP",
|
|
39897
|
+
"TEMP",
|
|
39898
|
+
"HTTP_PROXY",
|
|
39899
|
+
"HTTPS_PROXY",
|
|
39900
|
+
"NO_PROXY",
|
|
39901
|
+
"ALL_PROXY",
|
|
39902
|
+
"http_proxy",
|
|
39903
|
+
"https_proxy",
|
|
39904
|
+
"no_proxy",
|
|
39905
|
+
"all_proxy",
|
|
39906
|
+
"SSL_CERT_FILE",
|
|
39907
|
+
"SSL_CERT_DIR",
|
|
39908
|
+
"NODE_EXTRA_CA_CERTS",
|
|
39909
|
+
"SYSTEMROOT",
|
|
39910
|
+
"SYSTEMDRIVE",
|
|
39911
|
+
"WINDIR",
|
|
39912
|
+
"COMSPEC",
|
|
39913
|
+
"PATHEXT",
|
|
39914
|
+
"USERPROFILE",
|
|
39915
|
+
"APPDATA",
|
|
39916
|
+
"LOCALAPPDATA",
|
|
39917
|
+
"PROGRAMDATA",
|
|
39918
|
+
"PROGRAMFILES"
|
|
39919
|
+
]);
|
|
39920
|
+
var ENV_ALLOW_PREFIXES = ["LC_", "XDG_", "ANTHROPIC_", "CLAUDE_", "CODEX_", "OPENAI_", "AWS_", "GOOGLE_", "CLOUD_ML_", "VERTEX_"];
|
|
39921
|
+
function agentEnv(extraEnv, overrides) {
|
|
39922
|
+
const env3 = {};
|
|
39923
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
39924
|
+
if (value === void 0) continue;
|
|
39925
|
+
if (ENV_ALLOW.has(key) || ENV_ALLOW_PREFIXES.some((p2) => key.startsWith(p2))) env3[key] = value;
|
|
39926
|
+
}
|
|
39927
|
+
return { ...env3, ...extraEnv, ...overrides };
|
|
39928
|
+
}
|
|
39929
|
+
function gitAuthArgs(user, password) {
|
|
39930
|
+
if (!user && !password) return [];
|
|
39931
|
+
const basic = Buffer.from(`${user}:${password}`).toString("base64");
|
|
39932
|
+
return ["-c", `http.extraHeader=Authorization: Basic ${basic}`];
|
|
39933
|
+
}
|
|
39934
|
+
|
|
39793
39935
|
// src/core/support-answer.ts
|
|
39794
39936
|
var NO_USAGE = { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 };
|
|
39795
39937
|
var execFileAsync = promisify3(execFile3);
|
|
@@ -39803,18 +39945,34 @@ function repoDir(profileDir2, url, branch) {
|
|
|
39803
39945
|
const key = createHash3("sha1").update(`${url}#${branch}`).digest("hex").slice(0, 16);
|
|
39804
39946
|
return join14(kbRoot(profileDir2), key);
|
|
39805
39947
|
}
|
|
39806
|
-
|
|
39807
|
-
|
|
39808
|
-
|
|
39809
|
-
|
|
39948
|
+
var DENIED_TOOLS = ["Bash", "Edit", "Write", "NotebookEdit", "WebFetch", "WebSearch", "Task"];
|
|
39949
|
+
var KB_STALE_MS = 24 * 60 * 60 * 1e3;
|
|
39950
|
+
var kbTokens = /* @__PURE__ */ new Map();
|
|
39951
|
+
function kbAuth(token) {
|
|
39952
|
+
return token ? gitAuthArgs(token, "") : [];
|
|
39953
|
+
}
|
|
39954
|
+
async function pullKb(dir2) {
|
|
39955
|
+
const gitEnv = { ...process.env, GIT_TERMINAL_PROMPT: "0" };
|
|
39956
|
+
await execFileAsync("git", [...kbAuth(kbTokens.get(dir2)), "-C", dir2, "pull", "--ff-only"], { env: gitEnv });
|
|
39810
39957
|
}
|
|
39811
39958
|
async function ensureRepo(profileDir2, repo) {
|
|
39812
39959
|
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
39960
|
const gitEnv = { ...process.env, GIT_TERMINAL_PROMPT: "0" };
|
|
39961
|
+
if (repo.token) kbTokens.set(dir2, repo.token);
|
|
39962
|
+
else kbTokens.delete(dir2);
|
|
39963
|
+
if (existsSync10(join14(dir2, ".git"))) {
|
|
39964
|
+
const current2 = await execFileAsync("git", ["-C", dir2, "remote", "get-url", "origin"], { env: gitEnv }).then((r) => r.stdout.trim()).catch(() => repo.url);
|
|
39965
|
+
if (current2 !== repo.url) {
|
|
39966
|
+
await execFileAsync("git", ["-C", dir2, "remote", "set-url", "origin", repo.url], { env: gitEnv }).catch(() => void 0);
|
|
39967
|
+
}
|
|
39968
|
+
const marker = existsSync10(join14(dir2, ".git", "FETCH_HEAD")) ? join14(dir2, ".git", "FETCH_HEAD") : join14(dir2, ".git", "HEAD");
|
|
39969
|
+
if (Date.now() - statSync4(marker).mtimeMs > KB_STALE_MS) {
|
|
39970
|
+
void pullKb(dir2).catch((err) => logger.warn("support.kb.refresh.failed", { dir: dir2, error: String(err) }));
|
|
39971
|
+
}
|
|
39972
|
+
return dir2;
|
|
39973
|
+
}
|
|
39816
39974
|
mkdirSync5(kbRoot(profileDir2), { recursive: true });
|
|
39817
|
-
await execFileAsync("git", ["clone", "--depth", "1", "--branch", repo.branch, url, dir2], {
|
|
39975
|
+
await execFileAsync("git", [...kbAuth(repo.token), "clone", "--depth", "1", "--branch", repo.branch, repo.url, dir2], {
|
|
39818
39976
|
env: gitEnv
|
|
39819
39977
|
});
|
|
39820
39978
|
return dir2;
|
|
@@ -39822,12 +39980,11 @@ async function ensureRepo(profileDir2, repo) {
|
|
|
39822
39980
|
async function refreshSupportKb(profileDir2) {
|
|
39823
39981
|
const root = kbRoot(profileDir2);
|
|
39824
39982
|
if (!existsSync10(root)) return;
|
|
39825
|
-
const gitEnv = { ...process.env, GIT_TERMINAL_PROMPT: "0" };
|
|
39826
39983
|
for (const entry of readdirSync5(root)) {
|
|
39827
39984
|
const dir2 = join14(root, entry);
|
|
39828
39985
|
if (!existsSync10(join14(dir2, ".git"))) continue;
|
|
39829
39986
|
try {
|
|
39830
|
-
await
|
|
39987
|
+
await pullKb(dir2);
|
|
39831
39988
|
} catch (err) {
|
|
39832
39989
|
logger.warn("support.kb.refresh.failed", { dir: dir2, error: String(err) });
|
|
39833
39990
|
}
|
|
@@ -39890,6 +40047,33 @@ function buildPrompt(docs, question, askerRole) {
|
|
|
39890
40047
|
question
|
|
39891
40048
|
].join("\n");
|
|
39892
40049
|
}
|
|
40050
|
+
function buildDraftPrompt(docs, context, task) {
|
|
40051
|
+
const goal = task === "reply_draft" ? [
|
|
40052
|
+
"You are drafting a reply FROM the 4PM support team TO the customer in the support ticket below.",
|
|
40053
|
+
"Address the customer's latest message, using the whole conversation for context. Be accurate,",
|
|
40054
|
+
"friendly and concise. Write in the language the customer uses in the ticket."
|
|
40055
|
+
] : [
|
|
40056
|
+
"You are drafting an outreach message FROM the 4PM platform team TO the customer organizations.",
|
|
40057
|
+
"Use the subject and any current draft below as the starting point. Be clear, friendly and concise.",
|
|
40058
|
+
"Write in the language of the subject/draft (or of the admin's instructions when those are empty)."
|
|
40059
|
+
];
|
|
40060
|
+
return [
|
|
40061
|
+
...goal,
|
|
40062
|
+
"Use the documentation and FAQ below for any product facts; never invent features, prices or",
|
|
40063
|
+
"commitments that are not in the docs \u2014 leave a clear [placeholder] for the admin to fill instead.",
|
|
40064
|
+
"Follow the admin's instructions when given. A human admin reviews and edits your draft before",
|
|
40065
|
+
"sending it.",
|
|
40066
|
+
"",
|
|
40067
|
+
"Output ONLY the message body in markdown \u2014 no subject line, no preamble, no explanation, no code",
|
|
40068
|
+
"fences around the whole message.",
|
|
40069
|
+
"",
|
|
40070
|
+
"===== DOCUMENTATION =====",
|
|
40071
|
+
docs,
|
|
40072
|
+
"",
|
|
40073
|
+
"===== CONTEXT =====",
|
|
40074
|
+
context
|
|
40075
|
+
].join("\n");
|
|
40076
|
+
}
|
|
39893
40077
|
function imageExt(mime) {
|
|
39894
40078
|
switch (mime) {
|
|
39895
40079
|
case "image/png":
|
|
@@ -39904,9 +40088,8 @@ function imageExt(mime) {
|
|
|
39904
40088
|
return "png";
|
|
39905
40089
|
}
|
|
39906
40090
|
}
|
|
39907
|
-
function materializeImages2(
|
|
39908
|
-
const dir2 = join14(
|
|
39909
|
-
mkdirSync5(dir2, { recursive: true });
|
|
40091
|
+
function materializeImages2(images, question) {
|
|
40092
|
+
const dir2 = mkdtempSync2(join14(tmpdir3(), "4pm-help-images-"));
|
|
39910
40093
|
let rewritten = question;
|
|
39911
40094
|
images.forEach((img, i) => {
|
|
39912
40095
|
const file = join14(dir2, `image-${i + 1}.${imageExt(img.mime)}`);
|
|
@@ -39932,23 +40115,32 @@ function parseModeratedAnswer(text) {
|
|
|
39932
40115
|
}
|
|
39933
40116
|
return { body: text.trim() };
|
|
39934
40117
|
}
|
|
39935
|
-
function runClaudeOnce(cmd, profile, prompt, extraEnv, imageDir) {
|
|
39936
|
-
return new Promise((
|
|
40118
|
+
function runClaudeOnce(cmd, profile, prompt, extraEnv, imageDir, denyDirs = []) {
|
|
40119
|
+
return new Promise((resolve11, reject) => {
|
|
39937
40120
|
const isClaude = cmd.includes("claude");
|
|
39938
40121
|
const args = [
|
|
39939
40122
|
"-p",
|
|
39940
40123
|
...profile?.model ? ["--model", profile.model] : [],
|
|
39941
40124
|
...isClaude ? ["--output-format", "stream-json", "--verbose"] : [],
|
|
39942
|
-
//
|
|
39943
|
-
//
|
|
39944
|
-
|
|
40125
|
+
// No shell / edits / web for an untrusted question (ADR-0346). Reads need no grant inside the
|
|
40126
|
+
// (empty) working dir and any added dir; anywhere else they would need an approval a headless
|
|
40127
|
+
// run never gets.
|
|
40128
|
+
// Secret-path deny rules (ADR-0347) — reads are not confined to the working dir.
|
|
40129
|
+
...denySettingsArgs(cmd, [...denyDirs, profile?.dir]),
|
|
40130
|
+
...isClaude ? ["--disallowedTools", ...DENIED_TOOLS] : [],
|
|
40131
|
+
// When the question references pasted images (ADR-0273), add the materialized folder so the
|
|
40132
|
+
// agent can Read them (the question already carries their absolute paths) — scoped to it only.
|
|
40133
|
+
...isClaude && imageDir ? ["--add-dir", imageDir] : []
|
|
39945
40134
|
];
|
|
39946
|
-
const env3 = {
|
|
39947
|
-
|
|
39948
|
-
|
|
39949
|
-
|
|
40135
|
+
const env3 = agentEnv(extraEnv, profile ? { CLAUDE_CONFIG_DIR: profile.dir } : void 0);
|
|
40136
|
+
const cwd2 = mkdtempSync2(join14(tmpdir3(), "4pm-support-"));
|
|
40137
|
+
const cleanup = () => {
|
|
40138
|
+
try {
|
|
40139
|
+
rmSync5(cwd2, { recursive: true, force: true });
|
|
40140
|
+
} catch {
|
|
40141
|
+
}
|
|
39950
40142
|
};
|
|
39951
|
-
const child = spawn4(cmd, args, { stdio: ["pipe", "pipe", "pipe"], env: env3 });
|
|
40143
|
+
const child = spawn4(cmd, args, { stdio: ["pipe", "pipe", "pipe"], env: env3, cwd: cwd2 });
|
|
39952
40144
|
const parser = createAiStreamParser(cmd);
|
|
39953
40145
|
let out = "";
|
|
39954
40146
|
let err = "";
|
|
@@ -39961,12 +40153,14 @@ function runClaudeOnce(cmd, profile, prompt, extraEnv, imageDir) {
|
|
|
39961
40153
|
child.stderr.on("data", (d) => err += d.toString());
|
|
39962
40154
|
child.on("error", (e) => {
|
|
39963
40155
|
clearTimeout(timer);
|
|
40156
|
+
cleanup();
|
|
39964
40157
|
reject(e);
|
|
39965
40158
|
});
|
|
39966
40159
|
child.on("close", (code) => {
|
|
39967
40160
|
clearTimeout(timer);
|
|
40161
|
+
cleanup();
|
|
39968
40162
|
out += parser.flush();
|
|
39969
|
-
|
|
40163
|
+
resolve11({ code: code ?? -1, out, err, usage: parser.usage() });
|
|
39970
40164
|
});
|
|
39971
40165
|
child.stdin.write(prompt);
|
|
39972
40166
|
child.stdin.end();
|
|
@@ -39976,7 +40170,7 @@ async function runClaudeWithFailover(prompt, ai2, imageDir) {
|
|
|
39976
40170
|
const attempts = ai2.profiles.length > 0 ? ai2.profiles : [null];
|
|
39977
40171
|
let lastReason = "no attempt";
|
|
39978
40172
|
for (let i = 0; i < attempts.length; i++) {
|
|
39979
|
-
const { code, out, err, usage } = await runClaudeOnce(ai2.cmd, attempts[i], prompt, ai2.env, imageDir);
|
|
40173
|
+
const { code, out, err, usage } = await runClaudeOnce(ai2.cmd, attempts[i], prompt, ai2.env, imageDir, ai2.profiles.map((p2) => p2.dir));
|
|
39980
40174
|
if (code === 0 && out.trim()) return { text: out.trim(), usage };
|
|
39981
40175
|
const combined = err || out;
|
|
39982
40176
|
lastReason = `exited ${code}: ${combined.slice(0, 500)}`;
|
|
@@ -39988,7 +40182,7 @@ async function runSupportAnswer(req, ai2, profileDir2) {
|
|
|
39988
40182
|
let imageDir;
|
|
39989
40183
|
let question = req.question;
|
|
39990
40184
|
if (req.images?.length) {
|
|
39991
|
-
const m2 = materializeImages2(
|
|
40185
|
+
const m2 = materializeImages2(req.images, req.question);
|
|
39992
40186
|
imageDir = m2.dir;
|
|
39993
40187
|
question = m2.question;
|
|
39994
40188
|
}
|
|
@@ -39996,13 +40190,10 @@ async function runSupportAnswer(req, ai2, profileDir2) {
|
|
|
39996
40190
|
const dir2 = await ensureRepo(profileDir2, req.repo);
|
|
39997
40191
|
const docs = collectDocs(dir2);
|
|
39998
40192
|
if (!docs.trim()) return { body: "", error: "KB repo has no documentation" };
|
|
39999
|
-
const
|
|
40000
|
-
|
|
40001
|
-
ai2,
|
|
40002
|
-
imageDir
|
|
40003
|
-
);
|
|
40193
|
+
const prompt = req.task ? buildDraftPrompt(docs, question, req.task) : buildPrompt(docs, question, req.askerRole);
|
|
40194
|
+
const { text, usage } = await runClaudeWithFailover(prompt, ai2, imageDir);
|
|
40004
40195
|
if (!text) return { body: "", error: "empty answer" };
|
|
40005
|
-
const { body, moderation } = parseModeratedAnswer(text);
|
|
40196
|
+
const { body, moderation } = req.task ? { body: text.trim(), moderation: void 0 } : parseModeratedAnswer(text);
|
|
40006
40197
|
if (!body) return { body: "", error: "empty answer" };
|
|
40007
40198
|
const u2 = usage.tokens > 0 ? usage : { ...NO_USAGE, tokens: estimateTokens2(body) };
|
|
40008
40199
|
return {
|
|
@@ -40237,10 +40428,10 @@ async function crontabList() {
|
|
|
40237
40428
|
}
|
|
40238
40429
|
}
|
|
40239
40430
|
async function crontabSet(content) {
|
|
40240
|
-
return new Promise((
|
|
40431
|
+
return new Promise((resolve11) => {
|
|
40241
40432
|
const child = spawn5("crontab", ["-"], { stdio: ["pipe", "ignore", "ignore"] });
|
|
40242
|
-
child.on("error", () =>
|
|
40243
|
-
child.on("close", (code) =>
|
|
40433
|
+
child.on("error", () => resolve11(false));
|
|
40434
|
+
child.on("close", (code) => resolve11(code === 0));
|
|
40244
40435
|
child.stdin.end(content.endsWith("\n") ? content : content + "\n");
|
|
40245
40436
|
});
|
|
40246
40437
|
}
|
|
@@ -40409,14 +40600,14 @@ function detectContainerized() {
|
|
|
40409
40600
|
}
|
|
40410
40601
|
}
|
|
40411
40602
|
function canReach(host, port) {
|
|
40412
|
-
return new Promise((
|
|
40603
|
+
return new Promise((resolve11) => {
|
|
40413
40604
|
const socket = connect({ host, port });
|
|
40414
40605
|
let done = false;
|
|
40415
40606
|
const finish = (ok) => {
|
|
40416
40607
|
if (done) return;
|
|
40417
40608
|
done = true;
|
|
40418
40609
|
socket.destroy();
|
|
40419
|
-
|
|
40610
|
+
resolve11(ok);
|
|
40420
40611
|
};
|
|
40421
40612
|
socket.setTimeout(PROBE_TIMEOUT_MS);
|
|
40422
40613
|
socket.once("connect", () => finish(true));
|
|
@@ -40588,7 +40779,7 @@ async function runCommand(dispatch, emit, opts) {
|
|
|
40588
40779
|
});
|
|
40589
40780
|
child.stdout?.on("data", (data) => batcher.push(data.toString("utf8")));
|
|
40590
40781
|
child.stderr?.on("data", (data) => batcher.push(data.toString("utf8")));
|
|
40591
|
-
await new Promise((
|
|
40782
|
+
await new Promise((resolve11) => {
|
|
40592
40783
|
child.on("close", (exitCode) => {
|
|
40593
40784
|
clearTimers();
|
|
40594
40785
|
batcher.flush();
|
|
@@ -40601,7 +40792,7 @@ async function runCommand(dispatch, emit, opts) {
|
|
|
40601
40792
|
// server settles the command as failed and the web surfaces the note instead of spinning.
|
|
40602
40793
|
exitCode: timedOut ? 124 : exitCode ?? -1
|
|
40603
40794
|
});
|
|
40604
|
-
|
|
40795
|
+
resolve11();
|
|
40605
40796
|
});
|
|
40606
40797
|
child.on("error", (err) => {
|
|
40607
40798
|
clearTimers();
|
|
@@ -40613,7 +40804,7 @@ async function runCommand(dispatch, emit, opts) {
|
|
|
40613
40804
|
done: true,
|
|
40614
40805
|
exitCode: -1
|
|
40615
40806
|
});
|
|
40616
|
-
|
|
40807
|
+
resolve11();
|
|
40617
40808
|
});
|
|
40618
40809
|
});
|
|
40619
40810
|
}
|
|
@@ -40636,15 +40827,15 @@ function isOutOfCredits(text) {
|
|
|
40636
40827
|
);
|
|
40637
40828
|
}
|
|
40638
40829
|
function runAttempt(commandId, cmd, args, stdin, cwd2, env3, onChunk, timeoutMs) {
|
|
40639
|
-
return new Promise((
|
|
40830
|
+
return new Promise((resolve11) => {
|
|
40640
40831
|
runCommand(
|
|
40641
40832
|
{ commandId, cmd, args, path: cwd2, env: env3 },
|
|
40642
40833
|
(out) => {
|
|
40643
40834
|
if (out.chunk) onChunk(out.chunk);
|
|
40644
|
-
if (out.done)
|
|
40835
|
+
if (out.done) resolve11(out.exitCode ?? -1);
|
|
40645
40836
|
},
|
|
40646
40837
|
{ stdin, ...timeoutMs > 0 ? { timeoutMs } : {} }
|
|
40647
|
-
).catch(() =>
|
|
40838
|
+
).catch(() => resolve11(-1));
|
|
40648
40839
|
});
|
|
40649
40840
|
}
|
|
40650
40841
|
async function runAiFailover(plan, commandId, cwd2, handlers, timeoutMs = 0) {
|
|
@@ -41122,6 +41313,24 @@ function runSlashCommand(line, base) {
|
|
|
41122
41313
|
|
|
41123
41314
|
// src/core/memory-compact.ts
|
|
41124
41315
|
import { spawn as spawn7 } from "child_process";
|
|
41316
|
+
function parseJsonResult(out) {
|
|
41317
|
+
let parsed;
|
|
41318
|
+
try {
|
|
41319
|
+
parsed = JSON.parse(out.trim());
|
|
41320
|
+
} catch {
|
|
41321
|
+
return null;
|
|
41322
|
+
}
|
|
41323
|
+
if (parsed.is_error || typeof parsed.result !== "string") return null;
|
|
41324
|
+
const u2 = parsed.usage;
|
|
41325
|
+
const usage = u2 ? {
|
|
41326
|
+
input: u2.input_tokens ?? 0,
|
|
41327
|
+
output: u2.output_tokens ?? 0,
|
|
41328
|
+
cacheRead: u2.cache_read_input_tokens ?? 0,
|
|
41329
|
+
cacheCreation: u2.cache_creation_input_tokens ?? 0,
|
|
41330
|
+
tokens: (u2.input_tokens ?? 0) + (u2.output_tokens ?? 0) + (u2.cache_read_input_tokens ?? 0) + (u2.cache_creation_input_tokens ?? 0)
|
|
41331
|
+
} : null;
|
|
41332
|
+
return { text: parsed.result, usage };
|
|
41333
|
+
}
|
|
41125
41334
|
var COMPACT_TIMEOUT_MS = 6e4;
|
|
41126
41335
|
function buildPrompt2(input) {
|
|
41127
41336
|
return [
|
|
@@ -41141,9 +41350,21 @@ function buildPrompt2(input) {
|
|
|
41141
41350
|
input.answer
|
|
41142
41351
|
].join("\n");
|
|
41143
41352
|
}
|
|
41144
|
-
function runOnce(cmd, profile, prompt, cwd2, extraEnv) {
|
|
41145
|
-
return new Promise((
|
|
41146
|
-
const args = [
|
|
41353
|
+
function runOnce(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
|
|
41354
|
+
return new Promise((resolve11, reject) => {
|
|
41355
|
+
const args = [
|
|
41356
|
+
"-p",
|
|
41357
|
+
"--output-format",
|
|
41358
|
+
"json",
|
|
41359
|
+
...profile?.model ? ["--model", profile.model] : [],
|
|
41360
|
+
// Secret-path deny rules (ADR-0347) — before the variadic `--disallowedTools`.
|
|
41361
|
+
...denySettingsArgs(cmd, [...denyDirs, profile?.dir]),
|
|
41362
|
+
"--max-turns=1",
|
|
41363
|
+
"--disallowedTools",
|
|
41364
|
+
ONE_SHOT_DISALLOWED_CLAUDE_TOOLS,
|
|
41365
|
+
"--permission-mode",
|
|
41366
|
+
"default"
|
|
41367
|
+
];
|
|
41147
41368
|
const env3 = {
|
|
41148
41369
|
...process.env,
|
|
41149
41370
|
...extraEnv,
|
|
@@ -41165,7 +41386,7 @@ function runOnce(cmd, profile, prompt, cwd2, extraEnv) {
|
|
|
41165
41386
|
});
|
|
41166
41387
|
child.on("close", (code) => {
|
|
41167
41388
|
clearTimeout(timer);
|
|
41168
|
-
|
|
41389
|
+
resolve11({ code: code ?? -1, out, err });
|
|
41169
41390
|
});
|
|
41170
41391
|
child.stdin.write(prompt);
|
|
41171
41392
|
child.stdin.end();
|
|
@@ -41176,17 +41397,19 @@ async function runMemoryCompaction(ai2, cwd2, input) {
|
|
|
41176
41397
|
const attempts = ai2.profiles.length > 0 ? ai2.profiles : [null];
|
|
41177
41398
|
for (let i = 0; i < attempts.length; i++) {
|
|
41178
41399
|
try {
|
|
41179
|
-
const
|
|
41180
|
-
|
|
41400
|
+
const profile = attempts[i];
|
|
41401
|
+
const { code, out } = await runOnce(ai2.cmd, profile, prompt, cwd2, ai2.env, ai2.profiles.map((p2) => p2.dir));
|
|
41402
|
+
const parsed = code === 0 ? parseJsonResult(out) : null;
|
|
41403
|
+
if (parsed && parsed.text.trim()) {
|
|
41181
41404
|
reportToolResult(ai2.cmd, true);
|
|
41182
|
-
return
|
|
41405
|
+
return { memory: parsed.text.trim().slice(0, input.budgetChars), usage: parsed.usage, dir: profile?.dir ?? null };
|
|
41183
41406
|
}
|
|
41184
41407
|
if (i === attempts.length - 1) break;
|
|
41185
41408
|
} catch {
|
|
41186
41409
|
break;
|
|
41187
41410
|
}
|
|
41188
41411
|
}
|
|
41189
|
-
return "";
|
|
41412
|
+
return { memory: "", usage: null, dir: null };
|
|
41190
41413
|
}
|
|
41191
41414
|
|
|
41192
41415
|
// src/core/git-commit-identity.ts
|
|
@@ -41361,6 +41584,7 @@ async function runAiPrompt(ctx, prompt, commandId, origin, oneShot = false, imag
|
|
|
41361
41584
|
const guardedPrompt = ctx.restrictToFolder && ctx.physicRoot ? folderScopeGuard(ctx.physicRoot, aiBodyPrompt) : aiBodyPrompt;
|
|
41362
41585
|
const hint = unified ? { credential: startCred ?? workingCred } : { dir: startDir ?? workingDir };
|
|
41363
41586
|
const memCfg = resolveMemoryConfig(config);
|
|
41587
|
+
pruneResumableSessions(ctx, resolveResumePolicy(config), Date.now());
|
|
41364
41588
|
const firstAttempt = planAiRun(guardedPrompt, config, hint, /* @__PURE__ */ new Map(), oneShot, aiConfig, readOnly, bypass).attempts[0];
|
|
41365
41589
|
const resumeId = memCfg.enabled && firstAttempt?.key && firstAttempt.cmd === "claude" ? ctx.sessionIdByKey.get(firstAttempt.key) : void 0;
|
|
41366
41590
|
let effectivePrompt = guardedPrompt;
|
|
@@ -41508,6 +41732,8 @@ ${guardedPrompt}`;
|
|
|
41508
41732
|
outputTokens: result.usage.output,
|
|
41509
41733
|
cacheReadTokens: result.usage.cacheRead,
|
|
41510
41734
|
cacheCreationTokens: result.usage.cacheCreation,
|
|
41735
|
+
// Provider → the server's per-provider quota-token weights (ADR-0340).
|
|
41736
|
+
provider: aiProviderOf(result.workedCmd ?? plan.cmd),
|
|
41511
41737
|
// Auth mode for billing split (ADR-0192 §5): subscription (OAuth) vs api-key (API-billed).
|
|
41512
41738
|
authMode: resolveClaudeAuthMode(config)
|
|
41513
41739
|
});
|
|
@@ -41525,9 +41751,23 @@ ${guardedPrompt}`;
|
|
|
41525
41751
|
if (memCfg.enabled && result.exitCode === 0) {
|
|
41526
41752
|
if (result.workedKey && result.sessionId) {
|
|
41527
41753
|
ctx.sessionIdByKey.set(result.workedKey, result.sessionId);
|
|
41754
|
+
ctx.sessionMetaByKey.set(result.workedKey, { at: Date.now(), contextTokens: result.usage.contextTokens ?? 0 });
|
|
41528
41755
|
}
|
|
41529
41756
|
void updateSharedMemory(ctx, config, prompt, answerText, memCfg.budgetChars).catch(() => {
|
|
41530
41757
|
});
|
|
41758
|
+
} else if (result.exitCode !== 0) {
|
|
41759
|
+
ctx.sessionIdByKey.clear();
|
|
41760
|
+
ctx.sessionMetaByKey.clear();
|
|
41761
|
+
}
|
|
41762
|
+
}
|
|
41763
|
+
function pruneResumableSessions(ctx, policy, now) {
|
|
41764
|
+
for (const key of [...ctx.sessionIdByKey.keys()]) {
|
|
41765
|
+
const meta = ctx.sessionMetaByKey.get(key);
|
|
41766
|
+
const stale = !meta || now - meta.at > policy.maxIdleMs || meta.contextTokens > policy.maxContextTokens;
|
|
41767
|
+
if (stale) {
|
|
41768
|
+
ctx.sessionIdByKey.delete(key);
|
|
41769
|
+
ctx.sessionMetaByKey.delete(key);
|
|
41770
|
+
}
|
|
41531
41771
|
}
|
|
41532
41772
|
}
|
|
41533
41773
|
async function runWebSlashCommand(ctx, line, commandId) {
|
|
@@ -41635,11 +41875,29 @@ async function updateSharedMemory(ctx, config, userPrompt, answer, budgetChars)
|
|
|
41635
41875
|
if (!answer.trim()) return;
|
|
41636
41876
|
const profiles = resolveClaudeProfiles(config, ctx.physicRoot);
|
|
41637
41877
|
const cwd2 = ctx.physicRoot ?? process.cwd();
|
|
41638
|
-
const newMemory = await runMemoryCompaction(
|
|
41878
|
+
const { memory: newMemory, usage, dir: dir2 } = await runMemoryCompaction(
|
|
41639
41879
|
{ cmd: "claude", profiles, env: config.aiEnv },
|
|
41640
41880
|
cwd2,
|
|
41641
41881
|
{ oldMemory: ctx.aiMemory, prompt: userPrompt, answer, budgetChars }
|
|
41642
41882
|
);
|
|
41883
|
+
if (usage && usage.tokens > 0) {
|
|
41884
|
+
ctx.reportUsage(
|
|
41885
|
+
[
|
|
41886
|
+
{
|
|
41887
|
+
metric: UsageMetric.AI_TOKENS,
|
|
41888
|
+
amount: usage.tokens,
|
|
41889
|
+
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
41890
|
+
inputTokens: usage.input,
|
|
41891
|
+
outputTokens: usage.output,
|
|
41892
|
+
cacheReadTokens: usage.cacheRead,
|
|
41893
|
+
cacheCreationTokens: usage.cacheCreation,
|
|
41894
|
+
provider: "claude",
|
|
41895
|
+
authMode: resolveClaudeAuthMode(config)
|
|
41896
|
+
}
|
|
41897
|
+
],
|
|
41898
|
+
dir2 ? profileDisplayLabel(dir2) : null
|
|
41899
|
+
);
|
|
41900
|
+
}
|
|
41643
41901
|
if (!newMemory) return;
|
|
41644
41902
|
ctx.aiMemory = newMemory;
|
|
41645
41903
|
ctx.send(WsChannels.MEMORY_UPDATE, { text: newMemory });
|
|
@@ -41647,6 +41905,7 @@ async function updateSharedMemory(ctx, config, userPrompt, answer, budgetChars)
|
|
|
41647
41905
|
function resetMemorySession(ctx) {
|
|
41648
41906
|
ctx.aiMemory = "";
|
|
41649
41907
|
ctx.sessionIdByKey.clear();
|
|
41908
|
+
ctx.sessionMetaByKey.clear();
|
|
41650
41909
|
ctx.send(WsChannels.MEMORY_UPDATE, { text: "" });
|
|
41651
41910
|
}
|
|
41652
41911
|
function profileUnderSessionPressure(ctx, config, cmd, workingDir) {
|
|
@@ -41719,7 +41978,7 @@ function rejectByReview(ctx, commandId, origin, prompt, reasons) {
|
|
|
41719
41978
|
}
|
|
41720
41979
|
|
|
41721
41980
|
// src/core/autonomous-cycle.ts
|
|
41722
|
-
import { randomUUID
|
|
41981
|
+
import { randomUUID } from "crypto";
|
|
41723
41982
|
import { appendFile as appendFile2, mkdir as mkdir3, readFile as readFile5, readdir as readdir2, rm as rm2, stat } from "fs/promises";
|
|
41724
41983
|
import { join as join20 } from "path";
|
|
41725
41984
|
function buildAutonomousCyclePrompt() {
|
|
@@ -41859,7 +42118,7 @@ async function runAutonomousCycle(ctx) {
|
|
|
41859
42118
|
hist.ticks = { day: today, count: todayTickCount(hist, today) + 1 };
|
|
41860
42119
|
await writeHistories(root, hist);
|
|
41861
42120
|
await log(`[run] cycle (tick ${hist.ticks.count}/${today})`);
|
|
41862
|
-
const commandId =
|
|
42121
|
+
const commandId = randomUUID();
|
|
41863
42122
|
const override = cfg.model ? { model: cfg.model } : void 0;
|
|
41864
42123
|
try {
|
|
41865
42124
|
await runAiPrompt(ctx, buildAutonomousCyclePrompt(), commandId, "local", false, void 0, override, false, true);
|
|
@@ -41963,11 +42222,11 @@ async function allAiProfilesExhausted(ctx, cfg) {
|
|
|
41963
42222
|
|
|
41964
42223
|
// src/core/fs-browse.ts
|
|
41965
42224
|
import { readdir as readdir3 } from "fs/promises";
|
|
41966
|
-
import { resolve as
|
|
42225
|
+
import { resolve as resolve3, sep as sep2 } from "path";
|
|
41967
42226
|
async function listDir(path, root) {
|
|
41968
42227
|
if (!root) return { path: "", entries: [] };
|
|
41969
|
-
const base =
|
|
41970
|
-
const requested =
|
|
42228
|
+
const base = resolve3(root);
|
|
42229
|
+
const requested = resolve3(base, path && path.trim() ? path : ".");
|
|
41971
42230
|
const target = requested === base || requested.startsWith(base + sep2) ? requested : base;
|
|
41972
42231
|
try {
|
|
41973
42232
|
const dirents = await readdir3(target, { withFileTypes: true });
|
|
@@ -41986,12 +42245,12 @@ async function listDir(path, root) {
|
|
|
41986
42245
|
|
|
41987
42246
|
// src/core/fs-read.ts
|
|
41988
42247
|
import { readFile as readFile6 } from "fs/promises";
|
|
41989
|
-
import { resolve as
|
|
42248
|
+
import { resolve as resolve4, sep as sep3 } from "path";
|
|
41990
42249
|
var MAX_BYTES = 256 * 1024;
|
|
41991
42250
|
function resolveInRoot(root, relPath) {
|
|
41992
42251
|
if (!root) return null;
|
|
41993
|
-
const base =
|
|
41994
|
-
const target =
|
|
42252
|
+
const base = resolve4(root);
|
|
42253
|
+
const target = resolve4(base, relPath || ".");
|
|
41995
42254
|
return target === base || target.startsWith(base + sep3) ? target : null;
|
|
41996
42255
|
}
|
|
41997
42256
|
async function readWorkerFile(root, path) {
|
|
@@ -42011,12 +42270,12 @@ async function readWorkerFile(root, path) {
|
|
|
42011
42270
|
|
|
42012
42271
|
// src/core/fs-write.ts
|
|
42013
42272
|
import { mkdir as mkdir4, writeFile as writeFile5 } from "fs/promises";
|
|
42014
|
-
import { dirname as dirname4, resolve as
|
|
42273
|
+
import { dirname as dirname4, resolve as resolve5, sep as sep4 } from "path";
|
|
42015
42274
|
var MAX_BYTES2 = 2 * 1024 * 1024;
|
|
42016
42275
|
function resolveInRoot2(root, relPath) {
|
|
42017
42276
|
if (!root) return null;
|
|
42018
|
-
const base =
|
|
42019
|
-
const target =
|
|
42277
|
+
const base = resolve5(root);
|
|
42278
|
+
const target = resolve5(base, relPath || ".");
|
|
42020
42279
|
return target === base || target.startsWith(base + sep4) ? target : null;
|
|
42021
42280
|
}
|
|
42022
42281
|
async function writeWorkerFile(root, relPath, content) {
|
|
@@ -42039,11 +42298,11 @@ async function writeWorkerFile(root, relPath, content) {
|
|
|
42039
42298
|
|
|
42040
42299
|
// src/core/fs-mutate.ts
|
|
42041
42300
|
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
|
|
42301
|
+
import { basename as basename3, dirname as dirname5, resolve as resolve6, sep as sep5 } from "path";
|
|
42043
42302
|
var MAX_BYTES3 = 2 * 1024 * 1024;
|
|
42044
42303
|
function resolveInRoot3(root, rel) {
|
|
42045
|
-
const base =
|
|
42046
|
-
const target =
|
|
42304
|
+
const base = resolve6(root);
|
|
42305
|
+
const target = resolve6(base, rel || ".");
|
|
42047
42306
|
return target === base || target.startsWith(base + sep5) ? target : null;
|
|
42048
42307
|
}
|
|
42049
42308
|
async function isDir(p2) {
|
|
@@ -42068,13 +42327,13 @@ async function mutateFs(root, req) {
|
|
|
42068
42327
|
switch (req.op) {
|
|
42069
42328
|
case "mkdir": {
|
|
42070
42329
|
const target = resolveInRoot3(root, req.path ?? "");
|
|
42071
|
-
if (!target || target ===
|
|
42330
|
+
if (!target || target === resolve6(root)) return escaped(req.path ?? "");
|
|
42072
42331
|
await mkdir5(target, { recursive: true });
|
|
42073
42332
|
return { ok: true, path: target };
|
|
42074
42333
|
}
|
|
42075
42334
|
case "create": {
|
|
42076
42335
|
const target = resolveInRoot3(root, req.path ?? "");
|
|
42077
|
-
if (!target || target ===
|
|
42336
|
+
if (!target || target === resolve6(root)) return escaped(req.path ?? "");
|
|
42078
42337
|
if (await exists(target)) return { ok: false, path: target, error: "already exists" };
|
|
42079
42338
|
const content = req.content ?? "";
|
|
42080
42339
|
if (Buffer.byteLength(content, "utf8") > MAX_BYTES3) {
|
|
@@ -42088,9 +42347,9 @@ async function mutateFs(root, req) {
|
|
|
42088
42347
|
const from = resolveInRoot3(root, req.from ?? "");
|
|
42089
42348
|
let to2 = resolveInRoot3(root, req.to ?? "");
|
|
42090
42349
|
if (!from || !to2) return escaped(req.from ?? req.to ?? "");
|
|
42091
|
-
if (from ===
|
|
42092
|
-
if (await isDir(to2)) to2 =
|
|
42093
|
-
if (to2 !==
|
|
42350
|
+
if (from === resolve6(root)) return { ok: false, path: from, error: "cannot move the project root" };
|
|
42351
|
+
if (await isDir(to2)) to2 = resolve6(to2, basename3(from));
|
|
42352
|
+
if (to2 !== resolve6(root) && !to2.startsWith(resolve6(root) + sep5)) return escaped(req.to ?? "");
|
|
42094
42353
|
if (await exists(to2)) return { ok: false, path: to2, error: "destination already exists" };
|
|
42095
42354
|
await mkdir5(dirname5(to2), { recursive: true });
|
|
42096
42355
|
await rename(from, to2);
|
|
@@ -42099,7 +42358,7 @@ async function mutateFs(root, req) {
|
|
|
42099
42358
|
case "delete": {
|
|
42100
42359
|
const target = resolveInRoot3(root, req.path ?? "");
|
|
42101
42360
|
if (!target) return escaped(req.path ?? "");
|
|
42102
|
-
if (target ===
|
|
42361
|
+
if (target === resolve6(root)) return { ok: false, path: target, error: "cannot delete the project root" };
|
|
42103
42362
|
await rm3(target, { recursive: true, force: true });
|
|
42104
42363
|
return { ok: true, path: target };
|
|
42105
42364
|
}
|
|
@@ -42113,11 +42372,11 @@ async function mutateFs(root, req) {
|
|
|
42113
42372
|
|
|
42114
42373
|
// src/core/fs-transfer.ts
|
|
42115
42374
|
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
|
|
42375
|
+
import { basename as basename4, dirname as dirname6, extname, resolve as resolve7, sep as sep6 } from "path";
|
|
42117
42376
|
function resolveInRoot4(root, relPath) {
|
|
42118
42377
|
if (!root) return null;
|
|
42119
|
-
const base =
|
|
42120
|
-
const target =
|
|
42378
|
+
const base = resolve7(root);
|
|
42379
|
+
const target = resolve7(base, relPath || ".");
|
|
42121
42380
|
return target === base || target.startsWith(base + sep6) ? target : null;
|
|
42122
42381
|
}
|
|
42123
42382
|
var MIME_BY_EXT = {
|
|
@@ -42233,13 +42492,13 @@ function handleFsChannels(ctx, message, payload) {
|
|
|
42233
42492
|
// src/core/git-diff.ts
|
|
42234
42493
|
import { readFile as readFile8 } from "fs/promises";
|
|
42235
42494
|
import { execFile as execFile6 } from "child_process";
|
|
42236
|
-
import { basename as basename5, dirname as dirname7, resolve as
|
|
42495
|
+
import { basename as basename5, dirname as dirname7, resolve as resolve8, sep as sep7 } from "path";
|
|
42237
42496
|
import { promisify as promisify6 } from "util";
|
|
42238
42497
|
var run6 = promisify6(execFile6);
|
|
42239
42498
|
function resolveInRoot5(root, relPath) {
|
|
42240
42499
|
if (!root) return null;
|
|
42241
|
-
const base =
|
|
42242
|
-
const target =
|
|
42500
|
+
const base = resolve8(root);
|
|
42501
|
+
const target = resolve8(base, relPath || ".");
|
|
42243
42502
|
return target === base || target.startsWith(base + sep7) ? target : null;
|
|
42244
42503
|
}
|
|
42245
42504
|
function looksBinary(buf) {
|
|
@@ -42393,15 +42652,15 @@ async function manageSshKey(op, profileDir2) {
|
|
|
42393
42652
|
|
|
42394
42653
|
// src/core/git-history.ts
|
|
42395
42654
|
import { execFile as execFile9 } from "child_process";
|
|
42396
|
-
import { resolve as
|
|
42655
|
+
import { resolve as resolve9, sep as sep8 } from "path";
|
|
42397
42656
|
import { promisify as promisify9 } from "util";
|
|
42398
42657
|
var run9 = promisify9(execFile9);
|
|
42399
42658
|
var FS = "";
|
|
42400
42659
|
var RS = "";
|
|
42401
42660
|
function resolveRepo(root, repo) {
|
|
42402
42661
|
if (!root) return null;
|
|
42403
|
-
const base =
|
|
42404
|
-
const requested =
|
|
42662
|
+
const base = resolve9(root);
|
|
42663
|
+
const requested = resolve9(base, repo && repo.trim() ? repo : ".");
|
|
42405
42664
|
return requested === base || requested.startsWith(base + sep8) ? requested : base;
|
|
42406
42665
|
}
|
|
42407
42666
|
async function git(cwd2, args) {
|
|
@@ -42418,7 +42677,7 @@ async function git(cwd2, args) {
|
|
|
42418
42677
|
}
|
|
42419
42678
|
async function gitRepos(root) {
|
|
42420
42679
|
if (!root) return { repos: [] };
|
|
42421
|
-
const base =
|
|
42680
|
+
const base = resolve9(root);
|
|
42422
42681
|
const top = (await git(base, ["rev-parse", "--show-toplevel"])).trim();
|
|
42423
42682
|
if (!top || !(top === base || top.startsWith(base + sep8))) return { repos: [] };
|
|
42424
42683
|
const repos = [];
|
|
@@ -42430,7 +42689,7 @@ async function gitRepos(root) {
|
|
|
42430
42689
|
const m2 = line.trim().match(/^[-+U ]?[0-9a-f]{7,40}\s+(\S+)/);
|
|
42431
42690
|
const subPath = m2?.[1];
|
|
42432
42691
|
if (!subPath) continue;
|
|
42433
|
-
const abs =
|
|
42692
|
+
const abs = resolve9(base, subPath);
|
|
42434
42693
|
if (!(abs === base || abs.startsWith(base + sep8))) continue;
|
|
42435
42694
|
const remote = (await git(abs, ["remote", "get-url", "origin"])).trim();
|
|
42436
42695
|
const branch = (await git(abs, ["rev-parse", "--abbrev-ref", "HEAD"])).trim();
|
|
@@ -42752,9 +43011,9 @@ Extra focus from the author: ${req.prompt}` : "";
|
|
|
42752
43011
|
"Output ONLY the Markdown article, no preamble or code fences around the whole thing." + focus
|
|
42753
43012
|
].join("\n");
|
|
42754
43013
|
}
|
|
42755
|
-
function runOnce2(cmd, profile, prompt, cwd2, extraEnv) {
|
|
42756
|
-
return new Promise((
|
|
42757
|
-
const args = ["-p", ...profile?.model ? ["--model", profile.model] : []];
|
|
43014
|
+
function runOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
|
|
43015
|
+
return new Promise((resolve11, reject) => {
|
|
43016
|
+
const args = ["-p", ...profile?.model ? ["--model", profile.model] : [], ...denySettingsArgs(cmd, [...denyDirs, profile?.dir])];
|
|
42758
43017
|
const env3 = {
|
|
42759
43018
|
...process.env,
|
|
42760
43019
|
...extraEnv,
|
|
@@ -42776,7 +43035,7 @@ function runOnce2(cmd, profile, prompt, cwd2, extraEnv) {
|
|
|
42776
43035
|
});
|
|
42777
43036
|
child.on("close", (code) => {
|
|
42778
43037
|
clearTimeout(timer);
|
|
42779
|
-
|
|
43038
|
+
resolve11({ code: code ?? -1, out, err });
|
|
42780
43039
|
});
|
|
42781
43040
|
child.stdin.write(prompt);
|
|
42782
43041
|
child.stdin.end();
|
|
@@ -42788,7 +43047,7 @@ async function runKnowledgeCompose(req, ai2, physicRoot) {
|
|
|
42788
43047
|
let lastReason = "no attempt";
|
|
42789
43048
|
for (let i = 0; i < attempts.length; i++) {
|
|
42790
43049
|
try {
|
|
42791
|
-
const { code, out, err } = await runOnce2(ai2.cmd, attempts[i], prompt, physicRoot, ai2.env);
|
|
43050
|
+
const { code, out, err } = await runOnce2(ai2.cmd, attempts[i], prompt, physicRoot, ai2.env, ai2.profiles.map((p2) => p2.dir));
|
|
42792
43051
|
if (code === 0 && out.trim()) {
|
|
42793
43052
|
reportToolResult(ai2.cmd, true);
|
|
42794
43053
|
return { bodyMarkdown: out.trim() };
|
|
@@ -42808,17 +43067,14 @@ async function runKnowledgeCompose(req, ai2, physicRoot) {
|
|
|
42808
43067
|
// src/core/faq-compose.ts
|
|
42809
43068
|
import { spawn as spawn9, execFile as execFile10 } from "child_process";
|
|
42810
43069
|
import { existsSync as existsSync14, mkdirSync as mkdirSync7, rmSync as rmSync7 } from "fs";
|
|
42811
|
-
import { randomUUID as
|
|
43070
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
42812
43071
|
import { join as join24 } from "path";
|
|
42813
|
-
import { tmpdir as
|
|
43072
|
+
import { tmpdir as tmpdir4 } from "os";
|
|
42814
43073
|
import { promisify as promisify10 } from "util";
|
|
42815
43074
|
var execFileAsync2 = promisify10(execFile10);
|
|
42816
43075
|
var NO_USAGE2 = { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 };
|
|
42817
43076
|
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
|
-
}
|
|
43077
|
+
var AGENT_DENIED_TOOLS = ["Bash", "WebFetch", "WebSearch", "NotebookEdit", "Task"];
|
|
42822
43078
|
function buildPrompt4(tickets, customPrompt) {
|
|
42823
43079
|
const transcript = tickets.map((t2) => {
|
|
42824
43080
|
const thread = t2.messages.map((m2) => ` [${m2.author}] ${m2.body}`).join("\n");
|
|
@@ -42879,22 +43135,22 @@ function parsePerTicket(text) {
|
|
|
42879
43135
|
}
|
|
42880
43136
|
return [];
|
|
42881
43137
|
}
|
|
42882
|
-
function runClaudeOnce2(cmd, profile, prompt, cwd2, extraEnv) {
|
|
42883
|
-
return new Promise((
|
|
43138
|
+
function runClaudeOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
|
|
43139
|
+
return new Promise((resolve11, reject) => {
|
|
42884
43140
|
const isClaude = cmd.includes("claude");
|
|
42885
43141
|
const args = [
|
|
42886
43142
|
"-p",
|
|
42887
43143
|
...profile?.model ? ["--model", profile.model] : [],
|
|
42888
43144
|
...isClaude ? ["--output-format", "stream-json", "--verbose"] : [],
|
|
42889
|
-
//
|
|
42890
|
-
|
|
42891
|
-
...cmd
|
|
43145
|
+
// Secret-path deny rules (ADR-0347): reads are not confined to the clone, so the credential
|
|
43146
|
+
// dirs / cli profile / ssh / gh / git credentials are explicitly denied.
|
|
43147
|
+
...denySettingsArgs(cmd, [...denyDirs, profile?.dir]),
|
|
43148
|
+
// Edit-only headless agent (ADR-0346): file edits are auto-accepted, everything else (Bash, web)
|
|
43149
|
+
// is denied, since the prompt carries untrusted ticket text. git/PR are done by this module.
|
|
43150
|
+
...isClaude ? ["--permission-mode", "acceptEdits", "--disallowedTools", ...AGENT_DENIED_TOOLS] : [],
|
|
43151
|
+
...cmd.includes("codex") ? ["--sandbox", "workspace-write"] : []
|
|
42892
43152
|
];
|
|
42893
|
-
const env3 = {
|
|
42894
|
-
...process.env,
|
|
42895
|
-
...extraEnv,
|
|
42896
|
-
...profile ? { CLAUDE_CONFIG_DIR: profile.dir } : {}
|
|
42897
|
-
};
|
|
43153
|
+
const env3 = agentEnv(extraEnv, profile ? { CLAUDE_CONFIG_DIR: profile.dir } : void 0);
|
|
42898
43154
|
const child = spawn9(cmd, args, { stdio: ["pipe", "pipe", "pipe"], cwd: cwd2, env: env3 });
|
|
42899
43155
|
const parser = createAiStreamParser(cmd);
|
|
42900
43156
|
let out = "";
|
|
@@ -42913,7 +43169,7 @@ function runClaudeOnce2(cmd, profile, prompt, cwd2, extraEnv) {
|
|
|
42913
43169
|
child.on("close", (code) => {
|
|
42914
43170
|
clearTimeout(timer);
|
|
42915
43171
|
out += parser.flush();
|
|
42916
|
-
|
|
43172
|
+
resolve11({ code: code ?? -1, out, err, usage: parser.usage() });
|
|
42917
43173
|
});
|
|
42918
43174
|
child.stdin.write(prompt);
|
|
42919
43175
|
child.stdin.end();
|
|
@@ -42923,7 +43179,7 @@ async function runClaudeWithFailover2(prompt, cwd2, ai2) {
|
|
|
42923
43179
|
const attempts = ai2.profiles.length > 0 ? ai2.profiles : [null];
|
|
42924
43180
|
let lastReason = "no attempt";
|
|
42925
43181
|
for (let i = 0; i < attempts.length; i++) {
|
|
42926
|
-
const { code, out, err, usage } = await runClaudeOnce2(ai2.cmd, attempts[i], prompt, cwd2, ai2.env);
|
|
43182
|
+
const { code, out, err, usage } = await runClaudeOnce2(ai2.cmd, attempts[i], prompt, cwd2, ai2.env, ai2.profiles.map((p2) => p2.dir));
|
|
42927
43183
|
if (code === 0) return { usage, text: out.trim() };
|
|
42928
43184
|
lastReason = `exited ${code}: ${(err || out).slice(0, 500)}`;
|
|
42929
43185
|
if (i === attempts.length - 1) break;
|
|
@@ -42933,12 +43189,12 @@ async function runClaudeWithFailover2(prompt, cwd2, ai2) {
|
|
|
42933
43189
|
var MAX_OUTPUT_CHARS = 8e3;
|
|
42934
43190
|
async function runFaqCompose(req, ai2) {
|
|
42935
43191
|
if (!req.repo.token) return { prUrl: "", error: "no write token" };
|
|
42936
|
-
const workDir = join24(
|
|
43192
|
+
const workDir = join24(tmpdir4(), "4pm-faq-sync", randomUUID2());
|
|
42937
43193
|
const gitEnv = { ...process.env, GIT_TERMINAL_PROMPT: "0" };
|
|
42938
|
-
const
|
|
43194
|
+
const auth = gitAuthArgs("x-access-token", req.repo.token);
|
|
42939
43195
|
try {
|
|
42940
43196
|
mkdirSync7(workDir, { recursive: true });
|
|
42941
|
-
await execFileAsync2("git", ["clone", "--depth", "1", "--branch", req.repo.branch,
|
|
43197
|
+
await execFileAsync2("git", [...auth, "clone", "--depth", "1", "--branch", req.repo.branch, req.repo.url, workDir], { env: gitEnv });
|
|
42942
43198
|
await execFileAsync2("git", ["-C", workDir, "config", "user.name", "4PM FAQ Bot"], { env: gitEnv });
|
|
42943
43199
|
await execFileAsync2("git", ["-C", workDir, "config", "user.email", "faq-bot@4pm.app"], { env: gitEnv });
|
|
42944
43200
|
await execFileAsync2("git", ["-C", workDir, "checkout", "-b", req.headBranch], { env: gitEnv });
|
|
@@ -42951,7 +43207,7 @@ async function runFaqCompose(req, ai2) {
|
|
|
42951
43207
|
return { prUrl: "", output, perTicket, tokens: usage.tokens || estimateTokens2(text), finishedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
42952
43208
|
}
|
|
42953
43209
|
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 });
|
|
43210
|
+
await execFileAsync2("git", [...auth, "-C", workDir, "push", "-u", "origin", req.headBranch], { env: gitEnv });
|
|
42955
43211
|
let prUrl = "";
|
|
42956
43212
|
try {
|
|
42957
43213
|
const pr2 = await execFileAsync2(
|
|
@@ -43533,7 +43789,7 @@ async function removePackage(root, slug) {
|
|
|
43533
43789
|
|
|
43534
43790
|
// src/core/graph.ts
|
|
43535
43791
|
import { readFile as readFile14, readdir as readdir6 } from "fs/promises";
|
|
43536
|
-
import { join as join29, relative as relative2, dirname as dirname10, resolve as
|
|
43792
|
+
import { join as join29, relative as relative2, dirname as dirname10, resolve as resolve10, extname as extname2, basename as basename6 } from "path";
|
|
43537
43793
|
var MAX_FILES = 2e3;
|
|
43538
43794
|
var MAX_NODES = 3e3;
|
|
43539
43795
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "out", "coverage", ".turbo"]);
|
|
@@ -43588,7 +43844,7 @@ async function buildDocs(root) {
|
|
|
43588
43844
|
for (const m2 of text.matchAll(/\[[^\]]*\]\(([^)]+)\)/g)) {
|
|
43589
43845
|
const href = (m2[1] ?? "").split("#")[0].trim();
|
|
43590
43846
|
if (!href || !href.endsWith(".md") || href.startsWith("http")) continue;
|
|
43591
|
-
const target = relative2(root,
|
|
43847
|
+
const target = relative2(root, resolve10(dirname10(f2), href)).split("\\").join("/");
|
|
43592
43848
|
if (ids.has(target) && target !== rel) {
|
|
43593
43849
|
const key = `${rel}->${target}`;
|
|
43594
43850
|
if (!seen.has(key)) {
|
|
@@ -44220,6 +44476,7 @@ var WsClient = class {
|
|
|
44220
44476
|
*/
|
|
44221
44477
|
aiMemory = "";
|
|
44222
44478
|
sessionIdByKey = /* @__PURE__ */ new Map();
|
|
44479
|
+
sessionMetaByKey = /* @__PURE__ */ new Map();
|
|
44223
44480
|
stopped = false;
|
|
44224
44481
|
/** Resolver to wake the backoff sleep early (set while waiting) — for /reconnect. */
|
|
44225
44482
|
wakeReconnect = null;
|
|
@@ -44313,6 +44570,7 @@ var WsClient = class {
|
|
|
44313
44570
|
serverUrl: this.context.credential.serverUrl,
|
|
44314
44571
|
startedAtMs: this.startedAtMs,
|
|
44315
44572
|
sessionIdByKey: this.sessionIdByKey,
|
|
44573
|
+
sessionMetaByKey: this.sessionMetaByKey,
|
|
44316
44574
|
get machineUsername() {
|
|
44317
44575
|
return self.machineUsername;
|
|
44318
44576
|
},
|
|
@@ -44425,14 +44683,14 @@ var WsClient = class {
|
|
|
44425
44683
|
*/
|
|
44426
44684
|
awaitConnected(timeoutMs) {
|
|
44427
44685
|
if (this.sessionKey && this.bus.status === "connected") return Promise.resolve(true);
|
|
44428
|
-
return new Promise((
|
|
44686
|
+
return new Promise((resolve11) => {
|
|
44429
44687
|
const waiter = () => {
|
|
44430
44688
|
clearTimeout(timer);
|
|
44431
|
-
|
|
44689
|
+
resolve11(true);
|
|
44432
44690
|
};
|
|
44433
44691
|
const timer = setTimeout(() => {
|
|
44434
44692
|
this.connectWaiters = this.connectWaiters.filter((w2) => w2 !== waiter);
|
|
44435
|
-
|
|
44693
|
+
resolve11(false);
|
|
44436
44694
|
}, timeoutMs);
|
|
44437
44695
|
this.connectWaiters.push(waiter);
|
|
44438
44696
|
});
|
|
@@ -44459,16 +44717,16 @@ var WsClient = class {
|
|
|
44459
44717
|
this.forceReconnect = false;
|
|
44460
44718
|
return Promise.resolve();
|
|
44461
44719
|
}
|
|
44462
|
-
return new Promise((
|
|
44720
|
+
return new Promise((resolve11) => {
|
|
44463
44721
|
const timer = setTimeout(() => {
|
|
44464
44722
|
this.wakeReconnect = null;
|
|
44465
|
-
|
|
44723
|
+
resolve11();
|
|
44466
44724
|
}, ms2);
|
|
44467
44725
|
this.wakeReconnect = () => {
|
|
44468
44726
|
clearTimeout(timer);
|
|
44469
44727
|
this.wakeReconnect = null;
|
|
44470
44728
|
this.forceReconnect = false;
|
|
44471
|
-
|
|
44729
|
+
resolve11();
|
|
44472
44730
|
};
|
|
44473
44731
|
});
|
|
44474
44732
|
}
|
|
@@ -44522,7 +44780,7 @@ var WsClient = class {
|
|
|
44522
44780
|
* One WS session: token → connect → handshake → handle messages until close.
|
|
44523
44781
|
*/
|
|
44524
44782
|
connectOnce() {
|
|
44525
|
-
return new Promise((
|
|
44783
|
+
return new Promise((resolve11, reject) => {
|
|
44526
44784
|
void (async () => {
|
|
44527
44785
|
this.sessionEstablished = false;
|
|
44528
44786
|
this.bus.setStatus("connecting");
|
|
@@ -44618,7 +44876,7 @@ var WsClient = class {
|
|
|
44618
44876
|
if (code === CLOSE_DRAINING) {
|
|
44619
44877
|
this.bus.log(t("error.serverDraining"));
|
|
44620
44878
|
}
|
|
44621
|
-
|
|
44879
|
+
resolve11();
|
|
44622
44880
|
});
|
|
44623
44881
|
socket.on("error", (err) => {
|
|
44624
44882
|
this.clearHeartbeat();
|
|
@@ -44730,7 +44988,7 @@ var WsClient = class {
|
|
|
44730
44988
|
}
|
|
44731
44989
|
const { payload, nonce } = encryptPayload(this.sessionKey, data);
|
|
44732
44990
|
const envelope = {
|
|
44733
|
-
id:
|
|
44991
|
+
id: randomUUID3(),
|
|
44734
44992
|
replyTo,
|
|
44735
44993
|
channel,
|
|
44736
44994
|
wsToken: this.wsToken,
|
|
@@ -44744,14 +45002,14 @@ var WsClient = class {
|
|
|
44744
45002
|
* Used for quota.check before spawning an AI cli (ADR-0020).
|
|
44745
45003
|
*/
|
|
44746
45004
|
request(channel, data) {
|
|
44747
|
-
return new Promise((
|
|
45005
|
+
return new Promise((resolve11, reject) => {
|
|
44748
45006
|
if (!this.socket || !this.sessionKey) {
|
|
44749
45007
|
reject(new Error("Not connected to the server."));
|
|
44750
45008
|
return;
|
|
44751
45009
|
}
|
|
44752
45010
|
const { payload, nonce } = encryptPayload(this.sessionKey, data);
|
|
44753
45011
|
const envelope = {
|
|
44754
|
-
id:
|
|
45012
|
+
id: randomUUID3(),
|
|
44755
45013
|
replyTo: null,
|
|
44756
45014
|
channel,
|
|
44757
45015
|
wsToken: this.wsToken,
|
|
@@ -44764,7 +45022,7 @@ var WsClient = class {
|
|
|
44764
45022
|
}, 15e3);
|
|
44765
45023
|
timer.unref?.();
|
|
44766
45024
|
this.pending.set(envelope.id, {
|
|
44767
|
-
resolve: (value) =>
|
|
45025
|
+
resolve: (value) => resolve11(value),
|
|
44768
45026
|
timer
|
|
44769
45027
|
});
|
|
44770
45028
|
this.socket.send(JSON.stringify(envelope));
|
|
@@ -44854,7 +45112,10 @@ var WsClient = class {
|
|
|
44854
45112
|
// The machine-user AI-run limit (0 = unlimited) + profile count so a dispatch can resolve the
|
|
44855
45113
|
// effective timeout and derive the SSE reply windows server-side (ADR-0256).
|
|
44856
45114
|
aiRunTimeoutSec: config.aiRunTimeoutSec ?? 0,
|
|
44857
|
-
aiProfileCount
|
|
45115
|
+
aiProfileCount,
|
|
45116
|
+
// The configured AI accounts (emails when readable) — admin-only visibility of which AI
|
|
45117
|
+
// account a (rented) machine runs on + the shared-account check (ADR-0354).
|
|
45118
|
+
aiAccounts: aiAccountLabels(config)
|
|
44858
45119
|
});
|
|
44859
45120
|
}
|
|
44860
45121
|
/** Heartbeat every 30s: ping + periodically re-report machine.status. */
|
|
@@ -45140,7 +45401,7 @@ function releaseInstanceLock(profileDir2) {
|
|
|
45140
45401
|
|
|
45141
45402
|
// src/core/session-bus.ts
|
|
45142
45403
|
import { EventEmitter } from "events";
|
|
45143
|
-
import { randomUUID as
|
|
45404
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
45144
45405
|
var REPLAY_CAP = 500;
|
|
45145
45406
|
var SessionBus = class {
|
|
45146
45407
|
emitter = new EventEmitter();
|
|
@@ -45171,7 +45432,7 @@ var SessionBus = class {
|
|
|
45171
45432
|
/** Producer → consumers: push one transcript entry. Returns its id (for `updateEntry`). */
|
|
45172
45433
|
push(input) {
|
|
45173
45434
|
const entry = {
|
|
45174
|
-
id:
|
|
45435
|
+
id: randomUUID4(),
|
|
45175
45436
|
level: "info",
|
|
45176
45437
|
...input,
|
|
45177
45438
|
ts: Date.now()
|
|
@@ -50875,8 +51136,8 @@ var Ink = class {
|
|
|
50875
51136
|
}
|
|
50876
51137
|
}
|
|
50877
51138
|
async waitUntilExit() {
|
|
50878
|
-
this.exitPromise ||= new Promise((
|
|
50879
|
-
this.resolveExitPromise =
|
|
51139
|
+
this.exitPromise ||= new Promise((resolve11, reject) => {
|
|
51140
|
+
this.resolveExitPromise = resolve11;
|
|
50880
51141
|
this.rejectExitPromise = reject;
|
|
50881
51142
|
});
|
|
50882
51143
|
return this.exitPromise;
|
|
@@ -51929,7 +52190,7 @@ async function runStart(profileDir2, profileName) {
|
|
|
51929
52190
|
logger.error("ws.run.crash", { message: e.message, stack: e.stack });
|
|
51930
52191
|
if (client.isStopped) return;
|
|
51931
52192
|
bus.log(t("start.recovered", { message: e.message }), "error");
|
|
51932
|
-
await new Promise((
|
|
52193
|
+
await new Promise((resolve11) => setTimeout(resolve11, 1e3));
|
|
51933
52194
|
}
|
|
51934
52195
|
}
|
|
51935
52196
|
};
|
|
@@ -51975,7 +52236,7 @@ import { join as join38 } from "path";
|
|
|
51975
52236
|
// src/core/control-client.ts
|
|
51976
52237
|
import { connect as connect2 } from "net";
|
|
51977
52238
|
function connectControl(socketPath, bus, token) {
|
|
51978
|
-
return new Promise((
|
|
52239
|
+
return new Promise((resolve11, reject) => {
|
|
51979
52240
|
const socket = connect2(socketPath);
|
|
51980
52241
|
socket.setEncoding("utf8");
|
|
51981
52242
|
let ready = false;
|
|
@@ -52028,7 +52289,7 @@ function connectControl(socketPath, bus, token) {
|
|
|
52028
52289
|
bus.setTokens(frame.tokens);
|
|
52029
52290
|
for (const entry of frame.transcript) bus.pushEntry(entry);
|
|
52030
52291
|
ready = true;
|
|
52031
|
-
|
|
52292
|
+
resolve11({ info: frame.info, close });
|
|
52032
52293
|
break;
|
|
52033
52294
|
case "transcript":
|
|
52034
52295
|
bus.pushEntry(frame.entry);
|
|
@@ -52116,7 +52377,7 @@ async function runAutoRun(profileDir2, profileName) {
|
|
|
52116
52377
|
initI18n(readProfileConfig(profileDir2).locale);
|
|
52117
52378
|
const socketPath = join39(profileDir2, CONTROL_SOCKET_FILE);
|
|
52118
52379
|
const token = readControlToken(profileDir2);
|
|
52119
|
-
const code = await new Promise((
|
|
52380
|
+
const code = await new Promise((resolve11) => {
|
|
52120
52381
|
let settled = false;
|
|
52121
52382
|
const done = (c) => {
|
|
52122
52383
|
if (settled) return;
|
|
@@ -52126,7 +52387,7 @@ async function runAutoRun(profileDir2, profileName) {
|
|
|
52126
52387
|
socket.destroy();
|
|
52127
52388
|
} catch {
|
|
52128
52389
|
}
|
|
52129
|
-
|
|
52390
|
+
resolve11(c);
|
|
52130
52391
|
};
|
|
52131
52392
|
const socket = connect3(socketPath);
|
|
52132
52393
|
socket.setEncoding("utf8");
|
|
@@ -52174,16 +52435,16 @@ import { spawn as spawn12 } from "child_process";
|
|
|
52174
52435
|
import { mkdirSync as mkdirSync9 } from "fs";
|
|
52175
52436
|
import { basename as basename8 } from "path";
|
|
52176
52437
|
function runProviderLogin(cmd, envVar, dir2) {
|
|
52177
|
-
return new Promise((
|
|
52438
|
+
return new Promise((resolve11) => {
|
|
52178
52439
|
const child = spawn12(cmd, [], {
|
|
52179
52440
|
stdio: "inherit",
|
|
52180
52441
|
env: { ...process.env, [envVar]: dir2 }
|
|
52181
52442
|
});
|
|
52182
52443
|
child.on("error", (err) => {
|
|
52183
52444
|
console.error(t("aiLogin.launchFailed", { cmd, message: err.message }));
|
|
52184
|
-
|
|
52445
|
+
resolve11(1);
|
|
52185
52446
|
});
|
|
52186
|
-
child.on("exit", (code) =>
|
|
52447
|
+
child.on("exit", (code) => resolve11(code ?? 0));
|
|
52187
52448
|
});
|
|
52188
52449
|
}
|
|
52189
52450
|
async function runAiLogin(profileDir2, profileName, aiFilter) {
|
|
@@ -52323,10 +52584,10 @@ function Picker({
|
|
|
52323
52584
|
] });
|
|
52324
52585
|
}
|
|
52325
52586
|
function selectFromList(items, title) {
|
|
52326
|
-
return new Promise((
|
|
52587
|
+
return new Promise((resolve11) => {
|
|
52327
52588
|
let chosen = null;
|
|
52328
52589
|
const app = render_default(/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Picker, { items, title, onDone: (v2) => chosen = v2 }));
|
|
52329
|
-
void app.waitUntilExit().then(() =>
|
|
52590
|
+
void app.waitUntilExit().then(() => resolve11(chosen));
|
|
52330
52591
|
});
|
|
52331
52592
|
}
|
|
52332
52593
|
|