orquesta-cli 0.1.26 → 0.2.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/agents/planner/index.d.ts +2 -1
- package/dist/agents/planner/index.js +10 -2
- package/dist/core/config/config-manager.d.ts +11 -1
- package/dist/core/config/config-manager.js +60 -0
- package/dist/core/config/providers.js +16 -0
- package/dist/core/llm/llm-client.d.ts +1 -0
- package/dist/core/llm/llm-client.js +2 -0
- package/dist/core/project-context.d.ts +3 -0
- package/dist/core/project-context.js +54 -0
- package/dist/core/slash-command-handler.js +139 -0
- package/dist/orchestration/audit-log.d.ts +40 -0
- package/dist/orchestration/audit-log.js +156 -0
- package/dist/orchestration/index.d.ts +3 -0
- package/dist/orchestration/index.js +3 -0
- package/dist/orchestration/memory-store.d.ts +21 -0
- package/dist/orchestration/memory-store.js +102 -0
- package/dist/orchestration/parallel-orchestrator.d.ts +22 -0
- package/dist/orchestration/parallel-orchestrator.js +234 -0
- package/dist/orchestration/plan-executor.d.ts +1 -0
- package/dist/orchestration/plan-executor.js +160 -19
- package/dist/orchestration/worktree-manager.d.ts +25 -0
- package/dist/orchestration/worktree-manager.js +124 -0
- package/dist/tools/llm/simple/planning-tools.js +16 -2
- package/dist/types/index.d.ts +16 -0
- package/dist/ui/components/TodoListView.js +53 -15
- package/package.json +1 -1
|
@@ -7,7 +7,8 @@ export interface PlanningWithDocsResult extends PlanningResult {
|
|
|
7
7
|
export declare class PlanningLLM {
|
|
8
8
|
private llmClient;
|
|
9
9
|
private askUserCallback;
|
|
10
|
-
|
|
10
|
+
private modelOverride?;
|
|
11
|
+
constructor(llmClient: LLMClient, modelOverride?: string);
|
|
11
12
|
setAskUserCallback(callback: AskUserCallback): void;
|
|
12
13
|
clearAskUserCallback(): void;
|
|
13
14
|
generateTODOList(userRequest: string, contextMessages?: Message[]): Promise<PlanningResult>;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { logger } from '../../utils/logger.js';
|
|
2
2
|
import { buildPlanningSystemPrompt } from '../../prompts/agents/planning.js';
|
|
3
3
|
import { toolRegistry } from '../../tools/registry.js';
|
|
4
|
+
import { getProjectContext } from '../../core/project-context.js';
|
|
4
5
|
export class PlanningLLM {
|
|
5
6
|
llmClient;
|
|
6
7
|
askUserCallback = null;
|
|
7
|
-
|
|
8
|
+
modelOverride;
|
|
9
|
+
constructor(llmClient, modelOverride) {
|
|
8
10
|
this.llmClient = llmClient;
|
|
11
|
+
this.modelOverride = modelOverride;
|
|
9
12
|
}
|
|
10
13
|
setAskUserCallback(callback) {
|
|
11
14
|
logger.flow('Setting ask-user callback for Planning LLM');
|
|
@@ -18,7 +21,8 @@ export class PlanningLLM {
|
|
|
18
21
|
async generateTODOList(userRequest, contextMessages) {
|
|
19
22
|
const toolSummary = toolRegistry.getToolSummaryForPlanning();
|
|
20
23
|
const optionalToolsInfo = toolRegistry.getEnabledOptionalToolsInfo();
|
|
21
|
-
const
|
|
24
|
+
const projectContext = getProjectContext();
|
|
25
|
+
const systemPrompt = buildPlanningSystemPrompt(toolSummary, optionalToolsInfo) + projectContext;
|
|
22
26
|
const clarificationMessages = [];
|
|
23
27
|
const messages = [
|
|
24
28
|
{
|
|
@@ -71,6 +75,7 @@ Choose one of your 3 tools now.`,
|
|
|
71
75
|
tool_choice: 'required',
|
|
72
76
|
temperature: 0.7,
|
|
73
77
|
max_tokens: 2000,
|
|
78
|
+
...(this.modelOverride ? { model: this.modelOverride } : {}),
|
|
74
79
|
});
|
|
75
80
|
const choicesCount = response.choices?.length ?? 0;
|
|
76
81
|
const message = response.choices?.[0]?.message;
|
|
@@ -172,6 +177,9 @@ Choose one of your 3 tools now.`,
|
|
|
172
177
|
id: todo.id || `todo-${Date.now()}-${index}`,
|
|
173
178
|
title: todo.title || 'Untitled task',
|
|
174
179
|
status: (index === 0 ? 'in_progress' : 'pending'),
|
|
180
|
+
...(Array.isArray(todo.dependsOn) && todo.dependsOn.length > 0 ? { dependsOn: todo.dependsOn } : {}),
|
|
181
|
+
...(typeof todo.requiresFilesystem === 'boolean' ? { requiresFilesystem: todo.requiresFilesystem } : {}),
|
|
182
|
+
...(typeof todo.parallelGroup === 'string' && todo.parallelGroup ? { parallelGroup: todo.parallelGroup } : {}),
|
|
175
183
|
}));
|
|
176
184
|
return {
|
|
177
185
|
todos,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { OpenConfig, EndpointConfig, ModelInfo, OrquestaConfig } from '../../types/index.js';
|
|
1
|
+
import { OpenConfig, EndpointConfig, ModelInfo, OrquestaConfig, OrchestrationConfig, OrchestrationRole } from '../../types/index.js';
|
|
2
2
|
export declare class ConfigManager {
|
|
3
3
|
private config;
|
|
4
4
|
private initialized;
|
|
@@ -51,6 +51,16 @@ export declare class ConfigManager {
|
|
|
51
51
|
unchanged: number;
|
|
52
52
|
}>;
|
|
53
53
|
getLocalOnlyEndpoints(): EndpointConfig[];
|
|
54
|
+
getOrchestrationConfig(): OrchestrationConfig;
|
|
55
|
+
getRoleModel(role: OrchestrationRole): string | null;
|
|
56
|
+
setRoleModel(role: OrchestrationRole, modelId: string | null): Promise<void>;
|
|
57
|
+
clearRoleModels(): Promise<void>;
|
|
58
|
+
getMaxParallelWorkers(): number;
|
|
59
|
+
setMaxParallelWorkers(n: number): Promise<void>;
|
|
60
|
+
isRefinerEnabled(): boolean;
|
|
61
|
+
setRefinerEnabled(enabled: boolean): Promise<void>;
|
|
62
|
+
isWorktreeIsolationEnabled(): boolean;
|
|
63
|
+
setWorktreeIsolationEnabled(enabled: boolean): Promise<void>;
|
|
54
64
|
}
|
|
55
65
|
export declare const configManager: ConfigManager;
|
|
56
66
|
//# sourceMappingURL=config-manager.d.ts.map
|
|
@@ -337,6 +337,66 @@ export class ConfigManager {
|
|
|
337
337
|
const config = this.getConfig();
|
|
338
338
|
return config.endpoints;
|
|
339
339
|
}
|
|
340
|
+
getOrchestrationConfig() {
|
|
341
|
+
return this.config?.orchestration ?? {};
|
|
342
|
+
}
|
|
343
|
+
getRoleModel(role) {
|
|
344
|
+
const override = this.config?.orchestration?.roleModels?.[role];
|
|
345
|
+
if (override)
|
|
346
|
+
return override;
|
|
347
|
+
return this.config?.currentModel ?? null;
|
|
348
|
+
}
|
|
349
|
+
async setRoleModel(role, modelId) {
|
|
350
|
+
const config = this.getConfig();
|
|
351
|
+
if (!config.orchestration)
|
|
352
|
+
config.orchestration = {};
|
|
353
|
+
if (!config.orchestration.roleModels)
|
|
354
|
+
config.orchestration.roleModels = {};
|
|
355
|
+
if (modelId === null) {
|
|
356
|
+
delete config.orchestration.roleModels[role];
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
config.orchestration.roleModels[role] = modelId;
|
|
360
|
+
}
|
|
361
|
+
await this.saveConfig();
|
|
362
|
+
}
|
|
363
|
+
async clearRoleModels() {
|
|
364
|
+
const config = this.getConfig();
|
|
365
|
+
if (config.orchestration) {
|
|
366
|
+
config.orchestration.roleModels = {};
|
|
367
|
+
await this.saveConfig();
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
getMaxParallelWorkers() {
|
|
371
|
+
return this.config?.orchestration?.maxParallelWorkers ?? 3;
|
|
372
|
+
}
|
|
373
|
+
async setMaxParallelWorkers(n) {
|
|
374
|
+
const config = this.getConfig();
|
|
375
|
+
if (!config.orchestration)
|
|
376
|
+
config.orchestration = {};
|
|
377
|
+
config.orchestration.maxParallelWorkers = Math.max(1, Math.min(10, n));
|
|
378
|
+
await this.saveConfig();
|
|
379
|
+
}
|
|
380
|
+
isRefinerEnabled() {
|
|
381
|
+
return this.config?.orchestration?.refinerEnabled ?? false;
|
|
382
|
+
}
|
|
383
|
+
async setRefinerEnabled(enabled) {
|
|
384
|
+
const config = this.getConfig();
|
|
385
|
+
if (!config.orchestration)
|
|
386
|
+
config.orchestration = {};
|
|
387
|
+
config.orchestration.refinerEnabled = enabled;
|
|
388
|
+
await this.saveConfig();
|
|
389
|
+
}
|
|
390
|
+
isWorktreeIsolationEnabled() {
|
|
391
|
+
return this.config?.orchestration?.worktreeIsolation ?? false;
|
|
392
|
+
}
|
|
393
|
+
async setWorktreeIsolationEnabled(enabled) {
|
|
394
|
+
const config = this.getConfig();
|
|
395
|
+
if (!config.orchestration)
|
|
396
|
+
config.orchestration = {};
|
|
397
|
+
config.orchestration.worktreeIsolation = enabled;
|
|
398
|
+
await this.saveConfig();
|
|
399
|
+
}
|
|
340
400
|
}
|
|
341
401
|
export const configManager = new ConfigManager();
|
|
342
402
|
//# sourceMappingURL=config-manager.js.map
|
|
@@ -36,6 +36,22 @@ export const PROVIDERS = [
|
|
|
36
36
|
{ id: 'claude-haiku-4-5-20251001', name: 'Claude Haiku 4.5', maxTokens: 200000, capabilities: ['vision', 'tools', 'streaming'] },
|
|
37
37
|
],
|
|
38
38
|
},
|
|
39
|
+
{
|
|
40
|
+
id: 'anthropic-direct',
|
|
41
|
+
name: 'Anthropic (OpenAI-compat)',
|
|
42
|
+
baseUrl: 'https://api.anthropic.com/v1/openai',
|
|
43
|
+
authMethod: 'bearer',
|
|
44
|
+
envVars: ['ANTHROPIC_API_KEY'],
|
|
45
|
+
requiresApiKey: true,
|
|
46
|
+
isLocal: false,
|
|
47
|
+
modelsEndpoint: '/models',
|
|
48
|
+
openaiCompatible: true,
|
|
49
|
+
knownModels: [
|
|
50
|
+
{ id: 'claude-opus-4-7', name: 'Claude Opus 4.7', maxTokens: 200000, capabilities: ['vision', 'tools', 'extended_thinking', 'streaming'] },
|
|
51
|
+
{ id: 'claude-sonnet-4-6', name: 'Claude Sonnet 4.6', maxTokens: 200000, capabilities: ['vision', 'tools', 'extended_thinking', 'streaming'] },
|
|
52
|
+
{ id: 'claude-haiku-4-5-20251001', name: 'Claude Haiku 4.5', maxTokens: 200000, capabilities: ['vision', 'tools', 'streaming'] },
|
|
53
|
+
],
|
|
54
|
+
},
|
|
39
55
|
{
|
|
40
56
|
id: 'google',
|
|
41
57
|
name: 'Google Gemini',
|
|
@@ -61,6 +61,7 @@ export declare class LLMClient {
|
|
|
61
61
|
chatCompletionWithTools(messages: Message[], tools: import('../../types/index.js').ToolDefinition[], options?: {
|
|
62
62
|
getPendingMessage?: () => string | null;
|
|
63
63
|
clearPendingMessage?: () => void;
|
|
64
|
+
model?: string;
|
|
64
65
|
}): Promise<{
|
|
65
66
|
message: Message;
|
|
66
67
|
toolCalls: Array<{
|
|
@@ -415,6 +415,7 @@ export class LLMClient {
|
|
|
415
415
|
}
|
|
416
416
|
}
|
|
417
417
|
async chatCompletionWithTools(messages, tools, options) {
|
|
418
|
+
const roleModel = options?.model;
|
|
418
419
|
let workingMessages = [...messages];
|
|
419
420
|
const toolCallHistory = [];
|
|
420
421
|
let iterations = 0;
|
|
@@ -443,6 +444,7 @@ export class LLMClient {
|
|
|
443
444
|
messages: workingMessages,
|
|
444
445
|
tools,
|
|
445
446
|
tool_choice: 'required',
|
|
447
|
+
...(roleModel ? { model: roleModel } : {}),
|
|
446
448
|
});
|
|
447
449
|
}
|
|
448
450
|
catch (error) {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { logger } from '../utils/logger.js';
|
|
4
|
+
const MAX_BYTES = 32_000;
|
|
5
|
+
const CANDIDATE_PATHS = [
|
|
6
|
+
'CLAUDE.md',
|
|
7
|
+
'.claude/CLAUDE.md',
|
|
8
|
+
'AGENT.md',
|
|
9
|
+
'agent.md',
|
|
10
|
+
];
|
|
11
|
+
let cachedContext;
|
|
12
|
+
function readFirstAvailable(cwd) {
|
|
13
|
+
for (const rel of CANDIDATE_PATHS) {
|
|
14
|
+
const abs = path.join(cwd, rel);
|
|
15
|
+
try {
|
|
16
|
+
const stat = fs.statSync(abs);
|
|
17
|
+
if (!stat.isFile())
|
|
18
|
+
continue;
|
|
19
|
+
let content = fs.readFileSync(abs, 'utf-8');
|
|
20
|
+
if (Buffer.byteLength(content, 'utf-8') > MAX_BYTES) {
|
|
21
|
+
content = content.slice(0, MAX_BYTES) + '\n\n[...truncated for context window]';
|
|
22
|
+
}
|
|
23
|
+
return { path: rel, content };
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
export function getProjectContext(cwd = process.cwd()) {
|
|
32
|
+
if (cachedContext !== undefined)
|
|
33
|
+
return cachedContext ?? '';
|
|
34
|
+
const hit = readFirstAvailable(cwd);
|
|
35
|
+
if (!hit) {
|
|
36
|
+
cachedContext = null;
|
|
37
|
+
logger.debug('No CLAUDE.md / AGENT.md found in cwd', { cwd });
|
|
38
|
+
return '';
|
|
39
|
+
}
|
|
40
|
+
logger.info(`Loaded project context from ${hit.path} (${hit.content.length} chars)`);
|
|
41
|
+
cachedContext = [
|
|
42
|
+
'',
|
|
43
|
+
'## PROJECT CONTEXT',
|
|
44
|
+
`(from ${hit.path} — treat as authoritative project conventions)`,
|
|
45
|
+
'',
|
|
46
|
+
hit.content,
|
|
47
|
+
'',
|
|
48
|
+
].join('\n');
|
|
49
|
+
return cachedContext;
|
|
50
|
+
}
|
|
51
|
+
export function invalidateProjectContext() {
|
|
52
|
+
cachedContext = undefined;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=project-context.js.map
|
|
@@ -4,6 +4,7 @@ import { logger } from '../utils/logger.js';
|
|
|
4
4
|
import { fullSync } from '../orquesta/config-sync.js';
|
|
5
5
|
import { configManager } from './config/config-manager.js';
|
|
6
6
|
import { getForcedTier, setForcedTier, resetBatutaSession } from './routing-state.js';
|
|
7
|
+
import { auditLog } from '../orchestration/audit-log.js';
|
|
7
8
|
export async function executeSlashCommand(command, context) {
|
|
8
9
|
const trimmedCommand = command.trim();
|
|
9
10
|
logger.enter('executeSlashCommand', { command: trimmedCommand });
|
|
@@ -169,6 +170,144 @@ export async function executeSlashCommand(command, context) {
|
|
|
169
170
|
updatedContext: { messages: updatedMessages },
|
|
170
171
|
};
|
|
171
172
|
}
|
|
173
|
+
if (trimmedCommand === '/role' || trimmedCommand.startsWith('/role ')) {
|
|
174
|
+
const args = trimmedCommand.slice('/role'.length).trim().split(/\s+/).filter(Boolean);
|
|
175
|
+
const currentModelId = configManager.getConfig().currentModel ?? 'none';
|
|
176
|
+
const formatStatus = () => {
|
|
177
|
+
const roles = ['planner', 'executor', 'refiner'];
|
|
178
|
+
const lines = roles.map(r => {
|
|
179
|
+
const override = configManager.getOrchestrationConfig().roleModels?.[r];
|
|
180
|
+
const effective = override ?? currentModelId;
|
|
181
|
+
const suffix = override ? '' : ' (fallback to currentModel)';
|
|
182
|
+
return ` ${r.padEnd(10)} → ${effective}${suffix}`;
|
|
183
|
+
});
|
|
184
|
+
const parallel = configManager.getMaxParallelWorkers();
|
|
185
|
+
const refiner = configManager.isRefinerEnabled() ? 'on' : 'off';
|
|
186
|
+
const worktree = configManager.isWorktreeIsolationEnabled() ? 'on' : 'off';
|
|
187
|
+
return `Multi-role orchestration\n\n${lines.join('\n')}\n\n max parallel workers: ${parallel}\n refiner pass: ${refiner}\n worktree isolation: ${worktree}\n\nUsage:\n /role planner <model-id>\n /role executor <model-id>\n /role refiner <model-id> | off\n /role parallel <N>\n /role worktree on|off\n /role clear`;
|
|
188
|
+
};
|
|
189
|
+
let body;
|
|
190
|
+
try {
|
|
191
|
+
if (args.length === 0) {
|
|
192
|
+
body = formatStatus();
|
|
193
|
+
}
|
|
194
|
+
else if (args[0] === 'clear') {
|
|
195
|
+
await configManager.clearRoleModels();
|
|
196
|
+
body = 'Role pins cleared. Every role falls back to currentModel.';
|
|
197
|
+
}
|
|
198
|
+
else if (args[0] === 'parallel' && args[1]) {
|
|
199
|
+
const n = parseInt(args[1], 10);
|
|
200
|
+
if (!Number.isFinite(n) || n < 1) {
|
|
201
|
+
body = 'parallel must be an integer >= 1';
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
await configManager.setMaxParallelWorkers(n);
|
|
205
|
+
body = `Max parallel workers set to ${configManager.getMaxParallelWorkers()}.`;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
else if (args[0] === 'worktree' && (args[1] === 'on' || args[1] === 'off')) {
|
|
209
|
+
await configManager.setWorktreeIsolationEnabled(args[1] === 'on');
|
|
210
|
+
body = `Worktree isolation: ${args[1]}.`;
|
|
211
|
+
}
|
|
212
|
+
else if ((args[0] === 'planner' || args[0] === 'executor' || args[0] === 'refiner') && args[1]) {
|
|
213
|
+
const role = args[0];
|
|
214
|
+
if (role === 'refiner' && args[1] === 'off') {
|
|
215
|
+
await configManager.setRefinerEnabled(false);
|
|
216
|
+
body = 'Refiner pass disabled.';
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
await configManager.setRoleModel(role, args[1]);
|
|
220
|
+
if (role === 'refiner') {
|
|
221
|
+
await configManager.setRefinerEnabled(true);
|
|
222
|
+
body = `Refiner pinned to ${args[1]} and refiner pass enabled.`;
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
body = `${role} pinned to ${args[1]}.`;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
body = `Unknown /role usage. Run /role with no args for help.`;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
body = `Error: ${error.message}`;
|
|
235
|
+
}
|
|
236
|
+
const updatedMessages = [
|
|
237
|
+
...context.messages,
|
|
238
|
+
{ role: 'assistant', content: body },
|
|
239
|
+
];
|
|
240
|
+
context.setMessages(updatedMessages);
|
|
241
|
+
return {
|
|
242
|
+
handled: true,
|
|
243
|
+
shouldContinue: false,
|
|
244
|
+
updatedContext: { messages: updatedMessages },
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
if (trimmedCommand === '/audit' || trimmedCommand.startsWith('/audit ')) {
|
|
248
|
+
const args = trimmedCommand.slice('/audit'.length).trim().split(/\s+/).filter(Boolean);
|
|
249
|
+
let body;
|
|
250
|
+
try {
|
|
251
|
+
if (args[0] === 'tail') {
|
|
252
|
+
const n = Number(args[1]) || 20;
|
|
253
|
+
const events = await auditLog.tail(n);
|
|
254
|
+
if (events.length === 0) {
|
|
255
|
+
body = '(no audit events yet)';
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
body = `Last ${events.length} audit events:\n\n` + events
|
|
259
|
+
.map(e => `[${e.timestamp.slice(11, 19)}] ${e.kind.padEnd(22)} ${JSON.stringify(e.data).slice(0, 140)}`)
|
|
260
|
+
.join('\n');
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
else if (args[0] === 'clear') {
|
|
264
|
+
await auditLog.clear();
|
|
265
|
+
body = 'Audit log cleared.';
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
const sinceDays = args[0] === 'day' ? 1 : args[0] === 'week' ? 7 : undefined;
|
|
269
|
+
const s = await auditLog.stats({ sinceDays });
|
|
270
|
+
const period = sinceDays ? `last ${sinceDays}d` : 'lifetime';
|
|
271
|
+
const plannerLines = Object.entries(s.byPlannerModel)
|
|
272
|
+
.map(([m, c]) => ` ${m}: ${c}`).join('\n') || ' (none)';
|
|
273
|
+
const executorLines = Object.entries(s.byExecutorModel)
|
|
274
|
+
.map(([m, c]) => ` ${m}: ${c}`).join('\n') || ' (none)';
|
|
275
|
+
body = `Orchestration audit (${period})
|
|
276
|
+
|
|
277
|
+
runs: ${s.runs}
|
|
278
|
+
parallel runs: ${s.parallelRuns}
|
|
279
|
+
sequential runs: ${s.sequentialRuns}
|
|
280
|
+
total workers: ${s.totalWorkers}
|
|
281
|
+
worker failures: ${s.workerFailures}
|
|
282
|
+
avg planner ms: ${s.avgPlannerMs}
|
|
283
|
+
avg worker ms: ${s.avgWorkerMs}
|
|
284
|
+
avg waves per run: ${s.avgWavesPerRun}
|
|
285
|
+
refiner runs: ${s.refinerRuns}
|
|
286
|
+
refiner rewrites: ${s.refinerRewrites}
|
|
287
|
+
|
|
288
|
+
by planner model:
|
|
289
|
+
${plannerLines}
|
|
290
|
+
|
|
291
|
+
by executor model:
|
|
292
|
+
${executorLines}
|
|
293
|
+
|
|
294
|
+
Subcommands: /audit | /audit day | /audit week | /audit tail [N] | /audit clear`;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
body = `Error: ${error.message}`;
|
|
299
|
+
}
|
|
300
|
+
const updatedMessages = [
|
|
301
|
+
...context.messages,
|
|
302
|
+
{ role: 'assistant', content: body },
|
|
303
|
+
];
|
|
304
|
+
context.setMessages(updatedMessages);
|
|
305
|
+
return {
|
|
306
|
+
handled: true,
|
|
307
|
+
shouldContinue: false,
|
|
308
|
+
updatedContext: { messages: updatedMessages },
|
|
309
|
+
};
|
|
310
|
+
}
|
|
172
311
|
if (trimmedCommand === '/cost') {
|
|
173
312
|
const costMessage = usageTracker.formatCostDisplay();
|
|
174
313
|
const updatedMessages = [
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
declare const SCHEMA_VERSION = 1;
|
|
2
|
+
export type AuditEventKind = 'run.start' | 'planner.complete' | 'orchestrator.decision' | 'worker.start' | 'worker.complete' | 'wave.complete' | 'refiner.complete' | 'run.complete' | 'run.error';
|
|
3
|
+
export interface AuditEvent {
|
|
4
|
+
schema: typeof SCHEMA_VERSION;
|
|
5
|
+
timestamp: string;
|
|
6
|
+
sessionId: string;
|
|
7
|
+
runId: string;
|
|
8
|
+
kind: AuditEventKind;
|
|
9
|
+
data: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
declare class AuditLogger {
|
|
12
|
+
private initialized;
|
|
13
|
+
private writeQueue;
|
|
14
|
+
private currentRunId;
|
|
15
|
+
startRun(sessionId: string, data?: Record<string, unknown>): string;
|
|
16
|
+
emit(sessionId: string, kind: AuditEventKind, data?: Record<string, unknown>): void;
|
|
17
|
+
private write;
|
|
18
|
+
tail(n?: number): Promise<AuditEvent[]>;
|
|
19
|
+
stats(opts?: {
|
|
20
|
+
sinceDays?: number;
|
|
21
|
+
}): Promise<{
|
|
22
|
+
runs: number;
|
|
23
|
+
parallelRuns: number;
|
|
24
|
+
sequentialRuns: number;
|
|
25
|
+
totalWorkers: number;
|
|
26
|
+
workerFailures: number;
|
|
27
|
+
avgPlannerMs: number;
|
|
28
|
+
avgWorkerMs: number;
|
|
29
|
+
avgWavesPerRun: number;
|
|
30
|
+
byPlannerModel: Record<string, number>;
|
|
31
|
+
byExecutorModel: Record<string, number>;
|
|
32
|
+
refinerRuns: number;
|
|
33
|
+
refinerRewrites: number;
|
|
34
|
+
}>;
|
|
35
|
+
private readAll;
|
|
36
|
+
clear(): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
export declare const auditLog: AuditLogger;
|
|
39
|
+
export {};
|
|
40
|
+
//# sourceMappingURL=audit-log.d.ts.map
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { LOCAL_HOME_DIR } from '../constants.js';
|
|
4
|
+
import { logger } from '../utils/logger.js';
|
|
5
|
+
const AUDIT_LOG_PATH = path.join(LOCAL_HOME_DIR, 'audit.jsonl');
|
|
6
|
+
const SCHEMA_VERSION = 1;
|
|
7
|
+
class AuditLogger {
|
|
8
|
+
initialized = false;
|
|
9
|
+
writeQueue = Promise.resolve();
|
|
10
|
+
currentRunId = null;
|
|
11
|
+
startRun(sessionId, data = {}) {
|
|
12
|
+
this.currentRunId = `run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
13
|
+
this.emit(sessionId, 'run.start', data);
|
|
14
|
+
return this.currentRunId;
|
|
15
|
+
}
|
|
16
|
+
emit(sessionId, kind, data = {}) {
|
|
17
|
+
const runId = (typeof data['runId'] === 'string' ? data['runId'] : null) ?? this.currentRunId ?? 'unknown';
|
|
18
|
+
const event = {
|
|
19
|
+
schema: SCHEMA_VERSION,
|
|
20
|
+
timestamp: new Date().toISOString(),
|
|
21
|
+
sessionId,
|
|
22
|
+
runId,
|
|
23
|
+
kind,
|
|
24
|
+
data,
|
|
25
|
+
};
|
|
26
|
+
this.writeQueue = this.writeQueue.then(() => this.write(event)).catch(() => undefined);
|
|
27
|
+
}
|
|
28
|
+
async write(event) {
|
|
29
|
+
try {
|
|
30
|
+
if (!this.initialized) {
|
|
31
|
+
await fs.mkdir(LOCAL_HOME_DIR, { recursive: true });
|
|
32
|
+
this.initialized = true;
|
|
33
|
+
}
|
|
34
|
+
await fs.appendFile(AUDIT_LOG_PATH, JSON.stringify(event) + '\n', 'utf8');
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
logger.warn('Audit log write failed', { error: error.message });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async tail(n = 50) {
|
|
41
|
+
try {
|
|
42
|
+
const raw = await fs.readFile(AUDIT_LOG_PATH, 'utf8');
|
|
43
|
+
const lines = raw.split('\n').filter(Boolean);
|
|
44
|
+
const last = lines.slice(-n);
|
|
45
|
+
const events = [];
|
|
46
|
+
for (const line of last) {
|
|
47
|
+
try {
|
|
48
|
+
events.push(JSON.parse(line));
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return events.reverse();
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async stats(opts) {
|
|
60
|
+
const events = await this.readAll();
|
|
61
|
+
const cutoff = opts?.sinceDays ? Date.now() - opts.sinceDays * 86400_000 : 0;
|
|
62
|
+
const filtered = cutoff ? events.filter(e => Date.parse(e.timestamp) >= cutoff) : events;
|
|
63
|
+
const runs = new Set();
|
|
64
|
+
let parallelRuns = 0;
|
|
65
|
+
let sequentialRuns = 0;
|
|
66
|
+
let totalWorkers = 0;
|
|
67
|
+
let workerFailures = 0;
|
|
68
|
+
let plannerDurMs = 0;
|
|
69
|
+
let plannerCount = 0;
|
|
70
|
+
let workerDurMs = 0;
|
|
71
|
+
let workerCount = 0;
|
|
72
|
+
let waveCount = 0;
|
|
73
|
+
let refinerRuns = 0;
|
|
74
|
+
let refinerRewrites = 0;
|
|
75
|
+
const byPlannerModel = {};
|
|
76
|
+
const byExecutorModel = {};
|
|
77
|
+
for (const e of filtered) {
|
|
78
|
+
runs.add(e.runId);
|
|
79
|
+
const d = e.data;
|
|
80
|
+
if (e.kind === 'orchestrator.decision') {
|
|
81
|
+
if (d['parallel'])
|
|
82
|
+
parallelRuns++;
|
|
83
|
+
else
|
|
84
|
+
sequentialRuns++;
|
|
85
|
+
}
|
|
86
|
+
if (e.kind === 'planner.complete') {
|
|
87
|
+
plannerCount++;
|
|
88
|
+
const dur = d['durationMs'];
|
|
89
|
+
if (typeof dur === 'number')
|
|
90
|
+
plannerDurMs += dur;
|
|
91
|
+
const model = d['model'];
|
|
92
|
+
if (typeof model === 'string')
|
|
93
|
+
byPlannerModel[model] = (byPlannerModel[model] || 0) + 1;
|
|
94
|
+
}
|
|
95
|
+
if (e.kind === 'worker.complete') {
|
|
96
|
+
totalWorkers++;
|
|
97
|
+
if (d['success'] === false)
|
|
98
|
+
workerFailures++;
|
|
99
|
+
const dur = d['durationMs'];
|
|
100
|
+
if (typeof dur === 'number') {
|
|
101
|
+
workerDurMs += dur;
|
|
102
|
+
workerCount++;
|
|
103
|
+
}
|
|
104
|
+
const model = d['model'];
|
|
105
|
+
if (typeof model === 'string')
|
|
106
|
+
byExecutorModel[model] = (byExecutorModel[model] || 0) + 1;
|
|
107
|
+
}
|
|
108
|
+
if (e.kind === 'wave.complete')
|
|
109
|
+
waveCount++;
|
|
110
|
+
if (e.kind === 'refiner.complete') {
|
|
111
|
+
refinerRuns++;
|
|
112
|
+
if (d['rewrote'])
|
|
113
|
+
refinerRewrites++;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
runs: runs.size,
|
|
118
|
+
parallelRuns,
|
|
119
|
+
sequentialRuns,
|
|
120
|
+
totalWorkers,
|
|
121
|
+
workerFailures,
|
|
122
|
+
avgPlannerMs: plannerCount ? Math.round(plannerDurMs / plannerCount) : 0,
|
|
123
|
+
avgWorkerMs: workerCount ? Math.round(workerDurMs / workerCount) : 0,
|
|
124
|
+
avgWavesPerRun: runs.size ? Number((waveCount / runs.size).toFixed(2)) : 0,
|
|
125
|
+
byPlannerModel,
|
|
126
|
+
byExecutorModel,
|
|
127
|
+
refinerRuns,
|
|
128
|
+
refinerRewrites,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
async readAll() {
|
|
132
|
+
try {
|
|
133
|
+
const raw = await fs.readFile(AUDIT_LOG_PATH, 'utf8');
|
|
134
|
+
return raw.split('\n').filter(Boolean).flatMap(line => {
|
|
135
|
+
try {
|
|
136
|
+
return [JSON.parse(line)];
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async clear() {
|
|
148
|
+
try {
|
|
149
|
+
await fs.rm(AUDIT_LOG_PATH, { force: true });
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
export const auditLog = new AuditLogger();
|
|
156
|
+
//# sourceMappingURL=audit-log.js.map
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export type { ExecutionPhase, PlanExecutionState, AskUserRequest, AskUserResponse, AskUserState, StateCallbacks, ExecutionContext, ExecutionResult, PlanExecutionActions, } from './types.js';
|
|
2
2
|
export { formatErrorMessage, buildTodoContext, areAllTodosCompleted, findActiveTodo, getTodoStats, } from './utils.js';
|
|
3
3
|
export { PlanExecutor, planExecutor } from './plan-executor.js';
|
|
4
|
+
export { WorktreeManager, worktreeManager, type WorktreeAllocation, type WorktreeBackend, type MergeResult, } from './worktree-manager.js';
|
|
5
|
+
export { MemoryStore, memoryStore, type MemoryEntry, } from './memory-store.js';
|
|
6
|
+
export { auditLog, type AuditEvent, type AuditEventKind, } from './audit-log.js';
|
|
4
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
export { formatErrorMessage, buildTodoContext, areAllTodosCompleted, findActiveTodo, getTodoStats, } from './utils.js';
|
|
2
2
|
export { PlanExecutor, planExecutor } from './plan-executor.js';
|
|
3
|
+
export { WorktreeManager, worktreeManager, } from './worktree-manager.js';
|
|
4
|
+
export { MemoryStore, memoryStore, } from './memory-store.js';
|
|
5
|
+
export { auditLog, } from './audit-log.js';
|
|
3
6
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface MemoryEntry {
|
|
2
|
+
writtenAt: string;
|
|
3
|
+
source?: 'planner' | 'executor' | 'worker' | 'refiner' | 'user' | string;
|
|
4
|
+
value: unknown;
|
|
5
|
+
}
|
|
6
|
+
export declare class MemoryStore {
|
|
7
|
+
private cache;
|
|
8
|
+
private pendingFlush;
|
|
9
|
+
private pendingLoad;
|
|
10
|
+
private filePath;
|
|
11
|
+
load(sessionId: string): Promise<void>;
|
|
12
|
+
get(sessionId: string, key: string): MemoryEntry | null;
|
|
13
|
+
all(sessionId: string): Record<string, MemoryEntry>;
|
|
14
|
+
bySource(sessionId: string, source: MemoryEntry['source']): MemoryEntry[];
|
|
15
|
+
set(sessionId: string, key: string, value: unknown, source?: MemoryEntry['source']): Promise<void>;
|
|
16
|
+
delete(sessionId: string, key: string): Promise<void>;
|
|
17
|
+
clear(sessionId: string): Promise<void>;
|
|
18
|
+
private scheduleFlush;
|
|
19
|
+
}
|
|
20
|
+
export declare const memoryStore: MemoryStore;
|
|
21
|
+
//# sourceMappingURL=memory-store.d.ts.map
|