@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.
- package/.aicoder/index.json +304 -0
- package/AICODER_IMPLEMENTATION_PLAN_PHASE2.md +284 -0
- package/LICENSE +21 -0
- package/README.md +490 -0
- package/bin/interactive.tsx +232 -0
- package/dist/bin/interactive.d.ts +3 -0
- package/dist/bin/interactive.d.ts.map +1 -0
- package/dist/bin/interactive.js +155 -0
- package/dist/bin/interactive.js.map +1 -0
- package/dist/src/config/providers.d.ts +15 -0
- package/dist/src/config/providers.d.ts.map +1 -0
- package/dist/src/config/providers.js +142 -0
- package/dist/src/config/providers.js.map +1 -0
- package/dist/src/config/session.d.ts +25 -0
- package/dist/src/config/session.d.ts.map +1 -0
- package/dist/src/config/session.js +97 -0
- package/dist/src/config/session.js.map +1 -0
- package/dist/src/context/bm25.d.ts +68 -0
- package/dist/src/context/bm25.d.ts.map +1 -0
- package/dist/src/context/bm25.js +131 -0
- package/dist/src/context/bm25.js.map +1 -0
- package/dist/src/context/code-search.d.ts +30 -0
- package/dist/src/context/code-search.d.ts.map +1 -0
- package/dist/src/context/code-search.js +150 -0
- package/dist/src/context/code-search.js.map +1 -0
- package/dist/src/context/context-pack.d.ts +25 -0
- package/dist/src/context/context-pack.d.ts.map +1 -0
- package/dist/src/context/context-pack.js +92 -0
- package/dist/src/context/context-pack.js.map +1 -0
- package/dist/src/context/dependency-mapper.d.ts +10 -0
- package/dist/src/context/dependency-mapper.d.ts.map +1 -0
- package/dist/src/context/dependency-mapper.js +62 -0
- package/dist/src/context/dependency-mapper.js.map +1 -0
- package/dist/src/context/index.d.ts +8 -0
- package/dist/src/context/index.d.ts.map +1 -0
- package/dist/src/context/index.js +9 -0
- package/dist/src/context/index.js.map +1 -0
- package/dist/src/context/symbol-extractor.d.ts +26 -0
- package/dist/src/context/symbol-extractor.d.ts.map +1 -0
- package/dist/src/context/symbol-extractor.js +129 -0
- package/dist/src/context/symbol-extractor.js.map +1 -0
- package/dist/src/context/test-mapper.d.ts +16 -0
- package/dist/src/context/test-mapper.d.ts.map +1 -0
- package/dist/src/context/test-mapper.js +66 -0
- package/dist/src/context/test-mapper.js.map +1 -0
- package/dist/src/context/types.d.ts +61 -0
- package/dist/src/context/types.d.ts.map +1 -0
- package/dist/src/context/types.js +3 -0
- package/dist/src/context/types.js.map +1 -0
- package/dist/src/context/workspace-indexer.d.ts +39 -0
- package/dist/src/context/workspace-indexer.d.ts.map +1 -0
- package/dist/src/context/workspace-indexer.js +222 -0
- package/dist/src/context/workspace-indexer.js.map +1 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +6 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/registries.d.ts +34 -0
- package/dist/src/registries.d.ts.map +1 -0
- package/dist/src/registries.js +87 -0
- package/dist/src/registries.js.map +1 -0
- package/dist/src/runtime/budget-manager.d.ts +42 -0
- package/dist/src/runtime/budget-manager.d.ts.map +1 -0
- package/dist/src/runtime/budget-manager.js +82 -0
- package/dist/src/runtime/budget-manager.js.map +1 -0
- package/dist/src/runtime/interactive-runtime.d.ts +39 -0
- package/dist/src/runtime/interactive-runtime.d.ts.map +1 -0
- package/dist/src/runtime/interactive-runtime.js +321 -0
- package/dist/src/runtime/interactive-runtime.js.map +1 -0
- package/dist/src/tasks-v2/analysis-tasks.d.ts +117 -0
- package/dist/src/tasks-v2/analysis-tasks.d.ts.map +1 -0
- package/dist/src/tasks-v2/analysis-tasks.js +209 -0
- package/dist/src/tasks-v2/analysis-tasks.js.map +1 -0
- package/dist/src/tasks-v2/developer-tasks.d.ts +226 -0
- package/dist/src/tasks-v2/developer-tasks.d.ts.map +1 -0
- package/dist/src/tasks-v2/developer-tasks.js +322 -0
- package/dist/src/tasks-v2/developer-tasks.js.map +1 -0
- package/dist/src/tasks-v2/index.d.ts +3 -0
- package/dist/src/tasks-v2/index.d.ts.map +1 -0
- package/dist/src/tasks-v2/index.js +4 -0
- package/dist/src/tasks-v2/index.js.map +1 -0
- package/dist/src/tools-v2/edit-tools.d.ts +67 -0
- package/dist/src/tools-v2/edit-tools.d.ts.map +1 -0
- package/dist/src/tools-v2/edit-tools.js +117 -0
- package/dist/src/tools-v2/edit-tools.js.map +1 -0
- package/dist/src/tools-v2/index.d.ts +6 -0
- package/dist/src/tools-v2/index.d.ts.map +1 -0
- package/dist/src/tools-v2/index.js +7 -0
- package/dist/src/tools-v2/index.js.map +1 -0
- package/dist/src/tools-v2/read-tools.d.ts +129 -0
- package/dist/src/tools-v2/read-tools.d.ts.map +1 -0
- package/dist/src/tools-v2/read-tools.js +216 -0
- package/dist/src/tools-v2/read-tools.js.map +1 -0
- package/dist/src/tools-v2/search-tools.d.ts +73 -0
- package/dist/src/tools-v2/search-tools.d.ts.map +1 -0
- package/dist/src/tools-v2/search-tools.js +132 -0
- package/dist/src/tools-v2/search-tools.js.map +1 -0
- package/dist/src/tools-v2/test-tools.d.ts +59 -0
- package/dist/src/tools-v2/test-tools.d.ts.map +1 -0
- package/dist/src/tools-v2/test-tools.js +111 -0
- package/dist/src/tools-v2/test-tools.js.map +1 -0
- package/dist/src/tools-v2/workspace-context.d.ts +65 -0
- package/dist/src/tools-v2/workspace-context.d.ts.map +1 -0
- package/dist/src/tools-v2/workspace-context.js +336 -0
- package/dist/src/tools-v2/workspace-context.js.map +1 -0
- package/dist/src/ui/App.d.ts +9 -0
- package/dist/src/ui/App.d.ts.map +1 -0
- package/dist/src/ui/App.js +257 -0
- package/dist/src/ui/App.js.map +1 -0
- package/dist/src/ui/components/ChatPane.d.ts +12 -0
- package/dist/src/ui/components/ChatPane.d.ts.map +1 -0
- package/dist/src/ui/components/ChatPane.js +41 -0
- package/dist/src/ui/components/ChatPane.js.map +1 -0
- package/dist/src/ui/components/EventsPane.d.ts +12 -0
- package/dist/src/ui/components/EventsPane.d.ts.map +1 -0
- package/dist/src/ui/components/EventsPane.js +48 -0
- package/dist/src/ui/components/EventsPane.js.map +1 -0
- package/dist/src/ui/components/GoalsTasksPane.d.ts +18 -0
- package/dist/src/ui/components/GoalsTasksPane.d.ts.map +1 -0
- package/dist/src/ui/components/GoalsTasksPane.js +83 -0
- package/dist/src/ui/components/GoalsTasksPane.js.map +1 -0
- package/dist/src/ui/store.d.ts +74 -0
- package/dist/src/ui/store.d.ts.map +1 -0
- package/dist/src/ui/store.js +260 -0
- package/dist/src/ui/store.js.map +1 -0
- package/dist/tests/integration.test.d.ts +2 -0
- package/dist/tests/integration.test.d.ts.map +1 -0
- package/dist/tests/integration.test.js +415 -0
- package/dist/tests/integration.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/docs/AICODER.png +0 -0
- package/docs/INTERACTIVE_CLI_GUIDE.md +201 -0
- package/docs/TUI_MOCKUP.md +180 -0
- package/package.json +52 -0
- package/src/config/providers.ts +174 -0
- package/src/config/session.ts +143 -0
- package/src/context/bm25.ts +173 -0
- package/src/context/code-search.ts +188 -0
- package/src/context/context-pack.ts +133 -0
- package/src/context/dependency-mapper.ts +72 -0
- package/src/context/index.ts +8 -0
- package/src/context/symbol-extractor.ts +149 -0
- package/src/context/test-mapper.ts +77 -0
- package/src/context/types.ts +69 -0
- package/src/context/workspace-indexer.ts +249 -0
- package/src/index.ts +5 -0
- package/src/registries.ts +118 -0
- package/src/runtime/budget-manager.ts +118 -0
- package/src/runtime/interactive-runtime.ts +423 -0
- package/src/tasks-v2/analysis-tasks.ts +311 -0
- package/src/tasks-v2/developer-tasks.ts +437 -0
- package/src/tasks-v2/index.ts +3 -0
- package/src/tools-v2/edit-tools.ts +153 -0
- package/src/tools-v2/index.ts +6 -0
- package/src/tools-v2/read-tools.ts +286 -0
- package/src/tools-v2/search-tools.ts +175 -0
- package/src/tools-v2/test-tools.ts +147 -0
- package/src/tools-v2/workspace-context.ts +428 -0
- package/src/ui/App.tsx +392 -0
- package/src/ui/components/ChatPane.tsx +84 -0
- package/src/ui/components/EventsPane.tsx +81 -0
- package/src/ui/components/GoalsTasksPane.tsx +149 -0
- package/src/ui/store.ts +362 -0
- package/tests/integration.test.ts +537 -0
- 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
|
+
}
|