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
@@ -2,23 +2,36 @@ import { REMOTE_COMMAND_SCHEMA, REMOTE_R2_APPROVAL_SCHEMA, REMOTE_WORKER_REQUEST
2
2
  import { nowIso, readJson, sha256, writeJsonAtomic } from '../fsx.js';
3
3
  import { TelegramActionBroker, TelegramAuditLedger, TelegramTopicRegistry } from './ledgers.js';
4
4
  import { TelegramBotApiError } from './bot-api.js';
5
+ import { telegramConfigPairing } from './config.js';
5
6
  import { TelegramHubRouter } from './hub.js';
7
+ import { TelegramIngressSpool } from './ingress-spool.js';
6
8
  import { TelegramMessageProjector, publicSafeText } from './messages.js';
7
- import { asRecord, boundedIdentifier, cardFromSnapshot, commandView, emptyProjection, pickerAction, requestId, routeKey, snapshotSummary, successSummary, topicName } from './runtime-projection.js';
9
+ import { asRecord, boundedIdentifier, cardFromSnapshot, commandView, emptyProjection, pickerAction, requestId, routeKey, snapshotSummary, failureSummary, successSummary, topicName } from './runtime-projection.js';
8
10
  export class TelegramHubRuntime {
9
11
  options;
10
12
  clients = new Map();
11
13
  now;
14
+ pairedChatId;
12
15
  constructor(options) {
13
16
  this.options = options;
14
17
  this.now = options.now ?? Date.now;
18
+ this.pairedChatId = telegramConfigPairing(options.config).chatId;
15
19
  }
16
20
  async initialize() {
17
21
  return this.syncSessions();
18
22
  }
19
23
  async processUpdate(update) {
20
- const routed = await this.options.router.handleUpdate(update);
21
- await this.acknowledgeCallback(update, routed);
24
+ const routed = await this.prepareUpdate(update);
25
+ await this.executePreparedUpdate(update, routed);
26
+ return routed;
27
+ }
28
+ async prepareUpdate(update, options = {}) {
29
+ await this.acknowledgeCallback(update);
30
+ return this.options.router.handleUpdate(update, {
31
+ skipIdempotency: options.durablyAdmitted === true
32
+ });
33
+ }
34
+ async executePreparedUpdate(update, routed) {
22
35
  try {
23
36
  if (routed.session_picker)
24
37
  await this.sendSessionPicker(update);
@@ -26,9 +39,12 @@ export class TelegramHubRuntime {
26
39
  await this.executeRoutedAction(update, routed);
27
40
  }
28
41
  catch (error) {
29
- await this.reportEffectFailure(update, routed.action, error);
42
+ if (this.options.ingress)
43
+ throw error;
44
+ const handled = await this.reportEffectFailure(update, routed.action, error);
45
+ if (!handled)
46
+ throw error;
30
47
  }
31
- return routed;
32
48
  }
33
49
  async tick() {
34
50
  const routes = await this.options.topics.list();
@@ -38,6 +54,7 @@ export class TelegramHubRuntime {
38
54
  let changed = false;
39
55
  for (const route of routes) {
40
56
  try {
57
+ this.assertCurrentRoute(route);
41
58
  const key = routeKey(route);
42
59
  const row = state.sessions[key] ?? emptyProjection();
43
60
  state.sessions[key] = row;
@@ -103,10 +120,12 @@ export class TelegramHubRuntime {
103
120
  });
104
121
  const snapshot = snapshotResponse.ok ? asRecord(snapshotResponse.data) : null;
105
122
  let route = await this.options.topics.findBySession(target.machine_id, target.project_id, sessionId);
123
+ if (route && route.chat_id !== this.pairedChatId)
124
+ route = null;
106
125
  if (!route) {
107
126
  let messageThreadId = 0;
108
127
  try {
109
- const topic = await this.options.projector.createSessionTopic(this.options.config.paired_chat_ids[0], topicName(target, sessionId, snapshot));
128
+ const topic = await this.options.projector.createSessionTopic(this.pairedChatId, topicName(target, sessionId, snapshot));
110
129
  messageThreadId = topic.message_thread_id;
111
130
  }
112
131
  catch (error) {
@@ -118,11 +137,12 @@ export class TelegramHubRuntime {
118
137
  machine_id: target.machine_id,
119
138
  project_id: target.project_id,
120
139
  session_id: sessionId,
121
- chat_id: this.options.config.paired_chat_ids[0],
140
+ chat_id: this.pairedChatId,
122
141
  message_thread_id: messageThreadId,
123
142
  pinned_message_id: null
124
143
  });
125
144
  }
145
+ this.assertCurrentRoute(route);
126
146
  if (snapshot) {
127
147
  const needsInitialProjection = route.pinned_message_id === null || !state.sessions[routeKey(route)];
128
148
  await this.projectSnapshot(route, snapshot, state, needsInitialProjection);
@@ -146,53 +166,146 @@ export class TelegramHubRuntime {
146
166
  const route = await this.options.topics.findBySession(action.machine_id, action.project_id, action.session_id);
147
167
  if (!route)
148
168
  throw new Error('telegram_action_route_missing');
169
+ this.assertCurrentRoute(route);
170
+ if (route.generation !== action.route_generation) {
171
+ throw new Error('telegram_action_route_generation_stale');
172
+ }
149
173
  if (action.prompt === 'pick') {
150
- await this.options.projector.sendFinal({ route, text: 'Session selected. Use this topic for typed SKS controls.' });
174
+ const text = 'Session selected. Use this topic for typed SKS controls.';
175
+ await this.checkpointEffectless(update.update_id, text, action.status === 'claimed' ? 'resolved' : 'none');
176
+ await this.sendTelegramOnce(update.update_id, () => (this.options.projector.sendFinal({ route, text })));
151
177
  if (action.status === 'claimed')
152
178
  await this.options.actions.complete(action.action_id, 'resolved');
179
+ await this.options.ingress?.markAuditCompleted(update.update_id);
153
180
  return;
154
181
  }
155
182
  if (routed.status === 'approval_required' && routed.callback_data) {
156
- const messageId = await this.options.projector.sendApproval(route, routed.callback_data);
157
- await this.options.projector.setReaction(route, messageId, 'needs_input');
183
+ await this.checkpointEffectless(update.update_id, 'Cancel approval requested.', 'none');
184
+ const sent = await this.sendTelegramOnce(update.update_id, async () => {
185
+ const messageId = await this.options.projector.sendApproval(route, routed.callback_data);
186
+ return { method: 'sendMessage', message_id: messageId };
187
+ });
188
+ if (sent.newlyDelivered) {
189
+ await this.options.projector.setReaction(route, sent.receipt.message_id, 'needs_input');
190
+ }
191
+ await this.options.ingress?.markAuditCompleted(update.update_id);
158
192
  return;
159
193
  }
160
194
  if (action.kind === 'input' && action.prompt === '/input') {
161
- await this.options.projector.requestInput({ route, prompt: 'Reply to this message with the exact Codex follow-up input.', placeholder: 'Send input to this session' });
195
+ const text = 'Reply to this message with the exact Codex follow-up input.';
196
+ await this.checkpointEffectless(update.update_id, text, 'none');
197
+ await this.sendTelegramOnce(update.update_id, async () => ({
198
+ method: 'sendMessage',
199
+ message_id: await this.options.projector.requestInput({
200
+ route,
201
+ prompt: text,
202
+ placeholder: 'Send input to this session'
203
+ })
204
+ }));
205
+ await this.options.ingress?.markAuditCompleted(update.update_id);
162
206
  return;
163
207
  }
164
- const response = await this.dispatch(route, action);
165
- const data = asRecord(response.receipt?.result ?? response.data);
166
- if (response.ok) {
167
- await this.options.projector.sendFinal({ route, text: successSummary(action, data) });
168
- if (commandView(action.prompt) === 'artifacts')
169
- await this.options.projector.sendArtifactManifest({ route, artifacts: Array.isArray(data?.artifacts) ? data.artifacts : [] });
170
- if (data?.schema === 'sks.remote-session-snapshot.v1') {
171
- const state = await this.readProjectionState();
172
- await this.projectSnapshot(route, data, state, true);
173
- await writeJsonAtomic(this.options.projectionStatePath, state);
208
+ let execution = await this.options.ingress?.execution(update.update_id) ?? null;
209
+ let outcome = checkpointedOutcome(execution);
210
+ let responseData = null;
211
+ if (!outcome) {
212
+ await this.reactToUpdate(update, route, 'running');
213
+ await this.options.ingress?.markEffectDispatching(update.update_id);
214
+ try {
215
+ const response = await this.dispatch(route, action);
216
+ responseData = asRecord(response.receipt?.result ?? response.data);
217
+ const errorDelivery = response.error?.delivery;
218
+ outcome = {
219
+ effectState: response.ok
220
+ ? 'applied'
221
+ : errorDelivery === 'not_dispatched'
222
+ ? 'rejected'
223
+ : 'unknown',
224
+ effectReceipt: response.receipt
225
+ ? `${response.receipt.command_id}:${response.receipt.status}`
226
+ : null,
227
+ finalText: response.ok
228
+ ? successSummary(action, responseData)
229
+ : failureSummary(publicSafeText(response.error?.code ?? 'remote_command_failed'), errorDelivery),
230
+ decision: response.ok ? 'accepted' : 'rejected',
231
+ policyReason: response.ok
232
+ ? 'effect_completed'
233
+ : publicSafeText(response.error?.code ?? 'effect_failed').slice(0, 160),
234
+ actionCompletion: action.status === 'claimed'
235
+ ? response.ok || errorDelivery !== 'not_dispatched'
236
+ ? 'resolved'
237
+ : 'cancelled'
238
+ : 'none'
239
+ };
174
240
  }
175
- await this.reactToUpdate(update, route, action.kind === 'cancel' ? 'blocked' : action.kind === 'verify' ? 'verified' : 'observed');
176
- if (action.status === 'claimed')
177
- await this.options.actions.complete(action.action_id, 'resolved');
241
+ catch (error) {
242
+ const delivery = error instanceof RemoteSshClientError ? error.delivery : 'unknown';
243
+ const code = error instanceof RemoteSshClientError
244
+ ? `${error.code}:${delivery}`
245
+ : error instanceof Error
246
+ ? publicSafeText(error.message || 'telegram_effect_failed').slice(0, 160)
247
+ : 'telegram_effect_failed';
248
+ outcome = {
249
+ effectState: delivery === 'not_dispatched' ? 'rejected' : 'unknown',
250
+ effectReceipt: null,
251
+ finalText: failureSummary(code, delivery),
252
+ decision: 'rejected',
253
+ policyReason: code,
254
+ actionCompletion: action.status === 'claimed'
255
+ ? delivery === 'not_dispatched' ? 'cancelled' : 'resolved'
256
+ : 'none'
257
+ };
258
+ }
259
+ await this.options.ingress?.markEffectOutcome(update.update_id, outcome);
178
260
  }
179
- else {
180
- const code = publicSafeText(response.error?.code ?? 'remote_command_failed');
181
- await this.options.projector.sendFinal({ route, text: `Remote action blocked: ${code}` });
182
- await this.reactToUpdate(update, route, 'blocked');
183
- if (action.status === 'claimed')
184
- await this.options.actions.complete(action.action_id, 'cancelled');
261
+ const sent = await this.sendTelegramOnce(update.update_id, () => (this.options.projector.sendFinal({ route, text: outcome.finalText })));
262
+ if (sent.newlyDelivered) {
263
+ if (outcome.decision === 'accepted' && responseData) {
264
+ if (commandView(action.prompt) === 'artifacts') {
265
+ await this.options.projector.sendArtifactManifest({
266
+ route,
267
+ artifacts: Array.isArray(responseData.artifacts) ? responseData.artifacts : []
268
+ }).catch(() => undefined);
269
+ }
270
+ if (responseData.schema === 'sks.remote-session-snapshot.v1') {
271
+ try {
272
+ const state = await this.readProjectionState();
273
+ await this.projectSnapshot(route, responseData, state, true);
274
+ await writeJsonAtomic(this.options.projectionStatePath, state);
275
+ }
276
+ catch {
277
+ }
278
+ }
279
+ }
280
+ await this.reactToUpdate(update, route, outcome.decision === 'rejected'
281
+ ? 'blocked'
282
+ : action.kind === 'cancel'
283
+ ? 'blocked'
284
+ : action.kind === 'verify' || action.kind === 'input'
285
+ ? 'verified'
286
+ : 'observed');
287
+ }
288
+ if (action.status === 'claimed' && outcome.actionCompletion !== 'none') {
289
+ await this.options.actions.complete(action.action_id, outcome.actionCompletion);
290
+ }
291
+ execution = await this.options.ingress?.execution(update.update_id) ?? execution;
292
+ if (execution?.audit_state !== 'completed') {
293
+ await this.options.audit.record({
294
+ update_id: update.update_id,
295
+ chat_id: route.chat_id,
296
+ topic_id: route.message_thread_id,
297
+ action_alias: update.callback_query?.data?.startsWith('cb:')
298
+ ? update.callback_query.data.slice(3)
299
+ : null,
300
+ command_kind: action.kind,
301
+ decision: outcome.decision,
302
+ policy_reason: outcome.policyReason,
303
+ effect_receipt: outcome.effectReceipt,
304
+ telegram_delivery: 'delivered',
305
+ telegram_delivery_receipt: `${sent.receipt.method}:${sent.receipt.message_id}`
306
+ });
307
+ await this.options.ingress?.markAuditCompleted(update.update_id);
185
308
  }
186
- await this.options.audit.record({
187
- update_id: update.update_id,
188
- chat_id: route.chat_id,
189
- topic_id: route.message_thread_id,
190
- action_alias: update.callback_query?.data?.startsWith('cb:') ? update.callback_query.data.slice(3) : null,
191
- command_kind: action.kind,
192
- decision: response.ok ? 'accepted' : 'rejected',
193
- policy_reason: response.ok ? 'effect_completed' : response.error?.code ?? 'effect_failed',
194
- effect_receipt: response.receipt ? `${response.receipt.command_id}:${response.receipt.status}` : null
195
- });
196
309
  }
197
310
  async dispatch(route, action) {
198
311
  if (action.risk === 'R3')
@@ -260,40 +373,126 @@ export class TelegramHubRuntime {
260
373
  const chatId = update.message?.chat.id ?? update.callback_query?.message?.chat.id;
261
374
  if (chatId === undefined)
262
375
  return;
376
+ if (String(chatId) !== this.pairedChatId)
377
+ throw new Error('telegram_route_identity_mismatch');
263
378
  const routes = (await this.options.topics.list()).filter((route) => route.chat_id === String(chatId)).slice(0, 24);
379
+ for (const route of routes)
380
+ this.assertCurrentRoute(route);
264
381
  if (!routes.length) {
265
- await this.options.projector.sendFlat({ chatId: String(chatId), text: 'No registered SKS session topics are available. Run hub sync after the remote session index is configured.' });
382
+ const text = 'No registered SKS session topics are available. Run hub sync after the remote session index is configured.';
383
+ await this.checkpointEffectless(update.update_id, text, 'none');
384
+ await this.sendTelegramOnce(update.update_id, async () => ({
385
+ method: 'sendMessage',
386
+ message_id: await this.options.projector.sendFlat({ chatId: String(chatId), text })
387
+ }));
388
+ await this.options.ingress?.markAuditCompleted(update.update_id);
266
389
  return;
267
390
  }
391
+ const createdActions = await this.options.actions.createMany(routes.map((route) => ({
392
+ action: pickerAction(route, this.now(), update.update_id),
393
+ route: { chat_id: route.chat_id, message_thread_id: 0 }
394
+ })));
268
395
  const buttons = [];
269
- for (const route of routes) {
270
- const action = pickerAction(route, this.now());
271
- const created = await this.options.actions.create(action, { chat_id: route.chat_id, message_thread_id: 0 });
396
+ for (let index = 0; index < routes.length; index += 1) {
397
+ const route = routes[index];
398
+ const created = createdActions[index];
272
399
  buttons.push([{ text: `${route.machine_id} · ${route.project_id} · ${route.session_id}`.slice(0, 56), callback_data: created.callback_data }]);
273
400
  }
274
- await this.options.projector.sendFlat({
275
- chatId: String(chatId),
276
- text: 'Choose a session. The bot will post into the exact private topic; subsequent controls must stay in that topic.',
277
- replyMarkup: { inline_keyboard: buttons }
401
+ const text = 'Choose a session. The bot will post into the exact private topic; subsequent controls must stay in that topic.';
402
+ await this.checkpointEffectless(update.update_id, text, 'none');
403
+ await this.sendTelegramOnce(update.update_id, async () => ({
404
+ method: 'sendMessage',
405
+ message_id: await this.options.projector.sendFlat({
406
+ chatId: String(chatId),
407
+ text,
408
+ replyMarkup: { inline_keyboard: buttons }
409
+ })
410
+ }));
411
+ await this.options.ingress?.markAuditCompleted(update.update_id);
412
+ }
413
+ async checkpointEffectless(updateId, finalText, actionCompletion) {
414
+ const execution = await this.options.ingress?.execution(updateId) ?? null;
415
+ if (checkpointedOutcome(execution))
416
+ return;
417
+ await this.options.ingress?.markEffectOutcome(updateId, {
418
+ effectState: 'applied',
419
+ effectReceipt: null,
420
+ finalText,
421
+ decision: 'accepted',
422
+ policyReason: 'telegram_projection_prepared',
423
+ actionCompletion
278
424
  });
279
425
  }
280
- async acknowledgeCallback(update, routed) {
426
+ async sendTelegramOnce(updateId, send) {
427
+ const ingress = this.options.ingress;
428
+ if (!ingress)
429
+ return { receipt: await send(), newlyDelivered: true };
430
+ const execution = await ingress.execution(updateId);
431
+ if (!execution)
432
+ throw new Error('telegram_ingress_entry_missing');
433
+ if (execution.telegram_delivery === 'delivered') {
434
+ if (!execution.telegram_message_receipt) {
435
+ throw new Error('telegram_ingress_message_receipt_missing');
436
+ }
437
+ return {
438
+ receipt: execution.telegram_message_receipt,
439
+ newlyDelivered: false
440
+ };
441
+ }
442
+ if (execution.telegram_delivery === 'dispatching'
443
+ || execution.telegram_delivery === 'unknown') {
444
+ throw new Error('telegram_ingress_telegram_delivery_ambiguous');
445
+ }
446
+ await ingress.markTelegramDispatching(updateId);
447
+ try {
448
+ const receipt = await send();
449
+ await ingress.markTelegramDelivered(updateId, receipt);
450
+ return { receipt, newlyDelivered: true };
451
+ }
452
+ catch (error) {
453
+ await ingress.markTelegramDeliveryFailure(updateId, telegramDeliveryFromError(error));
454
+ throw error;
455
+ }
456
+ }
457
+ assertCurrentRoute(route) {
458
+ if (route.chat_id !== this.pairedChatId) {
459
+ throw new Error('telegram_route_identity_mismatch');
460
+ }
461
+ }
462
+ async acknowledgeCallback(update) {
281
463
  const id = update.callback_query?.id;
282
464
  if (!id)
283
465
  return;
284
- const text = routed.ok
285
- ? routed.status === 'claimed' ? 'Approved; applying exact scope.' : 'Request accepted.'
286
- : routed.status === 'already_resolved' ? 'Already resolved.' : `Request rejected: ${routed.status}`;
287
- await this.options.projector.answerCallbackQuery(id, text, false).catch(() => undefined);
466
+ await this.options.projector.answerCallbackQuery(id, 'Request received. SKS will validate and apply the exact linked scope.', false).catch(() => undefined);
288
467
  }
289
468
  async reportEffectFailure(update, action, error) {
290
469
  if (!action)
291
- return;
470
+ return false;
292
471
  const route = await this.options.topics.findBySession(action.machine_id, action.project_id, action.session_id);
293
472
  if (!route)
294
- return;
295
- const code = error instanceof RemoteSshClientError ? `${error.code}:${error.delivery}` : 'telegram_effect_failed';
296
- await this.options.projector.sendFinal({ route, text: `Remote action blocked: ${publicSafeText(code)}` }).catch(() => undefined);
473
+ return false;
474
+ const code = error instanceof RemoteSshClientError
475
+ ? `${error.code}:${error.delivery}`
476
+ : error instanceof Error
477
+ ? publicSafeText(error.message || 'telegram_effect_failed').slice(0, 160)
478
+ : 'telegram_effect_failed';
479
+ if (action.status === 'claimed') {
480
+ const appliedOrUnknown = error instanceof RemoteSshClientError && error.delivery !== 'not_dispatched';
481
+ await this.options.actions.complete(action.action_id, appliedOrUnknown ? 'resolved' : 'cancelled');
482
+ }
483
+ let telegramDelivery = 'not_dispatched';
484
+ let telegramDeliveryReceipt = null;
485
+ try {
486
+ const final = await this.options.projector.sendFinal({
487
+ route,
488
+ text: `Remote action blocked: ${publicSafeText(code)}`
489
+ });
490
+ telegramDelivery = 'delivered';
491
+ telegramDeliveryReceipt = `${final.method}:${final.message_id}`;
492
+ }
493
+ catch (deliveryError) {
494
+ telegramDelivery = telegramDeliveryFromError(deliveryError);
495
+ }
297
496
  await this.options.audit.record({
298
497
  update_id: update.update_id,
299
498
  chat_id: route.chat_id,
@@ -302,13 +501,15 @@ export class TelegramHubRuntime {
302
501
  command_kind: action.kind,
303
502
  decision: 'rejected',
304
503
  policy_reason: code,
305
- effect_receipt: null
504
+ effect_receipt: null,
505
+ telegram_delivery: telegramDelivery,
506
+ telegram_delivery_receipt: telegramDeliveryReceipt
306
507
  });
307
- if (action.status === 'claimed')
308
- await this.options.actions.complete(action.action_id, 'cancelled').catch(() => undefined);
508
+ return true;
309
509
  }
310
510
  async projectSnapshot(routeInput, snapshot, state, force = false) {
311
511
  let route = routeInput;
512
+ this.assertCurrentRoute(route);
312
513
  const key = routeKey(route);
313
514
  const projection = state.sessions[key] ?? emptyProjection();
314
515
  const digest = sha256(JSON.stringify(snapshot));
@@ -334,14 +535,28 @@ export class TelegramHubRuntime {
334
535
  projection.snapshot_digest = digest;
335
536
  if (draft.message_id)
336
537
  projection.draft_message_id = draft.message_id;
337
- if (snapshot.completion_verified === true && projection.final_generation !== route.generation) {
338
- await this.options.projector.sendFinal({ route, text: `Verified terminal session: ${route.session_id}. Machine gates and trust evidence are complete.` });
339
- projection.final_generation = route.generation;
538
+ if (snapshot.completion_verified === true
539
+ && projection.final_generation !== route.generation
540
+ && projection.final_generation_unknown !== route.generation) {
541
+ try {
542
+ await this.options.projector.sendFinal({
543
+ route,
544
+ text: `Verified terminal session: ${route.session_id}. Machine gates and trust evidence are complete.`
545
+ });
546
+ projection.final_generation = route.generation;
547
+ projection.final_generation_unknown = null;
548
+ }
549
+ catch (error) {
550
+ if (telegramDeliveryFromError(error) !== 'unknown')
551
+ throw error;
552
+ projection.final_generation_unknown = route.generation;
553
+ }
340
554
  }
341
555
  state.sessions[key] = projection;
342
556
  return true;
343
557
  }
344
558
  clientForRoute(route) {
559
+ this.assertCurrentRoute(route);
345
560
  return this.clientForTarget(findRemoteSessionTarget(this.options.sessionIndex, route.machine_id, route.project_id));
346
561
  }
347
562
  clientForTarget(target) {
@@ -376,3 +591,23 @@ export class TelegramHubRuntime {
376
591
  : { schema: 'sks.telegram-projection-state.v1', sessions: {} };
377
592
  }
378
593
  }
594
+ function telegramDeliveryFromError(error) {
595
+ return error instanceof TelegramBotApiError ? error.delivery : 'unknown';
596
+ }
597
+ function checkpointedOutcome(execution) {
598
+ if (!execution
599
+ || !['applied', 'rejected', 'unknown'].includes(execution.effect_state)
600
+ || !execution.final_text
601
+ || !execution.decision
602
+ || !execution.policy_reason) {
603
+ return null;
604
+ }
605
+ return {
606
+ effectState: execution.effect_state,
607
+ effectReceipt: execution.effect_receipt,
608
+ finalText: execution.final_text,
609
+ decision: execution.decision,
610
+ policyReason: execution.policy_reason,
611
+ actionCompletion: execution.action_completion
612
+ };
613
+ }