oh-my-opencode-slim 2.0.1 → 2.0.3
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.ja-JP.md +31 -1
- package/README.ko-KR.md +31 -1
- package/README.md +41 -2
- package/README.zh-CN.md +31 -1
- package/dist/agents/orchestrator.d.ts +0 -2
- package/dist/cli/companion.d.ts +2 -2
- package/dist/cli/index.js +326 -89
- package/dist/companion/manager.d.ts +1 -0
- package/dist/companion/updater.d.ts +36 -0
- package/dist/config/agent-mcps.d.ts +0 -4
- package/dist/config/constants.d.ts +1 -7
- package/dist/config/council-schema.d.ts +0 -15
- package/dist/config/index.d.ts +1 -1
- package/dist/config/runtime-preset.d.ts +0 -1
- package/dist/config/schema.d.ts +78 -68
- package/dist/config/utils.d.ts +1 -0
- package/dist/hooks/auto-update-checker/skill-sync.d.ts +9 -0
- package/dist/hooks/auto-update-checker/types.d.ts +2 -0
- package/dist/hooks/filter-available-skills/index.d.ts +1 -13
- package/dist/hooks/foreground-fallback/index.d.ts +1 -1
- package/dist/hooks/image-hook.d.ts +1 -13
- package/dist/hooks/index.d.ts +3 -2
- package/dist/hooks/phase-reminder/index.d.ts +10 -16
- package/dist/hooks/reflect/index.d.ts +13 -0
- package/dist/hooks/task-session-manager/index.d.ts +2 -16
- package/dist/hooks/types.d.ts +23 -0
- package/dist/index.js +1610 -585
- package/dist/tools/acp-run.d.ts +3 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/smartfetch/secondary-model.d.ts +7 -0
- package/dist/tui.js +114 -76
- package/dist/utils/agent-variant.d.ts +0 -40
- package/dist/utils/compat.d.ts +0 -1
- package/dist/utils/guards.d.ts +4 -0
- package/dist/utils/index.d.ts +1 -2
- package/dist/utils/logger.d.ts +1 -1
- package/dist/utils/task.d.ts +0 -2
- package/oh-my-opencode-slim.schema.json +103 -249
- package/package.json +2 -1
- package/src/companion/companion-manifest.json +12 -0
- package/src/skills/codemap.md +4 -1
- package/src/skills/reflect/SKILL.md +193 -0
- package/src/skills/worktrees/SKILL.md +164 -0
- package/dist/config/fallback-chains.d.ts +0 -1
- package/dist/hooks/apply-patch/patch.d.ts +0 -2
- package/dist/hooks/delegate-task-retry/guidance.d.ts +0 -2
- package/dist/hooks/delegate-task-retry/index.d.ts +0 -4
- package/dist/hooks/json-error-recovery/index.d.ts +0 -1
- package/dist/utils/env.d.ts +0 -1
package/dist/index.js
CHANGED
|
@@ -30,8 +30,160 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
30
30
|
return to;
|
|
31
31
|
};
|
|
32
32
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
33
|
+
var __returnValue = (v) => v;
|
|
34
|
+
function __exportSetter(name, newValue) {
|
|
35
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
36
|
+
}
|
|
37
|
+
var __export = (target, all) => {
|
|
38
|
+
for (var name in all)
|
|
39
|
+
__defProp(target, name, {
|
|
40
|
+
get: all[name],
|
|
41
|
+
enumerable: true,
|
|
42
|
+
configurable: true,
|
|
43
|
+
set: __exportSetter.bind(all, name)
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
33
47
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
34
48
|
|
|
49
|
+
// src/utils/compat.ts
|
|
50
|
+
import { spawn as nodeSpawn } from "node:child_process";
|
|
51
|
+
import { writeFile as fsWriteFile } from "node:fs/promises";
|
|
52
|
+
function collectStream(stream) {
|
|
53
|
+
if (!stream)
|
|
54
|
+
return () => Promise.resolve("");
|
|
55
|
+
const chunks = [];
|
|
56
|
+
stream.on("data", (chunk) => chunks.push(chunk));
|
|
57
|
+
return () => new Promise((resolve, reject) => {
|
|
58
|
+
if (!stream.readable) {
|
|
59
|
+
resolve(Buffer.concat(chunks).toString("utf-8"));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
|
|
63
|
+
stream.on("error", reject);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function crossSpawn(command, options) {
|
|
67
|
+
const [cmd, ...args] = command;
|
|
68
|
+
const proc = nodeSpawn(cmd, args, {
|
|
69
|
+
stdio: [
|
|
70
|
+
options?.stdin ?? "ignore",
|
|
71
|
+
options?.stdout ?? "pipe",
|
|
72
|
+
options?.stderr ?? "pipe"
|
|
73
|
+
],
|
|
74
|
+
cwd: options?.cwd,
|
|
75
|
+
env: options?.env
|
|
76
|
+
});
|
|
77
|
+
const stdoutCollector = collectStream(proc.stdout);
|
|
78
|
+
const stderrCollector = collectStream(proc.stderr);
|
|
79
|
+
const exited = new Promise((resolve, reject) => {
|
|
80
|
+
proc.on("error", reject);
|
|
81
|
+
proc.on("close", (code) => resolve(code ?? 1));
|
|
82
|
+
});
|
|
83
|
+
return {
|
|
84
|
+
proc,
|
|
85
|
+
stdout: stdoutCollector,
|
|
86
|
+
stderr: stderrCollector,
|
|
87
|
+
exited,
|
|
88
|
+
kill: (signal) => proc.kill(signal),
|
|
89
|
+
get exitCode() {
|
|
90
|
+
return proc.exitCode;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
async function crossWrite(path, data) {
|
|
95
|
+
await fsWriteFile(path, Buffer.from(data));
|
|
96
|
+
}
|
|
97
|
+
var init_compat = () => {};
|
|
98
|
+
|
|
99
|
+
// src/utils/zip-extractor.ts
|
|
100
|
+
var exports_zip_extractor = {};
|
|
101
|
+
__export(exports_zip_extractor, {
|
|
102
|
+
extractZip: () => extractZip
|
|
103
|
+
});
|
|
104
|
+
import { spawnSync } from "node:child_process";
|
|
105
|
+
import { release } from "node:os";
|
|
106
|
+
function getWindowsBuildNumber() {
|
|
107
|
+
if (process.platform !== "win32")
|
|
108
|
+
return null;
|
|
109
|
+
const parts = release().split(".");
|
|
110
|
+
if (parts.length >= 3) {
|
|
111
|
+
const build = parseInt(parts[2], 10);
|
|
112
|
+
if (!Number.isNaN(build))
|
|
113
|
+
return build;
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
function isPwshAvailable() {
|
|
118
|
+
if (process.platform !== "win32")
|
|
119
|
+
return false;
|
|
120
|
+
const result = spawnSync("where", ["pwsh"], {
|
|
121
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
122
|
+
});
|
|
123
|
+
return result.status === 0;
|
|
124
|
+
}
|
|
125
|
+
function escapePowerShellPath(path4) {
|
|
126
|
+
return path4.replace(/'/g, "''");
|
|
127
|
+
}
|
|
128
|
+
function getWindowsZipExtractor() {
|
|
129
|
+
const buildNumber = getWindowsBuildNumber();
|
|
130
|
+
if (buildNumber !== null && buildNumber >= WINDOWS_BUILD_WITH_TAR) {
|
|
131
|
+
return "tar";
|
|
132
|
+
}
|
|
133
|
+
if (isPwshAvailable()) {
|
|
134
|
+
return "pwsh";
|
|
135
|
+
}
|
|
136
|
+
return "powershell";
|
|
137
|
+
}
|
|
138
|
+
async function extractZip(archivePath, destDir) {
|
|
139
|
+
let proc;
|
|
140
|
+
if (process.platform === "win32") {
|
|
141
|
+
const extractor = getWindowsZipExtractor();
|
|
142
|
+
switch (extractor) {
|
|
143
|
+
case "tar":
|
|
144
|
+
proc = crossSpawn(["tar", "-xf", archivePath, "-C", destDir], {
|
|
145
|
+
stdout: "ignore",
|
|
146
|
+
stderr: "pipe"
|
|
147
|
+
});
|
|
148
|
+
break;
|
|
149
|
+
case "pwsh":
|
|
150
|
+
proc = crossSpawn([
|
|
151
|
+
"pwsh",
|
|
152
|
+
"-Command",
|
|
153
|
+
`Expand-Archive -Path '${escapePowerShellPath(archivePath)}' -DestinationPath '${escapePowerShellPath(destDir)}' -Force`
|
|
154
|
+
], {
|
|
155
|
+
stdout: "ignore",
|
|
156
|
+
stderr: "pipe"
|
|
157
|
+
});
|
|
158
|
+
break;
|
|
159
|
+
default:
|
|
160
|
+
proc = crossSpawn([
|
|
161
|
+
"powershell",
|
|
162
|
+
"-Command",
|
|
163
|
+
`Expand-Archive -Path '${escapePowerShellPath(archivePath)}' -DestinationPath '${escapePowerShellPath(destDir)}' -Force`
|
|
164
|
+
], {
|
|
165
|
+
stdout: "ignore",
|
|
166
|
+
stderr: "pipe"
|
|
167
|
+
});
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
} else {
|
|
171
|
+
proc = crossSpawn(["unzip", "-o", archivePath, "-d", destDir], {
|
|
172
|
+
stdout: "ignore",
|
|
173
|
+
stderr: "pipe"
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
const exitCode = await proc.exited;
|
|
177
|
+
if (exitCode !== 0) {
|
|
178
|
+
const stderr = await proc.stderr();
|
|
179
|
+
throw new Error(`zip extraction failed (exit ${exitCode}): ${stderr}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
var WINDOWS_BUILD_WITH_TAR = 17134;
|
|
183
|
+
var init_zip_extractor = __esm(() => {
|
|
184
|
+
init_compat();
|
|
185
|
+
});
|
|
186
|
+
|
|
35
187
|
// node_modules/.pnpm/@mozilla+readability@0.6.0/node_modules/@mozilla/readability/Readability.js
|
|
36
188
|
var require_Readability = __commonJS((exports, module) => {
|
|
37
189
|
function Readability(doc, options) {
|
|
@@ -6246,33 +6398,33 @@ var require_URL = __commonJS((exports, module) => {
|
|
|
6246
6398
|
else
|
|
6247
6399
|
return basepath.substring(0, lastslash + 1) + refpath;
|
|
6248
6400
|
}
|
|
6249
|
-
function remove_dot_segments(
|
|
6250
|
-
if (!
|
|
6251
|
-
return
|
|
6401
|
+
function remove_dot_segments(path19) {
|
|
6402
|
+
if (!path19)
|
|
6403
|
+
return path19;
|
|
6252
6404
|
var output = "";
|
|
6253
|
-
while (
|
|
6254
|
-
if (
|
|
6255
|
-
|
|
6405
|
+
while (path19.length > 0) {
|
|
6406
|
+
if (path19 === "." || path19 === "..") {
|
|
6407
|
+
path19 = "";
|
|
6256
6408
|
break;
|
|
6257
6409
|
}
|
|
6258
|
-
var twochars =
|
|
6259
|
-
var threechars =
|
|
6260
|
-
var fourchars =
|
|
6410
|
+
var twochars = path19.substring(0, 2);
|
|
6411
|
+
var threechars = path19.substring(0, 3);
|
|
6412
|
+
var fourchars = path19.substring(0, 4);
|
|
6261
6413
|
if (threechars === "../") {
|
|
6262
|
-
|
|
6414
|
+
path19 = path19.substring(3);
|
|
6263
6415
|
} else if (twochars === "./") {
|
|
6264
|
-
|
|
6416
|
+
path19 = path19.substring(2);
|
|
6265
6417
|
} else if (threechars === "/./") {
|
|
6266
|
-
|
|
6267
|
-
} else if (twochars === "/." &&
|
|
6268
|
-
|
|
6269
|
-
} else if (fourchars === "/../" || threechars === "/.." &&
|
|
6270
|
-
|
|
6418
|
+
path19 = "/" + path19.substring(3);
|
|
6419
|
+
} else if (twochars === "/." && path19.length === 2) {
|
|
6420
|
+
path19 = "/";
|
|
6421
|
+
} else if (fourchars === "/../" || threechars === "/.." && path19.length === 3) {
|
|
6422
|
+
path19 = "/" + path19.substring(4);
|
|
6271
6423
|
output = output.replace(/\/?[^\/]*$/, "");
|
|
6272
6424
|
} else {
|
|
6273
|
-
var segment =
|
|
6425
|
+
var segment = path19.match(/(\/?([^\/]*))/)[0];
|
|
6274
6426
|
output += segment;
|
|
6275
|
-
|
|
6427
|
+
path19 = path19.substring(segment.length);
|
|
6276
6428
|
}
|
|
6277
6429
|
}
|
|
6278
6430
|
return output;
|
|
@@ -18150,14 +18302,14 @@ var require_turndown_cjs = __commonJS((exports, module) => {
|
|
|
18150
18302
|
} else if (node.nodeType === 1) {
|
|
18151
18303
|
replacement = replacementForNode.call(self, node);
|
|
18152
18304
|
}
|
|
18153
|
-
return
|
|
18305
|
+
return join17(output, replacement);
|
|
18154
18306
|
}, "");
|
|
18155
18307
|
}
|
|
18156
18308
|
function postProcess(output) {
|
|
18157
18309
|
var self = this;
|
|
18158
18310
|
this.rules.forEach(function(rule) {
|
|
18159
18311
|
if (typeof rule.append === "function") {
|
|
18160
|
-
output =
|
|
18312
|
+
output = join17(output, rule.append(self.options));
|
|
18161
18313
|
}
|
|
18162
18314
|
});
|
|
18163
18315
|
return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
|
|
@@ -18170,7 +18322,7 @@ var require_turndown_cjs = __commonJS((exports, module) => {
|
|
|
18170
18322
|
content = content.trim();
|
|
18171
18323
|
return whitespace.leading + rule.replacement(content, node, this.options) + whitespace.trailing;
|
|
18172
18324
|
}
|
|
18173
|
-
function
|
|
18325
|
+
function join17(output, replacement) {
|
|
18174
18326
|
var s1 = trimTrailingNewlines(output);
|
|
18175
18327
|
var s2 = trimLeadingNewlines(replacement);
|
|
18176
18328
|
var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
|
|
@@ -18240,11 +18392,23 @@ var CUSTOM_SKILLS = [
|
|
|
18240
18392
|
allowedAgents: ["orchestrator"],
|
|
18241
18393
|
sourcePath: "src/skills/deepwork"
|
|
18242
18394
|
},
|
|
18395
|
+
{
|
|
18396
|
+
name: "reflect",
|
|
18397
|
+
description: "Review repeated work and suggest reusable workflow improvements",
|
|
18398
|
+
allowedAgents: ["orchestrator"],
|
|
18399
|
+
sourcePath: "src/skills/reflect"
|
|
18400
|
+
},
|
|
18243
18401
|
{
|
|
18244
18402
|
name: "oh-my-opencode-slim",
|
|
18245
18403
|
description: "Configure, customize, and safely improve oh-my-opencode-slim setups",
|
|
18246
18404
|
allowedAgents: ["orchestrator"],
|
|
18247
18405
|
sourcePath: "src/skills/oh-my-opencode-slim"
|
|
18406
|
+
},
|
|
18407
|
+
{
|
|
18408
|
+
name: "worktrees",
|
|
18409
|
+
description: "Manage Git worktrees as OMO safe isolated coding lanes for complex/risky/parallel work",
|
|
18410
|
+
allowedAgents: ["orchestrator"],
|
|
18411
|
+
sourcePath: "src/skills/worktrees"
|
|
18248
18412
|
}
|
|
18249
18413
|
];
|
|
18250
18414
|
|
|
@@ -18303,8 +18467,7 @@ var SUBAGENT_NAMES = [
|
|
|
18303
18467
|
"council",
|
|
18304
18468
|
"councillor"
|
|
18305
18469
|
];
|
|
18306
|
-
var
|
|
18307
|
-
var ALL_AGENT_NAMES = [ORCHESTRATOR_NAME, ...SUBAGENT_NAMES];
|
|
18470
|
+
var ALL_AGENT_NAMES = ["orchestrator", ...SUBAGENT_NAMES];
|
|
18308
18471
|
var PROTECTED_AGENTS = new Set(["orchestrator", "councillor"]);
|
|
18309
18472
|
var DEFAULT_MODELS = {
|
|
18310
18473
|
orchestrator: undefined,
|
|
@@ -18318,10 +18481,10 @@ var DEFAULT_MODELS = {
|
|
|
18318
18481
|
councillor: "openai/gpt-5.4-mini"
|
|
18319
18482
|
};
|
|
18320
18483
|
var POLL_INTERVAL_BACKGROUND_MS = 2000;
|
|
18321
|
-
var DEFAULT_TIMEOUT_MS = 2 * 60 * 1000;
|
|
18322
18484
|
var MAX_POLL_TIME_MS = 5 * 60 * 1000;
|
|
18323
18485
|
var DEFAULT_MAX_SUBAGENT_DEPTH = 3;
|
|
18324
18486
|
var PHASE_REMINDER_TEXT = `!IMPORTANT! Scheduler workflow: plan lanes/dependencies → dispatch background specialists → track task IDs → wait for hook-driven completion → reconcile terminal results → verify. Do not poll running jobs, consume running-job output, or advance dependent work. !END!`;
|
|
18487
|
+
var PHASE_REMINDER = `<internal_reminder>${PHASE_REMINDER_TEXT}</internal_reminder>`;
|
|
18325
18488
|
var WRITABLE_FILE_OPERATIONS_RULES = `**File Operations Rules**:
|
|
18326
18489
|
- Prefer dedicated file tools for normal code work: glob/grep/ast_grep_search for discovery, read for file contents, and edit/write/apply_patch for targeted source changes.
|
|
18327
18490
|
- Use bash for execution and automation: git, package managers, tests, builds, scripts, diagnostics, and shell-native filesystem operations.
|
|
@@ -18386,17 +18549,11 @@ var CouncilConfigSchema = z.object({
|
|
|
18386
18549
|
default_preset: z.string().default("default"),
|
|
18387
18550
|
councillor_execution_mode: CouncillorExecutionModeSchema.describe('Execution mode for councillors. "serial" runs them one at a time (required for single-model systems). "parallel" runs them concurrently (default, faster for multi-model systems).'),
|
|
18388
18551
|
councillor_retries: z.number().int().min(0).max(5).default(3).describe("Number of retry attempts for councillors that return empty responses " + "(e.g. due to provider rate limiting). Default: 3 retries."),
|
|
18389
|
-
master: z.unknown().optional().describe("DEPRECATED — ignored. Council agent synthesizes directly.")
|
|
18390
|
-
master_timeout: z.unknown().optional().describe('DEPRECATED — ignored. Use "timeout" instead.'),
|
|
18391
|
-
master_fallback: z.unknown().optional().describe("DEPRECATED — ignored. No separate master session.")
|
|
18552
|
+
master: z.unknown().optional().describe("DEPRECATED — ignored. Council agent synthesizes directly.")
|
|
18392
18553
|
}).transform((data) => {
|
|
18393
18554
|
const deprecated = [];
|
|
18394
18555
|
if (data.master !== undefined)
|
|
18395
18556
|
deprecated.push("master");
|
|
18396
|
-
if (data.master_timeout !== undefined)
|
|
18397
|
-
deprecated.push("master_timeout");
|
|
18398
|
-
if (data.master_fallback !== undefined)
|
|
18399
|
-
deprecated.push("master_fallback");
|
|
18400
18557
|
const legacyMasterModel = typeof data.master === "object" && data.master !== null && "model" in data.master && typeof data.master.model === "string" ? data.master.model : undefined;
|
|
18401
18558
|
return {
|
|
18402
18559
|
presets: data.presets,
|
|
@@ -18412,55 +18569,8 @@ var CouncilConfigSchema = z.object({
|
|
|
18412
18569
|
import * as fs from "node:fs";
|
|
18413
18570
|
import * as path from "node:path";
|
|
18414
18571
|
|
|
18415
|
-
// src/
|
|
18416
|
-
|
|
18417
|
-
import { writeFile as fsWriteFile } from "node:fs/promises";
|
|
18418
|
-
var isBun = typeof globalThis.Bun !== "undefined";
|
|
18419
|
-
function collectStream(stream) {
|
|
18420
|
-
if (!stream)
|
|
18421
|
-
return () => Promise.resolve("");
|
|
18422
|
-
const chunks = [];
|
|
18423
|
-
stream.on("data", (chunk) => chunks.push(chunk));
|
|
18424
|
-
return () => new Promise((resolve, reject) => {
|
|
18425
|
-
if (!stream.readable) {
|
|
18426
|
-
resolve(Buffer.concat(chunks).toString("utf-8"));
|
|
18427
|
-
return;
|
|
18428
|
-
}
|
|
18429
|
-
stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
|
|
18430
|
-
stream.on("error", reject);
|
|
18431
|
-
});
|
|
18432
|
-
}
|
|
18433
|
-
function crossSpawn(command, options) {
|
|
18434
|
-
const [cmd, ...args] = command;
|
|
18435
|
-
const proc = nodeSpawn(cmd, args, {
|
|
18436
|
-
stdio: [
|
|
18437
|
-
options?.stdin ?? "ignore",
|
|
18438
|
-
options?.stdout ?? "pipe",
|
|
18439
|
-
options?.stderr ?? "pipe"
|
|
18440
|
-
],
|
|
18441
|
-
cwd: options?.cwd,
|
|
18442
|
-
env: options?.env
|
|
18443
|
-
});
|
|
18444
|
-
const stdoutCollector = collectStream(proc.stdout);
|
|
18445
|
-
const stderrCollector = collectStream(proc.stderr);
|
|
18446
|
-
const exited = new Promise((resolve, reject) => {
|
|
18447
|
-
proc.on("error", reject);
|
|
18448
|
-
proc.on("close", (code) => resolve(code ?? 1));
|
|
18449
|
-
});
|
|
18450
|
-
return {
|
|
18451
|
-
proc,
|
|
18452
|
-
stdout: stdoutCollector,
|
|
18453
|
-
stderr: stderrCollector,
|
|
18454
|
-
exited,
|
|
18455
|
-
kill: (signal) => proc.kill(signal),
|
|
18456
|
-
get exitCode() {
|
|
18457
|
-
return proc.exitCode;
|
|
18458
|
-
}
|
|
18459
|
-
};
|
|
18460
|
-
}
|
|
18461
|
-
async function crossWrite(path, data) {
|
|
18462
|
-
await fsWriteFile(path, Buffer.from(data));
|
|
18463
|
-
}
|
|
18572
|
+
// src/cli/config-io.ts
|
|
18573
|
+
init_compat();
|
|
18464
18574
|
|
|
18465
18575
|
// src/config/agent-mcps.ts
|
|
18466
18576
|
var DEFAULT_AGENT_MCPS = {
|
|
@@ -18534,15 +18644,6 @@ var ManualPlanSchema = z2.object({
|
|
|
18534
18644
|
librarian: ManualAgentPlanSchema,
|
|
18535
18645
|
fixer: ManualAgentPlanSchema
|
|
18536
18646
|
}).strict();
|
|
18537
|
-
var AgentModelChainSchema = z2.array(z2.string()).min(1);
|
|
18538
|
-
var FallbackChainsSchema = z2.object({
|
|
18539
|
-
orchestrator: AgentModelChainSchema.optional(),
|
|
18540
|
-
oracle: AgentModelChainSchema.optional(),
|
|
18541
|
-
designer: AgentModelChainSchema.optional(),
|
|
18542
|
-
explorer: AgentModelChainSchema.optional(),
|
|
18543
|
-
librarian: AgentModelChainSchema.optional(),
|
|
18544
|
-
fixer: AgentModelChainSchema.optional()
|
|
18545
|
-
}).catchall(AgentModelChainSchema);
|
|
18546
18647
|
var AgentOverrideConfigSchema = z2.object({
|
|
18547
18648
|
model: z2.union([
|
|
18548
18649
|
z2.string(),
|
|
@@ -18605,14 +18706,32 @@ var FailoverConfigSchema = z2.object({
|
|
|
18605
18706
|
enabled: z2.boolean().default(true),
|
|
18606
18707
|
timeoutMs: z2.number().min(0).default(15000),
|
|
18607
18708
|
retryDelayMs: z2.number().min(0).default(500),
|
|
18608
|
-
chains: FallbackChainsSchema.default({}),
|
|
18609
18709
|
retry_on_empty: z2.boolean().default(true).describe("When true (default), empty provider responses are treated as failures, " + "triggering fallback/retry. Set to false to treat them as successes.")
|
|
18610
|
-
});
|
|
18710
|
+
}).strict();
|
|
18611
18711
|
var CompanionConfigSchema = z2.object({
|
|
18612
18712
|
enabled: z2.boolean().optional(),
|
|
18713
|
+
binaryPath: z2.string().min(1).optional().describe("Path to a custom companion binary to launch."),
|
|
18613
18714
|
position: z2.enum(["bottom-right", "bottom-left", "top-right", "top-left"]).optional(),
|
|
18614
|
-
size: z2.enum(["small", "medium", "large"]).optional()
|
|
18715
|
+
size: z2.enum(["small", "medium", "large"]).optional(),
|
|
18716
|
+
gifPack: z2.enum(["default"]).optional().describe("Bundled companion animation pack to use."),
|
|
18717
|
+
loopStyle: z2.enum(["classic", "smooth"]).optional().describe("Companion animation playback style: classic loops or smooth ping-pong playback."),
|
|
18718
|
+
speed: z2.number().min(0.25).max(4).optional().describe("Companion animation playback speed multiplier. Defaults to 1."),
|
|
18719
|
+
debug: z2.boolean().optional().describe("Enable verbose native companion debug logs.")
|
|
18615
18720
|
});
|
|
18721
|
+
var AcpAgentPermissionModeSchema = z2.enum(["ask", "allow", "reject"]);
|
|
18722
|
+
var AcpAgentConfigSchema = z2.object({
|
|
18723
|
+
command: z2.string().min(1),
|
|
18724
|
+
args: z2.array(z2.string()).default([]),
|
|
18725
|
+
env: z2.record(z2.string(), z2.string()).default({}),
|
|
18726
|
+
cwd: z2.string().min(1).optional(),
|
|
18727
|
+
description: z2.string().min(1).optional(),
|
|
18728
|
+
prompt: z2.string().min(1).optional(),
|
|
18729
|
+
orchestratorPrompt: z2.string().min(1).optional(),
|
|
18730
|
+
wrapperModel: ProviderModelIdSchema.optional(),
|
|
18731
|
+
timeoutMs: z2.number().int().min(1000).max(900000).default(300000),
|
|
18732
|
+
permissionMode: AcpAgentPermissionModeSchema.default("ask")
|
|
18733
|
+
}).strict();
|
|
18734
|
+
var AcpAgentsConfigSchema = z2.record(z2.string(), AcpAgentConfigSchema);
|
|
18616
18735
|
function validateCustomOnlyPromptFields(overrides, ctx, pathPrefix) {
|
|
18617
18736
|
for (const [name, override] of Object.entries(overrides)) {
|
|
18618
18737
|
const isBuiltInOrAlias = ALL_AGENT_NAMES.includes(name) || AGENT_ALIASES[name] !== undefined;
|
|
@@ -18638,10 +18757,7 @@ function validateCustomOnlyPromptFields(overrides, ctx, pathPrefix) {
|
|
|
18638
18757
|
var PluginConfigSchema = z2.object({
|
|
18639
18758
|
preset: z2.string().optional(),
|
|
18640
18759
|
setDefaultAgent: z2.boolean().optional(),
|
|
18641
|
-
scoringEngineVersion: z2.enum(["v1", "v2-shadow", "v2"]).optional(),
|
|
18642
|
-
balanceProviderUsage: z2.boolean().optional(),
|
|
18643
18760
|
autoUpdate: z2.boolean().optional().describe("Disable automatic installation of plugin updates when false. Defaults to true."),
|
|
18644
|
-
manualPlan: ManualPlanSchema.optional(),
|
|
18645
18761
|
presets: z2.record(z2.string(), PresetSchema).optional(),
|
|
18646
18762
|
agents: z2.record(z2.string(), AgentOverrideConfigSchema).optional(),
|
|
18647
18763
|
disabled_agents: z2.array(z2.string()).optional().describe("Agent names to disable completely. " + "Disabled agents are not instantiated and cannot be delegated to. " + "Orchestrator and council internal agents (councillor) cannot be disabled. " + "By default, 'observer' is disabled. Remove it from this list and configure a vision-capable model to enable."),
|
|
@@ -18653,7 +18769,8 @@ var PluginConfigSchema = z2.object({
|
|
|
18653
18769
|
backgroundJobs: BackgroundJobsConfigSchema.optional(),
|
|
18654
18770
|
fallback: FailoverConfigSchema.optional(),
|
|
18655
18771
|
council: CouncilConfigSchema.optional(),
|
|
18656
|
-
companion: CompanionConfigSchema.optional()
|
|
18772
|
+
companion: CompanionConfigSchema.optional(),
|
|
18773
|
+
acpAgents: AcpAgentsConfigSchema.optional()
|
|
18657
18774
|
}).superRefine((value, ctx) => {
|
|
18658
18775
|
if (value.agents) {
|
|
18659
18776
|
validateCustomOnlyPromptFields(value.agents, ctx, ["agents"]);
|
|
@@ -18753,6 +18870,7 @@ function mergePluginConfigs(base, override) {
|
|
|
18753
18870
|
backgroundJobs: deepMerge(base.backgroundJobs, override.backgroundJobs),
|
|
18754
18871
|
fallback: deepMerge(base.fallback, override.fallback),
|
|
18755
18872
|
council: deepMerge(base.council, override.council),
|
|
18873
|
+
acpAgents: deepMerge(base.acpAgents, override.acpAgents),
|
|
18756
18874
|
companion: deepMerge(base.companion, override.companion)
|
|
18757
18875
|
};
|
|
18758
18876
|
}
|
|
@@ -18806,8 +18924,13 @@ function loadPluginConfig(directory, options) {
|
|
|
18806
18924
|
if (config.companion) {
|
|
18807
18925
|
config.companion = {
|
|
18808
18926
|
enabled: config.companion.enabled ?? false,
|
|
18927
|
+
binaryPath: config.companion.binaryPath,
|
|
18809
18928
|
position: config.companion.position ?? "bottom-right",
|
|
18810
|
-
size: config.companion.size ?? "medium"
|
|
18929
|
+
size: config.companion.size ?? "medium",
|
|
18930
|
+
gifPack: config.companion.gifPack ?? "default",
|
|
18931
|
+
loopStyle: config.companion.loopStyle ?? "classic",
|
|
18932
|
+
speed: config.companion.speed ?? 1,
|
|
18933
|
+
debug: config.companion.debug ?? false
|
|
18811
18934
|
};
|
|
18812
18935
|
}
|
|
18813
18936
|
return config;
|
|
@@ -18868,6 +18991,9 @@ function getCustomAgentNames(config) {
|
|
|
18868
18991
|
return !ALL_AGENT_NAMES.includes(name);
|
|
18869
18992
|
});
|
|
18870
18993
|
}
|
|
18994
|
+
function getAcpAgentNames(config) {
|
|
18995
|
+
return Object.keys(config?.acpAgents ?? {});
|
|
18996
|
+
}
|
|
18871
18997
|
// src/utils/session.ts
|
|
18872
18998
|
var SESSION_ABORT_TIMEOUT_MS = 1000;
|
|
18873
18999
|
|
|
@@ -19201,7 +19327,6 @@ When user's approach seems problematic:
|
|
|
19201
19327
|
</Communication>
|
|
19202
19328
|
`;
|
|
19203
19329
|
}
|
|
19204
|
-
var ORCHESTRATOR_PROMPT = buildOrchestratorPrompt();
|
|
19205
19330
|
function createOrchestratorAgent(model, customPrompt, customAppendPrompt, disabledAgents) {
|
|
19206
19331
|
const basePrompt = buildOrchestratorPrompt(disabledAgents);
|
|
19207
19332
|
const prompt = resolvePrompt(basePrompt, customPrompt, customAppendPrompt);
|
|
@@ -19744,6 +19869,39 @@ function normalizeDisplayName(displayName) {
|
|
|
19744
19869
|
const trimmed = displayName.trim();
|
|
19745
19870
|
return trimmed.startsWith("@") ? trimmed.slice(1) : trimmed;
|
|
19746
19871
|
}
|
|
19872
|
+
function buildAcpAgentDefinition(name, config) {
|
|
19873
|
+
const description = config.description ?? `External ACP agent '${name}' via ${config.command}`;
|
|
19874
|
+
const prompt = config.prompt ?? [
|
|
19875
|
+
`You are the ${name} ACP wrapper agent.`,
|
|
19876
|
+
"",
|
|
19877
|
+
"Your only job is to send the user task to the configured external ACP agent using the acp_run tool, then return the ACP agent result.",
|
|
19878
|
+
`Always call acp_run with agent: ${JSON.stringify(name)} and pass the full user task as prompt.`,
|
|
19879
|
+
"Do not edit files yourself unless the ACP result explicitly asks you to report a local follow-up to the orchestrator."
|
|
19880
|
+
].join(`
|
|
19881
|
+
`);
|
|
19882
|
+
return {
|
|
19883
|
+
name,
|
|
19884
|
+
description,
|
|
19885
|
+
config: {
|
|
19886
|
+
model: config.wrapperModel ?? DEFAULT_MODELS.fixer ?? DEFAULT_MODELS.librarian ?? DEFAULT_MODELS.orchestrator ?? DEFAULT_MODELS.oracle,
|
|
19887
|
+
temperature: 0,
|
|
19888
|
+
prompt,
|
|
19889
|
+
permission: {
|
|
19890
|
+
read: "deny",
|
|
19891
|
+
edit: "deny",
|
|
19892
|
+
bash: "deny",
|
|
19893
|
+
task: "deny",
|
|
19894
|
+
glob: "deny",
|
|
19895
|
+
grep: "deny",
|
|
19896
|
+
list: "deny",
|
|
19897
|
+
webfetch: "deny",
|
|
19898
|
+
question: "deny",
|
|
19899
|
+
skill: "deny",
|
|
19900
|
+
acp_run: "allow"
|
|
19901
|
+
}
|
|
19902
|
+
}
|
|
19903
|
+
};
|
|
19904
|
+
}
|
|
19747
19905
|
function isSafeDisplayName(displayName) {
|
|
19748
19906
|
return SAFE_AGENT_ALIAS_RE.test(displayName);
|
|
19749
19907
|
}
|
|
@@ -19883,6 +20041,24 @@ function createAgents(config) {
|
|
|
19883
20041
|
buildCustomAgentDefinition(name, override, customPrompts.prompt, customPrompts.appendPrompt)
|
|
19884
20042
|
];
|
|
19885
20043
|
});
|
|
20044
|
+
const acpAgentNames = getAcpAgentNames(config).map(normalizeCustomAgentName).filter((name) => name.length > 0).filter((name) => {
|
|
20045
|
+
if (!SAFE_AGENT_ALIAS_RE.test(name)) {
|
|
20046
|
+
throw new Error(`ACP agent name '${name}' must match /^[a-z][a-z0-9_-]*$/i`);
|
|
20047
|
+
}
|
|
20048
|
+
if (isKnownAgentName(name) || AGENT_ALIASES[name] !== undefined) {
|
|
20049
|
+
throw new Error(`ACP agent '${name}' conflicts with a built-in agent name or alias`);
|
|
20050
|
+
}
|
|
20051
|
+
if (customAgentNames.includes(name)) {
|
|
20052
|
+
throw new Error(`ACP agent '${name}' conflicts with a custom agent of the same name`);
|
|
20053
|
+
}
|
|
20054
|
+
return !disabled.has(name);
|
|
20055
|
+
});
|
|
20056
|
+
const protoAcpAgents = acpAgentNames.map((name) => {
|
|
20057
|
+
const acp = config?.acpAgents?.[name];
|
|
20058
|
+
if (!acp)
|
|
20059
|
+
throw new Error(`ACP agent '${name}' is missing config`);
|
|
20060
|
+
return buildAcpAgentDefinition(name, acp);
|
|
20061
|
+
});
|
|
19886
20062
|
const builtInSubAgents = protoSubAgents.map((agent) => {
|
|
19887
20063
|
const override = getAgentOverride(config, agent.name);
|
|
19888
20064
|
if (override) {
|
|
@@ -19906,7 +20082,15 @@ function createAgents(config) {
|
|
|
19906
20082
|
applyDefaultPermissions(agent, override?.skills);
|
|
19907
20083
|
return agent;
|
|
19908
20084
|
});
|
|
19909
|
-
const
|
|
20085
|
+
const acpSubAgents = protoAcpAgents.map((agent) => {
|
|
20086
|
+
applyDefaultPermissions(agent);
|
|
20087
|
+
return agent;
|
|
20088
|
+
});
|
|
20089
|
+
const allSubAgents = [
|
|
20090
|
+
...builtInSubAgents,
|
|
20091
|
+
...customSubAgents,
|
|
20092
|
+
...acpSubAgents
|
|
20093
|
+
];
|
|
19910
20094
|
const orchestratorOverride = getAgentOverride(config, "orchestrator");
|
|
19911
20095
|
const orchestratorModel = orchestratorOverride?.model ?? DEFAULT_MODELS.orchestrator;
|
|
19912
20096
|
const orchestratorPrompts = loadAgentPrompt("orchestrator", config?.preset);
|
|
@@ -19928,6 +20112,20 @@ function createAgents(config) {
|
|
|
19928
20112
|
const override = getAgentOverride(config, agent.name);
|
|
19929
20113
|
return override?.orchestratorPrompt;
|
|
19930
20114
|
}).filter((prompt) => Boolean(prompt));
|
|
20115
|
+
const acpOrchestratorPrompts = acpSubAgents.map((agent) => {
|
|
20116
|
+
const acp = config?.acpAgents?.[agent.name];
|
|
20117
|
+
if (acp?.orchestratorPrompt)
|
|
20118
|
+
return acp.orchestratorPrompt;
|
|
20119
|
+
return [
|
|
20120
|
+
`@${agent.name}`,
|
|
20121
|
+
`- Lane: External ACP-connected agent (${acp?.command ?? "unknown command"})`,
|
|
20122
|
+
`- Role: ${agent.description ?? `External ACP agent ${agent.name}`}`,
|
|
20123
|
+
"- **Delegate when:** The user explicitly asks for this ACP-backed agent, or the task matches its role and benefits from software/subscription-specific capabilities outside OpenCode.",
|
|
20124
|
+
"- **Do not delegate when:** The built-in specialists can handle the task more directly or local file ownership would conflict with another writer lane.",
|
|
20125
|
+
"- **Result handling:** Treat returned output as external-agent work. Reconcile any reported file changes before continuing."
|
|
20126
|
+
].join(`
|
|
20127
|
+
`);
|
|
20128
|
+
});
|
|
19931
20129
|
const usedDisplayNames = new Set;
|
|
19932
20130
|
for (const [, displayName] of displayNameMap) {
|
|
19933
20131
|
const normalizedDisplayName = normalizeDisplayName(displayName);
|
|
@@ -19940,13 +20138,17 @@ function createAgents(config) {
|
|
|
19940
20138
|
usedDisplayNames.add(normalizedDisplayName);
|
|
19941
20139
|
}
|
|
19942
20140
|
for (const displayName of usedDisplayNames) {
|
|
19943
|
-
if (ALL_AGENT_NAMES.includes(displayName) || customAgentNames.includes(displayName)) {
|
|
20141
|
+
if (ALL_AGENT_NAMES.includes(displayName) || customAgentNames.includes(displayName) || acpAgentNames.includes(displayName)) {
|
|
19944
20142
|
throw new Error(`displayName '${displayName}' conflicts with an agent name`);
|
|
19945
20143
|
}
|
|
19946
20144
|
}
|
|
19947
20145
|
injectDisplayNames(orchestrator, displayNameMap);
|
|
19948
|
-
|
|
19949
|
-
|
|
20146
|
+
const extraOrchestratorPrompts = [
|
|
20147
|
+
...customOrchestratorPrompts,
|
|
20148
|
+
...acpOrchestratorPrompts
|
|
20149
|
+
];
|
|
20150
|
+
if (extraOrchestratorPrompts.length > 0) {
|
|
20151
|
+
const rewrittenPrompts = extraOrchestratorPrompts.map((promptText) => {
|
|
19950
20152
|
let text = promptText;
|
|
19951
20153
|
for (const [internalName, displayName] of displayNameMap) {
|
|
19952
20154
|
text = text.replace(new RegExp(`@${escapeRegExp(internalName)}\\b`, "g"), `@${normalizeDisplayName(displayName)}`);
|
|
@@ -20018,6 +20220,7 @@ import {
|
|
|
20018
20220
|
mkdirSync as mkdirSync2,
|
|
20019
20221
|
readFileSync as readFileSync2,
|
|
20020
20222
|
renameSync,
|
|
20223
|
+
rmSync,
|
|
20021
20224
|
writeFileSync
|
|
20022
20225
|
} from "node:fs";
|
|
20023
20226
|
import * as os2 from "node:os";
|
|
@@ -20106,11 +20309,15 @@ function stateFilePath() {
|
|
|
20106
20309
|
const base = xdg && path3.isAbsolute(xdg) ? xdg : path3.join(os2.homedir(), ".local", "share");
|
|
20107
20310
|
return path3.join(base, "opencode", "storage", "oh-my-opencode-slim", "companion-state.json");
|
|
20108
20311
|
}
|
|
20109
|
-
function
|
|
20312
|
+
function defaultBinaryPath() {
|
|
20110
20313
|
const xdg = process.env.XDG_DATA_HOME?.trim();
|
|
20111
20314
|
const base = xdg && path3.isAbsolute(xdg) ? xdg : path3.join(os2.homedir(), ".local", "share");
|
|
20112
20315
|
const binaryName = os2.platform() === "win32" ? "oh-my-opencode-slim-companion.exe" : "oh-my-opencode-slim-companion";
|
|
20113
|
-
|
|
20316
|
+
return path3.join(base, "opencode", "storage", "oh-my-opencode-slim", "bin", binaryName);
|
|
20317
|
+
}
|
|
20318
|
+
function resolveCompanionBinaryPath(config) {
|
|
20319
|
+
const configured = config?.binaryPath?.trim();
|
|
20320
|
+
const bin = configured || defaultBinaryPath();
|
|
20114
20321
|
return existsSync2(bin) ? bin : null;
|
|
20115
20322
|
}
|
|
20116
20323
|
function readState() {
|
|
@@ -20123,17 +20330,43 @@ function readState() {
|
|
|
20123
20330
|
} catch {}
|
|
20124
20331
|
return { version: 1, sessions: [] };
|
|
20125
20332
|
}
|
|
20126
|
-
function writeState(
|
|
20333
|
+
function writeState(mutator) {
|
|
20127
20334
|
const file = stateFilePath();
|
|
20128
20335
|
try {
|
|
20129
20336
|
mkdirSync2(path3.dirname(file), { recursive: true });
|
|
20130
|
-
const
|
|
20131
|
-
|
|
20132
|
-
|
|
20337
|
+
const release = acquireStateLock(file);
|
|
20338
|
+
try {
|
|
20339
|
+
const state = readState();
|
|
20340
|
+
mutator(state);
|
|
20341
|
+
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
20342
|
+
writeFileSync(tmp, JSON.stringify(state));
|
|
20343
|
+
renameSync(tmp, file);
|
|
20344
|
+
} finally {
|
|
20345
|
+
release();
|
|
20346
|
+
}
|
|
20133
20347
|
} catch (err) {
|
|
20134
20348
|
log("[companion] write failed", String(err));
|
|
20135
20349
|
}
|
|
20136
20350
|
}
|
|
20351
|
+
function acquireStateLock(file) {
|
|
20352
|
+
const lock = `${file}.lock`;
|
|
20353
|
+
for (let attempt = 0;attempt < 40; attempt++) {
|
|
20354
|
+
try {
|
|
20355
|
+
mkdirSync2(lock);
|
|
20356
|
+
return () => {
|
|
20357
|
+
try {
|
|
20358
|
+
rmSync(lock, { recursive: true, force: true });
|
|
20359
|
+
} catch {}
|
|
20360
|
+
};
|
|
20361
|
+
} catch (err) {
|
|
20362
|
+
const code = err.code;
|
|
20363
|
+
if (code !== "EEXIST")
|
|
20364
|
+
throw err;
|
|
20365
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
|
|
20366
|
+
}
|
|
20367
|
+
}
|
|
20368
|
+
throw new Error("timed out waiting for companion state lock");
|
|
20369
|
+
}
|
|
20137
20370
|
|
|
20138
20371
|
class CompanionManager {
|
|
20139
20372
|
id;
|
|
@@ -20149,12 +20382,11 @@ class CompanionManager {
|
|
|
20149
20382
|
onLoad() {
|
|
20150
20383
|
if (this.config?.enabled !== true) {
|
|
20151
20384
|
try {
|
|
20152
|
-
|
|
20153
|
-
|
|
20154
|
-
|
|
20155
|
-
state.sessions =
|
|
20156
|
-
|
|
20157
|
-
}
|
|
20385
|
+
if (!existsSync2(stateFilePath()))
|
|
20386
|
+
return;
|
|
20387
|
+
writeState((state) => {
|
|
20388
|
+
state.sessions = state.sessions.filter((s) => s.session_id !== this.id);
|
|
20389
|
+
});
|
|
20158
20390
|
} catch {}
|
|
20159
20391
|
return;
|
|
20160
20392
|
}
|
|
@@ -20206,9 +20438,9 @@ class CompanionManager {
|
|
|
20206
20438
|
onExit() {
|
|
20207
20439
|
if (this.config?.enabled !== true)
|
|
20208
20440
|
return;
|
|
20209
|
-
|
|
20210
|
-
|
|
20211
|
-
|
|
20441
|
+
writeState((state) => {
|
|
20442
|
+
state.sessions = state.sessions.filter((s) => s.session_id !== this.id);
|
|
20443
|
+
});
|
|
20212
20444
|
}
|
|
20213
20445
|
activeAgents() {
|
|
20214
20446
|
const agents = Array.from(this.busyAgentSessions.values());
|
|
@@ -20224,28 +20456,41 @@ class CompanionManager {
|
|
|
20224
20456
|
if (this.config?.enabled !== true)
|
|
20225
20457
|
return;
|
|
20226
20458
|
try {
|
|
20227
|
-
const state = readState();
|
|
20228
20459
|
const entry = {
|
|
20229
20460
|
session_id: this.id,
|
|
20230
20461
|
cwd: this.cwd,
|
|
20231
20462
|
active_agents: this.activeAgents(),
|
|
20232
20463
|
status: this.status,
|
|
20233
|
-
pid: process.pid
|
|
20234
|
-
|
|
20235
|
-
const idx = state.sessions.findIndex((s) => s.session_id === this.id);
|
|
20236
|
-
if (idx >= 0) {
|
|
20237
|
-
state.sessions[idx] = entry;
|
|
20238
|
-
} else {
|
|
20239
|
-
state.sessions.push(entry);
|
|
20240
|
-
}
|
|
20241
|
-
if (this.config) {
|
|
20242
|
-
state.config = {
|
|
20464
|
+
pid: process.pid,
|
|
20465
|
+
config: this.config ? {
|
|
20243
20466
|
enabled: this.config.enabled ?? false,
|
|
20244
20467
|
position: this.config.position ?? "bottom-right",
|
|
20245
|
-
size: this.config.size ?? "medium"
|
|
20246
|
-
|
|
20247
|
-
|
|
20248
|
-
|
|
20468
|
+
size: this.config.size ?? "medium",
|
|
20469
|
+
gifPack: this.config.gifPack ?? "default",
|
|
20470
|
+
loopStyle: this.config.loopStyle ?? "classic",
|
|
20471
|
+
speed: this.config.speed ?? 1,
|
|
20472
|
+
debug: this.config.debug ?? false
|
|
20473
|
+
} : undefined
|
|
20474
|
+
};
|
|
20475
|
+
writeState((state) => {
|
|
20476
|
+
const idx = state.sessions.findIndex((s) => s.session_id === this.id);
|
|
20477
|
+
if (idx >= 0) {
|
|
20478
|
+
state.sessions[idx] = entry;
|
|
20479
|
+
} else {
|
|
20480
|
+
state.sessions.push(entry);
|
|
20481
|
+
}
|
|
20482
|
+
if (this.config) {
|
|
20483
|
+
state.config = {
|
|
20484
|
+
enabled: this.config.enabled ?? false,
|
|
20485
|
+
position: this.config.position ?? "bottom-right",
|
|
20486
|
+
size: this.config.size ?? "medium",
|
|
20487
|
+
gifPack: this.config.gifPack ?? "default",
|
|
20488
|
+
loopStyle: this.config.loopStyle ?? "classic",
|
|
20489
|
+
speed: this.config.speed ?? 1,
|
|
20490
|
+
debug: this.config.debug ?? false
|
|
20491
|
+
};
|
|
20492
|
+
}
|
|
20493
|
+
});
|
|
20249
20494
|
} catch (err) {
|
|
20250
20495
|
log("[companion] flush failed", String(err));
|
|
20251
20496
|
}
|
|
@@ -20253,51 +20498,356 @@ class CompanionManager {
|
|
|
20253
20498
|
spawnIfAvailable() {
|
|
20254
20499
|
if (this.config?.enabled !== true)
|
|
20255
20500
|
return;
|
|
20256
|
-
const bin =
|
|
20501
|
+
const bin = resolveCompanionBinaryPath(this.config);
|
|
20257
20502
|
if (!bin) {
|
|
20258
|
-
const
|
|
20259
|
-
const base = xdg && path3.isAbsolute(xdg) ? xdg : path3.join(os2.homedir(), ".local", "share");
|
|
20260
|
-
const expected = path3.join(base, "opencode", "storage", "oh-my-opencode-slim", "bin", "oh-my-opencode-slim-companion");
|
|
20503
|
+
const expected = this.config.binaryPath?.trim() || defaultBinaryPath();
|
|
20261
20504
|
log(`[companion] enabled but companion binary not found at expected path: ${expected}. Please install/download the companion binary separately.`);
|
|
20262
20505
|
return;
|
|
20263
20506
|
}
|
|
20264
20507
|
try {
|
|
20265
|
-
const child = spawn(bin, [], {
|
|
20508
|
+
const child = spawn(bin, [], {
|
|
20509
|
+
detached: true,
|
|
20510
|
+
env: {
|
|
20511
|
+
...process.env,
|
|
20512
|
+
OH_MY_OPENCODE_SLIM_COMPANION_SESSION_ID: this.id,
|
|
20513
|
+
...this.config.debug === true ? { OH_MY_OPENCODE_SLIM_COMPANION_DEBUG: "1" } : {}
|
|
20514
|
+
},
|
|
20515
|
+
stdio: "ignore"
|
|
20516
|
+
});
|
|
20266
20517
|
child.unref();
|
|
20267
|
-
log("[companion] spawned",
|
|
20518
|
+
log("[companion] spawned", JSON.stringify({
|
|
20519
|
+
bin,
|
|
20520
|
+
sessionId: this.id,
|
|
20521
|
+
debug: this.config.debug === true
|
|
20522
|
+
}));
|
|
20268
20523
|
} catch (err) {
|
|
20269
20524
|
log("[companion] spawn failed", String(err));
|
|
20270
20525
|
}
|
|
20271
20526
|
}
|
|
20272
20527
|
}
|
|
20273
20528
|
|
|
20274
|
-
// src/
|
|
20275
|
-
|
|
20276
|
-
|
|
20277
|
-
|
|
20278
|
-
|
|
20279
|
-
|
|
20280
|
-
|
|
20281
|
-
|
|
20282
|
-
|
|
20283
|
-
|
|
20284
|
-
|
|
20285
|
-
|
|
20286
|
-
|
|
20287
|
-
|
|
20288
|
-
|
|
20289
|
-
|
|
20290
|
-
|
|
20291
|
-
|
|
20292
|
-
|
|
20293
|
-
|
|
20294
|
-
|
|
20529
|
+
// src/companion/updater.ts
|
|
20530
|
+
init_compat();
|
|
20531
|
+
import { createHash } from "node:crypto";
|
|
20532
|
+
import {
|
|
20533
|
+
chmodSync,
|
|
20534
|
+
copyFileSync,
|
|
20535
|
+
existsSync as existsSync3,
|
|
20536
|
+
mkdirSync as mkdirSync3,
|
|
20537
|
+
mkdtempSync,
|
|
20538
|
+
readFileSync as readFileSync3,
|
|
20539
|
+
renameSync as renameSync2,
|
|
20540
|
+
rmSync as rmSync2,
|
|
20541
|
+
statSync as statSync2,
|
|
20542
|
+
writeFileSync as writeFileSync2
|
|
20543
|
+
} from "node:fs";
|
|
20544
|
+
import { homedir as homedir4, platform as platform2, tmpdir } from "node:os";
|
|
20545
|
+
import * as path4 from "node:path";
|
|
20546
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
20547
|
+
var DOWNLOAD_TIMEOUT_MS = 30000;
|
|
20548
|
+
var LOCK_TIMEOUT_MS = 2000;
|
|
20549
|
+
var STALE_LOCK_MS = 5 * 60000;
|
|
20550
|
+
var FIRST_METADATA_VERSION = "0.1.2";
|
|
20551
|
+
var COMPANION_MANIFEST = {
|
|
20552
|
+
version: "0.1.3",
|
|
20553
|
+
tag: "companion-v0.1.3",
|
|
20554
|
+
repo: "alvinunreal/oh-my-opencode-slim",
|
|
20555
|
+
checksums: {
|
|
20556
|
+
"oh-my-opencode-slim-companion-v0.1.3-aarch64-apple-darwin.tar.gz": "b4885f9b1900c02376e5f8f5ae6f3b8a89d26f7514b03f836d7e3d618164a0ed",
|
|
20557
|
+
"oh-my-opencode-slim-companion-v0.1.3-aarch64-unknown-linux-gnu.tar.gz": "ed7cffc583e1eaa78c9bea702e6b6aa3bbc5bb4d881713fb2050237ba6b7aca5",
|
|
20558
|
+
"oh-my-opencode-slim-companion-v0.1.3-x86_64-apple-darwin.tar.gz": "98d8ea7c7bc4415b18e0d4c524adb4eb9a84c872919840fdc021f0f50c61f808",
|
|
20559
|
+
"oh-my-opencode-slim-companion-v0.1.3-x86_64-pc-windows-msvc.zip": "9316a49bf01f3b4fb1ce2d62edfc46094e73bb153d6ce023fb7df085afcf77bd",
|
|
20560
|
+
"oh-my-opencode-slim-companion-v0.1.3-x86_64-unknown-linux-gnu.tar.gz": "33f5fd4b6c80155a019391e5efb13904ca9531ba8dd8c6cba30a161f1b07b764"
|
|
20561
|
+
}
|
|
20562
|
+
};
|
|
20563
|
+
function getCompanionTarget() {
|
|
20564
|
+
const p = process.platform;
|
|
20565
|
+
const a = process.arch;
|
|
20566
|
+
if (p === "darwin") {
|
|
20567
|
+
if (a === "arm64")
|
|
20568
|
+
return "aarch64-apple-darwin";
|
|
20569
|
+
if (a === "x64")
|
|
20570
|
+
return "x86_64-apple-darwin";
|
|
20571
|
+
} else if (p === "linux") {
|
|
20572
|
+
if (a === "x64")
|
|
20573
|
+
return "x86_64-unknown-linux-gnu";
|
|
20574
|
+
if (a === "arm64")
|
|
20575
|
+
return "aarch64-unknown-linux-gnu";
|
|
20576
|
+
} else if (p === "win32") {
|
|
20577
|
+
if (a === "x64")
|
|
20578
|
+
return "x86_64-pc-windows-msvc";
|
|
20579
|
+
}
|
|
20580
|
+
return null;
|
|
20581
|
+
}
|
|
20582
|
+
function getCompanionBinaryPath() {
|
|
20583
|
+
const xdg = process.env.XDG_DATA_HOME?.trim();
|
|
20584
|
+
const base = xdg && path4.isAbsolute(xdg) ? xdg : path4.join(homedir4(), ".local", "share");
|
|
20585
|
+
return path4.join(base, "opencode", "storage", "oh-my-opencode-slim", "bin", platform2() === "win32" ? "oh-my-opencode-slim-companion.exe" : "oh-my-opencode-slim-companion");
|
|
20586
|
+
}
|
|
20587
|
+
function loadCompanionManifestFromPackageRoot(packageRoot) {
|
|
20588
|
+
const manifestPath = path4.join(packageRoot, "src", "companion", "companion-manifest.json");
|
|
20589
|
+
try {
|
|
20590
|
+
const parsed = JSON.parse(readFileSync3(manifestPath, "utf8"));
|
|
20591
|
+
if (parsed.version && parsed.tag && parsed.repo) {
|
|
20592
|
+
return {
|
|
20593
|
+
version: parsed.version,
|
|
20594
|
+
tag: parsed.tag,
|
|
20595
|
+
repo: parsed.repo,
|
|
20596
|
+
checksums: parsed.checksums
|
|
20597
|
+
};
|
|
20598
|
+
}
|
|
20599
|
+
} catch {}
|
|
20600
|
+
return null;
|
|
20601
|
+
}
|
|
20602
|
+
async function ensureCompanionVersion(options) {
|
|
20603
|
+
const { config, dryRun = false } = options;
|
|
20604
|
+
const manifest = options.manifest ?? COMPANION_MANIFEST;
|
|
20605
|
+
const binaryPath = getCompanionBinaryPath();
|
|
20606
|
+
if (config?.enabled !== true) {
|
|
20607
|
+
return { status: "skipped", reason: "disabled", binaryPath };
|
|
20608
|
+
}
|
|
20609
|
+
if (config.binaryPath?.trim()) {
|
|
20610
|
+
return { status: "skipped", reason: "custom-binary", binaryPath };
|
|
20611
|
+
}
|
|
20612
|
+
const target = getCompanionTarget();
|
|
20613
|
+
if (!target) {
|
|
20614
|
+
return {
|
|
20615
|
+
status: "failed",
|
|
20616
|
+
binaryPath,
|
|
20617
|
+
error: `Unsupported platform/architecture: ${process.platform} ${process.arch}`
|
|
20618
|
+
};
|
|
20619
|
+
}
|
|
20620
|
+
const current = readInstallMetadata(binaryPath);
|
|
20621
|
+
if (existsSync3(binaryPath) && !current && manifest.version === FIRST_METADATA_VERSION) {
|
|
20622
|
+
const archiveName = companionArchiveName(manifest.version, target);
|
|
20623
|
+
writeInstallMetadata(binaryPath, {
|
|
20624
|
+
version: manifest.version,
|
|
20625
|
+
tag: manifest.tag,
|
|
20626
|
+
target,
|
|
20627
|
+
installedAt: new Date().toISOString(),
|
|
20628
|
+
archiveName,
|
|
20629
|
+
checksum: manifest.checksums?.[archiveName]
|
|
20630
|
+
});
|
|
20631
|
+
return { status: "current", binaryPath, version: manifest.version };
|
|
20632
|
+
}
|
|
20633
|
+
if (existsSync3(binaryPath) && current?.target === target && compareSemver(current.version, manifest.version) >= 0) {
|
|
20634
|
+
return { status: "current", binaryPath, version: current.version };
|
|
20635
|
+
}
|
|
20636
|
+
if (dryRun) {
|
|
20637
|
+
return { status: "installed", binaryPath, version: manifest.version };
|
|
20638
|
+
}
|
|
20639
|
+
return withCompanionInstallLock(binaryPath, options.lockTimeoutMs, options.lockStaleMs, async () => {
|
|
20640
|
+
const lockedCurrent = readInstallMetadata(binaryPath);
|
|
20641
|
+
if (existsSync3(binaryPath) && !lockedCurrent && manifest.version === FIRST_METADATA_VERSION) {
|
|
20642
|
+
const archiveName = companionArchiveName(manifest.version, target);
|
|
20643
|
+
writeInstallMetadata(binaryPath, {
|
|
20644
|
+
version: manifest.version,
|
|
20645
|
+
tag: manifest.tag,
|
|
20646
|
+
target,
|
|
20647
|
+
installedAt: new Date().toISOString(),
|
|
20648
|
+
archiveName,
|
|
20649
|
+
checksum: manifest.checksums?.[archiveName]
|
|
20650
|
+
});
|
|
20651
|
+
return { status: "current", binaryPath, version: manifest.version };
|
|
20295
20652
|
}
|
|
20296
|
-
if (
|
|
20297
|
-
|
|
20653
|
+
if (existsSync3(binaryPath) && lockedCurrent?.target === target && compareSemver(lockedCurrent.version, manifest.version) >= 0) {
|
|
20654
|
+
return {
|
|
20655
|
+
status: "current",
|
|
20656
|
+
binaryPath,
|
|
20657
|
+
version: lockedCurrent.version
|
|
20658
|
+
};
|
|
20298
20659
|
}
|
|
20660
|
+
return installCompanionArchive(binaryPath, target, manifest, options.downloadTimeoutMs ?? DOWNLOAD_TIMEOUT_MS);
|
|
20661
|
+
});
|
|
20662
|
+
}
|
|
20663
|
+
async function installCompanionArchive(finalBinaryPath, target, manifest, downloadTimeoutMs) {
|
|
20664
|
+
const isWindows = process.platform === "win32";
|
|
20665
|
+
const archiveName = companionArchiveName(manifest.version, target, isWindows);
|
|
20666
|
+
const downloadUrl = `https://github.com/${manifest.repo}/releases/download/${manifest.tag}/${archiveName}`;
|
|
20667
|
+
const expectedChecksum = manifest.checksums?.[archiveName];
|
|
20668
|
+
if (!expectedChecksum) {
|
|
20669
|
+
return {
|
|
20670
|
+
status: "failed",
|
|
20671
|
+
binaryPath: finalBinaryPath,
|
|
20672
|
+
error: `Missing SHA256 checksum for companion archive: ${archiveName}`
|
|
20673
|
+
};
|
|
20299
20674
|
}
|
|
20300
|
-
|
|
20675
|
+
let buffer;
|
|
20676
|
+
const controller = new AbortController;
|
|
20677
|
+
const timeout = setTimeout(() => controller.abort(), downloadTimeoutMs);
|
|
20678
|
+
try {
|
|
20679
|
+
const res = await fetch(downloadUrl, { signal: controller.signal });
|
|
20680
|
+
if (!res.ok) {
|
|
20681
|
+
return {
|
|
20682
|
+
status: "failed",
|
|
20683
|
+
binaryPath: finalBinaryPath,
|
|
20684
|
+
error: `Failed to download companion binary (HTTP ${res.status}): ${res.statusText}`
|
|
20685
|
+
};
|
|
20686
|
+
}
|
|
20687
|
+
buffer = await res.arrayBuffer();
|
|
20688
|
+
} catch (err) {
|
|
20689
|
+
return {
|
|
20690
|
+
status: "failed",
|
|
20691
|
+
binaryPath: finalBinaryPath,
|
|
20692
|
+
error: `Failed to fetch companion archive: ${formatError(err)}`
|
|
20693
|
+
};
|
|
20694
|
+
} finally {
|
|
20695
|
+
clearTimeout(timeout);
|
|
20696
|
+
}
|
|
20697
|
+
const checksum = createHash("sha256").update(Buffer.from(buffer)).digest("hex");
|
|
20698
|
+
if (checksum !== expectedChecksum) {
|
|
20699
|
+
return {
|
|
20700
|
+
status: "failed",
|
|
20701
|
+
binaryPath: finalBinaryPath,
|
|
20702
|
+
error: "Companion archive checksum mismatch"
|
|
20703
|
+
};
|
|
20704
|
+
}
|
|
20705
|
+
let tempDir = "";
|
|
20706
|
+
try {
|
|
20707
|
+
tempDir = mkdtempSync(path4.join(tmpdir(), "companion-install-"));
|
|
20708
|
+
const archivePath = path4.join(tempDir, archiveName);
|
|
20709
|
+
writeFileSync2(archivePath, Buffer.from(buffer));
|
|
20710
|
+
const extractedDir = path4.join(tempDir, "extracted");
|
|
20711
|
+
mkdirSync3(extractedDir, { recursive: true });
|
|
20712
|
+
if (isWindows) {
|
|
20713
|
+
const { extractZip: extractZip2 } = await Promise.resolve().then(() => (init_zip_extractor(), exports_zip_extractor));
|
|
20714
|
+
await extractZip2(archivePath, extractedDir);
|
|
20715
|
+
} else {
|
|
20716
|
+
const proc = crossSpawn(["tar", "-xzf", archivePath, "-C", extractedDir]);
|
|
20717
|
+
const exitCode = await proc.exited;
|
|
20718
|
+
if (exitCode !== 0) {
|
|
20719
|
+
const stderr = await proc.stderr();
|
|
20720
|
+
return {
|
|
20721
|
+
status: "failed",
|
|
20722
|
+
binaryPath: finalBinaryPath,
|
|
20723
|
+
error: `Archive extraction failed (tar exited with ${exitCode}): ${stderr}`
|
|
20724
|
+
};
|
|
20725
|
+
}
|
|
20726
|
+
}
|
|
20727
|
+
const binaryName = isWindows ? "oh-my-opencode-slim-companion.exe" : "oh-my-opencode-slim-companion";
|
|
20728
|
+
const extractedBinaryPath = path4.join(extractedDir, binaryName);
|
|
20729
|
+
if (!existsSync3(extractedBinaryPath)) {
|
|
20730
|
+
return {
|
|
20731
|
+
status: "failed",
|
|
20732
|
+
binaryPath: finalBinaryPath,
|
|
20733
|
+
error: `Binary ${binaryName} not found in extracted archive`
|
|
20734
|
+
};
|
|
20735
|
+
}
|
|
20736
|
+
const binDir = path4.dirname(finalBinaryPath);
|
|
20737
|
+
mkdirSync3(binDir, { recursive: true });
|
|
20738
|
+
const tmpFinalPath = `${finalBinaryPath}.tmp`;
|
|
20739
|
+
copyFileSync(extractedBinaryPath, tmpFinalPath);
|
|
20740
|
+
if (!isWindows) {
|
|
20741
|
+
chmodSync(tmpFinalPath, 493);
|
|
20742
|
+
}
|
|
20743
|
+
renameSync2(tmpFinalPath, finalBinaryPath);
|
|
20744
|
+
writeInstallMetadata(finalBinaryPath, {
|
|
20745
|
+
version: manifest.version,
|
|
20746
|
+
tag: manifest.tag,
|
|
20747
|
+
target,
|
|
20748
|
+
installedAt: new Date().toISOString(),
|
|
20749
|
+
archiveName,
|
|
20750
|
+
checksum
|
|
20751
|
+
});
|
|
20752
|
+
return {
|
|
20753
|
+
status: "installed",
|
|
20754
|
+
binaryPath: finalBinaryPath,
|
|
20755
|
+
version: manifest.version
|
|
20756
|
+
};
|
|
20757
|
+
} catch (err) {
|
|
20758
|
+
return {
|
|
20759
|
+
status: "failed",
|
|
20760
|
+
binaryPath: finalBinaryPath,
|
|
20761
|
+
error: `Failed to install companion: ${formatError(err)}`
|
|
20762
|
+
};
|
|
20763
|
+
} finally {
|
|
20764
|
+
if (tempDir) {
|
|
20765
|
+
try {
|
|
20766
|
+
rmSync2(tempDir, { recursive: true, force: true });
|
|
20767
|
+
} catch {}
|
|
20768
|
+
}
|
|
20769
|
+
}
|
|
20770
|
+
}
|
|
20771
|
+
function readInstallMetadata(binaryPath) {
|
|
20772
|
+
try {
|
|
20773
|
+
const parsed = JSON.parse(readFileSync3(metadataPath(binaryPath), "utf8"));
|
|
20774
|
+
if (parsed?.version && parsed.tag && parsed.target) {
|
|
20775
|
+
return parsed;
|
|
20776
|
+
}
|
|
20777
|
+
} catch {}
|
|
20778
|
+
return null;
|
|
20779
|
+
}
|
|
20780
|
+
function writeInstallMetadata(binaryPath, metadata) {
|
|
20781
|
+
writeFileSync2(metadataPath(binaryPath), JSON.stringify(metadata, null, 2));
|
|
20782
|
+
}
|
|
20783
|
+
function metadataPath(binaryPath) {
|
|
20784
|
+
return `${binaryPath}.json`;
|
|
20785
|
+
}
|
|
20786
|
+
async function withCompanionInstallLock(binaryPath, timeoutMs, staleMs, run) {
|
|
20787
|
+
const lock = `${binaryPath}.lock`;
|
|
20788
|
+
const deadline = Date.now() + (timeoutMs ?? LOCK_TIMEOUT_MS);
|
|
20789
|
+
const staleAfterMs = staleMs ?? STALE_LOCK_MS;
|
|
20790
|
+
mkdirSync3(path4.dirname(binaryPath), { recursive: true });
|
|
20791
|
+
while (Date.now() <= deadline) {
|
|
20792
|
+
try {
|
|
20793
|
+
mkdirSync3(lock);
|
|
20794
|
+
try {
|
|
20795
|
+
return await run();
|
|
20796
|
+
} finally {
|
|
20797
|
+
try {
|
|
20798
|
+
rmSync2(lock, { recursive: true, force: true });
|
|
20799
|
+
} catch {}
|
|
20800
|
+
}
|
|
20801
|
+
} catch (err) {
|
|
20802
|
+
const code = err.code;
|
|
20803
|
+
if (code !== "EEXIST")
|
|
20804
|
+
throw err;
|
|
20805
|
+
try {
|
|
20806
|
+
const ageMs = Date.now() - statSync2(lock).mtimeMs;
|
|
20807
|
+
if (ageMs > staleAfterMs) {
|
|
20808
|
+
rmSync2(lock, { recursive: true, force: true });
|
|
20809
|
+
log("[companion] removed stale install lock", lock);
|
|
20810
|
+
continue;
|
|
20811
|
+
}
|
|
20812
|
+
} catch (statErr) {
|
|
20813
|
+
const statCode = statErr.code;
|
|
20814
|
+
if (statCode !== "ENOENT")
|
|
20815
|
+
throw statErr;
|
|
20816
|
+
}
|
|
20817
|
+
await delay(25);
|
|
20818
|
+
}
|
|
20819
|
+
}
|
|
20820
|
+
log("[companion] install lock timed out", lock);
|
|
20821
|
+
return {
|
|
20822
|
+
status: "failed",
|
|
20823
|
+
binaryPath,
|
|
20824
|
+
error: "Timed out waiting for companion install lock"
|
|
20825
|
+
};
|
|
20826
|
+
}
|
|
20827
|
+
function companionArchiveName(version, target, isWindows = process.platform === "win32") {
|
|
20828
|
+
const ext = isWindows ? "zip" : "tar.gz";
|
|
20829
|
+
return `oh-my-opencode-slim-companion-v${version}-${target}.${ext}`;
|
|
20830
|
+
}
|
|
20831
|
+
function compareSemver(a, b) {
|
|
20832
|
+
const left = parseSemver(a);
|
|
20833
|
+
const right = parseSemver(b);
|
|
20834
|
+
if (!left || !right)
|
|
20835
|
+
return a.localeCompare(b);
|
|
20836
|
+
for (let i = 0;i < 3; i++) {
|
|
20837
|
+
const diff = left[i] - right[i];
|
|
20838
|
+
if (diff !== 0)
|
|
20839
|
+
return diff;
|
|
20840
|
+
}
|
|
20841
|
+
return 0;
|
|
20842
|
+
}
|
|
20843
|
+
function parseSemver(version) {
|
|
20844
|
+
const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/);
|
|
20845
|
+
if (!match)
|
|
20846
|
+
return null;
|
|
20847
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
20848
|
+
}
|
|
20849
|
+
function formatError(err) {
|
|
20850
|
+
return err instanceof Error ? err.message : String(err);
|
|
20301
20851
|
}
|
|
20302
20852
|
|
|
20303
20853
|
// src/config/runtime-preset.ts
|
|
@@ -20636,7 +21186,7 @@ function ensureApplyPatchError(error, context) {
|
|
|
20636
21186
|
|
|
20637
21187
|
// src/hooks/apply-patch/execution-context.ts
|
|
20638
21188
|
import * as fs3 from "node:fs/promises";
|
|
20639
|
-
import
|
|
21189
|
+
import path5 from "node:path";
|
|
20640
21190
|
|
|
20641
21191
|
// src/hooks/apply-patch/codec.ts
|
|
20642
21192
|
function normalizeLineEndings(text) {
|
|
@@ -21505,7 +22055,7 @@ function isMissingPathError(error) {
|
|
|
21505
22055
|
}
|
|
21506
22056
|
async function real(target) {
|
|
21507
22057
|
const parts = [];
|
|
21508
|
-
let current =
|
|
22058
|
+
let current = path5.resolve(target);
|
|
21509
22059
|
while (true) {
|
|
21510
22060
|
const exact = await fs3.realpath(current).catch((error) => {
|
|
21511
22061
|
if (isMissingPathError(error)) {
|
|
@@ -21514,19 +22064,19 @@ async function real(target) {
|
|
|
21514
22064
|
throw createApplyPatchInternalError(`Failed to resolve real path: ${current}`, error);
|
|
21515
22065
|
});
|
|
21516
22066
|
if (exact) {
|
|
21517
|
-
return parts.length === 0 ? exact :
|
|
22067
|
+
return parts.length === 0 ? exact : path5.join(exact, ...parts.reverse());
|
|
21518
22068
|
}
|
|
21519
|
-
const parent =
|
|
22069
|
+
const parent = path5.dirname(current);
|
|
21520
22070
|
if (parent === current) {
|
|
21521
|
-
return parts.length === 0 ? current :
|
|
22071
|
+
return parts.length === 0 ? current : path5.join(current, ...parts.reverse());
|
|
21522
22072
|
}
|
|
21523
|
-
parts.push(
|
|
22073
|
+
parts.push(path5.basename(current));
|
|
21524
22074
|
current = parent;
|
|
21525
22075
|
}
|
|
21526
22076
|
}
|
|
21527
22077
|
function inside(root, target) {
|
|
21528
|
-
const rel =
|
|
21529
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
22078
|
+
const rel = path5.relative(root, target);
|
|
22079
|
+
return rel === "" || !rel.startsWith("..") && !path5.isAbsolute(rel);
|
|
21530
22080
|
}
|
|
21531
22081
|
function createPathGuardContext(root, worktree) {
|
|
21532
22082
|
return {
|
|
@@ -21536,7 +22086,7 @@ function createPathGuardContext(root, worktree) {
|
|
|
21536
22086
|
};
|
|
21537
22087
|
}
|
|
21538
22088
|
async function realCached(ctx, target) {
|
|
21539
|
-
const resolvedTarget =
|
|
22089
|
+
const resolvedTarget = path5.resolve(target);
|
|
21540
22090
|
let pending = ctx.realCache.get(resolvedTarget);
|
|
21541
22091
|
if (!pending) {
|
|
21542
22092
|
pending = real(resolvedTarget);
|
|
@@ -21587,22 +22137,22 @@ async function assertRegularFile(ctx, filePath, verb) {
|
|
|
21587
22137
|
function collectPatchTargets(root, hunks) {
|
|
21588
22138
|
const targets = new Set;
|
|
21589
22139
|
for (const hunk of hunks) {
|
|
21590
|
-
targets.add(
|
|
22140
|
+
targets.add(path5.resolve(root, hunk.path));
|
|
21591
22141
|
if (hunk.type === "update" && hunk.move_path) {
|
|
21592
|
-
targets.add(
|
|
22142
|
+
targets.add(path5.resolve(root, hunk.move_path));
|
|
21593
22143
|
}
|
|
21594
22144
|
}
|
|
21595
22145
|
return [...targets];
|
|
21596
22146
|
}
|
|
21597
22147
|
function toRelativePatchPath(root, target) {
|
|
21598
|
-
const relative =
|
|
22148
|
+
const relative = path5.relative(root, target);
|
|
21599
22149
|
return (relative.length === 0 ? "." : relative).replaceAll("\\", "/");
|
|
21600
22150
|
}
|
|
21601
22151
|
function normalizePatchPath(root, value) {
|
|
21602
|
-
return
|
|
22152
|
+
return path5.isAbsolute(value) ? toRelativePatchPath(root, path5.resolve(value)) : value;
|
|
21603
22153
|
}
|
|
21604
22154
|
function normalizePatchPaths(root, hunks) {
|
|
21605
|
-
const resolvedRoot =
|
|
22155
|
+
const resolvedRoot = path5.resolve(root);
|
|
21606
22156
|
const normalized = [];
|
|
21607
22157
|
let changed = false;
|
|
21608
22158
|
for (const hunk of hunks) {
|
|
@@ -21726,7 +22276,7 @@ function stageAddedText(contents) {
|
|
|
21726
22276
|
`;
|
|
21727
22277
|
}
|
|
21728
22278
|
// src/hooks/apply-patch/rewrite.ts
|
|
21729
|
-
import
|
|
22279
|
+
import path6 from "node:path";
|
|
21730
22280
|
function normalizeTextLineEndings(text) {
|
|
21731
22281
|
return text.replace(/\r\n/g, `
|
|
21732
22282
|
`).replace(/\r/g, `
|
|
@@ -21883,7 +22433,7 @@ async function rewritePatch(root, patchText, cfg, worktree) {
|
|
|
21883
22433
|
const dependencyGroups = new Map;
|
|
21884
22434
|
for (const hunk of hunks) {
|
|
21885
22435
|
if (hunk.type === "add") {
|
|
21886
|
-
const filePath2 =
|
|
22436
|
+
const filePath2 = path6.resolve(root, hunk.path);
|
|
21887
22437
|
await assertPreparedPathMissing(filePath2, "add");
|
|
21888
22438
|
rewritten.push(hunk);
|
|
21889
22439
|
clearDependencyGroup(filePath2);
|
|
@@ -21905,20 +22455,20 @@ async function rewritePatch(root, patchText, cfg, worktree) {
|
|
|
21905
22455
|
continue;
|
|
21906
22456
|
}
|
|
21907
22457
|
if (hunk.type === "delete") {
|
|
21908
|
-
const filePath2 =
|
|
22458
|
+
const filePath2 = path6.resolve(root, hunk.path);
|
|
21909
22459
|
await getPreparedFileState(filePath2, "delete");
|
|
21910
22460
|
clearDependencyGroup(filePath2);
|
|
21911
22461
|
rewritten.push(hunk);
|
|
21912
22462
|
staged.set(filePath2, { exists: false, derived: true });
|
|
21913
22463
|
continue;
|
|
21914
22464
|
}
|
|
21915
|
-
const filePath =
|
|
22465
|
+
const filePath = path6.resolve(root, hunk.path);
|
|
21916
22466
|
const currentDependency = dependencyGroups.get(filePath);
|
|
21917
22467
|
const current = await getPreparedFileState(filePath, "update");
|
|
21918
22468
|
if (!current.exists) {
|
|
21919
22469
|
throw createApplyPatchVerificationError(`Failed to read file to update: ${filePath}`);
|
|
21920
22470
|
}
|
|
21921
|
-
const movePath = hunk.move_path ?
|
|
22471
|
+
const movePath = hunk.move_path ? path6.resolve(root, hunk.move_path) : undefined;
|
|
21922
22472
|
if (movePath && movePath !== filePath) {
|
|
21923
22473
|
await assertPreparedPathMissing(movePath, "move");
|
|
21924
22474
|
}
|
|
@@ -22083,29 +22633,35 @@ function createApplyPatchHook(ctx) {
|
|
|
22083
22633
|
}
|
|
22084
22634
|
};
|
|
22085
22635
|
}
|
|
22636
|
+
// src/hooks/auto-update-checker/index.ts
|
|
22637
|
+
import * as path11 from "node:path";
|
|
22638
|
+
init_compat();
|
|
22639
|
+
|
|
22086
22640
|
// src/hooks/auto-update-checker/cache.ts
|
|
22087
22641
|
import * as fs5 from "node:fs";
|
|
22088
|
-
import * as
|
|
22642
|
+
import * as path9 from "node:path";
|
|
22643
|
+
// src/cli/system.ts
|
|
22644
|
+
init_compat();
|
|
22089
22645
|
// src/hooks/auto-update-checker/checker.ts
|
|
22090
22646
|
import * as fs4 from "node:fs";
|
|
22091
|
-
import * as
|
|
22647
|
+
import * as path8 from "node:path";
|
|
22092
22648
|
import { fileURLToPath } from "node:url";
|
|
22093
22649
|
|
|
22094
22650
|
// src/hooks/auto-update-checker/constants.ts
|
|
22095
22651
|
import * as os3 from "node:os";
|
|
22096
|
-
import * as
|
|
22652
|
+
import * as path7 from "node:path";
|
|
22097
22653
|
var PACKAGE_NAME = "oh-my-opencode-slim";
|
|
22098
22654
|
var NPM_REGISTRY_URL = `https://registry.npmjs.org/-/package/${PACKAGE_NAME}/dist-tags`;
|
|
22099
22655
|
var NPM_PACKAGE_URL = `https://registry.npmjs.org/${PACKAGE_NAME}`;
|
|
22100
22656
|
var NPM_FETCH_TIMEOUT = 5000;
|
|
22101
22657
|
function getCacheDir() {
|
|
22102
22658
|
if (process.platform === "win32") {
|
|
22103
|
-
return
|
|
22659
|
+
return path7.join(process.env.LOCALAPPDATA ?? os3.homedir(), "opencode");
|
|
22104
22660
|
}
|
|
22105
|
-
return
|
|
22661
|
+
return path7.join(os3.homedir(), ".cache", "opencode");
|
|
22106
22662
|
}
|
|
22107
22663
|
var CACHE_DIR = getCacheDir();
|
|
22108
|
-
var INSTALLED_PACKAGE_JSON =
|
|
22664
|
+
var INSTALLED_PACKAGE_JSON = path7.join(CACHE_DIR, "node_modules", PACKAGE_NAME, "package.json");
|
|
22109
22665
|
var configPaths = getOpenCodeConfigPaths();
|
|
22110
22666
|
var USER_OPENCODE_CONFIG = configPaths[0];
|
|
22111
22667
|
var USER_OPENCODE_CONFIG_JSONC = configPaths[1];
|
|
@@ -22217,8 +22773,8 @@ function extractChannel(version) {
|
|
|
22217
22773
|
}
|
|
22218
22774
|
function getConfigPaths(directory) {
|
|
22219
22775
|
return [
|
|
22220
|
-
|
|
22221
|
-
|
|
22776
|
+
path8.join(directory, ".opencode", "opencode.json"),
|
|
22777
|
+
path8.join(directory, ".opencode", "opencode.jsonc"),
|
|
22222
22778
|
USER_OPENCODE_CONFIG,
|
|
22223
22779
|
USER_OPENCODE_CONFIG_JSONC
|
|
22224
22780
|
];
|
|
@@ -22247,9 +22803,9 @@ function getLocalDevPath(directory) {
|
|
|
22247
22803
|
function findPackageJsonUp(startPath) {
|
|
22248
22804
|
try {
|
|
22249
22805
|
const stat2 = fs4.statSync(startPath);
|
|
22250
|
-
let dir = stat2.isDirectory() ? startPath :
|
|
22806
|
+
let dir = stat2.isDirectory() ? startPath : path8.dirname(startPath);
|
|
22251
22807
|
for (let i = 0;i < 10; i++) {
|
|
22252
|
-
const pkgPath =
|
|
22808
|
+
const pkgPath = path8.join(dir, "package.json");
|
|
22253
22809
|
if (fs4.existsSync(pkgPath)) {
|
|
22254
22810
|
try {
|
|
22255
22811
|
const content = fs4.readFileSync(pkgPath, "utf-8");
|
|
@@ -22258,7 +22814,7 @@ function findPackageJsonUp(startPath) {
|
|
|
22258
22814
|
return pkgPath;
|
|
22259
22815
|
} catch {}
|
|
22260
22816
|
}
|
|
22261
|
-
const parent =
|
|
22817
|
+
const parent = path8.dirname(dir);
|
|
22262
22818
|
if (parent === dir)
|
|
22263
22819
|
break;
|
|
22264
22820
|
dir = parent;
|
|
@@ -22283,7 +22839,7 @@ function getLocalDevVersion(directory) {
|
|
|
22283
22839
|
}
|
|
22284
22840
|
function getCurrentRuntimePackageJsonPath(currentModuleUrl = import.meta.url) {
|
|
22285
22841
|
try {
|
|
22286
|
-
const currentDir =
|
|
22842
|
+
const currentDir = path8.dirname(fileURLToPath(currentModuleUrl));
|
|
22287
22843
|
return findPackageJsonUp(currentDir);
|
|
22288
22844
|
} catch (err) {
|
|
22289
22845
|
log("[auto-update-checker] Failed to resolve runtime package path:", err);
|
|
@@ -22442,7 +22998,7 @@ function getBlockingMajorVersion(current, candidates) {
|
|
|
22442
22998
|
|
|
22443
22999
|
// src/hooks/auto-update-checker/cache.ts
|
|
22444
23000
|
function removeFromBunLock(installDir, packageName) {
|
|
22445
|
-
const lockPath =
|
|
23001
|
+
const lockPath = path9.join(installDir, "bun.lock");
|
|
22446
23002
|
if (!fs5.existsSync(lockPath))
|
|
22447
23003
|
return false;
|
|
22448
23004
|
try {
|
|
@@ -22493,7 +23049,7 @@ function ensureDependencyVersion(packageJsonPath, packageName, version) {
|
|
|
22493
23049
|
}
|
|
22494
23050
|
}
|
|
22495
23051
|
function removeInstalledPackage(installDir, packageName) {
|
|
22496
|
-
const pkgDir =
|
|
23052
|
+
const pkgDir = path9.join(installDir, "node_modules", packageName);
|
|
22497
23053
|
if (!fs5.existsSync(pkgDir))
|
|
22498
23054
|
return false;
|
|
22499
23055
|
fs5.rmSync(pkgDir, { recursive: true, force: true });
|
|
@@ -22502,18 +23058,18 @@ function removeInstalledPackage(installDir, packageName) {
|
|
|
22502
23058
|
}
|
|
22503
23059
|
function resolveInstallContext(runtimePackageJsonPath = getCurrentRuntimePackageJsonPath()) {
|
|
22504
23060
|
if (runtimePackageJsonPath) {
|
|
22505
|
-
const packageDir =
|
|
22506
|
-
const nodeModulesDir =
|
|
22507
|
-
if (
|
|
22508
|
-
const installDir =
|
|
22509
|
-
const packageJsonPath =
|
|
23061
|
+
const packageDir = path9.dirname(runtimePackageJsonPath);
|
|
23062
|
+
const nodeModulesDir = path9.dirname(packageDir);
|
|
23063
|
+
if (path9.basename(packageDir) === PACKAGE_NAME && path9.basename(nodeModulesDir) === "node_modules") {
|
|
23064
|
+
const installDir = path9.dirname(nodeModulesDir);
|
|
23065
|
+
const packageJsonPath = path9.join(installDir, "package.json");
|
|
22510
23066
|
if (fs5.existsSync(packageJsonPath)) {
|
|
22511
23067
|
return { installDir, packageJsonPath };
|
|
22512
23068
|
}
|
|
22513
23069
|
}
|
|
22514
23070
|
return null;
|
|
22515
23071
|
}
|
|
22516
|
-
const legacyPackageJsonPath =
|
|
23072
|
+
const legacyPackageJsonPath = path9.join(CACHE_DIR, "package.json");
|
|
22517
23073
|
if (fs5.existsSync(legacyPackageJsonPath)) {
|
|
22518
23074
|
return { installDir: CACHE_DIR, packageJsonPath: legacyPackageJsonPath };
|
|
22519
23075
|
}
|
|
@@ -22542,9 +23098,136 @@ function preparePackageUpdate(version, packageName = PACKAGE_NAME, runtimePackag
|
|
|
22542
23098
|
}
|
|
22543
23099
|
}
|
|
22544
23100
|
|
|
23101
|
+
// src/hooks/auto-update-checker/skill-sync.ts
|
|
23102
|
+
import {
|
|
23103
|
+
copyFileSync as copyFileSync2,
|
|
23104
|
+
existsSync as existsSync6,
|
|
23105
|
+
lstatSync,
|
|
23106
|
+
mkdirSync as mkdirSync4,
|
|
23107
|
+
mkdtempSync as mkdtempSync2,
|
|
23108
|
+
readdirSync as readdirSync2,
|
|
23109
|
+
renameSync as renameSync3,
|
|
23110
|
+
rmSync as rmSync4
|
|
23111
|
+
} from "node:fs";
|
|
23112
|
+
import * as path10 from "node:path";
|
|
23113
|
+
function copyDirRecursive(src, dest) {
|
|
23114
|
+
const stat2 = lstatSync(src);
|
|
23115
|
+
if (stat2.isSymbolicLink()) {
|
|
23116
|
+
return;
|
|
23117
|
+
}
|
|
23118
|
+
if (stat2.isDirectory()) {
|
|
23119
|
+
mkdirSync4(dest, { recursive: true });
|
|
23120
|
+
const entries = readdirSync2(src);
|
|
23121
|
+
for (const entry of entries) {
|
|
23122
|
+
copyDirRecursive(path10.join(src, entry), path10.join(dest, entry));
|
|
23123
|
+
}
|
|
23124
|
+
} else if (stat2.isFile()) {
|
|
23125
|
+
const destDir = path10.dirname(dest);
|
|
23126
|
+
if (!existsSync6(destDir)) {
|
|
23127
|
+
mkdirSync4(destDir, { recursive: true });
|
|
23128
|
+
}
|
|
23129
|
+
copyFileSync2(src, dest);
|
|
23130
|
+
}
|
|
23131
|
+
}
|
|
23132
|
+
function syncBundledSkillsFromPackage(packageRoot) {
|
|
23133
|
+
const installed = [];
|
|
23134
|
+
const skippedExisting = [];
|
|
23135
|
+
const failed = [];
|
|
23136
|
+
const sourceSkillsDir = path10.join(packageRoot, "src", "skills");
|
|
23137
|
+
try {
|
|
23138
|
+
const stat2 = lstatSync(sourceSkillsDir);
|
|
23139
|
+
if (stat2.isSymbolicLink() || !stat2.isDirectory()) {
|
|
23140
|
+
log(`[skill-sync] Source skills directory is not a valid directory: ${sourceSkillsDir}`);
|
|
23141
|
+
return { installed, skippedExisting, failed };
|
|
23142
|
+
}
|
|
23143
|
+
} catch {
|
|
23144
|
+
log(`[skill-sync] Source skills directory does not exist or is unreadable: ${sourceSkillsDir}`);
|
|
23145
|
+
return { installed, skippedExisting, failed };
|
|
23146
|
+
}
|
|
23147
|
+
const destSkillsDir = path10.join(getConfigDir(), "skills");
|
|
23148
|
+
try {
|
|
23149
|
+
if (!existsSync6(destSkillsDir)) {
|
|
23150
|
+
mkdirSync4(destSkillsDir, { recursive: true });
|
|
23151
|
+
}
|
|
23152
|
+
} catch (err) {
|
|
23153
|
+
log(`[skill-sync] Failed to create destination skills directory: ${destSkillsDir}`, err);
|
|
23154
|
+
}
|
|
23155
|
+
let entries = [];
|
|
23156
|
+
try {
|
|
23157
|
+
entries = readdirSync2(sourceSkillsDir);
|
|
23158
|
+
} catch (err) {
|
|
23159
|
+
log(`[skill-sync] Failed to read source skills directory: ${sourceSkillsDir}`, err);
|
|
23160
|
+
return { installed, skippedExisting, failed };
|
|
23161
|
+
}
|
|
23162
|
+
for (const entry of entries) {
|
|
23163
|
+
const entryPath = path10.join(sourceSkillsDir, entry);
|
|
23164
|
+
try {
|
|
23165
|
+
if (entry.startsWith(".")) {
|
|
23166
|
+
continue;
|
|
23167
|
+
}
|
|
23168
|
+
const entryStat = lstatSync(entryPath);
|
|
23169
|
+
if (entryStat.isSymbolicLink() || !entryStat.isDirectory()) {
|
|
23170
|
+
continue;
|
|
23171
|
+
}
|
|
23172
|
+
const skillMdPath = path10.join(entryPath, "SKILL.md");
|
|
23173
|
+
try {
|
|
23174
|
+
const skillMdStat = lstatSync(skillMdPath);
|
|
23175
|
+
if (skillMdStat.isSymbolicLink() || !skillMdStat.isFile()) {
|
|
23176
|
+
continue;
|
|
23177
|
+
}
|
|
23178
|
+
} catch {
|
|
23179
|
+
continue;
|
|
23180
|
+
}
|
|
23181
|
+
const destPath = path10.join(destSkillsDir, entry);
|
|
23182
|
+
let destExists = false;
|
|
23183
|
+
try {
|
|
23184
|
+
lstatSync(destPath);
|
|
23185
|
+
destExists = true;
|
|
23186
|
+
} catch {}
|
|
23187
|
+
if (destExists) {
|
|
23188
|
+
log(`[skill-sync] Skill already exists in destination: ${entry}`);
|
|
23189
|
+
skippedExisting.push(entry);
|
|
23190
|
+
continue;
|
|
23191
|
+
}
|
|
23192
|
+
const stagingDir = mkdtempSync2(path10.join(destSkillsDir, `.sync-staging-${entry}-`));
|
|
23193
|
+
try {
|
|
23194
|
+
copyDirRecursive(entryPath, stagingDir);
|
|
23195
|
+
let destExistsLate = false;
|
|
23196
|
+
try {
|
|
23197
|
+
lstatSync(destPath);
|
|
23198
|
+
destExistsLate = true;
|
|
23199
|
+
} catch {}
|
|
23200
|
+
if (destExistsLate) {
|
|
23201
|
+
log(`[skill-sync] Destination path was created during staging for ${entry}, skipping promotion.`);
|
|
23202
|
+
skippedExisting.push(entry);
|
|
23203
|
+
} else {
|
|
23204
|
+
renameSync3(stagingDir, destPath);
|
|
23205
|
+
installed.push(entry);
|
|
23206
|
+
log(`[skill-sync] Successfully synced skill: ${entry}`);
|
|
23207
|
+
}
|
|
23208
|
+
} catch (err) {
|
|
23209
|
+
log(`[skill-sync] Failed to sync skill ${entry}:`, err);
|
|
23210
|
+
failed.push(entry);
|
|
23211
|
+
} finally {
|
|
23212
|
+
try {
|
|
23213
|
+
if (existsSync6(stagingDir)) {
|
|
23214
|
+
rmSync4(stagingDir, { recursive: true, force: true });
|
|
23215
|
+
}
|
|
23216
|
+
} catch (err) {
|
|
23217
|
+
log(`[skill-sync] Failed to clean up staging directory ${stagingDir}:`, err);
|
|
23218
|
+
}
|
|
23219
|
+
}
|
|
23220
|
+
} catch (err) {
|
|
23221
|
+
log(`[skill-sync] Error processing source entry ${entry}:`, err);
|
|
23222
|
+
failed.push(entry);
|
|
23223
|
+
}
|
|
23224
|
+
}
|
|
23225
|
+
return { installed, skippedExisting, failed };
|
|
23226
|
+
}
|
|
23227
|
+
|
|
22545
23228
|
// src/hooks/auto-update-checker/index.ts
|
|
22546
23229
|
function createAutoUpdateCheckerHook(ctx, options = {}) {
|
|
22547
|
-
const { autoUpdate = true } = options;
|
|
23230
|
+
const { autoUpdate = true, companion } = options;
|
|
22548
23231
|
let hasChecked = false;
|
|
22549
23232
|
return {
|
|
22550
23233
|
event: ({ event }) => {
|
|
@@ -22562,14 +23245,14 @@ function createAutoUpdateCheckerHook(ctx, options = {}) {
|
|
|
22562
23245
|
log("[auto-update-checker] Local development mode");
|
|
22563
23246
|
return;
|
|
22564
23247
|
}
|
|
22565
|
-
runBackgroundUpdateCheck(ctx, autoUpdate).catch((err) => {
|
|
23248
|
+
runBackgroundUpdateCheck(ctx, autoUpdate, companion).catch((err) => {
|
|
22566
23249
|
log("[auto-update-checker] Background update check failed:", err);
|
|
22567
23250
|
});
|
|
22568
23251
|
}, 0);
|
|
22569
23252
|
}
|
|
22570
23253
|
};
|
|
22571
23254
|
}
|
|
22572
|
-
async function runBackgroundUpdateCheck(ctx, autoUpdate) {
|
|
23255
|
+
async function runBackgroundUpdateCheck(ctx, autoUpdate, companion) {
|
|
22573
23256
|
const pluginInfo = findPluginEntry(ctx.directory);
|
|
22574
23257
|
if (!pluginInfo) {
|
|
22575
23258
|
log("[auto-update-checker] Plugin not found in config");
|
|
@@ -22624,8 +23307,54 @@ Version is pinned. Update your plugin config to apply.`, "info", 8000);
|
|
|
22624
23307
|
}
|
|
22625
23308
|
const installSuccess = await runBunInstallSafe(installDir);
|
|
22626
23309
|
if (installSuccess) {
|
|
22627
|
-
|
|
22628
|
-
|
|
23310
|
+
let installedSkills = [];
|
|
23311
|
+
let companionUpdated = false;
|
|
23312
|
+
let companionWillRetry = false;
|
|
23313
|
+
const packageRoot = path11.join(installDir, "node_modules", PACKAGE_NAME);
|
|
23314
|
+
try {
|
|
23315
|
+
const syncResult = syncBundledSkillsFromPackage(packageRoot);
|
|
23316
|
+
installedSkills = syncResult.installed;
|
|
23317
|
+
if (syncResult.failed.length > 0) {
|
|
23318
|
+
log(`[auto-update-checker] Skill sync warnings/failures: ${syncResult.failed.join(", ")}`);
|
|
23319
|
+
}
|
|
23320
|
+
if (syncResult.skippedExisting.length > 0) {
|
|
23321
|
+
log(`[auto-update-checker] Skill sync skipped existing: ${syncResult.skippedExisting.join(", ")}`);
|
|
23322
|
+
}
|
|
23323
|
+
} catch (err) {
|
|
23324
|
+
log("[auto-update-checker] Skill sync failed silently:", err);
|
|
23325
|
+
}
|
|
23326
|
+
if (companion?.enabled === true) {
|
|
23327
|
+
try {
|
|
23328
|
+
const manifest = loadCompanionManifestFromPackageRoot(packageRoot);
|
|
23329
|
+
const companionResult = await ensureCompanionVersion({
|
|
23330
|
+
config: companion,
|
|
23331
|
+
manifest: manifest ?? undefined
|
|
23332
|
+
});
|
|
23333
|
+
if (companionResult.status === "installed") {
|
|
23334
|
+
companionUpdated = true;
|
|
23335
|
+
} else if (companionResult.status === "failed") {
|
|
23336
|
+
companionWillRetry = true;
|
|
23337
|
+
log("[auto-update-checker] Companion update failed; will retry on restart:", companionResult.error);
|
|
23338
|
+
} else if (companionResult.status === "skipped") {
|
|
23339
|
+
log("[auto-update-checker] Companion update skipped:", companionResult.reason);
|
|
23340
|
+
}
|
|
23341
|
+
} catch (err) {
|
|
23342
|
+
companionWillRetry = true;
|
|
23343
|
+
log("[auto-update-checker] Companion update failed silently; will retry on restart:", err);
|
|
23344
|
+
}
|
|
23345
|
+
}
|
|
23346
|
+
const messageLines = [`v${currentVersion} → v${latestVersion}`];
|
|
23347
|
+
if (installedSkills.length > 0) {
|
|
23348
|
+
messageLines.push(`Added bundled skills: ${installedSkills.join(", ")}`);
|
|
23349
|
+
}
|
|
23350
|
+
if (companionUpdated) {
|
|
23351
|
+
messageLines.push("Companion updated.");
|
|
23352
|
+
} else if (companionWillRetry) {
|
|
23353
|
+
messageLines.push("Companion update will retry on restart.");
|
|
23354
|
+
}
|
|
23355
|
+
messageLines.push("Restart OpenCode to apply.");
|
|
23356
|
+
showToast(ctx, "OMO-Slim Updated!", messageLines.join(`
|
|
23357
|
+
`), "success", 8000);
|
|
22629
23358
|
log(`[auto-update-checker] Update installed: ${currentVersion} → ${latestVersion}`);
|
|
22630
23359
|
} else {
|
|
22631
23360
|
showToast(ctx, `OMO-Slim ${latestVersion}`, `v${latestVersion} available, but auto-update failed to install it. Check logs or retry manually.`, "error", 8000);
|
|
@@ -22728,10 +23457,6 @@ function createDisplayNameMentionRewriter(config) {
|
|
|
22728
23457
|
};
|
|
22729
23458
|
}
|
|
22730
23459
|
// src/utils/task.ts
|
|
22731
|
-
var TRANSIENT_PROCESS_ERROR_TEXT = new Set([
|
|
22732
|
-
"Task is not running in this process and has no final output.",
|
|
22733
|
-
"Task is not running in this process and has not produced output."
|
|
22734
|
-
]);
|
|
22735
23460
|
function parseTaskIdFromTaskOutput(output) {
|
|
22736
23461
|
const xmlMatch = /<task\s+[^>]*\bid=["']([^"']+)["'][^>]*>/i.exec(output);
|
|
22737
23462
|
if (xmlMatch)
|
|
@@ -23012,7 +23737,7 @@ class BackgroundJobBoard {
|
|
|
23012
23737
|
existing.set(file.path, { ...file });
|
|
23013
23738
|
}
|
|
23014
23739
|
}
|
|
23015
|
-
const contextFiles = [...existing.values()].filter((file) => file.lineCount >= this.readContextMinLines).sort((a, b) => b.lastReadAt - a.lastReadAt).slice(0, this.readContextMaxFiles + 1);
|
|
23740
|
+
const contextFiles = [...existing.values()].filter((file) => file.lineCount >= this.readContextMinLines).sort((a, b) => b.lineCount - a.lineCount || b.lastReadAt - a.lastReadAt || a.path.localeCompare(b.path)).slice(0, this.readContextMaxFiles + 1);
|
|
23016
23741
|
this.jobs.set(taskID, { ...job, contextFiles });
|
|
23017
23742
|
}
|
|
23018
23743
|
list(parentSessionID) {
|
|
@@ -23134,11 +23859,13 @@ function normalizeCancelReason(reason) {
|
|
|
23134
23859
|
const normalized = reason?.replace(/\s+/g, " ").trim();
|
|
23135
23860
|
return normalized ? `cancelled: ${normalized}` : "cancelled";
|
|
23136
23861
|
}
|
|
23137
|
-
// src/utils/
|
|
23138
|
-
var SLIM_INTERNAL_INITIATOR_MARKER = "<!-- SLIM_INTERNAL_INITIATOR -->";
|
|
23862
|
+
// src/utils/guards.ts
|
|
23139
23863
|
function isRecord(value) {
|
|
23140
23864
|
return typeof value === "object" && value !== null;
|
|
23141
23865
|
}
|
|
23866
|
+
|
|
23867
|
+
// src/utils/internal-initiator.ts
|
|
23868
|
+
var SLIM_INTERNAL_INITIATOR_MARKER = "<!-- SLIM_INTERNAL_INITIATOR -->";
|
|
23142
23869
|
function createInternalAgentTextPart(text) {
|
|
23143
23870
|
return {
|
|
23144
23871
|
type: "text",
|
|
@@ -23155,86 +23882,10 @@ function hasInternalInitiatorMarker(part) {
|
|
|
23155
23882
|
}
|
|
23156
23883
|
return part.text.includes(SLIM_INTERNAL_INITIATOR_MARKER);
|
|
23157
23884
|
}
|
|
23158
|
-
|
|
23159
|
-
|
|
23160
|
-
|
|
23161
|
-
|
|
23162
|
-
function getWindowsBuildNumber() {
|
|
23163
|
-
if (process.platform !== "win32")
|
|
23164
|
-
return null;
|
|
23165
|
-
const parts = release().split(".");
|
|
23166
|
-
if (parts.length >= 3) {
|
|
23167
|
-
const build = parseInt(parts[2], 10);
|
|
23168
|
-
if (!Number.isNaN(build))
|
|
23169
|
-
return build;
|
|
23170
|
-
}
|
|
23171
|
-
return null;
|
|
23172
|
-
}
|
|
23173
|
-
function isPwshAvailable() {
|
|
23174
|
-
if (process.platform !== "win32")
|
|
23175
|
-
return false;
|
|
23176
|
-
const result = spawnSync("where", ["pwsh"], {
|
|
23177
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
23178
|
-
});
|
|
23179
|
-
return result.status === 0;
|
|
23180
|
-
}
|
|
23181
|
-
function escapePowerShellPath(path9) {
|
|
23182
|
-
return path9.replace(/'/g, "''");
|
|
23183
|
-
}
|
|
23184
|
-
function getWindowsZipExtractor() {
|
|
23185
|
-
const buildNumber = getWindowsBuildNumber();
|
|
23186
|
-
if (buildNumber !== null && buildNumber >= WINDOWS_BUILD_WITH_TAR) {
|
|
23187
|
-
return "tar";
|
|
23188
|
-
}
|
|
23189
|
-
if (isPwshAvailable()) {
|
|
23190
|
-
return "pwsh";
|
|
23191
|
-
}
|
|
23192
|
-
return "powershell";
|
|
23193
|
-
}
|
|
23194
|
-
async function extractZip(archivePath, destDir) {
|
|
23195
|
-
let proc;
|
|
23196
|
-
if (process.platform === "win32") {
|
|
23197
|
-
const extractor = getWindowsZipExtractor();
|
|
23198
|
-
switch (extractor) {
|
|
23199
|
-
case "tar":
|
|
23200
|
-
proc = crossSpawn(["tar", "-xf", archivePath, "-C", destDir], {
|
|
23201
|
-
stdout: "ignore",
|
|
23202
|
-
stderr: "pipe"
|
|
23203
|
-
});
|
|
23204
|
-
break;
|
|
23205
|
-
case "pwsh":
|
|
23206
|
-
proc = crossSpawn([
|
|
23207
|
-
"pwsh",
|
|
23208
|
-
"-Command",
|
|
23209
|
-
`Expand-Archive -Path '${escapePowerShellPath(archivePath)}' -DestinationPath '${escapePowerShellPath(destDir)}' -Force`
|
|
23210
|
-
], {
|
|
23211
|
-
stdout: "ignore",
|
|
23212
|
-
stderr: "pipe"
|
|
23213
|
-
});
|
|
23214
|
-
break;
|
|
23215
|
-
default:
|
|
23216
|
-
proc = crossSpawn([
|
|
23217
|
-
"powershell",
|
|
23218
|
-
"-Command",
|
|
23219
|
-
`Expand-Archive -Path '${escapePowerShellPath(archivePath)}' -DestinationPath '${escapePowerShellPath(destDir)}' -Force`
|
|
23220
|
-
], {
|
|
23221
|
-
stdout: "ignore",
|
|
23222
|
-
stderr: "pipe"
|
|
23223
|
-
});
|
|
23224
|
-
break;
|
|
23225
|
-
}
|
|
23226
|
-
} else {
|
|
23227
|
-
proc = crossSpawn(["unzip", "-o", archivePath, "-d", destDir], {
|
|
23228
|
-
stdout: "ignore",
|
|
23229
|
-
stderr: "pipe"
|
|
23230
|
-
});
|
|
23231
|
-
}
|
|
23232
|
-
const exitCode = await proc.exited;
|
|
23233
|
-
if (exitCode !== 0) {
|
|
23234
|
-
const stderr = await proc.stderr();
|
|
23235
|
-
throw new Error(`zip extraction failed (exit ${exitCode}): ${stderr}`);
|
|
23236
|
-
}
|
|
23237
|
-
}
|
|
23885
|
+
|
|
23886
|
+
// src/utils/index.ts
|
|
23887
|
+
init_zip_extractor();
|
|
23888
|
+
|
|
23238
23889
|
// src/hooks/chat-headers.ts
|
|
23239
23890
|
var INTERNAL_MARKER_CACHE_LIMIT = 1000;
|
|
23240
23891
|
var internalMarkerCache = new Map;
|
|
@@ -23392,7 +24043,7 @@ function detectDelegateTaskError(output) {
|
|
|
23392
24043
|
return null;
|
|
23393
24044
|
}
|
|
23394
24045
|
|
|
23395
|
-
// src/hooks/delegate-task-retry/
|
|
24046
|
+
// src/hooks/delegate-task-retry/hook.ts
|
|
23396
24047
|
function extractAvailableList(output) {
|
|
23397
24048
|
const match = output.match(/Allowed agents:\s*(.+)$/m);
|
|
23398
24049
|
if (match)
|
|
@@ -23422,7 +24073,6 @@ function buildRetryGuidance(errorInfo) {
|
|
|
23422
24073
|
return lines.join(`
|
|
23423
24074
|
`);
|
|
23424
24075
|
}
|
|
23425
|
-
// src/hooks/delegate-task-retry/hook.ts
|
|
23426
24076
|
function createDelegateTaskRetryHook(_ctx) {
|
|
23427
24077
|
return {
|
|
23428
24078
|
"tool.execute.after": async (input, output) => {
|
|
@@ -23548,12 +24198,6 @@ function isRateLimitError(error) {
|
|
|
23548
24198
|
].join(" ");
|
|
23549
24199
|
return RATE_LIMIT_PATTERNS.some((p) => p.test(text));
|
|
23550
24200
|
}
|
|
23551
|
-
function parseModel(model) {
|
|
23552
|
-
const slash = model.indexOf("/");
|
|
23553
|
-
if (slash <= 0 || slash >= model.length - 1)
|
|
23554
|
-
return null;
|
|
23555
|
-
return { providerID: model.slice(0, slash), modelID: model.slice(slash + 1) };
|
|
23556
|
-
}
|
|
23557
24201
|
var DEDUP_WINDOW_MS = 5000;
|
|
23558
24202
|
var REPROMPT_DELAY_MS = 500;
|
|
23559
24203
|
|
|
@@ -23671,7 +24315,7 @@ class ForegroundFallbackManager {
|
|
|
23671
24315
|
return;
|
|
23672
24316
|
}
|
|
23673
24317
|
tried.add(nextModel);
|
|
23674
|
-
const ref =
|
|
24318
|
+
const ref = parseModelReference(nextModel);
|
|
23675
24319
|
if (!ref) {
|
|
23676
24320
|
log("[foreground-fallback] invalid model format", {
|
|
23677
24321
|
sessionID,
|
|
@@ -23746,17 +24390,17 @@ class ForegroundFallbackManager {
|
|
|
23746
24390
|
}
|
|
23747
24391
|
}
|
|
23748
24392
|
// src/hooks/image-hook.ts
|
|
23749
|
-
import { createHash } from "node:crypto";
|
|
24393
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
23750
24394
|
import {
|
|
23751
|
-
existsSync as
|
|
23752
|
-
mkdirSync as
|
|
23753
|
-
readdirSync as
|
|
24395
|
+
existsSync as existsSync7,
|
|
24396
|
+
mkdirSync as mkdirSync5,
|
|
24397
|
+
readdirSync as readdirSync3,
|
|
23754
24398
|
rmdirSync,
|
|
23755
|
-
statSync as
|
|
24399
|
+
statSync as statSync4,
|
|
23756
24400
|
unlinkSync as unlinkSync2,
|
|
23757
|
-
writeFileSync as
|
|
24401
|
+
writeFileSync as writeFileSync5
|
|
23758
24402
|
} from "node:fs";
|
|
23759
|
-
import { basename as basename2, extname, join as
|
|
24403
|
+
import { basename as basename2, extname, join as join11 } from "node:path";
|
|
23760
24404
|
var lastCleanupByDir = new Map;
|
|
23761
24405
|
var CLEANUP_INTERVAL = 10 * 60 * 1000;
|
|
23762
24406
|
function isImagePart(p) {
|
|
@@ -23803,13 +24447,13 @@ function cleanupAllSessions(saveDir) {
|
|
|
23803
24447
|
const maxAge = 60 * 60 * 1000;
|
|
23804
24448
|
const dirsToScan = [];
|
|
23805
24449
|
try {
|
|
23806
|
-
for (const entry of
|
|
23807
|
-
const fp =
|
|
24450
|
+
for (const entry of readdirSync3(saveDir, { withFileTypes: true })) {
|
|
24451
|
+
const fp = join11(saveDir, entry.name);
|
|
23808
24452
|
if (entry.isDirectory()) {
|
|
23809
24453
|
dirsToScan.push(fp);
|
|
23810
24454
|
} else {
|
|
23811
24455
|
try {
|
|
23812
|
-
if (now -
|
|
24456
|
+
if (now - statSync4(fp).mtimeMs > maxAge)
|
|
23813
24457
|
unlinkSync2(fp);
|
|
23814
24458
|
} catch {}
|
|
23815
24459
|
}
|
|
@@ -23819,11 +24463,11 @@ function cleanupAllSessions(saveDir) {
|
|
|
23819
24463
|
try {
|
|
23820
24464
|
let isEmpty = true;
|
|
23821
24465
|
let allRemoved = true;
|
|
23822
|
-
for (const f of
|
|
24466
|
+
for (const f of readdirSync3(dir)) {
|
|
23823
24467
|
isEmpty = false;
|
|
23824
|
-
const fp =
|
|
24468
|
+
const fp = join11(dir, f);
|
|
23825
24469
|
try {
|
|
23826
|
-
if (now -
|
|
24470
|
+
if (now - statSync4(fp).mtimeMs > maxAge) {
|
|
23827
24471
|
unlinkSync2(fp);
|
|
23828
24472
|
} else {
|
|
23829
24473
|
allRemoved = false;
|
|
@@ -23843,20 +24487,20 @@ function cleanupAllSessions(saveDir) {
|
|
|
23843
24487
|
function writeUniqueFile(dir, name, data, log2) {
|
|
23844
24488
|
const ext = extname(name);
|
|
23845
24489
|
const base = basename2(name, ext) || name;
|
|
23846
|
-
let candidate =
|
|
23847
|
-
if (
|
|
24490
|
+
let candidate = join11(dir, name);
|
|
24491
|
+
if (existsSync7(candidate)) {
|
|
23848
24492
|
return candidate;
|
|
23849
24493
|
}
|
|
23850
24494
|
let counter = 0;
|
|
23851
24495
|
const MAX_ATTEMPTS = 1000;
|
|
23852
24496
|
for (let attempt = 0;attempt < MAX_ATTEMPTS; attempt++) {
|
|
23853
24497
|
try {
|
|
23854
|
-
|
|
24498
|
+
writeFileSync5(candidate, data, { flag: "wx" });
|
|
23855
24499
|
return candidate;
|
|
23856
24500
|
} catch (e) {
|
|
23857
24501
|
if (e instanceof Error && e.code === "EEXIST") {
|
|
23858
24502
|
counter += 1;
|
|
23859
|
-
candidate =
|
|
24503
|
+
candidate = join11(dir, `${base}-${counter}${ext}`);
|
|
23860
24504
|
continue;
|
|
23861
24505
|
}
|
|
23862
24506
|
log2(`[image-hook] failed to save image: ${e}`);
|
|
@@ -23880,17 +24524,17 @@ function processImageAttachments(args) {
|
|
|
23880
24524
|
messagesWithImages.push({ msg, imageParts });
|
|
23881
24525
|
}
|
|
23882
24526
|
}
|
|
23883
|
-
const saveDir =
|
|
24527
|
+
const saveDir = join11(workDir, ".opencode", "images");
|
|
23884
24528
|
if (messagesWithImages.length === 0) {
|
|
23885
|
-
if (
|
|
24529
|
+
if (existsSync7(saveDir))
|
|
23886
24530
|
cleanupAllSessions(saveDir);
|
|
23887
24531
|
return;
|
|
23888
24532
|
}
|
|
23889
|
-
const gitignorePath =
|
|
24533
|
+
const gitignorePath = join11(workDir, ".opencode", ".gitignore");
|
|
23890
24534
|
try {
|
|
23891
|
-
|
|
23892
|
-
if (!
|
|
23893
|
-
|
|
24535
|
+
mkdirSync5(saveDir, { recursive: true });
|
|
24536
|
+
if (!existsSync7(gitignorePath))
|
|
24537
|
+
writeFileSync5(gitignorePath, `*
|
|
23894
24538
|
`);
|
|
23895
24539
|
} catch (e) {
|
|
23896
24540
|
log2(`[image-hook] failed to create image directory: ${e}`);
|
|
@@ -23898,9 +24542,9 @@ function processImageAttachments(args) {
|
|
|
23898
24542
|
cleanupAllSessions(saveDir);
|
|
23899
24543
|
for (const { msg, imageParts } of messagesWithImages) {
|
|
23900
24544
|
const sessionSubdir = msg.info.sessionID ? sanitizeFilename(msg.info.sessionID) : undefined;
|
|
23901
|
-
const targetDir = sessionSubdir ?
|
|
24545
|
+
const targetDir = sessionSubdir ? join11(saveDir, sessionSubdir) : saveDir;
|
|
23902
24546
|
try {
|
|
23903
|
-
|
|
24547
|
+
mkdirSync5(targetDir, { recursive: true });
|
|
23904
24548
|
} catch (e) {
|
|
23905
24549
|
log2(`[image-hook] failed to create target image directory: ${e}`);
|
|
23906
24550
|
}
|
|
@@ -23911,7 +24555,7 @@ function processImageAttachments(args) {
|
|
|
23911
24555
|
if (url) {
|
|
23912
24556
|
const decoded = decodeDataUrl(url);
|
|
23913
24557
|
if (decoded) {
|
|
23914
|
-
const hash =
|
|
24558
|
+
const hash = createHash2("sha1").update(decoded.data).digest("hex").slice(0, 8);
|
|
23915
24559
|
const sanitizedFilename = filename ? sanitizeFilename(filename) : undefined;
|
|
23916
24560
|
const baseName = sanitizedFilename ? sanitizedFilename.replace(/\.[^.]+$/, "") || "image" : "image";
|
|
23917
24561
|
const ext = sanitizedFilename ? extname(sanitizedFilename) || extFromMime(decoded.mime) : extFromMime(decoded.mime);
|
|
@@ -23984,7 +24628,6 @@ ${JSON_ERROR_REMINDER}`;
|
|
|
23984
24628
|
};
|
|
23985
24629
|
}
|
|
23986
24630
|
// src/hooks/phase-reminder/index.ts
|
|
23987
|
-
var PHASE_REMINDER = `<internal_reminder>${PHASE_REMINDER_TEXT}</internal_reminder>`;
|
|
23988
24631
|
function createPhaseReminderHook() {
|
|
23989
24632
|
return {
|
|
23990
24633
|
"experimental.chat.messages.transform": async (_input, output) => {
|
|
@@ -24026,24 +24669,18 @@ function createPhaseReminderHook() {
|
|
|
24026
24669
|
};
|
|
24027
24670
|
}
|
|
24028
24671
|
// src/hooks/post-file-tool-nudge/index.ts
|
|
24029
|
-
var POST_FILE_TOOL_NUDGE = PHASE_REMINDER_TEXT;
|
|
24030
24672
|
var FILE_TOOLS = new Set(["Read", "read", "Write", "write"]);
|
|
24031
24673
|
function createPostFileToolNudgeHook(options = {}) {
|
|
24032
24674
|
function appendReminder(output) {
|
|
24033
24675
|
if (typeof output.output !== "string") {
|
|
24034
24676
|
return;
|
|
24035
24677
|
}
|
|
24036
|
-
if (output.output.includes(
|
|
24678
|
+
if (output.output.includes(PHASE_REMINDER)) {
|
|
24037
24679
|
return;
|
|
24038
24680
|
}
|
|
24039
|
-
output.output =
|
|
24040
|
-
|
|
24041
|
-
|
|
24042
|
-
"<internal_reminder>",
|
|
24043
|
-
POST_FILE_TOOL_NUDGE,
|
|
24044
|
-
"</internal_reminder>"
|
|
24045
|
-
].join(`
|
|
24046
|
-
`);
|
|
24681
|
+
output.output = `${output.output}
|
|
24682
|
+
|
|
24683
|
+
${PHASE_REMINDER}`;
|
|
24047
24684
|
}
|
|
24048
24685
|
return {
|
|
24049
24686
|
"tool.execute.after": async (input, output) => {
|
|
@@ -24057,8 +24694,59 @@ function createPostFileToolNudgeHook(options = {}) {
|
|
|
24057
24694
|
}
|
|
24058
24695
|
};
|
|
24059
24696
|
}
|
|
24697
|
+
// src/hooks/reflect/index.ts
|
|
24698
|
+
var COMMAND_NAME2 = "reflect";
|
|
24699
|
+
function activationPrompt2(focus) {
|
|
24700
|
+
const focusBlock = focus ? ["Focus:", focus] : [
|
|
24701
|
+
"Focus:",
|
|
24702
|
+
"Review recent work broadly and identify repeated workflow friction worth improving."
|
|
24703
|
+
];
|
|
24704
|
+
return [
|
|
24705
|
+
"Use the reflect skill for this request.",
|
|
24706
|
+
"",
|
|
24707
|
+
"Reflect requirements:",
|
|
24708
|
+
"- inspect existing skills, commands, agents, prompt overrides, MCP permissions, config, and project playbooks before suggesting anything new;",
|
|
24709
|
+
"- find repeated workflow patterns from the current conversation, project notes, local memories, logs, or session artifacts that are available and safe to inspect;",
|
|
24710
|
+
"- prefer evidence from repeated recent behavior over speculation;",
|
|
24711
|
+
"- recommend the smallest useful improvement: prompt/config rule, skill, command, custom agent, MCP/tool permission change, project playbook, or skip;",
|
|
24712
|
+
"- treat creating nothing as a valid result when evidence is weak;",
|
|
24713
|
+
"- ask before changing prompts, skills, commands, agents, MCP access, or config unless the user explicitly requested the exact edit;",
|
|
24714
|
+
"- return a compact report with findings, recommended changes, skipped candidates, and items needing more evidence.",
|
|
24715
|
+
"",
|
|
24716
|
+
...focusBlock
|
|
24717
|
+
].join(`
|
|
24718
|
+
`);
|
|
24719
|
+
}
|
|
24720
|
+
function createReflectCommandHook() {
|
|
24721
|
+
let shouldHandleCommand = false;
|
|
24722
|
+
return {
|
|
24723
|
+
registerCommand: (opencodeConfig) => {
|
|
24724
|
+
const commandConfig = opencodeConfig.command;
|
|
24725
|
+
if (commandConfig?.[COMMAND_NAME2]) {
|
|
24726
|
+
shouldHandleCommand = false;
|
|
24727
|
+
return;
|
|
24728
|
+
}
|
|
24729
|
+
if (!opencodeConfig.command)
|
|
24730
|
+
opencodeConfig.command = {};
|
|
24731
|
+
opencodeConfig.command[COMMAND_NAME2] = {
|
|
24732
|
+
template: "Review repeated work and suggest workflow improvements",
|
|
24733
|
+
description: "Use reflect to learn from repeated workflows and suggest reusable improvements"
|
|
24734
|
+
};
|
|
24735
|
+
shouldHandleCommand = true;
|
|
24736
|
+
},
|
|
24737
|
+
handleCommandExecuteBefore: async (input, output) => {
|
|
24738
|
+
if (input.command !== COMMAND_NAME2 || !shouldHandleCommand)
|
|
24739
|
+
return;
|
|
24740
|
+
output.parts.length = 0;
|
|
24741
|
+
output.parts.push({
|
|
24742
|
+
type: "text",
|
|
24743
|
+
text: activationPrompt2(input.arguments.trim())
|
|
24744
|
+
});
|
|
24745
|
+
}
|
|
24746
|
+
};
|
|
24747
|
+
}
|
|
24060
24748
|
// src/hooks/task-session-manager/index.ts
|
|
24061
|
-
import
|
|
24749
|
+
import path12 from "node:path";
|
|
24062
24750
|
var AGENT_NAME_SET = new Set([
|
|
24063
24751
|
"orchestrator",
|
|
24064
24752
|
"oracle",
|
|
@@ -24104,9 +24792,6 @@ function createOccurrenceId(part, message, partIndex) {
|
|
|
24104
24792
|
function isAgentName(value) {
|
|
24105
24793
|
return typeof value === "string" && AGENT_NAME_SET.has(value);
|
|
24106
24794
|
}
|
|
24107
|
-
function isObjectRecord(value) {
|
|
24108
|
-
return typeof value === "object" && value !== null;
|
|
24109
|
-
}
|
|
24110
24795
|
function extractPath(output) {
|
|
24111
24796
|
return /<path>([^<]+)<\/path>/.exec(output)?.[1];
|
|
24112
24797
|
}
|
|
@@ -24115,8 +24800,8 @@ function extractTaskSummary(output) {
|
|
|
24115
24800
|
return summary?.trim() || undefined;
|
|
24116
24801
|
}
|
|
24117
24802
|
function normalizePath(root, file) {
|
|
24118
|
-
const relative =
|
|
24119
|
-
if (!relative || relative.startsWith("..") ||
|
|
24803
|
+
const relative = path12.relative(root, file);
|
|
24804
|
+
if (!relative || relative.startsWith("..") || path12.isAbsolute(relative)) {
|
|
24120
24805
|
return file;
|
|
24121
24806
|
}
|
|
24122
24807
|
return relative;
|
|
@@ -24384,7 +25069,7 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
24384
25069
|
if (!input.sessionID || !options.shouldManageSession(input.sessionID)) {
|
|
24385
25070
|
return;
|
|
24386
25071
|
}
|
|
24387
|
-
if (!
|
|
25072
|
+
if (!isRecord(output.args))
|
|
24388
25073
|
return;
|
|
24389
25074
|
const args = output.args;
|
|
24390
25075
|
if (!isAgentName(args.subagent_type)) {
|
|
@@ -24658,7 +25343,7 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
24658
25343
|
result: status.result
|
|
24659
25344
|
});
|
|
24660
25345
|
output.output = formatCancelledTaskStatusOutput(status.taskID, existing?.resultSummary);
|
|
24661
|
-
if (
|
|
25346
|
+
if (isRecord(output) && isRecord(output.metadata)) {
|
|
24662
25347
|
output.metadata.state = "cancelled";
|
|
24663
25348
|
}
|
|
24664
25349
|
}
|
|
@@ -24682,7 +25367,7 @@ function formatCancelledTaskStatusOutput(taskID, summary = "cancelled") {
|
|
|
24682
25367
|
`);
|
|
24683
25368
|
}
|
|
24684
25369
|
// src/interview/manager.ts
|
|
24685
|
-
import
|
|
25370
|
+
import path16 from "node:path";
|
|
24686
25371
|
|
|
24687
25372
|
// src/interview/dashboard.ts
|
|
24688
25373
|
import crypto from "node:crypto";
|
|
@@ -24692,27 +25377,27 @@ import {
|
|
|
24692
25377
|
createServer
|
|
24693
25378
|
} from "node:http";
|
|
24694
25379
|
import os4 from "node:os";
|
|
24695
|
-
import
|
|
25380
|
+
import path14 from "node:path";
|
|
24696
25381
|
import { URL as URL2 } from "node:url";
|
|
24697
25382
|
|
|
24698
25383
|
// src/interview/document.ts
|
|
24699
25384
|
import * as fsSync from "node:fs";
|
|
24700
25385
|
import * as fs6 from "node:fs/promises";
|
|
24701
|
-
import * as
|
|
25386
|
+
import * as path13 from "node:path";
|
|
24702
25387
|
var DEFAULT_OUTPUT_FOLDER = "interview";
|
|
24703
25388
|
function normalizeOutputFolder(outputFolder) {
|
|
24704
25389
|
const normalized = outputFolder.trim().replace(/^\/+|\/+$/g, "");
|
|
24705
25390
|
return normalized || DEFAULT_OUTPUT_FOLDER;
|
|
24706
25391
|
}
|
|
24707
25392
|
function createInterviewDirectoryPath(directory, outputFolder) {
|
|
24708
|
-
return
|
|
25393
|
+
return path13.join(directory, normalizeOutputFolder(outputFolder));
|
|
24709
25394
|
}
|
|
24710
25395
|
function createInterviewFilePath(directory, outputFolder, idea) {
|
|
24711
25396
|
const fileName = `${slugify(idea) || "interview"}.md`;
|
|
24712
|
-
return
|
|
25397
|
+
return path13.join(createInterviewDirectoryPath(directory, outputFolder), fileName);
|
|
24713
25398
|
}
|
|
24714
25399
|
function relativeInterviewPath(directory, filePath) {
|
|
24715
|
-
return
|
|
25400
|
+
return path13.relative(directory, filePath) || path13.basename(filePath);
|
|
24716
25401
|
}
|
|
24717
25402
|
function resolveExistingInterviewPath(directory, outputFolder, value) {
|
|
24718
25403
|
const trimmed = value.trim();
|
|
@@ -24721,22 +25406,22 @@ function resolveExistingInterviewPath(directory, outputFolder, value) {
|
|
|
24721
25406
|
}
|
|
24722
25407
|
const outputDir = createInterviewDirectoryPath(directory, outputFolder);
|
|
24723
25408
|
const candidates = new Set;
|
|
24724
|
-
const resolvedRoot =
|
|
24725
|
-
if (
|
|
25409
|
+
const resolvedRoot = path13.resolve(directory);
|
|
25410
|
+
if (path13.isAbsolute(trimmed)) {
|
|
24726
25411
|
candidates.add(trimmed);
|
|
24727
25412
|
} else {
|
|
24728
|
-
candidates.add(
|
|
24729
|
-
candidates.add(
|
|
25413
|
+
candidates.add(path13.resolve(directory, trimmed));
|
|
25414
|
+
candidates.add(path13.join(outputDir, trimmed));
|
|
24730
25415
|
if (!trimmed.endsWith(".md")) {
|
|
24731
|
-
candidates.add(
|
|
25416
|
+
candidates.add(path13.join(outputDir, `${trimmed}.md`));
|
|
24732
25417
|
}
|
|
24733
25418
|
}
|
|
24734
25419
|
for (const candidate of candidates) {
|
|
24735
|
-
if (
|
|
25420
|
+
if (path13.extname(candidate) !== ".md") {
|
|
24736
25421
|
continue;
|
|
24737
25422
|
}
|
|
24738
|
-
const resolved =
|
|
24739
|
-
if (!resolved.startsWith(resolvedRoot +
|
|
25423
|
+
const resolved = path13.resolve(candidate);
|
|
25424
|
+
if (!resolved.startsWith(resolvedRoot + path13.sep) && resolved !== resolvedRoot) {
|
|
24740
25425
|
continue;
|
|
24741
25426
|
}
|
|
24742
25427
|
if (fsSync.existsSync(candidate)) {
|
|
@@ -24816,7 +25501,7 @@ function parseFrontmatter(content) {
|
|
|
24816
25501
|
return result;
|
|
24817
25502
|
}
|
|
24818
25503
|
async function ensureInterviewFile(record) {
|
|
24819
|
-
await fs6.mkdir(
|
|
25504
|
+
await fs6.mkdir(path13.dirname(record.markdownPath), { recursive: true });
|
|
24820
25505
|
try {
|
|
24821
25506
|
await fs6.access(record.markdownPath);
|
|
24822
25507
|
} catch {
|
|
@@ -26486,12 +27171,12 @@ function renderInterviewPage(interviewId, resumeSlug) {
|
|
|
26486
27171
|
|
|
26487
27172
|
// src/interview/dashboard.ts
|
|
26488
27173
|
function getAuthFilePath(port) {
|
|
26489
|
-
const dataHome = process.env.XDG_DATA_HOME ||
|
|
26490
|
-
return
|
|
27174
|
+
const dataHome = process.env.XDG_DATA_HOME || path14.join(os4.homedir(), ".local", "share");
|
|
27175
|
+
return path14.join(dataHome, "opencode", `.dashboard-${port}.json`);
|
|
26491
27176
|
}
|
|
26492
27177
|
function writeAuthFile(port, token) {
|
|
26493
27178
|
const filePath = getAuthFilePath(port);
|
|
26494
|
-
const dir =
|
|
27179
|
+
const dir = path14.dirname(filePath);
|
|
26495
27180
|
try {
|
|
26496
27181
|
fsSync2.mkdirSync(dir, { recursive: true });
|
|
26497
27182
|
} catch {}
|
|
@@ -26628,7 +27313,7 @@ function createDashboardServer(config) {
|
|
|
26628
27313
|
const directories = getKnownDirectories();
|
|
26629
27314
|
const items = [];
|
|
26630
27315
|
for (const dir of directories) {
|
|
26631
|
-
const interviewDir =
|
|
27316
|
+
const interviewDir = path14.join(dir, config.outputFolder);
|
|
26632
27317
|
let entries;
|
|
26633
27318
|
try {
|
|
26634
27319
|
entries = await fs7.readdir(interviewDir);
|
|
@@ -26640,7 +27325,7 @@ function createDashboardServer(config) {
|
|
|
26640
27325
|
continue;
|
|
26641
27326
|
let content;
|
|
26642
27327
|
try {
|
|
26643
|
-
content = await fs7.readFile(
|
|
27328
|
+
content = await fs7.readFile(path14.join(interviewDir, entry), "utf8");
|
|
26644
27329
|
} catch {
|
|
26645
27330
|
continue;
|
|
26646
27331
|
}
|
|
@@ -26666,7 +27351,7 @@ function createDashboardServer(config) {
|
|
|
26666
27351
|
const directories = getKnownDirectories();
|
|
26667
27352
|
let rebuilt = 0;
|
|
26668
27353
|
for (const dir of directories) {
|
|
26669
|
-
const interviewDir =
|
|
27354
|
+
const interviewDir = path14.join(dir, config.outputFolder);
|
|
26670
27355
|
let entries;
|
|
26671
27356
|
try {
|
|
26672
27357
|
entries = await fs7.readdir(interviewDir);
|
|
@@ -26678,7 +27363,7 @@ function createDashboardServer(config) {
|
|
|
26678
27363
|
continue;
|
|
26679
27364
|
let content;
|
|
26680
27365
|
try {
|
|
26681
|
-
content = await fs7.readFile(
|
|
27366
|
+
content = await fs7.readFile(path14.join(interviewDir, entry), "utf8");
|
|
26682
27367
|
} catch {
|
|
26683
27368
|
continue;
|
|
26684
27369
|
}
|
|
@@ -26704,7 +27389,7 @@ function createDashboardServer(config) {
|
|
|
26704
27389
|
questions: [],
|
|
26705
27390
|
pendingAnswers: null,
|
|
26706
27391
|
lastUpdatedAt: fm.updatedAt ? new Date(fm.updatedAt).getTime() : Date.now(),
|
|
26707
|
-
filePath:
|
|
27392
|
+
filePath: path14.join(interviewDir, entry),
|
|
26708
27393
|
nudgeAction: null
|
|
26709
27394
|
});
|
|
26710
27395
|
if (!sessions.has(fm.sessionID)) {
|
|
@@ -26968,7 +27653,7 @@ function createDashboardServer(config) {
|
|
|
26968
27653
|
const dirs = getKnownDirectories();
|
|
26969
27654
|
for (const dir of dirs) {
|
|
26970
27655
|
const slug = extractResumeSlug(interviewId);
|
|
26971
|
-
const candidate =
|
|
27656
|
+
const candidate = path14.join(dir, config.outputFolder, `${slug}.md`);
|
|
26972
27657
|
try {
|
|
26973
27658
|
document = await fs7.readFile(candidate, "utf8");
|
|
26974
27659
|
markdownPath = candidate;
|
|
@@ -27496,7 +28181,7 @@ function createInterviewServer(deps) {
|
|
|
27496
28181
|
// src/interview/service.ts
|
|
27497
28182
|
import { spawn as spawn2 } from "node:child_process";
|
|
27498
28183
|
import * as fs8 from "node:fs/promises";
|
|
27499
|
-
import * as
|
|
28184
|
+
import * as path15 from "node:path";
|
|
27500
28185
|
|
|
27501
28186
|
// src/interview/types.ts
|
|
27502
28187
|
import { z as z3 } from "zod";
|
|
@@ -27668,7 +28353,7 @@ function buildAnswerPrompt(answers, questions, maxQuestions) {
|
|
|
27668
28353
|
}
|
|
27669
28354
|
|
|
27670
28355
|
// src/interview/service.ts
|
|
27671
|
-
var
|
|
28356
|
+
var COMMAND_NAME3 = "interview";
|
|
27672
28357
|
var DEFAULT_MAX_QUESTIONS = 2;
|
|
27673
28358
|
function isTruthyEnvFlag(value) {
|
|
27674
28359
|
if (!value) {
|
|
@@ -27676,21 +28361,21 @@ function isTruthyEnvFlag(value) {
|
|
|
27676
28361
|
}
|
|
27677
28362
|
return value !== "0" && value.toLowerCase() !== "false";
|
|
27678
28363
|
}
|
|
27679
|
-
function isAutomatedRuntime(
|
|
27680
|
-
return
|
|
28364
|
+
function isAutomatedRuntime(env) {
|
|
28365
|
+
return env.NODE_ENV === "test" || isTruthyEnvFlag(env.CI) || isTruthyEnvFlag(env.BUN_TEST) || isTruthyEnvFlag(env.VITEST) || env.JEST_WORKER_ID !== undefined;
|
|
27681
28366
|
}
|
|
27682
|
-
function shouldAutoOpenBrowser(config,
|
|
28367
|
+
function shouldAutoOpenBrowser(config, env) {
|
|
27683
28368
|
const requested = config?.autoOpenBrowser ?? true;
|
|
27684
|
-
return requested && !isAutomatedRuntime(
|
|
28369
|
+
return requested && !isAutomatedRuntime(env);
|
|
27685
28370
|
}
|
|
27686
28371
|
function openBrowser(url) {
|
|
27687
|
-
const
|
|
28372
|
+
const platform3 = process.platform;
|
|
27688
28373
|
let command;
|
|
27689
28374
|
let args;
|
|
27690
|
-
if (
|
|
28375
|
+
if (platform3 === "darwin") {
|
|
27691
28376
|
command = "open";
|
|
27692
28377
|
args = [url];
|
|
27693
|
-
} else if (
|
|
28378
|
+
} else if (platform3 === "win32") {
|
|
27694
28379
|
command = "cmd";
|
|
27695
28380
|
args = ["/c", "start", "", url];
|
|
27696
28381
|
} else {
|
|
@@ -27763,12 +28448,12 @@ function createInterviewService(ctx, config, deps) {
|
|
|
27763
28448
|
if (!newSlug) {
|
|
27764
28449
|
return;
|
|
27765
28450
|
}
|
|
27766
|
-
const currentFileName =
|
|
28451
|
+
const currentFileName = path15.basename(interview.markdownPath, ".md");
|
|
27767
28452
|
if (currentFileName === newSlug) {
|
|
27768
28453
|
return;
|
|
27769
28454
|
}
|
|
27770
|
-
const dir =
|
|
27771
|
-
const newPath =
|
|
28455
|
+
const dir = path15.dirname(interview.markdownPath);
|
|
28456
|
+
const newPath = path15.join(dir, `${newSlug}.md`);
|
|
27772
28457
|
try {
|
|
27773
28458
|
await fs8.access(newPath);
|
|
27774
28459
|
return;
|
|
@@ -27844,9 +28529,9 @@ function createInterviewService(ctx, config, deps) {
|
|
|
27844
28529
|
const messages = await loadMessages(sessionID);
|
|
27845
28530
|
const title = extractTitle(document);
|
|
27846
28531
|
const record = {
|
|
27847
|
-
id: `${Date.now()}-${++idCounter}-${slugify(
|
|
28532
|
+
id: `${Date.now()}-${++idCounter}-${slugify(path15.basename(markdownPath, ".md")) || "interview"}`,
|
|
27848
28533
|
sessionID,
|
|
27849
|
-
idea: title ||
|
|
28534
|
+
idea: title || path15.basename(markdownPath, ".md"),
|
|
27850
28535
|
markdownPath,
|
|
27851
28536
|
createdAt: nowIso(),
|
|
27852
28537
|
status: "active",
|
|
@@ -27915,11 +28600,11 @@ function createInterviewService(ctx, config, deps) {
|
|
|
27915
28600
|
}
|
|
27916
28601
|
function registerCommand(opencodeConfig) {
|
|
27917
28602
|
const configCommand = opencodeConfig.command;
|
|
27918
|
-
if (!configCommand?.[
|
|
28603
|
+
if (!configCommand?.[COMMAND_NAME3]) {
|
|
27919
28604
|
if (!opencodeConfig.command) {
|
|
27920
28605
|
opencodeConfig.command = {};
|
|
27921
28606
|
}
|
|
27922
|
-
opencodeConfig.command[
|
|
28607
|
+
opencodeConfig.command[COMMAND_NAME3] = {
|
|
27923
28608
|
template: "Start an interview and write a live markdown spec",
|
|
27924
28609
|
description: "Open a localhost interview UI linked to the current OpenCode session"
|
|
27925
28610
|
};
|
|
@@ -27993,7 +28678,7 @@ function createInterviewService(ctx, config, deps) {
|
|
|
27993
28678
|
}
|
|
27994
28679
|
}
|
|
27995
28680
|
async function handleCommandExecuteBefore(input, output) {
|
|
27996
|
-
if (input.command !==
|
|
28681
|
+
if (input.command !== COMMAND_NAME3) {
|
|
27997
28682
|
return;
|
|
27998
28683
|
}
|
|
27999
28684
|
const idea = input.arguments.trim();
|
|
@@ -28073,7 +28758,7 @@ function createInterviewService(ctx, config, deps) {
|
|
|
28073
28758
|
return fileCache.items;
|
|
28074
28759
|
}
|
|
28075
28760
|
const outputDir = createInterviewDirectoryPath(ctx.directory, outputFolder);
|
|
28076
|
-
const activePaths = new Set([...interviewsById.values()].filter((i) => i.status === "active").map((i) =>
|
|
28761
|
+
const activePaths = new Set([...interviewsById.values()].filter((i) => i.status === "active").map((i) => path15.resolve(i.markdownPath)));
|
|
28077
28762
|
let entries;
|
|
28078
28763
|
try {
|
|
28079
28764
|
entries = await fs8.readdir(outputDir);
|
|
@@ -28084,8 +28769,8 @@ function createInterviewService(ctx, config, deps) {
|
|
|
28084
28769
|
for (const entry of entries) {
|
|
28085
28770
|
if (!entry.endsWith(".md"))
|
|
28086
28771
|
continue;
|
|
28087
|
-
const fullPath =
|
|
28088
|
-
if (activePaths.has(
|
|
28772
|
+
const fullPath = path15.join(outputDir, entry);
|
|
28773
|
+
if (activePaths.has(path15.resolve(fullPath)))
|
|
28089
28774
|
continue;
|
|
28090
28775
|
let content;
|
|
28091
28776
|
try {
|
|
@@ -28184,7 +28869,7 @@ function createInterviewManager(ctx, config) {
|
|
|
28184
28869
|
const outputFolder = interviewConfig?.outputFolder ?? "interview";
|
|
28185
28870
|
if (!dashboardEnabled) {
|
|
28186
28871
|
const service2 = createInterviewService(ctx, interviewConfig);
|
|
28187
|
-
const resolvedOutputPath =
|
|
28872
|
+
const resolvedOutputPath = path16.join(ctx.directory, outputFolder);
|
|
28188
28873
|
const server = createInterviewServer({
|
|
28189
28874
|
getState: async (interviewId) => service2.getInterviewState(interviewId),
|
|
28190
28875
|
listInterviewFiles: async () => service2.listInterviewFiles(),
|
|
@@ -28289,7 +28974,7 @@ function createInterviewManager(ctx, config) {
|
|
|
28289
28974
|
listInterviews: () => service.listInterviews(),
|
|
28290
28975
|
submitAnswers: async (interviewId, answers) => service.submitAnswers(interviewId, answers),
|
|
28291
28976
|
handleNudgeAction: async (interviewId, action) => service.handleNudgeAction(interviewId, action),
|
|
28292
|
-
outputFolder:
|
|
28977
|
+
outputFolder: path16.join(ctx.directory, outputFolder),
|
|
28293
28978
|
port: 0
|
|
28294
28979
|
});
|
|
28295
28980
|
service.setBaseUrlResolver(() => perSessionServer.ensureStarted());
|
|
@@ -28557,6 +29242,7 @@ function createBuiltinMcps(disabledMcps = [], websearchConfig) {
|
|
|
28557
29242
|
}
|
|
28558
29243
|
|
|
28559
29244
|
// src/multiplexer/tmux/index.ts
|
|
29245
|
+
init_compat();
|
|
28560
29246
|
var TMUX_LAYOUT_DEBOUNCE_MS = 150;
|
|
28561
29247
|
|
|
28562
29248
|
class TmuxMultiplexer {
|
|
@@ -28774,23 +29460,23 @@ class TmuxMultiplexer {
|
|
|
28774
29460
|
return null;
|
|
28775
29461
|
}
|
|
28776
29462
|
const stdout = await proc.stdout();
|
|
28777
|
-
const
|
|
29463
|
+
const path17 = stdout.trim().split(`
|
|
28778
29464
|
`)[0];
|
|
28779
|
-
if (!
|
|
29465
|
+
if (!path17) {
|
|
28780
29466
|
log("[tmux] findBinary: no path in output");
|
|
28781
29467
|
return null;
|
|
28782
29468
|
}
|
|
28783
|
-
const verifyProc = crossSpawn([
|
|
29469
|
+
const verifyProc = crossSpawn([path17, "-V"], {
|
|
28784
29470
|
stdout: "pipe",
|
|
28785
29471
|
stderr: "pipe"
|
|
28786
29472
|
});
|
|
28787
29473
|
const verifyExit = await verifyProc.exited;
|
|
28788
29474
|
if (verifyExit !== 0) {
|
|
28789
|
-
log("[tmux] findBinary: tmux -V failed", { path:
|
|
29475
|
+
log("[tmux] findBinary: tmux -V failed", { path: path17, verifyExit });
|
|
28790
29476
|
return null;
|
|
28791
29477
|
}
|
|
28792
|
-
log("[tmux] findBinary: found", { path:
|
|
28793
|
-
return
|
|
29478
|
+
log("[tmux] findBinary: found", { path: path17 });
|
|
29479
|
+
return path17;
|
|
28794
29480
|
} catch (err) {
|
|
28795
29481
|
log("[tmux] findBinary: exception", { error: String(err) });
|
|
28796
29482
|
return null;
|
|
@@ -28802,6 +29488,8 @@ function quoteShellArg(value) {
|
|
|
28802
29488
|
}
|
|
28803
29489
|
|
|
28804
29490
|
// src/multiplexer/zellij/index.ts
|
|
29491
|
+
init_compat();
|
|
29492
|
+
|
|
28805
29493
|
class ZellijMultiplexer {
|
|
28806
29494
|
paneMode;
|
|
28807
29495
|
type = "zellij";
|
|
@@ -29741,22 +30429,359 @@ async function isServerRunning(serverUrl, timeoutMs = 3000, maxAttempts = 2) {
|
|
|
29741
30429
|
}
|
|
29742
30430
|
return false;
|
|
29743
30431
|
}
|
|
29744
|
-
// src/tools/
|
|
30432
|
+
// src/tools/acp-run.ts
|
|
30433
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
30434
|
+
import { createInterface } from "node:readline";
|
|
29745
30435
|
import { tool } from "@opencode-ai/plugin";
|
|
30436
|
+
var z4 = tool.schema;
|
|
30437
|
+
|
|
30438
|
+
class AcpClient {
|
|
30439
|
+
name;
|
|
30440
|
+
config;
|
|
30441
|
+
cwd;
|
|
30442
|
+
ask;
|
|
30443
|
+
child;
|
|
30444
|
+
next = 1;
|
|
30445
|
+
pending = new Map;
|
|
30446
|
+
chunks = [];
|
|
30447
|
+
errors = [];
|
|
30448
|
+
sessionId;
|
|
30449
|
+
lastUpdate = Date.now();
|
|
30450
|
+
authMethods = [];
|
|
30451
|
+
active = false;
|
|
30452
|
+
activeRequests = 0;
|
|
30453
|
+
constructor(name, config, cwd, ask) {
|
|
30454
|
+
this.name = name;
|
|
30455
|
+
this.config = config;
|
|
30456
|
+
this.cwd = cwd;
|
|
30457
|
+
this.ask = ask;
|
|
30458
|
+
this.child = spawn3(config.command, config.args, {
|
|
30459
|
+
cwd,
|
|
30460
|
+
env: { ...process.env, ...config.env },
|
|
30461
|
+
stdio: "pipe"
|
|
30462
|
+
});
|
|
30463
|
+
this.child.stderr.on("data", (chunk) => {
|
|
30464
|
+
this.errors.push(String(chunk));
|
|
30465
|
+
});
|
|
30466
|
+
this.child.stdin.on("error", (error) => {
|
|
30467
|
+
this.errors.push(String(error));
|
|
30468
|
+
this.rejectPending(error);
|
|
30469
|
+
});
|
|
30470
|
+
this.child.on("error", (error) => {
|
|
30471
|
+
this.rejectPending(error);
|
|
30472
|
+
});
|
|
30473
|
+
this.child.on("exit", (code, signal) => {
|
|
30474
|
+
if (this.pending.size === 0)
|
|
30475
|
+
return;
|
|
30476
|
+
this.rejectPending(new Error(`ACP agent '${name}' exited before replying (code ${code ?? "null"}, signal ${signal ?? "null"})`));
|
|
30477
|
+
});
|
|
30478
|
+
createInterface({ input: this.child.stdout }).on("line", (line) => {
|
|
30479
|
+
this.receive(line).catch((error) => {
|
|
30480
|
+
this.errors.push(String(error));
|
|
30481
|
+
});
|
|
30482
|
+
});
|
|
30483
|
+
}
|
|
30484
|
+
async run(prompt) {
|
|
30485
|
+
const init = await this.request("initialize", {
|
|
30486
|
+
protocolVersion: 1,
|
|
30487
|
+
clientCapabilities: {},
|
|
30488
|
+
clientInfo: {
|
|
30489
|
+
name: "oh-my-opencode-slim",
|
|
30490
|
+
title: "oh-my-opencode-slim ACP bridge"
|
|
30491
|
+
}
|
|
30492
|
+
});
|
|
30493
|
+
this.authMethods = readAuthMethods(init);
|
|
30494
|
+
const created = await this.newSession();
|
|
30495
|
+
const sessionId = readSessionId(created);
|
|
30496
|
+
this.sessionId = sessionId;
|
|
30497
|
+
this.active = true;
|
|
30498
|
+
await this.request("session/prompt", {
|
|
30499
|
+
sessionId,
|
|
30500
|
+
prompt: [{ type: "text", text: prompt }]
|
|
30501
|
+
});
|
|
30502
|
+
await this.drain();
|
|
30503
|
+
this.active = false;
|
|
30504
|
+
return this.output();
|
|
30505
|
+
}
|
|
30506
|
+
async newSession() {
|
|
30507
|
+
try {
|
|
30508
|
+
return await this.request("session/new", {
|
|
30509
|
+
cwd: this.cwd,
|
|
30510
|
+
mcpServers: []
|
|
30511
|
+
});
|
|
30512
|
+
} catch (error) {
|
|
30513
|
+
if (!isAuthError(error) || this.authMethods.length === 0)
|
|
30514
|
+
throw error;
|
|
30515
|
+
const method = this.authMethods[0];
|
|
30516
|
+
if (typeof method.id !== "string")
|
|
30517
|
+
throw error;
|
|
30518
|
+
await this.request("authenticate", { methodId: method.id });
|
|
30519
|
+
return await this.request("session/new", {
|
|
30520
|
+
cwd: this.cwd,
|
|
30521
|
+
mcpServers: []
|
|
30522
|
+
});
|
|
30523
|
+
}
|
|
30524
|
+
}
|
|
30525
|
+
close() {
|
|
30526
|
+
if (this.active && this.sessionId && !this.child.killed) {
|
|
30527
|
+
this.notify("session/cancel", { sessionId: this.sessionId });
|
|
30528
|
+
}
|
|
30529
|
+
if (!this.child.killed)
|
|
30530
|
+
this.child.kill("SIGTERM");
|
|
30531
|
+
}
|
|
30532
|
+
request(method, params) {
|
|
30533
|
+
const id = this.next++;
|
|
30534
|
+
const payload = { jsonrpc: "2.0", id, method, params };
|
|
30535
|
+
return new Promise((resolve3, reject) => {
|
|
30536
|
+
this.pending.set(id, { resolve: resolve3, reject });
|
|
30537
|
+
this.child.stdin.write(`${JSON.stringify(payload)}
|
|
30538
|
+
`, (error) => {
|
|
30539
|
+
if (!error)
|
|
30540
|
+
return;
|
|
30541
|
+
this.pending.delete(id);
|
|
30542
|
+
reject(error);
|
|
30543
|
+
});
|
|
30544
|
+
});
|
|
30545
|
+
}
|
|
30546
|
+
notify(method, params) {
|
|
30547
|
+
this.child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}
|
|
30548
|
+
`);
|
|
30549
|
+
}
|
|
30550
|
+
async drain() {
|
|
30551
|
+
this.lastUpdate = Date.now();
|
|
30552
|
+
while (this.activeRequests > 0 || Date.now() - this.lastUpdate < 100) {
|
|
30553
|
+
await new Promise((resolve3) => setTimeout(resolve3, 25));
|
|
30554
|
+
}
|
|
30555
|
+
}
|
|
30556
|
+
async receive(line) {
|
|
30557
|
+
if (!line.trim())
|
|
30558
|
+
return;
|
|
30559
|
+
let message;
|
|
30560
|
+
try {
|
|
30561
|
+
message = JSON.parse(line);
|
|
30562
|
+
} catch {
|
|
30563
|
+
const error = new Error(`ACP agent '${this.name}' wrote non-JSON stdout: ${line.slice(0, 200)}`);
|
|
30564
|
+
this.errors.push(error.message);
|
|
30565
|
+
this.rejectPending(error);
|
|
30566
|
+
this.close();
|
|
30567
|
+
return;
|
|
30568
|
+
}
|
|
30569
|
+
if ("id" in message && (("result" in message) || ("error" in message))) {
|
|
30570
|
+
const pending = this.pending.get(message.id);
|
|
30571
|
+
if (!pending)
|
|
30572
|
+
return;
|
|
30573
|
+
this.pending.delete(message.id);
|
|
30574
|
+
if (message.error) {
|
|
30575
|
+
pending.reject(rpcError(message.error));
|
|
30576
|
+
return;
|
|
30577
|
+
}
|
|
30578
|
+
pending.resolve(message.result);
|
|
30579
|
+
return;
|
|
30580
|
+
}
|
|
30581
|
+
if ("id" in message && "method" in message) {
|
|
30582
|
+
this.activeRequests++;
|
|
30583
|
+
try {
|
|
30584
|
+
await this.handleRequest(message);
|
|
30585
|
+
} finally {
|
|
30586
|
+
this.activeRequests--;
|
|
30587
|
+
this.lastUpdate = Date.now();
|
|
30588
|
+
}
|
|
30589
|
+
return;
|
|
30590
|
+
}
|
|
30591
|
+
if ("method" in message)
|
|
30592
|
+
this.handleNotification(message);
|
|
30593
|
+
}
|
|
30594
|
+
rejectPending(error) {
|
|
30595
|
+
for (const item of this.pending.values())
|
|
30596
|
+
item.reject(error);
|
|
30597
|
+
this.pending.clear();
|
|
30598
|
+
}
|
|
30599
|
+
async handleRequest(message) {
|
|
30600
|
+
if (message.method === "session/request_permission") {
|
|
30601
|
+
const title = readPermissionTitle(message.params);
|
|
30602
|
+
try {
|
|
30603
|
+
if (this.config.permissionMode === "ask") {
|
|
30604
|
+
await this.ask(title, message.params ?? {});
|
|
30605
|
+
}
|
|
30606
|
+
const optionId = selectPermissionOption(message.params, this.config.permissionMode);
|
|
30607
|
+
if (!optionId)
|
|
30608
|
+
throw new Error("ACP permission request had no usable option");
|
|
30609
|
+
this.reply(message.id, {
|
|
30610
|
+
outcome: { outcome: "selected", optionId }
|
|
30611
|
+
});
|
|
30612
|
+
} catch {
|
|
30613
|
+
const optionId = selectPermissionOption(message.params, "reject");
|
|
30614
|
+
if (optionId) {
|
|
30615
|
+
this.reply(message.id, {
|
|
30616
|
+
outcome: { outcome: "selected", optionId }
|
|
30617
|
+
});
|
|
30618
|
+
return;
|
|
30619
|
+
}
|
|
30620
|
+
this.reply(message.id, { outcome: { outcome: "cancelled" } });
|
|
30621
|
+
}
|
|
30622
|
+
return;
|
|
30623
|
+
}
|
|
30624
|
+
this.replyError(message.id, `Unsupported ACP client method: ${message.method}`);
|
|
30625
|
+
}
|
|
30626
|
+
handleNotification(message) {
|
|
30627
|
+
if (message.method !== "session/update")
|
|
30628
|
+
return;
|
|
30629
|
+
this.lastUpdate = Date.now();
|
|
30630
|
+
const update = message.params?.update;
|
|
30631
|
+
if (!isRecord2(update))
|
|
30632
|
+
return;
|
|
30633
|
+
collectText(update, this.chunks);
|
|
30634
|
+
}
|
|
30635
|
+
reply(id, result) {
|
|
30636
|
+
this.child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, result })}
|
|
30637
|
+
`);
|
|
30638
|
+
}
|
|
30639
|
+
replyError(id, message) {
|
|
30640
|
+
this.child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, error: { code: -32601, message } })}
|
|
30641
|
+
`);
|
|
30642
|
+
}
|
|
30643
|
+
output() {
|
|
30644
|
+
const text = this.chunks.join("").trim();
|
|
30645
|
+
if (text)
|
|
30646
|
+
return text;
|
|
30647
|
+
const err = this.errors.join("").trim();
|
|
30648
|
+
return err ? `ACP agent '${this.name}' completed without text output. stderr:
|
|
30649
|
+
${err}` : `ACP agent '${this.name}' completed without text output.`;
|
|
30650
|
+
}
|
|
30651
|
+
}
|
|
30652
|
+
function createAcpRunTool(agents = {}) {
|
|
30653
|
+
return tool({
|
|
30654
|
+
description: "Run a configured external ACP-compatible coding agent and return its streamed result. Use for configured ACP agents such as Claude Code ACP, Gemini ACP, or custom ACP servers.",
|
|
30655
|
+
args: {
|
|
30656
|
+
agent: z4.string().describe("Configured ACP agent name"),
|
|
30657
|
+
prompt: z4.string().describe("Task or question to send to the ACP agent"),
|
|
30658
|
+
cwd: z4.string().optional().describe("Optional absolute working directory override"),
|
|
30659
|
+
timeout_ms: z4.number().int().min(1000).max(900000).optional().describe("Optional timeout override in milliseconds")
|
|
30660
|
+
},
|
|
30661
|
+
async execute(args, ctx) {
|
|
30662
|
+
if (ctx.agent !== args.agent) {
|
|
30663
|
+
throw new Error(`acp_run for '${args.agent}' can only be used by @${args.agent}`);
|
|
30664
|
+
}
|
|
30665
|
+
const config = agents[args.agent];
|
|
30666
|
+
if (!config) {
|
|
30667
|
+
throw new Error(`Unknown ACP agent '${args.agent}'. Configured agents: ${Object.keys(agents).join(", ") || "(none)"}`);
|
|
30668
|
+
}
|
|
30669
|
+
const cwd = args.cwd ?? config.cwd ?? ctx.directory;
|
|
30670
|
+
if (!cwd)
|
|
30671
|
+
throw new Error("acp_run requires a working directory");
|
|
30672
|
+
await ctx.ask({
|
|
30673
|
+
permission: "bash",
|
|
30674
|
+
patterns: [`${config.command} ${config.args.join(" ")}`.trim()],
|
|
30675
|
+
always: [],
|
|
30676
|
+
metadata: {
|
|
30677
|
+
agent: args.agent,
|
|
30678
|
+
cwd,
|
|
30679
|
+
command: config.command,
|
|
30680
|
+
args: config.args
|
|
30681
|
+
}
|
|
30682
|
+
});
|
|
30683
|
+
const client = new AcpClient(args.agent, config, cwd, async (title, metadata) => {
|
|
30684
|
+
if (config.permissionMode === "reject")
|
|
30685
|
+
return;
|
|
30686
|
+
await ctx.ask({
|
|
30687
|
+
permission: "bash",
|
|
30688
|
+
patterns: [`acp:${args.agent}:${title}`],
|
|
30689
|
+
always: [],
|
|
30690
|
+
metadata
|
|
30691
|
+
});
|
|
30692
|
+
});
|
|
30693
|
+
const timeoutMs = args.timeout_ms ?? config.timeoutMs;
|
|
30694
|
+
let timer;
|
|
30695
|
+
const timeout = new Promise((_, reject) => timer = setTimeout(() => reject(new Error(`ACP agent '${args.agent}' timed out after ${timeoutMs}ms`)), timeoutMs));
|
|
30696
|
+
const abort = () => client.close();
|
|
30697
|
+
ctx.abort.addEventListener("abort", abort, { once: true });
|
|
30698
|
+
try {
|
|
30699
|
+
return await Promise.race([client.run(args.prompt), timeout]);
|
|
30700
|
+
} finally {
|
|
30701
|
+
if (timer)
|
|
30702
|
+
clearTimeout(timer);
|
|
30703
|
+
ctx.abort.removeEventListener("abort", abort);
|
|
30704
|
+
client.close();
|
|
30705
|
+
}
|
|
30706
|
+
}
|
|
30707
|
+
});
|
|
30708
|
+
}
|
|
30709
|
+
function readSessionId(value) {
|
|
30710
|
+
if (!isRecord2(value) || typeof value.sessionId !== "string") {
|
|
30711
|
+
throw new Error("ACP agent did not return a sessionId");
|
|
30712
|
+
}
|
|
30713
|
+
return value.sessionId;
|
|
30714
|
+
}
|
|
30715
|
+
function readAuthMethods(value) {
|
|
30716
|
+
if (!isRecord2(value) || !Array.isArray(value.authMethods))
|
|
30717
|
+
return [];
|
|
30718
|
+
const methods = value.authMethods;
|
|
30719
|
+
return methods.filter(isRecord2);
|
|
30720
|
+
}
|
|
30721
|
+
function rpcError(error) {
|
|
30722
|
+
const err = new Error(error.message ?? "ACP request failed");
|
|
30723
|
+
err.code = error.code;
|
|
30724
|
+
err.data = error.data;
|
|
30725
|
+
return err;
|
|
30726
|
+
}
|
|
30727
|
+
function isAuthError(error) {
|
|
30728
|
+
if (!(error instanceof Error))
|
|
30729
|
+
return false;
|
|
30730
|
+
const meta = error;
|
|
30731
|
+
return meta.code === -32001 || error.message.toLowerCase().includes("auth_required") || error.message.toLowerCase().includes("auth required");
|
|
30732
|
+
}
|
|
30733
|
+
function isRecord2(value) {
|
|
30734
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
30735
|
+
}
|
|
30736
|
+
function readPermissionTitle(params) {
|
|
30737
|
+
const tool2 = isRecord2(params?.toolCall) ? params.toolCall : undefined;
|
|
30738
|
+
if (typeof tool2?.title === "string")
|
|
30739
|
+
return tool2.title;
|
|
30740
|
+
if (typeof params?.permission === "string")
|
|
30741
|
+
return params.permission;
|
|
30742
|
+
return "ACP permission request";
|
|
30743
|
+
}
|
|
30744
|
+
function selectPermissionOption(params, mode) {
|
|
30745
|
+
const options = Array.isArray(params?.options) ? params.options : [];
|
|
30746
|
+
const choices = options.filter(isRecord2).filter((item) => typeof item.optionId === "string");
|
|
30747
|
+
const reject = choices.find((item) => typeof item.kind === "string" && item.kind.startsWith("reject"));
|
|
30748
|
+
if (mode === "reject")
|
|
30749
|
+
return reject?.optionId;
|
|
30750
|
+
const allow = choices.find((item) => typeof item.kind === "string" && item.kind.startsWith("allow"));
|
|
30751
|
+
return allow?.optionId ?? reject?.optionId;
|
|
30752
|
+
}
|
|
30753
|
+
function collectText(update, chunks) {
|
|
30754
|
+
if (update.sessionUpdate !== "agent_message_chunk")
|
|
30755
|
+
return;
|
|
30756
|
+
const text = readText(update.delta) ?? readText(update.content);
|
|
30757
|
+
if (text)
|
|
30758
|
+
chunks.push(text);
|
|
30759
|
+
}
|
|
30760
|
+
function readText(value) {
|
|
30761
|
+
if (typeof value === "string")
|
|
30762
|
+
return value;
|
|
30763
|
+
if (isRecord2(value) && typeof value.text === "string")
|
|
30764
|
+
return value.text;
|
|
30765
|
+
return;
|
|
30766
|
+
}
|
|
30767
|
+
// src/tools/ast-grep/tools.ts
|
|
30768
|
+
import { tool as tool2 } from "@opencode-ai/plugin";
|
|
29746
30769
|
|
|
29747
30770
|
// src/tools/ast-grep/cli.ts
|
|
29748
|
-
|
|
30771
|
+
init_compat();
|
|
30772
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
29749
30773
|
|
|
29750
30774
|
// src/tools/ast-grep/constants.ts
|
|
29751
|
-
import { existsSync as
|
|
30775
|
+
import { existsSync as existsSync10, statSync as statSync5 } from "node:fs";
|
|
29752
30776
|
import { createRequire as createRequire3 } from "node:module";
|
|
29753
|
-
import { dirname as
|
|
30777
|
+
import { dirname as dirname9, join as join15 } from "node:path";
|
|
29754
30778
|
|
|
29755
30779
|
// src/tools/ast-grep/downloader.ts
|
|
29756
|
-
import { chmodSync, existsSync as
|
|
30780
|
+
import { chmodSync as chmodSync2, existsSync as existsSync9, mkdirSync as mkdirSync7, unlinkSync as unlinkSync4 } from "node:fs";
|
|
29757
30781
|
import { createRequire as createRequire2 } from "node:module";
|
|
29758
|
-
import { homedir as
|
|
29759
|
-
import { join as
|
|
30782
|
+
import { homedir as homedir6 } from "node:os";
|
|
30783
|
+
import { join as join14 } from "node:path";
|
|
30784
|
+
init_compat();
|
|
29760
30785
|
var REPO = "ast-grep/ast-grep";
|
|
29761
30786
|
var DEFAULT_VERSION = "0.40.0";
|
|
29762
30787
|
function getAstGrepVersion() {
|
|
@@ -29780,19 +30805,19 @@ var PLATFORM_MAP = {
|
|
|
29780
30805
|
function getCacheDir2() {
|
|
29781
30806
|
if (process.platform === "win32") {
|
|
29782
30807
|
const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
|
|
29783
|
-
const base2 = localAppData ||
|
|
29784
|
-
return
|
|
30808
|
+
const base2 = localAppData || join14(homedir6(), "AppData", "Local");
|
|
30809
|
+
return join14(base2, "oh-my-opencode-slim", "bin");
|
|
29785
30810
|
}
|
|
29786
30811
|
const xdgCache = process.env.XDG_CACHE_HOME;
|
|
29787
|
-
const base = xdgCache ||
|
|
29788
|
-
return
|
|
30812
|
+
const base = xdgCache || join14(homedir6(), ".cache");
|
|
30813
|
+
return join14(base, "oh-my-opencode-slim", "bin");
|
|
29789
30814
|
}
|
|
29790
30815
|
function getBinaryName() {
|
|
29791
30816
|
return process.platform === "win32" ? "sg.exe" : "sg";
|
|
29792
30817
|
}
|
|
29793
30818
|
function getCachedBinaryPath() {
|
|
29794
|
-
const
|
|
29795
|
-
return
|
|
30819
|
+
const binaryPath = join14(getCacheDir2(), getBinaryName());
|
|
30820
|
+
return existsSync9(binaryPath) ? binaryPath : null;
|
|
29796
30821
|
}
|
|
29797
30822
|
async function downloadAstGrep(version = DEFAULT_VERSION) {
|
|
29798
30823
|
const platformKey = `${process.platform}-${process.arch}`;
|
|
@@ -29803,34 +30828,34 @@ async function downloadAstGrep(version = DEFAULT_VERSION) {
|
|
|
29803
30828
|
}
|
|
29804
30829
|
const cacheDir = getCacheDir2();
|
|
29805
30830
|
const binaryName = getBinaryName();
|
|
29806
|
-
const
|
|
29807
|
-
if (
|
|
29808
|
-
return
|
|
30831
|
+
const binaryPath = join14(cacheDir, binaryName);
|
|
30832
|
+
if (existsSync9(binaryPath)) {
|
|
30833
|
+
return binaryPath;
|
|
29809
30834
|
}
|
|
29810
30835
|
const { arch, os: os5 } = platformInfo;
|
|
29811
30836
|
const assetName = `app-${arch}-${os5}.zip`;
|
|
29812
30837
|
const downloadUrl = `https://github.com/${REPO}/releases/download/${version}/${assetName}`;
|
|
29813
30838
|
console.log(`[oh-my-opencode-slim] Downloading ast-grep binary...`);
|
|
29814
30839
|
try {
|
|
29815
|
-
if (!
|
|
29816
|
-
|
|
30840
|
+
if (!existsSync9(cacheDir)) {
|
|
30841
|
+
mkdirSync7(cacheDir, { recursive: true });
|
|
29817
30842
|
}
|
|
29818
30843
|
const response = await fetch(downloadUrl, { redirect: "follow" });
|
|
29819
30844
|
if (!response.ok) {
|
|
29820
30845
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
29821
30846
|
}
|
|
29822
|
-
const archivePath =
|
|
30847
|
+
const archivePath = join14(cacheDir, assetName);
|
|
29823
30848
|
const arrayBuffer = await response.arrayBuffer();
|
|
29824
30849
|
await crossWrite(archivePath, arrayBuffer);
|
|
29825
30850
|
await extractZip(archivePath, cacheDir);
|
|
29826
|
-
if (
|
|
30851
|
+
if (existsSync9(archivePath)) {
|
|
29827
30852
|
unlinkSync4(archivePath);
|
|
29828
30853
|
}
|
|
29829
|
-
if (process.platform !== "win32" &&
|
|
29830
|
-
|
|
30854
|
+
if (process.platform !== "win32" && existsSync9(binaryPath)) {
|
|
30855
|
+
chmodSync2(binaryPath, 493);
|
|
29831
30856
|
}
|
|
29832
30857
|
console.log(`[oh-my-opencode-slim] ast-grep binary ready.`);
|
|
29833
|
-
return
|
|
30858
|
+
return binaryPath;
|
|
29834
30859
|
} catch (err) {
|
|
29835
30860
|
console.error(`[oh-my-opencode-slim] Failed to download ast-grep: ${err instanceof Error ? err.message : err}`);
|
|
29836
30861
|
return null;
|
|
@@ -29878,13 +30903,13 @@ var CLI_LANGUAGES = [
|
|
|
29878
30903
|
var MIN_BINARY_SIZE = 1e4;
|
|
29879
30904
|
function isValidBinary(filePath) {
|
|
29880
30905
|
try {
|
|
29881
|
-
return
|
|
30906
|
+
return statSync5(filePath).size > MIN_BINARY_SIZE;
|
|
29882
30907
|
} catch {
|
|
29883
30908
|
return false;
|
|
29884
30909
|
}
|
|
29885
30910
|
}
|
|
29886
30911
|
function getPlatformPackageName() {
|
|
29887
|
-
const
|
|
30912
|
+
const platform3 = process.platform;
|
|
29888
30913
|
const arch = process.arch;
|
|
29889
30914
|
const platformMap = {
|
|
29890
30915
|
"darwin-arm64": "@ast-grep/cli-darwin-arm64",
|
|
@@ -29895,7 +30920,7 @@ function getPlatformPackageName() {
|
|
|
29895
30920
|
"win32-arm64": "@ast-grep/cli-win32-arm64-msvc",
|
|
29896
30921
|
"win32-ia32": "@ast-grep/cli-win32-ia32-msvc"
|
|
29897
30922
|
};
|
|
29898
|
-
return platformMap[`${
|
|
30923
|
+
return platformMap[`${platform3}-${arch}`] ?? null;
|
|
29899
30924
|
}
|
|
29900
30925
|
var resolvedCliPath = null;
|
|
29901
30926
|
function findSgCliPathSync() {
|
|
@@ -29907,9 +30932,9 @@ function findSgCliPathSync() {
|
|
|
29907
30932
|
try {
|
|
29908
30933
|
const require2 = createRequire3(import.meta.url);
|
|
29909
30934
|
const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
|
|
29910
|
-
const cliDir =
|
|
29911
|
-
const sgPath =
|
|
29912
|
-
if (
|
|
30935
|
+
const cliDir = dirname9(cliPkgPath);
|
|
30936
|
+
const sgPath = join15(cliDir, binaryName);
|
|
30937
|
+
if (existsSync10(sgPath) && isValidBinary(sgPath)) {
|
|
29913
30938
|
return sgPath;
|
|
29914
30939
|
}
|
|
29915
30940
|
} catch {}
|
|
@@ -29918,19 +30943,19 @@ function findSgCliPathSync() {
|
|
|
29918
30943
|
try {
|
|
29919
30944
|
const require2 = createRequire3(import.meta.url);
|
|
29920
30945
|
const pkgPath = require2.resolve(`${platformPkg}/package.json`);
|
|
29921
|
-
const pkgDir =
|
|
30946
|
+
const pkgDir = dirname9(pkgPath);
|
|
29922
30947
|
const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
|
|
29923
|
-
const
|
|
29924
|
-
if (
|
|
29925
|
-
return
|
|
30948
|
+
const binaryPath = join15(pkgDir, astGrepName);
|
|
30949
|
+
if (existsSync10(binaryPath) && isValidBinary(binaryPath)) {
|
|
30950
|
+
return binaryPath;
|
|
29926
30951
|
}
|
|
29927
30952
|
} catch {}
|
|
29928
30953
|
}
|
|
29929
30954
|
if (process.platform === "darwin") {
|
|
29930
30955
|
const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
|
|
29931
|
-
for (const
|
|
29932
|
-
if (
|
|
29933
|
-
return
|
|
30956
|
+
for (const path17 of homebrewPaths) {
|
|
30957
|
+
if (existsSync10(path17) && isValidBinary(path17)) {
|
|
30958
|
+
return path17;
|
|
29934
30959
|
}
|
|
29935
30960
|
}
|
|
29936
30961
|
}
|
|
@@ -29947,10 +30972,10 @@ function getSgCliPath() {
|
|
|
29947
30972
|
}
|
|
29948
30973
|
return "sg";
|
|
29949
30974
|
}
|
|
29950
|
-
function setSgCliPath(
|
|
29951
|
-
resolvedCliPath =
|
|
30975
|
+
function setSgCliPath(path17) {
|
|
30976
|
+
resolvedCliPath = path17;
|
|
29952
30977
|
}
|
|
29953
|
-
var
|
|
30978
|
+
var DEFAULT_TIMEOUT_MS = 300000;
|
|
29954
30979
|
var DEFAULT_MAX_OUTPUT_BYTES = 1 * 1024 * 1024;
|
|
29955
30980
|
var DEFAULT_MAX_MATCHES = 500;
|
|
29956
30981
|
|
|
@@ -29958,7 +30983,7 @@ var DEFAULT_MAX_MATCHES = 500;
|
|
|
29958
30983
|
var initPromise = null;
|
|
29959
30984
|
async function getAstGrepPath() {
|
|
29960
30985
|
const currentPath = getSgCliPath();
|
|
29961
|
-
if (currentPath !== "sg" &&
|
|
30986
|
+
if (currentPath !== "sg" && existsSync11(currentPath)) {
|
|
29962
30987
|
return currentPath;
|
|
29963
30988
|
}
|
|
29964
30989
|
if (initPromise) {
|
|
@@ -29966,7 +30991,7 @@ async function getAstGrepPath() {
|
|
|
29966
30991
|
}
|
|
29967
30992
|
initPromise = (async () => {
|
|
29968
30993
|
const syncPath = findSgCliPathSync();
|
|
29969
|
-
if (syncPath &&
|
|
30994
|
+
if (syncPath && existsSync11(syncPath)) {
|
|
29970
30995
|
setSgCliPath(syncPath);
|
|
29971
30996
|
return syncPath;
|
|
29972
30997
|
}
|
|
@@ -30005,13 +31030,13 @@ async function runSg(options) {
|
|
|
30005
31030
|
const paths2 = options.paths && options.paths.length > 0 ? options.paths : ["."];
|
|
30006
31031
|
args.push(...paths2);
|
|
30007
31032
|
let cliPath = getSgCliPath();
|
|
30008
|
-
if (!
|
|
31033
|
+
if (!existsSync11(cliPath) && cliPath !== "sg") {
|
|
30009
31034
|
const downloadedPath = await getAstGrepPath();
|
|
30010
31035
|
if (downloadedPath) {
|
|
30011
31036
|
cliPath = downloadedPath;
|
|
30012
31037
|
}
|
|
30013
31038
|
}
|
|
30014
|
-
const timeout =
|
|
31039
|
+
const timeout = DEFAULT_TIMEOUT_MS;
|
|
30015
31040
|
const proc = crossSpawn([cliPath, ...args], {
|
|
30016
31041
|
stdout: "pipe",
|
|
30017
31042
|
stderr: "pipe"
|
|
@@ -30221,14 +31246,14 @@ function showOutputToUser(context, output) {
|
|
|
30221
31246
|
const ctx = context;
|
|
30222
31247
|
ctx.metadata?.({ metadata: { output } });
|
|
30223
31248
|
}
|
|
30224
|
-
var ast_grep_search =
|
|
31249
|
+
var ast_grep_search = tool2({
|
|
30225
31250
|
description: "Search code patterns across filesystem using AST-aware matching. Supports 25 languages. " + "Use meta-variables: $VAR (single node), $$$ (multiple nodes). " + "IMPORTANT: Patterns must be complete AST nodes (valid code). " + "For functions, include params and body: 'export async function $NAME($$$) { $$$ }' not 'export async function $NAME'. " + "Examples: 'console.log($MSG)', 'def $FUNC($$$):', 'async function $NAME($$$)'",
|
|
30226
31251
|
args: {
|
|
30227
|
-
pattern:
|
|
30228
|
-
lang:
|
|
30229
|
-
paths:
|
|
30230
|
-
globs:
|
|
30231
|
-
context:
|
|
31252
|
+
pattern: tool2.schema.string().describe("AST pattern with meta-variables ($VAR, $$$). Must be complete AST node."),
|
|
31253
|
+
lang: tool2.schema.enum(CLI_LANGUAGES).describe("Target language"),
|
|
31254
|
+
paths: tool2.schema.array(tool2.schema.string()).optional().describe("Paths to search (default: ['.'])"),
|
|
31255
|
+
globs: tool2.schema.array(tool2.schema.string()).optional().describe("Include/exclude globs (prefix ! to exclude)"),
|
|
31256
|
+
context: tool2.schema.number().optional().describe("Context lines around match")
|
|
30232
31257
|
},
|
|
30233
31258
|
execute: async (args, context) => {
|
|
30234
31259
|
try {
|
|
@@ -30257,15 +31282,15 @@ ${hint}`;
|
|
|
30257
31282
|
}
|
|
30258
31283
|
}
|
|
30259
31284
|
});
|
|
30260
|
-
var ast_grep_replace =
|
|
31285
|
+
var ast_grep_replace = tool2({
|
|
30261
31286
|
description: "Replace code patterns across filesystem with AST-aware rewriting. " + "Dry-run by default. Use meta-variables in rewrite to preserve matched content. " + "Example: pattern='console.log($MSG)' rewrite='logger.info($MSG)'",
|
|
30262
31287
|
args: {
|
|
30263
|
-
pattern:
|
|
30264
|
-
rewrite:
|
|
30265
|
-
lang:
|
|
30266
|
-
paths:
|
|
30267
|
-
globs:
|
|
30268
|
-
dryRun:
|
|
31288
|
+
pattern: tool2.schema.string().describe("AST pattern to match"),
|
|
31289
|
+
rewrite: tool2.schema.string().describe("Replacement pattern (can use $VAR from pattern)"),
|
|
31290
|
+
lang: tool2.schema.enum(CLI_LANGUAGES).describe("Target language"),
|
|
31291
|
+
paths: tool2.schema.array(tool2.schema.string()).optional().describe("Paths to search"),
|
|
31292
|
+
globs: tool2.schema.array(tool2.schema.string()).optional().describe("Include/exclude globs"),
|
|
31293
|
+
dryRun: tool2.schema.boolean().optional().describe("Preview changes without applying (default: true)")
|
|
30269
31294
|
},
|
|
30270
31295
|
execute: async (args, context) => {
|
|
30271
31296
|
try {
|
|
@@ -30289,20 +31314,20 @@ var ast_grep_replace = tool({
|
|
|
30289
31314
|
});
|
|
30290
31315
|
// src/tools/cancel-task.ts
|
|
30291
31316
|
import {
|
|
30292
|
-
tool as
|
|
31317
|
+
tool as tool3
|
|
30293
31318
|
} from "@opencode-ai/plugin";
|
|
30294
|
-
var
|
|
31319
|
+
var z5 = tool3.schema;
|
|
30295
31320
|
|
|
30296
31321
|
class SessionStillRunningError extends Error {
|
|
30297
31322
|
}
|
|
30298
31323
|
function createCancelTaskTool(options) {
|
|
30299
|
-
const cancel_task =
|
|
31324
|
+
const cancel_task = tool3({
|
|
30300
31325
|
description: `Cancel a tracked background specialist task.
|
|
30301
31326
|
|
|
30302
31327
|
Use only for obsolete, wrong, conflicting, or user-requested cancellation. Accepts either the native task_id/session ID or the parent-scoped alias shown in the Background Job Board. Cancellation is not rollback: if cancelling a writer, inspect and reconcile partial file changes before replacing the lane.`,
|
|
30303
31328
|
args: {
|
|
30304
|
-
task_id:
|
|
30305
|
-
reason:
|
|
31329
|
+
task_id: z5.string().describe("Tracked background task ID or Background Job Board alias"),
|
|
31330
|
+
reason: z5.string().optional().describe("Short cancellation reason")
|
|
30306
31331
|
},
|
|
30307
31332
|
async execute(args, toolContext) {
|
|
30308
31333
|
const parentSessionID = toolContext?.sessionID;
|
|
@@ -30515,7 +31540,7 @@ async function abortAndVerifySession(options, taskID) {
|
|
|
30515
31540
|
attempts,
|
|
30516
31541
|
status: statusSnapshot.status
|
|
30517
31542
|
});
|
|
30518
|
-
await
|
|
31543
|
+
await delay2(retryIntervalMs);
|
|
30519
31544
|
continue;
|
|
30520
31545
|
}
|
|
30521
31546
|
stableStoppedSince ??= Date.now();
|
|
@@ -30528,7 +31553,7 @@ async function abortAndVerifySession(options, taskID) {
|
|
|
30528
31553
|
});
|
|
30529
31554
|
return;
|
|
30530
31555
|
}
|
|
30531
|
-
await
|
|
31556
|
+
await delay2(retryIntervalMs);
|
|
30532
31557
|
}
|
|
30533
31558
|
log("[cancel-task] abort verification timed out", {
|
|
30534
31559
|
taskID,
|
|
@@ -30596,13 +31621,13 @@ async function deleteAndVerifySession(options, taskID, reason) {
|
|
|
30596
31621
|
});
|
|
30597
31622
|
if (status.status === "busy" || status.status === "retry") {
|
|
30598
31623
|
stableStoppedSince = undefined;
|
|
30599
|
-
await
|
|
31624
|
+
await delay2(retryIntervalMs);
|
|
30600
31625
|
continue;
|
|
30601
31626
|
}
|
|
30602
31627
|
stableStoppedSince ??= Date.now();
|
|
30603
31628
|
if (Date.now() - stableStoppedSince >= stableStoppedMs)
|
|
30604
31629
|
return;
|
|
30605
|
-
await
|
|
31630
|
+
await delay2(retryIntervalMs);
|
|
30606
31631
|
}
|
|
30607
31632
|
throw new SessionStillRunningError(`Session delete returned but task did not stay stopped: ${taskID} (${lastStatus ?? "unknown"})`);
|
|
30608
31633
|
}
|
|
@@ -30614,7 +31639,7 @@ async function getSessionStatus(client, taskID) {
|
|
|
30614
31639
|
try {
|
|
30615
31640
|
const result = await client.session.status();
|
|
30616
31641
|
const data = result.data;
|
|
30617
|
-
if (!
|
|
31642
|
+
if (!isRecord(data)) {
|
|
30618
31643
|
return { status: undefined, source: "invalid-data", keys: [] };
|
|
30619
31644
|
}
|
|
30620
31645
|
const keys = Object.keys(data).slice(0, 20);
|
|
@@ -30622,14 +31647,14 @@ async function getSessionStatus(client, taskID) {
|
|
|
30622
31647
|
if (item === undefined) {
|
|
30623
31648
|
return { status: "idle", source: "missing-from-map", keys };
|
|
30624
31649
|
}
|
|
30625
|
-
if (
|
|
31650
|
+
if (isRecord(item) && typeof item.type === "string") {
|
|
30626
31651
|
return { status: item.type, source: "task-map-entry", keys };
|
|
30627
31652
|
}
|
|
30628
31653
|
if (typeof data.type === "string") {
|
|
30629
31654
|
return { status: data.type, source: "legacy-data-type", keys };
|
|
30630
31655
|
}
|
|
30631
31656
|
const nested = data.status;
|
|
30632
|
-
if (
|
|
31657
|
+
if (isRecord(nested) && typeof nested.type === "string") {
|
|
30633
31658
|
return { status: nested.type, source: "legacy-data-status", keys };
|
|
30634
31659
|
}
|
|
30635
31660
|
return { status: undefined, source: "unknown-shape", keys };
|
|
@@ -30641,12 +31666,9 @@ async function getSessionStatus(client, taskID) {
|
|
|
30641
31666
|
return { status: undefined, source: "lookup-error", keys: [] };
|
|
30642
31667
|
}
|
|
30643
31668
|
}
|
|
30644
|
-
function
|
|
31669
|
+
function delay2(ms) {
|
|
30645
31670
|
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
30646
31671
|
}
|
|
30647
|
-
function isObjectRecord2(value) {
|
|
30648
|
-
return typeof value === "object" && value !== null;
|
|
30649
|
-
}
|
|
30650
31672
|
function isSessionID(value) {
|
|
30651
31673
|
return /^ses_[\w-]+$/.test(value);
|
|
30652
31674
|
}
|
|
@@ -30661,7 +31683,7 @@ async function getSessionParentID(client, taskID) {
|
|
|
30661
31683
|
try {
|
|
30662
31684
|
const response = await session2.get({ path: { id: taskID } });
|
|
30663
31685
|
const data = response.data;
|
|
30664
|
-
if (!
|
|
31686
|
+
if (!isRecord(data))
|
|
30665
31687
|
return;
|
|
30666
31688
|
const parentID = data.parentID;
|
|
30667
31689
|
return typeof parentID === "string" ? parentID : undefined;
|
|
@@ -30686,9 +31708,9 @@ function unknownTaskOutput(taskID, message) {
|
|
|
30686
31708
|
}
|
|
30687
31709
|
// src/tools/council.ts
|
|
30688
31710
|
import {
|
|
30689
|
-
tool as
|
|
31711
|
+
tool as tool4
|
|
30690
31712
|
} from "@opencode-ai/plugin";
|
|
30691
|
-
var
|
|
31713
|
+
var z6 = tool4.schema;
|
|
30692
31714
|
function formatModelComposition(councillorResults) {
|
|
30693
31715
|
return councillorResults.map((cr) => {
|
|
30694
31716
|
const shortModel = shortModelLabel(cr.model);
|
|
@@ -30696,15 +31718,15 @@ function formatModelComposition(councillorResults) {
|
|
|
30696
31718
|
}).join(", ");
|
|
30697
31719
|
}
|
|
30698
31720
|
function createCouncilTool(_ctx, councilManager) {
|
|
30699
|
-
const council_session =
|
|
31721
|
+
const council_session = tool4({
|
|
30700
31722
|
description: `Launch a multi-LLM council session for consensus-based analysis.
|
|
30701
31723
|
|
|
30702
31724
|
Sends the prompt to multiple models (councillors) in parallel and returns their formatted responses for you to synthesize.
|
|
30703
31725
|
|
|
30704
31726
|
Returns the councillor responses with a summary footer.`,
|
|
30705
31727
|
args: {
|
|
30706
|
-
prompt:
|
|
30707
|
-
preset:
|
|
31728
|
+
prompt: z6.string().describe("The prompt to send to all councillors"),
|
|
31729
|
+
preset: z6.string().optional().describe('Council preset to use (default: "default"). Must match a preset in the council config.')
|
|
30708
31730
|
},
|
|
30709
31731
|
async execute(args, toolContext) {
|
|
30710
31732
|
if (!toolContext || typeof toolContext !== "object" || !("sessionID" in toolContext)) {
|
|
@@ -30756,14 +31778,14 @@ import * as fs10 from "node:fs";
|
|
|
30756
31778
|
// src/tui-state.ts
|
|
30757
31779
|
import * as fs9 from "node:fs";
|
|
30758
31780
|
import * as os5 from "node:os";
|
|
30759
|
-
import * as
|
|
31781
|
+
import * as path17 from "node:path";
|
|
30760
31782
|
var STATE_DIR = "oh-my-opencode-slim";
|
|
30761
31783
|
var STATE_FILE = "tui-state.json";
|
|
30762
31784
|
function dataDir() {
|
|
30763
|
-
return process.env.XDG_DATA_HOME ??
|
|
31785
|
+
return process.env.XDG_DATA_HOME ?? path17.join(os5.homedir(), ".local", "share");
|
|
30764
31786
|
}
|
|
30765
31787
|
function getTuiStatePath() {
|
|
30766
|
-
return
|
|
31788
|
+
return path17.join(dataDir(), "opencode", "storage", STATE_DIR, STATE_FILE);
|
|
30767
31789
|
}
|
|
30768
31790
|
function emptySnapshot() {
|
|
30769
31791
|
return {
|
|
@@ -30799,7 +31821,7 @@ async function readTuiSnapshotAsync() {
|
|
|
30799
31821
|
function writeTuiSnapshot(snapshot) {
|
|
30800
31822
|
try {
|
|
30801
31823
|
const filePath = getTuiStatePath();
|
|
30802
|
-
fs9.mkdirSync(
|
|
31824
|
+
fs9.mkdirSync(path17.dirname(filePath), { recursive: true });
|
|
30803
31825
|
fs9.writeFileSync(filePath, `${JSON.stringify(snapshot)}
|
|
30804
31826
|
`);
|
|
30805
31827
|
} catch {}
|
|
@@ -30822,11 +31844,11 @@ function recordTuiAgentModel(input) {
|
|
|
30822
31844
|
}
|
|
30823
31845
|
|
|
30824
31846
|
// src/tools/preset-manager.ts
|
|
30825
|
-
var
|
|
31847
|
+
var COMMAND_NAME4 = "preset";
|
|
30826
31848
|
function createPresetManager(ctx, config) {
|
|
30827
31849
|
let activePreset = getActiveRuntimePreset() ?? config.preset ?? null;
|
|
30828
31850
|
async function handleCommandExecuteBefore(input, output) {
|
|
30829
|
-
if (input.command !==
|
|
31851
|
+
if (input.command !== COMMAND_NAME4) {
|
|
30830
31852
|
return;
|
|
30831
31853
|
}
|
|
30832
31854
|
output.parts.length = 0;
|
|
@@ -30845,11 +31867,11 @@ function createPresetManager(ctx, config) {
|
|
|
30845
31867
|
}
|
|
30846
31868
|
function registerCommand(opencodeConfig) {
|
|
30847
31869
|
const configCommand = opencodeConfig.command;
|
|
30848
|
-
if (!configCommand?.[
|
|
31870
|
+
if (!configCommand?.[COMMAND_NAME4]) {
|
|
30849
31871
|
if (!opencodeConfig.command) {
|
|
30850
31872
|
opencodeConfig.command = {};
|
|
30851
31873
|
}
|
|
30852
|
-
opencodeConfig.command[
|
|
31874
|
+
opencodeConfig.command[COMMAND_NAME4] = {
|
|
30853
31875
|
template: "List available presets and switch between them",
|
|
30854
31876
|
description: "Switch agent presets at runtime (e.g., /preset cheap, /preset powerful)"
|
|
30855
31877
|
};
|
|
@@ -30999,14 +32021,14 @@ var BINARY_PREFIXES = [
|
|
|
30999
32021
|
var WEBFETCH_DESCRIPTION = "Fetch a URL with better extraction for static/docs pages. Supports llms.txt probing, content-focused HTML extraction, metadata, redirects, and an optional prompt processed by a cheap secondary model.";
|
|
31000
32022
|
// src/tools/smartfetch/tool.ts
|
|
31001
32023
|
import os6 from "node:os";
|
|
31002
|
-
import
|
|
32024
|
+
import path21 from "node:path";
|
|
31003
32025
|
import {
|
|
31004
|
-
tool as
|
|
32026
|
+
tool as tool5
|
|
31005
32027
|
} from "@opencode-ai/plugin";
|
|
31006
32028
|
|
|
31007
32029
|
// src/tools/smartfetch/binary.ts
|
|
31008
32030
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
|
|
31009
|
-
import
|
|
32031
|
+
import path18 from "node:path";
|
|
31010
32032
|
function extensionForMime(contentType) {
|
|
31011
32033
|
const mime = contentType.split(";")[0]?.trim().toLowerCase();
|
|
31012
32034
|
const map = {
|
|
@@ -31027,10 +32049,10 @@ function buildBinaryResultMessage(fetchResult, savedPath) {
|
|
|
31027
32049
|
async function saveBinary(binaryDir, data, contentType, filename) {
|
|
31028
32050
|
await mkdir2(binaryDir, { recursive: true });
|
|
31029
32051
|
const initialName = filename || `webfetch-${Date.now()}.${extensionForMime(contentType)}`;
|
|
31030
|
-
const parsed =
|
|
32052
|
+
const parsed = path18.parse(initialName);
|
|
31031
32053
|
for (let attempt = 0;attempt < 1000; attempt++) {
|
|
31032
32054
|
const candidateName = attempt === 0 ? initialName : `${parsed.name}-${attempt}${parsed.ext || `.${extensionForMime(contentType)}`}`;
|
|
31033
|
-
const file =
|
|
32055
|
+
const file = path18.join(binaryDir, candidateName);
|
|
31034
32056
|
try {
|
|
31035
32057
|
await writeFile2(file, data, { flag: "wx" });
|
|
31036
32058
|
return file;
|
|
@@ -31169,7 +32191,7 @@ var M = class u2 {
|
|
|
31169
32191
|
return this.#S;
|
|
31170
32192
|
}
|
|
31171
32193
|
constructor(e) {
|
|
31172
|
-
let { max: t = 0, ttl: i, ttlResolution: s = 1, ttlAutopurge: n, updateAgeOnGet: o, updateAgeOnHas: r, allowStale: h, dispose: l, onInsert: c, disposeAfter: f, noDisposeOnSet: g, noUpdateTTL: p, maxSize: T = 0, maxEntrySize: w = 0, sizeCalculation: y, fetchMethod: a, memoMethod: m, noDeleteOnFetchRejection: _, noDeleteOnStaleGet: b, allowStaleOnFetchRejection: d, allowStaleOnFetchAbort: A, ignoreFetchAbort:
|
|
32194
|
+
let { max: t = 0, ttl: i, ttlResolution: s = 1, ttlAutopurge: n, updateAgeOnGet: o, updateAgeOnHas: r, allowStale: h, dispose: l, onInsert: c, disposeAfter: f, noDisposeOnSet: g, noUpdateTTL: p, maxSize: T = 0, maxEntrySize: w = 0, sizeCalculation: y, fetchMethod: a, memoMethod: m, noDeleteOnFetchRejection: _, noDeleteOnStaleGet: b, allowStaleOnFetchRejection: d, allowStaleOnFetchAbort: A, ignoreFetchAbort: z7, perf: x } = e;
|
|
31173
32195
|
if (x !== undefined && typeof x?.now != "function")
|
|
31174
32196
|
throw new TypeError("perf option must have a now() method if specified");
|
|
31175
32197
|
if (this.#m = x ?? C, t !== 0 && !F(t))
|
|
@@ -31187,7 +32209,7 @@ var M = class u2 {
|
|
|
31187
32209
|
throw new TypeError("memoMethod must be a function if defined");
|
|
31188
32210
|
if (this.#U = m, a !== undefined && typeof a != "function")
|
|
31189
32211
|
throw new TypeError("fetchMethod must be a function if specified");
|
|
31190
|
-
if (this.#M = a, this.#W = !!a, this.#s = new Map, this.#i = Array.from({ length: t }).fill(undefined), this.#t = Array.from({ length: t }).fill(undefined), this.#a = new v(t), this.#c = new v(t), this.#l = 0, this.#h = 0, this.#y = R.create(t), this.#n = 0, this.#b = 0, typeof l == "function" && (this.#w = l), typeof c == "function" && (this.#x = c), typeof f == "function" ? (this.#S = f, this.#r = []) : (this.#S = undefined, this.#r = undefined), this.#T = !!this.#w, this.#j = !!this.#x, this.#f = !!this.#S, this.noDisposeOnSet = !!g, this.noUpdateTTL = !!p, this.noDeleteOnFetchRejection = !!_, this.allowStaleOnFetchRejection = !!d, this.allowStaleOnFetchAbort = !!A, this.ignoreFetchAbort = !!
|
|
32212
|
+
if (this.#M = a, this.#W = !!a, this.#s = new Map, this.#i = Array.from({ length: t }).fill(undefined), this.#t = Array.from({ length: t }).fill(undefined), this.#a = new v(t), this.#c = new v(t), this.#l = 0, this.#h = 0, this.#y = R.create(t), this.#n = 0, this.#b = 0, typeof l == "function" && (this.#w = l), typeof c == "function" && (this.#x = c), typeof f == "function" ? (this.#S = f, this.#r = []) : (this.#S = undefined, this.#r = undefined), this.#T = !!this.#w, this.#j = !!this.#x, this.#f = !!this.#S, this.noDisposeOnSet = !!g, this.noUpdateTTL = !!p, this.noDeleteOnFetchRejection = !!_, this.allowStaleOnFetchRejection = !!d, this.allowStaleOnFetchAbort = !!A, this.ignoreFetchAbort = !!z7, this.maxEntrySize !== 0) {
|
|
31191
32213
|
if (this.#u !== 0 && !F(this.#u))
|
|
31192
32214
|
throw new TypeError("maxSize must be a positive integer if specified");
|
|
31193
32215
|
if (!F(this.maxEntrySize))
|
|
@@ -31567,8 +32589,8 @@ var M = class u2 {
|
|
|
31567
32589
|
let A = this.#p(b);
|
|
31568
32590
|
if (!y && !A)
|
|
31569
32591
|
return a && (a.fetch = "hit"), this.#L(b), s && this.#D(b), a && this.#E(a, b), d;
|
|
31570
|
-
let
|
|
31571
|
-
return a && (a.fetch = A ? "stale" : "refresh", v && A && (a.returnedStale = true)), v ?
|
|
32592
|
+
let z7 = this.#P(e, b, _, w), v = z7.__staleWhileFetching !== undefined && i;
|
|
32593
|
+
return a && (a.fetch = A ? "stale" : "refresh", v && A && (a.returnedStale = true)), v ? z7.__staleWhileFetching : z7.__returned = z7;
|
|
31572
32594
|
}
|
|
31573
32595
|
}
|
|
31574
32596
|
forceFetch(e, t = {}) {
|
|
@@ -31684,7 +32706,7 @@ var M = class u2 {
|
|
|
31684
32706
|
};
|
|
31685
32707
|
|
|
31686
32708
|
// src/tools/smartfetch/network.ts
|
|
31687
|
-
import
|
|
32709
|
+
import path19 from "node:path";
|
|
31688
32710
|
|
|
31689
32711
|
// src/tools/smartfetch/utils.ts
|
|
31690
32712
|
var import_readability = __toESM(require_readability(), 1);
|
|
@@ -32409,7 +33431,7 @@ function inferFilenameFromUrl(url) {
|
|
|
32409
33431
|
function truncateFilename(name, maxLength = 180) {
|
|
32410
33432
|
if (name.length <= maxLength)
|
|
32411
33433
|
return name;
|
|
32412
|
-
const parsed =
|
|
33434
|
+
const parsed = path19.parse(name);
|
|
32413
33435
|
const ext = parsed.ext || "";
|
|
32414
33436
|
const baseLimit = Math.max(1, maxLength - ext.length);
|
|
32415
33437
|
return `${parsed.name.slice(0, baseLimit)}${ext}`;
|
|
@@ -32579,9 +33601,9 @@ function isInvalidLlmsResult(fetchResult) {
|
|
|
32579
33601
|
}
|
|
32580
33602
|
|
|
32581
33603
|
// src/tools/smartfetch/secondary-model.ts
|
|
32582
|
-
import { existsSync as
|
|
33604
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
32583
33605
|
import { readFile as readFile4 } from "node:fs/promises";
|
|
32584
|
-
import
|
|
33606
|
+
import path20 from "node:path";
|
|
32585
33607
|
function parseModelRef(value) {
|
|
32586
33608
|
if (!value)
|
|
32587
33609
|
return;
|
|
@@ -32607,8 +33629,8 @@ function pickAgentModelRef(value) {
|
|
|
32607
33629
|
}
|
|
32608
33630
|
function findPreferredOpenCodeConfigPath(baseDir) {
|
|
32609
33631
|
for (const file of ["opencode.jsonc", "opencode.json"]) {
|
|
32610
|
-
const fullPath =
|
|
32611
|
-
if (
|
|
33632
|
+
const fullPath = path20.join(baseDir, file);
|
|
33633
|
+
if (existsSync12(fullPath))
|
|
32612
33634
|
return fullPath;
|
|
32613
33635
|
}
|
|
32614
33636
|
return;
|
|
@@ -32624,7 +33646,7 @@ async function readOpenCodeConfigFile(configPath) {
|
|
|
32624
33646
|
}
|
|
32625
33647
|
}
|
|
32626
33648
|
async function readEffectiveOpenCodeConfig(directory) {
|
|
32627
|
-
const projectDir =
|
|
33649
|
+
const projectDir = path20.join(directory, ".opencode");
|
|
32628
33650
|
const userDirs = getConfigSearchDirs();
|
|
32629
33651
|
const projectPath = findPreferredOpenCodeConfigPath(projectDir);
|
|
32630
33652
|
const userPath = userDirs.map((configDir) => findPreferredOpenCodeConfigPath(configDir)).find(Boolean);
|
|
@@ -32705,6 +33727,29 @@ function isUsableSecondaryText(text) {
|
|
|
32705
33727
|
return false;
|
|
32706
33728
|
return true;
|
|
32707
33729
|
}
|
|
33730
|
+
var SESSION_DELETE_RETRIES = 3;
|
|
33731
|
+
var SESSION_DELETE_RETRY_DELAY_MS = 500;
|
|
33732
|
+
var SECONDARY_MODEL_TIMEOUT_MS = 30000;
|
|
33733
|
+
var _testConfig = {
|
|
33734
|
+
deleteRetryDelayMs: SESSION_DELETE_RETRY_DELAY_MS
|
|
33735
|
+
};
|
|
33736
|
+
async function deleteSessionSafely(client, sessionId, directory) {
|
|
33737
|
+
for (let attempt = 1;attempt <= SESSION_DELETE_RETRIES; attempt++) {
|
|
33738
|
+
try {
|
|
33739
|
+
await client.session.delete({
|
|
33740
|
+
path: { id: sessionId },
|
|
33741
|
+
query: { directory }
|
|
33742
|
+
});
|
|
33743
|
+
return;
|
|
33744
|
+
} catch (error) {
|
|
33745
|
+
if (attempt >= SESSION_DELETE_RETRIES) {
|
|
33746
|
+
console.warn(`[smartfetch] Failed to clean up secondary session ${sessionId} ` + `after ${SESSION_DELETE_RETRIES} attempts: ` + (error instanceof Error ? error.message : String(error)));
|
|
33747
|
+
return;
|
|
33748
|
+
}
|
|
33749
|
+
await new Promise((resolve3) => setTimeout(resolve3, _testConfig.deleteRetryDelayMs));
|
|
33750
|
+
}
|
|
33751
|
+
}
|
|
33752
|
+
}
|
|
32708
33753
|
async function runSecondaryModel(client, directory, model, prompt, content) {
|
|
32709
33754
|
const session2 = await client.session.create({
|
|
32710
33755
|
responseStyle: "data",
|
|
@@ -32731,23 +33776,26 @@ Note: only the first ${inputChars} characters of a longer fetched document were
|
|
|
32731
33776
|
const toolIDsData = toolIDsResponse;
|
|
32732
33777
|
const toolIDs = Array.isArray(toolIDsData.data) ? toolIDsData.data : Array.isArray(toolIDsResponse) ? toolIDsResponse : [];
|
|
32733
33778
|
const disabledTools = Object.fromEntries((toolIDs || []).map((id) => [id, false]));
|
|
32734
|
-
const result = await
|
|
32735
|
-
|
|
32736
|
-
|
|
32737
|
-
|
|
32738
|
-
|
|
32739
|
-
|
|
32740
|
-
|
|
32741
|
-
|
|
32742
|
-
|
|
32743
|
-
|
|
32744
|
-
|
|
32745
|
-
|
|
32746
|
-
|
|
32747
|
-
|
|
32748
|
-
|
|
32749
|
-
|
|
32750
|
-
|
|
33779
|
+
const result = await Promise.race([
|
|
33780
|
+
client.session.prompt({
|
|
33781
|
+
responseStyle: "data",
|
|
33782
|
+
throwOnError: true,
|
|
33783
|
+
path: { id: sessionId },
|
|
33784
|
+
query: { directory },
|
|
33785
|
+
body: {
|
|
33786
|
+
model,
|
|
33787
|
+
system: "Answer only from the supplied content. Do not use tools or outside knowledge.",
|
|
33788
|
+
tools: disabledTools,
|
|
33789
|
+
parts: [
|
|
33790
|
+
{
|
|
33791
|
+
type: "text",
|
|
33792
|
+
text: buildPrompt(truncatedContent, effectivePrompt)
|
|
33793
|
+
}
|
|
33794
|
+
]
|
|
33795
|
+
}
|
|
33796
|
+
}),
|
|
33797
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("Secondary model timed out")), SECONDARY_MODEL_TIMEOUT_MS))
|
|
33798
|
+
]);
|
|
32751
33799
|
const parts = result?.data?.parts ?? result?.parts ?? [];
|
|
32752
33800
|
const text = parts.map((part) => part?.type === "text" ? part.text || "" : "").join("").trim();
|
|
32753
33801
|
return {
|
|
@@ -32757,12 +33805,7 @@ Note: only the first ${inputChars} characters of a longer fetched document were
|
|
|
32757
33805
|
sourceChars
|
|
32758
33806
|
};
|
|
32759
33807
|
} finally {
|
|
32760
|
-
await client
|
|
32761
|
-
path: { id: sessionId },
|
|
32762
|
-
query: { directory }
|
|
32763
|
-
}).catch(() => {
|
|
32764
|
-
return;
|
|
32765
|
-
});
|
|
33808
|
+
await deleteSessionSafely(client, sessionId, directory);
|
|
32766
33809
|
}
|
|
32767
33810
|
}
|
|
32768
33811
|
async function runSecondaryModelWithFallback(client, directory, models, prompt, content) {
|
|
@@ -32783,20 +33826,20 @@ async function runSecondaryModelWithFallback(client, directory, models, prompt,
|
|
|
32783
33826
|
}
|
|
32784
33827
|
|
|
32785
33828
|
// src/tools/smartfetch/tool.ts
|
|
32786
|
-
var
|
|
33829
|
+
var z7 = tool5.schema;
|
|
32787
33830
|
function createWebfetchTool(pluginCtx, options = {}) {
|
|
32788
|
-
const binaryDir = options.binaryDir ||
|
|
32789
|
-
return
|
|
33831
|
+
const binaryDir = options.binaryDir || path21.join(os6.tmpdir(), "opencode-smartfetch");
|
|
33832
|
+
return tool5({
|
|
32790
33833
|
description: WEBFETCH_DESCRIPTION,
|
|
32791
33834
|
args: {
|
|
32792
|
-
url:
|
|
32793
|
-
format:
|
|
32794
|
-
timeout:
|
|
32795
|
-
prompt:
|
|
32796
|
-
extract_main:
|
|
32797
|
-
prefer_llms_txt:
|
|
32798
|
-
include_metadata:
|
|
32799
|
-
save_binary:
|
|
33835
|
+
url: z7.httpUrl(),
|
|
33836
|
+
format: z7.enum(["text", "markdown", "html"]).default("markdown"),
|
|
33837
|
+
timeout: z7.number().positive().max(MAX_TIMEOUT_SECONDS).optional().describe("Timeout in seconds, max 120."),
|
|
33838
|
+
prompt: z7.string().optional().describe("Optional extraction task to run on the fetched content using a cheap secondary model."),
|
|
33839
|
+
extract_main: z7.boolean().default(true),
|
|
33840
|
+
prefer_llms_txt: z7.enum(["auto", "always", "never"]).default("auto"),
|
|
33841
|
+
include_metadata: z7.boolean().default(true),
|
|
33842
|
+
save_binary: z7.boolean().default(false).describe("Save binary payload to disk when it fits within the active download limit.")
|
|
32800
33843
|
},
|
|
32801
33844
|
async execute(args, ctx) {
|
|
32802
33845
|
const secondaryModels = await readSecondaryModelFromConfig(ctx.directory || pluginCtx.directory);
|
|
@@ -33341,7 +34384,7 @@ async function appLog(ctx, level, message) {
|
|
|
33341
34384
|
}
|
|
33342
34385
|
var HEALTH_CHECK = {
|
|
33343
34386
|
minAgents: 5,
|
|
33344
|
-
minTools:
|
|
34387
|
+
minTools: 4,
|
|
33345
34388
|
minMcps: 1
|
|
33346
34389
|
};
|
|
33347
34390
|
async function probeJSDOM() {
|
|
@@ -33378,6 +34421,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33378
34421
|
let jsonErrorRecoveryHook;
|
|
33379
34422
|
let foregroundFallback;
|
|
33380
34423
|
let deepworkCommandHook;
|
|
34424
|
+
let reflectCommandHook;
|
|
33381
34425
|
let taskSessionManagerHook;
|
|
33382
34426
|
let backgroundJobBoard;
|
|
33383
34427
|
let interviewManager;
|
|
@@ -33385,6 +34429,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33385
34429
|
let companionManager;
|
|
33386
34430
|
let councilTools;
|
|
33387
34431
|
let cancelTaskTools;
|
|
34432
|
+
let acpRunTools;
|
|
33388
34433
|
let webfetch;
|
|
33389
34434
|
let rewriteDisplayNameMentions;
|
|
33390
34435
|
let toolCount = 0;
|
|
@@ -33403,32 +34448,13 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33403
34448
|
agentDefs = createAgents(config);
|
|
33404
34449
|
agents = getAgentConfigs(config);
|
|
33405
34450
|
modelArrayMap = {};
|
|
33406
|
-
for (const agentDef of agentDefs) {
|
|
33407
|
-
if (agentDef._modelArray && agentDef._modelArray.length > 0) {
|
|
33408
|
-
modelArrayMap[agentDef.name] = agentDef._modelArray;
|
|
33409
|
-
}
|
|
33410
|
-
}
|
|
33411
34451
|
runtimeChains = {};
|
|
33412
34452
|
for (const agentDef of agentDefs) {
|
|
33413
34453
|
if (agentDef._modelArray?.length) {
|
|
34454
|
+
modelArrayMap[agentDef.name] = agentDef._modelArray;
|
|
33414
34455
|
runtimeChains[agentDef.name] = agentDef._modelArray.map((m) => m.id);
|
|
33415
34456
|
}
|
|
33416
34457
|
}
|
|
33417
|
-
const activePresetForFallback = getActiveRuntimePreset() ?? config.preset ?? null;
|
|
33418
|
-
if (config.fallback?.enabled !== false) {
|
|
33419
|
-
const chains = normalizeFallbackChainsForPreset(config.fallback?.chains ?? {}, activePresetForFallback);
|
|
33420
|
-
for (const [agentName, chainModels] of Object.entries(chains)) {
|
|
33421
|
-
const existing = runtimeChains[agentName] ?? [];
|
|
33422
|
-
const seen = new Set(existing);
|
|
33423
|
-
for (const m of chainModels) {
|
|
33424
|
-
if (!seen.has(m)) {
|
|
33425
|
-
seen.add(m);
|
|
33426
|
-
existing.push(m);
|
|
33427
|
-
}
|
|
33428
|
-
}
|
|
33429
|
-
runtimeChains[agentName] = existing;
|
|
33430
|
-
}
|
|
33431
|
-
}
|
|
33432
34458
|
multiplexerConfig = {
|
|
33433
34459
|
type: config.multiplexer?.type ?? "none",
|
|
33434
34460
|
layout: config.multiplexer?.layout ?? "main-vertical",
|
|
@@ -33448,6 +34474,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33448
34474
|
depthTracker = new SubagentDepthTracker;
|
|
33449
34475
|
councilTools = config.council ? createCouncilTool(ctx, new CouncilManager(ctx, config, depthTracker, multiplexerEnabled)) : {};
|
|
33450
34476
|
mcps = createBuiltinMcps(config.disabled_mcps, config.websearch);
|
|
34477
|
+
acpRunTools = Object.keys(config.acpAgents ?? {}).length > 0 ? { acp_run: createAcpRunTool(config.acpAgents) } : {};
|
|
33451
34478
|
webfetch = createWebfetchTool(ctx);
|
|
33452
34479
|
backgroundJobBoard = new BackgroundJobBoard({
|
|
33453
34480
|
maxReusablePerAgent: config.backgroundJobs?.maxSessionsPerAgent ?? 2,
|
|
@@ -33456,7 +34483,8 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33456
34483
|
});
|
|
33457
34484
|
multiplexerSessionManager = new MultiplexerSessionManager(ctx, multiplexerConfig, backgroundJobBoard);
|
|
33458
34485
|
autoUpdateChecker = createAutoUpdateCheckerHook(ctx, {
|
|
33459
|
-
autoUpdate: config.autoUpdate ?? true
|
|
34486
|
+
autoUpdate: config.autoUpdate ?? true,
|
|
34487
|
+
companion: config.companion
|
|
33460
34488
|
});
|
|
33461
34489
|
phaseReminderHook = createPhaseReminderHook();
|
|
33462
34490
|
filterAvailableSkillsHook = createFilterAvailableSkillsHook(ctx, config);
|
|
@@ -33470,6 +34498,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33470
34498
|
jsonErrorRecoveryHook = createJsonErrorRecoveryHook(ctx);
|
|
33471
34499
|
foregroundFallback = new ForegroundFallbackManager(ctx.client, runtimeChains, config.fallback?.enabled !== false && Object.keys(runtimeChains).length > 0);
|
|
33472
34500
|
deepworkCommandHook = createDeepworkCommandHook();
|
|
34501
|
+
reflectCommandHook = createReflectCommandHook();
|
|
33473
34502
|
taskSessionManagerHook = createTaskSessionManagerHook(ctx, {
|
|
33474
34503
|
maxSessionsPerAgent: config.backgroundJobs?.maxSessionsPerAgent ?? 2,
|
|
33475
34504
|
readContextMinLines: config.backgroundJobs?.readContextMinLines ?? 10,
|
|
@@ -33485,7 +34514,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33485
34514
|
backgroundJobBoard,
|
|
33486
34515
|
shouldManageSession: (sessionID) => sessionAgentMap.get(sessionID) === "orchestrator"
|
|
33487
34516
|
});
|
|
33488
|
-
toolCount = Object.keys(councilTools).length + Object.keys(cancelTaskTools).length + 1 + 2;
|
|
34517
|
+
toolCount = Object.keys(councilTools).length + Object.keys(cancelTaskTools).length + Object.keys(acpRunTools).length + 1 + 2;
|
|
33489
34518
|
} catch (err) {
|
|
33490
34519
|
log("[plugin] FATAL: init failed", String(err));
|
|
33491
34520
|
await appLog(ctx, "error", `INIT FAILED: ${String(err)}. Report at github.com/alvinunreal/oh-my-opencode-slim/issues/310`);
|
|
@@ -33521,6 +34550,22 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33521
34550
|
appLog(ctx, "warn", msg).catch(() => {});
|
|
33522
34551
|
}
|
|
33523
34552
|
});
|
|
34553
|
+
if (config.companion?.enabled === true) {
|
|
34554
|
+
try {
|
|
34555
|
+
const companionResult = await ensureCompanionVersion({
|
|
34556
|
+
config: config.companion,
|
|
34557
|
+
downloadTimeoutMs: 3000,
|
|
34558
|
+
lockTimeoutMs: 500
|
|
34559
|
+
});
|
|
34560
|
+
if (companionResult.status === "installed") {
|
|
34561
|
+
log("[companion] updated before startup", companionResult.version);
|
|
34562
|
+
} else if (companionResult.status === "failed") {
|
|
34563
|
+
log("[companion] startup update failed", companionResult.error);
|
|
34564
|
+
}
|
|
34565
|
+
} catch (err) {
|
|
34566
|
+
log("[companion] startup update failed", String(err));
|
|
34567
|
+
}
|
|
34568
|
+
}
|
|
33524
34569
|
companionManager.onLoad();
|
|
33525
34570
|
return {
|
|
33526
34571
|
name: "oh-my-opencode-slim",
|
|
@@ -33528,6 +34573,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33528
34573
|
tool: {
|
|
33529
34574
|
...councilTools,
|
|
33530
34575
|
...cancelTaskTools,
|
|
34576
|
+
...acpRunTools,
|
|
33531
34577
|
webfetch,
|
|
33532
34578
|
ast_grep_search,
|
|
33533
34579
|
ast_grep_replace
|
|
@@ -33555,34 +34601,11 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33555
34601
|
}
|
|
33556
34602
|
}
|
|
33557
34603
|
const configAgent = opencodeConfig.agent;
|
|
33558
|
-
|
|
33559
|
-
|
|
33560
|
-
|
|
33561
|
-
const effectiveArrays = {};
|
|
33562
|
-
for (const [agentName, models] of Object.entries(modelArrayMap)) {
|
|
33563
|
-
effectiveArrays[agentName] = [...models];
|
|
33564
|
-
}
|
|
33565
|
-
for (const [agentName, chainModels] of Object.entries(fallbackChains)) {
|
|
33566
|
-
if (!chainModels || chainModels.length === 0)
|
|
33567
|
-
continue;
|
|
33568
|
-
if (!effectiveArrays[agentName]) {
|
|
33569
|
-
const entry = configAgent[agentName];
|
|
33570
|
-
const currentModel = typeof entry?.model === "string" ? entry.model : undefined;
|
|
33571
|
-
effectiveArrays[agentName] = currentModel ? [{ id: currentModel }] : [];
|
|
33572
|
-
}
|
|
33573
|
-
const seen = new Set(effectiveArrays[agentName].map((m) => m.id));
|
|
33574
|
-
for (const chainModel of chainModels) {
|
|
33575
|
-
if (!seen.has(chainModel)) {
|
|
33576
|
-
seen.add(chainModel);
|
|
33577
|
-
effectiveArrays[agentName].push({ id: chainModel });
|
|
33578
|
-
}
|
|
33579
|
-
}
|
|
33580
|
-
}
|
|
33581
|
-
if (Object.keys(effectiveArrays).length > 0) {
|
|
33582
|
-
for (const [agentName, modelArray] of Object.entries(effectiveArrays)) {
|
|
33583
|
-
if (modelArray.length === 0)
|
|
34604
|
+
if (Object.keys(modelArrayMap).length > 0) {
|
|
34605
|
+
for (const [agentName, models] of Object.entries(modelArrayMap)) {
|
|
34606
|
+
if (models.length === 0)
|
|
33584
34607
|
continue;
|
|
33585
|
-
const chosen =
|
|
34608
|
+
const chosen = models[0];
|
|
33586
34609
|
const entry = configAgent[agentName];
|
|
33587
34610
|
if (entry) {
|
|
33588
34611
|
entry.model = chosen.id;
|
|
@@ -33718,6 +34741,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33718
34741
|
}
|
|
33719
34742
|
interviewManager.registerCommand(opencodeConfig);
|
|
33720
34743
|
deepworkCommandHook.registerCommand(opencodeConfig);
|
|
34744
|
+
reflectCommandHook.registerCommand(opencodeConfig);
|
|
33721
34745
|
presetManager.registerCommand(opencodeConfig);
|
|
33722
34746
|
},
|
|
33723
34747
|
event: async (input) => {
|
|
@@ -33784,6 +34808,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33784
34808
|
await interviewManager.handleCommandExecuteBefore(input, output);
|
|
33785
34809
|
await presetManager.handleCommandExecuteBefore(input, output);
|
|
33786
34810
|
await deepworkCommandHook.handleCommandExecuteBefore(input, output);
|
|
34811
|
+
await reflectCommandHook.handleCommandExecuteBefore(input, output);
|
|
33787
34812
|
},
|
|
33788
34813
|
"chat.headers": chatHeadersHook["chat.headers"],
|
|
33789
34814
|
"chat.message": async (input, output) => {
|