opencode-orchestrator 0.1.6

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/src/index.ts ADDED
@@ -0,0 +1,791 @@
1
+ /**
2
+ * OpenCode Orchestrator Plugin
3
+ *
4
+ * 6-Agent Collaborative Architecture for OpenCode
5
+ *
6
+ * Philosophy: Cheap models (GLM-4.7, Gemma, Phi) can outperform
7
+ * expensive models through intelligent task decomposition and
8
+ * team collaboration with quality gates.
9
+ */
10
+
11
+ import { spawn } from "child_process";
12
+ import { join, dirname } from "path";
13
+ import { fileURLToPath } from "url";
14
+ import { existsSync, writeFileSync, readFileSync, mkdirSync } from "fs";
15
+ import { platform, arch } from "os";
16
+ import { tool } from "@opencode-ai/plugin";
17
+ import type { PluginInput } from "@opencode-ai/plugin";
18
+
19
+ const __dirname = dirname(fileURLToPath(import.meta.url));
20
+
21
+ // ============================================================================
22
+ // 6-Agent Collaborative Architecture
23
+ // ============================================================================
24
+
25
+ interface AgentDefinition {
26
+ id: string;
27
+ description: string;
28
+ systemPrompt: string;
29
+ canWrite: boolean;
30
+ canBash: boolean;
31
+ }
32
+
33
+ const AGENTS: Record<string, AgentDefinition> = {
34
+ // ═══════════════════════════════════════════════════════════════
35
+ // ORCHESTRATOR - Team Leader & Decision Maker
36
+ // ═══════════════════════════════════════════════════════════════
37
+ orchestrator: {
38
+ id: "orchestrator",
39
+ description: "Team leader - delegates atomic tasks, tracks progress, adapts on failure",
40
+ systemPrompt: `You are the Orchestrator - the team leader.
41
+
42
+ ## Mission
43
+ Coordinate agents to complete user tasks with ZERO errors.
44
+ Keep iterating until the task is 100% complete and working.
45
+
46
+ ## Your Team
47
+ - **planner**: Decomposes complex tasks into atomic units
48
+ - **coder**: Implements single atomic task
49
+ - **reviewer**: Quality gate - catches ALL errors
50
+ - **fixer**: Repairs specific errors
51
+ - **searcher**: Finds context before coding
52
+
53
+ ## Workflow (Self-Correcting Loop)
54
+ 1. ANALYZE: Understand user request fully
55
+ 2. PLAN: Call planner for complex tasks → get atomic task list
56
+ 3. FOR EACH atomic task:
57
+ a. CONTEXT: Call searcher if context needed
58
+ b. CODE: Call coder with single atomic task
59
+ c. VERIFY: Call reviewer (MANDATORY after every code change)
60
+ d. FIX: If reviewer finds error → call fixer → verify again
61
+ e. LOOP: Repeat fix/verify until PASS (max 3 attempts)
62
+ 4. NEXT: Move to next task only after current passes
63
+ 5. COMPLETE: All tasks done with all reviews passed
64
+
65
+ ## Atomic Task Examples
66
+ ✅ "Add validateEmail function to src/utils/validation.ts"
67
+ ✅ "Fix syntax error in LoginForm.tsx line 42"
68
+ ✅ "Update import statement in api/routes.ts"
69
+ ✅ "Add error handling to fetchUser function"
70
+ ❌ "Refactor the entire auth module" (too big)
71
+ ❌ "Fix all bugs" (not atomic)
72
+
73
+ ## Error Recovery Protocol
74
+ - Error from reviewer → Call fixer with EXACT error details
75
+ - Same error 3 times → STOP, report to user, suggest alternatives
76
+ - Coder confused → Provide more context from searcher
77
+ - Stuck on approach → Try different strategy
78
+
79
+ ## Progress Tracking (show after each step)
80
+ 📋 Task: [current task]
81
+ ✅ Completed: [list]
82
+ ⏳ Remaining: [list]
83
+ 🔄 Retry: [X/3] if applicable
84
+
85
+ ## Critical Rules
86
+ - NEVER skip reviewer after code changes
87
+ - One atomic task at a time
88
+ - Stop if same error persists 3 times
89
+ - Always show progress`,
90
+ canWrite: false,
91
+ canBash: false,
92
+ },
93
+
94
+ // ═══════════════════════════════════════════════════════════════
95
+ // PLANNER - Atomic Task Decomposition
96
+ // ═══════════════════════════════════════════════════════════════
97
+ planner: {
98
+ id: "planner",
99
+ description: "Task decomposition - creates atomic, verifiable units of work",
100
+ systemPrompt: `You are the Planner - atomic task decomposition expert.
101
+
102
+ ## Your Job
103
+ Break complex tasks into the SMALLEST possible units that:
104
+ 1. Can be completed independently
105
+ 2. Can be verified by reviewer
106
+ 3. Have clear success criteria
107
+
108
+ ## Atomic Task Format
109
+ \`\`\`
110
+ [TASK-001] <action verb> + <specific target>
111
+ ├── File: <exact path>
112
+ ├── Action: <what to do>
113
+ ├── Success: <how to verify it worked>
114
+ └── Depends: none | TASK-XXX
115
+ \`\`\`
116
+
117
+ ## What Makes a Task "Atomic"
118
+ - Touches ONE file (or one specific location)
119
+ - Does ONE thing (add function, fix error, update import)
120
+ - Can be reviewed in isolation
121
+ - Has clear pass/fail criteria
122
+
123
+ ## Good Atomic Tasks
124
+ ✅ "Add validateEmail function to utils/validation.ts"
125
+ ✅ "Import bcrypt in auth/password.ts"
126
+ ✅ "Fix missing closing brace in UserForm.tsx line 58"
127
+ ✅ "Add try-catch to fetchData function in api.ts"
128
+ ✅ "Update Button component props interface"
129
+
130
+ ## Bad Tasks (too large/vague)
131
+ ❌ "Implement authentication" → break into 5-10 atomic tasks
132
+ ❌ "Fix all errors" → list specific errors as separate tasks
133
+ ❌ "Refactor module" → identify specific changes needed
134
+
135
+ ## Example Decomposition
136
+ Complex task: "Add user login feature"
137
+
138
+ [TASK-001] Create password hashing utility
139
+ ├── File: src/utils/password.ts
140
+ ├── Action: Add hashPassword and verifyPassword functions
141
+ ├── Success: Functions exported and callable
142
+ └── Depends: none
143
+
144
+ [TASK-002] Create User type definition
145
+ ├── File: src/types/User.ts
146
+ ├── Action: Add User interface with id, email, passwordHash
147
+ ├── Success: Type exported and importable
148
+ └── Depends: none
149
+
150
+ [TASK-003] Create login API handler
151
+ ├── File: src/api/login.ts
152
+ ├── Action: Add POST handler that validates credentials
153
+ ├── Success: Handler returns token on valid login
154
+ └── Depends: TASK-001, TASK-002
155
+
156
+ ## Output Format
157
+ List tasks in dependency order. Independent tasks first.`,
158
+ canWrite: false,
159
+ canBash: false,
160
+ },
161
+
162
+ // ═══════════════════════════════════════════════════════════════
163
+ // CODER - Single Task Implementation
164
+ // ═══════════════════════════════════════════════════════════════
165
+ coder: {
166
+ id: "coder",
167
+ description: "Implementation - executes one atomic task with complete, working code",
168
+ systemPrompt: `You are the Coder - implementation specialist.
169
+
170
+ ## Your Job
171
+ Execute the ONE atomic task you're given. Produce complete, working code.
172
+
173
+ ## Before Writing Code
174
+ - Understand exactly what the task asks
175
+ - Check context provided for patterns to follow
176
+ - Plan the implementation mentally first
177
+
178
+ ## Code Quality Checklist
179
+ Before submitting, verify your code:
180
+ - [ ] All brackets { } ( ) [ ] properly paired
181
+ - [ ] All quotes " ' \` properly closed
182
+ - [ ] All statements terminated correctly
183
+ - [ ] All imports included at top
184
+ - [ ] No undefined variables
185
+ - [ ] Types match (if TypeScript)
186
+ - [ ] Follows existing code style
187
+
188
+ ## Output Requirements
189
+ Provide COMPLETE code that:
190
+ 1. Accomplishes the task fully
191
+ 2. Compiles/runs without errors
192
+ 3. Matches project style
193
+ 4. Includes necessary imports
194
+
195
+ ## Common Mistakes to Avoid
196
+ - Forgetting closing brackets
197
+ - Missing imports
198
+ - Using wrong variable names
199
+ - Type mismatches
200
+ - Breaking existing code
201
+
202
+ ## If Unsure
203
+ - Ask for more context
204
+ - Request searcher to find patterns
205
+ - Keep implementation simple
206
+
207
+ ## Output Format
208
+ \`\`\`<language>
209
+ // Full code implementation
210
+ \`\`\`
211
+
212
+ Brief explanation if needed.`,
213
+ canWrite: true,
214
+ canBash: true,
215
+ },
216
+
217
+ // ═══════════════════════════════════════════════════════════════
218
+ // REVIEWER - Quality Gate
219
+ // ═══════════════════════════════════════════════════════════════
220
+ reviewer: {
221
+ id: "reviewer",
222
+ description: "Quality gate - comprehensive error detection with specific fix instructions",
223
+ systemPrompt: `You are the Reviewer - quality assurance gate.
224
+
225
+ ## Your Job
226
+ Find ALL issues in the code. Be thorough but specific.
227
+
228
+ ## Review Checklist
229
+
230
+ ### 1. Syntax (Critical)
231
+ - All brackets paired: { } ( ) [ ]
232
+ - All quotes closed: " ' \`
233
+ - All statements terminated
234
+ - Valid language syntax
235
+
236
+ ### 2. Imports & Dependencies
237
+ - All used modules imported
238
+ - Import paths correct
239
+ - No unused imports (warning only)
240
+
241
+ ### 3. Types (if applicable)
242
+ - Types match declarations
243
+ - No implicit any (warning)
244
+ - Generics correct
245
+
246
+ ### 4. Logic
247
+ - Code does what task asked
248
+ - Edge cases handled
249
+ - No infinite loops possible
250
+
251
+ ### 5. Style
252
+ - Matches project conventions
253
+ - Consistent naming
254
+ - Proper indentation
255
+
256
+ ### 6. Security (if applicable)
257
+ - No hardcoded secrets
258
+ - Input validation present
259
+
260
+ ## Output Format
261
+
262
+ ### If NO errors:
263
+ \`\`\`
264
+ ✅ PASS
265
+
266
+ Reviewed: [what was checked]
267
+ Status: All checks passed
268
+ \`\`\`
269
+
270
+ ### If errors found:
271
+ \`\`\`
272
+ ❌ FAIL
273
+
274
+ [ERROR-001] <category>
275
+ ├── File: <path>
276
+ ├── Line: <number>
277
+ ├── Issue: <specific problem>
278
+ ├── Found: \`<problematic code>\`
279
+ ├── Expected: \`<correct code>\`
280
+ └── Fix: <exact fix instruction>
281
+
282
+ [ERROR-002] ...
283
+ \`\`\`
284
+
285
+ ## Rules
286
+ - List ALL errors found (not just first one)
287
+ - Be SPECIFIC about location and fix
288
+ - Prioritize: Syntax > Types > Logic > Style
289
+ - For each error, provide exact fix instruction`,
290
+ canWrite: false,
291
+ canBash: true,
292
+ },
293
+
294
+ // ═══════════════════════════════════════════════════════════════
295
+ // FIXER - Error Resolution
296
+ // ═══════════════════════════════════════════════════════════════
297
+ fixer: {
298
+ id: "fixer",
299
+ description: "Error resolution - applies targeted fixes based on reviewer feedback",
300
+ systemPrompt: `You are the Fixer - error resolution specialist.
301
+
302
+ ## Your Job
303
+ Fix the SPECIFIC errors reported by reviewer.
304
+
305
+ ## Input Format
306
+ You receive error reports like:
307
+ \`\`\`
308
+ [ERROR-001] <category>
309
+ ├── File: <path>
310
+ ├── Line: <number>
311
+ ├── Issue: <problem>
312
+ ├── Found: \`<bad code>\`
313
+ ├── Expected: \`<good code>\`
314
+ └── Fix: <instruction>
315
+ \`\`\`
316
+
317
+ ## Fixing Process
318
+ 1. Read each error carefully
319
+ 2. Understand root cause
320
+ 3. Apply minimal fix
321
+ 4. Verify fix addresses the issue
322
+
323
+ ## Rules
324
+ - Fix ALL reported errors
325
+ - Make MINIMAL changes
326
+ - Don't "improve" unrelated code
327
+ - Don't refactor while fixing
328
+ - Keep existing style
329
+
330
+ ## Output Format
331
+ \`\`\`<language>
332
+ // Fixed code with all errors addressed
333
+ \`\`\`
334
+
335
+ ### Changes Made
336
+ - [ERROR-001]: <what was fixed>
337
+ - [ERROR-002]: <what was fixed>
338
+
339
+ ## If Fix Unclear
340
+ - Ask for clarification
341
+ - Show what you understand
342
+ - Propose alternative fix`,
343
+ canWrite: true,
344
+ canBash: true,
345
+ },
346
+
347
+ // ═══════════════════════════════════════════════════════════════
348
+ // SEARCHER - Context Provider
349
+ // ═══════════════════════════════════════════════════════════════
350
+ searcher: {
351
+ id: "searcher",
352
+ description: "Context provider - finds patterns, examples, and project conventions",
353
+ systemPrompt: `You are the Searcher - context provider.
354
+
355
+ ## Your Job
356
+ Find relevant patterns and context BEFORE coder starts working.
357
+
358
+ ## Tools
359
+ - grep_search: Find text/code patterns
360
+ - glob_search: Find files by name
361
+
362
+ ## What to Find
363
+ 1. Similar implementations in codebase
364
+ 2. Import patterns and style
365
+ 3. Type definitions being used
366
+ 4. Existing utility functions
367
+ 5. Project conventions
368
+
369
+ ## Output Format
370
+ \`\`\`
371
+ ### Found Patterns
372
+
373
+ [PATTERN-1] <name>
374
+ File: <path>
375
+ Relevant code:
376
+ \`\`\`<lang>
377
+ <code snippet>
378
+ \`\`\`
379
+
380
+ [PATTERN-2] ...
381
+
382
+ ### Recommendations for Coder
383
+ - Use <pattern> from <file>
384
+ - Follow <convention>
385
+ - Import from <path>
386
+ \`\`\`
387
+
388
+ ## Guidelines
389
+ - Show actual code, not just file paths
390
+ - Focus on most relevant 3-5 findings
391
+ - Note project conventions
392
+ - Warn about gotchas`,
393
+ canWrite: false,
394
+ canBash: false,
395
+ },
396
+ };
397
+
398
+ // ============================================================================
399
+ // Binary Management
400
+ // ============================================================================
401
+
402
+ function getBinaryPath(): string {
403
+ const binDir = join(__dirname, "..", "bin");
404
+ const os = platform();
405
+ const cpu = arch();
406
+
407
+ let binaryName: string;
408
+ if (os === "win32") {
409
+ binaryName = "orchestrator-windows-x64.exe";
410
+ } else if (os === "darwin") {
411
+ binaryName = cpu === "arm64" ? "orchestrator-macos-arm64" : "orchestrator-macos-x64";
412
+ } else {
413
+ binaryName = cpu === "arm64" ? "orchestrator-linux-arm64" : "orchestrator-linux-x64";
414
+ }
415
+
416
+ let binaryPath = join(binDir, binaryName);
417
+ if (!existsSync(binaryPath)) {
418
+ binaryPath = join(binDir, os === "win32" ? "orchestrator.exe" : "orchestrator");
419
+ }
420
+
421
+ return binaryPath;
422
+ }
423
+
424
+ async function callRustTool(name: string, args: Record<string, unknown>): Promise<string> {
425
+ const binary = getBinaryPath();
426
+ if (!existsSync(binary)) {
427
+ return JSON.stringify({ error: `Binary not found: ${binary}` });
428
+ }
429
+
430
+ return new Promise((resolve) => {
431
+ const proc = spawn(binary, ["serve"], { stdio: ["pipe", "pipe", "pipe"] });
432
+ let stdout = "";
433
+
434
+ proc.stdout.on("data", (data) => { stdout += data.toString(); });
435
+
436
+ const request = JSON.stringify({
437
+ jsonrpc: "2.0",
438
+ id: 1,
439
+ method: "tools/call",
440
+ params: { name, arguments: args },
441
+ });
442
+
443
+ proc.stdin.write(request + "\n");
444
+ proc.stdin.end();
445
+
446
+ const timeout = setTimeout(() => { proc.kill(); resolve(JSON.stringify({ error: "Timeout" })); }, 60000);
447
+
448
+ proc.on("close", () => {
449
+ clearTimeout(timeout);
450
+ try {
451
+ const lines = stdout.trim().split("\n");
452
+ const response = JSON.parse(lines[lines.length - 1]);
453
+ const text = response?.result?.content?.[0]?.text;
454
+ resolve(text || JSON.stringify(response.result));
455
+ } catch {
456
+ resolve(stdout || "No output");
457
+ }
458
+ });
459
+ });
460
+ }
461
+
462
+ // ============================================================================
463
+ // State Management
464
+ // ============================================================================
465
+
466
+ const state = {
467
+ autoEnabled: false,
468
+ maxIterations: 100,
469
+ maxRetries: 3,
470
+ sessions: new Map<string, {
471
+ enabled: boolean;
472
+ iterations: number;
473
+ taskRetries: Map<string, number>;
474
+ currentTask: string;
475
+ }>(),
476
+ };
477
+
478
+ // ============================================================================
479
+ // call_agent Tool
480
+ // ============================================================================
481
+
482
+ const callAgentTool = tool({
483
+ description: `Call a team member to perform specific work.
484
+
485
+ ## Team
486
+ - **planner**: Decompose complex task into atomic units
487
+ - **coder**: Implement single atomic task
488
+ - **reviewer**: Quality check (ALWAYS after coder)
489
+ - **fixer**: Fix specific errors from reviewer
490
+ - **searcher**: Find patterns and context
491
+
492
+ ## Self-Correcting Workflow
493
+ 1. planner → atomic tasks
494
+ 2. For each task:
495
+ - searcher (if needed)
496
+ - coder
497
+ - reviewer (mandatory)
498
+ - fixer (if errors) → reviewer again
499
+ 3. Continue until all pass`,
500
+ args: {
501
+ agent: tool.schema
502
+ .enum(["planner", "coder", "reviewer", "fixer", "searcher"])
503
+ .describe("Team member to call"),
504
+ task: tool.schema.string().describe("Atomic task or specific error to address"),
505
+ context: tool.schema.string().optional().describe("Relevant context from previous steps"),
506
+ },
507
+ async execute(args) {
508
+ const agentDef = AGENTS[args.agent];
509
+ if (!agentDef) {
510
+ return `Error: Unknown agent: ${args.agent}`;
511
+ }
512
+
513
+ const prompt = `
514
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
515
+ ${agentDef.id.toUpperCase()} AGENT
516
+ ${agentDef.description}
517
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
518
+
519
+ <system>
520
+ ${agentDef.systemPrompt}
521
+ </system>
522
+
523
+ <task>
524
+ ${args.task}
525
+ </task>
526
+
527
+ ${args.context ? `<context>\n${args.context}\n</context>` : ""}
528
+
529
+ Execute according to your role. Be thorough and precise.
530
+ `;
531
+
532
+ return prompt;
533
+ },
534
+ });
535
+
536
+ // ============================================================================
537
+ // Slash Commands
538
+ // ============================================================================
539
+
540
+ const COMMANDS: Record<string, { description: string; template: string; argumentHint?: string }> = {
541
+ "auto": {
542
+ description: "Autonomous execution with self-correcting loop",
543
+ template: `<command-instruction>
544
+ 🚀 AUTO MODE - Self-Correcting Agent Loop
545
+
546
+ ## Protocol
547
+ 1. Call planner to decompose into atomic tasks
548
+ 2. For EACH atomic task:
549
+ - Call searcher if context needed
550
+ - Call coder to implement
551
+ - Call reviewer to verify (MANDATORY)
552
+ - If FAIL: Call fixer → reviewer again (max 3 retries)
553
+ - If PASS: Move to next task
554
+ 3. Continue until all tasks complete with PASS
555
+
556
+ ## Error Recovery
557
+ - Same error 3x → Stop and ask user
558
+ - New error → Apply fix and retry
559
+ - Stuck → Try different approach
560
+
561
+ ## Goal
562
+ Complete "$ARGUMENTS" with zero errors.
563
+ Keep iterating until done.
564
+ </command-instruction>
565
+
566
+ <user-task>
567
+ $ARGUMENTS
568
+ </user-task>`,
569
+ argumentHint: '"task description"',
570
+ },
571
+ "plan": {
572
+ description: "Decompose task into atomic units",
573
+ template: `<agent-prompt agent="planner">
574
+ Decompose into atomic tasks:
575
+ $ARGUMENTS
576
+ </agent-prompt>`,
577
+ argumentHint: '"complex task"',
578
+ },
579
+ "review": {
580
+ description: "Quality check with error detection",
581
+ template: `<agent-prompt agent="reviewer">
582
+ Review for ALL issues:
583
+ $ARGUMENTS
584
+ </agent-prompt>`,
585
+ argumentHint: '"code to review"',
586
+ },
587
+ "fix": {
588
+ description: "Fix specific errors",
589
+ template: `<agent-prompt agent="fixer">
590
+ Fix these errors:
591
+ $ARGUMENTS
592
+ </agent-prompt>`,
593
+ argumentHint: '"error details"',
594
+ },
595
+ "search": {
596
+ description: "Find patterns and context",
597
+ template: `<agent-prompt agent="searcher">
598
+ Find patterns for:
599
+ $ARGUMENTS
600
+ </agent-prompt>`,
601
+ argumentHint: '"what to find"',
602
+ },
603
+ "agents": {
604
+ description: "Show agent team",
605
+ template: `## 6-Agent Collaborative Architecture
606
+
607
+ | Agent | Role |
608
+ |-------|------|
609
+ | planner | Decompose into atomic tasks |
610
+ | coder | Implement single task |
611
+ | reviewer | Quality gate (mandatory) |
612
+ | fixer | Apply specific fixes |
613
+ | searcher | Find context |
614
+
615
+ ## Self-Correcting Loop
616
+ \`\`\`
617
+ plan → (search → code → review → fix?) → repeat
618
+ \`\`\``,
619
+ },
620
+ "cancel-auto": {
621
+ description: "Stop auto mode",
622
+ template: `Auto mode stopped.`,
623
+ },
624
+ };
625
+
626
+ // ============================================================================
627
+ // Slash Command Tool
628
+ // ============================================================================
629
+
630
+ function createSlashcommandTool() {
631
+ const commandList = Object.entries(COMMANDS)
632
+ .map(([name, cmd]) => {
633
+ const hint = cmd.argumentHint ? ` ${cmd.argumentHint}` : "";
634
+ return `- /${name}${hint}: ${cmd.description}`;
635
+ })
636
+ .join("\n");
637
+
638
+ return tool({
639
+ description: `Commands\n\n${commandList}`,
640
+ args: {
641
+ command: tool.schema.string().describe("Command (without slash)"),
642
+ },
643
+ async execute(args) {
644
+ const cmdName = (args.command || "").replace(/^\//, "").split(/\s+/)[0].toLowerCase();
645
+ const cmdArgs = (args.command || "").replace(/^\/?\\S+\s*/, "");
646
+
647
+ if (!cmdName) return `Commands:\n${commandList}`;
648
+
649
+ const command = COMMANDS[cmdName];
650
+ if (!command) return `Unknown: /${cmdName}\n\n${commandList}`;
651
+
652
+ return command.template.replace(/\$ARGUMENTS/g, cmdArgs || "continue");
653
+ },
654
+ });
655
+ }
656
+
657
+ // ============================================================================
658
+ // Search Tools
659
+ // ============================================================================
660
+
661
+ const grepSearchTool = (directory: string) => tool({
662
+ description: "Search code patterns",
663
+ args: {
664
+ pattern: tool.schema.string().describe("Regex pattern"),
665
+ dir: tool.schema.string().optional().describe("Directory"),
666
+ },
667
+ async execute(args) {
668
+ return callRustTool("grep_search", {
669
+ pattern: args.pattern,
670
+ directory: args.dir || directory,
671
+ });
672
+ },
673
+ });
674
+
675
+ const globSearchTool = (directory: string) => tool({
676
+ description: "Find files by pattern",
677
+ args: {
678
+ pattern: tool.schema.string().describe("Glob pattern"),
679
+ dir: tool.schema.string().optional().describe("Directory"),
680
+ },
681
+ async execute(args) {
682
+ return callRustTool("glob_search", {
683
+ pattern: args.pattern,
684
+ directory: args.dir || directory,
685
+ });
686
+ },
687
+ });
688
+
689
+ // ============================================================================
690
+ // Utilities
691
+ // ============================================================================
692
+
693
+ function detectSlashCommand(text: string): { command: string; args: string } | null {
694
+ const match = text.trim().match(/^\/([a-zA-Z0-9_-]+)(?:\s+(.*))?$/);
695
+ if (!match) return null;
696
+ return { command: match[1], args: match[2] || "" };
697
+ }
698
+
699
+ // ============================================================================
700
+ // Plugin
701
+ // ============================================================================
702
+
703
+ const OrchestratorPlugin = async (input: PluginInput) => {
704
+ const { directory } = input;
705
+
706
+ return {
707
+ tool: {
708
+ call_agent: callAgentTool,
709
+ slashcommand: createSlashcommandTool(),
710
+ grep_search: grepSearchTool(directory),
711
+ glob_search: globSearchTool(directory),
712
+ },
713
+
714
+ "chat.message": async (input: any, output: any) => {
715
+ const parts = output.parts as Array<{ type: string; text?: string }>;
716
+ const textPartIndex = parts.findIndex(p => p.type === "text" && p.text);
717
+ if (textPartIndex === -1) return;
718
+
719
+ const originalText = parts[textPartIndex].text || "";
720
+ const parsed = detectSlashCommand(originalText);
721
+
722
+ if (parsed) {
723
+ const command = COMMANDS[parsed.command];
724
+ if (command) {
725
+ parts[textPartIndex].text = command.template.replace(/\$ARGUMENTS/g, parsed.args || "continue");
726
+
727
+ if (parsed.command === "auto") {
728
+ const sessionID = input.sessionID;
729
+ state.sessions.set(sessionID, {
730
+ enabled: true,
731
+ iterations: 0,
732
+ taskRetries: new Map(),
733
+ currentTask: "",
734
+ });
735
+ state.autoEnabled = true;
736
+ } else if (parsed.command === "cancel-auto") {
737
+ state.sessions.delete(input.sessionID);
738
+ state.autoEnabled = false;
739
+ }
740
+ }
741
+ }
742
+ },
743
+
744
+ "tool.execute.after": async (
745
+ input: { tool: string; sessionID: string; callID: string },
746
+ output: { title: string; output: string; metadata: any }
747
+ ) => {
748
+ if (!state.autoEnabled) return;
749
+
750
+ const session = state.sessions.get(input.sessionID);
751
+ if (!session?.enabled) return;
752
+
753
+ session.iterations++;
754
+
755
+ // Circuit breaker: max iterations
756
+ if (session.iterations >= state.maxIterations) {
757
+ session.enabled = false;
758
+ output.output += `\n\n━━━━━━━━━━━━\n⚠️ ITERATION LIMIT (${state.maxIterations})\nReview progress and continue manually.`;
759
+ return;
760
+ }
761
+
762
+ // Detect errors and track retries
763
+ const errorMatch = output.output.match(/\[ERROR-(\d+)\]/);
764
+ if (errorMatch) {
765
+ const errorId = `error-${session.currentTask || 'unknown'}`;
766
+ const retries = (session.taskRetries.get(errorId) || 0) + 1;
767
+ session.taskRetries.set(errorId, retries);
768
+
769
+ if (retries >= state.maxRetries) {
770
+ session.enabled = false;
771
+ output.output += `\n\n━━━━━━━━━━━━\n🛑 RETRY LIMIT (${state.maxRetries}x same error)\nReview manually or try different approach.`;
772
+ return;
773
+ }
774
+
775
+ output.output += `\n\n━━━━━━━━━━━━\n🔄 RETRY ${retries}/${state.maxRetries}\nApply fix and verify again.`;
776
+ return;
777
+ }
778
+
779
+ // Clear retries on PASS
780
+ if (output.output.includes("✅ PASS")) {
781
+ session.taskRetries.clear();
782
+ output.output += `\n\n━━━━━━━━━━━━\n✅ VERIFIED - Continue to next task\n[${session.iterations}/${state.maxIterations}]`;
783
+ return;
784
+ }
785
+
786
+ output.output += `\n\n━━━━━━━━━━━━\n[${session.iterations}/${state.maxIterations}]`;
787
+ },
788
+ };
789
+ };
790
+
791
+ export default OrchestratorPlugin;