oh-my-opencode-slim 2.0.2 → 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 +1 -0
- package/README.ko-KR.md +1 -0
- package/README.md +5 -1
- package/README.zh-CN.md +1 -0
- package/dist/cli/companion.d.ts +2 -2
- package/dist/cli/index.js +308 -59
- package/dist/companion/manager.d.ts +1 -0
- package/dist/companion/updater.d.ts +36 -0
- package/dist/config/index.d.ts +1 -1
- package/dist/config/schema.d.ts +76 -0
- package/dist/config/utils.d.ts +1 -0
- package/dist/hooks/auto-update-checker/types.d.ts +2 -0
- package/dist/index.js +1386 -463
- 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 +98 -51
- package/oh-my-opencode-slim.schema.json +101 -0
- package/package.json +2 -1
- package/src/companion/companion-manifest.json +12 -0
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);
|
|
@@ -18417,54 +18569,8 @@ var CouncilConfigSchema = z.object({
|
|
|
18417
18569
|
import * as fs from "node:fs";
|
|
18418
18570
|
import * as path from "node:path";
|
|
18419
18571
|
|
|
18420
|
-
// src/
|
|
18421
|
-
|
|
18422
|
-
import { writeFile as fsWriteFile } from "node:fs/promises";
|
|
18423
|
-
function collectStream(stream) {
|
|
18424
|
-
if (!stream)
|
|
18425
|
-
return () => Promise.resolve("");
|
|
18426
|
-
const chunks = [];
|
|
18427
|
-
stream.on("data", (chunk) => chunks.push(chunk));
|
|
18428
|
-
return () => new Promise((resolve, reject) => {
|
|
18429
|
-
if (!stream.readable) {
|
|
18430
|
-
resolve(Buffer.concat(chunks).toString("utf-8"));
|
|
18431
|
-
return;
|
|
18432
|
-
}
|
|
18433
|
-
stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
|
|
18434
|
-
stream.on("error", reject);
|
|
18435
|
-
});
|
|
18436
|
-
}
|
|
18437
|
-
function crossSpawn(command, options) {
|
|
18438
|
-
const [cmd, ...args] = command;
|
|
18439
|
-
const proc = nodeSpawn(cmd, args, {
|
|
18440
|
-
stdio: [
|
|
18441
|
-
options?.stdin ?? "ignore",
|
|
18442
|
-
options?.stdout ?? "pipe",
|
|
18443
|
-
options?.stderr ?? "pipe"
|
|
18444
|
-
],
|
|
18445
|
-
cwd: options?.cwd,
|
|
18446
|
-
env: options?.env
|
|
18447
|
-
});
|
|
18448
|
-
const stdoutCollector = collectStream(proc.stdout);
|
|
18449
|
-
const stderrCollector = collectStream(proc.stderr);
|
|
18450
|
-
const exited = new Promise((resolve, reject) => {
|
|
18451
|
-
proc.on("error", reject);
|
|
18452
|
-
proc.on("close", (code) => resolve(code ?? 1));
|
|
18453
|
-
});
|
|
18454
|
-
return {
|
|
18455
|
-
proc,
|
|
18456
|
-
stdout: stdoutCollector,
|
|
18457
|
-
stderr: stderrCollector,
|
|
18458
|
-
exited,
|
|
18459
|
-
kill: (signal) => proc.kill(signal),
|
|
18460
|
-
get exitCode() {
|
|
18461
|
-
return proc.exitCode;
|
|
18462
|
-
}
|
|
18463
|
-
};
|
|
18464
|
-
}
|
|
18465
|
-
async function crossWrite(path, data) {
|
|
18466
|
-
await fsWriteFile(path, Buffer.from(data));
|
|
18467
|
-
}
|
|
18572
|
+
// src/cli/config-io.ts
|
|
18573
|
+
init_compat();
|
|
18468
18574
|
|
|
18469
18575
|
// src/config/agent-mcps.ts
|
|
18470
18576
|
var DEFAULT_AGENT_MCPS = {
|
|
@@ -18604,9 +18710,28 @@ var FailoverConfigSchema = z2.object({
|
|
|
18604
18710
|
}).strict();
|
|
18605
18711
|
var CompanionConfigSchema = z2.object({
|
|
18606
18712
|
enabled: z2.boolean().optional(),
|
|
18713
|
+
binaryPath: z2.string().min(1).optional().describe("Path to a custom companion binary to launch."),
|
|
18607
18714
|
position: z2.enum(["bottom-right", "bottom-left", "top-right", "top-left"]).optional(),
|
|
18608
|
-
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.")
|
|
18609
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);
|
|
18610
18735
|
function validateCustomOnlyPromptFields(overrides, ctx, pathPrefix) {
|
|
18611
18736
|
for (const [name, override] of Object.entries(overrides)) {
|
|
18612
18737
|
const isBuiltInOrAlias = ALL_AGENT_NAMES.includes(name) || AGENT_ALIASES[name] !== undefined;
|
|
@@ -18644,7 +18769,8 @@ var PluginConfigSchema = z2.object({
|
|
|
18644
18769
|
backgroundJobs: BackgroundJobsConfigSchema.optional(),
|
|
18645
18770
|
fallback: FailoverConfigSchema.optional(),
|
|
18646
18771
|
council: CouncilConfigSchema.optional(),
|
|
18647
|
-
companion: CompanionConfigSchema.optional()
|
|
18772
|
+
companion: CompanionConfigSchema.optional(),
|
|
18773
|
+
acpAgents: AcpAgentsConfigSchema.optional()
|
|
18648
18774
|
}).superRefine((value, ctx) => {
|
|
18649
18775
|
if (value.agents) {
|
|
18650
18776
|
validateCustomOnlyPromptFields(value.agents, ctx, ["agents"]);
|
|
@@ -18744,6 +18870,7 @@ function mergePluginConfigs(base, override) {
|
|
|
18744
18870
|
backgroundJobs: deepMerge(base.backgroundJobs, override.backgroundJobs),
|
|
18745
18871
|
fallback: deepMerge(base.fallback, override.fallback),
|
|
18746
18872
|
council: deepMerge(base.council, override.council),
|
|
18873
|
+
acpAgents: deepMerge(base.acpAgents, override.acpAgents),
|
|
18747
18874
|
companion: deepMerge(base.companion, override.companion)
|
|
18748
18875
|
};
|
|
18749
18876
|
}
|
|
@@ -18797,8 +18924,13 @@ function loadPluginConfig(directory, options) {
|
|
|
18797
18924
|
if (config.companion) {
|
|
18798
18925
|
config.companion = {
|
|
18799
18926
|
enabled: config.companion.enabled ?? false,
|
|
18927
|
+
binaryPath: config.companion.binaryPath,
|
|
18800
18928
|
position: config.companion.position ?? "bottom-right",
|
|
18801
|
-
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
|
|
18802
18934
|
};
|
|
18803
18935
|
}
|
|
18804
18936
|
return config;
|
|
@@ -18859,6 +18991,9 @@ function getCustomAgentNames(config) {
|
|
|
18859
18991
|
return !ALL_AGENT_NAMES.includes(name);
|
|
18860
18992
|
});
|
|
18861
18993
|
}
|
|
18994
|
+
function getAcpAgentNames(config) {
|
|
18995
|
+
return Object.keys(config?.acpAgents ?? {});
|
|
18996
|
+
}
|
|
18862
18997
|
// src/utils/session.ts
|
|
18863
18998
|
var SESSION_ABORT_TIMEOUT_MS = 1000;
|
|
18864
18999
|
|
|
@@ -19734,6 +19869,39 @@ function normalizeDisplayName(displayName) {
|
|
|
19734
19869
|
const trimmed = displayName.trim();
|
|
19735
19870
|
return trimmed.startsWith("@") ? trimmed.slice(1) : trimmed;
|
|
19736
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
|
+
}
|
|
19737
19905
|
function isSafeDisplayName(displayName) {
|
|
19738
19906
|
return SAFE_AGENT_ALIAS_RE.test(displayName);
|
|
19739
19907
|
}
|
|
@@ -19873,6 +20041,24 @@ function createAgents(config) {
|
|
|
19873
20041
|
buildCustomAgentDefinition(name, override, customPrompts.prompt, customPrompts.appendPrompt)
|
|
19874
20042
|
];
|
|
19875
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
|
+
});
|
|
19876
20062
|
const builtInSubAgents = protoSubAgents.map((agent) => {
|
|
19877
20063
|
const override = getAgentOverride(config, agent.name);
|
|
19878
20064
|
if (override) {
|
|
@@ -19896,7 +20082,15 @@ function createAgents(config) {
|
|
|
19896
20082
|
applyDefaultPermissions(agent, override?.skills);
|
|
19897
20083
|
return agent;
|
|
19898
20084
|
});
|
|
19899
|
-
const
|
|
20085
|
+
const acpSubAgents = protoAcpAgents.map((agent) => {
|
|
20086
|
+
applyDefaultPermissions(agent);
|
|
20087
|
+
return agent;
|
|
20088
|
+
});
|
|
20089
|
+
const allSubAgents = [
|
|
20090
|
+
...builtInSubAgents,
|
|
20091
|
+
...customSubAgents,
|
|
20092
|
+
...acpSubAgents
|
|
20093
|
+
];
|
|
19900
20094
|
const orchestratorOverride = getAgentOverride(config, "orchestrator");
|
|
19901
20095
|
const orchestratorModel = orchestratorOverride?.model ?? DEFAULT_MODELS.orchestrator;
|
|
19902
20096
|
const orchestratorPrompts = loadAgentPrompt("orchestrator", config?.preset);
|
|
@@ -19918,6 +20112,20 @@ function createAgents(config) {
|
|
|
19918
20112
|
const override = getAgentOverride(config, agent.name);
|
|
19919
20113
|
return override?.orchestratorPrompt;
|
|
19920
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
|
+
});
|
|
19921
20129
|
const usedDisplayNames = new Set;
|
|
19922
20130
|
for (const [, displayName] of displayNameMap) {
|
|
19923
20131
|
const normalizedDisplayName = normalizeDisplayName(displayName);
|
|
@@ -19930,13 +20138,17 @@ function createAgents(config) {
|
|
|
19930
20138
|
usedDisplayNames.add(normalizedDisplayName);
|
|
19931
20139
|
}
|
|
19932
20140
|
for (const displayName of usedDisplayNames) {
|
|
19933
|
-
if (ALL_AGENT_NAMES.includes(displayName) || customAgentNames.includes(displayName)) {
|
|
20141
|
+
if (ALL_AGENT_NAMES.includes(displayName) || customAgentNames.includes(displayName) || acpAgentNames.includes(displayName)) {
|
|
19934
20142
|
throw new Error(`displayName '${displayName}' conflicts with an agent name`);
|
|
19935
20143
|
}
|
|
19936
20144
|
}
|
|
19937
20145
|
injectDisplayNames(orchestrator, displayNameMap);
|
|
19938
|
-
|
|
19939
|
-
|
|
20146
|
+
const extraOrchestratorPrompts = [
|
|
20147
|
+
...customOrchestratorPrompts,
|
|
20148
|
+
...acpOrchestratorPrompts
|
|
20149
|
+
];
|
|
20150
|
+
if (extraOrchestratorPrompts.length > 0) {
|
|
20151
|
+
const rewrittenPrompts = extraOrchestratorPrompts.map((promptText) => {
|
|
19940
20152
|
let text = promptText;
|
|
19941
20153
|
for (const [internalName, displayName] of displayNameMap) {
|
|
19942
20154
|
text = text.replace(new RegExp(`@${escapeRegExp(internalName)}\\b`, "g"), `@${normalizeDisplayName(displayName)}`);
|
|
@@ -20008,6 +20220,7 @@ import {
|
|
|
20008
20220
|
mkdirSync as mkdirSync2,
|
|
20009
20221
|
readFileSync as readFileSync2,
|
|
20010
20222
|
renameSync,
|
|
20223
|
+
rmSync,
|
|
20011
20224
|
writeFileSync
|
|
20012
20225
|
} from "node:fs";
|
|
20013
20226
|
import * as os2 from "node:os";
|
|
@@ -20096,11 +20309,15 @@ function stateFilePath() {
|
|
|
20096
20309
|
const base = xdg && path3.isAbsolute(xdg) ? xdg : path3.join(os2.homedir(), ".local", "share");
|
|
20097
20310
|
return path3.join(base, "opencode", "storage", "oh-my-opencode-slim", "companion-state.json");
|
|
20098
20311
|
}
|
|
20099
|
-
function
|
|
20312
|
+
function defaultBinaryPath() {
|
|
20100
20313
|
const xdg = process.env.XDG_DATA_HOME?.trim();
|
|
20101
20314
|
const base = xdg && path3.isAbsolute(xdg) ? xdg : path3.join(os2.homedir(), ".local", "share");
|
|
20102
20315
|
const binaryName = os2.platform() === "win32" ? "oh-my-opencode-slim-companion.exe" : "oh-my-opencode-slim-companion";
|
|
20103
|
-
|
|
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();
|
|
20104
20321
|
return existsSync2(bin) ? bin : null;
|
|
20105
20322
|
}
|
|
20106
20323
|
function readState() {
|
|
@@ -20113,17 +20330,43 @@ function readState() {
|
|
|
20113
20330
|
} catch {}
|
|
20114
20331
|
return { version: 1, sessions: [] };
|
|
20115
20332
|
}
|
|
20116
|
-
function writeState(
|
|
20333
|
+
function writeState(mutator) {
|
|
20117
20334
|
const file = stateFilePath();
|
|
20118
20335
|
try {
|
|
20119
20336
|
mkdirSync2(path3.dirname(file), { recursive: true });
|
|
20120
|
-
const
|
|
20121
|
-
|
|
20122
|
-
|
|
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
|
+
}
|
|
20123
20347
|
} catch (err) {
|
|
20124
20348
|
log("[companion] write failed", String(err));
|
|
20125
20349
|
}
|
|
20126
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
|
+
}
|
|
20127
20370
|
|
|
20128
20371
|
class CompanionManager {
|
|
20129
20372
|
id;
|
|
@@ -20139,12 +20382,11 @@ class CompanionManager {
|
|
|
20139
20382
|
onLoad() {
|
|
20140
20383
|
if (this.config?.enabled !== true) {
|
|
20141
20384
|
try {
|
|
20142
|
-
|
|
20143
|
-
|
|
20144
|
-
|
|
20145
|
-
state.sessions =
|
|
20146
|
-
|
|
20147
|
-
}
|
|
20385
|
+
if (!existsSync2(stateFilePath()))
|
|
20386
|
+
return;
|
|
20387
|
+
writeState((state) => {
|
|
20388
|
+
state.sessions = state.sessions.filter((s) => s.session_id !== this.id);
|
|
20389
|
+
});
|
|
20148
20390
|
} catch {}
|
|
20149
20391
|
return;
|
|
20150
20392
|
}
|
|
@@ -20196,9 +20438,9 @@ class CompanionManager {
|
|
|
20196
20438
|
onExit() {
|
|
20197
20439
|
if (this.config?.enabled !== true)
|
|
20198
20440
|
return;
|
|
20199
|
-
|
|
20200
|
-
|
|
20201
|
-
|
|
20441
|
+
writeState((state) => {
|
|
20442
|
+
state.sessions = state.sessions.filter((s) => s.session_id !== this.id);
|
|
20443
|
+
});
|
|
20202
20444
|
}
|
|
20203
20445
|
activeAgents() {
|
|
20204
20446
|
const agents = Array.from(this.busyAgentSessions.values());
|
|
@@ -20214,28 +20456,41 @@ class CompanionManager {
|
|
|
20214
20456
|
if (this.config?.enabled !== true)
|
|
20215
20457
|
return;
|
|
20216
20458
|
try {
|
|
20217
|
-
const state = readState();
|
|
20218
20459
|
const entry = {
|
|
20219
20460
|
session_id: this.id,
|
|
20220
20461
|
cwd: this.cwd,
|
|
20221
20462
|
active_agents: this.activeAgents(),
|
|
20222
20463
|
status: this.status,
|
|
20223
|
-
pid: process.pid
|
|
20224
|
-
|
|
20225
|
-
const idx = state.sessions.findIndex((s) => s.session_id === this.id);
|
|
20226
|
-
if (idx >= 0) {
|
|
20227
|
-
state.sessions[idx] = entry;
|
|
20228
|
-
} else {
|
|
20229
|
-
state.sessions.push(entry);
|
|
20230
|
-
}
|
|
20231
|
-
if (this.config) {
|
|
20232
|
-
state.config = {
|
|
20464
|
+
pid: process.pid,
|
|
20465
|
+
config: this.config ? {
|
|
20233
20466
|
enabled: this.config.enabled ?? false,
|
|
20234
20467
|
position: this.config.position ?? "bottom-right",
|
|
20235
|
-
size: this.config.size ?? "medium"
|
|
20236
|
-
|
|
20237
|
-
|
|
20238
|
-
|
|
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
|
+
});
|
|
20239
20494
|
} catch (err) {
|
|
20240
20495
|
log("[companion] flush failed", String(err));
|
|
20241
20496
|
}
|
|
@@ -20243,24 +20498,358 @@ class CompanionManager {
|
|
|
20243
20498
|
spawnIfAvailable() {
|
|
20244
20499
|
if (this.config?.enabled !== true)
|
|
20245
20500
|
return;
|
|
20246
|
-
const bin =
|
|
20501
|
+
const bin = resolveCompanionBinaryPath(this.config);
|
|
20247
20502
|
if (!bin) {
|
|
20248
|
-
const
|
|
20249
|
-
const base = xdg && path3.isAbsolute(xdg) ? xdg : path3.join(os2.homedir(), ".local", "share");
|
|
20250
|
-
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();
|
|
20251
20504
|
log(`[companion] enabled but companion binary not found at expected path: ${expected}. Please install/download the companion binary separately.`);
|
|
20252
20505
|
return;
|
|
20253
20506
|
}
|
|
20254
20507
|
try {
|
|
20255
|
-
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
|
+
});
|
|
20256
20517
|
child.unref();
|
|
20257
|
-
log("[companion] spawned",
|
|
20518
|
+
log("[companion] spawned", JSON.stringify({
|
|
20519
|
+
bin,
|
|
20520
|
+
sessionId: this.id,
|
|
20521
|
+
debug: this.config.debug === true
|
|
20522
|
+
}));
|
|
20258
20523
|
} catch (err) {
|
|
20259
20524
|
log("[companion] spawn failed", String(err));
|
|
20260
20525
|
}
|
|
20261
20526
|
}
|
|
20262
20527
|
}
|
|
20263
20528
|
|
|
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 };
|
|
20652
|
+
}
|
|
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
|
+
};
|
|
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
|
+
};
|
|
20674
|
+
}
|
|
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);
|
|
20851
|
+
}
|
|
20852
|
+
|
|
20264
20853
|
// src/config/runtime-preset.ts
|
|
20265
20854
|
var activeRuntimePreset = null;
|
|
20266
20855
|
function setActiveRuntimePreset(name) {
|
|
@@ -20597,7 +21186,7 @@ function ensureApplyPatchError(error, context) {
|
|
|
20597
21186
|
|
|
20598
21187
|
// src/hooks/apply-patch/execution-context.ts
|
|
20599
21188
|
import * as fs3 from "node:fs/promises";
|
|
20600
|
-
import
|
|
21189
|
+
import path5 from "node:path";
|
|
20601
21190
|
|
|
20602
21191
|
// src/hooks/apply-patch/codec.ts
|
|
20603
21192
|
function normalizeLineEndings(text) {
|
|
@@ -21466,7 +22055,7 @@ function isMissingPathError(error) {
|
|
|
21466
22055
|
}
|
|
21467
22056
|
async function real(target) {
|
|
21468
22057
|
const parts = [];
|
|
21469
|
-
let current =
|
|
22058
|
+
let current = path5.resolve(target);
|
|
21470
22059
|
while (true) {
|
|
21471
22060
|
const exact = await fs3.realpath(current).catch((error) => {
|
|
21472
22061
|
if (isMissingPathError(error)) {
|
|
@@ -21475,19 +22064,19 @@ async function real(target) {
|
|
|
21475
22064
|
throw createApplyPatchInternalError(`Failed to resolve real path: ${current}`, error);
|
|
21476
22065
|
});
|
|
21477
22066
|
if (exact) {
|
|
21478
|
-
return parts.length === 0 ? exact :
|
|
22067
|
+
return parts.length === 0 ? exact : path5.join(exact, ...parts.reverse());
|
|
21479
22068
|
}
|
|
21480
|
-
const parent =
|
|
22069
|
+
const parent = path5.dirname(current);
|
|
21481
22070
|
if (parent === current) {
|
|
21482
|
-
return parts.length === 0 ? current :
|
|
22071
|
+
return parts.length === 0 ? current : path5.join(current, ...parts.reverse());
|
|
21483
22072
|
}
|
|
21484
|
-
parts.push(
|
|
22073
|
+
parts.push(path5.basename(current));
|
|
21485
22074
|
current = parent;
|
|
21486
22075
|
}
|
|
21487
22076
|
}
|
|
21488
22077
|
function inside(root, target) {
|
|
21489
|
-
const rel =
|
|
21490
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
22078
|
+
const rel = path5.relative(root, target);
|
|
22079
|
+
return rel === "" || !rel.startsWith("..") && !path5.isAbsolute(rel);
|
|
21491
22080
|
}
|
|
21492
22081
|
function createPathGuardContext(root, worktree) {
|
|
21493
22082
|
return {
|
|
@@ -21497,7 +22086,7 @@ function createPathGuardContext(root, worktree) {
|
|
|
21497
22086
|
};
|
|
21498
22087
|
}
|
|
21499
22088
|
async function realCached(ctx, target) {
|
|
21500
|
-
const resolvedTarget =
|
|
22089
|
+
const resolvedTarget = path5.resolve(target);
|
|
21501
22090
|
let pending = ctx.realCache.get(resolvedTarget);
|
|
21502
22091
|
if (!pending) {
|
|
21503
22092
|
pending = real(resolvedTarget);
|
|
@@ -21548,22 +22137,22 @@ async function assertRegularFile(ctx, filePath, verb) {
|
|
|
21548
22137
|
function collectPatchTargets(root, hunks) {
|
|
21549
22138
|
const targets = new Set;
|
|
21550
22139
|
for (const hunk of hunks) {
|
|
21551
|
-
targets.add(
|
|
22140
|
+
targets.add(path5.resolve(root, hunk.path));
|
|
21552
22141
|
if (hunk.type === "update" && hunk.move_path) {
|
|
21553
|
-
targets.add(
|
|
22142
|
+
targets.add(path5.resolve(root, hunk.move_path));
|
|
21554
22143
|
}
|
|
21555
22144
|
}
|
|
21556
22145
|
return [...targets];
|
|
21557
22146
|
}
|
|
21558
22147
|
function toRelativePatchPath(root, target) {
|
|
21559
|
-
const relative =
|
|
22148
|
+
const relative = path5.relative(root, target);
|
|
21560
22149
|
return (relative.length === 0 ? "." : relative).replaceAll("\\", "/");
|
|
21561
22150
|
}
|
|
21562
22151
|
function normalizePatchPath(root, value) {
|
|
21563
|
-
return
|
|
22152
|
+
return path5.isAbsolute(value) ? toRelativePatchPath(root, path5.resolve(value)) : value;
|
|
21564
22153
|
}
|
|
21565
22154
|
function normalizePatchPaths(root, hunks) {
|
|
21566
|
-
const resolvedRoot =
|
|
22155
|
+
const resolvedRoot = path5.resolve(root);
|
|
21567
22156
|
const normalized = [];
|
|
21568
22157
|
let changed = false;
|
|
21569
22158
|
for (const hunk of hunks) {
|
|
@@ -21687,7 +22276,7 @@ function stageAddedText(contents) {
|
|
|
21687
22276
|
`;
|
|
21688
22277
|
}
|
|
21689
22278
|
// src/hooks/apply-patch/rewrite.ts
|
|
21690
|
-
import
|
|
22279
|
+
import path6 from "node:path";
|
|
21691
22280
|
function normalizeTextLineEndings(text) {
|
|
21692
22281
|
return text.replace(/\r\n/g, `
|
|
21693
22282
|
`).replace(/\r/g, `
|
|
@@ -21844,7 +22433,7 @@ async function rewritePatch(root, patchText, cfg, worktree) {
|
|
|
21844
22433
|
const dependencyGroups = new Map;
|
|
21845
22434
|
for (const hunk of hunks) {
|
|
21846
22435
|
if (hunk.type === "add") {
|
|
21847
|
-
const filePath2 =
|
|
22436
|
+
const filePath2 = path6.resolve(root, hunk.path);
|
|
21848
22437
|
await assertPreparedPathMissing(filePath2, "add");
|
|
21849
22438
|
rewritten.push(hunk);
|
|
21850
22439
|
clearDependencyGroup(filePath2);
|
|
@@ -21866,20 +22455,20 @@ async function rewritePatch(root, patchText, cfg, worktree) {
|
|
|
21866
22455
|
continue;
|
|
21867
22456
|
}
|
|
21868
22457
|
if (hunk.type === "delete") {
|
|
21869
|
-
const filePath2 =
|
|
22458
|
+
const filePath2 = path6.resolve(root, hunk.path);
|
|
21870
22459
|
await getPreparedFileState(filePath2, "delete");
|
|
21871
22460
|
clearDependencyGroup(filePath2);
|
|
21872
22461
|
rewritten.push(hunk);
|
|
21873
22462
|
staged.set(filePath2, { exists: false, derived: true });
|
|
21874
22463
|
continue;
|
|
21875
22464
|
}
|
|
21876
|
-
const filePath =
|
|
22465
|
+
const filePath = path6.resolve(root, hunk.path);
|
|
21877
22466
|
const currentDependency = dependencyGroups.get(filePath);
|
|
21878
22467
|
const current = await getPreparedFileState(filePath, "update");
|
|
21879
22468
|
if (!current.exists) {
|
|
21880
22469
|
throw createApplyPatchVerificationError(`Failed to read file to update: ${filePath}`);
|
|
21881
22470
|
}
|
|
21882
|
-
const movePath = hunk.move_path ?
|
|
22471
|
+
const movePath = hunk.move_path ? path6.resolve(root, hunk.move_path) : undefined;
|
|
21883
22472
|
if (movePath && movePath !== filePath) {
|
|
21884
22473
|
await assertPreparedPathMissing(movePath, "move");
|
|
21885
22474
|
}
|
|
@@ -22045,31 +22634,34 @@ function createApplyPatchHook(ctx) {
|
|
|
22045
22634
|
};
|
|
22046
22635
|
}
|
|
22047
22636
|
// src/hooks/auto-update-checker/index.ts
|
|
22048
|
-
import * as
|
|
22637
|
+
import * as path11 from "node:path";
|
|
22638
|
+
init_compat();
|
|
22049
22639
|
|
|
22050
22640
|
// src/hooks/auto-update-checker/cache.ts
|
|
22051
22641
|
import * as fs5 from "node:fs";
|
|
22052
|
-
import * as
|
|
22642
|
+
import * as path9 from "node:path";
|
|
22643
|
+
// src/cli/system.ts
|
|
22644
|
+
init_compat();
|
|
22053
22645
|
// src/hooks/auto-update-checker/checker.ts
|
|
22054
22646
|
import * as fs4 from "node:fs";
|
|
22055
|
-
import * as
|
|
22647
|
+
import * as path8 from "node:path";
|
|
22056
22648
|
import { fileURLToPath } from "node:url";
|
|
22057
22649
|
|
|
22058
22650
|
// src/hooks/auto-update-checker/constants.ts
|
|
22059
22651
|
import * as os3 from "node:os";
|
|
22060
|
-
import * as
|
|
22652
|
+
import * as path7 from "node:path";
|
|
22061
22653
|
var PACKAGE_NAME = "oh-my-opencode-slim";
|
|
22062
22654
|
var NPM_REGISTRY_URL = `https://registry.npmjs.org/-/package/${PACKAGE_NAME}/dist-tags`;
|
|
22063
22655
|
var NPM_PACKAGE_URL = `https://registry.npmjs.org/${PACKAGE_NAME}`;
|
|
22064
22656
|
var NPM_FETCH_TIMEOUT = 5000;
|
|
22065
22657
|
function getCacheDir() {
|
|
22066
22658
|
if (process.platform === "win32") {
|
|
22067
|
-
return
|
|
22659
|
+
return path7.join(process.env.LOCALAPPDATA ?? os3.homedir(), "opencode");
|
|
22068
22660
|
}
|
|
22069
|
-
return
|
|
22661
|
+
return path7.join(os3.homedir(), ".cache", "opencode");
|
|
22070
22662
|
}
|
|
22071
22663
|
var CACHE_DIR = getCacheDir();
|
|
22072
|
-
var INSTALLED_PACKAGE_JSON =
|
|
22664
|
+
var INSTALLED_PACKAGE_JSON = path7.join(CACHE_DIR, "node_modules", PACKAGE_NAME, "package.json");
|
|
22073
22665
|
var configPaths = getOpenCodeConfigPaths();
|
|
22074
22666
|
var USER_OPENCODE_CONFIG = configPaths[0];
|
|
22075
22667
|
var USER_OPENCODE_CONFIG_JSONC = configPaths[1];
|
|
@@ -22181,8 +22773,8 @@ function extractChannel(version) {
|
|
|
22181
22773
|
}
|
|
22182
22774
|
function getConfigPaths(directory) {
|
|
22183
22775
|
return [
|
|
22184
|
-
|
|
22185
|
-
|
|
22776
|
+
path8.join(directory, ".opencode", "opencode.json"),
|
|
22777
|
+
path8.join(directory, ".opencode", "opencode.jsonc"),
|
|
22186
22778
|
USER_OPENCODE_CONFIG,
|
|
22187
22779
|
USER_OPENCODE_CONFIG_JSONC
|
|
22188
22780
|
];
|
|
@@ -22211,9 +22803,9 @@ function getLocalDevPath(directory) {
|
|
|
22211
22803
|
function findPackageJsonUp(startPath) {
|
|
22212
22804
|
try {
|
|
22213
22805
|
const stat2 = fs4.statSync(startPath);
|
|
22214
|
-
let dir = stat2.isDirectory() ? startPath :
|
|
22806
|
+
let dir = stat2.isDirectory() ? startPath : path8.dirname(startPath);
|
|
22215
22807
|
for (let i = 0;i < 10; i++) {
|
|
22216
|
-
const pkgPath =
|
|
22808
|
+
const pkgPath = path8.join(dir, "package.json");
|
|
22217
22809
|
if (fs4.existsSync(pkgPath)) {
|
|
22218
22810
|
try {
|
|
22219
22811
|
const content = fs4.readFileSync(pkgPath, "utf-8");
|
|
@@ -22222,7 +22814,7 @@ function findPackageJsonUp(startPath) {
|
|
|
22222
22814
|
return pkgPath;
|
|
22223
22815
|
} catch {}
|
|
22224
22816
|
}
|
|
22225
|
-
const parent =
|
|
22817
|
+
const parent = path8.dirname(dir);
|
|
22226
22818
|
if (parent === dir)
|
|
22227
22819
|
break;
|
|
22228
22820
|
dir = parent;
|
|
@@ -22247,7 +22839,7 @@ function getLocalDevVersion(directory) {
|
|
|
22247
22839
|
}
|
|
22248
22840
|
function getCurrentRuntimePackageJsonPath(currentModuleUrl = import.meta.url) {
|
|
22249
22841
|
try {
|
|
22250
|
-
const currentDir =
|
|
22842
|
+
const currentDir = path8.dirname(fileURLToPath(currentModuleUrl));
|
|
22251
22843
|
return findPackageJsonUp(currentDir);
|
|
22252
22844
|
} catch (err) {
|
|
22253
22845
|
log("[auto-update-checker] Failed to resolve runtime package path:", err);
|
|
@@ -22406,7 +22998,7 @@ function getBlockingMajorVersion(current, candidates) {
|
|
|
22406
22998
|
|
|
22407
22999
|
// src/hooks/auto-update-checker/cache.ts
|
|
22408
23000
|
function removeFromBunLock(installDir, packageName) {
|
|
22409
|
-
const lockPath =
|
|
23001
|
+
const lockPath = path9.join(installDir, "bun.lock");
|
|
22410
23002
|
if (!fs5.existsSync(lockPath))
|
|
22411
23003
|
return false;
|
|
22412
23004
|
try {
|
|
@@ -22457,7 +23049,7 @@ function ensureDependencyVersion(packageJsonPath, packageName, version) {
|
|
|
22457
23049
|
}
|
|
22458
23050
|
}
|
|
22459
23051
|
function removeInstalledPackage(installDir, packageName) {
|
|
22460
|
-
const pkgDir =
|
|
23052
|
+
const pkgDir = path9.join(installDir, "node_modules", packageName);
|
|
22461
23053
|
if (!fs5.existsSync(pkgDir))
|
|
22462
23054
|
return false;
|
|
22463
23055
|
fs5.rmSync(pkgDir, { recursive: true, force: true });
|
|
@@ -22466,18 +23058,18 @@ function removeInstalledPackage(installDir, packageName) {
|
|
|
22466
23058
|
}
|
|
22467
23059
|
function resolveInstallContext(runtimePackageJsonPath = getCurrentRuntimePackageJsonPath()) {
|
|
22468
23060
|
if (runtimePackageJsonPath) {
|
|
22469
|
-
const packageDir =
|
|
22470
|
-
const nodeModulesDir =
|
|
22471
|
-
if (
|
|
22472
|
-
const installDir =
|
|
22473
|
-
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");
|
|
22474
23066
|
if (fs5.existsSync(packageJsonPath)) {
|
|
22475
23067
|
return { installDir, packageJsonPath };
|
|
22476
23068
|
}
|
|
22477
23069
|
}
|
|
22478
23070
|
return null;
|
|
22479
23071
|
}
|
|
22480
|
-
const legacyPackageJsonPath =
|
|
23072
|
+
const legacyPackageJsonPath = path9.join(CACHE_DIR, "package.json");
|
|
22481
23073
|
if (fs5.existsSync(legacyPackageJsonPath)) {
|
|
22482
23074
|
return { installDir: CACHE_DIR, packageJsonPath: legacyPackageJsonPath };
|
|
22483
23075
|
}
|
|
@@ -22508,40 +23100,40 @@ function preparePackageUpdate(version, packageName = PACKAGE_NAME, runtimePackag
|
|
|
22508
23100
|
|
|
22509
23101
|
// src/hooks/auto-update-checker/skill-sync.ts
|
|
22510
23102
|
import {
|
|
22511
|
-
copyFileSync,
|
|
22512
|
-
existsSync as
|
|
23103
|
+
copyFileSync as copyFileSync2,
|
|
23104
|
+
existsSync as existsSync6,
|
|
22513
23105
|
lstatSync,
|
|
22514
|
-
mkdirSync as
|
|
22515
|
-
mkdtempSync,
|
|
23106
|
+
mkdirSync as mkdirSync4,
|
|
23107
|
+
mkdtempSync as mkdtempSync2,
|
|
22516
23108
|
readdirSync as readdirSync2,
|
|
22517
|
-
renameSync as
|
|
22518
|
-
rmSync as
|
|
23109
|
+
renameSync as renameSync3,
|
|
23110
|
+
rmSync as rmSync4
|
|
22519
23111
|
} from "node:fs";
|
|
22520
|
-
import * as
|
|
23112
|
+
import * as path10 from "node:path";
|
|
22521
23113
|
function copyDirRecursive(src, dest) {
|
|
22522
23114
|
const stat2 = lstatSync(src);
|
|
22523
23115
|
if (stat2.isSymbolicLink()) {
|
|
22524
23116
|
return;
|
|
22525
23117
|
}
|
|
22526
23118
|
if (stat2.isDirectory()) {
|
|
22527
|
-
|
|
23119
|
+
mkdirSync4(dest, { recursive: true });
|
|
22528
23120
|
const entries = readdirSync2(src);
|
|
22529
23121
|
for (const entry of entries) {
|
|
22530
|
-
copyDirRecursive(
|
|
23122
|
+
copyDirRecursive(path10.join(src, entry), path10.join(dest, entry));
|
|
22531
23123
|
}
|
|
22532
23124
|
} else if (stat2.isFile()) {
|
|
22533
|
-
const destDir =
|
|
22534
|
-
if (!
|
|
22535
|
-
|
|
23125
|
+
const destDir = path10.dirname(dest);
|
|
23126
|
+
if (!existsSync6(destDir)) {
|
|
23127
|
+
mkdirSync4(destDir, { recursive: true });
|
|
22536
23128
|
}
|
|
22537
|
-
|
|
23129
|
+
copyFileSync2(src, dest);
|
|
22538
23130
|
}
|
|
22539
23131
|
}
|
|
22540
23132
|
function syncBundledSkillsFromPackage(packageRoot) {
|
|
22541
23133
|
const installed = [];
|
|
22542
23134
|
const skippedExisting = [];
|
|
22543
23135
|
const failed = [];
|
|
22544
|
-
const sourceSkillsDir =
|
|
23136
|
+
const sourceSkillsDir = path10.join(packageRoot, "src", "skills");
|
|
22545
23137
|
try {
|
|
22546
23138
|
const stat2 = lstatSync(sourceSkillsDir);
|
|
22547
23139
|
if (stat2.isSymbolicLink() || !stat2.isDirectory()) {
|
|
@@ -22552,10 +23144,10 @@ function syncBundledSkillsFromPackage(packageRoot) {
|
|
|
22552
23144
|
log(`[skill-sync] Source skills directory does not exist or is unreadable: ${sourceSkillsDir}`);
|
|
22553
23145
|
return { installed, skippedExisting, failed };
|
|
22554
23146
|
}
|
|
22555
|
-
const destSkillsDir =
|
|
23147
|
+
const destSkillsDir = path10.join(getConfigDir(), "skills");
|
|
22556
23148
|
try {
|
|
22557
|
-
if (!
|
|
22558
|
-
|
|
23149
|
+
if (!existsSync6(destSkillsDir)) {
|
|
23150
|
+
mkdirSync4(destSkillsDir, { recursive: true });
|
|
22559
23151
|
}
|
|
22560
23152
|
} catch (err) {
|
|
22561
23153
|
log(`[skill-sync] Failed to create destination skills directory: ${destSkillsDir}`, err);
|
|
@@ -22568,7 +23160,7 @@ function syncBundledSkillsFromPackage(packageRoot) {
|
|
|
22568
23160
|
return { installed, skippedExisting, failed };
|
|
22569
23161
|
}
|
|
22570
23162
|
for (const entry of entries) {
|
|
22571
|
-
const entryPath =
|
|
23163
|
+
const entryPath = path10.join(sourceSkillsDir, entry);
|
|
22572
23164
|
try {
|
|
22573
23165
|
if (entry.startsWith(".")) {
|
|
22574
23166
|
continue;
|
|
@@ -22577,7 +23169,7 @@ function syncBundledSkillsFromPackage(packageRoot) {
|
|
|
22577
23169
|
if (entryStat.isSymbolicLink() || !entryStat.isDirectory()) {
|
|
22578
23170
|
continue;
|
|
22579
23171
|
}
|
|
22580
|
-
const skillMdPath =
|
|
23172
|
+
const skillMdPath = path10.join(entryPath, "SKILL.md");
|
|
22581
23173
|
try {
|
|
22582
23174
|
const skillMdStat = lstatSync(skillMdPath);
|
|
22583
23175
|
if (skillMdStat.isSymbolicLink() || !skillMdStat.isFile()) {
|
|
@@ -22586,7 +23178,7 @@ function syncBundledSkillsFromPackage(packageRoot) {
|
|
|
22586
23178
|
} catch {
|
|
22587
23179
|
continue;
|
|
22588
23180
|
}
|
|
22589
|
-
const destPath =
|
|
23181
|
+
const destPath = path10.join(destSkillsDir, entry);
|
|
22590
23182
|
let destExists = false;
|
|
22591
23183
|
try {
|
|
22592
23184
|
lstatSync(destPath);
|
|
@@ -22597,7 +23189,7 @@ function syncBundledSkillsFromPackage(packageRoot) {
|
|
|
22597
23189
|
skippedExisting.push(entry);
|
|
22598
23190
|
continue;
|
|
22599
23191
|
}
|
|
22600
|
-
const stagingDir =
|
|
23192
|
+
const stagingDir = mkdtempSync2(path10.join(destSkillsDir, `.sync-staging-${entry}-`));
|
|
22601
23193
|
try {
|
|
22602
23194
|
copyDirRecursive(entryPath, stagingDir);
|
|
22603
23195
|
let destExistsLate = false;
|
|
@@ -22609,7 +23201,7 @@ function syncBundledSkillsFromPackage(packageRoot) {
|
|
|
22609
23201
|
log(`[skill-sync] Destination path was created during staging for ${entry}, skipping promotion.`);
|
|
22610
23202
|
skippedExisting.push(entry);
|
|
22611
23203
|
} else {
|
|
22612
|
-
|
|
23204
|
+
renameSync3(stagingDir, destPath);
|
|
22613
23205
|
installed.push(entry);
|
|
22614
23206
|
log(`[skill-sync] Successfully synced skill: ${entry}`);
|
|
22615
23207
|
}
|
|
@@ -22618,8 +23210,8 @@ function syncBundledSkillsFromPackage(packageRoot) {
|
|
|
22618
23210
|
failed.push(entry);
|
|
22619
23211
|
} finally {
|
|
22620
23212
|
try {
|
|
22621
|
-
if (
|
|
22622
|
-
|
|
23213
|
+
if (existsSync6(stagingDir)) {
|
|
23214
|
+
rmSync4(stagingDir, { recursive: true, force: true });
|
|
22623
23215
|
}
|
|
22624
23216
|
} catch (err) {
|
|
22625
23217
|
log(`[skill-sync] Failed to clean up staging directory ${stagingDir}:`, err);
|
|
@@ -22635,7 +23227,7 @@ function syncBundledSkillsFromPackage(packageRoot) {
|
|
|
22635
23227
|
|
|
22636
23228
|
// src/hooks/auto-update-checker/index.ts
|
|
22637
23229
|
function createAutoUpdateCheckerHook(ctx, options = {}) {
|
|
22638
|
-
const { autoUpdate = true } = options;
|
|
23230
|
+
const { autoUpdate = true, companion } = options;
|
|
22639
23231
|
let hasChecked = false;
|
|
22640
23232
|
return {
|
|
22641
23233
|
event: ({ event }) => {
|
|
@@ -22653,14 +23245,14 @@ function createAutoUpdateCheckerHook(ctx, options = {}) {
|
|
|
22653
23245
|
log("[auto-update-checker] Local development mode");
|
|
22654
23246
|
return;
|
|
22655
23247
|
}
|
|
22656
|
-
runBackgroundUpdateCheck(ctx, autoUpdate).catch((err) => {
|
|
23248
|
+
runBackgroundUpdateCheck(ctx, autoUpdate, companion).catch((err) => {
|
|
22657
23249
|
log("[auto-update-checker] Background update check failed:", err);
|
|
22658
23250
|
});
|
|
22659
23251
|
}, 0);
|
|
22660
23252
|
}
|
|
22661
23253
|
};
|
|
22662
23254
|
}
|
|
22663
|
-
async function runBackgroundUpdateCheck(ctx, autoUpdate) {
|
|
23255
|
+
async function runBackgroundUpdateCheck(ctx, autoUpdate, companion) {
|
|
22664
23256
|
const pluginInfo = findPluginEntry(ctx.directory);
|
|
22665
23257
|
if (!pluginInfo) {
|
|
22666
23258
|
log("[auto-update-checker] Plugin not found in config");
|
|
@@ -22716,8 +23308,10 @@ Version is pinned. Update your plugin config to apply.`, "info", 8000);
|
|
|
22716
23308
|
const installSuccess = await runBunInstallSafe(installDir);
|
|
22717
23309
|
if (installSuccess) {
|
|
22718
23310
|
let installedSkills = [];
|
|
23311
|
+
let companionUpdated = false;
|
|
23312
|
+
let companionWillRetry = false;
|
|
23313
|
+
const packageRoot = path11.join(installDir, "node_modules", PACKAGE_NAME);
|
|
22719
23314
|
try {
|
|
22720
|
-
const packageRoot = path10.join(installDir, "node_modules", PACKAGE_NAME);
|
|
22721
23315
|
const syncResult = syncBundledSkillsFromPackage(packageRoot);
|
|
22722
23316
|
installedSkills = syncResult.installed;
|
|
22723
23317
|
if (syncResult.failed.length > 0) {
|
|
@@ -22729,14 +23323,38 @@ Version is pinned. Update your plugin config to apply.`, "info", 8000);
|
|
|
22729
23323
|
} catch (err) {
|
|
22730
23324
|
log("[auto-update-checker] Skill sync failed silently:", err);
|
|
22731
23325
|
}
|
|
22732
|
-
|
|
22733
|
-
|
|
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}`];
|
|
22734
23347
|
if (installedSkills.length > 0) {
|
|
22735
|
-
|
|
22736
|
-
|
|
22737
|
-
|
|
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.");
|
|
22738
23354
|
}
|
|
22739
|
-
|
|
23355
|
+
messageLines.push("Restart OpenCode to apply.");
|
|
23356
|
+
showToast(ctx, "OMO-Slim Updated!", messageLines.join(`
|
|
23357
|
+
`), "success", 8000);
|
|
22740
23358
|
log(`[auto-update-checker] Update installed: ${currentVersion} → ${latestVersion}`);
|
|
22741
23359
|
} else {
|
|
22742
23360
|
showToast(ctx, `OMO-Slim ${latestVersion}`, `v${latestVersion} available, but auto-update failed to install it. Check logs or retry manually.`, "error", 8000);
|
|
@@ -23119,7 +23737,7 @@ class BackgroundJobBoard {
|
|
|
23119
23737
|
existing.set(file.path, { ...file });
|
|
23120
23738
|
}
|
|
23121
23739
|
}
|
|
23122
|
-
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);
|
|
23123
23741
|
this.jobs.set(taskID, { ...job, contextFiles });
|
|
23124
23742
|
}
|
|
23125
23743
|
list(parentSessionID) {
|
|
@@ -23264,86 +23882,10 @@ function hasInternalInitiatorMarker(part) {
|
|
|
23264
23882
|
}
|
|
23265
23883
|
return part.text.includes(SLIM_INTERNAL_INITIATOR_MARKER);
|
|
23266
23884
|
}
|
|
23267
|
-
|
|
23268
|
-
|
|
23269
|
-
|
|
23270
|
-
|
|
23271
|
-
function getWindowsBuildNumber() {
|
|
23272
|
-
if (process.platform !== "win32")
|
|
23273
|
-
return null;
|
|
23274
|
-
const parts = release().split(".");
|
|
23275
|
-
if (parts.length >= 3) {
|
|
23276
|
-
const build = parseInt(parts[2], 10);
|
|
23277
|
-
if (!Number.isNaN(build))
|
|
23278
|
-
return build;
|
|
23279
|
-
}
|
|
23280
|
-
return null;
|
|
23281
|
-
}
|
|
23282
|
-
function isPwshAvailable() {
|
|
23283
|
-
if (process.platform !== "win32")
|
|
23284
|
-
return false;
|
|
23285
|
-
const result = spawnSync("where", ["pwsh"], {
|
|
23286
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
23287
|
-
});
|
|
23288
|
-
return result.status === 0;
|
|
23289
|
-
}
|
|
23290
|
-
function escapePowerShellPath(path11) {
|
|
23291
|
-
return path11.replace(/'/g, "''");
|
|
23292
|
-
}
|
|
23293
|
-
function getWindowsZipExtractor() {
|
|
23294
|
-
const buildNumber = getWindowsBuildNumber();
|
|
23295
|
-
if (buildNumber !== null && buildNumber >= WINDOWS_BUILD_WITH_TAR) {
|
|
23296
|
-
return "tar";
|
|
23297
|
-
}
|
|
23298
|
-
if (isPwshAvailable()) {
|
|
23299
|
-
return "pwsh";
|
|
23300
|
-
}
|
|
23301
|
-
return "powershell";
|
|
23302
|
-
}
|
|
23303
|
-
async function extractZip(archivePath, destDir) {
|
|
23304
|
-
let proc;
|
|
23305
|
-
if (process.platform === "win32") {
|
|
23306
|
-
const extractor = getWindowsZipExtractor();
|
|
23307
|
-
switch (extractor) {
|
|
23308
|
-
case "tar":
|
|
23309
|
-
proc = crossSpawn(["tar", "-xf", archivePath, "-C", destDir], {
|
|
23310
|
-
stdout: "ignore",
|
|
23311
|
-
stderr: "pipe"
|
|
23312
|
-
});
|
|
23313
|
-
break;
|
|
23314
|
-
case "pwsh":
|
|
23315
|
-
proc = crossSpawn([
|
|
23316
|
-
"pwsh",
|
|
23317
|
-
"-Command",
|
|
23318
|
-
`Expand-Archive -Path '${escapePowerShellPath(archivePath)}' -DestinationPath '${escapePowerShellPath(destDir)}' -Force`
|
|
23319
|
-
], {
|
|
23320
|
-
stdout: "ignore",
|
|
23321
|
-
stderr: "pipe"
|
|
23322
|
-
});
|
|
23323
|
-
break;
|
|
23324
|
-
default:
|
|
23325
|
-
proc = crossSpawn([
|
|
23326
|
-
"powershell",
|
|
23327
|
-
"-Command",
|
|
23328
|
-
`Expand-Archive -Path '${escapePowerShellPath(archivePath)}' -DestinationPath '${escapePowerShellPath(destDir)}' -Force`
|
|
23329
|
-
], {
|
|
23330
|
-
stdout: "ignore",
|
|
23331
|
-
stderr: "pipe"
|
|
23332
|
-
});
|
|
23333
|
-
break;
|
|
23334
|
-
}
|
|
23335
|
-
} else {
|
|
23336
|
-
proc = crossSpawn(["unzip", "-o", archivePath, "-d", destDir], {
|
|
23337
|
-
stdout: "ignore",
|
|
23338
|
-
stderr: "pipe"
|
|
23339
|
-
});
|
|
23340
|
-
}
|
|
23341
|
-
const exitCode = await proc.exited;
|
|
23342
|
-
if (exitCode !== 0) {
|
|
23343
|
-
const stderr = await proc.stderr();
|
|
23344
|
-
throw new Error(`zip extraction failed (exit ${exitCode}): ${stderr}`);
|
|
23345
|
-
}
|
|
23346
|
-
}
|
|
23885
|
+
|
|
23886
|
+
// src/utils/index.ts
|
|
23887
|
+
init_zip_extractor();
|
|
23888
|
+
|
|
23347
23889
|
// src/hooks/chat-headers.ts
|
|
23348
23890
|
var INTERNAL_MARKER_CACHE_LIMIT = 1000;
|
|
23349
23891
|
var internalMarkerCache = new Map;
|
|
@@ -23848,17 +24390,17 @@ class ForegroundFallbackManager {
|
|
|
23848
24390
|
}
|
|
23849
24391
|
}
|
|
23850
24392
|
// src/hooks/image-hook.ts
|
|
23851
|
-
import { createHash } from "node:crypto";
|
|
24393
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
23852
24394
|
import {
|
|
23853
|
-
existsSync as
|
|
23854
|
-
mkdirSync as
|
|
24395
|
+
existsSync as existsSync7,
|
|
24396
|
+
mkdirSync as mkdirSync5,
|
|
23855
24397
|
readdirSync as readdirSync3,
|
|
23856
24398
|
rmdirSync,
|
|
23857
|
-
statSync as
|
|
24399
|
+
statSync as statSync4,
|
|
23858
24400
|
unlinkSync as unlinkSync2,
|
|
23859
|
-
writeFileSync as
|
|
24401
|
+
writeFileSync as writeFileSync5
|
|
23860
24402
|
} from "node:fs";
|
|
23861
|
-
import { basename as basename2, extname, join as
|
|
24403
|
+
import { basename as basename2, extname, join as join11 } from "node:path";
|
|
23862
24404
|
var lastCleanupByDir = new Map;
|
|
23863
24405
|
var CLEANUP_INTERVAL = 10 * 60 * 1000;
|
|
23864
24406
|
function isImagePart(p) {
|
|
@@ -23906,12 +24448,12 @@ function cleanupAllSessions(saveDir) {
|
|
|
23906
24448
|
const dirsToScan = [];
|
|
23907
24449
|
try {
|
|
23908
24450
|
for (const entry of readdirSync3(saveDir, { withFileTypes: true })) {
|
|
23909
|
-
const fp =
|
|
24451
|
+
const fp = join11(saveDir, entry.name);
|
|
23910
24452
|
if (entry.isDirectory()) {
|
|
23911
24453
|
dirsToScan.push(fp);
|
|
23912
24454
|
} else {
|
|
23913
24455
|
try {
|
|
23914
|
-
if (now -
|
|
24456
|
+
if (now - statSync4(fp).mtimeMs > maxAge)
|
|
23915
24457
|
unlinkSync2(fp);
|
|
23916
24458
|
} catch {}
|
|
23917
24459
|
}
|
|
@@ -23923,9 +24465,9 @@ function cleanupAllSessions(saveDir) {
|
|
|
23923
24465
|
let allRemoved = true;
|
|
23924
24466
|
for (const f of readdirSync3(dir)) {
|
|
23925
24467
|
isEmpty = false;
|
|
23926
|
-
const fp =
|
|
24468
|
+
const fp = join11(dir, f);
|
|
23927
24469
|
try {
|
|
23928
|
-
if (now -
|
|
24470
|
+
if (now - statSync4(fp).mtimeMs > maxAge) {
|
|
23929
24471
|
unlinkSync2(fp);
|
|
23930
24472
|
} else {
|
|
23931
24473
|
allRemoved = false;
|
|
@@ -23945,20 +24487,20 @@ function cleanupAllSessions(saveDir) {
|
|
|
23945
24487
|
function writeUniqueFile(dir, name, data, log2) {
|
|
23946
24488
|
const ext = extname(name);
|
|
23947
24489
|
const base = basename2(name, ext) || name;
|
|
23948
|
-
let candidate =
|
|
23949
|
-
if (
|
|
24490
|
+
let candidate = join11(dir, name);
|
|
24491
|
+
if (existsSync7(candidate)) {
|
|
23950
24492
|
return candidate;
|
|
23951
24493
|
}
|
|
23952
24494
|
let counter = 0;
|
|
23953
24495
|
const MAX_ATTEMPTS = 1000;
|
|
23954
24496
|
for (let attempt = 0;attempt < MAX_ATTEMPTS; attempt++) {
|
|
23955
24497
|
try {
|
|
23956
|
-
|
|
24498
|
+
writeFileSync5(candidate, data, { flag: "wx" });
|
|
23957
24499
|
return candidate;
|
|
23958
24500
|
} catch (e) {
|
|
23959
24501
|
if (e instanceof Error && e.code === "EEXIST") {
|
|
23960
24502
|
counter += 1;
|
|
23961
|
-
candidate =
|
|
24503
|
+
candidate = join11(dir, `${base}-${counter}${ext}`);
|
|
23962
24504
|
continue;
|
|
23963
24505
|
}
|
|
23964
24506
|
log2(`[image-hook] failed to save image: ${e}`);
|
|
@@ -23982,17 +24524,17 @@ function processImageAttachments(args) {
|
|
|
23982
24524
|
messagesWithImages.push({ msg, imageParts });
|
|
23983
24525
|
}
|
|
23984
24526
|
}
|
|
23985
|
-
const saveDir =
|
|
24527
|
+
const saveDir = join11(workDir, ".opencode", "images");
|
|
23986
24528
|
if (messagesWithImages.length === 0) {
|
|
23987
|
-
if (
|
|
24529
|
+
if (existsSync7(saveDir))
|
|
23988
24530
|
cleanupAllSessions(saveDir);
|
|
23989
24531
|
return;
|
|
23990
24532
|
}
|
|
23991
|
-
const gitignorePath =
|
|
24533
|
+
const gitignorePath = join11(workDir, ".opencode", ".gitignore");
|
|
23992
24534
|
try {
|
|
23993
|
-
|
|
23994
|
-
if (!
|
|
23995
|
-
|
|
24535
|
+
mkdirSync5(saveDir, { recursive: true });
|
|
24536
|
+
if (!existsSync7(gitignorePath))
|
|
24537
|
+
writeFileSync5(gitignorePath, `*
|
|
23996
24538
|
`);
|
|
23997
24539
|
} catch (e) {
|
|
23998
24540
|
log2(`[image-hook] failed to create image directory: ${e}`);
|
|
@@ -24000,9 +24542,9 @@ function processImageAttachments(args) {
|
|
|
24000
24542
|
cleanupAllSessions(saveDir);
|
|
24001
24543
|
for (const { msg, imageParts } of messagesWithImages) {
|
|
24002
24544
|
const sessionSubdir = msg.info.sessionID ? sanitizeFilename(msg.info.sessionID) : undefined;
|
|
24003
|
-
const targetDir = sessionSubdir ?
|
|
24545
|
+
const targetDir = sessionSubdir ? join11(saveDir, sessionSubdir) : saveDir;
|
|
24004
24546
|
try {
|
|
24005
|
-
|
|
24547
|
+
mkdirSync5(targetDir, { recursive: true });
|
|
24006
24548
|
} catch (e) {
|
|
24007
24549
|
log2(`[image-hook] failed to create target image directory: ${e}`);
|
|
24008
24550
|
}
|
|
@@ -24013,7 +24555,7 @@ function processImageAttachments(args) {
|
|
|
24013
24555
|
if (url) {
|
|
24014
24556
|
const decoded = decodeDataUrl(url);
|
|
24015
24557
|
if (decoded) {
|
|
24016
|
-
const hash =
|
|
24558
|
+
const hash = createHash2("sha1").update(decoded.data).digest("hex").slice(0, 8);
|
|
24017
24559
|
const sanitizedFilename = filename ? sanitizeFilename(filename) : undefined;
|
|
24018
24560
|
const baseName = sanitizedFilename ? sanitizedFilename.replace(/\.[^.]+$/, "") || "image" : "image";
|
|
24019
24561
|
const ext = sanitizedFilename ? extname(sanitizedFilename) || extFromMime(decoded.mime) : extFromMime(decoded.mime);
|
|
@@ -24204,7 +24746,7 @@ function createReflectCommandHook() {
|
|
|
24204
24746
|
};
|
|
24205
24747
|
}
|
|
24206
24748
|
// src/hooks/task-session-manager/index.ts
|
|
24207
|
-
import
|
|
24749
|
+
import path12 from "node:path";
|
|
24208
24750
|
var AGENT_NAME_SET = new Set([
|
|
24209
24751
|
"orchestrator",
|
|
24210
24752
|
"oracle",
|
|
@@ -24258,8 +24800,8 @@ function extractTaskSummary(output) {
|
|
|
24258
24800
|
return summary?.trim() || undefined;
|
|
24259
24801
|
}
|
|
24260
24802
|
function normalizePath(root, file) {
|
|
24261
|
-
const relative =
|
|
24262
|
-
if (!relative || relative.startsWith("..") ||
|
|
24803
|
+
const relative = path12.relative(root, file);
|
|
24804
|
+
if (!relative || relative.startsWith("..") || path12.isAbsolute(relative)) {
|
|
24263
24805
|
return file;
|
|
24264
24806
|
}
|
|
24265
24807
|
return relative;
|
|
@@ -24825,7 +25367,7 @@ function formatCancelledTaskStatusOutput(taskID, summary = "cancelled") {
|
|
|
24825
25367
|
`);
|
|
24826
25368
|
}
|
|
24827
25369
|
// src/interview/manager.ts
|
|
24828
|
-
import
|
|
25370
|
+
import path16 from "node:path";
|
|
24829
25371
|
|
|
24830
25372
|
// src/interview/dashboard.ts
|
|
24831
25373
|
import crypto from "node:crypto";
|
|
@@ -24835,27 +25377,27 @@ import {
|
|
|
24835
25377
|
createServer
|
|
24836
25378
|
} from "node:http";
|
|
24837
25379
|
import os4 from "node:os";
|
|
24838
|
-
import
|
|
25380
|
+
import path14 from "node:path";
|
|
24839
25381
|
import { URL as URL2 } from "node:url";
|
|
24840
25382
|
|
|
24841
25383
|
// src/interview/document.ts
|
|
24842
25384
|
import * as fsSync from "node:fs";
|
|
24843
25385
|
import * as fs6 from "node:fs/promises";
|
|
24844
|
-
import * as
|
|
25386
|
+
import * as path13 from "node:path";
|
|
24845
25387
|
var DEFAULT_OUTPUT_FOLDER = "interview";
|
|
24846
25388
|
function normalizeOutputFolder(outputFolder) {
|
|
24847
25389
|
const normalized = outputFolder.trim().replace(/^\/+|\/+$/g, "");
|
|
24848
25390
|
return normalized || DEFAULT_OUTPUT_FOLDER;
|
|
24849
25391
|
}
|
|
24850
25392
|
function createInterviewDirectoryPath(directory, outputFolder) {
|
|
24851
|
-
return
|
|
25393
|
+
return path13.join(directory, normalizeOutputFolder(outputFolder));
|
|
24852
25394
|
}
|
|
24853
25395
|
function createInterviewFilePath(directory, outputFolder, idea) {
|
|
24854
25396
|
const fileName = `${slugify(idea) || "interview"}.md`;
|
|
24855
|
-
return
|
|
25397
|
+
return path13.join(createInterviewDirectoryPath(directory, outputFolder), fileName);
|
|
24856
25398
|
}
|
|
24857
25399
|
function relativeInterviewPath(directory, filePath) {
|
|
24858
|
-
return
|
|
25400
|
+
return path13.relative(directory, filePath) || path13.basename(filePath);
|
|
24859
25401
|
}
|
|
24860
25402
|
function resolveExistingInterviewPath(directory, outputFolder, value) {
|
|
24861
25403
|
const trimmed = value.trim();
|
|
@@ -24864,22 +25406,22 @@ function resolveExistingInterviewPath(directory, outputFolder, value) {
|
|
|
24864
25406
|
}
|
|
24865
25407
|
const outputDir = createInterviewDirectoryPath(directory, outputFolder);
|
|
24866
25408
|
const candidates = new Set;
|
|
24867
|
-
const resolvedRoot =
|
|
24868
|
-
if (
|
|
25409
|
+
const resolvedRoot = path13.resolve(directory);
|
|
25410
|
+
if (path13.isAbsolute(trimmed)) {
|
|
24869
25411
|
candidates.add(trimmed);
|
|
24870
25412
|
} else {
|
|
24871
|
-
candidates.add(
|
|
24872
|
-
candidates.add(
|
|
25413
|
+
candidates.add(path13.resolve(directory, trimmed));
|
|
25414
|
+
candidates.add(path13.join(outputDir, trimmed));
|
|
24873
25415
|
if (!trimmed.endsWith(".md")) {
|
|
24874
|
-
candidates.add(
|
|
25416
|
+
candidates.add(path13.join(outputDir, `${trimmed}.md`));
|
|
24875
25417
|
}
|
|
24876
25418
|
}
|
|
24877
25419
|
for (const candidate of candidates) {
|
|
24878
|
-
if (
|
|
25420
|
+
if (path13.extname(candidate) !== ".md") {
|
|
24879
25421
|
continue;
|
|
24880
25422
|
}
|
|
24881
|
-
const resolved =
|
|
24882
|
-
if (!resolved.startsWith(resolvedRoot +
|
|
25423
|
+
const resolved = path13.resolve(candidate);
|
|
25424
|
+
if (!resolved.startsWith(resolvedRoot + path13.sep) && resolved !== resolvedRoot) {
|
|
24883
25425
|
continue;
|
|
24884
25426
|
}
|
|
24885
25427
|
if (fsSync.existsSync(candidate)) {
|
|
@@ -24959,7 +25501,7 @@ function parseFrontmatter(content) {
|
|
|
24959
25501
|
return result;
|
|
24960
25502
|
}
|
|
24961
25503
|
async function ensureInterviewFile(record) {
|
|
24962
|
-
await fs6.mkdir(
|
|
25504
|
+
await fs6.mkdir(path13.dirname(record.markdownPath), { recursive: true });
|
|
24963
25505
|
try {
|
|
24964
25506
|
await fs6.access(record.markdownPath);
|
|
24965
25507
|
} catch {
|
|
@@ -26629,12 +27171,12 @@ function renderInterviewPage(interviewId, resumeSlug) {
|
|
|
26629
27171
|
|
|
26630
27172
|
// src/interview/dashboard.ts
|
|
26631
27173
|
function getAuthFilePath(port) {
|
|
26632
|
-
const dataHome = process.env.XDG_DATA_HOME ||
|
|
26633
|
-
return
|
|
27174
|
+
const dataHome = process.env.XDG_DATA_HOME || path14.join(os4.homedir(), ".local", "share");
|
|
27175
|
+
return path14.join(dataHome, "opencode", `.dashboard-${port}.json`);
|
|
26634
27176
|
}
|
|
26635
27177
|
function writeAuthFile(port, token) {
|
|
26636
27178
|
const filePath = getAuthFilePath(port);
|
|
26637
|
-
const dir =
|
|
27179
|
+
const dir = path14.dirname(filePath);
|
|
26638
27180
|
try {
|
|
26639
27181
|
fsSync2.mkdirSync(dir, { recursive: true });
|
|
26640
27182
|
} catch {}
|
|
@@ -26771,7 +27313,7 @@ function createDashboardServer(config) {
|
|
|
26771
27313
|
const directories = getKnownDirectories();
|
|
26772
27314
|
const items = [];
|
|
26773
27315
|
for (const dir of directories) {
|
|
26774
|
-
const interviewDir =
|
|
27316
|
+
const interviewDir = path14.join(dir, config.outputFolder);
|
|
26775
27317
|
let entries;
|
|
26776
27318
|
try {
|
|
26777
27319
|
entries = await fs7.readdir(interviewDir);
|
|
@@ -26783,7 +27325,7 @@ function createDashboardServer(config) {
|
|
|
26783
27325
|
continue;
|
|
26784
27326
|
let content;
|
|
26785
27327
|
try {
|
|
26786
|
-
content = await fs7.readFile(
|
|
27328
|
+
content = await fs7.readFile(path14.join(interviewDir, entry), "utf8");
|
|
26787
27329
|
} catch {
|
|
26788
27330
|
continue;
|
|
26789
27331
|
}
|
|
@@ -26809,7 +27351,7 @@ function createDashboardServer(config) {
|
|
|
26809
27351
|
const directories = getKnownDirectories();
|
|
26810
27352
|
let rebuilt = 0;
|
|
26811
27353
|
for (const dir of directories) {
|
|
26812
|
-
const interviewDir =
|
|
27354
|
+
const interviewDir = path14.join(dir, config.outputFolder);
|
|
26813
27355
|
let entries;
|
|
26814
27356
|
try {
|
|
26815
27357
|
entries = await fs7.readdir(interviewDir);
|
|
@@ -26821,7 +27363,7 @@ function createDashboardServer(config) {
|
|
|
26821
27363
|
continue;
|
|
26822
27364
|
let content;
|
|
26823
27365
|
try {
|
|
26824
|
-
content = await fs7.readFile(
|
|
27366
|
+
content = await fs7.readFile(path14.join(interviewDir, entry), "utf8");
|
|
26825
27367
|
} catch {
|
|
26826
27368
|
continue;
|
|
26827
27369
|
}
|
|
@@ -26847,7 +27389,7 @@ function createDashboardServer(config) {
|
|
|
26847
27389
|
questions: [],
|
|
26848
27390
|
pendingAnswers: null,
|
|
26849
27391
|
lastUpdatedAt: fm.updatedAt ? new Date(fm.updatedAt).getTime() : Date.now(),
|
|
26850
|
-
filePath:
|
|
27392
|
+
filePath: path14.join(interviewDir, entry),
|
|
26851
27393
|
nudgeAction: null
|
|
26852
27394
|
});
|
|
26853
27395
|
if (!sessions.has(fm.sessionID)) {
|
|
@@ -27111,7 +27653,7 @@ function createDashboardServer(config) {
|
|
|
27111
27653
|
const dirs = getKnownDirectories();
|
|
27112
27654
|
for (const dir of dirs) {
|
|
27113
27655
|
const slug = extractResumeSlug(interviewId);
|
|
27114
|
-
const candidate =
|
|
27656
|
+
const candidate = path14.join(dir, config.outputFolder, `${slug}.md`);
|
|
27115
27657
|
try {
|
|
27116
27658
|
document = await fs7.readFile(candidate, "utf8");
|
|
27117
27659
|
markdownPath = candidate;
|
|
@@ -27639,7 +28181,7 @@ function createInterviewServer(deps) {
|
|
|
27639
28181
|
// src/interview/service.ts
|
|
27640
28182
|
import { spawn as spawn2 } from "node:child_process";
|
|
27641
28183
|
import * as fs8 from "node:fs/promises";
|
|
27642
|
-
import * as
|
|
28184
|
+
import * as path15 from "node:path";
|
|
27643
28185
|
|
|
27644
28186
|
// src/interview/types.ts
|
|
27645
28187
|
import { z as z3 } from "zod";
|
|
@@ -27827,13 +28369,13 @@ function shouldAutoOpenBrowser(config, env) {
|
|
|
27827
28369
|
return requested && !isAutomatedRuntime(env);
|
|
27828
28370
|
}
|
|
27829
28371
|
function openBrowser(url) {
|
|
27830
|
-
const
|
|
28372
|
+
const platform3 = process.platform;
|
|
27831
28373
|
let command;
|
|
27832
28374
|
let args;
|
|
27833
|
-
if (
|
|
28375
|
+
if (platform3 === "darwin") {
|
|
27834
28376
|
command = "open";
|
|
27835
28377
|
args = [url];
|
|
27836
|
-
} else if (
|
|
28378
|
+
} else if (platform3 === "win32") {
|
|
27837
28379
|
command = "cmd";
|
|
27838
28380
|
args = ["/c", "start", "", url];
|
|
27839
28381
|
} else {
|
|
@@ -27906,12 +28448,12 @@ function createInterviewService(ctx, config, deps) {
|
|
|
27906
28448
|
if (!newSlug) {
|
|
27907
28449
|
return;
|
|
27908
28450
|
}
|
|
27909
|
-
const currentFileName =
|
|
28451
|
+
const currentFileName = path15.basename(interview.markdownPath, ".md");
|
|
27910
28452
|
if (currentFileName === newSlug) {
|
|
27911
28453
|
return;
|
|
27912
28454
|
}
|
|
27913
|
-
const dir =
|
|
27914
|
-
const newPath =
|
|
28455
|
+
const dir = path15.dirname(interview.markdownPath);
|
|
28456
|
+
const newPath = path15.join(dir, `${newSlug}.md`);
|
|
27915
28457
|
try {
|
|
27916
28458
|
await fs8.access(newPath);
|
|
27917
28459
|
return;
|
|
@@ -27987,9 +28529,9 @@ function createInterviewService(ctx, config, deps) {
|
|
|
27987
28529
|
const messages = await loadMessages(sessionID);
|
|
27988
28530
|
const title = extractTitle(document);
|
|
27989
28531
|
const record = {
|
|
27990
|
-
id: `${Date.now()}-${++idCounter}-${slugify(
|
|
28532
|
+
id: `${Date.now()}-${++idCounter}-${slugify(path15.basename(markdownPath, ".md")) || "interview"}`,
|
|
27991
28533
|
sessionID,
|
|
27992
|
-
idea: title ||
|
|
28534
|
+
idea: title || path15.basename(markdownPath, ".md"),
|
|
27993
28535
|
markdownPath,
|
|
27994
28536
|
createdAt: nowIso(),
|
|
27995
28537
|
status: "active",
|
|
@@ -28216,7 +28758,7 @@ function createInterviewService(ctx, config, deps) {
|
|
|
28216
28758
|
return fileCache.items;
|
|
28217
28759
|
}
|
|
28218
28760
|
const outputDir = createInterviewDirectoryPath(ctx.directory, outputFolder);
|
|
28219
|
-
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)));
|
|
28220
28762
|
let entries;
|
|
28221
28763
|
try {
|
|
28222
28764
|
entries = await fs8.readdir(outputDir);
|
|
@@ -28227,8 +28769,8 @@ function createInterviewService(ctx, config, deps) {
|
|
|
28227
28769
|
for (const entry of entries) {
|
|
28228
28770
|
if (!entry.endsWith(".md"))
|
|
28229
28771
|
continue;
|
|
28230
|
-
const fullPath =
|
|
28231
|
-
if (activePaths.has(
|
|
28772
|
+
const fullPath = path15.join(outputDir, entry);
|
|
28773
|
+
if (activePaths.has(path15.resolve(fullPath)))
|
|
28232
28774
|
continue;
|
|
28233
28775
|
let content;
|
|
28234
28776
|
try {
|
|
@@ -28327,7 +28869,7 @@ function createInterviewManager(ctx, config) {
|
|
|
28327
28869
|
const outputFolder = interviewConfig?.outputFolder ?? "interview";
|
|
28328
28870
|
if (!dashboardEnabled) {
|
|
28329
28871
|
const service2 = createInterviewService(ctx, interviewConfig);
|
|
28330
|
-
const resolvedOutputPath =
|
|
28872
|
+
const resolvedOutputPath = path16.join(ctx.directory, outputFolder);
|
|
28331
28873
|
const server = createInterviewServer({
|
|
28332
28874
|
getState: async (interviewId) => service2.getInterviewState(interviewId),
|
|
28333
28875
|
listInterviewFiles: async () => service2.listInterviewFiles(),
|
|
@@ -28432,7 +28974,7 @@ function createInterviewManager(ctx, config) {
|
|
|
28432
28974
|
listInterviews: () => service.listInterviews(),
|
|
28433
28975
|
submitAnswers: async (interviewId, answers) => service.submitAnswers(interviewId, answers),
|
|
28434
28976
|
handleNudgeAction: async (interviewId, action) => service.handleNudgeAction(interviewId, action),
|
|
28435
|
-
outputFolder:
|
|
28977
|
+
outputFolder: path16.join(ctx.directory, outputFolder),
|
|
28436
28978
|
port: 0
|
|
28437
28979
|
});
|
|
28438
28980
|
service.setBaseUrlResolver(() => perSessionServer.ensureStarted());
|
|
@@ -28700,6 +29242,7 @@ function createBuiltinMcps(disabledMcps = [], websearchConfig) {
|
|
|
28700
29242
|
}
|
|
28701
29243
|
|
|
28702
29244
|
// src/multiplexer/tmux/index.ts
|
|
29245
|
+
init_compat();
|
|
28703
29246
|
var TMUX_LAYOUT_DEBOUNCE_MS = 150;
|
|
28704
29247
|
|
|
28705
29248
|
class TmuxMultiplexer {
|
|
@@ -28917,23 +29460,23 @@ class TmuxMultiplexer {
|
|
|
28917
29460
|
return null;
|
|
28918
29461
|
}
|
|
28919
29462
|
const stdout = await proc.stdout();
|
|
28920
|
-
const
|
|
29463
|
+
const path17 = stdout.trim().split(`
|
|
28921
29464
|
`)[0];
|
|
28922
|
-
if (!
|
|
29465
|
+
if (!path17) {
|
|
28923
29466
|
log("[tmux] findBinary: no path in output");
|
|
28924
29467
|
return null;
|
|
28925
29468
|
}
|
|
28926
|
-
const verifyProc = crossSpawn([
|
|
29469
|
+
const verifyProc = crossSpawn([path17, "-V"], {
|
|
28927
29470
|
stdout: "pipe",
|
|
28928
29471
|
stderr: "pipe"
|
|
28929
29472
|
});
|
|
28930
29473
|
const verifyExit = await verifyProc.exited;
|
|
28931
29474
|
if (verifyExit !== 0) {
|
|
28932
|
-
log("[tmux] findBinary: tmux -V failed", { path:
|
|
29475
|
+
log("[tmux] findBinary: tmux -V failed", { path: path17, verifyExit });
|
|
28933
29476
|
return null;
|
|
28934
29477
|
}
|
|
28935
|
-
log("[tmux] findBinary: found", { path:
|
|
28936
|
-
return
|
|
29478
|
+
log("[tmux] findBinary: found", { path: path17 });
|
|
29479
|
+
return path17;
|
|
28937
29480
|
} catch (err) {
|
|
28938
29481
|
log("[tmux] findBinary: exception", { error: String(err) });
|
|
28939
29482
|
return null;
|
|
@@ -28945,6 +29488,8 @@ function quoteShellArg(value) {
|
|
|
28945
29488
|
}
|
|
28946
29489
|
|
|
28947
29490
|
// src/multiplexer/zellij/index.ts
|
|
29491
|
+
init_compat();
|
|
29492
|
+
|
|
28948
29493
|
class ZellijMultiplexer {
|
|
28949
29494
|
paneMode;
|
|
28950
29495
|
type = "zellij";
|
|
@@ -29884,22 +30429,359 @@ async function isServerRunning(serverUrl, timeoutMs = 3000, maxAttempts = 2) {
|
|
|
29884
30429
|
}
|
|
29885
30430
|
return false;
|
|
29886
30431
|
}
|
|
29887
|
-
// src/tools/
|
|
30432
|
+
// src/tools/acp-run.ts
|
|
30433
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
30434
|
+
import { createInterface } from "node:readline";
|
|
29888
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";
|
|
29889
30769
|
|
|
29890
30770
|
// src/tools/ast-grep/cli.ts
|
|
29891
|
-
|
|
30771
|
+
init_compat();
|
|
30772
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
29892
30773
|
|
|
29893
30774
|
// src/tools/ast-grep/constants.ts
|
|
29894
|
-
import { existsSync as
|
|
30775
|
+
import { existsSync as existsSync10, statSync as statSync5 } from "node:fs";
|
|
29895
30776
|
import { createRequire as createRequire3 } from "node:module";
|
|
29896
|
-
import { dirname as
|
|
30777
|
+
import { dirname as dirname9, join as join15 } from "node:path";
|
|
29897
30778
|
|
|
29898
30779
|
// src/tools/ast-grep/downloader.ts
|
|
29899
|
-
import { chmodSync, existsSync as
|
|
30780
|
+
import { chmodSync as chmodSync2, existsSync as existsSync9, mkdirSync as mkdirSync7, unlinkSync as unlinkSync4 } from "node:fs";
|
|
29900
30781
|
import { createRequire as createRequire2 } from "node:module";
|
|
29901
|
-
import { homedir as
|
|
29902
|
-
import { join as
|
|
30782
|
+
import { homedir as homedir6 } from "node:os";
|
|
30783
|
+
import { join as join14 } from "node:path";
|
|
30784
|
+
init_compat();
|
|
29903
30785
|
var REPO = "ast-grep/ast-grep";
|
|
29904
30786
|
var DEFAULT_VERSION = "0.40.0";
|
|
29905
30787
|
function getAstGrepVersion() {
|
|
@@ -29923,19 +30805,19 @@ var PLATFORM_MAP = {
|
|
|
29923
30805
|
function getCacheDir2() {
|
|
29924
30806
|
if (process.platform === "win32") {
|
|
29925
30807
|
const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
|
|
29926
|
-
const base2 = localAppData ||
|
|
29927
|
-
return
|
|
30808
|
+
const base2 = localAppData || join14(homedir6(), "AppData", "Local");
|
|
30809
|
+
return join14(base2, "oh-my-opencode-slim", "bin");
|
|
29928
30810
|
}
|
|
29929
30811
|
const xdgCache = process.env.XDG_CACHE_HOME;
|
|
29930
|
-
const base = xdgCache ||
|
|
29931
|
-
return
|
|
30812
|
+
const base = xdgCache || join14(homedir6(), ".cache");
|
|
30813
|
+
return join14(base, "oh-my-opencode-slim", "bin");
|
|
29932
30814
|
}
|
|
29933
30815
|
function getBinaryName() {
|
|
29934
30816
|
return process.platform === "win32" ? "sg.exe" : "sg";
|
|
29935
30817
|
}
|
|
29936
30818
|
function getCachedBinaryPath() {
|
|
29937
|
-
const
|
|
29938
|
-
return
|
|
30819
|
+
const binaryPath = join14(getCacheDir2(), getBinaryName());
|
|
30820
|
+
return existsSync9(binaryPath) ? binaryPath : null;
|
|
29939
30821
|
}
|
|
29940
30822
|
async function downloadAstGrep(version = DEFAULT_VERSION) {
|
|
29941
30823
|
const platformKey = `${process.platform}-${process.arch}`;
|
|
@@ -29946,34 +30828,34 @@ async function downloadAstGrep(version = DEFAULT_VERSION) {
|
|
|
29946
30828
|
}
|
|
29947
30829
|
const cacheDir = getCacheDir2();
|
|
29948
30830
|
const binaryName = getBinaryName();
|
|
29949
|
-
const
|
|
29950
|
-
if (
|
|
29951
|
-
return
|
|
30831
|
+
const binaryPath = join14(cacheDir, binaryName);
|
|
30832
|
+
if (existsSync9(binaryPath)) {
|
|
30833
|
+
return binaryPath;
|
|
29952
30834
|
}
|
|
29953
30835
|
const { arch, os: os5 } = platformInfo;
|
|
29954
30836
|
const assetName = `app-${arch}-${os5}.zip`;
|
|
29955
30837
|
const downloadUrl = `https://github.com/${REPO}/releases/download/${version}/${assetName}`;
|
|
29956
30838
|
console.log(`[oh-my-opencode-slim] Downloading ast-grep binary...`);
|
|
29957
30839
|
try {
|
|
29958
|
-
if (!
|
|
29959
|
-
|
|
30840
|
+
if (!existsSync9(cacheDir)) {
|
|
30841
|
+
mkdirSync7(cacheDir, { recursive: true });
|
|
29960
30842
|
}
|
|
29961
30843
|
const response = await fetch(downloadUrl, { redirect: "follow" });
|
|
29962
30844
|
if (!response.ok) {
|
|
29963
30845
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
29964
30846
|
}
|
|
29965
|
-
const archivePath =
|
|
30847
|
+
const archivePath = join14(cacheDir, assetName);
|
|
29966
30848
|
const arrayBuffer = await response.arrayBuffer();
|
|
29967
30849
|
await crossWrite(archivePath, arrayBuffer);
|
|
29968
30850
|
await extractZip(archivePath, cacheDir);
|
|
29969
|
-
if (
|
|
30851
|
+
if (existsSync9(archivePath)) {
|
|
29970
30852
|
unlinkSync4(archivePath);
|
|
29971
30853
|
}
|
|
29972
|
-
if (process.platform !== "win32" &&
|
|
29973
|
-
|
|
30854
|
+
if (process.platform !== "win32" && existsSync9(binaryPath)) {
|
|
30855
|
+
chmodSync2(binaryPath, 493);
|
|
29974
30856
|
}
|
|
29975
30857
|
console.log(`[oh-my-opencode-slim] ast-grep binary ready.`);
|
|
29976
|
-
return
|
|
30858
|
+
return binaryPath;
|
|
29977
30859
|
} catch (err) {
|
|
29978
30860
|
console.error(`[oh-my-opencode-slim] Failed to download ast-grep: ${err instanceof Error ? err.message : err}`);
|
|
29979
30861
|
return null;
|
|
@@ -30021,13 +30903,13 @@ var CLI_LANGUAGES = [
|
|
|
30021
30903
|
var MIN_BINARY_SIZE = 1e4;
|
|
30022
30904
|
function isValidBinary(filePath) {
|
|
30023
30905
|
try {
|
|
30024
|
-
return
|
|
30906
|
+
return statSync5(filePath).size > MIN_BINARY_SIZE;
|
|
30025
30907
|
} catch {
|
|
30026
30908
|
return false;
|
|
30027
30909
|
}
|
|
30028
30910
|
}
|
|
30029
30911
|
function getPlatformPackageName() {
|
|
30030
|
-
const
|
|
30912
|
+
const platform3 = process.platform;
|
|
30031
30913
|
const arch = process.arch;
|
|
30032
30914
|
const platformMap = {
|
|
30033
30915
|
"darwin-arm64": "@ast-grep/cli-darwin-arm64",
|
|
@@ -30038,7 +30920,7 @@ function getPlatformPackageName() {
|
|
|
30038
30920
|
"win32-arm64": "@ast-grep/cli-win32-arm64-msvc",
|
|
30039
30921
|
"win32-ia32": "@ast-grep/cli-win32-ia32-msvc"
|
|
30040
30922
|
};
|
|
30041
|
-
return platformMap[`${
|
|
30923
|
+
return platformMap[`${platform3}-${arch}`] ?? null;
|
|
30042
30924
|
}
|
|
30043
30925
|
var resolvedCliPath = null;
|
|
30044
30926
|
function findSgCliPathSync() {
|
|
@@ -30050,9 +30932,9 @@ function findSgCliPathSync() {
|
|
|
30050
30932
|
try {
|
|
30051
30933
|
const require2 = createRequire3(import.meta.url);
|
|
30052
30934
|
const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
|
|
30053
|
-
const cliDir =
|
|
30054
|
-
const sgPath =
|
|
30055
|
-
if (
|
|
30935
|
+
const cliDir = dirname9(cliPkgPath);
|
|
30936
|
+
const sgPath = join15(cliDir, binaryName);
|
|
30937
|
+
if (existsSync10(sgPath) && isValidBinary(sgPath)) {
|
|
30056
30938
|
return sgPath;
|
|
30057
30939
|
}
|
|
30058
30940
|
} catch {}
|
|
@@ -30061,19 +30943,19 @@ function findSgCliPathSync() {
|
|
|
30061
30943
|
try {
|
|
30062
30944
|
const require2 = createRequire3(import.meta.url);
|
|
30063
30945
|
const pkgPath = require2.resolve(`${platformPkg}/package.json`);
|
|
30064
|
-
const pkgDir =
|
|
30946
|
+
const pkgDir = dirname9(pkgPath);
|
|
30065
30947
|
const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
|
|
30066
|
-
const
|
|
30067
|
-
if (
|
|
30068
|
-
return
|
|
30948
|
+
const binaryPath = join15(pkgDir, astGrepName);
|
|
30949
|
+
if (existsSync10(binaryPath) && isValidBinary(binaryPath)) {
|
|
30950
|
+
return binaryPath;
|
|
30069
30951
|
}
|
|
30070
30952
|
} catch {}
|
|
30071
30953
|
}
|
|
30072
30954
|
if (process.platform === "darwin") {
|
|
30073
30955
|
const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
|
|
30074
|
-
for (const
|
|
30075
|
-
if (
|
|
30076
|
-
return
|
|
30956
|
+
for (const path17 of homebrewPaths) {
|
|
30957
|
+
if (existsSync10(path17) && isValidBinary(path17)) {
|
|
30958
|
+
return path17;
|
|
30077
30959
|
}
|
|
30078
30960
|
}
|
|
30079
30961
|
}
|
|
@@ -30090,8 +30972,8 @@ function getSgCliPath() {
|
|
|
30090
30972
|
}
|
|
30091
30973
|
return "sg";
|
|
30092
30974
|
}
|
|
30093
|
-
function setSgCliPath(
|
|
30094
|
-
resolvedCliPath =
|
|
30975
|
+
function setSgCliPath(path17) {
|
|
30976
|
+
resolvedCliPath = path17;
|
|
30095
30977
|
}
|
|
30096
30978
|
var DEFAULT_TIMEOUT_MS = 300000;
|
|
30097
30979
|
var DEFAULT_MAX_OUTPUT_BYTES = 1 * 1024 * 1024;
|
|
@@ -30101,7 +30983,7 @@ var DEFAULT_MAX_MATCHES = 500;
|
|
|
30101
30983
|
var initPromise = null;
|
|
30102
30984
|
async function getAstGrepPath() {
|
|
30103
30985
|
const currentPath = getSgCliPath();
|
|
30104
|
-
if (currentPath !== "sg" &&
|
|
30986
|
+
if (currentPath !== "sg" && existsSync11(currentPath)) {
|
|
30105
30987
|
return currentPath;
|
|
30106
30988
|
}
|
|
30107
30989
|
if (initPromise) {
|
|
@@ -30109,7 +30991,7 @@ async function getAstGrepPath() {
|
|
|
30109
30991
|
}
|
|
30110
30992
|
initPromise = (async () => {
|
|
30111
30993
|
const syncPath = findSgCliPathSync();
|
|
30112
|
-
if (syncPath &&
|
|
30994
|
+
if (syncPath && existsSync11(syncPath)) {
|
|
30113
30995
|
setSgCliPath(syncPath);
|
|
30114
30996
|
return syncPath;
|
|
30115
30997
|
}
|
|
@@ -30148,7 +31030,7 @@ async function runSg(options) {
|
|
|
30148
31030
|
const paths2 = options.paths && options.paths.length > 0 ? options.paths : ["."];
|
|
30149
31031
|
args.push(...paths2);
|
|
30150
31032
|
let cliPath = getSgCliPath();
|
|
30151
|
-
if (!
|
|
31033
|
+
if (!existsSync11(cliPath) && cliPath !== "sg") {
|
|
30152
31034
|
const downloadedPath = await getAstGrepPath();
|
|
30153
31035
|
if (downloadedPath) {
|
|
30154
31036
|
cliPath = downloadedPath;
|
|
@@ -30364,14 +31246,14 @@ function showOutputToUser(context, output) {
|
|
|
30364
31246
|
const ctx = context;
|
|
30365
31247
|
ctx.metadata?.({ metadata: { output } });
|
|
30366
31248
|
}
|
|
30367
|
-
var ast_grep_search =
|
|
31249
|
+
var ast_grep_search = tool2({
|
|
30368
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($$$)'",
|
|
30369
31251
|
args: {
|
|
30370
|
-
pattern:
|
|
30371
|
-
lang:
|
|
30372
|
-
paths:
|
|
30373
|
-
globs:
|
|
30374
|
-
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")
|
|
30375
31257
|
},
|
|
30376
31258
|
execute: async (args, context) => {
|
|
30377
31259
|
try {
|
|
@@ -30400,15 +31282,15 @@ ${hint}`;
|
|
|
30400
31282
|
}
|
|
30401
31283
|
}
|
|
30402
31284
|
});
|
|
30403
|
-
var ast_grep_replace =
|
|
31285
|
+
var ast_grep_replace = tool2({
|
|
30404
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)'",
|
|
30405
31287
|
args: {
|
|
30406
|
-
pattern:
|
|
30407
|
-
rewrite:
|
|
30408
|
-
lang:
|
|
30409
|
-
paths:
|
|
30410
|
-
globs:
|
|
30411
|
-
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)")
|
|
30412
31294
|
},
|
|
30413
31295
|
execute: async (args, context) => {
|
|
30414
31296
|
try {
|
|
@@ -30432,20 +31314,20 @@ var ast_grep_replace = tool({
|
|
|
30432
31314
|
});
|
|
30433
31315
|
// src/tools/cancel-task.ts
|
|
30434
31316
|
import {
|
|
30435
|
-
tool as
|
|
31317
|
+
tool as tool3
|
|
30436
31318
|
} from "@opencode-ai/plugin";
|
|
30437
|
-
var
|
|
31319
|
+
var z5 = tool3.schema;
|
|
30438
31320
|
|
|
30439
31321
|
class SessionStillRunningError extends Error {
|
|
30440
31322
|
}
|
|
30441
31323
|
function createCancelTaskTool(options) {
|
|
30442
|
-
const cancel_task =
|
|
31324
|
+
const cancel_task = tool3({
|
|
30443
31325
|
description: `Cancel a tracked background specialist task.
|
|
30444
31326
|
|
|
30445
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.`,
|
|
30446
31328
|
args: {
|
|
30447
|
-
task_id:
|
|
30448
|
-
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")
|
|
30449
31331
|
},
|
|
30450
31332
|
async execute(args, toolContext) {
|
|
30451
31333
|
const parentSessionID = toolContext?.sessionID;
|
|
@@ -30658,7 +31540,7 @@ async function abortAndVerifySession(options, taskID) {
|
|
|
30658
31540
|
attempts,
|
|
30659
31541
|
status: statusSnapshot.status
|
|
30660
31542
|
});
|
|
30661
|
-
await
|
|
31543
|
+
await delay2(retryIntervalMs);
|
|
30662
31544
|
continue;
|
|
30663
31545
|
}
|
|
30664
31546
|
stableStoppedSince ??= Date.now();
|
|
@@ -30671,7 +31553,7 @@ async function abortAndVerifySession(options, taskID) {
|
|
|
30671
31553
|
});
|
|
30672
31554
|
return;
|
|
30673
31555
|
}
|
|
30674
|
-
await
|
|
31556
|
+
await delay2(retryIntervalMs);
|
|
30675
31557
|
}
|
|
30676
31558
|
log("[cancel-task] abort verification timed out", {
|
|
30677
31559
|
taskID,
|
|
@@ -30739,13 +31621,13 @@ async function deleteAndVerifySession(options, taskID, reason) {
|
|
|
30739
31621
|
});
|
|
30740
31622
|
if (status.status === "busy" || status.status === "retry") {
|
|
30741
31623
|
stableStoppedSince = undefined;
|
|
30742
|
-
await
|
|
31624
|
+
await delay2(retryIntervalMs);
|
|
30743
31625
|
continue;
|
|
30744
31626
|
}
|
|
30745
31627
|
stableStoppedSince ??= Date.now();
|
|
30746
31628
|
if (Date.now() - stableStoppedSince >= stableStoppedMs)
|
|
30747
31629
|
return;
|
|
30748
|
-
await
|
|
31630
|
+
await delay2(retryIntervalMs);
|
|
30749
31631
|
}
|
|
30750
31632
|
throw new SessionStillRunningError(`Session delete returned but task did not stay stopped: ${taskID} (${lastStatus ?? "unknown"})`);
|
|
30751
31633
|
}
|
|
@@ -30784,7 +31666,7 @@ async function getSessionStatus(client, taskID) {
|
|
|
30784
31666
|
return { status: undefined, source: "lookup-error", keys: [] };
|
|
30785
31667
|
}
|
|
30786
31668
|
}
|
|
30787
|
-
function
|
|
31669
|
+
function delay2(ms) {
|
|
30788
31670
|
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
30789
31671
|
}
|
|
30790
31672
|
function isSessionID(value) {
|
|
@@ -30826,9 +31708,9 @@ function unknownTaskOutput(taskID, message) {
|
|
|
30826
31708
|
}
|
|
30827
31709
|
// src/tools/council.ts
|
|
30828
31710
|
import {
|
|
30829
|
-
tool as
|
|
31711
|
+
tool as tool4
|
|
30830
31712
|
} from "@opencode-ai/plugin";
|
|
30831
|
-
var
|
|
31713
|
+
var z6 = tool4.schema;
|
|
30832
31714
|
function formatModelComposition(councillorResults) {
|
|
30833
31715
|
return councillorResults.map((cr) => {
|
|
30834
31716
|
const shortModel = shortModelLabel(cr.model);
|
|
@@ -30836,15 +31718,15 @@ function formatModelComposition(councillorResults) {
|
|
|
30836
31718
|
}).join(", ");
|
|
30837
31719
|
}
|
|
30838
31720
|
function createCouncilTool(_ctx, councilManager) {
|
|
30839
|
-
const council_session =
|
|
31721
|
+
const council_session = tool4({
|
|
30840
31722
|
description: `Launch a multi-LLM council session for consensus-based analysis.
|
|
30841
31723
|
|
|
30842
31724
|
Sends the prompt to multiple models (councillors) in parallel and returns their formatted responses for you to synthesize.
|
|
30843
31725
|
|
|
30844
31726
|
Returns the councillor responses with a summary footer.`,
|
|
30845
31727
|
args: {
|
|
30846
|
-
prompt:
|
|
30847
|
-
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.')
|
|
30848
31730
|
},
|
|
30849
31731
|
async execute(args, toolContext) {
|
|
30850
31732
|
if (!toolContext || typeof toolContext !== "object" || !("sessionID" in toolContext)) {
|
|
@@ -30896,14 +31778,14 @@ import * as fs10 from "node:fs";
|
|
|
30896
31778
|
// src/tui-state.ts
|
|
30897
31779
|
import * as fs9 from "node:fs";
|
|
30898
31780
|
import * as os5 from "node:os";
|
|
30899
|
-
import * as
|
|
31781
|
+
import * as path17 from "node:path";
|
|
30900
31782
|
var STATE_DIR = "oh-my-opencode-slim";
|
|
30901
31783
|
var STATE_FILE = "tui-state.json";
|
|
30902
31784
|
function dataDir() {
|
|
30903
|
-
return process.env.XDG_DATA_HOME ??
|
|
31785
|
+
return process.env.XDG_DATA_HOME ?? path17.join(os5.homedir(), ".local", "share");
|
|
30904
31786
|
}
|
|
30905
31787
|
function getTuiStatePath() {
|
|
30906
|
-
return
|
|
31788
|
+
return path17.join(dataDir(), "opencode", "storage", STATE_DIR, STATE_FILE);
|
|
30907
31789
|
}
|
|
30908
31790
|
function emptySnapshot() {
|
|
30909
31791
|
return {
|
|
@@ -30939,7 +31821,7 @@ async function readTuiSnapshotAsync() {
|
|
|
30939
31821
|
function writeTuiSnapshot(snapshot) {
|
|
30940
31822
|
try {
|
|
30941
31823
|
const filePath = getTuiStatePath();
|
|
30942
|
-
fs9.mkdirSync(
|
|
31824
|
+
fs9.mkdirSync(path17.dirname(filePath), { recursive: true });
|
|
30943
31825
|
fs9.writeFileSync(filePath, `${JSON.stringify(snapshot)}
|
|
30944
31826
|
`);
|
|
30945
31827
|
} catch {}
|
|
@@ -31139,14 +32021,14 @@ var BINARY_PREFIXES = [
|
|
|
31139
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.";
|
|
31140
32022
|
// src/tools/smartfetch/tool.ts
|
|
31141
32023
|
import os6 from "node:os";
|
|
31142
|
-
import
|
|
32024
|
+
import path21 from "node:path";
|
|
31143
32025
|
import {
|
|
31144
|
-
tool as
|
|
32026
|
+
tool as tool5
|
|
31145
32027
|
} from "@opencode-ai/plugin";
|
|
31146
32028
|
|
|
31147
32029
|
// src/tools/smartfetch/binary.ts
|
|
31148
32030
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
|
|
31149
|
-
import
|
|
32031
|
+
import path18 from "node:path";
|
|
31150
32032
|
function extensionForMime(contentType) {
|
|
31151
32033
|
const mime = contentType.split(";")[0]?.trim().toLowerCase();
|
|
31152
32034
|
const map = {
|
|
@@ -31167,10 +32049,10 @@ function buildBinaryResultMessage(fetchResult, savedPath) {
|
|
|
31167
32049
|
async function saveBinary(binaryDir, data, contentType, filename) {
|
|
31168
32050
|
await mkdir2(binaryDir, { recursive: true });
|
|
31169
32051
|
const initialName = filename || `webfetch-${Date.now()}.${extensionForMime(contentType)}`;
|
|
31170
|
-
const parsed =
|
|
32052
|
+
const parsed = path18.parse(initialName);
|
|
31171
32053
|
for (let attempt = 0;attempt < 1000; attempt++) {
|
|
31172
32054
|
const candidateName = attempt === 0 ? initialName : `${parsed.name}-${attempt}${parsed.ext || `.${extensionForMime(contentType)}`}`;
|
|
31173
|
-
const file =
|
|
32055
|
+
const file = path18.join(binaryDir, candidateName);
|
|
31174
32056
|
try {
|
|
31175
32057
|
await writeFile2(file, data, { flag: "wx" });
|
|
31176
32058
|
return file;
|
|
@@ -31309,7 +32191,7 @@ var M = class u2 {
|
|
|
31309
32191
|
return this.#S;
|
|
31310
32192
|
}
|
|
31311
32193
|
constructor(e) {
|
|
31312
|
-
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;
|
|
31313
32195
|
if (x !== undefined && typeof x?.now != "function")
|
|
31314
32196
|
throw new TypeError("perf option must have a now() method if specified");
|
|
31315
32197
|
if (this.#m = x ?? C, t !== 0 && !F(t))
|
|
@@ -31327,7 +32209,7 @@ var M = class u2 {
|
|
|
31327
32209
|
throw new TypeError("memoMethod must be a function if defined");
|
|
31328
32210
|
if (this.#U = m, a !== undefined && typeof a != "function")
|
|
31329
32211
|
throw new TypeError("fetchMethod must be a function if specified");
|
|
31330
|
-
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) {
|
|
31331
32213
|
if (this.#u !== 0 && !F(this.#u))
|
|
31332
32214
|
throw new TypeError("maxSize must be a positive integer if specified");
|
|
31333
32215
|
if (!F(this.maxEntrySize))
|
|
@@ -31707,8 +32589,8 @@ var M = class u2 {
|
|
|
31707
32589
|
let A = this.#p(b);
|
|
31708
32590
|
if (!y && !A)
|
|
31709
32591
|
return a && (a.fetch = "hit"), this.#L(b), s && this.#D(b), a && this.#E(a, b), d;
|
|
31710
|
-
let
|
|
31711
|
-
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;
|
|
31712
32594
|
}
|
|
31713
32595
|
}
|
|
31714
32596
|
forceFetch(e, t = {}) {
|
|
@@ -31824,7 +32706,7 @@ var M = class u2 {
|
|
|
31824
32706
|
};
|
|
31825
32707
|
|
|
31826
32708
|
// src/tools/smartfetch/network.ts
|
|
31827
|
-
import
|
|
32709
|
+
import path19 from "node:path";
|
|
31828
32710
|
|
|
31829
32711
|
// src/tools/smartfetch/utils.ts
|
|
31830
32712
|
var import_readability = __toESM(require_readability(), 1);
|
|
@@ -32549,7 +33431,7 @@ function inferFilenameFromUrl(url) {
|
|
|
32549
33431
|
function truncateFilename(name, maxLength = 180) {
|
|
32550
33432
|
if (name.length <= maxLength)
|
|
32551
33433
|
return name;
|
|
32552
|
-
const parsed =
|
|
33434
|
+
const parsed = path19.parse(name);
|
|
32553
33435
|
const ext = parsed.ext || "";
|
|
32554
33436
|
const baseLimit = Math.max(1, maxLength - ext.length);
|
|
32555
33437
|
return `${parsed.name.slice(0, baseLimit)}${ext}`;
|
|
@@ -32719,9 +33601,9 @@ function isInvalidLlmsResult(fetchResult) {
|
|
|
32719
33601
|
}
|
|
32720
33602
|
|
|
32721
33603
|
// src/tools/smartfetch/secondary-model.ts
|
|
32722
|
-
import { existsSync as
|
|
33604
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
32723
33605
|
import { readFile as readFile4 } from "node:fs/promises";
|
|
32724
|
-
import
|
|
33606
|
+
import path20 from "node:path";
|
|
32725
33607
|
function parseModelRef(value) {
|
|
32726
33608
|
if (!value)
|
|
32727
33609
|
return;
|
|
@@ -32747,8 +33629,8 @@ function pickAgentModelRef(value) {
|
|
|
32747
33629
|
}
|
|
32748
33630
|
function findPreferredOpenCodeConfigPath(baseDir) {
|
|
32749
33631
|
for (const file of ["opencode.jsonc", "opencode.json"]) {
|
|
32750
|
-
const fullPath =
|
|
32751
|
-
if (
|
|
33632
|
+
const fullPath = path20.join(baseDir, file);
|
|
33633
|
+
if (existsSync12(fullPath))
|
|
32752
33634
|
return fullPath;
|
|
32753
33635
|
}
|
|
32754
33636
|
return;
|
|
@@ -32764,7 +33646,7 @@ async function readOpenCodeConfigFile(configPath) {
|
|
|
32764
33646
|
}
|
|
32765
33647
|
}
|
|
32766
33648
|
async function readEffectiveOpenCodeConfig(directory) {
|
|
32767
|
-
const projectDir =
|
|
33649
|
+
const projectDir = path20.join(directory, ".opencode");
|
|
32768
33650
|
const userDirs = getConfigSearchDirs();
|
|
32769
33651
|
const projectPath = findPreferredOpenCodeConfigPath(projectDir);
|
|
32770
33652
|
const userPath = userDirs.map((configDir) => findPreferredOpenCodeConfigPath(configDir)).find(Boolean);
|
|
@@ -32845,6 +33727,29 @@ function isUsableSecondaryText(text) {
|
|
|
32845
33727
|
return false;
|
|
32846
33728
|
return true;
|
|
32847
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
|
+
}
|
|
32848
33753
|
async function runSecondaryModel(client, directory, model, prompt, content) {
|
|
32849
33754
|
const session2 = await client.session.create({
|
|
32850
33755
|
responseStyle: "data",
|
|
@@ -32871,23 +33776,26 @@ Note: only the first ${inputChars} characters of a longer fetched document were
|
|
|
32871
33776
|
const toolIDsData = toolIDsResponse;
|
|
32872
33777
|
const toolIDs = Array.isArray(toolIDsData.data) ? toolIDsData.data : Array.isArray(toolIDsResponse) ? toolIDsResponse : [];
|
|
32873
33778
|
const disabledTools = Object.fromEntries((toolIDs || []).map((id) => [id, false]));
|
|
32874
|
-
const result = await
|
|
32875
|
-
|
|
32876
|
-
|
|
32877
|
-
|
|
32878
|
-
|
|
32879
|
-
|
|
32880
|
-
|
|
32881
|
-
|
|
32882
|
-
|
|
32883
|
-
|
|
32884
|
-
|
|
32885
|
-
|
|
32886
|
-
|
|
32887
|
-
|
|
32888
|
-
|
|
32889
|
-
|
|
32890
|
-
|
|
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
|
+
]);
|
|
32891
33799
|
const parts = result?.data?.parts ?? result?.parts ?? [];
|
|
32892
33800
|
const text = parts.map((part) => part?.type === "text" ? part.text || "" : "").join("").trim();
|
|
32893
33801
|
return {
|
|
@@ -32897,12 +33805,7 @@ Note: only the first ${inputChars} characters of a longer fetched document were
|
|
|
32897
33805
|
sourceChars
|
|
32898
33806
|
};
|
|
32899
33807
|
} finally {
|
|
32900
|
-
await client
|
|
32901
|
-
path: { id: sessionId },
|
|
32902
|
-
query: { directory }
|
|
32903
|
-
}).catch(() => {
|
|
32904
|
-
return;
|
|
32905
|
-
});
|
|
33808
|
+
await deleteSessionSafely(client, sessionId, directory);
|
|
32906
33809
|
}
|
|
32907
33810
|
}
|
|
32908
33811
|
async function runSecondaryModelWithFallback(client, directory, models, prompt, content) {
|
|
@@ -32923,20 +33826,20 @@ async function runSecondaryModelWithFallback(client, directory, models, prompt,
|
|
|
32923
33826
|
}
|
|
32924
33827
|
|
|
32925
33828
|
// src/tools/smartfetch/tool.ts
|
|
32926
|
-
var
|
|
33829
|
+
var z7 = tool5.schema;
|
|
32927
33830
|
function createWebfetchTool(pluginCtx, options = {}) {
|
|
32928
|
-
const binaryDir = options.binaryDir ||
|
|
32929
|
-
return
|
|
33831
|
+
const binaryDir = options.binaryDir || path21.join(os6.tmpdir(), "opencode-smartfetch");
|
|
33832
|
+
return tool5({
|
|
32930
33833
|
description: WEBFETCH_DESCRIPTION,
|
|
32931
33834
|
args: {
|
|
32932
|
-
url:
|
|
32933
|
-
format:
|
|
32934
|
-
timeout:
|
|
32935
|
-
prompt:
|
|
32936
|
-
extract_main:
|
|
32937
|
-
prefer_llms_txt:
|
|
32938
|
-
include_metadata:
|
|
32939
|
-
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.")
|
|
32940
33843
|
},
|
|
32941
33844
|
async execute(args, ctx) {
|
|
32942
33845
|
const secondaryModels = await readSecondaryModelFromConfig(ctx.directory || pluginCtx.directory);
|
|
@@ -33481,7 +34384,7 @@ async function appLog(ctx, level, message) {
|
|
|
33481
34384
|
}
|
|
33482
34385
|
var HEALTH_CHECK = {
|
|
33483
34386
|
minAgents: 5,
|
|
33484
|
-
minTools:
|
|
34387
|
+
minTools: 4,
|
|
33485
34388
|
minMcps: 1
|
|
33486
34389
|
};
|
|
33487
34390
|
async function probeJSDOM() {
|
|
@@ -33526,6 +34429,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33526
34429
|
let companionManager;
|
|
33527
34430
|
let councilTools;
|
|
33528
34431
|
let cancelTaskTools;
|
|
34432
|
+
let acpRunTools;
|
|
33529
34433
|
let webfetch;
|
|
33530
34434
|
let rewriteDisplayNameMentions;
|
|
33531
34435
|
let toolCount = 0;
|
|
@@ -33570,6 +34474,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33570
34474
|
depthTracker = new SubagentDepthTracker;
|
|
33571
34475
|
councilTools = config.council ? createCouncilTool(ctx, new CouncilManager(ctx, config, depthTracker, multiplexerEnabled)) : {};
|
|
33572
34476
|
mcps = createBuiltinMcps(config.disabled_mcps, config.websearch);
|
|
34477
|
+
acpRunTools = Object.keys(config.acpAgents ?? {}).length > 0 ? { acp_run: createAcpRunTool(config.acpAgents) } : {};
|
|
33573
34478
|
webfetch = createWebfetchTool(ctx);
|
|
33574
34479
|
backgroundJobBoard = new BackgroundJobBoard({
|
|
33575
34480
|
maxReusablePerAgent: config.backgroundJobs?.maxSessionsPerAgent ?? 2,
|
|
@@ -33578,7 +34483,8 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33578
34483
|
});
|
|
33579
34484
|
multiplexerSessionManager = new MultiplexerSessionManager(ctx, multiplexerConfig, backgroundJobBoard);
|
|
33580
34485
|
autoUpdateChecker = createAutoUpdateCheckerHook(ctx, {
|
|
33581
|
-
autoUpdate: config.autoUpdate ?? true
|
|
34486
|
+
autoUpdate: config.autoUpdate ?? true,
|
|
34487
|
+
companion: config.companion
|
|
33582
34488
|
});
|
|
33583
34489
|
phaseReminderHook = createPhaseReminderHook();
|
|
33584
34490
|
filterAvailableSkillsHook = createFilterAvailableSkillsHook(ctx, config);
|
|
@@ -33608,7 +34514,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33608
34514
|
backgroundJobBoard,
|
|
33609
34515
|
shouldManageSession: (sessionID) => sessionAgentMap.get(sessionID) === "orchestrator"
|
|
33610
34516
|
});
|
|
33611
|
-
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;
|
|
33612
34518
|
} catch (err) {
|
|
33613
34519
|
log("[plugin] FATAL: init failed", String(err));
|
|
33614
34520
|
await appLog(ctx, "error", `INIT FAILED: ${String(err)}. Report at github.com/alvinunreal/oh-my-opencode-slim/issues/310`);
|
|
@@ -33644,6 +34550,22 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33644
34550
|
appLog(ctx, "warn", msg).catch(() => {});
|
|
33645
34551
|
}
|
|
33646
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
|
+
}
|
|
33647
34569
|
companionManager.onLoad();
|
|
33648
34570
|
return {
|
|
33649
34571
|
name: "oh-my-opencode-slim",
|
|
@@ -33651,6 +34573,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33651
34573
|
tool: {
|
|
33652
34574
|
...councilTools,
|
|
33653
34575
|
...cancelTaskTools,
|
|
34576
|
+
...acpRunTools,
|
|
33654
34577
|
webfetch,
|
|
33655
34578
|
ast_grep_search,
|
|
33656
34579
|
ast_grep_replace
|