sneakoscope 7.3.0 → 7.6.0

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 (283) hide show
  1. package/README.md +129 -10
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/dist/cli/codex-app-command.js +1 -1
  5. package/dist/cli/command-manifest-lite.js +4 -1
  6. package/dist/cli/command-registry.js +2 -0
  7. package/dist/cli/install-helpers-codex-lb-chain.js +42 -0
  8. package/dist/cli/install-helpers-codex-lb-config.js +114 -9
  9. package/dist/cli/install-helpers-install-support.js +6 -16
  10. package/dist/cli/install-helpers.js +788 -600
  11. package/dist/cli/router.js +30 -2
  12. package/dist/commands/codex-app.js +37 -1
  13. package/dist/commands/codex-lb.js +491 -300
  14. package/dist/config/skills-manifest.json +57 -71
  15. package/dist/core/align/align-route.js +127 -0
  16. package/dist/core/codex-app/codex-app-fast-ui-repair.js +8 -4
  17. package/dist/core/codex-app/codex-app-restart.js +83 -0
  18. package/dist/core/codex-app/codex-app-ui-state-snapshot.js +8 -4
  19. package/dist/core/codex-app/desktop-routing-snapshot.js +1 -1
  20. package/dist/core/codex-app/menubar/constants.js +5 -0
  21. package/dist/core/codex-app/menubar/generation-transaction-journal.js +1 -3
  22. package/dist/core/codex-app/menubar/installer.js +18 -0
  23. package/dist/core/codex-app/multi-provider-router.js +22 -10
  24. package/dist/core/codex-app/openrouter-activate.js +26 -1
  25. package/dist/core/codex-app.js +118 -26
  26. package/dist/core/codex-control/codex-app-server-runtime-env.js +86 -0
  27. package/dist/core/codex-control/codex-app-server-v2-client.js +78 -8
  28. package/dist/core/codex-control/codex-task-runner.js +2 -2
  29. package/dist/core/codex-lb/capability-runner.js +204 -0
  30. package/dist/core/codex-lb/capability-types.js +1 -0
  31. package/dist/core/codex-lb/codex-lb-env.js +9 -13
  32. package/dist/core/codex-lb/codex-lb-setup.js +45 -8
  33. package/dist/core/codex-lb/codex-lb-tool-catalog.js +52 -74
  34. package/dist/core/codex-lb/codex-lb-tool-output-recovery.js +1 -1
  35. package/dist/core/codex-lb/desktop-auth-invariant.js +185 -0
  36. package/dist/core/codex-lb/desktop-bridge/client.js +34 -0
  37. package/dist/core/codex-lb/desktop-bridge/header-policy.js +106 -0
  38. package/dist/core/codex-lb/desktop-bridge/http-forward.js +101 -0
  39. package/dist/core/codex-lb/desktop-bridge/index.js +10 -0
  40. package/dist/core/codex-lb/desktop-bridge/launchd.js +81 -0
  41. package/dist/core/codex-lb/desktop-bridge/location-rewrite.js +27 -0
  42. package/dist/core/codex-lb/desktop-bridge/security.js +274 -0
  43. package/dist/core/codex-lb/desktop-bridge/server.js +179 -0
  44. package/dist/core/codex-lb/desktop-bridge/state.js +119 -0
  45. package/dist/core/codex-lb/desktop-bridge/types.js +16 -0
  46. package/dist/core/codex-lb/desktop-bridge/websocket-forward.js +151 -0
  47. package/dist/core/codex-lb/desktop-center-credentials.js +189 -0
  48. package/dist/core/codex-lb/desktop-controller.js +1410 -0
  49. package/dist/core/codex-lb/desktop-mode.js +32 -0
  50. package/dist/core/codex-lb/desktop-real-evidence.js +168 -0
  51. package/dist/core/codex-lb/desktop-service.js +613 -0
  52. package/dist/core/codex-lb/legacy-migration.js +575 -0
  53. package/dist/core/codex-lb/migration-receipt.js +246 -0
  54. package/dist/core/codex-lb/probes/auxiliary-surfaces-probe.js +60 -0
  55. package/dist/core/codex-lb/probes/bridge-probe.js +75 -0
  56. package/dist/core/codex-lb/probes/catalog-probe.js +128 -0
  57. package/dist/core/codex-lb/probes/computer-use-probe.js +50 -0
  58. package/dist/core/codex-lb/probes/image-generation-probe.js +47 -0
  59. package/dist/core/codex-lb/probes/probe-evidence.js +39 -0
  60. package/dist/core/codex-lb/probes/voice-realtime-probe.js +44 -0
  61. package/dist/core/codex-lb/trusted-deep-evidence.js +403 -0
  62. package/dist/core/codex-native/core-skill-manifest.js +22 -6
  63. package/dist/core/codex-runtime/codex-desktop-config-policy.js +2 -1
  64. package/dist/core/commands/align-command.js +220 -0
  65. package/dist/core/commands/image-ux-review-command.js +280 -68
  66. package/dist/core/commands/naruto-command.js +257 -8
  67. package/dist/core/commands/ppt-command.js +2 -2
  68. package/dist/core/commands/telegram-command.js +402 -34
  69. package/dist/core/commands/triwiki-command.js +11 -1
  70. package/dist/core/commands/triwiki-graph-command.js +160 -0
  71. package/dist/core/commands/wiki-command.js +100 -20
  72. package/dist/core/doctor/skill-legacy-surface.js +1 -3
  73. package/dist/core/errors/message.js +3 -0
  74. package/dist/core/evidence/evidence-freshness.js +14 -0
  75. package/dist/core/feature-fixtures.js +9 -1
  76. package/dist/core/feature-registry.js +5 -3
  77. package/dist/core/fsx.js +7 -3
  78. package/dist/core/hooks-runtime/codex-commit-hooks-selftest.js +82 -0
  79. package/dist/core/hooks-runtime/context-graph-freshness-preflight.js +113 -0
  80. package/dist/core/hooks-runtime/hook-context.js +15 -2
  81. package/dist/core/hooks-runtime/managed-skill-generation-heal.js +46 -0
  82. package/dist/core/hooks-runtime/naruto-terminal-finalization.js +1 -0
  83. package/dist/core/hooks-runtime/official-subagent-lifecycle.js +1 -1
  84. package/dist/core/hooks-runtime/tool-output-quarantine.js +1 -1
  85. package/dist/core/hooks-runtime.js +33 -89
  86. package/dist/core/image-ux-review/callout-extraction.js +6 -0
  87. package/dist/core/image-ux-review/imagegen-adapter.js +64 -23
  88. package/dist/core/image-ux-review/real-callout-extractor.js +1 -0
  89. package/dist/core/image-ux-review.js +230 -12
  90. package/dist/core/imagegen/codex-lb-imagegen-target.js +21 -7
  91. package/dist/core/imagegen/imagegen-capability.js +10 -4
  92. package/dist/core/imagegen/require-imagegen.js +29 -5
  93. package/dist/core/init/skills.js +25 -13
  94. package/dist/core/init.js +1 -1
  95. package/dist/core/install/installed-package-smoke.js +157 -4
  96. package/dist/core/managed-assets/managed-assets-manifest.js +2 -1
  97. package/dist/core/managed-path-safety.js +8 -10
  98. package/dist/core/mcp-config/backup.js +2 -4
  99. package/dist/core/mcp-config/config-reader.js +2 -4
  100. package/dist/core/mcp-config/scope.js +3 -5
  101. package/dist/core/naruto/context-graph-advisor-scope.js +242 -0
  102. package/dist/core/naruto/context-graph-advisor.js +0 -0
  103. package/dist/core/perf/release-latency-slo.js +12 -6
  104. package/dist/core/pipeline-internals/runtime-core.js +78 -0
  105. package/dist/core/pipeline-internals/runtime-gates.js +5 -2
  106. package/dist/core/policy/latest-version-guidance.js +113 -0
  107. package/dist/core/ppt.js +8 -8
  108. package/dist/core/preflight/parallel-preflight-engine.js +1 -1
  109. package/dist/core/proof/auto-finalize.js +98 -6
  110. package/dist/core/proof/route-proof-gate.js +12 -1
  111. package/dist/core/proof/route-proof-policy.js +8 -8
  112. package/dist/core/release/gate-manifest.js +32 -1
  113. package/dist/core/release/npm-stage-contract.js +21 -0
  114. package/dist/core/release/package-size-budget.js +2 -2
  115. package/dist/core/release/release-gate-contract.js +5 -0
  116. package/dist/core/release/release-proof-io.js +1 -3
  117. package/dist/core/release/release-real-contract.js +5 -4
  118. package/dist/core/release/release-stamp-proof.js +1 -3
  119. package/dist/core/release/stage-publish.js +27 -0
  120. package/dist/core/remote/audit-idempotency.js +50 -18
  121. package/dist/core/remote/codex-public-activity.js +283 -0
  122. package/dist/core/remote/index.js +1 -0
  123. package/dist/core/remote/ssh-worker-client.js +4 -1
  124. package/dist/core/remote/worker.js +206 -27
  125. package/dist/core/research/research-adversarial-review.js +1 -3
  126. package/dist/core/research/research-claim-synthesizer.js +1 -3
  127. package/dist/core/research/research-falsification-runner.js +1 -3
  128. package/dist/core/research/research-source-evidence.js +1 -3
  129. package/dist/core/responses-stream.js +65 -19
  130. package/dist/core/routes/constants.js +1 -1
  131. package/dist/core/routes/design-policy.js +7 -2
  132. package/dist/core/routes/dollar-manifest-lite.js +2 -4
  133. package/dist/core/routes/evidence.js +1 -1
  134. package/dist/core/routes.js +30 -10
  135. package/dist/core/safety/command-contract/contracts.js +2 -0
  136. package/dist/core/safety/command-contract/types.js +1 -1
  137. package/dist/core/search/context-graph-seeds.js +205 -0
  138. package/dist/core/search/context.js +0 -0
  139. package/dist/core/search/provider.js +2 -2
  140. package/dist/core/secret-redaction.js +6 -0
  141. package/dist/core/subagents/naruto-help-contract.js +2 -1
  142. package/dist/core/subagents/naruto-host-credentials.js +161 -0
  143. package/dist/core/subagents/official-subagent-preparation.js +4 -1
  144. package/dist/core/subagents/official-subagent-prompt.js +44 -2
  145. package/dist/core/subagents/official-subagent-runner.js +26 -8
  146. package/dist/core/subagents/triwiki-attention.js +135 -123
  147. package/dist/core/telegram/bot-api.js +85 -10
  148. package/dist/core/telegram/center-snapshot.js +718 -0
  149. package/dist/core/telegram/config.js +158 -31
  150. package/dist/core/telegram/hub.js +288 -33
  151. package/dist/core/telegram/index.js +2 -0
  152. package/dist/core/telegram/ingress-spool.js +419 -0
  153. package/dist/core/telegram/ledgers.js +188 -29
  154. package/dist/core/telegram/messages.js +26 -6
  155. package/dist/core/telegram/owner-lock.js +10 -7
  156. package/dist/core/telegram/runtime-projection.js +38 -3
  157. package/dist/core/telegram/runtime.js +298 -63
  158. package/dist/core/telegram/service.js +137 -8
  159. package/dist/core/telegram/setup.js +291 -73
  160. package/dist/core/text/strings.js +3 -0
  161. package/dist/core/triwiki/code-pack.js +12 -120
  162. package/dist/core/triwiki/context-graph/compiler/cache-key.js +277 -0
  163. package/dist/core/triwiki/context-graph/compiler/extract.js +104 -0
  164. package/dist/core/triwiki/context-graph/compiler/freshness.js +108 -0
  165. package/dist/core/triwiki/context-graph/compiler/index.js +237 -0
  166. package/dist/core/triwiki/context-graph/compiler/merge.js +0 -0
  167. package/dist/core/triwiki/context-graph/compiler/serialize.js +115 -0
  168. package/dist/core/triwiki/context-graph/contracts.js +236 -0
  169. package/dist/core/triwiki/context-graph/extractors/code/builders.js +150 -0
  170. package/dist/core/triwiki/context-graph/extractors/code/declarations.js +161 -0
  171. package/dist/core/triwiki/context-graph/extractors/code/index.js +265 -0
  172. package/dist/core/triwiki/context-graph/extractors/code/inventory.js +237 -0
  173. package/dist/core/triwiki/context-graph/extractors/code/module-graph.js +252 -0
  174. package/dist/core/triwiki/context-graph/extractors/code/modules.js +79 -0
  175. package/dist/core/triwiki/context-graph/extractors/code/references.js +184 -0
  176. package/dist/core/triwiki/context-graph/extractors/code/selection.js +100 -0
  177. package/dist/core/triwiki/context-graph/extractors/code/ts-config.js +161 -0
  178. package/dist/core/triwiki/context-graph/extractors/code/types.js +18 -0
  179. package/dist/core/triwiki/context-graph/extractors/evidence/claims.js +289 -0
  180. package/dist/core/triwiki/context-graph/extractors/evidence/index.js +54 -0
  181. package/dist/core/triwiki/context-graph/extractors/evidence/proof-index.js +213 -0
  182. package/dist/core/triwiki/context-graph/extractors/evidence/proofs.js +266 -0
  183. package/dist/core/triwiki/context-graph/extractors/evidence/redaction.js +177 -0
  184. package/dist/core/triwiki/context-graph/extractors/evidence/shared.js +0 -0
  185. package/dist/core/triwiki/context-graph/extractors/evidence/sources.js +154 -0
  186. package/dist/core/triwiki/context-graph/extractors/index.js +7 -0
  187. package/dist/core/triwiki/context-graph/extractors/topology/commands.js +207 -0
  188. package/dist/core/triwiki/context-graph/extractors/topology/gate-edges.js +201 -0
  189. package/dist/core/triwiki/context-graph/extractors/topology/gates.js +182 -0
  190. package/dist/core/triwiki/context-graph/extractors/topology/globs.js +124 -0
  191. package/dist/core/triwiki/context-graph/extractors/topology/index.js +92 -0
  192. package/dist/core/triwiki/context-graph/extractors/topology/routes.js +135 -0
  193. package/dist/core/triwiki/context-graph/extractors/topology/shared.js +215 -0
  194. package/dist/core/triwiki/context-graph/graph-index.js +143 -0
  195. package/dist/core/triwiki/context-graph/ids.js +0 -0
  196. package/dist/core/triwiki/context-graph/lint/index.js +0 -0
  197. package/dist/core/triwiki/context-graph/lint/rules.js +232 -0
  198. package/dist/core/triwiki/context-graph/lint/warnings.js +93 -0
  199. package/dist/core/triwiki/context-graph/paths.js +116 -0
  200. package/dist/core/triwiki/context-graph/profiles.js +78 -0
  201. package/dist/core/triwiki/context-graph/projections/anchors.js +91 -0
  202. package/dist/core/triwiki/context-graph/projections/attention.js +58 -0
  203. package/dist/core/triwiki/context-graph/projections/code-pack-workspace.js +62 -0
  204. package/dist/core/triwiki/context-graph/projections/code-pack.js +144 -0
  205. package/dist/core/triwiki/context-graph/projections/index.js +7 -0
  206. package/dist/core/triwiki/context-graph/projections/module-view.js +140 -0
  207. package/dist/core/triwiki/context-graph/projections/node-summary.js +118 -0
  208. package/dist/core/triwiki/context-graph/projections/pack-contract.js +6 -0
  209. package/dist/core/triwiki/context-graph/query/explain.js +0 -0
  210. package/dist/core/triwiki/context-graph/query/index.js +178 -0
  211. package/dist/core/triwiki/context-graph/query/load.js +116 -0
  212. package/dist/core/triwiki/context-graph/query/pack.js +117 -0
  213. package/dist/core/triwiki/context-graph/query/rank.js +142 -0
  214. package/dist/core/triwiki/context-graph/query/ranking-config.js +64 -0
  215. package/dist/core/triwiki/context-graph/query/seeds.js +241 -0
  216. package/dist/core/triwiki/context-graph/query/snapshot-cache.js +72 -0
  217. package/dist/core/triwiki/context-graph/query/traverse.js +154 -0
  218. package/dist/core/triwiki/context-graph/query-types.js +29 -0
  219. package/dist/core/triwiki/context-graph/store/compile-lock.js +21 -0
  220. package/dist/core/triwiki/context-graph/store/event-log.js +48 -0
  221. package/dist/core/triwiki/context-graph/store/fragment-cache.js +85 -0
  222. package/dist/core/triwiki/context-graph/store/graph-status.js +85 -0
  223. package/dist/core/triwiki/context-graph/store/snapshot-store.js +155 -0
  224. package/dist/core/triwiki/triwiki-proof-bank-index-repair.js +115 -0
  225. package/dist/core/triwiki/triwiki-proof-bank-index-store.js +290 -0
  226. package/dist/core/triwiki/triwiki-proof-bank-index.js +126 -0
  227. package/dist/core/triwiki/triwiki-proof-bank.js +7 -1
  228. package/dist/core/verification/context-graph-affected.js +271 -0
  229. package/dist/core/version.js +1 -1
  230. package/dist/native/sks-menubar/Sources/AlertFactory.swift +11 -3
  231. package/dist/native/sks-menubar/Sources/AppDelegate.swift +9 -0
  232. package/dist/native/sks-menubar/Sources/ControlCenterWindowController.swift +25 -3
  233. package/dist/native/sks-menubar/Sources/ControlKit.swift +19 -8
  234. package/dist/native/sks-menubar/Sources/DiagnosticsViewController.swift +1 -2
  235. package/dist/native/sks-menubar/Sources/MCPServersViewController.swift +45 -17
  236. package/dist/native/sks-menubar/Sources/OverviewSummary.swift +162 -0
  237. package/dist/native/sks-menubar/Sources/OverviewViewController.swift +136 -190
  238. package/dist/native/sks-menubar/Sources/ProcessClient.swift +107 -11
  239. package/dist/native/sks-menubar/Sources/ProvidersMultiProvider.swift +5 -1
  240. package/dist/native/sks-menubar/Sources/ProvidersOpenRouter.swift +16 -4
  241. package/dist/native/sks-menubar/Sources/ProvidersRoleModels.swift +4 -1
  242. package/dist/native/sks-menubar/Sources/ProvidersRoutingTruth.swift +131 -0
  243. package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +288 -341
  244. package/dist/native/sks-menubar/Sources/RemoteTelegramCenterState.swift +447 -0
  245. package/dist/native/sks-menubar/Sources/RemoteTelegramRendering.swift +484 -0
  246. package/dist/native/sks-menubar/Sources/RemoteTelegramViewController.swift +364 -115
  247. package/dist/native/sks-menubar/Sources/SettingsViewController.swift +73 -12
  248. package/dist/native/sks-menubar/Sources/SidebarItem.swift +3 -3
  249. package/dist/native/sks-menubar/Sources/StatusItemController.swift +13 -13
  250. package/dist/native/sks-menubar/Sources/TelegramHubSupervisor.swift +469 -0
  251. package/dist/native/sks-menubar/Sources/UpdatesViewController.swift +1 -6
  252. package/dist/native/sks-menubar/Tests/AppRuntimeTestSupport.swift +12 -0
  253. package/dist/native/sks-menubar/Tests/NativeTelegramRegressionRunner.swift +55 -0
  254. package/dist/native/sks-menubar/Tests/ProcessClientTests.swift +33 -0
  255. package/dist/native/sks-menubar/Tests/ProvidersViewControllerTests.swift +142 -0
  256. package/dist/native/sks-menubar/Tests/RemoteTelegramCenterStateTests.swift +56 -0
  257. package/dist/native/sks-menubar/Tests/TelegramHubSupervisorTests.swift +55 -0
  258. package/dist/scripts/codex-lb-catalog-passthrough-check.js +120 -0
  259. package/dist/scripts/codex-lb-desktop-auth-invariant-check.js +169 -0
  260. package/dist/scripts/codex-lb-desktop-bridge-check.js +394 -0
  261. package/dist/scripts/codex-lb-desktop-bridge-latency-check.js +196 -0
  262. package/dist/scripts/codex-lb-desktop-capabilities-check.js +287 -0
  263. package/dist/scripts/codex-lb-desktop-real-evidence-check.js +123 -0
  264. package/dist/scripts/codex-lb-fast-mode-truth-check.js +10 -3
  265. package/dist/scripts/codex-lb-fast-ui-preservation-check.js +81 -40
  266. package/dist/scripts/codex-lb-gpt56-fast-profile-check.js +99 -37
  267. package/dist/scripts/context-graph-check.js +191 -0
  268. package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +1 -1
  269. package/dist/scripts/gate-policy-audit-check.js +1 -1
  270. package/dist/scripts/helper-dedup-check.js +0 -2
  271. package/dist/scripts/installed-package-smoke-check.js +4 -1
  272. package/dist/scripts/latest-version-guidance-check.js +74 -0
  273. package/dist/scripts/release-metadata-check.js +21 -4
  274. package/dist/scripts/release-real-check.js +7 -0
  275. package/dist/scripts/ux-review-imagegen-blackbox-check.js +25 -1
  276. package/dist/scripts/ux-review-run-wires-imagegen-check.js +9 -1
  277. package/package.json +8 -3
  278. package/release-gates.v2.json +176 -1
  279. package/runtime-required-scripts.json +30 -0
  280. package/schemas/triwiki/context-graph.schema.json +119 -0
  281. package/dist/cli/install-helpers-codex-lb-selftest-chain.js +0 -112
  282. package/dist/cli/install-helpers-codex-lb-selftest.js +0 -481
  283. package/dist/core/triwiki/code-index-scanner.js +0 -309
@@ -0,0 +1,419 @@
1
+ import { redactString } from '../secret-redaction.js';
2
+ import { nowIso, readJson, writeTextAtomic } from '../fsx.js';
3
+ import { withFileLock } from '../locks/file-lock.js';
4
+ export class TelegramIngressSpool {
5
+ file;
6
+ options;
7
+ maxPending;
8
+ maxCompleted;
9
+ maxDeadLetters;
10
+ maxAttempts;
11
+ identityFingerprint;
12
+ constructor(file, options) {
13
+ this.file = file;
14
+ this.options = options;
15
+ this.identityFingerprint = options.identityFingerprint ?? options.tokenFingerprint ?? '';
16
+ if (!/^sha256:[A-Za-z0-9._-]{3,128}$/.test(this.identityFingerprint)) {
17
+ throw new Error('telegram_ingress_identity_fingerprint_invalid');
18
+ }
19
+ this.maxPending = Math.max(16, options.maxPending ?? 512);
20
+ this.maxCompleted = Math.max(128, options.maxCompleted ?? 4096);
21
+ this.maxDeadLetters = Math.max(16, options.maxDeadLetters ?? 128);
22
+ this.maxAttempts = Math.max(1, options.maxAttempts ?? 3);
23
+ }
24
+ async admit(update) {
25
+ if (!Number.isSafeInteger(update.update_id) || update.update_id < 0) {
26
+ throw new Error('telegram_update_id_invalid');
27
+ }
28
+ return this.mutate((spool) => {
29
+ if (spool.completed.some((entry) => entry.update_id === update.update_id))
30
+ return 'completed';
31
+ if (spool.dead_letters.some((entry) => entry.update_id === update.update_id))
32
+ return 'dead_letter';
33
+ if (spool.pending.some((entry) => entry.update_id === update.update_id))
34
+ return 'pending';
35
+ if (spool.pending.length >= this.maxPending) {
36
+ throw new Error('telegram_ingress_spool_capacity_exceeded');
37
+ }
38
+ const at = nowIso();
39
+ spool.pending.push({
40
+ update_id: update.update_id,
41
+ update,
42
+ routed: null,
43
+ execution: emptyIngressExecution(),
44
+ attempts: 0,
45
+ admitted_at: at,
46
+ updated_at: at
47
+ });
48
+ spool.pending.sort((left, right) => left.update_id - right.update_id);
49
+ return 'admitted';
50
+ });
51
+ }
52
+ async pending() {
53
+ const spool = await this.read();
54
+ return spool.pending.map((entry) => structuredClone(normalizeIngressEntry(entry)));
55
+ }
56
+ async execution(updateId) {
57
+ assertIngressUpdateId(updateId);
58
+ const spool = await this.read();
59
+ const entry = spool.pending.find((candidate) => candidate.update_id === updateId);
60
+ return entry ? structuredClone(normalizeIngressEntry(entry).execution) : null;
61
+ }
62
+ async markRouted(updateId, routed) {
63
+ await this.mutate((spool) => {
64
+ const index = spool.pending.findIndex((entry) => entry.update_id === updateId);
65
+ if (index < 0)
66
+ throw new Error('telegram_ingress_entry_missing');
67
+ const current = spool.pending[index];
68
+ spool.pending[index] = {
69
+ ...normalizeIngressEntry(current),
70
+ routed,
71
+ updated_at: nowIso()
72
+ };
73
+ });
74
+ }
75
+ async markEffectDispatching(updateId) {
76
+ await this.updateExecution(updateId, (execution) => {
77
+ if (execution.effect_state === 'dispatching' || execution.effect_state === 'unknown') {
78
+ throw new Error('telegram_ingress_effect_delivery_ambiguous');
79
+ }
80
+ if (execution.effect_state === 'applied' || execution.effect_state === 'rejected')
81
+ return;
82
+ execution.effect_state = 'dispatching';
83
+ });
84
+ }
85
+ async markEffectOutcome(updateId, input) {
86
+ await this.updateExecution(updateId, (execution) => {
87
+ execution.effect_state = input.effectState;
88
+ execution.effect_receipt = boundedCheckpointText(input.effectReceipt, 240);
89
+ execution.final_text = boundedCheckpointText(input.finalText, 16_000);
90
+ execution.decision = input.decision;
91
+ execution.policy_reason = boundedCheckpointText(input.policyReason, 240);
92
+ execution.action_completion = input.actionCompletion;
93
+ });
94
+ }
95
+ async markTelegramDispatching(updateId) {
96
+ await this.updateExecution(updateId, (execution) => {
97
+ if (execution.telegram_delivery === 'delivered')
98
+ return;
99
+ if (execution.telegram_delivery === 'dispatching' || execution.telegram_delivery === 'unknown') {
100
+ throw new Error('telegram_ingress_telegram_delivery_ambiguous');
101
+ }
102
+ execution.telegram_delivery = 'dispatching';
103
+ execution.telegram_message_receipt = null;
104
+ });
105
+ }
106
+ async markTelegramDelivered(updateId, receipt) {
107
+ if (!Number.isInteger(receipt.message_id) || receipt.message_id < 1) {
108
+ throw new Error('telegram_ingress_message_receipt_invalid');
109
+ }
110
+ await this.updateExecution(updateId, (execution) => {
111
+ execution.telegram_delivery = 'delivered';
112
+ execution.telegram_message_receipt = {
113
+ method: boundedCheckpointText(receipt.method, 64) ?? 'sendMessage',
114
+ message_id: receipt.message_id
115
+ };
116
+ });
117
+ }
118
+ async markTelegramDeliveryFailure(updateId, delivery) {
119
+ await this.updateExecution(updateId, (execution) => {
120
+ execution.telegram_delivery = delivery;
121
+ execution.telegram_message_receipt = null;
122
+ });
123
+ }
124
+ async markAuditCompleted(updateId) {
125
+ await this.updateExecution(updateId, (execution) => {
126
+ execution.audit_state = 'completed';
127
+ });
128
+ }
129
+ async assertIdentityRotationSafe() {
130
+ const spool = await this.readRaw();
131
+ if (spool.pending.length > 0 || spool.dead_letters.length > 0) {
132
+ throw new Error('telegram_ingress_recovery_required_before_identity_change');
133
+ }
134
+ }
135
+ async resetForIdentity() {
136
+ await withFileLock({
137
+ lockPath: `${this.file}.lock`,
138
+ timeoutMs: 5_000,
139
+ staleMs: 30_000
140
+ }, async () => {
141
+ const raw = await this.readRaw();
142
+ if (raw.pending.length > 0 || raw.dead_letters.length > 0) {
143
+ throw new Error('telegram_ingress_recovery_required_before_identity_change');
144
+ }
145
+ await this.write(this.empty());
146
+ });
147
+ }
148
+ async markCompleted(updateId) {
149
+ await this.mutate((spool) => {
150
+ spool.pending = spool.pending.filter((entry) => entry.update_id !== updateId);
151
+ if (!spool.completed.some((entry) => entry.update_id === updateId)) {
152
+ spool.completed.push({ update_id: updateId, completed_at: nowIso() });
153
+ }
154
+ spool.completed = spool.completed
155
+ .sort((left, right) => right.update_id - left.update_id)
156
+ .slice(0, this.maxCompleted);
157
+ });
158
+ }
159
+ async markFailed(updateId, error) {
160
+ return this.mutate((spool) => {
161
+ const index = spool.pending.findIndex((entry) => entry.update_id === updateId);
162
+ if (index < 0)
163
+ return 'dead_letter';
164
+ const current = normalizeIngressEntry(spool.pending[index]);
165
+ const executionAmbiguous = current.execution.effect_state === 'dispatching'
166
+ || current.execution.effect_state === 'unknown'
167
+ || current.execution.telegram_delivery === 'dispatching'
168
+ || current.execution.telegram_delivery === 'unknown';
169
+ const attempts = current.attempts + 1;
170
+ if (!executionAmbiguous && attempts < this.maxAttempts) {
171
+ spool.pending[index] = {
172
+ ...current,
173
+ attempts,
174
+ updated_at: nowIso()
175
+ };
176
+ return 'pending';
177
+ }
178
+ spool.pending.splice(index, 1);
179
+ if (spool.dead_letters.length >= this.maxDeadLetters) {
180
+ throw new Error('telegram_ingress_dead_letter_capacity_exceeded');
181
+ }
182
+ const requeueSafe = current.execution.effect_state === 'not_started'
183
+ && ['not_started', 'not_dispatched'].includes(current.execution.telegram_delivery);
184
+ spool.dead_letters.push({
185
+ update_id: updateId,
186
+ failed_at: nowIso(),
187
+ error_code: boundedIngressError(error),
188
+ recovery: requeueSafe ? 'requeue_safe' : 'acknowledge_only',
189
+ effect_state: requeueSafe ? 'not_started' : 'ambiguous',
190
+ admitted_at: current.admitted_at,
191
+ routed_status: current.routed?.status ?? null,
192
+ update: requeueSafe ? current.update : null
193
+ });
194
+ spool.dead_letters = spool.dead_letters
195
+ .sort((left, right) => right.update_id - left.update_id);
196
+ return 'dead_letter';
197
+ });
198
+ }
199
+ async nextOffset() {
200
+ const spool = await this.read();
201
+ const ids = [
202
+ ...spool.pending.map((entry) => entry.update_id),
203
+ ...spool.completed.map((entry) => entry.update_id),
204
+ ...spool.dead_letters.map((entry) => entry.update_id)
205
+ ];
206
+ return ids.length ? Math.max(...ids) + 1 : 0;
207
+ }
208
+ async counts() {
209
+ const spool = await this.read();
210
+ return {
211
+ pending: spool.pending.length,
212
+ completed: spool.completed.length,
213
+ dead_letters: spool.dead_letters.length
214
+ };
215
+ }
216
+ async deadLetters(updateId) {
217
+ if (updateId !== undefined)
218
+ assertIngressUpdateId(updateId);
219
+ const spool = await this.read();
220
+ return spool.dead_letters
221
+ .filter((entry) => updateId === undefined || entry.update_id === updateId)
222
+ .map((entry) => summarizeDeadLetter(entry));
223
+ }
224
+ async requeueDeadLetter(updateId) {
225
+ assertIngressUpdateId(updateId);
226
+ return this.mutate((spool) => {
227
+ const index = spool.dead_letters.findIndex((entry) => entry.update_id === updateId);
228
+ if (index < 0)
229
+ throw new Error('telegram_ingress_dead_letter_not_found');
230
+ const deadLetter = spool.dead_letters[index];
231
+ if (!deadLetterRequeueAllowed(deadLetter) || !deadLetter.update) {
232
+ throw new Error('telegram_ingress_dead_letter_requeue_refused_effect_ambiguous');
233
+ }
234
+ if (spool.pending.some((entry) => entry.update_id === updateId)
235
+ || spool.completed.some((entry) => entry.update_id === updateId)) {
236
+ throw new Error('telegram_ingress_dead_letter_requeue_conflict');
237
+ }
238
+ if (spool.pending.length >= this.maxPending) {
239
+ throw new Error('telegram_ingress_spool_capacity_exceeded');
240
+ }
241
+ const at = nowIso();
242
+ spool.pending.push({
243
+ update_id: updateId,
244
+ update: structuredClone(deadLetter.update),
245
+ routed: null,
246
+ execution: emptyIngressExecution(),
247
+ attempts: 0,
248
+ admitted_at: deadLetter.admitted_at ?? at,
249
+ updated_at: at
250
+ });
251
+ spool.pending.sort((left, right) => left.update_id - right.update_id);
252
+ spool.dead_letters.splice(index, 1);
253
+ return summarizeDeadLetter(deadLetter);
254
+ });
255
+ }
256
+ async acknowledgeDeadLetters(updateIds) {
257
+ const selected = updateIds === undefined ? null : new Set(updateIds);
258
+ for (const updateId of selected ?? [])
259
+ assertIngressUpdateId(updateId);
260
+ return this.mutate((spool) => {
261
+ if (selected && selected.size > 0) {
262
+ const existingIds = new Set(spool.dead_letters.map((entry) => entry.update_id));
263
+ if ([...selected].some((updateId) => !existingIds.has(updateId))) {
264
+ throw new Error('telegram_ingress_dead_letter_not_found');
265
+ }
266
+ }
267
+ const acknowledgedEntries = selected === null
268
+ ? spool.dead_letters
269
+ : spool.dead_letters.filter((entry) => selected.has(entry.update_id));
270
+ const acknowledged = acknowledgedEntries.length;
271
+ if (acknowledged === 0)
272
+ return 0;
273
+ const completedAt = nowIso();
274
+ const completedIds = new Set(spool.completed.map((entry) => entry.update_id));
275
+ for (const deadLetter of acknowledgedEntries) {
276
+ if (!completedIds.has(deadLetter.update_id)) {
277
+ spool.completed.push({
278
+ update_id: deadLetter.update_id,
279
+ completed_at: completedAt
280
+ });
281
+ completedIds.add(deadLetter.update_id);
282
+ }
283
+ }
284
+ spool.completed = spool.completed
285
+ .sort((left, right) => right.update_id - left.update_id)
286
+ .slice(0, this.maxCompleted);
287
+ const acknowledgedIds = new Set(acknowledgedEntries.map((entry) => entry.update_id));
288
+ spool.dead_letters = spool.dead_letters.filter((entry) => !acknowledgedIds.has(entry.update_id));
289
+ return acknowledged;
290
+ });
291
+ }
292
+ async read() {
293
+ const spool = await this.readRaw();
294
+ const persistedIdentity = spool.identity_fingerprint ?? spool.token_fingerprint ?? null;
295
+ const legacyIdentityMatches = !spool.identity_fingerprint
296
+ && Boolean(this.options.legacyTokenFingerprint)
297
+ && spool.token_fingerprint === this.options.legacyTokenFingerprint;
298
+ if (persistedIdentity !== this.identityFingerprint && !legacyIdentityMatches) {
299
+ throw new Error('telegram_ingress_identity_mismatch:run_setup_before_starting_hub');
300
+ }
301
+ const { token_fingerprint: _legacyTokenFingerprint, ...current } = spool;
302
+ return {
303
+ ...current,
304
+ identity_fingerprint: this.identityFingerprint,
305
+ pending: spool.pending.map(normalizeIngressEntry)
306
+ };
307
+ }
308
+ async readRaw() {
309
+ const spool = await readJson(this.file, this.empty());
310
+ if (spool.schema !== 'sks.telegram-ingress-spool.v1'
311
+ || !Array.isArray(spool.pending)
312
+ || !Array.isArray(spool.completed)
313
+ || !Array.isArray(spool.dead_letters)) {
314
+ throw new Error('telegram_ingress_spool_schema_invalid');
315
+ }
316
+ return spool;
317
+ }
318
+ async mutate(fn) {
319
+ return withFileLock({
320
+ lockPath: `${this.file}.lock`,
321
+ timeoutMs: 5_000,
322
+ staleMs: 30_000
323
+ }, async () => {
324
+ const spool = await this.read();
325
+ const result = fn(spool);
326
+ await this.write(spool);
327
+ return result;
328
+ });
329
+ }
330
+ empty() {
331
+ return {
332
+ schema: 'sks.telegram-ingress-spool.v1',
333
+ identity_fingerprint: this.identityFingerprint,
334
+ pending: [],
335
+ completed: [],
336
+ dead_letters: []
337
+ };
338
+ }
339
+ async updateExecution(updateId, mutateExecution) {
340
+ assertIngressUpdateId(updateId);
341
+ await this.mutate((spool) => {
342
+ const index = spool.pending.findIndex((entry) => entry.update_id === updateId);
343
+ if (index < 0)
344
+ throw new Error('telegram_ingress_entry_missing');
345
+ const current = normalizeIngressEntry(spool.pending[index]);
346
+ const execution = structuredClone(current.execution);
347
+ mutateExecution(execution);
348
+ spool.pending[index] = {
349
+ ...current,
350
+ execution,
351
+ updated_at: nowIso()
352
+ };
353
+ });
354
+ }
355
+ async write(spool) {
356
+ await writeTextAtomic(this.file, `${JSON.stringify(spool, null, 2)}\n`, { mode: 0o600 });
357
+ }
358
+ }
359
+ function boundedIngressError(error) {
360
+ const message = error instanceof Error ? error.message : String(error || 'telegram_ingress_failed');
361
+ return redactString(message)
362
+ .replace(/(?:\/Users|\/home|\/private|\/Volumes|\/etc|\/root|[A-Za-z]:\\)[^\s]+/g, '[path-redacted]')
363
+ .replace(/[\r\n\t]/g, ' ')
364
+ .slice(0, 240);
365
+ }
366
+ function summarizeDeadLetter(entry) {
367
+ const requeueSafe = deadLetterRequeueAllowed(entry);
368
+ return {
369
+ schema: 'sks.telegram-ingress-dead-letter.v1',
370
+ update_id: entry.update_id,
371
+ failed_at: entry.failed_at,
372
+ error_code: boundedIngressError(entry.error_code),
373
+ recovery: requeueSafe ? 'requeue_safe' : 'acknowledge_only',
374
+ effect_state: requeueSafe ? 'not_started' : 'ambiguous',
375
+ routed_status: typeof entry.routed_status === 'string' ? entry.routed_status : null
376
+ };
377
+ }
378
+ function deadLetterRequeueAllowed(entry) {
379
+ return entry.recovery === 'requeue_safe'
380
+ && entry.effect_state === 'not_started'
381
+ && entry.update?.update_id === entry.update_id;
382
+ }
383
+ function assertIngressUpdateId(updateId) {
384
+ if (!Number.isSafeInteger(updateId) || updateId < 0) {
385
+ throw new Error('telegram_update_id_invalid');
386
+ }
387
+ }
388
+ function emptyIngressExecution() {
389
+ return {
390
+ effect_state: 'not_started',
391
+ effect_receipt: null,
392
+ final_text: null,
393
+ decision: null,
394
+ policy_reason: null,
395
+ action_completion: 'none',
396
+ telegram_delivery: 'not_started',
397
+ telegram_message_receipt: null,
398
+ audit_state: 'pending'
399
+ };
400
+ }
401
+ function normalizeIngressEntry(entry) {
402
+ return {
403
+ ...entry,
404
+ execution: entry.execution && typeof entry.execution === 'object'
405
+ ? {
406
+ ...emptyIngressExecution(),
407
+ ...entry.execution
408
+ }
409
+ : emptyIngressExecution()
410
+ };
411
+ }
412
+ function boundedCheckpointText(value, maxLength) {
413
+ if (value === null)
414
+ return null;
415
+ return redactString(value)
416
+ .replace(/(?:\/Users|\/home|\/private|\/Volumes|\/etc|\/root|[A-Za-z]:\\)[^\s]+/g, '[path-redacted]')
417
+ .replace(/[\r\n\t]/g, ' ')
418
+ .slice(0, maxLength);
419
+ }