@4pm/cli 1.22.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 +618 -217
- 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
|
}
|
|
@@ -39291,13 +39334,25 @@ function claudeHomeDirs(config) {
|
|
|
39291
39334
|
return dirs.length > 0 ? dirs : [join9(homedir3(), ".claude")];
|
|
39292
39335
|
}
|
|
39293
39336
|
function profileDisplayLabel(dir2) {
|
|
39337
|
+
return profileAccountEmail(dir2) ?? basename(dir2);
|
|
39338
|
+
}
|
|
39339
|
+
function profileAccountEmail(dir2) {
|
|
39294
39340
|
try {
|
|
39295
39341
|
const raw = JSON.parse(readFileSync8(join9(dir2, ".claude.json"), "utf8"));
|
|
39296
|
-
const email = raw.oauthAccount?.emailAddress;
|
|
39297
|
-
|
|
39342
|
+
const email = raw.oauthAccount?.emailAddress?.trim();
|
|
39343
|
+
return email ? email : null;
|
|
39298
39344
|
} catch {
|
|
39345
|
+
return null;
|
|
39299
39346
|
}
|
|
39300
|
-
|
|
39347
|
+
}
|
|
39348
|
+
function aiAccountLabels(config) {
|
|
39349
|
+
const labels = isUnifiedConfig(config) ? config.aiProfiles.filter(isUsableCredential).map((c) => {
|
|
39350
|
+
const dir2 = resolveHomePath(c.profile);
|
|
39351
|
+
return profileAccountEmail(dir2) ?? (c.label?.trim() || basename(dir2));
|
|
39352
|
+
}) : [...profileDirs(config.claudeHome), ...profileDirs(config.codexHome), ...profileDirs(config.antigravityHome)].map(
|
|
39353
|
+
(dir2) => profileDisplayLabel(dir2)
|
|
39354
|
+
);
|
|
39355
|
+
return [...new Set(labels)];
|
|
39301
39356
|
}
|
|
39302
39357
|
function profileEnvVar(cmd) {
|
|
39303
39358
|
if (cmd.includes("claude")) return "CLAUDE_CONFIG_DIR";
|
|
@@ -39426,16 +39481,16 @@ async function fetchUsage(token) {
|
|
|
39426
39481
|
}
|
|
39427
39482
|
}
|
|
39428
39483
|
function runUsageCli(dir2, aiCli2) {
|
|
39429
|
-
return new Promise((
|
|
39484
|
+
return new Promise((resolve12) => {
|
|
39430
39485
|
let out = "";
|
|
39431
39486
|
let child = null;
|
|
39432
39487
|
const finish = () => {
|
|
39433
39488
|
clearTimeout(timer);
|
|
39434
|
-
|
|
39489
|
+
resolve12(out);
|
|
39435
39490
|
};
|
|
39436
39491
|
const timer = setTimeout(() => {
|
|
39437
39492
|
child?.kill();
|
|
39438
|
-
|
|
39493
|
+
resolve12(out);
|
|
39439
39494
|
}, 3e4);
|
|
39440
39495
|
timer.unref?.();
|
|
39441
39496
|
try {
|
|
@@ -39925,6 +39980,7 @@ var NO_USAGE = { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0
|
|
|
39925
39980
|
var execFileAsync = promisify3(execFile3);
|
|
39926
39981
|
var MAX_CONTEXT_CHARS = 12e4;
|
|
39927
39982
|
var ANSWER_TIMEOUT_MS = 12e4;
|
|
39983
|
+
var LEGAL_DRAFT_TIMEOUT_MS = 54e4;
|
|
39928
39984
|
var DOC_EXTENSIONS = [".md", ".mdx", ".txt"];
|
|
39929
39985
|
function kbRoot(profileDir2) {
|
|
39930
39986
|
return join14(profileDir2, "support-kb");
|
|
@@ -40036,6 +40092,7 @@ function buildPrompt(docs, question, askerRole) {
|
|
|
40036
40092
|
].join("\n");
|
|
40037
40093
|
}
|
|
40038
40094
|
function buildDraftPrompt(docs, context, task) {
|
|
40095
|
+
if (task === "legal_draft") return buildLegalDraftPrompt(docs, context);
|
|
40039
40096
|
const goal = task === "reply_draft" ? [
|
|
40040
40097
|
"You are drafting a reply FROM the 4PM support team TO the customer in the support ticket below.",
|
|
40041
40098
|
"Address the customer's latest message, using the whole conversation for context. Be accurate,",
|
|
@@ -40062,6 +40119,31 @@ function buildDraftPrompt(docs, context, task) {
|
|
|
40062
40119
|
context
|
|
40063
40120
|
].join("\n");
|
|
40064
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
|
+
}
|
|
40065
40147
|
function imageExt(mime) {
|
|
40066
40148
|
switch (mime) {
|
|
40067
40149
|
case "image/png":
|
|
@@ -40103,8 +40185,8 @@ function parseModeratedAnswer(text) {
|
|
|
40103
40185
|
}
|
|
40104
40186
|
return { body: text.trim() };
|
|
40105
40187
|
}
|
|
40106
|
-
function runClaudeOnce(cmd, profile, prompt, extraEnv, imageDir, denyDirs = []) {
|
|
40107
|
-
return new Promise((
|
|
40188
|
+
function runClaudeOnce(cmd, profile, prompt, extraEnv, imageDir, denyDirs = [], timeoutMs = ANSWER_TIMEOUT_MS) {
|
|
40189
|
+
return new Promise((resolve12, reject) => {
|
|
40108
40190
|
const isClaude = cmd.includes("claude");
|
|
40109
40191
|
const args = [
|
|
40110
40192
|
"-p",
|
|
@@ -40135,7 +40217,7 @@ function runClaudeOnce(cmd, profile, prompt, extraEnv, imageDir, denyDirs = [])
|
|
|
40135
40217
|
const timer = setTimeout(() => {
|
|
40136
40218
|
child.kill("SIGKILL");
|
|
40137
40219
|
reject(new Error("claude answer timed out"));
|
|
40138
|
-
},
|
|
40220
|
+
}, timeoutMs);
|
|
40139
40221
|
timer.unref();
|
|
40140
40222
|
child.stdout.on("data", (d) => out += parser.push(d.toString()));
|
|
40141
40223
|
child.stderr.on("data", (d) => err += d.toString());
|
|
@@ -40148,17 +40230,25 @@ function runClaudeOnce(cmd, profile, prompt, extraEnv, imageDir, denyDirs = [])
|
|
|
40148
40230
|
clearTimeout(timer);
|
|
40149
40231
|
cleanup();
|
|
40150
40232
|
out += parser.flush();
|
|
40151
|
-
|
|
40233
|
+
resolve12({ code: code ?? -1, out, err, usage: parser.usage() });
|
|
40152
40234
|
});
|
|
40153
40235
|
child.stdin.write(prompt);
|
|
40154
40236
|
child.stdin.end();
|
|
40155
40237
|
});
|
|
40156
40238
|
}
|
|
40157
|
-
async function runClaudeWithFailover(prompt, ai2, imageDir) {
|
|
40239
|
+
async function runClaudeWithFailover(prompt, ai2, imageDir, timeoutMs = ANSWER_TIMEOUT_MS) {
|
|
40158
40240
|
const attempts = ai2.profiles.length > 0 ? ai2.profiles : [null];
|
|
40159
40241
|
let lastReason = "no attempt";
|
|
40160
40242
|
for (let i = 0; i < attempts.length; i++) {
|
|
40161
|
-
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
|
+
);
|
|
40162
40252
|
if (code === 0 && out.trim()) return { text: out.trim(), usage };
|
|
40163
40253
|
const combined = err || out;
|
|
40164
40254
|
lastReason = `exited ${code}: ${combined.slice(0, 500)}`;
|
|
@@ -40179,7 +40269,8 @@ async function runSupportAnswer(req, ai2, profileDir2) {
|
|
|
40179
40269
|
const docs = collectDocs(dir2);
|
|
40180
40270
|
if (!docs.trim()) return { body: "", error: "KB repo has no documentation" };
|
|
40181
40271
|
const prompt = req.task ? buildDraftPrompt(docs, question, req.task) : buildPrompt(docs, question, req.askerRole);
|
|
40182
|
-
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);
|
|
40183
40274
|
if (!text) return { body: "", error: "empty answer" };
|
|
40184
40275
|
const { body, moderation } = req.task ? { body: text.trim(), moderation: void 0 } : parseModeratedAnswer(text);
|
|
40185
40276
|
if (!body) return { body: "", error: "empty answer" };
|
|
@@ -40416,10 +40507,10 @@ async function crontabList() {
|
|
|
40416
40507
|
}
|
|
40417
40508
|
}
|
|
40418
40509
|
async function crontabSet(content) {
|
|
40419
|
-
return new Promise((
|
|
40510
|
+
return new Promise((resolve12) => {
|
|
40420
40511
|
const child = spawn5("crontab", ["-"], { stdio: ["pipe", "ignore", "ignore"] });
|
|
40421
|
-
child.on("error", () =>
|
|
40422
|
-
child.on("close", (code) =>
|
|
40512
|
+
child.on("error", () => resolve12(false));
|
|
40513
|
+
child.on("close", (code) => resolve12(code === 0));
|
|
40423
40514
|
child.stdin.end(content.endsWith("\n") ? content : content + "\n");
|
|
40424
40515
|
});
|
|
40425
40516
|
}
|
|
@@ -40588,14 +40679,14 @@ function detectContainerized() {
|
|
|
40588
40679
|
}
|
|
40589
40680
|
}
|
|
40590
40681
|
function canReach(host, port) {
|
|
40591
|
-
return new Promise((
|
|
40682
|
+
return new Promise((resolve12) => {
|
|
40592
40683
|
const socket = connect({ host, port });
|
|
40593
40684
|
let done = false;
|
|
40594
40685
|
const finish = (ok) => {
|
|
40595
40686
|
if (done) return;
|
|
40596
40687
|
done = true;
|
|
40597
40688
|
socket.destroy();
|
|
40598
|
-
|
|
40689
|
+
resolve12(ok);
|
|
40599
40690
|
};
|
|
40600
40691
|
socket.setTimeout(PROBE_TIMEOUT_MS);
|
|
40601
40692
|
socket.once("connect", () => finish(true));
|
|
@@ -40648,32 +40739,177 @@ async function probeNetwork() {
|
|
|
40648
40739
|
}
|
|
40649
40740
|
|
|
40650
40741
|
// src/core/git-auth.ts
|
|
40651
|
-
import { writeFileSync as writeFileSync8 } from "fs";
|
|
40652
|
-
import {
|
|
40653
|
-
import { join as join18 } from "path";
|
|
40654
|
-
|
|
40655
|
-
|
|
40656
|
-
|
|
40657
|
-
|
|
40658
|
-
|
|
40659
|
-
|
|
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;
|
|
40660
40766
|
}
|
|
40661
40767
|
return null;
|
|
40662
40768
|
}
|
|
40663
|
-
function
|
|
40664
|
-
|
|
40665
|
-
|
|
40666
|
-
|
|
40667
|
-
|
|
40668
|
-
|
|
40669
|
-
|
|
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}" "$@"
|
|
40670
40783
|
`;
|
|
40671
|
-
|
|
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 });
|
|
40672
40846
|
return true;
|
|
40673
|
-
} catch {
|
|
40847
|
+
} catch (err) {
|
|
40848
|
+
logger.warn("git.auth.configure.failed", { error: String(err) });
|
|
40674
40849
|
return false;
|
|
40675
40850
|
}
|
|
40676
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
|
+
}
|
|
40677
40913
|
|
|
40678
40914
|
// src/core/ws-client/transcript.ts
|
|
40679
40915
|
function toDtoEntry(e) {
|
|
@@ -40704,13 +40940,18 @@ import { randomUUID as randomCommandId } from "crypto";
|
|
|
40704
40940
|
// src/core/executor.ts
|
|
40705
40941
|
import { spawn as spawn6 } from "child_process";
|
|
40706
40942
|
var KILL_GRACE_MS = 5e3;
|
|
40707
|
-
async function runCommand(dispatch,
|
|
40943
|
+
async function runCommand(dispatch, emitRaw, opts) {
|
|
40708
40944
|
let seq = 0;
|
|
40945
|
+
const emit = (output) => {
|
|
40946
|
+
emitRaw(output);
|
|
40947
|
+
if (output.done) void endGitScope(dispatch.commandId);
|
|
40948
|
+
};
|
|
40709
40949
|
let child;
|
|
40710
40950
|
try {
|
|
40711
40951
|
child = spawn6(dispatch.cmd, dispatch.args ?? [], {
|
|
40712
40952
|
cwd: dispatch.path,
|
|
40713
|
-
|
|
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 },
|
|
40714
40955
|
shell: false
|
|
40715
40956
|
});
|
|
40716
40957
|
} catch (err) {
|
|
@@ -40767,7 +41008,7 @@ async function runCommand(dispatch, emit, opts) {
|
|
|
40767
41008
|
});
|
|
40768
41009
|
child.stdout?.on("data", (data) => batcher.push(data.toString("utf8")));
|
|
40769
41010
|
child.stderr?.on("data", (data) => batcher.push(data.toString("utf8")));
|
|
40770
|
-
await new Promise((
|
|
41011
|
+
await new Promise((resolve12) => {
|
|
40771
41012
|
child.on("close", (exitCode) => {
|
|
40772
41013
|
clearTimers();
|
|
40773
41014
|
batcher.flush();
|
|
@@ -40780,7 +41021,7 @@ async function runCommand(dispatch, emit, opts) {
|
|
|
40780
41021
|
// server settles the command as failed and the web surfaces the note instead of spinning.
|
|
40781
41022
|
exitCode: timedOut ? 124 : exitCode ?? -1
|
|
40782
41023
|
});
|
|
40783
|
-
|
|
41024
|
+
resolve12();
|
|
40784
41025
|
});
|
|
40785
41026
|
child.on("error", (err) => {
|
|
40786
41027
|
clearTimers();
|
|
@@ -40792,7 +41033,7 @@ async function runCommand(dispatch, emit, opts) {
|
|
|
40792
41033
|
done: true,
|
|
40793
41034
|
exitCode: -1
|
|
40794
41035
|
});
|
|
40795
|
-
|
|
41036
|
+
resolve12();
|
|
40796
41037
|
});
|
|
40797
41038
|
});
|
|
40798
41039
|
}
|
|
@@ -40815,15 +41056,15 @@ function isOutOfCredits(text) {
|
|
|
40815
41056
|
);
|
|
40816
41057
|
}
|
|
40817
41058
|
function runAttempt(commandId, cmd, args, stdin, cwd2, env3, onChunk, timeoutMs) {
|
|
40818
|
-
return new Promise((
|
|
41059
|
+
return new Promise((resolve12) => {
|
|
40819
41060
|
runCommand(
|
|
40820
41061
|
{ commandId, cmd, args, path: cwd2, env: env3 },
|
|
40821
41062
|
(out) => {
|
|
40822
41063
|
if (out.chunk) onChunk(out.chunk);
|
|
40823
|
-
if (out.done)
|
|
41064
|
+
if (out.done) resolve12(out.exitCode ?? -1);
|
|
40824
41065
|
},
|
|
40825
41066
|
{ stdin, ...timeoutMs > 0 ? { timeoutMs } : {} }
|
|
40826
|
-
).catch(() =>
|
|
41067
|
+
).catch(() => resolve12(-1));
|
|
40827
41068
|
});
|
|
40828
41069
|
}
|
|
40829
41070
|
async function runAiFailover(plan, commandId, cwd2, handlers, timeoutMs = 0) {
|
|
@@ -40906,7 +41147,7 @@ function formatTimestampInZone(date, timeZone) {
|
|
|
40906
41147
|
}
|
|
40907
41148
|
|
|
40908
41149
|
// src/ui/slash-commands.ts
|
|
40909
|
-
import { existsSync as
|
|
41150
|
+
import { existsSync as existsSync13 } from "fs";
|
|
40910
41151
|
import { join as join19 } from "path";
|
|
40911
41152
|
var CONFIG_KEYS = [
|
|
40912
41153
|
"aiCli",
|
|
@@ -40969,7 +41210,7 @@ function runConfig(ctx) {
|
|
|
40969
41210
|
ctx.print(t("slash.config.wroteDefault", { verb, path }));
|
|
40970
41211
|
ctx.print(t("slash.config.addClaudeHint"));
|
|
40971
41212
|
};
|
|
40972
|
-
if (
|
|
41213
|
+
if (existsSync13(path)) {
|
|
40973
41214
|
ctx.confirm(
|
|
40974
41215
|
t("slash.config.replaceConfirm", { path }),
|
|
40975
41216
|
() => writeDefaults(t("slash.config.verbReplaced"))
|
|
@@ -41031,8 +41272,8 @@ function runHistory(ctx) {
|
|
|
41031
41272
|
ctx.print(t("slash.history.header", { n: recent.length, total: all.length }));
|
|
41032
41273
|
recent.forEach((e, i) => {
|
|
41033
41274
|
const time = new Date(e.startedAt).toLocaleTimeString();
|
|
41034
|
-
const
|
|
41035
|
-
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}`);
|
|
41036
41277
|
});
|
|
41037
41278
|
}
|
|
41038
41279
|
function runOutput(ctx) {
|
|
@@ -41043,8 +41284,8 @@ function runOutput(ctx) {
|
|
|
41043
41284
|
return;
|
|
41044
41285
|
}
|
|
41045
41286
|
const entry = all.slice().reverse()[n - 1];
|
|
41046
|
-
const
|
|
41047
|
-
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 }));
|
|
41048
41289
|
const out = readCommandOutput(entry.commandId);
|
|
41049
41290
|
if (out == null) {
|
|
41050
41291
|
ctx.print(t("slash.output.none"), "warn");
|
|
@@ -41339,7 +41580,7 @@ function buildPrompt2(input) {
|
|
|
41339
41580
|
].join("\n");
|
|
41340
41581
|
}
|
|
41341
41582
|
function runOnce(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
|
|
41342
|
-
return new Promise((
|
|
41583
|
+
return new Promise((resolve12, reject) => {
|
|
41343
41584
|
const args = [
|
|
41344
41585
|
"-p",
|
|
41345
41586
|
"--output-format",
|
|
@@ -41374,7 +41615,7 @@ function runOnce(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
|
|
|
41374
41615
|
});
|
|
41375
41616
|
child.on("close", (code) => {
|
|
41376
41617
|
clearTimeout(timer);
|
|
41377
|
-
|
|
41618
|
+
resolve12({ code: code ?? -1, out, err });
|
|
41378
41619
|
});
|
|
41379
41620
|
child.stdin.write(prompt);
|
|
41380
41621
|
child.stdin.end();
|
|
@@ -41412,6 +41653,54 @@ async function setCommitAuthor(cwd2, machineUsername) {
|
|
|
41412
41653
|
}
|
|
41413
41654
|
}
|
|
41414
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
|
+
|
|
41415
41704
|
// src/core/ws-client/command-dispatch.ts
|
|
41416
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:";
|
|
41417
41706
|
function handleCommandChannels(ctx, message, payload) {
|
|
@@ -41665,8 +41954,28 @@ ${guardedPrompt}`;
|
|
|
41665
41954
|
}
|
|
41666
41955
|
};
|
|
41667
41956
|
let result;
|
|
41957
|
+
let queueTimedOut = false;
|
|
41668
41958
|
try {
|
|
41669
|
-
|
|
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
|
+
}
|
|
41670
41979
|
} finally {
|
|
41671
41980
|
ctx.bus.endBusy(plan.cmd);
|
|
41672
41981
|
}
|
|
@@ -41727,7 +42036,7 @@ ${guardedPrompt}`;
|
|
|
41727
42036
|
});
|
|
41728
42037
|
ctx.bus.addTokens?.(result.usage.tokens);
|
|
41729
42038
|
}
|
|
41730
|
-
ctx.reportUsage(events, profileLabel);
|
|
42039
|
+
if (!queueTimedOut) ctx.reportUsage(events, profileLabel);
|
|
41731
42040
|
void ctx.pollUsage();
|
|
41732
42041
|
ctx.bus.push({
|
|
41733
42042
|
source: origin,
|
|
@@ -42210,11 +42519,11 @@ async function allAiProfilesExhausted(ctx, cfg) {
|
|
|
42210
42519
|
|
|
42211
42520
|
// src/core/fs-browse.ts
|
|
42212
42521
|
import { readdir as readdir3 } from "fs/promises";
|
|
42213
|
-
import { resolve as
|
|
42522
|
+
import { resolve as resolve4, sep as sep2 } from "path";
|
|
42214
42523
|
async function listDir(path, root) {
|
|
42215
42524
|
if (!root) return { path: "", entries: [] };
|
|
42216
|
-
const base =
|
|
42217
|
-
const requested =
|
|
42525
|
+
const base = resolve4(root);
|
|
42526
|
+
const requested = resolve4(base, path && path.trim() ? path : ".");
|
|
42218
42527
|
const target = requested === base || requested.startsWith(base + sep2) ? requested : base;
|
|
42219
42528
|
try {
|
|
42220
42529
|
const dirents = await readdir3(target, { withFileTypes: true });
|
|
@@ -42233,12 +42542,12 @@ async function listDir(path, root) {
|
|
|
42233
42542
|
|
|
42234
42543
|
// src/core/fs-read.ts
|
|
42235
42544
|
import { readFile as readFile6 } from "fs/promises";
|
|
42236
|
-
import { resolve as
|
|
42545
|
+
import { resolve as resolve5, sep as sep3 } from "path";
|
|
42237
42546
|
var MAX_BYTES = 256 * 1024;
|
|
42238
42547
|
function resolveInRoot(root, relPath) {
|
|
42239
42548
|
if (!root) return null;
|
|
42240
|
-
const base =
|
|
42241
|
-
const target =
|
|
42549
|
+
const base = resolve5(root);
|
|
42550
|
+
const target = resolve5(base, relPath || ".");
|
|
42242
42551
|
return target === base || target.startsWith(base + sep3) ? target : null;
|
|
42243
42552
|
}
|
|
42244
42553
|
async function readWorkerFile(root, path) {
|
|
@@ -42258,12 +42567,12 @@ async function readWorkerFile(root, path) {
|
|
|
42258
42567
|
|
|
42259
42568
|
// src/core/fs-write.ts
|
|
42260
42569
|
import { mkdir as mkdir4, writeFile as writeFile5 } from "fs/promises";
|
|
42261
|
-
import { dirname as
|
|
42570
|
+
import { dirname as dirname5, resolve as resolve6, sep as sep4 } from "path";
|
|
42262
42571
|
var MAX_BYTES2 = 2 * 1024 * 1024;
|
|
42263
42572
|
function resolveInRoot2(root, relPath) {
|
|
42264
42573
|
if (!root) return null;
|
|
42265
|
-
const base =
|
|
42266
|
-
const target =
|
|
42574
|
+
const base = resolve6(root);
|
|
42575
|
+
const target = resolve6(base, relPath || ".");
|
|
42267
42576
|
return target === base || target.startsWith(base + sep4) ? target : null;
|
|
42268
42577
|
}
|
|
42269
42578
|
async function writeWorkerFile(root, relPath, content) {
|
|
@@ -42276,7 +42585,7 @@ async function writeWorkerFile(root, relPath, content) {
|
|
|
42276
42585
|
return { ok: false, path: target, bytes: 0, error: `content too large (max ${MAX_BYTES2} bytes)` };
|
|
42277
42586
|
}
|
|
42278
42587
|
try {
|
|
42279
|
-
await mkdir4(
|
|
42588
|
+
await mkdir4(dirname5(target), { recursive: true });
|
|
42280
42589
|
await writeFile5(target, content, "utf8");
|
|
42281
42590
|
return { ok: true, path: target, bytes };
|
|
42282
42591
|
} catch (err) {
|
|
@@ -42286,11 +42595,11 @@ async function writeWorkerFile(root, relPath, content) {
|
|
|
42286
42595
|
|
|
42287
42596
|
// src/core/fs-mutate.ts
|
|
42288
42597
|
import { mkdir as mkdir5, writeFile as writeFile6, rename, rm as rm3, stat as stat2 } from "fs/promises";
|
|
42289
|
-
import { basename as basename3, dirname as
|
|
42598
|
+
import { basename as basename3, dirname as dirname6, resolve as resolve7, sep as sep5 } from "path";
|
|
42290
42599
|
var MAX_BYTES3 = 2 * 1024 * 1024;
|
|
42291
42600
|
function resolveInRoot3(root, rel) {
|
|
42292
|
-
const base =
|
|
42293
|
-
const target =
|
|
42601
|
+
const base = resolve7(root);
|
|
42602
|
+
const target = resolve7(base, rel || ".");
|
|
42294
42603
|
return target === base || target.startsWith(base + sep5) ? target : null;
|
|
42295
42604
|
}
|
|
42296
42605
|
async function isDir(p2) {
|
|
@@ -42315,19 +42624,19 @@ async function mutateFs(root, req) {
|
|
|
42315
42624
|
switch (req.op) {
|
|
42316
42625
|
case "mkdir": {
|
|
42317
42626
|
const target = resolveInRoot3(root, req.path ?? "");
|
|
42318
|
-
if (!target || target ===
|
|
42627
|
+
if (!target || target === resolve7(root)) return escaped(req.path ?? "");
|
|
42319
42628
|
await mkdir5(target, { recursive: true });
|
|
42320
42629
|
return { ok: true, path: target };
|
|
42321
42630
|
}
|
|
42322
42631
|
case "create": {
|
|
42323
42632
|
const target = resolveInRoot3(root, req.path ?? "");
|
|
42324
|
-
if (!target || target ===
|
|
42633
|
+
if (!target || target === resolve7(root)) return escaped(req.path ?? "");
|
|
42325
42634
|
if (await exists(target)) return { ok: false, path: target, error: "already exists" };
|
|
42326
42635
|
const content = req.content ?? "";
|
|
42327
42636
|
if (Buffer.byteLength(content, "utf8") > MAX_BYTES3) {
|
|
42328
42637
|
return { ok: false, path: target, error: `content too large (max ${MAX_BYTES3} bytes)` };
|
|
42329
42638
|
}
|
|
42330
|
-
await mkdir5(
|
|
42639
|
+
await mkdir5(dirname6(target), { recursive: true });
|
|
42331
42640
|
await writeFile6(target, content, "utf8");
|
|
42332
42641
|
return { ok: true, path: target };
|
|
42333
42642
|
}
|
|
@@ -42335,18 +42644,18 @@ async function mutateFs(root, req) {
|
|
|
42335
42644
|
const from = resolveInRoot3(root, req.from ?? "");
|
|
42336
42645
|
let to2 = resolveInRoot3(root, req.to ?? "");
|
|
42337
42646
|
if (!from || !to2) return escaped(req.from ?? req.to ?? "");
|
|
42338
|
-
if (from ===
|
|
42339
|
-
if (await isDir(to2)) to2 =
|
|
42340
|
-
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 ?? "");
|
|
42341
42650
|
if (await exists(to2)) return { ok: false, path: to2, error: "destination already exists" };
|
|
42342
|
-
await mkdir5(
|
|
42651
|
+
await mkdir5(dirname6(to2), { recursive: true });
|
|
42343
42652
|
await rename(from, to2);
|
|
42344
42653
|
return { ok: true, path: to2 };
|
|
42345
42654
|
}
|
|
42346
42655
|
case "delete": {
|
|
42347
42656
|
const target = resolveInRoot3(root, req.path ?? "");
|
|
42348
42657
|
if (!target) return escaped(req.path ?? "");
|
|
42349
|
-
if (target ===
|
|
42658
|
+
if (target === resolve7(root)) return { ok: false, path: target, error: "cannot delete the project root" };
|
|
42350
42659
|
await rm3(target, { recursive: true, force: true });
|
|
42351
42660
|
return { ok: true, path: target };
|
|
42352
42661
|
}
|
|
@@ -42360,11 +42669,11 @@ async function mutateFs(root, req) {
|
|
|
42360
42669
|
|
|
42361
42670
|
// src/core/fs-transfer.ts
|
|
42362
42671
|
import { mkdir as mkdir6, readFile as readFile7, stat as stat3, writeFile as writeFile7 } from "fs/promises";
|
|
42363
|
-
import { basename as basename4, dirname as
|
|
42672
|
+
import { basename as basename4, dirname as dirname7, extname, resolve as resolve8, sep as sep6 } from "path";
|
|
42364
42673
|
function resolveInRoot4(root, relPath) {
|
|
42365
42674
|
if (!root) return null;
|
|
42366
|
-
const base =
|
|
42367
|
-
const target =
|
|
42675
|
+
const base = resolve8(root);
|
|
42676
|
+
const target = resolve8(base, relPath || ".");
|
|
42368
42677
|
return target === base || target.startsWith(base + sep6) ? target : null;
|
|
42369
42678
|
}
|
|
42370
42679
|
var MIME_BY_EXT = {
|
|
@@ -42403,7 +42712,7 @@ async function uploadBinaryFile(root, relPath, contentBase64) {
|
|
|
42403
42712
|
return { ok: false, path: target, bytes: 0, error: `file too large (max ${FS_TRANSFER_MAX_BYTES} bytes)` };
|
|
42404
42713
|
}
|
|
42405
42714
|
try {
|
|
42406
|
-
await mkdir6(
|
|
42715
|
+
await mkdir6(dirname7(target), { recursive: true });
|
|
42407
42716
|
await writeFile7(target, buf);
|
|
42408
42717
|
return { ok: true, path: target, bytes: buf.byteLength };
|
|
42409
42718
|
} catch (err) {
|
|
@@ -42480,13 +42789,13 @@ function handleFsChannels(ctx, message, payload) {
|
|
|
42480
42789
|
// src/core/git-diff.ts
|
|
42481
42790
|
import { readFile as readFile8 } from "fs/promises";
|
|
42482
42791
|
import { execFile as execFile6 } from "child_process";
|
|
42483
|
-
import { basename as basename5, dirname as
|
|
42792
|
+
import { basename as basename5, dirname as dirname8, resolve as resolve9, sep as sep7 } from "path";
|
|
42484
42793
|
import { promisify as promisify6 } from "util";
|
|
42485
42794
|
var run6 = promisify6(execFile6);
|
|
42486
42795
|
function resolveInRoot5(root, relPath) {
|
|
42487
42796
|
if (!root) return null;
|
|
42488
|
-
const base =
|
|
42489
|
-
const target =
|
|
42797
|
+
const base = resolve9(root);
|
|
42798
|
+
const target = resolve9(base, relPath || ".");
|
|
42490
42799
|
return target === base || target.startsWith(base + sep7) ? target : null;
|
|
42491
42800
|
}
|
|
42492
42801
|
function looksBinary(buf) {
|
|
@@ -42501,7 +42810,7 @@ async function gitDiff(root, path) {
|
|
|
42501
42810
|
if (!target) {
|
|
42502
42811
|
return { path, oldContent: "", newContent: "", isBinary: false };
|
|
42503
42812
|
}
|
|
42504
|
-
const dir2 =
|
|
42813
|
+
const dir2 = dirname8(target);
|
|
42505
42814
|
const base = basename5(target);
|
|
42506
42815
|
let newBuf = null;
|
|
42507
42816
|
try {
|
|
@@ -42640,15 +42949,15 @@ async function manageSshKey(op, profileDir2) {
|
|
|
42640
42949
|
|
|
42641
42950
|
// src/core/git-history.ts
|
|
42642
42951
|
import { execFile as execFile9 } from "child_process";
|
|
42643
|
-
import { resolve as
|
|
42952
|
+
import { resolve as resolve10, sep as sep8 } from "path";
|
|
42644
42953
|
import { promisify as promisify9 } from "util";
|
|
42645
42954
|
var run9 = promisify9(execFile9);
|
|
42646
42955
|
var FS = "";
|
|
42647
42956
|
var RS = "";
|
|
42648
42957
|
function resolveRepo(root, repo) {
|
|
42649
42958
|
if (!root) return null;
|
|
42650
|
-
const base =
|
|
42651
|
-
const requested =
|
|
42959
|
+
const base = resolve10(root);
|
|
42960
|
+
const requested = resolve10(base, repo && repo.trim() ? repo : ".");
|
|
42652
42961
|
return requested === base || requested.startsWith(base + sep8) ? requested : base;
|
|
42653
42962
|
}
|
|
42654
42963
|
async function git(cwd2, args) {
|
|
@@ -42665,7 +42974,7 @@ async function git(cwd2, args) {
|
|
|
42665
42974
|
}
|
|
42666
42975
|
async function gitRepos(root) {
|
|
42667
42976
|
if (!root) return { repos: [] };
|
|
42668
|
-
const base =
|
|
42977
|
+
const base = resolve10(root);
|
|
42669
42978
|
const top = (await git(base, ["rev-parse", "--show-toplevel"])).trim();
|
|
42670
42979
|
if (!top || !(top === base || top.startsWith(base + sep8))) return { repos: [] };
|
|
42671
42980
|
const repos = [];
|
|
@@ -42677,7 +42986,7 @@ async function gitRepos(root) {
|
|
|
42677
42986
|
const m2 = line.trim().match(/^[-+U ]?[0-9a-f]{7,40}\s+(\S+)/);
|
|
42678
42987
|
const subPath = m2?.[1];
|
|
42679
42988
|
if (!subPath) continue;
|
|
42680
|
-
const abs =
|
|
42989
|
+
const abs = resolve10(base, subPath);
|
|
42681
42990
|
if (!(abs === base || abs.startsWith(base + sep8))) continue;
|
|
42682
42991
|
const remote = (await git(abs, ["remote", "get-url", "origin"])).trim();
|
|
42683
42992
|
const branch = (await git(abs, ["rev-parse", "--abbrev-ref", "HEAD"])).trim();
|
|
@@ -42906,7 +43215,7 @@ function handleMiscChannels(ctx, message, payload) {
|
|
|
42906
43215
|
}
|
|
42907
43216
|
|
|
42908
43217
|
// src/core/ws-client/handlers/project.ts
|
|
42909
|
-
import { existsSync as
|
|
43218
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync7, renameSync, rmSync as rmSync7 } from "fs";
|
|
42910
43219
|
import { join as join23 } from "path";
|
|
42911
43220
|
function handleProjectChannels(ctx, message, payload) {
|
|
42912
43221
|
switch (message.channel) {
|
|
@@ -42919,11 +43228,11 @@ function handleProjectChannels(ctx, message, payload) {
|
|
|
42919
43228
|
const oldRoot = ctx.physicFolderPath(oldName);
|
|
42920
43229
|
if (oldRoot) void uninstallCron(oldRoot).catch(() => void 0);
|
|
42921
43230
|
}
|
|
42922
|
-
if (oldPath && oldName !== sync.newName &&
|
|
43231
|
+
if (oldPath && oldName !== sync.newName && existsSync14(oldPath) && !existsSync14(newPath)) {
|
|
42923
43232
|
renameSync(oldPath, newPath);
|
|
42924
43233
|
} else {
|
|
42925
|
-
if (oldPath && oldName !== sync.newName &&
|
|
42926
|
-
|
|
43234
|
+
if (oldPath && oldName !== sync.newName && existsSync14(oldPath)) rmSync7(oldPath, { recursive: true, force: true });
|
|
43235
|
+
mkdirSync7(newPath, { recursive: true });
|
|
42927
43236
|
}
|
|
42928
43237
|
ctx.physicRoot = ctx.physicFolderPath(sync.newName);
|
|
42929
43238
|
ctx.bus.setProject(sync.newName);
|
|
@@ -42936,7 +43245,7 @@ function handleProjectChannels(ctx, message, payload) {
|
|
|
42936
43245
|
if (del.name) {
|
|
42937
43246
|
const delRoot = ctx.physicFolderPath(del.name);
|
|
42938
43247
|
if (delRoot) void uninstallCron(delRoot).catch(() => void 0);
|
|
42939
|
-
|
|
43248
|
+
rmSync7(join23(ctx.profileDir, del.name), { recursive: true, force: true });
|
|
42940
43249
|
ctx.bus.log(t("project.folderDeleted", { name: del.name }));
|
|
42941
43250
|
}
|
|
42942
43251
|
void manageSshKey("delete", ctx.profileDir).catch(() => void 0);
|
|
@@ -43000,7 +43309,7 @@ Extra focus from the author: ${req.prompt}` : "";
|
|
|
43000
43309
|
].join("\n");
|
|
43001
43310
|
}
|
|
43002
43311
|
function runOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
|
|
43003
|
-
return new Promise((
|
|
43312
|
+
return new Promise((resolve12, reject) => {
|
|
43004
43313
|
const args = ["-p", ...profile?.model ? ["--model", profile.model] : [], ...denySettingsArgs(cmd, [...denyDirs, profile?.dir])];
|
|
43005
43314
|
const env3 = {
|
|
43006
43315
|
...process.env,
|
|
@@ -43023,7 +43332,7 @@ function runOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
|
|
|
43023
43332
|
});
|
|
43024
43333
|
child.on("close", (code) => {
|
|
43025
43334
|
clearTimeout(timer);
|
|
43026
|
-
|
|
43335
|
+
resolve12({ code: code ?? -1, out, err });
|
|
43027
43336
|
});
|
|
43028
43337
|
child.stdin.write(prompt);
|
|
43029
43338
|
child.stdin.end();
|
|
@@ -43054,7 +43363,7 @@ async function runKnowledgeCompose(req, ai2, physicRoot) {
|
|
|
43054
43363
|
|
|
43055
43364
|
// src/core/faq-compose.ts
|
|
43056
43365
|
import { spawn as spawn9, execFile as execFile10 } from "child_process";
|
|
43057
|
-
import { existsSync as
|
|
43366
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync8, rmSync as rmSync8 } from "fs";
|
|
43058
43367
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
43059
43368
|
import { join as join24 } from "path";
|
|
43060
43369
|
import { tmpdir as tmpdir4 } from "os";
|
|
@@ -43124,7 +43433,7 @@ function parsePerTicket(text) {
|
|
|
43124
43433
|
return [];
|
|
43125
43434
|
}
|
|
43126
43435
|
function runClaudeOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
|
|
43127
|
-
return new Promise((
|
|
43436
|
+
return new Promise((resolve12, reject) => {
|
|
43128
43437
|
const isClaude = cmd.includes("claude");
|
|
43129
43438
|
const args = [
|
|
43130
43439
|
"-p",
|
|
@@ -43157,7 +43466,7 @@ function runClaudeOnce2(cmd, profile, prompt, cwd2, extraEnv, denyDirs = []) {
|
|
|
43157
43466
|
child.on("close", (code) => {
|
|
43158
43467
|
clearTimeout(timer);
|
|
43159
43468
|
out += parser.flush();
|
|
43160
|
-
|
|
43469
|
+
resolve12({ code: code ?? -1, out, err, usage: parser.usage() });
|
|
43161
43470
|
});
|
|
43162
43471
|
child.stdin.write(prompt);
|
|
43163
43472
|
child.stdin.end();
|
|
@@ -43181,7 +43490,7 @@ async function runFaqCompose(req, ai2) {
|
|
|
43181
43490
|
const gitEnv = { ...process.env, GIT_TERMINAL_PROMPT: "0" };
|
|
43182
43491
|
const auth = gitAuthArgs("x-access-token", req.repo.token);
|
|
43183
43492
|
try {
|
|
43184
|
-
|
|
43493
|
+
mkdirSync8(workDir, { recursive: true });
|
|
43185
43494
|
await execFileAsync2("git", [...auth, "clone", "--depth", "1", "--branch", req.repo.branch, req.repo.url, workDir], { env: gitEnv });
|
|
43186
43495
|
await execFileAsync2("git", ["-C", workDir, "config", "user.name", "4PM FAQ Bot"], { env: gitEnv });
|
|
43187
43496
|
await execFileAsync2("git", ["-C", workDir, "config", "user.email", "faq-bot@4pm.app"], { env: gitEnv });
|
|
@@ -43221,9 +43530,9 @@ async function runFaqCompose(req, ai2) {
|
|
|
43221
43530
|
logger.warn("faq.compose.failed", { error: String(err) });
|
|
43222
43531
|
return { prUrl: "", error: String(err) };
|
|
43223
43532
|
} finally {
|
|
43224
|
-
if (
|
|
43533
|
+
if (existsSync15(workDir)) {
|
|
43225
43534
|
try {
|
|
43226
|
-
|
|
43535
|
+
rmSync8(workDir, { recursive: true, force: true });
|
|
43227
43536
|
} catch {
|
|
43228
43537
|
}
|
|
43229
43538
|
}
|
|
@@ -43547,7 +43856,7 @@ async function writeSecrets(root, req) {
|
|
|
43547
43856
|
|
|
43548
43857
|
// src/core/agent-tools.ts
|
|
43549
43858
|
import { readFile as readFile12, writeFile as writeFile10, mkdir as mkdir9 } from "fs/promises";
|
|
43550
|
-
import { dirname as
|
|
43859
|
+
import { dirname as dirname9, join as join27 } from "path";
|
|
43551
43860
|
var SHARED_REL = ".claude/settings.json";
|
|
43552
43861
|
var LOCAL_REL = ".claude/settings.local.json";
|
|
43553
43862
|
var SECRETS_DENY = "Read(./project.secrets.json)";
|
|
@@ -43578,7 +43887,7 @@ async function writeAgentTools(root, scope, permissions) {
|
|
|
43578
43887
|
const next = normalizePermissions(permissions);
|
|
43579
43888
|
if (scope === "shared" && !next.deny.includes(SECRETS_DENY)) next.deny = [...next.deny, SECRETS_DENY];
|
|
43580
43889
|
obj.permissions = next;
|
|
43581
|
-
await mkdir9(
|
|
43890
|
+
await mkdir9(dirname9(path), { recursive: true });
|
|
43582
43891
|
await writeFile10(path, JSON.stringify(obj, null, 2) + "\n", "utf8");
|
|
43583
43892
|
return { ok: true };
|
|
43584
43893
|
} catch (err) {
|
|
@@ -43589,7 +43898,7 @@ async function writeAgentTools(root, scope, permissions) {
|
|
|
43589
43898
|
// src/core/packages.ts
|
|
43590
43899
|
import { createHash as createHash4 } from "crypto";
|
|
43591
43900
|
import { readFile as readFile13, writeFile as writeFile11, mkdir as mkdir10, readdir as readdir5, rm as rm6, stat as stat4 } from "fs/promises";
|
|
43592
|
-
import { dirname as
|
|
43901
|
+
import { dirname as dirname10, join as join28, relative } from "path";
|
|
43593
43902
|
var AGENTS_REL2 = ".claude/agents";
|
|
43594
43903
|
var SKILLS_REL2 = ".claude/skills";
|
|
43595
43904
|
var MANIFEST_REL = ".claude/.4pm-packages.json";
|
|
@@ -43670,7 +43979,7 @@ async function readManifest(root) {
|
|
|
43670
43979
|
}
|
|
43671
43980
|
async function writeManifest(root, manifest) {
|
|
43672
43981
|
const path = join28(root, MANIFEST_REL);
|
|
43673
|
-
await mkdir10(
|
|
43982
|
+
await mkdir10(dirname10(path), { recursive: true });
|
|
43674
43983
|
await writeFile11(path, JSON.stringify(manifest, null, 2), "utf8");
|
|
43675
43984
|
}
|
|
43676
43985
|
async function writeArtifactFiles(root, kind, slug, files) {
|
|
@@ -43696,7 +44005,7 @@ async function writeArtifactFiles(root, kind, slug, files) {
|
|
|
43696
44005
|
for (const [rel, buf] of byPath) {
|
|
43697
44006
|
if (rel.includes("..") || rel.startsWith("/")) continue;
|
|
43698
44007
|
const dest = join28(base, rel);
|
|
43699
|
-
await mkdir10(
|
|
44008
|
+
await mkdir10(dirname10(dest), { recursive: true });
|
|
43700
44009
|
await writeFile11(dest, buf);
|
|
43701
44010
|
}
|
|
43702
44011
|
}
|
|
@@ -43777,7 +44086,7 @@ async function removePackage(root, slug) {
|
|
|
43777
44086
|
|
|
43778
44087
|
// src/core/graph.ts
|
|
43779
44088
|
import { readFile as readFile14, readdir as readdir6 } from "fs/promises";
|
|
43780
|
-
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";
|
|
43781
44090
|
var MAX_FILES = 2e3;
|
|
43782
44091
|
var MAX_NODES = 3e3;
|
|
43783
44092
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "out", "coverage", ".turbo"]);
|
|
@@ -43832,7 +44141,7 @@ async function buildDocs(root) {
|
|
|
43832
44141
|
for (const m2 of text.matchAll(/\[[^\]]*\]\(([^)]+)\)/g)) {
|
|
43833
44142
|
const href = (m2[1] ?? "").split("#")[0].trim();
|
|
43834
44143
|
if (!href || !href.endsWith(".md") || href.startsWith("http")) continue;
|
|
43835
|
-
const target = relative2(root,
|
|
44144
|
+
const target = relative2(root, resolve11(dirname11(f2), href)).split("\\").join("/");
|
|
43836
44145
|
if (ids.has(target) && target !== rel) {
|
|
43837
44146
|
const key = `${rel}->${target}`;
|
|
43838
44147
|
if (!seen.has(key)) {
|
|
@@ -43917,7 +44226,7 @@ async function buildGraph(root, mode) {
|
|
|
43917
44226
|
|
|
43918
44227
|
// src/core/rag.ts
|
|
43919
44228
|
import { readFile as readFile15, writeFile as writeFile12, statfs as statfs2, mkdir as mkdir11 } from "fs/promises";
|
|
43920
|
-
import { existsSync as
|
|
44229
|
+
import { existsSync as existsSync16 } from "fs";
|
|
43921
44230
|
import { execFile as execFile11, spawn as spawn10 } from "child_process";
|
|
43922
44231
|
import { totalmem as totalmem2, cpus as cpus2 } from "os";
|
|
43923
44232
|
import { join as join30 } from "path";
|
|
@@ -44010,7 +44319,7 @@ async function ragStatus(root) {
|
|
|
44010
44319
|
const modelPresent = modelName.length > 0;
|
|
44011
44320
|
return {
|
|
44012
44321
|
installed: fastembed && sqliteVec && modelPresent,
|
|
44013
|
-
installing:
|
|
44322
|
+
installing: existsSync16(join30(root, RAG_REL, ".installing")),
|
|
44014
44323
|
python: { found: py.found, version: py.version },
|
|
44015
44324
|
deps: { pip, fastembed, sqliteVec },
|
|
44016
44325
|
model: { present: modelPresent, name: modelName },
|
|
@@ -44021,7 +44330,7 @@ async function ragStatus(root) {
|
|
|
44021
44330
|
};
|
|
44022
44331
|
}
|
|
44023
44332
|
async function readIndexMeta(root) {
|
|
44024
|
-
const indexing =
|
|
44333
|
+
const indexing = existsSync16(join30(root, RAG_REL, ".indexing"));
|
|
44025
44334
|
try {
|
|
44026
44335
|
const m2 = JSON.parse(await readFile15(join30(root, RAG_REL, ".index.json"), "utf8"));
|
|
44027
44336
|
return {
|
|
@@ -44096,8 +44405,8 @@ async function ensureEngine(root) {
|
|
|
44096
44405
|
async function ragReindex(root) {
|
|
44097
44406
|
const py = await detectPython();
|
|
44098
44407
|
if (!py.found) return { ok: false, started: false, error: "python3 not found" };
|
|
44099
|
-
if (!
|
|
44100
|
-
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 };
|
|
44101
44410
|
await ensureEngine(root);
|
|
44102
44411
|
const script = `
|
|
44103
44412
|
set +e
|
|
@@ -44117,7 +44426,7 @@ rm -f "${RAG_REL}/.indexing"
|
|
|
44117
44426
|
async function ragQuery(root, queryText, k2 = 8) {
|
|
44118
44427
|
const py = await detectPython();
|
|
44119
44428
|
if (!py.found) return { results: [], error: "python3 not found" };
|
|
44120
|
-
if (!
|
|
44429
|
+
if (!existsSync16(join30(root, RAG_REL, "index.db"))) return { results: [], error: "no index \u2014 reindex first" };
|
|
44121
44430
|
await ensureEngine(root);
|
|
44122
44431
|
try {
|
|
44123
44432
|
const { stdout } = await run10(
|
|
@@ -44137,7 +44446,7 @@ async function ragInstall(root, model) {
|
|
|
44137
44446
|
}
|
|
44138
44447
|
const py = await detectPython();
|
|
44139
44448
|
if (!py.found) return { ok: false, started: false, error: "python3 not found on the worker" };
|
|
44140
|
-
if (
|
|
44449
|
+
if (existsSync16(join30(root, RAG_REL, ".installing"))) return { ok: true, started: true };
|
|
44141
44450
|
try {
|
|
44142
44451
|
await mkdir11(join30(root, RAG_REL), { recursive: true });
|
|
44143
44452
|
} catch {
|
|
@@ -44166,7 +44475,7 @@ rm -f "${RAG_REL}/.installing"
|
|
|
44166
44475
|
}
|
|
44167
44476
|
|
|
44168
44477
|
// src/core/config-sync.ts
|
|
44169
|
-
import { existsSync as
|
|
44478
|
+
import { existsSync as existsSync17, readFileSync as readFileSync16 } from "fs";
|
|
44170
44479
|
import { join as join31 } from "path";
|
|
44171
44480
|
var SERVER_MANAGED_KEYS = [
|
|
44172
44481
|
"physicPath",
|
|
@@ -44185,7 +44494,7 @@ var SERVER_MANAGED_KEYS = [
|
|
|
44185
44494
|
];
|
|
44186
44495
|
function readConfigText(profileDir2) {
|
|
44187
44496
|
const path = join31(profileDir2, "config.json");
|
|
44188
|
-
if (
|
|
44497
|
+
if (existsSync17(path)) {
|
|
44189
44498
|
try {
|
|
44190
44499
|
return readFileSync16(path, "utf8");
|
|
44191
44500
|
} catch {
|
|
@@ -44671,14 +44980,14 @@ var WsClient = class {
|
|
|
44671
44980
|
*/
|
|
44672
44981
|
awaitConnected(timeoutMs) {
|
|
44673
44982
|
if (this.sessionKey && this.bus.status === "connected") return Promise.resolve(true);
|
|
44674
|
-
return new Promise((
|
|
44983
|
+
return new Promise((resolve12) => {
|
|
44675
44984
|
const waiter = () => {
|
|
44676
44985
|
clearTimeout(timer);
|
|
44677
|
-
|
|
44986
|
+
resolve12(true);
|
|
44678
44987
|
};
|
|
44679
44988
|
const timer = setTimeout(() => {
|
|
44680
44989
|
this.connectWaiters = this.connectWaiters.filter((w2) => w2 !== waiter);
|
|
44681
|
-
|
|
44990
|
+
resolve12(false);
|
|
44682
44991
|
}, timeoutMs);
|
|
44683
44992
|
this.connectWaiters.push(waiter);
|
|
44684
44993
|
});
|
|
@@ -44705,16 +45014,16 @@ var WsClient = class {
|
|
|
44705
45014
|
this.forceReconnect = false;
|
|
44706
45015
|
return Promise.resolve();
|
|
44707
45016
|
}
|
|
44708
|
-
return new Promise((
|
|
45017
|
+
return new Promise((resolve12) => {
|
|
44709
45018
|
const timer = setTimeout(() => {
|
|
44710
45019
|
this.wakeReconnect = null;
|
|
44711
|
-
|
|
45020
|
+
resolve12();
|
|
44712
45021
|
}, ms2);
|
|
44713
45022
|
this.wakeReconnect = () => {
|
|
44714
45023
|
clearTimeout(timer);
|
|
44715
45024
|
this.wakeReconnect = null;
|
|
44716
45025
|
this.forceReconnect = false;
|
|
44717
|
-
|
|
45026
|
+
resolve12();
|
|
44718
45027
|
};
|
|
44719
45028
|
});
|
|
44720
45029
|
}
|
|
@@ -44768,7 +45077,7 @@ var WsClient = class {
|
|
|
44768
45077
|
* One WS session: token → connect → handshake → handle messages until close.
|
|
44769
45078
|
*/
|
|
44770
45079
|
connectOnce() {
|
|
44771
|
-
return new Promise((
|
|
45080
|
+
return new Promise((resolve12, reject) => {
|
|
44772
45081
|
void (async () => {
|
|
44773
45082
|
this.sessionEstablished = false;
|
|
44774
45083
|
this.bus.setStatus("connecting");
|
|
@@ -44810,7 +45119,12 @@ var WsClient = class {
|
|
|
44810
45119
|
if (token.aiMemory && !this.aiMemory) this.aiMemory = token.aiMemory.text;
|
|
44811
45120
|
this.outboundReviewEnabled = token.outboundReview?.enabled === true;
|
|
44812
45121
|
this.restrictToFolder = token.aiRestrictToFolder === true;
|
|
44813
|
-
|
|
45122
|
+
configureGitAuth(
|
|
45123
|
+
token.gitAuth ?? null,
|
|
45124
|
+
token.gitAuthHost ?? null,
|
|
45125
|
+
this.context.profileDir,
|
|
45126
|
+
(req) => this.request(WsChannels.GIT_TOKEN, req)
|
|
45127
|
+
);
|
|
44814
45128
|
this.machineUsername = token.machineUsername ?? "";
|
|
44815
45129
|
this.servingRepos = token.repos ?? [];
|
|
44816
45130
|
void this.ensureServingReposCloned();
|
|
@@ -44864,7 +45178,7 @@ var WsClient = class {
|
|
|
44864
45178
|
if (code === CLOSE_DRAINING) {
|
|
44865
45179
|
this.bus.log(t("error.serverDraining"));
|
|
44866
45180
|
}
|
|
44867
|
-
|
|
45181
|
+
resolve12();
|
|
44868
45182
|
});
|
|
44869
45183
|
socket.on("error", (err) => {
|
|
44870
45184
|
this.clearHeartbeat();
|
|
@@ -44990,7 +45304,7 @@ var WsClient = class {
|
|
|
44990
45304
|
* Used for quota.check before spawning an AI cli (ADR-0020).
|
|
44991
45305
|
*/
|
|
44992
45306
|
request(channel, data) {
|
|
44993
|
-
return new Promise((
|
|
45307
|
+
return new Promise((resolve12, reject) => {
|
|
44994
45308
|
if (!this.socket || !this.sessionKey) {
|
|
44995
45309
|
reject(new Error("Not connected to the server."));
|
|
44996
45310
|
return;
|
|
@@ -45010,7 +45324,7 @@ var WsClient = class {
|
|
|
45010
45324
|
}, 15e3);
|
|
45011
45325
|
timer.unref?.();
|
|
45012
45326
|
this.pending.set(envelope.id, {
|
|
45013
|
-
resolve: (value) =>
|
|
45327
|
+
resolve: (value) => resolve12(value),
|
|
45014
45328
|
timer
|
|
45015
45329
|
});
|
|
45016
45330
|
this.socket.send(JSON.stringify(envelope));
|
|
@@ -45064,8 +45378,8 @@ var WsClient = class {
|
|
|
45064
45378
|
/** Create the physic folder at an absolute path if missing (best-effort). */
|
|
45065
45379
|
ensurePhysicFolderPath(folder) {
|
|
45066
45380
|
try {
|
|
45067
|
-
if (!
|
|
45068
|
-
|
|
45381
|
+
if (!existsSync18(folder)) {
|
|
45382
|
+
mkdirSync9(folder, { recursive: true });
|
|
45069
45383
|
this.bus.log(t("project.createdFolder", { folder }));
|
|
45070
45384
|
}
|
|
45071
45385
|
} catch (err) {
|
|
@@ -45094,13 +45408,16 @@ var WsClient = class {
|
|
|
45094
45408
|
projects: [],
|
|
45095
45409
|
version: CLI_VERSION,
|
|
45096
45410
|
physicPath,
|
|
45097
|
-
physicPathExists: physicPath ?
|
|
45411
|
+
physicPathExists: physicPath ? existsSync18(physicPath) : void 0,
|
|
45098
45412
|
// Real coarse state (ADR-0152): !paused and a recent tick (was hard-coded false).
|
|
45099
45413
|
autonomousRunning: physicPath ? isAutonomousRunning(physicPath, this.context.profileDir) : false,
|
|
45100
45414
|
// The machine-user AI-run limit (0 = unlimited) + profile count so a dispatch can resolve the
|
|
45101
45415
|
// effective timeout and derive the SSE reply windows server-side (ADR-0256).
|
|
45102
45416
|
aiRunTimeoutSec: config.aiRunTimeoutSec ?? 0,
|
|
45103
|
-
aiProfileCount
|
|
45417
|
+
aiProfileCount,
|
|
45418
|
+
// The configured AI accounts (emails when readable) — admin-only visibility of which AI
|
|
45419
|
+
// account a (rented) machine runs on + the shared-account check (ADR-0354).
|
|
45420
|
+
aiAccounts: aiAccountLabels(config)
|
|
45104
45421
|
});
|
|
45105
45422
|
}
|
|
45106
45423
|
/** Heartbeat every 30s: ping + periodically re-report machine.status. */
|
|
@@ -45350,7 +45667,7 @@ var WsClient = class {
|
|
|
45350
45667
|
};
|
|
45351
45668
|
|
|
45352
45669
|
// src/core/instance-lock.ts
|
|
45353
|
-
import { readFileSync as readFileSync17, rmSync as
|
|
45670
|
+
import { readFileSync as readFileSync17, rmSync as rmSync9, writeFileSync as writeFileSync9 } from "fs";
|
|
45354
45671
|
import { join as join33 } from "path";
|
|
45355
45672
|
function lockPath(profileDir2) {
|
|
45356
45673
|
return join33(profileDir2, "start.lock");
|
|
@@ -45379,7 +45696,7 @@ function acquireInstanceLock(profileDir2) {
|
|
|
45379
45696
|
function releaseInstanceLock(profileDir2) {
|
|
45380
45697
|
try {
|
|
45381
45698
|
const pid = Number.parseInt(readFileSync17(lockPath(profileDir2), "utf8").trim(), 10);
|
|
45382
|
-
if (pid === process.pid)
|
|
45699
|
+
if (pid === process.pid) rmSync9(lockPath(profileDir2), { force: true });
|
|
45383
45700
|
} catch {
|
|
45384
45701
|
}
|
|
45385
45702
|
}
|
|
@@ -45674,7 +45991,7 @@ var SessionBus = class {
|
|
|
45674
45991
|
|
|
45675
45992
|
// src/core/control-server.ts
|
|
45676
45993
|
import { createServer } from "net";
|
|
45677
|
-
import { chmodSync as
|
|
45994
|
+
import { chmodSync as chmodSync5, rmSync as rmSync10 } from "fs";
|
|
45678
45995
|
import { join as join35 } from "path";
|
|
45679
45996
|
|
|
45680
45997
|
// src/core/control-protocol.ts
|
|
@@ -45704,7 +46021,7 @@ function createFrameParser() {
|
|
|
45704
46021
|
}
|
|
45705
46022
|
|
|
45706
46023
|
// src/core/control-token.ts
|
|
45707
|
-
import { chmodSync as
|
|
46024
|
+
import { chmodSync as chmodSync4, readFileSync as readFileSync18, writeFileSync as writeFileSync10 } from "fs";
|
|
45708
46025
|
import { randomBytes as randomBytes3, timingSafeEqual } from "crypto";
|
|
45709
46026
|
import { join as join34 } from "path";
|
|
45710
46027
|
function controlTokenPath(profileDir2) {
|
|
@@ -45715,7 +46032,7 @@ function writeControlToken(profileDir2) {
|
|
|
45715
46032
|
const path = controlTokenPath(profileDir2);
|
|
45716
46033
|
writeFileSync10(path, token, { mode: 384 });
|
|
45717
46034
|
try {
|
|
45718
|
-
|
|
46035
|
+
chmodSync4(path, 384);
|
|
45719
46036
|
} catch {
|
|
45720
46037
|
}
|
|
45721
46038
|
return token;
|
|
@@ -45739,7 +46056,7 @@ function tokenMatches(expected, given) {
|
|
|
45739
46056
|
function startControlServer(bus, profileDir2, info) {
|
|
45740
46057
|
const socketPath = join35(profileDir2, CONTROL_SOCKET_FILE);
|
|
45741
46058
|
try {
|
|
45742
|
-
|
|
46059
|
+
rmSync10(socketPath, { force: true });
|
|
45743
46060
|
} catch {
|
|
45744
46061
|
}
|
|
45745
46062
|
const clients = /* @__PURE__ */ new Set();
|
|
@@ -45792,8 +46109,10 @@ function startControlServer(bus, profileDir2, info) {
|
|
|
45792
46109
|
if (!authed) {
|
|
45793
46110
|
if (frame.t === "auth" && tokenMatches(token, frame.token)) {
|
|
45794
46111
|
authed = true;
|
|
45795
|
-
|
|
45796
|
-
|
|
46112
|
+
if (!frame.rpc) {
|
|
46113
|
+
clients.add(sock);
|
|
46114
|
+
sendSnapshot(sock);
|
|
46115
|
+
}
|
|
45797
46116
|
} else {
|
|
45798
46117
|
send(sock, { t: "authError" });
|
|
45799
46118
|
sock.destroy();
|
|
@@ -45803,6 +46122,9 @@ function startControlServer(bus, profileDir2, info) {
|
|
|
45803
46122
|
if (frame.t === "submit") bus.submitLocal(frame.input);
|
|
45804
46123
|
else if (frame.t === "reconnect") bus.requestReconnect();
|
|
45805
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
|
+
}
|
|
45806
46128
|
}
|
|
45807
46129
|
});
|
|
45808
46130
|
const drop = () => {
|
|
@@ -45815,7 +46137,7 @@ function startControlServer(bus, profileDir2, info) {
|
|
|
45815
46137
|
});
|
|
45816
46138
|
server.listen(socketPath, () => {
|
|
45817
46139
|
try {
|
|
45818
|
-
|
|
46140
|
+
chmodSync5(socketPath, 384);
|
|
45819
46141
|
} catch {
|
|
45820
46142
|
}
|
|
45821
46143
|
});
|
|
@@ -45825,8 +46147,8 @@ function startControlServer(bus, profileDir2, info) {
|
|
|
45825
46147
|
clients.clear();
|
|
45826
46148
|
server.close();
|
|
45827
46149
|
try {
|
|
45828
|
-
|
|
45829
|
-
|
|
46150
|
+
rmSync10(socketPath, { force: true });
|
|
46151
|
+
rmSync10(controlTokenPath(profileDir2), { force: true });
|
|
45830
46152
|
} catch {
|
|
45831
46153
|
}
|
|
45832
46154
|
};
|
|
@@ -50908,24 +51230,24 @@ var App = class extends import_react9.PureComponent {
|
|
|
50908
51230
|
})
|
|
50909
51231
|
}));
|
|
50910
51232
|
};
|
|
50911
|
-
findNextFocusable = (
|
|
50912
|
-
const activeIndex =
|
|
50913
|
-
return focusable.id ===
|
|
51233
|
+
findNextFocusable = (state2) => {
|
|
51234
|
+
const activeIndex = state2.focusables.findIndex((focusable) => {
|
|
51235
|
+
return focusable.id === state2.activeFocusId;
|
|
50914
51236
|
});
|
|
50915
|
-
for (let index = activeIndex + 1; index <
|
|
50916
|
-
const focusable =
|
|
51237
|
+
for (let index = activeIndex + 1; index < state2.focusables.length; index++) {
|
|
51238
|
+
const focusable = state2.focusables[index];
|
|
50917
51239
|
if (focusable?.isActive) {
|
|
50918
51240
|
return focusable.id;
|
|
50919
51241
|
}
|
|
50920
51242
|
}
|
|
50921
51243
|
return void 0;
|
|
50922
51244
|
};
|
|
50923
|
-
findPreviousFocusable = (
|
|
50924
|
-
const activeIndex =
|
|
50925
|
-
return focusable.id ===
|
|
51245
|
+
findPreviousFocusable = (state2) => {
|
|
51246
|
+
const activeIndex = state2.focusables.findIndex((focusable) => {
|
|
51247
|
+
return focusable.id === state2.activeFocusId;
|
|
50926
51248
|
});
|
|
50927
51249
|
for (let index = activeIndex - 1; index >= 0; index--) {
|
|
50928
|
-
const focusable =
|
|
51250
|
+
const focusable = state2.focusables[index];
|
|
50929
51251
|
if (focusable?.isActive) {
|
|
50930
51252
|
return focusable.id;
|
|
50931
51253
|
}
|
|
@@ -51121,8 +51443,8 @@ var Ink = class {
|
|
|
51121
51443
|
}
|
|
51122
51444
|
}
|
|
51123
51445
|
async waitUntilExit() {
|
|
51124
|
-
this.exitPromise ||= new Promise((
|
|
51125
|
-
this.resolveExitPromise =
|
|
51446
|
+
this.exitPromise ||= new Promise((resolve12, reject) => {
|
|
51447
|
+
this.resolveExitPromise = resolve12;
|
|
51126
51448
|
this.rejectExitPromise = reject;
|
|
51127
51449
|
});
|
|
51128
51450
|
return this.exitPromise;
|
|
@@ -51489,7 +51811,7 @@ var import_react21 = __toESM(require_react(), 1);
|
|
|
51489
51811
|
var import_react26 = __toESM(require_react(), 1);
|
|
51490
51812
|
|
|
51491
51813
|
// src/core/input-history.ts
|
|
51492
|
-
import { existsSync as
|
|
51814
|
+
import { existsSync as existsSync20, readFileSync as readFileSync20, writeFileSync as writeFileSync11 } from "fs";
|
|
51493
51815
|
import { join as join36 } from "path";
|
|
51494
51816
|
var MAX_HISTORY = 200;
|
|
51495
51817
|
function historyPath(profileDir2) {
|
|
@@ -51497,7 +51819,7 @@ function historyPath(profileDir2) {
|
|
|
51497
51819
|
}
|
|
51498
51820
|
function loadInputHistory(profileDir2) {
|
|
51499
51821
|
const path = historyPath(profileDir2);
|
|
51500
|
-
if (!
|
|
51822
|
+
if (!existsSync20(path)) return [];
|
|
51501
51823
|
try {
|
|
51502
51824
|
const data = JSON.parse(readFileSync20(path, "utf8"));
|
|
51503
51825
|
return Array.isArray(data) ? data.filter((x) => typeof x === "string") : [];
|
|
@@ -52175,7 +52497,7 @@ async function runStart(profileDir2, profileName) {
|
|
|
52175
52497
|
logger.error("ws.run.crash", { message: e.message, stack: e.stack });
|
|
52176
52498
|
if (client.isStopped) return;
|
|
52177
52499
|
bus.log(t("start.recovered", { message: e.message }), "error");
|
|
52178
|
-
await new Promise((
|
|
52500
|
+
await new Promise((resolve12) => setTimeout(resolve12, 1e3));
|
|
52179
52501
|
}
|
|
52180
52502
|
}
|
|
52181
52503
|
};
|
|
@@ -52210,6 +52532,7 @@ async function runStart(profileDir2, profileName) {
|
|
|
52210
52532
|
}
|
|
52211
52533
|
} finally {
|
|
52212
52534
|
stopIdleClear?.();
|
|
52535
|
+
await revokeAll().catch(() => void 0);
|
|
52213
52536
|
stopControl?.();
|
|
52214
52537
|
release();
|
|
52215
52538
|
}
|
|
@@ -52221,7 +52544,7 @@ import { join as join38 } from "path";
|
|
|
52221
52544
|
// src/core/control-client.ts
|
|
52222
52545
|
import { connect as connect2 } from "net";
|
|
52223
52546
|
function connectControl(socketPath, bus, token) {
|
|
52224
|
-
return new Promise((
|
|
52547
|
+
return new Promise((resolve12, reject) => {
|
|
52225
52548
|
const socket = connect2(socketPath);
|
|
52226
52549
|
socket.setEncoding("utf8");
|
|
52227
52550
|
let ready = false;
|
|
@@ -52274,7 +52597,7 @@ function connectControl(socketPath, bus, token) {
|
|
|
52274
52597
|
bus.setTokens(frame.tokens);
|
|
52275
52598
|
for (const entry of frame.transcript) bus.pushEntry(entry);
|
|
52276
52599
|
ready = true;
|
|
52277
|
-
|
|
52600
|
+
resolve12({ info: frame.info, close });
|
|
52278
52601
|
break;
|
|
52279
52602
|
case "transcript":
|
|
52280
52603
|
bus.pushEntry(frame.entry);
|
|
@@ -52362,7 +52685,7 @@ async function runAutoRun(profileDir2, profileName) {
|
|
|
52362
52685
|
initI18n(readProfileConfig(profileDir2).locale);
|
|
52363
52686
|
const socketPath = join39(profileDir2, CONTROL_SOCKET_FILE);
|
|
52364
52687
|
const token = readControlToken(profileDir2);
|
|
52365
|
-
const code = await new Promise((
|
|
52688
|
+
const code = await new Promise((resolve12) => {
|
|
52366
52689
|
let settled = false;
|
|
52367
52690
|
const done = (c) => {
|
|
52368
52691
|
if (settled) return;
|
|
@@ -52372,7 +52695,7 @@ async function runAutoRun(profileDir2, profileName) {
|
|
|
52372
52695
|
socket.destroy();
|
|
52373
52696
|
} catch {
|
|
52374
52697
|
}
|
|
52375
|
-
|
|
52698
|
+
resolve12(c);
|
|
52376
52699
|
};
|
|
52377
52700
|
const socket = connect3(socketPath);
|
|
52378
52701
|
socket.setEncoding("utf8");
|
|
@@ -52415,21 +52738,91 @@ async function runAutoRun(profileDir2, profileName) {
|
|
|
52415
52738
|
process.exitCode = code;
|
|
52416
52739
|
}
|
|
52417
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
|
+
|
|
52418
52811
|
// src/commands/ai-login.ts
|
|
52419
52812
|
import { spawn as spawn12 } from "child_process";
|
|
52420
|
-
import { mkdirSync as
|
|
52813
|
+
import { mkdirSync as mkdirSync10 } from "fs";
|
|
52421
52814
|
import { basename as basename8 } from "path";
|
|
52422
52815
|
function runProviderLogin(cmd, envVar, dir2) {
|
|
52423
|
-
return new Promise((
|
|
52816
|
+
return new Promise((resolve12) => {
|
|
52424
52817
|
const child = spawn12(cmd, [], {
|
|
52425
52818
|
stdio: "inherit",
|
|
52426
52819
|
env: { ...process.env, [envVar]: dir2 }
|
|
52427
52820
|
});
|
|
52428
52821
|
child.on("error", (err) => {
|
|
52429
52822
|
console.error(t("aiLogin.launchFailed", { cmd, message: err.message }));
|
|
52430
|
-
|
|
52823
|
+
resolve12(1);
|
|
52431
52824
|
});
|
|
52432
|
-
child.on("exit", (code) =>
|
|
52825
|
+
child.on("exit", (code) => resolve12(code ?? 0));
|
|
52433
52826
|
});
|
|
52434
52827
|
}
|
|
52435
52828
|
async function runAiLogin(profileDir2, profileName, aiFilter) {
|
|
@@ -52454,7 +52847,7 @@ async function runAiLogin(profileDir2, profileName, aiFilter) {
|
|
|
52454
52847
|
continue;
|
|
52455
52848
|
}
|
|
52456
52849
|
const dir2 = resolveHomePath(cred.profile);
|
|
52457
|
-
|
|
52850
|
+
mkdirSync10(dir2, { recursive: true });
|
|
52458
52851
|
console.log(t("aiLogin.loggingIn", { provider: cred.provider, profile: cred.profile, envVar, dir: dir2 }));
|
|
52459
52852
|
console.log(t("aiLogin.completeSignIn"));
|
|
52460
52853
|
const code = await runProviderLogin(cred.provider, envVar, dir2);
|
|
@@ -52466,7 +52859,7 @@ async function runAiLogin(profileDir2, profileName, aiFilter) {
|
|
|
52466
52859
|
}
|
|
52467
52860
|
|
|
52468
52861
|
// src/commands/unlink.ts
|
|
52469
|
-
import { rmSync as
|
|
52862
|
+
import { rmSync as rmSync11 } from "fs";
|
|
52470
52863
|
async function runUnlink(profileDir2, profileName) {
|
|
52471
52864
|
initI18n(readProfileConfig(profileDir2).locale);
|
|
52472
52865
|
const cred = readCredential(profileDir2, profileName);
|
|
@@ -52480,7 +52873,7 @@ async function runUnlink(profileDir2, profileName) {
|
|
|
52480
52873
|
} catch (err) {
|
|
52481
52874
|
console.log(t("unlink.revokeFailed", { message: err.message }));
|
|
52482
52875
|
}
|
|
52483
|
-
|
|
52876
|
+
rmSync11(profileDir2, { recursive: true, force: true });
|
|
52484
52877
|
clearDefaultProfileIf(profileName);
|
|
52485
52878
|
console.log(t("unlink.deleted"));
|
|
52486
52879
|
}
|
|
@@ -52569,10 +52962,10 @@ function Picker({
|
|
|
52569
52962
|
] });
|
|
52570
52963
|
}
|
|
52571
52964
|
function selectFromList(items, title) {
|
|
52572
|
-
return new Promise((
|
|
52965
|
+
return new Promise((resolve12) => {
|
|
52573
52966
|
let chosen = null;
|
|
52574
52967
|
const app = render_default(/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Picker, { items, title, onDone: (v2) => chosen = v2 }));
|
|
52575
|
-
void app.waitUntilExit().then(() =>
|
|
52968
|
+
void app.waitUntilExit().then(() => resolve12(chosen));
|
|
52576
52969
|
});
|
|
52577
52970
|
}
|
|
52578
52971
|
|
|
@@ -52639,6 +53032,14 @@ async function main() {
|
|
|
52639
53032
|
await runAttach(profileDir(name), name);
|
|
52640
53033
|
break;
|
|
52641
53034
|
}
|
|
53035
|
+
case "git-credential": {
|
|
53036
|
+
await runGitCredential(rest[0]);
|
|
53037
|
+
break;
|
|
53038
|
+
}
|
|
53039
|
+
case "git-token": {
|
|
53040
|
+
await runGitToken();
|
|
53041
|
+
break;
|
|
53042
|
+
}
|
|
52642
53043
|
case "auto-run": {
|
|
52643
53044
|
const name = await pickLinkedProfile(explicitProfile, "auto-run");
|
|
52644
53045
|
if (!name) {
|