@phuetz/code-buddy 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.fr.md +56 -0
- package/README.md +73 -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 +165 -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.js +19 -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 +1 -0
- package/dist/codebuddy/fleet-tool-defs.js +24 -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.js +1 -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 +4 -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.js +4 -0
- 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 +4 -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 +179 -0
- package/dist/doctor/integrations.js +227 -0
- package/dist/doctor/triage.d.ts +108 -0
- package/dist/doctor/triage.js +304 -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.js +6 -0
- 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/transports.d.ts +10 -5
- package/dist/mcp/transports.js +61 -99
- 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 +13 -5
- package/dist/services/runtime-settings-context.d.ts +73 -0
- package/dist/services/runtime-settings-context.js +69 -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 +21 -28
- 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 +9 -0
- package/dist/tools/registry/fleet-tools.js +22 -1
- 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/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/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
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { TaskStatus } from './index.js';
|
|
4
|
+
const identifier = z.string().min(1).max(128).regex(/^[A-Za-z0-9_.:-]+$/);
|
|
5
|
+
const requestSchema = z.object({ jsonrpc: z.literal('2.0'), id: z.union([z.string().max(128), z.number().finite(), z.null()]), method: z.string().max(64), params: z.record(z.string(), z.unknown()).default({}) }).strict();
|
|
6
|
+
const messageSchema = z.object({ messageId: identifier, contextId: identifier.optional(), role: z.enum(['ROLE_USER', 'user']).optional(),
|
|
7
|
+
parts: z.array(z.object({ text: z.string(), type: z.literal('text').optional(), kind: z.literal('text').optional() }).strict()).min(1).max(64) }).strict();
|
|
8
|
+
const MAX_TEXT = 64 * 1024;
|
|
9
|
+
const timestamp = () => new Date().toISOString();
|
|
10
|
+
function truncateUtf8(text, limit) {
|
|
11
|
+
const buffer = Buffer.from(text);
|
|
12
|
+
let end = Math.min(buffer.length, limit);
|
|
13
|
+
while (end > 0 && end < buffer.length && (buffer[end] & 0xc0) === 0x80)
|
|
14
|
+
end--;
|
|
15
|
+
return buffer.subarray(0, end).toString('utf8');
|
|
16
|
+
}
|
|
17
|
+
export const rpcError = (id, code, message) => ({ jsonrpc: '2.0', id: id ?? null, error: { code, message } });
|
|
18
|
+
/** Bounded text-only A2A 1.0 JSON-RPC subset; no automatic retries, streaming or pretend cancellation. */
|
|
19
|
+
export class A2AJsonRpcAdapter {
|
|
20
|
+
executor;
|
|
21
|
+
timeoutMs;
|
|
22
|
+
peerCount;
|
|
23
|
+
tasks = new Map();
|
|
24
|
+
messages = new Map();
|
|
25
|
+
contexts = new Map();
|
|
26
|
+
constructor(executor, timeoutMs = 120000, peerCount = 1) {
|
|
27
|
+
this.executor = executor;
|
|
28
|
+
this.timeoutMs = timeoutMs;
|
|
29
|
+
this.peerCount = peerCount;
|
|
30
|
+
}
|
|
31
|
+
async handle(peer, input, version) {
|
|
32
|
+
const parsed = requestSchema.safeParse(input);
|
|
33
|
+
if (!parsed.success)
|
|
34
|
+
return rpcError(null, -32600, 'Invalid JSON-RPC request');
|
|
35
|
+
const { id, method, params } = parsed.data;
|
|
36
|
+
if (version && !['1.0', '1.0.0'].includes(version))
|
|
37
|
+
return rpcError(id, -32602, 'Only A2A 1.0 is supported');
|
|
38
|
+
this.prune();
|
|
39
|
+
if (method === 'GetTask' || method === 'tasks/get') {
|
|
40
|
+
const key = typeof params.id === 'string' ? params.id : typeof params.name === 'string' ? params.name.replace(/^tasks\//, '') : '';
|
|
41
|
+
const stored = this.tasks.get(key);
|
|
42
|
+
return stored?.peer === peer ? { jsonrpc: '2.0', id, result: structuredClone(stored.task) } : rpcError(id, -32001, 'Task not found');
|
|
43
|
+
}
|
|
44
|
+
if (method === 'CancelTask' || method === 'tasks/cancel')
|
|
45
|
+
return rpcError(id, -32004, 'Cancellation is not supported; no cancellation was performed');
|
|
46
|
+
if (!['SendMessage', 'message/send'].includes(method))
|
|
47
|
+
return rpcError(id, -32601, 'Method not supported (including streaming)');
|
|
48
|
+
const message = messageSchema.safeParse(params.message);
|
|
49
|
+
if (!message.success)
|
|
50
|
+
return rpcError(id, -32602, 'A text message with messageId is required');
|
|
51
|
+
const text = message.data.parts.map(p => p.text).join('\n').trim();
|
|
52
|
+
if (!text || Buffer.byteLength(text) > MAX_TEXT)
|
|
53
|
+
return rpcError(id, -32602, 'Message must contain 1–65536 UTF-8 bytes');
|
|
54
|
+
if (params.contextId !== undefined && !identifier.safeParse(params.contextId).success)
|
|
55
|
+
return rpcError(id, -32602, 'Invalid contextId');
|
|
56
|
+
if (params.configuration !== undefined && (!params.configuration || typeof params.configuration !== 'object' || params.configuration.blocking !== true))
|
|
57
|
+
return rpcError(id, -32602, 'Only blocking SendMessage is supported');
|
|
58
|
+
const contextId = message.data.contextId ?? (typeof params.contextId === 'string' && identifier.safeParse(params.contextId).success ? params.contextId : randomUUID());
|
|
59
|
+
const fingerprint = createHash('sha256').update(JSON.stringify([message.data.contextId ?? params.contextId ?? null, text])).digest('hex');
|
|
60
|
+
const messageKey = `${peer}:${message.data.messageId}`;
|
|
61
|
+
const previousId = this.messages.get(messageKey);
|
|
62
|
+
const previous = previousId ? this.tasks.get(previousId) : undefined;
|
|
63
|
+
if (previous) {
|
|
64
|
+
if (previous.fingerprint !== fingerprint)
|
|
65
|
+
return rpcError(id, -32602, 'messageId already used with different content');
|
|
66
|
+
return { jsonrpc: '2.0', id, result: { task: await (previous.promise ?? Promise.resolve(structuredClone(previous.task))) } };
|
|
67
|
+
}
|
|
68
|
+
const contextKey = `${peer}:${contextId}`;
|
|
69
|
+
const context = this.contexts.get(contextKey) ?? { messages: [], turns: 0, busy: false, at: Date.now() };
|
|
70
|
+
if (context.messages.reduce((bytes, msg) => bytes + Buffer.byteLength(JSON.stringify(msg)), Buffer.byteLength(text)) > MAX_TEXT)
|
|
71
|
+
return rpcError(id, -32602, 'Context text limit reached');
|
|
72
|
+
if (context.busy)
|
|
73
|
+
return rpcError(id, -32005, 'Context is busy; no second task was started');
|
|
74
|
+
if (context.turns >= 5)
|
|
75
|
+
return rpcError(id, -32005, 'Context turn limit reached');
|
|
76
|
+
const shares = Math.max(1, Math.min(32, Math.floor(this.peerCount)));
|
|
77
|
+
const peerTasks = [...this.tasks.values()].filter(value => value.peer === peer).length;
|
|
78
|
+
const peerContexts = [...this.contexts.keys()].filter(key => key.startsWith(`${peer}:`)).length;
|
|
79
|
+
if (peerTasks >= Math.floor(256 / shares) || (!this.contexts.has(contextKey) && peerContexts >= Math.floor(128 / shares)))
|
|
80
|
+
return rpcError(id, -32005, 'Peer task capacity reached');
|
|
81
|
+
if (this.tasks.size >= 256 || (!this.contexts.has(contextKey) && this.contexts.size >= 128))
|
|
82
|
+
return rpcError(id, -32005, 'Task capacity reached');
|
|
83
|
+
const task = { id: randomUUID(), contextId, status: { state: 'TASK_STATE_WORKING', timestamp: timestamp() }, artifacts: [] };
|
|
84
|
+
const stored = { peer, contextId, fingerprint, task, at: Date.now() };
|
|
85
|
+
this.tasks.set(task.id, stored);
|
|
86
|
+
this.messages.set(messageKey, task.id);
|
|
87
|
+
if (text.startsWith('/')) {
|
|
88
|
+
task.status = { state: 'TASK_STATE_REJECTED', timestamp: timestamp(), message: { role: 'ROLE_AGENT', parts: [{ text: 'Slash commands are not allowed over A2A' }] } };
|
|
89
|
+
return { jsonrpc: '2.0', id, result: { task: structuredClone(task) } };
|
|
90
|
+
}
|
|
91
|
+
context.busy = true;
|
|
92
|
+
context.turns++;
|
|
93
|
+
context.at = Date.now();
|
|
94
|
+
this.contexts.set(contextKey, context);
|
|
95
|
+
const inputTask = { id: task.id, sessionId: `a2a:${peer}:${contextId}`, metadata: { peerId: peer },
|
|
96
|
+
messages: [...structuredClone(context.messages), { role: 'user', parts: [{ type: 'text', text }] }], artifacts: [], history: [], status: { status: TaskStatus.WORKING, timestamp: Date.now() } };
|
|
97
|
+
const controller = new AbortController();
|
|
98
|
+
let timer;
|
|
99
|
+
stored.running = true;
|
|
100
|
+
const execution = Promise.resolve().then(() => this.executor(inputTask, controller.signal)).finally(() => { context.busy = false; stored.running = false; });
|
|
101
|
+
stored.promise = (async () => {
|
|
102
|
+
try {
|
|
103
|
+
const completed = await Promise.race([execution, new Promise((_, reject) => {
|
|
104
|
+
timer = setTimeout(() => { controller.abort(); reject(new Error('TIMEOUT')); }, this.timeoutMs);
|
|
105
|
+
timer.unref?.();
|
|
106
|
+
})]);
|
|
107
|
+
const failed = completed.status.status !== TaskStatus.COMPLETED;
|
|
108
|
+
const output = completed.messages.filter(m => m.role === 'agent').at(-1)?.parts.filter(p => p.type === 'text').map(p => p.text).join('\n') ?? '';
|
|
109
|
+
task.status = { state: failed ? 'TASK_STATE_FAILED' : 'TASK_STATE_COMPLETED', timestamp: timestamp() };
|
|
110
|
+
if (!failed) {
|
|
111
|
+
const returnedText = Buffer.byteLength(output) > MAX_TEXT ? truncateUtf8(output, MAX_TEXT - 32) + '\n[Output truncated]' : output;
|
|
112
|
+
task.artifacts = [{ artifactId: randomUUID(), parts: [{ text: returnedText }] }];
|
|
113
|
+
context.messages = [...structuredClone(completed.messages).slice(0, -1), { role: 'agent', parts: [{ type: 'text', text: returnedText }] }].slice(-10);
|
|
114
|
+
while (context.messages.length > 1 && Buffer.byteLength(JSON.stringify(context.messages)) > MAX_TEXT)
|
|
115
|
+
context.messages.shift();
|
|
116
|
+
}
|
|
117
|
+
else
|
|
118
|
+
task.status.message = { role: 'ROLE_AGENT', parts: [{ text: 'Executor failed; inspect local diagnostics' }] };
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
task.status = { state: 'TASK_STATE_FAILED', timestamp: timestamp(), message: { role: 'ROLE_AGENT', parts: [{ text: 'Execution failed or timed out; no automatic retry' }] } };
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
clearTimeout(timer);
|
|
125
|
+
stored.promise = undefined;
|
|
126
|
+
stored.at = Date.now();
|
|
127
|
+
}
|
|
128
|
+
return structuredClone(task);
|
|
129
|
+
})();
|
|
130
|
+
return { jsonrpc: '2.0', id, result: { task: await stored.promise } };
|
|
131
|
+
}
|
|
132
|
+
prune() {
|
|
133
|
+
const cutoff = Date.now() - 30 * 60 * 1000;
|
|
134
|
+
for (const [id, value] of this.tasks)
|
|
135
|
+
if (!value.running && !value.promise && value.at < cutoff)
|
|
136
|
+
this.tasks.delete(id);
|
|
137
|
+
for (const [id, taskId] of this.messages)
|
|
138
|
+
if (!this.tasks.has(taskId))
|
|
139
|
+
this.messages.delete(id);
|
|
140
|
+
for (const [id, context] of this.contexts)
|
|
141
|
+
if (!context.busy && context.at < cutoff)
|
|
142
|
+
this.contexts.delete(id);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=jsonrpc-v1.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
declare const schema: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
3
|
+
url: z.ZodOptional<z.ZodString>;
|
|
4
|
+
inboundTokenEnv: z.ZodOptional<z.ZodString>;
|
|
5
|
+
outboundTokenEnv: z.ZodOptional<z.ZodString>;
|
|
6
|
+
}, "strict", z.ZodTypeAny, {
|
|
7
|
+
url?: string | undefined;
|
|
8
|
+
inboundTokenEnv?: string | undefined;
|
|
9
|
+
outboundTokenEnv?: string | undefined;
|
|
10
|
+
}, {
|
|
11
|
+
url?: string | undefined;
|
|
12
|
+
inboundTokenEnv?: string | undefined;
|
|
13
|
+
outboundTokenEnv?: string | undefined;
|
|
14
|
+
}>>;
|
|
15
|
+
export type A2APeers = z.infer<typeof schema>;
|
|
16
|
+
export declare function readA2APeers(raw?: string | undefined): A2APeers;
|
|
17
|
+
export declare function validateA2AUrl(raw: string): URL;
|
|
18
|
+
export declare function a2aToken(reference?: string): string | undefined;
|
|
19
|
+
export declare function authenticateA2APeer(peers: A2APeers, authorization?: string): string | undefined;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { createHash, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
const envRef = z.string().regex(/^[A-Z][A-Z0-9_]{0,95}$/);
|
|
4
|
+
const schema = z.record(z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$/), z.object({
|
|
5
|
+
url: z.string().optional(), inboundTokenEnv: envRef.optional(), outboundTokenEnv: envRef.optional(),
|
|
6
|
+
}).strict());
|
|
7
|
+
export function readA2APeers(raw = process.env.CODEBUDDY_A2A_PEERS) {
|
|
8
|
+
if (!raw)
|
|
9
|
+
return {};
|
|
10
|
+
try {
|
|
11
|
+
if (raw.length > 16384)
|
|
12
|
+
throw new Error();
|
|
13
|
+
const result = schema.parse(JSON.parse(raw));
|
|
14
|
+
if (Object.keys(result).length > 32)
|
|
15
|
+
throw new Error();
|
|
16
|
+
for (const peer of Object.values(result))
|
|
17
|
+
if (peer.url)
|
|
18
|
+
validateA2AUrl(peer.url);
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
throw new Error('Invalid CODEBUDDY_A2A_PEERS configuration (URLs and environment references only)');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function validateA2AUrl(raw) {
|
|
26
|
+
let url;
|
|
27
|
+
try {
|
|
28
|
+
url = new URL(raw);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
throw new Error('Invalid configured A2A URL');
|
|
32
|
+
}
|
|
33
|
+
if (!['https:', 'http:'].includes(url.protocol) || url.username || url.password || url.search || url.hash
|
|
34
|
+
|| (url.protocol === 'http:' && !['localhost', '127.0.0.1', '[::1]'].includes(url.hostname))) {
|
|
35
|
+
throw new Error('A2A requires HTTPS or loopback HTTP, without URL credentials/query/fragment');
|
|
36
|
+
}
|
|
37
|
+
return url;
|
|
38
|
+
}
|
|
39
|
+
export function a2aToken(reference) {
|
|
40
|
+
const value = reference ? process.env[reference] : undefined;
|
|
41
|
+
return value && value.length >= 16 && value.length <= 16384 && !/[\r\n]/.test(value) ? value : undefined;
|
|
42
|
+
}
|
|
43
|
+
export function authenticateA2APeer(peers, authorization) {
|
|
44
|
+
if (!authorization?.startsWith('Bearer ') || authorization.length > 16400)
|
|
45
|
+
return undefined;
|
|
46
|
+
const hash = (value) => createHash('sha256').update(value).digest();
|
|
47
|
+
const presented = hash(authorization.slice(7));
|
|
48
|
+
const matches = Object.entries(peers).filter(([, config]) => {
|
|
49
|
+
const token = a2aToken(config.inboundTokenEnv);
|
|
50
|
+
return token && timingSafeEqual(hash(token), presented);
|
|
51
|
+
});
|
|
52
|
+
// Duplicate credentials are ambiguous identities; fail closed.
|
|
53
|
+
return matches.length === 1 ? matches[0]?.[0] : undefined;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=peer-config.js.map
|
|
@@ -56,12 +56,21 @@ const TOKEN_REFRESH_AGE_MS = 60 * 60 * 1000; // 1 hour
|
|
|
56
56
|
* the same rotating refresh token more than once. */
|
|
57
57
|
let refreshAuthInFlight = null;
|
|
58
58
|
const AUTH_FILE_PATH = path.join(os.homedir(), '.codebuddy', 'codex-auth.json');
|
|
59
|
+
const CODEX_CLI_AUTH_PATH = path.join(os.homedir(), '.codex', 'auth.json');
|
|
59
60
|
// ─────────────────────────────────────────────────────────────────────
|
|
60
61
|
// Storage
|
|
61
62
|
// ─────────────────────────────────────────────────────────────────────
|
|
62
63
|
function loadAuthFile() {
|
|
63
64
|
try {
|
|
64
|
-
|
|
65
|
+
const primary = readJsonAtomicSync(AUTH_FILE_PATH, null);
|
|
66
|
+
if (primary?.tokens?.access_token)
|
|
67
|
+
return primary;
|
|
68
|
+
if (fs.existsSync(CODEX_CLI_AUTH_PATH)) {
|
|
69
|
+
const fallback = readJsonAtomicSync(CODEX_CLI_AUTH_PATH, null);
|
|
70
|
+
if (fallback?.tokens?.access_token)
|
|
71
|
+
return fallback;
|
|
72
|
+
}
|
|
73
|
+
return primary;
|
|
65
74
|
}
|
|
66
75
|
catch (err) {
|
|
67
76
|
logger.error('Error reading codex-auth.json', err instanceof Error ? err : { error: String(err) });
|
|
@@ -72,8 +81,10 @@ function saveAuthFile(auth) {
|
|
|
72
81
|
try {
|
|
73
82
|
writeJsonAtomicSync(AUTH_FILE_PATH, auth, { mode: 0o600 });
|
|
74
83
|
}
|
|
75
|
-
catch
|
|
76
|
-
|
|
84
|
+
catch {
|
|
85
|
+
// A successful exchange is not a successful login until tokens survive
|
|
86
|
+
// a restart. Never return freshly rotated tokens as if they were saved.
|
|
87
|
+
throw new Error('Could not save ChatGPT credentials. Check file permissions and available disk space, then run `buddy login` again.');
|
|
77
88
|
}
|
|
78
89
|
}
|
|
79
90
|
/** Remove cached tokens. `/logout chatgpt` calls this. */
|
|
@@ -92,9 +103,13 @@ export function clearCodexCredentials() {
|
|
|
92
103
|
export function hasCodexCredentials() {
|
|
93
104
|
try {
|
|
94
105
|
const parsed = readJsonAtomicSync(AUTH_FILE_PATH, null);
|
|
95
|
-
if (
|
|
96
|
-
return
|
|
97
|
-
|
|
106
|
+
if (parsed?.tokens?.access_token)
|
|
107
|
+
return true;
|
|
108
|
+
if (fs.existsSync(CODEX_CLI_AUTH_PATH)) {
|
|
109
|
+
const fallback = readJsonAtomicSync(CODEX_CLI_AUTH_PATH, null);
|
|
110
|
+
return Boolean(fallback?.tokens?.access_token);
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
98
113
|
}
|
|
99
114
|
catch {
|
|
100
115
|
return false;
|
|
@@ -343,7 +358,9 @@ export function loginInteractive(openUrl) {
|
|
|
343
358
|
let serverInstance = null;
|
|
344
359
|
let timeoutHandle = null;
|
|
345
360
|
let actualPort = CALLBACK_PORT;
|
|
361
|
+
let finished = false;
|
|
346
362
|
const cleanup = () => {
|
|
363
|
+
finished = true;
|
|
347
364
|
if (timeoutHandle)
|
|
348
365
|
clearTimeout(timeoutHandle);
|
|
349
366
|
if (serverInstance) {
|
|
@@ -392,6 +409,13 @@ export function loginInteractive(openUrl) {
|
|
|
392
409
|
}
|
|
393
410
|
const redirectUri = `http://localhost:${actualPort}/auth/callback`;
|
|
394
411
|
const tokens = await exchangeCodeForTokens(code, pkce.code_verifier, redirectUri);
|
|
412
|
+
if (finished) {
|
|
413
|
+
// Cancellation/timeout may happen while the issuer is responding.
|
|
414
|
+
// A late response must not replace credentials after the UI has ended.
|
|
415
|
+
res.writeHead(410, { 'Content-Type': 'text/plain' });
|
|
416
|
+
res.end('Login is no longer active. Run buddy login again.');
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
395
419
|
const authFile = {
|
|
396
420
|
tokens,
|
|
397
421
|
last_refresh: new Date().toISOString(),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isChatGptSubscriptionModel } from './chatgpt-models.js';
|
|
1
2
|
/**
|
|
2
3
|
* Model ↔ provider compatibility for the CLI model resolver (`loadModel`).
|
|
3
4
|
*
|
|
@@ -35,7 +36,7 @@ export function isModelCompatibleWithProvider(model, provider) {
|
|
|
35
36
|
if (provider === 'grok')
|
|
36
37
|
return looksGrok;
|
|
37
38
|
if (provider === 'chatgpt')
|
|
38
|
-
return
|
|
39
|
+
return isChatGptSubscriptionModel(model) && !looksGrok;
|
|
39
40
|
if (LOCAL_RUNTIME_PROVIDERS.has(provider))
|
|
40
41
|
return !looksLikeCloudModel(model);
|
|
41
42
|
if (FIRST_PARTY_PROVIDERS.has(provider)) {
|
|
@@ -240,4 +240,4 @@ export declare function createSandboxConfigForMode(mode: SandboxMode, cwd?: stri
|
|
|
240
240
|
export declare function createSandboxForMode(mode: SandboxMode, cwd?: string, extraReadOnly?: string[], extraReadWrite?: string[]): Promise<OSSandbox>;
|
|
241
241
|
export declare function getOSSandbox(config?: Partial<OSSandboxConfig>): OSSandbox;
|
|
242
242
|
export declare function resetOSSandbox(): void;
|
|
243
|
-
export { OSSandboxConfig as OSConfig };
|
|
243
|
+
export type { OSSandboxConfig as OSConfig };
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { EventEmitter } from 'events';
|
|
12
12
|
import type { CronPreCheck } from './pre-check-runner.js';
|
|
13
13
|
import type { CronWatchdog } from './watchdog-handlers.js';
|
|
14
|
+
import { type ContinuityFlag } from './job-notepad.js';
|
|
14
15
|
export type ScheduleType = 'at' | 'every' | 'cron';
|
|
15
16
|
export type JobStatus = 'active' | 'paused' | 'completed' | 'error';
|
|
16
17
|
export interface CronJob {
|
|
@@ -78,9 +79,17 @@ export interface CronJob {
|
|
|
78
79
|
/**
|
|
79
80
|
* Optional non-LLM pre-check. When present, it is evaluated before the task;
|
|
80
81
|
* if it decides nothing changed, the expensive task is skipped with evidence.
|
|
81
|
-
* `lastFingerprint` is updated and persisted across runs by the bridge
|
|
82
|
+
* `lastFingerprint` is updated and persisted across runs by the bridge,
|
|
83
|
+
* but only after a successful evaluation (skip or completed task) — a failed
|
|
84
|
+
* run must not consume the fingerprint, so an unchanged source can retry.
|
|
82
85
|
*/
|
|
83
86
|
preCheck?: CronPreCheck;
|
|
87
|
+
/**
|
|
88
|
+
* Opt-in per-job bounded continuity (scratchpad + last successful output).
|
|
89
|
+
* Off by default: agent prompts stay byte-identical. Notes passed at
|
|
90
|
+
* create/update are stored in the isolated notepad file, not in jobs.json.
|
|
91
|
+
*/
|
|
92
|
+
continuity?: ContinuityFlag;
|
|
84
93
|
/** Delivery options */
|
|
85
94
|
delivery?: {
|
|
86
95
|
/** Delivery mode: 'channel' (default), 'webhook', or 'none' (silent, no notification) */
|
|
@@ -175,7 +184,10 @@ export declare class CronScheduler extends EventEmitter {
|
|
|
175
184
|
private tickInFlight;
|
|
176
185
|
private running;
|
|
177
186
|
private taskExecutor?;
|
|
187
|
+
private notepad;
|
|
178
188
|
constructor(config?: Partial<CronSchedulerConfig>);
|
|
189
|
+
/** Directory of isolated per-job notepad files (`<cron-root>/notepads`). */
|
|
190
|
+
get notepadDir(): string;
|
|
179
191
|
/**
|
|
180
192
|
* Set the task executor (for CronAgentBridge integration)
|
|
181
193
|
*/
|
|
@@ -205,11 +217,12 @@ export declare class CronScheduler extends EventEmitter {
|
|
|
205
217
|
sessionTarget?: CronJob['sessionTarget'];
|
|
206
218
|
preCheck?: CronJob['preCheck'];
|
|
207
219
|
then?: string;
|
|
220
|
+
continuity?: CronJob['continuity'];
|
|
208
221
|
}): Promise<CronJob>;
|
|
209
222
|
/**
|
|
210
223
|
* Update a job
|
|
211
224
|
*/
|
|
212
|
-
updateJob(jobId: string, updates: Partial<Pick<CronJob, 'name' | 'description' | 'type' | 'schedule' | 'task' | 'delivery' | 'maxRuns' | 'enabled' | 'preCheck' | 'then'>>): Promise<CronJob | null>;
|
|
225
|
+
updateJob(jobId: string, updates: Partial<Pick<CronJob, 'name' | 'description' | 'type' | 'schedule' | 'task' | 'delivery' | 'maxRuns' | 'enabled' | 'preCheck' | 'then' | 'continuity'>>): Promise<CronJob | null>;
|
|
213
226
|
/**
|
|
214
227
|
* Remove a job
|
|
215
228
|
*/
|
|
@@ -17,6 +17,7 @@ import { logger } from '../utils/logger.js';
|
|
|
17
17
|
import { canonicalizeTimeZone } from '../life-rhythm/day-context.js';
|
|
18
18
|
import { findNextZonedMinute } from '../life-rhythm/zoned-minute.js';
|
|
19
19
|
import { readJsonAtomic, readTextAtomic, writeFileAtomic, writeJsonAtomic } from '../utils/atomic-write.js';
|
|
20
|
+
import { JobNotepadStore, isContinuityEnabled, persistableContinuity, validateContinuity, } from './job-notepad.js';
|
|
20
21
|
/** Exponential backoff delays in ms: 30s, 1m, 5m, 15m, 60m */
|
|
21
22
|
const BACKOFF_DELAYS_MS = [30_000, 60_000, 300_000, 900_000, 3_600_000];
|
|
22
23
|
/** Maximum chain depth to guard against cyclic `then` references (A→B→A). */
|
|
@@ -194,9 +195,15 @@ export class CronScheduler extends EventEmitter {
|
|
|
194
195
|
tickInFlight = false;
|
|
195
196
|
running = false;
|
|
196
197
|
taskExecutor;
|
|
198
|
+
notepad;
|
|
197
199
|
constructor(config = {}) {
|
|
198
200
|
super();
|
|
199
201
|
this.config = { ...DEFAULT_CRON_SCHEDULER_CONFIG, ...config };
|
|
202
|
+
this.notepad = new JobNotepadStore(path.join(path.dirname(this.config.persistPath), 'notepads'));
|
|
203
|
+
}
|
|
204
|
+
/** Directory of isolated per-job notepad files (`<cron-root>/notepads`). */
|
|
205
|
+
get notepadDir() {
|
|
206
|
+
return path.join(path.dirname(this.config.persistPath), 'notepads');
|
|
200
207
|
}
|
|
201
208
|
// ==========================================================================
|
|
202
209
|
// Lifecycle
|
|
@@ -267,6 +274,8 @@ export class CronScheduler extends EventEmitter {
|
|
|
267
274
|
async addJob(params) {
|
|
268
275
|
const id = crypto.randomUUID();
|
|
269
276
|
const now = new Date();
|
|
277
|
+
const continuity = params.continuity === undefined ? undefined : validateContinuity(params.continuity);
|
|
278
|
+
const seededNotes = typeof continuity === 'object' ? continuity.notes : undefined;
|
|
270
279
|
const job = {
|
|
271
280
|
id,
|
|
272
281
|
name: params.name,
|
|
@@ -285,6 +294,7 @@ export class CronScheduler extends EventEmitter {
|
|
|
285
294
|
sessionTarget: params.sessionTarget,
|
|
286
295
|
preCheck: params.preCheck,
|
|
287
296
|
then: params.then,
|
|
297
|
+
continuity: persistableContinuity(continuity),
|
|
288
298
|
};
|
|
289
299
|
// Resolve 'current' session target to concrete session ID at creation time
|
|
290
300
|
if (job.sessionTarget === 'current') {
|
|
@@ -292,6 +302,9 @@ export class CronScheduler extends EventEmitter {
|
|
|
292
302
|
}
|
|
293
303
|
// Calculate next run
|
|
294
304
|
job.nextRunAt = this.calculateNextRun(job);
|
|
305
|
+
if (seededNotes && isContinuityEnabled(job.continuity)) {
|
|
306
|
+
await this.notepad.replaceNotes(id, seededNotes);
|
|
307
|
+
}
|
|
295
308
|
this.jobs.set(id, job);
|
|
296
309
|
await this.persistJobs();
|
|
297
310
|
if (job.enabled && this.running) {
|
|
@@ -307,11 +320,23 @@ export class CronScheduler extends EventEmitter {
|
|
|
307
320
|
const job = this.jobs.get(jobId);
|
|
308
321
|
if (!job)
|
|
309
322
|
return null;
|
|
310
|
-
|
|
323
|
+
const { continuity: inputContinuity, ...rest } = updates;
|
|
324
|
+
const continuity = inputContinuity === undefined ? undefined : validateContinuity(inputContinuity);
|
|
325
|
+
const next = { ...job, ...rest };
|
|
326
|
+
if (continuity !== undefined)
|
|
327
|
+
next.continuity = persistableContinuity(continuity);
|
|
328
|
+
// Validate scheduling before writing notes; retain the live job/timer on failure.
|
|
329
|
+
this.calculateNextRun(next);
|
|
330
|
+
if (continuity !== undefined) {
|
|
331
|
+
const seededNotes = typeof continuity === 'object' ? continuity.notes : undefined;
|
|
332
|
+
if (seededNotes && isContinuityEnabled(next.continuity)) {
|
|
333
|
+
await this.notepad.replaceNotes(jobId, seededNotes);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
311
336
|
this.cancelJobTimer(jobId);
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
337
|
+
Object.assign(job, rest);
|
|
338
|
+
if (continuity !== undefined)
|
|
339
|
+
job.continuity = next.continuity;
|
|
315
340
|
job.nextRunAt = this.calculateNextRun(job);
|
|
316
341
|
// Reschedule if enabled
|
|
317
342
|
if (job.enabled && this.running) {
|
|
@@ -329,6 +354,12 @@ export class CronScheduler extends EventEmitter {
|
|
|
329
354
|
return false;
|
|
330
355
|
this.cancelJobTimer(jobId);
|
|
331
356
|
this.jobs.delete(jobId);
|
|
357
|
+
try {
|
|
358
|
+
await this.notepad.clear(jobId);
|
|
359
|
+
}
|
|
360
|
+
catch (error) {
|
|
361
|
+
logger.warn('Cron job removed but its notepad could not be cleared', { jobId, error: String(error) });
|
|
362
|
+
}
|
|
332
363
|
await this.persistJobs();
|
|
333
364
|
this.emit('job:deleted', jobId);
|
|
334
365
|
return true;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-job bounded continuity store (Hermes-inspired cron notepad).
|
|
3
|
+
*
|
|
4
|
+
* Isolated JSON files under `<cron-root>/notepads/<jobId>.json`, written
|
|
5
|
+
* atomically (0600): 16 KiB per value, 128-char keys, 64 KiB per serialized
|
|
6
|
+
* record. Mutations use an exclusive per-job lock across local processes.
|
|
7
|
+
* Contention requires retry; abandoned locks require operator inspection and
|
|
8
|
+
* are never stolen. Invalid/corrupt records are preserved for inspection.
|
|
9
|
+
*/
|
|
10
|
+
export declare const MAX_VALUE_BYTES: number;
|
|
11
|
+
export declare const MAX_KEY_CHARS = 128;
|
|
12
|
+
export declare const MAX_JOB_TOTAL_BYTES: number;
|
|
13
|
+
export declare const MAX_LAST_OUTPUT_BYTES: number;
|
|
14
|
+
export type ContinuityFlag = boolean | {
|
|
15
|
+
enabled?: boolean;
|
|
16
|
+
notes?: Record<string, string>;
|
|
17
|
+
};
|
|
18
|
+
export interface JobNotepadRecord {
|
|
19
|
+
notes: Record<string, string>;
|
|
20
|
+
/** Most recent non-empty successful output that was saved. */
|
|
21
|
+
lastSuccessfulOutput?: string;
|
|
22
|
+
updatedAt: string;
|
|
23
|
+
}
|
|
24
|
+
export declare class JobNotepadBusyError extends Error {
|
|
25
|
+
constructor();
|
|
26
|
+
}
|
|
27
|
+
/** Validate operator/tool input before changing jobs or writing any notes. */
|
|
28
|
+
export declare function validateContinuity(value: unknown): ContinuityFlag;
|
|
29
|
+
export declare function isContinuityEnabled(continuity: ContinuityFlag | undefined): boolean;
|
|
30
|
+
/** Persist the opt-in flag only — notes live in the notepad file, not jobs.json. */
|
|
31
|
+
export declare function persistableContinuity(continuity: ContinuityFlag | undefined): boolean | {
|
|
32
|
+
enabled: boolean;
|
|
33
|
+
} | undefined;
|
|
34
|
+
export declare function defaultNotepadDir(): string;
|
|
35
|
+
export declare function utf8ByteLength(value: string): number;
|
|
36
|
+
export declare function sanitizeJobId(jobId: string): string;
|
|
37
|
+
export declare function truncateUtf8(value: string, maxBytes: number): string;
|
|
38
|
+
export declare function notesByteTotal(notes: Record<string, string>): number;
|
|
39
|
+
export declare function assertNotesWithinCaps(notes: unknown): asserts notes is Record<string, string>;
|
|
40
|
+
export declare class JobNotepadStore {
|
|
41
|
+
private readonly dir;
|
|
42
|
+
constructor(dir?: string);
|
|
43
|
+
fileFor(jobId: string): string;
|
|
44
|
+
load(jobId: string): Promise<JobNotepadRecord>;
|
|
45
|
+
setNote(jobId: string, key: string, value: string): Promise<JobNotepadRecord>;
|
|
46
|
+
replaceNotes(jobId: string, notes: Record<string, string>): Promise<JobNotepadRecord>;
|
|
47
|
+
getNote(jobId: string, key: string): Promise<string | undefined>;
|
|
48
|
+
deleteNote(jobId: string, key: string): Promise<boolean>;
|
|
49
|
+
listNotes(jobId: string): Promise<Array<{
|
|
50
|
+
key: string;
|
|
51
|
+
value: string;
|
|
52
|
+
}>>;
|
|
53
|
+
/**
|
|
54
|
+
* Record the last *successful* job output. Truncates to the per-value and
|
|
55
|
+
* serialized per-job caps. Storage errors are reported to the caller.
|
|
56
|
+
* Empty output leaves the previous non-empty successful output in place.
|
|
57
|
+
*/
|
|
58
|
+
saveLastSuccessfulOutput(jobId: string, output: string): Promise<void>;
|
|
59
|
+
/** Missing files are a no-op; existing files share writer exclusion. */
|
|
60
|
+
clear(jobId: string): Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* Prompt section for this job. Empty notepad MUST return '' so jobs that
|
|
63
|
+
* never use continuity keep a byte-identical agent prompt.
|
|
64
|
+
*/
|
|
65
|
+
renderSection(jobId: string): Promise<string>;
|
|
66
|
+
private commitNotes;
|
|
67
|
+
private write;
|
|
68
|
+
private withMutationLock;
|
|
69
|
+
}
|