@phuetz/code-buddy 1.0.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 -164
- 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 +101 -13
- 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/lesson-auto-proposer.js +10 -0
- package/dist/agent/middleware/index.d.ts +1 -0
- package/dist/agent/middleware/index.js +1 -0
- package/dist/agent/middleware/session-duration.d.ts +36 -0
- package/dist/agent/middleware/session-duration.js +78 -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 +71 -0
- package/dist/agent/session-end-flush.js +233 -0
- 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 +107 -3
- package/dist/commands/cli/utility-commands.js +45 -0
- package/dist/commands/enhanced-command-handler.js +5 -0
- package/dist/commands/goal-cli.d.ts +71 -0
- package/dist/commands/goal-cli.js +280 -0
- package/dist/commands/handlers/goal-handler.d.ts +34 -0
- package/dist/commands/handlers/goal-handler.js +155 -0
- package/dist/commands/handlers/index.d.ts +1 -0
- package/dist/commands/handlers/index.js +2 -0
- 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/slash/builtin-commands.js +20 -0
- 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 +770 -0
- package/dist/config/feature-flags.js +7 -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.d.ts +39 -0
- package/dist/context/context-manager-v2.js +91 -0
- package/dist/daemon/agent-task-executor.js +12 -1
- package/dist/daemon/autonomous-daemon.d.ts +1 -1
- package/dist/daemon/autonomous-daemon.js +5 -3
- package/dist/daemon/autonomous-loop.d.ts +21 -1
- package/dist/daemon/autonomous-loop.js +69 -0
- package/dist/daemon/colab-goal.d.ts +38 -0
- package/dist/daemon/colab-goal.js +81 -0
- 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.d.ts +21 -0
- package/dist/fleet/colab-store.js +28 -0
- package/dist/fleet/peer-session-bridge.d.ts +1 -1
- package/dist/fleet/peer-session-bridge.js +243 -2
- package/dist/fleet/peer-session-store.d.ts +3 -0
- package/dist/fleet/peer-tool-bridge.js +14 -0
- package/dist/fleet/privacy-lint.d.ts +8 -0
- package/dist/fleet/privacy-lint.js +22 -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 +38 -0
- package/dist/goals/goal-judge.js +158 -0
- package/dist/goals/goal-loop.d.ts +37 -0
- package/dist/goals/goal-loop.js +101 -0
- package/dist/goals/goal-manager.d.ts +77 -0
- package/dist/goals/goal-manager.js +289 -0
- package/dist/goals/goal-state.d.ts +99 -0
- package/dist/goals/goal-state.js +315 -0
- package/dist/goals/goal-store.d.ts +28 -0
- package/dist/goals/goal-store.js +87 -0
- package/dist/goals/index.d.ts +5 -0
- package/dist/goals/index.js +6 -0
- package/dist/hooks/use-input-handler.js +36 -1
- package/dist/hooks/user-hooks.js +17 -3
- package/dist/index.js +506 -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/observability/run-store.d.ts +1 -1
- 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.d.ts +13 -1
- package/dist/server/websocket/fleet-bridge.js +16 -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
|
@@ -9,6 +9,16 @@ import { buildHeartbeatStatusReport } from '../../daemon/status-reports.js';
|
|
|
9
9
|
// Heartbeat commands
|
|
10
10
|
// ============================================================================
|
|
11
11
|
export { buildHeartbeatStatusReport };
|
|
12
|
+
function parsePositiveIntegerCliOption(value, flagName) {
|
|
13
|
+
if (value === undefined)
|
|
14
|
+
return undefined;
|
|
15
|
+
const trimmed = value.trim();
|
|
16
|
+
const n = Number(trimmed);
|
|
17
|
+
if (!/^[1-9]\d*$/.test(trimmed) || !Number.isSafeInteger(n)) {
|
|
18
|
+
throw new Error(`${flagName} must be a positive integer`);
|
|
19
|
+
}
|
|
20
|
+
return n;
|
|
21
|
+
}
|
|
12
22
|
export function registerHeartbeatCommands(program) {
|
|
13
23
|
const heartbeat = program
|
|
14
24
|
.command('heartbeat')
|
|
@@ -745,7 +755,7 @@ export function registerFleetAutonomyCommands(program) {
|
|
|
745
755
|
.action(async (opts) => {
|
|
746
756
|
const { createDefaultAutonomousLoop, FleetAutonomousDaemon, watchFleetTasks } = await import('../../daemon/autonomous-daemon.js');
|
|
747
757
|
const path = await import('path');
|
|
748
|
-
const loop = createDefaultAutonomousLoop({
|
|
758
|
+
const loop = await createDefaultAutonomousLoop({
|
|
749
759
|
...(opts.dir ? { dir: opts.dir } : {}),
|
|
750
760
|
...(opts.outputDir ? { outputDir: opts.outputDir } : {}),
|
|
751
761
|
});
|
|
@@ -780,6 +790,86 @@ export function registerFleetAutonomyCommands(program) {
|
|
|
780
790
|
console.log(`\nDone: ${summary.ticks} tick(s), ${JSON.stringify(summary.outcomes)} (${summary.stoppedReason}).`);
|
|
781
791
|
}
|
|
782
792
|
});
|
|
793
|
+
fleet
|
|
794
|
+
.command('bench')
|
|
795
|
+
.description('Benchmark live Tailnet Ollama peers and rank the network model tier')
|
|
796
|
+
.option('--peer <pattern>', 'only benchmark peers whose hostname or IP contains the pattern')
|
|
797
|
+
.option('--models <csv>', 'only benchmark models whose name contains one of the comma-separated patterns')
|
|
798
|
+
.option('--prompt-set <name>', 'prompt set: balanced, coding, or latency', 'coding')
|
|
799
|
+
.option('--runs <n>', 'repeat the prompt set N times per candidate', '1')
|
|
800
|
+
.option('--timeout <ms>', 'request timeout per prompt', '60000')
|
|
801
|
+
.option('--json', 'output JSON')
|
|
802
|
+
.option('--no-write-cache', 'do not persist the score cache used by the router')
|
|
803
|
+
.action(async (opts) => {
|
|
804
|
+
const { TailscaleManager } = await import('../../integrations/tailscale.js');
|
|
805
|
+
const { BENCHMARK_PROMPT_SETS, benchmarkCandidates, writeBenchmarkIndex, defaultBenchmarkIndexPath, } = await import('../../agent/model-benchmark.js');
|
|
806
|
+
const peers = await TailscaleManager.getInstance().discoverOllamaPeers();
|
|
807
|
+
const filteredPeers = opts.peer
|
|
808
|
+
? peers.filter((peer) => peer.hostname.toLowerCase().includes(opts.peer.toLowerCase())
|
|
809
|
+
|| peer.ip.includes(opts.peer.trim()))
|
|
810
|
+
: peers;
|
|
811
|
+
const modelFilters = opts.models
|
|
812
|
+
? opts.models.split(',').map((part) => part.trim()).filter(Boolean)
|
|
813
|
+
: [];
|
|
814
|
+
const candidates = filteredPeers.flatMap((peer) => peer.models
|
|
815
|
+
.filter((model) => modelFilters.length === 0
|
|
816
|
+
? true
|
|
817
|
+
: modelFilters.some((filter) => model.toLowerCase().includes(filter.toLowerCase())))
|
|
818
|
+
.map((model) => ({
|
|
819
|
+
model,
|
|
820
|
+
baseUrl: peer.baseURL,
|
|
821
|
+
label: peer.hostname,
|
|
822
|
+
})));
|
|
823
|
+
if (candidates.length === 0) {
|
|
824
|
+
const message = opts.peer
|
|
825
|
+
? `No Tailnet Ollama peers matched "${opts.peer}".`
|
|
826
|
+
: 'No Tailnet Ollama peers were discovered.';
|
|
827
|
+
if (opts.json) {
|
|
828
|
+
console.log(JSON.stringify({ ok: false, error: message }, null, 2));
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
console.log(message);
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
const suite = BENCHMARK_PROMPT_SETS[opts.promptSet] ? opts.promptSet : 'coding';
|
|
835
|
+
const reports = await benchmarkCandidates(candidates, {
|
|
836
|
+
promptSet: suite,
|
|
837
|
+
runs: Math.max(1, parseInt(opts.runs, 10) || 1),
|
|
838
|
+
timeoutMs: Math.max(5_000, parseInt(opts.timeout, 10) || 60_000),
|
|
839
|
+
});
|
|
840
|
+
const ranked = [...reports].sort((a, b) => b.summary.score - a.summary.score);
|
|
841
|
+
let indexPath;
|
|
842
|
+
if (opts.writeCache !== false) {
|
|
843
|
+
indexPath = defaultBenchmarkIndexPath();
|
|
844
|
+
await writeBenchmarkIndex(ranked, suite, indexPath);
|
|
845
|
+
}
|
|
846
|
+
const best = ranked[0];
|
|
847
|
+
if (opts.json) {
|
|
848
|
+
console.log(JSON.stringify({
|
|
849
|
+
ok: true,
|
|
850
|
+
suite,
|
|
851
|
+
indexPath,
|
|
852
|
+
best,
|
|
853
|
+
reports: ranked,
|
|
854
|
+
}, null, 2));
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
console.log(`\nTailnet Ollama benchmark (${suite})`);
|
|
858
|
+
if (indexPath)
|
|
859
|
+
console.log(` Cache: ${indexPath}`);
|
|
860
|
+
console.log(` Candidates: ${ranked.length}`);
|
|
861
|
+
if (best) {
|
|
862
|
+
console.log(` Best: ${best.candidate.label ?? best.candidate.model} / ${best.candidate.model}`);
|
|
863
|
+
console.log(` Score: ${best.summary.score.toFixed(1)} compliance ${(best.summary.complianceRate * 100).toFixed(0)}% ttft ${Math.round(best.summary.avgTtftMs)}ms total ${Math.round(best.summary.avgTotalMs)}ms`);
|
|
864
|
+
}
|
|
865
|
+
console.log('');
|
|
866
|
+
ranked.forEach((report, index) => {
|
|
867
|
+
console.log(`${String(index + 1).padStart(2, ' ')}. ${report.candidate.label ?? report.candidate.model} / ${report.candidate.model}`);
|
|
868
|
+
console.log(` ${report.candidate.baseUrl}`);
|
|
869
|
+
console.log(` score=${report.summary.score.toFixed(1)} compliance=${(report.summary.complianceRate * 100).toFixed(0)}% ttft=${Math.round(report.summary.avgTtftMs)}ms total=${Math.round(report.summary.avgTotalMs)}ms`);
|
|
870
|
+
});
|
|
871
|
+
console.log('');
|
|
872
|
+
});
|
|
783
873
|
fleet
|
|
784
874
|
.command('status')
|
|
785
875
|
.description('Show the fleet task queue + presence')
|
|
@@ -817,9 +907,20 @@ export function registerFleetAutonomyCommands(program) {
|
|
|
817
907
|
.option('--priority <p>', 'critical | high | medium | low', 'medium')
|
|
818
908
|
.option('--depends-on <ids>', 'comma-separated task ids this task depends on')
|
|
819
909
|
.option('--description <text>', 'task description')
|
|
910
|
+
.option('--goal-mode', 'judge-gated loop: the worker keeps going until an LLM judge confirms the task is done, then blocks for human review when the budget is spent')
|
|
911
|
+
.option('--goal-max-turns <n>', 'goal-mode turn budget (default 5)')
|
|
820
912
|
.option('--dir <path>', 'colab dir')
|
|
821
913
|
.option('--json', 'output JSON')
|
|
822
914
|
.action(async (title, opts) => {
|
|
915
|
+
let goalMaxTurns;
|
|
916
|
+
try {
|
|
917
|
+
goalMaxTurns = parsePositiveIntegerCliOption(opts.goalMaxTurns, '--goal-max-turns');
|
|
918
|
+
}
|
|
919
|
+
catch (error) {
|
|
920
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
921
|
+
process.exit(1);
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
823
924
|
const { FleetColabStore } = await import('../../fleet/colab-store.js');
|
|
824
925
|
const store = new FleetColabStore({ ...(opts.dir ? { dir: opts.dir } : {}) });
|
|
825
926
|
const priority = ['critical', 'high', 'medium', 'low'].find((p) => p === opts.priority) ?? 'medium';
|
|
@@ -828,12 +929,15 @@ export function registerFleetAutonomyCommands(program) {
|
|
|
828
929
|
priority,
|
|
829
930
|
...(opts.description ? { description: opts.description } : {}),
|
|
830
931
|
...(opts.dependsOn ? { dependsOn: opts.dependsOn.split(',').map((s) => s.trim()).filter(Boolean) } : {}),
|
|
932
|
+
...(opts.goalMode ? { goalMode: true } : {}),
|
|
933
|
+
...(goalMaxTurns !== undefined ? { goalMaxTurns } : {}),
|
|
831
934
|
});
|
|
832
935
|
if (opts.json) {
|
|
833
936
|
console.log(JSON.stringify({ task }, null, 2));
|
|
834
937
|
return;
|
|
835
938
|
}
|
|
836
|
-
|
|
939
|
+
const goalNote = task.goalMode ? ` goal-mode(${task.goalMaxTurns ?? 5} turns)` : '';
|
|
940
|
+
console.log(`Added task ${task.id} [${task.priority}]${goalNote}${task.dependsOn ? ` depends on ${task.dependsOn.join(', ')}` : ''}`);
|
|
837
941
|
});
|
|
838
942
|
fleet
|
|
839
943
|
.command('swarm <goal>')
|
|
@@ -1124,7 +1228,7 @@ export function registerCompanionCommands(program) {
|
|
|
1124
1228
|
.option('--no-set-model', 'Do not set the project model to the ChatGPT companion default')
|
|
1125
1229
|
.option('--language <lang>', 'Voice language', 'fr')
|
|
1126
1230
|
.option('--stt-provider <provider>', 'Voice input provider (system, whisper-local, whisper-api)')
|
|
1127
|
-
.option('--tts-provider <provider>', 'TTS provider (edge-tts, espeak, say, piper, audioreader
|
|
1231
|
+
.option('--tts-provider <provider>', 'TTS provider (edge-tts, espeak, say, piper, audioreader)')
|
|
1128
1232
|
.option('--tts-voice <voice>', 'TTS voice name')
|
|
1129
1233
|
.option('--model <model>', 'ChatGPT model to use when OAuth credentials are present')
|
|
1130
1234
|
.action(async (opts) => {
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Extracted from index.ts for modularity.
|
|
5
5
|
*/
|
|
6
6
|
import { resolve } from 'path';
|
|
7
|
+
import { buildOllamaUpdatePlan, fetchOllamaStatus, runOllamaUpdatePlan } from '../ollama.js';
|
|
7
8
|
function resolveCommandDirectory(program) {
|
|
8
9
|
const directory = program.opts().directory;
|
|
9
10
|
return resolve(process.cwd(), directory || process.cwd());
|
|
@@ -93,6 +94,50 @@ export function registerUtilityCommands(program) {
|
|
|
93
94
|
const { runOnboarding } = await import('../../wizard/onboarding.js');
|
|
94
95
|
await runOnboarding();
|
|
95
96
|
});
|
|
97
|
+
const ollamaCommand = program
|
|
98
|
+
.command('ollama')
|
|
99
|
+
.description('Inspect or update the local Ollama runtime');
|
|
100
|
+
ollamaCommand
|
|
101
|
+
.command('status')
|
|
102
|
+
.description('Show local Ollama version and models')
|
|
103
|
+
.option('--url <url>', 'Ollama base URL', process.env.OLLAMA_HOST ?? 'http://localhost:11434')
|
|
104
|
+
.option('--json', 'Output JSON')
|
|
105
|
+
.action(async (options) => {
|
|
106
|
+
const status = await fetchOllamaStatus(options.url);
|
|
107
|
+
if (options.json) {
|
|
108
|
+
console.log(JSON.stringify(status, null, 2));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
console.log('\nOllama status\n');
|
|
112
|
+
console.log(` Base URL: ${status.baseUrl}`);
|
|
113
|
+
console.log(` Reachable: ${status.reachable ? 'YES' : 'NO'}`);
|
|
114
|
+
console.log(` Version: ${status.version ?? '(unknown)'}`);
|
|
115
|
+
console.log(` Models: ${status.models.length > 0 ? status.models.join(', ') : '(none)'}`);
|
|
116
|
+
if (status.error) {
|
|
117
|
+
console.log(` Error: ${status.error}`);
|
|
118
|
+
}
|
|
119
|
+
console.log('');
|
|
120
|
+
});
|
|
121
|
+
ollamaCommand
|
|
122
|
+
.command('update')
|
|
123
|
+
.description('Run the official Ollama Windows update script')
|
|
124
|
+
.option('--script-url <url>', 'Official installer/update script URL', 'https://ollama.com/install.ps1')
|
|
125
|
+
.option('--repo-root <dir>', 'Repository root', resolveCommandDirectory(program))
|
|
126
|
+
.action(async (options) => {
|
|
127
|
+
const plan = buildOllamaUpdatePlan({
|
|
128
|
+
repoRoot: options.repoRoot,
|
|
129
|
+
scriptUrl: options.scriptUrl,
|
|
130
|
+
});
|
|
131
|
+
if (!plan.supported) {
|
|
132
|
+
console.error(plan.message);
|
|
133
|
+
console.error(`Use the update script directly on Windows: ${plan.scriptPath}`);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
console.log(`Running Ollama updater: ${plan.scriptPath}`);
|
|
138
|
+
await runOllamaUpdatePlan(plan);
|
|
139
|
+
console.log('Ollama update completed.');
|
|
140
|
+
});
|
|
96
141
|
// Webhook command
|
|
97
142
|
const webhookCommand = program
|
|
98
143
|
.command('webhook')
|
|
@@ -62,6 +62,8 @@ handleFastMode,
|
|
|
62
62
|
handleBtw, setBtwClient,
|
|
63
63
|
// Heartbeat handler (V4.x — wire user-facing activation of HeartbeatEngine)
|
|
64
64
|
handleHeartbeat,
|
|
65
|
+
// Goal handler (Hermes Agent parity — Ralph loop)
|
|
66
|
+
handleGoal, handleSubgoal,
|
|
65
67
|
// Daily reset handler (audit OpenClaw heritage activation)
|
|
66
68
|
handleDailyReset,
|
|
67
69
|
// Team session handler — slash /share (audit OpenClaw heritage activation, TeamSessionManager wake)
|
|
@@ -337,6 +339,9 @@ export class EnhancedCommandHandler {
|
|
|
337
339
|
['__BTW__', (args) => handleBtw(args)],
|
|
338
340
|
// Heartbeat engine activation (fleet AUTONOMOUS-FLEET-PROTOCOL v0.1)
|
|
339
341
|
['__HEARTBEAT__', (args) => handleHeartbeat(args)],
|
|
342
|
+
// Standing goal — judge + auto-continue loop (Hermes Agent parity)
|
|
343
|
+
['__GOAL__', (args) => handleGoal(args, { client: this.codebuddyClient })],
|
|
344
|
+
['__SUBGOAL__', (args) => handleSubgoal(args)],
|
|
340
345
|
// Daily reset scheduler (audit OpenClaw heritage activation)
|
|
341
346
|
['__DAILY_RESET__', (args) => handleDailyReset(args)],
|
|
342
347
|
// Team session manager — slash /share (audit OpenClaw heritage, TeamSessionManager wake)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* buddy goal — headless Ralph loop.
|
|
3
|
+
*
|
|
4
|
+
* Runs the full agentic loop toward a standing goal: each turn the agent
|
|
5
|
+
* works with tools, then the goal judge decides done/continue. Continuation
|
|
6
|
+
* prompts are fed back in-process until the goal is achieved, the turn
|
|
7
|
+
* budget is exhausted, or the judge auto-pauses.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* buddy goal "Fix every failing test in tests/auth/"
|
|
11
|
+
* buddy goal "Ship the feature" --max-turns 10 --judge-model qwen3:8b
|
|
12
|
+
*
|
|
13
|
+
* Exit codes: 0 = goal done, 1 = paused (budget/judge) or error.
|
|
14
|
+
*/
|
|
15
|
+
import { Command } from 'commander';
|
|
16
|
+
import type { ChatEntry } from '../agent/codebuddy-agent.js';
|
|
17
|
+
import type { CodeBuddyClient } from '../codebuddy/client.js';
|
|
18
|
+
import { type GoalJudgeProviderInfo } from '../goals/goal-judge-client.js';
|
|
19
|
+
import { GoalStatus } from '../goals/goal-state.js';
|
|
20
|
+
/** The slice of CodeBuddyAgent the loop needs — injectable for tests. */
|
|
21
|
+
export interface GoalLoopAgent {
|
|
22
|
+
processUserMessage(input: string): Promise<ChatEntry[]>;
|
|
23
|
+
getClient(): CodeBuddyClient;
|
|
24
|
+
}
|
|
25
|
+
export interface GoalCommandRuntimeAgent extends GoalLoopAgent {
|
|
26
|
+
systemPromptReady: Promise<unknown>;
|
|
27
|
+
setSystemPrompt?: (prompt: string) => void;
|
|
28
|
+
dispose?: () => void | Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
export interface GoalLoopRunOptions {
|
|
31
|
+
maxTurns?: number;
|
|
32
|
+
/** Optional standalone judge client. Defaults to the agent client. */
|
|
33
|
+
judgeClient?: CodeBuddyClient;
|
|
34
|
+
/** Progress sink (status lines ⊙/↻/✓/⏸). Defaults to silent. */
|
|
35
|
+
onMessage?: (text: string) => void;
|
|
36
|
+
}
|
|
37
|
+
export interface GoalLoopRunResult {
|
|
38
|
+
status: GoalStatus | 'unknown';
|
|
39
|
+
turnsUsed: number;
|
|
40
|
+
lastReason?: string;
|
|
41
|
+
}
|
|
42
|
+
export interface GoalCommandRuntimeOptions {
|
|
43
|
+
maxTurns?: number;
|
|
44
|
+
judgeModel?: string;
|
|
45
|
+
provider: GoalJudgeProviderInfo;
|
|
46
|
+
workingDirectory?: string;
|
|
47
|
+
onMessage?: (text: string) => void;
|
|
48
|
+
createJudgeClient?: typeof createStandaloneJudgeClient;
|
|
49
|
+
runLoop?: typeof runGoalLoop;
|
|
50
|
+
}
|
|
51
|
+
export declare const LOCAL_GOAL_ACTOR_SYSTEM_PROMPT: string;
|
|
52
|
+
export declare function buildLocalGoalActorSystemPrompt(workingDirectory?: string): string;
|
|
53
|
+
export declare function shouldUseLocalGoalActorPrompt(provider: GoalJudgeProviderInfo): boolean;
|
|
54
|
+
export declare function resolveLocalGoalActorSystemPrompt(provider: GoalJudgeProviderInfo, workingDirectory?: string): string | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Drive the goal loop headlessly on an in-process agent. Sets the goal,
|
|
57
|
+
* runs the first turn with the goal text (mirroring the interactive
|
|
58
|
+
* `/goal <text>` kick-off), then follows judge verdicts until the loop
|
|
59
|
+
* stops continuing.
|
|
60
|
+
*/
|
|
61
|
+
export declare function runGoalLoop(agent: GoalLoopAgent, goalText: string, options?: GoalLoopRunOptions): Promise<GoalLoopRunResult>;
|
|
62
|
+
declare function createStandaloneJudgeClient(judgeModel: string | undefined, agentProvider: GoalJudgeProviderInfo, currentClient: CodeBuddyClient): Promise<CodeBuddyClient | undefined>;
|
|
63
|
+
export declare function runGoalCommandWithAgent(agent: GoalCommandRuntimeAgent, goal: string, options: GoalCommandRuntimeOptions): Promise<GoalLoopRunResult>;
|
|
64
|
+
export declare function resolveGoalCliJudgeModel(explicitJudgeModel: string | undefined): string | undefined;
|
|
65
|
+
export declare function resolveGoalCliMaxToolRounds(commandValue: number | undefined, command?: Command): number;
|
|
66
|
+
export declare function resolveGoalCliWorkingDirectory(command?: Command): string | undefined;
|
|
67
|
+
export declare function applyGoalCliWorkingDirectory(command?: Command): string;
|
|
68
|
+
export declare function parsePositiveIntegerOption(value: string, optionName: string): number;
|
|
69
|
+
export declare function validateGoalCommandNumericOptions(argv: readonly string[]): void;
|
|
70
|
+
export declare function createGoalCommand(): Command;
|
|
71
|
+
export {};
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* buddy goal — headless Ralph loop.
|
|
3
|
+
*
|
|
4
|
+
* Runs the full agentic loop toward a standing goal: each turn the agent
|
|
5
|
+
* works with tools, then the goal judge decides done/continue. Continuation
|
|
6
|
+
* prompts are fed back in-process until the goal is achieved, the turn
|
|
7
|
+
* budget is exhausted, or the judge auto-pauses.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* buddy goal "Fix every failing test in tests/auth/"
|
|
11
|
+
* buddy goal "Ship the feature" --max-turns 10 --judge-model qwen3:8b
|
|
12
|
+
*
|
|
13
|
+
* Exit codes: 0 = goal done, 1 = paused (budget/judge) or error.
|
|
14
|
+
*/
|
|
15
|
+
import { Command, InvalidArgumentError } from 'commander';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
import { resolveGoalJudgeClient } from '../goals/goal-judge-client.js';
|
|
18
|
+
import { maybeContinueGoalAfterTurn } from '../goals/goal-loop.js';
|
|
19
|
+
import { getGoalManager, resolveGoalsConfig } from '../goals/goal-manager.js';
|
|
20
|
+
import { resolveCommandProvider } from './llm-provider-resolution.js';
|
|
21
|
+
const TOOL_RESULT_SNIPPET_CHARS = 1600;
|
|
22
|
+
export const LOCAL_GOAL_ACTOR_SYSTEM_PROMPT = 'You are Code Buddy running in headless goal mode on a local model. ' +
|
|
23
|
+
'Work only toward the user standing goal. Use available tools when they are necessary, ' +
|
|
24
|
+
'then report concrete progress or the final result. For simple answer-only goals, ' +
|
|
25
|
+
'output exactly the requested answer and no extra text. Do not greet the user, add markdown, ' +
|
|
26
|
+
'or explain unless the goal asks for it.';
|
|
27
|
+
export function buildLocalGoalActorSystemPrompt(workingDirectory = process.cwd()) {
|
|
28
|
+
return `${LOCAL_GOAL_ACTOR_SYSTEM_PROMPT}
|
|
29
|
+
|
|
30
|
+
Workspace:
|
|
31
|
+
- Current project directory: ${workingDirectory}
|
|
32
|
+
- Use paths relative to the current project directory whenever possible.
|
|
33
|
+
- Do not invent container paths such as /workspace or root paths such as /src unless a tool result proves they exist.`;
|
|
34
|
+
}
|
|
35
|
+
export function shouldUseLocalGoalActorPrompt(provider) {
|
|
36
|
+
const label = provider.providerLabel?.toLowerCase() ?? '';
|
|
37
|
+
const baseURL = provider.baseURL?.toLowerCase() ?? '';
|
|
38
|
+
return provider.apiKey === 'ollama' || label.includes('ollama') || baseURL.includes(':11434');
|
|
39
|
+
}
|
|
40
|
+
export function resolveLocalGoalActorSystemPrompt(provider, workingDirectory = process.cwd()) {
|
|
41
|
+
return shouldUseLocalGoalActorPrompt(provider)
|
|
42
|
+
? buildLocalGoalActorSystemPrompt(workingDirectory)
|
|
43
|
+
: undefined;
|
|
44
|
+
}
|
|
45
|
+
function truncateGoalTurnPart(text) {
|
|
46
|
+
if (text.length <= TOOL_RESULT_SNIPPET_CHARS)
|
|
47
|
+
return text;
|
|
48
|
+
return `${text.slice(0, TOOL_RESULT_SNIPPET_CHARS)}... [truncated]`;
|
|
49
|
+
}
|
|
50
|
+
function buildGoalTurnSummary(entries) {
|
|
51
|
+
const parts = [];
|
|
52
|
+
let hasToolResult = false;
|
|
53
|
+
for (const entry of entries) {
|
|
54
|
+
if (entry.type === 'assistant' && entry.content.trim()) {
|
|
55
|
+
parts.push(entry.content.trim());
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (entry.type !== 'tool_result')
|
|
59
|
+
continue;
|
|
60
|
+
hasToolResult = true;
|
|
61
|
+
const toolOutput = (entry.toolResult?.success ? entry.toolResult.output : entry.toolResult?.error) ??
|
|
62
|
+
entry.content;
|
|
63
|
+
const trimmedOutput = String(toolOutput ?? '').trim();
|
|
64
|
+
if (!trimmedOutput)
|
|
65
|
+
continue;
|
|
66
|
+
const toolName = entry.toolCall?.function?.name ?? 'tool';
|
|
67
|
+
const status = entry.toolResult ? (entry.toolResult.success ? 'success' : 'error') : 'result';
|
|
68
|
+
parts.push(`[tool:${toolName} ${status}]\n${truncateGoalTurnPart(trimmedOutput)}`);
|
|
69
|
+
}
|
|
70
|
+
const summary = parts.join('\n\n');
|
|
71
|
+
if (summary && !hasToolResult) {
|
|
72
|
+
return `[tool evidence: none]\n\n${summary}`;
|
|
73
|
+
}
|
|
74
|
+
return summary;
|
|
75
|
+
}
|
|
76
|
+
function buildGoalRunSummary(turnSummaries) {
|
|
77
|
+
return turnSummaries
|
|
78
|
+
.map((summary, index) => `[Goal turn ${index + 1}]\n${summary}`)
|
|
79
|
+
.join('\n\n');
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Drive the goal loop headlessly on an in-process agent. Sets the goal,
|
|
83
|
+
* runs the first turn with the goal text (mirroring the interactive
|
|
84
|
+
* `/goal <text>` kick-off), then follows judge verdicts until the loop
|
|
85
|
+
* stops continuing.
|
|
86
|
+
*/
|
|
87
|
+
export async function runGoalLoop(agent, goalText, options = {}) {
|
|
88
|
+
const manager = getGoalManager();
|
|
89
|
+
const state = manager.set(goalText, options.maxTurns !== undefined ? { maxTurns: options.maxTurns } : {});
|
|
90
|
+
const emit = options.onMessage ?? (() => { });
|
|
91
|
+
const judgeClient = options.judgeClient ?? agent.getClient();
|
|
92
|
+
emit(`⊙ Goal set (${state.maxTurns}-turn budget): ${state.goal}`);
|
|
93
|
+
let prompt = state.goal;
|
|
94
|
+
const turnSummaries = [];
|
|
95
|
+
// Hard backstop on top of the manager's own budget/auto-pause guards.
|
|
96
|
+
const maxIterations = state.maxTurns + 1;
|
|
97
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
98
|
+
const entries = await agent.processUserMessage(prompt);
|
|
99
|
+
const lastResponse = buildGoalTurnSummary(entries);
|
|
100
|
+
if (lastResponse.trim()) {
|
|
101
|
+
turnSummaries.push(lastResponse);
|
|
102
|
+
}
|
|
103
|
+
const outcome = await maybeContinueGoalAfterTurn({
|
|
104
|
+
client: judgeClient,
|
|
105
|
+
lastResponse: lastResponse.trim() ? buildGoalRunSummary(turnSummaries) : lastResponse,
|
|
106
|
+
interrupted: false,
|
|
107
|
+
});
|
|
108
|
+
if (outcome?.message)
|
|
109
|
+
emit(outcome.message);
|
|
110
|
+
if (!outcome && !lastResponse.trim() && manager.isActive()) {
|
|
111
|
+
manager.pause('empty response (nothing to evaluate)');
|
|
112
|
+
emit('⏸ Goal paused — the agent produced no judgeable response.');
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
if (!outcome?.continuationPrompt)
|
|
116
|
+
break;
|
|
117
|
+
prompt = outcome.continuationPrompt;
|
|
118
|
+
}
|
|
119
|
+
const final = manager.state;
|
|
120
|
+
return {
|
|
121
|
+
status: final?.status ?? 'unknown',
|
|
122
|
+
turnsUsed: final?.turnsUsed ?? 0,
|
|
123
|
+
...(final?.lastReason ? { lastReason: final.lastReason } : {}),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
async function createStandaloneJudgeClient(judgeModel, agentProvider, currentClient) {
|
|
127
|
+
const resolved = await resolveGoalJudgeClient(currentClient, judgeModel, agentProvider);
|
|
128
|
+
return resolved === currentClient ? undefined : resolved;
|
|
129
|
+
}
|
|
130
|
+
export async function runGoalCommandWithAgent(agent, goal, options) {
|
|
131
|
+
try {
|
|
132
|
+
const createJudgeClient = options.createJudgeClient ?? createStandaloneJudgeClient;
|
|
133
|
+
const runLoop = options.runLoop ?? runGoalLoop;
|
|
134
|
+
const judgeClient = await createJudgeClient(options.judgeModel, options.provider, agent.getClient());
|
|
135
|
+
await agent.systemPromptReady;
|
|
136
|
+
if (shouldUseLocalGoalActorPrompt(options.provider)) {
|
|
137
|
+
agent.setSystemPrompt?.(buildLocalGoalActorSystemPrompt(options.workingDirectory));
|
|
138
|
+
}
|
|
139
|
+
return await runLoop(agent, goal, {
|
|
140
|
+
...(options.maxTurns !== undefined ? { maxTurns: options.maxTurns } : {}),
|
|
141
|
+
...(judgeClient ? { judgeClient } : {}),
|
|
142
|
+
...(options.onMessage ? { onMessage: options.onMessage } : {}),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
finally {
|
|
146
|
+
await Promise.resolve(agent.dispose?.());
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
export function resolveGoalCliJudgeModel(explicitJudgeModel) {
|
|
150
|
+
const explicit = explicitJudgeModel?.trim();
|
|
151
|
+
if (explicit)
|
|
152
|
+
return explicit;
|
|
153
|
+
const configured = resolveGoalsConfig().judgeModel.trim();
|
|
154
|
+
return configured || undefined;
|
|
155
|
+
}
|
|
156
|
+
export function resolveGoalCliMaxToolRounds(commandValue, command) {
|
|
157
|
+
const commandSource = command?.getOptionValueSource?.('maxToolRounds');
|
|
158
|
+
if (commandSource && commandSource !== 'default') {
|
|
159
|
+
return commandValue ?? 50;
|
|
160
|
+
}
|
|
161
|
+
const parentSource = command?.parent?.getOptionValueSource?.('maxToolRounds');
|
|
162
|
+
const parentValue = command?.optsWithGlobals?.()?.maxToolRounds;
|
|
163
|
+
if (parentSource && parentSource !== 'default' && parentValue !== undefined) {
|
|
164
|
+
return parsePositiveIntegerOption(String(parentValue), '--max-tool-rounds');
|
|
165
|
+
}
|
|
166
|
+
return commandValue ?? 50;
|
|
167
|
+
}
|
|
168
|
+
export function resolveGoalCliWorkingDirectory(command) {
|
|
169
|
+
const value = command?.optsWithGlobals?.()?.directory;
|
|
170
|
+
if (typeof value !== 'string')
|
|
171
|
+
return undefined;
|
|
172
|
+
const directory = value.trim();
|
|
173
|
+
if (!directory)
|
|
174
|
+
return undefined;
|
|
175
|
+
return path.resolve(process.cwd(), directory);
|
|
176
|
+
}
|
|
177
|
+
export function applyGoalCliWorkingDirectory(command) {
|
|
178
|
+
const directory = resolveGoalCliWorkingDirectory(command);
|
|
179
|
+
if (directory) {
|
|
180
|
+
process.chdir(directory);
|
|
181
|
+
}
|
|
182
|
+
return process.cwd();
|
|
183
|
+
}
|
|
184
|
+
async function loadGoalCliEnv(directory) {
|
|
185
|
+
const dotenv = await import('dotenv');
|
|
186
|
+
dotenv.config({ path: path.join(directory, '.env') });
|
|
187
|
+
}
|
|
188
|
+
async function prepareGoalCliWorkspace(command) {
|
|
189
|
+
const launchDirectory = process.cwd();
|
|
190
|
+
await loadGoalCliEnv(launchDirectory);
|
|
191
|
+
const cwd = applyGoalCliWorkingDirectory(command);
|
|
192
|
+
if (cwd !== launchDirectory) {
|
|
193
|
+
await loadGoalCliEnv(cwd);
|
|
194
|
+
}
|
|
195
|
+
const opts = command?.optsWithGlobals?.();
|
|
196
|
+
const { initializeWorkspaceIsolation } = await import('../workspace/workspace-isolation.js');
|
|
197
|
+
initializeWorkspaceIsolation({
|
|
198
|
+
allowOutside: opts?.allowOutside,
|
|
199
|
+
directory: cwd,
|
|
200
|
+
additionalPaths: opts?.addDir,
|
|
201
|
+
});
|
|
202
|
+
return cwd;
|
|
203
|
+
}
|
|
204
|
+
export function parsePositiveIntegerOption(value, optionName) {
|
|
205
|
+
const trimmed = value.trim();
|
|
206
|
+
const n = Number(trimmed);
|
|
207
|
+
if (!/^[1-9]\d*$/.test(trimmed) || !Number.isSafeInteger(n)) {
|
|
208
|
+
throw new InvalidArgumentError(`${optionName} must be a positive integer`);
|
|
209
|
+
}
|
|
210
|
+
return n;
|
|
211
|
+
}
|
|
212
|
+
export function validateGoalCommandNumericOptions(argv) {
|
|
213
|
+
for (let i = 2; i < argv.length; i++) {
|
|
214
|
+
const arg = argv[i];
|
|
215
|
+
if (arg === undefined)
|
|
216
|
+
continue;
|
|
217
|
+
if (arg === '--')
|
|
218
|
+
break;
|
|
219
|
+
if (arg === '--max-turns' || arg === '--max-tool-rounds') {
|
|
220
|
+
const value = argv[i + 1];
|
|
221
|
+
if (value === undefined)
|
|
222
|
+
continue;
|
|
223
|
+
parsePositiveIntegerOption(value, arg);
|
|
224
|
+
i++;
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
const eqIndex = arg?.indexOf('=') ?? -1;
|
|
228
|
+
if (eqIndex <= 0)
|
|
229
|
+
continue;
|
|
230
|
+
const optionName = arg.slice(0, eqIndex);
|
|
231
|
+
if (optionName === '--max-turns' || optionName === '--max-tool-rounds') {
|
|
232
|
+
parsePositiveIntegerOption(arg.slice(eqIndex + 1), optionName);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
export function createGoalCommand() {
|
|
237
|
+
const cmd = new Command('goal')
|
|
238
|
+
.description('Run the agent toward a standing goal until a judge model confirms it is done (Ralph loop)')
|
|
239
|
+
.argument('<goal>', 'The goal to pursue')
|
|
240
|
+
.option('--max-turns <n>', 'Turn budget (default 20, or goals.maxTurns from settings)', value => parsePositiveIntegerOption(value, '--max-turns'))
|
|
241
|
+
.option('--judge-model <model>', 'Model for the goal judge (default: session model)')
|
|
242
|
+
.option('-m, --model <model>', 'Override the agent model for this run')
|
|
243
|
+
.option('--max-tool-rounds <n>', 'Max tool rounds per turn', value => parsePositiveIntegerOption(value, '--max-tool-rounds'), 50)
|
|
244
|
+
.action(async (goal, options, command) => {
|
|
245
|
+
try {
|
|
246
|
+
const cwd = await prepareGoalCliWorkspace(command);
|
|
247
|
+
const modelOverride = options.model ?? command?.optsWithGlobals?.()?.model;
|
|
248
|
+
const resolved = resolveCommandProvider({ explicitModel: modelOverride });
|
|
249
|
+
if (!resolved) {
|
|
250
|
+
console.error('Error: No provider available — set an API key, run `buddy onboard`, or point CODEBUDDY_PROVIDER=ollama at a local Ollama.');
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
if (options.judgeModel) {
|
|
254
|
+
process.env.CODEBUDDY_GOAL_JUDGE_MODEL = options.judgeModel;
|
|
255
|
+
}
|
|
256
|
+
const judgeModel = resolveGoalCliJudgeModel(options.judgeModel);
|
|
257
|
+
const maxToolRounds = resolveGoalCliMaxToolRounds(options.maxToolRounds, command);
|
|
258
|
+
process.env.CODEBUDDY_DISABLE_MCP = process.env.CODEBUDDY_DISABLE_MCP ?? 'true';
|
|
259
|
+
process.env.CODEBUDDY_HEADLESS = 'true';
|
|
260
|
+
const { CodeBuddyAgent } = await import('../agent/codebuddy-agent.js');
|
|
261
|
+
const { ConfirmationService } = await import('../utils/confirmation-service.js');
|
|
262
|
+
ConfirmationService.getInstance().setSessionFlag('allOperations', true);
|
|
263
|
+
const agent = new CodeBuddyAgent(resolved.apiKey, resolved.baseURL, resolved.model, maxToolRounds, true, undefined, cwd, undefined, resolveLocalGoalActorSystemPrompt(resolved, cwd));
|
|
264
|
+
const result = await runGoalCommandWithAgent(agent, goal, {
|
|
265
|
+
provider: resolved,
|
|
266
|
+
judgeModel,
|
|
267
|
+
workingDirectory: cwd,
|
|
268
|
+
...(options.maxTurns !== undefined ? { maxTurns: options.maxTurns } : {}),
|
|
269
|
+
onMessage: text => console.log(`\n${text}`),
|
|
270
|
+
});
|
|
271
|
+
process.exit(result.status === 'done' ? 0 : 1);
|
|
272
|
+
}
|
|
273
|
+
catch (err) {
|
|
274
|
+
console.error('Goal error:', err instanceof Error ? err.message : err);
|
|
275
|
+
process.exit(1);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
return cmd;
|
|
279
|
+
}
|
|
280
|
+
//# sourceMappingURL=goal-cli.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { CodeBuddyClient } from '../../codebuddy/client.js';
|
|
2
|
+
import { type GoalPlan } from '../../goals/goal-decomposer.js';
|
|
3
|
+
import { CommandHandlerResult } from './branch-handlers.js';
|
|
4
|
+
export interface GoalHandlerOptions {
|
|
5
|
+
sessionKey?: string;
|
|
6
|
+
client?: CodeBuddyClient | null;
|
|
7
|
+
planner?: (goal: string, client: CodeBuddyClient) => Promise<GoalPlan | null>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* /goal — standing goal with judge + auto-continue loop (the Ralph loop,
|
|
11
|
+
* ported from Hermes Agent).
|
|
12
|
+
*
|
|
13
|
+
* Forms:
|
|
14
|
+
* /goal <text> set a new goal (replaces any old one) and start turn 1
|
|
15
|
+
* /goal | status show current state
|
|
16
|
+
* /goal pause halt auto-continuation, keep the goal
|
|
17
|
+
* /goal resume restart the loop (resets the turn budget)
|
|
18
|
+
* /goal clear discard the goal (aliases: stop, done)
|
|
19
|
+
*/
|
|
20
|
+
export declare function handleGoal(args: string[], options?: GoalHandlerOptions): Promise<CommandHandlerResult>;
|
|
21
|
+
/**
|
|
22
|
+
* /subgoal — extra acceptance criteria added mid-loop.
|
|
23
|
+
*
|
|
24
|
+
* Forms:
|
|
25
|
+
* /subgoal show current subgoals
|
|
26
|
+
* /subgoal <text> append a criterion
|
|
27
|
+
* /subgoal remove <n> drop subgoal n (1-based)
|
|
28
|
+
* /subgoal clear wipe all subgoals
|
|
29
|
+
*
|
|
30
|
+
* Subgoals get appended to both the judge prompt (verdict must consider
|
|
31
|
+
* them) and the continuation prompt (agent sees them) on the next turn
|
|
32
|
+
* boundary — no special kick needed.
|
|
33
|
+
*/
|
|
34
|
+
export declare function handleSubgoal(args: string[], options?: GoalHandlerOptions): Promise<CommandHandlerResult>;
|