switchroom 0.15.43 → 0.15.45
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 +239 -38
- package/dist/cli/ui/index.html +1 -1
- package/dist/host-control/main.js +37 -1
- package/dist/vault/approvals/kernel-server.js +41 -2
- package/dist/vault/broker/server.js +41 -2
- package/package.json +1 -1
- package/profiles/_base/start.sh.hbs +6 -0
- package/profiles/default/CLAUDE.md.hbs +3 -3
- 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
|
|
|
@@ -14980,6 +15016,40 @@ var init_loader = __esm(() => {
|
|
|
14980
15016
|
};
|
|
14981
15017
|
});
|
|
14982
15018
|
|
|
15019
|
+
// src/config/users.ts
|
|
15020
|
+
function resolveUsers(config, agentName) {
|
|
15021
|
+
const users = config.users ?? {};
|
|
15022
|
+
const agentRaw = config.agents?.[agentName];
|
|
15023
|
+
const agentConfig = agentRaw ? resolveAgentConfig(config.defaults, config.profiles, agentRaw) : undefined;
|
|
15024
|
+
const uniq = (xs) => [...new Set(xs)];
|
|
15025
|
+
const serves = uniq([
|
|
15026
|
+
...config.defaults?.serves ?? [],
|
|
15027
|
+
...agentConfig?.serves ?? []
|
|
15028
|
+
]);
|
|
15029
|
+
const knows = uniq([
|
|
15030
|
+
...config.defaults?.knows ?? [],
|
|
15031
|
+
...agentConfig?.knows ?? []
|
|
15032
|
+
]);
|
|
15033
|
+
const senderBanks = {};
|
|
15034
|
+
for (const userName of serves) {
|
|
15035
|
+
const u = users[userName];
|
|
15036
|
+
if (!u)
|
|
15037
|
+
continue;
|
|
15038
|
+
for (const id of u.telegram_ids) {
|
|
15039
|
+
senderBanks[id] = u.profile_bank;
|
|
15040
|
+
}
|
|
15041
|
+
}
|
|
15042
|
+
Object.assign(senderBanks, agentConfig?.memory?.recall?.sender_banks ?? {});
|
|
15043
|
+
const additionalBanks = uniq([
|
|
15044
|
+
...knows.map((entry) => users[entry]?.profile_bank ?? entry),
|
|
15045
|
+
...agentConfig?.memory?.recall?.additional_banks ?? []
|
|
15046
|
+
]);
|
|
15047
|
+
return { senderBanks, additionalBanks };
|
|
15048
|
+
}
|
|
15049
|
+
var init_users = __esm(() => {
|
|
15050
|
+
init_merge();
|
|
15051
|
+
});
|
|
15052
|
+
|
|
14983
15053
|
// src/memory/hindsight.ts
|
|
14984
15054
|
function generateHindsightMcpConfig(collection, memoryConfig) {
|
|
14985
15055
|
const url = memoryConfig.config?.url ?? "http://localhost:8888/mcp/";
|
|
@@ -14995,6 +15065,27 @@ function getCollectionForAgent(agentName, config) {
|
|
|
14995
15065
|
const agentConfig = config.agents[agentName];
|
|
14996
15066
|
return agentConfig?.memory?.collection ?? agentName;
|
|
14997
15067
|
}
|
|
15068
|
+
function collectProfileBanks(config, opts = {}) {
|
|
15069
|
+
const { excludeAgentCollections = true } = opts;
|
|
15070
|
+
const banks = new Set;
|
|
15071
|
+
for (const user of Object.values(config.users ?? {})) {
|
|
15072
|
+
if (user.profile_bank)
|
|
15073
|
+
banks.add(user.profile_bank);
|
|
15074
|
+
}
|
|
15075
|
+
for (const agentName of Object.keys(config.agents)) {
|
|
15076
|
+
const { senderBanks, additionalBanks } = resolveUsers(config, agentName);
|
|
15077
|
+
for (const b of Object.values(senderBanks))
|
|
15078
|
+
banks.add(b);
|
|
15079
|
+
for (const b of additionalBanks)
|
|
15080
|
+
banks.add(b);
|
|
15081
|
+
}
|
|
15082
|
+
if (excludeAgentCollections) {
|
|
15083
|
+
for (const agentName of Object.keys(config.agents)) {
|
|
15084
|
+
banks.delete(getCollectionForAgent(agentName, config));
|
|
15085
|
+
}
|
|
15086
|
+
}
|
|
15087
|
+
return banks;
|
|
15088
|
+
}
|
|
14998
15089
|
function isStrictIsolation(agentName, config) {
|
|
14999
15090
|
const agentConfig = config.agents[agentName];
|
|
15000
15091
|
return agentConfig?.memory?.isolation === "strict";
|
|
@@ -15424,6 +15515,7 @@ async function addMemoryTag(apiUrl, bankId, memoryId, tag, opts) {
|
|
|
15424
15515
|
}
|
|
15425
15516
|
var DEFAULT_RETAIN_MISSION, DEMOTE_FROM_RECALL_TAG = "[demote-from-recall]";
|
|
15426
15517
|
var init_hindsight = __esm(() => {
|
|
15518
|
+
init_users();
|
|
15427
15519
|
DEFAULT_RETAIN_MISSION = "Extract user preferences, ongoing projects, recurring commitments, " + "important context, and durable facts that should help across future " + "conversations. Skip one-off chatter and temporary task noise.";
|
|
15428
15520
|
});
|
|
15429
15521
|
|
|
@@ -32928,9 +33020,14 @@ async function checkBankIngestHealth(config, url, opts) {
|
|
|
32928
33020
|
const bankId = agentConfig.memory?.collection ?? agentName;
|
|
32929
33021
|
banks.set(bankId, [...banks.get(bankId) ?? [], agentName]);
|
|
32930
33022
|
}
|
|
33023
|
+
const profileBanks = collectProfileBanks(config);
|
|
33024
|
+
for (const bank of profileBanks) {
|
|
33025
|
+
if (!banks.has(bank))
|
|
33026
|
+
banks.set(bank, []);
|
|
33027
|
+
}
|
|
32931
33028
|
const inspected = await Promise.all([...banks].map(async ([bankId, agents]) => [bankId, agents, await inspectBankHealth(url, bankId, { fetchImpl: opts?.fetchImpl })]));
|
|
32932
33029
|
for (const [bankId, agents, h] of inspected) {
|
|
32933
|
-
const label = `bank ${bankId}` + (agents[0] !== bankId ? ` (${agents.join(", ")})` : "");
|
|
33030
|
+
const label = `bank ${bankId}` + (profileBanks.has(bankId) ? " (profile)" : agents[0] !== bankId ? ` (${agents.join(", ")})` : "");
|
|
32934
33031
|
if (!h.ok) {
|
|
32935
33032
|
results.push({
|
|
32936
33033
|
name: label,
|
|
@@ -50767,8 +50864,8 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
50767
50864
|
import { dirname, join } from "node:path";
|
|
50768
50865
|
|
|
50769
50866
|
// src/build-info.ts
|
|
50770
|
-
var VERSION = "0.15.
|
|
50771
|
-
var COMMIT_SHA = "
|
|
50867
|
+
var VERSION = "0.15.45";
|
|
50868
|
+
var COMMIT_SHA = "41082e8b";
|
|
50772
50869
|
|
|
50773
50870
|
// src/cli/resolve-version.ts
|
|
50774
50871
|
function readPackageVersion() {
|
|
@@ -50899,6 +50996,7 @@ var LEGACY_CRON_SCRIPT_BASENAME_RE = /^cron-(\d+)\.sh$/;
|
|
|
50899
50996
|
init_schema();
|
|
50900
50997
|
init_cron_routing();
|
|
50901
50998
|
init_tier_selector();
|
|
50999
|
+
init_users();
|
|
50902
51000
|
init_merge();
|
|
50903
51001
|
init_timezone();
|
|
50904
51002
|
|
|
@@ -52920,13 +53018,14 @@ function installHindsightPlugin(agentName, agentDir, switchroomConfig) {
|
|
|
52920
53018
|
rmSync3(destPath, { recursive: true, force: true });
|
|
52921
53019
|
}
|
|
52922
53020
|
copyDirRecursive2(sourcePath, destPath);
|
|
52923
|
-
|
|
53021
|
+
const additionalBanks = resolveUsers(switchroomConfig, agentName).additionalBanks;
|
|
53022
|
+
applyHindsightSettingsOverrides(destPath, additionalBanks);
|
|
52924
53023
|
const bankId = agentMemory?.collection ?? agentName;
|
|
52925
53024
|
const mcpUrl = memory.config?.url ?? "http://127.0.0.1:8888/mcp/";
|
|
52926
53025
|
const apiBaseUrl = mcpUrl.replace(/\/mcp\/?$/, "").replace(/\/$/, "");
|
|
52927
53026
|
return { pluginDir: destPath, apiBaseUrl, bankId };
|
|
52928
53027
|
}
|
|
52929
|
-
function applyHindsightSettingsOverrides(pluginDestPath) {
|
|
53028
|
+
function applyHindsightSettingsOverrides(pluginDestPath, additionalBanks) {
|
|
52930
53029
|
const settingsPath = join10(pluginDestPath, "settings.json");
|
|
52931
53030
|
if (!existsSync15(settingsPath))
|
|
52932
53031
|
return;
|
|
@@ -52947,6 +53046,9 @@ function applyHindsightSettingsOverrides(pluginDestPath) {
|
|
|
52947
53046
|
settings.recallMinOverlap = 0.1;
|
|
52948
53047
|
settings.recallTypes = ["world", "experience", "observation"];
|
|
52949
53048
|
settings.recallSkipTrivial = true;
|
|
53049
|
+
if (additionalBanks.length > 0) {
|
|
53050
|
+
settings.recallAdditionalBanks = [...additionalBanks];
|
|
53051
|
+
}
|
|
52950
53052
|
writeFileSync5(settingsPath, JSON.stringify(settings, null, 2) + `
|
|
52951
53053
|
`, "utf-8");
|
|
52952
53054
|
}
|
|
@@ -52972,6 +53074,7 @@ function buildWorkspaceContext(args) {
|
|
|
52972
53074
|
hindsightRecallTypes,
|
|
52973
53075
|
hindsightRecallSkipTrivial,
|
|
52974
53076
|
hindsightTopicAliasesJson,
|
|
53077
|
+
hindsightSenderBanksJson,
|
|
52975
53078
|
hindsightTopicFilterMode
|
|
52976
53079
|
} = args;
|
|
52977
53080
|
return {
|
|
@@ -53015,6 +53118,7 @@ function buildWorkspaceContext(args) {
|
|
|
53015
53118
|
hindsightRecallTypes,
|
|
53016
53119
|
hindsightRecallSkipTrivial,
|
|
53017
53120
|
hindsightTopicAliasesJsonQ: hindsightTopicAliasesJson ? shellSingleQuote(hindsightTopicAliasesJson) : undefined,
|
|
53121
|
+
hindsightSenderBanksJsonQ: hindsightSenderBanksJson ? shellSingleQuote(hindsightSenderBanksJson) : undefined,
|
|
53018
53122
|
hindsightTopicFilterMode,
|
|
53019
53123
|
switchroomConfigPathQ: switchroomConfigPath ? shellSingleQuote(resolve11(switchroomConfigPath)) : undefined,
|
|
53020
53124
|
hostHomeQ: hostHomeQForBake(),
|
|
@@ -53224,6 +53328,8 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
|
|
|
53224
53328
|
const hindsightRecallSkipTrivial = agentConfig.memory?.recall?.skip_trivial === undefined ? undefined : String(agentConfig.memory.recall.skip_trivial);
|
|
53225
53329
|
const topicAliases = agentConfig.channels?.telegram?.topic_aliases;
|
|
53226
53330
|
const hindsightTopicAliasesJson = topicAliases && Object.keys(topicAliases).length > 0 ? JSON.stringify(topicAliases) : undefined;
|
|
53331
|
+
const senderBanks = switchroomConfig ? resolveUsers(switchroomConfig, name).senderBanks : agentConfig.memory?.recall?.sender_banks ?? {};
|
|
53332
|
+
const hindsightSenderBanksJson = senderBanks && Object.keys(senderBanks).length > 0 ? JSON.stringify(senderBanks) : undefined;
|
|
53227
53333
|
const hindsightTopicFilterMode = agentConfig.memory?.recall?.topic_filter_mode;
|
|
53228
53334
|
const context = buildWorkspaceContext({
|
|
53229
53335
|
name,
|
|
@@ -53247,6 +53353,7 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
|
|
|
53247
53353
|
hindsightRecallTypes,
|
|
53248
53354
|
hindsightRecallSkipTrivial,
|
|
53249
53355
|
hindsightTopicAliasesJson,
|
|
53356
|
+
hindsightSenderBanksJson,
|
|
53250
53357
|
hindsightTopicFilterMode
|
|
53251
53358
|
});
|
|
53252
53359
|
const dirs = [
|
|
@@ -53659,15 +53766,6 @@ ${body}
|
|
|
53659
53766
|
}).catch((err) => {
|
|
53660
53767
|
console.warn(` ${source_default.yellow("\u26a0")} Bank mission update error for ${formatAgentBankLabel(name, hindsightBankId)}: ${err}`);
|
|
53661
53768
|
});
|
|
53662
|
-
ensureUserProfileMentalModel(apiUrl, hindsightBankId, { timeoutMs: 5000 }).then((result) => {
|
|
53663
|
-
if (result.ok) {
|
|
53664
|
-
console.log(` ${source_default.green("\u2713")} User-profile Mental Model ready for ${formatAgentBankLabel(name, hindsightBankId)}`);
|
|
53665
|
-
} else {
|
|
53666
|
-
console.warn(` ${source_default.yellow("\u26a0")} Failed to create user-profile MM for ${formatAgentBankLabel(name, hindsightBankId)}: ${result.reason}`);
|
|
53667
|
-
}
|
|
53668
|
-
}).catch((err) => {
|
|
53669
|
-
console.warn(` ${source_default.yellow("\u26a0")} User-profile MM error for ${formatAgentBankLabel(name, hindsightBankId)}: ${err}`);
|
|
53670
|
-
});
|
|
53671
53769
|
});
|
|
53672
53770
|
}
|
|
53673
53771
|
for (const f of rewrittenWithBackup) {
|
|
@@ -53723,14 +53821,6 @@ function buildSettingsHooksBlock(p) {
|
|
|
53723
53821
|
async: true
|
|
53724
53822
|
});
|
|
53725
53823
|
}
|
|
53726
|
-
if (hindsightEnabled) {
|
|
53727
|
-
stopHooks.push({
|
|
53728
|
-
type: "command",
|
|
53729
|
-
command: wrap("hook:user-profile-refresh", `bash "${join10(DOCKER_BIN_PATH, "user-profile-refresh-hook.sh")}"`),
|
|
53730
|
-
timeout: 10,
|
|
53731
|
-
async: true
|
|
53732
|
-
});
|
|
53733
|
-
}
|
|
53734
53824
|
if (useSwitchroomPlugin) {
|
|
53735
53825
|
stopHooks.push({
|
|
53736
53826
|
type: "command",
|
|
@@ -54081,6 +54171,8 @@ function reconcileAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchr
|
|
|
54081
54171
|
const hindsightRecallSkipTrivial = agentConfig.memory?.recall?.skip_trivial === undefined ? undefined : String(agentConfig.memory.recall.skip_trivial);
|
|
54082
54172
|
const topicAliases = agentConfig.channels?.telegram?.topic_aliases;
|
|
54083
54173
|
const hindsightTopicAliasesJson = topicAliases && Object.keys(topicAliases).length > 0 ? JSON.stringify(topicAliases) : undefined;
|
|
54174
|
+
const senderBanks = switchroomConfig ? resolveUsers(switchroomConfig, name).senderBanks : agentConfig.memory?.recall?.sender_banks ?? {};
|
|
54175
|
+
const hindsightSenderBanksJson = senderBanks && Object.keys(senderBanks).length > 0 ? JSON.stringify(senderBanks) : undefined;
|
|
54084
54176
|
const hindsightTopicFilterMode = agentConfig.memory?.recall?.topic_filter_mode;
|
|
54085
54177
|
if (agentConfig.repos) {
|
|
54086
54178
|
for (const [slug, entry] of Object.entries(agentConfig.repos)) {
|
|
@@ -54117,6 +54209,7 @@ function reconcileAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchr
|
|
|
54117
54209
|
hindsightRecallTypes,
|
|
54118
54210
|
hindsightRecallSkipTrivial,
|
|
54119
54211
|
hindsightTopicAliasesJsonQ: hindsightTopicAliasesJson ? shellSingleQuote(hindsightTopicAliasesJson) : undefined,
|
|
54212
|
+
hindsightSenderBanksJsonQ: hindsightSenderBanksJson ? shellSingleQuote(hindsightSenderBanksJson) : undefined,
|
|
54120
54213
|
hindsightTopicFilterMode,
|
|
54121
54214
|
hostHomeQ: hostHomeQForBake(),
|
|
54122
54215
|
modelQ: shellSingleQuote(resolveMainModel(agentConfig.model)),
|
|
@@ -54506,6 +54599,7 @@ ${body}
|
|
|
54506
54599
|
hindsightRecallTypes,
|
|
54507
54600
|
hindsightRecallSkipTrivial,
|
|
54508
54601
|
hindsightTopicAliasesJson,
|
|
54602
|
+
hindsightSenderBanksJson,
|
|
54509
54603
|
hindsightTopicFilterMode
|
|
54510
54604
|
});
|
|
54511
54605
|
mkdirSync10(reconcileWorkspaceDir, { recursive: true });
|
|
@@ -54599,15 +54693,6 @@ ${body}
|
|
|
54599
54693
|
console.warn(` ${source_default.yellow("\u26a0")} Bank mission update error for ${formatAgentBankLabel(name, hindsightBankId)}: ${err}`);
|
|
54600
54694
|
});
|
|
54601
54695
|
}
|
|
54602
|
-
ensureUserProfileMentalModel(apiUrl, hindsightBankId, { timeoutMs: 5000 }).then((result) => {
|
|
54603
|
-
if (result.ok) {
|
|
54604
|
-
console.log(` ${source_default.green("\u2713")} User-profile Mental Model ready for ${formatAgentBankLabel(name, hindsightBankId)}`);
|
|
54605
|
-
} else {
|
|
54606
|
-
console.warn(` ${source_default.yellow("\u26a0")} Failed to create user-profile MM for ${formatAgentBankLabel(name, hindsightBankId)}: ${result.reason}`);
|
|
54607
|
-
}
|
|
54608
|
-
}).catch((err) => {
|
|
54609
|
-
console.warn(` ${source_default.yellow("\u26a0")} User-profile MM error for ${formatAgentBankLabel(name, hindsightBankId)}: ${err}`);
|
|
54610
|
-
});
|
|
54611
54696
|
});
|
|
54612
54697
|
}
|
|
54613
54698
|
return {
|
|
@@ -68895,8 +68980,10 @@ var HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8;
|
|
|
68895
68980
|
var HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600;
|
|
68896
68981
|
var HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_BATCH_SIZE = 12;
|
|
68897
68982
|
var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS = 3;
|
|
68898
|
-
var
|
|
68899
|
-
var
|
|
68983
|
+
var HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_PARALLELISM = 6;
|
|
68984
|
+
var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 300;
|
|
68985
|
+
var HINDSIGHT_DEFAULT_MEM_LIMIT = "8g";
|
|
68986
|
+
var HINDSIGHT_DEFAULT_MEM_RESERVATION = "4g";
|
|
68900
68987
|
var HINDSIGHT_DEFAULT_PIDS_LIMIT = 1000;
|
|
68901
68988
|
var HINDSIGHT_DEFAULT_SHM_SIZE = "2g";
|
|
68902
68989
|
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)';
|
|
@@ -68985,7 +69072,11 @@ function startHindsight(ports) {
|
|
|
68985
69072
|
"-e",
|
|
68986
69073
|
`HINDSIGHT_API_CONSOLIDATION_LLM_BATCH_SIZE=${HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_BATCH_SIZE}`,
|
|
68987
69074
|
"-e",
|
|
68988
|
-
`HINDSIGHT_API_WORKER_CONSOLIDATION_MAX_SLOTS=${HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS}
|
|
69075
|
+
`HINDSIGHT_API_WORKER_CONSOLIDATION_MAX_SLOTS=${HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS}`,
|
|
69076
|
+
"-e",
|
|
69077
|
+
`HINDSIGHT_API_CONSOLIDATION_LLM_PARALLELISM=${HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_PARALLELISM}`,
|
|
69078
|
+
"-e",
|
|
69079
|
+
`HINDSIGHT_API_CONSOLIDATION_MAX_MEMORIES_PER_ROUND=${HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND}`
|
|
68989
69080
|
];
|
|
68990
69081
|
const args = [
|
|
68991
69082
|
"run",
|
|
@@ -69086,6 +69177,8 @@ function generateHindsightComposeSnippet() {
|
|
|
69086
69177
|
` - HINDSIGHT_API_REFLECT_WALL_TIMEOUT=${HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S}`,
|
|
69087
69178
|
` - HINDSIGHT_API_CONSOLIDATION_LLM_BATCH_SIZE=${HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_BATCH_SIZE}`,
|
|
69088
69179
|
` - HINDSIGHT_API_WORKER_CONSOLIDATION_MAX_SLOTS=${HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS}`,
|
|
69180
|
+
` - HINDSIGHT_API_CONSOLIDATION_LLM_PARALLELISM=${HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_PARALLELISM}`,
|
|
69181
|
+
` - HINDSIGHT_API_CONSOLIDATION_MAX_MEMORIES_PER_ROUND=${HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND}`,
|
|
69089
69182
|
` mem_limit: ${HINDSIGHT_DEFAULT_MEM_LIMIT}`,
|
|
69090
69183
|
` mem_reservation: ${HINDSIGHT_DEFAULT_MEM_RESERVATION}`,
|
|
69091
69184
|
` pids_limit: ${HINDSIGHT_DEFAULT_PIDS_LIMIT}`,
|
|
@@ -69249,6 +69342,26 @@ Searching all eligible collections:
|
|
|
69249
69342
|
console.log(` ${row}`);
|
|
69250
69343
|
}
|
|
69251
69344
|
console.log();
|
|
69345
|
+
const profileBanks = collectProfileBanks(config);
|
|
69346
|
+
if (profileBanks.size > 0) {
|
|
69347
|
+
const owners = new Map;
|
|
69348
|
+
for (const [uname, u] of Object.entries(config.users ?? {})) {
|
|
69349
|
+
if (u.profile_bank) {
|
|
69350
|
+
owners.set(u.profile_bank, [
|
|
69351
|
+
...owners.get(u.profile_bank) ?? [],
|
|
69352
|
+
uname
|
|
69353
|
+
]);
|
|
69354
|
+
}
|
|
69355
|
+
}
|
|
69356
|
+
console.log(source_default.bold(` Profile banks (per-user / shared):
|
|
69357
|
+
`));
|
|
69358
|
+
for (const bank of [...profileBanks].sort()) {
|
|
69359
|
+
const who = owners.get(bank);
|
|
69360
|
+
const label = who ? `user: ${who.join(", ")}` : "shared";
|
|
69361
|
+
console.log(` ${bank.padEnd(widths[1])} ${source_default.gray(label)}`);
|
|
69362
|
+
}
|
|
69363
|
+
console.log();
|
|
69364
|
+
}
|
|
69252
69365
|
console.log(source_default.bold(` Hindsight CLI commands:
|
|
69253
69366
|
`));
|
|
69254
69367
|
for (const name of agentNames) {
|
|
@@ -69487,6 +69600,88 @@ Demoting memory ${source_default.cyan(memoryId)}`));
|
|
|
69487
69600
|
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."));
|
|
69488
69601
|
process.exit(1);
|
|
69489
69602
|
}));
|
|
69603
|
+
const restBase = (url) => (url ?? "http://127.0.0.1:8888/mcp/").replace(/\/mcp\/?$/, "").replace(/\/$/, "");
|
|
69604
|
+
const VALID_BANK = /^[a-zA-Z0-9_.-]+$/;
|
|
69605
|
+
const profile = memory.command("profile").description("Author + inspect operator profile banks (shared/per-user memory; wire via memory.recall.additional_banks)");
|
|
69606
|
+
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) => {
|
|
69607
|
+
const config = getConfig(program3);
|
|
69608
|
+
if (!bank || !VALID_BANK.test(bank)) {
|
|
69609
|
+
console.error(source_default.red("Bank name must be non-empty and contain only letters, digits, '.', '_', or '-'."));
|
|
69610
|
+
process.exit(1);
|
|
69611
|
+
}
|
|
69612
|
+
const content = factWords.join(" ").trim();
|
|
69613
|
+
if (content.length === 0) {
|
|
69614
|
+
console.error(source_default.red("A non-empty fact is required."));
|
|
69615
|
+
process.exit(1);
|
|
69616
|
+
}
|
|
69617
|
+
const base = restBase(config.memory?.config?.url);
|
|
69618
|
+
const url = `${base}/v1/default/banks/${encodeURIComponent(bank)}/memories`;
|
|
69619
|
+
const timeoutMs = Math.max(1000, parseInt(opts.timeout, 10) || 60000);
|
|
69620
|
+
const ctrl = new AbortController;
|
|
69621
|
+
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
69622
|
+
try {
|
|
69623
|
+
const res = await fetch(url, {
|
|
69624
|
+
method: "POST",
|
|
69625
|
+
headers: { "Content-Type": "application/json" },
|
|
69626
|
+
body: JSON.stringify({
|
|
69627
|
+
items: [{ content, tags: ["operator-authored", "profile"] }],
|
|
69628
|
+
async: false
|
|
69629
|
+
}),
|
|
69630
|
+
signal: ctrl.signal
|
|
69631
|
+
});
|
|
69632
|
+
if (!res.ok) {
|
|
69633
|
+
console.error(source_default.red(`\u2717 Retain failed: HTTP ${res.status}`), source_default.gray(await res.text().catch(() => "")));
|
|
69634
|
+
process.exit(1);
|
|
69635
|
+
}
|
|
69636
|
+
console.log(source_default.green(`\u2713 Added to profile bank "${bank}"`));
|
|
69637
|
+
console.log(source_default.gray(` ${content}`));
|
|
69638
|
+
console.log(source_default.gray(" (fact extraction runs in the background \u2014 `profile list` may lag a few seconds)"));
|
|
69639
|
+
console.log(source_default.gray(`
|
|
69640
|
+
Wire it into agents via memory.recall.additional_banks: ["${bank}"] in switchroom.yaml,
|
|
69641
|
+
then \`switchroom apply\` + restart the agent(s).`));
|
|
69642
|
+
} catch (e) {
|
|
69643
|
+
console.error(source_default.red("\u2717 Retain failed:"), source_default.gray(e instanceof Error ? e.message : String(e)));
|
|
69644
|
+
process.exit(1);
|
|
69645
|
+
} finally {
|
|
69646
|
+
clearTimeout(t);
|
|
69647
|
+
}
|
|
69648
|
+
}));
|
|
69649
|
+
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) => {
|
|
69650
|
+
const config = getConfig(program3);
|
|
69651
|
+
if (!bank || !VALID_BANK.test(bank)) {
|
|
69652
|
+
console.error(source_default.red("Bank name must contain only letters, digits, '.', '_', or '-'."));
|
|
69653
|
+
process.exit(1);
|
|
69654
|
+
}
|
|
69655
|
+
const base = restBase(config.memory?.config?.url);
|
|
69656
|
+
const limit = Math.max(1, parseInt(opts.limit, 10) || 50);
|
|
69657
|
+
const url = `${base}/v1/default/banks/${encodeURIComponent(bank)}/memories/list?limit=${limit}`;
|
|
69658
|
+
const timeoutMs = Math.max(1000, parseInt(opts.timeout, 10) || 1e4);
|
|
69659
|
+
const ctrl = new AbortController;
|
|
69660
|
+
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
69661
|
+
try {
|
|
69662
|
+
const res = await fetch(url, { signal: ctrl.signal });
|
|
69663
|
+
if (!res.ok) {
|
|
69664
|
+
console.error(source_default.red(`\u2717 List failed: HTTP ${res.status}`), source_default.gray(await res.text().catch(() => "")));
|
|
69665
|
+
process.exit(1);
|
|
69666
|
+
}
|
|
69667
|
+
const data = await res.json();
|
|
69668
|
+
const units = data.results ?? data.memories ?? data.units ?? [];
|
|
69669
|
+
console.log(source_default.bold(`
|
|
69670
|
+
Profile bank "${bank}" \u2014 ${units.length} unit(s)
|
|
69671
|
+
`));
|
|
69672
|
+
for (const u of units) {
|
|
69673
|
+
const ft = (u.fact_type ?? "?").padEnd(11);
|
|
69674
|
+
const text = String(u.content ?? u.text ?? "").replace(/\s+/g, " ").slice(0, 100);
|
|
69675
|
+
console.log(` ${source_default.gray(ft)} ${text}`);
|
|
69676
|
+
}
|
|
69677
|
+
console.log();
|
|
69678
|
+
} catch (e) {
|
|
69679
|
+
console.error(source_default.red("\u2717 List failed:"), source_default.gray(e instanceof Error ? e.message : String(e)));
|
|
69680
|
+
process.exit(1);
|
|
69681
|
+
} finally {
|
|
69682
|
+
clearTimeout(t);
|
|
69683
|
+
}
|
|
69684
|
+
}));
|
|
69490
69685
|
}
|
|
69491
69686
|
|
|
69492
69687
|
// src/cli/web.ts
|
|
@@ -75529,6 +75724,11 @@ async function handleGetMemoryHealth(config, opts) {
|
|
|
75529
75724
|
const bank = getCollectionForAgent(agentName, config);
|
|
75530
75725
|
banks.set(bank, [...banks.get(bank) ?? [], agentName]);
|
|
75531
75726
|
}
|
|
75727
|
+
const profileBankSet = collectProfileBanks(config);
|
|
75728
|
+
for (const bank of profileBankSet) {
|
|
75729
|
+
if (!banks.has(bank))
|
|
75730
|
+
banks.set(bank, []);
|
|
75731
|
+
}
|
|
75532
75732
|
const rows = await Promise.all([...banks].map(async ([bank, agents]) => {
|
|
75533
75733
|
const h = await inspectBankHealth(url, bank, { fetchImpl: opts?.fetchImpl });
|
|
75534
75734
|
const gaps = recentUnextracted(h.unextractedDocuments, 30, now);
|
|
@@ -75573,7 +75773,8 @@ async function handleGetMemoryHealth(config, opts) {
|
|
|
75573
75773
|
staleMentalModelCount: stale.length,
|
|
75574
75774
|
corruptedMentalModelNames: corrupted.map((m) => m.name),
|
|
75575
75775
|
status,
|
|
75576
|
-
statusDetail
|
|
75776
|
+
statusDetail,
|
|
75777
|
+
kind: profileBankSet.has(bank) ? "profile" : "agent"
|
|
75577
75778
|
};
|
|
75578
75779
|
}));
|
|
75579
75780
|
rows.sort((a, b) => a.bank.localeCompare(b.bank));
|
|
@@ -75587,7 +75788,7 @@ function isKnownBank(config, bank) {
|
|
|
75587
75788
|
if (getCollectionForAgent(agentName, config) === bank)
|
|
75588
75789
|
return true;
|
|
75589
75790
|
}
|
|
75590
|
-
return
|
|
75791
|
+
return collectProfileBanks(config).has(bank);
|
|
75591
75792
|
}
|
|
75592
75793
|
async function handleMemoryReprocess(config, body, deps) {
|
|
75593
75794
|
const bank = typeof body.bank === "string" ? body.bank : "";
|
package/dist/cli/ui/index.html
CHANGED
|
@@ -898,7 +898,7 @@
|
|
|
898
898
|
return `<div class="agent-card">
|
|
899
899
|
<div class="card-header" style="cursor:default">
|
|
900
900
|
${statusDot(b.status)}<span class="agent-name">${escapeHtml(b.bank)}</span>
|
|
901
|
-
<span style="color:var(--text-dim);font-size:.85em;margin-left:.5rem">${escapeHtml((b.agents || []).join(', '))}</span>
|
|
901
|
+
<span style="color:var(--text-dim);font-size:.85em;margin-left:.5rem">${b.kind === 'profile' ? '<em>profile bank</em>' : escapeHtml((b.agents || []).join(', '))}</span>
|
|
902
902
|
</div>
|
|
903
903
|
<div style="padding:0 1.25rem 1rem">
|
|
904
904
|
<div style="color:var(--text-dim);margin:.1rem 0 .3rem">${escapeHtml(b.statusDetail || '')}</div>
|
|
@@ -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
|