instar 0.28.76 → 0.28.78

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 (448) hide show
  1. package/dashboard/index.html +486 -0
  2. package/dist/cli.js +5 -8
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/discovery.d.ts.map +1 -1
  5. package/dist/commands/discovery.js +2 -2
  6. package/dist/commands/discovery.js.map +1 -1
  7. package/dist/commands/init.d.ts.map +1 -1
  8. package/dist/commands/init.js +22 -4
  9. package/dist/commands/init.js.map +1 -1
  10. package/dist/commands/job.d.ts.map +1 -1
  11. package/dist/commands/job.js +2 -2
  12. package/dist/commands/job.js.map +1 -1
  13. package/dist/commands/ledgerCleanup.d.ts.map +1 -1
  14. package/dist/commands/ledgerCleanup.js +2 -2
  15. package/dist/commands/ledgerCleanup.js.map +1 -1
  16. package/dist/commands/listener.d.ts.map +1 -1
  17. package/dist/commands/listener.js +7 -12
  18. package/dist/commands/listener.js.map +1 -1
  19. package/dist/commands/nuke.d.ts.map +1 -1
  20. package/dist/commands/nuke.js +11 -21
  21. package/dist/commands/nuke.js.map +1 -1
  22. package/dist/commands/server.d.ts.map +1 -1
  23. package/dist/commands/server.js +79 -5
  24. package/dist/commands/server.js.map +1 -1
  25. package/dist/commands/setup.d.ts.map +1 -1
  26. package/dist/commands/setup.js +11 -15
  27. package/dist/commands/setup.js.map +1 -1
  28. package/dist/commands/slack-cli.d.ts.map +1 -1
  29. package/dist/commands/slack-cli.js +5 -8
  30. package/dist/commands/slack-cli.js.map +1 -1
  31. package/dist/commands/whatsapp.d.ts.map +1 -1
  32. package/dist/commands/whatsapp.js +2 -2
  33. package/dist/commands/whatsapp.js.map +1 -1
  34. package/dist/commands/worktree.d.ts.map +1 -1
  35. package/dist/commands/worktree.js +2 -2
  36. package/dist/commands/worktree.js.map +1 -1
  37. package/dist/core/AgentConnector.d.ts.map +1 -1
  38. package/dist/core/AgentConnector.js +9 -10
  39. package/dist/core/AgentConnector.js.map +1 -1
  40. package/dist/core/AgentRegistry.d.ts.map +1 -1
  41. package/dist/core/AgentRegistry.js +3 -4
  42. package/dist/core/AgentRegistry.js.map +1 -1
  43. package/dist/core/AutoDispatcher.d.ts.map +1 -1
  44. package/dist/core/AutoDispatcher.js +2 -2
  45. package/dist/core/AutoDispatcher.js.map +1 -1
  46. package/dist/core/AutoUpdater.d.ts.map +1 -1
  47. package/dist/core/AutoUpdater.js +2 -2
  48. package/dist/core/AutoUpdater.js.map +1 -1
  49. package/dist/core/AutonomousEvolution.d.ts.map +1 -1
  50. package/dist/core/AutonomousEvolution.js +2 -2
  51. package/dist/core/AutonomousEvolution.js.map +1 -1
  52. package/dist/core/BackupManager.d.ts.map +1 -1
  53. package/dist/core/BackupManager.js +2 -2
  54. package/dist/core/BackupManager.js.map +1 -1
  55. package/dist/core/BranchManager.d.ts.map +1 -1
  56. package/dist/core/BranchManager.js +3 -3
  57. package/dist/core/BranchManager.js.map +1 -1
  58. package/dist/core/CaffeinateManager.d.ts.map +1 -1
  59. package/dist/core/CaffeinateManager.js +2 -2
  60. package/dist/core/CaffeinateManager.js.map +1 -1
  61. package/dist/core/DeferredDispatchTracker.d.ts.map +1 -1
  62. package/dist/core/DeferredDispatchTracker.js +2 -2
  63. package/dist/core/DeferredDispatchTracker.js.map +1 -1
  64. package/dist/core/DispatchManager.d.ts.map +1 -1
  65. package/dist/core/DispatchManager.js +3 -4
  66. package/dist/core/DispatchManager.js.map +1 -1
  67. package/dist/core/EvolutionManager.d.ts.map +1 -1
  68. package/dist/core/EvolutionManager.js +2 -2
  69. package/dist/core/EvolutionManager.js.map +1 -1
  70. package/dist/core/ExecutionJournal.d.ts.map +1 -1
  71. package/dist/core/ExecutionJournal.js +2 -2
  72. package/dist/core/ExecutionJournal.js.map +1 -1
  73. package/dist/core/FeedbackManager.d.ts.map +1 -1
  74. package/dist/core/FeedbackManager.js +2 -2
  75. package/dist/core/FeedbackManager.js.map +1 -1
  76. package/dist/core/FileClassifier.d.ts.map +1 -1
  77. package/dist/core/FileClassifier.js +8 -17
  78. package/dist/core/FileClassifier.js.map +1 -1
  79. package/dist/core/ForegroundRestartWatcher.d.ts.map +1 -1
  80. package/dist/core/ForegroundRestartWatcher.js +3 -4
  81. package/dist/core/ForegroundRestartWatcher.js.map +1 -1
  82. package/dist/core/GitStateManager.d.ts.map +1 -1
  83. package/dist/core/GitStateManager.js +3 -12
  84. package/dist/core/GitStateManager.js.map +1 -1
  85. package/dist/core/GitSync.d.ts.map +1 -1
  86. package/dist/core/GitSync.js +6 -6
  87. package/dist/core/GitSync.js.map +1 -1
  88. package/dist/core/GlobalInstallCleanup.d.ts.map +1 -1
  89. package/dist/core/GlobalInstallCleanup.js +3 -4
  90. package/dist/core/GlobalInstallCleanup.js.map +1 -1
  91. package/dist/core/GlobalSecretStore.d.ts.map +1 -1
  92. package/dist/core/GlobalSecretStore.js +3 -4
  93. package/dist/core/GlobalSecretStore.js.map +1 -1
  94. package/dist/core/HandoffManager.d.ts.map +1 -1
  95. package/dist/core/HandoffManager.js +5 -5
  96. package/dist/core/HandoffManager.js.map +1 -1
  97. package/dist/core/JargonDetector.d.ts +28 -0
  98. package/dist/core/JargonDetector.d.ts.map +1 -0
  99. package/dist/core/JargonDetector.js +59 -0
  100. package/dist/core/JargonDetector.js.map +1 -0
  101. package/dist/core/LedgerSessionRegistry.d.ts.map +1 -1
  102. package/dist/core/LedgerSessionRegistry.js +2 -2
  103. package/dist/core/LedgerSessionRegistry.js.map +1 -1
  104. package/dist/core/MachineIdentity.d.ts.map +1 -1
  105. package/dist/core/MachineIdentity.js +2 -2
  106. package/dist/core/MachineIdentity.js.map +1 -1
  107. package/dist/core/MessagingToneGate.d.ts +42 -5
  108. package/dist/core/MessagingToneGate.d.ts.map +1 -1
  109. package/dist/core/MessagingToneGate.js +40 -6
  110. package/dist/core/MessagingToneGate.js.map +1 -1
  111. package/dist/core/ParallelDevWiring.d.ts.map +1 -1
  112. package/dist/core/ParallelDevWiring.js +3 -6
  113. package/dist/core/ParallelDevWiring.js.map +1 -1
  114. package/dist/core/PostUpdateMigrator.d.ts +26 -0
  115. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  116. package/dist/core/PostUpdateMigrator.js +249 -46
  117. package/dist/core/PostUpdateMigrator.js.map +1 -1
  118. package/dist/core/ProjectMapper.d.ts.map +1 -1
  119. package/dist/core/ProjectMapper.js +5 -11
  120. package/dist/core/ProjectMapper.js.map +1 -1
  121. package/dist/core/RelationshipManager.d.ts.map +1 -1
  122. package/dist/core/RelationshipManager.js +4 -5
  123. package/dist/core/RelationshipManager.js.map +1 -1
  124. package/dist/core/SafeGitExecutor.d.ts +11 -5
  125. package/dist/core/SafeGitExecutor.d.ts.map +1 -1
  126. package/dist/core/SafeGitExecutor.js +87 -1
  127. package/dist/core/SafeGitExecutor.js.map +1 -1
  128. package/dist/core/ScopeVerifier.d.ts.map +1 -1
  129. package/dist/core/ScopeVerifier.js +3 -6
  130. package/dist/core/ScopeVerifier.js.map +1 -1
  131. package/dist/core/SecretStore.d.ts.map +1 -1
  132. package/dist/core/SecretStore.js +2 -2
  133. package/dist/core/SecretStore.js.map +1 -1
  134. package/dist/core/SharedStateLedger.d.ts.map +1 -1
  135. package/dist/core/SharedStateLedger.js +2 -2
  136. package/dist/core/SharedStateLedger.js.map +1 -1
  137. package/dist/core/SoulManager.d.ts.map +1 -1
  138. package/dist/core/SoulManager.js +3 -4
  139. package/dist/core/SoulManager.js.map +1 -1
  140. package/dist/core/StateManager.d.ts.map +1 -1
  141. package/dist/core/StateManager.js +4 -6
  142. package/dist/core/StateManager.js.map +1 -1
  143. package/dist/core/SyncOrchestrator.d.ts.map +1 -1
  144. package/dist/core/SyncOrchestrator.js +6 -7
  145. package/dist/core/SyncOrchestrator.js.map +1 -1
  146. package/dist/core/UpdateChecker.d.ts.map +1 -1
  147. package/dist/core/UpdateChecker.js +3 -4
  148. package/dist/core/UpdateChecker.js.map +1 -1
  149. package/dist/core/UpgradeGuideProcessor.d.ts.map +1 -1
  150. package/dist/core/UpgradeGuideProcessor.js +3 -4
  151. package/dist/core/UpgradeGuideProcessor.js.map +1 -1
  152. package/dist/core/WorktreeManager.d.ts.map +1 -1
  153. package/dist/core/WorktreeManager.js +9 -14
  154. package/dist/core/WorktreeManager.js.map +1 -1
  155. package/dist/knowledge/KnowledgeManager.d.ts.map +1 -1
  156. package/dist/knowledge/KnowledgeManager.js +2 -2
  157. package/dist/knowledge/KnowledgeManager.js.map +1 -1
  158. package/dist/lifeline/ServerSupervisor.d.ts +28 -0
  159. package/dist/lifeline/ServerSupervisor.d.ts.map +1 -1
  160. package/dist/lifeline/ServerSupervisor.js +171 -73
  161. package/dist/lifeline/ServerSupervisor.js.map +1 -1
  162. package/dist/lifeline/TelegramLifeline.d.ts.map +1 -1
  163. package/dist/lifeline/TelegramLifeline.js +10 -4
  164. package/dist/lifeline/TelegramLifeline.js.map +1 -1
  165. package/dist/lifeline/detectLaunchdSupervised.d.ts +43 -0
  166. package/dist/lifeline/detectLaunchdSupervised.d.ts.map +1 -0
  167. package/dist/lifeline/detectLaunchdSupervised.js +106 -0
  168. package/dist/lifeline/detectLaunchdSupervised.js.map +1 -0
  169. package/dist/lifeline/droppedMessages.d.ts.map +1 -1
  170. package/dist/lifeline/droppedMessages.js +2 -2
  171. package/dist/lifeline/droppedMessages.js.map +1 -1
  172. package/dist/memory/EpisodicMemory.d.ts.map +1 -1
  173. package/dist/memory/EpisodicMemory.js +2 -2
  174. package/dist/memory/EpisodicMemory.js.map +1 -1
  175. package/dist/memory/TopicMemory.d.ts.map +1 -1
  176. package/dist/memory/TopicMemory.js +5 -8
  177. package/dist/memory/TopicMemory.js.map +1 -1
  178. package/dist/messaging/AgentTokenManager.d.ts.map +1 -1
  179. package/dist/messaging/AgentTokenManager.js +2 -2
  180. package/dist/messaging/AgentTokenManager.js.map +1 -1
  181. package/dist/messaging/DropPickup.d.ts.map +1 -1
  182. package/dist/messaging/DropPickup.js +2 -2
  183. package/dist/messaging/DropPickup.js.map +1 -1
  184. package/dist/messaging/GitSyncTransport.d.ts.map +1 -1
  185. package/dist/messaging/GitSyncTransport.js +4 -6
  186. package/dist/messaging/GitSyncTransport.js.map +1 -1
  187. package/dist/messaging/MessageStore.d.ts.map +1 -1
  188. package/dist/messaging/MessageStore.js +3 -4
  189. package/dist/messaging/MessageStore.js.map +1 -1
  190. package/dist/messaging/TelegramAdapter.d.ts.map +1 -1
  191. package/dist/messaging/TelegramAdapter.js +5 -8
  192. package/dist/messaging/TelegramAdapter.js.map +1 -1
  193. package/dist/messaging/backends/BaileysBackend.d.ts.map +1 -1
  194. package/dist/messaging/backends/BaileysBackend.js +3 -4
  195. package/dist/messaging/backends/BaileysBackend.js.map +1 -1
  196. package/dist/messaging/local-tone-check.d.ts +61 -0
  197. package/dist/messaging/local-tone-check.d.ts.map +1 -0
  198. package/dist/messaging/local-tone-check.js +78 -0
  199. package/dist/messaging/local-tone-check.js.map +1 -0
  200. package/dist/messaging/pending-relay-store.d.ts +153 -0
  201. package/dist/messaging/pending-relay-store.d.ts.map +1 -0
  202. package/dist/messaging/pending-relay-store.js +351 -0
  203. package/dist/messaging/pending-relay-store.js.map +1 -0
  204. package/dist/messaging/secret-patterns.d.ts +35 -0
  205. package/dist/messaging/secret-patterns.d.ts.map +1 -0
  206. package/dist/messaging/secret-patterns.js +70 -0
  207. package/dist/messaging/secret-patterns.js.map +1 -0
  208. package/dist/messaging/shared/EncryptedAuthStore.d.ts.map +1 -1
  209. package/dist/messaging/shared/EncryptedAuthStore.js +3 -4
  210. package/dist/messaging/shared/EncryptedAuthStore.js.map +1 -1
  211. package/dist/messaging/shared/MessageLogger.d.ts.map +1 -1
  212. package/dist/messaging/shared/MessageLogger.js +2 -2
  213. package/dist/messaging/shared/MessageLogger.js.map +1 -1
  214. package/dist/messaging/shared/PrivacyConsent.d.ts.map +1 -1
  215. package/dist/messaging/shared/PrivacyConsent.js +2 -2
  216. package/dist/messaging/shared/PrivacyConsent.js.map +1 -1
  217. package/dist/messaging/shared/SessionChannelRegistry.d.ts.map +1 -1
  218. package/dist/messaging/shared/SessionChannelRegistry.js +2 -2
  219. package/dist/messaging/shared/SessionChannelRegistry.js.map +1 -1
  220. package/dist/messaging/system-templates.d.ts +87 -0
  221. package/dist/messaging/system-templates.d.ts.map +1 -0
  222. package/dist/messaging/system-templates.js +236 -0
  223. package/dist/messaging/system-templates.js.map +1 -0
  224. package/dist/messaging/whoami-cache.d.ts +66 -0
  225. package/dist/messaging/whoami-cache.d.ts.map +1 -0
  226. package/dist/messaging/whoami-cache.js +149 -0
  227. package/dist/messaging/whoami-cache.js.map +1 -0
  228. package/dist/moltbridge/ProfileCompiler.d.ts.map +1 -1
  229. package/dist/moltbridge/ProfileCompiler.js +13 -7
  230. package/dist/moltbridge/ProfileCompiler.js.map +1 -1
  231. package/dist/monitoring/CommitmentTracker.d.ts.map +1 -1
  232. package/dist/monitoring/CommitmentTracker.js +2 -2
  233. package/dist/monitoring/CommitmentTracker.js.map +1 -1
  234. package/dist/monitoring/CredentialProvider.d.ts.map +1 -1
  235. package/dist/monitoring/CredentialProvider.js +2 -2
  236. package/dist/monitoring/CredentialProvider.js.map +1 -1
  237. package/dist/monitoring/DegradationReporter.d.ts +41 -0
  238. package/dist/monitoring/DegradationReporter.d.ts.map +1 -1
  239. package/dist/monitoring/DegradationReporter.js +96 -4
  240. package/dist/monitoring/DegradationReporter.js.map +1 -1
  241. package/dist/monitoring/HealthChecker.d.ts.map +1 -1
  242. package/dist/monitoring/HealthChecker.js +2 -2
  243. package/dist/monitoring/HealthChecker.js.map +1 -1
  244. package/dist/monitoring/HookEventReceiver.d.ts.map +1 -1
  245. package/dist/monitoring/HookEventReceiver.js +2 -2
  246. package/dist/monitoring/HookEventReceiver.js.map +1 -1
  247. package/dist/monitoring/InstructionsVerifier.d.ts.map +1 -1
  248. package/dist/monitoring/InstructionsVerifier.js +2 -2
  249. package/dist/monitoring/InstructionsVerifier.js.map +1 -1
  250. package/dist/monitoring/PresenceProxy.d.ts.map +1 -1
  251. package/dist/monitoring/PresenceProxy.js +5 -8
  252. package/dist/monitoring/PresenceProxy.js.map +1 -1
  253. package/dist/monitoring/QuotaTracker.d.ts.map +1 -1
  254. package/dist/monitoring/QuotaTracker.js +2 -2
  255. package/dist/monitoring/QuotaTracker.js.map +1 -1
  256. package/dist/monitoring/SessionMigrator.d.ts.map +1 -1
  257. package/dist/monitoring/SessionMigrator.js +2 -2
  258. package/dist/monitoring/SessionMigrator.js.map +1 -1
  259. package/dist/monitoring/SessionRecovery.d.ts.map +1 -1
  260. package/dist/monitoring/SessionRecovery.js +2 -2
  261. package/dist/monitoring/SessionRecovery.js.map +1 -1
  262. package/dist/monitoring/TelemetryAuth.d.ts.map +1 -1
  263. package/dist/monitoring/TelemetryAuth.js +3 -4
  264. package/dist/monitoring/TelemetryAuth.js.map +1 -1
  265. package/dist/monitoring/TokenLedger.d.ts +130 -0
  266. package/dist/monitoring/TokenLedger.d.ts.map +1 -0
  267. package/dist/monitoring/TokenLedger.js +523 -0
  268. package/dist/monitoring/TokenLedger.js.map +1 -0
  269. package/dist/monitoring/TokenLedgerPoller.d.ts +26 -0
  270. package/dist/monitoring/TokenLedgerPoller.d.ts.map +1 -0
  271. package/dist/monitoring/TokenLedgerPoller.js +44 -0
  272. package/dist/monitoring/TokenLedgerPoller.js.map +1 -0
  273. package/dist/monitoring/TriageOrchestrator.d.ts.map +1 -1
  274. package/dist/monitoring/TriageOrchestrator.js +3 -4
  275. package/dist/monitoring/TriageOrchestrator.js.map +1 -1
  276. package/dist/monitoring/WorktreeReaper.d.ts.map +1 -1
  277. package/dist/monitoring/WorktreeReaper.js +5 -7
  278. package/dist/monitoring/WorktreeReaper.js.map +1 -1
  279. package/dist/monitoring/delivery-failure-sentinel/recovery-policy.d.ts +83 -0
  280. package/dist/monitoring/delivery-failure-sentinel/recovery-policy.d.ts.map +1 -0
  281. package/dist/monitoring/delivery-failure-sentinel/recovery-policy.js +218 -0
  282. package/dist/monitoring/delivery-failure-sentinel/recovery-policy.js.map +1 -0
  283. package/dist/monitoring/delivery-failure-sentinel.d.ts +177 -0
  284. package/dist/monitoring/delivery-failure-sentinel.d.ts.map +1 -0
  285. package/dist/monitoring/delivery-failure-sentinel.js +598 -0
  286. package/dist/monitoring/delivery-failure-sentinel.js.map +1 -0
  287. package/dist/monitoring/probes/PlatformProbe.d.ts.map +1 -1
  288. package/dist/monitoring/probes/PlatformProbe.js +3 -4
  289. package/dist/monitoring/probes/PlatformProbe.js.map +1 -1
  290. package/dist/monitoring/templates-drift-verifier.d.ts +109 -0
  291. package/dist/monitoring/templates-drift-verifier.d.ts.map +1 -0
  292. package/dist/monitoring/templates-drift-verifier.js +324 -0
  293. package/dist/monitoring/templates-drift-verifier.js.map +1 -0
  294. package/dist/paste/PasteManager.d.ts.map +1 -1
  295. package/dist/paste/PasteManager.js +5 -8
  296. package/dist/paste/PasteManager.js.map +1 -1
  297. package/dist/publishing/PrivateViewer.d.ts.map +1 -1
  298. package/dist/publishing/PrivateViewer.js +2 -2
  299. package/dist/publishing/PrivateViewer.js.map +1 -1
  300. package/dist/scheduler/JobScheduler.d.ts.map +1 -1
  301. package/dist/scheduler/JobScheduler.js +2 -2
  302. package/dist/scheduler/JobScheduler.js.map +1 -1
  303. package/dist/server/AgentServer.d.ts +22 -0
  304. package/dist/server/AgentServer.d.ts.map +1 -1
  305. package/dist/server/AgentServer.js +199 -1
  306. package/dist/server/AgentServer.js.map +1 -1
  307. package/dist/server/WebSocketManager.d.ts +11 -0
  308. package/dist/server/WebSocketManager.d.ts.map +1 -1
  309. package/dist/server/WebSocketManager.js +28 -0
  310. package/dist/server/WebSocketManager.js.map +1 -1
  311. package/dist/server/boot-id.d.ts +58 -0
  312. package/dist/server/boot-id.d.ts.map +1 -0
  313. package/dist/server/boot-id.js +121 -0
  314. package/dist/server/boot-id.js.map +1 -0
  315. package/dist/server/middleware.d.ts +14 -1
  316. package/dist/server/middleware.d.ts.map +1 -1
  317. package/dist/server/middleware.js +81 -1
  318. package/dist/server/middleware.js.map +1 -1
  319. package/dist/server/routes.d.ts +76 -0
  320. package/dist/server/routes.d.ts.map +1 -1
  321. package/dist/server/routes.js +626 -11
  322. package/dist/server/routes.js.map +1 -1
  323. package/dist/threadline/AgentDiscovery.d.ts.map +1 -1
  324. package/dist/threadline/AgentDiscovery.js +2 -2
  325. package/dist/threadline/AgentDiscovery.js.map +1 -1
  326. package/dist/threadline/AgentTrustManager.d.ts.map +1 -1
  327. package/dist/threadline/AgentTrustManager.js +2 -2
  328. package/dist/threadline/AgentTrustManager.js.map +1 -1
  329. package/dist/threadline/BackfillCore.d.ts +70 -0
  330. package/dist/threadline/BackfillCore.d.ts.map +1 -0
  331. package/dist/threadline/BackfillCore.js +117 -0
  332. package/dist/threadline/BackfillCore.js.map +1 -0
  333. package/dist/threadline/CircuitBreaker.d.ts.map +1 -1
  334. package/dist/threadline/CircuitBreaker.js +2 -2
  335. package/dist/threadline/CircuitBreaker.js.map +1 -1
  336. package/dist/threadline/ComputeMeter.d.ts.map +1 -1
  337. package/dist/threadline/ComputeMeter.js +2 -2
  338. package/dist/threadline/ComputeMeter.js.map +1 -1
  339. package/dist/threadline/ContextThreadMap.d.ts.map +1 -1
  340. package/dist/threadline/ContextThreadMap.js +2 -2
  341. package/dist/threadline/ContextThreadMap.js.map +1 -1
  342. package/dist/threadline/HeartbeatWatchdog.d.ts +78 -0
  343. package/dist/threadline/HeartbeatWatchdog.d.ts.map +1 -0
  344. package/dist/threadline/HeartbeatWatchdog.js +212 -0
  345. package/dist/threadline/HeartbeatWatchdog.js.map +1 -0
  346. package/dist/threadline/HeartbeatWriter.d.ts +79 -0
  347. package/dist/threadline/HeartbeatWriter.d.ts.map +1 -0
  348. package/dist/threadline/HeartbeatWriter.js +109 -0
  349. package/dist/threadline/HeartbeatWriter.js.map +1 -0
  350. package/dist/threadline/InvitationManager.d.ts.map +1 -1
  351. package/dist/threadline/InvitationManager.js +2 -2
  352. package/dist/threadline/InvitationManager.js.map +1 -1
  353. package/dist/threadline/ListenerSessionManager.d.ts +59 -0
  354. package/dist/threadline/ListenerSessionManager.d.ts.map +1 -1
  355. package/dist/threadline/ListenerSessionManager.js +79 -0
  356. package/dist/threadline/ListenerSessionManager.js.map +1 -1
  357. package/dist/threadline/MCPAuth.d.ts.map +1 -1
  358. package/dist/threadline/MCPAuth.js +2 -2
  359. package/dist/threadline/MCPAuth.js.map +1 -1
  360. package/dist/threadline/PipeSessionSpawner.d.ts.map +1 -1
  361. package/dist/threadline/PipeSessionSpawner.js +3 -4
  362. package/dist/threadline/PipeSessionSpawner.js.map +1 -1
  363. package/dist/threadline/RateLimiter.d.ts.map +1 -1
  364. package/dist/threadline/RateLimiter.js +2 -2
  365. package/dist/threadline/RateLimiter.js.map +1 -1
  366. package/dist/threadline/RelaySpawnFailureHandler.d.ts +53 -0
  367. package/dist/threadline/RelaySpawnFailureHandler.d.ts.map +1 -0
  368. package/dist/threadline/RelaySpawnFailureHandler.js +73 -0
  369. package/dist/threadline/RelaySpawnFailureHandler.js.map +1 -0
  370. package/dist/threadline/SessionLifecycle.d.ts.map +1 -1
  371. package/dist/threadline/SessionLifecycle.js +2 -2
  372. package/dist/threadline/SessionLifecycle.js.map +1 -1
  373. package/dist/threadline/SpawnLedger.d.ts +94 -0
  374. package/dist/threadline/SpawnLedger.d.ts.map +1 -0
  375. package/dist/threadline/SpawnLedger.js +194 -0
  376. package/dist/threadline/SpawnLedger.js.map +1 -0
  377. package/dist/threadline/SpawnNonce.d.ts +49 -0
  378. package/dist/threadline/SpawnNonce.d.ts.map +1 -0
  379. package/dist/threadline/SpawnNonce.js +99 -0
  380. package/dist/threadline/SpawnNonce.js.map +1 -0
  381. package/dist/threadline/TelegramBridge.d.ts +140 -0
  382. package/dist/threadline/TelegramBridge.d.ts.map +1 -0
  383. package/dist/threadline/TelegramBridge.js +224 -0
  384. package/dist/threadline/TelegramBridge.js.map +1 -0
  385. package/dist/threadline/TelegramBridgeConfig.d.ts +79 -0
  386. package/dist/threadline/TelegramBridgeConfig.d.ts.map +1 -0
  387. package/dist/threadline/TelegramBridgeConfig.js +168 -0
  388. package/dist/threadline/TelegramBridgeConfig.js.map +1 -0
  389. package/dist/threadline/ThreadlineBootstrap.d.ts.map +1 -1
  390. package/dist/threadline/ThreadlineBootstrap.js +2 -2
  391. package/dist/threadline/ThreadlineBootstrap.js.map +1 -1
  392. package/dist/threadline/ThreadlineMCPServer.d.ts.map +1 -1
  393. package/dist/threadline/ThreadlineMCPServer.js +5 -0
  394. package/dist/threadline/ThreadlineMCPServer.js.map +1 -1
  395. package/dist/threadline/ThreadlineObservability.d.ts +95 -0
  396. package/dist/threadline/ThreadlineObservability.d.ts.map +1 -0
  397. package/dist/threadline/ThreadlineObservability.js +310 -0
  398. package/dist/threadline/ThreadlineObservability.js.map +1 -0
  399. package/dist/threadline/WakeSocketServer.d.ts.map +1 -1
  400. package/dist/threadline/WakeSocketServer.js +3 -4
  401. package/dist/threadline/WakeSocketServer.js.map +1 -1
  402. package/dist/threadline/listener-daemon.d.ts.map +1 -1
  403. package/dist/threadline/listener-daemon.js +3 -4
  404. package/dist/threadline/listener-daemon.js.map +1 -1
  405. package/dist/users/UserManager.d.ts.map +1 -1
  406. package/dist/users/UserManager.js +2 -2
  407. package/dist/users/UserManager.js.map +1 -1
  408. package/dist/users/UserOnboarding.d.ts.map +1 -1
  409. package/dist/users/UserOnboarding.js +2 -2
  410. package/dist/users/UserOnboarding.js.map +1 -1
  411. package/dist/utils/jsonl-rotation.d.ts.map +1 -1
  412. package/dist/utils/jsonl-rotation.js +2 -2
  413. package/dist/utils/jsonl-rotation.js.map +1 -1
  414. package/package.json +1 -1
  415. package/scripts/analyze-release.js +7 -12
  416. package/scripts/check-contract-evidence.js +27 -10
  417. package/scripts/fix-better-sqlite3.cjs +0 -2
  418. package/scripts/instar-dev-precommit.js +0 -2
  419. package/scripts/lint-no-direct-destructive.js +24 -4
  420. package/scripts/lint-template-sha-history.ts +183 -0
  421. package/scripts/migrate-incident-2026-04-17.mjs +2 -2
  422. package/scripts/run-migration.js +500 -0
  423. package/scripts/test-bootstrap-relay.mjs +2 -2
  424. package/scripts/threadline-bridge-backfill.mjs +379 -0
  425. package/scripts/verify-deployed-templates.ts +87 -0
  426. package/src/data/builtin-manifest.json +140 -132
  427. package/src/templates/scripts/git-sync-gate.sh +0 -4
  428. package/src/templates/scripts/telegram-reply.sh +318 -13
  429. package/upgrades/0.28.77.md +133 -0
  430. package/upgrades/0.28.78.md +90 -0
  431. package/upgrades/side-effects/agent-health-alert-authority-routing.md +121 -0
  432. package/upgrades/side-effects/comprehensive-destructive-tool-containment-migration.md +82 -0
  433. package/upgrades/side-effects/deferral-detector-orphan-todo.md +101 -0
  434. package/upgrades/side-effects/lifeline-self-heal-hardening.md +151 -0
  435. package/upgrades/side-effects/relay-spawn-ghost-reply-phase1.md +139 -0
  436. package/upgrades/side-effects/telegram-delivery-robustness-layer-2.md +320 -0
  437. package/upgrades/side-effects/telegram-delivery-robustness-layer-3.md +202 -0
  438. package/upgrades/side-effects/telegram-delivery-robustness-layer-7.md +339 -0
  439. package/upgrades/side-effects/telegram-delivery-robustness.md +178 -0
  440. package/upgrades/side-effects/threadline-bridge-backfill.md +203 -0
  441. package/upgrades/side-effects/threadline-canonical-inbox-write.md +218 -0
  442. package/upgrades/side-effects/threadline-observability-tab.md +206 -0
  443. package/upgrades/side-effects/threadline-tg-bridge-module.md +196 -0
  444. package/upgrades/side-effects/threadline-tg-bridge-settings-surface.md +208 -0
  445. package/upgrades/side-effects/token-ledger-bounded-scan.md +230 -0
  446. package/upgrades/side-effects/token-ledger-phase1.md +123 -0
  447. package/upgrades/NEXT.md +0 -53
  448. /package/upgrades/side-effects/{telegram-lifeline-version-missing-info.md → 0.28.76.md} +0 -0
@@ -20,6 +20,8 @@ import { dayKeyFor } from '../core/StopGateDb.js';
20
20
  import { randomUUID as cryptoRandomUUID } from 'node:crypto';
21
21
  import { execFile as execFileCb } from 'node:child_process';
22
22
  import { promisify } from 'node:util';
23
+ import { SafeGitExecutor } from '../core/SafeGitExecutor.js';
24
+ import { SafeFsExecutor } from '../core/SafeFsExecutor.js';
23
25
  const execFile = promisify(execFileCb);
24
26
  /**
25
27
  * Per-session continue-ceiling (spec § (b) Outcomes).
@@ -80,6 +82,286 @@ import { ScopeCoherenceTracker } from '../core/ScopeCoherenceTracker.js';
80
82
  import { isJunkPayload } from '../core/junk-payload.js';
81
83
  import { TruncationDetector } from '../paste/TruncationDetector.js';
82
84
  import { SecretDrop } from './SecretDrop.js';
85
+ import { matchesSystemTemplate } from '../messaging/system-templates.js';
86
+ import { resolvePendingRelayPath } from '../messaging/pending-relay-store.js';
87
+ import Database from 'better-sqlite3';
88
+ /**
89
+ * Build the /whoami request handler.
90
+ *
91
+ * Spec docs/specs/telegram-delivery-robustness.md § Layer 1c. Exported
92
+ * separately from createRoutes so unit tests can mount it on a minimal
93
+ * Express app without instantiating the entire RouteContext.
94
+ *
95
+ * Behavior:
96
+ * - 403 `{error: 'agent_id_header_required'}` when X-Instar-AgentId is
97
+ * absent. The middleware accepts bare-token requests during the
98
+ * deprecation window for *other* endpoints; /whoami does not, because
99
+ * accepting a bare-token request here would let a caller learn the
100
+ * expected agent-id from the response (discovery oracle).
101
+ * - 403 `{error: 'agent_id_mismatch', expected}` when the header is
102
+ * present but does not match.
103
+ * - 429 with retry hint when the per-source rate limit (1 req/s) is
104
+ * exceeded — the bucket is keyed on (agent-id, remoteAddress) so a
105
+ * single misbehaving caller can't starve the budget for legitimate
106
+ * sentinel callers from other source addresses.
107
+ * - 200 `{agentId, port}` on a clean request. (We deliberately do NOT
108
+ * return `version`: an authed identity probe shouldn't double as a
109
+ * CVE-targeting oracle for an authed peer who has stolen a token.)
110
+ */
111
+ export function createWhoamiHandler(opts) {
112
+ const WHOAMI_WINDOW_MS = 1000;
113
+ // Bucket key is `${agentId}|${remoteAddress}` so a single noisy caller
114
+ // can't starve the budget for legitimate sentinel callers from other
115
+ // source addresses on the same authed agent-id.
116
+ const buckets = new Map();
117
+ const cleanup = setInterval(() => {
118
+ const cutoff = Date.now() - WHOAMI_WINDOW_MS * 60;
119
+ for (const [k, t] of buckets) {
120
+ if (t < cutoff)
121
+ buckets.delete(k);
122
+ }
123
+ }, WHOAMI_WINDOW_MS * 60);
124
+ cleanup.unref();
125
+ return (req, res) => {
126
+ const headerVal = req.headers['x-instar-agentid'];
127
+ const provided = Array.isArray(headerVal) ? headerVal[0] : headerVal;
128
+ const expected = opts.agentId;
129
+ if (!provided) {
130
+ res.status(403).json({ error: 'agent_id_header_required', expected });
131
+ return;
132
+ }
133
+ if (provided !== expected) {
134
+ // Defense-in-depth — auth middleware should have already caught this.
135
+ res.status(403).json({ error: 'agent_id_mismatch', expected });
136
+ return;
137
+ }
138
+ const remote = req.ip || req.socket?.remoteAddress || 'unknown';
139
+ const bucketKey = `${provided}|${remote}`;
140
+ const now = Date.now();
141
+ const last = buckets.get(bucketKey);
142
+ if (last !== undefined && now - last < WHOAMI_WINDOW_MS) {
143
+ res.status(429).json({
144
+ error: 'Rate limit exceeded',
145
+ retryAfterMs: WHOAMI_WINDOW_MS - (now - last),
146
+ });
147
+ return;
148
+ }
149
+ buckets.set(bucketKey, now);
150
+ // Deliberately omit `version`: an authed identity probe shouldn't
151
+ // double as a CVE-targeting oracle for a peer whose token has been
152
+ // stolen. Layer 3's recovery path needs agentId + port, not version.
153
+ // (`opts.configVersion` retained on the type for forward-compat
154
+ // — callers who need version must read it from a separate route.)
155
+ res.json({
156
+ agentId: expected,
157
+ port: opts.port,
158
+ });
159
+ };
160
+ }
161
+ /**
162
+ * Build the `POST /events/delivery-failed` request handler.
163
+ *
164
+ * Spec: docs/specs/telegram-delivery-robustness.md § Layer 2c.
165
+ *
166
+ * Contract:
167
+ * - Body: `{ delivery_id, topic_id, text_hash, http_code,
168
+ * error_body?, attempted_port, attempts }` — strict
169
+ * (any extra field rejected with 400).
170
+ * - Caps: text-equivalent fields ≤ 8KB, error_body ≤ 1KB, total
171
+ * body ≤ 16KB. The endpoint *does not store anything* — the
172
+ * SQLite queue on the script side is the durable record. This
173
+ * route only fans out an SSE event so listeners (the Layer 3
174
+ * sentinel, the dashboard) can react in real time.
175
+ * - Per-(agentId, remote) token bucket: 10 req/s sustained, burst 50.
176
+ * - Auth handled by upstream `authMiddleware`; we additionally
177
+ * reject responses missing `X-Instar-AgentId` so the auth-mismatch
178
+ * path returns the same structured 403 even when the route is
179
+ * mounted in tests without the full middleware stack.
180
+ *
181
+ * Validation is hand-rolled (rather than zod) for two reasons:
182
+ * (1) the schema is small and we want every failure mode to map to a
183
+ * precise error code without translating zod's verbose message
184
+ * shape; (2) zod is in deps but adds a non-trivial import-time
185
+ * cost on a hot route.
186
+ */
187
+ export function createDeliveryFailedHandler(opts) {
188
+ const now = opts.now ?? (() => Date.now());
189
+ // Per-source token bucket. Burst 50; refill 10 tokens/sec.
190
+ // Keyed on `${agentId}|${remoteAddress}` — the agent-id is opaque to
191
+ // the bucket itself but having it in the key means tests that mount
192
+ // multiple agents on a single Express app each get their own budget.
193
+ const BURST = 50;
194
+ const REFILL_PER_SEC = 10;
195
+ const buckets = new Map();
196
+ // Periodic GC so a large cardinality of (agent, IP) pairs doesn't bloat the map.
197
+ const gc = setInterval(() => {
198
+ const cutoff = now() - 5 * 60 * 1000;
199
+ for (const [k, b] of buckets) {
200
+ if (b.lastRefillMs < cutoff)
201
+ buckets.delete(k);
202
+ }
203
+ }, 60 * 1000);
204
+ gc.unref();
205
+ function takeToken(key) {
206
+ const t = now();
207
+ let b = buckets.get(key);
208
+ if (!b) {
209
+ b = { tokens: BURST, lastRefillMs: t };
210
+ buckets.set(key, b);
211
+ }
212
+ else {
213
+ const elapsedMs = t - b.lastRefillMs;
214
+ if (elapsedMs > 0) {
215
+ b.tokens = Math.min(BURST, b.tokens + (elapsedMs / 1000) * REFILL_PER_SEC);
216
+ b.lastRefillMs = t;
217
+ }
218
+ }
219
+ if (b.tokens < 1)
220
+ return false;
221
+ b.tokens -= 1;
222
+ return true;
223
+ }
224
+ // Caps — defense-in-depth on top of the body-parser limit.
225
+ const MAX_TOTAL_BYTES = 16 * 1024;
226
+ const MAX_TEXT_FIELD_BYTES = 8 * 1024;
227
+ const MAX_ERROR_BODY_BYTES = 1 * 1024;
228
+ const HEX64 = /^[a-f0-9]{64}$/i;
229
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
230
+ const allowedFields = new Set([
231
+ 'delivery_id',
232
+ 'topic_id',
233
+ 'text_hash',
234
+ 'http_code',
235
+ 'error_body',
236
+ 'attempted_port',
237
+ 'attempts',
238
+ ]);
239
+ /** Strip control chars (except \n, \t) and length-cap. */
240
+ function sanitizeErrorBody(s) {
241
+ // eslint-disable-next-line no-control-regex
242
+ const stripped = s.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '');
243
+ return stripped.length > MAX_ERROR_BODY_BYTES
244
+ ? stripped.slice(0, MAX_ERROR_BODY_BYTES)
245
+ : stripped;
246
+ }
247
+ return (req, res) => {
248
+ // Auth-mismatch defense in depth — emit a single 403 audit line and do
249
+ // not echo the body. The upstream auth middleware should already have
250
+ // rejected, but the route is mountable bare in tests, so we re-check.
251
+ const headerVal = req.headers['x-instar-agentid'];
252
+ const provided = Array.isArray(headerVal) ? headerVal[0] : headerVal;
253
+ if (provided !== undefined && provided !== opts.agentId) {
254
+ const remote = req.ip || req.socket?.remoteAddress || 'unknown';
255
+ console.warn(`[delivery-failed] auth_failure: agent_id_mismatch from ${remote} ` +
256
+ `(provided=${JSON.stringify(provided).slice(0, 64)}, expected=${opts.agentId})`);
257
+ res.status(403).json({ error: 'agent_id_mismatch', expected: opts.agentId });
258
+ return;
259
+ }
260
+ // Rate limit.
261
+ const remote = req.ip || req.socket?.remoteAddress || 'unknown';
262
+ const bucketKey = `${opts.agentId}|${remote}`;
263
+ if (!takeToken(bucketKey)) {
264
+ res.status(429).json({
265
+ error: 'Rate limit exceeded',
266
+ limit: { rps: REFILL_PER_SEC, burst: BURST },
267
+ });
268
+ return;
269
+ }
270
+ const body = req.body;
271
+ if (!body || typeof body !== 'object' || Array.isArray(body)) {
272
+ res.status(400).json({ error: 'body must be a JSON object' });
273
+ return;
274
+ }
275
+ // Total-body cap. Express's body-parser already imposes a limit, but we
276
+ // re-measure here so a misconfigured upstream limit can't smuggle through.
277
+ let totalBytes;
278
+ try {
279
+ totalBytes = Buffer.byteLength(JSON.stringify(body), 'utf-8');
280
+ }
281
+ catch {
282
+ res.status(400).json({ error: 'body not serializable' });
283
+ return;
284
+ }
285
+ if (totalBytes > MAX_TOTAL_BYTES) {
286
+ res.status(413).json({ error: 'body too large', maxBytes: MAX_TOTAL_BYTES });
287
+ return;
288
+ }
289
+ // Strict field set — reject extras.
290
+ for (const k of Object.keys(body)) {
291
+ if (!allowedFields.has(k)) {
292
+ res.status(400).json({ error: `unexpected field: ${k}` });
293
+ return;
294
+ }
295
+ }
296
+ const { delivery_id, topic_id, text_hash, http_code, error_body, attempted_port, attempts, } = body;
297
+ if (typeof delivery_id !== 'string' || !UUID.test(delivery_id)) {
298
+ res.status(400).json({ error: 'delivery_id must be a UUIDv4 string' });
299
+ return;
300
+ }
301
+ if (typeof topic_id !== 'number' || !Number.isInteger(topic_id) || topic_id < 0) {
302
+ res.status(400).json({ error: 'topic_id must be a non-negative integer' });
303
+ return;
304
+ }
305
+ if (typeof text_hash !== 'string' || !HEX64.test(text_hash)) {
306
+ res.status(400).json({ error: 'text_hash must be a 64-char hex string' });
307
+ return;
308
+ }
309
+ if (typeof http_code !== 'number' || !Number.isInteger(http_code) || http_code < 0 || http_code > 999) {
310
+ res.status(400).json({ error: 'http_code must be an integer in [0,999]' });
311
+ return;
312
+ }
313
+ if (typeof attempted_port !== 'number' || !Number.isInteger(attempted_port) || attempted_port < 1 || attempted_port > 65535) {
314
+ res.status(400).json({ error: 'attempted_port must be an integer in [1,65535]' });
315
+ return;
316
+ }
317
+ if (typeof attempts !== 'number' || !Number.isInteger(attempts) || attempts < 1) {
318
+ res.status(400).json({ error: 'attempts must be a positive integer' });
319
+ return;
320
+ }
321
+ let sanitizedErrorBody = null;
322
+ if (error_body !== undefined && error_body !== null) {
323
+ if (typeof error_body !== 'string') {
324
+ res.status(400).json({ error: 'error_body must be a string when present' });
325
+ return;
326
+ }
327
+ if (Buffer.byteLength(error_body, 'utf-8') > MAX_TEXT_FIELD_BYTES) {
328
+ // We could just silently truncate, but the script's contract caps at
329
+ // 1KB before send — anything bigger is a contract violation worth
330
+ // reporting back. Cap at 8KB as the field-size hard upper bound;
331
+ // sanitization below handles the per-field 1KB normalization.
332
+ res.status(413).json({ error: 'error_body too large', maxBytes: MAX_TEXT_FIELD_BYTES });
333
+ return;
334
+ }
335
+ sanitizedErrorBody = sanitizeErrorBody(error_body);
336
+ }
337
+ // Fan out to listeners. We do NOT persist anything — that's the script's
338
+ // job. Listeners care about the *fact* of failure plus enough metadata to
339
+ // look up the queued row.
340
+ const event = {
341
+ type: 'delivery_failed',
342
+ agentId: opts.agentId,
343
+ delivery_id,
344
+ topic_id,
345
+ text_hash,
346
+ http_code,
347
+ attempted_port,
348
+ attempts,
349
+ error_body: sanitizedErrorBody,
350
+ receivedAt: new Date(now()).toISOString(),
351
+ };
352
+ if (opts.emit) {
353
+ try {
354
+ opts.emit(event);
355
+ }
356
+ catch (err) {
357
+ // The endpoint must not fail because a listener crashed. The script
358
+ // already has the row in SQLite; the event is best-effort signal.
359
+ console.error('[delivery-failed] emit handler threw:', err);
360
+ }
361
+ }
362
+ res.status(202).json({ accepted: true, delivery_id });
363
+ };
364
+ }
83
365
  // Validation patterns for route parameters
84
366
  const SESSION_NAME_RE = /^[a-zA-Z0-9_-]{1,200}$/;
85
367
  const JOB_SLUG_RE = /^[a-zA-Z0-9_-]{1,100}$/;
@@ -130,6 +412,36 @@ export function createRoutes(ctx) {
130
412
  const homeostasisMonitor = new HomeostasisMonitor(ctx.config.stateDir);
131
413
  // Truncation detector for Telegram messages (Drop Zone integration)
132
414
  const truncationDetector = new TruncationDetector();
415
+ // ── /telegram/reply X-Instar-DeliveryId dedup LRU (Layer 3 §3d step 4) ──
416
+ // 24h sliding window of seen delivery_ids. Map preserves insertion order;
417
+ // we GC entries whose timestamp is older than 24h on each access.
418
+ const DELIVERY_LRU_MAX = 10_000;
419
+ const DELIVERY_LRU_TTL_MS = 24 * 60 * 60 * 1000;
420
+ const deliveryIdLru = new Map();
421
+ function deliveryLruHas(id) {
422
+ const at = deliveryIdLru.get(id);
423
+ if (at === undefined)
424
+ return false;
425
+ if (Date.now() - at > DELIVERY_LRU_TTL_MS) {
426
+ deliveryIdLru.delete(id);
427
+ return false;
428
+ }
429
+ return true;
430
+ }
431
+ function deliveryLruRecord(id) {
432
+ if (deliveryIdLru.size >= DELIVERY_LRU_MAX) {
433
+ // Drop oldest insertion-ordered entry.
434
+ const first = deliveryIdLru.keys().next().value;
435
+ if (first !== undefined)
436
+ deliveryIdLru.delete(first);
437
+ }
438
+ deliveryIdLru.set(id, Date.now());
439
+ }
440
+ // Wrap Map.has to use TTL-aware logic without rewriting call sites.
441
+ // We export via a small object so the route handler can call .has and .set.
442
+ // (Not using a Set — we need TTL.) Helper functions above provide that.
443
+ const deliveryIdLruHelpers = { has: deliveryLruHas, record: deliveryLruRecord };
444
+ void deliveryIdLruHelpers; // referenced via the helpers; alias kept for grep
133
445
  // ── Messaging tone gate ──────────────────────────────────────────
134
446
  //
135
447
  // Invoked before forwarding agent-authored messages to a user. Runs the
@@ -477,6 +789,46 @@ export function createRoutes(ctx) {
477
789
  }
478
790
  res.json(base);
479
791
  });
792
+ // GET /whoami — authenticated identity probe.
793
+ //
794
+ // Spec § Layer 1c: the sentinel hits this BEFORE any auth-bearing
795
+ // POST /telegram/reply during recovery. It returns this server's
796
+ // agentId/port/version so the caller can verify it's talking to the
797
+ // right agent before sending content.
798
+ //
799
+ // Hard requirement (no deprecation exception): the X-Instar-AgentId
800
+ // header MUST be present AND match this server's agent-id. If we
801
+ // accepted bare-token requests here, the endpoint would become a
802
+ // discovery oracle for token→port→agent-id triples. The auth
803
+ // middleware already validates the token and (when header present)
804
+ // the agent-id; we re-check the header presence here to close the
805
+ // deprecation hole that otherwise lets bare-token callers learn the
806
+ // expected agent-id from the response.
807
+ router.get('/whoami', createWhoamiHandler({
808
+ agentId: ctx.config.projectName,
809
+ port: ctx.config.port,
810
+ configVersion: ctx.config.version,
811
+ }));
812
+ // POST /events/delivery-failed — fan-out for the script-side detector.
813
+ //
814
+ // Spec § Layer 2c. The relay script INSERTs into its local SQLite queue
815
+ // and then best-effort POSTs here so the in-process Layer 3 sentinel
816
+ // can react in <1s rather than waiting for its 5-minute watchdog tick.
817
+ // The endpoint itself does not persist anything — SQLite is the
818
+ // durable record on the script side.
819
+ router.post('/events/delivery-failed', createDeliveryFailedHandler({
820
+ agentId: ctx.config.projectName,
821
+ emit: ctx.wsManager
822
+ ? (event) => {
823
+ // wsManager is set lazily after server.listen; if it's still
824
+ // null at request time we just no-op the broadcast — the
825
+ // event was still accepted, and the Layer 3 backstop watchdog
826
+ // (5-min tick over SQLite) catches up.
827
+ if (ctx.wsManager)
828
+ ctx.wsManager.broadcastEvent(event);
829
+ }
830
+ : undefined,
831
+ }));
480
832
  /**
481
833
  * Get all feature degradation events.
482
834
  * A degradation means a feature fallback activated — the primary path failed.
@@ -2019,8 +2371,7 @@ export function createRoutes(ctx) {
2019
2371
  let gitSyncJobEnabled = false;
2020
2372
  if (hasGitRepo) {
2021
2373
  try {
2022
- // safe-git-allow: incremental-migration
2023
- const remote = execFileSync('git', ['remote'], { cwd: projectDir, stdio: 'pipe' }).toString().trim();
2374
+ const remote = SafeGitExecutor.readSync(['remote'], { cwd: projectDir, stdio: 'pipe', operation: 'src/server/routes.ts:2286' }).toString().trim();
2024
2375
  hasRemote = remote.length > 0;
2025
2376
  }
2026
2377
  catch { /* no remote */ }
@@ -2700,13 +3051,10 @@ export function createRoutes(ctx) {
2700
3051
  // Detect GitHub repo from git remote
2701
3052
  let repo = null;
2702
3053
  try {
2703
- // safe-git-allow: incremental-migration
2704
- const remoteUrl = execFileSync('git', ['remote', 'get-url', 'origin'], {
2705
- cwd: projectDir,
3054
+ const remoteUrl = SafeGitExecutor.readSync(['remote', 'get-url', 'origin'], { cwd: projectDir,
2706
3055
  encoding: 'utf-8',
2707
3056
  timeout: 5000,
2708
- stdio: ['pipe', 'pipe', 'pipe'],
2709
- }).trim();
3057
+ stdio: ['pipe', 'pipe', 'pipe'], operation: 'src/server/routes.ts:3037' }).trim();
2710
3058
  // Extract owner/repo from SSH or HTTPS URL
2711
3059
  const match = remoteUrl.match(/github\.com[:/](.+?)(?:\.git)?$/);
2712
3060
  if (match)
@@ -2830,8 +3178,7 @@ export function createRoutes(ctx) {
2830
3178
  const fpath = path.join(failDir, fname);
2831
3179
  try {
2832
3180
  if (fs.statSync(fpath).mtimeMs < cutoff) {
2833
- // safe-git-allow: incremental-migration
2834
- fs.unlinkSync(fpath);
3181
+ SafeFsExecutor.safeUnlinkSync(fpath, { operation: 'src/server/routes.ts:3177' });
2835
3182
  purgedFiles++;
2836
3183
  }
2837
3184
  }
@@ -3019,6 +3366,64 @@ export function createRoutes(ctx) {
3019
3366
  res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
3020
3367
  }
3021
3368
  });
3369
+ // ── Token Ledger ────────────────────────────────────────────────
3370
+ // Read-only token-usage observability backed by SQLite. Source data
3371
+ // is Claude Code's per-session JSONL transcripts at
3372
+ // ~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl.
3373
+ // Auth is enforced globally by authMiddleware.
3374
+ const TOKEN_DEFAULT_WINDOW_MS = 24 * 60 * 60 * 1000;
3375
+ const TOKEN_DEFAULT_ORPHAN_IDLE_MS = 30 * 60 * 1000;
3376
+ function parseSinceMs(raw) {
3377
+ if (typeof raw === 'string' && /^\d+$/.test(raw)) {
3378
+ const n = Number(raw);
3379
+ if (n >= 0)
3380
+ return n;
3381
+ }
3382
+ return Date.now() - TOKEN_DEFAULT_WINDOW_MS;
3383
+ }
3384
+ router.get('/tokens/summary', (req, res) => {
3385
+ if (!ctx.tokenLedger) {
3386
+ res.status(503).json({ error: 'token ledger unavailable' });
3387
+ return;
3388
+ }
3389
+ const sinceMs = parseSinceMs(req.query.since);
3390
+ res.json({ sinceMs, summary: ctx.tokenLedger.summary({ sinceMs }) });
3391
+ });
3392
+ router.get('/tokens/sessions', (req, res) => {
3393
+ if (!ctx.tokenLedger) {
3394
+ res.status(503).json({ error: 'token ledger unavailable' });
3395
+ return;
3396
+ }
3397
+ const sinceMs = parseSinceMs(req.query.since);
3398
+ let limit = 20;
3399
+ if (typeof req.query.limit === 'string' && /^\d+$/.test(req.query.limit)) {
3400
+ const n = Number(req.query.limit);
3401
+ if (n > 0 && n <= 500)
3402
+ limit = n;
3403
+ }
3404
+ res.json({ sinceMs, limit, sessions: ctx.tokenLedger.topSessions({ limit, sinceMs }) });
3405
+ });
3406
+ router.get('/tokens/by-project', (req, res) => {
3407
+ if (!ctx.tokenLedger) {
3408
+ res.status(503).json({ error: 'token ledger unavailable' });
3409
+ return;
3410
+ }
3411
+ const sinceMs = parseSinceMs(req.query.since);
3412
+ res.json({ sinceMs, projects: ctx.tokenLedger.byProject({ sinceMs }) });
3413
+ });
3414
+ router.get('/tokens/orphans', (req, res) => {
3415
+ if (!ctx.tokenLedger) {
3416
+ res.status(503).json({ error: 'token ledger unavailable' });
3417
+ return;
3418
+ }
3419
+ let idleMs = TOKEN_DEFAULT_ORPHAN_IDLE_MS;
3420
+ if (typeof req.query.idleMs === 'string' && /^\d+$/.test(req.query.idleMs)) {
3421
+ const n = Number(req.query.idleMs);
3422
+ if (n > 0)
3423
+ idleMs = n;
3424
+ }
3425
+ res.json({ idleMs, orphans: ctx.tokenLedger.orphans({ idleMs }) });
3426
+ });
3022
3427
  // ── Jobs ────────────────────────────────────────────────────────
3023
3428
  router.get('/jobs', (_req, res) => {
3024
3429
  if (!ctx.scheduler) {
@@ -3654,6 +4059,30 @@ export function createRoutes(ctx) {
3654
4059
  res.status(400).json({ error: '"text" must be 4096 characters or fewer' });
3655
4060
  return;
3656
4061
  }
4062
+ // ── X-Instar-DeliveryId server-side dedup (Layer 3 spec §3d step 4) ──
4063
+ // 24h LRU keyed on the header value. A duplicate POST with the same
4064
+ // delivery_id returns 200 idempotent without sending again. This
4065
+ // closes the "200-but-client-blind" double-send class where the
4066
+ // sentinel re-sends a queued message that actually landed the first
4067
+ // time but the script-side response was lost.
4068
+ const deliveryIdHeader = req.headers['x-instar-deliveryid'];
4069
+ const deliveryId = Array.isArray(deliveryIdHeader) ? deliveryIdHeader[0] : deliveryIdHeader;
4070
+ if (deliveryId && typeof deliveryId === 'string' && /^[0-9a-f-]{16,64}$/i.test(deliveryId)) {
4071
+ if (deliveryLruHas(deliveryId)) {
4072
+ res.json({ ok: true, topicId, idempotent: true });
4073
+ return;
4074
+ }
4075
+ }
4076
+ // ── X-Instar-System bypass (Layer 3 spec §3f) ──
4077
+ // For sentinel-emitted templates, bypass the tone gate IF the body
4078
+ // matches a known system template. The bypass is deliberately
4079
+ // restricted to fixed templates whose content was reviewed at
4080
+ // code-review time. Membership check uses regex / SHA-256 against
4081
+ // the compiled-in template set; arbitrary text fails through to the
4082
+ // normal gate.
4083
+ const systemHeader = req.headers['x-instar-system'];
4084
+ const systemFlag = Array.isArray(systemHeader) ? systemHeader[0] : systemHeader;
4085
+ const isSystemTemplate = systemFlag === 'true' && matchesSystemTemplate(text);
3657
4086
  // Outbound gate — single authority. Skipped for proxy messages (PresenceProxy
3658
4087
  // etc. are system-generated). The authority receives structured signals from
3659
4088
  // the junk-payload and dedup detectors alongside conversational context, and
@@ -3663,6 +4092,7 @@ export function createRoutes(ctx) {
3663
4092
  const allowDebugText = metadata?.allowDebugText === true;
3664
4093
  const allowDuplicate = metadata?.allowDuplicate === true;
3665
4094
  if (!isProxy &&
4095
+ !isSystemTemplate &&
3666
4096
  (await checkOutboundMessage(text, 'telegram', res, {
3667
4097
  topicId,
3668
4098
  allowDebugText,
@@ -3676,12 +4106,63 @@ export function createRoutes(ctx) {
3676
4106
  if (!isProxy) {
3677
4107
  ctx.sessionManager.clearInjectionTracker(topicId);
3678
4108
  }
4109
+ // Record successful delivery in the dedup LRU so a sentinel retry
4110
+ // with the same delivery_id returns 200-idempotent.
4111
+ if (deliveryId && typeof deliveryId === 'string' && /^[0-9a-f-]{16,64}$/i.test(deliveryId)) {
4112
+ deliveryLruRecord(deliveryId);
4113
+ }
3679
4114
  res.json({ ok: true, topicId });
3680
4115
  }
3681
4116
  catch (err) {
3682
4117
  res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
3683
4118
  }
3684
4119
  });
4120
+ // ── GET /delivery-queue (Layer 3 spec §3i) ──
4121
+ // Authed read-only view of the pending-relay queue depth/oldest age
4122
+ // for the current agent. Used by the dashboard "Pending Replies" panel
4123
+ // and ops health checks. Read-only: never mutates queue rows.
4124
+ router.get('/delivery-queue', (_req, res) => {
4125
+ const stateDir = ctx.config.stateDir;
4126
+ const agentId = ctx.config.projectName;
4127
+ if (!stateDir || !agentId) {
4128
+ res.status(503).json({ error: 'state directory or agent id not configured' });
4129
+ return;
4130
+ }
4131
+ const dbPath = resolvePendingRelayPath(stateDir, agentId);
4132
+ let db = null;
4133
+ try {
4134
+ db = new Database(dbPath, { readonly: true, fileMustExist: true });
4135
+ const totalRow = db.prepare('SELECT COUNT(*) AS n FROM entries').get();
4136
+ const total = totalRow?.n ?? 0;
4137
+ const byState = db.prepare('SELECT state, COUNT(*) AS n FROM entries GROUP BY state').all();
4138
+ const oldestRow = db.prepare("SELECT MIN(attempted_at) AS oldest FROM entries WHERE state IN ('queued','claimed')").get();
4139
+ const oldestAgeSeconds = oldestRow?.oldest
4140
+ ? Math.max(0, Math.floor((Date.now() - Date.parse(oldestRow.oldest)) / 1000))
4141
+ : 0;
4142
+ res.json({
4143
+ depth: total,
4144
+ oldest_age_seconds: oldestAgeSeconds,
4145
+ by_state: Object.fromEntries(byState.map((r) => [r.state, r.n])),
4146
+ });
4147
+ }
4148
+ catch (err) {
4149
+ const msg = err instanceof Error ? err.message : String(err);
4150
+ // Missing-file is a normal "no queue yet" state — return zeros, not 500.
4151
+ if (/unable to open|no such file|does not exist|cannot open/i.test(msg)) {
4152
+ res.json({ depth: 0, oldest_age_seconds: 0, by_state: {} });
4153
+ return;
4154
+ }
4155
+ res.status(500).json({ error: msg });
4156
+ }
4157
+ finally {
4158
+ if (db) {
4159
+ try {
4160
+ db.close();
4161
+ }
4162
+ catch { /* best-effort */ }
4163
+ }
4164
+ }
4165
+ });
3685
4166
  // POST /build/heartbeat — /build pipeline status relay.
3686
4167
  //
3687
4168
  // BUILD-STALL-VISIBILITY-SPEC Fix 2. The /build skill / build-state.py calls
@@ -3872,6 +4353,80 @@ export function createRoutes(ctx) {
3872
4353
  }
3873
4354
  res.json(ctx.telegram.getLogStats());
3874
4355
  });
4356
+ // ── Threadline → Telegram Bridge: settings surface ─────────────
4357
+ //
4358
+ // Read/write toggles + allow-list/deny-list that gate the bridge module
4359
+ // (deliverable b). Default-OFF auto-create is a hard requirement — these
4360
+ // endpoints are how the user opts in. Bearer-auth enforced globally.
4361
+ router.get('/threadline/telegram-bridge/config', (_req, res) => {
4362
+ if (!ctx.telegramBridgeConfig) {
4363
+ res.status(503).json({ error: 'Telegram bridge config not initialized' });
4364
+ return;
4365
+ }
4366
+ res.json(ctx.telegramBridgeConfig.getSettings());
4367
+ });
4368
+ // ── Threadline observability — read-only views over inbox/outbox/bindings ──
4369
+ router.get('/threadline/observability/threads', (req, res) => {
4370
+ if (!ctx.threadlineObservability) {
4371
+ res.status(503).json({ error: 'Threadline observability not initialized' });
4372
+ return;
4373
+ }
4374
+ const remoteAgent = typeof req.query.remoteAgent === 'string' ? req.query.remoteAgent : undefined;
4375
+ const sinceIso = typeof req.query.since === 'string' ? req.query.since : undefined;
4376
+ const untilIso = typeof req.query.until === 'string' ? req.query.until : undefined;
4377
+ const hasTopicRaw = typeof req.query.hasTopic === 'string' ? req.query.hasTopic : undefined;
4378
+ const hasTopic = hasTopicRaw === 'yes' || hasTopicRaw === 'no' ? hasTopicRaw : undefined;
4379
+ const threads = ctx.threadlineObservability.listThreads({ remoteAgent, sinceIso, untilIso, hasTopic });
4380
+ res.json({ threads, count: threads.length });
4381
+ });
4382
+ router.get('/threadline/observability/threads/:threadId', (req, res) => {
4383
+ if (!ctx.threadlineObservability) {
4384
+ res.status(503).json({ error: 'Threadline observability not initialized' });
4385
+ return;
4386
+ }
4387
+ const detail = ctx.threadlineObservability.getThread(req.params.threadId);
4388
+ if (!detail) {
4389
+ res.status(404).json({ error: 'Thread not found' });
4390
+ return;
4391
+ }
4392
+ res.json(detail);
4393
+ });
4394
+ router.get('/threadline/observability/search', (req, res) => {
4395
+ if (!ctx.threadlineObservability) {
4396
+ res.status(503).json({ error: 'Threadline observability not initialized' });
4397
+ return;
4398
+ }
4399
+ const q = typeof req.query.q === 'string' ? req.query.q : '';
4400
+ const limitRaw = typeof req.query.limit === 'string' ? parseInt(req.query.limit, 10) : 50;
4401
+ const limit = Number.isFinite(limitRaw) && limitRaw > 0 ? Math.min(limitRaw, 200) : 50;
4402
+ const hits = ctx.threadlineObservability.searchMessages(q, limit);
4403
+ res.json({ hits, count: hits.length });
4404
+ });
4405
+ router.patch('/threadline/telegram-bridge/config', (req, res) => {
4406
+ if (!ctx.telegramBridgeConfig) {
4407
+ res.status(503).json({ error: 'Telegram bridge config not initialized' });
4408
+ return;
4409
+ }
4410
+ try {
4411
+ const body = req.body;
4412
+ const patch = {};
4413
+ if ('enabled' in body)
4414
+ patch.enabled = body.enabled;
4415
+ if ('autoCreateTopics' in body)
4416
+ patch.autoCreateTopics = body.autoCreateTopics;
4417
+ if ('mirrorExisting' in body)
4418
+ patch.mirrorExisting = body.mirrorExisting;
4419
+ if ('allowList' in body)
4420
+ patch.allowList = body.allowList;
4421
+ if ('denyList' in body)
4422
+ patch.denyList = body.denyList;
4423
+ const settings = ctx.telegramBridgeConfig.update(patch);
4424
+ res.json(settings);
4425
+ }
4426
+ catch (err) {
4427
+ res.status(400).json({ error: err instanceof Error ? err.message : String(err) });
4428
+ }
4429
+ });
3875
4430
  // ── Slack ──────────────────────────────────────────────────────
3876
4431
  router.post('/slack/reply/:channelId', async (req, res) => {
3877
4432
  if (!ctx.slack) {
@@ -8056,9 +8611,8 @@ export function createRoutes(ctx) {
8056
8611
  auth.deprovision();
8057
8612
  // Clear submissions log
8058
8613
  const submissionsLog = path.join(ctx.config.stateDir, 'telemetry', 'submissions.jsonl');
8059
- // safe-git-allow: incremental-migration
8060
8614
  try {
8061
- fs.unlinkSync(submissionsLog);
8615
+ SafeFsExecutor.safeUnlinkSync(submissionsLog, { operation: 'src/server/routes.ts:8932' });
8062
8616
  }
8063
8617
  catch { /* may not exist */ }
8064
8618
  // Update config.json
@@ -9257,6 +9811,37 @@ export function createRoutes(ctx) {
9257
9811
  : tl?.handled === false ? 'queued (no live session)'
9258
9812
  : 'accepted';
9259
9813
  console.log(`[relay-send] Local delivery to ${localTarget.name}:${localTarget.port} (thread: ${effectiveThreadId}) — ${outcome}`);
9814
+ // Canonical outbox write — single source of truth for outbound messages
9815
+ // across BOTH delivery paths (local + relay). Powers the dashboard
9816
+ // observability tab. Mirrors the inbound canonical write from PR #113.
9817
+ if (ctx.listenerManager) {
9818
+ try {
9819
+ ctx.listenerManager.appendCanonicalOutboxEntry({
9820
+ from: ctx.config.projectName ?? 'self',
9821
+ senderName: ctx.config.projectName ?? 'self',
9822
+ to: localTarget.name,
9823
+ recipientName: localTarget.name,
9824
+ threadId: effectiveThreadId,
9825
+ text: message,
9826
+ messageId: msgId,
9827
+ outcome,
9828
+ });
9829
+ }
9830
+ catch (err) {
9831
+ console.warn(`[relay-send] Canonical outbox append failed (non-fatal): ${err instanceof Error ? err.message : err}`);
9832
+ }
9833
+ }
9834
+ // Mirror outbound into Telegram bridge (relay-only — best effort).
9835
+ if (ctx.telegramBridge) {
9836
+ ctx.telegramBridge.mirrorOutbound({
9837
+ threadId: effectiveThreadId,
9838
+ remoteAgent: localTarget.name,
9839
+ remoteAgentName: localTarget.name,
9840
+ text: message,
9841
+ messageId: msgId,
9842
+ outcome,
9843
+ }).catch(() => { });
9844
+ }
9260
9845
  if (waitForReply) {
9261
9846
  const reply = await waitForThreadlineReply(ctx, localTarget.name, effectiveThreadId, timeoutSeconds);
9262
9847
  res.json({
@@ -9314,6 +9899,36 @@ export function createRoutes(ctx) {
9314
9899
  }
9315
9900
  const relayMsgId = relayClient.sendAuto(resolvedId, message, threadId);
9316
9901
  const effectiveRelayThreadId = threadId ?? relayMsgId;
9902
+ // Canonical outbox write for the relay-delivery path — same shape as the
9903
+ // local-delivery path above, so the observability tab sees both paths.
9904
+ if (ctx.listenerManager) {
9905
+ try {
9906
+ ctx.listenerManager.appendCanonicalOutboxEntry({
9907
+ from: ctx.config.projectName ?? 'self',
9908
+ senderName: ctx.config.projectName ?? 'self',
9909
+ to: resolvedId,
9910
+ recipientName: targetAgent,
9911
+ threadId: effectiveRelayThreadId,
9912
+ text: message,
9913
+ messageId: relayMsgId,
9914
+ outcome: 'relay-sent',
9915
+ });
9916
+ }
9917
+ catch (err) {
9918
+ console.warn(`[relay-send] Canonical outbox append failed (non-fatal): ${err instanceof Error ? err.message : err}`);
9919
+ }
9920
+ }
9921
+ // Mirror outbound into Telegram bridge (relay-only — best effort).
9922
+ if (ctx.telegramBridge) {
9923
+ ctx.telegramBridge.mirrorOutbound({
9924
+ threadId: effectiveRelayThreadId,
9925
+ remoteAgent: resolvedId,
9926
+ remoteAgentName: targetAgent,
9927
+ text: message,
9928
+ messageId: relayMsgId,
9929
+ outcome: 'relay-sent',
9930
+ }).catch(() => { });
9931
+ }
9317
9932
  if (waitForReply) {
9318
9933
  const reply = await waitForThreadlineReply(ctx, resolvedId, effectiveRelayThreadId, timeoutSeconds);
9319
9934
  res.json({