switchroom 0.19.17 → 0.19.19

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.
Files changed (91) hide show
  1. package/bin/run-hook.sh +148 -0
  2. package/bin/workspace-dynamic-hook.sh +147 -38
  3. package/dist/agent-scheduler/index.js +13 -4
  4. package/dist/auth-broker/index.js +32 -5
  5. package/dist/cli/drive-write-pretool.mjs +48 -5
  6. package/dist/cli/ms-365-write-pretool.mjs +40 -2
  7. package/dist/cli/notion-write-pretool.mjs +13 -4
  8. package/dist/cli/switchroom.js +10614 -8104
  9. package/dist/host-control/main.js +12849 -11446
  10. package/dist/vault/approvals/kernel-server.js +90 -12
  11. package/dist/vault/broker/server.js +277 -94
  12. package/package.json +5 -3
  13. package/profiles/_base/start.sh.hbs +69 -5
  14. package/profiles/coding/CLAUDE.md.hbs +1 -1
  15. package/profiles/default/CLAUDE.md.hbs +3 -3
  16. package/profiles/executive-assistant/CLAUDE.md.hbs +1 -1
  17. package/profiles/health-coach/CLAUDE.md.hbs +1 -1
  18. package/skills/mental-model-curator/SKILL.md +8 -6
  19. package/telegram-plugin/bridge/bridge.ts +25 -19
  20. package/telegram-plugin/bridge/mcp-instructions.ts +87 -0
  21. package/telegram-plugin/dist/bridge/bridge.js +28 -20
  22. package/telegram-plugin/dist/gateway/gateway.js +2077 -1087
  23. package/telegram-plugin/dist/server.js +32 -20
  24. package/telegram-plugin/gateway/always-allow-persist-queue.ts +97 -11
  25. package/telegram-plugin/gateway/boot-card.ts +5 -1
  26. package/telegram-plugin/gateway/boot-probes.ts +113 -0
  27. package/telegram-plugin/gateway/config-approval-handler.test.ts +54 -0
  28. package/telegram-plugin/gateway/config-approval-handler.ts +16 -1
  29. package/telegram-plugin/gateway/disconnect-flush.ts +17 -0
  30. package/telegram-plugin/gateway/gateway.ts +43 -1
  31. package/telegram-plugin/gateway/handback-preturn-signal.ts +61 -7
  32. package/telegram-plugin/gateway/ipc-protocol.ts +5 -0
  33. package/telegram-plugin/gateway/ipc-server.ts +13 -0
  34. package/telegram-plugin/gateway/liveness-wiring.ts +125 -5
  35. package/telegram-plugin/gateway/missed-approvals-store.ts +66 -17
  36. package/telegram-plugin/gateway/obligation-ledger.ts +84 -4
  37. package/telegram-plugin/gateway/pending-card-store.ts +46 -16
  38. package/telegram-plugin/gateway/resume-inbound-builder.ts +13 -4
  39. package/telegram-plugin/gateway/scoped-grant-store.ts +39 -14
  40. package/telegram-plugin/gateway/store-file.ts +244 -0
  41. package/telegram-plugin/gateway/stream-render.ts +24 -5
  42. package/telegram-plugin/hooks/secret-guard-pretool.mjs +249 -76
  43. package/telegram-plugin/hooks/tool-label-pretool.mjs +88 -2
  44. package/telegram-plugin/registry/turns-schema.test.ts +8 -3
  45. package/telegram-plugin/registry/turns-schema.ts +40 -12
  46. package/telegram-plugin/runtime-metrics.ts +14 -0
  47. package/telegram-plugin/silence-poke.ts +138 -0
  48. package/telegram-plugin/tests/boot-probe-drift.test.ts +152 -0
  49. package/telegram-plugin/tests/bridge-tool-parity.test.ts +95 -0
  50. package/telegram-plugin/tests/gateway-disconnect-flush.test.ts +32 -0
  51. package/telegram-plugin/tests/handback-preturn-signal.test.ts +62 -0
  52. package/telegram-plugin/tests/helpers/liveness-wiring-fixture.ts +178 -0
  53. package/telegram-plugin/tests/ipc-server-validate-config-approval.test.ts +95 -0
  54. package/telegram-plugin/tests/mcp-instructions-budget.test.ts +184 -0
  55. package/telegram-plugin/tests/multitopic-routing-wiring.test.ts +22 -2
  56. package/telegram-plugin/tests/obligation-determinism.test.ts +114 -3
  57. package/telegram-plugin/tests/obligation-ledger.test.ts +310 -0
  58. package/telegram-plugin/tests/registry-turns.test.ts +13 -0
  59. package/telegram-plugin/tests/resume-inbound-builder.test.ts +15 -0
  60. package/telegram-plugin/tests/secret-guard-pretool.test.ts +347 -16
  61. package/telegram-plugin/tests/silence-poke-orphan-reap.test.ts +392 -0
  62. package/telegram-plugin/tests/silence-poke-teardown-notice.test.ts +301 -0
  63. package/telegram-plugin/tests/store-atomic-write.test.ts +411 -0
  64. package/telegram-plugin/tests/stream-render-golden.test.ts +103 -1
  65. package/telegram-plugin/tests/tool-activity-summary.test.ts +9 -2
  66. package/telegram-plugin/tests/tool-label-pretool.test.ts +94 -0
  67. package/telegram-plugin/tests/tts-normalize.test.ts +43 -0
  68. package/telegram-plugin/tests/voice-normalize-text.test.ts +212 -3
  69. package/telegram-plugin/tests/worker-feed-repeat-steps.test.ts +147 -0
  70. package/telegram-plugin/tts-normalize.ts +6 -4
  71. package/telegram-plugin/voice-normalize-text.ts +168 -11
  72. package/telegram-plugin/worker-activity-feed.ts +51 -1
  73. package/vendor/hindsight-memory/CHANGELOG.md +73 -0
  74. package/vendor/hindsight-memory/scripts/drain_pending.py +668 -56
  75. package/vendor/hindsight-memory/scripts/lib/client.py +124 -0
  76. package/vendor/hindsight-memory/scripts/lib/config.py +8 -3
  77. package/vendor/hindsight-memory/scripts/lib/directives.py +62 -4
  78. package/vendor/hindsight-memory/scripts/lib/pending.py +865 -33
  79. package/vendor/hindsight-memory/scripts/lib/retain_split.py +449 -0
  80. package/vendor/hindsight-memory/scripts/recall.py +257 -12
  81. package/vendor/hindsight-memory/scripts/retain.py +12 -6
  82. package/vendor/hindsight-memory/scripts/session_start.py +48 -0
  83. package/vendor/hindsight-memory/scripts/tests/test_client_document_exists.py +470 -0
  84. package/vendor/hindsight-memory/scripts/tests/test_directives.py +80 -9
  85. package/vendor/hindsight-memory/scripts/tests/test_pending_drops.py +2121 -0
  86. package/vendor/hindsight-memory/scripts/tests/test_recall_integration.py +362 -18
  87. package/vendor/hindsight-memory/scripts/tests/test_retain_split.py +430 -0
  88. package/vendor/hindsight-memory/scripts/tests/test_session_start_version_skew.py +204 -0
  89. package/vendor/hindsight-memory/settings.json +1 -1
  90. package/vendor/hindsight-memory/tests/test_drain_pending.py +102 -6
  91. package/vendor/hindsight-memory/tests/test_pending.py +32 -7
@@ -4013,7 +4013,7 @@ var init_zod = __esm(() => {
4013
4013
  });
4014
4014
 
4015
4015
  // src/config/schema.ts
4016
- var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, MicrosoftAccountEmailSchema, MicrosoftToolTokenSchema, MicrosoftAccountBindingSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, ReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
4016
+ var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, MicrosoftAccountEmailSchema, MicrosoftToolTokenSchema, MicrosoftAccountBindingSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, releaseBlockFields, ReleaseBlock, RootReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
4017
4017
  var init_schema = __esm(() => {
4018
4018
  init_zod();
4019
4019
  CodeRepoEntrySchema = exports_external.object({
@@ -4164,7 +4164,7 @@ var init_schema = __esm(() => {
4164
4164
  recall: exports_external.object({
4165
4165
  max_memories: exports_external.number().int().min(0).optional().describe("Cap on the number of memories injected into the prompt by " + "auto-recall, regardless of token budget. Plugin default is 12. " + "0 disables the cap (all memories Hindsight returns are injected)."),
4166
4166
  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."),
4167
- 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`."),
4167
+ min_overlap: exports_external.number().min(0).max(1).optional().describe("Minimum containment token overlap [0.0–1.0] between the user " + "prompt and a memory's text for the memory to be injected. " + "A cheap FLOOR that removes candidates with (near-)zero lexical " + "relationship to the prompt — NOT a precision control: at the " + "recommended 0.10 the gate is near-passthrough, and the " + "effective precision control is the engine rerank plus the " + "max_memories head-slice. 0.0 disables (default — current " + "behaviour). Use 0.10; values at or above 0.20 measurably " + "starve recall — on production replay 0.20 leaves ~41.9% of " + "turns with NO memories at all, re-creating the bug #3541 " + "fixed. Observe the `overlap_dropped` field via " + "`switchroom memory recall-log`."),
4168
4168
  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)."),
4169
4169
  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)."),
4170
4170
  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`."),
@@ -4476,9 +4476,16 @@ var init_schema = __esm(() => {
4476
4476
  enabled: exports_external.boolean().optional().describe("Master switch for the reaction-dispatch path. Default false — " + "with no reaction_dispatch block, reactions are persisted (and may " + "feed the `reactions` feedback path) but are NEVER dispatched as " + "event-driven inbound turns."),
4477
4477
  emojis: exports_external.array(exports_external.string()).optional().describe('Emoji allowlist that triggers a `<channel event="reaction">` ' + "inbound turn when reacted to any message. Default [] (nothing " + "fires). Cascade mode: REPLACE (not union) — a layer's list " + "replaces lower layers entirely so an operator can narrow per-agent.")
4478
4478
  }).optional();
4479
- ReleaseBlock = exports_external.object({
4479
+ releaseBlockFields = {
4480
4480
  channel: exports_external.enum(["dev", "rc", "latest"]).optional(),
4481
4481
  pin: exports_external.string().regex(/^(sha-[0-9a-f]{7,40}|v\d+\.\d+\.\d+)$/).optional()
4482
+ };
4483
+ ReleaseBlock = exports_external.object(releaseBlockFields).strict().refine((r) => !(r.channel && r.pin), {
4484
+ message: "release.channel and release.pin are mutually exclusive"
4485
+ });
4486
+ RootReleaseBlock = exports_external.object({
4487
+ ...releaseBlockFields,
4488
+ auto_update: exports_external.boolean().optional().describe("Opt-in unattended fleet auto-update (KEN-131). When true, hostd's " + "release watcher polls the published release version and, on a new " + "release, drives the EXISTING staggered canary rollout " + "(`switchroom rollout --pin vX.Y.Z`) unattended: canary-first, " + "per-agent version assert, durable pin persisted only after the " + "canary is green, abort + operator alert card on canary failure, " + "and compose rollback on a failed apply. Default false — with it " + "unset/false, behaviour is unchanged.")
4482
4489
  }).strict().refine((r) => !(r.channel && r.pin), {
4483
4490
  message: "release.channel and release.pin are mutually exclusive"
4484
4491
  });
@@ -4722,6 +4729,7 @@ var init_schema = __esm(() => {
4722
4729
  enabled: exports_external.boolean().default(false).describe("When true, hostd polls the remote release tag every " + "`interval_minutes` and applies + restarts the fleet when a new " + "release is detected. Default false — opt-in."),
4723
4730
  interval_minutes: exports_external.number().int().min(5).max(1440).default(5).describe("Poll interval in minutes. Floor of 5m matches the agent-config " + "cron rate limit; ceiling of 1440m (24h) is a sanity cap."),
4724
4731
  apply_on_detect: exports_external.boolean().default(true).describe("When false, hostd logs `release_detected` but does NOT call " + "update_apply / restart all. Useful for dogfooding the detector " + "without rolling the fleet."),
4732
+ notify_on_detect: exports_external.boolean().default(false).describe("KEN-129 — operator-in-the-loop update prompt. Only consulted " + "when apply_on_detect is false (auto-apply supersedes notify): " + "a newly detected release posts ONE operator approval card " + "('fleet is behind — tap to apply') via an admin agent's " + "gateway; Approve runs hostd's update_apply path (fleet-" + "mutation-locked, durable status rows, get_status-pollable). " + "Dedup on release id: the last-notified id persists in " + "~/.switchroom/release-notify-state.json, so a card that " + "reached the operator is never re-posted for the same release."),
4725
4733
  image_ref: exports_external.string().default("ghcr.io/switchroom/switchroom-agent:latest").describe("Image reference whose remote digest is compared to the local " + "image digest. Defaults to the agent image's :latest tag, which " + "is the canonical signal that a release has been promoted.")
4726
4734
  });
4727
4735
  HostControlConfigSchema = exports_external.object({
@@ -4764,7 +4772,7 @@ var init_schema = __esm(() => {
4764
4772
  timezone: exports_external.string().regex(TIMEZONE_REGEX, "timezone must be an IANA zone name like 'Australia/Melbourne' or 'UTC'").optional().describe("Global default IANA timezone applied to every agent unless the " + "agent (or its profile) declares its own. See the per-agent " + "timezone field for the full cascade and auto-detection fallback.")
4765
4773
  }),
4766
4774
  telegram: TelegramConfigSchema,
4767
- release: ReleaseBlock.optional().describe("Fleet-wide default release-channel pin / pointer for the update " + "flow. Either `channel` (dev|rc|latest) or `pin` (sha-<hex>|v<semver>) " + "— mutually exclusive. Per-agent `release` REPLACES this entirely."),
4775
+ release: RootReleaseBlock.optional().describe("Fleet-wide default release-channel pin / pointer for the update " + "flow. Either `channel` (dev|rc|latest) or `pin` (sha-<hex>|v<semver>) " + "— mutually exclusive. Per-agent `release` REPLACES this entirely " + "(except `auto_update`, which is root-only — a fleet property read by " + "hostd's release watcher, never per-agent)."),
4768
4776
  memory: MemoryBackendConfigSchema.optional(),
4769
4777
  hindsight: HindsightConfigSchema.optional().describe("Fleet-singleton Hindsight (memory backend) configuration. Currently " + "just the LLM knob (provider + model) used for retain/reflect/" + "consolidation. Both fields optional; when unset the container falls " + "back to provider=claude-code + the hard-coded HINDSIGHT_DEFAULT_MODEL " + "so nothing changes for operators who don't set it. Read at container " + "launch by startHindsight() (src/setup/hindsight.ts) — takes effect on " + "the next `switchroom apply` / `memory setup --recreate`."),
4770
4778
  vault: VaultConfigSchema.optional(),
@@ -18538,7 +18546,8 @@ var ApprovalDecisionMetaSchema = exports_external.object({
18538
18546
  ttl_expires_at: exports_external.number().nullable(),
18539
18547
  last_used_at: exports_external.number().nullable(),
18540
18548
  revoked_at: exports_external.number().nullable(),
18541
- revoke_reason: exports_external.string().nullable()
18549
+ revoke_reason: exports_external.string().nullable(),
18550
+ origin: exports_external.enum(["agent", "operator"]).optional()
18542
18551
  });
18543
18552
  var OkApprovalLookupResponseSchema = exports_external.object({
18544
18553
  ok: exports_external.literal(true),
@@ -18702,6 +18711,7 @@ function migrateApprovalSchema(db) {
18702
18711
  var DEFAULT_MAX_TTL_LIFETIME_MS = 7 * 24 * 60 * 60 * 1000;
18703
18712
 
18704
18713
  // src/vault/approvals/kernel.ts
18714
+ var ALLOW_ONCE_CONSUMED_REASON = "allow_once_consumed";
18705
18715
  function generateRequestId() {
18706
18716
  return randomBytes(16).toString("hex");
18707
18717
  }
@@ -18849,6 +18859,38 @@ function evaluateDecisionRow(db, row, currentCanonical, opts, now) {
18849
18859
  if (decision.decision === "deny") {
18850
18860
  return { state: "denied", decision };
18851
18861
  }
18862
+ if (decision.decision === "allow_once") {
18863
+ const burn = db.run(`UPDATE approval_decisions
18864
+ SET last_used_at = ?, revoked_at = ?, revoke_reason = ?
18865
+ WHERE id = ? AND revoked_at IS NULL`, [now, now, ALLOW_ONCE_CONSUMED_REASON, decision.id]);
18866
+ if ((burn.changes ?? 0) === 0) {
18867
+ audit(db, "deny", {
18868
+ agent_unit: decision.agent_unit,
18869
+ scope: decision.scope,
18870
+ action: decision.action,
18871
+ decision_id: decision.id,
18872
+ context: { reason: ALLOW_ONCE_CONSUMED_REASON }
18873
+ });
18874
+ return { state: "denied", decision };
18875
+ }
18876
+ decision.last_used_at = now;
18877
+ decision.revoked_at = now;
18878
+ decision.revoke_reason = ALLOW_ONCE_CONSUMED_REASON;
18879
+ audit(db, "match", {
18880
+ agent_unit: decision.agent_unit,
18881
+ scope: decision.scope,
18882
+ action: decision.action,
18883
+ decision_id: decision.id
18884
+ });
18885
+ audit(db, "revoke", {
18886
+ agent_unit: decision.agent_unit,
18887
+ scope: decision.scope,
18888
+ action: decision.action,
18889
+ decision_id: decision.id,
18890
+ context: { actor: "kernel", reason: ALLOW_ONCE_CONSUMED_REASON }
18891
+ });
18892
+ return { state: "granted", decision };
18893
+ }
18852
18894
  if (decision.decision === "allow_ttl") {
18853
18895
  const maxLifetime = opts.max_ttl_lifetime_ms ?? DEFAULT_MAX_TTL_LIFETIME_MS;
18854
18896
  const hardCap = decision.granted_at + maxLifetime;
@@ -18917,7 +18959,8 @@ function consumeNonce(db, request_id, now = Date.now()) {
18917
18959
  }
18918
18960
  function recordDecision(db, input, now = Date.now()) {
18919
18961
  const id = randomUUID();
18920
- const ttl_expires_at = input.decision === "allow_ttl" && input.ttl_ms ? now + input.ttl_ms : null;
18962
+ const maxLifetime = input.max_ttl_lifetime_ms ?? DEFAULT_MAX_TTL_LIFETIME_MS;
18963
+ const ttl_expires_at = input.decision === "allow_ttl" && input.ttl_ms ? now + Math.min(input.ttl_ms, maxLifetime) : null;
18921
18964
  const canonical = canonicalizeApproverSet(input.approver_set);
18922
18965
  db.run(`INSERT INTO approval_decisions
18923
18966
  (id, agent_unit, scope, action, decision,
@@ -19115,9 +19158,26 @@ var HINDSIGHT_BROKER_SOCK_VOLUME = `auth-broker-${HINDSIGHT_CONSUMER_NAME}-sock`
19115
19158
  var HINDSIGHT_CREDS_MIRROR_VOLUME = `consumer-creds-${HINDSIGHT_CONSUMER_NAME}`;
19116
19159
  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)';
19117
19160
  var HINDSIGHT_HEALTHCHECK_CMD = `python3 -c '${HINDSIGHT_HEALTHCHECK_PY}'`;
19161
+ var DOCKER_PROBE_TIMEOUT_MS = 60 * 1000;
19118
19162
 
19119
19163
  // src/memory/hindsight.ts
19120
- var 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, " + "including in-flight workflow/process narration (a sub-task started, " + "paused, or is still running) — only retain the outcome once a task " + "actually completes or a decision is made.";
19164
+ var DEFAULT_RETAIN_MISSION = "Extract durable facts that will still be true and useful weeks from now: " + "user preferences and standing rules, ongoing projects and recurring " + "commitments, technical and architectural decisions with their rationale, " + "and people/tool relationships. A preference revealed by a request is " + "durable — record the preference (what the user likes, wants, or always " + `does), not the request itself.
19165
+
19166
+ ` + `NEVER extract:
19167
+ ` + "- Agent tool-use traces or narration of what the assistant did (e.g. " + `"the assistant used X to query Y", "ran a search", "sent the message").
19168
+ ` + "- In-flight workflow/process narration (a sub-task started, paused, or is " + "still running) — retain the outcome only once the task completes or a " + `decision is made.
19169
+ ` + `- Operation, request, batch or session IDs, UUIDs, hashes, or error codes.
19170
+ ` + "- Hindsight's own errors, retries, backlogs, or internal state — the " + `memory system's self-reports are not memories.
19171
+ ` + `- Restatements of the user's current request or the task in progress.
19172
+ ` + "- Transient state (unread counts, build status, what is running right now) " + "unless the fact is explicitly dated, in which case record it as a dated " + `observation.
19173
+ ` + `- Greetings, acknowledgements, and routine operational chatter.
19174
+
19175
+ ` + "If a candidate fact matches an exclusion, drop it rather than rewording " + "it. If nothing durable remains, return an empty facts list.";
19176
+ var SUPERSEDED_RETAIN_MISSIONS = [
19177
+ "Extract technical decisions, architectural choices, user preferences, project context, and people/tool relationships. Ignore routine greetings and transient operational details.",
19178
+ "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.",
19179
+ "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, " + "including in-flight workflow/process narration (a sub-task started, " + "paused, or is still running) — only retain the outcome once a task " + "actually completes or a decision is made."
19180
+ ];
19121
19181
  var PROFILE_MEMORY_DEFAULTS = {
19122
19182
  "health-coach": {
19123
19183
  disposition: { skepticism: 2, literalism: 2, empathy: 5 },
@@ -19172,6 +19232,7 @@ init_paths();
19172
19232
  // src/agents/scaffold.ts
19173
19233
  var REPO_ROOT = resolve5(import.meta.dirname, "../..");
19174
19234
  var CLAUDE_MD_YOURS_PLACEHOLDER = "This space is yours. Add per-agent rules, exceptions, or context the " + "Switchroom template doesn't capture. Everything above the marker line is " + "regenerated on every apply; this section is preserved.";
19235
+ var pendingBankOps = new Set;
19175
19236
  var SWITCHROOM_OWNED_SETTINGS_KEYS = new Set([
19176
19237
  "permissions",
19177
19238
  "mcpServers",
@@ -19360,6 +19421,9 @@ function handleConnection(socket, agent, db, isOperator = false) {
19360
19421
  }
19361
19422
  var KERNEL_OPERATOR_NAME = "operator";
19362
19423
  var OPERATOR_ALLOWED_OPS = new Set(["approval_list"]);
19424
+ function listenerOrigin(isOperator) {
19425
+ return isOperator ? "operator" : "agent";
19426
+ }
19363
19427
  function handleRequest(socket, req, agent, db, peerUid, isOperator = false) {
19364
19428
  if (isOperator && !OPERATOR_ALLOWED_OPS.has(req.op)) {
19365
19429
  socket.write(encodeResponse(errorResponse("DENIED", `kernel operator socket is read-only: '${req.op}' is not permitted (only ${[...OPERATOR_ALLOWED_OPS].join(", ")})`)));
@@ -19420,7 +19484,8 @@ function handleRequest(socket, req, agent, db, peerUid, isOperator = false) {
19420
19484
  ttl_expires_at: r.decision.ttl_expires_at,
19421
19485
  last_used_at: r.decision.last_used_at,
19422
19486
  revoked_at: r.decision.revoked_at,
19423
- revoke_reason: r.decision.revoke_reason
19487
+ revoke_reason: r.decision.revoke_reason,
19488
+ origin: r.decision.origin
19424
19489
  } : null;
19425
19490
  socket.write(encodeResponse({ ok: true, state: r.state, decision }));
19426
19491
  return;
@@ -19447,7 +19512,8 @@ function handleRequest(socket, req, agent, db, peerUid, isOperator = false) {
19447
19512
  ttl_expires_at: r.decision.ttl_expires_at,
19448
19513
  last_used_at: r.decision.last_used_at,
19449
19514
  revoked_at: r.decision.revoked_at,
19450
- revoke_reason: r.decision.revoke_reason
19515
+ revoke_reason: r.decision.revoke_reason,
19516
+ origin: r.decision.origin
19451
19517
  } : null;
19452
19518
  socket.write(encodeResponse({ ok: true, state: r.state, decision }));
19453
19519
  return;
@@ -19509,7 +19575,8 @@ function handleRequest(socket, req, agent, db, peerUid, isOperator = false) {
19509
19575
  decision: req.decision,
19510
19576
  approver_set: req.approver_set,
19511
19577
  granted_by_user_id: req.granted_by_user_id,
19512
- ttl_ms: req.ttl_ms ?? undefined
19578
+ ttl_ms: req.ttl_ms ?? undefined,
19579
+ origin: listenerOrigin(isOperator)
19513
19580
  });
19514
19581
  socket.write(encodeResponse({ ok: true, decision_id }));
19515
19582
  return;
@@ -19528,7 +19595,8 @@ function handleRequest(socket, req, agent, db, peerUid, isOperator = false) {
19528
19595
  decision: req.decision,
19529
19596
  approver_set: req.approver_set,
19530
19597
  granted_by_user_id: req.granted_by_user_id,
19531
- ttl_ms: req.ttl_ms ?? undefined
19598
+ ttl_ms: req.ttl_ms ?? undefined,
19599
+ origin: listenerOrigin(isOperator)
19532
19600
  });
19533
19601
  if (!res.consumed) {
19534
19602
  socket.write(encodeResponse({ ok: true, consumed: false }));
@@ -19546,7 +19614,16 @@ function handleRequest(socket, req, agent, db, peerUid, isOperator = false) {
19546
19614
  return;
19547
19615
  }
19548
19616
  if (req.op === "approval_list") {
19549
- const decisions = listDecisions(db, { agent_unit: req.agent_unit });
19617
+ let listFilter = { agent_unit: req.agent_unit };
19618
+ if (!isOperator) {
19619
+ const acl = checkApprovalAclByAgent(agent, req.agent_unit ?? agent);
19620
+ if (!acl.allow) {
19621
+ socket.write(encodeResponse(errorResponse("DENIED", acl.reason)));
19622
+ return;
19623
+ }
19624
+ listFilter = { agent_unit: agent };
19625
+ }
19626
+ const decisions = listDecisions(db, listFilter);
19550
19627
  const meta = decisions.map((d) => ({
19551
19628
  id: d.id,
19552
19629
  agent_unit: d.agent_unit,
@@ -19708,6 +19785,7 @@ if (import.meta.url === `file://${process.argv[1]}` && /(?:^|[/\\])(?:vault[/\\]
19708
19785
  export {
19709
19786
  openKernelDb,
19710
19787
  main,
19788
+ listenerOrigin,
19711
19789
  bootstrap,
19712
19790
  KERNEL_OPERATOR_NAME
19713
19791
  };