@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
|
@@ -20,6 +20,8 @@ export declare class Orchestrator extends EventEmitter {
|
|
|
20
20
|
private failedTaskCount;
|
|
21
21
|
private totalTaskDuration;
|
|
22
22
|
private running;
|
|
23
|
+
private workflowTaskSequence;
|
|
24
|
+
private workflowTaskIdentity;
|
|
23
25
|
constructor(config?: Partial<OrchestratorConfig>);
|
|
24
26
|
/**
|
|
25
27
|
* Register a new agent
|
|
@@ -81,6 +83,25 @@ export declare class Orchestrator extends EventEmitter {
|
|
|
81
83
|
* Cancel a task
|
|
82
84
|
*/
|
|
83
85
|
cancelTask(taskId: string): void;
|
|
86
|
+
private isTerminal;
|
|
87
|
+
/**
|
|
88
|
+
* A report (result, error, answered approval) for a task that is already
|
|
89
|
+
* terminal — typically abandoned when its workflow timed out — changes
|
|
90
|
+
* nothing: no status, output, retry, stat or event. The worker its executor
|
|
91
|
+
* kept busy is released, but only if it is still on that task.
|
|
92
|
+
*/
|
|
93
|
+
private releaseWorkerAfterLateReport;
|
|
94
|
+
/**
|
|
95
|
+
* A failed workflow must stop dispatching: its queued tasks leave the queue and
|
|
96
|
+
* every unfinished task becomes cancelled. A task whose executor is still busy
|
|
97
|
+
* keeps its worker until that executor reports back (see
|
|
98
|
+
* `releaseWorkerAfterLateReport`); an effect it already started is not undone.
|
|
99
|
+
*/
|
|
100
|
+
private abandonUnfinishedTasks;
|
|
101
|
+
private assertWorkflowRunning;
|
|
102
|
+
private createWorkflowTask;
|
|
103
|
+
private isDependencyComplete;
|
|
104
|
+
private pruneTerminalTasksForCapacity;
|
|
84
105
|
/**
|
|
85
106
|
* Process the task queue
|
|
86
107
|
*/
|
|
@@ -37,6 +37,8 @@ export class Orchestrator extends EventEmitter {
|
|
|
37
37
|
failedTaskCount = 0;
|
|
38
38
|
totalTaskDuration = 0;
|
|
39
39
|
running = false;
|
|
40
|
+
workflowTaskSequence = 0;
|
|
41
|
+
workflowTaskIdentity = new WeakMap();
|
|
40
42
|
constructor(config = {}) {
|
|
41
43
|
super();
|
|
42
44
|
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
@@ -147,6 +149,7 @@ export class Orchestrator extends EventEmitter {
|
|
|
147
149
|
* Create a new task
|
|
148
150
|
*/
|
|
149
151
|
createTask(definition) {
|
|
152
|
+
this.pruneTerminalTasksForCapacity();
|
|
150
153
|
if (this.tasks.size >= this.config.maxTasks) {
|
|
151
154
|
throw new Error(`Maximum task limit (${this.config.maxTasks}) reached`);
|
|
152
155
|
}
|
|
@@ -237,6 +240,10 @@ export class Orchestrator extends EventEmitter {
|
|
|
237
240
|
const task = this.tasks.get(taskId);
|
|
238
241
|
if (!task)
|
|
239
242
|
throw new Error(`Task '${taskId}' not found`);
|
|
243
|
+
if (this.isTerminal(task)) {
|
|
244
|
+
this.releaseWorkerAfterLateReport(task);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
240
247
|
task.status = 'completed';
|
|
241
248
|
task.output = output;
|
|
242
249
|
task.completedAt = new Date();
|
|
@@ -245,10 +252,10 @@ export class Orchestrator extends EventEmitter {
|
|
|
245
252
|
if (task.startedAt) {
|
|
246
253
|
this.totalTaskDuration += task.completedAt.getTime() - task.startedAt.getTime();
|
|
247
254
|
}
|
|
248
|
-
// Free up agent
|
|
255
|
+
// Free up agent — only if it is still on this task
|
|
249
256
|
if (task.assignedAgent) {
|
|
250
257
|
const agent = this.agents.get(task.assignedAgent);
|
|
251
|
-
if (agent) {
|
|
258
|
+
if (agent && agent.currentTask === taskId) {
|
|
252
259
|
agent.status = 'idle';
|
|
253
260
|
agent.currentTask = undefined;
|
|
254
261
|
agent.completedTasks++;
|
|
@@ -267,16 +274,20 @@ export class Orchestrator extends EventEmitter {
|
|
|
267
274
|
const task = this.tasks.get(taskId);
|
|
268
275
|
if (!task)
|
|
269
276
|
throw new Error(`Task '${taskId}' not found`);
|
|
277
|
+
if (this.isTerminal(task)) {
|
|
278
|
+
this.releaseWorkerAfterLateReport(task);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
270
281
|
// Check for retries
|
|
271
282
|
if (task.retries < (task.definition.maxRetries || 0)) {
|
|
272
283
|
task.retries++;
|
|
273
284
|
task.status = 'queued';
|
|
274
285
|
this.insertIntoQueue(task);
|
|
275
286
|
this.log('warn', `Task ${taskId} failed, retrying (${task.retries}/${task.definition.maxRetries})`);
|
|
276
|
-
// Free up agent
|
|
287
|
+
// Free up agent — only if it is still on this task
|
|
277
288
|
if (task.assignedAgent) {
|
|
278
289
|
const agent = this.agents.get(task.assignedAgent);
|
|
279
|
-
if (agent) {
|
|
290
|
+
if (agent && agent.currentTask === taskId) {
|
|
280
291
|
agent.status = 'idle';
|
|
281
292
|
agent.currentTask = undefined;
|
|
282
293
|
agent.lastActivity = new Date();
|
|
@@ -290,10 +301,10 @@ export class Orchestrator extends EventEmitter {
|
|
|
290
301
|
task.error = error;
|
|
291
302
|
task.completedAt = new Date();
|
|
292
303
|
this.failedTaskCount++;
|
|
293
|
-
// Free up agent
|
|
304
|
+
// Free up agent — only if it is still on this task
|
|
294
305
|
if (task.assignedAgent) {
|
|
295
306
|
const agent = this.agents.get(task.assignedAgent);
|
|
296
|
-
if (agent) {
|
|
307
|
+
if (agent && agent.currentTask === taskId) {
|
|
297
308
|
agent.status = 'idle';
|
|
298
309
|
agent.currentTask = undefined;
|
|
299
310
|
agent.failedTasks++;
|
|
@@ -318,6 +329,8 @@ export class Orchestrator extends EventEmitter {
|
|
|
318
329
|
const task = this.tasks.get(taskId);
|
|
319
330
|
if (!task)
|
|
320
331
|
throw new Error(`Task '${taskId}' not found`);
|
|
332
|
+
if (this.isTerminal(task))
|
|
333
|
+
return;
|
|
321
334
|
task.status = 'cancelled';
|
|
322
335
|
task.completedAt = new Date();
|
|
323
336
|
// Remove from queue
|
|
@@ -325,10 +338,10 @@ export class Orchestrator extends EventEmitter {
|
|
|
325
338
|
if (queueIdx !== -1) {
|
|
326
339
|
this.taskQueue.splice(queueIdx, 1);
|
|
327
340
|
}
|
|
328
|
-
// Free up agent
|
|
341
|
+
// Free up agent — only if it is still on this task
|
|
329
342
|
if (task.assignedAgent) {
|
|
330
343
|
const agent = this.agents.get(task.assignedAgent);
|
|
331
|
-
if (agent) {
|
|
344
|
+
if (agent && agent.currentTask === taskId) {
|
|
332
345
|
agent.status = 'idle';
|
|
333
346
|
agent.currentTask = undefined;
|
|
334
347
|
agent.lastActivity = new Date();
|
|
@@ -336,6 +349,95 @@ export class Orchestrator extends EventEmitter {
|
|
|
336
349
|
}
|
|
337
350
|
this.log('info', `Task cancelled: ${taskId}`);
|
|
338
351
|
}
|
|
352
|
+
isTerminal(task) {
|
|
353
|
+
return task.status === 'completed' || task.status === 'failed' || task.status === 'cancelled';
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* A report (result, error, answered approval) for a task that is already
|
|
357
|
+
* terminal — typically abandoned when its workflow timed out — changes
|
|
358
|
+
* nothing: no status, output, retry, stat or event. The worker its executor
|
|
359
|
+
* kept busy is released, but only if it is still on that task.
|
|
360
|
+
*/
|
|
361
|
+
releaseWorkerAfterLateReport(task) {
|
|
362
|
+
const taskId = task.definition.id;
|
|
363
|
+
const agent = task.assignedAgent ? this.agents.get(task.assignedAgent) : undefined;
|
|
364
|
+
this.log('debug', `Ignored late report for ${task.status} task ${taskId}`);
|
|
365
|
+
task.assignedAgent = undefined;
|
|
366
|
+
if (agent?.currentTask === taskId) {
|
|
367
|
+
agent.status = 'idle';
|
|
368
|
+
agent.currentTask = undefined;
|
|
369
|
+
agent.lastActivity = new Date();
|
|
370
|
+
this.processQueue();
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* A failed workflow must stop dispatching: its queued tasks leave the queue and
|
|
375
|
+
* every unfinished task becomes cancelled. A task whose executor is still busy
|
|
376
|
+
* keeps its worker until that executor reports back (see
|
|
377
|
+
* `releaseWorkerAfterLateReport`); an effect it already started is not undone.
|
|
378
|
+
*/
|
|
379
|
+
abandonUnfinishedTasks(instance) {
|
|
380
|
+
for (const task of instance.tasks.values()) {
|
|
381
|
+
if (this.isTerminal(task))
|
|
382
|
+
continue;
|
|
383
|
+
task.status = 'cancelled';
|
|
384
|
+
task.completedAt = new Date();
|
|
385
|
+
const queueIdx = this.taskQueue.indexOf(task);
|
|
386
|
+
if (queueIdx !== -1)
|
|
387
|
+
this.taskQueue.splice(queueIdx, 1);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
assertWorkflowRunning(instance) {
|
|
391
|
+
if (instance.status !== 'running') {
|
|
392
|
+
throw new Error(`Workflow '${instance.instanceId}' is no longer running`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
createWorkflowTask(instance, definition) {
|
|
396
|
+
this.assertWorkflowRunning(instance);
|
|
397
|
+
const logicalTaskId = definition.id;
|
|
398
|
+
const runtimeTaskId = `${instance.instanceId}:${logicalTaskId}:${++this.workflowTaskSequence}`;
|
|
399
|
+
const task = this.createTask({ ...definition, id: runtimeTaskId });
|
|
400
|
+
this.workflowTaskIdentity.set(task, { instanceId: instance.instanceId, logicalTaskId });
|
|
401
|
+
instance.tasks.set(runtimeTaskId, task);
|
|
402
|
+
return task;
|
|
403
|
+
}
|
|
404
|
+
isDependencyComplete(task, dependencyId) {
|
|
405
|
+
const identity = this.workflowTaskIdentity.get(task);
|
|
406
|
+
if (!identity)
|
|
407
|
+
return this.tasks.get(dependencyId)?.status === 'completed';
|
|
408
|
+
const workflow = this.workflows.get(identity.instanceId);
|
|
409
|
+
if (!workflow)
|
|
410
|
+
return false;
|
|
411
|
+
let latestDependency;
|
|
412
|
+
for (const candidate of workflow.tasks.values()) {
|
|
413
|
+
const candidateIdentity = this.workflowTaskIdentity.get(candidate);
|
|
414
|
+
if (candidateIdentity?.logicalTaskId === dependencyId)
|
|
415
|
+
latestDependency = candidate;
|
|
416
|
+
}
|
|
417
|
+
return latestDependency?.status === 'completed';
|
|
418
|
+
}
|
|
419
|
+
pruneTerminalTasksForCapacity() {
|
|
420
|
+
if (this.tasks.size < this.config.maxTasks)
|
|
421
|
+
return;
|
|
422
|
+
for (const [taskId, task] of this.tasks) {
|
|
423
|
+
if (!this.isTerminal(task))
|
|
424
|
+
continue;
|
|
425
|
+
const identity = this.workflowTaskIdentity.get(task);
|
|
426
|
+
if (identity && this.workflows.get(identity.instanceId)?.status === 'running')
|
|
427
|
+
continue;
|
|
428
|
+
const hasOutstandingExecutor = task.status === 'cancelled' && task.assignedAgent !== undefined;
|
|
429
|
+
if (hasOutstandingExecutor)
|
|
430
|
+
continue;
|
|
431
|
+
const isRequiredByActiveStandaloneTask = Array.from(this.tasks.values()).some((candidate) => !this.isTerminal(candidate) &&
|
|
432
|
+
!this.workflowTaskIdentity.has(candidate) &&
|
|
433
|
+
candidate.definition.dependsOn?.includes(taskId));
|
|
434
|
+
if (isRequiredByActiveStandaloneTask)
|
|
435
|
+
continue;
|
|
436
|
+
this.tasks.delete(taskId);
|
|
437
|
+
if (this.tasks.size < this.config.maxTasks)
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
339
441
|
// ============================================================================
|
|
340
442
|
// Queue Processing
|
|
341
443
|
// ============================================================================
|
|
@@ -348,10 +450,7 @@ export class Orchestrator extends EventEmitter {
|
|
|
348
450
|
for (const task of this.taskQueue) {
|
|
349
451
|
// Check dependencies
|
|
350
452
|
if (task.definition.dependsOn && task.definition.dependsOn.length > 0) {
|
|
351
|
-
const allDepsComplete = task.definition.dependsOn.every((depId) =>
|
|
352
|
-
const dep = this.tasks.get(depId);
|
|
353
|
-
return dep?.status === 'completed';
|
|
354
|
-
});
|
|
453
|
+
const allDepsComplete = task.definition.dependsOn.every((depId) => this.isDependencyComplete(task, depId));
|
|
355
454
|
if (!allDepsComplete)
|
|
356
455
|
continue;
|
|
357
456
|
}
|
|
@@ -412,6 +511,7 @@ export class Orchestrator extends EventEmitter {
|
|
|
412
511
|
instance.status = 'failed';
|
|
413
512
|
instance.error = errorMessage;
|
|
414
513
|
instance.completedAt = new Date();
|
|
514
|
+
this.abandonUnfinishedTasks(instance);
|
|
415
515
|
this.emit('workflow_failed', { type: 'workflow_failed', instanceId, error: errorMessage });
|
|
416
516
|
this.log('error', `Workflow failed: ${instanceId} - ${errorMessage}`);
|
|
417
517
|
}
|
|
@@ -425,6 +525,7 @@ export class Orchestrator extends EventEmitter {
|
|
|
425
525
|
for (const step of instance.definition.steps) {
|
|
426
526
|
instance.currentStep = step.id;
|
|
427
527
|
await this.executeWorkflowStep(instance, step, context);
|
|
528
|
+
this.assertWorkflowRunning(instance);
|
|
428
529
|
instance.completedSteps.push(step.id);
|
|
429
530
|
this.emit('workflow_step_completed', {
|
|
430
531
|
type: 'workflow_step_completed',
|
|
@@ -438,6 +539,7 @@ export class Orchestrator extends EventEmitter {
|
|
|
438
539
|
* Execute a workflow step
|
|
439
540
|
*/
|
|
440
541
|
async executeWorkflowStep(instance, step, context) {
|
|
542
|
+
this.assertWorkflowRunning(instance);
|
|
441
543
|
switch (step.type) {
|
|
442
544
|
case 'task':
|
|
443
545
|
await this.executeTaskStep(instance, step, context);
|
|
@@ -463,20 +565,21 @@ export class Orchestrator extends EventEmitter {
|
|
|
463
565
|
if (!step.tasks || step.tasks.length === 0)
|
|
464
566
|
return;
|
|
465
567
|
for (const taskDef of step.tasks) {
|
|
568
|
+
this.assertWorkflowRunning(instance);
|
|
466
569
|
// Substitute variables in task input
|
|
467
570
|
const resolvedInput = this.resolveVariables(taskDef.input, context);
|
|
468
571
|
const resolvedDef = { ...taskDef, input: resolvedInput };
|
|
469
|
-
const task = this.
|
|
470
|
-
instance.tasks.set(task.definition.id, task);
|
|
572
|
+
const task = this.createWorkflowTask(instance, resolvedDef);
|
|
471
573
|
this.queueTask(task.definition.id);
|
|
472
574
|
// Wait for completion
|
|
473
575
|
await this.waitForTask(task.definition.id);
|
|
576
|
+
this.assertWorkflowRunning(instance);
|
|
474
577
|
// Add output to context — both under the task's namespaced key and,
|
|
475
578
|
// optionally, under a user-provided alias for readability in
|
|
476
579
|
// downstream conditions / tool inputs.
|
|
477
580
|
const completedTask = this.tasks.get(task.definition.id);
|
|
478
581
|
if (completedTask?.output) {
|
|
479
|
-
context[`task_${
|
|
582
|
+
context[`task_${taskDef.id}`] = completedTask.output;
|
|
480
583
|
if (typeof taskDef.aliasAs === 'string' && taskDef.aliasAs.length > 0) {
|
|
481
584
|
context[taskDef.aliasAs] = completedTask.output;
|
|
482
585
|
}
|
|
@@ -497,6 +600,7 @@ export class Orchestrator extends EventEmitter {
|
|
|
497
600
|
return branchContext;
|
|
498
601
|
});
|
|
499
602
|
const results = await Promise.all(branchPromises);
|
|
603
|
+
this.assertWorkflowRunning(instance);
|
|
500
604
|
// Merge branch results
|
|
501
605
|
for (let i = 0; i < results.length; i++) {
|
|
502
606
|
context[`branch_${i}`] = results[i];
|
|
@@ -525,6 +629,7 @@ export class Orchestrator extends EventEmitter {
|
|
|
525
629
|
const maxIterations = Math.max(1, Math.min(configuredLimit, 100));
|
|
526
630
|
while (this.evaluateCondition(step.loopCondition || 'false', context) &&
|
|
527
631
|
iteration < maxIterations) {
|
|
632
|
+
this.assertWorkflowRunning(instance);
|
|
528
633
|
context['iteration'] = iteration;
|
|
529
634
|
this.emit('loop_iteration_started', {
|
|
530
635
|
type: 'loop_iteration_started',
|
|
@@ -557,6 +662,7 @@ export class Orchestrator extends EventEmitter {
|
|
|
557
662
|
return;
|
|
558
663
|
}
|
|
559
664
|
const tasks = items.map((item, index) => async () => {
|
|
665
|
+
this.assertWorkflowRunning(instance);
|
|
560
666
|
// Create local context fork for this batch item execution
|
|
561
667
|
const localContext = { ...context, [varName]: item, index };
|
|
562
668
|
this.emit('loop_iteration_started', {
|
|
@@ -581,6 +687,7 @@ export class Orchestrator extends EventEmitter {
|
|
|
581
687
|
}
|
|
582
688
|
});
|
|
583
689
|
await Promise.all(workers);
|
|
690
|
+
this.assertWorkflowRunning(instance);
|
|
584
691
|
}
|
|
585
692
|
evaluateExpression(expression, context) {
|
|
586
693
|
let resolved = expression;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { SessionMessage } from './session-store.js';
|
|
2
|
+
export declare class SessionDecryptionError extends Error {
|
|
3
|
+
constructor(cause: unknown);
|
|
4
|
+
}
|
|
5
|
+
export declare function hasEncryptedSessionContent(messages: SessionMessage[]): boolean;
|
|
6
|
+
/** Both async and legacy synchronous store readers use the same authenticated decoder. */
|
|
7
|
+
export declare function decryptSessionContent(messages: SessionMessage[], keyPath?: string): SessionMessage[];
|
|
8
|
+
export declare function encryptSessionContent(messages: SessionMessage[], keyPath?: string): Promise<SessionMessage[]>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { SessionEncryption } from '../security/session-encryption.js';
|
|
2
|
+
export class SessionDecryptionError extends Error {
|
|
3
|
+
constructor(cause) {
|
|
4
|
+
super('Cannot decrypt session: restore the original encryption key before continuing.', { cause });
|
|
5
|
+
this.name = 'SessionDecryptionError';
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
function envelope(messages) {
|
|
9
|
+
if (messages.length !== 1 || messages[0]?.type !== 'assistant')
|
|
10
|
+
return null;
|
|
11
|
+
let value;
|
|
12
|
+
try {
|
|
13
|
+
value = JSON.parse(messages[0].content);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
if (!value || typeof value !== 'object' || !('__encrypted' in value) || value.__encrypted !== true)
|
|
19
|
+
return null;
|
|
20
|
+
if (!('data' in value))
|
|
21
|
+
throw new SessionDecryptionError(new Error('Missing encrypted data'));
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
export function hasEncryptedSessionContent(messages) {
|
|
25
|
+
return envelope(messages) !== null;
|
|
26
|
+
}
|
|
27
|
+
/** Both async and legacy synchronous store readers use the same authenticated decoder. */
|
|
28
|
+
export function decryptSessionContent(messages, keyPath) {
|
|
29
|
+
const wrapped = envelope(messages);
|
|
30
|
+
if (!wrapped)
|
|
31
|
+
return messages;
|
|
32
|
+
const encryption = new SessionEncryption({ keyPath, requirePersistentKey: true });
|
|
33
|
+
try {
|
|
34
|
+
if (!encryption.isEncrypted(wrapped.data) || ![1, 2].includes(wrapped.data.version))
|
|
35
|
+
throw new Error('Invalid encrypted envelope');
|
|
36
|
+
encryption.initializeForRead();
|
|
37
|
+
const decoded = encryption.decryptObject(wrapped.data);
|
|
38
|
+
if (!Array.isArray(decoded))
|
|
39
|
+
throw new Error('Invalid encrypted history');
|
|
40
|
+
const types = new Set(['user', 'assistant', 'tool_call', 'tool_result', 'reasoning', 'plan_progress', 'steer', 'diff_preview']);
|
|
41
|
+
return decoded.map((entry) => {
|
|
42
|
+
if (!entry || typeof entry !== 'object')
|
|
43
|
+
throw new Error('Invalid encrypted message');
|
|
44
|
+
const message = entry;
|
|
45
|
+
if (!types.has(message.type) || typeof message.content !== 'string' ||
|
|
46
|
+
typeof message.timestamp !== 'string' || !Number.isFinite(Date.parse(message.timestamp))) {
|
|
47
|
+
throw new Error('Invalid encrypted message');
|
|
48
|
+
}
|
|
49
|
+
// Legacy envelopes contain ChatEntry JSON; its timestamp is already serialized.
|
|
50
|
+
return { ...message };
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
throw new SessionDecryptionError(error);
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
encryption.dispose();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export async function encryptSessionContent(messages, keyPath) {
|
|
61
|
+
if (hasEncryptedSessionContent(messages))
|
|
62
|
+
return messages;
|
|
63
|
+
const encryption = new SessionEncryption({ keyPath, requirePersistentKey: true });
|
|
64
|
+
try {
|
|
65
|
+
await encryption.initialize();
|
|
66
|
+
const data = encryption.encryptObject(messages);
|
|
67
|
+
if (!encryption.isEncrypted(data))
|
|
68
|
+
throw new Error('Session encryption produced plaintext');
|
|
69
|
+
return [{ type: 'assistant', timestamp: new Date().toISOString(),
|
|
70
|
+
content: JSON.stringify({ __encrypted: true, format: 'session-messages-v1', data }) }];
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
encryption.dispose();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=session-content.js.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GUI → terminal session handoff (comparatif plan P6).
|
|
3
|
+
*
|
|
4
|
+
* Converts a text conversation (e.g. a Cowork session) into the CLI session
|
|
5
|
+
* store format so `buddy --resume <id>` continues it in the terminal.
|
|
6
|
+
*
|
|
7
|
+
* Contract:
|
|
8
|
+
* - only user/assistant TEXT turns are exported (no tool payloads, attachments
|
|
9
|
+
* or system/context blocks);
|
|
10
|
+
* - every turn goes through the data-redaction engine; secrets become
|
|
11
|
+
* placeholders and are counted, never copied;
|
|
12
|
+
* - the file is written atomically with mode 0600 in the CLI sessions dir
|
|
13
|
+
* (CODEBUDDY_SESSIONS_DIR or ~/.codebuddy/sessions); the id is deterministic
|
|
14
|
+
* (`cowork-<source id>`) so a later export refreshes the same terminal session;
|
|
15
|
+
* - the returned command carries no credential.
|
|
16
|
+
*/
|
|
17
|
+
export interface HandoffTurn {
|
|
18
|
+
role: string;
|
|
19
|
+
text: string;
|
|
20
|
+
timestamp?: number | string;
|
|
21
|
+
}
|
|
22
|
+
export interface HandoffInput {
|
|
23
|
+
source: 'cowork';
|
|
24
|
+
sourceId: string;
|
|
25
|
+
name: string;
|
|
26
|
+
workingDirectory?: string;
|
|
27
|
+
model?: string;
|
|
28
|
+
createdAt?: number | string;
|
|
29
|
+
turns: HandoffTurn[];
|
|
30
|
+
}
|
|
31
|
+
export interface HandoffResult {
|
|
32
|
+
id: string;
|
|
33
|
+
path: string;
|
|
34
|
+
command: string;
|
|
35
|
+
messageCount: number;
|
|
36
|
+
redactions: number;
|
|
37
|
+
}
|
|
38
|
+
export declare function handoffSessionId(source: HandoffInput['source'], sourceId: string): string;
|
|
39
|
+
export declare function buildHandoffSession(input: HandoffInput, now?: Date): {
|
|
40
|
+
session: {
|
|
41
|
+
id: string;
|
|
42
|
+
name: string;
|
|
43
|
+
workingDirectory: string;
|
|
44
|
+
model: string;
|
|
45
|
+
messages: {
|
|
46
|
+
type: "user" | "assistant";
|
|
47
|
+
content: string;
|
|
48
|
+
timestamp: string;
|
|
49
|
+
}[];
|
|
50
|
+
createdAt: string;
|
|
51
|
+
lastAccessedAt: string;
|
|
52
|
+
metadata: {
|
|
53
|
+
handoffSource: "cowork";
|
|
54
|
+
handoffSourceId: string;
|
|
55
|
+
handoffAt: string;
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
redactions: number;
|
|
59
|
+
};
|
|
60
|
+
export declare function cliSessionsDir(): string;
|
|
61
|
+
export declare function writeHandoffSession(input: HandoffInput, options?: {
|
|
62
|
+
dir?: string;
|
|
63
|
+
now?: Date;
|
|
64
|
+
}): Promise<HandoffResult>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GUI → terminal session handoff (comparatif plan P6).
|
|
3
|
+
*
|
|
4
|
+
* Converts a text conversation (e.g. a Cowork session) into the CLI session
|
|
5
|
+
* store format so `buddy --resume <id>` continues it in the terminal.
|
|
6
|
+
*
|
|
7
|
+
* Contract:
|
|
8
|
+
* - only user/assistant TEXT turns are exported (no tool payloads, attachments
|
|
9
|
+
* or system/context blocks);
|
|
10
|
+
* - every turn goes through the data-redaction engine; secrets become
|
|
11
|
+
* placeholders and are counted, never copied;
|
|
12
|
+
* - the file is written atomically with mode 0600 in the CLI sessions dir
|
|
13
|
+
* (CODEBUDDY_SESSIONS_DIR or ~/.codebuddy/sessions); the id is deterministic
|
|
14
|
+
* (`cowork-<source id>`) so a later export refreshes the same terminal session;
|
|
15
|
+
* - the returned command carries no credential.
|
|
16
|
+
*/
|
|
17
|
+
import fs from 'node:fs';
|
|
18
|
+
import os from 'node:os';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import { getDataRedactionEngine } from '../security/data-redaction.js';
|
|
21
|
+
import { writeJsonAtomic } from '../utils/atomic-write.js';
|
|
22
|
+
const MAX_TURN_CHARS = 200_000;
|
|
23
|
+
function isoTime(value, fallback) {
|
|
24
|
+
const parsed = typeof value === 'number' ? new Date(value) : typeof value === 'string' ? new Date(value) : undefined;
|
|
25
|
+
return parsed && !Number.isNaN(parsed.getTime()) ? parsed.toISOString() : fallback.toISOString();
|
|
26
|
+
}
|
|
27
|
+
export function handoffSessionId(source, sourceId) {
|
|
28
|
+
const safe = sourceId.replace(/[^a-zA-Z0-9_-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 80);
|
|
29
|
+
if (!safe)
|
|
30
|
+
throw new Error('HANDOFF_INVALID_SOURCE_ID');
|
|
31
|
+
return `${source}-${safe}`;
|
|
32
|
+
}
|
|
33
|
+
export function buildHandoffSession(input, now = new Date()) {
|
|
34
|
+
const engine = getDataRedactionEngine();
|
|
35
|
+
let redactions = 0;
|
|
36
|
+
const redact = (text) => {
|
|
37
|
+
const result = engine.redact(text);
|
|
38
|
+
redactions += result.redactions.length;
|
|
39
|
+
return result.redacted;
|
|
40
|
+
};
|
|
41
|
+
const messages = input.turns
|
|
42
|
+
.filter((turn) => (turn.role === 'user' || turn.role === 'assistant') && typeof turn.text === 'string' && turn.text.trim())
|
|
43
|
+
.map((turn) => ({
|
|
44
|
+
type: turn.role,
|
|
45
|
+
content: redact(turn.text.slice(0, MAX_TURN_CHARS)),
|
|
46
|
+
timestamp: isoTime(turn.timestamp, now),
|
|
47
|
+
}));
|
|
48
|
+
const createdAt = isoTime(input.createdAt, now);
|
|
49
|
+
const session = {
|
|
50
|
+
id: handoffSessionId(input.source, input.sourceId),
|
|
51
|
+
name: redact(input.name || input.sourceId).slice(0, 200),
|
|
52
|
+
workingDirectory: input.workingDirectory && path.isAbsolute(input.workingDirectory) ? input.workingDirectory : process.cwd(),
|
|
53
|
+
model: input.model || 'unknown',
|
|
54
|
+
messages,
|
|
55
|
+
createdAt,
|
|
56
|
+
lastAccessedAt: now.toISOString(),
|
|
57
|
+
metadata: { handoffSource: input.source, handoffSourceId: input.sourceId, handoffAt: now.toISOString() },
|
|
58
|
+
};
|
|
59
|
+
return { session, redactions };
|
|
60
|
+
}
|
|
61
|
+
export function cliSessionsDir() {
|
|
62
|
+
return path.resolve(process.env.CODEBUDDY_SESSIONS_DIR || path.join(os.homedir(), '.codebuddy', 'sessions'));
|
|
63
|
+
}
|
|
64
|
+
export async function writeHandoffSession(input, options = {}) {
|
|
65
|
+
const { session, redactions } = buildHandoffSession(input, options.now);
|
|
66
|
+
if (session.messages.length === 0)
|
|
67
|
+
throw new Error('HANDOFF_EMPTY_CONVERSATION');
|
|
68
|
+
const dir = options.dir ?? cliSessionsDir();
|
|
69
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
70
|
+
const filePath = path.join(dir, `${session.id}.json`);
|
|
71
|
+
await writeJsonAtomic(filePath, session, { mode: 0o600 });
|
|
72
|
+
return {
|
|
73
|
+
id: session.id,
|
|
74
|
+
path: filePath,
|
|
75
|
+
command: `buddy --resume ${session.id}`,
|
|
76
|
+
messageCount: session.messages.length,
|
|
77
|
+
redactions,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=session-handoff.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ChatEntry } from '../agent/types.js';
|
|
2
|
+
import type { CodeBuddyMessage } from '../codebuddy/client.js';
|
|
3
|
+
/** Restore the provider transcript, not just the visible chat bubbles. */
|
|
4
|
+
export declare function restoreSessionHistory(entries: ChatEntry[]): CodeBuddyMessage[];
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { repairToolCallPairs } from '../context/transcript-repair.js';
|
|
2
|
+
import { sanitizeModelOutput } from '../utils/output-sanitizer.js';
|
|
3
|
+
/** Restore the provider transcript, not just the visible chat bubbles. */
|
|
4
|
+
export function restoreSessionHistory(entries) {
|
|
5
|
+
const messages = [];
|
|
6
|
+
const knownCalls = new Set();
|
|
7
|
+
for (const entry of entries) {
|
|
8
|
+
const calls = (entry.toolCalls ?? (entry.toolCall ? [entry.toolCall] : []))
|
|
9
|
+
.filter(call => Boolean(call.id && call.function?.name));
|
|
10
|
+
if (entry.type === 'user' || entry.type === 'steer') {
|
|
11
|
+
messages.push({ role: 'user', content: entry.content });
|
|
12
|
+
}
|
|
13
|
+
else if (entry.type === 'assistant' || entry.type === 'tool_call') {
|
|
14
|
+
const newCalls = calls.filter(call => !knownCalls.has(call.id));
|
|
15
|
+
for (const call of newCalls)
|
|
16
|
+
knownCalls.add(call.id);
|
|
17
|
+
if (entry.content || newCalls.length) {
|
|
18
|
+
messages.push({ role: 'assistant', content: sanitizeModelOutput(entry.content),
|
|
19
|
+
...(newCalls.length ? { tool_calls: newCalls } : {}) });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
else if (entry.type === 'tool_result') {
|
|
23
|
+
const call = calls[0];
|
|
24
|
+
const result = entry.toolResult;
|
|
25
|
+
const content = result ? JSON.stringify({ ...result, ...(result.output === undefined && entry.content ? { output: entry.content } : {}) }) : entry.content;
|
|
26
|
+
if (call && knownCalls.has(call.id)) {
|
|
27
|
+
messages.push({ role: 'tool', tool_call_id: call.id, content });
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
// Old sessions have no stable pairing. Keep the evidence as historical
|
|
31
|
+
// data without inventing a tool invocation or dropping a failed result.
|
|
32
|
+
messages.push({ role: 'assistant', content: `[Historical tool result (untrusted data)${call ? `: ${call.function.name}` : ''}]\n${content}` });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return repairToolCallPairs(messages);
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=session-history.js.map
|
|
@@ -35,6 +35,8 @@ export interface SessionUsageSnapshot {
|
|
|
35
35
|
turns?: SessionTurnUsage[];
|
|
36
36
|
}
|
|
37
37
|
export interface Session {
|
|
38
|
+
/** Sticky protection: metadata changes and forks must remain encrypted. */
|
|
39
|
+
encrypted?: boolean;
|
|
38
40
|
id: string;
|
|
39
41
|
name: string;
|
|
40
42
|
workingDirectory: string;
|
|
@@ -55,12 +57,17 @@ export interface SessionMessage {
|
|
|
55
57
|
timestamp: string;
|
|
56
58
|
toolCallName?: string;
|
|
57
59
|
toolCallSuccess?: boolean;
|
|
60
|
+
toolCall?: ChatEntry['toolCall'];
|
|
61
|
+
toolCalls?: ChatEntry['toolCalls'];
|
|
62
|
+
toolResult?: ChatEntry['toolResult'];
|
|
63
|
+
truncated?: boolean;
|
|
58
64
|
/** Task state for cross-session continuity */
|
|
59
65
|
taskState?: Record<string, unknown>;
|
|
60
66
|
}
|
|
61
67
|
export interface SessionStoreConfig {
|
|
62
68
|
/** Use SQLite database instead of JSON files */
|
|
63
69
|
useSQLite: boolean;
|
|
70
|
+
encryptionKeyPath?: string;
|
|
64
71
|
}
|
|
65
72
|
/**
|
|
66
73
|
* Session Store for persisting and restoring chat sessions
|
|
@@ -102,6 +109,8 @@ export declare class SessionStore {
|
|
|
102
109
|
* unprotected. Callers MUST hold the session lock before invoking this.
|
|
103
110
|
*/
|
|
104
111
|
private writeSessionUnlocked;
|
|
112
|
+
private shouldEncrypt;
|
|
113
|
+
private decodeContent;
|
|
105
114
|
/**
|
|
106
115
|
* Load a session from disk.
|
|
107
116
|
*
|