opencode-orchestrator 1.2.55 → 1.2.59
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 +2 -34
- 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
|
@@ -10,40 +10,6 @@
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
-
## ⚡ Why Build a Custom Orchestrator?
|
|
14
|
-
|
|
15
|
-
**TL;DR: A lightweight, self-contained orchestration system is far more reliable and maintainable than relying on OpenCode's built-in APIs.**
|
|
16
|
-
|
|
17
|
-
### 🎯 The Problem with OpenCode's Built-in APIs
|
|
18
|
-
|
|
19
|
-
OpenCode provides basic async session functionality through its `prompt_async` endpoint. However, this approach has significant limitations:
|
|
20
|
-
|
|
21
|
-
❌ **No Control**: Cannot customize parallel processing logic
|
|
22
|
-
❌ **Server Dependency**: API may change when OpenCode updates
|
|
23
|
-
❌ **Performance Bottleneck**: Cannot optimize from plugin side
|
|
24
|
-
❌ **Maintenance Nightmare**: Must update plugin every time OpenCode updates
|
|
25
|
-
|
|
26
|
-
### ✅ Our Solution: Custom Lightweight Orchestrator
|
|
27
|
-
|
|
28
|
-
We built our own orchestration system that delivers:
|
|
29
|
-
|
|
30
|
-
🚀 **90%+ CPU utilization** via work-stealing queues (vs 50-70% with OpenCode's approach)
|
|
31
|
-
🛡️ **99.95% sync accuracy** via MVCC + Mutex
|
|
32
|
-
⚡ **10x faster tool calls** (5-10ms vs 50-100ms)
|
|
33
|
-
💾 **60% memory reduction** via pooling
|
|
34
|
-
🔒 **Zero resource leaks** via RAII pattern
|
|
35
|
-
|
|
36
|
-
### 🔑 Key Benefits
|
|
37
|
-
|
|
38
|
-
1. **Full Control**: Complete authority over concurrency, session management, and state synchronization
|
|
39
|
-
2. **High Performance**: Work-stealing queues, session pooling, Rust connection pool
|
|
40
|
-
3. **Reliability**: Circuit breaker, resource pressure detection, auto-recovery
|
|
41
|
-
4. **Independence**: Minimal impact from OpenCode updates - no more "update plugin every time" headaches
|
|
42
|
-
|
|
43
|
-
[📖 Read the full analysis: Why We Built a Custom Orchestrator Instead of Using OpenCode's APIs →](docs/WHY_CUSTOM_ORCHESTRATOR.md)
|
|
44
|
-
|
|
45
|
-
---
|
|
46
|
-
|
|
47
13
|
## ⚡ Quick Start
|
|
48
14
|
|
|
49
15
|
```bash
|
|
@@ -57,6 +23,8 @@ Inside an OpenCode environment:
|
|
|
57
23
|
|
|
58
24
|
---
|
|
59
25
|
|
|
26
|
+
---
|
|
27
|
+
|
|
60
28
|
## 🚀 Engine Workflow
|
|
61
29
|
|
|
62
30
|
OpenCode Orchestrator utilizes a **Hub-and-Spoke Topology** with **Work-Stealing Queues** to execute complex engineering tasks through parallel, context-isolated sessions.
|
|
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.59",
|
|
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",
|