switchroom 0.18.28 → 0.18.29
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/bin/handoff-briefing.sh +8 -1
- package/dist/auth-broker/index.js +0 -57
- package/dist/cli/switchroom.js +501 -497
- package/dist/host-control/main.js +1 -58
- package/dist/vault/approvals/kernel-server.js +0 -57
- package/dist/vault/broker/server.js +0 -57
- package/package.json +1 -1
- package/profiles/_base/start.sh.hbs +37 -19
- package/telegram-plugin/dist/gateway/gateway.js +578 -580
- package/telegram-plugin/gateway/backstop-delivery.ts +272 -0
- package/telegram-plugin/gateway/forward-origin.ts +9 -1
- package/telegram-plugin/gateway/gateway.ts +458 -385
- package/telegram-plugin/gateway/model-command.ts +227 -602
- package/telegram-plugin/gateway/turn-record-status.ts +45 -0
- package/telegram-plugin/history.ts +153 -23
- package/telegram-plugin/shared/local-time.ts +56 -0
- package/telegram-plugin/tests/backstop-delivery.test.ts +250 -0
- package/telegram-plugin/tests/forward-origin.test.ts +30 -3
- package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +86 -59
- package/telegram-plugin/tests/history.test.ts +88 -0
- package/telegram-plugin/tests/local-time.test.ts +68 -0
- package/telegram-plugin/tests/model-command.test.ts +317 -1535
- package/telegram-plugin/tests/turn-flush-safety.test.ts +34 -0
- package/telegram-plugin/tier-downgrade.ts +4 -3
- package/telegram-plugin/turn-flush-safety.ts +25 -1
- package/vendor/hindsight-memory/scripts/lib/content.py +40 -6
- package/vendor/hindsight-memory/tests/test_content.py +28 -7
package/dist/cli/switchroom.js
CHANGED
|
@@ -2120,7 +2120,7 @@ var init_esm = __esm(() => {
|
|
|
2120
2120
|
});
|
|
2121
2121
|
|
|
2122
2122
|
// src/build-info.ts
|
|
2123
|
-
var VERSION = "0.18.
|
|
2123
|
+
var VERSION = "0.18.29", COMMIT_SHA = "8f9b38b3";
|
|
2124
2124
|
|
|
2125
2125
|
// src/cli/resolve-version.ts
|
|
2126
2126
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -15265,431 +15265,6 @@ var init_users = __esm(() => {
|
|
|
15265
15265
|
init_merge();
|
|
15266
15266
|
});
|
|
15267
15267
|
|
|
15268
|
-
// src/agents/pane-lock.ts
|
|
15269
|
-
function withPaneLock(key, fn) {
|
|
15270
|
-
const tail = tails.get(key) ?? Promise.resolve();
|
|
15271
|
-
const run = tail.then(() => fn());
|
|
15272
|
-
const next = run.then(() => {}, () => {});
|
|
15273
|
-
tails.set(key, next);
|
|
15274
|
-
next.then(() => {
|
|
15275
|
-
if (tails.get(key) === next)
|
|
15276
|
-
tails.delete(key);
|
|
15277
|
-
});
|
|
15278
|
-
return run;
|
|
15279
|
-
}
|
|
15280
|
-
var tails;
|
|
15281
|
-
var init_pane_lock = __esm(() => {
|
|
15282
|
-
tails = new Map;
|
|
15283
|
-
});
|
|
15284
|
-
|
|
15285
|
-
// src/agents/inject.ts
|
|
15286
|
-
var exports_inject = {};
|
|
15287
|
-
__export(exports_inject, {
|
|
15288
|
-
validateInjectCommand: () => validateInjectCommand,
|
|
15289
|
-
normalizeInjectCommand: () => normalizeInjectCommand,
|
|
15290
|
-
makeTmuxRunner: () => makeTmuxRunner,
|
|
15291
|
-
isTuiChromeLine: () => isTuiChromeLine,
|
|
15292
|
-
injectSlashCommandWith: () => injectSlashCommandWith,
|
|
15293
|
-
injectSlashCommand: () => injectSlashCommand,
|
|
15294
|
-
diffPane: () => diffPane,
|
|
15295
|
-
dedupeInjectQueue: () => dedupeInjectQueue,
|
|
15296
|
-
InjectError: () => InjectError,
|
|
15297
|
-
INJECT_COMMANDS: () => INJECT_COMMANDS,
|
|
15298
|
-
INJECT_BLOCKLIST: () => INJECT_BLOCKLIST,
|
|
15299
|
-
INJECT_BLOCKED: () => INJECT_BLOCKED,
|
|
15300
|
-
INJECT_ALLOWLIST: () => INJECT_ALLOWLIST
|
|
15301
|
-
});
|
|
15302
|
-
import { execFileSync } from "node:child_process";
|
|
15303
|
-
function validateInjectCommand(command) {
|
|
15304
|
-
if (typeof command !== "string" || command.trim().length === 0) {
|
|
15305
|
-
throw new InjectError("invalid", "command is empty");
|
|
15306
|
-
}
|
|
15307
|
-
const trimmed = command.trim();
|
|
15308
|
-
if (!trimmed.startsWith("/")) {
|
|
15309
|
-
throw new InjectError("invalid", `command must start with "/": got ${trimmed}`);
|
|
15310
|
-
}
|
|
15311
|
-
const bare = trimmed.split(/\s+/, 1)[0].toLowerCase();
|
|
15312
|
-
const blockedMeta = INJECT_BLOCKED.get(bare);
|
|
15313
|
-
if (blockedMeta) {
|
|
15314
|
-
throw new InjectError("blocked", `${bare} is explicitly blocked from inject (${blockedMeta.reason}).`);
|
|
15315
|
-
}
|
|
15316
|
-
const meta = INJECT_COMMANDS.get(bare);
|
|
15317
|
-
if (!meta) {
|
|
15318
|
-
const allowed = [...INJECT_COMMANDS.keys()].sort().join(", ");
|
|
15319
|
-
throw new InjectError("not_allowed", `${bare} is not in the inject allowlist. Allowed: ${allowed}`);
|
|
15320
|
-
}
|
|
15321
|
-
const hasArgs = trimmed.split(/\s+/).length > 1;
|
|
15322
|
-
if (hasArgs && !meta.argsAllowed) {
|
|
15323
|
-
throw new InjectError("args_not_allowed", `args not permitted for ${bare} \u2014 inject the bare verb only`);
|
|
15324
|
-
}
|
|
15325
|
-
return bare;
|
|
15326
|
-
}
|
|
15327
|
-
function normalizeInjectCommand(command) {
|
|
15328
|
-
return command.trim().replace(/\s+/g, " ").toLowerCase();
|
|
15329
|
-
}
|
|
15330
|
-
function dedupeInjectQueue(commands) {
|
|
15331
|
-
const seen = new Set;
|
|
15332
|
-
const out = [];
|
|
15333
|
-
for (const raw of commands) {
|
|
15334
|
-
if (typeof raw !== "string")
|
|
15335
|
-
continue;
|
|
15336
|
-
const key = normalizeInjectCommand(raw);
|
|
15337
|
-
if (key.length === 0)
|
|
15338
|
-
continue;
|
|
15339
|
-
if (seen.has(key))
|
|
15340
|
-
continue;
|
|
15341
|
-
seen.add(key);
|
|
15342
|
-
out.push(raw.trim());
|
|
15343
|
-
}
|
|
15344
|
-
return out;
|
|
15345
|
-
}
|
|
15346
|
-
function defaultSocketName(agentName) {
|
|
15347
|
-
return `switchroom-${agentName}`;
|
|
15348
|
-
}
|
|
15349
|
-
function makeTmuxRunner(tmuxBin) {
|
|
15350
|
-
return {
|
|
15351
|
-
capture(socket, session) {
|
|
15352
|
-
try {
|
|
15353
|
-
return execFileSync(tmuxBin, ["-L", socket, "capture-pane", "-p", "-t", session, "-S", "-200"], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
15354
|
-
} catch {
|
|
15355
|
-
return null;
|
|
15356
|
-
}
|
|
15357
|
-
},
|
|
15358
|
-
send(socket, session, args) {
|
|
15359
|
-
const [subcmd, ...rest] = args;
|
|
15360
|
-
const flagEnd = rest.findIndex((a) => !a.startsWith("-"));
|
|
15361
|
-
const flagsBeforeKeys = flagEnd === -1 ? rest : rest.slice(0, flagEnd);
|
|
15362
|
-
const keys = flagEnd === -1 ? [] : rest.slice(flagEnd);
|
|
15363
|
-
execFileSync(tmuxBin, ["-L", socket, subcmd, ...flagsBeforeKeys, "-t", session, ...keys], { stdio: ["pipe", "pipe", "pipe"] });
|
|
15364
|
-
},
|
|
15365
|
-
hasSession(socket, session) {
|
|
15366
|
-
try {
|
|
15367
|
-
execFileSync(tmuxBin, ["-L", socket, "has-session", "-t", session], {
|
|
15368
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
15369
|
-
});
|
|
15370
|
-
return true;
|
|
15371
|
-
} catch {
|
|
15372
|
-
return false;
|
|
15373
|
-
}
|
|
15374
|
-
}
|
|
15375
|
-
};
|
|
15376
|
-
}
|
|
15377
|
-
function sleep(ms) {
|
|
15378
|
-
return new Promise((res) => setTimeout(res, ms));
|
|
15379
|
-
}
|
|
15380
|
-
function isTuiChromeLine(line) {
|
|
15381
|
-
const t = line.trim();
|
|
15382
|
-
if (t.length === 0)
|
|
15383
|
-
return false;
|
|
15384
|
-
if (/^[\s\-_\u2502\u2500\u256d\u256e\u2570\u256f\u250c\u2510\u2514\u2518|]+$/.test(t))
|
|
15385
|
-
return true;
|
|
15386
|
-
if (/^[\u256d\u256e\u2570\u256f\u2502\s]*[>)\u276f][\u256d\u256e\u2570\u256f\u2502\s]*$/.test(t))
|
|
15387
|
-
return true;
|
|
15388
|
-
if (/accept edits on/i.test(t))
|
|
15389
|
-
return true;
|
|
15390
|
-
if (/shift\+tab to cycle/i.test(t))
|
|
15391
|
-
return true;
|
|
15392
|
-
if (/for agents/i.test(t))
|
|
15393
|
-
return true;
|
|
15394
|
-
if (/\? for shortcuts/i.test(t))
|
|
15395
|
-
return true;
|
|
15396
|
-
if (/bypassing permissions/i.test(t))
|
|
15397
|
-
return true;
|
|
15398
|
-
if (t === "copy")
|
|
15399
|
-
return true;
|
|
15400
|
-
return false;
|
|
15401
|
-
}
|
|
15402
|
-
function diffPane(before, after, command) {
|
|
15403
|
-
if (command) {
|
|
15404
|
-
const escaped = command.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
15405
|
-
const lines = after.split(`
|
|
15406
|
-
`);
|
|
15407
|
-
let anchorIdx = -1;
|
|
15408
|
-
for (let i = lines.length - 1;i >= 0; i--) {
|
|
15409
|
-
if (/[\u276f>]\s+/.test(lines[i]) && lines[i].includes(escaped)) {
|
|
15410
|
-
anchorIdx = i;
|
|
15411
|
-
break;
|
|
15412
|
-
}
|
|
15413
|
-
}
|
|
15414
|
-
if (anchorIdx >= 0) {
|
|
15415
|
-
const tail = lines.slice(anchorIdx + 1);
|
|
15416
|
-
const trimmed = [];
|
|
15417
|
-
for (const raw of tail) {
|
|
15418
|
-
const line = raw.trimEnd();
|
|
15419
|
-
if (line.length === 0 && trimmed.length === 0)
|
|
15420
|
-
continue;
|
|
15421
|
-
if (isTuiChromeLine(line))
|
|
15422
|
-
continue;
|
|
15423
|
-
trimmed.push(line);
|
|
15424
|
-
}
|
|
15425
|
-
while (trimmed.length > 0 && trimmed[trimmed.length - 1].length === 0) {
|
|
15426
|
-
trimmed.pop();
|
|
15427
|
-
}
|
|
15428
|
-
const affordances = /^(esc to cancel|press any key|\u21b5 select)/i;
|
|
15429
|
-
while (trimmed.length > 0 && affordances.test(trimmed[trimmed.length - 1].trim())) {
|
|
15430
|
-
trimmed.pop();
|
|
15431
|
-
}
|
|
15432
|
-
if (trimmed.length > 0) {
|
|
15433
|
-
return { output: trimmed.join(`
|
|
15434
|
-
`), anchored: true };
|
|
15435
|
-
}
|
|
15436
|
-
}
|
|
15437
|
-
}
|
|
15438
|
-
const beforeSet = new Set(before.split(`
|
|
15439
|
-
`).map((l) => l.trimEnd()));
|
|
15440
|
-
const newLines = [];
|
|
15441
|
-
for (const raw of after.split(`
|
|
15442
|
-
`)) {
|
|
15443
|
-
const line = raw.trimEnd();
|
|
15444
|
-
if (line.length === 0)
|
|
15445
|
-
continue;
|
|
15446
|
-
if (beforeSet.has(line))
|
|
15447
|
-
continue;
|
|
15448
|
-
if (isTuiChromeLine(line))
|
|
15449
|
-
continue;
|
|
15450
|
-
newLines.push(line);
|
|
15451
|
-
}
|
|
15452
|
-
return { output: newLines.join(`
|
|
15453
|
-
`), anchored: false };
|
|
15454
|
-
}
|
|
15455
|
-
async function injectSlashCommand(agentName, command, opts = {}) {
|
|
15456
|
-
validateInjectCommand(command);
|
|
15457
|
-
const tmuxBin = opts.tmuxBin ?? "tmux";
|
|
15458
|
-
const socket = opts.socketName ?? defaultSocketName(agentName);
|
|
15459
|
-
const session = opts.sessionName ?? agentName;
|
|
15460
|
-
const settleMs = opts.settleMs ?? 2000;
|
|
15461
|
-
const signalMode = !!(opts.successPattern || opts.errorPattern);
|
|
15462
|
-
const timeoutMs = opts.timeoutMs ?? (signalMode ? 8000 : 5000);
|
|
15463
|
-
return withPaneLock(`${socket}:${session}`, () => injectSlashCommandWith(makeTmuxRunner(tmuxBin), {
|
|
15464
|
-
socket,
|
|
15465
|
-
session,
|
|
15466
|
-
command: command.trim(),
|
|
15467
|
-
settleMs,
|
|
15468
|
-
timeoutMs,
|
|
15469
|
-
precondition: opts.precondition,
|
|
15470
|
-
successPattern: opts.successPattern,
|
|
15471
|
-
errorPattern: opts.errorPattern,
|
|
15472
|
-
settleBeforeSendMs: opts.settleBeforeSendMs
|
|
15473
|
-
}));
|
|
15474
|
-
}
|
|
15475
|
-
async function injectSlashCommandWith(runner, args) {
|
|
15476
|
-
const {
|
|
15477
|
-
socket,
|
|
15478
|
-
session,
|
|
15479
|
-
command,
|
|
15480
|
-
settleMs,
|
|
15481
|
-
timeoutMs,
|
|
15482
|
-
precondition,
|
|
15483
|
-
successPattern,
|
|
15484
|
-
errorPattern,
|
|
15485
|
-
settleBeforeSendMs
|
|
15486
|
-
} = args;
|
|
15487
|
-
let bareVerb;
|
|
15488
|
-
try {
|
|
15489
|
-
bareVerb = validateInjectCommand(command);
|
|
15490
|
-
} catch (err) {
|
|
15491
|
-
if (err instanceof InjectError) {
|
|
15492
|
-
return {
|
|
15493
|
-
outcome: "failed",
|
|
15494
|
-
output: "",
|
|
15495
|
-
truncated: false,
|
|
15496
|
-
command: command.trim().split(/\s+/, 1)[0]?.toLowerCase() ?? "",
|
|
15497
|
-
meta: null,
|
|
15498
|
-
errorCode: err.code,
|
|
15499
|
-
errorMessage: err.message
|
|
15500
|
-
};
|
|
15501
|
-
}
|
|
15502
|
-
throw err;
|
|
15503
|
-
}
|
|
15504
|
-
const meta = INJECT_COMMANDS.get(bareVerb) ?? null;
|
|
15505
|
-
if (!runner.hasSession(socket, session)) {
|
|
15506
|
-
return {
|
|
15507
|
-
outcome: "failed",
|
|
15508
|
-
output: "",
|
|
15509
|
-
truncated: false,
|
|
15510
|
-
command: bareVerb,
|
|
15511
|
-
meta,
|
|
15512
|
-
errorCode: "session_missing",
|
|
15513
|
-
errorMessage: `tmux session "${session}" on socket "${socket}" not found. ` + `Is the agent running under the tmux supervisor (the default)? ` + `If experimental.legacy_pty=true is set, inject is unsupported.`
|
|
15514
|
-
};
|
|
15515
|
-
}
|
|
15516
|
-
if (precondition && !precondition()) {
|
|
15517
|
-
return {
|
|
15518
|
-
outcome: "skipped",
|
|
15519
|
-
output: "",
|
|
15520
|
-
truncated: false,
|
|
15521
|
-
command: bareVerb,
|
|
15522
|
-
meta,
|
|
15523
|
-
errorCode: "precondition_failed",
|
|
15524
|
-
errorMessage: "inject precondition returned false at write time; send aborted " + "(no keys sent)."
|
|
15525
|
-
};
|
|
15526
|
-
}
|
|
15527
|
-
if (settleBeforeSendMs && settleBeforeSendMs > 0) {
|
|
15528
|
-
const settleStart = Date.now();
|
|
15529
|
-
let prevSettle = runner.capture(socket, session) ?? "";
|
|
15530
|
-
while (Date.now() - settleStart < settleBeforeSendMs) {
|
|
15531
|
-
await sleep(POLL_INTERVAL_MS);
|
|
15532
|
-
const cur = runner.capture(socket, session) ?? "";
|
|
15533
|
-
if (cur === prevSettle)
|
|
15534
|
-
break;
|
|
15535
|
-
prevSettle = cur;
|
|
15536
|
-
}
|
|
15537
|
-
}
|
|
15538
|
-
const before = runner.capture(socket, session) ?? "";
|
|
15539
|
-
try {
|
|
15540
|
-
runner.send(socket, session, ["send-keys", "-l", command]);
|
|
15541
|
-
runner.send(socket, session, ["send-keys", "Enter"]);
|
|
15542
|
-
} catch (err) {
|
|
15543
|
-
return {
|
|
15544
|
-
outcome: "failed",
|
|
15545
|
-
output: "",
|
|
15546
|
-
truncated: false,
|
|
15547
|
-
command: bareVerb,
|
|
15548
|
-
meta,
|
|
15549
|
-
errorCode: "tmux_failed",
|
|
15550
|
-
errorMessage: `tmux send-keys failed: ${err instanceof Error ? err.message : String(err)}`
|
|
15551
|
-
};
|
|
15552
|
-
}
|
|
15553
|
-
const start = Date.now();
|
|
15554
|
-
let last = before;
|
|
15555
|
-
let stableSince = null;
|
|
15556
|
-
const signalMode = !!(successPattern || errorPattern);
|
|
15557
|
-
while (Date.now() - start < timeoutMs) {
|
|
15558
|
-
await sleep(POLL_INTERVAL_MS);
|
|
15559
|
-
const cur = runner.capture(socket, session) ?? "";
|
|
15560
|
-
if (signalMode) {
|
|
15561
|
-
last = cur;
|
|
15562
|
-
if (cur !== before) {
|
|
15563
|
-
const { output: region } = diffPane(before, cur, command);
|
|
15564
|
-
const regionLines = region.split(`
|
|
15565
|
-
`).map((l) => l.trim());
|
|
15566
|
-
if (errorPattern && regionLines.some((l) => errorPattern.test(l)))
|
|
15567
|
-
break;
|
|
15568
|
-
if (successPattern && regionLines.some((l) => successPattern.test(l)))
|
|
15569
|
-
break;
|
|
15570
|
-
}
|
|
15571
|
-
continue;
|
|
15572
|
-
}
|
|
15573
|
-
if (cur === last && cur !== before) {
|
|
15574
|
-
if (stableSince === null) {
|
|
15575
|
-
stableSince = Date.now();
|
|
15576
|
-
} else if (Date.now() - stableSince >= POLL_INTERVAL_MS) {
|
|
15577
|
-
last = cur;
|
|
15578
|
-
break;
|
|
15579
|
-
}
|
|
15580
|
-
} else {
|
|
15581
|
-
stableSince = null;
|
|
15582
|
-
}
|
|
15583
|
-
last = cur;
|
|
15584
|
-
if (Date.now() - start >= settleMs && cur !== before) {
|
|
15585
|
-
break;
|
|
15586
|
-
}
|
|
15587
|
-
}
|
|
15588
|
-
const { output: rawOutput, anchored } = diffPane(before, last, command);
|
|
15589
|
-
let output = rawOutput;
|
|
15590
|
-
let truncated = false;
|
|
15591
|
-
const bytes = Buffer.byteLength(output, "utf-8");
|
|
15592
|
-
if (bytes > OUTPUT_BYTE_CAP) {
|
|
15593
|
-
while (Buffer.byteLength(output, "utf-8") > OUTPUT_BYTE_CAP) {
|
|
15594
|
-
output = output.slice(Math.floor(output.length * 0.1) + 1);
|
|
15595
|
-
}
|
|
15596
|
-
truncated = true;
|
|
15597
|
-
}
|
|
15598
|
-
if (meta?.dialog) {
|
|
15599
|
-
try {
|
|
15600
|
-
runner.send(socket, session, ["send-keys", "Escape"]);
|
|
15601
|
-
} catch {}
|
|
15602
|
-
}
|
|
15603
|
-
if (output.trim().length === 0) {
|
|
15604
|
-
return {
|
|
15605
|
-
outcome: "ok_no_output",
|
|
15606
|
-
output: "",
|
|
15607
|
-
truncated: false,
|
|
15608
|
-
command: bareVerb,
|
|
15609
|
-
meta,
|
|
15610
|
-
...anchored ? {} : { diagnostic: "anchor_missing" }
|
|
15611
|
-
};
|
|
15612
|
-
}
|
|
15613
|
-
return {
|
|
15614
|
-
outcome: "ok",
|
|
15615
|
-
output,
|
|
15616
|
-
truncated,
|
|
15617
|
-
command: bareVerb,
|
|
15618
|
-
meta,
|
|
15619
|
-
...truncated ? { diagnostic: "truncated_output" } : {}
|
|
15620
|
-
};
|
|
15621
|
-
}
|
|
15622
|
-
var INJECT_COMMANDS, INJECT_ALLOWLIST, INJECT_BLOCKED, INJECT_BLOCKLIST, InjectError, POLL_INTERVAL_MS = 150, OUTPUT_BYTE_CAP = 3000;
|
|
15623
|
-
var init_inject = __esm(() => {
|
|
15624
|
-
init_pane_lock();
|
|
15625
|
-
INJECT_COMMANDS = new Map([
|
|
15626
|
-
["/cost", { description: "Show session cost", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
15627
|
-
["/status", { description: "Show session status", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
15628
|
-
["/usage", { description: "Show plan quota", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
15629
|
-
["/hooks", { description: "List configured hooks", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
15630
|
-
["/memory", { description: "Open memory picker", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
15631
|
-
["/help", { description: "Show help / command discovery", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
15632
|
-
["/context", { description: "Show context window usage", expectsOutput: true, argsAllowed: false }],
|
|
15633
|
-
["/release-notes", { description: "Show release-notes version list", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
15634
|
-
["/model", { description: "Open model picker", expectsOutput: true, argsAllowed: true }],
|
|
15635
|
-
[
|
|
15636
|
-
"/clear",
|
|
15637
|
-
{
|
|
15638
|
-
description: "Clear session screen",
|
|
15639
|
-
expectsOutput: false,
|
|
15640
|
-
silentNote: "context cleared \u2014 fresh slate",
|
|
15641
|
-
argsAllowed: false
|
|
15642
|
-
}
|
|
15643
|
-
],
|
|
15644
|
-
[
|
|
15645
|
-
"/compact",
|
|
15646
|
-
{
|
|
15647
|
-
description: "Compact conversation history",
|
|
15648
|
-
expectsOutput: false,
|
|
15649
|
-
silentNote: "compaction runs silently",
|
|
15650
|
-
argsAllowed: false
|
|
15651
|
-
}
|
|
15652
|
-
]
|
|
15653
|
-
]);
|
|
15654
|
-
INJECT_ALLOWLIST = new Set(INJECT_COMMANDS.keys());
|
|
15655
|
-
INJECT_BLOCKED = new Map([
|
|
15656
|
-
["/login", { reason: "would mutate auth state" }],
|
|
15657
|
-
["/logout", { reason: "would terminate the agent's auth session" }],
|
|
15658
|
-
["/exit", { reason: "would kill the agent process" }],
|
|
15659
|
-
["/quit", { reason: "would kill the agent process" }],
|
|
15660
|
-
["/upgrade", { reason: "mutates the Claude Code installation" }],
|
|
15661
|
-
["/init", { reason: "generates/overwrites CLAUDE.md and runs a model turn" }],
|
|
15662
|
-
["/mcp", { reason: "opens an interactive MCP server management dialog" }],
|
|
15663
|
-
["/permissions", { reason: "opens an interactive permissions editor that mutates tool policy" }],
|
|
15664
|
-
["/install-github-app", { reason: "runs a network/OAuth install flow" }],
|
|
15665
|
-
["/add-dir", { reason: "mutates the session's working-directory set" }],
|
|
15666
|
-
["/terminal-setup", { reason: "mutates terminal keybinding configuration" }],
|
|
15667
|
-
["/privacy-settings", { reason: "opens an interactive privacy-settings dialog" }],
|
|
15668
|
-
["/bug", { reason: "submits a bug report over the network" }],
|
|
15669
|
-
[
|
|
15670
|
-
"/effort",
|
|
15671
|
-
{
|
|
15672
|
-
reason: "leaves a blocking confirmation modal open and wedges the pane; use the /effort command (it drives the modal), not raw inject"
|
|
15673
|
-
}
|
|
15674
|
-
]
|
|
15675
|
-
]);
|
|
15676
|
-
INJECT_BLOCKLIST = new Set(INJECT_BLOCKED.keys());
|
|
15677
|
-
InjectError = class InjectError extends Error {
|
|
15678
|
-
code;
|
|
15679
|
-
constructor(code, message) {
|
|
15680
|
-
super(message);
|
|
15681
|
-
this.name = "InjectError";
|
|
15682
|
-
this.code = code;
|
|
15683
|
-
}
|
|
15684
|
-
};
|
|
15685
|
-
});
|
|
15686
|
-
|
|
15687
|
-
// src/agents/model-picker.ts
|
|
15688
|
-
var init_model_picker = __esm(() => {
|
|
15689
|
-
init_inject();
|
|
15690
|
-
init_pane_lock();
|
|
15691
|
-
});
|
|
15692
|
-
|
|
15693
15268
|
// telegram-plugin/gateway/model-command.ts
|
|
15694
15269
|
function isClaudeModel(name) {
|
|
15695
15270
|
const lower = name.toLowerCase();
|
|
@@ -15699,7 +15274,6 @@ function isClaudeModel(name) {
|
|
|
15699
15274
|
}
|
|
15700
15275
|
var MODEL_ALIASES;
|
|
15701
15276
|
var init_model_command = __esm(() => {
|
|
15702
|
-
init_model_picker();
|
|
15703
15277
|
MODEL_ALIASES = ["opus", "sonnet", "haiku", "fable", "default"];
|
|
15704
15278
|
});
|
|
15705
15279
|
|
|
@@ -15771,7 +15345,7 @@ var init_atomic = __esm(() => {
|
|
|
15771
15345
|
});
|
|
15772
15346
|
|
|
15773
15347
|
// src/setup/hindsight.ts
|
|
15774
|
-
import { execFileSync
|
|
15348
|
+
import { execFileSync } from "node:child_process";
|
|
15775
15349
|
import { createServer } from "node:net";
|
|
15776
15350
|
function hindsightImageRef(tag) {
|
|
15777
15351
|
const normalized = normalizeHindsightVersionTag(tag);
|
|
@@ -15806,7 +15380,7 @@ function describePortHolder(port) {
|
|
|
15806
15380
|
];
|
|
15807
15381
|
for (const [cmd, args] of probes) {
|
|
15808
15382
|
try {
|
|
15809
|
-
const out =
|
|
15383
|
+
const out = execFileSync(cmd, args, {
|
|
15810
15384
|
stdio: "pipe",
|
|
15811
15385
|
encoding: "utf-8"
|
|
15812
15386
|
}).trim();
|
|
@@ -15863,7 +15437,7 @@ async function pickHindsightPorts() {
|
|
|
15863
15437
|
}
|
|
15864
15438
|
function isDockerAvailable() {
|
|
15865
15439
|
try {
|
|
15866
|
-
|
|
15440
|
+
execFileSync("docker", ["--version"], { stdio: "pipe" });
|
|
15867
15441
|
return true;
|
|
15868
15442
|
} catch {
|
|
15869
15443
|
return false;
|
|
@@ -15871,7 +15445,7 @@ function isDockerAvailable() {
|
|
|
15871
15445
|
}
|
|
15872
15446
|
function isHindsightRunning() {
|
|
15873
15447
|
try {
|
|
15874
|
-
const output =
|
|
15448
|
+
const output = execFileSync("docker", ["ps", "--filter", "name=switchroom-hindsight", "--format", "{{.Status}}"], { stdio: "pipe", encoding: "utf-8" });
|
|
15875
15449
|
return output.trim().length > 0;
|
|
15876
15450
|
} catch {
|
|
15877
15451
|
return false;
|
|
@@ -15879,7 +15453,7 @@ function isHindsightRunning() {
|
|
|
15879
15453
|
}
|
|
15880
15454
|
function isHindsightContainerExists() {
|
|
15881
15455
|
try {
|
|
15882
|
-
const output =
|
|
15456
|
+
const output = execFileSync("docker", ["ps", "-a", "--filter", "name=switchroom-hindsight", "--format", "{{.Names}}"], { stdio: "pipe", encoding: "utf-8" });
|
|
15883
15457
|
return output.trim().length > 0;
|
|
15884
15458
|
} catch {
|
|
15885
15459
|
return false;
|
|
@@ -15987,7 +15561,7 @@ x-litellm-tags: service:hindsight`);
|
|
|
15987
15561
|
}
|
|
15988
15562
|
args.push("-v", "switchroom-hindsight-data:/home/hindsight/.pg0", "-v", "switchroom-hindsight-backups:/backups", "-v", `${HINDSIGHT_BROKER_SOCK_VOLUME}:/run/switchroom/auth-broker`, ...mirrorDir ? ["-v", `${HINDSIGHT_CREDS_MIRROR_VOLUME}:${HINDSIGHT_CRED_DIR}`] : ["--tmpfs", `${HINDSIGHT_CRED_DIR}:rw,mode=0700,uid=${HINDSIGHT_DEFAULT_UID},gid=${HINDSIGHT_DEFAULT_UID}`], ...envArgs, hindsightImageRef(imageTag));
|
|
15989
15563
|
if (mirrorDir) {
|
|
15990
|
-
|
|
15564
|
+
execFileSync("docker", [
|
|
15991
15565
|
"run",
|
|
15992
15566
|
"--rm",
|
|
15993
15567
|
"--user",
|
|
@@ -16001,25 +15575,25 @@ x-litellm-tags: service:hindsight`);
|
|
|
16001
15575
|
`chown ${HINDSIGHT_DEFAULT_UID}:${HINDSIGHT_DEFAULT_UID} ${HINDSIGHT_CRED_DIR} && chmod 0700 ${HINDSIGHT_CRED_DIR}`
|
|
16002
15576
|
], { stdio: "pipe" });
|
|
16003
15577
|
}
|
|
16004
|
-
|
|
15578
|
+
execFileSync("docker", args, { stdio: "pipe" });
|
|
16005
15579
|
}
|
|
16006
15580
|
function stopHindsight() {
|
|
16007
15581
|
try {
|
|
16008
|
-
|
|
15582
|
+
execFileSync("docker", ["stop", "switchroom-hindsight"], { stdio: "pipe" });
|
|
16009
15583
|
} catch {}
|
|
16010
15584
|
try {
|
|
16011
|
-
|
|
15585
|
+
execFileSync("docker", ["rm", "switchroom-hindsight"], { stdio: "pipe" });
|
|
16012
15586
|
} catch {}
|
|
16013
15587
|
}
|
|
16014
15588
|
function pullHindsightImage(imageTag) {
|
|
16015
|
-
|
|
15589
|
+
execFileSync("docker", ["pull", hindsightImageRef(imageTag)], { stdio: "inherit" });
|
|
16016
15590
|
}
|
|
16017
15591
|
function getRunningHindsightPorts() {
|
|
16018
15592
|
const CONTAINER_API_PORT = 8888;
|
|
16019
15593
|
const CONTAINER_UI_PORT = 9999;
|
|
16020
15594
|
const readPort = (containerPort) => {
|
|
16021
15595
|
try {
|
|
16022
|
-
const out =
|
|
15596
|
+
const out = execFileSync("docker", ["port", "switchroom-hindsight", `${containerPort}/tcp`], { stdio: "pipe", encoding: "utf-8" });
|
|
16023
15597
|
const m = out.split(`
|
|
16024
15598
|
`).map((l) => l.trim()).find((l) => l.length > 0);
|
|
16025
15599
|
if (!m)
|
|
@@ -16040,7 +15614,7 @@ function getRunningHindsightPorts() {
|
|
|
16040
15614
|
function getHindsightPortsFromEnv() {
|
|
16041
15615
|
let env2;
|
|
16042
15616
|
try {
|
|
16043
|
-
env2 =
|
|
15617
|
+
env2 = execFileSync("docker", ["inspect", "--format", "{{.Config.Env}}", "switchroom-hindsight"], { stdio: "pipe", encoding: "utf-8" });
|
|
16044
15618
|
} catch {
|
|
16045
15619
|
return null;
|
|
16046
15620
|
}
|
|
@@ -16060,7 +15634,7 @@ function getHindsightPortsFromEnv() {
|
|
|
16060
15634
|
}
|
|
16061
15635
|
function getHindsightStatus() {
|
|
16062
15636
|
try {
|
|
16063
|
-
const output =
|
|
15637
|
+
const output = execFileSync("docker", ["ps", "-a", "--filter", "name=switchroom-hindsight", "--format", "{{.Status}}"], { stdio: "pipe", encoding: "utf-8" });
|
|
16064
15638
|
const status = output.trim();
|
|
16065
15639
|
return status.length > 0 ? status : null;
|
|
16066
15640
|
} catch {
|
|
@@ -16899,7 +16473,7 @@ function parseSocketIdentity(socketPath, spec) {
|
|
|
16899
16473
|
}
|
|
16900
16474
|
|
|
16901
16475
|
// src/vault/broker/peercred.ts
|
|
16902
|
-
import { execFileSync as
|
|
16476
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
16903
16477
|
import { readFileSync as readFileSync4, readlinkSync, fstatSync } from "node:fs";
|
|
16904
16478
|
function socketPathToIdentity(socketPath) {
|
|
16905
16479
|
return parseSocketIdentity(socketPath, {
|
|
@@ -17072,7 +16646,7 @@ function identify(socketPath, socket, execFileSyncOverride) {
|
|
|
17072
16646
|
if (process.platform !== "linux") {
|
|
17073
16647
|
return null;
|
|
17074
16648
|
}
|
|
17075
|
-
const runner = execFileSyncOverride ??
|
|
16649
|
+
const runner = execFileSyncOverride ?? execFileSync2;
|
|
17076
16650
|
let pid = null;
|
|
17077
16651
|
if (socket !== undefined) {
|
|
17078
16652
|
const fd = fdFromSocket(socket);
|
|
@@ -17165,7 +16739,7 @@ var init_agent_uid = __esm(() => {
|
|
|
17165
16739
|
});
|
|
17166
16740
|
|
|
17167
16741
|
// src/agents/agent-owned-tree.ts
|
|
17168
|
-
import { execFileSync as
|
|
16742
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
17169
16743
|
import { existsSync as existsSync5, lstatSync, statSync as statSync3 } from "node:fs";
|
|
17170
16744
|
function alignAgentTreeOwnershipIfRoot(name, agentDir) {
|
|
17171
16745
|
if (ownershipRuntime.geteuid() !== 0)
|
|
@@ -17206,7 +16780,7 @@ var init_agent_owned_tree = __esm(() => {
|
|
|
17206
16780
|
ownershipRuntime = {
|
|
17207
16781
|
geteuid: () => process.geteuid?.(),
|
|
17208
16782
|
chownTree: (uid, gid, rootDir) => {
|
|
17209
|
-
|
|
16783
|
+
execFileSync3("chown", ["-h", "-R", `${uid}:${gid}`, rootDir], {
|
|
17210
16784
|
stdio: ["ignore", "ignore", "pipe"]
|
|
17211
16785
|
});
|
|
17212
16786
|
}
|
|
@@ -24101,6 +23675,17 @@ var init_reconcile_default_skills = __esm(() => {
|
|
|
24101
23675
|
});
|
|
24102
23676
|
|
|
24103
23677
|
// src/agents/sub-agent-telegram-prompt.ts
|
|
23678
|
+
function buildSubAgentLocalTimeLine(tz) {
|
|
23679
|
+
return `
|
|
23680
|
+
|
|
23681
|
+
## Local time
|
|
23682
|
+
|
|
23683
|
+
This agent's configured timezone is **${tz}**, and the container clock is ALREADY set to it (the \`TZ\` env is wired at apply time). So the system clock is local time, not UTC: \`date\` in a shell and \`Date.now()\` in code both yield correct **local** time in \`${tz}\`. When you report or reason about the current time, "how long ago" something was, or scheduling, treat the local wall clock as "now" \u2014 never assume or emit UTC. If you need a fresh timestamp, read it live (e.g. run \`date '+%A %Y-%m-%d %I:%M %p %Z'\`) rather than guessing.
|
|
23684
|
+
`;
|
|
23685
|
+
}
|
|
23686
|
+
function applySubAgentLocalTimeGuidance(body, tz) {
|
|
23687
|
+
return body + buildSubAgentLocalTimeLine(tz);
|
|
23688
|
+
}
|
|
24104
23689
|
function shouldAppendTelegramProgressGuidance(args) {
|
|
24105
23690
|
return args.telegramEnabled && args.defaultChatId != null && args.defaultChatId.length > 0;
|
|
24106
23691
|
}
|
|
@@ -25984,7 +25569,7 @@ var init_onboarding = __esm(() => {
|
|
|
25984
25569
|
});
|
|
25985
25570
|
|
|
25986
25571
|
// src/repos/bare-clone.ts
|
|
25987
|
-
import { execFileSync as
|
|
25572
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
25988
25573
|
import { existsSync as existsSync14, mkdirSync as mkdirSync8 } from "node:fs";
|
|
25989
25574
|
import { resolve as resolve9 } from "node:path";
|
|
25990
25575
|
function bareClonePath(slug) {
|
|
@@ -25997,7 +25582,7 @@ function ensureBareClone(slug, url) {
|
|
|
25997
25582
|
if (!existsSync14(clonePath)) {
|
|
25998
25583
|
process.stderr.write(`[switchroom] repo "${slug}": cloning ${url} \u2026
|
|
25999
25584
|
`);
|
|
26000
|
-
|
|
25585
|
+
execFileSync4("git", ["clone", "--bare", url, clonePath], {
|
|
26001
25586
|
stdio: ["ignore", "pipe", "pipe"]
|
|
26002
25587
|
});
|
|
26003
25588
|
process.stderr.write(`[switchroom] repo "${slug}": bare clone ready at ${clonePath}
|
|
@@ -26006,7 +25591,7 @@ function ensureBareClone(slug, url) {
|
|
|
26006
25591
|
process.stderr.write(`[switchroom] repo "${slug}": fetching ${clonePath} \u2026
|
|
26007
25592
|
`);
|
|
26008
25593
|
try {
|
|
26009
|
-
|
|
25594
|
+
execFileSync4("git", ["fetch", "--all"], {
|
|
26010
25595
|
cwd: clonePath,
|
|
26011
25596
|
stdio: ["ignore", "pipe", "pipe"]
|
|
26012
25597
|
});
|
|
@@ -26023,7 +25608,7 @@ var init_bare_clone = __esm(() => {
|
|
|
26023
25608
|
});
|
|
26024
25609
|
|
|
26025
25610
|
// src/repos/agent-worktree.ts
|
|
26026
|
-
import { execFileSync as
|
|
25611
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
26027
25612
|
import { existsSync as existsSync15, mkdirSync as mkdirSync9, readdirSync as readdirSync5 } from "node:fs";
|
|
26028
25613
|
import { join as join9, resolve as resolve10 } from "node:path";
|
|
26029
25614
|
function agentBranchName(agentName) {
|
|
@@ -26034,7 +25619,7 @@ function agentWorktreePath(agentDir, slug) {
|
|
|
26034
25619
|
}
|
|
26035
25620
|
function isWorktreeDirty(worktreePath) {
|
|
26036
25621
|
try {
|
|
26037
|
-
const out =
|
|
25622
|
+
const out = execFileSync5("git", ["status", "--porcelain"], {
|
|
26038
25623
|
cwd: worktreePath,
|
|
26039
25624
|
stdio: ["ignore", "pipe", "pipe"],
|
|
26040
25625
|
encoding: "utf-8"
|
|
@@ -26046,7 +25631,7 @@ function isWorktreeDirty(worktreePath) {
|
|
|
26046
25631
|
}
|
|
26047
25632
|
function headShortSha(worktreePath) {
|
|
26048
25633
|
try {
|
|
26049
|
-
return
|
|
25634
|
+
return execFileSync5("git", ["rev-parse", "--short", "HEAD"], {
|
|
26050
25635
|
cwd: worktreePath,
|
|
26051
25636
|
stdio: ["ignore", "pipe", "pipe"],
|
|
26052
25637
|
encoding: "utf-8"
|
|
@@ -26057,7 +25642,7 @@ function headShortSha(worktreePath) {
|
|
|
26057
25642
|
}
|
|
26058
25643
|
function branchExistsInBare(bareClonePath2, branchName) {
|
|
26059
25644
|
try {
|
|
26060
|
-
|
|
25645
|
+
execFileSync5("git", ["rev-parse", "--verify", `refs/heads/${branchName}`], {
|
|
26061
25646
|
cwd: bareClonePath2,
|
|
26062
25647
|
stdio: ["ignore", "pipe", "pipe"]
|
|
26063
25648
|
});
|
|
@@ -26068,7 +25653,7 @@ function branchExistsInBare(bareClonePath2, branchName) {
|
|
|
26068
25653
|
}
|
|
26069
25654
|
function resolveDefaultBranch(bareClonePath2) {
|
|
26070
25655
|
try {
|
|
26071
|
-
const out =
|
|
25656
|
+
const out = execFileSync5("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
|
|
26072
25657
|
cwd: bareClonePath2,
|
|
26073
25658
|
stdio: ["ignore", "pipe", "pipe"],
|
|
26074
25659
|
encoding: "utf-8"
|
|
@@ -26086,12 +25671,12 @@ function ensureAgentWorktree(agentName, slug, bareClonePath2, agentDir) {
|
|
|
26086
25671
|
if (!existsSync15(worktreePath)) {
|
|
26087
25672
|
mkdirSync9(join9(agentDir, "work"), { recursive: true });
|
|
26088
25673
|
if (branchExistsInBare(bareClonePath2, branch)) {
|
|
26089
|
-
|
|
25674
|
+
execFileSync5("git", ["worktree", "add", worktreePath, branch], {
|
|
26090
25675
|
cwd: bareClonePath2,
|
|
26091
25676
|
stdio: ["ignore", "pipe", "pipe"]
|
|
26092
25677
|
});
|
|
26093
25678
|
} else {
|
|
26094
|
-
|
|
25679
|
+
execFileSync5("git", [
|
|
26095
25680
|
"worktree",
|
|
26096
25681
|
"add",
|
|
26097
25682
|
worktreePath,
|
|
@@ -26119,11 +25704,11 @@ function ensureAgentWorktree(agentName, slug, bareClonePath2, agentDir) {
|
|
|
26119
25704
|
};
|
|
26120
25705
|
}
|
|
26121
25706
|
try {
|
|
26122
|
-
|
|
25707
|
+
execFileSync5("git", ["fetch", "origin"], {
|
|
26123
25708
|
cwd: worktreePath,
|
|
26124
25709
|
stdio: ["ignore", "pipe", "pipe"]
|
|
26125
25710
|
});
|
|
26126
|
-
|
|
25711
|
+
execFileSync5("git", ["merge", "--ff-only", `origin/${defaultBranch}`], {
|
|
26127
25712
|
cwd: worktreePath,
|
|
26128
25713
|
stdio: ["ignore", "pipe", "pipe"]
|
|
26129
25714
|
});
|
|
@@ -26141,7 +25726,7 @@ function removeAgentWorktree(agentName, slug, bareClonePath2, agentDir) {
|
|
|
26141
25726
|
const branch = agentBranchName(agentName);
|
|
26142
25727
|
if (existsSync15(worktreePath)) {
|
|
26143
25728
|
try {
|
|
26144
|
-
|
|
25729
|
+
execFileSync5("git", ["worktree", "remove", "--force", worktreePath], {
|
|
26145
25730
|
cwd: bareClonePath2,
|
|
26146
25731
|
stdio: ["ignore", "pipe", "pipe"]
|
|
26147
25732
|
});
|
|
@@ -26153,7 +25738,7 @@ function removeAgentWorktree(agentName, slug, bareClonePath2, agentDir) {
|
|
|
26153
25738
|
}
|
|
26154
25739
|
if (branchExistsInBare(bareClonePath2, branch)) {
|
|
26155
25740
|
try {
|
|
26156
|
-
|
|
25741
|
+
execFileSync5("git", ["branch", "-D", branch], {
|
|
26157
25742
|
cwd: bareClonePath2,
|
|
26158
25743
|
stdio: ["ignore", "pipe", "pipe"]
|
|
26159
25744
|
});
|
|
@@ -26164,7 +25749,7 @@ function removeAgentWorktree(agentName, slug, bareClonePath2, agentDir) {
|
|
|
26164
25749
|
}
|
|
26165
25750
|
}
|
|
26166
25751
|
try {
|
|
26167
|
-
|
|
25752
|
+
execFileSync5("git", ["worktree", "prune"], {
|
|
26168
25753
|
cwd: bareClonePath2,
|
|
26169
25754
|
stdio: ["ignore", "pipe", "pipe"]
|
|
26170
25755
|
});
|
|
@@ -26193,7 +25778,7 @@ import {
|
|
|
26193
25778
|
unlinkSync as unlinkSync4
|
|
26194
25779
|
} from "node:fs";
|
|
26195
25780
|
import { homedir as homedir5 } from "node:os";
|
|
26196
|
-
import { execSync, execFileSync as
|
|
25781
|
+
import { execSync, execFileSync as execFileSync6 } from "node:child_process";
|
|
26197
25782
|
import { join as join10, resolve as resolve11 } from "node:path";
|
|
26198
25783
|
import { createHash as createHash3 } from "node:crypto";
|
|
26199
25784
|
function prependReplyDiscipline(rendered, context) {
|
|
@@ -26327,13 +25912,13 @@ function alignAgentUid(name, agentDir, uid, opts = {}) {
|
|
|
26327
25912
|
`));
|
|
26328
25913
|
}
|
|
26329
25914
|
try {
|
|
26330
|
-
|
|
25915
|
+
execFileSync6("chown", ["-h", "-R", `${uid}:${uid}`, ...paths], {
|
|
26331
25916
|
stdio: ["ignore", "ignore", "pipe"]
|
|
26332
25917
|
});
|
|
26333
25918
|
return { chowned: true, paths };
|
|
26334
25919
|
} catch {}
|
|
26335
25920
|
try {
|
|
26336
|
-
|
|
25921
|
+
execFileSync6("sudo", ["chown", "-h", "-R", `${uid}:${uid}`, ...paths], {
|
|
26337
25922
|
stdio: "inherit"
|
|
26338
25923
|
});
|
|
26339
25924
|
return { chowned: true, paths };
|
|
@@ -27055,7 +26640,7 @@ Thumbs.db
|
|
|
27055
26640
|
execSync("git add -A", { cwd: workspaceDir, stdio: "pipe" });
|
|
27056
26641
|
const userEmail = process.env.GIT_AUTHOR_EMAIL || "switchroom@localhost";
|
|
27057
26642
|
const userName = process.env.GIT_AUTHOR_NAME || "Switchroom Agent";
|
|
27058
|
-
|
|
26643
|
+
execFileSync6("git", [
|
|
27059
26644
|
"-c",
|
|
27060
26645
|
`user.email=${userEmail}`,
|
|
27061
26646
|
"-c",
|
|
@@ -27780,10 +27365,10 @@ ${v.map((i) => ` - ${i}`).join(`
|
|
|
27780
27365
|
}).join(`
|
|
27781
27366
|
`);
|
|
27782
27367
|
const rawBody = saDef.prompt ?? `You are the ${saName} sub-agent.`;
|
|
27783
|
-
const body = applyTelegramProgressGuidance(rawBody, {
|
|
27368
|
+
const body = applySubAgentLocalTimeGuidance(applyTelegramProgressGuidance(rawBody, {
|
|
27784
27369
|
telegramEnabled: true,
|
|
27785
27370
|
defaultChatId: userId
|
|
27786
|
-
});
|
|
27371
|
+
}), resolveTimezone(switchroomConfig ?? {}, agentConfig));
|
|
27787
27372
|
const content = `---
|
|
27788
27373
|
${fmLines}
|
|
27789
27374
|
---
|
|
@@ -28655,10 +28240,10 @@ ${v.map((i) => ` - ${i}`).join(`
|
|
|
28655
28240
|
}).join(`
|
|
28656
28241
|
`);
|
|
28657
28242
|
const rawBody = saDef.prompt ?? `You are the ${saName} sub-agent.`;
|
|
28658
|
-
const body = applyTelegramProgressGuidance(rawBody, {
|
|
28243
|
+
const body = applySubAgentLocalTimeGuidance(applyTelegramProgressGuidance(rawBody, {
|
|
28659
28244
|
telegramEnabled: true,
|
|
28660
28245
|
defaultChatId: greetingUserId
|
|
28661
|
-
});
|
|
28246
|
+
}), resolveTimezone(switchroomConfig, agentConfig));
|
|
28662
28247
|
const content = `---
|
|
28663
28248
|
${fmLines}
|
|
28664
28249
|
---
|
|
@@ -30735,7 +30320,7 @@ var init_write_compose = __esm(() => {
|
|
|
30735
30320
|
});
|
|
30736
30321
|
|
|
30737
30322
|
// src/agents/tmux.ts
|
|
30738
|
-
import { execFileSync as
|
|
30323
|
+
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
30739
30324
|
function sendAgentInterrupt(opts) {
|
|
30740
30325
|
const { agentName } = opts;
|
|
30741
30326
|
const attempts = typeof opts.attempts === "number" && opts.attempts > 0 ? opts.attempts : 1;
|
|
@@ -30745,7 +30330,7 @@ function sendAgentInterrupt(opts) {
|
|
|
30745
30330
|
let lastError = null;
|
|
30746
30331
|
for (let i = 0;i < attempts; i++) {
|
|
30747
30332
|
try {
|
|
30748
|
-
|
|
30333
|
+
execFileSync7("tmux", args, {
|
|
30749
30334
|
timeout: 3000,
|
|
30750
30335
|
stdio: ["ignore", "pipe", "pipe"]
|
|
30751
30336
|
});
|
|
@@ -30786,7 +30371,7 @@ var init_compose_env = () => {};
|
|
|
30786
30371
|
import { resolve as resolve13 } from "node:path";
|
|
30787
30372
|
import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync6 } from "node:fs";
|
|
30788
30373
|
import { homedir as homedir7 } from "node:os";
|
|
30789
|
-
import { execFileSync as
|
|
30374
|
+
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
30790
30375
|
function resolveSwitchroomHome(explicit) {
|
|
30791
30376
|
if (explicit && explicit.length > 0)
|
|
30792
30377
|
return explicit;
|
|
@@ -30817,7 +30402,7 @@ function bringUpAgentService(opts) {
|
|
|
30817
30402
|
const dockerBin = opts.dockerBin ?? "docker";
|
|
30818
30403
|
const stdio = opts.stdio ?? "inherit";
|
|
30819
30404
|
for (const svc of ["vault-broker", "approval-kernel", "switchroom-auth-broker"]) {
|
|
30820
|
-
|
|
30405
|
+
execFileSync8(dockerBin, [
|
|
30821
30406
|
"compose",
|
|
30822
30407
|
"-f",
|
|
30823
30408
|
composePath,
|
|
@@ -30829,7 +30414,7 @@ function bringUpAgentService(opts) {
|
|
|
30829
30414
|
svc
|
|
30830
30415
|
], { stdio });
|
|
30831
30416
|
}
|
|
30832
|
-
|
|
30417
|
+
execFileSync8(dockerBin, [
|
|
30833
30418
|
"compose",
|
|
30834
30419
|
"-f",
|
|
30835
30420
|
composePath,
|
|
@@ -30848,7 +30433,7 @@ var init_docker_fleet = __esm(() => {
|
|
|
30848
30433
|
});
|
|
30849
30434
|
|
|
30850
30435
|
// src/agents/singleton-reconcile.ts
|
|
30851
|
-
import { execFileSync as
|
|
30436
|
+
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
30852
30437
|
import { readFileSync as readFileSync16 } from "node:fs";
|
|
30853
30438
|
function resolveSingletonServices(voiceEngine) {
|
|
30854
30439
|
const engine = voiceEngine ?? loadHostCapabilities()?.voice.engine ?? "cloud";
|
|
@@ -30874,7 +30459,7 @@ function readPinnedSingletonImages(composeText, services = SINGLETON_SERVICES) {
|
|
|
30874
30459
|
}
|
|
30875
30460
|
function defaultInspectImage(dockerBin, container) {
|
|
30876
30461
|
try {
|
|
30877
|
-
const out =
|
|
30462
|
+
const out = execFileSync9(dockerBin, ["inspect", "-f", "{{.Config.Image}}", container], { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], timeout: 5000 }).trim();
|
|
30878
30463
|
return out.length > 0 ? out : null;
|
|
30879
30464
|
} catch {
|
|
30880
30465
|
return null;
|
|
@@ -30900,7 +30485,7 @@ function detectSingletonDrift(deps) {
|
|
|
30900
30485
|
});
|
|
30901
30486
|
}
|
|
30902
30487
|
function defaultRecreate(dockerBin, project, composeFile, service) {
|
|
30903
|
-
|
|
30488
|
+
execFileSync9(dockerBin, ["compose", "-p", project, "-f", composeFile, ...composeEnvFileArgs(composeFile), "up", "-d", "--no-deps", service], { stdio: ["ignore", "pipe", "pipe"], timeout: 120000 });
|
|
30904
30489
|
}
|
|
30905
30490
|
function reconcileSingletons(deps) {
|
|
30906
30491
|
const dockerBin = deps.dockerBin ?? "docker";
|
|
@@ -30943,7 +30528,7 @@ var init_singleton_reconcile = __esm(() => {
|
|
|
30943
30528
|
});
|
|
30944
30529
|
|
|
30945
30530
|
// src/agents/lifecycle.ts
|
|
30946
|
-
import { execFileSync as
|
|
30531
|
+
import { execFileSync as execFileSync10, spawn, spawnSync } from "node:child_process";
|
|
30947
30532
|
import { existsSync as existsSync21, mkdirSync as mkdirSync14, writeFileSync as writeFileSync7, renameSync as renameSync5, readFileSync as readFileSync17 } from "node:fs";
|
|
30948
30533
|
import { resolve as resolve14, join as join14 } from "node:path";
|
|
30949
30534
|
import { connect } from "node:net";
|
|
@@ -30974,7 +30559,7 @@ function buildCliRestartReason(opts) {
|
|
|
30974
30559
|
if (!buildCommit)
|
|
30975
30560
|
return "cli: restart";
|
|
30976
30561
|
try {
|
|
30977
|
-
const head =
|
|
30562
|
+
const head = execFileSync10("git", ["rev-parse", "HEAD"], {
|
|
30978
30563
|
cwd: cwd ?? process.cwd(),
|
|
30979
30564
|
encoding: "utf-8",
|
|
30980
30565
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -30985,7 +30570,7 @@ function buildCliRestartReason(opts) {
|
|
|
30985
30570
|
return "cli: restart";
|
|
30986
30571
|
let subject = "";
|
|
30987
30572
|
try {
|
|
30988
|
-
subject =
|
|
30573
|
+
subject = execFileSync10("git", ["log", "-1", "--pretty=%s", head], {
|
|
30989
30574
|
cwd: cwd ?? process.cwd(),
|
|
30990
30575
|
encoding: "utf-8",
|
|
30991
30576
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -31015,7 +30600,7 @@ function reconcileSingletonImages(log) {
|
|
|
31015
30600
|
}
|
|
31016
30601
|
function dockerSync(args) {
|
|
31017
30602
|
try {
|
|
31018
|
-
return
|
|
30603
|
+
return execFileSync10("docker", args, {
|
|
31019
30604
|
encoding: "utf-8",
|
|
31020
30605
|
stdio: ["ignore", "pipe", "pipe"]
|
|
31021
30606
|
}).trim();
|
|
@@ -31404,12 +30989,12 @@ var init_lifecycle = __esm(() => {
|
|
|
31404
30989
|
});
|
|
31405
30990
|
|
|
31406
30991
|
// src/auth/pane-ready-probe.ts
|
|
31407
|
-
import { execFileSync as
|
|
30992
|
+
import { execFileSync as execFileSync12 } from "node:child_process";
|
|
31408
30993
|
function defaultPaneReadyDeps() {
|
|
31409
30994
|
return {
|
|
31410
30995
|
capturePane(sessionName) {
|
|
31411
30996
|
try {
|
|
31412
|
-
return
|
|
30997
|
+
return execFileSync12("tmux", ["capture-pane", "-p", "-t", sessionName, "-S", "-200"], {
|
|
31413
30998
|
encoding: "utf-8",
|
|
31414
30999
|
stdio: ["pipe", "pipe", "pipe"]
|
|
31415
31000
|
}).trim();
|
|
@@ -31690,7 +31275,7 @@ var init_accounts = __esm(() => {
|
|
|
31690
31275
|
});
|
|
31691
31276
|
|
|
31692
31277
|
// src/auth/manager.ts
|
|
31693
|
-
import { execFileSync as
|
|
31278
|
+
import { execFileSync as execFileSync13 } from "node:child_process";
|
|
31694
31279
|
import {
|
|
31695
31280
|
readFileSync as readFileSync22,
|
|
31696
31281
|
readdirSync as readdirSync11,
|
|
@@ -31741,14 +31326,14 @@ function authSessionName(name, slot) {
|
|
|
31741
31326
|
return `${base}-${slot.replace(/[^a-zA-Z0-9_.-]/g, "-")}`;
|
|
31742
31327
|
}
|
|
31743
31328
|
function tmux(args) {
|
|
31744
|
-
return
|
|
31329
|
+
return execFileSync13("tmux", args, {
|
|
31745
31330
|
encoding: "utf-8",
|
|
31746
31331
|
stdio: ["pipe", "pipe", "pipe"]
|
|
31747
31332
|
}).trim();
|
|
31748
31333
|
}
|
|
31749
31334
|
function tmuxSessionExists(sessionName) {
|
|
31750
31335
|
try {
|
|
31751
|
-
|
|
31336
|
+
execFileSync13("tmux", ["has-session", "-t", sessionName], {
|
|
31752
31337
|
stdio: ["pipe", "pipe", "pipe"]
|
|
31753
31338
|
});
|
|
31754
31339
|
return true;
|
|
@@ -32264,6 +31849,425 @@ function readQuarantineMarkerForAgent(agentsDir, name) {
|
|
|
32264
31849
|
var QUARANTINE_FILENAME = "quarantine.json";
|
|
32265
31850
|
var init_quarantine = () => {};
|
|
32266
31851
|
|
|
31852
|
+
// src/agents/pane-lock.ts
|
|
31853
|
+
function withPaneLock(key, fn) {
|
|
31854
|
+
const tail = tails.get(key) ?? Promise.resolve();
|
|
31855
|
+
const run = tail.then(() => fn());
|
|
31856
|
+
const next = run.then(() => {}, () => {});
|
|
31857
|
+
tails.set(key, next);
|
|
31858
|
+
next.then(() => {
|
|
31859
|
+
if (tails.get(key) === next)
|
|
31860
|
+
tails.delete(key);
|
|
31861
|
+
});
|
|
31862
|
+
return run;
|
|
31863
|
+
}
|
|
31864
|
+
var tails;
|
|
31865
|
+
var init_pane_lock = __esm(() => {
|
|
31866
|
+
tails = new Map;
|
|
31867
|
+
});
|
|
31868
|
+
|
|
31869
|
+
// src/agents/inject.ts
|
|
31870
|
+
var exports_inject = {};
|
|
31871
|
+
__export(exports_inject, {
|
|
31872
|
+
validateInjectCommand: () => validateInjectCommand,
|
|
31873
|
+
normalizeInjectCommand: () => normalizeInjectCommand,
|
|
31874
|
+
makeTmuxRunner: () => makeTmuxRunner,
|
|
31875
|
+
isTuiChromeLine: () => isTuiChromeLine,
|
|
31876
|
+
injectSlashCommandWith: () => injectSlashCommandWith,
|
|
31877
|
+
injectSlashCommand: () => injectSlashCommand,
|
|
31878
|
+
diffPane: () => diffPane,
|
|
31879
|
+
dedupeInjectQueue: () => dedupeInjectQueue,
|
|
31880
|
+
InjectError: () => InjectError,
|
|
31881
|
+
INJECT_COMMANDS: () => INJECT_COMMANDS,
|
|
31882
|
+
INJECT_BLOCKLIST: () => INJECT_BLOCKLIST,
|
|
31883
|
+
INJECT_BLOCKED: () => INJECT_BLOCKED,
|
|
31884
|
+
INJECT_ALLOWLIST: () => INJECT_ALLOWLIST
|
|
31885
|
+
});
|
|
31886
|
+
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
31887
|
+
function validateInjectCommand(command) {
|
|
31888
|
+
if (typeof command !== "string" || command.trim().length === 0) {
|
|
31889
|
+
throw new InjectError("invalid", "command is empty");
|
|
31890
|
+
}
|
|
31891
|
+
const trimmed = command.trim();
|
|
31892
|
+
if (!trimmed.startsWith("/")) {
|
|
31893
|
+
throw new InjectError("invalid", `command must start with "/": got ${trimmed}`);
|
|
31894
|
+
}
|
|
31895
|
+
const bare = trimmed.split(/\s+/, 1)[0].toLowerCase();
|
|
31896
|
+
const blockedMeta = INJECT_BLOCKED.get(bare);
|
|
31897
|
+
if (blockedMeta) {
|
|
31898
|
+
throw new InjectError("blocked", `${bare} is explicitly blocked from inject (${blockedMeta.reason}).`);
|
|
31899
|
+
}
|
|
31900
|
+
const meta = INJECT_COMMANDS.get(bare);
|
|
31901
|
+
if (!meta) {
|
|
31902
|
+
const allowed = [...INJECT_COMMANDS.keys()].sort().join(", ");
|
|
31903
|
+
throw new InjectError("not_allowed", `${bare} is not in the inject allowlist. Allowed: ${allowed}`);
|
|
31904
|
+
}
|
|
31905
|
+
const hasArgs = trimmed.split(/\s+/).length > 1;
|
|
31906
|
+
if (hasArgs && !meta.argsAllowed) {
|
|
31907
|
+
throw new InjectError("args_not_allowed", `args not permitted for ${bare} \u2014 inject the bare verb only`);
|
|
31908
|
+
}
|
|
31909
|
+
return bare;
|
|
31910
|
+
}
|
|
31911
|
+
function normalizeInjectCommand(command) {
|
|
31912
|
+
return command.trim().replace(/\s+/g, " ").toLowerCase();
|
|
31913
|
+
}
|
|
31914
|
+
function dedupeInjectQueue(commands) {
|
|
31915
|
+
const seen = new Set;
|
|
31916
|
+
const out = [];
|
|
31917
|
+
for (const raw of commands) {
|
|
31918
|
+
if (typeof raw !== "string")
|
|
31919
|
+
continue;
|
|
31920
|
+
const key = normalizeInjectCommand(raw);
|
|
31921
|
+
if (key.length === 0)
|
|
31922
|
+
continue;
|
|
31923
|
+
if (seen.has(key))
|
|
31924
|
+
continue;
|
|
31925
|
+
seen.add(key);
|
|
31926
|
+
out.push(raw.trim());
|
|
31927
|
+
}
|
|
31928
|
+
return out;
|
|
31929
|
+
}
|
|
31930
|
+
function defaultSocketName(agentName) {
|
|
31931
|
+
return `switchroom-${agentName}`;
|
|
31932
|
+
}
|
|
31933
|
+
function makeTmuxRunner(tmuxBin) {
|
|
31934
|
+
return {
|
|
31935
|
+
capture(socket, session) {
|
|
31936
|
+
try {
|
|
31937
|
+
return execFileSync15(tmuxBin, ["-L", socket, "capture-pane", "-p", "-t", session, "-S", "-200"], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
31938
|
+
} catch {
|
|
31939
|
+
return null;
|
|
31940
|
+
}
|
|
31941
|
+
},
|
|
31942
|
+
send(socket, session, args) {
|
|
31943
|
+
const [subcmd, ...rest] = args;
|
|
31944
|
+
const flagEnd = rest.findIndex((a) => !a.startsWith("-"));
|
|
31945
|
+
const flagsBeforeKeys = flagEnd === -1 ? rest : rest.slice(0, flagEnd);
|
|
31946
|
+
const keys = flagEnd === -1 ? [] : rest.slice(flagEnd);
|
|
31947
|
+
execFileSync15(tmuxBin, ["-L", socket, subcmd, ...flagsBeforeKeys, "-t", session, ...keys], { stdio: ["pipe", "pipe", "pipe"] });
|
|
31948
|
+
},
|
|
31949
|
+
hasSession(socket, session) {
|
|
31950
|
+
try {
|
|
31951
|
+
execFileSync15(tmuxBin, ["-L", socket, "has-session", "-t", session], {
|
|
31952
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
31953
|
+
});
|
|
31954
|
+
return true;
|
|
31955
|
+
} catch {
|
|
31956
|
+
return false;
|
|
31957
|
+
}
|
|
31958
|
+
}
|
|
31959
|
+
};
|
|
31960
|
+
}
|
|
31961
|
+
function sleep2(ms) {
|
|
31962
|
+
return new Promise((res) => setTimeout(res, ms));
|
|
31963
|
+
}
|
|
31964
|
+
function isTuiChromeLine(line) {
|
|
31965
|
+
const t = line.trim();
|
|
31966
|
+
if (t.length === 0)
|
|
31967
|
+
return false;
|
|
31968
|
+
if (/^[\s\-_\u2502\u2500\u256d\u256e\u2570\u256f\u250c\u2510\u2514\u2518|]+$/.test(t))
|
|
31969
|
+
return true;
|
|
31970
|
+
if (/^[\u256d\u256e\u2570\u256f\u2502\s]*[>)\u276f][\u256d\u256e\u2570\u256f\u2502\s]*$/.test(t))
|
|
31971
|
+
return true;
|
|
31972
|
+
if (/accept edits on/i.test(t))
|
|
31973
|
+
return true;
|
|
31974
|
+
if (/shift\+tab to cycle/i.test(t))
|
|
31975
|
+
return true;
|
|
31976
|
+
if (/for agents/i.test(t))
|
|
31977
|
+
return true;
|
|
31978
|
+
if (/\? for shortcuts/i.test(t))
|
|
31979
|
+
return true;
|
|
31980
|
+
if (/bypassing permissions/i.test(t))
|
|
31981
|
+
return true;
|
|
31982
|
+
if (t === "copy")
|
|
31983
|
+
return true;
|
|
31984
|
+
return false;
|
|
31985
|
+
}
|
|
31986
|
+
function diffPane(before, after, command) {
|
|
31987
|
+
if (command) {
|
|
31988
|
+
const escaped = command.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
31989
|
+
const lines = after.split(`
|
|
31990
|
+
`);
|
|
31991
|
+
let anchorIdx = -1;
|
|
31992
|
+
for (let i = lines.length - 1;i >= 0; i--) {
|
|
31993
|
+
if (/[\u276f>]\s+/.test(lines[i]) && lines[i].includes(escaped)) {
|
|
31994
|
+
anchorIdx = i;
|
|
31995
|
+
break;
|
|
31996
|
+
}
|
|
31997
|
+
}
|
|
31998
|
+
if (anchorIdx >= 0) {
|
|
31999
|
+
const tail = lines.slice(anchorIdx + 1);
|
|
32000
|
+
const trimmed = [];
|
|
32001
|
+
for (const raw of tail) {
|
|
32002
|
+
const line = raw.trimEnd();
|
|
32003
|
+
if (line.length === 0 && trimmed.length === 0)
|
|
32004
|
+
continue;
|
|
32005
|
+
if (isTuiChromeLine(line))
|
|
32006
|
+
continue;
|
|
32007
|
+
trimmed.push(line);
|
|
32008
|
+
}
|
|
32009
|
+
while (trimmed.length > 0 && trimmed[trimmed.length - 1].length === 0) {
|
|
32010
|
+
trimmed.pop();
|
|
32011
|
+
}
|
|
32012
|
+
const affordances = /^(esc to cancel|press any key|\u21b5 select)/i;
|
|
32013
|
+
while (trimmed.length > 0 && affordances.test(trimmed[trimmed.length - 1].trim())) {
|
|
32014
|
+
trimmed.pop();
|
|
32015
|
+
}
|
|
32016
|
+
if (trimmed.length > 0) {
|
|
32017
|
+
return { output: trimmed.join(`
|
|
32018
|
+
`), anchored: true };
|
|
32019
|
+
}
|
|
32020
|
+
}
|
|
32021
|
+
}
|
|
32022
|
+
const beforeSet = new Set(before.split(`
|
|
32023
|
+
`).map((l) => l.trimEnd()));
|
|
32024
|
+
const newLines = [];
|
|
32025
|
+
for (const raw of after.split(`
|
|
32026
|
+
`)) {
|
|
32027
|
+
const line = raw.trimEnd();
|
|
32028
|
+
if (line.length === 0)
|
|
32029
|
+
continue;
|
|
32030
|
+
if (beforeSet.has(line))
|
|
32031
|
+
continue;
|
|
32032
|
+
if (isTuiChromeLine(line))
|
|
32033
|
+
continue;
|
|
32034
|
+
newLines.push(line);
|
|
32035
|
+
}
|
|
32036
|
+
return { output: newLines.join(`
|
|
32037
|
+
`), anchored: false };
|
|
32038
|
+
}
|
|
32039
|
+
async function injectSlashCommand(agentName, command, opts = {}) {
|
|
32040
|
+
validateInjectCommand(command);
|
|
32041
|
+
const tmuxBin = opts.tmuxBin ?? "tmux";
|
|
32042
|
+
const socket = opts.socketName ?? defaultSocketName(agentName);
|
|
32043
|
+
const session = opts.sessionName ?? agentName;
|
|
32044
|
+
const settleMs = opts.settleMs ?? 2000;
|
|
32045
|
+
const signalMode = !!(opts.successPattern || opts.errorPattern);
|
|
32046
|
+
const timeoutMs = opts.timeoutMs ?? (signalMode ? 8000 : 5000);
|
|
32047
|
+
return withPaneLock(`${socket}:${session}`, () => injectSlashCommandWith(makeTmuxRunner(tmuxBin), {
|
|
32048
|
+
socket,
|
|
32049
|
+
session,
|
|
32050
|
+
command: command.trim(),
|
|
32051
|
+
settleMs,
|
|
32052
|
+
timeoutMs,
|
|
32053
|
+
precondition: opts.precondition,
|
|
32054
|
+
successPattern: opts.successPattern,
|
|
32055
|
+
errorPattern: opts.errorPattern,
|
|
32056
|
+
settleBeforeSendMs: opts.settleBeforeSendMs
|
|
32057
|
+
}));
|
|
32058
|
+
}
|
|
32059
|
+
async function injectSlashCommandWith(runner, args) {
|
|
32060
|
+
const {
|
|
32061
|
+
socket,
|
|
32062
|
+
session,
|
|
32063
|
+
command,
|
|
32064
|
+
settleMs,
|
|
32065
|
+
timeoutMs,
|
|
32066
|
+
precondition,
|
|
32067
|
+
successPattern,
|
|
32068
|
+
errorPattern,
|
|
32069
|
+
settleBeforeSendMs
|
|
32070
|
+
} = args;
|
|
32071
|
+
let bareVerb;
|
|
32072
|
+
try {
|
|
32073
|
+
bareVerb = validateInjectCommand(command);
|
|
32074
|
+
} catch (err) {
|
|
32075
|
+
if (err instanceof InjectError) {
|
|
32076
|
+
return {
|
|
32077
|
+
outcome: "failed",
|
|
32078
|
+
output: "",
|
|
32079
|
+
truncated: false,
|
|
32080
|
+
command: command.trim().split(/\s+/, 1)[0]?.toLowerCase() ?? "",
|
|
32081
|
+
meta: null,
|
|
32082
|
+
errorCode: err.code,
|
|
32083
|
+
errorMessage: err.message
|
|
32084
|
+
};
|
|
32085
|
+
}
|
|
32086
|
+
throw err;
|
|
32087
|
+
}
|
|
32088
|
+
const meta = INJECT_COMMANDS.get(bareVerb) ?? null;
|
|
32089
|
+
if (!runner.hasSession(socket, session)) {
|
|
32090
|
+
return {
|
|
32091
|
+
outcome: "failed",
|
|
32092
|
+
output: "",
|
|
32093
|
+
truncated: false,
|
|
32094
|
+
command: bareVerb,
|
|
32095
|
+
meta,
|
|
32096
|
+
errorCode: "session_missing",
|
|
32097
|
+
errorMessage: `tmux session "${session}" on socket "${socket}" not found. ` + `Is the agent running under the tmux supervisor (the default)? ` + `If experimental.legacy_pty=true is set, inject is unsupported.`
|
|
32098
|
+
};
|
|
32099
|
+
}
|
|
32100
|
+
if (precondition && !precondition()) {
|
|
32101
|
+
return {
|
|
32102
|
+
outcome: "skipped",
|
|
32103
|
+
output: "",
|
|
32104
|
+
truncated: false,
|
|
32105
|
+
command: bareVerb,
|
|
32106
|
+
meta,
|
|
32107
|
+
errorCode: "precondition_failed",
|
|
32108
|
+
errorMessage: "inject precondition returned false at write time; send aborted " + "(no keys sent)."
|
|
32109
|
+
};
|
|
32110
|
+
}
|
|
32111
|
+
if (settleBeforeSendMs && settleBeforeSendMs > 0) {
|
|
32112
|
+
const settleStart = Date.now();
|
|
32113
|
+
let prevSettle = runner.capture(socket, session) ?? "";
|
|
32114
|
+
while (Date.now() - settleStart < settleBeforeSendMs) {
|
|
32115
|
+
await sleep2(POLL_INTERVAL_MS);
|
|
32116
|
+
const cur = runner.capture(socket, session) ?? "";
|
|
32117
|
+
if (cur === prevSettle)
|
|
32118
|
+
break;
|
|
32119
|
+
prevSettle = cur;
|
|
32120
|
+
}
|
|
32121
|
+
}
|
|
32122
|
+
const before = runner.capture(socket, session) ?? "";
|
|
32123
|
+
try {
|
|
32124
|
+
runner.send(socket, session, ["send-keys", "-l", command]);
|
|
32125
|
+
runner.send(socket, session, ["send-keys", "Enter"]);
|
|
32126
|
+
} catch (err) {
|
|
32127
|
+
return {
|
|
32128
|
+
outcome: "failed",
|
|
32129
|
+
output: "",
|
|
32130
|
+
truncated: false,
|
|
32131
|
+
command: bareVerb,
|
|
32132
|
+
meta,
|
|
32133
|
+
errorCode: "tmux_failed",
|
|
32134
|
+
errorMessage: `tmux send-keys failed: ${err instanceof Error ? err.message : String(err)}`
|
|
32135
|
+
};
|
|
32136
|
+
}
|
|
32137
|
+
const start = Date.now();
|
|
32138
|
+
let last = before;
|
|
32139
|
+
let stableSince = null;
|
|
32140
|
+
const signalMode = !!(successPattern || errorPattern);
|
|
32141
|
+
while (Date.now() - start < timeoutMs) {
|
|
32142
|
+
await sleep2(POLL_INTERVAL_MS);
|
|
32143
|
+
const cur = runner.capture(socket, session) ?? "";
|
|
32144
|
+
if (signalMode) {
|
|
32145
|
+
last = cur;
|
|
32146
|
+
if (cur !== before) {
|
|
32147
|
+
const { output: region } = diffPane(before, cur, command);
|
|
32148
|
+
const regionLines = region.split(`
|
|
32149
|
+
`).map((l) => l.trim());
|
|
32150
|
+
if (errorPattern && regionLines.some((l) => errorPattern.test(l)))
|
|
32151
|
+
break;
|
|
32152
|
+
if (successPattern && regionLines.some((l) => successPattern.test(l)))
|
|
32153
|
+
break;
|
|
32154
|
+
}
|
|
32155
|
+
continue;
|
|
32156
|
+
}
|
|
32157
|
+
if (cur === last && cur !== before) {
|
|
32158
|
+
if (stableSince === null) {
|
|
32159
|
+
stableSince = Date.now();
|
|
32160
|
+
} else if (Date.now() - stableSince >= POLL_INTERVAL_MS) {
|
|
32161
|
+
last = cur;
|
|
32162
|
+
break;
|
|
32163
|
+
}
|
|
32164
|
+
} else {
|
|
32165
|
+
stableSince = null;
|
|
32166
|
+
}
|
|
32167
|
+
last = cur;
|
|
32168
|
+
if (Date.now() - start >= settleMs && cur !== before) {
|
|
32169
|
+
break;
|
|
32170
|
+
}
|
|
32171
|
+
}
|
|
32172
|
+
const { output: rawOutput, anchored } = diffPane(before, last, command);
|
|
32173
|
+
let output = rawOutput;
|
|
32174
|
+
let truncated = false;
|
|
32175
|
+
const bytes = Buffer.byteLength(output, "utf-8");
|
|
32176
|
+
if (bytes > OUTPUT_BYTE_CAP) {
|
|
32177
|
+
while (Buffer.byteLength(output, "utf-8") > OUTPUT_BYTE_CAP) {
|
|
32178
|
+
output = output.slice(Math.floor(output.length * 0.1) + 1);
|
|
32179
|
+
}
|
|
32180
|
+
truncated = true;
|
|
32181
|
+
}
|
|
32182
|
+
if (meta?.dialog) {
|
|
32183
|
+
try {
|
|
32184
|
+
runner.send(socket, session, ["send-keys", "Escape"]);
|
|
32185
|
+
} catch {}
|
|
32186
|
+
}
|
|
32187
|
+
if (output.trim().length === 0) {
|
|
32188
|
+
return {
|
|
32189
|
+
outcome: "ok_no_output",
|
|
32190
|
+
output: "",
|
|
32191
|
+
truncated: false,
|
|
32192
|
+
command: bareVerb,
|
|
32193
|
+
meta,
|
|
32194
|
+
...anchored ? {} : { diagnostic: "anchor_missing" }
|
|
32195
|
+
};
|
|
32196
|
+
}
|
|
32197
|
+
return {
|
|
32198
|
+
outcome: "ok",
|
|
32199
|
+
output,
|
|
32200
|
+
truncated,
|
|
32201
|
+
command: bareVerb,
|
|
32202
|
+
meta,
|
|
32203
|
+
...truncated ? { diagnostic: "truncated_output" } : {}
|
|
32204
|
+
};
|
|
32205
|
+
}
|
|
32206
|
+
var INJECT_COMMANDS, INJECT_ALLOWLIST, INJECT_BLOCKED, INJECT_BLOCKLIST, InjectError, POLL_INTERVAL_MS = 150, OUTPUT_BYTE_CAP = 3000;
|
|
32207
|
+
var init_inject = __esm(() => {
|
|
32208
|
+
init_pane_lock();
|
|
32209
|
+
INJECT_COMMANDS = new Map([
|
|
32210
|
+
["/cost", { description: "Show session cost", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
32211
|
+
["/status", { description: "Show session status", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
32212
|
+
["/usage", { description: "Show plan quota", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
32213
|
+
["/hooks", { description: "List configured hooks", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
32214
|
+
["/memory", { description: "Open memory picker", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
32215
|
+
["/help", { description: "Show help / command discovery", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
32216
|
+
["/context", { description: "Show context window usage", expectsOutput: true, argsAllowed: false }],
|
|
32217
|
+
["/release-notes", { description: "Show release-notes version list", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
32218
|
+
["/model", { description: "Open model picker", expectsOutput: true, argsAllowed: true }],
|
|
32219
|
+
[
|
|
32220
|
+
"/clear",
|
|
32221
|
+
{
|
|
32222
|
+
description: "Clear session screen",
|
|
32223
|
+
expectsOutput: false,
|
|
32224
|
+
silentNote: "context cleared \u2014 fresh slate",
|
|
32225
|
+
argsAllowed: false
|
|
32226
|
+
}
|
|
32227
|
+
],
|
|
32228
|
+
[
|
|
32229
|
+
"/compact",
|
|
32230
|
+
{
|
|
32231
|
+
description: "Compact conversation history",
|
|
32232
|
+
expectsOutput: false,
|
|
32233
|
+
silentNote: "compaction runs silently",
|
|
32234
|
+
argsAllowed: false
|
|
32235
|
+
}
|
|
32236
|
+
]
|
|
32237
|
+
]);
|
|
32238
|
+
INJECT_ALLOWLIST = new Set(INJECT_COMMANDS.keys());
|
|
32239
|
+
INJECT_BLOCKED = new Map([
|
|
32240
|
+
["/login", { reason: "would mutate auth state" }],
|
|
32241
|
+
["/logout", { reason: "would terminate the agent's auth session" }],
|
|
32242
|
+
["/exit", { reason: "would kill the agent process" }],
|
|
32243
|
+
["/quit", { reason: "would kill the agent process" }],
|
|
32244
|
+
["/upgrade", { reason: "mutates the Claude Code installation" }],
|
|
32245
|
+
["/init", { reason: "generates/overwrites CLAUDE.md and runs a model turn" }],
|
|
32246
|
+
["/mcp", { reason: "opens an interactive MCP server management dialog" }],
|
|
32247
|
+
["/permissions", { reason: "opens an interactive permissions editor that mutates tool policy" }],
|
|
32248
|
+
["/install-github-app", { reason: "runs a network/OAuth install flow" }],
|
|
32249
|
+
["/add-dir", { reason: "mutates the session's working-directory set" }],
|
|
32250
|
+
["/terminal-setup", { reason: "mutates terminal keybinding configuration" }],
|
|
32251
|
+
["/privacy-settings", { reason: "opens an interactive privacy-settings dialog" }],
|
|
32252
|
+
["/bug", { reason: "submits a bug report over the network" }],
|
|
32253
|
+
[
|
|
32254
|
+
"/effort",
|
|
32255
|
+
{
|
|
32256
|
+
reason: "leaves a blocking confirmation modal open and wedges the pane; use the /effort command (it drives the modal), not raw inject"
|
|
32257
|
+
}
|
|
32258
|
+
]
|
|
32259
|
+
]);
|
|
32260
|
+
INJECT_BLOCKLIST = new Set(INJECT_BLOCKED.keys());
|
|
32261
|
+
InjectError = class InjectError extends Error {
|
|
32262
|
+
code;
|
|
32263
|
+
constructor(code, message) {
|
|
32264
|
+
super(message);
|
|
32265
|
+
this.name = "InjectError";
|
|
32266
|
+
this.code = code;
|
|
32267
|
+
}
|
|
32268
|
+
};
|
|
32269
|
+
});
|
|
32270
|
+
|
|
32267
32271
|
// src/auth/broker/protocol.ts
|
|
32268
32272
|
function encodeRequest2(req) {
|
|
32269
32273
|
const line = JSON.stringify(RequestSchema2.parse(req)) + `
|
|
@@ -63948,11 +63952,11 @@ function detectInFlight(opts) {
|
|
|
63948
63952
|
async function waitUntilIdle(opts) {
|
|
63949
63953
|
const pollMs = opts.pollMs ?? 2000;
|
|
63950
63954
|
const clock = opts.now ?? Date.now;
|
|
63951
|
-
const
|
|
63955
|
+
const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
63952
63956
|
const deadline = clock() + opts.timeoutMs;
|
|
63953
63957
|
let last = detectInFlight(opts);
|
|
63954
63958
|
while (last.busy && clock() < deadline) {
|
|
63955
|
-
await
|
|
63959
|
+
await sleep(pollMs);
|
|
63956
63960
|
last = detectInFlight(opts);
|
|
63957
63961
|
}
|
|
63958
63962
|
return last;
|
|
@@ -64108,7 +64112,7 @@ function spinner(message) {
|
|
|
64108
64112
|
// src/agents/status.ts
|
|
64109
64113
|
import { existsSync as existsSync24, readFileSync as readFileSync20, statSync as statSync12 } from "node:fs";
|
|
64110
64114
|
import { join as join16 } from "node:path";
|
|
64111
|
-
import { execFileSync as
|
|
64115
|
+
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
64112
64116
|
|
|
64113
64117
|
// src/agents/handoff-summarizer.ts
|
|
64114
64118
|
import { readFileSync as readFileSync18, writeFileSync as writeFileSync8, renameSync as renameSync6, mkdirSync as mkdirSync15, existsSync as existsSync22, statSync as statSync11, readdirSync as readdirSync9 } from "node:fs";
|
|
@@ -64693,7 +64697,7 @@ async function buildAgentStatusReport(inputs) {
|
|
|
64693
64697
|
async function waitForAgentReady(inputs, options = {}) {
|
|
64694
64698
|
const timeoutMs = options.timeoutMs ?? 30000;
|
|
64695
64699
|
const pollIntervalMs = options.pollIntervalMs ?? 750;
|
|
64696
|
-
const
|
|
64700
|
+
const sleep = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
64697
64701
|
const now = options.now ?? Date.now;
|
|
64698
64702
|
const startedAt = now();
|
|
64699
64703
|
const deadline = startedAt + timeoutMs;
|
|
@@ -64708,7 +64712,7 @@ async function waitForAgentReady(inputs, options = {}) {
|
|
|
64708
64712
|
notReady
|
|
64709
64713
|
};
|
|
64710
64714
|
}
|
|
64711
|
-
await
|
|
64715
|
+
await sleep(pollIntervalMs);
|
|
64712
64716
|
report = await buildAgentStatusReport(inputs);
|
|
64713
64717
|
notReady = readinessGaps(report);
|
|
64714
64718
|
}
|
|
@@ -64785,14 +64789,14 @@ function readLastMessages(historyDbPath) {
|
|
|
64785
64789
|
};
|
|
64786
64790
|
}
|
|
64787
64791
|
try {
|
|
64788
|
-
const out =
|
|
64792
|
+
const out = execFileSync11("bun", [
|
|
64789
64793
|
"-e",
|
|
64790
64794
|
`const { Database } = require("bun:sqlite"); ` + `const db = new Database(${JSON.stringify(historyDbPath)}, { readonly: true }); ` + `const rows = db.prepare("SELECT role, MAX(ts) as ts FROM messages GROUP BY role").all(); ` + `for (const r of rows) console.log(r.role + "|" + r.ts); ` + `db.close();`
|
|
64791
64795
|
], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
64792
64796
|
return parseSqliteRoleTsOutput(out);
|
|
64793
64797
|
} catch {}
|
|
64794
64798
|
try {
|
|
64795
|
-
const out =
|
|
64799
|
+
const out = execFileSync11("sqlite3", [
|
|
64796
64800
|
historyDbPath,
|
|
64797
64801
|
"SELECT role, MAX(ts) FROM messages GROUP BY role;"
|
|
64798
64802
|
], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
@@ -64904,7 +64908,7 @@ function escapeRegex(s) {
|
|
|
64904
64908
|
}
|
|
64905
64909
|
function readDockerContainer(containerName2) {
|
|
64906
64910
|
try {
|
|
64907
|
-
const out =
|
|
64911
|
+
const out = execFileSync11("docker", ["inspect", "--format", "{{json .State}}", containerName2], { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
|
|
64908
64912
|
const state = JSON.parse(out.trim());
|
|
64909
64913
|
const active = state.Status === "running" ? "active" : state.Status === "restarting" ? "restarting" : "inactive";
|
|
64910
64914
|
const pid = typeof state.Pid === "number" && state.Pid > 0 ? state.Pid : null;
|
|
@@ -65019,7 +65023,7 @@ async function pollForDmStart(token, timeoutMs = 120000) {
|
|
|
65019
65023
|
const response = await fetch(url);
|
|
65020
65024
|
data = await response.json();
|
|
65021
65025
|
} catch {
|
|
65022
|
-
await
|
|
65026
|
+
await sleep(2000);
|
|
65023
65027
|
continue;
|
|
65024
65028
|
}
|
|
65025
65029
|
if (data.ok && Array.isArray(data.result)) {
|
|
@@ -65035,11 +65039,11 @@ async function pollForDmStart(token, timeoutMs = 120000) {
|
|
|
65035
65039
|
}
|
|
65036
65040
|
}
|
|
65037
65041
|
}
|
|
65038
|
-
await
|
|
65042
|
+
await sleep(2000);
|
|
65039
65043
|
}
|
|
65040
65044
|
throw new Error("Timed out waiting for /start DM");
|
|
65041
65045
|
}
|
|
65042
|
-
function
|
|
65046
|
+
function sleep(ms) {
|
|
65043
65047
|
return new Promise((resolve17) => setTimeout(resolve17, ms));
|
|
65044
65048
|
}
|
|
65045
65049
|
|
|
@@ -65183,7 +65187,7 @@ async function completeCreation(name, code, opts = {}) {
|
|
|
65183
65187
|
// src/agents/add-orchestrator.ts
|
|
65184
65188
|
import { resolve as resolve19, join as join19 } from "node:path";
|
|
65185
65189
|
import { existsSync as existsSync29, rmSync as rmSync8, statSync as statSync16 } from "node:fs";
|
|
65186
|
-
import { execFileSync as
|
|
65190
|
+
import { execFileSync as execFileSync14 } from "node:child_process";
|
|
65187
65191
|
init_onboarding();
|
|
65188
65192
|
|
|
65189
65193
|
// src/setup/botfather-walkthrough.ts
|
|
@@ -65757,7 +65761,7 @@ function pruneBundledSkills(agentDir, keep, scope) {
|
|
|
65757
65761
|
}
|
|
65758
65762
|
function defaultIsUnitActive(unitName) {
|
|
65759
65763
|
try {
|
|
65760
|
-
const out =
|
|
65764
|
+
const out = execFileSync14("systemctl", ["--user", "is-active", unitName], {
|
|
65761
65765
|
encoding: "utf-8",
|
|
65762
65766
|
stdio: ["ignore", "pipe", "pipe"]
|
|
65763
65767
|
}).trim();
|