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.
- package/dashboard/index.html +486 -0
- package/dist/cli.js +5 -8
- package/dist/cli.js.map +1 -1
- package/dist/commands/discovery.d.ts.map +1 -1
- package/dist/commands/discovery.js +2 -2
- package/dist/commands/discovery.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +22 -4
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/job.d.ts.map +1 -1
- package/dist/commands/job.js +2 -2
- package/dist/commands/job.js.map +1 -1
- package/dist/commands/ledgerCleanup.d.ts.map +1 -1
- package/dist/commands/ledgerCleanup.js +2 -2
- package/dist/commands/ledgerCleanup.js.map +1 -1
- package/dist/commands/listener.d.ts.map +1 -1
- package/dist/commands/listener.js +7 -12
- package/dist/commands/listener.js.map +1 -1
- package/dist/commands/nuke.d.ts.map +1 -1
- package/dist/commands/nuke.js +11 -21
- package/dist/commands/nuke.js.map +1 -1
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +79 -5
- package/dist/commands/server.js.map +1 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +11 -15
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/slack-cli.d.ts.map +1 -1
- package/dist/commands/slack-cli.js +5 -8
- package/dist/commands/slack-cli.js.map +1 -1
- package/dist/commands/whatsapp.d.ts.map +1 -1
- package/dist/commands/whatsapp.js +2 -2
- package/dist/commands/whatsapp.js.map +1 -1
- package/dist/commands/worktree.d.ts.map +1 -1
- package/dist/commands/worktree.js +2 -2
- package/dist/commands/worktree.js.map +1 -1
- package/dist/core/AgentConnector.d.ts.map +1 -1
- package/dist/core/AgentConnector.js +9 -10
- package/dist/core/AgentConnector.js.map +1 -1
- package/dist/core/AgentRegistry.d.ts.map +1 -1
- package/dist/core/AgentRegistry.js +3 -4
- package/dist/core/AgentRegistry.js.map +1 -1
- package/dist/core/AutoDispatcher.d.ts.map +1 -1
- package/dist/core/AutoDispatcher.js +2 -2
- package/dist/core/AutoDispatcher.js.map +1 -1
- package/dist/core/AutoUpdater.d.ts.map +1 -1
- package/dist/core/AutoUpdater.js +2 -2
- package/dist/core/AutoUpdater.js.map +1 -1
- package/dist/core/AutonomousEvolution.d.ts.map +1 -1
- package/dist/core/AutonomousEvolution.js +2 -2
- package/dist/core/AutonomousEvolution.js.map +1 -1
- package/dist/core/BackupManager.d.ts.map +1 -1
- package/dist/core/BackupManager.js +2 -2
- package/dist/core/BackupManager.js.map +1 -1
- package/dist/core/BranchManager.d.ts.map +1 -1
- package/dist/core/BranchManager.js +3 -3
- package/dist/core/BranchManager.js.map +1 -1
- package/dist/core/CaffeinateManager.d.ts.map +1 -1
- package/dist/core/CaffeinateManager.js +2 -2
- package/dist/core/CaffeinateManager.js.map +1 -1
- package/dist/core/DeferredDispatchTracker.d.ts.map +1 -1
- package/dist/core/DeferredDispatchTracker.js +2 -2
- package/dist/core/DeferredDispatchTracker.js.map +1 -1
- package/dist/core/DispatchManager.d.ts.map +1 -1
- package/dist/core/DispatchManager.js +3 -4
- package/dist/core/DispatchManager.js.map +1 -1
- package/dist/core/EvolutionManager.d.ts.map +1 -1
- package/dist/core/EvolutionManager.js +2 -2
- package/dist/core/EvolutionManager.js.map +1 -1
- package/dist/core/ExecutionJournal.d.ts.map +1 -1
- package/dist/core/ExecutionJournal.js +2 -2
- package/dist/core/ExecutionJournal.js.map +1 -1
- package/dist/core/FeedbackManager.d.ts.map +1 -1
- package/dist/core/FeedbackManager.js +2 -2
- package/dist/core/FeedbackManager.js.map +1 -1
- package/dist/core/FileClassifier.d.ts.map +1 -1
- package/dist/core/FileClassifier.js +8 -17
- package/dist/core/FileClassifier.js.map +1 -1
- package/dist/core/ForegroundRestartWatcher.d.ts.map +1 -1
- package/dist/core/ForegroundRestartWatcher.js +3 -4
- package/dist/core/ForegroundRestartWatcher.js.map +1 -1
- package/dist/core/GitStateManager.d.ts.map +1 -1
- package/dist/core/GitStateManager.js +3 -12
- package/dist/core/GitStateManager.js.map +1 -1
- package/dist/core/GitSync.d.ts.map +1 -1
- package/dist/core/GitSync.js +6 -6
- package/dist/core/GitSync.js.map +1 -1
- package/dist/core/GlobalInstallCleanup.d.ts.map +1 -1
- package/dist/core/GlobalInstallCleanup.js +3 -4
- package/dist/core/GlobalInstallCleanup.js.map +1 -1
- package/dist/core/GlobalSecretStore.d.ts.map +1 -1
- package/dist/core/GlobalSecretStore.js +3 -4
- package/dist/core/GlobalSecretStore.js.map +1 -1
- package/dist/core/HandoffManager.d.ts.map +1 -1
- package/dist/core/HandoffManager.js +5 -5
- package/dist/core/HandoffManager.js.map +1 -1
- package/dist/core/JargonDetector.d.ts +28 -0
- package/dist/core/JargonDetector.d.ts.map +1 -0
- package/dist/core/JargonDetector.js +59 -0
- package/dist/core/JargonDetector.js.map +1 -0
- package/dist/core/LedgerSessionRegistry.d.ts.map +1 -1
- package/dist/core/LedgerSessionRegistry.js +2 -2
- package/dist/core/LedgerSessionRegistry.js.map +1 -1
- package/dist/core/MachineIdentity.d.ts.map +1 -1
- package/dist/core/MachineIdentity.js +2 -2
- package/dist/core/MachineIdentity.js.map +1 -1
- package/dist/core/MessagingToneGate.d.ts +42 -5
- package/dist/core/MessagingToneGate.d.ts.map +1 -1
- package/dist/core/MessagingToneGate.js +40 -6
- package/dist/core/MessagingToneGate.js.map +1 -1
- package/dist/core/ParallelDevWiring.d.ts.map +1 -1
- package/dist/core/ParallelDevWiring.js +3 -6
- package/dist/core/ParallelDevWiring.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +26 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +249 -46
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/ProjectMapper.d.ts.map +1 -1
- package/dist/core/ProjectMapper.js +5 -11
- package/dist/core/ProjectMapper.js.map +1 -1
- package/dist/core/RelationshipManager.d.ts.map +1 -1
- package/dist/core/RelationshipManager.js +4 -5
- package/dist/core/RelationshipManager.js.map +1 -1
- package/dist/core/SafeGitExecutor.d.ts +11 -5
- package/dist/core/SafeGitExecutor.d.ts.map +1 -1
- package/dist/core/SafeGitExecutor.js +87 -1
- package/dist/core/SafeGitExecutor.js.map +1 -1
- package/dist/core/ScopeVerifier.d.ts.map +1 -1
- package/dist/core/ScopeVerifier.js +3 -6
- package/dist/core/ScopeVerifier.js.map +1 -1
- package/dist/core/SecretStore.d.ts.map +1 -1
- package/dist/core/SecretStore.js +2 -2
- package/dist/core/SecretStore.js.map +1 -1
- package/dist/core/SharedStateLedger.d.ts.map +1 -1
- package/dist/core/SharedStateLedger.js +2 -2
- package/dist/core/SharedStateLedger.js.map +1 -1
- package/dist/core/SoulManager.d.ts.map +1 -1
- package/dist/core/SoulManager.js +3 -4
- package/dist/core/SoulManager.js.map +1 -1
- package/dist/core/StateManager.d.ts.map +1 -1
- package/dist/core/StateManager.js +4 -6
- package/dist/core/StateManager.js.map +1 -1
- package/dist/core/SyncOrchestrator.d.ts.map +1 -1
- package/dist/core/SyncOrchestrator.js +6 -7
- package/dist/core/SyncOrchestrator.js.map +1 -1
- package/dist/core/UpdateChecker.d.ts.map +1 -1
- package/dist/core/UpdateChecker.js +3 -4
- package/dist/core/UpdateChecker.js.map +1 -1
- package/dist/core/UpgradeGuideProcessor.d.ts.map +1 -1
- package/dist/core/UpgradeGuideProcessor.js +3 -4
- package/dist/core/UpgradeGuideProcessor.js.map +1 -1
- package/dist/core/WorktreeManager.d.ts.map +1 -1
- package/dist/core/WorktreeManager.js +9 -14
- package/dist/core/WorktreeManager.js.map +1 -1
- package/dist/knowledge/KnowledgeManager.d.ts.map +1 -1
- package/dist/knowledge/KnowledgeManager.js +2 -2
- package/dist/knowledge/KnowledgeManager.js.map +1 -1
- package/dist/lifeline/ServerSupervisor.d.ts +28 -0
- package/dist/lifeline/ServerSupervisor.d.ts.map +1 -1
- package/dist/lifeline/ServerSupervisor.js +171 -73
- package/dist/lifeline/ServerSupervisor.js.map +1 -1
- package/dist/lifeline/TelegramLifeline.d.ts.map +1 -1
- package/dist/lifeline/TelegramLifeline.js +10 -4
- package/dist/lifeline/TelegramLifeline.js.map +1 -1
- package/dist/lifeline/detectLaunchdSupervised.d.ts +43 -0
- package/dist/lifeline/detectLaunchdSupervised.d.ts.map +1 -0
- package/dist/lifeline/detectLaunchdSupervised.js +106 -0
- package/dist/lifeline/detectLaunchdSupervised.js.map +1 -0
- package/dist/lifeline/droppedMessages.d.ts.map +1 -1
- package/dist/lifeline/droppedMessages.js +2 -2
- package/dist/lifeline/droppedMessages.js.map +1 -1
- package/dist/memory/EpisodicMemory.d.ts.map +1 -1
- package/dist/memory/EpisodicMemory.js +2 -2
- package/dist/memory/EpisodicMemory.js.map +1 -1
- package/dist/memory/TopicMemory.d.ts.map +1 -1
- package/dist/memory/TopicMemory.js +5 -8
- package/dist/memory/TopicMemory.js.map +1 -1
- package/dist/messaging/AgentTokenManager.d.ts.map +1 -1
- package/dist/messaging/AgentTokenManager.js +2 -2
- package/dist/messaging/AgentTokenManager.js.map +1 -1
- package/dist/messaging/DropPickup.d.ts.map +1 -1
- package/dist/messaging/DropPickup.js +2 -2
- package/dist/messaging/DropPickup.js.map +1 -1
- package/dist/messaging/GitSyncTransport.d.ts.map +1 -1
- package/dist/messaging/GitSyncTransport.js +4 -6
- package/dist/messaging/GitSyncTransport.js.map +1 -1
- package/dist/messaging/MessageStore.d.ts.map +1 -1
- package/dist/messaging/MessageStore.js +3 -4
- package/dist/messaging/MessageStore.js.map +1 -1
- package/dist/messaging/TelegramAdapter.d.ts.map +1 -1
- package/dist/messaging/TelegramAdapter.js +5 -8
- package/dist/messaging/TelegramAdapter.js.map +1 -1
- package/dist/messaging/backends/BaileysBackend.d.ts.map +1 -1
- package/dist/messaging/backends/BaileysBackend.js +3 -4
- package/dist/messaging/backends/BaileysBackend.js.map +1 -1
- package/dist/messaging/local-tone-check.d.ts +61 -0
- package/dist/messaging/local-tone-check.d.ts.map +1 -0
- package/dist/messaging/local-tone-check.js +78 -0
- package/dist/messaging/local-tone-check.js.map +1 -0
- package/dist/messaging/pending-relay-store.d.ts +153 -0
- package/dist/messaging/pending-relay-store.d.ts.map +1 -0
- package/dist/messaging/pending-relay-store.js +351 -0
- package/dist/messaging/pending-relay-store.js.map +1 -0
- package/dist/messaging/secret-patterns.d.ts +35 -0
- package/dist/messaging/secret-patterns.d.ts.map +1 -0
- package/dist/messaging/secret-patterns.js +70 -0
- package/dist/messaging/secret-patterns.js.map +1 -0
- package/dist/messaging/shared/EncryptedAuthStore.d.ts.map +1 -1
- package/dist/messaging/shared/EncryptedAuthStore.js +3 -4
- package/dist/messaging/shared/EncryptedAuthStore.js.map +1 -1
- package/dist/messaging/shared/MessageLogger.d.ts.map +1 -1
- package/dist/messaging/shared/MessageLogger.js +2 -2
- package/dist/messaging/shared/MessageLogger.js.map +1 -1
- package/dist/messaging/shared/PrivacyConsent.d.ts.map +1 -1
- package/dist/messaging/shared/PrivacyConsent.js +2 -2
- package/dist/messaging/shared/PrivacyConsent.js.map +1 -1
- package/dist/messaging/shared/SessionChannelRegistry.d.ts.map +1 -1
- package/dist/messaging/shared/SessionChannelRegistry.js +2 -2
- package/dist/messaging/shared/SessionChannelRegistry.js.map +1 -1
- package/dist/messaging/system-templates.d.ts +87 -0
- package/dist/messaging/system-templates.d.ts.map +1 -0
- package/dist/messaging/system-templates.js +236 -0
- package/dist/messaging/system-templates.js.map +1 -0
- package/dist/messaging/whoami-cache.d.ts +66 -0
- package/dist/messaging/whoami-cache.d.ts.map +1 -0
- package/dist/messaging/whoami-cache.js +149 -0
- package/dist/messaging/whoami-cache.js.map +1 -0
- package/dist/moltbridge/ProfileCompiler.d.ts.map +1 -1
- package/dist/moltbridge/ProfileCompiler.js +13 -7
- package/dist/moltbridge/ProfileCompiler.js.map +1 -1
- package/dist/monitoring/CommitmentTracker.d.ts.map +1 -1
- package/dist/monitoring/CommitmentTracker.js +2 -2
- package/dist/monitoring/CommitmentTracker.js.map +1 -1
- package/dist/monitoring/CredentialProvider.d.ts.map +1 -1
- package/dist/monitoring/CredentialProvider.js +2 -2
- package/dist/monitoring/CredentialProvider.js.map +1 -1
- package/dist/monitoring/DegradationReporter.d.ts +41 -0
- package/dist/monitoring/DegradationReporter.d.ts.map +1 -1
- package/dist/monitoring/DegradationReporter.js +96 -4
- package/dist/monitoring/DegradationReporter.js.map +1 -1
- package/dist/monitoring/HealthChecker.d.ts.map +1 -1
- package/dist/monitoring/HealthChecker.js +2 -2
- package/dist/monitoring/HealthChecker.js.map +1 -1
- package/dist/monitoring/HookEventReceiver.d.ts.map +1 -1
- package/dist/monitoring/HookEventReceiver.js +2 -2
- package/dist/monitoring/HookEventReceiver.js.map +1 -1
- package/dist/monitoring/InstructionsVerifier.d.ts.map +1 -1
- package/dist/monitoring/InstructionsVerifier.js +2 -2
- package/dist/monitoring/InstructionsVerifier.js.map +1 -1
- package/dist/monitoring/PresenceProxy.d.ts.map +1 -1
- package/dist/monitoring/PresenceProxy.js +5 -8
- package/dist/monitoring/PresenceProxy.js.map +1 -1
- package/dist/monitoring/QuotaTracker.d.ts.map +1 -1
- package/dist/monitoring/QuotaTracker.js +2 -2
- package/dist/monitoring/QuotaTracker.js.map +1 -1
- package/dist/monitoring/SessionMigrator.d.ts.map +1 -1
- package/dist/monitoring/SessionMigrator.js +2 -2
- package/dist/monitoring/SessionMigrator.js.map +1 -1
- package/dist/monitoring/SessionRecovery.d.ts.map +1 -1
- package/dist/monitoring/SessionRecovery.js +2 -2
- package/dist/monitoring/SessionRecovery.js.map +1 -1
- package/dist/monitoring/TelemetryAuth.d.ts.map +1 -1
- package/dist/monitoring/TelemetryAuth.js +3 -4
- package/dist/monitoring/TelemetryAuth.js.map +1 -1
- package/dist/monitoring/TokenLedger.d.ts +130 -0
- package/dist/monitoring/TokenLedger.d.ts.map +1 -0
- package/dist/monitoring/TokenLedger.js +523 -0
- package/dist/monitoring/TokenLedger.js.map +1 -0
- package/dist/monitoring/TokenLedgerPoller.d.ts +26 -0
- package/dist/monitoring/TokenLedgerPoller.d.ts.map +1 -0
- package/dist/monitoring/TokenLedgerPoller.js +44 -0
- package/dist/monitoring/TokenLedgerPoller.js.map +1 -0
- package/dist/monitoring/TriageOrchestrator.d.ts.map +1 -1
- package/dist/monitoring/TriageOrchestrator.js +3 -4
- package/dist/monitoring/TriageOrchestrator.js.map +1 -1
- package/dist/monitoring/WorktreeReaper.d.ts.map +1 -1
- package/dist/monitoring/WorktreeReaper.js +5 -7
- package/dist/monitoring/WorktreeReaper.js.map +1 -1
- package/dist/monitoring/delivery-failure-sentinel/recovery-policy.d.ts +83 -0
- package/dist/monitoring/delivery-failure-sentinel/recovery-policy.d.ts.map +1 -0
- package/dist/monitoring/delivery-failure-sentinel/recovery-policy.js +218 -0
- package/dist/monitoring/delivery-failure-sentinel/recovery-policy.js.map +1 -0
- package/dist/monitoring/delivery-failure-sentinel.d.ts +177 -0
- package/dist/monitoring/delivery-failure-sentinel.d.ts.map +1 -0
- package/dist/monitoring/delivery-failure-sentinel.js +598 -0
- package/dist/monitoring/delivery-failure-sentinel.js.map +1 -0
- package/dist/monitoring/probes/PlatformProbe.d.ts.map +1 -1
- package/dist/monitoring/probes/PlatformProbe.js +3 -4
- package/dist/monitoring/probes/PlatformProbe.js.map +1 -1
- package/dist/monitoring/templates-drift-verifier.d.ts +109 -0
- package/dist/monitoring/templates-drift-verifier.d.ts.map +1 -0
- package/dist/monitoring/templates-drift-verifier.js +324 -0
- package/dist/monitoring/templates-drift-verifier.js.map +1 -0
- package/dist/paste/PasteManager.d.ts.map +1 -1
- package/dist/paste/PasteManager.js +5 -8
- package/dist/paste/PasteManager.js.map +1 -1
- package/dist/publishing/PrivateViewer.d.ts.map +1 -1
- package/dist/publishing/PrivateViewer.js +2 -2
- package/dist/publishing/PrivateViewer.js.map +1 -1
- package/dist/scheduler/JobScheduler.d.ts.map +1 -1
- package/dist/scheduler/JobScheduler.js +2 -2
- package/dist/scheduler/JobScheduler.js.map +1 -1
- package/dist/server/AgentServer.d.ts +22 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +199 -1
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/WebSocketManager.d.ts +11 -0
- package/dist/server/WebSocketManager.d.ts.map +1 -1
- package/dist/server/WebSocketManager.js +28 -0
- package/dist/server/WebSocketManager.js.map +1 -1
- package/dist/server/boot-id.d.ts +58 -0
- package/dist/server/boot-id.d.ts.map +1 -0
- package/dist/server/boot-id.js +121 -0
- package/dist/server/boot-id.js.map +1 -0
- package/dist/server/middleware.d.ts +14 -1
- package/dist/server/middleware.d.ts.map +1 -1
- package/dist/server/middleware.js +81 -1
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/routes.d.ts +76 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +626 -11
- package/dist/server/routes.js.map +1 -1
- package/dist/threadline/AgentDiscovery.d.ts.map +1 -1
- package/dist/threadline/AgentDiscovery.js +2 -2
- package/dist/threadline/AgentDiscovery.js.map +1 -1
- package/dist/threadline/AgentTrustManager.d.ts.map +1 -1
- package/dist/threadline/AgentTrustManager.js +2 -2
- package/dist/threadline/AgentTrustManager.js.map +1 -1
- package/dist/threadline/BackfillCore.d.ts +70 -0
- package/dist/threadline/BackfillCore.d.ts.map +1 -0
- package/dist/threadline/BackfillCore.js +117 -0
- package/dist/threadline/BackfillCore.js.map +1 -0
- package/dist/threadline/CircuitBreaker.d.ts.map +1 -1
- package/dist/threadline/CircuitBreaker.js +2 -2
- package/dist/threadline/CircuitBreaker.js.map +1 -1
- package/dist/threadline/ComputeMeter.d.ts.map +1 -1
- package/dist/threadline/ComputeMeter.js +2 -2
- package/dist/threadline/ComputeMeter.js.map +1 -1
- package/dist/threadline/ContextThreadMap.d.ts.map +1 -1
- package/dist/threadline/ContextThreadMap.js +2 -2
- package/dist/threadline/ContextThreadMap.js.map +1 -1
- package/dist/threadline/HeartbeatWatchdog.d.ts +78 -0
- package/dist/threadline/HeartbeatWatchdog.d.ts.map +1 -0
- package/dist/threadline/HeartbeatWatchdog.js +212 -0
- package/dist/threadline/HeartbeatWatchdog.js.map +1 -0
- package/dist/threadline/HeartbeatWriter.d.ts +79 -0
- package/dist/threadline/HeartbeatWriter.d.ts.map +1 -0
- package/dist/threadline/HeartbeatWriter.js +109 -0
- package/dist/threadline/HeartbeatWriter.js.map +1 -0
- package/dist/threadline/InvitationManager.d.ts.map +1 -1
- package/dist/threadline/InvitationManager.js +2 -2
- package/dist/threadline/InvitationManager.js.map +1 -1
- package/dist/threadline/ListenerSessionManager.d.ts +59 -0
- package/dist/threadline/ListenerSessionManager.d.ts.map +1 -1
- package/dist/threadline/ListenerSessionManager.js +79 -0
- package/dist/threadline/ListenerSessionManager.js.map +1 -1
- package/dist/threadline/MCPAuth.d.ts.map +1 -1
- package/dist/threadline/MCPAuth.js +2 -2
- package/dist/threadline/MCPAuth.js.map +1 -1
- package/dist/threadline/PipeSessionSpawner.d.ts.map +1 -1
- package/dist/threadline/PipeSessionSpawner.js +3 -4
- package/dist/threadline/PipeSessionSpawner.js.map +1 -1
- package/dist/threadline/RateLimiter.d.ts.map +1 -1
- package/dist/threadline/RateLimiter.js +2 -2
- package/dist/threadline/RateLimiter.js.map +1 -1
- package/dist/threadline/RelaySpawnFailureHandler.d.ts +53 -0
- package/dist/threadline/RelaySpawnFailureHandler.d.ts.map +1 -0
- package/dist/threadline/RelaySpawnFailureHandler.js +73 -0
- package/dist/threadline/RelaySpawnFailureHandler.js.map +1 -0
- package/dist/threadline/SessionLifecycle.d.ts.map +1 -1
- package/dist/threadline/SessionLifecycle.js +2 -2
- package/dist/threadline/SessionLifecycle.js.map +1 -1
- package/dist/threadline/SpawnLedger.d.ts +94 -0
- package/dist/threadline/SpawnLedger.d.ts.map +1 -0
- package/dist/threadline/SpawnLedger.js +194 -0
- package/dist/threadline/SpawnLedger.js.map +1 -0
- package/dist/threadline/SpawnNonce.d.ts +49 -0
- package/dist/threadline/SpawnNonce.d.ts.map +1 -0
- package/dist/threadline/SpawnNonce.js +99 -0
- package/dist/threadline/SpawnNonce.js.map +1 -0
- package/dist/threadline/TelegramBridge.d.ts +140 -0
- package/dist/threadline/TelegramBridge.d.ts.map +1 -0
- package/dist/threadline/TelegramBridge.js +224 -0
- package/dist/threadline/TelegramBridge.js.map +1 -0
- package/dist/threadline/TelegramBridgeConfig.d.ts +79 -0
- package/dist/threadline/TelegramBridgeConfig.d.ts.map +1 -0
- package/dist/threadline/TelegramBridgeConfig.js +168 -0
- package/dist/threadline/TelegramBridgeConfig.js.map +1 -0
- package/dist/threadline/ThreadlineBootstrap.d.ts.map +1 -1
- package/dist/threadline/ThreadlineBootstrap.js +2 -2
- package/dist/threadline/ThreadlineBootstrap.js.map +1 -1
- package/dist/threadline/ThreadlineMCPServer.d.ts.map +1 -1
- package/dist/threadline/ThreadlineMCPServer.js +5 -0
- package/dist/threadline/ThreadlineMCPServer.js.map +1 -1
- package/dist/threadline/ThreadlineObservability.d.ts +95 -0
- package/dist/threadline/ThreadlineObservability.d.ts.map +1 -0
- package/dist/threadline/ThreadlineObservability.js +310 -0
- package/dist/threadline/ThreadlineObservability.js.map +1 -0
- package/dist/threadline/WakeSocketServer.d.ts.map +1 -1
- package/dist/threadline/WakeSocketServer.js +3 -4
- package/dist/threadline/WakeSocketServer.js.map +1 -1
- package/dist/threadline/listener-daemon.d.ts.map +1 -1
- package/dist/threadline/listener-daemon.js +3 -4
- package/dist/threadline/listener-daemon.js.map +1 -1
- package/dist/users/UserManager.d.ts.map +1 -1
- package/dist/users/UserManager.js +2 -2
- package/dist/users/UserManager.js.map +1 -1
- package/dist/users/UserOnboarding.d.ts.map +1 -1
- package/dist/users/UserOnboarding.js +2 -2
- package/dist/users/UserOnboarding.js.map +1 -1
- package/dist/utils/jsonl-rotation.d.ts.map +1 -1
- package/dist/utils/jsonl-rotation.js +2 -2
- package/dist/utils/jsonl-rotation.js.map +1 -1
- package/package.json +1 -1
- package/scripts/analyze-release.js +7 -12
- package/scripts/check-contract-evidence.js +27 -10
- package/scripts/fix-better-sqlite3.cjs +0 -2
- package/scripts/instar-dev-precommit.js +0 -2
- package/scripts/lint-no-direct-destructive.js +24 -4
- package/scripts/lint-template-sha-history.ts +183 -0
- package/scripts/migrate-incident-2026-04-17.mjs +2 -2
- package/scripts/run-migration.js +500 -0
- package/scripts/test-bootstrap-relay.mjs +2 -2
- package/scripts/threadline-bridge-backfill.mjs +379 -0
- package/scripts/verify-deployed-templates.ts +87 -0
- package/src/data/builtin-manifest.json +140 -132
- package/src/templates/scripts/git-sync-gate.sh +0 -4
- package/src/templates/scripts/telegram-reply.sh +318 -13
- package/upgrades/0.28.77.md +133 -0
- package/upgrades/0.28.78.md +90 -0
- package/upgrades/side-effects/agent-health-alert-authority-routing.md +121 -0
- package/upgrades/side-effects/comprehensive-destructive-tool-containment-migration.md +82 -0
- package/upgrades/side-effects/deferral-detector-orphan-todo.md +101 -0
- package/upgrades/side-effects/lifeline-self-heal-hardening.md +151 -0
- package/upgrades/side-effects/relay-spawn-ghost-reply-phase1.md +139 -0
- package/upgrades/side-effects/telegram-delivery-robustness-layer-2.md +320 -0
- package/upgrades/side-effects/telegram-delivery-robustness-layer-3.md +202 -0
- package/upgrades/side-effects/telegram-delivery-robustness-layer-7.md +339 -0
- package/upgrades/side-effects/telegram-delivery-robustness.md +178 -0
- package/upgrades/side-effects/threadline-bridge-backfill.md +203 -0
- package/upgrades/side-effects/threadline-canonical-inbox-write.md +218 -0
- package/upgrades/side-effects/threadline-observability-tab.md +206 -0
- package/upgrades/side-effects/threadline-tg-bridge-module.md +196 -0
- package/upgrades/side-effects/threadline-tg-bridge-settings-surface.md +208 -0
- package/upgrades/side-effects/token-ledger-bounded-scan.md +230 -0
- package/upgrades/side-effects/token-ledger-phase1.md +123 -0
- package/upgrades/NEXT.md +0 -53
- /package/upgrades/side-effects/{telegram-lifeline-version-missing-info.md → 0.28.76.md} +0 -0
package/dist/server/routes.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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({
|