@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
|
@@ -56,6 +56,13 @@ const DEFAULT_FEATURE_FLAGS = {
|
|
|
56
56
|
category: 'ai',
|
|
57
57
|
envOverride: 'USER_MODEL_DIALECTIC_ON_SESSION_END',
|
|
58
58
|
},
|
|
59
|
+
SESSION_END_FLUSH: {
|
|
60
|
+
name: 'SESSION_END_FLUSH',
|
|
61
|
+
enabled: true,
|
|
62
|
+
description: 'At session end, write a short handoff (.codebuddy/HANDOFF.md) and propose review-gated lesson candidates from the transcript (WS3-T1)',
|
|
63
|
+
category: 'ai',
|
|
64
|
+
envOverride: 'CODEBUDDY_SESSION_END_FLUSH',
|
|
65
|
+
},
|
|
59
66
|
VOICE_CONTROL: {
|
|
60
67
|
name: 'VOICE_CONTROL',
|
|
61
68
|
enabled: false,
|
|
@@ -405,13 +405,13 @@ const DEFAULT_MODEL_CONFIGS = [
|
|
|
405
405
|
patchFormat: 'full_file',
|
|
406
406
|
promptProfile: 'lite',
|
|
407
407
|
},
|
|
408
|
-
// Gemma 4 (Ollama, e2b/e4b) — local
|
|
409
|
-
// profile
|
|
410
|
-
//
|
|
408
|
+
// Gemma 4 (Ollama, e2b/e4b) — local model with tool support. Keep the
|
|
409
|
+
// lite prompt/tool profile, but do not gate off tool calls: real `buddy goal`
|
|
410
|
+
// smoke with gemma4:12b only succeeds when the selected tools are preserved.
|
|
411
411
|
{
|
|
412
412
|
model: 'gemma4*',
|
|
413
413
|
supportsReasoning: false,
|
|
414
|
-
supportsToolCalls:
|
|
414
|
+
supportsToolCalls: true,
|
|
415
415
|
supportsVision: false,
|
|
416
416
|
contextWindow: 8192,
|
|
417
417
|
maxOutputTokens: 2048,
|
|
@@ -122,6 +122,20 @@ export interface ModelPairsConfig {
|
|
|
122
122
|
/** Model used for code edits and execution (the "doer") */
|
|
123
123
|
editor?: string;
|
|
124
124
|
}
|
|
125
|
+
/** Active-LLM registry: auto-failover across the user's live logins + "together". */
|
|
126
|
+
export interface LlmFailoverConfig {
|
|
127
|
+
/** Master gate. OFF by default — single-provider behavior is unchanged. */
|
|
128
|
+
enabled?: boolean;
|
|
129
|
+
/** Fallback ordering: 'resilience' (capable/subscription first, local last),
|
|
130
|
+
* 'free-first' (cheapest first), or 'manual' (explicit `manualOrder`). */
|
|
131
|
+
order?: 'resilience' | 'free-first' | 'manual';
|
|
132
|
+
/** Provider ids in preferred order when `order: 'manual'`. */
|
|
133
|
+
manualOrder?: string[];
|
|
134
|
+
/** Restrict the active set to local runtimes (privacy). */
|
|
135
|
+
local_only?: boolean;
|
|
136
|
+
/** Default strategy for `/llm ensemble` (Phase 2). */
|
|
137
|
+
together_strategy?: 'ensemble' | 'consensus' | 'fastest' | 'cascade';
|
|
138
|
+
}
|
|
125
139
|
/**
|
|
126
140
|
* External integrations configuration
|
|
127
141
|
*/
|
|
@@ -497,6 +511,8 @@ export interface CodeBuddyConfig {
|
|
|
497
511
|
integrations: IntegrationsConfig;
|
|
498
512
|
/** Model pairs for architect/editor split */
|
|
499
513
|
model_pairs?: ModelPairsConfig;
|
|
514
|
+
/** Active-LLM registry: auto-failover across live logins + "together" */
|
|
515
|
+
llm?: LlmFailoverConfig;
|
|
500
516
|
/** Agent defaults (model preferences) — Native Engine v2026.3.14 */
|
|
501
517
|
agent_defaults?: AgentDefaultsConfig;
|
|
502
518
|
/** Advisor tool settings (second opinion model) — V4.1 */
|
|
@@ -540,6 +540,9 @@ class ConfigManager {
|
|
|
540
540
|
if (partial.model_pairs) {
|
|
541
541
|
this.config.model_pairs = { ...this.config.model_pairs, ...partial.model_pairs };
|
|
542
542
|
}
|
|
543
|
+
if (partial.llm) {
|
|
544
|
+
this.config.llm = { ...this.config.llm, ...partial.llm };
|
|
545
|
+
}
|
|
543
546
|
}
|
|
544
547
|
/**
|
|
545
548
|
* Get current config
|
|
@@ -60,6 +60,23 @@ export interface ContextStats {
|
|
|
60
60
|
isNearLimit: boolean;
|
|
61
61
|
isCritical: boolean;
|
|
62
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Periodic memory snapshot (WS3-T2) — a compact, persisted view of the
|
|
65
|
+
* session so very long runs (12–15 h) survive crashes and aggressive
|
|
66
|
+
* compaction without losing the thread.
|
|
67
|
+
*/
|
|
68
|
+
export interface ContextSnapshot {
|
|
69
|
+
sessionId: string;
|
|
70
|
+
takenAt: string;
|
|
71
|
+
stats: {
|
|
72
|
+
messageCount: number;
|
|
73
|
+
tokenCount: number;
|
|
74
|
+
compressionCount: number;
|
|
75
|
+
totalTokensSaved: number;
|
|
76
|
+
};
|
|
77
|
+
/** Extractive, privacy-linted summary of the conversation so far. */
|
|
78
|
+
summary: string;
|
|
79
|
+
}
|
|
63
80
|
/**
|
|
64
81
|
* Memory metrics for monitoring context manager health
|
|
65
82
|
*/
|
|
@@ -125,6 +142,10 @@ export declare class ContextManagerV2 {
|
|
|
125
142
|
private _cachedStatsFingerprint;
|
|
126
143
|
/** Last compression timestamp */
|
|
127
144
|
private lastCompressionTime;
|
|
145
|
+
/** WS3-T2 — periodic snapshot timer (unref'd, never keeps the process alive) */
|
|
146
|
+
private snapshotTimer;
|
|
147
|
+
/** WS3-T2 — snapshots taken this session */
|
|
148
|
+
private snapshotCount;
|
|
128
149
|
static readonly DEFAULT_CONFIG: ContextManagerConfig;
|
|
129
150
|
constructor(config?: Partial<ContextManagerConfig>);
|
|
130
151
|
/**
|
|
@@ -344,6 +365,24 @@ export declare class ContextManagerV2 {
|
|
|
344
365
|
* Format compression statistics as human-readable string
|
|
345
366
|
*/
|
|
346
367
|
formatCompressionStats(): string;
|
|
368
|
+
/**
|
|
369
|
+
* Take a compact snapshot of the session and persist it to
|
|
370
|
+
* `.codebuddy/context-snapshot.json` (latest wins). Returns null when the
|
|
371
|
+
* conversation is too small to be worth snapshotting.
|
|
372
|
+
*
|
|
373
|
+
* The summary is privacy-linted before it touches disk (WS3 guard-rail).
|
|
374
|
+
*/
|
|
375
|
+
takeSnapshot(messages: CodeBuddyMessage[], workDir?: string): ContextSnapshot | null;
|
|
376
|
+
/**
|
|
377
|
+
* Start the periodic snapshot loop for long sessions (12–15 h).
|
|
378
|
+
*
|
|
379
|
+
* Interval resolution: explicit param → `CODEBUDDY_SNAPSHOT_INTERVAL_MIN`
|
|
380
|
+
* env (minutes) → 45 min default. `0` (or negative) disables. The timer
|
|
381
|
+
* is unref'd so it never keeps a finished process alive.
|
|
382
|
+
*/
|
|
383
|
+
startPeriodicSnapshot(getMessages: () => CodeBuddyMessage[], intervalMs?: number, workDir?: string): void;
|
|
384
|
+
/** Stop the periodic snapshot loop (idempotent). */
|
|
385
|
+
stopPeriodicSnapshot(): void;
|
|
347
386
|
}
|
|
348
387
|
export type { KeyInformation, ContextArchive, CompressionMetrics, EnhancedCompressionResult, };
|
|
349
388
|
/**
|
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
* - Content-type-aware Compression
|
|
13
13
|
* - Key Information Preservation
|
|
14
14
|
*/
|
|
15
|
+
import * as fs from 'fs';
|
|
16
|
+
import * as path from 'path';
|
|
17
|
+
import { redactSecrets } from '../fleet/privacy-lint.js';
|
|
15
18
|
import { createTokenCounter } from './token-counter.js';
|
|
16
19
|
import { logger } from '../utils/logger.js';
|
|
17
20
|
import { getModelToolConfig } from '../config/model-tools.js';
|
|
@@ -74,6 +77,10 @@ export class ContextManagerV2 {
|
|
|
74
77
|
_cachedStatsFingerprint = '';
|
|
75
78
|
/** Last compression timestamp */
|
|
76
79
|
lastCompressionTime = null;
|
|
80
|
+
/** WS3-T2 — periodic snapshot timer (unref'd, never keeps the process alive) */
|
|
81
|
+
snapshotTimer = null;
|
|
82
|
+
/** WS3-T2 — snapshots taken this session */
|
|
83
|
+
snapshotCount = 0;
|
|
77
84
|
// Default configuration based on research recommendations
|
|
78
85
|
static DEFAULT_CONFIG = {
|
|
79
86
|
maxContextTokens: 4096,
|
|
@@ -692,6 +699,7 @@ export class ContextManagerV2 {
|
|
|
692
699
|
* Clean up resources
|
|
693
700
|
*/
|
|
694
701
|
dispose() {
|
|
702
|
+
this.stopPeriodicSnapshot();
|
|
695
703
|
this.tokenCounter.dispose();
|
|
696
704
|
this.summaries = [];
|
|
697
705
|
this.triggeredWarnings.clear();
|
|
@@ -921,6 +929,89 @@ export class ContextManagerV2 {
|
|
|
921
929
|
}
|
|
922
930
|
return lines.join('\n');
|
|
923
931
|
}
|
|
932
|
+
// ==========================================================================
|
|
933
|
+
// WS3-T2 — Periodic memory snapshot
|
|
934
|
+
// ==========================================================================
|
|
935
|
+
/**
|
|
936
|
+
* Take a compact snapshot of the session and persist it to
|
|
937
|
+
* `.codebuddy/context-snapshot.json` (latest wins). Returns null when the
|
|
938
|
+
* conversation is too small to be worth snapshotting.
|
|
939
|
+
*
|
|
940
|
+
* The summary is privacy-linted before it touches disk (WS3 guard-rail).
|
|
941
|
+
*/
|
|
942
|
+
takeSnapshot(messages, workDir = process.cwd()) {
|
|
943
|
+
if (!messages || messages.length < 4)
|
|
944
|
+
return null;
|
|
945
|
+
const snapshot = {
|
|
946
|
+
sessionId: this.sessionId,
|
|
947
|
+
takenAt: new Date().toISOString(),
|
|
948
|
+
stats: {
|
|
949
|
+
messageCount: messages.length,
|
|
950
|
+
tokenCount: this.countTokens(messages),
|
|
951
|
+
compressionCount: this.compressionCount,
|
|
952
|
+
totalTokensSaved: this.totalTokensSaved,
|
|
953
|
+
},
|
|
954
|
+
summary: redactSecrets(this.createSummary(messages)),
|
|
955
|
+
};
|
|
956
|
+
try {
|
|
957
|
+
const dir = path.join(workDir, '.codebuddy');
|
|
958
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
959
|
+
fs.writeFileSync(path.join(dir, 'context-snapshot.json'), JSON.stringify(snapshot, null, 2), 'utf8');
|
|
960
|
+
}
|
|
961
|
+
catch (err) {
|
|
962
|
+
logger.debug('Context snapshot write failed', { error: String(err) });
|
|
963
|
+
return null;
|
|
964
|
+
}
|
|
965
|
+
this.snapshotCount++;
|
|
966
|
+
try {
|
|
967
|
+
const runStore = RunStore.getInstance();
|
|
968
|
+
if (runStore.getCurrentRunId()) {
|
|
969
|
+
runStore.appendEvent('context_snapshot', {
|
|
970
|
+
sessionId: snapshot.sessionId,
|
|
971
|
+
messageCount: snapshot.stats.messageCount,
|
|
972
|
+
tokenCount: snapshot.stats.tokenCount,
|
|
973
|
+
snapshotCount: this.snapshotCount,
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
catch {
|
|
978
|
+
// Observability must never break the snapshot path.
|
|
979
|
+
}
|
|
980
|
+
return snapshot;
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* Start the periodic snapshot loop for long sessions (12–15 h).
|
|
984
|
+
*
|
|
985
|
+
* Interval resolution: explicit param → `CODEBUDDY_SNAPSHOT_INTERVAL_MIN`
|
|
986
|
+
* env (minutes) → 45 min default. `0` (or negative) disables. The timer
|
|
987
|
+
* is unref'd so it never keeps a finished process alive.
|
|
988
|
+
*/
|
|
989
|
+
startPeriodicSnapshot(getMessages, intervalMs, workDir = process.cwd()) {
|
|
990
|
+
this.stopPeriodicSnapshot();
|
|
991
|
+
let resolved = intervalMs;
|
|
992
|
+
if (resolved === undefined) {
|
|
993
|
+
const envMin = parseInt(process.env.CODEBUDDY_SNAPSHOT_INTERVAL_MIN || '45', 10);
|
|
994
|
+
resolved = (Number.isFinite(envMin) ? envMin : 45) * 60_000;
|
|
995
|
+
}
|
|
996
|
+
if (!resolved || resolved <= 0)
|
|
997
|
+
return;
|
|
998
|
+
this.snapshotTimer = setInterval(() => {
|
|
999
|
+
try {
|
|
1000
|
+
this.takeSnapshot(getMessages(), workDir);
|
|
1001
|
+
}
|
|
1002
|
+
catch (err) {
|
|
1003
|
+
logger.debug('Periodic context snapshot failed', { error: String(err) });
|
|
1004
|
+
}
|
|
1005
|
+
}, resolved);
|
|
1006
|
+
this.snapshotTimer.unref();
|
|
1007
|
+
}
|
|
1008
|
+
/** Stop the periodic snapshot loop (idempotent). */
|
|
1009
|
+
stopPeriodicSnapshot() {
|
|
1010
|
+
if (this.snapshotTimer) {
|
|
1011
|
+
clearInterval(this.snapshotTimer);
|
|
1012
|
+
this.snapshotTimer = null;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
924
1015
|
}
|
|
925
1016
|
/**
|
|
926
1017
|
* Create a context manager with auto-detection of model limits
|
|
@@ -34,6 +34,9 @@
|
|
|
34
34
|
import * as fs from 'fs';
|
|
35
35
|
import * as path from 'path';
|
|
36
36
|
import { spawnSync } from 'child_process';
|
|
37
|
+
import { buildColabGoalContinuationPrompt } from './colab-goal.js';
|
|
38
|
+
/** Tail of agent stdout kept for the goal-mode judge (matches the judge's 4 KB cap). */
|
|
39
|
+
const OUTPUT_TAIL_CHARS = 4000;
|
|
37
40
|
/** Resolve the buddy CLI entrypoint inside `repoRoot`. */
|
|
38
41
|
function resolveEntrypoint(repoRoot) {
|
|
39
42
|
const tsx = path.join(repoRoot, 'node_modules', '.bin', 'tsx');
|
|
@@ -82,7 +85,11 @@ export function createAgentTaskExecutor(opts = {}) {
|
|
|
82
85
|
if (!entry) {
|
|
83
86
|
return { ok: false, summary: 'no buddy entrypoint', error: `no src/index.ts or dist/index.js under ${repoRoot}` };
|
|
84
87
|
}
|
|
85
|
-
|
|
88
|
+
// Goal-mode continuation: on later turns the worker gets the judge's
|
|
89
|
+
// nudge instead of the bare task text, so it targets the remaining gap.
|
|
90
|
+
const prompt = task.goalMode && (task.goalTurnsUsed ?? 0) > 0
|
|
91
|
+
? buildColabGoalContinuationPrompt(task)
|
|
92
|
+
: `${task.title}\n\n${task.description ?? ''}`.trim();
|
|
86
93
|
const env = buildAgentEnv(model);
|
|
87
94
|
const started = Date.now();
|
|
88
95
|
const res = doSpawn(entry.cmd, [...entry.baseArgs, '-p', prompt, '--permission-mode', permissionMode, '--output-format', 'text', ...extraArgs], { cwd: workspaceRoot, env, encoding: 'utf-8', timeout: timeoutMs, maxBuffer: 64 * 1024 * 1024 });
|
|
@@ -99,6 +106,8 @@ export function createAgentTaskExecutor(opts = {}) {
|
|
|
99
106
|
error: reason,
|
|
100
107
|
};
|
|
101
108
|
}
|
|
109
|
+
// Tail of the agent's real output — what the goal-mode judge evaluates.
|
|
110
|
+
const output = (res.stdout ?? '').slice(-OUTPUT_TAIL_CHARS).trim();
|
|
102
111
|
// Acceptance gate: if the task carries a verify command AND running task-
|
|
103
112
|
// supplied shell is allowed (opt-in), the agent "finishing" isn't enough —
|
|
104
113
|
// the gate must pass for the task to count as completed. When not allowed,
|
|
@@ -126,6 +135,7 @@ export function createAgentTaskExecutor(opts = {}) {
|
|
|
126
135
|
ok: true,
|
|
127
136
|
summary: `agent ran ${task.id} [${model.tier}/${model.model}] + gate \`${gate}\` passed (${elapsedSeconds}s)`,
|
|
128
137
|
elapsedSeconds,
|
|
138
|
+
...(output ? { output } : {}),
|
|
129
139
|
};
|
|
130
140
|
}
|
|
131
141
|
const elapsedSeconds = Math.round((Date.now() - started) / 1000);
|
|
@@ -133,6 +143,7 @@ export function createAgentTaskExecutor(opts = {}) {
|
|
|
133
143
|
ok: true,
|
|
134
144
|
summary: `agent ran ${task.id} [${model.tier}/${model.model}] in ${workspaceRoot} (${elapsedSeconds}s)`,
|
|
135
145
|
elapsedSeconds,
|
|
146
|
+
...(output ? { output } : {}),
|
|
136
147
|
};
|
|
137
148
|
};
|
|
138
149
|
}
|
|
@@ -89,7 +89,7 @@ export interface DefaultAutonomousLoopOptions {
|
|
|
89
89
|
* local/network executor. The CLI uses this so the same config drives one-shot
|
|
90
90
|
* and continuous runs.
|
|
91
91
|
*/
|
|
92
|
-
export declare function createDefaultAutonomousLoop(opts?: DefaultAutonomousLoopOptions): FleetAutonomousLoop
|
|
92
|
+
export declare function createDefaultAutonomousLoop(opts?: DefaultAutonomousLoopOptions): Promise<FleetAutonomousLoop>;
|
|
93
93
|
/**
|
|
94
94
|
* Event source for {@link AutonomousDaemonConfig.eventSourceFactory}: watches the
|
|
95
95
|
* fleet queue file (`colab-tasks.json`) and calls `onChange` when it changes —
|
|
@@ -13,11 +13,12 @@
|
|
|
13
13
|
* (see {@link createAgentTaskExecutor}).
|
|
14
14
|
*/
|
|
15
15
|
import { FleetColabStore } from '../fleet/colab-store.js';
|
|
16
|
-
import {
|
|
16
|
+
import { resolveLiveModelTierConfig, } from '../agent/model-tier.js';
|
|
17
17
|
import { FileWatcherTrigger } from '../agent/file-watcher-trigger.js';
|
|
18
18
|
import { FleetAutonomousLoop } from './autonomous-loop.js';
|
|
19
19
|
import { createLocalModelTaskExecutor } from './ollama-task-executor.js';
|
|
20
20
|
import { createAgentTaskExecutor } from './agent-task-executor.js';
|
|
21
|
+
import { createColabGoalJudge } from './colab-goal.js';
|
|
21
22
|
const DEFAULT_INTERVAL_MS = 30_000;
|
|
22
23
|
export class FleetAutonomousDaemon {
|
|
23
24
|
loop;
|
|
@@ -115,12 +116,12 @@ export class FleetAutonomousDaemon {
|
|
|
115
116
|
* local/network executor. The CLI uses this so the same config drives one-shot
|
|
116
117
|
* and continuous runs.
|
|
117
118
|
*/
|
|
118
|
-
export function createDefaultAutonomousLoop(opts = {}) {
|
|
119
|
+
export async function createDefaultAutonomousLoop(opts = {}) {
|
|
119
120
|
const store = new FleetColabStore({
|
|
120
121
|
...(opts.dir ? { dir: opts.dir } : {}),
|
|
121
122
|
...(opts.agentId ? { agentId: opts.agentId } : {}),
|
|
122
123
|
});
|
|
123
|
-
const tierConfig =
|
|
124
|
+
const tierConfig = await resolveLiveModelTierConfig();
|
|
124
125
|
const executorMode = opts.executorMode ?? (process.env['CODEBUDDY_AUTONOMY_EXECUTOR'] === 'agent' ? 'agent' : 'artifact');
|
|
125
126
|
const executor = executorMode === 'agent'
|
|
126
127
|
? createAgentTaskExecutor({ ...(opts.workspaceRoot ? { workspaceRoot: opts.workspaceRoot } : {}) })
|
|
@@ -132,6 +133,7 @@ export function createDefaultAutonomousLoop(opts = {}) {
|
|
|
132
133
|
store,
|
|
133
134
|
tierConfig,
|
|
134
135
|
executor,
|
|
136
|
+
goalJudge: createColabGoalJudge(),
|
|
135
137
|
...(opts.policy ? { policy: opts.policy } : {}),
|
|
136
138
|
...(opts.enabled ? { enabled: opts.enabled } : {}),
|
|
137
139
|
});
|
|
@@ -22,12 +22,19 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import { type AutonomousModelChoice, type ModelTierConfig, type ModelTierPolicy } from '../agent/model-tier.js';
|
|
24
24
|
import type { ColabTask, ColabWorklogFileChange, FleetColabStore } from '../fleet/colab-store.js';
|
|
25
|
+
import { type ColabGoalJudge } from './colab-goal.js';
|
|
25
26
|
export interface TaskExecutionResult {
|
|
26
27
|
ok: boolean;
|
|
27
28
|
summary: string;
|
|
28
29
|
filesModified?: ColabWorklogFileChange[];
|
|
29
30
|
elapsedSeconds?: number;
|
|
30
31
|
error?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Tail of the worker's actual output (capped). Goal-mode judges evaluate
|
|
34
|
+
* this; absent for executors that don't capture output (judge falls back
|
|
35
|
+
* to `summary`).
|
|
36
|
+
*/
|
|
37
|
+
output?: string;
|
|
31
38
|
}
|
|
32
39
|
export type TaskExecutor = (task: ColabTask, model: AutonomousModelChoice) => Promise<TaskExecutionResult>;
|
|
33
40
|
export interface AutonomousLoopConfig {
|
|
@@ -37,9 +44,14 @@ export interface AutonomousLoopConfig {
|
|
|
37
44
|
policy?: ModelTierPolicy;
|
|
38
45
|
/** Kill-switch — when it returns false the tick is a no-op. Default: always on. */
|
|
39
46
|
enabled?: () => boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Judge for `goalMode` tasks (Hermes kanban goal-mode parity). When absent,
|
|
49
|
+
* goal-mode tasks complete like plain tasks (no judge gate).
|
|
50
|
+
*/
|
|
51
|
+
goalJudge?: ColabGoalJudge;
|
|
40
52
|
}
|
|
41
53
|
export interface TickResult {
|
|
42
|
-
outcome: 'disabled' | 'idle' | 'completed' | 'failed' | 'saturated';
|
|
54
|
+
outcome: 'disabled' | 'idle' | 'completed' | 'failed' | 'saturated' | 'goal_continue' | 'goal_blocked';
|
|
43
55
|
taskId?: string;
|
|
44
56
|
taskTitle?: string;
|
|
45
57
|
model?: AutonomousModelChoice;
|
|
@@ -58,7 +70,15 @@ export declare class FleetAutonomousLoop {
|
|
|
58
70
|
* success. Resets across process restarts — escalation is a within-run feature.
|
|
59
71
|
*/
|
|
60
72
|
private readonly failures;
|
|
73
|
+
private readonly goalJudge;
|
|
61
74
|
constructor(config: AutonomousLoopConfig);
|
|
62
75
|
/** Run a single autonomous tick. Never throws — failures are logged + reported. */
|
|
63
76
|
tick(): Promise<TickResult>;
|
|
77
|
+
/**
|
|
78
|
+
* Goal-mode decision ladder. Returns a TickResult when the loop should NOT
|
|
79
|
+
* complete the task (judge says continue / budget exhausted), or null when
|
|
80
|
+
* the task may complete (judge done/skipped, or judge unreachable —
|
|
81
|
+
* fail-open like the interactive Ralph loop).
|
|
82
|
+
*/
|
|
83
|
+
private evaluateGoalModeTask;
|
|
64
84
|
}
|
|
@@ -22,6 +22,15 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import { chooseAutonomousModel, } from '../agent/model-tier.js';
|
|
24
24
|
import { beginFleetWork, isFleetSaturated } from '../fleet/fleet-load.js';
|
|
25
|
+
import { DEFAULT_COLAB_GOAL_MAX_TURNS } from './colab-goal.js';
|
|
26
|
+
function resolveGoalMaxTurns(raw) {
|
|
27
|
+
return typeof raw === 'number' && Number.isSafeInteger(raw) && raw > 0
|
|
28
|
+
? raw
|
|
29
|
+
: DEFAULT_COLAB_GOAL_MAX_TURNS;
|
|
30
|
+
}
|
|
31
|
+
function resolveGoalTurnsUsed(raw) {
|
|
32
|
+
return typeof raw === 'number' && Number.isSafeInteger(raw) && raw >= 0 ? raw : 0;
|
|
33
|
+
}
|
|
25
34
|
export class FleetAutonomousLoop {
|
|
26
35
|
store;
|
|
27
36
|
tierConfig;
|
|
@@ -35,12 +44,14 @@ export class FleetAutonomousLoop {
|
|
|
35
44
|
* success. Resets across process restarts — escalation is a within-run feature.
|
|
36
45
|
*/
|
|
37
46
|
failures = new Map();
|
|
47
|
+
goalJudge;
|
|
38
48
|
constructor(config) {
|
|
39
49
|
this.store = config.store;
|
|
40
50
|
this.tierConfig = config.tierConfig;
|
|
41
51
|
this.executor = config.executor;
|
|
42
52
|
this.policy = config.policy ?? {};
|
|
43
53
|
this.enabled = config.enabled ?? (() => true);
|
|
54
|
+
this.goalJudge = config.goalJudge;
|
|
44
55
|
}
|
|
45
56
|
/** Run a single autonomous tick. Never throws — failures are logged + reported. */
|
|
46
57
|
async tick() {
|
|
@@ -88,6 +99,14 @@ export class FleetAutonomousLoop {
|
|
|
88
99
|
doneLoad();
|
|
89
100
|
}
|
|
90
101
|
if (result.ok) {
|
|
102
|
+
// Goal-mode gate (Hermes kanban goal-mode): a successful attempt is not
|
|
103
|
+
// enough — the judge must confirm the task's criteria are satisfied.
|
|
104
|
+
if (task.goalMode && this.goalJudge) {
|
|
105
|
+
const goalOutcome = await this.evaluateGoalModeTask(task, result, model);
|
|
106
|
+
if (goalOutcome)
|
|
107
|
+
return goalOutcome;
|
|
108
|
+
// null → judge said done (or skipped): fall through to completion.
|
|
109
|
+
}
|
|
91
110
|
this.failures.delete(task.id);
|
|
92
111
|
this.store.completeTask(task.id, {
|
|
93
112
|
summary: result.summary,
|
|
@@ -118,5 +137,55 @@ export class FleetAutonomousLoop {
|
|
|
118
137
|
...(result.error ? { detail: result.error } : {}),
|
|
119
138
|
};
|
|
120
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Goal-mode decision ladder. Returns a TickResult when the loop should NOT
|
|
142
|
+
* complete the task (judge says continue / budget exhausted), or null when
|
|
143
|
+
* the task may complete (judge done/skipped, or judge unreachable —
|
|
144
|
+
* fail-open like the interactive Ralph loop).
|
|
145
|
+
*/
|
|
146
|
+
async evaluateGoalModeTask(task, result, model) {
|
|
147
|
+
let verdict;
|
|
148
|
+
try {
|
|
149
|
+
verdict = await this.goalJudge(task, result, model);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return null; // fail-open: an unusable judge never blocks completion
|
|
153
|
+
}
|
|
154
|
+
if (verdict.verdict !== 'continue')
|
|
155
|
+
return null;
|
|
156
|
+
const maxTurns = resolveGoalMaxTurns(task.goalMaxTurns);
|
|
157
|
+
const turnsUsed = resolveGoalTurnsUsed(task.goalTurnsUsed) + 1;
|
|
158
|
+
if (turnsUsed >= maxTurns) {
|
|
159
|
+
// Hermes rule: block for human review instead of spinning forever.
|
|
160
|
+
const reason = `goal budget exhausted (${turnsUsed}/${maxTurns}) — judge: ${verdict.reason}`;
|
|
161
|
+
this.store.recordGoalTurn(task.id, verdict.reason);
|
|
162
|
+
this.store.blockTask(task.id, reason);
|
|
163
|
+
this.store.appendWorklog({
|
|
164
|
+
agent: this.store.agentId,
|
|
165
|
+
taskId: task.id,
|
|
166
|
+
summary: `Goal-mode blocked after ${turnsUsed}/${maxTurns} turns: ${verdict.reason}`,
|
|
167
|
+
filesModified: result.filesModified ?? [],
|
|
168
|
+
issues: [reason],
|
|
169
|
+
nextSteps: ['human review: unblock, split, or complete the task manually'],
|
|
170
|
+
});
|
|
171
|
+
this.store.updatePresence({ status: 'idle', currentTask: null });
|
|
172
|
+
return { outcome: 'goal_blocked', taskId: task.id, taskTitle: task.title, model, detail: verdict.reason };
|
|
173
|
+
}
|
|
174
|
+
// Under budget: persist the consumed turn + reason, release the task so a
|
|
175
|
+
// later tick continues it with the continuation nudge. A judge "continue"
|
|
176
|
+
// is NOT an executor failure — the model ladder must not escalate.
|
|
177
|
+
this.store.recordGoalTurn(task.id, verdict.reason);
|
|
178
|
+
this.store.appendWorklog({
|
|
179
|
+
agent: this.store.agentId,
|
|
180
|
+
taskId: task.id,
|
|
181
|
+
summary: `Goal-mode turn ${turnsUsed}/${maxTurns} — judge: continue: ${verdict.reason}`,
|
|
182
|
+
filesModified: result.filesModified ?? [],
|
|
183
|
+
issues: [],
|
|
184
|
+
nextSteps: ['continue on a later tick with the goal continuation nudge'],
|
|
185
|
+
});
|
|
186
|
+
this.store.releaseTask(task.id);
|
|
187
|
+
this.store.updatePresence({ status: 'idle', currentTask: null });
|
|
188
|
+
return { outcome: 'goal_continue', taskId: task.id, taskTitle: task.title, model, detail: verdict.reason };
|
|
189
|
+
}
|
|
121
190
|
}
|
|
122
191
|
//# sourceMappingURL=autonomous-loop.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Goal-mode for fleet colab tasks — port of Hermes Agent's kanban goal-mode.
|
|
3
|
+
*
|
|
4
|
+
* A task with `goalMode: true` is not completed on the worker's first
|
|
5
|
+
* successful attempt: an LLM judge checks the task's title/description (with
|
|
6
|
+
* `acceptanceCriteria` as strict numbered criteria) against the worker's
|
|
7
|
+
* output. "Continue" re-opens the task with a continuation nudge so the next
|
|
8
|
+
* tick keeps going; once `goalMaxTurns` is spent the task is BLOCKED for
|
|
9
|
+
* human review instead of spinning (Hermes' "block instead of loop" rule).
|
|
10
|
+
*
|
|
11
|
+
* The judge is fail-open (judgeGoal semantics): a broken judge yields
|
|
12
|
+
* "continue", and the turn budget is the backstop.
|
|
13
|
+
*/
|
|
14
|
+
import type { ColabTask } from '../fleet/colab-store.js';
|
|
15
|
+
import { GoalJudgeResult } from '../goals/goal-judge.js';
|
|
16
|
+
import type { AutonomousModelChoice } from '../agent/model-tier.js';
|
|
17
|
+
import type { TaskExecutionResult } from './autonomous-loop.js';
|
|
18
|
+
/**
|
|
19
|
+
* Conservative default for unattended subprocess loops (Hermes' kanban
|
|
20
|
+
* example uses 7; interactive /goal uses 20).
|
|
21
|
+
*/
|
|
22
|
+
export declare const DEFAULT_COLAB_GOAL_MAX_TURNS = 5;
|
|
23
|
+
export type ColabGoalJudge = (task: ColabTask, result: TaskExecutionResult, model: AutonomousModelChoice) => Promise<GoalJudgeResult>;
|
|
24
|
+
/** The goal text the judge evaluates: title + description. */
|
|
25
|
+
export declare function goalTextForTask(task: Pick<ColabTask, 'title' | 'description'>): string;
|
|
26
|
+
/**
|
|
27
|
+
* Continuation nudge fed to the worker on later goal-mode turns (port of
|
|
28
|
+
* Hermes' KANBAN_GOAL_CONTINUATION_TEMPLATE, adapted to the colab lifecycle:
|
|
29
|
+
* completion is decided by the loop's judge, so the worker is nudged to
|
|
30
|
+
* finish and state the outcome explicitly).
|
|
31
|
+
*/
|
|
32
|
+
export declare function buildColabGoalContinuationPrompt(task: ColabTask): string;
|
|
33
|
+
/**
|
|
34
|
+
* Default judge: a one-shot call on the same tier model the worker ran on
|
|
35
|
+
* (local tiers stay free), overridable via `goals.judgeModel`. Task
|
|
36
|
+
* `acceptanceCriteria` become strict numbered criteria for the judge.
|
|
37
|
+
*/
|
|
38
|
+
export declare function createColabGoalJudge(): ColabGoalJudge;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Goal-mode for fleet colab tasks — port of Hermes Agent's kanban goal-mode.
|
|
3
|
+
*
|
|
4
|
+
* A task with `goalMode: true` is not completed on the worker's first
|
|
5
|
+
* successful attempt: an LLM judge checks the task's title/description (with
|
|
6
|
+
* `acceptanceCriteria` as strict numbered criteria) against the worker's
|
|
7
|
+
* output. "Continue" re-opens the task with a continuation nudge so the next
|
|
8
|
+
* tick keeps going; once `goalMaxTurns` is spent the task is BLOCKED for
|
|
9
|
+
* human review instead of spinning (Hermes' "block instead of loop" rule).
|
|
10
|
+
*
|
|
11
|
+
* The judge is fail-open (judgeGoal semantics): a broken judge yields
|
|
12
|
+
* "continue", and the turn budget is the backstop.
|
|
13
|
+
*/
|
|
14
|
+
import { CodeBuddyClient } from '../codebuddy/client.js';
|
|
15
|
+
import { resolveGoalJudgeClient } from '../goals/goal-judge-client.js';
|
|
16
|
+
import { judgeGoal } from '../goals/goal-judge.js';
|
|
17
|
+
import { resolveGoalsConfig } from '../goals/goal-manager.js';
|
|
18
|
+
import { logger } from '../utils/logger.js';
|
|
19
|
+
/**
|
|
20
|
+
* Conservative default for unattended subprocess loops (Hermes' kanban
|
|
21
|
+
* example uses 7; interactive /goal uses 20).
|
|
22
|
+
*/
|
|
23
|
+
export const DEFAULT_COLAB_GOAL_MAX_TURNS = 5;
|
|
24
|
+
/** The goal text the judge evaluates: title + description. */
|
|
25
|
+
export function goalTextForTask(task) {
|
|
26
|
+
return `${task.title}\n\n${task.description ?? ''}`.trim();
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Continuation nudge fed to the worker on later goal-mode turns (port of
|
|
30
|
+
* Hermes' KANBAN_GOAL_CONTINUATION_TEMPLATE, adapted to the colab lifecycle:
|
|
31
|
+
* completion is decided by the loop's judge, so the worker is nudged to
|
|
32
|
+
* finish and state the outcome explicitly).
|
|
33
|
+
*/
|
|
34
|
+
export function buildColabGoalContinuationPrompt(task) {
|
|
35
|
+
const criteria = task.acceptanceCriteria?.length
|
|
36
|
+
? `\nAcceptance criteria (ALL must be satisfied):\n${task.acceptanceCriteria
|
|
37
|
+
.map((text, i) => `- ${i + 1}. ${text}`)
|
|
38
|
+
.join('\n')}\n`
|
|
39
|
+
: '';
|
|
40
|
+
const lastReason = task.goalLastReason ? `\nJudge's last verdict: ${task.goalLastReason}\n` : '';
|
|
41
|
+
return ('[Continuing toward this fleet task — the judge says it is not done yet]\n' +
|
|
42
|
+
`Task: ${goalTextForTask(task)}\n` +
|
|
43
|
+
criteria +
|
|
44
|
+
lastReason +
|
|
45
|
+
'\nFinish the remaining work. When everything is done, state completion ' +
|
|
46
|
+
'explicitly with evidence (file contents, command output). If you are ' +
|
|
47
|
+
'blocked, state the blocker clearly.');
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Default judge: a one-shot call on the same tier model the worker ran on
|
|
51
|
+
* (local tiers stay free), overridable via `goals.judgeModel`. Task
|
|
52
|
+
* `acceptanceCriteria` become strict numbered criteria for the judge.
|
|
53
|
+
*/
|
|
54
|
+
export function createColabGoalJudge() {
|
|
55
|
+
return async (task, result, model) => {
|
|
56
|
+
try {
|
|
57
|
+
const config = resolveGoalsConfig();
|
|
58
|
+
const apiKey = process.env.GROK_API_KEY || process.env.OPENAI_API_KEY || 'local';
|
|
59
|
+
const baseClient = new CodeBuddyClient(apiKey, model.model, model.baseUrl);
|
|
60
|
+
const client = await resolveGoalJudgeClient(baseClient, config.judgeModel, {
|
|
61
|
+
apiKey,
|
|
62
|
+
...(model.baseUrl ? { baseURL: model.baseUrl } : {}),
|
|
63
|
+
providerLabel: model.baseUrl?.includes(':11434') ? 'ollama' : model.tier,
|
|
64
|
+
});
|
|
65
|
+
return await judgeGoal(client, {
|
|
66
|
+
goal: goalTextForTask(task),
|
|
67
|
+
lastResponse: result.output || result.summary,
|
|
68
|
+
...(task.acceptanceCriteria?.length ? { subgoals: task.acceptanceCriteria } : {}),
|
|
69
|
+
...(config.judgeModel ? { model: config.judgeModel } : {}),
|
|
70
|
+
maxTokens: config.judgeMaxTokens,
|
|
71
|
+
timeoutMs: config.judgeTimeoutMs,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
// Fail-open, like judgeGoal itself: never wedge the loop on judge setup.
|
|
76
|
+
logger.debug('colab goal judge: setup failed — continue', { error: String(error) });
|
|
77
|
+
return { verdict: 'continue', reason: `judge setup error: ${String(error)}`, parseFailed: false };
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=colab-goal.js.map
|
|
@@ -40,6 +40,12 @@ export interface JobExecutionResult {
|
|
|
40
40
|
skipReason?: string;
|
|
41
41
|
/** For watchdog jobs: false when any check produced an alert/error. */
|
|
42
42
|
watchdogOk?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Structured output data for cross-job data passing. When this job is part
|
|
45
|
+
* of a chain (`then`), its outputData is forwarded as the next job's
|
|
46
|
+
* inputData.
|
|
47
|
+
*/
|
|
48
|
+
outputData?: string;
|
|
43
49
|
}
|
|
44
50
|
export declare class CronAgentBridge extends EventEmitter {
|
|
45
51
|
private config;
|
|
@@ -48,11 +54,11 @@ export declare class CronAgentBridge extends EventEmitter {
|
|
|
48
54
|
/**
|
|
49
55
|
* Create a task executor function for the CronScheduler
|
|
50
56
|
*/
|
|
51
|
-
createTaskExecutor(): (job: CronJob) => Promise<unknown>;
|
|
57
|
+
createTaskExecutor(): (job: CronJob, inputData?: string) => Promise<unknown>;
|
|
52
58
|
/**
|
|
53
59
|
* Execute a cron job by creating an agent instance
|
|
54
60
|
*/
|
|
55
|
-
executeJob(job: CronJob): Promise<JobExecutionResult>;
|
|
61
|
+
executeJob(job: CronJob, inputData?: string): Promise<JobExecutionResult>;
|
|
56
62
|
/**
|
|
57
63
|
* Start a durable run record for a job execution, when an observability store
|
|
58
64
|
* is configured. Returns the run id, or undefined when recording is disabled
|
|
@@ -86,8 +92,11 @@ export declare class CronAgentBridge extends EventEmitter {
|
|
|
86
92
|
* WITHOUT instantiating a CodeBuddyAgent or calling any model provider.
|
|
87
93
|
* A non-zero exit (or timeout) throws so the run is recorded as failed and
|
|
88
94
|
* any chained `then` job does not fire.
|
|
95
|
+
*
|
|
96
|
+
* Returns both the combined output (for logging/delivery) and the separate
|
|
97
|
+
* stdout (for cross-job data passing, capped at 64KB by the script runner).
|
|
89
98
|
*/
|
|
90
|
-
private
|
|
99
|
+
private executeScriptTaskFull;
|
|
91
100
|
/**
|
|
92
101
|
* Execute a skill-type task — resolves a named skill from the SkillRegistry
|
|
93
102
|
* and runs it via the SkillExecutor. Without a configured tool executor the
|