@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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { bindFactsMemorySession } from '../../memory/facts-memory.js';
|
|
1
2
|
/**
|
|
2
3
|
* Agent Executor Module
|
|
3
4
|
*
|
|
@@ -7,6 +8,7 @@
|
|
|
7
8
|
*
|
|
8
9
|
* @module agent/execution
|
|
9
10
|
*/
|
|
11
|
+
import { BoundedOutput } from '../../utils/bounded-output.js';
|
|
10
12
|
import { resolveFirstTokenStallTimeoutMs, resolveStallTimeoutMs, withStallGuard } from "../../utils/stream-stall-guard.js";
|
|
11
13
|
import { startHeadlessPromptProgress } from "../../cli/headless-prompt-progress.js";
|
|
12
14
|
import { HEADLESS_LOCAL_COMPACT_ALWAYS_INCLUDE, HEADLESS_LOCAL_COMPACT_MAX_TOOLS, isHeadlessLocalPromptCompact, } from "../../config/headless-local-prompt.js";
|
|
@@ -27,6 +29,9 @@ import { compress as tokenJuice, isTokenJuiceEnabled, JUICE_MIN_CHARS } from "..
|
|
|
27
29
|
import { formatToolResultForRecovery, getRestorableCompressor, } from "../../context/restorable-compression.js";
|
|
28
30
|
import { recordCompactionFork } from "../../context/compaction-fork.js";
|
|
29
31
|
import { getActiveRunStore } from "../../observability/run-store.js";
|
|
32
|
+
import { ToolLoopGuard } from "./tool-loop-guard.js";
|
|
33
|
+
import { getGlobalEventBus } from "../../events/event-bus.js";
|
|
34
|
+
import { takeFirstUseHint } from "../../utils/first-use-hints.js";
|
|
30
35
|
import { getTurnMetricsRecorder } from '../../observability/turn-metrics.js';
|
|
31
36
|
import { shouldCompactBeforeToolExec, estimateToolResultTokens } from "../../context/proactive-compaction.js";
|
|
32
37
|
import { formatTokenUsage, estimateCost } from "../../utils/token-display.js";
|
|
@@ -35,7 +40,7 @@ import { getModelToolConfig } from "../../config/model-tools.js";
|
|
|
35
40
|
import { getLatencyOptimizer, getStreamingOptimizer } from "../../optimization/latency-optimizer.js";
|
|
36
41
|
import { buildTextEmotionalPresenceContext } from "../../companion/reply-augment.js";
|
|
37
42
|
import { guardRelationshipReply, SAFE_RELATIONSHIP_REPAIR, } from "../../conversation/relationship-safety.js";
|
|
38
|
-
import { classifyLisaIntrospection, guardLisaOperationalSelfInspectionReply, isLisaEvolutionRequest, renderLisaOperationalSelfResponse, } from '../../identity/lisa-introspection.js';
|
|
43
|
+
import { classifyLisaIntrospection, guardLisaOperationalSelfInspectionReply, isLisaEvolutionRequest, isLisaSubjectiveConsciousnessQuestion, renderLisaOperationalSelfResponse, } from '../../identity/lisa-introspection.js';
|
|
39
44
|
import { withLlmStreamRetry } from '../../codebuddy/llm-retry.js';
|
|
40
45
|
import { getStreamingAdapter } from '../../tools/streaming-adapter.js';
|
|
41
46
|
import { notify } from '../proactive/notification-default-sink.js';
|
|
@@ -467,16 +472,28 @@ export class AgentExecutor {
|
|
|
467
472
|
/** Execute one tool and buffer optional streaming-adapter output. */
|
|
468
473
|
async executeToolForBatch(toolCall, executionExtra, signal, startedAt = Date.now()) {
|
|
469
474
|
const streamChunks = [];
|
|
475
|
+
const streamPreview = new BoundedOutput(256 * 1024);
|
|
476
|
+
let collapsed = false;
|
|
477
|
+
const appendStreamChunk = (chunk) => {
|
|
478
|
+
if (!chunk || signal?.aborted)
|
|
479
|
+
return;
|
|
480
|
+
streamPreview.append(chunk);
|
|
481
|
+
collapsed ||= streamPreview.omittedBytes > 0 || streamChunks.length >= 1024;
|
|
482
|
+
if (collapsed)
|
|
483
|
+
streamChunks.splice(0, streamChunks.length, streamPreview.text());
|
|
484
|
+
else
|
|
485
|
+
streamChunks.push(chunk);
|
|
486
|
+
};
|
|
470
487
|
const extraWithSignal = signal
|
|
471
488
|
? { ...(executionExtra ?? {}), abortSignal: signal }
|
|
472
489
|
: executionExtra;
|
|
473
490
|
const execute = async () => {
|
|
474
|
-
const streamingTools = new Set(['bash', 'reason', 'generate_document']);
|
|
491
|
+
const streamingTools = new Set(['bash', 'reason', 'generate_document', 'code_exec']);
|
|
475
492
|
if (streamingTools.has(toolCall.function.name)) {
|
|
476
493
|
const generator = this.deps.toolHandler.executeToolStreaming(toolCall, extraWithSignal);
|
|
477
494
|
let generated = await generator.next();
|
|
478
495
|
while (!generated.done) {
|
|
479
|
-
|
|
496
|
+
appendStreamChunk(generated.value);
|
|
480
497
|
generated = await generator.next();
|
|
481
498
|
}
|
|
482
499
|
return {
|
|
@@ -486,7 +503,7 @@ export class AgentExecutor {
|
|
|
486
503
|
}
|
|
487
504
|
const streamingAdapter = getStreamingAdapter();
|
|
488
505
|
if (streamingAdapter.supportsStreaming(toolCall.function.name)) {
|
|
489
|
-
const result = await streamingAdapter.wrapWithStreaming(toolCall.function.name, () => this.executeToolViaLane(toolCall, extraWithSignal),
|
|
506
|
+
const result = await streamingAdapter.wrapWithStreaming(toolCall.function.name, () => this.executeToolViaLane(toolCall, extraWithSignal), appendStreamChunk);
|
|
490
507
|
return { result, streamChunks };
|
|
491
508
|
}
|
|
492
509
|
return {
|
|
@@ -740,7 +757,7 @@ export class AgentExecutor {
|
|
|
740
757
|
const operationId = getLatencyOptimizer().startOperation('assistant_turn', startedAt);
|
|
741
758
|
let recordedFirstVisibleResponse = false;
|
|
742
759
|
try {
|
|
743
|
-
for await (const event of this.runTurnLoop(message, history, messages, abortController, transientContext, relationshipSafety, surface, introspectionText)) {
|
|
760
|
+
for await (const event of bindFactsMemorySession(this.deps.client, this.runTurnLoop(message, history, messages, abortController, transientContext, relationshipSafety, surface, introspectionText))) {
|
|
744
761
|
if (!recordedFirstVisibleResponse &&
|
|
745
762
|
(event.type === 'content' || event.type === 'reasoning' || event.type === 'tool_calls')) {
|
|
746
763
|
recordedFirstVisibleResponse = true;
|
|
@@ -783,7 +800,9 @@ export class AgentExecutor {
|
|
|
783
800
|
const introspectionIntent = classifyLisaIntrospection(introspectionTextForTurn);
|
|
784
801
|
const readOnlySelfInspection = introspectionIntent === 'describe' || introspectionIntent === 'inspect';
|
|
785
802
|
const guardGenerativeSelfInspection = introspectionIntent === 'improve';
|
|
786
|
-
const
|
|
803
|
+
const codeResearch = surface === 'cli' && introspectionIntent === 'inspect' &&
|
|
804
|
+
!isLisaSubjectiveConsciousnessQuestion(introspectionTextForTurn);
|
|
805
|
+
const isolatedSharedHost = surface === 'http' || codeResearch;
|
|
787
806
|
const turnCwd = typeof this.deps.toolHandler.getWorkingDirectory === 'function'
|
|
788
807
|
? this.deps.toolHandler.getWorkingDirectory()
|
|
789
808
|
: process.cwd();
|
|
@@ -806,8 +825,9 @@ export class AgentExecutor {
|
|
|
806
825
|
let permissionMode;
|
|
807
826
|
let providerName;
|
|
808
827
|
let operationalRobotName;
|
|
809
|
-
if (introspectionIntent !== null) {
|
|
810
|
-
|
|
828
|
+
if (introspectionIntent !== null || surface === 'cli') {
|
|
829
|
+
if (introspectionIntent !== null)
|
|
830
|
+
operationalRobotName = await this.getOperationalRobotName();
|
|
811
831
|
try {
|
|
812
832
|
const { getPermissionModeManager } = await import('../../security/permission-modes.js');
|
|
813
833
|
permissionMode = getPermissionModeManager().getMode();
|
|
@@ -822,7 +842,7 @@ export class AgentExecutor {
|
|
|
822
842
|
// Legacy/test clients may not expose provider metadata.
|
|
823
843
|
}
|
|
824
844
|
}
|
|
825
|
-
if (readOnlySelfInspection) {
|
|
845
|
+
if (readOnlySelfInspection && !codeResearch) {
|
|
826
846
|
if (isLisaEvolutionRequest(introspectionTextForTurn)) {
|
|
827
847
|
const { formatEvolutionNotesForVoice, queryEvolutionNotes, readEvolutionNotes, } = await import('../../self-model/evolution-notes.js');
|
|
828
848
|
const since = introspectionTextForTurn.match(/\b20\d{2}-\d{2}-\d{2}\b/)?.[0];
|
|
@@ -850,6 +870,8 @@ export class AgentExecutor {
|
|
|
850
870
|
// every strict describe/inspect turn. No provider, plugin or generic
|
|
851
871
|
// workspace tool participates in this path.
|
|
852
872
|
const { buildOperationalSelfModel } = await import('../../identity/operational-self-model.js');
|
|
873
|
+
const { getRuntimeSettingsSnapshot } = await import('../../services/runtime-settings-context.js');
|
|
874
|
+
const activeSettings = getRuntimeSettingsSnapshot({ surface, model: this.deps.client.getCurrentModel(), provider: providerName, maxToolRounds: this.config.maxToolRounds });
|
|
853
875
|
const selfModel = buildOperationalSelfModel({
|
|
854
876
|
cwd: turnCwd,
|
|
855
877
|
focus: introspectionTextForTurn,
|
|
@@ -859,6 +881,7 @@ export class AgentExecutor {
|
|
|
859
881
|
: {}),
|
|
860
882
|
runtime: {
|
|
861
883
|
providerInvoked: false,
|
|
884
|
+
...(activeSettings.theme ? { theme: activeSettings.theme.active } : {}),
|
|
862
885
|
...(this.deps.client.getCurrentModel()
|
|
863
886
|
? { model: this.deps.client.getCurrentModel() }
|
|
864
887
|
: {}),
|
|
@@ -900,6 +923,11 @@ export class AgentExecutor {
|
|
|
900
923
|
};
|
|
901
924
|
const maxToolRounds = this.config.maxToolRounds;
|
|
902
925
|
let toolRounds = 0;
|
|
926
|
+
// One guard per task: warnings/stops never leak into the next user turn.
|
|
927
|
+
const loopGuard = new ToolLoopGuard();
|
|
928
|
+
let pendingLoopDecision = null;
|
|
929
|
+
let loopGuardStopped = false;
|
|
930
|
+
let observationShortened = false;
|
|
903
931
|
let totalOutputTokens = 0;
|
|
904
932
|
let totalInputTokensForCost = 0;
|
|
905
933
|
let providerPromptTokens = 0;
|
|
@@ -1042,6 +1070,15 @@ export class AgentExecutor {
|
|
|
1042
1070
|
alwaysInclude: ['view_file', 'bash', 'search'],
|
|
1043
1071
|
};
|
|
1044
1072
|
}
|
|
1073
|
+
if (surface === 'cli' && !codeResearch) {
|
|
1074
|
+
const { runtimeInspectionTools } = await import('../../services/runtime-settings-context.js');
|
|
1075
|
+
const inspectionTools = runtimeInspectionTools(turnQueryText);
|
|
1076
|
+
if (inspectionTools.length)
|
|
1077
|
+
selectionOpts = { ...selectionOpts, alwaysInclude: [...(selectionOpts.alwaysInclude ?? []), ...inspectionTools] };
|
|
1078
|
+
}
|
|
1079
|
+
if (codeResearch) {
|
|
1080
|
+
selectionOpts = { ...selectionOpts, alwaysInclude: ['self_describe'] };
|
|
1081
|
+
}
|
|
1045
1082
|
const selectionPromise = this.deps.toolSelectionStrategy.selectToolsForQuery(turnQueryText, selectionOpts);
|
|
1046
1083
|
// Build context in a scratch array while the prompt and tools are being
|
|
1047
1084
|
// prepared. It is appended only after transcript preparation so the
|
|
@@ -1094,7 +1131,9 @@ export class AgentExecutor {
|
|
|
1094
1131
|
incrementalTokenCounter.invalidate();
|
|
1095
1132
|
logger.debug(`[agent-executor] system prompt rebuilt query-aware (${rebuiltSystemPrompt.length} chars)`);
|
|
1096
1133
|
}
|
|
1097
|
-
let tools =
|
|
1134
|
+
let tools = codeResearch
|
|
1135
|
+
? selectionResult.tools.filter(tool => tool.function.name === 'self_describe')
|
|
1136
|
+
: selectionResult.tools;
|
|
1098
1137
|
let forcedChatOnlyToolRunModel = null;
|
|
1099
1138
|
if (toolRounds === 0) {
|
|
1100
1139
|
this.deps.toolSelectionStrategy.cacheTools(tools, activeModelName);
|
|
@@ -1109,7 +1148,7 @@ export class AgentExecutor {
|
|
|
1109
1148
|
tools = [];
|
|
1110
1149
|
}
|
|
1111
1150
|
}
|
|
1112
|
-
const turnExecutionExtra = introspectionIntent
|
|
1151
|
+
const turnExecutionExtra = introspectionIntent || surface === 'cli'
|
|
1113
1152
|
? {
|
|
1114
1153
|
...(activeModelName ? { model: activeModelName } : {}),
|
|
1115
1154
|
...(providerName ? { provider: providerName } : {}),
|
|
@@ -1118,6 +1157,7 @@ export class AgentExecutor {
|
|
|
1118
1157
|
...(operationalRobotName
|
|
1119
1158
|
? { robotName: operationalRobotName }
|
|
1120
1159
|
: {}),
|
|
1160
|
+
maxToolRounds,
|
|
1121
1161
|
exposedToolNames: tools.map((tool) => tool.function.name),
|
|
1122
1162
|
introspectionIntent,
|
|
1123
1163
|
}
|
|
@@ -1142,6 +1182,27 @@ export class AgentExecutor {
|
|
|
1142
1182
|
throw error;
|
|
1143
1183
|
}
|
|
1144
1184
|
preparedMessages.push(...contextBlocks);
|
|
1185
|
+
if (codeResearch) {
|
|
1186
|
+
preparedMessages.push({ role: 'system', content: 'Research your actual implementation using self_describe: operation=list/read/search, relative src/ paths (source checkout) or dist/ paths (installed package). Search literal symbols, read relevant code, and cite paths and line numbers. Only this confined read-only tool is exposed for this turn. Do not mistake the user project for your implementation. Do not claim a code graph is available without evidence.' });
|
|
1187
|
+
}
|
|
1188
|
+
if (surface === 'cli') {
|
|
1189
|
+
const { formatRuntimeSettingsContext } = await import('../../services/runtime-settings-context.js');
|
|
1190
|
+
preparedMessages.push({ role: 'system', content: formatRuntimeSettingsContext({
|
|
1191
|
+
surface, model: activeModelName, provider: providerName, maxToolRounds,
|
|
1192
|
+
}) });
|
|
1193
|
+
}
|
|
1194
|
+
else {
|
|
1195
|
+
// P5: other surfaces have no runtime_settings block; only a non-default
|
|
1196
|
+
// code_exec policy needs its short guidance.
|
|
1197
|
+
const { resolveCodeExecPolicy, CODE_EXEC_PREFER_HINT, CODE_EXEC_OFF_NOTICE } = await import('../../config/code-exec-policy.js');
|
|
1198
|
+
const codeExecPolicy = resolveCodeExecPolicy(activeModelName ?? undefined);
|
|
1199
|
+
if (codeExecPolicy.policy !== 'offer') {
|
|
1200
|
+
preparedMessages.push({
|
|
1201
|
+
role: 'system',
|
|
1202
|
+
content: `<code_exec_policy policy="${codeExecPolicy.policy}" source="${codeExecPolicy.source}">\n${codeExecPolicy.policy === 'prefer' ? CODE_EXEC_PREFER_HINT : CODE_EXEC_OFF_NOTICE}\n</code_exec_policy>`,
|
|
1203
|
+
});
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1145
1206
|
if (emotionalPresenceContext) {
|
|
1146
1207
|
preparedMessages.push({
|
|
1147
1208
|
role: 'system',
|
|
@@ -1190,6 +1251,9 @@ export class AgentExecutor {
|
|
|
1190
1251
|
const progress = startHeadlessPromptProgress();
|
|
1191
1252
|
const streamFactory = () => withStallGuard(this.deps.client.chatStream(preparedMessages, tools, {
|
|
1192
1253
|
streamRetry: false,
|
|
1254
|
+
// An explicit request to inspect implementation needs an observation
|
|
1255
|
+
// before an answer. Only the confined self_describe reader is exposed.
|
|
1256
|
+
...(codeResearch && toolRounds === 0 && tools.length ? { tool_choice: 'required' } : {}),
|
|
1193
1257
|
turnMetrics: {
|
|
1194
1258
|
recorder: getTurnMetricsRecorder(),
|
|
1195
1259
|
inputTokens,
|
|
@@ -1297,6 +1361,11 @@ export class AgentExecutor {
|
|
|
1297
1361
|
}
|
|
1298
1362
|
toolCalls = filteredToolCalls.length > 0 ? filteredToolCalls : undefined;
|
|
1299
1363
|
}
|
|
1364
|
+
if (codeResearch && toolCalls?.some(call => call.function.name !== 'self_describe')) {
|
|
1365
|
+
yield { type: 'content', content: 'Code inspection is read-only; the requested tool is not available in this inspection turn.' };
|
|
1366
|
+
yield { type: 'done' };
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1300
1369
|
if (relationshipSafety && Array.isArray(toolCalls)) {
|
|
1301
1370
|
toolCalls = toolCalls.map(prepareRelationshipSafeInteractiveToolCall);
|
|
1302
1371
|
}
|
|
@@ -1471,8 +1540,15 @@ export class AgentExecutor {
|
|
|
1471
1540
|
estimatedTokens,
|
|
1472
1541
|
contextWindow,
|
|
1473
1542
|
});
|
|
1543
|
+
// The results of this round's calls are pushed after execution:
|
|
1544
|
+
// name them so repair does not close them with a synthetic
|
|
1545
|
+
// '[result lost during compaction]' that would then win over
|
|
1546
|
+
// the real output.
|
|
1474
1547
|
const compacted = compactTurnMessagesInPlace(this.deps.contextManager, messages, {
|
|
1475
1548
|
isolatedSharedHost,
|
|
1549
|
+
pendingToolCallIds: toolCalls
|
|
1550
|
+
.map((call) => call.id)
|
|
1551
|
+
.filter((id) => typeof id === 'string' && id.length > 0),
|
|
1476
1552
|
});
|
|
1477
1553
|
if (compacted)
|
|
1478
1554
|
incrementalTokenCounter.invalidate();
|
|
@@ -1565,6 +1641,18 @@ export class AgentExecutor {
|
|
|
1565
1641
|
};
|
|
1566
1642
|
}
|
|
1567
1643
|
}
|
|
1644
|
+
// --- Tool loop guard (P1): observe the native result before any
|
|
1645
|
+
// optimizer so "same call + same result" means no progress. The
|
|
1646
|
+
// decision is applied at the batch boundary so tool_call/tool_result
|
|
1647
|
+
// pairs are never split.
|
|
1648
|
+
const loopDecision = loopGuard.observe({
|
|
1649
|
+
name: toolCall.function.name,
|
|
1650
|
+
argumentsJson: toolCall.function.arguments || '{}',
|
|
1651
|
+
result,
|
|
1652
|
+
});
|
|
1653
|
+
if (loopDecision.action === 'stop' || (loopDecision.action === 'warn' && !pendingLoopDecision)) {
|
|
1654
|
+
pendingLoopDecision = loopDecision;
|
|
1655
|
+
}
|
|
1568
1656
|
// Expand the current turn's cached schema after discovery or live
|
|
1569
1657
|
// authoring. Without this, a newly created tool is dispatchable but
|
|
1570
1658
|
// invisible to the model until the next user turn.
|
|
@@ -1705,6 +1793,8 @@ export class AgentExecutor {
|
|
|
1705
1793
|
signal: abortController?.signal,
|
|
1706
1794
|
});
|
|
1707
1795
|
let modelStreamContent = optimization.content;
|
|
1796
|
+
if (optimization.optimized)
|
|
1797
|
+
observationShortened = true;
|
|
1708
1798
|
// lm-resizer owns the semantic budget when available. Its absence or
|
|
1709
1799
|
// an intentionally raw failure still receives a model-aware hard cap;
|
|
1710
1800
|
// the exact observation remains available through restore_context.
|
|
@@ -1715,6 +1805,7 @@ export class AgentExecutor {
|
|
|
1715
1805
|
this.deps.tokenCounter.countTokens(modelStreamContent) > budgetTokens) {
|
|
1716
1806
|
const truncated = semanticTruncate(modelStreamContent, { maxChars: hardLimitChars });
|
|
1717
1807
|
if (truncated.truncated) {
|
|
1808
|
+
observationShortened = true;
|
|
1718
1809
|
const recoveryNote = toolCall.id
|
|
1719
1810
|
? `\n\n[Full exact observation: restore_context({"identifier":${JSON.stringify(toolCall.id)}})]`
|
|
1720
1811
|
: '';
|
|
@@ -1850,6 +1941,68 @@ export class AgentExecutor {
|
|
|
1850
1941
|
yield { type: "done" };
|
|
1851
1942
|
return;
|
|
1852
1943
|
}
|
|
1944
|
+
// First shortened observation in this profile: one user-facing tip (P4 first-use hints).
|
|
1945
|
+
if (observationShortened) {
|
|
1946
|
+
observationShortened = false;
|
|
1947
|
+
const tip = takeFirstUseHint('restore_context');
|
|
1948
|
+
if (tip)
|
|
1949
|
+
yield { type: "content", content: `\n💡 ${tip}\n` };
|
|
1950
|
+
}
|
|
1951
|
+
// Tool-call/result pairs are complete here: apply the loop guard.
|
|
1952
|
+
if (pendingLoopDecision) {
|
|
1953
|
+
const decision = pendingLoopDecision;
|
|
1954
|
+
pendingLoopDecision = null;
|
|
1955
|
+
const loopData = {
|
|
1956
|
+
action: decision.action,
|
|
1957
|
+
loopType: decision.kind,
|
|
1958
|
+
toolNames: decision.toolNames,
|
|
1959
|
+
repetitions: decision.repetitions,
|
|
1960
|
+
toolRounds,
|
|
1961
|
+
};
|
|
1962
|
+
try {
|
|
1963
|
+
getGlobalEventBus().emit('agent:loop_detected', {
|
|
1964
|
+
loopType: decision.kind,
|
|
1965
|
+
detail: decision.message,
|
|
1966
|
+
count: decision.repetitions,
|
|
1967
|
+
turnIndex: toolRounds,
|
|
1968
|
+
});
|
|
1969
|
+
}
|
|
1970
|
+
catch (err) {
|
|
1971
|
+
logger.debug('[loop-guard] event emission failed', { error: String(err) });
|
|
1972
|
+
}
|
|
1973
|
+
if (decision.action === 'warn') {
|
|
1974
|
+
logger.warn('[loop-guard] loop warning injected', loopData);
|
|
1975
|
+
yield { type: "content", content: `\n⚠️ ${decision.message}\n` };
|
|
1976
|
+
messages.push({
|
|
1977
|
+
role: 'system',
|
|
1978
|
+
content: `<context type="loop-guard">\n${decision.message}\n</context>`,
|
|
1979
|
+
});
|
|
1980
|
+
}
|
|
1981
|
+
else {
|
|
1982
|
+
logger.warn('[loop-guard] turn stopped', loopData);
|
|
1983
|
+
const runId = typeof this.deps.toolHandler.getRunId === 'function'
|
|
1984
|
+
? this.deps.toolHandler.getRunId()
|
|
1985
|
+
: undefined;
|
|
1986
|
+
if (runId) {
|
|
1987
|
+
try {
|
|
1988
|
+
getActiveRunStore()?.emit(runId, {
|
|
1989
|
+
type: 'decision',
|
|
1990
|
+
data: { kind: 'loop_guard_stopped', ...loopData },
|
|
1991
|
+
});
|
|
1992
|
+
}
|
|
1993
|
+
catch { /* observability is optional */ }
|
|
1994
|
+
}
|
|
1995
|
+
yield {
|
|
1996
|
+
type: 'run_event',
|
|
1997
|
+
runEvent: { runId: runId ?? '', eventType: 'loop_guard_stopped', data: loopData },
|
|
1998
|
+
};
|
|
1999
|
+
history.push({ type: 'assistant', content: decision.message, timestamp: new Date() });
|
|
2000
|
+
messages.push({ role: 'assistant', content: decision.message });
|
|
2001
|
+
yield { type: "content", content: `\n\n${decision.message}` };
|
|
2002
|
+
loopGuardStopped = true;
|
|
2003
|
+
break;
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
1853
2006
|
// Tool-call/result pairs are complete at this boundary, so a steer
|
|
1854
2007
|
// that arrived while tools were running can now be injected safely.
|
|
1855
2008
|
const deferredSteering = this.deps.messageQueue?.hasSteeringMessage()
|
|
@@ -1956,7 +2109,7 @@ export class AgentExecutor {
|
|
|
1956
2109
|
break;
|
|
1957
2110
|
}
|
|
1958
2111
|
}
|
|
1959
|
-
if (toolRounds >= maxToolRounds && !terminateDetectedStreaming) {
|
|
2112
|
+
if (toolRounds >= maxToolRounds && !terminateDetectedStreaming && !loopGuardStopped) {
|
|
1960
2113
|
const limitMessage = 'Maximum tool execution rounds reached.';
|
|
1961
2114
|
history.push({ type: 'assistant', content: limitMessage, timestamp: new Date() });
|
|
1962
2115
|
messages.push({ role: 'assistant', content: limitMessage });
|
|
@@ -59,9 +59,18 @@ export declare function prepareIsolatedTurnMessages(messages: CodeBuddyMessage[]
|
|
|
59
59
|
* transcript never shrank, the middleware 'compact' action did nothing, and
|
|
60
60
|
* proactive compaction re-fired forever while the provider limit approached.
|
|
61
61
|
* Returns true when the transcript actually changed.
|
|
62
|
+
*
|
|
63
|
+
* `pendingToolCallIds` names the calls of the round being executed. Their
|
|
64
|
+
* results are pushed AFTER this compaction, so repairing them here injected
|
|
65
|
+
* `[result lost during compaction]`; the real result then lost the first-wins
|
|
66
|
+
* duplicate arbitration and the model never saw the tool output. Those
|
|
67
|
+
* placeholders are removed while every other repair — historical orphans,
|
|
68
|
+
* id-less calls, duplicates, ordering — is kept. The provider frontier stays
|
|
69
|
+
* `prepareTurnMessages`, which still closes any call left unanswered.
|
|
62
70
|
*/
|
|
63
71
|
export declare function compactTurnMessagesInPlace(contextManager: ContextManagerV2, messages: CodeBuddyMessage[], options?: {
|
|
64
72
|
isolatedSharedHost?: boolean;
|
|
73
|
+
pendingToolCallIds?: Iterable<string>;
|
|
65
74
|
}): boolean;
|
|
66
75
|
export interface InitialContextDeps {
|
|
67
76
|
message: string;
|
|
@@ -151,6 +151,52 @@ export function prepareIsolatedTurnMessages(messages) {
|
|
|
151
151
|
? repairToolCallPairs(messages)
|
|
152
152
|
: [...messages];
|
|
153
153
|
}
|
|
154
|
+
/** Tool calls of the in-flight round that have no result in `messages` yet. */
|
|
155
|
+
function unansweredPendingCalls(messages, pendingToolCallIds) {
|
|
156
|
+
const pending = new Set();
|
|
157
|
+
for (const id of pendingToolCallIds ?? [])
|
|
158
|
+
if (id)
|
|
159
|
+
pending.add(id);
|
|
160
|
+
if (pending.size === 0)
|
|
161
|
+
return pending;
|
|
162
|
+
for (const message of messages) {
|
|
163
|
+
if (message.role !== 'tool')
|
|
164
|
+
continue;
|
|
165
|
+
const callId = message.tool_call_id;
|
|
166
|
+
if (callId)
|
|
167
|
+
pending.delete(callId);
|
|
168
|
+
}
|
|
169
|
+
return pending;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Drop the synthetic `[result lost during compaction]` placeholders that
|
|
173
|
+
* transcript repair injected for calls that are merely still running.
|
|
174
|
+
*
|
|
175
|
+
* Returns null when a pending call did not survive compaction at all: the
|
|
176
|
+
* caller then keeps the transcript untouched rather than leaving a call whose
|
|
177
|
+
* real result would become an unpairable orphan.
|
|
178
|
+
*/
|
|
179
|
+
function withoutPendingPlaceholders(compacted, pending) {
|
|
180
|
+
const survivingCallIds = new Set();
|
|
181
|
+
for (const message of compacted) {
|
|
182
|
+
const calls = message.tool_calls;
|
|
183
|
+
if (Array.isArray(calls))
|
|
184
|
+
for (const call of calls)
|
|
185
|
+
if (call?.id)
|
|
186
|
+
survivingCallIds.add(call.id);
|
|
187
|
+
}
|
|
188
|
+
for (const id of pending)
|
|
189
|
+
if (!survivingCallIds.has(id))
|
|
190
|
+
return null;
|
|
191
|
+
// `pending` only holds ids with no result in `original`, so every tool
|
|
192
|
+
// message carrying one of them was invented by repair just now.
|
|
193
|
+
return compacted.filter((message) => {
|
|
194
|
+
if (message.role !== 'tool')
|
|
195
|
+
return true;
|
|
196
|
+
const callId = message.tool_call_id;
|
|
197
|
+
return !(callId && pending.has(callId));
|
|
198
|
+
});
|
|
199
|
+
}
|
|
154
200
|
/**
|
|
155
201
|
* Compact + repair IN PLACE — for mid-loop compaction sites where `messages`
|
|
156
202
|
* is a SHARED reference (the turn loop and its helpers keep pushing into it).
|
|
@@ -159,11 +205,27 @@ export function prepareIsolatedTurnMessages(messages) {
|
|
|
159
205
|
* transcript never shrank, the middleware 'compact' action did nothing, and
|
|
160
206
|
* proactive compaction re-fired forever while the provider limit approached.
|
|
161
207
|
* Returns true when the transcript actually changed.
|
|
208
|
+
*
|
|
209
|
+
* `pendingToolCallIds` names the calls of the round being executed. Their
|
|
210
|
+
* results are pushed AFTER this compaction, so repairing them here injected
|
|
211
|
+
* `[result lost during compaction]`; the real result then lost the first-wins
|
|
212
|
+
* duplicate arbitration and the model never saw the tool output. Those
|
|
213
|
+
* placeholders are removed while every other repair — historical orphans,
|
|
214
|
+
* id-less calls, duplicates, ordering — is kept. The provider frontier stays
|
|
215
|
+
* `prepareTurnMessages`, which still closes any call left unanswered.
|
|
162
216
|
*/
|
|
163
217
|
export function compactTurnMessagesInPlace(contextManager, messages, options = {}) {
|
|
164
|
-
const
|
|
165
|
-
|
|
218
|
+
const pending = unansweredPendingCalls(messages, options.pendingToolCallIds);
|
|
219
|
+
const prepared = prepareTurnMessages(contextManager, messages, options);
|
|
220
|
+
if (prepared === messages)
|
|
166
221
|
return false;
|
|
222
|
+
let compacted = prepared;
|
|
223
|
+
if (pending.size > 0) {
|
|
224
|
+
const protectedMessages = withoutPendingPlaceholders(prepared, pending);
|
|
225
|
+
if (!protectedMessages)
|
|
226
|
+
return false; // pending call dropped: never risk its real result
|
|
227
|
+
compacted = protectedMessages;
|
|
228
|
+
}
|
|
167
229
|
const changed = compacted.length !== messages.length || compacted.some((m, i) => m !== messages[i]);
|
|
168
230
|
if (!changed)
|
|
169
231
|
return false;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToolLoopGuard — stops unproductive tool-call loops inside one agent task.
|
|
3
|
+
*
|
|
4
|
+
* New code written for Buddy (comparatif plan P1); nothing was copied from
|
|
5
|
+
* another project. It does not wire the older, unwired
|
|
6
|
+
* `src/agent/loop-detection-service.ts` for technical reasons: that service
|
|
7
|
+
* keys a loop on tool name + arguments only, so a polling call whose output
|
|
8
|
+
* changes (process status, job progress) is flagged as a loop; it has no
|
|
9
|
+
* per-tool exemption and no warn → stop escalation. This guard adds the result
|
|
10
|
+
* fingerprint, `repeatSafe` metadata and a one-warning-then-stop contract, and
|
|
11
|
+
* emits the same `agent:loop_detected` event, so the domain-event bridge and
|
|
12
|
+
* the `agent-loop-alert` rule template keep working unchanged.
|
|
13
|
+
*
|
|
14
|
+
* Contract:
|
|
15
|
+
* - "No progress" means the SAME tool, SAME canonical arguments AND the SAME
|
|
16
|
+
* result fingerprint. A polling call whose output changes is progress.
|
|
17
|
+
* - Tools whose metadata declares `repeatSafe: true` are never observed.
|
|
18
|
+
* - First detection in a task → `warn` (exactly once per task). Any loop
|
|
19
|
+
* pattern that persists for `stopAfterWarning` more observations after the
|
|
20
|
+
* warning → `stop`.
|
|
21
|
+
* - The guard is a validation, not a confirmation: nothing in the autonomy /
|
|
22
|
+
* YOLO configuration disables it. A new guard instance is created per task.
|
|
23
|
+
*/
|
|
24
|
+
import type { ToolResult } from '../../types/index.js';
|
|
25
|
+
export type ToolLoopKind = 'repeated_call' | 'repeated_cycle';
|
|
26
|
+
export interface ToolLoopObservation {
|
|
27
|
+
name: string;
|
|
28
|
+
/** Raw JSON arguments string as sent by the model. */
|
|
29
|
+
argumentsJson: string;
|
|
30
|
+
result?: Pick<ToolResult, 'success' | 'output' | 'error'> | null;
|
|
31
|
+
}
|
|
32
|
+
export type ToolLoopDecision = {
|
|
33
|
+
action: 'none';
|
|
34
|
+
} | {
|
|
35
|
+
action: 'warn' | 'stop';
|
|
36
|
+
kind: ToolLoopKind;
|
|
37
|
+
toolNames: string[];
|
|
38
|
+
repetitions: number;
|
|
39
|
+
message: string;
|
|
40
|
+
};
|
|
41
|
+
export interface ToolLoopGuardOptions {
|
|
42
|
+
/** Identical no-progress observations (or cycle repetitions) before warning. Default 5. */
|
|
43
|
+
threshold?: number;
|
|
44
|
+
/** Additional looping observations after the warning before stopping. Default 3. */
|
|
45
|
+
stopAfterWarning?: number;
|
|
46
|
+
/** Longest cycle period inspected (A→B→A→B is period 2). Default 3. */
|
|
47
|
+
maxCyclePeriod?: number;
|
|
48
|
+
/** Override for tests; defaults to the `repeatSafe` flag in tool metadata. */
|
|
49
|
+
isRepeatSafe?: (toolName: string) => boolean;
|
|
50
|
+
}
|
|
51
|
+
export declare function isRepeatSafeTool(toolName: string): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Loop fingerprint of a successful `a2a_call`: every SendMessage creates a new remote task,
|
|
54
|
+
* so `taskId` / `contextId` change even when the peer returns the same text. For that tool
|
|
55
|
+
* only, and only when the output has exactly the task shape, those two technical ids are
|
|
56
|
+
* dropped; peer, state, text and instruction stay. Anything else (another tool, invalid JSON,
|
|
57
|
+
* another shape such as a message result) is fingerprinted raw, so changing output is progress.
|
|
58
|
+
*/
|
|
59
|
+
export declare function loopFingerprintOutput(toolName: string, output: string): string;
|
|
60
|
+
export declare class ToolLoopGuard {
|
|
61
|
+
private readonly threshold;
|
|
62
|
+
private readonly stopAfterWarning;
|
|
63
|
+
private readonly maxCyclePeriod;
|
|
64
|
+
private readonly isRepeatSafe;
|
|
65
|
+
private history;
|
|
66
|
+
private warned;
|
|
67
|
+
private stopped;
|
|
68
|
+
constructor(options?: ToolLoopGuardOptions);
|
|
69
|
+
get hasWarned(): boolean;
|
|
70
|
+
get hasStopped(): boolean;
|
|
71
|
+
observe(observation: ToolLoopObservation): ToolLoopDecision;
|
|
72
|
+
/** Detect a repeated call or cycle repeated at least `repeats` times at the tail of history. */
|
|
73
|
+
private detect;
|
|
74
|
+
private warnMessage;
|
|
75
|
+
private stopMessage;
|
|
76
|
+
}
|