@4pm/cli 1.23.0 → 1.24.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 +599 -213
- 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;
|
|
@@ -32755,7 +32755,7 @@ async function checkAndUpdate(serverUrl, autoUpdate) {
|
|
|
32755
32755
|
init_wrapper();
|
|
32756
32756
|
import { randomUUID as randomUUID3 } 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");
|
|
@@ -38372,6 +38405,9 @@ var WsChannels = {
|
|
|
38372
38405
|
// server → cli (reply): manage the rented worker's ssh deploy key (ADR-0173) — generate
|
|
38373
38406
|
// (ed25519, on the worker) / get / delete. The private key never leaves the worker.
|
|
38374
38407
|
GIT_SSH_KEY: "git.ssh-key",
|
|
38408
|
+
// cli → server (reply): a short-lived GitHub-App installation token for the served project's repo,
|
|
38409
|
+
// per job scope; also the cli's "scope revoked" report (ADR-0356). The App key stays on the server.
|
|
38410
|
+
GIT_TOKEN: "git.token",
|
|
38375
38411
|
PROJECT_CREATE: "project.create",
|
|
38376
38412
|
PROJECT_ADD: "project.add",
|
|
38377
38413
|
PROJECT_PROGRESS: "project.progress",
|
|
@@ -38411,6 +38447,8 @@ var WsChannels = {
|
|
|
38411
38447
|
// command.dispatch({ ai:true }) → the profile-failover AI path (ADR-0100).
|
|
38412
38448
|
// quota & usage (ADR-0020)
|
|
38413
38449
|
QUOTA_CHECK: "quota.check",
|
|
38450
|
+
// cli → server (reply): per-org concurrent AI-run slot — acquire / renew / release (ADR-0359)
|
|
38451
|
+
RUN_SLOT: "run.slot",
|
|
38414
38452
|
USAGE_REPORT: "usage.report",
|
|
38415
38453
|
// cli → server: Claude subscription usage snapshot (5h / weekly — ADR-0072)
|
|
38416
38454
|
MACHINE_USAGE: "machine.usage",
|
|
@@ -38509,6 +38547,11 @@ function decryptPayload(key, payloadBase64, nonceBase64) {
|
|
|
38509
38547
|
return JSON.parse(plaintext.toString("utf8"));
|
|
38510
38548
|
}
|
|
38511
38549
|
|
|
38550
|
+
// vendor/@4pm/ws/messages/run-slot.ts
|
|
38551
|
+
var RUN_SLOT_RENEW_MS = 45e3;
|
|
38552
|
+
var RUN_SLOT_RETRY_MS = 3e3;
|
|
38553
|
+
var RUN_SLOT_MAX_WAIT_MS = 30 * 6e4;
|
|
38554
|
+
|
|
38512
38555
|
// src/core/update-scheduler.ts
|
|
38513
38556
|
import { spawn as spawn2 } from "child_process";
|
|
38514
38557
|
|
|
@@ -38625,8 +38668,8 @@ function resolveInstallTimeoutMs(sec) {
|
|
|
38625
38668
|
return sec && sec > 0 ? sec * 1e3 : TOOL_INSTALL_TIMEOUT_DEFAULT_MS;
|
|
38626
38669
|
}
|
|
38627
38670
|
function sleep(ms2) {
|
|
38628
|
-
return new Promise((
|
|
38629
|
-
const t2 = setTimeout(
|
|
38671
|
+
return new Promise((resolve12) => {
|
|
38672
|
+
const t2 = setTimeout(resolve12, ms2);
|
|
38630
38673
|
t2.unref?.();
|
|
38631
38674
|
});
|
|
38632
38675
|
}
|
|
@@ -38654,14 +38697,14 @@ function classifyToolFailure(code, out) {
|
|
|
38654
38697
|
return { reason: "other", retryable: false };
|
|
38655
38698
|
}
|
|
38656
38699
|
function run3(cmd, args, onLine, timeoutMs = 18e4) {
|
|
38657
|
-
return new Promise((
|
|
38700
|
+
return new Promise((resolve12) => {
|
|
38658
38701
|
let out = "";
|
|
38659
38702
|
let buf = "";
|
|
38660
38703
|
let child = null;
|
|
38661
38704
|
const done = (code) => {
|
|
38662
38705
|
clearTimeout(timer);
|
|
38663
38706
|
if (onLine && buf.trim()) onLine(buf.trim());
|
|
38664
|
-
|
|
38707
|
+
resolve12({ code, out });
|
|
38665
38708
|
};
|
|
38666
38709
|
const timer = setTimeout(() => {
|
|
38667
38710
|
child?.kill();
|
|
@@ -39044,11 +39087,11 @@ function getWorkingProfile(profileDir2, cmd) {
|
|
|
39044
39087
|
return readState(profileDir2)[cmd] ?? null;
|
|
39045
39088
|
}
|
|
39046
39089
|
function setWorkingProfile(profileDir2, cmd, dir2) {
|
|
39047
|
-
const
|
|
39048
|
-
if (
|
|
39049
|
-
|
|
39090
|
+
const state2 = readState(profileDir2);
|
|
39091
|
+
if (state2[cmd] === dir2) return;
|
|
39092
|
+
state2[cmd] = dir2;
|
|
39050
39093
|
try {
|
|
39051
|
-
writeFileSync4(statePath(profileDir2), JSON.stringify(
|
|
39094
|
+
writeFileSync4(statePath(profileDir2), JSON.stringify(state2, null, 2), "utf8");
|
|
39052
39095
|
} catch {
|
|
39053
39096
|
}
|
|
39054
39097
|
}
|
|
@@ -39057,11 +39100,11 @@ function getWorkingCredential(profileDir2) {
|
|
|
39057
39100
|
return readState(profileDir2)[WORKING_CREDENTIAL_KEY] ?? null;
|
|
39058
39101
|
}
|
|
39059
39102
|
function setWorkingCredential(profileDir2, key) {
|
|
39060
|
-
const
|
|
39061
|
-
if (
|
|
39062
|
-
|
|
39103
|
+
const state2 = readState(profileDir2);
|
|
39104
|
+
if (state2[WORKING_CREDENTIAL_KEY] === key) return;
|
|
39105
|
+
state2[WORKING_CREDENTIAL_KEY] = key;
|
|
39063
39106
|
try {
|
|
39064
|
-
writeFileSync4(statePath(profileDir2), JSON.stringify(
|
|
39107
|
+
writeFileSync4(statePath(profileDir2), JSON.stringify(state2, null, 2), "utf8");
|
|
39065
39108
|
} catch {
|
|
39066
39109
|
}
|
|
39067
39110
|
}
|
|
@@ -39070,20 +39113,20 @@ function getPinnedCredential(profileDir2) {
|
|
|
39070
39113
|
return readState(profileDir2)[PINNED_CREDENTIAL_KEY] ?? null;
|
|
39071
39114
|
}
|
|
39072
39115
|
function setPinnedCredential(profileDir2, key) {
|
|
39073
|
-
const
|
|
39074
|
-
if (
|
|
39075
|
-
|
|
39116
|
+
const state2 = readState(profileDir2);
|
|
39117
|
+
if (state2[PINNED_CREDENTIAL_KEY] === key) return;
|
|
39118
|
+
state2[PINNED_CREDENTIAL_KEY] = key;
|
|
39076
39119
|
try {
|
|
39077
|
-
writeFileSync4(statePath(profileDir2), JSON.stringify(
|
|
39120
|
+
writeFileSync4(statePath(profileDir2), JSON.stringify(state2, null, 2), "utf8");
|
|
39078
39121
|
} catch {
|
|
39079
39122
|
}
|
|
39080
39123
|
}
|
|
39081
39124
|
function clearPinnedCredential(profileDir2) {
|
|
39082
|
-
const
|
|
39083
|
-
if (!(PINNED_CREDENTIAL_KEY in
|
|
39084
|
-
delete
|
|
39125
|
+
const state2 = readState(profileDir2);
|
|
39126
|
+
if (!(PINNED_CREDENTIAL_KEY in state2)) return;
|
|
39127
|
+
delete state2[PINNED_CREDENTIAL_KEY];
|
|
39085
39128
|
try {
|
|
39086
|
-
writeFileSync4(statePath(profileDir2), JSON.stringify(
|
|
39129
|
+
writeFileSync4(statePath(profileDir2), JSON.stringify(state2, null, 2), "utf8");
|
|
39087
39130
|
} catch {
|
|
39088
39131
|
}
|
|
39089
39132
|
}
|
|
@@ -39438,16 +39481,16 @@ async function fetchUsage(token) {
|
|
|
39438
39481
|
}
|
|
39439
39482
|
}
|
|
39440
39483
|
function runUsageCli(dir2, aiCli2) {
|
|
39441
|
-
return new Promise((
|
|
39484
|
+
return new Promise((resolve12) => {
|
|
39442
39485
|
let out = "";
|
|
39443
39486
|
let child = null;
|
|
39444
39487
|
const finish = () => {
|
|
39445
39488
|
clearTimeout(timer);
|
|
39446
|
-
|
|
39489
|
+
resolve12(out);
|
|
39447
39490
|
};
|
|
39448
39491
|
const timer = setTimeout(() => {
|
|
39449
39492
|
child?.kill();
|
|
39450
|
-
|
|
39493
|
+
resolve12(out);
|
|
39451
39494
|
}, 3e4);
|
|
39452
39495
|
timer.unref?.();
|
|
39453
39496
|
try {
|
|
@@ -39937,6 +39980,7 @@ var NO_USAGE = { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0
|
|
|
39937
39980
|
var execFileAsync = promisify3(execFile3);
|
|
39938
39981
|
var MAX_CONTEXT_CHARS = 12e4;
|
|
39939
39982
|
var ANSWER_TIMEOUT_MS = 12e4;
|
|
39983
|
+
var LEGAL_DRAFT_TIMEOUT_MS = 54e4;
|
|
39940
39984
|
var DOC_EXTENSIONS = [".md", ".mdx", ".txt"];
|
|
39941
39985
|
function kbRoot(profileDir2) {
|
|
39942
39986
|
return join14(profileDir2, "support-kb");
|
|
@@ -40048,6 +40092,7 @@ function buildPrompt(docs, question, askerRole) {
|
|
|
40048
40092
|
].join("\n");
|
|
40049
40093
|
}
|
|
40050
40094
|
function buildDraftPrompt(docs, context, task) {
|
|
40095
|
+
if (task === "legal_draft") return buildLegalDraftPrompt(docs, context);
|
|
40051
40096
|
const goal = task === "reply_draft" ? [
|
|
40052
40097
|
"You are drafting a reply FROM the 4PM support team TO the customer in the support ticket below.",
|
|
40053
40098
|
"Address the customer's latest message, using the whole conversation for context. Be accurate,",
|
|
@@ -40074,6 +40119,31 @@ function buildDraftPrompt(docs, context, task) {
|
|
|
40074
40119
|
context
|
|
40075
40120
|
].join("\n");
|
|
40076
40121
|
}
|
|
40122
|
+
function buildLegalDraftPrompt(docs, context) {
|
|
40123
|
+
return [
|
|
40124
|
+
"You are drafting the body of one of 4PM's legal documents (Terms of Service, Privacy Policy,",
|
|
40125
|
+
"Add-on Terms, Rented Machine Addendum, \u2026) for the platform admin to review and publish.",
|
|
40126
|
+
"Write or revise the WHOLE document body in the target locale given in the context. Keep the",
|
|
40127
|
+
"existing heading structure and numbering unless the admin's instructions say otherwise.",
|
|
40128
|
+
"When the target locale is not English and an English reference is given, follow its meaning closely.",
|
|
40129
|
+
"Rules:",
|
|
40130
|
+
"- Every {{placeholder}} (e.g. {{companyName}}) must be kept verbatim \u2014 never fill or rename it.",
|
|
40131
|
+
"- Describe plans, prices, upgrades, downgrades, cancellations, renewals, refunds and add-ons ONLY",
|
|
40132
|
+
" as stated in the PLAN CATALOG and BILLING FACTS below or the documentation; never invent",
|
|
40133
|
+
" commitments, prices or legal guarantees. Where something is unknown write `[TODO: \u2026]`.",
|
|
40134
|
+
"- Plain, precise legal English (or the target language); short numbered sections and bullet lists.",
|
|
40135
|
+
"- This is a draft, not legal advice; do not add a disclaimer about that inside the document.",
|
|
40136
|
+
"",
|
|
40137
|
+
"Output ONLY the document body in markdown \u2014 no title line, no preamble, no explanation, no code",
|
|
40138
|
+
"fences around the whole document.",
|
|
40139
|
+
"",
|
|
40140
|
+
"===== DOCUMENTATION =====",
|
|
40141
|
+
docs,
|
|
40142
|
+
"",
|
|
40143
|
+
"===== CONTEXT =====",
|
|
40144
|
+
context
|
|
40145
|
+
].join("\n");
|
|
40146
|
+
}
|
|
40077
40147
|
function imageExt(mime) {
|
|
40078
40148
|
switch (mime) {
|
|
40079
40149
|
case "image/png":
|
|
@@ -40115,8 +40185,8 @@ function parseModeratedAnswer(text) {
|
|
|
40115
40185
|
}
|
|
40116
40186
|
return { body: text.trim() };
|
|
40117
40187
|
}
|
|
40118
|
-
function runClaudeOnce(cmd, profile, prompt, extraEnv, imageDir, denyDirs = []) {
|
|
40119
|
-
return new Promise((
|
|
40188
|
+
function runClaudeOnce(cmd, profile, prompt, extraEnv, imageDir, denyDirs = [], timeoutMs = ANSWER_TIMEOUT_MS) {
|
|
40189
|
+
return new Promise((resolve12, reject) => {
|
|
40120
40190
|
const isClaude = cmd.includes("claude");
|
|
40121
40191
|
const args = [
|
|
40122
40192
|
"-p",
|
|
@@ -40147,7 +40217,7 @@ function runClaudeOnce(cmd, profile, prompt, extraEnv, imageDir, denyDirs = [])
|
|
|
40147
40217
|
const timer = setTimeout(() => {
|
|
40148
40218
|
child.kill("SIGKILL");
|
|
40149
40219
|
reject(new Error("claude answer timed out"));
|
|
40150
|
-
},
|
|
40220
|
+
}, timeoutMs);
|
|
40151
40221
|
timer.unref();
|
|
40152
40222
|
child.stdout.on("data", (d) => out += parser.push(d.toString()));
|
|
40153
40223
|
child.stderr.on("data", (d) => err += d.toString());
|
|
@@ -40160,17 +40230,25 @@ function runClaudeOnce(cmd, profile, prompt, extraEnv, imageDir, denyDirs = [])
|
|
|
40160
40230
|
clearTimeout(timer);
|
|
40161
40231
|
cleanup();
|
|
40162
40232
|
out += parser.flush();
|
|
40163
|
-
|
|
40233
|
+
resolve12({ code: code ?? -1, out, err, usage: parser.usage() });
|
|
40164
40234
|
});
|
|
40165
40235
|
child.stdin.write(prompt);
|
|
40166
40236
|
child.stdin.end();
|
|
40167
40237
|
});
|
|
40168
40238
|
}
|
|
40169
|
-
async function runClaudeWithFailover(prompt, ai2, imageDir) {
|
|
40239
|
+
async function runClaudeWithFailover(prompt, ai2, imageDir, timeoutMs = ANSWER_TIMEOUT_MS) {
|
|
40170
40240
|
const attempts = ai2.profiles.length > 0 ? ai2.profiles : [null];
|
|
40171
40241
|
let lastReason = "no attempt";
|
|
40172
40242
|
for (let i = 0; i < attempts.length; i++) {
|
|
40173
|
-
const { code, out, err, usage } = await runClaudeOnce(
|
|
40243
|
+
const { code, out, err, usage } = await runClaudeOnce(
|
|
40244
|
+
ai2.cmd,
|
|
40245
|
+
attempts[i],
|
|
40246
|
+
prompt,
|
|
40247
|
+
ai2.env,
|
|
40248
|
+
imageDir,
|
|
40249
|
+
ai2.profiles.map((p2) => p2.dir),
|
|
40250
|
+
timeoutMs
|
|
40251
|
+
);
|
|
40174
40252
|
if (code === 0 && out.trim()) return { text: out.trim(), usage };
|
|
40175
40253
|
const combined = err || out;
|
|
40176
40254
|
lastReason = `exited ${code}: ${combined.slice(0, 500)}`;
|
|
@@ -40191,7 +40269,8 @@ async function runSupportAnswer(req, ai2, profileDir2) {
|
|
|
40191
40269
|
const docs = collectDocs(dir2);
|
|
40192
40270
|
if (!docs.trim()) return { body: "", error: "KB repo has no documentation" };
|
|
40193
40271
|
const prompt = req.task ? buildDraftPrompt(docs, question, req.task) : buildPrompt(docs, question, req.askerRole);
|
|
40194
|
-
const
|
|
40272
|
+
const timeoutMs = req.task === "legal_draft" ? LEGAL_DRAFT_TIMEOUT_MS : ANSWER_TIMEOUT_MS;
|
|
40273
|
+
const { text, usage } = await runClaudeWithFailover(prompt, ai2, imageDir, timeoutMs);
|
|
40195
40274
|
if (!text) return { body: "", error: "empty answer" };
|
|
40196
40275
|
const { body, moderation } = req.task ? { body: text.trim(), moderation: void 0 } : parseModeratedAnswer(text);
|
|
40197
40276
|
if (!body) return { body: "", error: "empty answer" };
|
|
@@ -40428,10 +40507,10 @@ async function crontabList() {
|
|
|
40428
40507
|
}
|
|
40429
40508
|
}
|
|
40430
40509
|
async function crontabSet(content) {
|
|
40431
|
-
return new Promise((
|
|
40510
|
+
return new Promise((resolve12) => {
|
|
40432
40511
|
const child = spawn5("crontab", ["-"], { stdio: ["pipe", "ignore", "ignore"] });
|
|
40433
|
-
child.on("error", () =>
|
|
40434
|
-
child.on("close", (code) =>
|
|
40512
|
+
child.on("error", () => resolve12(false));
|
|
40513
|
+
child.on("close", (code) => resolve12(code === 0));
|
|
40435
40514
|
child.stdin.end(content.endsWith("\n") ? content : content + "\n");
|
|
40436
40515
|
});
|
|
40437
40516
|
}
|
|
@@ -40600,14 +40679,14 @@ function detectContainerized() {
|
|
|
40600
40679
|
}
|
|
40601
40680
|
}
|
|
40602
40681
|
function canReach(host, port) {
|
|
40603
|
-
return new Promise((
|
|
40682
|
+
return new Promise((resolve12) => {
|
|
40604
40683
|
const socket = connect({ host, port });
|
|
40605
40684
|
let done = false;
|
|
40606
40685
|
const finish = (ok) => {
|
|
40607
40686
|
if (done) return;
|
|
40608
40687
|
done = true;
|
|
40609
40688
|
socket.destroy();
|
|
40610
|
-
|
|
40689
|
+
resolve12(ok);
|
|
40611
40690
|
};
|
|
40612
40691
|
socket.setTimeout(PROBE_TIMEOUT_MS);
|
|
40613
40692
|
socket.once("connect", () => finish(true));
|
|
@@ -40660,32 +40739,177 @@ async function probeNetwork() {
|
|
|
40660
40739
|
}
|
|
40661
40740
|
|
|
40662
40741
|
// 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
|
-
|
|
40742
|
+
import { chmodSync as chmodSync3, existsSync as existsSync12, mkdirSync as mkdirSync6, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "fs";
|
|
40743
|
+
import { request as httpsRequest } from "https";
|
|
40744
|
+
import { delimiter, dirname as dirname4, join as join18, resolve as resolve3 } from "path";
|
|
40745
|
+
var JOB_ID_ENV = "FOURPM_JOB_ID";
|
|
40746
|
+
var PROFILE_DIR_ENV = "FOURPM_PROFILE_DIR";
|
|
40747
|
+
var GIT_HOST_ENV = "FOURPM_GIT_HOST";
|
|
40748
|
+
var GIT_HOST_KIND_ENV = "FOURPM_GIT_HOST_KIND";
|
|
40749
|
+
var DEFAULT_SCOPE = "link";
|
|
40750
|
+
var HELPER_ENV_ENTRIES = 3;
|
|
40751
|
+
var DEFAULT_SCOPE_IDLE_MS = 10 * 60 * 1e3;
|
|
40752
|
+
var state = { host: null, transport: null, baseEnv: null, scopes: /* @__PURE__ */ new Map(), defaultTimer: null };
|
|
40753
|
+
function cliInvocation() {
|
|
40754
|
+
const quote = (s3) => `"${s3.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
40755
|
+
const script = process.argv[1];
|
|
40756
|
+
return [process.execPath, ...process.execArgv, ...script ? [script] : []].map(quote).join(" ");
|
|
40757
|
+
}
|
|
40758
|
+
function helperCommand() {
|
|
40759
|
+
return `!${cliInvocation()} git-credential`;
|
|
40760
|
+
}
|
|
40761
|
+
function findRealGh(pathValue, shimDir) {
|
|
40762
|
+
for (const dir2 of pathValue.split(delimiter)) {
|
|
40763
|
+
if (!dir2 || dir2 === shimDir) continue;
|
|
40764
|
+
const candidate = join18(dir2, process.platform === "win32" ? "gh.exe" : "gh");
|
|
40765
|
+
if (existsSync12(candidate)) return candidate;
|
|
40672
40766
|
}
|
|
40673
40767
|
return null;
|
|
40674
40768
|
}
|
|
40675
|
-
function
|
|
40676
|
-
|
|
40677
|
-
|
|
40678
|
-
|
|
40679
|
-
|
|
40680
|
-
|
|
40681
|
-
|
|
40769
|
+
function shimScript(realGh, cli) {
|
|
40770
|
+
return `#!/bin/sh
|
|
40771
|
+
# 4PM gh shim (ADR-0356) \u2014 injects a short-lived GitHub-App token for the served project.
|
|
40772
|
+
if [ -z "$GH_TOKEN$GH_ENTERPRISE_TOKEN$GITHUB_TOKEN" ]; then
|
|
40773
|
+
T="$(${cli} git-token 2>/dev/null)"
|
|
40774
|
+
if [ -n "$T" ]; then
|
|
40775
|
+
case "$${GIT_HOST_KIND_ENV}" in
|
|
40776
|
+
ghes) export GH_HOST="$${GIT_HOST_ENV}" GH_ENTERPRISE_TOKEN="$T"; unset GH_TOKEN ;;
|
|
40777
|
+
ghe-cloud) export GH_HOST="$${GIT_HOST_ENV}" GH_TOKEN="$T"; unset GH_ENTERPRISE_TOKEN ;;
|
|
40778
|
+
*) export GH_TOKEN="$T"; unset GH_ENTERPRISE_TOKEN ;;
|
|
40779
|
+
esac
|
|
40780
|
+
fi
|
|
40781
|
+
fi
|
|
40782
|
+
exec "${realGh}" "$@"
|
|
40682
40783
|
`;
|
|
40683
|
-
|
|
40784
|
+
}
|
|
40785
|
+
function clearEnv() {
|
|
40786
|
+
if (!state.baseEnv) return;
|
|
40787
|
+
const { count, path } = state.baseEnv;
|
|
40788
|
+
for (let i = count; i < count + HELPER_ENV_ENTRIES; i++) {
|
|
40789
|
+
delete process.env[`GIT_CONFIG_KEY_${i}`];
|
|
40790
|
+
delete process.env[`GIT_CONFIG_VALUE_${i}`];
|
|
40791
|
+
}
|
|
40792
|
+
if (count > 0) process.env.GIT_CONFIG_COUNT = String(count);
|
|
40793
|
+
else delete process.env.GIT_CONFIG_COUNT;
|
|
40794
|
+
process.env.PATH = path;
|
|
40795
|
+
delete process.env[GIT_HOST_ENV];
|
|
40796
|
+
delete process.env[GIT_HOST_KIND_ENV];
|
|
40797
|
+
state.baseEnv = null;
|
|
40798
|
+
}
|
|
40799
|
+
function configureGitAuth(method, host, profileDir2, transport) {
|
|
40800
|
+
try {
|
|
40801
|
+
const active = method === "github-app" && !!host;
|
|
40802
|
+
if (!active || state.host && state.host !== host) {
|
|
40803
|
+
if (state.host) void revokeAll();
|
|
40804
|
+
clearEnv();
|
|
40805
|
+
state.host = null;
|
|
40806
|
+
}
|
|
40807
|
+
if (!active) return false;
|
|
40808
|
+
state.transport = transport;
|
|
40809
|
+
if (state.host === host && state.baseEnv) return true;
|
|
40810
|
+
const baseCount = Number(process.env.GIT_CONFIG_COUNT ?? "0") || 0;
|
|
40811
|
+
state.baseEnv = { count: baseCount, path: process.env.PATH ?? "" };
|
|
40812
|
+
const prefix = `credential.https://${host}`;
|
|
40813
|
+
const entries2 = [
|
|
40814
|
+
[`${prefix}.helper`, ""],
|
|
40815
|
+
[`${prefix}.helper`, helperCommand()],
|
|
40816
|
+
[`${prefix}.useHttpPath`, "true"]
|
|
40817
|
+
];
|
|
40818
|
+
entries2.forEach(([key, value], i) => {
|
|
40819
|
+
process.env[`GIT_CONFIG_KEY_${baseCount + i}`] = key;
|
|
40820
|
+
process.env[`GIT_CONFIG_VALUE_${baseCount + i}`] = value;
|
|
40821
|
+
});
|
|
40822
|
+
process.env.GIT_CONFIG_COUNT = String(baseCount + HELPER_ENV_ENTRIES);
|
|
40823
|
+
process.env[PROFILE_DIR_ENV] = resolve3(profileDir2);
|
|
40824
|
+
const script = process.argv[1];
|
|
40825
|
+
const devTsconfig = script ? resolve3(dirname4(script), "..", "tsconfig.json") : "";
|
|
40826
|
+
if (process.execArgv.some((a) => a.includes("tsx")) && devTsconfig && existsSync12(devTsconfig) && !process.env.TSX_TSCONFIG_PATH) {
|
|
40827
|
+
process.env.TSX_TSCONFIG_PATH = devTsconfig;
|
|
40828
|
+
}
|
|
40829
|
+
process.env[GIT_HOST_ENV] = host;
|
|
40830
|
+
process.env[GIT_HOST_KIND_ENV] = githubHostKind(host);
|
|
40831
|
+
if (process.platform !== "win32") {
|
|
40832
|
+
const shimDir = join18(profileDir2, "bin");
|
|
40833
|
+
const realGh = findRealGh(state.baseEnv.path, shimDir);
|
|
40834
|
+
if (realGh) {
|
|
40835
|
+
mkdirSync6(shimDir, { recursive: true });
|
|
40836
|
+
const shim = join18(shimDir, "gh");
|
|
40837
|
+
writeFileSync8(shim, shimScript(realGh, cliInvocation()), { encoding: "utf8", mode: 493 });
|
|
40838
|
+
chmodSync3(shim, 493);
|
|
40839
|
+
process.env.PATH = `${shimDir}${delimiter}${state.baseEnv.path}`;
|
|
40840
|
+
} else {
|
|
40841
|
+
rmSync6(join18(shimDir, "gh"), { force: true });
|
|
40842
|
+
}
|
|
40843
|
+
}
|
|
40844
|
+
state.host = host;
|
|
40845
|
+
logger.info("git.auth.github-app", { host });
|
|
40684
40846
|
return true;
|
|
40685
|
-
} catch {
|
|
40847
|
+
} catch (err) {
|
|
40848
|
+
logger.warn("git.auth.configure.failed", { error: String(err) });
|
|
40686
40849
|
return false;
|
|
40687
40850
|
}
|
|
40688
40851
|
}
|
|
40852
|
+
function touchDefaultScope() {
|
|
40853
|
+
if (state.defaultTimer) clearTimeout(state.defaultTimer);
|
|
40854
|
+
state.defaultTimer = setTimeout(() => void endGitScope(DEFAULT_SCOPE), DEFAULT_SCOPE_IDLE_MS);
|
|
40855
|
+
state.defaultTimer.unref?.();
|
|
40856
|
+
}
|
|
40857
|
+
async function issueGitToken(scope, host, path) {
|
|
40858
|
+
try {
|
|
40859
|
+
if (!state.host || !state.transport || host.toLowerCase() !== state.host) return null;
|
|
40860
|
+
const s3 = scope || DEFAULT_SCOPE;
|
|
40861
|
+
const reply = await state.transport({ op: "issue", scope: s3, repo: { host, path } });
|
|
40862
|
+
if (!reply.token) return null;
|
|
40863
|
+
const list = state.scopes.get(s3) ?? [];
|
|
40864
|
+
if (!list.some((t2) => t2.token === reply.token)) list.push({ token: reply.token, apiBase: reply.apiBase ?? "" });
|
|
40865
|
+
state.scopes.set(s3, list);
|
|
40866
|
+
if (s3 === DEFAULT_SCOPE) touchDefaultScope();
|
|
40867
|
+
return reply.token;
|
|
40868
|
+
} catch (err) {
|
|
40869
|
+
logger.warn("git.token.issue.failed", { error: String(err) });
|
|
40870
|
+
return null;
|
|
40871
|
+
}
|
|
40872
|
+
}
|
|
40873
|
+
function revokeAtGithub(t2) {
|
|
40874
|
+
return new Promise((resolve12) => {
|
|
40875
|
+
try {
|
|
40876
|
+
const req = httpsRequest(
|
|
40877
|
+
new URL(`${t2.apiBase.replace(/\/+$/, "")}/installation/token`),
|
|
40878
|
+
{
|
|
40879
|
+
method: "DELETE",
|
|
40880
|
+
timeout: 1e4,
|
|
40881
|
+
headers: { Authorization: `token ${t2.token}`, Accept: "application/vnd.github+json", "User-Agent": "4pm-cli" }
|
|
40882
|
+
},
|
|
40883
|
+
(res) => {
|
|
40884
|
+
res.resume();
|
|
40885
|
+
res.on("end", () => resolve12());
|
|
40886
|
+
}
|
|
40887
|
+
);
|
|
40888
|
+
req.on("timeout", () => req.destroy());
|
|
40889
|
+
req.on("error", () => resolve12());
|
|
40890
|
+
req.end();
|
|
40891
|
+
} catch {
|
|
40892
|
+
resolve12();
|
|
40893
|
+
}
|
|
40894
|
+
});
|
|
40895
|
+
}
|
|
40896
|
+
async function endGitScope(scope) {
|
|
40897
|
+
const tokens = state.scopes.get(scope);
|
|
40898
|
+
if (!tokens) return;
|
|
40899
|
+
state.scopes.delete(scope);
|
|
40900
|
+
if (scope === DEFAULT_SCOPE && state.defaultTimer) {
|
|
40901
|
+
clearTimeout(state.defaultTimer);
|
|
40902
|
+
state.defaultTimer = null;
|
|
40903
|
+
}
|
|
40904
|
+
await Promise.all(tokens.filter((t2) => t2.apiBase).map((t2) => revokeAtGithub(t2)));
|
|
40905
|
+
try {
|
|
40906
|
+
await state.transport?.({ op: "revoked", scope });
|
|
40907
|
+
} catch {
|
|
40908
|
+
}
|
|
40909
|
+
}
|
|
40910
|
+
async function revokeAll() {
|
|
40911
|
+
await Promise.all([...state.scopes.keys()].map((s3) => endGitScope(s3)));
|
|
40912
|
+
}
|
|
40689
40913
|
|
|
40690
40914
|
// src/core/ws-client/transcript.ts
|
|
40691
40915
|
function toDtoEntry(e) {
|
|
@@ -40716,13 +40940,18 @@ import { randomUUID as randomCommandId } from "crypto";
|
|
|
40716
40940
|
// src/core/executor.ts
|
|
40717
40941
|
import { spawn as spawn6 } from "child_process";
|
|
40718
40942
|
var KILL_GRACE_MS = 5e3;
|
|
40719
|
-
async function runCommand(dispatch,
|
|
40943
|
+
async function runCommand(dispatch, emitRaw, opts) {
|
|
40720
40944
|
let seq = 0;
|
|
40945
|
+
const emit = (output) => {
|
|
40946
|
+
emitRaw(output);
|
|
40947
|
+
if (output.done) void endGitScope(dispatch.commandId);
|
|
40948
|
+
};
|
|
40721
40949
|
let child;
|
|
40722
40950
|
try {
|
|
40723
40951
|
child = spawn6(dispatch.cmd, dispatch.args ?? [], {
|
|
40724
40952
|
cwd: dispatch.path,
|
|
40725
|
-
|
|
40953
|
+
// FOURPM_JOB_ID scopes the git helper / gh shim tokens to this run (ADR-0356).
|
|
40954
|
+
env: { ...process.env, ...dispatch.env, [JOB_ID_ENV]: dispatch.commandId },
|
|
40726
40955
|
shell: false
|
|
40727
40956
|
});
|
|
40728
40957
|
} catch (err) {
|
|
@@ -40779,7 +41008,7 @@ async function runCommand(dispatch, emit, opts) {
|
|
|
40779
41008
|
});
|
|
40780
41009
|
child.stdout?.on("data", (data) => batcher.push(data.toString("utf8")));
|
|
40781
41010
|
child.stderr?.on("data", (data) => batcher.push(data.toString("utf8")));
|
|
40782
|
-
await new Promise((
|
|
41011
|
+
await new Promise((resolve12) => {
|
|
40783
41012
|
child.on("close", (exitCode) => {
|
|
40784
41013
|
clearTimers();
|
|
40785
41014
|
batcher.flush();
|
|
@@ -40792,7 +41021,7 @@ async function runCommand(dispatch, emit, opts) {
|
|
|
40792
41021
|
// server settles the command as failed and the web surfaces the note instead of spinning.
|
|
40793
41022
|
exitCode: timedOut ? 124 : exitCode ?? -1
|
|
40794
41023
|
});
|
|
40795
|
-
|
|
41024
|
+
resolve12();
|
|
40796
41025
|
});
|
|
40797
41026
|
child.on("error", (err) => {
|
|
40798
41027
|
clearTimers();
|
|
@@ -40804,7 +41033,7 @@ async function runCommand(dispatch, emit, opts) {
|
|
|
40804
41033
|
done: true,
|
|
40805
41034
|
exitCode: -1
|
|
40806
41035
|
});
|
|
40807
|
-
|
|
41036
|
+
resolve12();
|
|
40808
41037
|
});
|
|
40809
41038
|
});
|
|
40810
41039
|
}
|
|
@@ -40827,15 +41056,15 @@ function isOutOfCredits(text) {
|
|
|
40827
41056
|
);
|
|
40828
41057
|
}
|
|
40829
41058
|
function runAttempt(commandId, cmd, args, stdin, cwd2, env3, onChunk, timeoutMs) {
|
|
40830
|
-
return new Promise((
|
|
41059
|
+
return new Promise((resolve12) => {
|
|
40831
41060
|
runCommand(
|
|
40832
41061
|
{ commandId, cmd, args, path: cwd2, env: env3 },
|
|
40833
41062
|
(out) => {
|
|
40834
41063
|
if (out.chunk) onChunk(out.chunk);
|
|
40835
|
-
if (out.done)
|
|
41064
|
+
if (out.done) resolve12(out.exitCode ?? -1);
|
|
40836
41065
|
},
|
|
40837
41066
|
{ stdin, ...timeoutMs > 0 ? { timeoutMs } : {} }
|
|
40838
|
-
).catch(() =>
|
|
41067
|
+
).catch(() => resolve12(-1));
|
|
40839
41068
|
});
|
|
40840
41069
|
}
|
|
40841
41070
|
async function runAiFailover(plan, commandId, cwd2, handlers, timeoutMs = 0) {
|
|
@@ -40918,7 +41147,7 @@ function formatTimestampInZone(date, timeZone) {
|
|
|
40918
41147
|
}
|
|
40919
41148
|
|
|
40920
41149
|
// src/ui/slash-commands.ts
|
|
40921
|
-
import { existsSync as
|
|
41150
|
+
import { existsSync as existsSync13 } from "fs";
|
|
40922
41151
|
import { join as join19 } from "path";
|
|
40923
41152
|
var CONFIG_KEYS = [
|
|
40924
41153
|
"aiCli",
|
|
@@ -40981,7 +41210,7 @@ function runConfig(ctx) {
|
|
|
40981
41210
|
ctx.print(t("slash.config.wroteDefault", { verb, path }));
|
|
40982
41211
|
ctx.print(t("slash.config.addClaudeHint"));
|
|
40983
41212
|
};
|
|
40984
|
-
if (
|
|
41213
|
+
if (existsSync13(path)) {
|
|
40985
41214
|
ctx.confirm(
|
|
40986
41215
|
t("slash.config.replaceConfirm", { path }),
|
|
40987
41216
|
() => writeDefaults(t("slash.config.verbReplaced"))
|
|
@@ -41043,8 +41272,8 @@ function runHistory(ctx) {
|
|
|
41043
41272
|
ctx.print(t("slash.history.header", { n: recent.length, total: all.length }));
|
|
41044
41273
|
recent.forEach((e, i) => {
|
|
41045
41274
|
const time = new Date(e.startedAt).toLocaleTimeString();
|
|
41046
|
-
const
|
|
41047
|
-
ctx.print(` [${i + 1}] ${time} ${commandLine(e.cmd, e.args)} \xB7 ${
|
|
41275
|
+
const state2 = e.status === "done" ? "\u2713" : e.status === "failed" ? `\u2717 exit ${e.exitCode}` : t("slash.history.running");
|
|
41276
|
+
ctx.print(` [${i + 1}] ${time} ${commandLine(e.cmd, e.args)} \xB7 ${state2}`);
|
|
41048
41277
|
});
|
|
41049
41278
|
}
|
|
41050
41279
|
function runOutput(ctx) {
|
|
@@ -41055,8 +41284,8 @@ function runOutput(ctx) {
|
|
|
41055
41284
|
return;
|
|
41056
41285
|
}
|
|
41057
41286
|
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 }));
|
|
41287
|
+
const state2 = entry.exitCode != null ? ` (exit ${entry.exitCode})` : "";
|
|
41288
|
+
ctx.print(t("slash.output.header", { n, cmd: commandLine(entry.cmd, entry.args), status: entry.status, state: state2 }));
|
|
41060
41289
|
const out = readCommandOutput(entry.commandId);
|
|
41061
41290
|
if (out == null) {
|
|
41062
41291
|
ctx.print(t("slash.output.none"), "warn");
|
|
@@ -41351,7 +41580,7 @@ function buildPrompt2(input) {
|
|
|
41351
41580
|
].join("\n");
|
|
41352
41581
|
}
|
|
41353
41582
|
function runOnce(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
|
|
41354
|
-
return new Promise((
|
|
41583
|
+
return new Promise((resolve12, reject) => {
|
|
41355
41584
|
const args = [
|
|
41356
41585
|
"-p",
|
|
41357
41586
|
"--output-format",
|
|
@@ -41386,7 +41615,7 @@ function runOnce(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
|
|
|
41386
41615
|
});
|
|
41387
41616
|
child.on("close", (code) => {
|
|
41388
41617
|
clearTimeout(timer);
|
|
41389
|
-
|
|
41618
|
+
resolve12({ code: code ?? -1, out, err });
|
|
41390
41619
|
});
|
|
41391
41620
|
child.stdin.write(prompt);
|
|
41392
41621
|
child.stdin.end();
|
|
@@ -41424,6 +41653,54 @@ async function setCommitAuthor(cwd2, machineUsername) {
|
|
|
41424
41653
|
}
|
|
41425
41654
|
}
|
|
41426
41655
|
|
|
41656
|
+
// src/core/ws-client/run-slot.ts
|
|
41657
|
+
var NO_SLOT = { release: () => void 0 };
|
|
41658
|
+
function sleep2(ms2) {
|
|
41659
|
+
return new Promise((resolve12) => setTimeout(resolve12, ms2));
|
|
41660
|
+
}
|
|
41661
|
+
function holdLease(ctx, lease) {
|
|
41662
|
+
const timer = setInterval(() => {
|
|
41663
|
+
ctx.request(WsChannels.RUN_SLOT, { op: "renew", lease }).catch(() => void 0);
|
|
41664
|
+
}, RUN_SLOT_RENEW_MS);
|
|
41665
|
+
timer.unref?.();
|
|
41666
|
+
let released = false;
|
|
41667
|
+
return {
|
|
41668
|
+
release: () => {
|
|
41669
|
+
if (released) return;
|
|
41670
|
+
released = true;
|
|
41671
|
+
clearInterval(timer);
|
|
41672
|
+
ctx.request(WsChannels.RUN_SLOT, { op: "release", lease }).catch(() => void 0);
|
|
41673
|
+
}
|
|
41674
|
+
};
|
|
41675
|
+
}
|
|
41676
|
+
async function acquireRunSlot(ctx, onQueued) {
|
|
41677
|
+
const startedAt = Date.now();
|
|
41678
|
+
let ticket;
|
|
41679
|
+
let lastPosition = -1;
|
|
41680
|
+
let limit = 0;
|
|
41681
|
+
for (; ; ) {
|
|
41682
|
+
let reply;
|
|
41683
|
+
try {
|
|
41684
|
+
reply = await ctx.request(WsChannels.RUN_SLOT, { op: "acquire", ...ticket ? { ticket } : {} });
|
|
41685
|
+
} catch (err) {
|
|
41686
|
+
logger.warn("run.slot.unavailable", { error: err.message });
|
|
41687
|
+
return { kind: "granted", handle: NO_SLOT };
|
|
41688
|
+
}
|
|
41689
|
+
if (!reply || !("granted" in reply)) return { kind: "granted", handle: NO_SLOT };
|
|
41690
|
+
if (reply.granted) {
|
|
41691
|
+
return { kind: "granted", handle: reply.lease ? holdLease(ctx, reply.lease) : NO_SLOT };
|
|
41692
|
+
}
|
|
41693
|
+
ticket = reply.ticket;
|
|
41694
|
+
limit = reply.limit;
|
|
41695
|
+
if (reply.position !== lastPosition) {
|
|
41696
|
+
lastPosition = reply.position;
|
|
41697
|
+
onQueued({ position: reply.position, limit: reply.limit, running: reply.running });
|
|
41698
|
+
}
|
|
41699
|
+
if (Date.now() - startedAt >= RUN_SLOT_MAX_WAIT_MS) return { kind: "timeout", limit };
|
|
41700
|
+
await sleep2(reply.retryAfterMs > 0 ? reply.retryAfterMs : RUN_SLOT_RETRY_MS);
|
|
41701
|
+
}
|
|
41702
|
+
}
|
|
41703
|
+
|
|
41427
41704
|
// src/core/ws-client/command-dispatch.ts
|
|
41428
41705
|
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:";
|
|
41429
41706
|
function handleCommandChannels(ctx, message, payload) {
|
|
@@ -41677,8 +41954,28 @@ ${guardedPrompt}`;
|
|
|
41677
41954
|
}
|
|
41678
41955
|
};
|
|
41679
41956
|
let result;
|
|
41957
|
+
let queueTimedOut = false;
|
|
41680
41958
|
try {
|
|
41681
|
-
|
|
41959
|
+
const slot = await acquireRunSlot(ctx, ({ position, limit, running: running2 }) => {
|
|
41960
|
+
const text = `\u23F3 queued \u2014 ${position} ahead, ${running2}/${limit} parallel runs in use (plan limit)`;
|
|
41961
|
+
ctx.bus.push({ source: origin, kind: "log", text });
|
|
41962
|
+
ctx.send(WsChannels.COMMAND_OUTPUT, { commandId, seq: seq++, chunk: `${text}
|
|
41963
|
+
`, log: true });
|
|
41964
|
+
});
|
|
41965
|
+
if (slot.kind === "timeout") {
|
|
41966
|
+
queueTimedOut = true;
|
|
41967
|
+
const text = `RUN_QUEUE_TIMEOUT: waited 30 min for a free run slot (plan limit ${slot.limit} parallel runs) \u2014 not run`;
|
|
41968
|
+
ctx.bus.push({ source: origin, kind: "log", text, level: "warn" });
|
|
41969
|
+
ctx.send(WsChannels.COMMAND_OUTPUT, { commandId, seq: seq++, chunk: `${text}
|
|
41970
|
+
`, log: true });
|
|
41971
|
+
result = { exitCode: 1, workedDir: null, workedKey: null, workedCmd: null, usage: { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 }, sessionId: "" };
|
|
41972
|
+
} else {
|
|
41973
|
+
try {
|
|
41974
|
+
result = await runAiFailover(plan, commandId, cwd2, handlers, aiRunTimeoutMs);
|
|
41975
|
+
} finally {
|
|
41976
|
+
slot.handle.release();
|
|
41977
|
+
}
|
|
41978
|
+
}
|
|
41682
41979
|
} finally {
|
|
41683
41980
|
ctx.bus.endBusy(plan.cmd);
|
|
41684
41981
|
}
|
|
@@ -41739,7 +42036,7 @@ ${guardedPrompt}`;
|
|
|
41739
42036
|
});
|
|
41740
42037
|
ctx.bus.addTokens?.(result.usage.tokens);
|
|
41741
42038
|
}
|
|
41742
|
-
ctx.reportUsage(events, profileLabel);
|
|
42039
|
+
if (!queueTimedOut) ctx.reportUsage(events, profileLabel);
|
|
41743
42040
|
void ctx.pollUsage();
|
|
41744
42041
|
ctx.bus.push({
|
|
41745
42042
|
source: origin,
|
|
@@ -42222,11 +42519,11 @@ async function allAiProfilesExhausted(ctx, cfg) {
|
|
|
42222
42519
|
|
|
42223
42520
|
// src/core/fs-browse.ts
|
|
42224
42521
|
import { readdir as readdir3 } from "fs/promises";
|
|
42225
|
-
import { resolve as
|
|
42522
|
+
import { resolve as resolve4, sep as sep2 } from "path";
|
|
42226
42523
|
async function listDir(path, root) {
|
|
42227
42524
|
if (!root) return { path: "", entries: [] };
|
|
42228
|
-
const base =
|
|
42229
|
-
const requested =
|
|
42525
|
+
const base = resolve4(root);
|
|
42526
|
+
const requested = resolve4(base, path && path.trim() ? path : ".");
|
|
42230
42527
|
const target = requested === base || requested.startsWith(base + sep2) ? requested : base;
|
|
42231
42528
|
try {
|
|
42232
42529
|
const dirents = await readdir3(target, { withFileTypes: true });
|
|
@@ -42245,12 +42542,12 @@ async function listDir(path, root) {
|
|
|
42245
42542
|
|
|
42246
42543
|
// src/core/fs-read.ts
|
|
42247
42544
|
import { readFile as readFile6 } from "fs/promises";
|
|
42248
|
-
import { resolve as
|
|
42545
|
+
import { resolve as resolve5, sep as sep3 } from "path";
|
|
42249
42546
|
var MAX_BYTES = 256 * 1024;
|
|
42250
42547
|
function resolveInRoot(root, relPath) {
|
|
42251
42548
|
if (!root) return null;
|
|
42252
|
-
const base =
|
|
42253
|
-
const target =
|
|
42549
|
+
const base = resolve5(root);
|
|
42550
|
+
const target = resolve5(base, relPath || ".");
|
|
42254
42551
|
return target === base || target.startsWith(base + sep3) ? target : null;
|
|
42255
42552
|
}
|
|
42256
42553
|
async function readWorkerFile(root, path) {
|
|
@@ -42270,12 +42567,12 @@ async function readWorkerFile(root, path) {
|
|
|
42270
42567
|
|
|
42271
42568
|
// src/core/fs-write.ts
|
|
42272
42569
|
import { mkdir as mkdir4, writeFile as writeFile5 } from "fs/promises";
|
|
42273
|
-
import { dirname as
|
|
42570
|
+
import { dirname as dirname5, resolve as resolve6, sep as sep4 } from "path";
|
|
42274
42571
|
var MAX_BYTES2 = 2 * 1024 * 1024;
|
|
42275
42572
|
function resolveInRoot2(root, relPath) {
|
|
42276
42573
|
if (!root) return null;
|
|
42277
|
-
const base =
|
|
42278
|
-
const target =
|
|
42574
|
+
const base = resolve6(root);
|
|
42575
|
+
const target = resolve6(base, relPath || ".");
|
|
42279
42576
|
return target === base || target.startsWith(base + sep4) ? target : null;
|
|
42280
42577
|
}
|
|
42281
42578
|
async function writeWorkerFile(root, relPath, content) {
|
|
@@ -42288,7 +42585,7 @@ async function writeWorkerFile(root, relPath, content) {
|
|
|
42288
42585
|
return { ok: false, path: target, bytes: 0, error: `content too large (max ${MAX_BYTES2} bytes)` };
|
|
42289
42586
|
}
|
|
42290
42587
|
try {
|
|
42291
|
-
await mkdir4(
|
|
42588
|
+
await mkdir4(dirname5(target), { recursive: true });
|
|
42292
42589
|
await writeFile5(target, content, "utf8");
|
|
42293
42590
|
return { ok: true, path: target, bytes };
|
|
42294
42591
|
} catch (err) {
|
|
@@ -42298,11 +42595,11 @@ async function writeWorkerFile(root, relPath, content) {
|
|
|
42298
42595
|
|
|
42299
42596
|
// src/core/fs-mutate.ts
|
|
42300
42597
|
import { mkdir as mkdir5, writeFile as writeFile6, rename, rm as rm3, stat as stat2 } from "fs/promises";
|
|
42301
|
-
import { basename as basename3, dirname as
|
|
42598
|
+
import { basename as basename3, dirname as dirname6, resolve as resolve7, sep as sep5 } from "path";
|
|
42302
42599
|
var MAX_BYTES3 = 2 * 1024 * 1024;
|
|
42303
42600
|
function resolveInRoot3(root, rel) {
|
|
42304
|
-
const base =
|
|
42305
|
-
const target =
|
|
42601
|
+
const base = resolve7(root);
|
|
42602
|
+
const target = resolve7(base, rel || ".");
|
|
42306
42603
|
return target === base || target.startsWith(base + sep5) ? target : null;
|
|
42307
42604
|
}
|
|
42308
42605
|
async function isDir(p2) {
|
|
@@ -42327,19 +42624,19 @@ async function mutateFs(root, req) {
|
|
|
42327
42624
|
switch (req.op) {
|
|
42328
42625
|
case "mkdir": {
|
|
42329
42626
|
const target = resolveInRoot3(root, req.path ?? "");
|
|
42330
|
-
if (!target || target ===
|
|
42627
|
+
if (!target || target === resolve7(root)) return escaped(req.path ?? "");
|
|
42331
42628
|
await mkdir5(target, { recursive: true });
|
|
42332
42629
|
return { ok: true, path: target };
|
|
42333
42630
|
}
|
|
42334
42631
|
case "create": {
|
|
42335
42632
|
const target = resolveInRoot3(root, req.path ?? "");
|
|
42336
|
-
if (!target || target ===
|
|
42633
|
+
if (!target || target === resolve7(root)) return escaped(req.path ?? "");
|
|
42337
42634
|
if (await exists(target)) return { ok: false, path: target, error: "already exists" };
|
|
42338
42635
|
const content = req.content ?? "";
|
|
42339
42636
|
if (Buffer.byteLength(content, "utf8") > MAX_BYTES3) {
|
|
42340
42637
|
return { ok: false, path: target, error: `content too large (max ${MAX_BYTES3} bytes)` };
|
|
42341
42638
|
}
|
|
42342
|
-
await mkdir5(
|
|
42639
|
+
await mkdir5(dirname6(target), { recursive: true });
|
|
42343
42640
|
await writeFile6(target, content, "utf8");
|
|
42344
42641
|
return { ok: true, path: target };
|
|
42345
42642
|
}
|
|
@@ -42347,18 +42644,18 @@ async function mutateFs(root, req) {
|
|
|
42347
42644
|
const from = resolveInRoot3(root, req.from ?? "");
|
|
42348
42645
|
let to2 = resolveInRoot3(root, req.to ?? "");
|
|
42349
42646
|
if (!from || !to2) return escaped(req.from ?? req.to ?? "");
|
|
42350
|
-
if (from ===
|
|
42351
|
-
if (await isDir(to2)) to2 =
|
|
42352
|
-
if (to2 !==
|
|
42647
|
+
if (from === resolve7(root)) return { ok: false, path: from, error: "cannot move the project root" };
|
|
42648
|
+
if (await isDir(to2)) to2 = resolve7(to2, basename3(from));
|
|
42649
|
+
if (to2 !== resolve7(root) && !to2.startsWith(resolve7(root) + sep5)) return escaped(req.to ?? "");
|
|
42353
42650
|
if (await exists(to2)) return { ok: false, path: to2, error: "destination already exists" };
|
|
42354
|
-
await mkdir5(
|
|
42651
|
+
await mkdir5(dirname6(to2), { recursive: true });
|
|
42355
42652
|
await rename(from, to2);
|
|
42356
42653
|
return { ok: true, path: to2 };
|
|
42357
42654
|
}
|
|
42358
42655
|
case "delete": {
|
|
42359
42656
|
const target = resolveInRoot3(root, req.path ?? "");
|
|
42360
42657
|
if (!target) return escaped(req.path ?? "");
|
|
42361
|
-
if (target ===
|
|
42658
|
+
if (target === resolve7(root)) return { ok: false, path: target, error: "cannot delete the project root" };
|
|
42362
42659
|
await rm3(target, { recursive: true, force: true });
|
|
42363
42660
|
return { ok: true, path: target };
|
|
42364
42661
|
}
|
|
@@ -42372,11 +42669,11 @@ async function mutateFs(root, req) {
|
|
|
42372
42669
|
|
|
42373
42670
|
// src/core/fs-transfer.ts
|
|
42374
42671
|
import { mkdir as mkdir6, readFile as readFile7, stat as stat3, writeFile as writeFile7 } from "fs/promises";
|
|
42375
|
-
import { basename as basename4, dirname as
|
|
42672
|
+
import { basename as basename4, dirname as dirname7, extname, resolve as resolve8, sep as sep6 } from "path";
|
|
42376
42673
|
function resolveInRoot4(root, relPath) {
|
|
42377
42674
|
if (!root) return null;
|
|
42378
|
-
const base =
|
|
42379
|
-
const target =
|
|
42675
|
+
const base = resolve8(root);
|
|
42676
|
+
const target = resolve8(base, relPath || ".");
|
|
42380
42677
|
return target === base || target.startsWith(base + sep6) ? target : null;
|
|
42381
42678
|
}
|
|
42382
42679
|
var MIME_BY_EXT = {
|
|
@@ -42415,7 +42712,7 @@ async function uploadBinaryFile(root, relPath, contentBase64) {
|
|
|
42415
42712
|
return { ok: false, path: target, bytes: 0, error: `file too large (max ${FS_TRANSFER_MAX_BYTES} bytes)` };
|
|
42416
42713
|
}
|
|
42417
42714
|
try {
|
|
42418
|
-
await mkdir6(
|
|
42715
|
+
await mkdir6(dirname7(target), { recursive: true });
|
|
42419
42716
|
await writeFile7(target, buf);
|
|
42420
42717
|
return { ok: true, path: target, bytes: buf.byteLength };
|
|
42421
42718
|
} catch (err) {
|
|
@@ -42492,13 +42789,13 @@ function handleFsChannels(ctx, message, payload) {
|
|
|
42492
42789
|
// src/core/git-diff.ts
|
|
42493
42790
|
import { readFile as readFile8 } from "fs/promises";
|
|
42494
42791
|
import { execFile as execFile6 } from "child_process";
|
|
42495
|
-
import { basename as basename5, dirname as
|
|
42792
|
+
import { basename as basename5, dirname as dirname8, resolve as resolve9, sep as sep7 } from "path";
|
|
42496
42793
|
import { promisify as promisify6 } from "util";
|
|
42497
42794
|
var run6 = promisify6(execFile6);
|
|
42498
42795
|
function resolveInRoot5(root, relPath) {
|
|
42499
42796
|
if (!root) return null;
|
|
42500
|
-
const base =
|
|
42501
|
-
const target =
|
|
42797
|
+
const base = resolve9(root);
|
|
42798
|
+
const target = resolve9(base, relPath || ".");
|
|
42502
42799
|
return target === base || target.startsWith(base + sep7) ? target : null;
|
|
42503
42800
|
}
|
|
42504
42801
|
function looksBinary(buf) {
|
|
@@ -42513,7 +42810,7 @@ async function gitDiff(root, path) {
|
|
|
42513
42810
|
if (!target) {
|
|
42514
42811
|
return { path, oldContent: "", newContent: "", isBinary: false };
|
|
42515
42812
|
}
|
|
42516
|
-
const dir2 =
|
|
42813
|
+
const dir2 = dirname8(target);
|
|
42517
42814
|
const base = basename5(target);
|
|
42518
42815
|
let newBuf = null;
|
|
42519
42816
|
try {
|
|
@@ -42652,15 +42949,15 @@ async function manageSshKey(op, profileDir2) {
|
|
|
42652
42949
|
|
|
42653
42950
|
// src/core/git-history.ts
|
|
42654
42951
|
import { execFile as execFile9 } from "child_process";
|
|
42655
|
-
import { resolve as
|
|
42952
|
+
import { resolve as resolve10, sep as sep8 } from "path";
|
|
42656
42953
|
import { promisify as promisify9 } from "util";
|
|
42657
42954
|
var run9 = promisify9(execFile9);
|
|
42658
42955
|
var FS = "";
|
|
42659
42956
|
var RS = "";
|
|
42660
42957
|
function resolveRepo(root, repo) {
|
|
42661
42958
|
if (!root) return null;
|
|
42662
|
-
const base =
|
|
42663
|
-
const requested =
|
|
42959
|
+
const base = resolve10(root);
|
|
42960
|
+
const requested = resolve10(base, repo && repo.trim() ? repo : ".");
|
|
42664
42961
|
return requested === base || requested.startsWith(base + sep8) ? requested : base;
|
|
42665
42962
|
}
|
|
42666
42963
|
async function git(cwd2, args) {
|
|
@@ -42677,7 +42974,7 @@ async function git(cwd2, args) {
|
|
|
42677
42974
|
}
|
|
42678
42975
|
async function gitRepos(root) {
|
|
42679
42976
|
if (!root) return { repos: [] };
|
|
42680
|
-
const base =
|
|
42977
|
+
const base = resolve10(root);
|
|
42681
42978
|
const top = (await git(base, ["rev-parse", "--show-toplevel"])).trim();
|
|
42682
42979
|
if (!top || !(top === base || top.startsWith(base + sep8))) return { repos: [] };
|
|
42683
42980
|
const repos = [];
|
|
@@ -42689,7 +42986,7 @@ async function gitRepos(root) {
|
|
|
42689
42986
|
const m2 = line.trim().match(/^[-+U ]?[0-9a-f]{7,40}\s+(\S+)/);
|
|
42690
42987
|
const subPath = m2?.[1];
|
|
42691
42988
|
if (!subPath) continue;
|
|
42692
|
-
const abs =
|
|
42989
|
+
const abs = resolve10(base, subPath);
|
|
42693
42990
|
if (!(abs === base || abs.startsWith(base + sep8))) continue;
|
|
42694
42991
|
const remote = (await git(abs, ["remote", "get-url", "origin"])).trim();
|
|
42695
42992
|
const branch = (await git(abs, ["rev-parse", "--abbrev-ref", "HEAD"])).trim();
|
|
@@ -42918,7 +43215,7 @@ function handleMiscChannels(ctx, message, payload) {
|
|
|
42918
43215
|
}
|
|
42919
43216
|
|
|
42920
43217
|
// src/core/ws-client/handlers/project.ts
|
|
42921
|
-
import { existsSync as
|
|
43218
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync7, renameSync, rmSync as rmSync7 } from "fs";
|
|
42922
43219
|
import { join as join23 } from "path";
|
|
42923
43220
|
function handleProjectChannels(ctx, message, payload) {
|
|
42924
43221
|
switch (message.channel) {
|
|
@@ -42931,11 +43228,11 @@ function handleProjectChannels(ctx, message, payload) {
|
|
|
42931
43228
|
const oldRoot = ctx.physicFolderPath(oldName);
|
|
42932
43229
|
if (oldRoot) void uninstallCron(oldRoot).catch(() => void 0);
|
|
42933
43230
|
}
|
|
42934
|
-
if (oldPath && oldName !== sync.newName &&
|
|
43231
|
+
if (oldPath && oldName !== sync.newName && existsSync14(oldPath) && !existsSync14(newPath)) {
|
|
42935
43232
|
renameSync(oldPath, newPath);
|
|
42936
43233
|
} else {
|
|
42937
|
-
if (oldPath && oldName !== sync.newName &&
|
|
42938
|
-
|
|
43234
|
+
if (oldPath && oldName !== sync.newName && existsSync14(oldPath)) rmSync7(oldPath, { recursive: true, force: true });
|
|
43235
|
+
mkdirSync7(newPath, { recursive: true });
|
|
42939
43236
|
}
|
|
42940
43237
|
ctx.physicRoot = ctx.physicFolderPath(sync.newName);
|
|
42941
43238
|
ctx.bus.setProject(sync.newName);
|
|
@@ -42948,7 +43245,7 @@ function handleProjectChannels(ctx, message, payload) {
|
|
|
42948
43245
|
if (del.name) {
|
|
42949
43246
|
const delRoot = ctx.physicFolderPath(del.name);
|
|
42950
43247
|
if (delRoot) void uninstallCron(delRoot).catch(() => void 0);
|
|
42951
|
-
|
|
43248
|
+
rmSync7(join23(ctx.profileDir, del.name), { recursive: true, force: true });
|
|
42952
43249
|
ctx.bus.log(t("project.folderDeleted", { name: del.name }));
|
|
42953
43250
|
}
|
|
42954
43251
|
void manageSshKey("delete", ctx.profileDir).catch(() => void 0);
|
|
@@ -43012,7 +43309,7 @@ Extra focus from the author: ${req.prompt}` : "";
|
|
|
43012
43309
|
].join("\n");
|
|
43013
43310
|
}
|
|
43014
43311
|
function runOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
|
|
43015
|
-
return new Promise((
|
|
43312
|
+
return new Promise((resolve12, reject) => {
|
|
43016
43313
|
const args = ["-p", ...profile?.model ? ["--model", profile.model] : [], ...denySettingsArgs(cmd, [...denyDirs, profile?.dir])];
|
|
43017
43314
|
const env3 = {
|
|
43018
43315
|
...process.env,
|
|
@@ -43035,7 +43332,7 @@ function runOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
|
|
|
43035
43332
|
});
|
|
43036
43333
|
child.on("close", (code) => {
|
|
43037
43334
|
clearTimeout(timer);
|
|
43038
|
-
|
|
43335
|
+
resolve12({ code: code ?? -1, out, err });
|
|
43039
43336
|
});
|
|
43040
43337
|
child.stdin.write(prompt);
|
|
43041
43338
|
child.stdin.end();
|
|
@@ -43066,7 +43363,7 @@ async function runKnowledgeCompose(req, ai2, physicRoot) {
|
|
|
43066
43363
|
|
|
43067
43364
|
// src/core/faq-compose.ts
|
|
43068
43365
|
import { spawn as spawn9, execFile as execFile10 } from "child_process";
|
|
43069
|
-
import { existsSync as
|
|
43366
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync8, rmSync as rmSync8 } from "fs";
|
|
43070
43367
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
43071
43368
|
import { join as join24 } from "path";
|
|
43072
43369
|
import { tmpdir as tmpdir4 } from "os";
|
|
@@ -43136,7 +43433,7 @@ function parsePerTicket(text) {
|
|
|
43136
43433
|
return [];
|
|
43137
43434
|
}
|
|
43138
43435
|
function runClaudeOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
|
|
43139
|
-
return new Promise((
|
|
43436
|
+
return new Promise((resolve12, reject) => {
|
|
43140
43437
|
const isClaude = cmd.includes("claude");
|
|
43141
43438
|
const args = [
|
|
43142
43439
|
"-p",
|
|
@@ -43169,7 +43466,7 @@ function runClaudeOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
|
|
|
43169
43466
|
child.on("close", (code) => {
|
|
43170
43467
|
clearTimeout(timer);
|
|
43171
43468
|
out += parser.flush();
|
|
43172
|
-
|
|
43469
|
+
resolve12({ code: code ?? -1, out, err, usage: parser.usage() });
|
|
43173
43470
|
});
|
|
43174
43471
|
child.stdin.write(prompt);
|
|
43175
43472
|
child.stdin.end();
|
|
@@ -43193,7 +43490,7 @@ async function runFaqCompose(req, ai2) {
|
|
|
43193
43490
|
const gitEnv = { ...process.env, GIT_TERMINAL_PROMPT: "0" };
|
|
43194
43491
|
const auth = gitAuthArgs("x-access-token", req.repo.token);
|
|
43195
43492
|
try {
|
|
43196
|
-
|
|
43493
|
+
mkdirSync8(workDir, { recursive: true });
|
|
43197
43494
|
await execFileAsync2("git", [...auth, "clone", "--depth", "1", "--branch", req.repo.branch, req.repo.url, workDir], { env: gitEnv });
|
|
43198
43495
|
await execFileAsync2("git", ["-C", workDir, "config", "user.name", "4PM FAQ Bot"], { env: gitEnv });
|
|
43199
43496
|
await execFileAsync2("git", ["-C", workDir, "config", "user.email", "faq-bot@4pm.app"], { env: gitEnv });
|
|
@@ -43233,9 +43530,9 @@ async function runFaqCompose(req, ai2) {
|
|
|
43233
43530
|
logger.warn("faq.compose.failed", { error: String(err) });
|
|
43234
43531
|
return { prUrl: "", error: String(err) };
|
|
43235
43532
|
} finally {
|
|
43236
|
-
if (
|
|
43533
|
+
if (existsSync15(workDir)) {
|
|
43237
43534
|
try {
|
|
43238
|
-
|
|
43535
|
+
rmSync8(workDir, { recursive: true, force: true });
|
|
43239
43536
|
} catch {
|
|
43240
43537
|
}
|
|
43241
43538
|
}
|
|
@@ -43559,7 +43856,7 @@ async function writeSecrets(root, req) {
|
|
|
43559
43856
|
|
|
43560
43857
|
// src/core/agent-tools.ts
|
|
43561
43858
|
import { readFile as readFile12, writeFile as writeFile10, mkdir as mkdir9 } from "fs/promises";
|
|
43562
|
-
import { dirname as
|
|
43859
|
+
import { dirname as dirname9, join as join27 } from "path";
|
|
43563
43860
|
var SHARED_REL = ".claude/settings.json";
|
|
43564
43861
|
var LOCAL_REL = ".claude/settings.local.json";
|
|
43565
43862
|
var SECRETS_DENY = "Read(./project.secrets.json)";
|
|
@@ -43590,7 +43887,7 @@ async function writeAgentTools(root, scope, permissions) {
|
|
|
43590
43887
|
const next = normalizePermissions(permissions);
|
|
43591
43888
|
if (scope === "shared" && !next.deny.includes(SECRETS_DENY)) next.deny = [...next.deny, SECRETS_DENY];
|
|
43592
43889
|
obj.permissions = next;
|
|
43593
|
-
await mkdir9(
|
|
43890
|
+
await mkdir9(dirname9(path), { recursive: true });
|
|
43594
43891
|
await writeFile10(path, JSON.stringify(obj, null, 2) + "\n", "utf8");
|
|
43595
43892
|
return { ok: true };
|
|
43596
43893
|
} catch (err) {
|
|
@@ -43601,7 +43898,7 @@ async function writeAgentTools(root, scope, permissions) {
|
|
|
43601
43898
|
// src/core/packages.ts
|
|
43602
43899
|
import { createHash as createHash4 } from "crypto";
|
|
43603
43900
|
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
|
|
43901
|
+
import { dirname as dirname10, join as join28, relative } from "path";
|
|
43605
43902
|
var AGENTS_REL2 = ".claude/agents";
|
|
43606
43903
|
var SKILLS_REL2 = ".claude/skills";
|
|
43607
43904
|
var MANIFEST_REL = ".claude/.4pm-packages.json";
|
|
@@ -43682,7 +43979,7 @@ async function readManifest(root) {
|
|
|
43682
43979
|
}
|
|
43683
43980
|
async function writeManifest(root, manifest) {
|
|
43684
43981
|
const path = join28(root, MANIFEST_REL);
|
|
43685
|
-
await mkdir10(
|
|
43982
|
+
await mkdir10(dirname10(path), { recursive: true });
|
|
43686
43983
|
await writeFile11(path, JSON.stringify(manifest, null, 2), "utf8");
|
|
43687
43984
|
}
|
|
43688
43985
|
async function writeArtifactFiles(root, kind, slug, files) {
|
|
@@ -43708,7 +44005,7 @@ async function writeArtifactFiles(root, kind, slug, files) {
|
|
|
43708
44005
|
for (const [rel, buf] of byPath) {
|
|
43709
44006
|
if (rel.includes("..") || rel.startsWith("/")) continue;
|
|
43710
44007
|
const dest = join28(base, rel);
|
|
43711
|
-
await mkdir10(
|
|
44008
|
+
await mkdir10(dirname10(dest), { recursive: true });
|
|
43712
44009
|
await writeFile11(dest, buf);
|
|
43713
44010
|
}
|
|
43714
44011
|
}
|
|
@@ -43789,7 +44086,7 @@ async function removePackage(root, slug) {
|
|
|
43789
44086
|
|
|
43790
44087
|
// src/core/graph.ts
|
|
43791
44088
|
import { readFile as readFile14, readdir as readdir6 } from "fs/promises";
|
|
43792
|
-
import { join as join29, relative as relative2, dirname as
|
|
44089
|
+
import { join as join29, relative as relative2, dirname as dirname11, resolve as resolve11, extname as extname2, basename as basename6 } from "path";
|
|
43793
44090
|
var MAX_FILES = 2e3;
|
|
43794
44091
|
var MAX_NODES = 3e3;
|
|
43795
44092
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "out", "coverage", ".turbo"]);
|
|
@@ -43844,7 +44141,7 @@ async function buildDocs(root) {
|
|
|
43844
44141
|
for (const m2 of text.matchAll(/\[[^\]]*\]\(([^)]+)\)/g)) {
|
|
43845
44142
|
const href = (m2[1] ?? "").split("#")[0].trim();
|
|
43846
44143
|
if (!href || !href.endsWith(".md") || href.startsWith("http")) continue;
|
|
43847
|
-
const target = relative2(root,
|
|
44144
|
+
const target = relative2(root, resolve11(dirname11(f2), href)).split("\\").join("/");
|
|
43848
44145
|
if (ids.has(target) && target !== rel) {
|
|
43849
44146
|
const key = `${rel}->${target}`;
|
|
43850
44147
|
if (!seen.has(key)) {
|
|
@@ -43929,7 +44226,7 @@ async function buildGraph(root, mode) {
|
|
|
43929
44226
|
|
|
43930
44227
|
// src/core/rag.ts
|
|
43931
44228
|
import { readFile as readFile15, writeFile as writeFile12, statfs as statfs2, mkdir as mkdir11 } from "fs/promises";
|
|
43932
|
-
import { existsSync as
|
|
44229
|
+
import { existsSync as existsSync16 } from "fs";
|
|
43933
44230
|
import { execFile as execFile11, spawn as spawn10 } from "child_process";
|
|
43934
44231
|
import { totalmem as totalmem2, cpus as cpus2 } from "os";
|
|
43935
44232
|
import { join as join30 } from "path";
|
|
@@ -44022,7 +44319,7 @@ async function ragStatus(root) {
|
|
|
44022
44319
|
const modelPresent = modelName.length > 0;
|
|
44023
44320
|
return {
|
|
44024
44321
|
installed: fastembed && sqliteVec && modelPresent,
|
|
44025
|
-
installing:
|
|
44322
|
+
installing: existsSync16(join30(root, RAG_REL, ".installing")),
|
|
44026
44323
|
python: { found: py.found, version: py.version },
|
|
44027
44324
|
deps: { pip, fastembed, sqliteVec },
|
|
44028
44325
|
model: { present: modelPresent, name: modelName },
|
|
@@ -44033,7 +44330,7 @@ async function ragStatus(root) {
|
|
|
44033
44330
|
};
|
|
44034
44331
|
}
|
|
44035
44332
|
async function readIndexMeta(root) {
|
|
44036
|
-
const indexing =
|
|
44333
|
+
const indexing = existsSync16(join30(root, RAG_REL, ".indexing"));
|
|
44037
44334
|
try {
|
|
44038
44335
|
const m2 = JSON.parse(await readFile15(join30(root, RAG_REL, ".index.json"), "utf8"));
|
|
44039
44336
|
return {
|
|
@@ -44108,8 +44405,8 @@ async function ensureEngine(root) {
|
|
|
44108
44405
|
async function ragReindex(root) {
|
|
44109
44406
|
const py = await detectPython();
|
|
44110
44407
|
if (!py.found) return { ok: false, started: false, error: "python3 not found" };
|
|
44111
|
-
if (!
|
|
44112
|
-
if (
|
|
44408
|
+
if (!existsSync16(join30(root, RAG_REL, ".installed"))) return { ok: false, started: false, error: "RAG is not installed" };
|
|
44409
|
+
if (existsSync16(join30(root, RAG_REL, ".indexing"))) return { ok: true, started: true };
|
|
44113
44410
|
await ensureEngine(root);
|
|
44114
44411
|
const script = `
|
|
44115
44412
|
set +e
|
|
@@ -44129,7 +44426,7 @@ rm -f "${RAG_REL}/.indexing"
|
|
|
44129
44426
|
async function ragQuery(root, queryText, k2 = 8) {
|
|
44130
44427
|
const py = await detectPython();
|
|
44131
44428
|
if (!py.found) return { results: [], error: "python3 not found" };
|
|
44132
|
-
if (!
|
|
44429
|
+
if (!existsSync16(join30(root, RAG_REL, "index.db"))) return { results: [], error: "no index \u2014 reindex first" };
|
|
44133
44430
|
await ensureEngine(root);
|
|
44134
44431
|
try {
|
|
44135
44432
|
const { stdout } = await run10(
|
|
@@ -44149,7 +44446,7 @@ async function ragInstall(root, model) {
|
|
|
44149
44446
|
}
|
|
44150
44447
|
const py = await detectPython();
|
|
44151
44448
|
if (!py.found) return { ok: false, started: false, error: "python3 not found on the worker" };
|
|
44152
|
-
if (
|
|
44449
|
+
if (existsSync16(join30(root, RAG_REL, ".installing"))) return { ok: true, started: true };
|
|
44153
44450
|
try {
|
|
44154
44451
|
await mkdir11(join30(root, RAG_REL), { recursive: true });
|
|
44155
44452
|
} catch {
|
|
@@ -44178,7 +44475,7 @@ rm -f "${RAG_REL}/.installing"
|
|
|
44178
44475
|
}
|
|
44179
44476
|
|
|
44180
44477
|
// src/core/config-sync.ts
|
|
44181
|
-
import { existsSync as
|
|
44478
|
+
import { existsSync as existsSync17, readFileSync as readFileSync16 } from "fs";
|
|
44182
44479
|
import { join as join31 } from "path";
|
|
44183
44480
|
var SERVER_MANAGED_KEYS = [
|
|
44184
44481
|
"physicPath",
|
|
@@ -44197,7 +44494,7 @@ var SERVER_MANAGED_KEYS = [
|
|
|
44197
44494
|
];
|
|
44198
44495
|
function readConfigText(profileDir2) {
|
|
44199
44496
|
const path = join31(profileDir2, "config.json");
|
|
44200
|
-
if (
|
|
44497
|
+
if (existsSync17(path)) {
|
|
44201
44498
|
try {
|
|
44202
44499
|
return readFileSync16(path, "utf8");
|
|
44203
44500
|
} catch {
|
|
@@ -44683,14 +44980,14 @@ var WsClient = class {
|
|
|
44683
44980
|
*/
|
|
44684
44981
|
awaitConnected(timeoutMs) {
|
|
44685
44982
|
if (this.sessionKey && this.bus.status === "connected") return Promise.resolve(true);
|
|
44686
|
-
return new Promise((
|
|
44983
|
+
return new Promise((resolve12) => {
|
|
44687
44984
|
const waiter = () => {
|
|
44688
44985
|
clearTimeout(timer);
|
|
44689
|
-
|
|
44986
|
+
resolve12(true);
|
|
44690
44987
|
};
|
|
44691
44988
|
const timer = setTimeout(() => {
|
|
44692
44989
|
this.connectWaiters = this.connectWaiters.filter((w2) => w2 !== waiter);
|
|
44693
|
-
|
|
44990
|
+
resolve12(false);
|
|
44694
44991
|
}, timeoutMs);
|
|
44695
44992
|
this.connectWaiters.push(waiter);
|
|
44696
44993
|
});
|
|
@@ -44717,16 +45014,16 @@ var WsClient = class {
|
|
|
44717
45014
|
this.forceReconnect = false;
|
|
44718
45015
|
return Promise.resolve();
|
|
44719
45016
|
}
|
|
44720
|
-
return new Promise((
|
|
45017
|
+
return new Promise((resolve12) => {
|
|
44721
45018
|
const timer = setTimeout(() => {
|
|
44722
45019
|
this.wakeReconnect = null;
|
|
44723
|
-
|
|
45020
|
+
resolve12();
|
|
44724
45021
|
}, ms2);
|
|
44725
45022
|
this.wakeReconnect = () => {
|
|
44726
45023
|
clearTimeout(timer);
|
|
44727
45024
|
this.wakeReconnect = null;
|
|
44728
45025
|
this.forceReconnect = false;
|
|
44729
|
-
|
|
45026
|
+
resolve12();
|
|
44730
45027
|
};
|
|
44731
45028
|
});
|
|
44732
45029
|
}
|
|
@@ -44780,7 +45077,7 @@ var WsClient = class {
|
|
|
44780
45077
|
* One WS session: token → connect → handshake → handle messages until close.
|
|
44781
45078
|
*/
|
|
44782
45079
|
connectOnce() {
|
|
44783
|
-
return new Promise((
|
|
45080
|
+
return new Promise((resolve12, reject) => {
|
|
44784
45081
|
void (async () => {
|
|
44785
45082
|
this.sessionEstablished = false;
|
|
44786
45083
|
this.bus.setStatus("connecting");
|
|
@@ -44822,7 +45119,12 @@ var WsClient = class {
|
|
|
44822
45119
|
if (token.aiMemory && !this.aiMemory) this.aiMemory = token.aiMemory.text;
|
|
44823
45120
|
this.outboundReviewEnabled = token.outboundReview?.enabled === true;
|
|
44824
45121
|
this.restrictToFolder = token.aiRestrictToFolder === true;
|
|
44825
|
-
|
|
45122
|
+
configureGitAuth(
|
|
45123
|
+
token.gitAuth ?? null,
|
|
45124
|
+
token.gitAuthHost ?? null,
|
|
45125
|
+
this.context.profileDir,
|
|
45126
|
+
(req) => this.request(WsChannels.GIT_TOKEN, req)
|
|
45127
|
+
);
|
|
44826
45128
|
this.machineUsername = token.machineUsername ?? "";
|
|
44827
45129
|
this.servingRepos = token.repos ?? [];
|
|
44828
45130
|
void this.ensureServingReposCloned();
|
|
@@ -44876,7 +45178,7 @@ var WsClient = class {
|
|
|
44876
45178
|
if (code === CLOSE_DRAINING) {
|
|
44877
45179
|
this.bus.log(t("error.serverDraining"));
|
|
44878
45180
|
}
|
|
44879
|
-
|
|
45181
|
+
resolve12();
|
|
44880
45182
|
});
|
|
44881
45183
|
socket.on("error", (err) => {
|
|
44882
45184
|
this.clearHeartbeat();
|
|
@@ -45002,7 +45304,7 @@ var WsClient = class {
|
|
|
45002
45304
|
* Used for quota.check before spawning an AI cli (ADR-0020).
|
|
45003
45305
|
*/
|
|
45004
45306
|
request(channel, data) {
|
|
45005
|
-
return new Promise((
|
|
45307
|
+
return new Promise((resolve12, reject) => {
|
|
45006
45308
|
if (!this.socket || !this.sessionKey) {
|
|
45007
45309
|
reject(new Error("Not connected to the server."));
|
|
45008
45310
|
return;
|
|
@@ -45022,7 +45324,7 @@ var WsClient = class {
|
|
|
45022
45324
|
}, 15e3);
|
|
45023
45325
|
timer.unref?.();
|
|
45024
45326
|
this.pending.set(envelope.id, {
|
|
45025
|
-
resolve: (value) =>
|
|
45327
|
+
resolve: (value) => resolve12(value),
|
|
45026
45328
|
timer
|
|
45027
45329
|
});
|
|
45028
45330
|
this.socket.send(JSON.stringify(envelope));
|
|
@@ -45076,8 +45378,8 @@ var WsClient = class {
|
|
|
45076
45378
|
/** Create the physic folder at an absolute path if missing (best-effort). */
|
|
45077
45379
|
ensurePhysicFolderPath(folder) {
|
|
45078
45380
|
try {
|
|
45079
|
-
if (!
|
|
45080
|
-
|
|
45381
|
+
if (!existsSync18(folder)) {
|
|
45382
|
+
mkdirSync9(folder, { recursive: true });
|
|
45081
45383
|
this.bus.log(t("project.createdFolder", { folder }));
|
|
45082
45384
|
}
|
|
45083
45385
|
} catch (err) {
|
|
@@ -45106,7 +45408,7 @@ var WsClient = class {
|
|
|
45106
45408
|
projects: [],
|
|
45107
45409
|
version: CLI_VERSION,
|
|
45108
45410
|
physicPath,
|
|
45109
|
-
physicPathExists: physicPath ?
|
|
45411
|
+
physicPathExists: physicPath ? existsSync18(physicPath) : void 0,
|
|
45110
45412
|
// Real coarse state (ADR-0152): !paused and a recent tick (was hard-coded false).
|
|
45111
45413
|
autonomousRunning: physicPath ? isAutonomousRunning(physicPath, this.context.profileDir) : false,
|
|
45112
45414
|
// The machine-user AI-run limit (0 = unlimited) + profile count so a dispatch can resolve the
|
|
@@ -45365,7 +45667,7 @@ var WsClient = class {
|
|
|
45365
45667
|
};
|
|
45366
45668
|
|
|
45367
45669
|
// src/core/instance-lock.ts
|
|
45368
|
-
import { readFileSync as readFileSync17, rmSync as
|
|
45670
|
+
import { readFileSync as readFileSync17, rmSync as rmSync9, writeFileSync as writeFileSync9 } from "fs";
|
|
45369
45671
|
import { join as join33 } from "path";
|
|
45370
45672
|
function lockPath(profileDir2) {
|
|
45371
45673
|
return join33(profileDir2, "start.lock");
|
|
@@ -45394,7 +45696,7 @@ function acquireInstanceLock(profileDir2) {
|
|
|
45394
45696
|
function releaseInstanceLock(profileDir2) {
|
|
45395
45697
|
try {
|
|
45396
45698
|
const pid = Number.parseInt(readFileSync17(lockPath(profileDir2), "utf8").trim(), 10);
|
|
45397
|
-
if (pid === process.pid)
|
|
45699
|
+
if (pid === process.pid) rmSync9(lockPath(profileDir2), { force: true });
|
|
45398
45700
|
} catch {
|
|
45399
45701
|
}
|
|
45400
45702
|
}
|
|
@@ -45689,7 +45991,7 @@ var SessionBus = class {
|
|
|
45689
45991
|
|
|
45690
45992
|
// src/core/control-server.ts
|
|
45691
45993
|
import { createServer } from "net";
|
|
45692
|
-
import { chmodSync as
|
|
45994
|
+
import { chmodSync as chmodSync5, rmSync as rmSync10 } from "fs";
|
|
45693
45995
|
import { join as join35 } from "path";
|
|
45694
45996
|
|
|
45695
45997
|
// src/core/control-protocol.ts
|
|
@@ -45719,7 +46021,7 @@ function createFrameParser() {
|
|
|
45719
46021
|
}
|
|
45720
46022
|
|
|
45721
46023
|
// src/core/control-token.ts
|
|
45722
|
-
import { chmodSync as
|
|
46024
|
+
import { chmodSync as chmodSync4, readFileSync as readFileSync18, writeFileSync as writeFileSync10 } from "fs";
|
|
45723
46025
|
import { randomBytes as randomBytes3, timingSafeEqual } from "crypto";
|
|
45724
46026
|
import { join as join34 } from "path";
|
|
45725
46027
|
function controlTokenPath(profileDir2) {
|
|
@@ -45730,7 +46032,7 @@ function writeControlToken(profileDir2) {
|
|
|
45730
46032
|
const path = controlTokenPath(profileDir2);
|
|
45731
46033
|
writeFileSync10(path, token, { mode: 384 });
|
|
45732
46034
|
try {
|
|
45733
|
-
|
|
46035
|
+
chmodSync4(path, 384);
|
|
45734
46036
|
} catch {
|
|
45735
46037
|
}
|
|
45736
46038
|
return token;
|
|
@@ -45754,7 +46056,7 @@ function tokenMatches(expected, given) {
|
|
|
45754
46056
|
function startControlServer(bus, profileDir2, info) {
|
|
45755
46057
|
const socketPath = join35(profileDir2, CONTROL_SOCKET_FILE);
|
|
45756
46058
|
try {
|
|
45757
|
-
|
|
46059
|
+
rmSync10(socketPath, { force: true });
|
|
45758
46060
|
} catch {
|
|
45759
46061
|
}
|
|
45760
46062
|
const clients = /* @__PURE__ */ new Set();
|
|
@@ -45807,8 +46109,10 @@ function startControlServer(bus, profileDir2, info) {
|
|
|
45807
46109
|
if (!authed) {
|
|
45808
46110
|
if (frame.t === "auth" && tokenMatches(token, frame.token)) {
|
|
45809
46111
|
authed = true;
|
|
45810
|
-
|
|
45811
|
-
|
|
46112
|
+
if (!frame.rpc) {
|
|
46113
|
+
clients.add(sock);
|
|
46114
|
+
sendSnapshot(sock);
|
|
46115
|
+
}
|
|
45812
46116
|
} else {
|
|
45813
46117
|
send(sock, { t: "authError" });
|
|
45814
46118
|
sock.destroy();
|
|
@@ -45818,6 +46122,9 @@ function startControlServer(bus, profileDir2, info) {
|
|
|
45818
46122
|
if (frame.t === "submit") bus.submitLocal(frame.input);
|
|
45819
46123
|
else if (frame.t === "reconnect") bus.requestReconnect();
|
|
45820
46124
|
else if (frame.t === "autonomousRun") bus.submitAutonomous();
|
|
46125
|
+
else if (frame.t === "gitToken") {
|
|
46126
|
+
void issueGitToken(frame.scope, frame.host, frame.path).then((tok) => send(sock, { t: "gitToken", token: tok }));
|
|
46127
|
+
}
|
|
45821
46128
|
}
|
|
45822
46129
|
});
|
|
45823
46130
|
const drop = () => {
|
|
@@ -45830,7 +46137,7 @@ function startControlServer(bus, profileDir2, info) {
|
|
|
45830
46137
|
});
|
|
45831
46138
|
server.listen(socketPath, () => {
|
|
45832
46139
|
try {
|
|
45833
|
-
|
|
46140
|
+
chmodSync5(socketPath, 384);
|
|
45834
46141
|
} catch {
|
|
45835
46142
|
}
|
|
45836
46143
|
});
|
|
@@ -45840,8 +46147,8 @@ function startControlServer(bus, profileDir2, info) {
|
|
|
45840
46147
|
clients.clear();
|
|
45841
46148
|
server.close();
|
|
45842
46149
|
try {
|
|
45843
|
-
|
|
45844
|
-
|
|
46150
|
+
rmSync10(socketPath, { force: true });
|
|
46151
|
+
rmSync10(controlTokenPath(profileDir2), { force: true });
|
|
45845
46152
|
} catch {
|
|
45846
46153
|
}
|
|
45847
46154
|
};
|
|
@@ -50923,24 +51230,24 @@ var App = class extends import_react9.PureComponent {
|
|
|
50923
51230
|
})
|
|
50924
51231
|
}));
|
|
50925
51232
|
};
|
|
50926
|
-
findNextFocusable = (
|
|
50927
|
-
const activeIndex =
|
|
50928
|
-
return focusable.id ===
|
|
51233
|
+
findNextFocusable = (state2) => {
|
|
51234
|
+
const activeIndex = state2.focusables.findIndex((focusable) => {
|
|
51235
|
+
return focusable.id === state2.activeFocusId;
|
|
50929
51236
|
});
|
|
50930
|
-
for (let index = activeIndex + 1; index <
|
|
50931
|
-
const focusable =
|
|
51237
|
+
for (let index = activeIndex + 1; index < state2.focusables.length; index++) {
|
|
51238
|
+
const focusable = state2.focusables[index];
|
|
50932
51239
|
if (focusable?.isActive) {
|
|
50933
51240
|
return focusable.id;
|
|
50934
51241
|
}
|
|
50935
51242
|
}
|
|
50936
51243
|
return void 0;
|
|
50937
51244
|
};
|
|
50938
|
-
findPreviousFocusable = (
|
|
50939
|
-
const activeIndex =
|
|
50940
|
-
return focusable.id ===
|
|
51245
|
+
findPreviousFocusable = (state2) => {
|
|
51246
|
+
const activeIndex = state2.focusables.findIndex((focusable) => {
|
|
51247
|
+
return focusable.id === state2.activeFocusId;
|
|
50941
51248
|
});
|
|
50942
51249
|
for (let index = activeIndex - 1; index >= 0; index--) {
|
|
50943
|
-
const focusable =
|
|
51250
|
+
const focusable = state2.focusables[index];
|
|
50944
51251
|
if (focusable?.isActive) {
|
|
50945
51252
|
return focusable.id;
|
|
50946
51253
|
}
|
|
@@ -51136,8 +51443,8 @@ var Ink = class {
|
|
|
51136
51443
|
}
|
|
51137
51444
|
}
|
|
51138
51445
|
async waitUntilExit() {
|
|
51139
|
-
this.exitPromise ||= new Promise((
|
|
51140
|
-
this.resolveExitPromise =
|
|
51446
|
+
this.exitPromise ||= new Promise((resolve12, reject) => {
|
|
51447
|
+
this.resolveExitPromise = resolve12;
|
|
51141
51448
|
this.rejectExitPromise = reject;
|
|
51142
51449
|
});
|
|
51143
51450
|
return this.exitPromise;
|
|
@@ -51504,7 +51811,7 @@ var import_react21 = __toESM(require_react(), 1);
|
|
|
51504
51811
|
var import_react26 = __toESM(require_react(), 1);
|
|
51505
51812
|
|
|
51506
51813
|
// src/core/input-history.ts
|
|
51507
|
-
import { existsSync as
|
|
51814
|
+
import { existsSync as existsSync20, readFileSync as readFileSync20, writeFileSync as writeFileSync11 } from "fs";
|
|
51508
51815
|
import { join as join36 } from "path";
|
|
51509
51816
|
var MAX_HISTORY = 200;
|
|
51510
51817
|
function historyPath(profileDir2) {
|
|
@@ -51512,7 +51819,7 @@ function historyPath(profileDir2) {
|
|
|
51512
51819
|
}
|
|
51513
51820
|
function loadInputHistory(profileDir2) {
|
|
51514
51821
|
const path = historyPath(profileDir2);
|
|
51515
|
-
if (!
|
|
51822
|
+
if (!existsSync20(path)) return [];
|
|
51516
51823
|
try {
|
|
51517
51824
|
const data = JSON.parse(readFileSync20(path, "utf8"));
|
|
51518
51825
|
return Array.isArray(data) ? data.filter((x) => typeof x === "string") : [];
|
|
@@ -52190,7 +52497,7 @@ async function runStart(profileDir2, profileName) {
|
|
|
52190
52497
|
logger.error("ws.run.crash", { message: e.message, stack: e.stack });
|
|
52191
52498
|
if (client.isStopped) return;
|
|
52192
52499
|
bus.log(t("start.recovered", { message: e.message }), "error");
|
|
52193
|
-
await new Promise((
|
|
52500
|
+
await new Promise((resolve12) => setTimeout(resolve12, 1e3));
|
|
52194
52501
|
}
|
|
52195
52502
|
}
|
|
52196
52503
|
};
|
|
@@ -52225,6 +52532,7 @@ async function runStart(profileDir2, profileName) {
|
|
|
52225
52532
|
}
|
|
52226
52533
|
} finally {
|
|
52227
52534
|
stopIdleClear?.();
|
|
52535
|
+
await revokeAll().catch(() => void 0);
|
|
52228
52536
|
stopControl?.();
|
|
52229
52537
|
release();
|
|
52230
52538
|
}
|
|
@@ -52236,7 +52544,7 @@ import { join as join38 } from "path";
|
|
|
52236
52544
|
// src/core/control-client.ts
|
|
52237
52545
|
import { connect as connect2 } from "net";
|
|
52238
52546
|
function connectControl(socketPath, bus, token) {
|
|
52239
|
-
return new Promise((
|
|
52547
|
+
return new Promise((resolve12, reject) => {
|
|
52240
52548
|
const socket = connect2(socketPath);
|
|
52241
52549
|
socket.setEncoding("utf8");
|
|
52242
52550
|
let ready = false;
|
|
@@ -52289,7 +52597,7 @@ function connectControl(socketPath, bus, token) {
|
|
|
52289
52597
|
bus.setTokens(frame.tokens);
|
|
52290
52598
|
for (const entry of frame.transcript) bus.pushEntry(entry);
|
|
52291
52599
|
ready = true;
|
|
52292
|
-
|
|
52600
|
+
resolve12({ info: frame.info, close });
|
|
52293
52601
|
break;
|
|
52294
52602
|
case "transcript":
|
|
52295
52603
|
bus.pushEntry(frame.entry);
|
|
@@ -52377,7 +52685,7 @@ async function runAutoRun(profileDir2, profileName) {
|
|
|
52377
52685
|
initI18n(readProfileConfig(profileDir2).locale);
|
|
52378
52686
|
const socketPath = join39(profileDir2, CONTROL_SOCKET_FILE);
|
|
52379
52687
|
const token = readControlToken(profileDir2);
|
|
52380
|
-
const code = await new Promise((
|
|
52688
|
+
const code = await new Promise((resolve12) => {
|
|
52381
52689
|
let settled = false;
|
|
52382
52690
|
const done = (c) => {
|
|
52383
52691
|
if (settled) return;
|
|
@@ -52387,7 +52695,7 @@ async function runAutoRun(profileDir2, profileName) {
|
|
|
52387
52695
|
socket.destroy();
|
|
52388
52696
|
} catch {
|
|
52389
52697
|
}
|
|
52390
|
-
|
|
52698
|
+
resolve12(c);
|
|
52391
52699
|
};
|
|
52392
52700
|
const socket = connect3(socketPath);
|
|
52393
52701
|
socket.setEncoding("utf8");
|
|
@@ -52430,21 +52738,91 @@ async function runAutoRun(profileDir2, profileName) {
|
|
|
52430
52738
|
process.exitCode = code;
|
|
52431
52739
|
}
|
|
52432
52740
|
|
|
52741
|
+
// src/commands/git-credential.ts
|
|
52742
|
+
import { connect as connect4 } from "net";
|
|
52743
|
+
import { join as join40 } from "path";
|
|
52744
|
+
var REQUEST_TIMEOUT_MS = 2e4;
|
|
52745
|
+
function requestToken(host, path) {
|
|
52746
|
+
const profileDir2 = process.env[PROFILE_DIR_ENV];
|
|
52747
|
+
if (!profileDir2) return Promise.resolve(null);
|
|
52748
|
+
const token = readControlToken(profileDir2);
|
|
52749
|
+
return new Promise((resolve12) => {
|
|
52750
|
+
let done = false;
|
|
52751
|
+
const finish = (value) => {
|
|
52752
|
+
if (done) return;
|
|
52753
|
+
done = true;
|
|
52754
|
+
clearTimeout(timer);
|
|
52755
|
+
socket.destroy();
|
|
52756
|
+
resolve12(value);
|
|
52757
|
+
};
|
|
52758
|
+
const socket = connect4(join40(profileDir2, CONTROL_SOCKET_FILE));
|
|
52759
|
+
socket.setEncoding("utf8");
|
|
52760
|
+
const timer = setTimeout(() => finish(null), REQUEST_TIMEOUT_MS);
|
|
52761
|
+
socket.on("connect", () => {
|
|
52762
|
+
try {
|
|
52763
|
+
socket.write(encodeFrame({ t: "auth", token: token ?? "", rpc: true }));
|
|
52764
|
+
socket.write(encodeFrame({ t: "gitToken", scope: process.env[JOB_ID_ENV] ?? "", host, path }));
|
|
52765
|
+
} catch {
|
|
52766
|
+
finish(null);
|
|
52767
|
+
}
|
|
52768
|
+
});
|
|
52769
|
+
const parse = createFrameParser();
|
|
52770
|
+
socket.on("data", (chunk) => {
|
|
52771
|
+
for (const frame of parse(chunk)) {
|
|
52772
|
+
if (frame.t === "gitToken") finish(frame.token);
|
|
52773
|
+
else if (frame.t === "authError") finish(null);
|
|
52774
|
+
}
|
|
52775
|
+
});
|
|
52776
|
+
socket.on("error", () => finish(null));
|
|
52777
|
+
socket.on("close", () => finish(null));
|
|
52778
|
+
});
|
|
52779
|
+
}
|
|
52780
|
+
async function readCredentialInput() {
|
|
52781
|
+
const chunks = [];
|
|
52782
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
52783
|
+
const out = {};
|
|
52784
|
+
for (const line of Buffer.concat(chunks).toString("utf8").split("\n")) {
|
|
52785
|
+
const i = line.indexOf("=");
|
|
52786
|
+
if (i > 0) out[line.slice(0, i)] = line.slice(i + 1).trim();
|
|
52787
|
+
}
|
|
52788
|
+
return out;
|
|
52789
|
+
}
|
|
52790
|
+
async function runGitCredential(action) {
|
|
52791
|
+
if (action !== "get") {
|
|
52792
|
+
await readCredentialInput().catch(() => void 0);
|
|
52793
|
+
return;
|
|
52794
|
+
}
|
|
52795
|
+
const input = await readCredentialInput();
|
|
52796
|
+
const host = (input.host ?? "").toLowerCase();
|
|
52797
|
+
if (input.protocol !== "https" || !host || host !== (process.env[GIT_HOST_ENV] ?? "").toLowerCase()) return;
|
|
52798
|
+
const token = await requestToken(host, input.path ?? "");
|
|
52799
|
+
if (token) process.stdout.write(`username=x-access-token
|
|
52800
|
+
password=${token}
|
|
52801
|
+
`);
|
|
52802
|
+
}
|
|
52803
|
+
async function runGitToken() {
|
|
52804
|
+
const host = process.env[GIT_HOST_ENV] ?? "";
|
|
52805
|
+
if (!host) return;
|
|
52806
|
+
const token = await requestToken(host, "");
|
|
52807
|
+
if (token) process.stdout.write(`${token}
|
|
52808
|
+
`);
|
|
52809
|
+
}
|
|
52810
|
+
|
|
52433
52811
|
// src/commands/ai-login.ts
|
|
52434
52812
|
import { spawn as spawn12 } from "child_process";
|
|
52435
|
-
import { mkdirSync as
|
|
52813
|
+
import { mkdirSync as mkdirSync10 } from "fs";
|
|
52436
52814
|
import { basename as basename8 } from "path";
|
|
52437
52815
|
function runProviderLogin(cmd, envVar, dir2) {
|
|
52438
|
-
return new Promise((
|
|
52816
|
+
return new Promise((resolve12) => {
|
|
52439
52817
|
const child = spawn12(cmd, [], {
|
|
52440
52818
|
stdio: "inherit",
|
|
52441
52819
|
env: { ...process.env, [envVar]: dir2 }
|
|
52442
52820
|
});
|
|
52443
52821
|
child.on("error", (err) => {
|
|
52444
52822
|
console.error(t("aiLogin.launchFailed", { cmd, message: err.message }));
|
|
52445
|
-
|
|
52823
|
+
resolve12(1);
|
|
52446
52824
|
});
|
|
52447
|
-
child.on("exit", (code) =>
|
|
52825
|
+
child.on("exit", (code) => resolve12(code ?? 0));
|
|
52448
52826
|
});
|
|
52449
52827
|
}
|
|
52450
52828
|
async function runAiLogin(profileDir2, profileName, aiFilter) {
|
|
@@ -52469,7 +52847,7 @@ async function runAiLogin(profileDir2, profileName, aiFilter) {
|
|
|
52469
52847
|
continue;
|
|
52470
52848
|
}
|
|
52471
52849
|
const dir2 = resolveHomePath(cred.profile);
|
|
52472
|
-
|
|
52850
|
+
mkdirSync10(dir2, { recursive: true });
|
|
52473
52851
|
console.log(t("aiLogin.loggingIn", { provider: cred.provider, profile: cred.profile, envVar, dir: dir2 }));
|
|
52474
52852
|
console.log(t("aiLogin.completeSignIn"));
|
|
52475
52853
|
const code = await runProviderLogin(cred.provider, envVar, dir2);
|
|
@@ -52481,7 +52859,7 @@ async function runAiLogin(profileDir2, profileName, aiFilter) {
|
|
|
52481
52859
|
}
|
|
52482
52860
|
|
|
52483
52861
|
// src/commands/unlink.ts
|
|
52484
|
-
import { rmSync as
|
|
52862
|
+
import { rmSync as rmSync11 } from "fs";
|
|
52485
52863
|
async function runUnlink(profileDir2, profileName) {
|
|
52486
52864
|
initI18n(readProfileConfig(profileDir2).locale);
|
|
52487
52865
|
const cred = readCredential(profileDir2, profileName);
|
|
@@ -52495,7 +52873,7 @@ async function runUnlink(profileDir2, profileName) {
|
|
|
52495
52873
|
} catch (err) {
|
|
52496
52874
|
console.log(t("unlink.revokeFailed", { message: err.message }));
|
|
52497
52875
|
}
|
|
52498
|
-
|
|
52876
|
+
rmSync11(profileDir2, { recursive: true, force: true });
|
|
52499
52877
|
clearDefaultProfileIf(profileName);
|
|
52500
52878
|
console.log(t("unlink.deleted"));
|
|
52501
52879
|
}
|
|
@@ -52584,10 +52962,10 @@ function Picker({
|
|
|
52584
52962
|
] });
|
|
52585
52963
|
}
|
|
52586
52964
|
function selectFromList(items, title) {
|
|
52587
|
-
return new Promise((
|
|
52965
|
+
return new Promise((resolve12) => {
|
|
52588
52966
|
let chosen = null;
|
|
52589
52967
|
const app = render_default(/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Picker, { items, title, onDone: (v2) => chosen = v2 }));
|
|
52590
|
-
void app.waitUntilExit().then(() =>
|
|
52968
|
+
void app.waitUntilExit().then(() => resolve12(chosen));
|
|
52591
52969
|
});
|
|
52592
52970
|
}
|
|
52593
52971
|
|
|
@@ -52654,6 +53032,14 @@ async function main() {
|
|
|
52654
53032
|
await runAttach(profileDir(name), name);
|
|
52655
53033
|
break;
|
|
52656
53034
|
}
|
|
53035
|
+
case "git-credential": {
|
|
53036
|
+
await runGitCredential(rest[0]);
|
|
53037
|
+
break;
|
|
53038
|
+
}
|
|
53039
|
+
case "git-token": {
|
|
53040
|
+
await runGitToken();
|
|
53041
|
+
break;
|
|
53042
|
+
}
|
|
52657
53043
|
case "auto-run": {
|
|
52658
53044
|
const name = await pickLinkedProfile(explicitProfile, "auto-run");
|
|
52659
53045
|
if (!name) {
|