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 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
  [![MIT License](https://img.shields.io/badge/license-MIT-red.svg)](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
- The engine solves the "Concurrent TODO Update" problem using **Multi-Version Concurrency Control (MVCC) + Mutex**. Agents can safely mark tasks as complete in parallel without data loss or race conditions. Every state change is cryptographically hashed and logged for a complete audit trail.
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 are governed by a **Priority-Phase Hook Registry**. Hooks (Safety, UI, Protocol) are grouped into phases (`early`, `normal`, `late`) and executed using a **Topological Sort** to handle complex dependencies automatically, ensuring a predictable and stable environment.
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 using server-side compaction triggered by health monitors. This ensures that previous task context (old error messages, stale file references) never leaks into new tasks, maintaining 100% implementation integrity.
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 massive agent instruction sets on the server side. This reduces initial message payloads by **90%+**, slashing latency and preventing context fragmentation during long autonomous loops.
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 and **EMA-based Context Gating** to preserve "Architectural Truth" while pruning operational noise.
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**: Automatically senses when no progress is made across multiple iterations
136
- - **Diagnostic Intervention**: Forces the agent into a "Diagnostic Mode" when stagnation is detected, mandating log audits and strategy pivots
137
- - **Proactive Agency**: Mandates Speculative Planning and Parallel Thinking during background task execution
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
- ## โšก Elite Multi-Agent Swarm
112
+ ## ๏ฟฝ Performance Benchmarks
151
113
 
152
- | Agent | Expertise | Capability |
153
- |:------|:-----|:---|
154
- | **Commander** | Mission Hub | Session pooling, parallel thread control, state rehydration, work-stealing coordination |
155
- | **Planner** | Architect | Symbolic mapping, dependency research, roadmap generation, file-level planning |
156
- | **Worker** | Implementer | High-throughput coding, TDD workflow, documentation, isolated file execution |
157
- | **Reviewer** | Auditor | Rigid verification, LSP/Lint authority, integration testing, final mission seal |
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
- ## ๐Ÿ“ˆ Performance Benchmarks
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 Efficiency
171
- - **Memory Usage**: 60% reduction (40% of baseline) via pooling
172
- - **GC Pressure**: 80% reduction via object/string/buffer pooling
173
- - **Token Efficiency**: 40% reduction via Incremental State & System Transform
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
- ## ๐Ÿ—๏ธ Technical Stack
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 pattern + circuit breaker + resource pressure detection
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** - always merges with existing config
214
- 2. โœ… **Automatic backups** - timestamped, last 5 kept
215
- 3. โœ… **Atomic writes** - temp file + rename (OS-level atomic)
216
- 4. โœ… **Write verification** - ensures correctness after every change
217
- 5. โœ… **Automatic rollback** - restores from backup on any failure
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 - see [LICENSE](LICENSE) for details.
179
+ MIT License โ€” see [LICENSE](LICENSE) for details.
229
180
 
230
181
  ---
231
182
 
Binary file
@@ -4,4 +4,3 @@
4
4
  import { type Platform } from "../../../shared/os/index.js";
5
5
  export declare function detectPlatform(): Platform;
6
6
  export declare function getDefaultSoundPath(p: Platform): string;
7
- export declare function preloadPlatformCommands(platform: Platform): void;
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: string;
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
- try {
129
- const content = readFileSync(configFile, "utf-8").trim();
130
- if (content) {
131
- config = JSON.parse(content);
132
- if (!validateConfig(config)) {
133
- log("Invalid config structure detected, using safe defaults", { config });
134
- config = { plugin: [] };
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
- } catch (error) {
138
- log("Failed to parse existing config, creating new one", { error: String(error) });
139
- if (backupFile) {
140
- console.log(`\u26A0\uFE0F Corrupted config detected. Backup saved: ${backupFile}`);
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 = __require("fs").readdirSync(configDir);
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.success) {
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 (!registered && alreadyRegistered) {
244
- console.log("\u2705 Plugin already registered.");
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 (!registered) {
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 = __require("fs").readdirSync(configDir);
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.58",
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
- "rust:build": "cargo build --release && shx mkdir -p bin && shx cp target/release/orchestrator bin/orchestrator 2>/dev/null || shx cp target/release/orchestrator.exe bin/orchestrator.exe 2>/dev/null",
43
- "rust:dist": "npm run rust:build && node -e \"const {platform, arch} = require('os'); const fs = require('fs'); const os = platform(); const cpu = arch(); const name = os === 'win32' ? 'orchestrator-windows-x64.exe' : (os === 'darwin' ? (cpu === 'arm64' ? 'orchestrator-macos-arm64' : 'orchestrator-macos-x64') : (cpu === 'arm64' ? 'orchestrator-linux-arm64' : 'orchestrator-linux-x64')); const src = os === 'win32' ? 'target/release/orchestrator.exe' : 'target/release/orchestrator'; fs.copyFileSync(src, 'bin/' + name); console.log('Distributed to bin/' + name)\"",
44
- "rust:test": "cargo test --workspace",
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": "shx rm -rf dist && npx esbuild src/index.ts --bundle --outfile=dist/index.js --platform=node --format=esm && tsc --emitDeclarationOnly && shx 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",
52
- "build:all": "npm run build && npm run rust:dist",
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:all && npm run rust:test && vitest run --reporter=verbose && echo '=== ALL TESTS PASSED ==='",
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 && npm run rust:dist",
61
- "release:patch": "npm run build && npm run rust:dist && npm version patch && git push --follow-tags && npm publish --access public",
62
- "release:minor": "npm run build && npm run rust:dist && npm version minor && git push --follow-tags && npm publish --access public",
63
- "release:major": "npm run build && npm run rust:dist && npm version major && git push --follow-tags && npm publish --access public",
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",