switchroom 0.13.50 → 0.13.52
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 +161 -159
- package/package.json +1 -1
- package/profiles/_base/start.sh.hbs +30 -4
- package/profiles/_shared/agent-self-service.md.hbs +12 -22
- package/profiles/coding/CLAUDE.md.hbs +1 -1
- package/profiles/default/CLAUDE.md +122 -0
- package/profiles/default/CLAUDE.md.hbs +8 -1
- package/profiles/executive-assistant/CLAUDE.md.hbs +1 -1
- package/profiles/health-coach/CLAUDE.md.hbs +1 -1
- package/telegram-plugin/dist/gateway/gateway.js +4 -4
package/dist/cli/switchroom.js
CHANGED
|
@@ -23253,6 +23253,9 @@ function emitAgentService(lines, a, imageTag, buildMode, buildContext, homePrefi
|
|
|
23253
23253
|
if (existsSync12(`${hostHomeForChecks}/.switchroom/mcp-launchers`)) {
|
|
23254
23254
|
lines.push(` - ${homePrefix}/.switchroom/mcp-launchers:${homePrefix}/.switchroom/mcp-launchers:ro`);
|
|
23255
23255
|
}
|
|
23256
|
+
if (existsSync12(`${hostHomeForChecks}/.switchroom/fleet`)) {
|
|
23257
|
+
lines.push(` - ${homePrefix}/.switchroom/fleet:${homePrefix}/.switchroom/fleet:ro`);
|
|
23258
|
+
}
|
|
23256
23259
|
if (existsSync12(`${hostHomeForChecks}/.switchroom/credentials/${a.name}`)) {
|
|
23257
23260
|
lines.push(` - ${homePrefix}/.switchroom/credentials/${a.name}:${homePrefix}/.switchroom/credentials:ro`);
|
|
23258
23261
|
}
|
|
@@ -23263,6 +23266,12 @@ function emitAgentService(lines, a, imageTag, buildMode, buildContext, homePrefi
|
|
|
23263
23266
|
mkdirSync8(`${hostHomeForChecks}/.switchroom/agents/${a.name}/schedule.d`, { recursive: true });
|
|
23264
23267
|
} catch {}
|
|
23265
23268
|
lines.push(` - ${homePrefix}/.switchroom/audit/${a.name}:${homePrefix}/.switchroom/audit/${a.name}:rw`);
|
|
23269
|
+
if (existsSync12(`${hostHomeForChecks}/.switchroom-config`)) {
|
|
23270
|
+
try {
|
|
23271
|
+
mkdirSync8(`${hostHomeForChecks}/.switchroom-config/agents/${a.name}/personal-skills`, { recursive: true });
|
|
23272
|
+
} catch {}
|
|
23273
|
+
lines.push(` - ${homePrefix}/.switchroom-config/agents/${a.name}/personal-skills:${homePrefix}/.switchroom-config/agents/${a.name}/personal-skills:rw`);
|
|
23274
|
+
}
|
|
23266
23275
|
if (bundledSkillsPoolDir && existsSync12(bundledSkillsPoolDir) && !bundledSkillsPoolDir.startsWith(`${hostHomeForChecks}/.switchroom/skills`)) {
|
|
23267
23276
|
lines.push(` - ${bundledSkillsPoolDir}:${bundledSkillsPoolDir}:ro`);
|
|
23268
23277
|
}
|
|
@@ -47536,7 +47545,7 @@ __export(exports_server2, {
|
|
|
47536
47545
|
TOOLS: () => TOOLS2
|
|
47537
47546
|
});
|
|
47538
47547
|
import { randomBytes as randomBytes14 } from "node:crypto";
|
|
47539
|
-
import { existsSync as existsSync78, readFileSync as
|
|
47548
|
+
import { existsSync as existsSync78, readFileSync as readFileSync64 } from "node:fs";
|
|
47540
47549
|
function selfSocketPath() {
|
|
47541
47550
|
return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
|
|
47542
47551
|
}
|
|
@@ -47714,7 +47723,7 @@ function getLastUpdateApplyStatus() {
|
|
|
47714
47723
|
}
|
|
47715
47724
|
let raw;
|
|
47716
47725
|
try {
|
|
47717
|
-
raw =
|
|
47726
|
+
raw = readFileSync64(path8, "utf-8");
|
|
47718
47727
|
} catch (err2) {
|
|
47719
47728
|
return errorText2(`get_status: failed to read audit log at ${path8}: ${err2.message}`);
|
|
47720
47729
|
}
|
|
@@ -47947,8 +47956,8 @@ var {
|
|
|
47947
47956
|
} = import__.default;
|
|
47948
47957
|
|
|
47949
47958
|
// src/build-info.ts
|
|
47950
|
-
var VERSION = "0.13.
|
|
47951
|
-
var COMMIT_SHA = "
|
|
47959
|
+
var VERSION = "0.13.52";
|
|
47960
|
+
var COMMIT_SHA = "3d68efa2";
|
|
47952
47961
|
|
|
47953
47962
|
// src/cli/agent.ts
|
|
47954
47963
|
init_source();
|
|
@@ -48516,10 +48525,90 @@ Example response shapes:
|
|
|
48516
48525
|
\`docker/Dockerfile.agent\` and rebuild the agent image."
|
|
48517
48526
|
- "I tried to clone into \`/workspace\` \u2014 that path doesn't exist in my
|
|
48518
48527
|
sandbox. Cloning into \`$HOME/workspace\` instead."`;
|
|
48528
|
+
var TELEGRAM_GUIDANCE = `## Talking to a human on Telegram
|
|
48529
|
+
|
|
48530
|
+
There is a real person on the other end. Every turn should feel like
|
|
48531
|
+
messaging a capable colleague \u2014 not a tool emitting output. Five beats:
|
|
48532
|
+
|
|
48533
|
+
1. **Acknowledge first.** Unless your whole reply is a single short
|
|
48534
|
+
sentence you can send right now, your FIRST action this turn is a
|
|
48535
|
+
brief \`reply\` in your own voice \u2014 "on it", "good question, one
|
|
48536
|
+
sec", "let me dig in" \u2014 before any tool call and before you
|
|
48537
|
+
compose the full answer. This holds even for a pure-thinking
|
|
48538
|
+
answer: if it will run to a paragraph, ack first. It is the line
|
|
48539
|
+
between a colleague and a black box.
|
|
48540
|
+
2. **Then go quiet and work.** Heads-down is correct \u2014 do NOT narrate
|
|
48541
|
+
every tool call. A typing indicator runs automatically while you
|
|
48542
|
+
work; you do not maintain it.
|
|
48543
|
+
3. **Surface meaningful progress** at genuine inflection points \u2014 a
|
|
48544
|
+
hard step finished, a blocker, a pivot, dispatching a sub-agent, a
|
|
48545
|
+
notably slow wait, a finding worth knowing now. One short \`reply\`,
|
|
48546
|
+
\`disable_notification: true\`.
|
|
48547
|
+
4. **Hand back delegations with synthesis.** When a sub-agent / worker
|
|
48548
|
+
returns, re-enter in YOUR voice \u2014 what it found, and what you are
|
|
48549
|
+
doing next. Never let its raw report stand as your reply. A
|
|
48550
|
+
*background* worker finishes after your turn ends; its result
|
|
48551
|
+
arrives as a fresh \`<channel source="subagent_handback">\` turn \u2014
|
|
48552
|
+
treat that turn as the cue to do exactly this.
|
|
48553
|
+
5. **Deliver the answer** as a final \`reply\`.
|
|
48554
|
+
|
|
48555
|
+
The one thing to avoid is *spam*: a reply on every tool call, on a
|
|
48556
|
+
timer, or repeating what you already said. Responsive and human, never
|
|
48557
|
+
a flood. Going quiet mid-work is fine \u2014 going quiet *instead* of
|
|
48558
|
+
acknowledging, or *instead* of an update at a real milestone, is the
|
|
48559
|
+
black box this exists to prevent.
|
|
48560
|
+
|
|
48561
|
+
Every turn that answers a user message ends with a user-visible
|
|
48562
|
+
\`reply\` (or \`stream_reply\` done=true) \u2014 Telegram is all the user
|
|
48563
|
+
sees; your terminal output never reaches them.`;
|
|
48564
|
+
var MEMORY_GUIDANCE = `## Memory \u2014 proactive, conversational
|
|
48565
|
+
|
|
48566
|
+
You have Hindsight tools: \`mcp__hindsight__sync_retain\`, \`mcp__hindsight__delete_memory\`, \`mcp__hindsight__recall\`, \`mcp__hindsight__reflect\`. Use them without being asked.
|
|
48567
|
+
|
|
48568
|
+
### Retain proactively
|
|
48569
|
+
When the user shares a fact, preference, decision, or plan worth keeping across sessions, call \`sync_retain\` in the same turn. Briefly acknowledge in your reply ("got it, April 2nd anniversary"). Don't narrate the tool call. Skip small talk and transient tool output, the auto-retain hook handles conversation-level signal.
|
|
48570
|
+
|
|
48571
|
+
### Correct proactively
|
|
48572
|
+
When the user corrects you or contradicts a prior memory, call \`delete_memory\` on the wrong entry, then \`sync_retain\` the correction. Acknowledge the correction in one line ("noted, Alice not Bob").
|
|
48573
|
+
|
|
48574
|
+
### Forget proactively
|
|
48575
|
+
When the user asks you to forget something ("forget that", "delete X", "drop what I said about Y"), call \`delete_memory\` for matching entries and confirm what was removed.
|
|
48576
|
+
|
|
48577
|
+
### Inspect proactively
|
|
48578
|
+
When the user asks "what do you know about X / me", "what do you remember about Y", or any memory audit, use \`reflect\` to synthesize an answer across the bank. Return it as honest prose, not a raw dump. If the bank has little on the topic, say so.
|
|
48579
|
+
|
|
48580
|
+
Don't wait for a slash command. Don't ask permission. Memory work is table stakes, like a colleague who takes notes and remembers.`;
|
|
48581
|
+
function renderFleetInvariants() {
|
|
48582
|
+
return [
|
|
48583
|
+
"<!--",
|
|
48584
|
+
" ~/.switchroom/fleet/switchroom-invariants.md",
|
|
48585
|
+
"",
|
|
48586
|
+
" Release-controlled fleet invariants. Every Switchroom agent reads",
|
|
48587
|
+
" this via Claude Code native CLAUDE.md discovery, since the agent's",
|
|
48588
|
+
" `claude` process boots with `--add-dir ~/.switchroom/fleet`.",
|
|
48589
|
+
"",
|
|
48590
|
+
" DO NOT EDIT. `switchroom apply` regenerates this file on every run",
|
|
48591
|
+
" and restores it if it drifts from the release canonical. To extend",
|
|
48592
|
+
" the agent's behaviour fleet-wide, edit ~/.switchroom/fleet/CLAUDE.md",
|
|
48593
|
+
" instead (operator-owned, additive, preserved across applies).",
|
|
48594
|
+
"-->",
|
|
48595
|
+
"",
|
|
48596
|
+
"# Switchroom fleet invariants",
|
|
48597
|
+
"",
|
|
48598
|
+
SANDBOX_GUIDANCE,
|
|
48599
|
+
"",
|
|
48600
|
+
TELEGRAM_GUIDANCE,
|
|
48601
|
+
"",
|
|
48602
|
+
MEMORY_GUIDANCE,
|
|
48603
|
+
""
|
|
48604
|
+
].join(`
|
|
48605
|
+
`);
|
|
48606
|
+
}
|
|
48519
48607
|
function alignAgentUid(name, agentDir, uid, opts = {}) {
|
|
48520
48608
|
const writeOut = opts.writeOut ?? ((s) => process.stdout.write(s));
|
|
48521
48609
|
const logsDir = join8(homedir4(), ".switchroom", "logs", name);
|
|
48522
48610
|
const auditDir = join8(homedir4(), ".switchroom", "audit", name);
|
|
48611
|
+
const configMirrorDir = join8(homedir4(), ".switchroom-config", "agents", name, "personal-skills");
|
|
48523
48612
|
const paths = [];
|
|
48524
48613
|
if (existsSync11(agentDir))
|
|
48525
48614
|
paths.push(agentDir);
|
|
@@ -48527,6 +48616,8 @@ function alignAgentUid(name, agentDir, uid, opts = {}) {
|
|
|
48527
48616
|
paths.push(logsDir);
|
|
48528
48617
|
if (existsSync11(auditDir))
|
|
48529
48618
|
paths.push(auditDir);
|
|
48619
|
+
if (existsSync11(configMirrorDir))
|
|
48620
|
+
paths.push(configMirrorDir);
|
|
48530
48621
|
if (paths.length === 0)
|
|
48531
48622
|
return { chowned: false, paths: [] };
|
|
48532
48623
|
const priors = [];
|
|
@@ -49288,70 +49379,7 @@ function buildWorkspaceContext(args) {
|
|
|
49288
49379
|
return out;
|
|
49289
49380
|
})(),
|
|
49290
49381
|
systemPromptAppendShellQuoted: (() => {
|
|
49291
|
-
const useSwitchroomPlugin = usesSwitchroomTelegramPlugin(agentConfig);
|
|
49292
49382
|
const baseAppend = agentConfig.system_prompt_append ?? "";
|
|
49293
|
-
const telegramGuidance = `## Talking to a human on Telegram
|
|
49294
|
-
|
|
49295
|
-
There is a real person on the other end. Every turn should feel like
|
|
49296
|
-
messaging a capable colleague \u2014 not a tool emitting output. Five beats:
|
|
49297
|
-
|
|
49298
|
-
1. **Acknowledge first.** Unless your whole reply is a single short
|
|
49299
|
-
sentence you can send right now, your FIRST action this turn is a
|
|
49300
|
-
brief \`reply\` in your own voice \u2014 "on it", "good question, one
|
|
49301
|
-
sec", "let me dig in" \u2014 before any tool call and before you
|
|
49302
|
-
compose the full answer. This holds even for a pure-thinking
|
|
49303
|
-
answer: if it will run to a paragraph, ack first. It is the line
|
|
49304
|
-
between a colleague and a black box.
|
|
49305
|
-
2. **Then go quiet and work.** Heads-down is correct \u2014 do NOT narrate
|
|
49306
|
-
every tool call. A typing indicator runs automatically while you
|
|
49307
|
-
work; you do not maintain it.
|
|
49308
|
-
3. **Surface meaningful progress** at genuine inflection points \u2014 a
|
|
49309
|
-
hard step finished, a blocker, a pivot, dispatching a sub-agent, a
|
|
49310
|
-
notably slow wait, a finding worth knowing now. One short \`reply\`,
|
|
49311
|
-
\`disable_notification: true\`.
|
|
49312
|
-
4. **Hand back delegations with synthesis.** When a sub-agent / worker
|
|
49313
|
-
returns, re-enter in YOUR voice \u2014 what it found, and what you are
|
|
49314
|
-
doing next. Never let its raw report stand as your reply. A
|
|
49315
|
-
*background* worker finishes after your turn ends; its result
|
|
49316
|
-
arrives as a fresh \`<channel source="subagent_handback">\` turn \u2014
|
|
49317
|
-
treat that turn as the cue to do exactly this.
|
|
49318
|
-
5. **Deliver the answer** as a final \`reply\`.
|
|
49319
|
-
|
|
49320
|
-
The one thing to avoid is *spam*: a reply on every tool call, on a
|
|
49321
|
-
timer, or repeating what you already said. Responsive and human, never
|
|
49322
|
-
a flood. Going quiet mid-work is fine \u2014 going quiet *instead* of
|
|
49323
|
-
acknowledging, or *instead* of an update at a real milestone, is the
|
|
49324
|
-
black box this exists to prevent.
|
|
49325
|
-
|
|
49326
|
-
Every turn that answers a user message ends with a user-visible
|
|
49327
|
-
\`reply\` (or \`stream_reply\` done=true) \u2014 Telegram is all the user
|
|
49328
|
-
sees; your terminal output never reaches them.`;
|
|
49329
|
-
const memoryGuidance = `## Memory \u2014 proactive, conversational
|
|
49330
|
-
|
|
49331
|
-
You have Hindsight tools: \`mcp__hindsight__sync_retain\`, \`mcp__hindsight__delete_memory\`, \`mcp__hindsight__recall\`, \`mcp__hindsight__reflect\`. Use them without being asked.
|
|
49332
|
-
|
|
49333
|
-
### Retain proactively
|
|
49334
|
-
When the user shares a fact, preference, decision, or plan worth keeping across sessions, call \`sync_retain\` in the same turn. Briefly acknowledge in your reply ("got it, April 2nd anniversary"). Don't narrate the tool call. Skip small talk and transient tool output, the auto-retain hook handles conversation-level signal.
|
|
49335
|
-
|
|
49336
|
-
### Correct proactively
|
|
49337
|
-
When the user corrects you or contradicts a prior memory, call \`delete_memory\` on the wrong entry, then \`sync_retain\` the correction. Acknowledge the correction in one line ("noted, Alice not Bob").
|
|
49338
|
-
|
|
49339
|
-
### Forget proactively
|
|
49340
|
-
When the user asks you to forget something ("forget that", "delete X", "drop what I said about Y"), call \`delete_memory\` for matching entries and confirm what was removed.
|
|
49341
|
-
|
|
49342
|
-
### Inspect proactively
|
|
49343
|
-
When the user asks "what do you know about X / me", "what do you remember about Y", or any memory audit, use \`reflect\` to synthesize an answer across the bank. Return it as honest prose, not a raw dump. If the bank has little on the topic, say so.
|
|
49344
|
-
|
|
49345
|
-
Don't wait for a slash command. Don't ask permission. Memory work is table stakes, like a colleague who takes notes and remembers.`;
|
|
49346
|
-
if (useSwitchroomPlugin) {
|
|
49347
|
-
const parts = [baseAppend, telegramGuidance, memoryGuidance, SANDBOX_GUIDANCE].filter((s) => s.length > 0);
|
|
49348
|
-
const combined = parts.join(`
|
|
49349
|
-
|
|
49350
|
-
---
|
|
49351
|
-
|
|
49352
|
-
`);
|
|
49353
|
-
return shellSingleQuote(combined);
|
|
49354
|
-
}
|
|
49355
49383
|
return baseAppend.length > 0 ? shellSingleQuote(baseAppend) : undefined;
|
|
49356
49384
|
})(),
|
|
49357
49385
|
extraCliArgs: (() => {
|
|
@@ -50252,70 +50280,7 @@ function reconcileAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchr
|
|
|
50252
50280
|
})(),
|
|
50253
50281
|
model: agentConfig.model,
|
|
50254
50282
|
systemPromptAppendShellQuoted: (() => {
|
|
50255
|
-
const useSwitchroomPlugin = usesSwitchroomTelegramPlugin(agentConfig);
|
|
50256
50283
|
const baseAppend = agentConfig.system_prompt_append ?? "";
|
|
50257
|
-
const telegramGuidance = `## Talking to a human on Telegram
|
|
50258
|
-
|
|
50259
|
-
There is a real person on the other end. Every turn should feel like
|
|
50260
|
-
messaging a capable colleague \u2014 not a tool emitting output. Five beats:
|
|
50261
|
-
|
|
50262
|
-
1. **Acknowledge first.** Unless your whole reply is a single short
|
|
50263
|
-
sentence you can send right now, your FIRST action this turn is a
|
|
50264
|
-
brief \`reply\` in your own voice \u2014 "on it", "good question, one
|
|
50265
|
-
sec", "let me dig in" \u2014 before any tool call and before you
|
|
50266
|
-
compose the full answer. This holds even for a pure-thinking
|
|
50267
|
-
answer: if it will run to a paragraph, ack first. It is the line
|
|
50268
|
-
between a colleague and a black box.
|
|
50269
|
-
2. **Then go quiet and work.** Heads-down is correct \u2014 do NOT narrate
|
|
50270
|
-
every tool call. A typing indicator runs automatically while you
|
|
50271
|
-
work; you do not maintain it.
|
|
50272
|
-
3. **Surface meaningful progress** at genuine inflection points \u2014 a
|
|
50273
|
-
hard step finished, a blocker, a pivot, dispatching a sub-agent, a
|
|
50274
|
-
notably slow wait, a finding worth knowing now. One short \`reply\`,
|
|
50275
|
-
\`disable_notification: true\`.
|
|
50276
|
-
4. **Hand back delegations with synthesis.** When a sub-agent / worker
|
|
50277
|
-
returns, re-enter in YOUR voice \u2014 what it found, and what you are
|
|
50278
|
-
doing next. Never let its raw report stand as your reply. A
|
|
50279
|
-
*background* worker finishes after your turn ends; its result
|
|
50280
|
-
arrives as a fresh \`<channel source="subagent_handback">\` turn \u2014
|
|
50281
|
-
treat that turn as the cue to do exactly this.
|
|
50282
|
-
5. **Deliver the answer** as a final \`reply\`.
|
|
50283
|
-
|
|
50284
|
-
The one thing to avoid is *spam*: a reply on every tool call, on a
|
|
50285
|
-
timer, or repeating what you already said. Responsive and human, never
|
|
50286
|
-
a flood. Going quiet mid-work is fine \u2014 going quiet *instead* of
|
|
50287
|
-
acknowledging, or *instead* of an update at a real milestone, is the
|
|
50288
|
-
black box this exists to prevent.
|
|
50289
|
-
|
|
50290
|
-
Every turn that answers a user message ends with a user-visible
|
|
50291
|
-
\`reply\` (or \`stream_reply\` done=true) \u2014 Telegram is all the user
|
|
50292
|
-
sees; your terminal output never reaches them.`;
|
|
50293
|
-
const memoryGuidance = `## Memory \u2014 proactive, conversational
|
|
50294
|
-
|
|
50295
|
-
You have Hindsight tools: \`mcp__hindsight__sync_retain\`, \`mcp__hindsight__delete_memory\`, \`mcp__hindsight__recall\`, \`mcp__hindsight__reflect\`. Use them without being asked.
|
|
50296
|
-
|
|
50297
|
-
### Retain proactively
|
|
50298
|
-
When the user shares a fact, preference, decision, or plan worth keeping across sessions, call \`sync_retain\` in the same turn. Briefly acknowledge in your reply ("got it, April 2nd anniversary"). Don't narrate the tool call. Skip small talk and transient tool output, the auto-retain hook handles conversation-level signal.
|
|
50299
|
-
|
|
50300
|
-
### Correct proactively
|
|
50301
|
-
When the user corrects you or contradicts a prior memory, call \`delete_memory\` on the wrong entry, then \`sync_retain\` the correction. Acknowledge the correction in one line ("noted, Alice not Bob").
|
|
50302
|
-
|
|
50303
|
-
### Forget proactively
|
|
50304
|
-
When the user asks you to forget something ("forget that", "delete X", "drop what I said about Y"), call \`delete_memory\` for matching entries and confirm what was removed.
|
|
50305
|
-
|
|
50306
|
-
### Inspect proactively
|
|
50307
|
-
When the user asks "what do you know about X / me", "what do you remember about Y", or any memory audit, use \`reflect\` to synthesize an answer across the bank. Return it as honest prose, not a raw dump. If the bank has little on the topic, say so.
|
|
50308
|
-
|
|
50309
|
-
Don't wait for a slash command. Don't ask permission. Memory work is table stakes, like a colleague who takes notes and remembers.`;
|
|
50310
|
-
if (useSwitchroomPlugin) {
|
|
50311
|
-
const parts = [baseAppend, telegramGuidance, memoryGuidance, SANDBOX_GUIDANCE].filter((s) => s.length > 0);
|
|
50312
|
-
const combined = parts.join(`
|
|
50313
|
-
|
|
50314
|
-
---
|
|
50315
|
-
|
|
50316
|
-
`);
|
|
50317
|
-
return shellSingleQuote(combined);
|
|
50318
|
-
}
|
|
50319
50284
|
return baseAppend.length > 0 ? shellSingleQuote(baseAppend) : undefined;
|
|
50320
50285
|
})(),
|
|
50321
50286
|
extraCliArgs: (() => {
|
|
@@ -74048,7 +74013,7 @@ function registerDriveMcpLauncherCommand(program3) {
|
|
|
74048
74013
|
|
|
74049
74014
|
// src/cli/apply.ts
|
|
74050
74015
|
init_source();
|
|
74051
|
-
import { accessSync as accessSync3, chownSync as chownSync4, constants as fsConstants6, copyFileSync as copyFileSync11, existsSync as existsSync68, mkdirSync as mkdirSync36, readdirSync as readdirSync25, renameSync as renameSync13, writeFileSync as writeFileSync32 } from "node:fs";
|
|
74016
|
+
import { accessSync as accessSync3, chownSync as chownSync4, constants as fsConstants6, copyFileSync as copyFileSync11, existsSync as existsSync68, mkdirSync as mkdirSync36, readFileSync as readFileSync55, readdirSync as readdirSync25, renameSync as renameSync13, writeFileSync as writeFileSync32 } from "node:fs";
|
|
74052
74017
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
74053
74018
|
import { spawnSync as childSpawnSync } from "node:child_process";
|
|
74054
74019
|
import readline from "node:readline";
|
|
@@ -74762,6 +74727,9 @@ async function ensureHostMountSources(config) {
|
|
|
74762
74727
|
dirs.push(join62(home2, ".switchroom", "logs", name));
|
|
74763
74728
|
dirs.push(join62(home2, ".claude", "projects", name));
|
|
74764
74729
|
dirs.push(join62(home2, ".switchroom", "audit", name));
|
|
74730
|
+
if (existsSync68(join62(home2, ".switchroom-config"))) {
|
|
74731
|
+
dirs.push(join62(home2, ".switchroom-config", "agents", name, "personal-skills"));
|
|
74732
|
+
}
|
|
74765
74733
|
}
|
|
74766
74734
|
for (const dir of dirs) {
|
|
74767
74735
|
await mkdir(dir, { recursive: true });
|
|
@@ -74792,6 +74760,29 @@ async function ensureHostMountSources(config) {
|
|
|
74792
74760
|
chownSync4(tokenPath, uid, uid);
|
|
74793
74761
|
} catch {}
|
|
74794
74762
|
}
|
|
74763
|
+
const fleetDir = join62(home2, ".switchroom", "fleet");
|
|
74764
|
+
await mkdir(fleetDir, { recursive: true });
|
|
74765
|
+
const invariantsPath = join62(fleetDir, "switchroom-invariants.md");
|
|
74766
|
+
const invariantsCanonical = renderFleetInvariants();
|
|
74767
|
+
const invariantsCurrent = existsSync68(invariantsPath) ? readFileSync55(invariantsPath, "utf-8") : null;
|
|
74768
|
+
if (invariantsCurrent !== invariantsCanonical) {
|
|
74769
|
+
writeFileSync32(invariantsPath, invariantsCanonical, { mode: 420 });
|
|
74770
|
+
}
|
|
74771
|
+
const fleetClaudePath = join62(fleetDir, "CLAUDE.md");
|
|
74772
|
+
if (!existsSync68(fleetClaudePath)) {
|
|
74773
|
+
writeFileSync32(fleetClaudePath, [
|
|
74774
|
+
"# Switchroom fleet defaults",
|
|
74775
|
+
"",
|
|
74776
|
+
"Operator-owned fleet brain. Every agent reads this via",
|
|
74777
|
+
"`--add-dir ~/.switchroom/fleet` (Claude Code native CLAUDE.md",
|
|
74778
|
+
"discovery). Additions stack across the fleet; `switchroom apply`",
|
|
74779
|
+
"never clobbers your edits here.",
|
|
74780
|
+
"",
|
|
74781
|
+
"<!-- L2 fleet defaults content lands in switchroom #1855 -->",
|
|
74782
|
+
""
|
|
74783
|
+
].join(`
|
|
74784
|
+
`), { mode: 420 });
|
|
74785
|
+
}
|
|
74795
74786
|
}
|
|
74796
74787
|
function detectComposeV2() {
|
|
74797
74788
|
try {
|
|
@@ -75310,7 +75301,7 @@ function runRedactStdin() {
|
|
|
75310
75301
|
}
|
|
75311
75302
|
|
|
75312
75303
|
// src/cli/status-ask.ts
|
|
75313
|
-
import { readFileSync as
|
|
75304
|
+
import { readFileSync as readFileSync56, existsSync as existsSync69, readdirSync as readdirSync26 } from "node:fs";
|
|
75314
75305
|
import { join as join63 } from "node:path";
|
|
75315
75306
|
import { homedir as homedir36 } from "node:os";
|
|
75316
75307
|
|
|
@@ -75586,7 +75577,7 @@ function runReport(opts) {
|
|
|
75586
75577
|
for (const src of sources) {
|
|
75587
75578
|
let content;
|
|
75588
75579
|
try {
|
|
75589
|
-
content =
|
|
75580
|
+
content = readFileSync56(src.path, "utf-8");
|
|
75590
75581
|
} catch (err) {
|
|
75591
75582
|
process.stderr.write(`status-ask report: cannot read ${src.path}: ${err instanceof Error ? err.message : String(err)}
|
|
75592
75583
|
`);
|
|
@@ -75687,7 +75678,7 @@ import {
|
|
|
75687
75678
|
existsSync as existsSync70,
|
|
75688
75679
|
mkdirSync as mkdirSync37,
|
|
75689
75680
|
appendFileSync as appendFileSync4,
|
|
75690
|
-
readFileSync as
|
|
75681
|
+
readFileSync as readFileSync57
|
|
75691
75682
|
} from "node:fs";
|
|
75692
75683
|
var AUDIT_ROOT = join64(homedir37(), ".switchroom", "audit");
|
|
75693
75684
|
function auditPathFor(agent) {
|
|
@@ -75782,7 +75773,7 @@ function readAuditTail(agent, limit, opts = {}) {
|
|
|
75782
75773
|
return [];
|
|
75783
75774
|
let raw;
|
|
75784
75775
|
try {
|
|
75785
|
-
raw =
|
|
75776
|
+
raw = readFileSync57(path8, "utf-8");
|
|
75786
75777
|
} catch {
|
|
75787
75778
|
return [];
|
|
75788
75779
|
}
|
|
@@ -75943,7 +75934,7 @@ import {
|
|
|
75943
75934
|
mkdirSync as mkdirSync38,
|
|
75944
75935
|
openSync as openSync13,
|
|
75945
75936
|
readdirSync as readdirSync27,
|
|
75946
|
-
readFileSync as
|
|
75937
|
+
readFileSync as readFileSync58,
|
|
75947
75938
|
renameSync as renameSync14,
|
|
75948
75939
|
statSync as statSync28,
|
|
75949
75940
|
unlinkSync as unlinkSync14,
|
|
@@ -76067,7 +76058,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
|
|
|
76067
76058
|
continue;
|
|
76068
76059
|
const full = join65(paths.skillsDir, name);
|
|
76069
76060
|
try {
|
|
76070
|
-
const raw =
|
|
76061
|
+
const raw = readFileSync58(full, "utf-8");
|
|
76071
76062
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
76072
76063
|
out.push({ slug, path: full, raw });
|
|
76073
76064
|
} catch {}
|
|
@@ -76094,7 +76085,7 @@ function listOverlayEntries(agent, opts = {}) {
|
|
|
76094
76085
|
continue;
|
|
76095
76086
|
const full = join65(paths.scheduleDir, name);
|
|
76096
76087
|
try {
|
|
76097
|
-
const raw =
|
|
76088
|
+
const raw = readFileSync58(full, "utf-8");
|
|
76098
76089
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
76099
76090
|
out.push({ slug, path: full, raw });
|
|
76100
76091
|
} catch {}
|
|
@@ -76242,7 +76233,7 @@ import {
|
|
|
76242
76233
|
mkdirSync as mkdirSync39,
|
|
76243
76234
|
openSync as openSync14,
|
|
76244
76235
|
readdirSync as readdirSync28,
|
|
76245
|
-
readFileSync as
|
|
76236
|
+
readFileSync as readFileSync59,
|
|
76246
76237
|
renameSync as renameSync15,
|
|
76247
76238
|
unlinkSync as unlinkSync15,
|
|
76248
76239
|
writeFileSync as writeFileSync33,
|
|
@@ -76306,7 +76297,7 @@ function listPendingScheduleEntries(agent, opts = {}) {
|
|
|
76306
76297
|
if (!existsSync72(yamlPath))
|
|
76307
76298
|
continue;
|
|
76308
76299
|
try {
|
|
76309
|
-
const meta = JSON.parse(
|
|
76300
|
+
const meta = JSON.parse(readFileSync59(metaPath, "utf-8"));
|
|
76310
76301
|
if (meta?.v !== 1 || typeof meta.stage_id !== "string")
|
|
76311
76302
|
continue;
|
|
76312
76303
|
out.push({ stageId: meta.stage_id, agent: meta.agent, yamlPath, metaPath, meta });
|
|
@@ -76345,7 +76336,7 @@ function denyPendingScheduleEntry(opts) {
|
|
|
76345
76336
|
|
|
76346
76337
|
// src/cli/agent-config-write.ts
|
|
76347
76338
|
init_protocol3();
|
|
76348
|
-
import { existsSync as existsSync73, readFileSync as
|
|
76339
|
+
import { existsSync as existsSync73, readFileSync as readFileSync60 } from "node:fs";
|
|
76349
76340
|
var MAX_ENTRIES_PER_AGENT = 20;
|
|
76350
76341
|
var MIN_CRON_INTERVAL_MIN = 5;
|
|
76351
76342
|
function extractCronSmallestGapMin(expr) {
|
|
@@ -76632,7 +76623,7 @@ function scheduleRemove(opts) {
|
|
|
76632
76623
|
let priorContent = null;
|
|
76633
76624
|
try {
|
|
76634
76625
|
if (existsSync73(match.path))
|
|
76635
|
-
priorContent =
|
|
76626
|
+
priorContent = readFileSync60(match.path, "utf-8");
|
|
76636
76627
|
} catch {}
|
|
76637
76628
|
deleteOverlayEntry(agent, match.slug, { root: opts.root });
|
|
76638
76629
|
const reconcileFn = opts.reconcile === undefined ? opts.root ? null : reconcileAgentCronOnly : opts.reconcile;
|
|
@@ -77072,7 +77063,7 @@ import {
|
|
|
77072
77063
|
mkdirSync as mkdirSync40,
|
|
77073
77064
|
mkdtempSync as mkdtempSync5,
|
|
77074
77065
|
openSync as openSync15,
|
|
77075
|
-
readFileSync as
|
|
77066
|
+
readFileSync as readFileSync61,
|
|
77076
77067
|
readdirSync as readdirSync29,
|
|
77077
77068
|
realpathSync as realpathSync7,
|
|
77078
77069
|
renameSync as renameSync16,
|
|
@@ -77324,7 +77315,7 @@ function loadFromDir(dir) {
|
|
|
77324
77315
|
continue;
|
|
77325
77316
|
}
|
|
77326
77317
|
if (ent.isFile()) {
|
|
77327
|
-
const buf =
|
|
77318
|
+
const buf = readFileSync61(full);
|
|
77328
77319
|
files[rel.replace(/\\/g, "/")] = buf.toString("utf-8");
|
|
77329
77320
|
}
|
|
77330
77321
|
}
|
|
@@ -77373,7 +77364,7 @@ function loadFromTarball(tarPath) {
|
|
|
77373
77364
|
}
|
|
77374
77365
|
}
|
|
77375
77366
|
function loadSingleFile(filePath) {
|
|
77376
|
-
const content =
|
|
77367
|
+
const content = readFileSync61(filePath, "utf-8");
|
|
77377
77368
|
return { "SKILL.md": content };
|
|
77378
77369
|
}
|
|
77379
77370
|
function loadFromStdin() {
|
|
@@ -77464,7 +77455,7 @@ function diffSummary(currentDir, files) {
|
|
|
77464
77455
|
if (ent.isDirectory()) {
|
|
77465
77456
|
walk2(full);
|
|
77466
77457
|
} else if (ent.isFile()) {
|
|
77467
|
-
currentFiles[rel.replace(/\\/g, "/")] =
|
|
77458
|
+
currentFiles[rel.replace(/\\/g, "/")] = readFileSync61(full, "utf-8");
|
|
77468
77459
|
}
|
|
77469
77460
|
}
|
|
77470
77461
|
};
|
|
@@ -77624,7 +77615,7 @@ import {
|
|
|
77624
77615
|
mkdirSync as mkdirSync41,
|
|
77625
77616
|
mkdtempSync as mkdtempSync6,
|
|
77626
77617
|
openSync as openSync16,
|
|
77627
|
-
readFileSync as
|
|
77618
|
+
readFileSync as readFileSync62,
|
|
77628
77619
|
readdirSync as readdirSync30,
|
|
77629
77620
|
renameSync as renameSync17,
|
|
77630
77621
|
rmSync as rmSync17,
|
|
@@ -77706,7 +77697,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
77706
77697
|
if (ent.isDirectory())
|
|
77707
77698
|
walk2(s, d);
|
|
77708
77699
|
else if (ent.isFile()) {
|
|
77709
|
-
writeFileSync35(d,
|
|
77700
|
+
writeFileSync35(d, readFileSync62(s));
|
|
77710
77701
|
}
|
|
77711
77702
|
}
|
|
77712
77703
|
};
|
|
@@ -77785,7 +77776,7 @@ function loadFromDir2(dir) {
|
|
|
77785
77776
|
}
|
|
77786
77777
|
if (ent.isFile()) {
|
|
77787
77778
|
const rel = relative3(abs, full).replace(/\\/g, "/");
|
|
77788
|
-
files[rel] =
|
|
77779
|
+
files[rel] = readFileSync62(full, "utf-8");
|
|
77789
77780
|
}
|
|
77790
77781
|
}
|
|
77791
77782
|
};
|
|
@@ -77971,7 +77962,7 @@ function loadFiles(opts) {
|
|
|
77971
77962
|
return loadFromDir2(p);
|
|
77972
77963
|
}
|
|
77973
77964
|
if (p.endsWith(".md")) {
|
|
77974
|
-
return { "SKILL.md":
|
|
77965
|
+
return { "SKILL.md": readFileSync62(p, "utf-8") };
|
|
77975
77966
|
}
|
|
77976
77967
|
fail3(`--from must be a directory or a .md file. Got: ${opts.from}`);
|
|
77977
77968
|
}
|
|
@@ -78037,6 +78028,7 @@ function resolveCloneSource(source, opts) {
|
|
|
78037
78028
|
var CLONE_MAX_FILE_BYTES = 1024 * 1024;
|
|
78038
78029
|
function readSourceFiles(dir) {
|
|
78039
78030
|
const files = {};
|
|
78031
|
+
const skipped = [];
|
|
78040
78032
|
const walk2 = (sub) => {
|
|
78041
78033
|
for (const ent of readdirSync30(sub, { withFileTypes: true })) {
|
|
78042
78034
|
const full = join69(sub, ent.name);
|
|
@@ -78049,18 +78041,22 @@ function readSourceFiles(dir) {
|
|
|
78049
78041
|
}
|
|
78050
78042
|
if (ent.isFile()) {
|
|
78051
78043
|
const rel = relative3(dir, full).replace(/\\/g, "/");
|
|
78044
|
+
if (!validateRelPath(rel)) {
|
|
78045
|
+
skipped.push(rel);
|
|
78046
|
+
continue;
|
|
78047
|
+
}
|
|
78052
78048
|
try {
|
|
78053
78049
|
const st = lstatSync9(full);
|
|
78054
78050
|
if (st.size > CLONE_MAX_FILE_BYTES) {
|
|
78055
78051
|
fail3(`clone source has oversized file ${rel} (${st.size} bytes > ${CLONE_MAX_FILE_BYTES}); ` + `refuse to read`, 3);
|
|
78056
78052
|
}
|
|
78057
78053
|
} catch {}
|
|
78058
|
-
files[rel] =
|
|
78054
|
+
files[rel] = readFileSync62(full, "utf-8");
|
|
78059
78055
|
}
|
|
78060
78056
|
}
|
|
78061
78057
|
};
|
|
78062
78058
|
walk2(dir);
|
|
78063
|
-
return files;
|
|
78059
|
+
return { files, skipped };
|
|
78064
78060
|
}
|
|
78065
78061
|
function rewriteSkillMdName(content, newName) {
|
|
78066
78062
|
if (!content.startsWith(`---
|
|
@@ -78088,13 +78084,17 @@ function clonePersonalAction(source, opts) {
|
|
|
78088
78084
|
if (!SKILL_SLUG_RE.test(newName)) {
|
|
78089
78085
|
fail3(`destination name must match ${SKILL_SLUG_RE.source}: got ${JSON.stringify(newName)}`);
|
|
78090
78086
|
}
|
|
78091
|
-
const files = readSourceFiles(src.dir);
|
|
78087
|
+
const { files, skipped } = readSourceFiles(src.dir);
|
|
78092
78088
|
if (!files["SKILL.md"]) {
|
|
78093
78089
|
fail3(`source ${JSON.stringify(source)} has no SKILL.md at ${src.dir}`);
|
|
78094
78090
|
}
|
|
78095
78091
|
if (newName !== src.slug) {
|
|
78096
78092
|
files["SKILL.md"] = rewriteSkillMdName(files["SKILL.md"], newName);
|
|
78097
78093
|
}
|
|
78094
|
+
if (skipped.length > 0) {
|
|
78095
|
+
process.stderr.write(source_default.yellow(`note: skipped ${skipped.length} non-allowlisted path${skipped.length === 1 ? "" : "s"} from source: ${skipped.join(", ")}
|
|
78096
|
+
`));
|
|
78097
|
+
}
|
|
78098
78098
|
loadValidateWrite(agentsRoot, agent, newName, files, true);
|
|
78099
78099
|
const skillDir = personalSkillDir(agentsRoot, agent, newName);
|
|
78100
78100
|
mirrorToConfigRepo(agent, newName, skillDir);
|
|
@@ -78107,14 +78107,16 @@ function clonePersonalAction(source, opts) {
|
|
|
78107
78107
|
source_slug: src.slug,
|
|
78108
78108
|
name: newName,
|
|
78109
78109
|
path: skillDir,
|
|
78110
|
-
files: Object.keys(files).length
|
|
78110
|
+
files: Object.keys(files).length,
|
|
78111
|
+
skipped
|
|
78111
78112
|
}));
|
|
78112
78113
|
appendAudit(agent, "skill.clone_to_personal", {
|
|
78113
78114
|
source,
|
|
78114
78115
|
source_tier: src.tier,
|
|
78115
78116
|
source_slug: src.slug,
|
|
78116
78117
|
name: newName,
|
|
78117
|
-
files: Object.keys(files).length
|
|
78118
|
+
files: Object.keys(files).length,
|
|
78119
|
+
skipped_count: skipped.length
|
|
78118
78120
|
}, 0);
|
|
78119
78121
|
}
|
|
78120
78122
|
function removePersonalAction(name, opts) {
|
|
@@ -78218,7 +78220,7 @@ function registerSkillPersonalCommands(program3) {
|
|
|
78218
78220
|
// src/cli/skill-search.ts
|
|
78219
78221
|
init_helpers();
|
|
78220
78222
|
var import_yaml18 = __toESM(require_dist(), 1);
|
|
78221
|
-
import { existsSync as existsSync77, readdirSync as readdirSync31, readFileSync as
|
|
78223
|
+
import { existsSync as existsSync77, readdirSync as readdirSync31, readFileSync as readFileSync63, statSync as statSync31 } from "node:fs";
|
|
78222
78224
|
import { homedir as homedir40 } from "node:os";
|
|
78223
78225
|
import { join as join70, resolve as resolve46 } from "node:path";
|
|
78224
78226
|
var PERSONAL_PREFIX2 = "personal-";
|
|
@@ -78239,7 +78241,7 @@ function readSkillFrontmatter(skillDir) {
|
|
|
78239
78241
|
return null;
|
|
78240
78242
|
let content;
|
|
78241
78243
|
try {
|
|
78242
|
-
content =
|
|
78244
|
+
content = readFileSync63(mdPath, "utf-8");
|
|
78243
78245
|
} catch {
|
|
78244
78246
|
return null;
|
|
78245
78247
|
}
|
|
@@ -78511,7 +78513,7 @@ function registerHostdMcpCommand(program3) {
|
|
|
78511
78513
|
// src/cli/hostd.ts
|
|
78512
78514
|
init_source();
|
|
78513
78515
|
init_helpers();
|
|
78514
|
-
import { existsSync as existsSync79, mkdirSync as mkdirSync42, readdirSync as readdirSync32, readFileSync as
|
|
78516
|
+
import { existsSync as existsSync79, mkdirSync as mkdirSync42, readdirSync as readdirSync32, readFileSync as readFileSync65, writeFileSync as writeFileSync36, statSync as statSync32, copyFileSync as copyFileSync12 } from "node:fs";
|
|
78515
78517
|
import { homedir as homedir41 } from "node:os";
|
|
78516
78518
|
import { join as join71 } from "node:path";
|
|
78517
78519
|
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
@@ -78764,7 +78766,7 @@ function registerHostdCommand(program3) {
|
|
|
78764
78766
|
The log is created when hostd handles its first privileged-verb request.`));
|
|
78765
78767
|
return;
|
|
78766
78768
|
}
|
|
78767
|
-
const raw =
|
|
78769
|
+
const raw = readFileSync65(logPath, "utf-8");
|
|
78768
78770
|
const limit = Math.max(1, parseInt(opts.tail ?? "50", 10) || 50);
|
|
78769
78771
|
const filters = {
|
|
78770
78772
|
agent: opts.agent,
|
package/package.json
CHANGED
|
@@ -159,6 +159,19 @@ fi
|
|
|
159
159
|
if [ ! -e "$HOME/.switchroom" ] || [ -L "$HOME/.switchroom" ]; then
|
|
160
160
|
ln -sfn {{{hostHomeQ}}}/.switchroom "$HOME/.switchroom" 2>/dev/null || true
|
|
161
161
|
fi
|
|
162
|
+
# Host ~/.switchroom-config symlink (#1846). Same shape as the
|
|
163
|
+
# ~/.switchroom link above, for the operator's git-tracked config repo.
|
|
164
|
+
# The compose generator bind-mounts a per-agent slice at
|
|
165
|
+
# <host-home>/.switchroom-config/agents/<self>/personal-skills/ (when
|
|
166
|
+
# the operator has opted in). Without this symlink, the CLI's
|
|
167
|
+
# resolveConfigSkillsDir would call existsSync("$HOME/.switchroom-config")
|
|
168
|
+
# and miss the bind-mounted path entirely → mirror silently no-ops.
|
|
169
|
+
# The link lands ONLY when the host slice is actually mounted (the dir
|
|
170
|
+
# the bind-mount targets) — leaving a dangling link when the operator
|
|
171
|
+
# hasn't opted in is fine, existsSync resolves false through it.
|
|
172
|
+
if [ ! -e "$HOME/.switchroom-config" ] || [ -L "$HOME/.switchroom-config" ]; then
|
|
173
|
+
ln -sfn {{{hostHomeQ}}}/.switchroom-config "$HOME/.switchroom-config" 2>/dev/null || true
|
|
174
|
+
fi
|
|
162
175
|
{{/if}}
|
|
163
176
|
|
|
164
177
|
export NVM_DIR="$HOME/.nvm"
|
|
@@ -555,16 +568,29 @@ for f in \
|
|
|
555
568
|
fi
|
|
556
569
|
done
|
|
557
570
|
|
|
571
|
+
# Fleet directory — epic #1850 / issue #1852. Extends Claude Code's
|
|
572
|
+
# native CLAUDE.md auto-discovery to `~/.switchroom/fleet/` so every
|
|
573
|
+
# agent reads the release-pinned `switchroom-invariants.md` (lane 1)
|
|
574
|
+
# and operator-owned `CLAUDE.md` (lane 2) from there. Guarded on the
|
|
575
|
+
# directory existing so a fresh-install or a half-applied state
|
|
576
|
+
# doesn't fail boot — `switchroom apply`'s ensureHostMountSources
|
|
577
|
+
# creates the dir + seeds the files.
|
|
578
|
+
SR_FLEET_DIR="$HOME/.switchroom/fleet"
|
|
579
|
+
SR_FLEET_ARG=""
|
|
580
|
+
if [ -d "$SR_FLEET_DIR" ]; then
|
|
581
|
+
SR_FLEET_ARG="--add-dir $SR_FLEET_DIR"
|
|
582
|
+
fi
|
|
583
|
+
|
|
558
584
|
{{#if useSwitchroomPlugin}}
|
|
559
585
|
if [ -n "$APPEND_PROMPT" ]; then
|
|
560
|
-
exec claude $CONTINUE_FLAG --dangerously-load-development-channels server:switchroom-telegram --plugin-dir "{{securityPluginDir}}"{{#if hindsightEnabled}} --plugin-dir "{{agentDir}}/.claude/plugins/hindsight-memory"{{/if}}{{#if modelQ}} --model {{{modelQ}}}{{/if}}{{#if thinkingEffort}} --effort {{thinkingEffort}}{{/if}}{{#if permissionMode}} --permission-mode {{permissionMode}}{{/if}}{{#if fallbackModelQ}} --fallback-model {{{fallbackModelQ}}}{{/if}} --append-system-prompt "$APPEND_PROMPT"{{#if dangerousMode}} --dangerously-skip-permissions{{/if}}{{#if extraCliArgs}}{{{extraCliArgs}}}{{/if}}
|
|
586
|
+
exec claude $CONTINUE_FLAG --dangerously-load-development-channels server:switchroom-telegram --plugin-dir "{{securityPluginDir}}"{{#if hindsightEnabled}} --plugin-dir "{{agentDir}}/.claude/plugins/hindsight-memory"{{/if}} $SR_FLEET_ARG{{#if modelQ}} --model {{{modelQ}}}{{/if}}{{#if thinkingEffort}} --effort {{thinkingEffort}}{{/if}}{{#if permissionMode}} --permission-mode {{permissionMode}}{{/if}}{{#if fallbackModelQ}} --fallback-model {{{fallbackModelQ}}}{{/if}} --append-system-prompt "$APPEND_PROMPT"{{#if dangerousMode}} --dangerously-skip-permissions{{/if}}{{#if extraCliArgs}}{{{extraCliArgs}}}{{/if}}
|
|
561
587
|
else
|
|
562
|
-
exec claude $CONTINUE_FLAG --dangerously-load-development-channels server:switchroom-telegram --plugin-dir "{{securityPluginDir}}"{{#if hindsightEnabled}} --plugin-dir "{{agentDir}}/.claude/plugins/hindsight-memory"{{/if}}{{#if modelQ}} --model {{{modelQ}}}{{/if}}{{#if thinkingEffort}} --effort {{thinkingEffort}}{{/if}}{{#if permissionMode}} --permission-mode {{permissionMode}}{{/if}}{{#if fallbackModelQ}} --fallback-model {{{fallbackModelQ}}}{{/if}}{{#if dangerousMode}} --dangerously-skip-permissions{{/if}}{{#if extraCliArgs}}{{{extraCliArgs}}}{{/if}}
|
|
588
|
+
exec claude $CONTINUE_FLAG --dangerously-load-development-channels server:switchroom-telegram --plugin-dir "{{securityPluginDir}}"{{#if hindsightEnabled}} --plugin-dir "{{agentDir}}/.claude/plugins/hindsight-memory"{{/if}} $SR_FLEET_ARG{{#if modelQ}} --model {{{modelQ}}}{{/if}}{{#if thinkingEffort}} --effort {{thinkingEffort}}{{/if}}{{#if permissionMode}} --permission-mode {{permissionMode}}{{/if}}{{#if fallbackModelQ}} --fallback-model {{{fallbackModelQ}}}{{/if}}{{#if dangerousMode}} --dangerously-skip-permissions{{/if}}{{#if extraCliArgs}}{{{extraCliArgs}}}{{/if}}
|
|
563
589
|
fi
|
|
564
590
|
{{else}}
|
|
565
591
|
if [ -n "$APPEND_PROMPT" ]; then
|
|
566
|
-
exec claude $CONTINUE_FLAG --channels plugin:telegram@claude-plugins-official --plugin-dir "{{securityPluginDir}}"{{#if hindsightEnabled}} --plugin-dir "{{agentDir}}/.claude/plugins/hindsight-memory"{{/if}}{{#if modelQ}} --model {{{modelQ}}}{{/if}}{{#if thinkingEffort}} --effort {{thinkingEffort}}{{/if}}{{#if permissionMode}} --permission-mode {{permissionMode}}{{/if}}{{#if fallbackModelQ}} --fallback-model {{{fallbackModelQ}}}{{/if}} --append-system-prompt "$APPEND_PROMPT"{{#if dangerousMode}} --dangerously-skip-permissions{{/if}}{{#if extraCliArgs}}{{{extraCliArgs}}}{{/if}}
|
|
592
|
+
exec claude $CONTINUE_FLAG --channels plugin:telegram@claude-plugins-official --plugin-dir "{{securityPluginDir}}"{{#if hindsightEnabled}} --plugin-dir "{{agentDir}}/.claude/plugins/hindsight-memory"{{/if}} $SR_FLEET_ARG{{#if modelQ}} --model {{{modelQ}}}{{/if}}{{#if thinkingEffort}} --effort {{thinkingEffort}}{{/if}}{{#if permissionMode}} --permission-mode {{permissionMode}}{{/if}}{{#if fallbackModelQ}} --fallback-model {{{fallbackModelQ}}}{{/if}} --append-system-prompt "$APPEND_PROMPT"{{#if dangerousMode}} --dangerously-skip-permissions{{/if}}{{#if extraCliArgs}}{{{extraCliArgs}}}{{/if}}
|
|
567
593
|
else
|
|
568
|
-
exec claude $CONTINUE_FLAG --channels plugin:telegram@claude-plugins-official --plugin-dir "{{securityPluginDir}}"{{#if hindsightEnabled}} --plugin-dir "{{agentDir}}/.claude/plugins/hindsight-memory"{{/if}}{{#if modelQ}} --model {{{modelQ}}}{{/if}}{{#if thinkingEffort}} --effort {{thinkingEffort}}{{/if}}{{#if permissionMode}} --permission-mode {{permissionMode}}{{/if}}{{#if fallbackModelQ}} --fallback-model {{{fallbackModelQ}}}{{/if}}{{#if dangerousMode}} --dangerously-skip-permissions{{/if}}{{#if extraCliArgs}}{{{extraCliArgs}}}{{/if}}
|
|
594
|
+
exec claude $CONTINUE_FLAG --channels plugin:telegram@claude-plugins-official --plugin-dir "{{securityPluginDir}}"{{#if hindsightEnabled}} --plugin-dir "{{agentDir}}/.claude/plugins/hindsight-memory"{{/if}} $SR_FLEET_ARG{{#if modelQ}} --model {{{modelQ}}}{{/if}}{{#if thinkingEffort}} --effort {{thinkingEffort}}{{/if}}{{#if permissionMode}} --permission-mode {{permissionMode}}{{/if}}{{#if fallbackModelQ}} --fallback-model {{{fallbackModelQ}}}{{/if}}{{#if dangerousMode}} --dangerously-skip-permissions{{/if}}{{#if extraCliArgs}}{{{extraCliArgs}}}{{/if}}
|
|
569
595
|
fi
|
|
570
596
|
{{/if}}
|
|
@@ -22,6 +22,9 @@ tools is to let you do the edit yourself.
|
|
|
22
22
|
| "what other agents are running here?" / "is there an agent that does X?" / "who handles Y?" | `peers_list` |
|
|
23
23
|
| "install the foo skill" / "give yourself the foo skill" | `skill_install` with `source: "bundled:foo"` |
|
|
24
24
|
| "drop the foo skill" / "remove the foo skill" | `skill_remove` with `name: "foo"` |
|
|
25
|
+
| "is there a skill for X?" | `skill_search` with `query: "X"` |
|
|
26
|
+
| **YOU find a bug in a skill you're using** | `skill_clone_to_personal` then `skill_edit_personal` — fork-and-fix yourself |
|
|
27
|
+
| "write me a custom skill that does X" | `skill_init_personal` |
|
|
25
28
|
|
|
26
29
|
### Tools
|
|
27
30
|
|
|
@@ -66,6 +69,8 @@ tools is to let you do the edit yourself.
|
|
|
66
69
|
Does NOT remove skills the operator wrote directly into
|
|
67
70
|
`switchroom.yaml` — those are removed by the operator only.
|
|
68
71
|
|
|
72
|
+
- **`skill_search` / `skill_init_personal` / `skill_edit_personal` / `skill_remove_personal` / `skill_list_personal` / `skill_clone_to_personal`** — author/fork your own. `files` is `{path: content}` JSON; allowlist `SKILL.md`, `scripts/*.{sh,py}`, `references/*.md`.
|
|
73
|
+
|
|
69
74
|
### Safety rails — what gets rejected
|
|
70
75
|
|
|
71
76
|
The broker hard-rejects writes that would violate these limits. Anticipate
|
|
@@ -79,28 +84,13 @@ the rails will block:
|
|
|
79
84
|
- **20 entries per agent maximum.** `E_QUOTA_EXCEEDED`. If you're near the
|
|
80
85
|
cap, `cron_list` first; if full, prompt the user to remove an old one
|
|
81
86
|
before adding the new one.
|
|
82
|
-
- **No `secrets:` on agent-authored entries
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
- **
|
|
88
|
-
|
|
89
|
-
gateway sets when spawning your CLI — calls passing
|
|
90
|
-
`agent: "<other-agent>"` that doesn't match the pin are rejected. If
|
|
91
|
-
the user wants to set something up on a different agent, tell them
|
|
92
|
-
which agent to ask.
|
|
93
|
-
|
|
94
|
-
### Skills — self-service is live (#1163 Phase 2)
|
|
95
|
-
|
|
96
|
-
You can `skill_list` to inventory, `skill_install` to add, and
|
|
97
|
-
`skill_remove` to drop. v1 source format is `bundled:<name>` only — the
|
|
98
|
-
skill must exist in the host's bundled-skills pool (run `skill_list` on
|
|
99
|
-
the host to see what's available, or pass an obvious slug like
|
|
100
|
-
`webapp-testing`, `pdf`, `mcp-builder`). git+https sources are designed
|
|
101
|
-
but not yet shipped; if the user asks for an arbitrary URL, tell them
|
|
102
|
-
the operator needs to drop it under `~/.switchroom/skills/<name>/` and
|
|
103
|
-
run `switchroom apply`.
|
|
87
|
+
- **No `secrets:` on agent-authored entries** (`E_OVERLAY_SECRETS_REQUIRES_APPROVAL`). Cron fires the prompt; you go through `vault_request_access` at runtime.
|
|
88
|
+
- **Cross-agent writes rejected** — you manage only your own schedule. The broker pins identity via `$SWITCHROOM_AGENT_NAME`; if the user wants something on a different agent, tell them which agent to ask.
|
|
89
|
+
|
|
90
|
+
### Skills — self-service
|
|
91
|
+
|
|
92
|
+
- **Install** — `skill_install` (`bundled:<name>`) / `skill_remove`. git+https deferred.
|
|
93
|
+
- **Fix a skill yourself** — `skill_clone_to_personal` → `skill_edit_personal` → use `personal-<name>`. Fork is durable + auto-mirrors to `~/.switchroom-config/` when present.
|
|
104
94
|
|
|
105
95
|
### Honest confirmation pattern
|
|
106
96
|
|
|
@@ -34,7 +34,7 @@ You are a senior software engineering agent. You write, review, debug, and archi
|
|
|
34
34
|
- Clear commit messages in imperative mood explaining the why.
|
|
35
35
|
- Atomic commits. PR descriptions explain what, why, and how to test.
|
|
36
36
|
|
|
37
|
-
{{
|
|
37
|
+
{{!-- telegram-style now in ~/.switchroom/fleet/switchroom-invariants.md --}}
|
|
38
38
|
|
|
39
39
|
## Memory — Hindsight
|
|
40
40
|
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Agent:
|
|
2
|
+
|
|
3
|
+
## What you are
|
|
4
|
+
|
|
5
|
+
You are a **switchroom agent** — an instance of **Claude Code** (Anthropic's official `claude` CLI, unmodified) running in a Linux container, managed by switchroom. Your `$SWITCHROOM_AGENT_NAME` is ``. Be honest about this when asked ("what are you" / "what's running here"): switchroom agent `` running Claude Code under the official `claude` CLI. Not a custom model, not a wrapper, not "an AI assistant" in the abstract.
|
|
6
|
+
|
|
7
|
+
You are one of several agents here. To see the others, call `peers_list` on the `agent-config` MCP server — returns `[{name, purpose, admin}]` live from `switchroom.yaml`. **Never memorize peers into Hindsight or hard-code them into replies** — drift kills trust. On "who else is here" / "is there an agent that does X" / "who handles Y" / "who can do <admin op>", call `peers_list` first and answer from its result; if no peer matches, say so.
|
|
8
|
+
|
|
9
|
+
## Who you are
|
|
10
|
+
|
|
11
|
+
See `SOUL.md` (in this directory) for your identity, vibe, communication style, and expertise. That file is your persona source of truth.
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## Core Behavior
|
|
15
|
+
- Respond helpfully, concisely, and conversationally.
|
|
16
|
+
- Use your available tools when they add clear value — don't force tool use when a plain answer suffices.
|
|
17
|
+
- Save important facts, preferences, and decisions to memory so you can recall them later.
|
|
18
|
+
- When asked to do something ambiguous, ask one clarifying question rather than guessing.
|
|
19
|
+
- If a task has multiple steps, outline your plan before executing.
|
|
20
|
+
|
|
21
|
+
## Safety
|
|
22
|
+
- Don't exfiltrate private data. Ever.
|
|
23
|
+
- Don't run destructive commands without asking.
|
|
24
|
+
- Prefer `trash` over `rm` when available (recoverable beats gone forever).
|
|
25
|
+
- Safe to do freely: read files, explore, organize, search the web, check calendars, work within this workspace.
|
|
26
|
+
- Ask first: sending emails, tweets, public posts, anything that leaves the machine, anything you're uncertain about.
|
|
27
|
+
|
|
28
|
+
## Execution Bias
|
|
29
|
+
|
|
30
|
+
How you should decide what to do next. These are procedural rules, not vibe.
|
|
31
|
+
|
|
32
|
+
- **Act in-turn.** If the request is actionable, do it this turn. Don't finish with a plan or promise when tools can move it forward.
|
|
33
|
+
- **Verify mutable facts before claiming them.** Files, git state, clocks, versions, services, processes, package state, the contents of an `Edit` target: read live. Memory and prior context are not verification sources. "I think the function is at line 200" is not an answer; `Grep`/`Read` is.
|
|
34
|
+
- **Final answer needs evidence.** Test/build/lint output, screenshot, inspection, tool output, or a named blocker. "It should work" is not a finalization.
|
|
35
|
+
- **Weak or empty tool result is not a conclusion.** Vary the query, path, command, or source before deciding the thing isn't there.
|
|
36
|
+
- **Non-final turn:** use tools to advance, or ask the one clarifying question that unblocks safe progress. One question, not five.
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
## Memory — Hindsight is your single backend
|
|
40
|
+
|
|
41
|
+
**Claude Code's built-in file-based auto-memory is disabled for this agent.** Don't try to write `.md` files under `.claude/projects/.../memory/` or maintain a `MEMORY.md` index — that whole system is off. There's exactly one memory backend: **Hindsight**.
|
|
42
|
+
|
|
43
|
+
Hindsight is a memory bank with semantic search, knowledge graph, entity resolution, mental models, and directives. You talk to it through MCP tools (all pre-approved):
|
|
44
|
+
|
|
45
|
+
### Day-to-day tools
|
|
46
|
+
- `mcp__hindsight__recall` — semantic-search the bank for relevant past memories. Auto-fires on every inbound user message via the plugin's UserPromptSubmit hook (you'll see "Relevant memories from past conversations" in your context). Call manually when you need a more specific query than the auto-fired one.
|
|
47
|
+
- `mcp__hindsight__retain` — store a new memory. The plugin automatically retains the conversation transcript every ~10 turns via the Stop hook, so you usually don't need this. Call manually for significant decisions, corrections, or facts you want immediately searchable.
|
|
48
|
+
- `mcp__hindsight__reflect` — Hindsight's LLM-powered "answer this query using the bank's content + directives". Use when the user asks a question that requires synthesis across multiple past memories.
|
|
49
|
+
|
|
50
|
+
### Mental Models (replaces hand-curated user profile)
|
|
51
|
+
A mental model is a pre-computed semantic summary backed by reflection over the bank. It's the proper way to maintain things like "what do we know about this user" — semantically populated, automatically refreshed.
|
|
52
|
+
|
|
53
|
+
- `mcp__hindsight__create_mental_model(name, source_query)` — create one. When the user shares a fact about themselves (preferences, background, goals), don't write a file — instead, retain the fact and (if no User Profile mental model exists yet) create one with `source_query: "what do we know about this user?"`. Hindsight will populate it from the retained memories.
|
|
54
|
+
|
|
55
|
+
### Directives (replaces feedback rules)
|
|
56
|
+
Hard rules the agent must follow during reflect — guardrails that are always applied.
|
|
57
|
+
|
|
58
|
+
- `mcp__hindsight__create_directive(text)` — e.g., `create_directive("Always prefer TypeScript over JavaScript for this user's projects")`. When the user gives you a correction or "always do X" rule, create a directive instead of writing a feedback `.md` file.
|
|
59
|
+
|
|
60
|
+
(Inspection tools like `list_memories`, `list_mental_models`, `update_mental_model`, `refresh_mental_model`, `list_directives`, `delete_directive` are available under the `mcp__hindsight__*` namespace if you ever need them, but you rarely should — Hindsight's own auto-recall surfaces what matters and the operator handles bank curation out-of-band.)
|
|
61
|
+
|
|
62
|
+
### What to retain — and what NOT to retain
|
|
63
|
+
|
|
64
|
+
Retain proactively when:
|
|
65
|
+
- The user shares a preference or fact about themselves
|
|
66
|
+
- The user gives you a correction or rule (these go to directives, not retain)
|
|
67
|
+
- A significant decision was made and the rationale matters for next time
|
|
68
|
+
- You did real work and the result + the path you took would be useful next session
|
|
69
|
+
|
|
70
|
+
Don't retain:
|
|
71
|
+
- Routine pleasantries, "thanks", "got it"
|
|
72
|
+
- Conversation chatter that doesn't carry forward
|
|
73
|
+
- Sensitive content the user explicitly asked you to not remember
|
|
74
|
+
- Things already in a mental model — they'll be re-derived from underlying memories
|
|
75
|
+
|
|
76
|
+
The plugin's auto-retain (Stop hook) handles transcript-level storage on a 10-turn cadence, so you don't need to manually retain everything. Use manual `retain` for high-signal observations you want immediately searchable.
|
|
77
|
+
|
|
78
|
+
## Sub-Agent Delegation
|
|
79
|
+
|
|
80
|
+
The main session is for conversation. Execution belongs in sub-agents. Before making tool calls, classify the request:
|
|
81
|
+
|
|
82
|
+
**Stay in main (conversational):**
|
|
83
|
+
- Quick lookups (1-2 tool calls max)
|
|
84
|
+
- Memory/config reads and writes
|
|
85
|
+
- Questions that need user input before acting
|
|
86
|
+
- Simple status checks, coaching, motivation, emotional support
|
|
87
|
+
|
|
88
|
+
**Delegate to a sub-agent (execution):**
|
|
89
|
+
- Any code change — delegate to `@worker`
|
|
90
|
+
- Research requiring web searches or 3+ file reads — delegate to `@researcher`
|
|
91
|
+
- File creation, code generation, build/deploy, multi-step infra
|
|
92
|
+
- Data analysis or report generation
|
|
93
|
+
- Anything involving 3+ sequential tool calls without needing user input
|
|
94
|
+
- Review of completed work — delegate to `@reviewer`
|
|
95
|
+
|
|
96
|
+
**Golden rule:** when in doubt, delegate. Unnecessary delegation costs slightly more tokens. A blocked session costs the user's attention. Keep your own turns short — dispatch and acknowledge. The user should never wait more than 10 seconds for a response from you.
|
|
97
|
+
|
|
98
|
+
**Anti-patterns:** starting a task inline then realizing it's complex mid-way; doing 5+ tool calls "because it's almost done"; polling sub-agent status in a loop.
|
|
99
|
+
|
|
100
|
+
If no sub-agents are configured, do the work yourself.
|
|
101
|
+
|
|
102
|
+
## Session Continuity
|
|
103
|
+
|
|
104
|
+
By default, every restart starts a **fresh `claude` session** — the in-flight transcript is NOT carried over (`session_continuity.resume_mode: handoff`, the default since switchroom #362). Don't assume tool state, scratch variables, or unread tool output from before the restart are still available. What does survive:
|
|
105
|
+
|
|
106
|
+
- **Handoff briefing** — on a clean shutdown, the Stop hook writes a bounded raw transcript tail of the prior session to `.handoff.md`. On boot, start.sh injects it into your `--append-system-prompt` so you can reorient — read it, and lean on your memory files for anything older. If `.handoff.md` is missing or stale (fresh agent, or pre-Stop-hook crash), `start.sh` runs `handoff-briefing.sh` to assemble `.handoff-briefing.md` from Telegram + Hindsight + today's daily memory, and injects whichever is fresher.
|
|
107
|
+
- **Hindsight memory** — auto-recall fires on every inbound user message and surfaces relevant memories from past sessions. Long-term facts, decisions, and mental models live here, not in the transcript.
|
|
108
|
+
- **Telegram history** — the gateway's SQLite buffer remembers every inbound/outbound message. Use `get_recent_messages` to recover recent chat context if the handoff briefing doesn't cover what you need.
|
|
109
|
+
- **`SWITCHROOM_PENDING_TURN`** — if your previous session was killed mid-turn (watchdog, SIGTERM, timeout), start.sh exports this env var plus the chat/thread/last-user-message context. Acknowledge the interruption and ask for direction rather than silently resuming.
|
|
110
|
+
- **`.wake-audit-pending`** sentinel — every boot drops this file under `TELEGRAM_STATE_DIR`. On your first turn, run the three-signal check (owed reply / orphan sub-agents / open todos) per the wake-audit protocol in your CLAUDE.md, then `rm -f` the sentinel.
|
|
111
|
+
|
|
112
|
+
A config-summary greeting card is sent automatically by the SessionStart hook — you don't need to announce yourself. If your context feels thin (after compaction or any fresh session), proactively recall from Hindsight before proceeding.
|
|
113
|
+
|
|
114
|
+
(Operators can override the resume policy per-agent via `session_continuity.resume_mode` in switchroom.yaml — `auto`, `continue`, `handoff`, or `none`. The default is `handoff`.)
|
|
115
|
+
|
|
116
|
+
## Admin operations
|
|
117
|
+
|
|
118
|
+
You're NOT `admin: true`. If asked to restart agents / read peer logs / exec into peer containers / run fleet updates, call `peers_list`, find an entry with `admin: true`, and point the user there: _"I can't restart agents from here — ask `<admin-name>`, they're admin on this instance."_ No long apology; just hand off.
|
|
119
|
+
|
|
120
|
+
## Tools
|
|
121
|
+
Use your available tools when appropriate. If you lack the right tool for a task, say so clearly rather than attempting a workaround.
|
|
122
|
+
|
|
@@ -39,7 +39,14 @@ How you should decide what to do next. These are procedural rules, not vibe.
|
|
|
39
39
|
- **Weak or empty tool result is not a conclusion.** Vary the query, path, command, or source before deciding the thing isn't there.
|
|
40
40
|
- **Non-final turn:** use tools to advance, or ask the one clarifying question that unblocks safe progress. One question, not five.
|
|
41
41
|
|
|
42
|
-
{{
|
|
42
|
+
{{!--
|
|
43
|
+
Telegram-style guidance (the 5-beat pacing contract) lives in the
|
|
44
|
+
fleet invariants file at `~/.switchroom/fleet/switchroom-invariants.md`
|
|
45
|
+
and reaches the agent via Claude Code native CLAUDE.md discovery
|
|
46
|
+
(`--add-dir ~/.switchroom/fleet`). Do not re-include the
|
|
47
|
+
`{{> telegram-style}}` partial here — that would re-introduce the
|
|
48
|
+
session-level duplication the prompt redesign removed.
|
|
49
|
+
--}}
|
|
43
50
|
|
|
44
51
|
## Memory — Hindsight is your single backend
|
|
45
52
|
|
|
@@ -33,7 +33,7 @@ You help the user stay organized, prepared, and focused on high-leverage work. Y
|
|
|
33
33
|
- **Anticipate, don't just react.** Flag gaps proactively.
|
|
34
34
|
- **Be concise.** Lead with essentials. Details on request.
|
|
35
35
|
|
|
36
|
-
{{
|
|
36
|
+
{{!-- telegram-style now in ~/.switchroom/fleet/switchroom-invariants.md --}}
|
|
37
37
|
|
|
38
38
|
## Memory — Hindsight
|
|
39
39
|
|
|
@@ -27,7 +27,7 @@ You are a health and fitness coaching agent — an accountability partner, not a
|
|
|
27
27
|
## Boundaries
|
|
28
28
|
Recommend the user consult a professional for: persistent pain/injury, medical conditions, specialized nutrition plans, symptoms of overtraining or disordered eating. Say it plainly: "That's outside my lane — worth checking with your doctor."
|
|
29
29
|
|
|
30
|
-
{{
|
|
30
|
+
{{!-- telegram-style now in ~/.switchroom/fleet/switchroom-invariants.md --}}
|
|
31
31
|
|
|
32
32
|
## Memory — Hindsight
|
|
33
33
|
|
|
@@ -48732,10 +48732,10 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
|
|
|
48732
48732
|
}
|
|
48733
48733
|
|
|
48734
48734
|
// ../src/build-info.ts
|
|
48735
|
-
var VERSION = "0.13.
|
|
48736
|
-
var COMMIT_SHA = "
|
|
48737
|
-
var COMMIT_DATE = "2026-05-
|
|
48738
|
-
var LATEST_PR =
|
|
48735
|
+
var VERSION = "0.13.52";
|
|
48736
|
+
var COMMIT_SHA = "3d68efa2";
|
|
48737
|
+
var COMMIT_DATE = "2026-05-26T00:48:06Z";
|
|
48738
|
+
var LATEST_PR = 1864;
|
|
48739
48739
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
48740
48740
|
|
|
48741
48741
|
// gateway/boot-version.ts
|