@phuetz/code-buddy 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +211 -163
- package/dist/agent/autonomous/agentic-coding-contract.d.ts +12 -12
- package/dist/agent/base-agent.d.ts +4 -0
- package/dist/agent/base-agent.js +6 -0
- package/dist/agent/codebuddy-agent.d.ts +1 -1
- package/dist/agent/codebuddy-agent.js +69 -14
- package/dist/agent/execution/agent-executor.js +122 -12
- package/dist/agent/execution/tool-dependency-graph.js +7 -0
- package/dist/agent/execution/tool-hooks.d.ts +6 -0
- package/dist/agent/execution/tool-hooks.js +13 -5
- package/dist/agent/execution/tool-selection-strategy.d.ts +11 -2
- package/dist/agent/execution/tool-selection-strategy.js +67 -10
- package/dist/agent/extended-thinking.d.ts +8 -0
- package/dist/agent/extended-thinking.js +27 -0
- package/dist/agent/facades/infrastructure-facade.d.ts +9 -2
- package/dist/agent/facades/infrastructure-facade.js +15 -6
- package/dist/agent/hermes-browser-backends.js +151 -11
- package/dist/agent/hermes-claw-migrate.d.ts +59 -0
- package/dist/agent/hermes-claw-migrate.js +427 -26
- package/dist/agent/hermes-parity-manifest.js +78 -71
- package/dist/agent/hermes-runtime-lifecycle.d.ts +67 -0
- package/dist/agent/hermes-runtime-lifecycle.js +468 -0
- package/dist/agent/middleware/index.d.ts +1 -0
- package/dist/agent/middleware/index.js +1 -0
- package/dist/agent/middleware/visual-validation-middleware.d.ts +17 -0
- package/dist/agent/middleware/visual-validation-middleware.js +67 -0
- package/dist/agent/model-benchmark.d.ts +77 -0
- package/dist/agent/model-benchmark.js +309 -0
- package/dist/agent/model-tier.d.ts +14 -0
- package/dist/agent/model-tier.js +71 -0
- package/dist/agent/multi-agent/agents/orchestrator-agent.js +17 -6
- package/dist/agent/multi-agent/multi-agent-system.js +3 -1
- package/dist/agent/multi-agent/session-fleet-bridge.d.ts +2 -0
- package/dist/agent/multi-agent/session-fleet-bridge.js +9 -1
- package/dist/agent/multi-agent/session-tools.js +22 -5
- package/dist/agent/reasoning/mcts.js +20 -6
- package/dist/agent/self-improvement/authored-artifact-gate.d.ts +18 -0
- package/dist/agent/self-improvement/authored-artifact-gate.js +42 -0
- package/dist/agent/self-improvement/authored-tool-runtime.d.ts +27 -0
- package/dist/agent/self-improvement/authored-tool-runtime.js +57 -0
- package/dist/agent/self-improvement/authored-tool-store.d.ts +24 -0
- package/dist/agent/self-improvement/authored-tool-store.js +57 -0
- package/dist/agent/self-improvement/llm-tool-proposer.d.ts +41 -0
- package/dist/agent/self-improvement/llm-tool-proposer.js +136 -0
- package/dist/agent/self-improvement/sandbox-scorer.d.ts +17 -0
- package/dist/agent/self-improvement/sandbox-scorer.js +43 -0
- package/dist/agent/self-improvement/self-knowledge.d.ts +8 -0
- package/dist/agent/self-improvement/self-knowledge.js +24 -0
- package/dist/agent/self-improvement/skill-benchmark.d.ts +9 -0
- package/dist/agent/self-improvement/skill-benchmark.js +22 -0
- package/dist/agent/self-improvement/skill-consolidator.d.ts +71 -0
- package/dist/agent/self-improvement/skill-consolidator.js +137 -0
- package/dist/agent/self-improvement/skill-engine.d.ts +42 -0
- package/dist/agent/self-improvement/skill-engine.js +87 -0
- package/dist/agent/self-improvement/skill-gate.d.ts +19 -0
- package/dist/agent/self-improvement/skill-gate.js +62 -0
- package/dist/agent/self-improvement/skill-mutator.d.ts +74 -0
- package/dist/agent/self-improvement/skill-mutator.js +223 -0
- package/dist/agent/self-improvement/skill-proposer.d.ts +40 -0
- package/dist/agent/self-improvement/skill-proposer.js +82 -0
- package/dist/agent/self-improvement/skill-types.d.ts +41 -0
- package/dist/agent/self-improvement/skill-types.js +13 -0
- package/dist/agent/self-improvement/tool-benchmark.d.ts +10 -0
- package/dist/agent/self-improvement/tool-benchmark.js +37 -0
- package/dist/agent/self-improvement/tool-engine.d.ts +54 -0
- package/dist/agent/self-improvement/tool-engine.js +101 -0
- package/dist/agent/self-improvement/tool-gate.d.ts +20 -0
- package/dist/agent/self-improvement/tool-gate.js +78 -0
- package/dist/agent/self-improvement/tool-proposer.d.ts +31 -0
- package/dist/agent/self-improvement/tool-proposer.js +34 -0
- package/dist/agent/self-improvement/tool-skill-mutator.d.ts +40 -0
- package/dist/agent/self-improvement/tool-skill-mutator.js +79 -0
- package/dist/agent/self-improvement/tool-types.d.ts +48 -0
- package/dist/agent/self-improvement/tool-types.js +9 -0
- package/dist/agent/self-improvement/types.d.ts +3 -1
- package/dist/agent/session-end-flush.d.ts +6 -1
- package/dist/agent/session-end-flush.js +20 -4
- package/dist/agent/streaming/streaming-handler.d.ts +7 -0
- package/dist/agent/streaming/streaming-handler.js +8 -1
- package/dist/agent/tool-executor.js +1 -0
- package/dist/agent/tool-handler.d.ts +5 -0
- package/dist/agent/tool-handler.js +133 -4
- package/dist/browser-automation/browser-operator-executor.js +15 -0
- package/dist/browser-automation/browser-use-runner.d.ts +96 -0
- package/dist/browser-automation/browser-use-runner.js +492 -0
- package/dist/browser-automation/camofox-runner.d.ts +107 -0
- package/dist/browser-automation/camofox-runner.js +287 -0
- package/dist/channels/dingtalk/index.js +3 -0
- package/dist/channels/discord/client.d.ts +9 -0
- package/dist/channels/discord/client.js +12 -0
- package/dist/channels/feishu/index.d.ts +167 -1
- package/dist/channels/feishu/index.js +432 -7
- package/dist/channels/gateway-lifecycle.d.ts +132 -0
- package/dist/channels/gateway-lifecycle.js +219 -0
- package/dist/channels/google-chat/index.d.ts +5 -1
- package/dist/channels/google-chat/index.js +5 -1
- package/dist/channels/imessage/index.d.ts +14 -0
- package/dist/channels/imessage/index.js +64 -17
- package/dist/channels/index.d.ts +4 -0
- package/dist/channels/index.js +4 -0
- package/dist/channels/irc/index.d.ts +73 -3
- package/dist/channels/irc/index.js +446 -11
- package/dist/channels/line/index.js +3 -0
- package/dist/channels/mattermost/index.d.ts +51 -4
- package/dist/channels/mattermost/index.js +303 -20
- package/dist/channels/nextcloud-talk/index.d.ts +89 -8
- package/dist/channels/nextcloud-talk/index.js +367 -16
- package/dist/channels/nostr/index.d.ts +121 -1
- package/dist/channels/nostr/index.js +396 -8
- package/dist/channels/ntfy/index.js +3 -0
- package/dist/channels/qq/index.js +3 -0
- package/dist/channels/slash-parity.d.ts +117 -0
- package/dist/channels/slash-parity.js +185 -0
- package/dist/channels/synology-chat/index.js +3 -0
- package/dist/channels/teams/index.d.ts +3 -0
- package/dist/channels/teams/index.js +3 -0
- package/dist/channels/telegram/client.d.ts +9 -0
- package/dist/channels/telegram/client.js +12 -0
- package/dist/channels/twilio-voice/index.js +3 -0
- package/dist/channels/webchat/index.d.ts +7 -1
- package/dist/channels/webchat/index.js +7 -1
- package/dist/channels/wecom/index.js +3 -0
- package/dist/channels/weixin/index.js +3 -0
- package/dist/channels/zalo/index.js +3 -0
- package/dist/cli/config-loader.js +1 -1
- package/dist/codebuddy/client.d.ts +30 -1
- package/dist/codebuddy/client.js +167 -20
- package/dist/codebuddy/providers/provider-chatgpt-responses.d.ts +5 -0
- package/dist/codebuddy/providers/provider-chatgpt-responses.js +7 -1
- package/dist/codebuddy/providers/provider-gemini-native.js +1 -1
- package/dist/codebuddy/providers/provider-openai-compat.d.ts +5 -0
- package/dist/codebuddy/providers/provider-openai-compat.js +82 -2
- package/dist/codebuddy/tool-definitions/computer-control-tools.js +32 -2
- package/dist/codebuddy/tool-definitions/core-tools.js +3 -3
- package/dist/codebuddy/tool-definitions/index.d.ts +2 -1
- package/dist/codebuddy/tool-definitions/index.js +2 -1
- package/dist/codebuddy/tool-definitions/multimodal-tools.d.ts +1 -0
- package/dist/codebuddy/tool-definitions/multimodal-tools.js +44 -1
- package/dist/codebuddy/tool-definitions/windows-tools.d.ts +2 -0
- package/dist/codebuddy/tool-definitions/windows-tools.js +34 -0
- package/dist/codebuddy/tools.d.ts +8 -1
- package/dist/codebuddy/tools.js +67 -24
- package/dist/commands/cli/hermes-commands.js +108 -0
- package/dist/commands/cli/improve-command.js +123 -0
- package/dist/commands/cli/native-engine-commands.js +102 -3
- package/dist/commands/cli/utility-commands.js +45 -0
- package/dist/commands/enhanced-command-handler.js +2 -2
- package/dist/commands/goal-cli.d.ts +30 -0
- package/dist/commands/goal-cli.js +210 -27
- package/dist/commands/handlers/goal-handler.d.ts +9 -2
- package/dist/commands/handlers/goal-handler.js +36 -9
- package/dist/commands/handlers/infra-handlers.js +36 -0
- package/dist/commands/handlers/memory-handlers.js +96 -1
- package/dist/commands/handlers/missing-handlers.d.ts +1 -1
- package/dist/commands/handlers/missing-handlers.js +26 -3
- package/dist/commands/headless-slash.d.ts +2 -0
- package/dist/commands/headless-slash.js +12 -0
- package/dist/commands/llm-provider-resolution.d.ts +5 -3
- package/dist/commands/llm-provider-resolution.js +87 -33
- package/dist/commands/ollama.d.ts +25 -0
- package/dist/commands/ollama.js +100 -0
- package/dist/commands/provider.d.ts +5 -0
- package/dist/commands/provider.js +106 -55
- package/dist/commands/skills-cli/index.js +123 -0
- package/dist/commands/slash/builtin-commands.js +1 -1
- package/dist/commands/spec-next.js +2 -1
- package/dist/commands/spec-plan.js +5 -16
- package/dist/commands/tunnel.d.ts +2 -0
- package/dist/commands/tunnel.js +48 -0
- package/dist/companion/percepts.js +11 -1
- package/dist/config/config-resolver.d.ts +2 -1
- package/dist/config/config-resolver.js +54 -41
- package/dist/config/constants.d.ts +28 -0
- package/dist/config/constants.js +7 -0
- package/dist/config/env-schema.js +756 -0
- package/dist/config/model-tools.js +4 -4
- package/dist/config/toml-config.d.ts +16 -0
- package/dist/config/toml-config.js +3 -0
- package/dist/context/bootstrap-loader.js +6 -23
- package/dist/context/context-manager-v2.js +1 -0
- package/dist/context/import-directive-parser.d.ts +4 -0
- package/dist/context/import-directive-parser.js +51 -6
- package/dist/context/instruction-excludes.d.ts +30 -1
- package/dist/context/instruction-excludes.js +71 -1
- package/dist/context/jit-context.d.ts +8 -10
- package/dist/context/jit-context.js +28 -106
- package/dist/context/project-context.d.ts +90 -0
- package/dist/context/project-context.js +295 -0
- package/dist/daemon/autonomous-daemon.d.ts +1 -1
- package/dist/daemon/autonomous-daemon.js +3 -3
- package/dist/daemon/autonomous-loop.d.ts +31 -1
- package/dist/daemon/autonomous-loop.js +91 -4
- package/dist/daemon/colab-goal.js +9 -1
- package/dist/daemon/cron-agent-bridge.d.ts +12 -3
- package/dist/daemon/cron-agent-bridge.js +25 -9
- package/dist/desktop/codebuddy-engine-adapter.d.ts +32 -0
- package/dist/desktop/codebuddy-engine-adapter.js +257 -80
- package/dist/desktop/engine-adapter.d.ts +14 -0
- package/dist/desktop-automation/automation-manager.js +16 -0
- package/dist/desktop-automation/omniparser-runner.d.ts +63 -0
- package/dist/desktop-automation/omniparser-runner.js +115 -0
- package/dist/desktop-automation/smart-snapshot.d.ts +11 -0
- package/dist/desktop-automation/smart-snapshot.js +72 -5
- package/dist/doctor/index.js +40 -23
- package/dist/events/types.d.ts +11 -0
- package/dist/fleet/colab-store.js +14 -2
- package/dist/fleet/peer-session-bridge.js +50 -12
- package/dist/fleet/peer-tool-bridge.js +14 -0
- package/dist/goals/goal-decomposer.d.ts +27 -0
- package/dist/goals/goal-decomposer.js +293 -0
- package/dist/goals/goal-judge-client.d.ts +15 -0
- package/dist/goals/goal-judge-client.js +45 -0
- package/dist/goals/goal-judge.d.ts +2 -0
- package/dist/goals/goal-judge.js +35 -6
- package/dist/goals/goal-loop.d.ts +14 -0
- package/dist/goals/goal-loop.js +47 -2
- package/dist/goals/goal-manager.d.ts +6 -0
- package/dist/goals/goal-manager.js +67 -14
- package/dist/goals/goal-state.d.ts +13 -0
- package/dist/goals/goal-state.js +83 -13
- package/dist/goals/goal-store.d.ts +3 -0
- package/dist/goals/goal-store.js +31 -15
- package/dist/harness/contract.d.ts +28 -28
- package/dist/hooks/user-hooks.js +17 -3
- package/dist/identity/identity-manager.js +3 -2
- package/dist/index.js +481 -26
- package/dist/input/text-to-speech.d.ts +2 -6
- package/dist/input/text-to-speech.js +2 -27
- package/dist/integrations/screenpipe/screenpipe-client.d.ts +10 -0
- package/dist/integrations/screenpipe/screenpipe-client.js +12 -2
- package/dist/integrations/tailscale.d.ts +13 -0
- package/dist/integrations/tailscale.js +87 -34
- package/dist/knowledge/workspace-indexer.js +53 -9
- package/dist/mcp/client.js +1 -0
- package/dist/mcp/mcp-desktop-tools.d.ts +23 -0
- package/dist/mcp/mcp-desktop-tools.js +158 -0
- package/dist/mcp/mcp-resources.js +2 -3
- package/dist/mcp/mcp-server.js +2 -0
- package/dist/memory/enhanced-memory.d.ts +10 -0
- package/dist/memory/enhanced-memory.js +33 -2
- package/dist/memory/index.d.ts +3 -1
- package/dist/memory/index.js +4 -1
- package/dist/memory/memory-auto-proposer.d.ts +23 -0
- package/dist/memory/memory-auto-proposer.js +308 -0
- package/dist/memory/memory-candidate-queue.d.ts +92 -0
- package/dist/memory/memory-candidate-queue.js +261 -0
- package/dist/memory/persistent-memory.d.ts +45 -2
- package/dist/memory/persistent-memory.js +236 -40
- package/dist/ml/bayesian-qualifier.d.ts +1 -1
- package/dist/ml/bayesian-qualifier.js +35 -2
- package/dist/openclaw/gateway-bridge.js +5 -0
- package/dist/plugins/marketplace.d.ts +1 -0
- package/dist/plugins/marketplace.js +7 -0
- package/dist/prompts/execution-discipline.d.ts +14 -0
- package/dist/prompts/execution-discipline.js +29 -0
- package/dist/prompts/variation-injector.js +9 -2
- package/dist/protocols/acp/acp-agentic-runner.d.ts +7 -6
- package/dist/protocols/acp/acp-agentic-runner.js +115 -18
- package/dist/protocols/acp/acp-session-store.d.ts +23 -0
- package/dist/protocols/acp/acp-session-store.js +77 -0
- package/dist/protocols/acp/acp-stdio-server.d.ts +7 -0
- package/dist/protocols/acp/acp-stdio-server.js +71 -16
- package/dist/providers/active-llm-registry.d.ts +37 -0
- package/dist/providers/active-llm-registry.js +186 -0
- package/dist/providers/auxiliary-provider.d.ts +25 -0
- package/dist/providers/auxiliary-provider.js +192 -0
- package/dist/providers/codex-oauth.d.ts +1 -1
- package/dist/providers/codex-oauth.js +27 -4
- package/dist/providers/index.d.ts +3 -0
- package/dist/providers/index.js +4 -0
- package/dist/providers/provider-catalog.d.ts +70 -0
- package/dist/providers/provider-catalog.js +738 -0
- package/dist/providers/provider-fallback.d.ts +35 -0
- package/dist/providers/provider-fallback.js +171 -0
- package/dist/providers/xai-oauth.d.ts +128 -0
- package/dist/providers/xai-oauth.js +735 -0
- package/dist/scheduler/cron-scheduler.d.ts +15 -3
- package/dist/scheduler/cron-scheduler.js +53 -7
- package/dist/scheduler/script-runner.d.ts +2 -0
- package/dist/scheduler/script-runner.js +19 -4
- package/dist/scheduler/watchdog-handlers.js +7 -20
- package/dist/search/usearch-index.js +7 -2
- package/dist/security/tool-policy/tool-groups.js +2 -0
- package/dist/sensory/dreaming.d.ts +45 -0
- package/dist/sensory/dreaming.js +114 -0
- package/dist/sensory/heartbeat-scheduler.d.ts +38 -0
- package/dist/sensory/heartbeat-scheduler.js +72 -0
- package/dist/sensory/reactions.d.ts +24 -0
- package/dist/sensory/reactions.js +31 -0
- package/dist/sensory/screen-reaction.d.ts +23 -0
- package/dist/sensory/screen-reaction.js +59 -0
- package/dist/sensory/sensory-bridge.d.ts +23 -0
- package/dist/sensory/sensory-bridge.js +85 -0
- package/dist/sensory/sensory-memory.d.ts +20 -0
- package/dist/sensory/sensory-memory.js +39 -0
- package/dist/sensory/speech-reaction.d.ts +21 -0
- package/dist/sensory/speech-reaction.js +83 -0
- package/dist/sensory/vision-reaction.d.ts +31 -0
- package/dist/sensory/vision-reaction.js +74 -0
- package/dist/server/index.js +106 -2
- package/dist/server/routes/mobile.d.ts +12 -4
- package/dist/server/routes/mobile.js +116 -1
- package/dist/server/tls-config.d.ts +35 -0
- package/dist/server/tls-config.js +142 -0
- package/dist/server/tunnel-manager.d.ts +20 -0
- package/dist/server/tunnel-manager.js +58 -0
- package/dist/server/websocket/fleet-bridge.js +5 -0
- package/dist/services/prompt-builder.d.ts +11 -0
- package/dist/services/prompt-builder.js +124 -17
- package/dist/shared/engine-types.d.ts +15 -1
- package/dist/sidecar/sidecar-bridge.d.ts +1 -0
- package/dist/sidecar/sidecar-bridge.js +21 -10
- package/dist/skills/parser.js +3 -0
- package/dist/skills/skill-importer.d.ts +58 -0
- package/dist/skills/skill-importer.js +261 -0
- package/dist/skills/skill-sources.d.ts +20 -0
- package/dist/skills/skill-sources.js +102 -0
- package/dist/skills/types.d.ts +6 -0
- package/dist/spec/spec-store.js +8 -1
- package/dist/talk-mode/providers/audioreader-tts.d.ts +1 -1
- package/dist/talk-mode/providers/audioreader-tts.js +8 -8
- package/dist/templates/project-scaffolding.js +3 -2
- package/dist/themes/theme-schema.d.ts +10 -10
- package/dist/tools/application-profiles.js +38 -0
- package/dist/tools/bash/bash-tool.d.ts +1 -0
- package/dist/tools/bash/bash-tool.js +16 -2
- package/dist/tools/bash/command-validator.js +3 -0
- package/dist/tools/bash/rtk-rewrite.d.ts +16 -0
- package/dist/tools/bash/rtk-rewrite.js +100 -0
- package/dist/tools/bash/streaming-executor.js +14 -1
- package/dist/tools/computer-control-tool.d.ts +16 -1
- package/dist/tools/computer-control-tool.js +317 -5
- package/dist/tools/document-generator.d.ts +14 -0
- package/dist/tools/document-generator.js +79 -0
- package/dist/tools/execute-code-rpc-invoker.d.ts +26 -2
- package/dist/tools/execute-code-rpc-invoker.js +46 -5
- package/dist/tools/metadata.js +28 -0
- package/dist/tools/office-macro-tool.d.ts +10 -0
- package/dist/tools/office-macro-tool.js +93 -0
- package/dist/tools/register-tool-handler.d.ts +25 -0
- package/dist/tools/register-tool-handler.js +100 -0
- package/dist/tools/registry/index.d.ts +2 -1
- package/dist/tools/registry/index.js +2 -1
- package/dist/tools/registry/memory-tools.d.ts +20 -0
- package/dist/tools/registry/memory-tools.js +225 -3
- package/dist/tools/registry/multimodal-tools.js +2 -2
- package/dist/tools/registry/vision-tools.d.ts +33 -0
- package/dist/tools/registry/vision-tools.js +308 -6
- package/dist/tools/registry/windows-tools.d.ts +2 -0
- package/dist/tools/registry/windows-tools.js +47 -0
- package/dist/tools/registry.d.ts +6 -0
- package/dist/tools/registry.js +8 -0
- package/dist/tools/route-peer-tool.js +14 -0
- package/dist/tools/screenshot-tool.js +14 -2
- package/dist/tools/text-to-speech-tool.d.ts +1 -1
- package/dist/tools/text-to-speech-tool.js +2 -12
- package/dist/tools/tool-selector.js +3 -59
- package/dist/utils/config-validation/schema.d.ts +40 -7
- package/dist/utils/config-validation/schema.js +130 -1
- package/dist/utils/cost-tracker.js +21 -1
- package/dist/utils/disk-guard.d.ts +180 -0
- package/dist/utils/disk-guard.js +404 -0
- package/dist/utils/init-project.d.ts +7 -0
- package/dist/utils/init-project.js +37 -0
- package/dist/utils/interactive-setup.js +35 -2
- package/dist/utils/model-utils.js +11 -2
- package/dist/utils/output-sanitizer.js +11 -0
- package/dist/utils/provider-detector.d.ts +8 -7
- package/dist/utils/provider-detector.js +19 -77
- package/dist/utils/settings-manager.d.ts +19 -0
- package/dist/utils/token-display.d.ts +3 -3
- package/dist/utils/token-display.js +22 -4
- package/dist/wizard/onboarding.d.ts +39 -0
- package/dist/wizard/onboarding.js +433 -21
- package/dist/wizard/provider-onboarding.d.ts +8 -23
- package/dist/wizard/provider-onboarding.js +39 -158
- package/package.json +4 -2
- package/dist/plugins/provider-onboarding.d.ts +0 -23
- package/dist/plugins/provider-onboarding.js +0 -116
- package/dist/utils/kokoro-tts.d.ts +0 -8
- package/dist/utils/kokoro-tts.js +0 -67
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hibernate/wake lifecycle semantics for Hermes runtime backends.
|
|
3
|
+
*
|
|
4
|
+
* Each backend gets a strategy that maps to concrete CLI invocations:
|
|
5
|
+
* - local/wsl/os-sandbox/singularity: lifecycle unsupported (always running or job-based)
|
|
6
|
+
* - docker: pause / unpause / inspect
|
|
7
|
+
* - ssh: connection close / reconnect
|
|
8
|
+
* - modal: app stop / run
|
|
9
|
+
* - daytona: workspace stop / start
|
|
10
|
+
* - vercel-sandbox: unsupported (no lifecycle API)
|
|
11
|
+
*/
|
|
12
|
+
import os from 'os';
|
|
13
|
+
import { spawnSync } from 'child_process';
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Constants
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
const UNSUPPORTED_BACKEND_IDS = new Set([
|
|
18
|
+
'local',
|
|
19
|
+
'wsl',
|
|
20
|
+
'os-sandbox',
|
|
21
|
+
'singularity',
|
|
22
|
+
'vercel-sandbox',
|
|
23
|
+
]);
|
|
24
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
25
|
+
const BACKEND_LABELS = {
|
|
26
|
+
'local': 'Local process',
|
|
27
|
+
'docker': 'Docker sandbox',
|
|
28
|
+
'wsl': 'WSL',
|
|
29
|
+
'os-sandbox': 'Native OS sandbox',
|
|
30
|
+
'ssh': 'SSH remote shell',
|
|
31
|
+
'singularity': 'Singularity/Apptainer',
|
|
32
|
+
'modal': 'Modal',
|
|
33
|
+
'daytona': 'Daytona',
|
|
34
|
+
'vercel-sandbox': 'Vercel Sandbox',
|
|
35
|
+
};
|
|
36
|
+
const ALL_BACKEND_IDS = Object.keys(BACKEND_LABELS);
|
|
37
|
+
function runSpawn(command, args, env, timeoutMs) {
|
|
38
|
+
try {
|
|
39
|
+
const result = spawnSync(command, args, {
|
|
40
|
+
env,
|
|
41
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
42
|
+
timeout: timeoutMs,
|
|
43
|
+
windowsHide: true,
|
|
44
|
+
});
|
|
45
|
+
const stdout = decodeBuffer(result.stdout).trim();
|
|
46
|
+
const stderr = decodeBuffer(result.stderr).trim();
|
|
47
|
+
return {
|
|
48
|
+
exitCode: result.status,
|
|
49
|
+
ok: !result.error && result.status === 0,
|
|
50
|
+
stdout,
|
|
51
|
+
stderr,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return { exitCode: null, ok: false, stdout: '', stderr: '' };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function decodeBuffer(value) {
|
|
59
|
+
if (!value)
|
|
60
|
+
return '';
|
|
61
|
+
if (typeof value === 'string')
|
|
62
|
+
return value;
|
|
63
|
+
return value.toString('utf8');
|
|
64
|
+
}
|
|
65
|
+
function unsupportedAction(backendId, action, durationMs) {
|
|
66
|
+
return {
|
|
67
|
+
backendId,
|
|
68
|
+
action,
|
|
69
|
+
result: 'unsupported',
|
|
70
|
+
state: 'unsupported',
|
|
71
|
+
detail: `${BACKEND_LABELS[backendId] ?? backendId} does not support managed lifecycle operations.`,
|
|
72
|
+
durationMs,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function failedAction(backendId, action, detail, durationMs) {
|
|
76
|
+
return {
|
|
77
|
+
backendId,
|
|
78
|
+
action,
|
|
79
|
+
result: 'failed',
|
|
80
|
+
state: 'unknown',
|
|
81
|
+
detail,
|
|
82
|
+
durationMs,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// Docker lifecycle
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
function dockerHibernate(containerId, env, timeoutMs) {
|
|
89
|
+
const start = Date.now();
|
|
90
|
+
if (!containerId) {
|
|
91
|
+
return failedAction('docker', 'hibernate', 'containerId is required for docker hibernate.', 0);
|
|
92
|
+
}
|
|
93
|
+
const result = runSpawn('docker', ['pause', containerId], env, timeoutMs);
|
|
94
|
+
const durationMs = Date.now() - start;
|
|
95
|
+
if (result.ok) {
|
|
96
|
+
return {
|
|
97
|
+
backendId: 'docker',
|
|
98
|
+
action: 'hibernate',
|
|
99
|
+
result: 'success',
|
|
100
|
+
state: 'hibernated',
|
|
101
|
+
detail: `Container ${containerId} paused.`,
|
|
102
|
+
durationMs,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return failedAction('docker', 'hibernate', result.stderr || `docker pause failed (exit ${result.exitCode}).`, durationMs);
|
|
106
|
+
}
|
|
107
|
+
function dockerWake(containerId, env, timeoutMs) {
|
|
108
|
+
const start = Date.now();
|
|
109
|
+
if (!containerId) {
|
|
110
|
+
return failedAction('docker', 'wake', 'containerId is required for docker wake.', 0);
|
|
111
|
+
}
|
|
112
|
+
const result = runSpawn('docker', ['unpause', containerId], env, timeoutMs);
|
|
113
|
+
const durationMs = Date.now() - start;
|
|
114
|
+
if (result.ok) {
|
|
115
|
+
return {
|
|
116
|
+
backendId: 'docker',
|
|
117
|
+
action: 'wake',
|
|
118
|
+
result: 'success',
|
|
119
|
+
state: 'running',
|
|
120
|
+
detail: `Container ${containerId} unpaused.`,
|
|
121
|
+
durationMs,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return failedAction('docker', 'wake', result.stderr || `docker unpause failed (exit ${result.exitCode}).`, durationMs);
|
|
125
|
+
}
|
|
126
|
+
function dockerStatus(containerId, env, timeoutMs) {
|
|
127
|
+
const start = Date.now();
|
|
128
|
+
if (!containerId) {
|
|
129
|
+
return failedAction('docker', 'status', 'containerId is required for docker status.', 0);
|
|
130
|
+
}
|
|
131
|
+
const result = runSpawn('docker', ['inspect', '--format', '{{.State.Status}}', containerId], env, timeoutMs);
|
|
132
|
+
const durationMs = Date.now() - start;
|
|
133
|
+
if (!result.ok) {
|
|
134
|
+
return failedAction('docker', 'status', result.stderr || `docker inspect failed (exit ${result.exitCode}).`, durationMs);
|
|
135
|
+
}
|
|
136
|
+
const raw = result.stdout.trim().toLowerCase();
|
|
137
|
+
const state = dockerStatusToLifecycleState(raw);
|
|
138
|
+
return {
|
|
139
|
+
backendId: 'docker',
|
|
140
|
+
action: 'status',
|
|
141
|
+
result: 'success',
|
|
142
|
+
state,
|
|
143
|
+
detail: `Container ${containerId} status: ${raw}.`,
|
|
144
|
+
durationMs,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function dockerStatusToLifecycleState(dockerState) {
|
|
148
|
+
switch (dockerState) {
|
|
149
|
+
case 'running':
|
|
150
|
+
return 'running';
|
|
151
|
+
case 'paused':
|
|
152
|
+
return 'hibernated';
|
|
153
|
+
case 'restarting':
|
|
154
|
+
return 'starting';
|
|
155
|
+
case 'removing':
|
|
156
|
+
case 'exited':
|
|
157
|
+
case 'dead':
|
|
158
|
+
return 'stopping';
|
|
159
|
+
default:
|
|
160
|
+
return 'unknown';
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
// SSH lifecycle
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
function sshHibernate(_sshHost, _env, _timeoutMs) {
|
|
167
|
+
// SSH "hibernate" = drop connection. We don't manage persistent connections,
|
|
168
|
+
// so we just report success — the SSH session will close naturally.
|
|
169
|
+
return {
|
|
170
|
+
backendId: 'ssh',
|
|
171
|
+
action: 'hibernate',
|
|
172
|
+
result: 'success',
|
|
173
|
+
state: 'hibernated',
|
|
174
|
+
detail: 'SSH connection released; keepalive stopped.',
|
|
175
|
+
durationMs: 0,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function sshWake(sshHost, env, timeoutMs) {
|
|
179
|
+
const start = Date.now();
|
|
180
|
+
if (!sshHost) {
|
|
181
|
+
return failedAction('ssh', 'wake', 'sshHost is required for SSH wake.', 0);
|
|
182
|
+
}
|
|
183
|
+
const result = runSpawn('ssh', ['-T', '-o', 'ConnectTimeout=10', sshHost, 'true'], env, timeoutMs);
|
|
184
|
+
const durationMs = Date.now() - start;
|
|
185
|
+
if (result.ok) {
|
|
186
|
+
return {
|
|
187
|
+
backendId: 'ssh',
|
|
188
|
+
action: 'wake',
|
|
189
|
+
result: 'success',
|
|
190
|
+
state: 'running',
|
|
191
|
+
detail: `SSH connection to ${sshHost} verified.`,
|
|
192
|
+
durationMs,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
return failedAction('ssh', 'wake', result.stderr || `SSH connection to ${sshHost} failed (exit ${result.exitCode}).`, durationMs);
|
|
196
|
+
}
|
|
197
|
+
function sshStatus(sshHost, env, timeoutMs) {
|
|
198
|
+
const start = Date.now();
|
|
199
|
+
if (!sshHost) {
|
|
200
|
+
return failedAction('ssh', 'status', 'sshHost is required for SSH status.', 0);
|
|
201
|
+
}
|
|
202
|
+
const result = runSpawn('ssh', ['-T', '-o', 'ConnectTimeout=5', sshHost, 'true'], env, timeoutMs);
|
|
203
|
+
const durationMs = Date.now() - start;
|
|
204
|
+
if (result.ok) {
|
|
205
|
+
return {
|
|
206
|
+
backendId: 'ssh',
|
|
207
|
+
action: 'status',
|
|
208
|
+
result: 'success',
|
|
209
|
+
state: 'running',
|
|
210
|
+
detail: `SSH host ${sshHost} is reachable.`,
|
|
211
|
+
durationMs,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
backendId: 'ssh',
|
|
216
|
+
action: 'status',
|
|
217
|
+
result: 'success',
|
|
218
|
+
state: 'hibernated',
|
|
219
|
+
detail: `SSH host ${sshHost} is not reachable: ${result.stderr || `exit ${result.exitCode}`}.`,
|
|
220
|
+
durationMs,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
// Modal lifecycle
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
function modalHibernate(modalApp, env, timeoutMs) {
|
|
227
|
+
const start = Date.now();
|
|
228
|
+
if (!modalApp) {
|
|
229
|
+
return failedAction('modal', 'hibernate', 'modalApp is required for Modal hibernate.', 0);
|
|
230
|
+
}
|
|
231
|
+
const result = runSpawn('modal', ['app', 'stop', modalApp], env, timeoutMs);
|
|
232
|
+
const durationMs = Date.now() - start;
|
|
233
|
+
if (result.ok) {
|
|
234
|
+
return {
|
|
235
|
+
backendId: 'modal',
|
|
236
|
+
action: 'hibernate',
|
|
237
|
+
result: 'success',
|
|
238
|
+
state: 'hibernated',
|
|
239
|
+
detail: `Modal app ${modalApp} stopped.`,
|
|
240
|
+
durationMs,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
return failedAction('modal', 'hibernate', result.stderr || `modal app stop failed (exit ${result.exitCode}).`, durationMs);
|
|
244
|
+
}
|
|
245
|
+
function modalWake(modalApp, env, timeoutMs) {
|
|
246
|
+
const start = Date.now();
|
|
247
|
+
if (!modalApp) {
|
|
248
|
+
return failedAction('modal', 'wake', 'modalApp is required for Modal wake.', 0);
|
|
249
|
+
}
|
|
250
|
+
const result = runSpawn('modal', ['run', modalApp], env, timeoutMs);
|
|
251
|
+
const durationMs = Date.now() - start;
|
|
252
|
+
if (result.ok) {
|
|
253
|
+
return {
|
|
254
|
+
backendId: 'modal',
|
|
255
|
+
action: 'wake',
|
|
256
|
+
result: 'success',
|
|
257
|
+
state: 'running',
|
|
258
|
+
detail: `Modal app ${modalApp} started.`,
|
|
259
|
+
durationMs,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
return failedAction('modal', 'wake', result.stderr || `modal run failed (exit ${result.exitCode}).`, durationMs);
|
|
263
|
+
}
|
|
264
|
+
function modalStatus(modalApp, env, timeoutMs) {
|
|
265
|
+
const start = Date.now();
|
|
266
|
+
if (!modalApp) {
|
|
267
|
+
return failedAction('modal', 'status', 'modalApp is required for Modal status.', 0);
|
|
268
|
+
}
|
|
269
|
+
const result = runSpawn('modal', ['app', 'list'], env, timeoutMs);
|
|
270
|
+
const durationMs = Date.now() - start;
|
|
271
|
+
if (!result.ok) {
|
|
272
|
+
return failedAction('modal', 'status', result.stderr || `modal app list failed (exit ${result.exitCode}).`, durationMs);
|
|
273
|
+
}
|
|
274
|
+
const isRunning = result.stdout.includes(modalApp);
|
|
275
|
+
return {
|
|
276
|
+
backendId: 'modal',
|
|
277
|
+
action: 'status',
|
|
278
|
+
result: 'success',
|
|
279
|
+
state: isRunning ? 'running' : 'hibernated',
|
|
280
|
+
detail: isRunning
|
|
281
|
+
? `Modal app ${modalApp} appears in app list.`
|
|
282
|
+
: `Modal app ${modalApp} not found in app list.`,
|
|
283
|
+
durationMs,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
// ---------------------------------------------------------------------------
|
|
287
|
+
// Daytona lifecycle
|
|
288
|
+
// ---------------------------------------------------------------------------
|
|
289
|
+
function daytonaHibernate(workspace, env, timeoutMs) {
|
|
290
|
+
const start = Date.now();
|
|
291
|
+
if (!workspace) {
|
|
292
|
+
return failedAction('daytona', 'hibernate', 'daytonaWorkspace is required for Daytona hibernate.', 0);
|
|
293
|
+
}
|
|
294
|
+
const result = runSpawn('daytona', ['workspace', 'stop', workspace], env, timeoutMs);
|
|
295
|
+
const durationMs = Date.now() - start;
|
|
296
|
+
if (result.ok) {
|
|
297
|
+
return {
|
|
298
|
+
backendId: 'daytona',
|
|
299
|
+
action: 'hibernate',
|
|
300
|
+
result: 'success',
|
|
301
|
+
state: 'hibernated',
|
|
302
|
+
detail: `Daytona workspace ${workspace} stopped.`,
|
|
303
|
+
durationMs,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
return failedAction('daytona', 'hibernate', result.stderr || `daytona workspace stop failed (exit ${result.exitCode}).`, durationMs);
|
|
307
|
+
}
|
|
308
|
+
function daytonaWake(workspace, env, timeoutMs) {
|
|
309
|
+
const start = Date.now();
|
|
310
|
+
if (!workspace) {
|
|
311
|
+
return failedAction('daytona', 'wake', 'daytonaWorkspace is required for Daytona wake.', 0);
|
|
312
|
+
}
|
|
313
|
+
const result = runSpawn('daytona', ['workspace', 'start', workspace], env, timeoutMs);
|
|
314
|
+
const durationMs = Date.now() - start;
|
|
315
|
+
if (result.ok) {
|
|
316
|
+
return {
|
|
317
|
+
backendId: 'daytona',
|
|
318
|
+
action: 'wake',
|
|
319
|
+
result: 'success',
|
|
320
|
+
state: 'running',
|
|
321
|
+
detail: `Daytona workspace ${workspace} started.`,
|
|
322
|
+
durationMs,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
return failedAction('daytona', 'wake', result.stderr || `daytona workspace start failed (exit ${result.exitCode}).`, durationMs);
|
|
326
|
+
}
|
|
327
|
+
function daytonaStatus(workspace, env, timeoutMs) {
|
|
328
|
+
const start = Date.now();
|
|
329
|
+
if (!workspace) {
|
|
330
|
+
return failedAction('daytona', 'status', 'daytonaWorkspace is required for Daytona status.', 0);
|
|
331
|
+
}
|
|
332
|
+
const result = runSpawn('daytona', ['workspace', 'list'], env, timeoutMs);
|
|
333
|
+
const durationMs = Date.now() - start;
|
|
334
|
+
if (!result.ok) {
|
|
335
|
+
return failedAction('daytona', 'status', result.stderr || `daytona workspace list failed (exit ${result.exitCode}).`, durationMs);
|
|
336
|
+
}
|
|
337
|
+
const isRunning = result.stdout.includes(workspace);
|
|
338
|
+
return {
|
|
339
|
+
backendId: 'daytona',
|
|
340
|
+
action: 'status',
|
|
341
|
+
result: 'success',
|
|
342
|
+
state: isRunning ? 'running' : 'hibernated',
|
|
343
|
+
detail: isRunning
|
|
344
|
+
? `Daytona workspace ${workspace} appears in workspace list.`
|
|
345
|
+
: `Daytona workspace ${workspace} not found in workspace list.`,
|
|
346
|
+
durationMs,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
// ---------------------------------------------------------------------------
|
|
350
|
+
// Public API
|
|
351
|
+
// ---------------------------------------------------------------------------
|
|
352
|
+
/**
|
|
353
|
+
* Execute a lifecycle action on a backend.
|
|
354
|
+
*/
|
|
355
|
+
export function executeBackendLifecycleAction(backendId, action, options = {}) {
|
|
356
|
+
const env = options.env ?? process.env;
|
|
357
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
358
|
+
if (UNSUPPORTED_BACKEND_IDS.has(backendId)) {
|
|
359
|
+
return unsupportedAction(backendId, action, 0);
|
|
360
|
+
}
|
|
361
|
+
if (!BACKEND_LABELS[backendId]) {
|
|
362
|
+
return failedAction(backendId, action, `Unknown backend: ${backendId}.`, 0);
|
|
363
|
+
}
|
|
364
|
+
switch (backendId) {
|
|
365
|
+
case 'docker': {
|
|
366
|
+
const containerId = options.containerId ?? env.CODEBUDDY_DOCKER_CONTAINER ?? '';
|
|
367
|
+
if (action === 'hibernate')
|
|
368
|
+
return dockerHibernate(containerId, env, timeoutMs);
|
|
369
|
+
if (action === 'wake')
|
|
370
|
+
return dockerWake(containerId, env, timeoutMs);
|
|
371
|
+
return dockerStatus(containerId, env, timeoutMs);
|
|
372
|
+
}
|
|
373
|
+
case 'ssh': {
|
|
374
|
+
const sshHost = options.sshHost
|
|
375
|
+
?? env.CODEBUDDY_SSH_HOST
|
|
376
|
+
?? env.SSH_HOST
|
|
377
|
+
?? env.CODEBUDDY_REMOTE_HOST
|
|
378
|
+
?? '';
|
|
379
|
+
if (action === 'hibernate')
|
|
380
|
+
return sshHibernate(sshHost, env, timeoutMs);
|
|
381
|
+
if (action === 'wake')
|
|
382
|
+
return sshWake(sshHost, env, timeoutMs);
|
|
383
|
+
return sshStatus(sshHost, env, timeoutMs);
|
|
384
|
+
}
|
|
385
|
+
case 'modal': {
|
|
386
|
+
const modalApp = options.modalApp ?? env.CODEBUDDY_MODAL_APP ?? '';
|
|
387
|
+
if (action === 'hibernate')
|
|
388
|
+
return modalHibernate(modalApp, env, timeoutMs);
|
|
389
|
+
if (action === 'wake')
|
|
390
|
+
return modalWake(modalApp, env, timeoutMs);
|
|
391
|
+
return modalStatus(modalApp, env, timeoutMs);
|
|
392
|
+
}
|
|
393
|
+
case 'daytona': {
|
|
394
|
+
const workspace = options.daytonaWorkspace ?? env.CODEBUDDY_DAYTONA_WORKSPACE ?? '';
|
|
395
|
+
if (action === 'hibernate')
|
|
396
|
+
return daytonaHibernate(workspace, env, timeoutMs);
|
|
397
|
+
if (action === 'wake')
|
|
398
|
+
return daytonaWake(workspace, env, timeoutMs);
|
|
399
|
+
return daytonaStatus(workspace, env, timeoutMs);
|
|
400
|
+
}
|
|
401
|
+
default:
|
|
402
|
+
return unsupportedAction(backendId, action, 0);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Build a lifecycle status report for all backends.
|
|
407
|
+
*/
|
|
408
|
+
export function buildBackendLifecycleStatusReport(options = {}) {
|
|
409
|
+
const now = options.now ?? (() => new Date());
|
|
410
|
+
const entries = ALL_BACKEND_IDS.map((backendId) => {
|
|
411
|
+
const lifecycleSupported = !UNSUPPORTED_BACKEND_IDS.has(backendId);
|
|
412
|
+
if (!lifecycleSupported) {
|
|
413
|
+
return {
|
|
414
|
+
backendId,
|
|
415
|
+
label: BACKEND_LABELS[backendId] ?? backendId,
|
|
416
|
+
lifecycleSupported: false,
|
|
417
|
+
state: 'unsupported',
|
|
418
|
+
detail: `${BACKEND_LABELS[backendId] ?? backendId} does not support managed lifecycle operations.`,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
const statusAction = executeBackendLifecycleAction(backendId, 'status', options);
|
|
422
|
+
return {
|
|
423
|
+
backendId,
|
|
424
|
+
label: BACKEND_LABELS[backendId] ?? backendId,
|
|
425
|
+
lifecycleSupported: true,
|
|
426
|
+
state: statusAction.state,
|
|
427
|
+
detail: statusAction.detail,
|
|
428
|
+
};
|
|
429
|
+
});
|
|
430
|
+
return {
|
|
431
|
+
kind: 'hermes_runtime_lifecycle_status',
|
|
432
|
+
schemaVersion: 1,
|
|
433
|
+
generatedAt: now().toISOString(),
|
|
434
|
+
platform: os.platform(),
|
|
435
|
+
arch: os.arch(),
|
|
436
|
+
backends: entries,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Render a lifecycle status report as human-readable text.
|
|
441
|
+
*/
|
|
442
|
+
export function renderBackendLifecycleStatusReport(report) {
|
|
443
|
+
const lines = [
|
|
444
|
+
'Hermes runtime lifecycle status',
|
|
445
|
+
`Platform: ${report.platform}/${report.arch}`,
|
|
446
|
+
`Generated: ${report.generatedAt}`,
|
|
447
|
+
'',
|
|
448
|
+
'Backends:',
|
|
449
|
+
...report.backends.map((entry) => {
|
|
450
|
+
const supported = entry.lifecycleSupported ? 'lifecycle' : 'no-lifecycle';
|
|
451
|
+
return ` ${entry.backendId}: ${entry.state} (${supported}) — ${entry.detail}`;
|
|
452
|
+
}),
|
|
453
|
+
];
|
|
454
|
+
return lines.join('\n');
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Render a single lifecycle action result as human-readable text.
|
|
458
|
+
*/
|
|
459
|
+
export function renderBackendLifecycleAction(action) {
|
|
460
|
+
const lines = [
|
|
461
|
+
`Hermes runtime lifecycle ${action.action} (${action.backendId}): ${action.result}`,
|
|
462
|
+
` State: ${action.state}`,
|
|
463
|
+
` Detail: ${action.detail}`,
|
|
464
|
+
` Duration: ${action.durationMs}ms`,
|
|
465
|
+
];
|
|
466
|
+
return lines.join('\n');
|
|
467
|
+
}
|
|
468
|
+
//# sourceMappingURL=hermes-runtime-lifecycle.js.map
|
|
@@ -18,3 +18,4 @@ export type { AutoRepairConfig } from './auto-repair-middleware.js';
|
|
|
18
18
|
export { QualityGateMiddleware, createQualityGateMiddleware } from './quality-gate-middleware.js';
|
|
19
19
|
export type { QualityGateConfig, QualityGate } from './quality-gate-middleware.js';
|
|
20
20
|
export { ReasoningMiddleware, createReasoningMiddleware } from './reasoning-middleware.js';
|
|
21
|
+
export { VisualValidationMiddleware } from './visual-validation-middleware.js';
|
|
@@ -14,4 +14,5 @@ export { WorkflowGuardMiddleware } from './workflow-guard.js';
|
|
|
14
14
|
export { AutoRepairMiddleware, createAutoRepairMiddleware } from './auto-repair-middleware.js';
|
|
15
15
|
export { QualityGateMiddleware, createQualityGateMiddleware } from './quality-gate-middleware.js';
|
|
16
16
|
export { ReasoningMiddleware, createReasoningMiddleware } from './reasoning-middleware.js';
|
|
17
|
+
export { VisualValidationMiddleware } from './visual-validation-middleware.js';
|
|
17
18
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visual Validation Middleware
|
|
3
|
+
*
|
|
4
|
+
* Checks if the agent has saved an Office document (Excel, PowerPoint, Word).
|
|
5
|
+
* If so, it warns the agent to consider running a visual validation step
|
|
6
|
+
* using `snapshot_with_screenshot` to verify the layout, fonts, and alignments.
|
|
7
|
+
*
|
|
8
|
+
* Priority 156
|
|
9
|
+
*/
|
|
10
|
+
import type { ConversationMiddleware, MiddlewareContext, MiddlewareResult } from './types.js';
|
|
11
|
+
export declare class VisualValidationMiddleware implements ConversationMiddleware {
|
|
12
|
+
readonly name = "visual-validation";
|
|
13
|
+
readonly priority = 156;
|
|
14
|
+
private hasWarnedForFiles;
|
|
15
|
+
afterTurn(context: MiddlewareContext): Promise<MiddlewareResult>;
|
|
16
|
+
private getRecentlySavedOfficeFiles;
|
|
17
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visual Validation Middleware
|
|
3
|
+
*
|
|
4
|
+
* Checks if the agent has saved an Office document (Excel, PowerPoint, Word).
|
|
5
|
+
* If so, it warns the agent to consider running a visual validation step
|
|
6
|
+
* using `snapshot_with_screenshot` to verify the layout, fonts, and alignments.
|
|
7
|
+
*
|
|
8
|
+
* Priority 156
|
|
9
|
+
*/
|
|
10
|
+
import { logger } from '../../utils/logger.js';
|
|
11
|
+
export class VisualValidationMiddleware {
|
|
12
|
+
name = 'visual-validation';
|
|
13
|
+
priority = 156;
|
|
14
|
+
hasWarnedForFiles = new Set();
|
|
15
|
+
async afterTurn(context) {
|
|
16
|
+
if (process.platform !== 'win32') {
|
|
17
|
+
return { action: 'continue' };
|
|
18
|
+
}
|
|
19
|
+
const savedFiles = this.getRecentlySavedOfficeFiles(context);
|
|
20
|
+
if (savedFiles.length === 0) {
|
|
21
|
+
return { action: 'continue' };
|
|
22
|
+
}
|
|
23
|
+
// Filter out files we already warned about
|
|
24
|
+
const newFiles = savedFiles.filter((f) => !this.hasWarnedForFiles.has(f));
|
|
25
|
+
if (newFiles.length === 0) {
|
|
26
|
+
return { action: 'continue' };
|
|
27
|
+
}
|
|
28
|
+
for (const f of newFiles) {
|
|
29
|
+
this.hasWarnedForFiles.add(f);
|
|
30
|
+
}
|
|
31
|
+
logger.info('Visual validation middleware triggered', {
|
|
32
|
+
files: newFiles,
|
|
33
|
+
});
|
|
34
|
+
return {
|
|
35
|
+
action: 'warn',
|
|
36
|
+
message: `You just saved the following Office document(s): ${newFiles.join(', ')}.\n` +
|
|
37
|
+
`To ensure the best quality, please open the document and use the \`snapshot_with_screenshot\` action via \`computer_control\` to visually verify the layout, fonts, and alignment, and auto-correct any visual issues.`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
getRecentlySavedOfficeFiles(context) {
|
|
41
|
+
const files = new Set();
|
|
42
|
+
// Scan recent tool calls in the context history
|
|
43
|
+
for (let i = context.history.length - 1; i >= Math.max(0, context.history.length - 10); i--) {
|
|
44
|
+
const msg = context.history[i];
|
|
45
|
+
if (!msg || !msg.toolCalls)
|
|
46
|
+
continue;
|
|
47
|
+
for (const call of msg.toolCalls) {
|
|
48
|
+
if (call.function.name === 'computer_control') {
|
|
49
|
+
try {
|
|
50
|
+
const args = JSON.parse(call.function.arguments);
|
|
51
|
+
const action = args.action;
|
|
52
|
+
if (['excel_save_workbook', 'powerpoint_save_presentation', 'word_save_document'].includes(action)) {
|
|
53
|
+
if (args.saveAsPath || args.filePath) {
|
|
54
|
+
files.add(args.saveAsPath || args.filePath);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Ignore parse errors
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return Array.from(files);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=visual-validation-middleware.js.map
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live model benchmarking for Tailnet Ollama peers.
|
|
3
|
+
*
|
|
4
|
+
* Measures actual request latency and simple compliance signals so the router
|
|
5
|
+
* can prefer the best network model instead of relying on a static ordering.
|
|
6
|
+
* The benchmark is intentionally lightweight: a few objective prompts, temp=0,
|
|
7
|
+
* streamed completions, and a persisted score cache that the model-tier ladder
|
|
8
|
+
* can use to rank candidates.
|
|
9
|
+
*/
|
|
10
|
+
export interface ModelBenchmarkCandidate {
|
|
11
|
+
model: string;
|
|
12
|
+
baseUrl: string;
|
|
13
|
+
label?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface ModelBenchmarkPrompt {
|
|
16
|
+
name: string;
|
|
17
|
+
prompt: string;
|
|
18
|
+
maxTokens: number;
|
|
19
|
+
validate: (output: string) => boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface ModelBenchmarkRun {
|
|
22
|
+
promptName: string;
|
|
23
|
+
success: boolean;
|
|
24
|
+
compliance: boolean;
|
|
25
|
+
ttftMs: number;
|
|
26
|
+
totalMs: number;
|
|
27
|
+
outputChars: number;
|
|
28
|
+
outputTokensEstimate: number;
|
|
29
|
+
error?: string;
|
|
30
|
+
outputPreview?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface ModelBenchmarkSummary {
|
|
33
|
+
runs: number;
|
|
34
|
+
successes: number;
|
|
35
|
+
complianceRate: number;
|
|
36
|
+
avgTtftMs: number;
|
|
37
|
+
avgTotalMs: number;
|
|
38
|
+
avgOutputTokensEstimate: number;
|
|
39
|
+
score: number;
|
|
40
|
+
}
|
|
41
|
+
export interface ModelBenchmarkReport {
|
|
42
|
+
candidate: ModelBenchmarkCandidate;
|
|
43
|
+
runs: ModelBenchmarkRun[];
|
|
44
|
+
summary: ModelBenchmarkSummary;
|
|
45
|
+
}
|
|
46
|
+
export interface ModelBenchmarkIndexEntry {
|
|
47
|
+
model: string;
|
|
48
|
+
baseUrl: string;
|
|
49
|
+
label?: string;
|
|
50
|
+
score: number;
|
|
51
|
+
complianceRate: number;
|
|
52
|
+
avgTtftMs: number;
|
|
53
|
+
avgTotalMs: number;
|
|
54
|
+
updatedAt: string;
|
|
55
|
+
}
|
|
56
|
+
export interface ModelBenchmarkIndex {
|
|
57
|
+
updatedAt: string;
|
|
58
|
+
suite: string;
|
|
59
|
+
entries: ModelBenchmarkIndexEntry[];
|
|
60
|
+
}
|
|
61
|
+
export interface BenchmarkExecutionOptions {
|
|
62
|
+
runs?: number;
|
|
63
|
+
timeoutMs?: number;
|
|
64
|
+
fetchImpl?: typeof fetch;
|
|
65
|
+
}
|
|
66
|
+
export interface BenchmarkSuiteOptions extends BenchmarkExecutionOptions {
|
|
67
|
+
promptSet?: BenchmarkPromptSetName;
|
|
68
|
+
}
|
|
69
|
+
export type BenchmarkPromptSetName = 'balanced' | 'coding' | 'latency';
|
|
70
|
+
export declare const BENCHMARK_PROMPT_SETS: Record<BenchmarkPromptSetName, ModelBenchmarkPrompt[]>;
|
|
71
|
+
export declare function defaultBenchmarkIndexPath(homeDir?: string): string;
|
|
72
|
+
export declare function benchmarkCandidateKey(candidate: ModelBenchmarkCandidate): string;
|
|
73
|
+
export declare function benchmarkCandidates(candidates: ModelBenchmarkCandidate[], options?: BenchmarkSuiteOptions): Promise<ModelBenchmarkReport[]>;
|
|
74
|
+
export declare function summarizeBenchmarkRuns(runs: ModelBenchmarkRun[]): ModelBenchmarkSummary;
|
|
75
|
+
export declare function writeBenchmarkIndex(reports: ModelBenchmarkReport[], suite: string, indexPath?: string): Promise<ModelBenchmarkIndex>;
|
|
76
|
+
export declare function loadBenchmarkIndex(indexPath?: string): Promise<ModelBenchmarkIndex | null>;
|
|
77
|
+
export declare function loadBenchmarkScoreMap(indexPath?: string): Promise<Map<string, number>>;
|