@4pm/cli 1.20.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 +471 -183
- 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,23 +43055,21 @@ 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
|
-
|
|
42820
|
-
return url;
|
|
42821
|
-
}
|
|
42822
|
-
function buildPrompt4(tickets) {
|
|
43065
|
+
var AGENT_DENIED_TOOLS = ["Bash", "WebFetch", "WebSearch", "NotebookEdit", "Task"];
|
|
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");
|
|
42825
43069
|
return `### Ticket ${t2.id} \u2014 ${t2.subject} (${t2.category})
|
|
42826
43070
|
${thread}`;
|
|
42827
43071
|
}).join("\n\n");
|
|
43072
|
+
const ids = tickets.map((t2) => t2.id);
|
|
42828
43073
|
return [
|
|
42829
43074
|
"You are maintaining the 4PM product FAQ, stored as markdown in THIS git repository (your CWD).",
|
|
42830
43075
|
"From the resolved support tickets below, distil reusable, product-general FAQ entries and add or",
|
|
@@ -42839,27 +43084,61 @@ ${thread}`;
|
|
|
42839
43084
|
"- Only edit markdown documentation files; do not touch code, CI, or unrelated files.",
|
|
42840
43085
|
"- Do NOT run git commit/push or open a PR yourself \u2014 just leave the edited files in the working",
|
|
42841
43086
|
" tree; the surrounding tooling handles commit + PR.",
|
|
43087
|
+
...customPrompt?.trim() ? ["", "Additional instruction from the operator (follow it too):", customPrompt.trim()] : [],
|
|
42842
43088
|
"",
|
|
42843
43089
|
"===== RESOLVED TICKETS =====",
|
|
42844
|
-
transcript
|
|
43090
|
+
transcript,
|
|
43091
|
+
"",
|
|
43092
|
+
"===== REQUIRED FINAL OUTPUT =====",
|
|
43093
|
+
"After finishing all file edits, output \u2014 as the LAST thing, on its own \u2014 a single fenced JSON",
|
|
43094
|
+
"block summarizing what you did PER TICKET (created/updated which FAQ entry, or skipped + why):",
|
|
43095
|
+
"```json",
|
|
43096
|
+
`{"perTicket":[${ids.map((id) => `{"ticketId":"${id}","summary":"..."}`).join(",")}]}`,
|
|
43097
|
+
"```",
|
|
43098
|
+
"Include exactly one entry per ticket id above; keep each summary to one or two sentences."
|
|
42845
43099
|
].join("\n");
|
|
42846
43100
|
}
|
|
42847
|
-
function
|
|
42848
|
-
|
|
43101
|
+
function parsePerTicket(text) {
|
|
43102
|
+
const idx = text.lastIndexOf('"perTicket"');
|
|
43103
|
+
if (idx === -1) return [];
|
|
43104
|
+
const open = text.lastIndexOf("{", idx);
|
|
43105
|
+
if (open === -1) return [];
|
|
43106
|
+
let depth = 0;
|
|
43107
|
+
for (let i = open; i < text.length; i++) {
|
|
43108
|
+
if (text[i] === "{") depth++;
|
|
43109
|
+
else if (text[i] === "}") {
|
|
43110
|
+
depth--;
|
|
43111
|
+
if (depth === 0) {
|
|
43112
|
+
try {
|
|
43113
|
+
const obj = JSON.parse(text.slice(open, i + 1));
|
|
43114
|
+
if (Array.isArray(obj.perTicket)) {
|
|
43115
|
+
return obj.perTicket.filter((e) => !!e && typeof e.ticketId === "string").map((e) => ({ ticketId: e.ticketId, summary: String(e.summary ?? "") }));
|
|
43116
|
+
}
|
|
43117
|
+
} catch {
|
|
43118
|
+
return [];
|
|
43119
|
+
}
|
|
43120
|
+
return [];
|
|
43121
|
+
}
|
|
43122
|
+
}
|
|
43123
|
+
}
|
|
43124
|
+
return [];
|
|
43125
|
+
}
|
|
43126
|
+
function runClaudeOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
|
|
43127
|
+
return new Promise((resolve11, reject) => {
|
|
42849
43128
|
const isClaude = cmd.includes("claude");
|
|
42850
43129
|
const args = [
|
|
42851
43130
|
"-p",
|
|
42852
43131
|
...profile?.model ? ["--model", profile.model] : [],
|
|
42853
43132
|
...isClaude ? ["--output-format", "stream-json", "--verbose"] : [],
|
|
42854
|
-
//
|
|
42855
|
-
|
|
42856
|
-
...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"] : []
|
|
42857
43140
|
];
|
|
42858
|
-
const env3 = {
|
|
42859
|
-
...process.env,
|
|
42860
|
-
...extraEnv,
|
|
42861
|
-
...profile ? { CLAUDE_CONFIG_DIR: profile.dir } : {}
|
|
42862
|
-
};
|
|
43141
|
+
const env3 = agentEnv(extraEnv, profile ? { CLAUDE_CONFIG_DIR: profile.dir } : void 0);
|
|
42863
43142
|
const child = spawn9(cmd, args, { stdio: ["pipe", "pipe", "pipe"], cwd: cwd2, env: env3 });
|
|
42864
43143
|
const parser = createAiStreamParser(cmd);
|
|
42865
43144
|
let out = "";
|
|
@@ -42878,7 +43157,7 @@ function runClaudeOnce2(cmd, profile, prompt, cwd2, extraEnv) {
|
|
|
42878
43157
|
child.on("close", (code) => {
|
|
42879
43158
|
clearTimeout(timer);
|
|
42880
43159
|
out += parser.flush();
|
|
42881
|
-
|
|
43160
|
+
resolve11({ code: code ?? -1, out, err, usage: parser.usage() });
|
|
42882
43161
|
});
|
|
42883
43162
|
child.stdin.write(prompt);
|
|
42884
43163
|
child.stdin.end();
|
|
@@ -42888,30 +43167,35 @@ async function runClaudeWithFailover2(prompt, cwd2, ai2) {
|
|
|
42888
43167
|
const attempts = ai2.profiles.length > 0 ? ai2.profiles : [null];
|
|
42889
43168
|
let lastReason = "no attempt";
|
|
42890
43169
|
for (let i = 0; i < attempts.length; i++) {
|
|
42891
|
-
const { code, out, err, usage } = await runClaudeOnce2(ai2.cmd, attempts[i], prompt, cwd2, ai2.env);
|
|
42892
|
-
if (code === 0) return usage;
|
|
43170
|
+
const { code, out, err, usage } = await runClaudeOnce2(ai2.cmd, attempts[i], prompt, cwd2, ai2.env, ai2.profiles.map((p2) => p2.dir));
|
|
43171
|
+
if (code === 0) return { usage, text: out.trim() };
|
|
42893
43172
|
lastReason = `exited ${code}: ${(err || out).slice(0, 500)}`;
|
|
42894
43173
|
if (i === attempts.length - 1) break;
|
|
42895
43174
|
}
|
|
42896
43175
|
throw new Error(`claude ${lastReason}`);
|
|
42897
43176
|
}
|
|
43177
|
+
var MAX_OUTPUT_CHARS = 8e3;
|
|
42898
43178
|
async function runFaqCompose(req, ai2) {
|
|
42899
43179
|
if (!req.repo.token) return { prUrl: "", error: "no write token" };
|
|
42900
|
-
const workDir = join24(
|
|
43180
|
+
const workDir = join24(tmpdir4(), "4pm-faq-sync", randomUUID2());
|
|
42901
43181
|
const gitEnv = { ...process.env, GIT_TERMINAL_PROMPT: "0" };
|
|
42902
|
-
const
|
|
43182
|
+
const auth = gitAuthArgs("x-access-token", req.repo.token);
|
|
42903
43183
|
try {
|
|
42904
43184
|
mkdirSync7(workDir, { recursive: true });
|
|
42905
|
-
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 });
|
|
42906
43186
|
await execFileAsync2("git", ["-C", workDir, "config", "user.name", "4PM FAQ Bot"], { env: gitEnv });
|
|
42907
43187
|
await execFileAsync2("git", ["-C", workDir, "config", "user.email", "faq-bot@4pm.app"], { env: gitEnv });
|
|
42908
43188
|
await execFileAsync2("git", ["-C", workDir, "checkout", "-b", req.headBranch], { env: gitEnv });
|
|
42909
|
-
const usage = await runClaudeWithFailover2(buildPrompt4(req.tickets), workDir, ai2);
|
|
43189
|
+
const { usage, text } = await runClaudeWithFailover2(buildPrompt4(req.tickets, req.customPrompt), workDir, ai2);
|
|
43190
|
+
const output = text.slice(0, MAX_OUTPUT_CHARS);
|
|
43191
|
+
const perTicket = parsePerTicket(text);
|
|
42910
43192
|
await execFileAsync2("git", ["-C", workDir, "add", "-A"], { env: gitEnv });
|
|
42911
43193
|
const staged = await execFileAsync2("git", ["-C", workDir, "diff", "--cached", "--name-only"], { env: gitEnv });
|
|
42912
|
-
if (!staged.stdout.trim())
|
|
43194
|
+
if (!staged.stdout.trim()) {
|
|
43195
|
+
return { prUrl: "", output, perTicket, tokens: usage.tokens || estimateTokens2(text), finishedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
43196
|
+
}
|
|
42913
43197
|
await execFileAsync2("git", ["-C", workDir, "commit", "-m", "docs(faq): synthesize from support tickets (4PM)"], { env: gitEnv });
|
|
42914
|
-
await execFileAsync2("git", ["-C", workDir, "push", "-u", "origin", req.headBranch], { env: gitEnv });
|
|
43198
|
+
await execFileAsync2("git", [...auth, "-C", workDir, "push", "-u", "origin", req.headBranch], { env: gitEnv });
|
|
42915
43199
|
let prUrl = "";
|
|
42916
43200
|
try {
|
|
42917
43201
|
const pr2 = await execFileAsync2(
|
|
@@ -42927,6 +43211,8 @@ async function runFaqCompose(req, ai2) {
|
|
|
42927
43211
|
return {
|
|
42928
43212
|
prUrl,
|
|
42929
43213
|
branch: req.headBranch,
|
|
43214
|
+
output,
|
|
43215
|
+
perTicket,
|
|
42930
43216
|
tokens: u2.tokens,
|
|
42931
43217
|
...usage.tokens > 0 ? { tokensBreakdown: { input: usage.input, output: usage.output, cacheRead: usage.cacheRead, cacheCreation: usage.cacheCreation } } : {},
|
|
42932
43218
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -43491,7 +43777,7 @@ async function removePackage(root, slug) {
|
|
|
43491
43777
|
|
|
43492
43778
|
// src/core/graph.ts
|
|
43493
43779
|
import { readFile as readFile14, readdir as readdir6 } from "fs/promises";
|
|
43494
|
-
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";
|
|
43495
43781
|
var MAX_FILES = 2e3;
|
|
43496
43782
|
var MAX_NODES = 3e3;
|
|
43497
43783
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "out", "coverage", ".turbo"]);
|
|
@@ -43546,7 +43832,7 @@ async function buildDocs(root) {
|
|
|
43546
43832
|
for (const m2 of text.matchAll(/\[[^\]]*\]\(([^)]+)\)/g)) {
|
|
43547
43833
|
const href = (m2[1] ?? "").split("#")[0].trim();
|
|
43548
43834
|
if (!href || !href.endsWith(".md") || href.startsWith("http")) continue;
|
|
43549
|
-
const target = relative2(root,
|
|
43835
|
+
const target = relative2(root, resolve10(dirname10(f2), href)).split("\\").join("/");
|
|
43550
43836
|
if (ids.has(target) && target !== rel) {
|
|
43551
43837
|
const key = `${rel}->${target}`;
|
|
43552
43838
|
if (!seen.has(key)) {
|
|
@@ -44178,6 +44464,7 @@ var WsClient = class {
|
|
|
44178
44464
|
*/
|
|
44179
44465
|
aiMemory = "";
|
|
44180
44466
|
sessionIdByKey = /* @__PURE__ */ new Map();
|
|
44467
|
+
sessionMetaByKey = /* @__PURE__ */ new Map();
|
|
44181
44468
|
stopped = false;
|
|
44182
44469
|
/** Resolver to wake the backoff sleep early (set while waiting) — for /reconnect. */
|
|
44183
44470
|
wakeReconnect = null;
|
|
@@ -44271,6 +44558,7 @@ var WsClient = class {
|
|
|
44271
44558
|
serverUrl: this.context.credential.serverUrl,
|
|
44272
44559
|
startedAtMs: this.startedAtMs,
|
|
44273
44560
|
sessionIdByKey: this.sessionIdByKey,
|
|
44561
|
+
sessionMetaByKey: this.sessionMetaByKey,
|
|
44274
44562
|
get machineUsername() {
|
|
44275
44563
|
return self.machineUsername;
|
|
44276
44564
|
},
|
|
@@ -44383,14 +44671,14 @@ var WsClient = class {
|
|
|
44383
44671
|
*/
|
|
44384
44672
|
awaitConnected(timeoutMs) {
|
|
44385
44673
|
if (this.sessionKey && this.bus.status === "connected") return Promise.resolve(true);
|
|
44386
|
-
return new Promise((
|
|
44674
|
+
return new Promise((resolve11) => {
|
|
44387
44675
|
const waiter = () => {
|
|
44388
44676
|
clearTimeout(timer);
|
|
44389
|
-
|
|
44677
|
+
resolve11(true);
|
|
44390
44678
|
};
|
|
44391
44679
|
const timer = setTimeout(() => {
|
|
44392
44680
|
this.connectWaiters = this.connectWaiters.filter((w2) => w2 !== waiter);
|
|
44393
|
-
|
|
44681
|
+
resolve11(false);
|
|
44394
44682
|
}, timeoutMs);
|
|
44395
44683
|
this.connectWaiters.push(waiter);
|
|
44396
44684
|
});
|
|
@@ -44417,16 +44705,16 @@ var WsClient = class {
|
|
|
44417
44705
|
this.forceReconnect = false;
|
|
44418
44706
|
return Promise.resolve();
|
|
44419
44707
|
}
|
|
44420
|
-
return new Promise((
|
|
44708
|
+
return new Promise((resolve11) => {
|
|
44421
44709
|
const timer = setTimeout(() => {
|
|
44422
44710
|
this.wakeReconnect = null;
|
|
44423
|
-
|
|
44711
|
+
resolve11();
|
|
44424
44712
|
}, ms2);
|
|
44425
44713
|
this.wakeReconnect = () => {
|
|
44426
44714
|
clearTimeout(timer);
|
|
44427
44715
|
this.wakeReconnect = null;
|
|
44428
44716
|
this.forceReconnect = false;
|
|
44429
|
-
|
|
44717
|
+
resolve11();
|
|
44430
44718
|
};
|
|
44431
44719
|
});
|
|
44432
44720
|
}
|
|
@@ -44480,7 +44768,7 @@ var WsClient = class {
|
|
|
44480
44768
|
* One WS session: token → connect → handshake → handle messages until close.
|
|
44481
44769
|
*/
|
|
44482
44770
|
connectOnce() {
|
|
44483
|
-
return new Promise((
|
|
44771
|
+
return new Promise((resolve11, reject) => {
|
|
44484
44772
|
void (async () => {
|
|
44485
44773
|
this.sessionEstablished = false;
|
|
44486
44774
|
this.bus.setStatus("connecting");
|
|
@@ -44576,7 +44864,7 @@ var WsClient = class {
|
|
|
44576
44864
|
if (code === CLOSE_DRAINING) {
|
|
44577
44865
|
this.bus.log(t("error.serverDraining"));
|
|
44578
44866
|
}
|
|
44579
|
-
|
|
44867
|
+
resolve11();
|
|
44580
44868
|
});
|
|
44581
44869
|
socket.on("error", (err) => {
|
|
44582
44870
|
this.clearHeartbeat();
|
|
@@ -44688,7 +44976,7 @@ var WsClient = class {
|
|
|
44688
44976
|
}
|
|
44689
44977
|
const { payload, nonce } = encryptPayload(this.sessionKey, data);
|
|
44690
44978
|
const envelope = {
|
|
44691
|
-
id:
|
|
44979
|
+
id: randomUUID3(),
|
|
44692
44980
|
replyTo,
|
|
44693
44981
|
channel,
|
|
44694
44982
|
wsToken: this.wsToken,
|
|
@@ -44702,14 +44990,14 @@ var WsClient = class {
|
|
|
44702
44990
|
* Used for quota.check before spawning an AI cli (ADR-0020).
|
|
44703
44991
|
*/
|
|
44704
44992
|
request(channel, data) {
|
|
44705
|
-
return new Promise((
|
|
44993
|
+
return new Promise((resolve11, reject) => {
|
|
44706
44994
|
if (!this.socket || !this.sessionKey) {
|
|
44707
44995
|
reject(new Error("Not connected to the server."));
|
|
44708
44996
|
return;
|
|
44709
44997
|
}
|
|
44710
44998
|
const { payload, nonce } = encryptPayload(this.sessionKey, data);
|
|
44711
44999
|
const envelope = {
|
|
44712
|
-
id:
|
|
45000
|
+
id: randomUUID3(),
|
|
44713
45001
|
replyTo: null,
|
|
44714
45002
|
channel,
|
|
44715
45003
|
wsToken: this.wsToken,
|
|
@@ -44722,7 +45010,7 @@ var WsClient = class {
|
|
|
44722
45010
|
}, 15e3);
|
|
44723
45011
|
timer.unref?.();
|
|
44724
45012
|
this.pending.set(envelope.id, {
|
|
44725
|
-
resolve: (value) =>
|
|
45013
|
+
resolve: (value) => resolve11(value),
|
|
44726
45014
|
timer
|
|
44727
45015
|
});
|
|
44728
45016
|
this.socket.send(JSON.stringify(envelope));
|
|
@@ -45098,7 +45386,7 @@ function releaseInstanceLock(profileDir2) {
|
|
|
45098
45386
|
|
|
45099
45387
|
// src/core/session-bus.ts
|
|
45100
45388
|
import { EventEmitter } from "events";
|
|
45101
|
-
import { randomUUID as
|
|
45389
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
45102
45390
|
var REPLAY_CAP = 500;
|
|
45103
45391
|
var SessionBus = class {
|
|
45104
45392
|
emitter = new EventEmitter();
|
|
@@ -45129,7 +45417,7 @@ var SessionBus = class {
|
|
|
45129
45417
|
/** Producer → consumers: push one transcript entry. Returns its id (for `updateEntry`). */
|
|
45130
45418
|
push(input) {
|
|
45131
45419
|
const entry = {
|
|
45132
|
-
id:
|
|
45420
|
+
id: randomUUID4(),
|
|
45133
45421
|
level: "info",
|
|
45134
45422
|
...input,
|
|
45135
45423
|
ts: Date.now()
|
|
@@ -50833,8 +51121,8 @@ var Ink = class {
|
|
|
50833
51121
|
}
|
|
50834
51122
|
}
|
|
50835
51123
|
async waitUntilExit() {
|
|
50836
|
-
this.exitPromise ||= new Promise((
|
|
50837
|
-
this.resolveExitPromise =
|
|
51124
|
+
this.exitPromise ||= new Promise((resolve11, reject) => {
|
|
51125
|
+
this.resolveExitPromise = resolve11;
|
|
50838
51126
|
this.rejectExitPromise = reject;
|
|
50839
51127
|
});
|
|
50840
51128
|
return this.exitPromise;
|
|
@@ -51887,7 +52175,7 @@ async function runStart(profileDir2, profileName) {
|
|
|
51887
52175
|
logger.error("ws.run.crash", { message: e.message, stack: e.stack });
|
|
51888
52176
|
if (client.isStopped) return;
|
|
51889
52177
|
bus.log(t("start.recovered", { message: e.message }), "error");
|
|
51890
|
-
await new Promise((
|
|
52178
|
+
await new Promise((resolve11) => setTimeout(resolve11, 1e3));
|
|
51891
52179
|
}
|
|
51892
52180
|
}
|
|
51893
52181
|
};
|
|
@@ -51933,7 +52221,7 @@ import { join as join38 } from "path";
|
|
|
51933
52221
|
// src/core/control-client.ts
|
|
51934
52222
|
import { connect as connect2 } from "net";
|
|
51935
52223
|
function connectControl(socketPath, bus, token) {
|
|
51936
|
-
return new Promise((
|
|
52224
|
+
return new Promise((resolve11, reject) => {
|
|
51937
52225
|
const socket = connect2(socketPath);
|
|
51938
52226
|
socket.setEncoding("utf8");
|
|
51939
52227
|
let ready = false;
|
|
@@ -51986,7 +52274,7 @@ function connectControl(socketPath, bus, token) {
|
|
|
51986
52274
|
bus.setTokens(frame.tokens);
|
|
51987
52275
|
for (const entry of frame.transcript) bus.pushEntry(entry);
|
|
51988
52276
|
ready = true;
|
|
51989
|
-
|
|
52277
|
+
resolve11({ info: frame.info, close });
|
|
51990
52278
|
break;
|
|
51991
52279
|
case "transcript":
|
|
51992
52280
|
bus.pushEntry(frame.entry);
|
|
@@ -52074,7 +52362,7 @@ async function runAutoRun(profileDir2, profileName) {
|
|
|
52074
52362
|
initI18n(readProfileConfig(profileDir2).locale);
|
|
52075
52363
|
const socketPath = join39(profileDir2, CONTROL_SOCKET_FILE);
|
|
52076
52364
|
const token = readControlToken(profileDir2);
|
|
52077
|
-
const code = await new Promise((
|
|
52365
|
+
const code = await new Promise((resolve11) => {
|
|
52078
52366
|
let settled = false;
|
|
52079
52367
|
const done = (c) => {
|
|
52080
52368
|
if (settled) return;
|
|
@@ -52084,7 +52372,7 @@ async function runAutoRun(profileDir2, profileName) {
|
|
|
52084
52372
|
socket.destroy();
|
|
52085
52373
|
} catch {
|
|
52086
52374
|
}
|
|
52087
|
-
|
|
52375
|
+
resolve11(c);
|
|
52088
52376
|
};
|
|
52089
52377
|
const socket = connect3(socketPath);
|
|
52090
52378
|
socket.setEncoding("utf8");
|
|
@@ -52132,16 +52420,16 @@ import { spawn as spawn12 } from "child_process";
|
|
|
52132
52420
|
import { mkdirSync as mkdirSync9 } from "fs";
|
|
52133
52421
|
import { basename as basename8 } from "path";
|
|
52134
52422
|
function runProviderLogin(cmd, envVar, dir2) {
|
|
52135
|
-
return new Promise((
|
|
52423
|
+
return new Promise((resolve11) => {
|
|
52136
52424
|
const child = spawn12(cmd, [], {
|
|
52137
52425
|
stdio: "inherit",
|
|
52138
52426
|
env: { ...process.env, [envVar]: dir2 }
|
|
52139
52427
|
});
|
|
52140
52428
|
child.on("error", (err) => {
|
|
52141
52429
|
console.error(t("aiLogin.launchFailed", { cmd, message: err.message }));
|
|
52142
|
-
|
|
52430
|
+
resolve11(1);
|
|
52143
52431
|
});
|
|
52144
|
-
child.on("exit", (code) =>
|
|
52432
|
+
child.on("exit", (code) => resolve11(code ?? 0));
|
|
52145
52433
|
});
|
|
52146
52434
|
}
|
|
52147
52435
|
async function runAiLogin(profileDir2, profileName, aiFilter) {
|
|
@@ -52281,10 +52569,10 @@ function Picker({
|
|
|
52281
52569
|
] });
|
|
52282
52570
|
}
|
|
52283
52571
|
function selectFromList(items, title) {
|
|
52284
|
-
return new Promise((
|
|
52572
|
+
return new Promise((resolve11) => {
|
|
52285
52573
|
let chosen = null;
|
|
52286
52574
|
const app = render_default(/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Picker, { items, title, onDone: (v2) => chosen = v2 }));
|
|
52287
|
-
void app.waitUntilExit().then(() =>
|
|
52575
|
+
void app.waitUntilExit().then(() => resolve11(chosen));
|
|
52288
52576
|
});
|
|
52289
52577
|
}
|
|
52290
52578
|
|