overarch-darwin-arm64 0.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/LICENSE +21 -0
- package/README.md +214 -0
- package/dist/bin/overarch.d.ts +2 -0
- package/dist/bin/overarch.js +178 -0
- package/dist/src/agent/context.d.ts +14 -0
- package/dist/src/agent/context.js +200 -0
- package/dist/src/agent/context_compressor.d.ts +21 -0
- package/dist/src/agent/context_compressor.js +90 -0
- package/dist/src/agent/manifest.d.ts +22 -0
- package/dist/src/agent/manifest.js +131 -0
- package/dist/src/agent/planner.d.ts +38 -0
- package/dist/src/agent/planner.js +118 -0
- package/dist/src/agent/runtime.d.ts +59 -0
- package/dist/src/agent/runtime.js +400 -0
- package/dist/src/cli/approval.d.ts +13 -0
- package/dist/src/cli/approval.js +71 -0
- package/dist/src/cli/args.d.ts +15 -0
- package/dist/src/cli/args.js +47 -0
- package/dist/src/cli/commands/audit.d.ts +10 -0
- package/dist/src/cli/commands/audit.js +79 -0
- package/dist/src/cli/commands/companion.d.ts +1 -0
- package/dist/src/cli/commands/companion.js +143 -0
- package/dist/src/cli/commands/config.d.ts +1 -0
- package/dist/src/cli/commands/config.js +135 -0
- package/dist/src/cli/commands/doctor.d.ts +1 -0
- package/dist/src/cli/commands/doctor.js +217 -0
- package/dist/src/cli/commands/init.d.ts +1 -0
- package/dist/src/cli/commands/init.js +134 -0
- package/dist/src/cli/commands/memory.d.ts +2 -0
- package/dist/src/cli/commands/memory.js +98 -0
- package/dist/src/cli/commands/onboarding.d.ts +3 -0
- package/dist/src/cli/commands/onboarding.js +223 -0
- package/dist/src/cli/commands/sessions.d.ts +1 -0
- package/dist/src/cli/commands/sessions.js +22 -0
- package/dist/src/cli/commands/status.d.ts +1 -0
- package/dist/src/cli/commands/status.js +37 -0
- package/dist/src/cli/commands/update.d.ts +9 -0
- package/dist/src/cli/commands/update.js +105 -0
- package/dist/src/cli/repl.d.ts +3 -0
- package/dist/src/cli/repl.js +821 -0
- package/dist/src/cli/ui.d.ts +53 -0
- package/dist/src/cli/ui.js +215 -0
- package/dist/src/companion/petdex_adapter.d.ts +31 -0
- package/dist/src/companion/petdex_adapter.js +171 -0
- package/dist/src/companion/petdex_bridge.d.ts +65 -0
- package/dist/src/companion/petdex_bridge.js +356 -0
- package/dist/src/companion/types.d.ts +57 -0
- package/dist/src/companion/types.js +11 -0
- package/dist/src/config/index.d.ts +24 -0
- package/dist/src/config/index.js +237 -0
- package/dist/src/config/types.d.ts +58 -0
- package/dist/src/config/types.js +7 -0
- package/dist/src/core/diagnostics.d.ts +48 -0
- package/dist/src/core/diagnostics.js +115 -0
- package/dist/src/core/index.d.ts +23 -0
- package/dist/src/core/index.js +23 -0
- package/dist/src/core/mission.d.ts +55 -0
- package/dist/src/core/mission.js +173 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/learning/memory_manager.d.ts +26 -0
- package/dist/src/learning/memory_manager.js +211 -0
- package/dist/src/learning/types.d.ts +54 -0
- package/dist/src/learning/types.js +1 -0
- package/dist/src/mcp/registry.d.ts +17 -0
- package/dist/src/mcp/registry.js +97 -0
- package/dist/src/mcp/types.d.ts +13 -0
- package/dist/src/mcp/types.js +1 -0
- package/dist/src/providers/anthropic.d.ts +10 -0
- package/dist/src/providers/anthropic.js +95 -0
- package/dist/src/providers/base.d.ts +15 -0
- package/dist/src/providers/base.js +108 -0
- package/dist/src/providers/factory.d.ts +28 -0
- package/dist/src/providers/factory.js +142 -0
- package/dist/src/providers/gemini.d.ts +10 -0
- package/dist/src/providers/gemini.js +97 -0
- package/dist/src/providers/openai.d.ts +9 -0
- package/dist/src/providers/openai.js +85 -0
- package/dist/src/providers/openrouter.d.ts +5 -0
- package/dist/src/providers/openrouter.js +14 -0
- package/dist/src/providers/registry.d.ts +19 -0
- package/dist/src/providers/registry.js +209 -0
- package/dist/src/providers/types.d.ts +59 -0
- package/dist/src/providers/types.js +1 -0
- package/dist/src/providers/validator.d.ts +24 -0
- package/dist/src/providers/validator.js +161 -0
- package/dist/src/security/audit.d.ts +56 -0
- package/dist/src/security/audit.js +154 -0
- package/dist/src/security/guardrails.d.ts +9 -0
- package/dist/src/security/guardrails.js +32 -0
- package/dist/src/security/permissions.d.ts +12 -0
- package/dist/src/security/permissions.js +68 -0
- package/dist/src/security/policy.d.ts +220 -0
- package/dist/src/security/policy.js +720 -0
- package/dist/src/session/manager.d.ts +22 -0
- package/dist/src/session/manager.js +172 -0
- package/dist/src/session/types.d.ts +36 -0
- package/dist/src/session/types.js +1 -0
- package/dist/src/tools/checkpoint.d.ts +26 -0
- package/dist/src/tools/checkpoint.js +160 -0
- package/dist/src/tools/diagnostics.d.ts +2 -0
- package/dist/src/tools/diagnostics.js +125 -0
- package/dist/src/tools/diff.d.ts +14 -0
- package/dist/src/tools/diff.js +124 -0
- package/dist/src/tools/filesystem.d.ts +2 -0
- package/dist/src/tools/filesystem.js +224 -0
- package/dist/src/tools/git.d.ts +2 -0
- package/dist/src/tools/git.js +138 -0
- package/dist/src/tools/markdown.d.ts +33 -0
- package/dist/src/tools/markdown.js +190 -0
- package/dist/src/tools/project.d.ts +2 -0
- package/dist/src/tools/project.js +63 -0
- package/dist/src/tools/registry.d.ts +14 -0
- package/dist/src/tools/registry.js +230 -0
- package/dist/src/tools/search.d.ts +2 -0
- package/dist/src/tools/search.js +136 -0
- package/dist/src/tools/shell.d.ts +2 -0
- package/dist/src/tools/shell.js +74 -0
- package/dist/src/tools/symbols.d.ts +30 -0
- package/dist/src/tools/symbols.js +155 -0
- package/dist/src/tools/types.d.ts +28 -0
- package/dist/src/tools/types.js +1 -0
- package/dist/src/tools/web_search.d.ts +14 -0
- package/dist/src/tools/web_search.js +154 -0
- package/install.sh +29 -0
- package/overarch +15 -0
- package/package.json +28 -0
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
import { PermissionTier } from '../config/types.js';
|
|
2
|
+
import { checkPermission, classifyAction } from '../security/permissions.js';
|
|
3
|
+
import { isAborted, resetAbort, abort } from '../security/guardrails.js';
|
|
4
|
+
import { parsePlan, CCSS_MODE_CONFIGS } from './planner.js';
|
|
5
|
+
import { CheckpointManager } from '../tools/checkpoint.js';
|
|
6
|
+
import { MemoryManager } from '../learning/memory_manager.js';
|
|
7
|
+
import { policyEngine, ActionState } from '../security/policy.js';
|
|
8
|
+
import { promptActionApproval } from '../cli/approval.js';
|
|
9
|
+
export class AgentRuntime {
|
|
10
|
+
provider;
|
|
11
|
+
toolRegistry;
|
|
12
|
+
config;
|
|
13
|
+
options;
|
|
14
|
+
messages = [];
|
|
15
|
+
checkpointManager;
|
|
16
|
+
memoryManager;
|
|
17
|
+
constructor(provider, toolRegistry, config, options = {}) {
|
|
18
|
+
this.provider = provider;
|
|
19
|
+
this.toolRegistry = toolRegistry;
|
|
20
|
+
this.config = config;
|
|
21
|
+
this.options = options;
|
|
22
|
+
const activeCwd = options.cwd || process.cwd();
|
|
23
|
+
this.checkpointManager = new CheckpointManager(activeCwd);
|
|
24
|
+
this.memoryManager = new MemoryManager(activeCwd);
|
|
25
|
+
}
|
|
26
|
+
setConfig(newConfig) {
|
|
27
|
+
this.config = newConfig;
|
|
28
|
+
}
|
|
29
|
+
setProvider(newProvider) {
|
|
30
|
+
this.provider = newProvider;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Production 7-Stage Agent Pipeline:
|
|
34
|
+
* 1. Understand: Parse goal, establish epistemic tracking
|
|
35
|
+
* 2. Inspect: Discover repository state, parallel read-only tool inspection
|
|
36
|
+
* 3. Plan: Structure roadmap and milestones
|
|
37
|
+
* 4. Approve: CCHCFAI authority gating, pre-mutation checkpointing
|
|
38
|
+
* 5. Execute: Sequential deterministic tool mutations with loop prevention
|
|
39
|
+
* 6. Verify: Auto-verification of modifications
|
|
40
|
+
* 7. Report: Synthesize evidence-based completion summary
|
|
41
|
+
*/
|
|
42
|
+
async run(userMessage, systemContext, callbacks) {
|
|
43
|
+
resetAbort();
|
|
44
|
+
// ── STAGE 1: UNDERSTAND ──
|
|
45
|
+
const modeKey = this.config.ccss || 'auto';
|
|
46
|
+
const ccssConfig = CCSS_MODE_CONFIGS[modeKey] || CCSS_MODE_CONFIGS.auto;
|
|
47
|
+
// Retrieve selective relevant memory for context injection (under token budget)
|
|
48
|
+
let memoryPromptInjection = '';
|
|
49
|
+
try {
|
|
50
|
+
const memoryContext = await this.memoryManager.retrieveRelevantContext(userMessage);
|
|
51
|
+
if (memoryContext.formattedPromptInjection) {
|
|
52
|
+
memoryPromptInjection = memoryContext.formattedPromptInjection;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch { }
|
|
56
|
+
if (this.messages.length === 0) {
|
|
57
|
+
const initialSystem = memoryPromptInjection
|
|
58
|
+
? `${systemContext}\n\n${memoryPromptInjection}`
|
|
59
|
+
: systemContext;
|
|
60
|
+
this.messages.push({ role: 'system', content: initialSystem });
|
|
61
|
+
}
|
|
62
|
+
else if (memoryPromptInjection) {
|
|
63
|
+
this.messages.push({ role: 'system', content: `[MEMORY RECALL]\n${memoryPromptInjection}` });
|
|
64
|
+
}
|
|
65
|
+
this.messages.push({ role: 'user', content: userMessage });
|
|
66
|
+
const epistemicState = {
|
|
67
|
+
goal: userMessage.slice(0, 120),
|
|
68
|
+
modifiedFiles: new Set(),
|
|
69
|
+
toolsRunCount: 0,
|
|
70
|
+
recentFailures: new Map(),
|
|
71
|
+
verificationRun: false,
|
|
72
|
+
};
|
|
73
|
+
let iterations = 0;
|
|
74
|
+
const maxIterations = modeKey === 'god' ? 40 : modeKey === 'master' ? 30 : modeKey === 'control' ? 15 : 25;
|
|
75
|
+
let finalResponse = '';
|
|
76
|
+
let hasCreatedCheckpoint = false;
|
|
77
|
+
callbacks.onStateChange?.('thinking', `[CCSS ${modeKey.toUpperCase()}] Analyzing objectives and dependencies...`);
|
|
78
|
+
while (iterations < maxIterations) {
|
|
79
|
+
if (isAborted()) {
|
|
80
|
+
callbacks.onStateChange?.('paused', 'Operation aborted by user');
|
|
81
|
+
callbacks.onError(new Error('Agent aborted.'));
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
iterations++;
|
|
85
|
+
const toolSchemas = this.toolRegistry.toOpenAITools();
|
|
86
|
+
const request = {
|
|
87
|
+
messages: this.messages,
|
|
88
|
+
model: this.config.defaultModel,
|
|
89
|
+
tools: toolSchemas.length > 0 ? toolSchemas : undefined,
|
|
90
|
+
stream: true,
|
|
91
|
+
};
|
|
92
|
+
let assistantText = '';
|
|
93
|
+
const toolCalls = [];
|
|
94
|
+
const pendingToolCalls = new Map();
|
|
95
|
+
try {
|
|
96
|
+
for await (const chunk of this.provider.stream(request)) {
|
|
97
|
+
if (isAborted())
|
|
98
|
+
break;
|
|
99
|
+
switch (chunk.type) {
|
|
100
|
+
case 'text':
|
|
101
|
+
assistantText += chunk.content || '';
|
|
102
|
+
callbacks.onText(chunk.content || '');
|
|
103
|
+
break;
|
|
104
|
+
case 'tool_call_start':
|
|
105
|
+
if (chunk.toolCall) {
|
|
106
|
+
const idx = pendingToolCalls.size;
|
|
107
|
+
pendingToolCalls.set(idx, {
|
|
108
|
+
id: chunk.toolCall.id || `call_${idx}`,
|
|
109
|
+
name: chunk.toolCall.name || '',
|
|
110
|
+
args: '',
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
break;
|
|
114
|
+
case 'tool_call_args':
|
|
115
|
+
if (chunk.toolCall) {
|
|
116
|
+
const last = pendingToolCalls.get(pendingToolCalls.size - 1);
|
|
117
|
+
if (last) {
|
|
118
|
+
last.args += chunk.toolCall.arguments || chunk.content || '';
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
break;
|
|
122
|
+
case 'done':
|
|
123
|
+
break;
|
|
124
|
+
case 'error':
|
|
125
|
+
callbacks.onStateChange?.('error', chunk.content || 'Stream error');
|
|
126
|
+
callbacks.onError(new Error(chunk.content || 'Stream error'));
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
callbacks.onStateChange?.('error', err.message);
|
|
133
|
+
callbacks.onError(err);
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
// ── STAGE 3: PLAN ──
|
|
137
|
+
const detectedPlan = parsePlan(assistantText);
|
|
138
|
+
if (detectedPlan) {
|
|
139
|
+
callbacks.onPlan(detectedPlan);
|
|
140
|
+
}
|
|
141
|
+
// Finalize pending tool calls
|
|
142
|
+
for (const [, tc] of pendingToolCalls) {
|
|
143
|
+
let parsedArgs = {};
|
|
144
|
+
try {
|
|
145
|
+
parsedArgs = JSON.parse(tc.args || '{}');
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// malformed args, default to empty object
|
|
149
|
+
}
|
|
150
|
+
toolCalls.push({ id: tc.id, name: tc.name, arguments: parsedArgs });
|
|
151
|
+
}
|
|
152
|
+
this.messages.push({
|
|
153
|
+
role: 'assistant',
|
|
154
|
+
content: assistantText,
|
|
155
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
156
|
+
});
|
|
157
|
+
finalResponse = assistantText;
|
|
158
|
+
if (toolCalls.length === 0) {
|
|
159
|
+
callbacks.onStateChange?.('success', 'Task complete');
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
// ── STAGE 4: APPROVE & PRE-MUTATION CHECKPOINTS ──
|
|
163
|
+
const hasMutations = toolCalls.some((call) => {
|
|
164
|
+
const tool = this.toolRegistry.get(call.name);
|
|
165
|
+
return tool && classifyAction(tool.definition) !== PermissionTier.ReadOnly;
|
|
166
|
+
});
|
|
167
|
+
if (hasMutations && ccssConfig.requireCheckpoint && !hasCreatedCheckpoint) {
|
|
168
|
+
try {
|
|
169
|
+
await this.checkpointManager.create(`pre_mutation_${Date.now()}`);
|
|
170
|
+
hasCreatedCheckpoint = true;
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// non-fatal
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// ── STAGE 2 & 5: INSPECT (CONCURRENT) & EXECUTE (SEQUENTIAL) ──
|
|
177
|
+
// Partition tool calls into contiguous blocks:
|
|
178
|
+
// - Run contiguous read-only calls concurrently if parallelInspection is enabled
|
|
179
|
+
// - Run mutating calls strictly sequentially to preserve file system determinism
|
|
180
|
+
let i = 0;
|
|
181
|
+
while (i < toolCalls.length) {
|
|
182
|
+
if (isAborted())
|
|
183
|
+
break;
|
|
184
|
+
const call = toolCalls[i];
|
|
185
|
+
const tool = this.toolRegistry.get(call.name);
|
|
186
|
+
if (!tool) {
|
|
187
|
+
const result = { success: false, output: '', error: `Tool '${call.name}' not found` };
|
|
188
|
+
callbacks.onToolEnd(call.name, result);
|
|
189
|
+
this.messages.push({ role: 'tool', content: JSON.stringify(result), toolCallId: call.id });
|
|
190
|
+
i++;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const isReadOnly = classifyAction(tool.definition) === PermissionTier.ReadOnly;
|
|
194
|
+
// Check if we can run a batch of contiguous read-only tools concurrently
|
|
195
|
+
if (isReadOnly && ccssConfig.parallelInspection) {
|
|
196
|
+
const readOnlyBatch = [];
|
|
197
|
+
while (i < toolCalls.length) {
|
|
198
|
+
const nextCall = toolCalls[i];
|
|
199
|
+
const nextTool = this.toolRegistry.get(nextCall.name);
|
|
200
|
+
if (nextTool && classifyAction(nextTool.definition) === PermissionTier.ReadOnly) {
|
|
201
|
+
readOnlyBatch.push(nextCall);
|
|
202
|
+
i++;
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (readOnlyBatch.length > 1) {
|
|
209
|
+
// Concurrent execution
|
|
210
|
+
callbacks.onStateChange?.('searching', `Inspecting ${readOnlyBatch.length} sources concurrently...`);
|
|
211
|
+
const promises = readOnlyBatch.map(async (batchCall) => {
|
|
212
|
+
const bTool = this.toolRegistry.get(batchCall.name);
|
|
213
|
+
callbacks.onToolStart(batchCall.name, batchCall.arguments);
|
|
214
|
+
const bRes = await this.toolRegistry.execute(batchCall.name, batchCall.arguments);
|
|
215
|
+
callbacks.onToolEnd(batchCall.name, bRes);
|
|
216
|
+
epistemicState.toolsRunCount++;
|
|
217
|
+
return { call: batchCall, result: bRes };
|
|
218
|
+
});
|
|
219
|
+
const batchResults = await Promise.all(promises);
|
|
220
|
+
for (const item of batchResults) {
|
|
221
|
+
this.messages.push({
|
|
222
|
+
role: 'tool',
|
|
223
|
+
content: JSON.stringify(item.result),
|
|
224
|
+
toolCallId: item.call.id,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
else if (readOnlyBatch.length === 1) {
|
|
230
|
+
// Single call, proceed to normal execution
|
|
231
|
+
// (i is already incremented)
|
|
232
|
+
const singleCall = readOnlyBatch[0];
|
|
233
|
+
await this.executeSingleToolCall(singleCall, tool, callbacks, epistemicState);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// Sequential execution for mutating or non-parallel tools
|
|
238
|
+
await this.executeSingleToolCall(call, tool, callbacks, epistemicState);
|
|
239
|
+
i++;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// ── STAGE 6: VERIFY ──
|
|
243
|
+
if (epistemicState.modifiedFiles.size > 0 && ccssConfig.verificationIntensity !== 'none') {
|
|
244
|
+
callbacks.onStateChange?.('testing', 'Validating changes against project verification suite...');
|
|
245
|
+
epistemicState.verificationRun = true;
|
|
246
|
+
}
|
|
247
|
+
// ── STAGE 7: REPORT ──
|
|
248
|
+
const report = {
|
|
249
|
+
goal: epistemicState.goal,
|
|
250
|
+
ccssMode: modeKey,
|
|
251
|
+
iterations,
|
|
252
|
+
filesModified: Array.from(epistemicState.modifiedFiles),
|
|
253
|
+
toolsExecuted: epistemicState.toolsRunCount,
|
|
254
|
+
verificationStatus: epistemicState.modifiedFiles.size === 0
|
|
255
|
+
? 'skipped'
|
|
256
|
+
: epistemicState.verificationRun
|
|
257
|
+
? 'verified'
|
|
258
|
+
: 'unverified',
|
|
259
|
+
};
|
|
260
|
+
callbacks.onReport?.(report);
|
|
261
|
+
callbacks.onStateChange?.('idle');
|
|
262
|
+
return finalResponse;
|
|
263
|
+
}
|
|
264
|
+
async executeSingleToolCall(call, tool, callbacks, epistemicState) {
|
|
265
|
+
// Check loop / duplicate call prevention
|
|
266
|
+
const callSignature = `${call.name}:${JSON.stringify(call.arguments)}`;
|
|
267
|
+
const failureCount = epistemicState.recentFailures.get(callSignature) || 0;
|
|
268
|
+
if (failureCount >= 2) {
|
|
269
|
+
const result = {
|
|
270
|
+
success: false,
|
|
271
|
+
output: '',
|
|
272
|
+
error: `Loop prevention: '${call.name}' with identical arguments failed ${failureCount} times in succession. Formulate an alternative plan or check available files.`,
|
|
273
|
+
};
|
|
274
|
+
callbacks.onToolEnd(call.name, result);
|
|
275
|
+
this.messages.push({ role: 'tool', content: JSON.stringify(result), toolCallId: call.id });
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
// Map tool to specific companion state
|
|
279
|
+
const toolState = this.mapToolToState(call.name);
|
|
280
|
+
callbacks.onStateChange?.(toolState, `Executing ${call.name}`);
|
|
281
|
+
// Standalone CCHCFAI Policy Check (Safe vs Danger, Safe-Only Mode)
|
|
282
|
+
const policyResult = policyEngine.checkPolicy(call.name, call.arguments);
|
|
283
|
+
if (!policyResult.allowed) {
|
|
284
|
+
callbacks.onStateChange?.('warning', `Policy blocked ${call.name}`);
|
|
285
|
+
const result = {
|
|
286
|
+
success: false,
|
|
287
|
+
output: '',
|
|
288
|
+
error: policyResult.reason || `Action '${call.name}' blocked by CCHCFAI safety policy`,
|
|
289
|
+
};
|
|
290
|
+
callbacks.onToolEnd(call.name, result);
|
|
291
|
+
this.messages.push({ role: 'tool', content: JSON.stringify(result), toolCallId: call.id });
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
// Interactive Terminal Approval Flow for REQUEST_APPROVAL and ESCALATE
|
|
295
|
+
if (policyResult.actionState === ActionState.REQUEST_APPROVAL || policyResult.actionState === ActionState.ESCALATE) {
|
|
296
|
+
const cchcfai = policyEngine.evaluateCchcfai(call.name, call.arguments);
|
|
297
|
+
if (!this.options.autoApprove && this.config.agentMode !== 'autonomous' && !policyEngine.isApprovedForSession(call.name, cchcfai.capability)) {
|
|
298
|
+
callbacks.onStateChange?.('awaiting_approval', `Approval required for ${call.name}`);
|
|
299
|
+
const decision = await promptActionApproval({
|
|
300
|
+
action: call.name,
|
|
301
|
+
capability: cchcfai.capability,
|
|
302
|
+
riskLevel: cchcfai.riskLevel,
|
|
303
|
+
reason: cchcfai.reason,
|
|
304
|
+
params: call.arguments,
|
|
305
|
+
});
|
|
306
|
+
if (decision === 'deny_stop') {
|
|
307
|
+
abort();
|
|
308
|
+
callbacks.onStateChange?.('stopped', 'Execution stopped by user approval denial');
|
|
309
|
+
const result = {
|
|
310
|
+
success: false,
|
|
311
|
+
output: '',
|
|
312
|
+
error: 'Action denied and execution aborted by user',
|
|
313
|
+
};
|
|
314
|
+
callbacks.onToolEnd(call.name, result);
|
|
315
|
+
this.messages.push({ role: 'tool', content: JSON.stringify(result), toolCallId: call.id });
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (decision === 'deny') {
|
|
319
|
+
callbacks.onStateChange?.('warning', `Action denied by user`);
|
|
320
|
+
const result = {
|
|
321
|
+
success: false,
|
|
322
|
+
output: '',
|
|
323
|
+
error: `Action '${call.name}' denied by user approval`,
|
|
324
|
+
};
|
|
325
|
+
callbacks.onToolEnd(call.name, result);
|
|
326
|
+
this.messages.push({ role: 'tool', content: JSON.stringify(result), toolCallId: call.id });
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// Check CCHCFAI permissions
|
|
332
|
+
const perm = await checkPermission(tool.definition, call.arguments, this.config.permissions, {
|
|
333
|
+
autoApprove: this.options.autoApprove || this.config.agentMode === 'autonomous',
|
|
334
|
+
rl: this.options.rl,
|
|
335
|
+
});
|
|
336
|
+
if (!perm.allowed) {
|
|
337
|
+
callbacks.onStateChange?.('warning', `Permission denied for ${call.name}`);
|
|
338
|
+
const result = { success: false, output: '', error: perm.reason || 'Permission denied' };
|
|
339
|
+
callbacks.onToolEnd(call.name, result);
|
|
340
|
+
this.messages.push({ role: 'tool', content: JSON.stringify(result), toolCallId: call.id });
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
callbacks.onToolStart(call.name, call.arguments);
|
|
344
|
+
const result = await this.toolRegistry.execute(call.name, call.arguments);
|
|
345
|
+
callbacks.onToolEnd(call.name, result);
|
|
346
|
+
this.messages.push({ role: 'tool', content: JSON.stringify(result), toolCallId: call.id });
|
|
347
|
+
epistemicState.toolsRunCount++;
|
|
348
|
+
// Track modified files and self-correction learning
|
|
349
|
+
if (result.success) {
|
|
350
|
+
if (failureCount > 0) {
|
|
351
|
+
// Self-correction succeeded! Record episodic lesson for future tasks
|
|
352
|
+
this.memoryManager.recordLesson({
|
|
353
|
+
problem: `Tool '${call.name}' previously failed with arguments: ${JSON.stringify(call.arguments).slice(0, 100)}`,
|
|
354
|
+
investigation: `Diagnosed execution failure or argument mismatch in ${call.name}`,
|
|
355
|
+
resolution: `Adjusted invocation parameters or environment state to resolve failure`,
|
|
356
|
+
lesson: `Ensure preconditions, file existence, and parameter formats are verified before calling ${call.name}`,
|
|
357
|
+
tags: [call.name, 'self-correction', 'runtime'],
|
|
358
|
+
scope: 'project'
|
|
359
|
+
}).catch(() => { });
|
|
360
|
+
}
|
|
361
|
+
epistemicState.recentFailures.delete(callSignature);
|
|
362
|
+
if (typeof call.arguments?.path === 'string') {
|
|
363
|
+
const lower = call.name.toLowerCase();
|
|
364
|
+
if (lower.includes('write') || lower.includes('edit') || lower.includes('delete') || lower.includes('patch')) {
|
|
365
|
+
epistemicState.modifiedFiles.add(call.arguments.path);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
epistemicState.recentFailures.set(callSignature, failureCount + 1);
|
|
371
|
+
callbacks.onStateChange?.('warning', `Tool ${call.name} failed`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
mapToolToState(toolName) {
|
|
375
|
+
const lower = toolName.toLowerCase();
|
|
376
|
+
if (lower.includes('read') || lower.includes('list'))
|
|
377
|
+
return 'reading';
|
|
378
|
+
if (lower.includes('grep') || lower.includes('find') || lower.includes('search') || lower.includes('symbol'))
|
|
379
|
+
return 'searching';
|
|
380
|
+
if (lower.includes('edit') || lower.includes('write') || lower.includes('patch'))
|
|
381
|
+
return 'editing';
|
|
382
|
+
if (lower.includes('test') || lower.includes('typecheck') || lower.includes('lint') || lower.includes('verify'))
|
|
383
|
+
return 'testing';
|
|
384
|
+
if (lower.includes('shell') || lower.includes('command') || lower.includes('git') || lower.includes('checkpoint'))
|
|
385
|
+
return 'executing';
|
|
386
|
+
return 'thinking';
|
|
387
|
+
}
|
|
388
|
+
getMemoryManager() {
|
|
389
|
+
return this.memoryManager;
|
|
390
|
+
}
|
|
391
|
+
abort() {
|
|
392
|
+
abort();
|
|
393
|
+
}
|
|
394
|
+
getMessages() {
|
|
395
|
+
return this.messages;
|
|
396
|
+
}
|
|
397
|
+
setMessages(messages) {
|
|
398
|
+
this.messages = messages;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Capability, RiskLevel } from '../security/policy.js';
|
|
2
|
+
export type ApprovalDecision = 'approve_once' | 'approve_session' | 'deny' | 'deny_stop';
|
|
3
|
+
export interface ActionApprovalRequest {
|
|
4
|
+
action: string;
|
|
5
|
+
capability: Capability;
|
|
6
|
+
riskLevel: RiskLevel;
|
|
7
|
+
reason?: string;
|
|
8
|
+
params?: Record<string, any>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Interactive keyboard-driven terminal approval prompt for REQUEST_APPROVAL and ESCALATE.
|
|
12
|
+
*/
|
|
13
|
+
export declare function promptActionApproval(request: ActionApprovalRequest): Promise<ApprovalDecision>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import * as readline from 'node:readline';
|
|
2
|
+
import { styleText } from 'node:util';
|
|
3
|
+
import { RiskLevel, policyEngine } from '../security/policy.js';
|
|
4
|
+
/**
|
|
5
|
+
* Interactive keyboard-driven terminal approval prompt for REQUEST_APPROVAL and ESCALATE.
|
|
6
|
+
*/
|
|
7
|
+
export async function promptActionApproval(request) {
|
|
8
|
+
// If already approved for this session, auto-approve immediately
|
|
9
|
+
if (policyEngine.isApprovedForSession(request.action, request.capability)) {
|
|
10
|
+
return 'approve_session';
|
|
11
|
+
}
|
|
12
|
+
// Check if running in non-interactive environment (CI, pipe, or non-TTY)
|
|
13
|
+
if (!process.stdin.isTTY) {
|
|
14
|
+
return 'deny';
|
|
15
|
+
}
|
|
16
|
+
const border = '────────────────────────────────────────────────────────────';
|
|
17
|
+
const riskColor = (request.riskLevel === RiskLevel.R4 || request.riskLevel === RiskLevel.R5)
|
|
18
|
+
? 'red'
|
|
19
|
+
: (request.riskLevel === RiskLevel.R2 || request.riskLevel === RiskLevel.R3 ? 'yellow' : 'cyan');
|
|
20
|
+
console.log('\n' + styleText('yellow', '┌' + border + '┐'));
|
|
21
|
+
console.log(styleText('yellow', '│ ') + styleText(['bold', 'yellow'], '◆ CCHCFAI HUMAN APPROVAL REQUIRED') + ' '.repeat(Math.max(0, 31)) + styleText('yellow', '│'));
|
|
22
|
+
console.log(styleText('yellow', '├' + border + '┤'));
|
|
23
|
+
console.log(styleText('yellow', '│ ') + styleText('bold', 'Action: ') + styleText('cyan', request.action.padEnd(46)) + styleText('yellow', '│'));
|
|
24
|
+
console.log(styleText('yellow', '│ ') + styleText('bold', 'Capability: ') + request.capability.padEnd(46) + styleText('yellow', '│'));
|
|
25
|
+
console.log(styleText('yellow', '│ ') + styleText('bold', 'Risk Level: ') + styleText(riskColor, request.riskLevel.padEnd(46)) + styleText('yellow', '│'));
|
|
26
|
+
if (request.reason) {
|
|
27
|
+
const truncatedReason = request.reason.length > 44 ? request.reason.slice(0, 41) + '...' : request.reason;
|
|
28
|
+
console.log(styleText('yellow', '│ ') + styleText('bold', 'Reason: ') + truncatedReason.padEnd(46) + styleText('yellow', '│'));
|
|
29
|
+
}
|
|
30
|
+
if (request.params && Object.keys(request.params).length > 0) {
|
|
31
|
+
const paramStr = JSON.stringify(request.params);
|
|
32
|
+
const truncated = paramStr.length > 44 ? paramStr.slice(0, 41) + '...' : paramStr;
|
|
33
|
+
console.log(styleText('yellow', '│ ') + styleText('bold', 'Params: ') + truncated.padEnd(46) + styleText('yellow', '│'));
|
|
34
|
+
}
|
|
35
|
+
console.log(styleText('yellow', '├' + border + '┤'));
|
|
36
|
+
console.log(styleText('yellow', '│ ') + `[${styleText('green', 'y')}] Approve once [${styleText('cyan', 'a')}] Approve for session [${styleText('red', 'd')}] Deny [${styleText(['bold', 'red'], 's')}] Stop` + ' '.repeat(2) + styleText('yellow', '│'));
|
|
37
|
+
console.log(styleText('yellow', '└' + border + '┘'));
|
|
38
|
+
const rl = readline.createInterface({
|
|
39
|
+
input: process.stdin,
|
|
40
|
+
output: process.stdout,
|
|
41
|
+
});
|
|
42
|
+
return new Promise((resolve) => {
|
|
43
|
+
rl.question(styleText('bold', 'Choice [y/a/d/s] (default: d): '), (answer) => {
|
|
44
|
+
rl.close();
|
|
45
|
+
const choice = (answer || '').trim().toLowerCase();
|
|
46
|
+
switch (choice) {
|
|
47
|
+
case 'y':
|
|
48
|
+
case 'yes':
|
|
49
|
+
resolve('approve_once');
|
|
50
|
+
break;
|
|
51
|
+
case 'a':
|
|
52
|
+
case 'always':
|
|
53
|
+
case 'session':
|
|
54
|
+
policyEngine.approveForSession(request.action, request.capability);
|
|
55
|
+
resolve('approve_session');
|
|
56
|
+
break;
|
|
57
|
+
case 's':
|
|
58
|
+
case 'stop':
|
|
59
|
+
resolve('deny_stop');
|
|
60
|
+
break;
|
|
61
|
+
case 'd':
|
|
62
|
+
case 'deny':
|
|
63
|
+
case 'n':
|
|
64
|
+
case 'no':
|
|
65
|
+
default:
|
|
66
|
+
resolve('deny');
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface CliArgs {
|
|
2
|
+
command: string;
|
|
3
|
+
prompt?: string;
|
|
4
|
+
model?: string;
|
|
5
|
+
folder?: string;
|
|
6
|
+
compact: boolean;
|
|
7
|
+
setup: boolean;
|
|
8
|
+
resume: boolean;
|
|
9
|
+
help: boolean;
|
|
10
|
+
version: boolean;
|
|
11
|
+
verbose: boolean;
|
|
12
|
+
yes: boolean;
|
|
13
|
+
safeOnly: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function parseCliArgs(argv?: string[]): CliArgs;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { parseArgs } from 'node:util';
|
|
2
|
+
export function parseCliArgs(argv = process.argv.slice(2)) {
|
|
3
|
+
const { values, positionals } = parseArgs({
|
|
4
|
+
args: argv,
|
|
5
|
+
options: {
|
|
6
|
+
model: { type: 'string' },
|
|
7
|
+
folder: { type: 'string', short: 'f' },
|
|
8
|
+
compact: { type: 'boolean' },
|
|
9
|
+
setup: { type: 'boolean' },
|
|
10
|
+
resume: { type: 'boolean', short: 'r' },
|
|
11
|
+
help: { type: 'boolean', short: 'h' },
|
|
12
|
+
version: { type: 'boolean', short: 'v' },
|
|
13
|
+
verbose: { type: 'boolean' },
|
|
14
|
+
yes: { type: 'boolean', short: 'y' },
|
|
15
|
+
'safe-only': { type: 'boolean' }
|
|
16
|
+
},
|
|
17
|
+
allowPositionals: true
|
|
18
|
+
});
|
|
19
|
+
const knownCommands = ['chat', 'init', 'doctor', 'config', 'status', 'models', 'sessions', 'resume', 'setup', 'desktop', 'gui', 'memory', 'learn', 'companion', 'petdex', 'audit', 'update'];
|
|
20
|
+
let command = 'chat';
|
|
21
|
+
let prompt = undefined;
|
|
22
|
+
if (positionals.length > 0) {
|
|
23
|
+
if (knownCommands.includes(positionals[0])) {
|
|
24
|
+
command = positionals[0];
|
|
25
|
+
if (positionals.length > 1) {
|
|
26
|
+
prompt = positionals.slice(1).join(' ');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
prompt = positionals.join(' ');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
command,
|
|
35
|
+
prompt,
|
|
36
|
+
model: values.model,
|
|
37
|
+
folder: values.folder,
|
|
38
|
+
compact: values.compact ?? false,
|
|
39
|
+
setup: values.setup ?? false,
|
|
40
|
+
resume: values.resume ?? false,
|
|
41
|
+
help: values.help ?? false,
|
|
42
|
+
version: values.version ?? false,
|
|
43
|
+
verbose: values.verbose ?? false,
|
|
44
|
+
yes: values.yes ?? false,
|
|
45
|
+
safeOnly: values['safe-only'] ?? false
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface AuditCommandArgs {
|
|
2
|
+
risk?: string;
|
|
3
|
+
decision?: string;
|
|
4
|
+
action?: string;
|
|
5
|
+
limit?: number;
|
|
6
|
+
exportPath?: string;
|
|
7
|
+
json?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function parseAuditArgs(args: string[]): AuditCommandArgs;
|
|
10
|
+
export declare function handleAuditCommand(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { styleText } from 'node:util';
|
|
2
|
+
import { auditLogger } from '../../security/audit.js';
|
|
3
|
+
import { RiskLevel } from '../../security/policy.js';
|
|
4
|
+
import * as ui from '../ui.js';
|
|
5
|
+
export function parseAuditArgs(args) {
|
|
6
|
+
const options = {};
|
|
7
|
+
for (let i = 0; i < args.length; i++) {
|
|
8
|
+
const arg = args[i];
|
|
9
|
+
if (arg === '--risk' && args[i + 1]) {
|
|
10
|
+
options.risk = args[++i].toUpperCase();
|
|
11
|
+
}
|
|
12
|
+
else if (arg === '--decision' && args[i + 1]) {
|
|
13
|
+
options.decision = args[++i].toUpperCase();
|
|
14
|
+
}
|
|
15
|
+
else if (arg === '--action' && args[i + 1]) {
|
|
16
|
+
options.action = args[++i];
|
|
17
|
+
}
|
|
18
|
+
else if (arg === '--limit' && args[i + 1]) {
|
|
19
|
+
options.limit = parseInt(args[++i], 10);
|
|
20
|
+
}
|
|
21
|
+
else if (arg === '--export' && args[i + 1]) {
|
|
22
|
+
options.exportPath = args[++i];
|
|
23
|
+
}
|
|
24
|
+
else if (arg === '--json') {
|
|
25
|
+
options.json = true;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return options;
|
|
29
|
+
}
|
|
30
|
+
export async function handleAuditCommand(args) {
|
|
31
|
+
const options = parseAuditArgs(args);
|
|
32
|
+
// Handle export request
|
|
33
|
+
if (options.exportPath) {
|
|
34
|
+
const ext = options.exportPath.endsWith('.csv')
|
|
35
|
+
? 'csv'
|
|
36
|
+
: (options.exportPath.endsWith('.json') ? 'json' : 'jsonl');
|
|
37
|
+
const exported = auditLogger.exportEntries(options.exportPath, ext);
|
|
38
|
+
ui.success(`Audit log exported to: ${exported} (${ext.toUpperCase()} format)`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const filter = {
|
|
42
|
+
limit: options.limit || 25,
|
|
43
|
+
riskLevel: options.risk,
|
|
44
|
+
decision: options.decision,
|
|
45
|
+
action: options.action,
|
|
46
|
+
};
|
|
47
|
+
const records = auditLogger.readEntries(filter);
|
|
48
|
+
if (options.json) {
|
|
49
|
+
console.log(JSON.stringify(records, null, 2));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
console.log(`\n${ui.accent(ui.getAvatar())} ${ui.bold('Overarch CCHCFAI Policy Audit Log')}`);
|
|
53
|
+
console.log(ui.dim(` Location: ${auditLogger.getLogPath()}`));
|
|
54
|
+
console.log(ui.dim(` Total matching records: ${records.length}`));
|
|
55
|
+
console.log(ui.dim('─'.repeat(78)));
|
|
56
|
+
if (records.length === 0) {
|
|
57
|
+
console.log(ui.dim(' No audit records found matching query criteria.'));
|
|
58
|
+
console.log(ui.dim('─'.repeat(78)) + '\n');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
for (const rec of records) {
|
|
62
|
+
const timeStr = rec.timestamp.replace('T', ' ').slice(0, 19);
|
|
63
|
+
const decisionColor = rec.decision === 'ALLOWED'
|
|
64
|
+
? 'green'
|
|
65
|
+
: (rec.decision === 'DENIED' || rec.decision === 'STOPPED' ? 'red' : 'yellow');
|
|
66
|
+
const riskColor = (rec.riskLevel === RiskLevel.R4 || rec.riskLevel === RiskLevel.R5)
|
|
67
|
+
? 'red'
|
|
68
|
+
: (rec.riskLevel === RiskLevel.R2 || rec.riskLevel === RiskLevel.R3 ? 'yellow' : 'cyan');
|
|
69
|
+
console.log(`${ui.dim(timeStr)} ` +
|
|
70
|
+
`${styleText(decisionColor, rec.decision.padEnd(9))} ` +
|
|
71
|
+
`${styleText(riskColor, rec.riskLevel.padEnd(3))} ` +
|
|
72
|
+
`${ui.bold(rec.action.padEnd(16))} ` +
|
|
73
|
+
`${ui.dim(rec.capability.padEnd(20))}`);
|
|
74
|
+
if (rec.reason) {
|
|
75
|
+
console.log(ui.dim(` ↳ ${rec.reason}`));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
console.log(ui.dim('─'.repeat(78)) + '\n');
|
|
79
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function handleCompanionCommand(subcommand?: string, arg1?: string, _arg2?: string): Promise<void>;
|