@causyne/agent 1.10.13 → 1.10.18
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 +118 -17
- package/docs/SECURITY_REVIEW.md +13 -1
- package/docs/THIRD_PARTY_NOTICES.md +46 -10
- package/package.json +10 -4
- package/src/agentLoop.js +123 -21
- package/src/agentProfiles.js +135 -0
- package/src/agentProfiles.ts +143 -0
- package/src/approvalGrants.js +17 -35
- package/src/approvalGrants.ts +54 -0
- package/src/auditLog.d.ts +3 -0
- package/src/browser/browserRuntime.js +88 -0
- package/src/browser/browserRuntime.ts +107 -0
- package/src/browser/causyneBrowser.js +495 -0
- package/src/browser/causyneBrowser.ts +594 -0
- package/src/browser/searchProviders.js +156 -0
- package/src/browser/searchProviders.ts +156 -0
- package/src/browser/snapshotWindow.js +28 -0
- package/src/browser/snapshotWindow.ts +40 -0
- package/src/browser/urlPolicy.js +89 -0
- package/src/browser/urlPolicy.ts +89 -0
- package/src/checkpoints.js +19 -6
- package/src/cliMode.js +13 -1
- package/src/commands.js +37 -8
- package/src/config.d.ts +2 -0
- package/src/decisionPolicy.js +68 -0
- package/src/decisionPolicy.ts +106 -0
- package/src/hooks.js +121 -107
- package/src/hooks.ts +193 -0
- package/src/implementationPlan.js +50 -0
- package/src/implementationPlan.ts +59 -0
- package/src/mcp/mcpClient.js +520 -132
- package/src/mcp/mcpClient.ts +584 -0
- package/src/mcp/mcpRegistry.js +399 -185
- package/src/mcp/mcpRegistry.ts +451 -0
- package/src/planCache.js +473 -450
- package/src/planCache.ts +840 -0
- package/src/plugins/pluginManager.js +40 -5
- package/src/plugins/pluginSkills.js +84 -0
- package/src/plugins/pluginSkills.ts +106 -0
- package/src/provisioning/capabilityProvisioner.d.ts +10 -0
- package/src/provisioning/capabilityProvisioner.js +5 -0
- package/src/provisioning/capabilityTools.js +2 -1
- package/src/sandbox.js +32 -2
- package/src/sessionJournal.js +471 -0
- package/src/sessionJournal.ts +496 -0
- package/src/sessionStore.js +3 -279
- package/src/skillsManager.js +18 -0
- package/src/subagentJournal.js +215 -0
- package/src/subagentJournal.ts +254 -0
- package/src/taskShape.js +179 -198
- package/src/taskShape.ts +259 -0
- package/src/toolPacks.js +7 -5
- package/src/tools/browserAutomator.js +45 -241
- package/src/tools/browserAutomator.ts +57 -0
- 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/subagentOrchestrator.js +73 -12
- package/src/tools/updatePlan.js +37 -84
- package/src/tools/updatePlan.ts +48 -0
- package/src/tools/webFetch.js +183 -68
- package/src/tools/webFetch.ts +185 -0
- package/src/tools/webSearch.js +50 -75
- package/src/tools/webSearch.ts +56 -0
- package/src/tui/ask.js +41 -1
- package/src/tui/chatRenderer.js +28 -0
- package/src/tui/dialogs.js +91 -0
- package/src/tui/prompt.js +32 -3
- package/src/tui/toolDisplay.js +0 -1
- package/src/types/contracts.js +8 -0
- package/src/types/contracts.ts +263 -0
- package/src/utils/paths.d.ts +3 -0
- package/src/version.d.ts +1 -0
- package/src/mcp/jsonRpc.js +0 -165
- package/src/tools/todoWrite.js +0 -75
package/src/tools/registry.js
CHANGED
|
@@ -12,7 +12,6 @@ import { processList } from './processList.js';
|
|
|
12
12
|
import { procTerm } from './procTerm.js';
|
|
13
13
|
import { webFetch } from './webFetch.js';
|
|
14
14
|
import { desktopOpen } from './desktopOpen.js';
|
|
15
|
-
import { todoWrite } from './todoWrite.js';
|
|
16
15
|
import { multiEdit } from './multiEdit.js';
|
|
17
16
|
import { codebaseSearch } from './codebaseSearch.js';
|
|
18
17
|
import { browserAct } from './browserAutomator.js';
|
|
@@ -22,6 +21,7 @@ import { palmierAutomator } from './palmierAutomator.js';
|
|
|
22
21
|
import { viewImage } from './viewImage.js';
|
|
23
22
|
import { webSearch } from './webSearch.js';
|
|
24
23
|
import { updatePlan } from './updatePlan.js';
|
|
24
|
+
import { requestDecision } from './requestDecision.js';
|
|
25
25
|
import { clipboard } from './clipboard.js';
|
|
26
26
|
import { screenshot } from './screenshot.js';
|
|
27
27
|
import { axSnapshot } from './axSnapshot.js';
|
|
@@ -106,9 +106,9 @@ const TOOL_MODULES = [
|
|
|
106
106
|
sysRun, sysRunBg, fsRead, fsWrite,
|
|
107
107
|
fsEdit, fsFind, fsSearch, fsList, fsMove,
|
|
108
108
|
fsDelete, gitExec, processList, procTerm,
|
|
109
|
-
webFetch, desktopOpen,
|
|
109
|
+
webFetch, desktopOpen, multiEdit,
|
|
110
110
|
codebaseSearch, browserAct, viewImage, webSearch,
|
|
111
|
-
updatePlan, clipboard, screenshot, axSnapshot, desktopAct, archive,
|
|
111
|
+
updatePlan, requestDecision, clipboard, screenshot, axSnapshot, desktopAct, archive,
|
|
112
112
|
checkSystemSpecs, executeProceduralMedia, manageLocalAiDependencies,
|
|
113
113
|
generateMediaViaApi, memoryWrite, printerPrint, printerList, printerStatus, toolForgeTool, pluginManagerTool,
|
|
114
114
|
astryxOracle, pdfRead, codeVerifySyntax, codeListSymbols, blenderAct, resolveAutomator, palmierAutomator,
|
|
@@ -145,6 +145,7 @@ for (const module of TOOL_MODULES) {
|
|
|
145
145
|
// a tool whose canonical schema name is different. Resolved only at execution
|
|
146
146
|
// time — never advertised, so they cost no context.
|
|
147
147
|
const TOOL_ALIASES = {
|
|
148
|
+
TodoWrite: 'update_plan',
|
|
148
149
|
manage_local_ai_dependencies: 'manage_local_dependencies',
|
|
149
150
|
procTerm: 'proc_term',
|
|
150
151
|
gitExec: 'gitOp',
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
const KINDS = new Set(['architecture', 'product', 'data', 'security', 'workflow', 'other']);
|
|
3
|
+
function text(value, name, limit) {
|
|
4
|
+
if (typeof value !== 'string')
|
|
5
|
+
throw new Error(`${name} must be a string`);
|
|
6
|
+
const cleaned = value.replace(/\s+/g, ' ').trim();
|
|
7
|
+
if (!cleaned)
|
|
8
|
+
throw new Error(`${name} must not be empty`);
|
|
9
|
+
if (cleaned.length > limit)
|
|
10
|
+
throw new Error(`${name} exceeds ${limit} characters`);
|
|
11
|
+
return cleaned;
|
|
12
|
+
}
|
|
13
|
+
function normalizeRequest(args) {
|
|
14
|
+
const kind = String(args.kind ?? 'other');
|
|
15
|
+
if (!KINDS.has(kind))
|
|
16
|
+
throw new Error('kind is invalid');
|
|
17
|
+
if (!Array.isArray(args.options) || args.options.length < 2 || args.options.length > 4) {
|
|
18
|
+
throw new Error('options must contain 2-4 choices');
|
|
19
|
+
}
|
|
20
|
+
const ids = new Set();
|
|
21
|
+
const options = args.options.map((raw, index) => {
|
|
22
|
+
const option = (raw && typeof raw === 'object' ? raw : {});
|
|
23
|
+
const id = text(option.id ?? `option-${index + 1}`, `options[${index}].id`, 48);
|
|
24
|
+
if (ids.has(id))
|
|
25
|
+
throw new Error(`Duplicate option id: ${id}`);
|
|
26
|
+
ids.add(id);
|
|
27
|
+
return {
|
|
28
|
+
id,
|
|
29
|
+
label: text(option.label, `options[${index}].label`, 80),
|
|
30
|
+
description: text(option.description, `options[${index}].description`, 240)
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
id: crypto.randomUUID(),
|
|
35
|
+
kind,
|
|
36
|
+
header: text(args.header, 'header', 48),
|
|
37
|
+
question: text(args.question, 'question', 500),
|
|
38
|
+
reason: text(args.reason, 'reason', 500),
|
|
39
|
+
options,
|
|
40
|
+
allowCustom: true
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export const requestDecision = {
|
|
44
|
+
schema: {
|
|
45
|
+
name: 'request_decision',
|
|
46
|
+
description: 'Pause for a durable user decision when several materially different implementation directions are valid. Provide 2-4 concrete options; a custom answer is always available. This is a design decision, not a tool permission request.',
|
|
47
|
+
parameters: {
|
|
48
|
+
type: 'object',
|
|
49
|
+
properties: {
|
|
50
|
+
kind: { type: 'string', enum: ['architecture', 'product', 'data', 'security', 'workflow', 'other'] },
|
|
51
|
+
header: { type: 'string', description: 'Short decision label, at most 48 characters.' },
|
|
52
|
+
question: { type: 'string', description: 'One specific decision the user must make.' },
|
|
53
|
+
reason: { type: 'string', description: 'Why this choice has durable consequences.' },
|
|
54
|
+
options: {
|
|
55
|
+
type: 'array', minItems: 2, maxItems: 4,
|
|
56
|
+
items: {
|
|
57
|
+
type: 'object',
|
|
58
|
+
properties: {
|
|
59
|
+
id: { type: 'string' },
|
|
60
|
+
label: { type: 'string' },
|
|
61
|
+
description: { type: 'string' }
|
|
62
|
+
},
|
|
63
|
+
required: ['id', 'label', 'description'],
|
|
64
|
+
additionalProperties: false
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
required: ['kind', 'header', 'question', 'reason', 'options'],
|
|
69
|
+
additionalProperties: false
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
async execute(args, context = {}) {
|
|
73
|
+
try {
|
|
74
|
+
if (!context.parentLoop || typeof context.parentLoop.requestDecision !== 'function') {
|
|
75
|
+
return { success: false, error: 'Structured decisions require an active Causyne session' };
|
|
76
|
+
}
|
|
77
|
+
const request = normalizeRequest(args);
|
|
78
|
+
const answer = await context.parentLoop.requestDecision(request);
|
|
79
|
+
if (!answer)
|
|
80
|
+
return { success: false, error: 'The user dismissed the decision without choosing an option' };
|
|
81
|
+
return { success: true, output: JSON.stringify(answer), decision: answer };
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import type { DecisionKind, DecisionRequest } from '../decisionPolicy.js';
|
|
3
|
+
|
|
4
|
+
interface ToolContext {
|
|
5
|
+
parentLoop?: {
|
|
6
|
+
requestDecision(request: DecisionRequest): Promise<unknown>;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface RawOption {
|
|
11
|
+
id?: unknown;
|
|
12
|
+
label?: unknown;
|
|
13
|
+
description?: unknown;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface RawInput {
|
|
17
|
+
kind?: unknown;
|
|
18
|
+
header?: unknown;
|
|
19
|
+
question?: unknown;
|
|
20
|
+
reason?: unknown;
|
|
21
|
+
options?: unknown;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const KINDS = new Set<DecisionKind>(['architecture', 'product', 'data', 'security', 'workflow', 'other']);
|
|
25
|
+
|
|
26
|
+
function text(value: unknown, name: string, limit: number): string {
|
|
27
|
+
if (typeof value !== 'string') throw new Error(`${name} must be a string`);
|
|
28
|
+
const cleaned = value.replace(/\s+/g, ' ').trim();
|
|
29
|
+
if (!cleaned) throw new Error(`${name} must not be empty`);
|
|
30
|
+
if (cleaned.length > limit) throw new Error(`${name} exceeds ${limit} characters`);
|
|
31
|
+
return cleaned;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function normalizeRequest(args: RawInput): DecisionRequest {
|
|
35
|
+
const kind = String(args.kind ?? 'other') as DecisionKind;
|
|
36
|
+
if (!KINDS.has(kind)) throw new Error('kind is invalid');
|
|
37
|
+
if (!Array.isArray(args.options) || args.options.length < 2 || args.options.length > 4) {
|
|
38
|
+
throw new Error('options must contain 2-4 choices');
|
|
39
|
+
}
|
|
40
|
+
const ids = new Set<string>();
|
|
41
|
+
const options = args.options.map((raw, index) => {
|
|
42
|
+
const option = (raw && typeof raw === 'object' ? raw : {}) as RawOption;
|
|
43
|
+
const id = text(option.id ?? `option-${index + 1}`, `options[${index}].id`, 48);
|
|
44
|
+
if (ids.has(id)) throw new Error(`Duplicate option id: ${id}`);
|
|
45
|
+
ids.add(id);
|
|
46
|
+
return {
|
|
47
|
+
id,
|
|
48
|
+
label: text(option.label, `options[${index}].label`, 80),
|
|
49
|
+
description: text(option.description, `options[${index}].description`, 240)
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
return {
|
|
53
|
+
id: crypto.randomUUID(),
|
|
54
|
+
kind,
|
|
55
|
+
header: text(args.header, 'header', 48),
|
|
56
|
+
question: text(args.question, 'question', 500),
|
|
57
|
+
reason: text(args.reason, 'reason', 500),
|
|
58
|
+
options,
|
|
59
|
+
allowCustom: true
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const requestDecision = {
|
|
64
|
+
schema: {
|
|
65
|
+
name: 'request_decision',
|
|
66
|
+
description: 'Pause for a durable user decision when several materially different implementation directions are valid. Provide 2-4 concrete options; a custom answer is always available. This is a design decision, not a tool permission request.',
|
|
67
|
+
parameters: {
|
|
68
|
+
type: 'object',
|
|
69
|
+
properties: {
|
|
70
|
+
kind: { type: 'string', enum: ['architecture', 'product', 'data', 'security', 'workflow', 'other'] },
|
|
71
|
+
header: { type: 'string', description: 'Short decision label, at most 48 characters.' },
|
|
72
|
+
question: { type: 'string', description: 'One specific decision the user must make.' },
|
|
73
|
+
reason: { type: 'string', description: 'Why this choice has durable consequences.' },
|
|
74
|
+
options: {
|
|
75
|
+
type: 'array', minItems: 2, maxItems: 4,
|
|
76
|
+
items: {
|
|
77
|
+
type: 'object',
|
|
78
|
+
properties: {
|
|
79
|
+
id: { type: 'string' },
|
|
80
|
+
label: { type: 'string' },
|
|
81
|
+
description: { type: 'string' }
|
|
82
|
+
},
|
|
83
|
+
required: ['id', 'label', 'description'],
|
|
84
|
+
additionalProperties: false
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
required: ['kind', 'header', 'question', 'reason', 'options'],
|
|
89
|
+
additionalProperties: false
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
async execute(args: RawInput, context: ToolContext = {}) {
|
|
94
|
+
try {
|
|
95
|
+
if (!context.parentLoop || typeof context.parentLoop.requestDecision !== 'function') {
|
|
96
|
+
return { success: false, error: 'Structured decisions require an active Causyne session' };
|
|
97
|
+
}
|
|
98
|
+
const request = normalizeRequest(args);
|
|
99
|
+
const answer = await context.parentLoop.requestDecision(request);
|
|
100
|
+
if (!answer) return { success: false, error: 'The user dismissed the decision without choosing an option' };
|
|
101
|
+
return { success: true, output: JSON.stringify(answer), decision: answer };
|
|
102
|
+
} catch (error) {
|
|
103
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { TokenTracker } from '../tokenTracker.js';
|
|
2
|
+
import { listAgentProfiles, loadAgentProfile } from '../agentProfiles.js';
|
|
3
|
+
import {
|
|
4
|
+
completeSubagentJournal,
|
|
5
|
+
failSubagentJournal,
|
|
6
|
+
startSubagentJournal
|
|
7
|
+
} from '../subagentJournal.js';
|
|
2
8
|
|
|
3
9
|
// Subagent delegation.
|
|
4
10
|
//
|
|
@@ -67,6 +73,8 @@ async function executeSubagent(record, parentLoop) {
|
|
|
67
73
|
const child = new AgentLoop(transport, record.subagentId, record.prompt);
|
|
68
74
|
|
|
69
75
|
// A delegated task is a bounded errand, not a second full session.
|
|
76
|
+
child.maxStepsCeiling = record.maxSteps;
|
|
77
|
+
child.taskTokenLimit = record.maxTokens;
|
|
70
78
|
child.maxSteps = Math.min(child.maxSteps || 25, record.maxSteps);
|
|
71
79
|
child.maxCircuitBreaker = child.maxSteps;
|
|
72
80
|
child.messages = [];
|
|
@@ -78,8 +86,16 @@ async function executeSubagent(record, parentLoop) {
|
|
|
78
86
|
|
|
79
87
|
record.loop = child;
|
|
80
88
|
record.startedAtMs = Date.now();
|
|
89
|
+
record.journalStarted = startSubagentJournal(record);
|
|
81
90
|
|
|
82
|
-
|
|
91
|
+
try {
|
|
92
|
+
await child.run();
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (record.journalStarted) {
|
|
95
|
+
failSubagentJournal(record.subagentId, error instanceof Error ? error.message : String(error));
|
|
96
|
+
}
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
83
99
|
|
|
84
100
|
const finalAssistant = [...child.messages].reverse()
|
|
85
101
|
.find(m => m.role === 'assistant' && typeof m.content === 'string' && m.content.trim());
|
|
@@ -88,7 +104,7 @@ async function executeSubagent(record, parentLoop) {
|
|
|
88
104
|
try { return JSON.parse(m.content).success === false; } catch (e) { return false; }
|
|
89
105
|
});
|
|
90
106
|
|
|
91
|
-
|
|
107
|
+
const result = {
|
|
92
108
|
subagentId: record.subagentId,
|
|
93
109
|
name: record.name,
|
|
94
110
|
stepsCompleted: child.stepsCompleted,
|
|
@@ -100,6 +116,8 @@ async function executeSubagent(record, parentLoop) {
|
|
|
100
116
|
summary: finalAssistant ? finalAssistant.content : null,
|
|
101
117
|
stoppedEarly: child.stepsCompleted >= child.maxSteps
|
|
102
118
|
};
|
|
119
|
+
if (record.journalStarted) completeSubagentJournal(record.subagentId, child.messages, result);
|
|
120
|
+
return result;
|
|
103
121
|
}
|
|
104
122
|
|
|
105
123
|
export const subagentOrchestrator = {
|
|
@@ -111,15 +129,16 @@ export const subagentOrchestrator = {
|
|
|
111
129
|
properties: {
|
|
112
130
|
action: {
|
|
113
131
|
type: 'string',
|
|
114
|
-
enum: ['spawn', 'list', 'kill', 'delegate_and_wait'],
|
|
115
|
-
description: 'spawn (start in the background
|
|
132
|
+
enum: ['spawn', 'list', 'list_profiles', 'kill', 'delegate_and_wait'],
|
|
133
|
+
description: 'spawn (start in the background), list (show active subagents), list_profiles (show static user-defined agent profiles), kill (stop one), or delegate_and_wait (run and return the result).'
|
|
116
134
|
},
|
|
117
135
|
name: { type: 'string', description: 'A short name for the subagent, e.g. Audit_Worker.' },
|
|
118
136
|
prompt: { type: 'string', description: 'The complete instruction for the subagent. It has no memory of this conversation, so it must be self-contained.' },
|
|
137
|
+
profile: { type: 'string', description: 'Optional profile name from ~/.causyne/agents/<name>.json. Its tool and budget limits cannot be widened by the call.' },
|
|
119
138
|
subagentId: { type: 'string', description: 'Target subagent for the kill action.' },
|
|
120
139
|
toolScope: { type: 'array', items: { type: 'string' }, description: 'Catalogue tool names the subagent may use. Defaults to read-only inspection tools. It can never receive the delegation tool itself.' },
|
|
121
|
-
maxSteps: { type: 'integer', description: '
|
|
122
|
-
maxTokens: { type: 'integer', description: '
|
|
140
|
+
maxSteps: { type: 'integer', description: 'Hard step ceiling for the subagent. Defaults to 25.' },
|
|
141
|
+
maxTokens: { type: 'integer', description: 'Hard per-task token ceiling for the subagent. Defaults to 50000.' },
|
|
123
142
|
depth: { type: 'integer', description: 'Hierarchy depth. 0 = master, 1 = worker. Subagents cannot spawn subagents.' }
|
|
124
143
|
},
|
|
125
144
|
required: ['action']
|
|
@@ -153,6 +172,23 @@ export const subagentOrchestrator = {
|
|
|
153
172
|
};
|
|
154
173
|
}
|
|
155
174
|
|
|
175
|
+
if (action === 'list_profiles') {
|
|
176
|
+
const { profiles, problems } = listAgentProfiles();
|
|
177
|
+
return {
|
|
178
|
+
success: true,
|
|
179
|
+
output: JSON.stringify({
|
|
180
|
+
profiles: profiles.map(profile => ({
|
|
181
|
+
name: profile.name,
|
|
182
|
+
description: profile.description,
|
|
183
|
+
toolScope: profile.toolScope,
|
|
184
|
+
maxSteps: profile.maxSteps,
|
|
185
|
+
maxTokens: profile.maxTokens
|
|
186
|
+
})),
|
|
187
|
+
problems
|
|
188
|
+
}, null, 2)
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
156
192
|
if (action === 'kill') {
|
|
157
193
|
const id = args.subagentId || args.name;
|
|
158
194
|
if (!id) return { success: false, error: 'subagentId or name is required to stop a subagent.' };
|
|
@@ -173,7 +209,17 @@ export const subagentOrchestrator = {
|
|
|
173
209
|
return { success: false, error: `Unknown action: ${action}` };
|
|
174
210
|
}
|
|
175
211
|
|
|
176
|
-
|
|
212
|
+
let profile = null;
|
|
213
|
+
if (args.profile !== undefined) {
|
|
214
|
+
const loaded = loadAgentProfile(args.profile);
|
|
215
|
+
if (!loaded.ok) return { success: false, error: `Agent profile ${JSON.stringify(args.profile)}: ${loaded.error}` };
|
|
216
|
+
profile = loaded.profile;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const requestedPrompt = args.prompt;
|
|
220
|
+
const prompt = profile
|
|
221
|
+
? `${profile.instructions}\n\nDelegated task:\n${String(requestedPrompt || '')}`
|
|
222
|
+
: requestedPrompt;
|
|
177
223
|
if (!prompt) return { success: false, error: 'A prompt is required to delegate a task.' };
|
|
178
224
|
|
|
179
225
|
const depth = args.depth !== undefined ? args.depth : 1;
|
|
@@ -195,11 +241,19 @@ export const subagentOrchestrator = {
|
|
|
195
241
|
};
|
|
196
242
|
}
|
|
197
243
|
|
|
198
|
-
const name = args.name || `Worker_${Math.random().toString(36).slice(2, 7)}`;
|
|
244
|
+
const name = args.name || profile?.name || `Worker_${Math.random().toString(36).slice(2, 7)}`;
|
|
199
245
|
const subagentId = `sub_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
|
|
200
|
-
|
|
246
|
+
let requestedScope = Array.isArray(args.toolScope) && args.toolScope.length > 0
|
|
201
247
|
? args.toolScope
|
|
202
|
-
: DEFAULT_TOOL_SCOPE;
|
|
248
|
+
: profile?.toolScope || DEFAULT_TOOL_SCOPE;
|
|
249
|
+
if (profile) {
|
|
250
|
+
const ceiling = new Set(profile.toolScope);
|
|
251
|
+
requestedScope = requestedScope.filter(tool => ceiling.has(String(tool).replace(/\./g, '_')));
|
|
252
|
+
if (requestedScope.length === 0) {
|
|
253
|
+
return { success: false, error: `Agent profile ${profile.name} permits none of the requested tools.` };
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
requestedScope = [...new Set(requestedScope.map(tool => String(tool).replace(/\./g, '_')))];
|
|
203
257
|
|
|
204
258
|
// Reject an unknown tool name instead of silently accepting it. The old
|
|
205
259
|
// implementation accepted anything, which is easy to do when the scope is
|
|
@@ -211,14 +265,21 @@ export const subagentOrchestrator = {
|
|
|
211
265
|
return { success: false, error: `Unknown tools in toolScope: ${unknown.join(', ')}.` };
|
|
212
266
|
}
|
|
213
267
|
|
|
268
|
+
const requestedMaxSteps = Number.isInteger(args.maxSteps)
|
|
269
|
+
? Math.max(1, Math.min(args.maxSteps, 100))
|
|
270
|
+
: profile?.maxSteps || 25;
|
|
271
|
+
const requestedMaxTokens = Number.isInteger(args.maxTokens)
|
|
272
|
+
? Math.max(1_000, Math.min(args.maxTokens, 200_000))
|
|
273
|
+
: profile?.maxTokens || 50_000;
|
|
214
274
|
const record = {
|
|
215
275
|
subagentId,
|
|
216
276
|
name,
|
|
217
277
|
prompt,
|
|
218
278
|
toolScope: requestedScope,
|
|
219
279
|
depth,
|
|
220
|
-
maxSteps:
|
|
221
|
-
maxTokens:
|
|
280
|
+
maxSteps: Math.min(requestedMaxSteps, profile?.maxSteps || 100),
|
|
281
|
+
maxTokens: Math.min(requestedMaxTokens, profile?.maxTokens || 200_000),
|
|
282
|
+
profile: profile?.name || null,
|
|
222
283
|
status: 'running',
|
|
223
284
|
startTime: new Date().toISOString(),
|
|
224
285
|
summary: null,
|
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
|
+
};
|