switchroom 0.21.17 → 0.21.18
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/cli/switchroom.js +1729 -1505
- package/dist/host-control/main.js +1 -1
- package/package.json +1 -1
- package/telegram-plugin/dist/gateway/gateway.js +4 -4
- package/telegram-plugin/scripts/bun-test-ci.sh +6 -0
- package/telegram-plugin/uat/flip/allowlist.test.ts +229 -0
- package/telegram-plugin/uat/flip/allowlist.ts +349 -0
- package/telegram-plugin/uat/flip/gate.test.ts +153 -0
- package/telegram-plugin/uat/flip/gate.ts +232 -0
- package/telegram-plugin/uat/flip/probe-scoring.test.ts +210 -0
- package/telegram-plugin/uat/flip/probe-scoring.ts +200 -0
- package/telegram-plugin/uat/flip/probe-suite.test.ts +95 -0
- package/telegram-plugin/uat/flip/probe-suite.ts +155 -0
- package/telegram-plugin/uat/flip/probes/kdogg.probes.json +36 -0
- package/telegram-plugin/uat/flip/probes/test-harness.probes.json +15 -0
- package/telegram-plugin/uat/flip/recall-log.test.ts +131 -0
- package/telegram-plugin/uat/flip/recall-log.ts +178 -0
- package/telegram-plugin/uat/flip/report.ts +95 -0
- package/telegram-plugin/uat/flip/tier1-equivalence.test.ts +470 -0
- package/telegram-plugin/uat/flip/tier1-equivalence.ts +697 -0
- package/telegram-plugin/uat/flip/tier2-probe-runner.ts +327 -0
- package/telegram-plugin/uat/runners/scorer.ts +1 -1
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.21.
|
|
2123
|
+
var VERSION = "0.21.18", COMMIT_SHA = "ae063418", COMMIT_DATE = "2026-08-18T03:14:53Z";
|
|
2124
2124
|
|
|
2125
2125
|
// src/cli/resolve-version.ts
|
|
2126
2126
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -71736,11 +71736,11 @@ var init_apply = __esm(() => {
|
|
|
71736
71736
|
});
|
|
71737
71737
|
|
|
71738
71738
|
// src/host-control/audit-reader.ts
|
|
71739
|
-
import { closeSync as closeSync15, existsSync as
|
|
71739
|
+
import { closeSync as closeSync15, existsSync as existsSync84, openSync as openSync15, readSync as readSync5, statSync as statSync50 } from "node:fs";
|
|
71740
71740
|
import { homedir as homedir43 } from "node:os";
|
|
71741
|
-
import { join as
|
|
71741
|
+
import { join as join91 } from "node:path";
|
|
71742
71742
|
function defaultAuditLogPath2(home2 = homedir43()) {
|
|
71743
|
-
return
|
|
71743
|
+
return join91(home2, ".switchroom", "host-control-audit.log");
|
|
71744
71744
|
}
|
|
71745
71745
|
function auditRotation() {
|
|
71746
71746
|
return resolveRotationConfig({
|
|
@@ -71757,14 +71757,14 @@ function auditReadFullWindowBytes() {
|
|
|
71757
71757
|
function auditReadMaxGenerations() {
|
|
71758
71758
|
return auditRotation().maxFiles;
|
|
71759
71759
|
}
|
|
71760
|
-
function tailBytes(
|
|
71760
|
+
function tailBytes(path9, n, strict = false) {
|
|
71761
71761
|
if (n <= 0)
|
|
71762
71762
|
return "";
|
|
71763
71763
|
let fd;
|
|
71764
71764
|
let size;
|
|
71765
71765
|
try {
|
|
71766
|
-
size =
|
|
71767
|
-
fd = openSync15(
|
|
71766
|
+
size = statSync50(path9).size;
|
|
71767
|
+
fd = openSync15(path9, "r");
|
|
71768
71768
|
} catch (err) {
|
|
71769
71769
|
if (strict && err.code !== "ENOENT")
|
|
71770
71770
|
throw err;
|
|
@@ -71809,7 +71809,7 @@ function readAuditRaw(logPath, opts = {}) {
|
|
|
71809
71809
|
budget -= Buffer.byteLength(active, "utf-8");
|
|
71810
71810
|
for (let n = 1;n <= maxFiles && budget > 0; n++) {
|
|
71811
71811
|
const gen = `${logPath}.${n}`;
|
|
71812
|
-
if (!
|
|
71812
|
+
if (!existsSync84(gen))
|
|
71813
71813
|
break;
|
|
71814
71814
|
const chunk2 = tailBytes(gen, budget, strict);
|
|
71815
71815
|
if (chunk2.length === 0)
|
|
@@ -71987,16 +71987,16 @@ var init_audit_reader = __esm(() => {
|
|
|
71987
71987
|
});
|
|
71988
71988
|
|
|
71989
71989
|
// src/web/fleet-health-read.ts
|
|
71990
|
-
import { readFileSync as
|
|
71991
|
-
import { resolve as
|
|
71990
|
+
import { readFileSync as readFileSync79 } from "node:fs";
|
|
71991
|
+
import { resolve as resolve46 } from "node:path";
|
|
71992
71992
|
import { homedir as homedir46 } from "node:os";
|
|
71993
71993
|
function fleetHealthLedgerPath(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir46()) {
|
|
71994
|
-
return
|
|
71994
|
+
return resolve46(home2, ".switchroom", "fleet-health", "ledger.json");
|
|
71995
71995
|
}
|
|
71996
|
-
function readFleetHealth(
|
|
71996
|
+
function readFleetHealth(path9) {
|
|
71997
71997
|
let ledger;
|
|
71998
71998
|
try {
|
|
71999
|
-
ledger = JSON.parse(
|
|
71999
|
+
ledger = JSON.parse(readFileSync79(path9, "utf-8"));
|
|
72000
72000
|
} catch {
|
|
72001
72001
|
return {
|
|
72002
72002
|
owner_agent: null,
|
|
@@ -72022,8 +72022,8 @@ function readFleetHealth(path7) {
|
|
|
72022
72022
|
...ranked.length === 0 ? { reason: EMPTY_REASON } : {}
|
|
72023
72023
|
};
|
|
72024
72024
|
}
|
|
72025
|
-
function handleGetFleetHealth(
|
|
72026
|
-
return readFleetHealth(
|
|
72025
|
+
function handleGetFleetHealth(path9 = fleetHealthLedgerPath()) {
|
|
72026
|
+
return readFleetHealth(path9);
|
|
72027
72027
|
}
|
|
72028
72028
|
var EMPTY_REASON;
|
|
72029
72029
|
var init_fleet_health_read = __esm(() => {
|
|
@@ -110258,8 +110258,8 @@ init_loader();
|
|
|
110258
110258
|
init_hindsight();
|
|
110259
110259
|
var import_yaml16 = __toESM(require_dist(), 1);
|
|
110260
110260
|
import { spawn as spawn5 } from "node:child_process";
|
|
110261
|
-
import { existsSync as
|
|
110262
|
-
import { join as
|
|
110261
|
+
import { existsSync as existsSync83, readFileSync as readFileSync75, statSync as statSync49 } from "node:fs";
|
|
110262
|
+
import { join as join89 } from "node:path";
|
|
110263
110263
|
|
|
110264
110264
|
// src/cli/memory-rules.ts
|
|
110265
110265
|
init_source();
|
|
@@ -110526,301 +110526,1223 @@ function registerMemoryDirectiveCommand(memory, program3) {
|
|
|
110526
110526
|
}));
|
|
110527
110527
|
}
|
|
110528
110528
|
|
|
110529
|
-
// src/cli/memory.ts
|
|
110530
|
-
|
|
110531
|
-
|
|
110532
|
-
|
|
110533
|
-
|
|
110534
|
-
|
|
110535
|
-
|
|
110536
|
-
|
|
110537
|
-
|
|
110538
|
-
|
|
110539
|
-
|
|
110540
|
-
|
|
110541
|
-
|
|
110542
|
-
|
|
110543
|
-
|
|
110544
|
-
|
|
110545
|
-
|
|
110546
|
-
|
|
110547
|
-
}
|
|
110529
|
+
// src/cli/memory-flip.ts
|
|
110530
|
+
init_source();
|
|
110531
|
+
init_helpers();
|
|
110532
|
+
init_hindsight2();
|
|
110533
|
+
init_hindsight();
|
|
110534
|
+
init_hindsight_directive_admin();
|
|
110535
|
+
|
|
110536
|
+
// src/memory/directive-triage.ts
|
|
110537
|
+
init_hindsight_directive_admin();
|
|
110538
|
+
var SUPERSEDED_BY_TAG_RE = /^superseded-by:(.+)$/;
|
|
110539
|
+
function classifyDirective(directive, override) {
|
|
110540
|
+
const isActive = directive.is_active !== false;
|
|
110541
|
+
const overrideSignal = override?.signal.trim();
|
|
110542
|
+
if (hasRulesBlockMarker(directive.tags)) {
|
|
110543
|
+
return {
|
|
110544
|
+
category: "rules-block",
|
|
110545
|
+
signal: "carries the rules-block marker tag \u2014 staged for M3, never retired",
|
|
110546
|
+
action: "stage-for-m3"
|
|
110547
|
+
};
|
|
110548
110548
|
}
|
|
110549
|
-
|
|
110550
|
-
|
|
110551
|
-
|
|
110552
|
-
|
|
110553
|
-
|
|
110554
|
-
|
|
110555
|
-
}
|
|
110556
|
-
|
|
110557
|
-
|
|
110558
|
-
|
|
110559
|
-
|
|
110560
|
-
|
|
110549
|
+
if (override && override.category === "rules-block") {
|
|
110550
|
+
return {
|
|
110551
|
+
category: "rules-block",
|
|
110552
|
+
signal: overrideSignal || "rules-block override with no signal text \u2014 staged for M3, never retired",
|
|
110553
|
+
action: "stage-for-m3"
|
|
110554
|
+
};
|
|
110555
|
+
}
|
|
110556
|
+
const supersessionTag = (directive.tags ?? []).map((t) => SUPERSEDED_BY_TAG_RE.exec(t)).find((m) => m !== null);
|
|
110557
|
+
if (supersessionTag) {
|
|
110558
|
+
const winner = supersessionTag[1];
|
|
110559
|
+
return {
|
|
110560
|
+
category: "retire",
|
|
110561
|
+
signal: isActive ? `superseded by '${winner}' (superseded-by tag)` : `superseded by '${winner}' (superseded-by tag) \u2014 already inactive, nothing to retire`,
|
|
110562
|
+
action: isActive ? "retire" : "keep",
|
|
110563
|
+
supersededBy: winner
|
|
110564
|
+
};
|
|
110565
|
+
}
|
|
110566
|
+
if (override && overrideSignal) {
|
|
110567
|
+
if (override.category === "reflect-directive") {
|
|
110568
|
+
return { category: "reflect-directive", signal: overrideSignal, action: "keep" };
|
|
110561
110569
|
}
|
|
110562
|
-
|
|
110563
|
-
|
|
110564
|
-
|
|
110565
|
-
|
|
110570
|
+
return {
|
|
110571
|
+
category: override.category,
|
|
110572
|
+
signal: isActive ? overrideSignal : `${overrideSignal} \u2014 already inactive, nothing to retire`,
|
|
110573
|
+
action: isActive ? "retire" : "keep"
|
|
110574
|
+
};
|
|
110566
110575
|
}
|
|
110567
|
-
|
|
110568
|
-
|
|
110569
|
-
|
|
110570
|
-
|
|
110571
|
-
}
|
|
110572
|
-
function hindsightSecretValues(input) {
|
|
110573
|
-
const out = [];
|
|
110574
|
-
const push = (v) => {
|
|
110575
|
-
if (typeof v === "string" && v.trim().length > 0)
|
|
110576
|
-
out.push(v.trim());
|
|
110576
|
+
return {
|
|
110577
|
+
category: "reflect-directive",
|
|
110578
|
+
signal: "no deterministic signal \u2014 defaults to KEEP",
|
|
110579
|
+
action: "keep"
|
|
110577
110580
|
};
|
|
110578
|
-
push(input.cpAccessKey);
|
|
110579
|
-
push(input.llm?.api_key);
|
|
110580
|
-
for (const op of ["retain", "reflect", "consolidation"]) {
|
|
110581
|
-
push(input.llm?.[op]?.api_key);
|
|
110582
|
-
}
|
|
110583
|
-
return out;
|
|
110584
110581
|
}
|
|
110585
|
-
function
|
|
110586
|
-
|
|
110587
|
-
|
|
110588
|
-
|
|
110589
|
-
|
|
110590
|
-
|
|
110582
|
+
function buildDirectiveTriageRows(directives, overrides) {
|
|
110583
|
+
return directives.map((d) => {
|
|
110584
|
+
const classified = classifyDirective(d, overrides?.get(d.id));
|
|
110585
|
+
return {
|
|
110586
|
+
id: d.id,
|
|
110587
|
+
name: d.name,
|
|
110588
|
+
priority: d.priority ?? 0,
|
|
110589
|
+
isActive: d.is_active !== false,
|
|
110590
|
+
...classified
|
|
110591
|
+
};
|
|
110592
|
+
});
|
|
110591
110593
|
}
|
|
110592
|
-
|
|
110593
|
-
|
|
110594
|
+
|
|
110595
|
+
// src/cli/debug.ts
|
|
110596
|
+
init_helpers();
|
|
110597
|
+
init_loader();
|
|
110598
|
+
import { existsSync as existsSync82, readFileSync as readFileSync74, readdirSync as readdirSync30, statSync as statSync48 } from "node:fs";
|
|
110599
|
+
import { resolve as resolve43, join as join88 } from "node:path";
|
|
110600
|
+
import { createHash as createHash15 } from "node:crypto";
|
|
110601
|
+
|
|
110602
|
+
// src/agents/workspace.ts
|
|
110603
|
+
import { readFile as readFile2, stat } from "node:fs/promises";
|
|
110604
|
+
import path8 from "node:path";
|
|
110605
|
+
|
|
110606
|
+
// src/agents/bootstrap-budget.ts
|
|
110607
|
+
import path7 from "node:path";
|
|
110608
|
+
var DEFAULT_BOOTSTRAP_NEAR_LIMIT_RATIO = 0.85;
|
|
110609
|
+
var DEFAULT_BOOTSTRAP_PROMPT_WARNING_MAX_FILES = 3;
|
|
110610
|
+
var DEFAULT_BOOTSTRAP_PROMPT_WARNING_SIGNATURE_HISTORY_MAX = 32;
|
|
110611
|
+
|
|
110612
|
+
class BootstrapBudgetExceededError extends Error {
|
|
110613
|
+
analysis;
|
|
110614
|
+
constructor(analysis, message) {
|
|
110615
|
+
super(message);
|
|
110616
|
+
this.analysis = analysis;
|
|
110617
|
+
this.name = "BootstrapBudgetExceededError";
|
|
110618
|
+
}
|
|
110594
110619
|
}
|
|
110595
|
-
|
|
110596
|
-
|
|
110597
|
-
|
|
110620
|
+
function normalizePositiveLimit(value) {
|
|
110621
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
110622
|
+
return 1;
|
|
110623
|
+
}
|
|
110624
|
+
return Math.floor(value);
|
|
110598
110625
|
}
|
|
110599
|
-
function
|
|
110600
|
-
|
|
110601
|
-
|
|
110602
|
-
|
|
110603
|
-
|
|
110604
|
-
try {
|
|
110605
|
-
raw = readFileSync74(path7, "utf-8");
|
|
110606
|
-
} catch {
|
|
110626
|
+
function formatWarningCause(cause) {
|
|
110627
|
+
return cause === "per-file-limit" ? "max/file" : "max/total";
|
|
110628
|
+
}
|
|
110629
|
+
function normalizeSeenSignatures(signatures) {
|
|
110630
|
+
if (!Array.isArray(signatures) || signatures.length === 0) {
|
|
110607
110631
|
return [];
|
|
110608
110632
|
}
|
|
110609
|
-
const
|
|
110610
|
-
|
|
110611
|
-
const
|
|
110612
|
-
|
|
110613
|
-
|
|
110614
|
-
|
|
110615
|
-
|
|
110616
|
-
|
|
110633
|
+
const seen = new Set;
|
|
110634
|
+
const result = [];
|
|
110635
|
+
for (const signature of signatures) {
|
|
110636
|
+
const value = typeof signature === "string" ? signature.trim() : "";
|
|
110637
|
+
if (!value || seen.has(value)) {
|
|
110638
|
+
continue;
|
|
110639
|
+
}
|
|
110640
|
+
seen.add(value);
|
|
110641
|
+
result.push(value);
|
|
110617
110642
|
}
|
|
110618
|
-
return
|
|
110643
|
+
return result;
|
|
110619
110644
|
}
|
|
110620
|
-
function
|
|
110621
|
-
|
|
110622
|
-
|
|
110623
|
-
const total = entries.length;
|
|
110624
|
-
const hits = entries.filter((e) => e.cache_hit).length;
|
|
110625
|
-
const cappedTurns = entries.filter((e) => e.capped).length;
|
|
110626
|
-
const omittedTurns = entries.filter((e) => typeof e.directives_omitted === "number" && e.directives_omitted > 0).length;
|
|
110627
|
-
const memCounts = entries.map((e) => e.result_count).filter((n) => typeof n === "number");
|
|
110628
|
-
const avg = memCounts.length > 0 ? Math.round(memCounts.reduce((s, n) => s + n, 0) / memCounts.length * 10) / 10 : null;
|
|
110629
|
-
const max = memCounts.length > 0 ? Math.max(...memCounts) : null;
|
|
110630
|
-
const medians = entries.map((e) => e.injected_score_median).filter((n) => typeof n === "number");
|
|
110631
|
-
const avgScore = medians.length > 0 ? Math.round(medians.reduce((s, n) => s + n, 0) / medians.length * 100) / 100 : null;
|
|
110632
|
-
const collapseTurns = entries.filter((e) => typeof e.injected_own_bank_count === "number" && e.injected_own_bank_count === 0 && typeof e.injected_additional_bank_count === "number" && e.injected_additional_bank_count > 0).length;
|
|
110633
|
-
lines.push(source_default.gray(` last ${total} turn${total === 1 ? "" : "s"}: ` + `avg=${avg ?? "\u2014"} max=${max ?? "\u2014"} ` + `avg_score=${avgScore ?? "\u2014"} ` + `cache_hits=${hits} capped=${cappedTurns}` + (omittedTurns > 0 ? ` directives_omitted_turns=${omittedTurns}` : "") + (collapseTurns > 0 ? source_default.red(` own_bank_collapse_turns=${collapseTurns}`) : "")));
|
|
110634
|
-
for (const e of entries) {
|
|
110635
|
-
const flag = e.cache_hit ? source_default.cyan("CACHE") : e.capped ? source_default.yellow("CAP") : source_default.green("OK");
|
|
110636
|
-
const dem = e.demoted_count && e.demoted_count > 0 ? source_default.dim(` -${e.demoted_count}d`) : "";
|
|
110637
|
-
const omitted = typeof e.directives_omitted === "number" && e.directives_omitted > 0 ? source_default.yellow(` +${e.directives_omitted}omitted`) : "";
|
|
110638
|
-
const ids = e.memory_ids && e.memory_ids.length > 0 ? source_default.dim(` ids=${e.memory_ids.slice(0, 3).join(",")}${e.memory_ids.length > 3 ? `\u2026+${e.memory_ids.length - 3}` : ""}`) : "";
|
|
110639
|
-
const own = e.injected_own_bank_count;
|
|
110640
|
-
const additional = e.injected_additional_bank_count;
|
|
110641
|
-
const composition = typeof own === "number" || typeof additional === "number" ? (() => {
|
|
110642
|
-
const text = ` own/add=${own ?? "\u2014"}/${additional ?? "\u2014"}`;
|
|
110643
|
-
return own === 0 && typeof additional === "number" && additional > 0 ? source_default.red(text) : source_default.dim(text);
|
|
110644
|
-
})() : "";
|
|
110645
|
-
const score = typeof e.injected_score_min === "number" && typeof e.injected_score_median === "number" && typeof e.injected_score_max === "number" ? source_default.dim(` score=${e.injected_score_min.toFixed(2)}/` + `${e.injected_score_median.toFixed(2)}/` + `${e.injected_score_max.toFixed(2)}`) : "";
|
|
110646
|
-
lines.push(` ${source_default.gray(e.ts)} ${flag} ` + `n=${e.result_count ?? "\u2014"}${e.pre_cap_count != null && e.pre_cap_count !== e.result_count ? `/${e.pre_cap_count}` : ""}` + `${dem}${omitted}${score}${composition}${ids}`);
|
|
110645
|
+
function appendSeenSignature(signatures, signature) {
|
|
110646
|
+
if (!signature.trim()) {
|
|
110647
|
+
return signatures;
|
|
110647
110648
|
}
|
|
110648
|
-
|
|
110649
|
+
if (signatures.includes(signature)) {
|
|
110650
|
+
return signatures;
|
|
110651
|
+
}
|
|
110652
|
+
const next = [...signatures, signature];
|
|
110653
|
+
if (next.length <= DEFAULT_BOOTSTRAP_PROMPT_WARNING_SIGNATURE_HISTORY_MAX) {
|
|
110654
|
+
return next;
|
|
110655
|
+
}
|
|
110656
|
+
return next.slice(-DEFAULT_BOOTSTRAP_PROMPT_WARNING_SIGNATURE_HISTORY_MAX);
|
|
110649
110657
|
}
|
|
110650
|
-
function
|
|
110651
|
-
const
|
|
110652
|
-
|
|
110653
|
-
|
|
110654
|
-
|
|
110655
|
-
|
|
110656
|
-
|
|
110657
|
-
process.exit(1);
|
|
110658
|
-
}
|
|
110659
|
-
const collection = getCollectionForAgent(opts.agent, config);
|
|
110660
|
-
console.log(source_default.bold(`
|
|
110661
|
-
Search: ${opts.agent} (collection: ${collection})
|
|
110662
|
-
`));
|
|
110663
|
-
console.log(source_default.gray(` $ ${searchMemory(query, collection)}`));
|
|
110664
|
-
console.log();
|
|
110665
|
-
return;
|
|
110658
|
+
function buildBootstrapInjectionStats(params) {
|
|
110659
|
+
const injectedByPath = new Map;
|
|
110660
|
+
const injectedByBaseName = new Map;
|
|
110661
|
+
for (const file of params.injectedFiles) {
|
|
110662
|
+
const pathValue = typeof file.path === "string" ? file.path.trim() : "";
|
|
110663
|
+
if (!pathValue) {
|
|
110664
|
+
continue;
|
|
110666
110665
|
}
|
|
110667
|
-
|
|
110668
|
-
|
|
110669
|
-
Searching all eligible collections:
|
|
110670
|
-
`));
|
|
110671
|
-
for (const name of agentNames) {
|
|
110672
|
-
const collection = getCollectionForAgent(name, config);
|
|
110673
|
-
if (isStrictIsolation(name, config)) {
|
|
110674
|
-
console.log(source_default.gray(` ${name} (${collection}) \u2014 skipped (strict isolation)`));
|
|
110675
|
-
continue;
|
|
110676
|
-
}
|
|
110677
|
-
console.log(source_default.cyan(` ${name} (${collection}):`));
|
|
110678
|
-
console.log(source_default.gray(` $ ${searchMemory(query, collection)}`));
|
|
110666
|
+
if (!injectedByPath.has(pathValue)) {
|
|
110667
|
+
injectedByPath.set(pathValue, file.content);
|
|
110679
110668
|
}
|
|
110680
|
-
|
|
110681
|
-
|
|
110682
|
-
|
|
110683
|
-
|
|
110684
|
-
const agentNames = Object.keys(config.agents);
|
|
110685
|
-
if (agentNames.length === 0) {
|
|
110686
|
-
console.log(source_default.yellow("No agents defined in switchroom.yaml"));
|
|
110687
|
-
return;
|
|
110669
|
+
const normalizedPath = pathValue.replace(/\\/g, "/");
|
|
110670
|
+
const baseName = path7.posix.basename(normalizedPath);
|
|
110671
|
+
if (!injectedByBaseName.has(baseName)) {
|
|
110672
|
+
injectedByBaseName.set(baseName, file.content);
|
|
110688
110673
|
}
|
|
110689
|
-
|
|
110690
|
-
|
|
110691
|
-
const
|
|
110692
|
-
|
|
110693
|
-
|
|
110694
|
-
|
|
110695
|
-
|
|
110696
|
-
|
|
110697
|
-
|
|
110698
|
-
|
|
110699
|
-
|
|
110700
|
-
|
|
110701
|
-
|
|
110702
|
-
|
|
110703
|
-
|
|
110704
|
-
|
|
110674
|
+
}
|
|
110675
|
+
return params.bootstrapFiles.map((file) => {
|
|
110676
|
+
const pathValue = typeof file.path === "string" ? file.path.trim() : "";
|
|
110677
|
+
const rawChars = file.missing ? 0 : (file.content ?? "").trimEnd().length;
|
|
110678
|
+
const injected = (pathValue ? injectedByPath.get(pathValue) : undefined) ?? injectedByPath.get(file.name) ?? injectedByBaseName.get(file.name);
|
|
110679
|
+
const injectedChars = injected ? injected.length : 0;
|
|
110680
|
+
const truncated = !file.missing && injectedChars < rawChars;
|
|
110681
|
+
return {
|
|
110682
|
+
name: file.name,
|
|
110683
|
+
path: pathValue || file.name,
|
|
110684
|
+
missing: file.missing,
|
|
110685
|
+
rawChars,
|
|
110686
|
+
injectedChars,
|
|
110687
|
+
truncated
|
|
110688
|
+
};
|
|
110689
|
+
});
|
|
110690
|
+
}
|
|
110691
|
+
function analyzeBootstrapBudget(params) {
|
|
110692
|
+
const bootstrapMaxChars = normalizePositiveLimit(params.bootstrapMaxChars);
|
|
110693
|
+
const bootstrapTotalMaxChars = normalizePositiveLimit(params.bootstrapTotalMaxChars);
|
|
110694
|
+
const nearLimitRatio = typeof params.nearLimitRatio === "number" && Number.isFinite(params.nearLimitRatio) && params.nearLimitRatio > 0 && params.nearLimitRatio < 1 ? params.nearLimitRatio : DEFAULT_BOOTSTRAP_NEAR_LIMIT_RATIO;
|
|
110695
|
+
const nonMissing = params.files.filter((file) => !file.missing);
|
|
110696
|
+
const rawChars = nonMissing.reduce((sum, file) => sum + file.rawChars, 0);
|
|
110697
|
+
const injectedChars = nonMissing.reduce((sum, file) => sum + file.injectedChars, 0);
|
|
110698
|
+
const totalNearLimit = injectedChars >= Math.ceil(bootstrapTotalMaxChars * nearLimitRatio);
|
|
110699
|
+
const totalOverLimit = injectedChars >= bootstrapTotalMaxChars;
|
|
110700
|
+
const files = params.files.map((file) => {
|
|
110701
|
+
if (file.missing) {
|
|
110702
|
+
return { ...file, nearLimit: false, causes: [] };
|
|
110705
110703
|
}
|
|
110706
|
-
|
|
110707
|
-
const
|
|
110708
|
-
|
|
110709
|
-
|
|
110710
|
-
|
|
110711
|
-
|
|
110712
|
-
owners.set(u.profile_bank, [
|
|
110713
|
-
...owners.get(u.profile_bank) ?? [],
|
|
110714
|
-
uname
|
|
110715
|
-
]);
|
|
110716
|
-
}
|
|
110704
|
+
const perFileOverLimit = file.rawChars > bootstrapMaxChars;
|
|
110705
|
+
const nearLimit = file.rawChars >= Math.ceil(bootstrapMaxChars * nearLimitRatio);
|
|
110706
|
+
const causes = [];
|
|
110707
|
+
if (file.truncated) {
|
|
110708
|
+
if (perFileOverLimit) {
|
|
110709
|
+
causes.push("per-file-limit");
|
|
110717
110710
|
}
|
|
110718
|
-
|
|
110719
|
-
|
|
110720
|
-
for (const bank of [...profileBanks].sort()) {
|
|
110721
|
-
const who = owners.get(bank);
|
|
110722
|
-
const label = who ? `user: ${who.join(", ")}` : "shared";
|
|
110723
|
-
console.log(` ${bank.padEnd(widths[1])} ${source_default.gray(label)}`);
|
|
110711
|
+
if (totalOverLimit) {
|
|
110712
|
+
causes.push("total-limit");
|
|
110724
110713
|
}
|
|
110725
|
-
console.log();
|
|
110726
|
-
}
|
|
110727
|
-
console.log(source_default.bold(` Hindsight CLI commands:
|
|
110728
|
-
`));
|
|
110729
|
-
for (const name of agentNames) {
|
|
110730
|
-
const collection = getCollectionForAgent(name, config);
|
|
110731
|
-
console.log(source_default.gray(` $ ${getMemoryStats(collection)}`));
|
|
110732
110714
|
}
|
|
110733
|
-
|
|
110734
|
-
})
|
|
110735
|
-
|
|
110736
|
-
|
|
110737
|
-
|
|
110738
|
-
|
|
110739
|
-
|
|
110740
|
-
|
|
110741
|
-
|
|
110742
|
-
|
|
110743
|
-
|
|
110744
|
-
|
|
110745
|
-
|
|
110715
|
+
return { ...file, nearLimit, causes };
|
|
110716
|
+
});
|
|
110717
|
+
const truncatedFiles = files.filter((file) => file.truncated);
|
|
110718
|
+
const nearLimitFiles = files.filter((file) => file.nearLimit);
|
|
110719
|
+
return {
|
|
110720
|
+
files,
|
|
110721
|
+
truncatedFiles,
|
|
110722
|
+
nearLimitFiles,
|
|
110723
|
+
totalNearLimit,
|
|
110724
|
+
hasTruncation: truncatedFiles.length > 0,
|
|
110725
|
+
totals: {
|
|
110726
|
+
rawChars,
|
|
110727
|
+
injectedChars,
|
|
110728
|
+
truncatedChars: Math.max(0, rawChars - injectedChars),
|
|
110729
|
+
bootstrapMaxChars,
|
|
110730
|
+
bootstrapTotalMaxChars,
|
|
110731
|
+
nearLimitRatio
|
|
110746
110732
|
}
|
|
110747
|
-
|
|
110748
|
-
|
|
110749
|
-
|
|
110750
|
-
|
|
110751
|
-
|
|
110752
|
-
|
|
110733
|
+
};
|
|
110734
|
+
}
|
|
110735
|
+
function buildBootstrapTruncationSignature(analysis) {
|
|
110736
|
+
if (!analysis.hasTruncation) {
|
|
110737
|
+
return;
|
|
110738
|
+
}
|
|
110739
|
+
const files = analysis.truncatedFiles.map((file) => ({
|
|
110740
|
+
path: file.path || file.name,
|
|
110741
|
+
rawChars: file.rawChars,
|
|
110742
|
+
injectedChars: file.injectedChars,
|
|
110743
|
+
causes: [...file.causes].sort()
|
|
110744
|
+
})).sort((a, b) => {
|
|
110745
|
+
const pathCmp = a.path.localeCompare(b.path);
|
|
110746
|
+
if (pathCmp !== 0) {
|
|
110747
|
+
return pathCmp;
|
|
110753
110748
|
}
|
|
110754
|
-
if (
|
|
110755
|
-
|
|
110756
|
-
Hindsight CLI commands:
|
|
110757
|
-
`));
|
|
110758
|
-
for (const cmd of commands) {
|
|
110759
|
-
console.log(source_default.gray(` $ ${cmd}`));
|
|
110760
|
-
}
|
|
110761
|
-
} else {
|
|
110762
|
-
console.log(source_default.yellow(`
|
|
110763
|
-
No eligible collections for reflection.`));
|
|
110749
|
+
if (a.rawChars !== b.rawChars) {
|
|
110750
|
+
return a.rawChars - b.rawChars;
|
|
110764
110751
|
}
|
|
110765
|
-
|
|
110766
|
-
|
|
110767
|
-
memory.command("setup").description("Manage the Hindsight Docker container").option("--stop", "Stop and remove the Hindsight container").option("--status", "Show Hindsight container status").option("--recreate", "Pull the latest image and recreate the container (reusing its current port). Used by `switchroom update` to keep the hindsight singleton current. Also required after any `hindsight.llm` model/provider config change \u2014 env is only re-derived on recreate, a plain docker restart is not enough (see docs/operators/hindsight-model-change.md).").option("--tag <version>", "Pin the hindsight image tag to pull + run (e.g. v0.15.18), overriding the " + "default floating `:latest`. Threaded through by `switchroom rollout` so a " + "version-pinned roll recreates hindsight on the SAME tag as the rest of the " + "fleet. Omit for `:latest` (the standalone default).").option("--provider <provider>", "LLM provider (ollama, openai, anthropic)").option("--strict-env", "Refuse the recreate (before the running container is touched) if it would drop " + "env vars that are live on the container but declared in neither `hindsight.env` " + "nor switchroom's managed defaults. Default is to warn loudly and proceed, so a " + "rollout is never wedged half-way with memory down.").option("--gpu", "Force GPU passthrough on for this launch, overriding host autodetection. " + "Use when `~/.switchroom/host-capabilities.json` is wrong or unreadable and " + "you know the host has a working nvidia container toolkit (`docker run --gpus " + "all` hard-fails container create without one). Beats `hindsight.gpu`.").option("--no-gpu", "Force GPU passthrough off for this launch, overriding host autodetection. " + "Also the explicit opt-out for the recreate GPU drop guard.").option("--allow-gpu-drop", "Proceed with a --recreate that would take GPU passthrough away from the " + "running container. Without this the recreate refuses, because that drop " + "moves the embedding model and cross-encoder reranker to CPU on the " + "interactive recall path with no other signal.").action(async (opts) => {
|
|
110768
|
-
if (opts.status) {
|
|
110769
|
-
if (!isDockerAvailable()) {
|
|
110770
|
-
console.log(source_default.red(" Docker is not available."));
|
|
110771
|
-
process.exit(1);
|
|
110772
|
-
}
|
|
110773
|
-
const status = getHindsightStatus();
|
|
110774
|
-
if (status) {
|
|
110775
|
-
console.log(source_default.bold(`
|
|
110776
|
-
Hindsight container status:`));
|
|
110777
|
-
console.log(` ${source_default.cyan("switchroom-hindsight")}: ${status}
|
|
110778
|
-
`);
|
|
110779
|
-
} else {
|
|
110780
|
-
console.log(source_default.yellow(`
|
|
110781
|
-
Hindsight container not found.
|
|
110782
|
-
`));
|
|
110783
|
-
console.log(source_default.gray(" Run 'switchroom memory setup' to start it."));
|
|
110784
|
-
}
|
|
110785
|
-
return;
|
|
110752
|
+
if (a.injectedChars !== b.injectedChars) {
|
|
110753
|
+
return a.injectedChars - b.injectedChars;
|
|
110786
110754
|
}
|
|
110787
|
-
|
|
110788
|
-
|
|
110789
|
-
|
|
110790
|
-
|
|
110791
|
-
|
|
110792
|
-
|
|
110793
|
-
|
|
110794
|
-
|
|
110795
|
-
|
|
110796
|
-
|
|
110797
|
-
|
|
110798
|
-
|
|
110799
|
-
|
|
110755
|
+
return a.causes.join("+").localeCompare(b.causes.join("+"));
|
|
110756
|
+
});
|
|
110757
|
+
return JSON.stringify({
|
|
110758
|
+
bootstrapMaxChars: analysis.totals.bootstrapMaxChars,
|
|
110759
|
+
bootstrapTotalMaxChars: analysis.totals.bootstrapTotalMaxChars,
|
|
110760
|
+
files
|
|
110761
|
+
});
|
|
110762
|
+
}
|
|
110763
|
+
function formatBootstrapTruncationWarningLines(params) {
|
|
110764
|
+
if (!params.analysis.hasTruncation) {
|
|
110765
|
+
return [];
|
|
110766
|
+
}
|
|
110767
|
+
const maxFiles = typeof params.maxFiles === "number" && Number.isFinite(params.maxFiles) && params.maxFiles > 0 ? Math.floor(params.maxFiles) : DEFAULT_BOOTSTRAP_PROMPT_WARNING_MAX_FILES;
|
|
110768
|
+
const lines = [];
|
|
110769
|
+
const duplicateNameCounts = params.analysis.truncatedFiles.reduce((acc, file) => {
|
|
110770
|
+
acc.set(file.name, (acc.get(file.name) ?? 0) + 1);
|
|
110771
|
+
return acc;
|
|
110772
|
+
}, new Map);
|
|
110773
|
+
const topFiles = params.analysis.truncatedFiles.slice(0, maxFiles);
|
|
110774
|
+
for (const file of topFiles) {
|
|
110775
|
+
const pct = file.rawChars > 0 ? Math.round((file.rawChars - file.injectedChars) / file.rawChars * 100) : 0;
|
|
110776
|
+
const causeText = file.causes.length > 0 ? file.causes.map((cause) => formatWarningCause(cause)).join(", ") : "";
|
|
110777
|
+
const nameLabel = (duplicateNameCounts.get(file.name) ?? 0) > 1 && file.path.trim().length > 0 ? `${file.name} (${file.path})` : file.name;
|
|
110778
|
+
lines.push(`${nameLabel}: ${file.rawChars} raw -> ${file.injectedChars} injected (~${Math.max(0, pct)}% removed${causeText ? `; ${causeText}` : ""}).`);
|
|
110779
|
+
}
|
|
110780
|
+
if (params.analysis.truncatedFiles.length > topFiles.length) {
|
|
110781
|
+
lines.push(`+${params.analysis.truncatedFiles.length - topFiles.length} more truncated file(s).`);
|
|
110782
|
+
}
|
|
110783
|
+
lines.push("If unintentional, raise switchroom workspace bootstrapMaxChars and/or bootstrapTotalMaxChars config.");
|
|
110784
|
+
return lines;
|
|
110785
|
+
}
|
|
110786
|
+
function buildBootstrapPromptWarning(params) {
|
|
110787
|
+
const signature = buildBootstrapTruncationSignature(params.analysis);
|
|
110788
|
+
let seenSignatures = normalizeSeenSignatures(params.seenSignatures);
|
|
110789
|
+
if (params.previousSignature && !seenSignatures.includes(params.previousSignature)) {
|
|
110790
|
+
seenSignatures = appendSeenSignature(seenSignatures, params.previousSignature);
|
|
110791
|
+
}
|
|
110792
|
+
const hasSeenSignature = Boolean(signature && seenSignatures.includes(signature));
|
|
110793
|
+
const warningShown = params.mode !== "off" && Boolean(signature) && (params.mode === "always" || !hasSeenSignature);
|
|
110794
|
+
const warningSignaturesSeen = signature && params.mode !== "off" ? appendSeenSignature(seenSignatures, signature) : seenSignatures;
|
|
110795
|
+
return {
|
|
110796
|
+
signature,
|
|
110797
|
+
warningShown,
|
|
110798
|
+
lines: warningShown ? formatBootstrapTruncationWarningLines({
|
|
110799
|
+
analysis: params.analysis,
|
|
110800
|
+
maxFiles: params.maxFiles
|
|
110801
|
+
}) : [],
|
|
110802
|
+
warningSignaturesSeen
|
|
110803
|
+
};
|
|
110804
|
+
}
|
|
110805
|
+
|
|
110806
|
+
// src/agents/bootstrap-types.ts
|
|
110807
|
+
var DEFAULT_AGENTS_FILENAME = "AGENTS.md";
|
|
110808
|
+
var DEFAULT_SOUL_DEFAULT_FILENAME = "SOUL.default.md";
|
|
110809
|
+
var DEFAULT_SOUL_FILENAME = "SOUL.md";
|
|
110810
|
+
var DEFAULT_TOOLS_FILENAME = "TOOLS.md";
|
|
110811
|
+
var DEFAULT_IDENTITY_FILENAME = "IDENTITY.md";
|
|
110812
|
+
var DEFAULT_USER_FILENAME = "USER.md";
|
|
110813
|
+
var DEFAULT_BOOTSTRAP_FILENAME = "BOOTSTRAP.md";
|
|
110814
|
+
var DEFAULT_MEMORY_FILENAME = "MEMORY.md";
|
|
110815
|
+
|
|
110816
|
+
// src/agents/workspace.ts
|
|
110817
|
+
var DEFAULT_WORKSPACE_DIR_NAME = "workspace";
|
|
110818
|
+
var DEFAULT_MEMORY_SUBDIR = "memory";
|
|
110819
|
+
var STABLE_BOOTSTRAP_FILENAMES = [
|
|
110820
|
+
DEFAULT_AGENTS_FILENAME,
|
|
110821
|
+
DEFAULT_SOUL_DEFAULT_FILENAME,
|
|
110822
|
+
DEFAULT_SOUL_FILENAME,
|
|
110823
|
+
DEFAULT_IDENTITY_FILENAME,
|
|
110824
|
+
DEFAULT_USER_FILENAME,
|
|
110825
|
+
DEFAULT_TOOLS_FILENAME,
|
|
110826
|
+
DEFAULT_BOOTSTRAP_FILENAME
|
|
110827
|
+
];
|
|
110828
|
+
var DYNAMIC_BOOTSTRAP_FILENAMES = [
|
|
110829
|
+
DEFAULT_MEMORY_FILENAME
|
|
110830
|
+
];
|
|
110831
|
+
var DEFAULT_BOOTSTRAP_MAX_CHARS = 12000;
|
|
110832
|
+
var DEFAULT_BOOTSTRAP_TOTAL_MAX_CHARS = 64000;
|
|
110833
|
+
var DEFAULT_DYNAMIC_TOTAL_MAX_CHARS = 24000;
|
|
110834
|
+
function resolveAgentWorkspaceDir(agentDir) {
|
|
110835
|
+
return path8.join(agentDir, DEFAULT_WORKSPACE_DIR_NAME);
|
|
110836
|
+
}
|
|
110837
|
+
async function readOptionalFile(filePath) {
|
|
110838
|
+
try {
|
|
110839
|
+
const content = await readFile2(filePath, "utf8");
|
|
110840
|
+
return content;
|
|
110841
|
+
} catch (err) {
|
|
110842
|
+
if (isErrnoException(err) && (err.code === "ENOENT" || err.code === "EISDIR")) {
|
|
110800
110843
|
return;
|
|
110801
110844
|
}
|
|
110802
|
-
|
|
110803
|
-
|
|
110804
|
-
|
|
110805
|
-
|
|
110806
|
-
|
|
110807
|
-
|
|
110808
|
-
|
|
110809
|
-
|
|
110810
|
-
|
|
110811
|
-
|
|
110812
|
-
|
|
110813
|
-
|
|
110814
|
-
|
|
110815
|
-
|
|
110816
|
-
|
|
110817
|
-
|
|
110818
|
-
|
|
110819
|
-
|
|
110820
|
-
|
|
110821
|
-
|
|
110822
|
-
|
|
110823
|
-
|
|
110845
|
+
throw err;
|
|
110846
|
+
}
|
|
110847
|
+
}
|
|
110848
|
+
function isErrnoException(value) {
|
|
110849
|
+
return value instanceof Error && typeof value.code === "string";
|
|
110850
|
+
}
|
|
110851
|
+
async function loadNamedFile(workspaceDir, name, relativePath) {
|
|
110852
|
+
const filePath = path8.join(workspaceDir, relativePath ?? name);
|
|
110853
|
+
const content = await readOptionalFile(filePath);
|
|
110854
|
+
if (content === undefined) {
|
|
110855
|
+
return { name, path: filePath, missing: true };
|
|
110856
|
+
}
|
|
110857
|
+
return { name, path: filePath, content, missing: false };
|
|
110858
|
+
}
|
|
110859
|
+
async function loadStableBootstrapFiles(workspaceDir, options) {
|
|
110860
|
+
const defaultFiles = await Promise.all(STABLE_BOOTSTRAP_FILENAMES.map((name) => loadNamedFile(workspaceDir, name)));
|
|
110861
|
+
const extras = options?.extraStableFiles ?? [];
|
|
110862
|
+
if (extras.length === 0) {
|
|
110863
|
+
return defaultFiles;
|
|
110864
|
+
}
|
|
110865
|
+
const extraFiles = await Promise.all(extras.map((name) => loadNamedFile(workspaceDir, name)));
|
|
110866
|
+
return [...defaultFiles, ...extraFiles];
|
|
110867
|
+
}
|
|
110868
|
+
async function loadDynamicBootstrapFiles(workspaceDir, options) {
|
|
110869
|
+
const now2 = options?.now ?? new Date;
|
|
110870
|
+
const includeYesterday = options?.includeYesterday ?? true;
|
|
110871
|
+
const files = await Promise.all(DYNAMIC_BOOTSTRAP_FILENAMES.map((name) => loadNamedFile(workspaceDir, name)));
|
|
110872
|
+
const todayRelative = dailyMemoryRelativePath(now2);
|
|
110873
|
+
const todayName = DEFAULT_MEMORY_FILENAME;
|
|
110874
|
+
const today = await loadNamedFile(workspaceDir, todayName, todayRelative);
|
|
110875
|
+
today.path = path8.join(workspaceDir, todayRelative);
|
|
110876
|
+
files.push(today);
|
|
110877
|
+
if (includeYesterday) {
|
|
110878
|
+
const yesterdayRelative = dailyMemoryRelativePath(addDays(now2, -1));
|
|
110879
|
+
const yesterday = await loadNamedFile(workspaceDir, todayName, yesterdayRelative);
|
|
110880
|
+
yesterday.path = path8.join(workspaceDir, yesterdayRelative);
|
|
110881
|
+
files.push(yesterday);
|
|
110882
|
+
}
|
|
110883
|
+
return files;
|
|
110884
|
+
}
|
|
110885
|
+
function pad22(n) {
|
|
110886
|
+
return n < 10 ? `0${n}` : String(n);
|
|
110887
|
+
}
|
|
110888
|
+
function dailyMemoryRelativePath(date) {
|
|
110889
|
+
const y = date.getFullYear();
|
|
110890
|
+
const m = pad22(date.getMonth() + 1);
|
|
110891
|
+
const d = pad22(date.getDate());
|
|
110892
|
+
return path8.join(DEFAULT_MEMORY_SUBDIR, `${y}-${m}-${d}.md`);
|
|
110893
|
+
}
|
|
110894
|
+
function addDays(date, days) {
|
|
110895
|
+
const out = new Date(date.getTime());
|
|
110896
|
+
out.setDate(out.getDate() + days);
|
|
110897
|
+
return out;
|
|
110898
|
+
}
|
|
110899
|
+
function truncateContent(name, content, maxChars) {
|
|
110900
|
+
if (content.length <= maxChars) {
|
|
110901
|
+
return content;
|
|
110902
|
+
}
|
|
110903
|
+
if (maxChars < 200) {
|
|
110904
|
+
return content.slice(0, Math.max(0, maxChars));
|
|
110905
|
+
}
|
|
110906
|
+
const headRoomInitial = Math.floor(maxChars * 0.7);
|
|
110907
|
+
const sampleMarker = `
|
|
110908
|
+
\u2026(truncated ${name}: kept ${headRoomInitial}+${Math.max(0, maxChars - headRoomInitial - 96)} chars of ${content.length})\u2026
|
|
110909
|
+
`;
|
|
110910
|
+
const markerReserve = Math.max(64, sampleMarker.length + 8);
|
|
110911
|
+
const headRoom = Math.min(headRoomInitial, Math.max(0, maxChars - markerReserve - 8));
|
|
110912
|
+
const tailRoom = Math.max(0, maxChars - headRoom - markerReserve);
|
|
110913
|
+
const head = content.slice(0, Math.max(0, headRoom));
|
|
110914
|
+
const tail = tailRoom > 0 ? content.slice(content.length - tailRoom) : "";
|
|
110915
|
+
const result = `${head}
|
|
110916
|
+
\u2026(truncated ${name}: kept ${headRoom}+${tailRoom} chars of ${content.length})\u2026
|
|
110917
|
+
${tail}`;
|
|
110918
|
+
return result.length <= maxChars ? result : result.slice(0, maxChars);
|
|
110919
|
+
}
|
|
110920
|
+
function projectBootstrapFiles(params) {
|
|
110921
|
+
const {
|
|
110922
|
+
files,
|
|
110923
|
+
heading,
|
|
110924
|
+
budget,
|
|
110925
|
+
seenSignatures,
|
|
110926
|
+
warningMode = "error",
|
|
110927
|
+
warningMaxFiles = DEFAULT_BOOTSTRAP_PROMPT_WARNING_MAX_FILES
|
|
110928
|
+
} = params;
|
|
110929
|
+
const perFileCap = budget.bootstrapMaxChars;
|
|
110930
|
+
let remainingTotal = budget.bootstrapTotalMaxChars;
|
|
110931
|
+
const injectedFiles = [];
|
|
110932
|
+
const parts = [];
|
|
110933
|
+
for (const file of files) {
|
|
110934
|
+
if (file.missing || typeof file.content !== "string") {
|
|
110935
|
+
continue;
|
|
110936
|
+
}
|
|
110937
|
+
const raw = file.content.trimEnd();
|
|
110938
|
+
if (raw.length === 0) {
|
|
110939
|
+
continue;
|
|
110940
|
+
}
|
|
110941
|
+
const perFileAllowed = Math.min(perFileCap, remainingTotal);
|
|
110942
|
+
if (perFileAllowed <= 0) {
|
|
110943
|
+
break;
|
|
110944
|
+
}
|
|
110945
|
+
const injected = truncateContent(file.name, raw, perFileAllowed);
|
|
110946
|
+
const relativePath = file.path;
|
|
110947
|
+
injectedFiles.push({ path: relativePath, content: injected });
|
|
110948
|
+
parts.push(`## ${relativePath}`);
|
|
110949
|
+
parts.push(injected);
|
|
110950
|
+
remainingTotal -= injected.length;
|
|
110951
|
+
if (remainingTotal <= 0) {
|
|
110952
|
+
break;
|
|
110953
|
+
}
|
|
110954
|
+
}
|
|
110955
|
+
const concatenated = parts.length > 0 ? [heading.trim().length > 0 ? `# ${heading}` : null, ...parts].filter((p) => p !== null).join(`
|
|
110956
|
+
|
|
110957
|
+
`) : "";
|
|
110958
|
+
const stats = buildBootstrapInjectionStats({
|
|
110959
|
+
bootstrapFiles: files,
|
|
110960
|
+
injectedFiles
|
|
110961
|
+
});
|
|
110962
|
+
const analysis = analyzeBootstrapBudget({
|
|
110963
|
+
files: stats,
|
|
110964
|
+
bootstrapMaxChars: budget.bootstrapMaxChars,
|
|
110965
|
+
bootstrapTotalMaxChars: budget.bootstrapTotalMaxChars,
|
|
110966
|
+
nearLimitRatio: budget.nearLimitRatio ?? DEFAULT_BOOTSTRAP_NEAR_LIMIT_RATIO
|
|
110967
|
+
});
|
|
110968
|
+
const warning = buildBootstrapPromptWarning({
|
|
110969
|
+
analysis,
|
|
110970
|
+
mode: warningMode,
|
|
110971
|
+
seenSignatures,
|
|
110972
|
+
maxFiles: warningMaxFiles
|
|
110973
|
+
});
|
|
110974
|
+
if (warningMode === "error" && analysis.hasTruncation) {
|
|
110975
|
+
const errorLines = [
|
|
110976
|
+
"Bootstrap budget exceeded. The following files exceed limits:",
|
|
110977
|
+
""
|
|
110978
|
+
];
|
|
110979
|
+
for (const file of analysis.truncatedFiles) {
|
|
110980
|
+
const excessChars = file.rawChars - file.injectedChars;
|
|
110981
|
+
const causes = file.causes.map((c) => c === "per-file-limit" ? `per-file limit (${analysis.totals.bootstrapMaxChars.toLocaleString()} chars)` : `total limit (${analysis.totals.bootstrapTotalMaxChars.toLocaleString()} chars)`);
|
|
110982
|
+
errorLines.push(` ${file.name}: ${file.rawChars.toLocaleString()} bytes (exceeds ${causes.join(", ")})`);
|
|
110983
|
+
errorLines.push(` Trim ${excessChars.toLocaleString()} bytes, or pass --warning-mode warn to proceed with truncation.`);
|
|
110984
|
+
}
|
|
110985
|
+
throw new BootstrapBudgetExceededError(analysis, errorLines.join(`
|
|
110986
|
+
`));
|
|
110987
|
+
}
|
|
110988
|
+
return { files, injectedFiles, concatenated, analysis, warning };
|
|
110989
|
+
}
|
|
110990
|
+
async function buildStableBootstrapPrompt(params) {
|
|
110991
|
+
const files = await loadStableBootstrapFiles(params.workspaceDir, {
|
|
110992
|
+
extraStableFiles: params.extraStableFiles
|
|
110993
|
+
});
|
|
110994
|
+
const budget = {
|
|
110995
|
+
bootstrapMaxChars: params.budget?.bootstrapMaxChars ?? DEFAULT_BOOTSTRAP_MAX_CHARS,
|
|
110996
|
+
bootstrapTotalMaxChars: params.budget?.bootstrapTotalMaxChars ?? DEFAULT_BOOTSTRAP_TOTAL_MAX_CHARS,
|
|
110997
|
+
nearLimitRatio: params.budget?.nearLimitRatio
|
|
110998
|
+
};
|
|
110999
|
+
return projectBootstrapFiles({
|
|
111000
|
+
files,
|
|
111001
|
+
heading: "Project Context (stable workspace files)",
|
|
111002
|
+
budget,
|
|
111003
|
+
seenSignatures: params.seenSignatures,
|
|
111004
|
+
warningMode: params.budget?.warningMode,
|
|
111005
|
+
warningMaxFiles: params.budget?.warningMaxFiles
|
|
111006
|
+
});
|
|
111007
|
+
}
|
|
111008
|
+
async function buildDynamicBootstrapPrompt(params) {
|
|
111009
|
+
const files = await loadDynamicBootstrapFiles(params.workspaceDir, {
|
|
111010
|
+
now: params.now,
|
|
111011
|
+
includeYesterday: params.includeYesterday
|
|
111012
|
+
});
|
|
111013
|
+
const budget = {
|
|
111014
|
+
bootstrapMaxChars: params.budget?.bootstrapMaxChars ?? DEFAULT_BOOTSTRAP_MAX_CHARS,
|
|
111015
|
+
bootstrapTotalMaxChars: params.budget?.bootstrapTotalMaxChars ?? DEFAULT_DYNAMIC_TOTAL_MAX_CHARS,
|
|
111016
|
+
nearLimitRatio: params.budget?.nearLimitRatio
|
|
111017
|
+
};
|
|
111018
|
+
return projectBootstrapFiles({
|
|
111019
|
+
files,
|
|
111020
|
+
heading: "Project Context (dynamic workspace files)",
|
|
111021
|
+
budget,
|
|
111022
|
+
seenSignatures: params.seenSignatures,
|
|
111023
|
+
warningMode: params.budget?.warningMode,
|
|
111024
|
+
warningMaxFiles: params.budget?.warningMaxFiles
|
|
111025
|
+
});
|
|
111026
|
+
}
|
|
111027
|
+
|
|
111028
|
+
// src/cli/debug.ts
|
|
111029
|
+
init_merge();
|
|
111030
|
+
init_hindsight2();
|
|
111031
|
+
function formatBytes(bytes) {
|
|
111032
|
+
return `${bytes.toLocaleString()} bytes`;
|
|
111033
|
+
}
|
|
111034
|
+
function estimateTokens(bytes) {
|
|
111035
|
+
return Math.round(bytes / 3.7);
|
|
111036
|
+
}
|
|
111037
|
+
function readMcpServerNames(agentDir) {
|
|
111038
|
+
const mcpPath = join88(agentDir, ".mcp.json");
|
|
111039
|
+
if (!existsSync82(mcpPath))
|
|
111040
|
+
return [];
|
|
111041
|
+
try {
|
|
111042
|
+
const parsed = JSON.parse(readFileSync74(mcpPath, "utf-8"));
|
|
111043
|
+
return Object.keys(parsed.mcpServers ?? {});
|
|
111044
|
+
} catch {
|
|
111045
|
+
return null;
|
|
111046
|
+
}
|
|
111047
|
+
}
|
|
111048
|
+
function sha2562(content) {
|
|
111049
|
+
return createHash15("sha256").update(content).digest("hex").slice(0, 16);
|
|
111050
|
+
}
|
|
111051
|
+
function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
111052
|
+
const projectsDir = join88(claudeConfigDir, "projects");
|
|
111053
|
+
if (!existsSync82(projectsDir))
|
|
111054
|
+
return;
|
|
111055
|
+
try {
|
|
111056
|
+
const entries = readdirSync30(projectsDir, { withFileTypes: true });
|
|
111057
|
+
let latest;
|
|
111058
|
+
for (const entry of entries) {
|
|
111059
|
+
if (!entry.isDirectory())
|
|
111060
|
+
continue;
|
|
111061
|
+
const projectPath = join88(projectsDir, entry.name);
|
|
111062
|
+
const transcriptPath = join88(projectPath, "transcript.jsonl");
|
|
111063
|
+
if (!existsSync82(transcriptPath))
|
|
111064
|
+
continue;
|
|
111065
|
+
const stat2 = statSync48(transcriptPath);
|
|
111066
|
+
if (!latest || stat2.mtimeMs > latest.mtime) {
|
|
111067
|
+
latest = { path: transcriptPath, mtime: stat2.mtimeMs };
|
|
111068
|
+
}
|
|
111069
|
+
}
|
|
111070
|
+
return latest?.path;
|
|
111071
|
+
} catch {
|
|
111072
|
+
return;
|
|
111073
|
+
}
|
|
111074
|
+
}
|
|
111075
|
+
function extractLatestUserMessage(transcriptPath) {
|
|
111076
|
+
try {
|
|
111077
|
+
const content = readFileSync74(transcriptPath, "utf-8");
|
|
111078
|
+
const lines = content.trim().split(`
|
|
111079
|
+
`).filter(Boolean);
|
|
111080
|
+
for (let i2 = lines.length - 1;i2 >= 0; i2--) {
|
|
111081
|
+
const line = lines[i2];
|
|
111082
|
+
try {
|
|
111083
|
+
const event = JSON.parse(line);
|
|
111084
|
+
if (event.type === "message" && event.role === "user" && typeof event.content === "string") {
|
|
111085
|
+
const timestamp = event.timestamp ? new Date(event.timestamp).toLocaleString() : "unknown";
|
|
111086
|
+
return { text: event.content, timestamp };
|
|
111087
|
+
}
|
|
111088
|
+
} catch {
|
|
111089
|
+
continue;
|
|
111090
|
+
}
|
|
111091
|
+
}
|
|
111092
|
+
} catch {
|
|
111093
|
+
return;
|
|
111094
|
+
}
|
|
111095
|
+
}
|
|
111096
|
+
function buildProgressUpdateGuidance() {
|
|
111097
|
+
return `## Progress updates (human-style check-ins)
|
|
111098
|
+
|
|
111099
|
+
You're talking to a human colleague on Telegram. Alongside the emoji status
|
|
111100
|
+
ladder, send a short \`progress_update\` at inflection points, the moments a
|
|
111101
|
+
senior colleague would ping the person who asked them to do something:
|
|
111102
|
+
|
|
111103
|
+
- **Plan formed:** "Got it. Going to do X first, then Y, then Z."
|
|
111104
|
+
- **Pivot or blocker:** "First approach didn't work because <reason>. Trying
|
|
111105
|
+
<alternative> instead."
|
|
111106
|
+
- **Chunk finished:** "Done with X. Starting Y now."
|
|
111107
|
+
|
|
111108
|
+
Keep them short (one or two sentences). Don't narrate every step, the pinned
|
|
111109
|
+
progress card shows that for free. Don't send an update on a trivial one-shot
|
|
111110
|
+
task. Send them when a colleague would genuinely want to know what's happening.
|
|
111111
|
+
|
|
111112
|
+
Final answers still go through \`reply\` as usual,
|
|
111113
|
+
\`progress_update\` is only for mid-turn check-ins.`;
|
|
111114
|
+
}
|
|
111115
|
+
function registerDebugCommand(program3) {
|
|
111116
|
+
const cmd = program3.command("debug", { hidden: true }).description("Observability tools for inspecting agent prompt layering [advanced]");
|
|
111117
|
+
cmd.command("turn <agent>").description("Dump the exact prompt layering the model saw on the most recent turn").option("--last <n>", "Show N-th most recent turn instead of latest", "1").action(withConfigError(async (agentName, opts) => {
|
|
111118
|
+
const config = getConfig(program3);
|
|
111119
|
+
const agentConfig = config.agents[agentName];
|
|
111120
|
+
if (!agentConfig) {
|
|
111121
|
+
console.error(`Agent '${agentName}' not found in switchroom.yaml`);
|
|
111122
|
+
process.exit(1);
|
|
111123
|
+
}
|
|
111124
|
+
const agentsDir = resolveAgentsDir(config);
|
|
111125
|
+
const agentDir = resolve43(agentsDir, agentName);
|
|
111126
|
+
if (!existsSync82(agentDir)) {
|
|
111127
|
+
console.error(`Agent directory not found: ${agentDir}`);
|
|
111128
|
+
process.exit(1);
|
|
111129
|
+
}
|
|
111130
|
+
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
111131
|
+
const claudeConfigDir = join88(agentDir, ".claude");
|
|
111132
|
+
const claudeMdPath2 = join88(agentDir, "CLAUDE.md");
|
|
111133
|
+
const soulMdPath = join88(agentDir, "SOUL.md");
|
|
111134
|
+
const workspaceSoulMdPath = join88(workspaceDir, "SOUL.md");
|
|
111135
|
+
const handoffPath = join88(agentDir, ".handoff.md");
|
|
111136
|
+
const lastN = parseInt(opts.last, 10);
|
|
111137
|
+
if (isNaN(lastN) || lastN < 1) {
|
|
111138
|
+
console.error("--last must be a positive integer");
|
|
111139
|
+
process.exit(1);
|
|
111140
|
+
}
|
|
111141
|
+
if (lastN > 1) {
|
|
111142
|
+
console.error("Note: --last N where N > 1 not yet implemented (only latest turn supported)");
|
|
111143
|
+
process.exit(1);
|
|
111144
|
+
}
|
|
111145
|
+
console.log(`=== Debug Turn Dump: ${agentName} ===
|
|
111146
|
+
`);
|
|
111147
|
+
console.log(`=== Append System Prompt (stable) ===
|
|
111148
|
+
`);
|
|
111149
|
+
const resolved = resolveAgentConfig(config.defaults, config.profiles, agentConfig);
|
|
111150
|
+
const useHotReloadStable = resolved.channels?.telegram?.hotReloadStable === true;
|
|
111151
|
+
const stableResult = await buildStableBootstrapPrompt({
|
|
111152
|
+
workspaceDir,
|
|
111153
|
+
budget: { warningMode: "off" }
|
|
111154
|
+
});
|
|
111155
|
+
if (useHotReloadStable) {
|
|
111156
|
+
console.log(`-- Workspace Stable Render (not used \u2014 stable content is in per-turn hook) --`);
|
|
111157
|
+
console.log();
|
|
111158
|
+
} else if (stableResult.concatenated.trim().length > 0) {
|
|
111159
|
+
console.log(`-- Workspace Stable Render (${formatBytes(stableResult.concatenated.length)}) --`);
|
|
111160
|
+
console.log(stableResult.concatenated);
|
|
111161
|
+
console.log();
|
|
111162
|
+
} else {
|
|
111163
|
+
console.log("-- Workspace Stable Render (0 bytes, not set up) --");
|
|
111164
|
+
console.log();
|
|
111165
|
+
}
|
|
111166
|
+
const useSwitchroomPlugin = usesSwitchroomTelegramPlugin(resolved);
|
|
111167
|
+
const progressGuidance = useSwitchroomPlugin ? buildProgressUpdateGuidance() : "";
|
|
111168
|
+
if (progressGuidance.length > 0) {
|
|
111169
|
+
console.log(`-- Progress Updates Guidance (${formatBytes(progressGuidance.length)}) --`);
|
|
111170
|
+
console.log(progressGuidance);
|
|
111171
|
+
console.log();
|
|
111172
|
+
}
|
|
111173
|
+
const baseSystemPromptAppend = resolved.system_prompt_append ?? "";
|
|
111174
|
+
if (baseSystemPromptAppend.trim().length > 0) {
|
|
111175
|
+
console.log(`-- User System Prompt Append (${formatBytes(baseSystemPromptAppend.length)}) --`);
|
|
111176
|
+
console.log(baseSystemPromptAppend);
|
|
111177
|
+
console.log();
|
|
111178
|
+
}
|
|
111179
|
+
console.log(`=== Append System Prompt (per-session) ===
|
|
111180
|
+
`);
|
|
111181
|
+
const handoffContent = existsSync82(handoffPath) ? readFileSync74(handoffPath, "utf-8") : "";
|
|
111182
|
+
if (handoffContent.trim().length > 0) {
|
|
111183
|
+
console.log(`-- Handoff Briefing (${formatBytes(handoffContent.length)}) --`);
|
|
111184
|
+
console.log(handoffContent);
|
|
111185
|
+
console.log();
|
|
111186
|
+
} else {
|
|
111187
|
+
console.log("-- Handoff Briefing (0 bytes, no prior session) --");
|
|
111188
|
+
console.log();
|
|
111189
|
+
}
|
|
111190
|
+
console.log(`=== CLAUDE.md (auto-loaded by Claude Code) ===
|
|
111191
|
+
`);
|
|
111192
|
+
const claudeMdContent = existsSync82(claudeMdPath2) ? readFileSync74(claudeMdPath2, "utf-8") : "";
|
|
111193
|
+
if (claudeMdContent.trim().length > 0) {
|
|
111194
|
+
console.log(`(${formatBytes(claudeMdContent.length)})`);
|
|
111195
|
+
console.log(claudeMdContent);
|
|
111196
|
+
console.log();
|
|
111197
|
+
} else {
|
|
111198
|
+
console.log("(0 bytes, not present)");
|
|
111199
|
+
console.log();
|
|
111200
|
+
}
|
|
111201
|
+
console.log(`=== Persona (SOUL.md) ===
|
|
111202
|
+
`);
|
|
111203
|
+
const soulMdContent = existsSync82(soulMdPath) ? readFileSync74(soulMdPath, "utf-8") : existsSync82(workspaceSoulMdPath) ? readFileSync74(workspaceSoulMdPath, "utf-8") : "";
|
|
111204
|
+
if (soulMdContent.trim().length > 0) {
|
|
111205
|
+
console.log(`(${formatBytes(soulMdContent.length)})`);
|
|
111206
|
+
console.log(soulMdContent);
|
|
111207
|
+
console.log();
|
|
111208
|
+
} else {
|
|
111209
|
+
console.log("(0 bytes, stale placeholder \u2014 Phase 2 item: single source of truth for persona)");
|
|
111210
|
+
console.log();
|
|
111211
|
+
}
|
|
111212
|
+
console.log(`=== Per-Turn Injections (UserPromptSubmit) ===
|
|
111213
|
+
`);
|
|
111214
|
+
if (useHotReloadStable) {
|
|
111215
|
+
if (stableResult.concatenated.trim().length > 0) {
|
|
111216
|
+
console.log(`-- Workspace Stable (hot-reload hook): ${formatBytes(stableResult.concatenated.length)} --`);
|
|
111217
|
+
console.log(stableResult.concatenated);
|
|
111218
|
+
console.log();
|
|
111219
|
+
} else {
|
|
111220
|
+
console.log("-- Workspace Stable (hot-reload hook): 0 bytes, not set up --");
|
|
111221
|
+
console.log();
|
|
111222
|
+
}
|
|
111223
|
+
}
|
|
111224
|
+
const dynamicResult = await buildDynamicBootstrapPrompt({
|
|
111225
|
+
workspaceDir,
|
|
111226
|
+
budget: { warningMode: "off" }
|
|
111227
|
+
});
|
|
111228
|
+
if (dynamicResult.concatenated.trim().length > 0) {
|
|
111229
|
+
console.log(`-- Workspace Dynamic: fired, ${formatBytes(dynamicResult.concatenated.length)} --`);
|
|
111230
|
+
console.log(dynamicResult.concatenated);
|
|
111231
|
+
console.log();
|
|
111232
|
+
} else {
|
|
111233
|
+
console.log("-- Workspace Dynamic: no content (MEMORY.md and daily notes empty or missing) --");
|
|
111234
|
+
console.log();
|
|
111235
|
+
}
|
|
111236
|
+
const hindsightEnabled = isHindsightEnabled(config) && agentConfig.memory?.auto_recall !== false;
|
|
111237
|
+
if (hindsightEnabled) {
|
|
111238
|
+
console.log("-- Hindsight Recall: enabled (exact content unavailable, check hindsight logs) --");
|
|
111239
|
+
console.log();
|
|
111240
|
+
} else {
|
|
111241
|
+
console.log("-- Hindsight Recall: disabled --");
|
|
111242
|
+
console.log();
|
|
111243
|
+
}
|
|
111244
|
+
console.log(`=== User Message (latest turn) ===
|
|
111245
|
+
`);
|
|
111246
|
+
const transcriptPath = findLatestTranscriptJsonl(claudeConfigDir);
|
|
111247
|
+
const userMessage = transcriptPath ? extractLatestUserMessage(transcriptPath) : undefined;
|
|
111248
|
+
if (userMessage) {
|
|
111249
|
+
console.log(`(Turn timestamp: ${userMessage.timestamp})`);
|
|
111250
|
+
console.log(userMessage.text);
|
|
111251
|
+
console.log();
|
|
111252
|
+
} else {
|
|
111253
|
+
console.log("(unavailable: no transcript found or transcript empty)");
|
|
111254
|
+
console.log();
|
|
111255
|
+
}
|
|
111256
|
+
console.log(`=== Totals ===
|
|
111257
|
+
`);
|
|
111258
|
+
const stableBytes = stableResult.concatenated.length + progressGuidance.length + baseSystemPromptAppend.length;
|
|
111259
|
+
const perSessionBytes = handoffContent.length;
|
|
111260
|
+
const claudeMdBytes = claudeMdContent.length;
|
|
111261
|
+
const soulMdBytes = soulMdContent.length;
|
|
111262
|
+
const perTurnBytes = dynamicResult.concatenated.length;
|
|
111263
|
+
const userBytes = userMessage?.text.length ?? 0;
|
|
111264
|
+
const fleetDir = join88(agentsDir, "..", "fleet");
|
|
111265
|
+
const fleetInvPath = join88(fleetDir, "switchroom-invariants.md");
|
|
111266
|
+
const fleetClaudePath = join88(fleetDir, "CLAUDE.md");
|
|
111267
|
+
const fleetInvBytes = existsSync82(fleetInvPath) ? readFileSync74(fleetInvPath, "utf-8").length : 0;
|
|
111268
|
+
const fleetClaudeBytes = existsSync82(fleetClaudePath) ? readFileSync74(fleetClaudePath, "utf-8").length : 0;
|
|
111269
|
+
const fleetBytes = fleetInvBytes + fleetClaudeBytes;
|
|
111270
|
+
const totalBytes = stableBytes + perSessionBytes + claudeMdBytes + fleetBytes + perTurnBytes + userBytes;
|
|
111271
|
+
console.log(`Stable prefix: ${formatBytes(stableBytes).padEnd(20)} (cache-hot; includes SOUL.md ${soulMdBytes.toLocaleString()}B)`);
|
|
111272
|
+
console.log(`Per-session: ${formatBytes(perSessionBytes).padEnd(20)} (cache-warm until next session)`);
|
|
111273
|
+
console.log(`CLAUDE.md (cwd): ${formatBytes(claudeMdBytes).padEnd(20)} (cache-hot)`);
|
|
111274
|
+
console.log(`Fleet invariants: ${formatBytes(fleetBytes).padEnd(20)} (--add-dir, cache-hot)`);
|
|
111275
|
+
console.log(`Per-turn: ${formatBytes(perTurnBytes).padEnd(20)} (never cached \u2014 the real per-turn $ cost)`);
|
|
111276
|
+
console.log(`User message: ${formatBytes(userBytes).padEnd(20)}`);
|
|
111277
|
+
console.log(`Authored text: ${formatBytes(totalBytes).padEnd(20)} (~${estimateTokens(totalBytes).toLocaleString()} tokens est.)`);
|
|
111278
|
+
const mcpServers = readMcpServerNames(agentDir);
|
|
111279
|
+
const mcpCount = mcpServers?.length ?? null;
|
|
111280
|
+
const mcpEstK = mcpCount != null ? mcpCount * 3 : null;
|
|
111281
|
+
const mcpLabel = mcpCount != null ? `${mcpCount} servers` : "(unreadable \u2014 agent-private .mcp.json)";
|
|
111282
|
+
const mcpEstLabel = mcpEstK != null ? `~${mcpEstK.toLocaleString()}k tok` : "~30k tok (audit est.)";
|
|
111283
|
+
console.log(`MCP tool surface: ${mcpLabel.padEnd(20)} (NOT counted above; ~3k tok/server \u2248 ${mcpEstLabel} \u2014 deferred under tool search)`);
|
|
111284
|
+
if (mcpServers && mcpServers.length > 0) {
|
|
111285
|
+
console.log(` [${mcpServers.join(", ")}]`);
|
|
111286
|
+
}
|
|
111287
|
+
const floorMcp = mcpEstK != null ? `~${mcpEstK.toLocaleString()}k` : "~30k";
|
|
111288
|
+
console.log(`Per-turn FLOOR: ${`~${estimateTokens(totalBytes).toLocaleString()} + ${floorMcp} MCP + ~13k CLI-base`.padEnd(20)} tokens est. (before the user msg, recall, or tool results)`);
|
|
111289
|
+
const stableCacheInput = stableResult.concatenated + progressGuidance + baseSystemPromptAppend;
|
|
111290
|
+
const stableHash = sha2562(stableCacheInput);
|
|
111291
|
+
console.log(`Cache stable hash: sha256:${stableHash}`);
|
|
111292
|
+
console.log();
|
|
111293
|
+
}));
|
|
111294
|
+
}
|
|
111295
|
+
|
|
111296
|
+
// src/memory/directive-residue.ts
|
|
111297
|
+
var RESIDUE_CATEGORIES = new Set([
|
|
111298
|
+
"rules-block",
|
|
111299
|
+
"reflect-directive"
|
|
111300
|
+
]);
|
|
111301
|
+
function renderInjectedLine(index, directive) {
|
|
111302
|
+
const priority = directive.priority ?? 0;
|
|
111303
|
+
const name = (directive.name ?? "").trim() || "(unnamed)";
|
|
111304
|
+
const content = (directive.content ?? "").trim();
|
|
111305
|
+
return `${index}. [P${priority}] ${name}: ${content}`;
|
|
111306
|
+
}
|
|
111307
|
+
function measureDirectiveResidue(agent, rows, directivesById) {
|
|
111308
|
+
const residueRows = rows.filter((r) => r.isActive && RESIDUE_CATEGORIES.has(r.category));
|
|
111309
|
+
const lines = residueRows.map((row, i2) => {
|
|
111310
|
+
const directive = directivesById.get(row.id);
|
|
111311
|
+
if (!directive) {
|
|
111312
|
+
throw new Error(`measureDirectiveResidue: row '${row.name}' (id ${row.id}) has no ` + "matching directive in directivesById \u2014 pass the SAME list the " + "rows were built from.");
|
|
111313
|
+
}
|
|
111314
|
+
return renderInjectedLine(i2 + 1, directive);
|
|
111315
|
+
});
|
|
111316
|
+
const residueBytes = lines.reduce((sum, line) => sum + Buffer.byteLength(line, "utf8") + 1, 0);
|
|
111317
|
+
return {
|
|
111318
|
+
agent,
|
|
111319
|
+
residueDirectiveCount: residueRows.length,
|
|
111320
|
+
totalDirectiveCount: rows.length,
|
|
111321
|
+
residueBytes,
|
|
111322
|
+
residueTokensEstimate: estimateTokens(residueBytes)
|
|
111323
|
+
};
|
|
111324
|
+
}
|
|
111325
|
+
|
|
111326
|
+
// src/memory/directive-flip.ts
|
|
111327
|
+
init_rules_block();
|
|
111328
|
+
function evaluateFlipReadiness(measurement, opts) {
|
|
111329
|
+
const reasons = [];
|
|
111330
|
+
if (!opts.rulesBlockEnabled) {
|
|
111331
|
+
reasons.push("memory.rules_block (M1) is not enabled \u2014 enable the rules-block " + "toolchain and migrate the directives into the block BEFORE flipping " + "inject_directives off (two-flag ordering; flipping first strips every " + "guardrail).");
|
|
111332
|
+
}
|
|
111333
|
+
if (measurement.residueBytes > RULES_BLOCK_BUDGET_BYTES) {
|
|
111334
|
+
reasons.push(`directive residue is ${measurement.residueBytes}B, over the ` + `${RULES_BLOCK_BUDGET_BYTES}B rules-block budget (` + `${measurement.residueDirectiveCount} active residue directive(s)). ` + "Triage the directive set down \u2014 retire stale rules or shorten bodies \u2014 " + "until the residue fits, then re-check. The budget, not MAX_DIRECTIVES, " + "is the binding gate.");
|
|
111335
|
+
}
|
|
111336
|
+
return {
|
|
111337
|
+
agent: measurement.agent,
|
|
111338
|
+
ready: reasons.length === 0,
|
|
111339
|
+
reasons,
|
|
111340
|
+
residueBytes: measurement.residueBytes,
|
|
111341
|
+
budgetBytes: RULES_BLOCK_BUDGET_BYTES,
|
|
111342
|
+
residueDirectiveCount: measurement.residueDirectiveCount
|
|
111343
|
+
};
|
|
111344
|
+
}
|
|
111345
|
+
async function measureLiveResidue(admin, agent) {
|
|
111346
|
+
const directives = await admin.list();
|
|
111347
|
+
const rows = buildDirectiveTriageRows(directives);
|
|
111348
|
+
const byId = new Map(directives.map((d) => [d.id, d]));
|
|
111349
|
+
return measureDirectiveResidue(agent, rows, byId);
|
|
111350
|
+
}
|
|
111351
|
+
function flipConfigStanza(agent) {
|
|
111352
|
+
return [
|
|
111353
|
+
`# ${agent} \u2014 Memory v2 M3 Surface-A flip (suppress <active_directives>)`,
|
|
111354
|
+
"memory:",
|
|
111355
|
+
" rules_block: true # M1 must already be on (checked)",
|
|
111356
|
+
" inject_directives: false # M3: stop re-injecting migrated directives"
|
|
111357
|
+
].join(`
|
|
111358
|
+
`);
|
|
111359
|
+
}
|
|
111360
|
+
|
|
111361
|
+
// src/cli/memory-flip.ts
|
|
111362
|
+
function resolveFlipAdmin(program3, agent) {
|
|
111363
|
+
const config = getConfig(program3);
|
|
111364
|
+
if (!config.agents[agent]) {
|
|
111365
|
+
console.error(source_default.red(`Agent "${agent}" is not defined in switchroom.yaml`));
|
|
111366
|
+
process.exit(1);
|
|
111367
|
+
}
|
|
111368
|
+
if (!isHindsightEnabled(config)) {
|
|
111369
|
+
console.error(source_default.red("Hindsight memory is not enabled in this switchroom.yaml."));
|
|
111370
|
+
process.exit(1);
|
|
111371
|
+
}
|
|
111372
|
+
const agentConfig = config.agents[agent];
|
|
111373
|
+
const mcpBaseUrl = config.memory?.config?.url ?? HINDSIGHT_DEFAULT_MCP_URL;
|
|
111374
|
+
const apiBaseUrl = mcpBaseUrl.replace(/\/mcp\/?$/, "").replace(/\/+$/, "");
|
|
111375
|
+
const bankId = agentConfig?.memory?.collection ?? agent;
|
|
111376
|
+
return new DirectiveAdmin({ apiBaseUrl, bankId });
|
|
111377
|
+
}
|
|
111378
|
+
function readRulesBlockEnabled(program3, agent) {
|
|
111379
|
+
const config = getConfig(program3);
|
|
111380
|
+
return config.agents[agent]?.memory?.rules_block === true;
|
|
111381
|
+
}
|
|
111382
|
+
function buildFlipPreflightJson(readiness, rulesBlockEnabled) {
|
|
111383
|
+
return {
|
|
111384
|
+
ok: true,
|
|
111385
|
+
agent: readiness.agent,
|
|
111386
|
+
ready: readiness.ready,
|
|
111387
|
+
rules_block: rulesBlockEnabled,
|
|
111388
|
+
residue_bytes: readiness.residueBytes,
|
|
111389
|
+
budget_bytes: readiness.budgetBytes,
|
|
111390
|
+
fits_budget: readiness.residueBytes <= readiness.budgetBytes,
|
|
111391
|
+
residue_directive_count: readiness.residueDirectiveCount,
|
|
111392
|
+
reasons: readiness.reasons
|
|
111393
|
+
};
|
|
111394
|
+
}
|
|
111395
|
+
function renderFlipPreflight(readiness, rulesBlockEnabled) {
|
|
111396
|
+
const fits = readiness.residueBytes <= readiness.budgetBytes;
|
|
111397
|
+
const lines = [];
|
|
111398
|
+
const verdict = readiness.ready ? source_default.green("READY") : source_default.red("NOT-READY");
|
|
111399
|
+
lines.push(`${verdict} \u2014 ${readiness.agent} M3 flip preflight`);
|
|
111400
|
+
lines.push("");
|
|
111401
|
+
lines.push(` residue: ${readiness.residueBytes}B / ${readiness.budgetBytes}B budget ` + `(${readiness.residueDirectiveCount} active residue directive(s)) ` + (fits ? source_default.green("fits") : source_default.red("OVER budget")));
|
|
111402
|
+
lines.push(` rules_block: ${rulesBlockEnabled ? source_default.green("on") : source_default.red("off")}`);
|
|
111403
|
+
if (readiness.ready) {
|
|
111404
|
+
lines.push("");
|
|
111405
|
+
lines.push(" Apply this per-agent stanza to switchroom.yaml, then restart:");
|
|
111406
|
+
lines.push("");
|
|
111407
|
+
for (const l of flipConfigStanza(readiness.agent).split(`
|
|
111408
|
+
`)) {
|
|
111409
|
+
lines.push(` ${l}`);
|
|
111410
|
+
}
|
|
111411
|
+
} else {
|
|
111412
|
+
lines.push("");
|
|
111413
|
+
lines.push(source_default.red(" Blocked:"));
|
|
111414
|
+
for (const reason of readiness.reasons) {
|
|
111415
|
+
lines.push(` - ${reason}`);
|
|
111416
|
+
}
|
|
111417
|
+
}
|
|
111418
|
+
return lines.join(`
|
|
111419
|
+
`);
|
|
111420
|
+
}
|
|
111421
|
+
function registerMemoryFlipCommand(memory, program3, deps = {}) {
|
|
111422
|
+
const makeAdmin = deps.makeAdmin ?? resolveFlipAdmin;
|
|
111423
|
+
memory.command("flip-preflight <agent>").description("Deterministic M3 (Surface-A) flip-readiness gate for <agent>: measure " + "the live post-triage directive residue, check it fits the 6144B " + "rules-block budget AND that memory.rules_block is already on, and " + "print READY + the exact switchroom.yaml stanza, or NOT-READY + why. " + "Never mutates config \u2014 the flip itself is an operator-gated edit.").option("--json", "Machine-readable output").action(withConfigError(async (agent, opts) => {
|
|
111424
|
+
const admin = makeAdmin(program3, agent);
|
|
111425
|
+
const rulesBlockEnabled = readRulesBlockEnabled(program3, agent);
|
|
111426
|
+
let ready;
|
|
111427
|
+
try {
|
|
111428
|
+
const measurement = await measureLiveResidue(admin, agent);
|
|
111429
|
+
const readiness = evaluateFlipReadiness(measurement, { rulesBlockEnabled });
|
|
111430
|
+
ready = readiness.ready;
|
|
111431
|
+
if (opts.json) {
|
|
111432
|
+
console.log(JSON.stringify(buildFlipPreflightJson(readiness, rulesBlockEnabled)));
|
|
111433
|
+
} else {
|
|
111434
|
+
console.log(renderFlipPreflight(readiness, rulesBlockEnabled));
|
|
111435
|
+
}
|
|
111436
|
+
} catch (e) {
|
|
111437
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
111438
|
+
if (opts.json) {
|
|
111439
|
+
console.log(JSON.stringify({ ok: false, error: msg }));
|
|
111440
|
+
} else {
|
|
111441
|
+
console.error(source_default.red(`\u2717 flip preflight for ${agent} failed: ${msg}`));
|
|
111442
|
+
}
|
|
111443
|
+
process.exit(1);
|
|
111444
|
+
return;
|
|
111445
|
+
}
|
|
111446
|
+
if (!ready)
|
|
111447
|
+
process.exit(2);
|
|
111448
|
+
}));
|
|
111449
|
+
}
|
|
111450
|
+
|
|
111451
|
+
// src/cli/memory.ts
|
|
111452
|
+
function persistMemoryConfigUrl(configPath, provider, url) {
|
|
111453
|
+
if (!existsSync83(configPath))
|
|
111454
|
+
return false;
|
|
111455
|
+
const raw = readFileSync75(configPath, "utf-8");
|
|
111456
|
+
const doc = import_yaml16.default.parseDocument(raw);
|
|
111457
|
+
if (!doc.has("memory")) {
|
|
111458
|
+
doc.set("memory", { backend: "hindsight", shared_collection: "shared", config: { provider, url } });
|
|
111459
|
+
} else {
|
|
111460
|
+
const memNode = doc.get("memory");
|
|
111461
|
+
if (!memNode.has("config")) {
|
|
111462
|
+
memNode.set("config", { provider, url });
|
|
111463
|
+
} else {
|
|
111464
|
+
const configNode = memNode.get("config");
|
|
111465
|
+
configNode.set("url", url);
|
|
111466
|
+
if (!configNode.has("provider")) {
|
|
111467
|
+
configNode.set("provider", provider);
|
|
111468
|
+
}
|
|
111469
|
+
}
|
|
111470
|
+
}
|
|
111471
|
+
let mode = 420;
|
|
111472
|
+
try {
|
|
111473
|
+
mode = statSync49(configPath).mode & 511;
|
|
111474
|
+
} catch {}
|
|
111475
|
+
writeConfigFileSync(configPath, doc.toString(), mode);
|
|
111476
|
+
return true;
|
|
111477
|
+
}
|
|
111478
|
+
function resolveMemorySetupTag(input) {
|
|
111479
|
+
const explicit = input.explicitTag?.trim();
|
|
111480
|
+
if (explicit) {
|
|
111481
|
+
if (explicit.toLowerCase() === "latest") {
|
|
111482
|
+
return { tag: undefined, reason: "explicit-latest" };
|
|
111483
|
+
}
|
|
111484
|
+
const normalized = normalizeHindsightVersionTag(explicit);
|
|
111485
|
+
if (!normalized)
|
|
111486
|
+
return { tag: explicit, reason: "invalid" };
|
|
111487
|
+
return { tag: normalized, reason: "explicit" };
|
|
111488
|
+
}
|
|
111489
|
+
const pin = normalizeHindsightVersionTag(input.releasePin);
|
|
111490
|
+
if (pin)
|
|
111491
|
+
return { tag: pin, reason: "pin" };
|
|
111492
|
+
return { tag: undefined, reason: "latest" };
|
|
111493
|
+
}
|
|
111494
|
+
function hindsightSecretValues(input) {
|
|
111495
|
+
const out = [];
|
|
111496
|
+
const push = (v) => {
|
|
111497
|
+
if (typeof v === "string" && v.trim().length > 0)
|
|
111498
|
+
out.push(v.trim());
|
|
111499
|
+
};
|
|
111500
|
+
push(input.cpAccessKey);
|
|
111501
|
+
push(input.llm?.api_key);
|
|
111502
|
+
for (const op of ["retain", "reflect", "consolidation"]) {
|
|
111503
|
+
push(input.llm?.[op]?.api_key);
|
|
111504
|
+
}
|
|
111505
|
+
return out;
|
|
111506
|
+
}
|
|
111507
|
+
function hasUnresolvedHindsightVaultRef(config) {
|
|
111508
|
+
const hindsight = config.hindsight;
|
|
111509
|
+
return hindsightSecretValues({
|
|
111510
|
+
llm: hindsight?.llm,
|
|
111511
|
+
cpAccessKey: hindsight?.cp_access_key
|
|
111512
|
+
}).some((v) => isVaultReference(v));
|
|
111513
|
+
}
|
|
111514
|
+
function emitsCleartextHindsightSecret(input) {
|
|
111515
|
+
return hindsightSecretValues(input).some((v) => !isVaultReference(v));
|
|
111516
|
+
}
|
|
111517
|
+
var HINDSIGHT_COMPOSE_CLEARTEXT_SECRETS_WARNING = "This snippet embeds secrets in CLEARTEXT (LLM api_key and/or cp_access_key)." + " Treat the output as sensitive \u2014 do not paste it into shared channels or" + " commit it unredacted.";
|
|
111518
|
+
async function resolveLiteLLMForHindsight(config) {
|
|
111519
|
+
return resolveHindsightLiteLlm(config);
|
|
111520
|
+
}
|
|
111521
|
+
function readRecallLog(agentDir, limit) {
|
|
111522
|
+
const path9 = join89(agentDir, ".claude", "plugins", "data", "hindsight-memory-inline", "state", "recall_log.jsonl");
|
|
111523
|
+
if (!existsSync83(path9))
|
|
111524
|
+
return [];
|
|
111525
|
+
let raw;
|
|
111526
|
+
try {
|
|
111527
|
+
raw = readFileSync75(path9, "utf-8");
|
|
111528
|
+
} catch {
|
|
111529
|
+
return [];
|
|
111530
|
+
}
|
|
111531
|
+
const lines = raw.split(`
|
|
111532
|
+
`).filter((l) => l.trim().length > 0);
|
|
111533
|
+
const tail = lines.slice(-limit);
|
|
111534
|
+
const out = [];
|
|
111535
|
+
for (const line of tail) {
|
|
111536
|
+
try {
|
|
111537
|
+
out.push(JSON.parse(line));
|
|
111538
|
+
} catch {}
|
|
111539
|
+
}
|
|
111540
|
+
return out;
|
|
111541
|
+
}
|
|
111542
|
+
function formatRecallLogView(name, entries) {
|
|
111543
|
+
const lines = [source_default.bold(`
|
|
111544
|
+
${name}:`)];
|
|
111545
|
+
const total = entries.length;
|
|
111546
|
+
const hits = entries.filter((e) => e.cache_hit).length;
|
|
111547
|
+
const cappedTurns = entries.filter((e) => e.capped).length;
|
|
111548
|
+
const omittedTurns = entries.filter((e) => typeof e.directives_omitted === "number" && e.directives_omitted > 0).length;
|
|
111549
|
+
const memCounts = entries.map((e) => e.result_count).filter((n) => typeof n === "number");
|
|
111550
|
+
const avg = memCounts.length > 0 ? Math.round(memCounts.reduce((s, n) => s + n, 0) / memCounts.length * 10) / 10 : null;
|
|
111551
|
+
const max = memCounts.length > 0 ? Math.max(...memCounts) : null;
|
|
111552
|
+
const medians = entries.map((e) => e.injected_score_median).filter((n) => typeof n === "number");
|
|
111553
|
+
const avgScore = medians.length > 0 ? Math.round(medians.reduce((s, n) => s + n, 0) / medians.length * 100) / 100 : null;
|
|
111554
|
+
const collapseTurns = entries.filter((e) => typeof e.injected_own_bank_count === "number" && e.injected_own_bank_count === 0 && typeof e.injected_additional_bank_count === "number" && e.injected_additional_bank_count > 0).length;
|
|
111555
|
+
lines.push(source_default.gray(` last ${total} turn${total === 1 ? "" : "s"}: ` + `avg=${avg ?? "\u2014"} max=${max ?? "\u2014"} ` + `avg_score=${avgScore ?? "\u2014"} ` + `cache_hits=${hits} capped=${cappedTurns}` + (omittedTurns > 0 ? ` directives_omitted_turns=${omittedTurns}` : "") + (collapseTurns > 0 ? source_default.red(` own_bank_collapse_turns=${collapseTurns}`) : "")));
|
|
111556
|
+
for (const e of entries) {
|
|
111557
|
+
const flag = e.cache_hit ? source_default.cyan("CACHE") : e.capped ? source_default.yellow("CAP") : source_default.green("OK");
|
|
111558
|
+
const dem = e.demoted_count && e.demoted_count > 0 ? source_default.dim(` -${e.demoted_count}d`) : "";
|
|
111559
|
+
const omitted = typeof e.directives_omitted === "number" && e.directives_omitted > 0 ? source_default.yellow(` +${e.directives_omitted}omitted`) : "";
|
|
111560
|
+
const ids = e.memory_ids && e.memory_ids.length > 0 ? source_default.dim(` ids=${e.memory_ids.slice(0, 3).join(",")}${e.memory_ids.length > 3 ? `\u2026+${e.memory_ids.length - 3}` : ""}`) : "";
|
|
111561
|
+
const own = e.injected_own_bank_count;
|
|
111562
|
+
const additional = e.injected_additional_bank_count;
|
|
111563
|
+
const composition = typeof own === "number" || typeof additional === "number" ? (() => {
|
|
111564
|
+
const text = ` own/add=${own ?? "\u2014"}/${additional ?? "\u2014"}`;
|
|
111565
|
+
return own === 0 && typeof additional === "number" && additional > 0 ? source_default.red(text) : source_default.dim(text);
|
|
111566
|
+
})() : "";
|
|
111567
|
+
const score = typeof e.injected_score_min === "number" && typeof e.injected_score_median === "number" && typeof e.injected_score_max === "number" ? source_default.dim(` score=${e.injected_score_min.toFixed(2)}/` + `${e.injected_score_median.toFixed(2)}/` + `${e.injected_score_max.toFixed(2)}`) : "";
|
|
111568
|
+
lines.push(` ${source_default.gray(e.ts)} ${flag} ` + `n=${e.result_count ?? "\u2014"}${e.pre_cap_count != null && e.pre_cap_count !== e.result_count ? `/${e.pre_cap_count}` : ""}` + `${dem}${omitted}${score}${composition}${ids}`);
|
|
111569
|
+
}
|
|
111570
|
+
return lines;
|
|
111571
|
+
}
|
|
111572
|
+
function registerMemoryCommand(program3) {
|
|
111573
|
+
const memory = program3.command("memory").description("Hindsight memory operations");
|
|
111574
|
+
memory.command("search <query>").description("Search agent memories via Hindsight").option("-a, --agent <name>", "Search a specific agent's collection").action(withConfigError(async (query, opts) => {
|
|
111575
|
+
const config = getConfig(program3);
|
|
111576
|
+
if (opts.agent) {
|
|
111577
|
+
if (!config.agents[opts.agent]) {
|
|
111578
|
+
console.error(source_default.red(`Agent "${opts.agent}" is not defined in switchroom.yaml`));
|
|
111579
|
+
process.exit(1);
|
|
111580
|
+
}
|
|
111581
|
+
const collection = getCollectionForAgent(opts.agent, config);
|
|
111582
|
+
console.log(source_default.bold(`
|
|
111583
|
+
Search: ${opts.agent} (collection: ${collection})
|
|
111584
|
+
`));
|
|
111585
|
+
console.log(source_default.gray(` $ ${searchMemory(query, collection)}`));
|
|
111586
|
+
console.log();
|
|
111587
|
+
return;
|
|
111588
|
+
}
|
|
111589
|
+
const agentNames = Object.keys(config.agents);
|
|
111590
|
+
console.log(source_default.bold(`
|
|
111591
|
+
Searching all eligible collections:
|
|
111592
|
+
`));
|
|
111593
|
+
for (const name of agentNames) {
|
|
111594
|
+
const collection = getCollectionForAgent(name, config);
|
|
111595
|
+
if (isStrictIsolation(name, config)) {
|
|
111596
|
+
console.log(source_default.gray(` ${name} (${collection}) \u2014 skipped (strict isolation)`));
|
|
111597
|
+
continue;
|
|
111598
|
+
}
|
|
111599
|
+
console.log(source_default.cyan(` ${name} (${collection}):`));
|
|
111600
|
+
console.log(source_default.gray(` $ ${searchMemory(query, collection)}`));
|
|
111601
|
+
}
|
|
111602
|
+
console.log();
|
|
111603
|
+
}));
|
|
111604
|
+
memory.command("stats").description("List agents with their collection names and isolation mode").action(withConfigError(async () => {
|
|
111605
|
+
const config = getConfig(program3);
|
|
111606
|
+
const agentNames = Object.keys(config.agents);
|
|
111607
|
+
if (agentNames.length === 0) {
|
|
111608
|
+
console.log(source_default.yellow("No agents defined in switchroom.yaml"));
|
|
111609
|
+
return;
|
|
111610
|
+
}
|
|
111611
|
+
const headers = ["Agent", "Collection", "Isolation", "Auto-recall"];
|
|
111612
|
+
const widths = [20, 20, 12, 12];
|
|
111613
|
+
const headerLine = headers.map((h, i2) => source_default.bold(h.padEnd(widths[i2]))).join(" ");
|
|
111614
|
+
console.log(`
|
|
111615
|
+
${headerLine}`);
|
|
111616
|
+
for (const name of agentNames) {
|
|
111617
|
+
const collection = getCollectionForAgent(name, config);
|
|
111618
|
+
const isolation = isStrictIsolation(name, config) ? "strict" : "default";
|
|
111619
|
+
const autoRecall = config.agents[name].memory?.auto_recall ?? true;
|
|
111620
|
+
const row = [
|
|
111621
|
+
name.padEnd(widths[0]),
|
|
111622
|
+
collection.padEnd(widths[1]),
|
|
111623
|
+
isolation.padEnd(widths[2]),
|
|
111624
|
+
(autoRecall ? "yes" : "no").padEnd(widths[3])
|
|
111625
|
+
].join(" ");
|
|
111626
|
+
console.log(` ${row}`);
|
|
111627
|
+
}
|
|
111628
|
+
console.log();
|
|
111629
|
+
const profileBanks = collectProfileBanks(config);
|
|
111630
|
+
if (profileBanks.size > 0) {
|
|
111631
|
+
const owners = new Map;
|
|
111632
|
+
for (const [uname, u] of Object.entries(config.users ?? {})) {
|
|
111633
|
+
if (u.profile_bank) {
|
|
111634
|
+
owners.set(u.profile_bank, [
|
|
111635
|
+
...owners.get(u.profile_bank) ?? [],
|
|
111636
|
+
uname
|
|
111637
|
+
]);
|
|
111638
|
+
}
|
|
111639
|
+
}
|
|
111640
|
+
console.log(source_default.bold(` Profile banks (per-user / shared):
|
|
111641
|
+
`));
|
|
111642
|
+
for (const bank of [...profileBanks].sort()) {
|
|
111643
|
+
const who = owners.get(bank);
|
|
111644
|
+
const label = who ? `user: ${who.join(", ")}` : "shared";
|
|
111645
|
+
console.log(` ${bank.padEnd(widths[1])} ${source_default.gray(label)}`);
|
|
111646
|
+
}
|
|
111647
|
+
console.log();
|
|
111648
|
+
}
|
|
111649
|
+
console.log(source_default.bold(` Hindsight CLI commands:
|
|
111650
|
+
`));
|
|
111651
|
+
for (const name of agentNames) {
|
|
111652
|
+
const collection = getCollectionForAgent(name, config);
|
|
111653
|
+
console.log(source_default.gray(` $ ${getMemoryStats(collection)}`));
|
|
111654
|
+
}
|
|
111655
|
+
console.log();
|
|
111656
|
+
}));
|
|
111657
|
+
memory.command("reflect").description("Show cross-agent reflection plan").action(withConfigError(async () => {
|
|
111658
|
+
const config = getConfig(program3);
|
|
111659
|
+
const { eligible, excluded, commands } = reflectAcrossAgents(config);
|
|
111660
|
+
console.log(source_default.bold(`
|
|
111661
|
+
Cross-agent reflection plan
|
|
111662
|
+
`));
|
|
111663
|
+
if (eligible.length > 0) {
|
|
111664
|
+
console.log(source_default.green(" Eligible collections:"));
|
|
111665
|
+
for (const { agent, collection } of eligible) {
|
|
111666
|
+
console.log(source_default.white(` ${agent} -> ${collection}`));
|
|
111667
|
+
}
|
|
111668
|
+
}
|
|
111669
|
+
if (excluded.length > 0) {
|
|
111670
|
+
console.log(source_default.red(`
|
|
111671
|
+
Excluded (strict isolation):`));
|
|
111672
|
+
for (const { agent, collection } of excluded) {
|
|
111673
|
+
console.log(source_default.gray(` ${agent} -> ${collection}`));
|
|
111674
|
+
}
|
|
111675
|
+
}
|
|
111676
|
+
if (commands.length > 0) {
|
|
111677
|
+
console.log(source_default.bold(`
|
|
111678
|
+
Hindsight CLI commands:
|
|
111679
|
+
`));
|
|
111680
|
+
for (const cmd of commands) {
|
|
111681
|
+
console.log(source_default.gray(` $ ${cmd}`));
|
|
111682
|
+
}
|
|
111683
|
+
} else {
|
|
111684
|
+
console.log(source_default.yellow(`
|
|
111685
|
+
No eligible collections for reflection.`));
|
|
111686
|
+
}
|
|
111687
|
+
console.log();
|
|
111688
|
+
}));
|
|
111689
|
+
memory.command("setup").description("Manage the Hindsight Docker container").option("--stop", "Stop and remove the Hindsight container").option("--status", "Show Hindsight container status").option("--recreate", "Pull the latest image and recreate the container (reusing its current port). Used by `switchroom update` to keep the hindsight singleton current. Also required after any `hindsight.llm` model/provider config change \u2014 env is only re-derived on recreate, a plain docker restart is not enough (see docs/operators/hindsight-model-change.md).").option("--tag <version>", "Pin the hindsight image tag to pull + run (e.g. v0.15.18), overriding the " + "default floating `:latest`. Threaded through by `switchroom rollout` so a " + "version-pinned roll recreates hindsight on the SAME tag as the rest of the " + "fleet. Omit for `:latest` (the standalone default).").option("--provider <provider>", "LLM provider (ollama, openai, anthropic)").option("--strict-env", "Refuse the recreate (before the running container is touched) if it would drop " + "env vars that are live on the container but declared in neither `hindsight.env` " + "nor switchroom's managed defaults. Default is to warn loudly and proceed, so a " + "rollout is never wedged half-way with memory down.").option("--gpu", "Force GPU passthrough on for this launch, overriding host autodetection. " + "Use when `~/.switchroom/host-capabilities.json` is wrong or unreadable and " + "you know the host has a working nvidia container toolkit (`docker run --gpus " + "all` hard-fails container create without one). Beats `hindsight.gpu`.").option("--no-gpu", "Force GPU passthrough off for this launch, overriding host autodetection. " + "Also the explicit opt-out for the recreate GPU drop guard.").option("--allow-gpu-drop", "Proceed with a --recreate that would take GPU passthrough away from the " + "running container. Without this the recreate refuses, because that drop " + "moves the embedding model and cross-encoder reranker to CPU on the " + "interactive recall path with no other signal.").action(async (opts) => {
|
|
111690
|
+
if (opts.status) {
|
|
111691
|
+
if (!isDockerAvailable()) {
|
|
111692
|
+
console.log(source_default.red(" Docker is not available."));
|
|
111693
|
+
process.exit(1);
|
|
111694
|
+
}
|
|
111695
|
+
const status = getHindsightStatus();
|
|
111696
|
+
if (status) {
|
|
111697
|
+
console.log(source_default.bold(`
|
|
111698
|
+
Hindsight container status:`));
|
|
111699
|
+
console.log(` ${source_default.cyan("switchroom-hindsight")}: ${status}
|
|
111700
|
+
`);
|
|
111701
|
+
} else {
|
|
111702
|
+
console.log(source_default.yellow(`
|
|
111703
|
+
Hindsight container not found.
|
|
111704
|
+
`));
|
|
111705
|
+
console.log(source_default.gray(" Run 'switchroom memory setup' to start it."));
|
|
111706
|
+
}
|
|
111707
|
+
return;
|
|
111708
|
+
}
|
|
111709
|
+
if (opts.stop) {
|
|
111710
|
+
if (!isDockerAvailable()) {
|
|
111711
|
+
console.log(source_default.red(" Docker is not available."));
|
|
111712
|
+
process.exit(1);
|
|
111713
|
+
}
|
|
111714
|
+
if (!isHindsightContainerExists()) {
|
|
111715
|
+
console.log(source_default.yellow(" No switchroom-hindsight container found."));
|
|
111716
|
+
return;
|
|
111717
|
+
}
|
|
111718
|
+
console.log(source_default.gray(" Stopping switchroom-hindsight..."));
|
|
111719
|
+
stopHindsightRecallPool();
|
|
111720
|
+
stopHindsight();
|
|
111721
|
+
console.log(source_default.green(" Hindsight container stopped and removed."));
|
|
111722
|
+
return;
|
|
111723
|
+
}
|
|
111724
|
+
if (!isDockerAvailable()) {
|
|
111725
|
+
console.log(source_default.red(`
|
|
111726
|
+
Docker is not available.`));
|
|
111727
|
+
console.log(source_default.gray(` Install Docker: https://docs.docker.com/get-docker/
|
|
111728
|
+
`));
|
|
111729
|
+
process.exit(1);
|
|
111730
|
+
}
|
|
111731
|
+
const recreate = opts.recreate === true;
|
|
111732
|
+
let resolvedLlmPromise;
|
|
111733
|
+
const getResolvedLlm = () => {
|
|
111734
|
+
if (!resolvedLlmPromise) {
|
|
111735
|
+
resolvedLlmPromise = resolveHindsightLlmSecrets(getConfig(program3).hindsight?.llm);
|
|
111736
|
+
}
|
|
111737
|
+
return resolvedLlmPromise;
|
|
111738
|
+
};
|
|
111739
|
+
let releasePin;
|
|
111740
|
+
try {
|
|
111741
|
+
releasePin = getConfig(program3).release?.pin ?? undefined;
|
|
111742
|
+
} catch {
|
|
111743
|
+
releasePin = undefined;
|
|
111744
|
+
}
|
|
111745
|
+
const { tag: effectiveTag, reason: tagReason } = resolveMemorySetupTag({
|
|
110824
111746
|
explicitTag: opts.tag,
|
|
110825
111747
|
releasePin
|
|
110826
111748
|
});
|
|
@@ -111366,9 +112288,9 @@ Repairing vector index coverage for ${target}${opts.dryRun ? source_default.yell
|
|
|
111366
112288
|
captured += chunk2.toString();
|
|
111367
112289
|
process.stdout.write(chunk2);
|
|
111368
112290
|
});
|
|
111369
|
-
const childExit = await new Promise((
|
|
111370
|
-
child.on("error", () =>
|
|
111371
|
-
child.on("close", (c) =>
|
|
112291
|
+
const childExit = await new Promise((resolve44) => {
|
|
112292
|
+
child.on("error", () => resolve44(127));
|
|
112293
|
+
child.on("close", (c) => resolve44(c ?? 1));
|
|
111372
112294
|
});
|
|
111373
112295
|
const outcome = classifyRepairOutcome({
|
|
111374
112296
|
childExit,
|
|
@@ -111393,7 +112315,7 @@ Repairing vector index coverage for ${target}${opts.dryRun ? source_default.yell
|
|
|
111393
112315
|
process.exit(1);
|
|
111394
112316
|
})() : Object.keys(config.agents);
|
|
111395
112317
|
for (const name of targets) {
|
|
111396
|
-
const agentDir =
|
|
112318
|
+
const agentDir = join89(agentsDir, name);
|
|
111397
112319
|
const entries = readRecallLog(agentDir, limit);
|
|
111398
112320
|
if (opts.json) {
|
|
111399
112321
|
for (const e of entries) {
|
|
@@ -111586,11 +112508,12 @@ Observation-scope cleanup \u2014 DRY RUN (no writes)
|
|
|
111586
112508
|
}));
|
|
111587
112509
|
registerMemoryRuleCommand(memory, program3);
|
|
111588
112510
|
registerMemoryDirectiveCommand(memory, program3);
|
|
112511
|
+
registerMemoryFlipCommand(memory, program3);
|
|
111589
112512
|
}
|
|
111590
112513
|
|
|
111591
112514
|
// src/cli/web.ts
|
|
111592
112515
|
init_source();
|
|
111593
|
-
import { join as
|
|
112516
|
+
import { join as join101 } from "node:path";
|
|
111594
112517
|
import { homedir as homedir50 } from "node:os";
|
|
111595
112518
|
|
|
111596
112519
|
// src/web/server.ts
|
|
@@ -111598,8 +112521,8 @@ init_merge();
|
|
|
111598
112521
|
init_loader();
|
|
111599
112522
|
init_client();
|
|
111600
112523
|
import {
|
|
111601
|
-
readFileSync as
|
|
111602
|
-
existsSync as
|
|
112524
|
+
readFileSync as readFileSync83,
|
|
112525
|
+
existsSync as existsSync91,
|
|
111603
112526
|
realpathSync as realpathSync7,
|
|
111604
112527
|
mkdirSync as mkdirSync50,
|
|
111605
112528
|
openSync as openSync17,
|
|
@@ -111607,7 +112530,7 @@ import {
|
|
|
111607
112530
|
writeSync as writeSync8,
|
|
111608
112531
|
constants as fsConstants6
|
|
111609
112532
|
} from "node:fs";
|
|
111610
|
-
import { resolve as
|
|
112533
|
+
import { resolve as resolve48, extname, join as join100, relative as relative5, dirname as dirname36 } from "node:path";
|
|
111611
112534
|
import { homedir as homedir49 } from "node:os";
|
|
111612
112535
|
import { timingSafeEqual as timingSafeEqual3, randomBytes as randomBytes15 } from "node:crypto";
|
|
111613
112536
|
|
|
@@ -111616,8 +112539,8 @@ init_lifecycle();
|
|
|
111616
112539
|
init_manager();
|
|
111617
112540
|
init_hindsight2();
|
|
111618
112541
|
import { spawnSync as spawnSync17 } from "node:child_process";
|
|
111619
|
-
import { existsSync as
|
|
111620
|
-
import { resolve as
|
|
112542
|
+
import { existsSync as existsSync87, readFileSync as readFileSync78, statSync as statSync52 } from "node:fs";
|
|
112543
|
+
import { resolve as resolve45, join as join96 } from "node:path";
|
|
111621
112544
|
|
|
111622
112545
|
// src/web/memory-remediation.ts
|
|
111623
112546
|
init_hindsight2();
|
|
@@ -111685,15 +112608,15 @@ async function buildUserProfile(mcpUrl, bank, opts) {
|
|
|
111685
112608
|
init_hindsight();
|
|
111686
112609
|
|
|
111687
112610
|
// src/web/blocked-approvals-read.ts
|
|
111688
|
-
import { readFileSync as
|
|
111689
|
-
import { resolve as
|
|
112611
|
+
import { readFileSync as readFileSync76, readdirSync as readdirSync31 } from "node:fs";
|
|
112612
|
+
import { resolve as resolve44, join as join90 } from "node:path";
|
|
111690
112613
|
import { homedir as homedir42 } from "node:os";
|
|
111691
112614
|
function blockedApprovalsDir(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir42()) {
|
|
111692
|
-
return
|
|
112615
|
+
return resolve44(home2, ".switchroom", "blocked-approvals");
|
|
111693
112616
|
}
|
|
111694
112617
|
var FALLBACK_RECORD_NAME = "blocked-approval.json";
|
|
111695
112618
|
function agentStateRoot(sharedDir) {
|
|
111696
|
-
return
|
|
112619
|
+
return resolve44(sharedDir, "..", "agents");
|
|
111697
112620
|
}
|
|
111698
112621
|
function finiteOrNull(v) {
|
|
111699
112622
|
const n = Number(v);
|
|
@@ -111732,7 +112655,7 @@ function readBlockedApprovalsWithErrors(dir, agentsRoot = agentStateRoot(dir)) {
|
|
|
111732
112655
|
function readSharedRecords(dir) {
|
|
111733
112656
|
let entries;
|
|
111734
112657
|
try {
|
|
111735
|
-
entries =
|
|
112658
|
+
entries = readdirSync31(dir);
|
|
111736
112659
|
} catch (err) {
|
|
111737
112660
|
if (err?.code === "ENOENT") {
|
|
111738
112661
|
return { blocked: [], unreadable: 0 };
|
|
@@ -111746,7 +112669,7 @@ function readSharedRecords(dir) {
|
|
|
111746
112669
|
continue;
|
|
111747
112670
|
let text;
|
|
111748
112671
|
try {
|
|
111749
|
-
text =
|
|
112672
|
+
text = readFileSync76(join90(dir, name), "utf-8");
|
|
111750
112673
|
} catch {
|
|
111751
112674
|
unreadable++;
|
|
111752
112675
|
continue;
|
|
@@ -111765,7 +112688,7 @@ function readSharedRecords(dir) {
|
|
|
111765
112688
|
function readFallbackRecords(agentsRoot, alreadySeen) {
|
|
111766
112689
|
let agents;
|
|
111767
112690
|
try {
|
|
111768
|
-
agents =
|
|
112691
|
+
agents = readdirSync31(agentsRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
111769
112692
|
} catch (err) {
|
|
111770
112693
|
if (err?.code === "ENOENT") {
|
|
111771
112694
|
return { blocked: [], unreadable: 0 };
|
|
@@ -111779,7 +112702,7 @@ function readFallbackRecords(agentsRoot, alreadySeen) {
|
|
|
111779
112702
|
continue;
|
|
111780
112703
|
let text;
|
|
111781
112704
|
try {
|
|
111782
|
-
text =
|
|
112705
|
+
text = readFileSync76(join90(agentsRoot, agent, FALLBACK_RECORD_NAME), "utf-8");
|
|
111783
112706
|
} catch (err) {
|
|
111784
112707
|
if (err?.code !== "ENOENT")
|
|
111785
112708
|
unreadable++;
|
|
@@ -111826,7 +112749,7 @@ init_audit_reader();
|
|
|
111826
112749
|
// src/scheduler/dispatch.ts
|
|
111827
112750
|
init_merge();
|
|
111828
112751
|
init_overlay_loader();
|
|
111829
|
-
import { createHash as
|
|
112752
|
+
import { createHash as createHash16 } from "node:crypto";
|
|
111830
112753
|
function collectScheduleEntries(config) {
|
|
111831
112754
|
const out = [];
|
|
111832
112755
|
const agentNames = Object.keys(config.agents).sort();
|
|
@@ -111846,7 +112769,7 @@ function collectScheduleEntries(config) {
|
|
|
111846
112769
|
cron: entry.cron,
|
|
111847
112770
|
...typeof title === "string" && title.length > 0 ? { name: title } : {},
|
|
111848
112771
|
...entry.prompt !== undefined ? { prompt: entry.prompt } : {},
|
|
111849
|
-
promptKey:
|
|
112772
|
+
promptKey: createHash16("sha256").update(auditMaterial).digest("hex").slice(0, 12),
|
|
111850
112773
|
...entry.topic !== undefined ? { topic: entry.topic } : {},
|
|
111851
112774
|
...entry.kind !== undefined ? { kind: entry.kind } : {},
|
|
111852
112775
|
...entry.model !== undefined ? { model: entry.model } : {},
|
|
@@ -111895,7 +112818,7 @@ import { homedir as homedir45 } from "node:os";
|
|
|
111895
112818
|
// src/web/config-edit-plan.ts
|
|
111896
112819
|
init_schema();
|
|
111897
112820
|
var import_yaml17 = __toESM(require_dist(), 1);
|
|
111898
|
-
import { readFileSync as
|
|
112821
|
+
import { readFileSync as readFileSync77 } from "node:fs";
|
|
111899
112822
|
|
|
111900
112823
|
class ConfigPlanError extends Error {
|
|
111901
112824
|
}
|
|
@@ -111905,7 +112828,7 @@ function composeTransforms(...transforms) {
|
|
|
111905
112828
|
function planConfigEdit(configPath, transform) {
|
|
111906
112829
|
let before;
|
|
111907
112830
|
try {
|
|
111908
|
-
before =
|
|
112831
|
+
before = readFileSync77(configPath, "utf-8");
|
|
111909
112832
|
} catch (err) {
|
|
111910
112833
|
throw new ConfigPlanError(`could not read config: ${err.message}`);
|
|
111911
112834
|
}
|
|
@@ -111940,19 +112863,19 @@ import {
|
|
|
111940
112863
|
} from "node:fs";
|
|
111941
112864
|
import { spawnSync as spawnSync16 } from "node:child_process";
|
|
111942
112865
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
111943
|
-
import { join as
|
|
112866
|
+
import { join as join92 } from "node:path";
|
|
111944
112867
|
|
|
111945
112868
|
class ConfigDiffError extends Error {
|
|
111946
112869
|
}
|
|
111947
112870
|
function generateUnifiedDiff(before, after, name = "switchroom.yaml", gitBin = "git") {
|
|
111948
112871
|
if (before === after)
|
|
111949
112872
|
return "";
|
|
111950
|
-
const dir = mkdtemp(
|
|
112873
|
+
const dir = mkdtemp(join92(tmpdir5(), "switchroom-config-diff-"));
|
|
111951
112874
|
try {
|
|
111952
|
-
mkdirSync47(
|
|
111953
|
-
mkdirSync47(
|
|
111954
|
-
writeFileSync32(
|
|
111955
|
-
writeFileSync32(
|
|
112875
|
+
mkdirSync47(join92(dir, "cur"), { recursive: true });
|
|
112876
|
+
mkdirSync47(join92(dir, "new"), { recursive: true });
|
|
112877
|
+
writeFileSync32(join92(dir, "cur", name), before);
|
|
112878
|
+
writeFileSync32(join92(dir, "new", name), after);
|
|
111956
112879
|
const r = spawnSync16(gitBin, ["diff", "--no-index", "--no-color", "--", `cur/${name}`, `new/${name}`], { cwd: dir, encoding: "utf-8", timeout: 1e4 });
|
|
111957
112880
|
if (r.status === 0)
|
|
111958
112881
|
return "";
|
|
@@ -111984,9 +112907,9 @@ function generateUnifiedDiff(before, after, name = "switchroom.yaml", gitBin = "
|
|
|
111984
112907
|
|
|
111985
112908
|
// src/web/hostd-config-propose.ts
|
|
111986
112909
|
init_client5();
|
|
111987
|
-
import { existsSync as
|
|
112910
|
+
import { existsSync as existsSync85 } from "node:fs";
|
|
111988
112911
|
import { homedir as homedir44 } from "node:os";
|
|
111989
|
-
import { join as
|
|
112912
|
+
import { join as join93 } from "node:path";
|
|
111990
112913
|
var PROPOSE_TIMEOUT_MS = 61 * 60 * 1000;
|
|
111991
112914
|
function resolveHostdOperatorSocket(env2 = process.env) {
|
|
111992
112915
|
const override = env2.SWITCHROOM_HOSTD_OPERATOR_SOCKET;
|
|
@@ -111994,10 +112917,10 @@ function resolveHostdOperatorSocket(env2 = process.env) {
|
|
|
111994
112917
|
return override;
|
|
111995
112918
|
const candidates = [
|
|
111996
112919
|
"/host-home/.switchroom/hostd/operator/sock",
|
|
111997
|
-
|
|
112920
|
+
join93(homedir44(), ".switchroom", "hostd", "operator", "sock")
|
|
111998
112921
|
];
|
|
111999
112922
|
for (const c of candidates) {
|
|
112000
|
-
if (
|
|
112923
|
+
if (existsSync85(c))
|
|
112001
112924
|
return c;
|
|
112002
112925
|
}
|
|
112003
112926
|
return null;
|
|
@@ -112205,7 +113128,7 @@ init_client2();
|
|
|
112205
113128
|
|
|
112206
113129
|
// telegram-plugin/registry/turns-schema.ts
|
|
112207
113130
|
import { chmodSync as chmodSync15 } from "fs";
|
|
112208
|
-
import { join as
|
|
113131
|
+
import { join as join95 } from "path";
|
|
112209
113132
|
|
|
112210
113133
|
// src/util/state-owner.ts
|
|
112211
113134
|
init_atomic();
|
|
@@ -112213,28 +113136,28 @@ import {
|
|
|
112213
113136
|
appendFileSync as appendFileSync4,
|
|
112214
113137
|
closeSync as closeSync16,
|
|
112215
113138
|
constants as constants2,
|
|
112216
|
-
existsSync as
|
|
113139
|
+
existsSync as existsSync86,
|
|
112217
113140
|
fchownSync as fchownSync2,
|
|
112218
113141
|
fstatSync as fstatSync5,
|
|
112219
113142
|
mkdirSync as mkdirSync48,
|
|
112220
113143
|
openSync as openSync16,
|
|
112221
|
-
readdirSync as
|
|
112222
|
-
statSync as
|
|
113144
|
+
readdirSync as readdirSync32,
|
|
113145
|
+
statSync as statSync51,
|
|
112223
113146
|
writeFileSync as writeFileSync33
|
|
112224
113147
|
} from "node:fs";
|
|
112225
|
-
import { dirname as dirname35, join as
|
|
113148
|
+
import { dirname as dirname35, join as join94, relative as relative4, sep as sep6 } from "node:path";
|
|
112226
113149
|
var NOFOLLOW = constants2.O_NOFOLLOW ?? 0;
|
|
112227
113150
|
var ownerCache = new Map;
|
|
112228
113151
|
var loggedFailure = false;
|
|
112229
113152
|
function defaultLog(line) {
|
|
112230
113153
|
process.stderr.write(line);
|
|
112231
113154
|
}
|
|
112232
|
-
function noteFailure(op,
|
|
113155
|
+
function noteFailure(op, path9, err, log) {
|
|
112233
113156
|
if (loggedFailure)
|
|
112234
113157
|
return;
|
|
112235
113158
|
loggedFailure = true;
|
|
112236
113159
|
const code = err.code ?? "";
|
|
112237
|
-
log(`switchroom state-owner: ${op} could not adopt directory ownership for ${
|
|
113160
|
+
log(`switchroom state-owner: ${op} could not adopt directory ownership for ${path9}` + `${code ? ` (${code})` : ""}: ${err.message}. ` + `Continuing; state files may stay owned by the writing uid. ` + `Further ownership warnings this process are suppressed.
|
|
112238
113161
|
`);
|
|
112239
113162
|
}
|
|
112240
113163
|
function isMultiplyLinked(st) {
|
|
@@ -112248,7 +113171,7 @@ function resolveStateOwner(dir) {
|
|
|
112248
113171
|
return cached;
|
|
112249
113172
|
let owner = null;
|
|
112250
113173
|
try {
|
|
112251
|
-
const st =
|
|
113174
|
+
const st = statSync51(dir);
|
|
112252
113175
|
owner = st.uid === 0 && st.gid === 0 ? null : { uid: st.uid, gid: st.gid };
|
|
112253
113176
|
} catch {
|
|
112254
113177
|
owner = null;
|
|
@@ -112256,13 +113179,13 @@ function resolveStateOwner(dir) {
|
|
|
112256
113179
|
ownerCache.set(dir, owner);
|
|
112257
113180
|
return owner;
|
|
112258
113181
|
}
|
|
112259
|
-
function adoptStateOwnership(
|
|
112260
|
-
const target = owner === undefined ? resolveStateOwner(dirname35(
|
|
113182
|
+
function adoptStateOwnership(path9, owner, log = defaultLog) {
|
|
113183
|
+
const target = owner === undefined ? resolveStateOwner(dirname35(path9)) : owner;
|
|
112261
113184
|
if (!target)
|
|
112262
113185
|
return;
|
|
112263
113186
|
let fd = null;
|
|
112264
113187
|
try {
|
|
112265
|
-
fd = openSync16(
|
|
113188
|
+
fd = openSync16(path9, constants2.O_RDONLY | NOFOLLOW);
|
|
112266
113189
|
const st = fstatSync5(fd);
|
|
112267
113190
|
if (st.uid === target.uid && st.gid === target.gid)
|
|
112268
113191
|
return;
|
|
@@ -112271,7 +113194,7 @@ function adoptStateOwnership(path7, owner, log = defaultLog) {
|
|
|
112271
113194
|
fchownSync2(fd, target.uid, target.gid);
|
|
112272
113195
|
} catch (err) {
|
|
112273
113196
|
if (err.code !== "ENOENT") {
|
|
112274
|
-
noteFailure("adopt",
|
|
113197
|
+
noteFailure("adopt", path9, err, log);
|
|
112275
113198
|
}
|
|
112276
113199
|
} finally {
|
|
112277
113200
|
if (fd !== null) {
|
|
@@ -112289,8 +113212,8 @@ function adoptSqliteOwnership(dbPath, log = defaultLog) {
|
|
|
112289
113212
|
adoptStateOwnership(dbPath + suffix, owner, log);
|
|
112290
113213
|
}
|
|
112291
113214
|
}
|
|
112292
|
-
function mkdirStateSync(
|
|
112293
|
-
const first = mkdirSync48(
|
|
113215
|
+
function mkdirStateSync(path9, options = { recursive: true }, log = defaultLog) {
|
|
113216
|
+
const first = mkdirSync48(path9, { ...options, recursive: true });
|
|
112294
113217
|
if (first === undefined)
|
|
112295
113218
|
return;
|
|
112296
113219
|
const owner = resolveStateOwner(dirname35(first));
|
|
@@ -112298,13 +113221,13 @@ function mkdirStateSync(path7, options = { recursive: true }, log = defaultLog)
|
|
|
112298
113221
|
return;
|
|
112299
113222
|
let current = first;
|
|
112300
113223
|
adoptStateOwnership(current, owner, log);
|
|
112301
|
-
const tail = relative4(first,
|
|
113224
|
+
const tail = relative4(first, path9);
|
|
112302
113225
|
if (tail === "" || tail.startsWith(".."))
|
|
112303
113226
|
return;
|
|
112304
113227
|
for (const part of tail.split(sep6)) {
|
|
112305
113228
|
if (part === "")
|
|
112306
113229
|
continue;
|
|
112307
|
-
current =
|
|
113230
|
+
current = join94(current, part);
|
|
112308
113231
|
adoptStateOwnership(current, owner, log);
|
|
112309
113232
|
}
|
|
112310
113233
|
}
|
|
@@ -112381,20 +113304,20 @@ function applySchema(db) {
|
|
|
112381
113304
|
}
|
|
112382
113305
|
function openTurnsDb(agentDir) {
|
|
112383
113306
|
const Database2 = loadDatabaseClass();
|
|
112384
|
-
const dir =
|
|
113307
|
+
const dir = join95(agentDir, "telegram");
|
|
112385
113308
|
mkdirStateSync(dir, { recursive: true, mode: 448 });
|
|
112386
|
-
const
|
|
112387
|
-
const db = new Database2(
|
|
113309
|
+
const path9 = join95(dir, "registry.db");
|
|
113310
|
+
const db = new Database2(path9, { create: true });
|
|
112388
113311
|
applySchema(db);
|
|
112389
113312
|
try {
|
|
112390
|
-
chmodSync15(
|
|
113313
|
+
chmodSync15(path9, 420);
|
|
112391
113314
|
for (const suffix of ["-shm", "-wal"]) {
|
|
112392
113315
|
try {
|
|
112393
|
-
chmodSync15(
|
|
113316
|
+
chmodSync15(path9 + suffix, 420);
|
|
112394
113317
|
} catch {}
|
|
112395
113318
|
}
|
|
112396
113319
|
} catch {}
|
|
112397
|
-
adoptSqliteOwnership(
|
|
113320
|
+
adoptSqliteOwnership(path9);
|
|
112398
113321
|
return db;
|
|
112399
113322
|
}
|
|
112400
113323
|
function mapRow(row) {
|
|
@@ -112596,7 +113519,7 @@ var DASHBOARD_CACHE_TTL = {
|
|
|
112596
113519
|
};
|
|
112597
113520
|
function readContextOccupancy(agentsDir, name) {
|
|
112598
113521
|
try {
|
|
112599
|
-
const raw =
|
|
113522
|
+
const raw = readFileSync78(resolve45(agentsDir, name, "context-occupancy.json"), "utf-8");
|
|
112600
113523
|
const s = JSON.parse(raw);
|
|
112601
113524
|
if (typeof s?.occupancy !== "number")
|
|
112602
113525
|
return null;
|
|
@@ -112607,7 +113530,7 @@ function readContextOccupancy(agentsDir, name) {
|
|
|
112607
113530
|
}
|
|
112608
113531
|
function readLastTurnAt(agentsDir, name) {
|
|
112609
113532
|
try {
|
|
112610
|
-
const db = openTurnsDb(
|
|
113533
|
+
const db = openTurnsDb(resolve45(agentsDir, name));
|
|
112611
113534
|
try {
|
|
112612
113535
|
const turns = listTurnsForAgent(db, { limit: 1 });
|
|
112613
113536
|
return turns.length > 0 ? turns[0].started_at : null;
|
|
@@ -112620,8 +113543,8 @@ function readLastTurnAt(agentsDir, name) {
|
|
|
112620
113543
|
}
|
|
112621
113544
|
function agentBridgeAlive(agentsDir, name, maxAgeMs = 30000, now2 = Date.now()) {
|
|
112622
113545
|
try {
|
|
112623
|
-
const f =
|
|
112624
|
-
return now2 -
|
|
113546
|
+
const f = resolve45(agentsDir, name, "telegram", ".bridge-alive");
|
|
113547
|
+
return now2 - statSync52(f).mtimeMs <= maxAgeMs;
|
|
112625
113548
|
} catch {
|
|
112626
113549
|
return false;
|
|
112627
113550
|
}
|
|
@@ -112719,7 +113642,7 @@ function handleGetTurns(config, sessionId, limit) {
|
|
|
112719
113642
|
const agentName = parsed.agentName;
|
|
112720
113643
|
const hasThread = "threadId" in parsed;
|
|
112721
113644
|
const agentsDir = resolveAgentsDir(config);
|
|
112722
|
-
const agentDir =
|
|
113645
|
+
const agentDir = resolve45(agentsDir, agentName);
|
|
112723
113646
|
const chatId = config.agents?.[agentName]?.channels?.telegram?.chat_id ?? undefined;
|
|
112724
113647
|
const db = openTurnsDb(agentDir);
|
|
112725
113648
|
try {
|
|
@@ -112742,7 +113665,7 @@ function handleListThreadIds(config, agentName) {
|
|
|
112742
113665
|
if (!chatId)
|
|
112743
113666
|
return [];
|
|
112744
113667
|
const agentsDir = resolveAgentsDir(config);
|
|
112745
|
-
const db = openTurnsDb(
|
|
113668
|
+
const db = openTurnsDb(resolve45(agentsDir, agentName));
|
|
112746
113669
|
try {
|
|
112747
113670
|
return listDistinctThreadIds(db, chatId);
|
|
112748
113671
|
} finally {
|
|
@@ -112755,7 +113678,7 @@ function handleListThreadIds(config, agentName) {
|
|
|
112755
113678
|
function handleGetSubagents(config, agentName, status) {
|
|
112756
113679
|
try {
|
|
112757
113680
|
const agentsDir = resolveAgentsDir(config);
|
|
112758
|
-
const agentDir =
|
|
113681
|
+
const agentDir = resolve45(agentsDir, agentName);
|
|
112759
113682
|
const db = openTurnsDb(agentDir);
|
|
112760
113683
|
try {
|
|
112761
113684
|
applySubagentsSchema(db);
|
|
@@ -113012,7 +113935,7 @@ async function handleGetSystemHealth(config, home2) {
|
|
|
113012
113935
|
};
|
|
113013
113936
|
try {
|
|
113014
113937
|
const logPath = defaultAuditLogPath2(home2);
|
|
113015
|
-
if (
|
|
113938
|
+
if (existsSync87(logPath)) {
|
|
113016
113939
|
hostd.auditLogPresent = true;
|
|
113017
113940
|
const raw = readAuditRaw(logPath);
|
|
113018
113941
|
hostd.recent = readAndFilter(raw, {}, 1000).filter((e) => !HOSTD_NOISE_OPS.has(e.op)).slice(-10);
|
|
@@ -113321,7 +114244,7 @@ async function handleGetSchedule(config, deps = {}) {
|
|
|
113321
114244
|
const recentByAgent = {};
|
|
113322
114245
|
const agents = new Set(entries.map((e) => e.agent));
|
|
113323
114246
|
for (const agent of agents) {
|
|
113324
|
-
const rows = readRecentFires(
|
|
114247
|
+
const rows = readRecentFires(resolve45(agentsDir, agent, "scheduler.jsonl"));
|
|
113325
114248
|
if (rows.length > 0)
|
|
113326
114249
|
recentByAgent[agent] = rows.slice(-10);
|
|
113327
114250
|
}
|
|
@@ -113353,11 +114276,11 @@ async function handleGetApprovals() {
|
|
|
113353
114276
|
return { reachable: true, decisions: sorted };
|
|
113354
114277
|
}
|
|
113355
114278
|
function brokerOperatorSocketPath(home2 = process.env.HOME || homedir45()) {
|
|
113356
|
-
return
|
|
114279
|
+
return join96(home2, ".switchroom", "broker-operator", "sock");
|
|
113357
114280
|
}
|
|
113358
114281
|
function resolveBrokerOperatorSocket(home2 = process.env.HOME || homedir45()) {
|
|
113359
114282
|
const p = brokerOperatorSocketPath(home2);
|
|
113360
|
-
return
|
|
114283
|
+
return existsSync87(p) ? p : null;
|
|
113361
114284
|
}
|
|
113362
114285
|
async function handleGetGrants(deps = {}) {
|
|
113363
114286
|
const socket = "socketPath" in deps ? deps.socketPath : resolveBrokerOperatorSocket();
|
|
@@ -113748,8 +114671,8 @@ async function handleGetSummary(deps, now2 = Date.now()) {
|
|
|
113748
114671
|
init_fleet_health_read();
|
|
113749
114672
|
|
|
113750
114673
|
// src/web/webhook-handler.ts
|
|
113751
|
-
import { appendFileSync as appendFileSync5, existsSync as
|
|
113752
|
-
import { join as
|
|
114674
|
+
import { appendFileSync as appendFileSync5, existsSync as existsSync89, mkdirSync as mkdirSync49, readFileSync as readFileSync81, writeFileSync as writeFileSync34 } from "fs";
|
|
114675
|
+
import { join as join98 } from "path";
|
|
113753
114676
|
import { homedir as homedir48 } from "os";
|
|
113754
114677
|
|
|
113755
114678
|
// src/web/webhook-verify.ts
|
|
@@ -113917,7 +114840,7 @@ init_log_rotation();
|
|
|
113917
114840
|
import net4 from "node:net";
|
|
113918
114841
|
function forwardToGateway(socketPath, req, opts = {}) {
|
|
113919
114842
|
const timeoutMs = opts.timeoutMs ?? 5000;
|
|
113920
|
-
return new Promise((
|
|
114843
|
+
return new Promise((resolve47) => {
|
|
113921
114844
|
let settled = false;
|
|
113922
114845
|
let buf = "";
|
|
113923
114846
|
const done = (value) => {
|
|
@@ -113927,7 +114850,7 @@ function forwardToGateway(socketPath, req, opts = {}) {
|
|
|
113927
114850
|
try {
|
|
113928
114851
|
conn.destroy();
|
|
113929
114852
|
} catch {}
|
|
113930
|
-
|
|
114853
|
+
resolve47(value);
|
|
113931
114854
|
};
|
|
113932
114855
|
const conn = net4.createConnection(socketPath);
|
|
113933
114856
|
conn.setEncoding("utf8");
|
|
@@ -113958,20 +114881,20 @@ function forwardToGateway(socketPath, req, opts = {}) {
|
|
|
113958
114881
|
}
|
|
113959
114882
|
|
|
113960
114883
|
// src/web/webhook-edge.ts
|
|
113961
|
-
import { existsSync as
|
|
113962
|
-
import { join as
|
|
114884
|
+
import { existsSync as existsSync88, readFileSync as readFileSync80 } from "fs";
|
|
114885
|
+
import { join as join97 } from "path";
|
|
113963
114886
|
import { homedir as homedir47 } from "os";
|
|
113964
114887
|
import { timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
113965
114888
|
var EDGE_HEADER = "x-switchroom-edge";
|
|
113966
114889
|
function edgeSecretPath() {
|
|
113967
|
-
return
|
|
114890
|
+
return join97(homedir47(), ".switchroom", "webhook-edge-secret");
|
|
113968
114891
|
}
|
|
113969
|
-
function loadEdgeSecret(
|
|
113970
|
-
const p =
|
|
114892
|
+
function loadEdgeSecret(path9) {
|
|
114893
|
+
const p = path9 ?? edgeSecretPath();
|
|
113971
114894
|
try {
|
|
113972
|
-
if (!
|
|
114895
|
+
if (!existsSync88(p))
|
|
113973
114896
|
return null;
|
|
113974
|
-
const raw =
|
|
114897
|
+
const raw = readFileSync80(p, "utf-8").trim();
|
|
113975
114898
|
return raw.length > 0 ? raw : null;
|
|
113976
114899
|
} catch {
|
|
113977
114900
|
return null;
|
|
@@ -114020,17 +114943,17 @@ function jsonReply(status, body, extraHeaders) {
|
|
|
114020
114943
|
}
|
|
114021
114944
|
var DEDUP_MAX = 1000;
|
|
114022
114945
|
var DEDUP_TTL_MS = 24 * 60 * 60 * 1000;
|
|
114023
|
-
function loadDedupFile(
|
|
114946
|
+
function loadDedupFile(path9) {
|
|
114024
114947
|
try {
|
|
114025
|
-
if (!
|
|
114948
|
+
if (!existsSync89(path9))
|
|
114026
114949
|
return {};
|
|
114027
|
-
const raw = JSON.parse(
|
|
114950
|
+
const raw = JSON.parse(readFileSync81(path9, "utf-8"));
|
|
114028
114951
|
return typeof raw.deliveries === "object" && raw.deliveries !== null ? raw.deliveries : {};
|
|
114029
114952
|
} catch {
|
|
114030
114953
|
return {};
|
|
114031
114954
|
}
|
|
114032
114955
|
}
|
|
114033
|
-
function saveDedupFile(
|
|
114956
|
+
function saveDedupFile(path9, deliveries, now2) {
|
|
114034
114957
|
const pruned = {};
|
|
114035
114958
|
for (const [id, ts] of Object.entries(deliveries)) {
|
|
114036
114959
|
if (now2 - ts < DEDUP_TTL_MS)
|
|
@@ -114038,7 +114961,7 @@ function saveDedupFile(path7, deliveries, now2) {
|
|
|
114038
114961
|
}
|
|
114039
114962
|
const sorted = Object.entries(pruned).sort((a, b) => b[1] - a[1]).slice(0, DEDUP_MAX);
|
|
114040
114963
|
const final = Object.fromEntries(sorted);
|
|
114041
|
-
writeFileSync34(
|
|
114964
|
+
writeFileSync34(path9, JSON.stringify({ deliveries: final }), {
|
|
114042
114965
|
mode: 384
|
|
114043
114966
|
});
|
|
114044
114967
|
}
|
|
@@ -114046,8 +114969,8 @@ var agentDedupCache = new Map;
|
|
|
114046
114969
|
function createFileDedupStore(resolveAgentDir2) {
|
|
114047
114970
|
return {
|
|
114048
114971
|
check(agent, deliveryId, now2) {
|
|
114049
|
-
const telegramDir =
|
|
114050
|
-
const filePath =
|
|
114972
|
+
const telegramDir = join98(resolveAgentDir2(agent), "telegram");
|
|
114973
|
+
const filePath = join98(telegramDir, "webhook-dedup.json");
|
|
114051
114974
|
if (!agentDedupCache.has(agent)) {
|
|
114052
114975
|
agentDedupCache.set(agent, loadDedupFile(filePath));
|
|
114053
114976
|
}
|
|
@@ -114099,7 +115022,7 @@ function shouldWriteThrottleIssue(agent, source, now2, windowMap) {
|
|
|
114099
115022
|
return true;
|
|
114100
115023
|
}
|
|
114101
115024
|
function writeThrottleIssue(agent, source, now2, telegramDir, log) {
|
|
114102
|
-
const issuesPath =
|
|
115025
|
+
const issuesPath = join98(telegramDir, "issues.jsonl");
|
|
114103
115026
|
try {
|
|
114104
115027
|
mkdirSync49(telegramDir, { recursive: true });
|
|
114105
115028
|
const record = {
|
|
@@ -114124,7 +115047,7 @@ function writeThrottleIssue(agent, source, now2, telegramDir, log) {
|
|
|
114124
115047
|
async function handleWebhookIngest(args, deps = {}) {
|
|
114125
115048
|
const log = deps.log ?? ((s) => process.stderr.write(s));
|
|
114126
115049
|
const now2 = (deps.now ?? Date.now)();
|
|
114127
|
-
const resolveAgentDir2 = deps.resolveAgentDir ?? ((a) =>
|
|
115050
|
+
const resolveAgentDir2 = deps.resolveAgentDir ?? ((a) => join98(homedir48(), ".switchroom", "agents", a));
|
|
114128
115051
|
const rateLimiter = deps.rateLimiter ?? defaultRateLimiter;
|
|
114129
115052
|
const dedupStore = deps.dedupStore ?? createFileDedupStore(resolveAgentDir2);
|
|
114130
115053
|
if (!args.agentExists) {
|
|
@@ -114191,7 +115114,7 @@ async function handleWebhookIngest(args, deps = {}) {
|
|
|
114191
115114
|
if (retryAfter !== null) {
|
|
114192
115115
|
if (!args.viaGateway) {
|
|
114193
115116
|
const agentDir2 = resolveAgentDir2(args.agent);
|
|
114194
|
-
const telegramDir2 =
|
|
115117
|
+
const telegramDir2 = join98(agentDir2, "telegram");
|
|
114195
115118
|
if (shouldWriteThrottleIssue(args.agent, source, now2)) {
|
|
114196
115119
|
writeThrottleIssue(args.agent, source, now2, telegramDir2, log);
|
|
114197
115120
|
}
|
|
@@ -114211,7 +115134,7 @@ async function handleWebhookIngest(args, deps = {}) {
|
|
|
114211
115134
|
const eventType = source === "github" ? args.headers.get("x-github-event") ?? "unknown" : source === "linear" ? String(payload.type ?? "unknown").toLowerCase() : args.source;
|
|
114212
115135
|
const rendered = source === "github" ? renderGithubEvent(eventType, payload) : source === "linear" ? renderLinearEvent(eventType, payload) : renderGenericEvent(args.source, payload);
|
|
114213
115136
|
if (args.viaGateway) {
|
|
114214
|
-
const socketPath =
|
|
115137
|
+
const socketPath = join98(resolveAgentDir2(args.agent), "telegram", "webhook.sock");
|
|
114215
115138
|
const forward = deps.forwardFn ?? forwardToGateway;
|
|
114216
115139
|
const deliveryId = source === "github" ? args.headers.get("x-github-delivery") ?? undefined : undefined;
|
|
114217
115140
|
let resp;
|
|
@@ -114250,8 +115173,8 @@ async function handleWebhookIngest(args, deps = {}) {
|
|
|
114250
115173
|
return jsonReply(202, { ok: true, recorded: true, ts: resp.ts });
|
|
114251
115174
|
}
|
|
114252
115175
|
const agentDir = resolveAgentDir2(args.agent);
|
|
114253
|
-
const telegramDir =
|
|
114254
|
-
const logPath =
|
|
115176
|
+
const telegramDir = join98(agentDir, "telegram");
|
|
115177
|
+
const logPath = join98(telegramDir, "webhook-events.jsonl");
|
|
114255
115178
|
try {
|
|
114256
115179
|
mkdirSync49(telegramDir, { recursive: true });
|
|
114257
115180
|
rotateWebhookLogIfNeeded(logPath);
|
|
@@ -114280,9 +115203,9 @@ init_shipped_assets();
|
|
|
114280
115203
|
// src/web/hermes-adapter.ts
|
|
114281
115204
|
init_loader();
|
|
114282
115205
|
import { createConnection as createConnection3 } from "node:net";
|
|
114283
|
-
import { createHash as
|
|
114284
|
-
import { join as
|
|
114285
|
-
import { existsSync as
|
|
115206
|
+
import { createHash as createHash17 } from "node:crypto";
|
|
115207
|
+
import { join as join99, resolve as resolve47 } from "node:path";
|
|
115208
|
+
import { existsSync as existsSync90, readFileSync as readFileSync82 } from "node:fs";
|
|
114286
115209
|
|
|
114287
115210
|
// src/agent-scheduler/channel-target.ts
|
|
114288
115211
|
init_merge();
|
|
@@ -114326,8 +115249,8 @@ function agentLiveness(config, agentName) {
|
|
|
114326
115249
|
const agentsDir = resolveAgentsDir(config);
|
|
114327
115250
|
if (agentBridgeAlive(agentsDir, agentName))
|
|
114328
115251
|
return "active";
|
|
114329
|
-
const alive =
|
|
114330
|
-
if (
|
|
115252
|
+
const alive = join99(agentsDir, agentName, "telegram", ".bridge-alive");
|
|
115253
|
+
if (existsSync90(alive))
|
|
114331
115254
|
return "idle";
|
|
114332
115255
|
return "offline";
|
|
114333
115256
|
}
|
|
@@ -114435,11 +115358,11 @@ function resolveAgentChat(config, sessionId, agentsDir) {
|
|
|
114435
115358
|
const threadId = topicId !== undefined ? topicId === null ? undefined : parseInt(topicId, 10) || undefined : channel.threadId;
|
|
114436
115359
|
return { chatId: channel.chatId, threadId };
|
|
114437
115360
|
}
|
|
114438
|
-
const accessPath =
|
|
114439
|
-
if (!
|
|
115361
|
+
const accessPath = resolve47(agentsDir, agentName, "telegram", "access.json");
|
|
115362
|
+
if (!existsSync90(accessPath))
|
|
114440
115363
|
return null;
|
|
114441
115364
|
try {
|
|
114442
|
-
const raw =
|
|
115365
|
+
const raw = readFileSync82(accessPath, "utf-8");
|
|
114443
115366
|
const access = JSON.parse(raw);
|
|
114444
115367
|
const chatId = access.allowFrom?.[0];
|
|
114445
115368
|
if (!chatId)
|
|
@@ -114450,8 +115373,8 @@ function resolveAgentChat(config, sessionId, agentsDir) {
|
|
|
114450
115373
|
}
|
|
114451
115374
|
}
|
|
114452
115375
|
async function injectInbound(agentsDir, agentName, chatId, threadId, text, promptKey) {
|
|
114453
|
-
const socketPath =
|
|
114454
|
-
if (!
|
|
115376
|
+
const socketPath = resolve47(agentsDir, agentName, "telegram", "gateway.sock");
|
|
115377
|
+
if (!existsSync90(socketPath)) {
|
|
114455
115378
|
return { ok: false, error: `agent ${agentName} gateway socket not found \u2014 is it running?` };
|
|
114456
115379
|
}
|
|
114457
115380
|
const ts = Date.now();
|
|
@@ -114498,8 +115421,8 @@ async function injectInbound(agentsDir, agentName, chatId, threadId, text, promp
|
|
|
114498
115421
|
});
|
|
114499
115422
|
}
|
|
114500
115423
|
function readHistoryDb(agentsDir, agentName, limit) {
|
|
114501
|
-
const dbPath =
|
|
114502
|
-
if (!
|
|
115424
|
+
const dbPath = resolve47(agentsDir, agentName, "telegram", "history.db");
|
|
115425
|
+
if (!existsSync90(dbPath))
|
|
114503
115426
|
return null;
|
|
114504
115427
|
try {
|
|
114505
115428
|
let SqliteDatabase;
|
|
@@ -114528,7 +115451,7 @@ function readHistoryDb(agentsDir, agentName, limit) {
|
|
|
114528
115451
|
}
|
|
114529
115452
|
}
|
|
114530
115453
|
function startHistoryPoll(ctx, agentsDir, agentName, sessionId, pollIntervalMs = 2000) {
|
|
114531
|
-
const dbPath =
|
|
115454
|
+
const dbPath = resolve47(agentsDir, agentName, "telegram", "history.db");
|
|
114532
115455
|
let SqliteDatabase = null;
|
|
114533
115456
|
try {
|
|
114534
115457
|
const meta = import.meta;
|
|
@@ -114538,7 +115461,7 @@ function startHistoryPoll(ctx, agentsDir, agentName, sessionId, pollIntervalMs =
|
|
|
114538
115461
|
}
|
|
114539
115462
|
} catch {}
|
|
114540
115463
|
let cursor = 0;
|
|
114541
|
-
if (SqliteDatabase &&
|
|
115464
|
+
if (SqliteDatabase && existsSync90(dbPath)) {
|
|
114542
115465
|
try {
|
|
114543
115466
|
const db = new SqliteDatabase(dbPath, { readonly: true });
|
|
114544
115467
|
try {
|
|
@@ -114550,7 +115473,7 @@ function startHistoryPoll(ctx, agentsDir, agentName, sessionId, pollIntervalMs =
|
|
|
114550
115473
|
} catch {}
|
|
114551
115474
|
}
|
|
114552
115475
|
const timer = setInterval(() => {
|
|
114553
|
-
if (!SqliteDatabase || !
|
|
115476
|
+
if (!SqliteDatabase || !existsSync90(dbPath))
|
|
114554
115477
|
return;
|
|
114555
115478
|
try {
|
|
114556
115479
|
const db = new SqliteDatabase(dbPath, { readonly: true });
|
|
@@ -115213,7 +116136,7 @@ async function onHermesMessage(ctx, raw) {
|
|
|
115213
116136
|
sendResponse(ctx, rpcErr(id, -32603, `Could not resolve chat for ${sessionId} \u2014 ensure the agent has telegram.forum_chat_id or channels.telegram.chat_id configured.`));
|
|
115214
116137
|
break;
|
|
115215
116138
|
}
|
|
115216
|
-
const promptKey =
|
|
116139
|
+
const promptKey = createHash17("sha256").update(`${sessionId}:${Date.now()}:${content}`).digest("hex").slice(0, 12);
|
|
115217
116140
|
sendEvent(ctx, "message.start", sessionId, { prompt_key: promptKey });
|
|
115218
116141
|
pendingPromptKeys(ctx).set(sessionId, promptKey);
|
|
115219
116142
|
const result = await injectInbound(agentsDir, submitAgent, chat.chatId, chat.threadId, content, promptKey);
|
|
@@ -115409,9 +116332,9 @@ function resolveWebToken() {
|
|
|
115409
116332
|
if (fromEnv && fromEnv.length > 0)
|
|
115410
116333
|
return fromEnv;
|
|
115411
116334
|
const home2 = process.env.HOME ?? homedir49();
|
|
115412
|
-
const tokenPath =
|
|
115413
|
-
if (
|
|
115414
|
-
const existing =
|
|
116335
|
+
const tokenPath = join100(home2, ".switchroom", "web-token");
|
|
116336
|
+
if (existsSync91(tokenPath)) {
|
|
116337
|
+
const existing = readFileSync83(tokenPath, "utf8").trim();
|
|
115415
116338
|
if (existing.length > 0)
|
|
115416
116339
|
return existing;
|
|
115417
116340
|
}
|
|
@@ -115428,7 +116351,7 @@ function resolveWebToken() {
|
|
|
115428
116351
|
return token;
|
|
115429
116352
|
} catch (err) {
|
|
115430
116353
|
if (err.code === "EEXIST") {
|
|
115431
|
-
const existing =
|
|
116354
|
+
const existing = readFileSync83(tokenPath, "utf8").trim();
|
|
115432
116355
|
if (existing.length > 0)
|
|
115433
116356
|
return existing;
|
|
115434
116357
|
}
|
|
@@ -115501,14 +116424,14 @@ function checkWsAuth(req, token, server) {
|
|
|
115501
116424
|
return presented !== null && constantTimeEqual(presented, token);
|
|
115502
116425
|
}
|
|
115503
116426
|
function loadWebhookSecrets() {
|
|
115504
|
-
const
|
|
115505
|
-
if (!
|
|
116427
|
+
const path9 = join100(homedir49(), ".switchroom", "webhook-secrets.json");
|
|
116428
|
+
if (!existsSync91(path9))
|
|
115506
116429
|
return {};
|
|
115507
116430
|
try {
|
|
115508
|
-
const parsed = JSON.parse(
|
|
116431
|
+
const parsed = JSON.parse(readFileSync83(path9, "utf-8"));
|
|
115509
116432
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
115510
116433
|
} catch (err) {
|
|
115511
|
-
process.stderr.write(`webhook-ingest: failed to parse ${
|
|
116434
|
+
process.stderr.write(`webhook-ingest: failed to parse ${path9}: ${err.message} \u2014 webhooks will return 401 until fixed
|
|
115512
116435
|
`);
|
|
115513
116436
|
return {};
|
|
115514
116437
|
}
|
|
@@ -115695,11 +116618,11 @@ function resolveUiDir(probe2 = {
|
|
|
115695
116618
|
bundleDir: import.meta.dirname,
|
|
115696
116619
|
execPath: process.execPath
|
|
115697
116620
|
}) {
|
|
115698
|
-
return resolveShippedAsset(WEB_UI_ASSET, probe2).path ??
|
|
116621
|
+
return resolveShippedAsset(WEB_UI_ASSET, probe2).path ?? resolve48(probe2.bundleDir, WEB_UI_ASSET.asset);
|
|
115699
116622
|
}
|
|
115700
116623
|
function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
115701
116624
|
const uiDirRaw = resolveUiDir();
|
|
115702
|
-
const uiDir =
|
|
116625
|
+
const uiDir = existsSync91(uiDirRaw) ? realpathSync7(uiDirRaw) : uiDirRaw;
|
|
115703
116626
|
const token = resolveWebToken();
|
|
115704
116627
|
const freshConfig = () => {
|
|
115705
116628
|
if (!configPath)
|
|
@@ -115999,8 +116922,8 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
|
115999
116922
|
})();
|
|
116000
116923
|
}
|
|
116001
116924
|
const filePath = resolveDashboardFilePath(pathname);
|
|
116002
|
-
const fullPath =
|
|
116003
|
-
if (!
|
|
116925
|
+
const fullPath = join100(uiDir, filePath);
|
|
116926
|
+
if (!existsSync91(fullPath)) {
|
|
116004
116927
|
return new Response("Not Found", { status: 404 });
|
|
116005
116928
|
}
|
|
116006
116929
|
let realFullPath;
|
|
@@ -116010,12 +116933,12 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
|
116010
116933
|
return new Response("Not Found", { status: 404 });
|
|
116011
116934
|
}
|
|
116012
116935
|
const rel = relative5(uiDir, realFullPath);
|
|
116013
|
-
if (rel.startsWith("..") ||
|
|
116936
|
+
if (rel.startsWith("..") || resolve48(uiDir, rel) !== realFullPath) {
|
|
116014
116937
|
return new Response("Forbidden", { status: 403 });
|
|
116015
116938
|
}
|
|
116016
116939
|
const ext = extname(realFullPath);
|
|
116017
116940
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
116018
|
-
const content =
|
|
116941
|
+
const content = readFileSync83(realFullPath);
|
|
116019
116942
|
const headers = { "Content-Type": contentType };
|
|
116020
116943
|
const cacheControl = dashboardCacheControl(ext);
|
|
116021
116944
|
if (cacheControl)
|
|
@@ -116138,13 +117061,13 @@ init_helpers();
|
|
|
116138
117061
|
init_loader();
|
|
116139
117062
|
|
|
116140
117063
|
// src/web/startup-guard.ts
|
|
116141
|
-
import { existsSync as
|
|
117064
|
+
import { existsSync as existsSync92, readFileSync as readFileSync84, writeFileSync as writeFileSync35, mkdirSync as mkdirSync51, statSync as statSync53, unlinkSync as unlinkSync18 } from "node:fs";
|
|
116142
117065
|
import { dirname as dirname37 } from "node:path";
|
|
116143
117066
|
function detectConfigMountFault(configPath, deps = {}) {
|
|
116144
|
-
const
|
|
117067
|
+
const stat2 = deps.stat ?? ((p) => statSync53(p));
|
|
116145
117068
|
let st;
|
|
116146
117069
|
try {
|
|
116147
|
-
st =
|
|
117070
|
+
st = stat2(configPath);
|
|
116148
117071
|
} catch {
|
|
116149
117072
|
return null;
|
|
116150
117073
|
}
|
|
@@ -116174,9 +117097,9 @@ function nextCrashBackoff(prior, now2, options = {}) {
|
|
|
116174
117097
|
}
|
|
116175
117098
|
function readCrashState(statePath) {
|
|
116176
117099
|
try {
|
|
116177
|
-
if (!
|
|
117100
|
+
if (!existsSync92(statePath))
|
|
116178
117101
|
return null;
|
|
116179
|
-
const raw = JSON.parse(
|
|
117102
|
+
const raw = JSON.parse(readFileSync84(statePath, "utf-8"));
|
|
116180
117103
|
if (typeof raw.count !== "number" || typeof raw.lastTs !== "number")
|
|
116181
117104
|
return null;
|
|
116182
117105
|
return { count: raw.count, lastTs: raw.lastTs };
|
|
@@ -116192,7 +117115,7 @@ function writeCrashState(statePath, state) {
|
|
|
116192
117115
|
}
|
|
116193
117116
|
function clearCrashState(statePath) {
|
|
116194
117117
|
try {
|
|
116195
|
-
if (
|
|
117118
|
+
if (existsSync92(statePath))
|
|
116196
117119
|
unlinkSync18(statePath);
|
|
116197
117120
|
} catch {}
|
|
116198
117121
|
}
|
|
@@ -116215,7 +117138,7 @@ async function handleWebStartupFailure(err, configPath, stateFile) {
|
|
|
116215
117138
|
throw err;
|
|
116216
117139
|
}
|
|
116217
117140
|
function webCrashStatePath() {
|
|
116218
|
-
return
|
|
117141
|
+
return join101(homedir50(), ".switchroom", "web", ".crashloop-state.json");
|
|
116219
117142
|
}
|
|
116220
117143
|
function registerWebCommand(program3) {
|
|
116221
117144
|
program3.command("web").description("Start the web dashboard for monitoring agents").option("-p, --port <port>", "Port to listen on", "8080").option("-b, --bind <host>", "Host/IP to bind to (default: 127.0.0.1, localhost-only)", "127.0.0.1").action(withConfigError(async (opts) => {
|
|
@@ -116282,9 +117205,9 @@ init_embedded_examples();
|
|
|
116282
117205
|
init_atomic();
|
|
116283
117206
|
init_loader();
|
|
116284
117207
|
init_scaffold();
|
|
116285
|
-
import { existsSync as
|
|
117208
|
+
import { existsSync as existsSync94, readFileSync as readFileSync85, mkdirSync as mkdirSync52, statSync as statSync54, writeFileSync as writeFileSync36 } from "node:fs";
|
|
116286
117209
|
import { execFileSync as execFileSync29 } from "node:child_process";
|
|
116287
|
-
import { resolve as
|
|
117210
|
+
import { resolve as resolve49, dirname as dirname38 } from "node:path";
|
|
116288
117211
|
init_state();
|
|
116289
117212
|
init_vault();
|
|
116290
117213
|
init_manager();
|
|
@@ -116699,7 +117622,7 @@ ${status} ${source_default.bold(`Step ${num3}:`)} ${title}`);
|
|
|
116699
117622
|
init_source();
|
|
116700
117623
|
init_loader();
|
|
116701
117624
|
init_hindsight();
|
|
116702
|
-
import { existsSync as
|
|
117625
|
+
import { existsSync as existsSync93 } from "node:fs";
|
|
116703
117626
|
var HINDSIGHT_READY_RETRIES = 5;
|
|
116704
117627
|
var HINDSIGHT_READY_INTERVAL_MS = 1000;
|
|
116705
117628
|
async function resolveLiteLLMForHindsight2(config, deps = {}) {
|
|
@@ -116736,7 +117659,7 @@ async function stepMemoryBackend(config, nonInteractive, switchroomConfigPath, d
|
|
|
116736
117659
|
try {
|
|
116737
117660
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
116738
117661
|
const passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
116739
|
-
if (passphrase &&
|
|
117662
|
+
if (passphrase && existsSync93(vaultPath)) {
|
|
116740
117663
|
const { getStringSecret: getStringSecret2 } = await Promise.resolve().then(() => (init_vault(), exports_vault));
|
|
116741
117664
|
const existing = getStringSecret2(passphrase, vaultPath, "hindsight-api-key");
|
|
116742
117665
|
if (existing) {
|
|
@@ -117006,7 +117929,7 @@ async function stepMemoryBackend(config, nonInteractive, switchroomConfigPath, d
|
|
|
117006
117929
|
function writeSwitchroomYaml(configPath, text) {
|
|
117007
117930
|
let mode = 420;
|
|
117008
117931
|
try {
|
|
117009
|
-
mode =
|
|
117932
|
+
mode = statSync54(configPath).mode & 511;
|
|
117010
117933
|
} catch {}
|
|
117011
117934
|
writeConfigFileSync(configPath, text, mode);
|
|
117012
117935
|
}
|
|
@@ -117105,7 +118028,7 @@ async function stepConfigFile(configPath, nonInteractive) {
|
|
|
117105
118028
|
existingConfig = null;
|
|
117106
118029
|
}
|
|
117107
118030
|
}
|
|
117108
|
-
if (existingConfig &&
|
|
118031
|
+
if (existingConfig && existsSync94(existingConfig)) {
|
|
117109
118032
|
if (!nonInteractive) {
|
|
117110
118033
|
const useExisting = await askYesNo(` Found ${source_default.cyan(existingConfig)}. Use it?`, true);
|
|
117111
118034
|
if (!useExisting) {
|
|
@@ -117117,9 +118040,9 @@ async function stepConfigFile(configPath, nonInteractive) {
|
|
|
117117
118040
|
console.log(source_default.green(` ${STEP_DONE} Config loaded`) + source_default.gray(` (${Object.keys(config.agents).length} agents)`));
|
|
117118
118041
|
const hasExplicitTimezone = config.switchroom?.timezone !== undefined || config.defaults?.timezone !== undefined;
|
|
117119
118042
|
if (!hasExplicitTimezone) {
|
|
117120
|
-
await writeDetectedTimezone(
|
|
118043
|
+
await writeDetectedTimezone(resolve49(existingConfig), nonInteractive);
|
|
117121
118044
|
}
|
|
117122
|
-
return { config, configPath:
|
|
118045
|
+
return { config, configPath: resolve49(existingConfig) };
|
|
117123
118046
|
}
|
|
117124
118047
|
return await copyExampleConfig2(nonInteractive);
|
|
117125
118048
|
}
|
|
@@ -117146,12 +118069,12 @@ async function copyExampleConfig2(nonInteractive) {
|
|
|
117146
118069
|
await writeDetectedTimezone(destFile, nonInteractive);
|
|
117147
118070
|
const config = loadConfig(destFile);
|
|
117148
118071
|
console.log(source_default.green(` ${STEP_DONE} Config loaded`) + source_default.gray(` (${Object.keys(config.agents).length} agents)`));
|
|
117149
|
-
return { config, configPath:
|
|
118072
|
+
return { config, configPath: resolve49(destFile) };
|
|
117150
118073
|
}
|
|
117151
118074
|
async function writeDetectedTimezone(destFile, nonInteractive, detect = detectServerTimezone, prompt = ask) {
|
|
117152
118075
|
const detected = detect();
|
|
117153
118076
|
if (detected !== undefined && detected !== "UTC" && isValidTimezone(detected)) {
|
|
117154
|
-
const before2 =
|
|
118077
|
+
const before2 = readFileSync85(destFile, "utf-8");
|
|
117155
118078
|
const after2 = setSwitchroomTimezone(before2, detected);
|
|
117156
118079
|
if (after2 !== before2)
|
|
117157
118080
|
writeSwitchroomYaml(destFile, after2);
|
|
@@ -117172,7 +118095,7 @@ async function writeDetectedTimezone(destFile, nonInteractive, detect = detectSe
|
|
|
117172
118095
|
console.error(source_default.yellow(` \u26a0 "${zone}" is not a valid IANA zone (expected Region/City like ` + '"Australia/Melbourne"). Skipping \u2014 agents will use UTC. ' + "Edit switchroom.timezone in switchroom.yaml by hand."));
|
|
117173
118096
|
return;
|
|
117174
118097
|
}
|
|
117175
|
-
const before =
|
|
118098
|
+
const before = readFileSync85(destFile, "utf-8");
|
|
117176
118099
|
const after = setSwitchroomTimezone(before, zone);
|
|
117177
118100
|
if (after !== before)
|
|
117178
118101
|
writeSwitchroomYaml(destFile, after);
|
|
@@ -117251,7 +118174,7 @@ async function resolveOrPromptToken(rawToken, label, config, nonInteractive) {
|
|
|
117251
118174
|
try {
|
|
117252
118175
|
const { openVault: openVault2 } = await Promise.resolve().then(() => (init_vault(), exports_vault));
|
|
117253
118176
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
117254
|
-
if (
|
|
118177
|
+
if (existsSync94(vaultPath)) {
|
|
117255
118178
|
const secrets = openVault2(passphrase, vaultPath);
|
|
117256
118179
|
const key = rawToken.replace("vault:", "");
|
|
117257
118180
|
const entry = secrets[key];
|
|
@@ -117279,7 +118202,7 @@ async function resolveOrPromptToken(rawToken, label, config, nonInteractive) {
|
|
|
117279
118202
|
async function storeTokenInVault(config, vaultRef, token) {
|
|
117280
118203
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
117281
118204
|
const key = vaultRef.replace("vault:", "");
|
|
117282
|
-
if (!
|
|
118205
|
+
if (!existsSync94(vaultPath)) {
|
|
117283
118206
|
console.log(source_default.gray(" Creating encrypted vault..."));
|
|
117284
118207
|
let passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
117285
118208
|
if (!passphrase) {
|
|
@@ -117562,13 +118485,13 @@ async function stepAutoUnlock(config, switchroomConfigPath, nonInteractive) {
|
|
|
117562
118485
|
return;
|
|
117563
118486
|
}
|
|
117564
118487
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
117565
|
-
if (!
|
|
118488
|
+
if (!existsSync94(vaultPath)) {
|
|
117566
118489
|
console.log(source_default.gray(" Skipping (vault not created yet)."));
|
|
117567
118490
|
return;
|
|
117568
118491
|
}
|
|
117569
118492
|
const credPathRaw = config.vault?.broker?.autoUnlockCredentialPath ?? "~/.switchroom/vault-auto-unlock";
|
|
117570
118493
|
const credPath = resolvePath(credPathRaw);
|
|
117571
|
-
if (config.vault?.broker?.autoUnlock === true &&
|
|
118494
|
+
if (config.vault?.broker?.autoUnlock === true && existsSync94(credPath)) {
|
|
117572
118495
|
console.log(source_default.green(` ${STEP_DONE} Already configured (${credPath})`));
|
|
117573
118496
|
return;
|
|
117574
118497
|
}
|
|
@@ -117646,10 +118569,10 @@ async function stepAutoUnlock(config, switchroomConfigPath, nonInteractive) {
|
|
|
117646
118569
|
}
|
|
117647
118570
|
}
|
|
117648
118571
|
function persistApprovalAuthTelegramId(configPath) {
|
|
117649
|
-
if (!
|
|
118572
|
+
if (!existsSync94(configPath)) {
|
|
117650
118573
|
throw new ConfigError(`Cannot persist vault.broker.approvalAuth: config not found at ${configPath}`);
|
|
117651
118574
|
}
|
|
117652
|
-
const content =
|
|
118575
|
+
const content = readFileSync85(configPath, "utf-8");
|
|
117653
118576
|
const result = insertVaultBrokerApprovalAuth(content, "telegram-id");
|
|
117654
118577
|
if (result.kind === "rewritten") {
|
|
117655
118578
|
writeSwitchroomYaml(configPath, result.content);
|
|
@@ -117657,10 +118580,10 @@ function persistApprovalAuthTelegramId(configPath) {
|
|
|
117657
118580
|
return result.kind;
|
|
117658
118581
|
}
|
|
117659
118582
|
function persistDangerousMode(config, configPath) {
|
|
117660
|
-
if (!
|
|
118583
|
+
if (!existsSync94(configPath)) {
|
|
117661
118584
|
throw new ConfigError(`Cannot persist dangerous_mode: config not found at ${configPath}`);
|
|
117662
118585
|
}
|
|
117663
|
-
let content =
|
|
118586
|
+
let content = readFileSync85(configPath, "utf-8");
|
|
117664
118587
|
const agentNames = Object.keys(config.agents);
|
|
117665
118588
|
for (const name of agentNames) {
|
|
117666
118589
|
const agentPattern = new RegExp(`(^ ${name}:\\s*\\n)`, "m");
|
|
@@ -117700,7 +118623,7 @@ async function stepOnboardingGuidance(config, nonInteractive) {
|
|
|
117700
118623
|
const agentNames = Object.keys(config.agents);
|
|
117701
118624
|
let allAuthenticated = true;
|
|
117702
118625
|
for (const name of agentNames) {
|
|
117703
|
-
const agentDir =
|
|
118626
|
+
const agentDir = resolve49(agentsDir, name);
|
|
117704
118627
|
const status = getAuthStatus(name, agentDir);
|
|
117705
118628
|
if (status.authenticated) {
|
|
117706
118629
|
console.log(` ${source_default.green("OK")} ${source_default.bold(name)}` + source_default.gray(` - authenticated (expires: ${status.timeUntilExpiry ?? "unknown"})`));
|
|
@@ -117796,7 +118719,7 @@ async function stepVerification(config, nonInteractive, deps = {}, expect = {})
|
|
|
117796
118719
|
const findings = [];
|
|
117797
118720
|
let applyPending = false;
|
|
117798
118721
|
if (!nonInteractive && agentNames.length > 0) {
|
|
117799
|
-
const composeExists = (deps.composeFileExists ?? (() =>
|
|
118722
|
+
const composeExists = (deps.composeFileExists ?? (() => existsSync94(composeFilePath())))();
|
|
117800
118723
|
if (!composeExists) {
|
|
117801
118724
|
applyPending = true;
|
|
117802
118725
|
log(source_default.gray(" Not started: `switchroom apply` has not generated the compose file yet."));
|
|
@@ -117893,9 +118816,9 @@ init_source();
|
|
|
117893
118816
|
init_loader();
|
|
117894
118817
|
init_lifecycle();
|
|
117895
118818
|
init_compose_env();
|
|
117896
|
-
import { existsSync as
|
|
118819
|
+
import { existsSync as existsSync98, mkdirSync as mkdirSync56, readFileSync as readFileSync89, realpathSync as realpathSync8, statSync as statSync57, chownSync as chownSync13 } from "node:fs";
|
|
117897
118820
|
import { spawnSync as spawnSync20 } from "node:child_process";
|
|
117898
|
-
import { join as
|
|
118821
|
+
import { join as join102, dirname as dirname42 } from "node:path";
|
|
117899
118822
|
import { homedir as homedir51 } from "node:os";
|
|
117900
118823
|
|
|
117901
118824
|
// src/cli/release-yaml.ts
|
|
@@ -117956,7 +118879,7 @@ init_operator_uid();
|
|
|
117956
118879
|
init_atomic();
|
|
117957
118880
|
|
|
117958
118881
|
// src/cli/preflight-mounts.ts
|
|
117959
|
-
import { statSync as
|
|
118882
|
+
import { statSync as statSync55 } from "node:fs";
|
|
117960
118883
|
function parseHostBindSources(composeText) {
|
|
117961
118884
|
const out = [];
|
|
117962
118885
|
const FILE_HINT = /\.(ya?ml|db|log|toml|json|token|id)$|\/\.vault-token$|machine-id$|localtime$|vault-auto-unlock$|\/webkite$/;
|
|
@@ -117983,7 +118906,7 @@ function parseHostBindSources(composeText) {
|
|
|
117983
118906
|
return out;
|
|
117984
118907
|
}
|
|
117985
118908
|
function validateBindSources(composeText, deps = {}) {
|
|
117986
|
-
const
|
|
118909
|
+
const stat2 = deps.stat ?? ((p) => statSync55(p));
|
|
117987
118910
|
const sources = parseHostBindSources(composeText);
|
|
117988
118911
|
const issues = [];
|
|
117989
118912
|
const seen = new Set;
|
|
@@ -117995,7 +118918,7 @@ function validateBindSources(composeText, deps = {}) {
|
|
|
117995
118918
|
checked++;
|
|
117996
118919
|
let st = null;
|
|
117997
118920
|
try {
|
|
117998
|
-
st =
|
|
118921
|
+
st = stat2(source);
|
|
117999
118922
|
} catch {
|
|
118000
118923
|
st = null;
|
|
118001
118924
|
}
|
|
@@ -118029,18 +118952,18 @@ init_self_update();
|
|
|
118029
118952
|
init_shipped_assets();
|
|
118030
118953
|
|
|
118031
118954
|
// src/cli/self-update-io.ts
|
|
118032
|
-
import { createHash as
|
|
118955
|
+
import { createHash as createHash18 } from "node:crypto";
|
|
118033
118956
|
import {
|
|
118034
118957
|
chmodSync as chmodSync16,
|
|
118035
118958
|
closeSync as closeSync18,
|
|
118036
118959
|
copyFileSync as copyFileSync12,
|
|
118037
118960
|
createWriteStream,
|
|
118038
|
-
existsSync as
|
|
118961
|
+
existsSync as existsSync95,
|
|
118039
118962
|
lstatSync as lstatSync13,
|
|
118040
118963
|
mkdirSync as mkdirSync53,
|
|
118041
118964
|
openSync as openSync18,
|
|
118042
|
-
readdirSync as
|
|
118043
|
-
readFileSync as
|
|
118965
|
+
readdirSync as readdirSync33,
|
|
118966
|
+
readFileSync as readFileSync86,
|
|
118044
118967
|
readSync as readSync6,
|
|
118045
118968
|
renameSync as renameSync23,
|
|
118046
118969
|
rmSync as rmSync18,
|
|
@@ -118079,9 +119002,9 @@ function defaultSelfUpdateIO() {
|
|
|
118079
119002
|
throw new Error(`empty response body for ${url}`);
|
|
118080
119003
|
await pipeline(Readable.fromWeb(res.body), createWriteStream(dest));
|
|
118081
119004
|
},
|
|
118082
|
-
sha256File(
|
|
118083
|
-
const hash2 =
|
|
118084
|
-
const fd = openSync18(
|
|
119005
|
+
sha256File(path9) {
|
|
119006
|
+
const hash2 = createHash18("sha256");
|
|
119007
|
+
const fd = openSync18(path9, "r");
|
|
118085
119008
|
try {
|
|
118086
119009
|
const buf = Buffer.allocUnsafe(1 << 20);
|
|
118087
119010
|
for (;; ) {
|
|
@@ -118095,8 +119018,8 @@ function defaultSelfUpdateIO() {
|
|
|
118095
119018
|
}
|
|
118096
119019
|
return hash2.digest("hex");
|
|
118097
119020
|
},
|
|
118098
|
-
probeBinary(
|
|
118099
|
-
const r = spawnSync18(
|
|
119021
|
+
probeBinary(path9) {
|
|
119022
|
+
const r = spawnSync18(path9, ["--version"], {
|
|
118100
119023
|
encoding: "utf-8",
|
|
118101
119024
|
timeout: 30000,
|
|
118102
119025
|
env: { ...process.env, SWITCHROOM_SELF_UPDATED: "" }
|
|
@@ -118136,24 +119059,24 @@ function defaultSelfUpdateIO() {
|
|
|
118136
119059
|
copyFile(src, dest) {
|
|
118137
119060
|
copyFileSync12(src, dest);
|
|
118138
119061
|
},
|
|
118139
|
-
chmodExec(
|
|
118140
|
-
chmodSync16(
|
|
119062
|
+
chmodExec(path9) {
|
|
119063
|
+
chmodSync16(path9, 493);
|
|
118141
119064
|
},
|
|
118142
119065
|
rename(src, dest) {
|
|
118143
119066
|
renameSync23(src, dest);
|
|
118144
119067
|
},
|
|
118145
|
-
remove(
|
|
118146
|
-
rmSync18(
|
|
119068
|
+
remove(path9) {
|
|
119069
|
+
rmSync18(path9, { force: true });
|
|
118147
119070
|
},
|
|
118148
|
-
exists(
|
|
118149
|
-
return
|
|
119071
|
+
exists(path9) {
|
|
119072
|
+
return existsSync95(path9);
|
|
118150
119073
|
},
|
|
118151
|
-
dirname(
|
|
118152
|
-
return dirname39(
|
|
119074
|
+
dirname(path9) {
|
|
119075
|
+
return dirname39(path9);
|
|
118153
119076
|
},
|
|
118154
|
-
readText(
|
|
119077
|
+
readText(path9) {
|
|
118155
119078
|
try {
|
|
118156
|
-
return
|
|
119079
|
+
return readFileSync86(path9, "utf-8");
|
|
118157
119080
|
} catch {
|
|
118158
119081
|
return null;
|
|
118159
119082
|
}
|
|
@@ -118173,19 +119096,19 @@ function defaultSelfUpdateIO() {
|
|
|
118173
119096
|
symlink(target, linkPath) {
|
|
118174
119097
|
symlinkSync5(target, linkPath);
|
|
118175
119098
|
},
|
|
118176
|
-
isSymlink(
|
|
119099
|
+
isSymlink(path9) {
|
|
118177
119100
|
try {
|
|
118178
|
-
return lstatSync13(
|
|
119101
|
+
return lstatSync13(path9).isSymbolicLink();
|
|
118179
119102
|
} catch {
|
|
118180
119103
|
return false;
|
|
118181
119104
|
}
|
|
118182
119105
|
},
|
|
118183
|
-
removeTree(
|
|
118184
|
-
rmSync18(
|
|
119106
|
+
removeTree(path9) {
|
|
119107
|
+
rmSync18(path9, { recursive: true, force: true });
|
|
118185
119108
|
},
|
|
118186
119109
|
listDir(dir) {
|
|
118187
119110
|
try {
|
|
118188
|
-
return
|
|
119111
|
+
return readdirSync33(dir);
|
|
118189
119112
|
} catch {
|
|
118190
119113
|
return [];
|
|
118191
119114
|
}
|
|
@@ -118200,7 +119123,7 @@ init_install_cron();
|
|
|
118200
119123
|
|
|
118201
119124
|
// src/memory/mm-refresh-cron.ts
|
|
118202
119125
|
import { execFileSync as execFileSync30 } from "node:child_process";
|
|
118203
|
-
import { chownSync as chownSync12, existsSync as
|
|
119126
|
+
import { chownSync as chownSync12, existsSync as existsSync96, mkdirSync as mkdirSync54, readFileSync as readFileSync87, renameSync as renameSync24, statSync as statSync56, writeFileSync as writeFileSync37 } from "node:fs";
|
|
118204
119127
|
import { dirname as dirname40 } from "node:path";
|
|
118205
119128
|
var CRON_PATH3 = "/etc/cron.d/mental-model-refresh";
|
|
118206
119129
|
var CRON_SCHEDULE2 = "27 4 * * *";
|
|
@@ -118217,20 +119140,20 @@ function renderCron3(opts) {
|
|
|
118217
119140
|
`;
|
|
118218
119141
|
}
|
|
118219
119142
|
function installCron3(opts) {
|
|
118220
|
-
const
|
|
119143
|
+
const path9 = opts.path ?? CRON_PATH3;
|
|
118221
119144
|
const content = renderCron3(opts);
|
|
118222
|
-
if (
|
|
119145
|
+
if (existsSync96(path9)) {
|
|
118223
119146
|
try {
|
|
118224
|
-
if (
|
|
118225
|
-
return { status: "unchanged", path:
|
|
119147
|
+
if (readFileSync87(path9, "utf8") === content) {
|
|
119148
|
+
return { status: "unchanged", path: path9, content };
|
|
118226
119149
|
}
|
|
118227
119150
|
} catch {}
|
|
118228
119151
|
}
|
|
118229
|
-
mkdirSync54(dirname40(
|
|
118230
|
-
const tmp = `${
|
|
119152
|
+
mkdirSync54(dirname40(path9), { recursive: true });
|
|
119153
|
+
const tmp = `${path9}.${process.pid}.tmp`;
|
|
118231
119154
|
writeFileSync37(tmp, content, { mode: 420 });
|
|
118232
|
-
renameSync24(tmp,
|
|
118233
|
-
return { status: "installed", path:
|
|
119155
|
+
renameSync24(tmp, path9);
|
|
119156
|
+
return { status: "installed", path: path9, content };
|
|
118234
119157
|
}
|
|
118235
119158
|
var defaultIdResolver3 = (user) => {
|
|
118236
119159
|
const uid = Number(execFileSync30("id", ["-u", user], { encoding: "utf8" }).trim());
|
|
@@ -118241,27 +119164,27 @@ var defaultIdResolver3 = (user) => {
|
|
|
118241
119164
|
return { uid, gid };
|
|
118242
119165
|
};
|
|
118243
119166
|
function ensureLogFile3(opts) {
|
|
118244
|
-
const
|
|
118245
|
-
if (
|
|
118246
|
-
return { status: "unchanged", path:
|
|
119167
|
+
const path9 = opts.path ?? CRON_LOG_PATH3;
|
|
119168
|
+
if (existsSync96(path9))
|
|
119169
|
+
return { status: "unchanged", path: path9 };
|
|
118247
119170
|
const { uid, gid } = (opts.resolveIds ?? defaultIdResolver3)(opts.user);
|
|
118248
|
-
mkdirSync54(dirname40(
|
|
118249
|
-
writeFileSync37(
|
|
119171
|
+
mkdirSync54(dirname40(path9), { recursive: true });
|
|
119172
|
+
writeFileSync37(path9, "", { mode: 420, flag: "a" });
|
|
118250
119173
|
const isRoot = process.getuid?.() === 0;
|
|
118251
119174
|
let alreadyOwned = false;
|
|
118252
119175
|
try {
|
|
118253
|
-
const st =
|
|
119176
|
+
const st = statSync56(path9);
|
|
118254
119177
|
alreadyOwned = st.uid === uid && st.gid === gid;
|
|
118255
119178
|
} catch {}
|
|
118256
119179
|
if (isRoot && !alreadyOwned) {
|
|
118257
119180
|
try {
|
|
118258
|
-
chownSync12(
|
|
119181
|
+
chownSync12(path9, uid, gid);
|
|
118259
119182
|
} catch (e) {
|
|
118260
119183
|
if (e.code !== "EPERM")
|
|
118261
119184
|
throw e;
|
|
118262
119185
|
}
|
|
118263
119186
|
}
|
|
118264
|
-
return { status: "created", path:
|
|
119187
|
+
return { status: "created", path: path9 };
|
|
118265
119188
|
}
|
|
118266
119189
|
function renderLogrotate3(opts) {
|
|
118267
119190
|
return `${opts.logPath} {
|
|
@@ -118277,20 +119200,20 @@ function renderLogrotate3(opts) {
|
|
|
118277
119200
|
`;
|
|
118278
119201
|
}
|
|
118279
119202
|
function installLogrotate3(opts) {
|
|
118280
|
-
const
|
|
119203
|
+
const path9 = opts.path ?? LOGROTATE_PATH3;
|
|
118281
119204
|
const content = renderLogrotate3({ logPath: opts.logPath ?? CRON_LOG_PATH3, user: opts.user });
|
|
118282
|
-
if (
|
|
119205
|
+
if (existsSync96(path9)) {
|
|
118283
119206
|
try {
|
|
118284
|
-
if (
|
|
118285
|
-
return { status: "unchanged", path:
|
|
119207
|
+
if (readFileSync87(path9, "utf8") === content) {
|
|
119208
|
+
return { status: "unchanged", path: path9, content };
|
|
118286
119209
|
}
|
|
118287
119210
|
} catch {}
|
|
118288
119211
|
}
|
|
118289
|
-
mkdirSync54(dirname40(
|
|
118290
|
-
const tmp = `${
|
|
119212
|
+
mkdirSync54(dirname40(path9), { recursive: true });
|
|
119213
|
+
const tmp = `${path9}.${process.pid}.tmp`;
|
|
118291
119214
|
writeFileSync37(tmp, content, { mode: 420 });
|
|
118292
|
-
renameSync24(tmp,
|
|
118293
|
-
return { status: "installed", path:
|
|
119215
|
+
renameSync24(tmp, path9);
|
|
119216
|
+
return { status: "installed", path: path9, content };
|
|
118294
119217
|
}
|
|
118295
119218
|
function parseCronUser3(content) {
|
|
118296
119219
|
for (const line of content.split(`
|
|
@@ -118307,20 +119230,20 @@ function parseCronUser3(content) {
|
|
|
118307
119230
|
return null;
|
|
118308
119231
|
}
|
|
118309
119232
|
function reconcileCron3(opts) {
|
|
118310
|
-
const
|
|
118311
|
-
if (!
|
|
118312
|
-
return { status: "absent", path:
|
|
119233
|
+
const path9 = opts.path ?? CRON_PATH3;
|
|
119234
|
+
if (!existsSync96(path9))
|
|
119235
|
+
return { status: "absent", path: path9 };
|
|
118313
119236
|
let user = opts.fallbackUser;
|
|
118314
119237
|
try {
|
|
118315
|
-
user = parseCronUser3(
|
|
119238
|
+
user = parseCronUser3(readFileSync87(path9, "utf8")) ?? opts.fallbackUser;
|
|
118316
119239
|
} catch {}
|
|
118317
119240
|
if (!user) {
|
|
118318
|
-
throw new Error(`cannot reconcile ${
|
|
119241
|
+
throw new Error(`cannot reconcile ${path9}: no cron-user parseable from the existing fragment and no fallback user available`);
|
|
118319
119242
|
}
|
|
118320
|
-
const r = installCron3({ user, binary: opts.binary, path:
|
|
119243
|
+
const r = installCron3({ user, binary: opts.binary, path: path9 });
|
|
118321
119244
|
return {
|
|
118322
119245
|
status: r.status === "installed" ? "reconciled" : "unchanged",
|
|
118323
|
-
path:
|
|
119246
|
+
path: path9,
|
|
118324
119247
|
content: r.content
|
|
118325
119248
|
};
|
|
118326
119249
|
}
|
|
@@ -118330,7 +119253,7 @@ init_install_cron2();
|
|
|
118330
119253
|
|
|
118331
119254
|
// src/host-cli-self-heal/install-timer.ts
|
|
118332
119255
|
import { spawnSync as spawnSync19 } from "node:child_process";
|
|
118333
|
-
import { existsSync as
|
|
119256
|
+
import { existsSync as existsSync97, mkdirSync as mkdirSync55, readFileSync as readFileSync88, renameSync as renameSync25, writeFileSync as writeFileSync38 } from "node:fs";
|
|
118334
119257
|
import { dirname as dirname41 } from "node:path";
|
|
118335
119258
|
var SERVICE_PATH = "/etc/systemd/system/switchroom-self-heal.service";
|
|
118336
119259
|
var TIMER_PATH = "/etc/systemd/system/switchroom-self-heal.timer";
|
|
@@ -118376,22 +119299,22 @@ function renderTimer() {
|
|
|
118376
119299
|
` + `WantedBy=timers.target
|
|
118377
119300
|
`;
|
|
118378
119301
|
}
|
|
118379
|
-
function writeUnitIdempotent(
|
|
118380
|
-
if (
|
|
119302
|
+
function writeUnitIdempotent(path9, content) {
|
|
119303
|
+
if (existsSync97(path9)) {
|
|
118381
119304
|
try {
|
|
118382
|
-
if (
|
|
119305
|
+
if (readFileSync88(path9, "utf8") === content)
|
|
118383
119306
|
return "unchanged";
|
|
118384
119307
|
} catch {}
|
|
118385
119308
|
}
|
|
118386
|
-
mkdirSync55(dirname41(
|
|
118387
|
-
const tmp = `${
|
|
119309
|
+
mkdirSync55(dirname41(path9), { recursive: true });
|
|
119310
|
+
const tmp = `${path9}.${process.pid}.tmp`;
|
|
118388
119311
|
writeFileSync38(tmp, content, { mode: 420 });
|
|
118389
|
-
renameSync25(tmp,
|
|
119312
|
+
renameSync25(tmp, path9);
|
|
118390
119313
|
return "written";
|
|
118391
119314
|
}
|
|
118392
119315
|
function homeFromPasswd2(user) {
|
|
118393
119316
|
try {
|
|
118394
|
-
for (const line of
|
|
119317
|
+
for (const line of readFileSync88("/etc/passwd", "utf8").split(`
|
|
118395
119318
|
`)) {
|
|
118396
119319
|
const f = line.split(":");
|
|
118397
119320
|
if (f[0] === user && f[5])
|
|
@@ -118408,7 +119331,7 @@ function resolveOperatorUser(env2) {
|
|
|
118408
119331
|
}
|
|
118409
119332
|
function installSelfHealTimer(deps = {}) {
|
|
118410
119333
|
const env2 = deps.env ?? process.env;
|
|
118411
|
-
const systemdBooted = deps.systemdBooted ?? (() =>
|
|
119334
|
+
const systemdBooted = deps.systemdBooted ?? (() => existsSync97(SYSTEMD_MARKER));
|
|
118412
119335
|
const geteuid = deps.geteuid ?? (() => typeof process.geteuid === "function" ? process.geteuid() : undefined);
|
|
118413
119336
|
const homeForUser = deps.homeForUser ?? homeFromPasswd2;
|
|
118414
119337
|
const servicePath = deps.servicePath ?? SERVICE_PATH;
|
|
@@ -118469,22 +119392,22 @@ function installSelfHealTimer(deps = {}) {
|
|
|
118469
119392
|
// src/cli/update.ts
|
|
118470
119393
|
function defaultPersistPin(configPath) {
|
|
118471
119394
|
return (pin) => {
|
|
118472
|
-
const
|
|
118473
|
-
const before =
|
|
119395
|
+
const path9 = configPath ?? findConfigFile();
|
|
119396
|
+
const before = readFileSync89(path9, "utf8");
|
|
118474
119397
|
const after = setReleasePinInConfig(before, pin);
|
|
118475
119398
|
if (after === before)
|
|
118476
119399
|
return;
|
|
118477
|
-
writeConfigFileSync(
|
|
119400
|
+
writeConfigFileSync(path9, after, statSync57(path9).mode & 511);
|
|
118478
119401
|
try {
|
|
118479
119402
|
if (typeof process.geteuid === "function" && process.geteuid() === 0) {
|
|
118480
119403
|
const uid = resolveOperatorUid();
|
|
118481
119404
|
if (uid !== undefined)
|
|
118482
|
-
chownSync13(
|
|
119405
|
+
chownSync13(path9, uid, uid);
|
|
118483
119406
|
}
|
|
118484
119407
|
} catch {}
|
|
118485
119408
|
};
|
|
118486
119409
|
}
|
|
118487
|
-
var DEFAULT_COMPOSE_PATH2 =
|
|
119410
|
+
var DEFAULT_COMPOSE_PATH2 = join102(homedir51(), ".switchroom", "compose", "docker-compose.yml");
|
|
118488
119411
|
var PACKAGED_INSTALL_KINDS = new Set([
|
|
118489
119412
|
"static-binary",
|
|
118490
119413
|
"npm-global",
|
|
@@ -118493,9 +119416,9 @@ var PACKAGED_INSTALL_KINDS = new Set([
|
|
|
118493
119416
|
function runningFromSwitchroomCheckout(scriptPath) {
|
|
118494
119417
|
let dir = dirname42(scriptPath);
|
|
118495
119418
|
for (let i2 = 0;i2 < 12; i2++) {
|
|
118496
|
-
if (
|
|
119419
|
+
if (existsSync98(join102(dir, ".git"))) {
|
|
118497
119420
|
try {
|
|
118498
|
-
const pkg = JSON.parse(
|
|
119421
|
+
const pkg = JSON.parse(readFileSync89(join102(dir, "package.json"), "utf-8"));
|
|
118499
119422
|
if (pkg.name === "switchroom")
|
|
118500
119423
|
return true;
|
|
118501
119424
|
} catch {}
|
|
@@ -118562,7 +119485,7 @@ function planUpdate(opts) {
|
|
|
118562
119485
|
bundleDir: import.meta.dirname,
|
|
118563
119486
|
execPath: process.execPath,
|
|
118564
119487
|
scriptPath,
|
|
118565
|
-
inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" ||
|
|
119488
|
+
inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" || existsSync98("/.dockerenv")
|
|
118566
119489
|
});
|
|
118567
119490
|
if (detection.kind !== "static-binary") {
|
|
118568
119491
|
say(source_default.gray(` host CLI not self-updated: ${detection.reason}
|
|
@@ -118645,7 +119568,7 @@ function planUpdate(opts) {
|
|
|
118645
119568
|
steps.push({
|
|
118646
119569
|
name: "pull-images",
|
|
118647
119570
|
description: "Pull broker / kernel / agent images from GHCR",
|
|
118648
|
-
skipReason: opts.skipImages ? "--skip-images flag set" : !
|
|
119571
|
+
skipReason: opts.skipImages ? "--skip-images flag set" : !existsSync98(composePath) ? `compose file not found at ${composePath} (run \`switchroom apply --compose-only\` first)` : undefined,
|
|
118649
119572
|
run: () => {
|
|
118650
119573
|
const r = runner("docker", [
|
|
118651
119574
|
"compose",
|
|
@@ -118902,13 +119825,13 @@ function planUpdate(opts) {
|
|
|
118902
119825
|
bundleDir: import.meta.dirname,
|
|
118903
119826
|
execPath: process.execPath
|
|
118904
119827
|
});
|
|
118905
|
-
const dest =
|
|
119828
|
+
const dest = join102(homedir51(), ".switchroom", "skills", "_bundled");
|
|
118906
119829
|
if (resolution.path === null) {
|
|
118907
119830
|
const detection = detectInstallKind(opts.installProbe ?? {
|
|
118908
119831
|
bundleDir: import.meta.dirname,
|
|
118909
119832
|
execPath: process.execPath,
|
|
118910
119833
|
scriptPath,
|
|
118911
|
-
inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" ||
|
|
119834
|
+
inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" || existsSync98("/.dockerenv")
|
|
118912
119835
|
});
|
|
118913
119836
|
const detail = `no shipped skills/ payload found for this install ` + `(${detection.kind}; ${describeShippedAssetSearch(resolution)})`;
|
|
118914
119837
|
if (PACKAGED_INSTALL_KINDS.has(detection.kind)) {
|
|
@@ -118947,11 +119870,11 @@ function planUpdate(opts) {
|
|
|
118947
119870
|
run: () => {
|
|
118948
119871
|
if (opts.syncBundledSkillsFn)
|
|
118949
119872
|
return;
|
|
118950
|
-
const dest =
|
|
118951
|
-
if (!
|
|
119873
|
+
const dest = join102(homedir51(), ".switchroom", "skills", "_bundled");
|
|
119874
|
+
if (!existsSync98(dest)) {
|
|
118952
119875
|
return;
|
|
118953
119876
|
}
|
|
118954
|
-
const missing = getBuiltinDefaultSkillEntries().map((e) => e.key).filter((key) => !
|
|
119877
|
+
const missing = getBuiltinDefaultSkillEntries().map((e) => e.key).filter((key) => !existsSync98(join102(dest, key)));
|
|
118955
119878
|
if (missing.length > 0) {
|
|
118956
119879
|
throw new Error(`verify-bundled-skills: builtin default skill(s) missing from the pool after sync: ` + `${missing.join(", ")}. These ship in the CLI package and must exist in ${dest}. ` + `This is a broken sync or a packaging regression \u2014 the pool is not converged.`);
|
|
118957
119880
|
}
|
|
@@ -118986,7 +119909,7 @@ function planUpdate(opts) {
|
|
|
118986
119909
|
description: "docker compose up -d --remove-orphans (recreates services with new images / compose)",
|
|
118987
119910
|
run: () => {
|
|
118988
119911
|
try {
|
|
118989
|
-
const composeText =
|
|
119912
|
+
const composeText = readFileSync89(composePath, "utf8");
|
|
118990
119913
|
const pf = validateBindSources(composeText);
|
|
118991
119914
|
if (!pf.ok)
|
|
118992
119915
|
throw new Error(formatPreflightError(pf));
|
|
@@ -119061,14 +119984,14 @@ function defaultStatusProbe(composePath) {
|
|
|
119061
119984
|
} catch {}
|
|
119062
119985
|
if (scriptPath) {
|
|
119063
119986
|
try {
|
|
119064
|
-
cliBuiltAt = new Date(
|
|
119987
|
+
cliBuiltAt = new Date(statSync57(scriptPath).mtimeMs).toISOString();
|
|
119065
119988
|
} catch {}
|
|
119066
119989
|
let dir = dirname42(scriptPath);
|
|
119067
119990
|
for (let i2 = 0;i2 < 8; i2++) {
|
|
119068
|
-
const pkgPath =
|
|
119069
|
-
if (
|
|
119991
|
+
const pkgPath = join102(dir, "package.json");
|
|
119992
|
+
if (existsSync98(pkgPath)) {
|
|
119070
119993
|
try {
|
|
119071
|
-
const pkg = JSON.parse(
|
|
119994
|
+
const pkg = JSON.parse(readFileSync89(pkgPath, "utf-8"));
|
|
119072
119995
|
if (typeof pkg.version === "string")
|
|
119073
119996
|
cliVersion = pkg.version;
|
|
119074
119997
|
} catch (err) {
|
|
@@ -119089,7 +120012,7 @@ function defaultStatusProbe(composePath) {
|
|
|
119089
120012
|
warnings.push("could not resolve CLI version (no package.json found above the resolved script path)");
|
|
119090
120013
|
}
|
|
119091
120014
|
const services = [];
|
|
119092
|
-
if (!
|
|
120015
|
+
if (!existsSync98(composePath)) {
|
|
119093
120016
|
warnings.push(`compose file not found at ${composePath}; service status unknown`);
|
|
119094
120017
|
return { cliVersion, cliBuiltAt, services, warnings };
|
|
119095
120018
|
}
|
|
@@ -119352,22 +120275,22 @@ function registerUpdateCommand(program3) {
|
|
|
119352
120275
|
// src/cli/rollout.ts
|
|
119353
120276
|
init_helpers();
|
|
119354
120277
|
import { spawnSync as spawnSync22 } from "node:child_process";
|
|
119355
|
-
import { readFileSync as
|
|
120278
|
+
import { readFileSync as readFileSync91, chownSync as chownSync14, statSync as statSync60 } from "node:fs";
|
|
119356
120279
|
import { homedir as homedir53 } from "node:os";
|
|
119357
120280
|
|
|
119358
120281
|
// src/cli/rollout-pin-journal.ts
|
|
119359
120282
|
import {
|
|
119360
|
-
existsSync as
|
|
119361
|
-
readFileSync as
|
|
120283
|
+
existsSync as existsSync99,
|
|
120284
|
+
readFileSync as readFileSync90,
|
|
119362
120285
|
writeFileSync as writeFileSync39,
|
|
119363
120286
|
renameSync as renameSync26,
|
|
119364
120287
|
unlinkSync as unlinkSync19,
|
|
119365
|
-
statSync as
|
|
120288
|
+
statSync as statSync58,
|
|
119366
120289
|
mkdirSync as mkdirSync57
|
|
119367
120290
|
} from "node:fs";
|
|
119368
120291
|
import { homedir as homedir52 } from "node:os";
|
|
119369
|
-
import { createHash as
|
|
119370
|
-
import { join as
|
|
120292
|
+
import { createHash as createHash19 } from "node:crypto";
|
|
120293
|
+
import { join as join103, basename as basename9, resolve as resolve51, dirname as dirname43 } from "node:path";
|
|
119371
120294
|
init_flock();
|
|
119372
120295
|
var PIN_JOURNAL_MAX_AGE_MS = 15 * 60 * 1000;
|
|
119373
120296
|
var STATE_DIR_NAME = ".switchroom";
|
|
@@ -119376,16 +120299,16 @@ function pinJournalDir(configPath) {
|
|
|
119376
120299
|
if (override && override.trim().length > 0)
|
|
119377
120300
|
return override.trim();
|
|
119378
120301
|
if (configPath) {
|
|
119379
|
-
const dir = dirname43(
|
|
120302
|
+
const dir = dirname43(resolve51(configPath));
|
|
119380
120303
|
if (basename9(dir) === STATE_DIR_NAME)
|
|
119381
120304
|
return dir;
|
|
119382
120305
|
}
|
|
119383
|
-
return
|
|
120306
|
+
return join103(homedir52(), STATE_DIR_NAME);
|
|
119384
120307
|
}
|
|
119385
120308
|
function pinJournalPath(configPath) {
|
|
119386
|
-
const abs =
|
|
119387
|
-
const key =
|
|
119388
|
-
return
|
|
120309
|
+
const abs = resolve51(configPath);
|
|
120310
|
+
const key = createHash19("sha256").update(abs).digest("hex").slice(0, 12);
|
|
120311
|
+
return join103(pinJournalDir(abs), `.rollout-pin-journal.${basename9(abs)}.${key}.json`);
|
|
119389
120312
|
}
|
|
119390
120313
|
function isPidAlive(pid) {
|
|
119391
120314
|
if (!Number.isInteger(pid) || pid <= 0)
|
|
@@ -119419,9 +120342,9 @@ function readPinJournal(configPath, warn = (m) => process.stderr.write(m)) {
|
|
|
119419
120342
|
const p = pinJournalPath(configPath);
|
|
119420
120343
|
let raw;
|
|
119421
120344
|
try {
|
|
119422
|
-
raw =
|
|
120345
|
+
raw = readFileSync90(p, "utf8");
|
|
119423
120346
|
} catch (e) {
|
|
119424
|
-
if (
|
|
120347
|
+
if (existsSync99(p)) {
|
|
119425
120348
|
warn(`\u26a0\ufe0f rollout pin journal: ${p} exists but could not be read ` + `(${e.message}). A provisional \`release.pin\` may be ` + `uncommitted \u2014 verify it host-side before the next reconcile.
|
|
119426
120349
|
`);
|
|
119427
120350
|
}
|
|
@@ -119472,7 +120395,7 @@ function beginPinPersist(configPath, pin, opts = {}) {
|
|
|
119472
120395
|
warn(`\u26a0\ufe0f rollout pin journal: overwriting an ABANDONED journal at ${p} ` + `(pid ${existing.pid} recorded ${existing.at}, provisional pin ` + `${existing.pin}). Its roll never committed or reverted; \`release.pin\` ` + `in ${configPath} may still name that unproven build \u2014 verify it.
|
|
119473
120396
|
`);
|
|
119474
120397
|
}
|
|
119475
|
-
const priorPin = getReleasePinFromConfig(
|
|
120398
|
+
const priorPin = getReleasePinFromConfig(readFileSync90(configPath, "utf8"));
|
|
119476
120399
|
const journal = {
|
|
119477
120400
|
v: 1,
|
|
119478
120401
|
configPath,
|
|
@@ -119493,7 +120416,7 @@ function commitPinPersist(configPath) {
|
|
|
119493
120416
|
unlinkSync19(p);
|
|
119494
120417
|
return null;
|
|
119495
120418
|
} catch (e) {
|
|
119496
|
-
if (!
|
|
120419
|
+
if (!existsSync99(p))
|
|
119497
120420
|
return null;
|
|
119498
120421
|
return `rollout pin journal: FAILED to clear ${p} after a SUCCESSFUL roll ` + `(${e.message}). Delete it host-side \u2014 while it exists, ` + `recovery may revert a proven \`release.pin\`.`;
|
|
119499
120422
|
}
|
|
@@ -119515,11 +120438,11 @@ function rollbackPinPersist(configPath, opts = {}) {
|
|
|
119515
120438
|
}
|
|
119516
120439
|
}
|
|
119517
120440
|
try {
|
|
119518
|
-
const current =
|
|
120441
|
+
const current = readFileSync90(configPath, "utf8");
|
|
119519
120442
|
const next = journal.priorPin ? setReleasePinInConfig(current, journal.priorPin) : deleteReleasePinInConfig(current);
|
|
119520
120443
|
let mode = 384;
|
|
119521
120444
|
try {
|
|
119522
|
-
mode =
|
|
120445
|
+
mode = statSync58(configPath).mode & 511;
|
|
119523
120446
|
} catch {}
|
|
119524
120447
|
if (opts.writeConfig) {
|
|
119525
120448
|
opts.writeConfig(configPath, next, mode);
|
|
@@ -119569,8 +120492,8 @@ init_host_cli_stamp();
|
|
|
119569
120492
|
|
|
119570
120493
|
// src/cli/host-cli-upgrade.ts
|
|
119571
120494
|
init_self_update();
|
|
119572
|
-
import { lchownSync, lstatSync as lstatSync14, readdirSync as
|
|
119573
|
-
import { basename as basename10, dirname as dirname44, join as
|
|
120495
|
+
import { lchownSync, lstatSync as lstatSync14, readdirSync as readdirSync34, statSync as statSync59 } from "node:fs";
|
|
120496
|
+
import { basename as basename10, dirname as dirname44, join as join104 } from "node:path";
|
|
119574
120497
|
init_shipped_assets();
|
|
119575
120498
|
var HOST_CLI_UPGRADE_SENTINEL = "SWITCHROOM_HOST_CLI_UPGRADE:";
|
|
119576
120499
|
function encodeHostCliUpgradeResult(r) {
|
|
@@ -119606,7 +120529,7 @@ function defaultIo() {
|
|
|
119606
120529
|
return {
|
|
119607
120530
|
isFile: (p) => {
|
|
119608
120531
|
try {
|
|
119609
|
-
return
|
|
120532
|
+
return statSync59(p).isFile();
|
|
119610
120533
|
} catch {
|
|
119611
120534
|
return false;
|
|
119612
120535
|
}
|
|
@@ -119636,7 +120559,7 @@ function defaultIo() {
|
|
|
119636
120559
|
chown: (p, uid, gid) => lchownSync(p, uid, gid),
|
|
119637
120560
|
list: (d) => {
|
|
119638
120561
|
try {
|
|
119639
|
-
return
|
|
120562
|
+
return readdirSync34(d);
|
|
119640
120563
|
} catch {
|
|
119641
120564
|
return [];
|
|
119642
120565
|
}
|
|
@@ -119667,7 +120590,7 @@ function handBackOwnership(paths, owner, io) {
|
|
|
119667
120590
|
if (kind !== "dir")
|
|
119668
120591
|
continue;
|
|
119669
120592
|
for (const child of io.list(p))
|
|
119670
|
-
stack.push(
|
|
120593
|
+
stack.push(join104(p, child));
|
|
119671
120594
|
}
|
|
119672
120595
|
return failures;
|
|
119673
120596
|
}
|
|
@@ -119706,7 +120629,7 @@ async function runHostCliUpgrade(opts, io = defaultIo(), log = () => {}) {
|
|
|
119706
120629
|
const installRoot = payloadInstallRoot(binary);
|
|
119707
120630
|
const failures = handBackOwnership([
|
|
119708
120631
|
binary,
|
|
119709
|
-
|
|
120632
|
+
join104(dirname44(binary), ".switchroom-versions"),
|
|
119710
120633
|
dirname44(installRoot),
|
|
119711
120634
|
installRoot,
|
|
119712
120635
|
payloadVersionDir(installRoot, pin)
|
|
@@ -119802,21 +120725,21 @@ function planHostCliHeal(opts) {
|
|
|
119802
120725
|
reason: `the host CLI is a ${stamp.installKind} install \u2014 only a static-binary ` + `install can be replaced by swapping one file, so an operator must run ` + `the upgrade host-side`
|
|
119803
120726
|
};
|
|
119804
120727
|
}
|
|
119805
|
-
const
|
|
119806
|
-
if (!
|
|
119807
|
-
return { action: "skip", reason: `recorded host CLI path "${
|
|
120728
|
+
const path9 = stamp.path;
|
|
120729
|
+
if (!path9.startsWith("/") || path9.includes("/..")) {
|
|
120730
|
+
return { action: "skip", reason: `recorded host CLI path "${path9}" is not a plain absolute path` };
|
|
119808
120731
|
}
|
|
119809
|
-
const installDir =
|
|
119810
|
-
const name =
|
|
120732
|
+
const installDir = path9.slice(0, path9.lastIndexOf("/"));
|
|
120733
|
+
const name = path9.slice(path9.lastIndexOf("/") + 1);
|
|
119811
120734
|
if (name !== "switchroom") {
|
|
119812
|
-
return { action: "skip", reason: `recorded host CLI path "${
|
|
120735
|
+
return { action: "skip", reason: `recorded host CLI path "${path9}" is not named \`switchroom\`` };
|
|
119813
120736
|
}
|
|
119814
120737
|
const prefixHostPath = installDir.slice(0, installDir.lastIndexOf("/"));
|
|
119815
120738
|
const dirName = installDir.slice(installDir.lastIndexOf("/") + 1);
|
|
119816
120739
|
if (prefixHostPath.length === 0 || dirName.length === 0) {
|
|
119817
120740
|
return {
|
|
119818
120741
|
action: "skip",
|
|
119819
|
-
reason: `recorded host CLI path "${
|
|
120742
|
+
reason: `recorded host CLI path "${path9}" has no install prefix to bind \u2014 ` + `refusing to mount the host root`
|
|
119820
120743
|
};
|
|
119821
120744
|
}
|
|
119822
120745
|
return {
|
|
@@ -120501,13 +121424,13 @@ function createRolloutDeps(params) {
|
|
|
120501
121424
|
}
|
|
120502
121425
|
return { ok: r.status === 0, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
|
|
120503
121426
|
};
|
|
120504
|
-
const writeConfigPreservingOwnership = (
|
|
120505
|
-
writeConfigFileSync(
|
|
121427
|
+
const writeConfigPreservingOwnership = (path9, text, mode) => {
|
|
121428
|
+
writeConfigFileSync(path9, text, mode);
|
|
120506
121429
|
try {
|
|
120507
121430
|
if (typeof process.geteuid === "function" && process.geteuid() === 0) {
|
|
120508
121431
|
const uid = resolveOperatorUid();
|
|
120509
121432
|
if (uid !== undefined) {
|
|
120510
|
-
chownSync14(
|
|
121433
|
+
chownSync14(path9, uid, uid);
|
|
120511
121434
|
} else if (hostdCtx) {
|
|
120512
121435
|
warn("\u26a0\ufe0f rollout (hostd path): could not resolve operator uid; " + "skipping config chown-back to avoid leaving switchroom.yaml " + "root-owned. Verify ownership host-side if other yaml verbs " + `EACCES.
|
|
120513
121436
|
`);
|
|
@@ -120566,12 +121489,12 @@ function createRolloutDeps(params) {
|
|
|
120566
121489
|
return stamp ? { hostCli: stamp } : {};
|
|
120567
121490
|
})(),
|
|
120568
121491
|
persistPin: (pin) => {
|
|
120569
|
-
const before =
|
|
121492
|
+
const before = readFileSync91(configPath, "utf8");
|
|
120570
121493
|
const after = setReleasePinInConfig(before, pin);
|
|
120571
121494
|
if (after === before)
|
|
120572
121495
|
return false;
|
|
120573
121496
|
beginPinPersist(configPath, pin);
|
|
120574
|
-
writeConfigPreservingOwnership(configPath, after,
|
|
121497
|
+
writeConfigPreservingOwnership(configPath, after, statSync60(configPath).mode & 511);
|
|
120575
121498
|
return true;
|
|
120576
121499
|
},
|
|
120577
121500
|
commitPin: () => {
|
|
@@ -120847,7 +121770,7 @@ init_source();
|
|
|
120847
121770
|
init_helpers();
|
|
120848
121771
|
init_loader();
|
|
120849
121772
|
init_lifecycle();
|
|
120850
|
-
import { resolve as
|
|
121773
|
+
import { resolve as resolve52 } from "node:path";
|
|
120851
121774
|
|
|
120852
121775
|
// src/cli/version.ts
|
|
120853
121776
|
init_source();
|
|
@@ -120855,8 +121778,8 @@ init_helpers();
|
|
|
120855
121778
|
init_lifecycle();
|
|
120856
121779
|
init_resolve_version();
|
|
120857
121780
|
import { execSync as execSync3 } from "node:child_process";
|
|
120858
|
-
import { existsSync as
|
|
120859
|
-
import { dirname as dirname45, join as
|
|
121781
|
+
import { existsSync as existsSync100, readFileSync as readFileSync92 } from "node:fs";
|
|
121782
|
+
import { dirname as dirname45, join as join105 } from "node:path";
|
|
120860
121783
|
function getClaudeCodeVersion() {
|
|
120861
121784
|
try {
|
|
120862
121785
|
const out = execSync3("claude --version 2>/dev/null", {
|
|
@@ -120906,11 +121829,11 @@ function formatUptime3(timestamp) {
|
|
|
120906
121829
|
function locateSwitchroomInstallDir() {
|
|
120907
121830
|
let dir = import.meta.dirname;
|
|
120908
121831
|
for (let i2 = 0;i2 < 10 && dir && dir !== "/"; i2++) {
|
|
120909
|
-
const pkgPath =
|
|
120910
|
-
if (
|
|
121832
|
+
const pkgPath = join105(dir, "package.json");
|
|
121833
|
+
if (existsSync100(pkgPath)) {
|
|
120911
121834
|
try {
|
|
120912
|
-
const pkg = JSON.parse(
|
|
120913
|
-
if (pkg.name === "switchroom" &&
|
|
121835
|
+
const pkg = JSON.parse(readFileSync92(pkgPath, "utf-8"));
|
|
121836
|
+
if (pkg.name === "switchroom" && existsSync100(join105(dir, ".git"))) {
|
|
120914
121837
|
return dir;
|
|
120915
121838
|
}
|
|
120916
121839
|
} catch {}
|
|
@@ -121003,7 +121926,7 @@ function registerRestartCommand(program3) {
|
|
|
121003
121926
|
}
|
|
121004
121927
|
const didRestart = res.restarted || !graceful;
|
|
121005
121928
|
if (didRestart) {
|
|
121006
|
-
const agentDir =
|
|
121929
|
+
const agentDir = resolve52(agentsDir, name);
|
|
121007
121930
|
const converged = waitForAuthConverge(name, agentDir);
|
|
121008
121931
|
if (!converged) {
|
|
121009
121932
|
console.log(source_default.yellow(` ${name}: agent is up but auth status didn't converge in 30s \u2014 check logs`));
|
|
@@ -121086,37 +122009,37 @@ Dependency manifest`));
|
|
|
121086
122009
|
// src/cli/handoff.ts
|
|
121087
122010
|
init_helpers();
|
|
121088
122011
|
init_loader();
|
|
121089
|
-
import { resolve as
|
|
122012
|
+
import { resolve as resolve53 } from "node:path";
|
|
121090
122013
|
init_merge();
|
|
121091
122014
|
|
|
121092
122015
|
// src/agents/session-retention.ts
|
|
121093
122016
|
import {
|
|
121094
|
-
existsSync as
|
|
121095
|
-
readdirSync as
|
|
121096
|
-
statSync as
|
|
122017
|
+
existsSync as existsSync101,
|
|
122018
|
+
readdirSync as readdirSync35,
|
|
122019
|
+
statSync as statSync61,
|
|
121097
122020
|
unlinkSync as unlinkSync20
|
|
121098
122021
|
} from "node:fs";
|
|
121099
|
-
import { join as
|
|
122022
|
+
import { join as join106 } from "node:path";
|
|
121100
122023
|
var DEFAULT_SESSION_RETENTION_MAX_COUNT = 20;
|
|
121101
122024
|
var DEFAULT_SESSION_RETENTION_MAX_AGE_DAYS = 30;
|
|
121102
122025
|
var MIN_KEEP = 2;
|
|
121103
122026
|
function collectSessionJsonl(claudeConfigDir) {
|
|
121104
|
-
const projects =
|
|
121105
|
-
if (!
|
|
122027
|
+
const projects = join106(claudeConfigDir, "projects");
|
|
122028
|
+
if (!existsSync101(projects))
|
|
121106
122029
|
return [];
|
|
121107
122030
|
const found = [];
|
|
121108
122031
|
const walk2 = (dir) => {
|
|
121109
122032
|
let entries;
|
|
121110
122033
|
try {
|
|
121111
|
-
entries =
|
|
122034
|
+
entries = readdirSync35(dir);
|
|
121112
122035
|
} catch {
|
|
121113
122036
|
return;
|
|
121114
122037
|
}
|
|
121115
122038
|
for (const name of entries) {
|
|
121116
|
-
const full =
|
|
122039
|
+
const full = join106(dir, name);
|
|
121117
122040
|
let st;
|
|
121118
122041
|
try {
|
|
121119
|
-
st =
|
|
122042
|
+
st = statSync61(full);
|
|
121120
122043
|
} catch {
|
|
121121
122044
|
continue;
|
|
121122
122045
|
}
|
|
@@ -121187,7 +122110,7 @@ function registerHandoffCommand(program3) {
|
|
|
121187
122110
|
}
|
|
121188
122111
|
agentConfig = resolveAgentConfig(config.defaults, config.profiles, rawAgent);
|
|
121189
122112
|
const agentsDir = resolveAgentsDir(config);
|
|
121190
|
-
agentDir =
|
|
122113
|
+
agentDir = resolve53(agentsDir, agentName);
|
|
121191
122114
|
} catch (err) {
|
|
121192
122115
|
if (!(err instanceof ConfigError))
|
|
121193
122116
|
throw err;
|
|
@@ -121202,7 +122125,7 @@ function registerHandoffCommand(program3) {
|
|
|
121202
122125
|
`);
|
|
121203
122126
|
return;
|
|
121204
122127
|
}
|
|
121205
|
-
const claudeConfigDir =
|
|
122128
|
+
const claudeConfigDir = resolve53(agentDir, ".claude");
|
|
121206
122129
|
const jsonl = findLatestSessionJsonl(claudeConfigDir);
|
|
121207
122130
|
if (!jsonl) {
|
|
121208
122131
|
process.stderr.write(`handoff: no session JSONL under ${claudeConfigDir}/projects; skipping
|
|
@@ -121234,18 +122157,18 @@ function registerHandoffCommand(program3) {
|
|
|
121234
122157
|
// src/issues/store.ts
|
|
121235
122158
|
import {
|
|
121236
122159
|
closeSync as closeSync19,
|
|
121237
|
-
existsSync as
|
|
122160
|
+
existsSync as existsSync102,
|
|
121238
122161
|
mkdirSync as mkdirSync58,
|
|
121239
122162
|
openSync as openSync19,
|
|
121240
|
-
readdirSync as
|
|
121241
|
-
readFileSync as
|
|
122163
|
+
readdirSync as readdirSync36,
|
|
122164
|
+
readFileSync as readFileSync93,
|
|
121242
122165
|
renameSync as renameSync27,
|
|
121243
|
-
statSync as
|
|
122166
|
+
statSync as statSync62,
|
|
121244
122167
|
unlinkSync as unlinkSync21,
|
|
121245
122168
|
writeFileSync as writeFileSync40,
|
|
121246
122169
|
writeSync as writeSync9
|
|
121247
122170
|
} from "node:fs";
|
|
121248
|
-
import { join as
|
|
122171
|
+
import { join as join107 } from "node:path";
|
|
121249
122172
|
import { randomBytes as randomBytes16 } from "node:crypto";
|
|
121250
122173
|
import { execSync as execSync4 } from "node:child_process";
|
|
121251
122174
|
|
|
@@ -121272,12 +122195,12 @@ function computeFingerprint(source, code) {
|
|
|
121272
122195
|
var ISSUES_FILE = "issues.jsonl";
|
|
121273
122196
|
var ISSUES_LOCK = "issues.lock";
|
|
121274
122197
|
function readAll(stateDir) {
|
|
121275
|
-
const
|
|
121276
|
-
if (!
|
|
122198
|
+
const path9 = join107(stateDir, ISSUES_FILE);
|
|
122199
|
+
if (!existsSync102(path9))
|
|
121277
122200
|
return [];
|
|
121278
122201
|
let raw;
|
|
121279
122202
|
try {
|
|
121280
|
-
raw =
|
|
122203
|
+
raw = readFileSync93(path9, "utf-8");
|
|
121281
122204
|
} catch {
|
|
121282
122205
|
return [];
|
|
121283
122206
|
}
|
|
@@ -121349,8 +122272,8 @@ function record(stateDir, input, nowFn = Date.now) {
|
|
|
121349
122272
|
return result;
|
|
121350
122273
|
});
|
|
121351
122274
|
}
|
|
121352
|
-
function
|
|
121353
|
-
if (!
|
|
122275
|
+
function resolve54(stateDir, fingerprint, nowFn = Date.now) {
|
|
122276
|
+
if (!existsSync102(join107(stateDir, ISSUES_FILE)))
|
|
121354
122277
|
return 0;
|
|
121355
122278
|
return withLock(stateDir, () => {
|
|
121356
122279
|
const all = readAll(stateDir);
|
|
@@ -121368,7 +122291,7 @@ function resolve53(stateDir, fingerprint, nowFn = Date.now) {
|
|
|
121368
122291
|
});
|
|
121369
122292
|
}
|
|
121370
122293
|
function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
121371
|
-
if (!
|
|
122294
|
+
if (!existsSync102(join107(stateDir, ISSUES_FILE)))
|
|
121372
122295
|
return 0;
|
|
121373
122296
|
return withLock(stateDir, () => {
|
|
121374
122297
|
const all = readAll(stateDir);
|
|
@@ -121386,7 +122309,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
|
121386
122309
|
});
|
|
121387
122310
|
}
|
|
121388
122311
|
function prune(stateDir, opts = {}) {
|
|
121389
|
-
if (!
|
|
122312
|
+
if (!existsSync102(join107(stateDir, ISSUES_FILE)))
|
|
121390
122313
|
return 0;
|
|
121391
122314
|
return withLock(stateDir, () => {
|
|
121392
122315
|
const all = readAll(stateDir);
|
|
@@ -121419,21 +122342,21 @@ function ensureDir2(stateDir) {
|
|
|
121419
122342
|
mkdirSync58(stateDir, { recursive: true });
|
|
121420
122343
|
}
|
|
121421
122344
|
function writeAll(stateDir, events) {
|
|
121422
|
-
const
|
|
122345
|
+
const path9 = join107(stateDir, ISSUES_FILE);
|
|
121423
122346
|
sweepOrphanTmpFiles(stateDir);
|
|
121424
|
-
const tmp = `${
|
|
122347
|
+
const tmp = `${path9}.tmp-${process.pid}-${randomBytes16(4).toString("hex")}`;
|
|
121425
122348
|
const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
|
|
121426
122349
|
`) + `
|
|
121427
122350
|
`;
|
|
121428
122351
|
writeFileSync40(tmp, body, "utf-8");
|
|
121429
|
-
renameSync27(tmp,
|
|
122352
|
+
renameSync27(tmp, path9);
|
|
121430
122353
|
}
|
|
121431
122354
|
var ORPHAN_TMP_TTL_MS = 60000;
|
|
121432
122355
|
var TMP_PREFIX = `${ISSUES_FILE}.tmp-`;
|
|
121433
122356
|
function sweepOrphanTmpFiles(stateDir) {
|
|
121434
122357
|
let entries;
|
|
121435
122358
|
try {
|
|
121436
|
-
entries =
|
|
122359
|
+
entries = readdirSync36(stateDir);
|
|
121437
122360
|
} catch {
|
|
121438
122361
|
return;
|
|
121439
122362
|
}
|
|
@@ -121441,10 +122364,10 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
121441
122364
|
for (const entry of entries) {
|
|
121442
122365
|
if (!entry.startsWith(TMP_PREFIX))
|
|
121443
122366
|
continue;
|
|
121444
|
-
const tmpPath =
|
|
122367
|
+
const tmpPath = join107(stateDir, entry);
|
|
121445
122368
|
try {
|
|
121446
|
-
const
|
|
121447
|
-
if (
|
|
122369
|
+
const stat2 = statSync62(tmpPath);
|
|
122370
|
+
if (stat2.mtimeMs < cutoff) {
|
|
121448
122371
|
unlinkSync21(tmpPath);
|
|
121449
122372
|
}
|
|
121450
122373
|
} catch {}
|
|
@@ -121453,7 +122376,7 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
121453
122376
|
var LOCK_RETRY_MS = 25;
|
|
121454
122377
|
var LOCK_TIMEOUT_MS = 1e4;
|
|
121455
122378
|
function withLock(stateDir, fn) {
|
|
121456
|
-
const lockPath =
|
|
122379
|
+
const lockPath = join107(stateDir, ISSUES_LOCK);
|
|
121457
122380
|
const startedAt = Date.now();
|
|
121458
122381
|
let fd = null;
|
|
121459
122382
|
while (fd === null) {
|
|
@@ -121488,7 +122411,7 @@ function withLock(stateDir, fn) {
|
|
|
121488
122411
|
function tryStealStaleLock(lockPath) {
|
|
121489
122412
|
let pidStr;
|
|
121490
122413
|
try {
|
|
121491
|
-
pidStr =
|
|
122414
|
+
pidStr = readFileSync93(lockPath, "utf-8").trim();
|
|
121492
122415
|
} catch {
|
|
121493
122416
|
return true;
|
|
121494
122417
|
}
|
|
@@ -121637,11 +122560,11 @@ function registerIssuesCommand(program3) {
|
|
|
121637
122560
|
const stateDir = resolveStateDir2(opts);
|
|
121638
122561
|
let flipped;
|
|
121639
122562
|
if (opts.source && opts.code) {
|
|
121640
|
-
flipped =
|
|
122563
|
+
flipped = resolve54(stateDir, computeFingerprint(opts.source, opts.code));
|
|
121641
122564
|
} else if (opts.source && !fingerprint) {
|
|
121642
122565
|
flipped = resolveAllBySource(stateDir, opts.source);
|
|
121643
122566
|
} else if (fingerprint) {
|
|
121644
|
-
flipped =
|
|
122567
|
+
flipped = resolve54(stateDir, fingerprint);
|
|
121645
122568
|
} else {
|
|
121646
122569
|
process.stderr.write(`issues resolve: need either <fingerprint>, --source, or --source + --code
|
|
121647
122570
|
`);
|
|
@@ -121736,20 +122659,20 @@ function relTime(deltaMs) {
|
|
|
121736
122659
|
|
|
121737
122660
|
// src/cli/deps.ts
|
|
121738
122661
|
init_source();
|
|
121739
|
-
import { existsSync as
|
|
122662
|
+
import { existsSync as existsSync105 } from "node:fs";
|
|
121740
122663
|
import { homedir as homedir56 } from "node:os";
|
|
121741
|
-
import { join as
|
|
122664
|
+
import { join as join110, resolve as resolve55 } from "node:path";
|
|
121742
122665
|
|
|
121743
122666
|
// src/deps/python.ts
|
|
121744
|
-
import { createHash as
|
|
122667
|
+
import { createHash as createHash20 } from "node:crypto";
|
|
121745
122668
|
import {
|
|
121746
|
-
existsSync as
|
|
122669
|
+
existsSync as existsSync103,
|
|
121747
122670
|
mkdirSync as mkdirSync59,
|
|
121748
|
-
readFileSync as
|
|
122671
|
+
readFileSync as readFileSync94,
|
|
121749
122672
|
rmSync as rmSync19,
|
|
121750
122673
|
writeFileSync as writeFileSync41
|
|
121751
122674
|
} from "node:fs";
|
|
121752
|
-
import { dirname as dirname46, join as
|
|
122675
|
+
import { dirname as dirname46, join as join108 } from "node:path";
|
|
121753
122676
|
import { homedir as homedir54 } from "node:os";
|
|
121754
122677
|
import { execFileSync as execFileSync31 } from "node:child_process";
|
|
121755
122678
|
|
|
@@ -121762,26 +122685,26 @@ class PythonEnvError extends Error {
|
|
|
121762
122685
|
}
|
|
121763
122686
|
}
|
|
121764
122687
|
function defaultPythonCacheRoot() {
|
|
121765
|
-
return
|
|
122688
|
+
return join108(homedir54(), ".switchroom", "deps", "python");
|
|
121766
122689
|
}
|
|
121767
|
-
function hashFile(
|
|
121768
|
-
return
|
|
122690
|
+
function hashFile(path9) {
|
|
122691
|
+
return createHash20("sha256").update(readFileSync94(path9)).digest("hex");
|
|
121769
122692
|
}
|
|
121770
122693
|
function ensurePythonEnv(opts) {
|
|
121771
122694
|
const { skillName, requirementsPath, force = false } = opts;
|
|
121772
122695
|
const cacheRoot = opts.cacheRoot ?? defaultPythonCacheRoot();
|
|
121773
122696
|
const hostPython = opts.pythonBin ?? "python3";
|
|
121774
|
-
if (!
|
|
122697
|
+
if (!existsSync103(requirementsPath)) {
|
|
121775
122698
|
throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
|
|
121776
122699
|
}
|
|
121777
|
-
const venvDir =
|
|
121778
|
-
const stampPath =
|
|
121779
|
-
const binDir =
|
|
121780
|
-
const pythonBin =
|
|
121781
|
-
const pipBin =
|
|
122700
|
+
const venvDir = join108(cacheRoot, skillName);
|
|
122701
|
+
const stampPath = join108(venvDir, ".requirements.sha256");
|
|
122702
|
+
const binDir = join108(venvDir, "bin");
|
|
122703
|
+
const pythonBin = join108(binDir, "python");
|
|
122704
|
+
const pipBin = join108(binDir, "pip");
|
|
121782
122705
|
const targetHash = hashFile(requirementsPath);
|
|
121783
|
-
if (!force &&
|
|
121784
|
-
const existingHash =
|
|
122706
|
+
if (!force && existsSync103(stampPath) && existsSync103(pythonBin)) {
|
|
122707
|
+
const existingHash = readFileSync94(stampPath, "utf8").trim();
|
|
121785
122708
|
if (existingHash === targetHash) {
|
|
121786
122709
|
return {
|
|
121787
122710
|
skillName,
|
|
@@ -121793,7 +122716,7 @@ function ensurePythonEnv(opts) {
|
|
|
121793
122716
|
};
|
|
121794
122717
|
}
|
|
121795
122718
|
}
|
|
121796
|
-
if (
|
|
122719
|
+
if (existsSync103(venvDir)) {
|
|
121797
122720
|
rmSync19(venvDir, { recursive: true, force: true });
|
|
121798
122721
|
}
|
|
121799
122722
|
mkdirSync59(dirname46(venvDir), { recursive: true });
|
|
@@ -121828,16 +122751,16 @@ function ensurePythonEnv(opts) {
|
|
|
121828
122751
|
}
|
|
121829
122752
|
|
|
121830
122753
|
// src/deps/node.ts
|
|
121831
|
-
import { createHash as
|
|
122754
|
+
import { createHash as createHash21 } from "node:crypto";
|
|
121832
122755
|
import {
|
|
121833
122756
|
copyFileSync as copyFileSync13,
|
|
121834
|
-
existsSync as
|
|
122757
|
+
existsSync as existsSync104,
|
|
121835
122758
|
mkdirSync as mkdirSync60,
|
|
121836
|
-
readFileSync as
|
|
122759
|
+
readFileSync as readFileSync95,
|
|
121837
122760
|
rmSync as rmSync20,
|
|
121838
122761
|
writeFileSync as writeFileSync42
|
|
121839
122762
|
} from "node:fs";
|
|
121840
|
-
import { dirname as dirname47, join as
|
|
122763
|
+
import { dirname as dirname47, join as join109 } from "node:path";
|
|
121841
122764
|
import { homedir as homedir55 } from "node:os";
|
|
121842
122765
|
import { execFileSync as execFileSync32 } from "node:child_process";
|
|
121843
122766
|
|
|
@@ -121845,614 +122768,188 @@ class NodeEnvError extends Error {
|
|
|
121845
122768
|
stderr;
|
|
121846
122769
|
constructor(message, stderr) {
|
|
121847
122770
|
super(message);
|
|
121848
|
-
this.name = "NodeEnvError";
|
|
121849
|
-
this.stderr = stderr;
|
|
121850
|
-
}
|
|
121851
|
-
}
|
|
121852
|
-
var ALL_LOCKFILES = [
|
|
121853
|
-
"bun.lock",
|
|
121854
|
-
"bun.lockb",
|
|
121855
|
-
"package-lock.json",
|
|
121856
|
-
"pnpm-lock.yaml",
|
|
121857
|
-
"yarn.lock"
|
|
121858
|
-
];
|
|
121859
|
-
var LOCKFILES_FOR = {
|
|
121860
|
-
bun: ["bun.lock", "bun.lockb"],
|
|
121861
|
-
npm: ["package-lock.json"]
|
|
121862
|
-
};
|
|
121863
|
-
function defaultNodeCacheRoot() {
|
|
121864
|
-
return join108(homedir55(), ".switchroom", "deps", "node");
|
|
121865
|
-
}
|
|
121866
|
-
function hashDepInputs(packageJsonPath) {
|
|
121867
|
-
const sourceDir = dirname47(packageJsonPath);
|
|
121868
|
-
const hasher = createHash20("sha256");
|
|
121869
|
-
hasher.update(`package.json
|
|
121870
|
-
`);
|
|
121871
|
-
hasher.update(readFileSync94(packageJsonPath));
|
|
121872
|
-
for (const lockName of ALL_LOCKFILES) {
|
|
121873
|
-
const lockPath = join108(sourceDir, lockName);
|
|
121874
|
-
if (existsSync103(lockPath)) {
|
|
121875
|
-
hasher.update(`
|
|
121876
|
-
`);
|
|
121877
|
-
hasher.update(lockName);
|
|
121878
|
-
hasher.update(`
|
|
121879
|
-
`);
|
|
121880
|
-
hasher.update(readFileSync94(lockPath));
|
|
121881
|
-
}
|
|
121882
|
-
}
|
|
121883
|
-
return hasher.digest("hex");
|
|
121884
|
-
}
|
|
121885
|
-
function ensureNodeEnv(opts) {
|
|
121886
|
-
const { skillName, packageJsonPath, force = false } = opts;
|
|
121887
|
-
const cacheRoot = opts.cacheRoot ?? defaultNodeCacheRoot();
|
|
121888
|
-
const installer = opts.installer ?? "bun";
|
|
121889
|
-
if (!existsSync103(packageJsonPath)) {
|
|
121890
|
-
throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
|
|
121891
|
-
}
|
|
121892
|
-
const sourceDir = dirname47(packageJsonPath);
|
|
121893
|
-
const envDir = join108(cacheRoot, skillName);
|
|
121894
|
-
const stampPath = join108(envDir, ".package.sha256");
|
|
121895
|
-
const nodeModulesDir = join108(envDir, "node_modules");
|
|
121896
|
-
const binDir = join108(nodeModulesDir, ".bin");
|
|
121897
|
-
const targetHash = hashDepInputs(packageJsonPath);
|
|
121898
|
-
if (!force && existsSync103(stampPath) && existsSync103(nodeModulesDir)) {
|
|
121899
|
-
const existingHash = readFileSync94(stampPath, "utf8").trim();
|
|
121900
|
-
if (existingHash === targetHash) {
|
|
121901
|
-
return {
|
|
121902
|
-
skillName,
|
|
121903
|
-
dir: envDir,
|
|
121904
|
-
nodeModulesDir,
|
|
121905
|
-
binDir,
|
|
121906
|
-
rebuilt: false
|
|
121907
|
-
};
|
|
121908
|
-
}
|
|
121909
|
-
}
|
|
121910
|
-
if (existsSync103(envDir)) {
|
|
121911
|
-
rmSync20(envDir, { recursive: true, force: true });
|
|
121912
|
-
}
|
|
121913
|
-
mkdirSync60(envDir, { recursive: true });
|
|
121914
|
-
copyFileSync13(packageJsonPath, join108(envDir, "package.json"));
|
|
121915
|
-
let copiedLockfile = false;
|
|
121916
|
-
for (const lockName of LOCKFILES_FOR[installer]) {
|
|
121917
|
-
const lockPath = join108(sourceDir, lockName);
|
|
121918
|
-
if (existsSync103(lockPath)) {
|
|
121919
|
-
copyFileSync13(lockPath, join108(envDir, lockName));
|
|
121920
|
-
copiedLockfile = true;
|
|
121921
|
-
}
|
|
121922
|
-
}
|
|
121923
|
-
try {
|
|
121924
|
-
if (installer === "bun") {
|
|
121925
|
-
const args = copiedLockfile ? ["install", "--frozen-lockfile"] : ["install"];
|
|
121926
|
-
execFileSync32("bun", args, { cwd: envDir, stdio: "pipe" });
|
|
121927
|
-
} else {
|
|
121928
|
-
const args = copiedLockfile ? ["ci"] : ["install"];
|
|
121929
|
-
execFileSync32("npm", args, { cwd: envDir, stdio: "pipe" });
|
|
121930
|
-
}
|
|
121931
|
-
} catch (err) {
|
|
121932
|
-
const e = err;
|
|
121933
|
-
throw new NodeEnvError(`Failed to install node deps for skill "${skillName}" with ${installer}: ${e.message}`, e.stderr?.toString());
|
|
121934
|
-
}
|
|
121935
|
-
writeFileSync42(stampPath, targetHash + `
|
|
121936
|
-
`);
|
|
121937
|
-
return {
|
|
121938
|
-
skillName,
|
|
121939
|
-
dir: envDir,
|
|
121940
|
-
nodeModulesDir,
|
|
121941
|
-
binDir,
|
|
121942
|
-
rebuilt: true
|
|
121943
|
-
};
|
|
121944
|
-
}
|
|
121945
|
-
|
|
121946
|
-
// src/cli/deps.ts
|
|
121947
|
-
function builtinSkillsRoot() {
|
|
121948
|
-
return resolve54(homedir56(), ".switchroom/skills/_bundled");
|
|
121949
|
-
}
|
|
121950
|
-
function registerDepsCommand(program3) {
|
|
121951
|
-
const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
|
|
121952
|
-
deps.command("rebuild <skill>").description("Rebuild the Python venv and/or Node node_modules cache for a skill").option("-p, --python", "Rebuild only the Python env").option("-n, --node", "Rebuild only the Node env").action(async (skill, opts) => {
|
|
121953
|
-
const skillsRoot = builtinSkillsRoot();
|
|
121954
|
-
if (!existsSync104(skillsRoot)) {
|
|
121955
|
-
console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
|
|
121956
|
-
process.exit(1);
|
|
121957
|
-
}
|
|
121958
|
-
const skillDir = join109(skillsRoot, skill);
|
|
121959
|
-
if (!existsSync104(skillDir)) {
|
|
121960
|
-
console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
|
|
121961
|
-
process.exit(1);
|
|
121962
|
-
}
|
|
121963
|
-
const requirementsPath = join109(skillDir, "requirements.txt");
|
|
121964
|
-
const packageJsonPath = join109(skillDir, "package.json");
|
|
121965
|
-
const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync104(requirementsPath));
|
|
121966
|
-
const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync104(packageJsonPath));
|
|
121967
|
-
let did = 0;
|
|
121968
|
-
if (wantPython) {
|
|
121969
|
-
if (!existsSync104(requirementsPath)) {
|
|
121970
|
-
console.error(source_default.red(`Skill "${skill}" has no requirements.txt at ${requirementsPath}`));
|
|
121971
|
-
process.exit(1);
|
|
121972
|
-
}
|
|
121973
|
-
try {
|
|
121974
|
-
console.log(source_default.gray(`Rebuilding Python env for ${skill}...`));
|
|
121975
|
-
const env2 = ensurePythonEnv({
|
|
121976
|
-
skillName: skill,
|
|
121977
|
-
requirementsPath,
|
|
121978
|
-
force: true
|
|
121979
|
-
});
|
|
121980
|
-
console.log(source_default.green(` \u2713 Python env at ${env2.venvDir} (python: ${env2.pythonBin})`));
|
|
121981
|
-
did++;
|
|
121982
|
-
} catch (err) {
|
|
121983
|
-
if (err instanceof PythonEnvError) {
|
|
121984
|
-
console.error(source_default.red(` Python env failed: ${err.message}`));
|
|
121985
|
-
if (err.stderr)
|
|
121986
|
-
console.error(source_default.dim(err.stderr));
|
|
121987
|
-
process.exit(1);
|
|
121988
|
-
}
|
|
121989
|
-
throw err;
|
|
121990
|
-
}
|
|
121991
|
-
}
|
|
121992
|
-
if (wantNode) {
|
|
121993
|
-
if (!existsSync104(packageJsonPath)) {
|
|
121994
|
-
console.error(source_default.red(`Skill "${skill}" has no package.json at ${packageJsonPath}`));
|
|
121995
|
-
process.exit(1);
|
|
121996
|
-
}
|
|
121997
|
-
try {
|
|
121998
|
-
console.log(source_default.gray(`Rebuilding Node env for ${skill}...`));
|
|
121999
|
-
const env2 = ensureNodeEnv({
|
|
122000
|
-
skillName: skill,
|
|
122001
|
-
packageJsonPath,
|
|
122002
|
-
force: true
|
|
122003
|
-
});
|
|
122004
|
-
console.log(source_default.green(` \u2713 Node env at ${env2.dir} (node_modules: ${env2.nodeModulesDir})`));
|
|
122005
|
-
did++;
|
|
122006
|
-
} catch (err) {
|
|
122007
|
-
if (err instanceof NodeEnvError) {
|
|
122008
|
-
console.error(source_default.red(` Node env failed: ${err.message}`));
|
|
122009
|
-
if (err.stderr)
|
|
122010
|
-
console.error(source_default.dim(err.stderr));
|
|
122011
|
-
process.exit(1);
|
|
122012
|
-
}
|
|
122013
|
-
throw err;
|
|
122014
|
-
}
|
|
122015
|
-
}
|
|
122016
|
-
if (did === 0) {
|
|
122017
|
-
console.error(source_default.yellow(`Skill "${skill}" has neither requirements.txt nor package.json \u2014 nothing to rebuild.`));
|
|
122018
|
-
process.exit(1);
|
|
122019
|
-
}
|
|
122020
|
-
});
|
|
122021
|
-
}
|
|
122022
|
-
|
|
122023
|
-
// src/cli/workspace.ts
|
|
122024
|
-
init_helpers();
|
|
122025
|
-
init_loader();
|
|
122026
|
-
import { existsSync as existsSync105 } from "node:fs";
|
|
122027
|
-
import { resolve as resolve55, sep as sep7 } from "node:path";
|
|
122028
|
-
import { spawnSync as spawnSync23 } from "node:child_process";
|
|
122029
|
-
|
|
122030
|
-
// src/agents/workspace.ts
|
|
122031
|
-
import { readFile as readFile2, stat } from "node:fs/promises";
|
|
122032
|
-
import path8 from "node:path";
|
|
122033
|
-
|
|
122034
|
-
// src/agents/bootstrap-budget.ts
|
|
122035
|
-
import path7 from "node:path";
|
|
122036
|
-
var DEFAULT_BOOTSTRAP_NEAR_LIMIT_RATIO = 0.85;
|
|
122037
|
-
var DEFAULT_BOOTSTRAP_PROMPT_WARNING_MAX_FILES = 3;
|
|
122038
|
-
var DEFAULT_BOOTSTRAP_PROMPT_WARNING_SIGNATURE_HISTORY_MAX = 32;
|
|
122039
|
-
|
|
122040
|
-
class BootstrapBudgetExceededError extends Error {
|
|
122041
|
-
analysis;
|
|
122042
|
-
constructor(analysis, message) {
|
|
122043
|
-
super(message);
|
|
122044
|
-
this.analysis = analysis;
|
|
122045
|
-
this.name = "BootstrapBudgetExceededError";
|
|
122046
|
-
}
|
|
122047
|
-
}
|
|
122048
|
-
function normalizePositiveLimit(value) {
|
|
122049
|
-
if (!Number.isFinite(value) || value <= 0) {
|
|
122050
|
-
return 1;
|
|
122051
|
-
}
|
|
122052
|
-
return Math.floor(value);
|
|
122053
|
-
}
|
|
122054
|
-
function formatWarningCause(cause) {
|
|
122055
|
-
return cause === "per-file-limit" ? "max/file" : "max/total";
|
|
122056
|
-
}
|
|
122057
|
-
function normalizeSeenSignatures(signatures) {
|
|
122058
|
-
if (!Array.isArray(signatures) || signatures.length === 0) {
|
|
122059
|
-
return [];
|
|
122060
|
-
}
|
|
122061
|
-
const seen = new Set;
|
|
122062
|
-
const result = [];
|
|
122063
|
-
for (const signature of signatures) {
|
|
122064
|
-
const value = typeof signature === "string" ? signature.trim() : "";
|
|
122065
|
-
if (!value || seen.has(value)) {
|
|
122066
|
-
continue;
|
|
122067
|
-
}
|
|
122068
|
-
seen.add(value);
|
|
122069
|
-
result.push(value);
|
|
122070
|
-
}
|
|
122071
|
-
return result;
|
|
122072
|
-
}
|
|
122073
|
-
function appendSeenSignature(signatures, signature) {
|
|
122074
|
-
if (!signature.trim()) {
|
|
122075
|
-
return signatures;
|
|
122076
|
-
}
|
|
122077
|
-
if (signatures.includes(signature)) {
|
|
122078
|
-
return signatures;
|
|
122079
|
-
}
|
|
122080
|
-
const next = [...signatures, signature];
|
|
122081
|
-
if (next.length <= DEFAULT_BOOTSTRAP_PROMPT_WARNING_SIGNATURE_HISTORY_MAX) {
|
|
122082
|
-
return next;
|
|
122083
|
-
}
|
|
122084
|
-
return next.slice(-DEFAULT_BOOTSTRAP_PROMPT_WARNING_SIGNATURE_HISTORY_MAX);
|
|
122085
|
-
}
|
|
122086
|
-
function buildBootstrapInjectionStats(params) {
|
|
122087
|
-
const injectedByPath = new Map;
|
|
122088
|
-
const injectedByBaseName = new Map;
|
|
122089
|
-
for (const file of params.injectedFiles) {
|
|
122090
|
-
const pathValue = typeof file.path === "string" ? file.path.trim() : "";
|
|
122091
|
-
if (!pathValue) {
|
|
122092
|
-
continue;
|
|
122093
|
-
}
|
|
122094
|
-
if (!injectedByPath.has(pathValue)) {
|
|
122095
|
-
injectedByPath.set(pathValue, file.content);
|
|
122096
|
-
}
|
|
122097
|
-
const normalizedPath = pathValue.replace(/\\/g, "/");
|
|
122098
|
-
const baseName = path7.posix.basename(normalizedPath);
|
|
122099
|
-
if (!injectedByBaseName.has(baseName)) {
|
|
122100
|
-
injectedByBaseName.set(baseName, file.content);
|
|
122101
|
-
}
|
|
122102
|
-
}
|
|
122103
|
-
return params.bootstrapFiles.map((file) => {
|
|
122104
|
-
const pathValue = typeof file.path === "string" ? file.path.trim() : "";
|
|
122105
|
-
const rawChars = file.missing ? 0 : (file.content ?? "").trimEnd().length;
|
|
122106
|
-
const injected = (pathValue ? injectedByPath.get(pathValue) : undefined) ?? injectedByPath.get(file.name) ?? injectedByBaseName.get(file.name);
|
|
122107
|
-
const injectedChars = injected ? injected.length : 0;
|
|
122108
|
-
const truncated = !file.missing && injectedChars < rawChars;
|
|
122109
|
-
return {
|
|
122110
|
-
name: file.name,
|
|
122111
|
-
path: pathValue || file.name,
|
|
122112
|
-
missing: file.missing,
|
|
122113
|
-
rawChars,
|
|
122114
|
-
injectedChars,
|
|
122115
|
-
truncated
|
|
122116
|
-
};
|
|
122117
|
-
});
|
|
122118
|
-
}
|
|
122119
|
-
function analyzeBootstrapBudget(params) {
|
|
122120
|
-
const bootstrapMaxChars = normalizePositiveLimit(params.bootstrapMaxChars);
|
|
122121
|
-
const bootstrapTotalMaxChars = normalizePositiveLimit(params.bootstrapTotalMaxChars);
|
|
122122
|
-
const nearLimitRatio = typeof params.nearLimitRatio === "number" && Number.isFinite(params.nearLimitRatio) && params.nearLimitRatio > 0 && params.nearLimitRatio < 1 ? params.nearLimitRatio : DEFAULT_BOOTSTRAP_NEAR_LIMIT_RATIO;
|
|
122123
|
-
const nonMissing = params.files.filter((file) => !file.missing);
|
|
122124
|
-
const rawChars = nonMissing.reduce((sum, file) => sum + file.rawChars, 0);
|
|
122125
|
-
const injectedChars = nonMissing.reduce((sum, file) => sum + file.injectedChars, 0);
|
|
122126
|
-
const totalNearLimit = injectedChars >= Math.ceil(bootstrapTotalMaxChars * nearLimitRatio);
|
|
122127
|
-
const totalOverLimit = injectedChars >= bootstrapTotalMaxChars;
|
|
122128
|
-
const files = params.files.map((file) => {
|
|
122129
|
-
if (file.missing) {
|
|
122130
|
-
return { ...file, nearLimit: false, causes: [] };
|
|
122131
|
-
}
|
|
122132
|
-
const perFileOverLimit = file.rawChars > bootstrapMaxChars;
|
|
122133
|
-
const nearLimit = file.rawChars >= Math.ceil(bootstrapMaxChars * nearLimitRatio);
|
|
122134
|
-
const causes = [];
|
|
122135
|
-
if (file.truncated) {
|
|
122136
|
-
if (perFileOverLimit) {
|
|
122137
|
-
causes.push("per-file-limit");
|
|
122138
|
-
}
|
|
122139
|
-
if (totalOverLimit) {
|
|
122140
|
-
causes.push("total-limit");
|
|
122141
|
-
}
|
|
122142
|
-
}
|
|
122143
|
-
return { ...file, nearLimit, causes };
|
|
122144
|
-
});
|
|
122145
|
-
const truncatedFiles = files.filter((file) => file.truncated);
|
|
122146
|
-
const nearLimitFiles = files.filter((file) => file.nearLimit);
|
|
122147
|
-
return {
|
|
122148
|
-
files,
|
|
122149
|
-
truncatedFiles,
|
|
122150
|
-
nearLimitFiles,
|
|
122151
|
-
totalNearLimit,
|
|
122152
|
-
hasTruncation: truncatedFiles.length > 0,
|
|
122153
|
-
totals: {
|
|
122154
|
-
rawChars,
|
|
122155
|
-
injectedChars,
|
|
122156
|
-
truncatedChars: Math.max(0, rawChars - injectedChars),
|
|
122157
|
-
bootstrapMaxChars,
|
|
122158
|
-
bootstrapTotalMaxChars,
|
|
122159
|
-
nearLimitRatio
|
|
122160
|
-
}
|
|
122161
|
-
};
|
|
122162
|
-
}
|
|
122163
|
-
function buildBootstrapTruncationSignature(analysis) {
|
|
122164
|
-
if (!analysis.hasTruncation) {
|
|
122165
|
-
return;
|
|
122166
|
-
}
|
|
122167
|
-
const files = analysis.truncatedFiles.map((file) => ({
|
|
122168
|
-
path: file.path || file.name,
|
|
122169
|
-
rawChars: file.rawChars,
|
|
122170
|
-
injectedChars: file.injectedChars,
|
|
122171
|
-
causes: [...file.causes].sort()
|
|
122172
|
-
})).sort((a, b) => {
|
|
122173
|
-
const pathCmp = a.path.localeCompare(b.path);
|
|
122174
|
-
if (pathCmp !== 0) {
|
|
122175
|
-
return pathCmp;
|
|
122176
|
-
}
|
|
122177
|
-
if (a.rawChars !== b.rawChars) {
|
|
122178
|
-
return a.rawChars - b.rawChars;
|
|
122179
|
-
}
|
|
122180
|
-
if (a.injectedChars !== b.injectedChars) {
|
|
122181
|
-
return a.injectedChars - b.injectedChars;
|
|
122182
|
-
}
|
|
122183
|
-
return a.causes.join("+").localeCompare(b.causes.join("+"));
|
|
122184
|
-
});
|
|
122185
|
-
return JSON.stringify({
|
|
122186
|
-
bootstrapMaxChars: analysis.totals.bootstrapMaxChars,
|
|
122187
|
-
bootstrapTotalMaxChars: analysis.totals.bootstrapTotalMaxChars,
|
|
122188
|
-
files
|
|
122189
|
-
});
|
|
122190
|
-
}
|
|
122191
|
-
function formatBootstrapTruncationWarningLines(params) {
|
|
122192
|
-
if (!params.analysis.hasTruncation) {
|
|
122193
|
-
return [];
|
|
122194
|
-
}
|
|
122195
|
-
const maxFiles = typeof params.maxFiles === "number" && Number.isFinite(params.maxFiles) && params.maxFiles > 0 ? Math.floor(params.maxFiles) : DEFAULT_BOOTSTRAP_PROMPT_WARNING_MAX_FILES;
|
|
122196
|
-
const lines = [];
|
|
122197
|
-
const duplicateNameCounts = params.analysis.truncatedFiles.reduce((acc, file) => {
|
|
122198
|
-
acc.set(file.name, (acc.get(file.name) ?? 0) + 1);
|
|
122199
|
-
return acc;
|
|
122200
|
-
}, new Map);
|
|
122201
|
-
const topFiles = params.analysis.truncatedFiles.slice(0, maxFiles);
|
|
122202
|
-
for (const file of topFiles) {
|
|
122203
|
-
const pct = file.rawChars > 0 ? Math.round((file.rawChars - file.injectedChars) / file.rawChars * 100) : 0;
|
|
122204
|
-
const causeText = file.causes.length > 0 ? file.causes.map((cause) => formatWarningCause(cause)).join(", ") : "";
|
|
122205
|
-
const nameLabel = (duplicateNameCounts.get(file.name) ?? 0) > 1 && file.path.trim().length > 0 ? `${file.name} (${file.path})` : file.name;
|
|
122206
|
-
lines.push(`${nameLabel}: ${file.rawChars} raw -> ${file.injectedChars} injected (~${Math.max(0, pct)}% removed${causeText ? `; ${causeText}` : ""}).`);
|
|
122207
|
-
}
|
|
122208
|
-
if (params.analysis.truncatedFiles.length > topFiles.length) {
|
|
122209
|
-
lines.push(`+${params.analysis.truncatedFiles.length - topFiles.length} more truncated file(s).`);
|
|
122210
|
-
}
|
|
122211
|
-
lines.push("If unintentional, raise switchroom workspace bootstrapMaxChars and/or bootstrapTotalMaxChars config.");
|
|
122212
|
-
return lines;
|
|
122213
|
-
}
|
|
122214
|
-
function buildBootstrapPromptWarning(params) {
|
|
122215
|
-
const signature = buildBootstrapTruncationSignature(params.analysis);
|
|
122216
|
-
let seenSignatures = normalizeSeenSignatures(params.seenSignatures);
|
|
122217
|
-
if (params.previousSignature && !seenSignatures.includes(params.previousSignature)) {
|
|
122218
|
-
seenSignatures = appendSeenSignature(seenSignatures, params.previousSignature);
|
|
122219
|
-
}
|
|
122220
|
-
const hasSeenSignature = Boolean(signature && seenSignatures.includes(signature));
|
|
122221
|
-
const warningShown = params.mode !== "off" && Boolean(signature) && (params.mode === "always" || !hasSeenSignature);
|
|
122222
|
-
const warningSignaturesSeen = signature && params.mode !== "off" ? appendSeenSignature(seenSignatures, signature) : seenSignatures;
|
|
122223
|
-
return {
|
|
122224
|
-
signature,
|
|
122225
|
-
warningShown,
|
|
122226
|
-
lines: warningShown ? formatBootstrapTruncationWarningLines({
|
|
122227
|
-
analysis: params.analysis,
|
|
122228
|
-
maxFiles: params.maxFiles
|
|
122229
|
-
}) : [],
|
|
122230
|
-
warningSignaturesSeen
|
|
122231
|
-
};
|
|
122232
|
-
}
|
|
122233
|
-
|
|
122234
|
-
// src/agents/bootstrap-types.ts
|
|
122235
|
-
var DEFAULT_AGENTS_FILENAME = "AGENTS.md";
|
|
122236
|
-
var DEFAULT_SOUL_DEFAULT_FILENAME = "SOUL.default.md";
|
|
122237
|
-
var DEFAULT_SOUL_FILENAME = "SOUL.md";
|
|
122238
|
-
var DEFAULT_TOOLS_FILENAME = "TOOLS.md";
|
|
122239
|
-
var DEFAULT_IDENTITY_FILENAME = "IDENTITY.md";
|
|
122240
|
-
var DEFAULT_USER_FILENAME = "USER.md";
|
|
122241
|
-
var DEFAULT_BOOTSTRAP_FILENAME = "BOOTSTRAP.md";
|
|
122242
|
-
var DEFAULT_MEMORY_FILENAME = "MEMORY.md";
|
|
122243
|
-
|
|
122244
|
-
// src/agents/workspace.ts
|
|
122245
|
-
var DEFAULT_WORKSPACE_DIR_NAME = "workspace";
|
|
122246
|
-
var DEFAULT_MEMORY_SUBDIR = "memory";
|
|
122247
|
-
var STABLE_BOOTSTRAP_FILENAMES = [
|
|
122248
|
-
DEFAULT_AGENTS_FILENAME,
|
|
122249
|
-
DEFAULT_SOUL_DEFAULT_FILENAME,
|
|
122250
|
-
DEFAULT_SOUL_FILENAME,
|
|
122251
|
-
DEFAULT_IDENTITY_FILENAME,
|
|
122252
|
-
DEFAULT_USER_FILENAME,
|
|
122253
|
-
DEFAULT_TOOLS_FILENAME,
|
|
122254
|
-
DEFAULT_BOOTSTRAP_FILENAME
|
|
122255
|
-
];
|
|
122256
|
-
var DYNAMIC_BOOTSTRAP_FILENAMES = [
|
|
122257
|
-
DEFAULT_MEMORY_FILENAME
|
|
122258
|
-
];
|
|
122259
|
-
var DEFAULT_BOOTSTRAP_MAX_CHARS = 12000;
|
|
122260
|
-
var DEFAULT_BOOTSTRAP_TOTAL_MAX_CHARS = 64000;
|
|
122261
|
-
var DEFAULT_DYNAMIC_TOTAL_MAX_CHARS = 24000;
|
|
122262
|
-
function resolveAgentWorkspaceDir(agentDir) {
|
|
122263
|
-
return path8.join(agentDir, DEFAULT_WORKSPACE_DIR_NAME);
|
|
122264
|
-
}
|
|
122265
|
-
async function readOptionalFile(filePath) {
|
|
122266
|
-
try {
|
|
122267
|
-
const content = await readFile2(filePath, "utf8");
|
|
122268
|
-
return content;
|
|
122269
|
-
} catch (err) {
|
|
122270
|
-
if (isErrnoException(err) && (err.code === "ENOENT" || err.code === "EISDIR")) {
|
|
122271
|
-
return;
|
|
122272
|
-
}
|
|
122273
|
-
throw err;
|
|
122274
|
-
}
|
|
122275
|
-
}
|
|
122276
|
-
function isErrnoException(value) {
|
|
122277
|
-
return value instanceof Error && typeof value.code === "string";
|
|
122278
|
-
}
|
|
122279
|
-
async function loadNamedFile(workspaceDir, name, relativePath) {
|
|
122280
|
-
const filePath = path8.join(workspaceDir, relativePath ?? name);
|
|
122281
|
-
const content = await readOptionalFile(filePath);
|
|
122282
|
-
if (content === undefined) {
|
|
122283
|
-
return { name, path: filePath, missing: true };
|
|
122284
|
-
}
|
|
122285
|
-
return { name, path: filePath, content, missing: false };
|
|
122286
|
-
}
|
|
122287
|
-
async function loadStableBootstrapFiles(workspaceDir, options) {
|
|
122288
|
-
const defaultFiles = await Promise.all(STABLE_BOOTSTRAP_FILENAMES.map((name) => loadNamedFile(workspaceDir, name)));
|
|
122289
|
-
const extras = options?.extraStableFiles ?? [];
|
|
122290
|
-
if (extras.length === 0) {
|
|
122291
|
-
return defaultFiles;
|
|
122292
|
-
}
|
|
122293
|
-
const extraFiles = await Promise.all(extras.map((name) => loadNamedFile(workspaceDir, name)));
|
|
122294
|
-
return [...defaultFiles, ...extraFiles];
|
|
122295
|
-
}
|
|
122296
|
-
async function loadDynamicBootstrapFiles(workspaceDir, options) {
|
|
122297
|
-
const now2 = options?.now ?? new Date;
|
|
122298
|
-
const includeYesterday = options?.includeYesterday ?? true;
|
|
122299
|
-
const files = await Promise.all(DYNAMIC_BOOTSTRAP_FILENAMES.map((name) => loadNamedFile(workspaceDir, name)));
|
|
122300
|
-
const todayRelative = dailyMemoryRelativePath(now2);
|
|
122301
|
-
const todayName = DEFAULT_MEMORY_FILENAME;
|
|
122302
|
-
const today = await loadNamedFile(workspaceDir, todayName, todayRelative);
|
|
122303
|
-
today.path = path8.join(workspaceDir, todayRelative);
|
|
122304
|
-
files.push(today);
|
|
122305
|
-
if (includeYesterday) {
|
|
122306
|
-
const yesterdayRelative = dailyMemoryRelativePath(addDays(now2, -1));
|
|
122307
|
-
const yesterday = await loadNamedFile(workspaceDir, todayName, yesterdayRelative);
|
|
122308
|
-
yesterday.path = path8.join(workspaceDir, yesterdayRelative);
|
|
122309
|
-
files.push(yesterday);
|
|
122771
|
+
this.name = "NodeEnvError";
|
|
122772
|
+
this.stderr = stderr;
|
|
122310
122773
|
}
|
|
122311
|
-
return files;
|
|
122312
|
-
}
|
|
122313
|
-
function pad22(n) {
|
|
122314
|
-
return n < 10 ? `0${n}` : String(n);
|
|
122315
|
-
}
|
|
122316
|
-
function dailyMemoryRelativePath(date) {
|
|
122317
|
-
const y = date.getFullYear();
|
|
122318
|
-
const m = pad22(date.getMonth() + 1);
|
|
122319
|
-
const d = pad22(date.getDate());
|
|
122320
|
-
return path8.join(DEFAULT_MEMORY_SUBDIR, `${y}-${m}-${d}.md`);
|
|
122321
122774
|
}
|
|
122322
|
-
|
|
122323
|
-
|
|
122324
|
-
|
|
122325
|
-
|
|
122775
|
+
var ALL_LOCKFILES = [
|
|
122776
|
+
"bun.lock",
|
|
122777
|
+
"bun.lockb",
|
|
122778
|
+
"package-lock.json",
|
|
122779
|
+
"pnpm-lock.yaml",
|
|
122780
|
+
"yarn.lock"
|
|
122781
|
+
];
|
|
122782
|
+
var LOCKFILES_FOR = {
|
|
122783
|
+
bun: ["bun.lock", "bun.lockb"],
|
|
122784
|
+
npm: ["package-lock.json"]
|
|
122785
|
+
};
|
|
122786
|
+
function defaultNodeCacheRoot() {
|
|
122787
|
+
return join109(homedir55(), ".switchroom", "deps", "node");
|
|
122326
122788
|
}
|
|
122327
|
-
function
|
|
122328
|
-
|
|
122329
|
-
|
|
122330
|
-
|
|
122331
|
-
|
|
122332
|
-
|
|
122789
|
+
function hashDepInputs(packageJsonPath) {
|
|
122790
|
+
const sourceDir = dirname47(packageJsonPath);
|
|
122791
|
+
const hasher = createHash21("sha256");
|
|
122792
|
+
hasher.update(`package.json
|
|
122793
|
+
`);
|
|
122794
|
+
hasher.update(readFileSync95(packageJsonPath));
|
|
122795
|
+
for (const lockName of ALL_LOCKFILES) {
|
|
122796
|
+
const lockPath = join109(sourceDir, lockName);
|
|
122797
|
+
if (existsSync104(lockPath)) {
|
|
122798
|
+
hasher.update(`
|
|
122799
|
+
`);
|
|
122800
|
+
hasher.update(lockName);
|
|
122801
|
+
hasher.update(`
|
|
122802
|
+
`);
|
|
122803
|
+
hasher.update(readFileSync95(lockPath));
|
|
122804
|
+
}
|
|
122333
122805
|
}
|
|
122334
|
-
|
|
122335
|
-
const sampleMarker = `
|
|
122336
|
-
\u2026(truncated ${name}: kept ${headRoomInitial}+${Math.max(0, maxChars - headRoomInitial - 96)} chars of ${content.length})\u2026
|
|
122337
|
-
`;
|
|
122338
|
-
const markerReserve = Math.max(64, sampleMarker.length + 8);
|
|
122339
|
-
const headRoom = Math.min(headRoomInitial, Math.max(0, maxChars - markerReserve - 8));
|
|
122340
|
-
const tailRoom = Math.max(0, maxChars - headRoom - markerReserve);
|
|
122341
|
-
const head = content.slice(0, Math.max(0, headRoom));
|
|
122342
|
-
const tail = tailRoom > 0 ? content.slice(content.length - tailRoom) : "";
|
|
122343
|
-
const result = `${head}
|
|
122344
|
-
\u2026(truncated ${name}: kept ${headRoom}+${tailRoom} chars of ${content.length})\u2026
|
|
122345
|
-
${tail}`;
|
|
122346
|
-
return result.length <= maxChars ? result : result.slice(0, maxChars);
|
|
122806
|
+
return hasher.digest("hex");
|
|
122347
122807
|
}
|
|
122348
|
-
function
|
|
122349
|
-
const {
|
|
122350
|
-
|
|
122351
|
-
|
|
122352
|
-
|
|
122353
|
-
|
|
122354
|
-
|
|
122355
|
-
|
|
122356
|
-
|
|
122357
|
-
const
|
|
122358
|
-
|
|
122359
|
-
const
|
|
122360
|
-
const
|
|
122361
|
-
|
|
122362
|
-
|
|
122363
|
-
|
|
122364
|
-
|
|
122365
|
-
|
|
122366
|
-
|
|
122367
|
-
|
|
122368
|
-
|
|
122369
|
-
|
|
122370
|
-
|
|
122371
|
-
break;
|
|
122808
|
+
function ensureNodeEnv(opts) {
|
|
122809
|
+
const { skillName, packageJsonPath, force = false } = opts;
|
|
122810
|
+
const cacheRoot = opts.cacheRoot ?? defaultNodeCacheRoot();
|
|
122811
|
+
const installer = opts.installer ?? "bun";
|
|
122812
|
+
if (!existsSync104(packageJsonPath)) {
|
|
122813
|
+
throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
|
|
122814
|
+
}
|
|
122815
|
+
const sourceDir = dirname47(packageJsonPath);
|
|
122816
|
+
const envDir = join109(cacheRoot, skillName);
|
|
122817
|
+
const stampPath = join109(envDir, ".package.sha256");
|
|
122818
|
+
const nodeModulesDir = join109(envDir, "node_modules");
|
|
122819
|
+
const binDir = join109(nodeModulesDir, ".bin");
|
|
122820
|
+
const targetHash = hashDepInputs(packageJsonPath);
|
|
122821
|
+
if (!force && existsSync104(stampPath) && existsSync104(nodeModulesDir)) {
|
|
122822
|
+
const existingHash = readFileSync95(stampPath, "utf8").trim();
|
|
122823
|
+
if (existingHash === targetHash) {
|
|
122824
|
+
return {
|
|
122825
|
+
skillName,
|
|
122826
|
+
dir: envDir,
|
|
122827
|
+
nodeModulesDir,
|
|
122828
|
+
binDir,
|
|
122829
|
+
rebuilt: false
|
|
122830
|
+
};
|
|
122372
122831
|
}
|
|
122373
|
-
|
|
122374
|
-
|
|
122375
|
-
|
|
122376
|
-
|
|
122377
|
-
|
|
122378
|
-
|
|
122379
|
-
|
|
122380
|
-
|
|
122832
|
+
}
|
|
122833
|
+
if (existsSync104(envDir)) {
|
|
122834
|
+
rmSync20(envDir, { recursive: true, force: true });
|
|
122835
|
+
}
|
|
122836
|
+
mkdirSync60(envDir, { recursive: true });
|
|
122837
|
+
copyFileSync13(packageJsonPath, join109(envDir, "package.json"));
|
|
122838
|
+
let copiedLockfile = false;
|
|
122839
|
+
for (const lockName of LOCKFILES_FOR[installer]) {
|
|
122840
|
+
const lockPath = join109(sourceDir, lockName);
|
|
122841
|
+
if (existsSync104(lockPath)) {
|
|
122842
|
+
copyFileSync13(lockPath, join109(envDir, lockName));
|
|
122843
|
+
copiedLockfile = true;
|
|
122381
122844
|
}
|
|
122382
122845
|
}
|
|
122383
|
-
|
|
122384
|
-
|
|
122385
|
-
|
|
122386
|
-
|
|
122387
|
-
|
|
122388
|
-
|
|
122389
|
-
|
|
122390
|
-
const analysis = analyzeBootstrapBudget({
|
|
122391
|
-
files: stats,
|
|
122392
|
-
bootstrapMaxChars: budget.bootstrapMaxChars,
|
|
122393
|
-
bootstrapTotalMaxChars: budget.bootstrapTotalMaxChars,
|
|
122394
|
-
nearLimitRatio: budget.nearLimitRatio ?? DEFAULT_BOOTSTRAP_NEAR_LIMIT_RATIO
|
|
122395
|
-
});
|
|
122396
|
-
const warning = buildBootstrapPromptWarning({
|
|
122397
|
-
analysis,
|
|
122398
|
-
mode: warningMode,
|
|
122399
|
-
seenSignatures,
|
|
122400
|
-
maxFiles: warningMaxFiles
|
|
122401
|
-
});
|
|
122402
|
-
if (warningMode === "error" && analysis.hasTruncation) {
|
|
122403
|
-
const errorLines = [
|
|
122404
|
-
"Bootstrap budget exceeded. The following files exceed limits:",
|
|
122405
|
-
""
|
|
122406
|
-
];
|
|
122407
|
-
for (const file of analysis.truncatedFiles) {
|
|
122408
|
-
const excessChars = file.rawChars - file.injectedChars;
|
|
122409
|
-
const causes = file.causes.map((c) => c === "per-file-limit" ? `per-file limit (${analysis.totals.bootstrapMaxChars.toLocaleString()} chars)` : `total limit (${analysis.totals.bootstrapTotalMaxChars.toLocaleString()} chars)`);
|
|
122410
|
-
errorLines.push(` ${file.name}: ${file.rawChars.toLocaleString()} bytes (exceeds ${causes.join(", ")})`);
|
|
122411
|
-
errorLines.push(` Trim ${excessChars.toLocaleString()} bytes, or pass --warning-mode warn to proceed with truncation.`);
|
|
122846
|
+
try {
|
|
122847
|
+
if (installer === "bun") {
|
|
122848
|
+
const args = copiedLockfile ? ["install", "--frozen-lockfile"] : ["install"];
|
|
122849
|
+
execFileSync32("bun", args, { cwd: envDir, stdio: "pipe" });
|
|
122850
|
+
} else {
|
|
122851
|
+
const args = copiedLockfile ? ["ci"] : ["install"];
|
|
122852
|
+
execFileSync32("npm", args, { cwd: envDir, stdio: "pipe" });
|
|
122412
122853
|
}
|
|
122413
|
-
|
|
122414
|
-
|
|
122854
|
+
} catch (err) {
|
|
122855
|
+
const e = err;
|
|
122856
|
+
throw new NodeEnvError(`Failed to install node deps for skill "${skillName}" with ${installer}: ${e.message}`, e.stderr?.toString());
|
|
122415
122857
|
}
|
|
122416
|
-
|
|
122417
|
-
|
|
122418
|
-
|
|
122419
|
-
|
|
122420
|
-
|
|
122421
|
-
|
|
122422
|
-
|
|
122423
|
-
|
|
122424
|
-
bootstrapTotalMaxChars: params.budget?.bootstrapTotalMaxChars ?? DEFAULT_BOOTSTRAP_TOTAL_MAX_CHARS,
|
|
122425
|
-
nearLimitRatio: params.budget?.nearLimitRatio
|
|
122858
|
+
writeFileSync42(stampPath, targetHash + `
|
|
122859
|
+
`);
|
|
122860
|
+
return {
|
|
122861
|
+
skillName,
|
|
122862
|
+
dir: envDir,
|
|
122863
|
+
nodeModulesDir,
|
|
122864
|
+
binDir,
|
|
122865
|
+
rebuilt: true
|
|
122426
122866
|
};
|
|
122427
|
-
return projectBootstrapFiles({
|
|
122428
|
-
files,
|
|
122429
|
-
heading: "Project Context (stable workspace files)",
|
|
122430
|
-
budget,
|
|
122431
|
-
seenSignatures: params.seenSignatures,
|
|
122432
|
-
warningMode: params.budget?.warningMode,
|
|
122433
|
-
warningMaxFiles: params.budget?.warningMaxFiles
|
|
122434
|
-
});
|
|
122435
122867
|
}
|
|
122436
|
-
|
|
122437
|
-
|
|
122438
|
-
|
|
122439
|
-
|
|
122440
|
-
|
|
122441
|
-
|
|
122442
|
-
|
|
122443
|
-
|
|
122444
|
-
|
|
122445
|
-
|
|
122446
|
-
|
|
122447
|
-
|
|
122448
|
-
|
|
122449
|
-
|
|
122450
|
-
|
|
122451
|
-
|
|
122452
|
-
|
|
122868
|
+
|
|
122869
|
+
// src/cli/deps.ts
|
|
122870
|
+
function builtinSkillsRoot() {
|
|
122871
|
+
return resolve55(homedir56(), ".switchroom/skills/_bundled");
|
|
122872
|
+
}
|
|
122873
|
+
function registerDepsCommand(program3) {
|
|
122874
|
+
const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
|
|
122875
|
+
deps.command("rebuild <skill>").description("Rebuild the Python venv and/or Node node_modules cache for a skill").option("-p, --python", "Rebuild only the Python env").option("-n, --node", "Rebuild only the Node env").action(async (skill, opts) => {
|
|
122876
|
+
const skillsRoot = builtinSkillsRoot();
|
|
122877
|
+
if (!existsSync105(skillsRoot)) {
|
|
122878
|
+
console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
|
|
122879
|
+
process.exit(1);
|
|
122880
|
+
}
|
|
122881
|
+
const skillDir = join110(skillsRoot, skill);
|
|
122882
|
+
if (!existsSync105(skillDir)) {
|
|
122883
|
+
console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
|
|
122884
|
+
process.exit(1);
|
|
122885
|
+
}
|
|
122886
|
+
const requirementsPath = join110(skillDir, "requirements.txt");
|
|
122887
|
+
const packageJsonPath = join110(skillDir, "package.json");
|
|
122888
|
+
const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync105(requirementsPath));
|
|
122889
|
+
const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync105(packageJsonPath));
|
|
122890
|
+
let did = 0;
|
|
122891
|
+
if (wantPython) {
|
|
122892
|
+
if (!existsSync105(requirementsPath)) {
|
|
122893
|
+
console.error(source_default.red(`Skill "${skill}" has no requirements.txt at ${requirementsPath}`));
|
|
122894
|
+
process.exit(1);
|
|
122895
|
+
}
|
|
122896
|
+
try {
|
|
122897
|
+
console.log(source_default.gray(`Rebuilding Python env for ${skill}...`));
|
|
122898
|
+
const env2 = ensurePythonEnv({
|
|
122899
|
+
skillName: skill,
|
|
122900
|
+
requirementsPath,
|
|
122901
|
+
force: true
|
|
122902
|
+
});
|
|
122903
|
+
console.log(source_default.green(` \u2713 Python env at ${env2.venvDir} (python: ${env2.pythonBin})`));
|
|
122904
|
+
did++;
|
|
122905
|
+
} catch (err) {
|
|
122906
|
+
if (err instanceof PythonEnvError) {
|
|
122907
|
+
console.error(source_default.red(` Python env failed: ${err.message}`));
|
|
122908
|
+
if (err.stderr)
|
|
122909
|
+
console.error(source_default.dim(err.stderr));
|
|
122910
|
+
process.exit(1);
|
|
122911
|
+
}
|
|
122912
|
+
throw err;
|
|
122913
|
+
}
|
|
122914
|
+
}
|
|
122915
|
+
if (wantNode) {
|
|
122916
|
+
if (!existsSync105(packageJsonPath)) {
|
|
122917
|
+
console.error(source_default.red(`Skill "${skill}" has no package.json at ${packageJsonPath}`));
|
|
122918
|
+
process.exit(1);
|
|
122919
|
+
}
|
|
122920
|
+
try {
|
|
122921
|
+
console.log(source_default.gray(`Rebuilding Node env for ${skill}...`));
|
|
122922
|
+
const env2 = ensureNodeEnv({
|
|
122923
|
+
skillName: skill,
|
|
122924
|
+
packageJsonPath,
|
|
122925
|
+
force: true
|
|
122926
|
+
});
|
|
122927
|
+
console.log(source_default.green(` \u2713 Node env at ${env2.dir} (node_modules: ${env2.nodeModulesDir})`));
|
|
122928
|
+
did++;
|
|
122929
|
+
} catch (err) {
|
|
122930
|
+
if (err instanceof NodeEnvError) {
|
|
122931
|
+
console.error(source_default.red(` Node env failed: ${err.message}`));
|
|
122932
|
+
if (err.stderr)
|
|
122933
|
+
console.error(source_default.dim(err.stderr));
|
|
122934
|
+
process.exit(1);
|
|
122935
|
+
}
|
|
122936
|
+
throw err;
|
|
122937
|
+
}
|
|
122938
|
+
}
|
|
122939
|
+
if (did === 0) {
|
|
122940
|
+
console.error(source_default.yellow(`Skill "${skill}" has neither requirements.txt nor package.json \u2014 nothing to rebuild.`));
|
|
122941
|
+
process.exit(1);
|
|
122942
|
+
}
|
|
122453
122943
|
});
|
|
122454
122944
|
}
|
|
122455
122945
|
|
|
122946
|
+
// src/cli/workspace.ts
|
|
122947
|
+
init_helpers();
|
|
122948
|
+
init_loader();
|
|
122949
|
+
import { existsSync as existsSync106 } from "node:fs";
|
|
122950
|
+
import { resolve as resolve56, sep as sep7 } from "node:path";
|
|
122951
|
+
import { spawnSync as spawnSync23 } from "node:child_process";
|
|
122952
|
+
|
|
122456
122953
|
// src/agents/memory-search.ts
|
|
122457
122954
|
import { readFile as readFile3, readdir, realpath, stat as stat2 } from "node:fs/promises";
|
|
122458
122955
|
import path9 from "node:path";
|
|
@@ -122730,8 +123227,8 @@ function registerWorkspaceCommand(program3) {
|
|
|
122730
123227
|
const dir = resolveAgentWorkspaceDirOrExit(program3, agentName);
|
|
122731
123228
|
if (!dir)
|
|
122732
123229
|
return;
|
|
122733
|
-
const resolvedWorkspace =
|
|
122734
|
-
const target =
|
|
123230
|
+
const resolvedWorkspace = resolve56(dir);
|
|
123231
|
+
const target = resolve56(resolvedWorkspace, file ?? "AGENTS.md");
|
|
122735
123232
|
if (!isInsideWorkspace(resolvedWorkspace, target)) {
|
|
122736
123233
|
process.stderr.write(`workspace edit: refusing path traversal outside workspace dir (${target})
|
|
122737
123234
|
`);
|
|
@@ -122799,8 +123296,8 @@ function registerWorkspaceCommand(program3) {
|
|
|
122799
123296
|
const dir = resolveAgentWorkspaceDirOrExit(program3, agentName);
|
|
122800
123297
|
if (!dir)
|
|
122801
123298
|
return;
|
|
122802
|
-
const gitDir =
|
|
122803
|
-
if (!
|
|
123299
|
+
const gitDir = resolve56(dir, ".git");
|
|
123300
|
+
if (!existsSync106(gitDir)) {
|
|
122804
123301
|
process.stdout.write(`Workspace is not a git repository. Re-run \`switchroom agent create ${agentName}\` ` + `or manually \`git init\` in ${dir} to enable versioning.
|
|
122805
123302
|
`);
|
|
122806
123303
|
return;
|
|
@@ -122853,8 +123350,8 @@ function registerWorkspaceCommand(program3) {
|
|
|
122853
123350
|
const dir = resolveAgentWorkspaceDirOrExit(program3, agentName);
|
|
122854
123351
|
if (!dir)
|
|
122855
123352
|
return;
|
|
122856
|
-
const gitDir =
|
|
122857
|
-
if (!
|
|
123353
|
+
const gitDir = resolve56(dir, ".git");
|
|
123354
|
+
if (!existsSync106(gitDir)) {
|
|
122858
123355
|
process.stdout.write(`Workspace is not a git repository.
|
|
122859
123356
|
`);
|
|
122860
123357
|
return;
|
|
@@ -122877,9 +123374,9 @@ function resolveAgentWorkspaceDirOrExit(program3, agentName) {
|
|
|
122877
123374
|
return;
|
|
122878
123375
|
}
|
|
122879
123376
|
const agentsDir = resolveAgentsDir(config);
|
|
122880
|
-
const agentDir =
|
|
123377
|
+
const agentDir = resolve56(agentsDir, agentName);
|
|
122881
123378
|
const dir = resolveAgentWorkspaceDir(agentDir);
|
|
122882
|
-
if (!
|
|
123379
|
+
if (!existsSync106(dir)) {
|
|
122883
123380
|
process.stderr.write(`workspace: ${dir} does not exist yet. Run \`switchroom setup\` or \`switchroom agent scaffold ${agentName}\` to seed it.
|
|
122884
123381
|
`);
|
|
122885
123382
|
return;
|
|
@@ -122915,8 +123412,8 @@ function safeParseInt(value, fallback) {
|
|
|
122915
123412
|
init_helpers();
|
|
122916
123413
|
init_loader();
|
|
122917
123414
|
init_merge();
|
|
122918
|
-
import { copyFileSync as copyFileSync14, existsSync as
|
|
122919
|
-
import { join as
|
|
123415
|
+
import { copyFileSync as copyFileSync14, existsSync as existsSync107, readFileSync as readFileSync96, writeFileSync as writeFileSync43 } from "node:fs";
|
|
123416
|
+
import { join as join111, resolve as resolve57 } from "node:path";
|
|
122920
123417
|
init_scaffold();
|
|
122921
123418
|
init_profiles();
|
|
122922
123419
|
init_schema();
|
|
@@ -122931,9 +123428,9 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
122931
123428
|
const profileName = merged.extends ?? DEFAULT_PROFILE;
|
|
122932
123429
|
const profilePath = getProfilePath(profileName);
|
|
122933
123430
|
const agentsDir = resolveAgentsDir(config);
|
|
122934
|
-
const agentDir =
|
|
123431
|
+
const agentDir = resolve57(agentsDir, agentName);
|
|
122935
123432
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
122936
|
-
if (!
|
|
123433
|
+
if (!existsSync107(workspaceDir)) {
|
|
122937
123434
|
console.error(`soul: ${workspaceDir} does not exist yet. Run \`switchroom setup\` ` + `or \`switchroom agent scaffold ${agentName}\` to seed it.`);
|
|
122938
123435
|
process.exit(1);
|
|
122939
123436
|
}
|
|
@@ -122942,7 +123439,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
122942
123439
|
profileName,
|
|
122943
123440
|
profilePath,
|
|
122944
123441
|
workspaceDir,
|
|
122945
|
-
soulPath:
|
|
123442
|
+
soulPath: join111(workspaceDir, "SOUL.md"),
|
|
122946
123443
|
soul: merged.soul
|
|
122947
123444
|
};
|
|
122948
123445
|
}
|
|
@@ -122959,11 +123456,11 @@ function registerSoulCommand(program3) {
|
|
|
122959
123456
|
const t = resolveSoulTargetOrExit(program3, agentName);
|
|
122960
123457
|
if (!t)
|
|
122961
123458
|
return;
|
|
122962
|
-
if (!
|
|
123459
|
+
if (!existsSync107(t.soulPath)) {
|
|
122963
123460
|
console.error(`soul: ${t.soulPath} does not exist yet \u2014 run ` + `\`switchroom soul reset ${agentName}\` to seed it.`);
|
|
122964
123461
|
process.exit(1);
|
|
122965
123462
|
}
|
|
122966
|
-
process.stdout.write(
|
|
123463
|
+
process.stdout.write(readFileSync96(t.soulPath, "utf-8"));
|
|
122967
123464
|
}));
|
|
122968
123465
|
cmd.command("reset <agent>").description("Re-seed SOUL.md from the agent's current profile " + "(backs the existing file up to SOUL.md.bak first)").option("-y, --yes", "Skip the confirmation prompt").action(withConfigError(async (agentName, opts) => {
|
|
122969
123466
|
const t = resolveSoulTargetOrExit(program3, agentName);
|
|
@@ -122974,7 +123471,7 @@ function registerSoulCommand(program3) {
|
|
|
122974
123471
|
console.error(`soul: profile "${t.profileName}" ships no SOUL.md.hbs \u2014 ` + `nothing to re-seed from.`);
|
|
122975
123472
|
process.exit(1);
|
|
122976
123473
|
}
|
|
122977
|
-
const exists =
|
|
123474
|
+
const exists = existsSync107(t.soulPath);
|
|
122978
123475
|
if (exists && !opts.yes) {
|
|
122979
123476
|
if (!isInteractive()) {
|
|
122980
123477
|
console.error(`soul: ${t.soulPath} already exists. Re-run with --yes to ` + `replace it (the current file is backed up to SOUL.md.bak).`);
|
|
@@ -122989,7 +123486,7 @@ function registerSoulCommand(program3) {
|
|
|
122989
123486
|
let backupPath;
|
|
122990
123487
|
if (exists) {
|
|
122991
123488
|
backupPath = `${t.soulPath}.bak`;
|
|
122992
|
-
if (
|
|
123489
|
+
if (existsSync107(backupPath)) {
|
|
122993
123490
|
backupPath = `${t.soulPath}.bak.${Date.now()}`;
|
|
122994
123491
|
}
|
|
122995
123492
|
copyFileSync14(t.soulPath, backupPath);
|
|
@@ -123005,279 +123502,6 @@ function registerSoulCommand(program3) {
|
|
|
123005
123502
|
}));
|
|
123006
123503
|
}
|
|
123007
123504
|
|
|
123008
|
-
// src/cli/debug.ts
|
|
123009
|
-
init_helpers();
|
|
123010
|
-
init_loader();
|
|
123011
|
-
import { existsSync as existsSync107, readFileSync as readFileSync96, readdirSync as readdirSync36, statSync as statSync62 } from "node:fs";
|
|
123012
|
-
import { resolve as resolve57, join as join111 } from "node:path";
|
|
123013
|
-
import { createHash as createHash21 } from "node:crypto";
|
|
123014
|
-
init_merge();
|
|
123015
|
-
init_hindsight2();
|
|
123016
|
-
function formatBytes(bytes) {
|
|
123017
|
-
return `${bytes.toLocaleString()} bytes`;
|
|
123018
|
-
}
|
|
123019
|
-
function estimateTokens(bytes) {
|
|
123020
|
-
return Math.round(bytes / 3.7);
|
|
123021
|
-
}
|
|
123022
|
-
function readMcpServerNames(agentDir) {
|
|
123023
|
-
const mcpPath = join111(agentDir, ".mcp.json");
|
|
123024
|
-
if (!existsSync107(mcpPath))
|
|
123025
|
-
return [];
|
|
123026
|
-
try {
|
|
123027
|
-
const parsed = JSON.parse(readFileSync96(mcpPath, "utf-8"));
|
|
123028
|
-
return Object.keys(parsed.mcpServers ?? {});
|
|
123029
|
-
} catch {
|
|
123030
|
-
return null;
|
|
123031
|
-
}
|
|
123032
|
-
}
|
|
123033
|
-
function sha2562(content) {
|
|
123034
|
-
return createHash21("sha256").update(content).digest("hex").slice(0, 16);
|
|
123035
|
-
}
|
|
123036
|
-
function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
123037
|
-
const projectsDir = join111(claudeConfigDir, "projects");
|
|
123038
|
-
if (!existsSync107(projectsDir))
|
|
123039
|
-
return;
|
|
123040
|
-
try {
|
|
123041
|
-
const entries = readdirSync36(projectsDir, { withFileTypes: true });
|
|
123042
|
-
let latest;
|
|
123043
|
-
for (const entry of entries) {
|
|
123044
|
-
if (!entry.isDirectory())
|
|
123045
|
-
continue;
|
|
123046
|
-
const projectPath = join111(projectsDir, entry.name);
|
|
123047
|
-
const transcriptPath = join111(projectPath, "transcript.jsonl");
|
|
123048
|
-
if (!existsSync107(transcriptPath))
|
|
123049
|
-
continue;
|
|
123050
|
-
const stat3 = statSync62(transcriptPath);
|
|
123051
|
-
if (!latest || stat3.mtimeMs > latest.mtime) {
|
|
123052
|
-
latest = { path: transcriptPath, mtime: stat3.mtimeMs };
|
|
123053
|
-
}
|
|
123054
|
-
}
|
|
123055
|
-
return latest?.path;
|
|
123056
|
-
} catch {
|
|
123057
|
-
return;
|
|
123058
|
-
}
|
|
123059
|
-
}
|
|
123060
|
-
function extractLatestUserMessage(transcriptPath) {
|
|
123061
|
-
try {
|
|
123062
|
-
const content = readFileSync96(transcriptPath, "utf-8");
|
|
123063
|
-
const lines = content.trim().split(`
|
|
123064
|
-
`).filter(Boolean);
|
|
123065
|
-
for (let i2 = lines.length - 1;i2 >= 0; i2--) {
|
|
123066
|
-
const line = lines[i2];
|
|
123067
|
-
try {
|
|
123068
|
-
const event = JSON.parse(line);
|
|
123069
|
-
if (event.type === "message" && event.role === "user" && typeof event.content === "string") {
|
|
123070
|
-
const timestamp = event.timestamp ? new Date(event.timestamp).toLocaleString() : "unknown";
|
|
123071
|
-
return { text: event.content, timestamp };
|
|
123072
|
-
}
|
|
123073
|
-
} catch {
|
|
123074
|
-
continue;
|
|
123075
|
-
}
|
|
123076
|
-
}
|
|
123077
|
-
} catch {
|
|
123078
|
-
return;
|
|
123079
|
-
}
|
|
123080
|
-
}
|
|
123081
|
-
function buildProgressUpdateGuidance() {
|
|
123082
|
-
return `## Progress updates (human-style check-ins)
|
|
123083
|
-
|
|
123084
|
-
You're talking to a human colleague on Telegram. Alongside the emoji status
|
|
123085
|
-
ladder, send a short \`progress_update\` at inflection points, the moments a
|
|
123086
|
-
senior colleague would ping the person who asked them to do something:
|
|
123087
|
-
|
|
123088
|
-
- **Plan formed:** "Got it. Going to do X first, then Y, then Z."
|
|
123089
|
-
- **Pivot or blocker:** "First approach didn't work because <reason>. Trying
|
|
123090
|
-
<alternative> instead."
|
|
123091
|
-
- **Chunk finished:** "Done with X. Starting Y now."
|
|
123092
|
-
|
|
123093
|
-
Keep them short (one or two sentences). Don't narrate every step, the pinned
|
|
123094
|
-
progress card shows that for free. Don't send an update on a trivial one-shot
|
|
123095
|
-
task. Send them when a colleague would genuinely want to know what's happening.
|
|
123096
|
-
|
|
123097
|
-
Final answers still go through \`reply\` as usual,
|
|
123098
|
-
\`progress_update\` is only for mid-turn check-ins.`;
|
|
123099
|
-
}
|
|
123100
|
-
function registerDebugCommand(program3) {
|
|
123101
|
-
const cmd = program3.command("debug", { hidden: true }).description("Observability tools for inspecting agent prompt layering [advanced]");
|
|
123102
|
-
cmd.command("turn <agent>").description("Dump the exact prompt layering the model saw on the most recent turn").option("--last <n>", "Show N-th most recent turn instead of latest", "1").action(withConfigError(async (agentName, opts) => {
|
|
123103
|
-
const config = getConfig(program3);
|
|
123104
|
-
const agentConfig = config.agents[agentName];
|
|
123105
|
-
if (!agentConfig) {
|
|
123106
|
-
console.error(`Agent '${agentName}' not found in switchroom.yaml`);
|
|
123107
|
-
process.exit(1);
|
|
123108
|
-
}
|
|
123109
|
-
const agentsDir = resolveAgentsDir(config);
|
|
123110
|
-
const agentDir = resolve57(agentsDir, agentName);
|
|
123111
|
-
if (!existsSync107(agentDir)) {
|
|
123112
|
-
console.error(`Agent directory not found: ${agentDir}`);
|
|
123113
|
-
process.exit(1);
|
|
123114
|
-
}
|
|
123115
|
-
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
123116
|
-
const claudeConfigDir = join111(agentDir, ".claude");
|
|
123117
|
-
const claudeMdPath2 = join111(agentDir, "CLAUDE.md");
|
|
123118
|
-
const soulMdPath = join111(agentDir, "SOUL.md");
|
|
123119
|
-
const workspaceSoulMdPath = join111(workspaceDir, "SOUL.md");
|
|
123120
|
-
const handoffPath = join111(agentDir, ".handoff.md");
|
|
123121
|
-
const lastN = parseInt(opts.last, 10);
|
|
123122
|
-
if (isNaN(lastN) || lastN < 1) {
|
|
123123
|
-
console.error("--last must be a positive integer");
|
|
123124
|
-
process.exit(1);
|
|
123125
|
-
}
|
|
123126
|
-
if (lastN > 1) {
|
|
123127
|
-
console.error("Note: --last N where N > 1 not yet implemented (only latest turn supported)");
|
|
123128
|
-
process.exit(1);
|
|
123129
|
-
}
|
|
123130
|
-
console.log(`=== Debug Turn Dump: ${agentName} ===
|
|
123131
|
-
`);
|
|
123132
|
-
console.log(`=== Append System Prompt (stable) ===
|
|
123133
|
-
`);
|
|
123134
|
-
const resolved = resolveAgentConfig(config.defaults, config.profiles, agentConfig);
|
|
123135
|
-
const useHotReloadStable = resolved.channels?.telegram?.hotReloadStable === true;
|
|
123136
|
-
const stableResult = await buildStableBootstrapPrompt({
|
|
123137
|
-
workspaceDir,
|
|
123138
|
-
budget: { warningMode: "off" }
|
|
123139
|
-
});
|
|
123140
|
-
if (useHotReloadStable) {
|
|
123141
|
-
console.log(`-- Workspace Stable Render (not used \u2014 stable content is in per-turn hook) --`);
|
|
123142
|
-
console.log();
|
|
123143
|
-
} else if (stableResult.concatenated.trim().length > 0) {
|
|
123144
|
-
console.log(`-- Workspace Stable Render (${formatBytes(stableResult.concatenated.length)}) --`);
|
|
123145
|
-
console.log(stableResult.concatenated);
|
|
123146
|
-
console.log();
|
|
123147
|
-
} else {
|
|
123148
|
-
console.log("-- Workspace Stable Render (0 bytes, not set up) --");
|
|
123149
|
-
console.log();
|
|
123150
|
-
}
|
|
123151
|
-
const useSwitchroomPlugin = usesSwitchroomTelegramPlugin(resolved);
|
|
123152
|
-
const progressGuidance = useSwitchroomPlugin ? buildProgressUpdateGuidance() : "";
|
|
123153
|
-
if (progressGuidance.length > 0) {
|
|
123154
|
-
console.log(`-- Progress Updates Guidance (${formatBytes(progressGuidance.length)}) --`);
|
|
123155
|
-
console.log(progressGuidance);
|
|
123156
|
-
console.log();
|
|
123157
|
-
}
|
|
123158
|
-
const baseSystemPromptAppend = resolved.system_prompt_append ?? "";
|
|
123159
|
-
if (baseSystemPromptAppend.trim().length > 0) {
|
|
123160
|
-
console.log(`-- User System Prompt Append (${formatBytes(baseSystemPromptAppend.length)}) --`);
|
|
123161
|
-
console.log(baseSystemPromptAppend);
|
|
123162
|
-
console.log();
|
|
123163
|
-
}
|
|
123164
|
-
console.log(`=== Append System Prompt (per-session) ===
|
|
123165
|
-
`);
|
|
123166
|
-
const handoffContent = existsSync107(handoffPath) ? readFileSync96(handoffPath, "utf-8") : "";
|
|
123167
|
-
if (handoffContent.trim().length > 0) {
|
|
123168
|
-
console.log(`-- Handoff Briefing (${formatBytes(handoffContent.length)}) --`);
|
|
123169
|
-
console.log(handoffContent);
|
|
123170
|
-
console.log();
|
|
123171
|
-
} else {
|
|
123172
|
-
console.log("-- Handoff Briefing (0 bytes, no prior session) --");
|
|
123173
|
-
console.log();
|
|
123174
|
-
}
|
|
123175
|
-
console.log(`=== CLAUDE.md (auto-loaded by Claude Code) ===
|
|
123176
|
-
`);
|
|
123177
|
-
const claudeMdContent = existsSync107(claudeMdPath2) ? readFileSync96(claudeMdPath2, "utf-8") : "";
|
|
123178
|
-
if (claudeMdContent.trim().length > 0) {
|
|
123179
|
-
console.log(`(${formatBytes(claudeMdContent.length)})`);
|
|
123180
|
-
console.log(claudeMdContent);
|
|
123181
|
-
console.log();
|
|
123182
|
-
} else {
|
|
123183
|
-
console.log("(0 bytes, not present)");
|
|
123184
|
-
console.log();
|
|
123185
|
-
}
|
|
123186
|
-
console.log(`=== Persona (SOUL.md) ===
|
|
123187
|
-
`);
|
|
123188
|
-
const soulMdContent = existsSync107(soulMdPath) ? readFileSync96(soulMdPath, "utf-8") : existsSync107(workspaceSoulMdPath) ? readFileSync96(workspaceSoulMdPath, "utf-8") : "";
|
|
123189
|
-
if (soulMdContent.trim().length > 0) {
|
|
123190
|
-
console.log(`(${formatBytes(soulMdContent.length)})`);
|
|
123191
|
-
console.log(soulMdContent);
|
|
123192
|
-
console.log();
|
|
123193
|
-
} else {
|
|
123194
|
-
console.log("(0 bytes, stale placeholder \u2014 Phase 2 item: single source of truth for persona)");
|
|
123195
|
-
console.log();
|
|
123196
|
-
}
|
|
123197
|
-
console.log(`=== Per-Turn Injections (UserPromptSubmit) ===
|
|
123198
|
-
`);
|
|
123199
|
-
if (useHotReloadStable) {
|
|
123200
|
-
if (stableResult.concatenated.trim().length > 0) {
|
|
123201
|
-
console.log(`-- Workspace Stable (hot-reload hook): ${formatBytes(stableResult.concatenated.length)} --`);
|
|
123202
|
-
console.log(stableResult.concatenated);
|
|
123203
|
-
console.log();
|
|
123204
|
-
} else {
|
|
123205
|
-
console.log("-- Workspace Stable (hot-reload hook): 0 bytes, not set up --");
|
|
123206
|
-
console.log();
|
|
123207
|
-
}
|
|
123208
|
-
}
|
|
123209
|
-
const dynamicResult = await buildDynamicBootstrapPrompt({
|
|
123210
|
-
workspaceDir,
|
|
123211
|
-
budget: { warningMode: "off" }
|
|
123212
|
-
});
|
|
123213
|
-
if (dynamicResult.concatenated.trim().length > 0) {
|
|
123214
|
-
console.log(`-- Workspace Dynamic: fired, ${formatBytes(dynamicResult.concatenated.length)} --`);
|
|
123215
|
-
console.log(dynamicResult.concatenated);
|
|
123216
|
-
console.log();
|
|
123217
|
-
} else {
|
|
123218
|
-
console.log("-- Workspace Dynamic: no content (MEMORY.md and daily notes empty or missing) --");
|
|
123219
|
-
console.log();
|
|
123220
|
-
}
|
|
123221
|
-
const hindsightEnabled = isHindsightEnabled(config) && agentConfig.memory?.auto_recall !== false;
|
|
123222
|
-
if (hindsightEnabled) {
|
|
123223
|
-
console.log("-- Hindsight Recall: enabled (exact content unavailable, check hindsight logs) --");
|
|
123224
|
-
console.log();
|
|
123225
|
-
} else {
|
|
123226
|
-
console.log("-- Hindsight Recall: disabled --");
|
|
123227
|
-
console.log();
|
|
123228
|
-
}
|
|
123229
|
-
console.log(`=== User Message (latest turn) ===
|
|
123230
|
-
`);
|
|
123231
|
-
const transcriptPath = findLatestTranscriptJsonl(claudeConfigDir);
|
|
123232
|
-
const userMessage = transcriptPath ? extractLatestUserMessage(transcriptPath) : undefined;
|
|
123233
|
-
if (userMessage) {
|
|
123234
|
-
console.log(`(Turn timestamp: ${userMessage.timestamp})`);
|
|
123235
|
-
console.log(userMessage.text);
|
|
123236
|
-
console.log();
|
|
123237
|
-
} else {
|
|
123238
|
-
console.log("(unavailable: no transcript found or transcript empty)");
|
|
123239
|
-
console.log();
|
|
123240
|
-
}
|
|
123241
|
-
console.log(`=== Totals ===
|
|
123242
|
-
`);
|
|
123243
|
-
const stableBytes = stableResult.concatenated.length + progressGuidance.length + baseSystemPromptAppend.length;
|
|
123244
|
-
const perSessionBytes = handoffContent.length;
|
|
123245
|
-
const claudeMdBytes = claudeMdContent.length;
|
|
123246
|
-
const soulMdBytes = soulMdContent.length;
|
|
123247
|
-
const perTurnBytes = dynamicResult.concatenated.length;
|
|
123248
|
-
const userBytes = userMessage?.text.length ?? 0;
|
|
123249
|
-
const fleetDir = join111(agentsDir, "..", "fleet");
|
|
123250
|
-
const fleetInvPath = join111(fleetDir, "switchroom-invariants.md");
|
|
123251
|
-
const fleetClaudePath = join111(fleetDir, "CLAUDE.md");
|
|
123252
|
-
const fleetInvBytes = existsSync107(fleetInvPath) ? readFileSync96(fleetInvPath, "utf-8").length : 0;
|
|
123253
|
-
const fleetClaudeBytes = existsSync107(fleetClaudePath) ? readFileSync96(fleetClaudePath, "utf-8").length : 0;
|
|
123254
|
-
const fleetBytes = fleetInvBytes + fleetClaudeBytes;
|
|
123255
|
-
const totalBytes = stableBytes + perSessionBytes + claudeMdBytes + fleetBytes + perTurnBytes + userBytes;
|
|
123256
|
-
console.log(`Stable prefix: ${formatBytes(stableBytes).padEnd(20)} (cache-hot; includes SOUL.md ${soulMdBytes.toLocaleString()}B)`);
|
|
123257
|
-
console.log(`Per-session: ${formatBytes(perSessionBytes).padEnd(20)} (cache-warm until next session)`);
|
|
123258
|
-
console.log(`CLAUDE.md (cwd): ${formatBytes(claudeMdBytes).padEnd(20)} (cache-hot)`);
|
|
123259
|
-
console.log(`Fleet invariants: ${formatBytes(fleetBytes).padEnd(20)} (--add-dir, cache-hot)`);
|
|
123260
|
-
console.log(`Per-turn: ${formatBytes(perTurnBytes).padEnd(20)} (never cached \u2014 the real per-turn $ cost)`);
|
|
123261
|
-
console.log(`User message: ${formatBytes(userBytes).padEnd(20)}`);
|
|
123262
|
-
console.log(`Authored text: ${formatBytes(totalBytes).padEnd(20)} (~${estimateTokens(totalBytes).toLocaleString()} tokens est.)`);
|
|
123263
|
-
const mcpServers = readMcpServerNames(agentDir);
|
|
123264
|
-
const mcpCount = mcpServers?.length ?? null;
|
|
123265
|
-
const mcpEstK = mcpCount != null ? mcpCount * 3 : null;
|
|
123266
|
-
const mcpLabel = mcpCount != null ? `${mcpCount} servers` : "(unreadable \u2014 agent-private .mcp.json)";
|
|
123267
|
-
const mcpEstLabel = mcpEstK != null ? `~${mcpEstK.toLocaleString()}k tok` : "~30k tok (audit est.)";
|
|
123268
|
-
console.log(`MCP tool surface: ${mcpLabel.padEnd(20)} (NOT counted above; ~3k tok/server \u2248 ${mcpEstLabel} \u2014 deferred under tool search)`);
|
|
123269
|
-
if (mcpServers && mcpServers.length > 0) {
|
|
123270
|
-
console.log(` [${mcpServers.join(", ")}]`);
|
|
123271
|
-
}
|
|
123272
|
-
const floorMcp = mcpEstK != null ? `~${mcpEstK.toLocaleString()}k` : "~30k";
|
|
123273
|
-
console.log(`Per-turn FLOOR: ${`~${estimateTokens(totalBytes).toLocaleString()} + ${floorMcp} MCP + ~13k CLI-base`.padEnd(20)} tokens est. (before the user msg, recall, or tool results)`);
|
|
123274
|
-
const stableCacheInput = stableResult.concatenated + progressGuidance + baseSystemPromptAppend;
|
|
123275
|
-
const stableHash = sha2562(stableCacheInput);
|
|
123276
|
-
console.log(`Cache stable hash: sha256:${stableHash}`);
|
|
123277
|
-
console.log();
|
|
123278
|
-
}));
|
|
123279
|
-
}
|
|
123280
|
-
|
|
123281
123505
|
// src/cli/worktree.ts
|
|
123282
123506
|
init_source();
|
|
123283
123507
|
init_claim();
|