@ddse/acm-aicoder 0.5.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.
Files changed (165) hide show
  1. package/.aicoder/index.json +304 -0
  2. package/AICODER_IMPLEMENTATION_PLAN_PHASE2.md +284 -0
  3. package/LICENSE +21 -0
  4. package/README.md +490 -0
  5. package/bin/interactive.tsx +232 -0
  6. package/dist/bin/interactive.d.ts +3 -0
  7. package/dist/bin/interactive.d.ts.map +1 -0
  8. package/dist/bin/interactive.js +155 -0
  9. package/dist/bin/interactive.js.map +1 -0
  10. package/dist/src/config/providers.d.ts +15 -0
  11. package/dist/src/config/providers.d.ts.map +1 -0
  12. package/dist/src/config/providers.js +142 -0
  13. package/dist/src/config/providers.js.map +1 -0
  14. package/dist/src/config/session.d.ts +25 -0
  15. package/dist/src/config/session.d.ts.map +1 -0
  16. package/dist/src/config/session.js +97 -0
  17. package/dist/src/config/session.js.map +1 -0
  18. package/dist/src/context/bm25.d.ts +68 -0
  19. package/dist/src/context/bm25.d.ts.map +1 -0
  20. package/dist/src/context/bm25.js +131 -0
  21. package/dist/src/context/bm25.js.map +1 -0
  22. package/dist/src/context/code-search.d.ts +30 -0
  23. package/dist/src/context/code-search.d.ts.map +1 -0
  24. package/dist/src/context/code-search.js +150 -0
  25. package/dist/src/context/code-search.js.map +1 -0
  26. package/dist/src/context/context-pack.d.ts +25 -0
  27. package/dist/src/context/context-pack.d.ts.map +1 -0
  28. package/dist/src/context/context-pack.js +92 -0
  29. package/dist/src/context/context-pack.js.map +1 -0
  30. package/dist/src/context/dependency-mapper.d.ts +10 -0
  31. package/dist/src/context/dependency-mapper.d.ts.map +1 -0
  32. package/dist/src/context/dependency-mapper.js +62 -0
  33. package/dist/src/context/dependency-mapper.js.map +1 -0
  34. package/dist/src/context/index.d.ts +8 -0
  35. package/dist/src/context/index.d.ts.map +1 -0
  36. package/dist/src/context/index.js +9 -0
  37. package/dist/src/context/index.js.map +1 -0
  38. package/dist/src/context/symbol-extractor.d.ts +26 -0
  39. package/dist/src/context/symbol-extractor.d.ts.map +1 -0
  40. package/dist/src/context/symbol-extractor.js +129 -0
  41. package/dist/src/context/symbol-extractor.js.map +1 -0
  42. package/dist/src/context/test-mapper.d.ts +16 -0
  43. package/dist/src/context/test-mapper.d.ts.map +1 -0
  44. package/dist/src/context/test-mapper.js +66 -0
  45. package/dist/src/context/test-mapper.js.map +1 -0
  46. package/dist/src/context/types.d.ts +61 -0
  47. package/dist/src/context/types.d.ts.map +1 -0
  48. package/dist/src/context/types.js +3 -0
  49. package/dist/src/context/types.js.map +1 -0
  50. package/dist/src/context/workspace-indexer.d.ts +39 -0
  51. package/dist/src/context/workspace-indexer.d.ts.map +1 -0
  52. package/dist/src/context/workspace-indexer.js +222 -0
  53. package/dist/src/context/workspace-indexer.js.map +1 -0
  54. package/dist/src/index.d.ts +5 -0
  55. package/dist/src/index.d.ts.map +1 -0
  56. package/dist/src/index.js +6 -0
  57. package/dist/src/index.js.map +1 -0
  58. package/dist/src/registries.d.ts +34 -0
  59. package/dist/src/registries.d.ts.map +1 -0
  60. package/dist/src/registries.js +87 -0
  61. package/dist/src/registries.js.map +1 -0
  62. package/dist/src/runtime/budget-manager.d.ts +42 -0
  63. package/dist/src/runtime/budget-manager.d.ts.map +1 -0
  64. package/dist/src/runtime/budget-manager.js +82 -0
  65. package/dist/src/runtime/budget-manager.js.map +1 -0
  66. package/dist/src/runtime/interactive-runtime.d.ts +39 -0
  67. package/dist/src/runtime/interactive-runtime.d.ts.map +1 -0
  68. package/dist/src/runtime/interactive-runtime.js +321 -0
  69. package/dist/src/runtime/interactive-runtime.js.map +1 -0
  70. package/dist/src/tasks-v2/analysis-tasks.d.ts +117 -0
  71. package/dist/src/tasks-v2/analysis-tasks.d.ts.map +1 -0
  72. package/dist/src/tasks-v2/analysis-tasks.js +209 -0
  73. package/dist/src/tasks-v2/analysis-tasks.js.map +1 -0
  74. package/dist/src/tasks-v2/developer-tasks.d.ts +226 -0
  75. package/dist/src/tasks-v2/developer-tasks.d.ts.map +1 -0
  76. package/dist/src/tasks-v2/developer-tasks.js +322 -0
  77. package/dist/src/tasks-v2/developer-tasks.js.map +1 -0
  78. package/dist/src/tasks-v2/index.d.ts +3 -0
  79. package/dist/src/tasks-v2/index.d.ts.map +1 -0
  80. package/dist/src/tasks-v2/index.js +4 -0
  81. package/dist/src/tasks-v2/index.js.map +1 -0
  82. package/dist/src/tools-v2/edit-tools.d.ts +67 -0
  83. package/dist/src/tools-v2/edit-tools.d.ts.map +1 -0
  84. package/dist/src/tools-v2/edit-tools.js +117 -0
  85. package/dist/src/tools-v2/edit-tools.js.map +1 -0
  86. package/dist/src/tools-v2/index.d.ts +6 -0
  87. package/dist/src/tools-v2/index.d.ts.map +1 -0
  88. package/dist/src/tools-v2/index.js +7 -0
  89. package/dist/src/tools-v2/index.js.map +1 -0
  90. package/dist/src/tools-v2/read-tools.d.ts +129 -0
  91. package/dist/src/tools-v2/read-tools.d.ts.map +1 -0
  92. package/dist/src/tools-v2/read-tools.js +216 -0
  93. package/dist/src/tools-v2/read-tools.js.map +1 -0
  94. package/dist/src/tools-v2/search-tools.d.ts +73 -0
  95. package/dist/src/tools-v2/search-tools.d.ts.map +1 -0
  96. package/dist/src/tools-v2/search-tools.js +132 -0
  97. package/dist/src/tools-v2/search-tools.js.map +1 -0
  98. package/dist/src/tools-v2/test-tools.d.ts +59 -0
  99. package/dist/src/tools-v2/test-tools.d.ts.map +1 -0
  100. package/dist/src/tools-v2/test-tools.js +111 -0
  101. package/dist/src/tools-v2/test-tools.js.map +1 -0
  102. package/dist/src/tools-v2/workspace-context.d.ts +65 -0
  103. package/dist/src/tools-v2/workspace-context.d.ts.map +1 -0
  104. package/dist/src/tools-v2/workspace-context.js +336 -0
  105. package/dist/src/tools-v2/workspace-context.js.map +1 -0
  106. package/dist/src/ui/App.d.ts +9 -0
  107. package/dist/src/ui/App.d.ts.map +1 -0
  108. package/dist/src/ui/App.js +257 -0
  109. package/dist/src/ui/App.js.map +1 -0
  110. package/dist/src/ui/components/ChatPane.d.ts +12 -0
  111. package/dist/src/ui/components/ChatPane.d.ts.map +1 -0
  112. package/dist/src/ui/components/ChatPane.js +41 -0
  113. package/dist/src/ui/components/ChatPane.js.map +1 -0
  114. package/dist/src/ui/components/EventsPane.d.ts +12 -0
  115. package/dist/src/ui/components/EventsPane.d.ts.map +1 -0
  116. package/dist/src/ui/components/EventsPane.js +48 -0
  117. package/dist/src/ui/components/EventsPane.js.map +1 -0
  118. package/dist/src/ui/components/GoalsTasksPane.d.ts +18 -0
  119. package/dist/src/ui/components/GoalsTasksPane.d.ts.map +1 -0
  120. package/dist/src/ui/components/GoalsTasksPane.js +83 -0
  121. package/dist/src/ui/components/GoalsTasksPane.js.map +1 -0
  122. package/dist/src/ui/store.d.ts +74 -0
  123. package/dist/src/ui/store.d.ts.map +1 -0
  124. package/dist/src/ui/store.js +260 -0
  125. package/dist/src/ui/store.js.map +1 -0
  126. package/dist/tests/integration.test.d.ts +2 -0
  127. package/dist/tests/integration.test.d.ts.map +1 -0
  128. package/dist/tests/integration.test.js +415 -0
  129. package/dist/tests/integration.test.js.map +1 -0
  130. package/dist/tsconfig.tsbuildinfo +1 -0
  131. package/docs/AICODER.png +0 -0
  132. package/docs/INTERACTIVE_CLI_GUIDE.md +201 -0
  133. package/docs/TUI_MOCKUP.md +180 -0
  134. package/package.json +52 -0
  135. package/src/config/providers.ts +174 -0
  136. package/src/config/session.ts +143 -0
  137. package/src/context/bm25.ts +173 -0
  138. package/src/context/code-search.ts +188 -0
  139. package/src/context/context-pack.ts +133 -0
  140. package/src/context/dependency-mapper.ts +72 -0
  141. package/src/context/index.ts +8 -0
  142. package/src/context/symbol-extractor.ts +149 -0
  143. package/src/context/test-mapper.ts +77 -0
  144. package/src/context/types.ts +69 -0
  145. package/src/context/workspace-indexer.ts +249 -0
  146. package/src/index.ts +5 -0
  147. package/src/registries.ts +118 -0
  148. package/src/runtime/budget-manager.ts +118 -0
  149. package/src/runtime/interactive-runtime.ts +423 -0
  150. package/src/tasks-v2/analysis-tasks.ts +311 -0
  151. package/src/tasks-v2/developer-tasks.ts +437 -0
  152. package/src/tasks-v2/index.ts +3 -0
  153. package/src/tools-v2/edit-tools.ts +153 -0
  154. package/src/tools-v2/index.ts +6 -0
  155. package/src/tools-v2/read-tools.ts +286 -0
  156. package/src/tools-v2/search-tools.ts +175 -0
  157. package/src/tools-v2/test-tools.ts +147 -0
  158. package/src/tools-v2/workspace-context.ts +428 -0
  159. package/src/ui/App.tsx +392 -0
  160. package/src/ui/components/ChatPane.tsx +84 -0
  161. package/src/ui/components/EventsPane.tsx +81 -0
  162. package/src/ui/components/GoalsTasksPane.tsx +149 -0
  163. package/src/ui/store.ts +362 -0
  164. package/tests/integration.test.ts +537 -0
  165. package/tsconfig.json +22 -0
@@ -0,0 +1,118 @@
1
+ // Token budget manager
2
+ // Tracks cumulative token usage and ensures calls stay within provider limits
3
+
4
+ import {
5
+ ProviderMetadata,
6
+ getProviderMetadata,
7
+ estimateTokenCount,
8
+ } from '../config/providers.js';
9
+
10
+ export interface BudgetEstimate {
11
+ inputTokens: number;
12
+ outputTokens: number;
13
+ totalTokens: number;
14
+ }
15
+
16
+ export interface BudgetStatus {
17
+ totalInputTokens: number;
18
+ totalOutputTokens: number;
19
+ totalTokens: number;
20
+ maxTokens?: number;
21
+ remainingTokens?: number;
22
+ callCount: number;
23
+ }
24
+
25
+ export class BudgetManager {
26
+ private metadata: ProviderMetadata;
27
+ private maxTokens?: number;
28
+ private totalInputTokens = 0;
29
+ private totalOutputTokens = 0;
30
+ private callCount = 0;
31
+
32
+ constructor(model: string, overrideMaxTokens?: number) {
33
+ this.metadata = getProviderMetadata(model);
34
+ this.maxTokens = overrideMaxTokens ?? this.metadata.maxContextTokens;
35
+ }
36
+
37
+ /**
38
+ * Estimate whether an upcoming inference fits within the token allowance
39
+ * @param inputText Planned prompt text
40
+ * @param estimatedOutputTokens Rough guess for completion tokens
41
+ */
42
+ checkBudget(inputText: string, estimatedOutputTokens: number = 1000): BudgetEstimate {
43
+ const inputTokens = estimateTokenCount(inputText);
44
+ const totalTokens = inputTokens + estimatedOutputTokens;
45
+
46
+ if (this.maxTokens !== undefined) {
47
+ const projected = this.totalTokensConsumed() + totalTokens;
48
+ if (projected > this.maxTokens) {
49
+ throw new TokenBudgetExceededError(
50
+ `Token allowance exceeded: projected ${projected} tokens > limit ${this.maxTokens}`,
51
+ {
52
+ inputTokens,
53
+ outputTokens: estimatedOutputTokens,
54
+ totalTokens,
55
+ },
56
+ this.getStatus()
57
+ );
58
+ }
59
+ }
60
+
61
+ return {
62
+ inputTokens,
63
+ outputTokens: estimatedOutputTokens,
64
+ totalTokens,
65
+ };
66
+ }
67
+
68
+ /**
69
+ * Record actual token usage after a call completes
70
+ */
71
+ recordUsage(actualInputTokens: number, actualOutputTokens: number): void {
72
+ this.totalInputTokens += actualInputTokens;
73
+ this.totalOutputTokens += actualOutputTokens;
74
+ this.callCount += 1;
75
+ }
76
+
77
+ getStatus(): BudgetStatus {
78
+ const totalTokens = this.totalTokensConsumed();
79
+ const status: BudgetStatus = {
80
+ totalInputTokens: this.totalInputTokens,
81
+ totalOutputTokens: this.totalOutputTokens,
82
+ totalTokens,
83
+ maxTokens: this.maxTokens,
84
+ callCount: this.callCount,
85
+ };
86
+
87
+ if (this.maxTokens !== undefined) {
88
+ status.remainingTokens = Math.max(0, this.maxTokens - totalTokens);
89
+ }
90
+
91
+ return status;
92
+ }
93
+
94
+ getMetadata(): ProviderMetadata {
95
+ return this.metadata;
96
+ }
97
+
98
+ reset(): void {
99
+ this.totalInputTokens = 0;
100
+ this.totalOutputTokens = 0;
101
+ this.callCount = 0;
102
+ }
103
+
104
+ private totalTokensConsumed(): number {
105
+ return this.totalInputTokens + this.totalOutputTokens;
106
+ }
107
+ }
108
+
109
+ export class TokenBudgetExceededError extends Error {
110
+ constructor(
111
+ message: string,
112
+ public estimate: BudgetEstimate,
113
+ public status: BudgetStatus
114
+ ) {
115
+ super(message);
116
+ this.name = 'TokenBudgetExceededError';
117
+ }
118
+ }
@@ -0,0 +1,423 @@
1
+ // Interactive Runtime Orchestrator
2
+ // Connects ACM framework components with the TUI
3
+
4
+ import type { LLM } from '@ddse/acm-llm';
5
+ import {
6
+ type Goal,
7
+ type Context,
8
+ type Plan,
9
+ type StreamSink,
10
+ type NucleusConfig,
11
+ type LLMCallFn,
12
+ type CapabilityRegistry,
13
+ ExternalContextProviderAdapter,
14
+ } from '@ddse/acm-sdk';
15
+ import { type PlannerResult } from '@ddse/acm-planner';
16
+ import { ExecutionTranscript, MemoryLedger, type ExecutionTranscriptEvent } from '@ddse/acm-runtime';
17
+ import { ACMFramework } from '@ddse/acm-framework';
18
+ import type { SessionConfig } from '../config/session.js';
19
+ import { BudgetManager } from './budget-manager.js';
20
+ import { AppStore } from '../ui/store.js';
21
+
22
+ export interface InteractiveRuntimeOptions {
23
+ config: SessionConfig;
24
+ llm: LLM;
25
+ capabilityRegistry: CapabilityRegistry;
26
+ toolRegistry: any;
27
+ policyEngine: any;
28
+ store: AppStore;
29
+ contextProvider?: ExternalContextProviderAdapter;
30
+ }
31
+
32
+ export class InteractiveRuntime {
33
+ private config: SessionConfig;
34
+ private llm: LLM;
35
+ private capabilityRegistry: CapabilityRegistry;
36
+ private toolRegistry: any;
37
+ private policyEngine: any;
38
+ private store: AppStore;
39
+ private budgetManager: BudgetManager;
40
+ private ledger: MemoryLedger;
41
+ private nucleusConfig: {
42
+ llmCall: NucleusConfig['llmCall'];
43
+ hooks?: NucleusConfig['hooks'];
44
+ };
45
+ private nucleusLLMCall: LLMCallFn;
46
+ private transcript: ExecutionTranscript;
47
+ private contextProvider?: ExternalContextProviderAdapter;
48
+ private framework: ACMFramework;
49
+
50
+ constructor(options: InteractiveRuntimeOptions) {
51
+ this.config = options.config;
52
+ this.llm = options.llm;
53
+ this.capabilityRegistry = options.capabilityRegistry;
54
+ this.toolRegistry = options.toolRegistry;
55
+ this.policyEngine = options.policyEngine;
56
+ this.store = options.store;
57
+ this.contextProvider = options.contextProvider;
58
+
59
+ this.budgetManager = new BudgetManager(options.config.model);
60
+
61
+ this.ledger = new MemoryLedger();
62
+
63
+ if (!this.llm.generateWithTools) {
64
+ throw new Error('Configured LLM must support structured tool calls to drive Nucleus.');
65
+ }
66
+
67
+ this.nucleusLLMCall = async (prompt, tools, callConfig) => {
68
+ const toolDefs = tools.map(tool => ({
69
+ name: tool.name,
70
+ description: tool.description ?? 'Interactive runtime tool',
71
+ inputSchema: tool.inputSchema ?? { type: 'object', properties: {} },
72
+ }));
73
+
74
+ const response = await this.llm.generateWithTools!(
75
+ [
76
+ {
77
+ role: 'system',
78
+ content: prompt,
79
+ },
80
+ ],
81
+ toolDefs,
82
+ {
83
+ temperature: callConfig.temperature,
84
+ seed: callConfig.seed,
85
+ maxTokens: callConfig.maxTokens,
86
+ }
87
+ );
88
+
89
+ return {
90
+ reasoning: response.text,
91
+ toolCalls: (response.toolCalls ?? []).map(tc => ({
92
+ id: tc.id,
93
+ name: tc.name,
94
+ input: tc.arguments,
95
+ })),
96
+ raw: response.raw,
97
+ };
98
+ };
99
+
100
+ this.nucleusConfig = {
101
+ llmCall: {
102
+ provider: this.llm.name(),
103
+ model: this.config.model,
104
+ temperature: this.config.temperature ?? 0.1,
105
+ maxTokens: 512,
106
+ },
107
+ hooks: {
108
+ preflight: true,
109
+ postcheck: true,
110
+ },
111
+ };
112
+
113
+ this.framework = ACMFramework.create({
114
+ capabilityRegistry: this.capabilityRegistry,
115
+ toolRegistry: this.toolRegistry,
116
+ policyEngine: this.policyEngine,
117
+ contextProvider: this.contextProvider,
118
+ verify: async () => true,
119
+ nucleus: {
120
+ call: this.nucleusLLMCall,
121
+ llmConfig: this.nucleusConfig.llmCall,
122
+ hooks: this.nucleusConfig.hooks,
123
+ },
124
+ });
125
+
126
+ // Subscribe to ledger events
127
+ this.setupLedgerSubscription();
128
+
129
+ this.transcript = new ExecutionTranscript({
130
+ onEvent: event => this.handleTranscriptEvent(event),
131
+ });
132
+ this.transcript.attach(this.ledger);
133
+ }
134
+
135
+ private setupLedgerSubscription(): void {
136
+ // Monitor ledger entries and emit to event stream
137
+ const originalAppend = this.ledger.append.bind(this.ledger);
138
+ this.ledger.append = (type: any, details: Record<string, any>, computeDigest = true) => {
139
+ const entry = originalAppend(type, details, computeDigest);
140
+
141
+ // Map ledger entry to event
142
+ const eventColors: Record<string, any> = {
143
+ PLAN_SELECTED: 'green',
144
+ TASK_START: 'blue',
145
+ TASK_END: 'green',
146
+ ERROR: 'red',
147
+ POLICY_DECISION: 'yellow',
148
+ VERIFICATION: 'blue',
149
+ };
150
+
151
+ this.store.addEvent(
152
+ entry.type,
153
+ entry.details,
154
+ eventColors[entry.type] || 'gray'
155
+ );
156
+
157
+ if (entry.type === 'NUCLEUS_INFERENCE') {
158
+ const reasoning = (entry.details && (entry.details.reasoning ?? entry.details.nucleus?.reasoning)) as
159
+ | string
160
+ | undefined;
161
+
162
+ if (typeof reasoning === 'string' && reasoning.trim().length > 0) {
163
+ this.store.addMessage('nucleus', reasoning.trim());
164
+ }
165
+ }
166
+
167
+ // Update task status based on ledger entry
168
+ if (entry.type === 'TASK_START' && entry.details.taskId) {
169
+ this.store.updateTaskStatus(entry.details.taskId, 'running');
170
+ } else if (entry.type === 'TASK_END' && entry.details.taskId) {
171
+ this.store.updateTaskStatus(entry.details.taskId, 'succeeded');
172
+ } else if (entry.type === 'ERROR' && entry.details.taskId) {
173
+ this.store.updateTaskStatus(
174
+ entry.details.taskId,
175
+ 'failed',
176
+ undefined,
177
+ entry.details.message || entry.details.error || 'Unknown error'
178
+ );
179
+ }
180
+
181
+ return entry;
182
+ };
183
+ }
184
+
185
+ private handleTranscriptEvent(event: ExecutionTranscriptEvent): void {
186
+ if (event.type === 'task-completed') {
187
+ this.store.recordTaskOutput(event.taskId, event.output, event.narrative);
188
+ } else if (event.type === 'goal-summary') {
189
+ this.store.setGoalSummary(event.summary);
190
+ }
191
+ }
192
+
193
+ async processGoal(goalText: string): Promise<void> {
194
+ try {
195
+ this.store.setProcessing(true);
196
+ this.store.addMessage('user', goalText);
197
+
198
+ // Create goal and context
199
+ const goal: Goal = {
200
+ id: `goal-${Date.now()}`,
201
+ intent: goalText,
202
+ };
203
+
204
+ const context: Context = {
205
+ id: `ctx-${Date.now()}`,
206
+ facts: {
207
+ workspace: this.config.workspace,
208
+ timestamp: new Date().toISOString(),
209
+ },
210
+ };
211
+
212
+ this.store.addEvent('GOAL_CREATED', { goalId: goal.id, intent: goalText }, 'green');
213
+
214
+ // Create streaming sink for planner
215
+ const streamSink: StreamSink = {
216
+ attach: (source: string, callback: (chunk: any) => void) => {
217
+ // Not used in this implementation
218
+ },
219
+ emit: (channel, event) => {
220
+ if (channel === 'planner') {
221
+ const msgs = this.store.getState().messages;
222
+ const lastPlanner = msgs.filter(m => m.role === 'planner').pop();
223
+
224
+ if ('delta' in event && event.delta) {
225
+ // Stream planner reasoning
226
+ if (lastPlanner && lastPlanner.streaming) {
227
+ this.store.appendToMessage(lastPlanner.id, event.delta);
228
+ } else {
229
+ this.store.addMessage('planner', event.delta, true);
230
+ }
231
+ }
232
+
233
+ const summaryParts: string[] = [];
234
+ if (typeof event.plans === 'number') {
235
+ const planLabel = event.plans === 1 ? 'plan' : 'plans';
236
+ summaryParts.push(`Generated ${event.plans} ${planLabel}.`);
237
+ }
238
+ if (event.rationale) {
239
+ summaryParts.push(event.rationale);
240
+ }
241
+
242
+ if (summaryParts.length > 0 && lastPlanner) {
243
+ const summary = summaryParts.join('\n\n');
244
+ this.store.updateMessage(lastPlanner.id, summary, false);
245
+ } else if ('done' in event && event.done && lastPlanner) {
246
+ // Mark streaming complete with default message if no summary
247
+ const content = lastPlanner.content?.trim().length
248
+ ? lastPlanner.content
249
+ : 'Planner finished.';
250
+ this.store.updateMessage(lastPlanner.id, content, false);
251
+ }
252
+ }
253
+
254
+ if (channel === 'summary') {
255
+ this.store.setGoalSummary((event as any)?.summary);
256
+ }
257
+ },
258
+ close: (source: string) => {
259
+ // Not used in this implementation
260
+ },
261
+ };
262
+
263
+ // Token allowance check for planning
264
+ let planningEstimate;
265
+ try {
266
+ const promptText = `Goal: ${goalText}\nContext: ${JSON.stringify(context.facts)}`;
267
+ planningEstimate = this.budgetManager.checkBudget(promptText, 2000);
268
+ this.store.addEvent('TOKEN_BUDGET_CHECK', {
269
+ estimatedTokens: planningEstimate.totalTokens,
270
+ inputTokens: planningEstimate.inputTokens,
271
+ outputTokens: planningEstimate.outputTokens,
272
+ }, 'blue');
273
+ } catch (err: any) {
274
+ this.store.addMessage('system', `Token allowance exceeded: ${err.message}`);
275
+ this.store.setProcessing(false);
276
+ return;
277
+ }
278
+
279
+ // Plan with ACM framework wrapper
280
+ this.store.addMessage('planner', 'Planner is generating plan(s)...', true);
281
+ const planResponse = await this.framework.plan({
282
+ goal,
283
+ context,
284
+ planCount: this.config.planCount || 1,
285
+ stream: streamSink,
286
+ ledger: this.ledger,
287
+ });
288
+ const plannerResult = planResponse.result;
289
+
290
+ // Record estimated token usage for planning stage
291
+ if (planningEstimate) {
292
+ this.budgetManager.recordUsage(planningEstimate.inputTokens, planningEstimate.outputTokens);
293
+ }
294
+ this.store.updateBudgetStatus(this.budgetManager.getStatus());
295
+
296
+ if (plannerResult.plans.length === 0) {
297
+ this.store.addMessage('system', 'No plans generated. Try rephrasing your goal.');
298
+ this.store.setProcessing(false);
299
+ return;
300
+ }
301
+
302
+ const selectedPlan = planResponse.selectedPlan;
303
+ this.store.setGoal(goal, context, selectedPlan);
304
+ this.store.addEvent('PLAN_SELECTED', {
305
+ planId: selectedPlan.id,
306
+ tasks: selectedPlan.tasks.length,
307
+ }, 'green');
308
+
309
+ // Execute plan
310
+ await this.executePlan(goal, context, selectedPlan, plannerResult, streamSink);
311
+
312
+ } catch (error: any) {
313
+ this.store.addMessage('system', `Error: ${error.message}`);
314
+ this.store.addEvent('ERROR', { message: error.message }, 'red');
315
+ } finally {
316
+ this.store.setProcessing(false);
317
+ }
318
+ }
319
+
320
+ private async executePlan(
321
+ goal: Goal,
322
+ context: Context,
323
+ plan: Plan,
324
+ plannerResult: PlannerResult,
325
+ stream: StreamSink,
326
+ ): Promise<void> {
327
+ try {
328
+ const result = await this.framework.execute({
329
+ goal,
330
+ context,
331
+ stream,
332
+ ledger: this.ledger,
333
+ runId: `run-${Date.now()}`,
334
+ existingPlan: {
335
+ plan,
336
+ plannerResult,
337
+ },
338
+ });
339
+ const execution = result.execution;
340
+
341
+ this.store.addMessage('system', 'Goal completed successfully!');
342
+ this.store.addEvent('GOAL_COMPLETED', { goalId: goal.id }, 'green');
343
+
344
+ if (execution.goalSummary) {
345
+ this.store.setGoalSummary(execution.goalSummary);
346
+ }
347
+
348
+ const completedTasks = this.store
349
+ .getState()
350
+ .tasks.filter(task => task.outputSummary && task.status === 'succeeded');
351
+
352
+ if (completedTasks.length > 0) {
353
+ const summaryLines = completedTasks.map(task => `• ${task.name}: ${task.outputSummary}`);
354
+ this.store.addMessage('system', `Summary of task outputs:\n${summaryLines.join('\n')}`);
355
+ } else if (Object.keys(execution.outputsByTask || {}).length === 0) {
356
+ this.store.addMessage('system', 'Execution completed, but no tasks produced structured outputs.');
357
+ }
358
+
359
+ // Cleanup after goal
360
+ await this.cleanupGoal();
361
+
362
+ } catch (error: any) {
363
+ this.store.addMessage('system', `Execution error: ${error.message}`);
364
+ this.store.addEvent('EXECUTION_ERROR', { message: error.message }, 'red');
365
+ }
366
+ }
367
+
368
+ private async cleanupGoal(): Promise<void> {
369
+ // Persist replay bundle
370
+ try {
371
+ const fs = await import('fs/promises');
372
+ const path = await import('path');
373
+
374
+ const replayDir = path.join(this.config.workspace, '.aicoder', 'replays');
375
+ const timestamp = new Date().toISOString().replace(/:/g, '-').replace(/\..+/, '');
376
+ const bundleDir = path.join(replayDir, timestamp);
377
+
378
+ // Create directory
379
+ await fs.mkdir(bundleDir, { recursive: true });
380
+
381
+ // Save session config
382
+ await fs.writeFile(
383
+ path.join(bundleDir, 'session.json'),
384
+ JSON.stringify(this.config, null, 2),
385
+ 'utf-8'
386
+ );
387
+
388
+ // Save ledger entries
389
+ const ledgerEntries = this.ledger.getEntries();
390
+ await fs.writeFile(
391
+ path.join(bundleDir, 'ledger.jsonl'),
392
+ ledgerEntries.map(e => JSON.stringify(e)).join('\n'),
393
+ 'utf-8'
394
+ );
395
+
396
+ // Save budget summary
397
+ const budgetStatus = this.budgetManager.getStatus();
398
+ await fs.writeFile(
399
+ path.join(bundleDir, 'budget.json'),
400
+ JSON.stringify(budgetStatus, null, 2),
401
+ 'utf-8'
402
+ );
403
+
404
+ this.store.addEvent('REPLAY_SAVED', { path: bundleDir }, 'blue');
405
+ this.store.addMessage('system', `Replay bundle saved to: ${bundleDir}`);
406
+ } catch (err: any) {
407
+ // Non-fatal
408
+ this.store.addEvent('REPLAY_SAVE_ERROR', { error: err.message }, 'red');
409
+ }
410
+
411
+ // Reset budget for next goal
412
+ this.budgetManager.reset();
413
+ this.store.updateBudgetStatus(this.budgetManager.getStatus());
414
+ }
415
+
416
+ getBudgetManager(): BudgetManager {
417
+ return this.budgetManager;
418
+ }
419
+
420
+ getLedger(): MemoryLedger {
421
+ return this.ledger;
422
+ }
423
+ }