opencode-orchestrator 1.2.62 → 1.2.66
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 +196 -41
- package/dist/core/agents/consts/task-status.const.d.ts +2 -21
- package/dist/core/loop/circuit-breaker.d.ts +19 -0
- package/dist/core/loop/compaction-guard.d.ts +15 -0
- package/dist/core/loop/mission-loop-handler.d.ts +3 -12
- package/dist/core/loop/mission-loop.d.ts +0 -4
- package/dist/core/loop/progress-tracker.d.ts +39 -0
- package/dist/core/loop/session-state-store.d.ts +26 -0
- package/dist/core/notification/toast-core.d.ts +1 -1
- package/dist/core/plugins/plugin-manager.d.ts +2 -1
- package/dist/index.js +578 -113
- package/dist/scripts/postinstall.js +296 -38
- package/dist/scripts/preuninstall.js +267 -19
- package/dist/shared/index.d.ts +0 -1
- package/dist/shared/loop/constants/index.d.ts +1 -0
- package/dist/shared/loop/constants/task-status.d.ts +9 -0
- package/package.json +11 -6
- package/scripts/run-install-hook.mjs +66 -0
|
@@ -1,9 +1,197 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
9
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
10
|
+
}) : x)(function(x) {
|
|
11
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
12
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
13
|
+
});
|
|
14
|
+
var __commonJS = (cb, mod) => function __require2() {
|
|
15
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
16
|
+
};
|
|
17
|
+
var __copyProps = (to, from, except, desc) => {
|
|
18
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
19
|
+
for (let key of __getOwnPropNames(from))
|
|
20
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
21
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
22
|
+
}
|
|
23
|
+
return to;
|
|
24
|
+
};
|
|
25
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
26
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
27
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
28
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
29
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
30
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
31
|
+
mod
|
|
32
|
+
));
|
|
33
|
+
|
|
34
|
+
// node_modules/jsonc-parser/lib/umd/main.js
|
|
35
|
+
var require_main = __commonJS({
|
|
36
|
+
"node_modules/jsonc-parser/lib/umd/main.js"(exports, module) {
|
|
37
|
+
(function(factory) {
|
|
38
|
+
if (typeof module === "object" && typeof module.exports === "object") {
|
|
39
|
+
var v = factory(__require, exports);
|
|
40
|
+
if (v !== void 0) module.exports = v;
|
|
41
|
+
} else if (typeof define === "function" && define.amd) {
|
|
42
|
+
define(["require", "exports", "./impl/format", "./impl/edit", "./impl/scanner", "./impl/parser"], factory);
|
|
43
|
+
}
|
|
44
|
+
})(function(require2, exports2) {
|
|
45
|
+
"use strict";
|
|
46
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
47
|
+
exports2.applyEdits = exports2.modify = exports2.format = exports2.printParseErrorCode = exports2.ParseErrorCode = exports2.stripComments = exports2.visit = exports2.getNodeValue = exports2.getNodePath = exports2.findNodeAtOffset = exports2.findNodeAtLocation = exports2.parseTree = exports2.parse = exports2.getLocation = exports2.SyntaxKind = exports2.ScanError = exports2.createScanner = void 0;
|
|
48
|
+
const formatter = require2("./impl/format");
|
|
49
|
+
const edit = require2("./impl/edit");
|
|
50
|
+
const scanner = require2("./impl/scanner");
|
|
51
|
+
const parser = require2("./impl/parser");
|
|
52
|
+
exports2.createScanner = scanner.createScanner;
|
|
53
|
+
var ScanError;
|
|
54
|
+
(function(ScanError2) {
|
|
55
|
+
ScanError2[ScanError2["None"] = 0] = "None";
|
|
56
|
+
ScanError2[ScanError2["UnexpectedEndOfComment"] = 1] = "UnexpectedEndOfComment";
|
|
57
|
+
ScanError2[ScanError2["UnexpectedEndOfString"] = 2] = "UnexpectedEndOfString";
|
|
58
|
+
ScanError2[ScanError2["UnexpectedEndOfNumber"] = 3] = "UnexpectedEndOfNumber";
|
|
59
|
+
ScanError2[ScanError2["InvalidUnicode"] = 4] = "InvalidUnicode";
|
|
60
|
+
ScanError2[ScanError2["InvalidEscapeCharacter"] = 5] = "InvalidEscapeCharacter";
|
|
61
|
+
ScanError2[ScanError2["InvalidCharacter"] = 6] = "InvalidCharacter";
|
|
62
|
+
})(ScanError || (exports2.ScanError = ScanError = {}));
|
|
63
|
+
var SyntaxKind;
|
|
64
|
+
(function(SyntaxKind2) {
|
|
65
|
+
SyntaxKind2[SyntaxKind2["OpenBraceToken"] = 1] = "OpenBraceToken";
|
|
66
|
+
SyntaxKind2[SyntaxKind2["CloseBraceToken"] = 2] = "CloseBraceToken";
|
|
67
|
+
SyntaxKind2[SyntaxKind2["OpenBracketToken"] = 3] = "OpenBracketToken";
|
|
68
|
+
SyntaxKind2[SyntaxKind2["CloseBracketToken"] = 4] = "CloseBracketToken";
|
|
69
|
+
SyntaxKind2[SyntaxKind2["CommaToken"] = 5] = "CommaToken";
|
|
70
|
+
SyntaxKind2[SyntaxKind2["ColonToken"] = 6] = "ColonToken";
|
|
71
|
+
SyntaxKind2[SyntaxKind2["NullKeyword"] = 7] = "NullKeyword";
|
|
72
|
+
SyntaxKind2[SyntaxKind2["TrueKeyword"] = 8] = "TrueKeyword";
|
|
73
|
+
SyntaxKind2[SyntaxKind2["FalseKeyword"] = 9] = "FalseKeyword";
|
|
74
|
+
SyntaxKind2[SyntaxKind2["StringLiteral"] = 10] = "StringLiteral";
|
|
75
|
+
SyntaxKind2[SyntaxKind2["NumericLiteral"] = 11] = "NumericLiteral";
|
|
76
|
+
SyntaxKind2[SyntaxKind2["LineCommentTrivia"] = 12] = "LineCommentTrivia";
|
|
77
|
+
SyntaxKind2[SyntaxKind2["BlockCommentTrivia"] = 13] = "BlockCommentTrivia";
|
|
78
|
+
SyntaxKind2[SyntaxKind2["LineBreakTrivia"] = 14] = "LineBreakTrivia";
|
|
79
|
+
SyntaxKind2[SyntaxKind2["Trivia"] = 15] = "Trivia";
|
|
80
|
+
SyntaxKind2[SyntaxKind2["Unknown"] = 16] = "Unknown";
|
|
81
|
+
SyntaxKind2[SyntaxKind2["EOF"] = 17] = "EOF";
|
|
82
|
+
})(SyntaxKind || (exports2.SyntaxKind = SyntaxKind = {}));
|
|
83
|
+
exports2.getLocation = parser.getLocation;
|
|
84
|
+
exports2.parse = parser.parse;
|
|
85
|
+
exports2.parseTree = parser.parseTree;
|
|
86
|
+
exports2.findNodeAtLocation = parser.findNodeAtLocation;
|
|
87
|
+
exports2.findNodeAtOffset = parser.findNodeAtOffset;
|
|
88
|
+
exports2.getNodePath = parser.getNodePath;
|
|
89
|
+
exports2.getNodeValue = parser.getNodeValue;
|
|
90
|
+
exports2.visit = parser.visit;
|
|
91
|
+
exports2.stripComments = parser.stripComments;
|
|
92
|
+
var ParseErrorCode;
|
|
93
|
+
(function(ParseErrorCode2) {
|
|
94
|
+
ParseErrorCode2[ParseErrorCode2["InvalidSymbol"] = 1] = "InvalidSymbol";
|
|
95
|
+
ParseErrorCode2[ParseErrorCode2["InvalidNumberFormat"] = 2] = "InvalidNumberFormat";
|
|
96
|
+
ParseErrorCode2[ParseErrorCode2["PropertyNameExpected"] = 3] = "PropertyNameExpected";
|
|
97
|
+
ParseErrorCode2[ParseErrorCode2["ValueExpected"] = 4] = "ValueExpected";
|
|
98
|
+
ParseErrorCode2[ParseErrorCode2["ColonExpected"] = 5] = "ColonExpected";
|
|
99
|
+
ParseErrorCode2[ParseErrorCode2["CommaExpected"] = 6] = "CommaExpected";
|
|
100
|
+
ParseErrorCode2[ParseErrorCode2["CloseBraceExpected"] = 7] = "CloseBraceExpected";
|
|
101
|
+
ParseErrorCode2[ParseErrorCode2["CloseBracketExpected"] = 8] = "CloseBracketExpected";
|
|
102
|
+
ParseErrorCode2[ParseErrorCode2["EndOfFileExpected"] = 9] = "EndOfFileExpected";
|
|
103
|
+
ParseErrorCode2[ParseErrorCode2["InvalidCommentToken"] = 10] = "InvalidCommentToken";
|
|
104
|
+
ParseErrorCode2[ParseErrorCode2["UnexpectedEndOfComment"] = 11] = "UnexpectedEndOfComment";
|
|
105
|
+
ParseErrorCode2[ParseErrorCode2["UnexpectedEndOfString"] = 12] = "UnexpectedEndOfString";
|
|
106
|
+
ParseErrorCode2[ParseErrorCode2["UnexpectedEndOfNumber"] = 13] = "UnexpectedEndOfNumber";
|
|
107
|
+
ParseErrorCode2[ParseErrorCode2["InvalidUnicode"] = 14] = "InvalidUnicode";
|
|
108
|
+
ParseErrorCode2[ParseErrorCode2["InvalidEscapeCharacter"] = 15] = "InvalidEscapeCharacter";
|
|
109
|
+
ParseErrorCode2[ParseErrorCode2["InvalidCharacter"] = 16] = "InvalidCharacter";
|
|
110
|
+
})(ParseErrorCode || (exports2.ParseErrorCode = ParseErrorCode = {}));
|
|
111
|
+
function printParseErrorCode2(code) {
|
|
112
|
+
switch (code) {
|
|
113
|
+
case 1:
|
|
114
|
+
return "InvalidSymbol";
|
|
115
|
+
case 2:
|
|
116
|
+
return "InvalidNumberFormat";
|
|
117
|
+
case 3:
|
|
118
|
+
return "PropertyNameExpected";
|
|
119
|
+
case 4:
|
|
120
|
+
return "ValueExpected";
|
|
121
|
+
case 5:
|
|
122
|
+
return "ColonExpected";
|
|
123
|
+
case 6:
|
|
124
|
+
return "CommaExpected";
|
|
125
|
+
case 7:
|
|
126
|
+
return "CloseBraceExpected";
|
|
127
|
+
case 8:
|
|
128
|
+
return "CloseBracketExpected";
|
|
129
|
+
case 9:
|
|
130
|
+
return "EndOfFileExpected";
|
|
131
|
+
case 10:
|
|
132
|
+
return "InvalidCommentToken";
|
|
133
|
+
case 11:
|
|
134
|
+
return "UnexpectedEndOfComment";
|
|
135
|
+
case 12:
|
|
136
|
+
return "UnexpectedEndOfString";
|
|
137
|
+
case 13:
|
|
138
|
+
return "UnexpectedEndOfNumber";
|
|
139
|
+
case 14:
|
|
140
|
+
return "InvalidUnicode";
|
|
141
|
+
case 15:
|
|
142
|
+
return "InvalidEscapeCharacter";
|
|
143
|
+
case 16:
|
|
144
|
+
return "InvalidCharacter";
|
|
145
|
+
}
|
|
146
|
+
return "<unknown ParseErrorCode>";
|
|
147
|
+
}
|
|
148
|
+
exports2.printParseErrorCode = printParseErrorCode2;
|
|
149
|
+
function format(documentText, range, options) {
|
|
150
|
+
return formatter.format(documentText, range, options);
|
|
151
|
+
}
|
|
152
|
+
exports2.format = format;
|
|
153
|
+
function modify2(text, path, value, options) {
|
|
154
|
+
return edit.setProperty(text, path, value, options);
|
|
155
|
+
}
|
|
156
|
+
exports2.modify = modify2;
|
|
157
|
+
function applyEdits2(text, edits) {
|
|
158
|
+
let sortedEdits = edits.slice(0).sort((a, b) => {
|
|
159
|
+
const diff = a.offset - b.offset;
|
|
160
|
+
if (diff === 0) {
|
|
161
|
+
return a.length - b.length;
|
|
162
|
+
}
|
|
163
|
+
return diff;
|
|
164
|
+
});
|
|
165
|
+
let lastModifiedOffset = text.length;
|
|
166
|
+
for (let i = sortedEdits.length - 1; i >= 0; i--) {
|
|
167
|
+
let e = sortedEdits[i];
|
|
168
|
+
if (e.offset + e.length <= lastModifiedOffset) {
|
|
169
|
+
text = edit.applyEdit(text, e);
|
|
170
|
+
} else {
|
|
171
|
+
throw new Error("Overlapping edit");
|
|
172
|
+
}
|
|
173
|
+
lastModifiedOffset = e.offset;
|
|
174
|
+
}
|
|
175
|
+
return text;
|
|
176
|
+
}
|
|
177
|
+
exports2.applyEdits = applyEdits2;
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
});
|
|
2
181
|
|
|
3
182
|
// scripts/postinstall.ts
|
|
183
|
+
var import_jsonc_parser = __toESM(require_main(), 1);
|
|
4
184
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, appendFileSync, copyFileSync, renameSync, unlinkSync, readdirSync } from "fs";
|
|
5
185
|
import { homedir, tmpdir } from "os";
|
|
6
|
-
import { join } from "path";
|
|
186
|
+
import { dirname, join, basename } from "path";
|
|
187
|
+
var isCI = process.env.CI === "true" || process.env.CONTINUOUS_INTEGRATION === "true";
|
|
188
|
+
var TIMEOUT_MS = 3e4;
|
|
189
|
+
var timeoutId = setTimeout(() => {
|
|
190
|
+
console.log("\u26A0\uFE0F postinstall timeout - exiting gracefully");
|
|
191
|
+
process.exit(0);
|
|
192
|
+
}, TIMEOUT_MS);
|
|
193
|
+
process.on("SIGINT", () => process.exit(0));
|
|
194
|
+
process.on("SIGTERM", () => process.exit(0));
|
|
7
195
|
var LOG_FILE = join(tmpdir(), "opencode-orchestrator.log");
|
|
8
196
|
function log(message, data) {
|
|
9
197
|
try {
|
|
@@ -40,6 +228,41 @@ function formatError(err, context) {
|
|
|
40
228
|
return `Failed to ${context}: ${String(err)}`;
|
|
41
229
|
}
|
|
42
230
|
var PLUGIN_NAME = "opencode-orchestrator";
|
|
231
|
+
function isOurPluginEntry(p) {
|
|
232
|
+
return p === PLUGIN_NAME || p.startsWith(`${PLUGIN_NAME}@`);
|
|
233
|
+
}
|
|
234
|
+
function getConfigFileCandidates(configDir) {
|
|
235
|
+
return [join(configDir, "opencode.jsonc"), join(configDir, "opencode.json")];
|
|
236
|
+
}
|
|
237
|
+
function resolveConfigFile(configDir) {
|
|
238
|
+
for (const candidate of getConfigFileCandidates(configDir)) {
|
|
239
|
+
if (existsSync(candidate)) {
|
|
240
|
+
return candidate;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return getConfigFileCandidates(configDir)[0];
|
|
244
|
+
}
|
|
245
|
+
function parseConfigContent(rawContent) {
|
|
246
|
+
const errors = [];
|
|
247
|
+
const config = (0, import_jsonc_parser.parse)(rawContent, errors, {
|
|
248
|
+
allowTrailingComma: true,
|
|
249
|
+
disallowComments: false
|
|
250
|
+
});
|
|
251
|
+
if (errors.length > 0) {
|
|
252
|
+
const [firstError] = errors;
|
|
253
|
+
const line = rawContent.slice(0, firstError.offset).split("\n").length;
|
|
254
|
+
const column = firstError.offset - rawContent.lastIndexOf("\n", firstError.offset - 1);
|
|
255
|
+
return {
|
|
256
|
+
parseError: `${(0, import_jsonc_parser.printParseErrorCode)(firstError.error)} at line ${line}, column ${column}`
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
if (typeof config !== "object" || config === null || Array.isArray(config)) {
|
|
260
|
+
return {
|
|
261
|
+
parseError: "Root config must be a JSON object"
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
return { config };
|
|
265
|
+
}
|
|
43
266
|
function detectWSLWindowsConfigDir() {
|
|
44
267
|
try {
|
|
45
268
|
const isWSL = process.env.WSL_DISTRO_NAME || process.env.WSLENV;
|
|
@@ -99,7 +322,21 @@ function getConfigPaths() {
|
|
|
99
322
|
paths.push(wslWindowsConfig);
|
|
100
323
|
}
|
|
101
324
|
}
|
|
102
|
-
return paths;
|
|
325
|
+
return [...new Set(paths)];
|
|
326
|
+
}
|
|
327
|
+
function readExistingConfig(configDir) {
|
|
328
|
+
for (const configFile of getConfigFileCandidates(configDir)) {
|
|
329
|
+
if (!existsSync(configFile)) continue;
|
|
330
|
+
const rawContent = readFileSync(configFile, "utf-8").trim();
|
|
331
|
+
if (!rawContent) {
|
|
332
|
+
return { file: configFile, config: {} };
|
|
333
|
+
}
|
|
334
|
+
const parsed = parseConfigContent(rawContent);
|
|
335
|
+
if (parsed.config) {
|
|
336
|
+
return { file: configFile, config: parsed.config };
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return null;
|
|
103
340
|
}
|
|
104
341
|
function validateConfig(config) {
|
|
105
342
|
try {
|
|
@@ -136,10 +373,19 @@ function createBackup(configFile) {
|
|
|
136
373
|
return null;
|
|
137
374
|
}
|
|
138
375
|
}
|
|
139
|
-
function atomicWriteJSON(filePath, data) {
|
|
376
|
+
function atomicWriteJSON(filePath, data, originalContent) {
|
|
140
377
|
const tempFile = `${filePath}.tmp.${Date.now()}`;
|
|
141
378
|
try {
|
|
142
|
-
|
|
379
|
+
let output = JSON.stringify(data, null, 2) + "\n";
|
|
380
|
+
if (filePath.endsWith(".jsonc") && originalContent !== void 0) {
|
|
381
|
+
const source = originalContent.trim() ? originalContent : "{}";
|
|
382
|
+
const edits = (0, import_jsonc_parser.modify)(source, ["plugin"], data.plugin, {
|
|
383
|
+
formattingOptions: { tabSize: 2, insertSpaces: true }
|
|
384
|
+
});
|
|
385
|
+
output = (0, import_jsonc_parser.applyEdits)(source, edits);
|
|
386
|
+
if (!output.endsWith("\n")) output += "\n";
|
|
387
|
+
}
|
|
388
|
+
writeFileSync(tempFile, output, { mode: 420 });
|
|
143
389
|
renameSync(tempFile, filePath);
|
|
144
390
|
log("Atomic write successful", { filePath });
|
|
145
391
|
} catch (error) {
|
|
@@ -153,8 +399,9 @@ function atomicWriteJSON(filePath, data) {
|
|
|
153
399
|
}
|
|
154
400
|
}
|
|
155
401
|
function registerInConfig(configDir) {
|
|
156
|
-
const configFile =
|
|
402
|
+
const configFile = resolveConfigFile(configDir);
|
|
157
403
|
let backupFile = null;
|
|
404
|
+
let originalContent;
|
|
158
405
|
try {
|
|
159
406
|
if (!existsSync(configDir)) {
|
|
160
407
|
mkdirSync(configDir, { recursive: true, mode: 493 });
|
|
@@ -164,24 +411,22 @@ function registerInConfig(configDir) {
|
|
|
164
411
|
let fileExisted = false;
|
|
165
412
|
if (existsSync(configFile)) {
|
|
166
413
|
fileExisted = true;
|
|
167
|
-
const rawContent = readFileSync(configFile, "utf-8")
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
parseError = err;
|
|
174
|
-
}
|
|
175
|
-
if (parseError) {
|
|
414
|
+
const rawContent = readFileSync(configFile, "utf-8");
|
|
415
|
+
originalContent = rawContent;
|
|
416
|
+
const trimmedContent = rawContent.trim();
|
|
417
|
+
if (trimmedContent) {
|
|
418
|
+
const parsed = parseConfigContent(trimmedContent);
|
|
419
|
+
if (parsed.parseError) {
|
|
176
420
|
backupFile = createBackup(configFile);
|
|
177
|
-
log("Corrupted config JSON, skipping this path to avoid data loss", { configFile });
|
|
178
|
-
console.log(`\u26A0\uFE0F opencode
|
|
421
|
+
log("Corrupted config JSON, skipping this path to avoid data loss", { configFile, parseError: parsed.parseError });
|
|
422
|
+
console.log(`\u26A0\uFE0F opencode config at ${configFile} has invalid JSON/JSONC and was skipped.`);
|
|
179
423
|
if (backupFile) {
|
|
180
424
|
console.log(` Backup saved: ${backupFile}`);
|
|
181
425
|
}
|
|
182
426
|
console.log(` Please fix the file manually, then add "${PLUGIN_NAME}" to the "plugin" array.`);
|
|
183
427
|
return { success: false, backupFile, skipped: true };
|
|
184
428
|
}
|
|
429
|
+
config = parsed.config ?? {};
|
|
185
430
|
if (!validateConfig(config)) {
|
|
186
431
|
log("Unexpected config structure, skipping to avoid corruption", { config, configFile });
|
|
187
432
|
console.log(`\u26A0\uFE0F Unexpected config structure in ${configFile}. Skipping to avoid corruption.`);
|
|
@@ -198,7 +443,7 @@ function registerInConfig(configDir) {
|
|
|
198
443
|
}
|
|
199
444
|
const hasPlugin = config.plugin.some((p) => {
|
|
200
445
|
if (typeof p !== "string") return false;
|
|
201
|
-
return p
|
|
446
|
+
return isOurPluginEntry(p);
|
|
202
447
|
});
|
|
203
448
|
if (hasPlugin) {
|
|
204
449
|
log("Plugin already registered", { configFile });
|
|
@@ -209,11 +454,15 @@ function registerInConfig(configDir) {
|
|
|
209
454
|
}
|
|
210
455
|
config.plugin.push(PLUGIN_NAME);
|
|
211
456
|
log("Adding plugin to config", { plugin: PLUGIN_NAME, configFile });
|
|
212
|
-
atomicWriteJSON(configFile, config);
|
|
457
|
+
atomicWriteJSON(configFile, config, originalContent);
|
|
213
458
|
try {
|
|
214
459
|
const verifyContent = readFileSync(configFile, "utf-8");
|
|
215
|
-
const
|
|
216
|
-
if (!
|
|
460
|
+
const verifyParsed = parseConfigContent(verifyContent);
|
|
461
|
+
if (verifyParsed.parseError || !verifyParsed.config) {
|
|
462
|
+
throw new Error(`Verification parse failed: ${verifyParsed.parseError ?? "unknown parse error"}`);
|
|
463
|
+
}
|
|
464
|
+
const verifyConfig = verifyParsed.config;
|
|
465
|
+
if (!verifyConfig.plugin?.some((p) => isOurPluginEntry(p))) {
|
|
217
466
|
throw new Error("Verification failed: plugin not found after write");
|
|
218
467
|
}
|
|
219
468
|
} catch (verifyError) {
|
|
@@ -242,9 +491,10 @@ function registerInConfig(configDir) {
|
|
|
242
491
|
}
|
|
243
492
|
function cleanupOldBackups(configFile) {
|
|
244
493
|
try {
|
|
245
|
-
const configDir =
|
|
494
|
+
const configDir = dirname(configFile);
|
|
495
|
+
const configBase = basename(configFile);
|
|
246
496
|
const files = readdirSync(configDir);
|
|
247
|
-
const backupFiles = files.filter((f) => f.startsWith(
|
|
497
|
+
const backupFiles = files.filter((f) => f.startsWith(`${configBase}.backup.`)).sort().reverse();
|
|
248
498
|
for (let i = 5; i < backupFiles.length; i++) {
|
|
249
499
|
const backupPath = join(configDir, backupFiles[i]);
|
|
250
500
|
try {
|
|
@@ -257,32 +507,37 @@ function cleanupOldBackups(configFile) {
|
|
|
257
507
|
}
|
|
258
508
|
}
|
|
259
509
|
try {
|
|
510
|
+
if (isCI) log("Running in CI mode");
|
|
260
511
|
console.log("\u{1F3AF} OpenCode Orchestrator - Installing...");
|
|
261
512
|
log("Installation started", { platform: process.platform, node: process.version });
|
|
513
|
+
if (isCI) {
|
|
514
|
+
console.log("\u2139\uFE0F CI environment detected. Skipping automatic plugin registration.");
|
|
515
|
+
log("Skipping automatic plugin registration in CI");
|
|
516
|
+
clearTimeout(timeoutId);
|
|
517
|
+
process.exit(0);
|
|
518
|
+
}
|
|
262
519
|
const configPaths = getConfigPaths();
|
|
263
520
|
log("Config paths to check", configPaths);
|
|
264
521
|
let registered = false;
|
|
265
522
|
let alreadyRegistered = false;
|
|
266
523
|
let skippedCorrupt = false;
|
|
267
524
|
let backupCreated = null;
|
|
525
|
+
let targetConfigDir = configPaths[0];
|
|
268
526
|
for (const configDir of configPaths) {
|
|
269
|
-
const
|
|
270
|
-
if (
|
|
271
|
-
|
|
272
|
-
const content = readFileSync(configFile, "utf-8").trim();
|
|
273
|
-
if (content) {
|
|
274
|
-
const config = JSON.parse(content);
|
|
275
|
-
if (config.plugin?.some((p) => typeof p === "string" && (p === PLUGIN_NAME || p.includes(PLUGIN_NAME)))) {
|
|
276
|
-
alreadyRegistered = true;
|
|
277
|
-
log("Plugin already registered in this location", { configFile });
|
|
278
|
-
continue;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
} catch (error) {
|
|
282
|
-
log("Error checking existing config", { error: String(error), configFile });
|
|
283
|
-
}
|
|
527
|
+
const existing = readExistingConfig(configDir);
|
|
528
|
+
if (!existing) {
|
|
529
|
+
continue;
|
|
284
530
|
}
|
|
285
|
-
|
|
531
|
+
targetConfigDir = configDir;
|
|
532
|
+
if (existing.config.plugin?.some((p) => typeof p === "string" && isOurPluginEntry(p))) {
|
|
533
|
+
alreadyRegistered = true;
|
|
534
|
+
log("Plugin already registered in this location", { configFile: existing.file });
|
|
535
|
+
break;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
if (!alreadyRegistered && targetConfigDir) {
|
|
539
|
+
const configFile = resolveConfigFile(targetConfigDir);
|
|
540
|
+
const result = registerInConfig(targetConfigDir);
|
|
286
541
|
if (result.skipped) {
|
|
287
542
|
skippedCorrupt = true;
|
|
288
543
|
if (result.backupFile) backupCreated = result.backupFile;
|
|
@@ -310,6 +565,7 @@ try {
|
|
|
310
565
|
console.log(` Check logs: ${LOG_FILE}`);
|
|
311
566
|
log("Failed to register plugin in any location");
|
|
312
567
|
}
|
|
568
|
+
clearTimeout(timeoutId);
|
|
313
569
|
console.log("");
|
|
314
570
|
console.log("\u{1F680} Ready! Restart OpenCode to use.");
|
|
315
571
|
console.log("");
|
|
@@ -319,4 +575,6 @@ try {
|
|
|
319
575
|
console.error("\u274C " + formatError(error, "register plugin"));
|
|
320
576
|
console.log(` Check logs: ${LOG_FILE}`);
|
|
321
577
|
process.exit(0);
|
|
578
|
+
} finally {
|
|
579
|
+
clearTimeout(timeoutId);
|
|
322
580
|
}
|