switchroom 0.15.42 → 0.15.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-scheduler/index.js +37 -1
- package/dist/auth-broker/index.js +37 -1
- package/dist/cli/notion-write-pretool.mjs +37 -1
- package/dist/cli/switchroom.js +194 -9
- package/dist/host-control/main.js +37 -1
- package/dist/vault/approvals/kernel-server.js +38 -2
- package/dist/vault/broker/server.js +38 -2
- package/package.json +1 -1
- package/profiles/_base/start.sh.hbs +6 -0
- package/telegram-plugin/dist/gateway/gateway.js +43 -7
- package/vendor/hindsight-memory/scripts/lib/gateway_ipc.py +29 -0
- package/vendor/hindsight-memory/scripts/recall.py +65 -2
- package/vendor/hindsight-memory/scripts/tests/test_sender_routing.py +127 -0
package/dist/cli/switchroom.js
CHANGED
|
@@ -13520,7 +13520,7 @@ var init_zod = __esm(() => {
|
|
|
13520
13520
|
});
|
|
13521
13521
|
|
|
13522
13522
|
// src/config/schema.ts
|
|
13523
|
-
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, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, ReleaseBlock, NetworkIsolationSchema, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, DEFAULT_PROFILE = "default", AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, SwitchroomConfigSchema;
|
|
13523
|
+
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, 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, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
|
|
13524
13524
|
var init_schema = __esm(() => {
|
|
13525
13525
|
init_zod();
|
|
13526
13526
|
CodeRepoEntrySchema = exports_external.object({
|
|
@@ -13641,6 +13641,8 @@ var init_schema = __esm(() => {
|
|
|
13641
13641
|
cache_ttl_secs: exports_external.number().int().min(0).optional().describe("Per-session recall cache TTL in seconds. When > 0, identical " + "(prompt, bank) within the same session reuse the cached recall " + "result instead of round-tripping to Hindsight. 0 disables. " + "Default is 600 (10 min) for switchroom-managed agents."),
|
|
13642
13642
|
min_overlap: exports_external.number().min(0).max(1).optional().describe("Minimum Jaccard token overlap [0.0\u20131.0] between the user " + "prompt and a memory's text for the memory to be injected. " + "Drops low-relevance matches before the count cap so weak hits " + "don't fill the slot on real queries. 0.0 disables (default \u2014 " + "current behaviour). Try 0.10\u20130.20 to start; observe the " + "`overlap_dropped` field via `switchroom memory recall-log`."),
|
|
13643
13643
|
types: exports_external.array(exports_external.string()).optional().describe("Hindsight fact types to recall. Switchroom default is " + '["world", "experience", "observation"] \u2014 the synthesized ' + "`observation` tier is on by default. Set to " + '["world", "experience"] to opt out of observation-backed ' + "recall for this agent (or fleet-wide under defaults)."),
|
|
13644
|
+
additional_banks: exports_external.array(exports_external.string()).optional().describe("Extra Hindsight banks to recall from on every turn, merged into " + "the agent's own bank results \u2014 e.g. a shared operator/household " + "profile bank authored via `switchroom memory profile`. Each is " + "recalled with an 8s timeout and is non-fatal on failure. Stays " + "within the single tenant: all banks are the operator's data, in " + "the operator's Hindsight instance (see the `single-tenant` " + "invariant). Defaults to [] (no extra banks)."),
|
|
13645
|
+
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`."),
|
|
13644
13646
|
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."),
|
|
13645
13647
|
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).")
|
|
13646
13648
|
}).optional().describe("Auto-recall tuning knobs")
|
|
@@ -13854,8 +13856,12 @@ var init_schema = __esm(() => {
|
|
|
13854
13856
|
message: "release.channel and release.pin are mutually exclusive"
|
|
13855
13857
|
});
|
|
13856
13858
|
NetworkIsolationSchema = exports_external.enum(["host", "strict"]).optional().describe("Container network mode (sec WS6-F1 #1390 / feature #1413). " + "'host' (DEFAULT when unset): `network_mode: host` \u2014 the agent " + "shares the host network stack; hindsight 127.0.0.1:18888 and " + "operator-LAN devices are reachable, but there is NO network " + "isolation from sibling agents or host services (the documented, " + "deliberate shared-host tradeoff). 'strict': the agent joins its " + "OWN dedicated docker bridge network instead \u2014 it cannot reach " + "sibling agents; host services are reached via " + "`host.docker.internal`. OPT-IN: validate hindsight / operator-" + "LAN / cron / boot-self-test paths for your deployment before " + "adopting fleet-wide (default-flip is deferred to that validation " + "cycle, #1413). Cascades override (agent \u2192 profile \u2192 defaults).");
|
|
13859
|
+
servesField = exports_external.array(exports_external.string()).optional().describe("Users (keys in the top-level `users:` block) this agent works for. When " + "a served user messages this agent, their profile_bank is recalled " + "(speaker routing \u2192 memory.recall.sender_banks). Unions with any explicit " + "memory.recall.sender_banks. NOTE: this does not yet generate access " + "(allowFrom) \u2014 pair agent access as today; allowFrom generation is a " + "later phase.");
|
|
13860
|
+
knowsField = exports_external.array(exports_external.string()).optional().describe("Users or banks this agent always knows as subjects \u2014 recalled and " + "recall-ranked even when that person is not the speaker (\u2192 " + "memory.recall.additional_banks). A `users:` key resolves to that user's " + "profile_bank; any other string is used as a raw bank name (e.g. a `kids` " + "profile bank with no Telegram identity). Unions with any explicit " + "memory.recall.additional_banks.");
|
|
13857
13861
|
profileFields = {
|
|
13858
13862
|
extends: exports_external.string().optional(),
|
|
13863
|
+
serves: servesField,
|
|
13864
|
+
knows: knowsField,
|
|
13859
13865
|
bot_token: exports_external.string().optional(),
|
|
13860
13866
|
release: ReleaseBlock.optional().describe("Release-channel pin / pointer. Either `channel` (dev|rc|latest) or " + "`pin` (sha-<hex>|v<semver>) \u2014 mutually exclusive. Per-agent value " + "REPLACES the root entirely (no field merge)."),
|
|
13861
13867
|
timezone: exports_external.string().regex(TIMEZONE_REGEX, "timezone must be an IANA zone name like 'Australia/Melbourne' or 'UTC' " + "(three-letter aliases like EST/PST and bare offsets like UTC+10 are not accepted)").optional().describe("IANA timezone name (e.g. 'Australia/Melbourne', 'America/New_York', " + "'UTC'). Used to generate the per-turn local-time hint the agent's " + "UserPromptSubmit timezone hook emits, and baked into the agent " + "container's `environment.TZ` in compose so subprocess `date`/" + "`Date.now()` are correct. If unset at every cascade layer, switchroom " + "auto-detects from /etc/timezone and warns on `reconcile` when the " + "detected zone is UTC."),
|
|
@@ -13876,7 +13882,9 @@ var init_schema = __esm(() => {
|
|
|
13876
13882
|
recall: exports_external.object({
|
|
13877
13883
|
max_memories: exports_external.number().int().min(0).optional(),
|
|
13878
13884
|
cache_ttl_secs: exports_external.number().int().min(0).optional(),
|
|
13879
|
-
min_overlap: exports_external.number().min(0).max(1).optional()
|
|
13885
|
+
min_overlap: exports_external.number().min(0).max(1).optional(),
|
|
13886
|
+
additional_banks: exports_external.array(exports_external.string()).optional(),
|
|
13887
|
+
sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional()
|
|
13880
13888
|
}).optional()
|
|
13881
13889
|
}).optional(),
|
|
13882
13890
|
schedule: exports_external.array(ScheduleEntrySchema).optional(),
|
|
@@ -13921,6 +13929,8 @@ var init_schema = __esm(() => {
|
|
|
13921
13929
|
AgentDefaultsSchema = exports_external.object(defaultsFields).optional();
|
|
13922
13930
|
AgentSchema = exports_external.object({
|
|
13923
13931
|
extends: exports_external.string().optional().describe("Name of a profile to inherit from (e.g., 'coding', 'health-coach'). " + "Profiles may be defined inline under switchroom.yaml `profiles:` or as a " + "filesystem directory `profiles/<name>/`. Defaults to DEFAULT_PROFILE " + "('default') when unset."),
|
|
13932
|
+
serves: servesField,
|
|
13933
|
+
knows: knowsField,
|
|
13924
13934
|
bot_token: exports_external.string().optional().describe("Per-agent Telegram bot token or vault reference (overrides global telegram.bot_token)"),
|
|
13925
13935
|
release: ReleaseBlock.optional().describe("Per-agent release-channel pin / pointer. REPLACES the root " + "`release` block entirely (no field merge) \u2014 a pinned agent does " + "not inherit the fleet channel, and vice versa."),
|
|
13926
13936
|
bot_username: exports_external.string().optional().describe("Per-agent Telegram bot username (without leading @) when it doesn't " + "contain the agent slug. Replaces the default 'username includes slug' " + "preflight check with an exact (case-insensitive) match. Use when an " + "agent and its bot have intentionally divergent names (e.g. agent " + "'lawgpt' paired with bot '@meken_law_bot')."),
|
|
@@ -14094,6 +14104,11 @@ var init_schema = __esm(() => {
|
|
|
14094
14104
|
CronConfigSchema = exports_external.object({
|
|
14095
14105
|
egress: CronEgressSchema.optional().describe("SSRF/exfil fence for http-diff polls.")
|
|
14096
14106
|
});
|
|
14107
|
+
UserSchema = exports_external.object({
|
|
14108
|
+
name: exports_external.string().optional().describe("Display name for the user."),
|
|
14109
|
+
telegram_ids: exports_external.array(exports_external.string()).min(1).describe("Telegram username(s) and/or numeric user id(s) identifying this user " + "(a leading @ is optional). Matched against the message sender for " + "per-speaker memory routing."),
|
|
14110
|
+
profile_bank: exports_external.string().describe("Hindsight bank holding this user's memory profile (author via " + "`switchroom memory profile add <bank> ...`).")
|
|
14111
|
+
});
|
|
14097
14112
|
SwitchroomConfigSchema = exports_external.object({
|
|
14098
14113
|
switchroom: exports_external.object({
|
|
14099
14114
|
version: exports_external.literal(1).describe("Config schema version"),
|
|
@@ -14140,10 +14155,31 @@ var init_schema = __esm(() => {
|
|
|
14140
14155
|
})).optional().describe("RFC #1873: per-Microsoft-account ACL. Maps account email \u2192 list of " + "agents permitted to use that account's broker credentials. Written " + "by `switchroom auth microsoft enable|disable`; read by the broker " + "on get-credentials with provider=microsoft."),
|
|
14141
14156
|
defaults: AgentDefaultsSchema.describe("Implicit bottom-of-cascade profile applied to every agent before " + "per-agent config and `extends:` resolution. Tools, mcp_servers, and " + "schedule are unioned/concatenated; scalars and nested objects are " + "shallow-merged with per-agent values winning."),
|
|
14142
14157
|
profiles: exports_external.record(exports_external.string(), ProfileSchema).optional().describe("Named profile definitions. Agents reference via `extends: <name>`. " + "Inline profiles declared here take priority over filesystem " + "profiles/<name>/ directories when both exist."),
|
|
14158
|
+
users: exports_external.record(exports_external.string(), UserSchema).optional().describe("Trusted users the fleet serves \u2014 each a Telegram identity plus a " + "memory profile bank. Assigned to agents via `serves` / `knows`. The " + "operator's own trusted people (single-tenant), not multi-tenant. See " + "reference/rfcs/user-concept.md."),
|
|
14143
14159
|
agents: exports_external.record(exports_external.string().regex(/^[a-z0-9][a-z0-9_-]{0,50}$/, {
|
|
14144
14160
|
message: "Agent name must start with a letter/digit, contain only lowercase letters/digits/hyphens/underscores, and be at most 51 characters (Telegram callback_data byte limit)"
|
|
14145
14161
|
}), AgentSchema).describe("Map of agent name to agent configuration"),
|
|
14146
14162
|
cron: CronConfigSchema.optional().describe("Cheap-cron settings (reference/rfcs/cheap-cron-sessions.md). Operator-owned " + "egress allowlist + host-pinned secret bindings for Tier-0 http-diff " + "polls (\u00a76.1). Required to enable any http-diff poll; not agent-writable.")
|
|
14163
|
+
}).superRefine((cfg, ctx) => {
|
|
14164
|
+
const userKeys = new Set(Object.keys(cfg.users ?? {}));
|
|
14165
|
+
const checkServes = (serves, path) => {
|
|
14166
|
+
(serves ?? []).forEach((s, i) => {
|
|
14167
|
+
if (!userKeys.has(s)) {
|
|
14168
|
+
ctx.addIssue({
|
|
14169
|
+
code: exports_external.ZodIssueCode.custom,
|
|
14170
|
+
message: `serves references unknown user "${s}" \u2014 add it to the top-level ` + "`users:` block (or did you mean `knows` for a raw bank name?)",
|
|
14171
|
+
path: [...path, i]
|
|
14172
|
+
});
|
|
14173
|
+
}
|
|
14174
|
+
});
|
|
14175
|
+
};
|
|
14176
|
+
checkServes(cfg.defaults?.serves, ["defaults", "serves"]);
|
|
14177
|
+
for (const [name, p] of Object.entries(cfg.profiles ?? {})) {
|
|
14178
|
+
checkServes(p.serves, ["profiles", name, "serves"]);
|
|
14179
|
+
}
|
|
14180
|
+
for (const [name, a] of Object.entries(cfg.agents ?? {})) {
|
|
14181
|
+
checkServes(a.serves, ["agents", name, "serves"]);
|
|
14182
|
+
}
|
|
14147
14183
|
});
|
|
14148
14184
|
});
|
|
14149
14185
|
|
|
@@ -50767,8 +50803,8 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
50767
50803
|
import { dirname, join } from "node:path";
|
|
50768
50804
|
|
|
50769
50805
|
// src/build-info.ts
|
|
50770
|
-
var VERSION = "0.15.
|
|
50771
|
-
var COMMIT_SHA = "
|
|
50806
|
+
var VERSION = "0.15.44";
|
|
50807
|
+
var COMMIT_SHA = "a375378f";
|
|
50772
50808
|
|
|
50773
50809
|
// src/cli/resolve-version.ts
|
|
50774
50810
|
function readPackageVersion() {
|
|
@@ -50899,6 +50935,40 @@ var LEGACY_CRON_SCRIPT_BASENAME_RE = /^cron-(\d+)\.sh$/;
|
|
|
50899
50935
|
init_schema();
|
|
50900
50936
|
init_cron_routing();
|
|
50901
50937
|
init_tier_selector();
|
|
50938
|
+
|
|
50939
|
+
// src/config/users.ts
|
|
50940
|
+
init_merge();
|
|
50941
|
+
function resolveUsers(config, agentName) {
|
|
50942
|
+
const users = config.users ?? {};
|
|
50943
|
+
const agentRaw = config.agents?.[agentName];
|
|
50944
|
+
const agentConfig = agentRaw ? resolveAgentConfig(config.defaults, config.profiles, agentRaw) : undefined;
|
|
50945
|
+
const uniq = (xs) => [...new Set(xs)];
|
|
50946
|
+
const serves = uniq([
|
|
50947
|
+
...config.defaults?.serves ?? [],
|
|
50948
|
+
...agentConfig?.serves ?? []
|
|
50949
|
+
]);
|
|
50950
|
+
const knows = uniq([
|
|
50951
|
+
...config.defaults?.knows ?? [],
|
|
50952
|
+
...agentConfig?.knows ?? []
|
|
50953
|
+
]);
|
|
50954
|
+
const senderBanks = {};
|
|
50955
|
+
for (const userName of serves) {
|
|
50956
|
+
const u = users[userName];
|
|
50957
|
+
if (!u)
|
|
50958
|
+
continue;
|
|
50959
|
+
for (const id of u.telegram_ids) {
|
|
50960
|
+
senderBanks[id] = u.profile_bank;
|
|
50961
|
+
}
|
|
50962
|
+
}
|
|
50963
|
+
Object.assign(senderBanks, agentConfig?.memory?.recall?.sender_banks ?? {});
|
|
50964
|
+
const additionalBanks = uniq([
|
|
50965
|
+
...knows.map((entry) => users[entry]?.profile_bank ?? entry),
|
|
50966
|
+
...agentConfig?.memory?.recall?.additional_banks ?? []
|
|
50967
|
+
]);
|
|
50968
|
+
return { senderBanks, additionalBanks };
|
|
50969
|
+
}
|
|
50970
|
+
|
|
50971
|
+
// src/agents/scaffold.ts
|
|
50902
50972
|
init_merge();
|
|
50903
50973
|
init_timezone();
|
|
50904
50974
|
|
|
@@ -52920,13 +52990,14 @@ function installHindsightPlugin(agentName, agentDir, switchroomConfig) {
|
|
|
52920
52990
|
rmSync3(destPath, { recursive: true, force: true });
|
|
52921
52991
|
}
|
|
52922
52992
|
copyDirRecursive2(sourcePath, destPath);
|
|
52923
|
-
|
|
52993
|
+
const additionalBanks = resolveUsers(switchroomConfig, agentName).additionalBanks;
|
|
52994
|
+
applyHindsightSettingsOverrides(destPath, additionalBanks);
|
|
52924
52995
|
const bankId = agentMemory?.collection ?? agentName;
|
|
52925
52996
|
const mcpUrl = memory.config?.url ?? "http://127.0.0.1:8888/mcp/";
|
|
52926
52997
|
const apiBaseUrl = mcpUrl.replace(/\/mcp\/?$/, "").replace(/\/$/, "");
|
|
52927
52998
|
return { pluginDir: destPath, apiBaseUrl, bankId };
|
|
52928
52999
|
}
|
|
52929
|
-
function applyHindsightSettingsOverrides(pluginDestPath) {
|
|
53000
|
+
function applyHindsightSettingsOverrides(pluginDestPath, additionalBanks) {
|
|
52930
53001
|
const settingsPath = join10(pluginDestPath, "settings.json");
|
|
52931
53002
|
if (!existsSync15(settingsPath))
|
|
52932
53003
|
return;
|
|
@@ -52947,6 +53018,9 @@ function applyHindsightSettingsOverrides(pluginDestPath) {
|
|
|
52947
53018
|
settings.recallMinOverlap = 0.1;
|
|
52948
53019
|
settings.recallTypes = ["world", "experience", "observation"];
|
|
52949
53020
|
settings.recallSkipTrivial = true;
|
|
53021
|
+
if (additionalBanks.length > 0) {
|
|
53022
|
+
settings.recallAdditionalBanks = [...additionalBanks];
|
|
53023
|
+
}
|
|
52950
53024
|
writeFileSync5(settingsPath, JSON.stringify(settings, null, 2) + `
|
|
52951
53025
|
`, "utf-8");
|
|
52952
53026
|
}
|
|
@@ -52972,6 +53046,7 @@ function buildWorkspaceContext(args) {
|
|
|
52972
53046
|
hindsightRecallTypes,
|
|
52973
53047
|
hindsightRecallSkipTrivial,
|
|
52974
53048
|
hindsightTopicAliasesJson,
|
|
53049
|
+
hindsightSenderBanksJson,
|
|
52975
53050
|
hindsightTopicFilterMode
|
|
52976
53051
|
} = args;
|
|
52977
53052
|
return {
|
|
@@ -53015,6 +53090,7 @@ function buildWorkspaceContext(args) {
|
|
|
53015
53090
|
hindsightRecallTypes,
|
|
53016
53091
|
hindsightRecallSkipTrivial,
|
|
53017
53092
|
hindsightTopicAliasesJsonQ: hindsightTopicAliasesJson ? shellSingleQuote(hindsightTopicAliasesJson) : undefined,
|
|
53093
|
+
hindsightSenderBanksJsonQ: hindsightSenderBanksJson ? shellSingleQuote(hindsightSenderBanksJson) : undefined,
|
|
53018
53094
|
hindsightTopicFilterMode,
|
|
53019
53095
|
switchroomConfigPathQ: switchroomConfigPath ? shellSingleQuote(resolve11(switchroomConfigPath)) : undefined,
|
|
53020
53096
|
hostHomeQ: hostHomeQForBake(),
|
|
@@ -53224,6 +53300,8 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
|
|
|
53224
53300
|
const hindsightRecallSkipTrivial = agentConfig.memory?.recall?.skip_trivial === undefined ? undefined : String(agentConfig.memory.recall.skip_trivial);
|
|
53225
53301
|
const topicAliases = agentConfig.channels?.telegram?.topic_aliases;
|
|
53226
53302
|
const hindsightTopicAliasesJson = topicAliases && Object.keys(topicAliases).length > 0 ? JSON.stringify(topicAliases) : undefined;
|
|
53303
|
+
const senderBanks = switchroomConfig ? resolveUsers(switchroomConfig, name).senderBanks : agentConfig.memory?.recall?.sender_banks ?? {};
|
|
53304
|
+
const hindsightSenderBanksJson = senderBanks && Object.keys(senderBanks).length > 0 ? JSON.stringify(senderBanks) : undefined;
|
|
53227
53305
|
const hindsightTopicFilterMode = agentConfig.memory?.recall?.topic_filter_mode;
|
|
53228
53306
|
const context = buildWorkspaceContext({
|
|
53229
53307
|
name,
|
|
@@ -53247,6 +53325,7 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
|
|
|
53247
53325
|
hindsightRecallTypes,
|
|
53248
53326
|
hindsightRecallSkipTrivial,
|
|
53249
53327
|
hindsightTopicAliasesJson,
|
|
53328
|
+
hindsightSenderBanksJson,
|
|
53250
53329
|
hindsightTopicFilterMode
|
|
53251
53330
|
});
|
|
53252
53331
|
const dirs = [
|
|
@@ -54081,6 +54160,8 @@ function reconcileAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchr
|
|
|
54081
54160
|
const hindsightRecallSkipTrivial = agentConfig.memory?.recall?.skip_trivial === undefined ? undefined : String(agentConfig.memory.recall.skip_trivial);
|
|
54082
54161
|
const topicAliases = agentConfig.channels?.telegram?.topic_aliases;
|
|
54083
54162
|
const hindsightTopicAliasesJson = topicAliases && Object.keys(topicAliases).length > 0 ? JSON.stringify(topicAliases) : undefined;
|
|
54163
|
+
const senderBanks = switchroomConfig ? resolveUsers(switchroomConfig, name).senderBanks : agentConfig.memory?.recall?.sender_banks ?? {};
|
|
54164
|
+
const hindsightSenderBanksJson = senderBanks && Object.keys(senderBanks).length > 0 ? JSON.stringify(senderBanks) : undefined;
|
|
54084
54165
|
const hindsightTopicFilterMode = agentConfig.memory?.recall?.topic_filter_mode;
|
|
54085
54166
|
if (agentConfig.repos) {
|
|
54086
54167
|
for (const [slug, entry] of Object.entries(agentConfig.repos)) {
|
|
@@ -54117,6 +54198,7 @@ function reconcileAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchr
|
|
|
54117
54198
|
hindsightRecallTypes,
|
|
54118
54199
|
hindsightRecallSkipTrivial,
|
|
54119
54200
|
hindsightTopicAliasesJsonQ: hindsightTopicAliasesJson ? shellSingleQuote(hindsightTopicAliasesJson) : undefined,
|
|
54201
|
+
hindsightSenderBanksJsonQ: hindsightSenderBanksJson ? shellSingleQuote(hindsightSenderBanksJson) : undefined,
|
|
54120
54202
|
hindsightTopicFilterMode,
|
|
54121
54203
|
hostHomeQ: hostHomeQForBake(),
|
|
54122
54204
|
modelQ: shellSingleQuote(resolveMainModel(agentConfig.model)),
|
|
@@ -54506,6 +54588,7 @@ ${body}
|
|
|
54506
54588
|
hindsightRecallTypes,
|
|
54507
54589
|
hindsightRecallSkipTrivial,
|
|
54508
54590
|
hindsightTopicAliasesJson,
|
|
54591
|
+
hindsightSenderBanksJson,
|
|
54509
54592
|
hindsightTopicFilterMode
|
|
54510
54593
|
});
|
|
54511
54594
|
mkdirSync10(reconcileWorkspaceDir, { recursive: true });
|
|
@@ -68892,8 +68975,13 @@ var HINDSIGHT_DEFAULT_RERANKER_BUCKET_BATCHING = "true";
|
|
|
68892
68975
|
var HINDSIGHT_DEFAULT_RERANKER_MAX_CANDIDATES = 150;
|
|
68893
68976
|
var HINDSIGHT_DEFAULT_RERANKER_LOCAL_MAX_CONCURRENT = 4;
|
|
68894
68977
|
var HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8;
|
|
68895
|
-
var
|
|
68896
|
-
var
|
|
68978
|
+
var HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600;
|
|
68979
|
+
var HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_BATCH_SIZE = 12;
|
|
68980
|
+
var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS = 3;
|
|
68981
|
+
var HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_PARALLELISM = 6;
|
|
68982
|
+
var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 300;
|
|
68983
|
+
var HINDSIGHT_DEFAULT_MEM_LIMIT = "8g";
|
|
68984
|
+
var HINDSIGHT_DEFAULT_MEM_RESERVATION = "4g";
|
|
68897
68985
|
var HINDSIGHT_DEFAULT_PIDS_LIMIT = 1000;
|
|
68898
68986
|
var HINDSIGHT_DEFAULT_SHM_SIZE = "2g";
|
|
68899
68987
|
var HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.request.urlopen("http://localhost:8888/health",timeout=4).getcode()==200 else 1)';
|
|
@@ -68976,7 +69064,17 @@ function startHindsight(ports) {
|
|
|
68976
69064
|
"-e",
|
|
68977
69065
|
`HINDSIGHT_API_RERANKER_LOCAL_MAX_CONCURRENT=${HINDSIGHT_DEFAULT_RERANKER_LOCAL_MAX_CONCURRENT}`,
|
|
68978
69066
|
"-e",
|
|
68979
|
-
`HINDSIGHT_API_RECALL_MAX_CONCURRENT=${HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT}
|
|
69067
|
+
`HINDSIGHT_API_RECALL_MAX_CONCURRENT=${HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT}`,
|
|
69068
|
+
"-e",
|
|
69069
|
+
`HINDSIGHT_API_REFLECT_WALL_TIMEOUT=${HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S}`,
|
|
69070
|
+
"-e",
|
|
69071
|
+
`HINDSIGHT_API_CONSOLIDATION_LLM_BATCH_SIZE=${HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_BATCH_SIZE}`,
|
|
69072
|
+
"-e",
|
|
69073
|
+
`HINDSIGHT_API_WORKER_CONSOLIDATION_MAX_SLOTS=${HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS}`,
|
|
69074
|
+
"-e",
|
|
69075
|
+
`HINDSIGHT_API_CONSOLIDATION_LLM_PARALLELISM=${HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_PARALLELISM}`,
|
|
69076
|
+
"-e",
|
|
69077
|
+
`HINDSIGHT_API_CONSOLIDATION_MAX_MEMORIES_PER_ROUND=${HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND}`
|
|
68980
69078
|
];
|
|
68981
69079
|
const args = [
|
|
68982
69080
|
"run",
|
|
@@ -69074,6 +69172,11 @@ function generateHindsightComposeSnippet() {
|
|
|
69074
69172
|
` - HINDSIGHT_API_RERANKER_MAX_CANDIDATES=${HINDSIGHT_DEFAULT_RERANKER_MAX_CANDIDATES}`,
|
|
69075
69173
|
` - HINDSIGHT_API_RERANKER_LOCAL_MAX_CONCURRENT=${HINDSIGHT_DEFAULT_RERANKER_LOCAL_MAX_CONCURRENT}`,
|
|
69076
69174
|
` - HINDSIGHT_API_RECALL_MAX_CONCURRENT=${HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT}`,
|
|
69175
|
+
` - HINDSIGHT_API_REFLECT_WALL_TIMEOUT=${HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S}`,
|
|
69176
|
+
` - HINDSIGHT_API_CONSOLIDATION_LLM_BATCH_SIZE=${HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_BATCH_SIZE}`,
|
|
69177
|
+
` - HINDSIGHT_API_WORKER_CONSOLIDATION_MAX_SLOTS=${HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS}`,
|
|
69178
|
+
` - HINDSIGHT_API_CONSOLIDATION_LLM_PARALLELISM=${HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_PARALLELISM}`,
|
|
69179
|
+
` - HINDSIGHT_API_CONSOLIDATION_MAX_MEMORIES_PER_ROUND=${HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND}`,
|
|
69077
69180
|
` mem_limit: ${HINDSIGHT_DEFAULT_MEM_LIMIT}`,
|
|
69078
69181
|
` mem_reservation: ${HINDSIGHT_DEFAULT_MEM_RESERVATION}`,
|
|
69079
69182
|
` pids_limit: ${HINDSIGHT_DEFAULT_PIDS_LIMIT}`,
|
|
@@ -69475,6 +69578,88 @@ Demoting memory ${source_default.cyan(memoryId)}`));
|
|
|
69475
69578
|
console.error(source_default.gray(" The Hindsight MCP `update_memory` tool may not be exposed by your deployment, or the memory ID may be wrong. Try `switchroom memory recall-log " + agent + " --json` to confirm the ID surfaced recently."));
|
|
69476
69579
|
process.exit(1);
|
|
69477
69580
|
}));
|
|
69581
|
+
const restBase = (url) => (url ?? "http://127.0.0.1:8888/mcp/").replace(/\/mcp\/?$/, "").replace(/\/$/, "");
|
|
69582
|
+
const VALID_BANK = /^[a-zA-Z0-9_.-]+$/;
|
|
69583
|
+
const profile = memory.command("profile").description("Author + inspect operator profile banks (shared/per-user memory; wire via memory.recall.additional_banks)");
|
|
69584
|
+
profile.command("add <bank> <fact...>").description("Add an operator-authored fact to a profile bank (creates the bank on first write)").option("--timeout <ms>", "HTTP timeout in milliseconds (retain runs synchronously; default: 60000)", "60000").action(withConfigError(async (bank, factWords, opts) => {
|
|
69585
|
+
const config = getConfig(program3);
|
|
69586
|
+
if (!bank || !VALID_BANK.test(bank)) {
|
|
69587
|
+
console.error(source_default.red("Bank name must be non-empty and contain only letters, digits, '.', '_', or '-'."));
|
|
69588
|
+
process.exit(1);
|
|
69589
|
+
}
|
|
69590
|
+
const content = factWords.join(" ").trim();
|
|
69591
|
+
if (content.length === 0) {
|
|
69592
|
+
console.error(source_default.red("A non-empty fact is required."));
|
|
69593
|
+
process.exit(1);
|
|
69594
|
+
}
|
|
69595
|
+
const base = restBase(config.memory?.config?.url);
|
|
69596
|
+
const url = `${base}/v1/default/banks/${encodeURIComponent(bank)}/memories`;
|
|
69597
|
+
const timeoutMs = Math.max(1000, parseInt(opts.timeout, 10) || 60000);
|
|
69598
|
+
const ctrl = new AbortController;
|
|
69599
|
+
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
69600
|
+
try {
|
|
69601
|
+
const res = await fetch(url, {
|
|
69602
|
+
method: "POST",
|
|
69603
|
+
headers: { "Content-Type": "application/json" },
|
|
69604
|
+
body: JSON.stringify({
|
|
69605
|
+
items: [{ content, tags: ["operator-authored", "profile"] }],
|
|
69606
|
+
async: false
|
|
69607
|
+
}),
|
|
69608
|
+
signal: ctrl.signal
|
|
69609
|
+
});
|
|
69610
|
+
if (!res.ok) {
|
|
69611
|
+
console.error(source_default.red(`\u2717 Retain failed: HTTP ${res.status}`), source_default.gray(await res.text().catch(() => "")));
|
|
69612
|
+
process.exit(1);
|
|
69613
|
+
}
|
|
69614
|
+
console.log(source_default.green(`\u2713 Added to profile bank "${bank}"`));
|
|
69615
|
+
console.log(source_default.gray(` ${content}`));
|
|
69616
|
+
console.log(source_default.gray(" (fact extraction runs in the background \u2014 `profile list` may lag a few seconds)"));
|
|
69617
|
+
console.log(source_default.gray(`
|
|
69618
|
+
Wire it into agents via memory.recall.additional_banks: ["${bank}"] in switchroom.yaml,
|
|
69619
|
+
then \`switchroom apply\` + restart the agent(s).`));
|
|
69620
|
+
} catch (e) {
|
|
69621
|
+
console.error(source_default.red("\u2717 Retain failed:"), source_default.gray(e instanceof Error ? e.message : String(e)));
|
|
69622
|
+
process.exit(1);
|
|
69623
|
+
} finally {
|
|
69624
|
+
clearTimeout(t);
|
|
69625
|
+
}
|
|
69626
|
+
}));
|
|
69627
|
+
profile.command("list <bank>").description("List the memory units in a profile bank").option("--limit <n>", "Max units to show (default: 50)", "50").option("--timeout <ms>", "HTTP timeout in milliseconds (default: 10000)", "10000").action(withConfigError(async (bank, opts) => {
|
|
69628
|
+
const config = getConfig(program3);
|
|
69629
|
+
if (!bank || !VALID_BANK.test(bank)) {
|
|
69630
|
+
console.error(source_default.red("Bank name must contain only letters, digits, '.', '_', or '-'."));
|
|
69631
|
+
process.exit(1);
|
|
69632
|
+
}
|
|
69633
|
+
const base = restBase(config.memory?.config?.url);
|
|
69634
|
+
const limit = Math.max(1, parseInt(opts.limit, 10) || 50);
|
|
69635
|
+
const url = `${base}/v1/default/banks/${encodeURIComponent(bank)}/memories/list?limit=${limit}`;
|
|
69636
|
+
const timeoutMs = Math.max(1000, parseInt(opts.timeout, 10) || 1e4);
|
|
69637
|
+
const ctrl = new AbortController;
|
|
69638
|
+
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
69639
|
+
try {
|
|
69640
|
+
const res = await fetch(url, { signal: ctrl.signal });
|
|
69641
|
+
if (!res.ok) {
|
|
69642
|
+
console.error(source_default.red(`\u2717 List failed: HTTP ${res.status}`), source_default.gray(await res.text().catch(() => "")));
|
|
69643
|
+
process.exit(1);
|
|
69644
|
+
}
|
|
69645
|
+
const data = await res.json();
|
|
69646
|
+
const units = data.results ?? data.memories ?? data.units ?? [];
|
|
69647
|
+
console.log(source_default.bold(`
|
|
69648
|
+
Profile bank "${bank}" \u2014 ${units.length} unit(s)
|
|
69649
|
+
`));
|
|
69650
|
+
for (const u of units) {
|
|
69651
|
+
const ft = (u.fact_type ?? "?").padEnd(11);
|
|
69652
|
+
const text = String(u.content ?? u.text ?? "").replace(/\s+/g, " ").slice(0, 100);
|
|
69653
|
+
console.log(` ${source_default.gray(ft)} ${text}`);
|
|
69654
|
+
}
|
|
69655
|
+
console.log();
|
|
69656
|
+
} catch (e) {
|
|
69657
|
+
console.error(source_default.red("\u2717 List failed:"), source_default.gray(e instanceof Error ? e.message : String(e)));
|
|
69658
|
+
process.exit(1);
|
|
69659
|
+
} finally {
|
|
69660
|
+
clearTimeout(t);
|
|
69661
|
+
}
|
|
69662
|
+
}));
|
|
69478
69663
|
}
|
|
69479
69664
|
|
|
69480
69665
|
// src/cli/web.ts
|
|
@@ -13812,6 +13812,8 @@ var AgentMemorySchema = exports_external.object({
|
|
|
13812
13812
|
cache_ttl_secs: exports_external.number().int().min(0).optional().describe("Per-session recall cache TTL in seconds. When > 0, identical " + "(prompt, bank) within the same session reuse the cached recall " + "result instead of round-tripping to Hindsight. 0 disables. " + "Default is 600 (10 min) for switchroom-managed agents."),
|
|
13813
13813
|
min_overlap: exports_external.number().min(0).max(1).optional().describe("Minimum Jaccard token overlap [0.0–1.0] between the user " + "prompt and a memory's text for the memory to be injected. " + "Drops low-relevance matches before the count cap so weak hits " + "don't fill the slot on real queries. 0.0 disables (default — " + "current behaviour). Try 0.10–0.20 to start; observe the " + "`overlap_dropped` field via `switchroom memory recall-log`."),
|
|
13814
13814
|
types: exports_external.array(exports_external.string()).optional().describe("Hindsight fact types to recall. Switchroom default is " + '["world", "experience", "observation"] — the synthesized ' + "`observation` tier is on by default. Set to " + '["world", "experience"] to opt out of observation-backed ' + "recall for this agent (or fleet-wide under defaults)."),
|
|
13815
|
+
additional_banks: exports_external.array(exports_external.string()).optional().describe("Extra Hindsight banks to recall from on every turn, merged into " + "the agent's own bank results — e.g. a shared operator/household " + "profile bank authored via `switchroom memory profile`. Each is " + "recalled with an 8s timeout and is non-fatal on failure. Stays " + "within the single tenant: all banks are the operator's data, in " + "the operator's Hindsight instance (see the `single-tenant` " + "invariant). Defaults to [] (no extra banks)."),
|
|
13816
|
+
sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Per-speaker recall routing: a map of Telegram sender → extra " + "recall bank. When a message arrives, the agent also recalls the " + "speaker's bank (matched by Telegram username — a leading @ is " + "optional — or numeric user_id), merged " + "into its own results — 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`."),
|
|
13815
13817
|
skip_trivial: exports_external.boolean().optional().describe("Skip recall on plausibly-stateless trivial turns (time/date/" + "greeting). Switchroom default true — 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."),
|
|
13816
13818
|
topic_filter_mode: exports_external.enum(["soft-preamble", "hard-filter"]).optional().describe("Supergroup-mode cross-topic memory behaviour. Default " + "(unset) → soft-preamble: recall returns memories from all " + "topics, and a 'Current topic: …' 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).")
|
|
13817
13819
|
}).optional().describe("Auto-recall tuning knobs")
|
|
@@ -14025,8 +14027,12 @@ var ReleaseBlock = exports_external.object({
|
|
|
14025
14027
|
message: "release.channel and release.pin are mutually exclusive"
|
|
14026
14028
|
});
|
|
14027
14029
|
var NetworkIsolationSchema = exports_external.enum(["host", "strict"]).optional().describe("Container network mode (sec WS6-F1 #1390 / feature #1413). " + "'host' (DEFAULT when unset): `network_mode: host` — the agent " + "shares the host network stack; hindsight 127.0.0.1:18888 and " + "operator-LAN devices are reachable, but there is NO network " + "isolation from sibling agents or host services (the documented, " + "deliberate shared-host tradeoff). 'strict': the agent joins its " + "OWN dedicated docker bridge network instead — it cannot reach " + "sibling agents; host services are reached via " + "`host.docker.internal`. OPT-IN: validate hindsight / operator-" + "LAN / cron / boot-self-test paths for your deployment before " + "adopting fleet-wide (default-flip is deferred to that validation " + "cycle, #1413). Cascades override (agent → profile → defaults).");
|
|
14030
|
+
var servesField = exports_external.array(exports_external.string()).optional().describe("Users (keys in the top-level `users:` block) this agent works for. When " + "a served user messages this agent, their profile_bank is recalled " + "(speaker routing → memory.recall.sender_banks). Unions with any explicit " + "memory.recall.sender_banks. NOTE: this does not yet generate access " + "(allowFrom) — pair agent access as today; allowFrom generation is a " + "later phase.");
|
|
14031
|
+
var knowsField = exports_external.array(exports_external.string()).optional().describe("Users or banks this agent always knows as subjects — recalled and " + "recall-ranked even when that person is not the speaker (→ " + "memory.recall.additional_banks). A `users:` key resolves to that user's " + "profile_bank; any other string is used as a raw bank name (e.g. a `kids` " + "profile bank with no Telegram identity). Unions with any explicit " + "memory.recall.additional_banks.");
|
|
14028
14032
|
var profileFields = {
|
|
14029
14033
|
extends: exports_external.string().optional(),
|
|
14034
|
+
serves: servesField,
|
|
14035
|
+
knows: knowsField,
|
|
14030
14036
|
bot_token: exports_external.string().optional(),
|
|
14031
14037
|
release: ReleaseBlock.optional().describe("Release-channel pin / pointer. Either `channel` (dev|rc|latest) or " + "`pin` (sha-<hex>|v<semver>) — mutually exclusive. Per-agent value " + "REPLACES the root entirely (no field merge)."),
|
|
14032
14038
|
timezone: exports_external.string().regex(TIMEZONE_REGEX, "timezone must be an IANA zone name like 'Australia/Melbourne' or 'UTC' " + "(three-letter aliases like EST/PST and bare offsets like UTC+10 are not accepted)").optional().describe("IANA timezone name (e.g. 'Australia/Melbourne', 'America/New_York', " + "'UTC'). Used to generate the per-turn local-time hint the agent's " + "UserPromptSubmit timezone hook emits, and baked into the agent " + "container's `environment.TZ` in compose so subprocess `date`/" + "`Date.now()` are correct. If unset at every cascade layer, switchroom " + "auto-detects from /etc/timezone and warns on `reconcile` when the " + "detected zone is UTC."),
|
|
@@ -14047,7 +14053,9 @@ var profileFields = {
|
|
|
14047
14053
|
recall: exports_external.object({
|
|
14048
14054
|
max_memories: exports_external.number().int().min(0).optional(),
|
|
14049
14055
|
cache_ttl_secs: exports_external.number().int().min(0).optional(),
|
|
14050
|
-
min_overlap: exports_external.number().min(0).max(1).optional()
|
|
14056
|
+
min_overlap: exports_external.number().min(0).max(1).optional(),
|
|
14057
|
+
additional_banks: exports_external.array(exports_external.string()).optional(),
|
|
14058
|
+
sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional()
|
|
14051
14059
|
}).optional()
|
|
14052
14060
|
}).optional(),
|
|
14053
14061
|
schedule: exports_external.array(ScheduleEntrySchema).optional(),
|
|
@@ -14092,6 +14100,8 @@ var { extends: _omitExtends, ...defaultsFields } = profileFields;
|
|
|
14092
14100
|
var AgentDefaultsSchema = exports_external.object(defaultsFields).optional();
|
|
14093
14101
|
var AgentSchema = exports_external.object({
|
|
14094
14102
|
extends: exports_external.string().optional().describe("Name of a profile to inherit from (e.g., 'coding', 'health-coach'). " + "Profiles may be defined inline under switchroom.yaml `profiles:` or as a " + "filesystem directory `profiles/<name>/`. Defaults to DEFAULT_PROFILE " + "('default') when unset."),
|
|
14103
|
+
serves: servesField,
|
|
14104
|
+
knows: knowsField,
|
|
14095
14105
|
bot_token: exports_external.string().optional().describe("Per-agent Telegram bot token or vault reference (overrides global telegram.bot_token)"),
|
|
14096
14106
|
release: ReleaseBlock.optional().describe("Per-agent release-channel pin / pointer. REPLACES the root " + "`release` block entirely (no field merge) — a pinned agent does " + "not inherit the fleet channel, and vice versa."),
|
|
14097
14107
|
bot_username: exports_external.string().optional().describe("Per-agent Telegram bot username (without leading @) when it doesn't " + "contain the agent slug. Replaces the default 'username includes slug' " + "preflight check with an exact (case-insensitive) match. Use when an " + "agent and its bot have intentionally divergent names (e.g. agent " + "'lawgpt' paired with bot '@meken_law_bot')."),
|
|
@@ -14265,6 +14275,11 @@ var CronEgressSchema = exports_external.object({
|
|
|
14265
14275
|
var CronConfigSchema = exports_external.object({
|
|
14266
14276
|
egress: CronEgressSchema.optional().describe("SSRF/exfil fence for http-diff polls.")
|
|
14267
14277
|
});
|
|
14278
|
+
var UserSchema = exports_external.object({
|
|
14279
|
+
name: exports_external.string().optional().describe("Display name for the user."),
|
|
14280
|
+
telegram_ids: exports_external.array(exports_external.string()).min(1).describe("Telegram username(s) and/or numeric user id(s) identifying this user " + "(a leading @ is optional). Matched against the message sender for " + "per-speaker memory routing."),
|
|
14281
|
+
profile_bank: exports_external.string().describe("Hindsight bank holding this user's memory profile (author via " + "`switchroom memory profile add <bank> ...`).")
|
|
14282
|
+
});
|
|
14268
14283
|
var SwitchroomConfigSchema = exports_external.object({
|
|
14269
14284
|
switchroom: exports_external.object({
|
|
14270
14285
|
version: exports_external.literal(1).describe("Config schema version"),
|
|
@@ -14311,10 +14326,31 @@ var SwitchroomConfigSchema = exports_external.object({
|
|
|
14311
14326
|
})).optional().describe("RFC #1873: per-Microsoft-account ACL. Maps account email → list of " + "agents permitted to use that account's broker credentials. Written " + "by `switchroom auth microsoft enable|disable`; read by the broker " + "on get-credentials with provider=microsoft."),
|
|
14312
14327
|
defaults: AgentDefaultsSchema.describe("Implicit bottom-of-cascade profile applied to every agent before " + "per-agent config and `extends:` resolution. Tools, mcp_servers, and " + "schedule are unioned/concatenated; scalars and nested objects are " + "shallow-merged with per-agent values winning."),
|
|
14313
14328
|
profiles: exports_external.record(exports_external.string(), ProfileSchema).optional().describe("Named profile definitions. Agents reference via `extends: <name>`. " + "Inline profiles declared here take priority over filesystem " + "profiles/<name>/ directories when both exist."),
|
|
14329
|
+
users: exports_external.record(exports_external.string(), UserSchema).optional().describe("Trusted users the fleet serves — each a Telegram identity plus a " + "memory profile bank. Assigned to agents via `serves` / `knows`. The " + "operator's own trusted people (single-tenant), not multi-tenant. See " + "reference/rfcs/user-concept.md."),
|
|
14314
14330
|
agents: exports_external.record(exports_external.string().regex(/^[a-z0-9][a-z0-9_-]{0,50}$/, {
|
|
14315
14331
|
message: "Agent name must start with a letter/digit, contain only lowercase letters/digits/hyphens/underscores, and be at most 51 characters (Telegram callback_data byte limit)"
|
|
14316
14332
|
}), AgentSchema).describe("Map of agent name to agent configuration"),
|
|
14317
14333
|
cron: CronConfigSchema.optional().describe("Cheap-cron settings (reference/rfcs/cheap-cron-sessions.md). Operator-owned " + "egress allowlist + host-pinned secret bindings for Tier-0 http-diff " + "polls (§6.1). Required to enable any http-diff poll; not agent-writable.")
|
|
14334
|
+
}).superRefine((cfg, ctx) => {
|
|
14335
|
+
const userKeys = new Set(Object.keys(cfg.users ?? {}));
|
|
14336
|
+
const checkServes = (serves, path) => {
|
|
14337
|
+
(serves ?? []).forEach((s, i) => {
|
|
14338
|
+
if (!userKeys.has(s)) {
|
|
14339
|
+
ctx.addIssue({
|
|
14340
|
+
code: exports_external.ZodIssueCode.custom,
|
|
14341
|
+
message: `serves references unknown user "${s}" — add it to the top-level ` + "`users:` block (or did you mean `knows` for a raw bank name?)",
|
|
14342
|
+
path: [...path, i]
|
|
14343
|
+
});
|
|
14344
|
+
}
|
|
14345
|
+
});
|
|
14346
|
+
};
|
|
14347
|
+
checkServes(cfg.defaults?.serves, ["defaults", "serves"]);
|
|
14348
|
+
for (const [name, p] of Object.entries(cfg.profiles ?? {})) {
|
|
14349
|
+
checkServes(p.serves, ["profiles", name, "serves"]);
|
|
14350
|
+
}
|
|
14351
|
+
for (const [name, a] of Object.entries(cfg.agents ?? {})) {
|
|
14352
|
+
checkServes(a.serves, ["agents", name, "serves"]);
|
|
14353
|
+
}
|
|
14318
14354
|
});
|
|
14319
14355
|
|
|
14320
14356
|
// src/config/paths.ts
|
|
@@ -11299,7 +11299,7 @@ var init_dist = __esm(() => {
|
|
|
11299
11299
|
});
|
|
11300
11300
|
|
|
11301
11301
|
// src/config/schema.ts
|
|
11302
|
-
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, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, ReleaseBlock, NetworkIsolationSchema, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, SwitchroomConfigSchema;
|
|
11302
|
+
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, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, ReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
|
|
11303
11303
|
var init_schema = __esm(() => {
|
|
11304
11304
|
init_zod();
|
|
11305
11305
|
CodeRepoEntrySchema = exports_external.object({
|
|
@@ -11420,6 +11420,8 @@ var init_schema = __esm(() => {
|
|
|
11420
11420
|
cache_ttl_secs: exports_external.number().int().min(0).optional().describe("Per-session recall cache TTL in seconds. When > 0, identical " + "(prompt, bank) within the same session reuse the cached recall " + "result instead of round-tripping to Hindsight. 0 disables. " + "Default is 600 (10 min) for switchroom-managed agents."),
|
|
11421
11421
|
min_overlap: exports_external.number().min(0).max(1).optional().describe("Minimum Jaccard token overlap [0.0–1.0] between the user " + "prompt and a memory's text for the memory to be injected. " + "Drops low-relevance matches before the count cap so weak hits " + "don't fill the slot on real queries. 0.0 disables (default — " + "current behaviour). Try 0.10–0.20 to start; observe the " + "`overlap_dropped` field via `switchroom memory recall-log`."),
|
|
11422
11422
|
types: exports_external.array(exports_external.string()).optional().describe("Hindsight fact types to recall. Switchroom default is " + '["world", "experience", "observation"] — the synthesized ' + "`observation` tier is on by default. Set to " + '["world", "experience"] to opt out of observation-backed ' + "recall for this agent (or fleet-wide under defaults)."),
|
|
11423
|
+
additional_banks: exports_external.array(exports_external.string()).optional().describe("Extra Hindsight banks to recall from on every turn, merged into " + "the agent's own bank results — e.g. a shared operator/household " + "profile bank authored via `switchroom memory profile`. Each is " + "recalled with an 8s timeout and is non-fatal on failure. Stays " + "within the single tenant: all banks are the operator's data, in " + "the operator's Hindsight instance (see the `single-tenant` " + "invariant). Defaults to [] (no extra banks)."),
|
|
11424
|
+
sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Per-speaker recall routing: a map of Telegram sender → extra " + "recall bank. When a message arrives, the agent also recalls the " + "speaker's bank (matched by Telegram username — a leading @ is " + "optional — or numeric user_id), merged " + "into its own results — 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`."),
|
|
11423
11425
|
skip_trivial: exports_external.boolean().optional().describe("Skip recall on plausibly-stateless trivial turns (time/date/" + "greeting). Switchroom default true — 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."),
|
|
11424
11426
|
topic_filter_mode: exports_external.enum(["soft-preamble", "hard-filter"]).optional().describe("Supergroup-mode cross-topic memory behaviour. Default " + "(unset) → soft-preamble: recall returns memories from all " + "topics, and a 'Current topic: …' 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).")
|
|
11425
11427
|
}).optional().describe("Auto-recall tuning knobs")
|
|
@@ -11633,8 +11635,12 @@ var init_schema = __esm(() => {
|
|
|
11633
11635
|
message: "release.channel and release.pin are mutually exclusive"
|
|
11634
11636
|
});
|
|
11635
11637
|
NetworkIsolationSchema = exports_external.enum(["host", "strict"]).optional().describe("Container network mode (sec WS6-F1 #1390 / feature #1413). " + "'host' (DEFAULT when unset): `network_mode: host` — the agent " + "shares the host network stack; hindsight 127.0.0.1:18888 and " + "operator-LAN devices are reachable, but there is NO network " + "isolation from sibling agents or host services (the documented, " + "deliberate shared-host tradeoff). 'strict': the agent joins its " + "OWN dedicated docker bridge network instead — it cannot reach " + "sibling agents; host services are reached via " + "`host.docker.internal`. OPT-IN: validate hindsight / operator-" + "LAN / cron / boot-self-test paths for your deployment before " + "adopting fleet-wide (default-flip is deferred to that validation " + "cycle, #1413). Cascades override (agent → profile → defaults).");
|
|
11638
|
+
servesField = exports_external.array(exports_external.string()).optional().describe("Users (keys in the top-level `users:` block) this agent works for. When " + "a served user messages this agent, their profile_bank is recalled " + "(speaker routing → memory.recall.sender_banks). Unions with any explicit " + "memory.recall.sender_banks. NOTE: this does not yet generate access " + "(allowFrom) — pair agent access as today; allowFrom generation is a " + "later phase.");
|
|
11639
|
+
knowsField = exports_external.array(exports_external.string()).optional().describe("Users or banks this agent always knows as subjects — recalled and " + "recall-ranked even when that person is not the speaker (→ " + "memory.recall.additional_banks). A `users:` key resolves to that user's " + "profile_bank; any other string is used as a raw bank name (e.g. a `kids` " + "profile bank with no Telegram identity). Unions with any explicit " + "memory.recall.additional_banks.");
|
|
11636
11640
|
profileFields = {
|
|
11637
11641
|
extends: exports_external.string().optional(),
|
|
11642
|
+
serves: servesField,
|
|
11643
|
+
knows: knowsField,
|
|
11638
11644
|
bot_token: exports_external.string().optional(),
|
|
11639
11645
|
release: ReleaseBlock.optional().describe("Release-channel pin / pointer. Either `channel` (dev|rc|latest) or " + "`pin` (sha-<hex>|v<semver>) — mutually exclusive. Per-agent value " + "REPLACES the root entirely (no field merge)."),
|
|
11640
11646
|
timezone: exports_external.string().regex(TIMEZONE_REGEX, "timezone must be an IANA zone name like 'Australia/Melbourne' or 'UTC' " + "(three-letter aliases like EST/PST and bare offsets like UTC+10 are not accepted)").optional().describe("IANA timezone name (e.g. 'Australia/Melbourne', 'America/New_York', " + "'UTC'). Used to generate the per-turn local-time hint the agent's " + "UserPromptSubmit timezone hook emits, and baked into the agent " + "container's `environment.TZ` in compose so subprocess `date`/" + "`Date.now()` are correct. If unset at every cascade layer, switchroom " + "auto-detects from /etc/timezone and warns on `reconcile` when the " + "detected zone is UTC."),
|
|
@@ -11655,7 +11661,9 @@ var init_schema = __esm(() => {
|
|
|
11655
11661
|
recall: exports_external.object({
|
|
11656
11662
|
max_memories: exports_external.number().int().min(0).optional(),
|
|
11657
11663
|
cache_ttl_secs: exports_external.number().int().min(0).optional(),
|
|
11658
|
-
min_overlap: exports_external.number().min(0).max(1).optional()
|
|
11664
|
+
min_overlap: exports_external.number().min(0).max(1).optional(),
|
|
11665
|
+
additional_banks: exports_external.array(exports_external.string()).optional(),
|
|
11666
|
+
sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional()
|
|
11659
11667
|
}).optional()
|
|
11660
11668
|
}).optional(),
|
|
11661
11669
|
schedule: exports_external.array(ScheduleEntrySchema).optional(),
|
|
@@ -11700,6 +11708,8 @@ var init_schema = __esm(() => {
|
|
|
11700
11708
|
AgentDefaultsSchema = exports_external.object(defaultsFields).optional();
|
|
11701
11709
|
AgentSchema = exports_external.object({
|
|
11702
11710
|
extends: exports_external.string().optional().describe("Name of a profile to inherit from (e.g., 'coding', 'health-coach'). " + "Profiles may be defined inline under switchroom.yaml `profiles:` or as a " + "filesystem directory `profiles/<name>/`. Defaults to DEFAULT_PROFILE " + "('default') when unset."),
|
|
11711
|
+
serves: servesField,
|
|
11712
|
+
knows: knowsField,
|
|
11703
11713
|
bot_token: exports_external.string().optional().describe("Per-agent Telegram bot token or vault reference (overrides global telegram.bot_token)"),
|
|
11704
11714
|
release: ReleaseBlock.optional().describe("Per-agent release-channel pin / pointer. REPLACES the root " + "`release` block entirely (no field merge) — a pinned agent does " + "not inherit the fleet channel, and vice versa."),
|
|
11705
11715
|
bot_username: exports_external.string().optional().describe("Per-agent Telegram bot username (without leading @) when it doesn't " + "contain the agent slug. Replaces the default 'username includes slug' " + "preflight check with an exact (case-insensitive) match. Use when an " + "agent and its bot have intentionally divergent names (e.g. agent " + "'lawgpt' paired with bot '@meken_law_bot')."),
|
|
@@ -11873,6 +11883,11 @@ var init_schema = __esm(() => {
|
|
|
11873
11883
|
CronConfigSchema = exports_external.object({
|
|
11874
11884
|
egress: CronEgressSchema.optional().describe("SSRF/exfil fence for http-diff polls.")
|
|
11875
11885
|
});
|
|
11886
|
+
UserSchema = exports_external.object({
|
|
11887
|
+
name: exports_external.string().optional().describe("Display name for the user."),
|
|
11888
|
+
telegram_ids: exports_external.array(exports_external.string()).min(1).describe("Telegram username(s) and/or numeric user id(s) identifying this user " + "(a leading @ is optional). Matched against the message sender for " + "per-speaker memory routing."),
|
|
11889
|
+
profile_bank: exports_external.string().describe("Hindsight bank holding this user's memory profile (author via " + "`switchroom memory profile add <bank> ...`).")
|
|
11890
|
+
});
|
|
11876
11891
|
SwitchroomConfigSchema = exports_external.object({
|
|
11877
11892
|
switchroom: exports_external.object({
|
|
11878
11893
|
version: exports_external.literal(1).describe("Config schema version"),
|
|
@@ -11919,10 +11934,31 @@ var init_schema = __esm(() => {
|
|
|
11919
11934
|
})).optional().describe("RFC #1873: per-Microsoft-account ACL. Maps account email → list of " + "agents permitted to use that account's broker credentials. Written " + "by `switchroom auth microsoft enable|disable`; read by the broker " + "on get-credentials with provider=microsoft."),
|
|
11920
11935
|
defaults: AgentDefaultsSchema.describe("Implicit bottom-of-cascade profile applied to every agent before " + "per-agent config and `extends:` resolution. Tools, mcp_servers, and " + "schedule are unioned/concatenated; scalars and nested objects are " + "shallow-merged with per-agent values winning."),
|
|
11921
11936
|
profiles: exports_external.record(exports_external.string(), ProfileSchema).optional().describe("Named profile definitions. Agents reference via `extends: <name>`. " + "Inline profiles declared here take priority over filesystem " + "profiles/<name>/ directories when both exist."),
|
|
11937
|
+
users: exports_external.record(exports_external.string(), UserSchema).optional().describe("Trusted users the fleet serves — each a Telegram identity plus a " + "memory profile bank. Assigned to agents via `serves` / `knows`. The " + "operator's own trusted people (single-tenant), not multi-tenant. See " + "reference/rfcs/user-concept.md."),
|
|
11922
11938
|
agents: exports_external.record(exports_external.string().regex(/^[a-z0-9][a-z0-9_-]{0,50}$/, {
|
|
11923
11939
|
message: "Agent name must start with a letter/digit, contain only lowercase letters/digits/hyphens/underscores, and be at most 51 characters (Telegram callback_data byte limit)"
|
|
11924
11940
|
}), AgentSchema).describe("Map of agent name to agent configuration"),
|
|
11925
11941
|
cron: CronConfigSchema.optional().describe("Cheap-cron settings (reference/rfcs/cheap-cron-sessions.md). Operator-owned " + "egress allowlist + host-pinned secret bindings for Tier-0 http-diff " + "polls (§6.1). Required to enable any http-diff poll; not agent-writable.")
|
|
11942
|
+
}).superRefine((cfg, ctx) => {
|
|
11943
|
+
const userKeys = new Set(Object.keys(cfg.users ?? {}));
|
|
11944
|
+
const checkServes = (serves, path) => {
|
|
11945
|
+
(serves ?? []).forEach((s, i) => {
|
|
11946
|
+
if (!userKeys.has(s)) {
|
|
11947
|
+
ctx.addIssue({
|
|
11948
|
+
code: exports_external.ZodIssueCode.custom,
|
|
11949
|
+
message: `serves references unknown user "${s}" — add it to the top-level ` + "`users:` block (or did you mean `knows` for a raw bank name?)",
|
|
11950
|
+
path: [...path, i]
|
|
11951
|
+
});
|
|
11952
|
+
}
|
|
11953
|
+
});
|
|
11954
|
+
};
|
|
11955
|
+
checkServes(cfg.defaults?.serves, ["defaults", "serves"]);
|
|
11956
|
+
for (const [name, p] of Object.entries(cfg.profiles ?? {})) {
|
|
11957
|
+
checkServes(p.serves, ["profiles", name, "serves"]);
|
|
11958
|
+
}
|
|
11959
|
+
for (const [name, a] of Object.entries(cfg.agents ?? {})) {
|
|
11960
|
+
checkServes(a.serves, ["agents", name, "serves"]);
|
|
11961
|
+
}
|
|
11926
11962
|
});
|
|
11927
11963
|
});
|
|
11928
11964
|
|