@adhdev/daemon-core 0.9.82-rc.48 → 0.9.82-rc.481
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/dist/boot/daemon-lifecycle.d.ts +9 -0
- package/dist/boot/process-hardening.d.ts +50 -0
- package/dist/build-info.d.ts +37 -0
- package/dist/chat/source-machine.d.ts +166 -0
- package/dist/chat/source-resolver.d.ts +104 -0
- package/dist/chat/subscription-updates.d.ts +1 -0
- package/dist/cli-adapter-types.d.ts +68 -2
- package/dist/cli-adapters/cli-script-runner.d.ts +117 -0
- package/dist/cli-adapters/cli-state-engine.d.ts +220 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +126 -64
- package/dist/cli-adapters/provider-cli-parse.d.ts +5 -0
- package/dist/cli-adapters/provider-cli-runtime.d.ts +34 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +74 -0
- package/dist/cli-adapters/pty-write-chunking.d.ts +34 -0
- package/dist/cli-adapters/raw-terminal-io.d.ts +37 -0
- package/dist/cli-adapters/resolve-executable.d.ts +69 -0
- package/dist/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +6 -3
- package/dist/cli-adapters/terminal-backends/types.d.ts +10 -2
- package/dist/cli-adapters/terminal-screen.d.ts +4 -11
- package/dist/commands/chat-commands-debug-bundle.d.ts +14 -0
- package/dist/commands/chat-commands-read.d.ts +23 -0
- package/dist/commands/chat-commands-scope.d.ts +39 -0
- package/dist/commands/chat-commands-shared.d.ts +33 -0
- package/dist/commands/chat-commands-write.d.ts +14 -0
- package/dist/commands/chat-commands.d.ts +10 -23
- package/dist/commands/cli-manager.d.ts +51 -1
- package/dist/commands/handler.d.ts +125 -0
- package/dist/commands/high-family/index.d.ts +3 -0
- package/dist/commands/high-family/mesh-coordinator-launch.d.ts +2 -0
- package/dist/commands/high-family/mesh-events.d.ts +2 -0
- package/dist/commands/high-family/mesh-status.d.ts +2 -0
- package/dist/commands/high-family/types.d.ts +64 -0
- package/dist/commands/low-family/coordinator-prompt.d.ts +9 -0
- package/dist/commands/low-family/daemon-lifecycle.d.ts +2 -0
- package/dist/commands/low-family/diagnostics.d.ts +2 -0
- package/dist/commands/low-family/index.d.ts +3 -0
- package/dist/commands/low-family/mesh-ledger.d.ts +10 -0
- package/dist/commands/low-family/mesh-node-logs.d.ts +2 -0
- package/dist/commands/low-family/notification.d.ts +2 -0
- package/dist/commands/low-family/refine-config.d.ts +2 -0
- package/dist/commands/low-family/session-host.d.ts +2 -0
- package/dist/commands/low-family/spec-providerdev.d.ts +11 -0
- package/dist/commands/low-family/status-meta.d.ts +2 -0
- package/dist/commands/low-family/types.d.ts +33 -0
- package/dist/commands/med-family/cli-agent.d.ts +2 -0
- package/dist/commands/med-family/fast-forward.d.ts +2 -0
- package/dist/commands/med-family/ide.d.ts +10 -0
- package/dist/commands/med-family/index.d.ts +3 -0
- package/dist/commands/med-family/mesh-crud.d.ts +48 -0
- package/dist/commands/med-family/mesh-host-pairing.d.ts +2 -0
- package/dist/commands/med-family/mesh-queue.d.ts +2 -0
- package/dist/commands/med-family/mesh-restart.d.ts +2 -0
- package/dist/commands/med-family/types.d.ts +152 -0
- package/dist/commands/mesh-coordinator.d.ts +85 -1
- package/dist/commands/router-aggregate-status.d.ts +24 -0
- package/dist/commands/router-mesh-session-owner.d.ts +59 -0
- package/dist/commands/router-refine.d.ts +137 -0
- package/dist/commands/router-worktree-cleanup.d.ts +133 -0
- package/dist/commands/router.d.ts +204 -15
- package/dist/commands/upgrade-helper.d.ts +41 -1
- package/dist/config/chat-history.d.ts +11 -0
- package/dist/config/config.d.ts +19 -0
- package/dist/config/mesh-config.d.ts +136 -1
- package/dist/config/mesh-json-config.d.ts +199 -0
- package/dist/config/registry-resolver.d.ts +54 -0
- package/dist/config/repo-settings.d.ts +77 -0
- package/dist/config/state-store.d.ts +30 -0
- package/dist/daemon/dev-server.d.ts +0 -2
- package/dist/detection/cli-detector.d.ts +17 -0
- package/dist/detection/ide-detector.d.ts +13 -0
- package/dist/detection/win32-ide-version.d.ts +37 -0
- package/dist/git/change-impact-config.d.ts +159 -0
- package/dist/git/git-commands.d.ts +26 -2
- package/dist/git/git-diff.d.ts +6 -0
- package/dist/git/git-executor.d.ts +11 -0
- package/dist/git/git-status.d.ts +57 -0
- package/dist/git/git-types.d.ts +2 -50
- package/dist/git/git-worktree.d.ts +71 -1
- package/dist/git/index.d.ts +3 -1
- package/dist/index.d.ts +73 -17
- package/dist/index.js +52979 -18657
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +59496 -25355
- package/dist/index.mjs.map +1 -1
- package/dist/installer.d.ts +1 -4
- package/dist/ipc/local-ipc-server.d.ts +91 -0
- package/dist/launch.d.ts +1 -1
- package/dist/logging/async-batch-writer.d.ts +10 -0
- package/dist/logging/debug-config.d.ts +16 -0
- package/dist/logging/log-redactor.d.ts +24 -0
- package/dist/logging/log-tail-reader.d.ts +81 -0
- package/dist/logging/logger.d.ts +1 -1
- package/dist/mesh/contracts.d.ts +230 -0
- package/dist/mesh/coordinator-prompt.d.ts +76 -0
- package/dist/mesh/coordinator-registry.d.ts +59 -0
- package/dist/mesh/mesh-active-work.d.ts +203 -0
- package/dist/mesh/mesh-clone-grace.d.ts +8 -0
- package/dist/mesh/mesh-completion-synthesis.d.ts +4 -0
- package/dist/mesh/mesh-coordinator-config.d.ts +21 -0
- package/dist/mesh/mesh-delivery-policy.d.ts +104 -0
- package/dist/mesh/mesh-event-classify.d.ts +7 -0
- package/dist/mesh/mesh-event-forwarding.d.ts +49 -0
- package/dist/mesh/mesh-event-trace.d.ts +21 -0
- package/dist/mesh/mesh-events-coordinator.d.ts +4 -0
- package/dist/mesh/mesh-events-pending.d.ts +246 -0
- package/dist/mesh/mesh-events-stale.d.ts +61 -0
- package/dist/mesh/mesh-events-utils.d.ts +75 -0
- package/dist/mesh/mesh-events.d.ts +6 -49
- package/dist/mesh/mesh-fast-forward.d.ts +81 -0
- package/dist/mesh/mesh-host-ownership.d.ts +29 -0
- package/dist/mesh/mesh-init.d.ts +128 -0
- package/dist/mesh/mesh-ledger-reconciliation.d.ts +23 -4
- package/dist/mesh/mesh-ledger.d.ts +139 -1
- package/dist/mesh/mesh-magi-status.d.ts +63 -0
- package/dist/mesh/mesh-missions.d.ts +281 -0
- package/dist/mesh/mesh-node-identity.d.ts +310 -0
- package/dist/mesh/mesh-queue-assignment.d.ts +158 -0
- package/dist/mesh/mesh-reconcile-acked-hold.d.ts +17 -0
- package/dist/mesh/mesh-reconcile-config.d.ts +6 -0
- package/dist/mesh/mesh-reconcile-identity.d.ts +6 -0
- package/dist/mesh/mesh-reconcile-loop.d.ts +80 -0
- package/dist/mesh/mesh-reconcile-v2-backstop.d.ts +16 -0
- package/dist/mesh/mesh-refine-batch.d.ts +68 -0
- package/dist/mesh/mesh-refine-gates.d.ts +428 -0
- package/dist/mesh/mesh-refine-status.d.ts +62 -0
- package/dist/mesh/mesh-remote-event-pull.d.ts +15 -0
- package/dist/mesh/mesh-review-inbox.d.ts +90 -0
- package/dist/mesh/mesh-routing.d.ts +70 -0
- package/dist/mesh/mesh-runtime-store.d.ts +508 -0
- package/dist/mesh/mesh-scheduling-runtime.d.ts +78 -0
- package/dist/mesh/mesh-task-inflight.d.ts +46 -0
- package/dist/mesh/mesh-task-stats.d.ts +49 -0
- package/dist/mesh/mesh-unresolved-forward-outbox.d.ts +30 -0
- package/dist/mesh/mesh-warmup-deadline.d.ts +68 -0
- package/dist/mesh/mesh-work-queue.d.ts +363 -5
- package/dist/mesh/preview-freshness.d.ts +28 -0
- package/dist/mesh/refine-config.d.ts +216 -0
- package/dist/mesh/worktree-bootstrap-config.d.ts +219 -0
- package/dist/providers/acp-provider-instance.d.ts +5 -0
- package/dist/providers/approval-utils.d.ts +46 -0
- package/dist/providers/chat-message-normalization.d.ts +58 -1
- package/dist/providers/cli-provider-effect-format.d.ts +30 -0
- package/dist/providers/cli-provider-history-dedup.d.ts +17 -0
- package/dist/providers/cli-provider-input-prompt.d.ts +12 -0
- package/dist/providers/cli-provider-instance-types.d.ts +45 -0
- package/dist/providers/cli-provider-instance.d.ts +406 -30
- package/dist/providers/cli-provider-status-helpers.d.ts +46 -0
- package/dist/providers/cli-provider-transcript-merge.d.ts +7 -0
- package/dist/providers/contracts.d.ts +149 -6
- package/dist/providers/external-sources.d.ts +71 -0
- package/dist/providers/manual-attendance.d.ts +79 -0
- package/dist/providers/native-history/antigravity-claim-registry.d.ts +39 -0
- package/dist/providers/native-history/antigravity-cli-transcript.d.ts +137 -0
- package/dist/providers/native-history/claude-cli-transcript.d.ts +70 -0
- package/dist/providers/native-history/codex-cli-transcript.d.ts +73 -0
- package/dist/providers/native-history/constants.d.ts +12 -0
- package/dist/providers/native-history/dispatcher.d.ts +44 -0
- package/dist/providers/native-history/hermes-cli-transcript.d.ts +30 -0
- package/dist/providers/native-history/index.d.ts +13 -0
- package/dist/providers/provider-instance-manager.d.ts +29 -0
- package/dist/providers/provider-instance.d.ts +30 -1
- package/dist/providers/provider-loader.d.ts +42 -5
- package/dist/providers/provider-trust.d.ts +31 -0
- package/dist/providers/read-chat-contract.d.ts +29 -0
- package/dist/providers/sdk/v1/builders/acp/detect-status.d.ts +68 -0
- package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +86 -0
- package/dist/providers/sdk/v1/builders/cli/parse-approval-squash.d.ts +59 -0
- package/dist/providers/sdk/v1/builders/cli/parse-approval.d.ts +65 -0
- package/dist/providers/sdk/v1/builders/cli/parse-session.d.ts +91 -0
- package/dist/providers/sdk/v1/builders/cli/visible-region.d.ts +42 -0
- package/dist/providers/sdk/v1/fixture-tooling/format.d.ts +126 -0
- package/dist/providers/sdk/v1/fixture-tooling/index.d.ts +8 -0
- package/dist/providers/sdk/v1/fixture-tooling/replay.d.ts +38 -0
- package/dist/providers/sdk/v1/index.d.ts +30 -0
- package/dist/providers/sdk/v1/sandbox/README-design.d.ts +193 -0
- package/dist/providers/sdk/v1/sandbox/require-whitelist.d.ts +74 -0
- package/dist/providers/sdk/v1/sandbox/script-runner.d.ts +98 -0
- package/dist/providers/sdk/v1/types/cli/index.d.ts +277 -0
- package/dist/providers/sdk/v1/types/common/index.d.ts +203 -0
- package/dist/providers/sdk/v1/validators/index.d.ts +5 -0
- package/dist/providers/sdk/v1/validators/manifest.d.ts +40 -0
- package/dist/providers/sdk/v1/validators/taint.d.ts +52 -0
- package/dist/providers/spec/adapter.d.ts +98 -0
- package/dist/providers/spec/cli-adapter.d.ts +223 -0
- package/dist/providers/spec/evaluator.d.ts +45 -0
- package/dist/providers/spec/fsm-driver.d.ts +449 -0
- package/dist/providers/spec/fsm-evaluator.d.ts +93 -0
- package/dist/providers/spec/fsm-loader.d.ts +14 -0
- package/dist/providers/spec/fsm-types.d.ts +202 -0
- package/dist/providers/spec/native-history-executor.d.ts +41 -0
- package/dist/providers/spec/pre-launch-trust.d.ts +16 -0
- package/dist/providers/spec/route.d.ts +4 -0
- package/dist/providers/spec/types.d.ts +244 -0
- package/dist/providers/status-monitor.d.ts +7 -7
- package/dist/providers/transcript-v2.d.ts +176 -0
- package/dist/providers/types/interactive-prompt.d.ts +84 -0
- package/dist/providers/working-dir.d.ts +17 -0
- package/dist/repo-mesh-types.d.ts +629 -12
- package/dist/runtime-defaults.d.ts +2 -0
- package/dist/session-host/managed-host.d.ts +64 -0
- package/dist/session-host/runtime-surface.d.ts +10 -16
- package/dist/sessions/registry.d.ts +26 -0
- package/dist/shared-types-extra.d.ts +2 -4
- package/dist/shared-types.d.ts +98 -55
- package/dist/status/chat-tail-hot-sessions.d.ts +40 -0
- package/dist/status/normalize.d.ts +1 -1
- package/dist/status/normalize.js +1 -0
- package/dist/status/normalize.js.map +1 -1
- package/dist/status/normalize.mjs +1 -0
- package/dist/status/normalize.mjs.map +1 -1
- package/dist/status/reporter.d.ts +2 -0
- package/dist/status/snapshot.d.ts +26 -0
- package/dist/system/hash.d.ts +8 -0
- package/dist/system/load-better-sqlite3.d.ts +21 -0
- package/dist/types.d.ts +5 -0
- package/package.json +11 -4
- package/src/agent-stream/poller.ts +2 -3
- package/src/agent-stream/provider-adapter.ts +1 -1
- package/src/boot/daemon-lifecycle.ts +80 -14
- package/src/boot/process-hardening.ts +89 -0
- package/src/build-info.ts +73 -0
- package/src/chat/source-machine.ts +534 -0
- package/src/chat/source-resolver.ts +0 -0
- package/src/chat/subscription-updates.ts +20 -1
- package/src/cli-adapter-types.d.ts +3 -1
- package/src/cli-adapter-types.ts +68 -2
- package/src/cli-adapters/cli-script-runner.ts +421 -0
- package/src/cli-adapters/cli-state-engine.ts +1282 -0
- package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +954 -1144
- package/src/cli-adapters/provider-cli-parse.d.ts +2 -0
- package/src/cli-adapters/provider-cli-parse.ts +19 -0
- package/src/cli-adapters/provider-cli-runtime.ts +56 -7
- package/src/cli-adapters/provider-cli-shared.d.ts +4 -0
- package/src/cli-adapters/provider-cli-shared.ts +121 -11
- package/src/cli-adapters/pty-transport.ts +2 -1
- package/src/cli-adapters/pty-write-chunking.ts +106 -0
- package/src/cli-adapters/raw-terminal-io.ts +252 -0
- package/src/cli-adapters/resolve-executable.ts +204 -0
- package/src/cli-adapters/session-host-transport.ts +2 -1
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +63 -29
- package/src/cli-adapters/terminal-backends/types.ts +10 -3
- package/src/cli-adapters/terminal-screen.ts +16 -81
- package/src/commands/chat-commands-debug-bundle.ts +398 -0
- package/src/commands/chat-commands-read.ts +2767 -0
- package/src/commands/chat-commands-scope.ts +54 -0
- package/src/commands/chat-commands-shared.ts +114 -0
- package/src/commands/chat-commands-write.ts +891 -0
- package/src/commands/chat-commands.ts +19 -1841
- package/src/commands/cli-manager.d.ts +2 -0
- package/src/commands/cli-manager.ts +659 -27
- package/src/commands/handler.ts +845 -2
- package/src/commands/high-family/index.ts +28 -0
- package/src/commands/high-family/mesh-coordinator-launch.ts +680 -0
- package/src/commands/high-family/mesh-events.ts +102 -0
- package/src/commands/high-family/mesh-status.ts +849 -0
- package/src/commands/high-family/types.ts +80 -0
- package/src/commands/low-family/coordinator-prompt.ts +72 -0
- package/src/commands/low-family/daemon-lifecycle.ts +120 -0
- package/src/commands/low-family/diagnostics.ts +57 -0
- package/src/commands/low-family/index.ts +37 -0
- package/src/commands/low-family/mesh-ledger.ts +62 -0
- package/src/commands/low-family/mesh-node-logs.ts +87 -0
- package/src/commands/low-family/notification.ts +116 -0
- package/src/commands/low-family/refine-config.ts +106 -0
- package/src/commands/low-family/session-host.ts +282 -0
- package/src/commands/low-family/spec-providerdev.ts +217 -0
- package/src/commands/low-family/status-meta.ts +112 -0
- package/src/commands/low-family/types.ts +39 -0
- package/src/commands/med-family/cli-agent.ts +241 -0
- package/src/commands/med-family/fast-forward.ts +230 -0
- package/src/commands/med-family/ide.ts +163 -0
- package/src/commands/med-family/index.ts +37 -0
- package/src/commands/med-family/mesh-crud.ts +1287 -0
- package/src/commands/med-family/mesh-host-pairing.ts +234 -0
- package/src/commands/med-family/mesh-queue.ts +167 -0
- package/src/commands/med-family/mesh-restart.ts +92 -0
- package/src/commands/med-family/types.ts +153 -0
- package/src/commands/mesh-coordinator.ts +334 -124
- package/src/commands/router-aggregate-status.ts +209 -0
- package/src/commands/router-mesh-session-owner.ts +114 -0
- package/src/commands/router-refine.ts +1711 -0
- package/src/commands/router-worktree-cleanup.ts +870 -0
- package/src/commands/router.ts +795 -4143
- package/src/commands/stream-commands.ts +8 -0
- package/src/commands/upgrade-helper.ts +310 -45
- package/src/config/chat-history.ts +517 -24
- package/src/config/config.ts +30 -0
- package/src/config/mesh-config.ts +632 -23
- package/src/config/mesh-json-config.ts +376 -0
- package/src/config/recent-activity.ts +8 -2
- package/src/config/registry-resolver.ts +100 -0
- package/src/config/repo-settings.ts +111 -0
- package/src/config/state-store.ts +55 -0
- package/src/daemon/dev-auto-implement.ts +3 -2
- package/src/daemon/dev-cli-debug.ts +10 -1
- package/src/daemon/dev-server.ts +0 -541
- package/src/detection/cli-detector.ts +94 -9
- package/src/detection/ide-detector.ts +55 -16
- package/src/detection/win32-ide-version.ts +106 -0
- package/src/git/change-impact-config.ts +354 -0
- package/src/git/git-commands.ts +112 -16
- package/src/git/git-diff.ts +81 -11
- package/src/git/git-executor.ts +12 -0
- package/src/git/git-status.ts +767 -48
- package/src/git/git-types.ts +14 -62
- package/src/git/git-worktree.ts +261 -4
- package/src/git/index.ts +17 -0
- package/src/index.ts +213 -13
- package/src/installer.d.ts +1 -1
- package/src/installer.ts +8 -6
- package/src/ipc/local-ipc-server.ts +278 -0
- package/src/launch.d.ts +1 -1
- package/src/launch.ts +37 -28
- package/src/logging/async-batch-writer.ts +55 -0
- package/src/logging/command-log.ts +7 -5
- package/src/logging/debug-config.ts +25 -0
- package/src/logging/debug-trace.ts +7 -2
- package/src/logging/log-redactor.ts +100 -0
- package/src/logging/log-tail-reader.ts +341 -0
- package/src/logging/logger.ts +14 -7
- package/src/mesh/contracts.ts +469 -0
- package/src/mesh/coordinator-prompt.ts +572 -34
- package/src/mesh/coordinator-registry.ts +121 -0
- package/src/mesh/mesh-active-work.ts +706 -0
- package/src/mesh/mesh-clone-grace.ts +68 -0
- package/src/mesh/mesh-completion-synthesis.ts +416 -0
- package/src/mesh/mesh-coordinator-config.ts +97 -0
- package/src/mesh/mesh-delivery-policy.ts +284 -0
- package/src/mesh/mesh-event-classify.ts +78 -0
- package/src/mesh/mesh-event-forwarding.ts +2002 -0
- package/src/mesh/mesh-event-trace.ts +67 -0
- package/src/mesh/mesh-events-coordinator.ts +32 -0
- package/src/mesh/mesh-events-pending.ts +1494 -0
- package/src/mesh/mesh-events-stale.ts +452 -0
- package/src/mesh/mesh-events-utils.ts +443 -0
- package/src/mesh/mesh-events.ts +43 -1035
- package/src/mesh/mesh-fast-forward.ts +856 -0
- package/src/mesh/mesh-host-ownership.ts +111 -0
- package/src/mesh/mesh-init.ts +350 -0
- package/src/mesh/mesh-ledger-reconciliation.ts +12 -7
- package/src/mesh/mesh-ledger.ts +898 -102
- package/src/mesh/mesh-magi-status.ts +223 -0
- package/src/mesh/mesh-missions.ts +695 -0
- package/src/mesh/mesh-node-identity.ts +2065 -0
- package/src/mesh/mesh-queue-assignment.ts +2263 -0
- package/src/mesh/mesh-reconcile-acked-hold.ts +230 -0
- package/src/mesh/mesh-reconcile-config.ts +66 -0
- package/src/mesh/mesh-reconcile-identity.ts +103 -0
- package/src/mesh/mesh-reconcile-loop.ts +1473 -0
- package/src/mesh/mesh-reconcile-v2-backstop.ts +62 -0
- package/src/mesh/mesh-refine-batch.ts +205 -0
- package/src/mesh/mesh-refine-gates.ts +1686 -0
- package/src/mesh/mesh-refine-status.ts +231 -0
- package/src/mesh/mesh-remote-event-pull.ts +279 -0
- package/src/mesh/mesh-review-inbox.ts +308 -0
- package/src/mesh/mesh-routing.ts +291 -0
- package/src/mesh/mesh-runtime-store.ts +2150 -0
- package/src/mesh/mesh-scheduling-runtime.ts +198 -0
- package/src/mesh/mesh-task-inflight.ts +70 -0
- package/src/mesh/mesh-task-stats.ts +161 -0
- package/src/mesh/mesh-unresolved-forward-outbox.ts +185 -0
- package/src/mesh/mesh-warmup-deadline.ts +152 -0
- package/src/mesh/mesh-work-queue.ts +1437 -147
- package/src/mesh/preview-freshness.ts +163 -0
- package/src/mesh/refine-config.ts +423 -0
- package/src/mesh/worktree-bootstrap-config.ts +599 -0
- package/src/providers/acp-provider-instance.ts +43 -10
- package/src/providers/approval-utils.d.ts +5 -0
- package/src/providers/approval-utils.ts +100 -6
- package/src/providers/chat-message-normalization.ts +126 -4
- package/src/providers/cli-provider-effect-format.ts +53 -0
- package/src/providers/cli-provider-history-dedup.ts +75 -0
- package/src/providers/cli-provider-input-prompt.ts +133 -0
- package/src/providers/cli-provider-instance-types.ts +131 -0
- package/src/providers/cli-provider-instance.ts +2709 -499
- package/src/providers/cli-provider-status-helpers.ts +123 -0
- package/src/providers/cli-provider-transcript-merge.ts +114 -0
- package/src/providers/contracts.d.ts +55 -0
- package/src/providers/contracts.ts +160 -6
- package/src/providers/extension-provider-instance.ts +12 -7
- package/src/providers/external-sources.ts +218 -0
- package/src/providers/ide-provider-instance.ts +35 -12
- package/src/providers/manual-attendance.ts +105 -0
- package/src/providers/native-history/antigravity-claim-registry.ts +138 -0
- package/src/providers/native-history/antigravity-cli-transcript.ts +1275 -0
- package/src/providers/native-history/claude-cli-transcript.ts +396 -0
- package/src/providers/native-history/codex-cli-transcript.ts +479 -0
- package/src/providers/native-history/constants.ts +19 -0
- package/src/providers/native-history/dispatcher.ts +617 -0
- package/src/providers/native-history/hermes-cli-transcript.ts +312 -0
- package/src/providers/native-history/index.ts +30 -0
- package/src/providers/provider-instance-manager.ts +71 -0
- package/src/providers/provider-instance.ts +24 -1
- package/src/providers/provider-loader.ts +700 -55
- package/src/providers/provider-schema.ts +87 -14
- package/src/providers/provider-trust.ts +114 -0
- package/src/providers/read-chat-contract.ts +76 -16
- package/src/providers/sdk/README.md +49 -0
- package/src/providers/sdk/v1/builders/acp/detect-status.ts +144 -0
- package/src/providers/sdk/v1/builders/cli/detect-status.ts +313 -0
- package/src/providers/sdk/v1/builders/cli/parse-approval-squash.ts +158 -0
- package/src/providers/sdk/v1/builders/cli/parse-approval.ts +261 -0
- package/src/providers/sdk/v1/builders/cli/parse-session.ts +276 -0
- package/src/providers/sdk/v1/builders/cli/visible-region.ts +143 -0
- package/src/providers/sdk/v1/fixture-tooling/format.ts +130 -0
- package/src/providers/sdk/v1/fixture-tooling/index.ts +22 -0
- package/src/providers/sdk/v1/fixture-tooling/replay.ts +352 -0
- package/src/providers/sdk/v1/index.ts +152 -0
- package/src/providers/sdk/v1/sandbox/README-design.ts +195 -0
- package/src/providers/sdk/v1/sandbox/require-whitelist.ts +472 -0
- package/src/providers/sdk/v1/sandbox/script-runner.ts +150 -0
- package/src/providers/sdk/v1/schemas/cli/provider.schema.json +509 -0
- package/src/providers/sdk/v1/schemas/primitives/acp-session-protocol-v1.json +131 -0
- package/src/providers/sdk/v1/schemas/primitives/native-history-codex-rollout-v1.json +66 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-approval-squash-v1.json +91 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-assistant-block-v1.json +91 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-cue-ordering-v1.json +47 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-dispatch-order-v1.json +32 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-footer-chrome-v1.json +42 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-index-finder-v1.json +27 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-modal-v1.json +125 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-prompt-marker-v1.json +45 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-session-id-extraction-v1.json +46 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-settled-prompt-v1.json +71 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-spinner-v1.json +83 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-transcript-pty-v1.json +83 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-visible-region-v1.json +57 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-welcome-screen-v1.json +35 -0
- package/src/providers/sdk/v1/types/cli/index.ts +365 -0
- package/src/providers/sdk/v1/types/common/index.ts +229 -0
- package/src/providers/sdk/v1/validators/index.ts +19 -0
- package/src/providers/sdk/v1/validators/manifest.ts +110 -0
- package/src/providers/sdk/v1/validators/taint.ts +309 -0
- package/src/providers/spec/adapter.ts +246 -0
- package/src/providers/spec/cli-adapter.ts +1064 -0
- package/src/providers/spec/evaluator.ts +407 -0
- package/src/providers/spec/fsm-driver.ts +1528 -0
- package/src/providers/spec/fsm-evaluator.ts +290 -0
- package/src/providers/spec/fsm-loader.ts +128 -0
- package/src/providers/spec/fsm-types.ts +301 -0
- package/src/providers/spec/native-history-executor.ts +1241 -0
- package/src/providers/spec/pre-launch-trust.ts +104 -0
- package/src/providers/spec/route.ts +51 -0
- package/src/providers/spec/types.ts +284 -0
- package/src/providers/status-monitor.d.ts +7 -7
- package/src/providers/status-monitor.ts +37 -22
- package/src/providers/transcript-v2.ts +567 -0
- package/src/providers/types/interactive-prompt.ts +536 -0
- package/src/providers/version-archive.ts +64 -24
- package/src/providers/working-dir.ts +23 -0
- package/src/repo-mesh-types.ts +868 -13
- package/src/runtime-defaults.ts +39 -0
- package/src/session-host/managed-host.ts +218 -0
- package/src/session-host/runtime-surface.ts +20 -80
- package/src/sessions/registry.ts +44 -0
- package/src/shared-types-extra.ts +2 -4
- package/src/shared-types.d.ts +8 -0
- package/src/shared-types.ts +136 -55
- package/src/status/builders.ts +26 -6
- package/src/status/chat-tail-hot-sessions.ts +117 -2
- package/src/status/normalize.ts +2 -0
- package/src/status/reporter.ts +19 -1
- package/src/status/snapshot.ts +95 -26
- package/src/system/hash.ts +23 -0
- package/src/system/host-memory.ts +29 -12
- package/src/system/load-better-sqlite3.ts +68 -0
- package/src/types.ts +5 -0
- package/dist/cli-adapters/terminal-backends/xterm-backend.d.ts +0 -17
- package/dist/mesh/mesh-sync.d.ts +0 -53
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +0 -16
- package/src/cli-adapters/terminal-backends/xterm-backend.ts +0 -97
- package/src/mesh/mesh-sync.ts +0 -111
package/src/commands/handler.ts
CHANGED
|
@@ -20,11 +20,14 @@ import type { ProviderModule, ProviderScripts } from '../providers/contracts.js'
|
|
|
20
20
|
import type { DaemonAgentStreamManager } from '../agent-stream/index.js';
|
|
21
21
|
import type { CliAdapter } from '../cli-adapter-types.js';
|
|
22
22
|
import { loadConfig } from '../config/config.js';
|
|
23
|
+
import { resolveRegistryBaseUrl } from '../config/registry-resolver.js';
|
|
23
24
|
import { ChatHistoryWriter } from '../config/chat-history.js';
|
|
24
25
|
import type { SessionRegistry, SessionRuntimeTarget } from '../sessions/registry.js';
|
|
25
26
|
import { reconcileIdeRuntimeSessions } from '../sessions/reconcile.js';
|
|
26
27
|
import { LOG } from '../logging/logger.js';
|
|
27
28
|
import { resolveLegacyProviderScript, type LegacyStringScript } from './provider-script-resolver.js';
|
|
29
|
+
import { sha256Hex } from '../system/hash.js';
|
|
30
|
+
import { MANUAL_ATTENDANCE_COMMANDS, MANUAL_ATTENDANCE_PASSIVE_VIEW_COMMANDS } from '../providers/manual-attendance.js';
|
|
28
31
|
|
|
29
32
|
// Sub-module imports
|
|
30
33
|
import * as Chat from './chat-commands.js';
|
|
@@ -375,6 +378,39 @@ export class DaemonCommandHandler implements CommandHelpers {
|
|
|
375
378
|
this._agentStream = manager;
|
|
376
379
|
}
|
|
377
380
|
|
|
381
|
+
/**
|
|
382
|
+
* When a command in the manual-attendance set arrives for a session this
|
|
383
|
+
* daemon hosts, stamp the live instance so auto-approve holds while the user
|
|
384
|
+
* drives the session by hand. Provider-common: the signal is the command
|
|
385
|
+
* (foreground select_session / open_panel, controlbar invoke_provider_script
|
|
386
|
+
* / set_mode / change_model / set_thought_level, manual resolve_action,
|
|
387
|
+
* pty_input), never any CLI-specific modal text — so it works identically for
|
|
388
|
+
* every CLI/ACP provider. send_chat is deliberately excluded because a
|
|
389
|
+
* coordinator delegating a task to a worker also uses send_chat; counting it
|
|
390
|
+
* would wrongly suppress the worker's delegated auto-approve. For a remote
|
|
391
|
+
* mesh worker session the controlbar commands are forwarded to the owning
|
|
392
|
+
* worker daemon, which runs this same hook there, so attendance is recorded
|
|
393
|
+
* on the daemon that actually hosts the instance.
|
|
394
|
+
*/
|
|
395
|
+
private noteManualAttendanceIfApplicable(cmd: string, args: any): void {
|
|
396
|
+
if (!MANUAL_ATTENDANCE_COMMANDS.has(cmd)) return;
|
|
397
|
+
// Passive view-only actions (select_session / open_panel) attend a
|
|
398
|
+
// foreground session but NOT a delegated worker — the instance decides.
|
|
399
|
+
const passive = MANUAL_ATTENDANCE_PASSIVE_VIEW_COMMANDS.has(cmd);
|
|
400
|
+
const sessionId = this._currentRoute.session?.sessionId
|
|
401
|
+
|| (typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '');
|
|
402
|
+
if (!sessionId) return;
|
|
403
|
+
const session = this._ctx.sessionRegistry?.get(sessionId);
|
|
404
|
+
const instanceKey = session?.adapterKey || session?.instanceKey || sessionId;
|
|
405
|
+
const instance = this._ctx.instanceManager?.getInstance(instanceKey) as
|
|
406
|
+
{ noteManualInteraction?: (now?: number, opts?: { passive?: boolean }) => void } | undefined;
|
|
407
|
+
try {
|
|
408
|
+
instance?.noteManualInteraction?.(undefined, { passive });
|
|
409
|
+
} catch {
|
|
410
|
+
// attendance is best-effort — never block command dispatch
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
378
414
|
// ─── Command Dispatcher ──────────────────────────
|
|
379
415
|
|
|
380
416
|
async handle(cmd: string, args: any): Promise<CommandResult> {
|
|
@@ -382,6 +418,7 @@ export class DaemonCommandHandler implements CommandHelpers {
|
|
|
382
418
|
this._currentRoute = this.resolveRoute(args);
|
|
383
419
|
const startedAt = Date.now();
|
|
384
420
|
this.logCommandStart(cmd, args);
|
|
421
|
+
this.noteManualAttendanceIfApplicable(cmd, args);
|
|
385
422
|
let result: CommandResult;
|
|
386
423
|
|
|
387
424
|
if (isGitCommandName(cmd)) {
|
|
@@ -408,12 +445,19 @@ export class DaemonCommandHandler implements CommandHelpers {
|
|
|
408
445
|
'invoke_provider_script',
|
|
409
446
|
]);
|
|
410
447
|
|
|
448
|
+
// read_chat and get_chat_debug_bundle can serve historical transcript data even
|
|
449
|
+
// when the live session record is gone (stopped/destroyed). Allow the fallback
|
|
450
|
+
// when the provider type is known and any session identity hint is present:
|
|
451
|
+
// an explicit providerSessionId/historySessionId, or the targetSessionId itself
|
|
452
|
+
// (which getHistorySessionId already uses as a fallback history key).
|
|
453
|
+
const isReadOrDebugCmd = cmd === 'read_chat' || cmd === 'get_chat_debug_bundle';
|
|
411
454
|
const allowsInactiveReadChatFallback =
|
|
412
|
-
|
|
455
|
+
isReadOrDebugCmd
|
|
413
456
|
&& !!this._currentRoute.providerType
|
|
414
457
|
&& (
|
|
415
458
|
(typeof args?.providerSessionId === 'string' && args.providerSessionId.trim().length > 0)
|
|
416
459
|
|| (typeof args?.historySessionId === 'string' && args.historySessionId.trim().length > 0)
|
|
460
|
+
|| (typeof args?.targetSessionId === 'string' && args.targetSessionId.trim().length > 0)
|
|
417
461
|
);
|
|
418
462
|
|
|
419
463
|
if (this._currentRoute.sessionLookupFailed && sessionScopedCommands.has(cmd) && !allowsInactiveReadChatFallback) {
|
|
@@ -503,6 +547,15 @@ export class DaemonCommandHandler implements CommandHelpers {
|
|
|
503
547
|
|
|
504
548
|
// ─── Script manage ───────────────────
|
|
505
549
|
case 'refresh_scripts': return this.handleRefreshScripts(args);
|
|
550
|
+
case 'list_provider_availability': return this.handleListProviderAvailability(args);
|
|
551
|
+
case 'install_provider_manifest': return this.handleInstallProviderManifest(args);
|
|
552
|
+
case 'uninstall_provider_manifest': return this.handleUninstallProviderManifest(args);
|
|
553
|
+
case 'check_provider_updates': return this.handleCheckProviderUpdates(args);
|
|
554
|
+
case 'list_installed_providers': return this.handleListInstalledProviders(args);
|
|
555
|
+
case 'add_provider_source': return this.handleAddProviderSource(args);
|
|
556
|
+
case 'remove_provider_source': return this.handleRemoveProviderSource(args);
|
|
557
|
+
case 'list_provider_sources': return this.handleListProviderSources(args);
|
|
558
|
+
case 'set_active_provider_source': return this.handleSetActiveProviderSource(args);
|
|
506
559
|
|
|
507
560
|
// ─── Stream commands (stream-commands.ts) ───────────
|
|
508
561
|
case 'select_session': return Stream.handleSelectSession(this, args);
|
|
@@ -538,9 +591,15 @@ export class DaemonCommandHandler implements CommandHelpers {
|
|
|
538
591
|
|
|
539
592
|
// ─── Misc (kept in handler — too small to extract) ───────
|
|
540
593
|
|
|
594
|
+
/**
|
|
595
|
+
* Reload providers from disk. Does NOT pull from the registry — the user
|
|
596
|
+
* controls installs explicitly via install_provider_manifest. To upgrade
|
|
597
|
+
* an installed provider, call install_provider_manifest again with the
|
|
598
|
+
* desired version (or with no version to pick up the latest from
|
|
599
|
+
* registry), or use check_provider_updates to see what is out of date.
|
|
600
|
+
*/
|
|
541
601
|
private async handleRefreshScripts(_args: any): Promise<CommandResult> {
|
|
542
602
|
if (this._ctx.providerLoader) {
|
|
543
|
-
await this._ctx.providerLoader.fetchLatest().catch(() => {});
|
|
544
603
|
this._ctx.providerLoader.reload();
|
|
545
604
|
this._ctx.providerLoader.registerToDetector();
|
|
546
605
|
const refreshedInstances = this._ctx.instanceManager
|
|
@@ -556,6 +615,790 @@ export class DaemonCommandHandler implements CommandHelpers {
|
|
|
556
615
|
return { success: false, error: 'ProviderLoader not initialized' };
|
|
557
616
|
}
|
|
558
617
|
|
|
618
|
+
/**
|
|
619
|
+
* Return per-provider availability so the dashboard's provider catalog
|
|
620
|
+
* can show "Installed" badges. Reuses the existing detection state from
|
|
621
|
+
* ProviderLoader.getMachineProviderStatus() — no probing is triggered.
|
|
622
|
+
*/
|
|
623
|
+
private handleListProviderAvailability(_args: any): CommandResult {
|
|
624
|
+
if (!this._ctx.providerLoader) {
|
|
625
|
+
return { success: false, error: 'ProviderLoader not initialized' };
|
|
626
|
+
}
|
|
627
|
+
const { describeTrust, requiresConfirmation } =
|
|
628
|
+
require('../providers/provider-trust.js') as typeof import('../providers/provider-trust.js');
|
|
629
|
+
const loader = this._ctx.providerLoader;
|
|
630
|
+
const items = loader.getAll().map((provider) => {
|
|
631
|
+
const machineConfig = loader.getMachineProviderConfig(provider.type);
|
|
632
|
+
const lastDetection = machineConfig.lastDetection;
|
|
633
|
+
const trust = (provider as any)._sourceTrust ?? 'trusted';
|
|
634
|
+
const layer = (provider as any)._sourceLayer ?? 'upstream';
|
|
635
|
+
const sourceName = (provider as any)._sourceName ?? null;
|
|
636
|
+
return {
|
|
637
|
+
type: provider.type,
|
|
638
|
+
category: provider.category,
|
|
639
|
+
status: loader.getMachineProviderStatus(provider.type),
|
|
640
|
+
installed: lastDetection?.ok === true,
|
|
641
|
+
detectedPath: lastDetection?.path ?? null,
|
|
642
|
+
checkedAt: lastDetection?.checkedAt ?? null,
|
|
643
|
+
trust,
|
|
644
|
+
trustDescription: describeTrust(trust),
|
|
645
|
+
requiresConfirmation: requiresConfirmation(trust),
|
|
646
|
+
sourceLayer: layer,
|
|
647
|
+
sourceName,
|
|
648
|
+
};
|
|
649
|
+
});
|
|
650
|
+
return { success: true, providers: items };
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Compute the *upstream cache root*. install_provider_manifest writes
|
|
655
|
+
* official-registry manifests here so the daemon's standard upstream
|
|
656
|
+
* layer picks them up — no special handling needed at load time, and
|
|
657
|
+
* the manifests inherit the official-trust badge instead of the
|
|
658
|
+
* untrusted-external one.
|
|
659
|
+
*
|
|
660
|
+
* Path matches ProviderLoader.upstreamDir but we recompute it from
|
|
661
|
+
* homedir() so this method stays usable in dev where userDir can
|
|
662
|
+
* point at a sibling git checkout.
|
|
663
|
+
*/
|
|
664
|
+
private getUpstreamInstallRoot(): string {
|
|
665
|
+
const os = require('os') as typeof import('os');
|
|
666
|
+
const path = require('path') as typeof import('path');
|
|
667
|
+
return path.join(os.homedir(), '.adhdev', 'providers', '.upstream');
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Download a single provider manifest from the registry and write it to
|
|
672
|
+
* ~/.adhdev/providers/.upstream/{category}/{type}/provider.json.
|
|
673
|
+
*
|
|
674
|
+
* Used by standalone onboarding to seed the upstream cache with the
|
|
675
|
+
* default provider set on first launch. Verifies SHA-256 checksum
|
|
676
|
+
* against the registry meta before persisting. Refuses to write
|
|
677
|
+
* outside the upstream root.
|
|
678
|
+
*
|
|
679
|
+
* Args: { type: string, category?: string, version?: string }
|
|
680
|
+
* If category/version are omitted, looks up the latest from the registry.
|
|
681
|
+
*/
|
|
682
|
+
private async handleInstallProviderManifest(args: any): Promise<CommandResult> {
|
|
683
|
+
if (!this._ctx.providerLoader) {
|
|
684
|
+
return { success: false, error: 'ProviderLoader not initialized' };
|
|
685
|
+
}
|
|
686
|
+
const type = typeof args?.type === 'string' ? args.type : '';
|
|
687
|
+
if (!type) return { success: false, error: 'type is required' };
|
|
688
|
+
// Defense in depth: reject any obvious path-traversal in the type.
|
|
689
|
+
if (!/^[a-z0-9][a-z0-9_-]{0,63}$/i.test(type)) {
|
|
690
|
+
return { success: false, error: 'invalid type' };
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const https = require('https') as typeof import('https');
|
|
694
|
+
const fs = require('fs') as typeof import('fs');
|
|
695
|
+
const path = require('path') as typeof import('path');
|
|
696
|
+
const REGISTRY = resolveRegistryBaseUrl(loadConfig().registryUrl);
|
|
697
|
+
|
|
698
|
+
function fetchText(url: string, timeoutMs: number): Promise<string> {
|
|
699
|
+
return new Promise((resolve, reject) => {
|
|
700
|
+
const req = https.get(url, { headers: { 'User-Agent': 'adhdev-daemon', 'Accept': 'application/json' }, timeout: timeoutMs }, (res) => {
|
|
701
|
+
if (res.statusCode !== 200) { reject(new Error(`HTTP ${res.statusCode}`)); return; }
|
|
702
|
+
const chunks: Buffer[] = [];
|
|
703
|
+
res.on('data', (c: Buffer) => chunks.push(c));
|
|
704
|
+
res.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
|
|
705
|
+
});
|
|
706
|
+
req.on('error', reject);
|
|
707
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
try {
|
|
712
|
+
// 1. Look up provider metadata so we know the expected category, version, checksum.
|
|
713
|
+
const metaBody = await fetchText(`${REGISTRY}/providers/${encodeURIComponent(type)}`, 10000);
|
|
714
|
+
const meta = JSON.parse(metaBody) as { type: string; category: string; version: string; checksum: string };
|
|
715
|
+
const category = typeof args?.category === 'string' ? args.category : meta.category;
|
|
716
|
+
const version = typeof args?.version === 'string' ? args.version : meta.version;
|
|
717
|
+
|
|
718
|
+
// Defense in depth on category as well — only known categories.
|
|
719
|
+
if (!['cli', 'ide', 'extension', 'acp'].includes(category)) {
|
|
720
|
+
return { success: false, error: `unknown category: ${category}` };
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// 2. Download the manifest body.
|
|
724
|
+
const manifestBody = await fetchText(
|
|
725
|
+
`${REGISTRY}/providers/${encodeURIComponent(type)}/${encodeURIComponent(version)}/download`,
|
|
726
|
+
30000
|
|
727
|
+
);
|
|
728
|
+
|
|
729
|
+
// 3. Verify checksum.
|
|
730
|
+
const actualChecksum = sha256Hex(manifestBody);
|
|
731
|
+
if (actualChecksum !== meta.checksum) {
|
|
732
|
+
return { success: false, error: `checksum mismatch: expected ${meta.checksum}, got ${actualChecksum}` };
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// 4. Write to the upstream cache root, NOT to ProviderLoader.getUserDir():
|
|
736
|
+
// in dev, userDir points at the sibling adhdev-providers git checkout.
|
|
737
|
+
const installRoot = this.getUpstreamInstallRoot();
|
|
738
|
+
const installRootResolved = path.resolve(installRoot);
|
|
739
|
+
const targetDir = path.resolve(path.join(installRoot, category, type));
|
|
740
|
+
if (!targetDir.startsWith(installRootResolved + path.sep)) {
|
|
741
|
+
return { success: false, error: 'install path escaped upstream root' };
|
|
742
|
+
}
|
|
743
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
744
|
+
// v1 vs v0 manifest selection — v1 manifests carry an SDK
|
|
745
|
+
// $schema URL or v1-only keys (tui, overrides-as-object,
|
|
746
|
+
// source, canonicalHistory). The loader prefers
|
|
747
|
+
// provider.v1.json when both exist, so writing v1 manifests
|
|
748
|
+
// under the v0 name would shadow them. Detect and write to
|
|
749
|
+
// the right file.
|
|
750
|
+
let manifestProbe: Record<string, any> = {};
|
|
751
|
+
try { manifestProbe = JSON.parse(manifestBody) as Record<string, any>; } catch { /* validation below */ }
|
|
752
|
+
const isV1 = typeof manifestProbe?.$schema === 'string' && manifestProbe.$schema.includes('/v1/')
|
|
753
|
+
|| (manifestProbe?.overrides && typeof manifestProbe.overrides === 'object' && !Array.isArray(manifestProbe.overrides))
|
|
754
|
+
|| !!manifestProbe?.tui;
|
|
755
|
+
|
|
756
|
+
// Reject v1 manifests that don't match the schema at install
|
|
757
|
+
// time so the daemon never persists a known-bad manifest.
|
|
758
|
+
// The provider-loader keeps a permissive warn-only behavior
|
|
759
|
+
// for manifests already on disk, but the install path is the
|
|
760
|
+
// right place to fail fast.
|
|
761
|
+
if (isV1 && manifestProbe?.category === 'cli') {
|
|
762
|
+
try {
|
|
763
|
+
const { validateCliProviderManifest, formatManifestValidationIssues } =
|
|
764
|
+
require('../providers/sdk/v1/validators/manifest.js') as typeof import('../providers/sdk/v1/validators/manifest.js');
|
|
765
|
+
const validation = validateCliProviderManifest(manifestProbe);
|
|
766
|
+
if (!validation.ok) {
|
|
767
|
+
return {
|
|
768
|
+
success: false,
|
|
769
|
+
error: `manifest failed v1 schema validation:\n${formatManifestValidationIssues(validation.issues)}`,
|
|
770
|
+
validationIssues: validation.issues,
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
} catch (e: any) {
|
|
774
|
+
// Validator load failure shouldn't block install — log
|
|
775
|
+
// and continue. The loader's warn-only path will
|
|
776
|
+
// surface the same issue at boot if it's real.
|
|
777
|
+
LOG.warn('Command', `[install_provider_manifest] schema validator unavailable: ${e?.message || e}`);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const targetFile = isV1 ? 'provider.v1.json' : 'provider.json';
|
|
782
|
+
const targetPath = path.join(targetDir, targetFile);
|
|
783
|
+
fs.writeFileSync(targetPath, manifestBody, 'utf-8');
|
|
784
|
+
|
|
785
|
+
// 5. If the manifest declares a `source` GitHub repo, fetch the
|
|
786
|
+
// script directories listed in the manifest (defaultScriptDir +
|
|
787
|
+
// each compatibility[].scriptDir). Extended-tier providers
|
|
788
|
+
// bundle their override JS this way — the registry intentionally
|
|
789
|
+
// only stores the manifest JSON, not the script bytes, so a
|
|
790
|
+
// third-party can publish a manifest pointing at their own fork
|
|
791
|
+
// without pushing files into our R2 bucket.
|
|
792
|
+
const manifestJson = JSON.parse(manifestBody) as Record<string, any>;
|
|
793
|
+
const scriptFetch = await this.fetchProviderSources(
|
|
794
|
+
manifestJson,
|
|
795
|
+
category,
|
|
796
|
+
type,
|
|
797
|
+
targetDir,
|
|
798
|
+
);
|
|
799
|
+
|
|
800
|
+
// Hot-reload so the daemon picks up the new manifest.
|
|
801
|
+
this._ctx.providerLoader.reload();
|
|
802
|
+
this._ctx.providerLoader.registerToDetector();
|
|
803
|
+
|
|
804
|
+
return {
|
|
805
|
+
success: true,
|
|
806
|
+
installed: {
|
|
807
|
+
type, category, version, checksum: actualChecksum, path: targetPath,
|
|
808
|
+
scriptsFetched: scriptFetch.fetchedCount,
|
|
809
|
+
scriptSource: scriptFetch.source,
|
|
810
|
+
scriptErrors: scriptFetch.errors,
|
|
811
|
+
},
|
|
812
|
+
};
|
|
813
|
+
} catch (e: any) {
|
|
814
|
+
return { success: false, error: `install failed: ${e?.message || e}` };
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* If `manifest.source = { type:'github', repo, ref, subdir? }` is set,
|
|
820
|
+
* walk each script directory the manifest references and download every
|
|
821
|
+
* file from the public GitHub raw endpoint. Returns a small summary so
|
|
822
|
+
* the caller can report what was fetched.
|
|
823
|
+
*
|
|
824
|
+
* Best-effort: failures don't reject the install — the manifest itself is
|
|
825
|
+
* usable for declarative-only providers, and the user still gets a clear
|
|
826
|
+
* error string back if a needed script is missing.
|
|
827
|
+
*/
|
|
828
|
+
private async fetchProviderSources(
|
|
829
|
+
manifest: Record<string, any>,
|
|
830
|
+
category: string,
|
|
831
|
+
type: string,
|
|
832
|
+
targetDir: string,
|
|
833
|
+
): Promise<{ fetchedCount: number; source: string | null; errors: string[] }> {
|
|
834
|
+
const errors: string[] = [];
|
|
835
|
+
const source = manifest?.source;
|
|
836
|
+
if (!source || source.type !== 'github' || typeof source.repo !== 'string' || typeof source.ref !== 'string') {
|
|
837
|
+
return { fetchedCount: 0, source: null, errors };
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// Collect every script directory the manifest references. v1 manifests
|
|
841
|
+
// use `defaultScriptDir` and `compatibility[].scriptDir`. We also pull
|
|
842
|
+
// any override path's directory (e.g. overrides.detectStatus.path =
|
|
843
|
+
// "scripts/v1/detect_status.js" → fetch the scripts/v1/ directory too).
|
|
844
|
+
const scriptDirs = new Set<string>();
|
|
845
|
+
if (typeof manifest.defaultScriptDir === 'string') scriptDirs.add(manifest.defaultScriptDir);
|
|
846
|
+
if (Array.isArray(manifest.compatibility)) {
|
|
847
|
+
for (const c of manifest.compatibility) {
|
|
848
|
+
if (typeof c?.scriptDir === 'string') scriptDirs.add(c.scriptDir);
|
|
849
|
+
// Spec-driven providers (claude/codex/agy/…) point at a
|
|
850
|
+
// single specs/<version>.json instead of a scriptDir.
|
|
851
|
+
// The whole specs/ directory needs to come down so the
|
|
852
|
+
// spec adapter can resolve the file at runtime — without
|
|
853
|
+
// this, install_provider_manifest leaves the marketplace
|
|
854
|
+
// copy spec-less and provider-loader falls back to the
|
|
855
|
+
// legacy tui-based ProviderCliAdapter.
|
|
856
|
+
if (typeof c?.spec === 'string' && c.spec.includes('/')) {
|
|
857
|
+
const dir = c.spec.substring(0, c.spec.lastIndexOf('/'));
|
|
858
|
+
if (dir) scriptDirs.add(dir);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
if (manifest.overrides && typeof manifest.overrides === 'object' && !Array.isArray(manifest.overrides)) {
|
|
863
|
+
for (const override of Object.values(manifest.overrides) as Array<Record<string, unknown>>) {
|
|
864
|
+
const overridePath = override?.path;
|
|
865
|
+
if (typeof overridePath === 'string' && overridePath.includes('/')) {
|
|
866
|
+
const dir = overridePath.substring(0, overridePath.lastIndexOf('/'));
|
|
867
|
+
if (dir) scriptDirs.add(dir);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
if (scriptDirs.size === 0) {
|
|
872
|
+
return { fetchedCount: 0, source: `${source.repo}@${source.ref}`, errors };
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
const subdir: string = typeof source.subdir === 'string' && source.subdir.length > 0
|
|
876
|
+
? source.subdir
|
|
877
|
+
: `${category}/${type}`;
|
|
878
|
+
const repo: string = source.repo;
|
|
879
|
+
const ref: string = source.ref;
|
|
880
|
+
|
|
881
|
+
const https = require('https') as typeof import('https');
|
|
882
|
+
const fs = require('fs') as typeof import('fs');
|
|
883
|
+
const path = require('path') as typeof import('path');
|
|
884
|
+
|
|
885
|
+
function fetchJson(url: string, timeoutMs: number): Promise<any> {
|
|
886
|
+
return new Promise((resolve, reject) => {
|
|
887
|
+
const req = https.get(url, {
|
|
888
|
+
headers: { 'User-Agent': 'adhdev-daemon', 'Accept': 'application/vnd.github+json' },
|
|
889
|
+
timeout: timeoutMs,
|
|
890
|
+
}, (res) => {
|
|
891
|
+
if (res.statusCode !== 200) { reject(new Error(`HTTP ${res.statusCode}`)); return; }
|
|
892
|
+
const chunks: Buffer[] = [];
|
|
893
|
+
res.on('data', (c: Buffer) => chunks.push(c));
|
|
894
|
+
res.on('end', () => {
|
|
895
|
+
try { resolve(JSON.parse(Buffer.concat(chunks).toString('utf-8'))); }
|
|
896
|
+
catch (e) { reject(e); }
|
|
897
|
+
});
|
|
898
|
+
});
|
|
899
|
+
req.on('error', reject);
|
|
900
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
function fetchBinary(url: string, timeoutMs: number): Promise<Buffer> {
|
|
905
|
+
return new Promise((resolve, reject) => {
|
|
906
|
+
const req = https.get(url, {
|
|
907
|
+
headers: { 'User-Agent': 'adhdev-daemon' },
|
|
908
|
+
timeout: timeoutMs,
|
|
909
|
+
}, (res) => {
|
|
910
|
+
// Raw endpoint redirects through codeload — follow the redirect.
|
|
911
|
+
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
912
|
+
if (res.headers.location) {
|
|
913
|
+
return fetchBinary(res.headers.location, timeoutMs).then(resolve, reject);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
if (res.statusCode !== 200) { reject(new Error(`HTTP ${res.statusCode}`)); return; }
|
|
917
|
+
const chunks: Buffer[] = [];
|
|
918
|
+
res.on('data', (c: Buffer) => chunks.push(c));
|
|
919
|
+
res.on('end', () => resolve(Buffer.concat(chunks)));
|
|
920
|
+
});
|
|
921
|
+
req.on('error', reject);
|
|
922
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
let fetchedCount = 0;
|
|
927
|
+
|
|
928
|
+
// The shared helpers directory (cli/_shared/) is referenced by most
|
|
929
|
+
// CLI providers via `require('../../../_shared/...')`. Fetch it once
|
|
930
|
+
// alongside the provider's own scripts so node's require resolution
|
|
931
|
+
// succeeds at runtime. Best-effort — silent if the source repo has no
|
|
932
|
+
// _shared dir (e.g. ACP-only repos).
|
|
933
|
+
const sharedDirRel = `${category}/_shared`;
|
|
934
|
+
const sharedTargetDir = path.resolve(path.join(targetDir, '../_shared'));
|
|
935
|
+
const installRootResolved = path.resolve(path.join(targetDir, '../..'));
|
|
936
|
+
if (sharedTargetDir.startsWith(installRootResolved + path.sep)) {
|
|
937
|
+
const sharedStack: string[] = [sharedDirRel];
|
|
938
|
+
while (sharedStack.length) {
|
|
939
|
+
const relDir = sharedStack.pop()!;
|
|
940
|
+
const apiUrl = `https://api.github.com/repos/${repo}/contents/${encodeURI(relDir)}?ref=${encodeURIComponent(ref)}`;
|
|
941
|
+
let entries: Array<{ type: string; path: string; name: string; download_url: string | null }>;
|
|
942
|
+
try {
|
|
943
|
+
entries = await fetchJson(apiUrl, 15000);
|
|
944
|
+
} catch (e: any) {
|
|
945
|
+
// Silent: _shared may not exist on third-party repos
|
|
946
|
+
if (relDir === sharedDirRel) break;
|
|
947
|
+
errors.push(`list shared ${relDir}: ${e?.message ?? e}`);
|
|
948
|
+
continue;
|
|
949
|
+
}
|
|
950
|
+
if (!Array.isArray(entries)) continue;
|
|
951
|
+
for (const entry of entries) {
|
|
952
|
+
if (entry.type === 'dir') { sharedStack.push(entry.path); continue; }
|
|
953
|
+
if (entry.type !== 'file' || !entry.download_url) continue;
|
|
954
|
+
try {
|
|
955
|
+
const body = await fetchBinary(entry.download_url, 30000);
|
|
956
|
+
// entry.path is like 'cli/_shared/foo.js' — strip 'cli/_shared/' prefix
|
|
957
|
+
const relInside = entry.path.startsWith(sharedDirRel + '/')
|
|
958
|
+
? entry.path.slice(sharedDirRel.length + 1)
|
|
959
|
+
: entry.path;
|
|
960
|
+
const outPath = path.resolve(path.join(sharedTargetDir, relInside));
|
|
961
|
+
if (!outPath.startsWith(path.resolve(sharedTargetDir) + path.sep)) continue;
|
|
962
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
963
|
+
fs.writeFileSync(outPath, body);
|
|
964
|
+
fetchedCount++;
|
|
965
|
+
} catch (e: any) {
|
|
966
|
+
errors.push(`fetch shared ${entry.path}: ${e?.message ?? e}`);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
for (const scriptDir of scriptDirs) {
|
|
973
|
+
// GitHub Contents API returns the file list under the dir. We
|
|
974
|
+
// recurse into subdirectories so e.g. scripts/v1/helpers/foo.js is
|
|
975
|
+
// captured too.
|
|
976
|
+
const stack: string[] = [`${subdir}/${scriptDir}`];
|
|
977
|
+
while (stack.length) {
|
|
978
|
+
const relDir = stack.pop()!;
|
|
979
|
+
const apiUrl = `https://api.github.com/repos/${repo}/contents/${encodeURI(relDir)}?ref=${encodeURIComponent(ref)}`;
|
|
980
|
+
let entries: Array<{ type: string; path: string; name: string; download_url: string | null }>;
|
|
981
|
+
try {
|
|
982
|
+
entries = await fetchJson(apiUrl, 15000);
|
|
983
|
+
} catch (e: any) {
|
|
984
|
+
errors.push(`list ${relDir}: ${e?.message ?? e}`);
|
|
985
|
+
continue;
|
|
986
|
+
}
|
|
987
|
+
if (!Array.isArray(entries)) {
|
|
988
|
+
errors.push(`list ${relDir}: unexpected response shape`);
|
|
989
|
+
continue;
|
|
990
|
+
}
|
|
991
|
+
for (const entry of entries) {
|
|
992
|
+
if (entry.type === 'dir') {
|
|
993
|
+
stack.push(entry.path);
|
|
994
|
+
continue;
|
|
995
|
+
}
|
|
996
|
+
if (entry.type !== 'file' || !entry.download_url) continue;
|
|
997
|
+
try {
|
|
998
|
+
const body = await fetchBinary(entry.download_url, 30000);
|
|
999
|
+
// entry.path is relative to repo root → strip the repo subdir prefix
|
|
1000
|
+
// so the path inside targetDir matches the layout the loader expects.
|
|
1001
|
+
const relInsideProvider = entry.path.startsWith(subdir + '/')
|
|
1002
|
+
? entry.path.slice(subdir.length + 1)
|
|
1003
|
+
: entry.path;
|
|
1004
|
+
const outPath = path.resolve(path.join(targetDir, relInsideProvider));
|
|
1005
|
+
// Path-traversal guard.
|
|
1006
|
+
if (!outPath.startsWith(path.resolve(targetDir) + path.sep)) {
|
|
1007
|
+
errors.push(`refusing to write outside targetDir: ${entry.path}`);
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
1010
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
1011
|
+
fs.writeFileSync(outPath, body);
|
|
1012
|
+
fetchedCount++;
|
|
1013
|
+
} catch (e: any) {
|
|
1014
|
+
errors.push(`fetch ${entry.path}: ${e?.message ?? e}`);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
return { fetchedCount, source: `${repo}@${ref}`, errors };
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
/**
|
|
1024
|
+
* Remove a provider manifest from the upstream cache root
|
|
1025
|
+
* (~/.adhdev/providers/.upstream/{category}/{type}/). Refuses to touch
|
|
1026
|
+
* anything outside that root. Used by onboarding to opt out of a
|
|
1027
|
+
* provider the user doesn't want; the dashboard no longer exposes a
|
|
1028
|
+
* per-provider uninstall button (external sources are removed as a
|
|
1029
|
+
* whole via remove_provider_source).
|
|
1030
|
+
*/
|
|
1031
|
+
private async handleUninstallProviderManifest(args: any): Promise<CommandResult> {
|
|
1032
|
+
const type = typeof args?.type === 'string' ? args.type : '';
|
|
1033
|
+
const category = typeof args?.category === 'string' ? args.category : '';
|
|
1034
|
+
if (!type || !category) return { success: false, error: 'type and category are required' };
|
|
1035
|
+
if (!/^[a-z0-9][a-z0-9_-]{0,63}$/i.test(type)) {
|
|
1036
|
+
return { success: false, error: 'invalid type' };
|
|
1037
|
+
}
|
|
1038
|
+
if (!['cli', 'ide', 'extension', 'acp'].includes(category)) {
|
|
1039
|
+
return { success: false, error: `unknown category: ${category}` };
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
const fs = require('fs') as typeof import('fs');
|
|
1043
|
+
const path = require('path') as typeof import('path');
|
|
1044
|
+
|
|
1045
|
+
try {
|
|
1046
|
+
const installRoot = this.getUpstreamInstallRoot();
|
|
1047
|
+
const installRootResolved = path.resolve(installRoot);
|
|
1048
|
+
const targetDir = path.resolve(path.join(installRoot, category, type));
|
|
1049
|
+
|
|
1050
|
+
if (!targetDir.startsWith(installRootResolved + path.sep)) {
|
|
1051
|
+
return { success: false, error: 'refusing to delete outside upstream root' };
|
|
1052
|
+
}
|
|
1053
|
+
if (!fs.existsSync(targetDir)) {
|
|
1054
|
+
return { success: false, error: 'not installed' };
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
1058
|
+
|
|
1059
|
+
if (this._ctx.providerLoader) {
|
|
1060
|
+
this._ctx.providerLoader.reload();
|
|
1061
|
+
this._ctx.providerLoader.registerToDetector();
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
return { success: true, removed: { type, category, path: targetDir } };
|
|
1065
|
+
} catch (e: any) {
|
|
1066
|
+
return { success: false, error: `uninstall failed: ${e?.message || e}` };
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
/**
|
|
1071
|
+
* Return everything currently installed in the upstream cache with its
|
|
1072
|
+
* version. This is the "what does this daemon have" answer used both by
|
|
1073
|
+
* the UI and by the update checker.
|
|
1074
|
+
*/
|
|
1075
|
+
private handleListInstalledProviders(_args: any): CommandResult {
|
|
1076
|
+
const fs = require('fs') as typeof import('fs');
|
|
1077
|
+
const path = require('path') as typeof import('path');
|
|
1078
|
+
|
|
1079
|
+
const installRoot = this.getUpstreamInstallRoot();
|
|
1080
|
+
if (!fs.existsSync(installRoot)) return { success: true, providers: [] };
|
|
1081
|
+
|
|
1082
|
+
const CATEGORIES = ['cli', 'ide', 'extension', 'acp'] as const;
|
|
1083
|
+
const items: Array<{ type: string; category: string; version: string; path: string }> = [];
|
|
1084
|
+
|
|
1085
|
+
for (const category of CATEGORIES) {
|
|
1086
|
+
const categoryDir = path.join(installRoot, category);
|
|
1087
|
+
if (!fs.existsSync(categoryDir)) continue;
|
|
1088
|
+
let entries: string[];
|
|
1089
|
+
try { entries = fs.readdirSync(categoryDir); } catch { continue; }
|
|
1090
|
+
for (const type of entries) {
|
|
1091
|
+
// v1 manifest takes precedence over v0 when both are present.
|
|
1092
|
+
const v1Path = path.join(categoryDir, type, 'provider.v1.json');
|
|
1093
|
+
const v0Path = path.join(categoryDir, type, 'provider.json');
|
|
1094
|
+
const manifestPath = fs.existsSync(v1Path) ? v1Path : (fs.existsSync(v0Path) ? v0Path : null);
|
|
1095
|
+
if (!manifestPath) continue;
|
|
1096
|
+
try {
|
|
1097
|
+
const m = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
1098
|
+
items.push({
|
|
1099
|
+
type,
|
|
1100
|
+
category,
|
|
1101
|
+
version: typeof m.providerVersion === 'string' ? m.providerVersion : '0.0.0',
|
|
1102
|
+
path: manifestPath,
|
|
1103
|
+
});
|
|
1104
|
+
} catch {
|
|
1105
|
+
// Corrupt manifest — skip but don't fail the whole listing.
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
return { success: true, providers: items };
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
/**
|
|
1113
|
+
* For each installed provider, ask the registry for its current latest
|
|
1114
|
+
* version and report whether an update is available. The user can then
|
|
1115
|
+
* call install_provider_manifest to upgrade (it overwrites the file).
|
|
1116
|
+
*
|
|
1117
|
+
* Returns { providers: [{ type, category, installedVersion, latestVersion,
|
|
1118
|
+
* updateAvailable, error? }] }
|
|
1119
|
+
*/
|
|
1120
|
+
private async handleCheckProviderUpdates(_args: any): Promise<CommandResult> {
|
|
1121
|
+
const installed = this.handleListInstalledProviders({});
|
|
1122
|
+
if (!installed.success) return installed;
|
|
1123
|
+
|
|
1124
|
+
const https = require('https') as typeof import('https');
|
|
1125
|
+
const REGISTRY = resolveRegistryBaseUrl(loadConfig().registryUrl);
|
|
1126
|
+
|
|
1127
|
+
function fetchJson(url: string): Promise<any> {
|
|
1128
|
+
return new Promise((resolve, reject) => {
|
|
1129
|
+
const req = https.get(url, { headers: { 'User-Agent': 'adhdev-daemon', 'Accept': 'application/json' }, timeout: 10000 }, (res) => {
|
|
1130
|
+
if (res.statusCode !== 200) { reject(new Error(`HTTP ${res.statusCode}`)); return; }
|
|
1131
|
+
const chunks: Buffer[] = [];
|
|
1132
|
+
res.on('data', (c: Buffer) => chunks.push(c));
|
|
1133
|
+
res.on('end', () => {
|
|
1134
|
+
try { resolve(JSON.parse(Buffer.concat(chunks).toString('utf-8'))); }
|
|
1135
|
+
catch (e) { reject(e); }
|
|
1136
|
+
});
|
|
1137
|
+
});
|
|
1138
|
+
req.on('error', reject);
|
|
1139
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
const installedList = (installed as unknown as { providers: Array<{ type: string; category: string; version: string }> }).providers;
|
|
1144
|
+
const checks = await Promise.all(
|
|
1145
|
+
installedList.map(async (p) => {
|
|
1146
|
+
try {
|
|
1147
|
+
const remote = await fetchJson(`${REGISTRY}/providers/${encodeURIComponent(p.type)}`);
|
|
1148
|
+
const latestVersion = String(remote?.version ?? '');
|
|
1149
|
+
return {
|
|
1150
|
+
type: p.type,
|
|
1151
|
+
category: p.category,
|
|
1152
|
+
installedVersion: p.version,
|
|
1153
|
+
latestVersion,
|
|
1154
|
+
updateAvailable: latestVersion !== '' && latestVersion !== p.version,
|
|
1155
|
+
};
|
|
1156
|
+
} catch (e: any) {
|
|
1157
|
+
return {
|
|
1158
|
+
type: p.type,
|
|
1159
|
+
category: p.category,
|
|
1160
|
+
installedVersion: p.version,
|
|
1161
|
+
latestVersion: null,
|
|
1162
|
+
updateAvailable: false,
|
|
1163
|
+
error: e?.message ?? String(e),
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
})
|
|
1167
|
+
);
|
|
1168
|
+
|
|
1169
|
+
return { success: true, providers: checks };
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
// ─── External provider sources (3rd-party git URLs) ──────────────
|
|
1173
|
+
|
|
1174
|
+
/**
|
|
1175
|
+
* Register a new external provider source. The daemon clones the repo
|
|
1176
|
+
* to ~/.adhdev/external/<name>/, walks it once to detect provided
|
|
1177
|
+
* types, and surfaces any conflicts with already-installed types so
|
|
1178
|
+
* the dashboard can ask the user how to resolve them.
|
|
1179
|
+
*
|
|
1180
|
+
* Args: { url: string, ref?: string, name?: string }
|
|
1181
|
+
* - url: https://, git@, or any git-cloneable URL
|
|
1182
|
+
* - ref: branch/tag/commit (default "main")
|
|
1183
|
+
* - name: short identifier (default derived from URL)
|
|
1184
|
+
*
|
|
1185
|
+
* Returns: { source, providers, conflicts }
|
|
1186
|
+
* - conflicts: list of types this new source provides that another
|
|
1187
|
+
* source already exposes. UI uses this to prompt for active-source
|
|
1188
|
+
* selection before the load takes effect.
|
|
1189
|
+
*/
|
|
1190
|
+
private async handleAddProviderSource(args: any): Promise<CommandResult> {
|
|
1191
|
+
const url = typeof args?.url === 'string' ? args.url.trim() : '';
|
|
1192
|
+
if (!url) return { success: false, error: 'url is required' };
|
|
1193
|
+
const ref = typeof args?.ref === 'string' && args.ref.trim() ? args.ref.trim() : 'main';
|
|
1194
|
+
|
|
1195
|
+
// Defense in depth against argv flag-smuggling: reject anything that
|
|
1196
|
+
// looks like a git option in either positional. The `--` end-of-options
|
|
1197
|
+
// sentinel below catches accidental cases, but rejecting early gives
|
|
1198
|
+
// a clear error message and stops obviously malicious inputs from
|
|
1199
|
+
// even touching git.
|
|
1200
|
+
if (url.startsWith('-')) return { success: false, error: 'url must not start with "-"' };
|
|
1201
|
+
if (ref.startsWith('-')) return { success: false, error: 'ref must not start with "-"' };
|
|
1202
|
+
// Whitelist the protocols we'll forward to git. Anything else
|
|
1203
|
+
// (file://, ext-protocol-handlers, …) is refused outright.
|
|
1204
|
+
if (!/^(https?:\/\/|git@[a-z0-9._-]+:)[a-z0-9._@:/~\-]+$/i.test(url)) {
|
|
1205
|
+
return { success: false, error: 'url must be https://… or git@host:… and contain only URL-safe characters' };
|
|
1206
|
+
}
|
|
1207
|
+
// Refs are git refnames — letters, digits, slashes, dots, underscores,
|
|
1208
|
+
// dashes. Rejects e.g. spaces, semicolons, backticks, shell metas.
|
|
1209
|
+
if (!/^[A-Za-z0-9._/-]+$/.test(ref)) {
|
|
1210
|
+
return { success: false, error: 'ref must contain only [A-Za-z0-9._/-]' };
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
const ext = require('../providers/external-sources.js') as typeof import('../providers/external-sources.js');
|
|
1214
|
+
const requestedName = typeof args?.name === 'string' && args.name.trim() ? args.name.trim() : ext.deriveSourceName(url);
|
|
1215
|
+
if (!/^@[a-z0-9_-]+$/i.test(requestedName)) {
|
|
1216
|
+
return { success: false, error: 'name must match @[a-z0-9_-]+' };
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
const fs = require('node:fs') as typeof import('node:fs');
|
|
1220
|
+
const path = require('node:path') as typeof import('node:path');
|
|
1221
|
+
const { spawnSync } = require('node:child_process') as typeof import('node:child_process');
|
|
1222
|
+
|
|
1223
|
+
const file = ext.loadExternalSources();
|
|
1224
|
+
if (file.sources.some(s => s.name === requestedName)) {
|
|
1225
|
+
return { success: false, error: `source name "${requestedName}" is already registered` };
|
|
1226
|
+
}
|
|
1227
|
+
if (file.sources.some(s => s.url === url && s.ref === ref)) {
|
|
1228
|
+
return { success: false, error: `source url+ref already registered (use a different name to track another ref)` };
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
const sourceDir = path.join(ext.externalRoot(), requestedName);
|
|
1232
|
+
if (!fs.existsSync(ext.externalRoot())) fs.mkdirSync(ext.externalRoot(), { recursive: true });
|
|
1233
|
+
if (fs.existsSync(sourceDir)) {
|
|
1234
|
+
return { success: false, error: `directory already exists: ${sourceDir} (rename or remove first)` };
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
// `--` sentinel after the option list so any future regex-bypassing
|
|
1238
|
+
// url that *did* start with `-` would still be treated as a path
|
|
1239
|
+
// by git rather than an option.
|
|
1240
|
+
const clone = spawnSync('git', ['clone', '--depth=1', '--branch', ref, '--', url, sourceDir], {
|
|
1241
|
+
encoding: 'utf-8',
|
|
1242
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1243
|
+
timeout: 60_000,
|
|
1244
|
+
});
|
|
1245
|
+
if (clone.status !== 0) {
|
|
1246
|
+
try { fs.rmSync(sourceDir, { recursive: true, force: true }); } catch { /* best-effort */ }
|
|
1247
|
+
return { success: false, error: `git clone failed: ${(clone.stderr || clone.stdout || '').trim() || 'unknown error'}` };
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
const source: import('../providers/external-sources.js').ExternalSource = {
|
|
1251
|
+
name: requestedName,
|
|
1252
|
+
url,
|
|
1253
|
+
ref,
|
|
1254
|
+
addedAt: new Date().toISOString(),
|
|
1255
|
+
};
|
|
1256
|
+
ext.saveExternalSources({ schema: 1, sources: [...file.sources, source] });
|
|
1257
|
+
|
|
1258
|
+
// Detect type-level conflicts with what's already on disk after this clone.
|
|
1259
|
+
const inventory = ext.inventoryExternalSources();
|
|
1260
|
+
const conflicts: { category: string; type: string; sources: string[] }[] = [];
|
|
1261
|
+
const newEntry = inventory.find(e => e.sourceName === requestedName);
|
|
1262
|
+
if (newEntry) {
|
|
1263
|
+
for (const [category, types] of Object.entries(newEntry.providers)) {
|
|
1264
|
+
for (const type of types) {
|
|
1265
|
+
const sources = ext.sourcesProviding(category, type);
|
|
1266
|
+
if (sources.length > 1) conflicts.push({ category, type, sources });
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
// Hot-reload so the daemon picks up the new providers immediately.
|
|
1272
|
+
if (this._ctx.providerLoader) {
|
|
1273
|
+
this._ctx.providerLoader.reload();
|
|
1274
|
+
this._ctx.providerLoader.registerToDetector();
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
return {
|
|
1278
|
+
success: true,
|
|
1279
|
+
source,
|
|
1280
|
+
providers: newEntry?.providers ?? {},
|
|
1281
|
+
conflicts,
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
/**
|
|
1286
|
+
* Remove a registered external source. Deletes the clone directory and
|
|
1287
|
+
* any active-source entry pointing to it.
|
|
1288
|
+
*
|
|
1289
|
+
* Args: { name: string }
|
|
1290
|
+
*/
|
|
1291
|
+
private async handleRemoveProviderSource(args: any): Promise<CommandResult> {
|
|
1292
|
+
const name = typeof args?.name === 'string' ? args.name.trim() : '';
|
|
1293
|
+
if (!name) return { success: false, error: 'name is required' };
|
|
1294
|
+
const ext = require('../providers/external-sources.js') as typeof import('../providers/external-sources.js');
|
|
1295
|
+
|
|
1296
|
+
const fs = require('node:fs') as typeof import('node:fs');
|
|
1297
|
+
const path = require('node:path') as typeof import('node:path');
|
|
1298
|
+
const file = ext.loadExternalSources();
|
|
1299
|
+
const match = file.sources.find(s => s.name === name);
|
|
1300
|
+
if (!match) return { success: false, error: `source "${name}" not registered` };
|
|
1301
|
+
|
|
1302
|
+
const sourceDir = path.join(ext.externalRoot(), name);
|
|
1303
|
+
if (fs.existsSync(sourceDir)) {
|
|
1304
|
+
try { fs.rmSync(sourceDir, { recursive: true, force: true }); }
|
|
1305
|
+
catch (e: any) { return { success: false, error: `failed to delete ${sourceDir}: ${e?.message || e}` }; }
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
ext.saveExternalSources({
|
|
1309
|
+
schema: 1,
|
|
1310
|
+
sources: file.sources.filter(s => s.name !== name),
|
|
1311
|
+
});
|
|
1312
|
+
|
|
1313
|
+
// Drop any active-source entries that pointed at this source.
|
|
1314
|
+
const active = ext.loadProvidersActive();
|
|
1315
|
+
const filteredActive: Record<string, string> = {};
|
|
1316
|
+
for (const [type, src] of Object.entries(active.active)) {
|
|
1317
|
+
if (src !== name) filteredActive[type] = src;
|
|
1318
|
+
}
|
|
1319
|
+
ext.saveProvidersActive({ schema: 1, active: filteredActive });
|
|
1320
|
+
|
|
1321
|
+
if (this._ctx.providerLoader) {
|
|
1322
|
+
this._ctx.providerLoader.reload();
|
|
1323
|
+
this._ctx.providerLoader.registerToDetector();
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
return { success: true, removed: { name } };
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
/**
|
|
1330
|
+
* List registered external sources + each source's currently installed
|
|
1331
|
+
* providers + the active selection for any conflicting types. Used by
|
|
1332
|
+
* the dashboard's "Sources" tab.
|
|
1333
|
+
*/
|
|
1334
|
+
private handleListProviderSources(_args: any): CommandResult {
|
|
1335
|
+
const ext = require('../providers/external-sources.js') as typeof import('../providers/external-sources.js');
|
|
1336
|
+
const file = ext.loadExternalSources();
|
|
1337
|
+
const inventory = ext.inventoryExternalSources();
|
|
1338
|
+
const active = ext.loadProvidersActive();
|
|
1339
|
+
|
|
1340
|
+
// Build a per-source view + flag types that have ambiguity.
|
|
1341
|
+
const sources = file.sources.map(s => {
|
|
1342
|
+
const inv = inventory.find(e => e.sourceName === s.name);
|
|
1343
|
+
return {
|
|
1344
|
+
...s,
|
|
1345
|
+
providers: inv?.providers ?? {},
|
|
1346
|
+
};
|
|
1347
|
+
});
|
|
1348
|
+
|
|
1349
|
+
// Compute conflicts globally — any type provided by ≥ 2 sources.
|
|
1350
|
+
const conflictMap = new Map<string, { category: string; sources: string[] }>();
|
|
1351
|
+
for (const inv of inventory) {
|
|
1352
|
+
for (const [category, types] of Object.entries(inv.providers)) {
|
|
1353
|
+
for (const type of types) {
|
|
1354
|
+
const candidates = ext.sourcesProviding(category, type);
|
|
1355
|
+
if (candidates.length > 1 && !conflictMap.has(type)) {
|
|
1356
|
+
conflictMap.set(type, { category, sources: candidates });
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
const conflicts = [...conflictMap.entries()].map(([type, info]) => ({
|
|
1362
|
+
type,
|
|
1363
|
+
category: info.category,
|
|
1364
|
+
candidates: info.sources,
|
|
1365
|
+
active: active.active[type] ?? null,
|
|
1366
|
+
}));
|
|
1367
|
+
|
|
1368
|
+
return { success: true, sources, conflicts };
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
/**
|
|
1372
|
+
* Pick which source's copy of a conflicting provider type is active.
|
|
1373
|
+
* Other sources' copies stay on disk but the loader ignores them.
|
|
1374
|
+
*
|
|
1375
|
+
* Args: { type: string, sourceName: string }
|
|
1376
|
+
*/
|
|
1377
|
+
private handleSetActiveProviderSource(args: any): CommandResult {
|
|
1378
|
+
const type = typeof args?.type === 'string' ? args.type.trim() : '';
|
|
1379
|
+
const sourceName = typeof args?.sourceName === 'string' ? args.sourceName.trim() : '';
|
|
1380
|
+
if (!type || !sourceName) return { success: false, error: 'type and sourceName are required' };
|
|
1381
|
+
const ext = require('../providers/external-sources.js') as typeof import('../providers/external-sources.js');
|
|
1382
|
+
|
|
1383
|
+
// Validate: the source must actually provide that type.
|
|
1384
|
+
const inventory = ext.inventoryExternalSources();
|
|
1385
|
+
const entry = inventory.find(e => e.sourceName === sourceName);
|
|
1386
|
+
if (!entry) return { success: false, error: `source "${sourceName}" not found` };
|
|
1387
|
+
const provided = Object.values(entry.providers).some(types => types.includes(type));
|
|
1388
|
+
if (!provided) return { success: false, error: `source "${sourceName}" does not provide type "${type}"` };
|
|
1389
|
+
|
|
1390
|
+
const active = ext.loadProvidersActive();
|
|
1391
|
+
active.active[type] = sourceName;
|
|
1392
|
+
ext.saveProvidersActive(active);
|
|
1393
|
+
|
|
1394
|
+
if (this._ctx.providerLoader) {
|
|
1395
|
+
this._ctx.providerLoader.reload();
|
|
1396
|
+
this._ctx.providerLoader.registerToDetector();
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
return { success: true, type, sourceName };
|
|
1400
|
+
}
|
|
1401
|
+
|
|
559
1402
|
// ─── DevServer HTTP proxy helpers ─────────────────
|
|
560
1403
|
// These bridge WS commands to the DevServer REST API (localhost:19280)
|
|
561
1404
|
|