@causyne/agent 1.10.21 → 1.11.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.
Files changed (55) hide show
  1. package/native-skills/media/professional-document-publishing/SKILL.md +44 -0
  2. package/native-skills/media/screenplay-and-shotlist-forge/SKILL.md +42 -0
  3. package/package.json +1 -1
  4. package/src/acp/client.js +235 -0
  5. package/src/acp/client.ts +282 -0
  6. package/src/acp/peerProfiles.js +122 -0
  7. package/src/acp/peerProfiles.ts +128 -0
  8. package/src/acp/protocol.js +54 -0
  9. package/src/acp/protocol.ts +434 -0
  10. package/src/acp/server.js +609 -0
  11. package/src/acp/server.ts +676 -0
  12. package/src/agentLoop.d.ts +43 -0
  13. package/src/agentLoop.js +142 -8
  14. package/src/commands.js +203 -0
  15. package/src/compiledSkills.js +10 -0
  16. package/src/documentEngine/antiSlopAuditor.js +156 -0
  17. package/src/documentEngine/antiSlopAuditor.ts +221 -0
  18. package/src/documentEngine/documentForge.js +443 -0
  19. package/src/documentEngine/documentForge.ts +519 -0
  20. package/src/documentEngine/screenplayFormatter.js +336 -0
  21. package/src/documentEngine/screenplayFormatter.ts +407 -0
  22. package/src/entrypoint.js +13 -0
  23. package/src/execSafe.js +10 -13
  24. package/src/execSafe.ts +25 -0
  25. package/src/modelRouter.js +56 -123
  26. package/src/modelRouter.ts +134 -0
  27. package/src/observer/advisory.js +164 -0
  28. package/src/observer/advisory.ts +171 -0
  29. package/src/observer/coldObserver.js +462 -0
  30. package/src/observer/coldObserver.ts +540 -0
  31. package/src/observer/evidence.js +435 -0
  32. package/src/observer/evidence.ts +499 -0
  33. package/src/perception.js +303 -118
  34. package/src/perception.ts +387 -0
  35. package/src/procSignal.js +15 -16
  36. package/src/procSignal.ts +31 -0
  37. package/src/promptMentions.js +170 -0
  38. package/src/promptMentions.ts +212 -0
  39. package/src/sandbox.js +1 -1
  40. package/src/textDiff.js +117 -109
  41. package/src/textDiff.ts +169 -0
  42. package/src/toolPacks.js +5 -2
  43. package/src/tools/documentForge.js +215 -0
  44. package/src/tools/documentForge.ts +265 -0
  45. package/src/tools/peerAgent.js +102 -0
  46. package/src/tools/peerAgent.ts +116 -0
  47. package/src/tools/registry.js +3 -1
  48. package/src/tui/completion.js +97 -41
  49. package/src/tui/prompt.js +17 -17
  50. package/src/uncertainty.js +93 -205
  51. package/src/uncertainty.ts +255 -0
  52. package/src/utils/paths.js +15 -18
  53. package/src/utils/paths.ts +36 -0
  54. package/src/vfeTracker.js +122 -181
  55. package/src/vfeTracker.ts +242 -0
@@ -0,0 +1,215 @@
1
+ /*
2
+ * Copyright (c) 2026 Causyne.
3
+ * SPDX-License-Identifier: BUSL-1.1
4
+ *
5
+ * Causyne Document Forge Tool Wrapper.
6
+ * Bridges HTML/CSS-first publishing, headless PDF compilation, visual page QA,
7
+ * anti-slop compliance auditing, and Hollywood screenplay & shot list generation.
8
+ */
9
+ import fs from 'node:fs';
10
+ import path from 'node:path';
11
+ import { compileDocumentHtml, renderHtmlToPdf, inspectPdfPages } from '../documentEngine/documentForge.js';
12
+ import { formatScreenplayHtml, formatShotListHtml, analyzeVoiceOverTiming } from '../documentEngine/screenplayFormatter.js';
13
+ import { auditTextContent, auditBudgetArithmetic } from '../documentEngine/antiSlopAuditor.js';
14
+ import { resolveUserPath } from '../utils/paths.js';
15
+ export const documentForgeTool = {
16
+ schema: {
17
+ name: 'document_forge',
18
+ description: 'Causyne Document Forge: Professional document & media production harness. Generates pixel-perfect A4/Letter publications via HTML/CSS-First Paged Media and Headless Chrome, renders vector PDFs (<2MB compliant), extracts page images for visual QA ("Lihat Hasilnya"), audits anti-slop and math integrity, formats Hollywood Courier 12pt screenplays, creates production shot lists, and times VO narration.',
19
+ parameters: {
20
+ type: 'object',
21
+ properties: {
22
+ action: {
23
+ type: 'string',
24
+ enum: [
25
+ 'compile_document',
26
+ 'render_pdf',
27
+ 'inspect_pages',
28
+ 'audit_text',
29
+ 'audit_budget',
30
+ 'format_screenplay',
31
+ 'format_shotlist',
32
+ 'analyze_vo'
33
+ ],
34
+ description: 'Action to execute.'
35
+ },
36
+ documentSpec: {
37
+ type: 'object',
38
+ properties: {
39
+ title: { type: 'string' },
40
+ subtitle: { type: 'string' },
41
+ categoryOrKicker: { type: 'string' },
42
+ authorOrProposer: { type: 'string' },
43
+ palette: { type: 'string', enum: ['corporate', 'editorial_dair', 'minimal_clean'] },
44
+ includeCover: { type: 'boolean' }
45
+ },
46
+ description: 'Formal publication spec with sections, tables, and palette (corporate, editorial_dair, minimal_clean).'
47
+ },
48
+ screenplaySpec: {
49
+ type: 'object',
50
+ properties: {
51
+ title: { type: 'string' },
52
+ author: { type: 'string' },
53
+ contact: { type: 'string' }
54
+ },
55
+ description: 'Hollywood screenplay spec with scene headings, action, characters, dialogue, and transitions.'
56
+ },
57
+ shotList: {
58
+ type: 'object',
59
+ properties: {
60
+ title: { type: 'string' },
61
+ shots: {
62
+ type: 'array',
63
+ items: {
64
+ type: 'object',
65
+ properties: {
66
+ shotNumber: { type: 'number' },
67
+ framing: { type: 'string' },
68
+ subject: { type: 'string' },
69
+ action: { type: 'string' },
70
+ durationSeconds: { type: 'number' }
71
+ }
72
+ }
73
+ }
74
+ },
75
+ description: 'Director production shot list with framing, lenses, camera movements, and audio cues.'
76
+ },
77
+ voBlocks: {
78
+ type: 'array',
79
+ items: {
80
+ type: 'object',
81
+ properties: {
82
+ id: { type: 'string' },
83
+ timecode: { type: 'string' },
84
+ text: { type: 'string' },
85
+ targetDurationSeconds: { type: 'number' }
86
+ }
87
+ },
88
+ description: 'Voiceover blocks with timecode and target durations for WPM pacing analysis.'
89
+ },
90
+ budgetSpec: {
91
+ type: 'object',
92
+ properties: {
93
+ grossUpTaxRate: { type: 'number' },
94
+ targetGross: { type: 'number' }
95
+ },
96
+ description: 'Budget specification for arithmetic validation and tax gross-up verification.'
97
+ },
98
+ textContent: {
99
+ type: 'string',
100
+ description: 'Raw text or HTML content to audit for AI tropes, long em-dashes, and placeholder brackets.'
101
+ },
102
+ sourceHtmlPath: {
103
+ type: 'string',
104
+ description: 'Path to HTML file to render into PDF via Headless Chrome.'
105
+ },
106
+ outputPdfPath: {
107
+ type: 'string',
108
+ description: 'Destination PDF file path.'
109
+ },
110
+ outputPath: {
111
+ type: 'string',
112
+ description: 'Destination HTML or JSON output file path.'
113
+ },
114
+ inspectImagesDir: {
115
+ type: 'string',
116
+ description: 'Directory to save extracted page images for visual inspection.'
117
+ }
118
+ }
119
+ }
120
+ },
121
+ async execute(args = {}, _context = {}) {
122
+ const action = args.action || 'compile_document';
123
+ switch (action) {
124
+ case 'compile_document': {
125
+ if (!args.documentSpec) {
126
+ return { success: false, error: 'compile_document requires documentSpec.' };
127
+ }
128
+ const compiled = compileDocumentHtml(args.documentSpec);
129
+ if (args.outputPath) {
130
+ const target = resolveUserPath(args.outputPath);
131
+ fs.mkdirSync(path.dirname(target), { recursive: true });
132
+ fs.writeFileSync(target, compiled.html, 'utf8');
133
+ }
134
+ return {
135
+ success: true,
136
+ html: compiled.html,
137
+ outputPath: args.outputPath ? resolveUserPath(args.outputPath) : undefined
138
+ };
139
+ }
140
+ case 'render_pdf': {
141
+ if (!args.sourceHtmlPath || !args.outputPdfPath) {
142
+ return { success: false, error: 'render_pdf requires sourceHtmlPath and outputPdfPath.' };
143
+ }
144
+ const src = resolveUserPath(args.sourceHtmlPath);
145
+ const out = resolveUserPath(args.outputPdfPath);
146
+ const res = await renderHtmlToPdf(src, out);
147
+ return res;
148
+ }
149
+ case 'inspect_pages': {
150
+ if (!args.outputPdfPath) {
151
+ return { success: false, error: 'inspect_pages requires outputPdfPath.' };
152
+ }
153
+ const pdf = resolveUserPath(args.outputPdfPath);
154
+ const imgDir = args.inspectImagesDir ? resolveUserPath(args.inspectImagesDir) : undefined;
155
+ const res = await inspectPdfPages(pdf, imgDir);
156
+ return res;
157
+ }
158
+ case 'audit_text': {
159
+ if (!args.textContent) {
160
+ return { success: false, error: 'audit_text requires textContent parameter.' };
161
+ }
162
+ const res = auditTextContent(args.textContent);
163
+ return { success: true, ...res };
164
+ }
165
+ case 'audit_budget': {
166
+ if (!args.budgetSpec) {
167
+ return { success: false, error: 'audit_budget requires budgetSpec parameter.' };
168
+ }
169
+ const res = auditBudgetArithmetic(args.budgetSpec);
170
+ return { success: true, ...res };
171
+ }
172
+ case 'format_screenplay': {
173
+ if (!args.screenplaySpec) {
174
+ return { success: false, error: 'format_screenplay requires screenplaySpec.' };
175
+ }
176
+ const formatted = formatScreenplayHtml(args.screenplaySpec);
177
+ if (args.outputPath) {
178
+ const target = resolveUserPath(args.outputPath);
179
+ fs.mkdirSync(path.dirname(target), { recursive: true });
180
+ fs.writeFileSync(target, formatted.html, 'utf8');
181
+ }
182
+ return {
183
+ success: true,
184
+ html: formatted.html,
185
+ outputPath: args.outputPath ? resolveUserPath(args.outputPath) : undefined
186
+ };
187
+ }
188
+ case 'format_shotlist': {
189
+ if (!args.shotList) {
190
+ return { success: false, error: 'format_shotlist requires shotList parameter.' };
191
+ }
192
+ const formatted = formatShotListHtml(args.shotList.title, args.shotList.shots);
193
+ if (args.outputPath) {
194
+ const target = resolveUserPath(args.outputPath);
195
+ fs.mkdirSync(path.dirname(target), { recursive: true });
196
+ fs.writeFileSync(target, formatted.html, 'utf8');
197
+ }
198
+ return {
199
+ success: true,
200
+ html: formatted.html,
201
+ outputPath: args.outputPath ? resolveUserPath(args.outputPath) : undefined
202
+ };
203
+ }
204
+ case 'analyze_vo': {
205
+ if (!args.voBlocks || !Array.isArray(args.voBlocks)) {
206
+ return { success: false, error: 'analyze_vo requires voBlocks array.' };
207
+ }
208
+ const res = analyzeVoiceOverTiming(args.voBlocks);
209
+ return { success: true, analysis: res };
210
+ }
211
+ default:
212
+ return { success: false, error: `Unsupported document_forge action: ${action}` };
213
+ }
214
+ }
215
+ };
@@ -0,0 +1,265 @@
1
+ /*
2
+ * Copyright (c) 2026 Causyne.
3
+ * SPDX-License-Identifier: BUSL-1.1
4
+ *
5
+ * Causyne Document Forge Tool Wrapper.
6
+ * Bridges HTML/CSS-first publishing, headless PDF compilation, visual page QA,
7
+ * anti-slop compliance auditing, and Hollywood screenplay & shot list generation.
8
+ */
9
+
10
+ import fs from 'node:fs';
11
+ import path from 'node:path';
12
+ import {
13
+ compileDocumentHtml,
14
+ renderHtmlToPdf,
15
+ inspectPdfPages,
16
+ type FormalDocumentSpec
17
+ } from '../documentEngine/documentForge.js';
18
+ import {
19
+ formatScreenplayHtml,
20
+ formatShotListHtml,
21
+ analyzeVoiceOverTiming,
22
+ type ScreenplaySpec,
23
+ type ProductionShot,
24
+ type VoBlock
25
+ } from '../documentEngine/screenplayFormatter.js';
26
+ import {
27
+ auditTextContent,
28
+ auditBudgetArithmetic,
29
+ type ArithmeticBudgetSpec
30
+ } from '../documentEngine/antiSlopAuditor.js';
31
+ import { resolveUserPath } from '../utils/paths.js';
32
+
33
+ export interface DocumentForgeArgs {
34
+ action?:
35
+ | 'compile_document'
36
+ | 'render_pdf'
37
+ | 'inspect_pages'
38
+ | 'audit_text'
39
+ | 'audit_budget'
40
+ | 'format_screenplay'
41
+ | 'format_shotlist'
42
+ | 'analyze_vo';
43
+ documentSpec?: FormalDocumentSpec;
44
+ screenplaySpec?: ScreenplaySpec;
45
+ shotList?: { title: string; shots: ProductionShot[] };
46
+ voBlocks?: VoBlock[];
47
+ budgetSpec?: ArithmeticBudgetSpec;
48
+ textContent?: string;
49
+ sourceHtmlPath?: string;
50
+ outputPdfPath?: string;
51
+ outputPath?: string;
52
+ inspectImagesDir?: string;
53
+ }
54
+
55
+ export const documentForgeTool = {
56
+ schema: {
57
+ name: 'document_forge',
58
+ description: 'Causyne Document Forge: Professional document & media production harness. Generates pixel-perfect A4/Letter publications via HTML/CSS-First Paged Media and Headless Chrome, renders vector PDFs (<2MB compliant), extracts page images for visual QA ("Lihat Hasilnya"), audits anti-slop and math integrity, formats Hollywood Courier 12pt screenplays, creates production shot lists, and times VO narration.',
59
+ parameters: {
60
+ type: 'object',
61
+ properties: {
62
+ action: {
63
+ type: 'string',
64
+ enum: [
65
+ 'compile_document',
66
+ 'render_pdf',
67
+ 'inspect_pages',
68
+ 'audit_text',
69
+ 'audit_budget',
70
+ 'format_screenplay',
71
+ 'format_shotlist',
72
+ 'analyze_vo'
73
+ ],
74
+ description: 'Action to execute.'
75
+ },
76
+ documentSpec: {
77
+ type: 'object',
78
+ properties: {
79
+ title: { type: 'string' },
80
+ subtitle: { type: 'string' },
81
+ categoryOrKicker: { type: 'string' },
82
+ authorOrProposer: { type: 'string' },
83
+ palette: { type: 'string', enum: ['corporate', 'editorial_dair', 'minimal_clean'] },
84
+ includeCover: { type: 'boolean' }
85
+ },
86
+ description: 'Formal publication spec with sections, tables, and palette (corporate, editorial_dair, minimal_clean).'
87
+ },
88
+ screenplaySpec: {
89
+ type: 'object',
90
+ properties: {
91
+ title: { type: 'string' },
92
+ author: { type: 'string' },
93
+ contact: { type: 'string' }
94
+ },
95
+ description: 'Hollywood screenplay spec with scene headings, action, characters, dialogue, and transitions.'
96
+ },
97
+ shotList: {
98
+ type: 'object',
99
+ properties: {
100
+ title: { type: 'string' },
101
+ shots: {
102
+ type: 'array',
103
+ items: {
104
+ type: 'object',
105
+ properties: {
106
+ shotNumber: { type: 'number' },
107
+ framing: { type: 'string' },
108
+ subject: { type: 'string' },
109
+ action: { type: 'string' },
110
+ durationSeconds: { type: 'number' }
111
+ }
112
+ }
113
+ }
114
+ },
115
+ description: 'Director production shot list with framing, lenses, camera movements, and audio cues.'
116
+ },
117
+ voBlocks: {
118
+ type: 'array',
119
+ items: {
120
+ type: 'object',
121
+ properties: {
122
+ id: { type: 'string' },
123
+ timecode: { type: 'string' },
124
+ text: { type: 'string' },
125
+ targetDurationSeconds: { type: 'number' }
126
+ }
127
+ },
128
+ description: 'Voiceover blocks with timecode and target durations for WPM pacing analysis.'
129
+ },
130
+ budgetSpec: {
131
+ type: 'object',
132
+ properties: {
133
+ grossUpTaxRate: { type: 'number' },
134
+ targetGross: { type: 'number' }
135
+ },
136
+ description: 'Budget specification for arithmetic validation and tax gross-up verification.'
137
+ },
138
+ textContent: {
139
+ type: 'string',
140
+ description: 'Raw text or HTML content to audit for AI tropes, long em-dashes, and placeholder brackets.'
141
+ },
142
+ sourceHtmlPath: {
143
+ type: 'string',
144
+ description: 'Path to HTML file to render into PDF via Headless Chrome.'
145
+ },
146
+ outputPdfPath: {
147
+ type: 'string',
148
+ description: 'Destination PDF file path.'
149
+ },
150
+ outputPath: {
151
+ type: 'string',
152
+ description: 'Destination HTML or JSON output file path.'
153
+ },
154
+ inspectImagesDir: {
155
+ type: 'string',
156
+ description: 'Directory to save extracted page images for visual inspection.'
157
+ }
158
+ }
159
+ }
160
+ },
161
+
162
+ async execute(args: DocumentForgeArgs = {}, _context: any = {}): Promise<{ success: boolean; [key: string]: any }> {
163
+ const action = args.action || 'compile_document';
164
+
165
+ switch (action) {
166
+ case 'compile_document': {
167
+ if (!args.documentSpec) {
168
+ return { success: false, error: 'compile_document requires documentSpec.' };
169
+ }
170
+ const compiled = compileDocumentHtml(args.documentSpec);
171
+ if (args.outputPath) {
172
+ const target = resolveUserPath(args.outputPath);
173
+ fs.mkdirSync(path.dirname(target), { recursive: true });
174
+ fs.writeFileSync(target, compiled.html, 'utf8');
175
+ }
176
+ return {
177
+ success: true,
178
+ html: compiled.html,
179
+ outputPath: args.outputPath ? resolveUserPath(args.outputPath) : undefined
180
+ };
181
+ }
182
+
183
+ case 'render_pdf': {
184
+ if (!args.sourceHtmlPath || !args.outputPdfPath) {
185
+ return { success: false, error: 'render_pdf requires sourceHtmlPath and outputPdfPath.' };
186
+ }
187
+ const src = resolveUserPath(args.sourceHtmlPath);
188
+ const out = resolveUserPath(args.outputPdfPath);
189
+ const res = await renderHtmlToPdf(src, out);
190
+ return res;
191
+ }
192
+
193
+ case 'inspect_pages': {
194
+ if (!args.outputPdfPath) {
195
+ return { success: false, error: 'inspect_pages requires outputPdfPath.' };
196
+ }
197
+ const pdf = resolveUserPath(args.outputPdfPath);
198
+ const imgDir = args.inspectImagesDir ? resolveUserPath(args.inspectImagesDir) : undefined;
199
+ const res = await inspectPdfPages(pdf, imgDir);
200
+ return res;
201
+ }
202
+
203
+ case 'audit_text': {
204
+ if (!args.textContent) {
205
+ return { success: false, error: 'audit_text requires textContent parameter.' };
206
+ }
207
+ const res = auditTextContent(args.textContent);
208
+ return { success: true, ...res };
209
+ }
210
+
211
+ case 'audit_budget': {
212
+ if (!args.budgetSpec) {
213
+ return { success: false, error: 'audit_budget requires budgetSpec parameter.' };
214
+ }
215
+ const res = auditBudgetArithmetic(args.budgetSpec);
216
+ return { success: true, ...res };
217
+ }
218
+
219
+ case 'format_screenplay': {
220
+ if (!args.screenplaySpec) {
221
+ return { success: false, error: 'format_screenplay requires screenplaySpec.' };
222
+ }
223
+ const formatted = formatScreenplayHtml(args.screenplaySpec);
224
+ if (args.outputPath) {
225
+ const target = resolveUserPath(args.outputPath);
226
+ fs.mkdirSync(path.dirname(target), { recursive: true });
227
+ fs.writeFileSync(target, formatted.html, 'utf8');
228
+ }
229
+ return {
230
+ success: true,
231
+ html: formatted.html,
232
+ outputPath: args.outputPath ? resolveUserPath(args.outputPath) : undefined
233
+ };
234
+ }
235
+
236
+ case 'format_shotlist': {
237
+ if (!args.shotList) {
238
+ return { success: false, error: 'format_shotlist requires shotList parameter.' };
239
+ }
240
+ const formatted = formatShotListHtml(args.shotList.title, args.shotList.shots);
241
+ if (args.outputPath) {
242
+ const target = resolveUserPath(args.outputPath);
243
+ fs.mkdirSync(path.dirname(target), { recursive: true });
244
+ fs.writeFileSync(target, formatted.html, 'utf8');
245
+ }
246
+ return {
247
+ success: true,
248
+ html: formatted.html,
249
+ outputPath: args.outputPath ? resolveUserPath(args.outputPath) : undefined
250
+ };
251
+ }
252
+
253
+ case 'analyze_vo': {
254
+ if (!args.voBlocks || !Array.isArray(args.voBlocks)) {
255
+ return { success: false, error: 'analyze_vo requires voBlocks array.' };
256
+ }
257
+ const res = analyzeVoiceOverTiming(args.voBlocks);
258
+ return { success: true, analysis: res };
259
+ }
260
+
261
+ default:
262
+ return { success: false, error: `Unsupported document_forge action: ${action}` };
263
+ }
264
+ }
265
+ };
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Causyne Peer Agent Tool
3
+ *
4
+ * Delegates a bounded task to an external ACP agent that the user configured
5
+ * in `~/.causyne/peers.json`. The model chooses a profile and writes a prompt;
6
+ * it never chooses the executable, arguments, working directory, or
7
+ * environment. Those come from the user's file and nowhere else.
8
+ */
9
+ import { AcpClient } from '../acp/client.js';
10
+ import { getPeerProfile, loadPeerProfiles, peerEnvironment } from '../acp/peerProfiles.js';
11
+ const MAX_PROMPT_CHARS = 100_000;
12
+ const READ_ONLY_KINDS = new Set(['read', 'search', 'fetch', 'think']);
13
+ function selectPermission(profile, request) {
14
+ const kind = request.toolCall.kind;
15
+ const readLike = typeof kind === 'string' && READ_ONLY_KINDS.has(kind);
16
+ if (profile.policy === 'read-only' && !readLike) {
17
+ const reject = request.options.find((option) => option.kind === 'reject_once' || option.kind === 'reject_always');
18
+ return reject ? { outcome: 'selected', optionId: reject.optionId } : { outcome: 'cancelled' };
19
+ }
20
+ const allow = request.options.find((option) => option.kind === 'allow_once' || option.kind === 'allow_always');
21
+ return allow ? { outcome: 'selected', optionId: allow.optionId } : { outcome: 'cancelled' };
22
+ }
23
+ export const peerAgentTool = {
24
+ schema: {
25
+ name: 'peer_agent_delegate',
26
+ description: 'Delegate a bounded task to a peer ACP agent that the user configured in the peers file. The model supplies the peer id and the prompt only; the executable, arguments, working directory, environment, and permission policy come from the user configuration. Peers default to rejecting mutating permission requests; the external agent decides how to honor that policy.',
27
+ parameters: {
28
+ type: 'object',
29
+ properties: {
30
+ peer: {
31
+ type: 'string',
32
+ description: 'Id of a configured peer profile from the peers file.'
33
+ },
34
+ prompt: {
35
+ type: 'string',
36
+ description: 'Task instructions and context for the peer agent.'
37
+ },
38
+ timeoutSeconds: {
39
+ type: 'number',
40
+ description: 'Optional shorter timeout in seconds, bounded by the profile timeout.'
41
+ }
42
+ },
43
+ required: ['peer', 'prompt']
44
+ }
45
+ },
46
+ async execute(args) {
47
+ const peerId = String(args.peer || '').trim();
48
+ const prompt = String(args.prompt || '').trim().slice(0, MAX_PROMPT_CHARS);
49
+ if (!peerId) {
50
+ const configured = loadPeerProfiles().map((profile) => profile.id);
51
+ return {
52
+ success: false,
53
+ error: configured.length > 0
54
+ ? `A peer id is required. Configured peers: ${configured.join(', ')}.`
55
+ : 'No peer profiles are configured. Add one to the peers file first.'
56
+ };
57
+ }
58
+ if (!prompt) {
59
+ return { success: false, error: 'A prompt is required for peer delegation.' };
60
+ }
61
+ const profile = getPeerProfile(peerId);
62
+ if (!profile) {
63
+ return { success: false, error: `Unknown peer profile: ${peerId}.` };
64
+ }
65
+ const requested = typeof args.timeoutSeconds === 'number' && Number.isFinite(args.timeoutSeconds)
66
+ ? Math.round(args.timeoutSeconds)
67
+ : profile.timeoutSeconds;
68
+ const timeoutSeconds = Math.max(5, Math.min(Math.min(profile.timeoutSeconds, requested), 1800));
69
+ const client = new AcpClient({
70
+ command: profile.command,
71
+ args: profile.args,
72
+ cwd: profile.cwd ?? process.cwd(),
73
+ env: peerEnvironment(profile),
74
+ promptTimeoutMs: timeoutSeconds * 1000,
75
+ permissionHandler: async (request) => selectPermission(profile, request)
76
+ });
77
+ try {
78
+ await client.start();
79
+ const sessionId = await client.newSession(profile.cwd ?? process.cwd());
80
+ const result = await client.prompt(sessionId, prompt);
81
+ if (result.stopReason === 'cancelled') {
82
+ return { success: false, error: 'The peer agent cancelled the task.' };
83
+ }
84
+ if (result.stopReason === 'refusal') {
85
+ return { success: false, error: 'The peer agent refused the task.' };
86
+ }
87
+ return {
88
+ success: true,
89
+ output: result.text.trim() || 'Peer agent completed the task without a written summary.'
90
+ };
91
+ }
92
+ catch (error) {
93
+ return {
94
+ success: false,
95
+ error: error instanceof Error ? error.message : 'Peer agent delegation failed.'
96
+ };
97
+ }
98
+ finally {
99
+ await client.close();
100
+ }
101
+ }
102
+ };