bingocode 1.1.182 → 1.1.184
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/.claude/scheduled_tasks.json +34 -0
- package/.claude/skills/leanchy/SKILL.md +1 -1
- package/package.json +1 -1
- package/src/bootstrap/state.ts +61 -13
- package/src/hooks/useGoalAutoUpdater.ts +187 -0
- package/src/hooks/useGoalEvaluator.ts +29 -16
- package/src/query.ts +21 -0
- package/src/skills/bundled/goal.ts +41 -32
- package/src/types/goal.ts +322 -0
- package/src/utils/__tests__/goalDag.test.ts +177 -0
- package/src/utils/__tests__/goalDrift.test.ts +132 -0
- package/src/utils/__tests__/goalIntegration.test.ts +217 -0
- package/src/utils/__tests__/goalStore.test.ts +592 -0
- package/src/utils/goalDag.ts +408 -0
- package/src/utils/goalDrift.ts +243 -0
- package/src/utils/goalEvaluator.ts +708 -122
- package/src/utils/goalHelpers.ts +155 -0
- package/src/utils/goalPersistence.ts +206 -0
- package/src/utils/goalStore.ts +463 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tasks": [
|
|
3
|
+
{
|
|
4
|
+
"id": "274f11cf",
|
|
5
|
+
"cron": "0 9 * * *",
|
|
6
|
+
"prompt": "test prompt",
|
|
7
|
+
"createdAt": 1782997215925,
|
|
8
|
+
"recurring": true,
|
|
9
|
+
"name": "test-name",
|
|
10
|
+
"description": "test description",
|
|
11
|
+
"folder": "/test/folder",
|
|
12
|
+
"model": "claude-opus-4-7",
|
|
13
|
+
"permissionMode": "ask",
|
|
14
|
+
"worktree": false,
|
|
15
|
+
"frequency": "daily",
|
|
16
|
+
"scheduledTime": "09:00"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "1c4152a1",
|
|
20
|
+
"cron": "0 9 * * *",
|
|
21
|
+
"prompt": "test prompt",
|
|
22
|
+
"createdAt": 1782998699654,
|
|
23
|
+
"recurring": true,
|
|
24
|
+
"name": "test-name",
|
|
25
|
+
"description": "test description",
|
|
26
|
+
"folder": "/test/folder",
|
|
27
|
+
"model": "claude-opus-4-7",
|
|
28
|
+
"permissionMode": "ask",
|
|
29
|
+
"worktree": false,
|
|
30
|
+
"frequency": "daily",
|
|
31
|
+
"scheduledTime": "09:00"
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
@@ -52,7 +52,7 @@ Example — destructive op:
|
|
|
52
52
|
- Embrace full context: system-wide impact, underlying motivations.
|
|
53
53
|
|
|
54
54
|
## Execution
|
|
55
|
-
- Confirm DoD before start. Lead with conclusions. Append reasoning only if asked.
|
|
55
|
+
- Confirm DoD with user before start. Lead with conclusions. Append reasoning only if asked.
|
|
56
56
|
- Every token earns its place. (Communication defines concrete rules.)
|
|
57
57
|
|
|
58
58
|
## Diagnosis
|
package/package.json
CHANGED
package/src/bootstrap/state.ts
CHANGED
|
@@ -1793,38 +1793,86 @@ export function setPromptId(id: string | null): void {
|
|
|
1793
1793
|
// /goal session state accessors
|
|
1794
1794
|
// ============================================================================
|
|
1795
1795
|
|
|
1796
|
+
// ============================================================================
|
|
1797
|
+
// /goal session state accessors (DEPRECATED — use goalStore.ts instead)
|
|
1798
|
+
// ============================================================================
|
|
1799
|
+
// These accessors are kept for backwards compatibility with existing code.
|
|
1800
|
+
// They delegate to the new GoalStore in src/utils/goalStore.ts.
|
|
1801
|
+
// New code should import from goalStore.ts directly.
|
|
1802
|
+
// TODO: remove after all consumers are migrated.
|
|
1803
|
+
|
|
1804
|
+
import { getGoalState as _getGoalState } from '../utils/goalStore.js'
|
|
1805
|
+
|
|
1796
1806
|
export function getGoalCondition(): string | null {
|
|
1797
|
-
|
|
1807
|
+
try {
|
|
1808
|
+
return _getGoalState().userGoal?.text ?? _getGoalState().operationalGoal?.text ?? null
|
|
1809
|
+
} catch {
|
|
1810
|
+
return STATE.goalCondition // fallback to old field during migration
|
|
1811
|
+
}
|
|
1798
1812
|
}
|
|
1799
1813
|
|
|
1800
1814
|
export function setGoalCondition(condition: string | null): void {
|
|
1801
|
-
|
|
1802
|
-
|
|
1815
|
+
if (condition) {
|
|
1816
|
+
try {
|
|
1817
|
+
// Delegate to new store for structured goal management
|
|
1818
|
+
const { setUserGoal: _setUserGoal, setOperationalGoal: _setOperationalGoal } = require('../utils/goalStore.js')
|
|
1819
|
+
_setUserGoal(condition)
|
|
1820
|
+
_setOperationalGoal(condition)
|
|
1821
|
+
} catch {
|
|
1822
|
+
STATE.goalCondition = condition // fallback
|
|
1823
|
+
STATE.goalIterationCount = 0
|
|
1824
|
+
}
|
|
1825
|
+
} else {
|
|
1826
|
+
STATE.goalCondition = null
|
|
1827
|
+
STATE.goalIterationCount = 0
|
|
1828
|
+
}
|
|
1803
1829
|
}
|
|
1804
1830
|
|
|
1805
1831
|
export function getGoalIterationCount(): number {
|
|
1806
|
-
|
|
1832
|
+
try {
|
|
1833
|
+
return _getGoalState().metrics.iterationCount
|
|
1834
|
+
} catch {
|
|
1835
|
+
return STATE.goalIterationCount
|
|
1836
|
+
}
|
|
1807
1837
|
}
|
|
1808
1838
|
|
|
1809
1839
|
export function incrementGoalIterationCount(): void {
|
|
1810
|
-
|
|
1840
|
+
try {
|
|
1841
|
+
const { incrementIteration } = require('../utils/goalStore.js')
|
|
1842
|
+
incrementIteration()
|
|
1843
|
+
} catch {
|
|
1844
|
+
STATE.goalIterationCount++
|
|
1845
|
+
}
|
|
1811
1846
|
}
|
|
1812
1847
|
|
|
1813
1848
|
export function getGoalMaxIterations(): number {
|
|
1814
|
-
|
|
1849
|
+
try {
|
|
1850
|
+
return _getGoalState().metrics.maxIterations
|
|
1851
|
+
} catch {
|
|
1852
|
+
return STATE.goalMaxIterations
|
|
1853
|
+
}
|
|
1815
1854
|
}
|
|
1816
1855
|
|
|
1817
|
-
// Goal evaluator history accessors
|
|
1856
|
+
// Goal evaluator history accessors (DEPRECATED — use goalStore.ts)
|
|
1818
1857
|
export function getGoalEvalHistory() {
|
|
1819
|
-
|
|
1858
|
+
try {
|
|
1859
|
+
return _getGoalState().metrics.evalHistory
|
|
1860
|
+
} catch {
|
|
1861
|
+
return STATE.goalEvalHistory
|
|
1862
|
+
}
|
|
1820
1863
|
}
|
|
1821
1864
|
|
|
1822
1865
|
export function updateGoalEvalHistory(lastGap: string | null): void {
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
STATE.goalEvalHistory.
|
|
1866
|
+
try {
|
|
1867
|
+
const { recordEvalGap: _recordEvalGap } = require('../utils/goalStore.js')
|
|
1868
|
+
_recordEvalGap(lastGap)
|
|
1869
|
+
} catch {
|
|
1870
|
+
if (lastGap === STATE.goalEvalHistory.lastGap) {
|
|
1871
|
+
STATE.goalEvalHistory.consecutiveSameGapCount++
|
|
1872
|
+
} else {
|
|
1873
|
+
STATE.goalEvalHistory.lastGap = lastGap
|
|
1874
|
+
STATE.goalEvalHistory.consecutiveSameGapCount = 1
|
|
1875
|
+
}
|
|
1828
1876
|
}
|
|
1829
1877
|
}
|
|
1830
1878
|
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Goal Auto-Updater Hook — bridges agent tool execution with goal state.
|
|
3
|
+
*
|
|
4
|
+
* Monitors PostToolUse events and automatically updates goal state in real-time.
|
|
5
|
+
* This replaces the manual "output EVAL blocks" approach with automatic state
|
|
6
|
+
* tracking based on what the agent actually does.
|
|
7
|
+
*
|
|
8
|
+
* Integration point: called in REPL.tsx after each tool execution completes.
|
|
9
|
+
* Uses the GoalStore to update state, which triggers re-renders in any
|
|
10
|
+
* subscribed components via the signal pattern.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { ToolResult, SubGoalStatus } from '../types/goal.js'
|
|
14
|
+
import { getGoalState, addArtifact, updateSubGoalStatus, recalculateProgress } from '../utils/goalStore.js'
|
|
15
|
+
import { asSubGoalId } from '../utils/goalHelpers.js'
|
|
16
|
+
import { assessDrift } from '../utils/goalDrift.js'
|
|
17
|
+
import type { DriftAssessment, SubGoal } from '../types/goal.js'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Classify a tool execution result into a goal-relevant action category.
|
|
21
|
+
* Returns null if the tool is not relevant to goal progress tracking.
|
|
22
|
+
*/
|
|
23
|
+
function classifyToolAction(
|
|
24
|
+
toolName: string,
|
|
25
|
+
toolInput: Record<string, unknown>,
|
|
26
|
+
toolOutput: string,
|
|
27
|
+
success: boolean,
|
|
28
|
+
): ToolAction | null {
|
|
29
|
+
// Read-only tools (query) → informational, not state-changing
|
|
30
|
+
if (isReadTool(toolName)) return null
|
|
31
|
+
|
|
32
|
+
// Write tools (create/modify)
|
|
33
|
+
if (isCreateTool(toolName)) {
|
|
34
|
+
const filePath = extractFilePath(toolInput)
|
|
35
|
+
return {
|
|
36
|
+
type: 'create',
|
|
37
|
+
filePath,
|
|
38
|
+
subGoalId: inferSubGoal(toolName, filePath),
|
|
39
|
+
description: `Created ${filePath}`,
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Build tools (verify)
|
|
44
|
+
if (isVerifyTool(toolName)) {
|
|
45
|
+
return {
|
|
46
|
+
type: 'verify',
|
|
47
|
+
filePath: extractFilePath(toolInput),
|
|
48
|
+
subGoalId: inferSubGoal(toolName, extractFilePath(toolInput)),
|
|
49
|
+
description: `Verified with ${toolName}`,
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Default: generic mutation
|
|
54
|
+
return {
|
|
55
|
+
type: 'modify',
|
|
56
|
+
filePath: extractFilePath(toolInput),
|
|
57
|
+
subGoalId: inferSubGoal(toolName, extractFilePath(toolInput)),
|
|
58
|
+
description: `Modified via ${toolName}`,
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
type ToolAction = {
|
|
63
|
+
type: 'create' | 'modify' | 'verify'
|
|
64
|
+
filePath: string
|
|
65
|
+
subGoalId: string
|
|
66
|
+
description: string
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Update goal state after a tool execution. Called from REPL.tsx after
|
|
70
|
+
* each PostToolUse event. Also performs drift assessment to detect
|
|
71
|
+
* when agent behavior starts deviating from target goals. */
|
|
72
|
+
export function useGoalAutoUpdater(
|
|
73
|
+
toolResult: ToolResult,
|
|
74
|
+
): void {
|
|
75
|
+
const state = getGoalState()
|
|
76
|
+
if (!state.activeGoalId) return
|
|
77
|
+
if (!state.operationalGoal) return
|
|
78
|
+
|
|
79
|
+
const action = classifyToolAction(
|
|
80
|
+
toolResult.toolName,
|
|
81
|
+
toolResult.toolInput,
|
|
82
|
+
toolResult.toolOutput,
|
|
83
|
+
toolResult.success,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
if (!action) return
|
|
87
|
+
|
|
88
|
+
// Phase 1: Update goal state based on action type
|
|
89
|
+
switch (action.type) {
|
|
90
|
+
case 'create':
|
|
91
|
+
addArtifact(action.filePath)
|
|
92
|
+
break
|
|
93
|
+
case 'modify':
|
|
94
|
+
addArtifact(action.filePath)
|
|
95
|
+
break
|
|
96
|
+
case 'verify':
|
|
97
|
+
updateSubGoalStatus(action.subGoalId, 'active')
|
|
98
|
+
break
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Phase 2: Recalculate progress after state mutation
|
|
102
|
+
recalculateProgress()
|
|
103
|
+
|
|
104
|
+
// Phase 3: Assess drift (after state update, before next turn)
|
|
105
|
+
const currentSubGoal = state.dag.nodes.get(action.subGoalId)
|
|
106
|
+
if (currentSubGoal) {
|
|
107
|
+
const drift = assessDrift(
|
|
108
|
+
action.description ?? `${toolResult.toolName}: ${toolResult.toolOutput.slice(0, 100)}`,
|
|
109
|
+
currentSubGoal,
|
|
110
|
+
state.operationalGoal.text,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
// Log drift warnings for debugging — future: escalate to enqueue()
|
|
114
|
+
if (drift.warnings.length > 0) {
|
|
115
|
+
const highWarnings = drift.warnings.filter(w => w.severity === 'high')
|
|
116
|
+
if (highWarnings.length > 0) {
|
|
117
|
+
// Drift is severe — agent should replan
|
|
118
|
+
// TODO: wire into enqueue() for user-visible warning
|
|
119
|
+
console.warn(`[GoalDrift] ${drift.warnings.length} warning(s): score ${drift.consistencyScore}%, trend ${drift.trend}`)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ============================================================================
|
|
126
|
+
// Helpers
|
|
127
|
+
// ============================================================================
|
|
128
|
+
|
|
129
|
+
/** Check if a tool is read-only (doesn't modify state). */
|
|
130
|
+
function isReadTool(toolName: string): boolean {
|
|
131
|
+
const readTools = new Set([
|
|
132
|
+
'Read',
|
|
133
|
+
'Glob',
|
|
134
|
+
'Grep',
|
|
135
|
+
'LS',
|
|
136
|
+
'WebSearch',
|
|
137
|
+
'WebFetch',
|
|
138
|
+
'TaskOutput',
|
|
139
|
+
'TaskStatus',
|
|
140
|
+
])
|
|
141
|
+
return readTools.has(toolName)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Check if a tool creates new files. */
|
|
145
|
+
function isCreateTool(toolName: string): boolean {
|
|
146
|
+
const createTools = new Set([
|
|
147
|
+
'Write',
|
|
148
|
+
'Edit', // can create or modify, but classified as create for new files
|
|
149
|
+
'NotebookEdit',
|
|
150
|
+
])
|
|
151
|
+
return createTools.has(toolName)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Check if a tool verifies existing work (tests, builds). */
|
|
155
|
+
function isVerifyTool(toolName: string): boolean {
|
|
156
|
+
const verifyTools = new Set([
|
|
157
|
+
'Bash', // often used for test/build commands
|
|
158
|
+
])
|
|
159
|
+
return verifyTools.has(toolName)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Extract file path from tool input. Handles different tool input shapes. */
|
|
163
|
+
function extractFilePath(input: Record<string, unknown>): string {
|
|
164
|
+
if (typeof input.file_path === 'string') return input.file_path
|
|
165
|
+
if (typeof input.path === 'string') return input.path
|
|
166
|
+
if (typeof input.notebook_path === 'string') return input.notebook_path
|
|
167
|
+
return ''
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Infer which sub-goal this tool action likely belongs to.
|
|
171
|
+
* Uses file path heuristics to match against known sub-goals. */
|
|
172
|
+
function inferSubGoal(toolName: string, filePath: string): string {
|
|
173
|
+
// For now, use a simple heuristic — match by file path pattern
|
|
174
|
+
// This will be enhanced in Phase 6 with actual DAG integration
|
|
175
|
+
if (!filePath) return 'unknown'
|
|
176
|
+
|
|
177
|
+
// Strip the path and use the filename as a sub-goal identifier
|
|
178
|
+
// In practice, the real mapping will come from the DAG's SubGoal nodes
|
|
179
|
+
const fileName = filePath.split('/').pop()?.split('\\').pop() ?? filePath
|
|
180
|
+
return `task-${fileName}`
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ============================================================================
|
|
184
|
+
// Re-export for convenience
|
|
185
|
+
// ============================================================================
|
|
186
|
+
|
|
187
|
+
export { getGoalState as useGoalStore }
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Goal evaluator hook — post-turn goal assessment.
|
|
3
|
+
*
|
|
4
|
+
* Fires after each turn completes (triggered by lastQueryCompletionTime change).
|
|
5
|
+
* Evaluates progress toward active goal using env-aware evaluator v2.
|
|
6
|
+
*/
|
|
7
|
+
|
|
1
8
|
import { useEffect, useRef } from 'react'
|
|
2
9
|
import type { MutableRefObject } from 'react'
|
|
3
10
|
import type { MessageType } from '../components/messages.js'
|
|
@@ -10,6 +17,7 @@ import {
|
|
|
10
17
|
getGoalEvalHistory,
|
|
11
18
|
updateGoalEvalHistory,
|
|
12
19
|
} from '../bootstrap/state.js'
|
|
20
|
+
import { getGoalState, recalculateProgress } from '../utils/goalStore.js'
|
|
13
21
|
import { enqueue } from '../utils/messageQueueManager.js'
|
|
14
22
|
import { evaluateGoal } from '../utils/goalEvaluator.js'
|
|
15
23
|
|
|
@@ -20,16 +28,15 @@ type UseGoalEvaluatorParams = {
|
|
|
20
28
|
}
|
|
21
29
|
|
|
22
30
|
/**
|
|
23
|
-
* React hook
|
|
24
|
-
*
|
|
25
|
-
* Triggered by lastQueryCompletionTime changing (set in REPL.tsx after
|
|
26
|
-
* queryGuard.end() succeeds). Uses messagesRef (not React state) to avoid
|
|
27
|
-
* batching issues — the ref is synchronously updated via Zustand pattern
|
|
28
|
-
* before React re-renders.
|
|
31
|
+
* React hook — fires after each turn. Triggered by lastQueryCompletionTime.
|
|
32
|
+
* Uses messagesRef (sync ref, not React state) to avoid batching issues.
|
|
29
33
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
34
|
+
* Flow:
|
|
35
|
+
* 1. Check max iterations reached → if so, stop
|
|
36
|
+
* 2. Call env-aware evaluator v2 (rule engine + Haiku + main model)
|
|
37
|
+
* 3. On satisfied → clear goal, enqueue success message
|
|
38
|
+
* 4. On repeated gap (3x) → circuit breaker, clear goal
|
|
39
|
+
* 5. On not satisfied → enqueue continuation message
|
|
33
40
|
*/
|
|
34
41
|
export function useGoalEvaluator({
|
|
35
42
|
lastQueryCompletionTime,
|
|
@@ -55,6 +62,7 @@ export function useGoalEvaluator({
|
|
|
55
62
|
const iterCount = getGoalIterationCount()
|
|
56
63
|
const maxIter = getGoalMaxIterations()
|
|
57
64
|
|
|
65
|
+
// Max iterations check
|
|
58
66
|
if (iterCount >= maxIter) {
|
|
59
67
|
setGoalCondition(null)
|
|
60
68
|
enqueue({
|
|
@@ -65,17 +73,24 @@ export function useGoalEvaluator({
|
|
|
65
73
|
return
|
|
66
74
|
}
|
|
67
75
|
|
|
68
|
-
//
|
|
76
|
+
// Get current goal state + sub-goal from store
|
|
69
77
|
const messages = messagesRef.current
|
|
70
|
-
const
|
|
78
|
+
const goalState = getGoalState()
|
|
79
|
+
const currentSubGoal = goalState.immediateGoal
|
|
80
|
+
? goalState.dag.nodes.get(goalState.immediateGoal.subGoalId)
|
|
81
|
+
: undefined
|
|
82
|
+
|
|
83
|
+
// Run environment-aware evaluation
|
|
84
|
+
const result = await evaluateGoal(condition, messages, currentSubGoal)
|
|
71
85
|
|
|
72
|
-
// Race
|
|
86
|
+
// Race: user may have cleared goal during await
|
|
73
87
|
if (getGoalCondition() !== condition) return
|
|
74
88
|
|
|
75
89
|
incrementGoalIterationCount()
|
|
76
90
|
updateGoalEvalHistory(result.gap)
|
|
91
|
+
recalculateProgress()
|
|
77
92
|
|
|
78
|
-
//
|
|
93
|
+
// Circuit breaker: 3 consecutive same gap
|
|
79
94
|
const evalHistory = getGoalEvalHistory()
|
|
80
95
|
const isRepeatedGap = evalHistory.consecutiveSameGapCount >= 3 && result.gap !== null
|
|
81
96
|
|
|
@@ -87,10 +102,9 @@ export function useGoalEvaluator({
|
|
|
87
102
|
priority: 'now',
|
|
88
103
|
})
|
|
89
104
|
} else if (isRepeatedGap) {
|
|
90
|
-
// Circuit breaker: stop after 3 repeated gaps
|
|
91
105
|
setGoalCondition(null)
|
|
92
106
|
enqueue({
|
|
93
|
-
value: `⚠️ Goal evaluator stopped
|
|
107
|
+
value: `⚠️ Goal evaluator stopped — same gap "${result.gap}" 3x. Adjust approach or output EVAL blocks.`,
|
|
94
108
|
mode: 'task-notification',
|
|
95
109
|
priority: 'now',
|
|
96
110
|
})
|
|
@@ -108,6 +122,5 @@ export function useGoalEvaluator({
|
|
|
108
122
|
evaluating.current = false
|
|
109
123
|
}
|
|
110
124
|
})()
|
|
111
|
-
// messages intentionally excluded from deps — read via ref to avoid batching issues
|
|
112
125
|
}, [lastQueryCompletionTime, isQueryActive])
|
|
113
126
|
}
|
package/src/query.ts
CHANGED
|
@@ -110,6 +110,8 @@ import {
|
|
|
110
110
|
} from './bootstrap/state.js'
|
|
111
111
|
import { createBudgetTracker, checkTokenBudget } from './query/tokenBudget.js'
|
|
112
112
|
import { count } from './utils/array.js'
|
|
113
|
+
import { convertSDKToolResults } from './utils/goalHelpers.js'
|
|
114
|
+
import { useGoalAutoUpdater } from './hooks/useGoalAutoUpdater.js'
|
|
113
115
|
|
|
114
116
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
115
117
|
const snipModule = feature('HISTORY_SNIP')
|
|
@@ -1408,6 +1410,25 @@ async function* queryLoop(
|
|
|
1408
1410
|
}
|
|
1409
1411
|
queryCheckpoint('query_tool_execution_end')
|
|
1410
1412
|
|
|
1413
|
+
// === Goal system auto-update: PostToolUse hook ===
|
|
1414
|
+
// Update goal state based on actual tool execution results.
|
|
1415
|
+
// This replaces the old "output EVAL blocks" approach — now we track
|
|
1416
|
+
// progress automatically from what the agent actually does.
|
|
1417
|
+
if (toolUseBlocks.length > 0) {
|
|
1418
|
+
try {
|
|
1419
|
+
const goalResults = convertSDKToolResults(
|
|
1420
|
+
toolUseBlocks as ReadonlyArray<{ id: string; name: string; input: Record<string, unknown> }>,
|
|
1421
|
+
toolResults as ReadonlyArray<{ type: string; message: { content: { type: string; text?: string; tool_use_id?: string; content?: string }[] } }>,
|
|
1422
|
+
)
|
|
1423
|
+
for (const result of goalResults) {
|
|
1424
|
+
useGoalAutoUpdater(result)
|
|
1425
|
+
}
|
|
1426
|
+
} catch (err) {
|
|
1427
|
+
// Goal auto-update is non-critical — don't block the main loop
|
|
1428
|
+
logError('goal_auto_update_error', err instanceof Error ? err.message : String(err))
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1411
1432
|
// Generate tool use summary after tool batch completes — passed to next recursive call
|
|
1412
1433
|
let nextPendingToolUseSummary:
|
|
1413
1434
|
| Promise<ToolUseSummaryMessage | null>
|
|
@@ -1,27 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Goal command — CLI entry point for Goal Management system.
|
|
3
|
+
*
|
|
4
|
+
* 4-layer architecture: User Goal → Operational Goal → Sub Goals → Immediate Goal.
|
|
5
|
+
* Orchestrates creation, status queries, and management commands.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getGoalState, setUserGoal, setOperationalGoal, clearState } from '../../utils/goalStore.js'
|
|
6
9
|
import { registerBundledSkill } from '../bundledSkills.js'
|
|
7
10
|
|
|
8
|
-
const USAGE = `Usage: /goal <
|
|
11
|
+
const USAGE = `Usage: /goal <create | clear | cancel>
|
|
9
12
|
|
|
10
|
-
Set a session goal.
|
|
13
|
+
Set a session goal. Agent will work autonomously until goal met.
|
|
11
14
|
|
|
12
15
|
Examples:
|
|
13
|
-
/goal all tests pass
|
|
14
|
-
/goal login flow handles empty email without crash
|
|
15
|
-
/goal PR
|
|
16
|
+
/goal create all tests pass
|
|
17
|
+
/goal create login flow handles empty email without crash
|
|
18
|
+
/goal create PR ready for review with passing CI
|
|
16
19
|
|
|
17
|
-
To cancel: /goal clear`
|
|
20
|
+
To cancel: /goal clear or /goal cancel`
|
|
18
21
|
|
|
19
22
|
export function registerGoalSkill(): void {
|
|
20
23
|
registerBundledSkill({
|
|
21
24
|
name: 'goal',
|
|
22
25
|
description:
|
|
23
|
-
'
|
|
24
|
-
argumentHint: '<
|
|
26
|
+
'Manage task goals with hierarchical decomposition and progress tracking.',
|
|
27
|
+
argumentHint: '<create | clear | cancel>',
|
|
25
28
|
userInvocable: true,
|
|
26
29
|
async getPromptForCommand(args) {
|
|
27
30
|
const trimmed = args.trim()
|
|
@@ -30,48 +33,54 @@ export function registerGoalSkill(): void {
|
|
|
30
33
|
return [{ type: 'text', text: USAGE }]
|
|
31
34
|
}
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
// --- Cancel / Clear ---
|
|
37
|
+
if (['clear', 'stop', 'cancel'].includes(trimmed.toLowerCase())) {
|
|
38
|
+
const state = getGoalState()
|
|
39
|
+
if (state.activeGoalId) {
|
|
40
|
+
const userText = state.userGoal?.text ?? '(unknown)'
|
|
41
|
+
clearState()
|
|
37
42
|
return [
|
|
38
43
|
{
|
|
39
44
|
type: 'text',
|
|
40
|
-
text: `Goal cancelled: "${
|
|
45
|
+
text: `Goal cancelled: "${userText}". Goal state cleared from memory and disk.`,
|
|
41
46
|
},
|
|
42
47
|
]
|
|
43
48
|
}
|
|
44
49
|
return [
|
|
45
50
|
{
|
|
46
51
|
type: 'text',
|
|
47
|
-
text: 'No active goal
|
|
52
|
+
text: 'No active goal. Use `/goal create <text>` to start.',
|
|
48
53
|
},
|
|
49
54
|
]
|
|
50
55
|
}
|
|
51
56
|
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
// --- Create ---
|
|
58
|
+
setUserGoal(trimmed)
|
|
59
|
+
setOperationalGoal(trimmed)
|
|
60
|
+
const state = getGoalState()
|
|
61
|
+
const maxIter = state.metrics.maxIterations
|
|
54
62
|
|
|
55
63
|
return [
|
|
56
64
|
{
|
|
57
65
|
type: 'text',
|
|
58
66
|
text: `# /goal activated
|
|
59
67
|
|
|
60
|
-
Goal
|
|
61
|
-
|
|
62
|
-
|
|
68
|
+
**Goal**: "${trimmed}"
|
|
69
|
+
**Architecture**: User Goal → Operational Goal → DAG Sub Goals → Immediate Action
|
|
70
|
+
**Max iterations**: ${maxIter}
|
|
63
71
|
|
|
64
|
-
|
|
72
|
+
The evaluator now uses **environment-aware assessment** (3 tiers):
|
|
73
|
+
Level 1: Rule engine — checks file existence, test output, compile status
|
|
74
|
+
Level 2: Haiku 4.5 — semantic interpretation of evidence
|
|
75
|
+
Level 3: Main model — final verification (ambiguous cases only)
|
|
65
76
|
|
|
66
|
-
|
|
67
|
-
|
|
77
|
+
EVAL blocks still welcome for agent self-reporting, but no longer required.
|
|
78
|
+
Format: \`EVAL: metric: value / target → ✓ or ✗\`
|
|
68
79
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
Note: The EVAL block can appear anywhere in your text response (not just in quote blocks).
|
|
80
|
+
Tell user: Goal set. Agent works autonomously until "${trimmed}" achieved (max ${maxIter} turns).
|
|
81
|
+
Send \`/goal clear\` to cancel.
|
|
72
82
|
|
|
73
|
-
|
|
74
|
-
Now begin: assess current state and take the first concrete action toward the goal.`,
|
|
83
|
+
Now: assess current state, take first concrete action toward goal.`,
|
|
75
84
|
},
|
|
76
85
|
]
|
|
77
86
|
},
|