oxe-cc 1.5.1 → 1.7.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/AGENTS.md +1 -1
- package/CHANGELOG.md +45 -0
- package/README.md +19 -15
- package/bin/lib/oxe-agent-install.cjs +125 -24
- package/bin/lib/oxe-dashboard.cjs +21 -5
- package/bin/lib/oxe-project-health.cjs +120 -42
- package/bin/lib/oxe-release.cjs +77 -4
- package/bin/oxe-cc.js +155 -78
- 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/RELEASE-READINESS.md +8 -0
- package/docs/RUNTIME-SMOKE-MATRIX.md +9 -2
- 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/lib/sdk/index.cjs +10 -5
- package/lib/sdk/index.d.ts +21 -10
- 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/CONFIG.md +3 -3
- package/oxe/templates/EXECUTION-RUNTIME.template.md +1 -1
- package/oxe/templates/FIXTURE-PACK.template.json +29 -22
- package/oxe/templates/FIXTURE-PACK.template.md +20 -11
- package/oxe/templates/IMPLEMENTATION-PACK.template.json +55 -39
- package/oxe/templates/IMPLEMENTATION-PACK.template.md +28 -16
- package/oxe/templates/INVESTIGATION.template.md +38 -38
- package/oxe/templates/PLAN.template.md +63 -32
- package/oxe/templates/REFERENCE-ANCHORS.template.md +18 -14
- package/oxe/templates/RESEARCH.template.md +11 -11
- package/oxe/templates/SPEC.template.md +6 -6
- package/oxe/templates/SUMMARY.template.md +33 -3
- package/oxe/templates/config.template.json +1 -1
- package/oxe/workflows/debug.md +9 -7
- package/oxe/workflows/execute.md +31 -28
- package/oxe/workflows/forensics.md +5 -3
- package/oxe/workflows/milestone.md +12 -12
- package/oxe/workflows/next.md +1 -1
- package/oxe/workflows/plan.md +409 -132
- package/oxe/workflows/references/adaptive-discovery.md +27 -27
- package/oxe/workflows/references/flow-robustness-contract.md +80 -80
- package/oxe/workflows/references/session-path-resolution.md +71 -71
- package/oxe/workflows/references/workflow-runtime-contracts.json +127 -127
- 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 +12 -9
- package/oxe/workflows/workstream.md +16 -16
- package/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- 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.6.0.vsix +0 -0
- package/vscode-extension/oxe-agents-1.7.0.vsix +0 -0
- package/vscode-extension/package.json +1 -1
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LlmTaskExecutor = void 0;
|
|
4
|
+
const stream_completion_1 = require("./stream-completion");
|
|
5
|
+
const built_in_tools_1 = require("./built-in-tools");
|
|
6
|
+
const action_tool_map_1 = require("./action-tool-map");
|
|
7
|
+
const node_prompt_builder_1 = require("./node-prompt-builder");
|
|
8
|
+
const DEFAULT_SYSTEM_PROMPT = 'You are a precise software engineering agent. Use the tools provided to complete the task. ' +
|
|
9
|
+
'When the task is done, summarize what was accomplished in your final message without calling any tools.';
|
|
10
|
+
class LlmTaskExecutor {
|
|
11
|
+
constructor(provider, registry, onProgress) {
|
|
12
|
+
this.provider = provider;
|
|
13
|
+
this.registry = registry;
|
|
14
|
+
this.onProgress = onProgress;
|
|
15
|
+
}
|
|
16
|
+
async execute(node, lease, runId, attempt) {
|
|
17
|
+
const prompt = (0, node_prompt_builder_1.buildNodePrompt)(node, lease, runId, attempt);
|
|
18
|
+
const tools = (0, action_tool_map_1.selectToolsForActions)(node.actions);
|
|
19
|
+
const cwd = lease.root_path;
|
|
20
|
+
const maxTurns = this.provider.maxTurns ?? 10;
|
|
21
|
+
const systemPrompt = this.provider.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
22
|
+
const messages = [
|
|
23
|
+
{ role: 'system', content: systemPrompt },
|
|
24
|
+
{ role: 'user', content: prompt },
|
|
25
|
+
];
|
|
26
|
+
let finalOutput = '';
|
|
27
|
+
const evidencePaths = [];
|
|
28
|
+
for (let turn = 0; turn < maxTurns; turn++) {
|
|
29
|
+
this.emit({ type: 'turn_start', nodeId: node.id, attempt, detail: { turn } });
|
|
30
|
+
let response;
|
|
31
|
+
try {
|
|
32
|
+
response = await (0, stream_completion_1.streamCompletion)({
|
|
33
|
+
baseUrl: this.provider.baseUrl,
|
|
34
|
+
apiKey: this.provider.apiKey,
|
|
35
|
+
model: this.provider.model,
|
|
36
|
+
messages,
|
|
37
|
+
tools: tools.length > 0 ? tools : undefined,
|
|
38
|
+
maxTokens: this.provider.maxTokens,
|
|
39
|
+
timeoutMs: this.provider.timeoutMs,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
return { success: false, failure_class: 'env', evidence: evidencePaths, output: String(err) };
|
|
44
|
+
}
|
|
45
|
+
this.emit({ type: 'turn_complete', nodeId: node.id, attempt, detail: { turn, finish_reason: response.finish_reason } });
|
|
46
|
+
if (response.content)
|
|
47
|
+
finalOutput = response.content;
|
|
48
|
+
if (!response.tool_calls.length)
|
|
49
|
+
break;
|
|
50
|
+
messages.push({
|
|
51
|
+
role: 'assistant',
|
|
52
|
+
content: response.content || null,
|
|
53
|
+
tool_calls: response.tool_calls,
|
|
54
|
+
});
|
|
55
|
+
// Partition: idempotent tools run concurrently, mutations run serially
|
|
56
|
+
const [concurrent, serial] = partitionToolCalls(response.tool_calls, tools);
|
|
57
|
+
const concurrentResults = await Promise.all(concurrent.map((tc) => this.invokeToolCall(tc, cwd, node, evidencePaths)));
|
|
58
|
+
const serialResults = [];
|
|
59
|
+
for (const tc of serial) {
|
|
60
|
+
serialResults.push(await this.invokeToolCall(tc, cwd, node, evidencePaths));
|
|
61
|
+
}
|
|
62
|
+
messages.push(...concurrentResults, ...serialResults);
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
success: true,
|
|
66
|
+
failure_class: null,
|
|
67
|
+
evidence: evidencePaths,
|
|
68
|
+
output: finalOutput,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
async invokeToolCall(tc, cwd, node, evidencePaths) {
|
|
72
|
+
this.emit({ type: 'tool_call', nodeId: node.id, attempt: 0, detail: { tool: tc.function.name } });
|
|
73
|
+
let args = {};
|
|
74
|
+
try {
|
|
75
|
+
args = JSON.parse(tc.function.arguments || '{}');
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// malformed args — pass empty
|
|
79
|
+
}
|
|
80
|
+
let result;
|
|
81
|
+
const builtIn = built_in_tools_1.BUILT_IN_TOOLS[tc.function.name];
|
|
82
|
+
if (builtIn) {
|
|
83
|
+
try {
|
|
84
|
+
result = await builtIn.execute(args, cwd);
|
|
85
|
+
if (tc.function.name === 'write_file' || tc.function.name === 'patch_file') {
|
|
86
|
+
if (typeof args.path === 'string')
|
|
87
|
+
evidencePaths.push(args.path);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
result = `[tool error] ${err}`;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
// Delegate to plugin registry
|
|
96
|
+
const provider = this.registry?.toolProviderFor(tc.function.name);
|
|
97
|
+
if (provider) {
|
|
98
|
+
try {
|
|
99
|
+
const res = await provider.invoke({
|
|
100
|
+
action_type: tc.function.name,
|
|
101
|
+
work_item_id: node.id,
|
|
102
|
+
run_id: '',
|
|
103
|
+
attempt_id: '',
|
|
104
|
+
params: args,
|
|
105
|
+
workspace_root: cwd,
|
|
106
|
+
});
|
|
107
|
+
result = res.output || (res.success ? 'done' : res.error ?? 'failed');
|
|
108
|
+
evidencePaths.push(...res.evidence_paths);
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
result = `[plugin error] ${err}`;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
result = `[unknown tool: ${tc.function.name}]`;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
this.emit({ type: 'tool_result', nodeId: node.id, attempt: 0, detail: { tool: tc.function.name, length: result.length } });
|
|
119
|
+
return {
|
|
120
|
+
role: 'tool',
|
|
121
|
+
tool_call_id: tc.id,
|
|
122
|
+
name: tc.function.name,
|
|
123
|
+
content: result,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
emit(event) {
|
|
127
|
+
this.onProgress?.(event);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
exports.LlmTaskExecutor = LlmTaskExecutor;
|
|
131
|
+
function partitionToolCalls(toolCalls, schemas) {
|
|
132
|
+
const idempotentNames = new Set(schemas
|
|
133
|
+
.map((s) => s.function.name)
|
|
134
|
+
.filter((name) => built_in_tools_1.BUILT_IN_TOOLS[name]?.idempotent ?? true));
|
|
135
|
+
const concurrent = toolCalls.filter((tc) => idempotentNames.has(tc.function.name));
|
|
136
|
+
const serial = toolCalls.filter((tc) => !idempotentNames.has(tc.function.name));
|
|
137
|
+
return [concurrent, serial];
|
|
138
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildNodePrompt = buildNodePrompt;
|
|
4
|
+
function buildNodePrompt(node, lease, runId, attempt) {
|
|
5
|
+
const lines = [
|
|
6
|
+
`# Tarefa: ${node.title}`,
|
|
7
|
+
'',
|
|
8
|
+
`**Run:** ${runId} | **Attempt:** ${attempt}`,
|
|
9
|
+
`**Workspace:** ${lease.root_path}`,
|
|
10
|
+
];
|
|
11
|
+
if (node.mutation_scope.length > 0) {
|
|
12
|
+
lines.push(`**Escopo de mutação:** ${node.mutation_scope.join(', ')}`);
|
|
13
|
+
}
|
|
14
|
+
if (node.actions.length > 0) {
|
|
15
|
+
lines.push('', '## Ações requeridas');
|
|
16
|
+
for (const action of node.actions) {
|
|
17
|
+
let line = `- ${action.type}`;
|
|
18
|
+
if (action.command)
|
|
19
|
+
line += `: \`${action.command}\``;
|
|
20
|
+
if (action.targets?.length)
|
|
21
|
+
line += ` em ${action.targets.join(', ')}`;
|
|
22
|
+
lines.push(line);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (node.verify.must_pass.length > 0) {
|
|
26
|
+
lines.push('', '## Critérios de aceite');
|
|
27
|
+
for (const criterion of node.verify.must_pass) {
|
|
28
|
+
lines.push(`- ${criterion}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (node.verify.command) {
|
|
32
|
+
lines.push('', `**Verificação:** \`${node.verify.command}\``);
|
|
33
|
+
}
|
|
34
|
+
lines.push('', 'Execute as ações acima usando as ferramentas disponíveis e confirme o resultado.');
|
|
35
|
+
return lines.join('\n');
|
|
36
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export interface ChatMessage {
|
|
2
|
+
role: 'system' | 'user' | 'assistant' | 'tool';
|
|
3
|
+
content: string | null;
|
|
4
|
+
tool_calls?: ToolCall[];
|
|
5
|
+
tool_call_id?: string;
|
|
6
|
+
name?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ToolCall {
|
|
9
|
+
id: string;
|
|
10
|
+
type: 'function';
|
|
11
|
+
function: {
|
|
12
|
+
name: string;
|
|
13
|
+
arguments: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export interface CompletionResponse {
|
|
17
|
+
content: string;
|
|
18
|
+
tool_calls: ToolCall[];
|
|
19
|
+
finish_reason: string | null;
|
|
20
|
+
}
|
|
21
|
+
export interface StreamCompletionOptions {
|
|
22
|
+
baseUrl: string;
|
|
23
|
+
apiKey: string;
|
|
24
|
+
model: string;
|
|
25
|
+
messages: ChatMessage[];
|
|
26
|
+
tools?: ToolSchema[];
|
|
27
|
+
maxTokens?: number;
|
|
28
|
+
timeoutMs?: number;
|
|
29
|
+
}
|
|
30
|
+
export interface ToolSchema {
|
|
31
|
+
type: 'function';
|
|
32
|
+
function: {
|
|
33
|
+
name: string;
|
|
34
|
+
description: string;
|
|
35
|
+
parameters: Record<string, unknown>;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export declare function streamCompletion(opts: StreamCompletionOptions): Promise<CompletionResponse>;
|
|
@@ -0,0 +1,105 @@
|
|
|
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.streamCompletion = streamCompletion;
|
|
7
|
+
const https_1 = __importDefault(require("https"));
|
|
8
|
+
const http_1 = __importDefault(require("http"));
|
|
9
|
+
const url_1 = require("url");
|
|
10
|
+
async function streamCompletion(opts) {
|
|
11
|
+
const url = new url_1.URL('/chat/completions', opts.baseUrl.replace(/\/$/, '') + '/');
|
|
12
|
+
const body = JSON.stringify({
|
|
13
|
+
model: opts.model,
|
|
14
|
+
messages: opts.messages,
|
|
15
|
+
max_tokens: opts.maxTokens ?? 4096,
|
|
16
|
+
stream: true,
|
|
17
|
+
...(opts.tools?.length ? { tools: opts.tools, tool_choice: 'auto' } : {}),
|
|
18
|
+
});
|
|
19
|
+
const rawText = await fetchWithTimeout(url, opts.apiKey, body, opts.timeoutMs ?? 120000);
|
|
20
|
+
return parseSseResponse(rawText);
|
|
21
|
+
}
|
|
22
|
+
function fetchWithTimeout(url, apiKey, body, timeoutMs) {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
const isHttps = url.protocol === 'https:';
|
|
25
|
+
const transport = isHttps ? https_1.default : http_1.default;
|
|
26
|
+
const chunks = [];
|
|
27
|
+
const req = transport.request({
|
|
28
|
+
hostname: url.hostname,
|
|
29
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
30
|
+
path: url.pathname + url.search,
|
|
31
|
+
method: 'POST',
|
|
32
|
+
headers: {
|
|
33
|
+
'Content-Type': 'application/json',
|
|
34
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
35
|
+
'Content-Length': Buffer.byteLength(body),
|
|
36
|
+
},
|
|
37
|
+
}, (res) => {
|
|
38
|
+
res.on('data', (chunk) => chunks.push(chunk));
|
|
39
|
+
res.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
|
40
|
+
res.on('error', reject);
|
|
41
|
+
});
|
|
42
|
+
const timer = setTimeout(() => {
|
|
43
|
+
req.destroy(new Error(`LLM request timed out after ${timeoutMs}ms`));
|
|
44
|
+
}, timeoutMs);
|
|
45
|
+
req.on('close', () => clearTimeout(timer));
|
|
46
|
+
req.on('error', (err) => { clearTimeout(timer); reject(err); });
|
|
47
|
+
req.write(body);
|
|
48
|
+
req.end();
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function parseSseResponse(raw) {
|
|
52
|
+
let content = '';
|
|
53
|
+
const toolCallsMap = new Map();
|
|
54
|
+
let finish_reason = null;
|
|
55
|
+
for (const line of raw.split('\n')) {
|
|
56
|
+
const trimmed = line.trim();
|
|
57
|
+
if (!trimmed.startsWith('data:'))
|
|
58
|
+
continue;
|
|
59
|
+
const payload = trimmed.slice(5).trim();
|
|
60
|
+
if (payload === '[DONE]')
|
|
61
|
+
break;
|
|
62
|
+
let chunk;
|
|
63
|
+
try {
|
|
64
|
+
chunk = JSON.parse(payload);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const choices = chunk.choices;
|
|
70
|
+
if (!choices?.length)
|
|
71
|
+
continue;
|
|
72
|
+
const delta = choices[0].delta;
|
|
73
|
+
if (!delta)
|
|
74
|
+
continue;
|
|
75
|
+
if (typeof delta.content === 'string')
|
|
76
|
+
content += delta.content;
|
|
77
|
+
if (choices[0].finish_reason)
|
|
78
|
+
finish_reason = choices[0].finish_reason;
|
|
79
|
+
const deltaToolCalls = delta.tool_calls;
|
|
80
|
+
if (deltaToolCalls) {
|
|
81
|
+
for (const dtc of deltaToolCalls) {
|
|
82
|
+
const idx = dtc.index;
|
|
83
|
+
if (!toolCallsMap.has(idx)) {
|
|
84
|
+
toolCallsMap.set(idx, { id: '', name: '', args: '' });
|
|
85
|
+
}
|
|
86
|
+
const entry = toolCallsMap.get(idx);
|
|
87
|
+
if (dtc.id)
|
|
88
|
+
entry.id += dtc.id;
|
|
89
|
+
const fn = dtc.function;
|
|
90
|
+
if (fn?.name)
|
|
91
|
+
entry.name += fn.name;
|
|
92
|
+
if (fn?.arguments)
|
|
93
|
+
entry.args += fn.arguments;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const tool_calls = [...toolCallsMap.entries()]
|
|
98
|
+
.sort(([a], [b]) => a - b)
|
|
99
|
+
.map(([, tc]) => ({
|
|
100
|
+
id: tc.id || `call_${Math.random().toString(36).slice(2)}`,
|
|
101
|
+
type: 'function',
|
|
102
|
+
function: { name: tc.name, arguments: tc.args },
|
|
103
|
+
}));
|
|
104
|
+
return { content, tool_calls, finish_reason };
|
|
105
|
+
}
|
package/lib/runtime/index.d.ts
CHANGED
package/lib/runtime/index.js
CHANGED
|
@@ -44,3 +44,5 @@ __exportStar(require("./scheduler/multi-agent-coordinator"), exports);
|
|
|
44
44
|
// R4 Public ABI — Decision, Audit & Enterprise
|
|
45
45
|
__exportStar(require("./decision/index"), exports);
|
|
46
46
|
__exportStar(require("./audit/index"), exports);
|
|
47
|
+
// R5 Public ABI — LLM Task Executor
|
|
48
|
+
__exportStar(require("./executor/index"), exports);
|
|
@@ -6,7 +6,16 @@ interface CapabilityManifest {
|
|
|
6
6
|
evidenceOutputs: string[];
|
|
7
7
|
checkTypes: string[];
|
|
8
8
|
dir: string;
|
|
9
|
+
timeoutMs: number;
|
|
10
|
+
preInvokeHook: string | null;
|
|
11
|
+
postInvokeHook: string | null;
|
|
9
12
|
}
|
|
13
|
+
export declare function runCapabilityAsync(program: string, args: string[], env: NodeJS.ProcessEnv, cwd: string, timeoutMs: number, onChunk?: (chunk: string, stream: 'stdout' | 'stderr') => void): Promise<{
|
|
14
|
+
exitCode: number | null;
|
|
15
|
+
stdout: string;
|
|
16
|
+
stderr: string;
|
|
17
|
+
timedOut: boolean;
|
|
18
|
+
}>;
|
|
10
19
|
export declare function createCapabilityPlugin(projectRoot: string, manifest: CapabilityManifest): OxePlugin;
|
|
11
20
|
export declare function loadCapabilityPlugins(projectRoot: string): OxePlugin[];
|
|
12
21
|
export {};
|
|
@@ -3,12 +3,47 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runCapabilityAsync = runCapabilityAsync;
|
|
6
7
|
exports.createCapabilityPlugin = createCapabilityPlugin;
|
|
7
8
|
exports.loadCapabilityPlugins = loadCapabilityPlugins;
|
|
8
9
|
const fs_1 = __importDefault(require("fs"));
|
|
9
10
|
const path_1 = __importDefault(require("path"));
|
|
10
11
|
const child_process_1 = require("child_process");
|
|
11
12
|
const plugin_manifest_1 = require("./plugin-manifest");
|
|
13
|
+
const DEFAULT_CAPABILITY_TIMEOUT_MS = 60000;
|
|
14
|
+
async function runCapabilityAsync(program, args, env, cwd, timeoutMs, onChunk) {
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
const proc = (0, child_process_1.spawn)(program, args, { cwd, env, stdio: 'pipe' });
|
|
17
|
+
const stdoutChunks = [];
|
|
18
|
+
const stderrChunks = [];
|
|
19
|
+
let timedOut = false;
|
|
20
|
+
const timer = setTimeout(() => {
|
|
21
|
+
timedOut = true;
|
|
22
|
+
proc.kill('SIGTERM');
|
|
23
|
+
}, timeoutMs);
|
|
24
|
+
proc.stdout.on('data', (chunk) => {
|
|
25
|
+
stdoutChunks.push(chunk);
|
|
26
|
+
onChunk?.(chunk.toString(), 'stdout');
|
|
27
|
+
});
|
|
28
|
+
proc.stderr.on('data', (chunk) => {
|
|
29
|
+
stderrChunks.push(chunk);
|
|
30
|
+
onChunk?.(chunk.toString(), 'stderr');
|
|
31
|
+
});
|
|
32
|
+
proc.on('close', (exitCode) => {
|
|
33
|
+
clearTimeout(timer);
|
|
34
|
+
resolve({
|
|
35
|
+
exitCode,
|
|
36
|
+
stdout: Buffer.concat(stdoutChunks).toString('utf8'),
|
|
37
|
+
stderr: Buffer.concat(stderrChunks).toString('utf8'),
|
|
38
|
+
timedOut,
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
proc.on('error', (err) => {
|
|
42
|
+
clearTimeout(timer);
|
|
43
|
+
resolve({ exitCode: null, stdout: '', stderr: String(err), timedOut: false });
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
12
47
|
function parseFrontmatter(text) {
|
|
13
48
|
const match = String(text || '').match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
14
49
|
if (!match)
|
|
@@ -55,6 +90,7 @@ function loadCapabilityManifests(projectRoot) {
|
|
|
55
90
|
const id = String(fm.id || '').trim();
|
|
56
91
|
if (!id)
|
|
57
92
|
return null;
|
|
93
|
+
const rawTimeout = parseInt(String(fm.timeout_ms || ''), 10);
|
|
58
94
|
return {
|
|
59
95
|
id,
|
|
60
96
|
entrypoint: String(fm.entrypoint || '').trim() || null,
|
|
@@ -62,6 +98,9 @@ function loadCapabilityManifests(projectRoot) {
|
|
|
62
98
|
evidenceOutputs: parseArrayField(fm.evidence_outputs),
|
|
63
99
|
checkTypes: parseArrayField(fm.check_types || fm.supports_checks),
|
|
64
100
|
dir,
|
|
101
|
+
timeoutMs: isNaN(rawTimeout) ? DEFAULT_CAPABILITY_TIMEOUT_MS : rawTimeout,
|
|
102
|
+
preInvokeHook: String(fm.pre_invoke_hook || '').trim() || null,
|
|
103
|
+
postInvokeHook: String(fm.post_invoke_hook || '').trim() || null,
|
|
65
104
|
};
|
|
66
105
|
})
|
|
67
106
|
.filter((item) => Boolean(item));
|
|
@@ -141,18 +180,82 @@ function buildToolProvider(projectRoot, manifest) {
|
|
|
141
180
|
program = 'powershell';
|
|
142
181
|
args = ['-File', entrypoint];
|
|
143
182
|
}
|
|
144
|
-
const result =
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
183
|
+
const result = await runCapabilityAsync(program, args, env, projectRoot, manifest.timeoutMs);
|
|
184
|
+
const output = [result.stdout, result.stderr].filter(Boolean).join('\n').trim();
|
|
185
|
+
if (result.timedOut) {
|
|
186
|
+
return {
|
|
187
|
+
success: false,
|
|
188
|
+
output,
|
|
189
|
+
evidence_paths: resolveEvidencePaths(projectRoot, manifest),
|
|
190
|
+
side_effects_applied: manifest.sideEffects,
|
|
191
|
+
error: `Capability ${manifest.id} timed out after ${manifest.timeoutMs}ms`,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
149
194
|
return {
|
|
150
|
-
success: result.
|
|
151
|
-
output
|
|
195
|
+
success: result.exitCode === 0,
|
|
196
|
+
output,
|
|
152
197
|
evidence_paths: resolveEvidencePaths(projectRoot, manifest),
|
|
153
198
|
side_effects_applied: manifest.sideEffects,
|
|
154
|
-
error: result.
|
|
199
|
+
error: result.exitCode === 0 ? undefined : (result.stderr || result.stdout || `Capability exited with status ${result.exitCode}`),
|
|
200
|
+
};
|
|
201
|
+
},
|
|
202
|
+
async preInvoke(input) {
|
|
203
|
+
if (!manifest.preInvokeHook)
|
|
204
|
+
return { allowed: true };
|
|
205
|
+
const hookPath = path_1.default.isAbsolute(manifest.preInvokeHook)
|
|
206
|
+
? manifest.preInvokeHook
|
|
207
|
+
: path_1.default.join(manifest.dir, manifest.preInvokeHook);
|
|
208
|
+
const env = {
|
|
209
|
+
...process.env,
|
|
210
|
+
OXE_CAPABILITY_INPUT: JSON.stringify(input.params || {}),
|
|
211
|
+
OXE_CAPABILITY_RUN_ID: input.run_id,
|
|
212
|
+
OXE_CAPABILITY_WORK_ITEM_ID: input.work_item_id,
|
|
213
|
+
OXE_CAPABILITY_ATTEMPT_ID: input.attempt_id,
|
|
214
|
+
OXE_CAPABILITY_WORKSPACE_ROOT: input.workspace_root,
|
|
155
215
|
};
|
|
216
|
+
const ext = path_1.default.extname(hookPath).toLowerCase();
|
|
217
|
+
let program = hookPath;
|
|
218
|
+
let args = [];
|
|
219
|
+
if (ext === '.js' || ext === '.cjs' || ext === '.mjs') {
|
|
220
|
+
program = process.execPath;
|
|
221
|
+
args = [hookPath];
|
|
222
|
+
}
|
|
223
|
+
else if (ext === '.ps1') {
|
|
224
|
+
program = 'powershell';
|
|
225
|
+
args = ['-File', hookPath];
|
|
226
|
+
}
|
|
227
|
+
const result = await runCapabilityAsync(program, args, env, projectRoot, 10000);
|
|
228
|
+
return result.exitCode === 0
|
|
229
|
+
? { allowed: true }
|
|
230
|
+
: { allowed: false, reason: result.stderr || result.stdout || `pre_invoke_hook exited with status ${result.exitCode}` };
|
|
231
|
+
},
|
|
232
|
+
async postInvoke(input, _result) {
|
|
233
|
+
if (!manifest.postInvokeHook)
|
|
234
|
+
return;
|
|
235
|
+
const hookPath = path_1.default.isAbsolute(manifest.postInvokeHook)
|
|
236
|
+
? manifest.postInvokeHook
|
|
237
|
+
: path_1.default.join(manifest.dir, manifest.postInvokeHook);
|
|
238
|
+
const env = {
|
|
239
|
+
...process.env,
|
|
240
|
+
OXE_CAPABILITY_INPUT: JSON.stringify(input.params || {}),
|
|
241
|
+
OXE_CAPABILITY_RUN_ID: input.run_id,
|
|
242
|
+
OXE_CAPABILITY_WORK_ITEM_ID: input.work_item_id,
|
|
243
|
+
OXE_CAPABILITY_ATTEMPT_ID: input.attempt_id,
|
|
244
|
+
OXE_CAPABILITY_WORKSPACE_ROOT: input.workspace_root,
|
|
245
|
+
OXE_INVOKE_SUCCESS: _result.success ? '1' : '0',
|
|
246
|
+
};
|
|
247
|
+
const ext = path_1.default.extname(hookPath).toLowerCase();
|
|
248
|
+
let program = hookPath;
|
|
249
|
+
let args = [];
|
|
250
|
+
if (ext === '.js' || ext === '.cjs' || ext === '.mjs') {
|
|
251
|
+
program = process.execPath;
|
|
252
|
+
args = [hookPath];
|
|
253
|
+
}
|
|
254
|
+
else if (ext === '.ps1') {
|
|
255
|
+
program = 'powershell';
|
|
256
|
+
args = ['-File', hookPath];
|
|
257
|
+
}
|
|
258
|
+
await runCapabilityAsync(program, args, env, projectRoot, 10000).catch(() => { });
|
|
156
259
|
},
|
|
157
260
|
};
|
|
158
261
|
}
|
|
@@ -15,12 +15,20 @@ export interface ToolInvocationResult {
|
|
|
15
15
|
side_effects_applied: string[];
|
|
16
16
|
error?: string;
|
|
17
17
|
}
|
|
18
|
+
export interface PreInvokeResult {
|
|
19
|
+
allowed: boolean;
|
|
20
|
+
reason?: string;
|
|
21
|
+
}
|
|
18
22
|
export interface ToolProvider {
|
|
19
23
|
readonly name: string;
|
|
20
24
|
readonly kind: 'read' | 'mutation' | 'verification' | 'analysis' | 'external_operation';
|
|
21
25
|
readonly idempotent: boolean;
|
|
22
26
|
supports(actionType: string): boolean;
|
|
23
27
|
invoke(input: ToolInvocationInput): Promise<ToolInvocationResult>;
|
|
28
|
+
/** Optional: called before invoke. Return allowed:false to block execution. */
|
|
29
|
+
preInvoke?(input: ToolInvocationInput): Promise<PreInvokeResult>;
|
|
30
|
+
/** Optional: called after invoke. Errors are swallowed — does not affect outcome. */
|
|
31
|
+
postInvoke?(input: ToolInvocationInput, result: ToolInvocationResult): Promise<void>;
|
|
24
32
|
}
|
|
25
33
|
export interface WorkspaceProvider extends WorkspaceManager {
|
|
26
34
|
readonly name: string;
|
|
@@ -6,7 +6,8 @@ export declare class PluginRegistry {
|
|
|
6
6
|
register(plugin: OxePlugin): void;
|
|
7
7
|
unregister(name: string): void;
|
|
8
8
|
loadFromDirectory(dir: string): string[];
|
|
9
|
-
toolProviderFor(actionType: string): ToolProvider
|
|
9
|
+
toolProviderFor(actionType: string, required: true): ToolProvider;
|
|
10
|
+
toolProviderFor(actionType: string, required?: false): ToolProvider | null;
|
|
10
11
|
workspaceProviderFor(strategy: string): WorkspaceProvider | null;
|
|
11
12
|
verifierProviderFor(checkType: string): VerifierProvider | null;
|
|
12
13
|
contextProviderFor(name: string): ContextProvider | null;
|
|
@@ -54,12 +54,17 @@ class PluginRegistry {
|
|
|
54
54
|
}
|
|
55
55
|
return loaded;
|
|
56
56
|
}
|
|
57
|
-
toolProviderFor(actionType) {
|
|
57
|
+
toolProviderFor(actionType, required = false) {
|
|
58
58
|
for (const plugin of this.plugins) {
|
|
59
59
|
const provider = plugin.toolProviders?.find((p) => p.supports(actionType));
|
|
60
60
|
if (provider)
|
|
61
61
|
return provider;
|
|
62
62
|
}
|
|
63
|
+
if (required) {
|
|
64
|
+
const loaded = this.plugins.map(p => p.name).join(', ') || '(none)';
|
|
65
|
+
throw new Error(`[plugin-registry] No provider supports action type "${actionType}". ` +
|
|
66
|
+
`Loaded plugins: [${loaded}]. Load errors: ${this.loadErrors.length}.`);
|
|
67
|
+
}
|
|
63
68
|
return null;
|
|
64
69
|
}
|
|
65
70
|
workspaceProviderFor(strategy) {
|
|
@@ -31,6 +31,38 @@ function createEmptyRunState() {
|
|
|
31
31
|
function reduce(events) {
|
|
32
32
|
return events.reduce(applyEvent, createEmptyRunState());
|
|
33
33
|
}
|
|
34
|
+
const VALID_WORK_ITEM_TRANSITIONS = {
|
|
35
|
+
pending: ['ready'],
|
|
36
|
+
ready: ['running', 'completed', 'failed', 'blocked'],
|
|
37
|
+
running: ['completed', 'failed', 'blocked'],
|
|
38
|
+
failed: ['ready'], // retry path
|
|
39
|
+
completed: [], // terminal
|
|
40
|
+
blocked: [], // terminal
|
|
41
|
+
skipped: [], // terminal
|
|
42
|
+
};
|
|
43
|
+
const VALID_RUN_TRANSITIONS = {
|
|
44
|
+
planned: ['running'],
|
|
45
|
+
running: ['paused', 'failed', 'completed', 'aborted', 'cancelled', 'waiting_approval'],
|
|
46
|
+
paused: ['running', 'cancelled'],
|
|
47
|
+
waiting_approval: ['running', 'cancelled'],
|
|
48
|
+
failed: ['replaying'],
|
|
49
|
+
replaying: ['running', 'failed', 'completed'],
|
|
50
|
+
completed: [],
|
|
51
|
+
aborted: [],
|
|
52
|
+
cancelled: [],
|
|
53
|
+
};
|
|
54
|
+
function assertWorkItemTransition(itemId, from, to, eventType) {
|
|
55
|
+
const allowed = VALID_WORK_ITEM_TRANSITIONS[from] ?? [];
|
|
56
|
+
if (!allowed.includes(to)) {
|
|
57
|
+
throw new Error(`[state-machine] Invalid work item transition for "${itemId}": ${from} → ${to} (event: ${eventType})`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function assertRunTransition(from, to, eventType) {
|
|
61
|
+
const allowed = VALID_RUN_TRANSITIONS[from] ?? [];
|
|
62
|
+
if (!allowed.includes(to)) {
|
|
63
|
+
throw new Error(`[state-machine] Invalid run transition: ${from} → ${to} (event: ${eventType})`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
34
66
|
function applyEvent(state, event) {
|
|
35
67
|
switch (event.type) {
|
|
36
68
|
case 'RunStarted': {
|
|
@@ -41,6 +73,7 @@ function applyEvent(state, event) {
|
|
|
41
73
|
if (!state.run)
|
|
42
74
|
return state;
|
|
43
75
|
const status = event.payload.status ?? 'completed';
|
|
76
|
+
assertRunTransition(state.run.status, status, event.type);
|
|
44
77
|
return {
|
|
45
78
|
...state,
|
|
46
79
|
run: { ...state.run, status, ended_at: event.timestamp },
|
|
@@ -92,8 +125,10 @@ function applyEvent(state, event) {
|
|
|
92
125
|
return state;
|
|
93
126
|
const workItems = new Map(state.workItems);
|
|
94
127
|
const item = workItems.get(event.work_item_id);
|
|
95
|
-
if (item)
|
|
128
|
+
if (item) {
|
|
129
|
+
assertWorkItemTransition(event.work_item_id, item.status, 'completed', event.type);
|
|
96
130
|
workItems.set(event.work_item_id, { ...item, status: 'completed' });
|
|
131
|
+
}
|
|
97
132
|
const completedWorkItems = new Set(state.completedWorkItems);
|
|
98
133
|
completedWorkItems.add(event.work_item_id);
|
|
99
134
|
// Collect evidence refs from payload
|
|
@@ -111,8 +146,10 @@ function applyEvent(state, event) {
|
|
|
111
146
|
return state;
|
|
112
147
|
const workItems = new Map(state.workItems);
|
|
113
148
|
const item = workItems.get(event.work_item_id);
|
|
114
|
-
if (item)
|
|
149
|
+
if (item) {
|
|
150
|
+
assertWorkItemTransition(event.work_item_id, item.status, 'blocked', event.type);
|
|
115
151
|
workItems.set(event.work_item_id, { ...item, status: 'blocked' });
|
|
152
|
+
}
|
|
116
153
|
const blockedWorkItems = new Set(state.blockedWorkItems);
|
|
117
154
|
blockedWorkItems.add(event.work_item_id);
|
|
118
155
|
return { ...state, workItems, blockedWorkItems };
|