switchroom 0.17.10 → 0.18.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/workspace-dynamic-hook.sh +12 -13
- package/dist/agent-scheduler/index.js +27 -1
- package/dist/auth-broker/index.js +6161 -151
- package/dist/cli/notion-write-pretool.mjs +29 -2
- package/dist/cli/switchroom.js +578 -454
- package/dist/host-control/main.js +6182 -172
- package/dist/vault/approvals/kernel-server.js +5891 -164
- package/dist/vault/broker/server.js +6597 -881
- package/package.json +1 -1
- package/profiles/_base/settings.json.hbs +2 -2
- package/profiles/_base/start.sh.hbs +170 -21
- package/profiles/coding/CLAUDE.md.hbs +1 -1
- package/profiles/default/CLAUDE.md +2 -2
- package/profiles/default/CLAUDE.md.hbs +2 -2
- package/profiles/executive-assistant/CLAUDE.md.hbs +1 -1
- package/profiles/health-coach/CLAUDE.md.hbs +1 -1
- package/telegram-plugin/auth-snapshot-format.ts +22 -24
- package/telegram-plugin/context-exhaustion.ts +124 -0
- package/telegram-plugin/dist/gateway/gateway.js +24086 -8727
- package/telegram-plugin/gateway/activity-card-store.ts +76 -0
- package/telegram-plugin/gateway/gateway.ts +480 -85
- package/telegram-plugin/gateway/inbound-delivery-gate.ts +26 -0
- package/telegram-plugin/gateway/model-command.ts +70 -10
- package/telegram-plugin/package.json +6 -0
- package/telegram-plugin/quota-watch.ts +4 -6
- package/telegram-plugin/registry/turns-schema.test.ts +97 -0
- package/telegram-plugin/registry/turns-schema.ts +78 -0
- package/telegram-plugin/render/ir.ts +209 -0
- package/telegram-plugin/render/parse.ts +363 -0
- package/telegram-plugin/render/render.ts +440 -0
- package/telegram-plugin/render/rich-render.ts +72 -0
- package/telegram-plugin/stream-controller.ts +14 -3
- package/telegram-plugin/tests/activity-card-store.test.ts +94 -0
- package/telegram-plugin/tests/auth-command-format2.test.ts +1 -1
- package/telegram-plugin/tests/auth-snapshot-format.test.ts +30 -16
- package/telegram-plugin/tests/claude-code-event-contract.test.ts +48 -0
- package/telegram-plugin/tests/feed-heartbeat-liveness-open.test.ts +11 -0
- package/telegram-plugin/tests/feed-survival.test.ts +39 -0
- package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +81 -0
- package/telegram-plugin/tests/inbound-emit-after-intercepts.test.ts +82 -0
- package/telegram-plugin/tests/liveness-tracker.test.ts +228 -0
- package/telegram-plugin/tests/model-command.test.ts +193 -16
- package/telegram-plugin/tests/narrative-render.test.ts +125 -0
- package/telegram-plugin/tests/orphaned-reply-rearm.test.ts +123 -163
- package/telegram-plugin/tests/quota-watch.test.ts +1 -4
- package/telegram-plugin/tests/rapid-fire-delivery-ordering.test.ts +149 -0
- package/telegram-plugin/tests/render/parse-torture.test.ts +136 -0
- package/telegram-plugin/tests/render/parse.test.ts +393 -0
- package/telegram-plugin/tests/render/render.test.ts +436 -0
- package/telegram-plugin/tests/render/rich-render.test.ts +85 -0
- package/telegram-plugin/tests/telegram-activity-visibility-integration.test.ts +155 -1
- package/telegram-plugin/tests/worktree-watch-cwds.test.ts +98 -3
- package/telegram-plugin/turn-liveness-floor.ts +35 -1
- package/telegram-plugin/uat/scenarios/jtbd-rich-formatting-render-dm.test.ts +99 -7
- package/telegram-plugin/worktree-watch-cwds.ts +92 -17
- package/vendor/hindsight-memory/scripts/lib/client.py +11 -1
- package/vendor/hindsight-memory/scripts/lib/config.py +9 -2
- package/vendor/hindsight-memory/scripts/recall.py +64 -6
- package/vendor/hindsight-memory/scripts/tests/test_recall_integration.py +1 -0
- package/vendor/hindsight-memory/tests/test_client.py +43 -0
- package/vendor/hindsight-memory/tests/test_recall_precision.py +114 -0
package/dist/cli/switchroom.js
CHANGED
|
@@ -13542,7 +13542,7 @@ var init_zod = __esm(() => {
|
|
|
13542
13542
|
function isValidTimezone(value) {
|
|
13543
13543
|
return TIMEZONE_REGEX.test(value);
|
|
13544
13544
|
}
|
|
13545
|
-
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, ReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, DEFAULT_PROFILE = "default", AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
|
|
13545
|
+
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, ReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, DEFAULT_PROFILE = "default", AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
|
|
13546
13546
|
var init_schema = __esm(() => {
|
|
13547
13547
|
init_zod();
|
|
13548
13548
|
CodeRepoEntrySchema = exports_external.object({
|
|
@@ -13694,7 +13694,11 @@ var init_schema = __esm(() => {
|
|
|
13694
13694
|
sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Per-speaker recall routing: a map of Telegram sender \u2192 extra " + "recall bank. When a message arrives, the agent also recalls the " + "speaker's bank (matched by Telegram username \u2014 a leading @ is " + "optional \u2014 or numeric user_id), merged " + "into its own results \u2014 so each trusted user gets their own " + "profile context. Additive recall scoping within the single " + "tenant: never an access boundary (who may drive an agent stays " + "the per-agent user assignment in `access.allowFrom`). Author the " + "banks via `switchroom memory profile`."),
|
|
13695
13695
|
skip_trivial: exports_external.boolean().optional().describe("Skip recall on plausibly-stateless trivial turns (time/date/" + "greeting). Switchroom default true \u2014 saves the recall arm + " + "injected tokens on turns that never need memory, guarded so it " + "never skips a turn that references user/project/session state. " + "Set false to always run recall."),
|
|
13696
13696
|
topic_filter_mode: exports_external.enum(["soft-preamble", "hard-filter"]).optional().describe("Supergroup-mode cross-topic memory behaviour. Default " + "(unset) \u2192 soft-preamble: recall returns memories from all " + "topics, and a 'Current topic: \u2026' preamble tells the model " + "to self-scope. hard-filter: drop any recalled memory whose " + "metadata.thread_id differs from the active inbound's topic. " + "Flip to hard-filter when the recall_log shows binding " + "failures (model surfacing the right memory but applying " + "it to the wrong topic).")
|
|
13697
|
-
}).optional().describe("Auto-recall tuning knobs")
|
|
13697
|
+
}).optional().describe("Auto-recall tuning knobs"),
|
|
13698
|
+
retain: exports_external.object({
|
|
13699
|
+
every_n_turns: exports_external.number().int().min(1).optional().describe("How often the Stop hook fires auto-retention, in turns. The " + "vendor plugin default is 10 (a short session can end before " + "retention ever fires); switchroom's scaffold default is 3. " + "Lower = more frequent, smaller retains + tighter crash " + "durability (at 1, every turn is retained before a restart can " + "lose it); higher = fewer, larger retains + less LLM churn. " + "Raised from the historical 1 to 3 because the local reasoning " + "consolidation model (Ollama gpt-oss-20b) ran away on the large " + "overlapping every-turn payloads. Set to 1 for the old " + "every-turn crash-durability guarantee. Min 1. Cascade: " + "per-field merge (agent wins over default)."),
|
|
13700
|
+
overlap_turns: exports_external.number().int().min(0).optional().describe("Extra recent turns included in each chunked retain window on " + "top of `every_n_turns`, so window = overlap_turns + " + "every_n_turns recent turns. Vendor default is 2; switchroom's " + "scaffold default is 1 (smaller payloads for the local " + "reasoning consolidation model). Higher = more redundant " + "context re-sent per fire. Min 0. Cascade: per-field merge.")
|
|
13701
|
+
}).optional().describe("Auto-retain (Stop-hook consolidation) cadence knobs")
|
|
13698
13702
|
}).optional();
|
|
13699
13703
|
HookEntrySchema = exports_external.object({
|
|
13700
13704
|
command: exports_external.string().describe("Shell command to run. Supports ${CLAUDE_CONFIG_DIR} and ${CLAUDE_PLUGIN_ROOT} substitution."),
|
|
@@ -13872,6 +13876,21 @@ var init_schema = __esm(() => {
|
|
|
13872
13876
|
small_fast_model: exports_external.string().optional().describe("Model id exported as ANTHROPIC_SMALL_FAST_MODEL for the claude CLI's " + "background/fast lane, e.g. 'claude-haiku-4-5-20251001'."),
|
|
13873
13877
|
tags: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Extra key/value metadata tags attached to the provisioned LiteLLM " + "virtual key. Merged per-key across cascade layers (agent wins).")
|
|
13874
13878
|
}).optional().describe("LiteLLM routing config \u2014 opt-in per-agent virtual-key auto-provisioning " + "+ routing env. Default OFF. See LiteLLMConfigSchema doc for the full flow.");
|
|
13879
|
+
HindsightPerOpLlmSchema = exports_external.object({
|
|
13880
|
+
model: exports_external.string().min(1).optional().describe("Per-op model (upstream `HINDSIGHT_API_<OP>_LLM_MODEL`). Absent \u2192 " + "inherit the global `hindsight.llm.model`."),
|
|
13881
|
+
provider: exports_external.string().min(1).optional().describe("Per-op provider (upstream `HINDSIGHT_API_<OP>_LLM_PROVIDER`). " + "Absent \u2192 inherit the global `hindsight.llm.provider`."),
|
|
13882
|
+
base_url: exports_external.string().min(1).optional().describe("Per-op base URL (upstream `HINDSIGHT_API_<OP>_LLM_BASE_URL`). " + "Optional passthrough; absent \u2192 inherit the global."),
|
|
13883
|
+
api_key: exports_external.string().min(1).optional().describe("Per-op API key (upstream `HINDSIGHT_API_<OP>_LLM_API_KEY`). Literal " + "or `vault:` reference. Optional passthrough; absent \u2192 inherit global.")
|
|
13884
|
+
}).describe("Per-operation LLM override. Every field optional; an unset field (or " + "an omitted op block) inherits the global `hindsight.llm.*`, which is " + "already the engine's fallback \u2014 switchroom emits only the vars set.");
|
|
13885
|
+
HindsightConfigSchema = exports_external.object({
|
|
13886
|
+
llm: exports_external.object({
|
|
13887
|
+
provider: exports_external.string().min(1).optional().describe("Hindsight LLM provider (upstream `HINDSIGHT_API_LLM_PROVIDER`). " + "Defaults to `claude-code` (subscription-honest, broker-fed OAuth). " + "Any litellm-routable provider the upstream image supports is valid. " + "Serves as the GLOBAL default for every op absent a per-op override."),
|
|
13888
|
+
model: exports_external.string().min(1).optional().describe("Hindsight LLM model (upstream `HINDSIGHT_API_LLM_MODEL`). Defaults " + "to HINDSIGHT_DEFAULT_MODEL. Any model your LiteLLM proxy can route " + "is valid, e.g. `openrouter/z-ai/glm-5.2` when routing through the " + "fleet proxy. With provider=claude-code this value is ALSO exported " + "as `ANTHROPIC_MODEL` to the claude subprocess. Serves as the GLOBAL " + "default for every op absent a per-op override."),
|
|
13889
|
+
retain: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `retain` LLM op (memory ingestion). Emits " + "`HINDSIGHT_API_RETAIN_LLM_*`. Absent \u2192 uses the global model/provider."),
|
|
13890
|
+
reflect: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `reflect` LLM op (synthesis / mental-model " + "refresh). Emits `HINDSIGHT_API_REFLECT_LLM_*`. Absent \u2192 uses global."),
|
|
13891
|
+
consolidation: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `consolidation` LLM op (background memory " + "merge). Emits `HINDSIGHT_API_CONSOLIDATION_LLM_*`. Absent \u2192 global.")
|
|
13892
|
+
}).optional().describe("LLM knob for the hindsight container. The flat `provider`/`model` set " + "the global default (backward-compatible); optional `retain`/`reflect`/" + "`consolidation` blocks override individual ops. All fields optional; " + "unset fields fall back to the hard-coded defaults.")
|
|
13893
|
+
});
|
|
13875
13894
|
MicrosoftWorkspaceConfigSchema = exports_external.object({
|
|
13876
13895
|
microsoft_client_id: exports_external.string().min(1).optional().describe("Microsoft OAuth application (client) ID from Entra portal " + "(literal string or vault reference e.g. " + "'vault:microsoft-oauth-client-id'). OPTIONAL \u2014 omit it to use " + "switchroom's shipped default Microsoft app (zero-config). " + "Set it only to bring your own Entra app (BYO)."),
|
|
13877
13896
|
microsoft_client_secret: exports_external.string().min(1).optional().describe("Microsoft OAuth client secret. Optional \u2014 public-client apps " + "(Mobile + Desktop platform with 'Allow public client flows' " + "enabled) work without a secret; confidential clients pass " + "one. Either literal or vault reference e.g. " + "'vault:microsoft-oauth-client-secret'."),
|
|
@@ -14120,6 +14139,7 @@ var init_schema = __esm(() => {
|
|
|
14120
14139
|
config: exports_external.object({
|
|
14121
14140
|
provider: exports_external.string().default("ollama").describe("Embedding provider (ollama, openai, anthropic)"),
|
|
14122
14141
|
model: exports_external.string().optional().describe("Embedding model (e.g., 'nomic-embed-text')"),
|
|
14142
|
+
llm_model: exports_external.string().optional().describe("LiteLLM model name for Hindsight's LLM ops (retain/reflect/" + "consolidation) when the top-level `litellm.enabled` carve-out is " + "on. Defaults to a cheap OpenRouter model (routed via LiteLLM's " + "model-mapped path, not the Anthropic OAuth pass-through) to keep " + "background memory-op cost off the Claude subscription quota. Set " + "to a `claude-*` model name to route it back through the OAuth " + "pass-through instead. Has no effect when litellm is disabled."),
|
|
14123
14143
|
api_key: exports_external.string().optional().describe("API key or vault reference for embedding provider"),
|
|
14124
14144
|
docker_service: exports_external.boolean().default(true).describe("Whether to include Hindsight in docker-compose"),
|
|
14125
14145
|
url: exports_external.string().url("Hindsight URL must be a valid URL (no shell-special characters)").optional().describe("Hindsight MCP endpoint URL (e.g., http://localhost:18888/mcp/). Defaults to http://localhost:8888/mcp/.")
|
|
@@ -14197,6 +14217,7 @@ var init_schema = __esm(() => {
|
|
|
14197
14217
|
telegram: TelegramConfigSchema,
|
|
14198
14218
|
release: ReleaseBlock.optional().describe("Fleet-wide default release-channel pin / pointer for the update " + "flow. Either `channel` (dev|rc|latest) or `pin` (sha-<hex>|v<semver>) " + "\u2014 mutually exclusive. Per-agent `release` REPLACES this entirely."),
|
|
14199
14219
|
memory: MemoryBackendConfigSchema.optional(),
|
|
14220
|
+
hindsight: HindsightConfigSchema.optional().describe("Fleet-singleton Hindsight (memory backend) configuration. Currently " + "just the LLM knob (provider + model) used for retain/reflect/" + "consolidation. Both fields optional; when unset the container falls " + "back to provider=claude-code + the hard-coded HINDSIGHT_DEFAULT_MODEL " + "so nothing changes for operators who don't set it. Read at container " + "launch by startHindsight() (src/setup/hindsight.ts) \u2014 takes effect on " + "the next `switchroom apply` / `memory setup --recreate`."),
|
|
14200
14221
|
vault: VaultConfigSchema.optional(),
|
|
14201
14222
|
auth: exports_external.object({
|
|
14202
14223
|
active: exports_external.string().min(1).optional().describe("Fleet-wide active Anthropic account label. Every agent without " + "an explicit `agent.auth.override` uses this account. See " + "docs/auth.md for the full model. Set by `switchroom auth use <label>`."),
|
|
@@ -14645,6 +14666,11 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
14645
14666
|
continue;
|
|
14646
14667
|
if (k === "recall" && base.recall && typeof v === "object" && v !== null && !Array.isArray(v)) {
|
|
14647
14668
|
combined[k] = { ...base.recall, ...v };
|
|
14669
|
+
} else if (k === "retain" && base.retain && typeof v === "object" && v !== null && !Array.isArray(v)) {
|
|
14670
|
+
combined[k] = {
|
|
14671
|
+
...base.retain,
|
|
14672
|
+
...v
|
|
14673
|
+
};
|
|
14648
14674
|
} else if (k === "disposition" && base.disposition && typeof v === "object" && v !== null && !Array.isArray(v)) {
|
|
14649
14675
|
combined[k] = {
|
|
14650
14676
|
...base.disposition,
|
|
@@ -15098,7 +15124,7 @@ function resolveAgentsDir(config) {
|
|
|
15098
15124
|
if (override && override.length > 0 && override.startsWith("/")) {
|
|
15099
15125
|
return override;
|
|
15100
15126
|
}
|
|
15101
|
-
return resolveDualPath(config.switchroom.agents_dir);
|
|
15127
|
+
return resolveDualPath(config.switchroom.agents_dir || "~/.switchroom/agents");
|
|
15102
15128
|
}
|
|
15103
15129
|
function resolvePath(pathStr) {
|
|
15104
15130
|
return resolveDualPath(pathStr);
|
|
@@ -15155,6 +15181,374 @@ var init_users = __esm(() => {
|
|
|
15155
15181
|
init_merge();
|
|
15156
15182
|
});
|
|
15157
15183
|
|
|
15184
|
+
// src/agents/pane-lock.ts
|
|
15185
|
+
function withPaneLock(key, fn) {
|
|
15186
|
+
const tail = tails.get(key) ?? Promise.resolve();
|
|
15187
|
+
const run = tail.then(() => fn());
|
|
15188
|
+
const next = run.then(() => {}, () => {});
|
|
15189
|
+
tails.set(key, next);
|
|
15190
|
+
next.then(() => {
|
|
15191
|
+
if (tails.get(key) === next)
|
|
15192
|
+
tails.delete(key);
|
|
15193
|
+
});
|
|
15194
|
+
return run;
|
|
15195
|
+
}
|
|
15196
|
+
var tails;
|
|
15197
|
+
var init_pane_lock = __esm(() => {
|
|
15198
|
+
tails = new Map;
|
|
15199
|
+
});
|
|
15200
|
+
|
|
15201
|
+
// src/agents/inject.ts
|
|
15202
|
+
var exports_inject = {};
|
|
15203
|
+
__export(exports_inject, {
|
|
15204
|
+
validateInjectCommand: () => validateInjectCommand,
|
|
15205
|
+
normalizeInjectCommand: () => normalizeInjectCommand,
|
|
15206
|
+
makeTmuxRunner: () => makeTmuxRunner,
|
|
15207
|
+
isTuiChromeLine: () => isTuiChromeLine,
|
|
15208
|
+
injectSlashCommandWith: () => injectSlashCommandWith,
|
|
15209
|
+
injectSlashCommand: () => injectSlashCommand,
|
|
15210
|
+
diffPane: () => diffPane,
|
|
15211
|
+
dedupeInjectQueue: () => dedupeInjectQueue,
|
|
15212
|
+
InjectError: () => InjectError,
|
|
15213
|
+
INJECT_COMMANDS: () => INJECT_COMMANDS,
|
|
15214
|
+
INJECT_BLOCKLIST: () => INJECT_BLOCKLIST,
|
|
15215
|
+
INJECT_BLOCKED: () => INJECT_BLOCKED,
|
|
15216
|
+
INJECT_ALLOWLIST: () => INJECT_ALLOWLIST
|
|
15217
|
+
});
|
|
15218
|
+
import { execFileSync } from "node:child_process";
|
|
15219
|
+
function validateInjectCommand(command) {
|
|
15220
|
+
if (typeof command !== "string" || command.trim().length === 0) {
|
|
15221
|
+
throw new InjectError("invalid", "command is empty");
|
|
15222
|
+
}
|
|
15223
|
+
const trimmed = command.trim();
|
|
15224
|
+
if (!trimmed.startsWith("/")) {
|
|
15225
|
+
throw new InjectError("invalid", `command must start with "/": got ${trimmed}`);
|
|
15226
|
+
}
|
|
15227
|
+
const bare = trimmed.split(/\s+/, 1)[0].toLowerCase();
|
|
15228
|
+
const blockedMeta = INJECT_BLOCKED.get(bare);
|
|
15229
|
+
if (blockedMeta) {
|
|
15230
|
+
throw new InjectError("blocked", `${bare} is explicitly blocked from inject (${blockedMeta.reason}).`);
|
|
15231
|
+
}
|
|
15232
|
+
if (!INJECT_COMMANDS.has(bare)) {
|
|
15233
|
+
const allowed = [...INJECT_COMMANDS.keys()].sort().join(", ");
|
|
15234
|
+
throw new InjectError("not_allowed", `${bare} is not in the inject allowlist. Allowed: ${allowed}`);
|
|
15235
|
+
}
|
|
15236
|
+
return bare;
|
|
15237
|
+
}
|
|
15238
|
+
function normalizeInjectCommand(command) {
|
|
15239
|
+
return command.trim().replace(/\s+/g, " ").toLowerCase();
|
|
15240
|
+
}
|
|
15241
|
+
function dedupeInjectQueue(commands) {
|
|
15242
|
+
const seen = new Set;
|
|
15243
|
+
const out = [];
|
|
15244
|
+
for (const raw of commands) {
|
|
15245
|
+
if (typeof raw !== "string")
|
|
15246
|
+
continue;
|
|
15247
|
+
const key = normalizeInjectCommand(raw);
|
|
15248
|
+
if (key.length === 0)
|
|
15249
|
+
continue;
|
|
15250
|
+
if (seen.has(key))
|
|
15251
|
+
continue;
|
|
15252
|
+
seen.add(key);
|
|
15253
|
+
out.push(raw.trim());
|
|
15254
|
+
}
|
|
15255
|
+
return out;
|
|
15256
|
+
}
|
|
15257
|
+
function defaultSocketName(agentName) {
|
|
15258
|
+
return `switchroom-${agentName}`;
|
|
15259
|
+
}
|
|
15260
|
+
function makeTmuxRunner(tmuxBin) {
|
|
15261
|
+
return {
|
|
15262
|
+
capture(socket, session) {
|
|
15263
|
+
try {
|
|
15264
|
+
return execFileSync(tmuxBin, ["-L", socket, "capture-pane", "-p", "-t", session, "-S", "-200"], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
15265
|
+
} catch {
|
|
15266
|
+
return null;
|
|
15267
|
+
}
|
|
15268
|
+
},
|
|
15269
|
+
send(socket, session, args) {
|
|
15270
|
+
const [subcmd, ...rest] = args;
|
|
15271
|
+
const flagEnd = rest.findIndex((a) => !a.startsWith("-"));
|
|
15272
|
+
const flagsBeforeKeys = flagEnd === -1 ? rest : rest.slice(0, flagEnd);
|
|
15273
|
+
const keys = flagEnd === -1 ? [] : rest.slice(flagEnd);
|
|
15274
|
+
execFileSync(tmuxBin, ["-L", socket, subcmd, ...flagsBeforeKeys, "-t", session, ...keys], { stdio: ["pipe", "pipe", "pipe"] });
|
|
15275
|
+
},
|
|
15276
|
+
hasSession(socket, session) {
|
|
15277
|
+
try {
|
|
15278
|
+
execFileSync(tmuxBin, ["-L", socket, "has-session", "-t", session], {
|
|
15279
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
15280
|
+
});
|
|
15281
|
+
return true;
|
|
15282
|
+
} catch {
|
|
15283
|
+
return false;
|
|
15284
|
+
}
|
|
15285
|
+
}
|
|
15286
|
+
};
|
|
15287
|
+
}
|
|
15288
|
+
function sleep(ms) {
|
|
15289
|
+
return new Promise((res) => setTimeout(res, ms));
|
|
15290
|
+
}
|
|
15291
|
+
function isTuiChromeLine(line) {
|
|
15292
|
+
const t = line.trim();
|
|
15293
|
+
if (t.length === 0)
|
|
15294
|
+
return false;
|
|
15295
|
+
if (/^[\s\-_\u2502\u2500\u256d\u256e\u2570\u256f\u250c\u2510\u2514\u2518|]+$/.test(t))
|
|
15296
|
+
return true;
|
|
15297
|
+
if (/^[\u256d\u256e\u2570\u256f\u2502\s]*[>)\u276f][\u256d\u256e\u2570\u256f\u2502\s]*$/.test(t))
|
|
15298
|
+
return true;
|
|
15299
|
+
if (/accept edits on/i.test(t))
|
|
15300
|
+
return true;
|
|
15301
|
+
if (/shift\+tab to cycle/i.test(t))
|
|
15302
|
+
return true;
|
|
15303
|
+
if (/for agents/i.test(t))
|
|
15304
|
+
return true;
|
|
15305
|
+
if (/\? for shortcuts/i.test(t))
|
|
15306
|
+
return true;
|
|
15307
|
+
if (/bypassing permissions/i.test(t))
|
|
15308
|
+
return true;
|
|
15309
|
+
if (t === "copy")
|
|
15310
|
+
return true;
|
|
15311
|
+
return false;
|
|
15312
|
+
}
|
|
15313
|
+
function diffPane(before, after, command) {
|
|
15314
|
+
if (command) {
|
|
15315
|
+
const escaped = command.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
15316
|
+
const lines = after.split(`
|
|
15317
|
+
`);
|
|
15318
|
+
let anchorIdx = -1;
|
|
15319
|
+
for (let i = lines.length - 1;i >= 0; i--) {
|
|
15320
|
+
if (/[\u276f>]\s+/.test(lines[i]) && lines[i].includes(escaped)) {
|
|
15321
|
+
anchorIdx = i;
|
|
15322
|
+
break;
|
|
15323
|
+
}
|
|
15324
|
+
}
|
|
15325
|
+
if (anchorIdx >= 0) {
|
|
15326
|
+
const tail = lines.slice(anchorIdx + 1);
|
|
15327
|
+
const trimmed = [];
|
|
15328
|
+
for (const raw of tail) {
|
|
15329
|
+
const line = raw.trimEnd();
|
|
15330
|
+
if (line.length === 0 && trimmed.length === 0)
|
|
15331
|
+
continue;
|
|
15332
|
+
if (isTuiChromeLine(line))
|
|
15333
|
+
continue;
|
|
15334
|
+
trimmed.push(line);
|
|
15335
|
+
}
|
|
15336
|
+
while (trimmed.length > 0 && trimmed[trimmed.length - 1].length === 0) {
|
|
15337
|
+
trimmed.pop();
|
|
15338
|
+
}
|
|
15339
|
+
const affordances = /^(esc to cancel|press any key|\u21b5 select)/i;
|
|
15340
|
+
while (trimmed.length > 0 && affordances.test(trimmed[trimmed.length - 1].trim())) {
|
|
15341
|
+
trimmed.pop();
|
|
15342
|
+
}
|
|
15343
|
+
if (trimmed.length > 0) {
|
|
15344
|
+
return { output: trimmed.join(`
|
|
15345
|
+
`), anchored: true };
|
|
15346
|
+
}
|
|
15347
|
+
}
|
|
15348
|
+
}
|
|
15349
|
+
const beforeSet = new Set(before.split(`
|
|
15350
|
+
`).map((l) => l.trimEnd()));
|
|
15351
|
+
const newLines = [];
|
|
15352
|
+
for (const raw of after.split(`
|
|
15353
|
+
`)) {
|
|
15354
|
+
const line = raw.trimEnd();
|
|
15355
|
+
if (line.length === 0)
|
|
15356
|
+
continue;
|
|
15357
|
+
if (beforeSet.has(line))
|
|
15358
|
+
continue;
|
|
15359
|
+
if (isTuiChromeLine(line))
|
|
15360
|
+
continue;
|
|
15361
|
+
newLines.push(line);
|
|
15362
|
+
}
|
|
15363
|
+
return { output: newLines.join(`
|
|
15364
|
+
`), anchored: false };
|
|
15365
|
+
}
|
|
15366
|
+
async function injectSlashCommand(agentName, command, opts = {}) {
|
|
15367
|
+
validateInjectCommand(command);
|
|
15368
|
+
const tmuxBin = opts.tmuxBin ?? "tmux";
|
|
15369
|
+
const socket = opts.socketName ?? defaultSocketName(agentName);
|
|
15370
|
+
const session = opts.sessionName ?? agentName;
|
|
15371
|
+
const settleMs = opts.settleMs ?? 2000;
|
|
15372
|
+
const timeoutMs = opts.timeoutMs ?? 5000;
|
|
15373
|
+
return withPaneLock(`${socket}:${session}`, () => injectSlashCommandWith(makeTmuxRunner(tmuxBin), {
|
|
15374
|
+
socket,
|
|
15375
|
+
session,
|
|
15376
|
+
command: command.trim(),
|
|
15377
|
+
settleMs,
|
|
15378
|
+
timeoutMs
|
|
15379
|
+
}));
|
|
15380
|
+
}
|
|
15381
|
+
async function injectSlashCommandWith(runner, args) {
|
|
15382
|
+
const { socket, session, command, settleMs, timeoutMs } = args;
|
|
15383
|
+
let bareVerb;
|
|
15384
|
+
try {
|
|
15385
|
+
bareVerb = validateInjectCommand(command);
|
|
15386
|
+
} catch (err) {
|
|
15387
|
+
if (err instanceof InjectError) {
|
|
15388
|
+
return {
|
|
15389
|
+
outcome: "failed",
|
|
15390
|
+
output: "",
|
|
15391
|
+
truncated: false,
|
|
15392
|
+
command: command.trim().split(/\s+/, 1)[0]?.toLowerCase() ?? "",
|
|
15393
|
+
meta: null,
|
|
15394
|
+
errorCode: err.code,
|
|
15395
|
+
errorMessage: err.message
|
|
15396
|
+
};
|
|
15397
|
+
}
|
|
15398
|
+
throw err;
|
|
15399
|
+
}
|
|
15400
|
+
const meta = INJECT_COMMANDS.get(bareVerb) ?? null;
|
|
15401
|
+
if (!runner.hasSession(socket, session)) {
|
|
15402
|
+
return {
|
|
15403
|
+
outcome: "failed",
|
|
15404
|
+
output: "",
|
|
15405
|
+
truncated: false,
|
|
15406
|
+
command: bareVerb,
|
|
15407
|
+
meta,
|
|
15408
|
+
errorCode: "session_missing",
|
|
15409
|
+
errorMessage: `tmux session "${session}" on socket "${socket}" not found. ` + `Is the agent running under the tmux supervisor (the default)? ` + `If experimental.legacy_pty=true is set, inject is unsupported.`
|
|
15410
|
+
};
|
|
15411
|
+
}
|
|
15412
|
+
const before = runner.capture(socket, session) ?? "";
|
|
15413
|
+
try {
|
|
15414
|
+
runner.send(socket, session, ["send-keys", "-l", command]);
|
|
15415
|
+
runner.send(socket, session, ["send-keys", "Enter"]);
|
|
15416
|
+
} catch (err) {
|
|
15417
|
+
return {
|
|
15418
|
+
outcome: "failed",
|
|
15419
|
+
output: "",
|
|
15420
|
+
truncated: false,
|
|
15421
|
+
command: bareVerb,
|
|
15422
|
+
meta,
|
|
15423
|
+
errorCode: "tmux_failed",
|
|
15424
|
+
errorMessage: `tmux send-keys failed: ${err instanceof Error ? err.message : String(err)}`
|
|
15425
|
+
};
|
|
15426
|
+
}
|
|
15427
|
+
const start = Date.now();
|
|
15428
|
+
let last = before;
|
|
15429
|
+
let stableSince = null;
|
|
15430
|
+
while (Date.now() - start < timeoutMs) {
|
|
15431
|
+
await sleep(POLL_INTERVAL_MS);
|
|
15432
|
+
const cur = runner.capture(socket, session) ?? "";
|
|
15433
|
+
if (cur === last && cur !== before) {
|
|
15434
|
+
if (stableSince === null) {
|
|
15435
|
+
stableSince = Date.now();
|
|
15436
|
+
} else if (Date.now() - stableSince >= POLL_INTERVAL_MS) {
|
|
15437
|
+
last = cur;
|
|
15438
|
+
break;
|
|
15439
|
+
}
|
|
15440
|
+
} else {
|
|
15441
|
+
stableSince = null;
|
|
15442
|
+
}
|
|
15443
|
+
last = cur;
|
|
15444
|
+
if (Date.now() - start >= settleMs && cur !== before) {
|
|
15445
|
+
break;
|
|
15446
|
+
}
|
|
15447
|
+
}
|
|
15448
|
+
const { output: rawOutput, anchored } = diffPane(before, last, command);
|
|
15449
|
+
let output = rawOutput;
|
|
15450
|
+
let truncated = false;
|
|
15451
|
+
const bytes = Buffer.byteLength(output, "utf-8");
|
|
15452
|
+
if (bytes > OUTPUT_BYTE_CAP) {
|
|
15453
|
+
while (Buffer.byteLength(output, "utf-8") > OUTPUT_BYTE_CAP) {
|
|
15454
|
+
output = output.slice(Math.floor(output.length * 0.1) + 1);
|
|
15455
|
+
}
|
|
15456
|
+
truncated = true;
|
|
15457
|
+
}
|
|
15458
|
+
if (meta?.dialog) {
|
|
15459
|
+
try {
|
|
15460
|
+
runner.send(socket, session, ["send-keys", "Escape"]);
|
|
15461
|
+
} catch {}
|
|
15462
|
+
}
|
|
15463
|
+
if (output.trim().length === 0) {
|
|
15464
|
+
return {
|
|
15465
|
+
outcome: "ok_no_output",
|
|
15466
|
+
output: "",
|
|
15467
|
+
truncated: false,
|
|
15468
|
+
command: bareVerb,
|
|
15469
|
+
meta,
|
|
15470
|
+
...anchored ? {} : { diagnostic: "anchor_missing" }
|
|
15471
|
+
};
|
|
15472
|
+
}
|
|
15473
|
+
return {
|
|
15474
|
+
outcome: "ok",
|
|
15475
|
+
output,
|
|
15476
|
+
truncated,
|
|
15477
|
+
command: bareVerb,
|
|
15478
|
+
meta,
|
|
15479
|
+
...truncated ? { diagnostic: "truncated_output" } : {}
|
|
15480
|
+
};
|
|
15481
|
+
}
|
|
15482
|
+
var INJECT_COMMANDS, INJECT_ALLOWLIST, INJECT_BLOCKED, INJECT_BLOCKLIST, InjectError, POLL_INTERVAL_MS = 150, OUTPUT_BYTE_CAP = 3000;
|
|
15483
|
+
var init_inject = __esm(() => {
|
|
15484
|
+
init_pane_lock();
|
|
15485
|
+
INJECT_COMMANDS = new Map([
|
|
15486
|
+
["/cost", { description: "Show session cost", expectsOutput: true, dialog: true }],
|
|
15487
|
+
["/status", { description: "Show session status", expectsOutput: true, dialog: true }],
|
|
15488
|
+
["/usage", { description: "Show plan quota", expectsOutput: true, dialog: true }],
|
|
15489
|
+
["/hooks", { description: "List configured hooks", expectsOutput: true, dialog: true }],
|
|
15490
|
+
["/memory", { description: "Open memory picker", expectsOutput: true, dialog: true }],
|
|
15491
|
+
["/model", { description: "Open model picker", expectsOutput: true }],
|
|
15492
|
+
[
|
|
15493
|
+
"/clear",
|
|
15494
|
+
{
|
|
15495
|
+
description: "Clear session screen",
|
|
15496
|
+
expectsOutput: false,
|
|
15497
|
+
silentNote: "context cleared \u2014 fresh slate"
|
|
15498
|
+
}
|
|
15499
|
+
],
|
|
15500
|
+
[
|
|
15501
|
+
"/compact",
|
|
15502
|
+
{
|
|
15503
|
+
description: "Compact conversation history",
|
|
15504
|
+
expectsOutput: false,
|
|
15505
|
+
silentNote: "compaction runs silently"
|
|
15506
|
+
}
|
|
15507
|
+
]
|
|
15508
|
+
]);
|
|
15509
|
+
INJECT_ALLOWLIST = new Set(INJECT_COMMANDS.keys());
|
|
15510
|
+
INJECT_BLOCKED = new Map([
|
|
15511
|
+
["/login", { reason: "would mutate auth state" }],
|
|
15512
|
+
["/logout", { reason: "would terminate the agent's auth session" }],
|
|
15513
|
+
["/exit", { reason: "would kill the agent process" }],
|
|
15514
|
+
["/quit", { reason: "would kill the agent process" }],
|
|
15515
|
+
[
|
|
15516
|
+
"/effort",
|
|
15517
|
+
{
|
|
15518
|
+
reason: "leaves a blocking confirmation modal open and wedges the pane; use the /effort command (it drives the modal), not raw inject"
|
|
15519
|
+
}
|
|
15520
|
+
]
|
|
15521
|
+
]);
|
|
15522
|
+
INJECT_BLOCKLIST = new Set(INJECT_BLOCKED.keys());
|
|
15523
|
+
InjectError = class InjectError extends Error {
|
|
15524
|
+
code;
|
|
15525
|
+
constructor(code, message) {
|
|
15526
|
+
super(message);
|
|
15527
|
+
this.name = "InjectError";
|
|
15528
|
+
this.code = code;
|
|
15529
|
+
}
|
|
15530
|
+
};
|
|
15531
|
+
});
|
|
15532
|
+
|
|
15533
|
+
// src/agents/model-picker.ts
|
|
15534
|
+
var init_model_picker = __esm(() => {
|
|
15535
|
+
init_inject();
|
|
15536
|
+
init_pane_lock();
|
|
15537
|
+
});
|
|
15538
|
+
|
|
15539
|
+
// telegram-plugin/gateway/model-command.ts
|
|
15540
|
+
function isClaudeModel(name) {
|
|
15541
|
+
const lower = name.toLowerCase();
|
|
15542
|
+
if (MODEL_ALIASES.includes(lower))
|
|
15543
|
+
return true;
|
|
15544
|
+
return lower.startsWith("claude-");
|
|
15545
|
+
}
|
|
15546
|
+
var MODEL_ALIASES;
|
|
15547
|
+
var init_model_command = __esm(() => {
|
|
15548
|
+
init_model_picker();
|
|
15549
|
+
MODEL_ALIASES = ["opus", "sonnet", "haiku", "fable", "default"];
|
|
15550
|
+
});
|
|
15551
|
+
|
|
15158
15552
|
// src/util/atomic.ts
|
|
15159
15553
|
var exports_atomic = {};
|
|
15160
15554
|
__export(exports_atomic, {
|
|
@@ -15223,7 +15617,7 @@ var init_atomic = __esm(() => {
|
|
|
15223
15617
|
});
|
|
15224
15618
|
|
|
15225
15619
|
// src/setup/hindsight.ts
|
|
15226
|
-
import { execFileSync } from "node:child_process";
|
|
15620
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
15227
15621
|
import { createServer } from "node:net";
|
|
15228
15622
|
function hindsightImageRef(tag) {
|
|
15229
15623
|
const t = tag?.trim();
|
|
@@ -15249,7 +15643,7 @@ function describePortHolder(port) {
|
|
|
15249
15643
|
];
|
|
15250
15644
|
for (const [cmd, args] of probes) {
|
|
15251
15645
|
try {
|
|
15252
|
-
const out =
|
|
15646
|
+
const out = execFileSync2(cmd, args, {
|
|
15253
15647
|
stdio: "pipe",
|
|
15254
15648
|
encoding: "utf-8"
|
|
15255
15649
|
}).trim();
|
|
@@ -15306,7 +15700,7 @@ async function pickHindsightPorts() {
|
|
|
15306
15700
|
}
|
|
15307
15701
|
function isDockerAvailable() {
|
|
15308
15702
|
try {
|
|
15309
|
-
|
|
15703
|
+
execFileSync2("docker", ["--version"], { stdio: "pipe" });
|
|
15310
15704
|
return true;
|
|
15311
15705
|
} catch {
|
|
15312
15706
|
return false;
|
|
@@ -15314,7 +15708,7 @@ function isDockerAvailable() {
|
|
|
15314
15708
|
}
|
|
15315
15709
|
function isHindsightRunning() {
|
|
15316
15710
|
try {
|
|
15317
|
-
const output =
|
|
15711
|
+
const output = execFileSync2("docker", ["ps", "--filter", "name=switchroom-hindsight", "--format", "{{.Status}}"], { stdio: "pipe", encoding: "utf-8" });
|
|
15318
15712
|
return output.trim().length > 0;
|
|
15319
15713
|
} catch {
|
|
15320
15714
|
return false;
|
|
@@ -15322,24 +15716,54 @@ function isHindsightRunning() {
|
|
|
15322
15716
|
}
|
|
15323
15717
|
function isHindsightContainerExists() {
|
|
15324
15718
|
try {
|
|
15325
|
-
const output =
|
|
15719
|
+
const output = execFileSync2("docker", ["ps", "-a", "--filter", "name=switchroom-hindsight", "--format", "{{.Names}}"], { stdio: "pipe", encoding: "utf-8" });
|
|
15326
15720
|
return output.trim().length > 0;
|
|
15327
15721
|
} catch {
|
|
15328
15722
|
return false;
|
|
15329
15723
|
}
|
|
15330
15724
|
}
|
|
15331
|
-
function
|
|
15725
|
+
function resolveHindsightPerOpLlm(llm) {
|
|
15726
|
+
const out = [];
|
|
15727
|
+
if (!llm)
|
|
15728
|
+
return out;
|
|
15729
|
+
for (const op of HINDSIGHT_LLM_OPS) {
|
|
15730
|
+
const cfg = llm[op];
|
|
15731
|
+
if (!cfg)
|
|
15732
|
+
continue;
|
|
15733
|
+
const prefix = `HINDSIGHT_API_${op.toUpperCase()}_LLM`;
|
|
15734
|
+
const model = cfg.model?.trim();
|
|
15735
|
+
const provider = cfg.provider?.trim();
|
|
15736
|
+
const baseUrl = cfg.base_url?.trim();
|
|
15737
|
+
const apiKey = cfg.api_key?.trim();
|
|
15738
|
+
if (model)
|
|
15739
|
+
out.push([`${prefix}_MODEL`, model]);
|
|
15740
|
+
if (provider)
|
|
15741
|
+
out.push([`${prefix}_PROVIDER`, provider]);
|
|
15742
|
+
if (baseUrl)
|
|
15743
|
+
out.push([`${prefix}_BASE_URL`, baseUrl]);
|
|
15744
|
+
if (apiKey)
|
|
15745
|
+
out.push([`${prefix}_API_KEY`, apiKey]);
|
|
15746
|
+
}
|
|
15747
|
+
return out;
|
|
15748
|
+
}
|
|
15749
|
+
function resolveHindsightLlm(llm, litellm) {
|
|
15750
|
+
const provider = llm?.provider?.trim() || "claude-code";
|
|
15751
|
+
const model = llm?.model?.trim() || (litellm ? litellm.model ?? HINDSIGHT_DEFAULT_LITELLM_MODEL : HINDSIGHT_DEFAULT_MODEL);
|
|
15752
|
+
return { provider, model };
|
|
15753
|
+
}
|
|
15754
|
+
function startHindsight(ports, litellm, imageTag, llm) {
|
|
15332
15755
|
const apiPort = ports?.apiPort ?? HINDSIGHT_DEFAULT_API_PORT;
|
|
15333
15756
|
const uiPort = ports?.uiPort ?? HINDSIGHT_DEFAULT_UI_PORT;
|
|
15757
|
+
const { provider: llmProvider, model: llmModel } = resolveHindsightLlm(llm, litellm);
|
|
15758
|
+
const perOpLlm = resolveHindsightPerOpLlm(llm);
|
|
15334
15759
|
const envArgs = [
|
|
15335
15760
|
"-e",
|
|
15336
15761
|
`HINDSIGHT_API_MAX_OBSERVATIONS_PER_SCOPE=${HINDSIGHT_DEFAULT_MAX_OBSERVATIONS_PER_SCOPE}`,
|
|
15337
15762
|
"-e",
|
|
15338
|
-
|
|
15339
|
-
"-e",
|
|
15340
|
-
`HINDSIGHT_API_LLM_MODEL=${HINDSIGHT_DEFAULT_MODEL}`,
|
|
15763
|
+
`HINDSIGHT_API_LLM_PROVIDER=${llmProvider}`,
|
|
15341
15764
|
"-e",
|
|
15342
|
-
`
|
|
15765
|
+
`HINDSIGHT_API_LLM_MODEL=${llmModel}`,
|
|
15766
|
+
...perOpLlm.flatMap(([k, v]) => ["-e", `${k}=${v}`]),
|
|
15343
15767
|
"-e",
|
|
15344
15768
|
`HINDSIGHT_API_MCP_STATELESS=${HINDSIGHT_DEFAULT_MCP_STATELESS}`,
|
|
15345
15769
|
"-e",
|
|
@@ -15361,8 +15785,13 @@ function startHindsight(ports, litellm, imageTag) {
|
|
|
15361
15785
|
"-e",
|
|
15362
15786
|
`HINDSIGHT_API_CONSOLIDATION_MAX_MEMORIES_PER_ROUND=${HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND}`
|
|
15363
15787
|
];
|
|
15788
|
+
if (llmProvider === "claude-code") {
|
|
15789
|
+
envArgs.push("-e", `ANTHROPIC_MODEL=${llmModel}`);
|
|
15790
|
+
}
|
|
15364
15791
|
if (litellm) {
|
|
15365
|
-
|
|
15792
|
+
const litellmRoot = litellm.baseUrl.replace(/\/+$/, "");
|
|
15793
|
+
const anthropicBaseUrl = isClaudeModel(llmModel) ? `${litellmRoot}/anthropic` : litellmRoot;
|
|
15794
|
+
envArgs.push("-e", `HINDSIGHT_API_PORT=${apiPort}`, "-e", `HINDSIGHT_CP_DATAPLANE_API_URL=http://localhost:${apiPort}`, "-e", `ANTHROPIC_BASE_URL=${anthropicBaseUrl}`, "-e", `ANTHROPIC_CUSTOM_HEADERS=x-litellm-api-key: Bearer ${litellm.apiKey}
|
|
15366
15795
|
x-litellm-customer-id: hindsight
|
|
15367
15796
|
x-litellm-tags: service:hindsight`);
|
|
15368
15797
|
}
|
|
@@ -15394,25 +15823,25 @@ x-litellm-tags: service:hindsight`);
|
|
|
15394
15823
|
args.push("-p", `127.0.0.1:${apiPort}:8888`, "-p", `127.0.0.1:${uiPort}:9999`);
|
|
15395
15824
|
}
|
|
15396
15825
|
args.push("-v", "switchroom-hindsight-data:/home/hindsight/.pg0", "-v", "switchroom-hindsight-backups:/backups", "-v", `${HINDSIGHT_BROKER_SOCK_VOLUME}:/run/switchroom/auth-broker`, "--tmpfs", `/run/claude-creds:rw,mode=0700,uid=${HINDSIGHT_DEFAULT_UID},gid=${HINDSIGHT_DEFAULT_UID}`, ...envArgs, hindsightImageRef(imageTag));
|
|
15397
|
-
|
|
15826
|
+
execFileSync2("docker", args, { stdio: "pipe" });
|
|
15398
15827
|
}
|
|
15399
15828
|
function stopHindsight() {
|
|
15400
15829
|
try {
|
|
15401
|
-
|
|
15830
|
+
execFileSync2("docker", ["stop", "switchroom-hindsight"], { stdio: "pipe" });
|
|
15402
15831
|
} catch {}
|
|
15403
15832
|
try {
|
|
15404
|
-
|
|
15833
|
+
execFileSync2("docker", ["rm", "switchroom-hindsight"], { stdio: "pipe" });
|
|
15405
15834
|
} catch {}
|
|
15406
15835
|
}
|
|
15407
15836
|
function pullHindsightImage(imageTag) {
|
|
15408
|
-
|
|
15837
|
+
execFileSync2("docker", ["pull", hindsightImageRef(imageTag)], { stdio: "inherit" });
|
|
15409
15838
|
}
|
|
15410
15839
|
function getRunningHindsightPorts() {
|
|
15411
15840
|
const CONTAINER_API_PORT = 8888;
|
|
15412
15841
|
const CONTAINER_UI_PORT = 9999;
|
|
15413
15842
|
const readPort = (containerPort) => {
|
|
15414
15843
|
try {
|
|
15415
|
-
const out =
|
|
15844
|
+
const out = execFileSync2("docker", ["port", "switchroom-hindsight", `${containerPort}/tcp`], { stdio: "pipe", encoding: "utf-8" });
|
|
15416
15845
|
const m = out.split(`
|
|
15417
15846
|
`).map((l) => l.trim()).find((l) => l.length > 0);
|
|
15418
15847
|
if (!m)
|
|
@@ -15433,7 +15862,7 @@ function getRunningHindsightPorts() {
|
|
|
15433
15862
|
function getHindsightPortsFromEnv() {
|
|
15434
15863
|
let env2;
|
|
15435
15864
|
try {
|
|
15436
|
-
env2 =
|
|
15865
|
+
env2 = execFileSync2("docker", ["inspect", "--format", "{{.Config.Env}}", "switchroom-hindsight"], { stdio: "pipe", encoding: "utf-8" });
|
|
15437
15866
|
} catch {
|
|
15438
15867
|
return null;
|
|
15439
15868
|
}
|
|
@@ -15453,14 +15882,24 @@ function getHindsightPortsFromEnv() {
|
|
|
15453
15882
|
}
|
|
15454
15883
|
function getHindsightStatus() {
|
|
15455
15884
|
try {
|
|
15456
|
-
const output =
|
|
15885
|
+
const output = execFileSync2("docker", ["ps", "-a", "--filter", "name=switchroom-hindsight", "--format", "{{.Status}}"], { stdio: "pipe", encoding: "utf-8" });
|
|
15457
15886
|
const status = output.trim();
|
|
15458
15887
|
return status.length > 0 ? status : null;
|
|
15459
15888
|
} catch {
|
|
15460
15889
|
return null;
|
|
15461
15890
|
}
|
|
15462
15891
|
}
|
|
15463
|
-
function generateHindsightComposeSnippet() {
|
|
15892
|
+
function generateHindsightComposeSnippet(llm) {
|
|
15893
|
+
const { provider: llmProvider, model: llmModel } = resolveHindsightLlm(llm);
|
|
15894
|
+
const perOpLlm = resolveHindsightPerOpLlm(llm);
|
|
15895
|
+
const environment = [
|
|
15896
|
+
` - HINDSIGHT_API_MAX_OBSERVATIONS_PER_SCOPE=${HINDSIGHT_DEFAULT_MAX_OBSERVATIONS_PER_SCOPE}`,
|
|
15897
|
+
` - HINDSIGHT_API_LLM_PROVIDER=${llmProvider}`,
|
|
15898
|
+
` - HINDSIGHT_API_LLM_MODEL=${llmModel}`,
|
|
15899
|
+
...perOpLlm.map(([k, v]) => ` - ${k}=${v}`),
|
|
15900
|
+
...llmProvider === "claude-code" ? [` - ANTHROPIC_MODEL=${llmModel}`] : [],
|
|
15901
|
+
` - HINDSIGHT_CP_DATAPLANE_API_URL=http://localhost:8888`
|
|
15902
|
+
];
|
|
15464
15903
|
return [
|
|
15465
15904
|
"services:",
|
|
15466
15905
|
" switchroom-hindsight:",
|
|
@@ -15470,10 +15909,7 @@ function generateHindsightComposeSnippet() {
|
|
|
15470
15909
|
` - "127.0.0.1:${HINDSIGHT_DEFAULT_API_PORT}:8888"`,
|
|
15471
15910
|
' - "127.0.0.1:19999:9999"',
|
|
15472
15911
|
" environment:",
|
|
15473
|
-
|
|
15474
|
-
" - HINDSIGHT_API_LLM_PROVIDER=claude-code",
|
|
15475
|
-
` - HINDSIGHT_API_LLM_MODEL=${HINDSIGHT_DEFAULT_MODEL}`,
|
|
15476
|
-
` - ANTHROPIC_MODEL=${HINDSIGHT_DEFAULT_MODEL}`,
|
|
15912
|
+
...environment,
|
|
15477
15913
|
` - HINDSIGHT_API_MCP_STATELESS=${HINDSIGHT_DEFAULT_MCP_STATELESS}`,
|
|
15478
15914
|
` - HINDSIGHT_API_RERANKER_LOCAL_BUCKET_BATCHING=${HINDSIGHT_DEFAULT_RERANKER_BUCKET_BATCHING}`,
|
|
15479
15915
|
` - HINDSIGHT_API_RERANKER_MAX_CANDIDATES=${HINDSIGHT_DEFAULT_RERANKER_MAX_CANDIDATES}`,
|
|
@@ -15564,13 +16000,15 @@ async function ensureHindsightConsumer(configPath, account, uid = HINDSIGHT_DEFA
|
|
|
15564
16000
|
atomicWriteFileSync2(configPath, tail, mode);
|
|
15565
16001
|
return { added: true, reason: "added" };
|
|
15566
16002
|
}
|
|
15567
|
-
var HINDSIGHT_DEFAULT_API_PORT = 18888, HINDSIGHT_DEFAULT_UI_PORT = 9999, HINDSIGHT_DEFAULT_MCP_URL, HINDSIGHT_DEFAULT_API_BASE_URL, HINDSIGHT_DEFAULT_MAX_OBSERVATIONS_PER_SCOPE = 1000, HINDSIGHT_CONSUMER_NAME = "hindsight", HINDSIGHT_DEFAULT_UID = 11000, HINDSIGHT_IMAGE_REPO = "ghcr.io/switchroom/switchroom-hindsight", HINDSIGHT_IMAGE, HINDSIGHT_DEFAULT_MODEL = "claude-sonnet-5", HINDSIGHT_DEFAULT_MCP_STATELESS = true, HINDSIGHT_BROKER_SOCK_VOLUME, HINDSIGHT_DEFAULT_RERANKER_BUCKET_BATCHING = "true", HINDSIGHT_DEFAULT_RERANKER_MAX_CANDIDATES = 150, HINDSIGHT_DEFAULT_RERANKER_LOCAL_MAX_CONCURRENT = 4, HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8, HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_BATCH_SIZE = 12, HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS = 1, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_PARALLELISM = 2, HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 100, HINDSIGHT_DEFAULT_MEM_LIMIT = "8g", HINDSIGHT_DEFAULT_MEM_RESERVATION = "4g", HINDSIGHT_DEFAULT_PIDS_LIMIT = 1000, HINDSIGHT_DEFAULT_SHM_SIZE = "2g", HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.request.urlopen("http://localhost:8888/health",timeout=4).getcode()==200 else 1)', HINDSIGHT_HEALTHCHECK_CMD;
|
|
16003
|
+
var HINDSIGHT_DEFAULT_API_PORT = 18888, HINDSIGHT_DEFAULT_UI_PORT = 9999, HINDSIGHT_DEFAULT_MCP_URL, HINDSIGHT_DEFAULT_API_BASE_URL, HINDSIGHT_DEFAULT_MAX_OBSERVATIONS_PER_SCOPE = 1000, HINDSIGHT_CONSUMER_NAME = "hindsight", HINDSIGHT_DEFAULT_UID = 11000, HINDSIGHT_IMAGE_REPO = "ghcr.io/switchroom/switchroom-hindsight", HINDSIGHT_IMAGE, HINDSIGHT_DEFAULT_MODEL = "claude-sonnet-5", HINDSIGHT_DEFAULT_LITELLM_MODEL = "openrouter/google/gemini-3.1-flash-lite", HINDSIGHT_DEFAULT_MCP_STATELESS = true, HINDSIGHT_BROKER_SOCK_VOLUME, HINDSIGHT_DEFAULT_RERANKER_BUCKET_BATCHING = "true", HINDSIGHT_DEFAULT_RERANKER_MAX_CANDIDATES = 150, HINDSIGHT_DEFAULT_RERANKER_LOCAL_MAX_CONCURRENT = 4, HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8, HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_BATCH_SIZE = 12, HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS = 1, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_PARALLELISM = 2, HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 100, HINDSIGHT_DEFAULT_MEM_LIMIT = "8g", HINDSIGHT_DEFAULT_MEM_RESERVATION = "4g", HINDSIGHT_DEFAULT_PIDS_LIMIT = 1000, HINDSIGHT_DEFAULT_SHM_SIZE = "2g", HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.request.urlopen("http://localhost:8888/health",timeout=4).getcode()==200 else 1)', HINDSIGHT_HEALTHCHECK_CMD, HINDSIGHT_LLM_OPS;
|
|
15568
16004
|
var init_hindsight = __esm(() => {
|
|
16005
|
+
init_model_command();
|
|
15569
16006
|
HINDSIGHT_DEFAULT_MCP_URL = `http://127.0.0.1:${HINDSIGHT_DEFAULT_API_PORT}/mcp/`;
|
|
15570
16007
|
HINDSIGHT_DEFAULT_API_BASE_URL = HINDSIGHT_DEFAULT_MCP_URL.replace(/\/mcp\/?$/, "");
|
|
15571
16008
|
HINDSIGHT_IMAGE = `${HINDSIGHT_IMAGE_REPO}:latest`;
|
|
15572
16009
|
HINDSIGHT_BROKER_SOCK_VOLUME = `auth-broker-${HINDSIGHT_CONSUMER_NAME}-sock`;
|
|
15573
16010
|
HINDSIGHT_HEALTHCHECK_CMD = `python3 -c '${HINDSIGHT_HEALTHCHECK_PY}'`;
|
|
16011
|
+
HINDSIGHT_LLM_OPS = ["retain", "reflect", "consolidation"];
|
|
15574
16012
|
});
|
|
15575
16013
|
|
|
15576
16014
|
// src/memory/hindsight.ts
|
|
@@ -23797,7 +24235,7 @@ function parseSocketIdentity(socketPath, spec) {
|
|
|
23797
24235
|
}
|
|
23798
24236
|
|
|
23799
24237
|
// src/vault/broker/peercred.ts
|
|
23800
|
-
import { execFileSync as
|
|
24238
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
23801
24239
|
import { readFileSync as readFileSync10, readlinkSync as readlinkSync3, fstatSync } from "node:fs";
|
|
23802
24240
|
function socketPathToIdentity(socketPath) {
|
|
23803
24241
|
return parseSocketIdentity(socketPath, {
|
|
@@ -23970,7 +24408,7 @@ function identify(socketPath, socket, execFileSyncOverride) {
|
|
|
23970
24408
|
if (process.platform !== "linux") {
|
|
23971
24409
|
return null;
|
|
23972
24410
|
}
|
|
23973
|
-
const runner = execFileSyncOverride ??
|
|
24411
|
+
const runner = execFileSyncOverride ?? execFileSync3;
|
|
23974
24412
|
let pid = null;
|
|
23975
24413
|
if (socket !== undefined) {
|
|
23976
24414
|
const fd = fdFromSocket(socket);
|
|
@@ -24454,7 +24892,8 @@ function brokerIsComposeManaged() {
|
|
|
24454
24892
|
return fs.existsSync(OPERATOR_SOCKET_PATH);
|
|
24455
24893
|
}
|
|
24456
24894
|
function vaultTokenFilePath(agentSlug) {
|
|
24457
|
-
|
|
24895
|
+
const base = process.env.SWITCHROOM_AGENTS_DIR || join6(homedir4(), ".switchroom", "agents");
|
|
24896
|
+
return join6(base, agentSlug, ".vault-token");
|
|
24458
24897
|
}
|
|
24459
24898
|
function readVaultTokenFile(agentSlug) {
|
|
24460
24899
|
const filePath = vaultTokenFilePath(agentSlug);
|
|
@@ -25263,7 +25702,7 @@ var init_onboarding = __esm(() => {
|
|
|
25263
25702
|
});
|
|
25264
25703
|
|
|
25265
25704
|
// src/repos/bare-clone.ts
|
|
25266
|
-
import { execFileSync as
|
|
25705
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
25267
25706
|
import { existsSync as existsSync13, mkdirSync as mkdirSync8 } from "node:fs";
|
|
25268
25707
|
import { resolve as resolve9 } from "node:path";
|
|
25269
25708
|
function bareClonePath(slug) {
|
|
@@ -25276,7 +25715,7 @@ function ensureBareClone(slug, url) {
|
|
|
25276
25715
|
if (!existsSync13(clonePath)) {
|
|
25277
25716
|
process.stderr.write(`[switchroom] repo "${slug}": cloning ${url} \u2026
|
|
25278
25717
|
`);
|
|
25279
|
-
|
|
25718
|
+
execFileSync4("git", ["clone", "--bare", url, clonePath], {
|
|
25280
25719
|
stdio: ["ignore", "pipe", "pipe"]
|
|
25281
25720
|
});
|
|
25282
25721
|
process.stderr.write(`[switchroom] repo "${slug}": bare clone ready at ${clonePath}
|
|
@@ -25285,7 +25724,7 @@ function ensureBareClone(slug, url) {
|
|
|
25285
25724
|
process.stderr.write(`[switchroom] repo "${slug}": fetching ${clonePath} \u2026
|
|
25286
25725
|
`);
|
|
25287
25726
|
try {
|
|
25288
|
-
|
|
25727
|
+
execFileSync4("git", ["fetch", "--all"], {
|
|
25289
25728
|
cwd: clonePath,
|
|
25290
25729
|
stdio: ["ignore", "pipe", "pipe"]
|
|
25291
25730
|
});
|
|
@@ -25302,7 +25741,7 @@ var init_bare_clone = __esm(() => {
|
|
|
25302
25741
|
});
|
|
25303
25742
|
|
|
25304
25743
|
// src/repos/agent-worktree.ts
|
|
25305
|
-
import { execFileSync as
|
|
25744
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
25306
25745
|
import { existsSync as existsSync14, mkdirSync as mkdirSync9, readdirSync as readdirSync5 } from "node:fs";
|
|
25307
25746
|
import { join as join9, resolve as resolve10 } from "node:path";
|
|
25308
25747
|
function agentBranchName(agentName) {
|
|
@@ -25313,7 +25752,7 @@ function agentWorktreePath(agentDir, slug) {
|
|
|
25313
25752
|
}
|
|
25314
25753
|
function isWorktreeDirty(worktreePath) {
|
|
25315
25754
|
try {
|
|
25316
|
-
const out =
|
|
25755
|
+
const out = execFileSync5("git", ["status", "--porcelain"], {
|
|
25317
25756
|
cwd: worktreePath,
|
|
25318
25757
|
stdio: ["ignore", "pipe", "pipe"],
|
|
25319
25758
|
encoding: "utf-8"
|
|
@@ -25325,7 +25764,7 @@ function isWorktreeDirty(worktreePath) {
|
|
|
25325
25764
|
}
|
|
25326
25765
|
function headShortSha(worktreePath) {
|
|
25327
25766
|
try {
|
|
25328
|
-
return
|
|
25767
|
+
return execFileSync5("git", ["rev-parse", "--short", "HEAD"], {
|
|
25329
25768
|
cwd: worktreePath,
|
|
25330
25769
|
stdio: ["ignore", "pipe", "pipe"],
|
|
25331
25770
|
encoding: "utf-8"
|
|
@@ -25336,7 +25775,7 @@ function headShortSha(worktreePath) {
|
|
|
25336
25775
|
}
|
|
25337
25776
|
function branchExistsInBare(bareClonePath2, branchName) {
|
|
25338
25777
|
try {
|
|
25339
|
-
|
|
25778
|
+
execFileSync5("git", ["rev-parse", "--verify", `refs/heads/${branchName}`], {
|
|
25340
25779
|
cwd: bareClonePath2,
|
|
25341
25780
|
stdio: ["ignore", "pipe", "pipe"]
|
|
25342
25781
|
});
|
|
@@ -25347,7 +25786,7 @@ function branchExistsInBare(bareClonePath2, branchName) {
|
|
|
25347
25786
|
}
|
|
25348
25787
|
function resolveDefaultBranch(bareClonePath2) {
|
|
25349
25788
|
try {
|
|
25350
|
-
const out =
|
|
25789
|
+
const out = execFileSync5("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
|
|
25351
25790
|
cwd: bareClonePath2,
|
|
25352
25791
|
stdio: ["ignore", "pipe", "pipe"],
|
|
25353
25792
|
encoding: "utf-8"
|
|
@@ -25365,12 +25804,12 @@ function ensureAgentWorktree(agentName, slug, bareClonePath2, agentDir) {
|
|
|
25365
25804
|
if (!existsSync14(worktreePath)) {
|
|
25366
25805
|
mkdirSync9(join9(agentDir, "work"), { recursive: true });
|
|
25367
25806
|
if (branchExistsInBare(bareClonePath2, branch)) {
|
|
25368
|
-
|
|
25807
|
+
execFileSync5("git", ["worktree", "add", worktreePath, branch], {
|
|
25369
25808
|
cwd: bareClonePath2,
|
|
25370
25809
|
stdio: ["ignore", "pipe", "pipe"]
|
|
25371
25810
|
});
|
|
25372
25811
|
} else {
|
|
25373
|
-
|
|
25812
|
+
execFileSync5("git", [
|
|
25374
25813
|
"worktree",
|
|
25375
25814
|
"add",
|
|
25376
25815
|
worktreePath,
|
|
@@ -25398,11 +25837,11 @@ function ensureAgentWorktree(agentName, slug, bareClonePath2, agentDir) {
|
|
|
25398
25837
|
};
|
|
25399
25838
|
}
|
|
25400
25839
|
try {
|
|
25401
|
-
|
|
25840
|
+
execFileSync5("git", ["fetch", "origin"], {
|
|
25402
25841
|
cwd: worktreePath,
|
|
25403
25842
|
stdio: ["ignore", "pipe", "pipe"]
|
|
25404
25843
|
});
|
|
25405
|
-
|
|
25844
|
+
execFileSync5("git", ["merge", "--ff-only", `origin/${defaultBranch}`], {
|
|
25406
25845
|
cwd: worktreePath,
|
|
25407
25846
|
stdio: ["ignore", "pipe", "pipe"]
|
|
25408
25847
|
});
|
|
@@ -25420,7 +25859,7 @@ function removeAgentWorktree(agentName, slug, bareClonePath2, agentDir) {
|
|
|
25420
25859
|
const branch = agentBranchName(agentName);
|
|
25421
25860
|
if (existsSync14(worktreePath)) {
|
|
25422
25861
|
try {
|
|
25423
|
-
|
|
25862
|
+
execFileSync5("git", ["worktree", "remove", "--force", worktreePath], {
|
|
25424
25863
|
cwd: bareClonePath2,
|
|
25425
25864
|
stdio: ["ignore", "pipe", "pipe"]
|
|
25426
25865
|
});
|
|
@@ -25432,7 +25871,7 @@ function removeAgentWorktree(agentName, slug, bareClonePath2, agentDir) {
|
|
|
25432
25871
|
}
|
|
25433
25872
|
if (branchExistsInBare(bareClonePath2, branch)) {
|
|
25434
25873
|
try {
|
|
25435
|
-
|
|
25874
|
+
execFileSync5("git", ["branch", "-D", branch], {
|
|
25436
25875
|
cwd: bareClonePath2,
|
|
25437
25876
|
stdio: ["ignore", "pipe", "pipe"]
|
|
25438
25877
|
});
|
|
@@ -25443,7 +25882,7 @@ function removeAgentWorktree(agentName, slug, bareClonePath2, agentDir) {
|
|
|
25443
25882
|
}
|
|
25444
25883
|
}
|
|
25445
25884
|
try {
|
|
25446
|
-
|
|
25885
|
+
execFileSync5("git", ["worktree", "prune"], {
|
|
25447
25886
|
cwd: bareClonePath2,
|
|
25448
25887
|
stdio: ["ignore", "pipe", "pipe"]
|
|
25449
25888
|
});
|
|
@@ -25472,7 +25911,7 @@ import {
|
|
|
25472
25911
|
unlinkSync as unlinkSync4
|
|
25473
25912
|
} from "node:fs";
|
|
25474
25913
|
import { homedir as homedir5 } from "node:os";
|
|
25475
|
-
import { execSync, execFileSync as
|
|
25914
|
+
import { execSync, execFileSync as execFileSync6 } from "node:child_process";
|
|
25476
25915
|
import { join as join10, resolve as resolve11 } from "node:path";
|
|
25477
25916
|
import { createHash as createHash2 } from "node:crypto";
|
|
25478
25917
|
function prependReplyDiscipline(rendered, context) {
|
|
@@ -25606,13 +26045,13 @@ function alignAgentUid(name, agentDir, uid, opts = {}) {
|
|
|
25606
26045
|
`));
|
|
25607
26046
|
}
|
|
25608
26047
|
try {
|
|
25609
|
-
|
|
26048
|
+
execFileSync6("chown", ["-R", `${uid}:${uid}`, ...paths], {
|
|
25610
26049
|
stdio: ["ignore", "ignore", "pipe"]
|
|
25611
26050
|
});
|
|
25612
26051
|
return { chowned: true, paths };
|
|
25613
26052
|
} catch {}
|
|
25614
26053
|
try {
|
|
25615
|
-
|
|
26054
|
+
execFileSync6("sudo", ["chown", "-R", `${uid}:${uid}`, ...paths], {
|
|
25616
26055
|
stdio: "inherit"
|
|
25617
26056
|
});
|
|
25618
26057
|
return { chowned: true, paths };
|
|
@@ -26260,7 +26699,7 @@ Thumbs.db
|
|
|
26260
26699
|
execSync("git add -A", { cwd: workspaceDir, stdio: "pipe" });
|
|
26261
26700
|
const userEmail = process.env.GIT_AUTHOR_EMAIL || "switchroom@localhost";
|
|
26262
26701
|
const userName = process.env.GIT_AUTHOR_NAME || "Switchroom Agent";
|
|
26263
|
-
|
|
26702
|
+
execFileSync6("git", [
|
|
26264
26703
|
"-c",
|
|
26265
26704
|
`user.email=${userEmail}`,
|
|
26266
26705
|
"-c",
|
|
@@ -26298,11 +26737,12 @@ function installHindsightPlugin(agentName, agentDir, switchroomConfig) {
|
|
|
26298
26737
|
}
|
|
26299
26738
|
const destPath = join10(agentDir, ".claude", "plugins", "hindsight-memory");
|
|
26300
26739
|
const additionalBanks = resolveUsers(switchroomConfig, agentName).additionalBanks;
|
|
26740
|
+
const retainConfig = resolveHindsightRetainConfig(switchroomConfig, agentName);
|
|
26301
26741
|
let expectedSettings = null;
|
|
26302
26742
|
try {
|
|
26303
26743
|
const vendorSettingsPath = join10(sourcePath, "settings.json");
|
|
26304
26744
|
if (existsSync15(vendorSettingsPath)) {
|
|
26305
|
-
expectedSettings = renderHindsightSettingsOverrides(readFileSync13(vendorSettingsPath, "utf-8"), additionalBanks);
|
|
26745
|
+
expectedSettings = renderHindsightSettingsOverrides(readFileSync13(vendorSettingsPath, "utf-8"), additionalBanks, retainConfig);
|
|
26306
26746
|
}
|
|
26307
26747
|
} catch {}
|
|
26308
26748
|
const upToDate = dirContentEquals(sourcePath, destPath, expectedSettings != null ? { "settings.json": expectedSettings } : undefined);
|
|
@@ -26312,13 +26752,21 @@ function installHindsightPlugin(agentName, agentDir, switchroomConfig) {
|
|
|
26312
26752
|
}
|
|
26313
26753
|
copyDirRecursive2(sourcePath, destPath);
|
|
26314
26754
|
}
|
|
26315
|
-
applyHindsightSettingsOverrides(destPath, additionalBanks);
|
|
26755
|
+
applyHindsightSettingsOverrides(destPath, additionalBanks, retainConfig);
|
|
26316
26756
|
const bankId = agentMemory?.collection ?? agentName;
|
|
26317
26757
|
const mcpUrl = memory.config?.url ?? HINDSIGHT_DEFAULT_MCP_URL;
|
|
26318
26758
|
const apiBaseUrl = mcpUrl.replace(/\/mcp\/?$/, "").replace(/\/$/, "");
|
|
26319
26759
|
return { pluginDir: destPath, apiBaseUrl, bankId };
|
|
26320
26760
|
}
|
|
26321
|
-
function
|
|
26761
|
+
function resolveHindsightRetainConfig(switchroomConfig, agentName) {
|
|
26762
|
+
const resolved = switchroomConfig ? resolveAgentConfig(switchroomConfig.defaults, switchroomConfig.profiles, switchroomConfig.agents[agentName] ?? {}) : undefined;
|
|
26763
|
+
const retain = resolved?.memory?.retain;
|
|
26764
|
+
return {
|
|
26765
|
+
everyNTurns: retain?.every_n_turns ?? HINDSIGHT_DEFAULT_RETAIN_EVERY_N_TURNS,
|
|
26766
|
+
overlapTurns: retain?.overlap_turns ?? HINDSIGHT_DEFAULT_RETAIN_OVERLAP_TURNS
|
|
26767
|
+
};
|
|
26768
|
+
}
|
|
26769
|
+
function applyHindsightSettingsOverrides(pluginDestPath, additionalBanks, retainConfig) {
|
|
26322
26770
|
const settingsPath = join10(pluginDestPath, "settings.json");
|
|
26323
26771
|
if (!existsSync15(settingsPath))
|
|
26324
26772
|
return;
|
|
@@ -26328,20 +26776,21 @@ function applyHindsightSettingsOverrides(pluginDestPath, additionalBanks) {
|
|
|
26328
26776
|
} catch {
|
|
26329
26777
|
return;
|
|
26330
26778
|
}
|
|
26331
|
-
const next = renderHindsightSettingsOverrides(raw, additionalBanks);
|
|
26779
|
+
const next = renderHindsightSettingsOverrides(raw, additionalBanks, retainConfig);
|
|
26332
26780
|
if (next == null)
|
|
26333
26781
|
return;
|
|
26334
26782
|
writeFileSyncIfChanged(settingsPath, next);
|
|
26335
26783
|
}
|
|
26336
|
-
function renderHindsightSettingsOverrides(raw, additionalBanks) {
|
|
26784
|
+
function renderHindsightSettingsOverrides(raw, additionalBanks, retainConfig) {
|
|
26337
26785
|
let settings;
|
|
26338
26786
|
try {
|
|
26339
26787
|
settings = JSON.parse(raw);
|
|
26340
26788
|
} catch {
|
|
26341
26789
|
return null;
|
|
26342
26790
|
}
|
|
26343
|
-
settings.retainEveryNTurns =
|
|
26791
|
+
settings.retainEveryNTurns = retainConfig.everyNTurns;
|
|
26344
26792
|
settings.retainMode = "chunked";
|
|
26793
|
+
settings.retainOverlapTurns = retainConfig.overlapTurns;
|
|
26345
26794
|
settings.recallMaxMemories = 8;
|
|
26346
26795
|
settings.recallMinOverlap = 0.1;
|
|
26347
26796
|
settings.recallTypes = ["world", "experience", "observation"];
|
|
@@ -26353,17 +26802,25 @@ function renderHindsightSettingsOverrides(raw, additionalBanks) {
|
|
|
26353
26802
|
return JSON.stringify(settings, null, 2) + `
|
|
26354
26803
|
`;
|
|
26355
26804
|
}
|
|
26805
|
+
function resolvePermissionDefaultMode(agentConfig, switchroomConfig) {
|
|
26806
|
+
const raw = agentConfig.permission_mode ?? switchroomConfig?.defaults?.permission_mode ?? SWITCHROOM_DEFAULT_PERMISSION_MODE;
|
|
26807
|
+
if (VALID_SETTINGS_DEFAULT_MODES.includes(raw)) {
|
|
26808
|
+
return raw;
|
|
26809
|
+
}
|
|
26810
|
+
console.warn(source_default.yellow(`[switchroom] permission_mode "${raw}" has no settings.json defaultMode ` + `equivalent \u2014 using "${SWITCHROOM_DEFAULT_PERMISSION_MODE}" for ` + `permissions.defaultMode (the --permission-mode CLI flag still carries "${raw}").`));
|
|
26811
|
+
return SWITCHROOM_DEFAULT_PERMISSION_MODE;
|
|
26812
|
+
}
|
|
26356
26813
|
function buildWorkspaceContext(args) {
|
|
26357
26814
|
const {
|
|
26358
26815
|
name,
|
|
26359
26816
|
agentDir,
|
|
26360
26817
|
agentConfig,
|
|
26361
26818
|
telegramConfig,
|
|
26819
|
+
switchroomConfig,
|
|
26362
26820
|
switchroomConfigPath,
|
|
26363
26821
|
topicId,
|
|
26364
26822
|
tools,
|
|
26365
26823
|
permissionAllow,
|
|
26366
|
-
hasAllWildcard,
|
|
26367
26824
|
resolvedBotToken,
|
|
26368
26825
|
rawBotToken,
|
|
26369
26826
|
hindsightAutoRecallEnabled,
|
|
@@ -26396,7 +26853,7 @@ function buildWorkspaceContext(args) {
|
|
|
26396
26853
|
...INTERACTIVE_TUI_FLEET_DENY_TOOLS
|
|
26397
26854
|
]),
|
|
26398
26855
|
permissionAllow,
|
|
26399
|
-
|
|
26856
|
+
defaultMode: resolvePermissionDefaultMode(agentConfig, switchroomConfig),
|
|
26400
26857
|
memory: agentConfig.memory,
|
|
26401
26858
|
model: agentConfig.model,
|
|
26402
26859
|
mcpServers: agentConfig.mcp_servers ? filterMcpServers(agentConfig.mcp_servers) : agentConfig.mcp_servers,
|
|
@@ -27312,7 +27769,7 @@ function buildSettingsHooksBlock(p) {
|
|
|
27312
27769
|
hooks: [
|
|
27313
27770
|
{
|
|
27314
27771
|
type: "command",
|
|
27315
|
-
command: wrap("hook:workspace-dynamic", `${useInjectOnChange ? `env SWITCHROOM_INJECT_ON_CHANGE=
|
|
27772
|
+
command: wrap("hook:workspace-dynamic", `${useInjectOnChange ? "" : `env SWITCHROOM_INJECT_ON_CHANGE=0 `}bash "${join10(DOCKER_BIN_PATH, "workspace-dynamic-hook.sh")}"`),
|
|
27316
27773
|
timeout: 5
|
|
27317
27774
|
}
|
|
27318
27775
|
]
|
|
@@ -27662,11 +28119,7 @@ ${baseAppend}` : TELEGRAM_FORMATTING_FLOOR_CARD;
|
|
|
27662
28119
|
settings.permissions = settings.permissions ?? {};
|
|
27663
28120
|
settings.permissions.allow = desiredAllow;
|
|
27664
28121
|
settings.permissions.deny = desiredDeny;
|
|
27665
|
-
|
|
27666
|
-
settings.permissions.defaultMode = "acceptEdits";
|
|
27667
|
-
} else {
|
|
27668
|
-
delete settings.permissions.defaultMode;
|
|
27669
|
-
}
|
|
28122
|
+
settings.permissions.defaultMode = resolvePermissionDefaultMode(agentConfig, switchroomConfig);
|
|
27670
28123
|
const mcpServers = {};
|
|
27671
28124
|
const hindsightEntry = getHindsightSettingsEntry(name, switchroomConfig);
|
|
27672
28125
|
if (hindsightEntry) {
|
|
@@ -28470,7 +28923,7 @@ in doubt, shorter and plainer wins.
|
|
|
28470
28923
|
|
|
28471
28924
|
Every turn that answers a user message ends with a user-visible \`reply\`
|
|
28472
28925
|
\u2014 Telegram is all the user sees; your terminal output
|
|
28473
|
-
never reaches them.`, SWITCHROOM_TELEGRAM_MCP_TOOLS, HINDSIGHT_MCP_TOOLS, AGENT_CONFIG_MCP_TOOLS, HOSTD_MCP_TOOLS, WEBKITE_MCP_TOOLS, LEGACY_SWITCHROOM_MCP_TOKENS, LEGACY_HOSTD_BLANKET_TOKENS, DEFAULT_READ_ONLY_PREAPPROVED_TOOLS, WEBKITE_FLEET_DENY_TOOLS, INTERACTIVE_TUI_FLEET_DENY_TOOLS, WEBKITE_BINARY_CONTAINER_PATH = "/usr/local/bin/webkite", SWITCHROOM_DEFAULT_MAIN_MODEL = "claude-sonnet-5", SWITCHROOM_DEFAULT_THINKING_EFFORT = "low", SWITCHROOM_OWNED_SETTINGS_KEYS, ALL_BUILTIN_TOOLS, DOCKER_TELEGRAM_PLUGIN_PATH = "/opt/switchroom/telegram-plugin", DOCKER_HOOKS_PATH, DOCKER_BUNDLED_HOOKS_PATH = "/opt/switchroom/hooks", DOCKER_BIN_PATH = "/opt/switchroom/bin", DOCKER_SECURITY_PLUGIN_PATH = "/opt/switchroom/security-plugin", DOCKER_CONFIG_PATH = "/state/config/switchroom.yaml", DOCKER_SWITCHROOM_CLI_PATH = "/usr/local/bin/switchroom", DOCKER_AUTH_BROKER_SOCKET = "/run/switchroom/auth-broker/sock", DOCKER_VAULT_BROKER_SOCKET = "/run/switchroom/broker/sock", DOCKER_AGENT_HOME = "/state/agent/home", INTEGRATION_MCP_RESOLVERS;
|
|
28926
|
+
never reaches them.`, SWITCHROOM_TELEGRAM_MCP_TOOLS, HINDSIGHT_MCP_TOOLS, AGENT_CONFIG_MCP_TOOLS, HOSTD_MCP_TOOLS, WEBKITE_MCP_TOOLS, LEGACY_SWITCHROOM_MCP_TOKENS, LEGACY_HOSTD_BLANKET_TOKENS, DEFAULT_READ_ONLY_PREAPPROVED_TOOLS, WEBKITE_FLEET_DENY_TOOLS, INTERACTIVE_TUI_FLEET_DENY_TOOLS, WEBKITE_BINARY_CONTAINER_PATH = "/usr/local/bin/webkite", SWITCHROOM_DEFAULT_MAIN_MODEL = "claude-sonnet-5", SWITCHROOM_DEFAULT_THINKING_EFFORT = "low", SWITCHROOM_OWNED_SETTINGS_KEYS, ALL_BUILTIN_TOOLS, HINDSIGHT_DEFAULT_RETAIN_EVERY_N_TURNS = 3, HINDSIGHT_DEFAULT_RETAIN_OVERLAP_TURNS = 1, VALID_SETTINGS_DEFAULT_MODES, SWITCHROOM_DEFAULT_PERMISSION_MODE = "acceptEdits", DOCKER_TELEGRAM_PLUGIN_PATH = "/opt/switchroom/telegram-plugin", DOCKER_HOOKS_PATH, DOCKER_BUNDLED_HOOKS_PATH = "/opt/switchroom/hooks", DOCKER_BIN_PATH = "/opt/switchroom/bin", DOCKER_SECURITY_PLUGIN_PATH = "/opt/switchroom/security-plugin", DOCKER_CONFIG_PATH = "/state/config/switchroom.yaml", DOCKER_SWITCHROOM_CLI_PATH = "/usr/local/bin/switchroom", DOCKER_AUTH_BROKER_SOCKET = "/run/switchroom/auth-broker/sock", DOCKER_VAULT_BROKER_SOCKET = "/run/switchroom/broker/sock", DOCKER_AGENT_HOME = "/state/agent/home", INTEGRATION_MCP_RESOLVERS;
|
|
28474
28927
|
var init_scaffold = __esm(() => {
|
|
28475
28928
|
init_source();
|
|
28476
28929
|
init_cron_unit_name();
|
|
@@ -28585,6 +29038,12 @@ var init_scaffold = __esm(() => {
|
|
|
28585
29038
|
"Agent",
|
|
28586
29039
|
"ExitPlanMode"
|
|
28587
29040
|
];
|
|
29041
|
+
VALID_SETTINGS_DEFAULT_MODES = [
|
|
29042
|
+
"default",
|
|
29043
|
+
"acceptEdits",
|
|
29044
|
+
"plan",
|
|
29045
|
+
"bypassPermissions"
|
|
29046
|
+
];
|
|
28588
29047
|
DOCKER_HOOKS_PATH = `${DOCKER_TELEGRAM_PLUGIN_PATH}/hooks`;
|
|
28589
29048
|
INTEGRATION_MCP_RESOLVERS = [
|
|
28590
29049
|
{
|
|
@@ -28829,6 +29288,7 @@ function describeAgents(config, litellmConfirmedAgents) {
|
|
|
28829
29288
|
baseUrl: resolved.litellm?.base_url ?? config.litellm?.base_url,
|
|
28830
29289
|
smallFastModel: resolved.litellm?.small_fast_model ?? config.litellm?.small_fast_model ?? "claude-haiku-4-5-20251001"
|
|
28831
29290
|
},
|
|
29291
|
+
model: resolveMainModel(resolved.model),
|
|
28832
29292
|
timezone: resolveTimezone(config, resolved, {
|
|
28833
29293
|
onUtcFallback: () => {
|
|
28834
29294
|
console.warn(` \u26a0 timezone: no explicit timezone set and server detection resolved to UTC ` + `for agent "${name}" \u2014 cron schedules and the per-turn time hint will ` + `run in UTC. Add \`switchroom.timezone: "Region/City"\` (e.g. ` + `"Australia/Melbourne") to switchroom.yaml to silence this warning.`);
|
|
@@ -29261,7 +29721,7 @@ function emitAgentService(lines, a, imageTag, buildMode, buildContext, homePrefi
|
|
|
29261
29721
|
const root = a.litellm.baseUrl.replace(/\/+$/, "");
|
|
29262
29722
|
env2.SWITCHROOM_LITELLM = "1";
|
|
29263
29723
|
env2.SWITCHROOM_LITELLM_BASE = root;
|
|
29264
|
-
env2.ANTHROPIC_BASE_URL = `${root}/anthropic
|
|
29724
|
+
env2.ANTHROPIC_BASE_URL = isClaudeModel(a.model) ? `${root}/anthropic` : root;
|
|
29265
29725
|
env2.ANTHROPIC_SMALL_FAST_MODEL = a.litellm.smallFastModel;
|
|
29266
29726
|
}
|
|
29267
29727
|
env2.SWITCHROOM_VOICE_ENGINE = voiceEngine;
|
|
@@ -29359,6 +29819,8 @@ var init_compose = __esm(() => {
|
|
|
29359
29819
|
init_schema();
|
|
29360
29820
|
init_cron_routing();
|
|
29361
29821
|
init_tier_selector();
|
|
29822
|
+
init_scaffold();
|
|
29823
|
+
init_model_command();
|
|
29362
29824
|
init_merge();
|
|
29363
29825
|
init_timezone();
|
|
29364
29826
|
init_peercred();
|
|
@@ -29686,7 +30148,7 @@ var init_write_compose = __esm(() => {
|
|
|
29686
30148
|
});
|
|
29687
30149
|
|
|
29688
30150
|
// src/agents/tmux.ts
|
|
29689
|
-
import { execFileSync as
|
|
30151
|
+
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
29690
30152
|
function sendAgentInterrupt(opts) {
|
|
29691
30153
|
const { agentName } = opts;
|
|
29692
30154
|
const attempts = typeof opts.attempts === "number" && opts.attempts > 0 ? opts.attempts : 1;
|
|
@@ -29696,7 +30158,7 @@ function sendAgentInterrupt(opts) {
|
|
|
29696
30158
|
let lastError = null;
|
|
29697
30159
|
for (let i = 0;i < attempts; i++) {
|
|
29698
30160
|
try {
|
|
29699
|
-
|
|
30161
|
+
execFileSync7("tmux", args, {
|
|
29700
30162
|
timeout: 3000,
|
|
29701
30163
|
stdio: ["ignore", "pipe", "pipe"]
|
|
29702
30164
|
});
|
|
@@ -29737,7 +30199,7 @@ var init_compose_env = () => {};
|
|
|
29737
30199
|
import { resolve as resolve13 } from "node:path";
|
|
29738
30200
|
import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync6 } from "node:fs";
|
|
29739
30201
|
import { homedir as homedir7 } from "node:os";
|
|
29740
|
-
import { execFileSync as
|
|
30202
|
+
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
29741
30203
|
function resolveSwitchroomHome(explicit) {
|
|
29742
30204
|
if (explicit && explicit.length > 0)
|
|
29743
30205
|
return explicit;
|
|
@@ -29768,7 +30230,7 @@ function bringUpAgentService(opts) {
|
|
|
29768
30230
|
const dockerBin = opts.dockerBin ?? "docker";
|
|
29769
30231
|
const stdio = opts.stdio ?? "inherit";
|
|
29770
30232
|
for (const svc of ["vault-broker", "approval-kernel", "switchroom-auth-broker"]) {
|
|
29771
|
-
|
|
30233
|
+
execFileSync8(dockerBin, [
|
|
29772
30234
|
"compose",
|
|
29773
30235
|
"-f",
|
|
29774
30236
|
composePath,
|
|
@@ -29780,7 +30242,7 @@ function bringUpAgentService(opts) {
|
|
|
29780
30242
|
svc
|
|
29781
30243
|
], { stdio });
|
|
29782
30244
|
}
|
|
29783
|
-
|
|
30245
|
+
execFileSync8(dockerBin, [
|
|
29784
30246
|
"compose",
|
|
29785
30247
|
"-f",
|
|
29786
30248
|
composePath,
|
|
@@ -29799,7 +30261,7 @@ var init_docker_fleet = __esm(() => {
|
|
|
29799
30261
|
});
|
|
29800
30262
|
|
|
29801
30263
|
// src/agents/singleton-reconcile.ts
|
|
29802
|
-
import { execFileSync as
|
|
30264
|
+
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
29803
30265
|
import { readFileSync as readFileSync16 } from "node:fs";
|
|
29804
30266
|
function resolveSingletonServices(voiceEngine) {
|
|
29805
30267
|
const engine = voiceEngine ?? loadHostCapabilities()?.voice.engine ?? "cloud";
|
|
@@ -29825,7 +30287,7 @@ function readPinnedSingletonImages(composeText, services = SINGLETON_SERVICES) {
|
|
|
29825
30287
|
}
|
|
29826
30288
|
function defaultInspectImage(dockerBin, container) {
|
|
29827
30289
|
try {
|
|
29828
|
-
const out =
|
|
30290
|
+
const out = execFileSync9(dockerBin, ["inspect", "-f", "{{.Config.Image}}", container], { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], timeout: 5000 }).trim();
|
|
29829
30291
|
return out.length > 0 ? out : null;
|
|
29830
30292
|
} catch {
|
|
29831
30293
|
return null;
|
|
@@ -29851,7 +30313,7 @@ function detectSingletonDrift(deps) {
|
|
|
29851
30313
|
});
|
|
29852
30314
|
}
|
|
29853
30315
|
function defaultRecreate(dockerBin, project, composeFile, service) {
|
|
29854
|
-
|
|
30316
|
+
execFileSync9(dockerBin, ["compose", "-p", project, "-f", composeFile, ...composeEnvFileArgs(composeFile), "up", "-d", "--no-deps", service], { stdio: ["ignore", "pipe", "pipe"], timeout: 120000 });
|
|
29855
30317
|
}
|
|
29856
30318
|
function reconcileSingletons(deps) {
|
|
29857
30319
|
const dockerBin = deps.dockerBin ?? "docker";
|
|
@@ -29894,7 +30356,7 @@ var init_singleton_reconcile = __esm(() => {
|
|
|
29894
30356
|
});
|
|
29895
30357
|
|
|
29896
30358
|
// src/agents/lifecycle.ts
|
|
29897
|
-
import { execFileSync as
|
|
30359
|
+
import { execFileSync as execFileSync10, spawn, spawnSync } from "node:child_process";
|
|
29898
30360
|
import { existsSync as existsSync20, mkdirSync as mkdirSync14, writeFileSync as writeFileSync7, renameSync as renameSync5, readFileSync as readFileSync17 } from "node:fs";
|
|
29899
30361
|
import { resolve as resolve14, join as join14 } from "node:path";
|
|
29900
30362
|
import { connect } from "node:net";
|
|
@@ -29925,7 +30387,7 @@ function buildCliRestartReason(opts) {
|
|
|
29925
30387
|
if (!buildCommit)
|
|
29926
30388
|
return "cli: restart";
|
|
29927
30389
|
try {
|
|
29928
|
-
const head =
|
|
30390
|
+
const head = execFileSync10("git", ["rev-parse", "HEAD"], {
|
|
29929
30391
|
cwd: cwd ?? process.cwd(),
|
|
29930
30392
|
encoding: "utf-8",
|
|
29931
30393
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -29936,7 +30398,7 @@ function buildCliRestartReason(opts) {
|
|
|
29936
30398
|
return "cli: restart";
|
|
29937
30399
|
let subject = "";
|
|
29938
30400
|
try {
|
|
29939
|
-
subject =
|
|
30401
|
+
subject = execFileSync10("git", ["log", "-1", "--pretty=%s", head], {
|
|
29940
30402
|
cwd: cwd ?? process.cwd(),
|
|
29941
30403
|
encoding: "utf-8",
|
|
29942
30404
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -29966,7 +30428,7 @@ function reconcileSingletonImages(log) {
|
|
|
29966
30428
|
}
|
|
29967
30429
|
function dockerSync(args) {
|
|
29968
30430
|
try {
|
|
29969
|
-
return
|
|
30431
|
+
return execFileSync10("docker", args, {
|
|
29970
30432
|
encoding: "utf-8",
|
|
29971
30433
|
stdio: ["ignore", "pipe", "pipe"]
|
|
29972
30434
|
}).trim();
|
|
@@ -30355,12 +30817,12 @@ var init_lifecycle = __esm(() => {
|
|
|
30355
30817
|
});
|
|
30356
30818
|
|
|
30357
30819
|
// src/auth/pane-ready-probe.ts
|
|
30358
|
-
import { execFileSync as
|
|
30820
|
+
import { execFileSync as execFileSync12 } from "node:child_process";
|
|
30359
30821
|
function defaultPaneReadyDeps() {
|
|
30360
30822
|
return {
|
|
30361
30823
|
capturePane(sessionName) {
|
|
30362
30824
|
try {
|
|
30363
|
-
return
|
|
30825
|
+
return execFileSync12("tmux", ["capture-pane", "-p", "-t", sessionName, "-S", "-200"], {
|
|
30364
30826
|
encoding: "utf-8",
|
|
30365
30827
|
stdio: ["pipe", "pipe", "pipe"]
|
|
30366
30828
|
}).trim();
|
|
@@ -30641,7 +31103,7 @@ var init_accounts = __esm(() => {
|
|
|
30641
31103
|
});
|
|
30642
31104
|
|
|
30643
31105
|
// src/auth/manager.ts
|
|
30644
|
-
import { execFileSync as
|
|
31106
|
+
import { execFileSync as execFileSync13 } from "node:child_process";
|
|
30645
31107
|
import {
|
|
30646
31108
|
readFileSync as readFileSync22,
|
|
30647
31109
|
readdirSync as readdirSync11,
|
|
@@ -30692,14 +31154,14 @@ function authSessionName(name, slot) {
|
|
|
30692
31154
|
return `${base}-${slot.replace(/[^a-zA-Z0-9_.-]/g, "-")}`;
|
|
30693
31155
|
}
|
|
30694
31156
|
function tmux(args) {
|
|
30695
|
-
return
|
|
31157
|
+
return execFileSync13("tmux", args, {
|
|
30696
31158
|
encoding: "utf-8",
|
|
30697
31159
|
stdio: ["pipe", "pipe", "pipe"]
|
|
30698
31160
|
}).trim();
|
|
30699
31161
|
}
|
|
30700
31162
|
function tmuxSessionExists(sessionName) {
|
|
30701
31163
|
try {
|
|
30702
|
-
|
|
31164
|
+
execFileSync13("tmux", ["has-session", "-t", sessionName], {
|
|
30703
31165
|
stdio: ["pipe", "pipe", "pipe"]
|
|
30704
31166
|
});
|
|
30705
31167
|
return true;
|
|
@@ -31215,355 +31677,6 @@ function readQuarantineMarkerForAgent(agentsDir, name) {
|
|
|
31215
31677
|
var QUARANTINE_FILENAME = "quarantine.json";
|
|
31216
31678
|
var init_quarantine = () => {};
|
|
31217
31679
|
|
|
31218
|
-
// src/agents/pane-lock.ts
|
|
31219
|
-
function withPaneLock(key, fn) {
|
|
31220
|
-
const tail = tails.get(key) ?? Promise.resolve();
|
|
31221
|
-
const run = tail.then(() => fn());
|
|
31222
|
-
const next = run.then(() => {}, () => {});
|
|
31223
|
-
tails.set(key, next);
|
|
31224
|
-
next.then(() => {
|
|
31225
|
-
if (tails.get(key) === next)
|
|
31226
|
-
tails.delete(key);
|
|
31227
|
-
});
|
|
31228
|
-
return run;
|
|
31229
|
-
}
|
|
31230
|
-
var tails;
|
|
31231
|
-
var init_pane_lock = __esm(() => {
|
|
31232
|
-
tails = new Map;
|
|
31233
|
-
});
|
|
31234
|
-
|
|
31235
|
-
// src/agents/inject.ts
|
|
31236
|
-
var exports_inject = {};
|
|
31237
|
-
__export(exports_inject, {
|
|
31238
|
-
validateInjectCommand: () => validateInjectCommand,
|
|
31239
|
-
normalizeInjectCommand: () => normalizeInjectCommand,
|
|
31240
|
-
makeTmuxRunner: () => makeTmuxRunner,
|
|
31241
|
-
isTuiChromeLine: () => isTuiChromeLine,
|
|
31242
|
-
injectSlashCommandWith: () => injectSlashCommandWith,
|
|
31243
|
-
injectSlashCommand: () => injectSlashCommand,
|
|
31244
|
-
diffPane: () => diffPane,
|
|
31245
|
-
dedupeInjectQueue: () => dedupeInjectQueue,
|
|
31246
|
-
InjectError: () => InjectError,
|
|
31247
|
-
INJECT_COMMANDS: () => INJECT_COMMANDS,
|
|
31248
|
-
INJECT_BLOCKLIST: () => INJECT_BLOCKLIST,
|
|
31249
|
-
INJECT_BLOCKED: () => INJECT_BLOCKED,
|
|
31250
|
-
INJECT_ALLOWLIST: () => INJECT_ALLOWLIST
|
|
31251
|
-
});
|
|
31252
|
-
import { execFileSync as execFileSync14 } from "node:child_process";
|
|
31253
|
-
function validateInjectCommand(command) {
|
|
31254
|
-
if (typeof command !== "string" || command.trim().length === 0) {
|
|
31255
|
-
throw new InjectError("invalid", "command is empty");
|
|
31256
|
-
}
|
|
31257
|
-
const trimmed = command.trim();
|
|
31258
|
-
if (!trimmed.startsWith("/")) {
|
|
31259
|
-
throw new InjectError("invalid", `command must start with "/": got ${trimmed}`);
|
|
31260
|
-
}
|
|
31261
|
-
const bare = trimmed.split(/\s+/, 1)[0].toLowerCase();
|
|
31262
|
-
const blockedMeta = INJECT_BLOCKED.get(bare);
|
|
31263
|
-
if (blockedMeta) {
|
|
31264
|
-
throw new InjectError("blocked", `${bare} is explicitly blocked from inject (${blockedMeta.reason}).`);
|
|
31265
|
-
}
|
|
31266
|
-
if (!INJECT_COMMANDS.has(bare)) {
|
|
31267
|
-
const allowed = [...INJECT_COMMANDS.keys()].sort().join(", ");
|
|
31268
|
-
throw new InjectError("not_allowed", `${bare} is not in the inject allowlist. Allowed: ${allowed}`);
|
|
31269
|
-
}
|
|
31270
|
-
return bare;
|
|
31271
|
-
}
|
|
31272
|
-
function normalizeInjectCommand(command) {
|
|
31273
|
-
return command.trim().replace(/\s+/g, " ").toLowerCase();
|
|
31274
|
-
}
|
|
31275
|
-
function dedupeInjectQueue(commands) {
|
|
31276
|
-
const seen = new Set;
|
|
31277
|
-
const out = [];
|
|
31278
|
-
for (const raw of commands) {
|
|
31279
|
-
if (typeof raw !== "string")
|
|
31280
|
-
continue;
|
|
31281
|
-
const key = normalizeInjectCommand(raw);
|
|
31282
|
-
if (key.length === 0)
|
|
31283
|
-
continue;
|
|
31284
|
-
if (seen.has(key))
|
|
31285
|
-
continue;
|
|
31286
|
-
seen.add(key);
|
|
31287
|
-
out.push(raw.trim());
|
|
31288
|
-
}
|
|
31289
|
-
return out;
|
|
31290
|
-
}
|
|
31291
|
-
function defaultSocketName(agentName) {
|
|
31292
|
-
return `switchroom-${agentName}`;
|
|
31293
|
-
}
|
|
31294
|
-
function makeTmuxRunner(tmuxBin) {
|
|
31295
|
-
return {
|
|
31296
|
-
capture(socket, session) {
|
|
31297
|
-
try {
|
|
31298
|
-
return execFileSync14(tmuxBin, ["-L", socket, "capture-pane", "-p", "-t", session, "-S", "-200"], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
31299
|
-
} catch {
|
|
31300
|
-
return null;
|
|
31301
|
-
}
|
|
31302
|
-
},
|
|
31303
|
-
send(socket, session, args) {
|
|
31304
|
-
const [subcmd, ...rest] = args;
|
|
31305
|
-
const flagEnd = rest.findIndex((a) => !a.startsWith("-"));
|
|
31306
|
-
const flagsBeforeKeys = flagEnd === -1 ? rest : rest.slice(0, flagEnd);
|
|
31307
|
-
const keys = flagEnd === -1 ? [] : rest.slice(flagEnd);
|
|
31308
|
-
execFileSync14(tmuxBin, ["-L", socket, subcmd, ...flagsBeforeKeys, "-t", session, ...keys], { stdio: ["pipe", "pipe", "pipe"] });
|
|
31309
|
-
},
|
|
31310
|
-
hasSession(socket, session) {
|
|
31311
|
-
try {
|
|
31312
|
-
execFileSync14(tmuxBin, ["-L", socket, "has-session", "-t", session], {
|
|
31313
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
31314
|
-
});
|
|
31315
|
-
return true;
|
|
31316
|
-
} catch {
|
|
31317
|
-
return false;
|
|
31318
|
-
}
|
|
31319
|
-
}
|
|
31320
|
-
};
|
|
31321
|
-
}
|
|
31322
|
-
function sleep2(ms) {
|
|
31323
|
-
return new Promise((res) => setTimeout(res, ms));
|
|
31324
|
-
}
|
|
31325
|
-
function isTuiChromeLine(line) {
|
|
31326
|
-
const t = line.trim();
|
|
31327
|
-
if (t.length === 0)
|
|
31328
|
-
return false;
|
|
31329
|
-
if (/^[\s\-_\u2502\u2500\u256d\u256e\u2570\u256f\u250c\u2510\u2514\u2518|]+$/.test(t))
|
|
31330
|
-
return true;
|
|
31331
|
-
if (/^[\u256d\u256e\u2570\u256f\u2502\s]*[>)\u276f][\u256d\u256e\u2570\u256f\u2502\s]*$/.test(t))
|
|
31332
|
-
return true;
|
|
31333
|
-
if (/accept edits on/i.test(t))
|
|
31334
|
-
return true;
|
|
31335
|
-
if (/shift\+tab to cycle/i.test(t))
|
|
31336
|
-
return true;
|
|
31337
|
-
if (/for agents/i.test(t))
|
|
31338
|
-
return true;
|
|
31339
|
-
if (/\? for shortcuts/i.test(t))
|
|
31340
|
-
return true;
|
|
31341
|
-
if (/bypassing permissions/i.test(t))
|
|
31342
|
-
return true;
|
|
31343
|
-
if (t === "copy")
|
|
31344
|
-
return true;
|
|
31345
|
-
return false;
|
|
31346
|
-
}
|
|
31347
|
-
function diffPane(before, after, command) {
|
|
31348
|
-
if (command) {
|
|
31349
|
-
const escaped = command.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
31350
|
-
const lines = after.split(`
|
|
31351
|
-
`);
|
|
31352
|
-
let anchorIdx = -1;
|
|
31353
|
-
for (let i = lines.length - 1;i >= 0; i--) {
|
|
31354
|
-
if (/[\u276f>]\s+/.test(lines[i]) && lines[i].includes(escaped)) {
|
|
31355
|
-
anchorIdx = i;
|
|
31356
|
-
break;
|
|
31357
|
-
}
|
|
31358
|
-
}
|
|
31359
|
-
if (anchorIdx >= 0) {
|
|
31360
|
-
const tail = lines.slice(anchorIdx + 1);
|
|
31361
|
-
const trimmed = [];
|
|
31362
|
-
for (const raw of tail) {
|
|
31363
|
-
const line = raw.trimEnd();
|
|
31364
|
-
if (line.length === 0 && trimmed.length === 0)
|
|
31365
|
-
continue;
|
|
31366
|
-
if (isTuiChromeLine(line))
|
|
31367
|
-
continue;
|
|
31368
|
-
trimmed.push(line);
|
|
31369
|
-
}
|
|
31370
|
-
while (trimmed.length > 0 && trimmed[trimmed.length - 1].length === 0) {
|
|
31371
|
-
trimmed.pop();
|
|
31372
|
-
}
|
|
31373
|
-
const affordances = /^(esc to cancel|press any key|\u21b5 select)/i;
|
|
31374
|
-
while (trimmed.length > 0 && affordances.test(trimmed[trimmed.length - 1].trim())) {
|
|
31375
|
-
trimmed.pop();
|
|
31376
|
-
}
|
|
31377
|
-
if (trimmed.length > 0) {
|
|
31378
|
-
return { output: trimmed.join(`
|
|
31379
|
-
`), anchored: true };
|
|
31380
|
-
}
|
|
31381
|
-
}
|
|
31382
|
-
}
|
|
31383
|
-
const beforeSet = new Set(before.split(`
|
|
31384
|
-
`).map((l) => l.trimEnd()));
|
|
31385
|
-
const newLines = [];
|
|
31386
|
-
for (const raw of after.split(`
|
|
31387
|
-
`)) {
|
|
31388
|
-
const line = raw.trimEnd();
|
|
31389
|
-
if (line.length === 0)
|
|
31390
|
-
continue;
|
|
31391
|
-
if (beforeSet.has(line))
|
|
31392
|
-
continue;
|
|
31393
|
-
if (isTuiChromeLine(line))
|
|
31394
|
-
continue;
|
|
31395
|
-
newLines.push(line);
|
|
31396
|
-
}
|
|
31397
|
-
return { output: newLines.join(`
|
|
31398
|
-
`), anchored: false };
|
|
31399
|
-
}
|
|
31400
|
-
async function injectSlashCommand(agentName, command, opts = {}) {
|
|
31401
|
-
validateInjectCommand(command);
|
|
31402
|
-
const tmuxBin = opts.tmuxBin ?? "tmux";
|
|
31403
|
-
const socket = opts.socketName ?? defaultSocketName(agentName);
|
|
31404
|
-
const session = opts.sessionName ?? agentName;
|
|
31405
|
-
const settleMs = opts.settleMs ?? 2000;
|
|
31406
|
-
const timeoutMs = opts.timeoutMs ?? 5000;
|
|
31407
|
-
return withPaneLock(`${socket}:${session}`, () => injectSlashCommandWith(makeTmuxRunner(tmuxBin), {
|
|
31408
|
-
socket,
|
|
31409
|
-
session,
|
|
31410
|
-
command: command.trim(),
|
|
31411
|
-
settleMs,
|
|
31412
|
-
timeoutMs
|
|
31413
|
-
}));
|
|
31414
|
-
}
|
|
31415
|
-
async function injectSlashCommandWith(runner, args) {
|
|
31416
|
-
const { socket, session, command, settleMs, timeoutMs } = args;
|
|
31417
|
-
let bareVerb;
|
|
31418
|
-
try {
|
|
31419
|
-
bareVerb = validateInjectCommand(command);
|
|
31420
|
-
} catch (err) {
|
|
31421
|
-
if (err instanceof InjectError) {
|
|
31422
|
-
return {
|
|
31423
|
-
outcome: "failed",
|
|
31424
|
-
output: "",
|
|
31425
|
-
truncated: false,
|
|
31426
|
-
command: command.trim().split(/\s+/, 1)[0]?.toLowerCase() ?? "",
|
|
31427
|
-
meta: null,
|
|
31428
|
-
errorCode: err.code,
|
|
31429
|
-
errorMessage: err.message
|
|
31430
|
-
};
|
|
31431
|
-
}
|
|
31432
|
-
throw err;
|
|
31433
|
-
}
|
|
31434
|
-
const meta = INJECT_COMMANDS.get(bareVerb) ?? null;
|
|
31435
|
-
if (!runner.hasSession(socket, session)) {
|
|
31436
|
-
return {
|
|
31437
|
-
outcome: "failed",
|
|
31438
|
-
output: "",
|
|
31439
|
-
truncated: false,
|
|
31440
|
-
command: bareVerb,
|
|
31441
|
-
meta,
|
|
31442
|
-
errorCode: "session_missing",
|
|
31443
|
-
errorMessage: `tmux session "${session}" on socket "${socket}" not found. ` + `Is the agent running under the tmux supervisor (the default)? ` + `If experimental.legacy_pty=true is set, inject is unsupported.`
|
|
31444
|
-
};
|
|
31445
|
-
}
|
|
31446
|
-
const before = runner.capture(socket, session) ?? "";
|
|
31447
|
-
try {
|
|
31448
|
-
runner.send(socket, session, ["send-keys", "-l", command]);
|
|
31449
|
-
runner.send(socket, session, ["send-keys", "Enter"]);
|
|
31450
|
-
} catch (err) {
|
|
31451
|
-
return {
|
|
31452
|
-
outcome: "failed",
|
|
31453
|
-
output: "",
|
|
31454
|
-
truncated: false,
|
|
31455
|
-
command: bareVerb,
|
|
31456
|
-
meta,
|
|
31457
|
-
errorCode: "tmux_failed",
|
|
31458
|
-
errorMessage: `tmux send-keys failed: ${err instanceof Error ? err.message : String(err)}`
|
|
31459
|
-
};
|
|
31460
|
-
}
|
|
31461
|
-
const start = Date.now();
|
|
31462
|
-
let last = before;
|
|
31463
|
-
let stableSince = null;
|
|
31464
|
-
while (Date.now() - start < timeoutMs) {
|
|
31465
|
-
await sleep2(POLL_INTERVAL_MS);
|
|
31466
|
-
const cur = runner.capture(socket, session) ?? "";
|
|
31467
|
-
if (cur === last && cur !== before) {
|
|
31468
|
-
if (stableSince === null) {
|
|
31469
|
-
stableSince = Date.now();
|
|
31470
|
-
} else if (Date.now() - stableSince >= POLL_INTERVAL_MS) {
|
|
31471
|
-
last = cur;
|
|
31472
|
-
break;
|
|
31473
|
-
}
|
|
31474
|
-
} else {
|
|
31475
|
-
stableSince = null;
|
|
31476
|
-
}
|
|
31477
|
-
last = cur;
|
|
31478
|
-
if (Date.now() - start >= settleMs && cur !== before) {
|
|
31479
|
-
break;
|
|
31480
|
-
}
|
|
31481
|
-
}
|
|
31482
|
-
const { output: rawOutput, anchored } = diffPane(before, last, command);
|
|
31483
|
-
let output = rawOutput;
|
|
31484
|
-
let truncated = false;
|
|
31485
|
-
const bytes = Buffer.byteLength(output, "utf-8");
|
|
31486
|
-
if (bytes > OUTPUT_BYTE_CAP) {
|
|
31487
|
-
while (Buffer.byteLength(output, "utf-8") > OUTPUT_BYTE_CAP) {
|
|
31488
|
-
output = output.slice(Math.floor(output.length * 0.1) + 1);
|
|
31489
|
-
}
|
|
31490
|
-
truncated = true;
|
|
31491
|
-
}
|
|
31492
|
-
if (meta?.dialog) {
|
|
31493
|
-
try {
|
|
31494
|
-
runner.send(socket, session, ["send-keys", "Escape"]);
|
|
31495
|
-
} catch {}
|
|
31496
|
-
}
|
|
31497
|
-
if (output.trim().length === 0) {
|
|
31498
|
-
return {
|
|
31499
|
-
outcome: "ok_no_output",
|
|
31500
|
-
output: "",
|
|
31501
|
-
truncated: false,
|
|
31502
|
-
command: bareVerb,
|
|
31503
|
-
meta,
|
|
31504
|
-
...anchored ? {} : { diagnostic: "anchor_missing" }
|
|
31505
|
-
};
|
|
31506
|
-
}
|
|
31507
|
-
return {
|
|
31508
|
-
outcome: "ok",
|
|
31509
|
-
output,
|
|
31510
|
-
truncated,
|
|
31511
|
-
command: bareVerb,
|
|
31512
|
-
meta,
|
|
31513
|
-
...truncated ? { diagnostic: "truncated_output" } : {}
|
|
31514
|
-
};
|
|
31515
|
-
}
|
|
31516
|
-
var INJECT_COMMANDS, INJECT_ALLOWLIST, INJECT_BLOCKED, INJECT_BLOCKLIST, InjectError, POLL_INTERVAL_MS = 150, OUTPUT_BYTE_CAP = 3000;
|
|
31517
|
-
var init_inject = __esm(() => {
|
|
31518
|
-
init_pane_lock();
|
|
31519
|
-
INJECT_COMMANDS = new Map([
|
|
31520
|
-
["/cost", { description: "Show session cost", expectsOutput: true, dialog: true }],
|
|
31521
|
-
["/status", { description: "Show session status", expectsOutput: true, dialog: true }],
|
|
31522
|
-
["/usage", { description: "Show plan quota", expectsOutput: true, dialog: true }],
|
|
31523
|
-
["/hooks", { description: "List configured hooks", expectsOutput: true, dialog: true }],
|
|
31524
|
-
["/memory", { description: "Open memory picker", expectsOutput: true, dialog: true }],
|
|
31525
|
-
["/model", { description: "Open model picker", expectsOutput: true }],
|
|
31526
|
-
[
|
|
31527
|
-
"/clear",
|
|
31528
|
-
{
|
|
31529
|
-
description: "Clear session screen",
|
|
31530
|
-
expectsOutput: false,
|
|
31531
|
-
silentNote: "context cleared \u2014 fresh slate"
|
|
31532
|
-
}
|
|
31533
|
-
],
|
|
31534
|
-
[
|
|
31535
|
-
"/compact",
|
|
31536
|
-
{
|
|
31537
|
-
description: "Compact conversation history",
|
|
31538
|
-
expectsOutput: false,
|
|
31539
|
-
silentNote: "compaction runs silently"
|
|
31540
|
-
}
|
|
31541
|
-
]
|
|
31542
|
-
]);
|
|
31543
|
-
INJECT_ALLOWLIST = new Set(INJECT_COMMANDS.keys());
|
|
31544
|
-
INJECT_BLOCKED = new Map([
|
|
31545
|
-
["/login", { reason: "would mutate auth state" }],
|
|
31546
|
-
["/logout", { reason: "would terminate the agent's auth session" }],
|
|
31547
|
-
["/exit", { reason: "would kill the agent process" }],
|
|
31548
|
-
["/quit", { reason: "would kill the agent process" }],
|
|
31549
|
-
[
|
|
31550
|
-
"/effort",
|
|
31551
|
-
{
|
|
31552
|
-
reason: "leaves a blocking confirmation modal open and wedges the pane; use the /effort command (it drives the modal), not raw inject"
|
|
31553
|
-
}
|
|
31554
|
-
]
|
|
31555
|
-
]);
|
|
31556
|
-
INJECT_BLOCKLIST = new Set(INJECT_BLOCKED.keys());
|
|
31557
|
-
InjectError = class InjectError extends Error {
|
|
31558
|
-
code;
|
|
31559
|
-
constructor(code, message) {
|
|
31560
|
-
super(message);
|
|
31561
|
-
this.name = "InjectError";
|
|
31562
|
-
this.code = code;
|
|
31563
|
-
}
|
|
31564
|
-
};
|
|
31565
|
-
});
|
|
31566
|
-
|
|
31567
31680
|
// src/auth/broker/protocol.ts
|
|
31568
31681
|
function encodeRequest2(req) {
|
|
31569
31682
|
const line = JSON.stringify(RequestSchema2.parse(req)) + `
|
|
@@ -64552,8 +64665,8 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
64552
64665
|
import { dirname, join } from "node:path";
|
|
64553
64666
|
|
|
64554
64667
|
// src/build-info.ts
|
|
64555
|
-
var VERSION = "0.
|
|
64556
|
-
var COMMIT_SHA = "
|
|
64668
|
+
var VERSION = "0.18.3";
|
|
64669
|
+
var COMMIT_SHA = "dbd8b112";
|
|
64557
64670
|
|
|
64558
64671
|
// src/cli/resolve-version.ts
|
|
64559
64672
|
function readPackageVersion() {
|
|
@@ -64767,11 +64880,11 @@ function detectInFlight(opts) {
|
|
|
64767
64880
|
async function waitUntilIdle(opts) {
|
|
64768
64881
|
const pollMs = opts.pollMs ?? 2000;
|
|
64769
64882
|
const clock = opts.now ?? Date.now;
|
|
64770
|
-
const
|
|
64883
|
+
const sleep2 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
64771
64884
|
const deadline = clock() + opts.timeoutMs;
|
|
64772
64885
|
let last = detectInFlight(opts);
|
|
64773
64886
|
while (last.busy && clock() < deadline) {
|
|
64774
|
-
await
|
|
64887
|
+
await sleep2(pollMs);
|
|
64775
64888
|
last = detectInFlight(opts);
|
|
64776
64889
|
}
|
|
64777
64890
|
return last;
|
|
@@ -64883,7 +64996,7 @@ function spinner(message) {
|
|
|
64883
64996
|
// src/agents/status.ts
|
|
64884
64997
|
import { existsSync as existsSync23, readFileSync as readFileSync20, statSync as statSync11 } from "node:fs";
|
|
64885
64998
|
import { join as join16 } from "node:path";
|
|
64886
|
-
import { execFileSync as
|
|
64999
|
+
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
64887
65000
|
|
|
64888
65001
|
// src/agents/handoff-summarizer.ts
|
|
64889
65002
|
import { readFileSync as readFileSync18, writeFileSync as writeFileSync8, renameSync as renameSync6, mkdirSync as mkdirSync15, existsSync as existsSync21, statSync as statSync10, readdirSync as readdirSync9 } from "node:fs";
|
|
@@ -65468,7 +65581,7 @@ async function buildAgentStatusReport(inputs) {
|
|
|
65468
65581
|
async function waitForAgentReady(inputs, options = {}) {
|
|
65469
65582
|
const timeoutMs = options.timeoutMs ?? 30000;
|
|
65470
65583
|
const pollIntervalMs = options.pollIntervalMs ?? 750;
|
|
65471
|
-
const
|
|
65584
|
+
const sleep2 = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
65472
65585
|
const now = options.now ?? Date.now;
|
|
65473
65586
|
const startedAt = now();
|
|
65474
65587
|
const deadline = startedAt + timeoutMs;
|
|
@@ -65483,7 +65596,7 @@ async function waitForAgentReady(inputs, options = {}) {
|
|
|
65483
65596
|
notReady
|
|
65484
65597
|
};
|
|
65485
65598
|
}
|
|
65486
|
-
await
|
|
65599
|
+
await sleep2(pollIntervalMs);
|
|
65487
65600
|
report = await buildAgentStatusReport(inputs);
|
|
65488
65601
|
notReady = readinessGaps(report);
|
|
65489
65602
|
}
|
|
@@ -65560,14 +65673,14 @@ function readLastMessages(historyDbPath) {
|
|
|
65560
65673
|
};
|
|
65561
65674
|
}
|
|
65562
65675
|
try {
|
|
65563
|
-
const out =
|
|
65676
|
+
const out = execFileSync11("bun", [
|
|
65564
65677
|
"-e",
|
|
65565
65678
|
`const { Database } = require("bun:sqlite"); ` + `const db = new Database(${JSON.stringify(historyDbPath)}, { readonly: true }); ` + `const rows = db.prepare("SELECT role, MAX(ts) as ts FROM messages GROUP BY role").all(); ` + `for (const r of rows) console.log(r.role + "|" + r.ts); ` + `db.close();`
|
|
65566
65679
|
], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
65567
65680
|
return parseSqliteRoleTsOutput(out);
|
|
65568
65681
|
} catch {}
|
|
65569
65682
|
try {
|
|
65570
|
-
const out =
|
|
65683
|
+
const out = execFileSync11("sqlite3", [
|
|
65571
65684
|
historyDbPath,
|
|
65572
65685
|
"SELECT role, MAX(ts) FROM messages GROUP BY role;"
|
|
65573
65686
|
], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
@@ -65679,7 +65792,7 @@ function escapeRegex(s) {
|
|
|
65679
65792
|
}
|
|
65680
65793
|
function readDockerContainer(containerName2) {
|
|
65681
65794
|
try {
|
|
65682
|
-
const out =
|
|
65795
|
+
const out = execFileSync11("docker", ["inspect", "--format", "{{json .State}}", containerName2], { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
|
|
65683
65796
|
const state = JSON.parse(out.trim());
|
|
65684
65797
|
const active = state.Status === "running" ? "active" : state.Status === "restarting" ? "restarting" : "inactive";
|
|
65685
65798
|
const pid = typeof state.Pid === "number" && state.Pid > 0 ? state.Pid : null;
|
|
@@ -65794,7 +65907,7 @@ async function pollForDmStart(token, timeoutMs = 120000) {
|
|
|
65794
65907
|
const response = await fetch(url);
|
|
65795
65908
|
data = await response.json();
|
|
65796
65909
|
} catch {
|
|
65797
|
-
await
|
|
65910
|
+
await sleep2(2000);
|
|
65798
65911
|
continue;
|
|
65799
65912
|
}
|
|
65800
65913
|
if (data.ok && Array.isArray(data.result)) {
|
|
@@ -65810,11 +65923,11 @@ async function pollForDmStart(token, timeoutMs = 120000) {
|
|
|
65810
65923
|
}
|
|
65811
65924
|
}
|
|
65812
65925
|
}
|
|
65813
|
-
await
|
|
65926
|
+
await sleep2(2000);
|
|
65814
65927
|
}
|
|
65815
65928
|
throw new Error("Timed out waiting for /start DM");
|
|
65816
65929
|
}
|
|
65817
|
-
function
|
|
65930
|
+
function sleep2(ms) {
|
|
65818
65931
|
return new Promise((resolve17) => setTimeout(resolve17, ms));
|
|
65819
65932
|
}
|
|
65820
65933
|
|
|
@@ -65958,7 +66071,7 @@ async function completeCreation(name, code, opts = {}) {
|
|
|
65958
66071
|
// src/agents/add-orchestrator.ts
|
|
65959
66072
|
import { resolve as resolve19, join as join19 } from "node:path";
|
|
65960
66073
|
import { existsSync as existsSync28, rmSync as rmSync8, statSync as statSync15 } from "node:fs";
|
|
65961
|
-
import { execFileSync as
|
|
66074
|
+
import { execFileSync as execFileSync14 } from "node:child_process";
|
|
65962
66075
|
init_onboarding();
|
|
65963
66076
|
|
|
65964
66077
|
// src/setup/botfather-walkthrough.ts
|
|
@@ -66532,7 +66645,7 @@ function pruneBundledSkills(agentDir, keep, scope) {
|
|
|
66532
66645
|
}
|
|
66533
66646
|
function defaultIsUnitActive(unitName) {
|
|
66534
66647
|
try {
|
|
66535
|
-
const out =
|
|
66648
|
+
const out = execFileSync14("systemctl", ["--user", "is-active", unitName], {
|
|
66536
66649
|
encoding: "utf-8",
|
|
66537
66650
|
stdio: ["ignore", "pipe", "pipe"]
|
|
66538
66651
|
}).trim();
|
|
@@ -74986,7 +75099,8 @@ class VaultBroker {
|
|
|
74986
75099
|
return;
|
|
74987
75100
|
}
|
|
74988
75101
|
try {
|
|
74989
|
-
const
|
|
75102
|
+
const agentsDir = this.config ? resolveAgentsDir(this.config) : path3.join(os4.homedir(), ".switchroom", "agents");
|
|
75103
|
+
const tokenDir = path3.join(agentsDir, agent);
|
|
74990
75104
|
mkdirSync24(tokenDir, { recursive: true });
|
|
74991
75105
|
const tokenPath = path3.join(tokenDir, ".vault-token");
|
|
74992
75106
|
const tmpPath = `${tokenPath}.tmp.${process.pid}`;
|
|
@@ -75050,7 +75164,8 @@ class VaultBroker {
|
|
|
75050
75164
|
try {
|
|
75051
75165
|
const row = this.grantsDb.query("SELECT agent_slug FROM vault_grants WHERE id = ?").get(id);
|
|
75052
75166
|
if (row && AgentNameSchema.safeParse(row.agent_slug).success) {
|
|
75053
|
-
const
|
|
75167
|
+
const agentsDir = this.config ? resolveAgentsDir(this.config) : path3.join(os4.homedir(), ".switchroom", "agents");
|
|
75168
|
+
const tokenPath = path3.join(agentsDir, row.agent_slug, ".vault-token");
|
|
75054
75169
|
if (existsSync41(tokenPath)) {
|
|
75055
75170
|
try {
|
|
75056
75171
|
unlinkSync10(tokenPath);
|
|
@@ -78288,7 +78403,11 @@ async function resolveLiteLLMForHindsight(config) {
|
|
|
78288
78403
|
const result = await getViaBrokerStructured("litellm/hindsight/api-key").catch(() => null);
|
|
78289
78404
|
if (!result || result.kind !== "ok" || result.entry.kind !== "string")
|
|
78290
78405
|
return;
|
|
78291
|
-
return {
|
|
78406
|
+
return {
|
|
78407
|
+
baseUrl: topLiteLLM.base_url,
|
|
78408
|
+
apiKey: result.entry.value,
|
|
78409
|
+
model: config.memory?.config?.llm_model
|
|
78410
|
+
};
|
|
78292
78411
|
}
|
|
78293
78412
|
function readRecallLog(agentDir, limit) {
|
|
78294
78413
|
const path4 = join40(agentDir, ".claude", "plugins", "data", "hindsight-memory-inline", "state", "recall_log.jsonl");
|
|
@@ -78552,8 +78671,9 @@ Cross-agent reflection plan
|
|
|
78552
78671
|
const provider = "claude-code";
|
|
78553
78672
|
console.log(source_default.gray(" Starting Hindsight Docker container..."));
|
|
78554
78673
|
try {
|
|
78555
|
-
const
|
|
78556
|
-
|
|
78674
|
+
const hindsightConfig = getConfig(program3);
|
|
78675
|
+
const litellmCfg = await resolveLiteLLMForHindsight(hindsightConfig);
|
|
78676
|
+
startHindsight(ports, litellmCfg, opts.tag, hindsightConfig.hindsight?.llm);
|
|
78557
78677
|
if (litellmCfg) {
|
|
78558
78678
|
console.log(source_default.gray(" LiteLLM routing enabled for hindsight (--network host)."));
|
|
78559
78679
|
}
|
|
@@ -78599,7 +78719,7 @@ Cross-agent reflection plan
|
|
|
78599
78719
|
console.log(source_default.bold(`
|
|
78600
78720
|
# Add this to your docker-compose.yml:
|
|
78601
78721
|
`));
|
|
78602
|
-
console.log(generateHindsightComposeSnippet());
|
|
78722
|
+
console.log(generateHindsightComposeSnippet(getConfig(program3).hindsight?.llm));
|
|
78603
78723
|
console.log();
|
|
78604
78724
|
});
|
|
78605
78725
|
memory.command("recall-log [agent]").description("Show recent auto-recall events (per-turn JSONL log) \u2014 see what was injected, when the cap fired, hit rate hints").option("-n, --limit <n>", "Tail the last N events per agent (default 20)", "20").option("--json", "Emit raw JSONL (one entry per line)").action(withConfigError(async (agent, opts) => {
|
|
@@ -83403,7 +83523,11 @@ async function resolveLiteLLMForHindsight2(config) {
|
|
|
83403
83523
|
const result = await getViaBrokerStructured("litellm/hindsight/api-key").catch(() => null);
|
|
83404
83524
|
if (!result || result.kind !== "ok" || result.entry.kind !== "string")
|
|
83405
83525
|
return;
|
|
83406
|
-
return {
|
|
83526
|
+
return {
|
|
83527
|
+
baseUrl: topLiteLLM.base_url,
|
|
83528
|
+
apiKey: result.entry.value,
|
|
83529
|
+
model: config.memory?.config?.llm_model
|
|
83530
|
+
};
|
|
83407
83531
|
}
|
|
83408
83532
|
async function stepMemoryBackend(config, nonInteractive, switchroomConfigPath) {
|
|
83409
83533
|
stepHeader(6, "Memory backend", STEP_ACTIVE);
|
|
@@ -83495,7 +83619,7 @@ async function stepMemoryBackend(config, nonInteractive, switchroomConfigPath) {
|
|
|
83495
83619
|
const spin = spinner("Starting Hindsight Docker container...");
|
|
83496
83620
|
try {
|
|
83497
83621
|
const litellmCfg = await resolveLiteLLMForHindsight2(config);
|
|
83498
|
-
startHindsight(ports, litellmCfg);
|
|
83622
|
+
startHindsight(ports, litellmCfg, undefined, config.hindsight?.llm);
|
|
83499
83623
|
if (litellmCfg) {
|
|
83500
83624
|
console.log(source_default.gray(" LiteLLM routing enabled (--network host, ANTHROPIC_BASE_URL set)."));
|
|
83501
83625
|
}
|