opencode-orchestrator 1.2.58 โ 1.2.60
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/README.md +66 -115
- package/bin/orchestrator-linux-x64 +0 -0
- package/dist/core/notification/os-notify/platform.d.ts +0 -1
- package/dist/index.js +21 -9
- package/dist/plugin-handlers/interfaces/system-transform.d.ts +2 -2
- package/dist/scripts/postinstall.js +87 -26
- package/dist/scripts/preuninstall.js +44 -8
- package/package.json +12 -15
package/README.md
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
> ๐ **Also by the same author** โ [**pentesting**](https://www.npmjs.com/package/pentesting): Autonomous Penetration Testing AI Agent CLI ยท `npm install -g pentesting` ยท [Homepage](https://pentesting.agnusdei.kr)
|
|
4
|
+
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
1
9
|
<div align="center">
|
|
2
10
|
<img src="assets/logo.png" alt="logo" width="200" />
|
|
3
11
|
<h1>OpenCode Orchestrator</h1>
|
|
4
|
-
|
|
5
12
|
<p>Production-Grade Multi-Agent Orchestration Engine for High-Integrity Software Engineering</p>
|
|
6
13
|
|
|
7
14
|
[](LICENSE)
|
|
@@ -23,8 +30,6 @@ Inside an OpenCode environment:
|
|
|
23
30
|
|
|
24
31
|
---
|
|
25
32
|
|
|
26
|
-
---
|
|
27
|
-
|
|
28
33
|
## ๐ Engine Workflow
|
|
29
34
|
|
|
30
35
|
OpenCode Orchestrator utilizes a **Hub-and-Spoke Topology** with **Work-Stealing Queues** to execute complex engineering tasks through parallel, context-isolated sessions.
|
|
@@ -64,13 +69,24 @@ OpenCode Orchestrator utilizes a **Hub-and-Spoke Topology** with **Work-Stealing
|
|
|
64
69
|
|
|
65
70
|
---
|
|
66
71
|
|
|
72
|
+
## โก Elite Multi-Agent Swarm
|
|
73
|
+
|
|
74
|
+
| Agent | Expertise | Capability |
|
|
75
|
+
|:------|:----------|:-----------|
|
|
76
|
+
| **Commander** | Mission Hub | Session pooling, parallel thread control, state rehydration, work-stealing coordination |
|
|
77
|
+
| **Planner** | Architect | Symbolic mapping, dependency research, roadmap generation, file-level planning |
|
|
78
|
+
| **Worker** | Implementer | High-throughput coding, TDD workflow, documentation, isolated file execution |
|
|
79
|
+
| **Reviewer** | Auditor | Rigid verification, LSP/Lint authority, integration testing, final mission seal |
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
67
83
|
## ๐ ๏ธ Core Capabilities
|
|
68
84
|
|
|
69
85
|
### ๐ Atomic MVCC State Synchronization
|
|
70
|
-
|
|
86
|
+
Solves the "Concurrent TODO Update" problem using **MVCC + Mutex**. Agents safely mark tasks complete in parallel without data loss or race conditions. Every state change is cryptographically hashed and logged.
|
|
71
87
|
|
|
72
88
|
### ๐งฉ Advanced Hook Orchestration
|
|
73
|
-
Execution flows
|
|
89
|
+
Execution flows governed by a **Priority-Phase Hook Registry**. Hooks are grouped into phases (`early`, `normal`, `late`) and executed via **Topological Sort** for predictable, dependency-aware ordering.
|
|
74
90
|
|
|
75
91
|
### ๐ก๏ธ Autonomous Recovery
|
|
76
92
|
- **Self-healing loops** with adaptive stagnation detection
|
|
@@ -78,130 +94,57 @@ Execution flows are governed by a **Priority-Phase Hook Registry**. Hooks (Safet
|
|
|
78
94
|
- **Auto-retry with backoff**: Exponential backoff for transient failures
|
|
79
95
|
|
|
80
96
|
### ๐ฏ State-Level Session Isolation
|
|
81
|
-
Reused sessions in the **SessionPool** are explicitly reset
|
|
97
|
+
Reused sessions in the **SessionPool** are explicitly reset via server-side compaction, ensuring previous task context never leaks into new tasks.
|
|
82
98
|
|
|
83
99
|
### ๐ Zero-Payload Turbo Mode
|
|
84
|
-
Leverages `system.transform` to unshift
|
|
85
|
-
|
|
86
|
-
---
|
|
87
|
-
|
|
88
|
-
## ๐ ๏ธ Infrastructure & Reliability
|
|
89
|
-
|
|
90
|
-
### ๐ Resource Safety & Reliability
|
|
91
|
-
- **RAII Pattern (ConcurrencyToken)**: Guaranteed resource cleanup with zero leaks
|
|
92
|
-
- **ShutdownManager**: Priority-based graceful shutdown with 5-second timeout per handler
|
|
93
|
-
- **Automatic Backups**: All config changes backed up with rollback support
|
|
94
|
-
- **Atomic File Operations**: Temp file + rename for corruption-proof writes
|
|
95
|
-
- **Finally Blocks**: Guaranteed cleanup in all critical paths
|
|
96
|
-
- **Zero Resource Leaks**: File watchers, event listeners, concurrency slots all properly released
|
|
97
|
-
|
|
98
|
-
### โก Performance Optimizations
|
|
99
|
-
- **Work-Stealing Queues**: Chase-Lev deque implementation for 90%+ CPU utilization
|
|
100
|
-
- Planner: 2 workers, Worker: 8 workers, Reviewer: 4 workers
|
|
101
|
-
- LIFO for owner (cache locality), FIFO for thieves (fairness)
|
|
102
|
-
- **Memory Pooling**: 80% GC pressure reduction
|
|
103
|
-
- Object Pool: 200 ParallelTask instances (50 prewarmed)
|
|
104
|
-
- String Interning: Deduplication for agent names, status strings
|
|
105
|
-
- Buffer Pool: Reusable ArrayBuffers (1KB, 4KB, 16KB, 64KB)
|
|
106
|
-
- **Session Reuse**: 90% faster session creation (500ms โ 50ms)
|
|
107
|
-
- Pool size: 5 sessions per agent type
|
|
108
|
-
- Max reuse: 10 times per session
|
|
109
|
-
- Health check: Every 60 seconds
|
|
110
|
-
- **Rust Connection Pool**: 10x faster tool calls (50-100ms โ 5-10ms)
|
|
111
|
-
- Max 4 persistent processes
|
|
112
|
-
- 30-second idle timeout
|
|
113
|
-
- **Adaptive Polling**: Dynamic 500ms-5s intervals based on system load
|
|
114
|
-
|
|
115
|
-
### ๐ก๏ธ Safety Features
|
|
116
|
-
- **Circuit Breaker**: Auto-recovery from API failures (5 failures โ open)
|
|
117
|
-
- **Resource Pressure Detection**: Rejects low-priority tasks when memory > 80%
|
|
118
|
-
- **Terminal Node Guard**: Prevents infinite recursion (depth limit enforcement)
|
|
119
|
-
- **Auto-Scaling**: Concurrency slots adjust based on success/failure rate
|
|
120
|
-
|
|
121
|
-
---
|
|
122
|
-
|
|
123
|
-
## ๐ ๏ธ Key Innovations
|
|
100
|
+
Leverages `system.transform` to unshift agent instruction sets server-side, reducing initial message payloads by **90%+** and preventing context fragmentation.
|
|
124
101
|
|
|
125
102
|
### ๐ง Hierarchical Memory System
|
|
126
|
-
Maintains focus across thousands of conversation turns using a 4-tier memory structure
|
|
127
|
-
|
|
128
|
-
### Dynamic Concurrency Auto-Scaling
|
|
129
|
-
Slots for parallel implementation scale up automatically after a **3-success streak** and scale down aggressively upon detection of API instability or implementation failures.
|
|
130
|
-
|
|
131
|
-
### ๐ก๏ธ Neuro-Symbolic Safety
|
|
132
|
-
Combines LLM reasoning with deterministic **AST/LSP verification**. Every code change is verified by native system tools before being accepted into the master roadmap.
|
|
103
|
+
Maintains focus across thousands of conversation turns using a 4-tier memory structure with **EMA-based Context Gating** to preserve architectural truth while pruning noise.
|
|
133
104
|
|
|
134
105
|
### ๐ Adaptive Intelligence Loop
|
|
135
|
-
- **Stagnation Detection**:
|
|
136
|
-
- **Diagnostic Intervention**: Forces
|
|
137
|
-
- **Proactive Agency**: Mandates Speculative Planning
|
|
138
|
-
|
|
139
|
-
### ๐ Native TUI Integration
|
|
140
|
-
Seamless integration with OpenCode's native TUI via **TaskToastManager**. Provides non-intrusive, real-time feedback on **Mission Progress**, active **Agent Sub-sessions**, and **Technical Metrics** using protocol-safe Toast notifications.
|
|
141
|
-
|
|
142
|
-
### โก Event-Driven Architecture
|
|
143
|
-
Utilizes a hybrid event-driven pipeline (`EventHandler` + `TaskPoller`) to maximize responsiveness while maintaining robust state tracking and resource cleanup.
|
|
144
|
-
|
|
145
|
-
### ๐ Secure Configuration
|
|
146
|
-
Runtime agent configuration is strictly validated using **Zod schemas**, ensuring that custom agent definitions in `agents.json` are type-safe and error-free before execution.
|
|
106
|
+
- **Stagnation Detection**: Senses when no progress is made across iterations
|
|
107
|
+
- **Diagnostic Intervention**: Forces "Diagnostic Mode" mandating log audits and strategy pivots
|
|
108
|
+
- **Proactive Agency**: Mandates Speculative Planning during background task execution
|
|
147
109
|
|
|
148
110
|
---
|
|
149
111
|
|
|
150
|
-
##
|
|
112
|
+
## ๏ฟฝ Performance Benchmarks
|
|
151
113
|
|
|
152
|
-
|
|
|
153
|
-
|
|
154
|
-
|
|
|
155
|
-
|
|
|
156
|
-
|
|
|
157
|
-
|
|
|
114
|
+
| Metric | Improvement |
|
|
115
|
+
|:-------|:------------|
|
|
116
|
+
| CPU Utilization | 90%+ (up from 50-70%) |
|
|
117
|
+
| Tool Call Speed | 10x faster (5-10ms vs 50-100ms) via Rust pool |
|
|
118
|
+
| Session Creation | 90% faster (50ms vs 500ms) via session pooling |
|
|
119
|
+
| Memory Usage | 60% reduction via object/string/buffer pooling |
|
|
120
|
+
| GC Pressure | 80% reduction |
|
|
121
|
+
| Token Efficiency | 40% reduction via Incremental State & System Transform |
|
|
122
|
+
| Sync Accuracy | 99.95% via MVCC+Mutex |
|
|
123
|
+
| Parallel Efficiency | 80% improvement (50% โ 90%+) |
|
|
158
124
|
|
|
159
125
|
---
|
|
160
126
|
|
|
161
|
-
##
|
|
162
|
-
|
|
163
|
-
### Throughput & Efficiency
|
|
164
|
-
- **Concurrent Sessions**: 50+ parallel agent sessions with work-stealing
|
|
165
|
-
- **CPU Utilization**: 90%+ (up from 50-70%)
|
|
166
|
-
- **Tool Call Speed**: 10x faster (5-10ms vs 50-100ms) via Rust connection pool
|
|
167
|
-
- **Session Creation**: 90% faster (50ms vs 500ms) via session pooling
|
|
168
|
-
- **Processing Speed**: 3-5x baseline throughput
|
|
127
|
+
## ๐๏ธ Infrastructure & Reliability
|
|
169
128
|
|
|
170
|
-
### Resource
|
|
171
|
-
- **
|
|
172
|
-
- **
|
|
173
|
-
- **
|
|
174
|
-
|
|
175
|
-
### Reliability
|
|
176
|
-
- **Sync Accuracy**: 99.95% reliability via MVCC+Mutex transaction logic
|
|
177
|
-
- **Mission Survival**: 100% uptime through plugin restarts via S.H.R (Self-Healing Rehydration)
|
|
178
|
-
- **Resource Leaks**: Zero (guaranteed by RAII pattern)
|
|
179
|
-
- **Config Safety**: 100% (atomic writes + auto-backup + rollback)
|
|
180
|
-
|
|
181
|
-
### Scalability
|
|
182
|
-
- **Work-Stealing Efficiency**: 80% improvement in parallel efficiency (50% โ 90%+)
|
|
183
|
-
- **Adaptive Polling**: Dynamic 500ms-5s based on load
|
|
184
|
-
- **Auto-Scaling**: Concurrency slots adjust automatically based on success rate
|
|
185
|
-
|
|
186
|
-
---
|
|
129
|
+
### Resource Safety
|
|
130
|
+
- **RAII Pattern**: Guaranteed resource cleanup with zero leaks
|
|
131
|
+
- **ShutdownManager**: Priority-based graceful shutdown (5s timeout per handler)
|
|
132
|
+
- **Atomic File Operations**: Temp file + rename for corruption-proof writes
|
|
133
|
+
- **Automatic Backups**: Timestamped config backups with rollback support
|
|
187
134
|
|
|
188
|
-
|
|
135
|
+
### Safety Features
|
|
136
|
+
- **Circuit Breaker**: Auto-recovery from API failures (5 failures โ open)
|
|
137
|
+
- **Resource Pressure Detection**: Rejects low-priority tasks when memory > 80%
|
|
138
|
+
- **Terminal Node Guard**: Prevents infinite recursion via depth limit
|
|
139
|
+
- **Auto-Scaling**: Concurrency slots adjust based on success/failure rate
|
|
189
140
|
|
|
141
|
+
### Technical Stack
|
|
190
142
|
- **Runtime**: Node.js 18+ (TypeScript)
|
|
191
143
|
- **Tools**: Rust-based CLI tools (grep, glob, ast) via connection pool
|
|
192
144
|
- **Concurrency**: Chase-Lev work-stealing deque + priority queues
|
|
193
145
|
- **Memory**: Object pooling + string interning + buffer pooling
|
|
194
146
|
- **State Management**: MVCC + Mutex
|
|
195
|
-
- **Safety**: RAII
|
|
196
|
-
|
|
197
|
-
---
|
|
198
|
-
|
|
199
|
-
## ๐ Documentation
|
|
200
|
-
|
|
201
|
-
- [Why We Built a Custom Orchestrator Instead of Using OpenCode's APIs โ](docs/WHY_CUSTOM_ORCHESTRATOR.md)
|
|
202
|
-
- [System Architecture Deep-Dive โ](docs/SYSTEM_ARCHITECTURE.md)
|
|
203
|
-
- [Windows Configuration Guide โ](docs/WINDOWS_CONFIGURATION.md)
|
|
204
|
-
- [Developer Notes โ](docs/DEVELOPERS_NOTE.md)
|
|
147
|
+
- **Safety**: RAII + circuit breaker + resource pressure detection
|
|
205
148
|
|
|
206
149
|
---
|
|
207
150
|
|
|
@@ -210,12 +153,11 @@ Runtime agent configuration is strictly validated using **Zod schemas**, ensurin
|
|
|
210
153
|
### Safe Installation
|
|
211
154
|
The installation process is **production-safe** with multiple protection layers:
|
|
212
155
|
|
|
213
|
-
1. โ
**Never overwrites**
|
|
214
|
-
2. โ
**Automatic backups**
|
|
215
|
-
3. โ
**Atomic writes**
|
|
216
|
-
4. โ
**
|
|
217
|
-
5. โ
**
|
|
218
|
-
6. โ
**Cross-platform** - Windows (native, Git Bash, WSL), macOS, Linux
|
|
156
|
+
1. โ
**Never overwrites** โ always merges with existing config
|
|
157
|
+
2. โ
**Automatic backups** โ timestamped, last 5 kept
|
|
158
|
+
3. โ
**Atomic writes** โ temp file + rename (OS-level atomic)
|
|
159
|
+
4. โ
**Automatic rollback** โ restores from backup on any failure
|
|
160
|
+
5. โ
**Cross-platform** โ Windows (native, Git Bash, WSL2), macOS, Linux
|
|
219
161
|
|
|
220
162
|
### Configuration Logs
|
|
221
163
|
- Unix: `/tmp/opencode-orchestrator.log`
|
|
@@ -223,9 +165,18 @@ The installation process is **production-safe** with multiple protection layers:
|
|
|
223
165
|
|
|
224
166
|
---
|
|
225
167
|
|
|
168
|
+
## ๐ Documentation
|
|
169
|
+
|
|
170
|
+
- [Why We Built a Custom Orchestrator โ](docs/WHY_CUSTOM_ORCHESTRATOR.md)
|
|
171
|
+
- [System Architecture Deep-Dive โ](docs/SYSTEM_ARCHITECTURE.md)
|
|
172
|
+
- [Windows Configuration Guide โ](docs/WINDOWS_CONFIGURATION.md)
|
|
173
|
+
- [Developer Notes โ](docs/DEVELOPERS_NOTE.md)
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
226
177
|
## ๐ License
|
|
227
178
|
|
|
228
|
-
MIT License
|
|
179
|
+
MIT License โ see [LICENSE](LICENSE) for details.
|
|
229
180
|
|
|
230
181
|
---
|
|
231
182
|
|
|
Binary file
|
package/dist/index.js
CHANGED
|
@@ -36390,6 +36390,7 @@ function buildVerificationSummary(result) {
|
|
|
36390
36390
|
init_logger();
|
|
36391
36391
|
import { exec as exec2 } from "node:child_process";
|
|
36392
36392
|
import { promisify as promisify2 } from "node:util";
|
|
36393
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
36393
36394
|
|
|
36394
36395
|
// src/shared/notification/os-notify/constants/notification-commands.ts
|
|
36395
36396
|
var NOTIFICATION_COMMANDS = {
|
|
@@ -36462,14 +36463,24 @@ async function notifyDarwin(title, message) {
|
|
|
36462
36463
|
if (!path10) return;
|
|
36463
36464
|
const escT = title.replace(/"/g, '\\"');
|
|
36464
36465
|
const escM = message.replace(/"/g, '\\"');
|
|
36465
|
-
await execAsync2(`${path10} -e 'display notification "${escM}" with title "${escT}" sound name "Glass"'`);
|
|
36466
|
+
await execAsync2(`${path10} -e 'display notification "${escM}" with title "${escT}" sound name "Glass"' >/dev/null 2>/dev/null`);
|
|
36467
|
+
}
|
|
36468
|
+
function isWSL() {
|
|
36469
|
+
try {
|
|
36470
|
+
if (process.env.WSL_DISTRO_NAME || process.env.WSLENV) return true;
|
|
36471
|
+
const procVersion = readFileSync3("/proc/version", "utf-8");
|
|
36472
|
+
return /microsoft|WSL/i.test(procVersion);
|
|
36473
|
+
} catch {
|
|
36474
|
+
return false;
|
|
36475
|
+
}
|
|
36466
36476
|
}
|
|
36467
36477
|
async function notifyLinux(title, message) {
|
|
36478
|
+
if (isWSL()) return;
|
|
36468
36479
|
const path10 = await resolveCommandPath(
|
|
36469
36480
|
NOTIFICATION_COMMAND_KEYS.NOTIFY_SEND,
|
|
36470
36481
|
NOTIFICATION_COMMANDS.NOTIFY_SEND
|
|
36471
36482
|
);
|
|
36472
|
-
if (path10) await execAsync2(`${path10} "${title}" "${message}" 2>/dev/null`);
|
|
36483
|
+
if (path10) await execAsync2(`${path10} "${title}" "${message}" >/dev/null 2>/dev/null`);
|
|
36473
36484
|
}
|
|
36474
36485
|
async function notifyWindows(title, message) {
|
|
36475
36486
|
const ps = await resolveCommandPath(
|
|
@@ -36491,7 +36502,7 @@ $Toast = [Windows.UI.Notifications.ToastNotification]::new($SerializedXml)
|
|
|
36491
36502
|
$Notifier = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('OpenCode Orchestrator')
|
|
36492
36503
|
$Notifier.Show($Toast)
|
|
36493
36504
|
`.trim().replace(/\n/g, "; ");
|
|
36494
|
-
await execAsync2(`${ps} -Command "${script}"`);
|
|
36505
|
+
await execAsync2(`${ps} -Command "${script}" >NUL 2>NUL`);
|
|
36495
36506
|
}
|
|
36496
36507
|
async function sendNotification(platform2, title, message) {
|
|
36497
36508
|
try {
|
|
@@ -36521,7 +36532,7 @@ async function playDarwin(soundPath) {
|
|
|
36521
36532
|
NOTIFICATION_COMMAND_KEYS.AFPLAY,
|
|
36522
36533
|
NOTIFICATION_COMMANDS.AFPLAY
|
|
36523
36534
|
);
|
|
36524
|
-
if (path10) exec3(`"${path10}" "${soundPath}"`);
|
|
36535
|
+
if (path10) exec3(`"${path10}" "${soundPath}" >/dev/null 2>/dev/null`);
|
|
36525
36536
|
} catch (err) {
|
|
36526
36537
|
log(`[session-notify] Error playing sound (Darwin): ${err}`);
|
|
36527
36538
|
}
|
|
@@ -36534,14 +36545,14 @@ async function playLinux(soundPath) {
|
|
|
36534
36545
|
NOTIFICATION_COMMANDS.PAPLAY
|
|
36535
36546
|
);
|
|
36536
36547
|
if (paplay) {
|
|
36537
|
-
exec3(`"${paplay}" "${soundPath}" 2>/dev/null`);
|
|
36548
|
+
exec3(`"${paplay}" "${soundPath}" >/dev/null 2>/dev/null`);
|
|
36538
36549
|
return;
|
|
36539
36550
|
}
|
|
36540
36551
|
const aplay = await resolveCommandPath(
|
|
36541
36552
|
NOTIFICATION_COMMAND_KEYS.APLAY,
|
|
36542
36553
|
NOTIFICATION_COMMANDS.APLAY
|
|
36543
36554
|
);
|
|
36544
|
-
if (aplay) exec3(`"${aplay}" "${soundPath}" 2>/dev/null`);
|
|
36555
|
+
if (aplay) exec3(`"${aplay}" "${soundPath}" >/dev/null 2>/dev/null`);
|
|
36545
36556
|
} catch (err) {
|
|
36546
36557
|
log(`[session-notify] Error playing sound (Linux): ${err}`);
|
|
36547
36558
|
}
|
|
@@ -36554,10 +36565,10 @@ async function playWindows(soundPath) {
|
|
|
36554
36565
|
);
|
|
36555
36566
|
if (!ps) return;
|
|
36556
36567
|
if (!soundPath) {
|
|
36557
|
-
exec3(`"${ps}" -Command "[System.Media.SystemSounds]::Asterisk.Play()"`);
|
|
36568
|
+
exec3(`"${ps}" -Command "[System.Media.SystemSounds]::Asterisk.Play()" >NUL 2>NUL`);
|
|
36558
36569
|
} else {
|
|
36559
36570
|
const escaped = soundPath.replace(/'/g, "''");
|
|
36560
|
-
exec3(`"${ps}" -Command "(New-Object Media.SoundPlayer '${escaped}').PlaySync()"`);
|
|
36571
|
+
exec3(`"${ps}" -Command "(New-Object Media.SoundPlayer '${escaped}').PlaySync()" >NUL 2>NUL`);
|
|
36561
36572
|
}
|
|
36562
36573
|
} catch (err) {
|
|
36563
36574
|
log(`[session-notify] Error playing sound (Windows): ${err}`);
|
|
@@ -36577,8 +36588,8 @@ async function playSound(platform2, soundPath) {
|
|
|
36577
36588
|
}
|
|
36578
36589
|
|
|
36579
36590
|
// src/core/notification/os-notify/platform.ts
|
|
36580
|
-
import { platform as osPlatform } from "node:os";
|
|
36581
36591
|
init_os();
|
|
36592
|
+
import { platform as osPlatform } from "node:os";
|
|
36582
36593
|
function detectPlatform() {
|
|
36583
36594
|
const p = osPlatform();
|
|
36584
36595
|
if (p === PLATFORM.DARWIN) return PLATFORM.DARWIN;
|
|
@@ -40502,6 +40513,7 @@ function createSystemTransformHandler(ctx) {
|
|
|
40502
40513
|
const { directory, sessions, state: state2 } = ctx;
|
|
40503
40514
|
return async (input, output) => {
|
|
40504
40515
|
const { sessionID } = input;
|
|
40516
|
+
if (!sessionID) return;
|
|
40505
40517
|
const loopState = readLoopState(directory);
|
|
40506
40518
|
const isActiveLoop = isMissionActive(sessionID, directory) || loopState?.active && loopState?.sessionID === sessionID;
|
|
40507
40519
|
const session = ensureSessionInitialized(sessions, sessionID, directory);
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* Input for system transform hook
|
|
6
6
|
*/
|
|
7
7
|
export interface SystemTransformInput {
|
|
8
|
-
/** Session ID for the chat */
|
|
9
|
-
sessionID
|
|
8
|
+
/** Session ID for the chat (optional per opencode Plugin type) */
|
|
9
|
+
sessionID?: string;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
* Output for system transform hook
|
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
-
}) : x)(function(x) {
|
|
5
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
-
});
|
|
8
2
|
|
|
9
3
|
// scripts/postinstall.ts
|
|
10
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync, appendFileSync, copyFileSync, renameSync, unlinkSync } from "fs";
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, appendFileSync, copyFileSync, renameSync, unlinkSync, readdirSync } from "fs";
|
|
11
5
|
import { homedir, tmpdir } from "os";
|
|
12
6
|
import { join } from "path";
|
|
13
7
|
var LOG_FILE = join(tmpdir(), "opencode-orchestrator.log");
|
|
@@ -46,6 +40,45 @@ function formatError(err, context) {
|
|
|
46
40
|
return `Failed to ${context}: ${String(err)}`;
|
|
47
41
|
}
|
|
48
42
|
var PLUGIN_NAME = "opencode-orchestrator";
|
|
43
|
+
function detectWSLWindowsConfigDir() {
|
|
44
|
+
try {
|
|
45
|
+
const isWSL = process.env.WSL_DISTRO_NAME || process.env.WSLENV;
|
|
46
|
+
if (!isWSL) {
|
|
47
|
+
try {
|
|
48
|
+
const procVersion = readFileSync("/proc/version", "utf-8");
|
|
49
|
+
if (!/microsoft|WSL/i.test(procVersion)) return null;
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const windowsUser = process.env.WINDOWS_USERNAME || process.env.USERNAME;
|
|
55
|
+
const candidates = [];
|
|
56
|
+
const userDir = "/mnt/c/Users";
|
|
57
|
+
if (existsSync(userDir)) {
|
|
58
|
+
try {
|
|
59
|
+
const users = readdirSync(userDir);
|
|
60
|
+
for (const user of users) {
|
|
61
|
+
if (["Public", "Default", "Default User", "All Users", "desktop.ini"].includes(user) || user.startsWith(".")) continue;
|
|
62
|
+
const candidate = join(userDir, user, "AppData", "Roaming", "opencode");
|
|
63
|
+
candidates.push(candidate);
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (windowsUser) {
|
|
69
|
+
const preferred = `/mnt/c/Users/${windowsUser}/AppData/Roaming/opencode`;
|
|
70
|
+
if (candidates.includes(preferred)) {
|
|
71
|
+
return preferred;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
for (const c of candidates) {
|
|
75
|
+
if (existsSync(c)) return c;
|
|
76
|
+
}
|
|
77
|
+
return candidates[0] || null;
|
|
78
|
+
} catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
49
82
|
function getConfigPaths() {
|
|
50
83
|
const paths = [];
|
|
51
84
|
if (process.env.XDG_CONFIG_HOME) {
|
|
@@ -60,6 +93,11 @@ function getConfigPaths() {
|
|
|
60
93
|
}
|
|
61
94
|
} else {
|
|
62
95
|
paths.push(join(homedir(), ".config", "opencode"));
|
|
96
|
+
const wslWindowsConfig = detectWSLWindowsConfigDir();
|
|
97
|
+
if (wslWindowsConfig && !paths.includes(wslWindowsConfig)) {
|
|
98
|
+
log("Detected WSL2 - also checking Windows config path", { wslWindowsConfig });
|
|
99
|
+
paths.push(wslWindowsConfig);
|
|
100
|
+
}
|
|
63
101
|
}
|
|
64
102
|
return paths;
|
|
65
103
|
}
|
|
@@ -122,28 +160,41 @@ function registerInConfig(configDir) {
|
|
|
122
160
|
mkdirSync(configDir, { recursive: true, mode: 493 });
|
|
123
161
|
log("Created config directory", { configDir });
|
|
124
162
|
}
|
|
125
|
-
backupFile = createBackup(configFile);
|
|
126
163
|
let config = {};
|
|
164
|
+
let fileExisted = false;
|
|
127
165
|
if (existsSync(configFile)) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
166
|
+
fileExisted = true;
|
|
167
|
+
const rawContent = readFileSync(configFile, "utf-8").trim();
|
|
168
|
+
if (rawContent) {
|
|
169
|
+
let parseError;
|
|
170
|
+
try {
|
|
171
|
+
config = JSON.parse(rawContent);
|
|
172
|
+
} catch (err) {
|
|
173
|
+
parseError = err;
|
|
174
|
+
}
|
|
175
|
+
if (parseError) {
|
|
176
|
+
backupFile = createBackup(configFile);
|
|
177
|
+
log("Corrupted config JSON, skipping this path to avoid data loss", { configFile });
|
|
178
|
+
console.log(`\u26A0\uFE0F opencode.json at ${configFile} has invalid JSON and was skipped.`);
|
|
179
|
+
if (backupFile) {
|
|
180
|
+
console.log(` Backup saved: ${backupFile}`);
|
|
135
181
|
}
|
|
182
|
+
console.log(` Please fix the file manually, then add "${PLUGIN_NAME}" to the "plugin" array.`);
|
|
183
|
+
return { success: false, backupFile, skipped: true };
|
|
136
184
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
console.log(
|
|
185
|
+
if (!validateConfig(config)) {
|
|
186
|
+
log("Unexpected config structure, skipping to avoid corruption", { config, configFile });
|
|
187
|
+
console.log(`\u26A0\uFE0F Unexpected config structure in ${configFile}. Skipping to avoid corruption.`);
|
|
188
|
+
console.log(` Please manually add "${PLUGIN_NAME}" to the "plugin" array.`);
|
|
189
|
+
return { success: false, backupFile: null, skipped: true };
|
|
141
190
|
}
|
|
142
|
-
config = { plugin: [] };
|
|
143
191
|
}
|
|
144
192
|
}
|
|
145
193
|
if (!config.plugin) {
|
|
146
194
|
config.plugin = [];
|
|
195
|
+
if (!fileExisted && !config["$schema"]) {
|
|
196
|
+
config["$schema"] = "https://opencode.ai/config.json";
|
|
197
|
+
}
|
|
147
198
|
}
|
|
148
199
|
const hasPlugin = config.plugin.some((p) => {
|
|
149
200
|
if (typeof p !== "string") return false;
|
|
@@ -153,6 +204,9 @@ function registerInConfig(configDir) {
|
|
|
153
204
|
log("Plugin already registered", { configFile });
|
|
154
205
|
return { success: false, backupFile };
|
|
155
206
|
}
|
|
207
|
+
if (fileExisted) {
|
|
208
|
+
backupFile = createBackup(configFile);
|
|
209
|
+
}
|
|
156
210
|
config.plugin.push(PLUGIN_NAME);
|
|
157
211
|
log("Adding plugin to config", { plugin: PLUGIN_NAME, configFile });
|
|
158
212
|
atomicWriteJSON(configFile, config);
|
|
@@ -189,7 +243,7 @@ function registerInConfig(configDir) {
|
|
|
189
243
|
function cleanupOldBackups(configFile) {
|
|
190
244
|
try {
|
|
191
245
|
const configDir = join(configFile, "..");
|
|
192
|
-
const files =
|
|
246
|
+
const files = readdirSync(configDir);
|
|
193
247
|
const backupFiles = files.filter((f) => f.startsWith("opencode.json.backup.")).sort().reverse();
|
|
194
248
|
for (let i = 5; i < backupFiles.length; i++) {
|
|
195
249
|
const backupPath = join(configDir, backupFiles[i]);
|
|
@@ -209,6 +263,7 @@ try {
|
|
|
209
263
|
log("Config paths to check", configPaths);
|
|
210
264
|
let registered = false;
|
|
211
265
|
let alreadyRegistered = false;
|
|
266
|
+
let skippedCorrupt = false;
|
|
212
267
|
let backupCreated = null;
|
|
213
268
|
for (const configDir of configPaths) {
|
|
214
269
|
const configFile = join(configDir, "opencode.json");
|
|
@@ -228,7 +283,10 @@ try {
|
|
|
228
283
|
}
|
|
229
284
|
}
|
|
230
285
|
const result = registerInConfig(configDir);
|
|
231
|
-
if (result.
|
|
286
|
+
if (result.skipped) {
|
|
287
|
+
skippedCorrupt = true;
|
|
288
|
+
if (result.backupFile) backupCreated = result.backupFile;
|
|
289
|
+
} else if (result.success) {
|
|
232
290
|
console.log(`\u2705 Plugin registered: ${configFile}`);
|
|
233
291
|
if (result.backupFile) {
|
|
234
292
|
console.log(` Backup created: ${result.backupFile}`);
|
|
@@ -240,10 +298,13 @@ try {
|
|
|
240
298
|
backupCreated = result.backupFile;
|
|
241
299
|
}
|
|
242
300
|
}
|
|
243
|
-
if (
|
|
244
|
-
|
|
301
|
+
if (registered) {
|
|
302
|
+
} else if (alreadyRegistered) {
|
|
303
|
+
console.log("\u2705 Plugin already registered in all detected config locations.");
|
|
245
304
|
log("Plugin was already registered");
|
|
246
|
-
} else if (
|
|
305
|
+
} else if (skippedCorrupt) {
|
|
306
|
+
log("Skipped due to corrupted config");
|
|
307
|
+
} else {
|
|
247
308
|
console.log("\u26A0\uFE0F Could not register plugin in any config location.");
|
|
248
309
|
console.log(" This may be due to permissions or file system issues.");
|
|
249
310
|
console.log(` Check logs: ${LOG_FILE}`);
|
|
@@ -252,7 +313,7 @@ try {
|
|
|
252
313
|
console.log("");
|
|
253
314
|
console.log("\u{1F680} Ready! Restart OpenCode to use.");
|
|
254
315
|
console.log("");
|
|
255
|
-
log("Installation completed", { registered, alreadyRegistered, backupCreated });
|
|
316
|
+
log("Installation completed", { registered, alreadyRegistered, skippedCorrupt, backupCreated });
|
|
256
317
|
} catch (error) {
|
|
257
318
|
log("Installation error", { error: String(error) });
|
|
258
319
|
console.error("\u274C " + formatError(error, "register plugin"));
|
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
-
}) : x)(function(x) {
|
|
5
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
-
});
|
|
8
2
|
|
|
9
3
|
// scripts/preuninstall.ts
|
|
10
|
-
import { existsSync, readFileSync, writeFileSync, appendFileSync, copyFileSync, renameSync, unlinkSync } from "fs";
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync, appendFileSync, copyFileSync, renameSync, unlinkSync, readdirSync } from "fs";
|
|
11
5
|
import { homedir, tmpdir } from "os";
|
|
12
6
|
import { join } from "path";
|
|
13
7
|
var LOG_FILE = join(tmpdir(), "opencode-orchestrator.log");
|
|
@@ -46,6 +40,43 @@ function formatError(err, context) {
|
|
|
46
40
|
return `Failed to ${context}: ${String(err)}`;
|
|
47
41
|
}
|
|
48
42
|
var PLUGIN_NAME = "opencode-orchestrator";
|
|
43
|
+
function detectWSLWindowsConfigDir() {
|
|
44
|
+
try {
|
|
45
|
+
const isWSL = process.env.WSL_DISTRO_NAME || process.env.WSLENV;
|
|
46
|
+
if (!isWSL) {
|
|
47
|
+
try {
|
|
48
|
+
const procVersion = readFileSync("/proc/version", "utf-8");
|
|
49
|
+
if (!/microsoft|WSL/i.test(procVersion)) return null;
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const windowsUser = process.env.WINDOWS_USERNAME || process.env.USERNAME;
|
|
55
|
+
const candidates = [];
|
|
56
|
+
const userDir = "/mnt/c/Users";
|
|
57
|
+
if (existsSync(userDir)) {
|
|
58
|
+
try {
|
|
59
|
+
const users = readdirSync(userDir);
|
|
60
|
+
for (const user of users) {
|
|
61
|
+
if (["Public", "Default", "Default User", "All Users", "desktop.ini"].includes(user) || user.startsWith(".")) continue;
|
|
62
|
+
const candidate = join(userDir, user, "AppData", "Roaming", "opencode");
|
|
63
|
+
candidates.push(candidate);
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (windowsUser) {
|
|
69
|
+
const preferred = `/mnt/c/Users/${windowsUser}/AppData/Roaming/opencode`;
|
|
70
|
+
if (candidates.includes(preferred)) return preferred;
|
|
71
|
+
}
|
|
72
|
+
for (const c of candidates) {
|
|
73
|
+
if (existsSync(c)) return c;
|
|
74
|
+
}
|
|
75
|
+
return candidates[0] || null;
|
|
76
|
+
} catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
49
80
|
function getConfigPaths() {
|
|
50
81
|
const paths = [];
|
|
51
82
|
if (process.env.XDG_CONFIG_HOME) {
|
|
@@ -60,6 +91,11 @@ function getConfigPaths() {
|
|
|
60
91
|
}
|
|
61
92
|
} else {
|
|
62
93
|
paths.push(join(homedir(), ".config", "opencode"));
|
|
94
|
+
const wslWindowsConfig = detectWSLWindowsConfigDir();
|
|
95
|
+
if (wslWindowsConfig && !paths.includes(wslWindowsConfig)) {
|
|
96
|
+
log("Detected WSL2 - also checking Windows config path", { wslWindowsConfig });
|
|
97
|
+
paths.push(wslWindowsConfig);
|
|
98
|
+
}
|
|
63
99
|
}
|
|
64
100
|
return paths;
|
|
65
101
|
}
|
|
@@ -199,7 +235,7 @@ function removeFromConfig(configDir) {
|
|
|
199
235
|
function cleanupOldBackups(configFile) {
|
|
200
236
|
try {
|
|
201
237
|
const configDir = join(configFile, "..");
|
|
202
|
-
const files =
|
|
238
|
+
const files = readdirSync(configDir);
|
|
203
239
|
const backupFiles = files.filter((f) => f.startsWith("opencode.json.backup.")).sort().reverse();
|
|
204
240
|
for (let i = 5; i < backupFiles.length; i++) {
|
|
205
241
|
const backupPath = join(configDir, backupFiles[i]);
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "opencode-orchestrator",
|
|
3
3
|
"displayName": "OpenCode Orchestrator",
|
|
4
4
|
"description": "Distributed Cognitive Architecture for OpenCode. Turns simple prompts into specialized multi-agent workflows (Planner, Coder, Reviewer).",
|
|
5
|
-
"version": "1.2.
|
|
5
|
+
"version": "1.2.60",
|
|
6
6
|
"author": "agnusdei1207",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"repository": {
|
|
@@ -39,28 +39,25 @@
|
|
|
39
39
|
"LICENSE"
|
|
40
40
|
],
|
|
41
41
|
"scripts": {
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"rust
|
|
45
|
-
"rust:check": "cargo check --workspace",
|
|
46
|
-
"docker:build-all": "docker compose up dev win-builder --build",
|
|
47
|
-
"docker:build-win": "docker compose up win-builder --build",
|
|
42
|
+
"docker:build-all": "docker compose run --rm dev && docker compose run --rm win-builder",
|
|
43
|
+
"docker:build-win": "docker compose run --rm win-builder",
|
|
44
|
+
"docker:rust-dist": "docker compose run --rm dev && (sudo chown -R $(id -u):$(id -g) bin/ 2>/dev/null || true)",
|
|
48
45
|
"docker:test": "docker compose run --rm test",
|
|
49
|
-
"docker:dist": "npm run docker:build-all && shx cp target/x86_64-pc-windows-gnu/release/orchestrator.exe bin/orchestrator-windows-x64.exe && shx cp target/release/orchestrator bin/orchestrator-linux-x64",
|
|
50
46
|
"docker:clean": "docker compose down -v",
|
|
51
|
-
"build": "
|
|
52
|
-
"build:all": "npm run build && npm run rust
|
|
47
|
+
"build": "rm -rf dist && npx esbuild src/index.ts --bundle --outfile=dist/index.js --platform=node --format=esm && tsc --emitDeclarationOnly && mkdir -p dist/scripts && npx esbuild scripts/postinstall.ts --bundle --outfile=dist/scripts/postinstall.js --platform=node --format=esm && npx esbuild scripts/preuninstall.ts --bundle --outfile=dist/scripts/preuninstall.js --platform=node --format=esm",
|
|
48
|
+
"build:all": "npm run build && npm run docker:rust-dist",
|
|
53
49
|
"test": "vitest run --reporter=verbose",
|
|
54
50
|
"test:coverage": "vitest run --coverage",
|
|
55
51
|
"test:unit": "vitest run tests/unit --reporter=verbose",
|
|
56
52
|
"test:e2e": "vitest run tests/e2e --reporter=verbose",
|
|
57
|
-
"test:all": "npm run build
|
|
53
|
+
"test:all": "npm run build && vitest run --reporter=verbose && echo '=== ALL TESTS PASSED ==='",
|
|
58
54
|
"postinstall": "node dist/scripts/postinstall.js",
|
|
59
55
|
"preuninstall": "node dist/scripts/preuninstall.js",
|
|
60
|
-
"prepublishOnly": "npm run build
|
|
61
|
-
"
|
|
62
|
-
"release:
|
|
63
|
-
"release:
|
|
56
|
+
"prepublishOnly": "npm run build",
|
|
57
|
+
"publish:token": "npm publish --access public",
|
|
58
|
+
"release:patch": "npm run build && npm run docker:rust-dist && npm version patch && git push --follow-tags && npm run publish:token",
|
|
59
|
+
"release:minor": "npm run build && npm run docker:rust-dist && npm version minor && git push --follow-tags && npm run publish:token",
|
|
60
|
+
"release:major": "npm run build && npm run docker:rust-dist && npm version major && git push --follow-tags && npm run publish:token",
|
|
64
61
|
"reset:local": "brew uninstall opencode 2>/dev/null; rm -rf ~/.config/opencode ~/.opencode ~/.local/share/opencode ~/.cache/opencode/node_modules/opencode-orchestrator && echo '=== Clean done ===' && brew install opencode && echo '{\"plugin\": [\"opencode-orchestrator\"], \"$schema\": \"https://opencode.ai/config.json\"}' > ~/.config/opencode/opencode.json && echo '=== Reset (Dev) complete. Run: opencode ==='",
|
|
65
62
|
"reset:prod": "brew uninstall opencode 2>/dev/null; rm -rf ~/.config/opencode ~/.opencode ~/.local/share/opencode ~/.cache/opencode/node_modules/opencode-orchestrator && echo '=== Clean done ===' && brew install opencode && echo '{\"plugin\": [\"opencode-orchestrator\"], \"$schema\": \"https://opencode.ai/config.json\"}' > ~/.config/opencode/opencode.json && npm uninstall -g opencode-orchestrator && echo '=== Reset (Prod) complete. Run: opencode ==='",
|
|
66
63
|
"ginstall": "npm install -g opencode-orchestrator",
|