@phuetz/code-buddy 2.0.0 → 2.2.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.fr.md +56 -0
- package/README.md +74 -16
- package/codebuddy-runtime.json +5 -5
- package/dist/agent/codebuddy-agent.d.ts +18 -0
- package/dist/agent/codebuddy-agent.js +45 -8
- package/dist/agent/execution/agent-executor.d.ts +0 -9
- package/dist/agent/execution/agent-executor.js +176 -12
- package/dist/agent/execution/context-pipeline.d.ts +9 -0
- package/dist/agent/execution/context-pipeline.js +64 -2
- package/dist/agent/execution/tool-loop-guard.d.ts +76 -0
- package/dist/agent/execution/tool-loop-guard.js +165 -0
- package/dist/agent/execution/tool-selection-strategy.d.ts +7 -0
- package/dist/agent/execution/tool-selection-strategy.js +32 -2
- package/dist/agent/facades/session-facade.d.ts +0 -6
- package/dist/agent/facades/session-facade.js +0 -45
- package/dist/agent/hermes-claw-migrate.d.ts +0 -21
- package/dist/agent/hermes-claw-migrate.js +9 -4
- package/dist/agent/hermes-protocol-gateways.js +8 -1
- package/dist/agent/repo-profiler.js +17 -4
- package/dist/agent/self-improvement/authored-tool-runtime.js +10 -2
- package/dist/agent/self-improvement/evolution/ast-novelty.d.ts +5 -0
- package/dist/agent/self-improvement/evolution/ast-novelty.js +58 -23
- package/dist/agent/self-improvement/evolution/code-variant-store.d.ts +3 -0
- package/dist/agent/self-improvement/evolution/evolution-engine.js +2 -0
- package/dist/agent/self-improvement/evolution/protected-paths.js +6 -0
- package/dist/agent/self-improvement/evolution/variant-fitness.d.ts +13 -2
- package/dist/agent/self-improvement/evolution/variant-fitness.js +184 -58
- package/dist/agent/self-improvement/evolution/worktree-scorer.d.ts +2 -0
- package/dist/agent/self-improvement/evolution/worktree-scorer.js +15 -0
- package/dist/agent/self-improvement/evolutionary-archive.d.ts +2 -1
- package/dist/agent/self-improvement/evolutionary-archive.js +34 -5
- package/dist/agent/self-improvement/proposal-store.d.ts +1 -0
- package/dist/agent/self-improvement/proposal-store.js +79 -17
- package/dist/agent/self-improvement/skill-apply-journal.d.ts +44 -0
- package/dist/agent/self-improvement/skill-apply-journal.js +178 -0
- package/dist/agent/self-improvement/skill-behavior-benchmark.d.ts +3 -0
- package/dist/agent/self-improvement/skill-behavior-benchmark.js +19 -0
- package/dist/agent/self-improvement/skill-behavior-gate.d.ts +34 -0
- package/dist/agent/self-improvement/skill-behavior-gate.js +96 -0
- package/dist/agent/self-improvement/skill-engine.d.ts +22 -0
- package/dist/agent/self-improvement/skill-engine.js +385 -13
- package/dist/agent/self-improvement/skill-gate.d.ts +2 -0
- package/dist/agent/self-improvement/skill-gate.js +17 -2
- package/dist/agent/self-improvement/skill-mutator.d.ts +6 -1
- package/dist/agent/self-improvement/skill-mutator.js +51 -15
- package/dist/agent/self-improvement/skill-types.d.ts +2 -1
- package/dist/agent/self-improvement/types.d.ts +11 -0
- package/dist/agent/tool-handler.d.ts +1 -1
- package/dist/agent/tool-handler.js +28 -9
- package/dist/auth/profile-manager.js +1 -1
- package/dist/channels/companion-channel-turn.d.ts +53 -4
- package/dist/channels/companion-channel-turn.js +218 -16
- package/dist/channels/slash-parity.d.ts +4 -6
- package/dist/channels/slash-parity.js +9 -44
- package/dist/cli/session-commands.d.ts +9 -0
- package/dist/cli/session-commands.js +45 -5
- package/dist/cli/session-picker.d.ts +43 -0
- package/dist/cli/session-picker.js +128 -0
- package/dist/codebuddy/a2a-call-tool-defs.d.ts +26 -0
- package/dist/codebuddy/a2a-call-tool-defs.js +12 -0
- package/dist/codebuddy/fleet-tool-defs.d.ts +2 -0
- package/dist/codebuddy/fleet-tool-defs.js +55 -0
- package/dist/codebuddy/providers/chatgpt-headers.d.ts +18 -0
- package/dist/codebuddy/providers/chatgpt-headers.js +37 -0
- package/dist/codebuddy/providers/provider-chatgpt-responses.d.ts +2 -1
- package/dist/codebuddy/providers/provider-chatgpt-responses.js +10 -23
- package/dist/codebuddy/providers/provider-openai-compat.d.ts +7 -0
- package/dist/codebuddy/providers/provider-openai-compat.js +29 -1
- package/dist/codebuddy/ragchat-tool-defs.d.ts +31 -0
- package/dist/codebuddy/ragchat-tool-defs.js +19 -0
- package/dist/codebuddy/resource-catalog-tool-defs.d.ts +28 -0
- package/dist/codebuddy/resource-catalog-tool-defs.js +18 -0
- package/dist/codebuddy/tool-definitions/agent-tools.js +2 -2
- package/dist/codebuddy/tool-definitions/code-exec-tools.js +5 -1
- package/dist/codebuddy/tool-definitions/cron-tools.js +6 -0
- package/dist/codebuddy/tool-definitions/self-describe-tools.js +6 -1
- package/dist/codebuddy/tools.d.ts +0 -6
- package/dist/codebuddy/tools.js +25 -3
- package/dist/commands/cli/daemon-commands.js +3 -1
- package/dist/commands/cli/evolve-command.js +21 -5
- package/dist/commands/cli/fleet-collaboration-commands.d.ts +2 -0
- package/dist/commands/cli/fleet-collaboration-commands.js +57 -0
- package/dist/commands/cli/fleet-commands.js +29 -0
- package/dist/commands/cli/fleet-mission-commands.d.ts +3 -0
- package/dist/commands/cli/fleet-mission-commands.js +72 -0
- package/dist/commands/cli/fleet-rooms-commands.d.ts +10 -0
- package/dist/commands/cli/fleet-rooms-commands.js +289 -0
- package/dist/commands/cli/tools-commands.js +2 -1
- package/dist/commands/cli/triage-command.d.ts +8 -0
- package/dist/commands/cli/triage-command.js +72 -0
- package/dist/commands/cli/utility-commands.js +25 -1
- package/dist/commands/client-dispatcher.d.ts +7 -0
- package/dist/commands/client-dispatcher.js +73 -21
- package/dist/commands/cron-cli/index.d.ts +3 -1
- package/dist/commands/cron-cli/index.js +25 -0
- package/dist/commands/device-auth.d.ts +10 -0
- package/dist/commands/device-auth.js +91 -0
- package/dist/commands/enhanced-command-handler.d.ts +9 -0
- package/dist/commands/enhanced-command-handler.js +28 -6
- package/dist/commands/handlers/agent-handlers.d.ts +1 -1
- package/dist/commands/handlers/agent-handlers.js +4 -4
- package/dist/commands/handlers/channel-handlers.js +53 -2
- package/dist/commands/handlers/export-handlers.d.ts +3 -2
- package/dist/commands/handlers/export-handlers.js +55 -28
- package/dist/commands/handlers/extra-handlers.js +5 -1
- package/dist/commands/handlers/grill-me-handler.js +26 -18
- package/dist/commands/handlers/memory-handlers.d.ts +6 -2
- package/dist/commands/handlers/memory-handlers.js +35 -15
- package/dist/commands/handlers/missing-handlers.d.ts +2 -2
- package/dist/commands/handlers/missing-handlers.js +12 -4
- package/dist/commands/handlers/resources-handler.d.ts +12 -0
- package/dist/commands/handlers/resources-handler.js +52 -0
- package/dist/commands/handlers/starter-handlers.d.ts +1 -1
- package/dist/commands/handlers/starter-handlers.js +32 -2
- package/dist/commands/handlers/switch-handler.d.ts +1 -1
- package/dist/commands/handlers/switch-handler.js +15 -0
- package/dist/commands/handlers/test-handlers.js +4 -86
- package/dist/commands/handlers/ui-handlers.js +11 -5
- package/dist/commands/login-chatgpt.d.ts +8 -0
- package/dist/commands/login-chatgpt.js +17 -0
- package/dist/commands/mcp-import.d.ts +11 -0
- package/dist/commands/mcp-import.js +67 -0
- package/dist/commands/mcp-invoke.d.ts +2 -0
- package/dist/commands/mcp-invoke.js +68 -0
- package/dist/commands/mcp.d.ts +13 -0
- package/dist/commands/mcp.js +66 -21
- package/dist/commands/pipeline.d.ts +7 -1
- package/dist/commands/pipeline.js +104 -7
- package/dist/commands/resources.d.ts +2 -0
- package/dist/commands/resources.js +64 -0
- package/dist/commands/skills-cli/index.js +35 -12
- package/dist/commands/slash/builtin-commands.js +16 -3
- package/dist/commands/slash/index.d.ts +3 -1
- package/dist/commands/slash/index.js +2 -0
- package/dist/commands/slash/surfaces.d.ts +53 -0
- package/dist/commands/slash/surfaces.js +177 -0
- package/dist/commands/slash/types.d.ts +17 -0
- package/dist/commands/try.d.ts +1 -1
- package/dist/commands/try.js +1 -1
- package/dist/companion/companion-identity.d.ts +43 -0
- package/dist/companion/companion-identity.js +136 -0
- package/dist/companion/companion-toolset.d.ts +66 -0
- package/dist/companion/companion-toolset.js +368 -0
- package/dist/companion/companion-turn.d.ts +10 -2
- package/dist/companion/companion-turn.js +38 -1
- package/dist/config/code-exec-policy.d.ts +25 -0
- package/dist/config/code-exec-policy.js +39 -0
- package/dist/config/model-tools.d.ts +9 -0
- package/dist/config/model-tools.js +103 -0
- package/dist/context/context-manager-v2.js +2 -2
- package/dist/context/lm-resizer-compressor.js +3 -2
- package/dist/context/lm-resizer-diagnostics.d.ts +9 -0
- package/dist/context/lm-resizer-diagnostics.js +30 -0
- package/dist/context/tool-output-masking.js +3 -4
- package/dist/curator/curator.d.ts +6 -0
- package/dist/curator/curator.js +53 -3
- package/dist/daemon/cron-agent-bridge.d.ts +6 -0
- package/dist/daemon/cron-agent-bridge.js +31 -4
- package/dist/doctor/index.d.ts +11 -2
- package/dist/doctor/index.js +84 -21
- package/dist/doctor/integrations.d.ts +197 -0
- package/dist/doctor/integrations.js +241 -0
- package/dist/doctor/triage.d.ts +108 -0
- package/dist/doctor/triage.js +304 -0
- package/dist/errors/crash-handler.js +2 -0
- package/dist/fleet/collaboration.d.ts +44 -0
- package/dist/fleet/collaboration.js +170 -0
- package/dist/fleet/fleet-listener.d.ts +1 -0
- package/dist/fleet/fleet-listener.js +87 -46
- package/dist/fleet/fleet-registry.d.ts +2 -0
- package/dist/fleet/resource-catalog.d.ts +245 -0
- package/dist/fleet/resource-catalog.js +211 -0
- package/dist/fleet/rooms/room-access.d.ts +138 -0
- package/dist/fleet/rooms/room-access.js +196 -0
- package/dist/fleet/rooms/room-client.d.ts +158 -0
- package/dist/fleet/rooms/room-client.js +685 -0
- package/dist/fleet/rooms/room-event.d.ts +139 -0
- package/dist/fleet/rooms/room-event.js +330 -0
- package/dist/fleet/rooms/room-filter.d.ts +48 -0
- package/dist/fleet/rooms/room-filter.js +152 -0
- package/dist/fleet/rooms/room-hub.d.ts +154 -0
- package/dist/fleet/rooms/room-hub.js +363 -0
- package/dist/fleet/rooms/room-identity.d.ts +26 -0
- package/dist/fleet/rooms/room-identity.js +82 -0
- package/dist/fleet/rooms/room-lock.d.ts +34 -0
- package/dist/fleet/rooms/room-lock.js +180 -0
- package/dist/fleet/rooms/room-observations.d.ts +7 -0
- package/dist/fleet/rooms/room-observations.js +61 -0
- package/dist/fleet/rooms/room-server.d.ts +28 -0
- package/dist/fleet/rooms/room-server.js +75 -0
- package/dist/fleet/rooms/room-store.d.ts +119 -0
- package/dist/fleet/rooms/room-store.js +448 -0
- package/dist/fleet/rooms/room-ws-bridge.d.ts +27 -0
- package/dist/fleet/rooms/room-ws-bridge.js +137 -0
- package/dist/fleet/sensory-mission-bridge.d.ts +42 -0
- package/dist/fleet/sensory-mission-bridge.js +136 -0
- package/dist/git/worktree-sessions.js +1 -0
- package/dist/harness/contract.d.ts +4 -4
- package/dist/harness/fleet-supervisor.d.ts +16 -0
- package/dist/harness/fleet-supervisor.js +64 -0
- package/dist/harness/index.d.ts +4 -0
- package/dist/harness/index.js +4 -0
- package/dist/harness/mission-runner.d.ts +3 -0
- package/dist/harness/mission-runner.js +53 -0
- package/dist/harness/mission-store.d.ts +93 -0
- package/dist/harness/mission-store.js +297 -0
- package/dist/harness/tool-harness.d.ts +56 -0
- package/dist/harness/tool-harness.js +153 -0
- package/dist/hooks/use-enhanced-input.js +48 -4
- package/dist/hooks/use-input-handler.d.ts +1 -0
- package/dist/hooks/use-input-handler.js +82 -22
- package/dist/hooks/use-input-history.js +1 -0
- package/dist/identity/lisa-introspection.js +5 -2
- package/dist/identity/operational-self-model.d.ts +2 -0
- package/dist/identity/operational-self-model.js +4 -0
- package/dist/index.js +52 -6
- package/dist/mcp/client.d.ts +11 -0
- package/dist/mcp/client.js +68 -3
- package/dist/mcp/config.d.ts +20 -0
- package/dist/mcp/config.js +121 -8
- package/dist/mcp/import-normalize.d.ts +12 -0
- package/dist/mcp/import-normalize.js +114 -0
- package/dist/mcp/mcp-oauth-constants.d.ts +8 -0
- package/dist/mcp/mcp-oauth-constants.js +9 -0
- package/dist/mcp/mcp-oauth-provider.d.ts +80 -0
- package/dist/mcp/mcp-oauth-provider.js +241 -0
- package/dist/mcp/mcp-oauth.d.ts +60 -1
- package/dist/mcp/mcp-oauth.js +241 -65
- package/dist/mcp/transports.d.ts +14 -5
- package/dist/mcp/transports.js +112 -101
- package/dist/mcp/types.d.ts +5 -0
- package/dist/media/comfyui-recipe-contract.d.ts +7 -7
- package/dist/memory/facts-memory.d.ts +11 -1
- package/dist/memory/facts-memory.js +47 -8
- package/dist/memory/memory-candidate-queue.d.ts +2 -2
- package/dist/memory/memory-candidate-queue.js +6 -6
- package/dist/memory/persistent-memory.d.ts +13 -8
- package/dist/memory/persistent-memory.js +77 -30
- package/dist/nodes/device-node.js +5 -9
- package/dist/observability/mobile-supervision-gateway-contract.js +20 -7
- package/dist/observability/run-event-writer.d.ts +21 -0
- package/dist/observability/run-event-writer.js +81 -0
- package/dist/observability/run-store.d.ts +5 -0
- package/dist/observability/run-store.js +18 -13
- package/dist/observability/run-trajectory.js +1 -1
- package/dist/optimization/prompt-cache.d.ts +19 -2
- package/dist/optimization/prompt-cache.js +41 -6
- package/dist/orchestration/orchestrator.d.ts +21 -0
- package/dist/orchestration/orchestrator.js +122 -15
- package/dist/persistence/session-content.d.ts +8 -0
- package/dist/persistence/session-content.js +76 -0
- package/dist/persistence/session-handoff.d.ts +64 -0
- package/dist/persistence/session-handoff.js +80 -0
- package/dist/persistence/session-history.d.ts +4 -0
- package/dist/persistence/session-history.js +38 -0
- package/dist/persistence/session-store.d.ts +9 -0
- package/dist/persistence/session-store.js +48 -20
- package/dist/personas/persona-manager.js +17 -1
- package/dist/plugins/code-explorer/CodeExplorerManager.d.ts +10 -4
- package/dist/plugins/code-explorer/CodeExplorerManager.js +69 -16
- package/dist/plugins/plugin-manager.js +10 -1
- package/dist/prompts/prompt-manager.js +11 -4
- package/dist/prompts/system-base.js +6 -2
- package/dist/protocols/a2a/codebuddy-executor.d.ts +11 -2
- package/dist/protocols/a2a/codebuddy-executor.js +9 -8
- package/dist/protocols/a2a/index.d.ts +1 -1
- package/dist/protocols/a2a/jsonrpc-v1.d.ts +41 -0
- package/dist/protocols/a2a/jsonrpc-v1.js +145 -0
- package/dist/protocols/a2a/peer-config.d.ts +20 -0
- package/dist/protocols/a2a/peer-config.js +55 -0
- package/dist/providers/codex-oauth.js +30 -6
- package/dist/providers/model-provider-compat.js +2 -1
- package/dist/sandbox/os-sandbox.d.ts +1 -1
- package/dist/scheduler/cron-scheduler.d.ts +15 -2
- package/dist/scheduler/cron-scheduler.js +35 -4
- package/dist/scheduler/job-notepad.d.ts +69 -0
- package/dist/scheduler/job-notepad.js +339 -0
- package/dist/security/audit-logger.d.ts +1 -1
- package/dist/security/compute-confinement.d.ts +4 -0
- package/dist/security/compute-confinement.js +89 -0
- package/dist/security/session-encryption.d.ts +4 -0
- package/dist/security/session-encryption.js +31 -0
- package/dist/sensory/voice-loop.d.ts +17 -0
- package/dist/sensory/voice-loop.js +72 -15
- package/dist/server/auth/device-session-context.d.ts +9 -0
- package/dist/server/auth/device-session-context.js +13 -0
- package/dist/server/auth/device-store.d.ts +89 -0
- package/dist/server/auth/device-store.js +261 -0
- package/dist/server/auth/device-token.d.ts +3 -0
- package/dist/server/auth/device-token.js +6 -0
- package/dist/server/auth/jwt.js +4 -0
- package/dist/server/index.d.ts +0 -10
- package/dist/server/index.js +28 -0
- package/dist/server/middleware/auth.js +3 -2
- package/dist/server/routes/a2a-jsonrpc.d.ts +9 -0
- package/dist/server/routes/a2a-jsonrpc.js +66 -0
- package/dist/server/routes/a2a-protocol.d.ts +12 -0
- package/dist/server/routes/a2a-protocol.js +1 -1
- package/dist/server/routes/device-auth.d.ts +4 -0
- package/dist/server/routes/device-auth.js +40 -0
- package/dist/server/types.d.ts +4 -0
- package/dist/server/websocket/handler.d.ts +13 -0
- package/dist/server/websocket/handler.js +102 -17
- package/dist/services/prompt-builder.d.ts +2 -0
- package/dist/services/prompt-builder.js +16 -6
- package/dist/services/runtime-settings-context.d.ts +81 -0
- package/dist/services/runtime-settings-context.js +79 -0
- package/dist/skills/executor.js +2 -0
- package/dist/skills/hub.js +16 -14
- package/dist/skills/local-inventory.d.ts +21 -0
- package/dist/skills/local-inventory.js +72 -0
- package/dist/skills/skill-loader.js +2 -2
- package/dist/skills/skill-usage-store.d.ts +49 -0
- package/dist/skills/skill-usage-store.js +121 -0
- package/dist/testing/ai-integration-tests.js +8 -5
- package/dist/themes/theme-manager.d.ts +12 -0
- package/dist/themes/theme-manager.js +29 -0
- package/dist/themes/theme-schema.d.ts +24 -24
- package/dist/tools/a2a-call-tool.d.ts +34 -0
- package/dist/tools/a2a-call-tool.js +75 -0
- package/dist/tools/bash/streaming-executor.d.ts +1 -0
- package/dist/tools/bash/streaming-executor.js +78 -90
- package/dist/tools/code-exec-preflight-runner.d.ts +41 -0
- package/dist/tools/code-exec-preflight-runner.js +254 -0
- package/dist/tools/code-exec-preflight.d.ts +44 -0
- package/dist/tools/code-exec-preflight.js +501 -0
- package/dist/tools/code-exec-tool.d.ts +48 -1
- package/dist/tools/code-exec-tool.js +191 -47
- package/dist/tools/comment-watcher.js +29 -12
- package/dist/tools/core-code-inspection.d.ts +56 -0
- package/dist/tools/core-code-inspection.js +86 -0
- package/dist/tools/create-skill-tool.d.ts +2 -3
- package/dist/tools/create-skill-tool.js +16 -75
- package/dist/tools/cronjob-tool.d.ts +1 -0
- package/dist/tools/cronjob-tool.js +1 -0
- package/dist/tools/env-doctor-tool.d.ts +2 -0
- package/dist/tools/env-doctor-tool.js +4 -3
- package/dist/tools/execute-code-rpc-invoker.d.ts +1 -1
- package/dist/tools/execute-code-rpc-invoker.js +35 -19
- package/dist/tools/execute-code-runner.d.ts +2 -0
- package/dist/tools/execute-code-runner.js +52 -17
- package/dist/tools/fleet-room-tool.d.ts +104 -0
- package/dist/tools/fleet-room-tool.js +124 -0
- package/dist/tools/integration-tool-hints.d.ts +4 -0
- package/dist/tools/integration-tool-hints.js +19 -0
- package/dist/tools/list-peers-tool.d.ts +3 -0
- package/dist/tools/list-peers-tool.js +11 -4
- package/dist/tools/media-generation-tool.d.ts +16 -1
- package/dist/tools/media-generation-tool.js +341 -9
- package/dist/tools/merge-conflict-tool.js +53 -25
- package/dist/tools/metadata.d.ts +1 -5
- package/dist/tools/metadata.js +30 -28
- package/dist/tools/peer-tool-invoke-tool.d.ts +61 -0
- package/dist/tools/peer-tool-invoke-tool.js +396 -0
- package/dist/tools/ragchat-tool.d.ts +39 -0
- package/dist/tools/ragchat-tool.js +105 -0
- package/dist/tools/registry/fleet-tools.d.ts +21 -3
- package/dist/tools/registry/fleet-tools.js +137 -4
- package/dist/tools/registry/index.d.ts +1 -1
- package/dist/tools/registry/index.js +2 -2
- package/dist/tools/registry/knowledge-tools.d.ts +2 -2
- package/dist/tools/registry/knowledge-tools.js +2 -2
- package/dist/tools/registry/lessons-tools.d.ts +7 -0
- package/dist/tools/registry/lessons-tools.js +43 -3
- package/dist/tools/registry/memory-tools.d.ts +1 -1
- package/dist/tools/registry/memory-tools.js +8 -8
- package/dist/tools/registry/research-tools.d.ts +1 -0
- package/dist/tools/registry/research-tools.js +4 -13
- package/dist/tools/registry/self-describe-tools.d.ts +1 -1
- package/dist/tools/registry/self-describe-tools.js +43 -30
- package/dist/tools/resource-catalog-tool.d.ts +38 -0
- package/dist/tools/resource-catalog-tool.js +46 -0
- package/dist/tools/skills-inspection-tool.js +30 -1
- package/dist/tools/stream-tool-output.d.ts +3 -0
- package/dist/tools/stream-tool-output.js +33 -0
- package/dist/tools/tool-call-scheduler.d.ts +10 -0
- package/dist/tools/tool-call-scheduler.js +58 -0
- package/dist/tools/tool-effect.d.ts +5 -0
- package/dist/tools/tool-effect.js +26 -0
- package/dist/tools/tool-search.d.ts +12 -11
- package/dist/tools/tool-search.js +67 -26
- package/dist/tools/types.d.ts +6 -0
- package/dist/ui/components/ChatHistory.d.ts +1 -1
- package/dist/ui/components/ChatHistory.js +2 -2
- package/dist/ui/components/ChatInput.d.ts +2 -1
- package/dist/ui/components/ChatInput.js +42 -88
- package/dist/ui/components/ChatInterface.d.ts +4 -2
- package/dist/ui/components/ChatInterface.js +27 -63
- package/dist/ui/components/CommandSuggestions.d.ts +0 -1
- package/dist/ui/components/CommandSuggestions.js +4 -6
- package/dist/ui/components/FileAutocomplete.js +1 -1
- package/dist/ui/components/KeyboardHelp.js +8 -6
- package/dist/ui/components/ModelSelection.js +6 -5
- package/dist/ui/components/StatusBar.d.ts +2 -1
- package/dist/ui/components/StatusBar.js +8 -7
- package/dist/ui/context/theme-context.js +3 -2
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +1 -1
- package/dist/ui/utils/markdown-renderer.js +4 -13
- package/dist/utils/atomic-write.d.ts +22 -0
- package/dist/utils/atomic-write.js +29 -0
- package/dist/utils/bounded-output.d.ts +16 -0
- package/dist/utils/bounded-output.js +63 -0
- package/dist/utils/config-validation/schema.d.ts +2 -2
- package/dist/utils/device-store-file.d.ts +4 -0
- package/dist/utils/device-store-file.js +40 -0
- package/dist/utils/export-manager.d.ts +9 -0
- package/dist/utils/export-manager.js +19 -0
- package/dist/utils/first-use-hints.d.ts +25 -0
- package/dist/utils/first-use-hints.js +58 -0
- package/dist/utils/graceful-shutdown.d.ts +8 -0
- package/dist/utils/graceful-shutdown.js +23 -7
- package/dist/utils/history-manager.js +1 -1
- package/dist/utils/update-notifier.js +16 -18
- package/dist/wizard/environment-detection.d.ts +5 -1
- package/dist/wizard/environment-detection.js +49 -7
- package/dist/wizard/onboarding.d.ts +5 -1
- package/dist/wizard/onboarding.js +90 -30
- package/dist/wizard/provider-onboarding.d.ts +2 -0
- package/dist/wizard/provider-onboarding.js +55 -17
- package/package.json +7 -3
|
@@ -49,8 +49,24 @@ import { EventEmitter } from 'events';
|
|
|
49
49
|
import { WebSocket } from 'ws';
|
|
50
50
|
import { logger } from '../utils/logger.js';
|
|
51
51
|
import { ReconnectionManager, } from '../channels/reconnection-manager.js';
|
|
52
|
+
/** Close a superseded socket without letting ws emit an unhandled error while CONNECTING. */
|
|
53
|
+
function closeReplacedSocket(ws) {
|
|
54
|
+
ws.removeAllListeners();
|
|
55
|
+
ws.on('error', () => {
|
|
56
|
+
/* ws emits when close() interrupts a CONNECTING handshake */
|
|
57
|
+
});
|
|
58
|
+
try {
|
|
59
|
+
if (ws.readyState === 0 /* CONNECTING */ || ws.readyState === 1 /* OPEN */) {
|
|
60
|
+
ws.close();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
/* best-effort replacement cleanup */
|
|
65
|
+
}
|
|
66
|
+
}
|
|
52
67
|
export class FleetListener extends EventEmitter {
|
|
53
68
|
ws = null;
|
|
69
|
+
cancelConnectAttempt = null;
|
|
54
70
|
connected = false;
|
|
55
71
|
authenticated = false;
|
|
56
72
|
// Set true once we have completed an `authenticated` handshake at least
|
|
@@ -156,49 +172,83 @@ export class FleetListener extends EventEmitter {
|
|
|
156
172
|
return this.connectInternal();
|
|
157
173
|
}
|
|
158
174
|
async connectInternal() {
|
|
175
|
+
const replacingSocket = this.ws !== null;
|
|
176
|
+
// Reject and clean up an in-flight attempt before replacing its socket.
|
|
177
|
+
// In particular, ws.close() on CONNECTING emits an error; the replacement
|
|
178
|
+
// cleanup installs a sink so that cannot become an uncaught exception.
|
|
179
|
+
this.cancelConnectAttempt?.();
|
|
180
|
+
this.cancelConnectAttempt = null;
|
|
159
181
|
// Cleanup any leftover ws from a previous attempt. Important for the
|
|
160
182
|
// reconnect path so the new ws's events don't interleave with the
|
|
161
183
|
// old one's. Mirrors the discord adapter's reconnect cleanup.
|
|
162
184
|
if (this.ws) {
|
|
163
|
-
|
|
164
|
-
this.ws.removeAllListeners();
|
|
165
|
-
if (this.ws.readyState === 1 /* OPEN */) {
|
|
166
|
-
this.ws.close();
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
catch {
|
|
170
|
-
/* ignore */
|
|
171
|
-
}
|
|
185
|
+
closeReplacedSocket(this.ws);
|
|
172
186
|
this.ws = null;
|
|
173
187
|
}
|
|
188
|
+
if (replacingSocket) {
|
|
189
|
+
this.connected = false;
|
|
190
|
+
this.authenticated = false;
|
|
191
|
+
this.rejectPendingRequests('connection replaced');
|
|
192
|
+
}
|
|
174
193
|
return new Promise((resolve, reject) => {
|
|
175
194
|
let settled = false;
|
|
195
|
+
let cancelAttempt = null;
|
|
196
|
+
let connectTimer = null;
|
|
197
|
+
let authTimer = null;
|
|
198
|
+
const clearAttemptTimers = () => {
|
|
199
|
+
if (connectTimer) {
|
|
200
|
+
clearTimeout(connectTimer);
|
|
201
|
+
connectTimer = null;
|
|
202
|
+
}
|
|
203
|
+
if (authTimer) {
|
|
204
|
+
clearTimeout(authTimer);
|
|
205
|
+
authTimer = null;
|
|
206
|
+
}
|
|
207
|
+
};
|
|
176
208
|
const settle = (err) => {
|
|
177
209
|
if (settled)
|
|
178
210
|
return;
|
|
179
211
|
settled = true;
|
|
212
|
+
clearAttemptTimers();
|
|
213
|
+
if (cancelAttempt && this.cancelConnectAttempt === cancelAttempt) {
|
|
214
|
+
this.cancelConnectAttempt = null;
|
|
215
|
+
}
|
|
180
216
|
if (err)
|
|
181
217
|
reject(err);
|
|
182
218
|
else
|
|
183
219
|
resolve();
|
|
184
220
|
};
|
|
185
|
-
const
|
|
221
|
+
const ws = new WebSocket(this.options.url);
|
|
222
|
+
this.ws = ws;
|
|
223
|
+
cancelAttempt = () => {
|
|
224
|
+
if (this.ws === ws)
|
|
225
|
+
this.ws = null;
|
|
226
|
+
settle(new Error('Fleet listener connection attempt replaced'));
|
|
227
|
+
closeReplacedSocket(ws);
|
|
228
|
+
};
|
|
229
|
+
this.cancelConnectAttempt = cancelAttempt;
|
|
230
|
+
connectTimer = setTimeout(() => {
|
|
186
231
|
settle(new Error(`Fleet listener connect timeout (${this.options.connectTimeoutMs ?? 10_000}ms)`));
|
|
187
232
|
try {
|
|
188
|
-
|
|
233
|
+
ws.close();
|
|
189
234
|
}
|
|
190
235
|
catch {
|
|
191
236
|
/* ignore */
|
|
192
237
|
}
|
|
193
238
|
}, this.options.connectTimeoutMs ?? 10_000);
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
239
|
+
ws.on('open', () => {
|
|
240
|
+
if (this.ws !== ws)
|
|
241
|
+
return;
|
|
242
|
+
if (connectTimer) {
|
|
243
|
+
clearTimeout(connectTimer);
|
|
244
|
+
connectTimer = null;
|
|
245
|
+
}
|
|
198
246
|
this.connected = true;
|
|
199
247
|
this.emit('connected');
|
|
200
248
|
});
|
|
201
|
-
|
|
249
|
+
ws.on('message', (data) => {
|
|
250
|
+
if (this.ws !== ws)
|
|
251
|
+
return;
|
|
202
252
|
let msg;
|
|
203
253
|
try {
|
|
204
254
|
msg = JSON.parse(data.toString());
|
|
@@ -212,12 +262,24 @@ export class FleetListener extends EventEmitter {
|
|
|
212
262
|
clearTimeout(authTimer);
|
|
213
263
|
authTimer = null;
|
|
214
264
|
}
|
|
265
|
+
}, () => {
|
|
266
|
+
if (settled || authTimer)
|
|
267
|
+
return;
|
|
268
|
+
authTimer = setTimeout(() => {
|
|
269
|
+
settle(new Error(`Fleet listener auth timeout (${this.options.authTimeoutMs ?? 5_000}ms)`));
|
|
270
|
+
try {
|
|
271
|
+
ws.close();
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
/* ignore */
|
|
275
|
+
}
|
|
276
|
+
}, this.options.authTimeoutMs ?? 5_000);
|
|
215
277
|
});
|
|
216
278
|
});
|
|
217
|
-
|
|
218
|
-
if (
|
|
219
|
-
|
|
220
|
-
|
|
279
|
+
ws.on('close', () => {
|
|
280
|
+
if (this.ws !== ws)
|
|
281
|
+
return;
|
|
282
|
+
clearAttemptTimers();
|
|
221
283
|
this.connected = false;
|
|
222
284
|
this.authenticated = false;
|
|
223
285
|
this.emit('disconnected');
|
|
@@ -250,36 +312,15 @@ export class FleetListener extends EventEmitter {
|
|
|
250
312
|
this.scheduleReconnectDeferred();
|
|
251
313
|
}
|
|
252
314
|
});
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
clearTimeout(authTimer);
|
|
315
|
+
ws.on('error', (err) => {
|
|
316
|
+
if (this.ws !== ws)
|
|
317
|
+
return;
|
|
257
318
|
this.emit('error', err);
|
|
258
319
|
settle(err instanceof Error ? err : new Error(String(err)));
|
|
259
320
|
});
|
|
260
|
-
// After 'connected' from the server, send the auth message. Set up
|
|
261
|
-
// an auth timeout in case the server never responds.
|
|
262
|
-
this.once('connected', () => {
|
|
263
|
-
// 'connected' here is OUR emitted event when ws opened; we still
|
|
264
|
-
// need to wait for the SERVER's 'connected' message. The
|
|
265
|
-
// handleIncomingMessage path does the auth-send — see below.
|
|
266
|
-
});
|
|
267
|
-
// Bound auth wait: schedule the timeout after we send authenticate.
|
|
268
|
-
// We'll trigger this from handleIncomingMessage after sending auth.
|
|
269
|
-
this.once('__internal:auth-sent', () => {
|
|
270
|
-
authTimer = setTimeout(() => {
|
|
271
|
-
settle(new Error(`Fleet listener auth timeout (${this.options.authTimeoutMs ?? 5_000}ms)`));
|
|
272
|
-
try {
|
|
273
|
-
this.ws?.close();
|
|
274
|
-
}
|
|
275
|
-
catch {
|
|
276
|
-
/* ignore */
|
|
277
|
-
}
|
|
278
|
-
}, this.options.authTimeoutMs ?? 5_000);
|
|
279
|
-
});
|
|
280
321
|
});
|
|
281
322
|
}
|
|
282
|
-
handleIncomingMessage(msg, settle, clearAuthTimer) {
|
|
323
|
+
handleIncomingMessage(msg, settle, clearAuthTimer, startAuthTimer) {
|
|
283
324
|
// Server's welcome → send authenticate.
|
|
284
325
|
if (msg.type === 'connected') {
|
|
285
326
|
const auth = {};
|
|
@@ -288,7 +329,7 @@ export class FleetListener extends EventEmitter {
|
|
|
288
329
|
if (this.options.jwt)
|
|
289
330
|
auth.token = this.options.jwt;
|
|
290
331
|
this.send('authenticate', auth);
|
|
291
|
-
|
|
332
|
+
startAuthTimer();
|
|
292
333
|
return;
|
|
293
334
|
}
|
|
294
335
|
if (msg.type === 'authenticated') {
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
* full listener module (avoids pulling ws at handler-load time).
|
|
20
20
|
*/
|
|
21
21
|
export interface FleetListenerPublicAPI {
|
|
22
|
+
/** Actual transport state when supplied by the listener. */
|
|
23
|
+
isConnected?: () => boolean;
|
|
22
24
|
disconnect: () => Promise<void>;
|
|
23
25
|
getReconnectAttempts: () => number;
|
|
24
26
|
isReconnecting: () => boolean;
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const resourceSchema: z.ZodObject<{
|
|
3
|
+
id: z.ZodString;
|
|
4
|
+
kind: z.ZodEnum<["inference", "comfyui", "storage", "rag", "database", "docker", "code-explorer", "camera", "microphone"]>;
|
|
5
|
+
hostId: z.ZodString;
|
|
6
|
+
declaredCapabilities: z.ZodArray<z.ZodString, "many">;
|
|
7
|
+
endpointRef: z.ZodString;
|
|
8
|
+
healthPath: z.ZodEnum<["/health", "/healthz", "/api/health", "/v1/models", "/api/tags", "/system_stats", "/_ping"]>;
|
|
9
|
+
permissions: z.ZodObject<{
|
|
10
|
+
probe: z.ZodBoolean;
|
|
11
|
+
use: z.ZodBoolean;
|
|
12
|
+
}, "strict", z.ZodTypeAny, {
|
|
13
|
+
use: boolean;
|
|
14
|
+
probe: boolean;
|
|
15
|
+
}, {
|
|
16
|
+
use: boolean;
|
|
17
|
+
probe: boolean;
|
|
18
|
+
}>;
|
|
19
|
+
ttlMs: z.ZodDefault<z.ZodNumber>;
|
|
20
|
+
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
21
|
+
}, "strict", z.ZodTypeAny, {
|
|
22
|
+
timeoutMs: number;
|
|
23
|
+
id: string;
|
|
24
|
+
kind: "docker" | "inference" | "comfyui" | "storage" | "rag" | "database" | "code-explorer" | "camera" | "microphone";
|
|
25
|
+
ttlMs: number;
|
|
26
|
+
permissions: {
|
|
27
|
+
use: boolean;
|
|
28
|
+
probe: boolean;
|
|
29
|
+
};
|
|
30
|
+
hostId: string;
|
|
31
|
+
declaredCapabilities: string[];
|
|
32
|
+
endpointRef: string;
|
|
33
|
+
healthPath: "/health" | "/v1/models" | "/api/tags" | "/api/health" | "/healthz" | "/system_stats" | "/_ping";
|
|
34
|
+
}, {
|
|
35
|
+
id: string;
|
|
36
|
+
kind: "docker" | "inference" | "comfyui" | "storage" | "rag" | "database" | "code-explorer" | "camera" | "microphone";
|
|
37
|
+
permissions: {
|
|
38
|
+
use: boolean;
|
|
39
|
+
probe: boolean;
|
|
40
|
+
};
|
|
41
|
+
hostId: string;
|
|
42
|
+
declaredCapabilities: string[];
|
|
43
|
+
endpointRef: string;
|
|
44
|
+
healthPath: "/health" | "/v1/models" | "/api/tags" | "/api/health" | "/healthz" | "/system_stats" | "/_ping";
|
|
45
|
+
timeoutMs?: number | undefined;
|
|
46
|
+
ttlMs?: number | undefined;
|
|
47
|
+
}>;
|
|
48
|
+
export type Resource = z.infer<typeof resourceSchema>;
|
|
49
|
+
declare const observationSchema: z.ZodObject<{
|
|
50
|
+
state: z.ZodEnum<["online", "offline", "unknown"]>;
|
|
51
|
+
checkedAt: z.ZodNumber;
|
|
52
|
+
lastSeen: z.ZodNullable<z.ZodNumber>;
|
|
53
|
+
latencyMs: z.ZodNullable<z.ZodNumber>;
|
|
54
|
+
endpointFingerprint: z.ZodString;
|
|
55
|
+
reason: z.ZodString;
|
|
56
|
+
}, "strict", z.ZodTypeAny, {
|
|
57
|
+
reason: string;
|
|
58
|
+
state: "unknown" | "offline" | "online";
|
|
59
|
+
lastSeen: number | null;
|
|
60
|
+
latencyMs: number | null;
|
|
61
|
+
checkedAt: number;
|
|
62
|
+
endpointFingerprint: string;
|
|
63
|
+
}, {
|
|
64
|
+
reason: string;
|
|
65
|
+
state: "unknown" | "offline" | "online";
|
|
66
|
+
lastSeen: number | null;
|
|
67
|
+
latencyMs: number | null;
|
|
68
|
+
checkedAt: number;
|
|
69
|
+
endpointFingerprint: string;
|
|
70
|
+
}>;
|
|
71
|
+
export type Observation = z.infer<typeof observationSchema>;
|
|
72
|
+
declare const entrySchema: z.ZodObject<{
|
|
73
|
+
resource: z.ZodObject<{
|
|
74
|
+
id: z.ZodString;
|
|
75
|
+
kind: z.ZodEnum<["inference", "comfyui", "storage", "rag", "database", "docker", "code-explorer", "camera", "microphone"]>;
|
|
76
|
+
hostId: z.ZodString;
|
|
77
|
+
declaredCapabilities: z.ZodArray<z.ZodString, "many">;
|
|
78
|
+
endpointRef: z.ZodString;
|
|
79
|
+
healthPath: z.ZodEnum<["/health", "/healthz", "/api/health", "/v1/models", "/api/tags", "/system_stats", "/_ping"]>;
|
|
80
|
+
permissions: z.ZodObject<{
|
|
81
|
+
probe: z.ZodBoolean;
|
|
82
|
+
use: z.ZodBoolean;
|
|
83
|
+
}, "strict", z.ZodTypeAny, {
|
|
84
|
+
use: boolean;
|
|
85
|
+
probe: boolean;
|
|
86
|
+
}, {
|
|
87
|
+
use: boolean;
|
|
88
|
+
probe: boolean;
|
|
89
|
+
}>;
|
|
90
|
+
ttlMs: z.ZodDefault<z.ZodNumber>;
|
|
91
|
+
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
92
|
+
}, "strict", z.ZodTypeAny, {
|
|
93
|
+
timeoutMs: number;
|
|
94
|
+
id: string;
|
|
95
|
+
kind: "docker" | "inference" | "comfyui" | "storage" | "rag" | "database" | "code-explorer" | "camera" | "microphone";
|
|
96
|
+
ttlMs: number;
|
|
97
|
+
permissions: {
|
|
98
|
+
use: boolean;
|
|
99
|
+
probe: boolean;
|
|
100
|
+
};
|
|
101
|
+
hostId: string;
|
|
102
|
+
declaredCapabilities: string[];
|
|
103
|
+
endpointRef: string;
|
|
104
|
+
healthPath: "/health" | "/v1/models" | "/api/tags" | "/api/health" | "/healthz" | "/system_stats" | "/_ping";
|
|
105
|
+
}, {
|
|
106
|
+
id: string;
|
|
107
|
+
kind: "docker" | "inference" | "comfyui" | "storage" | "rag" | "database" | "code-explorer" | "camera" | "microphone";
|
|
108
|
+
permissions: {
|
|
109
|
+
use: boolean;
|
|
110
|
+
probe: boolean;
|
|
111
|
+
};
|
|
112
|
+
hostId: string;
|
|
113
|
+
declaredCapabilities: string[];
|
|
114
|
+
endpointRef: string;
|
|
115
|
+
healthPath: "/health" | "/v1/models" | "/api/tags" | "/api/health" | "/healthz" | "/system_stats" | "/_ping";
|
|
116
|
+
timeoutMs?: number | undefined;
|
|
117
|
+
ttlMs?: number | undefined;
|
|
118
|
+
}>;
|
|
119
|
+
observation: z.ZodNullable<z.ZodObject<{
|
|
120
|
+
state: z.ZodEnum<["online", "offline", "unknown"]>;
|
|
121
|
+
checkedAt: z.ZodNumber;
|
|
122
|
+
lastSeen: z.ZodNullable<z.ZodNumber>;
|
|
123
|
+
latencyMs: z.ZodNullable<z.ZodNumber>;
|
|
124
|
+
endpointFingerprint: z.ZodString;
|
|
125
|
+
reason: z.ZodString;
|
|
126
|
+
}, "strict", z.ZodTypeAny, {
|
|
127
|
+
reason: string;
|
|
128
|
+
state: "unknown" | "offline" | "online";
|
|
129
|
+
lastSeen: number | null;
|
|
130
|
+
latencyMs: number | null;
|
|
131
|
+
checkedAt: number;
|
|
132
|
+
endpointFingerprint: string;
|
|
133
|
+
}, {
|
|
134
|
+
reason: string;
|
|
135
|
+
state: "unknown" | "offline" | "online";
|
|
136
|
+
lastSeen: number | null;
|
|
137
|
+
latencyMs: number | null;
|
|
138
|
+
checkedAt: number;
|
|
139
|
+
endpointFingerprint: string;
|
|
140
|
+
}>>;
|
|
141
|
+
}, "strict", z.ZodTypeAny, {
|
|
142
|
+
resource: {
|
|
143
|
+
timeoutMs: number;
|
|
144
|
+
id: string;
|
|
145
|
+
kind: "docker" | "inference" | "comfyui" | "storage" | "rag" | "database" | "code-explorer" | "camera" | "microphone";
|
|
146
|
+
ttlMs: number;
|
|
147
|
+
permissions: {
|
|
148
|
+
use: boolean;
|
|
149
|
+
probe: boolean;
|
|
150
|
+
};
|
|
151
|
+
hostId: string;
|
|
152
|
+
declaredCapabilities: string[];
|
|
153
|
+
endpointRef: string;
|
|
154
|
+
healthPath: "/health" | "/v1/models" | "/api/tags" | "/api/health" | "/healthz" | "/system_stats" | "/_ping";
|
|
155
|
+
};
|
|
156
|
+
observation: {
|
|
157
|
+
reason: string;
|
|
158
|
+
state: "unknown" | "offline" | "online";
|
|
159
|
+
lastSeen: number | null;
|
|
160
|
+
latencyMs: number | null;
|
|
161
|
+
checkedAt: number;
|
|
162
|
+
endpointFingerprint: string;
|
|
163
|
+
} | null;
|
|
164
|
+
}, {
|
|
165
|
+
resource: {
|
|
166
|
+
id: string;
|
|
167
|
+
kind: "docker" | "inference" | "comfyui" | "storage" | "rag" | "database" | "code-explorer" | "camera" | "microphone";
|
|
168
|
+
permissions: {
|
|
169
|
+
use: boolean;
|
|
170
|
+
probe: boolean;
|
|
171
|
+
};
|
|
172
|
+
hostId: string;
|
|
173
|
+
declaredCapabilities: string[];
|
|
174
|
+
endpointRef: string;
|
|
175
|
+
healthPath: "/health" | "/v1/models" | "/api/tags" | "/api/health" | "/healthz" | "/system_stats" | "/_ping";
|
|
176
|
+
timeoutMs?: number | undefined;
|
|
177
|
+
ttlMs?: number | undefined;
|
|
178
|
+
};
|
|
179
|
+
observation: {
|
|
180
|
+
reason: string;
|
|
181
|
+
state: "unknown" | "offline" | "online";
|
|
182
|
+
lastSeen: number | null;
|
|
183
|
+
latencyMs: number | null;
|
|
184
|
+
checkedAt: number;
|
|
185
|
+
endpointFingerprint: string;
|
|
186
|
+
} | null;
|
|
187
|
+
}>;
|
|
188
|
+
export type ResourceStatus = z.infer<typeof entrySchema> & {
|
|
189
|
+
state: 'online' | 'offline' | 'unknown' | 'stale';
|
|
190
|
+
reason: string;
|
|
191
|
+
/** No heuristic RAM or inferred utilisation. This tranche has no load collector. */
|
|
192
|
+
load: null;
|
|
193
|
+
usageConfirmed: false;
|
|
194
|
+
};
|
|
195
|
+
/** Declarations and observations only: never the resolved endpoint value or the internal fingerprint. */
|
|
196
|
+
export declare function publicResourceStatus(entry: ResourceStatus): {
|
|
197
|
+
resource: {
|
|
198
|
+
timeoutMs: number;
|
|
199
|
+
id: string;
|
|
200
|
+
kind: "docker" | "inference" | "comfyui" | "storage" | "rag" | "database" | "code-explorer" | "camera" | "microphone";
|
|
201
|
+
ttlMs: number;
|
|
202
|
+
permissions: {
|
|
203
|
+
use: boolean;
|
|
204
|
+
probe: boolean;
|
|
205
|
+
};
|
|
206
|
+
hostId: string;
|
|
207
|
+
declaredCapabilities: string[];
|
|
208
|
+
endpointRef: string;
|
|
209
|
+
healthPath: "/health" | "/v1/models" | "/api/tags" | "/api/health" | "/healthz" | "/system_stats" | "/_ping";
|
|
210
|
+
};
|
|
211
|
+
state: "unknown" | "offline" | "online" | "stale";
|
|
212
|
+
reason: string;
|
|
213
|
+
checkedAt: number | null;
|
|
214
|
+
lastSeen: number | null;
|
|
215
|
+
latencyMs: number | null;
|
|
216
|
+
load: null;
|
|
217
|
+
usageConfirmed: false;
|
|
218
|
+
};
|
|
219
|
+
export type PublicResourceStatus = ReturnType<typeof publicResourceStatus>;
|
|
220
|
+
/** The RagChat connector reads this reference only; a selection never redirects it. */
|
|
221
|
+
export declare const RAGCHAT_ENDPOINT_REF = "RAGCHAT_BASE_URL";
|
|
222
|
+
export declare function selectionWarning(selected: Pick<ResourceStatus, 'resource'> | null): string | undefined;
|
|
223
|
+
export declare class ResourceCatalog {
|
|
224
|
+
readonly filename: string;
|
|
225
|
+
private readonly now;
|
|
226
|
+
constructor(filename?: string, now?: () => number);
|
|
227
|
+
private read;
|
|
228
|
+
private update;
|
|
229
|
+
add(input: unknown): Promise<Resource>;
|
|
230
|
+
remove(id: string): Promise<{
|
|
231
|
+
removed: string;
|
|
232
|
+
}>;
|
|
233
|
+
private status;
|
|
234
|
+
list(): Promise<ResourceStatus[]>;
|
|
235
|
+
probe(id: string): Promise<ResourceStatus>;
|
|
236
|
+
select(capability: string, kind?: Resource['kind']): Promise<{
|
|
237
|
+
selected: ResourceStatus | null;
|
|
238
|
+
excluded: Array<{
|
|
239
|
+
id: string;
|
|
240
|
+
reason: string;
|
|
241
|
+
}>;
|
|
242
|
+
reason: string;
|
|
243
|
+
}>;
|
|
244
|
+
}
|
|
245
|
+
export {};
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/** Explicit resources; health is not proof that a model/capability works. */
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { createHash } from 'node:crypto';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { writeJsonAtomic } from '../utils/atomic-write.js';
|
|
8
|
+
const identifier = z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,95}$/);
|
|
9
|
+
export const resourceSchema = z.object({
|
|
10
|
+
id: identifier,
|
|
11
|
+
kind: z.enum(['inference', 'comfyui', 'storage', 'rag', 'database', 'docker', 'code-explorer', 'camera', 'microphone']),
|
|
12
|
+
hostId: identifier,
|
|
13
|
+
declaredCapabilities: z.array(identifier).min(1).max(32),
|
|
14
|
+
endpointRef: z.string().regex(/^[A-Z][A-Z0-9_]{0,95}$/),
|
|
15
|
+
healthPath: z.enum(['/health', '/healthz', '/api/health', '/v1/models', '/api/tags', '/system_stats', '/_ping']),
|
|
16
|
+
permissions: z.object({ probe: z.boolean(), use: z.boolean() }).strict(),
|
|
17
|
+
ttlMs: z.number().int().min(1000).max(3600000).default(60000),
|
|
18
|
+
timeoutMs: z.number().int().min(50).max(5000).default(1000),
|
|
19
|
+
}).strict();
|
|
20
|
+
const observationSchema = z.object({
|
|
21
|
+
state: z.enum(['online', 'offline', 'unknown']),
|
|
22
|
+
checkedAt: z.number().nonnegative(),
|
|
23
|
+
lastSeen: z.number().nonnegative().nullable(),
|
|
24
|
+
latencyMs: z.number().nonnegative().nullable(),
|
|
25
|
+
endpointFingerprint: z.string(),
|
|
26
|
+
reason: z.string(),
|
|
27
|
+
}).strict();
|
|
28
|
+
const entrySchema = z.object({ resource: resourceSchema, observation: observationSchema.nullable() }).strict();
|
|
29
|
+
const storeSchema = z.object({ version: z.literal(1), entries: z.array(entrySchema).max(100) }).strict();
|
|
30
|
+
/** Declarations and observations only: never the resolved endpoint value or the internal fingerprint. */
|
|
31
|
+
export function publicResourceStatus(entry) {
|
|
32
|
+
return { resource: entry.resource, state: entry.state, reason: entry.reason,
|
|
33
|
+
checkedAt: entry.observation?.checkedAt ?? null, lastSeen: entry.observation?.lastSeen ?? null,
|
|
34
|
+
latencyMs: entry.observation?.latencyMs ?? null, load: entry.load, usageConfirmed: entry.usageConfirmed };
|
|
35
|
+
}
|
|
36
|
+
/** The RagChat connector reads this reference only; a selection never redirects it. */
|
|
37
|
+
export const RAGCHAT_ENDPOINT_REF = 'RAGCHAT_BASE_URL';
|
|
38
|
+
export function selectionWarning(selected) {
|
|
39
|
+
if (!selected || selected.resource.kind !== 'rag' || selected.resource.endpointRef === RAGCHAT_ENDPOINT_REF)
|
|
40
|
+
return undefined;
|
|
41
|
+
return `ragchat_search interroge toujours ${RAGCHAT_ENDPOINT_REF} ; cette ressource référence ${selected.resource.endpointRef} et la sélection ne redirige pas le connecteur.`;
|
|
42
|
+
}
|
|
43
|
+
const allowedPaths = {
|
|
44
|
+
inference: ['/health', '/healthz', '/v1/models', '/api/tags'],
|
|
45
|
+
comfyui: ['/system_stats'],
|
|
46
|
+
rag: ['/health', '/healthz', '/api/health'],
|
|
47
|
+
docker: ['/_ping'],
|
|
48
|
+
'code-explorer': ['/health', '/healthz', '/api/health'],
|
|
49
|
+
};
|
|
50
|
+
function endpoint(resource) {
|
|
51
|
+
const raw = process.env[resource.endpointRef];
|
|
52
|
+
if (!raw)
|
|
53
|
+
throw new Error('ENDPOINT_UNCONFIGURED');
|
|
54
|
+
let url;
|
|
55
|
+
try {
|
|
56
|
+
url = new URL(raw);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
throw new Error('ENDPOINT_INVALID');
|
|
60
|
+
}
|
|
61
|
+
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.search || url.hash || url.pathname !== '/') {
|
|
62
|
+
throw new Error('ENDPOINT_INVALID');
|
|
63
|
+
}
|
|
64
|
+
url.pathname = resource.healthPath;
|
|
65
|
+
return { url, fingerprint: createHash('sha256').update(url.href).digest('hex') };
|
|
66
|
+
}
|
|
67
|
+
export class ResourceCatalog {
|
|
68
|
+
filename;
|
|
69
|
+
now;
|
|
70
|
+
constructor(filename = path.join(os.homedir(), '.codebuddy', 'resources', 'catalog.json'), now = Date.now) {
|
|
71
|
+
this.filename = filename;
|
|
72
|
+
this.now = now;
|
|
73
|
+
}
|
|
74
|
+
async read() {
|
|
75
|
+
try {
|
|
76
|
+
const stat = await fs.lstat(this.filename);
|
|
77
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 1024 * 1024)
|
|
78
|
+
throw new Error('INVALID_CATALOG_FILE');
|
|
79
|
+
const parsed = storeSchema.safeParse(JSON.parse(await fs.readFile(this.filename, 'utf8')));
|
|
80
|
+
if (!parsed.success || new Set(parsed.data.entries.map(e => e.resource.id)).size !== parsed.data.entries.length) {
|
|
81
|
+
throw new Error('INVALID_CATALOG');
|
|
82
|
+
}
|
|
83
|
+
return parsed.data;
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
if (error.code === 'ENOENT')
|
|
87
|
+
return { version: 1, entries: [] };
|
|
88
|
+
throw new Error('INVALID_CATALOG: existing data was not replaced');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async update(action) {
|
|
92
|
+
const directory = path.dirname(this.filename);
|
|
93
|
+
await fs.mkdir(directory, { recursive: true, mode: 0o700 });
|
|
94
|
+
const stat = await fs.lstat(directory);
|
|
95
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
96
|
+
throw new Error('INVALID_CATALOG_DIRECTORY');
|
|
97
|
+
await fs.chmod(directory, 0o700);
|
|
98
|
+
const lock = `${this.filename}.lock`;
|
|
99
|
+
try {
|
|
100
|
+
await fs.mkdir(lock, { mode: 0o700 });
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
throw new Error('CATALOG_BUSY: another writer or unrecovered lock');
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const store = await this.read();
|
|
107
|
+
const result = await action(store);
|
|
108
|
+
await writeJsonAtomic(this.filename, store, { mode: 0o600 });
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
await fs.rmdir(lock);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async add(input) {
|
|
116
|
+
const parsed = resourceSchema.safeParse(input);
|
|
117
|
+
if (!parsed.success)
|
|
118
|
+
throw new Error('INVALID_RESOURCE: check schema; inline endpoints and extra fields are forbidden');
|
|
119
|
+
return this.update(async (store) => {
|
|
120
|
+
if (store.entries.some(e => e.resource.id === parsed.data.id))
|
|
121
|
+
throw new Error('RESOURCE_EXISTS');
|
|
122
|
+
if (store.entries.length >= 100)
|
|
123
|
+
throw new Error('CATALOG_FULL');
|
|
124
|
+
store.entries.push({ resource: parsed.data, observation: null });
|
|
125
|
+
return parsed.data;
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
async remove(id) {
|
|
129
|
+
return this.update(async (store) => {
|
|
130
|
+
const index = store.entries.findIndex(e => e.resource.id === id);
|
|
131
|
+
if (index < 0)
|
|
132
|
+
throw new Error('RESOURCE_NOT_FOUND');
|
|
133
|
+
store.entries.splice(index, 1);
|
|
134
|
+
return { removed: id };
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
status(entry) {
|
|
138
|
+
const { resource, observation } = entry;
|
|
139
|
+
let state = observation?.state ?? 'unknown';
|
|
140
|
+
let reason = observation?.reason ?? 'NEVER_PROBED';
|
|
141
|
+
if (observation && (this.now() - observation.checkedAt >= resource.ttlMs || observation.checkedAt > this.now())) {
|
|
142
|
+
state = 'stale';
|
|
143
|
+
reason = 'OBSERVATION_EXPIRED';
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
if (observation && observation.endpointFingerprint !== endpoint(resource).fingerprint) {
|
|
147
|
+
state = 'unknown';
|
|
148
|
+
reason = 'ENDPOINT_CHANGED';
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
state = 'unknown';
|
|
153
|
+
reason = 'ENDPOINT_UNCONFIGURED_OR_INVALID';
|
|
154
|
+
}
|
|
155
|
+
return { ...entry, state, reason, load: null, usageConfirmed: false };
|
|
156
|
+
}
|
|
157
|
+
async list() { return (await this.read()).entries.map(e => this.status(e)); }
|
|
158
|
+
async probe(id) {
|
|
159
|
+
// Lock covers the bounded probe so a later probe cannot be overwritten by an older completion.
|
|
160
|
+
return this.update(async (store) => {
|
|
161
|
+
const entry = store.entries.find(e => e.resource.id === id);
|
|
162
|
+
if (!entry)
|
|
163
|
+
throw new Error('RESOURCE_NOT_FOUND');
|
|
164
|
+
const r = entry.resource;
|
|
165
|
+
if (!r.permissions.probe)
|
|
166
|
+
throw new Error('PROBE_NOT_PERMITTED');
|
|
167
|
+
if (!allowedPaths[r.kind]?.includes(r.healthPath))
|
|
168
|
+
throw new Error('NO_READ_ONLY_PROBE_FOR_KIND_PATH');
|
|
169
|
+
const target = endpoint(r);
|
|
170
|
+
const start = this.now();
|
|
171
|
+
let state = 'offline';
|
|
172
|
+
let reason = 'PROBE_FAILED';
|
|
173
|
+
try {
|
|
174
|
+
const response = await fetch(target.url, { method: 'GET', redirect: 'manual', signal: AbortSignal.timeout(r.timeoutMs) });
|
|
175
|
+
state = response.ok ? 'online' : 'offline';
|
|
176
|
+
reason = response.ok ? 'HTTP_HEALTH_OK_NOT_USAGE_PROOF' : `HTTP_${response.status}`;
|
|
177
|
+
// Never parse service content, model lists, database contents or device frames.
|
|
178
|
+
await response.body?.cancel();
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
state = 'offline';
|
|
182
|
+
reason = 'UNREACHABLE_OR_TIMEOUT';
|
|
183
|
+
}
|
|
184
|
+
const checkedAt = this.now();
|
|
185
|
+
entry.observation = { state, reason, checkedAt, lastSeen: state === 'online' ? checkedAt
|
|
186
|
+
: entry.observation?.endpointFingerprint === target.fingerprint ? entry.observation.lastSeen : null,
|
|
187
|
+
latencyMs: state === 'online' ? Math.max(0, checkedAt - start) : null, endpointFingerprint: target.fingerprint };
|
|
188
|
+
return this.status(entry);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
async select(capability, kind) {
|
|
192
|
+
if (!identifier.safeParse(capability).success)
|
|
193
|
+
throw new Error('INVALID_CAPABILITY');
|
|
194
|
+
const excluded = [];
|
|
195
|
+
const candidates = (await this.list()).filter(e => {
|
|
196
|
+
const reason = kind && e.resource.kind !== kind ? 'KIND_MISMATCH'
|
|
197
|
+
: !e.resource.declaredCapabilities.includes(capability) ? 'CAPABILITY_NOT_DECLARED'
|
|
198
|
+
: !e.resource.permissions.use ? 'USE_NOT_PERMITTED'
|
|
199
|
+
: e.state !== 'online' ? e.reason : null;
|
|
200
|
+
if (reason)
|
|
201
|
+
excluded.push({ id: e.resource.id, reason });
|
|
202
|
+
return !reason;
|
|
203
|
+
});
|
|
204
|
+
// Load unknown: do not fabricate free capacity. Stable deterministic latency/id choice only.
|
|
205
|
+
candidates.sort((a, b) => (a.observation?.latencyMs ?? Infinity) - (b.observation?.latencyMs ?? Infinity)
|
|
206
|
+
|| a.resource.id.localeCompare(b.resource.id));
|
|
207
|
+
return { selected: candidates[0] ?? null, excluded,
|
|
208
|
+
reason: candidates.length ? 'FRESH_HEALTH_DECLARED_CAPABILITY_LOAD_UNKNOWN' : 'NO_ELIGIBLE_RESOURCE' };
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=resource-catalog.js.map
|