bingocode 1.1.183 → 1.1.185

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.
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bingocode",
3
- "version": "1.1.183",
3
+ "version": "1.1.185",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "claude": "bin/claude-win.cjs",
@@ -135,16 +135,7 @@ type State = {
135
135
  // (useScheduledTasks). Set by cronScheduler.start() when the JSON has
136
136
  // entries, or by CronCreateTool. Not persisted.
137
137
  scheduledTasksEnabled: boolean
138
- // Session-only goal condition for /goal command. Null when no active goal.
139
- goalCondition: string | null
140
- goalIterationCount: number
141
- goalMaxIterations: number
142
- // Goal evaluator history for detecting repeated gaps
143
- goalEvalHistory: {
144
- lastGap: string | null
145
- consecutiveSameGapCount: number
146
- }
147
- // Session-only cron tasks created via CronCreate with durable: false.
138
+ // Session-only cron tasks created via CronCreate with durable: false.
148
139
  // Fire on schedule like file-backed tasks but are never written to
149
140
  // .claude/scheduled_tasks.json — they die with the process. Typed via
150
141
  // SessionCronTask below (not importing from cronTasks.ts keeps
@@ -366,15 +357,7 @@ function getInitialState(): State {
366
357
  sessionBypassPermissionsMode: false,
367
358
  // Scheduled tasks disabled until flag or dialog enables them
368
359
  scheduledTasksEnabled: false,
369
- // Goal condition for /goal command (null = no active goal)
370
- goalCondition: null,
371
- goalIterationCount: 0,
372
- goalMaxIterations: 20,
373
- goalEvalHistory: {
374
- lastGap: null,
375
- consecutiveSameGapCount: 0,
376
- },
377
- sessionCronTasks: [],
360
+ sessionCronTasks: [],
378
361
  sessionCreatedTeams: new Set(),
379
362
  // Session-only trust flag (not persisted to disk)
380
363
  sessionTrustAccepted: false,
@@ -1793,38 +1776,5 @@ export function setPromptId(id: string | null): void {
1793
1776
  // /goal session state accessors
1794
1777
  // ============================================================================
1795
1778
 
1796
- export function getGoalCondition(): string | null {
1797
- return STATE.goalCondition
1798
- }
1799
-
1800
- export function setGoalCondition(condition: string | null): void {
1801
- STATE.goalCondition = condition
1802
- STATE.goalIterationCount = 0
1803
- }
1804
-
1805
- export function getGoalIterationCount(): number {
1806
- return STATE.goalIterationCount
1807
- }
1808
-
1809
- export function incrementGoalIterationCount(): void {
1810
- STATE.goalIterationCount++
1811
- }
1812
-
1813
- export function getGoalMaxIterations(): number {
1814
- return STATE.goalMaxIterations
1815
- }
1816
-
1817
- // Goal evaluator history accessors
1818
- export function getGoalEvalHistory() {
1819
- return STATE.goalEvalHistory
1820
- }
1821
-
1822
- export function updateGoalEvalHistory(lastGap: string | null): void {
1823
- if (lastGap === STATE.goalEvalHistory.lastGap) {
1824
- STATE.goalEvalHistory.consecutiveSameGapCount++
1825
- } else {
1826
- STATE.goalEvalHistory.lastGap = lastGap
1827
- STATE.goalEvalHistory.consecutiveSameGapCount = 1
1828
- }
1829
- }
1779
+ // ============================================================================
1830
1780
 
@@ -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,15 +1,25 @@
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
+ * Migrated from old state.ts flat fields to the new GoalStore API.
8
+ * All state access goes through GoalStore — no more STATE.goalCondition fallbacks.
9
+ */
10
+
1
11
  import { useEffect, useRef } from 'react'
2
12
  import type { MutableRefObject } from 'react'
3
13
  import type { MessageType } from '../components/messages.js'
4
14
  import {
5
- getGoalCondition,
6
- getGoalIterationCount,
7
- getGoalMaxIterations,
8
- incrementGoalIterationCount,
9
- setGoalCondition,
10
- getGoalEvalHistory,
11
- updateGoalEvalHistory,
12
- } from '../bootstrap/state.js'
15
+ getGoalState,
16
+ recalculateProgress,
17
+ incrementIteration,
18
+ recordEvalGap,
19
+ getActiveGoalId,
20
+ getReadySubGoals,
21
+ getBlockedSubGoals,
22
+ } from '../utils/goalStore.js'
13
23
  import { enqueue } from '../utils/messageQueueManager.js'
14
24
  import { evaluateGoal } from '../utils/goalEvaluator.js'
15
25
 
@@ -20,16 +30,15 @@ type UseGoalEvaluatorParams = {
20
30
  }
21
31
 
22
32
  /**
23
- * React hook that fires an independent goal evaluator after each turn.
33
+ * React hook fires after each turn. Triggered by lastQueryCompletionTime.
34
+ * Uses messagesRef (sync ref, not React state) to avoid batching issues.
24
35
  *
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.
29
- *
30
- * On goal not satisfied: enqueues a continuation message with priority 'now'
31
- * so useQueueProcessor picks it up immediately for the next turn.
32
- * On goal satisfied or max iterations reached: clears the goal condition.
36
+ * Flow:
37
+ * 1. Check max iterations reached if so, stop
38
+ * 2. Call env-aware evaluator v2 (rule engine + Haiku + main model)
39
+ * 3. On satisfied → clear goal, enqueue success message
40
+ * 4. On repeated gap (3x) → circuit breaker, clear goal
41
+ * 5. On not satisfied enqueue continuation message
33
42
  */
34
43
  export function useGoalEvaluator({
35
44
  lastQueryCompletionTime,
@@ -40,7 +49,8 @@ export function useGoalEvaluator({
40
49
  const evaluating = useRef(false)
41
50
 
42
51
  useEffect(() => {
43
- const condition = getGoalCondition()
52
+ const goalState = getGoalState()
53
+ const condition = goalState.userGoal?.text ?? null
44
54
  if (!condition) return
45
55
  if (isQueryActive) return
46
56
  if (lastQueryCompletionTime === 0) return
@@ -52,49 +62,71 @@ export function useGoalEvaluator({
52
62
 
53
63
  void (async () => {
54
64
  try {
55
- const iterCount = getGoalIterationCount()
56
- const maxIter = getGoalMaxIterations()
65
+ const iterCount = goalState.metrics.iterationCount
66
+ const maxIter = goalState.metrics.maxIterations
57
67
 
68
+ // Max iterations check — circuit breaker
58
69
  if (iterCount >= maxIter) {
59
- setGoalCondition(null)
70
+ // Clear goal from store (delegates to clear() which resets engine too)
71
+ const { clear } = require('../utils/goalStore.js')
72
+ clear()
60
73
  enqueue({
61
- value: `⚠️ /goal stopped after ${maxIter} iterations. Goal not achieved: "${condition}"`,
74
+ value: `Goal not achieved after ${maxIter} iterations. Goal was: "${condition}"`,
62
75
  mode: 'task-notification',
63
76
  priority: 'now',
64
77
  })
65
78
  return
66
79
  }
67
80
 
68
- // Read snapshot BEFORE await to avoid stale data
81
+ // Get messages + current sub-goal for evaluation context
69
82
  const messages = messagesRef.current
70
- const result = await evaluateGoal(condition, messages)
83
+ const currentSubGoal = goalState.immediateGoal
84
+ ? goalState.dag.nodes.get(goalState.immediateGoal.subGoalId)
85
+ : undefined
86
+
87
+ // Run environment-aware evaluation (rule engine + semantic + final)
88
+ const result = await evaluateGoal(condition, messages, currentSubGoal)
71
89
 
72
- // Race protection: user may have called /goal clear during the await
73
- if (getGoalCondition() !== condition) return
90
+ // Race: user may have cleared goal during async evaluation
91
+ if (!getActiveGoalId()) return
74
92
 
75
- incrementGoalIterationCount()
76
- updateGoalEvalHistory(result.gap)
93
+ incrementIteration()
94
+ recordEvalGap(result.gap)
95
+ recalculateProgress()
96
+
97
+ // Check active sub-goals and blocked count for drift diagnosis
98
+ const ready = getReadySubGoals()
99
+ const blocked = getBlockedSubGoals()
100
+ if (blocked.length > 0 && ready.length === 0) {
101
+ // All sub-goals blocked — need to replan or escalate
102
+ void ready
103
+ void blocked
104
+ }
77
105
 
78
- // Check for repeated gaps - implement circuit breaker
79
- const evalHistory = getGoalEvalHistory()
106
+ // Circuit breaker: 3 consecutive same gap
107
+ const evalHistory = goalState.metrics.evalHistory
80
108
  const isRepeatedGap = evalHistory.consecutiveSameGapCount >= 3 && result.gap !== null
81
109
 
82
110
  if (result.satisfied) {
83
- setGoalCondition(null)
111
+ // Goal achieved — clear from store and file system
112
+ const { clearAndDelete } = require('../utils/goalStore.js')
113
+ clearAndDelete()
84
114
  enqueue({
85
- value: `✅ Goal achieved (iteration ${iterCount + 1}): ${result.reason}`,
115
+ value: `Goal achieved (iteration ${iterCount + 1}): ${result.reason}`,
86
116
  mode: 'task-notification',
87
117
  priority: 'now',
88
118
  })
89
119
  } else if (isRepeatedGap) {
90
- // Circuit breaker: stop after 3 repeated gaps
91
- setGoalCondition(null)
120
+ // Circuit breaker same gap repeated 3 times, stop looping
121
+ const { clear } = require('../utils/goalStore.js')
122
+ clear()
92
123
  enqueue({
93
- value: `⚠️ Goal evaluator stopped after detecting the same gap "${result.gap}" 3 times in a row. Please adjust your approach or output EVAL blocks in the correct format.`,
124
+ value: `Goal evaluator stopped same gap "${result.gap}" ${evalHistory.consecutiveSameGapCount}x. Adjust approach or output EVAL blocks.`,
94
125
  mode: 'task-notification',
95
126
  priority: 'now',
96
127
  })
97
128
  } else {
129
+ // Not yet satisfied — continue
98
130
  const continueMsg = result.gap
99
131
  ? `Goal not yet met (${iterCount + 1}/${maxIter}). Gap: ${result.gap}. Continue toward: "${condition}"`
100
132
  : `Goal not yet met (${iterCount + 1}/${maxIter}, reason: ${result.reason}). Continue toward: "${condition}"`
@@ -104,10 +136,12 @@ export function useGoalEvaluator({
104
136
  priority: 'now',
105
137
  })
106
138
  }
139
+ } catch (err) {
140
+ // Evaluation error is non-fatal — log and continue
141
+ console.error('[GoalEvaluator] Evaluation error:', err)
107
142
  } finally {
108
143
  evaluating.current = false
109
144
  }
110
145
  })()
111
- // messages intentionally excluded from deps read via ref to avoid batching issues
112
- }, [lastQueryCompletionTime, isQueryActive])
146
+ }, [lastQueryCompletionTime, isQueryActive]) // messagesRef is stable ref not included as dep
113
147
  }
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
- import {
2
- getGoalCondition,
3
- getGoalMaxIterations,
4
- setGoalCondition,
5
- } from '../../bootstrap/state.js'
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 <condition>
11
+ const USAGE = `Usage: /goal <create | clear | cancel>
9
12
 
10
- Set a session goal. The agent will keep working until the condition is met.
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 is ready for review with passing CI
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
- 'Set a session-level goal condition and loop until met. Use when user says "/goal <condition>" or wants autonomous execution until a specific outcome is reached.',
24
- argumentHint: '<condition | clear>',
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
- if (['clear', 'stop', 'cancel'].includes(trimmed)) {
34
- const current = getGoalCondition()
35
- if (current) {
36
- setGoalCondition(null)
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: "${current}". Tell the user their goal has been 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 to cancel. Tell the user there is no active goal.',
52
+ text: 'No active goal. Use `/goal create <text>` to start.',
48
53
  },
49
54
  ]
50
55
  }
51
56
 
52
- setGoalCondition(trimmed)
53
- const maxIter = getGoalMaxIterations()
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 condition: "${trimmed}"
61
-
62
- This goal is now registered for this session. After each turn, an independent evaluator (Haiku 4.5, a weak model) will check whether the goal is satisfied. Maximum ${maxIter} iterations.
68
+ **Goal**: "${trimmed}"
69
+ **Architecture**: User Goal → Operational Goal → DAG Sub Goals → Immediate Action
70
+ **Max iterations**: ${maxIter}
63
71
 
64
- CRITICAL: The evaluator reads ONLY your text output. It cannot see code changes, tool results, or file contents — only the plain text you write.
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
- At each turn toward the goal, output a short evaluation block like:
67
- EVAL: [metric1]: [value] / [target] → ✓ or
77
+ EVAL blocks still welcome for agent self-reporting, but no longer required.
78
+ Format: \`EVAL: metric: value / target → ✓ or ✗\`
68
79
 
69
- This block is the ONLY signal the evaluator can reliably process. Make it short,
70
- unambiguous, and quantitative. Do NOT expect the evaluator to infer success from narrative discussion.
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
- Tell the user: Goal set you will work autonomously until "${trimmed}" is achieved (max ${maxIter} turns). Send \`/goal clear\` to cancel.
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
  },