switchroom 0.19.18 → 0.19.22

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 (76) hide show
  1. package/dist/agent-scheduler/index.js +2 -1
  2. package/dist/auth-broker/index.js +56 -1
  3. package/dist/cli/drive-write-pretool.mjs +48 -5
  4. package/dist/cli/ms-365-write-pretool.mjs +40 -2
  5. package/dist/cli/notion-write-pretool.mjs +2 -1
  6. package/dist/cli/switchroom.js +5242 -2239
  7. package/dist/host-control/main.js +12241 -11375
  8. package/dist/vault/approvals/kernel-server.js +113 -7
  9. package/dist/vault/broker/server.js +259 -76
  10. package/package.json +6 -3
  11. package/profiles/_base/start.sh.hbs +61 -1
  12. package/skills/switchroom-release/SKILL.md +103 -20
  13. package/telegram-plugin/bridge/bridge.ts +14 -0
  14. package/telegram-plugin/card-format.ts +92 -3
  15. package/telegram-plugin/dist/bridge/bridge.js +13 -0
  16. package/telegram-plugin/dist/gateway/gateway.js +2356 -1159
  17. package/telegram-plugin/dist/server.js +13 -0
  18. package/telegram-plugin/edit-flood-fuse.ts +477 -0
  19. package/telegram-plugin/format.ts +19 -7
  20. package/telegram-plugin/gateway/always-allow-persist-queue.ts +97 -11
  21. package/telegram-plugin/gateway/boot-sweep-gate.ts +164 -0
  22. package/telegram-plugin/gateway/callback-query-handlers.ts +454 -81
  23. package/telegram-plugin/gateway/gateway.ts +66 -56
  24. package/telegram-plugin/gateway/inbound-interceptors.ts +27 -4
  25. package/telegram-plugin/gateway/missed-approvals-store.ts +66 -17
  26. package/telegram-plugin/gateway/narrative-lane.ts +49 -3
  27. package/telegram-plugin/gateway/pending-card-store.ts +46 -16
  28. package/telegram-plugin/gateway/scoped-grant-store.ts +39 -14
  29. package/telegram-plugin/gateway/status-pin-api.ts +145 -0
  30. package/telegram-plugin/gateway/store-file.ts +244 -0
  31. package/telegram-plugin/hooks/subagent-tracker-posttool.mjs +325 -45
  32. package/telegram-plugin/hooks/tool-label-pretool.mjs +88 -2
  33. package/telegram-plugin/retry-api-call.ts +15 -2
  34. package/telegram-plugin/send-gate.ts +1 -1
  35. package/telegram-plugin/status-no-truncate.ts +64 -1
  36. package/telegram-plugin/status-pin-driver.ts +50 -27
  37. package/telegram-plugin/status-pin.ts +43 -5
  38. package/telegram-plugin/tests/activity-card-send-gate.test.ts +275 -0
  39. package/telegram-plugin/tests/activity-card-wiring.test.ts +16 -7
  40. package/telegram-plugin/tests/boot-pin-sweep-wiring.test.ts +101 -0
  41. package/telegram-plugin/tests/boot-sweep-gate.test.ts +293 -0
  42. package/telegram-plugin/tests/boot-version-string.test.ts +0 -0
  43. package/telegram-plugin/tests/bridge-tool-parity.test.ts +95 -0
  44. package/telegram-plugin/tests/edit-flood-fuse.test.ts +431 -0
  45. package/telegram-plugin/tests/pinned-card-collapse.test.ts +356 -0
  46. package/telegram-plugin/tests/status-pin-api.test.ts +178 -0
  47. package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +94 -11
  48. package/telegram-plugin/tests/status-pin.test.ts +106 -5
  49. package/telegram-plugin/tests/store-atomic-write.test.ts +411 -0
  50. package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +631 -1
  51. package/telegram-plugin/tests/tool-activity-summary.test.ts +28 -12
  52. package/telegram-plugin/tests/tool-label-pretool.test.ts +94 -0
  53. package/telegram-plugin/tests/vault-approval-posture.test.ts +6 -1
  54. package/telegram-plugin/tests/vault-passphrase-retry.test.ts +666 -0
  55. package/telegram-plugin/tests/vault-request-access-unlock-resume.test.ts +42 -21
  56. package/telegram-plugin/tests/worker-feed-coalesce.test.ts +233 -1
  57. package/telegram-plugin/tests/worker-feed-repeat-steps.test.ts +147 -0
  58. package/telegram-plugin/tool-activity-summary.ts +85 -13
  59. package/telegram-plugin/worker-activity-feed.ts +56 -2
  60. package/vendor/hindsight-memory/scripts/drain_pending.py +847 -67
  61. package/vendor/hindsight-memory/scripts/lib/client.py +124 -0
  62. package/vendor/hindsight-memory/scripts/lib/pending.py +944 -33
  63. package/vendor/hindsight-memory/scripts/lib/retain_split.py +460 -0
  64. package/vendor/hindsight-memory/scripts/recall.py +74 -5
  65. package/vendor/hindsight-memory/scripts/session_start.py +48 -0
  66. package/vendor/hindsight-memory/scripts/tests/test_client_document_exists.py +470 -0
  67. package/vendor/hindsight-memory/scripts/tests/test_pending_drops.py +2275 -0
  68. package/vendor/hindsight-memory/scripts/tests/test_pending_failure_class.py +105 -0
  69. package/vendor/hindsight-memory/scripts/tests/test_pending_wedge.py +300 -0
  70. package/vendor/hindsight-memory/scripts/tests/test_recall_degraded_notice.py +365 -0
  71. package/vendor/hindsight-memory/scripts/tests/test_recall_envelope_strip_telemetry.py +12 -4
  72. package/vendor/hindsight-memory/scripts/tests/test_recall_transcript_fallback.py +27 -2
  73. package/vendor/hindsight-memory/scripts/tests/test_retain_split.py +438 -0
  74. package/vendor/hindsight-memory/scripts/tests/test_session_start_version_skew.py +204 -0
  75. package/vendor/hindsight-memory/tests/test_drain_pending.py +130 -8
  76. package/vendor/hindsight-memory/tests/test_pending.py +32 -7
@@ -18546,7 +18546,8 @@ var ApprovalDecisionMetaSchema = exports_external.object({
18546
18546
  ttl_expires_at: exports_external.number().nullable(),
18547
18547
  last_used_at: exports_external.number().nullable(),
18548
18548
  revoked_at: exports_external.number().nullable(),
18549
- revoke_reason: exports_external.string().nullable()
18549
+ revoke_reason: exports_external.string().nullable(),
18550
+ origin: exports_external.enum(["agent", "operator"]).optional()
18550
18551
  });
18551
18552
  var OkApprovalLookupResponseSchema = exports_external.object({
18552
18553
  ok: exports_external.literal(true),
@@ -18710,6 +18711,7 @@ function migrateApprovalSchema(db) {
18710
18711
  var DEFAULT_MAX_TTL_LIFETIME_MS = 7 * 24 * 60 * 60 * 1000;
18711
18712
 
18712
18713
  // src/vault/approvals/kernel.ts
18714
+ var ALLOW_ONCE_CONSUMED_REASON = "allow_once_consumed";
18713
18715
  function generateRequestId() {
18714
18716
  return randomBytes(16).toString("hex");
18715
18717
  }
@@ -18857,6 +18859,38 @@ function evaluateDecisionRow(db, row, currentCanonical, opts, now) {
18857
18859
  if (decision.decision === "deny") {
18858
18860
  return { state: "denied", decision };
18859
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
+ }
18860
18894
  if (decision.decision === "allow_ttl") {
18861
18895
  const maxLifetime = opts.max_ttl_lifetime_ms ?? DEFAULT_MAX_TTL_LIFETIME_MS;
18862
18896
  const hardCap = decision.granted_at + maxLifetime;
@@ -18925,7 +18959,8 @@ function consumeNonce(db, request_id, now = Date.now()) {
18925
18959
  }
18926
18960
  function recordDecision(db, input, now = Date.now()) {
18927
18961
  const id = randomUUID();
18928
- 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;
18929
18964
  const canonical = canonicalizeApproverSet(input.approver_set);
18930
18965
  db.run(`INSERT INTO approval_decisions
18931
18966
  (id, agent_unit, scope, action, decision,
@@ -19112,6 +19147,47 @@ for (const name of SHARED_FRAGMENTS) {
19112
19147
  }
19113
19148
  }
19114
19149
 
19150
+ // src/litellm/timeout-budget.ts
19151
+ var LITELLM_ROUTER_MARGIN_S = 10;
19152
+ var LITELLM_TIMEOUT_TIERS = {
19153
+ interactive: {
19154
+ group: "gpt-oss-20b",
19155
+ localTimeoutS: 90,
19156
+ fallbackGroup: "gpt-oss-20b-openrouter",
19157
+ fallbackTimeoutS: 60
19158
+ },
19159
+ retain: {
19160
+ group: "gpt-oss-20b-retain",
19161
+ localTimeoutS: 200,
19162
+ fallbackGroup: "gpt-oss-20b-retain-openrouter",
19163
+ fallbackTimeoutS: 90
19164
+ },
19165
+ consolidation: {
19166
+ group: "gpt-oss-20b-consolidation",
19167
+ localTimeoutS: 200,
19168
+ fallbackGroup: "gpt-oss-20b-consolidation-openrouter",
19169
+ fallbackTimeoutS: 90
19170
+ }
19171
+ };
19172
+ function litellmChainSeconds(tier) {
19173
+ assertPositive(tier.localTimeoutS, `${tier.group} localTimeoutS`);
19174
+ if (tier.fallbackGroup === null)
19175
+ return tier.localTimeoutS;
19176
+ assertPositive(tier.fallbackTimeoutS, `${tier.fallbackGroup} fallbackTimeoutS`);
19177
+ return tier.localTimeoutS + tier.fallbackTimeoutS;
19178
+ }
19179
+ function minimumClientBudgetSeconds(tier, marginS = LITELLM_ROUTER_MARGIN_S) {
19180
+ return litellmChainSeconds(tier) + marginS;
19181
+ }
19182
+ function clientBudgetSeconds(tier, floorS = 0, marginS = LITELLM_ROUTER_MARGIN_S) {
19183
+ return Math.max(Math.ceil(floorS), minimumClientBudgetSeconds(tier, marginS));
19184
+ }
19185
+ function assertPositive(value, label) {
19186
+ if (!Number.isFinite(value) || value <= 0) {
19187
+ throw new Error(`litellm timeout budget: ${label} must be a positive number, got ${value}`);
19188
+ }
19189
+ }
19190
+
19115
19191
  // src/setup/hindsight.ts
19116
19192
  var HINDSIGHT_DEFAULT_API_PORT = 18888;
19117
19193
  var HINDSIGHT_DEFAULT_MCP_URL = `http://127.0.0.1:${HINDSIGHT_DEFAULT_API_PORT}/mcp/`;
@@ -19121,8 +19197,21 @@ var HINDSIGHT_IMAGE_REPO = "ghcr.io/switchroom/switchroom-hindsight";
19121
19197
  var HINDSIGHT_IMAGE = `${HINDSIGHT_IMAGE_REPO}:latest`;
19122
19198
  var HINDSIGHT_BROKER_SOCK_VOLUME = `auth-broker-${HINDSIGHT_CONSUMER_NAME}-sock`;
19123
19199
  var HINDSIGHT_CREDS_MIRROR_VOLUME = `consumer-creds-${HINDSIGHT_CONSUMER_NAME}`;
19200
+ var HINDSIGHT_DEFAULT_RETAIN_MAX_COMPLETION_TOKENS = 16384;
19201
+ var HINDSIGHT_RETAIN_MIN_TOKENS_PER_SECOND = 80.6;
19202
+ function hindsightRetainLlmTimeoutSeconds(maxCompletionTokens = HINDSIGHT_DEFAULT_RETAIN_MAX_COMPLETION_TOKENS, tokensPerSecond = HINDSIGHT_RETAIN_MIN_TOKENS_PER_SECOND) {
19203
+ if (!(maxCompletionTokens > 0) || !(tokensPerSecond > 0)) {
19204
+ throw new Error(`hindsight retain budget: maxCompletionTokens (${maxCompletionTokens}) and ` + `tokensPerSecond (${tokensPerSecond}) must both be positive`);
19205
+ }
19206
+ return Math.ceil(maxCompletionTokens / tokensPerSecond);
19207
+ }
19208
+ function hindsightRetainClientTimeoutSeconds() {
19209
+ return clientBudgetSeconds(LITELLM_TIMEOUT_TIERS.retain, hindsightRetainLlmTimeoutSeconds());
19210
+ }
19211
+ var HINDSIGHT_RETAIN_CLIENT_DEADLINE_S = hindsightRetainClientTimeoutSeconds() + LITELLM_ROUTER_MARGIN_S;
19124
19212
  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)';
19125
19213
  var HINDSIGHT_HEALTHCHECK_CMD = `python3 -c '${HINDSIGHT_HEALTHCHECK_PY}'`;
19214
+ var DOCKER_PROBE_TIMEOUT_MS = 60 * 1000;
19126
19215
 
19127
19216
  // src/memory/hindsight.ts
19128
19217
  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.
@@ -19385,6 +19474,9 @@ function handleConnection(socket, agent, db, isOperator = false) {
19385
19474
  }
19386
19475
  var KERNEL_OPERATOR_NAME = "operator";
19387
19476
  var OPERATOR_ALLOWED_OPS = new Set(["approval_list"]);
19477
+ function listenerOrigin(isOperator) {
19478
+ return isOperator ? "operator" : "agent";
19479
+ }
19388
19480
  function handleRequest(socket, req, agent, db, peerUid, isOperator = false) {
19389
19481
  if (isOperator && !OPERATOR_ALLOWED_OPS.has(req.op)) {
19390
19482
  socket.write(encodeResponse(errorResponse("DENIED", `kernel operator socket is read-only: '${req.op}' is not permitted (only ${[...OPERATOR_ALLOWED_OPS].join(", ")})`)));
@@ -19445,7 +19537,8 @@ function handleRequest(socket, req, agent, db, peerUid, isOperator = false) {
19445
19537
  ttl_expires_at: r.decision.ttl_expires_at,
19446
19538
  last_used_at: r.decision.last_used_at,
19447
19539
  revoked_at: r.decision.revoked_at,
19448
- revoke_reason: r.decision.revoke_reason
19540
+ revoke_reason: r.decision.revoke_reason,
19541
+ origin: r.decision.origin
19449
19542
  } : null;
19450
19543
  socket.write(encodeResponse({ ok: true, state: r.state, decision }));
19451
19544
  return;
@@ -19472,7 +19565,8 @@ function handleRequest(socket, req, agent, db, peerUid, isOperator = false) {
19472
19565
  ttl_expires_at: r.decision.ttl_expires_at,
19473
19566
  last_used_at: r.decision.last_used_at,
19474
19567
  revoked_at: r.decision.revoked_at,
19475
- revoke_reason: r.decision.revoke_reason
19568
+ revoke_reason: r.decision.revoke_reason,
19569
+ origin: r.decision.origin
19476
19570
  } : null;
19477
19571
  socket.write(encodeResponse({ ok: true, state: r.state, decision }));
19478
19572
  return;
@@ -19534,7 +19628,8 @@ function handleRequest(socket, req, agent, db, peerUid, isOperator = false) {
19534
19628
  decision: req.decision,
19535
19629
  approver_set: req.approver_set,
19536
19630
  granted_by_user_id: req.granted_by_user_id,
19537
- ttl_ms: req.ttl_ms ?? undefined
19631
+ ttl_ms: req.ttl_ms ?? undefined,
19632
+ origin: listenerOrigin(isOperator)
19538
19633
  });
19539
19634
  socket.write(encodeResponse({ ok: true, decision_id }));
19540
19635
  return;
@@ -19553,7 +19648,8 @@ function handleRequest(socket, req, agent, db, peerUid, isOperator = false) {
19553
19648
  decision: req.decision,
19554
19649
  approver_set: req.approver_set,
19555
19650
  granted_by_user_id: req.granted_by_user_id,
19556
- ttl_ms: req.ttl_ms ?? undefined
19651
+ ttl_ms: req.ttl_ms ?? undefined,
19652
+ origin: listenerOrigin(isOperator)
19557
19653
  });
19558
19654
  if (!res.consumed) {
19559
19655
  socket.write(encodeResponse({ ok: true, consumed: false }));
@@ -19571,7 +19667,16 @@ function handleRequest(socket, req, agent, db, peerUid, isOperator = false) {
19571
19667
  return;
19572
19668
  }
19573
19669
  if (req.op === "approval_list") {
19574
- const decisions = listDecisions(db, { agent_unit: req.agent_unit });
19670
+ let listFilter = { agent_unit: req.agent_unit };
19671
+ if (!isOperator) {
19672
+ const acl = checkApprovalAclByAgent(agent, req.agent_unit ?? agent);
19673
+ if (!acl.allow) {
19674
+ socket.write(encodeResponse(errorResponse("DENIED", acl.reason)));
19675
+ return;
19676
+ }
19677
+ listFilter = { agent_unit: agent };
19678
+ }
19679
+ const decisions = listDecisions(db, listFilter);
19575
19680
  const meta = decisions.map((d) => ({
19576
19681
  id: d.id,
19577
19682
  agent_unit: d.agent_unit,
@@ -19733,6 +19838,7 @@ if (import.meta.url === `file://${process.argv[1]}` && /(?:^|[/\\])(?:vault[/\\]
19733
19838
  export {
19734
19839
  openKernelDb,
19735
19840
  main,
19841
+ listenerOrigin,
19736
19842
  bootstrap,
19737
19843
  KERNEL_OPERATOR_NAME
19738
19844
  };