tryassay 0.1.2 → 0.3.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/dist/cli.js +37 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/runtime.d.ts +23 -0
- package/dist/commands/runtime.js +130 -0
- package/dist/commands/runtime.js.map +1 -0
- package/dist/runtime/agent-loop.d.ts +55 -0
- package/dist/runtime/agent-loop.js +502 -0
- package/dist/runtime/agent-loop.js.map +1 -0
- package/dist/runtime/audit-log.d.ts +35 -0
- package/dist/runtime/audit-log.js +115 -0
- package/dist/runtime/audit-log.js.map +1 -0
- package/dist/runtime/config-loader.d.ts +41 -0
- package/dist/runtime/config-loader.js +116 -0
- package/dist/runtime/config-loader.js.map +1 -0
- package/dist/runtime/control-server.d.ts +25 -0
- package/dist/runtime/control-server.js +83 -0
- package/dist/runtime/control-server.js.map +1 -0
- package/dist/runtime/executor.d.ts +37 -0
- package/dist/runtime/executor.js +518 -0
- package/dist/runtime/executor.js.map +1 -0
- package/dist/runtime/logger.d.ts +20 -0
- package/dist/runtime/logger.js +73 -0
- package/dist/runtime/logger.js.map +1 -0
- package/dist/runtime/observer.d.ts +48 -0
- package/dist/runtime/observer.js +294 -0
- package/dist/runtime/observer.js.map +1 -0
- package/dist/runtime/planner.d.ts +4 -0
- package/dist/runtime/planner.js +299 -0
- package/dist/runtime/planner.js.map +1 -0
- package/dist/runtime/reasoner.d.ts +4 -0
- package/dist/runtime/reasoner.js +238 -0
- package/dist/runtime/reasoner.js.map +1 -0
- package/dist/runtime/reflector.d.ts +67 -0
- package/dist/runtime/reflector.js +393 -0
- package/dist/runtime/reflector.js.map +1 -0
- package/dist/runtime/types.d.ts +321 -0
- package/dist/runtime/types.js +6 -0
- package/dist/runtime/types.js.map +1 -0
- package/dist/runtime/verifier.d.ts +46 -0
- package/dist/runtime/verifier.js +404 -0
- package/dist/runtime/verifier.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import type { SignalSourceType, Observation, FileSystemSignalConfig, WebhookSignalConfig, ScheduleSignalConfig } from './types.js';
|
|
3
|
+
export interface SignalAdapter {
|
|
4
|
+
readonly type: SignalSourceType;
|
|
5
|
+
start(emit: (obs: Observation) => void): Promise<void>;
|
|
6
|
+
stop(): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
export declare class FileWatcherAdapter implements SignalAdapter {
|
|
9
|
+
readonly type: SignalSourceType;
|
|
10
|
+
private watchers;
|
|
11
|
+
private config;
|
|
12
|
+
private debounceMs;
|
|
13
|
+
private pendingChanges;
|
|
14
|
+
private debounceTimer;
|
|
15
|
+
constructor(config: FileSystemSignalConfig, debounceMs?: number);
|
|
16
|
+
start(emit: (obs: Observation) => void): Promise<void>;
|
|
17
|
+
stop(): Promise<void>;
|
|
18
|
+
private flushPendingChanges;
|
|
19
|
+
}
|
|
20
|
+
export declare class WebhookAdapter implements SignalAdapter {
|
|
21
|
+
readonly type: SignalSourceType;
|
|
22
|
+
private server;
|
|
23
|
+
private config;
|
|
24
|
+
constructor(config: WebhookSignalConfig);
|
|
25
|
+
start(emit: (obs: Observation) => void): Promise<void>;
|
|
26
|
+
stop(): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
export declare class ScheduleAdapter implements SignalAdapter {
|
|
29
|
+
readonly type: SignalSourceType;
|
|
30
|
+
private timer;
|
|
31
|
+
private tickCount;
|
|
32
|
+
private config;
|
|
33
|
+
constructor(config: ScheduleSignalConfig);
|
|
34
|
+
start(emit: (obs: Observation) => void): Promise<void>;
|
|
35
|
+
stop(): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
export declare class Observer extends EventEmitter {
|
|
38
|
+
private adapters;
|
|
39
|
+
private queue;
|
|
40
|
+
private running;
|
|
41
|
+
private waitResolve;
|
|
42
|
+
register(adapter: SignalAdapter): void;
|
|
43
|
+
start(): Promise<void>;
|
|
44
|
+
stop(): Promise<void>;
|
|
45
|
+
dequeue(): Promise<Observation | null>;
|
|
46
|
+
get queueDepth(): number;
|
|
47
|
+
get isRunning(): boolean;
|
|
48
|
+
}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Assay Verified Agent Runtime — Observer Component
|
|
3
|
+
// Watches external signals and converts them to Observations
|
|
4
|
+
// ============================================================
|
|
5
|
+
import { EventEmitter } from 'node:events';
|
|
6
|
+
import { watch } from 'node:fs';
|
|
7
|
+
import { createServer } from 'node:http';
|
|
8
|
+
import { randomUUID } from 'node:crypto';
|
|
9
|
+
import { resolve } from 'node:path';
|
|
10
|
+
// ── FileWatcherAdapter ────────────────────────────────────────
|
|
11
|
+
export class FileWatcherAdapter {
|
|
12
|
+
type = 'filesystem';
|
|
13
|
+
watchers = [];
|
|
14
|
+
config;
|
|
15
|
+
debounceMs;
|
|
16
|
+
pendingChanges = new Map();
|
|
17
|
+
debounceTimer = null;
|
|
18
|
+
constructor(config, debounceMs = 500) {
|
|
19
|
+
this.config = config;
|
|
20
|
+
this.debounceMs = debounceMs;
|
|
21
|
+
}
|
|
22
|
+
async start(emit) {
|
|
23
|
+
for (const dirPath of this.config.paths) {
|
|
24
|
+
const resolved = resolve(dirPath);
|
|
25
|
+
const watcher = watch(resolved, { recursive: true }, (eventType, filename) => {
|
|
26
|
+
if (!filename)
|
|
27
|
+
return;
|
|
28
|
+
if (this.config.ignorePatterns?.some(p => filename.includes(p))) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const event = eventType === 'rename' ? 'create' : 'modify';
|
|
32
|
+
const fullPath = `${resolved}/${filename}`;
|
|
33
|
+
// Accumulate changes in the debounce window
|
|
34
|
+
this.pendingChanges.set(fullPath, { event, path: fullPath });
|
|
35
|
+
// Reset the debounce timer
|
|
36
|
+
if (this.debounceTimer) {
|
|
37
|
+
clearTimeout(this.debounceTimer);
|
|
38
|
+
}
|
|
39
|
+
this.debounceTimer = setTimeout(() => {
|
|
40
|
+
this.flushPendingChanges(emit);
|
|
41
|
+
}, this.debounceMs);
|
|
42
|
+
});
|
|
43
|
+
this.watchers.push(watcher);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async stop() {
|
|
47
|
+
if (this.debounceTimer) {
|
|
48
|
+
clearTimeout(this.debounceTimer);
|
|
49
|
+
this.debounceTimer = null;
|
|
50
|
+
}
|
|
51
|
+
this.pendingChanges.clear();
|
|
52
|
+
for (const watcher of this.watchers) {
|
|
53
|
+
watcher.close();
|
|
54
|
+
}
|
|
55
|
+
this.watchers = [];
|
|
56
|
+
}
|
|
57
|
+
flushPendingChanges(emit) {
|
|
58
|
+
if (this.pendingChanges.size === 0)
|
|
59
|
+
return;
|
|
60
|
+
const changes = Array.from(this.pendingChanges.values());
|
|
61
|
+
this.pendingChanges.clear();
|
|
62
|
+
this.debounceTimer = null;
|
|
63
|
+
if (changes.length === 1) {
|
|
64
|
+
// Single file change — emit as-is
|
|
65
|
+
const change = changes[0];
|
|
66
|
+
const payload = {
|
|
67
|
+
type: 'file_change',
|
|
68
|
+
event: change.event,
|
|
69
|
+
path: change.path,
|
|
70
|
+
};
|
|
71
|
+
emit({
|
|
72
|
+
id: randomUUID(),
|
|
73
|
+
source: 'filesystem',
|
|
74
|
+
urgency: 'normal',
|
|
75
|
+
timestamp: new Date().toISOString(),
|
|
76
|
+
payload,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
// Multiple file changes — emit first as representative, include all paths in observation
|
|
81
|
+
const primary = changes[0];
|
|
82
|
+
const payload = {
|
|
83
|
+
type: 'file_change',
|
|
84
|
+
event: primary.event,
|
|
85
|
+
path: primary.path,
|
|
86
|
+
};
|
|
87
|
+
emit({
|
|
88
|
+
id: randomUUID(),
|
|
89
|
+
source: 'filesystem',
|
|
90
|
+
urgency: 'normal',
|
|
91
|
+
timestamp: new Date().toISOString(),
|
|
92
|
+
payload,
|
|
93
|
+
// Store all changed paths for context
|
|
94
|
+
relatedExperienceIds: changes.map(c => c.path),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// ── WebhookAdapter ────────────────────────────────────────────
|
|
100
|
+
export class WebhookAdapter {
|
|
101
|
+
type = 'webhook';
|
|
102
|
+
server = null;
|
|
103
|
+
config;
|
|
104
|
+
constructor(config) {
|
|
105
|
+
this.config = config;
|
|
106
|
+
}
|
|
107
|
+
async start(emit) {
|
|
108
|
+
this.server = createServer((req, res) => {
|
|
109
|
+
if (req.method !== 'POST' || req.url !== this.config.path) {
|
|
110
|
+
res.writeHead(404);
|
|
111
|
+
res.end('Not Found');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (this.config.secret) {
|
|
115
|
+
const provided = req.headers['x-webhook-secret'];
|
|
116
|
+
if (provided !== this.config.secret) {
|
|
117
|
+
res.writeHead(401);
|
|
118
|
+
res.end('Unauthorized');
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const chunks = [];
|
|
123
|
+
req.on('data', (chunk) => chunks.push(chunk));
|
|
124
|
+
req.on('end', () => {
|
|
125
|
+
const rawBody = Buffer.concat(chunks).toString('utf-8');
|
|
126
|
+
let body;
|
|
127
|
+
try {
|
|
128
|
+
body = JSON.parse(rawBody);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
body = rawBody;
|
|
132
|
+
}
|
|
133
|
+
const headers = {};
|
|
134
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
135
|
+
if (typeof value === 'string') {
|
|
136
|
+
headers[key] = value;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const payload = {
|
|
140
|
+
type: 'webhook',
|
|
141
|
+
method: req.method ?? 'POST',
|
|
142
|
+
path: req.url ?? this.config.path,
|
|
143
|
+
headers,
|
|
144
|
+
body,
|
|
145
|
+
};
|
|
146
|
+
const obs = {
|
|
147
|
+
id: randomUUID(),
|
|
148
|
+
source: 'webhook',
|
|
149
|
+
urgency: 'high',
|
|
150
|
+
timestamp: new Date().toISOString(),
|
|
151
|
+
payload,
|
|
152
|
+
};
|
|
153
|
+
emit(obs);
|
|
154
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
155
|
+
res.end(JSON.stringify({ received: true, observationId: obs.id }));
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
await new Promise((resolve, reject) => {
|
|
159
|
+
this.server.listen(this.config.port, () => resolve());
|
|
160
|
+
this.server.on('error', reject);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
async stop() {
|
|
164
|
+
if (!this.server)
|
|
165
|
+
return;
|
|
166
|
+
await new Promise((resolve, reject) => {
|
|
167
|
+
this.server.close((err) => {
|
|
168
|
+
if (err)
|
|
169
|
+
reject(err);
|
|
170
|
+
else
|
|
171
|
+
resolve();
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
this.server = null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// ── ScheduleAdapter ───────────────────────────────────────────
|
|
178
|
+
export class ScheduleAdapter {
|
|
179
|
+
type = 'schedule';
|
|
180
|
+
timer = null;
|
|
181
|
+
tickCount = 0;
|
|
182
|
+
config;
|
|
183
|
+
constructor(config) {
|
|
184
|
+
this.config = config;
|
|
185
|
+
}
|
|
186
|
+
async start(emit) {
|
|
187
|
+
this.tickCount = 0;
|
|
188
|
+
this.timer = setInterval(() => {
|
|
189
|
+
this.tickCount++;
|
|
190
|
+
const payload = {
|
|
191
|
+
type: 'schedule_tick',
|
|
192
|
+
label: this.config.label,
|
|
193
|
+
tickNumber: this.tickCount,
|
|
194
|
+
};
|
|
195
|
+
const obs = {
|
|
196
|
+
id: randomUUID(),
|
|
197
|
+
source: 'schedule',
|
|
198
|
+
urgency: 'low',
|
|
199
|
+
timestamp: new Date().toISOString(),
|
|
200
|
+
payload,
|
|
201
|
+
};
|
|
202
|
+
emit(obs);
|
|
203
|
+
}, this.config.intervalMs);
|
|
204
|
+
}
|
|
205
|
+
async stop() {
|
|
206
|
+
if (this.timer !== null) {
|
|
207
|
+
clearInterval(this.timer);
|
|
208
|
+
this.timer = null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// ── Priority Queue ────────────────────────────────────────────
|
|
213
|
+
const URGENCY_ORDER = {
|
|
214
|
+
critical: 0,
|
|
215
|
+
high: 1,
|
|
216
|
+
normal: 2,
|
|
217
|
+
low: 3,
|
|
218
|
+
};
|
|
219
|
+
class PriorityQueue {
|
|
220
|
+
items = [];
|
|
221
|
+
enqueue(obs) {
|
|
222
|
+
this.items.push(obs);
|
|
223
|
+
this.items.sort((a, b) => URGENCY_ORDER[a.urgency] - URGENCY_ORDER[b.urgency]);
|
|
224
|
+
}
|
|
225
|
+
dequeue() {
|
|
226
|
+
return this.items.shift();
|
|
227
|
+
}
|
|
228
|
+
get length() {
|
|
229
|
+
return this.items.length;
|
|
230
|
+
}
|
|
231
|
+
drain() {
|
|
232
|
+
const all = this.items.splice(0);
|
|
233
|
+
return all;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// ── Observer ──────────────────────────────────────────────────
|
|
237
|
+
export class Observer extends EventEmitter {
|
|
238
|
+
adapters = [];
|
|
239
|
+
queue = new PriorityQueue();
|
|
240
|
+
running = false;
|
|
241
|
+
waitResolve = null;
|
|
242
|
+
register(adapter) {
|
|
243
|
+
this.adapters.push(adapter);
|
|
244
|
+
}
|
|
245
|
+
async start() {
|
|
246
|
+
if (this.running)
|
|
247
|
+
return;
|
|
248
|
+
this.running = true;
|
|
249
|
+
const emitFn = (obs) => {
|
|
250
|
+
if (!this.running)
|
|
251
|
+
return;
|
|
252
|
+
if (this.waitResolve) {
|
|
253
|
+
const resolve = this.waitResolve;
|
|
254
|
+
this.waitResolve = null;
|
|
255
|
+
resolve(obs);
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
this.queue.enqueue(obs);
|
|
259
|
+
}
|
|
260
|
+
this.emit('observation', obs);
|
|
261
|
+
};
|
|
262
|
+
await Promise.all(this.adapters.map(a => a.start(emitFn)));
|
|
263
|
+
}
|
|
264
|
+
async stop() {
|
|
265
|
+
this.running = false;
|
|
266
|
+
if (this.waitResolve) {
|
|
267
|
+
const resolve = this.waitResolve;
|
|
268
|
+
this.waitResolve = null;
|
|
269
|
+
resolve(null);
|
|
270
|
+
}
|
|
271
|
+
await Promise.all(this.adapters.map(a => a.stop()));
|
|
272
|
+
}
|
|
273
|
+
async dequeue() {
|
|
274
|
+
const immediate = this.queue.dequeue();
|
|
275
|
+
if (immediate)
|
|
276
|
+
return immediate;
|
|
277
|
+
if (!this.running)
|
|
278
|
+
return null;
|
|
279
|
+
return new Promise((resolve) => {
|
|
280
|
+
if (!this.running) {
|
|
281
|
+
resolve(null);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
this.waitResolve = resolve;
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
get queueDepth() {
|
|
288
|
+
return this.queue.length;
|
|
289
|
+
}
|
|
290
|
+
get isRunning() {
|
|
291
|
+
return this.running;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
//# sourceMappingURL=observer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observer.js","sourceRoot":"","sources":["../../src/runtime/observer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Assay Verified Agent Runtime — Planner
|
|
3
|
+
// Decomposes a Decision into concrete, verifiable ActionSteps
|
|
4
|
+
// ============================================================
|
|
5
|
+
import { randomUUID } from 'node:crypto';
|
|
6
|
+
import { getClient, MODEL } from '../lib/anthropic.js';
|
|
7
|
+
// ── Approval level ranking (for comparison) ─────────────────
|
|
8
|
+
const APPROVAL_RANK = {
|
|
9
|
+
auto: 0,
|
|
10
|
+
single: 1,
|
|
11
|
+
escalate: 2,
|
|
12
|
+
};
|
|
13
|
+
function highestApproval(a, b) {
|
|
14
|
+
return APPROVAL_RANK[a] >= APPROVAL_RANK[b] ? a : b;
|
|
15
|
+
}
|
|
16
|
+
// ── System prompt ───────────────────────────────────────────
|
|
17
|
+
function buildSystemPrompt(config) {
|
|
18
|
+
return `You are an operational planner for a verified agent runtime.
|
|
19
|
+
|
|
20
|
+
Your job: decompose a high-level decision into concrete, ordered action steps.
|
|
21
|
+
|
|
22
|
+
## Agent scope
|
|
23
|
+
- Name: ${config.name}
|
|
24
|
+
- Allowed directories: ${config.scope.allowedDirectories.join(', ')}
|
|
25
|
+
- Allowed commands: ${config.scope.allowedCommands.join(', ')}
|
|
26
|
+
- Allowed URLs: ${config.scope.allowedUrls.join(', ')}
|
|
27
|
+
- Blocked patterns: ${config.scope.blockedPatterns.join(', ')}
|
|
28
|
+
- Max plan steps: ${config.limits.maxPlanSteps}
|
|
29
|
+
- Command timeout: ${config.limits.commandTimeoutMs}ms
|
|
30
|
+
|
|
31
|
+
## Rules
|
|
32
|
+
1. Each step must have ONE operation (code_write, code_run, api_call, git, or message).
|
|
33
|
+
2. Every step needs preConditions (what must be true before) and postConditions (what must be true after).
|
|
34
|
+
3. Use dependsOn (array of step indices) to express ordering constraints.
|
|
35
|
+
4. Stay within the agent's allowed scope. Never produce steps that violate blocked patterns.
|
|
36
|
+
5. Be concrete — file paths, exact commands, real URLs. No placeholders.
|
|
37
|
+
6. Keep the total number of steps at or below ${config.limits.maxPlanSteps}.
|
|
38
|
+
|
|
39
|
+
## Output format
|
|
40
|
+
Respond with ONLY a JSON array of steps. No markdown, no explanation, no wrapping.
|
|
41
|
+
|
|
42
|
+
Each step object:
|
|
43
|
+
{
|
|
44
|
+
"index": 0,
|
|
45
|
+
"description": "Human-readable description",
|
|
46
|
+
"operation": { "type": "code_write"|"code_run"|"api_call"|"git"|"message", ...fields },
|
|
47
|
+
"preConditions": [{ "description": "...", "check": "..." }],
|
|
48
|
+
"postConditions": [{ "description": "...", "check": "..." }],
|
|
49
|
+
"dependsOn": [],
|
|
50
|
+
"estimatedDurationMs": 5000
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
Operation schemas:
|
|
54
|
+
- code_write: { type: "code_write", filePath: string, content: string, mode: "create"|"edit"|"append", editTarget?: string }
|
|
55
|
+
- code_run: { type: "code_run", command: string, cwd?: string, timeoutMs: number, env?: Record<string,string> }
|
|
56
|
+
- api_call: { type: "api_call", method: "GET"|"POST"|"PUT"|"DELETE"|"PATCH", url: string, headers?: Record<string,string>, body?: any, expectedStatus?: number }
|
|
57
|
+
- git: { type: "git", command: "add"|"commit"|"push"|"branch"|"checkout", args: string[], cwd?: string }
|
|
58
|
+
- message: { type: "message", channel: "console"|"slack"|"email", recipient?: string, subject?: string, content: string }`;
|
|
59
|
+
}
|
|
60
|
+
// ── User prompt ─────────────────────────────────────────────
|
|
61
|
+
function buildUserPrompt(decision) {
|
|
62
|
+
const actions = decision.proposedActions
|
|
63
|
+
.map((a, i) => `${i + 1}. [${a.operationType}] ${a.description}\n Target: ${a.target}\n Details: ${a.details}`)
|
|
64
|
+
.join('\n');
|
|
65
|
+
return `Decompose this decision into concrete action steps.
|
|
66
|
+
|
|
67
|
+
## Decision
|
|
68
|
+
ID: ${decision.id}
|
|
69
|
+
Reasoning: ${decision.reasoning}
|
|
70
|
+
Confidence: ${decision.confidence}
|
|
71
|
+
Risks: ${decision.risks.join('; ')}
|
|
72
|
+
|
|
73
|
+
## Proposed actions
|
|
74
|
+
${actions}
|
|
75
|
+
|
|
76
|
+
Produce the JSON array of steps now.`;
|
|
77
|
+
}
|
|
78
|
+
// ── Parse helpers ───────────────────────────────────────────
|
|
79
|
+
function extractJsonArray(raw) {
|
|
80
|
+
// Try direct parse first
|
|
81
|
+
const trimmed = raw.trim();
|
|
82
|
+
try {
|
|
83
|
+
const parsed = JSON.parse(trimmed);
|
|
84
|
+
if (Array.isArray(parsed))
|
|
85
|
+
return parsed;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// fall through
|
|
89
|
+
}
|
|
90
|
+
// Try extracting from markdown code block
|
|
91
|
+
const fenceMatch = trimmed.match(/```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/);
|
|
92
|
+
if (fenceMatch) {
|
|
93
|
+
try {
|
|
94
|
+
const parsed = JSON.parse(fenceMatch[1].trim());
|
|
95
|
+
if (Array.isArray(parsed))
|
|
96
|
+
return parsed;
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// fall through
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Try finding array boundaries
|
|
103
|
+
const start = trimmed.indexOf('[');
|
|
104
|
+
const end = trimmed.lastIndexOf(']');
|
|
105
|
+
if (start !== -1 && end > start) {
|
|
106
|
+
try {
|
|
107
|
+
const parsed = JSON.parse(trimmed.slice(start, end + 1));
|
|
108
|
+
if (Array.isArray(parsed))
|
|
109
|
+
return parsed;
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// fall through
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
throw new Error('Failed to extract JSON array from Claude response');
|
|
116
|
+
}
|
|
117
|
+
function isValidOperationType(t) {
|
|
118
|
+
return typeof t === 'string' && ['code_write', 'code_run', 'api_call', 'git', 'message'].includes(t);
|
|
119
|
+
}
|
|
120
|
+
function parseConditions(raw) {
|
|
121
|
+
if (!Array.isArray(raw))
|
|
122
|
+
return [];
|
|
123
|
+
return raw
|
|
124
|
+
.filter((c) => c !== null && typeof c === 'object')
|
|
125
|
+
.map((c) => ({
|
|
126
|
+
description: typeof c.description === 'string' ? c.description : 'unknown',
|
|
127
|
+
check: typeof c.check === 'string' ? c.check : '',
|
|
128
|
+
}));
|
|
129
|
+
}
|
|
130
|
+
function parseOperation(raw) {
|
|
131
|
+
if (raw === null || typeof raw !== 'object')
|
|
132
|
+
return null;
|
|
133
|
+
const op = raw;
|
|
134
|
+
if (!isValidOperationType(op.type))
|
|
135
|
+
return null;
|
|
136
|
+
switch (op.type) {
|
|
137
|
+
case 'code_write':
|
|
138
|
+
return {
|
|
139
|
+
type: 'code_write',
|
|
140
|
+
filePath: typeof op.filePath === 'string' ? op.filePath : '',
|
|
141
|
+
content: typeof op.content === 'string' ? op.content : '',
|
|
142
|
+
mode: op.mode === 'create' || op.mode === 'edit' || op.mode === 'append'
|
|
143
|
+
? op.mode
|
|
144
|
+
: 'create',
|
|
145
|
+
editTarget: typeof op.editTarget === 'string' ? op.editTarget : undefined,
|
|
146
|
+
};
|
|
147
|
+
case 'code_run':
|
|
148
|
+
return {
|
|
149
|
+
type: 'code_run',
|
|
150
|
+
command: typeof op.command === 'string' ? op.command : '',
|
|
151
|
+
cwd: typeof op.cwd === 'string' ? op.cwd : undefined,
|
|
152
|
+
timeoutMs: typeof op.timeoutMs === 'number' ? op.timeoutMs : 30_000,
|
|
153
|
+
env: op.env !== null && typeof op.env === 'object' ? op.env : undefined,
|
|
154
|
+
};
|
|
155
|
+
case 'api_call':
|
|
156
|
+
return {
|
|
157
|
+
type: 'api_call',
|
|
158
|
+
method: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'].includes(op.method)
|
|
159
|
+
? op.method
|
|
160
|
+
: 'GET',
|
|
161
|
+
url: typeof op.url === 'string' ? op.url : '',
|
|
162
|
+
headers: op.headers !== null && typeof op.headers === 'object' ? op.headers : undefined,
|
|
163
|
+
body: op.body,
|
|
164
|
+
expectedStatus: typeof op.expectedStatus === 'number' ? op.expectedStatus : undefined,
|
|
165
|
+
};
|
|
166
|
+
case 'git':
|
|
167
|
+
return {
|
|
168
|
+
type: 'git',
|
|
169
|
+
command: ['add', 'commit', 'push', 'branch', 'checkout'].includes(op.command)
|
|
170
|
+
? op.command
|
|
171
|
+
: 'add',
|
|
172
|
+
args: Array.isArray(op.args) ? op.args.filter((a) => typeof a === 'string') : [],
|
|
173
|
+
cwd: typeof op.cwd === 'string' ? op.cwd : undefined,
|
|
174
|
+
};
|
|
175
|
+
case 'message':
|
|
176
|
+
return {
|
|
177
|
+
type: 'message',
|
|
178
|
+
channel: op.channel === 'console' || op.channel === 'slack' || op.channel === 'email'
|
|
179
|
+
? op.channel
|
|
180
|
+
: 'console',
|
|
181
|
+
recipient: typeof op.recipient === 'string' ? op.recipient : undefined,
|
|
182
|
+
subject: typeof op.subject === 'string' ? op.subject : undefined,
|
|
183
|
+
content: typeof op.content === 'string' ? op.content : '',
|
|
184
|
+
};
|
|
185
|
+
default:
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function parseRawStep(raw) {
|
|
190
|
+
if (raw === null || typeof raw !== 'object')
|
|
191
|
+
return null;
|
|
192
|
+
const obj = raw;
|
|
193
|
+
const operation = parseOperation(obj.operation);
|
|
194
|
+
if (!operation)
|
|
195
|
+
return null;
|
|
196
|
+
return {
|
|
197
|
+
index: typeof obj.index === 'number' ? obj.index : 0,
|
|
198
|
+
description: typeof obj.description === 'string' ? obj.description : 'unnamed step',
|
|
199
|
+
operation,
|
|
200
|
+
preConditions: parseConditions(obj.preConditions),
|
|
201
|
+
postConditions: parseConditions(obj.postConditions),
|
|
202
|
+
dependsOn: Array.isArray(obj.dependsOn)
|
|
203
|
+
? obj.dependsOn.filter((d) => typeof d === 'number')
|
|
204
|
+
: [],
|
|
205
|
+
estimatedDurationMs: typeof obj.estimatedDurationMs === 'number' ? obj.estimatedDurationMs : 5000,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
// ── Planner ─────────────────────────────────────────────────
|
|
209
|
+
export class Planner {
|
|
210
|
+
async plan(decision, config) {
|
|
211
|
+
const planId = randomUUID();
|
|
212
|
+
const client = getClient();
|
|
213
|
+
const systemPrompt = buildSystemPrompt(config);
|
|
214
|
+
const userPrompt = buildUserPrompt(decision);
|
|
215
|
+
// Stream the response and collect content
|
|
216
|
+
let content = '';
|
|
217
|
+
let inputTokens = 0;
|
|
218
|
+
let outputTokens = 0;
|
|
219
|
+
const stream = client.messages.stream({
|
|
220
|
+
model: config.modelId || MODEL,
|
|
221
|
+
max_tokens: 8_000,
|
|
222
|
+
system: systemPrompt,
|
|
223
|
+
messages: [{ role: 'user', content: userPrompt }],
|
|
224
|
+
});
|
|
225
|
+
stream.on('text', (text) => {
|
|
226
|
+
content += text;
|
|
227
|
+
});
|
|
228
|
+
const finalMessage = await stream.finalMessage();
|
|
229
|
+
inputTokens = finalMessage.usage.input_tokens;
|
|
230
|
+
outputTokens = finalMessage.usage.output_tokens;
|
|
231
|
+
// Parse the response into steps
|
|
232
|
+
let rawSteps;
|
|
233
|
+
try {
|
|
234
|
+
const parsed = extractJsonArray(content);
|
|
235
|
+
rawSteps = parsed
|
|
236
|
+
.map(parseRawStep)
|
|
237
|
+
.filter((s) => s !== null);
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
// Graceful fallback: return a single-step plan that logs the failure
|
|
241
|
+
rawSteps = [{
|
|
242
|
+
index: 0,
|
|
243
|
+
description: `Plan generation failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
244
|
+
operation: {
|
|
245
|
+
type: 'message',
|
|
246
|
+
channel: 'console',
|
|
247
|
+
content: `Planner could not parse Claude response. Raw output length: ${content.length} chars.`,
|
|
248
|
+
},
|
|
249
|
+
preConditions: [],
|
|
250
|
+
postConditions: [],
|
|
251
|
+
dependsOn: [],
|
|
252
|
+
estimatedDurationMs: 0,
|
|
253
|
+
}];
|
|
254
|
+
}
|
|
255
|
+
// Enforce max plan steps
|
|
256
|
+
if (rawSteps.length > config.limits.maxPlanSteps) {
|
|
257
|
+
rawSteps = rawSteps.slice(0, config.limits.maxPlanSteps);
|
|
258
|
+
}
|
|
259
|
+
// Build index-to-ID map for dependsOn resolution
|
|
260
|
+
const stepIds = new Map();
|
|
261
|
+
for (const raw of rawSteps) {
|
|
262
|
+
stepIds.set(raw.index, randomUUID());
|
|
263
|
+
}
|
|
264
|
+
// Convert raw steps to ActionSteps
|
|
265
|
+
let overallRisk = 'auto';
|
|
266
|
+
const steps = rawSteps.map((raw, i) => {
|
|
267
|
+
const stepId = stepIds.get(raw.index) ?? randomUUID();
|
|
268
|
+
const opType = raw.operation.type;
|
|
269
|
+
const approvalLevel = config.approvalDefaults[opType] ?? 'single';
|
|
270
|
+
overallRisk = highestApproval(overallRisk, approvalLevel);
|
|
271
|
+
// Resolve dependsOn indices to step IDs
|
|
272
|
+
const dependsOn = raw.dependsOn
|
|
273
|
+
.map((depIndex) => stepIds.get(depIndex))
|
|
274
|
+
.filter((id) => id !== undefined);
|
|
275
|
+
return {
|
|
276
|
+
id: stepId,
|
|
277
|
+
planId,
|
|
278
|
+
index: i,
|
|
279
|
+
description: raw.description,
|
|
280
|
+
operation: raw.operation,
|
|
281
|
+
preConditions: raw.preConditions,
|
|
282
|
+
postConditions: raw.postConditions,
|
|
283
|
+
approvalLevel,
|
|
284
|
+
dependsOn,
|
|
285
|
+
};
|
|
286
|
+
});
|
|
287
|
+
const estimatedDurationMs = rawSteps.reduce((sum, s) => sum + (s.estimatedDurationMs ?? 5000), 0);
|
|
288
|
+
return {
|
|
289
|
+
id: planId,
|
|
290
|
+
decisionId: decision.id,
|
|
291
|
+
steps,
|
|
292
|
+
totalSteps: steps.length,
|
|
293
|
+
estimatedDurationMs,
|
|
294
|
+
overallRisk,
|
|
295
|
+
timestamp: new Date().toISOString(),
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
//# sourceMappingURL=planner.js.map
|