@phuetz/code-buddy 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +160 -165
- package/dist/agent/autonomous/agentic-coding-contract.d.ts +6 -6
- package/dist/agent/codebuddy-agent.d.ts +1 -1
- package/dist/agent/codebuddy-agent.js +69 -14
- package/dist/agent/execution/agent-executor.js +122 -12
- package/dist/agent/execution/tool-dependency-graph.js +7 -0
- package/dist/agent/execution/tool-hooks.d.ts +6 -0
- package/dist/agent/execution/tool-hooks.js +13 -5
- package/dist/agent/execution/tool-selection-strategy.d.ts +11 -2
- package/dist/agent/execution/tool-selection-strategy.js +67 -10
- package/dist/agent/extended-thinking.d.ts +8 -0
- package/dist/agent/extended-thinking.js +27 -0
- package/dist/agent/hermes-browser-backends.js +151 -11
- package/dist/agent/hermes-claw-migrate.d.ts +59 -0
- package/dist/agent/hermes-claw-migrate.js +427 -26
- package/dist/agent/hermes-parity-manifest.js +78 -71
- package/dist/agent/hermes-runtime-lifecycle.d.ts +67 -0
- package/dist/agent/hermes-runtime-lifecycle.js +468 -0
- package/dist/agent/middleware/index.d.ts +1 -0
- package/dist/agent/middleware/index.js +1 -0
- package/dist/agent/middleware/visual-validation-middleware.d.ts +17 -0
- package/dist/agent/middleware/visual-validation-middleware.js +67 -0
- package/dist/agent/model-benchmark.d.ts +77 -0
- package/dist/agent/model-benchmark.js +309 -0
- package/dist/agent/model-tier.d.ts +14 -0
- package/dist/agent/model-tier.js +71 -0
- package/dist/agent/multi-agent/agents/orchestrator-agent.js +17 -6
- package/dist/agent/multi-agent/multi-agent-system.js +3 -1
- package/dist/agent/multi-agent/session-fleet-bridge.d.ts +2 -0
- package/dist/agent/multi-agent/session-fleet-bridge.js +9 -1
- package/dist/agent/multi-agent/session-tools.js +22 -5
- package/dist/agent/reasoning/mcts.js +20 -6
- package/dist/agent/session-end-flush.d.ts +6 -1
- package/dist/agent/session-end-flush.js +20 -4
- package/dist/agent/streaming/streaming-handler.d.ts +7 -0
- package/dist/agent/streaming/streaming-handler.js +8 -1
- package/dist/agent/tool-executor.js +1 -0
- package/dist/agent/tool-handler.d.ts +5 -0
- package/dist/agent/tool-handler.js +130 -4
- package/dist/browser-automation/browser-operator-executor.js +15 -0
- package/dist/browser-automation/browser-use-runner.d.ts +96 -0
- package/dist/browser-automation/browser-use-runner.js +492 -0
- package/dist/browser-automation/camofox-runner.d.ts +107 -0
- package/dist/browser-automation/camofox-runner.js +287 -0
- package/dist/channels/dingtalk/index.js +3 -0
- package/dist/channels/discord/client.d.ts +9 -0
- package/dist/channels/discord/client.js +12 -0
- package/dist/channels/feishu/index.d.ts +167 -1
- package/dist/channels/feishu/index.js +432 -7
- package/dist/channels/gateway-lifecycle.d.ts +132 -0
- package/dist/channels/gateway-lifecycle.js +219 -0
- package/dist/channels/google-chat/index.d.ts +5 -1
- package/dist/channels/google-chat/index.js +5 -1
- package/dist/channels/imessage/index.d.ts +14 -0
- package/dist/channels/imessage/index.js +64 -17
- package/dist/channels/index.d.ts +4 -0
- package/dist/channels/index.js +4 -0
- package/dist/channels/irc/index.d.ts +73 -3
- package/dist/channels/irc/index.js +446 -11
- package/dist/channels/line/index.js +3 -0
- package/dist/channels/mattermost/index.d.ts +51 -4
- package/dist/channels/mattermost/index.js +303 -20
- package/dist/channels/nextcloud-talk/index.d.ts +89 -8
- package/dist/channels/nextcloud-talk/index.js +367 -16
- package/dist/channels/nostr/index.d.ts +121 -1
- package/dist/channels/nostr/index.js +396 -8
- package/dist/channels/ntfy/index.js +3 -0
- package/dist/channels/qq/index.js +3 -0
- package/dist/channels/slash-parity.d.ts +117 -0
- package/dist/channels/slash-parity.js +185 -0
- package/dist/channels/synology-chat/index.js +3 -0
- package/dist/channels/teams/index.d.ts +3 -0
- package/dist/channels/teams/index.js +3 -0
- package/dist/channels/telegram/client.d.ts +9 -0
- package/dist/channels/telegram/client.js +12 -0
- package/dist/channels/twilio-voice/index.js +3 -0
- package/dist/channels/webchat/index.d.ts +7 -1
- package/dist/channels/webchat/index.js +7 -1
- package/dist/channels/wecom/index.js +3 -0
- package/dist/channels/weixin/index.js +3 -0
- package/dist/channels/zalo/index.js +3 -0
- package/dist/cli/config-loader.js +1 -1
- package/dist/codebuddy/client.d.ts +30 -1
- package/dist/codebuddy/client.js +167 -20
- package/dist/codebuddy/providers/provider-chatgpt-responses.d.ts +5 -0
- package/dist/codebuddy/providers/provider-chatgpt-responses.js +1 -0
- package/dist/codebuddy/providers/provider-gemini-native.js +1 -1
- package/dist/codebuddy/providers/provider-openai-compat.d.ts +5 -0
- package/dist/codebuddy/providers/provider-openai-compat.js +82 -2
- package/dist/codebuddy/tool-definitions/computer-control-tools.js +32 -2
- package/dist/codebuddy/tool-definitions/core-tools.js +3 -3
- package/dist/codebuddy/tool-definitions/index.d.ts +2 -1
- package/dist/codebuddy/tool-definitions/index.js +2 -1
- package/dist/codebuddy/tool-definitions/multimodal-tools.d.ts +1 -0
- package/dist/codebuddy/tool-definitions/multimodal-tools.js +44 -1
- package/dist/codebuddy/tool-definitions/windows-tools.d.ts +2 -0
- package/dist/codebuddy/tool-definitions/windows-tools.js +34 -0
- package/dist/codebuddy/tools.d.ts +1 -1
- package/dist/codebuddy/tools.js +27 -24
- package/dist/commands/cli/hermes-commands.js +108 -0
- package/dist/commands/cli/native-engine-commands.js +102 -3
- package/dist/commands/cli/utility-commands.js +45 -0
- package/dist/commands/enhanced-command-handler.js +1 -1
- package/dist/commands/goal-cli.d.ts +30 -0
- package/dist/commands/goal-cli.js +210 -27
- package/dist/commands/handlers/goal-handler.d.ts +9 -2
- package/dist/commands/handlers/goal-handler.js +36 -9
- package/dist/commands/handlers/infra-handlers.js +36 -0
- package/dist/commands/handlers/memory-handlers.js +96 -1
- package/dist/commands/headless-slash.d.ts +2 -0
- package/dist/commands/headless-slash.js +12 -0
- package/dist/commands/llm-provider-resolution.d.ts +5 -3
- package/dist/commands/llm-provider-resolution.js +87 -33
- package/dist/commands/ollama.d.ts +25 -0
- package/dist/commands/ollama.js +100 -0
- package/dist/commands/provider.d.ts +5 -0
- package/dist/commands/provider.js +106 -55
- package/dist/commands/spec-next.js +2 -1
- package/dist/commands/spec-plan.js +5 -16
- package/dist/commands/tunnel.d.ts +2 -0
- package/dist/commands/tunnel.js +48 -0
- package/dist/config/config-resolver.d.ts +2 -1
- package/dist/config/config-resolver.js +54 -41
- package/dist/config/constants.d.ts +28 -0
- package/dist/config/constants.js +7 -0
- package/dist/config/env-schema.js +756 -0
- package/dist/config/model-tools.js +4 -4
- package/dist/config/toml-config.d.ts +16 -0
- package/dist/config/toml-config.js +3 -0
- package/dist/context/context-manager-v2.js +1 -0
- package/dist/daemon/autonomous-daemon.d.ts +1 -1
- package/dist/daemon/autonomous-daemon.js +3 -3
- package/dist/daemon/autonomous-loop.js +11 -2
- package/dist/daemon/colab-goal.js +9 -1
- package/dist/daemon/cron-agent-bridge.d.ts +12 -3
- package/dist/daemon/cron-agent-bridge.js +25 -9
- package/dist/desktop/codebuddy-engine-adapter.d.ts +32 -0
- package/dist/desktop/codebuddy-engine-adapter.js +257 -80
- package/dist/desktop/engine-adapter.d.ts +14 -0
- package/dist/desktop-automation/automation-manager.js +16 -0
- package/dist/desktop-automation/omniparser-runner.d.ts +63 -0
- package/dist/desktop-automation/omniparser-runner.js +115 -0
- package/dist/desktop-automation/smart-snapshot.d.ts +11 -0
- package/dist/desktop-automation/smart-snapshot.js +72 -5
- package/dist/doctor/index.js +40 -23
- package/dist/events/types.d.ts +11 -0
- package/dist/fleet/colab-store.js +14 -2
- package/dist/fleet/peer-session-bridge.js +50 -12
- package/dist/fleet/peer-tool-bridge.js +14 -0
- package/dist/goals/goal-decomposer.d.ts +27 -0
- package/dist/goals/goal-decomposer.js +293 -0
- package/dist/goals/goal-judge-client.d.ts +15 -0
- package/dist/goals/goal-judge-client.js +45 -0
- package/dist/goals/goal-judge.d.ts +2 -0
- package/dist/goals/goal-judge.js +35 -6
- package/dist/goals/goal-loop.d.ts +14 -0
- package/dist/goals/goal-loop.js +47 -2
- package/dist/goals/goal-manager.d.ts +6 -0
- package/dist/goals/goal-manager.js +67 -14
- package/dist/goals/goal-state.d.ts +13 -0
- package/dist/goals/goal-state.js +83 -13
- package/dist/goals/goal-store.d.ts +3 -0
- package/dist/goals/goal-store.js +31 -15
- package/dist/hooks/user-hooks.js +17 -3
- package/dist/index.js +464 -25
- package/dist/input/text-to-speech.d.ts +2 -6
- package/dist/input/text-to-speech.js +2 -27
- package/dist/integrations/screenpipe/screenpipe-client.d.ts +10 -0
- package/dist/integrations/screenpipe/screenpipe-client.js +12 -2
- package/dist/integrations/tailscale.d.ts +13 -0
- package/dist/integrations/tailscale.js +87 -34
- package/dist/knowledge/workspace-indexer.js +53 -9
- package/dist/mcp/client.js +1 -0
- package/dist/mcp/mcp-desktop-tools.d.ts +23 -0
- package/dist/mcp/mcp-desktop-tools.js +158 -0
- package/dist/mcp/mcp-server.js +2 -0
- package/dist/memory/enhanced-memory.d.ts +10 -0
- package/dist/memory/enhanced-memory.js +33 -2
- package/dist/memory/index.d.ts +3 -1
- package/dist/memory/index.js +4 -1
- package/dist/memory/memory-auto-proposer.d.ts +23 -0
- package/dist/memory/memory-auto-proposer.js +308 -0
- package/dist/memory/memory-candidate-queue.d.ts +92 -0
- package/dist/memory/memory-candidate-queue.js +261 -0
- package/dist/memory/persistent-memory.d.ts +45 -2
- package/dist/memory/persistent-memory.js +236 -40
- package/dist/ml/bayesian-qualifier.d.ts +1 -1
- package/dist/ml/bayesian-qualifier.js +35 -2
- package/dist/openclaw/gateway-bridge.js +5 -0
- package/dist/plugins/marketplace.d.ts +1 -0
- package/dist/plugins/marketplace.js +7 -0
- package/dist/prompts/execution-discipline.d.ts +14 -0
- package/dist/prompts/execution-discipline.js +29 -0
- package/dist/prompts/variation-injector.js +9 -2
- package/dist/protocols/acp/acp-agentic-runner.d.ts +7 -6
- package/dist/protocols/acp/acp-agentic-runner.js +115 -18
- package/dist/protocols/acp/acp-session-store.d.ts +23 -0
- package/dist/protocols/acp/acp-session-store.js +77 -0
- package/dist/protocols/acp/acp-stdio-server.d.ts +7 -0
- package/dist/protocols/acp/acp-stdio-server.js +71 -16
- package/dist/providers/active-llm-registry.d.ts +37 -0
- package/dist/providers/active-llm-registry.js +186 -0
- package/dist/providers/auxiliary-provider.d.ts +25 -0
- package/dist/providers/auxiliary-provider.js +192 -0
- package/dist/providers/codex-oauth.d.ts +1 -1
- package/dist/providers/codex-oauth.js +27 -4
- package/dist/providers/index.d.ts +3 -0
- package/dist/providers/index.js +4 -0
- package/dist/providers/provider-catalog.d.ts +70 -0
- package/dist/providers/provider-catalog.js +738 -0
- package/dist/providers/provider-fallback.d.ts +35 -0
- package/dist/providers/provider-fallback.js +171 -0
- package/dist/providers/xai-oauth.d.ts +128 -0
- package/dist/providers/xai-oauth.js +735 -0
- package/dist/scheduler/cron-scheduler.d.ts +15 -3
- package/dist/scheduler/cron-scheduler.js +53 -7
- package/dist/scheduler/script-runner.d.ts +2 -0
- package/dist/scheduler/script-runner.js +19 -4
- package/dist/scheduler/watchdog-handlers.js +7 -20
- package/dist/search/usearch-index.js +7 -2
- package/dist/security/tool-policy/tool-groups.js +2 -0
- package/dist/server/index.js +17 -2
- package/dist/server/routes/mobile.d.ts +12 -4
- package/dist/server/routes/mobile.js +116 -1
- package/dist/server/tls-config.d.ts +35 -0
- package/dist/server/tls-config.js +142 -0
- package/dist/server/tunnel-manager.d.ts +20 -0
- package/dist/server/tunnel-manager.js +58 -0
- package/dist/server/websocket/fleet-bridge.js +5 -0
- package/dist/services/prompt-builder.d.ts +1 -0
- package/dist/services/prompt-builder.js +49 -8
- package/dist/shared/engine-types.d.ts +15 -1
- package/dist/sidecar/sidecar-bridge.d.ts +1 -0
- package/dist/sidecar/sidecar-bridge.js +21 -10
- package/dist/spec/spec-store.js +8 -1
- package/dist/talk-mode/providers/audioreader-tts.d.ts +1 -1
- package/dist/talk-mode/providers/audioreader-tts.js +8 -8
- package/dist/templates/project-scaffolding.js +3 -2
- package/dist/themes/theme-schema.d.ts +10 -10
- package/dist/tools/application-profiles.js +38 -0
- package/dist/tools/bash/bash-tool.d.ts +1 -0
- package/dist/tools/bash/bash-tool.js +16 -2
- package/dist/tools/bash/command-validator.js +3 -0
- package/dist/tools/bash/rtk-rewrite.d.ts +16 -0
- package/dist/tools/bash/rtk-rewrite.js +100 -0
- package/dist/tools/bash/streaming-executor.js +14 -1
- package/dist/tools/computer-control-tool.d.ts +16 -1
- package/dist/tools/computer-control-tool.js +317 -5
- package/dist/tools/document-generator.d.ts +14 -0
- package/dist/tools/document-generator.js +79 -0
- package/dist/tools/execute-code-rpc-invoker.d.ts +26 -2
- package/dist/tools/execute-code-rpc-invoker.js +46 -5
- package/dist/tools/metadata.js +28 -0
- package/dist/tools/office-macro-tool.d.ts +10 -0
- package/dist/tools/office-macro-tool.js +93 -0
- package/dist/tools/registry/index.d.ts +2 -1
- package/dist/tools/registry/index.js +2 -1
- package/dist/tools/registry/memory-tools.d.ts +20 -0
- package/dist/tools/registry/memory-tools.js +225 -3
- package/dist/tools/registry/multimodal-tools.js +2 -2
- package/dist/tools/registry/vision-tools.d.ts +33 -0
- package/dist/tools/registry/vision-tools.js +308 -6
- package/dist/tools/registry/windows-tools.d.ts +2 -0
- package/dist/tools/registry/windows-tools.js +47 -0
- package/dist/tools/route-peer-tool.js +14 -0
- package/dist/tools/screenshot-tool.js +14 -2
- package/dist/tools/text-to-speech-tool.d.ts +1 -1
- package/dist/tools/text-to-speech-tool.js +2 -12
- package/dist/tools/tool-selector.js +3 -59
- package/dist/utils/config-validation/schema.d.ts +40 -7
- package/dist/utils/config-validation/schema.js +130 -1
- package/dist/utils/cost-tracker.js +21 -1
- package/dist/utils/disk-guard.d.ts +180 -0
- package/dist/utils/disk-guard.js +404 -0
- package/dist/utils/interactive-setup.js +35 -2
- package/dist/utils/model-utils.js +11 -2
- package/dist/utils/output-sanitizer.js +11 -0
- package/dist/utils/provider-detector.d.ts +8 -7
- package/dist/utils/provider-detector.js +19 -77
- package/dist/utils/settings-manager.d.ts +7 -0
- package/dist/utils/token-display.d.ts +3 -3
- package/dist/utils/token-display.js +22 -4
- package/dist/wizard/onboarding.d.ts +39 -0
- package/dist/wizard/onboarding.js +433 -21
- package/dist/wizard/provider-onboarding.d.ts +8 -23
- package/dist/wizard/provider-onboarding.js +39 -158
- package/package.json +4 -2
- package/dist/plugins/provider-onboarding.d.ts +0 -23
- package/dist/plugins/provider-onboarding.js +0 -116
- package/dist/utils/kokoro-tts.d.ts +0 -8
- package/dist/utils/kokoro-tts.js +0 -67
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { parseJsonResponse } from '../utils/llm-retry.js';
|
|
2
|
+
const DEFAULT_PLAN_MAX_TOKENS = 4096;
|
|
3
|
+
const DEFAULT_PLAN_TIMEOUT_MS = 45_000;
|
|
4
|
+
const MAX_TOP_LEVEL_TASKS = 8;
|
|
5
|
+
const MAX_SUBTASKS_PER_TASK = 6;
|
|
6
|
+
const MAX_CRITERIA_PER_ITEM = 6;
|
|
7
|
+
const MAX_NOTES = 6;
|
|
8
|
+
const MAX_FIELD_LENGTH = 500;
|
|
9
|
+
export function shouldAutoDecomposeGoal(goal) {
|
|
10
|
+
const text = goal.trim().toLowerCase();
|
|
11
|
+
if (!text)
|
|
12
|
+
return false;
|
|
13
|
+
if (text.length >= 140)
|
|
14
|
+
return true;
|
|
15
|
+
const structuralHints = [
|
|
16
|
+
/\bthen\b/,
|
|
17
|
+
/\bfinally\b/,
|
|
18
|
+
/\bfirst\b/,
|
|
19
|
+
/\bsecond\b/,
|
|
20
|
+
/\bthird\b/,
|
|
21
|
+
/\bstep\s+\d+\b/,
|
|
22
|
+
/\bsous[-\s]?t[aâ]che/,
|
|
23
|
+
/\bsub[-\s]?task/,
|
|
24
|
+
/\bdecompos/,
|
|
25
|
+
/\bplan\b/,
|
|
26
|
+
/\bdag\b/,
|
|
27
|
+
/\bparallel/,
|
|
28
|
+
/\bimpl[eé]ment.*test/,
|
|
29
|
+
/\bresearch.*implement/,
|
|
30
|
+
];
|
|
31
|
+
return structuralHints.some((pattern) => pattern.test(text));
|
|
32
|
+
}
|
|
33
|
+
export async function decomposeGoal(goal, client, options = {}) {
|
|
34
|
+
const messages = [
|
|
35
|
+
{
|
|
36
|
+
role: 'system',
|
|
37
|
+
content: 'You are a Hermes-style kanban orchestrator. Decompose a user goal into ' +
|
|
38
|
+
'a durable task graph before execution. Extract independent work lanes, ' +
|
|
39
|
+
'link only true dependencies, and add concrete acceptance criteria. ' +
|
|
40
|
+
'Return only valid JSON.',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
role: 'user',
|
|
44
|
+
content: buildGoalDecompositionPrompt(goal),
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
const response = await withTimeout(client.chat(messages, [], {
|
|
48
|
+
...(options.model ? { model: options.model } : {}),
|
|
49
|
+
maxTokens: options.maxTokens ?? DEFAULT_PLAN_MAX_TOKENS,
|
|
50
|
+
temperature: 0,
|
|
51
|
+
}), options.timeoutMs ?? DEFAULT_PLAN_TIMEOUT_MS);
|
|
52
|
+
const raw = response.choices?.[0]?.message?.content ?? '';
|
|
53
|
+
return parseGoalPlan(raw, goal);
|
|
54
|
+
}
|
|
55
|
+
export function parseGoalPlan(raw, originalGoal = '') {
|
|
56
|
+
if (!raw.trim())
|
|
57
|
+
return null;
|
|
58
|
+
let parsed;
|
|
59
|
+
try {
|
|
60
|
+
parsed = parseJsonResponse(raw);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return normalizeGoalPlan(parsed, originalGoal);
|
|
66
|
+
}
|
|
67
|
+
export function normalizeGoalPlan(raw, originalGoal = '') {
|
|
68
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
69
|
+
return null;
|
|
70
|
+
const record = raw;
|
|
71
|
+
const rawTasks = Array.isArray(record.tasks) ? record.tasks : [];
|
|
72
|
+
if (rawTasks.length === 0)
|
|
73
|
+
return null;
|
|
74
|
+
const tasks = [];
|
|
75
|
+
const seenIds = new Set();
|
|
76
|
+
const rawById = new Map();
|
|
77
|
+
for (let i = 0; i < rawTasks.length && tasks.length < MAX_TOP_LEVEL_TASKS; i++) {
|
|
78
|
+
const rawTask = rawTasks[i];
|
|
79
|
+
if (!rawTask || typeof rawTask !== 'object' || Array.isArray(rawTask))
|
|
80
|
+
continue;
|
|
81
|
+
const taskRecord = rawTask;
|
|
82
|
+
const fallbackId = `T${tasks.length + 1}`;
|
|
83
|
+
const id = uniqueId(cleanId(taskRecord.id, fallbackId), seenIds);
|
|
84
|
+
const title = cleanText(taskRecord.title, `Task ${tasks.length + 1}`);
|
|
85
|
+
const description = cleanOptionalText(taskRecord.description);
|
|
86
|
+
const acceptanceCriteria = cleanCriteria(taskRecord, title);
|
|
87
|
+
const subtasks = cleanSubtasks(taskRecord.subtasks, id);
|
|
88
|
+
tasks.push({
|
|
89
|
+
id,
|
|
90
|
+
title,
|
|
91
|
+
...(description ? { description } : {}),
|
|
92
|
+
acceptanceCriteria,
|
|
93
|
+
dependsOn: [],
|
|
94
|
+
subtasks,
|
|
95
|
+
});
|
|
96
|
+
rawById.set(id, taskRecord);
|
|
97
|
+
}
|
|
98
|
+
if (tasks.length === 0)
|
|
99
|
+
return null;
|
|
100
|
+
const validIds = new Set(tasks.map((task) => task.id));
|
|
101
|
+
for (const task of tasks) {
|
|
102
|
+
const taskRecord = rawById.get(task.id);
|
|
103
|
+
task.dependsOn = cleanDependsOn(taskRecord, task.id, validIds);
|
|
104
|
+
}
|
|
105
|
+
const summary = cleanText(record.summary, originalGoal ? `Decomposed plan for: ${originalGoal}` : 'Decomposed goal plan');
|
|
106
|
+
const notes = cleanStringArray(record.notes).slice(0, MAX_NOTES);
|
|
107
|
+
return {
|
|
108
|
+
summary,
|
|
109
|
+
tasks,
|
|
110
|
+
...(notes.length ? { notes } : {}),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
export function goalPlanToCriteria(plan, maxCriteria = 30) {
|
|
114
|
+
if (!plan)
|
|
115
|
+
return [];
|
|
116
|
+
const criteria = [];
|
|
117
|
+
for (const task of plan.tasks) {
|
|
118
|
+
const depends = task.dependsOn.length ? ` after ${task.dependsOn.join(', ')}` : '';
|
|
119
|
+
const taskCriteria = task.acceptanceCriteria.length
|
|
120
|
+
? task.acceptanceCriteria.join('; ')
|
|
121
|
+
: task.description || task.title;
|
|
122
|
+
criteria.push(`${task.id} ${task.title}${depends}: ${taskCriteria}`);
|
|
123
|
+
for (const subtask of task.subtasks) {
|
|
124
|
+
const subtaskCriteria = subtask.acceptanceCriteria.length
|
|
125
|
+
? subtask.acceptanceCriteria.join('; ')
|
|
126
|
+
: subtask.description || subtask.title;
|
|
127
|
+
criteria.push(`${subtask.id} ${task.title} / ${subtask.title}: ${subtaskCriteria}`);
|
|
128
|
+
if (criteria.length >= maxCriteria)
|
|
129
|
+
return criteria;
|
|
130
|
+
}
|
|
131
|
+
if (criteria.length >= maxCriteria)
|
|
132
|
+
return criteria;
|
|
133
|
+
}
|
|
134
|
+
return criteria;
|
|
135
|
+
}
|
|
136
|
+
export function formatGoalPlan(plan) {
|
|
137
|
+
const lines = [`Plan: ${plan.summary}`, `Tasks: ${plan.tasks.length}`];
|
|
138
|
+
for (const task of plan.tasks) {
|
|
139
|
+
lines.push(`- ${task.id}: ${task.title}`);
|
|
140
|
+
if (task.dependsOn.length) {
|
|
141
|
+
lines.push(` depends on: ${task.dependsOn.join(', ')}`);
|
|
142
|
+
}
|
|
143
|
+
if (task.acceptanceCriteria.length) {
|
|
144
|
+
lines.push(` acceptance: ${task.acceptanceCriteria.join('; ')}`);
|
|
145
|
+
}
|
|
146
|
+
for (const subtask of task.subtasks) {
|
|
147
|
+
lines.push(` - ${subtask.id}: ${subtask.title}`);
|
|
148
|
+
if (subtask.acceptanceCriteria.length) {
|
|
149
|
+
lines.push(` acceptance: ${subtask.acceptanceCriteria.join('; ')}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (plan.notes?.length) {
|
|
154
|
+
lines.push(`Notes: ${plan.notes.join('; ')}`);
|
|
155
|
+
}
|
|
156
|
+
return lines.join('\n');
|
|
157
|
+
}
|
|
158
|
+
function buildGoalDecompositionPrompt(goal) {
|
|
159
|
+
return `Goal:
|
|
160
|
+
${goal}
|
|
161
|
+
|
|
162
|
+
Decompose this goal into a task graph with sub-subtasks.
|
|
163
|
+
|
|
164
|
+
Use this exact JSON shape:
|
|
165
|
+
{
|
|
166
|
+
"summary": "one sentence",
|
|
167
|
+
"tasks": [
|
|
168
|
+
{
|
|
169
|
+
"id": "T1",
|
|
170
|
+
"title": "short task title",
|
|
171
|
+
"description": "what this task does",
|
|
172
|
+
"dependsOn": [],
|
|
173
|
+
"acceptanceCriteria": ["specific evidence required"],
|
|
174
|
+
"subtasks": [
|
|
175
|
+
{
|
|
176
|
+
"id": "T1.1",
|
|
177
|
+
"title": "short subtask title",
|
|
178
|
+
"description": "what this subtask does",
|
|
179
|
+
"acceptanceCriteria": ["specific evidence required"]
|
|
180
|
+
}
|
|
181
|
+
]
|
|
182
|
+
}
|
|
183
|
+
],
|
|
184
|
+
"notes": ["optional orchestration notes"]
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
Rules:
|
|
188
|
+
1. Create 2-8 top-level tasks only when the goal is genuinely multi-step; otherwise return one task.
|
|
189
|
+
2. Treat independent lanes as parallel tasks with empty dependsOn.
|
|
190
|
+
3. Add dependsOn only when a task cannot start until another task's output exists.
|
|
191
|
+
4. Include final integration/review tasks only when useful, with dependsOn set in the original task object.
|
|
192
|
+
5. Each top-level task should include 1-5 concrete subtasks when there is real substructure.
|
|
193
|
+
6. Acceptance criteria must be observable evidence, not generic "done" statements.
|
|
194
|
+
7. Return only the JSON object.`;
|
|
195
|
+
}
|
|
196
|
+
function cleanSubtasks(raw, parentId) {
|
|
197
|
+
if (!Array.isArray(raw))
|
|
198
|
+
return [];
|
|
199
|
+
const subtasks = [];
|
|
200
|
+
const seenIds = new Set();
|
|
201
|
+
for (const item of raw) {
|
|
202
|
+
if (subtasks.length >= MAX_SUBTASKS_PER_TASK)
|
|
203
|
+
break;
|
|
204
|
+
if (!item || typeof item !== 'object' || Array.isArray(item))
|
|
205
|
+
continue;
|
|
206
|
+
const record = item;
|
|
207
|
+
const fallbackId = `${parentId}.${subtasks.length + 1}`;
|
|
208
|
+
const id = uniqueId(cleanId(record.id, fallbackId), seenIds);
|
|
209
|
+
const title = cleanText(record.title, `Subtask ${subtasks.length + 1}`);
|
|
210
|
+
const description = cleanOptionalText(record.description);
|
|
211
|
+
subtasks.push({
|
|
212
|
+
id,
|
|
213
|
+
title,
|
|
214
|
+
...(description ? { description } : {}),
|
|
215
|
+
acceptanceCriteria: cleanCriteria(record, title),
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
return subtasks;
|
|
219
|
+
}
|
|
220
|
+
function cleanCriteria(record, fallbackTitle) {
|
|
221
|
+
const raw = record.acceptanceCriteria ??
|
|
222
|
+
record.criteria ??
|
|
223
|
+
record.acceptance ??
|
|
224
|
+
record.doneWhen;
|
|
225
|
+
const values = cleanStringArray(raw).slice(0, MAX_CRITERIA_PER_ITEM);
|
|
226
|
+
return values.length ? values : [`Evidence shows "${fallbackTitle}" is complete`];
|
|
227
|
+
}
|
|
228
|
+
function cleanDependsOn(record, ownId, validIds) {
|
|
229
|
+
if (!record)
|
|
230
|
+
return [];
|
|
231
|
+
const raw = record.dependsOn ?? record.dependencies ?? record.parents;
|
|
232
|
+
const seen = new Set();
|
|
233
|
+
return cleanStringArray(raw).filter((id) => {
|
|
234
|
+
if (id === ownId || !validIds.has(id) || seen.has(id))
|
|
235
|
+
return false;
|
|
236
|
+
seen.add(id);
|
|
237
|
+
return true;
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
function cleanId(raw, fallback) {
|
|
241
|
+
const text = typeof raw === 'string' ? raw.trim() : '';
|
|
242
|
+
const cleaned = text.replace(/[^a-zA-Z0-9_.-]/g, '').slice(0, 32);
|
|
243
|
+
return cleaned || fallback;
|
|
244
|
+
}
|
|
245
|
+
function uniqueId(id, seen) {
|
|
246
|
+
if (!seen.has(id)) {
|
|
247
|
+
seen.add(id);
|
|
248
|
+
return id;
|
|
249
|
+
}
|
|
250
|
+
let suffix = 2;
|
|
251
|
+
while (seen.has(`${id}-${suffix}`))
|
|
252
|
+
suffix += 1;
|
|
253
|
+
const unique = `${id}-${suffix}`;
|
|
254
|
+
seen.add(unique);
|
|
255
|
+
return unique;
|
|
256
|
+
}
|
|
257
|
+
function cleanText(raw, fallback) {
|
|
258
|
+
const text = typeof raw === 'string' ? raw.trim() : '';
|
|
259
|
+
return (text || fallback).slice(0, MAX_FIELD_LENGTH);
|
|
260
|
+
}
|
|
261
|
+
function cleanOptionalText(raw) {
|
|
262
|
+
const text = typeof raw === 'string' ? raw.trim() : '';
|
|
263
|
+
return text ? text.slice(0, MAX_FIELD_LENGTH) : undefined;
|
|
264
|
+
}
|
|
265
|
+
function cleanStringArray(raw) {
|
|
266
|
+
const values = Array.isArray(raw) ? raw : typeof raw === 'string' ? [raw] : [];
|
|
267
|
+
const seen = new Set();
|
|
268
|
+
const cleaned = [];
|
|
269
|
+
for (const value of values) {
|
|
270
|
+
const text = String(value).trim().replace(/\s+/g, ' ').slice(0, MAX_FIELD_LENGTH);
|
|
271
|
+
if (!text || seen.has(text))
|
|
272
|
+
continue;
|
|
273
|
+
seen.add(text);
|
|
274
|
+
cleaned.push(text);
|
|
275
|
+
}
|
|
276
|
+
return cleaned;
|
|
277
|
+
}
|
|
278
|
+
async function withTimeout(promise, ms) {
|
|
279
|
+
let timer;
|
|
280
|
+
try {
|
|
281
|
+
return await Promise.race([
|
|
282
|
+
promise,
|
|
283
|
+
new Promise((_, reject) => {
|
|
284
|
+
timer = setTimeout(() => reject(new Error(`goal decomposition timed out after ${ms}ms`)), ms);
|
|
285
|
+
}),
|
|
286
|
+
]);
|
|
287
|
+
}
|
|
288
|
+
finally {
|
|
289
|
+
if (timer)
|
|
290
|
+
clearTimeout(timer);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
//# sourceMappingURL=goal-decomposer.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CodeBuddyClient } from '../codebuddy/client.js';
|
|
2
|
+
export interface GoalJudgeProviderInfo {
|
|
3
|
+
apiKey: string;
|
|
4
|
+
baseURL?: string;
|
|
5
|
+
providerLabel?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface GoalJudgeClientLike {
|
|
8
|
+
getCurrentModel?: () => string | undefined;
|
|
9
|
+
}
|
|
10
|
+
export declare function isChatGptJudgeModel(model: string | undefined): boolean;
|
|
11
|
+
export declare function isChatGptProvider(provider: GoalJudgeProviderInfo | undefined): boolean;
|
|
12
|
+
export declare function shouldUseStandaloneChatGptJudge(judgeModel: string | undefined, agentProvider?: GoalJudgeProviderInfo, currentClient?: GoalJudgeClientLike | null): boolean;
|
|
13
|
+
export declare function createStandaloneChatGptJudgeClient(judgeModel: string): Promise<CodeBuddyClient>;
|
|
14
|
+
export declare function resolveGoalJudgeClient<T extends GoalJudgeClientLike | null>(currentClient: T, judgeModel: string | undefined, agentProvider?: GoalJudgeProviderInfo): Promise<T | CodeBuddyClient>;
|
|
15
|
+
export declare function resolveGoalJudgeClientFailOpen<T extends GoalJudgeClientLike | null>(currentClient: T, judgeModel: string | undefined, agentProvider?: GoalJudgeProviderInfo): Promise<T | CodeBuddyClient | null>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { CHATGPT_OAUTH_SENTINEL, CHATGPT_RESPONSES_BASE_URL, CodeBuddyClient, } from '../codebuddy/client.js';
|
|
2
|
+
export function isChatGptJudgeModel(model) {
|
|
3
|
+
const normalized = model?.trim().toLowerCase() ?? '';
|
|
4
|
+
return normalized === 'gpt-5.5' || normalized.startsWith('gpt-5.5-');
|
|
5
|
+
}
|
|
6
|
+
export function isChatGptProvider(provider) {
|
|
7
|
+
if (!provider)
|
|
8
|
+
return false;
|
|
9
|
+
return (provider.apiKey === CHATGPT_OAUTH_SENTINEL ||
|
|
10
|
+
provider.baseURL?.includes('chatgpt.com/backend-api/codex') === true ||
|
|
11
|
+
provider.providerLabel?.toLowerCase().includes('chatgpt') === true);
|
|
12
|
+
}
|
|
13
|
+
export function shouldUseStandaloneChatGptJudge(judgeModel, agentProvider, currentClient) {
|
|
14
|
+
if (!isChatGptJudgeModel(judgeModel))
|
|
15
|
+
return false;
|
|
16
|
+
if (isChatGptProvider(agentProvider))
|
|
17
|
+
return false;
|
|
18
|
+
const currentModel = currentClient?.getCurrentModel?.();
|
|
19
|
+
return !isChatGptJudgeModel(currentModel);
|
|
20
|
+
}
|
|
21
|
+
export async function createStandaloneChatGptJudgeClient(judgeModel) {
|
|
22
|
+
const { hasCodexCredentials } = await import('../providers/codex-oauth.js');
|
|
23
|
+
if (!hasCodexCredentials()) {
|
|
24
|
+
throw new Error('ChatGPT judge model gpt-5.5 requires `buddy login chatgpt` first.');
|
|
25
|
+
}
|
|
26
|
+
return new CodeBuddyClient(CHATGPT_OAUTH_SENTINEL, judgeModel, CHATGPT_RESPONSES_BASE_URL);
|
|
27
|
+
}
|
|
28
|
+
export async function resolveGoalJudgeClient(currentClient, judgeModel, agentProvider) {
|
|
29
|
+
if (judgeModel && shouldUseStandaloneChatGptJudge(judgeModel, agentProvider, currentClient)) {
|
|
30
|
+
return createStandaloneChatGptJudgeClient(judgeModel);
|
|
31
|
+
}
|
|
32
|
+
return currentClient;
|
|
33
|
+
}
|
|
34
|
+
export async function resolveGoalJudgeClientFailOpen(currentClient, judgeModel, agentProvider) {
|
|
35
|
+
if (judgeModel && shouldUseStandaloneChatGptJudge(judgeModel, agentProvider, currentClient)) {
|
|
36
|
+
try {
|
|
37
|
+
return await createStandaloneChatGptJudgeClient(judgeModel);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return currentClient;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=goal-judge-client.js.map
|
|
@@ -23,6 +23,8 @@ export interface GoalJudgeParams {
|
|
|
23
23
|
subgoals?: string[];
|
|
24
24
|
/** Override the judge model (config `goals.judgeModel`). Empty → client default. */
|
|
25
25
|
model?: string;
|
|
26
|
+
/** Optional per-call cap for judge output. */
|
|
27
|
+
maxTokens?: number;
|
|
26
28
|
timeoutMs?: number;
|
|
27
29
|
}
|
|
28
30
|
/** Signature used by GoalManager so tests can inject a fake judge. */
|
package/dist/goals/goal-judge.js
CHANGED
|
@@ -32,7 +32,11 @@ export async function judgeGoal(client, params) {
|
|
|
32
32
|
const response = await withTimeout(client.chat([
|
|
33
33
|
{ role: 'system', content: JUDGE_SYSTEM_PROMPT },
|
|
34
34
|
{ role: 'user', content: prompt },
|
|
35
|
-
], [], {
|
|
35
|
+
], [], {
|
|
36
|
+
...(params.model ? { model: params.model } : {}),
|
|
37
|
+
...(params.maxTokens ? { maxTokens: params.maxTokens } : {}),
|
|
38
|
+
temperature: 0,
|
|
39
|
+
}), timeoutMs);
|
|
36
40
|
raw = response?.choices?.[0]?.message?.content ?? '';
|
|
37
41
|
recordJudgeCost(client, params.model, response?.usage);
|
|
38
42
|
}
|
|
@@ -92,12 +96,37 @@ export function parseJudgeResponse(raw) {
|
|
|
92
96
|
};
|
|
93
97
|
}
|
|
94
98
|
const record = data;
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
+
const parsedDone = parseDoneField(record.done);
|
|
100
|
+
if (!parsedDone.ok) {
|
|
101
|
+
return {
|
|
102
|
+
verdict: 'continue',
|
|
103
|
+
reason: `judge reply had invalid done field: ${truncateText(raw, 200)}`,
|
|
104
|
+
parseFailed: true,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
99
107
|
const reason = String(record.reason ?? '').trim() || 'no reason provided';
|
|
100
|
-
return { verdict: done ? 'done' : 'continue', reason, parseFailed: false };
|
|
108
|
+
return { verdict: parsedDone.done ? 'done' : 'continue', reason, parseFailed: false };
|
|
109
|
+
}
|
|
110
|
+
function parseDoneField(value) {
|
|
111
|
+
if (typeof value === 'boolean')
|
|
112
|
+
return { ok: true, done: value };
|
|
113
|
+
if (typeof value === 'number') {
|
|
114
|
+
if (value === 1)
|
|
115
|
+
return { ok: true, done: true };
|
|
116
|
+
if (value === 0)
|
|
117
|
+
return { ok: true, done: false };
|
|
118
|
+
return { ok: false };
|
|
119
|
+
}
|
|
120
|
+
if (typeof value !== 'string')
|
|
121
|
+
return { ok: false };
|
|
122
|
+
const normalized = value.trim().toLowerCase();
|
|
123
|
+
if (['true', 'yes', '1', 'done'].includes(normalized)) {
|
|
124
|
+
return { ok: true, done: true };
|
|
125
|
+
}
|
|
126
|
+
if (['false', 'no', '0', 'continue', 'not_done', 'not done'].includes(normalized)) {
|
|
127
|
+
return { ok: true, done: false };
|
|
128
|
+
}
|
|
129
|
+
return { ok: false };
|
|
101
130
|
}
|
|
102
131
|
/** Judge calls consume real tokens — record them in the session cost ledger. */
|
|
103
132
|
function recordJudgeCost(client, modelOverride, usage) {
|
|
@@ -7,11 +7,23 @@
|
|
|
7
7
|
* back into the session. Safe to call when no goal is set — returns fast.
|
|
8
8
|
*/
|
|
9
9
|
import { CodeBuddyClient } from '../codebuddy/client.js';
|
|
10
|
+
import type { GoalStatus, GoalVerdict } from './goal-state.js';
|
|
10
11
|
export interface GoalTurnOutcome {
|
|
11
12
|
/** User-visible status line (✓ / ⏸ / ↻) to append to chat history. */
|
|
12
13
|
message?: string;
|
|
13
14
|
/** When set, the caller should auto-submit this as the next user message. */
|
|
14
15
|
continuationPrompt?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Structured snapshot for host UIs (e.g. the Cowork goal banner). Mirrors the
|
|
18
|
+
* post-decision GoalState so the renderer can show turn progress + verdict
|
|
19
|
+
* without re-reading goal storage. Absent fields when no goal is active.
|
|
20
|
+
*/
|
|
21
|
+
goalText?: string;
|
|
22
|
+
status?: GoalStatus;
|
|
23
|
+
turnsUsed?: number;
|
|
24
|
+
maxTurns?: number;
|
|
25
|
+
lastVerdict?: GoalVerdict;
|
|
26
|
+
lastReason?: string;
|
|
15
27
|
}
|
|
16
28
|
export interface GoalAfterTurnOptions {
|
|
17
29
|
client: CodeBuddyClient | null;
|
|
@@ -19,5 +31,7 @@ export interface GoalAfterTurnOptions {
|
|
|
19
31
|
lastResponse: string;
|
|
20
32
|
/** True when the turn was user-interrupted (Esc). */
|
|
21
33
|
interrupted: boolean;
|
|
34
|
+
/** Optional goal-state key for host surfaces with their own session ids. */
|
|
35
|
+
sessionKey?: string;
|
|
22
36
|
}
|
|
23
37
|
export declare function maybeContinueGoalAfterTurn(options: GoalAfterTurnOptions): Promise<GoalTurnOutcome | null>;
|
package/dist/goals/goal-loop.js
CHANGED
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
* back into the session. Safe to call when no goal is set — returns fast.
|
|
8
8
|
*/
|
|
9
9
|
import { logger } from '../utils/logger.js';
|
|
10
|
+
import { decomposeGoal, shouldAutoDecomposeGoal } from './goal-decomposer.js';
|
|
10
11
|
import { judgeGoal } from './goal-judge.js';
|
|
11
12
|
import { getGoalManager, resolveGoalsConfig } from './goal-manager.js';
|
|
12
13
|
// The executor appends a per-turn usage footer ("[tokens: … | cost: …]") as a
|
|
13
14
|
// final content chunk; strip it so the judge sees only substantive output.
|
|
14
15
|
const USAGE_FOOTER_RE = /\n?\[tokens: [^\]]*\]\s*$/;
|
|
15
16
|
export async function maybeContinueGoalAfterTurn(options) {
|
|
16
|
-
const manager = getGoalManager();
|
|
17
|
+
const manager = getGoalManager(options.sessionKey);
|
|
17
18
|
if (!manager.isActive())
|
|
18
19
|
return null;
|
|
19
20
|
// If the turn was user-interrupted, auto-pause instead of judging: the
|
|
@@ -28,6 +29,7 @@ export async function maybeContinueGoalAfterTurn(options) {
|
|
|
28
29
|
}
|
|
29
30
|
return {
|
|
30
31
|
message: '⏸ Goal paused — turn was interrupted. Use /goal resume to continue, or /goal clear to stop.',
|
|
32
|
+
...goalSnapshot(manager),
|
|
31
33
|
};
|
|
32
34
|
}
|
|
33
35
|
const lastResponse = options.lastResponse.replace(USAGE_FOOTER_RE, '').trim();
|
|
@@ -36,16 +38,18 @@ export async function maybeContinueGoalAfterTurn(options) {
|
|
|
36
38
|
if (!lastResponse)
|
|
37
39
|
return null;
|
|
38
40
|
const config = resolveGoalsConfig();
|
|
41
|
+
await maybeAttachGoalPlan(manager, options.client, config.plannerModel);
|
|
39
42
|
const decision = await manager.evaluateAfterTurn(lastResponse, {
|
|
40
43
|
judge: params => judgeGoal(options.client, {
|
|
41
44
|
...params,
|
|
42
45
|
...(config.judgeModel ? { model: config.judgeModel } : {}),
|
|
46
|
+
maxTokens: config.judgeMaxTokens,
|
|
43
47
|
timeoutMs: config.judgeTimeoutMs,
|
|
44
48
|
}),
|
|
45
49
|
});
|
|
46
50
|
if (decision.verdict === 'inactive')
|
|
47
51
|
return null;
|
|
48
|
-
const outcome = {};
|
|
52
|
+
const outcome = { ...goalSnapshot(manager) };
|
|
49
53
|
if (decision.message)
|
|
50
54
|
outcome.message = decision.message;
|
|
51
55
|
if (decision.shouldContinue && decision.continuationPrompt) {
|
|
@@ -53,4 +57,45 @@ export async function maybeContinueGoalAfterTurn(options) {
|
|
|
53
57
|
}
|
|
54
58
|
return outcome;
|
|
55
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Structured per-session goal snapshot for host UIs. Reads the manager's
|
|
62
|
+
* post-decision state; returns an empty object when no state is present so
|
|
63
|
+
* spreading it is always safe.
|
|
64
|
+
*/
|
|
65
|
+
function goalSnapshot(manager) {
|
|
66
|
+
const s = manager.state;
|
|
67
|
+
if (!s)
|
|
68
|
+
return {};
|
|
69
|
+
return {
|
|
70
|
+
goalText: s.goal,
|
|
71
|
+
status: s.status,
|
|
72
|
+
turnsUsed: s.turnsUsed,
|
|
73
|
+
maxTurns: s.maxTurns,
|
|
74
|
+
...(s.lastVerdict ? { lastVerdict: s.lastVerdict } : {}),
|
|
75
|
+
...(s.lastReason ? { lastReason: s.lastReason } : {}),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
async function maybeAttachGoalPlan(manager, client, model) {
|
|
79
|
+
const state = manager.state;
|
|
80
|
+
if (!state || state.goalPlan || state.goalPlanAttempted)
|
|
81
|
+
return;
|
|
82
|
+
if (!client || !shouldAutoDecomposeGoal(state.goal))
|
|
83
|
+
return;
|
|
84
|
+
try {
|
|
85
|
+
const plan = await decomposeGoal(state.goal, client, {
|
|
86
|
+
...(model ? { model } : {}),
|
|
87
|
+
});
|
|
88
|
+
if (plan) {
|
|
89
|
+
manager.attachGoalPlan(plan);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
manager.markGoalPlanAttempted('planner returned no usable task graph');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
97
|
+
manager.markGoalPlanAttempted(message);
|
|
98
|
+
logger.debug('goal decomposition failed', { error: message });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
56
101
|
//# sourceMappingURL=goal-loop.js.map
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* the manager (and its tests) never touch a provider.
|
|
10
10
|
*/
|
|
11
11
|
import { GoalJudgeFn } from './goal-judge.js';
|
|
12
|
+
import type { GoalPlan } from './goal-decomposer.js';
|
|
12
13
|
import { GoalState, GoalStatus, GoalVerdict } from './goal-state.js';
|
|
13
14
|
import { GoalStore } from './goal-store.js';
|
|
14
15
|
export interface GoalTurnDecision {
|
|
@@ -23,6 +24,7 @@ export interface GoalTurnDecision {
|
|
|
23
24
|
export interface GoalsConfig {
|
|
24
25
|
maxTurns: number;
|
|
25
26
|
judgeModel: string;
|
|
27
|
+
plannerModel: string;
|
|
26
28
|
judgeMaxTokens: number;
|
|
27
29
|
judgeTimeoutMs: number;
|
|
28
30
|
}
|
|
@@ -38,7 +40,11 @@ export declare class GoalManager {
|
|
|
38
40
|
statusLine(): string;
|
|
39
41
|
set(goal: string, options?: {
|
|
40
42
|
maxTurns?: number;
|
|
43
|
+
goalPlan?: GoalPlan;
|
|
44
|
+
goalPlanAttempted?: boolean;
|
|
41
45
|
}): GoalState;
|
|
46
|
+
attachGoalPlan(plan: GoalPlan): GoalState | null;
|
|
47
|
+
markGoalPlanAttempted(error?: string): GoalState | null;
|
|
42
48
|
pause(reason?: string): GoalState | null;
|
|
43
49
|
resume(options?: {
|
|
44
50
|
resetBudget?: boolean;
|