oh-my-opencode-slim 2.0.2 → 2.0.4
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 +7 -1
- package/README.zh-CN.md +1 -0
- package/dist/cli/companion.d.ts +2 -2
- package/dist/cli/index.js +331 -68
- package/dist/companion/manager.d.ts +2 -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 +1405 -481
- 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
|
|
|
@@ -19116,6 +19251,11 @@ Build a short work graph before dispatching:
|
|
|
19116
19251
|
- Advisory ownership for write-capable lanes
|
|
19117
19252
|
- Verification/review lanes that run after implementation
|
|
19118
19253
|
|
|
19254
|
+
### Todo Continuity
|
|
19255
|
+
- When the user adds a new task while a todo list exists, append the new task to the end of the existing todo list instead of replacing the list.
|
|
19256
|
+
- Preserve existing todo order, statuses, and priorities unless the user explicitly asks to reprioritize, cancel, or replace them.
|
|
19257
|
+
- Finish the current in-progress task before starting the newly appended task unless the current task is blocked or the user explicitly overrides the order.
|
|
19258
|
+
|
|
19119
19259
|
Can tasks be split into background specialist work?
|
|
19120
19260
|
${enabledParallelExamples}
|
|
19121
19261
|
|
|
@@ -19123,6 +19263,7 @@ Balance: respect dependencies, avoid parallelizing what must be sequential, and
|
|
|
19123
19263
|
|
|
19124
19264
|
### Background Task Discipline
|
|
19125
19265
|
- Prefer \`task(..., background: true)\` for delegated work that can run independently.
|
|
19266
|
+
- Launch specialist agents in the background by default so the orchestrator stays unblocked and can reconcile results when they return.
|
|
19126
19267
|
- Track each task's specialist, objective, task/session ID, and file/topic ownership.
|
|
19127
19268
|
- Continue orchestration only on non-overlapping work; otherwise briefly report what was launched and stop.
|
|
19128
19269
|
- Before local edits or another writer task, compare against running task scopes.
|
|
@@ -19734,6 +19875,39 @@ function normalizeDisplayName(displayName) {
|
|
|
19734
19875
|
const trimmed = displayName.trim();
|
|
19735
19876
|
return trimmed.startsWith("@") ? trimmed.slice(1) : trimmed;
|
|
19736
19877
|
}
|
|
19878
|
+
function buildAcpAgentDefinition(name, config) {
|
|
19879
|
+
const description = config.description ?? `External ACP agent '${name}' via ${config.command}`;
|
|
19880
|
+
const prompt = config.prompt ?? [
|
|
19881
|
+
`You are the ${name} ACP wrapper agent.`,
|
|
19882
|
+
"",
|
|
19883
|
+
"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.",
|
|
19884
|
+
`Always call acp_run with agent: ${JSON.stringify(name)} and pass the full user task as prompt.`,
|
|
19885
|
+
"Do not edit files yourself unless the ACP result explicitly asks you to report a local follow-up to the orchestrator."
|
|
19886
|
+
].join(`
|
|
19887
|
+
`);
|
|
19888
|
+
return {
|
|
19889
|
+
name,
|
|
19890
|
+
description,
|
|
19891
|
+
config: {
|
|
19892
|
+
model: config.wrapperModel ?? DEFAULT_MODELS.fixer ?? DEFAULT_MODELS.librarian ?? DEFAULT_MODELS.orchestrator ?? DEFAULT_MODELS.oracle,
|
|
19893
|
+
temperature: 0,
|
|
19894
|
+
prompt,
|
|
19895
|
+
permission: {
|
|
19896
|
+
read: "deny",
|
|
19897
|
+
edit: "deny",
|
|
19898
|
+
bash: "deny",
|
|
19899
|
+
task: "deny",
|
|
19900
|
+
glob: "deny",
|
|
19901
|
+
grep: "deny",
|
|
19902
|
+
list: "deny",
|
|
19903
|
+
webfetch: "deny",
|
|
19904
|
+
question: "deny",
|
|
19905
|
+
skill: "deny",
|
|
19906
|
+
acp_run: "allow"
|
|
19907
|
+
}
|
|
19908
|
+
}
|
|
19909
|
+
};
|
|
19910
|
+
}
|
|
19737
19911
|
function isSafeDisplayName(displayName) {
|
|
19738
19912
|
return SAFE_AGENT_ALIAS_RE.test(displayName);
|
|
19739
19913
|
}
|
|
@@ -19873,6 +20047,24 @@ function createAgents(config) {
|
|
|
19873
20047
|
buildCustomAgentDefinition(name, override, customPrompts.prompt, customPrompts.appendPrompt)
|
|
19874
20048
|
];
|
|
19875
20049
|
});
|
|
20050
|
+
const acpAgentNames = getAcpAgentNames(config).map(normalizeCustomAgentName).filter((name) => name.length > 0).filter((name) => {
|
|
20051
|
+
if (!SAFE_AGENT_ALIAS_RE.test(name)) {
|
|
20052
|
+
throw new Error(`ACP agent name '${name}' must match /^[a-z][a-z0-9_-]*$/i`);
|
|
20053
|
+
}
|
|
20054
|
+
if (isKnownAgentName(name) || AGENT_ALIASES[name] !== undefined) {
|
|
20055
|
+
throw new Error(`ACP agent '${name}' conflicts with a built-in agent name or alias`);
|
|
20056
|
+
}
|
|
20057
|
+
if (customAgentNames.includes(name)) {
|
|
20058
|
+
throw new Error(`ACP agent '${name}' conflicts with a custom agent of the same name`);
|
|
20059
|
+
}
|
|
20060
|
+
return !disabled.has(name);
|
|
20061
|
+
});
|
|
20062
|
+
const protoAcpAgents = acpAgentNames.map((name) => {
|
|
20063
|
+
const acp = config?.acpAgents?.[name];
|
|
20064
|
+
if (!acp)
|
|
20065
|
+
throw new Error(`ACP agent '${name}' is missing config`);
|
|
20066
|
+
return buildAcpAgentDefinition(name, acp);
|
|
20067
|
+
});
|
|
19876
20068
|
const builtInSubAgents = protoSubAgents.map((agent) => {
|
|
19877
20069
|
const override = getAgentOverride(config, agent.name);
|
|
19878
20070
|
if (override) {
|
|
@@ -19896,7 +20088,15 @@ function createAgents(config) {
|
|
|
19896
20088
|
applyDefaultPermissions(agent, override?.skills);
|
|
19897
20089
|
return agent;
|
|
19898
20090
|
});
|
|
19899
|
-
const
|
|
20091
|
+
const acpSubAgents = protoAcpAgents.map((agent) => {
|
|
20092
|
+
applyDefaultPermissions(agent);
|
|
20093
|
+
return agent;
|
|
20094
|
+
});
|
|
20095
|
+
const allSubAgents = [
|
|
20096
|
+
...builtInSubAgents,
|
|
20097
|
+
...customSubAgents,
|
|
20098
|
+
...acpSubAgents
|
|
20099
|
+
];
|
|
19900
20100
|
const orchestratorOverride = getAgentOverride(config, "orchestrator");
|
|
19901
20101
|
const orchestratorModel = orchestratorOverride?.model ?? DEFAULT_MODELS.orchestrator;
|
|
19902
20102
|
const orchestratorPrompts = loadAgentPrompt("orchestrator", config?.preset);
|
|
@@ -19918,6 +20118,20 @@ function createAgents(config) {
|
|
|
19918
20118
|
const override = getAgentOverride(config, agent.name);
|
|
19919
20119
|
return override?.orchestratorPrompt;
|
|
19920
20120
|
}).filter((prompt) => Boolean(prompt));
|
|
20121
|
+
const acpOrchestratorPrompts = acpSubAgents.map((agent) => {
|
|
20122
|
+
const acp = config?.acpAgents?.[agent.name];
|
|
20123
|
+
if (acp?.orchestratorPrompt)
|
|
20124
|
+
return acp.orchestratorPrompt;
|
|
20125
|
+
return [
|
|
20126
|
+
`@${agent.name}`,
|
|
20127
|
+
`- Lane: External ACP-connected agent (${acp?.command ?? "unknown command"})`,
|
|
20128
|
+
`- Role: ${agent.description ?? `External ACP agent ${agent.name}`}`,
|
|
20129
|
+
"- **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.",
|
|
20130
|
+
"- **Do not delegate when:** The built-in specialists can handle the task more directly or local file ownership would conflict with another writer lane.",
|
|
20131
|
+
"- **Result handling:** Treat returned output as external-agent work. Reconcile any reported file changes before continuing."
|
|
20132
|
+
].join(`
|
|
20133
|
+
`);
|
|
20134
|
+
});
|
|
19921
20135
|
const usedDisplayNames = new Set;
|
|
19922
20136
|
for (const [, displayName] of displayNameMap) {
|
|
19923
20137
|
const normalizedDisplayName = normalizeDisplayName(displayName);
|
|
@@ -19930,13 +20144,17 @@ function createAgents(config) {
|
|
|
19930
20144
|
usedDisplayNames.add(normalizedDisplayName);
|
|
19931
20145
|
}
|
|
19932
20146
|
for (const displayName of usedDisplayNames) {
|
|
19933
|
-
if (ALL_AGENT_NAMES.includes(displayName) || customAgentNames.includes(displayName)) {
|
|
20147
|
+
if (ALL_AGENT_NAMES.includes(displayName) || customAgentNames.includes(displayName) || acpAgentNames.includes(displayName)) {
|
|
19934
20148
|
throw new Error(`displayName '${displayName}' conflicts with an agent name`);
|
|
19935
20149
|
}
|
|
19936
20150
|
}
|
|
19937
20151
|
injectDisplayNames(orchestrator, displayNameMap);
|
|
19938
|
-
|
|
19939
|
-
|
|
20152
|
+
const extraOrchestratorPrompts = [
|
|
20153
|
+
...customOrchestratorPrompts,
|
|
20154
|
+
...acpOrchestratorPrompts
|
|
20155
|
+
];
|
|
20156
|
+
if (extraOrchestratorPrompts.length > 0) {
|
|
20157
|
+
const rewrittenPrompts = extraOrchestratorPrompts.map((promptText) => {
|
|
19940
20158
|
let text = promptText;
|
|
19941
20159
|
for (const [internalName, displayName] of displayNameMap) {
|
|
19942
20160
|
text = text.replace(new RegExp(`@${escapeRegExp(internalName)}\\b`, "g"), `@${normalizeDisplayName(displayName)}`);
|
|
@@ -20008,6 +20226,7 @@ import {
|
|
|
20008
20226
|
mkdirSync as mkdirSync2,
|
|
20009
20227
|
readFileSync as readFileSync2,
|
|
20010
20228
|
renameSync,
|
|
20229
|
+
rmSync,
|
|
20011
20230
|
writeFileSync
|
|
20012
20231
|
} from "node:fs";
|
|
20013
20232
|
import * as os2 from "node:os";
|
|
@@ -20096,11 +20315,15 @@ function stateFilePath() {
|
|
|
20096
20315
|
const base = xdg && path3.isAbsolute(xdg) ? xdg : path3.join(os2.homedir(), ".local", "share");
|
|
20097
20316
|
return path3.join(base, "opencode", "storage", "oh-my-opencode-slim", "companion-state.json");
|
|
20098
20317
|
}
|
|
20099
|
-
function
|
|
20318
|
+
function defaultBinaryPath() {
|
|
20100
20319
|
const xdg = process.env.XDG_DATA_HOME?.trim();
|
|
20101
20320
|
const base = xdg && path3.isAbsolute(xdg) ? xdg : path3.join(os2.homedir(), ".local", "share");
|
|
20102
20321
|
const binaryName = os2.platform() === "win32" ? "oh-my-opencode-slim-companion.exe" : "oh-my-opencode-slim-companion";
|
|
20103
|
-
|
|
20322
|
+
return path3.join(base, "opencode", "storage", "oh-my-opencode-slim", "bin", binaryName);
|
|
20323
|
+
}
|
|
20324
|
+
function resolveCompanionBinaryPath(config) {
|
|
20325
|
+
const configured = config?.binaryPath?.trim();
|
|
20326
|
+
const bin = configured || defaultBinaryPath();
|
|
20104
20327
|
return existsSync2(bin) ? bin : null;
|
|
20105
20328
|
}
|
|
20106
20329
|
function readState() {
|
|
@@ -20113,17 +20336,43 @@ function readState() {
|
|
|
20113
20336
|
} catch {}
|
|
20114
20337
|
return { version: 1, sessions: [] };
|
|
20115
20338
|
}
|
|
20116
|
-
function writeState(
|
|
20339
|
+
function writeState(mutator) {
|
|
20117
20340
|
const file = stateFilePath();
|
|
20118
20341
|
try {
|
|
20119
20342
|
mkdirSync2(path3.dirname(file), { recursive: true });
|
|
20120
|
-
const
|
|
20121
|
-
|
|
20122
|
-
|
|
20343
|
+
const release = acquireStateLock(file);
|
|
20344
|
+
try {
|
|
20345
|
+
const state = readState();
|
|
20346
|
+
mutator(state);
|
|
20347
|
+
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
20348
|
+
writeFileSync(tmp, JSON.stringify(state));
|
|
20349
|
+
renameSync(tmp, file);
|
|
20350
|
+
} finally {
|
|
20351
|
+
release();
|
|
20352
|
+
}
|
|
20123
20353
|
} catch (err) {
|
|
20124
20354
|
log("[companion] write failed", String(err));
|
|
20125
20355
|
}
|
|
20126
20356
|
}
|
|
20357
|
+
function acquireStateLock(file) {
|
|
20358
|
+
const lock = `${file}.lock`;
|
|
20359
|
+
for (let attempt = 0;attempt < 40; attempt++) {
|
|
20360
|
+
try {
|
|
20361
|
+
mkdirSync2(lock);
|
|
20362
|
+
return () => {
|
|
20363
|
+
try {
|
|
20364
|
+
rmSync(lock, { recursive: true, force: true });
|
|
20365
|
+
} catch {}
|
|
20366
|
+
};
|
|
20367
|
+
} catch (err) {
|
|
20368
|
+
const code = err.code;
|
|
20369
|
+
if (code !== "EEXIST")
|
|
20370
|
+
throw err;
|
|
20371
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
|
|
20372
|
+
}
|
|
20373
|
+
}
|
|
20374
|
+
throw new Error("timed out waiting for companion state lock");
|
|
20375
|
+
}
|
|
20127
20376
|
|
|
20128
20377
|
class CompanionManager {
|
|
20129
20378
|
id;
|
|
@@ -20131,6 +20380,7 @@ class CompanionManager {
|
|
|
20131
20380
|
status = "idle";
|
|
20132
20381
|
busyAgentSessions = new Map;
|
|
20133
20382
|
config;
|
|
20383
|
+
companionProcess = null;
|
|
20134
20384
|
constructor(sessionId, cwd, config) {
|
|
20135
20385
|
this.id = sessionId;
|
|
20136
20386
|
this.cwd = cwd;
|
|
@@ -20139,12 +20389,11 @@ class CompanionManager {
|
|
|
20139
20389
|
onLoad() {
|
|
20140
20390
|
if (this.config?.enabled !== true) {
|
|
20141
20391
|
try {
|
|
20142
|
-
|
|
20143
|
-
|
|
20144
|
-
|
|
20145
|
-
state.sessions =
|
|
20146
|
-
|
|
20147
|
-
}
|
|
20392
|
+
if (!existsSync2(stateFilePath()))
|
|
20393
|
+
return;
|
|
20394
|
+
writeState((state) => {
|
|
20395
|
+
state.sessions = state.sessions.filter((s) => s.session_id !== this.id);
|
|
20396
|
+
});
|
|
20148
20397
|
} catch {}
|
|
20149
20398
|
return;
|
|
20150
20399
|
}
|
|
@@ -20196,9 +20445,15 @@ class CompanionManager {
|
|
|
20196
20445
|
onExit() {
|
|
20197
20446
|
if (this.config?.enabled !== true)
|
|
20198
20447
|
return;
|
|
20199
|
-
|
|
20200
|
-
|
|
20201
|
-
|
|
20448
|
+
if (this.companionProcess) {
|
|
20449
|
+
try {
|
|
20450
|
+
this.companionProcess.kill();
|
|
20451
|
+
} catch {}
|
|
20452
|
+
this.companionProcess = null;
|
|
20453
|
+
}
|
|
20454
|
+
writeState((state) => {
|
|
20455
|
+
state.sessions = state.sessions.filter((s) => s.session_id !== this.id);
|
|
20456
|
+
});
|
|
20202
20457
|
}
|
|
20203
20458
|
activeAgents() {
|
|
20204
20459
|
const agents = Array.from(this.busyAgentSessions.values());
|
|
@@ -20214,28 +20469,41 @@ class CompanionManager {
|
|
|
20214
20469
|
if (this.config?.enabled !== true)
|
|
20215
20470
|
return;
|
|
20216
20471
|
try {
|
|
20217
|
-
const state = readState();
|
|
20218
20472
|
const entry = {
|
|
20219
20473
|
session_id: this.id,
|
|
20220
20474
|
cwd: this.cwd,
|
|
20221
20475
|
active_agents: this.activeAgents(),
|
|
20222
20476
|
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 = {
|
|
20477
|
+
pid: process.pid,
|
|
20478
|
+
config: this.config ? {
|
|
20233
20479
|
enabled: this.config.enabled ?? false,
|
|
20234
20480
|
position: this.config.position ?? "bottom-right",
|
|
20235
|
-
size: this.config.size ?? "medium"
|
|
20236
|
-
|
|
20237
|
-
|
|
20238
|
-
|
|
20481
|
+
size: this.config.size ?? "medium",
|
|
20482
|
+
gifPack: this.config.gifPack ?? "default",
|
|
20483
|
+
loopStyle: this.config.loopStyle ?? "classic",
|
|
20484
|
+
speed: this.config.speed ?? 1,
|
|
20485
|
+
debug: this.config.debug ?? false
|
|
20486
|
+
} : undefined
|
|
20487
|
+
};
|
|
20488
|
+
writeState((state) => {
|
|
20489
|
+
const idx = state.sessions.findIndex((s) => s.session_id === this.id);
|
|
20490
|
+
if (idx >= 0) {
|
|
20491
|
+
state.sessions[idx] = entry;
|
|
20492
|
+
} else {
|
|
20493
|
+
state.sessions.push(entry);
|
|
20494
|
+
}
|
|
20495
|
+
if (this.config) {
|
|
20496
|
+
state.config = {
|
|
20497
|
+
enabled: this.config.enabled ?? false,
|
|
20498
|
+
position: this.config.position ?? "bottom-right",
|
|
20499
|
+
size: this.config.size ?? "medium",
|
|
20500
|
+
gifPack: this.config.gifPack ?? "default",
|
|
20501
|
+
loopStyle: this.config.loopStyle ?? "classic",
|
|
20502
|
+
speed: this.config.speed ?? 1,
|
|
20503
|
+
debug: this.config.debug ?? false
|
|
20504
|
+
};
|
|
20505
|
+
}
|
|
20506
|
+
});
|
|
20239
20507
|
} catch (err) {
|
|
20240
20508
|
log("[companion] flush failed", String(err));
|
|
20241
20509
|
}
|
|
@@ -20243,24 +20511,359 @@ class CompanionManager {
|
|
|
20243
20511
|
spawnIfAvailable() {
|
|
20244
20512
|
if (this.config?.enabled !== true)
|
|
20245
20513
|
return;
|
|
20246
|
-
const bin =
|
|
20514
|
+
const bin = resolveCompanionBinaryPath(this.config);
|
|
20247
20515
|
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");
|
|
20516
|
+
const expected = this.config.binaryPath?.trim() || defaultBinaryPath();
|
|
20251
20517
|
log(`[companion] enabled but companion binary not found at expected path: ${expected}. Please install/download the companion binary separately.`);
|
|
20252
20518
|
return;
|
|
20253
20519
|
}
|
|
20254
20520
|
try {
|
|
20255
|
-
const child = spawn(bin, [], {
|
|
20521
|
+
const child = spawn(bin, [], {
|
|
20522
|
+
detached: true,
|
|
20523
|
+
env: {
|
|
20524
|
+
...process.env,
|
|
20525
|
+
OH_MY_OPENCODE_SLIM_COMPANION_SESSION_ID: this.id,
|
|
20526
|
+
...this.config.debug === true ? { OH_MY_OPENCODE_SLIM_COMPANION_DEBUG: "1" } : {}
|
|
20527
|
+
},
|
|
20528
|
+
stdio: "ignore"
|
|
20529
|
+
});
|
|
20530
|
+
this.companionProcess = child;
|
|
20256
20531
|
child.unref();
|
|
20257
|
-
log("[companion] spawned",
|
|
20532
|
+
log("[companion] spawned", JSON.stringify({
|
|
20533
|
+
bin,
|
|
20534
|
+
sessionId: this.id,
|
|
20535
|
+
debug: this.config.debug === true
|
|
20536
|
+
}));
|
|
20258
20537
|
} catch (err) {
|
|
20259
20538
|
log("[companion] spawn failed", String(err));
|
|
20260
20539
|
}
|
|
20261
20540
|
}
|
|
20262
20541
|
}
|
|
20263
20542
|
|
|
20543
|
+
// src/companion/updater.ts
|
|
20544
|
+
init_compat();
|
|
20545
|
+
import { createHash } from "node:crypto";
|
|
20546
|
+
import {
|
|
20547
|
+
chmodSync,
|
|
20548
|
+
copyFileSync,
|
|
20549
|
+
existsSync as existsSync3,
|
|
20550
|
+
mkdirSync as mkdirSync3,
|
|
20551
|
+
mkdtempSync,
|
|
20552
|
+
readFileSync as readFileSync3,
|
|
20553
|
+
renameSync as renameSync2,
|
|
20554
|
+
rmSync as rmSync2,
|
|
20555
|
+
statSync as statSync2,
|
|
20556
|
+
writeFileSync as writeFileSync2
|
|
20557
|
+
} from "node:fs";
|
|
20558
|
+
import { homedir as homedir4, platform as platform2, tmpdir } from "node:os";
|
|
20559
|
+
import * as path4 from "node:path";
|
|
20560
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
20561
|
+
var DOWNLOAD_TIMEOUT_MS = 30000;
|
|
20562
|
+
var LOCK_TIMEOUT_MS = 2000;
|
|
20563
|
+
var STALE_LOCK_MS = 5 * 60000;
|
|
20564
|
+
var FIRST_METADATA_VERSION = "0.1.2";
|
|
20565
|
+
var COMPANION_MANIFEST = {
|
|
20566
|
+
version: "0.1.3",
|
|
20567
|
+
tag: "companion-v0.1.3",
|
|
20568
|
+
repo: "alvinunreal/oh-my-opencode-slim",
|
|
20569
|
+
checksums: {
|
|
20570
|
+
"oh-my-opencode-slim-companion-v0.1.3-aarch64-apple-darwin.tar.gz": "b4885f9b1900c02376e5f8f5ae6f3b8a89d26f7514b03f836d7e3d618164a0ed",
|
|
20571
|
+
"oh-my-opencode-slim-companion-v0.1.3-aarch64-unknown-linux-gnu.tar.gz": "ed7cffc583e1eaa78c9bea702e6b6aa3bbc5bb4d881713fb2050237ba6b7aca5",
|
|
20572
|
+
"oh-my-opencode-slim-companion-v0.1.3-x86_64-apple-darwin.tar.gz": "98d8ea7c7bc4415b18e0d4c524adb4eb9a84c872919840fdc021f0f50c61f808",
|
|
20573
|
+
"oh-my-opencode-slim-companion-v0.1.3-x86_64-pc-windows-msvc.zip": "9316a49bf01f3b4fb1ce2d62edfc46094e73bb153d6ce023fb7df085afcf77bd",
|
|
20574
|
+
"oh-my-opencode-slim-companion-v0.1.3-x86_64-unknown-linux-gnu.tar.gz": "33f5fd4b6c80155a019391e5efb13904ca9531ba8dd8c6cba30a161f1b07b764"
|
|
20575
|
+
}
|
|
20576
|
+
};
|
|
20577
|
+
function getCompanionTarget() {
|
|
20578
|
+
const p = process.platform;
|
|
20579
|
+
const a = process.arch;
|
|
20580
|
+
if (p === "darwin") {
|
|
20581
|
+
if (a === "arm64")
|
|
20582
|
+
return "aarch64-apple-darwin";
|
|
20583
|
+
if (a === "x64")
|
|
20584
|
+
return "x86_64-apple-darwin";
|
|
20585
|
+
} else if (p === "linux") {
|
|
20586
|
+
if (a === "x64")
|
|
20587
|
+
return "x86_64-unknown-linux-gnu";
|
|
20588
|
+
if (a === "arm64")
|
|
20589
|
+
return "aarch64-unknown-linux-gnu";
|
|
20590
|
+
} else if (p === "win32") {
|
|
20591
|
+
if (a === "x64")
|
|
20592
|
+
return "x86_64-pc-windows-msvc";
|
|
20593
|
+
}
|
|
20594
|
+
return null;
|
|
20595
|
+
}
|
|
20596
|
+
function getCompanionBinaryPath() {
|
|
20597
|
+
const xdg = process.env.XDG_DATA_HOME?.trim();
|
|
20598
|
+
const base = xdg && path4.isAbsolute(xdg) ? xdg : path4.join(homedir4(), ".local", "share");
|
|
20599
|
+
return path4.join(base, "opencode", "storage", "oh-my-opencode-slim", "bin", platform2() === "win32" ? "oh-my-opencode-slim-companion.exe" : "oh-my-opencode-slim-companion");
|
|
20600
|
+
}
|
|
20601
|
+
function loadCompanionManifestFromPackageRoot(packageRoot) {
|
|
20602
|
+
const manifestPath = path4.join(packageRoot, "src", "companion", "companion-manifest.json");
|
|
20603
|
+
try {
|
|
20604
|
+
const parsed = JSON.parse(readFileSync3(manifestPath, "utf8"));
|
|
20605
|
+
if (parsed.version && parsed.tag && parsed.repo) {
|
|
20606
|
+
return {
|
|
20607
|
+
version: parsed.version,
|
|
20608
|
+
tag: parsed.tag,
|
|
20609
|
+
repo: parsed.repo,
|
|
20610
|
+
checksums: parsed.checksums
|
|
20611
|
+
};
|
|
20612
|
+
}
|
|
20613
|
+
} catch {}
|
|
20614
|
+
return null;
|
|
20615
|
+
}
|
|
20616
|
+
async function ensureCompanionVersion(options) {
|
|
20617
|
+
const { config, dryRun = false } = options;
|
|
20618
|
+
const manifest = options.manifest ?? COMPANION_MANIFEST;
|
|
20619
|
+
const binaryPath = getCompanionBinaryPath();
|
|
20620
|
+
if (config?.enabled !== true) {
|
|
20621
|
+
return { status: "skipped", reason: "disabled", binaryPath };
|
|
20622
|
+
}
|
|
20623
|
+
if (config.binaryPath?.trim()) {
|
|
20624
|
+
return { status: "skipped", reason: "custom-binary", binaryPath };
|
|
20625
|
+
}
|
|
20626
|
+
const target = getCompanionTarget();
|
|
20627
|
+
if (!target) {
|
|
20628
|
+
return {
|
|
20629
|
+
status: "failed",
|
|
20630
|
+
binaryPath,
|
|
20631
|
+
error: `Unsupported platform/architecture: ${process.platform} ${process.arch}`
|
|
20632
|
+
};
|
|
20633
|
+
}
|
|
20634
|
+
const current = readInstallMetadata(binaryPath);
|
|
20635
|
+
if (existsSync3(binaryPath) && !current && manifest.version === FIRST_METADATA_VERSION) {
|
|
20636
|
+
const archiveName = companionArchiveName(manifest.version, target);
|
|
20637
|
+
writeInstallMetadata(binaryPath, {
|
|
20638
|
+
version: manifest.version,
|
|
20639
|
+
tag: manifest.tag,
|
|
20640
|
+
target,
|
|
20641
|
+
installedAt: new Date().toISOString(),
|
|
20642
|
+
archiveName,
|
|
20643
|
+
checksum: manifest.checksums?.[archiveName]
|
|
20644
|
+
});
|
|
20645
|
+
return { status: "current", binaryPath, version: manifest.version };
|
|
20646
|
+
}
|
|
20647
|
+
if (existsSync3(binaryPath) && current?.target === target && compareSemver(current.version, manifest.version) >= 0) {
|
|
20648
|
+
return { status: "current", binaryPath, version: current.version };
|
|
20649
|
+
}
|
|
20650
|
+
if (dryRun) {
|
|
20651
|
+
return { status: "installed", binaryPath, version: manifest.version };
|
|
20652
|
+
}
|
|
20653
|
+
return withCompanionInstallLock(binaryPath, options.lockTimeoutMs, options.lockStaleMs, async () => {
|
|
20654
|
+
const lockedCurrent = readInstallMetadata(binaryPath);
|
|
20655
|
+
if (existsSync3(binaryPath) && !lockedCurrent && manifest.version === FIRST_METADATA_VERSION) {
|
|
20656
|
+
const archiveName = companionArchiveName(manifest.version, target);
|
|
20657
|
+
writeInstallMetadata(binaryPath, {
|
|
20658
|
+
version: manifest.version,
|
|
20659
|
+
tag: manifest.tag,
|
|
20660
|
+
target,
|
|
20661
|
+
installedAt: new Date().toISOString(),
|
|
20662
|
+
archiveName,
|
|
20663
|
+
checksum: manifest.checksums?.[archiveName]
|
|
20664
|
+
});
|
|
20665
|
+
return { status: "current", binaryPath, version: manifest.version };
|
|
20666
|
+
}
|
|
20667
|
+
if (existsSync3(binaryPath) && lockedCurrent?.target === target && compareSemver(lockedCurrent.version, manifest.version) >= 0) {
|
|
20668
|
+
return {
|
|
20669
|
+
status: "current",
|
|
20670
|
+
binaryPath,
|
|
20671
|
+
version: lockedCurrent.version
|
|
20672
|
+
};
|
|
20673
|
+
}
|
|
20674
|
+
return installCompanionArchive(binaryPath, target, manifest, options.downloadTimeoutMs ?? DOWNLOAD_TIMEOUT_MS);
|
|
20675
|
+
});
|
|
20676
|
+
}
|
|
20677
|
+
async function installCompanionArchive(finalBinaryPath, target, manifest, downloadTimeoutMs) {
|
|
20678
|
+
const isWindows = process.platform === "win32";
|
|
20679
|
+
const archiveName = companionArchiveName(manifest.version, target, isWindows);
|
|
20680
|
+
const downloadUrl = `https://github.com/${manifest.repo}/releases/download/${manifest.tag}/${archiveName}`;
|
|
20681
|
+
const expectedChecksum = manifest.checksums?.[archiveName];
|
|
20682
|
+
if (!expectedChecksum) {
|
|
20683
|
+
return {
|
|
20684
|
+
status: "failed",
|
|
20685
|
+
binaryPath: finalBinaryPath,
|
|
20686
|
+
error: `Missing SHA256 checksum for companion archive: ${archiveName}`
|
|
20687
|
+
};
|
|
20688
|
+
}
|
|
20689
|
+
let buffer;
|
|
20690
|
+
const controller = new AbortController;
|
|
20691
|
+
const timeout = setTimeout(() => controller.abort(), downloadTimeoutMs);
|
|
20692
|
+
try {
|
|
20693
|
+
const res = await fetch(downloadUrl, { signal: controller.signal });
|
|
20694
|
+
if (!res.ok) {
|
|
20695
|
+
return {
|
|
20696
|
+
status: "failed",
|
|
20697
|
+
binaryPath: finalBinaryPath,
|
|
20698
|
+
error: `Failed to download companion binary (HTTP ${res.status}): ${res.statusText}`
|
|
20699
|
+
};
|
|
20700
|
+
}
|
|
20701
|
+
buffer = await res.arrayBuffer();
|
|
20702
|
+
} catch (err) {
|
|
20703
|
+
return {
|
|
20704
|
+
status: "failed",
|
|
20705
|
+
binaryPath: finalBinaryPath,
|
|
20706
|
+
error: `Failed to fetch companion archive: ${formatError(err)}`
|
|
20707
|
+
};
|
|
20708
|
+
} finally {
|
|
20709
|
+
clearTimeout(timeout);
|
|
20710
|
+
}
|
|
20711
|
+
const checksum = createHash("sha256").update(Buffer.from(buffer)).digest("hex");
|
|
20712
|
+
if (checksum !== expectedChecksum) {
|
|
20713
|
+
return {
|
|
20714
|
+
status: "failed",
|
|
20715
|
+
binaryPath: finalBinaryPath,
|
|
20716
|
+
error: "Companion archive checksum mismatch"
|
|
20717
|
+
};
|
|
20718
|
+
}
|
|
20719
|
+
let tempDir = "";
|
|
20720
|
+
try {
|
|
20721
|
+
tempDir = mkdtempSync(path4.join(tmpdir(), "companion-install-"));
|
|
20722
|
+
const archivePath = path4.join(tempDir, archiveName);
|
|
20723
|
+
writeFileSync2(archivePath, Buffer.from(buffer));
|
|
20724
|
+
const extractedDir = path4.join(tempDir, "extracted");
|
|
20725
|
+
mkdirSync3(extractedDir, { recursive: true });
|
|
20726
|
+
if (isWindows) {
|
|
20727
|
+
const { extractZip: extractZip2 } = await Promise.resolve().then(() => (init_zip_extractor(), exports_zip_extractor));
|
|
20728
|
+
await extractZip2(archivePath, extractedDir);
|
|
20729
|
+
} else {
|
|
20730
|
+
const proc = crossSpawn(["tar", "-xzf", archivePath, "-C", extractedDir]);
|
|
20731
|
+
const exitCode = await proc.exited;
|
|
20732
|
+
if (exitCode !== 0) {
|
|
20733
|
+
const stderr = await proc.stderr();
|
|
20734
|
+
return {
|
|
20735
|
+
status: "failed",
|
|
20736
|
+
binaryPath: finalBinaryPath,
|
|
20737
|
+
error: `Archive extraction failed (tar exited with ${exitCode}): ${stderr}`
|
|
20738
|
+
};
|
|
20739
|
+
}
|
|
20740
|
+
}
|
|
20741
|
+
const binaryName = isWindows ? "oh-my-opencode-slim-companion.exe" : "oh-my-opencode-slim-companion";
|
|
20742
|
+
const extractedBinaryPath = path4.join(extractedDir, binaryName);
|
|
20743
|
+
if (!existsSync3(extractedBinaryPath)) {
|
|
20744
|
+
return {
|
|
20745
|
+
status: "failed",
|
|
20746
|
+
binaryPath: finalBinaryPath,
|
|
20747
|
+
error: `Binary ${binaryName} not found in extracted archive`
|
|
20748
|
+
};
|
|
20749
|
+
}
|
|
20750
|
+
const binDir = path4.dirname(finalBinaryPath);
|
|
20751
|
+
mkdirSync3(binDir, { recursive: true });
|
|
20752
|
+
const tmpFinalPath = `${finalBinaryPath}.tmp`;
|
|
20753
|
+
copyFileSync(extractedBinaryPath, tmpFinalPath);
|
|
20754
|
+
if (!isWindows) {
|
|
20755
|
+
chmodSync(tmpFinalPath, 493);
|
|
20756
|
+
}
|
|
20757
|
+
renameSync2(tmpFinalPath, finalBinaryPath);
|
|
20758
|
+
writeInstallMetadata(finalBinaryPath, {
|
|
20759
|
+
version: manifest.version,
|
|
20760
|
+
tag: manifest.tag,
|
|
20761
|
+
target,
|
|
20762
|
+
installedAt: new Date().toISOString(),
|
|
20763
|
+
archiveName,
|
|
20764
|
+
checksum
|
|
20765
|
+
});
|
|
20766
|
+
return {
|
|
20767
|
+
status: "installed",
|
|
20768
|
+
binaryPath: finalBinaryPath,
|
|
20769
|
+
version: manifest.version
|
|
20770
|
+
};
|
|
20771
|
+
} catch (err) {
|
|
20772
|
+
return {
|
|
20773
|
+
status: "failed",
|
|
20774
|
+
binaryPath: finalBinaryPath,
|
|
20775
|
+
error: `Failed to install companion: ${formatError(err)}`
|
|
20776
|
+
};
|
|
20777
|
+
} finally {
|
|
20778
|
+
if (tempDir) {
|
|
20779
|
+
try {
|
|
20780
|
+
rmSync2(tempDir, { recursive: true, force: true });
|
|
20781
|
+
} catch {}
|
|
20782
|
+
}
|
|
20783
|
+
}
|
|
20784
|
+
}
|
|
20785
|
+
function readInstallMetadata(binaryPath) {
|
|
20786
|
+
try {
|
|
20787
|
+
const parsed = JSON.parse(readFileSync3(metadataPath(binaryPath), "utf8"));
|
|
20788
|
+
if (parsed?.version && parsed.tag && parsed.target) {
|
|
20789
|
+
return parsed;
|
|
20790
|
+
}
|
|
20791
|
+
} catch {}
|
|
20792
|
+
return null;
|
|
20793
|
+
}
|
|
20794
|
+
function writeInstallMetadata(binaryPath, metadata) {
|
|
20795
|
+
writeFileSync2(metadataPath(binaryPath), JSON.stringify(metadata, null, 2));
|
|
20796
|
+
}
|
|
20797
|
+
function metadataPath(binaryPath) {
|
|
20798
|
+
return `${binaryPath}.json`;
|
|
20799
|
+
}
|
|
20800
|
+
async function withCompanionInstallLock(binaryPath, timeoutMs, staleMs, run) {
|
|
20801
|
+
const lock = `${binaryPath}.lock`;
|
|
20802
|
+
const deadline = Date.now() + (timeoutMs ?? LOCK_TIMEOUT_MS);
|
|
20803
|
+
const staleAfterMs = staleMs ?? STALE_LOCK_MS;
|
|
20804
|
+
mkdirSync3(path4.dirname(binaryPath), { recursive: true });
|
|
20805
|
+
while (Date.now() <= deadline) {
|
|
20806
|
+
try {
|
|
20807
|
+
mkdirSync3(lock);
|
|
20808
|
+
try {
|
|
20809
|
+
return await run();
|
|
20810
|
+
} finally {
|
|
20811
|
+
try {
|
|
20812
|
+
rmSync2(lock, { recursive: true, force: true });
|
|
20813
|
+
} catch {}
|
|
20814
|
+
}
|
|
20815
|
+
} catch (err) {
|
|
20816
|
+
const code = err.code;
|
|
20817
|
+
if (code !== "EEXIST")
|
|
20818
|
+
throw err;
|
|
20819
|
+
try {
|
|
20820
|
+
const ageMs = Date.now() - statSync2(lock).mtimeMs;
|
|
20821
|
+
if (ageMs > staleAfterMs) {
|
|
20822
|
+
rmSync2(lock, { recursive: true, force: true });
|
|
20823
|
+
log("[companion] removed stale install lock", lock);
|
|
20824
|
+
continue;
|
|
20825
|
+
}
|
|
20826
|
+
} catch (statErr) {
|
|
20827
|
+
const statCode = statErr.code;
|
|
20828
|
+
if (statCode !== "ENOENT")
|
|
20829
|
+
throw statErr;
|
|
20830
|
+
}
|
|
20831
|
+
await delay(25);
|
|
20832
|
+
}
|
|
20833
|
+
}
|
|
20834
|
+
log("[companion] install lock timed out", lock);
|
|
20835
|
+
return {
|
|
20836
|
+
status: "failed",
|
|
20837
|
+
binaryPath,
|
|
20838
|
+
error: "Timed out waiting for companion install lock"
|
|
20839
|
+
};
|
|
20840
|
+
}
|
|
20841
|
+
function companionArchiveName(version, target, isWindows = process.platform === "win32") {
|
|
20842
|
+
const ext = isWindows ? "zip" : "tar.gz";
|
|
20843
|
+
return `oh-my-opencode-slim-companion-v${version}-${target}.${ext}`;
|
|
20844
|
+
}
|
|
20845
|
+
function compareSemver(a, b) {
|
|
20846
|
+
const left = parseSemver(a);
|
|
20847
|
+
const right = parseSemver(b);
|
|
20848
|
+
if (!left || !right)
|
|
20849
|
+
return a.localeCompare(b);
|
|
20850
|
+
for (let i = 0;i < 3; i++) {
|
|
20851
|
+
const diff = left[i] - right[i];
|
|
20852
|
+
if (diff !== 0)
|
|
20853
|
+
return diff;
|
|
20854
|
+
}
|
|
20855
|
+
return 0;
|
|
20856
|
+
}
|
|
20857
|
+
function parseSemver(version) {
|
|
20858
|
+
const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/);
|
|
20859
|
+
if (!match)
|
|
20860
|
+
return null;
|
|
20861
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
20862
|
+
}
|
|
20863
|
+
function formatError(err) {
|
|
20864
|
+
return err instanceof Error ? err.message : String(err);
|
|
20865
|
+
}
|
|
20866
|
+
|
|
20264
20867
|
// src/config/runtime-preset.ts
|
|
20265
20868
|
var activeRuntimePreset = null;
|
|
20266
20869
|
function setActiveRuntimePreset(name) {
|
|
@@ -20597,7 +21200,7 @@ function ensureApplyPatchError(error, context) {
|
|
|
20597
21200
|
|
|
20598
21201
|
// src/hooks/apply-patch/execution-context.ts
|
|
20599
21202
|
import * as fs3 from "node:fs/promises";
|
|
20600
|
-
import
|
|
21203
|
+
import path5 from "node:path";
|
|
20601
21204
|
|
|
20602
21205
|
// src/hooks/apply-patch/codec.ts
|
|
20603
21206
|
function normalizeLineEndings(text) {
|
|
@@ -21466,7 +22069,7 @@ function isMissingPathError(error) {
|
|
|
21466
22069
|
}
|
|
21467
22070
|
async function real(target) {
|
|
21468
22071
|
const parts = [];
|
|
21469
|
-
let current =
|
|
22072
|
+
let current = path5.resolve(target);
|
|
21470
22073
|
while (true) {
|
|
21471
22074
|
const exact = await fs3.realpath(current).catch((error) => {
|
|
21472
22075
|
if (isMissingPathError(error)) {
|
|
@@ -21475,19 +22078,19 @@ async function real(target) {
|
|
|
21475
22078
|
throw createApplyPatchInternalError(`Failed to resolve real path: ${current}`, error);
|
|
21476
22079
|
});
|
|
21477
22080
|
if (exact) {
|
|
21478
|
-
return parts.length === 0 ? exact :
|
|
22081
|
+
return parts.length === 0 ? exact : path5.join(exact, ...parts.reverse());
|
|
21479
22082
|
}
|
|
21480
|
-
const parent =
|
|
22083
|
+
const parent = path5.dirname(current);
|
|
21481
22084
|
if (parent === current) {
|
|
21482
|
-
return parts.length === 0 ? current :
|
|
22085
|
+
return parts.length === 0 ? current : path5.join(current, ...parts.reverse());
|
|
21483
22086
|
}
|
|
21484
|
-
parts.push(
|
|
22087
|
+
parts.push(path5.basename(current));
|
|
21485
22088
|
current = parent;
|
|
21486
22089
|
}
|
|
21487
22090
|
}
|
|
21488
22091
|
function inside(root, target) {
|
|
21489
|
-
const rel =
|
|
21490
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
22092
|
+
const rel = path5.relative(root, target);
|
|
22093
|
+
return rel === "" || !rel.startsWith("..") && !path5.isAbsolute(rel);
|
|
21491
22094
|
}
|
|
21492
22095
|
function createPathGuardContext(root, worktree) {
|
|
21493
22096
|
return {
|
|
@@ -21497,7 +22100,7 @@ function createPathGuardContext(root, worktree) {
|
|
|
21497
22100
|
};
|
|
21498
22101
|
}
|
|
21499
22102
|
async function realCached(ctx, target) {
|
|
21500
|
-
const resolvedTarget =
|
|
22103
|
+
const resolvedTarget = path5.resolve(target);
|
|
21501
22104
|
let pending = ctx.realCache.get(resolvedTarget);
|
|
21502
22105
|
if (!pending) {
|
|
21503
22106
|
pending = real(resolvedTarget);
|
|
@@ -21548,22 +22151,22 @@ async function assertRegularFile(ctx, filePath, verb) {
|
|
|
21548
22151
|
function collectPatchTargets(root, hunks) {
|
|
21549
22152
|
const targets = new Set;
|
|
21550
22153
|
for (const hunk of hunks) {
|
|
21551
|
-
targets.add(
|
|
22154
|
+
targets.add(path5.resolve(root, hunk.path));
|
|
21552
22155
|
if (hunk.type === "update" && hunk.move_path) {
|
|
21553
|
-
targets.add(
|
|
22156
|
+
targets.add(path5.resolve(root, hunk.move_path));
|
|
21554
22157
|
}
|
|
21555
22158
|
}
|
|
21556
22159
|
return [...targets];
|
|
21557
22160
|
}
|
|
21558
22161
|
function toRelativePatchPath(root, target) {
|
|
21559
|
-
const relative =
|
|
22162
|
+
const relative = path5.relative(root, target);
|
|
21560
22163
|
return (relative.length === 0 ? "." : relative).replaceAll("\\", "/");
|
|
21561
22164
|
}
|
|
21562
22165
|
function normalizePatchPath(root, value) {
|
|
21563
|
-
return
|
|
22166
|
+
return path5.isAbsolute(value) ? toRelativePatchPath(root, path5.resolve(value)) : value;
|
|
21564
22167
|
}
|
|
21565
22168
|
function normalizePatchPaths(root, hunks) {
|
|
21566
|
-
const resolvedRoot =
|
|
22169
|
+
const resolvedRoot = path5.resolve(root);
|
|
21567
22170
|
const normalized = [];
|
|
21568
22171
|
let changed = false;
|
|
21569
22172
|
for (const hunk of hunks) {
|
|
@@ -21687,7 +22290,7 @@ function stageAddedText(contents) {
|
|
|
21687
22290
|
`;
|
|
21688
22291
|
}
|
|
21689
22292
|
// src/hooks/apply-patch/rewrite.ts
|
|
21690
|
-
import
|
|
22293
|
+
import path6 from "node:path";
|
|
21691
22294
|
function normalizeTextLineEndings(text) {
|
|
21692
22295
|
return text.replace(/\r\n/g, `
|
|
21693
22296
|
`).replace(/\r/g, `
|
|
@@ -21844,7 +22447,7 @@ async function rewritePatch(root, patchText, cfg, worktree) {
|
|
|
21844
22447
|
const dependencyGroups = new Map;
|
|
21845
22448
|
for (const hunk of hunks) {
|
|
21846
22449
|
if (hunk.type === "add") {
|
|
21847
|
-
const filePath2 =
|
|
22450
|
+
const filePath2 = path6.resolve(root, hunk.path);
|
|
21848
22451
|
await assertPreparedPathMissing(filePath2, "add");
|
|
21849
22452
|
rewritten.push(hunk);
|
|
21850
22453
|
clearDependencyGroup(filePath2);
|
|
@@ -21866,20 +22469,20 @@ async function rewritePatch(root, patchText, cfg, worktree) {
|
|
|
21866
22469
|
continue;
|
|
21867
22470
|
}
|
|
21868
22471
|
if (hunk.type === "delete") {
|
|
21869
|
-
const filePath2 =
|
|
22472
|
+
const filePath2 = path6.resolve(root, hunk.path);
|
|
21870
22473
|
await getPreparedFileState(filePath2, "delete");
|
|
21871
22474
|
clearDependencyGroup(filePath2);
|
|
21872
22475
|
rewritten.push(hunk);
|
|
21873
22476
|
staged.set(filePath2, { exists: false, derived: true });
|
|
21874
22477
|
continue;
|
|
21875
22478
|
}
|
|
21876
|
-
const filePath =
|
|
22479
|
+
const filePath = path6.resolve(root, hunk.path);
|
|
21877
22480
|
const currentDependency = dependencyGroups.get(filePath);
|
|
21878
22481
|
const current = await getPreparedFileState(filePath, "update");
|
|
21879
22482
|
if (!current.exists) {
|
|
21880
22483
|
throw createApplyPatchVerificationError(`Failed to read file to update: ${filePath}`);
|
|
21881
22484
|
}
|
|
21882
|
-
const movePath = hunk.move_path ?
|
|
22485
|
+
const movePath = hunk.move_path ? path6.resolve(root, hunk.move_path) : undefined;
|
|
21883
22486
|
if (movePath && movePath !== filePath) {
|
|
21884
22487
|
await assertPreparedPathMissing(movePath, "move");
|
|
21885
22488
|
}
|
|
@@ -22045,31 +22648,34 @@ function createApplyPatchHook(ctx) {
|
|
|
22045
22648
|
};
|
|
22046
22649
|
}
|
|
22047
22650
|
// src/hooks/auto-update-checker/index.ts
|
|
22048
|
-
import * as
|
|
22651
|
+
import * as path11 from "node:path";
|
|
22652
|
+
init_compat();
|
|
22049
22653
|
|
|
22050
22654
|
// src/hooks/auto-update-checker/cache.ts
|
|
22051
22655
|
import * as fs5 from "node:fs";
|
|
22052
|
-
import * as
|
|
22656
|
+
import * as path9 from "node:path";
|
|
22657
|
+
// src/cli/system.ts
|
|
22658
|
+
init_compat();
|
|
22053
22659
|
// src/hooks/auto-update-checker/checker.ts
|
|
22054
22660
|
import * as fs4 from "node:fs";
|
|
22055
|
-
import * as
|
|
22661
|
+
import * as path8 from "node:path";
|
|
22056
22662
|
import { fileURLToPath } from "node:url";
|
|
22057
22663
|
|
|
22058
22664
|
// src/hooks/auto-update-checker/constants.ts
|
|
22059
22665
|
import * as os3 from "node:os";
|
|
22060
|
-
import * as
|
|
22666
|
+
import * as path7 from "node:path";
|
|
22061
22667
|
var PACKAGE_NAME = "oh-my-opencode-slim";
|
|
22062
22668
|
var NPM_REGISTRY_URL = `https://registry.npmjs.org/-/package/${PACKAGE_NAME}/dist-tags`;
|
|
22063
22669
|
var NPM_PACKAGE_URL = `https://registry.npmjs.org/${PACKAGE_NAME}`;
|
|
22064
22670
|
var NPM_FETCH_TIMEOUT = 5000;
|
|
22065
22671
|
function getCacheDir() {
|
|
22066
22672
|
if (process.platform === "win32") {
|
|
22067
|
-
return
|
|
22673
|
+
return path7.join(process.env.LOCALAPPDATA ?? os3.homedir(), "opencode");
|
|
22068
22674
|
}
|
|
22069
|
-
return
|
|
22675
|
+
return path7.join(os3.homedir(), ".cache", "opencode");
|
|
22070
22676
|
}
|
|
22071
22677
|
var CACHE_DIR = getCacheDir();
|
|
22072
|
-
var INSTALLED_PACKAGE_JSON =
|
|
22678
|
+
var INSTALLED_PACKAGE_JSON = path7.join(CACHE_DIR, "node_modules", PACKAGE_NAME, "package.json");
|
|
22073
22679
|
var configPaths = getOpenCodeConfigPaths();
|
|
22074
22680
|
var USER_OPENCODE_CONFIG = configPaths[0];
|
|
22075
22681
|
var USER_OPENCODE_CONFIG_JSONC = configPaths[1];
|
|
@@ -22181,8 +22787,8 @@ function extractChannel(version) {
|
|
|
22181
22787
|
}
|
|
22182
22788
|
function getConfigPaths(directory) {
|
|
22183
22789
|
return [
|
|
22184
|
-
|
|
22185
|
-
|
|
22790
|
+
path8.join(directory, ".opencode", "opencode.json"),
|
|
22791
|
+
path8.join(directory, ".opencode", "opencode.jsonc"),
|
|
22186
22792
|
USER_OPENCODE_CONFIG,
|
|
22187
22793
|
USER_OPENCODE_CONFIG_JSONC
|
|
22188
22794
|
];
|
|
@@ -22211,9 +22817,9 @@ function getLocalDevPath(directory) {
|
|
|
22211
22817
|
function findPackageJsonUp(startPath) {
|
|
22212
22818
|
try {
|
|
22213
22819
|
const stat2 = fs4.statSync(startPath);
|
|
22214
|
-
let dir = stat2.isDirectory() ? startPath :
|
|
22820
|
+
let dir = stat2.isDirectory() ? startPath : path8.dirname(startPath);
|
|
22215
22821
|
for (let i = 0;i < 10; i++) {
|
|
22216
|
-
const pkgPath =
|
|
22822
|
+
const pkgPath = path8.join(dir, "package.json");
|
|
22217
22823
|
if (fs4.existsSync(pkgPath)) {
|
|
22218
22824
|
try {
|
|
22219
22825
|
const content = fs4.readFileSync(pkgPath, "utf-8");
|
|
@@ -22222,7 +22828,7 @@ function findPackageJsonUp(startPath) {
|
|
|
22222
22828
|
return pkgPath;
|
|
22223
22829
|
} catch {}
|
|
22224
22830
|
}
|
|
22225
|
-
const parent =
|
|
22831
|
+
const parent = path8.dirname(dir);
|
|
22226
22832
|
if (parent === dir)
|
|
22227
22833
|
break;
|
|
22228
22834
|
dir = parent;
|
|
@@ -22247,7 +22853,7 @@ function getLocalDevVersion(directory) {
|
|
|
22247
22853
|
}
|
|
22248
22854
|
function getCurrentRuntimePackageJsonPath(currentModuleUrl = import.meta.url) {
|
|
22249
22855
|
try {
|
|
22250
|
-
const currentDir =
|
|
22856
|
+
const currentDir = path8.dirname(fileURLToPath(currentModuleUrl));
|
|
22251
22857
|
return findPackageJsonUp(currentDir);
|
|
22252
22858
|
} catch (err) {
|
|
22253
22859
|
log("[auto-update-checker] Failed to resolve runtime package path:", err);
|
|
@@ -22406,7 +23012,7 @@ function getBlockingMajorVersion(current, candidates) {
|
|
|
22406
23012
|
|
|
22407
23013
|
// src/hooks/auto-update-checker/cache.ts
|
|
22408
23014
|
function removeFromBunLock(installDir, packageName) {
|
|
22409
|
-
const lockPath =
|
|
23015
|
+
const lockPath = path9.join(installDir, "bun.lock");
|
|
22410
23016
|
if (!fs5.existsSync(lockPath))
|
|
22411
23017
|
return false;
|
|
22412
23018
|
try {
|
|
@@ -22457,7 +23063,7 @@ function ensureDependencyVersion(packageJsonPath, packageName, version) {
|
|
|
22457
23063
|
}
|
|
22458
23064
|
}
|
|
22459
23065
|
function removeInstalledPackage(installDir, packageName) {
|
|
22460
|
-
const pkgDir =
|
|
23066
|
+
const pkgDir = path9.join(installDir, "node_modules", packageName);
|
|
22461
23067
|
if (!fs5.existsSync(pkgDir))
|
|
22462
23068
|
return false;
|
|
22463
23069
|
fs5.rmSync(pkgDir, { recursive: true, force: true });
|
|
@@ -22466,18 +23072,18 @@ function removeInstalledPackage(installDir, packageName) {
|
|
|
22466
23072
|
}
|
|
22467
23073
|
function resolveInstallContext(runtimePackageJsonPath = getCurrentRuntimePackageJsonPath()) {
|
|
22468
23074
|
if (runtimePackageJsonPath) {
|
|
22469
|
-
const packageDir =
|
|
22470
|
-
const nodeModulesDir =
|
|
22471
|
-
if (
|
|
22472
|
-
const installDir =
|
|
22473
|
-
const packageJsonPath =
|
|
23075
|
+
const packageDir = path9.dirname(runtimePackageJsonPath);
|
|
23076
|
+
const nodeModulesDir = path9.dirname(packageDir);
|
|
23077
|
+
if (path9.basename(packageDir) === PACKAGE_NAME && path9.basename(nodeModulesDir) === "node_modules") {
|
|
23078
|
+
const installDir = path9.dirname(nodeModulesDir);
|
|
23079
|
+
const packageJsonPath = path9.join(installDir, "package.json");
|
|
22474
23080
|
if (fs5.existsSync(packageJsonPath)) {
|
|
22475
23081
|
return { installDir, packageJsonPath };
|
|
22476
23082
|
}
|
|
22477
23083
|
}
|
|
22478
23084
|
return null;
|
|
22479
23085
|
}
|
|
22480
|
-
const legacyPackageJsonPath =
|
|
23086
|
+
const legacyPackageJsonPath = path9.join(CACHE_DIR, "package.json");
|
|
22481
23087
|
if (fs5.existsSync(legacyPackageJsonPath)) {
|
|
22482
23088
|
return { installDir: CACHE_DIR, packageJsonPath: legacyPackageJsonPath };
|
|
22483
23089
|
}
|
|
@@ -22508,40 +23114,40 @@ function preparePackageUpdate(version, packageName = PACKAGE_NAME, runtimePackag
|
|
|
22508
23114
|
|
|
22509
23115
|
// src/hooks/auto-update-checker/skill-sync.ts
|
|
22510
23116
|
import {
|
|
22511
|
-
copyFileSync,
|
|
22512
|
-
existsSync as
|
|
23117
|
+
copyFileSync as copyFileSync2,
|
|
23118
|
+
existsSync as existsSync6,
|
|
22513
23119
|
lstatSync,
|
|
22514
|
-
mkdirSync as
|
|
22515
|
-
mkdtempSync,
|
|
23120
|
+
mkdirSync as mkdirSync4,
|
|
23121
|
+
mkdtempSync as mkdtempSync2,
|
|
22516
23122
|
readdirSync as readdirSync2,
|
|
22517
|
-
renameSync as
|
|
22518
|
-
rmSync as
|
|
23123
|
+
renameSync as renameSync3,
|
|
23124
|
+
rmSync as rmSync4
|
|
22519
23125
|
} from "node:fs";
|
|
22520
|
-
import * as
|
|
23126
|
+
import * as path10 from "node:path";
|
|
22521
23127
|
function copyDirRecursive(src, dest) {
|
|
22522
23128
|
const stat2 = lstatSync(src);
|
|
22523
23129
|
if (stat2.isSymbolicLink()) {
|
|
22524
23130
|
return;
|
|
22525
23131
|
}
|
|
22526
23132
|
if (stat2.isDirectory()) {
|
|
22527
|
-
|
|
23133
|
+
mkdirSync4(dest, { recursive: true });
|
|
22528
23134
|
const entries = readdirSync2(src);
|
|
22529
23135
|
for (const entry of entries) {
|
|
22530
|
-
copyDirRecursive(
|
|
23136
|
+
copyDirRecursive(path10.join(src, entry), path10.join(dest, entry));
|
|
22531
23137
|
}
|
|
22532
23138
|
} else if (stat2.isFile()) {
|
|
22533
|
-
const destDir =
|
|
22534
|
-
if (!
|
|
22535
|
-
|
|
23139
|
+
const destDir = path10.dirname(dest);
|
|
23140
|
+
if (!existsSync6(destDir)) {
|
|
23141
|
+
mkdirSync4(destDir, { recursive: true });
|
|
22536
23142
|
}
|
|
22537
|
-
|
|
23143
|
+
copyFileSync2(src, dest);
|
|
22538
23144
|
}
|
|
22539
23145
|
}
|
|
22540
23146
|
function syncBundledSkillsFromPackage(packageRoot) {
|
|
22541
23147
|
const installed = [];
|
|
22542
23148
|
const skippedExisting = [];
|
|
22543
23149
|
const failed = [];
|
|
22544
|
-
const sourceSkillsDir =
|
|
23150
|
+
const sourceSkillsDir = path10.join(packageRoot, "src", "skills");
|
|
22545
23151
|
try {
|
|
22546
23152
|
const stat2 = lstatSync(sourceSkillsDir);
|
|
22547
23153
|
if (stat2.isSymbolicLink() || !stat2.isDirectory()) {
|
|
@@ -22552,10 +23158,10 @@ function syncBundledSkillsFromPackage(packageRoot) {
|
|
|
22552
23158
|
log(`[skill-sync] Source skills directory does not exist or is unreadable: ${sourceSkillsDir}`);
|
|
22553
23159
|
return { installed, skippedExisting, failed };
|
|
22554
23160
|
}
|
|
22555
|
-
const destSkillsDir =
|
|
23161
|
+
const destSkillsDir = path10.join(getConfigDir(), "skills");
|
|
22556
23162
|
try {
|
|
22557
|
-
if (!
|
|
22558
|
-
|
|
23163
|
+
if (!existsSync6(destSkillsDir)) {
|
|
23164
|
+
mkdirSync4(destSkillsDir, { recursive: true });
|
|
22559
23165
|
}
|
|
22560
23166
|
} catch (err) {
|
|
22561
23167
|
log(`[skill-sync] Failed to create destination skills directory: ${destSkillsDir}`, err);
|
|
@@ -22568,7 +23174,7 @@ function syncBundledSkillsFromPackage(packageRoot) {
|
|
|
22568
23174
|
return { installed, skippedExisting, failed };
|
|
22569
23175
|
}
|
|
22570
23176
|
for (const entry of entries) {
|
|
22571
|
-
const entryPath =
|
|
23177
|
+
const entryPath = path10.join(sourceSkillsDir, entry);
|
|
22572
23178
|
try {
|
|
22573
23179
|
if (entry.startsWith(".")) {
|
|
22574
23180
|
continue;
|
|
@@ -22577,7 +23183,7 @@ function syncBundledSkillsFromPackage(packageRoot) {
|
|
|
22577
23183
|
if (entryStat.isSymbolicLink() || !entryStat.isDirectory()) {
|
|
22578
23184
|
continue;
|
|
22579
23185
|
}
|
|
22580
|
-
const skillMdPath =
|
|
23186
|
+
const skillMdPath = path10.join(entryPath, "SKILL.md");
|
|
22581
23187
|
try {
|
|
22582
23188
|
const skillMdStat = lstatSync(skillMdPath);
|
|
22583
23189
|
if (skillMdStat.isSymbolicLink() || !skillMdStat.isFile()) {
|
|
@@ -22586,7 +23192,7 @@ function syncBundledSkillsFromPackage(packageRoot) {
|
|
|
22586
23192
|
} catch {
|
|
22587
23193
|
continue;
|
|
22588
23194
|
}
|
|
22589
|
-
const destPath =
|
|
23195
|
+
const destPath = path10.join(destSkillsDir, entry);
|
|
22590
23196
|
let destExists = false;
|
|
22591
23197
|
try {
|
|
22592
23198
|
lstatSync(destPath);
|
|
@@ -22597,7 +23203,7 @@ function syncBundledSkillsFromPackage(packageRoot) {
|
|
|
22597
23203
|
skippedExisting.push(entry);
|
|
22598
23204
|
continue;
|
|
22599
23205
|
}
|
|
22600
|
-
const stagingDir =
|
|
23206
|
+
const stagingDir = mkdtempSync2(path10.join(destSkillsDir, `.sync-staging-${entry}-`));
|
|
22601
23207
|
try {
|
|
22602
23208
|
copyDirRecursive(entryPath, stagingDir);
|
|
22603
23209
|
let destExistsLate = false;
|
|
@@ -22609,7 +23215,7 @@ function syncBundledSkillsFromPackage(packageRoot) {
|
|
|
22609
23215
|
log(`[skill-sync] Destination path was created during staging for ${entry}, skipping promotion.`);
|
|
22610
23216
|
skippedExisting.push(entry);
|
|
22611
23217
|
} else {
|
|
22612
|
-
|
|
23218
|
+
renameSync3(stagingDir, destPath);
|
|
22613
23219
|
installed.push(entry);
|
|
22614
23220
|
log(`[skill-sync] Successfully synced skill: ${entry}`);
|
|
22615
23221
|
}
|
|
@@ -22618,8 +23224,8 @@ function syncBundledSkillsFromPackage(packageRoot) {
|
|
|
22618
23224
|
failed.push(entry);
|
|
22619
23225
|
} finally {
|
|
22620
23226
|
try {
|
|
22621
|
-
if (
|
|
22622
|
-
|
|
23227
|
+
if (existsSync6(stagingDir)) {
|
|
23228
|
+
rmSync4(stagingDir, { recursive: true, force: true });
|
|
22623
23229
|
}
|
|
22624
23230
|
} catch (err) {
|
|
22625
23231
|
log(`[skill-sync] Failed to clean up staging directory ${stagingDir}:`, err);
|
|
@@ -22635,7 +23241,7 @@ function syncBundledSkillsFromPackage(packageRoot) {
|
|
|
22635
23241
|
|
|
22636
23242
|
// src/hooks/auto-update-checker/index.ts
|
|
22637
23243
|
function createAutoUpdateCheckerHook(ctx, options = {}) {
|
|
22638
|
-
const { autoUpdate = true } = options;
|
|
23244
|
+
const { autoUpdate = true, companion } = options;
|
|
22639
23245
|
let hasChecked = false;
|
|
22640
23246
|
return {
|
|
22641
23247
|
event: ({ event }) => {
|
|
@@ -22653,14 +23259,14 @@ function createAutoUpdateCheckerHook(ctx, options = {}) {
|
|
|
22653
23259
|
log("[auto-update-checker] Local development mode");
|
|
22654
23260
|
return;
|
|
22655
23261
|
}
|
|
22656
|
-
runBackgroundUpdateCheck(ctx, autoUpdate).catch((err) => {
|
|
23262
|
+
runBackgroundUpdateCheck(ctx, autoUpdate, companion).catch((err) => {
|
|
22657
23263
|
log("[auto-update-checker] Background update check failed:", err);
|
|
22658
23264
|
});
|
|
22659
23265
|
}, 0);
|
|
22660
23266
|
}
|
|
22661
23267
|
};
|
|
22662
23268
|
}
|
|
22663
|
-
async function runBackgroundUpdateCheck(ctx, autoUpdate) {
|
|
23269
|
+
async function runBackgroundUpdateCheck(ctx, autoUpdate, companion) {
|
|
22664
23270
|
const pluginInfo = findPluginEntry(ctx.directory);
|
|
22665
23271
|
if (!pluginInfo) {
|
|
22666
23272
|
log("[auto-update-checker] Plugin not found in config");
|
|
@@ -22716,8 +23322,10 @@ Version is pinned. Update your plugin config to apply.`, "info", 8000);
|
|
|
22716
23322
|
const installSuccess = await runBunInstallSafe(installDir);
|
|
22717
23323
|
if (installSuccess) {
|
|
22718
23324
|
let installedSkills = [];
|
|
23325
|
+
let companionUpdated = false;
|
|
23326
|
+
let companionWillRetry = false;
|
|
23327
|
+
const packageRoot = path11.join(installDir, "node_modules", PACKAGE_NAME);
|
|
22719
23328
|
try {
|
|
22720
|
-
const packageRoot = path10.join(installDir, "node_modules", PACKAGE_NAME);
|
|
22721
23329
|
const syncResult = syncBundledSkillsFromPackage(packageRoot);
|
|
22722
23330
|
installedSkills = syncResult.installed;
|
|
22723
23331
|
if (syncResult.failed.length > 0) {
|
|
@@ -22729,14 +23337,38 @@ Version is pinned. Update your plugin config to apply.`, "info", 8000);
|
|
|
22729
23337
|
} catch (err) {
|
|
22730
23338
|
log("[auto-update-checker] Skill sync failed silently:", err);
|
|
22731
23339
|
}
|
|
22732
|
-
|
|
22733
|
-
|
|
23340
|
+
if (companion?.enabled === true) {
|
|
23341
|
+
try {
|
|
23342
|
+
const manifest = loadCompanionManifestFromPackageRoot(packageRoot);
|
|
23343
|
+
const companionResult = await ensureCompanionVersion({
|
|
23344
|
+
config: companion,
|
|
23345
|
+
manifest: manifest ?? undefined
|
|
23346
|
+
});
|
|
23347
|
+
if (companionResult.status === "installed") {
|
|
23348
|
+
companionUpdated = true;
|
|
23349
|
+
} else if (companionResult.status === "failed") {
|
|
23350
|
+
companionWillRetry = true;
|
|
23351
|
+
log("[auto-update-checker] Companion update failed; will retry on restart:", companionResult.error);
|
|
23352
|
+
} else if (companionResult.status === "skipped") {
|
|
23353
|
+
log("[auto-update-checker] Companion update skipped:", companionResult.reason);
|
|
23354
|
+
}
|
|
23355
|
+
} catch (err) {
|
|
23356
|
+
companionWillRetry = true;
|
|
23357
|
+
log("[auto-update-checker] Companion update failed silently; will retry on restart:", err);
|
|
23358
|
+
}
|
|
23359
|
+
}
|
|
23360
|
+
const messageLines = [`v${currentVersion} → v${latestVersion}`];
|
|
22734
23361
|
if (installedSkills.length > 0) {
|
|
22735
|
-
|
|
22736
|
-
|
|
22737
|
-
|
|
23362
|
+
messageLines.push(`Added bundled skills: ${installedSkills.join(", ")}`);
|
|
23363
|
+
}
|
|
23364
|
+
if (companionUpdated) {
|
|
23365
|
+
messageLines.push("Companion updated.");
|
|
23366
|
+
} else if (companionWillRetry) {
|
|
23367
|
+
messageLines.push("Companion update will retry on restart.");
|
|
22738
23368
|
}
|
|
22739
|
-
|
|
23369
|
+
messageLines.push("Restart OpenCode to apply.");
|
|
23370
|
+
showToast(ctx, "OMO-Slim Updated!", messageLines.join(`
|
|
23371
|
+
`), "success", 8000);
|
|
22740
23372
|
log(`[auto-update-checker] Update installed: ${currentVersion} → ${latestVersion}`);
|
|
22741
23373
|
} else {
|
|
22742
23374
|
showToast(ctx, `OMO-Slim ${latestVersion}`, `v${latestVersion} available, but auto-update failed to install it. Check logs or retry manually.`, "error", 8000);
|
|
@@ -23119,7 +23751,7 @@ class BackgroundJobBoard {
|
|
|
23119
23751
|
existing.set(file.path, { ...file });
|
|
23120
23752
|
}
|
|
23121
23753
|
}
|
|
23122
|
-
const contextFiles = [...existing.values()].filter((file) => file.lineCount >= this.readContextMinLines).sort((a, b) => b.lastReadAt - a.lastReadAt).slice(0, this.readContextMaxFiles + 1);
|
|
23754
|
+
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
23755
|
this.jobs.set(taskID, { ...job, contextFiles });
|
|
23124
23756
|
}
|
|
23125
23757
|
list(parentSessionID) {
|
|
@@ -23264,86 +23896,10 @@ function hasInternalInitiatorMarker(part) {
|
|
|
23264
23896
|
}
|
|
23265
23897
|
return part.text.includes(SLIM_INTERNAL_INITIATOR_MARKER);
|
|
23266
23898
|
}
|
|
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
|
-
}
|
|
23899
|
+
|
|
23900
|
+
// src/utils/index.ts
|
|
23901
|
+
init_zip_extractor();
|
|
23902
|
+
|
|
23347
23903
|
// src/hooks/chat-headers.ts
|
|
23348
23904
|
var INTERNAL_MARKER_CACHE_LIMIT = 1000;
|
|
23349
23905
|
var internalMarkerCache = new Map;
|
|
@@ -23848,17 +24404,17 @@ class ForegroundFallbackManager {
|
|
|
23848
24404
|
}
|
|
23849
24405
|
}
|
|
23850
24406
|
// src/hooks/image-hook.ts
|
|
23851
|
-
import { createHash } from "node:crypto";
|
|
24407
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
23852
24408
|
import {
|
|
23853
|
-
existsSync as
|
|
23854
|
-
mkdirSync as
|
|
24409
|
+
existsSync as existsSync7,
|
|
24410
|
+
mkdirSync as mkdirSync5,
|
|
23855
24411
|
readdirSync as readdirSync3,
|
|
23856
24412
|
rmdirSync,
|
|
23857
|
-
statSync as
|
|
24413
|
+
statSync as statSync4,
|
|
23858
24414
|
unlinkSync as unlinkSync2,
|
|
23859
|
-
writeFileSync as
|
|
24415
|
+
writeFileSync as writeFileSync5
|
|
23860
24416
|
} from "node:fs";
|
|
23861
|
-
import { basename as basename2, extname, join as
|
|
24417
|
+
import { basename as basename2, extname, join as join11 } from "node:path";
|
|
23862
24418
|
var lastCleanupByDir = new Map;
|
|
23863
24419
|
var CLEANUP_INTERVAL = 10 * 60 * 1000;
|
|
23864
24420
|
function isImagePart(p) {
|
|
@@ -23906,12 +24462,12 @@ function cleanupAllSessions(saveDir) {
|
|
|
23906
24462
|
const dirsToScan = [];
|
|
23907
24463
|
try {
|
|
23908
24464
|
for (const entry of readdirSync3(saveDir, { withFileTypes: true })) {
|
|
23909
|
-
const fp =
|
|
24465
|
+
const fp = join11(saveDir, entry.name);
|
|
23910
24466
|
if (entry.isDirectory()) {
|
|
23911
24467
|
dirsToScan.push(fp);
|
|
23912
24468
|
} else {
|
|
23913
24469
|
try {
|
|
23914
|
-
if (now -
|
|
24470
|
+
if (now - statSync4(fp).mtimeMs > maxAge)
|
|
23915
24471
|
unlinkSync2(fp);
|
|
23916
24472
|
} catch {}
|
|
23917
24473
|
}
|
|
@@ -23923,9 +24479,9 @@ function cleanupAllSessions(saveDir) {
|
|
|
23923
24479
|
let allRemoved = true;
|
|
23924
24480
|
for (const f of readdirSync3(dir)) {
|
|
23925
24481
|
isEmpty = false;
|
|
23926
|
-
const fp =
|
|
24482
|
+
const fp = join11(dir, f);
|
|
23927
24483
|
try {
|
|
23928
|
-
if (now -
|
|
24484
|
+
if (now - statSync4(fp).mtimeMs > maxAge) {
|
|
23929
24485
|
unlinkSync2(fp);
|
|
23930
24486
|
} else {
|
|
23931
24487
|
allRemoved = false;
|
|
@@ -23945,20 +24501,20 @@ function cleanupAllSessions(saveDir) {
|
|
|
23945
24501
|
function writeUniqueFile(dir, name, data, log2) {
|
|
23946
24502
|
const ext = extname(name);
|
|
23947
24503
|
const base = basename2(name, ext) || name;
|
|
23948
|
-
let candidate =
|
|
23949
|
-
if (
|
|
24504
|
+
let candidate = join11(dir, name);
|
|
24505
|
+
if (existsSync7(candidate)) {
|
|
23950
24506
|
return candidate;
|
|
23951
24507
|
}
|
|
23952
24508
|
let counter = 0;
|
|
23953
24509
|
const MAX_ATTEMPTS = 1000;
|
|
23954
24510
|
for (let attempt = 0;attempt < MAX_ATTEMPTS; attempt++) {
|
|
23955
24511
|
try {
|
|
23956
|
-
|
|
24512
|
+
writeFileSync5(candidate, data, { flag: "wx" });
|
|
23957
24513
|
return candidate;
|
|
23958
24514
|
} catch (e) {
|
|
23959
24515
|
if (e instanceof Error && e.code === "EEXIST") {
|
|
23960
24516
|
counter += 1;
|
|
23961
|
-
candidate =
|
|
24517
|
+
candidate = join11(dir, `${base}-${counter}${ext}`);
|
|
23962
24518
|
continue;
|
|
23963
24519
|
}
|
|
23964
24520
|
log2(`[image-hook] failed to save image: ${e}`);
|
|
@@ -23982,17 +24538,17 @@ function processImageAttachments(args) {
|
|
|
23982
24538
|
messagesWithImages.push({ msg, imageParts });
|
|
23983
24539
|
}
|
|
23984
24540
|
}
|
|
23985
|
-
const saveDir =
|
|
24541
|
+
const saveDir = join11(workDir, ".opencode", "images");
|
|
23986
24542
|
if (messagesWithImages.length === 0) {
|
|
23987
|
-
if (
|
|
24543
|
+
if (existsSync7(saveDir))
|
|
23988
24544
|
cleanupAllSessions(saveDir);
|
|
23989
24545
|
return;
|
|
23990
24546
|
}
|
|
23991
|
-
const gitignorePath =
|
|
24547
|
+
const gitignorePath = join11(workDir, ".opencode", ".gitignore");
|
|
23992
24548
|
try {
|
|
23993
|
-
|
|
23994
|
-
if (!
|
|
23995
|
-
|
|
24549
|
+
mkdirSync5(saveDir, { recursive: true });
|
|
24550
|
+
if (!existsSync7(gitignorePath))
|
|
24551
|
+
writeFileSync5(gitignorePath, `*
|
|
23996
24552
|
`);
|
|
23997
24553
|
} catch (e) {
|
|
23998
24554
|
log2(`[image-hook] failed to create image directory: ${e}`);
|
|
@@ -24000,9 +24556,9 @@ function processImageAttachments(args) {
|
|
|
24000
24556
|
cleanupAllSessions(saveDir);
|
|
24001
24557
|
for (const { msg, imageParts } of messagesWithImages) {
|
|
24002
24558
|
const sessionSubdir = msg.info.sessionID ? sanitizeFilename(msg.info.sessionID) : undefined;
|
|
24003
|
-
const targetDir = sessionSubdir ?
|
|
24559
|
+
const targetDir = sessionSubdir ? join11(saveDir, sessionSubdir) : saveDir;
|
|
24004
24560
|
try {
|
|
24005
|
-
|
|
24561
|
+
mkdirSync5(targetDir, { recursive: true });
|
|
24006
24562
|
} catch (e) {
|
|
24007
24563
|
log2(`[image-hook] failed to create target image directory: ${e}`);
|
|
24008
24564
|
}
|
|
@@ -24013,7 +24569,7 @@ function processImageAttachments(args) {
|
|
|
24013
24569
|
if (url) {
|
|
24014
24570
|
const decoded = decodeDataUrl(url);
|
|
24015
24571
|
if (decoded) {
|
|
24016
|
-
const hash =
|
|
24572
|
+
const hash = createHash2("sha1").update(decoded.data).digest("hex").slice(0, 8);
|
|
24017
24573
|
const sanitizedFilename = filename ? sanitizeFilename(filename) : undefined;
|
|
24018
24574
|
const baseName = sanitizedFilename ? sanitizedFilename.replace(/\.[^.]+$/, "") || "image" : "image";
|
|
24019
24575
|
const ext = sanitizedFilename ? extname(sanitizedFilename) || extFromMime(decoded.mime) : extFromMime(decoded.mime);
|
|
@@ -24204,18 +24760,7 @@ function createReflectCommandHook() {
|
|
|
24204
24760
|
};
|
|
24205
24761
|
}
|
|
24206
24762
|
// src/hooks/task-session-manager/index.ts
|
|
24207
|
-
import
|
|
24208
|
-
var AGENT_NAME_SET = new Set([
|
|
24209
|
-
"orchestrator",
|
|
24210
|
-
"oracle",
|
|
24211
|
-
"designer",
|
|
24212
|
-
"explorer",
|
|
24213
|
-
"librarian",
|
|
24214
|
-
"fixer",
|
|
24215
|
-
"observer",
|
|
24216
|
-
"council",
|
|
24217
|
-
"councillor"
|
|
24218
|
-
]);
|
|
24763
|
+
import path12 from "node:path";
|
|
24219
24764
|
var MAX_PENDING_TASK_CALLS = 100;
|
|
24220
24765
|
var BACKGROUND_JOB_BOARD_SENTINEL = "SENTINEL: background-job-board-v2";
|
|
24221
24766
|
var BACKGROUND_COMPLETION_COMPLETED = /^Background task completed: /;
|
|
@@ -24247,9 +24792,6 @@ function createOccurrenceId(part, message, partIndex) {
|
|
|
24247
24792
|
const hash = djb2Hash(`${sessionID}:${content}`);
|
|
24248
24793
|
return `anon:${hash}`;
|
|
24249
24794
|
}
|
|
24250
|
-
function isAgentName(value) {
|
|
24251
|
-
return typeof value === "string" && AGENT_NAME_SET.has(value);
|
|
24252
|
-
}
|
|
24253
24795
|
function extractPath(output) {
|
|
24254
24796
|
return /<path>([^<]+)<\/path>/.exec(output)?.[1];
|
|
24255
24797
|
}
|
|
@@ -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;
|
|
@@ -24530,16 +25072,17 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
24530
25072
|
if (!isRecord(output.args))
|
|
24531
25073
|
return;
|
|
24532
25074
|
const args = output.args;
|
|
24533
|
-
if (
|
|
25075
|
+
if (typeof args.subagent_type !== "string" || args.subagent_type.trim() === "") {
|
|
24534
25076
|
if (typeof args.task_id === "string" && args.task_id.trim() !== "") {
|
|
24535
25077
|
delete args.task_id;
|
|
24536
25078
|
}
|
|
24537
25079
|
return;
|
|
24538
25080
|
}
|
|
25081
|
+
const agentType = args.subagent_type.trim();
|
|
24539
25082
|
const label = deriveTaskSessionLabel({
|
|
24540
25083
|
description: typeof args.description === "string" ? args.description : undefined,
|
|
24541
25084
|
prompt: typeof args.prompt === "string" ? args.prompt : undefined,
|
|
24542
|
-
agentType
|
|
25085
|
+
agentType
|
|
24543
25086
|
});
|
|
24544
25087
|
const pendingCall = {
|
|
24545
25088
|
callId: pendingCallId({
|
|
@@ -24547,7 +25090,7 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
24547
25090
|
sessionID: input.sessionID
|
|
24548
25091
|
}),
|
|
24549
25092
|
parentSessionId: input.sessionID,
|
|
24550
|
-
agentType
|
|
25093
|
+
agentType,
|
|
24551
25094
|
label
|
|
24552
25095
|
};
|
|
24553
25096
|
rememberPendingCall(pendingCall);
|
|
@@ -24555,7 +25098,7 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
24555
25098
|
return;
|
|
24556
25099
|
}
|
|
24557
25100
|
const requested = args.task_id.trim();
|
|
24558
|
-
const remembered = backgroundJobBoard.resolveReusable(input.sessionID, requested,
|
|
25101
|
+
const remembered = backgroundJobBoard.resolveReusable(input.sessionID, requested, agentType);
|
|
24559
25102
|
if (!remembered) {
|
|
24560
25103
|
if (RAW_SESSION_ID_PATTERN.test(requested)) {
|
|
24561
25104
|
pendingCall.resumedTaskId = requested;
|
|
@@ -24825,7 +25368,7 @@ function formatCancelledTaskStatusOutput(taskID, summary = "cancelled") {
|
|
|
24825
25368
|
`);
|
|
24826
25369
|
}
|
|
24827
25370
|
// src/interview/manager.ts
|
|
24828
|
-
import
|
|
25371
|
+
import path16 from "node:path";
|
|
24829
25372
|
|
|
24830
25373
|
// src/interview/dashboard.ts
|
|
24831
25374
|
import crypto from "node:crypto";
|
|
@@ -24835,27 +25378,27 @@ import {
|
|
|
24835
25378
|
createServer
|
|
24836
25379
|
} from "node:http";
|
|
24837
25380
|
import os4 from "node:os";
|
|
24838
|
-
import
|
|
25381
|
+
import path14 from "node:path";
|
|
24839
25382
|
import { URL as URL2 } from "node:url";
|
|
24840
25383
|
|
|
24841
25384
|
// src/interview/document.ts
|
|
24842
25385
|
import * as fsSync from "node:fs";
|
|
24843
25386
|
import * as fs6 from "node:fs/promises";
|
|
24844
|
-
import * as
|
|
25387
|
+
import * as path13 from "node:path";
|
|
24845
25388
|
var DEFAULT_OUTPUT_FOLDER = "interview";
|
|
24846
25389
|
function normalizeOutputFolder(outputFolder) {
|
|
24847
25390
|
const normalized = outputFolder.trim().replace(/^\/+|\/+$/g, "");
|
|
24848
25391
|
return normalized || DEFAULT_OUTPUT_FOLDER;
|
|
24849
25392
|
}
|
|
24850
25393
|
function createInterviewDirectoryPath(directory, outputFolder) {
|
|
24851
|
-
return
|
|
25394
|
+
return path13.join(directory, normalizeOutputFolder(outputFolder));
|
|
24852
25395
|
}
|
|
24853
25396
|
function createInterviewFilePath(directory, outputFolder, idea) {
|
|
24854
25397
|
const fileName = `${slugify(idea) || "interview"}.md`;
|
|
24855
|
-
return
|
|
25398
|
+
return path13.join(createInterviewDirectoryPath(directory, outputFolder), fileName);
|
|
24856
25399
|
}
|
|
24857
25400
|
function relativeInterviewPath(directory, filePath) {
|
|
24858
|
-
return
|
|
25401
|
+
return path13.relative(directory, filePath) || path13.basename(filePath);
|
|
24859
25402
|
}
|
|
24860
25403
|
function resolveExistingInterviewPath(directory, outputFolder, value) {
|
|
24861
25404
|
const trimmed = value.trim();
|
|
@@ -24864,22 +25407,22 @@ function resolveExistingInterviewPath(directory, outputFolder, value) {
|
|
|
24864
25407
|
}
|
|
24865
25408
|
const outputDir = createInterviewDirectoryPath(directory, outputFolder);
|
|
24866
25409
|
const candidates = new Set;
|
|
24867
|
-
const resolvedRoot =
|
|
24868
|
-
if (
|
|
25410
|
+
const resolvedRoot = path13.resolve(directory);
|
|
25411
|
+
if (path13.isAbsolute(trimmed)) {
|
|
24869
25412
|
candidates.add(trimmed);
|
|
24870
25413
|
} else {
|
|
24871
|
-
candidates.add(
|
|
24872
|
-
candidates.add(
|
|
25414
|
+
candidates.add(path13.resolve(directory, trimmed));
|
|
25415
|
+
candidates.add(path13.join(outputDir, trimmed));
|
|
24873
25416
|
if (!trimmed.endsWith(".md")) {
|
|
24874
|
-
candidates.add(
|
|
25417
|
+
candidates.add(path13.join(outputDir, `${trimmed}.md`));
|
|
24875
25418
|
}
|
|
24876
25419
|
}
|
|
24877
25420
|
for (const candidate of candidates) {
|
|
24878
|
-
if (
|
|
25421
|
+
if (path13.extname(candidate) !== ".md") {
|
|
24879
25422
|
continue;
|
|
24880
25423
|
}
|
|
24881
|
-
const resolved =
|
|
24882
|
-
if (!resolved.startsWith(resolvedRoot +
|
|
25424
|
+
const resolved = path13.resolve(candidate);
|
|
25425
|
+
if (!resolved.startsWith(resolvedRoot + path13.sep) && resolved !== resolvedRoot) {
|
|
24883
25426
|
continue;
|
|
24884
25427
|
}
|
|
24885
25428
|
if (fsSync.existsSync(candidate)) {
|
|
@@ -24959,7 +25502,7 @@ function parseFrontmatter(content) {
|
|
|
24959
25502
|
return result;
|
|
24960
25503
|
}
|
|
24961
25504
|
async function ensureInterviewFile(record) {
|
|
24962
|
-
await fs6.mkdir(
|
|
25505
|
+
await fs6.mkdir(path13.dirname(record.markdownPath), { recursive: true });
|
|
24963
25506
|
try {
|
|
24964
25507
|
await fs6.access(record.markdownPath);
|
|
24965
25508
|
} catch {
|
|
@@ -26629,12 +27172,12 @@ function renderInterviewPage(interviewId, resumeSlug) {
|
|
|
26629
27172
|
|
|
26630
27173
|
// src/interview/dashboard.ts
|
|
26631
27174
|
function getAuthFilePath(port) {
|
|
26632
|
-
const dataHome = process.env.XDG_DATA_HOME ||
|
|
26633
|
-
return
|
|
27175
|
+
const dataHome = process.env.XDG_DATA_HOME || path14.join(os4.homedir(), ".local", "share");
|
|
27176
|
+
return path14.join(dataHome, "opencode", `.dashboard-${port}.json`);
|
|
26634
27177
|
}
|
|
26635
27178
|
function writeAuthFile(port, token) {
|
|
26636
27179
|
const filePath = getAuthFilePath(port);
|
|
26637
|
-
const dir =
|
|
27180
|
+
const dir = path14.dirname(filePath);
|
|
26638
27181
|
try {
|
|
26639
27182
|
fsSync2.mkdirSync(dir, { recursive: true });
|
|
26640
27183
|
} catch {}
|
|
@@ -26771,7 +27314,7 @@ function createDashboardServer(config) {
|
|
|
26771
27314
|
const directories = getKnownDirectories();
|
|
26772
27315
|
const items = [];
|
|
26773
27316
|
for (const dir of directories) {
|
|
26774
|
-
const interviewDir =
|
|
27317
|
+
const interviewDir = path14.join(dir, config.outputFolder);
|
|
26775
27318
|
let entries;
|
|
26776
27319
|
try {
|
|
26777
27320
|
entries = await fs7.readdir(interviewDir);
|
|
@@ -26783,7 +27326,7 @@ function createDashboardServer(config) {
|
|
|
26783
27326
|
continue;
|
|
26784
27327
|
let content;
|
|
26785
27328
|
try {
|
|
26786
|
-
content = await fs7.readFile(
|
|
27329
|
+
content = await fs7.readFile(path14.join(interviewDir, entry), "utf8");
|
|
26787
27330
|
} catch {
|
|
26788
27331
|
continue;
|
|
26789
27332
|
}
|
|
@@ -26809,7 +27352,7 @@ function createDashboardServer(config) {
|
|
|
26809
27352
|
const directories = getKnownDirectories();
|
|
26810
27353
|
let rebuilt = 0;
|
|
26811
27354
|
for (const dir of directories) {
|
|
26812
|
-
const interviewDir =
|
|
27355
|
+
const interviewDir = path14.join(dir, config.outputFolder);
|
|
26813
27356
|
let entries;
|
|
26814
27357
|
try {
|
|
26815
27358
|
entries = await fs7.readdir(interviewDir);
|
|
@@ -26821,7 +27364,7 @@ function createDashboardServer(config) {
|
|
|
26821
27364
|
continue;
|
|
26822
27365
|
let content;
|
|
26823
27366
|
try {
|
|
26824
|
-
content = await fs7.readFile(
|
|
27367
|
+
content = await fs7.readFile(path14.join(interviewDir, entry), "utf8");
|
|
26825
27368
|
} catch {
|
|
26826
27369
|
continue;
|
|
26827
27370
|
}
|
|
@@ -26847,7 +27390,7 @@ function createDashboardServer(config) {
|
|
|
26847
27390
|
questions: [],
|
|
26848
27391
|
pendingAnswers: null,
|
|
26849
27392
|
lastUpdatedAt: fm.updatedAt ? new Date(fm.updatedAt).getTime() : Date.now(),
|
|
26850
|
-
filePath:
|
|
27393
|
+
filePath: path14.join(interviewDir, entry),
|
|
26851
27394
|
nudgeAction: null
|
|
26852
27395
|
});
|
|
26853
27396
|
if (!sessions.has(fm.sessionID)) {
|
|
@@ -27111,7 +27654,7 @@ function createDashboardServer(config) {
|
|
|
27111
27654
|
const dirs = getKnownDirectories();
|
|
27112
27655
|
for (const dir of dirs) {
|
|
27113
27656
|
const slug = extractResumeSlug(interviewId);
|
|
27114
|
-
const candidate =
|
|
27657
|
+
const candidate = path14.join(dir, config.outputFolder, `${slug}.md`);
|
|
27115
27658
|
try {
|
|
27116
27659
|
document = await fs7.readFile(candidate, "utf8");
|
|
27117
27660
|
markdownPath = candidate;
|
|
@@ -27639,7 +28182,7 @@ function createInterviewServer(deps) {
|
|
|
27639
28182
|
// src/interview/service.ts
|
|
27640
28183
|
import { spawn as spawn2 } from "node:child_process";
|
|
27641
28184
|
import * as fs8 from "node:fs/promises";
|
|
27642
|
-
import * as
|
|
28185
|
+
import * as path15 from "node:path";
|
|
27643
28186
|
|
|
27644
28187
|
// src/interview/types.ts
|
|
27645
28188
|
import { z as z3 } from "zod";
|
|
@@ -27827,13 +28370,13 @@ function shouldAutoOpenBrowser(config, env) {
|
|
|
27827
28370
|
return requested && !isAutomatedRuntime(env);
|
|
27828
28371
|
}
|
|
27829
28372
|
function openBrowser(url) {
|
|
27830
|
-
const
|
|
28373
|
+
const platform3 = process.platform;
|
|
27831
28374
|
let command;
|
|
27832
28375
|
let args;
|
|
27833
|
-
if (
|
|
28376
|
+
if (platform3 === "darwin") {
|
|
27834
28377
|
command = "open";
|
|
27835
28378
|
args = [url];
|
|
27836
|
-
} else if (
|
|
28379
|
+
} else if (platform3 === "win32") {
|
|
27837
28380
|
command = "cmd";
|
|
27838
28381
|
args = ["/c", "start", "", url];
|
|
27839
28382
|
} else {
|
|
@@ -27906,12 +28449,12 @@ function createInterviewService(ctx, config, deps) {
|
|
|
27906
28449
|
if (!newSlug) {
|
|
27907
28450
|
return;
|
|
27908
28451
|
}
|
|
27909
|
-
const currentFileName =
|
|
28452
|
+
const currentFileName = path15.basename(interview.markdownPath, ".md");
|
|
27910
28453
|
if (currentFileName === newSlug) {
|
|
27911
28454
|
return;
|
|
27912
28455
|
}
|
|
27913
|
-
const dir =
|
|
27914
|
-
const newPath =
|
|
28456
|
+
const dir = path15.dirname(interview.markdownPath);
|
|
28457
|
+
const newPath = path15.join(dir, `${newSlug}.md`);
|
|
27915
28458
|
try {
|
|
27916
28459
|
await fs8.access(newPath);
|
|
27917
28460
|
return;
|
|
@@ -27987,9 +28530,9 @@ function createInterviewService(ctx, config, deps) {
|
|
|
27987
28530
|
const messages = await loadMessages(sessionID);
|
|
27988
28531
|
const title = extractTitle(document);
|
|
27989
28532
|
const record = {
|
|
27990
|
-
id: `${Date.now()}-${++idCounter}-${slugify(
|
|
28533
|
+
id: `${Date.now()}-${++idCounter}-${slugify(path15.basename(markdownPath, ".md")) || "interview"}`,
|
|
27991
28534
|
sessionID,
|
|
27992
|
-
idea: title ||
|
|
28535
|
+
idea: title || path15.basename(markdownPath, ".md"),
|
|
27993
28536
|
markdownPath,
|
|
27994
28537
|
createdAt: nowIso(),
|
|
27995
28538
|
status: "active",
|
|
@@ -28216,7 +28759,7 @@ function createInterviewService(ctx, config, deps) {
|
|
|
28216
28759
|
return fileCache.items;
|
|
28217
28760
|
}
|
|
28218
28761
|
const outputDir = createInterviewDirectoryPath(ctx.directory, outputFolder);
|
|
28219
|
-
const activePaths = new Set([...interviewsById.values()].filter((i) => i.status === "active").map((i) =>
|
|
28762
|
+
const activePaths = new Set([...interviewsById.values()].filter((i) => i.status === "active").map((i) => path15.resolve(i.markdownPath)));
|
|
28220
28763
|
let entries;
|
|
28221
28764
|
try {
|
|
28222
28765
|
entries = await fs8.readdir(outputDir);
|
|
@@ -28227,8 +28770,8 @@ function createInterviewService(ctx, config, deps) {
|
|
|
28227
28770
|
for (const entry of entries) {
|
|
28228
28771
|
if (!entry.endsWith(".md"))
|
|
28229
28772
|
continue;
|
|
28230
|
-
const fullPath =
|
|
28231
|
-
if (activePaths.has(
|
|
28773
|
+
const fullPath = path15.join(outputDir, entry);
|
|
28774
|
+
if (activePaths.has(path15.resolve(fullPath)))
|
|
28232
28775
|
continue;
|
|
28233
28776
|
let content;
|
|
28234
28777
|
try {
|
|
@@ -28327,7 +28870,7 @@ function createInterviewManager(ctx, config) {
|
|
|
28327
28870
|
const outputFolder = interviewConfig?.outputFolder ?? "interview";
|
|
28328
28871
|
if (!dashboardEnabled) {
|
|
28329
28872
|
const service2 = createInterviewService(ctx, interviewConfig);
|
|
28330
|
-
const resolvedOutputPath =
|
|
28873
|
+
const resolvedOutputPath = path16.join(ctx.directory, outputFolder);
|
|
28331
28874
|
const server = createInterviewServer({
|
|
28332
28875
|
getState: async (interviewId) => service2.getInterviewState(interviewId),
|
|
28333
28876
|
listInterviewFiles: async () => service2.listInterviewFiles(),
|
|
@@ -28432,7 +28975,7 @@ function createInterviewManager(ctx, config) {
|
|
|
28432
28975
|
listInterviews: () => service.listInterviews(),
|
|
28433
28976
|
submitAnswers: async (interviewId, answers) => service.submitAnswers(interviewId, answers),
|
|
28434
28977
|
handleNudgeAction: async (interviewId, action) => service.handleNudgeAction(interviewId, action),
|
|
28435
|
-
outputFolder:
|
|
28978
|
+
outputFolder: path16.join(ctx.directory, outputFolder),
|
|
28436
28979
|
port: 0
|
|
28437
28980
|
});
|
|
28438
28981
|
service.setBaseUrlResolver(() => perSessionServer.ensureStarted());
|
|
@@ -28700,6 +29243,7 @@ function createBuiltinMcps(disabledMcps = [], websearchConfig) {
|
|
|
28700
29243
|
}
|
|
28701
29244
|
|
|
28702
29245
|
// src/multiplexer/tmux/index.ts
|
|
29246
|
+
init_compat();
|
|
28703
29247
|
var TMUX_LAYOUT_DEBOUNCE_MS = 150;
|
|
28704
29248
|
|
|
28705
29249
|
class TmuxMultiplexer {
|
|
@@ -28917,23 +29461,23 @@ class TmuxMultiplexer {
|
|
|
28917
29461
|
return null;
|
|
28918
29462
|
}
|
|
28919
29463
|
const stdout = await proc.stdout();
|
|
28920
|
-
const
|
|
29464
|
+
const path17 = stdout.trim().split(`
|
|
28921
29465
|
`)[0];
|
|
28922
|
-
if (!
|
|
29466
|
+
if (!path17) {
|
|
28923
29467
|
log("[tmux] findBinary: no path in output");
|
|
28924
29468
|
return null;
|
|
28925
29469
|
}
|
|
28926
|
-
const verifyProc = crossSpawn([
|
|
29470
|
+
const verifyProc = crossSpawn([path17, "-V"], {
|
|
28927
29471
|
stdout: "pipe",
|
|
28928
29472
|
stderr: "pipe"
|
|
28929
29473
|
});
|
|
28930
29474
|
const verifyExit = await verifyProc.exited;
|
|
28931
29475
|
if (verifyExit !== 0) {
|
|
28932
|
-
log("[tmux] findBinary: tmux -V failed", { path:
|
|
29476
|
+
log("[tmux] findBinary: tmux -V failed", { path: path17, verifyExit });
|
|
28933
29477
|
return null;
|
|
28934
29478
|
}
|
|
28935
|
-
log("[tmux] findBinary: found", { path:
|
|
28936
|
-
return
|
|
29479
|
+
log("[tmux] findBinary: found", { path: path17 });
|
|
29480
|
+
return path17;
|
|
28937
29481
|
} catch (err) {
|
|
28938
29482
|
log("[tmux] findBinary: exception", { error: String(err) });
|
|
28939
29483
|
return null;
|
|
@@ -28945,6 +29489,8 @@ function quoteShellArg(value) {
|
|
|
28945
29489
|
}
|
|
28946
29490
|
|
|
28947
29491
|
// src/multiplexer/zellij/index.ts
|
|
29492
|
+
init_compat();
|
|
29493
|
+
|
|
28948
29494
|
class ZellijMultiplexer {
|
|
28949
29495
|
paneMode;
|
|
28950
29496
|
type = "zellij";
|
|
@@ -29884,22 +30430,359 @@ async function isServerRunning(serverUrl, timeoutMs = 3000, maxAttempts = 2) {
|
|
|
29884
30430
|
}
|
|
29885
30431
|
return false;
|
|
29886
30432
|
}
|
|
29887
|
-
// src/tools/
|
|
30433
|
+
// src/tools/acp-run.ts
|
|
30434
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
30435
|
+
import { createInterface } from "node:readline";
|
|
29888
30436
|
import { tool } from "@opencode-ai/plugin";
|
|
30437
|
+
var z4 = tool.schema;
|
|
30438
|
+
|
|
30439
|
+
class AcpClient {
|
|
30440
|
+
name;
|
|
30441
|
+
config;
|
|
30442
|
+
cwd;
|
|
30443
|
+
ask;
|
|
30444
|
+
child;
|
|
30445
|
+
next = 1;
|
|
30446
|
+
pending = new Map;
|
|
30447
|
+
chunks = [];
|
|
30448
|
+
errors = [];
|
|
30449
|
+
sessionId;
|
|
30450
|
+
lastUpdate = Date.now();
|
|
30451
|
+
authMethods = [];
|
|
30452
|
+
active = false;
|
|
30453
|
+
activeRequests = 0;
|
|
30454
|
+
constructor(name, config, cwd, ask) {
|
|
30455
|
+
this.name = name;
|
|
30456
|
+
this.config = config;
|
|
30457
|
+
this.cwd = cwd;
|
|
30458
|
+
this.ask = ask;
|
|
30459
|
+
this.child = spawn3(config.command, config.args, {
|
|
30460
|
+
cwd,
|
|
30461
|
+
env: { ...process.env, ...config.env },
|
|
30462
|
+
stdio: "pipe"
|
|
30463
|
+
});
|
|
30464
|
+
this.child.stderr.on("data", (chunk) => {
|
|
30465
|
+
this.errors.push(String(chunk));
|
|
30466
|
+
});
|
|
30467
|
+
this.child.stdin.on("error", (error) => {
|
|
30468
|
+
this.errors.push(String(error));
|
|
30469
|
+
this.rejectPending(error);
|
|
30470
|
+
});
|
|
30471
|
+
this.child.on("error", (error) => {
|
|
30472
|
+
this.rejectPending(error);
|
|
30473
|
+
});
|
|
30474
|
+
this.child.on("exit", (code, signal) => {
|
|
30475
|
+
if (this.pending.size === 0)
|
|
30476
|
+
return;
|
|
30477
|
+
this.rejectPending(new Error(`ACP agent '${name}' exited before replying (code ${code ?? "null"}, signal ${signal ?? "null"})`));
|
|
30478
|
+
});
|
|
30479
|
+
createInterface({ input: this.child.stdout }).on("line", (line) => {
|
|
30480
|
+
this.receive(line).catch((error) => {
|
|
30481
|
+
this.errors.push(String(error));
|
|
30482
|
+
});
|
|
30483
|
+
});
|
|
30484
|
+
}
|
|
30485
|
+
async run(prompt) {
|
|
30486
|
+
const init = await this.request("initialize", {
|
|
30487
|
+
protocolVersion: 1,
|
|
30488
|
+
clientCapabilities: {},
|
|
30489
|
+
clientInfo: {
|
|
30490
|
+
name: "oh-my-opencode-slim",
|
|
30491
|
+
title: "oh-my-opencode-slim ACP bridge"
|
|
30492
|
+
}
|
|
30493
|
+
});
|
|
30494
|
+
this.authMethods = readAuthMethods(init);
|
|
30495
|
+
const created = await this.newSession();
|
|
30496
|
+
const sessionId = readSessionId(created);
|
|
30497
|
+
this.sessionId = sessionId;
|
|
30498
|
+
this.active = true;
|
|
30499
|
+
await this.request("session/prompt", {
|
|
30500
|
+
sessionId,
|
|
30501
|
+
prompt: [{ type: "text", text: prompt }]
|
|
30502
|
+
});
|
|
30503
|
+
await this.drain();
|
|
30504
|
+
this.active = false;
|
|
30505
|
+
return this.output();
|
|
30506
|
+
}
|
|
30507
|
+
async newSession() {
|
|
30508
|
+
try {
|
|
30509
|
+
return await this.request("session/new", {
|
|
30510
|
+
cwd: this.cwd,
|
|
30511
|
+
mcpServers: []
|
|
30512
|
+
});
|
|
30513
|
+
} catch (error) {
|
|
30514
|
+
if (!isAuthError(error) || this.authMethods.length === 0)
|
|
30515
|
+
throw error;
|
|
30516
|
+
const method = this.authMethods[0];
|
|
30517
|
+
if (typeof method.id !== "string")
|
|
30518
|
+
throw error;
|
|
30519
|
+
await this.request("authenticate", { methodId: method.id });
|
|
30520
|
+
return await this.request("session/new", {
|
|
30521
|
+
cwd: this.cwd,
|
|
30522
|
+
mcpServers: []
|
|
30523
|
+
});
|
|
30524
|
+
}
|
|
30525
|
+
}
|
|
30526
|
+
close() {
|
|
30527
|
+
if (this.active && this.sessionId && !this.child.killed) {
|
|
30528
|
+
this.notify("session/cancel", { sessionId: this.sessionId });
|
|
30529
|
+
}
|
|
30530
|
+
if (!this.child.killed)
|
|
30531
|
+
this.child.kill("SIGTERM");
|
|
30532
|
+
}
|
|
30533
|
+
request(method, params) {
|
|
30534
|
+
const id = this.next++;
|
|
30535
|
+
const payload = { jsonrpc: "2.0", id, method, params };
|
|
30536
|
+
return new Promise((resolve3, reject) => {
|
|
30537
|
+
this.pending.set(id, { resolve: resolve3, reject });
|
|
30538
|
+
this.child.stdin.write(`${JSON.stringify(payload)}
|
|
30539
|
+
`, (error) => {
|
|
30540
|
+
if (!error)
|
|
30541
|
+
return;
|
|
30542
|
+
this.pending.delete(id);
|
|
30543
|
+
reject(error);
|
|
30544
|
+
});
|
|
30545
|
+
});
|
|
30546
|
+
}
|
|
30547
|
+
notify(method, params) {
|
|
30548
|
+
this.child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}
|
|
30549
|
+
`);
|
|
30550
|
+
}
|
|
30551
|
+
async drain() {
|
|
30552
|
+
this.lastUpdate = Date.now();
|
|
30553
|
+
while (this.activeRequests > 0 || Date.now() - this.lastUpdate < 100) {
|
|
30554
|
+
await new Promise((resolve3) => setTimeout(resolve3, 25));
|
|
30555
|
+
}
|
|
30556
|
+
}
|
|
30557
|
+
async receive(line) {
|
|
30558
|
+
if (!line.trim())
|
|
30559
|
+
return;
|
|
30560
|
+
let message;
|
|
30561
|
+
try {
|
|
30562
|
+
message = JSON.parse(line);
|
|
30563
|
+
} catch {
|
|
30564
|
+
const error = new Error(`ACP agent '${this.name}' wrote non-JSON stdout: ${line.slice(0, 200)}`);
|
|
30565
|
+
this.errors.push(error.message);
|
|
30566
|
+
this.rejectPending(error);
|
|
30567
|
+
this.close();
|
|
30568
|
+
return;
|
|
30569
|
+
}
|
|
30570
|
+
if ("id" in message && (("result" in message) || ("error" in message))) {
|
|
30571
|
+
const pending = this.pending.get(message.id);
|
|
30572
|
+
if (!pending)
|
|
30573
|
+
return;
|
|
30574
|
+
this.pending.delete(message.id);
|
|
30575
|
+
if (message.error) {
|
|
30576
|
+
pending.reject(rpcError(message.error));
|
|
30577
|
+
return;
|
|
30578
|
+
}
|
|
30579
|
+
pending.resolve(message.result);
|
|
30580
|
+
return;
|
|
30581
|
+
}
|
|
30582
|
+
if ("id" in message && "method" in message) {
|
|
30583
|
+
this.activeRequests++;
|
|
30584
|
+
try {
|
|
30585
|
+
await this.handleRequest(message);
|
|
30586
|
+
} finally {
|
|
30587
|
+
this.activeRequests--;
|
|
30588
|
+
this.lastUpdate = Date.now();
|
|
30589
|
+
}
|
|
30590
|
+
return;
|
|
30591
|
+
}
|
|
30592
|
+
if ("method" in message)
|
|
30593
|
+
this.handleNotification(message);
|
|
30594
|
+
}
|
|
30595
|
+
rejectPending(error) {
|
|
30596
|
+
for (const item of this.pending.values())
|
|
30597
|
+
item.reject(error);
|
|
30598
|
+
this.pending.clear();
|
|
30599
|
+
}
|
|
30600
|
+
async handleRequest(message) {
|
|
30601
|
+
if (message.method === "session/request_permission") {
|
|
30602
|
+
const title = readPermissionTitle(message.params);
|
|
30603
|
+
try {
|
|
30604
|
+
if (this.config.permissionMode === "ask") {
|
|
30605
|
+
await this.ask(title, message.params ?? {});
|
|
30606
|
+
}
|
|
30607
|
+
const optionId = selectPermissionOption(message.params, this.config.permissionMode);
|
|
30608
|
+
if (!optionId)
|
|
30609
|
+
throw new Error("ACP permission request had no usable option");
|
|
30610
|
+
this.reply(message.id, {
|
|
30611
|
+
outcome: { outcome: "selected", optionId }
|
|
30612
|
+
});
|
|
30613
|
+
} catch {
|
|
30614
|
+
const optionId = selectPermissionOption(message.params, "reject");
|
|
30615
|
+
if (optionId) {
|
|
30616
|
+
this.reply(message.id, {
|
|
30617
|
+
outcome: { outcome: "selected", optionId }
|
|
30618
|
+
});
|
|
30619
|
+
return;
|
|
30620
|
+
}
|
|
30621
|
+
this.reply(message.id, { outcome: { outcome: "cancelled" } });
|
|
30622
|
+
}
|
|
30623
|
+
return;
|
|
30624
|
+
}
|
|
30625
|
+
this.replyError(message.id, `Unsupported ACP client method: ${message.method}`);
|
|
30626
|
+
}
|
|
30627
|
+
handleNotification(message) {
|
|
30628
|
+
if (message.method !== "session/update")
|
|
30629
|
+
return;
|
|
30630
|
+
this.lastUpdate = Date.now();
|
|
30631
|
+
const update = message.params?.update;
|
|
30632
|
+
if (!isRecord2(update))
|
|
30633
|
+
return;
|
|
30634
|
+
collectText(update, this.chunks);
|
|
30635
|
+
}
|
|
30636
|
+
reply(id, result) {
|
|
30637
|
+
this.child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, result })}
|
|
30638
|
+
`);
|
|
30639
|
+
}
|
|
30640
|
+
replyError(id, message) {
|
|
30641
|
+
this.child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, error: { code: -32601, message } })}
|
|
30642
|
+
`);
|
|
30643
|
+
}
|
|
30644
|
+
output() {
|
|
30645
|
+
const text = this.chunks.join("").trim();
|
|
30646
|
+
if (text)
|
|
30647
|
+
return text;
|
|
30648
|
+
const err = this.errors.join("").trim();
|
|
30649
|
+
return err ? `ACP agent '${this.name}' completed without text output. stderr:
|
|
30650
|
+
${err}` : `ACP agent '${this.name}' completed without text output.`;
|
|
30651
|
+
}
|
|
30652
|
+
}
|
|
30653
|
+
function createAcpRunTool(agents = {}) {
|
|
30654
|
+
return tool({
|
|
30655
|
+
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.",
|
|
30656
|
+
args: {
|
|
30657
|
+
agent: z4.string().describe("Configured ACP agent name"),
|
|
30658
|
+
prompt: z4.string().describe("Task or question to send to the ACP agent"),
|
|
30659
|
+
cwd: z4.string().optional().describe("Optional absolute working directory override"),
|
|
30660
|
+
timeout_ms: z4.number().int().min(1000).max(900000).optional().describe("Optional timeout override in milliseconds")
|
|
30661
|
+
},
|
|
30662
|
+
async execute(args, ctx) {
|
|
30663
|
+
if (ctx.agent !== args.agent) {
|
|
30664
|
+
throw new Error(`acp_run for '${args.agent}' can only be used by @${args.agent}`);
|
|
30665
|
+
}
|
|
30666
|
+
const config = agents[args.agent];
|
|
30667
|
+
if (!config) {
|
|
30668
|
+
throw new Error(`Unknown ACP agent '${args.agent}'. Configured agents: ${Object.keys(agents).join(", ") || "(none)"}`);
|
|
30669
|
+
}
|
|
30670
|
+
const cwd = args.cwd ?? config.cwd ?? ctx.directory;
|
|
30671
|
+
if (!cwd)
|
|
30672
|
+
throw new Error("acp_run requires a working directory");
|
|
30673
|
+
await ctx.ask({
|
|
30674
|
+
permission: "bash",
|
|
30675
|
+
patterns: [`${config.command} ${config.args.join(" ")}`.trim()],
|
|
30676
|
+
always: [],
|
|
30677
|
+
metadata: {
|
|
30678
|
+
agent: args.agent,
|
|
30679
|
+
cwd,
|
|
30680
|
+
command: config.command,
|
|
30681
|
+
args: config.args
|
|
30682
|
+
}
|
|
30683
|
+
});
|
|
30684
|
+
const client = new AcpClient(args.agent, config, cwd, async (title, metadata) => {
|
|
30685
|
+
if (config.permissionMode === "reject")
|
|
30686
|
+
return;
|
|
30687
|
+
await ctx.ask({
|
|
30688
|
+
permission: "bash",
|
|
30689
|
+
patterns: [`acp:${args.agent}:${title}`],
|
|
30690
|
+
always: [],
|
|
30691
|
+
metadata
|
|
30692
|
+
});
|
|
30693
|
+
});
|
|
30694
|
+
const timeoutMs = args.timeout_ms ?? config.timeoutMs;
|
|
30695
|
+
let timer;
|
|
30696
|
+
const timeout = new Promise((_, reject) => timer = setTimeout(() => reject(new Error(`ACP agent '${args.agent}' timed out after ${timeoutMs}ms`)), timeoutMs));
|
|
30697
|
+
const abort = () => client.close();
|
|
30698
|
+
ctx.abort.addEventListener("abort", abort, { once: true });
|
|
30699
|
+
try {
|
|
30700
|
+
return await Promise.race([client.run(args.prompt), timeout]);
|
|
30701
|
+
} finally {
|
|
30702
|
+
if (timer)
|
|
30703
|
+
clearTimeout(timer);
|
|
30704
|
+
ctx.abort.removeEventListener("abort", abort);
|
|
30705
|
+
client.close();
|
|
30706
|
+
}
|
|
30707
|
+
}
|
|
30708
|
+
});
|
|
30709
|
+
}
|
|
30710
|
+
function readSessionId(value) {
|
|
30711
|
+
if (!isRecord2(value) || typeof value.sessionId !== "string") {
|
|
30712
|
+
throw new Error("ACP agent did not return a sessionId");
|
|
30713
|
+
}
|
|
30714
|
+
return value.sessionId;
|
|
30715
|
+
}
|
|
30716
|
+
function readAuthMethods(value) {
|
|
30717
|
+
if (!isRecord2(value) || !Array.isArray(value.authMethods))
|
|
30718
|
+
return [];
|
|
30719
|
+
const methods = value.authMethods;
|
|
30720
|
+
return methods.filter(isRecord2);
|
|
30721
|
+
}
|
|
30722
|
+
function rpcError(error) {
|
|
30723
|
+
const err = new Error(error.message ?? "ACP request failed");
|
|
30724
|
+
err.code = error.code;
|
|
30725
|
+
err.data = error.data;
|
|
30726
|
+
return err;
|
|
30727
|
+
}
|
|
30728
|
+
function isAuthError(error) {
|
|
30729
|
+
if (!(error instanceof Error))
|
|
30730
|
+
return false;
|
|
30731
|
+
const meta = error;
|
|
30732
|
+
return meta.code === -32001 || error.message.toLowerCase().includes("auth_required") || error.message.toLowerCase().includes("auth required");
|
|
30733
|
+
}
|
|
30734
|
+
function isRecord2(value) {
|
|
30735
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
30736
|
+
}
|
|
30737
|
+
function readPermissionTitle(params) {
|
|
30738
|
+
const tool2 = isRecord2(params?.toolCall) ? params.toolCall : undefined;
|
|
30739
|
+
if (typeof tool2?.title === "string")
|
|
30740
|
+
return tool2.title;
|
|
30741
|
+
if (typeof params?.permission === "string")
|
|
30742
|
+
return params.permission;
|
|
30743
|
+
return "ACP permission request";
|
|
30744
|
+
}
|
|
30745
|
+
function selectPermissionOption(params, mode) {
|
|
30746
|
+
const options = Array.isArray(params?.options) ? params.options : [];
|
|
30747
|
+
const choices = options.filter(isRecord2).filter((item) => typeof item.optionId === "string");
|
|
30748
|
+
const reject = choices.find((item) => typeof item.kind === "string" && item.kind.startsWith("reject"));
|
|
30749
|
+
if (mode === "reject")
|
|
30750
|
+
return reject?.optionId;
|
|
30751
|
+
const allow = choices.find((item) => typeof item.kind === "string" && item.kind.startsWith("allow"));
|
|
30752
|
+
return allow?.optionId ?? reject?.optionId;
|
|
30753
|
+
}
|
|
30754
|
+
function collectText(update, chunks) {
|
|
30755
|
+
if (update.sessionUpdate !== "agent_message_chunk")
|
|
30756
|
+
return;
|
|
30757
|
+
const text = readText(update.delta) ?? readText(update.content);
|
|
30758
|
+
if (text)
|
|
30759
|
+
chunks.push(text);
|
|
30760
|
+
}
|
|
30761
|
+
function readText(value) {
|
|
30762
|
+
if (typeof value === "string")
|
|
30763
|
+
return value;
|
|
30764
|
+
if (isRecord2(value) && typeof value.text === "string")
|
|
30765
|
+
return value.text;
|
|
30766
|
+
return;
|
|
30767
|
+
}
|
|
30768
|
+
// src/tools/ast-grep/tools.ts
|
|
30769
|
+
import { tool as tool2 } from "@opencode-ai/plugin";
|
|
29889
30770
|
|
|
29890
30771
|
// src/tools/ast-grep/cli.ts
|
|
29891
|
-
|
|
30772
|
+
init_compat();
|
|
30773
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
29892
30774
|
|
|
29893
30775
|
// src/tools/ast-grep/constants.ts
|
|
29894
|
-
import { existsSync as
|
|
30776
|
+
import { existsSync as existsSync10, statSync as statSync5 } from "node:fs";
|
|
29895
30777
|
import { createRequire as createRequire3 } from "node:module";
|
|
29896
|
-
import { dirname as
|
|
30778
|
+
import { dirname as dirname9, join as join15 } from "node:path";
|
|
29897
30779
|
|
|
29898
30780
|
// src/tools/ast-grep/downloader.ts
|
|
29899
|
-
import { chmodSync, existsSync as
|
|
30781
|
+
import { chmodSync as chmodSync2, existsSync as existsSync9, mkdirSync as mkdirSync7, unlinkSync as unlinkSync4 } from "node:fs";
|
|
29900
30782
|
import { createRequire as createRequire2 } from "node:module";
|
|
29901
|
-
import { homedir as
|
|
29902
|
-
import { join as
|
|
30783
|
+
import { homedir as homedir6 } from "node:os";
|
|
30784
|
+
import { join as join14 } from "node:path";
|
|
30785
|
+
init_compat();
|
|
29903
30786
|
var REPO = "ast-grep/ast-grep";
|
|
29904
30787
|
var DEFAULT_VERSION = "0.40.0";
|
|
29905
30788
|
function getAstGrepVersion() {
|
|
@@ -29923,19 +30806,19 @@ var PLATFORM_MAP = {
|
|
|
29923
30806
|
function getCacheDir2() {
|
|
29924
30807
|
if (process.platform === "win32") {
|
|
29925
30808
|
const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
|
|
29926
|
-
const base2 = localAppData ||
|
|
29927
|
-
return
|
|
30809
|
+
const base2 = localAppData || join14(homedir6(), "AppData", "Local");
|
|
30810
|
+
return join14(base2, "oh-my-opencode-slim", "bin");
|
|
29928
30811
|
}
|
|
29929
30812
|
const xdgCache = process.env.XDG_CACHE_HOME;
|
|
29930
|
-
const base = xdgCache ||
|
|
29931
|
-
return
|
|
30813
|
+
const base = xdgCache || join14(homedir6(), ".cache");
|
|
30814
|
+
return join14(base, "oh-my-opencode-slim", "bin");
|
|
29932
30815
|
}
|
|
29933
30816
|
function getBinaryName() {
|
|
29934
30817
|
return process.platform === "win32" ? "sg.exe" : "sg";
|
|
29935
30818
|
}
|
|
29936
30819
|
function getCachedBinaryPath() {
|
|
29937
|
-
const
|
|
29938
|
-
return
|
|
30820
|
+
const binaryPath = join14(getCacheDir2(), getBinaryName());
|
|
30821
|
+
return existsSync9(binaryPath) ? binaryPath : null;
|
|
29939
30822
|
}
|
|
29940
30823
|
async function downloadAstGrep(version = DEFAULT_VERSION) {
|
|
29941
30824
|
const platformKey = `${process.platform}-${process.arch}`;
|
|
@@ -29946,34 +30829,34 @@ async function downloadAstGrep(version = DEFAULT_VERSION) {
|
|
|
29946
30829
|
}
|
|
29947
30830
|
const cacheDir = getCacheDir2();
|
|
29948
30831
|
const binaryName = getBinaryName();
|
|
29949
|
-
const
|
|
29950
|
-
if (
|
|
29951
|
-
return
|
|
30832
|
+
const binaryPath = join14(cacheDir, binaryName);
|
|
30833
|
+
if (existsSync9(binaryPath)) {
|
|
30834
|
+
return binaryPath;
|
|
29952
30835
|
}
|
|
29953
30836
|
const { arch, os: os5 } = platformInfo;
|
|
29954
30837
|
const assetName = `app-${arch}-${os5}.zip`;
|
|
29955
30838
|
const downloadUrl = `https://github.com/${REPO}/releases/download/${version}/${assetName}`;
|
|
29956
30839
|
console.log(`[oh-my-opencode-slim] Downloading ast-grep binary...`);
|
|
29957
30840
|
try {
|
|
29958
|
-
if (!
|
|
29959
|
-
|
|
30841
|
+
if (!existsSync9(cacheDir)) {
|
|
30842
|
+
mkdirSync7(cacheDir, { recursive: true });
|
|
29960
30843
|
}
|
|
29961
30844
|
const response = await fetch(downloadUrl, { redirect: "follow" });
|
|
29962
30845
|
if (!response.ok) {
|
|
29963
30846
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
29964
30847
|
}
|
|
29965
|
-
const archivePath =
|
|
30848
|
+
const archivePath = join14(cacheDir, assetName);
|
|
29966
30849
|
const arrayBuffer = await response.arrayBuffer();
|
|
29967
30850
|
await crossWrite(archivePath, arrayBuffer);
|
|
29968
30851
|
await extractZip(archivePath, cacheDir);
|
|
29969
|
-
if (
|
|
30852
|
+
if (existsSync9(archivePath)) {
|
|
29970
30853
|
unlinkSync4(archivePath);
|
|
29971
30854
|
}
|
|
29972
|
-
if (process.platform !== "win32" &&
|
|
29973
|
-
|
|
30855
|
+
if (process.platform !== "win32" && existsSync9(binaryPath)) {
|
|
30856
|
+
chmodSync2(binaryPath, 493);
|
|
29974
30857
|
}
|
|
29975
30858
|
console.log(`[oh-my-opencode-slim] ast-grep binary ready.`);
|
|
29976
|
-
return
|
|
30859
|
+
return binaryPath;
|
|
29977
30860
|
} catch (err) {
|
|
29978
30861
|
console.error(`[oh-my-opencode-slim] Failed to download ast-grep: ${err instanceof Error ? err.message : err}`);
|
|
29979
30862
|
return null;
|
|
@@ -30021,13 +30904,13 @@ var CLI_LANGUAGES = [
|
|
|
30021
30904
|
var MIN_BINARY_SIZE = 1e4;
|
|
30022
30905
|
function isValidBinary(filePath) {
|
|
30023
30906
|
try {
|
|
30024
|
-
return
|
|
30907
|
+
return statSync5(filePath).size > MIN_BINARY_SIZE;
|
|
30025
30908
|
} catch {
|
|
30026
30909
|
return false;
|
|
30027
30910
|
}
|
|
30028
30911
|
}
|
|
30029
30912
|
function getPlatformPackageName() {
|
|
30030
|
-
const
|
|
30913
|
+
const platform3 = process.platform;
|
|
30031
30914
|
const arch = process.arch;
|
|
30032
30915
|
const platformMap = {
|
|
30033
30916
|
"darwin-arm64": "@ast-grep/cli-darwin-arm64",
|
|
@@ -30038,7 +30921,7 @@ function getPlatformPackageName() {
|
|
|
30038
30921
|
"win32-arm64": "@ast-grep/cli-win32-arm64-msvc",
|
|
30039
30922
|
"win32-ia32": "@ast-grep/cli-win32-ia32-msvc"
|
|
30040
30923
|
};
|
|
30041
|
-
return platformMap[`${
|
|
30924
|
+
return platformMap[`${platform3}-${arch}`] ?? null;
|
|
30042
30925
|
}
|
|
30043
30926
|
var resolvedCliPath = null;
|
|
30044
30927
|
function findSgCliPathSync() {
|
|
@@ -30050,9 +30933,9 @@ function findSgCliPathSync() {
|
|
|
30050
30933
|
try {
|
|
30051
30934
|
const require2 = createRequire3(import.meta.url);
|
|
30052
30935
|
const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
|
|
30053
|
-
const cliDir =
|
|
30054
|
-
const sgPath =
|
|
30055
|
-
if (
|
|
30936
|
+
const cliDir = dirname9(cliPkgPath);
|
|
30937
|
+
const sgPath = join15(cliDir, binaryName);
|
|
30938
|
+
if (existsSync10(sgPath) && isValidBinary(sgPath)) {
|
|
30056
30939
|
return sgPath;
|
|
30057
30940
|
}
|
|
30058
30941
|
} catch {}
|
|
@@ -30061,19 +30944,19 @@ function findSgCliPathSync() {
|
|
|
30061
30944
|
try {
|
|
30062
30945
|
const require2 = createRequire3(import.meta.url);
|
|
30063
30946
|
const pkgPath = require2.resolve(`${platformPkg}/package.json`);
|
|
30064
|
-
const pkgDir =
|
|
30947
|
+
const pkgDir = dirname9(pkgPath);
|
|
30065
30948
|
const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
|
|
30066
|
-
const
|
|
30067
|
-
if (
|
|
30068
|
-
return
|
|
30949
|
+
const binaryPath = join15(pkgDir, astGrepName);
|
|
30950
|
+
if (existsSync10(binaryPath) && isValidBinary(binaryPath)) {
|
|
30951
|
+
return binaryPath;
|
|
30069
30952
|
}
|
|
30070
30953
|
} catch {}
|
|
30071
30954
|
}
|
|
30072
30955
|
if (process.platform === "darwin") {
|
|
30073
30956
|
const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
|
|
30074
|
-
for (const
|
|
30075
|
-
if (
|
|
30076
|
-
return
|
|
30957
|
+
for (const path17 of homebrewPaths) {
|
|
30958
|
+
if (existsSync10(path17) && isValidBinary(path17)) {
|
|
30959
|
+
return path17;
|
|
30077
30960
|
}
|
|
30078
30961
|
}
|
|
30079
30962
|
}
|
|
@@ -30090,8 +30973,8 @@ function getSgCliPath() {
|
|
|
30090
30973
|
}
|
|
30091
30974
|
return "sg";
|
|
30092
30975
|
}
|
|
30093
|
-
function setSgCliPath(
|
|
30094
|
-
resolvedCliPath =
|
|
30976
|
+
function setSgCliPath(path17) {
|
|
30977
|
+
resolvedCliPath = path17;
|
|
30095
30978
|
}
|
|
30096
30979
|
var DEFAULT_TIMEOUT_MS = 300000;
|
|
30097
30980
|
var DEFAULT_MAX_OUTPUT_BYTES = 1 * 1024 * 1024;
|
|
@@ -30101,7 +30984,7 @@ var DEFAULT_MAX_MATCHES = 500;
|
|
|
30101
30984
|
var initPromise = null;
|
|
30102
30985
|
async function getAstGrepPath() {
|
|
30103
30986
|
const currentPath = getSgCliPath();
|
|
30104
|
-
if (currentPath !== "sg" &&
|
|
30987
|
+
if (currentPath !== "sg" && existsSync11(currentPath)) {
|
|
30105
30988
|
return currentPath;
|
|
30106
30989
|
}
|
|
30107
30990
|
if (initPromise) {
|
|
@@ -30109,7 +30992,7 @@ async function getAstGrepPath() {
|
|
|
30109
30992
|
}
|
|
30110
30993
|
initPromise = (async () => {
|
|
30111
30994
|
const syncPath = findSgCliPathSync();
|
|
30112
|
-
if (syncPath &&
|
|
30995
|
+
if (syncPath && existsSync11(syncPath)) {
|
|
30113
30996
|
setSgCliPath(syncPath);
|
|
30114
30997
|
return syncPath;
|
|
30115
30998
|
}
|
|
@@ -30148,7 +31031,7 @@ async function runSg(options) {
|
|
|
30148
31031
|
const paths2 = options.paths && options.paths.length > 0 ? options.paths : ["."];
|
|
30149
31032
|
args.push(...paths2);
|
|
30150
31033
|
let cliPath = getSgCliPath();
|
|
30151
|
-
if (!
|
|
31034
|
+
if (!existsSync11(cliPath) && cliPath !== "sg") {
|
|
30152
31035
|
const downloadedPath = await getAstGrepPath();
|
|
30153
31036
|
if (downloadedPath) {
|
|
30154
31037
|
cliPath = downloadedPath;
|
|
@@ -30364,14 +31247,14 @@ function showOutputToUser(context, output) {
|
|
|
30364
31247
|
const ctx = context;
|
|
30365
31248
|
ctx.metadata?.({ metadata: { output } });
|
|
30366
31249
|
}
|
|
30367
|
-
var ast_grep_search =
|
|
31250
|
+
var ast_grep_search = tool2({
|
|
30368
31251
|
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
31252
|
args: {
|
|
30370
|
-
pattern:
|
|
30371
|
-
lang:
|
|
30372
|
-
paths:
|
|
30373
|
-
globs:
|
|
30374
|
-
context:
|
|
31253
|
+
pattern: tool2.schema.string().describe("AST pattern with meta-variables ($VAR, $$$). Must be complete AST node."),
|
|
31254
|
+
lang: tool2.schema.enum(CLI_LANGUAGES).describe("Target language"),
|
|
31255
|
+
paths: tool2.schema.array(tool2.schema.string()).optional().describe("Paths to search (default: ['.'])"),
|
|
31256
|
+
globs: tool2.schema.array(tool2.schema.string()).optional().describe("Include/exclude globs (prefix ! to exclude)"),
|
|
31257
|
+
context: tool2.schema.number().optional().describe("Context lines around match")
|
|
30375
31258
|
},
|
|
30376
31259
|
execute: async (args, context) => {
|
|
30377
31260
|
try {
|
|
@@ -30400,15 +31283,15 @@ ${hint}`;
|
|
|
30400
31283
|
}
|
|
30401
31284
|
}
|
|
30402
31285
|
});
|
|
30403
|
-
var ast_grep_replace =
|
|
31286
|
+
var ast_grep_replace = tool2({
|
|
30404
31287
|
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
31288
|
args: {
|
|
30406
|
-
pattern:
|
|
30407
|
-
rewrite:
|
|
30408
|
-
lang:
|
|
30409
|
-
paths:
|
|
30410
|
-
globs:
|
|
30411
|
-
dryRun:
|
|
31289
|
+
pattern: tool2.schema.string().describe("AST pattern to match"),
|
|
31290
|
+
rewrite: tool2.schema.string().describe("Replacement pattern (can use $VAR from pattern)"),
|
|
31291
|
+
lang: tool2.schema.enum(CLI_LANGUAGES).describe("Target language"),
|
|
31292
|
+
paths: tool2.schema.array(tool2.schema.string()).optional().describe("Paths to search"),
|
|
31293
|
+
globs: tool2.schema.array(tool2.schema.string()).optional().describe("Include/exclude globs"),
|
|
31294
|
+
dryRun: tool2.schema.boolean().optional().describe("Preview changes without applying (default: true)")
|
|
30412
31295
|
},
|
|
30413
31296
|
execute: async (args, context) => {
|
|
30414
31297
|
try {
|
|
@@ -30432,20 +31315,20 @@ var ast_grep_replace = tool({
|
|
|
30432
31315
|
});
|
|
30433
31316
|
// src/tools/cancel-task.ts
|
|
30434
31317
|
import {
|
|
30435
|
-
tool as
|
|
31318
|
+
tool as tool3
|
|
30436
31319
|
} from "@opencode-ai/plugin";
|
|
30437
|
-
var
|
|
31320
|
+
var z5 = tool3.schema;
|
|
30438
31321
|
|
|
30439
31322
|
class SessionStillRunningError extends Error {
|
|
30440
31323
|
}
|
|
30441
31324
|
function createCancelTaskTool(options) {
|
|
30442
|
-
const cancel_task =
|
|
31325
|
+
const cancel_task = tool3({
|
|
30443
31326
|
description: `Cancel a tracked background specialist task.
|
|
30444
31327
|
|
|
30445
31328
|
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
31329
|
args: {
|
|
30447
|
-
task_id:
|
|
30448
|
-
reason:
|
|
31330
|
+
task_id: z5.string().describe("Tracked background task ID or Background Job Board alias"),
|
|
31331
|
+
reason: z5.string().optional().describe("Short cancellation reason")
|
|
30449
31332
|
},
|
|
30450
31333
|
async execute(args, toolContext) {
|
|
30451
31334
|
const parentSessionID = toolContext?.sessionID;
|
|
@@ -30658,7 +31541,7 @@ async function abortAndVerifySession(options, taskID) {
|
|
|
30658
31541
|
attempts,
|
|
30659
31542
|
status: statusSnapshot.status
|
|
30660
31543
|
});
|
|
30661
|
-
await
|
|
31544
|
+
await delay2(retryIntervalMs);
|
|
30662
31545
|
continue;
|
|
30663
31546
|
}
|
|
30664
31547
|
stableStoppedSince ??= Date.now();
|
|
@@ -30671,7 +31554,7 @@ async function abortAndVerifySession(options, taskID) {
|
|
|
30671
31554
|
});
|
|
30672
31555
|
return;
|
|
30673
31556
|
}
|
|
30674
|
-
await
|
|
31557
|
+
await delay2(retryIntervalMs);
|
|
30675
31558
|
}
|
|
30676
31559
|
log("[cancel-task] abort verification timed out", {
|
|
30677
31560
|
taskID,
|
|
@@ -30739,13 +31622,13 @@ async function deleteAndVerifySession(options, taskID, reason) {
|
|
|
30739
31622
|
});
|
|
30740
31623
|
if (status.status === "busy" || status.status === "retry") {
|
|
30741
31624
|
stableStoppedSince = undefined;
|
|
30742
|
-
await
|
|
31625
|
+
await delay2(retryIntervalMs);
|
|
30743
31626
|
continue;
|
|
30744
31627
|
}
|
|
30745
31628
|
stableStoppedSince ??= Date.now();
|
|
30746
31629
|
if (Date.now() - stableStoppedSince >= stableStoppedMs)
|
|
30747
31630
|
return;
|
|
30748
|
-
await
|
|
31631
|
+
await delay2(retryIntervalMs);
|
|
30749
31632
|
}
|
|
30750
31633
|
throw new SessionStillRunningError(`Session delete returned but task did not stay stopped: ${taskID} (${lastStatus ?? "unknown"})`);
|
|
30751
31634
|
}
|
|
@@ -30784,7 +31667,7 @@ async function getSessionStatus(client, taskID) {
|
|
|
30784
31667
|
return { status: undefined, source: "lookup-error", keys: [] };
|
|
30785
31668
|
}
|
|
30786
31669
|
}
|
|
30787
|
-
function
|
|
31670
|
+
function delay2(ms) {
|
|
30788
31671
|
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
30789
31672
|
}
|
|
30790
31673
|
function isSessionID(value) {
|
|
@@ -30826,9 +31709,9 @@ function unknownTaskOutput(taskID, message) {
|
|
|
30826
31709
|
}
|
|
30827
31710
|
// src/tools/council.ts
|
|
30828
31711
|
import {
|
|
30829
|
-
tool as
|
|
31712
|
+
tool as tool4
|
|
30830
31713
|
} from "@opencode-ai/plugin";
|
|
30831
|
-
var
|
|
31714
|
+
var z6 = tool4.schema;
|
|
30832
31715
|
function formatModelComposition(councillorResults) {
|
|
30833
31716
|
return councillorResults.map((cr) => {
|
|
30834
31717
|
const shortModel = shortModelLabel(cr.model);
|
|
@@ -30836,15 +31719,15 @@ function formatModelComposition(councillorResults) {
|
|
|
30836
31719
|
}).join(", ");
|
|
30837
31720
|
}
|
|
30838
31721
|
function createCouncilTool(_ctx, councilManager) {
|
|
30839
|
-
const council_session =
|
|
31722
|
+
const council_session = tool4({
|
|
30840
31723
|
description: `Launch a multi-LLM council session for consensus-based analysis.
|
|
30841
31724
|
|
|
30842
31725
|
Sends the prompt to multiple models (councillors) in parallel and returns their formatted responses for you to synthesize.
|
|
30843
31726
|
|
|
30844
31727
|
Returns the councillor responses with a summary footer.`,
|
|
30845
31728
|
args: {
|
|
30846
|
-
prompt:
|
|
30847
|
-
preset:
|
|
31729
|
+
prompt: z6.string().describe("The prompt to send to all councillors"),
|
|
31730
|
+
preset: z6.string().optional().describe('Council preset to use (default: "default"). Must match a preset in the council config.')
|
|
30848
31731
|
},
|
|
30849
31732
|
async execute(args, toolContext) {
|
|
30850
31733
|
if (!toolContext || typeof toolContext !== "object" || !("sessionID" in toolContext)) {
|
|
@@ -30896,14 +31779,14 @@ import * as fs10 from "node:fs";
|
|
|
30896
31779
|
// src/tui-state.ts
|
|
30897
31780
|
import * as fs9 from "node:fs";
|
|
30898
31781
|
import * as os5 from "node:os";
|
|
30899
|
-
import * as
|
|
31782
|
+
import * as path17 from "node:path";
|
|
30900
31783
|
var STATE_DIR = "oh-my-opencode-slim";
|
|
30901
31784
|
var STATE_FILE = "tui-state.json";
|
|
30902
31785
|
function dataDir() {
|
|
30903
|
-
return process.env.XDG_DATA_HOME ??
|
|
31786
|
+
return process.env.XDG_DATA_HOME ?? path17.join(os5.homedir(), ".local", "share");
|
|
30904
31787
|
}
|
|
30905
31788
|
function getTuiStatePath() {
|
|
30906
|
-
return
|
|
31789
|
+
return path17.join(dataDir(), "opencode", "storage", STATE_DIR, STATE_FILE);
|
|
30907
31790
|
}
|
|
30908
31791
|
function emptySnapshot() {
|
|
30909
31792
|
return {
|
|
@@ -30939,7 +31822,7 @@ async function readTuiSnapshotAsync() {
|
|
|
30939
31822
|
function writeTuiSnapshot(snapshot) {
|
|
30940
31823
|
try {
|
|
30941
31824
|
const filePath = getTuiStatePath();
|
|
30942
|
-
fs9.mkdirSync(
|
|
31825
|
+
fs9.mkdirSync(path17.dirname(filePath), { recursive: true });
|
|
30943
31826
|
fs9.writeFileSync(filePath, `${JSON.stringify(snapshot)}
|
|
30944
31827
|
`);
|
|
30945
31828
|
} catch {}
|
|
@@ -31139,14 +32022,14 @@ var BINARY_PREFIXES = [
|
|
|
31139
32022
|
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
32023
|
// src/tools/smartfetch/tool.ts
|
|
31141
32024
|
import os6 from "node:os";
|
|
31142
|
-
import
|
|
32025
|
+
import path21 from "node:path";
|
|
31143
32026
|
import {
|
|
31144
|
-
tool as
|
|
32027
|
+
tool as tool5
|
|
31145
32028
|
} from "@opencode-ai/plugin";
|
|
31146
32029
|
|
|
31147
32030
|
// src/tools/smartfetch/binary.ts
|
|
31148
32031
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
|
|
31149
|
-
import
|
|
32032
|
+
import path18 from "node:path";
|
|
31150
32033
|
function extensionForMime(contentType) {
|
|
31151
32034
|
const mime = contentType.split(";")[0]?.trim().toLowerCase();
|
|
31152
32035
|
const map = {
|
|
@@ -31167,10 +32050,10 @@ function buildBinaryResultMessage(fetchResult, savedPath) {
|
|
|
31167
32050
|
async function saveBinary(binaryDir, data, contentType, filename) {
|
|
31168
32051
|
await mkdir2(binaryDir, { recursive: true });
|
|
31169
32052
|
const initialName = filename || `webfetch-${Date.now()}.${extensionForMime(contentType)}`;
|
|
31170
|
-
const parsed =
|
|
32053
|
+
const parsed = path18.parse(initialName);
|
|
31171
32054
|
for (let attempt = 0;attempt < 1000; attempt++) {
|
|
31172
32055
|
const candidateName = attempt === 0 ? initialName : `${parsed.name}-${attempt}${parsed.ext || `.${extensionForMime(contentType)}`}`;
|
|
31173
|
-
const file =
|
|
32056
|
+
const file = path18.join(binaryDir, candidateName);
|
|
31174
32057
|
try {
|
|
31175
32058
|
await writeFile2(file, data, { flag: "wx" });
|
|
31176
32059
|
return file;
|
|
@@ -31309,7 +32192,7 @@ var M = class u2 {
|
|
|
31309
32192
|
return this.#S;
|
|
31310
32193
|
}
|
|
31311
32194
|
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:
|
|
32195
|
+
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
32196
|
if (x !== undefined && typeof x?.now != "function")
|
|
31314
32197
|
throw new TypeError("perf option must have a now() method if specified");
|
|
31315
32198
|
if (this.#m = x ?? C, t !== 0 && !F(t))
|
|
@@ -31327,7 +32210,7 @@ var M = class u2 {
|
|
|
31327
32210
|
throw new TypeError("memoMethod must be a function if defined");
|
|
31328
32211
|
if (this.#U = m, a !== undefined && typeof a != "function")
|
|
31329
32212
|
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 = !!
|
|
32213
|
+
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
32214
|
if (this.#u !== 0 && !F(this.#u))
|
|
31332
32215
|
throw new TypeError("maxSize must be a positive integer if specified");
|
|
31333
32216
|
if (!F(this.maxEntrySize))
|
|
@@ -31707,8 +32590,8 @@ var M = class u2 {
|
|
|
31707
32590
|
let A = this.#p(b);
|
|
31708
32591
|
if (!y && !A)
|
|
31709
32592
|
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 ?
|
|
32593
|
+
let z7 = this.#P(e, b, _, w), v = z7.__staleWhileFetching !== undefined && i;
|
|
32594
|
+
return a && (a.fetch = A ? "stale" : "refresh", v && A && (a.returnedStale = true)), v ? z7.__staleWhileFetching : z7.__returned = z7;
|
|
31712
32595
|
}
|
|
31713
32596
|
}
|
|
31714
32597
|
forceFetch(e, t = {}) {
|
|
@@ -31824,7 +32707,7 @@ var M = class u2 {
|
|
|
31824
32707
|
};
|
|
31825
32708
|
|
|
31826
32709
|
// src/tools/smartfetch/network.ts
|
|
31827
|
-
import
|
|
32710
|
+
import path19 from "node:path";
|
|
31828
32711
|
|
|
31829
32712
|
// src/tools/smartfetch/utils.ts
|
|
31830
32713
|
var import_readability = __toESM(require_readability(), 1);
|
|
@@ -32549,7 +33432,7 @@ function inferFilenameFromUrl(url) {
|
|
|
32549
33432
|
function truncateFilename(name, maxLength = 180) {
|
|
32550
33433
|
if (name.length <= maxLength)
|
|
32551
33434
|
return name;
|
|
32552
|
-
const parsed =
|
|
33435
|
+
const parsed = path19.parse(name);
|
|
32553
33436
|
const ext = parsed.ext || "";
|
|
32554
33437
|
const baseLimit = Math.max(1, maxLength - ext.length);
|
|
32555
33438
|
return `${parsed.name.slice(0, baseLimit)}${ext}`;
|
|
@@ -32719,9 +33602,9 @@ function isInvalidLlmsResult(fetchResult) {
|
|
|
32719
33602
|
}
|
|
32720
33603
|
|
|
32721
33604
|
// src/tools/smartfetch/secondary-model.ts
|
|
32722
|
-
import { existsSync as
|
|
33605
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
32723
33606
|
import { readFile as readFile4 } from "node:fs/promises";
|
|
32724
|
-
import
|
|
33607
|
+
import path20 from "node:path";
|
|
32725
33608
|
function parseModelRef(value) {
|
|
32726
33609
|
if (!value)
|
|
32727
33610
|
return;
|
|
@@ -32747,8 +33630,8 @@ function pickAgentModelRef(value) {
|
|
|
32747
33630
|
}
|
|
32748
33631
|
function findPreferredOpenCodeConfigPath(baseDir) {
|
|
32749
33632
|
for (const file of ["opencode.jsonc", "opencode.json"]) {
|
|
32750
|
-
const fullPath =
|
|
32751
|
-
if (
|
|
33633
|
+
const fullPath = path20.join(baseDir, file);
|
|
33634
|
+
if (existsSync12(fullPath))
|
|
32752
33635
|
return fullPath;
|
|
32753
33636
|
}
|
|
32754
33637
|
return;
|
|
@@ -32764,7 +33647,7 @@ async function readOpenCodeConfigFile(configPath) {
|
|
|
32764
33647
|
}
|
|
32765
33648
|
}
|
|
32766
33649
|
async function readEffectiveOpenCodeConfig(directory) {
|
|
32767
|
-
const projectDir =
|
|
33650
|
+
const projectDir = path20.join(directory, ".opencode");
|
|
32768
33651
|
const userDirs = getConfigSearchDirs();
|
|
32769
33652
|
const projectPath = findPreferredOpenCodeConfigPath(projectDir);
|
|
32770
33653
|
const userPath = userDirs.map((configDir) => findPreferredOpenCodeConfigPath(configDir)).find(Boolean);
|
|
@@ -32845,6 +33728,29 @@ function isUsableSecondaryText(text) {
|
|
|
32845
33728
|
return false;
|
|
32846
33729
|
return true;
|
|
32847
33730
|
}
|
|
33731
|
+
var SESSION_DELETE_RETRIES = 3;
|
|
33732
|
+
var SESSION_DELETE_RETRY_DELAY_MS = 500;
|
|
33733
|
+
var SECONDARY_MODEL_TIMEOUT_MS = 30000;
|
|
33734
|
+
var _testConfig = {
|
|
33735
|
+
deleteRetryDelayMs: SESSION_DELETE_RETRY_DELAY_MS
|
|
33736
|
+
};
|
|
33737
|
+
async function deleteSessionSafely(client, sessionId, directory) {
|
|
33738
|
+
for (let attempt = 1;attempt <= SESSION_DELETE_RETRIES; attempt++) {
|
|
33739
|
+
try {
|
|
33740
|
+
await client.session.delete({
|
|
33741
|
+
path: { id: sessionId },
|
|
33742
|
+
query: { directory }
|
|
33743
|
+
});
|
|
33744
|
+
return;
|
|
33745
|
+
} catch (error) {
|
|
33746
|
+
if (attempt >= SESSION_DELETE_RETRIES) {
|
|
33747
|
+
console.warn(`[smartfetch] Failed to clean up secondary session ${sessionId} ` + `after ${SESSION_DELETE_RETRIES} attempts: ` + (error instanceof Error ? error.message : String(error)));
|
|
33748
|
+
return;
|
|
33749
|
+
}
|
|
33750
|
+
await new Promise((resolve3) => setTimeout(resolve3, _testConfig.deleteRetryDelayMs));
|
|
33751
|
+
}
|
|
33752
|
+
}
|
|
33753
|
+
}
|
|
32848
33754
|
async function runSecondaryModel(client, directory, model, prompt, content) {
|
|
32849
33755
|
const session2 = await client.session.create({
|
|
32850
33756
|
responseStyle: "data",
|
|
@@ -32871,23 +33777,26 @@ Note: only the first ${inputChars} characters of a longer fetched document were
|
|
|
32871
33777
|
const toolIDsData = toolIDsResponse;
|
|
32872
33778
|
const toolIDs = Array.isArray(toolIDsData.data) ? toolIDsData.data : Array.isArray(toolIDsResponse) ? toolIDsResponse : [];
|
|
32873
33779
|
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
|
-
|
|
33780
|
+
const result = await Promise.race([
|
|
33781
|
+
client.session.prompt({
|
|
33782
|
+
responseStyle: "data",
|
|
33783
|
+
throwOnError: true,
|
|
33784
|
+
path: { id: sessionId },
|
|
33785
|
+
query: { directory },
|
|
33786
|
+
body: {
|
|
33787
|
+
model,
|
|
33788
|
+
system: "Answer only from the supplied content. Do not use tools or outside knowledge.",
|
|
33789
|
+
tools: disabledTools,
|
|
33790
|
+
parts: [
|
|
33791
|
+
{
|
|
33792
|
+
type: "text",
|
|
33793
|
+
text: buildPrompt(truncatedContent, effectivePrompt)
|
|
33794
|
+
}
|
|
33795
|
+
]
|
|
33796
|
+
}
|
|
33797
|
+
}),
|
|
33798
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("Secondary model timed out")), SECONDARY_MODEL_TIMEOUT_MS))
|
|
33799
|
+
]);
|
|
32891
33800
|
const parts = result?.data?.parts ?? result?.parts ?? [];
|
|
32892
33801
|
const text = parts.map((part) => part?.type === "text" ? part.text || "" : "").join("").trim();
|
|
32893
33802
|
return {
|
|
@@ -32897,12 +33806,7 @@ Note: only the first ${inputChars} characters of a longer fetched document were
|
|
|
32897
33806
|
sourceChars
|
|
32898
33807
|
};
|
|
32899
33808
|
} finally {
|
|
32900
|
-
await client
|
|
32901
|
-
path: { id: sessionId },
|
|
32902
|
-
query: { directory }
|
|
32903
|
-
}).catch(() => {
|
|
32904
|
-
return;
|
|
32905
|
-
});
|
|
33809
|
+
await deleteSessionSafely(client, sessionId, directory);
|
|
32906
33810
|
}
|
|
32907
33811
|
}
|
|
32908
33812
|
async function runSecondaryModelWithFallback(client, directory, models, prompt, content) {
|
|
@@ -32923,20 +33827,20 @@ async function runSecondaryModelWithFallback(client, directory, models, prompt,
|
|
|
32923
33827
|
}
|
|
32924
33828
|
|
|
32925
33829
|
// src/tools/smartfetch/tool.ts
|
|
32926
|
-
var
|
|
33830
|
+
var z7 = tool5.schema;
|
|
32927
33831
|
function createWebfetchTool(pluginCtx, options = {}) {
|
|
32928
|
-
const binaryDir = options.binaryDir ||
|
|
32929
|
-
return
|
|
33832
|
+
const binaryDir = options.binaryDir || path21.join(os6.tmpdir(), "opencode-smartfetch");
|
|
33833
|
+
return tool5({
|
|
32930
33834
|
description: WEBFETCH_DESCRIPTION,
|
|
32931
33835
|
args: {
|
|
32932
|
-
url:
|
|
32933
|
-
format:
|
|
32934
|
-
timeout:
|
|
32935
|
-
prompt:
|
|
32936
|
-
extract_main:
|
|
32937
|
-
prefer_llms_txt:
|
|
32938
|
-
include_metadata:
|
|
32939
|
-
save_binary:
|
|
33836
|
+
url: z7.httpUrl(),
|
|
33837
|
+
format: z7.enum(["text", "markdown", "html"]).default("markdown"),
|
|
33838
|
+
timeout: z7.number().positive().max(MAX_TIMEOUT_SECONDS).optional().describe("Timeout in seconds, max 120."),
|
|
33839
|
+
prompt: z7.string().optional().describe("Optional extraction task to run on the fetched content using a cheap secondary model."),
|
|
33840
|
+
extract_main: z7.boolean().default(true),
|
|
33841
|
+
prefer_llms_txt: z7.enum(["auto", "always", "never"]).default("auto"),
|
|
33842
|
+
include_metadata: z7.boolean().default(true),
|
|
33843
|
+
save_binary: z7.boolean().default(false).describe("Save binary payload to disk when it fits within the active download limit.")
|
|
32940
33844
|
},
|
|
32941
33845
|
async execute(args, ctx) {
|
|
32942
33846
|
const secondaryModels = await readSecondaryModelFromConfig(ctx.directory || pluginCtx.directory);
|
|
@@ -33481,7 +34385,7 @@ async function appLog(ctx, level, message) {
|
|
|
33481
34385
|
}
|
|
33482
34386
|
var HEALTH_CHECK = {
|
|
33483
34387
|
minAgents: 5,
|
|
33484
|
-
minTools:
|
|
34388
|
+
minTools: 4,
|
|
33485
34389
|
minMcps: 1
|
|
33486
34390
|
};
|
|
33487
34391
|
async function probeJSDOM() {
|
|
@@ -33526,6 +34430,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33526
34430
|
let companionManager;
|
|
33527
34431
|
let councilTools;
|
|
33528
34432
|
let cancelTaskTools;
|
|
34433
|
+
let acpRunTools;
|
|
33529
34434
|
let webfetch;
|
|
33530
34435
|
let rewriteDisplayNameMentions;
|
|
33531
34436
|
let toolCount = 0;
|
|
@@ -33570,6 +34475,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33570
34475
|
depthTracker = new SubagentDepthTracker;
|
|
33571
34476
|
councilTools = config.council ? createCouncilTool(ctx, new CouncilManager(ctx, config, depthTracker, multiplexerEnabled)) : {};
|
|
33572
34477
|
mcps = createBuiltinMcps(config.disabled_mcps, config.websearch);
|
|
34478
|
+
acpRunTools = Object.keys(config.acpAgents ?? {}).length > 0 ? { acp_run: createAcpRunTool(config.acpAgents) } : {};
|
|
33573
34479
|
webfetch = createWebfetchTool(ctx);
|
|
33574
34480
|
backgroundJobBoard = new BackgroundJobBoard({
|
|
33575
34481
|
maxReusablePerAgent: config.backgroundJobs?.maxSessionsPerAgent ?? 2,
|
|
@@ -33578,7 +34484,8 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33578
34484
|
});
|
|
33579
34485
|
multiplexerSessionManager = new MultiplexerSessionManager(ctx, multiplexerConfig, backgroundJobBoard);
|
|
33580
34486
|
autoUpdateChecker = createAutoUpdateCheckerHook(ctx, {
|
|
33581
|
-
autoUpdate: config.autoUpdate ?? true
|
|
34487
|
+
autoUpdate: config.autoUpdate ?? true,
|
|
34488
|
+
companion: config.companion
|
|
33582
34489
|
});
|
|
33583
34490
|
phaseReminderHook = createPhaseReminderHook();
|
|
33584
34491
|
filterAvailableSkillsHook = createFilterAvailableSkillsHook(ctx, config);
|
|
@@ -33608,7 +34515,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33608
34515
|
backgroundJobBoard,
|
|
33609
34516
|
shouldManageSession: (sessionID) => sessionAgentMap.get(sessionID) === "orchestrator"
|
|
33610
34517
|
});
|
|
33611
|
-
toolCount = Object.keys(councilTools).length + Object.keys(cancelTaskTools).length + 1 + 2;
|
|
34518
|
+
toolCount = Object.keys(councilTools).length + Object.keys(cancelTaskTools).length + Object.keys(acpRunTools).length + 1 + 2;
|
|
33612
34519
|
} catch (err) {
|
|
33613
34520
|
log("[plugin] FATAL: init failed", String(err));
|
|
33614
34521
|
await appLog(ctx, "error", `INIT FAILED: ${String(err)}. Report at github.com/alvinunreal/oh-my-opencode-slim/issues/310`);
|
|
@@ -33644,6 +34551,22 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33644
34551
|
appLog(ctx, "warn", msg).catch(() => {});
|
|
33645
34552
|
}
|
|
33646
34553
|
});
|
|
34554
|
+
if (config.companion?.enabled === true) {
|
|
34555
|
+
try {
|
|
34556
|
+
const companionResult = await ensureCompanionVersion({
|
|
34557
|
+
config: config.companion,
|
|
34558
|
+
downloadTimeoutMs: 3000,
|
|
34559
|
+
lockTimeoutMs: 500
|
|
34560
|
+
});
|
|
34561
|
+
if (companionResult.status === "installed") {
|
|
34562
|
+
log("[companion] updated before startup", companionResult.version);
|
|
34563
|
+
} else if (companionResult.status === "failed") {
|
|
34564
|
+
log("[companion] startup update failed", companionResult.error);
|
|
34565
|
+
}
|
|
34566
|
+
} catch (err) {
|
|
34567
|
+
log("[companion] startup update failed", String(err));
|
|
34568
|
+
}
|
|
34569
|
+
}
|
|
33647
34570
|
companionManager.onLoad();
|
|
33648
34571
|
return {
|
|
33649
34572
|
name: "oh-my-opencode-slim",
|
|
@@ -33651,6 +34574,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33651
34574
|
tool: {
|
|
33652
34575
|
...councilTools,
|
|
33653
34576
|
...cancelTaskTools,
|
|
34577
|
+
...acpRunTools,
|
|
33654
34578
|
webfetch,
|
|
33655
34579
|
ast_grep_search,
|
|
33656
34580
|
ast_grep_replace
|