@causyne/agent 1.10.12 → 1.10.17

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.
Files changed (68) hide show
  1. package/LICENSE +1 -1
  2. package/docs/ARCHITECTURE.md +136 -7
  3. package/docs/SECURITY_REVIEW.md +13 -1
  4. package/docs/THIRD_PARTY_NOTICES.md +43 -10
  5. package/package.json +11 -4
  6. package/src/agentLoop.js +161 -93
  7. package/src/agentProfiles.js +135 -0
  8. package/src/agentProfiles.ts +143 -0
  9. package/src/approvalGrants.js +17 -35
  10. package/src/approvalGrants.ts +54 -0
  11. package/src/auditLog.d.ts +3 -0
  12. package/src/benchmarks/planCache.js +159 -0
  13. package/src/browser/camofoxRuntime.js +93 -0
  14. package/src/browser/camofoxRuntime.ts +112 -0
  15. package/src/browser/causyneBrowser.js +495 -0
  16. package/src/browser/causyneBrowser.ts +594 -0
  17. package/src/browser/searchProviders.js +156 -0
  18. package/src/browser/searchProviders.ts +156 -0
  19. package/src/browser/snapshotWindow.js +28 -0
  20. package/src/browser/snapshotWindow.ts +40 -0
  21. package/src/browser/urlPolicy.js +89 -0
  22. package/src/browser/urlPolicy.ts +89 -0
  23. package/src/checkpoints.js +19 -6
  24. package/src/commands.js +120 -8
  25. package/src/config.d.ts +2 -0
  26. package/src/cortex/autoCondenser.js +1 -1
  27. package/src/hooks.js +150 -0
  28. package/src/hooks.ts +193 -0
  29. package/src/mcp/mcpClient.js +520 -132
  30. package/src/mcp/mcpClient.ts +584 -0
  31. package/src/mcp/mcpRegistry.js +399 -185
  32. package/src/mcp/mcpRegistry.ts +451 -0
  33. package/src/planCache.js +529 -225
  34. package/src/planCache.ts +840 -0
  35. package/src/plugins/pluginManager.js +460 -0
  36. package/src/plugins/pluginRunner.js +50 -0
  37. package/src/plugins/pluginSkills.js +84 -0
  38. package/src/plugins/pluginSkills.ts +106 -0
  39. package/src/provisioning/capabilityProvisioner.d.ts +10 -0
  40. package/src/provisioning/capabilityProvisioner.js +5 -0
  41. package/src/provisioning/capabilityTools.js +2 -1
  42. package/src/sandbox.js +53 -2
  43. package/src/sessionJournal.js +471 -0
  44. package/src/sessionJournal.ts +496 -0
  45. package/src/sessionStore.js +3 -279
  46. package/src/skillsManager.js +18 -0
  47. package/src/subagentJournal.js +215 -0
  48. package/src/subagentJournal.ts +254 -0
  49. package/src/taskShape.js +179 -198
  50. package/src/taskShape.ts +259 -0
  51. package/src/toolPacks.js +6 -3
  52. package/src/tools/browserAutomator.js +45 -241
  53. package/src/tools/browserAutomator.ts +57 -0
  54. package/src/tools/deferredMcpSearch.js +2 -2
  55. package/src/tools/manageLocalAiDependencies.js +1 -1
  56. package/src/tools/pluginManager.js +61 -0
  57. package/src/tools/registry.js +47 -53
  58. package/src/tools/subagentOrchestrator.js +73 -12
  59. package/src/tools/toolForge.js +47 -95
  60. package/src/tools/webFetch.js +183 -68
  61. package/src/tools/webFetch.ts +185 -0
  62. package/src/tools/webSearch.js +50 -75
  63. package/src/tools/webSearch.ts +56 -0
  64. package/src/types/contracts.js +8 -0
  65. package/src/types/contracts.ts +263 -0
  66. package/src/utils/paths.d.ts +3 -0
  67. package/src/version.d.ts +1 -0
  68. package/src/mcp/jsonRpc.js +0 -165
@@ -0,0 +1,254 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { causyneHome } from './utils/paths.js';
4
+
5
+ const ROOT = path.join(causyneHome(), 'subagents');
6
+ const SAFE_ID = /^sub_[a-zA-Z0-9_-]{1,120}$/;
7
+ const MAX_MESSAGES = 500;
8
+ const MAX_CONTENT_BYTES = 128 * 1024;
9
+ const MAX_EVENT_BYTES = 512 * 1024;
10
+ const MAX_JOURNAL_BYTES = 16 * 1024 * 1024;
11
+
12
+ interface StartedEvent {
13
+ version: 1;
14
+ type: 'subagent.started';
15
+ at: number;
16
+ subagentId: string;
17
+ name: string;
18
+ prompt: string;
19
+ profile: string | null;
20
+ toolScope: string[];
21
+ maxSteps: number;
22
+ maxTokens: number;
23
+ }
24
+
25
+ interface MessageEvent {
26
+ version: 1;
27
+ type: 'message.recorded';
28
+ at: number;
29
+ message: Record<string, unknown>;
30
+ }
31
+
32
+ interface FinishedEvent {
33
+ version: 1;
34
+ type: 'subagent.completed' | 'subagent.failed';
35
+ at: number;
36
+ result?: Record<string, unknown>;
37
+ error?: string;
38
+ }
39
+
40
+ type SidechainEvent = StartedEvent | MessageEvent | FinishedEvent;
41
+
42
+ export interface SubagentJournalProjection {
43
+ subagentId: string;
44
+ name: string;
45
+ prompt: string;
46
+ profile: string | null;
47
+ toolScope: string[];
48
+ maxSteps: number;
49
+ maxTokens: number;
50
+ startedAt: number;
51
+ updatedAt: number;
52
+ status: 'running' | 'completed' | 'failed';
53
+ messages: Array<Record<string, unknown>>;
54
+ result: Record<string, unknown> | null;
55
+ error: string | null;
56
+ }
57
+
58
+ function ensureRoot(): void {
59
+ if (!fs.existsSync(ROOT)) fs.mkdirSync(ROOT, { recursive: true, mode: 0o700 });
60
+ }
61
+
62
+ function safeId(value: unknown): string | null {
63
+ const id = String(value ?? '');
64
+ return SAFE_ID.test(id) ? id : null;
65
+ }
66
+
67
+ export function subagentJournalFile(id: unknown): string | null {
68
+ const safe = safeId(id);
69
+ return safe ? path.join(ROOT, `${safe}.jsonl`) : null;
70
+ }
71
+
72
+ function boundedText(value: unknown, maximumBytes: number): string {
73
+ const text = String(value ?? '');
74
+ if (Buffer.byteLength(text, 'utf8') <= maximumBytes) return text;
75
+ let end = Math.min(text.length, maximumBytes);
76
+ while (end > 0 && Buffer.byteLength(text.slice(0, end), 'utf8') > maximumBytes) end -= 1;
77
+ return `${text.slice(0, end)}\n[truncated by Causyne]`;
78
+ }
79
+
80
+ function boundedMessage(value: unknown): Record<string, unknown> | null {
81
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) return null;
82
+ const input = value as Record<string, unknown>;
83
+ if (typeof input.role !== 'string') return null;
84
+ let content: unknown = input.content;
85
+ if (typeof content === 'string') {
86
+ content = boundedText(content, MAX_CONTENT_BYTES);
87
+ } else {
88
+ try {
89
+ const serialized = JSON.stringify(content);
90
+ content = Buffer.byteLength(serialized, 'utf8') <= MAX_CONTENT_BYTES
91
+ ? JSON.parse(serialized) as unknown
92
+ : '[structured content omitted: exceeds sidechain limit]';
93
+ } catch {
94
+ content = '[unserializable content omitted]';
95
+ }
96
+ }
97
+ const message: Record<string, unknown> = { role: input.role, content };
98
+ for (const key of ['name', 'tool_call_id']) {
99
+ if (typeof input[key] === 'string') message[key] = input[key];
100
+ }
101
+ return message;
102
+ }
103
+
104
+ function appendEvent(file: string, event: SidechainEvent): boolean {
105
+ try {
106
+ const line = `${JSON.stringify(event)}\n`;
107
+ if (Buffer.byteLength(line, 'utf8') > MAX_EVENT_BYTES) return false;
108
+ fs.appendFileSync(file, line, { encoding: 'utf8', mode: 0o600, flag: 'a' });
109
+ return true;
110
+ } catch {
111
+ return false;
112
+ }
113
+ }
114
+
115
+ export function startSubagentJournal(input: {
116
+ subagentId: string;
117
+ name: string;
118
+ prompt: string;
119
+ profile?: string | null;
120
+ toolScope: string[];
121
+ maxSteps: number;
122
+ maxTokens: number;
123
+ }): boolean {
124
+ const file = subagentJournalFile(input.subagentId);
125
+ if (!file) return false;
126
+ try {
127
+ ensureRoot();
128
+ const event: StartedEvent = {
129
+ version: 1,
130
+ type: 'subagent.started',
131
+ at: Date.now(),
132
+ subagentId: input.subagentId,
133
+ name: boundedText(input.name, 256),
134
+ prompt: boundedText(input.prompt, 32 * 1024),
135
+ profile: input.profile ?? null,
136
+ toolScope: input.toolScope.slice(0, 64),
137
+ maxSteps: input.maxSteps,
138
+ maxTokens: input.maxTokens
139
+ };
140
+ fs.writeFileSync(file, `${JSON.stringify(event)}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
141
+ return true;
142
+ } catch {
143
+ return false;
144
+ }
145
+ }
146
+
147
+ export function completeSubagentJournal(
148
+ id: unknown,
149
+ messages: unknown[],
150
+ result: Record<string, unknown>
151
+ ): boolean {
152
+ const file = subagentJournalFile(id);
153
+ if (!file || !fs.existsSync(file)) return false;
154
+ let ok = true;
155
+ for (const value of messages.slice(-MAX_MESSAGES)) {
156
+ const message = boundedMessage(value);
157
+ if (!message) continue;
158
+ ok = appendEvent(file, { version: 1, type: 'message.recorded', at: Date.now(), message }) && ok;
159
+ }
160
+ return appendEvent(file, {
161
+ version: 1,
162
+ type: 'subagent.completed',
163
+ at: Date.now(),
164
+ result
165
+ }) && ok;
166
+ }
167
+
168
+ export function failSubagentJournal(id: unknown, error: unknown): boolean {
169
+ const file = subagentJournalFile(id);
170
+ if (!file || !fs.existsSync(file)) return false;
171
+ return appendEvent(file, {
172
+ version: 1,
173
+ type: 'subagent.failed',
174
+ at: Date.now(),
175
+ error: boundedText(error, 8 * 1024)
176
+ });
177
+ }
178
+
179
+ export function readSubagentJournal(id: unknown): SubagentJournalProjection | null {
180
+ const file = subagentJournalFile(id);
181
+ if (!file) return null;
182
+ const requestedId = safeId(id);
183
+ if (!requestedId) return null;
184
+ try {
185
+ const stat = fs.statSync(file);
186
+ if (!stat.isFile() || stat.size > MAX_JOURNAL_BYTES) return null;
187
+ const text = fs.readFileSync(file, 'utf8');
188
+ const trailingNewline = text.endsWith('\n');
189
+ const lines = text.split('\n');
190
+ if (lines.at(-1) === '') lines.pop();
191
+ let projection: SubagentJournalProjection | null = null;
192
+ for (let index = 0; index < lines.length; index += 1) {
193
+ const line = lines[index];
194
+ if (!line) continue;
195
+ let event: unknown;
196
+ try {
197
+ event = JSON.parse(line);
198
+ } catch {
199
+ if (index === lines.length - 1 && !trailingNewline) break;
200
+ return null;
201
+ }
202
+ if (event === null || typeof event !== 'object' || Array.isArray(event)) return null;
203
+ const record = event as Record<string, unknown>;
204
+ if (record.version !== 1 || typeof record.type !== 'string' || typeof record.at !== 'number') return null;
205
+ if (record.type === 'subagent.started') {
206
+ if (projection
207
+ || typeof record.subagentId !== 'string'
208
+ || record.subagentId !== requestedId
209
+ || typeof record.name !== 'string'
210
+ || typeof record.prompt !== 'string'
211
+ || !Array.isArray(record.toolScope)
212
+ || typeof record.maxSteps !== 'number'
213
+ || typeof record.maxTokens !== 'number') return null;
214
+ projection = {
215
+ subagentId: record.subagentId,
216
+ name: record.name,
217
+ prompt: record.prompt,
218
+ profile: typeof record.profile === 'string' ? record.profile : null,
219
+ toolScope: record.toolScope.filter((tool): tool is string => typeof tool === 'string'),
220
+ maxSteps: record.maxSteps,
221
+ maxTokens: record.maxTokens,
222
+ startedAt: record.at,
223
+ updatedAt: record.at,
224
+ status: 'running',
225
+ messages: [],
226
+ result: null,
227
+ error: null
228
+ };
229
+ continue;
230
+ }
231
+ if (!projection) return null;
232
+ projection.updatedAt = record.at;
233
+ if (record.type === 'message.recorded') {
234
+ if (record.message === null || typeof record.message !== 'object' || Array.isArray(record.message)) return null;
235
+ projection.messages.push(record.message as Record<string, unknown>);
236
+ } else if (record.type === 'subagent.completed') {
237
+ projection.status = 'completed';
238
+ projection.result = record.result !== null && typeof record.result === 'object' && !Array.isArray(record.result)
239
+ ? record.result as Record<string, unknown>
240
+ : {};
241
+ } else if (record.type === 'subagent.failed') {
242
+ projection.status = 'failed';
243
+ projection.error = typeof record.error === 'string' ? record.error : 'unknown failure';
244
+ } else {
245
+ return null;
246
+ }
247
+ }
248
+ return projection;
249
+ } catch {
250
+ return null;
251
+ }
252
+ }
253
+
254
+ export { ROOT as SUBAGENT_JOURNAL_ROOT };