@4pm/cli 1.23.0 → 1.25.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 +1001 -435
- 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(resolve12, 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, resolve12, reject);
|
|
5722
5722
|
} else {
|
|
5723
|
-
|
|
5723
|
+
resolve12(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(resolve12, reject) {
|
|
5754
5754
|
if (ReactCurrentActQueue.current === null) {
|
|
5755
5755
|
ReactCurrentActQueue.current = [];
|
|
5756
|
-
recursivelyFlushAsyncActWork(returnValue,
|
|
5756
|
+
recursivelyFlushAsyncActWork(returnValue, resolve12, reject);
|
|
5757
5757
|
} else {
|
|
5758
|
-
|
|
5758
|
+
resolve12(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(resolve12, reject) {
|
|
5766
|
+
resolve12(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, resolve12, 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
|
+
resolve12(returnValue);
|
|
5792
5792
|
} else {
|
|
5793
|
-
recursivelyFlushAsyncActWork(returnValue,
|
|
5793
|
+
recursivelyFlushAsyncActWork(returnValue, resolve12, reject);
|
|
5794
5794
|
}
|
|
5795
5795
|
});
|
|
5796
5796
|
} catch (error2) {
|
|
5797
5797
|
reject(error2);
|
|
5798
5798
|
}
|
|
5799
5799
|
} else {
|
|
5800
|
-
|
|
5800
|
+
resolve12(returnValue);
|
|
5801
5801
|
}
|
|
5802
5802
|
}
|
|
5803
5803
|
}
|
|
@@ -16203,9 +16203,9 @@ var require_react_reconciler_development = __commonJS({
|
|
|
16203
16203
|
var node = row;
|
|
16204
16204
|
while (node !== null) {
|
|
16205
16205
|
if (node.tag === SuspenseComponent) {
|
|
16206
|
-
var
|
|
16207
|
-
if (
|
|
16208
|
-
var dehydrated =
|
|
16206
|
+
var state2 = node.memoizedState;
|
|
16207
|
+
if (state2 !== null) {
|
|
16208
|
+
var dehydrated = state2.dehydrated;
|
|
16209
16209
|
if (dehydrated === null || isSuspenseInstancePending(dehydrated) || isSuspenseInstanceFallback(dehydrated)) {
|
|
16210
16210
|
return node;
|
|
16211
16211
|
}
|
|
@@ -16549,8 +16549,8 @@ var require_react_reconciler_development = __commonJS({
|
|
|
16549
16549
|
stores: null
|
|
16550
16550
|
};
|
|
16551
16551
|
}
|
|
16552
|
-
function basicStateReducer(
|
|
16553
|
-
return typeof action === "function" ? action(
|
|
16552
|
+
function basicStateReducer(state2, action) {
|
|
16553
|
+
return typeof action === "function" ? action(state2) : action;
|
|
16554
16554
|
}
|
|
16555
16555
|
function mountReducer(reducer, initialArg, init) {
|
|
16556
16556
|
var hook = mountWorkInProgressHook();
|
|
@@ -18537,10 +18537,10 @@ var require_react_reconciler_development = __commonJS({
|
|
|
18537
18537
|
}
|
|
18538
18538
|
}
|
|
18539
18539
|
}
|
|
18540
|
-
var
|
|
18540
|
+
var state2 = workInProgress2.memoizedState = instance.state !== null && instance.state !== void 0 ? instance.state : null;
|
|
18541
18541
|
adoptClassInstance(workInProgress2, instance);
|
|
18542
18542
|
{
|
|
18543
|
-
if (typeof ctor.getDerivedStateFromProps === "function" &&
|
|
18543
|
+
if (typeof ctor.getDerivedStateFromProps === "function" && state2 === null) {
|
|
18544
18544
|
var componentName = getComponentNameFromType(ctor) || "Component";
|
|
18545
18545
|
if (!didWarnAboutUninitializedState.has(componentName)) {
|
|
18546
18546
|
didWarnAboutUninitializedState.add(componentName);
|
|
@@ -19463,8 +19463,8 @@ var require_react_reconciler_development = __commonJS({
|
|
|
19463
19463
|
var _instance = workInProgress2.stateNode;
|
|
19464
19464
|
var ctor = workInProgress2.type;
|
|
19465
19465
|
var tempInstance = new ctor(workInProgress2.memoizedProps, _instance.context);
|
|
19466
|
-
var
|
|
19467
|
-
_instance.updater.enqueueSetState(_instance,
|
|
19466
|
+
var state2 = tempInstance.state;
|
|
19467
|
+
_instance.updater.enqueueSetState(_instance, state2, null);
|
|
19468
19468
|
break;
|
|
19469
19469
|
}
|
|
19470
19470
|
case true: {
|
|
@@ -20239,8 +20239,8 @@ var require_react_reconciler_development = __commonJS({
|
|
|
20239
20239
|
var node = firstChild;
|
|
20240
20240
|
while (node !== null) {
|
|
20241
20241
|
if (node.tag === SuspenseComponent) {
|
|
20242
|
-
var
|
|
20243
|
-
if (
|
|
20242
|
+
var state2 = node.memoizedState;
|
|
20243
|
+
if (state2 !== null) {
|
|
20244
20244
|
scheduleSuspenseWorkOnFiber(node, renderLanes2, workInProgress2);
|
|
20245
20245
|
}
|
|
20246
20246
|
} else if (node.tag === SuspenseListComponent) {
|
|
@@ -20669,9 +20669,9 @@ var require_react_reconciler_development = __commonJS({
|
|
|
20669
20669
|
}
|
|
20670
20670
|
break;
|
|
20671
20671
|
case SuspenseComponent: {
|
|
20672
|
-
var
|
|
20673
|
-
if (
|
|
20674
|
-
if (
|
|
20672
|
+
var state2 = workInProgress2.memoizedState;
|
|
20673
|
+
if (state2 !== null) {
|
|
20674
|
+
if (state2.dehydrated !== null) {
|
|
20675
20675
|
pushSuspenseContext(workInProgress2, setDefaultShallowSuspenseContext(suspenseStackCursor.current));
|
|
20676
20676
|
workInProgress2.flags |= DidCapture;
|
|
20677
20677
|
return null;
|
|
@@ -32753,9 +32753,9 @@ async function checkAndUpdate(serverUrl, autoUpdate) {
|
|
|
32753
32753
|
|
|
32754
32754
|
// src/core/ws-client.ts
|
|
32755
32755
|
init_wrapper();
|
|
32756
|
-
import { randomUUID as
|
|
32756
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
32757
32757
|
import { basename as basename7, join as join32 } from "path";
|
|
32758
|
-
import { existsSync as
|
|
32758
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync9 } from "fs";
|
|
32759
32759
|
|
|
32760
32760
|
// vendor/@4pm/utils/helpers.ts
|
|
32761
32761
|
function backoffJitterMs(attempt, baseMs = 500, maxMs = 1e4) {
|
|
@@ -37017,6 +37017,39 @@ var CLI_SLASH_COMMANDS = [
|
|
|
37017
37017
|
];
|
|
37018
37018
|
var CLI_SLASH_COMMAND_NAMES = CLI_SLASH_COMMANDS.map((c) => c.name);
|
|
37019
37019
|
|
|
37020
|
+
// vendor/@4pm/dto/git-credential.ts
|
|
37021
|
+
var hostSchema = external_exports.string().trim().toLowerCase().regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+(:\d{1,5})?$/, "invalid host");
|
|
37022
|
+
var gitCredentialRepoInputSchema = external_exports.object({
|
|
37023
|
+
url: external_exports.string().trim().max(500),
|
|
37024
|
+
subdir: external_exports.string().trim().max(120).optional().default(""),
|
|
37025
|
+
primary: external_exports.boolean().optional().default(false)
|
|
37026
|
+
});
|
|
37027
|
+
var testGitCredentialRequestSchema = external_exports.object({
|
|
37028
|
+
repos: external_exports.array(gitCredentialRepoInputSchema).max(50).optional()
|
|
37029
|
+
});
|
|
37030
|
+
var putGitCredentialRequestSchema = external_exports.object({
|
|
37031
|
+
appId: external_exports.string().trim().regex(/^\d{1,20}$/, "App ID must be numeric"),
|
|
37032
|
+
/** PEM; omit to keep the stored key (required on first save — enforced server-side). */
|
|
37033
|
+
privateKey: external_exports.string().trim().max(16384).optional(),
|
|
37034
|
+
enterprise: external_exports.boolean().default(false),
|
|
37035
|
+
host: hostSchema.optional(),
|
|
37036
|
+
apiBaseUrl: external_exports.string().trim().url().startsWith("https://").max(500).nullable().optional(),
|
|
37037
|
+
caCert: external_exports.string().trim().max(32768).nullable().optional(),
|
|
37038
|
+
installationOverrides: external_exports.array(external_exports.object({ url: external_exports.string().trim().max(500), installationId: external_exports.string().trim().regex(/^\d{1,20}$/).nullable() })).max(50).optional(),
|
|
37039
|
+
/** The wizard's in-progress repos (else the project's spec / draft repos are used). */
|
|
37040
|
+
repos: external_exports.array(gitCredentialRepoInputSchema).max(50).optional()
|
|
37041
|
+
}).superRefine((v2, ctx) => {
|
|
37042
|
+
if (v2.enterprise && !v2.host) {
|
|
37043
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["host"], message: "host is required for GitHub Enterprise" });
|
|
37044
|
+
}
|
|
37045
|
+
});
|
|
37046
|
+
function githubHostKind(host) {
|
|
37047
|
+
const h = host.toLowerCase();
|
|
37048
|
+
if (h === "github.com") return "github";
|
|
37049
|
+
if (h.endsWith(".ghe.com")) return "ghe-cloud";
|
|
37050
|
+
return "ghes";
|
|
37051
|
+
}
|
|
37052
|
+
|
|
37020
37053
|
// vendor/@4pm/validation/schemas.ts
|
|
37021
37054
|
var passwordSchema = external_exports.string().min(8, "password: at least 8 characters").max(32, "password: at most 32 characters").regex(/[a-zA-Z]/, "password: needs at least 1 letter").regex(/[0-9]/, "password: needs at least 1 digit");
|
|
37022
37055
|
var usernameSchema = external_exports.string().min(3).max(32).regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/, "username: invalid characters");
|
|
@@ -37531,116 +37564,12 @@ var userCardsQuerySchema = external_exports.object({
|
|
|
37531
37564
|
// src/core/scaffold.ts
|
|
37532
37565
|
import { cp, mkdir, mkdtemp, rm, writeFile } from "fs/promises";
|
|
37533
37566
|
import { existsSync as existsSync4 } from "fs";
|
|
37534
|
-
import { execFile
|
|
37567
|
+
import { execFile } from "child_process";
|
|
37535
37568
|
import { tmpdir as tmpdir2 } from "os";
|
|
37536
|
-
import { dirname as dirname3, join as join6, resolve
|
|
37569
|
+
import { dirname as dirname3, join as join6, resolve } from "path";
|
|
37537
37570
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
37538
|
-
import { promisify as promisify2 } from "util";
|
|
37539
|
-
|
|
37540
|
-
// src/core/ai-assist.ts
|
|
37541
|
-
import { execFile } from "child_process";
|
|
37542
37571
|
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
|
|
37573
37572
|
var run = promisify(execFile);
|
|
37574
|
-
function aiCli() {
|
|
37575
|
-
return process.env.AI_CLI || "claude";
|
|
37576
|
-
}
|
|
37577
|
-
function aiArgs(cli, prompt) {
|
|
37578
|
-
if (cli === "claude") return ["-p", prompt, "--output-format", "json", ...denySettingsArgs(cli, [process.env.CLAUDE_CONFIG_DIR])];
|
|
37579
|
-
if (cli === "codex") return ["exec", "--json", prompt];
|
|
37580
|
-
return ["-p", prompt];
|
|
37581
|
-
}
|
|
37582
|
-
function estimateTokens(prompt, output) {
|
|
37583
|
-
return Math.max(1, Math.ceil((prompt.length + output.length) / 4));
|
|
37584
|
-
}
|
|
37585
|
-
function parseAi(cli, stdout, prompt) {
|
|
37586
|
-
if (cli === "claude") {
|
|
37587
|
-
try {
|
|
37588
|
-
const j2 = JSON.parse(stdout);
|
|
37589
|
-
const text2 = (j2.result ?? "").trim();
|
|
37590
|
-
const tokens = (j2.usage?.input_tokens ?? 0) + (j2.usage?.output_tokens ?? 0);
|
|
37591
|
-
if (text2 || tokens) return { text: text2, tokens: tokens || estimateTokens(prompt, text2) };
|
|
37592
|
-
} catch {
|
|
37593
|
-
}
|
|
37594
|
-
}
|
|
37595
|
-
if (cli === "codex") {
|
|
37596
|
-
try {
|
|
37597
|
-
let text2 = "";
|
|
37598
|
-
let tokens = 0;
|
|
37599
|
-
for (const line of stdout.split("\n").filter(Boolean)) {
|
|
37600
|
-
const obj = JSON.parse(line);
|
|
37601
|
-
if (obj.text) text2 = obj.text;
|
|
37602
|
-
else if (obj.message) text2 = obj.message;
|
|
37603
|
-
if (obj.usage) {
|
|
37604
|
-
tokens = obj.usage.total_tokens ?? (obj.usage.input_tokens ?? 0) + (obj.usage.output_tokens ?? 0);
|
|
37605
|
-
}
|
|
37606
|
-
}
|
|
37607
|
-
if (text2 || tokens) return { text: text2.trim(), tokens: tokens || estimateTokens(prompt, text2) };
|
|
37608
|
-
} catch {
|
|
37609
|
-
}
|
|
37610
|
-
}
|
|
37611
|
-
const text = stdout.trim();
|
|
37612
|
-
return { text, tokens: estimateTokens(prompt, text) };
|
|
37613
|
-
}
|
|
37614
|
-
async function runAi(prompt) {
|
|
37615
|
-
const cli = aiCli();
|
|
37616
|
-
const { stdout } = await run(cli, aiArgs(cli, prompt), {
|
|
37617
|
-
timeout: 12e4,
|
|
37618
|
-
maxBuffer: 8 * 1024 * 1024
|
|
37619
|
-
});
|
|
37620
|
-
return parseAi(cli, stdout, prompt);
|
|
37621
|
-
}
|
|
37622
|
-
async function aiGenerate(prompt) {
|
|
37623
|
-
const { text } = await runAi(prompt);
|
|
37624
|
-
return text;
|
|
37625
|
-
}
|
|
37626
|
-
async function aiOutboundReview(input, allowedRepos, projectPath) {
|
|
37627
|
-
const prompt = `You are a security reviewer for an autonomous coding agent. Decide if the following INPUT is SAFE to run. It is UNSAFE if it: (1) asks the AI using secret VALUES (api keys, env values, passwords); (2) would affect other environments (only the sandbox project is allowed, no ssh/deploy/prod); (3) edits outside the project folder "${projectPath}"; (4) commits/pushes to a repo not in this allowlist: ${allowedRepos.length ? allowedRepos.join(", ") : "(none declared)"}. Reply with ONLY JSON: {"ok": <true|false>, "reasons": ["secret"|"environment"|"path"|"repo", ...]}. INPUT:
|
|
37628
|
-
${input}`;
|
|
37629
|
-
const { text, tokens } = await runAi(prompt);
|
|
37630
|
-
const start = text.indexOf("{");
|
|
37631
|
-
const end = text.lastIndexOf("}");
|
|
37632
|
-
if (start < 0 || end <= start) return { ok: false, reasons: ["ai_parse"], tokens };
|
|
37633
|
-
try {
|
|
37634
|
-
const j2 = JSON.parse(text.slice(start, end + 1));
|
|
37635
|
-
const reasons = Array.isArray(j2.reasons) ? j2.reasons.filter((r) => typeof r === "string") : [];
|
|
37636
|
-
return { ok: j2.ok === true && reasons.length === 0, reasons, tokens };
|
|
37637
|
-
} catch {
|
|
37638
|
-
return { ok: false, reasons: ["ai_parse"], tokens };
|
|
37639
|
-
}
|
|
37640
|
-
}
|
|
37641
|
-
|
|
37642
|
-
// src/core/scaffold.ts
|
|
37643
|
-
var run2 = promisify2(execFile2);
|
|
37644
37573
|
function reposOf(spec) {
|
|
37645
37574
|
const repos = spec?.repos;
|
|
37646
37575
|
return Array.isArray(repos) ? repos : [];
|
|
@@ -37662,7 +37591,7 @@ async function attachSubmodules(root, submodules, emit, createMissingBranch = fa
|
|
|
37662
37591
|
if (existsSync4(join6(root, dir2, ".git")) || existsSync4(join6(root, ".git", "modules", dir2))) {
|
|
37663
37592
|
emit("submodule", `Submodule ${dir2} already present \u2014 updating\u2026`);
|
|
37664
37593
|
try {
|
|
37665
|
-
await
|
|
37594
|
+
await run("git", ["submodule", "update", "--init", "--", dir2], { cwd: root, timeout: 12e4 });
|
|
37666
37595
|
} catch {
|
|
37667
37596
|
}
|
|
37668
37597
|
continue;
|
|
@@ -37676,11 +37605,11 @@ async function attachSubmodules(root, submodules, emit, createMissingBranch = fa
|
|
|
37676
37605
|
const b2 = (sub.branch ?? "").trim();
|
|
37677
37606
|
if (b2 && createMissingBranch && !await ensureRemoteBranch(sub.url, b2, emit)) {
|
|
37678
37607
|
try {
|
|
37679
|
-
await
|
|
37680
|
-
await
|
|
37608
|
+
await run("git", ["submodule", "add", sub.url, dir2], { cwd: root, timeout: 12e4 });
|
|
37609
|
+
await run("git", ["config", "-f", ".gitmodules", `submodule.${dir2}.branch`, b2], { cwd: root, timeout: 3e4 });
|
|
37681
37610
|
try {
|
|
37682
|
-
await
|
|
37683
|
-
await
|
|
37611
|
+
await run("git", ["checkout", "-b", b2], { cwd: join6(root, dir2), timeout: 3e4 });
|
|
37612
|
+
await run("git", ["push", "-u", "origin", b2], { cwd: join6(root, dir2), timeout: 12e4 });
|
|
37684
37613
|
} catch (pushErr) {
|
|
37685
37614
|
const pmsg = pushErr instanceof Error ? pushErr.message : String(pushErr);
|
|
37686
37615
|
emit("git-branch-push-failed", `Submodule ${dir2}: branch "${b2}" created locally but the push failed: ${pmsg}`);
|
|
@@ -37695,13 +37624,13 @@ async function attachSubmodules(root, submodules, emit, createMissingBranch = fa
|
|
|
37695
37624
|
}
|
|
37696
37625
|
}
|
|
37697
37626
|
try {
|
|
37698
|
-
await
|
|
37627
|
+
await run("git", ["submodule", "add", ...b2 ? ["-b", b2] : [], sub.url, dir2], { cwd: root, timeout: 12e4 });
|
|
37699
37628
|
added++;
|
|
37700
37629
|
} catch (err) {
|
|
37701
37630
|
const msg = err instanceof Error ? err.message : String(err);
|
|
37702
37631
|
if (/already exists|already registered|in the index/i.test(msg)) {
|
|
37703
37632
|
try {
|
|
37704
|
-
await
|
|
37633
|
+
await run("git", ["submodule", "update", "--init", "--", dir2], { cwd: root, timeout: 12e4 });
|
|
37705
37634
|
added++;
|
|
37706
37635
|
} catch {
|
|
37707
37636
|
}
|
|
@@ -37720,18 +37649,18 @@ async function commitAndPushSubmodules(root, submodules, emit) {
|
|
|
37720
37649
|
];
|
|
37721
37650
|
if (addPaths.length === 0) return;
|
|
37722
37651
|
emit("submodule", "Committing .gitmodules\u2026");
|
|
37723
|
-
await
|
|
37724
|
-
const staged = (await
|
|
37652
|
+
await run("git", ["add", ...addPaths], { cwd: root, timeout: 6e4 });
|
|
37653
|
+
const staged = (await run("git", ["diff", "--cached", "--name-only"], { cwd: root, timeout: 3e4 })).stdout.trim();
|
|
37725
37654
|
if (!staged) return;
|
|
37726
37655
|
const commitArgs = ["commit", "-m", "chore: add git submodules (4PM)"];
|
|
37727
37656
|
try {
|
|
37728
|
-
await
|
|
37657
|
+
await run("git", commitArgs, { cwd: root, timeout: 6e4 });
|
|
37729
37658
|
} catch {
|
|
37730
|
-
await
|
|
37659
|
+
await run("git", ["-c", "user.name=4PM", "-c", "user.email=noreply@4pm.app", ...commitArgs], { cwd: root, timeout: 6e4 });
|
|
37731
37660
|
}
|
|
37732
37661
|
emit("submodule", "Pushing .gitmodules to the primary remote\u2026");
|
|
37733
37662
|
try {
|
|
37734
|
-
await
|
|
37663
|
+
await run("git", ["push"], { cwd: root, timeout: 12e4 });
|
|
37735
37664
|
} catch (err) {
|
|
37736
37665
|
const msg = err instanceof Error ? err.message : String(err);
|
|
37737
37666
|
emit("submodule-push-failed", `Submodules committed locally but the push failed: ${msg}`);
|
|
@@ -37744,22 +37673,22 @@ function providerSlug(url) {
|
|
|
37744
37673
|
}
|
|
37745
37674
|
async function commitAndOpenPr(root, repo, emit) {
|
|
37746
37675
|
emit("commit", "Committing the scaffolded project\u2026");
|
|
37747
|
-
await
|
|
37748
|
-
const staged = (await
|
|
37676
|
+
await run("git", ["add", "-A"], { cwd: root, timeout: 6e4 });
|
|
37677
|
+
const staged = (await run("git", ["diff", "--cached", "--name-only"], { cwd: root, timeout: 3e4 })).stdout.trim();
|
|
37749
37678
|
if (!staged) {
|
|
37750
37679
|
emit("commit", "Nothing to commit \u2014 skipping the pull request.");
|
|
37751
37680
|
return;
|
|
37752
37681
|
}
|
|
37753
37682
|
const commitArgs = ["commit", "-m", "chore: scaffold project (4PM)"];
|
|
37754
37683
|
try {
|
|
37755
|
-
await
|
|
37684
|
+
await run("git", commitArgs, { cwd: root, timeout: 6e4 });
|
|
37756
37685
|
} catch {
|
|
37757
|
-
await
|
|
37686
|
+
await run("git", ["-c", "user.name=4PM", "-c", "user.email=noreply@4pm.app", ...commitArgs], { cwd: root, timeout: 6e4 });
|
|
37758
37687
|
}
|
|
37759
37688
|
const branch = (repo.branch ?? "").trim();
|
|
37760
37689
|
emit("push", "Pushing the scaffold branch\u2026");
|
|
37761
37690
|
try {
|
|
37762
|
-
await
|
|
37691
|
+
await run("git", ["push", "-u", "origin", ...branch ? [branch] : ["HEAD"]], { cwd: root, timeout: 12e4 });
|
|
37763
37692
|
} catch (err) {
|
|
37764
37693
|
const msg = err instanceof Error ? err.message : String(err);
|
|
37765
37694
|
emit("push-failed", `Scaffold committed locally but the push failed: ${msg} \u2014 push it from the Git tab when credentials are available.`);
|
|
@@ -37773,7 +37702,7 @@ async function commitAndOpenPr(root, repo, emit) {
|
|
|
37773
37702
|
emit("pr", `Opening a pull request via ${provider}\u2026`);
|
|
37774
37703
|
try {
|
|
37775
37704
|
const args = provider === "glab" ? ["mr", "create", "--fill", ...slug ? ["-R", slug] : []] : ["pr", "create", "--fill", ...slug ? ["-R", slug] : []];
|
|
37776
|
-
const { stdout } = await
|
|
37705
|
+
const { stdout } = await run(provider, args, { cwd: root, timeout: 12e4 });
|
|
37777
37706
|
const link2 = stdout.trim().split(/\s+/).find((s3) => /^https?:\/\//.test(s3)) ?? stdout.trim().split("\n").pop() ?? "";
|
|
37778
37707
|
emit("pr", `Pull request opened${link2 ? `: ${link2}` : "."}`);
|
|
37779
37708
|
} catch (err) {
|
|
@@ -37787,7 +37716,7 @@ function cloneArgs(url, dest, branch) {
|
|
|
37787
37716
|
}
|
|
37788
37717
|
async function remoteHasBranch(url, branch) {
|
|
37789
37718
|
try {
|
|
37790
|
-
const { stdout } = await
|
|
37719
|
+
const { stdout } = await run("git", ["ls-remote", "--heads", url, branch], { timeout: 3e4 });
|
|
37791
37720
|
return stdout.trim().length > 0;
|
|
37792
37721
|
} catch {
|
|
37793
37722
|
return false;
|
|
@@ -37798,9 +37727,9 @@ async function ensureRemoteBranch(url, branch, emit) {
|
|
|
37798
37727
|
const tmp = await mkdtemp(join6(tmpdir2(), "4pm-branch-"));
|
|
37799
37728
|
try {
|
|
37800
37729
|
emit("git", `Branch "${branch}" not found on ${url} \u2014 creating it from the default branch\u2026`);
|
|
37801
|
-
await
|
|
37802
|
-
await
|
|
37803
|
-
await
|
|
37730
|
+
await run("git", ["clone", "--depth", "1", url, tmp], { timeout: 12e4 });
|
|
37731
|
+
await run("git", ["checkout", "-b", branch], { cwd: tmp, timeout: 3e4 });
|
|
37732
|
+
await run("git", ["push", "-u", "origin", branch], { cwd: tmp, timeout: 12e4 });
|
|
37804
37733
|
return true;
|
|
37805
37734
|
} catch (err) {
|
|
37806
37735
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -37813,9 +37742,9 @@ async function ensureRemoteBranch(url, branch, emit) {
|
|
|
37813
37742
|
async function updateRepo(dir2, branch, emit) {
|
|
37814
37743
|
const b2 = (branch ?? "").trim();
|
|
37815
37744
|
emit("git", `Updating repository (fetch + ${b2 ? `checkout ${b2} + ` : ""}fast-forward pull)\u2026`);
|
|
37816
|
-
await
|
|
37817
|
-
if (b2) await
|
|
37818
|
-
await
|
|
37745
|
+
await run("git", ["fetch", "origin", "--prune"], { cwd: dir2, timeout: 12e4 });
|
|
37746
|
+
if (b2) await run("git", ["checkout", b2], { cwd: dir2, timeout: 6e4 });
|
|
37747
|
+
await run("git", ["pull", "--ff-only"], { cwd: dir2, timeout: 12e4 });
|
|
37819
37748
|
}
|
|
37820
37749
|
async function provisionRepo(target, repo, emit, opts = {}) {
|
|
37821
37750
|
const mode = opts.mode ?? "clone";
|
|
@@ -37823,7 +37752,7 @@ async function provisionRepo(target, repo, emit, opts = {}) {
|
|
|
37823
37752
|
if (!existsSync4(join6(target, ".git"))) {
|
|
37824
37753
|
emit("git", "Initializing repository\u2026");
|
|
37825
37754
|
await mkdir(target, { recursive: true });
|
|
37826
|
-
await
|
|
37755
|
+
await run("git", ["init"], { cwd: target, timeout: 2e4 });
|
|
37827
37756
|
}
|
|
37828
37757
|
return;
|
|
37829
37758
|
}
|
|
@@ -37833,7 +37762,7 @@ async function provisionRepo(target, repo, emit, opts = {}) {
|
|
|
37833
37762
|
} else if (mode === "force") {
|
|
37834
37763
|
emit("git", `Re-cloning ${repo.url} (force)\u2026`);
|
|
37835
37764
|
await rm(target, { recursive: true, force: true });
|
|
37836
|
-
await
|
|
37765
|
+
await run("git", cloneArgs(repo.url, target, repo.branch), { timeout: 12e4 });
|
|
37837
37766
|
} else {
|
|
37838
37767
|
emit("git", "Repository already present \u2014 skipping clone.");
|
|
37839
37768
|
}
|
|
@@ -37842,10 +37771,10 @@ async function provisionRepo(target, repo, emit, opts = {}) {
|
|
|
37842
37771
|
const b2 = (repo.branch ?? "").trim();
|
|
37843
37772
|
if (b2 && opts.createMissingBranch && !await remoteHasBranch(repo.url, b2)) {
|
|
37844
37773
|
emit("git", `Cloning ${repo.url} (default branch) to create "${b2}"\u2026`);
|
|
37845
|
-
await
|
|
37846
|
-
await
|
|
37774
|
+
await run("git", ["clone", repo.url, target], { timeout: 12e4 });
|
|
37775
|
+
await run("git", ["checkout", "-b", b2], { cwd: target, timeout: 3e4 });
|
|
37847
37776
|
try {
|
|
37848
|
-
await
|
|
37777
|
+
await run("git", ["push", "-u", "origin", b2], { cwd: target, timeout: 12e4 });
|
|
37849
37778
|
emit("git", `Created and pushed branch "${b2}".`);
|
|
37850
37779
|
} catch (err) {
|
|
37851
37780
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -37854,7 +37783,7 @@ async function provisionRepo(target, repo, emit, opts = {}) {
|
|
|
37854
37783
|
return;
|
|
37855
37784
|
}
|
|
37856
37785
|
emit("git", `Cloning ${repo.url}\u2026`);
|
|
37857
|
-
await
|
|
37786
|
+
await run("git", cloneArgs(repo.url, target, repo.branch), { timeout: 12e4 });
|
|
37858
37787
|
}
|
|
37859
37788
|
async function ensureReposCloned(physicRoot, repos, emit) {
|
|
37860
37789
|
const list = repos;
|
|
@@ -37867,27 +37796,27 @@ async function ensureReposCloned(physicRoot, repos, emit) {
|
|
|
37867
37796
|
function sampleDir() {
|
|
37868
37797
|
if (process.env.SCAFFOLD_SAMPLE_DIR) return process.env.SCAFFOLD_SAMPLE_DIR;
|
|
37869
37798
|
const here = dirname3(fileURLToPath3(import.meta.url));
|
|
37870
|
-
const bundled =
|
|
37871
|
-
for (const candidate of [bundled,
|
|
37799
|
+
const bundled = resolve(here, "project-sample");
|
|
37800
|
+
for (const candidate of [bundled, resolve(here, "../project-sample"), resolve(here, "../../project-sample")]) {
|
|
37872
37801
|
if (existsSync4(candidate)) return candidate;
|
|
37873
37802
|
}
|
|
37874
37803
|
return bundled;
|
|
37875
37804
|
}
|
|
37876
|
-
async function scaffoldProject(payload, profileDir2, onProgress) {
|
|
37805
|
+
async function scaffoldProject(payload, profileDir2, onProgress, ai2) {
|
|
37877
37806
|
let lastStep = "start";
|
|
37878
37807
|
const emit = (step, message) => {
|
|
37879
37808
|
lastStep = step;
|
|
37880
37809
|
onProgress?.({ projectId: payload.projectId, step, message });
|
|
37881
37810
|
};
|
|
37882
37811
|
try {
|
|
37883
|
-
const root = process.env.SCAFFOLD_ROOT ?
|
|
37812
|
+
const root = process.env.SCAFFOLD_ROOT ? resolve(process.env.SCAFFOLD_ROOT) : resolve(profileDir2);
|
|
37884
37813
|
const target = join6(root, payload.projectName);
|
|
37885
37814
|
await mkdir(target, { recursive: true });
|
|
37886
37815
|
const repo = singleRepo(reposOf(payload.spec));
|
|
37887
37816
|
if (!repo) throw new Error("A project must declare one repo (ADR-0314).");
|
|
37888
37817
|
emit("git", "Cloning repository\u2026");
|
|
37889
37818
|
await provisionRepo(target, repo, emit, { createMissingBranch: true });
|
|
37890
|
-
await scaffoldRepo(target, payload.projectName, payload.spec, emit, { resetClaude: true });
|
|
37819
|
+
await scaffoldRepo(target, payload.projectName, payload.spec, emit, { resetClaude: true, ai: ai2 });
|
|
37891
37820
|
await attachSubmodules(target, submodulesOf(reposOf(payload.spec)), emit, true);
|
|
37892
37821
|
emit("version", "Writing .4pm/.4pm.json\u2026");
|
|
37893
37822
|
await writeTemplateMarker(target);
|
|
@@ -37906,7 +37835,7 @@ async function scaffoldRepo(dir2, label, spec, emit, opts = {}) {
|
|
|
37906
37835
|
if (spec) {
|
|
37907
37836
|
emit("spec", `Writing project.spec.json into ${label}\u2026`);
|
|
37908
37837
|
await writeFile(join6(dir2, "project.spec.json"), JSON.stringify(spec, null, 2), "utf8");
|
|
37909
|
-
await aiInit(dir2, spec, emit);
|
|
37838
|
+
await aiInit(dir2, spec, emit, opts.ai);
|
|
37910
37839
|
}
|
|
37911
37840
|
}
|
|
37912
37841
|
async function resetTemplateManagedClaude(dir2, label, emit) {
|
|
@@ -37932,7 +37861,7 @@ function readSpecField(spec, key) {
|
|
|
37932
37861
|
}
|
|
37933
37862
|
return typeof spec[key] === "string" ? spec[key] : "";
|
|
37934
37863
|
}
|
|
37935
|
-
async function aiInit(target, spec, emit) {
|
|
37864
|
+
async function aiInit(target, spec, emit, ai2) {
|
|
37936
37865
|
const guideFile = readSpecField(spec, "ai_guide_file") === "AGENT.md" ? "AGENT.md" : "CLAUDE.md";
|
|
37937
37866
|
const guideInstructions = readSpecField(spec, "ai_guide_instructions").trim();
|
|
37938
37867
|
emit("ai-init", `AI init: subagents, README, ${guideFile}\u2026`);
|
|
@@ -37952,25 +37881,47 @@ ${(sa.description || "").trim()}
|
|
|
37952
37881
|
}
|
|
37953
37882
|
}
|
|
37954
37883
|
const specJson = JSON.stringify(spec);
|
|
37955
|
-
|
|
37956
|
-
|
|
37957
|
-
|
|
37884
|
+
let generate3 = null;
|
|
37885
|
+
let releaseSlot = () => void 0;
|
|
37886
|
+
if (ai2) {
|
|
37887
|
+
const slot = await ai2.acquireSlot(
|
|
37888
|
+
(q2) => emit("ai-queued", `Waiting for an AI run slot \u2014 ${q2.position} ahead (${q2.running}/${q2.limit} parallel runs in use)\u2026`)
|
|
37889
|
+
);
|
|
37890
|
+
if (slot.kind === "granted") {
|
|
37891
|
+
releaseSlot = () => slot.handle.release();
|
|
37892
|
+
generate3 = (prompt, label) => ai2.generate(prompt, target, label);
|
|
37893
|
+
if (slot.queued) emit("ai-init", `AI init: subagents, README, ${guideFile}\u2026`);
|
|
37894
|
+
} else {
|
|
37895
|
+
emit("ai-skipped", "No AI run slot freed up in time \u2014 AI init skipped; fallback content written.");
|
|
37896
|
+
}
|
|
37897
|
+
}
|
|
37898
|
+
try {
|
|
37899
|
+
await generateFile(
|
|
37900
|
+
generate3,
|
|
37901
|
+
join6(target, "README.md"),
|
|
37902
|
+
"README.md",
|
|
37903
|
+
`Write a concise README.md (Markdown only, no preamble) for this project from its spec JSON:
|
|
37958
37904
|
${specJson}`
|
|
37959
|
-
|
|
37960
|
-
|
|
37961
|
-
|
|
37962
|
-
|
|
37905
|
+
);
|
|
37906
|
+
await generateFile(
|
|
37907
|
+
generate3,
|
|
37908
|
+
join6(target, guideFile),
|
|
37909
|
+
guideFile,
|
|
37910
|
+
`Write a ${guideFile} (Markdown only, no preamble) with guidance/conventions for AI agents working in this project, derived from its spec JSON:
|
|
37963
37911
|
${specJson}` + (guideInstructions ? `
|
|
37964
37912
|
Also incorporate these additional instructions/content:
|
|
37965
37913
|
${guideInstructions}
|
|
37966
37914
|
` : ""),
|
|
37967
|
-
|
|
37968
|
-
|
|
37915
|
+
guideInstructions
|
|
37916
|
+
);
|
|
37917
|
+
} finally {
|
|
37918
|
+
releaseSlot();
|
|
37919
|
+
}
|
|
37969
37920
|
}
|
|
37970
|
-
async function generateFile(path, prompt, fallback) {
|
|
37921
|
+
async function generateFile(generate3, path, label, prompt, fallback) {
|
|
37971
37922
|
try {
|
|
37972
|
-
const text = await
|
|
37973
|
-
if (text.trim()) {
|
|
37923
|
+
const text = generate3 ? await generate3(prompt, label) : null;
|
|
37924
|
+
if (text && text.trim()) {
|
|
37974
37925
|
await writeFile(path, text.trim() + "\n", "utf8");
|
|
37975
37926
|
return;
|
|
37976
37927
|
}
|
|
@@ -37978,21 +37929,21 @@ async function generateFile(path, prompt, fallback) {
|
|
|
37978
37929
|
}
|
|
37979
37930
|
if (fallback && fallback.trim()) await writeFile(path, fallback.trim() + "\n", "utf8");
|
|
37980
37931
|
}
|
|
37981
|
-
async function addProject(payload, profileDir2, onProgress) {
|
|
37932
|
+
async function addProject(payload, profileDir2, onProgress, ai2) {
|
|
37982
37933
|
let lastStep = "start";
|
|
37983
37934
|
const emit = (step, message) => {
|
|
37984
37935
|
lastStep = step;
|
|
37985
37936
|
onProgress?.({ projectId: payload.projectId, step, message });
|
|
37986
37937
|
};
|
|
37987
37938
|
try {
|
|
37988
|
-
const root = process.env.SCAFFOLD_ROOT ?
|
|
37939
|
+
const root = process.env.SCAFFOLD_ROOT ? resolve(process.env.SCAFFOLD_ROOT) : resolve(profileDir2);
|
|
37989
37940
|
const target = join6(root, payload.projectName);
|
|
37990
37941
|
await mkdir(target, { recursive: true });
|
|
37991
37942
|
const repo = singleRepo(payload.repos ?? []);
|
|
37992
37943
|
const mode = payload.mode ?? "clone";
|
|
37993
37944
|
if (repo) await provisionRepo(target, repo, emit, { mode });
|
|
37994
37945
|
if (payload.scaffoldRepos && payload.scaffoldRepos.length > 0) {
|
|
37995
|
-
await scaffoldRepo(target, payload.projectName, payload.spec, emit);
|
|
37946
|
+
await scaffoldRepo(target, payload.projectName, payload.spec, emit, { ai: ai2 });
|
|
37996
37947
|
}
|
|
37997
37948
|
await attachSubmodules(target, submodulesOf(payload.repos ?? []), emit);
|
|
37998
37949
|
onProgress?.({ projectId: payload.projectId, step: "done", message: "Project added.", done: true });
|
|
@@ -38372,6 +38323,9 @@ var WsChannels = {
|
|
|
38372
38323
|
// server → cli (reply): manage the rented worker's ssh deploy key (ADR-0173) — generate
|
|
38373
38324
|
// (ed25519, on the worker) / get / delete. The private key never leaves the worker.
|
|
38374
38325
|
GIT_SSH_KEY: "git.ssh-key",
|
|
38326
|
+
// cli → server (reply): a short-lived GitHub-App installation token for the served project's repo,
|
|
38327
|
+
// per job scope; also the cli's "scope revoked" report (ADR-0356). The App key stays on the server.
|
|
38328
|
+
GIT_TOKEN: "git.token",
|
|
38375
38329
|
PROJECT_CREATE: "project.create",
|
|
38376
38330
|
PROJECT_ADD: "project.add",
|
|
38377
38331
|
PROJECT_PROGRESS: "project.progress",
|
|
@@ -38396,6 +38350,9 @@ var WsChannels = {
|
|
|
38396
38350
|
CLI_UPDATE_RESULT: "cli.update-result",
|
|
38397
38351
|
// command lifecycle
|
|
38398
38352
|
COMMAND_ANNOUNCE: "command.announce",
|
|
38353
|
+
// server → cli (reply): stop an AI run (ADR-0362) — queued ⇒ leave the run-slot queue, running ⇒ kill
|
|
38354
|
+
// the AI process with no failover; the cli then ends the stream with `command.output {done, cancelled}`.
|
|
38355
|
+
COMMAND_CANCEL: "command.cancel",
|
|
38399
38356
|
// console transcript sync (ADR-0150): the cli streams its authoritative SessionBus
|
|
38400
38357
|
// transcript so the web Console renders it verbatim; gated by console.watch (server → cli:
|
|
38401
38358
|
// whether a web viewer is attached) so an unwatched cli syncs nothing.
|
|
@@ -38411,6 +38368,8 @@ var WsChannels = {
|
|
|
38411
38368
|
// command.dispatch({ ai:true }) → the profile-failover AI path (ADR-0100).
|
|
38412
38369
|
// quota & usage (ADR-0020)
|
|
38413
38370
|
QUOTA_CHECK: "quota.check",
|
|
38371
|
+
// cli → server (reply): per-org concurrent AI-run slot — acquire / renew / release (ADR-0359)
|
|
38372
|
+
RUN_SLOT: "run.slot",
|
|
38414
38373
|
USAGE_REPORT: "usage.report",
|
|
38415
38374
|
// cli → server: Claude subscription usage snapshot (5h / weekly — ADR-0072)
|
|
38416
38375
|
MACHINE_USAGE: "machine.usage",
|
|
@@ -38509,6 +38468,12 @@ function decryptPayload(key, payloadBase64, nonceBase64) {
|
|
|
38509
38468
|
return JSON.parse(plaintext.toString("utf8"));
|
|
38510
38469
|
}
|
|
38511
38470
|
|
|
38471
|
+
// vendor/@4pm/ws/messages/run-slot.ts
|
|
38472
|
+
var RUN_SLOT_RENEW_MS = 45e3;
|
|
38473
|
+
var RUN_SLOT_RETRY_MS = 3e3;
|
|
38474
|
+
var RUN_SLOT_MAX_WAIT_MS = 30 * 6e4;
|
|
38475
|
+
var COMMAND_CANCELLED_EXIT_CODE = 130;
|
|
38476
|
+
|
|
38512
38477
|
// src/core/update-scheduler.ts
|
|
38513
38478
|
import { spawn as spawn2 } from "child_process";
|
|
38514
38479
|
|
|
@@ -38625,8 +38590,8 @@ function resolveInstallTimeoutMs(sec) {
|
|
|
38625
38590
|
return sec && sec > 0 ? sec * 1e3 : TOOL_INSTALL_TIMEOUT_DEFAULT_MS;
|
|
38626
38591
|
}
|
|
38627
38592
|
function sleep(ms2) {
|
|
38628
|
-
return new Promise((
|
|
38629
|
-
const t2 = setTimeout(
|
|
38593
|
+
return new Promise((resolve12) => {
|
|
38594
|
+
const t2 = setTimeout(resolve12, ms2);
|
|
38630
38595
|
t2.unref?.();
|
|
38631
38596
|
});
|
|
38632
38597
|
}
|
|
@@ -38653,15 +38618,15 @@ function classifyToolFailure(code, out) {
|
|
|
38653
38618
|
}
|
|
38654
38619
|
return { reason: "other", retryable: false };
|
|
38655
38620
|
}
|
|
38656
|
-
function
|
|
38657
|
-
return new Promise((
|
|
38621
|
+
function run2(cmd, args, onLine, timeoutMs = 18e4) {
|
|
38622
|
+
return new Promise((resolve12) => {
|
|
38658
38623
|
let out = "";
|
|
38659
38624
|
let buf = "";
|
|
38660
38625
|
let child = null;
|
|
38661
38626
|
const done = (code) => {
|
|
38662
38627
|
clearTimeout(timer);
|
|
38663
38628
|
if (onLine && buf.trim()) onLine(buf.trim());
|
|
38664
|
-
|
|
38629
|
+
resolve12({ code, out });
|
|
38665
38630
|
};
|
|
38666
38631
|
const timer = setTimeout(() => {
|
|
38667
38632
|
child?.kill();
|
|
@@ -38693,13 +38658,13 @@ function run3(cmd, args, onLine, timeoutMs = 18e4) {
|
|
|
38693
38658
|
});
|
|
38694
38659
|
}
|
|
38695
38660
|
async function detectOne(cmd, versionArg) {
|
|
38696
|
-
const { code, out } = await
|
|
38661
|
+
const { code, out } = await run2(cmd, [versionArg], void 0, 15e3);
|
|
38697
38662
|
if (code !== 0) return null;
|
|
38698
38663
|
const first = out.split("\n").map((l) => l.trim()).find(Boolean);
|
|
38699
38664
|
return first ?? "";
|
|
38700
38665
|
}
|
|
38701
38666
|
async function listExtras(autoUpdateSet = /* @__PURE__ */ new Set()) {
|
|
38702
|
-
const { out } = await
|
|
38667
|
+
const { out } = await run2("npm", ["ls", "-g", "--depth=0", "--json"], void 0, 2e4);
|
|
38703
38668
|
if (!out.trim()) return [];
|
|
38704
38669
|
let deps = {};
|
|
38705
38670
|
try {
|
|
@@ -38775,7 +38740,7 @@ async function runWorkerToolOp(op, name, manager, onLine, timeoutMs = TOOL_INSTA
|
|
|
38775
38740
|
return { ok: false, exitCode: 1, error: resolved.error ?? "Invalid package." };
|
|
38776
38741
|
}
|
|
38777
38742
|
onLine(`$ ${manager} ${opArgs(op, manager, resolved.pkg).join(" ")}`);
|
|
38778
|
-
const { code, out } = await
|
|
38743
|
+
const { code, out } = await run2(manager, opArgs(op, manager, resolved.pkg), onLine, timeoutMs);
|
|
38779
38744
|
if (code === 0) return { ok: true, exitCode: code };
|
|
38780
38745
|
const detail = extractToolError(out);
|
|
38781
38746
|
return { ok: false, exitCode: code, error: detail ? `${manager} exited ${code}: ${detail}` : `${manager} exited ${code}` };
|
|
@@ -38808,7 +38773,7 @@ async function installPinned(name, version, manager, onLine, timeoutMs = TOOL_IN
|
|
|
38808
38773
|
for (let attempt = 1; attempt <= TOOL_INSTALL_MAX_ATTEMPTS; attempt++) {
|
|
38809
38774
|
const suffix = attempt > 1 ? ` (attempt ${attempt}/${TOOL_INSTALL_MAX_ATTEMPTS})` : "";
|
|
38810
38775
|
onLine(`$ ${manager} ${args.join(" ")}${suffix}`);
|
|
38811
|
-
const { code, out } = await
|
|
38776
|
+
const { code, out } = await run2(manager, args, onLine, timeoutMs);
|
|
38812
38777
|
if (code === 0) return { ok: true };
|
|
38813
38778
|
const { reason, retryable } = classifyToolFailure(code, out);
|
|
38814
38779
|
if (!retryable || attempt === TOOL_INSTALL_MAX_ATTEMPTS) {
|
|
@@ -39044,11 +39009,11 @@ function getWorkingProfile(profileDir2, cmd) {
|
|
|
39044
39009
|
return readState(profileDir2)[cmd] ?? null;
|
|
39045
39010
|
}
|
|
39046
39011
|
function setWorkingProfile(profileDir2, cmd, dir2) {
|
|
39047
|
-
const
|
|
39048
|
-
if (
|
|
39049
|
-
|
|
39012
|
+
const state2 = readState(profileDir2);
|
|
39013
|
+
if (state2[cmd] === dir2) return;
|
|
39014
|
+
state2[cmd] = dir2;
|
|
39050
39015
|
try {
|
|
39051
|
-
writeFileSync4(statePath(profileDir2), JSON.stringify(
|
|
39016
|
+
writeFileSync4(statePath(profileDir2), JSON.stringify(state2, null, 2), "utf8");
|
|
39052
39017
|
} catch {
|
|
39053
39018
|
}
|
|
39054
39019
|
}
|
|
@@ -39057,11 +39022,11 @@ function getWorkingCredential(profileDir2) {
|
|
|
39057
39022
|
return readState(profileDir2)[WORKING_CREDENTIAL_KEY] ?? null;
|
|
39058
39023
|
}
|
|
39059
39024
|
function setWorkingCredential(profileDir2, key) {
|
|
39060
|
-
const
|
|
39061
|
-
if (
|
|
39062
|
-
|
|
39025
|
+
const state2 = readState(profileDir2);
|
|
39026
|
+
if (state2[WORKING_CREDENTIAL_KEY] === key) return;
|
|
39027
|
+
state2[WORKING_CREDENTIAL_KEY] = key;
|
|
39063
39028
|
try {
|
|
39064
|
-
writeFileSync4(statePath(profileDir2), JSON.stringify(
|
|
39029
|
+
writeFileSync4(statePath(profileDir2), JSON.stringify(state2, null, 2), "utf8");
|
|
39065
39030
|
} catch {
|
|
39066
39031
|
}
|
|
39067
39032
|
}
|
|
@@ -39070,20 +39035,20 @@ function getPinnedCredential(profileDir2) {
|
|
|
39070
39035
|
return readState(profileDir2)[PINNED_CREDENTIAL_KEY] ?? null;
|
|
39071
39036
|
}
|
|
39072
39037
|
function setPinnedCredential(profileDir2, key) {
|
|
39073
|
-
const
|
|
39074
|
-
if (
|
|
39075
|
-
|
|
39038
|
+
const state2 = readState(profileDir2);
|
|
39039
|
+
if (state2[PINNED_CREDENTIAL_KEY] === key) return;
|
|
39040
|
+
state2[PINNED_CREDENTIAL_KEY] = key;
|
|
39076
39041
|
try {
|
|
39077
|
-
writeFileSync4(statePath(profileDir2), JSON.stringify(
|
|
39042
|
+
writeFileSync4(statePath(profileDir2), JSON.stringify(state2, null, 2), "utf8");
|
|
39078
39043
|
} catch {
|
|
39079
39044
|
}
|
|
39080
39045
|
}
|
|
39081
39046
|
function clearPinnedCredential(profileDir2) {
|
|
39082
|
-
const
|
|
39083
|
-
if (!(PINNED_CREDENTIAL_KEY in
|
|
39084
|
-
delete
|
|
39047
|
+
const state2 = readState(profileDir2);
|
|
39048
|
+
if (!(PINNED_CREDENTIAL_KEY in state2)) return;
|
|
39049
|
+
delete state2[PINNED_CREDENTIAL_KEY];
|
|
39085
39050
|
try {
|
|
39086
|
-
writeFileSync4(statePath(profileDir2), JSON.stringify(
|
|
39051
|
+
writeFileSync4(statePath(profileDir2), JSON.stringify(state2, null, 2), "utf8");
|
|
39087
39052
|
} catch {
|
|
39088
39053
|
}
|
|
39089
39054
|
}
|
|
@@ -39092,6 +39057,36 @@ function clearPinnedCredential(profileDir2) {
|
|
|
39092
39057
|
import { readFileSync as readFileSync8 } from "fs";
|
|
39093
39058
|
import { homedir as homedir3 } from "os";
|
|
39094
39059
|
import { basename, isAbsolute as isAbsolute2, join as join9 } from "path";
|
|
39060
|
+
|
|
39061
|
+
// src/utils/agent-deny.ts
|
|
39062
|
+
import { isAbsolute, resolve as resolve2 } from "path";
|
|
39063
|
+
var HOME_SECRET_PATTERNS = [
|
|
39064
|
+
"~/.4pm/**",
|
|
39065
|
+
"~/.ssh/**",
|
|
39066
|
+
"~/.config/gh/**",
|
|
39067
|
+
"~/.git-credentials",
|
|
39068
|
+
"~/.claude*",
|
|
39069
|
+
"~/.claude*/**"
|
|
39070
|
+
];
|
|
39071
|
+
function absDirPattern(dir2) {
|
|
39072
|
+
const abs = (isAbsolute(dir2) ? dir2 : resolve2(dir2)).replace(/\\/g, "/").replace(/\/+$/, "");
|
|
39073
|
+
return `/${abs.startsWith("/") ? abs : `/${abs}`}/**`;
|
|
39074
|
+
}
|
|
39075
|
+
function agentDenyRules(profileDirs2 = []) {
|
|
39076
|
+
const patterns = new Set(HOME_SECRET_PATTERNS);
|
|
39077
|
+
for (const dir2 of profileDirs2) {
|
|
39078
|
+
if (dir2 && dir2.trim()) patterns.add(absDirPattern(dir2.trim()));
|
|
39079
|
+
}
|
|
39080
|
+
const rules = [];
|
|
39081
|
+
for (const p2 of patterns) rules.push(`Read(${p2})`, `Edit(${p2})`);
|
|
39082
|
+
return rules;
|
|
39083
|
+
}
|
|
39084
|
+
function denySettingsArgs(cmd, profileDirs2 = []) {
|
|
39085
|
+
if (!cmd.includes("claude")) return [];
|
|
39086
|
+
return ["--settings", JSON.stringify({ permissions: { deny: agentDenyRules(profileDirs2) } })];
|
|
39087
|
+
}
|
|
39088
|
+
|
|
39089
|
+
// src/utils/ai-cli.ts
|
|
39095
39090
|
var REQUIRED_AI_ARGS = {
|
|
39096
39091
|
claude: ["-p", "--output-format", "stream-json", "--verbose"],
|
|
39097
39092
|
codex: ["exec", "--json"],
|
|
@@ -39438,16 +39433,16 @@ async function fetchUsage(token) {
|
|
|
39438
39433
|
}
|
|
39439
39434
|
}
|
|
39440
39435
|
function runUsageCli(dir2, aiCli2) {
|
|
39441
|
-
return new Promise((
|
|
39436
|
+
return new Promise((resolve12) => {
|
|
39442
39437
|
let out = "";
|
|
39443
39438
|
let child = null;
|
|
39444
39439
|
const finish = () => {
|
|
39445
39440
|
clearTimeout(timer);
|
|
39446
|
-
|
|
39441
|
+
resolve12(out);
|
|
39447
39442
|
};
|
|
39448
39443
|
const timer = setTimeout(() => {
|
|
39449
39444
|
child?.kill();
|
|
39450
|
-
|
|
39445
|
+
resolve12(out);
|
|
39451
39446
|
}, 3e4);
|
|
39452
39447
|
timer.unref?.();
|
|
39453
39448
|
try {
|
|
@@ -39768,12 +39763,12 @@ function pruneCommandOutputByAge(days) {
|
|
|
39768
39763
|
|
|
39769
39764
|
// src/core/support-answer.ts
|
|
39770
39765
|
import { spawn as spawn4 } from "child_process";
|
|
39771
|
-
import { execFile as
|
|
39766
|
+
import { execFile as execFile2 } from "child_process";
|
|
39772
39767
|
import { createHash as createHash3 } from "crypto";
|
|
39773
39768
|
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";
|
|
39774
39769
|
import { join as join14 } from "path";
|
|
39775
39770
|
import { tmpdir as tmpdir3 } from "os";
|
|
39776
|
-
import { promisify as
|
|
39771
|
+
import { promisify as promisify2 } from "util";
|
|
39777
39772
|
|
|
39778
39773
|
// src/core/ai-stream.ts
|
|
39779
39774
|
function assistantText(ev) {
|
|
@@ -39878,7 +39873,7 @@ function aiProviderOf(cmd) {
|
|
|
39878
39873
|
if (cmd.includes("antigravity")) return "antigravity";
|
|
39879
39874
|
return "claude";
|
|
39880
39875
|
}
|
|
39881
|
-
function
|
|
39876
|
+
function estimateTokens(text) {
|
|
39882
39877
|
return Math.max(1, Math.ceil(text.length / 4));
|
|
39883
39878
|
}
|
|
39884
39879
|
|
|
@@ -39934,9 +39929,10 @@ function gitAuthArgs(user, password) {
|
|
|
39934
39929
|
|
|
39935
39930
|
// src/core/support-answer.ts
|
|
39936
39931
|
var NO_USAGE = { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 };
|
|
39937
|
-
var execFileAsync =
|
|
39932
|
+
var execFileAsync = promisify2(execFile2);
|
|
39938
39933
|
var MAX_CONTEXT_CHARS = 12e4;
|
|
39939
39934
|
var ANSWER_TIMEOUT_MS = 12e4;
|
|
39935
|
+
var LEGAL_DRAFT_TIMEOUT_MS = 54e4;
|
|
39940
39936
|
var DOC_EXTENSIONS = [".md", ".mdx", ".txt"];
|
|
39941
39937
|
function kbRoot(profileDir2) {
|
|
39942
39938
|
return join14(profileDir2, "support-kb");
|
|
@@ -40048,6 +40044,7 @@ function buildPrompt(docs, question, askerRole) {
|
|
|
40048
40044
|
].join("\n");
|
|
40049
40045
|
}
|
|
40050
40046
|
function buildDraftPrompt(docs, context, task) {
|
|
40047
|
+
if (task === "legal_draft") return buildLegalDraftPrompt(docs, context);
|
|
40051
40048
|
const goal = task === "reply_draft" ? [
|
|
40052
40049
|
"You are drafting a reply FROM the 4PM support team TO the customer in the support ticket below.",
|
|
40053
40050
|
"Address the customer's latest message, using the whole conversation for context. Be accurate,",
|
|
@@ -40074,6 +40071,31 @@ function buildDraftPrompt(docs, context, task) {
|
|
|
40074
40071
|
context
|
|
40075
40072
|
].join("\n");
|
|
40076
40073
|
}
|
|
40074
|
+
function buildLegalDraftPrompt(docs, context) {
|
|
40075
|
+
return [
|
|
40076
|
+
"You are drafting the body of one of 4PM's legal documents (Terms of Service, Privacy Policy,",
|
|
40077
|
+
"Add-on Terms, Rented Machine Addendum, \u2026) for the platform admin to review and publish.",
|
|
40078
|
+
"Write or revise the WHOLE document body in the target locale given in the context. Keep the",
|
|
40079
|
+
"existing heading structure and numbering unless the admin's instructions say otherwise.",
|
|
40080
|
+
"When the target locale is not English and an English reference is given, follow its meaning closely.",
|
|
40081
|
+
"Rules:",
|
|
40082
|
+
"- Every {{placeholder}} (e.g. {{companyName}}) must be kept verbatim \u2014 never fill or rename it.",
|
|
40083
|
+
"- Describe plans, prices, upgrades, downgrades, cancellations, renewals, refunds and add-ons ONLY",
|
|
40084
|
+
" as stated in the PLAN CATALOG and BILLING FACTS below or the documentation; never invent",
|
|
40085
|
+
" commitments, prices or legal guarantees. Where something is unknown write `[TODO: \u2026]`.",
|
|
40086
|
+
"- Plain, precise legal English (or the target language); short numbered sections and bullet lists.",
|
|
40087
|
+
"- This is a draft, not legal advice; do not add a disclaimer about that inside the document.",
|
|
40088
|
+
"",
|
|
40089
|
+
"Output ONLY the document body in markdown \u2014 no title line, no preamble, no explanation, no code",
|
|
40090
|
+
"fences around the whole document.",
|
|
40091
|
+
"",
|
|
40092
|
+
"===== DOCUMENTATION =====",
|
|
40093
|
+
docs,
|
|
40094
|
+
"",
|
|
40095
|
+
"===== CONTEXT =====",
|
|
40096
|
+
context
|
|
40097
|
+
].join("\n");
|
|
40098
|
+
}
|
|
40077
40099
|
function imageExt(mime) {
|
|
40078
40100
|
switch (mime) {
|
|
40079
40101
|
case "image/png":
|
|
@@ -40115,8 +40137,8 @@ function parseModeratedAnswer(text) {
|
|
|
40115
40137
|
}
|
|
40116
40138
|
return { body: text.trim() };
|
|
40117
40139
|
}
|
|
40118
|
-
function runClaudeOnce(cmd, profile, prompt, extraEnv, imageDir, denyDirs = []) {
|
|
40119
|
-
return new Promise((
|
|
40140
|
+
function runClaudeOnce(cmd, profile, prompt, extraEnv, imageDir, denyDirs = [], timeoutMs = ANSWER_TIMEOUT_MS) {
|
|
40141
|
+
return new Promise((resolve12, reject) => {
|
|
40120
40142
|
const isClaude = cmd.includes("claude");
|
|
40121
40143
|
const args = [
|
|
40122
40144
|
"-p",
|
|
@@ -40147,7 +40169,7 @@ function runClaudeOnce(cmd, profile, prompt, extraEnv, imageDir, denyDirs = [])
|
|
|
40147
40169
|
const timer = setTimeout(() => {
|
|
40148
40170
|
child.kill("SIGKILL");
|
|
40149
40171
|
reject(new Error("claude answer timed out"));
|
|
40150
|
-
},
|
|
40172
|
+
}, timeoutMs);
|
|
40151
40173
|
timer.unref();
|
|
40152
40174
|
child.stdout.on("data", (d) => out += parser.push(d.toString()));
|
|
40153
40175
|
child.stderr.on("data", (d) => err += d.toString());
|
|
@@ -40160,17 +40182,25 @@ function runClaudeOnce(cmd, profile, prompt, extraEnv, imageDir, denyDirs = [])
|
|
|
40160
40182
|
clearTimeout(timer);
|
|
40161
40183
|
cleanup();
|
|
40162
40184
|
out += parser.flush();
|
|
40163
|
-
|
|
40185
|
+
resolve12({ code: code ?? -1, out, err, usage: parser.usage() });
|
|
40164
40186
|
});
|
|
40165
40187
|
child.stdin.write(prompt);
|
|
40166
40188
|
child.stdin.end();
|
|
40167
40189
|
});
|
|
40168
40190
|
}
|
|
40169
|
-
async function runClaudeWithFailover(prompt, ai2, imageDir) {
|
|
40191
|
+
async function runClaudeWithFailover(prompt, ai2, imageDir, timeoutMs = ANSWER_TIMEOUT_MS) {
|
|
40170
40192
|
const attempts = ai2.profiles.length > 0 ? ai2.profiles : [null];
|
|
40171
40193
|
let lastReason = "no attempt";
|
|
40172
40194
|
for (let i = 0; i < attempts.length; i++) {
|
|
40173
|
-
const { code, out, err, usage } = await runClaudeOnce(
|
|
40195
|
+
const { code, out, err, usage } = await runClaudeOnce(
|
|
40196
|
+
ai2.cmd,
|
|
40197
|
+
attempts[i],
|
|
40198
|
+
prompt,
|
|
40199
|
+
ai2.env,
|
|
40200
|
+
imageDir,
|
|
40201
|
+
ai2.profiles.map((p2) => p2.dir),
|
|
40202
|
+
timeoutMs
|
|
40203
|
+
);
|
|
40174
40204
|
if (code === 0 && out.trim()) return { text: out.trim(), usage };
|
|
40175
40205
|
const combined = err || out;
|
|
40176
40206
|
lastReason = `exited ${code}: ${combined.slice(0, 500)}`;
|
|
@@ -40191,11 +40221,12 @@ async function runSupportAnswer(req, ai2, profileDir2) {
|
|
|
40191
40221
|
const docs = collectDocs(dir2);
|
|
40192
40222
|
if (!docs.trim()) return { body: "", error: "KB repo has no documentation" };
|
|
40193
40223
|
const prompt = req.task ? buildDraftPrompt(docs, question, req.task) : buildPrompt(docs, question, req.askerRole);
|
|
40194
|
-
const
|
|
40224
|
+
const timeoutMs = req.task === "legal_draft" ? LEGAL_DRAFT_TIMEOUT_MS : ANSWER_TIMEOUT_MS;
|
|
40225
|
+
const { text, usage } = await runClaudeWithFailover(prompt, ai2, imageDir, timeoutMs);
|
|
40195
40226
|
if (!text) return { body: "", error: "empty answer" };
|
|
40196
40227
|
const { body, moderation } = req.task ? { body: text.trim(), moderation: void 0 } : parseModeratedAnswer(text);
|
|
40197
40228
|
if (!body) return { body: "", error: "empty answer" };
|
|
40198
|
-
const u2 = usage.tokens > 0 ? usage : { ...NO_USAGE, tokens:
|
|
40229
|
+
const u2 = usage.tokens > 0 ? usage : { ...NO_USAGE, tokens: estimateTokens(body) };
|
|
40199
40230
|
return {
|
|
40200
40231
|
body,
|
|
40201
40232
|
...moderation ? { moderation } : {},
|
|
@@ -40244,9 +40275,9 @@ function reportToolResult(tool, ok, message) {
|
|
|
40244
40275
|
// src/core/autonomous.ts
|
|
40245
40276
|
import { readFile as readFile4, writeFile as writeFile4, chmod } from "fs/promises";
|
|
40246
40277
|
import { readFileSync as readFileSync14 } from "fs";
|
|
40247
|
-
import { execFile as
|
|
40278
|
+
import { execFile as execFile3, spawn as spawn5 } from "child_process";
|
|
40248
40279
|
import { join as join17 } from "path";
|
|
40249
|
-
import { promisify as
|
|
40280
|
+
import { promisify as promisify3 } from "util";
|
|
40250
40281
|
|
|
40251
40282
|
// src/core/autonomous-config.ts
|
|
40252
40283
|
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
@@ -40357,7 +40388,7 @@ function pushRecord(h, rec) {
|
|
|
40357
40388
|
}
|
|
40358
40389
|
|
|
40359
40390
|
// src/core/autonomous.ts
|
|
40360
|
-
var
|
|
40391
|
+
var run3 = promisify3(execFile3);
|
|
40361
40392
|
var APPROVALS_REL = ".claude/.autonomous.approvals.json";
|
|
40362
40393
|
var AUTHORS_REL = ".claude/.autonomous.authors.json";
|
|
40363
40394
|
var TICK_REL = ".claude/hooks/autonomous-tick.sh";
|
|
@@ -40421,17 +40452,17 @@ function tickScript(root) {
|
|
|
40421
40452
|
}
|
|
40422
40453
|
async function crontabList() {
|
|
40423
40454
|
try {
|
|
40424
|
-
const { stdout } = await
|
|
40455
|
+
const { stdout } = await run3("crontab", ["-l"], { timeout: 1e4 });
|
|
40425
40456
|
return stdout;
|
|
40426
40457
|
} catch {
|
|
40427
40458
|
return "";
|
|
40428
40459
|
}
|
|
40429
40460
|
}
|
|
40430
40461
|
async function crontabSet(content) {
|
|
40431
|
-
return new Promise((
|
|
40462
|
+
return new Promise((resolve12) => {
|
|
40432
40463
|
const child = spawn5("crontab", ["-"], { stdio: ["pipe", "ignore", "ignore"] });
|
|
40433
|
-
child.on("error", () =>
|
|
40434
|
-
child.on("close", (code) =>
|
|
40464
|
+
child.on("error", () => resolve12(false));
|
|
40465
|
+
child.on("close", (code) => resolve12(code === 0));
|
|
40435
40466
|
child.stdin.end(content.endsWith("\n") ? content : content + "\n");
|
|
40436
40467
|
});
|
|
40437
40468
|
}
|
|
@@ -40600,14 +40631,14 @@ function detectContainerized() {
|
|
|
40600
40631
|
}
|
|
40601
40632
|
}
|
|
40602
40633
|
function canReach(host, port) {
|
|
40603
|
-
return new Promise((
|
|
40634
|
+
return new Promise((resolve12) => {
|
|
40604
40635
|
const socket = connect({ host, port });
|
|
40605
40636
|
let done = false;
|
|
40606
40637
|
const finish = (ok) => {
|
|
40607
40638
|
if (done) return;
|
|
40608
40639
|
done = true;
|
|
40609
40640
|
socket.destroy();
|
|
40610
|
-
|
|
40641
|
+
resolve12(ok);
|
|
40611
40642
|
};
|
|
40612
40643
|
socket.setTimeout(PROBE_TIMEOUT_MS);
|
|
40613
40644
|
socket.once("connect", () => finish(true));
|
|
@@ -40660,32 +40691,177 @@ async function probeNetwork() {
|
|
|
40660
40691
|
}
|
|
40661
40692
|
|
|
40662
40693
|
// src/core/git-auth.ts
|
|
40663
|
-
import { writeFileSync as writeFileSync8 } from "fs";
|
|
40664
|
-
import {
|
|
40665
|
-
import { join as join18 } from "path";
|
|
40666
|
-
|
|
40667
|
-
|
|
40668
|
-
|
|
40669
|
-
|
|
40670
|
-
|
|
40671
|
-
|
|
40694
|
+
import { chmodSync as chmodSync3, existsSync as existsSync12, mkdirSync as mkdirSync6, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "fs";
|
|
40695
|
+
import { request as httpsRequest } from "https";
|
|
40696
|
+
import { delimiter, dirname as dirname4, join as join18, resolve as resolve3 } from "path";
|
|
40697
|
+
var JOB_ID_ENV = "FOURPM_JOB_ID";
|
|
40698
|
+
var PROFILE_DIR_ENV = "FOURPM_PROFILE_DIR";
|
|
40699
|
+
var GIT_HOST_ENV = "FOURPM_GIT_HOST";
|
|
40700
|
+
var GIT_HOST_KIND_ENV = "FOURPM_GIT_HOST_KIND";
|
|
40701
|
+
var DEFAULT_SCOPE = "link";
|
|
40702
|
+
var HELPER_ENV_ENTRIES = 3;
|
|
40703
|
+
var DEFAULT_SCOPE_IDLE_MS = 10 * 60 * 1e3;
|
|
40704
|
+
var state = { host: null, transport: null, baseEnv: null, scopes: /* @__PURE__ */ new Map(), defaultTimer: null };
|
|
40705
|
+
function cliInvocation() {
|
|
40706
|
+
const quote = (s3) => `"${s3.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
40707
|
+
const script = process.argv[1];
|
|
40708
|
+
return [process.execPath, ...process.execArgv, ...script ? [script] : []].map(quote).join(" ");
|
|
40709
|
+
}
|
|
40710
|
+
function helperCommand() {
|
|
40711
|
+
return `!${cliInvocation()} git-credential`;
|
|
40712
|
+
}
|
|
40713
|
+
function findRealGh(pathValue, shimDir) {
|
|
40714
|
+
for (const dir2 of pathValue.split(delimiter)) {
|
|
40715
|
+
if (!dir2 || dir2 === shimDir) continue;
|
|
40716
|
+
const candidate = join18(dir2, process.platform === "win32" ? "gh.exe" : "gh");
|
|
40717
|
+
if (existsSync12(candidate)) return candidate;
|
|
40672
40718
|
}
|
|
40673
40719
|
return null;
|
|
40674
40720
|
}
|
|
40675
|
-
function
|
|
40676
|
-
|
|
40677
|
-
|
|
40678
|
-
|
|
40679
|
-
|
|
40680
|
-
|
|
40681
|
-
|
|
40721
|
+
function shimScript(realGh, cli) {
|
|
40722
|
+
return `#!/bin/sh
|
|
40723
|
+
# 4PM gh shim (ADR-0356) \u2014 injects a short-lived GitHub-App token for the served project.
|
|
40724
|
+
if [ -z "$GH_TOKEN$GH_ENTERPRISE_TOKEN$GITHUB_TOKEN" ]; then
|
|
40725
|
+
T="$(${cli} git-token 2>/dev/null)"
|
|
40726
|
+
if [ -n "$T" ]; then
|
|
40727
|
+
case "$${GIT_HOST_KIND_ENV}" in
|
|
40728
|
+
ghes) export GH_HOST="$${GIT_HOST_ENV}" GH_ENTERPRISE_TOKEN="$T"; unset GH_TOKEN ;;
|
|
40729
|
+
ghe-cloud) export GH_HOST="$${GIT_HOST_ENV}" GH_TOKEN="$T"; unset GH_ENTERPRISE_TOKEN ;;
|
|
40730
|
+
*) export GH_TOKEN="$T"; unset GH_ENTERPRISE_TOKEN ;;
|
|
40731
|
+
esac
|
|
40732
|
+
fi
|
|
40733
|
+
fi
|
|
40734
|
+
exec "${realGh}" "$@"
|
|
40682
40735
|
`;
|
|
40683
|
-
|
|
40736
|
+
}
|
|
40737
|
+
function clearEnv() {
|
|
40738
|
+
if (!state.baseEnv) return;
|
|
40739
|
+
const { count, path } = state.baseEnv;
|
|
40740
|
+
for (let i = count; i < count + HELPER_ENV_ENTRIES; i++) {
|
|
40741
|
+
delete process.env[`GIT_CONFIG_KEY_${i}`];
|
|
40742
|
+
delete process.env[`GIT_CONFIG_VALUE_${i}`];
|
|
40743
|
+
}
|
|
40744
|
+
if (count > 0) process.env.GIT_CONFIG_COUNT = String(count);
|
|
40745
|
+
else delete process.env.GIT_CONFIG_COUNT;
|
|
40746
|
+
process.env.PATH = path;
|
|
40747
|
+
delete process.env[GIT_HOST_ENV];
|
|
40748
|
+
delete process.env[GIT_HOST_KIND_ENV];
|
|
40749
|
+
state.baseEnv = null;
|
|
40750
|
+
}
|
|
40751
|
+
function configureGitAuth(method, host, profileDir2, transport) {
|
|
40752
|
+
try {
|
|
40753
|
+
const active = method === "github-app" && !!host;
|
|
40754
|
+
if (!active || state.host && state.host !== host) {
|
|
40755
|
+
if (state.host) void revokeAll();
|
|
40756
|
+
clearEnv();
|
|
40757
|
+
state.host = null;
|
|
40758
|
+
}
|
|
40759
|
+
if (!active) return false;
|
|
40760
|
+
state.transport = transport;
|
|
40761
|
+
if (state.host === host && state.baseEnv) return true;
|
|
40762
|
+
const baseCount = Number(process.env.GIT_CONFIG_COUNT ?? "0") || 0;
|
|
40763
|
+
state.baseEnv = { count: baseCount, path: process.env.PATH ?? "" };
|
|
40764
|
+
const prefix = `credential.https://${host}`;
|
|
40765
|
+
const entries2 = [
|
|
40766
|
+
[`${prefix}.helper`, ""],
|
|
40767
|
+
[`${prefix}.helper`, helperCommand()],
|
|
40768
|
+
[`${prefix}.useHttpPath`, "true"]
|
|
40769
|
+
];
|
|
40770
|
+
entries2.forEach(([key, value], i) => {
|
|
40771
|
+
process.env[`GIT_CONFIG_KEY_${baseCount + i}`] = key;
|
|
40772
|
+
process.env[`GIT_CONFIG_VALUE_${baseCount + i}`] = value;
|
|
40773
|
+
});
|
|
40774
|
+
process.env.GIT_CONFIG_COUNT = String(baseCount + HELPER_ENV_ENTRIES);
|
|
40775
|
+
process.env[PROFILE_DIR_ENV] = resolve3(profileDir2);
|
|
40776
|
+
const script = process.argv[1];
|
|
40777
|
+
const devTsconfig = script ? resolve3(dirname4(script), "..", "tsconfig.json") : "";
|
|
40778
|
+
if (process.execArgv.some((a) => a.includes("tsx")) && devTsconfig && existsSync12(devTsconfig) && !process.env.TSX_TSCONFIG_PATH) {
|
|
40779
|
+
process.env.TSX_TSCONFIG_PATH = devTsconfig;
|
|
40780
|
+
}
|
|
40781
|
+
process.env[GIT_HOST_ENV] = host;
|
|
40782
|
+
process.env[GIT_HOST_KIND_ENV] = githubHostKind(host);
|
|
40783
|
+
if (process.platform !== "win32") {
|
|
40784
|
+
const shimDir = join18(profileDir2, "bin");
|
|
40785
|
+
const realGh = findRealGh(state.baseEnv.path, shimDir);
|
|
40786
|
+
if (realGh) {
|
|
40787
|
+
mkdirSync6(shimDir, { recursive: true });
|
|
40788
|
+
const shim = join18(shimDir, "gh");
|
|
40789
|
+
writeFileSync8(shim, shimScript(realGh, cliInvocation()), { encoding: "utf8", mode: 493 });
|
|
40790
|
+
chmodSync3(shim, 493);
|
|
40791
|
+
process.env.PATH = `${shimDir}${delimiter}${state.baseEnv.path}`;
|
|
40792
|
+
} else {
|
|
40793
|
+
rmSync6(join18(shimDir, "gh"), { force: true });
|
|
40794
|
+
}
|
|
40795
|
+
}
|
|
40796
|
+
state.host = host;
|
|
40797
|
+
logger.info("git.auth.github-app", { host });
|
|
40684
40798
|
return true;
|
|
40685
|
-
} catch {
|
|
40799
|
+
} catch (err) {
|
|
40800
|
+
logger.warn("git.auth.configure.failed", { error: String(err) });
|
|
40686
40801
|
return false;
|
|
40687
40802
|
}
|
|
40688
40803
|
}
|
|
40804
|
+
function touchDefaultScope() {
|
|
40805
|
+
if (state.defaultTimer) clearTimeout(state.defaultTimer);
|
|
40806
|
+
state.defaultTimer = setTimeout(() => void endGitScope(DEFAULT_SCOPE), DEFAULT_SCOPE_IDLE_MS);
|
|
40807
|
+
state.defaultTimer.unref?.();
|
|
40808
|
+
}
|
|
40809
|
+
async function issueGitToken(scope, host, path) {
|
|
40810
|
+
try {
|
|
40811
|
+
if (!state.host || !state.transport || host.toLowerCase() !== state.host) return null;
|
|
40812
|
+
const s3 = scope || DEFAULT_SCOPE;
|
|
40813
|
+
const reply = await state.transport({ op: "issue", scope: s3, repo: { host, path } });
|
|
40814
|
+
if (!reply.token) return null;
|
|
40815
|
+
const list = state.scopes.get(s3) ?? [];
|
|
40816
|
+
if (!list.some((t2) => t2.token === reply.token)) list.push({ token: reply.token, apiBase: reply.apiBase ?? "" });
|
|
40817
|
+
state.scopes.set(s3, list);
|
|
40818
|
+
if (s3 === DEFAULT_SCOPE) touchDefaultScope();
|
|
40819
|
+
return reply.token;
|
|
40820
|
+
} catch (err) {
|
|
40821
|
+
logger.warn("git.token.issue.failed", { error: String(err) });
|
|
40822
|
+
return null;
|
|
40823
|
+
}
|
|
40824
|
+
}
|
|
40825
|
+
function revokeAtGithub(t2) {
|
|
40826
|
+
return new Promise((resolve12) => {
|
|
40827
|
+
try {
|
|
40828
|
+
const req = httpsRequest(
|
|
40829
|
+
new URL(`${t2.apiBase.replace(/\/+$/, "")}/installation/token`),
|
|
40830
|
+
{
|
|
40831
|
+
method: "DELETE",
|
|
40832
|
+
timeout: 1e4,
|
|
40833
|
+
headers: { Authorization: `token ${t2.token}`, Accept: "application/vnd.github+json", "User-Agent": "4pm-cli" }
|
|
40834
|
+
},
|
|
40835
|
+
(res) => {
|
|
40836
|
+
res.resume();
|
|
40837
|
+
res.on("end", () => resolve12());
|
|
40838
|
+
}
|
|
40839
|
+
);
|
|
40840
|
+
req.on("timeout", () => req.destroy());
|
|
40841
|
+
req.on("error", () => resolve12());
|
|
40842
|
+
req.end();
|
|
40843
|
+
} catch {
|
|
40844
|
+
resolve12();
|
|
40845
|
+
}
|
|
40846
|
+
});
|
|
40847
|
+
}
|
|
40848
|
+
async function endGitScope(scope) {
|
|
40849
|
+
const tokens = state.scopes.get(scope);
|
|
40850
|
+
if (!tokens) return;
|
|
40851
|
+
state.scopes.delete(scope);
|
|
40852
|
+
if (scope === DEFAULT_SCOPE && state.defaultTimer) {
|
|
40853
|
+
clearTimeout(state.defaultTimer);
|
|
40854
|
+
state.defaultTimer = null;
|
|
40855
|
+
}
|
|
40856
|
+
await Promise.all(tokens.filter((t2) => t2.apiBase).map((t2) => revokeAtGithub(t2)));
|
|
40857
|
+
try {
|
|
40858
|
+
await state.transport?.({ op: "revoked", scope });
|
|
40859
|
+
} catch {
|
|
40860
|
+
}
|
|
40861
|
+
}
|
|
40862
|
+
async function revokeAll() {
|
|
40863
|
+
await Promise.all([...state.scopes.keys()].map((s3) => endGitScope(s3)));
|
|
40864
|
+
}
|
|
40689
40865
|
|
|
40690
40866
|
// src/core/ws-client/transcript.ts
|
|
40691
40867
|
function toDtoEntry(e) {
|
|
@@ -40716,13 +40892,18 @@ import { randomUUID as randomCommandId } from "crypto";
|
|
|
40716
40892
|
// src/core/executor.ts
|
|
40717
40893
|
import { spawn as spawn6 } from "child_process";
|
|
40718
40894
|
var KILL_GRACE_MS = 5e3;
|
|
40719
|
-
async function runCommand(dispatch,
|
|
40895
|
+
async function runCommand(dispatch, emitRaw, opts) {
|
|
40720
40896
|
let seq = 0;
|
|
40897
|
+
const emit = (output) => {
|
|
40898
|
+
emitRaw(output);
|
|
40899
|
+
if (output.done) void endGitScope(dispatch.commandId);
|
|
40900
|
+
};
|
|
40721
40901
|
let child;
|
|
40722
40902
|
try {
|
|
40723
40903
|
child = spawn6(dispatch.cmd, dispatch.args ?? [], {
|
|
40724
40904
|
cwd: dispatch.path,
|
|
40725
|
-
|
|
40905
|
+
// FOURPM_JOB_ID scopes the git helper / gh shim tokens to this run (ADR-0356).
|
|
40906
|
+
env: { ...process.env, ...dispatch.env, [JOB_ID_ENV]: dispatch.commandId },
|
|
40726
40907
|
shell: false
|
|
40727
40908
|
});
|
|
40728
40909
|
} catch (err) {
|
|
@@ -40759,9 +40940,20 @@ async function runCommand(dispatch, emit, opts) {
|
|
|
40759
40940
|
hardKillTimer.unref?.();
|
|
40760
40941
|
}, timeoutMs) : null;
|
|
40761
40942
|
timeoutTimer?.unref?.();
|
|
40943
|
+
let cancelled = false;
|
|
40944
|
+
const onAbort = () => {
|
|
40945
|
+
if (cancelled) return;
|
|
40946
|
+
cancelled = true;
|
|
40947
|
+
child.kill("SIGTERM");
|
|
40948
|
+
hardKillTimer = setTimeout(() => child.kill("SIGKILL"), KILL_GRACE_MS);
|
|
40949
|
+
hardKillTimer.unref?.();
|
|
40950
|
+
};
|
|
40951
|
+
if (opts?.signal?.aborted) onAbort();
|
|
40952
|
+
else opts?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
40762
40953
|
const clearTimers = () => {
|
|
40763
40954
|
if (timeoutTimer) clearTimeout(timeoutTimer);
|
|
40764
40955
|
if (hardKillTimer) clearTimeout(hardKillTimer);
|
|
40956
|
+
opts?.signal?.removeEventListener("abort", onAbort);
|
|
40765
40957
|
};
|
|
40766
40958
|
const batcher = new OutputBatcher({
|
|
40767
40959
|
onFlush: (chunk, truncated) => {
|
|
@@ -40779,7 +40971,7 @@ async function runCommand(dispatch, emit, opts) {
|
|
|
40779
40971
|
});
|
|
40780
40972
|
child.stdout?.on("data", (data) => batcher.push(data.toString("utf8")));
|
|
40781
40973
|
child.stderr?.on("data", (data) => batcher.push(data.toString("utf8")));
|
|
40782
|
-
await new Promise((
|
|
40974
|
+
await new Promise((resolve12) => {
|
|
40783
40975
|
child.on("close", (exitCode) => {
|
|
40784
40976
|
clearTimers();
|
|
40785
40977
|
batcher.flush();
|
|
@@ -40788,11 +40980,12 @@ async function runCommand(dispatch, emit, opts) {
|
|
|
40788
40980
|
seq: seq++,
|
|
40789
40981
|
chunk: "",
|
|
40790
40982
|
done: true,
|
|
40791
|
-
// 124 (the conventional timeout code) when we killed it
|
|
40792
|
-
// server settles the command as failed and the web surfaces the
|
|
40793
|
-
|
|
40983
|
+
// 130 when the run was stopped (ADR-0362); 124 (the conventional timeout code) when we killed it
|
|
40984
|
+
// for exceeding the limit, so the server settles the command as failed and the web surfaces the
|
|
40985
|
+
// note instead of spinning.
|
|
40986
|
+
exitCode: cancelled ? COMMAND_CANCELLED_EXIT_CODE : timedOut ? 124 : exitCode ?? -1
|
|
40794
40987
|
});
|
|
40795
|
-
|
|
40988
|
+
resolve12();
|
|
40796
40989
|
});
|
|
40797
40990
|
child.on("error", (err) => {
|
|
40798
40991
|
clearTimers();
|
|
@@ -40804,7 +40997,7 @@ async function runCommand(dispatch, emit, opts) {
|
|
|
40804
40997
|
done: true,
|
|
40805
40998
|
exitCode: -1
|
|
40806
40999
|
});
|
|
40807
|
-
|
|
41000
|
+
resolve12();
|
|
40808
41001
|
});
|
|
40809
41002
|
});
|
|
40810
41003
|
}
|
|
@@ -40826,24 +41019,25 @@ function isOutOfCredits(text) {
|
|
|
40826
41019
|
text
|
|
40827
41020
|
);
|
|
40828
41021
|
}
|
|
40829
|
-
function runAttempt(commandId, cmd, args, stdin, cwd2, env3, onChunk, timeoutMs) {
|
|
40830
|
-
return new Promise((
|
|
41022
|
+
function runAttempt(commandId, cmd, args, stdin, cwd2, env3, onChunk, timeoutMs, signal) {
|
|
41023
|
+
return new Promise((resolve12) => {
|
|
40831
41024
|
runCommand(
|
|
40832
41025
|
{ commandId, cmd, args, path: cwd2, env: env3 },
|
|
40833
41026
|
(out) => {
|
|
40834
41027
|
if (out.chunk) onChunk(out.chunk);
|
|
40835
|
-
if (out.done)
|
|
41028
|
+
if (out.done) resolve12(out.exitCode ?? -1);
|
|
40836
41029
|
},
|
|
40837
|
-
{ stdin, ...timeoutMs > 0 ? { timeoutMs } : {} }
|
|
40838
|
-
).catch(() =>
|
|
41030
|
+
{ stdin, ...timeoutMs > 0 ? { timeoutMs } : {}, ...signal ? { signal } : {} }
|
|
41031
|
+
).catch(() => resolve12(-1));
|
|
40839
41032
|
});
|
|
40840
41033
|
}
|
|
40841
|
-
async function runAiFailover(plan, commandId, cwd2, handlers, timeoutMs = 0) {
|
|
41034
|
+
async function runAiFailover(plan, commandId, cwd2, handlers, timeoutMs = 0, signal) {
|
|
40842
41035
|
const total = plan.attempts.length;
|
|
40843
41036
|
let finalExit = -1;
|
|
40844
41037
|
let lastCmd = plan.attempts[0]?.cmd ?? "claude";
|
|
40845
41038
|
let lastCaptured = "";
|
|
40846
41039
|
for (let i = 0; i < total; i++) {
|
|
41040
|
+
if (signal?.aborted) return cancelledResult();
|
|
40847
41041
|
const attempt = plan.attempts[i];
|
|
40848
41042
|
lastCmd = attempt.cmd;
|
|
40849
41043
|
if (total > 1) handlers.onAttemptStart(attempt.label, i, total, attempt.cmd);
|
|
@@ -40856,17 +41050,18 @@ async function runAiFailover(plan, commandId, cwd2, handlers, timeoutMs = 0) {
|
|
|
40856
41050
|
handlers.onChunk(text);
|
|
40857
41051
|
}
|
|
40858
41052
|
};
|
|
40859
|
-
finalExit = await runAttempt(commandId, attempt.cmd, attempt.args, attempt.stdin, cwd2, attempt.env, emit, timeoutMs);
|
|
41053
|
+
finalExit = await runAttempt(commandId, attempt.cmd, attempt.args, attempt.stdin, cwd2, attempt.env, emit, timeoutMs, signal);
|
|
40860
41054
|
const tail = parser.flush();
|
|
40861
41055
|
if (tail) {
|
|
40862
41056
|
captured += tail;
|
|
40863
41057
|
handlers.onChunk(tail);
|
|
40864
41058
|
}
|
|
41059
|
+
if (signal?.aborted) return cancelledResult();
|
|
40865
41060
|
lastCaptured = captured;
|
|
40866
41061
|
const apiErr = parser.apiError();
|
|
40867
41062
|
if (finalExit === 0 && !apiErr.isError) {
|
|
40868
41063
|
const usage = parser.usage();
|
|
40869
|
-
if (usage.tokens === 0) usage.tokens =
|
|
41064
|
+
if (usage.tokens === 0) usage.tokens = estimateTokens(captured);
|
|
40870
41065
|
reportToolResult(attempt.cmd, true);
|
|
40871
41066
|
return {
|
|
40872
41067
|
exitCode: 0,
|
|
@@ -40885,6 +41080,17 @@ async function runAiFailover(plan, commandId, cwd2, handlers, timeoutMs = 0) {
|
|
|
40885
41080
|
reportToolResult(lastCmd, false, summarizeAiFailure(lastCaptured, finalExit));
|
|
40886
41081
|
return { exitCode: finalExit, workedDir: null, workedKey: null, workedCmd: null, usage: { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 }, sessionId: "" };
|
|
40887
41082
|
}
|
|
41083
|
+
function cancelledResult() {
|
|
41084
|
+
return {
|
|
41085
|
+
exitCode: COMMAND_CANCELLED_EXIT_CODE,
|
|
41086
|
+
workedDir: null,
|
|
41087
|
+
workedKey: null,
|
|
41088
|
+
workedCmd: null,
|
|
41089
|
+
usage: { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 },
|
|
41090
|
+
sessionId: "",
|
|
41091
|
+
cancelled: true
|
|
41092
|
+
};
|
|
41093
|
+
}
|
|
40888
41094
|
function summarizeAiFailure(captured, exitCode) {
|
|
40889
41095
|
if (isAuthFailure(captured)) return "Not logged in";
|
|
40890
41096
|
if (isOutOfCredits(captured)) return "Out of usage credits";
|
|
@@ -40918,7 +41124,7 @@ function formatTimestampInZone(date, timeZone) {
|
|
|
40918
41124
|
}
|
|
40919
41125
|
|
|
40920
41126
|
// src/ui/slash-commands.ts
|
|
40921
|
-
import { existsSync as
|
|
41127
|
+
import { existsSync as existsSync13 } from "fs";
|
|
40922
41128
|
import { join as join19 } from "path";
|
|
40923
41129
|
var CONFIG_KEYS = [
|
|
40924
41130
|
"aiCli",
|
|
@@ -40981,7 +41187,7 @@ function runConfig(ctx) {
|
|
|
40981
41187
|
ctx.print(t("slash.config.wroteDefault", { verb, path }));
|
|
40982
41188
|
ctx.print(t("slash.config.addClaudeHint"));
|
|
40983
41189
|
};
|
|
40984
|
-
if (
|
|
41190
|
+
if (existsSync13(path)) {
|
|
40985
41191
|
ctx.confirm(
|
|
40986
41192
|
t("slash.config.replaceConfirm", { path }),
|
|
40987
41193
|
() => writeDefaults(t("slash.config.verbReplaced"))
|
|
@@ -41043,8 +41249,8 @@ function runHistory(ctx) {
|
|
|
41043
41249
|
ctx.print(t("slash.history.header", { n: recent.length, total: all.length }));
|
|
41044
41250
|
recent.forEach((e, i) => {
|
|
41045
41251
|
const time = new Date(e.startedAt).toLocaleTimeString();
|
|
41046
|
-
const
|
|
41047
|
-
ctx.print(` [${i + 1}] ${time} ${commandLine(e.cmd, e.args)} \xB7 ${
|
|
41252
|
+
const state2 = e.status === "done" ? "\u2713" : e.status === "failed" ? `\u2717 exit ${e.exitCode}` : t("slash.history.running");
|
|
41253
|
+
ctx.print(` [${i + 1}] ${time} ${commandLine(e.cmd, e.args)} \xB7 ${state2}`);
|
|
41048
41254
|
});
|
|
41049
41255
|
}
|
|
41050
41256
|
function runOutput(ctx) {
|
|
@@ -41055,8 +41261,8 @@ function runOutput(ctx) {
|
|
|
41055
41261
|
return;
|
|
41056
41262
|
}
|
|
41057
41263
|
const entry = all.slice().reverse()[n - 1];
|
|
41058
|
-
const
|
|
41059
|
-
ctx.print(t("slash.output.header", { n, cmd: commandLine(entry.cmd, entry.args), status: entry.status, state }));
|
|
41264
|
+
const state2 = entry.exitCode != null ? ` (exit ${entry.exitCode})` : "";
|
|
41265
|
+
ctx.print(t("slash.output.header", { n, cmd: commandLine(entry.cmd, entry.args), status: entry.status, state: state2 }));
|
|
41060
41266
|
const out = readCommandOutput(entry.commandId);
|
|
41061
41267
|
if (out == null) {
|
|
41062
41268
|
ctx.print(t("slash.output.none"), "warn");
|
|
@@ -41351,7 +41557,7 @@ function buildPrompt2(input) {
|
|
|
41351
41557
|
].join("\n");
|
|
41352
41558
|
}
|
|
41353
41559
|
function runOnce(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
|
|
41354
|
-
return new Promise((
|
|
41560
|
+
return new Promise((resolve12, reject) => {
|
|
41355
41561
|
const args = [
|
|
41356
41562
|
"-p",
|
|
41357
41563
|
"--output-format",
|
|
@@ -41386,7 +41592,7 @@ function runOnce(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
|
|
|
41386
41592
|
});
|
|
41387
41593
|
child.on("close", (code) => {
|
|
41388
41594
|
clearTimeout(timer);
|
|
41389
|
-
|
|
41595
|
+
resolve12({ code: code ?? -1, out, err });
|
|
41390
41596
|
});
|
|
41391
41597
|
child.stdin.write(prompt);
|
|
41392
41598
|
child.stdin.end();
|
|
@@ -41413,20 +41619,101 @@ async function runMemoryCompaction(ai2, cwd2, input) {
|
|
|
41413
41619
|
}
|
|
41414
41620
|
|
|
41415
41621
|
// src/core/git-commit-identity.ts
|
|
41416
|
-
import { execFile as
|
|
41417
|
-
import { promisify as
|
|
41418
|
-
var
|
|
41622
|
+
import { execFile as execFile4 } from "child_process";
|
|
41623
|
+
import { promisify as promisify4 } from "util";
|
|
41624
|
+
var run4 = promisify4(execFile4);
|
|
41419
41625
|
async function setCommitAuthor(cwd2, machineUsername) {
|
|
41420
41626
|
if (!machineUsername) return;
|
|
41421
41627
|
try {
|
|
41422
|
-
await
|
|
41628
|
+
await run4("git", ["config", "--local", "user.name", machineUsername], { cwd: cwd2, timeout: 8e3 });
|
|
41423
41629
|
} catch {
|
|
41424
41630
|
}
|
|
41425
41631
|
}
|
|
41426
41632
|
|
|
41633
|
+
// src/core/ws-client/run-slot.ts
|
|
41634
|
+
var NO_SLOT = { release: () => void 0 };
|
|
41635
|
+
function sleep2(ms2, signal) {
|
|
41636
|
+
return new Promise((resolve12) => {
|
|
41637
|
+
if (signal?.aborted) return resolve12();
|
|
41638
|
+
const timer = setTimeout(done, ms2);
|
|
41639
|
+
function done() {
|
|
41640
|
+
clearTimeout(timer);
|
|
41641
|
+
signal?.removeEventListener("abort", done);
|
|
41642
|
+
resolve12();
|
|
41643
|
+
}
|
|
41644
|
+
signal?.addEventListener("abort", done, { once: true });
|
|
41645
|
+
});
|
|
41646
|
+
}
|
|
41647
|
+
function holdLease(ctx, lease) {
|
|
41648
|
+
const timer = setInterval(() => {
|
|
41649
|
+
ctx.request(WsChannels.RUN_SLOT, { op: "renew", lease }).catch(() => void 0);
|
|
41650
|
+
}, RUN_SLOT_RENEW_MS);
|
|
41651
|
+
timer.unref?.();
|
|
41652
|
+
let released = false;
|
|
41653
|
+
return {
|
|
41654
|
+
release: () => {
|
|
41655
|
+
if (released) return;
|
|
41656
|
+
released = true;
|
|
41657
|
+
clearInterval(timer);
|
|
41658
|
+
ctx.request(WsChannels.RUN_SLOT, { op: "release", lease }).catch(() => void 0);
|
|
41659
|
+
}
|
|
41660
|
+
};
|
|
41661
|
+
}
|
|
41662
|
+
async function acquireRunSlot(ctx, onQueued, signal) {
|
|
41663
|
+
const startedAt = Date.now();
|
|
41664
|
+
let ticket;
|
|
41665
|
+
let lastPosition = -1;
|
|
41666
|
+
let limit = 0;
|
|
41667
|
+
for (; ; ) {
|
|
41668
|
+
if (signal?.aborted) return leaveQueue(ctx, ticket);
|
|
41669
|
+
let reply;
|
|
41670
|
+
try {
|
|
41671
|
+
reply = await ctx.request(WsChannels.RUN_SLOT, { op: "acquire", ...ticket ? { ticket } : {} });
|
|
41672
|
+
} catch (err) {
|
|
41673
|
+
logger.warn("run.slot.unavailable", { error: err.message });
|
|
41674
|
+
return { kind: "granted", handle: NO_SLOT, queued: ticket !== void 0 };
|
|
41675
|
+
}
|
|
41676
|
+
if (!reply || !("granted" in reply)) return { kind: "granted", handle: NO_SLOT, queued: ticket !== void 0 };
|
|
41677
|
+
if (reply.granted) {
|
|
41678
|
+
const handle = reply.lease ? holdLease(ctx, reply.lease) : NO_SLOT;
|
|
41679
|
+
if (signal?.aborted) {
|
|
41680
|
+
handle.release();
|
|
41681
|
+
return { kind: "cancelled" };
|
|
41682
|
+
}
|
|
41683
|
+
return { kind: "granted", handle, queued: ticket !== void 0 };
|
|
41684
|
+
}
|
|
41685
|
+
ticket = reply.ticket;
|
|
41686
|
+
limit = reply.limit;
|
|
41687
|
+
if (reply.position !== lastPosition) {
|
|
41688
|
+
lastPosition = reply.position;
|
|
41689
|
+
onQueued({ position: reply.position, limit: reply.limit, running: reply.running });
|
|
41690
|
+
}
|
|
41691
|
+
if (Date.now() - startedAt >= RUN_SLOT_MAX_WAIT_MS) return { kind: "timeout", limit };
|
|
41692
|
+
await sleep2(reply.retryAfterMs > 0 ? reply.retryAfterMs : RUN_SLOT_RETRY_MS, signal);
|
|
41693
|
+
}
|
|
41694
|
+
}
|
|
41695
|
+
function leaveQueue(ctx, ticket) {
|
|
41696
|
+
if (ticket) ctx.request(WsChannels.RUN_SLOT, { op: "leave", ticket }).catch(() => void 0);
|
|
41697
|
+
return { kind: "cancelled" };
|
|
41698
|
+
}
|
|
41699
|
+
|
|
41427
41700
|
// src/core/ws-client/command-dispatch.ts
|
|
41428
41701
|
var MEMORY_SEED_HEADER = "CONTEXT MEMORY from earlier in this conversation (may span prior sessions/accounts). Use it as background; do not repeat it back unless relevant:";
|
|
41702
|
+
var activeRuns = /* @__PURE__ */ new Map();
|
|
41703
|
+
function cancelActiveRun(commandId) {
|
|
41704
|
+
const run11 = activeRuns.get(commandId);
|
|
41705
|
+
if (!run11) return { ok: false, state: "unknown" };
|
|
41706
|
+
run11.controller.abort();
|
|
41707
|
+
return { ok: true, state: run11.state };
|
|
41708
|
+
}
|
|
41429
41709
|
function handleCommandChannels(ctx, message, payload) {
|
|
41710
|
+
if (message.channel === WsChannels.COMMAND_CANCEL) {
|
|
41711
|
+
const commandId = typeof payload.commandId === "string" ? payload.commandId : "";
|
|
41712
|
+
const reply = cancelActiveRun(commandId);
|
|
41713
|
+
logger.info("command.cancel", { commandId, ...reply });
|
|
41714
|
+
ctx.send(WsChannels.COMMAND_CANCEL, reply, message.id);
|
|
41715
|
+
return true;
|
|
41716
|
+
}
|
|
41430
41717
|
if (message.channel !== WsChannels.COMMAND_DISPATCH) return false;
|
|
41431
41718
|
const dispatch = payload;
|
|
41432
41719
|
if (ctx.hasHandledCommand(dispatch.commandId)) {
|
|
@@ -41513,6 +41800,15 @@ async function runLocalCommand(ctx, input) {
|
|
|
41513
41800
|
await runAiPrompt(ctx, prompt, commandId, "local");
|
|
41514
41801
|
}
|
|
41515
41802
|
async function runAiPrompt(ctx, prompt, commandId, origin, oneShot = false, images, aiConfig, readOnly = false, bypass = false) {
|
|
41803
|
+
const run11 = { controller: new AbortController(), state: "queued" };
|
|
41804
|
+
activeRuns.set(commandId, run11);
|
|
41805
|
+
try {
|
|
41806
|
+
await runAiPromptInner(ctx, prompt, commandId, origin, run11, oneShot, images, aiConfig, readOnly, bypass);
|
|
41807
|
+
} finally {
|
|
41808
|
+
activeRuns.delete(commandId);
|
|
41809
|
+
}
|
|
41810
|
+
}
|
|
41811
|
+
async function runAiPromptInner(ctx, prompt, commandId, origin, run11, oneShot = false, images, aiConfig, readOnly = false, bypass = false) {
|
|
41516
41812
|
const config = readProfileConfig(ctx.profileDir);
|
|
41517
41813
|
const projectTimeoutSec = config.projectAiRunTimeoutSec ?? 0;
|
|
41518
41814
|
const machineTimeoutSec = config.aiRunTimeoutSec ?? 0;
|
|
@@ -41520,7 +41816,7 @@ async function runAiPrompt(ctx, prompt, commandId, origin, oneShot = false, imag
|
|
|
41520
41816
|
const aiRunTimeoutMs = timeoutSec > 0 ? timeoutSec * 1e3 : 0;
|
|
41521
41817
|
const perPromptLimit = config.perPromptTokenLimit ?? 0;
|
|
41522
41818
|
if (perPromptLimit > 0) {
|
|
41523
|
-
const estimated =
|
|
41819
|
+
const estimated = estimateTokens(prompt);
|
|
41524
41820
|
if (estimated > perPromptLimit) {
|
|
41525
41821
|
rejectPromptOverLimit(ctx, commandId, origin, prompt, estimated, perPromptLimit);
|
|
41526
41822
|
return;
|
|
@@ -41677,11 +41973,52 @@ ${guardedPrompt}`;
|
|
|
41677
41973
|
}
|
|
41678
41974
|
};
|
|
41679
41975
|
let result;
|
|
41976
|
+
let neverStarted = false;
|
|
41680
41977
|
try {
|
|
41681
|
-
|
|
41978
|
+
const slot = await acquireRunSlot(
|
|
41979
|
+
ctx,
|
|
41980
|
+
({ position, limit, running: running2 }) => {
|
|
41981
|
+
const text = `\u23F3 queued \u2014 ${position} ahead, ${running2}/${limit} parallel runs in use (plan limit)`;
|
|
41982
|
+
ctx.bus.push({ source: origin, kind: "log", text });
|
|
41983
|
+
ctx.send(WsChannels.COMMAND_OUTPUT, {
|
|
41984
|
+
commandId,
|
|
41985
|
+
seq: seq++,
|
|
41986
|
+
chunk: `${text}
|
|
41987
|
+
`,
|
|
41988
|
+
log: true,
|
|
41989
|
+
queue: { position, limit, running: running2 }
|
|
41990
|
+
});
|
|
41991
|
+
},
|
|
41992
|
+
run11.controller.signal
|
|
41993
|
+
);
|
|
41994
|
+
if (slot.kind === "timeout") {
|
|
41995
|
+
neverStarted = true;
|
|
41996
|
+
const text = `RUN_QUEUE_TIMEOUT: waited 30 min for a free run slot (plan limit ${slot.limit} parallel runs) \u2014 not run`;
|
|
41997
|
+
ctx.bus.push({ source: origin, kind: "log", text, level: "warn" });
|
|
41998
|
+
ctx.send(WsChannels.COMMAND_OUTPUT, { commandId, seq: seq++, chunk: `${text}
|
|
41999
|
+
`, log: true });
|
|
42000
|
+
result = { exitCode: 1, workedDir: null, workedKey: null, workedCmd: null, usage: { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 }, sessionId: "" };
|
|
42001
|
+
} else if (slot.kind === "cancelled") {
|
|
42002
|
+
neverStarted = true;
|
|
42003
|
+
result = { exitCode: COMMAND_CANCELLED_EXIT_CODE, workedDir: null, workedKey: null, workedCmd: null, usage: { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 }, sessionId: "", cancelled: true };
|
|
42004
|
+
} else {
|
|
42005
|
+
if (slot.queued) ctx.send(WsChannels.COMMAND_OUTPUT, { commandId, seq: seq++, chunk: "", log: true, queue: null });
|
|
42006
|
+
run11.state = "running";
|
|
42007
|
+
try {
|
|
42008
|
+
result = await runAiFailover(plan, commandId, cwd2, handlers, aiRunTimeoutMs, run11.controller.signal);
|
|
42009
|
+
} finally {
|
|
42010
|
+
slot.handle.release();
|
|
42011
|
+
}
|
|
42012
|
+
}
|
|
41682
42013
|
} finally {
|
|
41683
42014
|
ctx.bus.endBusy(plan.cmd);
|
|
41684
42015
|
}
|
|
42016
|
+
if (result.cancelled) {
|
|
42017
|
+
const text = "\u25A0 stopped";
|
|
42018
|
+
ctx.bus.push({ source: origin, kind: "log", text, level: "warn" });
|
|
42019
|
+
ctx.send(WsChannels.COMMAND_OUTPUT, { commandId, seq: seq++, chunk: `${text}
|
|
42020
|
+
`, log: true });
|
|
42021
|
+
}
|
|
41685
42022
|
const profileLabel = result.workedDir ? profileDisplayLabel(result.workedDir) : null;
|
|
41686
42023
|
if (result.workedDir) {
|
|
41687
42024
|
if (unified && result.workedKey) {
|
|
@@ -41696,14 +42033,16 @@ ${guardedPrompt}`;
|
|
|
41696
42033
|
seq: seq++,
|
|
41697
42034
|
chunk: "",
|
|
41698
42035
|
done: true,
|
|
41699
|
-
exitCode: result.exitCode
|
|
42036
|
+
exitCode: result.exitCode,
|
|
42037
|
+
// A stopped run (ADR-0362): the server settles it `cancelled` and the web applies nothing.
|
|
42038
|
+
...result.cancelled ? { cancelled: true } : {}
|
|
41700
42039
|
});
|
|
41701
42040
|
finishCommand(commandId, result.exitCode, result.usage);
|
|
41702
42041
|
ctx.send(WsChannels.COMMAND_HISTORY, {
|
|
41703
42042
|
commandId,
|
|
41704
42043
|
cmd: plan.cmd,
|
|
41705
42044
|
args: markerArgs,
|
|
41706
|
-
status: result.exitCode === 0 ? "done" : "failed",
|
|
42045
|
+
status: result.cancelled ? "cancelled" : result.exitCode === 0 ? "done" : "failed",
|
|
41707
42046
|
exitCode: result.exitCode,
|
|
41708
42047
|
tokens: result.usage.tokens,
|
|
41709
42048
|
// The ai_tokens split for this run (ADR-0145); attached only when we metered real tokens.
|
|
@@ -41739,13 +42078,13 @@ ${guardedPrompt}`;
|
|
|
41739
42078
|
});
|
|
41740
42079
|
ctx.bus.addTokens?.(result.usage.tokens);
|
|
41741
42080
|
}
|
|
41742
|
-
ctx.reportUsage(events, profileLabel);
|
|
42081
|
+
if (!neverStarted) ctx.reportUsage(events, profileLabel);
|
|
41743
42082
|
void ctx.pollUsage();
|
|
41744
42083
|
ctx.bus.push({
|
|
41745
42084
|
source: origin,
|
|
41746
42085
|
kind: "exit",
|
|
41747
|
-
text: result.exitCode === 0 ? "\u2713 ready \u2014 enter your next prompt" : `\u2717 ${plan.cmd} failed (exit ${result.exitCode}) \u2014 see the error above`,
|
|
41748
|
-
level: result.exitCode === 0 ? "info" : "error",
|
|
42086
|
+
text: result.exitCode === 0 ? "\u2713 ready \u2014 enter your next prompt" : result.cancelled ? "\u25A0 stopped \u2014 enter your next prompt" : `\u2717 ${plan.cmd} failed (exit ${result.exitCode}) \u2014 see the error above`,
|
|
42087
|
+
level: result.exitCode === 0 ? "info" : result.cancelled ? "warn" : "error",
|
|
41749
42088
|
durationMs: Date.now() - aiStartedMs
|
|
41750
42089
|
});
|
|
41751
42090
|
if (memCfg.enabled && result.exitCode === 0) {
|
|
@@ -42222,11 +42561,11 @@ async function allAiProfilesExhausted(ctx, cfg) {
|
|
|
42222
42561
|
|
|
42223
42562
|
// src/core/fs-browse.ts
|
|
42224
42563
|
import { readdir as readdir3 } from "fs/promises";
|
|
42225
|
-
import { resolve as
|
|
42564
|
+
import { resolve as resolve4, sep as sep2 } from "path";
|
|
42226
42565
|
async function listDir(path, root) {
|
|
42227
42566
|
if (!root) return { path: "", entries: [] };
|
|
42228
|
-
const base =
|
|
42229
|
-
const requested =
|
|
42567
|
+
const base = resolve4(root);
|
|
42568
|
+
const requested = resolve4(base, path && path.trim() ? path : ".");
|
|
42230
42569
|
const target = requested === base || requested.startsWith(base + sep2) ? requested : base;
|
|
42231
42570
|
try {
|
|
42232
42571
|
const dirents = await readdir3(target, { withFileTypes: true });
|
|
@@ -42245,12 +42584,12 @@ async function listDir(path, root) {
|
|
|
42245
42584
|
|
|
42246
42585
|
// src/core/fs-read.ts
|
|
42247
42586
|
import { readFile as readFile6 } from "fs/promises";
|
|
42248
|
-
import { resolve as
|
|
42587
|
+
import { resolve as resolve5, sep as sep3 } from "path";
|
|
42249
42588
|
var MAX_BYTES = 256 * 1024;
|
|
42250
42589
|
function resolveInRoot(root, relPath) {
|
|
42251
42590
|
if (!root) return null;
|
|
42252
|
-
const base =
|
|
42253
|
-
const target =
|
|
42591
|
+
const base = resolve5(root);
|
|
42592
|
+
const target = resolve5(base, relPath || ".");
|
|
42254
42593
|
return target === base || target.startsWith(base + sep3) ? target : null;
|
|
42255
42594
|
}
|
|
42256
42595
|
async function readWorkerFile(root, path) {
|
|
@@ -42270,12 +42609,12 @@ async function readWorkerFile(root, path) {
|
|
|
42270
42609
|
|
|
42271
42610
|
// src/core/fs-write.ts
|
|
42272
42611
|
import { mkdir as mkdir4, writeFile as writeFile5 } from "fs/promises";
|
|
42273
|
-
import { dirname as
|
|
42612
|
+
import { dirname as dirname5, resolve as resolve6, sep as sep4 } from "path";
|
|
42274
42613
|
var MAX_BYTES2 = 2 * 1024 * 1024;
|
|
42275
42614
|
function resolveInRoot2(root, relPath) {
|
|
42276
42615
|
if (!root) return null;
|
|
42277
|
-
const base =
|
|
42278
|
-
const target =
|
|
42616
|
+
const base = resolve6(root);
|
|
42617
|
+
const target = resolve6(base, relPath || ".");
|
|
42279
42618
|
return target === base || target.startsWith(base + sep4) ? target : null;
|
|
42280
42619
|
}
|
|
42281
42620
|
async function writeWorkerFile(root, relPath, content) {
|
|
@@ -42288,7 +42627,7 @@ async function writeWorkerFile(root, relPath, content) {
|
|
|
42288
42627
|
return { ok: false, path: target, bytes: 0, error: `content too large (max ${MAX_BYTES2} bytes)` };
|
|
42289
42628
|
}
|
|
42290
42629
|
try {
|
|
42291
|
-
await mkdir4(
|
|
42630
|
+
await mkdir4(dirname5(target), { recursive: true });
|
|
42292
42631
|
await writeFile5(target, content, "utf8");
|
|
42293
42632
|
return { ok: true, path: target, bytes };
|
|
42294
42633
|
} catch (err) {
|
|
@@ -42298,11 +42637,11 @@ async function writeWorkerFile(root, relPath, content) {
|
|
|
42298
42637
|
|
|
42299
42638
|
// src/core/fs-mutate.ts
|
|
42300
42639
|
import { mkdir as mkdir5, writeFile as writeFile6, rename, rm as rm3, stat as stat2 } from "fs/promises";
|
|
42301
|
-
import { basename as basename3, dirname as
|
|
42640
|
+
import { basename as basename3, dirname as dirname6, resolve as resolve7, sep as sep5 } from "path";
|
|
42302
42641
|
var MAX_BYTES3 = 2 * 1024 * 1024;
|
|
42303
42642
|
function resolveInRoot3(root, rel) {
|
|
42304
|
-
const base =
|
|
42305
|
-
const target =
|
|
42643
|
+
const base = resolve7(root);
|
|
42644
|
+
const target = resolve7(base, rel || ".");
|
|
42306
42645
|
return target === base || target.startsWith(base + sep5) ? target : null;
|
|
42307
42646
|
}
|
|
42308
42647
|
async function isDir(p2) {
|
|
@@ -42327,19 +42666,19 @@ async function mutateFs(root, req) {
|
|
|
42327
42666
|
switch (req.op) {
|
|
42328
42667
|
case "mkdir": {
|
|
42329
42668
|
const target = resolveInRoot3(root, req.path ?? "");
|
|
42330
|
-
if (!target || target ===
|
|
42669
|
+
if (!target || target === resolve7(root)) return escaped(req.path ?? "");
|
|
42331
42670
|
await mkdir5(target, { recursive: true });
|
|
42332
42671
|
return { ok: true, path: target };
|
|
42333
42672
|
}
|
|
42334
42673
|
case "create": {
|
|
42335
42674
|
const target = resolveInRoot3(root, req.path ?? "");
|
|
42336
|
-
if (!target || target ===
|
|
42675
|
+
if (!target || target === resolve7(root)) return escaped(req.path ?? "");
|
|
42337
42676
|
if (await exists(target)) return { ok: false, path: target, error: "already exists" };
|
|
42338
42677
|
const content = req.content ?? "";
|
|
42339
42678
|
if (Buffer.byteLength(content, "utf8") > MAX_BYTES3) {
|
|
42340
42679
|
return { ok: false, path: target, error: `content too large (max ${MAX_BYTES3} bytes)` };
|
|
42341
42680
|
}
|
|
42342
|
-
await mkdir5(
|
|
42681
|
+
await mkdir5(dirname6(target), { recursive: true });
|
|
42343
42682
|
await writeFile6(target, content, "utf8");
|
|
42344
42683
|
return { ok: true, path: target };
|
|
42345
42684
|
}
|
|
@@ -42347,18 +42686,18 @@ async function mutateFs(root, req) {
|
|
|
42347
42686
|
const from = resolveInRoot3(root, req.from ?? "");
|
|
42348
42687
|
let to2 = resolveInRoot3(root, req.to ?? "");
|
|
42349
42688
|
if (!from || !to2) return escaped(req.from ?? req.to ?? "");
|
|
42350
|
-
if (from ===
|
|
42351
|
-
if (await isDir(to2)) to2 =
|
|
42352
|
-
if (to2 !==
|
|
42689
|
+
if (from === resolve7(root)) return { ok: false, path: from, error: "cannot move the project root" };
|
|
42690
|
+
if (await isDir(to2)) to2 = resolve7(to2, basename3(from));
|
|
42691
|
+
if (to2 !== resolve7(root) && !to2.startsWith(resolve7(root) + sep5)) return escaped(req.to ?? "");
|
|
42353
42692
|
if (await exists(to2)) return { ok: false, path: to2, error: "destination already exists" };
|
|
42354
|
-
await mkdir5(
|
|
42693
|
+
await mkdir5(dirname6(to2), { recursive: true });
|
|
42355
42694
|
await rename(from, to2);
|
|
42356
42695
|
return { ok: true, path: to2 };
|
|
42357
42696
|
}
|
|
42358
42697
|
case "delete": {
|
|
42359
42698
|
const target = resolveInRoot3(root, req.path ?? "");
|
|
42360
42699
|
if (!target) return escaped(req.path ?? "");
|
|
42361
|
-
if (target ===
|
|
42700
|
+
if (target === resolve7(root)) return { ok: false, path: target, error: "cannot delete the project root" };
|
|
42362
42701
|
await rm3(target, { recursive: true, force: true });
|
|
42363
42702
|
return { ok: true, path: target };
|
|
42364
42703
|
}
|
|
@@ -42372,11 +42711,11 @@ async function mutateFs(root, req) {
|
|
|
42372
42711
|
|
|
42373
42712
|
// src/core/fs-transfer.ts
|
|
42374
42713
|
import { mkdir as mkdir6, readFile as readFile7, stat as stat3, writeFile as writeFile7 } from "fs/promises";
|
|
42375
|
-
import { basename as basename4, dirname as
|
|
42714
|
+
import { basename as basename4, dirname as dirname7, extname, resolve as resolve8, sep as sep6 } from "path";
|
|
42376
42715
|
function resolveInRoot4(root, relPath) {
|
|
42377
42716
|
if (!root) return null;
|
|
42378
|
-
const base =
|
|
42379
|
-
const target =
|
|
42717
|
+
const base = resolve8(root);
|
|
42718
|
+
const target = resolve8(base, relPath || ".");
|
|
42380
42719
|
return target === base || target.startsWith(base + sep6) ? target : null;
|
|
42381
42720
|
}
|
|
42382
42721
|
var MIME_BY_EXT = {
|
|
@@ -42415,7 +42754,7 @@ async function uploadBinaryFile(root, relPath, contentBase64) {
|
|
|
42415
42754
|
return { ok: false, path: target, bytes: 0, error: `file too large (max ${FS_TRANSFER_MAX_BYTES} bytes)` };
|
|
42416
42755
|
}
|
|
42417
42756
|
try {
|
|
42418
|
-
await mkdir6(
|
|
42757
|
+
await mkdir6(dirname7(target), { recursive: true });
|
|
42419
42758
|
await writeFile7(target, buf);
|
|
42420
42759
|
return { ok: true, path: target, bytes: buf.byteLength };
|
|
42421
42760
|
} catch (err) {
|
|
@@ -42491,14 +42830,14 @@ function handleFsChannels(ctx, message, payload) {
|
|
|
42491
42830
|
|
|
42492
42831
|
// src/core/git-diff.ts
|
|
42493
42832
|
import { readFile as readFile8 } from "fs/promises";
|
|
42494
|
-
import { execFile as
|
|
42495
|
-
import { basename as basename5, dirname as
|
|
42496
|
-
import { promisify as
|
|
42497
|
-
var
|
|
42833
|
+
import { execFile as execFile5 } from "child_process";
|
|
42834
|
+
import { basename as basename5, dirname as dirname8, resolve as resolve9, sep as sep7 } from "path";
|
|
42835
|
+
import { promisify as promisify5 } from "util";
|
|
42836
|
+
var run5 = promisify5(execFile5);
|
|
42498
42837
|
function resolveInRoot5(root, relPath) {
|
|
42499
42838
|
if (!root) return null;
|
|
42500
|
-
const base =
|
|
42501
|
-
const target =
|
|
42839
|
+
const base = resolve9(root);
|
|
42840
|
+
const target = resolve9(base, relPath || ".");
|
|
42502
42841
|
return target === base || target.startsWith(base + sep7) ? target : null;
|
|
42503
42842
|
}
|
|
42504
42843
|
function looksBinary(buf) {
|
|
@@ -42513,7 +42852,7 @@ async function gitDiff(root, path) {
|
|
|
42513
42852
|
if (!target) {
|
|
42514
42853
|
return { path, oldContent: "", newContent: "", isBinary: false };
|
|
42515
42854
|
}
|
|
42516
|
-
const dir2 =
|
|
42855
|
+
const dir2 = dirname8(target);
|
|
42517
42856
|
const base = basename5(target);
|
|
42518
42857
|
let newBuf = null;
|
|
42519
42858
|
try {
|
|
@@ -42522,7 +42861,7 @@ async function gitDiff(root, path) {
|
|
|
42522
42861
|
}
|
|
42523
42862
|
let oldBuf = null;
|
|
42524
42863
|
try {
|
|
42525
|
-
const { stdout } = await
|
|
42864
|
+
const { stdout } = await run5("git", ["show", `HEAD:./${base}`], {
|
|
42526
42865
|
cwd: dir2,
|
|
42527
42866
|
timeout: 15e3,
|
|
42528
42867
|
maxBuffer: FS_TRANSFER_MAX_BYTES + 1024,
|
|
@@ -42552,12 +42891,12 @@ async function gitDiff(root, path) {
|
|
|
42552
42891
|
}
|
|
42553
42892
|
|
|
42554
42893
|
// src/core/git-env.ts
|
|
42555
|
-
import { execFile as
|
|
42556
|
-
import { promisify as
|
|
42557
|
-
var
|
|
42894
|
+
import { execFile as execFile6 } from "child_process";
|
|
42895
|
+
import { promisify as promisify6 } from "util";
|
|
42896
|
+
var run6 = promisify6(execFile6);
|
|
42558
42897
|
async function hasBinary(bin, args = ["--version"]) {
|
|
42559
42898
|
try {
|
|
42560
|
-
await
|
|
42899
|
+
await run6(bin, args, { timeout: 5e3 });
|
|
42561
42900
|
return true;
|
|
42562
42901
|
} catch {
|
|
42563
42902
|
return false;
|
|
@@ -42570,7 +42909,7 @@ async function checkGitEnv(provider) {
|
|
|
42570
42909
|
let account = null;
|
|
42571
42910
|
if (installed) {
|
|
42572
42911
|
try {
|
|
42573
|
-
const { stdout, stderr } = await
|
|
42912
|
+
const { stdout, stderr } = await run6(bin, ["auth", "status"], { timeout: 8e3 });
|
|
42574
42913
|
const out = `${stdout}
|
|
42575
42914
|
${stderr}`;
|
|
42576
42915
|
authenticated = /logged in|logged into|✓/i.test(out);
|
|
@@ -42583,11 +42922,11 @@ ${stderr}`;
|
|
|
42583
42922
|
}
|
|
42584
42923
|
|
|
42585
42924
|
// src/core/git-ssh-key.ts
|
|
42586
|
-
import { execFile as
|
|
42925
|
+
import { execFile as execFile7 } from "child_process";
|
|
42587
42926
|
import { readFile as readFile9, rm as rm4, mkdir as mkdir7 } from "fs/promises";
|
|
42588
42927
|
import { join as join21 } from "path";
|
|
42589
|
-
import { promisify as
|
|
42590
|
-
var
|
|
42928
|
+
import { promisify as promisify7 } from "util";
|
|
42929
|
+
var run7 = promisify7(execFile7);
|
|
42591
42930
|
function keyPaths(profileDir2) {
|
|
42592
42931
|
const dir2 = join21(profileDir2, ".ssh");
|
|
42593
42932
|
const key = join21(dir2, "id_4pm");
|
|
@@ -42602,14 +42941,14 @@ async function readPublicKey(pub) {
|
|
|
42602
42941
|
}
|
|
42603
42942
|
async function fingerprintOf(pub) {
|
|
42604
42943
|
try {
|
|
42605
|
-
const { stdout } = await
|
|
42944
|
+
const { stdout } = await run7("ssh-keygen", ["-lf", pub], { timeout: 5e3 });
|
|
42606
42945
|
return stdout.match(/SHA256:[A-Za-z0-9+/=]+/)?.[0] ?? null;
|
|
42607
42946
|
} catch {
|
|
42608
42947
|
return null;
|
|
42609
42948
|
}
|
|
42610
42949
|
}
|
|
42611
42950
|
async function configureGit(key) {
|
|
42612
|
-
await
|
|
42951
|
+
await run7(
|
|
42613
42952
|
"git",
|
|
42614
42953
|
["config", "--global", "core.sshCommand", `ssh -i ${key} -o IdentitiesOnly=yes`],
|
|
42615
42954
|
{ timeout: 5e3 }
|
|
@@ -42620,7 +42959,7 @@ async function generate(profileDir2) {
|
|
|
42620
42959
|
await mkdir7(dir2, { recursive: true });
|
|
42621
42960
|
await rm4(key, { force: true });
|
|
42622
42961
|
await rm4(pub, { force: true });
|
|
42623
|
-
await
|
|
42962
|
+
await run7("ssh-keygen", ["-t", "ed25519", "-f", key, "-N", "", "-C", "4pm-deploy-key"], {
|
|
42624
42963
|
timeout: 15e3
|
|
42625
42964
|
});
|
|
42626
42965
|
await configureGit(key);
|
|
@@ -42630,7 +42969,7 @@ async function remove(profileDir2) {
|
|
|
42630
42969
|
const { key, pub } = keyPaths(profileDir2);
|
|
42631
42970
|
await rm4(key, { force: true });
|
|
42632
42971
|
await rm4(pub, { force: true });
|
|
42633
|
-
await
|
|
42972
|
+
await run7("git", ["config", "--global", "--unset", "core.sshCommand"], { timeout: 5e3 }).catch(
|
|
42634
42973
|
() => void 0
|
|
42635
42974
|
);
|
|
42636
42975
|
return { publicKey: null, fingerprint: null };
|
|
@@ -42651,21 +42990,21 @@ async function manageSshKey(op, profileDir2) {
|
|
|
42651
42990
|
}
|
|
42652
42991
|
|
|
42653
42992
|
// src/core/git-history.ts
|
|
42654
|
-
import { execFile as
|
|
42655
|
-
import { resolve as
|
|
42656
|
-
import { promisify as
|
|
42657
|
-
var
|
|
42993
|
+
import { execFile as execFile8 } from "child_process";
|
|
42994
|
+
import { resolve as resolve10, sep as sep8 } from "path";
|
|
42995
|
+
import { promisify as promisify8 } from "util";
|
|
42996
|
+
var run8 = promisify8(execFile8);
|
|
42658
42997
|
var FS = "";
|
|
42659
42998
|
var RS = "";
|
|
42660
42999
|
function resolveRepo(root, repo) {
|
|
42661
43000
|
if (!root) return null;
|
|
42662
|
-
const base =
|
|
42663
|
-
const requested =
|
|
43001
|
+
const base = resolve10(root);
|
|
43002
|
+
const requested = resolve10(base, repo && repo.trim() ? repo : ".");
|
|
42664
43003
|
return requested === base || requested.startsWith(base + sep8) ? requested : base;
|
|
42665
43004
|
}
|
|
42666
43005
|
async function git(cwd2, args) {
|
|
42667
43006
|
try {
|
|
42668
|
-
const { stdout } = await
|
|
43007
|
+
const { stdout } = await run8("git", args, {
|
|
42669
43008
|
cwd: cwd2,
|
|
42670
43009
|
timeout: 15e3,
|
|
42671
43010
|
maxBuffer: 8 * 1024 * 1024
|
|
@@ -42677,7 +43016,7 @@ async function git(cwd2, args) {
|
|
|
42677
43016
|
}
|
|
42678
43017
|
async function gitRepos(root) {
|
|
42679
43018
|
if (!root) return { repos: [] };
|
|
42680
|
-
const base =
|
|
43019
|
+
const base = resolve10(root);
|
|
42681
43020
|
const top = (await git(base, ["rev-parse", "--show-toplevel"])).trim();
|
|
42682
43021
|
if (!top || !(top === base || top.startsWith(base + sep8))) return { repos: [] };
|
|
42683
43022
|
const repos = [];
|
|
@@ -42689,7 +43028,7 @@ async function gitRepos(root) {
|
|
|
42689
43028
|
const m2 = line.trim().match(/^[-+U ]?[0-9a-f]{7,40}\s+(\S+)/);
|
|
42690
43029
|
const subPath = m2?.[1];
|
|
42691
43030
|
if (!subPath) continue;
|
|
42692
|
-
const abs =
|
|
43031
|
+
const abs = resolve10(base, subPath);
|
|
42693
43032
|
if (!(abs === base || abs.startsWith(base + sep8))) continue;
|
|
42694
43033
|
const remote = (await git(abs, ["remote", "get-url", "origin"])).trim();
|
|
42695
43034
|
const branch = (await git(abs, ["rev-parse", "--abbrev-ref", "HEAD"])).trim();
|
|
@@ -42807,6 +43146,74 @@ function handleGitChannels(ctx, message, payload) {
|
|
|
42807
43146
|
// src/core/ws-client/handlers/misc.ts
|
|
42808
43147
|
import { join as join22 } from "path";
|
|
42809
43148
|
|
|
43149
|
+
// src/core/ai-assist.ts
|
|
43150
|
+
import { execFile as execFile9 } from "child_process";
|
|
43151
|
+
import { promisify as promisify9 } from "util";
|
|
43152
|
+
var run9 = promisify9(execFile9);
|
|
43153
|
+
function aiCli() {
|
|
43154
|
+
return process.env.AI_CLI || "claude";
|
|
43155
|
+
}
|
|
43156
|
+
function aiArgs(cli, prompt) {
|
|
43157
|
+
if (cli === "claude") return ["-p", prompt, "--output-format", "json", ...denySettingsArgs(cli, [process.env.CLAUDE_CONFIG_DIR])];
|
|
43158
|
+
if (cli === "codex") return ["exec", "--json", prompt];
|
|
43159
|
+
return ["-p", prompt];
|
|
43160
|
+
}
|
|
43161
|
+
function estimateTokens2(prompt, output) {
|
|
43162
|
+
return Math.max(1, Math.ceil((prompt.length + output.length) / 4));
|
|
43163
|
+
}
|
|
43164
|
+
function parseAi(cli, stdout, prompt) {
|
|
43165
|
+
if (cli === "claude") {
|
|
43166
|
+
try {
|
|
43167
|
+
const j2 = JSON.parse(stdout);
|
|
43168
|
+
const text2 = (j2.result ?? "").trim();
|
|
43169
|
+
const tokens = (j2.usage?.input_tokens ?? 0) + (j2.usage?.output_tokens ?? 0);
|
|
43170
|
+
if (text2 || tokens) return { text: text2, tokens: tokens || estimateTokens2(prompt, text2) };
|
|
43171
|
+
} catch {
|
|
43172
|
+
}
|
|
43173
|
+
}
|
|
43174
|
+
if (cli === "codex") {
|
|
43175
|
+
try {
|
|
43176
|
+
let text2 = "";
|
|
43177
|
+
let tokens = 0;
|
|
43178
|
+
for (const line of stdout.split("\n").filter(Boolean)) {
|
|
43179
|
+
const obj = JSON.parse(line);
|
|
43180
|
+
if (obj.text) text2 = obj.text;
|
|
43181
|
+
else if (obj.message) text2 = obj.message;
|
|
43182
|
+
if (obj.usage) {
|
|
43183
|
+
tokens = obj.usage.total_tokens ?? (obj.usage.input_tokens ?? 0) + (obj.usage.output_tokens ?? 0);
|
|
43184
|
+
}
|
|
43185
|
+
}
|
|
43186
|
+
if (text2 || tokens) return { text: text2.trim(), tokens: tokens || estimateTokens2(prompt, text2) };
|
|
43187
|
+
} catch {
|
|
43188
|
+
}
|
|
43189
|
+
}
|
|
43190
|
+
const text = stdout.trim();
|
|
43191
|
+
return { text, tokens: estimateTokens2(prompt, text) };
|
|
43192
|
+
}
|
|
43193
|
+
async function runAi(prompt) {
|
|
43194
|
+
const cli = aiCli();
|
|
43195
|
+
const { stdout } = await run9(cli, aiArgs(cli, prompt), {
|
|
43196
|
+
timeout: 12e4,
|
|
43197
|
+
maxBuffer: 8 * 1024 * 1024
|
|
43198
|
+
});
|
|
43199
|
+
return parseAi(cli, stdout, prompt);
|
|
43200
|
+
}
|
|
43201
|
+
async function aiOutboundReview(input, allowedRepos, projectPath) {
|
|
43202
|
+
const prompt = `You are a security reviewer for an autonomous coding agent. Decide if the following INPUT is SAFE to run. It is UNSAFE if it: (1) asks the AI using secret VALUES (api keys, env values, passwords); (2) would affect other environments (only the sandbox project is allowed, no ssh/deploy/prod); (3) edits outside the project folder "${projectPath}"; (4) commits/pushes to a repo not in this allowlist: ${allowedRepos.length ? allowedRepos.join(", ") : "(none declared)"}. Reply with ONLY JSON: {"ok": <true|false>, "reasons": ["secret"|"environment"|"path"|"repo", ...]}. INPUT:
|
|
43203
|
+
${input}`;
|
|
43204
|
+
const { text, tokens } = await runAi(prompt);
|
|
43205
|
+
const start = text.indexOf("{");
|
|
43206
|
+
const end = text.lastIndexOf("}");
|
|
43207
|
+
if (start < 0 || end <= start) return { ok: false, reasons: ["ai_parse"], tokens };
|
|
43208
|
+
try {
|
|
43209
|
+
const j2 = JSON.parse(text.slice(start, end + 1));
|
|
43210
|
+
const reasons = Array.isArray(j2.reasons) ? j2.reasons.filter((r) => typeof r === "string") : [];
|
|
43211
|
+
return { ok: j2.ok === true && reasons.length === 0, reasons, tokens };
|
|
43212
|
+
} catch {
|
|
43213
|
+
return { ok: false, reasons: ["ai_parse"], tokens };
|
|
43214
|
+
}
|
|
43215
|
+
}
|
|
43216
|
+
|
|
42810
43217
|
// src/core/outbound-review.ts
|
|
42811
43218
|
function compilePatterns(sources, fallback) {
|
|
42812
43219
|
const list = sources ?? fallback;
|
|
@@ -42918,8 +43325,74 @@ function handleMiscChannels(ctx, message, payload) {
|
|
|
42918
43325
|
}
|
|
42919
43326
|
|
|
42920
43327
|
// src/core/ws-client/handlers/project.ts
|
|
42921
|
-
import { existsSync as
|
|
43328
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync7, renameSync, rmSync as rmSync7 } from "fs";
|
|
42922
43329
|
import { join as join23 } from "path";
|
|
43330
|
+
|
|
43331
|
+
// src/core/ai-task.ts
|
|
43332
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
43333
|
+
function createAiTaskRunner(ctx) {
|
|
43334
|
+
return {
|
|
43335
|
+
acquireSlot: (onQueued) => acquireRunSlot(ctx, onQueued),
|
|
43336
|
+
generate: (prompt, cwd2, label) => generate2(ctx, prompt, cwd2, label)
|
|
43337
|
+
};
|
|
43338
|
+
}
|
|
43339
|
+
async function generate2(ctx, prompt, cwd2, label) {
|
|
43340
|
+
const config = readProfileConfig(ctx.profileDir);
|
|
43341
|
+
const cmd = config.aiCli || "claude";
|
|
43342
|
+
const unified = isUnifiedConfig(config);
|
|
43343
|
+
const hint = unified ? {
|
|
43344
|
+
credential: getPinnedCredential(ctx.profileDir) ?? ((config.aiFailoverMode ?? "remember") === "remember" ? getWorkingCredential(ctx.profileDir) : null)
|
|
43345
|
+
} : { dir: getWorkingProfile(ctx.profileDir, cmd) };
|
|
43346
|
+
const plan = planAiRun(prompt, config, hint, /* @__PURE__ */ new Map(), true);
|
|
43347
|
+
const timeoutSec = config.projectAiRunTimeoutSec || config.aiRunTimeoutSec || 0;
|
|
43348
|
+
ctx.bus.push({ source: "system", kind: "aireq", text: `${plan.cmd} \u2039 ${label}` });
|
|
43349
|
+
let text = "";
|
|
43350
|
+
const handlers = {
|
|
43351
|
+
onChunk: (chunk) => {
|
|
43352
|
+
text += chunk;
|
|
43353
|
+
},
|
|
43354
|
+
onAttemptStart: (profile, index, total, attemptCmd) => {
|
|
43355
|
+
ctx.bus.push({ source: "system", kind: "log", text: `\u2192 trying ${attemptCmd} profile "${profile}" (${index + 1}/${total})\u2026` });
|
|
43356
|
+
if (index > 0) text = "";
|
|
43357
|
+
},
|
|
43358
|
+
onAttemptFail: (profile) => {
|
|
43359
|
+
ctx.bus.push({ source: "system", kind: "log", text: `profile "${profile}" failed \u2014 trying next`, level: "warn" });
|
|
43360
|
+
}
|
|
43361
|
+
};
|
|
43362
|
+
ctx.bus.startBusy(plan.cmd);
|
|
43363
|
+
let result;
|
|
43364
|
+
try {
|
|
43365
|
+
result = await runAiFailover(plan, `ai-task-${randomUUID2()}`, cwd2, handlers, timeoutSec * 1e3);
|
|
43366
|
+
} finally {
|
|
43367
|
+
ctx.bus.endBusy(plan.cmd);
|
|
43368
|
+
}
|
|
43369
|
+
const occurredAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
43370
|
+
const events = [{ metric: UsageMetric.COMMANDS, amount: 1, occurredAt }];
|
|
43371
|
+
if (result.exitCode === 0 && result.usage.tokens > 0) {
|
|
43372
|
+
events.push({
|
|
43373
|
+
metric: UsageMetric.AI_TOKENS,
|
|
43374
|
+
amount: result.usage.tokens,
|
|
43375
|
+
occurredAt,
|
|
43376
|
+
inputTokens: result.usage.input,
|
|
43377
|
+
outputTokens: result.usage.output,
|
|
43378
|
+
cacheReadTokens: result.usage.cacheRead,
|
|
43379
|
+
cacheCreationTokens: result.usage.cacheCreation,
|
|
43380
|
+
provider: aiProviderOf(result.workedCmd ?? plan.cmd),
|
|
43381
|
+
authMode: resolveClaudeAuthMode(config)
|
|
43382
|
+
});
|
|
43383
|
+
}
|
|
43384
|
+
ctx.reportUsage(events, result.workedDir ? profileDisplayLabel(result.workedDir) : null);
|
|
43385
|
+
const ok = result.exitCode === 0 && text.trim().length > 0;
|
|
43386
|
+
ctx.bus.push({
|
|
43387
|
+
source: "system",
|
|
43388
|
+
kind: ok ? "aires" : "log",
|
|
43389
|
+
text: ok ? `${plan.cmd} \u203A ${label} \u2713` : `${label}: AI run failed (exit ${result.exitCode}) \u2014 using the fallback`,
|
|
43390
|
+
...ok ? {} : { level: "warn" }
|
|
43391
|
+
});
|
|
43392
|
+
return ok ? text.trim() : null;
|
|
43393
|
+
}
|
|
43394
|
+
|
|
43395
|
+
// src/core/ws-client/handlers/project.ts
|
|
42923
43396
|
function handleProjectChannels(ctx, message, payload) {
|
|
42924
43397
|
switch (message.channel) {
|
|
42925
43398
|
case WsChannels.PHYSIC_SYNC: {
|
|
@@ -42931,11 +43404,11 @@ function handleProjectChannels(ctx, message, payload) {
|
|
|
42931
43404
|
const oldRoot = ctx.physicFolderPath(oldName);
|
|
42932
43405
|
if (oldRoot) void uninstallCron(oldRoot).catch(() => void 0);
|
|
42933
43406
|
}
|
|
42934
|
-
if (oldPath && oldName !== sync.newName &&
|
|
43407
|
+
if (oldPath && oldName !== sync.newName && existsSync14(oldPath) && !existsSync14(newPath)) {
|
|
42935
43408
|
renameSync(oldPath, newPath);
|
|
42936
43409
|
} else {
|
|
42937
|
-
if (oldPath && oldName !== sync.newName &&
|
|
42938
|
-
|
|
43410
|
+
if (oldPath && oldName !== sync.newName && existsSync14(oldPath)) rmSync7(oldPath, { recursive: true, force: true });
|
|
43411
|
+
mkdirSync7(newPath, { recursive: true });
|
|
42939
43412
|
}
|
|
42940
43413
|
ctx.physicRoot = ctx.physicFolderPath(sync.newName);
|
|
42941
43414
|
ctx.bus.setProject(sync.newName);
|
|
@@ -42948,7 +43421,7 @@ function handleProjectChannels(ctx, message, payload) {
|
|
|
42948
43421
|
if (del.name) {
|
|
42949
43422
|
const delRoot = ctx.physicFolderPath(del.name);
|
|
42950
43423
|
if (delRoot) void uninstallCron(delRoot).catch(() => void 0);
|
|
42951
|
-
|
|
43424
|
+
rmSync7(join23(ctx.profileDir, del.name), { recursive: true, force: true });
|
|
42952
43425
|
ctx.bus.log(t("project.folderDeleted", { name: del.name }));
|
|
42953
43426
|
}
|
|
42954
43427
|
void manageSshKey("delete", ctx.profileDir).catch(() => void 0);
|
|
@@ -42977,14 +43450,18 @@ function handleProjectChannels(ctx, message, payload) {
|
|
|
42977
43450
|
void scaffoldProject(
|
|
42978
43451
|
payload,
|
|
42979
43452
|
ctx.profileDir,
|
|
42980
|
-
(p2) => ctx.send(WsChannels.PROJECT_PROGRESS, p2)
|
|
43453
|
+
(p2) => ctx.send(WsChannels.PROJECT_PROGRESS, p2),
|
|
43454
|
+
// AI init on the standard AI path + one org run slot (ADR-0362).
|
|
43455
|
+
createAiTaskRunner(ctx)
|
|
42981
43456
|
).then((reply) => ctx.send(WsChannels.PROJECT_CREATE, reply, message.id));
|
|
42982
43457
|
return true;
|
|
42983
43458
|
case WsChannels.PROJECT_ADD:
|
|
42984
43459
|
void addProject(
|
|
42985
43460
|
payload,
|
|
42986
43461
|
ctx.profileDir,
|
|
42987
|
-
(p2) => ctx.send(WsChannels.PROJECT_PROGRESS, p2)
|
|
43462
|
+
(p2) => ctx.send(WsChannels.PROJECT_PROGRESS, p2),
|
|
43463
|
+
// An add-with-scaffold runs AI init too (ADR-0362).
|
|
43464
|
+
createAiTaskRunner(ctx)
|
|
42988
43465
|
).then((reply) => ctx.send(WsChannels.PROJECT_ADD, reply, message.id));
|
|
42989
43466
|
return true;
|
|
42990
43467
|
default:
|
|
@@ -43012,7 +43489,7 @@ Extra focus from the author: ${req.prompt}` : "";
|
|
|
43012
43489
|
].join("\n");
|
|
43013
43490
|
}
|
|
43014
43491
|
function runOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
|
|
43015
|
-
return new Promise((
|
|
43492
|
+
return new Promise((resolve12, reject) => {
|
|
43016
43493
|
const args = ["-p", ...profile?.model ? ["--model", profile.model] : [], ...denySettingsArgs(cmd, [...denyDirs, profile?.dir])];
|
|
43017
43494
|
const env3 = {
|
|
43018
43495
|
...process.env,
|
|
@@ -43035,7 +43512,7 @@ function runOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
|
|
|
43035
43512
|
});
|
|
43036
43513
|
child.on("close", (code) => {
|
|
43037
43514
|
clearTimeout(timer);
|
|
43038
|
-
|
|
43515
|
+
resolve12({ code: code ?? -1, out, err });
|
|
43039
43516
|
});
|
|
43040
43517
|
child.stdin.write(prompt);
|
|
43041
43518
|
child.stdin.end();
|
|
@@ -43066,8 +43543,8 @@ async function runKnowledgeCompose(req, ai2, physicRoot) {
|
|
|
43066
43543
|
|
|
43067
43544
|
// src/core/faq-compose.ts
|
|
43068
43545
|
import { spawn as spawn9, execFile as execFile10 } from "child_process";
|
|
43069
|
-
import { existsSync as
|
|
43070
|
-
import { randomUUID as
|
|
43546
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync8, rmSync as rmSync8 } from "fs";
|
|
43547
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
43071
43548
|
import { join as join24 } from "path";
|
|
43072
43549
|
import { tmpdir as tmpdir4 } from "os";
|
|
43073
43550
|
import { promisify as promisify10 } from "util";
|
|
@@ -43136,7 +43613,7 @@ function parsePerTicket(text) {
|
|
|
43136
43613
|
return [];
|
|
43137
43614
|
}
|
|
43138
43615
|
function runClaudeOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
|
|
43139
|
-
return new Promise((
|
|
43616
|
+
return new Promise((resolve12, reject) => {
|
|
43140
43617
|
const isClaude = cmd.includes("claude");
|
|
43141
43618
|
const args = [
|
|
43142
43619
|
"-p",
|
|
@@ -43169,7 +43646,7 @@ function runClaudeOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
|
|
|
43169
43646
|
child.on("close", (code) => {
|
|
43170
43647
|
clearTimeout(timer);
|
|
43171
43648
|
out += parser.flush();
|
|
43172
|
-
|
|
43649
|
+
resolve12({ code: code ?? -1, out, err, usage: parser.usage() });
|
|
43173
43650
|
});
|
|
43174
43651
|
child.stdin.write(prompt);
|
|
43175
43652
|
child.stdin.end();
|
|
@@ -43189,11 +43666,11 @@ async function runClaudeWithFailover2(prompt, cwd2, ai2) {
|
|
|
43189
43666
|
var MAX_OUTPUT_CHARS = 8e3;
|
|
43190
43667
|
async function runFaqCompose(req, ai2) {
|
|
43191
43668
|
if (!req.repo.token) return { prUrl: "", error: "no write token" };
|
|
43192
|
-
const workDir = join24(tmpdir4(), "4pm-faq-sync",
|
|
43669
|
+
const workDir = join24(tmpdir4(), "4pm-faq-sync", randomUUID3());
|
|
43193
43670
|
const gitEnv = { ...process.env, GIT_TERMINAL_PROMPT: "0" };
|
|
43194
43671
|
const auth = gitAuthArgs("x-access-token", req.repo.token);
|
|
43195
43672
|
try {
|
|
43196
|
-
|
|
43673
|
+
mkdirSync8(workDir, { recursive: true });
|
|
43197
43674
|
await execFileAsync2("git", [...auth, "clone", "--depth", "1", "--branch", req.repo.branch, req.repo.url, workDir], { env: gitEnv });
|
|
43198
43675
|
await execFileAsync2("git", ["-C", workDir, "config", "user.name", "4PM FAQ Bot"], { env: gitEnv });
|
|
43199
43676
|
await execFileAsync2("git", ["-C", workDir, "config", "user.email", "faq-bot@4pm.app"], { env: gitEnv });
|
|
@@ -43204,7 +43681,7 @@ async function runFaqCompose(req, ai2) {
|
|
|
43204
43681
|
await execFileAsync2("git", ["-C", workDir, "add", "-A"], { env: gitEnv });
|
|
43205
43682
|
const staged = await execFileAsync2("git", ["-C", workDir, "diff", "--cached", "--name-only"], { env: gitEnv });
|
|
43206
43683
|
if (!staged.stdout.trim()) {
|
|
43207
|
-
return { prUrl: "", output, perTicket, tokens: usage.tokens ||
|
|
43684
|
+
return { prUrl: "", output, perTicket, tokens: usage.tokens || estimateTokens(text), finishedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
43208
43685
|
}
|
|
43209
43686
|
await execFileAsync2("git", ["-C", workDir, "commit", "-m", "docs(faq): synthesize from support tickets (4PM)"], { env: gitEnv });
|
|
43210
43687
|
await execFileAsync2("git", [...auth, "-C", workDir, "push", "-u", "origin", req.headBranch], { env: gitEnv });
|
|
@@ -43233,9 +43710,9 @@ async function runFaqCompose(req, ai2) {
|
|
|
43233
43710
|
logger.warn("faq.compose.failed", { error: String(err) });
|
|
43234
43711
|
return { prUrl: "", error: String(err) };
|
|
43235
43712
|
} finally {
|
|
43236
|
-
if (
|
|
43713
|
+
if (existsSync15(workDir)) {
|
|
43237
43714
|
try {
|
|
43238
|
-
|
|
43715
|
+
rmSync8(workDir, { recursive: true, force: true });
|
|
43239
43716
|
} catch {
|
|
43240
43717
|
}
|
|
43241
43718
|
}
|
|
@@ -43559,7 +44036,7 @@ async function writeSecrets(root, req) {
|
|
|
43559
44036
|
|
|
43560
44037
|
// src/core/agent-tools.ts
|
|
43561
44038
|
import { readFile as readFile12, writeFile as writeFile10, mkdir as mkdir9 } from "fs/promises";
|
|
43562
|
-
import { dirname as
|
|
44039
|
+
import { dirname as dirname9, join as join27 } from "path";
|
|
43563
44040
|
var SHARED_REL = ".claude/settings.json";
|
|
43564
44041
|
var LOCAL_REL = ".claude/settings.local.json";
|
|
43565
44042
|
var SECRETS_DENY = "Read(./project.secrets.json)";
|
|
@@ -43590,7 +44067,7 @@ async function writeAgentTools(root, scope, permissions) {
|
|
|
43590
44067
|
const next = normalizePermissions(permissions);
|
|
43591
44068
|
if (scope === "shared" && !next.deny.includes(SECRETS_DENY)) next.deny = [...next.deny, SECRETS_DENY];
|
|
43592
44069
|
obj.permissions = next;
|
|
43593
|
-
await mkdir9(
|
|
44070
|
+
await mkdir9(dirname9(path), { recursive: true });
|
|
43594
44071
|
await writeFile10(path, JSON.stringify(obj, null, 2) + "\n", "utf8");
|
|
43595
44072
|
return { ok: true };
|
|
43596
44073
|
} catch (err) {
|
|
@@ -43601,7 +44078,7 @@ async function writeAgentTools(root, scope, permissions) {
|
|
|
43601
44078
|
// src/core/packages.ts
|
|
43602
44079
|
import { createHash as createHash4 } from "crypto";
|
|
43603
44080
|
import { readFile as readFile13, writeFile as writeFile11, mkdir as mkdir10, readdir as readdir5, rm as rm6, stat as stat4 } from "fs/promises";
|
|
43604
|
-
import { dirname as
|
|
44081
|
+
import { dirname as dirname10, join as join28, relative } from "path";
|
|
43605
44082
|
var AGENTS_REL2 = ".claude/agents";
|
|
43606
44083
|
var SKILLS_REL2 = ".claude/skills";
|
|
43607
44084
|
var MANIFEST_REL = ".claude/.4pm-packages.json";
|
|
@@ -43682,7 +44159,7 @@ async function readManifest(root) {
|
|
|
43682
44159
|
}
|
|
43683
44160
|
async function writeManifest(root, manifest) {
|
|
43684
44161
|
const path = join28(root, MANIFEST_REL);
|
|
43685
|
-
await mkdir10(
|
|
44162
|
+
await mkdir10(dirname10(path), { recursive: true });
|
|
43686
44163
|
await writeFile11(path, JSON.stringify(manifest, null, 2), "utf8");
|
|
43687
44164
|
}
|
|
43688
44165
|
async function writeArtifactFiles(root, kind, slug, files) {
|
|
@@ -43708,7 +44185,7 @@ async function writeArtifactFiles(root, kind, slug, files) {
|
|
|
43708
44185
|
for (const [rel, buf] of byPath) {
|
|
43709
44186
|
if (rel.includes("..") || rel.startsWith("/")) continue;
|
|
43710
44187
|
const dest = join28(base, rel);
|
|
43711
|
-
await mkdir10(
|
|
44188
|
+
await mkdir10(dirname10(dest), { recursive: true });
|
|
43712
44189
|
await writeFile11(dest, buf);
|
|
43713
44190
|
}
|
|
43714
44191
|
}
|
|
@@ -43789,7 +44266,7 @@ async function removePackage(root, slug) {
|
|
|
43789
44266
|
|
|
43790
44267
|
// src/core/graph.ts
|
|
43791
44268
|
import { readFile as readFile14, readdir as readdir6 } from "fs/promises";
|
|
43792
|
-
import { join as join29, relative as relative2, dirname as
|
|
44269
|
+
import { join as join29, relative as relative2, dirname as dirname11, resolve as resolve11, extname as extname2, basename as basename6 } from "path";
|
|
43793
44270
|
var MAX_FILES = 2e3;
|
|
43794
44271
|
var MAX_NODES = 3e3;
|
|
43795
44272
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "out", "coverage", ".turbo"]);
|
|
@@ -43844,7 +44321,7 @@ async function buildDocs(root) {
|
|
|
43844
44321
|
for (const m2 of text.matchAll(/\[[^\]]*\]\(([^)]+)\)/g)) {
|
|
43845
44322
|
const href = (m2[1] ?? "").split("#")[0].trim();
|
|
43846
44323
|
if (!href || !href.endsWith(".md") || href.startsWith("http")) continue;
|
|
43847
|
-
const target = relative2(root,
|
|
44324
|
+
const target = relative2(root, resolve11(dirname11(f2), href)).split("\\").join("/");
|
|
43848
44325
|
if (ids.has(target) && target !== rel) {
|
|
43849
44326
|
const key = `${rel}->${target}`;
|
|
43850
44327
|
if (!seen.has(key)) {
|
|
@@ -43929,7 +44406,7 @@ async function buildGraph(root, mode) {
|
|
|
43929
44406
|
|
|
43930
44407
|
// src/core/rag.ts
|
|
43931
44408
|
import { readFile as readFile15, writeFile as writeFile12, statfs as statfs2, mkdir as mkdir11 } from "fs/promises";
|
|
43932
|
-
import { existsSync as
|
|
44409
|
+
import { existsSync as existsSync16 } from "fs";
|
|
43933
44410
|
import { execFile as execFile11, spawn as spawn10 } from "child_process";
|
|
43934
44411
|
import { totalmem as totalmem2, cpus as cpus2 } from "os";
|
|
43935
44412
|
import { join as join30 } from "path";
|
|
@@ -44022,7 +44499,7 @@ async function ragStatus(root) {
|
|
|
44022
44499
|
const modelPresent = modelName.length > 0;
|
|
44023
44500
|
return {
|
|
44024
44501
|
installed: fastembed && sqliteVec && modelPresent,
|
|
44025
|
-
installing:
|
|
44502
|
+
installing: existsSync16(join30(root, RAG_REL, ".installing")),
|
|
44026
44503
|
python: { found: py.found, version: py.version },
|
|
44027
44504
|
deps: { pip, fastembed, sqliteVec },
|
|
44028
44505
|
model: { present: modelPresent, name: modelName },
|
|
@@ -44033,7 +44510,7 @@ async function ragStatus(root) {
|
|
|
44033
44510
|
};
|
|
44034
44511
|
}
|
|
44035
44512
|
async function readIndexMeta(root) {
|
|
44036
|
-
const indexing =
|
|
44513
|
+
const indexing = existsSync16(join30(root, RAG_REL, ".indexing"));
|
|
44037
44514
|
try {
|
|
44038
44515
|
const m2 = JSON.parse(await readFile15(join30(root, RAG_REL, ".index.json"), "utf8"));
|
|
44039
44516
|
return {
|
|
@@ -44108,8 +44585,8 @@ async function ensureEngine(root) {
|
|
|
44108
44585
|
async function ragReindex(root) {
|
|
44109
44586
|
const py = await detectPython();
|
|
44110
44587
|
if (!py.found) return { ok: false, started: false, error: "python3 not found" };
|
|
44111
|
-
if (!
|
|
44112
|
-
if (
|
|
44588
|
+
if (!existsSync16(join30(root, RAG_REL, ".installed"))) return { ok: false, started: false, error: "RAG is not installed" };
|
|
44589
|
+
if (existsSync16(join30(root, RAG_REL, ".indexing"))) return { ok: true, started: true };
|
|
44113
44590
|
await ensureEngine(root);
|
|
44114
44591
|
const script = `
|
|
44115
44592
|
set +e
|
|
@@ -44129,7 +44606,7 @@ rm -f "${RAG_REL}/.indexing"
|
|
|
44129
44606
|
async function ragQuery(root, queryText, k2 = 8) {
|
|
44130
44607
|
const py = await detectPython();
|
|
44131
44608
|
if (!py.found) return { results: [], error: "python3 not found" };
|
|
44132
|
-
if (!
|
|
44609
|
+
if (!existsSync16(join30(root, RAG_REL, "index.db"))) return { results: [], error: "no index \u2014 reindex first" };
|
|
44133
44610
|
await ensureEngine(root);
|
|
44134
44611
|
try {
|
|
44135
44612
|
const { stdout } = await run10(
|
|
@@ -44149,7 +44626,7 @@ async function ragInstall(root, model) {
|
|
|
44149
44626
|
}
|
|
44150
44627
|
const py = await detectPython();
|
|
44151
44628
|
if (!py.found) return { ok: false, started: false, error: "python3 not found on the worker" };
|
|
44152
|
-
if (
|
|
44629
|
+
if (existsSync16(join30(root, RAG_REL, ".installing"))) return { ok: true, started: true };
|
|
44153
44630
|
try {
|
|
44154
44631
|
await mkdir11(join30(root, RAG_REL), { recursive: true });
|
|
44155
44632
|
} catch {
|
|
@@ -44178,7 +44655,7 @@ rm -f "${RAG_REL}/.installing"
|
|
|
44178
44655
|
}
|
|
44179
44656
|
|
|
44180
44657
|
// src/core/config-sync.ts
|
|
44181
|
-
import { existsSync as
|
|
44658
|
+
import { existsSync as existsSync17, readFileSync as readFileSync16 } from "fs";
|
|
44182
44659
|
import { join as join31 } from "path";
|
|
44183
44660
|
var SERVER_MANAGED_KEYS = [
|
|
44184
44661
|
"physicPath",
|
|
@@ -44197,7 +44674,7 @@ var SERVER_MANAGED_KEYS = [
|
|
|
44197
44674
|
];
|
|
44198
44675
|
function readConfigText(profileDir2) {
|
|
44199
44676
|
const path = join31(profileDir2, "config.json");
|
|
44200
|
-
if (
|
|
44677
|
+
if (existsSync17(path)) {
|
|
44201
44678
|
try {
|
|
44202
44679
|
return readFileSync16(path, "utf8");
|
|
44203
44680
|
} catch {
|
|
@@ -44683,14 +45160,14 @@ var WsClient = class {
|
|
|
44683
45160
|
*/
|
|
44684
45161
|
awaitConnected(timeoutMs) {
|
|
44685
45162
|
if (this.sessionKey && this.bus.status === "connected") return Promise.resolve(true);
|
|
44686
|
-
return new Promise((
|
|
45163
|
+
return new Promise((resolve12) => {
|
|
44687
45164
|
const waiter = () => {
|
|
44688
45165
|
clearTimeout(timer);
|
|
44689
|
-
|
|
45166
|
+
resolve12(true);
|
|
44690
45167
|
};
|
|
44691
45168
|
const timer = setTimeout(() => {
|
|
44692
45169
|
this.connectWaiters = this.connectWaiters.filter((w2) => w2 !== waiter);
|
|
44693
|
-
|
|
45170
|
+
resolve12(false);
|
|
44694
45171
|
}, timeoutMs);
|
|
44695
45172
|
this.connectWaiters.push(waiter);
|
|
44696
45173
|
});
|
|
@@ -44717,16 +45194,16 @@ var WsClient = class {
|
|
|
44717
45194
|
this.forceReconnect = false;
|
|
44718
45195
|
return Promise.resolve();
|
|
44719
45196
|
}
|
|
44720
|
-
return new Promise((
|
|
45197
|
+
return new Promise((resolve12) => {
|
|
44721
45198
|
const timer = setTimeout(() => {
|
|
44722
45199
|
this.wakeReconnect = null;
|
|
44723
|
-
|
|
45200
|
+
resolve12();
|
|
44724
45201
|
}, ms2);
|
|
44725
45202
|
this.wakeReconnect = () => {
|
|
44726
45203
|
clearTimeout(timer);
|
|
44727
45204
|
this.wakeReconnect = null;
|
|
44728
45205
|
this.forceReconnect = false;
|
|
44729
|
-
|
|
45206
|
+
resolve12();
|
|
44730
45207
|
};
|
|
44731
45208
|
});
|
|
44732
45209
|
}
|
|
@@ -44780,7 +45257,7 @@ var WsClient = class {
|
|
|
44780
45257
|
* One WS session: token → connect → handshake → handle messages until close.
|
|
44781
45258
|
*/
|
|
44782
45259
|
connectOnce() {
|
|
44783
|
-
return new Promise((
|
|
45260
|
+
return new Promise((resolve12, reject) => {
|
|
44784
45261
|
void (async () => {
|
|
44785
45262
|
this.sessionEstablished = false;
|
|
44786
45263
|
this.bus.setStatus("connecting");
|
|
@@ -44822,7 +45299,12 @@ var WsClient = class {
|
|
|
44822
45299
|
if (token.aiMemory && !this.aiMemory) this.aiMemory = token.aiMemory.text;
|
|
44823
45300
|
this.outboundReviewEnabled = token.outboundReview?.enabled === true;
|
|
44824
45301
|
this.restrictToFolder = token.aiRestrictToFolder === true;
|
|
44825
|
-
|
|
45302
|
+
configureGitAuth(
|
|
45303
|
+
token.gitAuth ?? null,
|
|
45304
|
+
token.gitAuthHost ?? null,
|
|
45305
|
+
this.context.profileDir,
|
|
45306
|
+
(req) => this.request(WsChannels.GIT_TOKEN, req)
|
|
45307
|
+
);
|
|
44826
45308
|
this.machineUsername = token.machineUsername ?? "";
|
|
44827
45309
|
this.servingRepos = token.repos ?? [];
|
|
44828
45310
|
void this.ensureServingReposCloned();
|
|
@@ -44876,7 +45358,7 @@ var WsClient = class {
|
|
|
44876
45358
|
if (code === CLOSE_DRAINING) {
|
|
44877
45359
|
this.bus.log(t("error.serverDraining"));
|
|
44878
45360
|
}
|
|
44879
|
-
|
|
45361
|
+
resolve12();
|
|
44880
45362
|
});
|
|
44881
45363
|
socket.on("error", (err) => {
|
|
44882
45364
|
this.clearHeartbeat();
|
|
@@ -44988,7 +45470,7 @@ var WsClient = class {
|
|
|
44988
45470
|
}
|
|
44989
45471
|
const { payload, nonce } = encryptPayload(this.sessionKey, data);
|
|
44990
45472
|
const envelope = {
|
|
44991
|
-
id:
|
|
45473
|
+
id: randomUUID4(),
|
|
44992
45474
|
replyTo,
|
|
44993
45475
|
channel,
|
|
44994
45476
|
wsToken: this.wsToken,
|
|
@@ -45002,14 +45484,14 @@ var WsClient = class {
|
|
|
45002
45484
|
* Used for quota.check before spawning an AI cli (ADR-0020).
|
|
45003
45485
|
*/
|
|
45004
45486
|
request(channel, data) {
|
|
45005
|
-
return new Promise((
|
|
45487
|
+
return new Promise((resolve12, reject) => {
|
|
45006
45488
|
if (!this.socket || !this.sessionKey) {
|
|
45007
45489
|
reject(new Error("Not connected to the server."));
|
|
45008
45490
|
return;
|
|
45009
45491
|
}
|
|
45010
45492
|
const { payload, nonce } = encryptPayload(this.sessionKey, data);
|
|
45011
45493
|
const envelope = {
|
|
45012
|
-
id:
|
|
45494
|
+
id: randomUUID4(),
|
|
45013
45495
|
replyTo: null,
|
|
45014
45496
|
channel,
|
|
45015
45497
|
wsToken: this.wsToken,
|
|
@@ -45022,7 +45504,7 @@ var WsClient = class {
|
|
|
45022
45504
|
}, 15e3);
|
|
45023
45505
|
timer.unref?.();
|
|
45024
45506
|
this.pending.set(envelope.id, {
|
|
45025
|
-
resolve: (value) =>
|
|
45507
|
+
resolve: (value) => resolve12(value),
|
|
45026
45508
|
timer
|
|
45027
45509
|
});
|
|
45028
45510
|
this.socket.send(JSON.stringify(envelope));
|
|
@@ -45076,8 +45558,8 @@ var WsClient = class {
|
|
|
45076
45558
|
/** Create the physic folder at an absolute path if missing (best-effort). */
|
|
45077
45559
|
ensurePhysicFolderPath(folder) {
|
|
45078
45560
|
try {
|
|
45079
|
-
if (!
|
|
45080
|
-
|
|
45561
|
+
if (!existsSync18(folder)) {
|
|
45562
|
+
mkdirSync9(folder, { recursive: true });
|
|
45081
45563
|
this.bus.log(t("project.createdFolder", { folder }));
|
|
45082
45564
|
}
|
|
45083
45565
|
} catch (err) {
|
|
@@ -45106,7 +45588,7 @@ var WsClient = class {
|
|
|
45106
45588
|
projects: [],
|
|
45107
45589
|
version: CLI_VERSION,
|
|
45108
45590
|
physicPath,
|
|
45109
|
-
physicPathExists: physicPath ?
|
|
45591
|
+
physicPathExists: physicPath ? existsSync18(physicPath) : void 0,
|
|
45110
45592
|
// Real coarse state (ADR-0152): !paused and a recent tick (was hard-coded false).
|
|
45111
45593
|
autonomousRunning: physicPath ? isAutonomousRunning(physicPath, this.context.profileDir) : false,
|
|
45112
45594
|
// The machine-user AI-run limit (0 = unlimited) + profile count so a dispatch can resolve the
|
|
@@ -45365,7 +45847,7 @@ var WsClient = class {
|
|
|
45365
45847
|
};
|
|
45366
45848
|
|
|
45367
45849
|
// src/core/instance-lock.ts
|
|
45368
|
-
import { readFileSync as readFileSync17, rmSync as
|
|
45850
|
+
import { readFileSync as readFileSync17, rmSync as rmSync9, writeFileSync as writeFileSync9 } from "fs";
|
|
45369
45851
|
import { join as join33 } from "path";
|
|
45370
45852
|
function lockPath(profileDir2) {
|
|
45371
45853
|
return join33(profileDir2, "start.lock");
|
|
@@ -45394,14 +45876,14 @@ function acquireInstanceLock(profileDir2) {
|
|
|
45394
45876
|
function releaseInstanceLock(profileDir2) {
|
|
45395
45877
|
try {
|
|
45396
45878
|
const pid = Number.parseInt(readFileSync17(lockPath(profileDir2), "utf8").trim(), 10);
|
|
45397
|
-
if (pid === process.pid)
|
|
45879
|
+
if (pid === process.pid) rmSync9(lockPath(profileDir2), { force: true });
|
|
45398
45880
|
} catch {
|
|
45399
45881
|
}
|
|
45400
45882
|
}
|
|
45401
45883
|
|
|
45402
45884
|
// src/core/session-bus.ts
|
|
45403
45885
|
import { EventEmitter } from "events";
|
|
45404
|
-
import { randomUUID as
|
|
45886
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
45405
45887
|
var REPLAY_CAP = 500;
|
|
45406
45888
|
var SessionBus = class {
|
|
45407
45889
|
emitter = new EventEmitter();
|
|
@@ -45432,7 +45914,7 @@ var SessionBus = class {
|
|
|
45432
45914
|
/** Producer → consumers: push one transcript entry. Returns its id (for `updateEntry`). */
|
|
45433
45915
|
push(input) {
|
|
45434
45916
|
const entry = {
|
|
45435
|
-
id:
|
|
45917
|
+
id: randomUUID5(),
|
|
45436
45918
|
level: "info",
|
|
45437
45919
|
...input,
|
|
45438
45920
|
ts: Date.now()
|
|
@@ -45689,7 +46171,7 @@ var SessionBus = class {
|
|
|
45689
46171
|
|
|
45690
46172
|
// src/core/control-server.ts
|
|
45691
46173
|
import { createServer } from "net";
|
|
45692
|
-
import { chmodSync as
|
|
46174
|
+
import { chmodSync as chmodSync5, rmSync as rmSync10 } from "fs";
|
|
45693
46175
|
import { join as join35 } from "path";
|
|
45694
46176
|
|
|
45695
46177
|
// src/core/control-protocol.ts
|
|
@@ -45719,7 +46201,7 @@ function createFrameParser() {
|
|
|
45719
46201
|
}
|
|
45720
46202
|
|
|
45721
46203
|
// src/core/control-token.ts
|
|
45722
|
-
import { chmodSync as
|
|
46204
|
+
import { chmodSync as chmodSync4, readFileSync as readFileSync18, writeFileSync as writeFileSync10 } from "fs";
|
|
45723
46205
|
import { randomBytes as randomBytes3, timingSafeEqual } from "crypto";
|
|
45724
46206
|
import { join as join34 } from "path";
|
|
45725
46207
|
function controlTokenPath(profileDir2) {
|
|
@@ -45730,7 +46212,7 @@ function writeControlToken(profileDir2) {
|
|
|
45730
46212
|
const path = controlTokenPath(profileDir2);
|
|
45731
46213
|
writeFileSync10(path, token, { mode: 384 });
|
|
45732
46214
|
try {
|
|
45733
|
-
|
|
46215
|
+
chmodSync4(path, 384);
|
|
45734
46216
|
} catch {
|
|
45735
46217
|
}
|
|
45736
46218
|
return token;
|
|
@@ -45754,7 +46236,7 @@ function tokenMatches(expected, given) {
|
|
|
45754
46236
|
function startControlServer(bus, profileDir2, info) {
|
|
45755
46237
|
const socketPath = join35(profileDir2, CONTROL_SOCKET_FILE);
|
|
45756
46238
|
try {
|
|
45757
|
-
|
|
46239
|
+
rmSync10(socketPath, { force: true });
|
|
45758
46240
|
} catch {
|
|
45759
46241
|
}
|
|
45760
46242
|
const clients = /* @__PURE__ */ new Set();
|
|
@@ -45807,8 +46289,10 @@ function startControlServer(bus, profileDir2, info) {
|
|
|
45807
46289
|
if (!authed) {
|
|
45808
46290
|
if (frame.t === "auth" && tokenMatches(token, frame.token)) {
|
|
45809
46291
|
authed = true;
|
|
45810
|
-
|
|
45811
|
-
|
|
46292
|
+
if (!frame.rpc) {
|
|
46293
|
+
clients.add(sock);
|
|
46294
|
+
sendSnapshot(sock);
|
|
46295
|
+
}
|
|
45812
46296
|
} else {
|
|
45813
46297
|
send(sock, { t: "authError" });
|
|
45814
46298
|
sock.destroy();
|
|
@@ -45818,6 +46302,9 @@ function startControlServer(bus, profileDir2, info) {
|
|
|
45818
46302
|
if (frame.t === "submit") bus.submitLocal(frame.input);
|
|
45819
46303
|
else if (frame.t === "reconnect") bus.requestReconnect();
|
|
45820
46304
|
else if (frame.t === "autonomousRun") bus.submitAutonomous();
|
|
46305
|
+
else if (frame.t === "gitToken") {
|
|
46306
|
+
void issueGitToken(frame.scope, frame.host, frame.path).then((tok) => send(sock, { t: "gitToken", token: tok }));
|
|
46307
|
+
}
|
|
45821
46308
|
}
|
|
45822
46309
|
});
|
|
45823
46310
|
const drop = () => {
|
|
@@ -45830,7 +46317,7 @@ function startControlServer(bus, profileDir2, info) {
|
|
|
45830
46317
|
});
|
|
45831
46318
|
server.listen(socketPath, () => {
|
|
45832
46319
|
try {
|
|
45833
|
-
|
|
46320
|
+
chmodSync5(socketPath, 384);
|
|
45834
46321
|
} catch {
|
|
45835
46322
|
}
|
|
45836
46323
|
});
|
|
@@ -45840,8 +46327,8 @@ function startControlServer(bus, profileDir2, info) {
|
|
|
45840
46327
|
clients.clear();
|
|
45841
46328
|
server.close();
|
|
45842
46329
|
try {
|
|
45843
|
-
|
|
45844
|
-
|
|
46330
|
+
rmSync10(socketPath, { force: true });
|
|
46331
|
+
rmSync10(controlTokenPath(profileDir2), { force: true });
|
|
45845
46332
|
} catch {
|
|
45846
46333
|
}
|
|
45847
46334
|
};
|
|
@@ -47820,7 +48307,7 @@ var src_default = Yoga;
|
|
|
47820
48307
|
|
|
47821
48308
|
// ../../node_modules/.pnpm/ink@5.2.1_@types+react@18.3.31_react@18.3.1/node_modules/ink/build/reconciler.js
|
|
47822
48309
|
var import_react_reconciler = __toESM(require_react_reconciler(), 1);
|
|
47823
|
-
var
|
|
48310
|
+
var import_constants12 = __toESM(require_constants2(), 1);
|
|
47824
48311
|
import process3 from "process";
|
|
47825
48312
|
|
|
47826
48313
|
// ../../node_modules/.pnpm/ansi-regex@6.2.2/node_modules/ansi-regex/index.js
|
|
@@ -49062,7 +49549,7 @@ var reconciler_default = (0, import_react_reconciler.default)({
|
|
|
49062
49549
|
scheduleTimeout: setTimeout,
|
|
49063
49550
|
cancelTimeout: clearTimeout,
|
|
49064
49551
|
noTimeout: -1,
|
|
49065
|
-
getCurrentEventPriority: () =>
|
|
49552
|
+
getCurrentEventPriority: () => import_constants12.DefaultEventPriority,
|
|
49066
49553
|
beforeActiveInstanceBlur() {
|
|
49067
49554
|
},
|
|
49068
49555
|
afterActiveInstanceBlur() {
|
|
@@ -50923,24 +51410,24 @@ var App = class extends import_react9.PureComponent {
|
|
|
50923
51410
|
})
|
|
50924
51411
|
}));
|
|
50925
51412
|
};
|
|
50926
|
-
findNextFocusable = (
|
|
50927
|
-
const activeIndex =
|
|
50928
|
-
return focusable.id ===
|
|
51413
|
+
findNextFocusable = (state2) => {
|
|
51414
|
+
const activeIndex = state2.focusables.findIndex((focusable) => {
|
|
51415
|
+
return focusable.id === state2.activeFocusId;
|
|
50929
51416
|
});
|
|
50930
|
-
for (let index = activeIndex + 1; index <
|
|
50931
|
-
const focusable =
|
|
51417
|
+
for (let index = activeIndex + 1; index < state2.focusables.length; index++) {
|
|
51418
|
+
const focusable = state2.focusables[index];
|
|
50932
51419
|
if (focusable?.isActive) {
|
|
50933
51420
|
return focusable.id;
|
|
50934
51421
|
}
|
|
50935
51422
|
}
|
|
50936
51423
|
return void 0;
|
|
50937
51424
|
};
|
|
50938
|
-
findPreviousFocusable = (
|
|
50939
|
-
const activeIndex =
|
|
50940
|
-
return focusable.id ===
|
|
51425
|
+
findPreviousFocusable = (state2) => {
|
|
51426
|
+
const activeIndex = state2.focusables.findIndex((focusable) => {
|
|
51427
|
+
return focusable.id === state2.activeFocusId;
|
|
50941
51428
|
});
|
|
50942
51429
|
for (let index = activeIndex - 1; index >= 0; index--) {
|
|
50943
|
-
const focusable =
|
|
51430
|
+
const focusable = state2.focusables[index];
|
|
50944
51431
|
if (focusable?.isActive) {
|
|
50945
51432
|
return focusable.id;
|
|
50946
51433
|
}
|
|
@@ -51136,8 +51623,8 @@ var Ink = class {
|
|
|
51136
51623
|
}
|
|
51137
51624
|
}
|
|
51138
51625
|
async waitUntilExit() {
|
|
51139
|
-
this.exitPromise ||= new Promise((
|
|
51140
|
-
this.resolveExitPromise =
|
|
51626
|
+
this.exitPromise ||= new Promise((resolve12, reject) => {
|
|
51627
|
+
this.resolveExitPromise = resolve12;
|
|
51141
51628
|
this.rejectExitPromise = reject;
|
|
51142
51629
|
});
|
|
51143
51630
|
return this.exitPromise;
|
|
@@ -51504,7 +51991,7 @@ var import_react21 = __toESM(require_react(), 1);
|
|
|
51504
51991
|
var import_react26 = __toESM(require_react(), 1);
|
|
51505
51992
|
|
|
51506
51993
|
// src/core/input-history.ts
|
|
51507
|
-
import { existsSync as
|
|
51994
|
+
import { existsSync as existsSync20, readFileSync as readFileSync20, writeFileSync as writeFileSync11 } from "fs";
|
|
51508
51995
|
import { join as join36 } from "path";
|
|
51509
51996
|
var MAX_HISTORY = 200;
|
|
51510
51997
|
function historyPath(profileDir2) {
|
|
@@ -51512,7 +51999,7 @@ function historyPath(profileDir2) {
|
|
|
51512
51999
|
}
|
|
51513
52000
|
function loadInputHistory(profileDir2) {
|
|
51514
52001
|
const path = historyPath(profileDir2);
|
|
51515
|
-
if (!
|
|
52002
|
+
if (!existsSync20(path)) return [];
|
|
51516
52003
|
try {
|
|
51517
52004
|
const data = JSON.parse(readFileSync20(path, "utf8"));
|
|
51518
52005
|
return Array.isArray(data) ? data.filter((x) => typeof x === "string") : [];
|
|
@@ -52190,7 +52677,7 @@ async function runStart(profileDir2, profileName) {
|
|
|
52190
52677
|
logger.error("ws.run.crash", { message: e.message, stack: e.stack });
|
|
52191
52678
|
if (client.isStopped) return;
|
|
52192
52679
|
bus.log(t("start.recovered", { message: e.message }), "error");
|
|
52193
|
-
await new Promise((
|
|
52680
|
+
await new Promise((resolve12) => setTimeout(resolve12, 1e3));
|
|
52194
52681
|
}
|
|
52195
52682
|
}
|
|
52196
52683
|
};
|
|
@@ -52225,6 +52712,7 @@ async function runStart(profileDir2, profileName) {
|
|
|
52225
52712
|
}
|
|
52226
52713
|
} finally {
|
|
52227
52714
|
stopIdleClear?.();
|
|
52715
|
+
await revokeAll().catch(() => void 0);
|
|
52228
52716
|
stopControl?.();
|
|
52229
52717
|
release();
|
|
52230
52718
|
}
|
|
@@ -52236,7 +52724,7 @@ import { join as join38 } from "path";
|
|
|
52236
52724
|
// src/core/control-client.ts
|
|
52237
52725
|
import { connect as connect2 } from "net";
|
|
52238
52726
|
function connectControl(socketPath, bus, token) {
|
|
52239
|
-
return new Promise((
|
|
52727
|
+
return new Promise((resolve12, reject) => {
|
|
52240
52728
|
const socket = connect2(socketPath);
|
|
52241
52729
|
socket.setEncoding("utf8");
|
|
52242
52730
|
let ready = false;
|
|
@@ -52289,7 +52777,7 @@ function connectControl(socketPath, bus, token) {
|
|
|
52289
52777
|
bus.setTokens(frame.tokens);
|
|
52290
52778
|
for (const entry of frame.transcript) bus.pushEntry(entry);
|
|
52291
52779
|
ready = true;
|
|
52292
|
-
|
|
52780
|
+
resolve12({ info: frame.info, close });
|
|
52293
52781
|
break;
|
|
52294
52782
|
case "transcript":
|
|
52295
52783
|
bus.pushEntry(frame.entry);
|
|
@@ -52377,7 +52865,7 @@ async function runAutoRun(profileDir2, profileName) {
|
|
|
52377
52865
|
initI18n(readProfileConfig(profileDir2).locale);
|
|
52378
52866
|
const socketPath = join39(profileDir2, CONTROL_SOCKET_FILE);
|
|
52379
52867
|
const token = readControlToken(profileDir2);
|
|
52380
|
-
const code = await new Promise((
|
|
52868
|
+
const code = await new Promise((resolve12) => {
|
|
52381
52869
|
let settled = false;
|
|
52382
52870
|
const done = (c) => {
|
|
52383
52871
|
if (settled) return;
|
|
@@ -52387,7 +52875,7 @@ async function runAutoRun(profileDir2, profileName) {
|
|
|
52387
52875
|
socket.destroy();
|
|
52388
52876
|
} catch {
|
|
52389
52877
|
}
|
|
52390
|
-
|
|
52878
|
+
resolve12(c);
|
|
52391
52879
|
};
|
|
52392
52880
|
const socket = connect3(socketPath);
|
|
52393
52881
|
socket.setEncoding("utf8");
|
|
@@ -52430,21 +52918,91 @@ async function runAutoRun(profileDir2, profileName) {
|
|
|
52430
52918
|
process.exitCode = code;
|
|
52431
52919
|
}
|
|
52432
52920
|
|
|
52921
|
+
// src/commands/git-credential.ts
|
|
52922
|
+
import { connect as connect4 } from "net";
|
|
52923
|
+
import { join as join40 } from "path";
|
|
52924
|
+
var REQUEST_TIMEOUT_MS = 2e4;
|
|
52925
|
+
function requestToken(host, path) {
|
|
52926
|
+
const profileDir2 = process.env[PROFILE_DIR_ENV];
|
|
52927
|
+
if (!profileDir2) return Promise.resolve(null);
|
|
52928
|
+
const token = readControlToken(profileDir2);
|
|
52929
|
+
return new Promise((resolve12) => {
|
|
52930
|
+
let done = false;
|
|
52931
|
+
const finish = (value) => {
|
|
52932
|
+
if (done) return;
|
|
52933
|
+
done = true;
|
|
52934
|
+
clearTimeout(timer);
|
|
52935
|
+
socket.destroy();
|
|
52936
|
+
resolve12(value);
|
|
52937
|
+
};
|
|
52938
|
+
const socket = connect4(join40(profileDir2, CONTROL_SOCKET_FILE));
|
|
52939
|
+
socket.setEncoding("utf8");
|
|
52940
|
+
const timer = setTimeout(() => finish(null), REQUEST_TIMEOUT_MS);
|
|
52941
|
+
socket.on("connect", () => {
|
|
52942
|
+
try {
|
|
52943
|
+
socket.write(encodeFrame({ t: "auth", token: token ?? "", rpc: true }));
|
|
52944
|
+
socket.write(encodeFrame({ t: "gitToken", scope: process.env[JOB_ID_ENV] ?? "", host, path }));
|
|
52945
|
+
} catch {
|
|
52946
|
+
finish(null);
|
|
52947
|
+
}
|
|
52948
|
+
});
|
|
52949
|
+
const parse = createFrameParser();
|
|
52950
|
+
socket.on("data", (chunk) => {
|
|
52951
|
+
for (const frame of parse(chunk)) {
|
|
52952
|
+
if (frame.t === "gitToken") finish(frame.token);
|
|
52953
|
+
else if (frame.t === "authError") finish(null);
|
|
52954
|
+
}
|
|
52955
|
+
});
|
|
52956
|
+
socket.on("error", () => finish(null));
|
|
52957
|
+
socket.on("close", () => finish(null));
|
|
52958
|
+
});
|
|
52959
|
+
}
|
|
52960
|
+
async function readCredentialInput() {
|
|
52961
|
+
const chunks = [];
|
|
52962
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
52963
|
+
const out = {};
|
|
52964
|
+
for (const line of Buffer.concat(chunks).toString("utf8").split("\n")) {
|
|
52965
|
+
const i = line.indexOf("=");
|
|
52966
|
+
if (i > 0) out[line.slice(0, i)] = line.slice(i + 1).trim();
|
|
52967
|
+
}
|
|
52968
|
+
return out;
|
|
52969
|
+
}
|
|
52970
|
+
async function runGitCredential(action) {
|
|
52971
|
+
if (action !== "get") {
|
|
52972
|
+
await readCredentialInput().catch(() => void 0);
|
|
52973
|
+
return;
|
|
52974
|
+
}
|
|
52975
|
+
const input = await readCredentialInput();
|
|
52976
|
+
const host = (input.host ?? "").toLowerCase();
|
|
52977
|
+
if (input.protocol !== "https" || !host || host !== (process.env[GIT_HOST_ENV] ?? "").toLowerCase()) return;
|
|
52978
|
+
const token = await requestToken(host, input.path ?? "");
|
|
52979
|
+
if (token) process.stdout.write(`username=x-access-token
|
|
52980
|
+
password=${token}
|
|
52981
|
+
`);
|
|
52982
|
+
}
|
|
52983
|
+
async function runGitToken() {
|
|
52984
|
+
const host = process.env[GIT_HOST_ENV] ?? "";
|
|
52985
|
+
if (!host) return;
|
|
52986
|
+
const token = await requestToken(host, "");
|
|
52987
|
+
if (token) process.stdout.write(`${token}
|
|
52988
|
+
`);
|
|
52989
|
+
}
|
|
52990
|
+
|
|
52433
52991
|
// src/commands/ai-login.ts
|
|
52434
52992
|
import { spawn as spawn12 } from "child_process";
|
|
52435
|
-
import { mkdirSync as
|
|
52993
|
+
import { mkdirSync as mkdirSync10 } from "fs";
|
|
52436
52994
|
import { basename as basename8 } from "path";
|
|
52437
52995
|
function runProviderLogin(cmd, envVar, dir2) {
|
|
52438
|
-
return new Promise((
|
|
52996
|
+
return new Promise((resolve12) => {
|
|
52439
52997
|
const child = spawn12(cmd, [], {
|
|
52440
52998
|
stdio: "inherit",
|
|
52441
52999
|
env: { ...process.env, [envVar]: dir2 }
|
|
52442
53000
|
});
|
|
52443
53001
|
child.on("error", (err) => {
|
|
52444
53002
|
console.error(t("aiLogin.launchFailed", { cmd, message: err.message }));
|
|
52445
|
-
|
|
53003
|
+
resolve12(1);
|
|
52446
53004
|
});
|
|
52447
|
-
child.on("exit", (code) =>
|
|
53005
|
+
child.on("exit", (code) => resolve12(code ?? 0));
|
|
52448
53006
|
});
|
|
52449
53007
|
}
|
|
52450
53008
|
async function runAiLogin(profileDir2, profileName, aiFilter) {
|
|
@@ -52469,7 +53027,7 @@ async function runAiLogin(profileDir2, profileName, aiFilter) {
|
|
|
52469
53027
|
continue;
|
|
52470
53028
|
}
|
|
52471
53029
|
const dir2 = resolveHomePath(cred.profile);
|
|
52472
|
-
|
|
53030
|
+
mkdirSync10(dir2, { recursive: true });
|
|
52473
53031
|
console.log(t("aiLogin.loggingIn", { provider: cred.provider, profile: cred.profile, envVar, dir: dir2 }));
|
|
52474
53032
|
console.log(t("aiLogin.completeSignIn"));
|
|
52475
53033
|
const code = await runProviderLogin(cred.provider, envVar, dir2);
|
|
@@ -52481,7 +53039,7 @@ async function runAiLogin(profileDir2, profileName, aiFilter) {
|
|
|
52481
53039
|
}
|
|
52482
53040
|
|
|
52483
53041
|
// src/commands/unlink.ts
|
|
52484
|
-
import { rmSync as
|
|
53042
|
+
import { rmSync as rmSync11 } from "fs";
|
|
52485
53043
|
async function runUnlink(profileDir2, profileName) {
|
|
52486
53044
|
initI18n(readProfileConfig(profileDir2).locale);
|
|
52487
53045
|
const cred = readCredential(profileDir2, profileName);
|
|
@@ -52495,7 +53053,7 @@ async function runUnlink(profileDir2, profileName) {
|
|
|
52495
53053
|
} catch (err) {
|
|
52496
53054
|
console.log(t("unlink.revokeFailed", { message: err.message }));
|
|
52497
53055
|
}
|
|
52498
|
-
|
|
53056
|
+
rmSync11(profileDir2, { recursive: true, force: true });
|
|
52499
53057
|
clearDefaultProfileIf(profileName);
|
|
52500
53058
|
console.log(t("unlink.deleted"));
|
|
52501
53059
|
}
|
|
@@ -52584,10 +53142,10 @@ function Picker({
|
|
|
52584
53142
|
] });
|
|
52585
53143
|
}
|
|
52586
53144
|
function selectFromList(items, title) {
|
|
52587
|
-
return new Promise((
|
|
53145
|
+
return new Promise((resolve12) => {
|
|
52588
53146
|
let chosen = null;
|
|
52589
53147
|
const app = render_default(/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Picker, { items, title, onDone: (v2) => chosen = v2 }));
|
|
52590
|
-
void app.waitUntilExit().then(() =>
|
|
53148
|
+
void app.waitUntilExit().then(() => resolve12(chosen));
|
|
52591
53149
|
});
|
|
52592
53150
|
}
|
|
52593
53151
|
|
|
@@ -52654,6 +53212,14 @@ async function main() {
|
|
|
52654
53212
|
await runAttach(profileDir(name), name);
|
|
52655
53213
|
break;
|
|
52656
53214
|
}
|
|
53215
|
+
case "git-credential": {
|
|
53216
|
+
await runGitCredential(rest[0]);
|
|
53217
|
+
break;
|
|
53218
|
+
}
|
|
53219
|
+
case "git-token": {
|
|
53220
|
+
await runGitToken();
|
|
53221
|
+
break;
|
|
53222
|
+
}
|
|
52657
53223
|
case "auto-run": {
|
|
52658
53224
|
const name = await pickLinkedProfile(explicitProfile, "auto-run");
|
|
52659
53225
|
if (!name) {
|