instar 1.3.1648 → 1.3.1650

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 (475) hide show
  1. package/dashboard/console/accounts.js +2 -1
  2. package/dashboard/console/agents-model.js +3 -3
  3. package/dashboard/console/agents.js +7 -4
  4. package/dashboard/console/console.css +1 -0
  5. package/dashboard/console/console.js +35 -4
  6. package/dashboard/console/costs-model.js +12 -0
  7. package/dashboard/console/costs.js +6 -3
  8. package/dashboard/console/development-model.js +9 -3
  9. package/dashboard/console/development.js +7 -4
  10. package/dashboard/console/home-model.js +30 -14
  11. package/dashboard/console/mandates.js +3 -3
  12. package/dashboard/console/needs-model.js +105 -0
  13. package/dashboard/console/plans.js +2 -1
  14. package/dashboard/console/secrets.js +2 -2
  15. package/dashboard/console/ticket-model.js +36 -0
  16. package/dashboard/console/ui.js +3 -0
  17. package/dist/cli.js +8 -0
  18. package/dist/cli.js.map +1 -1
  19. package/dist/commands/install.d.ts +9 -7
  20. package/dist/commands/install.d.ts.map +1 -1
  21. package/dist/commands/install.js +2 -19
  22. package/dist/commands/install.js.map +1 -1
  23. package/dist/commands/reflect.d.ts.map +1 -1
  24. package/dist/commands/reflect.js +2 -0
  25. package/dist/commands/reflect.js.map +1 -1
  26. package/dist/commands/route.d.ts.map +1 -1
  27. package/dist/commands/route.js +2 -0
  28. package/dist/commands/route.js.map +1 -1
  29. package/dist/commands/server.d.ts.map +1 -1
  30. package/dist/commands/server.js +335 -14
  31. package/dist/commands/server.js.map +1 -1
  32. package/dist/commands/setClaudeKey.d.ts +8 -0
  33. package/dist/commands/setClaudeKey.d.ts.map +1 -0
  34. package/dist/commands/setClaudeKey.js +35 -0
  35. package/dist/commands/setClaudeKey.js.map +1 -0
  36. package/dist/core/AccountFollowMeOrchestrator.d.ts.map +1 -1
  37. package/dist/core/AccountFollowMeOrchestrator.js +4 -0
  38. package/dist/core/AccountFollowMeOrchestrator.js.map +1 -1
  39. package/dist/core/AppMediaStore.d.ts.map +1 -1
  40. package/dist/core/AppMediaStore.js +3 -0
  41. package/dist/core/AppMediaStore.js.map +1 -1
  42. package/dist/core/BackupManager.d.ts.map +1 -1
  43. package/dist/core/BackupManager.js +2 -0
  44. package/dist/core/BackupManager.js.map +1 -1
  45. package/dist/core/ClaudeCliIntelligenceProvider.d.ts.map +1 -1
  46. package/dist/core/ClaudeCliIntelligenceProvider.js +11 -3
  47. package/dist/core/ClaudeCliIntelligenceProvider.js.map +1 -1
  48. package/dist/core/ClaudeKeyHealth.d.ts +141 -0
  49. package/dist/core/ClaudeKeyHealth.d.ts.map +1 -0
  50. package/dist/core/ClaudeKeyHealth.js +467 -0
  51. package/dist/core/ClaudeKeyHealth.js.map +1 -0
  52. package/dist/core/CodexTaskContinuationStore.d.ts.map +1 -1
  53. package/dist/core/CodexTaskContinuationStore.js +7 -0
  54. package/dist/core/CodexTaskContinuationStore.js.map +1 -1
  55. package/dist/core/CoherenceJournal.d.ts +1 -1
  56. package/dist/core/CoherenceJournal.d.ts.map +1 -1
  57. package/dist/core/CoherenceJournal.js +11 -2
  58. package/dist/core/CoherenceJournal.js.map +1 -1
  59. package/dist/core/Config.d.ts +8 -0
  60. package/dist/core/Config.d.ts.map +1 -1
  61. package/dist/core/Config.js +26 -0
  62. package/dist/core/Config.js.map +1 -1
  63. package/dist/core/CredentialSwapExecutor.d.ts.map +1 -1
  64. package/dist/core/CredentialSwapExecutor.js +10 -0
  65. package/dist/core/CredentialSwapExecutor.js.map +1 -1
  66. package/dist/core/DecisionQualityRecorderImpl.d.ts.map +1 -1
  67. package/dist/core/DecisionQualityRecorderImpl.js +6 -0
  68. package/dist/core/DecisionQualityRecorderImpl.js.map +1 -1
  69. package/dist/core/DiscoveryEvaluator.d.ts.map +1 -1
  70. package/dist/core/DiscoveryEvaluator.js +4 -0
  71. package/dist/core/DiscoveryEvaluator.js.map +1 -1
  72. package/dist/core/IdentityReannounceClaimant.d.ts.map +1 -1
  73. package/dist/core/IdentityReannounceClaimant.js +4 -0
  74. package/dist/core/IdentityReannounceClaimant.js.map +1 -1
  75. package/dist/core/IntelligenceRouter.d.ts.map +1 -1
  76. package/dist/core/IntelligenceRouter.js +9 -0
  77. package/dist/core/IntelligenceRouter.js.map +1 -1
  78. package/dist/core/JournalSyncApplier.d.ts +1 -0
  79. package/dist/core/JournalSyncApplier.d.ts.map +1 -1
  80. package/dist/core/JournalSyncApplier.js +48 -0
  81. package/dist/core/JournalSyncApplier.js.map +1 -1
  82. package/dist/core/LeaseCoordinator.d.ts +4 -0
  83. package/dist/core/LeaseCoordinator.d.ts.map +1 -1
  84. package/dist/core/LeaseCoordinator.js +64 -1
  85. package/dist/core/LeaseCoordinator.js.map +1 -1
  86. package/dist/core/MessagingToneGate.d.ts +11 -7
  87. package/dist/core/MessagingToneGate.d.ts.map +1 -1
  88. package/dist/core/MessagingToneGate.js +15 -1
  89. package/dist/core/MessagingToneGate.js.map +1 -1
  90. package/dist/core/MobileDeviceKeys.d.ts +10 -0
  91. package/dist/core/MobileDeviceKeys.d.ts.map +1 -1
  92. package/dist/core/MobileDeviceKeys.js +17 -0
  93. package/dist/core/MobileDeviceKeys.js.map +1 -1
  94. package/dist/core/MultiMachineCoordinator.d.ts +12 -0
  95. package/dist/core/MultiMachineCoordinator.d.ts.map +1 -1
  96. package/dist/core/MultiMachineCoordinator.js +115 -3
  97. package/dist/core/MultiMachineCoordinator.js.map +1 -1
  98. package/dist/core/MutualSshRuntime.d.ts +1 -0
  99. package/dist/core/MutualSshRuntime.d.ts.map +1 -1
  100. package/dist/core/MutualSshRuntime.js +17 -1
  101. package/dist/core/MutualSshRuntime.js.map +1 -1
  102. package/dist/core/ObservedEndpointTracker.d.ts +1 -0
  103. package/dist/core/ObservedEndpointTracker.d.ts.map +1 -1
  104. package/dist/core/ObservedEndpointTracker.js +17 -0
  105. package/dist/core/ObservedEndpointTracker.js.map +1 -1
  106. package/dist/core/OverloadShedPlanner.d.ts.map +1 -1
  107. package/dist/core/OverloadShedPlanner.js +9 -0
  108. package/dist/core/OverloadShedPlanner.js.map +1 -1
  109. package/dist/core/OwnershipApplier.d.ts.map +1 -1
  110. package/dist/core/OwnershipApplier.js +7 -0
  111. package/dist/core/OwnershipApplier.js.map +1 -1
  112. package/dist/core/OwnershipReconciler.d.ts.map +1 -1
  113. package/dist/core/OwnershipReconciler.js +5 -0
  114. package/dist/core/OwnershipReconciler.js.map +1 -1
  115. package/dist/core/PostUpdateMigrator.d.ts +8 -0
  116. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  117. package/dist/core/PostUpdateMigrator.js +76 -2
  118. package/dist/core/PostUpdateMigrator.js.map +1 -1
  119. package/dist/core/PrHandLease.d.ts.map +1 -1
  120. package/dist/core/PrHandLease.js +2 -0
  121. package/dist/core/PrHandLease.js.map +1 -1
  122. package/dist/core/ProactiveSwapMonitor.d.ts.map +1 -1
  123. package/dist/core/ProactiveSwapMonitor.js +7 -0
  124. package/dist/core/ProactiveSwapMonitor.js.map +1 -1
  125. package/dist/core/ProjectRoundExecution.d.ts.map +1 -1
  126. package/dist/core/ProjectRoundExecution.js +3 -2
  127. package/dist/core/ProjectRoundExecution.js.map +1 -1
  128. package/dist/core/QueueDrainLoop.d.ts +7 -0
  129. package/dist/core/QueueDrainLoop.d.ts.map +1 -1
  130. package/dist/core/QueueDrainLoop.js +37 -1
  131. package/dist/core/QueueDrainLoop.js.map +1 -1
  132. package/dist/core/RemoteAckStore.d.ts.map +1 -1
  133. package/dist/core/RemoteAckStore.js +4 -0
  134. package/dist/core/RemoteAckStore.js.map +1 -1
  135. package/dist/core/ReplicatedStoreReconcilerWiring.d.ts.map +1 -1
  136. package/dist/core/ReplicatedStoreReconcilerWiring.js +1 -0
  137. package/dist/core/ReplicatedStoreReconcilerWiring.js.map +1 -1
  138. package/dist/core/SecretSync.d.ts +1 -0
  139. package/dist/core/SecretSync.d.ts.map +1 -1
  140. package/dist/core/SecretSync.js +16 -0
  141. package/dist/core/SecretSync.js.map +1 -1
  142. package/dist/core/SessionManager.d.ts +17 -121
  143. package/dist/core/SessionManager.d.ts.map +1 -1
  144. package/dist/core/SessionManager.js +161 -17
  145. package/dist/core/SessionManager.js.map +1 -1
  146. package/dist/core/SessionPoolFailoverRunner.d.ts.map +1 -1
  147. package/dist/core/SessionPoolFailoverRunner.js +4 -0
  148. package/dist/core/SessionPoolFailoverRunner.js.map +1 -1
  149. package/dist/core/SleepWakeDetector.d.ts.map +1 -1
  150. package/dist/core/SleepWakeDetector.js +10 -0
  151. package/dist/core/SleepWakeDetector.js.map +1 -1
  152. package/dist/core/SubscriptionReloginStore.d.ts.map +1 -1
  153. package/dist/core/SubscriptionReloginStore.js +10 -1
  154. package/dist/core/SubscriptionReloginStore.js.map +1 -1
  155. package/dist/core/TopicPinMutation.d.ts.map +1 -1
  156. package/dist/core/TopicPinMutation.js +5 -0
  157. package/dist/core/TopicPinMutation.js.map +1 -1
  158. package/dist/core/UnjustifiedStopGate.d.ts.map +1 -1
  159. package/dist/core/UnjustifiedStopGate.js +22 -0
  160. package/dist/core/UnjustifiedStopGate.js.map +1 -1
  161. package/dist/core/WindowRunCadenceExecutor.d.ts.map +1 -1
  162. package/dist/core/WindowRunCadenceExecutor.js +20 -0
  163. package/dist/core/WindowRunCadenceExecutor.js.map +1 -1
  164. package/dist/core/claudeChildAuth.d.ts +68 -0
  165. package/dist/core/claudeChildAuth.d.ts.map +1 -0
  166. package/dist/core/claudeChildAuth.js +173 -0
  167. package/dist/core/claudeChildAuth.js.map +1 -0
  168. package/dist/core/claudeKeyProbe.d.ts +23 -0
  169. package/dist/core/claudeKeyProbe.d.ts.map +1 -0
  170. package/dist/core/claudeKeyProbe.js +68 -0
  171. package/dist/core/claudeKeyProbe.js.map +1 -0
  172. package/dist/core/codexDeviceLimitWiring.d.ts.map +1 -1
  173. package/dist/core/codexDeviceLimitWiring.js +12 -0
  174. package/dist/core/codexDeviceLimitWiring.js.map +1 -1
  175. package/dist/core/deliverToConversation.d.ts.map +1 -1
  176. package/dist/core/deliverToConversation.js +11 -0
  177. package/dist/core/deliverToConversation.js.map +1 -1
  178. package/dist/core/meteredCallEntry.d.ts.map +1 -1
  179. package/dist/core/meteredCallEntry.js +21 -0
  180. package/dist/core/meteredCallEntry.js.map +1 -1
  181. package/dist/core/nobodyPollingActuator.d.ts.map +1 -1
  182. package/dist/core/nobodyPollingActuator.js +5 -0
  183. package/dist/core/nobodyPollingActuator.js.map +1 -1
  184. package/dist/core/operatorSurfaceReferral.d.ts +18 -0
  185. package/dist/core/operatorSurfaceReferral.d.ts.map +1 -0
  186. package/dist/core/operatorSurfaceReferral.js +32 -0
  187. package/dist/core/operatorSurfaceReferral.js.map +1 -0
  188. package/dist/core/ownershipFollowsLiveWork.d.ts.map +1 -1
  189. package/dist/core/ownershipFollowsLiveWork.js +10 -1
  190. package/dist/core/ownershipFollowsLiveWork.js.map +1 -1
  191. package/dist/core/sessionCapacityRefusedError.d.ts +8 -0
  192. package/dist/core/sessionCapacityRefusedError.d.ts.map +1 -0
  193. package/dist/core/sessionCapacityRefusedError.js +13 -0
  194. package/dist/core/sessionCapacityRefusedError.js.map +1 -0
  195. package/dist/core/types.d.ts +15 -0
  196. package/dist/core/types.d.ts.map +1 -1
  197. package/dist/core/types.js.map +1 -1
  198. package/dist/core/worktreeDirtyCheck.d.ts.map +1 -1
  199. package/dist/core/worktreeDirtyCheck.js +2 -0
  200. package/dist/core/worktreeDirtyCheck.js.map +1 -1
  201. package/dist/data/stageBCertifiedSet.d.ts +9 -0
  202. package/dist/data/stageBCertifiedSet.d.ts.map +1 -1
  203. package/dist/data/stageBCertifiedSet.js +12 -0
  204. package/dist/data/stageBCertifiedSet.js.map +1 -1
  205. package/dist/data/standards-guard-index.json +1 -1
  206. package/dist/data/standards-guard-index.meta.json +2 -2
  207. package/dist/data/standards-registry.meta.json +1 -1
  208. package/dist/lifeline/TelegramLifeline.d.ts.map +1 -1
  209. package/dist/lifeline/TelegramLifeline.js +17 -3
  210. package/dist/lifeline/TelegramLifeline.js.map +1 -1
  211. package/dist/messaging/TelegramAdapter.d.ts +10 -0
  212. package/dist/messaging/TelegramAdapter.d.ts.map +1 -1
  213. package/dist/messaging/TelegramAdapter.js +12 -1
  214. package/dist/messaging/TelegramAdapter.js.map +1 -1
  215. package/dist/messaging/attentionHubPolicy.d.ts.map +1 -1
  216. package/dist/messaging/attentionHubPolicy.js +1 -0
  217. package/dist/messaging/attentionHubPolicy.js.map +1 -1
  218. package/dist/messaging/telegram-origin/TelegramOriginBoot.d.ts +2 -0
  219. package/dist/messaging/telegram-origin/TelegramOriginBoot.d.ts.map +1 -1
  220. package/dist/messaging/telegram-origin/TelegramOriginBoot.js +2 -0
  221. package/dist/messaging/telegram-origin/TelegramOriginBoot.js.map +1 -1
  222. package/dist/messaging/telegram-origin/TelegramOriginRuntime.d.ts +16 -0
  223. package/dist/messaging/telegram-origin/TelegramOriginRuntime.d.ts.map +1 -1
  224. package/dist/messaging/telegram-origin/TelegramOriginRuntime.js +23 -2
  225. package/dist/messaging/telegram-origin/TelegramOriginRuntime.js.map +1 -1
  226. package/dist/messaging/telegram-origin/TelegramOriginService.d.ts +1 -0
  227. package/dist/messaging/telegram-origin/TelegramOriginService.d.ts.map +1 -1
  228. package/dist/messaging/telegram-origin/TelegramOriginService.js +5 -2
  229. package/dist/messaging/telegram-origin/TelegramOriginService.js.map +1 -1
  230. package/dist/monitoring/AgentWorktreeReaper.d.ts.map +1 -1
  231. package/dist/monitoring/AgentWorktreeReaper.js +15 -0
  232. package/dist/monitoring/AgentWorktreeReaper.js.map +1 -1
  233. package/dist/monitoring/ApprenticeshipCycleSlaMonitor.d.ts.map +1 -1
  234. package/dist/monitoring/ApprenticeshipCycleSlaMonitor.js +18 -9
  235. package/dist/monitoring/ApprenticeshipCycleSlaMonitor.js.map +1 -1
  236. package/dist/monitoring/CodexArmSessionSweeper.d.ts.map +1 -1
  237. package/dist/monitoring/CodexArmSessionSweeper.js +11 -0
  238. package/dist/monitoring/CodexArmSessionSweeper.js.map +1 -1
  239. package/dist/monitoring/CrashLoopPauser.d.ts.map +1 -1
  240. package/dist/monitoring/CrashLoopPauser.js +8 -0
  241. package/dist/monitoring/CrashLoopPauser.js.map +1 -1
  242. package/dist/monitoring/DegradationReporter.d.ts.map +1 -1
  243. package/dist/monitoring/DegradationReporter.js +7 -1
  244. package/dist/monitoring/DegradationReporter.js.map +1 -1
  245. package/dist/monitoring/DegradedTmuxGuard.d.ts +2 -0
  246. package/dist/monitoring/DegradedTmuxGuard.d.ts.map +1 -1
  247. package/dist/monitoring/DegradedTmuxGuard.js +14 -0
  248. package/dist/monitoring/DegradedTmuxGuard.js.map +1 -1
  249. package/dist/monitoring/DurableOutputScrubber.d.ts.map +1 -1
  250. package/dist/monitoring/DurableOutputScrubber.js +7 -0
  251. package/dist/monitoring/DurableOutputScrubber.js.map +1 -1
  252. package/dist/monitoring/DurableVaultSession.d.ts.map +1 -1
  253. package/dist/monitoring/DurableVaultSession.js +15 -0
  254. package/dist/monitoring/DurableVaultSession.js.map +1 -1
  255. package/dist/monitoring/ExternalHogSentinel.d.ts +2 -0
  256. package/dist/monitoring/ExternalHogSentinel.d.ts.map +1 -1
  257. package/dist/monitoring/ExternalHogSentinel.js +27 -1
  258. package/dist/monitoring/ExternalHogSentinel.js.map +1 -1
  259. package/dist/monitoring/FailureLedger.d.ts.map +1 -1
  260. package/dist/monitoring/FailureLedger.js +38 -1
  261. package/dist/monitoring/FailureLedger.js.map +1 -1
  262. package/dist/monitoring/FeatureEvidenceLog.d.ts +5 -0
  263. package/dist/monitoring/FeatureEvidenceLog.d.ts.map +1 -1
  264. package/dist/monitoring/FeatureEvidenceLog.js +25 -0
  265. package/dist/monitoring/FeatureEvidenceLog.js.map +1 -1
  266. package/dist/monitoring/GeminiCapacityEscalationMonitor.d.ts.map +1 -1
  267. package/dist/monitoring/GeminiCapacityEscalationMonitor.js +20 -2
  268. package/dist/monitoring/GeminiCapacityEscalationMonitor.js.map +1 -1
  269. package/dist/monitoring/GeminiLoopRunner.d.ts.map +1 -1
  270. package/dist/monitoring/GeminiLoopRunner.js +11 -1
  271. package/dist/monitoring/GeminiLoopRunner.js.map +1 -1
  272. package/dist/monitoring/GreenPrAutoMerger.d.ts.map +1 -1
  273. package/dist/monitoring/GreenPrAutoMerger.js +11 -0
  274. package/dist/monitoring/GreenPrAutoMerger.js.map +1 -1
  275. package/dist/monitoring/GrowthDigestPublisher.d.ts.map +1 -1
  276. package/dist/monitoring/GrowthDigestPublisher.js +16 -0
  277. package/dist/monitoring/GrowthDigestPublisher.js.map +1 -1
  278. package/dist/monitoring/GrowthMilestoneAnalyst.d.ts +3 -0
  279. package/dist/monitoring/GrowthMilestoneAnalyst.d.ts.map +1 -1
  280. package/dist/monitoring/GrowthMilestoneAnalyst.js +45 -1
  281. package/dist/monitoring/GrowthMilestoneAnalyst.js.map +1 -1
  282. package/dist/monitoring/McpProcessReaper.d.ts +2 -0
  283. package/dist/monitoring/McpProcessReaper.d.ts.map +1 -1
  284. package/dist/monitoring/McpProcessReaper.js +32 -0
  285. package/dist/monitoring/McpProcessReaper.js.map +1 -1
  286. package/dist/monitoring/MissingLoginSessionDetector.d.ts +2 -1
  287. package/dist/monitoring/MissingLoginSessionDetector.d.ts.map +1 -1
  288. package/dist/monitoring/MissingLoginSessionDetector.js +23 -0
  289. package/dist/monitoring/MissingLoginSessionDetector.js.map +1 -1
  290. package/dist/monitoring/OrchestratorPoller.d.ts +1 -0
  291. package/dist/monitoring/OrchestratorPoller.d.ts.map +1 -1
  292. package/dist/monitoring/OrchestratorPoller.js +13 -0
  293. package/dist/monitoring/OrchestratorPoller.js.map +1 -1
  294. package/dist/monitoring/OrphanedWorkSentinel.d.ts.map +1 -1
  295. package/dist/monitoring/OrphanedWorkSentinel.js +7 -0
  296. package/dist/monitoring/OrphanedWorkSentinel.js.map +1 -1
  297. package/dist/monitoring/PresenceProxy.d.ts.map +1 -1
  298. package/dist/monitoring/PresenceProxy.js +9 -1
  299. package/dist/monitoring/PresenceProxy.js.map +1 -1
  300. package/dist/monitoring/ProcessFootprintMonitor.d.ts.map +1 -1
  301. package/dist/monitoring/ProcessFootprintMonitor.js +20 -2
  302. package/dist/monitoring/ProcessFootprintMonitor.js.map +1 -1
  303. package/dist/monitoring/PromiseBeacon.d.ts.map +1 -1
  304. package/dist/monitoring/PromiseBeacon.js +20 -0
  305. package/dist/monitoring/PromiseBeacon.js.map +1 -1
  306. package/dist/monitoring/ReleaseReadinessSentinel.d.ts +2 -0
  307. package/dist/monitoring/ReleaseReadinessSentinel.d.ts.map +1 -1
  308. package/dist/monitoring/ReleaseReadinessSentinel.js +26 -0
  309. package/dist/monitoring/ReleaseReadinessSentinel.js.map +1 -1
  310. package/dist/monitoring/RopeHealthMonitor.d.ts.map +1 -1
  311. package/dist/monitoring/RopeHealthMonitor.js +35 -1
  312. package/dist/monitoring/RopeHealthMonitor.js.map +1 -1
  313. package/dist/monitoring/SentinelNotifier.d.ts.map +1 -1
  314. package/dist/monitoring/SentinelNotifier.js +16 -0
  315. package/dist/monitoring/SentinelNotifier.js.map +1 -1
  316. package/dist/monitoring/SessionReaper.d.ts.map +1 -1
  317. package/dist/monitoring/SessionReaper.js +11 -2
  318. package/dist/monitoring/SessionReaper.js.map +1 -1
  319. package/dist/monitoring/SingleMachineFailoverGapDetector.d.ts +2 -0
  320. package/dist/monitoring/SingleMachineFailoverGapDetector.d.ts.map +1 -1
  321. package/dist/monitoring/SingleMachineFailoverGapDetector.js +20 -0
  322. package/dist/monitoring/SingleMachineFailoverGapDetector.js.map +1 -1
  323. package/dist/monitoring/SpeakerElection.d.ts.map +1 -1
  324. package/dist/monitoring/SpeakerElection.js +8 -0
  325. package/dist/monitoring/SpeakerElection.js.map +1 -1
  326. package/dist/monitoring/StrandedTopicSentinel.d.ts +3 -0
  327. package/dist/monitoring/StrandedTopicSentinel.d.ts.map +1 -1
  328. package/dist/monitoring/StrandedTopicSentinel.js +33 -0
  329. package/dist/monitoring/StrandedTopicSentinel.js.map +1 -1
  330. package/dist/native/InstarWedgeCanary.app/Contents/MacOS/InstarWedgeCanary +0 -0
  331. package/dist/native/instar-desktop-host +0 -0
  332. package/dist/providers/adapters/anthropic-headless/transport/oneShotCompletion.d.ts.map +1 -1
  333. package/dist/providers/adapters/anthropic-headless/transport/oneShotCompletion.js +5 -2
  334. package/dist/providers/adapters/anthropic-headless/transport/oneShotCompletion.js.map +1 -1
  335. package/dist/recommended/RecommendedServicesLedger.d.ts +91 -0
  336. package/dist/recommended/RecommendedServicesLedger.d.ts.map +1 -0
  337. package/dist/recommended/RecommendedServicesLedger.js +193 -0
  338. package/dist/recommended/RecommendedServicesLedger.js.map +1 -0
  339. package/dist/recommended/RecommendedServicesReplicatedStore.d.ts +14 -0
  340. package/dist/recommended/RecommendedServicesReplicatedStore.d.ts.map +1 -0
  341. package/dist/recommended/RecommendedServicesReplicatedStore.js +34 -0
  342. package/dist/recommended/RecommendedServicesReplicatedStore.js.map +1 -0
  343. package/dist/recommended/legacyDeclines.d.ts +11 -0
  344. package/dist/recommended/legacyDeclines.d.ts.map +1 -0
  345. package/dist/recommended/legacyDeclines.js +17 -0
  346. package/dist/recommended/legacyDeclines.js.map +1 -0
  347. package/dist/recommended/serviceIds.d.ts +3 -0
  348. package/dist/recommended/serviceIds.d.ts.map +1 -0
  349. package/dist/recommended/serviceIds.js +6 -0
  350. package/dist/recommended/serviceIds.js.map +1 -0
  351. package/dist/scaffold/templates.d.ts +2 -0
  352. package/dist/scaffold/templates.d.ts.map +1 -1
  353. package/dist/scaffold/templates.js +8 -0
  354. package/dist/scaffold/templates.js.map +1 -1
  355. package/dist/scheduler/JobResultTriage.d.ts.map +1 -1
  356. package/dist/scheduler/JobResultTriage.js +10 -0
  357. package/dist/scheduler/JobResultTriage.js.map +1 -1
  358. package/dist/scheduler/JobScheduler.d.ts.map +1 -1
  359. package/dist/scheduler/JobScheduler.js +18 -1
  360. package/dist/scheduler/JobScheduler.js.map +1 -1
  361. package/dist/scheduler/MentorOnboardingRunner.d.ts +3 -0
  362. package/dist/scheduler/MentorOnboardingRunner.d.ts.map +1 -1
  363. package/dist/scheduler/MentorOnboardingRunner.js +24 -2
  364. package/dist/scheduler/MentorOnboardingRunner.js.map +1 -1
  365. package/dist/server/AgentServer.d.ts +2 -0
  366. package/dist/server/AgentServer.d.ts.map +1 -1
  367. package/dist/server/AgentServer.js +76 -15
  368. package/dist/server/AgentServer.js.map +1 -1
  369. package/dist/server/BootHealthBeacon.d.ts +3 -0
  370. package/dist/server/BootHealthBeacon.d.ts.map +1 -1
  371. package/dist/server/BootHealthBeacon.js +9 -0
  372. package/dist/server/BootHealthBeacon.js.map +1 -1
  373. package/dist/server/CapabilityIndex.d.ts.map +1 -1
  374. package/dist/server/CapabilityIndex.js +10 -1
  375. package/dist/server/CapabilityIndex.js.map +1 -1
  376. package/dist/server/LifelineIngressGuard.d.ts.map +1 -1
  377. package/dist/server/LifelineIngressGuard.js +11 -0
  378. package/dist/server/LifelineIngressGuard.js.map +1 -1
  379. package/dist/server/PoolPollCache.d.ts +4 -0
  380. package/dist/server/PoolPollCache.d.ts.map +1 -1
  381. package/dist/server/PoolPollCache.js +33 -0
  382. package/dist/server/PoolPollCache.js.map +1 -1
  383. package/dist/server/machineRoutes.d.ts.map +1 -1
  384. package/dist/server/machineRoutes.js +6 -0
  385. package/dist/server/machineRoutes.js.map +1 -1
  386. package/dist/server/middleware.d.ts.map +1 -1
  387. package/dist/server/middleware.js +6 -0
  388. package/dist/server/middleware.js.map +1 -1
  389. package/dist/server/mobileConsoleTicket.d.ts +113 -0
  390. package/dist/server/mobileConsoleTicket.d.ts.map +1 -0
  391. package/dist/server/mobileConsoleTicket.js +405 -0
  392. package/dist/server/mobileConsoleTicket.js.map +1 -0
  393. package/dist/server/mobileConversationRoutes.d.ts.map +1 -1
  394. package/dist/server/mobileConversationRoutes.js +20 -1
  395. package/dist/server/mobileConversationRoutes.js.map +1 -1
  396. package/dist/server/mobileMediaRoutes.d.ts.map +1 -1
  397. package/dist/server/mobileMediaRoutes.js +3 -0
  398. package/dist/server/mobileMediaRoutes.js.map +1 -1
  399. package/dist/server/mobilePairingRoutes.d.ts +8 -2
  400. package/dist/server/mobilePairingRoutes.d.ts.map +1 -1
  401. package/dist/server/mobilePairingRoutes.js +67 -6
  402. package/dist/server/mobilePairingRoutes.js.map +1 -1
  403. package/dist/server/mobileSessionTerminalRoutes.d.ts +128 -0
  404. package/dist/server/mobileSessionTerminalRoutes.d.ts.map +1 -0
  405. package/dist/server/mobileSessionTerminalRoutes.js +389 -0
  406. package/dist/server/mobileSessionTerminalRoutes.js.map +1 -0
  407. package/dist/server/recommendedServiceRoutes.d.ts +13 -0
  408. package/dist/server/recommendedServiceRoutes.d.ts.map +1 -0
  409. package/dist/server/recommendedServiceRoutes.js +49 -0
  410. package/dist/server/recommendedServiceRoutes.js.map +1 -0
  411. package/dist/server/routes.d.ts +2 -0
  412. package/dist/server/routes.d.ts.map +1 -1
  413. package/dist/server/routes.js +175 -5
  414. package/dist/server/routes.js.map +1 -1
  415. package/dist/testing/selfActionRegistry.d.ts.map +1 -1
  416. package/dist/testing/selfActionRegistry.js +36 -0
  417. package/dist/testing/selfActionRegistry.js.map +1 -1
  418. package/dist/threadline/A2ACheckInProxy.d.ts.map +1 -1
  419. package/dist/threadline/A2ACheckInProxy.js +26 -15
  420. package/dist/threadline/A2ACheckInProxy.js.map +1 -1
  421. package/dist/threadline/AgentTrustManager.d.ts.map +1 -1
  422. package/dist/threadline/AgentTrustManager.js +3 -0
  423. package/dist/threadline/AgentTrustManager.js.map +1 -1
  424. package/dist/threadline/InboundIdLedger.d.ts.map +1 -1
  425. package/dist/threadline/InboundIdLedger.js +18 -1
  426. package/dist/threadline/InboundIdLedger.js.map +1 -1
  427. package/dist/threadline/NegotiatorGate.d.ts.map +1 -1
  428. package/dist/threadline/NegotiatorGate.js +11 -0
  429. package/dist/threadline/NegotiatorGate.js.map +1 -1
  430. package/dist/threadline/PipeSessionSpawner.d.ts.map +1 -1
  431. package/dist/threadline/PipeSessionSpawner.js +8 -0
  432. package/dist/threadline/PipeSessionSpawner.js.map +1 -1
  433. package/dist/threadline/ThreadlineRouter.d.ts.map +1 -1
  434. package/dist/threadline/ThreadlineRouter.js +13 -0
  435. package/dist/threadline/ThreadlineRouter.js.map +1 -1
  436. package/dist/threadline/pairingReplicationWiring.d.ts.map +1 -1
  437. package/dist/threadline/pairingReplicationWiring.js +9 -0
  438. package/dist/threadline/pairingReplicationWiring.js.map +1 -1
  439. package/dist/threadline/relayForward.d.ts.map +1 -1
  440. package/dist/threadline/relayForward.js +13 -2
  441. package/dist/threadline/relayForward.js.map +1 -1
  442. package/dist/threadline/relayUnknownSenderTrust.d.ts +0 -9
  443. package/dist/threadline/relayUnknownSenderTrust.d.ts.map +1 -1
  444. package/dist/threadline/relayUnknownSenderTrust.js +16 -0
  445. package/dist/threadline/relayUnknownSenderTrust.js.map +1 -1
  446. package/package.json +1 -1
  447. package/scripts/check-constitution-pairing.mjs +273 -0
  448. package/scripts/safe-merge.mjs +9 -0
  449. package/src/data/builtin-manifest.json +103 -103
  450. package/src/data/feature-ladders.json +113 -113
  451. package/src/data/stageBCertifiedSet.ts +12 -0
  452. package/src/data/standards-guard-index.json +1 -1
  453. package/src/data/standards-guard-index.meta.json +2 -2
  454. package/src/data/standards-registry.meta.json +1 -1
  455. package/src/data/state-coherence-registry.json +18 -0
  456. package/src/scaffold/templates.ts +9 -0
  457. package/upgrades/1.3.1649.md +28 -0
  458. package/upgrades/1.3.1650.md +105 -0
  459. package/upgrades/job-alert-global-cap.eli16.md +9 -0
  460. package/upgrades/mobile-console-device-signin.eli16.md +5 -0
  461. package/upgrades/side-effects/cloud-key-internal-calls.md +116 -0
  462. package/upgrades/side-effects/constitution-alignment.eli16.md +11 -0
  463. package/upgrades/side-effects/constitution-alignment.md +69 -0
  464. package/upgrades/side-effects/echo-bugs-recovered-reply-history.md +67 -0
  465. package/upgrades/side-effects/feature-evidence-recording.eli16.md +3 -0
  466. package/upgrades/side-effects/feature-evidence-recording.md +66 -0
  467. package/upgrades/side-effects/feedback-reroute-admission-parity.md +77 -0
  468. package/upgrades/side-effects/job-alert-global-cap.md +66 -0
  469. package/upgrades/side-effects/mobile-console-device-signin.md +102 -0
  470. package/upgrades/side-effects/mobile-native-session-screen.md +88 -0
  471. package/upgrades/side-effects/operator-needs-first-screen.md +87 -0
  472. package/upgrades/side-effects/recommended-services-choices.md +50 -0
  473. package/upgrades/side-effects/rerouted-lifetime-active-process.md +63 -0
  474. package/upgrades/side-effects/undated-resurfacer-e2e-wait.eli16.md +5 -0
  475. package/upgrades/side-effects/undated-resurfacer-e2e-wait.md +9 -0
@@ -7,7 +7,7 @@
7
7
  // repair / retry / cancel POST /subscription-relogin/repair-cell operator session
8
8
 
9
9
  import { buildAccountsPage, frameworkName } from './accounts-model.js';
10
- import { el, setKids, pageHead, sheet, closeSheet, story, details, note, cantTell, askPin, forgetOperatorSession, firstPaint, arrivalScroll } from './ui.js';
10
+ import { el, setKids, pageHead, sheet, closeSheet, story, details, note, cantTell, askPin, forgetOperatorSession, firstPaint, arrivalScroll, markNeed } from './ui.js';
11
11
  import { legacyHref } from './nav-model.js';
12
12
  import { verifiedDisplayName } from './display-identity.js';
13
13
 
@@ -60,6 +60,7 @@ export async function renderAccounts(main, ctx) {
60
60
  return row;
61
61
  })))));
62
62
 
63
+ markNeed(needs, 'accounts');
63
64
  setKids(main,
64
65
  pageHead({ eyebrow: 'Capacity · Accounts', headline: page.headline, sub: page.sub, tone: page.tone, facts: page.facts }),
65
66
  page.partial ? note(page.partial) : null,
@@ -315,7 +315,7 @@ export function waitingCards(pairing, now = Date.now()) {
315
315
  for (const n of pairing.restartNotices) cards.push({ kind: 'restart', id: n.id, title: 'An invite was cleared by a restart', body: `An invite from ${claimed(n.claimedAgent, n.claimedOwner)} was waiting for your approval when I restarted, and it was cleared (its secret lived only in memory). Paste it again, or forward it to my Telegram, before it expires at ${localTime(n.exp, now)}.`, actions: ['dismiss'] });
316
316
  for (const a of pairing.alarms) {
317
317
  const secondName = verifiedDisplayName(a.secondAccept && a.secondAccept.agent);
318
- cards.push({ kind: 'alarm', iid: a.iid, title: 'Warning: your invite got out', body: `Two agents used the same invite, so I paused the first one: it cannot receive anything secret. The second ${secondName ? `calls itself ${secondName}` : 'has no verified name'}. Make a new invite and send it privately.`, actions: [] });
318
+ cards.push({ kind: 'alarm', iid: a.iid, title: 'Warning: your invite got out', body: `Two agents used the same invite, so I paused the first one: it cannot receive anything secret. The second ${secondName ? `calls itself ${secondName}` : 'has no verified name'}. Make a new invite and send it privately.`, actions: ['new-invite'] });
319
319
  }
320
320
  for (const p of pairing.pendingAccepts) cards.push({ kind: 'accept', iid: p.iid, name: verifiedDisplayName(p.claimedAgent) || 'An unnamed agent', title: `Pair with ${claimed(p.claimedAgent, p.claimedOwner)}?`, body: `Only approve if you know who sent you this invite. Check code (optional, to read to them): ${p.checkCode || '—'}. It expires at ${localTime(p.exp, now)}.`, actions: ['approve', 'decline'] });
321
321
  for (const i of pairing.finishPrompts) cards.push({ kind: 'finish', iid: i.iid, name: verifiedDisplayName(i.acceptor && i.acceptor.agent) || 'An unnamed agent', title: `${verifiedDisplayName(i.acceptor && i.acceptor.agent) || 'An unnamed agent'} accepted your invite.`, body: `${claimed(i.acceptor && i.acceptor.agent, i.acceptor && i.acceptor.owner).replace(/^the/, 'The')} accepted it. Finish only if that is who you sent it to.`, actions: ['finish', 'not-them'] });
@@ -341,7 +341,7 @@ export function buildAgentsPage({ health, pairingRes, invitesRes, peersRes, conv
341
341
  return {
342
342
  mode: 'off', certain: relayOff, relay, pairing, agents, waiting, conversations, facts: [], topicNames,
343
343
  headline: relayOff ? 'The agent network is off on this agent.' : 'The agent network looks off on this agent.',
344
- sub: `${relayOff ? '' : `${relay.word}. `}Pairing is turned off too. To turn it on, ask me to “connect to the agent network”, or tap Turn on pairing below; that restarts my server, and this page reconnects by itself.`,
344
+ sub: `${relayOff ? '' : `${relay.word}. `}Pairing is turned off too. To turn it on, ask me to “connect to the agent network”, or tap Turn on pairing; that restarts my server, and this page reconnects by itself.`,
345
345
  tone: 'muted', notes: [],
346
346
  };
347
347
  }
@@ -378,7 +378,7 @@ export function buildAgentsPage({ health, pairingRes, invitesRes, peersRes, conv
378
378
  const subParts = [];
379
379
  if (waiting.length && !relay.connected && relay.state !== 'cant-tell' && !relayOff) subParts.push(`${relay.word}.`);
380
380
  if (pairing.state === 'elsewhere') subParts.push(pairing.holder ? `Pairing runs on ${pairing.holder.name}; open the console there to make or approve invites.` : 'Pairing runs on another of my machines.');
381
- if (pairing.state === 'off' && agents.length) subParts.push(paired.length ? 'Existing agents are listed below.' : agents.some(a => a.historyOnly) ? 'Past conversations are saved below.' : 'No agents are paired here.');
381
+ if (pairing.state === 'off' && agents.length) subParts.push(paired.length ? 'Existing agents are still listed.' : agents.some(a => a.historyOnly) ? 'Past conversations are still saved.' : 'No agents are paired here.');
382
382
  if (pairing.state === 'cant-tell') subParts.push(pairing.reason);
383
383
  if (!agents.length && pairing.state === 'ok' && !waiting.length) subParts.push('Connect another agent to start.');
384
384
  if (relay.identity && relay.identity.tone === 'bad') subParts.push(relay.identity.word + '.');
@@ -14,7 +14,7 @@
14
14
  import {
15
15
  buildAgentsPage, threadModel, inviteResultWords, pinResultWords, bearerResultWords, enableWords, bindRequest, bindResultWords, localTime,
16
16
  } from './agents-model.js';
17
- import { el, setKids, pageHead, section, sheet, closeSheet, story, details, note, cantTell, askPin, firstPaint } from './ui.js';
17
+ import { el, setKids, pageHead, section, sheet, closeSheet, story, details, note, cantTell, askPin, firstPaint, markNeed } from './ui.js';
18
18
  import { legacyHref } from './nav-model.js';
19
19
  import { verifiedDisplayName, uniqueNamedChoices } from './display-identity.js';
20
20
  import { topicNameMap } from './topic-names.js';
@@ -70,16 +70,18 @@ export async function renderAgents(main, ctx) {
70
70
  const connect = el('button', { class: 'btn-primary btn-inline connect-btn', type: 'button', text: '+ Connect another agent', onclick: () => openConnect(deps) });
71
71
  setKids(main, el('div', { class: 'agents-page' },
72
72
  pageHead({ eyebrow: EYEBROW, headline: page.headline, sub: page.sub, tone: page.tone, facts: page.facts }),
73
- page.waiting.map((w) => waitingCard(w, deps)),
73
+ // Pairing being off is the thing to fix here, so its switch leads rather than following the lists.
74
+ page.pairing.state === 'off' ? turnOnCard(deps) : null,
75
+ page.waiting.map((w) => markNeed(waitingCard(w, deps), 'agents')),
74
76
  page.relay.connected || page.relay.state === 'cant-tell' ? null : relayLine(page.relay),
75
77
  page.relay.state === 'cant-tell' ? note(page.relay.word + '.') : null,
76
78
  page.notes.map((n) => note(n)),
77
79
  el('div', { class: 'page-sections' },
78
- agentRows.length || page.pairing.state === 'ok' ? section({ id: 'agents', title: 'Agents', rows: agentRows, empty: 'No other agents yet. Connect one below.', onRow: (r) => openAgent(r.agent, deps) }) : null,
80
+ agentRows.length || page.pairing.state === 'ok' ? section({ id: 'agents', title: 'Agents', rows: agentRows, empty: 'No other agents yet. Use Connect another agent to add one.', onRow: (r) => openAgent(r.agent, deps) }) : null,
79
81
  pastRows.length ? section({ id: 'past-agents', title: 'Past conversations', collapsed: true, rows: pastRows, onRow: (r) => openAgent(r.agent, deps) }) : null,
80
82
  page.pairing.openInvites.length ? section({ id: 'open-invites', title: 'Invites not used yet', collapsed: true, rows: page.pairing.openInvites.map((i) => ({ key: `inv:${i.iid}`, main: 'Unused invitation', sub: `works once · expires at ${localTime(i.exp)}`, invite: i })), onRow: (r) => openInvite(r.invite, deps) }) : null,
81
83
  ),
82
- page.pairing.state === 'off' ? turnOnCard(deps) : page.pairing.state === 'elsewhere' ? note(page.pairing.holder ? `Pairing runs on ${page.pairing.holder.name}; open the console there to connect another agent.` : 'Pairing runs on another of my machines; connect another agent from there.') : el('div', { class: 'page-primary' }, connect),
84
+ page.pairing.state === 'elsewhere' ? note(page.pairing.holder ? `Pairing runs on ${page.pairing.holder.name}; open the console there to connect another agent.` : 'Pairing runs on another of my machines; connect another agent from there.') : el('div', { class: 'page-primary' }, connect),
83
85
  footLink(),
84
86
  ));
85
87
  }
@@ -158,6 +160,7 @@ function waitingCard(w, deps) {
158
160
  b.addEventListener('click', () => pinned('Finish pairing', `/threadline/pairing/invites/${encodeURIComponent(w.iid)}/finish`, `Finish pairing with ${w.name}: I pin it as verified.`, 'Finishing. Checking we can talk…', b));
159
161
  acts.append(b);
160
162
  }
163
+ if (w.actions.includes('new-invite')) acts.append(el('button', { class: 'btn-primary btn-inline', type: 'button', text: 'Make a new invite', onclick: () => openConnect(deps) }));
161
164
  if (w.actions.includes('not-them')) {
162
165
  const b = el('button', { class: 'btn-quiet btn-danger-outline', type: 'button', text: 'Not them' });
163
166
  b.addEventListener('click', () => {
@@ -772,6 +772,7 @@ button.run-mark:focus-visible { outline: 2px solid var(--accent); outline-offset
772
772
  /* ── Development (under-the-hood.md: Shipping + Feedback + Pace) ─────────── */
773
773
  .tiles-dev { margin-top: var(--s-3); }
774
774
  .dev-page .need-card { margin-bottom: var(--s-3); }
775
+ .dev-page .need-line { display: block; margin-bottom: var(--s-3); }
775
776
  .ideas-page .need-card { margin: var(--s-3) 0; }
776
777
  .ideas-page .need-sub { margin: 0; }
777
778
  .ideas-page .need-sub:empty { display: none; }
@@ -10,6 +10,7 @@ import {
10
10
  } from './home-model.js';
11
11
  import { GROUPS, legacyHref, icon, tabHref, LEGACY_DASHBOARD } from './nav-model.js';
12
12
  import { fillPartial } from './home-model.js';
13
+ import { NEED_SOURCES, surfacedNeeds } from './needs-model.js';
13
14
  import { renderPromises, topicLink } from './promises.js';
14
15
  import { renderMachines } from './machines.js';
15
16
  import { renderAccounts } from './accounts.js';
@@ -30,6 +31,7 @@ import { renderSessions } from './sessions.js';
30
31
  import { renderMaturation, maturationTile } from './maturation-board.js';
31
32
  import { renderIdeas, ideasTile } from './ideas.js';
32
33
  import { paceCard } from './pace.js';
34
+ import { takeTicket, signInAtBoot } from './ticket-model.js';
33
35
  import { sheetIsOpen, closeSheet, unlockCheck, machineIssue, MACHINES_PAGE, newArrival, refreshHeld } from './ui.js';
34
36
 
35
37
  const TOKEN_KEY = 'instar_token'; // shared with the legacy dashboard
@@ -91,11 +93,12 @@ function toggleTheme() {
91
93
  // told "can't tell". Cross-machine reads in particular can be slow for a moment.
92
94
  const MIN_TIMEOUT_MS = 20000;
93
95
  const RETRY_DELAY_MS = 0; // retry at once: a timeout already waited MIN_TIMEOUT_MS
94
- async function getOnce(path, timeoutMs) {
96
+ async function getOnce(path, timeoutMs, soft403 = false) {
95
97
  const ctl = new AbortController();
96
98
  const t = setTimeout(() => ctl.abort(), timeoutMs);
97
99
  try {
98
100
  const r = await fetch(path, { headers: { Authorization: `Bearer ${token}` }, signal: ctl.signal, cache: 'no-store' });
101
+ if (r.status === 403 && soft403) return failure('the server answered 403');
99
102
  if (r.status === 401 || r.status === 403) { signOut(); return failure('not signed in'); }
100
103
  if (r.status === 503) return failure('switched off on this agent');
101
104
  if (!r.ok) return { ...failure(`the server answered ${r.status}`), retryable: r.status >= 500 };
@@ -468,7 +471,8 @@ function loadHome() {
468
471
  const { models, raw, results } = run;
469
472
  const paint = (id) => {
470
473
  // "What needs you" is the one answer: queue items plus promises waiting on the operator.
471
- const model = id === 'needs' ? combineNeeds(raw.needs, raw.promised) : raw[id];
474
+ const model = id === 'needs' ? combineNeeds(raw.needs, raw.promised, surfacedNeeds(run.sources)) : raw[id];
475
+ if (!model) return; // nothing read yet for this card
472
476
  const shown = paintCard(CARDS.find((c) => c.id === id), model) || model;
473
477
  models[id] = shown;
474
478
  // The action queue must use the same last-known promise reading as its
@@ -527,6 +531,24 @@ function loadHome() {
527
531
  };
528
532
  attempt();
529
533
  };
534
+ // What needs the operator on other pages (needs-model.js): one in-flight read per source, no
535
+ // retry timer of its own (the next tick is the retry), and a 403 here never signs anyone out.
536
+ run.tick = (run.tick || 0) + 1;
537
+ for (const source of NEED_SOURCES) {
538
+ if (run.sourceBusy[source.id]) continue;
539
+ if (source.when && !source.when(run.sources)) continue;
540
+ if (source.everyTicks && run.sourceAt[source.id] && run.tick - run.sourceAt[source.id] < source.everyTicks) continue;
541
+ run.sourceAt[source.id] = run.tick;
542
+ run.sourceBusy[source.id] = true;
543
+ const keys = Object.keys(source.reads);
544
+ Promise.all(keys.map((k) => getOnce(source.reads[k], Math.max(TIMEOUT_MS, MIN_TIMEOUT_MS), true))).then((reads) => {
545
+ run.sourceBusy[source.id] = false;
546
+ if (!run.active || run.document !== document) return;
547
+ run.sources[source.id] = { ...(run.sources[source.id] || {}), results: Object.fromEntries(keys.map((k, i) => [k, reads[i]])) };
548
+ paint('needs');
549
+ paintHeadline(models);
550
+ });
551
+ }
530
552
  // Every source has one in-flight request and its own bounded retry timer.
531
553
  // A slow source cannot block another card or start overlapping refreshes.
532
554
  read('pool', '/pool', POOL_TIMEOUT_MS);
@@ -542,7 +564,7 @@ function loadHome() {
542
564
  }
543
565
 
544
566
  function renderHome(main) {
545
- homeRun = { active: true, document, models: Object.fromEntries(CARDS.map((c) => [c.id, { state: 'loading' }])), raw: {}, results: {}, pending: {}, failures: {} };
567
+ homeRun = { active: true, document, models: Object.fromEntries(CARDS.map((c) => [c.id, { state: 'loading' }])), raw: {}, results: {}, pending: {}, failures: {}, sources: {}, sourceBusy: {}, sourceAt: {} };
546
568
  main.replaceChildren(
547
569
  el('section', { class: 'hero' },
548
570
  el('div', { class: 'eyebrow', id: 'heroTime', text: 'Checking in' }),
@@ -696,5 +718,14 @@ function previewBanner() {
696
718
  applyTheme(readTheme());
697
719
  await loadPreviewInfo();
698
720
  try { token = localStorage.getItem(TOKEN_KEY) || ''; } catch { token = ''; }
699
- if (await validToken(token)) start(); else renderUnlock();
721
+ // An owner's paired phone opens signed in without the PIN (mobile-console-device-signin §3.5).
722
+ let ticket = '';
723
+ try { ticket = takeTicket(sessionStorage); } catch { ticket = ''; }
724
+ const signIn = await signInAtBoot({ storedToken: token, validToken, ticket, fetchFn: (p, o) => fetch(p, o) });
725
+ if (signIn.via === 'ticket') {
726
+ // Stored exactly as the PIN unlock stores it.
727
+ try { localStorage.setItem(TOKEN_KEY, signIn.token); } catch { /* storage blocked */ }
728
+ }
729
+ token = signIn.token;
730
+ if (signIn.via === 'pin') renderUnlock(); else start();
700
731
  })();
@@ -387,6 +387,18 @@ export function buildRouting(res) {
387
387
  * Build the whole page from the reads: { spend, caps, status, metrics, insights, tokens, claude, codex, orphans, chains, names }.
388
388
  * Returns { headline, sub, tone, facts, window, recorded, limits, checks, conversations, routing }.
389
389
  */
390
+ /**
391
+ * Spending limits the operator turned on that are not enforcing: they believe money is capped
392
+ * and it is not. Only this counts: a route they froze is their own choice, and a pending
393
+ * restart is the agent's job. The only control is in the legacy Spend tab, so that is the action.
394
+ */
395
+ export function spendingNeed(statusRes) {
396
+ if (!statusRes || !statusRes.ok) return null;
397
+ const st = (statusRes.data || {}).lifecycleState;
398
+ if (st !== 'probe-failed' && st !== 'construction-failed') return null;
399
+ return { title: 'Spending limits are on but not enforcing', why: 'Paid use is not capped right now, even though limits are turned on.' };
400
+ }
401
+
390
402
  export function buildCostsPage(res, now = Date.now(), { window = DEFAULT_WINDOW } = {}) {
391
403
  const r = res || {};
392
404
  const w = windowParams(window, now);
@@ -3,8 +3,8 @@
3
3
  // switch stay in the legacy Spend tab until their native sheets ship. Every value
4
4
  // reaches the DOM through textContent; the model (costs-model.js) decides everything.
5
5
 
6
- import { buildCostsPage, WINDOWS, DEFAULT_WINDOW, GRAINS, windowParams, spendQuery, committedLine, basisWord, fmtTokens, fmtUsd, modelWord, doorWord } from './costs-model.js';
7
- import { el, setKids, pageHead, section, sheet, story, details, note, cantTell, firstPaint } from './ui.js';
6
+ import { buildCostsPage, WINDOWS, DEFAULT_WINDOW, GRAINS, windowParams, spendQuery, committedLine, basisWord, fmtTokens, fmtUsd, modelWord, doorWord, spendingNeed } from './costs-model.js';
7
+ import { el, setKids, pageHead, section, sheet, story, details, note, cantTell, firstPaint, markNeed } from './ui.js';
8
8
  import { legacyHref } from './nav-model.js';
9
9
  import { sessionLabel } from './home-model.js';
10
10
 
@@ -43,8 +43,11 @@ export async function renderCosts(main, ctx) {
43
43
  timeControl.classList.add('time-control');
44
44
  hero.insertBefore(timeControl, hero.querySelector('.facts'));
45
45
 
46
+ const spending = spendingNeed(status);
46
47
  setKids(main, el('div', { class: 'costs-page' },
47
48
  hero,
49
+ spending ? markNeed(el('div', { class: 'need-card' }, el('span', { class: 'need-title', text: spending.title }), el('span', { class: 'need-sub', text: spending.why }),
50
+ el('a', { class: 'btn-primary btn-inline', href: legacyHref('spend'), text: 'Open the spending controls' })), 'spending') : null,
48
51
  el('div', { class: 'page-controls' }, routingLink),
49
52
  el('div', { class: 'page-sections' },
50
53
  recordedSection(page, repaint),
@@ -75,7 +78,7 @@ function recordedSection(page, repaint) {
75
78
  const more = el('details', { class: 'raw more-detail' }, el('summary', { text: 'More detail' }),
76
79
  el('p', { class: 'card-line', text: 'How the ledger is grouped. By hour and by day follow the time window above; by month and all time cover every retained day.' }), grainChips,
77
80
  r.horizonNote ? el('p', { class: 'card-note', text: r.horizonNote }) : null,
78
- el('p', { class: 'card-note', text: `Source: ${r.source || 'the feature-metrics ledger'}${r.lastReconcileAt ? `, reconciled ${new Date(r.lastReconcileAt).toLocaleString([], { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' })}` : ''}${r.rollupMaintained ? '' : ' · rollup not maintained'}. Reporting figures are recomputed at current prices; the committed figures below are what a limit enforces.` }));
81
+ el('p', { class: 'card-note', text: `Source: ${r.source || 'the feature-metrics ledger'}${r.lastReconcileAt ? `, reconciled ${new Date(r.lastReconcileAt).toLocaleString([], { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' })}` : ''}${r.rollupMaintained ? '' : ' · rollup not maintained'}. Reporting figures are recomputed at current prices; the committed figures are what a limit enforces.` }));
79
82
  wrap.append(more);
80
83
  }
81
84
  // Paid routes: committed limits and committed spend, visibly apart from the reporting rows.
@@ -74,14 +74,20 @@ function shippingRow(group, pr, sub, end, tone, record, repo, stage) {
74
74
  }
75
75
 
76
76
  /** The watcher's own health line (slow vs stuck vs silently failed are separate states). */
77
+ /** A stop only the agent's own records hold: it releases this itself, so nothing is asked of the operator yet. */
78
+ export function agentOwnedStop(w) {
79
+ const stops = w && w.gate && Array.isArray(w.gate.rollbackStops) ? w.gate.rollbackStops : [];
80
+ return stops.length > 0 && stops.every((s) => s && s.class === 'agent');
81
+ }
82
+
77
83
  export function watcherLine(w, now = Date.now()) {
78
84
  if (!w) return { text: 'Merge watcher: no reading.', tone: 'unknown' };
79
85
  if (w.invariantOk === false) return { text: 'Merge watcher: its safety invariant failed; it will not arm anything until that clears.', tone: 'bad' };
80
86
  if (w.breaker && w.breaker.open) return { text: 'Merge watcher paused itself after repeated failures.', tone: 'bad' };
81
87
  const gate = w.gate || {};
82
88
  const stops = Array.isArray(gate.rollbackStops) ? gate.rollbackStops : [];
83
- if (stops.length && stops.every((s) => s && s.class === 'agent')) return { text: 'Auto-merging is stopped by the agent for its own reason. It releases this itself when its condition is met; you can also turn it back on below.', tone: 'attn', stopped: true };
84
- if (Array.isArray(gate.rollback) && gate.rollback.length) return { text: 'Auto-merging is stopped (you or the agent rolled it back). Turn it back on below.', tone: 'attn', stopped: true };
89
+ if (stops.length && stops.every((s) => s && s.class === 'agent')) return { text: 'Auto-merging is stopped by the agent for its own reason. It releases this itself when its condition is met; you can also turn it back on here.', tone: 'attn', stopped: true };
90
+ if (Array.isArray(gate.rollback) && gate.rollback.length) return { text: 'Auto-merging is stopped (you or the agent rolled it back).', tone: 'attn', stopped: true };
85
91
  if (!w.lastTickAt) return { text: 'Merge watcher has not checked yet.', tone: 'unknown' };
86
92
  const age = now - w.lastTickAt;
87
93
  if (age > 2 * WATCHER_CADENCE_MS) return { text: `Merge watcher last checked ${ago(w.lastTickAt, now)}, usually every ${Math.round(WATCHER_CADENCE_MS / 60000)} min.`, tone: 'attn' };
@@ -464,7 +470,7 @@ export function buildFeedback(res = {}, now = Date.now()) {
464
470
  state = 'cant-tell';
465
471
  const k = failed.find((x) => todoFeeds.includes(x));
466
472
  headline = `I can’t tell whether feedback needs you: ${names[k]} didn’t answer (${reasonOf(res[k])}).`; tone = 'muted';
467
- if (todos.length) sub = `${plural(todos.length, 'thing')} that ${todos.length === 1 ? 'does' : 'do'} need you ${todos.length === 1 ? 'is' : 'are'} listed below from the reads that answered.`;
473
+ if (todos.length) sub = `${plural(todos.length, 'thing')} that ${todos.length === 1 ? 'does' : 'do'} need you ${todos.length === 1 ? 'is' : 'are'} shown, from the reads that answered.`;
468
474
  } else if (todos.length) {
469
475
  headline = todos[0].sentence; tone = 'attn';
470
476
  const more = todos.length - 1;
@@ -13,7 +13,7 @@ import {
13
13
  buildShipping, buildPace, buildFeedback, developmentHeadline, stoppedLead, foldFromHash, maintainerMode, authorityCard, holdWords, verifyWords, rollbackWords,
14
14
  readinessAuthorityRequest, triageAuthorityRequest, commitRequest, authorityResultWords, commitResultWords, newDecisionRef, PACE_METRICS, DEV_TOOLS_KEY,
15
15
  } from './development-model.js';
16
- import { el, setKids, pageHead, section, sheet, closeSheet, story, details, note, cantTell, askPin, firstPaint, arrivalScroll } from './ui.js';
16
+ import { el, setKids, pageHead, section, sheet, closeSheet, story, details, note, cantTell, askPin, firstPaint, arrivalScroll, markNeed } from './ui.js';
17
17
  import { legacyHref } from './nav-model.js';
18
18
 
19
19
  const EYEBROW = 'Work · Development';
@@ -120,7 +120,10 @@ function paint() {
120
120
  // The one thing that needs the operator comes first; the rest wait as a counted line.
121
121
  const todos = feedback.todos;
122
122
  const first = todos[0] || null;
123
- const lead = first ? todoCard(first, deps, true) : null;
123
+ const lead = first ? markNeed(todoCard(first, deps, true), 'feedback') : null;
124
+ const approvals = shipping.rows ? shipping.rows.filter((r) => r.group === 'needs').length : 0;
125
+ // Approvals live in the Shipping fold, which is often closed: the lead line opens it and goes there.
126
+ const approvalsLine = approvals ? markNeed(el('button', { class: 'btn-primary btn-inline need-line', type: 'button', text: `${approvals === 1 ? 'A pull request needs' : `${approvals} pull requests need`} your approval: show ${approvals === 1 ? 'it' : 'them'}`, onclick: () => { view.open = 'shipping'; paint(); arrivalScroll(main.querySelector('#sec-shipping'), { key: `tap:${Date.now()}` }); } }), 'shipping-approvals') : null;
124
127
  const stopped = stoppedCard(shipping, deps);
125
128
  const moreLine = todos.length > 1 ? el('button', { class: 'link-btn older-line', type: 'button', text: `${todos.length - 1} more ${todos.length === 2 ? 'thing needs' : 'things need'} you ›`, onclick: () => openMore(todos.slice(1), deps) }) : null;
126
129
 
@@ -128,7 +131,7 @@ function paint() {
128
131
  if (!view.open) view.open = feedback.tone === 'attn' || feedback.state === 'cant-tell' ? 'feedback' : 'shipping';
129
132
  setKids(main, el('div', { class: 'dev-page' },
130
133
  pageHead({ eyebrow: EYEBROW, headline: h.text, sub: h.sub, tone: h.tone }),
131
- stopped, lead, moreLine,
134
+ stopped, lead, approvalsLine, moreLine,
132
135
  el('div', { class: 'page-sections' },
133
136
  fold({ id: 'shipping', title: 'Shipping', line: shipping.line, tone: shipping.tone, body: () => shippingBody(shipping, deps) }),
134
137
  fold({ id: 'feedback', title: 'Feedback', line: feedback.line, tone: feedback.tone, body: () => feedbackBody(feedback, deps) }),
@@ -176,7 +179,7 @@ function backOnButton(s, deps, primary = false) {
176
179
  function stoppedCard(s, deps) {
177
180
  const c = stoppedLead(s);
178
181
  if (!c) return null;
179
- return el('div', { class: 'need-card stopped-lead' }, el('span', { class: 'need-title', text: c.title }), el('span', { class: 'need-sub', text: c.detail }), el('span', { class: 'need-sub', text: c.why }), backOnButton(s, deps, true));
182
+ return el('div', { class: 'need-card stopped-lead', 'data-operator-need': 'merging' }, el('span', { class: 'need-title', text: c.title }), el('span', { class: 'need-sub', text: c.detail }), el('span', { class: 'need-sub', text: c.why }), backOnButton(s, deps, true));
180
183
  }
181
184
 
182
185
  // ── to-dos (Feedback approvals and links) ────────────────────────────────────
@@ -466,29 +466,45 @@ export function pullRequestRefs(row) {
466
466
  return [...out];
467
467
  }
468
468
 
469
- export function combineNeeds(needs, promised) {
470
- if (!needs || !Number.isFinite(needs.count)) return needs; // loading or can't tell: say so as-is
471
- if (!promised || promised.state === 'loading') return needs;
472
- const pCount = Number.isFinite(promised.count) ? promised.count : 0;
469
+ /**
470
+ * The one count. Surfaced rows (needs-model.js) merge in even while the queue or the promises
471
+ * are unread, so a slow read cannot hide a known need. A row naming the same thing as a queue
472
+ * item (its refs appear in that item) is dropped by identity: the queue item carries the action.
473
+ */
474
+ export function combineNeeds(needs, promised, surfaced) {
475
+ // Nothing surfaced was asked for: the queue alone, exactly as before.
476
+ if (!surfaced && (!needs || !Number.isFinite(needs.count) || !promised || promised.state === 'loading')) return needs;
477
+ const known = surfaced && Array.isArray(surfaced.rows) ? surfaced : { rows: [], pending: [], failed: [] };
478
+ const pending = Array.isArray(known.pending) && known.pending.length > 0;
479
+ const failed = Array.isArray(known.failed) && known.failed.length > 0;
480
+ if (!needs || !Number.isFinite(needs.count)) {
481
+ if (!known.rows.length) return needs ? { ...needs, surfacedPending: pending } : needs; // loading or can't tell: say so as-is
482
+ needs = { ...(needs || {}), count: 0, all: [], rows: [], line: '', unreadQueue: true };
483
+ }
473
484
  const queue = needs.all || needs.rows || [];
485
+ const words = (r) => `${r.main || ''} ${r.sub || ''}`.toLowerCase();
486
+ const hasToken = (text, ref) => { const at = text.indexOf(ref); return at >= 0 && !/[\w.@-]/.test(text[at + ref.length] || ' ') && !/[\w.@-]/.test(text[at - 1] || ' '); };
487
+ const rows = known.rows.filter((r) => !(r.refs || []).some((ref) => queue.some((q) => hasToken(words(q), String(ref).toLowerCase()))));
474
488
  // A promise that waits on the same pull request as a queue item is the same
475
489
  // ask twice (the approval itself, and my promise to merge after it): show the
476
490
  // queue item, which carries the action, and drop the promise row.
477
491
  const queueRefs = new Set(queue.flatMap(pullRequestRefs));
478
- const waitingAll = (promised.all || []).filter((r) => r.tone === 'attn').slice(0, pCount);
492
+ const promisesRead = promised && promised.state !== 'loading';
493
+ const pCount = promisesRead && Number.isFinite(promised.count) ? promised.count : 0;
494
+ const waitingAll = promisesRead ? (promised.all || []).filter((r) => r.tone === 'attn').slice(0, pCount) : [];
479
495
  const waiting = waitingAll.filter((r) => !pullRequestRefs(r).some((ref) => queueRefs.has(ref)))
480
496
  .map((r) => ({ ...r, sub: ['a promise', r.sub].filter(Boolean).join(' · ') }));
481
- const promisesKnown = Number.isFinite(promised.count);
482
- const count = needs.count + waiting.length;
483
- // Urgent queue items lead (the headline names the first row), then promises, then the rest.
484
- const all = [...queue.filter((r) => r.tone === 'bad'), ...waiting, ...queue.filter((r) => r.tone !== 'bad')];
497
+ const promisesKnown = !promisesRead || Number.isFinite(promised.count);
498
+ const count = needs.count + rows.length + waiting.length;
499
+ // Urgent queue items lead (the headline names the first row), then what is surfaced, then promises, then the rest.
500
+ const all = [...queue.filter((r) => r.tone === 'bad'), ...rows, ...waiting, ...queue.filter((r) => r.tone !== 'bad')];
485
501
  const notes = !promisesKnown ? "Couldn't check for promises waiting on you."
486
- : promised.state === 'stale' ? 'Promise information is from the last successful check.' : '';
502
+ : promisesRead && promised.state === 'stale' ? 'Promise information is from the last successful check.' : '';
487
503
  const queueLine = needs.line === 'The queue is clear.' || needs.line === 'Waiting on you.' ? null : needs.line;
488
504
  const line = count ? '' : [notes, queueLine].filter(Boolean).join(' ') || 'Nothing is waiting on you.';
489
- const partial = needs.state === 'partial' || !promisesKnown || promised.state === 'stale' || !!promised.coverage;
505
+ const partial = needs.state === 'partial' || !promisesKnown || needs.unreadQueue || failed || (promisesRead && (promised.state === 'stale' || !!promised.coverage));
490
506
  return {
491
- ...needs, combined: true, count, all, rows: all.slice(0, MAX_ROWS), line,
507
+ ...needs, combined: true, count, all, rows: all.slice(0, MAX_ROWS), line, surfacedPending: pending, surfacedCount: rows.length,
492
508
  note: count ? null : needs.note,
493
509
  statusNote: count ? [needs.statusNote, notes, queueLine].filter(Boolean).join(' ') : needs.statusNote,
494
510
  coverage: partial ? 'partial view' : needs.coverage,
@@ -520,7 +536,7 @@ export function headline(allCards) {
520
536
  const otherAttn = attention.length;
521
537
  const loading = list.filter((c) => c.state === 'loading').length;
522
538
  // Never announce a count, or "all quiet", before both counting cards answered.
523
- if (loading === list.length || (cards.needs && cards.needs.state === 'loading') || (cards.promised && cards.promised.state === 'loading')) return { text: 'Checking in…', tone: 'muted' };
539
+ if (loading === list.length || (cards.needs && cards.needs.state === 'loading' && !Number.isFinite(cards.needs.count)) || (cards.promised && cards.promised.state === 'loading' && !(cards.needs && cards.needs.surfacedCount > 0)) || (cards.needs && cards.needs.surfacedPending && !(cards.needs.count > 0))) return { text: 'Checking in…', tone: 'muted' };
524
540
  // A known action survives incomplete coverage. Coverage belongs under agent
525
541
  // status, not in the sentence describing the action.
526
542
  const count = cards.needs && cards.needs.combined ? known(cards.needs) : known(cards.needs) + known(cards.promised);
@@ -537,7 +553,7 @@ export function headline(allCards) {
537
553
  const context = withContext ? `${withContext[2].replace(/,\s*/g, ' · ')}.` : null;
538
554
  return { text: title || 'Open what needs your attention', tone: 'attn',
539
555
  sub: [context, first?.action ? 'Tap to review and respond.' : null,
540
- count > 1 ? `${count - 1 === 1 ? 'One more thing needs' : `${count - 1} more things need`} you, listed below.` : null].filter(Boolean).join(' '),
556
+ count > 1 ? `${count - 1 === 1 ? 'One more thing needs' : `${count - 1} more things need`} you.` : null].filter(Boolean).join(' '),
541
557
  action: first?.action || null };
542
558
  }
543
559
  // Only what the operator can act on takes the headline. My own areas
@@ -9,7 +9,7 @@ import {
9
9
  issueRequest, revokeRequest, grantRequest, approveRequest, denyRequest, resultWords,
10
10
  FLOOR_ACTIONS, GRANT_DURATIONS, AUTHORITIES_TEMPLATE, humanAction, defaultExpiryLocal,
11
11
  } from './mandates-model.js';
12
- import { el, setKids, pageHead, section, sheet, closeSheet, story, details, note, cantTell, askPin, firstPaint } from './ui.js';
12
+ import { el, setKids, pageHead, section, sheet, closeSheet, story, details, note, cantTell, askPin, firstPaint, markNeed } from './ui.js';
13
13
  import { legacyHref } from './nav-model.js';
14
14
  import { verifiedDisplayName, uniqueNamedChoices } from './display-identity.js';
15
15
 
@@ -50,7 +50,7 @@ export async function renderMandates(main, ctx) {
50
50
 
51
51
  const parts = [pageHead({ eyebrow: 'People · Permission slips', headline: h.text, tone: h.tone, facts: mandatesFacts({ slips, approvals, audit }) })];
52
52
  // A broken chain is an incident: it leads the page, above everything else.
53
- if (audit.ok && audit.chain.state === 'broken') parts.push(el('section', { class: 'incident', role: 'alert' }, el('span', { class: 'incident-title', text: 'Treat as an incident' }), el('p', { class: 'incident-text', text: `${audit.chain.words} Every decision below is suspect until the record is checked.` })));
53
+ if (audit.ok && audit.chain.state === 'broken') parts.push(el('section', { class: 'incident', role: 'alert' }, el('span', { class: 'incident-title', text: 'Treat as an incident' }), el('p', { class: 'incident-text', text: `${audit.chain.words} Every decision on this page is suspect until the record is checked.` })));
54
54
  const sections = [];
55
55
  if (!approvals.hidden) sections.push(approvalsBlock(approvals, deps));
56
56
  if (!slips.ok) sections.push(el('section', { class: 'list-section', id: 'sec-active' }, el('div', { class: 'list-head' }, el('h2', { class: 'eyebrow', text: 'Active slips' })), cantTell(slips.reason)));
@@ -70,7 +70,7 @@ function approvalsBlock(approvals, deps) {
70
70
  if (!approvals.ok) { wrap.append(el('p', { class: 'card-note', text: approvals.reason })); return wrap; }
71
71
  if (!approvals.rows.length) { wrap.append(el('p', { class: 'empty-line', text: 'Nothing is waiting for your approval.' })); return wrap; }
72
72
  for (const r of approvals.rows) wrap.append(approvalCard(r, deps));
73
- return wrap;
73
+ return markNeed(wrap, 'approvals');
74
74
  }
75
75
 
76
76
  function approvalCard(r, deps) {
@@ -0,0 +1,105 @@
1
+ // The one list of states that need the operator (docs/specs/operator-needs-first-screen.md).
2
+ // Home's count and headline come from it, and every state's own page leads with it, so
3
+ // neither can miss one. Pure: it reads nothing itself and touches no DOM. Rows are built with
4
+ // each page's own model, so Home and the page cannot disagree.
5
+
6
+ import { buildShipping, stoppedLead, operatorTodos, sourceState, agentOwnedStop } from './development-model.js';
7
+ import { buildAccountsPage } from './accounts-model.js';
8
+ import { pairingModel, waitingCards } from './agents-model.js';
9
+ import { buildApprovals } from './mandates-model.js';
10
+ import { buildPlansPage } from './plans-model.js';
11
+ import { buildRequests } from './secrets-model.js';
12
+ import { spendingNeed } from './costs-model.js';
13
+
14
+ const row = (key, main, sub, href, refs = []) => ({ key: `need:${key}`, main, sub: sub || '', tone: 'attn', refs, action: { kind: 'href', href } });
15
+
16
+ /** A 200 that does not have the expected shape: it told us nothing, so never read it as "nothing waiting". */
17
+ const UNREADABLE = null;
18
+
19
+ export const NEED_SOURCES = [
20
+ { id: 'merging', page: 'work/development', reads: { watcher: '/green-pr-automerge' },
21
+ rows: ({ watcher }, now) => {
22
+ if (agentOwnedStop(watcher.data)) return []; // the agent releases this itself; the page still shows it
23
+ const lead = stoppedLead(buildShipping({ watcher }, now, {}));
24
+ return lead ? [row('merging', lead.title, lead.detail, '#/work/development')] : [];
25
+ } },
26
+ { id: 'shipping-approvals', page: 'work/development', reads: { watcher: '/green-pr-automerge' },
27
+ rows: ({ watcher }, now) => buildShipping({ watcher }, now, {}).rows.filter((r) => r.group === 'needs')
28
+ .map((r) => row(`pr:${r.pr}`, `Pull request #${r.pr} needs your approval on GitHub`, '', '#/work/development?s=shipping', [`#${r.pr}`, `PR ${r.pr}`])) },
29
+ // Four reads, on a slower beat, and only where the merge watcher says this is a maintainer's agent.
30
+ { id: 'feedback', page: 'work/development', everyTicks: 4, when: (known) => sourceState(known.merging && known.merging.results && known.merging.results.watcher) === 'ok',
31
+ reads: { readiness: '/feedback-factory/readiness-authorities/proposal', triageProposal: '/feedback-factory/triage/authority/proposal', execute: '/feedback-factory/execute/status', queue: '/feedback-factory/triage/queue' },
32
+ rows: (r) => operatorTodos(r).map((t, i) => row(`feedback:${t.action || t.kind}:${i}`, t.sentence, t.detail, '#/work/development?s=feedback')) },
33
+ { id: 'accounts', page: 'capacity/accounts', reads: { pool: '/subscription-pool?scope=pool', relogin: '/subscription-relogin?scope=pool' },
34
+ rows: ({ pool, relogin }, now) => {
35
+ const episodes = relogin.ok && Array.isArray(relogin.data && relogin.data.episodes) ? relogin.data.episodes : [];
36
+ const page = buildAccountsPage(pool, now, { episodes });
37
+ if (!page.ok) return pool.data && pool.data.enabled === false ? [] : UNREADABLE;
38
+ return page.needs.map((n) => row(`account:${n.key}`, n.title, n.sub, '#/capacity/accounts?s=needs', [String(n.label || '').split(' · ').pop()].filter(Boolean)));
39
+ } },
40
+ { id: 'agents', page: 'people/agents', reads: { pairing: '/threadline/pairing', invites: '/threadline/pairing/invites' },
41
+ rows: ({ pairing, invites }, now) => waitingCards(pairingModel(pairing, invites, undefined, now), now)
42
+ .map((c, i) => row(`agent:${c.kind}:${c.iid || c.id || i}`, c.title, '', '#/people/agents')) },
43
+ { id: 'approvals', page: 'people/mandates', reads: { approvals: '/authorization-requests?status=pending' },
44
+ rows: ({ approvals }) => { const a = buildApprovals(approvals); return a.ok ? a.rows.map((r) => row(r.key, r.headline, r.reason, '#/people/mandates?s=waiting')) : UNREADABLE; } },
45
+ { id: 'plans', page: 'work/plans', reads: { initiatives: '/initiatives', blockers: '/blockers' },
46
+ rows: ({ initiatives, blockers }, now) => {
47
+ const page = buildPlansPage({ initiatives, blockers }, now);
48
+ if (!page.ok) return UNREADABLE;
49
+ return page.sections.needsYou.map((r) => row(`plan:${r.key}`, r.main, r.needs, '#/work/plans?s=needsYou'));
50
+ } },
51
+ { id: 'secrets', page: 'people/secrets', reads: { pending: '/secrets/pending' },
52
+ // Never the card's own key or link: both carry the one-time drop token.
53
+ rows: ({ pending }, now) => {
54
+ const reqs = buildRequests(pending, { now, names: {}, origin: '' });
55
+ if (!reqs.ok) return UNREADABLE;
56
+ return reqs.waiting.map((c, i) => row(`secret:${i}`, `Waiting for you to supply: ${c.label}`, c.leftWords, '#/people/secrets?s=waiting'));
57
+ } },
58
+ { id: 'spending', page: 'capacity/costs', reads: { status: '/routing-spend/enable-status' },
59
+ rows: ({ status }) => { const n = spendingNeed(status); return n ? [row('spending', n.title, n.why, '#/capacity/costs')] : []; } },
60
+ ];
61
+
62
+ /**
63
+ * Where a console file says a state needs the operator and it is NOT one of the sources.
64
+ * Exact phrases, each with its reason, so a real need added to one of these pages still
65
+ * fails the floor. `file` + `phrase` must both match the line.
66
+ */
67
+ export const NEED_EXEMPT = [
68
+ { file: 'home-model.js', phrase: '', why: 'Home itself: this is the card the sources feed.' },
69
+ { file: 'console.js', phrase: 'What needs you', why: 'Home itself: the card title.' },
70
+ { file: 'nav-model.js', phrase: 'waiting', why: 'A navigation description of the page, not a state.' },
71
+ { file: 'promises-model.js', phrase: '', why: 'Already on Home; the operator decided 2026-11-02 that only promises due within 24 hours count.' },
72
+ { file: 'promises.js', phrase: '', why: 'As promises-model.js.' },
73
+ { file: 'allowed-model.js', phrase: 'will wait for your approval', why: 'Describes what a safety profile means, not something waiting now.' },
74
+ { file: 'pace-model.js', phrase: 'waiting on you', why: 'A verdict on queue pace; the items themselves appear in their own sources.' },
75
+ { file: 'sessions-model.js', phrase: 'Waiting on you', why: 'A comment-like note that sessions do not report this; nothing waits.' },
76
+ ];
77
+
78
+ const failedRead = (res) => sourceState(res) === 'failed';
79
+
80
+ /**
81
+ * known: id → { results, rows?, rowsAt? }, Home's reads so far. Returns the rows that need the
82
+ * operator, plus which sources are unread (say "checking", never "quiet") or failed (say
83
+ * partial, never "quiet"). A failed read keeps its last good rows for graceMs. 503 means off.
84
+ */
85
+ export function surfacedNeeds(known, now = Date.now(), graceMs = 120000) {
86
+ const out = { rows: [], pending: [], failed: [] };
87
+ for (const s of NEED_SOURCES) {
88
+ const k = known && known[s.id];
89
+ if (s.when && !s.when(known || {})) continue;
90
+ if (!k || !k.results) { if (!s.when) out.pending.push(s.id); continue; } // a conditional source is read on a later tick and never holds the headline
91
+ const reads = Object.values(k.results);
92
+ if (reads.every((r) => sourceState(r) === 'off')) continue; // switched off here; a page's builder handles one optional read being off
93
+ if (reads.some(failedRead)) {
94
+ out.failed.push(s.id);
95
+ if (k.rows && now - (k.rowsAt || 0) <= graceMs) out.rows.push(...k.rows);
96
+ continue;
97
+ }
98
+ const rows = s.rows(k.results, now);
99
+ if (rows === UNREADABLE) { out.failed.push(s.id); if (k.rows && now - (k.rowsAt || 0) <= graceMs) out.rows.push(...k.rows); continue; }
100
+ k.rows = rows;
101
+ k.rowsAt = now;
102
+ out.rows.push(...rows);
103
+ }
104
+ return out;
105
+ }
@@ -7,7 +7,7 @@
7
7
  import {
8
8
  buildPlansPage, roundsTrack, projectActions, resultWords, refusalWords, driftSentence, linkChips, stepList, blockerStory, reasonWord, statusWord, PROJECT_DETAIL_CAP, OPEN_STATUSES,
9
9
  } from './plans-model.js';
10
- import { el, setKids, pageHead, section, sheet, closeSheet, sheetIsOpen, story, details, note, cantTell, firstPaint, arrivalScroll } from './ui.js';
10
+ import { el, setKids, pageHead, section, sheet, closeSheet, sheetIsOpen, story, details, note, cantTell, firstPaint, arrivalScroll, markNeed } from './ui.js';
11
11
  import { legacyHref } from './nav-model.js';
12
12
  import { topicNameMap } from './topic-names.js';
13
13
  import { topicLink } from './promises.js';
@@ -49,6 +49,7 @@ function paint() {
49
49
 
50
50
  // Needs you: fresh items as rows; older ones waiting on you are a counted line, never dropped.
51
51
  const needs = section({ id: 'needsYou', title: 'Needs you', tone: 'attn', rows: s.needsYou, empty: 'Nothing is waiting on you.', onRow: open });
52
+ if (s.needsYou.length) markNeed(needs, 'plans');
52
53
  if (n.olderNeedsYou) needs.querySelector('.list-rows').append(el('a', { class: 'link-btn older-line', href: '#/work/plans?s=older', text: `+${n.olderNeedsYou} older, still waiting on you ›` }));
53
54
 
54
55
  // In the way, above In flight. Off and failed are one dashed line each, not a page.
@@ -9,7 +9,7 @@ import {
9
9
  topicNameMap, conversationChoices, requestCard, requestBody, testRequestBody, resultWords, shareWords,
10
10
  leftWords, TTL_CHOICES,
11
11
  } from './secrets-model.js';
12
- import { el, setKids, pageHead, section, sheet, closeSheet, story, details, note, cantTell, firstPaint } from './ui.js';
12
+ import { el, setKids, pageHead, section, sheet, closeSheet, story, details, note, cantTell, firstPaint, markNeed } from './ui.js';
13
13
  import { legacyHref } from './nav-model.js';
14
14
 
15
15
  const RECORD_KEYS = ['token', 'topicId', 'createdAt', 'expiresAt'];
@@ -90,7 +90,7 @@ function waitingBlock(requests, deps, multi) {
90
90
  const wrap = el('section', { class: 'list-section', id: 'sec-waiting' }, el('div', { class: 'list-head' }, el('h2', { class: 'eyebrow', text: 'Waiting' }), requests.ok ? el('span', { class: 'list-count', text: String(requests.waiting.length) }) : null));
91
91
  if (!requests.ok) { wrap.append(cantTell(requests.reason)); return wrap; }
92
92
  if (!requests.waiting.length) wrap.append(el('p', { class: 'empty-line', text: 'Nothing is waiting. When the agent needs a password or key, it asks here with a one-time link.' }));
93
- else wrap.append(el('div', { class: 'secret-cards' }, requests.waiting.map((c) => waitingCard(c, deps))));
93
+ else { markNeed(wrap, 'secrets'); wrap.append(el('div', { class: 'secret-cards' }, requests.waiting.map((c) => waitingCard(c, deps)))); }
94
94
  wrap.append(note('Once someone submits, it moves to the agent; it doesn’t stay listed here.'));
95
95
  if (multi) wrap.append(note('These are this machine’s requests. Each machine keeps its own.'));
96
96
  return wrap;
@@ -0,0 +1,36 @@
1
+ // Console sign-in from an owner's paired phone (docs/specs/mobile-console-device-signin.md §3.5).
2
+ // The Instar app puts a one-minute, single-use ticket in session storage; the console trades it
3
+ // once for the same token a PIN unlock returns. Any failure falls back to the PIN box.
4
+
5
+ export const TICKET_KEY = 'instar_console_ticket';
6
+ export const REDEEM_PATH = '/dashboard/console-ticket';
7
+
8
+ /** Reads the ticket and removes it at once, before any request, on every outcome. */
9
+ export function takeTicket(storage) {
10
+ let t = '';
11
+ try { t = storage.getItem(TICKET_KEY) || ''; } catch { t = ''; }
12
+ try { storage.removeItem(TICKET_KEY); } catch { /* storage blocked */ }
13
+ return typeof t === 'string' && t.length > 0 && t.length <= 128 ? t : '';
14
+ }
15
+
16
+ /** POSTs the ticket; resolves to the token, or '' on any failure. */
17
+ export async function redeemTicket(fetchFn, ticket) {
18
+ if (!ticket) return '';
19
+ try {
20
+ const r = await fetchFn(REDEEM_PATH, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ticket }), cache: 'no-store' });
21
+ if (!r || !r.ok) return '';
22
+ const data = await r.json().catch(() => ({}));
23
+ return data && typeof data.token === 'string' && data.token.length > 0 ? data.token : '';
24
+ } catch { return ''; }
25
+ }
26
+
27
+ /**
28
+ * The boot decision: a valid stored token wins; else a ticket is redeemed; else the PIN box.
29
+ * Returns { token, via: 'stored' | 'ticket' | 'pin' }.
30
+ */
31
+ export async function signInAtBoot({ storedToken, validToken, ticket, fetchFn }) {
32
+ if (storedToken && await validToken(storedToken)) return { token: storedToken, via: 'stored' };
33
+ const t = await redeemTicket(fetchFn, ticket);
34
+ if (t) return { token: t, via: 'ticket' };
35
+ return { token: '', via: 'pin' };
36
+ }