@cleocode/core 2026.3.74 → 2026.4.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/README.md +1 -1
- package/dist/agents/agent-schema.d.ts.map +1 -1
- package/dist/agents/retry.js +26 -21
- package/dist/agents/retry.js.map +1 -1
- package/dist/cant/approval.d.ts +110 -0
- package/dist/cant/approval.d.ts.map +1 -0
- package/dist/cant/approval.js +185 -0
- package/dist/cant/approval.js.map +1 -0
- package/dist/cant/context-builder.d.ts +79 -0
- package/dist/cant/context-builder.d.ts.map +1 -0
- package/dist/cant/context-builder.js +117 -0
- package/dist/cant/context-builder.js.map +1 -0
- package/dist/cant/discretion.d.ts +95 -0
- package/dist/cant/discretion.d.ts.map +1 -0
- package/dist/cant/discretion.js +116 -0
- package/dist/cant/discretion.js.map +1 -0
- package/dist/cant/index.d.ts +25 -0
- package/dist/cant/index.d.ts.map +1 -0
- package/dist/cant/index.js +23 -0
- package/dist/cant/index.js.map +1 -0
- package/dist/cant/parallel-runner.d.ts +38 -0
- package/dist/cant/parallel-runner.d.ts.map +1 -0
- package/dist/cant/parallel-runner.js +173 -0
- package/dist/cant/parallel-runner.js.map +1 -0
- package/dist/cant/types.d.ts +127 -0
- package/dist/cant/types.d.ts.map +1 -0
- package/dist/cant/types.js +11 -0
- package/dist/cant/types.js.map +1 -0
- package/dist/cant/workflow-executor.d.ts +105 -0
- package/dist/cant/workflow-executor.d.ts.map +1 -0
- package/dist/cant/workflow-executor.js +440 -0
- package/dist/cant/workflow-executor.js.map +1 -0
- package/dist/cleo.js +21 -1
- package/dist/cleo.js.map +1 -1
- package/dist/code/index.d.ts +10 -0
- package/dist/code/index.d.ts.map +1 -0
- package/dist/code/outline.d.ts +51 -0
- package/dist/code/outline.d.ts.map +1 -0
- package/dist/code/parser.d.ts +30 -0
- package/dist/code/parser.d.ts.map +1 -0
- package/dist/code/search.d.ts +42 -0
- package/dist/code/search.d.ts.map +1 -0
- package/dist/code/unfold.d.ts +44 -0
- package/dist/code/unfold.d.ts.map +1 -0
- package/dist/conduit/conduit-client.d.ts +35 -0
- package/dist/conduit/conduit-client.d.ts.map +1 -0
- package/dist/conduit/conduit-client.js +94 -0
- package/dist/conduit/conduit-client.js.map +1 -0
- package/dist/conduit/factory.d.ts +15 -0
- package/dist/conduit/factory.d.ts.map +1 -0
- package/dist/conduit/factory.js +35 -0
- package/dist/conduit/factory.js.map +1 -0
- package/dist/conduit/http-transport.d.ts +44 -0
- package/dist/conduit/http-transport.d.ts.map +1 -0
- package/dist/conduit/http-transport.js +165 -0
- package/dist/conduit/http-transport.js.map +1 -0
- package/dist/conduit/index.d.ts +15 -0
- package/dist/conduit/index.d.ts.map +1 -0
- package/dist/conduit/index.js +12 -0
- package/dist/conduit/index.js.map +1 -0
- package/dist/conduit/local-transport.d.ts +91 -0
- package/dist/conduit/local-transport.d.ts.map +1 -0
- package/dist/conduit/sse-transport.d.ts +68 -0
- package/dist/conduit/sse-transport.d.ts.map +1 -0
- package/dist/config.js +4 -3
- package/dist/config.js.map +1 -1
- package/dist/crypto/credentials.d.ts +40 -0
- package/dist/crypto/credentials.d.ts.map +1 -0
- package/dist/crypto/credentials.js +144 -0
- package/dist/crypto/credentials.js.map +1 -0
- package/dist/engine-result.d.ts +1 -1
- package/dist/engine-result.d.ts.map +1 -1
- package/dist/error-catalog.d.ts +1 -1
- package/dist/error-catalog.d.ts.map +1 -1
- package/dist/error-registry.d.ts +1 -1
- package/dist/error-registry.d.ts.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/hooks/handlers/agent-hooks.d.ts.map +1 -1
- package/dist/hooks/handlers/agent-hooks.js +106 -0
- package/dist/hooks/handlers/agent-hooks.js.map +1 -0
- package/dist/hooks/handlers/context-hooks.d.ts.map +1 -1
- package/dist/hooks/handlers/context-hooks.js +111 -0
- package/dist/hooks/handlers/context-hooks.js.map +1 -0
- package/dist/hooks/handlers/error-hooks.d.ts +14 -5
- package/dist/hooks/handlers/error-hooks.d.ts.map +1 -1
- package/dist/hooks/handlers/error-hooks.js +15 -6
- package/dist/hooks/handlers/error-hooks.js.map +1 -1
- package/dist/hooks/handlers/file-hooks.d.ts.map +1 -1
- package/dist/hooks/handlers/file-hooks.js +35 -11
- package/dist/hooks/handlers/file-hooks.js.map +1 -1
- package/dist/hooks/handlers/handler-helpers.d.ts +41 -0
- package/dist/hooks/handlers/handler-helpers.d.ts.map +1 -0
- package/dist/hooks/handlers/handler-helpers.js +61 -0
- package/dist/hooks/handlers/handler-helpers.js.map +1 -0
- package/dist/hooks/handlers/index.js +10 -1
- package/dist/hooks/handlers/index.js.map +1 -1
- package/dist/hooks/handlers/mcp-hooks.d.ts.map +1 -1
- package/dist/hooks/handlers/mcp-hooks.js +88 -21
- package/dist/hooks/handlers/mcp-hooks.js.map +1 -1
- package/dist/hooks/handlers/session-hooks.d.ts.map +1 -1
- package/dist/hooks/handlers/session-hooks.js +5 -10
- package/dist/hooks/handlers/session-hooks.js.map +1 -1
- package/dist/hooks/handlers/task-hooks.d.ts.map +1 -1
- package/dist/hooks/handlers/task-hooks.js +5 -10
- package/dist/hooks/handlers/task-hooks.js.map +1 -1
- package/dist/hooks/handlers/work-capture-hooks.d.ts.map +1 -1
- package/dist/hooks/handlers/work-capture-hooks.js +165 -0
- package/dist/hooks/handlers/work-capture-hooks.js.map +1 -0
- package/dist/hooks/payload-schemas.js +83 -26
- package/dist/hooks/payload-schemas.js.map +1 -1
- package/dist/hooks/provider-hooks.js +37 -5
- package/dist/hooks/provider-hooks.js.map +1 -1
- package/dist/hooks/registry.js +76 -23
- package/dist/hooks/registry.js.map +1 -1
- package/dist/hooks/types.js +17 -13
- package/dist/hooks/types.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6452 -3371
- package/dist/index.js.map +4 -4
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +12 -0
- package/dist/init.js.map +1 -1
- package/dist/internal.d.ts +11 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +10 -0
- package/dist/internal.js.map +1 -1
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/tree-sitter-languages.d.ts +29 -0
- package/dist/lib/tree-sitter-languages.d.ts.map +1 -0
- package/dist/memory/brain-links.d.ts.map +1 -1
- package/dist/memory/brain-maintenance.d.ts +13 -0
- package/dist/memory/brain-maintenance.d.ts.map +1 -1
- package/dist/memory/brain-retrieval.d.ts +3 -0
- package/dist/memory/brain-retrieval.d.ts.map +1 -1
- package/dist/memory/brain-retrieval.js +5 -0
- package/dist/memory/brain-retrieval.js.map +1 -1
- package/dist/memory/decisions.d.ts.map +1 -1
- package/dist/mvi-helpers.d.ts +52 -0
- package/dist/mvi-helpers.d.ts.map +1 -0
- package/dist/mvi-helpers.js +74 -0
- package/dist/mvi-helpers.js.map +1 -0
- package/dist/nexus/index.js +2 -0
- package/dist/nexus/index.js.map +1 -1
- package/dist/nexus/workspace.d.ts.map +1 -1
- package/dist/nexus/workspace.js +355 -0
- package/dist/nexus/workspace.js.map +1 -0
- package/dist/orchestration/hierarchy.d.ts +32 -0
- package/dist/orchestration/hierarchy.d.ts.map +1 -0
- package/dist/orchestration/index.d.ts +1 -0
- package/dist/orchestration/index.d.ts.map +1 -1
- package/dist/output.d.ts +2 -2
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +40 -8
- package/dist/output.js.map +1 -1
- package/dist/pagination.d.ts +1 -1
- package/dist/pagination.d.ts.map +1 -1
- package/dist/sessions/find.d.ts +3 -0
- package/dist/sessions/find.d.ts.map +1 -1
- package/dist/sessions/find.js +3 -1
- package/dist/sessions/find.js.map +1 -1
- package/dist/sessions/index.d.ts.map +1 -1
- package/dist/sessions/index.js +11 -4
- package/dist/sessions/index.js.map +1 -1
- package/dist/sessions/snapshot.js +213 -0
- package/dist/sessions/snapshot.js.map +1 -0
- package/dist/store/agent-registry-accessor.d.ts +31 -0
- package/dist/store/agent-registry-accessor.d.ts.map +1 -0
- package/dist/store/agent-registry-accessor.js +169 -0
- package/dist/store/agent-registry-accessor.js.map +1 -0
- package/dist/store/converters.d.ts.map +1 -1
- package/dist/store/converters.js +2 -0
- package/dist/store/converters.js.map +1 -1
- package/dist/store/cross-db-cleanup.d.ts +34 -0
- package/dist/store/cross-db-cleanup.d.ts.map +1 -1
- package/dist/store/db-helpers.d.ts.map +1 -1
- package/dist/store/db-helpers.js +1 -0
- package/dist/store/db-helpers.js.map +1 -1
- package/dist/store/json.js +2 -2
- package/dist/store/safety-data-accessor.d.ts +7 -0
- package/dist/store/safety-data-accessor.d.ts.map +1 -1
- package/dist/store/safety-data-accessor.js +14 -0
- package/dist/store/safety-data-accessor.js.map +1 -1
- package/dist/store/signaldock-sqlite.d.ts +48 -0
- package/dist/store/signaldock-sqlite.d.ts.map +1 -0
- package/dist/store/signaldock-sqlite.js +178 -0
- package/dist/store/signaldock-sqlite.js.map +1 -0
- package/dist/store/sqlite-data-accessor.d.ts.map +1 -1
- package/dist/store/sqlite-data-accessor.js +50 -0
- package/dist/store/sqlite-data-accessor.js.map +1 -1
- package/dist/store/sqlite.d.ts.map +1 -1
- package/dist/store/sqlite.js +30 -1
- package/dist/store/sqlite.js.map +1 -1
- package/dist/store/task-store.d.ts.map +1 -1
- package/dist/store/task-store.js +2 -0
- package/dist/store/task-store.js.map +1 -1
- package/dist/store/tasks-schema.d.ts +16 -0
- package/dist/store/tasks-schema.d.ts.map +1 -1
- package/dist/store/tasks-schema.js +33 -0
- package/dist/store/tasks-schema.js.map +1 -1
- package/dist/store/validation-schemas.d.ts +32 -0
- package/dist/store/validation-schemas.d.ts.map +1 -1
- package/dist/system/health.d.ts +1 -1
- package/dist/system/health.d.ts.map +1 -1
- package/dist/system/health.js +35 -0
- package/dist/system/health.js.map +1 -1
- package/dist/task-work/index.d.ts.map +1 -1
- package/dist/task-work/index.js +8 -4
- package/dist/task-work/index.js.map +1 -1
- package/dist/tasks/complete.js +5 -2
- package/dist/tasks/complete.js.map +1 -1
- package/dist/tasks/find.d.ts +3 -0
- package/dist/tasks/find.d.ts.map +1 -1
- package/dist/tasks/find.js +7 -1
- package/dist/tasks/find.js.map +1 -1
- package/dist/tasks/list.d.ts +5 -2
- package/dist/tasks/list.d.ts.map +1 -1
- package/dist/tasks/list.js +9 -2
- package/dist/tasks/list.js.map +1 -1
- package/dist/tasks/show.d.ts +3 -0
- package/dist/tasks/show.d.ts.map +1 -1
- package/dist/tasks/show.js +2 -0
- package/dist/tasks/show.js.map +1 -1
- package/dist/upgrade.d.ts.map +1 -1
- package/dist/upgrade.js +15 -0
- package/dist/upgrade.js.map +1 -1
- package/migrations/drizzle-tasks/20260324000000_assignee-column/migration.sql +6 -0
- package/migrations/drizzle-tasks/20260324000000_assignee-column/snapshot.json +9 -0
- package/migrations/drizzle-tasks/20260327000000_agent-credentials/migration.sql +23 -0
- package/package.json +17 -7
- package/src/__tests__/cli-parity.test.js +11 -1
- package/src/__tests__/cli-parity.test.js.map +1 -1
- package/src/__tests__/cli-parity.test.ts +17 -1
- package/src/__tests__/human-output.test.js +11 -1
- package/src/__tests__/human-output.test.js.map +1 -1
- package/src/__tests__/human-output.test.ts +18 -1
- package/src/__tests__/injection-chain.test.js +3 -2
- package/src/__tests__/injection-chain.test.js.map +1 -1
- package/src/__tests__/injection-mvi-tiers.test.d.ts +2 -2
- package/src/__tests__/injection-mvi-tiers.test.js +15 -15
- package/src/__tests__/injection-mvi-tiers.test.js.map +1 -1
- package/src/__tests__/lafs-conformance.test.d.ts +1 -1
- package/src/__tests__/lafs-conformance.test.js +2 -2
- package/src/__tests__/sharing.test.js +19 -0
- package/src/__tests__/sharing.test.js.map +1 -1
- package/src/agents/__tests__/agent-registry.test.d.ts +12 -0
- package/src/agents/__tests__/agent-registry.test.d.ts.map +1 -0
- package/src/agents/__tests__/agent-registry.test.js +262 -0
- package/src/agents/__tests__/agent-registry.test.js.map +1 -0
- package/src/agents/__tests__/execution-learning.test.d.ts +14 -0
- package/src/agents/__tests__/execution-learning.test.d.ts.map +1 -0
- package/src/agents/__tests__/execution-learning.test.js +533 -0
- package/src/agents/__tests__/execution-learning.test.js.map +1 -0
- package/src/agents/__tests__/health-monitor.test.d.ts +10 -0
- package/src/agents/__tests__/health-monitor.test.d.ts.map +1 -0
- package/src/agents/__tests__/health-monitor.test.js +259 -0
- package/src/agents/__tests__/health-monitor.test.js.map +1 -0
- package/src/agents/__tests__/registry.test.js +27 -2
- package/src/agents/__tests__/registry.test.js.map +1 -1
- package/src/agents/agent-schema.ts +2 -5
- package/src/cant/__tests__/cant-agent-parse.test.ts +94 -0
- package/src/cant/approval.ts +218 -0
- package/src/cant/context-builder.ts +135 -0
- package/src/cant/discretion.ts +149 -0
- package/src/cant/index.ts +58 -0
- package/src/cant/parallel-runner.ts +205 -0
- package/src/cant/types.ts +158 -0
- package/src/cant/workflow-executor.ts +618 -0
- package/src/code/index.ts +10 -0
- package/src/code/outline.ts +214 -0
- package/src/code/parser.ts +299 -0
- package/src/code/search.ts +173 -0
- package/src/code/unfold.ts +204 -0
- package/src/conduit/__tests__/dual-api-e2e.test.ts +212 -0
- package/src/conduit/__tests__/local-credential-flow.test.ts +230 -0
- package/src/conduit/__tests__/local-transport.test.ts +320 -0
- package/src/conduit/__tests__/sse-transport.test.ts +344 -0
- package/src/conduit/conduit-client.ts +123 -0
- package/src/conduit/factory.ts +49 -0
- package/src/conduit/http-transport.ts +201 -0
- package/src/conduit/index.ts +15 -0
- package/src/conduit/local-transport.ts +309 -0
- package/src/conduit/sse-transport.ts +382 -0
- package/src/crypto/credentials.ts +166 -0
- package/src/engine-result.ts +1 -1
- package/src/error-catalog.ts +1 -1
- package/src/error-registry.ts +1 -1
- package/src/errors.ts +1 -1
- package/src/hooks/handlers/__tests__/hook-automation-e2e.test.d.ts +13 -0
- package/src/hooks/handlers/__tests__/hook-automation-e2e.test.d.ts.map +1 -0
- package/src/hooks/handlers/__tests__/hook-automation-e2e.test.js +501 -0
- package/src/hooks/handlers/__tests__/hook-automation-e2e.test.js.map +1 -0
- package/src/hooks/handlers/agent-hooks.ts +1 -30
- package/src/hooks/handlers/context-hooks.ts +1 -30
- package/src/hooks/handlers/error-hooks.ts +14 -5
- package/src/hooks/handlers/file-hooks.ts +1 -6
- package/src/hooks/handlers/handler-helpers.ts +62 -0
- package/src/hooks/handlers/mcp-hooks.ts +2 -14
- package/src/hooks/handlers/session-hooks.ts +1 -6
- package/src/hooks/handlers/task-hooks.ts +1 -6
- package/src/hooks/handlers/work-capture-hooks.ts +1 -10
- package/src/index.ts +12 -1
- package/src/init.ts +12 -0
- package/src/intelligence/__tests__/adaptive-validation.test.d.ts +11 -0
- package/src/intelligence/__tests__/adaptive-validation.test.d.ts.map +1 -0
- package/src/intelligence/__tests__/adaptive-validation.test.js +517 -0
- package/src/intelligence/__tests__/adaptive-validation.test.js.map +1 -0
- package/src/intelligence/__tests__/impact.test.d.ts +1 -0
- package/src/intelligence/__tests__/impact.test.d.ts.map +1 -1
- package/src/intelligence/__tests__/impact.test.js +132 -1
- package/src/intelligence/__tests__/impact.test.js.map +1 -1
- package/src/internal.ts +22 -0
- package/src/lib/__tests__/retry.test.d.ts +7 -0
- package/src/lib/__tests__/retry.test.d.ts.map +1 -0
- package/src/lib/__tests__/retry.test.js +225 -0
- package/src/lib/__tests__/retry.test.js.map +1 -0
- package/src/lib/index.ts +8 -0
- package/src/lib/tree-sitter-languages.ts +88 -0
- package/src/lifecycle/__tests__/chain-store.test.js +6 -0
- package/src/lifecycle/__tests__/chain-store.test.js.map +1 -1
- package/src/lifecycle/__tests__/tessera-engine.test.js +52 -0
- package/src/lifecycle/__tests__/tessera-engine.test.js.map +1 -1
- package/src/memory/__tests__/brain-automation.test.d.ts +11 -0
- package/src/memory/__tests__/brain-automation.test.d.ts.map +1 -0
- package/src/memory/__tests__/brain-automation.test.js +730 -0
- package/src/memory/__tests__/brain-automation.test.js.map +1 -0
- package/src/memory/__tests__/brain-links.test.ts +14 -0
- package/src/memory/__tests__/brain-retrieval.test.ts +10 -0
- package/src/memory/__tests__/session-memory.test.ts +17 -0
- package/src/memory/brain-links.ts +17 -0
- package/src/memory/brain-maintenance.ts +33 -1
- package/src/memory/brain-retrieval.ts +27 -2
- package/src/memory/decisions.ts +18 -2
- package/src/mvi-helpers.ts +81 -0
- package/src/nexus/workspace.ts +19 -7
- package/src/orchestration/hierarchy.ts +202 -0
- package/src/orchestration/index.ts +1 -0
- package/src/output.ts +43 -10
- package/src/pagination.ts +1 -1
- package/src/sessions/__tests__/session-edge-cases.test.js +20 -1
- package/src/sessions/__tests__/session-edge-cases.test.js.map +1 -1
- package/src/sessions/__tests__/session-find.test.js +1 -1
- package/src/sessions/__tests__/session-find.test.js.map +1 -1
- package/src/sessions/__tests__/session-find.test.ts +1 -1
- package/src/sessions/find.ts +6 -1
- package/src/sessions/index.ts +9 -0
- package/src/store/__tests__/migration-safety.test.js +3 -0
- package/src/store/__tests__/migration-safety.test.js.map +1 -1
- package/src/store/__tests__/session-store.test.js +128 -1
- package/src/store/__tests__/session-store.test.js.map +1 -1
- package/src/store/__tests__/task-store.test.js +18 -1
- package/src/store/__tests__/task-store.test.js.map +1 -1
- package/src/store/__tests__/test-db-helper.d.ts.map +1 -1
- package/src/store/__tests__/test-db-helper.js +12 -0
- package/src/store/__tests__/test-db-helper.js.map +1 -1
- package/src/store/agent-registry-accessor.ts +375 -0
- package/src/store/converters.ts +2 -0
- package/src/store/cross-db-cleanup.ts +175 -1
- package/src/store/db-helpers.ts +1 -0
- package/src/store/safety-data-accessor.ts +23 -0
- package/src/store/signaldock-sqlite.ts +429 -0
- package/src/store/sqlite-data-accessor.ts +72 -0
- package/src/store/sqlite.ts +4 -1
- package/src/store/task-store.ts +9 -1
- package/src/store/tasks-schema.ts +7 -0
- package/src/system/__tests__/health.test.ts +2 -2
- package/src/system/health.ts +54 -2
- package/src/task-work/index.ts +5 -0
- package/src/tasks/__tests__/add.test.js +19 -1
- package/src/tasks/__tests__/add.test.js.map +1 -1
- package/src/tasks/__tests__/assignee.test.d.ts +14 -0
- package/src/tasks/__tests__/assignee.test.d.ts.map +1 -0
- package/src/tasks/__tests__/assignee.test.js +125 -0
- package/src/tasks/__tests__/assignee.test.js.map +1 -0
- package/src/tasks/__tests__/assignee.test.ts +162 -0
- package/src/tasks/__tests__/complete-unblocks.test.js +13 -1
- package/src/tasks/__tests__/complete-unblocks.test.js.map +1 -1
- package/src/tasks/__tests__/complete.test.js +28 -7
- package/src/tasks/__tests__/complete.test.js.map +1 -1
- package/src/tasks/__tests__/epic-enforcement.test.d.ts +15 -0
- package/src/tasks/__tests__/epic-enforcement.test.d.ts.map +1 -0
- package/src/tasks/__tests__/epic-enforcement.test.js +669 -0
- package/src/tasks/__tests__/epic-enforcement.test.js.map +1 -0
- package/src/tasks/__tests__/hierarchy-policy.test.js +5 -0
- package/src/tasks/__tests__/hierarchy-policy.test.js.map +1 -1
- package/src/tasks/__tests__/minimal-test.test.d.ts +2 -0
- package/src/tasks/__tests__/minimal-test.test.d.ts.map +1 -0
- package/src/tasks/__tests__/minimal-test.test.js +25 -0
- package/src/tasks/__tests__/minimal-test.test.js.map +1 -0
- package/src/tasks/__tests__/pipeline-stage.test.d.ts +14 -0
- package/src/tasks/__tests__/pipeline-stage.test.d.ts.map +1 -0
- package/src/tasks/__tests__/pipeline-stage.test.js +277 -0
- package/src/tasks/__tests__/pipeline-stage.test.js.map +1 -0
- package/src/tasks/__tests__/update.test.js +43 -6
- package/src/tasks/__tests__/update.test.js.map +1 -1
- package/src/tasks/find.ts +11 -1
- package/src/tasks/list.ts +14 -3
- package/src/tasks/show.ts +6 -0
- package/src/upgrade.ts +16 -0
- package/dist/tasks/reparent.d.ts +0 -38
- package/dist/tasks/reparent.d.ts.map +0 -1
- package/dist/ui/injection-legacy.d.ts +0 -26
- package/dist/ui/injection-legacy.d.ts.map +0 -1
- package/dist/ui/injection-legacy.js +0 -42
- package/dist/ui/injection-legacy.js.map +0 -1
- package/src/signaldock/__tests__/claude-code-transport.test.d.ts +0 -7
- package/src/signaldock/__tests__/claude-code-transport.test.d.ts.map +0 -1
- package/src/signaldock/__tests__/claude-code-transport.test.js +0 -147
- package/src/signaldock/__tests__/claude-code-transport.test.js.map +0 -1
- package/src/signaldock/__tests__/claude-code-transport.test.ts +0 -180
- package/src/signaldock/__tests__/factory.test.d.ts +0 -7
- package/src/signaldock/__tests__/factory.test.d.ts.map +0 -1
- package/src/signaldock/__tests__/factory.test.js +0 -55
- package/src/signaldock/__tests__/factory.test.js.map +0 -1
- package/src/signaldock/__tests__/factory.test.ts +0 -61
- package/src/signaldock/__tests__/signaldock-transport.test.d.ts +0 -9
- package/src/signaldock/__tests__/signaldock-transport.test.d.ts.map +0 -1
- package/src/signaldock/__tests__/signaldock-transport.test.js +0 -321
- package/src/signaldock/__tests__/signaldock-transport.test.js.map +0 -1
- package/src/signaldock/__tests__/signaldock-transport.test.ts +0 -421
- package/src/signaldock/claude-code-transport.ts +0 -137
- package/src/signaldock/factory.ts +0 -39
- package/src/signaldock/index.ts +0 -28
- package/src/signaldock/signaldock-transport.ts +0 -194
- package/src/signaldock/transport.ts +0 -78
- package/src/signaldock/types.ts +0 -100
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConduitClient — High-level agent messaging that wraps a Transport adapter.
|
|
3
|
+
*
|
|
4
|
+
* This is the WHAT layer: send messages, subscribe to events, manage presence.
|
|
5
|
+
* The Transport adapter (HttpTransport, LocalTransport, etc.) handles the HOW.
|
|
6
|
+
*
|
|
7
|
+
* @see docs/specs/SIGNALDOCK-UNIFIED-AGENT-REGISTRY.md Section 4.3
|
|
8
|
+
* @task T177
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
AgentCredential,
|
|
13
|
+
Conduit,
|
|
14
|
+
ConduitMessage,
|
|
15
|
+
ConduitSendOptions,
|
|
16
|
+
ConduitSendResult,
|
|
17
|
+
ConduitState,
|
|
18
|
+
ConduitUnsubscribe,
|
|
19
|
+
Transport,
|
|
20
|
+
} from '@cleocode/contracts';
|
|
21
|
+
|
|
22
|
+
/** ConduitClient wraps a Transport, adding high-level messaging semantics. */
|
|
23
|
+
export class ConduitClient implements Conduit {
|
|
24
|
+
private transport: Transport;
|
|
25
|
+
private credential: AgentCredential;
|
|
26
|
+
private state: ConduitState = 'disconnected';
|
|
27
|
+
|
|
28
|
+
/** Create a ConduitClient backed by the given transport and credential. */
|
|
29
|
+
constructor(transport: Transport, credential: AgentCredential) {
|
|
30
|
+
this.transport = transport;
|
|
31
|
+
this.credential = credential;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The agent ID from the bound credential. */
|
|
35
|
+
get agentId(): string {
|
|
36
|
+
return this.credential.agentId;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Current connection state (disconnected → connecting → connected | error). */
|
|
40
|
+
getState(): ConduitState {
|
|
41
|
+
return this.state;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Connect the underlying transport using the bound credential. */
|
|
45
|
+
async connect(): Promise<void> {
|
|
46
|
+
this.state = 'connecting';
|
|
47
|
+
try {
|
|
48
|
+
await this.transport.connect({
|
|
49
|
+
agentId: this.credential.agentId,
|
|
50
|
+
apiKey: this.credential.apiKey,
|
|
51
|
+
apiBaseUrl: this.credential.apiBaseUrl,
|
|
52
|
+
...this.credential.transportConfig,
|
|
53
|
+
});
|
|
54
|
+
this.state = 'connected';
|
|
55
|
+
} catch (err) {
|
|
56
|
+
// H6 fix: transition to 'error' state instead of stuck at 'connecting'
|
|
57
|
+
this.state = 'error';
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Send a message to another agent, optionally within a thread. */
|
|
63
|
+
async send(
|
|
64
|
+
to: string,
|
|
65
|
+
content: string,
|
|
66
|
+
options?: ConduitSendOptions,
|
|
67
|
+
): Promise<ConduitSendResult> {
|
|
68
|
+
const result = await this.transport.push(to, content, {
|
|
69
|
+
conversationId: options?.threadId,
|
|
70
|
+
});
|
|
71
|
+
return {
|
|
72
|
+
messageId: result.messageId,
|
|
73
|
+
deliveredAt: new Date().toISOString(),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Subscribe to incoming messages. Uses real-time transport when available, else polls. */
|
|
78
|
+
onMessage(handler: (message: ConduitMessage) => void): ConduitUnsubscribe {
|
|
79
|
+
// Prefer real-time subscription if transport supports it
|
|
80
|
+
if (this.transport.subscribe) {
|
|
81
|
+
return this.transport.subscribe(handler);
|
|
82
|
+
}
|
|
83
|
+
// Fallback: polling loop
|
|
84
|
+
const interval = setInterval(async () => {
|
|
85
|
+
const messages = await this.transport.poll();
|
|
86
|
+
for (const msg of messages) handler(msg);
|
|
87
|
+
if (messages.length > 0) {
|
|
88
|
+
await this.transport.ack(messages.map((m) => m.id));
|
|
89
|
+
}
|
|
90
|
+
}, this.credential.transportConfig.pollIntervalMs ?? 5000);
|
|
91
|
+
return () => clearInterval(interval);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Send an empty heartbeat to maintain presence on the relay. */
|
|
95
|
+
async heartbeat(): Promise<void> {
|
|
96
|
+
// Send empty heartbeat via transport
|
|
97
|
+
await this.transport.push(this.credential.agentId, '', {});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Check whether a remote agent is currently online via the cloud API. */
|
|
101
|
+
async isOnline(agentId: string): Promise<boolean> {
|
|
102
|
+
// Delegate to cloud API check — stub for now
|
|
103
|
+
try {
|
|
104
|
+
const response = await fetch(`${this.credential.apiBaseUrl}/agents/${agentId}`, {
|
|
105
|
+
headers: {
|
|
106
|
+
Authorization: `Bearer ${this.credential.apiKey}`,
|
|
107
|
+
'X-Agent-Id': this.credential.agentId,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
if (!response.ok) return false;
|
|
111
|
+
const data = (await response.json()) as { data?: { agent?: { status?: string } } };
|
|
112
|
+
return data.data?.agent?.status === 'online';
|
|
113
|
+
} catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Disconnect the transport and reset state to disconnected. */
|
|
119
|
+
async disconnect(): Promise<void> {
|
|
120
|
+
await this.transport.disconnect();
|
|
121
|
+
this.state = 'disconnected';
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conduit factory — creates a Conduit instance from the agent registry.
|
|
3
|
+
*
|
|
4
|
+
* Auto-selects the appropriate Transport based on the agent's credential
|
|
5
|
+
* configuration. Priority: Local (napi-rs) > WebSocket > SSE > HTTP polling.
|
|
6
|
+
*
|
|
7
|
+
* @see docs/specs/SIGNALDOCK-UNIFIED-AGENT-REGISTRY.md Section 4.5
|
|
8
|
+
* @task T177
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { AgentCredential, AgentRegistryAPI, Conduit, Transport } from '@cleocode/contracts';
|
|
12
|
+
import { ConduitClient } from './conduit-client.js';
|
|
13
|
+
import { HttpTransport } from './http-transport.js';
|
|
14
|
+
import { LocalTransport } from './local-transport.js';
|
|
15
|
+
import { SseTransport } from './sse-transport.js';
|
|
16
|
+
|
|
17
|
+
/** Resolve the best available transport for a credential. */
|
|
18
|
+
export function resolveTransport(credential: AgentCredential): Transport {
|
|
19
|
+
// Priority: Local (SQLite) > WebSocket > SSE > HTTP polling
|
|
20
|
+
if (LocalTransport.isAvailable()) {
|
|
21
|
+
return new LocalTransport();
|
|
22
|
+
}
|
|
23
|
+
if (credential.transportConfig.wsUrl) {
|
|
24
|
+
// WsTransport — fall through to SSE/HTTP for now
|
|
25
|
+
}
|
|
26
|
+
if (credential.transportConfig.sseEndpoint) {
|
|
27
|
+
return new SseTransport();
|
|
28
|
+
}
|
|
29
|
+
return new HttpTransport();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Create a Conduit instance from the agent registry. */
|
|
33
|
+
export async function createConduit(
|
|
34
|
+
registry: AgentRegistryAPI,
|
|
35
|
+
agentId?: string,
|
|
36
|
+
): Promise<Conduit> {
|
|
37
|
+
const credential = agentId ? await registry.get(agentId) : await registry.getActive();
|
|
38
|
+
|
|
39
|
+
if (!credential) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
'No agent credential found. Run: cleo agent register --id <id> --api-key <key>',
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const transport = resolveTransport(credential);
|
|
46
|
+
const conduit = new ConduitClient(transport, credential);
|
|
47
|
+
await conduit.connect();
|
|
48
|
+
return conduit;
|
|
49
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HttpTransport — HTTP polling transport with automatic failover.
|
|
3
|
+
*
|
|
4
|
+
* Tries the primary API URL (api.signaldock.io) first. If unreachable,
|
|
5
|
+
* falls back to the legacy URL (api.clawmsgr.com). Failover is transparent
|
|
6
|
+
* to callers — they see a single transport that always works if either
|
|
7
|
+
* endpoint is up.
|
|
8
|
+
*
|
|
9
|
+
* @see docs/specs/SIGNALDOCK-UNIFIED-AGENT-REGISTRY.md Section 4.4
|
|
10
|
+
* @task T177
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { ConduitMessage, Transport, TransportConnectConfig } from '@cleocode/contracts';
|
|
14
|
+
|
|
15
|
+
/** Internal connection state. */
|
|
16
|
+
interface HttpTransportState {
|
|
17
|
+
agentId: string;
|
|
18
|
+
apiKey: string;
|
|
19
|
+
primaryUrl: string;
|
|
20
|
+
fallbackUrl: string | null;
|
|
21
|
+
activeUrl: string;
|
|
22
|
+
connected: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** HTTP transport with automatic primary/fallback failover. */
|
|
26
|
+
export class HttpTransport implements Transport {
|
|
27
|
+
readonly name = 'http';
|
|
28
|
+
private state: HttpTransportState | null = null;
|
|
29
|
+
|
|
30
|
+
/** Connect to the SignalDock API, probing primary/fallback health when both are configured. */
|
|
31
|
+
async connect(config: TransportConnectConfig): Promise<void> {
|
|
32
|
+
const primaryUrl = config.apiBaseUrl;
|
|
33
|
+
const fallbackUrl = config.apiBaseUrlFallback ?? null;
|
|
34
|
+
|
|
35
|
+
// Only probe health when there's a fallback to choose between
|
|
36
|
+
let activeUrl = primaryUrl;
|
|
37
|
+
if (fallbackUrl) {
|
|
38
|
+
const [primaryResult, fallbackResult] = await Promise.allSettled([
|
|
39
|
+
fetch(`${primaryUrl}/health`, { method: 'GET', signal: AbortSignal.timeout(5000) }),
|
|
40
|
+
fetch(`${fallbackUrl}/health`, { method: 'GET', signal: AbortSignal.timeout(5000) }),
|
|
41
|
+
]);
|
|
42
|
+
const primaryOk = primaryResult.status === 'fulfilled' && primaryResult.value.ok;
|
|
43
|
+
const fallbackOk = fallbackResult.status === 'fulfilled' && fallbackResult.value.ok;
|
|
44
|
+
if (!primaryOk && fallbackOk) {
|
|
45
|
+
activeUrl = fallbackUrl;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
this.state = {
|
|
50
|
+
agentId: config.agentId,
|
|
51
|
+
apiKey: config.apiKey,
|
|
52
|
+
primaryUrl,
|
|
53
|
+
fallbackUrl,
|
|
54
|
+
activeUrl,
|
|
55
|
+
connected: true,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Disconnect and clear connection state. */
|
|
60
|
+
async disconnect(): Promise<void> {
|
|
61
|
+
this.state = null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Send a message to an agent (direct or within a conversation thread). */
|
|
65
|
+
async push(
|
|
66
|
+
to: string,
|
|
67
|
+
content: string,
|
|
68
|
+
options?: { conversationId?: string; replyTo?: string },
|
|
69
|
+
): Promise<{ messageId: string }> {
|
|
70
|
+
this.ensureConnected();
|
|
71
|
+
|
|
72
|
+
const body: Record<string, string> = { content };
|
|
73
|
+
|
|
74
|
+
let path: string;
|
|
75
|
+
if (options?.conversationId) {
|
|
76
|
+
path = `/conversations/${options.conversationId}/messages`;
|
|
77
|
+
if (options.replyTo) {
|
|
78
|
+
body['replyTo'] = options.replyTo;
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
path = '/messages';
|
|
82
|
+
body['toAgentId'] = to;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const response = await this.fetchWithFallback(path, {
|
|
86
|
+
method: 'POST',
|
|
87
|
+
headers: this.headers(),
|
|
88
|
+
body: JSON.stringify(body),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
const text = await response.text().catch(() => '');
|
|
93
|
+
throw new Error(`HttpTransport push failed: ${response.status} ${text}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const data = (await response.json()) as {
|
|
97
|
+
success?: boolean;
|
|
98
|
+
data?: { message?: { id?: string }; id?: string };
|
|
99
|
+
};
|
|
100
|
+
const messageId = data.data?.message?.id ?? data.data?.id ?? 'unknown';
|
|
101
|
+
return { messageId };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Poll for new messages mentioning this agent. Returns empty array on HTTP error. */
|
|
105
|
+
async poll(options?: { limit?: number; since?: string }): Promise<ConduitMessage[]> {
|
|
106
|
+
this.ensureConnected();
|
|
107
|
+
|
|
108
|
+
const params = new URLSearchParams();
|
|
109
|
+
params.set('mentioned', this.state!.agentId);
|
|
110
|
+
if (options?.limit) params.set('limit', String(options.limit));
|
|
111
|
+
if (options?.since) params.set('since', options.since);
|
|
112
|
+
|
|
113
|
+
const response = await this.fetchWithFallback(`/messages/peek?${params}`, {
|
|
114
|
+
method: 'GET',
|
|
115
|
+
headers: this.headers(),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (!response.ok) return [];
|
|
119
|
+
|
|
120
|
+
const data = (await response.json()) as {
|
|
121
|
+
data?: {
|
|
122
|
+
messages?: Array<{
|
|
123
|
+
id: string;
|
|
124
|
+
fromAgentId?: string;
|
|
125
|
+
content?: string;
|
|
126
|
+
conversationId?: string;
|
|
127
|
+
createdAt?: string;
|
|
128
|
+
}>;
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
return (data.data?.messages ?? []).map((m) => ({
|
|
133
|
+
id: m.id,
|
|
134
|
+
from: m.fromAgentId ?? 'unknown',
|
|
135
|
+
content: m.content ?? '',
|
|
136
|
+
threadId: m.conversationId,
|
|
137
|
+
timestamp: m.createdAt ?? new Date().toISOString(),
|
|
138
|
+
}));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Acknowledge messages by ID so they are not returned by future polls. */
|
|
142
|
+
async ack(messageIds: string[]): Promise<void> {
|
|
143
|
+
this.ensureConnected();
|
|
144
|
+
|
|
145
|
+
await this.fetchWithFallback('/messages/ack', {
|
|
146
|
+
method: 'POST',
|
|
147
|
+
headers: this.headers(),
|
|
148
|
+
body: JSON.stringify({ messageIds }),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Fetch with automatic failover. Tries activeUrl first.
|
|
154
|
+
* If it fails and a fallback exists, retries on the other URL
|
|
155
|
+
* and swaps activeUrl for subsequent calls.
|
|
156
|
+
*/
|
|
157
|
+
private async fetchWithFallback(path: string, init: RequestInit): Promise<Response> {
|
|
158
|
+
const timeout = AbortSignal.timeout(10000);
|
|
159
|
+
const signal = init.signal ? AbortSignal.any([init.signal, timeout]) : timeout;
|
|
160
|
+
const url = `${this.state!.activeUrl}${path}`;
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
return await fetch(url, { ...init, signal });
|
|
164
|
+
} catch (primaryErr) {
|
|
165
|
+
const otherUrl =
|
|
166
|
+
this.state!.activeUrl === this.state!.primaryUrl
|
|
167
|
+
? this.state!.fallbackUrl
|
|
168
|
+
: this.state!.primaryUrl;
|
|
169
|
+
|
|
170
|
+
if (!otherUrl) throw primaryErr;
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const fallbackSignal = init.signal
|
|
174
|
+
? AbortSignal.any([init.signal, AbortSignal.timeout(10000)])
|
|
175
|
+
: AbortSignal.timeout(10000);
|
|
176
|
+
const fallbackResponse = await fetch(`${otherUrl}${path}`, {
|
|
177
|
+
...init,
|
|
178
|
+
signal: fallbackSignal,
|
|
179
|
+
});
|
|
180
|
+
this.state!.activeUrl = otherUrl;
|
|
181
|
+
return fallbackResponse;
|
|
182
|
+
} catch {
|
|
183
|
+
throw primaryErr;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private headers(): Record<string, string> {
|
|
189
|
+
return {
|
|
190
|
+
'Content-Type': 'application/json',
|
|
191
|
+
Authorization: `Bearer ${this.state!.apiKey}`,
|
|
192
|
+
'X-Agent-Id': this.state!.agentId,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private ensureConnected(): void {
|
|
197
|
+
if (!this.state?.connected) {
|
|
198
|
+
throw new Error('HttpTransport not connected. Call connect() first.');
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conduit — High-level agent messaging for the CLEO ecosystem.
|
|
3
|
+
*
|
|
4
|
+
* Exports the ConduitClient (high-level messaging), HttpTransport
|
|
5
|
+
* (HTTP polling to cloud), LocalTransport (offline SQLite), and
|
|
6
|
+
* createConduit factory.
|
|
7
|
+
*
|
|
8
|
+
* @module conduit
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export { ConduitClient } from './conduit-client.js';
|
|
12
|
+
export { createConduit, resolveTransport } from './factory.js';
|
|
13
|
+
export { HttpTransport } from './http-transport.js';
|
|
14
|
+
export { LocalTransport } from './local-transport.js';
|
|
15
|
+
export { SseTransport } from './sse-transport.js';
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LocalTransport — In-process SQLite transport for fully offline agent messaging.
|
|
3
|
+
*
|
|
4
|
+
* Reads and writes messages directly to signaldock.db via node:sqlite.
|
|
5
|
+
* No network calls. Works fully offline. Messages are stored in the
|
|
6
|
+
* same schema that the Rust signaldock-storage crate manages, so both
|
|
7
|
+
* the local CLI and the cloud backend see the same data.
|
|
8
|
+
*
|
|
9
|
+
* Priority: LocalTransport is preferred over HttpTransport when
|
|
10
|
+
* signaldock.db is available (see factory.ts).
|
|
11
|
+
*
|
|
12
|
+
* @see docs/specs/SIGNALDOCK-UNIFIED-AGENT-REGISTRY.md Section 4.4
|
|
13
|
+
* @task T213
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { randomUUID } from 'node:crypto';
|
|
17
|
+
import { existsSync } from 'node:fs';
|
|
18
|
+
import { createRequire } from 'node:module';
|
|
19
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
20
|
+
import type { ConduitMessage, Transport, TransportConnectConfig } from '@cleocode/contracts';
|
|
21
|
+
import { getSignaldockDbPath } from '../store/signaldock-sqlite.js';
|
|
22
|
+
|
|
23
|
+
const _require = createRequire(import.meta.url);
|
|
24
|
+
const { DatabaseSync: DatabaseSyncClass } = _require('node:sqlite') as {
|
|
25
|
+
DatabaseSync: new (...args: ConstructorParameters<typeof DatabaseSync>) => DatabaseSync;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** Internal state for an active local transport connection. */
|
|
29
|
+
interface LocalTransportState {
|
|
30
|
+
agentId: string;
|
|
31
|
+
db: DatabaseSync;
|
|
32
|
+
dbPath: string;
|
|
33
|
+
subscribers: Set<(message: ConduitMessage) => void>;
|
|
34
|
+
pollTimer: ReturnType<typeof setInterval> | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** In-process SQLite transport for fully offline agent messaging. */
|
|
38
|
+
export class LocalTransport implements Transport {
|
|
39
|
+
readonly name = 'local';
|
|
40
|
+
private state: LocalTransportState | null = null;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Connect to signaldock.db for in-process messaging.
|
|
44
|
+
*
|
|
45
|
+
* Opens the database, sets WAL mode pragmas, and verifies
|
|
46
|
+
* the messages table exists. Throws if signaldock.db is missing
|
|
47
|
+
* or uninitialized (run `cleo init` first).
|
|
48
|
+
*/
|
|
49
|
+
async connect(config: TransportConnectConfig): Promise<void> {
|
|
50
|
+
const dbPath = getSignaldockDbPath();
|
|
51
|
+
|
|
52
|
+
if (!existsSync(dbPath)) {
|
|
53
|
+
throw new Error(`LocalTransport: signaldock.db not found at ${dbPath}. Run: cleo init`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const db = new DatabaseSyncClass(dbPath);
|
|
57
|
+
db.exec('PRAGMA journal_mode = WAL');
|
|
58
|
+
db.exec('PRAGMA busy_timeout = 5000');
|
|
59
|
+
db.exec('PRAGMA foreign_keys = ON');
|
|
60
|
+
|
|
61
|
+
// Verify the messages table exists
|
|
62
|
+
const hasMessages = db
|
|
63
|
+
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='messages'")
|
|
64
|
+
.get() as { name: string } | undefined;
|
|
65
|
+
|
|
66
|
+
if (!hasMessages) {
|
|
67
|
+
db.close();
|
|
68
|
+
throw new Error(
|
|
69
|
+
'LocalTransport: signaldock.db exists but messages table missing. Run: cleo upgrade',
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this.state = {
|
|
74
|
+
agentId: config.agentId,
|
|
75
|
+
db,
|
|
76
|
+
dbPath,
|
|
77
|
+
subscribers: new Set(),
|
|
78
|
+
pollTimer: null,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Close the database connection and stop any subscriber polling. */
|
|
83
|
+
async disconnect(): Promise<void> {
|
|
84
|
+
if (!this.state) return;
|
|
85
|
+
|
|
86
|
+
if (this.state.pollTimer) {
|
|
87
|
+
clearInterval(this.state.pollTimer);
|
|
88
|
+
}
|
|
89
|
+
this.state.subscribers.clear();
|
|
90
|
+
this.state.db.close();
|
|
91
|
+
this.state = null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Store a message in signaldock.db.
|
|
96
|
+
*
|
|
97
|
+
* Inserts into the messages table with status 'pending'.
|
|
98
|
+
* For conversation messages, also links via conversation_participants
|
|
99
|
+
* if not already present.
|
|
100
|
+
*/
|
|
101
|
+
async push(
|
|
102
|
+
to: string,
|
|
103
|
+
content: string,
|
|
104
|
+
options?: { conversationId?: string; replyTo?: string },
|
|
105
|
+
): Promise<{ messageId: string }> {
|
|
106
|
+
this.ensureConnected();
|
|
107
|
+
const { db, agentId } = this.state!;
|
|
108
|
+
const messageId = randomUUID();
|
|
109
|
+
const nowUnix = Math.floor(Date.now() / 1000);
|
|
110
|
+
|
|
111
|
+
if (options?.conversationId) {
|
|
112
|
+
db.prepare(
|
|
113
|
+
`INSERT INTO messages (id, conversation_id, from_agent_id, to_agent_id, content, content_type, status, created_at)
|
|
114
|
+
VALUES (?, ?, ?, ?, ?, 'text', 'pending', ?)`,
|
|
115
|
+
).run(messageId, options.conversationId, agentId, to, content, nowUnix);
|
|
116
|
+
} else {
|
|
117
|
+
// Direct message — create or reuse a DM conversation
|
|
118
|
+
const convId = this.ensureDmConversation(agentId, to);
|
|
119
|
+
db.prepare(
|
|
120
|
+
`INSERT INTO messages (id, conversation_id, from_agent_id, to_agent_id, content, content_type, status, created_at)
|
|
121
|
+
VALUES (?, ?, ?, ?, ?, 'text', 'pending', ?)`,
|
|
122
|
+
).run(messageId, convId, agentId, to, content, nowUnix);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Notify local subscribers
|
|
126
|
+
this.notifySubscribers({
|
|
127
|
+
id: messageId,
|
|
128
|
+
from: agentId,
|
|
129
|
+
content,
|
|
130
|
+
threadId: options?.conversationId,
|
|
131
|
+
timestamp: new Date(nowUnix * 1000).toISOString(),
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return { messageId };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Poll for messages addressed to this agent.
|
|
139
|
+
*
|
|
140
|
+
* Returns messages with status 'pending' where to_agent_id matches
|
|
141
|
+
* the connected agent. Messages are returned oldest-first.
|
|
142
|
+
*/
|
|
143
|
+
async poll(options?: { limit?: number; since?: string }): Promise<ConduitMessage[]> {
|
|
144
|
+
this.ensureConnected();
|
|
145
|
+
const { db, agentId } = this.state!;
|
|
146
|
+
const limit = options?.limit ?? 50;
|
|
147
|
+
|
|
148
|
+
let query: string;
|
|
149
|
+
let params: (string | number)[];
|
|
150
|
+
|
|
151
|
+
if (options?.since) {
|
|
152
|
+
query = `SELECT id, from_agent_id, content, conversation_id, created_at
|
|
153
|
+
FROM messages
|
|
154
|
+
WHERE to_agent_id = ? AND status = 'pending' AND created_at > ?
|
|
155
|
+
ORDER BY created_at ASC
|
|
156
|
+
LIMIT ?`;
|
|
157
|
+
params = [agentId, options.since, limit];
|
|
158
|
+
} else {
|
|
159
|
+
query = `SELECT id, from_agent_id, content, conversation_id, created_at
|
|
160
|
+
FROM messages
|
|
161
|
+
WHERE to_agent_id = ? AND status = 'pending'
|
|
162
|
+
ORDER BY created_at ASC
|
|
163
|
+
LIMIT ?`;
|
|
164
|
+
params = [agentId, limit];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const rows = db.prepare(query).all(...params) as Array<{
|
|
168
|
+
id: string;
|
|
169
|
+
from_agent_id: string;
|
|
170
|
+
content: string;
|
|
171
|
+
conversation_id: string | null;
|
|
172
|
+
created_at: number;
|
|
173
|
+
}>;
|
|
174
|
+
|
|
175
|
+
return rows.map((r) => ({
|
|
176
|
+
id: r.id,
|
|
177
|
+
from: r.from_agent_id,
|
|
178
|
+
content: r.content,
|
|
179
|
+
threadId: r.conversation_id ?? undefined,
|
|
180
|
+
timestamp: new Date(r.created_at * 1000).toISOString(),
|
|
181
|
+
}));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Acknowledge messages by marking them as 'delivered'.
|
|
186
|
+
*
|
|
187
|
+
* Updates the status and delivered_at timestamp for each message ID.
|
|
188
|
+
*/
|
|
189
|
+
async ack(messageIds: string[]): Promise<void> {
|
|
190
|
+
this.ensureConnected();
|
|
191
|
+
if (messageIds.length === 0) return;
|
|
192
|
+
|
|
193
|
+
const { db } = this.state!;
|
|
194
|
+
const nowUnix = Math.floor(Date.now() / 1000);
|
|
195
|
+
|
|
196
|
+
const placeholders = messageIds.map(() => '?').join(', ');
|
|
197
|
+
db.prepare(
|
|
198
|
+
`UPDATE messages SET status = 'delivered', delivered_at = ? WHERE id IN (${placeholders})`,
|
|
199
|
+
).run(nowUnix, ...messageIds);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Subscribe to real-time local messages.
|
|
204
|
+
*
|
|
205
|
+
* Since this is in-process, subscribers are notified synchronously
|
|
206
|
+
* when push() is called. Additionally, a polling interval checks
|
|
207
|
+
* for messages inserted by other processes (e.g., Rust CLI).
|
|
208
|
+
*
|
|
209
|
+
* @returns Unsubscribe function.
|
|
210
|
+
*/
|
|
211
|
+
subscribe(handler: (message: ConduitMessage) => void): () => void {
|
|
212
|
+
this.ensureConnected();
|
|
213
|
+
this.state!.subscribers.add(handler);
|
|
214
|
+
|
|
215
|
+
// Start cross-process polling if not already running
|
|
216
|
+
if (!this.state!.pollTimer && this.state!.subscribers.size === 1) {
|
|
217
|
+
this.state!.pollTimer = setInterval(() => {
|
|
218
|
+
void this.pollAndNotify();
|
|
219
|
+
}, 1000);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return () => {
|
|
223
|
+
this.state?.subscribers.delete(handler);
|
|
224
|
+
if (this.state?.subscribers.size === 0 && this.state.pollTimer) {
|
|
225
|
+
clearInterval(this.state.pollTimer);
|
|
226
|
+
this.state.pollTimer = null;
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Check whether signaldock.db is available for local transport.
|
|
233
|
+
*
|
|
234
|
+
* Used by factory.ts to decide whether to use LocalTransport.
|
|
235
|
+
*/
|
|
236
|
+
static isAvailable(cwd?: string): boolean {
|
|
237
|
+
const dbPath = getSignaldockDbPath(cwd);
|
|
238
|
+
return existsSync(dbPath);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Poll for new messages and notify subscribers (cross-process sync). */
|
|
242
|
+
private async pollAndNotify(): Promise<void> {
|
|
243
|
+
if (!this.state || this.state.subscribers.size === 0) return;
|
|
244
|
+
|
|
245
|
+
const messages = await this.poll({ limit: 20 });
|
|
246
|
+
for (const msg of messages) {
|
|
247
|
+
this.notifySubscribers(msg);
|
|
248
|
+
}
|
|
249
|
+
if (messages.length > 0) {
|
|
250
|
+
await this.ack(messages.map((m) => m.id));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** Notify all active subscribers of a new message. */
|
|
255
|
+
private notifySubscribers(message: ConduitMessage): void {
|
|
256
|
+
if (!this.state) return;
|
|
257
|
+
for (const handler of this.state.subscribers) {
|
|
258
|
+
try {
|
|
259
|
+
handler(message);
|
|
260
|
+
} catch {
|
|
261
|
+
// Subscriber errors must not break the transport
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Ensure a DM conversation exists between two agents.
|
|
268
|
+
*
|
|
269
|
+
* Conversations store participants as a comma-separated TEXT field.
|
|
270
|
+
* We search for existing private conversations containing both agents.
|
|
271
|
+
*
|
|
272
|
+
* @returns The conversation ID.
|
|
273
|
+
*/
|
|
274
|
+
private ensureDmConversation(fromAgentId: string, toAgentId: string): string {
|
|
275
|
+
const { db } = this.state!;
|
|
276
|
+
|
|
277
|
+
// Participants are stored as comma-separated text, sorted alphabetically
|
|
278
|
+
const sortedParticipants = [fromAgentId, toAgentId].sort().join(',');
|
|
279
|
+
|
|
280
|
+
// Check for existing DM conversation with these exact participants
|
|
281
|
+
const existing = db
|
|
282
|
+
.prepare(
|
|
283
|
+
`SELECT id FROM conversations
|
|
284
|
+
WHERE visibility = 'private' AND participants = ?
|
|
285
|
+
LIMIT 1`,
|
|
286
|
+
)
|
|
287
|
+
.get(sortedParticipants) as { id: string } | undefined;
|
|
288
|
+
|
|
289
|
+
if (existing) return existing.id;
|
|
290
|
+
|
|
291
|
+
// Create new DM conversation
|
|
292
|
+
const convId = randomUUID();
|
|
293
|
+
const nowUnix = Math.floor(Date.now() / 1000);
|
|
294
|
+
|
|
295
|
+
db.prepare(
|
|
296
|
+
`INSERT INTO conversations (id, participants, visibility, message_count, created_at, updated_at)
|
|
297
|
+
VALUES (?, ?, 'private', 0, ?, ?)`,
|
|
298
|
+
).run(convId, sortedParticipants, nowUnix, nowUnix);
|
|
299
|
+
|
|
300
|
+
return convId;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** Throw if not connected. */
|
|
304
|
+
private ensureConnected(): void {
|
|
305
|
+
if (!this.state) {
|
|
306
|
+
throw new Error('LocalTransport not connected. Call connect() first.');
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|