oxe-cc 1.6.0 → 1.8.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/CHANGELOG.md +61 -0
- package/README.md +36 -34
- package/bin/lib/oxe-agent-install.cjs +149 -32
- package/bin/lib/oxe-operational.cjs +141 -34
- package/bin/lib/oxe-project-health.cjs +150 -42
- package/bin/lib/oxe-release.cjs +1 -0
- package/bin/oxe-cc.js +205 -113
- package/commands/oxe/debug.md +6 -1
- package/commands/oxe/discuss.md +7 -2
- package/commands/oxe/execute.md +7 -2
- package/commands/oxe/plan-agent.md +7 -2
- package/commands/oxe/plan.md +7 -2
- package/commands/oxe/scan.md +6 -1
- package/commands/oxe/spec.md +6 -1
- package/commands/oxe/verify.md +6 -1
- package/docs/CONTENT-MIGRATION-AUDIT.md +49 -0
- package/docs/RUNTIME-SMOKE-MATRIX.md +1 -1
- package/lib/runtime/compiler/graph-compiler.js +32 -0
- package/lib/runtime/context/context-pack-builder.d.ts +15 -0
- package/lib/runtime/context/context-pack-builder.js +78 -0
- package/lib/runtime/events/catalog.d.ts +1 -1
- package/lib/runtime/events/catalog.js +5 -0
- package/lib/runtime/executor/action-tool-map.d.ts +3 -0
- package/lib/runtime/executor/action-tool-map.js +41 -0
- package/lib/runtime/executor/built-in-tools.d.ts +8 -0
- package/lib/runtime/executor/built-in-tools.js +267 -0
- package/lib/runtime/executor/index.d.ts +6 -0
- package/lib/runtime/executor/index.js +12 -0
- package/lib/runtime/executor/llm-task-executor.d.ts +29 -0
- package/lib/runtime/executor/llm-task-executor.js +138 -0
- package/lib/runtime/executor/node-prompt-builder.d.ts +3 -0
- package/lib/runtime/executor/node-prompt-builder.js +36 -0
- package/lib/runtime/executor/stream-completion.d.ts +38 -0
- package/lib/runtime/executor/stream-completion.js +105 -0
- package/lib/runtime/index.d.ts +1 -0
- package/lib/runtime/index.js +2 -0
- package/lib/runtime/models/failure.d.ts +5 -0
- package/lib/runtime/models/failure.js +2 -0
- package/lib/runtime/plugins/capability-adapter.d.ts +9 -0
- package/lib/runtime/plugins/capability-adapter.js +111 -8
- package/lib/runtime/plugins/plugin-abi.d.ts +8 -0
- package/lib/runtime/plugins/plugin-registry.d.ts +2 -1
- package/lib/runtime/plugins/plugin-registry.js +6 -1
- package/lib/runtime/reducers/run-state-reducer.js +39 -2
- package/lib/runtime/scheduler/scheduler.d.ts +14 -2
- package/lib/runtime/scheduler/scheduler.js +131 -11
- package/lib/runtime/verification/verification-manifest.d.ts +5 -2
- package/oxe/agents/oxe-assumptions-analyzer.md +136 -0
- package/oxe/agents/oxe-codebase-mapper.md +142 -0
- package/oxe/agents/oxe-debugger.md +145 -0
- package/oxe/agents/oxe-executor.md +139 -0
- package/oxe/agents/oxe-integration-checker.md +142 -0
- package/oxe/agents/oxe-plan-checker.md +143 -0
- package/oxe/agents/oxe-planner.md +151 -0
- package/oxe/agents/oxe-research-synthesizer.md +146 -0
- package/oxe/agents/oxe-researcher.md +163 -0
- package/oxe/agents/oxe-ui-auditor.md +151 -0
- package/oxe/agents/oxe-ui-checker.md +157 -0
- package/oxe/agents/oxe-ui-researcher.md +179 -0
- package/oxe/agents/oxe-validation-auditor.md +154 -0
- package/oxe/agents/oxe-verifier.md +132 -0
- package/oxe/personas/README.md +91 -39
- package/oxe/personas/architect.md +149 -37
- package/oxe/personas/db-specialist.md +149 -36
- package/oxe/personas/debugger.md +155 -38
- package/oxe/personas/executor.md +164 -38
- package/oxe/personas/planner.md +165 -36
- package/oxe/personas/researcher.md +148 -35
- package/oxe/personas/ui-specialist.md +164 -36
- package/oxe/personas/verifier.md +174 -39
- package/oxe/templates/FIXTURE-PACK.template.json +18 -11
- package/oxe/templates/FIXTURE-PACK.template.md +19 -10
- package/oxe/templates/IMPLEMENTATION-PACK.template.json +26 -10
- package/oxe/templates/IMPLEMENTATION-PACK.template.md +32 -20
- package/oxe/templates/PLAN.template.md +62 -31
- package/oxe/templates/REFERENCE-ANCHORS.template.md +14 -10
- package/oxe/templates/SUMMARY.template.md +50 -20
- package/oxe/workflows/debug.md +9 -7
- package/oxe/workflows/execute.md +11 -8
- package/oxe/workflows/forensics.md +5 -3
- package/oxe/workflows/plan.md +277 -0
- package/oxe/workflows/scan.md +355 -69
- package/oxe/workflows/spec.md +302 -9
- package/oxe/workflows/ui-review.md +5 -4
- package/oxe/workflows/ui-spec.md +4 -3
- package/oxe/workflows/verify.md +8 -5
- package/package.json +26 -26
- package/packages/runtime/package.json +5 -5
- package/packages/runtime/src/compiler/graph-compiler.ts +40 -0
- package/packages/runtime/src/context/context-pack-builder.ts +80 -0
- package/packages/runtime/src/events/catalog.ts +5 -0
- package/packages/runtime/src/executor/action-tool-map.ts +46 -0
- package/packages/runtime/src/executor/built-in-tools.ts +276 -0
- package/packages/runtime/src/executor/index.ts +6 -0
- package/packages/runtime/src/executor/llm-task-executor.ts +194 -0
- package/packages/runtime/src/executor/node-prompt-builder.ts +45 -0
- package/packages/runtime/src/executor/stream-completion.ts +145 -0
- package/packages/runtime/src/index.ts +3 -0
- package/packages/runtime/src/models/failure.ts +11 -0
- package/packages/runtime/src/plugins/capability-adapter.ts +117 -10
- package/packages/runtime/src/plugins/plugin-abi.ts +9 -0
- package/packages/runtime/src/plugins/plugin-registry.ts +10 -1
- package/packages/runtime/src/reducers/run-state-reducer.ts +59 -2
- package/packages/runtime/src/scheduler/scheduler.ts +152 -14
- package/packages/runtime/src/verification/verification-manifest.ts +12 -8
- package/vscode-extension/oxe-agents-1.7.0.vsix +0 -0
- package/vscode-extension/oxe-agents-1.8.0.vsix +0 -0
- package/vscode-extension/package.json +2 -2
|
@@ -95,6 +95,38 @@ function validateGraph(graph) {
|
|
|
95
95
|
break;
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
+
// Validate mutation_scope conflicts between parallel nodes in the same wave
|
|
99
|
+
const waveMap = new Map();
|
|
100
|
+
for (const [id, node] of graph.nodes) {
|
|
101
|
+
const list = waveMap.get(node.wave) ?? [];
|
|
102
|
+
waveMap.set(node.wave, [...list, id]);
|
|
103
|
+
}
|
|
104
|
+
for (const [wave, waveNodeIds] of waveMap) {
|
|
105
|
+
for (let i = 0; i < waveNodeIds.length; i++) {
|
|
106
|
+
for (let j = i + 1; j < waveNodeIds.length; j++) {
|
|
107
|
+
const idA = waveNodeIds[i];
|
|
108
|
+
const idB = waveNodeIds[j];
|
|
109
|
+
const a = graph.nodes.get(idA);
|
|
110
|
+
const b = graph.nodes.get(idB);
|
|
111
|
+
const notDependent = !a.depends_on.includes(idB) && !b.depends_on.includes(idA);
|
|
112
|
+
if (notDependent && a.mutation_scope.length > 0 && b.mutation_scope.length > 0) {
|
|
113
|
+
const overlap = a.mutation_scope.filter(p => b.mutation_scope.includes(p));
|
|
114
|
+
if (overlap.length > 0) {
|
|
115
|
+
errors.push(`Wave ${wave}: nodes "${idA}" and "${idB}" mutate the same paths in parallel: ${overlap.join(', ')}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Validate wave ordering: a node must only depend on nodes from earlier waves
|
|
122
|
+
for (const [id, node] of graph.nodes) {
|
|
123
|
+
for (const dep of node.depends_on) {
|
|
124
|
+
const depNode = graph.nodes.get(dep);
|
|
125
|
+
if (depNode && depNode.wave >= node.wave) {
|
|
126
|
+
errors.push(`Node "${id}" (wave ${node.wave}) depends on "${dep}" (wave ${depNode.wave}) — dependency must come from an earlier wave`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
98
130
|
return errors;
|
|
99
131
|
}
|
|
100
132
|
function toSerializable(graph) {
|
|
@@ -43,6 +43,21 @@ export declare class ContextPackBuilder {
|
|
|
43
43
|
build(workItem: WorkItem, state: RunState, evidenceItems: Evidence[], evidenceContents: Map<string, string>, lessons: LessonMetric[]): ContextPack;
|
|
44
44
|
/** Convenience: build with no evidence, just lessons and state summary */
|
|
45
45
|
buildLightweight(workItem: WorkItem, state: RunState, lessons: LessonMetric[]): ContextPack;
|
|
46
|
+
/**
|
|
47
|
+
* Remove artifacts with lowest relevance until the pack fits within targetTokens.
|
|
48
|
+
* Artifacts already sorted by relevance; we trim the tail.
|
|
49
|
+
*/
|
|
50
|
+
compact(pack: ContextPack, targetTokens: number): ContextPack;
|
|
51
|
+
/**
|
|
52
|
+
* Merge groups of similar artifacts (cosine similarity >= threshold) into single
|
|
53
|
+
* combined artifacts to reduce redundancy without discarding information entirely.
|
|
54
|
+
*/
|
|
55
|
+
microCompact(artifacts: ContextArtifact[], similarityThreshold?: number): ContextArtifact[];
|
|
56
|
+
/**
|
|
57
|
+
* Automatically compact a pack to fit within hardLimitTokens.
|
|
58
|
+
* First applies microCompact (lossless merging), then compact (trimming by relevance).
|
|
59
|
+
*/
|
|
60
|
+
autoCompact(pack: ContextPack, hardLimitTokens: number): ContextPack;
|
|
46
61
|
/**
|
|
47
62
|
* Filter artifacts to those whose path-like tags are within mutation_scope.
|
|
48
63
|
* L0/L1 tiers apply the filter; L2/L3 skip it (full access).
|
|
@@ -159,6 +159,84 @@ class ContextPackBuilder {
|
|
|
159
159
|
buildLightweight(workItem, state, lessons) {
|
|
160
160
|
return this.build(workItem, state, [], new Map(), lessons);
|
|
161
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Remove artifacts with lowest relevance until the pack fits within targetTokens.
|
|
164
|
+
* Artifacts already sorted by relevance; we trim the tail.
|
|
165
|
+
*/
|
|
166
|
+
compact(pack, targetTokens) {
|
|
167
|
+
if (estimateTokens(pack.artifacts.map((a) => a.content).join('\n')) <= targetTokens) {
|
|
168
|
+
return pack;
|
|
169
|
+
}
|
|
170
|
+
const sorted = [...pack.artifacts].sort((a, b) => b.relevanceScore - a.relevanceScore);
|
|
171
|
+
const trimmed = [];
|
|
172
|
+
let used = 0;
|
|
173
|
+
for (const artifact of sorted) {
|
|
174
|
+
const t = estimateTokens(artifact.content);
|
|
175
|
+
if (used + t > targetTokens)
|
|
176
|
+
break;
|
|
177
|
+
trimmed.push(artifact);
|
|
178
|
+
used += t;
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
...pack,
|
|
182
|
+
artifacts: trimmed,
|
|
183
|
+
redundancy_removed: pack.redundancy_removed + (pack.artifacts.length - trimmed.length),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Merge groups of similar artifacts (cosine similarity >= threshold) into single
|
|
188
|
+
* combined artifacts to reduce redundancy without discarding information entirely.
|
|
189
|
+
*/
|
|
190
|
+
microCompact(artifacts, similarityThreshold = 0.7) {
|
|
191
|
+
const merged = [];
|
|
192
|
+
const used = new Set();
|
|
193
|
+
for (let i = 0; i < artifacts.length; i++) {
|
|
194
|
+
if (used.has(i))
|
|
195
|
+
continue;
|
|
196
|
+
const group = [artifacts[i]];
|
|
197
|
+
for (let j = i + 1; j < artifacts.length; j++) {
|
|
198
|
+
if (!used.has(j) && cosineSimilarity(artifacts[i], artifacts[j]) >= similarityThreshold) {
|
|
199
|
+
group.push(artifacts[j]);
|
|
200
|
+
used.add(j);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
used.add(i);
|
|
204
|
+
if (group.length === 1) {
|
|
205
|
+
merged.push(group[0]);
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
const combinedContent = group
|
|
209
|
+
.map((a) => a.content)
|
|
210
|
+
.join('\n---\n')
|
|
211
|
+
.slice(0, 4000);
|
|
212
|
+
merged.push({
|
|
213
|
+
id: group[0].id,
|
|
214
|
+
kind: group[0].kind,
|
|
215
|
+
content: combinedContent,
|
|
216
|
+
relevanceScore: group.reduce((s, a) => s + a.relevanceScore, 0) / group.length,
|
|
217
|
+
tags: [...new Set(group.flatMap((a) => a.tags))],
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return merged;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Automatically compact a pack to fit within hardLimitTokens.
|
|
225
|
+
* First applies microCompact (lossless merging), then compact (trimming by relevance).
|
|
226
|
+
*/
|
|
227
|
+
autoCompact(pack, hardLimitTokens) {
|
|
228
|
+
const currentTokens = estimateTokens(pack.artifacts.map((a) => a.content).join('\n'));
|
|
229
|
+
if (currentTokens <= hardLimitTokens)
|
|
230
|
+
return pack;
|
|
231
|
+
const microCompacted = this.microCompact(pack.artifacts);
|
|
232
|
+
const removedByMicro = pack.artifacts.length - microCompacted.length;
|
|
233
|
+
const interim = {
|
|
234
|
+
...pack,
|
|
235
|
+
artifacts: microCompacted,
|
|
236
|
+
redundancy_removed: pack.redundancy_removed + removedByMicro,
|
|
237
|
+
};
|
|
238
|
+
return this.compact(interim, hardLimitTokens);
|
|
239
|
+
}
|
|
162
240
|
/**
|
|
163
241
|
* Filter artifacts to those whose path-like tags are within mutation_scope.
|
|
164
242
|
* L0/L1 tiers apply the filter; L2/L3 skip it (full access).
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export declare const EVENT_TYPES: readonly ["SessionCreated", "RunStarted", "GraphCompiled", "WorkItemReady", "WorkspaceAllocated", "AttemptStarted", "ToolInvoked", "ToolCompleted", "ToolFailed", "EvidenceCollected", "PolicyEvaluated", "GateRequested", "GateResolved", "VerificationStarted", "VerificationCompleted", "RetryScheduled", "WorkItemCompleted", "WorkItemBlocked", "RunCompleted", "RetroPublished", "LessonPromoted"];
|
|
1
|
+
export declare const EVENT_TYPES: readonly ["SessionCreated", "RunStarted", "GraphCompiled", "WorkItemReady", "WorkspaceAllocated", "AttemptStarted", "ToolInvoked", "ToolCompleted", "ToolFailed", "EvidenceCollected", "PolicyEvaluated", "GateRequested", "GateResolved", "VerificationStarted", "VerificationCompleted", "RetryScheduled", "WorkItemCompleted", "WorkItemBlocked", "RunCompleted", "RetroPublished", "LessonPromoted", "RunAborted", "RollbackExecuted", "RollbackFailed", "TaskErrorBoundaryTripped", "WorkspaceDisposeFailed"];
|
|
2
2
|
export type EventType = (typeof EVENT_TYPES)[number];
|
|
3
3
|
export declare function isValidEventType(type: string): type is EventType;
|
|
@@ -24,6 +24,11 @@ exports.EVENT_TYPES = [
|
|
|
24
24
|
'RunCompleted',
|
|
25
25
|
'RetroPublished',
|
|
26
26
|
'LessonPromoted',
|
|
27
|
+
'RunAborted',
|
|
28
|
+
'RollbackExecuted',
|
|
29
|
+
'RollbackFailed',
|
|
30
|
+
'TaskErrorBoundaryTripped',
|
|
31
|
+
'WorkspaceDisposeFailed',
|
|
27
32
|
];
|
|
28
33
|
function isValidEventType(type) {
|
|
29
34
|
return exports.EVENT_TYPES.includes(type);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.selectToolsForActions = selectToolsForActions;
|
|
4
|
+
const built_in_tools_1 = require("./built-in-tools");
|
|
5
|
+
const READ_TOOLS = [
|
|
6
|
+
built_in_tools_1.BUILT_IN_TOOLS.read_file.schema,
|
|
7
|
+
built_in_tools_1.BUILT_IN_TOOLS.glob.schema,
|
|
8
|
+
built_in_tools_1.BUILT_IN_TOOLS.grep.schema,
|
|
9
|
+
];
|
|
10
|
+
const PATCH_TOOLS = [
|
|
11
|
+
built_in_tools_1.BUILT_IN_TOOLS.read_file.schema,
|
|
12
|
+
built_in_tools_1.BUILT_IN_TOOLS.write_file.schema,
|
|
13
|
+
built_in_tools_1.BUILT_IN_TOOLS.patch_file.schema,
|
|
14
|
+
];
|
|
15
|
+
const RUN_TOOLS = [built_in_tools_1.BUILT_IN_TOOLS.run_command.schema];
|
|
16
|
+
const EVIDENCE_TOOLS = [
|
|
17
|
+
built_in_tools_1.BUILT_IN_TOOLS.read_file.schema,
|
|
18
|
+
built_in_tools_1.BUILT_IN_TOOLS.glob.schema,
|
|
19
|
+
built_in_tools_1.BUILT_IN_TOOLS.run_command.schema,
|
|
20
|
+
];
|
|
21
|
+
const ACTION_TOOL_MAP = {
|
|
22
|
+
read_code: READ_TOOLS,
|
|
23
|
+
generate_patch: PATCH_TOOLS,
|
|
24
|
+
run_tests: RUN_TOOLS,
|
|
25
|
+
run_lint: RUN_TOOLS,
|
|
26
|
+
collect_evidence: EVIDENCE_TOOLS,
|
|
27
|
+
custom: built_in_tools_1.ALL_BUILT_IN_SCHEMAS,
|
|
28
|
+
};
|
|
29
|
+
function selectToolsForActions(actions) {
|
|
30
|
+
const seen = new Set();
|
|
31
|
+
const result = [];
|
|
32
|
+
for (const action of actions) {
|
|
33
|
+
for (const tool of ACTION_TOOL_MAP[action.type] ?? built_in_tools_1.ALL_BUILT_IN_SCHEMAS) {
|
|
34
|
+
if (!seen.has(tool.function.name)) {
|
|
35
|
+
seen.add(tool.function.name);
|
|
36
|
+
result.push(tool);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ToolSchema } from './stream-completion';
|
|
2
|
+
export interface BuiltInToolHandler {
|
|
3
|
+
schema: ToolSchema;
|
|
4
|
+
idempotent: boolean;
|
|
5
|
+
execute(args: Record<string, unknown>, cwd: string): Promise<string>;
|
|
6
|
+
}
|
|
7
|
+
export declare const BUILT_IN_TOOLS: Record<string, BuiltInToolHandler>;
|
|
8
|
+
export declare const ALL_BUILT_IN_SCHEMAS: ToolSchema[];
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ALL_BUILT_IN_SCHEMAS = exports.BUILT_IN_TOOLS = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const child_process_1 = require("child_process");
|
|
10
|
+
// ─── read_file ────────────────────────────────────────────────────────────────
|
|
11
|
+
const readFile = {
|
|
12
|
+
idempotent: true,
|
|
13
|
+
schema: {
|
|
14
|
+
type: 'function',
|
|
15
|
+
function: {
|
|
16
|
+
name: 'read_file',
|
|
17
|
+
description: 'Read the contents of a file',
|
|
18
|
+
parameters: {
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: {
|
|
21
|
+
path: { type: 'string', description: 'File path relative to workspace root' },
|
|
22
|
+
offset: { type: 'integer', description: 'Line to start reading from (1-based)' },
|
|
23
|
+
limit: { type: 'integer', description: 'Maximum number of lines to read' },
|
|
24
|
+
},
|
|
25
|
+
required: ['path'],
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
async execute(args, cwd) {
|
|
30
|
+
const filePath = path_1.default.resolve(cwd, String(args.path));
|
|
31
|
+
if (!fs_1.default.existsSync(filePath))
|
|
32
|
+
return `Error: file not found: ${args.path}`;
|
|
33
|
+
const content = fs_1.default.readFileSync(filePath, 'utf8');
|
|
34
|
+
const lines = content.split('\n');
|
|
35
|
+
const offset = typeof args.offset === 'number' ? args.offset - 1 : 0;
|
|
36
|
+
const limit = typeof args.limit === 'number' ? args.limit : lines.length;
|
|
37
|
+
return lines.slice(offset, offset + limit).join('\n');
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
// ─── write_file ───────────────────────────────────────────────────────────────
|
|
41
|
+
const writeFile = {
|
|
42
|
+
idempotent: false,
|
|
43
|
+
schema: {
|
|
44
|
+
type: 'function',
|
|
45
|
+
function: {
|
|
46
|
+
name: 'write_file',
|
|
47
|
+
description: 'Write content to a file (creates or overwrites)',
|
|
48
|
+
parameters: {
|
|
49
|
+
type: 'object',
|
|
50
|
+
properties: {
|
|
51
|
+
path: { type: 'string', description: 'File path relative to workspace root' },
|
|
52
|
+
content: { type: 'string', description: 'Content to write' },
|
|
53
|
+
},
|
|
54
|
+
required: ['path', 'content'],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
async execute(args, cwd) {
|
|
59
|
+
const filePath = path_1.default.resolve(cwd, String(args.path));
|
|
60
|
+
fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
|
|
61
|
+
fs_1.default.writeFileSync(filePath, String(args.content), 'utf8');
|
|
62
|
+
return `Written ${String(args.content).length} bytes to ${args.path}`;
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
// ─── patch_file ───────────────────────────────────────────────────────────────
|
|
66
|
+
const patchFile = {
|
|
67
|
+
idempotent: false,
|
|
68
|
+
schema: {
|
|
69
|
+
type: 'function',
|
|
70
|
+
function: {
|
|
71
|
+
name: 'patch_file',
|
|
72
|
+
description: 'Replace an exact string in a file with new content',
|
|
73
|
+
parameters: {
|
|
74
|
+
type: 'object',
|
|
75
|
+
properties: {
|
|
76
|
+
path: { type: 'string', description: 'File path relative to workspace root' },
|
|
77
|
+
old_string: { type: 'string', description: 'Exact string to replace' },
|
|
78
|
+
new_string: { type: 'string', description: 'Replacement string' },
|
|
79
|
+
},
|
|
80
|
+
required: ['path', 'old_string', 'new_string'],
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
async execute(args, cwd) {
|
|
85
|
+
const filePath = path_1.default.resolve(cwd, String(args.path));
|
|
86
|
+
if (!fs_1.default.existsSync(filePath))
|
|
87
|
+
return `Error: file not found: ${args.path}`;
|
|
88
|
+
const content = fs_1.default.readFileSync(filePath, 'utf8');
|
|
89
|
+
const oldStr = String(args.old_string);
|
|
90
|
+
if (!content.includes(oldStr))
|
|
91
|
+
return `Error: old_string not found in ${args.path}`;
|
|
92
|
+
const updated = content.replace(oldStr, String(args.new_string));
|
|
93
|
+
fs_1.default.writeFileSync(filePath, updated, 'utf8');
|
|
94
|
+
return `Patched ${args.path}`;
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
// ─── glob ─────────────────────────────────────────────────────────────────────
|
|
98
|
+
const glob = {
|
|
99
|
+
idempotent: true,
|
|
100
|
+
schema: {
|
|
101
|
+
type: 'function',
|
|
102
|
+
function: {
|
|
103
|
+
name: 'glob',
|
|
104
|
+
description: 'List files matching a glob pattern within the workspace',
|
|
105
|
+
parameters: {
|
|
106
|
+
type: 'object',
|
|
107
|
+
properties: {
|
|
108
|
+
pattern: { type: 'string', description: 'Glob pattern, e.g. src/**/*.ts' },
|
|
109
|
+
},
|
|
110
|
+
required: ['pattern'],
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
async execute(args, cwd) {
|
|
115
|
+
const results = globSync(String(args.pattern), cwd);
|
|
116
|
+
return results.length ? results.join('\n') : '(no files matched)';
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
function globSync(pattern, root) {
|
|
120
|
+
const results = [];
|
|
121
|
+
const parts = pattern.split('/');
|
|
122
|
+
function walk(dir, remaining) {
|
|
123
|
+
if (!fs_1.default.existsSync(dir))
|
|
124
|
+
return;
|
|
125
|
+
const [head, ...tail] = remaining;
|
|
126
|
+
if (!head)
|
|
127
|
+
return;
|
|
128
|
+
const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
129
|
+
for (const entry of entries) {
|
|
130
|
+
const matches = micromatch(entry.name, head);
|
|
131
|
+
if (!matches)
|
|
132
|
+
continue;
|
|
133
|
+
const full = path_1.default.join(dir, entry.name);
|
|
134
|
+
if (tail.length === 0) {
|
|
135
|
+
results.push(path_1.default.relative(root, full).replace(/\\/g, '/'));
|
|
136
|
+
}
|
|
137
|
+
else if (entry.isDirectory()) {
|
|
138
|
+
if (head === '**') {
|
|
139
|
+
walk(full, remaining);
|
|
140
|
+
walk(full, tail);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
walk(full, tail);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
walk(root, parts);
|
|
149
|
+
return results;
|
|
150
|
+
}
|
|
151
|
+
function micromatch(name, pattern) {
|
|
152
|
+
if (pattern === '**')
|
|
153
|
+
return true;
|
|
154
|
+
const regex = new RegExp('^' + pattern.replace(/\./g, '\\.').replace(/\*/g, '[^/]*').replace(/\?/g, '[^/]') + '$');
|
|
155
|
+
return regex.test(name);
|
|
156
|
+
}
|
|
157
|
+
// ─── grep ─────────────────────────────────────────────────────────────────────
|
|
158
|
+
const grep = {
|
|
159
|
+
idempotent: true,
|
|
160
|
+
schema: {
|
|
161
|
+
type: 'function',
|
|
162
|
+
function: {
|
|
163
|
+
name: 'grep',
|
|
164
|
+
description: 'Search for a regex pattern in files',
|
|
165
|
+
parameters: {
|
|
166
|
+
type: 'object',
|
|
167
|
+
properties: {
|
|
168
|
+
pattern: { type: 'string', description: 'Regex pattern to search for' },
|
|
169
|
+
path: { type: 'string', description: 'File or directory to search in (relative to workspace)' },
|
|
170
|
+
},
|
|
171
|
+
required: ['pattern'],
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
async execute(args, cwd) {
|
|
176
|
+
const searchRoot = args.path ? path_1.default.resolve(cwd, String(args.path)) : cwd;
|
|
177
|
+
const regex = new RegExp(String(args.pattern));
|
|
178
|
+
const results = [];
|
|
179
|
+
grepDir(searchRoot, regex, cwd, results);
|
|
180
|
+
return results.length ? results.slice(0, 200).join('\n') : '(no matches)';
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
function grepDir(dir, regex, root, out) {
|
|
184
|
+
if (!fs_1.default.existsSync(dir))
|
|
185
|
+
return;
|
|
186
|
+
const stat = fs_1.default.statSync(dir);
|
|
187
|
+
if (stat.isFile()) {
|
|
188
|
+
grepFile(dir, regex, root, out);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
for (const entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
|
|
192
|
+
const full = path_1.default.join(dir, entry.name);
|
|
193
|
+
if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
|
|
194
|
+
grepDir(full, regex, root, out);
|
|
195
|
+
}
|
|
196
|
+
else if (entry.isFile()) {
|
|
197
|
+
grepFile(full, regex, root, out);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function grepFile(filePath, regex, root, out) {
|
|
202
|
+
try {
|
|
203
|
+
const content = fs_1.default.readFileSync(filePath, 'utf8');
|
|
204
|
+
content.split('\n').forEach((line, i) => {
|
|
205
|
+
if (regex.test(line)) {
|
|
206
|
+
out.push(`${path_1.default.relative(root, filePath).replace(/\\/g, '/')}:${i + 1}: ${line}`);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
catch { /* skip binary or unreadable files */ }
|
|
211
|
+
}
|
|
212
|
+
// ─── run_command ──────────────────────────────────────────────────────────────
|
|
213
|
+
const runCommand = {
|
|
214
|
+
idempotent: false,
|
|
215
|
+
schema: {
|
|
216
|
+
type: 'function',
|
|
217
|
+
function: {
|
|
218
|
+
name: 'run_command',
|
|
219
|
+
description: 'Run a shell command in the workspace directory',
|
|
220
|
+
parameters: {
|
|
221
|
+
type: 'object',
|
|
222
|
+
properties: {
|
|
223
|
+
command: { type: 'string', description: 'Shell command to execute' },
|
|
224
|
+
timeout_ms: { type: 'integer', description: 'Timeout in milliseconds (default 30000)' },
|
|
225
|
+
},
|
|
226
|
+
required: ['command'],
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
async execute(args, cwd) {
|
|
231
|
+
const command = String(args.command);
|
|
232
|
+
const timeoutMs = typeof args.timeout_ms === 'number' ? args.timeout_ms : 30000;
|
|
233
|
+
return runShell(command, cwd, timeoutMs);
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
function runShell(command, cwd, timeoutMs) {
|
|
237
|
+
return new Promise((resolve) => {
|
|
238
|
+
const isWin = process.platform === 'win32';
|
|
239
|
+
const shell = isWin ? 'cmd' : 'sh';
|
|
240
|
+
const shellArgs = isWin ? ['/c', command] : ['-c', command];
|
|
241
|
+
const proc = (0, child_process_1.spawn)(shell, shellArgs, { cwd, stdio: 'pipe' });
|
|
242
|
+
const out = [];
|
|
243
|
+
let timedOut = false;
|
|
244
|
+
const timer = setTimeout(() => { timedOut = true; proc.kill('SIGTERM'); }, timeoutMs);
|
|
245
|
+
proc.stdout.on('data', (c) => out.push(c));
|
|
246
|
+
proc.stderr.on('data', (c) => out.push(c));
|
|
247
|
+
proc.on('close', (code) => {
|
|
248
|
+
clearTimeout(timer);
|
|
249
|
+
const text = Buffer.concat(out).toString('utf8').trim();
|
|
250
|
+
if (timedOut)
|
|
251
|
+
resolve(`[timed out after ${timeoutMs}ms]\n${text}`);
|
|
252
|
+
else
|
|
253
|
+
resolve(code === 0 ? text || '(no output)' : `[exit ${code}]\n${text}`);
|
|
254
|
+
});
|
|
255
|
+
proc.on('error', (err) => { clearTimeout(timer); resolve(`[error] ${err}`); });
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
// ─── Registry ─────────────────────────────────────────────────────────────────
|
|
259
|
+
exports.BUILT_IN_TOOLS = {
|
|
260
|
+
read_file: readFile,
|
|
261
|
+
write_file: writeFile,
|
|
262
|
+
patch_file: patchFile,
|
|
263
|
+
glob,
|
|
264
|
+
grep,
|
|
265
|
+
run_command: runCommand,
|
|
266
|
+
};
|
|
267
|
+
exports.ALL_BUILT_IN_SCHEMAS = Object.values(exports.BUILT_IN_TOOLS).map((t) => t.schema);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { LlmTaskExecutor } from './llm-task-executor';
|
|
2
|
+
export type { LlmProviderConfig, LlmExecutorEvent, LlmExecutorEventType } from './llm-task-executor';
|
|
3
|
+
export { buildNodePrompt } from './node-prompt-builder';
|
|
4
|
+
export { selectToolsForActions } from './action-tool-map';
|
|
5
|
+
export { BUILT_IN_TOOLS, ALL_BUILT_IN_SCHEMAS } from './built-in-tools';
|
|
6
|
+
export type { BuiltInToolHandler } from './built-in-tools';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ALL_BUILT_IN_SCHEMAS = exports.BUILT_IN_TOOLS = exports.selectToolsForActions = exports.buildNodePrompt = exports.LlmTaskExecutor = void 0;
|
|
4
|
+
var llm_task_executor_1 = require("./llm-task-executor");
|
|
5
|
+
Object.defineProperty(exports, "LlmTaskExecutor", { enumerable: true, get: function () { return llm_task_executor_1.LlmTaskExecutor; } });
|
|
6
|
+
var node_prompt_builder_1 = require("./node-prompt-builder");
|
|
7
|
+
Object.defineProperty(exports, "buildNodePrompt", { enumerable: true, get: function () { return node_prompt_builder_1.buildNodePrompt; } });
|
|
8
|
+
var action_tool_map_1 = require("./action-tool-map");
|
|
9
|
+
Object.defineProperty(exports, "selectToolsForActions", { enumerable: true, get: function () { return action_tool_map_1.selectToolsForActions; } });
|
|
10
|
+
var built_in_tools_1 = require("./built-in-tools");
|
|
11
|
+
Object.defineProperty(exports, "BUILT_IN_TOOLS", { enumerable: true, get: function () { return built_in_tools_1.BUILT_IN_TOOLS; } });
|
|
12
|
+
Object.defineProperty(exports, "ALL_BUILT_IN_SCHEMAS", { enumerable: true, get: function () { return built_in_tools_1.ALL_BUILT_IN_SCHEMAS; } });
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { GraphNode } from '../compiler/graph-compiler';
|
|
2
|
+
import type { WorkspaceLease } from '../models/workspace';
|
|
3
|
+
import type { TaskExecutor, TaskResult } from '../scheduler/scheduler';
|
|
4
|
+
import type { PluginRegistry } from '../plugins/plugin-registry';
|
|
5
|
+
export interface LlmProviderConfig {
|
|
6
|
+
baseUrl: string;
|
|
7
|
+
apiKey: string;
|
|
8
|
+
model: string;
|
|
9
|
+
maxTokens?: number;
|
|
10
|
+
maxTurns?: number;
|
|
11
|
+
timeoutMs?: number;
|
|
12
|
+
systemPrompt?: string;
|
|
13
|
+
}
|
|
14
|
+
export type LlmExecutorEventType = 'turn_start' | 'turn_complete' | 'tool_call' | 'tool_result' | 'executor_complete';
|
|
15
|
+
export interface LlmExecutorEvent {
|
|
16
|
+
type: LlmExecutorEventType;
|
|
17
|
+
nodeId: string;
|
|
18
|
+
attempt: number;
|
|
19
|
+
detail?: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
export declare class LlmTaskExecutor implements TaskExecutor {
|
|
22
|
+
private readonly provider;
|
|
23
|
+
private readonly registry?;
|
|
24
|
+
private readonly onProgress?;
|
|
25
|
+
constructor(provider: LlmProviderConfig, registry?: PluginRegistry | undefined, onProgress?: ((event: LlmExecutorEvent) => void) | undefined);
|
|
26
|
+
execute(node: GraphNode, lease: WorkspaceLease, runId: string, attempt: number): Promise<TaskResult>;
|
|
27
|
+
private invokeToolCall;
|
|
28
|
+
private emit;
|
|
29
|
+
}
|