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
@@ -13406,7 +13406,8 @@ var ApprovalDecisionMetaSchema = exports_external.object({
13406
13406
  ttl_expires_at: exports_external.number().nullable(),
13407
13407
  last_used_at: exports_external.number().nullable(),
13408
13408
  revoked_at: exports_external.number().nullable(),
13409
- revoke_reason: exports_external.string().nullable()
13409
+ revoke_reason: exports_external.string().nullable(),
13410
+ origin: exports_external.enum(["agent", "operator"]).optional()
13410
13411
  });
13411
13412
  var OkApprovalLookupResponseSchema = exports_external.object({
13412
13413
  ok: exports_external.literal(true),
@@ -18352,6 +18352,47 @@ for (const name of SHARED_FRAGMENTS) {
18352
18352
  }
18353
18353
  }
18354
18354
 
18355
+ // src/litellm/timeout-budget.ts
18356
+ var LITELLM_ROUTER_MARGIN_S = 10;
18357
+ var LITELLM_TIMEOUT_TIERS = {
18358
+ interactive: {
18359
+ group: "gpt-oss-20b",
18360
+ localTimeoutS: 90,
18361
+ fallbackGroup: "gpt-oss-20b-openrouter",
18362
+ fallbackTimeoutS: 60
18363
+ },
18364
+ retain: {
18365
+ group: "gpt-oss-20b-retain",
18366
+ localTimeoutS: 200,
18367
+ fallbackGroup: "gpt-oss-20b-retain-openrouter",
18368
+ fallbackTimeoutS: 90
18369
+ },
18370
+ consolidation: {
18371
+ group: "gpt-oss-20b-consolidation",
18372
+ localTimeoutS: 200,
18373
+ fallbackGroup: "gpt-oss-20b-consolidation-openrouter",
18374
+ fallbackTimeoutS: 90
18375
+ }
18376
+ };
18377
+ function litellmChainSeconds(tier) {
18378
+ assertPositive(tier.localTimeoutS, `${tier.group} localTimeoutS`);
18379
+ if (tier.fallbackGroup === null)
18380
+ return tier.localTimeoutS;
18381
+ assertPositive(tier.fallbackTimeoutS, `${tier.fallbackGroup} fallbackTimeoutS`);
18382
+ return tier.localTimeoutS + tier.fallbackTimeoutS;
18383
+ }
18384
+ function minimumClientBudgetSeconds(tier, marginS = LITELLM_ROUTER_MARGIN_S) {
18385
+ return litellmChainSeconds(tier) + marginS;
18386
+ }
18387
+ function clientBudgetSeconds(tier, floorS = 0, marginS = LITELLM_ROUTER_MARGIN_S) {
18388
+ return Math.max(Math.ceil(floorS), minimumClientBudgetSeconds(tier, marginS));
18389
+ }
18390
+ function assertPositive(value, label) {
18391
+ if (!Number.isFinite(value) || value <= 0) {
18392
+ throw new Error(`litellm timeout budget: ${label} must be a positive number, got ${value}`);
18393
+ }
18394
+ }
18395
+
18355
18396
  // src/setup/hindsight.ts
18356
18397
  var HINDSIGHT_DEFAULT_API_PORT = 18888;
18357
18398
  var HINDSIGHT_DEFAULT_MCP_URL = `http://127.0.0.1:${HINDSIGHT_DEFAULT_API_PORT}/mcp/`;
@@ -18361,8 +18402,21 @@ var HINDSIGHT_IMAGE_REPO = "ghcr.io/switchroom/switchroom-hindsight";
18361
18402
  var HINDSIGHT_IMAGE = `${HINDSIGHT_IMAGE_REPO}:latest`;
18362
18403
  var HINDSIGHT_BROKER_SOCK_VOLUME = `auth-broker-${HINDSIGHT_CONSUMER_NAME}-sock`;
18363
18404
  var HINDSIGHT_CREDS_MIRROR_VOLUME = `consumer-creds-${HINDSIGHT_CONSUMER_NAME}`;
18405
+ var HINDSIGHT_DEFAULT_RETAIN_MAX_COMPLETION_TOKENS = 16384;
18406
+ var HINDSIGHT_RETAIN_MIN_TOKENS_PER_SECOND = 80.6;
18407
+ function hindsightRetainLlmTimeoutSeconds(maxCompletionTokens = HINDSIGHT_DEFAULT_RETAIN_MAX_COMPLETION_TOKENS, tokensPerSecond = HINDSIGHT_RETAIN_MIN_TOKENS_PER_SECOND) {
18408
+ if (!(maxCompletionTokens > 0) || !(tokensPerSecond > 0)) {
18409
+ throw new Error(`hindsight retain budget: maxCompletionTokens (${maxCompletionTokens}) and ` + `tokensPerSecond (${tokensPerSecond}) must both be positive`);
18410
+ }
18411
+ return Math.ceil(maxCompletionTokens / tokensPerSecond);
18412
+ }
18413
+ function hindsightRetainClientTimeoutSeconds() {
18414
+ return clientBudgetSeconds(LITELLM_TIMEOUT_TIERS.retain, hindsightRetainLlmTimeoutSeconds());
18415
+ }
18416
+ var HINDSIGHT_RETAIN_CLIENT_DEADLINE_S = hindsightRetainClientTimeoutSeconds() + LITELLM_ROUTER_MARGIN_S;
18364
18417
  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)';
18365
18418
  var HINDSIGHT_HEALTHCHECK_CMD = `python3 -c '${HINDSIGHT_HEALTHCHECK_PY}'`;
18419
+ var DOCKER_PROBE_TIMEOUT_MS = 60 * 1000;
18366
18420
 
18367
18421
  // src/memory/hindsight.ts
18368
18422
  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.
@@ -18695,7 +18749,8 @@ var ApprovalDecisionMetaSchema = exports_external.object({
18695
18749
  ttl_expires_at: exports_external.number().nullable(),
18696
18750
  last_used_at: exports_external.number().nullable(),
18697
18751
  revoked_at: exports_external.number().nullable(),
18698
- revoke_reason: exports_external.string().nullable()
18752
+ revoke_reason: exports_external.string().nullable(),
18753
+ origin: exports_external.enum(["agent", "operator"]).optional()
18699
18754
  });
18700
18755
  var OkApprovalLookupResponseSchema = exports_external.object({
18701
18756
  ok: exports_external.literal(true),
@@ -5300,6 +5300,34 @@ function truncateLine2(s) {
5300
5300
  return cleaned.length <= 60 ? cleaned : `${cleaned.slice(0, 59)}\u2026`;
5301
5301
  }
5302
5302
 
5303
+ // src/vault/approvals/gated-write-policy.ts
5304
+ var REQUIRE_OPERATOR_WRITE_ENV = "SWITCHROOM_REQUIRE_OPERATOR_APPROVAL_WRITE";
5305
+ function requireOperatorApprovalForWrites(env = process.env) {
5306
+ return env[REQUIRE_OPERATOR_WRITE_ENV] === "1";
5307
+ }
5308
+ function isGatedWriteApproved(lookup, requireOperator) {
5309
+ if (lookup.state !== "granted") {
5310
+ return { allow: false, reason: `approval state is '${lookup.state ?? "unknown"}', not 'granted'` };
5311
+ }
5312
+ if (!requireOperator)
5313
+ return { allow: true };
5314
+ if (lookup.origin !== "operator") {
5315
+ return {
5316
+ allow: false,
5317
+ reason: `approval was recorded with origin='${lookup.origin ?? "unknown"}', not 'operator' \u2014 ` + `an agent-origin decision is not proof an operator tapped ` + `(${REQUIRE_OPERATOR_WRITE_ENV}=1)`
5318
+ };
5319
+ }
5320
+ return { allow: true };
5321
+ }
5322
+ function evaluateGatedWrite(lookup, requireOperator) {
5323
+ const verdict = isGatedWriteApproved(lookup, requireOperator);
5324
+ if (verdict.allow)
5325
+ return { kind: "allow" };
5326
+ if (lookup.state === "granted")
5327
+ return { kind: "block", reason: verdict.reason };
5328
+ return { kind: "wait" };
5329
+ }
5330
+
5303
5331
  // src/cli/drive-write-pretool.ts
5304
5332
  var HOOK_TIMEOUT_MS = 5 * 60 * 1000;
5305
5333
  var KERNEL_POLL_INTERVAL_MS = 2000;
@@ -5383,9 +5411,15 @@ async function approvalLookup(scope, approverSet) {
5383
5411
  current_approver_set: approverSet
5384
5412
  });
5385
5413
  if (r === null || r.ok !== true)
5386
- return null;
5387
- return typeof r.state === "string" ? r.state : null;
5414
+ return { state: null };
5415
+ const dec = r.decision;
5416
+ const origin = dec != null && (dec.origin === "operator" || dec.origin === "agent") ? dec.origin : undefined;
5417
+ return {
5418
+ state: typeof r.state === "string" ? r.state : null,
5419
+ origin
5420
+ };
5388
5421
  }
5422
+ var REQUIRE_OPERATOR_ORIGIN = requireOperatorApprovalForWrites();
5389
5423
  function loadAllowFrom() {
5390
5424
  const stateDir = process.env.TELEGRAM_STATE_DIR ?? join2(homedir2(), ".claude", "channels", "telegram");
5391
5425
  const accessPath = join2(stateDir, "access.json");
@@ -5500,9 +5534,13 @@ async function main() {
5500
5534
  block("no operator paired \u2014 cannot post approval card");
5501
5535
  }
5502
5536
  const existing = await approvalLookup(scope, allowFrom);
5503
- if (existing === "granted") {
5537
+ const existingAction = evaluateGatedWrite(existing, REQUIRE_OPERATOR_ORIGIN);
5538
+ if (existingAction.kind === "allow") {
5504
5539
  allow();
5505
5540
  }
5541
+ if (existingAction.kind === "block") {
5542
+ block(`Drive write refused: ${existingAction.reason}`);
5543
+ }
5506
5544
  let handle;
5507
5545
  try {
5508
5546
  handle = await loadFromAuthBroker();
@@ -5546,10 +5584,15 @@ async function main() {
5546
5584
  if (!first) {
5547
5585
  await new Promise((r) => setTimeout(r, KERNEL_POLL_INTERVAL_MS));
5548
5586
  }
5549
- const state = await approvalLookup(scope, allowFrom);
5550
- if (state === "granted") {
5587
+ const lookup = await approvalLookup(scope, allowFrom);
5588
+ const state = lookup.state;
5589
+ const action = evaluateGatedWrite(lookup, REQUIRE_OPERATOR_ORIGIN);
5590
+ if (action.kind === "allow") {
5551
5591
  allow();
5552
5592
  }
5593
+ if (action.kind === "block") {
5594
+ block(`Drive write refused: ${action.reason}`);
5595
+ }
5553
5596
  if (state === "denied" || state === "drift_revoked") {
5554
5597
  block(`user denied Drive write (kernel verdict: ${state})`);
5555
5598
  }
@@ -4944,6 +4944,34 @@ function buildBatchAbortReason(appliedInBatch) {
4944
4944
  return `grant lapsed mid-batch \u2014 ${n} MS-365 write${n === 1 ? "" : "s"} already ` + `applied in this batch; remaining ops aborted. STOP: do not retry blindly \u2014 ` + `reconcile what landed before re-issuing.`;
4945
4945
  }
4946
4946
 
4947
+ // src/vault/approvals/gated-write-policy.ts
4948
+ var REQUIRE_OPERATOR_WRITE_ENV = "SWITCHROOM_REQUIRE_OPERATOR_APPROVAL_WRITE";
4949
+ function requireOperatorApprovalForWrites(env = process.env) {
4950
+ return env[REQUIRE_OPERATOR_WRITE_ENV] === "1";
4951
+ }
4952
+ function isGatedWriteApproved(lookup, requireOperator) {
4953
+ if (lookup.state !== "granted") {
4954
+ return { allow: false, reason: `approval state is '${lookup.state ?? "unknown"}', not 'granted'` };
4955
+ }
4956
+ if (!requireOperator)
4957
+ return { allow: true };
4958
+ if (lookup.origin !== "operator") {
4959
+ return {
4960
+ allow: false,
4961
+ reason: `approval was recorded with origin='${lookup.origin ?? "unknown"}', not 'operator' \u2014 ` + `an agent-origin decision is not proof an operator tapped ` + `(${REQUIRE_OPERATOR_WRITE_ENV}=1)`
4962
+ };
4963
+ }
4964
+ return { allow: true };
4965
+ }
4966
+ function evaluateGatedWrite(lookup, requireOperator) {
4967
+ const verdict = isGatedWriteApproved(lookup, requireOperator);
4968
+ if (verdict.allow)
4969
+ return { kind: "allow" };
4970
+ if (lookup.state === "granted")
4971
+ return { kind: "block", reason: verdict.reason };
4972
+ return { kind: "wait" };
4973
+ }
4974
+
4947
4975
  // src/cli/ms-365-write-pretool.ts
4948
4976
  var HOOK_TIMEOUT_MS = 5 * 60 * 1000;
4949
4977
  var KERNEL_POLL_INTERVAL_MS = 2000;
@@ -5350,8 +5378,14 @@ async function approvalLookupByRequest(agentUnit, requestId, approverSet) {
5350
5378
  });
5351
5379
  if (!res.ok)
5352
5380
  return null;
5353
- return res.value;
5381
+ const value = res.value;
5382
+ const o = value.decision?.origin;
5383
+ return {
5384
+ state: value.state,
5385
+ origin: o === "operator" || o === "agent" ? o : undefined
5386
+ };
5354
5387
  }
5388
+ var REQUIRE_OPERATOR_ORIGIN = requireOperatorApprovalForWrites();
5355
5389
  function fail(reason) {
5356
5390
  process.stdout.write(JSON.stringify({
5357
5391
  decision: "block",
@@ -5426,10 +5460,14 @@ async function main() {
5426
5460
  if (!lookup)
5427
5461
  continue;
5428
5462
  const state = lookup.state;
5429
- if (state === "granted") {
5463
+ const action = evaluateGatedWrite({ state: state ?? null, origin: lookup.origin }, REQUIRE_OPERATOR_ORIGIN);
5464
+ if (action.kind === "allow") {
5430
5465
  recordOutcome(Date.now(), { ...ledgerEntry, outcome: "applied" });
5431
5466
  allow();
5432
5467
  }
5468
+ if (action.kind === "block") {
5469
+ fail(action.reason);
5470
+ }
5433
5471
  if (state === "expired" || state === "drift_revoked") {
5434
5472
  const t = Date.now();
5435
5473
  const applied = countCurrentBatchApplied(readLedger(t).entries, t);
@@ -11190,7 +11190,8 @@ var init_protocol = __esm(() => {
11190
11190
  ttl_expires_at: exports_external.number().nullable(),
11191
11191
  last_used_at: exports_external.number().nullable(),
11192
11192
  revoked_at: exports_external.number().nullable(),
11193
- revoke_reason: exports_external.string().nullable()
11193
+ revoke_reason: exports_external.string().nullable(),
11194
+ origin: exports_external.enum(["agent", "operator"]).optional()
11194
11195
  });
11195
11196
  OkApprovalLookupResponseSchema = exports_external.object({
11196
11197
  ok: exports_external.literal(true),