@phuetz/code-buddy 1.1.0 → 1.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.md +160 -165
- package/dist/agent/autonomous/agentic-coding-contract.d.ts +6 -6
- package/dist/agent/codebuddy-agent.d.ts +1 -1
- package/dist/agent/codebuddy-agent.js +69 -14
- package/dist/agent/execution/agent-executor.js +122 -12
- package/dist/agent/execution/tool-dependency-graph.js +7 -0
- package/dist/agent/execution/tool-hooks.d.ts +6 -0
- package/dist/agent/execution/tool-hooks.js +13 -5
- package/dist/agent/execution/tool-selection-strategy.d.ts +11 -2
- package/dist/agent/execution/tool-selection-strategy.js +67 -10
- package/dist/agent/extended-thinking.d.ts +8 -0
- package/dist/agent/extended-thinking.js +27 -0
- package/dist/agent/hermes-browser-backends.js +151 -11
- package/dist/agent/hermes-claw-migrate.d.ts +59 -0
- package/dist/agent/hermes-claw-migrate.js +427 -26
- package/dist/agent/hermes-parity-manifest.js +78 -71
- package/dist/agent/hermes-runtime-lifecycle.d.ts +67 -0
- package/dist/agent/hermes-runtime-lifecycle.js +468 -0
- package/dist/agent/middleware/index.d.ts +1 -0
- package/dist/agent/middleware/index.js +1 -0
- package/dist/agent/middleware/visual-validation-middleware.d.ts +17 -0
- package/dist/agent/middleware/visual-validation-middleware.js +67 -0
- package/dist/agent/model-benchmark.d.ts +77 -0
- package/dist/agent/model-benchmark.js +309 -0
- package/dist/agent/model-tier.d.ts +14 -0
- package/dist/agent/model-tier.js +71 -0
- package/dist/agent/multi-agent/agents/orchestrator-agent.js +17 -6
- package/dist/agent/multi-agent/multi-agent-system.js +3 -1
- package/dist/agent/multi-agent/session-fleet-bridge.d.ts +2 -0
- package/dist/agent/multi-agent/session-fleet-bridge.js +9 -1
- package/dist/agent/multi-agent/session-tools.js +22 -5
- package/dist/agent/reasoning/mcts.js +20 -6
- package/dist/agent/session-end-flush.d.ts +6 -1
- package/dist/agent/session-end-flush.js +20 -4
- package/dist/agent/streaming/streaming-handler.d.ts +7 -0
- package/dist/agent/streaming/streaming-handler.js +8 -1
- package/dist/agent/tool-executor.js +1 -0
- package/dist/agent/tool-handler.d.ts +5 -0
- package/dist/agent/tool-handler.js +130 -4
- package/dist/browser-automation/browser-operator-executor.js +15 -0
- package/dist/browser-automation/browser-use-runner.d.ts +96 -0
- package/dist/browser-automation/browser-use-runner.js +492 -0
- package/dist/browser-automation/camofox-runner.d.ts +107 -0
- package/dist/browser-automation/camofox-runner.js +287 -0
- package/dist/channels/dingtalk/index.js +3 -0
- package/dist/channels/discord/client.d.ts +9 -0
- package/dist/channels/discord/client.js +12 -0
- package/dist/channels/feishu/index.d.ts +167 -1
- package/dist/channels/feishu/index.js +432 -7
- package/dist/channels/gateway-lifecycle.d.ts +132 -0
- package/dist/channels/gateway-lifecycle.js +219 -0
- package/dist/channels/google-chat/index.d.ts +5 -1
- package/dist/channels/google-chat/index.js +5 -1
- package/dist/channels/imessage/index.d.ts +14 -0
- package/dist/channels/imessage/index.js +64 -17
- package/dist/channels/index.d.ts +4 -0
- package/dist/channels/index.js +4 -0
- package/dist/channels/irc/index.d.ts +73 -3
- package/dist/channels/irc/index.js +446 -11
- package/dist/channels/line/index.js +3 -0
- package/dist/channels/mattermost/index.d.ts +51 -4
- package/dist/channels/mattermost/index.js +303 -20
- package/dist/channels/nextcloud-talk/index.d.ts +89 -8
- package/dist/channels/nextcloud-talk/index.js +367 -16
- package/dist/channels/nostr/index.d.ts +121 -1
- package/dist/channels/nostr/index.js +396 -8
- package/dist/channels/ntfy/index.js +3 -0
- package/dist/channels/qq/index.js +3 -0
- package/dist/channels/slash-parity.d.ts +117 -0
- package/dist/channels/slash-parity.js +185 -0
- package/dist/channels/synology-chat/index.js +3 -0
- package/dist/channels/teams/index.d.ts +3 -0
- package/dist/channels/teams/index.js +3 -0
- package/dist/channels/telegram/client.d.ts +9 -0
- package/dist/channels/telegram/client.js +12 -0
- package/dist/channels/twilio-voice/index.js +3 -0
- package/dist/channels/webchat/index.d.ts +7 -1
- package/dist/channels/webchat/index.js +7 -1
- package/dist/channels/wecom/index.js +3 -0
- package/dist/channels/weixin/index.js +3 -0
- package/dist/channels/zalo/index.js +3 -0
- package/dist/cli/config-loader.js +1 -1
- package/dist/codebuddy/client.d.ts +30 -1
- package/dist/codebuddy/client.js +167 -20
- package/dist/codebuddy/providers/provider-chatgpt-responses.d.ts +5 -0
- package/dist/codebuddy/providers/provider-chatgpt-responses.js +1 -0
- package/dist/codebuddy/providers/provider-gemini-native.js +1 -1
- package/dist/codebuddy/providers/provider-openai-compat.d.ts +5 -0
- package/dist/codebuddy/providers/provider-openai-compat.js +82 -2
- package/dist/codebuddy/tool-definitions/computer-control-tools.js +32 -2
- package/dist/codebuddy/tool-definitions/core-tools.js +3 -3
- package/dist/codebuddy/tool-definitions/index.d.ts +2 -1
- package/dist/codebuddy/tool-definitions/index.js +2 -1
- package/dist/codebuddy/tool-definitions/multimodal-tools.d.ts +1 -0
- package/dist/codebuddy/tool-definitions/multimodal-tools.js +44 -1
- package/dist/codebuddy/tool-definitions/windows-tools.d.ts +2 -0
- package/dist/codebuddy/tool-definitions/windows-tools.js +34 -0
- package/dist/codebuddy/tools.d.ts +1 -1
- package/dist/codebuddy/tools.js +27 -24
- package/dist/commands/cli/hermes-commands.js +108 -0
- package/dist/commands/cli/native-engine-commands.js +102 -3
- package/dist/commands/cli/utility-commands.js +45 -0
- package/dist/commands/enhanced-command-handler.js +1 -1
- package/dist/commands/goal-cli.d.ts +30 -0
- package/dist/commands/goal-cli.js +210 -27
- package/dist/commands/handlers/goal-handler.d.ts +9 -2
- package/dist/commands/handlers/goal-handler.js +36 -9
- package/dist/commands/handlers/infra-handlers.js +36 -0
- package/dist/commands/handlers/memory-handlers.js +96 -1
- package/dist/commands/headless-slash.d.ts +2 -0
- package/dist/commands/headless-slash.js +12 -0
- package/dist/commands/llm-provider-resolution.d.ts +5 -3
- package/dist/commands/llm-provider-resolution.js +87 -33
- package/dist/commands/ollama.d.ts +25 -0
- package/dist/commands/ollama.js +100 -0
- package/dist/commands/provider.d.ts +5 -0
- package/dist/commands/provider.js +106 -55
- package/dist/commands/spec-next.js +2 -1
- package/dist/commands/spec-plan.js +5 -16
- package/dist/commands/tunnel.d.ts +2 -0
- package/dist/commands/tunnel.js +48 -0
- package/dist/config/config-resolver.d.ts +2 -1
- package/dist/config/config-resolver.js +54 -41
- package/dist/config/constants.d.ts +28 -0
- package/dist/config/constants.js +7 -0
- package/dist/config/env-schema.js +756 -0
- package/dist/config/model-tools.js +4 -4
- package/dist/config/toml-config.d.ts +16 -0
- package/dist/config/toml-config.js +3 -0
- package/dist/context/context-manager-v2.js +1 -0
- package/dist/daemon/autonomous-daemon.d.ts +1 -1
- package/dist/daemon/autonomous-daemon.js +3 -3
- package/dist/daemon/autonomous-loop.js +11 -2
- package/dist/daemon/colab-goal.js +9 -1
- package/dist/daemon/cron-agent-bridge.d.ts +12 -3
- package/dist/daemon/cron-agent-bridge.js +25 -9
- package/dist/desktop/codebuddy-engine-adapter.d.ts +32 -0
- package/dist/desktop/codebuddy-engine-adapter.js +257 -80
- package/dist/desktop/engine-adapter.d.ts +14 -0
- package/dist/desktop-automation/automation-manager.js +16 -0
- package/dist/desktop-automation/omniparser-runner.d.ts +63 -0
- package/dist/desktop-automation/omniparser-runner.js +115 -0
- package/dist/desktop-automation/smart-snapshot.d.ts +11 -0
- package/dist/desktop-automation/smart-snapshot.js +72 -5
- package/dist/doctor/index.js +40 -23
- package/dist/events/types.d.ts +11 -0
- package/dist/fleet/colab-store.js +14 -2
- package/dist/fleet/peer-session-bridge.js +50 -12
- package/dist/fleet/peer-tool-bridge.js +14 -0
- package/dist/goals/goal-decomposer.d.ts +27 -0
- package/dist/goals/goal-decomposer.js +293 -0
- package/dist/goals/goal-judge-client.d.ts +15 -0
- package/dist/goals/goal-judge-client.js +45 -0
- package/dist/goals/goal-judge.d.ts +2 -0
- package/dist/goals/goal-judge.js +35 -6
- package/dist/goals/goal-loop.d.ts +14 -0
- package/dist/goals/goal-loop.js +47 -2
- package/dist/goals/goal-manager.d.ts +6 -0
- package/dist/goals/goal-manager.js +67 -14
- package/dist/goals/goal-state.d.ts +13 -0
- package/dist/goals/goal-state.js +83 -13
- package/dist/goals/goal-store.d.ts +3 -0
- package/dist/goals/goal-store.js +31 -15
- package/dist/hooks/user-hooks.js +17 -3
- package/dist/index.js +464 -25
- package/dist/input/text-to-speech.d.ts +2 -6
- package/dist/input/text-to-speech.js +2 -27
- package/dist/integrations/screenpipe/screenpipe-client.d.ts +10 -0
- package/dist/integrations/screenpipe/screenpipe-client.js +12 -2
- package/dist/integrations/tailscale.d.ts +13 -0
- package/dist/integrations/tailscale.js +87 -34
- package/dist/knowledge/workspace-indexer.js +53 -9
- package/dist/mcp/client.js +1 -0
- package/dist/mcp/mcp-desktop-tools.d.ts +23 -0
- package/dist/mcp/mcp-desktop-tools.js +158 -0
- package/dist/mcp/mcp-server.js +2 -0
- package/dist/memory/enhanced-memory.d.ts +10 -0
- package/dist/memory/enhanced-memory.js +33 -2
- package/dist/memory/index.d.ts +3 -1
- package/dist/memory/index.js +4 -1
- package/dist/memory/memory-auto-proposer.d.ts +23 -0
- package/dist/memory/memory-auto-proposer.js +308 -0
- package/dist/memory/memory-candidate-queue.d.ts +92 -0
- package/dist/memory/memory-candidate-queue.js +261 -0
- package/dist/memory/persistent-memory.d.ts +45 -2
- package/dist/memory/persistent-memory.js +236 -40
- package/dist/ml/bayesian-qualifier.d.ts +1 -1
- package/dist/ml/bayesian-qualifier.js +35 -2
- package/dist/openclaw/gateway-bridge.js +5 -0
- package/dist/plugins/marketplace.d.ts +1 -0
- package/dist/plugins/marketplace.js +7 -0
- package/dist/prompts/execution-discipline.d.ts +14 -0
- package/dist/prompts/execution-discipline.js +29 -0
- package/dist/prompts/variation-injector.js +9 -2
- package/dist/protocols/acp/acp-agentic-runner.d.ts +7 -6
- package/dist/protocols/acp/acp-agentic-runner.js +115 -18
- package/dist/protocols/acp/acp-session-store.d.ts +23 -0
- package/dist/protocols/acp/acp-session-store.js +77 -0
- package/dist/protocols/acp/acp-stdio-server.d.ts +7 -0
- package/dist/protocols/acp/acp-stdio-server.js +71 -16
- package/dist/providers/active-llm-registry.d.ts +37 -0
- package/dist/providers/active-llm-registry.js +186 -0
- package/dist/providers/auxiliary-provider.d.ts +25 -0
- package/dist/providers/auxiliary-provider.js +192 -0
- package/dist/providers/codex-oauth.d.ts +1 -1
- package/dist/providers/codex-oauth.js +27 -4
- package/dist/providers/index.d.ts +3 -0
- package/dist/providers/index.js +4 -0
- package/dist/providers/provider-catalog.d.ts +70 -0
- package/dist/providers/provider-catalog.js +738 -0
- package/dist/providers/provider-fallback.d.ts +35 -0
- package/dist/providers/provider-fallback.js +171 -0
- package/dist/providers/xai-oauth.d.ts +128 -0
- package/dist/providers/xai-oauth.js +735 -0
- package/dist/scheduler/cron-scheduler.d.ts +15 -3
- package/dist/scheduler/cron-scheduler.js +53 -7
- package/dist/scheduler/script-runner.d.ts +2 -0
- package/dist/scheduler/script-runner.js +19 -4
- package/dist/scheduler/watchdog-handlers.js +7 -20
- package/dist/search/usearch-index.js +7 -2
- package/dist/security/tool-policy/tool-groups.js +2 -0
- package/dist/server/index.js +17 -2
- package/dist/server/routes/mobile.d.ts +12 -4
- package/dist/server/routes/mobile.js +116 -1
- package/dist/server/tls-config.d.ts +35 -0
- package/dist/server/tls-config.js +142 -0
- package/dist/server/tunnel-manager.d.ts +20 -0
- package/dist/server/tunnel-manager.js +58 -0
- package/dist/server/websocket/fleet-bridge.js +5 -0
- package/dist/services/prompt-builder.d.ts +1 -0
- package/dist/services/prompt-builder.js +49 -8
- package/dist/shared/engine-types.d.ts +15 -1
- package/dist/sidecar/sidecar-bridge.d.ts +1 -0
- package/dist/sidecar/sidecar-bridge.js +21 -10
- package/dist/spec/spec-store.js +8 -1
- package/dist/talk-mode/providers/audioreader-tts.d.ts +1 -1
- package/dist/talk-mode/providers/audioreader-tts.js +8 -8
- package/dist/templates/project-scaffolding.js +3 -2
- package/dist/themes/theme-schema.d.ts +10 -10
- package/dist/tools/application-profiles.js +38 -0
- package/dist/tools/bash/bash-tool.d.ts +1 -0
- package/dist/tools/bash/bash-tool.js +16 -2
- package/dist/tools/bash/command-validator.js +3 -0
- package/dist/tools/bash/rtk-rewrite.d.ts +16 -0
- package/dist/tools/bash/rtk-rewrite.js +100 -0
- package/dist/tools/bash/streaming-executor.js +14 -1
- package/dist/tools/computer-control-tool.d.ts +16 -1
- package/dist/tools/computer-control-tool.js +317 -5
- package/dist/tools/document-generator.d.ts +14 -0
- package/dist/tools/document-generator.js +79 -0
- package/dist/tools/execute-code-rpc-invoker.d.ts +26 -2
- package/dist/tools/execute-code-rpc-invoker.js +46 -5
- package/dist/tools/metadata.js +28 -0
- package/dist/tools/office-macro-tool.d.ts +10 -0
- package/dist/tools/office-macro-tool.js +93 -0
- package/dist/tools/registry/index.d.ts +2 -1
- package/dist/tools/registry/index.js +2 -1
- package/dist/tools/registry/memory-tools.d.ts +20 -0
- package/dist/tools/registry/memory-tools.js +225 -3
- package/dist/tools/registry/multimodal-tools.js +2 -2
- package/dist/tools/registry/vision-tools.d.ts +33 -0
- package/dist/tools/registry/vision-tools.js +308 -6
- package/dist/tools/registry/windows-tools.d.ts +2 -0
- package/dist/tools/registry/windows-tools.js +47 -0
- package/dist/tools/route-peer-tool.js +14 -0
- package/dist/tools/screenshot-tool.js +14 -2
- package/dist/tools/text-to-speech-tool.d.ts +1 -1
- package/dist/tools/text-to-speech-tool.js +2 -12
- package/dist/tools/tool-selector.js +3 -59
- package/dist/utils/config-validation/schema.d.ts +40 -7
- package/dist/utils/config-validation/schema.js +130 -1
- package/dist/utils/cost-tracker.js +21 -1
- package/dist/utils/disk-guard.d.ts +180 -0
- package/dist/utils/disk-guard.js +404 -0
- package/dist/utils/interactive-setup.js +35 -2
- package/dist/utils/model-utils.js +11 -2
- package/dist/utils/output-sanitizer.js +11 -0
- package/dist/utils/provider-detector.d.ts +8 -7
- package/dist/utils/provider-detector.js +19 -77
- package/dist/utils/settings-manager.d.ts +7 -0
- package/dist/utils/token-display.d.ts +3 -3
- package/dist/utils/token-display.js +22 -4
- package/dist/wizard/onboarding.d.ts +39 -0
- package/dist/wizard/onboarding.js +433 -21
- package/dist/wizard/provider-onboarding.d.ts +8 -23
- package/dist/wizard/provider-onboarding.js +39 -158
- package/package.json +4 -2
- package/dist/plugins/provider-onboarding.d.ts +0 -23
- package/dist/plugins/provider-onboarding.js +0 -116
- package/dist/utils/kokoro-tts.d.ts +0 -8
- package/dist/utils/kokoro-tts.js +0 -67
|
@@ -14,7 +14,7 @@ import { ToolHandler } from "./tool-handler.js";
|
|
|
14
14
|
import { BaseAgent } from "./base-agent.js";
|
|
15
15
|
import { createAgentInfrastructureSync } from "./infrastructure/index.js";
|
|
16
16
|
import { getLaneQueue } from "../concurrency/lane-queue.js";
|
|
17
|
-
import { findSkill, findStarterPack } from "../skills/index.js";
|
|
17
|
+
import { findSkill, findStarterPack, resetSkillRegistry } from "../skills/index.js";
|
|
18
18
|
import { skillMdToUnified } from "../skills/adapters/index.js";
|
|
19
19
|
import { getSkillsHub } from "../skills/hub.js";
|
|
20
20
|
import { MessageQueue } from "./message-queue.js";
|
|
@@ -24,6 +24,12 @@ import { initializeMemory, getMemoryManager } from "../memory/persistent-memory.
|
|
|
24
24
|
import { getUserHooksManager } from "../hooks/user-hooks.js";
|
|
25
25
|
import { isFeatureEnabled } from "../config/feature-flags.js";
|
|
26
26
|
import { getActiveRunStore } from "../observability/run-store.js";
|
|
27
|
+
import { resetIdentityManager } from "../identity/identity-manager.js";
|
|
28
|
+
import { resetHotReloadManager } from "../config/hot-reload/index.js";
|
|
29
|
+
import { resetConfigWatcher } from "../config/hot-reload/watcher.js";
|
|
30
|
+
import { resetPersonaManager } from "../personas/persona-manager.js";
|
|
31
|
+
import { resetEnhancedMemory } from "../memory/enhanced-memory.js";
|
|
32
|
+
import { resetPluginMarketplace } from "../plugins/marketplace.js";
|
|
27
33
|
/**
|
|
28
34
|
* Main agent class that orchestrates conversation with CodeBuddy AI and tool execution
|
|
29
35
|
*
|
|
@@ -76,9 +82,10 @@ export class CodeBuddyAgent extends BaseAgent {
|
|
|
76
82
|
* @param useRAGToolSelection - Enable RAG-based tool selection (default: true)
|
|
77
83
|
*/
|
|
78
84
|
constructor(apiKey, baseURL, model, maxToolRounds, useRAGToolSelection = true, systemPromptId, // New: external prompt ID (default, minimal, secure, etc.)
|
|
79
|
-
workingDirectory, systemPromptAppend) {
|
|
85
|
+
workingDirectory, systemPromptAppend, initialSystemPromptOverride) {
|
|
80
86
|
super();
|
|
81
87
|
this.systemPromptAppend = systemPromptAppend;
|
|
88
|
+
this.customSystemPromptOverride = initialSystemPromptOverride?.trim() || null;
|
|
82
89
|
this.visionGroundingModel = process.env.CODEBUDDY_VISION_GROUNDING_MODEL || undefined;
|
|
83
90
|
const initialWorkingDirectory = workingDirectory || process.cwd();
|
|
84
91
|
// Determine model to use
|
|
@@ -334,6 +341,27 @@ export class CodeBuddyAgent extends BaseAgent {
|
|
|
334
341
|
catch (err) {
|
|
335
342
|
logger.debug('Failed to register AutoRepairMiddleware (non-critical)', { error: err instanceof Error ? err.message : String(err) });
|
|
336
343
|
}
|
|
344
|
+
// Verification enforcement (priority 155) — after >= 3 file changes with
|
|
345
|
+
// no task_verify/run_tests, nudges the model to verify before finishing.
|
|
346
|
+
// Closes the "loop completes on no-more-tool-calls without a verify gate"
|
|
347
|
+
// gap surfaced by the Hermes agentic-loop audit (2026-06-16).
|
|
348
|
+
try {
|
|
349
|
+
const { createVerificationEnforcementMiddleware } = await import('./middleware/verification-enforcement.js');
|
|
350
|
+
pipeline.use(createVerificationEnforcementMiddleware());
|
|
351
|
+
logger.debug('VerificationEnforcementMiddleware registered in pipeline (priority 155)');
|
|
352
|
+
}
|
|
353
|
+
catch (err) {
|
|
354
|
+
logger.debug('Failed to register VerificationEnforcementMiddleware (non-critical)', { error: err instanceof Error ? err.message : String(err) });
|
|
355
|
+
}
|
|
356
|
+
// Visual validation middleware (priority 156) — suggests taking screenshots of generated Office documents
|
|
357
|
+
try {
|
|
358
|
+
const { VisualValidationMiddleware } = await import('./middleware/visual-validation-middleware.js');
|
|
359
|
+
pipeline.use(new VisualValidationMiddleware());
|
|
360
|
+
logger.debug('VisualValidationMiddleware registered in pipeline (priority 156)');
|
|
361
|
+
}
|
|
362
|
+
catch (err) {
|
|
363
|
+
logger.debug('Failed to register VisualValidationMiddleware (non-critical)', { error: err instanceof Error ? err.message : String(err) });
|
|
364
|
+
}
|
|
337
365
|
// Quality gate middleware (priority 200) — auto-delegates to specialized agents
|
|
338
366
|
try {
|
|
339
367
|
const { createQualityGateMiddleware } = await import('./middleware/quality-gate-middleware.js');
|
|
@@ -492,18 +520,22 @@ Look at the screenshot and find the element matching the user's intent. Output o
|
|
|
492
520
|
}
|
|
493
521
|
async initializeAgentSystemPrompt(systemPromptId, modelName, customInstructions) {
|
|
494
522
|
try {
|
|
495
|
-
|
|
523
|
+
const hasInitialOverride = !!this.customSystemPromptOverride;
|
|
524
|
+
let systemPrompt = this.customSystemPromptOverride
|
|
525
|
+
?? await this.promptBuilder.buildSystemPrompt(systemPromptId, modelName, customInstructions);
|
|
496
526
|
// Inject repoProfile.contextPack if available
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
527
|
+
if (!hasInitialOverride) {
|
|
528
|
+
try {
|
|
529
|
+
const profiler = getRepoProfiler();
|
|
530
|
+
const profile = await profiler.getProfile();
|
|
531
|
+
if (profile.contextPack) {
|
|
532
|
+
systemPrompt = `${systemPrompt}\n\n[Repo] ${profile.contextPack}`;
|
|
533
|
+
logger.debug('RepoProfiler: injected contextPack into system prompt');
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
catch {
|
|
537
|
+
// Non-fatal — repo profiling is best-effort
|
|
503
538
|
}
|
|
504
|
-
}
|
|
505
|
-
catch {
|
|
506
|
-
// Non-fatal — repo profiling is best-effort
|
|
507
539
|
}
|
|
508
540
|
systemPrompt = this.applyRuntimeSystemPromptAppend(systemPrompt);
|
|
509
541
|
// Knowledge base is already injected by PromptBuilder.buildSystemPrompt() — skip duplicate injection here.
|
|
@@ -1639,7 +1671,9 @@ Look at the screenshot and find the element matching the user's intent. Output o
|
|
|
1639
1671
|
// Fire SessionEnd user hook (non-blocking). Shutdown errors should be
|
|
1640
1672
|
// visible (warn, not debug) — silent debug logs masked prior bugs.
|
|
1641
1673
|
getUserHooksManager(process.cwd()).executeHooks('SessionEnd', {}).catch((err) => logger.warn(`[user-hooks] SessionEnd error: ${err instanceof Error ? err.message : String(err)}`));
|
|
1642
|
-
|
|
1674
|
+
const skipAsyncSessionLearning = process.env.CODEBUDDY_HEADLESS === 'true' ||
|
|
1675
|
+
process.env.CODEBUDDY_HEADLESS === '1';
|
|
1676
|
+
if (!skipAsyncSessionLearning && isFeatureEnabled('USER_MODEL_DIALECTIC_ON_SESSION_END')) {
|
|
1643
1677
|
const chatHistory = this.historyManager.getChatHistory();
|
|
1644
1678
|
if (chatHistory && chatHistory.length > 0) {
|
|
1645
1679
|
import('../memory/user-model.js')
|
|
@@ -1659,7 +1693,7 @@ Look at the screenshot and find the element matching the user's intent. Output o
|
|
|
1659
1693
|
// WS3-T1 — session-end flush: handoff + review-gated lesson candidates.
|
|
1660
1694
|
// Fire-and-forget like the dialectic above; callers that need the flush
|
|
1661
1695
|
// to complete before process exit await runSessionEndFlush directly.
|
|
1662
|
-
{
|
|
1696
|
+
if (!skipAsyncSessionLearning) {
|
|
1663
1697
|
const chatHistory = this.historyManager.getChatHistory();
|
|
1664
1698
|
if (chatHistory && chatHistory.length > 0) {
|
|
1665
1699
|
import('./session-end-flush.js')
|
|
@@ -1696,6 +1730,27 @@ Look at the screenshot and find the element matching the user's intent. Output o
|
|
|
1696
1730
|
this.contextManager.stopPeriodicSnapshot?.();
|
|
1697
1731
|
this.peerRoutingConfig = null;
|
|
1698
1732
|
super.dispose();
|
|
1733
|
+
if (skipAsyncSessionLearning) {
|
|
1734
|
+
cleanupHeadlessSingletonWatchers();
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
function cleanupHeadlessSingletonWatchers() {
|
|
1739
|
+
for (const cleanup of [
|
|
1740
|
+
resetSkillRegistry,
|
|
1741
|
+
resetIdentityManager,
|
|
1742
|
+
resetHotReloadManager,
|
|
1743
|
+
resetConfigWatcher,
|
|
1744
|
+
resetPersonaManager,
|
|
1745
|
+
resetEnhancedMemory,
|
|
1746
|
+
resetPluginMarketplace,
|
|
1747
|
+
]) {
|
|
1748
|
+
try {
|
|
1749
|
+
cleanup();
|
|
1750
|
+
}
|
|
1751
|
+
catch (error) {
|
|
1752
|
+
logger.debug('Headless watcher cleanup skipped', { error: String(error) });
|
|
1753
|
+
}
|
|
1699
1754
|
}
|
|
1700
1755
|
}
|
|
1701
1756
|
//# sourceMappingURL=codebuddy-agent.js.map
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @module agent/execution
|
|
9
9
|
*/
|
|
10
|
+
import { normalizeHallucinatedLocalToolCall } from "../tool-handler.js";
|
|
10
11
|
import { logger } from "../../utils/logger.js";
|
|
11
12
|
import { getErrorMessage } from "../../errors/index.js";
|
|
12
13
|
import { sanitizeToolResult } from "../../utils/sanitize.js";
|
|
@@ -32,6 +33,22 @@ function withTimeout(promise, ms, fallback) {
|
|
|
32
33
|
new Promise(resolve => setTimeout(() => resolve(fallback), ms)),
|
|
33
34
|
]);
|
|
34
35
|
}
|
|
36
|
+
function isIgnorableControlToolCall(toolCall) {
|
|
37
|
+
const name = toolCall.function?.name?.trim();
|
|
38
|
+
if (!name)
|
|
39
|
+
return true;
|
|
40
|
+
// Some local models leak thinking/channel markers as tool names. Keep
|
|
41
|
+
// malformed-but-actionable aliases such as `thought-tool:execute_command`
|
|
42
|
+
// because ToolHandler normalizes those to real tools.
|
|
43
|
+
if (normalizeHallucinatedLocalToolCall(name, {}))
|
|
44
|
+
return false;
|
|
45
|
+
const lower = name.toLowerCase();
|
|
46
|
+
return (lower === 'thought' ||
|
|
47
|
+
lower === 'thought|' ||
|
|
48
|
+
lower.startsWith('thought|') ||
|
|
49
|
+
lower.includes('<|channel>') ||
|
|
50
|
+
lower.includes('<|tool_call>'));
|
|
51
|
+
}
|
|
35
52
|
// Lazy-loaded workspace context to avoid blocking tests.
|
|
36
53
|
// Includes a 3s hard timeout so git commands never stall the agent loop.
|
|
37
54
|
let _getWorkspaceContext = null;
|
|
@@ -418,6 +435,21 @@ export class AgentExecutor {
|
|
|
418
435
|
const maxToolRounds = this.config.maxToolRounds;
|
|
419
436
|
let toolRounds = 0;
|
|
420
437
|
let totalOutputTokens = 0;
|
|
438
|
+
// In-loop recovery budgets (Hermes parity): bound re-prompts WITHIN a turn
|
|
439
|
+
// so a length-truncated or post-tool-empty response is recovered instead of
|
|
440
|
+
// returned half-written. 0 disables. Per-turn counters.
|
|
441
|
+
const parseRecoveryBudget = (raw, dflt) => {
|
|
442
|
+
const n = Number.parseInt(raw ?? '', 10);
|
|
443
|
+
return Number.isFinite(n) && n >= 0 ? n : dflt;
|
|
444
|
+
};
|
|
445
|
+
const maxLengthContinuations = parseRecoveryBudget(process.env.CODEBUDDY_MAX_LENGTH_CONTINUATIONS, 3);
|
|
446
|
+
// Length-continuation ships ON (default 3, real-tested). The post-tool
|
|
447
|
+
// empty-response re-prompt is harder to trigger deterministically with a
|
|
448
|
+
// real model, so it ships OFF by default (no untested-by-default behaviour
|
|
449
|
+
// in the hot loop) — opt in with CODEBUDDY_MAX_EMPTY_RETRIES=N.
|
|
450
|
+
const maxEmptyRetries = parseRecoveryBudget(process.env.CODEBUDDY_MAX_EMPTY_RETRIES, 0);
|
|
451
|
+
let lengthContinuations = 0;
|
|
452
|
+
let emptyRetries = 0;
|
|
421
453
|
// Phase (d).21 ship 4 — start a progress session for this turn.
|
|
422
454
|
// The default sink (boot-wired) logs at 25/50/75/100. Lazy-import to
|
|
423
455
|
// avoid circular load at module init time.
|
|
@@ -485,12 +517,14 @@ export class AgentExecutor {
|
|
|
485
517
|
// shrink the tool set to ~5 with a minimal alwaysInclude — we
|
|
486
518
|
// don't want to dangle `remember`/`lessons_*` in front of a model
|
|
487
519
|
// that can't actually call tools and would inline-hallucinate them.
|
|
488
|
-
|
|
520
|
+
const activeModelName = this.deps.client.getCurrentModel() ?? '';
|
|
521
|
+
let selectionOpts = activeModelName ? { modelName: activeModelName } : {};
|
|
489
522
|
try {
|
|
490
523
|
const { getModelToolConfig } = await import('../../config/model-tools.js');
|
|
491
|
-
const cfg = getModelToolConfig(
|
|
524
|
+
const cfg = getModelToolConfig(activeModelName);
|
|
492
525
|
if (cfg.promptProfile === 'lite') {
|
|
493
526
|
selectionOpts = {
|
|
527
|
+
...selectionOpts,
|
|
494
528
|
maxTools: 5,
|
|
495
529
|
alwaysInclude: ['view_file', 'bash', 'search'],
|
|
496
530
|
};
|
|
@@ -499,8 +533,9 @@ export class AgentExecutor {
|
|
|
499
533
|
catch { /* model-tools optional, never block */ }
|
|
500
534
|
const selectionResult = await this.deps.toolSelectionStrategy.selectToolsForQuery(message, selectionOpts);
|
|
501
535
|
let tools = selectionResult.tools;
|
|
536
|
+
let forcedChatOnlyToolRunModel = null;
|
|
502
537
|
if (toolRounds === 0)
|
|
503
|
-
this.deps.toolSelectionStrategy.cacheTools(tools);
|
|
538
|
+
this.deps.toolSelectionStrategy.cacheTools(tools, activeModelName);
|
|
504
539
|
// If the active model is flagged `supportsToolCalls: false` in
|
|
505
540
|
// model-tools.ts (typical of small Ollama / LM Studio models that
|
|
506
541
|
// can't reliably emit OpenAI-style tool_call frames), drop the
|
|
@@ -515,12 +550,17 @@ export class AgentExecutor {
|
|
|
515
550
|
// `(client as { defaultModel? }).defaultModel` access always
|
|
516
551
|
// resolved to undefined because that field doesn't exist on
|
|
517
552
|
// the dispatcher class — left the guard latent for ages.
|
|
518
|
-
const modelName =
|
|
553
|
+
const modelName = activeModelName;
|
|
519
554
|
if (modelName) {
|
|
520
555
|
const cfg = getModelToolConfig(modelName);
|
|
521
556
|
if (cfg.supportsToolCalls === false && tools.length > 0) {
|
|
522
|
-
|
|
523
|
-
|
|
557
|
+
if (process.env.GROK_FORCE_TOOLS === 'true') {
|
|
558
|
+
forcedChatOnlyToolRunModel = modelName;
|
|
559
|
+
}
|
|
560
|
+
else {
|
|
561
|
+
logger.debug(`[agent-executor] supportsToolCalls=false for ${modelName} — dropping ${tools.length} tools from chat call`);
|
|
562
|
+
tools = [];
|
|
563
|
+
}
|
|
524
564
|
}
|
|
525
565
|
}
|
|
526
566
|
}
|
|
@@ -597,7 +637,7 @@ export class AgentExecutor {
|
|
|
597
637
|
yield { type: "token_count", tokenCount: inputTokens + result.tokenCount };
|
|
598
638
|
}
|
|
599
639
|
}
|
|
600
|
-
if (!this.deps.streamingHandler.hasYieldedToolCalls()) {
|
|
640
|
+
if (tools.length > 0 && !this.deps.streamingHandler.hasYieldedToolCalls()) {
|
|
601
641
|
const extracted = this.deps.streamingHandler.extractToolCalls();
|
|
602
642
|
if (extracted.toolCalls.length > 0) {
|
|
603
643
|
yield { type: "tool_calls", toolCalls: extracted.toolCalls };
|
|
@@ -605,9 +645,33 @@ export class AgentExecutor {
|
|
|
605
645
|
}
|
|
606
646
|
const accumulatedMessage = this.deps.streamingHandler.getAccumulatedMessage();
|
|
607
647
|
// Sanitize streamed assistant content: strip model control tokens and invisible chars
|
|
608
|
-
|
|
648
|
+
let toolCalls = accumulatedMessage.tool_calls;
|
|
649
|
+
if (Array.isArray(toolCalls) && toolCalls.length > 0) {
|
|
650
|
+
const filteredToolCalls = toolCalls.filter((toolCall) => !isIgnorableControlToolCall(toolCall));
|
|
651
|
+
if (filteredToolCalls.length !== toolCalls.length) {
|
|
652
|
+
logger.debug('[agent-executor] dropped hallucinated control tool calls', {
|
|
653
|
+
dropped: toolCalls.length - filteredToolCalls.length,
|
|
654
|
+
kept: filteredToolCalls.length,
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
toolCalls = filteredToolCalls.length > 0 ? filteredToolCalls : undefined;
|
|
658
|
+
}
|
|
659
|
+
const hasToolCalls = Array.isArray(toolCalls) && toolCalls.length > 0;
|
|
660
|
+
// Pre-fallback raw content — used by in-loop recovery to tell a real
|
|
661
|
+
// partial answer (retry-able) from a truly empty turn (give up).
|
|
662
|
+
const streamedContentRaw = (accumulatedMessage.content || "").trim();
|
|
663
|
+
const streamFinishReason = accumulatedMessage.finishReason;
|
|
664
|
+
let rawStreamedContent = accumulatedMessage.content || "";
|
|
665
|
+
if (forcedChatOnlyToolRunModel && !hasToolCalls && !rawStreamedContent.trim()) {
|
|
666
|
+
rawStreamedContent =
|
|
667
|
+
`Blocked: ${forcedChatOnlyToolRunModel} is configured as a chat-only local model and ` +
|
|
668
|
+
'returned no structured tool call even with GROK_FORCE_TOOLS=true. ' +
|
|
669
|
+
'Use a tool-capable model such as qwen3.5-ctx32k or gpt-5.5 for goals that need shell/tools.';
|
|
670
|
+
yield { type: "content", content: `${rawStreamedContent}\n` };
|
|
671
|
+
}
|
|
672
|
+
if (!rawStreamedContent)
|
|
673
|
+
rawStreamedContent = "Using tools to help you...";
|
|
609
674
|
const content = sanitizeAssistantOutput(rawStreamedContent);
|
|
610
|
-
const toolCalls = accumulatedMessage.tool_calls;
|
|
611
675
|
const assistantEntry = {
|
|
612
676
|
type: "assistant",
|
|
613
677
|
content: content,
|
|
@@ -616,6 +680,9 @@ export class AgentExecutor {
|
|
|
616
680
|
};
|
|
617
681
|
history.push(assistantEntry);
|
|
618
682
|
messages.push({ role: "assistant", content: content, tool_calls: toolCalls });
|
|
683
|
+
const currentOutputTokens = this.deps.streamingHandler.getTokenCount() || 0;
|
|
684
|
+
totalOutputTokens += currentOutputTokens;
|
|
685
|
+
yield { type: "token_count", tokenCount: inputTokens + totalOutputTokens };
|
|
619
686
|
if (toolCalls && toolCalls.length > 0) {
|
|
620
687
|
toolRounds++;
|
|
621
688
|
// Pre-check cost limit before executing tools (estimate only — no side effects)
|
|
@@ -703,7 +770,7 @@ export class AgentExecutor {
|
|
|
703
770
|
// Use streaming execution for tools that support it (bash, reason, + adapter-based)
|
|
704
771
|
let result;
|
|
705
772
|
const _streamToolStartMs = Date.now();
|
|
706
|
-
const STREAMING_TOOLS = ['bash', 'reason'];
|
|
773
|
+
const STREAMING_TOOLS = ['bash', 'reason', 'generate_document'];
|
|
707
774
|
if (STREAMING_TOOLS.includes(toolCall.function.name)) {
|
|
708
775
|
const gen = this.deps.toolHandler.executeToolStreaming(toolCall);
|
|
709
776
|
let genResult = await gen.next();
|
|
@@ -914,8 +981,6 @@ export class AgentExecutor {
|
|
|
914
981
|
if (terminateDetectedStreaming)
|
|
915
982
|
break;
|
|
916
983
|
inputTokens = this.deps.tokenCounter.countMessageTokens(messages);
|
|
917
|
-
const currentOutputTokens = this.deps.streamingHandler.getTokenCount() || 0;
|
|
918
|
-
totalOutputTokens += currentOutputTokens;
|
|
919
984
|
yield { type: "token_count", tokenCount: inputTokens + totalOutputTokens };
|
|
920
985
|
// Run after_turn middleware (handles cost recording + limit)
|
|
921
986
|
if (pipeline) {
|
|
@@ -942,6 +1007,51 @@ export class AgentExecutor {
|
|
|
942
1007
|
catch { /* masking is optional */ }
|
|
943
1008
|
}
|
|
944
1009
|
else {
|
|
1010
|
+
// ── In-loop recovery (Hermes parity) ─────────────────────────────
|
|
1011
|
+
// Re-prompt WITHIN the turn before accepting this as the final answer.
|
|
1012
|
+
// Only reachable in the no-tool-calls branch, so we never split a
|
|
1013
|
+
// tool_call/tool_result pair (keeps transcript-repair invariants intact).
|
|
1014
|
+
if (!abortController?.signal.aborted) {
|
|
1015
|
+
// (1) Length truncation: the model hit the output-token cap mid-prose.
|
|
1016
|
+
// Ask it to continue from where it stopped, bounded. A continuation
|
|
1017
|
+
// that produced ZERO new tokens (e.g. a too-small num_ctx) leaves
|
|
1018
|
+
// `streamedContentRaw` empty — we do NOT retry that (the fix is config,
|
|
1019
|
+
// not looping); we fall through and stop.
|
|
1020
|
+
if (streamFinishReason === 'length' &&
|
|
1021
|
+
streamedContentRaw.length > 0 &&
|
|
1022
|
+
lengthContinuations < maxLengthContinuations) {
|
|
1023
|
+
lengthContinuations++;
|
|
1024
|
+
logger.debug('[agent-executor] length-truncation continuation', {
|
|
1025
|
+
attempt: lengthContinuations,
|
|
1026
|
+
max: maxLengthContinuations,
|
|
1027
|
+
});
|
|
1028
|
+
messages.push({
|
|
1029
|
+
role: 'user',
|
|
1030
|
+
content: 'Your previous message was cut off because it reached the output length limit. ' +
|
|
1031
|
+
'Continue it from exactly where it stopped — do not repeat earlier text and do not ' +
|
|
1032
|
+
'restart. When the full response is complete, finish normally.',
|
|
1033
|
+
});
|
|
1034
|
+
continue;
|
|
1035
|
+
}
|
|
1036
|
+
// (2) Post-tool empty response: the model went silent after running
|
|
1037
|
+
// tools. Nudge it to use the results and continue, bounded.
|
|
1038
|
+
if (streamedContentRaw.length === 0 &&
|
|
1039
|
+
streamFinishReason !== 'length' &&
|
|
1040
|
+
toolRounds > 0 &&
|
|
1041
|
+
emptyRetries < maxEmptyRetries) {
|
|
1042
|
+
emptyRetries++;
|
|
1043
|
+
logger.debug('[agent-executor] empty-response re-prompt', {
|
|
1044
|
+
attempt: emptyRetries,
|
|
1045
|
+
max: maxEmptyRetries,
|
|
1046
|
+
});
|
|
1047
|
+
messages.push({
|
|
1048
|
+
role: 'user',
|
|
1049
|
+
content: 'Your last response was empty. Use the results of the tool calls you just made ' +
|
|
1050
|
+
'to continue the task and produce your answer.',
|
|
1051
|
+
});
|
|
1052
|
+
continue;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
945
1055
|
// Fire-and-forget auto-capture on final assistant response (streaming)
|
|
946
1056
|
try {
|
|
947
1057
|
const { getAutoCaptureManager } = await import('../../memory/auto-capture.js');
|
|
@@ -183,6 +183,13 @@ export const TOOL_METADATA = {
|
|
|
183
183
|
parallelSafe: false, // Single local camera device + explicit privacy boundary
|
|
184
184
|
priority: 6,
|
|
185
185
|
},
|
|
186
|
+
camera_analyze: {
|
|
187
|
+
reads: [],
|
|
188
|
+
writes: ["file"],
|
|
189
|
+
hasSideEffects: true,
|
|
190
|
+
parallelSafe: false, // Single local camera device + network vision call
|
|
191
|
+
priority: 6,
|
|
192
|
+
},
|
|
186
193
|
diagram: {
|
|
187
194
|
reads: [],
|
|
188
195
|
writes: ["file"],
|
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
* @module agent/execution/tool-hooks
|
|
12
12
|
*/
|
|
13
13
|
import type { CodeBuddyMessage } from '../../codebuddy/client.js';
|
|
14
|
+
export type FleetEventBroadcaster = (type: any, payload: any, agentId?: string) => void;
|
|
15
|
+
/**
|
|
16
|
+
* Register the fleet event broadcaster. Called by the server at boot
|
|
17
|
+
* to wire up fleet events without introducing a circular dependency.
|
|
18
|
+
*/
|
|
19
|
+
export declare function registerFleetBroadcaster(broadcaster: FleetEventBroadcaster): void;
|
|
14
20
|
/**
|
|
15
21
|
* Emit a fleet event for tool execution. Best-effort: never throws,
|
|
16
22
|
* silently no-ops when fleet streaming is disabled or the WS server
|
|
@@ -11,11 +11,19 @@
|
|
|
11
11
|
* @module agent/execution/tool-hooks
|
|
12
12
|
*/
|
|
13
13
|
import { getUserHooksManager } from '../../hooks/user-hooks.js';
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
let _fleetBroadcaster = null;
|
|
15
|
+
/**
|
|
16
|
+
* Register the fleet event broadcaster. Called by the server at boot
|
|
17
|
+
* to wire up fleet events without introducing a circular dependency.
|
|
18
|
+
*/
|
|
19
|
+
export function registerFleetBroadcaster(broadcaster) {
|
|
20
|
+
_fleetBroadcaster = broadcaster;
|
|
21
|
+
}
|
|
22
|
+
function _broadcastFleetEvent(type, payload, agentId) {
|
|
23
|
+
if (_fleetBroadcaster) {
|
|
24
|
+
_fleetBroadcaster(type, payload, agentId);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
19
27
|
/**
|
|
20
28
|
* Phase (d).2 V0.4.1 — fleet stream opt-in. When CODEBUDDY_FLEET_STREAM=1
|
|
21
29
|
* (or =true), every tool execution emits a fleet:agent:tool_started /
|
|
@@ -37,6 +37,12 @@ export interface ToolSelectionConfig {
|
|
|
37
37
|
enableCaching: boolean;
|
|
38
38
|
/** Cache TTL in milliseconds (default: 5 minutes) */
|
|
39
39
|
cacheTTLMs: number;
|
|
40
|
+
/**
|
|
41
|
+
* Active model name. When present, model capability rules from
|
|
42
|
+
* model-tools.ts are applied to the model-facing schemas before they are
|
|
43
|
+
* cached or sent to the provider.
|
|
44
|
+
*/
|
|
45
|
+
modelName?: string;
|
|
40
46
|
}
|
|
41
47
|
/**
|
|
42
48
|
* Result of a tool selection operation
|
|
@@ -80,6 +86,7 @@ export declare class ToolSelectionStrategy {
|
|
|
80
86
|
private config;
|
|
81
87
|
private cachedTools;
|
|
82
88
|
private cachedToolNames;
|
|
89
|
+
private cacheModelName;
|
|
83
90
|
private lastQuery;
|
|
84
91
|
private lastSelection;
|
|
85
92
|
private cacheTimestamp;
|
|
@@ -109,13 +116,13 @@ export declare class ToolSelectionStrategy {
|
|
|
109
116
|
*
|
|
110
117
|
* @param tools - Tools to cache
|
|
111
118
|
*/
|
|
112
|
-
cacheTools(tools: CodeBuddyTool[]): void;
|
|
119
|
+
cacheTools(tools: CodeBuddyTool[], modelName?: string): void;
|
|
113
120
|
/**
|
|
114
121
|
* Get cached tools if available and valid
|
|
115
122
|
*
|
|
116
123
|
* @returns Cached tools or null if cache is invalid/empty
|
|
117
124
|
*/
|
|
118
|
-
getCachedTools(): CodeBuddyTool[] | null;
|
|
125
|
+
getCachedTools(modelName?: string): CodeBuddyTool[] | null;
|
|
119
126
|
/**
|
|
120
127
|
* Set the active skill for tool augmentation.
|
|
121
128
|
*
|
|
@@ -140,6 +147,8 @@ export declare class ToolSelectionStrategy {
|
|
|
140
147
|
* Check if the cache is still valid
|
|
141
148
|
*/
|
|
142
149
|
private isCacheValid;
|
|
150
|
+
private static normalizeModelName;
|
|
151
|
+
private applyModelFacingSchemaFilter;
|
|
143
152
|
/**
|
|
144
153
|
* Record a tool request for metrics tracking
|
|
145
154
|
*
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { getAllCodeBuddyTools, getRelevantTools, classifyQuery, getSkillAugmentedTools, } from '../../codebuddy/tools.js';
|
|
19
19
|
import { getToolSelector, recordToolRequest, formatToolSelectionMetrics, } from '../../tools/tool-selector.js';
|
|
20
|
+
import { filterToolsForModel, getModelToolConfig, } from '../../config/model-tools.js';
|
|
20
21
|
import { getPromptCacheManager } from '../../optimization/prompt-cache.js';
|
|
21
22
|
import { logger } from '../../utils/logger.js';
|
|
22
23
|
import { getSkillsHub } from '../../skills/hub.js';
|
|
@@ -27,10 +28,10 @@ const DEFAULT_CONFIG = {
|
|
|
27
28
|
useRAG: true,
|
|
28
29
|
maxTools: 15,
|
|
29
30
|
minScore: 0.5,
|
|
30
|
-
// `remember`
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
// Paired with the auto-memory directive in `prompt-builder.ts`.
|
|
31
|
+
// `remember` and `memory_propose` are force-included so the LLM can always
|
|
32
|
+
// either persist explicit durable facts or queue inferred/ambiguous facts for
|
|
33
|
+
// review, even on tasks where the RAG selector wouldn't otherwise surface the
|
|
34
|
+
// memory tools. Paired with the auto-memory directive in `prompt-builder.ts`.
|
|
34
35
|
//
|
|
35
36
|
// `lessons_add`, `lessons_propose`, and `lessons_search` are force-included for
|
|
36
37
|
// the same reason (Manus AI-inspired self-improvement loop) — paired with the
|
|
@@ -38,7 +39,7 @@ const DEFAULT_CONFIG = {
|
|
|
38
39
|
// "agent proposes, human approves" path, so the model must always see it (a
|
|
39
40
|
// RAG-gated propose tool would rarely surface). `lessons_list` stays out
|
|
40
41
|
// (admin-style, not needed per-turn). Wakes the dormant feature in lessons-tracker.ts.
|
|
41
|
-
alwaysInclude: ['view_file', 'bash', 'search', 'str_replace_editor', 'web_search', 'remember', 'lessons_add', 'lessons_propose', 'lessons_search'],
|
|
42
|
+
alwaysInclude: ['view_file', 'bash', 'search', 'str_replace_editor', 'web_search', 'remember', 'memory_propose', 'lessons_add', 'lessons_propose', 'lessons_search'],
|
|
42
43
|
useAdaptiveThreshold: true,
|
|
43
44
|
enableCaching: true,
|
|
44
45
|
cacheTTLMs: 5 * 60 * 1000, // 5 minutes
|
|
@@ -68,6 +69,7 @@ export class ToolSelectionStrategy {
|
|
|
68
69
|
config;
|
|
69
70
|
cachedTools = null;
|
|
70
71
|
cachedToolNames = [];
|
|
72
|
+
cacheModelName = null;
|
|
71
73
|
lastQuery = '';
|
|
72
74
|
lastSelection = null;
|
|
73
75
|
cacheTimestamp = 0;
|
|
@@ -92,9 +94,10 @@ export class ToolSelectionStrategy {
|
|
|
92
94
|
*/
|
|
93
95
|
async selectToolsForQuery(query, options = {}) {
|
|
94
96
|
const effectiveConfig = { ...this.config, ...options };
|
|
97
|
+
const modelName = ToolSelectionStrategy.normalizeModelName(effectiveConfig.modelName);
|
|
95
98
|
this.lastQuery = query;
|
|
96
99
|
// Check if we should use cached tools
|
|
97
|
-
if (effectiveConfig.enableCaching && this.isCacheValid()) {
|
|
100
|
+
if (effectiveConfig.enableCaching && this.isCacheValid(modelName)) {
|
|
98
101
|
logger.debug('Using cached tools for query', { query: query.slice(0, 50) });
|
|
99
102
|
return {
|
|
100
103
|
tools: this.cachedTools,
|
|
@@ -142,6 +145,15 @@ export class ToolSelectionStrategy {
|
|
|
142
145
|
toolCount: tools.length,
|
|
143
146
|
});
|
|
144
147
|
}
|
|
148
|
+
tools = this.applyModelFacingSchemaFilter(tools, modelName);
|
|
149
|
+
this.cachedToolNames = tools.map(t => t.function.name);
|
|
150
|
+
if (selection) {
|
|
151
|
+
selection = {
|
|
152
|
+
...selection,
|
|
153
|
+
selectedTools: tools,
|
|
154
|
+
};
|
|
155
|
+
this.lastSelection = selection;
|
|
156
|
+
}
|
|
145
157
|
// Cache tools for prompt optimization
|
|
146
158
|
const promptCacheManager = getPromptCacheManager();
|
|
147
159
|
promptCacheManager.cacheTools(tools);
|
|
@@ -169,14 +181,16 @@ export class ToolSelectionStrategy {
|
|
|
169
181
|
*
|
|
170
182
|
* @param tools - Tools to cache
|
|
171
183
|
*/
|
|
172
|
-
cacheTools(tools) {
|
|
184
|
+
cacheTools(tools, modelName) {
|
|
173
185
|
if (!this.config.enableCaching)
|
|
174
186
|
return;
|
|
175
187
|
this.cachedTools = tools;
|
|
176
188
|
this.cachedToolNames = tools.map(t => t.function.name);
|
|
189
|
+
this.cacheModelName = ToolSelectionStrategy.normalizeModelName(modelName);
|
|
177
190
|
this.cacheTimestamp = Date.now();
|
|
178
191
|
logger.debug('Tools cached for multi-round consistency', {
|
|
179
192
|
toolCount: tools.length,
|
|
193
|
+
modelName: this.cacheModelName,
|
|
180
194
|
});
|
|
181
195
|
}
|
|
182
196
|
/**
|
|
@@ -184,10 +198,10 @@ export class ToolSelectionStrategy {
|
|
|
184
198
|
*
|
|
185
199
|
* @returns Cached tools or null if cache is invalid/empty
|
|
186
200
|
*/
|
|
187
|
-
getCachedTools() {
|
|
201
|
+
getCachedTools(modelName) {
|
|
188
202
|
if (!this.config.enableCaching)
|
|
189
203
|
return null;
|
|
190
|
-
if (!this.isCacheValid())
|
|
204
|
+
if (!this.isCacheValid(modelName))
|
|
191
205
|
return null;
|
|
192
206
|
return this.cachedTools;
|
|
193
207
|
}
|
|
@@ -243,6 +257,7 @@ export class ToolSelectionStrategy {
|
|
|
243
257
|
clearCache() {
|
|
244
258
|
this.cachedTools = null;
|
|
245
259
|
this.cachedToolNames = [];
|
|
260
|
+
this.cacheModelName = null;
|
|
246
261
|
this.cacheTimestamp = 0;
|
|
247
262
|
this.activeSkill = null;
|
|
248
263
|
logger.debug('Tool selection cache cleared');
|
|
@@ -250,15 +265,57 @@ export class ToolSelectionStrategy {
|
|
|
250
265
|
/**
|
|
251
266
|
* Check if the cache is still valid
|
|
252
267
|
*/
|
|
253
|
-
isCacheValid() {
|
|
268
|
+
isCacheValid(modelName) {
|
|
254
269
|
if (!this.cachedTools || this.cachedTools.length === 0)
|
|
255
270
|
return false;
|
|
256
271
|
if (this.cacheTimestamp === 0)
|
|
257
272
|
return false;
|
|
273
|
+
if (modelName !== undefined) {
|
|
274
|
+
const normalizedModelName = ToolSelectionStrategy.normalizeModelName(modelName);
|
|
275
|
+
if (this.cacheModelName !== normalizedModelName)
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
258
278
|
const now = Date.now();
|
|
259
279
|
const age = now - this.cacheTimestamp;
|
|
260
280
|
return age < this.config.cacheTTLMs;
|
|
261
281
|
}
|
|
282
|
+
static normalizeModelName(modelName) {
|
|
283
|
+
const normalized = modelName?.trim();
|
|
284
|
+
return normalized ? normalized : null;
|
|
285
|
+
}
|
|
286
|
+
applyModelFacingSchemaFilter(tools, modelName) {
|
|
287
|
+
if (!modelName || tools.length === 0) {
|
|
288
|
+
return tools;
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
const modelConfig = getModelToolConfig(modelName);
|
|
292
|
+
if (modelConfig.supportsToolCalls === false && process.env.GROK_FORCE_TOOLS !== 'true') {
|
|
293
|
+
logger.debug('Model-facing tool schemas suppressed for chat-only model', {
|
|
294
|
+
modelName,
|
|
295
|
+
removed: tools.map(t => t.function.name),
|
|
296
|
+
});
|
|
297
|
+
return [];
|
|
298
|
+
}
|
|
299
|
+
const allowedNames = new Set(filterToolsForModel(tools.map(tool => tool.function.name), modelConfig));
|
|
300
|
+
const filtered = tools.filter(tool => allowedNames.has(tool.function.name));
|
|
301
|
+
if (filtered.length !== tools.length) {
|
|
302
|
+
logger.debug('Model-facing tool schemas filtered by model capabilities', {
|
|
303
|
+
modelName,
|
|
304
|
+
removed: tools
|
|
305
|
+
.map(tool => tool.function.name)
|
|
306
|
+
.filter(name => !allowedNames.has(name)),
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
return filtered;
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
logger.debug('Model-facing tool schema filter skipped', {
|
|
313
|
+
modelName,
|
|
314
|
+
error: error instanceof Error ? error.message : String(error),
|
|
315
|
+
});
|
|
316
|
+
return tools;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
262
319
|
/**
|
|
263
320
|
* Record a tool request for metrics tracking
|
|
264
321
|
*
|
|
@@ -48,6 +48,14 @@ export declare class ExtendedThinkingManager {
|
|
|
48
48
|
* Set whether extended thinking is always enabled
|
|
49
49
|
*/
|
|
50
50
|
setAlwaysEnabled(value: boolean): void;
|
|
51
|
+
/**
|
|
52
|
+
* Apply a UI/settings thinking level — `off | minimal | low | medium | high | xhigh`.
|
|
53
|
+
* `off` (or any unknown level) disables thinking; any other level enables it with a
|
|
54
|
+
* scaled token budget. This is the runtime entry point for hot-swapping the level from
|
|
55
|
+
* the Cowork ReasoningLevelPicker; the OpenAI-compat / Grok / Ollama providers read
|
|
56
|
+
* {@link getThinkingConfig} fresh per request, so the change takes effect next turn.
|
|
57
|
+
*/
|
|
58
|
+
applyThinkingLevel(level: string): void;
|
|
51
59
|
/**
|
|
52
60
|
* Get the thinking configuration to merge into API request parameters.
|
|
53
61
|
* Returns an object with a `thinking` key when enabled, or an empty object when disabled.
|