@causyne/agent 1.10.17 → 1.10.19
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/docs/ARCHITECTURE.md +25 -8
- package/docs/SECURITY_REVIEW.md +4 -4
- package/docs/THIRD_PARTY_NOTICES.md +3 -0
- package/package.json +1 -1
- package/src/agentLoop.js +99 -19
- package/src/browser/{camofoxRuntime.js → browserRuntime.js} +10 -15
- package/src/browser/{camofoxRuntime.ts → browserRuntime.ts} +11 -16
- package/src/browser/causyneBrowser.js +6 -6
- package/src/browser/causyneBrowser.ts +9 -9
- package/src/cliMode.js +13 -1
- package/src/decisionPolicy.js +68 -0
- package/src/decisionPolicy.ts +106 -0
- package/src/implementationPlan.js +50 -0
- package/src/implementationPlan.ts +59 -0
- package/src/provisioning/capabilityProvisioner.js +1 -1
- package/src/taskShape.js +1 -1
- package/src/taskShape.ts +1 -1
- package/src/toolPacks.js +7 -5
- package/src/tools/browserAutomator.js +1 -1
- package/src/tools/browserAutomator.ts +1 -1
- package/src/tools/manageLocalAiDependencies.js +1 -1
- package/src/tools/registry.js +4 -3
- package/src/tools/requestDecision.js +87 -0
- package/src/tools/requestDecision.ts +107 -0
- package/src/tools/updatePlan.js +37 -84
- package/src/tools/updatePlan.ts +48 -0
- package/src/tools/webFetch.js +1 -1
- package/src/tools/webFetch.ts +1 -1
- package/src/tools/webSearch.js +1 -1
- package/src/tools/webSearch.ts +1 -1
- package/src/tui/ask.js +41 -1
- package/src/tui/banner.js +389 -190
- package/src/tui/banner.ts +407 -0
- package/src/tui/chatRenderer.js +29 -1
- package/src/tui/dialogs.js +91 -0
- package/src/tui/prompt.js +32 -3
- package/src/tui/toolDisplay.js +0 -1
- package/src/tools/todoWrite.js +0 -75
package/src/tools/updatePlan.js
CHANGED
|
@@ -1,88 +1,41 @@
|
|
|
1
|
-
import
|
|
2
|
-
import path from 'path';
|
|
3
|
-
|
|
1
|
+
import { normalizePlanUpdate, summarizePlan } from '../implementationPlan.js';
|
|
4
2
|
export const updatePlan = {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (args.action === 'ADD_TASK') {
|
|
43
|
-
if (!args.taskId || !args.taskContent) return "Missing taskId or taskContent";
|
|
44
|
-
todos.push({ id: args.taskId, content: args.taskContent, status: 'pending' });
|
|
45
|
-
}
|
|
46
|
-
else if (args.action === 'REMOVE_TASK') {
|
|
47
|
-
todos = todos.filter(t => t.id !== args.taskId);
|
|
48
|
-
}
|
|
49
|
-
else if (args.action === 'MARK_COMPLETED') {
|
|
50
|
-
const t = todos.find(t => t.id === args.taskId);
|
|
51
|
-
if (t) t.status = 'completed';
|
|
52
|
-
}
|
|
53
|
-
else if (args.action === 'MARK_FAILED') {
|
|
54
|
-
const t = todos.find(t => t.id === args.taskId);
|
|
55
|
-
if (t) {
|
|
56
|
-
t.status = 'pending'; // Reset it
|
|
57
|
-
// Phase 2 AGI Upgrade: Auto-Pivot
|
|
58
|
-
todos.push({
|
|
59
|
-
id: `investigate_${args.taskId}`,
|
|
60
|
-
content: `Investigate failure of ${args.taskId}: ${args.failureReason}`,
|
|
61
|
-
status: 'in_progress'
|
|
62
|
-
});
|
|
3
|
+
schema: {
|
|
4
|
+
name: 'update_plan',
|
|
5
|
+
description: 'Create or replace the visible implementation checklist. Use it before complex implementation and update statuses as work advances. Keep exactly one step in_progress.',
|
|
6
|
+
parameters: {
|
|
7
|
+
type: 'object',
|
|
8
|
+
properties: {
|
|
9
|
+
explanation: { type: 'string', description: 'Short reason for this plan update.' },
|
|
10
|
+
plan: {
|
|
11
|
+
type: 'array', minItems: 1, maxItems: 24,
|
|
12
|
+
items: {
|
|
13
|
+
type: 'object',
|
|
14
|
+
properties: {
|
|
15
|
+
id: { type: 'string' },
|
|
16
|
+
step: { type: 'string', description: 'Atomic, verifiable implementation step.' },
|
|
17
|
+
status: { type: 'string', enum: ['pending', 'in_progress', 'completed'] }
|
|
18
|
+
},
|
|
19
|
+
required: ['id', 'step', 'status'],
|
|
20
|
+
additionalProperties: false
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
required: ['plan'],
|
|
25
|
+
additionalProperties: false
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
async execute(args, context = {}) {
|
|
29
|
+
try {
|
|
30
|
+
if (!context.parentLoop || typeof context.parentLoop.updateImplementationPlan !== 'function') {
|
|
31
|
+
return { success: false, error: 'Implementation plans require an active Causyne session' };
|
|
32
|
+
}
|
|
33
|
+
const update = normalizePlanUpdate(args);
|
|
34
|
+
context.parentLoop.updateImplementationPlan(update);
|
|
35
|
+
return { success: true, output: summarizePlan(update.plan), plan: update.plan };
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
63
39
|
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Sync JSON
|
|
67
|
-
fs.writeFileSync(todoPath, JSON.stringify(todos, null, 2), 'utf8');
|
|
68
|
-
|
|
69
|
-
// Sync MD
|
|
70
|
-
let mdContent = '# AGI Execution Plan\n\n';
|
|
71
|
-
for (const t of todos) {
|
|
72
|
-
let box = '[ ]';
|
|
73
|
-
if (t.status === 'in_progress') box = '[/]';
|
|
74
|
-
if (t.status === 'completed') box = '[x]';
|
|
75
|
-
mdContent += `- ${box} ${t.id}: ${t.content}\n`;
|
|
76
|
-
}
|
|
77
|
-
fs.writeFileSync(mdPath, mdContent, 'utf8');
|
|
78
|
-
|
|
79
|
-
if (args.action === 'MARK_FAILED') {
|
|
80
|
-
return `Task failed. Auto-pivot engaged. Added investigation task.`;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return "Plan delta updated successfully.";
|
|
84
|
-
} catch (error) {
|
|
85
|
-
return `Error updating plan: ${error.message}`;
|
|
86
40
|
}
|
|
87
|
-
}
|
|
88
41
|
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { normalizePlanUpdate, summarizePlan, type PlanUpdate } from '../implementationPlan.js';
|
|
2
|
+
|
|
3
|
+
interface ToolContext {
|
|
4
|
+
parentLoop?: {
|
|
5
|
+
updateImplementationPlan(update: PlanUpdate): void;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const updatePlan = {
|
|
10
|
+
schema: {
|
|
11
|
+
name: 'update_plan',
|
|
12
|
+
description: 'Create or replace the visible implementation checklist. Use it before complex implementation and update statuses as work advances. Keep exactly one step in_progress.',
|
|
13
|
+
parameters: {
|
|
14
|
+
type: 'object',
|
|
15
|
+
properties: {
|
|
16
|
+
explanation: { type: 'string', description: 'Short reason for this plan update.' },
|
|
17
|
+
plan: {
|
|
18
|
+
type: 'array', minItems: 1, maxItems: 24,
|
|
19
|
+
items: {
|
|
20
|
+
type: 'object',
|
|
21
|
+
properties: {
|
|
22
|
+
id: { type: 'string' },
|
|
23
|
+
step: { type: 'string', description: 'Atomic, verifiable implementation step.' },
|
|
24
|
+
status: { type: 'string', enum: ['pending', 'in_progress', 'completed'] }
|
|
25
|
+
},
|
|
26
|
+
required: ['id', 'step', 'status'],
|
|
27
|
+
additionalProperties: false
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
required: ['plan'],
|
|
32
|
+
additionalProperties: false
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
async execute(args: unknown, context: ToolContext = {}) {
|
|
37
|
+
try {
|
|
38
|
+
if (!context.parentLoop || typeof context.parentLoop.updateImplementationPlan !== 'function') {
|
|
39
|
+
return { success: false, error: 'Implementation plans require an active Causyne session' };
|
|
40
|
+
}
|
|
41
|
+
const update = normalizePlanUpdate(args);
|
|
42
|
+
context.parentLoop.updateImplementationPlan(update);
|
|
43
|
+
return { success: true, output: summarizePlan(update.plan), plan: update.plan };
|
|
44
|
+
} catch (error) {
|
|
45
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
package/src/tools/webFetch.js
CHANGED
|
@@ -144,7 +144,7 @@ export const webFetch = {
|
|
|
144
144
|
if (!response.ok && !args.outputFile && contentType.includes('text/html') && [401, 403, 429, 503].includes(response.status)) {
|
|
145
145
|
const fallback = await browserFallback(finalUrl.href);
|
|
146
146
|
if (fallback.success)
|
|
147
|
-
return { ...fallback, status: response.status, finalUrl: finalUrl.href, via: '
|
|
147
|
+
return { ...fallback, status: response.status, finalUrl: finalUrl.href, via: 'causyne-browser' };
|
|
148
148
|
return { success: false, error: `HTTP ${response.status}; browser fallback failed: ${fallback.error}`, requiresConsent: fallback.requiresConsent };
|
|
149
149
|
}
|
|
150
150
|
if (!response.ok)
|
package/src/tools/webFetch.ts
CHANGED
|
@@ -142,7 +142,7 @@ export const webFetch = {
|
|
|
142
142
|
|
|
143
143
|
if (!response.ok && !args.outputFile && contentType.includes('text/html') && [401, 403, 429, 503].includes(response.status)) {
|
|
144
144
|
const fallback = await browserFallback(finalUrl.href);
|
|
145
|
-
if (fallback.success) return { ...fallback, status: response.status, finalUrl: finalUrl.href, via: '
|
|
145
|
+
if (fallback.success) return { ...fallback, status: response.status, finalUrl: finalUrl.href, via: 'causyne-browser' };
|
|
146
146
|
return { success: false, error: `HTTP ${response.status}; browser fallback failed: ${fallback.error}`, requiresConsent: fallback.requiresConsent };
|
|
147
147
|
}
|
|
148
148
|
if (!response.ok) return { success: false, error: `HTTP ${response.status} ${response.statusText}`.trim(), status: response.status, finalUrl: finalUrl.href };
|
package/src/tools/webSearch.js
CHANGED
|
@@ -14,7 +14,7 @@ function renderResults(query, results) {
|
|
|
14
14
|
export const webSearch = {
|
|
15
15
|
schema: {
|
|
16
16
|
name: 'web_search',
|
|
17
|
-
description: 'Search the public web through lightweight providers, with the optional Causyne
|
|
17
|
+
description: 'Search the public web through lightweight providers, with the optional Causyne Browser as a rendered-page fallback. No browser binary is downloaded automatically.',
|
|
18
18
|
parameters: {
|
|
19
19
|
type: 'object',
|
|
20
20
|
properties: {
|
package/src/tools/webSearch.ts
CHANGED
|
@@ -15,7 +15,7 @@ function renderResults(query: string, results: SearchResult[]): string {
|
|
|
15
15
|
export const webSearch = {
|
|
16
16
|
schema: {
|
|
17
17
|
name: 'web_search',
|
|
18
|
-
description: 'Search the public web through lightweight providers, with the optional Causyne
|
|
18
|
+
description: 'Search the public web through lightweight providers, with the optional Causyne Browser as a rendered-page fallback. No browser binary is downloaded automatically.',
|
|
19
19
|
parameters: {
|
|
20
20
|
type: 'object',
|
|
21
21
|
properties: {
|
package/src/tui/ask.js
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
import readline from 'readline';
|
|
27
27
|
import { ChatPrompt, isInteractive } from './prompt.js';
|
|
28
|
-
import { SelectLayer, InputLayer, ConfirmLayer } from './dialogs.js';
|
|
28
|
+
import { SelectLayer, InputLayer, ConfirmLayer, DecisionLayer } from './dialogs.js';
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Run one layer on a temporary prompt host.
|
|
@@ -162,3 +162,43 @@ export async function askConfirm(message) {
|
|
|
162
162
|
const raw = await fallbackQuestion(`${message} [y/N]`);
|
|
163
163
|
return /^y(es)?$/i.test(String(raw).trim());
|
|
164
164
|
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Ask for a durable product or architecture choice.
|
|
168
|
+
*
|
|
169
|
+
* This remains separate from permissions: choosing an implementation direction
|
|
170
|
+
* never grants a tool the right to mutate files or execute a command.
|
|
171
|
+
*/
|
|
172
|
+
export async function askDecision(request = {}) {
|
|
173
|
+
const options = Array.isArray(request.options) ? request.options : [];
|
|
174
|
+
|
|
175
|
+
if (canAskInPanel()) {
|
|
176
|
+
const answer = await hostOneLayer((resolve) => new DecisionLayer({
|
|
177
|
+
request,
|
|
178
|
+
onAnswer: resolve
|
|
179
|
+
}));
|
|
180
|
+
if (answer !== undefined) return answer;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const custom = { id: '__custom__', label: 'Type your own answer', description: '' };
|
|
184
|
+
const selected = await askSelect({
|
|
185
|
+
title: request.header || 'Decision required',
|
|
186
|
+
items: [...options, custom]
|
|
187
|
+
});
|
|
188
|
+
if (!selected) return null;
|
|
189
|
+
if (selected.id === custom.id) {
|
|
190
|
+
const label = await askText({
|
|
191
|
+
title: request.question || 'Describe the direction you want',
|
|
192
|
+
lines: request.reason ? [request.reason] : [],
|
|
193
|
+
validate: (value) => String(value).trim() ? null : 'An answer is required.'
|
|
194
|
+
});
|
|
195
|
+
if (!label) return null;
|
|
196
|
+
return { requestId: request.id, optionId: null, label: String(label).trim(), custom: true };
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
requestId: request.id,
|
|
200
|
+
optionId: selected.id,
|
|
201
|
+
label: selected.label,
|
|
202
|
+
custom: false
|
|
203
|
+
};
|
|
204
|
+
}
|