@4pm/cli 1.4.12 → 1.5.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 +316 -60
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -28312,14 +28312,45 @@ function defaultProfileConfig() {
|
|
|
28312
28312
|
aiFailoverMode: "remember",
|
|
28313
28313
|
autoUpdate: true,
|
|
28314
28314
|
commandHistoryUploadMinutes: 10,
|
|
28315
|
-
autoClearIdleMinutes: 10
|
|
28315
|
+
autoClearIdleMinutes: 10,
|
|
28316
|
+
// Terminate a single AI run after 5 min by default (ADR-0243) so a hung/looping AI CLI
|
|
28317
|
+
// (e.g. a heavy spec review/compose) can't spin the dispatcher forever; 0 = no limit.
|
|
28318
|
+
aiRunTimeoutSec: 300,
|
|
28319
|
+
// Shared AI memory (ADR-0245) — off by default (opt-in; costs a compaction call per turn).
|
|
28320
|
+
aiMemoryEnabled: false,
|
|
28321
|
+
aiMemoryBudgetChars: 6e3
|
|
28316
28322
|
};
|
|
28317
28323
|
}
|
|
28324
|
+
function resolveMemoryConfig(config) {
|
|
28325
|
+
const mode = config.projectAiMemoryMode ?? "inherit";
|
|
28326
|
+
const enabled = mode === "on" ? true : mode === "off" ? false : config.aiMemoryEnabled ?? false;
|
|
28327
|
+
const projectBudget = config.projectAiMemoryBudgetChars ?? 0;
|
|
28328
|
+
const budgetChars = projectBudget > 0 ? projectBudget : config.aiMemoryBudgetChars ?? 6e3;
|
|
28329
|
+
return { enabled, budgetChars };
|
|
28330
|
+
}
|
|
28331
|
+
function resolveIdleAutoClearMinutes(config) {
|
|
28332
|
+
const project = config.projectAutoClearIdleMinutes ?? 0;
|
|
28333
|
+
return project > 0 ? project : config.autoClearIdleMinutes ?? 10;
|
|
28334
|
+
}
|
|
28318
28335
|
function ensureProfileConfig(dir2) {
|
|
28319
28336
|
if (existsSync(join(dir2, "config.json"))) return false;
|
|
28320
28337
|
writeProfileConfig(dir2, defaultProfileConfig());
|
|
28321
28338
|
return true;
|
|
28322
28339
|
}
|
|
28340
|
+
function backfillProfileConfig(dir2) {
|
|
28341
|
+
if (!existsSync(join(dir2, "config.json"))) return false;
|
|
28342
|
+
const current = readProfileConfig(dir2);
|
|
28343
|
+
const next = { ...current };
|
|
28344
|
+
let changed = false;
|
|
28345
|
+
for (const [key, value] of Object.entries(defaultProfileConfig())) {
|
|
28346
|
+
if (next[key] === void 0) {
|
|
28347
|
+
next[key] = value;
|
|
28348
|
+
changed = true;
|
|
28349
|
+
}
|
|
28350
|
+
}
|
|
28351
|
+
if (changed) overwriteProfileConfig(dir2, next);
|
|
28352
|
+
return changed;
|
|
28353
|
+
}
|
|
28323
28354
|
function readProfileConfig(dir2) {
|
|
28324
28355
|
const path = join(dir2, "config.json");
|
|
28325
28356
|
if (!existsSync(path)) return {};
|
|
@@ -28557,7 +28588,7 @@ async function runLink(serverUrl, explicitProfile, token = null) {
|
|
|
28557
28588
|
}
|
|
28558
28589
|
|
|
28559
28590
|
// src/commands/start.ts
|
|
28560
|
-
import { spawn as
|
|
28591
|
+
import { spawn as spawn10 } from "child_process";
|
|
28561
28592
|
import { join as join29 } from "path";
|
|
28562
28593
|
|
|
28563
28594
|
// src/core/update.ts
|
|
@@ -28615,6 +28646,9 @@ function readInstalledVersion() {
|
|
|
28615
28646
|
}
|
|
28616
28647
|
}
|
|
28617
28648
|
function updateViaNpm(version) {
|
|
28649
|
+
if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(version)) {
|
|
28650
|
+
throw new Error(`Refusing to update: invalid version string "${version}".`);
|
|
28651
|
+
}
|
|
28618
28652
|
execSync2(`npm i -g @4pm/cli@${version}`, { stdio: "inherit" });
|
|
28619
28653
|
}
|
|
28620
28654
|
async function updateViaDownload(tarballUrl, checksum, signature) {
|
|
@@ -33699,7 +33733,11 @@ var WsChannels = {
|
|
|
33699
33733
|
SUPPORT_ANSWER: "support.answer",
|
|
33700
33734
|
// AI knowledge distill (ADR-0190): server → project cli request–reply. The worker runs the AI CLI
|
|
33701
33735
|
// in the project's working dir with a distillation prompt and replies with the composed markdown.
|
|
33702
|
-
KNOWLEDGE_COMPOSE: "knowledge.compose"
|
|
33736
|
+
KNOWLEDGE_COMPOSE: "knowledge.compose",
|
|
33737
|
+
// Shared AI memory (ADR-0245): cli → server one-way write-back of the compacted rolling memory for
|
|
33738
|
+
// the project it serves (persisted per project × machine-user link). The read direction rides
|
|
33739
|
+
// `ws_token` (seeds the cli cache on connect); this channel only carries updates.
|
|
33740
|
+
MEMORY_UPDATE: "memory.update"
|
|
33703
33741
|
};
|
|
33704
33742
|
|
|
33705
33743
|
// vendor/@4pm/ws/crypto.ts
|
|
@@ -33973,7 +34011,8 @@ var UpdateScheduler = class {
|
|
|
33973
34011
|
|
|
33974
34012
|
// src/core/executor.ts
|
|
33975
34013
|
import { spawn as spawn2 } from "child_process";
|
|
33976
|
-
|
|
34014
|
+
var KILL_GRACE_MS = 5e3;
|
|
34015
|
+
async function runCommand(dispatch, emit, opts) {
|
|
33977
34016
|
let seq = 0;
|
|
33978
34017
|
const child = spawn2(dispatch.cmd, dispatch.args ?? [], {
|
|
33979
34018
|
cwd: dispatch.path,
|
|
@@ -33981,6 +34020,27 @@ async function runCommand(dispatch, emit) {
|
|
|
33981
34020
|
shell: false
|
|
33982
34021
|
});
|
|
33983
34022
|
child.stdin?.end();
|
|
34023
|
+
const timeoutMs = opts?.timeoutMs && opts.timeoutMs > 0 ? opts.timeoutMs : 0;
|
|
34024
|
+
let timedOut = false;
|
|
34025
|
+
let hardKillTimer = null;
|
|
34026
|
+
const timeoutTimer = timeoutMs > 0 ? setTimeout(() => {
|
|
34027
|
+
timedOut = true;
|
|
34028
|
+
emit({
|
|
34029
|
+
commandId: dispatch.commandId,
|
|
34030
|
+
seq: seq++,
|
|
34031
|
+
chunk: `
|
|
34032
|
+
[4PM] AI run exceeded the ${Math.round(timeoutMs / 1e3)}s time limit \u2014 terminating.
|
|
34033
|
+
`
|
|
34034
|
+
});
|
|
34035
|
+
child.kill("SIGTERM");
|
|
34036
|
+
hardKillTimer = setTimeout(() => child.kill("SIGKILL"), KILL_GRACE_MS);
|
|
34037
|
+
hardKillTimer.unref?.();
|
|
34038
|
+
}, timeoutMs) : null;
|
|
34039
|
+
timeoutTimer?.unref?.();
|
|
34040
|
+
const clearTimers = () => {
|
|
34041
|
+
if (timeoutTimer) clearTimeout(timeoutTimer);
|
|
34042
|
+
if (hardKillTimer) clearTimeout(hardKillTimer);
|
|
34043
|
+
};
|
|
33984
34044
|
const batcher = new OutputBatcher({
|
|
33985
34045
|
onFlush: (chunk, truncated) => {
|
|
33986
34046
|
emit({ commandId: dispatch.commandId, seq: seq++, chunk, truncated });
|
|
@@ -33999,17 +34059,21 @@ async function runCommand(dispatch, emit) {
|
|
|
33999
34059
|
child.stderr?.on("data", (data) => batcher.push(data.toString("utf8")));
|
|
34000
34060
|
await new Promise((resolve8) => {
|
|
34001
34061
|
child.on("close", (exitCode) => {
|
|
34062
|
+
clearTimers();
|
|
34002
34063
|
batcher.flush();
|
|
34003
34064
|
emit({
|
|
34004
34065
|
commandId: dispatch.commandId,
|
|
34005
34066
|
seq: seq++,
|
|
34006
34067
|
chunk: "",
|
|
34007
34068
|
done: true,
|
|
34008
|
-
|
|
34069
|
+
// 124 (the conventional timeout code) when we killed it for exceeding the limit, so the
|
|
34070
|
+
// server settles the command as failed and the web surfaces the note instead of spinning.
|
|
34071
|
+
exitCode: timedOut ? 124 : exitCode ?? -1
|
|
34009
34072
|
});
|
|
34010
34073
|
resolve8();
|
|
34011
34074
|
});
|
|
34012
34075
|
child.on("error", (err) => {
|
|
34076
|
+
clearTimers();
|
|
34013
34077
|
emit({
|
|
34014
34078
|
commandId: dispatch.commandId,
|
|
34015
34079
|
seq: seq++,
|
|
@@ -34052,7 +34116,9 @@ function createAiStreamParser(cli) {
|
|
|
34052
34116
|
const isJson = isClaude || isCodex;
|
|
34053
34117
|
let buffer = "";
|
|
34054
34118
|
const acc = { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 };
|
|
34119
|
+
let sessionId = "";
|
|
34055
34120
|
function handleClaude(ev) {
|
|
34121
|
+
if (typeof ev.session_id === "string" && ev.session_id) sessionId = ev.session_id;
|
|
34056
34122
|
if (ev.type === "result" && ev.usage) {
|
|
34057
34123
|
acc.input = ev.usage.input_tokens ?? 0;
|
|
34058
34124
|
acc.output = ev.usage.output_tokens ?? 0;
|
|
@@ -34118,7 +34184,8 @@ function createAiStreamParser(cli) {
|
|
|
34118
34184
|
const handled = handleLine(line);
|
|
34119
34185
|
return handled === null ? line : handled;
|
|
34120
34186
|
},
|
|
34121
|
-
usage: () => ({ ...acc })
|
|
34187
|
+
usage: () => ({ ...acc }),
|
|
34188
|
+
sessionId: () => sessionId
|
|
34122
34189
|
};
|
|
34123
34190
|
}
|
|
34124
34191
|
function estimateTokens(text) {
|
|
@@ -34136,15 +34203,15 @@ function isSessionLimit(text) {
|
|
|
34136
34203
|
text
|
|
34137
34204
|
);
|
|
34138
34205
|
}
|
|
34139
|
-
function runAttempt(commandId, cmd, args, cwd2, env3, onChunk) {
|
|
34206
|
+
function runAttempt(commandId, cmd, args, cwd2, env3, onChunk, timeoutMs) {
|
|
34140
34207
|
return new Promise((resolve8) => {
|
|
34141
34208
|
void runCommand({ commandId, cmd, args, path: cwd2, env: env3 }, (out) => {
|
|
34142
34209
|
if (out.chunk) onChunk(out.chunk);
|
|
34143
34210
|
if (out.done) resolve8(out.exitCode ?? -1);
|
|
34144
|
-
});
|
|
34211
|
+
}, timeoutMs > 0 ? { timeoutMs } : void 0);
|
|
34145
34212
|
});
|
|
34146
34213
|
}
|
|
34147
|
-
async function runAiFailover(plan, commandId, cwd2, handlers) {
|
|
34214
|
+
async function runAiFailover(plan, commandId, cwd2, handlers, timeoutMs = 0) {
|
|
34148
34215
|
const total = plan.attempts.length;
|
|
34149
34216
|
let finalExit = -1;
|
|
34150
34217
|
let lastCmd = plan.attempts[0]?.cmd ?? "claude";
|
|
@@ -34162,7 +34229,7 @@ async function runAiFailover(plan, commandId, cwd2, handlers) {
|
|
|
34162
34229
|
handlers.onChunk(text);
|
|
34163
34230
|
}
|
|
34164
34231
|
};
|
|
34165
|
-
finalExit = await runAttempt(commandId, attempt.cmd, attempt.args, cwd2, attempt.env, emit);
|
|
34232
|
+
finalExit = await runAttempt(commandId, attempt.cmd, attempt.args, cwd2, attempt.env, emit, timeoutMs);
|
|
34166
34233
|
const tail = parser.flush();
|
|
34167
34234
|
if (tail) {
|
|
34168
34235
|
captured += tail;
|
|
@@ -34173,14 +34240,21 @@ async function runAiFailover(plan, commandId, cwd2, handlers) {
|
|
|
34173
34240
|
const usage = parser.usage();
|
|
34174
34241
|
if (usage.tokens === 0) usage.tokens = estimateTokens(captured);
|
|
34175
34242
|
reportToolResult(attempt.cmd, true);
|
|
34176
|
-
return {
|
|
34243
|
+
return {
|
|
34244
|
+
exitCode: 0,
|
|
34245
|
+
workedDir: attempt.dir,
|
|
34246
|
+
workedKey: attempt.key,
|
|
34247
|
+
workedCmd: attempt.cmd,
|
|
34248
|
+
usage,
|
|
34249
|
+
sessionId: parser.sessionId()
|
|
34250
|
+
};
|
|
34177
34251
|
}
|
|
34178
|
-
const reason = isAuthFailure(captured) ? "auth" : isSessionLimit(captured) ? "limit" :
|
|
34179
|
-
if (
|
|
34252
|
+
const reason = isAuthFailure(captured) ? "auth" : isSessionLimit(captured) ? "limit" : "other";
|
|
34253
|
+
if (i === total - 1) break;
|
|
34180
34254
|
handlers.onAttemptFail(attempt.label, reason);
|
|
34181
34255
|
}
|
|
34182
34256
|
reportToolResult(lastCmd, false, summarizeAiFailure(lastCaptured, finalExit));
|
|
34183
|
-
return { exitCode: finalExit, workedDir: null, workedKey: null, workedCmd: null, usage: { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 } };
|
|
34257
|
+
return { exitCode: finalExit, workedDir: null, workedKey: null, workedCmd: null, usage: { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 }, sessionId: "" };
|
|
34184
34258
|
}
|
|
34185
34259
|
function summarizeAiFailure(captured, exitCode) {
|
|
34186
34260
|
if (isAuthFailure(captured)) return "Not logged in";
|
|
@@ -34298,11 +34372,12 @@ function folderScopeGuard(folder, prompt) {
|
|
|
34298
34372
|
prompt
|
|
34299
34373
|
].join("\n");
|
|
34300
34374
|
}
|
|
34301
|
-
function buildRunArgs(profile, cmd, prompt) {
|
|
34375
|
+
function buildRunArgs(profile, cmd, prompt, resumeId) {
|
|
34302
34376
|
const extras = profile.args ?? [];
|
|
34303
34377
|
const model = profile.model?.trim();
|
|
34304
34378
|
const modelArgs = model ? ["--model", model] : [];
|
|
34305
|
-
|
|
34379
|
+
const resumeArgs = resumeId && cmd.includes("claude") ? ["--resume", resumeId] : [];
|
|
34380
|
+
return [...requiredArgs(cmd), ...resumeArgs, ...extras, ...modelArgs, prompt];
|
|
34306
34381
|
}
|
|
34307
34382
|
function claudeHomeDirs(config) {
|
|
34308
34383
|
const dirs = profileDirs(claudeProfilesOf(config));
|
|
@@ -34341,10 +34416,10 @@ function resolveClaudeProfiles(config, workingDir) {
|
|
|
34341
34416
|
if (idx > 0) profiles.unshift(profiles.splice(idx, 1)[0]);
|
|
34342
34417
|
return profiles;
|
|
34343
34418
|
}
|
|
34344
|
-
function planAiRun(prompt, config, hint = {}) {
|
|
34345
|
-
return isUnifiedConfig(config) ? planUnifiedRun(prompt, config, hint.credential ?? null) : planLegacyRun(prompt, config, hint.dir ?? null);
|
|
34419
|
+
function planAiRun(prompt, config, hint = {}, resume = /* @__PURE__ */ new Map()) {
|
|
34420
|
+
return isUnifiedConfig(config) ? planUnifiedRun(prompt, config, hint.credential ?? null, resume) : planLegacyRun(prompt, config, hint.dir ?? null, resume);
|
|
34346
34421
|
}
|
|
34347
|
-
function planUnifiedRun(prompt, config, workingCredential) {
|
|
34422
|
+
function planUnifiedRun(prompt, config, workingCredential, resume) {
|
|
34348
34423
|
const baseEnv = config.aiEnv ?? {};
|
|
34349
34424
|
const active = activeProvider(config);
|
|
34350
34425
|
const usable = config.aiProfiles.filter(isUsableCredential);
|
|
@@ -34371,12 +34446,12 @@ function planUnifiedRun(prompt, config, workingCredential) {
|
|
|
34371
34446
|
label: cred.label?.trim() || profileDisplayLabel(dir2),
|
|
34372
34447
|
dir: dir2,
|
|
34373
34448
|
key,
|
|
34374
|
-
args: buildRunArgs(cred, cmd, prompt),
|
|
34449
|
+
args: buildRunArgs(cred, cmd, prompt, resume.get(key)),
|
|
34375
34450
|
env: envVar ? { ...baseEnv, [envVar]: dir2 } : { ...baseEnv }
|
|
34376
34451
|
}));
|
|
34377
34452
|
return { cmd: attempts[0]?.cmd ?? DEFAULT_AI_CLI, attempts };
|
|
34378
34453
|
}
|
|
34379
|
-
function planLegacyRun(prompt, config, workingDir) {
|
|
34454
|
+
function planLegacyRun(prompt, config, workingDir, resume) {
|
|
34380
34455
|
const cmd = config.aiCli || DEFAULT_AI_CLI;
|
|
34381
34456
|
const baseEnv = config.aiEnv ?? {};
|
|
34382
34457
|
const envVar = profileEnvVar(cmd);
|
|
@@ -34393,14 +34468,17 @@ function planLegacyRun(prompt, config, workingDir) {
|
|
|
34393
34468
|
resolved.find((r) => r.dir === workingDir),
|
|
34394
34469
|
...resolved.filter((r) => r.dir !== workingDir)
|
|
34395
34470
|
] : resolved;
|
|
34396
|
-
const attempts = ordered.map(({ profile, dir: dir2 }) =>
|
|
34397
|
-
cmd,
|
|
34398
|
-
|
|
34399
|
-
|
|
34400
|
-
|
|
34401
|
-
|
|
34402
|
-
|
|
34403
|
-
|
|
34471
|
+
const attempts = ordered.map(({ profile, dir: dir2 }) => {
|
|
34472
|
+
const key = credentialKey(cmd, dir2);
|
|
34473
|
+
return {
|
|
34474
|
+
cmd,
|
|
34475
|
+
label: profileDisplayLabel(dir2),
|
|
34476
|
+
dir: dir2,
|
|
34477
|
+
key,
|
|
34478
|
+
args: buildRunArgs(profile, cmd, prompt, resume.get(key)),
|
|
34479
|
+
env: { ...baseEnv, [envVar]: dir2 }
|
|
34480
|
+
};
|
|
34481
|
+
});
|
|
34404
34482
|
return { cmd, attempts };
|
|
34405
34483
|
}
|
|
34406
34484
|
|
|
@@ -35075,7 +35153,7 @@ async function runClaudeWithFailover(prompt, ai) {
|
|
|
35075
35153
|
if (code === 0 && out.trim()) return { text: out.trim(), usage };
|
|
35076
35154
|
const combined = err || out;
|
|
35077
35155
|
lastReason = `exited ${code}: ${combined.slice(0, 500)}`;
|
|
35078
|
-
if (
|
|
35156
|
+
if (i === attempts.length - 1) break;
|
|
35079
35157
|
}
|
|
35080
35158
|
throw new Error(`claude ${lastReason}`);
|
|
35081
35159
|
}
|
|
@@ -35174,7 +35252,7 @@ async function runKnowledgeCompose(req, ai, physicRoot) {
|
|
|
35174
35252
|
}
|
|
35175
35253
|
const combined = err || out;
|
|
35176
35254
|
lastReason = `exited ${code}: ${combined.slice(0, 500)}`;
|
|
35177
|
-
if (
|
|
35255
|
+
if (i === attempts.length - 1) break;
|
|
35178
35256
|
} catch (e) {
|
|
35179
35257
|
lastReason = String(e);
|
|
35180
35258
|
break;
|
|
@@ -36177,6 +36255,9 @@ async function ragQuery(root, queryText, k = 8) {
|
|
|
36177
36255
|
}
|
|
36178
36256
|
}
|
|
36179
36257
|
async function ragInstall(root, model) {
|
|
36258
|
+
if (!/^[A-Za-z0-9._/-]+$/.test(model)) {
|
|
36259
|
+
return { ok: false, started: false, error: "invalid model name" };
|
|
36260
|
+
}
|
|
36180
36261
|
const py = await detectPython();
|
|
36181
36262
|
if (!py.found) return { ok: false, started: false, error: "python3 not found on the worker" };
|
|
36182
36263
|
if (existsSync10(join20(root, RAG_REL, ".installing"))) return { ok: true, started: true };
|
|
@@ -36587,10 +36668,93 @@ async function addProject(payload, profileDir2, onProgress) {
|
|
|
36587
36668
|
}
|
|
36588
36669
|
}
|
|
36589
36670
|
|
|
36671
|
+
// src/core/memory-compact.ts
|
|
36672
|
+
import { spawn as spawn8 } from "child_process";
|
|
36673
|
+
var COMPACT_TIMEOUT_MS = 6e4;
|
|
36674
|
+
function buildPrompt3(input) {
|
|
36675
|
+
return [
|
|
36676
|
+
"You maintain a COMPACT running memory of an ongoing assistant conversation so it can continue",
|
|
36677
|
+
"across sessions and accounts. Merge the latest exchange into the existing memory and return the",
|
|
36678
|
+
`UPDATED memory. Keep it UNDER ${input.budgetChars} characters. Use terse Markdown bullets grouped`,
|
|
36679
|
+
"as: Decisions, Constraints, Open tasks, Glossary. Keep durable facts; drop chit-chat and anything",
|
|
36680
|
+
"now obsolete. Output ONLY the updated memory text \u2014 no preamble, no code fences.",
|
|
36681
|
+
"",
|
|
36682
|
+
"=== EXISTING MEMORY ===",
|
|
36683
|
+
input.oldMemory || "(empty)",
|
|
36684
|
+
"",
|
|
36685
|
+
"=== LATEST USER PROMPT ===",
|
|
36686
|
+
input.prompt,
|
|
36687
|
+
"",
|
|
36688
|
+
"=== LATEST ASSISTANT ANSWER ===",
|
|
36689
|
+
input.answer
|
|
36690
|
+
].join("\n");
|
|
36691
|
+
}
|
|
36692
|
+
function runOnce2(cmd, profile, prompt, cwd2, extraEnv) {
|
|
36693
|
+
return new Promise((resolve8, reject) => {
|
|
36694
|
+
const args = ["-p", ...profile?.model ? ["--model", profile.model] : []];
|
|
36695
|
+
const env3 = {
|
|
36696
|
+
...process.env,
|
|
36697
|
+
...extraEnv,
|
|
36698
|
+
...profile ? { CLAUDE_CONFIG_DIR: profile.dir } : {}
|
|
36699
|
+
};
|
|
36700
|
+
const child = spawn8(cmd, args, { stdio: ["pipe", "pipe", "pipe"], cwd: cwd2, env: env3 });
|
|
36701
|
+
let out = "";
|
|
36702
|
+
let err = "";
|
|
36703
|
+
const timer = setTimeout(() => {
|
|
36704
|
+
child.kill("SIGKILL");
|
|
36705
|
+
reject(new Error("memory compaction timed out"));
|
|
36706
|
+
}, COMPACT_TIMEOUT_MS);
|
|
36707
|
+
timer.unref();
|
|
36708
|
+
child.stdout.on("data", (d) => out += d.toString());
|
|
36709
|
+
child.stderr.on("data", (d) => err += d.toString());
|
|
36710
|
+
child.on("error", (e) => {
|
|
36711
|
+
clearTimeout(timer);
|
|
36712
|
+
reject(e);
|
|
36713
|
+
});
|
|
36714
|
+
child.on("close", (code) => {
|
|
36715
|
+
clearTimeout(timer);
|
|
36716
|
+
resolve8({ code: code ?? -1, out, err });
|
|
36717
|
+
});
|
|
36718
|
+
child.stdin.write(prompt);
|
|
36719
|
+
child.stdin.end();
|
|
36720
|
+
});
|
|
36721
|
+
}
|
|
36722
|
+
async function runMemoryCompaction(ai, cwd2, input) {
|
|
36723
|
+
const prompt = buildPrompt3(input);
|
|
36724
|
+
const attempts = ai.profiles.length > 0 ? ai.profiles : [null];
|
|
36725
|
+
for (let i = 0; i < attempts.length; i++) {
|
|
36726
|
+
try {
|
|
36727
|
+
const { code, out } = await runOnce2(ai.cmd, attempts[i], prompt, cwd2, ai.env);
|
|
36728
|
+
if (code === 0 && out.trim()) {
|
|
36729
|
+
reportToolResult(ai.cmd, true);
|
|
36730
|
+
return out.trim().slice(0, input.budgetChars);
|
|
36731
|
+
}
|
|
36732
|
+
if (i === attempts.length - 1) break;
|
|
36733
|
+
} catch {
|
|
36734
|
+
break;
|
|
36735
|
+
}
|
|
36736
|
+
}
|
|
36737
|
+
return "";
|
|
36738
|
+
}
|
|
36739
|
+
|
|
36590
36740
|
// src/core/config-sync.ts
|
|
36591
36741
|
import { existsSync as existsSync11, readFileSync as readFileSync15 } from "fs";
|
|
36592
36742
|
import { join as join23 } from "path";
|
|
36593
|
-
var SERVER_MANAGED_KEYS = [
|
|
36743
|
+
var SERVER_MANAGED_KEYS = [
|
|
36744
|
+
"physicPath",
|
|
36745
|
+
"sessionSwitchPct",
|
|
36746
|
+
"perPromptTokenLimit",
|
|
36747
|
+
// Project AI-run timeout override mirror (ADR-0243) — the ws_token refreshes it; a Worker-config
|
|
36748
|
+
// edit must not fight it. NOTE: the machine-user's own `aiRunTimeoutSec` is NOT here (operator-editable).
|
|
36749
|
+
"projectAiRunTimeoutSec",
|
|
36750
|
+
// Project idle auto-clear override mirror (ADR-0244) — same rule; the machine-user's own
|
|
36751
|
+
// `autoClearIdleMinutes` is operator-editable and NOT listed here.
|
|
36752
|
+
"projectAutoClearIdleMinutes",
|
|
36753
|
+
// Project shared-AI-memory override mirrors (ADR-0245) — the machine-user's own `aiMemoryEnabled`
|
|
36754
|
+
// / `aiMemoryBudgetChars` are operator-editable and NOT listed here.
|
|
36755
|
+
"projectAiMemoryMode",
|
|
36756
|
+
"projectAiMemoryBudgetChars"
|
|
36757
|
+
];
|
|
36594
36758
|
function readConfigText(profileDir2) {
|
|
36595
36759
|
const path = join23(profileDir2, "config.json");
|
|
36596
36760
|
if (existsSync11(path)) {
|
|
@@ -36622,7 +36786,7 @@ function applyConfigText(profileDir2, text) {
|
|
|
36622
36786
|
}
|
|
36623
36787
|
|
|
36624
36788
|
// src/core/worker-tools.ts
|
|
36625
|
-
import { spawn as
|
|
36789
|
+
import { spawn as spawn9 } from "child_process";
|
|
36626
36790
|
var NPM_NAME = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
|
|
36627
36791
|
function run10(cmd, args, onLine, timeoutMs = 18e4) {
|
|
36628
36792
|
return new Promise((resolve8) => {
|
|
@@ -36653,7 +36817,7 @@ function run10(cmd, args, onLine, timeoutMs = 18e4) {
|
|
|
36653
36817
|
}
|
|
36654
36818
|
};
|
|
36655
36819
|
try {
|
|
36656
|
-
child =
|
|
36820
|
+
child = spawn9(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
36657
36821
|
child.stdout?.on("data", feed);
|
|
36658
36822
|
child.stderr?.on("data", feed);
|
|
36659
36823
|
child.on("exit", (code) => done(code ?? 0));
|
|
@@ -36741,6 +36905,7 @@ async function runWorkerToolOp(op, name, manager, onLine) {
|
|
|
36741
36905
|
}
|
|
36742
36906
|
|
|
36743
36907
|
// src/core/ws-client.ts
|
|
36908
|
+
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:";
|
|
36744
36909
|
var HEARTBEAT_MS = 3e4;
|
|
36745
36910
|
var USAGE_POLL_MS = 5 * 6e4;
|
|
36746
36911
|
var LOG_UPLOAD_MS = 10 * 6e4;
|
|
@@ -36780,6 +36945,7 @@ var WsClient = class {
|
|
|
36780
36945
|
(entry) => this.emitConsole({ kind: "update", entry: toDtoEntry(entry) })
|
|
36781
36946
|
);
|
|
36782
36947
|
context.bus.onClear(() => this.emitConsoleClear());
|
|
36948
|
+
context.bus.onClearSession(() => this.resetMemorySession());
|
|
36783
36949
|
}
|
|
36784
36950
|
context;
|
|
36785
36951
|
socket = null;
|
|
@@ -36805,6 +36971,13 @@ var WsClient = class {
|
|
|
36805
36971
|
* when true the cli prepends a folder-scope guard to every AI prompt. Fed by each ws_token.
|
|
36806
36972
|
*/
|
|
36807
36973
|
restrictToFolder = false;
|
|
36974
|
+
/**
|
|
36975
|
+
* Shared AI memory (ADR-0245) cache for the served project: the rolling compacted text (seeded by
|
|
36976
|
+
* `ws_token`, updated after each run) + the last native `session_id` per credential key (for
|
|
36977
|
+
* `--resume` on the same profile). Per (project × link) — this cli instance is one link.
|
|
36978
|
+
*/
|
|
36979
|
+
aiMemory = "";
|
|
36980
|
+
sessionIdByKey = /* @__PURE__ */ new Map();
|
|
36808
36981
|
stopped = false;
|
|
36809
36982
|
/** Resolver to wake the backoff sleep early (set while waiting) — for /reconnect. */
|
|
36810
36983
|
wakeReconnect = null;
|
|
@@ -37015,8 +37188,16 @@ var WsClient = class {
|
|
|
37015
37188
|
}
|
|
37016
37189
|
writeProfileConfig(this.context.profileDir, {
|
|
37017
37190
|
sessionSwitchPct: token.projectTokens?.sessionSwitchPct ?? 0,
|
|
37018
|
-
perPromptTokenLimit: token.projectTokens?.perPromptTokenLimit ?? 0
|
|
37191
|
+
perPromptTokenLimit: token.projectTokens?.perPromptTokenLimit ?? 0,
|
|
37192
|
+
// Project AI-run timeout override (ADR-0243) — 0 ⇒ inherit the machine-user setting.
|
|
37193
|
+
projectAiRunTimeoutSec: token.projectTokens?.aiRunTimeoutSec ?? 0,
|
|
37194
|
+
// Project idle auto-clear override (ADR-0244) — 0 ⇒ inherit the machine-user setting.
|
|
37195
|
+
projectAutoClearIdleMinutes: token.projectTokens?.autoClearIdleMinutes ?? 0,
|
|
37196
|
+
// Project shared-AI-memory override mirrors (ADR-0245).
|
|
37197
|
+
projectAiMemoryMode: token.aiMemory?.mode ?? "inherit",
|
|
37198
|
+
projectAiMemoryBudgetChars: token.aiMemory?.budgetChars ?? 0
|
|
37019
37199
|
});
|
|
37200
|
+
if (token.aiMemory && !this.aiMemory) this.aiMemory = token.aiMemory.text;
|
|
37020
37201
|
this.outboundReviewEnabled = token.outboundReview?.enabled === true;
|
|
37021
37202
|
this.restrictToFolder = token.aiRestrictToFolder === true;
|
|
37022
37203
|
applyGitAuth(token.gitAuth ?? null);
|
|
@@ -37636,6 +37817,10 @@ var WsClient = class {
|
|
|
37636
37817
|
*/
|
|
37637
37818
|
async runAiPrompt(prompt, commandId, origin) {
|
|
37638
37819
|
const config = readProfileConfig(this.context.profileDir);
|
|
37820
|
+
const projectTimeoutSec = config.projectAiRunTimeoutSec ?? 0;
|
|
37821
|
+
const machineTimeoutSec = config.aiRunTimeoutSec ?? 0;
|
|
37822
|
+
const timeoutSec = projectTimeoutSec > 0 ? projectTimeoutSec : machineTimeoutSec;
|
|
37823
|
+
const aiRunTimeoutMs = timeoutSec > 0 ? timeoutSec * 1e3 : 0;
|
|
37639
37824
|
const perPromptLimit = config.perPromptTokenLimit ?? 0;
|
|
37640
37825
|
if (perPromptLimit > 0) {
|
|
37641
37826
|
const estimated = estimateTokens(prompt);
|
|
@@ -37687,12 +37872,22 @@ var WsClient = class {
|
|
|
37687
37872
|
text: `session ${this.usageSnapshot?.session.utilizationPct ?? 0}% \u2265 ${config.sessionSwitchPct}% \u2014 switching to Claude profile "${labelFromCredentialKey(startCred)}"`
|
|
37688
37873
|
});
|
|
37689
37874
|
}
|
|
37690
|
-
const
|
|
37691
|
-
const
|
|
37692
|
-
|
|
37693
|
-
|
|
37694
|
-
|
|
37695
|
-
|
|
37875
|
+
const guardedPrompt = this.restrictToFolder && this.physicRoot ? folderScopeGuard(this.physicRoot, prompt) : prompt;
|
|
37876
|
+
const hint = unified ? { credential: startCred ?? workingCred } : { dir: startDir ?? workingDir };
|
|
37877
|
+
const memCfg = resolveMemoryConfig(config);
|
|
37878
|
+
const firstAttempt = planAiRun(guardedPrompt, config, hint).attempts[0];
|
|
37879
|
+
const resumeId = memCfg.enabled && firstAttempt?.key && firstAttempt.cmd === "claude" ? this.sessionIdByKey.get(firstAttempt.key) : void 0;
|
|
37880
|
+
let effectivePrompt = guardedPrompt;
|
|
37881
|
+
let resume = /* @__PURE__ */ new Map();
|
|
37882
|
+
if (resumeId) {
|
|
37883
|
+
resume = this.sessionIdByKey;
|
|
37884
|
+
} else if (memCfg.enabled && this.aiMemory) {
|
|
37885
|
+
effectivePrompt = `${MEMORY_SEED_HEADER}
|
|
37886
|
+
${this.aiMemory}
|
|
37887
|
+
|
|
37888
|
+
${guardedPrompt}`;
|
|
37889
|
+
}
|
|
37890
|
+
const plan = planAiRun(effectivePrompt, config, hint, resume);
|
|
37696
37891
|
const markerArgs = plan.attempts[0]?.args ?? [];
|
|
37697
37892
|
this.bus.push({ source: origin, kind: "aireq", text: `${plan.cmd} \u2039 ${prompt}` });
|
|
37698
37893
|
logger.info("command.ai", { commandId, origin, cmd: plan.cmd, profiles: plan.attempts.length });
|
|
@@ -37714,12 +37909,14 @@ var WsClient = class {
|
|
|
37714
37909
|
let displayMode = "pending";
|
|
37715
37910
|
let resultId = null;
|
|
37716
37911
|
let resultBuf = "";
|
|
37717
|
-
|
|
37912
|
+
let answerText = "";
|
|
37913
|
+
const handlers = {
|
|
37718
37914
|
onChunk: (text) => {
|
|
37719
37915
|
if (!responseHeaderShown) {
|
|
37720
37916
|
this.bus.push({ source: origin, kind: "aires", text: `${plan.cmd} \u203A` });
|
|
37721
37917
|
responseHeaderShown = true;
|
|
37722
37918
|
}
|
|
37919
|
+
answerText += text;
|
|
37723
37920
|
appendCommandOutput(commandId, text);
|
|
37724
37921
|
this.send(WsChannels.COMMAND_OUTPUT, { commandId, seq: seq++, chunk: text });
|
|
37725
37922
|
if (displayMode === "result") {
|
|
@@ -37754,8 +37951,13 @@ var WsClient = class {
|
|
|
37754
37951
|
this.send(WsChannels.COMMAND_OUTPUT, { commandId, seq: seq++, chunk: `${text}
|
|
37755
37952
|
`, log: true });
|
|
37756
37953
|
}
|
|
37757
|
-
}
|
|
37758
|
-
|
|
37954
|
+
};
|
|
37955
|
+
let result;
|
|
37956
|
+
try {
|
|
37957
|
+
result = await runAiFailover(plan, commandId, cwd2, handlers, aiRunTimeoutMs);
|
|
37958
|
+
} finally {
|
|
37959
|
+
this.bus.endBusy(plan.cmd);
|
|
37960
|
+
}
|
|
37759
37961
|
const profileLabel = result.workedDir ? profileDisplayLabel(result.workedDir) : null;
|
|
37760
37962
|
if (result.workedDir) {
|
|
37761
37963
|
if (unified && result.workedKey) {
|
|
@@ -37819,6 +38021,43 @@ var WsClient = class {
|
|
|
37819
38021
|
text: result.exitCode === 0 ? "\u2713 ready \u2014 enter your next prompt" : `\u2717 ${plan.cmd} failed (exit ${result.exitCode}) \u2014 see the error above`,
|
|
37820
38022
|
level: result.exitCode === 0 ? "info" : "error"
|
|
37821
38023
|
});
|
|
38024
|
+
if (memCfg.enabled && result.exitCode === 0) {
|
|
38025
|
+
if (result.workedKey && result.sessionId) {
|
|
38026
|
+
this.sessionIdByKey.set(result.workedKey, result.sessionId);
|
|
38027
|
+
}
|
|
38028
|
+
void this.updateSharedMemory(config, prompt, answerText, memCfg.budgetChars).catch(() => {
|
|
38029
|
+
});
|
|
38030
|
+
}
|
|
38031
|
+
}
|
|
38032
|
+
/**
|
|
38033
|
+
* Fold the latest exchange into the shared AI memory (ADR-0245): a background claude compaction to a
|
|
38034
|
+
* budget-bounded summary, cached locally + written back to the server (`memory.update`). Best-effort —
|
|
38035
|
+
* a failed/empty compaction keeps the previous memory (no write). Claude-only (mirrors the design's
|
|
38036
|
+
* native-session focus); a non-claude worker simply never compacts.
|
|
38037
|
+
*/
|
|
38038
|
+
async updateSharedMemory(config, userPrompt, answer, budgetChars) {
|
|
38039
|
+
if (!answer.trim()) return;
|
|
38040
|
+
const profiles = resolveClaudeProfiles(config, this.physicRoot);
|
|
38041
|
+
const cwd2 = this.physicRoot ?? process.cwd();
|
|
38042
|
+
const newMemory = await runMemoryCompaction(
|
|
38043
|
+
{ cmd: "claude", profiles, env: config.aiEnv },
|
|
38044
|
+
cwd2,
|
|
38045
|
+
{ oldMemory: this.aiMemory, prompt: userPrompt, answer, budgetChars }
|
|
38046
|
+
);
|
|
38047
|
+
if (!newMemory) return;
|
|
38048
|
+
this.aiMemory = newMemory;
|
|
38049
|
+
this.send(WsChannels.MEMORY_UPDATE, { text: newMemory });
|
|
38050
|
+
}
|
|
38051
|
+
/**
|
|
38052
|
+
* Reset the shared AI memory + native sessions for a "new conversation" (ADR-0245) — a manual
|
|
38053
|
+
* `/clear`. Drops the cached memory + every remembered `session_id` (so the next run starts a fresh
|
|
38054
|
+
* native session) and clears the server-stored memory. Idle auto-clear does NOT call this
|
|
38055
|
+
* (display-only — ADR-0244).
|
|
38056
|
+
*/
|
|
38057
|
+
resetMemorySession() {
|
|
38058
|
+
this.aiMemory = "";
|
|
38059
|
+
this.sessionIdByKey.clear();
|
|
38060
|
+
this.send(WsChannels.MEMORY_UPDATE, { text: "" });
|
|
37822
38061
|
}
|
|
37823
38062
|
/**
|
|
37824
38063
|
* Pick the Claude profile to try first when under session pressure (ADR-0081). Returns
|
|
@@ -38379,6 +38618,18 @@ var SessionBus = class {
|
|
|
38379
38618
|
this.emitter.on("clear", fn);
|
|
38380
38619
|
return () => this.emitter.off("clear", fn);
|
|
38381
38620
|
}
|
|
38621
|
+
/**
|
|
38622
|
+
* Signal a "new conversation" (a MANUAL `/clear`, ADR-0245) — distinct from `clear()`, which the
|
|
38623
|
+
* idle auto-clear (ADR-0244) also calls. Consumers reset the shared AI memory + native session.
|
|
38624
|
+
*/
|
|
38625
|
+
clearSession() {
|
|
38626
|
+
this.emitter.emit("clearSession");
|
|
38627
|
+
}
|
|
38628
|
+
/** Subscribe to a manual `/clear` reset (shared-memory + native-session reset — ADR-0245). */
|
|
38629
|
+
onClearSession(fn) {
|
|
38630
|
+
this.emitter.on("clearSession", fn);
|
|
38631
|
+
return () => this.emitter.off("clearSession", fn);
|
|
38632
|
+
}
|
|
38382
38633
|
/** The current transcript entries (a copy) — the console-sync snapshot source (ADR-0150). */
|
|
38383
38634
|
snapshot() {
|
|
38384
38635
|
return [...this.history];
|
|
@@ -38684,14 +38935,13 @@ function startControlServer(bus, profileDir2, info) {
|
|
|
38684
38935
|
|
|
38685
38936
|
// src/core/idle-auto-clear.ts
|
|
38686
38937
|
function startIdleAutoClear(bus, idleMinutes) {
|
|
38687
|
-
const idleMs = Math.max(0, idleMinutes) * 6e4;
|
|
38688
|
-
if (idleMs <= 0) return () => {
|
|
38689
|
-
};
|
|
38690
38938
|
let lastActivity = Date.now();
|
|
38691
38939
|
const off = bus.onTranscript(() => {
|
|
38692
38940
|
lastActivity = Date.now();
|
|
38693
38941
|
});
|
|
38694
38942
|
const timer = setInterval(() => {
|
|
38943
|
+
const idleMs = Math.max(0, idleMinutes()) * 6e4;
|
|
38944
|
+
if (idleMs <= 0) return;
|
|
38695
38945
|
if (bus.busy !== null || bus.snapshot().length === 0) return;
|
|
38696
38946
|
if (Date.now() - lastActivity < idleMs) return;
|
|
38697
38947
|
bus.clear();
|
|
@@ -45083,10 +45333,6 @@ function App2({
|
|
|
45083
45333
|
const [history, setHistory] = (0, import_react26.useState)(() => loadInputHistory(info.profileDir));
|
|
45084
45334
|
const [startedAt] = (0, import_react26.useState)(() => Date.now());
|
|
45085
45335
|
const [pendingConfirm, setPendingConfirm] = (0, import_react26.useState)(null);
|
|
45086
|
-
const [autoClearIdleMs] = (0, import_react26.useState)(() => {
|
|
45087
|
-
const min = readProfileConfig(info.profileDir).autoClearIdleMinutes ?? 10;
|
|
45088
|
-
return min > 0 ? min * 6e4 : 0;
|
|
45089
|
-
});
|
|
45090
45336
|
const lastActivityRef = (0, import_react26.useRef)(startedAt);
|
|
45091
45337
|
const busyRef = (0, import_react26.useRef)(busy);
|
|
45092
45338
|
busyRef.current = busy;
|
|
@@ -45151,20 +45397,21 @@ function App2({
|
|
|
45151
45397
|
};
|
|
45152
45398
|
}, [bus, flushUpdates]);
|
|
45153
45399
|
(0, import_react26.useEffect)(() => {
|
|
45154
|
-
if (autoClearIdleMs <= 0) return;
|
|
45155
45400
|
const timer = setInterval(() => {
|
|
45401
|
+
const idleMs = resolveIdleAutoClearMinutes(readProfileConfig(info.profileDir)) * 6e4;
|
|
45402
|
+
if (idleMs <= 0) return;
|
|
45156
45403
|
if (busyRef.current !== null || pendingConfirmRef.current || entriesLenRef.current === 0) return;
|
|
45157
|
-
if (Date.now() - lastActivityRef.current <
|
|
45404
|
+
if (Date.now() - lastActivityRef.current < idleMs) return;
|
|
45158
45405
|
clearTranscript();
|
|
45159
45406
|
bus.push({
|
|
45160
45407
|
source: "system",
|
|
45161
45408
|
kind: "log",
|
|
45162
|
-
text: `Transcript auto-cleared after ${Math.round(
|
|
45409
|
+
text: `Transcript auto-cleared after ${Math.round(idleMs / 6e4)} min idle.`
|
|
45163
45410
|
});
|
|
45164
45411
|
lastActivityRef.current = Date.now();
|
|
45165
45412
|
}, 6e4);
|
|
45166
45413
|
return () => clearInterval(timer);
|
|
45167
|
-
}, [
|
|
45414
|
+
}, [info.profileDir, bus, clearTranscript]);
|
|
45168
45415
|
const frameRows = Math.max(4, rows - 1);
|
|
45169
45416
|
const bodyHeight = Math.max(3, frameRows - CHROME_ROWS);
|
|
45170
45417
|
const textWidth = Math.max(10, cols - 1 - 8);
|
|
@@ -45207,7 +45454,12 @@ function App2({
|
|
|
45207
45454
|
runSlashCommand(trimmed, {
|
|
45208
45455
|
info,
|
|
45209
45456
|
print: (text, level) => bus.push({ source: "local", kind: "log", text, level }),
|
|
45210
|
-
clear
|
|
45457
|
+
// A manual `/clear` also resets the shared AI memory + native session (ADR-0245); the idle
|
|
45458
|
+
// auto-clear path calls clearTranscript() directly (display-only), so it does NOT reset.
|
|
45459
|
+
clear: () => {
|
|
45460
|
+
clearTranscript();
|
|
45461
|
+
bus.clearSession();
|
|
45462
|
+
},
|
|
45211
45463
|
quit: () => exit(),
|
|
45212
45464
|
confirm: (prompt, onYes) => {
|
|
45213
45465
|
bus.push({ source: "local", kind: "log", text: prompt });
|
|
@@ -45285,6 +45537,7 @@ async function runStart(profileDir2, profileName) {
|
|
|
45285
45537
|
});
|
|
45286
45538
|
logger.info("cli.start", { serverUrl: credential.serverUrl });
|
|
45287
45539
|
ensureProfileConfig(profileDir2);
|
|
45540
|
+
backfillProfileConfig(profileDir2);
|
|
45288
45541
|
const config = readProfileConfig(profileDir2);
|
|
45289
45542
|
const autoUpdate = config.autoUpdate !== false && process.env.FOURPM_NO_UPDATE !== "1";
|
|
45290
45543
|
const result = await checkAndUpdate(credential.serverUrl, autoUpdate);
|
|
@@ -45299,7 +45552,7 @@ async function runStart(profileDir2, profileName) {
|
|
|
45299
45552
|
if (result.action === "updated") {
|
|
45300
45553
|
logger.info("update.updated", { version: result.version });
|
|
45301
45554
|
console.log("Restarting with the new version\u2026");
|
|
45302
|
-
const child =
|
|
45555
|
+
const child = spawn10(process.execPath, process.argv.slice(1), {
|
|
45303
45556
|
stdio: "inherit",
|
|
45304
45557
|
env: { ...process.env, FOURPM_NO_UPDATE: "1" }
|
|
45305
45558
|
});
|
|
@@ -45373,7 +45626,10 @@ async function runStart(profileDir2, profileName) {
|
|
|
45373
45626
|
await tui;
|
|
45374
45627
|
} else {
|
|
45375
45628
|
attachConsoleSink(bus);
|
|
45376
|
-
stopIdleClear = startIdleAutoClear(
|
|
45629
|
+
stopIdleClear = startIdleAutoClear(
|
|
45630
|
+
bus,
|
|
45631
|
+
() => resolveIdleAutoClearMinutes(readProfileConfig(profileDir2))
|
|
45632
|
+
);
|
|
45377
45633
|
bus.log(`Connecting to ${credential.serverUrl} (profile: ${profileName})\u2026`);
|
|
45378
45634
|
await runResilient();
|
|
45379
45635
|
}
|
|
@@ -45516,12 +45772,12 @@ async function runAttach(profileDir2, profileName) {
|
|
|
45516
45772
|
}
|
|
45517
45773
|
|
|
45518
45774
|
// src/commands/ai-login.ts
|
|
45519
|
-
import { spawn as
|
|
45775
|
+
import { spawn as spawn11 } from "child_process";
|
|
45520
45776
|
import { mkdirSync as mkdirSync6 } from "fs";
|
|
45521
45777
|
import { basename as basename5 } from "path";
|
|
45522
45778
|
function runProviderLogin(cmd, envVar, dir2) {
|
|
45523
45779
|
return new Promise((resolve8) => {
|
|
45524
|
-
const child =
|
|
45780
|
+
const child = spawn11(cmd, [], {
|
|
45525
45781
|
stdio: "inherit",
|
|
45526
45782
|
env: { ...process.env, [envVar]: dir2 }
|
|
45527
45783
|
});
|