@decibelsystems/tools 2.2.0-beta.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +226 -0
- package/dist/agentCommands.d.ts +0 -1
- package/dist/agentCommands.d.ts.map +1 -1
- package/dist/agentCommands.js +19 -8
- package/dist/agentCommands.js.map +1 -1
- package/dist/agentPresence.d.ts +82 -0
- package/dist/agentPresence.d.ts.map +1 -1
- package/dist/agentPresence.js +194 -26
- package/dist/agentPresence.js.map +1 -1
- package/dist/agentic/index.d.ts +18 -8
- package/dist/agentic/index.d.ts.map +1 -1
- package/dist/agentic/index.js +18 -144
- package/dist/agentic/index.js.map +1 -1
- package/dist/agentic/types.d.ts +106 -0
- package/dist/agentic/types.d.ts.map +1 -1
- package/dist/agentic/types.js +77 -1
- package/dist/agentic/types.js.map +1 -1
- package/dist/architectAdrs.d.ts +27 -3
- package/dist/architectAdrs.d.ts.map +1 -1
- package/dist/architectAdrs.js +92 -4
- package/dist/architectAdrs.js.map +1 -1
- package/dist/artifacts.d.ts +60 -0
- package/dist/artifacts.d.ts.map +1 -0
- package/dist/artifacts.js +208 -0
- package/dist/artifacts.js.map +1 -0
- package/dist/asks.d.ts +93 -0
- package/dist/asks.d.ts.map +1 -0
- package/dist/asks.js +383 -0
- package/dist/asks.js.map +1 -0
- package/dist/chat.d.ts +47 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +288 -0
- package/dist/chat.js.map +1 -0
- package/dist/client/transports.d.ts.map +1 -1
- package/dist/client/transports.js +13 -3
- package/dist/client/transports.js.map +1 -1
- package/dist/daemon.d.ts +33 -3
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +225 -52
- package/dist/daemon.js.map +1 -1
- package/dist/daemonConfig.d.ts +46 -0
- package/dist/daemonConfig.d.ts.map +1 -1
- package/dist/daemonConfig.js +57 -12
- package/dist/daemonConfig.js.map +1 -1
- package/dist/decibelPaths.d.ts.map +1 -1
- package/dist/decibelPaths.js +39 -6
- package/dist/decibelPaths.js.map +1 -1
- package/dist/domain/issue.d.ts +81 -0
- package/dist/domain/issue.d.ts.map +1 -0
- package/dist/domain/issue.js +106 -0
- package/dist/domain/issue.js.map +1 -0
- package/dist/domain/issueCodec.d.ts +39 -0
- package/dist/domain/issueCodec.d.ts.map +1 -0
- package/dist/domain/issueCodec.js +308 -0
- package/dist/domain/issueCodec.js.map +1 -0
- package/dist/domain/issueRepository.d.ts +124 -0
- package/dist/domain/issueRepository.d.ts.map +1 -0
- package/dist/domain/issueRepository.js +296 -0
- package/dist/domain/issueRepository.js.map +1 -0
- package/dist/domain/uid.d.ts +16 -0
- package/dist/domain/uid.d.ts.map +1 -0
- package/dist/domain/uid.js +43 -0
- package/dist/domain/uid.js.map +1 -0
- package/dist/facades/definitions.d.ts +0 -1
- package/dist/facades/definitions.d.ts.map +1 -1
- package/dist/facades/definitions.js +140 -116
- package/dist/facades/definitions.js.map +1 -1
- package/dist/facades/index.d.ts +2 -1
- package/dist/facades/index.d.ts.map +1 -1
- package/dist/facades/index.js +1 -1
- package/dist/facades/index.js.map +1 -1
- package/dist/facades/types.d.ts +17 -0
- package/dist/facades/types.d.ts.map +1 -1
- package/dist/hq/defineAgent.d.ts.map +1 -1
- package/dist/hq/defineAgent.js +7 -11
- package/dist/hq/defineAgent.js.map +1 -1
- package/dist/httpArgs.d.ts +11 -0
- package/dist/httpArgs.d.ts.map +1 -0
- package/dist/httpArgs.js +39 -0
- package/dist/httpArgs.js.map +1 -0
- package/dist/httpServer.d.ts +1 -12
- package/dist/httpServer.d.ts.map +1 -1
- package/dist/httpServer.js +298 -144
- package/dist/httpServer.js.map +1 -1
- package/dist/identity.d.ts +123 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +312 -0
- package/dist/identity.js.map +1 -0
- package/dist/kernel.d.ts +46 -1
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +334 -26
- package/dist/kernel.js.map +1 -1
- package/dist/killSwitch.d.ts +117 -0
- package/dist/killSwitch.d.ts.map +1 -0
- package/dist/killSwitch.js +161 -0
- package/dist/killSwitch.js.map +1 -0
- package/dist/lib/agent-services/assumptions.d.ts.map +1 -1
- package/dist/lib/agent-services/assumptions.js +8 -2
- package/dist/lib/agent-services/assumptions.js.map +1 -1
- package/dist/lib/atomicWrite.d.ts +49 -0
- package/dist/lib/atomicWrite.d.ts.map +1 -0
- package/dist/lib/atomicWrite.js +157 -0
- package/dist/lib/atomicWrite.js.map +1 -0
- package/dist/lib/envelope.d.ts +31 -0
- package/dist/lib/envelope.d.ts.map +1 -0
- package/dist/lib/envelope.js +80 -0
- package/dist/lib/envelope.js.map +1 -0
- package/dist/lib/fileLock.d.ts +38 -0
- package/dist/lib/fileLock.d.ts.map +1 -0
- package/dist/lib/fileLock.js +124 -0
- package/dist/lib/fileLock.js.map +1 -0
- package/dist/lib/recordIdAllocator.d.ts +102 -0
- package/dist/lib/recordIdAllocator.d.ts.map +1 -0
- package/dist/lib/recordIdAllocator.js +179 -0
- package/dist/lib/recordIdAllocator.js.map +1 -0
- package/dist/orgId.d.ts +23 -0
- package/dist/orgId.d.ts.map +1 -0
- package/dist/orgId.js +73 -0
- package/dist/orgId.js.map +1 -0
- package/dist/pairCli.d.ts +2 -0
- package/dist/pairCli.d.ts.map +1 -0
- package/dist/pairCli.js +97 -0
- package/dist/pairCli.js.map +1 -0
- package/dist/peerBroker.d.ts +40 -0
- package/dist/peerBroker.d.ts.map +1 -0
- package/dist/peerBroker.js +70 -0
- package/dist/peerBroker.js.map +1 -0
- package/dist/peerBrokerSupervisor.d.ts +21 -0
- package/dist/peerBrokerSupervisor.d.ts.map +1 -0
- package/dist/peerBrokerSupervisor.js +131 -0
- package/dist/peerBrokerSupervisor.js.map +1 -0
- package/dist/projectPaths.d.ts.map +1 -1
- package/dist/projectPaths.js +1 -19
- package/dist/projectPaths.js.map +1 -1
- package/dist/projectRegistry.d.ts +22 -0
- package/dist/projectRegistry.d.ts.map +1 -1
- package/dist/projectRegistry.js +69 -2
- package/dist/projectRegistry.js.map +1 -1
- package/dist/pty/boundary.d.ts +39 -0
- package/dist/pty/boundary.d.ts.map +1 -0
- package/dist/pty/boundary.js +100 -0
- package/dist/pty/boundary.js.map +1 -0
- package/dist/pty/errors.d.ts +6 -0
- package/dist/pty/errors.d.ts.map +1 -0
- package/dist/pty/errors.js +12 -0
- package/dist/pty/errors.js.map +1 -0
- package/dist/pty/fleet.d.ts +159 -0
- package/dist/pty/fleet.d.ts.map +1 -0
- package/dist/pty/fleet.js +543 -0
- package/dist/pty/fleet.js.map +1 -0
- package/dist/pty/launchProfile.d.ts +45 -0
- package/dist/pty/launchProfile.d.ts.map +1 -0
- package/dist/pty/launchProfile.js +149 -0
- package/dist/pty/launchProfile.js.map +1 -0
- package/dist/pty/openTerminal.d.ts +48 -0
- package/dist/pty/openTerminal.d.ts.map +1 -0
- package/dist/pty/openTerminal.js +185 -0
- package/dist/pty/openTerminal.js.map +1 -0
- package/dist/pty/pairDialog.d.ts +16 -0
- package/dist/pty/pairDialog.d.ts.map +1 -0
- package/dist/pty/pairDialog.js +121 -0
- package/dist/pty/pairDialog.js.map +1 -0
- package/dist/pty/pairing.d.ts +86 -0
- package/dist/pty/pairing.d.ts.map +1 -0
- package/dist/pty/pairing.js +351 -0
- package/dist/pty/pairing.js.map +1 -0
- package/dist/pty/routes.d.ts +49 -0
- package/dist/pty/routes.d.ts.map +1 -0
- package/dist/pty/routes.js +624 -0
- package/dist/pty/routes.js.map +1 -0
- package/dist/pty/sessions.d.ts +167 -0
- package/dist/pty/sessions.d.ts.map +1 -0
- package/dist/pty/sessions.js +513 -0
- package/dist/pty/sessions.js.map +1 -0
- package/dist/pty/tmux.d.ts +109 -0
- package/dist/pty/tmux.d.ts.map +1 -0
- package/dist/pty/tmux.js +271 -0
- package/dist/pty/tmux.js.map +1 -0
- package/dist/roster.d.ts +37 -0
- package/dist/roster.d.ts.map +1 -0
- package/dist/roster.js +126 -0
- package/dist/roster.js.map +1 -0
- package/dist/runtime/circuitBreaker.d.ts +95 -0
- package/dist/runtime/circuitBreaker.d.ts.map +1 -0
- package/dist/runtime/circuitBreaker.js +178 -0
- package/dist/runtime/circuitBreaker.js.map +1 -0
- package/dist/runtime/daemonMeta.d.ts +53 -0
- package/dist/runtime/daemonMeta.d.ts.map +1 -0
- package/dist/runtime/daemonMeta.js +121 -0
- package/dist/runtime/daemonMeta.js.map +1 -0
- package/dist/runtime/ensureRuntime.d.ts +103 -0
- package/dist/runtime/ensureRuntime.d.ts.map +1 -0
- package/dist/runtime/ensureRuntime.js +276 -0
- package/dist/runtime/ensureRuntime.js.map +1 -0
- package/dist/runtime/extensions.d.ts +57 -0
- package/dist/runtime/extensions.d.ts.map +1 -0
- package/dist/runtime/extensions.js +216 -0
- package/dist/runtime/extensions.js.map +1 -0
- package/dist/runtime/localHttp.d.ts +24 -0
- package/dist/runtime/localHttp.d.ts.map +1 -0
- package/dist/runtime/localHttp.js +84 -0
- package/dist/runtime/localHttp.js.map +1 -0
- package/dist/runtime/projectResolution.d.ts +29 -0
- package/dist/runtime/projectResolution.d.ts.map +1 -0
- package/dist/runtime/projectResolution.js +67 -0
- package/dist/runtime/projectResolution.js.map +1 -0
- package/dist/runtime/protocol.d.ts +62 -0
- package/dist/runtime/protocol.d.ts.map +1 -0
- package/dist/runtime/protocol.js +74 -0
- package/dist/runtime/protocol.js.map +1 -0
- package/dist/sentinelIssues.d.ts +42 -2
- package/dist/sentinelIssues.d.ts.map +1 -1
- package/dist/sentinelIssues.js +167 -65
- package/dist/sentinelIssues.js.map +1 -1
- package/dist/server.js +123 -15
- package/dist/server.js.map +1 -1
- package/dist/toolConfig.d.ts.map +1 -1
- package/dist/toolConfig.js +12 -0
- package/dist/toolConfig.js.map +1 -1
- package/dist/tools/agentic/agentQueue.d.ts.map +1 -1
- package/dist/tools/agentic/agentQueue.js +4 -0
- package/dist/tools/agentic/agentQueue.js.map +1 -1
- package/dist/tools/agentic/index.d.ts.map +1 -1
- package/dist/tools/agentic/index.js +28 -2
- package/dist/tools/agentic/index.js.map +1 -1
- package/dist/tools/architect/index.d.ts +1 -0
- package/dist/tools/architect/index.d.ts.map +1 -1
- package/dist/tools/architect/index.js +52 -3
- package/dist/tools/architect/index.js.map +1 -1
- package/dist/tools/architect.d.ts.map +1 -1
- package/dist/tools/architect.js +59 -8
- package/dist/tools/architect.js.map +1 -1
- package/dist/tools/artifacts.d.ts +5 -0
- package/dist/tools/artifacts.d.ts.map +1 -0
- package/dist/tools/artifacts.js +89 -0
- package/dist/tools/artifacts.js.map +1 -0
- package/dist/tools/asks.d.ts +6 -0
- package/dist/tools/asks.d.ts.map +1 -0
- package/dist/tools/asks.js +104 -0
- package/dist/tools/asks.js.map +1 -0
- package/dist/tools/context.d.ts +10 -5
- package/dist/tools/context.d.ts.map +1 -1
- package/dist/tools/context.js +45 -18
- package/dist/tools/context.js.map +1 -1
- package/dist/tools/coordinator/coordinator.d.ts.map +1 -1
- package/dist/tools/coordinator/coordinator.js +48 -19
- package/dist/tools/coordinator/coordinator.js.map +1 -1
- package/dist/tools/coordinator/index.d.ts +1 -0
- package/dist/tools/coordinator/index.d.ts.map +1 -1
- package/dist/tools/coordinator/index.js +42 -2
- package/dist/tools/coordinator/index.js.map +1 -1
- package/dist/tools/crit.d.ts +2 -1
- package/dist/tools/crit.d.ts.map +1 -1
- package/dist/tools/crit.js +19 -4
- package/dist/tools/crit.js.map +1 -1
- package/dist/tools/data-inspector.d.ts +1 -1
- package/dist/tools/data-inspector.d.ts.map +1 -1
- package/dist/tools/data-inspector.js +1 -1
- package/dist/tools/data-inspector.js.map +1 -1
- package/dist/tools/designer/index.d.ts.map +1 -1
- package/dist/tools/designer/index.js +4 -3
- package/dist/tools/designer/index.js.map +1 -1
- package/dist/tools/designer/lateral-tools.d.ts.map +1 -1
- package/dist/tools/designer/lateral-tools.js +4 -2
- package/dist/tools/designer/lateral-tools.js.map +1 -1
- package/dist/tools/designer.d.ts +2 -1
- package/dist/tools/designer.d.ts.map +1 -1
- package/dist/tools/designer.js +24 -12
- package/dist/tools/designer.js.map +1 -1
- package/dist/tools/dojo/index.d.ts.map +1 -1
- package/dist/tools/dojo/index.js +52 -3
- package/dist/tools/dojo/index.js.map +1 -1
- package/dist/tools/dojo.d.ts +65 -3
- package/dist/tools/dojo.d.ts.map +1 -1
- package/dist/tools/dojo.js +148 -23
- package/dist/tools/dojo.js.map +1 -1
- package/dist/tools/friction.d.ts.map +1 -1
- package/dist/tools/friction.js +16 -6
- package/dist/tools/friction.js.map +1 -1
- package/dist/tools/guardian.d.ts +7 -0
- package/dist/tools/guardian.d.ts.map +1 -1
- package/dist/tools/guardian.js +152 -11
- package/dist/tools/guardian.js.map +1 -1
- package/dist/tools/hygiene/oracle-hygiene.d.ts.map +1 -1
- package/dist/tools/hygiene/oracle-hygiene.js +8 -2
- package/dist/tools/hygiene/oracle-hygiene.js.map +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +33 -18
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/killswitch/index.d.ts +13 -0
- package/dist/tools/killswitch/index.d.ts.map +1 -0
- package/dist/tools/killswitch/index.js +138 -0
- package/dist/tools/killswitch/index.js.map +1 -0
- package/dist/tools/learnings.d.ts +2 -1
- package/dist/tools/learnings.d.ts.map +1 -1
- package/dist/tools/learnings.js +31 -15
- package/dist/tools/learnings.js.map +1 -1
- package/dist/tools/oracle/index.d.ts.map +1 -1
- package/dist/tools/oracle/index.js +8 -0
- package/dist/tools/oracle/index.js.map +1 -1
- package/dist/tools/oracle.d.ts +7 -0
- package/dist/tools/oracle.d.ts.map +1 -1
- package/dist/tools/oracle.js +41 -12
- package/dist/tools/oracle.js.map +1 -1
- package/dist/tools/policy.d.ts +2 -1
- package/dist/tools/policy.d.ts.map +1 -1
- package/dist/tools/policy.js +28 -9
- package/dist/tools/policy.js.map +1 -1
- package/dist/tools/postoffice/index.d.ts +10 -0
- package/dist/tools/postoffice/index.d.ts.map +1 -0
- package/dist/tools/postoffice/index.js +218 -0
- package/dist/tools/postoffice/index.js.map +1 -0
- package/dist/tools/postoffice.d.ts +78 -0
- package/dist/tools/postoffice.d.ts.map +1 -0
- package/dist/tools/postoffice.js +199 -0
- package/dist/tools/postoffice.js.map +1 -0
- package/dist/tools/provenance.d.ts +18 -2
- package/dist/tools/provenance.d.ts.map +1 -1
- package/dist/tools/provenance.js +68 -40
- package/dist/tools/provenance.js.map +1 -1
- package/dist/tools/registry/index.d.ts.map +1 -1
- package/dist/tools/registry/index.js +24 -0
- package/dist/tools/registry/index.js.map +1 -1
- package/dist/tools/sentinel/index.d.ts +1 -0
- package/dist/tools/sentinel/index.d.ts.map +1 -1
- package/dist/tools/sentinel/index.js +142 -6
- package/dist/tools/sentinel/index.js.map +1 -1
- package/dist/tools/sentinel.d.ts +152 -1
- package/dist/tools/sentinel.d.ts.map +1 -1
- package/dist/tools/sentinel.js +609 -328
- package/dist/tools/sentinel.js.map +1 -1
- package/dist/tools/shared/index.d.ts +2 -1
- package/dist/tools/shared/index.d.ts.map +1 -1
- package/dist/tools/shared/index.js +2 -1
- package/dist/tools/shared/index.js.map +1 -1
- package/dist/tools/shared/response.d.ts +24 -0
- package/dist/tools/shared/response.d.ts.map +1 -1
- package/dist/tools/shared/response.js +61 -7
- package/dist/tools/shared/response.js.map +1 -1
- package/dist/tools/shared/runTracker.d.ts +2 -0
- package/dist/tools/shared/runTracker.d.ts.map +1 -1
- package/dist/tools/shared/runTracker.js +38 -0
- package/dist/tools/shared/runTracker.js.map +1 -1
- package/dist/tools/shared/storeRead.d.ts +153 -0
- package/dist/tools/shared/storeRead.d.ts.map +1 -0
- package/dist/tools/shared/storeRead.js +242 -0
- package/dist/tools/shared/storeRead.js.map +1 -0
- package/dist/tools/swarm.d.ts +3 -1
- package/dist/tools/swarm.d.ts.map +1 -1
- package/dist/tools/swarm.js +6 -12
- package/dist/tools/swarm.js.map +1 -1
- package/dist/tools/testSpec.d.ts +3 -2
- package/dist/tools/testSpec.d.ts.map +1 -1
- package/dist/tools/testSpec.js +51 -14
- package/dist/tools/testSpec.js.map +1 -1
- package/dist/tools/vector.d.ts +33 -1
- package/dist/tools/vector.d.ts.map +1 -1
- package/dist/tools/vector.js +31 -5
- package/dist/tools/vector.js.map +1 -1
- package/dist/tools/voice/index.d.ts +1 -0
- package/dist/tools/voice/index.d.ts.map +1 -1
- package/dist/tools/voice/index.js +33 -2
- package/dist/tools/voice/index.js.map +1 -1
- package/dist/tools/voice.d.ts +48 -1
- package/dist/tools/voice.d.ts.map +1 -1
- package/dist/tools/voice.js +63 -3
- package/dist/tools/voice.js.map +1 -1
- package/dist/tools/zoom/index.d.ts +15 -0
- package/dist/tools/zoom/index.d.ts.map +1 -0
- package/dist/tools/zoom/index.js +174 -0
- package/dist/tools/zoom/index.js.map +1 -0
- package/dist/tools/zoom.d.ts +234 -0
- package/dist/tools/zoom.d.ts.map +1 -0
- package/dist/tools/zoom.js +627 -0
- package/dist/tools/zoom.js.map +1 -0
- package/dist/transports/bridge.d.ts +1 -1
- package/dist/transports/bridge.d.ts.map +1 -1
- package/dist/transports/bridge.js +4 -1
- package/dist/transports/bridge.js.map +1 -1
- package/dist/transports/http.d.ts +1 -1
- package/dist/transports/http.d.ts.map +1 -1
- package/dist/transports/http.js +3 -1
- package/dist/transports/http.js.map +1 -1
- package/dist/transports/index.d.ts +1 -0
- package/dist/transports/index.d.ts.map +1 -1
- package/dist/transports/index.js +1 -0
- package/dist/transports/index.js.map +1 -1
- package/dist/transports/mcp.d.ts +1 -1
- package/dist/transports/mcp.d.ts.map +1 -1
- package/dist/transports/mcp.js +9 -4
- package/dist/transports/mcp.js.map +1 -1
- package/dist/transports/stdio.d.ts +1 -1
- package/dist/transports/stdio.d.ts.map +1 -1
- package/dist/transports/stdio.js +2 -0
- package/dist/transports/stdio.js.map +1 -1
- package/dist/transports/thinStdio.d.ts +22 -0
- package/dist/transports/thinStdio.d.ts.map +1 -0
- package/dist/transports/thinStdio.js +218 -0
- package/dist/transports/thinStdio.js.map +1 -0
- package/dist/transports/types.d.ts +8 -1
- package/dist/transports/types.d.ts.map +1 -1
- package/dist/types/agent-services.d.ts +7 -0
- package/dist/types/agent-services.d.ts.map +1 -1
- package/dist/version.d.ts +5 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +23 -0
- package/dist/version.js.map +1 -0
- package/package.json +20 -4
- package/templates/AGENT.md +3 -0
- package/templates/CLAUDE-decibel.md +11 -0
- package/templates/com.decibel.daemon.plist +6 -1
- package/dist/tools/deck.d.ts +0 -11
- package/dist/tools/deck.d.ts.map +0 -1
- package/dist/tools/deck.js +0 -1656
- package/dist/tools/deck.js.map +0 -1
- package/dist/tools/mother.d.ts +0 -11
- package/dist/tools/mother.d.ts.map +0 -1
- package/dist/tools/mother.js +0 -503
- package/dist/tools/mother.js.map +0 -1
- package/dist/tools/registry.d.ts +0 -3
- package/dist/tools/registry.d.ts.map +0 -1
- package/dist/tools/registry.js +0 -189
- package/dist/tools/registry.js.map +0 -1
- package/dist/tools/senken.d.ts +0 -11
- package/dist/tools/senken.d.ts.map +0 -1
- package/dist/tools/senken.js +0 -482
- package/dist/tools/senken.js.map +0 -1
- package/dist/tools/terminal.d.ts +0 -17
- package/dist/tools/terminal.d.ts.map +0 -1
- package/dist/tools/terminal.js +0 -968
- package/dist/tools/terminal.js.map +0 -1
- package/dist/tools/toolsIndex.d.ts +0 -5
- package/dist/tools/toolsIndex.d.ts.map +0 -1
- package/dist/tools/toolsIndex.js +0 -37
- package/dist/tools/toolsIndex.js.map +0 -1
package/dist/httpServer.js
CHANGED
|
@@ -25,7 +25,13 @@
|
|
|
25
25
|
* { "status": "error", "error": "...", "code": "..." }
|
|
26
26
|
*/
|
|
27
27
|
import { createServer } from 'http';
|
|
28
|
-
import {
|
|
28
|
+
import { randomBytes } from 'crypto';
|
|
29
|
+
import { isLoopbackPeer, isAllowedOrigin, presentedBearer, respondToPnaPreflight, timingSafeTokenCompare, } from './pty/boundary.js';
|
|
30
|
+
import { pairingOf } from './pty/pairing.js';
|
|
31
|
+
import { isSessionWritePath, isSessionAttachPath, handleSessionWrite, handleSessionUpgrade, closeAllAttachSockets, sessionAuthConfig, handleChatWrite, handlePair, isPairPath, applyPairingAuth, } from './pty/routes.js';
|
|
32
|
+
import { readChat } from './chat.js';
|
|
33
|
+
import { listHostedSessions, SessionError } from './pty/sessions.js';
|
|
34
|
+
import { fleet } from './pty/fleet.js';
|
|
29
35
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
30
36
|
import { readFileSync, existsSync } from 'fs';
|
|
31
37
|
import { fileURLToPath } from 'url';
|
|
@@ -33,12 +39,18 @@ import { dirname, join, resolve } from 'path';
|
|
|
33
39
|
import { log } from './config.js';
|
|
34
40
|
import { writeLocalAgentSession } from './agentPresence.js';
|
|
35
41
|
import { drainCommands, setSessionToken, getSessionToken, dropSessionToken, ackOwnerOf, clearAckOwner, } from './agentInbox.js';
|
|
36
|
-
import { settleCommandFromAck
|
|
42
|
+
import { settleCommandFromAck } from './agentCommands.js';
|
|
43
|
+
import { getOrgId } from './orgId.js';
|
|
37
44
|
import { isSupabaseConfigured, getSupabaseServiceClient } from './lib/supabase.js';
|
|
38
45
|
import { shouldQueueForAgent, parseToolCall } from './httpQueueDetection.js';
|
|
46
|
+
import { getExtensionDiagnostics } from './kernel.js';
|
|
39
47
|
import { getLicenseValidator } from './license.js';
|
|
40
48
|
import { listProjects } from './projectRegistry.js';
|
|
41
|
-
import { setDaemonPort } from './daemon.js';
|
|
49
|
+
import { setDaemonPort, clearDaemonPort } from './daemon.js';
|
|
50
|
+
import { RUNTIME_PROTOCOL_VERSION } from './runtime/protocol.js';
|
|
51
|
+
import { daemonStateDir } from './runtime/daemonMeta.js';
|
|
52
|
+
import { PACKAGE } from './version.js';
|
|
53
|
+
import { wrapSuccess, wrapError, envelopeHttpStatus, } from './lib/envelope.js';
|
|
42
54
|
import { listEpics, listRepoIssues, isProjectResolutionError, } from './tools/sentinel.js';
|
|
43
55
|
import { voiceInboxAdd, } from './tools/voice.js';
|
|
44
56
|
import { generateImage, getImageStatus, meshyGenerate, getMeshyStatus, meshyDownload, tripoGenerate, getTripoStatus, tripoDownload, klingGenerateVideo, klingGenerateTextVideo, klingGenerateAvatar, getKlingStatus, listTasks, } from './tools/studio/index.js';
|
|
@@ -82,6 +94,21 @@ class RateLimiter {
|
|
|
82
94
|
entry.count++;
|
|
83
95
|
return entry.count <= limit;
|
|
84
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* A named bucket with its own limit, outside the ip:/agent: namespaces so no
|
|
99
|
+
* header can land a request in it.
|
|
100
|
+
*/
|
|
101
|
+
checkBucket(bucket, limit) {
|
|
102
|
+
const key = `bucket:${bucket}`;
|
|
103
|
+
const now = Date.now();
|
|
104
|
+
const entry = this.windows.get(key);
|
|
105
|
+
if (!entry || now - entry.start > 60_000) {
|
|
106
|
+
this.windows.set(key, { count: 1, start: now });
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
entry.count++;
|
|
110
|
+
return entry.count <= limit;
|
|
111
|
+
}
|
|
85
112
|
/** Set a per-agent RPM override */
|
|
86
113
|
setAgentLimit(agentId, rpm) {
|
|
87
114
|
this.agentLimits.set(agentId, rpm);
|
|
@@ -101,29 +128,31 @@ class RateLimiter {
|
|
|
101
128
|
}
|
|
102
129
|
}
|
|
103
130
|
// ============================================================================
|
|
104
|
-
// Security:
|
|
131
|
+
// Security: shared trust boundary (src/pty/boundary.ts)
|
|
105
132
|
// ============================================================================
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
133
|
+
// The timing-safe compare and the loopback check live in one module so the REST
|
|
134
|
+
// surface and the session/attach surface cannot drift apart.
|
|
135
|
+
//
|
|
136
|
+
// isLocalAgentPeer is the trust boundary for the /agents/* endpoints: ONLY a
|
|
137
|
+
// process on this machine may register/poll/ack a local runtime (the 127.0.0.1
|
|
138
|
+
// bind is the principal — confused-deputy rule).
|
|
139
|
+
const isLocalAgentPeer = isLoopbackPeer;
|
|
140
|
+
/** Request headers any allowed browser origin may send. */
|
|
141
|
+
const GENERIC_ALLOWED_HEADERS = [
|
|
142
|
+
'Content-Type', 'Authorization', 'Mcp-Session-Id', 'Accept', 'X-Agent-Id', 'X-Run-Id',
|
|
143
|
+
'X-License-Key', 'X-Allowed-Facades', 'X-Scope', 'X-Request-Id', 'X-Parent-Call-Id',
|
|
144
|
+
'X-Engagement-Mode', 'X-User-Key', 'X-Org-Key',
|
|
145
|
+
'X-Pairing-Id', 'X-Pairing-Nonce', 'X-Pairing-Iat', 'X-Pairing-Sig',
|
|
146
|
+
];
|
|
147
|
+
/** The request carries the daemon auth_token or the sessions token as its bearer. */
|
|
148
|
+
function holdsLocalBearer(req, daemonToken) {
|
|
149
|
+
const presented = presentedBearer(req);
|
|
150
|
+
if (!presented)
|
|
112
151
|
return false;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
* The connection's trust boundary for the /agents/* endpoints: ONLY a process on
|
|
118
|
-
* this machine may register/poll/ack a local runtime (the 127.0.0.1 bind is the
|
|
119
|
-
* principal — confused-deputy rule). We check the real socket peer address (not a
|
|
120
|
-
* spoofable header). An EMPTY remoteAddress is rejected (was previously allowed) —
|
|
121
|
-
* a genuine TCP loopback connection always presents 127.0.0.1/::1, so empty only
|
|
122
|
-
* arises from a destroyed/odd socket and must not be treated as local.
|
|
123
|
-
*/
|
|
124
|
-
function isLocalAgentPeer(req) {
|
|
125
|
-
const peer = (req.socket.remoteAddress || '').replace('::ffff:', '');
|
|
126
|
-
return peer === '127.0.0.1' || peer === '::1';
|
|
152
|
+
if (daemonToken && timingSafeTokenCompare(presented, daemonToken))
|
|
153
|
+
return true;
|
|
154
|
+
const sessionsToken = sessionAuthConfig().token;
|
|
155
|
+
return !!sessionsToken && timingSafeTokenCompare(presented, sessionsToken);
|
|
127
156
|
}
|
|
128
157
|
/**
|
|
129
158
|
* Mint-or-return the per-session capability token (proof-of-possession the SDK must
|
|
@@ -158,18 +187,7 @@ function isLocalhostOrigin(origin) {
|
|
|
158
187
|
// ============================================================================
|
|
159
188
|
const __filename = fileURLToPath(import.meta.url);
|
|
160
189
|
const __dirname = dirname(__filename);
|
|
161
|
-
|
|
162
|
-
try {
|
|
163
|
-
// Try to read from package.json (works in both dev and prod)
|
|
164
|
-
const pkgPath = join(__dirname, '..', 'package.json');
|
|
165
|
-
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
166
|
-
return { version: pkg.version || '0.0.0', name: pkg.name || '@decibelsystems/tools' };
|
|
167
|
-
}
|
|
168
|
-
catch {
|
|
169
|
-
return { version: '2.0.0', name: '@decibelsystems/tools' };
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
const PKG = getVersion();
|
|
190
|
+
const PKG = PACKAGE;
|
|
173
191
|
// ============================================================================
|
|
174
192
|
// Landing Page HTML
|
|
175
193
|
// ============================================================================
|
|
@@ -208,43 +226,6 @@ function formatUptime(ms) {
|
|
|
208
226
|
return `${minutes}m ${seconds % 60}s`;
|
|
209
227
|
return `${seconds}s`;
|
|
210
228
|
}
|
|
211
|
-
/**
|
|
212
|
-
* Wrap a successful result in status envelope
|
|
213
|
-
*/
|
|
214
|
-
function wrapSuccess(data) {
|
|
215
|
-
return { status: 'executed', ...data };
|
|
216
|
-
}
|
|
217
|
-
/**
|
|
218
|
-
* Wrap an error in status envelope
|
|
219
|
-
*/
|
|
220
|
-
/**
|
|
221
|
-
* Sanitize error messages to prevent information disclosure.
|
|
222
|
-
* Removes absolute file paths and replaces with generic placeholders.
|
|
223
|
-
* Preserves URLs and relative paths.
|
|
224
|
-
*/
|
|
225
|
-
function sanitizeErrorMessage(message) {
|
|
226
|
-
let sanitized = message;
|
|
227
|
-
// Step 1: Preserve paths containing .decibel (keep the .decibel part, strip prefix)
|
|
228
|
-
// /home/user/project/.decibel/foo -> .decibel/foo
|
|
229
|
-
sanitized = sanitized.replace(/(?:\/[^\s:'"]+)?\/\.decibel\//g, '.decibel/');
|
|
230
|
-
sanitized = sanitized.replace(/(?:[A-Z]:\\[^\s:'"]+)?\\\.decibel\\/gi, '.decibel\\');
|
|
231
|
-
// Step 2: Remove absolute Unix paths (must start with / and have at least one more /)
|
|
232
|
-
// But exclude URLs (http://, https://, file://)
|
|
233
|
-
// Match: /home/user/file.txt, /var/log/app.log
|
|
234
|
-
// Don't match: http://example.com, ./relative/path
|
|
235
|
-
sanitized = sanitized.replace(/(?<!:)\/(?:home|Users|var|tmp|opt|usr|etc|media|mnt)\/[^\s:'"]+/g, '[path]');
|
|
236
|
-
// Step 3: Remove Windows absolute paths (C:\, D:\, etc.)
|
|
237
|
-
sanitized = sanitized.replace(/[A-Z]:\\[^\s:'"]+/gi, '[path]');
|
|
238
|
-
// Step 4: Sanitize any remaining usernames in paths that weren't caught
|
|
239
|
-
sanitized = sanitized.replace(/\/home\/[^\/\s]+\//g, '/home/[user]/');
|
|
240
|
-
sanitized = sanitized.replace(/\/Users\/[^\/\s]+\//g, '/Users/[user]/');
|
|
241
|
-
sanitized = sanitized.replace(/C:\\Users\\[^\\]+\\/gi, 'C:\\Users\\[user]\\');
|
|
242
|
-
return sanitized;
|
|
243
|
-
}
|
|
244
|
-
function wrapError(error, code) {
|
|
245
|
-
const sanitized = sanitizeErrorMessage(error);
|
|
246
|
-
return { status: 'error', error: sanitized, ...(code && { code }) };
|
|
247
|
-
}
|
|
248
229
|
/**
|
|
249
230
|
* Send JSON response with status envelope
|
|
250
231
|
*/
|
|
@@ -346,20 +327,30 @@ async function executeTool(tool, args, req, tierOverride) {
|
|
|
346
327
|
requestId: req.headers['x-request-id'],
|
|
347
328
|
tier: tierOverride,
|
|
348
329
|
allowedFacades,
|
|
349
|
-
|
|
330
|
+
transport: 'http',
|
|
331
|
+
} : tierOverride ? { tier: tierOverride, transport: 'http' } : { transport: 'http' };
|
|
350
332
|
const toolResult = await kernel.dispatch(tool, args, context);
|
|
351
333
|
const text = toolResult.content[0]?.text;
|
|
352
334
|
if (toolResult.isError) {
|
|
353
335
|
return wrapError(text || 'Tool execution failed', 'TOOL_ERROR');
|
|
354
336
|
}
|
|
355
|
-
// Parse
|
|
337
|
+
// Parse the tool's payload, or carry it as prose when it isn't JSON.
|
|
338
|
+
//
|
|
339
|
+
// The prose branch is legitimate — some tools return markdown — but it used
|
|
340
|
+
// to be indistinguishable from a data response that failed to parse. A
|
|
341
|
+
// caller expecting `events` got an object with no `events` key, `ok: true`,
|
|
342
|
+
// and no indication that anything unusual happened. That is how a
|
|
343
|
+
// concatenated feedback prompt (fixed in tools/shared/response.ts) turned
|
|
344
|
+
// one call in fifteen into a silent empty result for every HTTP consumer.
|
|
345
|
+
// `payload_format` makes the two cases tellable apart.
|
|
356
346
|
let result;
|
|
357
347
|
if (text) {
|
|
358
348
|
try {
|
|
359
349
|
result = JSON.parse(text);
|
|
360
350
|
}
|
|
361
351
|
catch {
|
|
362
|
-
|
|
352
|
+
log(`HTTP: tool ${tool} returned non-JSON text (${text.length} bytes) — carrying it as prose`);
|
|
353
|
+
result = { message: text, payload_format: 'text' };
|
|
363
354
|
}
|
|
364
355
|
}
|
|
365
356
|
else {
|
|
@@ -406,6 +397,7 @@ async function queueForAgent(facade, action, args, agentId, projectId) {
|
|
|
406
397
|
}
|
|
407
398
|
return {
|
|
408
399
|
status: 'queued',
|
|
400
|
+
ok: true,
|
|
409
401
|
queue_id: data.id,
|
|
410
402
|
message: 'Queued for local sync. Use agentic queue_status to check result.',
|
|
411
403
|
};
|
|
@@ -424,7 +416,7 @@ function getAvailableTools() {
|
|
|
424
416
|
* Get tools in OpenAI function calling format (facade-based)
|
|
425
417
|
*/
|
|
426
418
|
function getOpenAITools() {
|
|
427
|
-
return kernel.getMcpToolDefinitions('full').map(def => ({
|
|
419
|
+
return kernel.getMcpToolDefinitions('full', { transport: 'http' }).map(def => ({
|
|
428
420
|
type: 'function',
|
|
429
421
|
function: {
|
|
430
422
|
name: def.name,
|
|
@@ -563,14 +555,31 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
563
555
|
// else: untrusted origin → no ACAO header (browser blocks the read).
|
|
564
556
|
}
|
|
565
557
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
|
|
566
|
-
res.setHeader('Access-Control-Allow-Headers', '
|
|
558
|
+
res.setHeader('Access-Control-Allow-Headers', GENERIC_ALLOWED_HEADERS.join(', '));
|
|
567
559
|
res.setHeader('Access-Control-Expose-Headers', 'Mcp-Session-Id');
|
|
560
|
+
// Hosted-session write routes answer their own preflight: they carry the
|
|
561
|
+
// Private Network Access header and a stricter origin allowlist than the
|
|
562
|
+
// generic localhost CORS above (src/pty/routes.ts).
|
|
563
|
+
// A preflight from an allowlisted HQ origin (the dev server, or deployed HQ
|
|
564
|
+
// for paired requests) gets the Private Network Access header and the
|
|
565
|
+
// pairing headers; without PNA an https page cannot reach 127.0.0.1 at all.
|
|
566
|
+
if (isDaemon && req.method === 'OPTIONS' && isAllowedOrigin(req.headers.origin, sessionAuthConfig().allowedOrigins)) {
|
|
567
|
+
respondToPnaPreflight(req, res, sessionAuthConfig().allowedOrigins, GENERIC_ALLOWED_HEADERS);
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
568
570
|
// (a) Handle preflight OPTIONS requests
|
|
569
571
|
if (req.method === 'OPTIONS') {
|
|
570
572
|
res.writeHead(204);
|
|
571
573
|
res.end();
|
|
572
574
|
return;
|
|
573
575
|
}
|
|
576
|
+
// Signed requests from a paired browser (ADR-0012 in this repo). Verified
|
|
577
|
+
// here, once, before rate limiting and auth: a verified request is marked
|
|
578
|
+
// (pairingOf) and stands in for a bearer everywhere below. A request that
|
|
579
|
+
// CLAIMS a pairing and fails is refused outright rather than falling
|
|
580
|
+
// through as anonymous.
|
|
581
|
+
if (isDaemon && !applyPairingAuth(req, res, url))
|
|
582
|
+
return;
|
|
574
583
|
// HOSTED FAIL-CLOSED, checked EARLY (before the rate limiter) so an
|
|
575
584
|
// unauthenticated hosted caller can't consume/poison the rate-limit map or any
|
|
576
585
|
// downstream state before being refused. Public infra routes stay open so
|
|
@@ -587,9 +596,20 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
587
596
|
return;
|
|
588
597
|
}
|
|
589
598
|
// Rate limiting (check before auth to prevent brute force)
|
|
599
|
+
//
|
|
600
|
+
// Every local caller is 127.0.0.1, so by IP they all share one 100-rpm
|
|
601
|
+
// bucket: an HQ tab refreshing its roster used to spend it for the whole
|
|
602
|
+
// machine, and then attach-token and every hook call got RATE_LIMITED.
|
|
603
|
+
// A loopback caller that proves it holds the daemon bearer or the sessions
|
|
604
|
+
// bearer (HQ's dev proxy injects one server-side) gets its own, larger
|
|
605
|
+
// bucket. Anonymous loopback keeps the shared one.
|
|
590
606
|
const clientIp = (req.socket.remoteAddress || '127.0.0.1').replace('::ffff:', '');
|
|
591
607
|
const reqAgentId = req.headers['x-agent-id'];
|
|
592
|
-
|
|
608
|
+
const trustedLocal = isDaemon && isLoopbackPeer(req) && (!!pairingOf(req) || holdsLocalBearer(req, authToken));
|
|
609
|
+
const withinLimit = trustedLocal
|
|
610
|
+
? rateLimiter.checkBucket('trusted-local', Math.max(rateLimitRpm * 20, 2000))
|
|
611
|
+
: rateLimiter.check(clientIp, reqAgentId);
|
|
612
|
+
if (!withinLimit) {
|
|
593
613
|
log(`HTTP: Rate limited ${reqAgentId || clientIp}`);
|
|
594
614
|
sendJson(res, 429, wrapError('Too many requests (rate limit exceeded)', 'RATE_LIMITED'));
|
|
595
615
|
return;
|
|
@@ -602,6 +622,25 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
602
622
|
activeRequests.add(req);
|
|
603
623
|
res.on('finish', () => activeRequests.delete(req));
|
|
604
624
|
res.on('close', () => activeRequests.delete(req));
|
|
625
|
+
// Hosted-session writes (attach-token, control, spawn) are gated on the
|
|
626
|
+
// sessions bearer, not the daemon auth_token, so they are routed BEFORE the
|
|
627
|
+
// daemon's own auth gate. See src/pty/routes.ts for why. Daemon mode only:
|
|
628
|
+
// a hosted --http server has no business starting local terminals.
|
|
629
|
+
if (isDaemon && isSessionWritePath(path)) {
|
|
630
|
+
await handleSessionWrite(req, res, path);
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
// Pairing: request, status, challenge, revoke. Pre-auth by nature; none of
|
|
634
|
+
// them approves anything (approval is the CLI, on this machine).
|
|
635
|
+
if (isDaemon && isPairPath(path)) {
|
|
636
|
+
await handlePair(req, res, path);
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
// POST /chat puts text into other sessions: same gate as the session writes.
|
|
640
|
+
if (isDaemon && path === '/chat' && req.method === 'POST') {
|
|
641
|
+
await handleChatWrite(req, res);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
605
644
|
// (c) Root health check - GET / returns 200
|
|
606
645
|
if (path === '/' && req.method === 'GET') {
|
|
607
646
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -630,6 +669,10 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
630
669
|
status: 'ok',
|
|
631
670
|
version: PKG.version,
|
|
632
671
|
api_version: 'v1',
|
|
672
|
+
// Wire-contract version, negotiated by ensureRuntime(). Distinct from
|
|
673
|
+
// `version` — a long-lived daemon can outlive the clients connecting to
|
|
674
|
+
// it, and most releases do not change the contract. See runtime/protocol.ts.
|
|
675
|
+
protocol_version: RUNTIME_PROTOCOL_VERSION,
|
|
633
676
|
uptime_ms: uptimeMs,
|
|
634
677
|
uptime_human: formatUptime(uptimeMs),
|
|
635
678
|
pid: process.pid,
|
|
@@ -642,6 +685,17 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
642
685
|
pro: licenseTier !== 'core',
|
|
643
686
|
license_tier: licenseTier,
|
|
644
687
|
supabase_configured: isSupabaseConfigured(),
|
|
688
|
+
// Facades whose dependency is failing. `{}` is the healthy case — a
|
|
689
|
+
// non-empty object means calls to those facades are being refused fast
|
|
690
|
+
// rather than left to time out. See runtime/circuitBreaker.ts.
|
|
691
|
+
circuits: kernel.circuitSnapshot(),
|
|
692
|
+
// Private facades loaded from the allowlist, and every entry refused.
|
|
693
|
+
// Reported because the alternative is silence: an extension that failed
|
|
694
|
+
// to load looks exactly like one that was never configured, and this
|
|
695
|
+
// daemon has already shipped one bug of that shape — a broken voice
|
|
696
|
+
// inbox that read as "0 messages" for five hours. See
|
|
697
|
+
// runtime/extensions.ts.
|
|
698
|
+
extensions: getExtensionDiagnostics(),
|
|
645
699
|
}));
|
|
646
700
|
return;
|
|
647
701
|
}
|
|
@@ -667,14 +721,14 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
667
721
|
sendJson(res, 401, wrapError('This endpoint requires authentication. Hosted (--http) mode must be started with DECIBEL_AUTH_TOKEN set.', 'AUTH_NOT_CONFIGURED'));
|
|
668
722
|
return;
|
|
669
723
|
}
|
|
670
|
-
if (authToken) {
|
|
724
|
+
if (authToken && !pairingOf(req)) {
|
|
671
725
|
const authHeader = req.headers.authorization;
|
|
672
726
|
if (!authHeader || !timingSafeTokenCompare(authHeader, `Bearer ${authToken}`)) {
|
|
673
727
|
sendJson(res, 401, wrapError('Unauthorized', 'UNAUTHORIZED'));
|
|
674
728
|
return;
|
|
675
729
|
}
|
|
676
730
|
}
|
|
677
|
-
const dispatchLogPath = join(
|
|
731
|
+
const dispatchLogPath = join(daemonStateDir(), 'logs', 'dispatch.jsonl');
|
|
678
732
|
try {
|
|
679
733
|
const content = readFileSync(dispatchLogPath, 'utf-8');
|
|
680
734
|
const lines = content.trim().split('\n').filter(Boolean);
|
|
@@ -772,8 +826,9 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
772
826
|
sendJson(res, 401, wrapError('This endpoint requires authentication. Hosted (--http) mode must be started with DECIBEL_AUTH_TOKEN set.', 'AUTH_NOT_CONFIGURED'));
|
|
773
827
|
return;
|
|
774
828
|
}
|
|
775
|
-
// Auth check (timing-safe comparison to prevent timing attacks)
|
|
776
|
-
|
|
829
|
+
// Auth check (timing-safe comparison to prevent timing attacks). A request
|
|
830
|
+
// verified under an approved pairing already proved more than a bearer.
|
|
831
|
+
if (authToken && !pairingOf(req)) {
|
|
777
832
|
const authHeader = req.headers.authorization;
|
|
778
833
|
if (!authHeader || !timingSafeTokenCompare(authHeader, `Bearer ${authToken}`)) {
|
|
779
834
|
log('HTTP: Unauthorized request');
|
|
@@ -784,7 +839,62 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
784
839
|
// ========================================================================
|
|
785
840
|
// Simple REST Endpoints (for external AI agents)
|
|
786
841
|
// ========================================================================
|
|
842
|
+
// GET /sessions[?project_id=<id>] — hosted-session roster for HQ (ADR-0012 /
|
|
843
|
+
// decibel-hq EPIC-0015). Without project_id, every session on the machine.
|
|
844
|
+
// Read-only, so it sits behind the normal daemon gate; the write routes are
|
|
845
|
+
// handled above with their own bearer.
|
|
846
|
+
if (isDaemon && path === '/sessions' && req.method === 'GET') {
|
|
847
|
+
const projectId = url.searchParams.get('project_id') || undefined;
|
|
848
|
+
try {
|
|
849
|
+
const sessions = await listHostedSessions(projectId);
|
|
850
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
851
|
+
res.end(JSON.stringify({ sessions }));
|
|
852
|
+
}
|
|
853
|
+
catch (err) {
|
|
854
|
+
if (err instanceof SessionError) {
|
|
855
|
+
sendJson(res, err.status, wrapError(err.message, err.code));
|
|
856
|
+
}
|
|
857
|
+
else {
|
|
858
|
+
sendJson(res, 500, wrapError(err instanceof Error ? err.message : String(err), 'INTERNAL'));
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
// GET /sessions/last-fleet — the sessions to offer for restore (decibel-hq
|
|
864
|
+
// ADR-0018). Read-only, behind the normal daemon gate like GET /sessions.
|
|
865
|
+
if (isDaemon && path === '/sessions/last-fleet' && req.method === 'GET') {
|
|
866
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
867
|
+
res.end(JSON.stringify(fleet().lastFleet()));
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
// GET /chat?since=<id>&limit= — the HQ chat log (decibel-hq EPIC-0016).
|
|
871
|
+
// Read-only, behind the normal daemon gate. human_peer_id changes whenever
|
|
872
|
+
// the peer re-registers, so clients read it from here every time.
|
|
873
|
+
if (isDaemon && path === '/chat' && req.method === 'GET') {
|
|
874
|
+
const since = Number(url.searchParams.get('since') ?? 0) || 0;
|
|
875
|
+
const limit = Number(url.searchParams.get('limit') ?? 200) || 200;
|
|
876
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
877
|
+
res.end(JSON.stringify(readChat(since, limit)));
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
787
880
|
// GET /tools - List available tools
|
|
881
|
+
// MCP tool definitions, verbatim — the shape a `tools/list` response needs.
|
|
882
|
+
//
|
|
883
|
+
// This is what lets a stdio client stop building its own kernel. /tools
|
|
884
|
+
// above is a human/OpenAI-shaped summary; this one is the exact array the
|
|
885
|
+
// MCP handler returns, so a thin adapter can serve tools/list by forwarding
|
|
886
|
+
// it rather than loading 195 tool modules to produce the same bytes.
|
|
887
|
+
if (path === '/mcp/tools' && req.method === 'GET') {
|
|
888
|
+
const tierParam = url.searchParams.get('tier');
|
|
889
|
+
const tier = (tierParam === 'compact' || tierParam === 'micro' ? tierParam : 'full');
|
|
890
|
+
sendJson(res, 200, wrapSuccess({
|
|
891
|
+
version: PKG.version,
|
|
892
|
+
protocol_version: RUNTIME_PROTOCOL_VERSION,
|
|
893
|
+
tier,
|
|
894
|
+
tools: kernel.getMcpToolDefinitions(tier, { transport: 'http' }),
|
|
895
|
+
}));
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
788
898
|
if (path === '/tools' && req.method === 'GET') {
|
|
789
899
|
sendJson(res, 200, wrapSuccess({
|
|
790
900
|
version: PKG.version,
|
|
@@ -944,7 +1054,13 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
944
1054
|
sendJson(res, 403, wrapError('Invalid or missing session token', 'BAD_SESSION_TOKEN'));
|
|
945
1055
|
return;
|
|
946
1056
|
}
|
|
947
|
-
|
|
1057
|
+
// No org means no dispatcher ran, so nothing can be queued for this session.
|
|
1058
|
+
let org = null;
|
|
1059
|
+
try {
|
|
1060
|
+
org = getOrgId();
|
|
1061
|
+
}
|
|
1062
|
+
catch { /* malformed: the startup gate reports it */ }
|
|
1063
|
+
const commands = org ? drainCommands(sessionKey, org) : [];
|
|
948
1064
|
sendJson(res, 200, wrapSuccess({ session_key: sessionKey, commands, count: commands.length }));
|
|
949
1065
|
return;
|
|
950
1066
|
}
|
|
@@ -1151,7 +1267,7 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
1151
1267
|
const tier = await resolveTier(req, configLicenseKey);
|
|
1152
1268
|
log(`HTTP: /call tool=${tool} tier=${tier}`);
|
|
1153
1269
|
const result = await executeTool(tool, args, req, tier);
|
|
1154
|
-
sendJson(res, result
|
|
1270
|
+
sendJson(res, envelopeHttpStatus(result), result);
|
|
1155
1271
|
}
|
|
1156
1272
|
catch (error) {
|
|
1157
1273
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1191,10 +1307,30 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
1191
1307
|
requestId: req.headers['x-request-id'] || bodyContext.requestId,
|
|
1192
1308
|
allowedFacades: bodyContext.allowedFacades,
|
|
1193
1309
|
tier,
|
|
1310
|
+
// Set here, never read from the request: a caller that could name its
|
|
1311
|
+
// own transport could name 'stdio' and walk straight through the
|
|
1312
|
+
// local-only gate.
|
|
1313
|
+
transport: 'http',
|
|
1194
1314
|
};
|
|
1195
1315
|
log(`HTTP: /batch — ${calls.length} calls (agent=${context.agentId || 'anonymous'}, tier=${tier})`);
|
|
1196
1316
|
const results = await kernel.batch(calls, context);
|
|
1197
|
-
|
|
1317
|
+
// `ok` reflects STRUCTURAL validity: did every call name something this
|
|
1318
|
+
// runtime actually has. A call that ran and returned isError leaves ok
|
|
1319
|
+
// true — partial failure is a normal batch outcome and callers depend on
|
|
1320
|
+
// that. A call naming a facade that is not registered does not.
|
|
1321
|
+
//
|
|
1322
|
+
// The status stays 200 because the body still carries real results for
|
|
1323
|
+
// the other calls in the batch, and throwing those away behind a 4xx
|
|
1324
|
+
// would trade one silent failure for another.
|
|
1325
|
+
const unknown = results.filter(r => r.code === 'UNKNOWN_FACADE');
|
|
1326
|
+
if (unknown.length > 0) {
|
|
1327
|
+
log(`HTTP: /batch — ${unknown.length} unknown facade(s): ${unknown.map(r => r.facade).join(', ')}`);
|
|
1328
|
+
}
|
|
1329
|
+
sendJson(res, 200, {
|
|
1330
|
+
status: unknown.length > 0 ? 'error' : 'executed',
|
|
1331
|
+
ok: unknown.length === 0,
|
|
1332
|
+
results,
|
|
1333
|
+
});
|
|
1198
1334
|
}
|
|
1199
1335
|
catch (error) {
|
|
1200
1336
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1223,7 +1359,7 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
1223
1359
|
const body = await parseBody(req);
|
|
1224
1360
|
log(`HTTP: /api/tools/${toolName}`);
|
|
1225
1361
|
const result = await executeTool(toolName, body);
|
|
1226
|
-
sendJson(res, result
|
|
1362
|
+
sendJson(res, envelopeHttpStatus(result), result);
|
|
1227
1363
|
}
|
|
1228
1364
|
catch (error) {
|
|
1229
1365
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1240,7 +1376,7 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
1240
1376
|
const body = await parseBody(req);
|
|
1241
1377
|
log('HTTP: /dojo/wish');
|
|
1242
1378
|
const result = await executeTool('dojo_add_wish', body);
|
|
1243
|
-
sendJson(res, result
|
|
1379
|
+
sendJson(res, envelopeHttpStatus(result), result);
|
|
1244
1380
|
}
|
|
1245
1381
|
catch (error) {
|
|
1246
1382
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1254,7 +1390,7 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
1254
1390
|
const body = await parseBody(req);
|
|
1255
1391
|
log('HTTP: /dojo/propose');
|
|
1256
1392
|
const result = await executeTool('dojo_create_proposal', body);
|
|
1257
|
-
sendJson(res, result
|
|
1393
|
+
sendJson(res, envelopeHttpStatus(result), result);
|
|
1258
1394
|
}
|
|
1259
1395
|
catch (error) {
|
|
1260
1396
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1268,7 +1404,7 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
1268
1404
|
const body = await parseBody(req);
|
|
1269
1405
|
log('HTTP: /dojo/scaffold');
|
|
1270
1406
|
const result = await executeTool('dojo_scaffold_experiment', body);
|
|
1271
|
-
sendJson(res, result
|
|
1407
|
+
sendJson(res, envelopeHttpStatus(result), result);
|
|
1272
1408
|
}
|
|
1273
1409
|
catch (error) {
|
|
1274
1410
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1282,7 +1418,7 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
1282
1418
|
const body = await parseBody(req);
|
|
1283
1419
|
log('HTTP: /dojo/run');
|
|
1284
1420
|
const result = await executeTool('dojo_run_experiment', body);
|
|
1285
|
-
sendJson(res, result
|
|
1421
|
+
sendJson(res, envelopeHttpStatus(result), result);
|
|
1286
1422
|
}
|
|
1287
1423
|
catch (error) {
|
|
1288
1424
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1296,7 +1432,7 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
1296
1432
|
const body = await parseBody(req);
|
|
1297
1433
|
log('HTTP: /dojo/results');
|
|
1298
1434
|
const result = await executeTool('dojo_read_results', body);
|
|
1299
|
-
sendJson(res, result
|
|
1435
|
+
sendJson(res, envelopeHttpStatus(result), result);
|
|
1300
1436
|
}
|
|
1301
1437
|
catch (error) {
|
|
1302
1438
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1317,7 +1453,7 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
1317
1453
|
}
|
|
1318
1454
|
log('HTTP: /dojo/list');
|
|
1319
1455
|
const result = await executeTool('dojo_list', body);
|
|
1320
|
-
sendJson(res, result
|
|
1456
|
+
sendJson(res, envelopeHttpStatus(result), result);
|
|
1321
1457
|
}
|
|
1322
1458
|
catch (error) {
|
|
1323
1459
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1337,7 +1473,7 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
1337
1473
|
}
|
|
1338
1474
|
log('HTTP: /dojo/wishes');
|
|
1339
1475
|
const result = await executeTool('dojo_list_wishes', body);
|
|
1340
|
-
sendJson(res, result
|
|
1476
|
+
sendJson(res, envelopeHttpStatus(result), result);
|
|
1341
1477
|
}
|
|
1342
1478
|
catch (error) {
|
|
1343
1479
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1351,7 +1487,7 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
1351
1487
|
const body = await parseBody(req);
|
|
1352
1488
|
log('HTTP: /dojo/can-graduate');
|
|
1353
1489
|
const result = await executeTool('dojo_can_graduate', body);
|
|
1354
|
-
sendJson(res, result
|
|
1490
|
+
sendJson(res, envelopeHttpStatus(result), result);
|
|
1355
1491
|
}
|
|
1356
1492
|
catch (error) {
|
|
1357
1493
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1365,7 +1501,7 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
1365
1501
|
const body = await parseBody(req);
|
|
1366
1502
|
log('HTTP: /dojo/artifact');
|
|
1367
1503
|
const result = await executeTool('dojo_read_artifact', body);
|
|
1368
|
-
sendJson(res, result
|
|
1504
|
+
sendJson(res, envelopeHttpStatus(result), result);
|
|
1369
1505
|
}
|
|
1370
1506
|
catch (error) {
|
|
1371
1507
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1379,7 +1515,7 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
1379
1515
|
const body = await parseBody(req);
|
|
1380
1516
|
log('HTTP: /dojo/bench');
|
|
1381
1517
|
const result = await executeTool('dojo_bench', body);
|
|
1382
|
-
sendJson(res, result
|
|
1518
|
+
sendJson(res, envelopeHttpStatus(result), result);
|
|
1383
1519
|
}
|
|
1384
1520
|
catch (error) {
|
|
1385
1521
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1396,7 +1532,7 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
1396
1532
|
const body = await parseBody(req);
|
|
1397
1533
|
log('HTTP: /bench/run');
|
|
1398
1534
|
const result = await executeTool('decibel_bench', body);
|
|
1399
|
-
sendJson(res, result
|
|
1535
|
+
sendJson(res, envelopeHttpStatus(result), result);
|
|
1400
1536
|
}
|
|
1401
1537
|
catch (error) {
|
|
1402
1538
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1410,7 +1546,7 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
1410
1546
|
const body = await parseBody(req);
|
|
1411
1547
|
log('HTTP: /bench/compare');
|
|
1412
1548
|
const result = await executeTool('decibel_bench_compare', body);
|
|
1413
|
-
sendJson(res, result
|
|
1549
|
+
sendJson(res, envelopeHttpStatus(result), result);
|
|
1414
1550
|
}
|
|
1415
1551
|
catch (error) {
|
|
1416
1552
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1427,7 +1563,7 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
1427
1563
|
const body = await parseBody(req);
|
|
1428
1564
|
log('HTTP: /context/refresh');
|
|
1429
1565
|
const result = await executeTool('decibel_context_refresh', body);
|
|
1430
|
-
sendJson(res, result
|
|
1566
|
+
sendJson(res, envelopeHttpStatus(result), result);
|
|
1431
1567
|
}
|
|
1432
1568
|
catch (error) {
|
|
1433
1569
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1441,7 +1577,7 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
1441
1577
|
const body = await parseBody(req);
|
|
1442
1578
|
log('HTTP: /context/pin');
|
|
1443
1579
|
const result = await executeTool('decibel_context_pin', body);
|
|
1444
|
-
sendJson(res, result
|
|
1580
|
+
sendJson(res, envelopeHttpStatus(result), result);
|
|
1445
1581
|
}
|
|
1446
1582
|
catch (error) {
|
|
1447
1583
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1455,7 +1591,7 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
1455
1591
|
const body = await parseBody(req);
|
|
1456
1592
|
log('HTTP: /context/unpin');
|
|
1457
1593
|
const result = await executeTool('decibel_context_unpin', body);
|
|
1458
|
-
sendJson(res, result
|
|
1594
|
+
sendJson(res, envelopeHttpStatus(result), result);
|
|
1459
1595
|
}
|
|
1460
1596
|
catch (error) {
|
|
1461
1597
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1475,7 +1611,7 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
1475
1611
|
}
|
|
1476
1612
|
log('HTTP: /context/list');
|
|
1477
1613
|
const result = await executeTool('decibel_context_list', body);
|
|
1478
|
-
sendJson(res, result
|
|
1614
|
+
sendJson(res, envelopeHttpStatus(result), result);
|
|
1479
1615
|
}
|
|
1480
1616
|
catch (error) {
|
|
1481
1617
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1489,7 +1625,7 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
1489
1625
|
const body = await parseBody(req);
|
|
1490
1626
|
log('HTTP: /event/append');
|
|
1491
1627
|
const result = await executeTool('decibel_event_append', body);
|
|
1492
|
-
sendJson(res, result
|
|
1628
|
+
sendJson(res, envelopeHttpStatus(result), result);
|
|
1493
1629
|
}
|
|
1494
1630
|
catch (error) {
|
|
1495
1631
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1517,7 +1653,7 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
1517
1653
|
}
|
|
1518
1654
|
log('HTTP: /event/search');
|
|
1519
1655
|
const result = await executeTool('decibel_event_search', body);
|
|
1520
|
-
sendJson(res, result
|
|
1656
|
+
sendJson(res, envelopeHttpStatus(result), result);
|
|
1521
1657
|
}
|
|
1522
1658
|
catch (error) {
|
|
1523
1659
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1531,7 +1667,7 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
1531
1667
|
const body = await parseBody(req);
|
|
1532
1668
|
log('HTTP: /artifact/list');
|
|
1533
1669
|
const result = await executeTool('decibel_artifact_list', body);
|
|
1534
|
-
sendJson(res, result
|
|
1670
|
+
sendJson(res, envelopeHttpStatus(result), result);
|
|
1535
1671
|
}
|
|
1536
1672
|
catch (error) {
|
|
1537
1673
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1545,7 +1681,7 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
1545
1681
|
const body = await parseBody(req);
|
|
1546
1682
|
log('HTTP: /artifact/read');
|
|
1547
1683
|
const result = await executeTool('decibel_artifact_read', body);
|
|
1548
|
-
sendJson(res, result
|
|
1684
|
+
sendJson(res, envelopeHttpStatus(result), result);
|
|
1549
1685
|
}
|
|
1550
1686
|
catch (error) {
|
|
1551
1687
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1769,6 +1905,16 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
1769
1905
|
// ========================================================================
|
|
1770
1906
|
// Studio API Endpoints (frontend_v0.2 compatible)
|
|
1771
1907
|
// ========================================================================
|
|
1908
|
+
// Tier guard: these REST shortcuts call studio (pro-tier) tools directly,
|
|
1909
|
+
// bypassing kernel dispatch where tier gating normally lives. Enforce it
|
|
1910
|
+
// here once for all /api/generate-* routes. (crucible re-run 2026-06-07)
|
|
1911
|
+
if (path.startsWith('/api/generate-') && req.method === 'POST') {
|
|
1912
|
+
const tier = await resolveTier(req, configLicenseKey);
|
|
1913
|
+
if (tier === 'core') {
|
|
1914
|
+
sendJson(res, 403, wrapError('Studio generation requires a pro license', 'TIER_REQUIRED'));
|
|
1915
|
+
return;
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1772
1918
|
// POST /api/generate-flux-kontext-image - Start image generation
|
|
1773
1919
|
if (path === '/api/generate-flux-kontext-image' && req.method === 'POST') {
|
|
1774
1920
|
try {
|
|
@@ -2122,17 +2268,46 @@ export async function startHttpServer(server, kernelInstance, options) {
|
|
|
2122
2268
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
2123
2269
|
res.end(JSON.stringify({ error: 'Not found' }));
|
|
2124
2270
|
});
|
|
2271
|
+
// WebSocket upgrades. The only upgrade this server accepts is a hosted-session
|
|
2272
|
+
// attach (daemon mode); everything else is refused rather than left hanging,
|
|
2273
|
+
// since Node leaves an unhandled upgrade socket open until it times out.
|
|
2274
|
+
httpServer.on('upgrade', (req, socket, head) => {
|
|
2275
|
+
const upgradeUrl = new URL(req.url || '/', 'http://127.0.0.1');
|
|
2276
|
+
if (isDaemon && isSessionAttachPath(upgradeUrl.pathname)) {
|
|
2277
|
+
handleSessionUpgrade(req, socket, head, upgradeUrl).catch((err) => {
|
|
2278
|
+
log(`HTTP: attach upgrade failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2279
|
+
try {
|
|
2280
|
+
socket.destroy();
|
|
2281
|
+
}
|
|
2282
|
+
catch { /* ignore */ }
|
|
2283
|
+
});
|
|
2284
|
+
return;
|
|
2285
|
+
}
|
|
2286
|
+
socket.write('HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\nConnection: close\r\n\r\n');
|
|
2287
|
+
socket.destroy();
|
|
2288
|
+
});
|
|
2125
2289
|
// Configure HTTP server timeouts to prevent premature connection drops
|
|
2126
2290
|
httpServer.keepAliveTimeout = timeoutMs;
|
|
2127
2291
|
httpServer.headersTimeout = timeoutMs + 1000; // Slightly longer than keepAliveTimeout
|
|
2128
2292
|
httpServer.listen(port, host, () => {
|
|
2129
2293
|
log(`HTTP Server listening on http://${host}:${port}`);
|
|
2130
|
-
// Advertise port + pid in daemon.meta so clients can discover the daemon
|
|
2131
|
-
|
|
2132
|
-
|
|
2294
|
+
// Advertise port + pid in daemon.meta so clients can discover the daemon —
|
|
2295
|
+
// but ONLY as the daemon. daemon.meta is machine-global: the session-init
|
|
2296
|
+
// hook, the close hook, the HQ client and ensureRuntime all follow it. A
|
|
2297
|
+
// plain `--http` run is a process someone started for a reason of their own
|
|
2298
|
+
// (a test, a second checkout, CI, the hosted deployment), and when it
|
|
2299
|
+
// advertised, every hook on the machine followed it to a port that died
|
|
2300
|
+
// with it (ISS-0179).
|
|
2301
|
+
if (isDaemon) {
|
|
2302
|
+
try {
|
|
2303
|
+
setDaemonPort(port);
|
|
2304
|
+
}
|
|
2305
|
+
catch (err) {
|
|
2306
|
+
log(`Daemon: failed to write port to meta: ${err instanceof Error ? err.message : String(err)}`);
|
|
2307
|
+
}
|
|
2133
2308
|
}
|
|
2134
|
-
|
|
2135
|
-
log(`
|
|
2309
|
+
else {
|
|
2310
|
+
log(`HTTP Server: not advertising in daemon.meta (not --daemon); clients keep their existing discovery`);
|
|
2136
2311
|
}
|
|
2137
2312
|
console.log(`
|
|
2138
2313
|
╔══════════════════════════════════════════════════════════════╗
|
|
@@ -2183,6 +2358,8 @@ ${authToken ? '║ Auth: Bearer token required
|
|
|
2183
2358
|
log(`HTTP: ${activeRequests.size} request(s) still active after 10s drain timeout`);
|
|
2184
2359
|
}
|
|
2185
2360
|
}
|
|
2361
|
+
// Close attach sockets (their tmux sessions live on)
|
|
2362
|
+
closeAllAttachSockets();
|
|
2186
2363
|
// Close SSE connections
|
|
2187
2364
|
for (const conn of activeSseConnections) {
|
|
2188
2365
|
try {
|
|
@@ -2199,40 +2376,17 @@ ${authToken ? '║ Auth: Bearer token required
|
|
|
2199
2376
|
// If already closed, resolve immediately
|
|
2200
2377
|
setTimeout(resolve, 100);
|
|
2201
2378
|
});
|
|
2379
|
+
// Withdraw discovery if we owned it, so a clean shutdown does not leave
|
|
2380
|
+
// every client on the machine dialling a port nothing is serving
|
|
2381
|
+
// (ISS-0179). clearDaemonPort only clears an entry that is ours.
|
|
2382
|
+
if (isDaemon)
|
|
2383
|
+
clearDaemonPort();
|
|
2202
2384
|
log('HTTP server stopped');
|
|
2203
2385
|
},
|
|
2204
2386
|
};
|
|
2205
2387
|
}
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
export
|
|
2210
|
-
const httpMode = args.includes('--http');
|
|
2211
|
-
const portIndex = args.indexOf('--port');
|
|
2212
|
-
// --port flag wins; else honor PORT env (Render sets it); else leave undefined
|
|
2213
|
-
// so daemonConfig's default (4888) applies downstream instead of being
|
|
2214
|
-
// short-circuited by a baked-in default here. See server.ts port/host resolution.
|
|
2215
|
-
const port = portIndex !== -1
|
|
2216
|
-
? parseInt(args[portIndex + 1], 10)
|
|
2217
|
-
: process.env.PORT
|
|
2218
|
-
? parseInt(process.env.PORT, 10)
|
|
2219
|
-
: undefined;
|
|
2220
|
-
// SECURITY: Prefer env var for auth token (CLI args visible in ps/history)
|
|
2221
|
-
// Fall back to --auth-token for backwards compatibility
|
|
2222
|
-
const authIndex = args.indexOf('--auth-token');
|
|
2223
|
-
const authToken = process.env.DECIBEL_AUTH_TOKEN ||
|
|
2224
|
-
(authIndex !== -1 ? args[authIndex + 1] : undefined);
|
|
2225
|
-
const hostIndex = args.indexOf('--host');
|
|
2226
|
-
// --host flag wins; else leave undefined so daemonConfig host (127.0.0.1, daemon
|
|
2227
|
-
// mode) or the transport default applies instead of being short-circuited here.
|
|
2228
|
-
const host = hostIndex !== -1 ? args[hostIndex + 1] : undefined;
|
|
2229
|
-
// SSE/Connection tuning arguments
|
|
2230
|
-
const keepaliveIndex = args.indexOf('--sse-keepalive');
|
|
2231
|
-
const sseKeepaliveMs = keepaliveIndex !== -1 ? parseInt(args[keepaliveIndex + 1], 10) : undefined;
|
|
2232
|
-
const timeoutIndex = args.indexOf('--timeout');
|
|
2233
|
-
const timeoutMs = timeoutIndex !== -1 ? parseInt(args[timeoutIndex + 1], 10) : undefined;
|
|
2234
|
-
const retryIndex = args.indexOf('--sse-retry');
|
|
2235
|
-
const retryIntervalMs = retryIndex !== -1 ? parseInt(args[retryIndex + 1], 10) : undefined;
|
|
2236
|
-
return { httpMode, port, authToken, host, sseKeepaliveMs, timeoutMs, retryIntervalMs };
|
|
2237
|
-
}
|
|
2388
|
+
// parseHttpArgs moved to ./httpArgs.js so that argv parsing carries no
|
|
2389
|
+
// dependencies. Re-exported here because it has always been part of this
|
|
2390
|
+
// module's surface.
|
|
2391
|
+
export { parseHttpArgs } from './httpArgs.js';
|
|
2238
2392
|
//# sourceMappingURL=httpServer.js.map
|