@phuetz/code-buddy 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.fr.md +56 -0
- package/README.md +74 -16
- package/codebuddy-runtime.json +5 -5
- package/dist/agent/codebuddy-agent.d.ts +18 -0
- package/dist/agent/codebuddy-agent.js +45 -8
- package/dist/agent/execution/agent-executor.d.ts +0 -9
- package/dist/agent/execution/agent-executor.js +176 -12
- package/dist/agent/execution/context-pipeline.d.ts +9 -0
- package/dist/agent/execution/context-pipeline.js +64 -2
- package/dist/agent/execution/tool-loop-guard.d.ts +76 -0
- package/dist/agent/execution/tool-loop-guard.js +165 -0
- package/dist/agent/execution/tool-selection-strategy.d.ts +7 -0
- package/dist/agent/execution/tool-selection-strategy.js +32 -2
- package/dist/agent/facades/session-facade.d.ts +0 -6
- package/dist/agent/facades/session-facade.js +0 -45
- package/dist/agent/hermes-claw-migrate.d.ts +0 -21
- package/dist/agent/hermes-claw-migrate.js +9 -4
- package/dist/agent/hermes-protocol-gateways.js +8 -1
- package/dist/agent/repo-profiler.js +17 -4
- package/dist/agent/self-improvement/authored-tool-runtime.js +10 -2
- package/dist/agent/self-improvement/evolution/ast-novelty.d.ts +5 -0
- package/dist/agent/self-improvement/evolution/ast-novelty.js +58 -23
- package/dist/agent/self-improvement/evolution/code-variant-store.d.ts +3 -0
- package/dist/agent/self-improvement/evolution/evolution-engine.js +2 -0
- package/dist/agent/self-improvement/evolution/protected-paths.js +6 -0
- package/dist/agent/self-improvement/evolution/variant-fitness.d.ts +13 -2
- package/dist/agent/self-improvement/evolution/variant-fitness.js +184 -58
- package/dist/agent/self-improvement/evolution/worktree-scorer.d.ts +2 -0
- package/dist/agent/self-improvement/evolution/worktree-scorer.js +15 -0
- package/dist/agent/self-improvement/evolutionary-archive.d.ts +2 -1
- package/dist/agent/self-improvement/evolutionary-archive.js +34 -5
- package/dist/agent/self-improvement/proposal-store.d.ts +1 -0
- package/dist/agent/self-improvement/proposal-store.js +79 -17
- package/dist/agent/self-improvement/skill-apply-journal.d.ts +44 -0
- package/dist/agent/self-improvement/skill-apply-journal.js +178 -0
- package/dist/agent/self-improvement/skill-behavior-benchmark.d.ts +3 -0
- package/dist/agent/self-improvement/skill-behavior-benchmark.js +19 -0
- package/dist/agent/self-improvement/skill-behavior-gate.d.ts +34 -0
- package/dist/agent/self-improvement/skill-behavior-gate.js +96 -0
- package/dist/agent/self-improvement/skill-engine.d.ts +22 -0
- package/dist/agent/self-improvement/skill-engine.js +385 -13
- package/dist/agent/self-improvement/skill-gate.d.ts +2 -0
- package/dist/agent/self-improvement/skill-gate.js +17 -2
- package/dist/agent/self-improvement/skill-mutator.d.ts +6 -1
- package/dist/agent/self-improvement/skill-mutator.js +51 -15
- package/dist/agent/self-improvement/skill-types.d.ts +2 -1
- package/dist/agent/self-improvement/types.d.ts +11 -0
- package/dist/agent/tool-handler.d.ts +1 -1
- package/dist/agent/tool-handler.js +28 -9
- package/dist/auth/profile-manager.js +1 -1
- package/dist/channels/companion-channel-turn.d.ts +53 -4
- package/dist/channels/companion-channel-turn.js +218 -16
- package/dist/channels/slash-parity.d.ts +4 -6
- package/dist/channels/slash-parity.js +9 -44
- package/dist/cli/session-commands.d.ts +9 -0
- package/dist/cli/session-commands.js +45 -5
- package/dist/cli/session-picker.d.ts +43 -0
- package/dist/cli/session-picker.js +128 -0
- package/dist/codebuddy/a2a-call-tool-defs.d.ts +26 -0
- package/dist/codebuddy/a2a-call-tool-defs.js +12 -0
- package/dist/codebuddy/fleet-tool-defs.d.ts +2 -0
- package/dist/codebuddy/fleet-tool-defs.js +55 -0
- package/dist/codebuddy/providers/chatgpt-headers.d.ts +18 -0
- package/dist/codebuddy/providers/chatgpt-headers.js +37 -0
- package/dist/codebuddy/providers/provider-chatgpt-responses.d.ts +2 -1
- package/dist/codebuddy/providers/provider-chatgpt-responses.js +10 -23
- package/dist/codebuddy/providers/provider-openai-compat.d.ts +7 -0
- package/dist/codebuddy/providers/provider-openai-compat.js +29 -1
- package/dist/codebuddy/ragchat-tool-defs.d.ts +31 -0
- package/dist/codebuddy/ragchat-tool-defs.js +19 -0
- package/dist/codebuddy/resource-catalog-tool-defs.d.ts +28 -0
- package/dist/codebuddy/resource-catalog-tool-defs.js +18 -0
- package/dist/codebuddy/tool-definitions/agent-tools.js +2 -2
- package/dist/codebuddy/tool-definitions/code-exec-tools.js +5 -1
- package/dist/codebuddy/tool-definitions/cron-tools.js +6 -0
- package/dist/codebuddy/tool-definitions/self-describe-tools.js +6 -1
- package/dist/codebuddy/tools.d.ts +0 -6
- package/dist/codebuddy/tools.js +25 -3
- package/dist/commands/cli/daemon-commands.js +3 -1
- package/dist/commands/cli/evolve-command.js +21 -5
- package/dist/commands/cli/fleet-collaboration-commands.d.ts +2 -0
- package/dist/commands/cli/fleet-collaboration-commands.js +57 -0
- package/dist/commands/cli/fleet-commands.js +29 -0
- package/dist/commands/cli/fleet-mission-commands.d.ts +3 -0
- package/dist/commands/cli/fleet-mission-commands.js +72 -0
- package/dist/commands/cli/fleet-rooms-commands.d.ts +10 -0
- package/dist/commands/cli/fleet-rooms-commands.js +289 -0
- package/dist/commands/cli/tools-commands.js +2 -1
- package/dist/commands/cli/triage-command.d.ts +8 -0
- package/dist/commands/cli/triage-command.js +72 -0
- package/dist/commands/cli/utility-commands.js +25 -1
- package/dist/commands/client-dispatcher.d.ts +7 -0
- package/dist/commands/client-dispatcher.js +73 -21
- package/dist/commands/cron-cli/index.d.ts +3 -1
- package/dist/commands/cron-cli/index.js +25 -0
- package/dist/commands/device-auth.d.ts +10 -0
- package/dist/commands/device-auth.js +91 -0
- package/dist/commands/enhanced-command-handler.d.ts +9 -0
- package/dist/commands/enhanced-command-handler.js +28 -6
- package/dist/commands/handlers/agent-handlers.d.ts +1 -1
- package/dist/commands/handlers/agent-handlers.js +4 -4
- package/dist/commands/handlers/channel-handlers.js +53 -2
- package/dist/commands/handlers/export-handlers.d.ts +3 -2
- package/dist/commands/handlers/export-handlers.js +55 -28
- package/dist/commands/handlers/extra-handlers.js +5 -1
- package/dist/commands/handlers/grill-me-handler.js +26 -18
- package/dist/commands/handlers/memory-handlers.d.ts +6 -2
- package/dist/commands/handlers/memory-handlers.js +35 -15
- package/dist/commands/handlers/missing-handlers.d.ts +2 -2
- package/dist/commands/handlers/missing-handlers.js +12 -4
- package/dist/commands/handlers/resources-handler.d.ts +12 -0
- package/dist/commands/handlers/resources-handler.js +52 -0
- package/dist/commands/handlers/starter-handlers.d.ts +1 -1
- package/dist/commands/handlers/starter-handlers.js +32 -2
- package/dist/commands/handlers/switch-handler.d.ts +1 -1
- package/dist/commands/handlers/switch-handler.js +15 -0
- package/dist/commands/handlers/test-handlers.js +4 -86
- package/dist/commands/handlers/ui-handlers.js +11 -5
- package/dist/commands/login-chatgpt.d.ts +8 -0
- package/dist/commands/login-chatgpt.js +17 -0
- package/dist/commands/mcp-import.d.ts +11 -0
- package/dist/commands/mcp-import.js +67 -0
- package/dist/commands/mcp-invoke.d.ts +2 -0
- package/dist/commands/mcp-invoke.js +68 -0
- package/dist/commands/mcp.d.ts +13 -0
- package/dist/commands/mcp.js +66 -21
- package/dist/commands/pipeline.d.ts +7 -1
- package/dist/commands/pipeline.js +104 -7
- package/dist/commands/resources.d.ts +2 -0
- package/dist/commands/resources.js +64 -0
- package/dist/commands/skills-cli/index.js +35 -12
- package/dist/commands/slash/builtin-commands.js +16 -3
- package/dist/commands/slash/index.d.ts +3 -1
- package/dist/commands/slash/index.js +2 -0
- package/dist/commands/slash/surfaces.d.ts +53 -0
- package/dist/commands/slash/surfaces.js +177 -0
- package/dist/commands/slash/types.d.ts +17 -0
- package/dist/commands/try.d.ts +1 -1
- package/dist/commands/try.js +1 -1
- package/dist/companion/companion-identity.d.ts +43 -0
- package/dist/companion/companion-identity.js +136 -0
- package/dist/companion/companion-toolset.d.ts +66 -0
- package/dist/companion/companion-toolset.js +368 -0
- package/dist/companion/companion-turn.d.ts +10 -2
- package/dist/companion/companion-turn.js +38 -1
- package/dist/config/code-exec-policy.d.ts +25 -0
- package/dist/config/code-exec-policy.js +39 -0
- package/dist/config/model-tools.d.ts +9 -0
- package/dist/config/model-tools.js +103 -0
- package/dist/context/context-manager-v2.js +2 -2
- package/dist/context/lm-resizer-compressor.js +3 -2
- package/dist/context/lm-resizer-diagnostics.d.ts +9 -0
- package/dist/context/lm-resizer-diagnostics.js +30 -0
- package/dist/context/tool-output-masking.js +3 -4
- package/dist/curator/curator.d.ts +6 -0
- package/dist/curator/curator.js +53 -3
- package/dist/daemon/cron-agent-bridge.d.ts +6 -0
- package/dist/daemon/cron-agent-bridge.js +31 -4
- package/dist/doctor/index.d.ts +11 -2
- package/dist/doctor/index.js +84 -21
- package/dist/doctor/integrations.d.ts +197 -0
- package/dist/doctor/integrations.js +241 -0
- package/dist/doctor/triage.d.ts +108 -0
- package/dist/doctor/triage.js +304 -0
- package/dist/errors/crash-handler.js +2 -0
- package/dist/fleet/collaboration.d.ts +44 -0
- package/dist/fleet/collaboration.js +170 -0
- package/dist/fleet/fleet-listener.d.ts +1 -0
- package/dist/fleet/fleet-listener.js +87 -46
- package/dist/fleet/fleet-registry.d.ts +2 -0
- package/dist/fleet/resource-catalog.d.ts +245 -0
- package/dist/fleet/resource-catalog.js +211 -0
- package/dist/fleet/rooms/room-access.d.ts +138 -0
- package/dist/fleet/rooms/room-access.js +196 -0
- package/dist/fleet/rooms/room-client.d.ts +158 -0
- package/dist/fleet/rooms/room-client.js +685 -0
- package/dist/fleet/rooms/room-event.d.ts +139 -0
- package/dist/fleet/rooms/room-event.js +330 -0
- package/dist/fleet/rooms/room-filter.d.ts +48 -0
- package/dist/fleet/rooms/room-filter.js +152 -0
- package/dist/fleet/rooms/room-hub.d.ts +154 -0
- package/dist/fleet/rooms/room-hub.js +363 -0
- package/dist/fleet/rooms/room-identity.d.ts +26 -0
- package/dist/fleet/rooms/room-identity.js +82 -0
- package/dist/fleet/rooms/room-lock.d.ts +34 -0
- package/dist/fleet/rooms/room-lock.js +180 -0
- package/dist/fleet/rooms/room-observations.d.ts +7 -0
- package/dist/fleet/rooms/room-observations.js +61 -0
- package/dist/fleet/rooms/room-server.d.ts +28 -0
- package/dist/fleet/rooms/room-server.js +75 -0
- package/dist/fleet/rooms/room-store.d.ts +119 -0
- package/dist/fleet/rooms/room-store.js +448 -0
- package/dist/fleet/rooms/room-ws-bridge.d.ts +27 -0
- package/dist/fleet/rooms/room-ws-bridge.js +137 -0
- package/dist/fleet/sensory-mission-bridge.d.ts +42 -0
- package/dist/fleet/sensory-mission-bridge.js +136 -0
- package/dist/git/worktree-sessions.js +1 -0
- package/dist/harness/contract.d.ts +4 -4
- package/dist/harness/fleet-supervisor.d.ts +16 -0
- package/dist/harness/fleet-supervisor.js +64 -0
- package/dist/harness/index.d.ts +4 -0
- package/dist/harness/index.js +4 -0
- package/dist/harness/mission-runner.d.ts +3 -0
- package/dist/harness/mission-runner.js +53 -0
- package/dist/harness/mission-store.d.ts +93 -0
- package/dist/harness/mission-store.js +297 -0
- package/dist/harness/tool-harness.d.ts +56 -0
- package/dist/harness/tool-harness.js +153 -0
- package/dist/hooks/use-enhanced-input.js +48 -4
- package/dist/hooks/use-input-handler.d.ts +1 -0
- package/dist/hooks/use-input-handler.js +82 -22
- package/dist/hooks/use-input-history.js +1 -0
- package/dist/identity/lisa-introspection.js +5 -2
- package/dist/identity/operational-self-model.d.ts +2 -0
- package/dist/identity/operational-self-model.js +4 -0
- package/dist/index.js +52 -6
- package/dist/mcp/client.d.ts +11 -0
- package/dist/mcp/client.js +68 -3
- package/dist/mcp/config.d.ts +20 -0
- package/dist/mcp/config.js +121 -8
- package/dist/mcp/import-normalize.d.ts +12 -0
- package/dist/mcp/import-normalize.js +114 -0
- package/dist/mcp/mcp-oauth-constants.d.ts +8 -0
- package/dist/mcp/mcp-oauth-constants.js +9 -0
- package/dist/mcp/mcp-oauth-provider.d.ts +80 -0
- package/dist/mcp/mcp-oauth-provider.js +241 -0
- package/dist/mcp/mcp-oauth.d.ts +60 -1
- package/dist/mcp/mcp-oauth.js +241 -65
- package/dist/mcp/transports.d.ts +14 -5
- package/dist/mcp/transports.js +112 -101
- package/dist/mcp/types.d.ts +5 -0
- package/dist/media/comfyui-recipe-contract.d.ts +7 -7
- package/dist/memory/facts-memory.d.ts +11 -1
- package/dist/memory/facts-memory.js +47 -8
- package/dist/memory/memory-candidate-queue.d.ts +2 -2
- package/dist/memory/memory-candidate-queue.js +6 -6
- package/dist/memory/persistent-memory.d.ts +13 -8
- package/dist/memory/persistent-memory.js +77 -30
- package/dist/nodes/device-node.js +5 -9
- package/dist/observability/mobile-supervision-gateway-contract.js +20 -7
- package/dist/observability/run-event-writer.d.ts +21 -0
- package/dist/observability/run-event-writer.js +81 -0
- package/dist/observability/run-store.d.ts +5 -0
- package/dist/observability/run-store.js +18 -13
- package/dist/observability/run-trajectory.js +1 -1
- package/dist/optimization/prompt-cache.d.ts +19 -2
- package/dist/optimization/prompt-cache.js +41 -6
- package/dist/orchestration/orchestrator.d.ts +21 -0
- package/dist/orchestration/orchestrator.js +122 -15
- package/dist/persistence/session-content.d.ts +8 -0
- package/dist/persistence/session-content.js +76 -0
- package/dist/persistence/session-handoff.d.ts +64 -0
- package/dist/persistence/session-handoff.js +80 -0
- package/dist/persistence/session-history.d.ts +4 -0
- package/dist/persistence/session-history.js +38 -0
- package/dist/persistence/session-store.d.ts +9 -0
- package/dist/persistence/session-store.js +48 -20
- package/dist/personas/persona-manager.js +17 -1
- package/dist/plugins/code-explorer/CodeExplorerManager.d.ts +10 -4
- package/dist/plugins/code-explorer/CodeExplorerManager.js +69 -16
- package/dist/plugins/plugin-manager.js +10 -1
- package/dist/prompts/prompt-manager.js +11 -4
- package/dist/prompts/system-base.js +6 -2
- package/dist/protocols/a2a/codebuddy-executor.d.ts +11 -2
- package/dist/protocols/a2a/codebuddy-executor.js +9 -8
- package/dist/protocols/a2a/index.d.ts +1 -1
- package/dist/protocols/a2a/jsonrpc-v1.d.ts +41 -0
- package/dist/protocols/a2a/jsonrpc-v1.js +145 -0
- package/dist/protocols/a2a/peer-config.d.ts +20 -0
- package/dist/protocols/a2a/peer-config.js +55 -0
- package/dist/providers/codex-oauth.js +30 -6
- package/dist/providers/model-provider-compat.js +2 -1
- package/dist/sandbox/os-sandbox.d.ts +1 -1
- package/dist/scheduler/cron-scheduler.d.ts +15 -2
- package/dist/scheduler/cron-scheduler.js +35 -4
- package/dist/scheduler/job-notepad.d.ts +69 -0
- package/dist/scheduler/job-notepad.js +339 -0
- package/dist/security/audit-logger.d.ts +1 -1
- package/dist/security/compute-confinement.d.ts +4 -0
- package/dist/security/compute-confinement.js +89 -0
- package/dist/security/session-encryption.d.ts +4 -0
- package/dist/security/session-encryption.js +31 -0
- package/dist/sensory/voice-loop.d.ts +17 -0
- package/dist/sensory/voice-loop.js +72 -15
- package/dist/server/auth/device-session-context.d.ts +9 -0
- package/dist/server/auth/device-session-context.js +13 -0
- package/dist/server/auth/device-store.d.ts +89 -0
- package/dist/server/auth/device-store.js +261 -0
- package/dist/server/auth/device-token.d.ts +3 -0
- package/dist/server/auth/device-token.js +6 -0
- package/dist/server/auth/jwt.js +4 -0
- package/dist/server/index.d.ts +0 -10
- package/dist/server/index.js +28 -0
- package/dist/server/middleware/auth.js +3 -2
- package/dist/server/routes/a2a-jsonrpc.d.ts +9 -0
- package/dist/server/routes/a2a-jsonrpc.js +66 -0
- package/dist/server/routes/a2a-protocol.d.ts +12 -0
- package/dist/server/routes/a2a-protocol.js +1 -1
- package/dist/server/routes/device-auth.d.ts +4 -0
- package/dist/server/routes/device-auth.js +40 -0
- package/dist/server/types.d.ts +4 -0
- package/dist/server/websocket/handler.d.ts +13 -0
- package/dist/server/websocket/handler.js +102 -17
- package/dist/services/prompt-builder.d.ts +2 -0
- package/dist/services/prompt-builder.js +16 -6
- package/dist/services/runtime-settings-context.d.ts +81 -0
- package/dist/services/runtime-settings-context.js +79 -0
- package/dist/skills/executor.js +2 -0
- package/dist/skills/hub.js +16 -14
- package/dist/skills/local-inventory.d.ts +21 -0
- package/dist/skills/local-inventory.js +72 -0
- package/dist/skills/skill-loader.js +2 -2
- package/dist/skills/skill-usage-store.d.ts +49 -0
- package/dist/skills/skill-usage-store.js +121 -0
- package/dist/testing/ai-integration-tests.js +8 -5
- package/dist/themes/theme-manager.d.ts +12 -0
- package/dist/themes/theme-manager.js +29 -0
- package/dist/themes/theme-schema.d.ts +24 -24
- package/dist/tools/a2a-call-tool.d.ts +34 -0
- package/dist/tools/a2a-call-tool.js +75 -0
- package/dist/tools/bash/streaming-executor.d.ts +1 -0
- package/dist/tools/bash/streaming-executor.js +78 -90
- package/dist/tools/code-exec-preflight-runner.d.ts +41 -0
- package/dist/tools/code-exec-preflight-runner.js +254 -0
- package/dist/tools/code-exec-preflight.d.ts +44 -0
- package/dist/tools/code-exec-preflight.js +501 -0
- package/dist/tools/code-exec-tool.d.ts +48 -1
- package/dist/tools/code-exec-tool.js +191 -47
- package/dist/tools/comment-watcher.js +29 -12
- package/dist/tools/core-code-inspection.d.ts +56 -0
- package/dist/tools/core-code-inspection.js +86 -0
- package/dist/tools/create-skill-tool.d.ts +2 -3
- package/dist/tools/create-skill-tool.js +16 -75
- package/dist/tools/cronjob-tool.d.ts +1 -0
- package/dist/tools/cronjob-tool.js +1 -0
- package/dist/tools/env-doctor-tool.d.ts +2 -0
- package/dist/tools/env-doctor-tool.js +4 -3
- package/dist/tools/execute-code-rpc-invoker.d.ts +1 -1
- package/dist/tools/execute-code-rpc-invoker.js +35 -19
- package/dist/tools/execute-code-runner.d.ts +2 -0
- package/dist/tools/execute-code-runner.js +52 -17
- package/dist/tools/fleet-room-tool.d.ts +104 -0
- package/dist/tools/fleet-room-tool.js +124 -0
- package/dist/tools/integration-tool-hints.d.ts +4 -0
- package/dist/tools/integration-tool-hints.js +19 -0
- package/dist/tools/list-peers-tool.d.ts +3 -0
- package/dist/tools/list-peers-tool.js +11 -4
- package/dist/tools/media-generation-tool.d.ts +16 -1
- package/dist/tools/media-generation-tool.js +341 -9
- package/dist/tools/merge-conflict-tool.js +53 -25
- package/dist/tools/metadata.d.ts +1 -5
- package/dist/tools/metadata.js +30 -28
- package/dist/tools/peer-tool-invoke-tool.d.ts +61 -0
- package/dist/tools/peer-tool-invoke-tool.js +396 -0
- package/dist/tools/ragchat-tool.d.ts +39 -0
- package/dist/tools/ragchat-tool.js +105 -0
- package/dist/tools/registry/fleet-tools.d.ts +21 -3
- package/dist/tools/registry/fleet-tools.js +137 -4
- package/dist/tools/registry/index.d.ts +1 -1
- package/dist/tools/registry/index.js +2 -2
- package/dist/tools/registry/knowledge-tools.d.ts +2 -2
- package/dist/tools/registry/knowledge-tools.js +2 -2
- package/dist/tools/registry/lessons-tools.d.ts +7 -0
- package/dist/tools/registry/lessons-tools.js +43 -3
- package/dist/tools/registry/memory-tools.d.ts +1 -1
- package/dist/tools/registry/memory-tools.js +8 -8
- package/dist/tools/registry/research-tools.d.ts +1 -0
- package/dist/tools/registry/research-tools.js +4 -13
- package/dist/tools/registry/self-describe-tools.d.ts +1 -1
- package/dist/tools/registry/self-describe-tools.js +43 -30
- package/dist/tools/resource-catalog-tool.d.ts +38 -0
- package/dist/tools/resource-catalog-tool.js +46 -0
- package/dist/tools/skills-inspection-tool.js +30 -1
- package/dist/tools/stream-tool-output.d.ts +3 -0
- package/dist/tools/stream-tool-output.js +33 -0
- package/dist/tools/tool-call-scheduler.d.ts +10 -0
- package/dist/tools/tool-call-scheduler.js +58 -0
- package/dist/tools/tool-effect.d.ts +5 -0
- package/dist/tools/tool-effect.js +26 -0
- package/dist/tools/tool-search.d.ts +12 -11
- package/dist/tools/tool-search.js +67 -26
- package/dist/tools/types.d.ts +6 -0
- package/dist/ui/components/ChatHistory.d.ts +1 -1
- package/dist/ui/components/ChatHistory.js +2 -2
- package/dist/ui/components/ChatInput.d.ts +2 -1
- package/dist/ui/components/ChatInput.js +42 -88
- package/dist/ui/components/ChatInterface.d.ts +4 -2
- package/dist/ui/components/ChatInterface.js +27 -63
- package/dist/ui/components/CommandSuggestions.d.ts +0 -1
- package/dist/ui/components/CommandSuggestions.js +4 -6
- package/dist/ui/components/FileAutocomplete.js +1 -1
- package/dist/ui/components/KeyboardHelp.js +8 -6
- package/dist/ui/components/ModelSelection.js +6 -5
- package/dist/ui/components/StatusBar.d.ts +2 -1
- package/dist/ui/components/StatusBar.js +8 -7
- package/dist/ui/context/theme-context.js +3 -2
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +1 -1
- package/dist/ui/utils/markdown-renderer.js +4 -13
- package/dist/utils/atomic-write.d.ts +22 -0
- package/dist/utils/atomic-write.js +29 -0
- package/dist/utils/bounded-output.d.ts +16 -0
- package/dist/utils/bounded-output.js +63 -0
- package/dist/utils/config-validation/schema.d.ts +2 -2
- package/dist/utils/device-store-file.d.ts +4 -0
- package/dist/utils/device-store-file.js +40 -0
- package/dist/utils/export-manager.d.ts +9 -0
- package/dist/utils/export-manager.js +19 -0
- package/dist/utils/first-use-hints.d.ts +25 -0
- package/dist/utils/first-use-hints.js +58 -0
- package/dist/utils/graceful-shutdown.d.ts +8 -0
- package/dist/utils/graceful-shutdown.js +23 -7
- package/dist/utils/history-manager.js +1 -1
- package/dist/utils/update-notifier.js +16 -18
- package/dist/wizard/environment-detection.d.ts +5 -1
- package/dist/wizard/environment-detection.js +49 -7
- package/dist/wizard/onboarding.d.ts +5 -1
- package/dist/wizard/onboarding.js +90 -30
- package/dist/wizard/provider-onboarding.d.ts +2 -0
- package/dist/wizard/provider-onboarding.js +55 -17
- package/package.json +7 -3
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { bindFactsMemorySession } from '../../memory/facts-memory.js';
|
|
1
2
|
/**
|
|
2
3
|
* Agent Executor Module
|
|
3
4
|
*
|
|
@@ -7,10 +8,12 @@
|
|
|
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";
|
|
13
15
|
import { normalizeHallucinatedLocalToolCall } from "../tool-handler.js";
|
|
16
|
+
import { mergeAlwaysInclude } from "./tool-selection-strategy.js";
|
|
14
17
|
import { ContextCompactionError } from "../../context/context-manager-v2.js";
|
|
15
18
|
import { logger } from "../../utils/logger.js";
|
|
16
19
|
import { getErrorMessage } from "../../errors/index.js";
|
|
@@ -27,6 +30,9 @@ import { compress as tokenJuice, isTokenJuiceEnabled, JUICE_MIN_CHARS } from "..
|
|
|
27
30
|
import { formatToolResultForRecovery, getRestorableCompressor, } from "../../context/restorable-compression.js";
|
|
28
31
|
import { recordCompactionFork } from "../../context/compaction-fork.js";
|
|
29
32
|
import { getActiveRunStore } from "../../observability/run-store.js";
|
|
33
|
+
import { ToolLoopGuard } from "./tool-loop-guard.js";
|
|
34
|
+
import { getGlobalEventBus } from "../../events/event-bus.js";
|
|
35
|
+
import { takeFirstUseHint } from "../../utils/first-use-hints.js";
|
|
30
36
|
import { getTurnMetricsRecorder } from '../../observability/turn-metrics.js';
|
|
31
37
|
import { shouldCompactBeforeToolExec, estimateToolResultTokens } from "../../context/proactive-compaction.js";
|
|
32
38
|
import { formatTokenUsage, estimateCost } from "../../utils/token-display.js";
|
|
@@ -35,7 +41,7 @@ import { getModelToolConfig } from "../../config/model-tools.js";
|
|
|
35
41
|
import { getLatencyOptimizer, getStreamingOptimizer } from "../../optimization/latency-optimizer.js";
|
|
36
42
|
import { buildTextEmotionalPresenceContext } from "../../companion/reply-augment.js";
|
|
37
43
|
import { guardRelationshipReply, SAFE_RELATIONSHIP_REPAIR, } from "../../conversation/relationship-safety.js";
|
|
38
|
-
import { classifyLisaIntrospection, guardLisaOperationalSelfInspectionReply, isLisaEvolutionRequest, renderLisaOperationalSelfResponse, } from '../../identity/lisa-introspection.js';
|
|
44
|
+
import { classifyLisaIntrospection, guardLisaOperationalSelfInspectionReply, isLisaEvolutionRequest, isLisaSubjectiveConsciousnessQuestion, renderLisaOperationalSelfResponse, } from '../../identity/lisa-introspection.js';
|
|
39
45
|
import { withLlmStreamRetry } from '../../codebuddy/llm-retry.js';
|
|
40
46
|
import { getStreamingAdapter } from '../../tools/streaming-adapter.js';
|
|
41
47
|
import { notify } from '../proactive/notification-default-sink.js';
|
|
@@ -467,16 +473,28 @@ export class AgentExecutor {
|
|
|
467
473
|
/** Execute one tool and buffer optional streaming-adapter output. */
|
|
468
474
|
async executeToolForBatch(toolCall, executionExtra, signal, startedAt = Date.now()) {
|
|
469
475
|
const streamChunks = [];
|
|
476
|
+
const streamPreview = new BoundedOutput(256 * 1024);
|
|
477
|
+
let collapsed = false;
|
|
478
|
+
const appendStreamChunk = (chunk) => {
|
|
479
|
+
if (!chunk || signal?.aborted)
|
|
480
|
+
return;
|
|
481
|
+
streamPreview.append(chunk);
|
|
482
|
+
collapsed ||= streamPreview.omittedBytes > 0 || streamChunks.length >= 1024;
|
|
483
|
+
if (collapsed)
|
|
484
|
+
streamChunks.splice(0, streamChunks.length, streamPreview.text());
|
|
485
|
+
else
|
|
486
|
+
streamChunks.push(chunk);
|
|
487
|
+
};
|
|
470
488
|
const extraWithSignal = signal
|
|
471
489
|
? { ...(executionExtra ?? {}), abortSignal: signal }
|
|
472
490
|
: executionExtra;
|
|
473
491
|
const execute = async () => {
|
|
474
|
-
const streamingTools = new Set(['bash', 'reason', 'generate_document']);
|
|
492
|
+
const streamingTools = new Set(['bash', 'reason', 'generate_document', 'code_exec']);
|
|
475
493
|
if (streamingTools.has(toolCall.function.name)) {
|
|
476
494
|
const generator = this.deps.toolHandler.executeToolStreaming(toolCall, extraWithSignal);
|
|
477
495
|
let generated = await generator.next();
|
|
478
496
|
while (!generated.done) {
|
|
479
|
-
|
|
497
|
+
appendStreamChunk(generated.value);
|
|
480
498
|
generated = await generator.next();
|
|
481
499
|
}
|
|
482
500
|
return {
|
|
@@ -486,7 +504,7 @@ export class AgentExecutor {
|
|
|
486
504
|
}
|
|
487
505
|
const streamingAdapter = getStreamingAdapter();
|
|
488
506
|
if (streamingAdapter.supportsStreaming(toolCall.function.name)) {
|
|
489
|
-
const result = await streamingAdapter.wrapWithStreaming(toolCall.function.name, () => this.executeToolViaLane(toolCall, extraWithSignal),
|
|
507
|
+
const result = await streamingAdapter.wrapWithStreaming(toolCall.function.name, () => this.executeToolViaLane(toolCall, extraWithSignal), appendStreamChunk);
|
|
490
508
|
return { result, streamChunks };
|
|
491
509
|
}
|
|
492
510
|
return {
|
|
@@ -740,7 +758,7 @@ export class AgentExecutor {
|
|
|
740
758
|
const operationId = getLatencyOptimizer().startOperation('assistant_turn', startedAt);
|
|
741
759
|
let recordedFirstVisibleResponse = false;
|
|
742
760
|
try {
|
|
743
|
-
for await (const event of this.runTurnLoop(message, history, messages, abortController, transientContext, relationshipSafety, surface, introspectionText)) {
|
|
761
|
+
for await (const event of bindFactsMemorySession(this.deps.client, this.runTurnLoop(message, history, messages, abortController, transientContext, relationshipSafety, surface, introspectionText))) {
|
|
744
762
|
if (!recordedFirstVisibleResponse &&
|
|
745
763
|
(event.type === 'content' || event.type === 'reasoning' || event.type === 'tool_calls')) {
|
|
746
764
|
recordedFirstVisibleResponse = true;
|
|
@@ -783,7 +801,9 @@ export class AgentExecutor {
|
|
|
783
801
|
const introspectionIntent = classifyLisaIntrospection(introspectionTextForTurn);
|
|
784
802
|
const readOnlySelfInspection = introspectionIntent === 'describe' || introspectionIntent === 'inspect';
|
|
785
803
|
const guardGenerativeSelfInspection = introspectionIntent === 'improve';
|
|
786
|
-
const
|
|
804
|
+
const codeResearch = surface === 'cli' && introspectionIntent === 'inspect' &&
|
|
805
|
+
!isLisaSubjectiveConsciousnessQuestion(introspectionTextForTurn);
|
|
806
|
+
const isolatedSharedHost = surface === 'http' || codeResearch;
|
|
787
807
|
const turnCwd = typeof this.deps.toolHandler.getWorkingDirectory === 'function'
|
|
788
808
|
? this.deps.toolHandler.getWorkingDirectory()
|
|
789
809
|
: process.cwd();
|
|
@@ -806,8 +826,9 @@ export class AgentExecutor {
|
|
|
806
826
|
let permissionMode;
|
|
807
827
|
let providerName;
|
|
808
828
|
let operationalRobotName;
|
|
809
|
-
if (introspectionIntent !== null) {
|
|
810
|
-
|
|
829
|
+
if (introspectionIntent !== null || surface === 'cli') {
|
|
830
|
+
if (introspectionIntent !== null)
|
|
831
|
+
operationalRobotName = await this.getOperationalRobotName();
|
|
811
832
|
try {
|
|
812
833
|
const { getPermissionModeManager } = await import('../../security/permission-modes.js');
|
|
813
834
|
permissionMode = getPermissionModeManager().getMode();
|
|
@@ -822,7 +843,7 @@ export class AgentExecutor {
|
|
|
822
843
|
// Legacy/test clients may not expose provider metadata.
|
|
823
844
|
}
|
|
824
845
|
}
|
|
825
|
-
if (readOnlySelfInspection) {
|
|
846
|
+
if (readOnlySelfInspection && !codeResearch) {
|
|
826
847
|
if (isLisaEvolutionRequest(introspectionTextForTurn)) {
|
|
827
848
|
const { formatEvolutionNotesForVoice, queryEvolutionNotes, readEvolutionNotes, } = await import('../../self-model/evolution-notes.js');
|
|
828
849
|
const since = introspectionTextForTurn.match(/\b20\d{2}-\d{2}-\d{2}\b/)?.[0];
|
|
@@ -850,6 +871,8 @@ export class AgentExecutor {
|
|
|
850
871
|
// every strict describe/inspect turn. No provider, plugin or generic
|
|
851
872
|
// workspace tool participates in this path.
|
|
852
873
|
const { buildOperationalSelfModel } = await import('../../identity/operational-self-model.js');
|
|
874
|
+
const { getRuntimeSettingsSnapshot } = await import('../../services/runtime-settings-context.js');
|
|
875
|
+
const activeSettings = getRuntimeSettingsSnapshot({ surface, model: this.deps.client.getCurrentModel(), provider: providerName, maxToolRounds: this.config.maxToolRounds });
|
|
853
876
|
const selfModel = buildOperationalSelfModel({
|
|
854
877
|
cwd: turnCwd,
|
|
855
878
|
focus: introspectionTextForTurn,
|
|
@@ -859,6 +882,7 @@ export class AgentExecutor {
|
|
|
859
882
|
: {}),
|
|
860
883
|
runtime: {
|
|
861
884
|
providerInvoked: false,
|
|
885
|
+
...(activeSettings.theme ? { theme: activeSettings.theme.active } : {}),
|
|
862
886
|
...(this.deps.client.getCurrentModel()
|
|
863
887
|
? { model: this.deps.client.getCurrentModel() }
|
|
864
888
|
: {}),
|
|
@@ -900,6 +924,11 @@ export class AgentExecutor {
|
|
|
900
924
|
};
|
|
901
925
|
const maxToolRounds = this.config.maxToolRounds;
|
|
902
926
|
let toolRounds = 0;
|
|
927
|
+
// One guard per task: warnings/stops never leak into the next user turn.
|
|
928
|
+
const loopGuard = new ToolLoopGuard();
|
|
929
|
+
let pendingLoopDecision = null;
|
|
930
|
+
let loopGuardStopped = false;
|
|
931
|
+
let observationShortened = false;
|
|
903
932
|
let totalOutputTokens = 0;
|
|
904
933
|
let totalInputTokensForCost = 0;
|
|
905
934
|
let providerPromptTokens = 0;
|
|
@@ -1042,6 +1071,25 @@ export class AgentExecutor {
|
|
|
1042
1071
|
alwaysInclude: ['view_file', 'bash', 'search'],
|
|
1043
1072
|
};
|
|
1044
1073
|
}
|
|
1074
|
+
if (!codeResearch) {
|
|
1075
|
+
const { runtimeInspectionTools, connectedFleetSurfaceTools } = await import('../../services/runtime-settings-context.js');
|
|
1076
|
+
const inspectionTools = [...new Set([
|
|
1077
|
+
...(surface === 'cli' ? runtimeInspectionTools(turnQueryText) : []),
|
|
1078
|
+
...connectedFleetSurfaceTools(),
|
|
1079
|
+
])];
|
|
1080
|
+
if (inspectionTools.length) {
|
|
1081
|
+
// Fleet tools first (Qwen3.6 GGUF otherwise prefers bash and
|
|
1082
|
+
// shells list_peers) but they are ADDED to the guaranteed
|
|
1083
|
+
// default list, never substituted for it.
|
|
1084
|
+
const alwaysInclude = mergeAlwaysInclude(selectionOpts.alwaysInclude, inspectionTools);
|
|
1085
|
+
if (alwaysInclude) {
|
|
1086
|
+
selectionOpts = { ...selectionOpts, alwaysInclude };
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
if (codeResearch) {
|
|
1091
|
+
selectionOpts = { ...selectionOpts, alwaysInclude: ['self_describe'] };
|
|
1092
|
+
}
|
|
1045
1093
|
const selectionPromise = this.deps.toolSelectionStrategy.selectToolsForQuery(turnQueryText, selectionOpts);
|
|
1046
1094
|
// Build context in a scratch array while the prompt and tools are being
|
|
1047
1095
|
// prepared. It is appended only after transcript preparation so the
|
|
@@ -1094,7 +1142,9 @@ export class AgentExecutor {
|
|
|
1094
1142
|
incrementalTokenCounter.invalidate();
|
|
1095
1143
|
logger.debug(`[agent-executor] system prompt rebuilt query-aware (${rebuiltSystemPrompt.length} chars)`);
|
|
1096
1144
|
}
|
|
1097
|
-
let tools =
|
|
1145
|
+
let tools = codeResearch
|
|
1146
|
+
? selectionResult.tools.filter(tool => tool.function.name === 'self_describe')
|
|
1147
|
+
: selectionResult.tools;
|
|
1098
1148
|
let forcedChatOnlyToolRunModel = null;
|
|
1099
1149
|
if (toolRounds === 0) {
|
|
1100
1150
|
this.deps.toolSelectionStrategy.cacheTools(tools, activeModelName);
|
|
@@ -1109,7 +1159,7 @@ export class AgentExecutor {
|
|
|
1109
1159
|
tools = [];
|
|
1110
1160
|
}
|
|
1111
1161
|
}
|
|
1112
|
-
const turnExecutionExtra = introspectionIntent
|
|
1162
|
+
const turnExecutionExtra = introspectionIntent || surface === 'cli'
|
|
1113
1163
|
? {
|
|
1114
1164
|
...(activeModelName ? { model: activeModelName } : {}),
|
|
1115
1165
|
...(providerName ? { provider: providerName } : {}),
|
|
@@ -1118,6 +1168,7 @@ export class AgentExecutor {
|
|
|
1118
1168
|
...(operationalRobotName
|
|
1119
1169
|
? { robotName: operationalRobotName }
|
|
1120
1170
|
: {}),
|
|
1171
|
+
maxToolRounds,
|
|
1121
1172
|
exposedToolNames: tools.map((tool) => tool.function.name),
|
|
1122
1173
|
introspectionIntent,
|
|
1123
1174
|
}
|
|
@@ -1142,6 +1193,27 @@ export class AgentExecutor {
|
|
|
1142
1193
|
throw error;
|
|
1143
1194
|
}
|
|
1144
1195
|
preparedMessages.push(...contextBlocks);
|
|
1196
|
+
if (codeResearch) {
|
|
1197
|
+
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.' });
|
|
1198
|
+
}
|
|
1199
|
+
if (surface === 'cli') {
|
|
1200
|
+
const { formatRuntimeSettingsContext } = await import('../../services/runtime-settings-context.js');
|
|
1201
|
+
preparedMessages.push({ role: 'system', content: formatRuntimeSettingsContext({
|
|
1202
|
+
surface, model: activeModelName, provider: providerName, maxToolRounds,
|
|
1203
|
+
}) });
|
|
1204
|
+
}
|
|
1205
|
+
else {
|
|
1206
|
+
// P5: other surfaces have no runtime_settings block; only a non-default
|
|
1207
|
+
// code_exec policy needs its short guidance.
|
|
1208
|
+
const { resolveCodeExecPolicy, CODE_EXEC_PREFER_HINT, CODE_EXEC_OFF_NOTICE } = await import('../../config/code-exec-policy.js');
|
|
1209
|
+
const codeExecPolicy = resolveCodeExecPolicy(activeModelName ?? undefined);
|
|
1210
|
+
if (codeExecPolicy.policy !== 'offer') {
|
|
1211
|
+
preparedMessages.push({
|
|
1212
|
+
role: 'system',
|
|
1213
|
+
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>`,
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1145
1217
|
if (emotionalPresenceContext) {
|
|
1146
1218
|
preparedMessages.push({
|
|
1147
1219
|
role: 'system',
|
|
@@ -1190,6 +1262,9 @@ export class AgentExecutor {
|
|
|
1190
1262
|
const progress = startHeadlessPromptProgress();
|
|
1191
1263
|
const streamFactory = () => withStallGuard(this.deps.client.chatStream(preparedMessages, tools, {
|
|
1192
1264
|
streamRetry: false,
|
|
1265
|
+
// An explicit request to inspect implementation needs an observation
|
|
1266
|
+
// before an answer. Only the confined self_describe reader is exposed.
|
|
1267
|
+
...(codeResearch && toolRounds === 0 && tools.length ? { tool_choice: 'required' } : {}),
|
|
1193
1268
|
turnMetrics: {
|
|
1194
1269
|
recorder: getTurnMetricsRecorder(),
|
|
1195
1270
|
inputTokens,
|
|
@@ -1297,6 +1372,11 @@ export class AgentExecutor {
|
|
|
1297
1372
|
}
|
|
1298
1373
|
toolCalls = filteredToolCalls.length > 0 ? filteredToolCalls : undefined;
|
|
1299
1374
|
}
|
|
1375
|
+
if (codeResearch && toolCalls?.some(call => call.function.name !== 'self_describe')) {
|
|
1376
|
+
yield { type: 'content', content: 'Code inspection is read-only; the requested tool is not available in this inspection turn.' };
|
|
1377
|
+
yield { type: 'done' };
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1300
1380
|
if (relationshipSafety && Array.isArray(toolCalls)) {
|
|
1301
1381
|
toolCalls = toolCalls.map(prepareRelationshipSafeInteractiveToolCall);
|
|
1302
1382
|
}
|
|
@@ -1471,8 +1551,15 @@ export class AgentExecutor {
|
|
|
1471
1551
|
estimatedTokens,
|
|
1472
1552
|
contextWindow,
|
|
1473
1553
|
});
|
|
1554
|
+
// The results of this round's calls are pushed after execution:
|
|
1555
|
+
// name them so repair does not close them with a synthetic
|
|
1556
|
+
// '[result lost during compaction]' that would then win over
|
|
1557
|
+
// the real output.
|
|
1474
1558
|
const compacted = compactTurnMessagesInPlace(this.deps.contextManager, messages, {
|
|
1475
1559
|
isolatedSharedHost,
|
|
1560
|
+
pendingToolCallIds: toolCalls
|
|
1561
|
+
.map((call) => call.id)
|
|
1562
|
+
.filter((id) => typeof id === 'string' && id.length > 0),
|
|
1476
1563
|
});
|
|
1477
1564
|
if (compacted)
|
|
1478
1565
|
incrementalTokenCounter.invalidate();
|
|
@@ -1565,6 +1652,18 @@ export class AgentExecutor {
|
|
|
1565
1652
|
};
|
|
1566
1653
|
}
|
|
1567
1654
|
}
|
|
1655
|
+
// --- Tool loop guard (P1): observe the native result before any
|
|
1656
|
+
// optimizer so "same call + same result" means no progress. The
|
|
1657
|
+
// decision is applied at the batch boundary so tool_call/tool_result
|
|
1658
|
+
// pairs are never split.
|
|
1659
|
+
const loopDecision = loopGuard.observe({
|
|
1660
|
+
name: toolCall.function.name,
|
|
1661
|
+
argumentsJson: toolCall.function.arguments || '{}',
|
|
1662
|
+
result,
|
|
1663
|
+
});
|
|
1664
|
+
if (loopDecision.action === 'stop' || (loopDecision.action === 'warn' && !pendingLoopDecision)) {
|
|
1665
|
+
pendingLoopDecision = loopDecision;
|
|
1666
|
+
}
|
|
1568
1667
|
// Expand the current turn's cached schema after discovery or live
|
|
1569
1668
|
// authoring. Without this, a newly created tool is dispatchable but
|
|
1570
1669
|
// invisible to the model until the next user turn.
|
|
@@ -1705,6 +1804,8 @@ export class AgentExecutor {
|
|
|
1705
1804
|
signal: abortController?.signal,
|
|
1706
1805
|
});
|
|
1707
1806
|
let modelStreamContent = optimization.content;
|
|
1807
|
+
if (optimization.optimized)
|
|
1808
|
+
observationShortened = true;
|
|
1708
1809
|
// lm-resizer owns the semantic budget when available. Its absence or
|
|
1709
1810
|
// an intentionally raw failure still receives a model-aware hard cap;
|
|
1710
1811
|
// the exact observation remains available through restore_context.
|
|
@@ -1715,6 +1816,7 @@ export class AgentExecutor {
|
|
|
1715
1816
|
this.deps.tokenCounter.countTokens(modelStreamContent) > budgetTokens) {
|
|
1716
1817
|
const truncated = semanticTruncate(modelStreamContent, { maxChars: hardLimitChars });
|
|
1717
1818
|
if (truncated.truncated) {
|
|
1819
|
+
observationShortened = true;
|
|
1718
1820
|
const recoveryNote = toolCall.id
|
|
1719
1821
|
? `\n\n[Full exact observation: restore_context({"identifier":${JSON.stringify(toolCall.id)}})]`
|
|
1720
1822
|
: '';
|
|
@@ -1850,6 +1952,68 @@ export class AgentExecutor {
|
|
|
1850
1952
|
yield { type: "done" };
|
|
1851
1953
|
return;
|
|
1852
1954
|
}
|
|
1955
|
+
// First shortened observation in this profile: one user-facing tip (P4 first-use hints).
|
|
1956
|
+
if (observationShortened) {
|
|
1957
|
+
observationShortened = false;
|
|
1958
|
+
const tip = takeFirstUseHint('restore_context');
|
|
1959
|
+
if (tip)
|
|
1960
|
+
yield { type: "content", content: `\n💡 ${tip}\n` };
|
|
1961
|
+
}
|
|
1962
|
+
// Tool-call/result pairs are complete here: apply the loop guard.
|
|
1963
|
+
if (pendingLoopDecision) {
|
|
1964
|
+
const decision = pendingLoopDecision;
|
|
1965
|
+
pendingLoopDecision = null;
|
|
1966
|
+
const loopData = {
|
|
1967
|
+
action: decision.action,
|
|
1968
|
+
loopType: decision.kind,
|
|
1969
|
+
toolNames: decision.toolNames,
|
|
1970
|
+
repetitions: decision.repetitions,
|
|
1971
|
+
toolRounds,
|
|
1972
|
+
};
|
|
1973
|
+
try {
|
|
1974
|
+
getGlobalEventBus().emit('agent:loop_detected', {
|
|
1975
|
+
loopType: decision.kind,
|
|
1976
|
+
detail: decision.message,
|
|
1977
|
+
count: decision.repetitions,
|
|
1978
|
+
turnIndex: toolRounds,
|
|
1979
|
+
});
|
|
1980
|
+
}
|
|
1981
|
+
catch (err) {
|
|
1982
|
+
logger.debug('[loop-guard] event emission failed', { error: String(err) });
|
|
1983
|
+
}
|
|
1984
|
+
if (decision.action === 'warn') {
|
|
1985
|
+
logger.warn('[loop-guard] loop warning injected', loopData);
|
|
1986
|
+
yield { type: "content", content: `\n⚠️ ${decision.message}\n` };
|
|
1987
|
+
messages.push({
|
|
1988
|
+
role: 'system',
|
|
1989
|
+
content: `<context type="loop-guard">\n${decision.message}\n</context>`,
|
|
1990
|
+
});
|
|
1991
|
+
}
|
|
1992
|
+
else {
|
|
1993
|
+
logger.warn('[loop-guard] turn stopped', loopData);
|
|
1994
|
+
const runId = typeof this.deps.toolHandler.getRunId === 'function'
|
|
1995
|
+
? this.deps.toolHandler.getRunId()
|
|
1996
|
+
: undefined;
|
|
1997
|
+
if (runId) {
|
|
1998
|
+
try {
|
|
1999
|
+
getActiveRunStore()?.emit(runId, {
|
|
2000
|
+
type: 'decision',
|
|
2001
|
+
data: { kind: 'loop_guard_stopped', ...loopData },
|
|
2002
|
+
});
|
|
2003
|
+
}
|
|
2004
|
+
catch { /* observability is optional */ }
|
|
2005
|
+
}
|
|
2006
|
+
yield {
|
|
2007
|
+
type: 'run_event',
|
|
2008
|
+
runEvent: { runId: runId ?? '', eventType: 'loop_guard_stopped', data: loopData },
|
|
2009
|
+
};
|
|
2010
|
+
history.push({ type: 'assistant', content: decision.message, timestamp: new Date() });
|
|
2011
|
+
messages.push({ role: 'assistant', content: decision.message });
|
|
2012
|
+
yield { type: "content", content: `\n\n${decision.message}` };
|
|
2013
|
+
loopGuardStopped = true;
|
|
2014
|
+
break;
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
1853
2017
|
// Tool-call/result pairs are complete at this boundary, so a steer
|
|
1854
2018
|
// that arrived while tools were running can now be injected safely.
|
|
1855
2019
|
const deferredSteering = this.deps.messageQueue?.hasSteeringMessage()
|
|
@@ -1956,7 +2120,7 @@ export class AgentExecutor {
|
|
|
1956
2120
|
break;
|
|
1957
2121
|
}
|
|
1958
2122
|
}
|
|
1959
|
-
if (toolRounds >= maxToolRounds && !terminateDetectedStreaming) {
|
|
2123
|
+
if (toolRounds >= maxToolRounds && !terminateDetectedStreaming && !loopGuardStopped) {
|
|
1960
2124
|
const limitMessage = 'Maximum tool execution rounds reached.';
|
|
1961
2125
|
history.push({ type: 'assistant', content: limitMessage, timestamp: new Date() });
|
|
1962
2126
|
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
|
+
}
|