@4pm/cli 1.5.1 → 1.5.2
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 +183 -21
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -32990,6 +32990,7 @@ var CLI_SLASH_COMMANDS = [
|
|
|
32990
32990
|
{ name: "collapse", usage: "/collapse [N]", description: "Collapse an expanded \u25B8[N] block" },
|
|
32991
32991
|
{ name: "reconnect", usage: "/reconnect", description: "Reconnect to the server now (skip the backoff)" },
|
|
32992
32992
|
{ name: "whoami", usage: "/whoami", description: "Show this machine account + its teams & projects" },
|
|
32993
|
+
{ name: "ai-profile", usage: "/ai-profile [list|use <n>|reset]", description: "List AI profiles / switch which one runs" },
|
|
32993
32994
|
{ name: "config", usage: "/config [show|init|set <k> <v>|delete <k>]", description: "View / init / update / delete profile config" },
|
|
32994
32995
|
{ name: "claude-cmd", usage: "/claude-cmd /context", description: "Run an AI-CLI slash command (e.g. /context, /usage)" },
|
|
32995
32996
|
{ name: "clear", usage: "/clear", description: "Clear the transcript" },
|
|
@@ -33038,7 +33039,10 @@ var listWorkersQuerySchema = baseRequestSchema.extend({
|
|
|
33038
33039
|
/** Only workers whose live CPU/memory/disk usage % ≥ the given threshold (0–100). */
|
|
33039
33040
|
minCpuPct: external_exports.coerce.number().min(0).max(100).optional(),
|
|
33040
33041
|
minMemPct: external_exports.coerce.number().min(0).max(100).optional(),
|
|
33041
|
-
minDiskPct: external_exports.coerce.number().min(0).max(100).optional()
|
|
33042
|
+
minDiskPct: external_exports.coerce.number().min(0).max(100).optional(),
|
|
33043
|
+
/** Hide workers with no cli attached (clis = 0). The web defaults this on; unchecking the
|
|
33044
|
+
* "show empty CLIs" box drops it so empty workers are listed too. */
|
|
33045
|
+
hideEmptyClis: external_exports.enum(["true", "false"]).transform((v) => v === "true").optional()
|
|
33042
33046
|
});
|
|
33043
33047
|
var updateWorkerRequestSchema = external_exports.object({
|
|
33044
33048
|
name: external_exports.string().min(1).max(100).optional()
|
|
@@ -34051,11 +34055,29 @@ import { spawn as spawn2 } from "child_process";
|
|
|
34051
34055
|
var KILL_GRACE_MS = 5e3;
|
|
34052
34056
|
async function runCommand(dispatch, emit, opts) {
|
|
34053
34057
|
let seq = 0;
|
|
34054
|
-
|
|
34055
|
-
|
|
34056
|
-
|
|
34057
|
-
|
|
34058
|
+
let child;
|
|
34059
|
+
try {
|
|
34060
|
+
child = spawn2(dispatch.cmd, dispatch.args ?? [], {
|
|
34061
|
+
cwd: dispatch.path,
|
|
34062
|
+
env: { ...process.env, ...dispatch.env },
|
|
34063
|
+
shell: false
|
|
34064
|
+
});
|
|
34065
|
+
} catch (err) {
|
|
34066
|
+
emit({
|
|
34067
|
+
commandId: dispatch.commandId,
|
|
34068
|
+
seq: seq++,
|
|
34069
|
+
chunk: `spawn error: ${err.message}
|
|
34070
|
+
`,
|
|
34071
|
+
done: true,
|
|
34072
|
+
exitCode: -1
|
|
34073
|
+
});
|
|
34074
|
+
return;
|
|
34075
|
+
}
|
|
34076
|
+
child.stdin?.on("error", () => {
|
|
34058
34077
|
});
|
|
34078
|
+
if (opts?.stdin != null) {
|
|
34079
|
+
child.stdin?.write(opts.stdin);
|
|
34080
|
+
}
|
|
34059
34081
|
child.stdin?.end();
|
|
34060
34082
|
const timeoutMs = opts?.timeoutMs && opts.timeoutMs > 0 ? opts.timeoutMs : 0;
|
|
34061
34083
|
let timedOut = false;
|
|
@@ -34255,12 +34277,16 @@ function isOutOfCredits(text) {
|
|
|
34255
34277
|
text
|
|
34256
34278
|
);
|
|
34257
34279
|
}
|
|
34258
|
-
function runAttempt(commandId, cmd, args, cwd2, env3, onChunk, timeoutMs) {
|
|
34280
|
+
function runAttempt(commandId, cmd, args, stdin, cwd2, env3, onChunk, timeoutMs) {
|
|
34259
34281
|
return new Promise((resolve8) => {
|
|
34260
|
-
|
|
34261
|
-
|
|
34262
|
-
|
|
34263
|
-
|
|
34282
|
+
runCommand(
|
|
34283
|
+
{ commandId, cmd, args, path: cwd2, env: env3 },
|
|
34284
|
+
(out) => {
|
|
34285
|
+
if (out.chunk) onChunk(out.chunk);
|
|
34286
|
+
if (out.done) resolve8(out.exitCode ?? -1);
|
|
34287
|
+
},
|
|
34288
|
+
{ stdin, ...timeoutMs > 0 ? { timeoutMs } : {} }
|
|
34289
|
+
).catch(() => resolve8(-1));
|
|
34264
34290
|
});
|
|
34265
34291
|
}
|
|
34266
34292
|
async function runAiFailover(plan, commandId, cwd2, handlers, timeoutMs = 0) {
|
|
@@ -34281,7 +34307,7 @@ async function runAiFailover(plan, commandId, cwd2, handlers, timeoutMs = 0) {
|
|
|
34281
34307
|
handlers.onChunk(text);
|
|
34282
34308
|
}
|
|
34283
34309
|
};
|
|
34284
|
-
finalExit = await runAttempt(commandId, attempt.cmd, attempt.args, cwd2, attempt.env, emit, timeoutMs);
|
|
34310
|
+
finalExit = await runAttempt(commandId, attempt.cmd, attempt.args, attempt.stdin, cwd2, attempt.env, emit, timeoutMs);
|
|
34285
34311
|
const tail = parser.flush();
|
|
34286
34312
|
if (tail) {
|
|
34287
34313
|
captured += tail;
|
|
@@ -34358,6 +34384,52 @@ function setWorkingCredential(profileDir2, key) {
|
|
|
34358
34384
|
} catch {
|
|
34359
34385
|
}
|
|
34360
34386
|
}
|
|
34387
|
+
var PINNED_CREDENTIAL_KEY = "__pinned__";
|
|
34388
|
+
function getPinnedCredential(profileDir2) {
|
|
34389
|
+
return readState(profileDir2)[PINNED_CREDENTIAL_KEY] ?? null;
|
|
34390
|
+
}
|
|
34391
|
+
function setPinnedCredential(profileDir2, key) {
|
|
34392
|
+
const state = readState(profileDir2);
|
|
34393
|
+
if (state[PINNED_CREDENTIAL_KEY] === key) return;
|
|
34394
|
+
state[PINNED_CREDENTIAL_KEY] = key;
|
|
34395
|
+
try {
|
|
34396
|
+
writeFileSync4(statePath(profileDir2), JSON.stringify(state, null, 2), "utf8");
|
|
34397
|
+
} catch {
|
|
34398
|
+
}
|
|
34399
|
+
}
|
|
34400
|
+
function clearPinnedCredential(profileDir2) {
|
|
34401
|
+
const state = readState(profileDir2);
|
|
34402
|
+
if (!(PINNED_CREDENTIAL_KEY in state)) return;
|
|
34403
|
+
delete state[PINNED_CREDENTIAL_KEY];
|
|
34404
|
+
try {
|
|
34405
|
+
writeFileSync4(statePath(profileDir2), JSON.stringify(state, null, 2), "utf8");
|
|
34406
|
+
} catch {
|
|
34407
|
+
}
|
|
34408
|
+
}
|
|
34409
|
+
|
|
34410
|
+
// src/utils/time.ts
|
|
34411
|
+
function formatTimestampInZone(date, timeZone) {
|
|
34412
|
+
const build = (tz) => {
|
|
34413
|
+
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
34414
|
+
...tz ? { timeZone: tz } : {},
|
|
34415
|
+
hour12: false,
|
|
34416
|
+
year: "numeric",
|
|
34417
|
+
month: "2-digit",
|
|
34418
|
+
day: "2-digit",
|
|
34419
|
+
hour: "2-digit",
|
|
34420
|
+
minute: "2-digit",
|
|
34421
|
+
second: "2-digit"
|
|
34422
|
+
}).formatToParts(date);
|
|
34423
|
+
const get2 = (type) => parts.find((p) => p.type === type)?.value ?? "";
|
|
34424
|
+
const hour = get2("hour") === "24" ? "00" : get2("hour");
|
|
34425
|
+
return `${get2("year")}/${get2("month")}/${get2("day")} ${hour}:${get2("minute")}:${get2("second")}`;
|
|
34426
|
+
};
|
|
34427
|
+
try {
|
|
34428
|
+
return build(timeZone);
|
|
34429
|
+
} catch {
|
|
34430
|
+
return build();
|
|
34431
|
+
}
|
|
34432
|
+
}
|
|
34361
34433
|
|
|
34362
34434
|
// src/utils/ai-cli.ts
|
|
34363
34435
|
import { readFileSync as readFileSync8 } from "fs";
|
|
@@ -34463,7 +34535,7 @@ function folderScopeGuard(folder, prompt) {
|
|
|
34463
34535
|
prompt
|
|
34464
34536
|
].join("\n");
|
|
34465
34537
|
}
|
|
34466
|
-
function buildRunArgs(profile, cmd,
|
|
34538
|
+
function buildRunArgs(profile, cmd, resumeId, oneShot = false) {
|
|
34467
34539
|
const extras = profile.args ?? [];
|
|
34468
34540
|
const model = profile.model?.trim();
|
|
34469
34541
|
const modelArgs = model ? ["--model", model] : [];
|
|
@@ -34471,7 +34543,7 @@ function buildRunArgs(profile, cmd, prompt, resumeId, oneShot = false) {
|
|
|
34471
34543
|
const turns = profile.maxTurns;
|
|
34472
34544
|
const perProfileTurns = !oneShot && cmd.includes("claude") && typeof turns === "number" && turns > 0 ? [`--max-turns=${Math.floor(turns)}`] : [];
|
|
34473
34545
|
const oneShotExtra = oneShot ? oneShotArgs(cmd) : perProfileTurns;
|
|
34474
|
-
return [...requiredArgs(cmd), ...resumeArgs, ...extras, ...modelArgs, ...oneShotExtra
|
|
34546
|
+
return [...requiredArgs(cmd), ...resumeArgs, ...extras, ...modelArgs, ...oneShotExtra];
|
|
34475
34547
|
}
|
|
34476
34548
|
function claudeHomeDirs(config) {
|
|
34477
34549
|
const dirs = profileDirs(claudeProfilesOf(config));
|
|
@@ -34522,7 +34594,7 @@ function planUnifiedRun(prompt, config, workingCredential, resume, oneShot) {
|
|
|
34522
34594
|
return {
|
|
34523
34595
|
cmd: active,
|
|
34524
34596
|
attempts: [
|
|
34525
|
-
{ cmd: active, label: "default", dir: null, key: null, args: buildRunArgs({ profile: "" }, active,
|
|
34597
|
+
{ cmd: active, label: "default", dir: null, key: null, args: buildRunArgs({ profile: "" }, active, void 0, oneShot), stdin: prompt, env: { ...baseEnv } }
|
|
34526
34598
|
]
|
|
34527
34599
|
};
|
|
34528
34600
|
}
|
|
@@ -34540,7 +34612,8 @@ function planUnifiedRun(prompt, config, workingCredential, resume, oneShot) {
|
|
|
34540
34612
|
label: cred.label?.trim() || profileDisplayLabel(dir2),
|
|
34541
34613
|
dir: dir2,
|
|
34542
34614
|
key,
|
|
34543
|
-
args: buildRunArgs(cred, cmd,
|
|
34615
|
+
args: buildRunArgs(cred, cmd, resume.get(key), oneShot),
|
|
34616
|
+
stdin: prompt,
|
|
34544
34617
|
env: envVar ? { ...baseEnv, [envVar]: dir2 } : { ...baseEnv }
|
|
34545
34618
|
}));
|
|
34546
34619
|
return { cmd: attempts[0]?.cmd ?? DEFAULT_AI_CLI, attempts };
|
|
@@ -34552,7 +34625,7 @@ function planLegacyRun(prompt, config, workingDir, resume, oneShot) {
|
|
|
34552
34625
|
const defaultPlan = {
|
|
34553
34626
|
cmd,
|
|
34554
34627
|
attempts: [
|
|
34555
|
-
{ cmd, label: "default", dir: null, key: null, args: buildRunArgs({ profile: "" }, cmd,
|
|
34628
|
+
{ cmd, label: "default", dir: null, key: null, args: buildRunArgs({ profile: "" }, cmd, void 0, oneShot), stdin: prompt, env: { ...baseEnv } }
|
|
34556
34629
|
]
|
|
34557
34630
|
};
|
|
34558
34631
|
if (!envVar) return defaultPlan;
|
|
@@ -34569,7 +34642,8 @@ function planLegacyRun(prompt, config, workingDir, resume, oneShot) {
|
|
|
34569
34642
|
label: profileDisplayLabel(dir2),
|
|
34570
34643
|
dir: dir2,
|
|
34571
34644
|
key,
|
|
34572
|
-
args: buildRunArgs(profile, cmd,
|
|
34645
|
+
args: buildRunArgs(profile, cmd, resume.get(key), oneShot),
|
|
34646
|
+
stdin: prompt,
|
|
34573
34647
|
env: { ...baseEnv, [envVar]: dir2 }
|
|
34574
34648
|
};
|
|
34575
34649
|
});
|
|
@@ -36948,6 +37022,70 @@ function runWhoami(ctx) {
|
|
|
36948
37022
|
ctx.print(`projects: ${w.projects.length ? w.projects.map((p) => p.name).join(", ") : "(none)"}`);
|
|
36949
37023
|
});
|
|
36950
37024
|
}
|
|
37025
|
+
function aiProfileLabel(c) {
|
|
37026
|
+
return c.label?.trim() || profileDisplayLabel(resolveHomePath(c.profile));
|
|
37027
|
+
}
|
|
37028
|
+
function runAiProfile(ctx) {
|
|
37029
|
+
const dir2 = ctx.info.profileDir;
|
|
37030
|
+
const config = readProfileConfig(dir2);
|
|
37031
|
+
const list = Array.isArray(config.aiProfiles) ? config.aiProfiles : [];
|
|
37032
|
+
const [sub, ...rest] = ctx.args;
|
|
37033
|
+
const action = (sub ?? "list").toLowerCase();
|
|
37034
|
+
if (action === "list") {
|
|
37035
|
+
if (list.length === 0) {
|
|
37036
|
+
ctx.print("No AI profiles configured (config.aiProfiles is empty).", "warn");
|
|
37037
|
+
ctx.print("This worker may use a legacy per-provider config \u2014 /config show to inspect.");
|
|
37038
|
+
return;
|
|
37039
|
+
}
|
|
37040
|
+
const pinned = getPinnedCredential(dir2);
|
|
37041
|
+
const working = getWorkingCredential(dir2);
|
|
37042
|
+
ctx.print("AI profiles (config order = failover priority) \xB7 /ai-profile use <n> to switch:");
|
|
37043
|
+
list.forEach((c, i) => {
|
|
37044
|
+
const usable = isUsableCredential(c);
|
|
37045
|
+
const key = usable ? credentialKey(c.provider, resolveHomePath(c.profile)) : null;
|
|
37046
|
+
const flags = [];
|
|
37047
|
+
if (key && key === pinned) flags.push("pinned");
|
|
37048
|
+
else if (key && !pinned && key === working) flags.push("active");
|
|
37049
|
+
if (c.enabled === false) flags.push("disabled");
|
|
37050
|
+
const model = c.model?.trim() ? ` \xB7 model=${c.model.trim()}` : "";
|
|
37051
|
+
const flagStr = flags.length ? ` (${flags.join(", ")})` : "";
|
|
37052
|
+
const marker = key && key === pinned ? "*" : " ";
|
|
37053
|
+
ctx.print(` ${marker}[${i + 1}] ${c.provider.padEnd(11)} ${aiProfileLabel(c)}${model}${flagStr}`);
|
|
37054
|
+
});
|
|
37055
|
+
if (pinned) {
|
|
37056
|
+
ctx.print("A profile is pinned \u2014 tried first every prompt; /ai-profile reset returns to auto.");
|
|
37057
|
+
}
|
|
37058
|
+
return;
|
|
37059
|
+
}
|
|
37060
|
+
if (action === "reset" || action === "auto" || action === "clear") {
|
|
37061
|
+
clearPinnedCredential(dir2);
|
|
37062
|
+
ctx.print("\u2714 AI profile pin cleared \u2014 failover is back to automatic (config order / remembered).");
|
|
37063
|
+
const working = getWorkingCredential(dir2);
|
|
37064
|
+
ctx.setActiveProfile(working ? labelFromCredentialKey(working) : null);
|
|
37065
|
+
return;
|
|
37066
|
+
}
|
|
37067
|
+
if (action === "use" || action === "switch") {
|
|
37068
|
+
const n = Number(rest[0]);
|
|
37069
|
+
if (!Number.isInteger(n) || n < 1 || n > list.length) {
|
|
37070
|
+
ctx.print(`usage: /ai-profile use <n> (n = 1-${list.length || "?"} from /ai-profile list)`, "error");
|
|
37071
|
+
return;
|
|
37072
|
+
}
|
|
37073
|
+
const cred = list[n - 1];
|
|
37074
|
+
if (!isUsableCredential(cred)) {
|
|
37075
|
+
const why = cred.enabled === false ? "it is disabled" : "it has no usable profile dir / provider";
|
|
37076
|
+
ctx.print(`Profile [${n}] "${aiProfileLabel(cred)}" can't be used \u2014 ${why}.`, "error");
|
|
37077
|
+
return;
|
|
37078
|
+
}
|
|
37079
|
+
const credDir = resolveHomePath(cred.profile);
|
|
37080
|
+
setPinnedCredential(dir2, credentialKey(cred.provider, credDir));
|
|
37081
|
+
ctx.setActiveProfile(profileDisplayLabel(credDir));
|
|
37082
|
+
ctx.print(
|
|
37083
|
+
`\u2714 switched to ${cred.provider} profile "${aiProfileLabel(cred)}" \u2014 tried first from the next prompt (failover keeps the rest as backup).`
|
|
37084
|
+
);
|
|
37085
|
+
return;
|
|
37086
|
+
}
|
|
37087
|
+
ctx.print(`Unknown /ai-profile subcommand "${sub}". Use: list | use <n> | reset.`, "error");
|
|
37088
|
+
}
|
|
36951
37089
|
function runFold(ctx, action) {
|
|
36952
37090
|
if (ctx.maxBlock === 0) {
|
|
36953
37091
|
ctx.print("No collapsible blocks on screen.", "warn");
|
|
@@ -37037,6 +37175,12 @@ var COMMANDS = [
|
|
|
37037
37175
|
description: "Show this machine account + its teams & projects",
|
|
37038
37176
|
run: runWhoami
|
|
37039
37177
|
},
|
|
37178
|
+
{
|
|
37179
|
+
name: "ai-profile",
|
|
37180
|
+
usage: "/ai-profile [list|use <n>|reset]",
|
|
37181
|
+
description: "List AI profiles / switch which one runs (ADR-0250)",
|
|
37182
|
+
run: runAiProfile
|
|
37183
|
+
},
|
|
37040
37184
|
{
|
|
37041
37185
|
name: "config",
|
|
37042
37186
|
usage: "/config [show|init|set <k> <v>|delete <k>]",
|
|
@@ -37417,6 +37561,9 @@ var WsClient = class {
|
|
|
37417
37561
|
reconnectEntryId = null;
|
|
37418
37562
|
/** Org-configured reconnect cap (seconds) from the last ws_token — overrides env. */
|
|
37419
37563
|
orgReconnectMaxSec = null;
|
|
37564
|
+
/** Org timezone (IANA) from the last ws_token (ADR-0132) — a rented worker follows its renter's
|
|
37565
|
+
* org; used to stamp the AI-run start time on the `aireq` transcript marker (ADR-0249). */
|
|
37566
|
+
orgTimezone = "UTC";
|
|
37420
37567
|
/** Env override for the reconnect cap (seconds); resolved once at construction. */
|
|
37421
37568
|
envReconnectMaxSec = envReconnectMaxSec();
|
|
37422
37569
|
/** Process start (ms) — uptime for a web-dispatched `/status` (ADR-0249). */
|
|
@@ -37601,6 +37748,9 @@ var WsClient = class {
|
|
|
37601
37748
|
if (typeof token.reconnectMaxBackoffSec === "number") {
|
|
37602
37749
|
this.orgReconnectMaxSec = token.reconnectMaxBackoffSec;
|
|
37603
37750
|
}
|
|
37751
|
+
if (typeof token.timezone === "string" && token.timezone) {
|
|
37752
|
+
this.orgTimezone = token.timezone;
|
|
37753
|
+
}
|
|
37604
37754
|
if (typeof token.autoUpdateDaily === "boolean") {
|
|
37605
37755
|
this.updateScheduler.configure({
|
|
37606
37756
|
autoUpdateDaily: token.autoUpdateDaily,
|
|
@@ -38288,9 +38438,10 @@ var WsClient = class {
|
|
|
38288
38438
|
const unified = isUnifiedConfig(config);
|
|
38289
38439
|
const mode = config.aiFailoverMode ?? "remember";
|
|
38290
38440
|
const workingDir = unified ? null : getWorkingProfile(this.context.profileDir, cmd);
|
|
38291
|
-
const
|
|
38441
|
+
const pinnedCred = unified ? getPinnedCredential(this.context.profileDir) : null;
|
|
38442
|
+
const workingCred = unified ? pinnedCred ?? (mode === "remember" ? getWorkingCredential(this.context.profileDir) : null) : null;
|
|
38292
38443
|
const startDir = unified ? null : this.profileUnderSessionPressure(config, cmd, workingDir);
|
|
38293
|
-
const startCred = unified ? this.credentialUnderSessionPressure(config, workingCred) : null;
|
|
38444
|
+
const startCred = unified && !pinnedCred ? this.credentialUnderSessionPressure(config, workingCred) : null;
|
|
38294
38445
|
if (startDir && startDir !== workingDir) {
|
|
38295
38446
|
this.bus.push({
|
|
38296
38447
|
source: origin,
|
|
@@ -38322,7 +38473,8 @@ ${guardedPrompt}`;
|
|
|
38322
38473
|
}
|
|
38323
38474
|
const plan = planAiRun(effectivePrompt, config, hint, resume, oneShot);
|
|
38324
38475
|
const markerArgs = plan.attempts[0]?.args ?? [];
|
|
38325
|
-
|
|
38476
|
+
const startStamp = formatTimestampInZone(/* @__PURE__ */ new Date(), this.orgTimezone);
|
|
38477
|
+
this.bus.push({ source: origin, kind: "aireq", text: `${startStamp} ${plan.cmd} \u2039 ${prompt}` });
|
|
38326
38478
|
logger.info("command.ai", { commandId, origin, cmd: plan.cmd, profiles: plan.attempts.length });
|
|
38327
38479
|
if (origin === "local") {
|
|
38328
38480
|
this.send(WsChannels.COMMAND_ANNOUNCE, {
|
|
@@ -38557,6 +38709,8 @@ ${guardedPrompt}`;
|
|
|
38557
38709
|
submitAi: () => {
|
|
38558
38710
|
},
|
|
38559
38711
|
// only /claude-cmd uses this, and it is special-cased above
|
|
38712
|
+
// /ai-profile use/reset from the web Console updates the shared header too (ADR-0250).
|
|
38713
|
+
setActiveProfile: (label) => this.bus.setActiveProfile(label),
|
|
38560
38714
|
reconnect: () => this.reconnectNow(),
|
|
38561
38715
|
expand: () => emit("Use the web console's fold viewer to expand blocks."),
|
|
38562
38716
|
collapse: () => emit("Use the web console's fold viewer to collapse blocks."),
|
|
@@ -45263,12 +45417,17 @@ function textColor(line) {
|
|
|
45263
45417
|
if (line.kind === "exit") return "green";
|
|
45264
45418
|
return void 0;
|
|
45265
45419
|
}
|
|
45420
|
+
var START_STAMP_RE = /^(\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2} )/;
|
|
45266
45421
|
function AiMarkerLine({ line }) {
|
|
45267
45422
|
const arrow = line.kind === "aireq" ? "\u2039" : "\u203A";
|
|
45268
45423
|
const idx = line.text.indexOf(arrow);
|
|
45269
45424
|
if (idx < 0) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { children: line.text || " " });
|
|
45425
|
+
const before = line.text.slice(0, idx);
|
|
45426
|
+
const stamp = line.kind === "aireq" ? START_STAMP_RE.exec(before)?.[1] ?? "" : "";
|
|
45427
|
+
const name = stamp ? before.slice(stamp.length) : before;
|
|
45270
45428
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Text, { children: [
|
|
45271
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, {
|
|
45429
|
+
stamp ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { dimColor: true, children: stamp }) : null,
|
|
45430
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "cyan", bold: true, children: name }),
|
|
45272
45431
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { dimColor: true, children: arrow }),
|
|
45273
45432
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { children: line.text.slice(idx + 1) })
|
|
45274
45433
|
] });
|
|
@@ -45685,6 +45844,9 @@ function App2({
|
|
|
45685
45844
|
setPendingConfirm({ prompt, onYes });
|
|
45686
45845
|
},
|
|
45687
45846
|
submitAi: (aiInput) => bus.submitLocal(aiInput),
|
|
45847
|
+
// /ai-profile use/reset updates the header's active-profile label at once (ADR-0250);
|
|
45848
|
+
// the bus event feeds the React state (see the onActiveProfile subscription above).
|
|
45849
|
+
setActiveProfile: (label) => bus.setActiveProfile(label),
|
|
45688
45850
|
reconnect: () => bus.requestReconnect(),
|
|
45689
45851
|
expand: expandFold,
|
|
45690
45852
|
collapse: collapseFold,
|