@cortexkit/aft-opencode 0.30.0 → 0.30.2
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/dist/bg-notifications.d.ts +2 -0
- package/dist/bg-notifications.d.ts.map +1 -1
- package/dist/hooks/auto-update-checker/cache.d.ts +5 -16
- package/dist/hooks/auto-update-checker/cache.d.ts.map +1 -1
- package/dist/hooks/auto-update-checker/index.d.ts +1 -1
- package/dist/hooks/auto-update-checker/index.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1005 -632
- package/dist/notifications.d.ts +8 -5
- package/dist/notifications.d.ts.map +1 -1
- package/dist/shared/last-assistant-model.d.ts +5 -6
- package/dist/shared/last-assistant-model.d.ts.map +1 -1
- package/dist/shared/live-server-client.d.ts +24 -22
- package/dist/shared/live-server-client.d.ts.map +1 -1
- package/dist/shared/rpc-client.d.ts +10 -3
- package/dist/shared/rpc-client.d.ts.map +1 -1
- package/dist/shared/storage-paths.d.ts +2 -0
- package/dist/shared/storage-paths.d.ts.map +1 -0
- package/dist/tools/_shared.d.ts +39 -0
- package/dist/tools/_shared.d.ts.map +1 -1
- package/dist/tools/ast.d.ts.map +1 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash_watch.d.ts +4 -0
- package/dist/tools/bash_watch.d.ts.map +1 -1
- package/dist/tools/hoisted.d.ts.map +1 -1
- package/dist/tools/lsp.d.ts.map +1 -1
- package/dist/tools/navigation.d.ts.map +1 -1
- package/dist/tools/reading.d.ts.map +1 -1
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/semantic.d.ts.map +1 -1
- package/dist/tui.js +222 -123
- package/package.json +8 -8
- package/src/shared/last-assistant-model.ts +9 -27
- package/src/shared/live-server-client.ts +80 -48
- package/src/shared/rpc-client.ts +132 -28
- package/src/shared/storage-paths.ts +22 -0
- package/src/tui/index.tsx +39 -17
- package/src/tui/sidebar.tsx +94 -27
package/dist/index.js
CHANGED
|
@@ -12399,7 +12399,7 @@ var require_snapshot_utils = __commonJS((exports, module) => {
|
|
|
12399
12399
|
// ../../node_modules/.bun/undici@7.25.0/node_modules/undici/lib/mock/snapshot-recorder.js
|
|
12400
12400
|
var require_snapshot_recorder = __commonJS((exports, module) => {
|
|
12401
12401
|
var { writeFile, readFile, mkdir } = __require("node:fs/promises");
|
|
12402
|
-
var { dirname:
|
|
12402
|
+
var { dirname: dirname4, resolve: resolve2 } = __require("node:path");
|
|
12403
12403
|
var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("node:timers");
|
|
12404
12404
|
var { InvalidArgumentError, UndiciError } = require_errors();
|
|
12405
12405
|
var { hashId, isUrlExcludedFactory, normalizeHeaders, createHeaderFilters } = require_snapshot_utils();
|
|
@@ -12594,7 +12594,7 @@ var require_snapshot_recorder = __commonJS((exports, module) => {
|
|
|
12594
12594
|
throw new InvalidArgumentError("Snapshot path is required");
|
|
12595
12595
|
}
|
|
12596
12596
|
const resolvedPath = resolve2(path);
|
|
12597
|
-
await mkdir(
|
|
12597
|
+
await mkdir(dirname4(resolvedPath), { recursive: true });
|
|
12598
12598
|
const data = Array.from(this.#snapshots.entries()).map(([hash, snapshot]) => ({
|
|
12599
12599
|
hash,
|
|
12600
12600
|
snapshot
|
|
@@ -29810,10 +29810,10 @@ var require_stringify = __commonJS((exports, module) => {
|
|
|
29810
29810
|
replacer = null;
|
|
29811
29811
|
indent = EMPTY;
|
|
29812
29812
|
};
|
|
29813
|
-
var
|
|
29813
|
+
var join9 = (one, two, gap) => one ? two ? one + two.trim() + LF + gap : one.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(one, gap)), gap) : two ? two.trimRight() + repeat_line_breaks(Math.max(1, count_trailing_line_breaks(two, gap)), gap) : EMPTY;
|
|
29814
29814
|
var join_content = (inside, value, gap) => {
|
|
29815
29815
|
const comment = process_comments(value, PREFIX_BEFORE, gap + indent, true);
|
|
29816
|
-
return
|
|
29816
|
+
return join9(comment, inside, gap);
|
|
29817
29817
|
};
|
|
29818
29818
|
var stringify_string = (holder, key, value) => {
|
|
29819
29819
|
const raw = get_raw_string_literal(holder, key);
|
|
@@ -29835,13 +29835,13 @@ var require_stringify = __commonJS((exports, module) => {
|
|
|
29835
29835
|
if (i !== 0) {
|
|
29836
29836
|
inside += COMMA;
|
|
29837
29837
|
}
|
|
29838
|
-
const before =
|
|
29838
|
+
const before = join9(after_comma, process_comments(value, BEFORE(i), deeper_gap), deeper_gap);
|
|
29839
29839
|
inside += before || LF + deeper_gap;
|
|
29840
29840
|
inside += stringify(i, value, deeper_gap) || STR_NULL;
|
|
29841
29841
|
inside += process_comments(value, AFTER_VALUE(i), deeper_gap);
|
|
29842
29842
|
after_comma = process_comments(value, AFTER(i), deeper_gap);
|
|
29843
29843
|
}
|
|
29844
|
-
inside +=
|
|
29844
|
+
inside += join9(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
|
|
29845
29845
|
return BRACKET_OPEN + join_content(inside, value, gap) + BRACKET_CLOSE;
|
|
29846
29846
|
};
|
|
29847
29847
|
var object_stringify = (value, gap) => {
|
|
@@ -29862,13 +29862,13 @@ var require_stringify = __commonJS((exports, module) => {
|
|
|
29862
29862
|
inside += COMMA;
|
|
29863
29863
|
}
|
|
29864
29864
|
first = false;
|
|
29865
|
-
const before =
|
|
29865
|
+
const before = join9(after_comma, process_comments(value, BEFORE(key), deeper_gap), deeper_gap);
|
|
29866
29866
|
inside += before || LF + deeper_gap;
|
|
29867
29867
|
inside += quote(key) + process_comments(value, AFTER_PROP(key), deeper_gap) + COLON + process_comments(value, AFTER_COLON(key), deeper_gap) + SPACE + sv + process_comments(value, AFTER_VALUE(key), deeper_gap);
|
|
29868
29868
|
after_comma = process_comments(value, AFTER(key), deeper_gap);
|
|
29869
29869
|
};
|
|
29870
29870
|
keys.forEach(iteratee);
|
|
29871
|
-
inside +=
|
|
29871
|
+
inside += join9(after_comma, process_comments(value, PREFIX_AFTER, deeper_gap), deeper_gap);
|
|
29872
29872
|
return CURLY_BRACKET_OPEN + join_content(inside, value, gap) + CURLY_BRACKET_CLOSE;
|
|
29873
29873
|
};
|
|
29874
29874
|
function stringify(key, holder, gap) {
|
|
@@ -33691,9 +33691,9 @@ var require_xterm_headless = __commonJS((exports) => {
|
|
|
33691
33691
|
});
|
|
33692
33692
|
|
|
33693
33693
|
// src/index.ts
|
|
33694
|
-
import { existsSync as
|
|
33694
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync14, readFileSync as readFileSync14, writeFileSync as writeFileSync12 } from "node:fs";
|
|
33695
33695
|
import { createRequire as createRequire3 } from "node:module";
|
|
33696
|
-
import {
|
|
33696
|
+
import { dirname as dirname14 } from "node:path";
|
|
33697
33697
|
|
|
33698
33698
|
// ../aft-bridge/dist/active-logger.js
|
|
33699
33699
|
var ACTIVE_LOGGER_SYMBOL = Symbol.for("aft-bridge-active-logger");
|
|
@@ -33982,6 +33982,11 @@ class BinaryBridge {
|
|
|
33982
33982
|
if (requestSessionId && options?.configureWarningClient !== undefined) {
|
|
33983
33983
|
this.configureWarningClients.set(requestSessionId, options.configureWarningClient);
|
|
33984
33984
|
}
|
|
33985
|
+
const effectiveTimeoutMs = options?.transportTimeoutMs ?? options?.timeoutMs ?? this.timeoutMs;
|
|
33986
|
+
const implicitTransportOptions = {
|
|
33987
|
+
...options?.transportTimeoutMs !== undefined || options?.timeoutMs !== undefined ? { transportTimeoutMs: effectiveTimeoutMs } : {},
|
|
33988
|
+
markConfiguredOnSuccess: false
|
|
33989
|
+
};
|
|
33985
33990
|
if (!this.configured) {
|
|
33986
33991
|
if (command !== "configure" && command !== "version") {
|
|
33987
33992
|
if (!this._configurePromise) {
|
|
@@ -33992,12 +33997,12 @@ class BinaryBridge {
|
|
|
33992
33997
|
project_root: this.cwd,
|
|
33993
33998
|
...this.configOverrides,
|
|
33994
33999
|
...sessionIdForConfigure ? { session_id: sessionIdForConfigure } : {}
|
|
33995
|
-
});
|
|
34000
|
+
}, implicitTransportOptions);
|
|
33996
34001
|
if (configResult.success === false) {
|
|
33997
34002
|
throw new Error(`${this.errorPrefix} Configure failed: ${configResult.message ?? "unknown error"}`);
|
|
33998
34003
|
}
|
|
33999
34004
|
await this.deliverConfigureWarnings(configResult, params, options);
|
|
34000
|
-
await this.checkVersion();
|
|
34005
|
+
await this.checkVersion(implicitTransportOptions);
|
|
34001
34006
|
if (!this.isAlive()) {
|
|
34002
34007
|
throw new Error(`${this.errorPrefix} Bridge died during version check. Check logs: ${this.getLogFilePathVia()}`);
|
|
34003
34008
|
}
|
|
@@ -34027,9 +34032,8 @@ class BinaryBridge {
|
|
|
34027
34032
|
}
|
|
34028
34033
|
const line = `${JSON.stringify(request)}
|
|
34029
34034
|
`;
|
|
34030
|
-
const effectiveTimeoutMs = options?.transportTimeoutMs ?? options?.timeoutMs ?? this.timeoutMs;
|
|
34031
34035
|
const keepBridgeOnTimeout = options?.keepBridgeOnTimeout === true;
|
|
34032
|
-
|
|
34036
|
+
const response = await new Promise((resolve, reject) => {
|
|
34033
34037
|
const timer = setTimeout(() => {
|
|
34034
34038
|
this.pending.delete(id);
|
|
34035
34039
|
const restartSuffix = keepBridgeOnTimeout ? "" : " — restarting bridge";
|
|
@@ -34062,6 +34066,10 @@ class BinaryBridge {
|
|
|
34062
34066
|
}
|
|
34063
34067
|
});
|
|
34064
34068
|
});
|
|
34069
|
+
if (command === "configure" && response.success === true && options?.markConfiguredOnSuccess !== false) {
|
|
34070
|
+
this.configured = true;
|
|
34071
|
+
}
|
|
34072
|
+
return response;
|
|
34065
34073
|
} catch (err) {
|
|
34066
34074
|
if (err instanceof BridgeReplacedDuringVersionCheck && canRetryAfterVersionSwap && command !== "configure" && command !== "version") {
|
|
34067
34075
|
this.logVia(`Retrying request "${command}" once after coordinated binary replacement: ${err.newBinaryPath}`);
|
|
@@ -34152,11 +34160,11 @@ class BinaryBridge {
|
|
|
34152
34160
|
});
|
|
34153
34161
|
}
|
|
34154
34162
|
}
|
|
34155
|
-
async checkVersion() {
|
|
34163
|
+
async checkVersion(options) {
|
|
34156
34164
|
if (!this.minVersion)
|
|
34157
34165
|
return;
|
|
34158
34166
|
try {
|
|
34159
|
-
const resp = await this.send("version");
|
|
34167
|
+
const resp = await this.send("version", {}, options);
|
|
34160
34168
|
if (resp.success === false) {
|
|
34161
34169
|
throw new Error(`Binary version check failed: ${String(resp.code ?? "unknown")} — likely too old`);
|
|
34162
34170
|
}
|
|
@@ -34231,10 +34239,14 @@ class BinaryBridge {
|
|
|
34231
34239
|
...process.env,
|
|
34232
34240
|
...envPath ? { PATH: envPath } : {}
|
|
34233
34241
|
};
|
|
34242
|
+
this.logVia(`bridge.spawnProcess: useFastembedBackend=${useFastembedBackend}, ` + `parentORT=${process.env.ORT_DYLIB_PATH ?? "(unset)"}, ` + `ortLibraryPath=${ortLibraryPath ?? "(none)"}`);
|
|
34234
34243
|
if (useFastembedBackend) {
|
|
34235
34244
|
env.FASTEMBED_CACHE_DIR = process.env.FASTEMBED_CACHE_DIR || (typeof this.configOverrides.storage_dir === "string" ? join(this.configOverrides.storage_dir, "semantic", "models") : join(homedir() || "", ".cache", "fastembed"));
|
|
34236
|
-
if (
|
|
34245
|
+
if (process.env.ORT_DYLIB_PATH) {
|
|
34246
|
+
this.logVia(`ORT_DYLIB_PATH inherited from parent env: ${process.env.ORT_DYLIB_PATH}`);
|
|
34247
|
+
} else if (ortLibraryPath) {
|
|
34237
34248
|
env.ORT_DYLIB_PATH = ortLibraryPath;
|
|
34249
|
+
this.logVia(`ORT_DYLIB_PATH set from managed ONNX Runtime: ${ortLibraryPath}`);
|
|
34238
34250
|
}
|
|
34239
34251
|
}
|
|
34240
34252
|
const child = spawn(this.binaryPath, [], {
|
|
@@ -34480,8 +34492,9 @@ class BinaryBridge {
|
|
|
34480
34492
|
}
|
|
34481
34493
|
}
|
|
34482
34494
|
// ../aft-bridge/dist/downloader.js
|
|
34483
|
-
import {
|
|
34484
|
-
import {
|
|
34495
|
+
import { spawnSync } from "node:child_process";
|
|
34496
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
34497
|
+
import { chmodSync, closeSync, createWriteStream, existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, statSync, unlinkSync, writeSync } from "node:fs";
|
|
34485
34498
|
import { homedir as homedir2 } from "node:os";
|
|
34486
34499
|
import { join as join2 } from "node:path";
|
|
34487
34500
|
import { Readable } from "node:stream";
|
|
@@ -34509,6 +34522,34 @@ var LATEST_TAG_TIMEOUT_MS = 30000;
|
|
|
34509
34522
|
var MAX_DOWNLOAD_BYTES = 200 * 1024 * 1024;
|
|
34510
34523
|
var DOWNLOAD_LOCK_TIMEOUT_MS = 120000;
|
|
34511
34524
|
var DOWNLOAD_LOCK_STALE_MS = 10 * 60000;
|
|
34525
|
+
function readBinaryVersion(binaryPath) {
|
|
34526
|
+
try {
|
|
34527
|
+
const result = spawnSync(binaryPath, ["--version"], {
|
|
34528
|
+
encoding: "utf-8",
|
|
34529
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
34530
|
+
timeout: 5000
|
|
34531
|
+
});
|
|
34532
|
+
const stdoutVersion = result.stdout?.trim();
|
|
34533
|
+
const stderrVersion = result.stderr?.trim();
|
|
34534
|
+
const rawVersion = stdoutVersion || stderrVersion;
|
|
34535
|
+
if (!rawVersion)
|
|
34536
|
+
return null;
|
|
34537
|
+
return rawVersion.replace(/^aft\s+/, "");
|
|
34538
|
+
} catch {
|
|
34539
|
+
return null;
|
|
34540
|
+
}
|
|
34541
|
+
}
|
|
34542
|
+
function expectedVersionFromTag(tag) {
|
|
34543
|
+
return tag.startsWith("v") ? tag.slice(1) : tag;
|
|
34544
|
+
}
|
|
34545
|
+
function isExpectedCachedBinary(binaryPath, tag) {
|
|
34546
|
+
const expected = expectedVersionFromTag(tag);
|
|
34547
|
+
const actual = readBinaryVersion(binaryPath);
|
|
34548
|
+
if (actual === expected)
|
|
34549
|
+
return true;
|
|
34550
|
+
warn(`Cached binary at ${binaryPath} reports ${actual ?? "no version"}, expected ${expected}; refreshing cache entry`);
|
|
34551
|
+
return false;
|
|
34552
|
+
}
|
|
34512
34553
|
function getCacheDir() {
|
|
34513
34554
|
if (process.platform === "win32") {
|
|
34514
34555
|
const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
|
|
@@ -34544,7 +34585,7 @@ async function downloadBinary(version) {
|
|
|
34544
34585
|
const versionedCacheDir = join2(getCacheDir(), tag);
|
|
34545
34586
|
const binaryName = getBinaryName();
|
|
34546
34587
|
const binaryPath = join2(versionedCacheDir, binaryName);
|
|
34547
|
-
if (existsSync(binaryPath)) {
|
|
34588
|
+
if (existsSync(binaryPath) && isExpectedCachedBinary(binaryPath, tag)) {
|
|
34548
34589
|
return binaryPath;
|
|
34549
34590
|
}
|
|
34550
34591
|
const downloadUrl = `https://github.com/${REPO}/releases/download/${tag}/${assetName}`;
|
|
@@ -34562,7 +34603,7 @@ async function downloadBinary(version) {
|
|
|
34562
34603
|
mkdirSync(versionedCacheDir, { recursive: true });
|
|
34563
34604
|
}
|
|
34564
34605
|
releaseLock = await acquireDownloadLock(lockPath);
|
|
34565
|
-
if (existsSync(binaryPath)) {
|
|
34606
|
+
if (existsSync(binaryPath) && isExpectedCachedBinary(binaryPath, tag)) {
|
|
34566
34607
|
return binaryPath;
|
|
34567
34608
|
}
|
|
34568
34609
|
binaryController = new AbortController;
|
|
@@ -34651,7 +34692,7 @@ async function ensureBinary(version) {
|
|
|
34651
34692
|
if (version) {
|
|
34652
34693
|
const tag = version.startsWith("v") ? version : `v${version}`;
|
|
34653
34694
|
const versionCached = getCachedBinaryPath(tag);
|
|
34654
|
-
if (versionCached) {
|
|
34695
|
+
if (versionCached && isExpectedCachedBinary(versionCached, tag)) {
|
|
34655
34696
|
log(`Found cached binary for ${tag}: ${versionCached}`);
|
|
34656
34697
|
return versionCached;
|
|
34657
34698
|
}
|
|
@@ -34665,13 +34706,17 @@ async function acquireDownloadLock(lockPath) {
|
|
|
34665
34706
|
const startedAt = Date.now();
|
|
34666
34707
|
while (true) {
|
|
34667
34708
|
try {
|
|
34709
|
+
const owner = `${process.pid}:${Date.now()}:${randomUUID()}`;
|
|
34668
34710
|
const fd = openSync(lockPath, "wx");
|
|
34711
|
+
writeSync(fd, owner);
|
|
34669
34712
|
return () => {
|
|
34670
34713
|
try {
|
|
34671
34714
|
closeSync(fd);
|
|
34672
34715
|
} catch {}
|
|
34673
34716
|
try {
|
|
34674
|
-
|
|
34717
|
+
if (readFileSync(lockPath, "utf-8") === owner) {
|
|
34718
|
+
rmSync(lockPath, { force: true });
|
|
34719
|
+
}
|
|
34675
34720
|
} catch {}
|
|
34676
34721
|
};
|
|
34677
34722
|
} catch (err) {
|
|
@@ -34727,34 +34772,35 @@ async function fetchLatestTag() {
|
|
|
34727
34772
|
}
|
|
34728
34773
|
// ../aft-bridge/dist/migration.js
|
|
34729
34774
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
34730
|
-
import { existsSync as
|
|
34775
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4 } from "node:fs";
|
|
34731
34776
|
import { homedir as homedir4, tmpdir } from "node:os";
|
|
34732
|
-
import { dirname, join as
|
|
34777
|
+
import { dirname as dirname2, join as join5 } from "node:path";
|
|
34778
|
+
|
|
34779
|
+
// ../aft-bridge/dist/paths.js
|
|
34780
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, renameSync as renameSync2 } from "node:fs";
|
|
34781
|
+
import { dirname, join as join3 } from "node:path";
|
|
34782
|
+
function resolveHarnessStoragePath(storageRoot, harness, ...segments) {
|
|
34783
|
+
return join3(storageRoot, harness, ...segments);
|
|
34784
|
+
}
|
|
34785
|
+
function repairRootScopedStorageFile(storageRoot, harness, fileName) {
|
|
34786
|
+
const harnessPath = resolveHarnessStoragePath(storageRoot, harness, fileName);
|
|
34787
|
+
const rootPath = join3(storageRoot, fileName);
|
|
34788
|
+
if (existsSync2(harnessPath) || !existsSync2(rootPath))
|
|
34789
|
+
return harnessPath;
|
|
34790
|
+
try {
|
|
34791
|
+
mkdirSync2(dirname(harnessPath), { recursive: true });
|
|
34792
|
+
renameSync2(rootPath, harnessPath);
|
|
34793
|
+
} catch {}
|
|
34794
|
+
return harnessPath;
|
|
34795
|
+
}
|
|
34733
34796
|
|
|
34734
34797
|
// ../aft-bridge/dist/resolver.js
|
|
34735
|
-
import { execSync
|
|
34736
|
-
import { chmodSync as chmodSync2, copyFileSync, existsSync as
|
|
34798
|
+
import { execSync } from "node:child_process";
|
|
34799
|
+
import { chmodSync as chmodSync2, copyFileSync, existsSync as existsSync3, mkdirSync as mkdirSync3, renameSync as renameSync3 } from "node:fs";
|
|
34737
34800
|
import { createRequire as createRequire2 } from "node:module";
|
|
34738
34801
|
import { homedir as homedir3 } from "node:os";
|
|
34739
|
-
import { join as
|
|
34802
|
+
import { join as join4 } from "node:path";
|
|
34740
34803
|
var ensureBinaryForResolver = ensureBinary;
|
|
34741
|
-
function readBinaryVersion(binaryPath) {
|
|
34742
|
-
try {
|
|
34743
|
-
const result = spawnSync(binaryPath, ["--version"], {
|
|
34744
|
-
encoding: "utf-8",
|
|
34745
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
34746
|
-
timeout: 5000
|
|
34747
|
-
});
|
|
34748
|
-
const stdoutVersion = result.stdout?.trim();
|
|
34749
|
-
const stderrVersion = result.stderr?.trim();
|
|
34750
|
-
const rawVersion = stdoutVersion || stderrVersion;
|
|
34751
|
-
if (!rawVersion)
|
|
34752
|
-
return null;
|
|
34753
|
-
return rawVersion.replace(/^aft\s+/, "");
|
|
34754
|
-
} catch {
|
|
34755
|
-
return null;
|
|
34756
|
-
}
|
|
34757
|
-
}
|
|
34758
34804
|
function copyToVersionedCache(npmBinaryPath, knownVersion) {
|
|
34759
34805
|
try {
|
|
34760
34806
|
const version = knownVersion ?? readBinaryVersion(npmBinaryPath);
|
|
@@ -34762,22 +34808,22 @@ function copyToVersionedCache(npmBinaryPath, knownVersion) {
|
|
|
34762
34808
|
return null;
|
|
34763
34809
|
const tag = version.startsWith("v") ? version : `v${version}`;
|
|
34764
34810
|
const cacheDir = getCacheDir();
|
|
34765
|
-
const versionedDir =
|
|
34811
|
+
const versionedDir = join4(cacheDir, tag);
|
|
34766
34812
|
const ext = process.platform === "win32" ? ".exe" : "";
|
|
34767
|
-
const cachedPath =
|
|
34768
|
-
if (
|
|
34813
|
+
const cachedPath = join4(versionedDir, `aft${ext}`);
|
|
34814
|
+
if (existsSync3(cachedPath)) {
|
|
34769
34815
|
const cachedVersion = readBinaryVersion(cachedPath);
|
|
34770
34816
|
if (cachedVersion === version)
|
|
34771
34817
|
return cachedPath;
|
|
34772
34818
|
warn(`Cached binary at ${cachedPath} reports ${cachedVersion ?? "no version"}, expected ${version}; refreshing from npm package`);
|
|
34773
34819
|
}
|
|
34774
|
-
|
|
34820
|
+
mkdirSync3(versionedDir, { recursive: true });
|
|
34775
34821
|
const tmpPath = `${cachedPath}.${process.pid}.${Date.now()}.tmp`;
|
|
34776
34822
|
copyFileSync(npmBinaryPath, tmpPath);
|
|
34777
34823
|
if (process.platform !== "win32") {
|
|
34778
34824
|
chmodSync2(tmpPath, 493);
|
|
34779
34825
|
}
|
|
34780
|
-
|
|
34826
|
+
renameSync3(tmpPath, cachedPath);
|
|
34781
34827
|
log(`Copied npm binary to versioned cache: ${cachedPath}`);
|
|
34782
34828
|
return cachedPath;
|
|
34783
34829
|
} catch (err) {
|
|
@@ -34793,17 +34839,17 @@ function homeDirFromEnv(env) {
|
|
|
34793
34839
|
}
|
|
34794
34840
|
function cacheDirFromEnv(env) {
|
|
34795
34841
|
if (process.platform === "win32") {
|
|
34796
|
-
const base2 = env.LOCALAPPDATA || env.APPDATA ||
|
|
34797
|
-
return
|
|
34842
|
+
const base2 = env.LOCALAPPDATA || env.APPDATA || join4(homeDirFromEnv(env), "AppData", "Local");
|
|
34843
|
+
return join4(base2, "aft", "bin");
|
|
34798
34844
|
}
|
|
34799
|
-
const base = env.XDG_CACHE_HOME ||
|
|
34800
|
-
return
|
|
34845
|
+
const base = env.XDG_CACHE_HOME || join4(homeDirFromEnv(env), ".cache");
|
|
34846
|
+
return join4(base, "aft", "bin");
|
|
34801
34847
|
}
|
|
34802
34848
|
function cachedBinaryPathFromEnv(version, env, ext) {
|
|
34803
|
-
const binaryPath =
|
|
34804
|
-
return
|
|
34849
|
+
const binaryPath = join4(cacheDirFromEnv(env), version, `aft${ext}`);
|
|
34850
|
+
return existsSync3(binaryPath) ? binaryPath : null;
|
|
34805
34851
|
}
|
|
34806
|
-
function
|
|
34852
|
+
function isExpectedCachedBinary2(binaryPath, expectedVersion) {
|
|
34807
34853
|
const expected = normalizeBareVersion(expectedVersion);
|
|
34808
34854
|
const actual = readBinaryVersion(binaryPath);
|
|
34809
34855
|
if (actual === expected)
|
|
@@ -34811,6 +34857,21 @@ function isExpectedCachedBinary(binaryPath, expectedVersion) {
|
|
|
34811
34857
|
warn(`Cached binary at ${binaryPath} reports ${actual ?? "no version"}, expected ${expected}; skipping cache candidate`);
|
|
34812
34858
|
return false;
|
|
34813
34859
|
}
|
|
34860
|
+
function probeBinaryCandidate(binaryPath, source, expectedVersion) {
|
|
34861
|
+
const actual = readBinaryVersion(binaryPath);
|
|
34862
|
+
if (actual === null) {
|
|
34863
|
+
warn(`${source} binary at ${binaryPath} did not report a version; skipping`);
|
|
34864
|
+
return null;
|
|
34865
|
+
}
|
|
34866
|
+
if (expectedVersion && actual !== normalizeBareVersion(expectedVersion)) {
|
|
34867
|
+
warn(`${source} binary at ${binaryPath} reports ${actual}, expected ${normalizeBareVersion(expectedVersion)}; skipping`);
|
|
34868
|
+
return null;
|
|
34869
|
+
}
|
|
34870
|
+
return binaryPath;
|
|
34871
|
+
}
|
|
34872
|
+
function parsePathLookupOutput(output) {
|
|
34873
|
+
return output.split(/\r?\n/).map((candidate) => candidate.trim()).filter(Boolean);
|
|
34874
|
+
}
|
|
34814
34875
|
function platformKey(platform = process.platform, arch = process.arch) {
|
|
34815
34876
|
const archMap = PLATFORM_ARCH_MAP[platform];
|
|
34816
34877
|
if (!archMap) {
|
|
@@ -34836,7 +34897,7 @@ function findBinarySync(expectedVersion) {
|
|
|
34836
34897
|
if (pluginVersion) {
|
|
34837
34898
|
const tag = pluginVersion.startsWith("v") ? pluginVersion : `v${pluginVersion}`;
|
|
34838
34899
|
const versionCached = cachedBinaryPathFromEnv(tag, env, ext);
|
|
34839
|
-
if (versionCached &&
|
|
34900
|
+
if (versionCached && isExpectedCachedBinary2(versionCached, pluginVersion))
|
|
34840
34901
|
return versionCached;
|
|
34841
34902
|
}
|
|
34842
34903
|
try {
|
|
@@ -34844,7 +34905,7 @@ function findBinarySync(expectedVersion) {
|
|
|
34844
34905
|
const packageBin = `@cortexkit/aft-${key}/bin/aft${ext}`;
|
|
34845
34906
|
const req = createRequire2(import.meta.url);
|
|
34846
34907
|
const resolved = req.resolve(packageBin);
|
|
34847
|
-
if (
|
|
34908
|
+
if (existsSync3(resolved)) {
|
|
34848
34909
|
const npmVersion = readBinaryVersion(resolved);
|
|
34849
34910
|
if (npmVersion === null) {
|
|
34850
34911
|
warn(`npm platform package binary at ${resolved} did not report a version; skipping (continuing to PATH lookup)`);
|
|
@@ -34863,12 +34924,18 @@ function findBinarySync(expectedVersion) {
|
|
|
34863
34924
|
env,
|
|
34864
34925
|
stdio: ["pipe", "pipe", "pipe"]
|
|
34865
34926
|
}).trim();
|
|
34866
|
-
|
|
34867
|
-
|
|
34927
|
+
for (const candidate of parsePathLookupOutput(result)) {
|
|
34928
|
+
const usable = probeBinaryCandidate(candidate, "PATH", expectedVersion);
|
|
34929
|
+
if (usable)
|
|
34930
|
+
return usable;
|
|
34931
|
+
}
|
|
34868
34932
|
} catch {}
|
|
34869
|
-
const cargoPath =
|
|
34870
|
-
if (
|
|
34871
|
-
|
|
34933
|
+
const cargoPath = join4(homeDirFromEnv(env), ".cargo", "bin", `aft${ext}`);
|
|
34934
|
+
if (existsSync3(cargoPath)) {
|
|
34935
|
+
const usable = probeBinaryCandidate(cargoPath, "cargo", expectedVersion);
|
|
34936
|
+
if (usable)
|
|
34937
|
+
return usable;
|
|
34938
|
+
}
|
|
34872
34939
|
return null;
|
|
34873
34940
|
}
|
|
34874
34941
|
async function findBinary(expectedVersion) {
|
|
@@ -34909,9 +34976,9 @@ function dataHome() {
|
|
|
34909
34976
|
if (process.env.XDG_DATA_HOME)
|
|
34910
34977
|
return process.env.XDG_DATA_HOME;
|
|
34911
34978
|
if (process.platform === "win32") {
|
|
34912
|
-
return process.env.LOCALAPPDATA || process.env.APPDATA ||
|
|
34979
|
+
return process.env.LOCALAPPDATA || process.env.APPDATA || join5(homeDir(), "AppData", "Local");
|
|
34913
34980
|
}
|
|
34914
|
-
return
|
|
34981
|
+
return join5(homeDir(), ".local", "share");
|
|
34915
34982
|
}
|
|
34916
34983
|
function homeDir() {
|
|
34917
34984
|
if (process.platform === "win32")
|
|
@@ -34920,11 +34987,11 @@ function homeDir() {
|
|
|
34920
34987
|
}
|
|
34921
34988
|
function resolveLegacyStorageRoot(harness) {
|
|
34922
34989
|
if (harness === "pi")
|
|
34923
|
-
return
|
|
34924
|
-
return
|
|
34990
|
+
return join5(homeDir(), ".pi", "agent", "aft");
|
|
34991
|
+
return join5(dataHome(), "opencode", "storage", "plugin", "aft");
|
|
34925
34992
|
}
|
|
34926
34993
|
function resolveCortexKitStorageRoot() {
|
|
34927
|
-
return
|
|
34994
|
+
return join5(dataHome(), "cortexkit", "aft");
|
|
34928
34995
|
}
|
|
34929
34996
|
function tail(value) {
|
|
34930
34997
|
if (!value)
|
|
@@ -34938,26 +35005,26 @@ function spawnErrorLabel(error2) {
|
|
|
34938
35005
|
return [code, error2.message].filter(Boolean).join(": ");
|
|
34939
35006
|
}
|
|
34940
35007
|
function migrationLogPath(newRoot, harness, logger) {
|
|
34941
|
-
const desired =
|
|
35008
|
+
const desired = join5(newRoot, "logs", "migration", `${harness}-${Date.now()}.jsonl`);
|
|
34942
35009
|
try {
|
|
34943
|
-
|
|
35010
|
+
mkdirSync4(dirname2(desired), { recursive: true });
|
|
34944
35011
|
return desired;
|
|
34945
35012
|
} catch (err) {
|
|
34946
|
-
const fallback =
|
|
34947
|
-
logger?.warn?.(`Failed to create AFT migration log directory ${
|
|
35013
|
+
const fallback = join5(tmpdir(), `aft-migration-${harness}-${Date.now()}.jsonl`);
|
|
35014
|
+
logger?.warn?.(`Failed to create AFT migration log directory ${dirname2(desired)}: ${err instanceof Error ? err.message : String(err)}. ` + `Using fallback log path ${fallback}.`);
|
|
34948
35015
|
return fallback;
|
|
34949
35016
|
}
|
|
34950
35017
|
}
|
|
34951
35018
|
async function ensureStorageMigrated(opts) {
|
|
34952
35019
|
const legacyRoot = resolveLegacyStorageRoot(opts.harness);
|
|
34953
35020
|
const newRoot = resolveCortexKitStorageRoot();
|
|
34954
|
-
const targetMarker =
|
|
35021
|
+
const targetMarker = resolveHarnessStoragePath(newRoot, opts.harness, TARGET_MARKER);
|
|
34955
35022
|
const info = opts.logger?.info ?? opts.logger?.log;
|
|
34956
|
-
if (
|
|
35023
|
+
if (existsSync4(targetMarker)) {
|
|
34957
35024
|
info?.(`AFT storage already migrated for ${opts.harness}; using ${newRoot}`);
|
|
34958
35025
|
return;
|
|
34959
35026
|
}
|
|
34960
|
-
if (!
|
|
35027
|
+
if (!existsSync4(legacyRoot)) {
|
|
34961
35028
|
info?.(`AFT storage migration skipped for ${opts.harness}: no legacy data at ${legacyRoot}; ` + `using ${newRoot} for fresh install`);
|
|
34962
35029
|
return;
|
|
34963
35030
|
}
|
|
@@ -35007,8 +35074,8 @@ async function ensureStorageMigrated(opts) {
|
|
|
35007
35074
|
// ../aft-bridge/dist/onnx-runtime.js
|
|
35008
35075
|
import { execFileSync } from "node:child_process";
|
|
35009
35076
|
import { createHash as createHash2 } from "node:crypto";
|
|
35010
|
-
import { chmodSync as chmodSync3, closeSync as closeSync2, copyFileSync as copyFileSync2, createWriteStream as createWriteStream2, existsSync as
|
|
35011
|
-
import { dirname as
|
|
35077
|
+
import { chmodSync as chmodSync3, closeSync as closeSync2, copyFileSync as copyFileSync2, createWriteStream as createWriteStream2, existsSync as existsSync5, lstatSync, mkdirSync as mkdirSync5, openSync as openSync2, readdirSync, readFileSync as readFileSync2, readlinkSync, realpathSync, rmSync as rmSync2, statSync as statSync2, symlinkSync, unlinkSync as unlinkSync2, writeFileSync } from "node:fs";
|
|
35078
|
+
import { basename, dirname as dirname3, join as join6, relative, resolve } from "node:path";
|
|
35012
35079
|
import { Readable as Readable2 } from "node:stream";
|
|
35013
35080
|
import { pipeline as pipeline2 } from "node:stream/promises";
|
|
35014
35081
|
var ORT_VERSION = "1.24.4";
|
|
@@ -35074,9 +35141,9 @@ function getManualInstallHint() {
|
|
|
35074
35141
|
}
|
|
35075
35142
|
async function ensureOnnxRuntime(storageDir) {
|
|
35076
35143
|
const info = getPlatformInfo();
|
|
35077
|
-
const ortDir =
|
|
35078
|
-
const libPath =
|
|
35079
|
-
if (
|
|
35144
|
+
const ortDir = join6(storageDir, "onnxruntime", ORT_VERSION);
|
|
35145
|
+
const libPath = join6(ortDir, info?.libName ?? "libonnxruntime.dylib");
|
|
35146
|
+
if (existsSync5(libPath)) {
|
|
35080
35147
|
const meta = readOnnxInstalledMeta(ortDir);
|
|
35081
35148
|
if (meta?.sha256) {
|
|
35082
35149
|
try {
|
|
@@ -35105,9 +35172,9 @@ async function ensureOnnxRuntime(storageDir) {
|
|
|
35105
35172
|
warn(`ONNX Runtime auto-download not available for ${process.platform}/${process.arch}. Install manually: ${getManualInstallHint()}`);
|
|
35106
35173
|
return null;
|
|
35107
35174
|
}
|
|
35108
|
-
const onnxBaseDir =
|
|
35109
|
-
|
|
35110
|
-
const lockPath =
|
|
35175
|
+
const onnxBaseDir = join6(storageDir, "onnxruntime");
|
|
35176
|
+
mkdirSync5(onnxBaseDir, { recursive: true });
|
|
35177
|
+
const lockPath = join6(onnxBaseDir, ONNX_LOCK_FILE);
|
|
35111
35178
|
cleanupAbandonedStagingDirs(onnxBaseDir);
|
|
35112
35179
|
if (!acquireLock(lockPath)) {
|
|
35113
35180
|
warn(`ONNX Runtime install already in progress in another process (lock: ${lockPath}). Skipping.`);
|
|
@@ -35126,7 +35193,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
|
|
|
35126
35193
|
for (const entry of entries) {
|
|
35127
35194
|
if (!entry.startsWith(`${ORT_VERSION}.tmp.`))
|
|
35128
35195
|
continue;
|
|
35129
|
-
const stagingDir =
|
|
35196
|
+
const stagingDir = join6(onnxBaseDir, entry);
|
|
35130
35197
|
const parts = entry.split(".");
|
|
35131
35198
|
const pidStr = parts[parts.length - 2];
|
|
35132
35199
|
const pid = pidStr ? Number.parseInt(pidStr, 10) : NaN;
|
|
@@ -35158,7 +35225,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
|
|
|
35158
35225
|
}
|
|
35159
35226
|
function cleanupIncompleteTargetIfUnowned(ortDir) {
|
|
35160
35227
|
try {
|
|
35161
|
-
if (
|
|
35228
|
+
if (existsSync5(ortDir) && !existsSync5(join6(ortDir, ONNX_INSTALLED_META_FILE))) {
|
|
35162
35229
|
log(`[onnx] removing half-populated install dir ${ortDir} (no meta file)`);
|
|
35163
35230
|
rmSync2(ortDir, { recursive: true, force: true });
|
|
35164
35231
|
}
|
|
@@ -35168,6 +35235,14 @@ function cleanupIncompleteTargetIfUnowned(ortDir) {
|
|
|
35168
35235
|
}
|
|
35169
35236
|
var REQUIRED_ORT_MAJOR = 1;
|
|
35170
35237
|
var REQUIRED_ORT_MIN_MINOR = 20;
|
|
35238
|
+
var INVALID_ORT_VERSION = "<invalid>";
|
|
35239
|
+
function parseOnnxVersionFromPath(value) {
|
|
35240
|
+
const name = basename(value);
|
|
35241
|
+
const semverish = name.match(/\.(\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?)(?:\.dylib)?$/);
|
|
35242
|
+
if (semverish)
|
|
35243
|
+
return semverish[1].split(/[-+]/, 1)[0];
|
|
35244
|
+
return /\d+\.\d+\.\d+/.test(name) ? INVALID_ORT_VERSION : null;
|
|
35245
|
+
}
|
|
35171
35246
|
function detectOnnxVersion(libDir, libName) {
|
|
35172
35247
|
try {
|
|
35173
35248
|
const entries = readdirSync(libDir);
|
|
@@ -35175,23 +35250,23 @@ function detectOnnxVersion(libDir, libName) {
|
|
|
35175
35250
|
for (const entry of entries) {
|
|
35176
35251
|
if (!entry.startsWith(barePrefix))
|
|
35177
35252
|
continue;
|
|
35178
|
-
const
|
|
35179
|
-
if (
|
|
35180
|
-
return
|
|
35253
|
+
const version = parseOnnxVersionFromPath(entry);
|
|
35254
|
+
if (version)
|
|
35255
|
+
return version;
|
|
35181
35256
|
}
|
|
35182
|
-
const base =
|
|
35183
|
-
if (
|
|
35257
|
+
const base = join6(libDir, libName);
|
|
35258
|
+
if (existsSync5(base)) {
|
|
35184
35259
|
try {
|
|
35185
35260
|
const real = realpathSync(base);
|
|
35186
|
-
const
|
|
35187
|
-
if (
|
|
35188
|
-
return
|
|
35261
|
+
const version = parseOnnxVersionFromPath(real);
|
|
35262
|
+
if (version)
|
|
35263
|
+
return version;
|
|
35189
35264
|
} catch {}
|
|
35190
35265
|
try {
|
|
35191
35266
|
const target = readlinkSync(base);
|
|
35192
|
-
const
|
|
35193
|
-
if (
|
|
35194
|
-
return
|
|
35267
|
+
const version = parseOnnxVersionFromPath(target);
|
|
35268
|
+
if (version)
|
|
35269
|
+
return version;
|
|
35195
35270
|
} catch {}
|
|
35196
35271
|
}
|
|
35197
35272
|
} catch {}
|
|
@@ -35216,7 +35291,7 @@ function findSystemOnnxRuntime(libName) {
|
|
|
35216
35291
|
searchPaths.push("/usr/lib", "/usr/lib/x86_64-linux-gnu", "/usr/lib/aarch64-linux-gnu", "/usr/local/lib");
|
|
35217
35292
|
}
|
|
35218
35293
|
for (const dir of searchPaths) {
|
|
35219
|
-
if (!
|
|
35294
|
+
if (!existsSync5(join6(dir, libName)))
|
|
35220
35295
|
continue;
|
|
35221
35296
|
const version = detectOnnxVersion(dir, libName);
|
|
35222
35297
|
if (version && !isOnnxVersionCompatible(version)) {
|
|
@@ -35243,7 +35318,7 @@ async function downloadFileWithCap(url, destPath) {
|
|
|
35243
35318
|
if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES2) {
|
|
35244
35319
|
throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES2}`);
|
|
35245
35320
|
}
|
|
35246
|
-
|
|
35321
|
+
mkdirSync5(dirname3(destPath), { recursive: true });
|
|
35247
35322
|
let bytesWritten = 0;
|
|
35248
35323
|
const guard = new TransformStream({
|
|
35249
35324
|
transform(chunk, transformController) {
|
|
@@ -35273,11 +35348,11 @@ function validateExtractedTree(stagingRoot) {
|
|
|
35273
35348
|
const walk = (dir) => {
|
|
35274
35349
|
const entries = readdirSync(dir);
|
|
35275
35350
|
for (const entry of entries) {
|
|
35276
|
-
const fullPath =
|
|
35351
|
+
const fullPath = join6(dir, entry);
|
|
35277
35352
|
const lst = lstatSync(fullPath);
|
|
35278
35353
|
if (lst.isSymbolicLink()) {
|
|
35279
35354
|
const linkTarget = readlinkSync(fullPath);
|
|
35280
|
-
const resolvedTarget = resolve(
|
|
35355
|
+
const resolvedTarget = resolve(dirname3(fullPath), linkTarget);
|
|
35281
35356
|
const rel2 = relative(realRoot, resolvedTarget);
|
|
35282
35357
|
if (rel2.startsWith("..") || process.platform !== "win32" && rel2.startsWith("/")) {
|
|
35283
35358
|
throw new Error(`extracted symlink ${fullPath} points outside staging root: ${linkTarget}`);
|
|
@@ -35307,8 +35382,8 @@ async function downloadOnnxRuntime(info, targetDir) {
|
|
|
35307
35382
|
log(`Downloading ONNX Runtime v${ORT_VERSION} for ${process.platform}/${process.arch}...`);
|
|
35308
35383
|
const tmpDir = `${targetDir}.tmp.${process.pid}.${Date.now().toString(36)}`;
|
|
35309
35384
|
try {
|
|
35310
|
-
|
|
35311
|
-
const archivePath =
|
|
35385
|
+
mkdirSync5(tmpDir, { recursive: true });
|
|
35386
|
+
const archivePath = join6(tmpDir, `onnxruntime.${info.archiveType}`);
|
|
35312
35387
|
await downloadFileWithCap(url, archivePath);
|
|
35313
35388
|
const archiveSha256 = sha256File(archivePath);
|
|
35314
35389
|
log(`ONNX Runtime archive sha256=${archiveSha256}`);
|
|
@@ -35324,16 +35399,16 @@ async function downloadOnnxRuntime(info, targetDir) {
|
|
|
35324
35399
|
unlinkSync2(archivePath);
|
|
35325
35400
|
} catch {}
|
|
35326
35401
|
validateExtractedTree(tmpDir);
|
|
35327
|
-
const extractedDir =
|
|
35328
|
-
if (!
|
|
35402
|
+
const extractedDir = join6(tmpDir, info.assetName, "lib");
|
|
35403
|
+
if (!existsSync5(extractedDir)) {
|
|
35329
35404
|
throw new Error(`Expected directory not found: ${extractedDir}`);
|
|
35330
35405
|
}
|
|
35331
|
-
|
|
35406
|
+
mkdirSync5(targetDir, { recursive: true });
|
|
35332
35407
|
const libFiles = readdirSync(extractedDir).filter((f) => f.startsWith("libonnxruntime") || f.startsWith("onnxruntime"));
|
|
35333
35408
|
const realFiles = [];
|
|
35334
35409
|
const symlinks = [];
|
|
35335
35410
|
for (const libFile of libFiles) {
|
|
35336
|
-
const src =
|
|
35411
|
+
const src = join6(extractedDir, libFile);
|
|
35337
35412
|
try {
|
|
35338
35413
|
const stat = lstatSync(src);
|
|
35339
35414
|
log(`ORT extract: ${libFile} — isSymlink=${stat.isSymbolicLink()}, isFile=${stat.isFile()}, size=${stat.size}`);
|
|
@@ -35348,7 +35423,7 @@ async function downloadOnnxRuntime(info, targetDir) {
|
|
|
35348
35423
|
}
|
|
35349
35424
|
}
|
|
35350
35425
|
copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks);
|
|
35351
|
-
const libPath =
|
|
35426
|
+
const libPath = join6(targetDir, info.libName);
|
|
35352
35427
|
let libHash = null;
|
|
35353
35428
|
try {
|
|
35354
35429
|
libHash = sha256File(libPath);
|
|
@@ -35373,8 +35448,8 @@ async function downloadOnnxRuntime(info, targetDir) {
|
|
|
35373
35448
|
function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, copyFile = copyFileSync2) {
|
|
35374
35449
|
const requiredLibs = new Set([info.libName]);
|
|
35375
35450
|
for (const libFile of realFiles) {
|
|
35376
|
-
const src =
|
|
35377
|
-
const dst =
|
|
35451
|
+
const src = join6(extractedDir, libFile);
|
|
35452
|
+
const dst = join6(targetDir, libFile);
|
|
35378
35453
|
try {
|
|
35379
35454
|
copyFile(src, dst);
|
|
35380
35455
|
if (process.platform !== "win32") {
|
|
@@ -35389,7 +35464,7 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
|
|
|
35389
35464
|
}
|
|
35390
35465
|
}
|
|
35391
35466
|
for (const link of symlinks) {
|
|
35392
|
-
const dst =
|
|
35467
|
+
const dst = join6(targetDir, link.name);
|
|
35393
35468
|
try {
|
|
35394
35469
|
unlinkSync2(dst);
|
|
35395
35470
|
} catch {}
|
|
@@ -35403,8 +35478,8 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
|
|
|
35403
35478
|
log(`ORT extract: failed to symlink optional ${link.name}: ${symlinkErr}`);
|
|
35404
35479
|
}
|
|
35405
35480
|
}
|
|
35406
|
-
const requiredPath =
|
|
35407
|
-
if (!
|
|
35481
|
+
const requiredPath = join6(targetDir, info.libName);
|
|
35482
|
+
if (!existsSync5(requiredPath)) {
|
|
35408
35483
|
rmSync2(targetDir, { recursive: true, force: true });
|
|
35409
35484
|
throw new Error(`Required ONNX Runtime library missing after install: ${requiredPath}`);
|
|
35410
35485
|
}
|
|
@@ -35430,17 +35505,17 @@ function writeOnnxInstalledMeta(installDir, version, sha256, archiveSha256) {
|
|
|
35430
35505
|
...sha256 ? { sha256 } : {},
|
|
35431
35506
|
archiveSha256
|
|
35432
35507
|
};
|
|
35433
|
-
writeFileSync(
|
|
35508
|
+
writeFileSync(join6(installDir, ONNX_INSTALLED_META_FILE), JSON.stringify(meta), "utf8");
|
|
35434
35509
|
} catch (err) {
|
|
35435
35510
|
log(`[onnx] failed to write installed-meta in ${installDir}: ${err}`);
|
|
35436
35511
|
}
|
|
35437
35512
|
}
|
|
35438
35513
|
function readOnnxInstalledMeta(installDir) {
|
|
35439
|
-
const path =
|
|
35514
|
+
const path = join6(installDir, ONNX_INSTALLED_META_FILE);
|
|
35440
35515
|
try {
|
|
35441
35516
|
if (!statSync2(path).isFile())
|
|
35442
35517
|
return null;
|
|
35443
|
-
const raw =
|
|
35518
|
+
const raw = readFileSync2(path, "utf8");
|
|
35444
35519
|
const parsed = JSON.parse(raw);
|
|
35445
35520
|
if (typeof parsed.version !== "string" || parsed.version.length === 0)
|
|
35446
35521
|
return null;
|
|
@@ -35456,7 +35531,7 @@ function readOnnxInstalledMeta(installDir) {
|
|
|
35456
35531
|
}
|
|
35457
35532
|
function sha256File(path) {
|
|
35458
35533
|
const hash = createHash2("sha256");
|
|
35459
|
-
hash.update(
|
|
35534
|
+
hash.update(readFileSync2(path));
|
|
35460
35535
|
return hash.digest("hex");
|
|
35461
35536
|
}
|
|
35462
35537
|
function acquireLock(lockPath) {
|
|
@@ -35484,7 +35559,7 @@ ${new Date().toISOString()}
|
|
|
35484
35559
|
let owningPid = null;
|
|
35485
35560
|
let lockMtimeMs = 0;
|
|
35486
35561
|
try {
|
|
35487
|
-
const raw =
|
|
35562
|
+
const raw = readFileSync2(lockPath, "utf8");
|
|
35488
35563
|
const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
|
|
35489
35564
|
const parsed = Number.parseInt(firstLine, 10);
|
|
35490
35565
|
if (Number.isFinite(parsed) && parsed > 0)
|
|
@@ -35510,7 +35585,7 @@ function releaseLock(lockPath) {
|
|
|
35510
35585
|
try {
|
|
35511
35586
|
let owningPid = null;
|
|
35512
35587
|
try {
|
|
35513
|
-
const raw =
|
|
35588
|
+
const raw = readFileSync2(lockPath, "utf8");
|
|
35514
35589
|
const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
|
|
35515
35590
|
const parsed = Number.parseInt(firstLine, 10);
|
|
35516
35591
|
if (Number.isFinite(parsed) && parsed > 0)
|
|
@@ -35556,6 +35631,7 @@ var DEFAULT_MAX_POOL_SIZE = 8;
|
|
|
35556
35631
|
var CLEANUP_INTERVAL_MS = 60 * 1000;
|
|
35557
35632
|
class BridgePool {
|
|
35558
35633
|
bridges = new Map;
|
|
35634
|
+
staleBridges = new Set;
|
|
35559
35635
|
binaryPath;
|
|
35560
35636
|
maxPoolSize;
|
|
35561
35637
|
idleTimeoutMs;
|
|
@@ -35578,6 +35654,7 @@ class BridgePool {
|
|
|
35578
35654
|
onConfigureWarnings: options.onConfigureWarnings,
|
|
35579
35655
|
onBashCompletion: options.onBashCompletion,
|
|
35580
35656
|
onBashLongRunning: options.onBashLongRunning,
|
|
35657
|
+
onBashPatternMatch: options.onBashPatternMatch,
|
|
35581
35658
|
errorPrefix: options.errorPrefix,
|
|
35582
35659
|
logger: options.logger
|
|
35583
35660
|
};
|
|
@@ -35629,6 +35706,12 @@ class BridgePool {
|
|
|
35629
35706
|
this.bridges.delete(dir);
|
|
35630
35707
|
}
|
|
35631
35708
|
}
|
|
35709
|
+
for (const bridge of this.staleBridges) {
|
|
35710
|
+
if (bridge.hasPendingRequests())
|
|
35711
|
+
continue;
|
|
35712
|
+
bridge.shutdown().catch((err) => this.error("stale cleanup shutdown failed:", err));
|
|
35713
|
+
this.staleBridges.delete(bridge);
|
|
35714
|
+
}
|
|
35632
35715
|
}
|
|
35633
35716
|
evictLRU() {
|
|
35634
35717
|
let oldestDir = null;
|
|
@@ -35652,14 +35735,21 @@ class BridgePool {
|
|
|
35652
35735
|
clearInterval(this.cleanupTimer);
|
|
35653
35736
|
this.cleanupTimer = null;
|
|
35654
35737
|
}
|
|
35655
|
-
const shutdowns =
|
|
35738
|
+
const shutdowns = [
|
|
35739
|
+
...Array.from(this.bridges.values(), (e) => e.bridge.shutdown()),
|
|
35740
|
+
...Array.from(this.staleBridges.values(), (bridge) => bridge.shutdown())
|
|
35741
|
+
];
|
|
35656
35742
|
this.bridges.clear();
|
|
35743
|
+
this.staleBridges.clear();
|
|
35657
35744
|
await Promise.allSettled(shutdowns);
|
|
35658
35745
|
}
|
|
35659
35746
|
async replaceBinary(newPath) {
|
|
35660
35747
|
this.binaryPath = newPath;
|
|
35748
|
+
for (const entry of this.bridges.values()) {
|
|
35749
|
+
this.staleBridges.add(entry.bridge);
|
|
35750
|
+
}
|
|
35661
35751
|
this.bridges.clear();
|
|
35662
|
-
this.log(`Binary path updated to ${newPath}.
|
|
35752
|
+
this.log(`Binary path updated to ${newPath}. Active bridges marked stale — next calls will use the new binary.`);
|
|
35663
35753
|
return newPath;
|
|
35664
35754
|
}
|
|
35665
35755
|
log(message, meta) {
|
|
@@ -35709,11 +35799,11 @@ function normalizeKey(projectRoot) {
|
|
|
35709
35799
|
}
|
|
35710
35800
|
}
|
|
35711
35801
|
// ../aft-bridge/dist/url-fetch.js
|
|
35712
|
-
import { createHash as createHash3 } from "node:crypto";
|
|
35802
|
+
import { createHash as createHash3, randomBytes } from "node:crypto";
|
|
35713
35803
|
import { lookup } from "node:dns/promises";
|
|
35714
|
-
import { existsSync as
|
|
35804
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync6, readdirSync as readdirSync2, readFileSync as readFileSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
35715
35805
|
import { isIP } from "node:net";
|
|
35716
|
-
import { join as
|
|
35806
|
+
import { join as join7 } from "node:path";
|
|
35717
35807
|
|
|
35718
35808
|
// ../../node_modules/.bun/undici@7.25.0/node_modules/undici/index.js
|
|
35719
35809
|
var __filename = "/home/runner/work/aft/aft/node_modules/.bun/undici@7.25.0/node_modules/undici/index.js";
|
|
@@ -35863,16 +35953,16 @@ var FETCH_TIMEOUT_MS = 30000;
|
|
|
35863
35953
|
var BODY_CHUNK_TIMEOUT_MS = 15000;
|
|
35864
35954
|
var MAX_REDIRECTS = 5;
|
|
35865
35955
|
function cacheDir(storageDir) {
|
|
35866
|
-
return
|
|
35956
|
+
return join7(storageDir, "url_cache");
|
|
35867
35957
|
}
|
|
35868
35958
|
function hashUrl(url) {
|
|
35869
35959
|
return createHash3("sha256").update(url).digest("hex").slice(0, 16);
|
|
35870
35960
|
}
|
|
35871
35961
|
function metaPath(storageDir, hash) {
|
|
35872
|
-
return
|
|
35962
|
+
return join7(cacheDir(storageDir), `${hash}.meta.json`);
|
|
35873
35963
|
}
|
|
35874
35964
|
function contentPath(storageDir, hash, extension) {
|
|
35875
|
-
return
|
|
35965
|
+
return join7(cacheDir(storageDir), `${hash}${extension}`);
|
|
35876
35966
|
}
|
|
35877
35967
|
function _isPrivateIpv4(address) {
|
|
35878
35968
|
const parts = address.split(".").map((part) => Number.parseInt(part, 10));
|
|
@@ -35997,13 +36087,21 @@ function resolveRedirectUrl(currentUrl, location) {
|
|
|
35997
36087
|
}
|
|
35998
36088
|
return new URL(location, currentUrl);
|
|
35999
36089
|
}
|
|
36090
|
+
var isBunRuntime = typeof globalThis.Bun !== "undefined";
|
|
36000
36091
|
async function fetchWithRedirects(startUrl, allowPrivate, deps = {}) {
|
|
36001
36092
|
let currentUrl = startUrl;
|
|
36002
|
-
const fetchImpl2 = deps.fetchImpl ?? $fetch;
|
|
36093
|
+
const fetchImpl2 = deps.fetchImpl ?? (isBunRuntime ? globalThis.fetch : $fetch);
|
|
36003
36094
|
const dnsLookup = deps.lookup ?? lookup;
|
|
36004
36095
|
for (let redirectCount = 0;redirectCount <= MAX_REDIRECTS; redirectCount++) {
|
|
36005
36096
|
const validatedIp = await assertPublicUrl(currentUrl, allowPrivate, dnsLookup);
|
|
36006
|
-
|
|
36097
|
+
let dispatcher;
|
|
36098
|
+
if (validatedIp) {
|
|
36099
|
+
if (deps.dispatcherFactory) {
|
|
36100
|
+
dispatcher = deps.dispatcherFactory(validatedIp);
|
|
36101
|
+
} else if (!isBunRuntime) {
|
|
36102
|
+
dispatcher = createPinnedDispatcher(validatedIp);
|
|
36103
|
+
}
|
|
36104
|
+
}
|
|
36007
36105
|
const controller = new AbortController;
|
|
36008
36106
|
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
36009
36107
|
let response;
|
|
@@ -36014,7 +36112,7 @@ async function fetchWithRedirects(startUrl, allowPrivate, deps = {}) {
|
|
|
36014
36112
|
dispatcher,
|
|
36015
36113
|
headers: {
|
|
36016
36114
|
"user-agent": "aft-opencode-plugin",
|
|
36017
|
-
accept: "application/vnd.github.raw, text/markdown, text/x-markdown, text/html;q=0.9, text/plain;q=0.5"
|
|
36115
|
+
accept: "application/vnd.github.raw, text/markdown, text/x-markdown, text/html;q=0.9, application/json;q=0.8, text/plain;q=0.5"
|
|
36018
36116
|
}
|
|
36019
36117
|
});
|
|
36020
36118
|
} catch (err) {
|
|
@@ -36055,6 +36153,9 @@ function resolveExtension(contentType) {
|
|
|
36055
36153
|
if (lower === "text/plain") {
|
|
36056
36154
|
return ".md";
|
|
36057
36155
|
}
|
|
36156
|
+
if (lower === "application/json" || lower === "application/ld+json" || lower.endsWith("+json")) {
|
|
36157
|
+
return ".json";
|
|
36158
|
+
}
|
|
36058
36159
|
return null;
|
|
36059
36160
|
}
|
|
36060
36161
|
async function fetchUrlToTempFile(url, storageDir, options = {}) {
|
|
@@ -36070,15 +36171,15 @@ async function fetchUrlToTempFile(url, storageDir, options = {}) {
|
|
|
36070
36171
|
const allowPrivate = options.allowPrivate === true;
|
|
36071
36172
|
await assertPublicUrl(parsed, allowPrivate, options.lookup);
|
|
36072
36173
|
const dir = cacheDir(storageDir);
|
|
36073
|
-
|
|
36174
|
+
mkdirSync6(dir, { recursive: true });
|
|
36074
36175
|
const hash = hashUrl(url);
|
|
36075
36176
|
const metaFile = metaPath(storageDir, hash);
|
|
36076
|
-
if (
|
|
36177
|
+
if (existsSync6(metaFile)) {
|
|
36077
36178
|
try {
|
|
36078
|
-
const meta2 = JSON.parse(
|
|
36179
|
+
const meta2 = JSON.parse(readFileSync3(metaFile, "utf8"));
|
|
36079
36180
|
const age = Date.now() - meta2.fetchedAt;
|
|
36080
36181
|
const cached = contentPath(storageDir, hash, meta2.extension);
|
|
36081
|
-
if (age < CACHE_TTL_MS &&
|
|
36182
|
+
if (age < CACHE_TTL_MS && existsSync6(cached)) {
|
|
36082
36183
|
log(`URL cache hit: ${url} (${Math.round(age / 1000)}s old)`);
|
|
36083
36184
|
return cached;
|
|
36084
36185
|
}
|
|
@@ -36144,39 +36245,40 @@ async function fetchUrlToTempFile(url, storageDir, options = {}) {
|
|
|
36144
36245
|
}
|
|
36145
36246
|
const body = Buffer.concat(chunks);
|
|
36146
36247
|
const contentFile = contentPath(storageDir, hash, extension);
|
|
36147
|
-
const
|
|
36248
|
+
const nonce = randomBytes(8).toString("hex");
|
|
36249
|
+
const tmpContent = `${contentFile}.tmp-${process.pid}-${nonce}`;
|
|
36148
36250
|
writeFileSync2(tmpContent, body);
|
|
36149
|
-
const { renameSync:
|
|
36150
|
-
|
|
36251
|
+
const { renameSync: renameSync4 } = await import("node:fs");
|
|
36252
|
+
renameSync4(tmpContent, contentFile);
|
|
36151
36253
|
const meta = {
|
|
36152
36254
|
url,
|
|
36153
36255
|
contentType,
|
|
36154
36256
|
extension,
|
|
36155
36257
|
fetchedAt: Date.now()
|
|
36156
36258
|
};
|
|
36157
|
-
const tmpMeta = `${metaFile}.tmp-${process.pid}`;
|
|
36259
|
+
const tmpMeta = `${metaFile}.tmp-${process.pid}-${nonce}`;
|
|
36158
36260
|
writeFileSync2(tmpMeta, JSON.stringify(meta));
|
|
36159
|
-
|
|
36261
|
+
renameSync4(tmpMeta, metaFile);
|
|
36160
36262
|
log(`URL cached (${total} bytes): ${url}`);
|
|
36161
36263
|
return contentFile;
|
|
36162
36264
|
}
|
|
36163
36265
|
function cleanupUrlCache(storageDir) {
|
|
36164
36266
|
const dir = cacheDir(storageDir);
|
|
36165
|
-
if (!
|
|
36267
|
+
if (!existsSync6(dir))
|
|
36166
36268
|
return;
|
|
36167
36269
|
let removed = 0;
|
|
36168
36270
|
try {
|
|
36169
36271
|
for (const entry of readdirSync2(dir)) {
|
|
36170
36272
|
if (!entry.endsWith(".meta.json"))
|
|
36171
36273
|
continue;
|
|
36172
|
-
const metaFile =
|
|
36274
|
+
const metaFile = join7(dir, entry);
|
|
36173
36275
|
try {
|
|
36174
|
-
const meta = JSON.parse(
|
|
36276
|
+
const meta = JSON.parse(readFileSync3(metaFile, "utf8"));
|
|
36175
36277
|
const age = Date.now() - meta.fetchedAt;
|
|
36176
36278
|
if (age > CACHE_TTL_MS) {
|
|
36177
36279
|
const hash = entry.slice(0, -".meta.json".length);
|
|
36178
36280
|
const content = contentPath(storageDir, hash, meta.extension);
|
|
36179
|
-
if (
|
|
36281
|
+
if (existsSync6(content))
|
|
36180
36282
|
unlinkSync3(content);
|
|
36181
36283
|
unlinkSync3(metaFile);
|
|
36182
36284
|
removed++;
|
|
@@ -36247,7 +36349,7 @@ function formatZoomText(targetLabel, response) {
|
|
|
36247
36349
|
`);
|
|
36248
36350
|
}
|
|
36249
36351
|
// src/bg-notifications.ts
|
|
36250
|
-
import { createHash as createHash4, randomUUID } from "node:crypto";
|
|
36352
|
+
import { createHash as createHash4, randomUUID as randomUUID2 } from "node:crypto";
|
|
36251
36353
|
|
|
36252
36354
|
// src/logger.ts
|
|
36253
36355
|
import * as fs from "node:fs";
|
|
@@ -36346,11 +36448,6 @@ function extractMessages(response) {
|
|
|
36346
36448
|
return response.data;
|
|
36347
36449
|
return [];
|
|
36348
36450
|
}
|
|
36349
|
-
function getRole(message) {
|
|
36350
|
-
if (!isRecord(message) || !isRecord(message.info))
|
|
36351
|
-
return;
|
|
36352
|
-
return typeof message.info.role === "string" ? message.info.role : undefined;
|
|
36353
|
-
}
|
|
36354
36451
|
function extractFromMessage(message) {
|
|
36355
36452
|
if (!isRecord(message) || !isRecord(message.info))
|
|
36356
36453
|
return null;
|
|
@@ -36401,16 +36498,6 @@ async function resolvePromptContext(client, sessionId) {
|
|
|
36401
36498
|
if (messages.length === 0)
|
|
36402
36499
|
return null;
|
|
36403
36500
|
let result = {};
|
|
36404
|
-
for (let i = messages.length - 1;i >= 0; i -= 1) {
|
|
36405
|
-
if (getRole(messages[i]) !== "assistant")
|
|
36406
|
-
continue;
|
|
36407
|
-
const ctx = extractFromMessage(messages[i]);
|
|
36408
|
-
if (!ctx)
|
|
36409
|
-
continue;
|
|
36410
|
-
result = mergeContexts(result, ctx);
|
|
36411
|
-
if (isComplete(result))
|
|
36412
|
-
return result;
|
|
36413
|
-
}
|
|
36414
36501
|
for (let i = messages.length - 1;i >= 0; i -= 1) {
|
|
36415
36502
|
const ctx = extractFromMessage(messages[i]);
|
|
36416
36503
|
if (!ctx)
|
|
@@ -36430,6 +36517,13 @@ var clientCache = new Map;
|
|
|
36430
36517
|
function cacheKey(serverUrl, directory) {
|
|
36431
36518
|
return `${serverUrl}|${directory}`;
|
|
36432
36519
|
}
|
|
36520
|
+
function normalizeServerUrl(serverUrl) {
|
|
36521
|
+
try {
|
|
36522
|
+
return new URL(serverUrl).toString();
|
|
36523
|
+
} catch {
|
|
36524
|
+
return serverUrl;
|
|
36525
|
+
}
|
|
36526
|
+
}
|
|
36433
36527
|
function serverAuthHeaders() {
|
|
36434
36528
|
const password = process.env.OPENCODE_SERVER_PASSWORD;
|
|
36435
36529
|
if (!password)
|
|
@@ -36453,30 +36547,46 @@ function getLiveServerClient(serverUrl, directory) {
|
|
|
36453
36547
|
clientCache.set(key, client);
|
|
36454
36548
|
return client;
|
|
36455
36549
|
}
|
|
36550
|
+
var liveServerWakeAvailableByServerUrl = new Map;
|
|
36551
|
+
var legacyLiveServerWakeAvailable = false;
|
|
36456
36552
|
async function probeServerReachable(serverUrl, timeoutMs = 1500) {
|
|
36457
36553
|
if (!serverUrl)
|
|
36458
36554
|
return false;
|
|
36555
|
+
const normalizedServerUrl = normalizeServerUrl(serverUrl);
|
|
36459
36556
|
const controller = new AbortController;
|
|
36460
36557
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
36558
|
+
let reachable = false;
|
|
36461
36559
|
try {
|
|
36462
36560
|
const probeUrl = new URL("/session", serverUrl).toString();
|
|
36463
36561
|
const res = await globalThis.fetch(probeUrl, {
|
|
36464
36562
|
method: "GET",
|
|
36563
|
+
headers: serverAuthHeaders(),
|
|
36465
36564
|
signal: controller.signal
|
|
36466
36565
|
});
|
|
36467
|
-
|
|
36566
|
+
reachable = res.ok || res.status === 401 || res.status === 403;
|
|
36468
36567
|
} catch {
|
|
36469
|
-
|
|
36568
|
+
reachable = false;
|
|
36470
36569
|
} finally {
|
|
36471
36570
|
clearTimeout(timer);
|
|
36571
|
+
liveServerWakeAvailableByServerUrl.set(normalizedServerUrl, reachable);
|
|
36472
36572
|
}
|
|
36573
|
+
return reachable;
|
|
36473
36574
|
}
|
|
36474
|
-
|
|
36475
|
-
|
|
36476
|
-
|
|
36575
|
+
function setLiveServerWakeAvailable(serverUrlOrAvailable, available) {
|
|
36576
|
+
if (typeof serverUrlOrAvailable === "boolean") {
|
|
36577
|
+
legacyLiveServerWakeAvailable = serverUrlOrAvailable;
|
|
36578
|
+
return;
|
|
36579
|
+
}
|
|
36580
|
+
if (!serverUrlOrAvailable) {
|
|
36581
|
+
legacyLiveServerWakeAvailable = available ?? false;
|
|
36582
|
+
return;
|
|
36583
|
+
}
|
|
36584
|
+
liveServerWakeAvailableByServerUrl.set(normalizeServerUrl(serverUrlOrAvailable), available ?? false);
|
|
36477
36585
|
}
|
|
36478
|
-
function useLiveServerWake() {
|
|
36479
|
-
|
|
36586
|
+
function useLiveServerWake(serverUrl) {
|
|
36587
|
+
if (!serverUrl)
|
|
36588
|
+
return legacyLiveServerWakeAvailable;
|
|
36589
|
+
return liveServerWakeAvailableByServerUrl.get(normalizeServerUrl(serverUrl)) ?? false;
|
|
36480
36590
|
}
|
|
36481
36591
|
|
|
36482
36592
|
// src/bg-notifications.ts
|
|
@@ -36571,7 +36681,7 @@ function markExplicitControl(sessionID, taskId, trackOutstanding = true) {
|
|
|
36571
36681
|
if (idx >= 0) {
|
|
36572
36682
|
const completion = state.pendingCompletions[idx];
|
|
36573
36683
|
state.pendingCompletions.splice(idx, 1);
|
|
36574
|
-
queuePendingPatternMatch(state, completionToExitPattern(completion));
|
|
36684
|
+
queuePendingPatternMatch(state, completionToExitPattern(completion, true));
|
|
36575
36685
|
state.wakeDeferredTaskIds.delete(taskId);
|
|
36576
36686
|
}
|
|
36577
36687
|
}
|
|
@@ -36599,7 +36709,7 @@ function routeExplicitControlCompletions(state) {
|
|
|
36599
36709
|
state.outstandingTaskIds.delete(completion.task_id);
|
|
36600
36710
|
state.explicitControlTasks.delete(completion.task_id);
|
|
36601
36711
|
state.wakeDeferredTaskIds.delete(completion.task_id);
|
|
36602
|
-
queuePendingPatternMatch(state, completionToExitPattern(completion));
|
|
36712
|
+
queuePendingPatternMatch(state, completionToExitPattern(completion, true));
|
|
36603
36713
|
} else {
|
|
36604
36714
|
remaining.push(completion);
|
|
36605
36715
|
}
|
|
@@ -36626,7 +36736,7 @@ function ingestBgCompletions(sessionID, completions) {
|
|
|
36626
36736
|
if (state.explicitControlTasks.has(completion.task_id)) {
|
|
36627
36737
|
state.outstandingTaskIds.delete(completion.task_id);
|
|
36628
36738
|
state.explicitControlTasks.delete(completion.task_id);
|
|
36629
|
-
queuePendingPatternMatch(state, completionToExitPattern(completion));
|
|
36739
|
+
queuePendingPatternMatch(state, completionToExitPattern(completion, true));
|
|
36630
36740
|
continue;
|
|
36631
36741
|
}
|
|
36632
36742
|
if (!state.outstandingTaskIds.has(completion.task_id)) {
|
|
@@ -36666,6 +36776,8 @@ async function appendInTurnBgCompletions(drainContext, output) {
|
|
|
36666
36776
|
if (state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0 && state.pendingPatternMatches.length === 0)
|
|
36667
36777
|
return;
|
|
36668
36778
|
const deliveredCompletions = [...state.pendingCompletions];
|
|
36779
|
+
const deliveredPatternMatches = [...state.pendingPatternMatches];
|
|
36780
|
+
const completionAcks = completionAcksForDelivery(deliveredCompletions, deliveredPatternMatches);
|
|
36669
36781
|
const reminder = formatCombinedSystemReminder(state.pendingCompletions, state.pendingLongRunning, state.pendingPatternMatches);
|
|
36670
36782
|
output.output = appendReminder(output.output ?? "", reminder);
|
|
36671
36783
|
sessionLog(drainContext.sessionID, `${LOG_PREFIX} in-turn append`, {
|
|
@@ -36683,7 +36795,7 @@ async function appendInTurnBgCompletions(drainContext, output) {
|
|
|
36683
36795
|
state.pendingPatternMatches = [];
|
|
36684
36796
|
state.wakeRetryAttempts = 0;
|
|
36685
36797
|
state.wakeHardStopped = false;
|
|
36686
|
-
await ackCompletions(drainContext,
|
|
36798
|
+
await ackCompletions(drainContext, completionAcks);
|
|
36687
36799
|
if (state.debounceTimer) {
|
|
36688
36800
|
clearTimeout(state.debounceTimer);
|
|
36689
36801
|
state.debounceTimer = null;
|
|
@@ -36705,86 +36817,112 @@ async function triggerWakeIfPending(drainContext, skipDrain, includeDeferredComp
|
|
|
36705
36817
|
if (!hasWakeEligiblePending(state, includeDeferredCompletions))
|
|
36706
36818
|
return;
|
|
36707
36819
|
scheduleWake(state, async (reminder, deliveredCompletions) => {
|
|
36708
|
-
|
|
36709
|
-
|
|
36710
|
-
if (useLiveServerWake() && drainContext.serverUrl) {
|
|
36711
|
-
client = getLiveServerClient(drainContext.serverUrl, drainContext.directory);
|
|
36712
|
-
clientPath = "live-server";
|
|
36713
|
-
} else {
|
|
36820
|
+
const taskIDs = deliveredCompletions.map((completion) => completion.task_id);
|
|
36821
|
+
const getInProcessClient = () => {
|
|
36714
36822
|
if (!drainContext.client) {
|
|
36715
36823
|
sessionWarn(drainContext.sessionID, `${LOG_PREFIX} wake client unavailable`, {
|
|
36716
36824
|
event: "bash_completion_wake_client_unavailable",
|
|
36717
|
-
task_ids:
|
|
36825
|
+
task_ids: taskIDs,
|
|
36718
36826
|
directory: drainContext.directory,
|
|
36719
36827
|
attempt: state.wakeRetryAttempts + 1
|
|
36720
36828
|
});
|
|
36721
36829
|
throw new Error("no wake transport available: live-server unreachable and input.client absent");
|
|
36722
36830
|
}
|
|
36723
|
-
|
|
36724
|
-
clientPath = "in-process-fallback";
|
|
36725
|
-
}
|
|
36726
|
-
if (typeof client.session?.promptAsync !== "function") {
|
|
36727
|
-
throw new Error(`wake client.session.promptAsync is unavailable (path=${clientPath})`);
|
|
36728
|
-
}
|
|
36729
|
-
const promptContext = await resolvePromptContext(client, drainContext.sessionID);
|
|
36730
|
-
const body = {
|
|
36731
|
-
noReply: false,
|
|
36732
|
-
parts: [{ type: "text", text: reminder }]
|
|
36831
|
+
return drainContext.client;
|
|
36733
36832
|
};
|
|
36734
|
-
|
|
36735
|
-
|
|
36736
|
-
|
|
36737
|
-
|
|
36738
|
-
|
|
36739
|
-
|
|
36833
|
+
const sendPrompt = async (client, clientPath) => {
|
|
36834
|
+
if (typeof client.session?.promptAsync !== "function") {
|
|
36835
|
+
throw new Error(`wake client.session.promptAsync is unavailable (path=${clientPath})`);
|
|
36836
|
+
}
|
|
36837
|
+
const promptContext = await resolvePromptContext(client, drainContext.sessionID);
|
|
36838
|
+
const body = {
|
|
36839
|
+
noReply: false,
|
|
36840
|
+
parts: [{ type: "text", text: reminder }]
|
|
36740
36841
|
};
|
|
36741
|
-
|
|
36742
|
-
|
|
36743
|
-
|
|
36744
|
-
|
|
36745
|
-
const taskIDs = deliveredCompletions.map((c) => c.task_id);
|
|
36746
|
-
const wakeMeta = {
|
|
36747
|
-
delivery_id: deliveryID,
|
|
36748
|
-
attempt: state.wakeRetryAttempts + 1,
|
|
36749
|
-
task_ids: taskIDs,
|
|
36750
|
-
directory: drainContext.directory,
|
|
36751
|
-
reminder_sha256: hashReminder(reminder),
|
|
36752
|
-
reminder_chars: reminder.length,
|
|
36753
|
-
wake_client_path: clientPath,
|
|
36754
|
-
prompt_context: promptContext ? {
|
|
36755
|
-
agent: promptContext.agent,
|
|
36756
|
-
model: promptContext.model ? {
|
|
36842
|
+
if (promptContext?.agent)
|
|
36843
|
+
body.agent = promptContext.agent;
|
|
36844
|
+
if (promptContext?.model) {
|
|
36845
|
+
body.model = {
|
|
36757
36846
|
providerID: promptContext.model.providerID,
|
|
36758
36847
|
modelID: promptContext.model.modelID
|
|
36759
|
-
}
|
|
36760
|
-
|
|
36761
|
-
|
|
36762
|
-
|
|
36763
|
-
|
|
36764
|
-
|
|
36765
|
-
|
|
36766
|
-
|
|
36767
|
-
|
|
36768
|
-
|
|
36769
|
-
|
|
36770
|
-
|
|
36848
|
+
};
|
|
36849
|
+
}
|
|
36850
|
+
if (promptContext?.variant)
|
|
36851
|
+
body.variant = promptContext.variant;
|
|
36852
|
+
const deliveryID2 = `aftdel_${randomUUID2()}`;
|
|
36853
|
+
const wakeMeta = {
|
|
36854
|
+
delivery_id: deliveryID2,
|
|
36855
|
+
attempt: state.wakeRetryAttempts + 1,
|
|
36856
|
+
task_ids: taskIDs,
|
|
36857
|
+
directory: drainContext.directory,
|
|
36858
|
+
reminder_sha256: hashReminder(reminder),
|
|
36859
|
+
reminder_chars: reminder.length,
|
|
36860
|
+
wake_client_path: clientPath,
|
|
36861
|
+
prompt_context: promptContext ? {
|
|
36862
|
+
agent: promptContext.agent,
|
|
36863
|
+
model: promptContext.model ? {
|
|
36864
|
+
providerID: promptContext.model.providerID,
|
|
36865
|
+
modelID: promptContext.model.modelID
|
|
36866
|
+
} : null,
|
|
36867
|
+
variant: promptContext.variant ?? null
|
|
36868
|
+
} : null
|
|
36869
|
+
};
|
|
36870
|
+
sessionLog(drainContext.sessionID, `${LOG_PREFIX} wake promptAsync start`, {
|
|
36871
|
+
event: "bash_completion_wake_prompt_async_start",
|
|
36872
|
+
...wakeMeta
|
|
36771
36873
|
});
|
|
36772
|
-
|
|
36773
|
-
|
|
36774
|
-
|
|
36775
|
-
|
|
36874
|
+
try {
|
|
36875
|
+
await client.session.promptAsync({
|
|
36876
|
+
path: { id: drainContext.sessionID },
|
|
36877
|
+
body
|
|
36878
|
+
});
|
|
36879
|
+
} catch (err) {
|
|
36880
|
+
sessionWarn(drainContext.sessionID, `${LOG_PREFIX} wake promptAsync error`, {
|
|
36881
|
+
event: "bash_completion_wake_prompt_async_error",
|
|
36882
|
+
delivery_id: deliveryID2,
|
|
36883
|
+
attempt: state.wakeRetryAttempts + 1,
|
|
36884
|
+
task_ids: taskIDs,
|
|
36885
|
+
wake_client_path: clientPath,
|
|
36886
|
+
error: err instanceof Error ? err.message : String(err)
|
|
36887
|
+
});
|
|
36888
|
+
throw err;
|
|
36889
|
+
}
|
|
36890
|
+
sessionLog(drainContext.sessionID, `${LOG_PREFIX} wake promptAsync ok`, {
|
|
36891
|
+
event: "bash_completion_wake_prompt_async_ok",
|
|
36892
|
+
delivery_id: deliveryID2,
|
|
36776
36893
|
attempt: state.wakeRetryAttempts + 1,
|
|
36777
36894
|
task_ids: taskIDs,
|
|
36778
|
-
|
|
36895
|
+
wake_client_path: clientPath
|
|
36779
36896
|
});
|
|
36780
|
-
|
|
36897
|
+
return deliveryID2;
|
|
36898
|
+
};
|
|
36899
|
+
if (useLiveServerWake(drainContext.serverUrl) && drainContext.serverUrl) {
|
|
36900
|
+
try {
|
|
36901
|
+
const liveClient = getLiveServerClient(drainContext.serverUrl, drainContext.directory);
|
|
36902
|
+
const deliveryID2 = await sendPrompt(liveClient, "live-server");
|
|
36903
|
+
await ackCompletions(drainContext, deliveredCompletions, deliveryID2);
|
|
36904
|
+
return;
|
|
36905
|
+
} catch (err) {
|
|
36906
|
+
setLiveServerWakeAvailable(drainContext.serverUrl, false);
|
|
36907
|
+
sessionWarn(drainContext.sessionID, `${LOG_PREFIX} live-server wake failed; falling back`, {
|
|
36908
|
+
event: "bash_completion_wake_live_server_fallback",
|
|
36909
|
+
task_ids: taskIDs,
|
|
36910
|
+
directory: drainContext.directory,
|
|
36911
|
+
server_url: drainContext.serverUrl,
|
|
36912
|
+
attempt: state.wakeRetryAttempts + 1,
|
|
36913
|
+
error: err instanceof Error ? err.message : String(err)
|
|
36914
|
+
});
|
|
36915
|
+
const fallbackClient2 = getInProcessClient();
|
|
36916
|
+
const deliveryID2 = await sendPrompt(fallbackClient2, "in-process-fallback");
|
|
36917
|
+
state.retryDelayMs = null;
|
|
36918
|
+
state.wakeRetryAttempts = 0;
|
|
36919
|
+
state.wakeHardStopped = false;
|
|
36920
|
+
await ackCompletions(drainContext, deliveredCompletions, deliveryID2);
|
|
36921
|
+
return;
|
|
36922
|
+
}
|
|
36781
36923
|
}
|
|
36782
|
-
|
|
36783
|
-
|
|
36784
|
-
delivery_id: deliveryID,
|
|
36785
|
-
attempt: state.wakeRetryAttempts + 1,
|
|
36786
|
-
task_ids: taskIDs
|
|
36787
|
-
});
|
|
36924
|
+
const fallbackClient = getInProcessClient();
|
|
36925
|
+
const deliveryID = await sendPrompt(fallbackClient, "in-process-fallback");
|
|
36788
36926
|
await ackCompletions(drainContext, deliveredCompletions, deliveryID);
|
|
36789
36927
|
}, (err, hardStopped) => {
|
|
36790
36928
|
sessionWarn(drainContext.sessionID, hardStopped ? `${LOG_PREFIX} wake send failed ${MAX_WAKE_SEND_ATTEMPTS} times; stopping retries: ${err instanceof Error ? err.message : String(err)}` : `${LOG_PREFIX} wake send failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -36965,7 +37103,8 @@ function scheduleWake(state, sendWake, onSendFailure, sessionID, includeDeferred
|
|
|
36965
37103
|
state.wakeDeferredTaskIds.delete(taskId);
|
|
36966
37104
|
state.pendingLongRunning = [];
|
|
36967
37105
|
state.pendingPatternMatches = [];
|
|
36968
|
-
|
|
37106
|
+
const completionAcks = completionAcksForDelivery(pending, pendingPatternMatches);
|
|
37107
|
+
sendWake(reminder, completionAcks).then(() => {
|
|
36969
37108
|
state.retryDelayMs = null;
|
|
36970
37109
|
state.wakeRetryAttempts = 0;
|
|
36971
37110
|
state.wakeHardStopped = false;
|
|
@@ -37030,7 +37169,7 @@ function ingestDrainedBgCompletions(sessionID, completions) {
|
|
|
37030
37169
|
state.outstandingTaskIds.delete(completion.task_id);
|
|
37031
37170
|
if (state.explicitControlTasks.has(completion.task_id)) {
|
|
37032
37171
|
state.explicitControlTasks.delete(completion.task_id);
|
|
37033
|
-
queuePendingPatternMatch(state, completionToExitPattern(completion));
|
|
37172
|
+
queuePendingPatternMatch(state, completionToExitPattern(completion, true));
|
|
37034
37173
|
continue;
|
|
37035
37174
|
}
|
|
37036
37175
|
if (state.consumedTaskIds.has(completion.task_id))
|
|
@@ -37066,10 +37205,10 @@ function bufferUnknownCompletion(state, completion) {
|
|
|
37066
37205
|
function pruneUnknownCompletions(state, now) {
|
|
37067
37206
|
state.unknownCompletions = state.unknownCompletions.filter((entry) => now - entry.receivedAt <= UNKNOWN_COMPLETION_TTL_MS);
|
|
37068
37207
|
}
|
|
37069
|
-
function completionToExitPattern(completion) {
|
|
37208
|
+
function completionToExitPattern(completion, ackCompletionOnDelivery = false) {
|
|
37070
37209
|
const status = formatStatus(completion);
|
|
37071
37210
|
const preview = formatOutputPreview(completion).replace(/^ {4}/gm, "").slice(-300);
|
|
37072
|
-
|
|
37211
|
+
const entry = {
|
|
37073
37212
|
task_id: completion.task_id,
|
|
37074
37213
|
session_id: "",
|
|
37075
37214
|
watch_id: "exit",
|
|
@@ -37080,6 +37219,20 @@ ${preview}` : `task ${completion.task_id} exited (${status})`,
|
|
|
37080
37219
|
once: true,
|
|
37081
37220
|
reason: "task_exit"
|
|
37082
37221
|
};
|
|
37222
|
+
if (ackCompletionOnDelivery)
|
|
37223
|
+
entry.ackCompletionOnDelivery = true;
|
|
37224
|
+
return entry;
|
|
37225
|
+
}
|
|
37226
|
+
function completionAcksForDelivery(completions, patternMatches) {
|
|
37227
|
+
const acks = [...completions];
|
|
37228
|
+
const ackedTaskIds = new Set(acks.map((completion) => completion.task_id));
|
|
37229
|
+
for (const match of patternMatches) {
|
|
37230
|
+
if (!match.ackCompletionOnDelivery || ackedTaskIds.has(match.task_id))
|
|
37231
|
+
continue;
|
|
37232
|
+
acks.push({ task_id: match.task_id, status: "unknown", exit_code: null, command: "" });
|
|
37233
|
+
ackedTaskIds.add(match.task_id);
|
|
37234
|
+
}
|
|
37235
|
+
return acks;
|
|
37083
37236
|
}
|
|
37084
37237
|
function isBgCompletion(value) {
|
|
37085
37238
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
@@ -37147,9 +37300,9 @@ function formatDuration(completion) {
|
|
|
37147
37300
|
|
|
37148
37301
|
// src/config.ts
|
|
37149
37302
|
var import_comment_json = __toESM(require_src2(), 1);
|
|
37150
|
-
import { existsSync as
|
|
37303
|
+
import { existsSync as existsSync7, readFileSync as readFileSync4, renameSync as renameSync4, unlinkSync as unlinkSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
37151
37304
|
import { homedir as homedir5 } from "node:os";
|
|
37152
|
-
import { join as
|
|
37305
|
+
import { join as join9 } from "node:path";
|
|
37153
37306
|
|
|
37154
37307
|
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/external.js
|
|
37155
37308
|
var exports_external = {};
|
|
@@ -51032,13 +51185,13 @@ function migrateExperimentalBashBlock(rawConfig, configPath, logger) {
|
|
|
51032
51185
|
return movedKeys;
|
|
51033
51186
|
}
|
|
51034
51187
|
function migrateAftConfigFile(configPath, logger = { log: log2, warn: warn2 }) {
|
|
51035
|
-
if (!
|
|
51188
|
+
if (!existsSync7(configPath)) {
|
|
51036
51189
|
return { migrated: false, oldKeys: [] };
|
|
51037
51190
|
}
|
|
51038
51191
|
let tmpPath = null;
|
|
51039
51192
|
let oldKeys = [];
|
|
51040
51193
|
try {
|
|
51041
|
-
const content =
|
|
51194
|
+
const content = readFileSync4(configPath, "utf-8");
|
|
51042
51195
|
const rawConfig = import_comment_json.parse(content);
|
|
51043
51196
|
if (!rawConfig || typeof rawConfig !== "object" || Array.isArray(rawConfig)) {
|
|
51044
51197
|
return { migrated: false, oldKeys: [] };
|
|
@@ -51056,7 +51209,7 @@ function migrateAftConfigFile(configPath, logger = { log: log2, warn: warn2 }) {
|
|
|
51056
51209
|
${serialized}` : serialized;
|
|
51057
51210
|
tmpPath = `${configPath}.tmp.${process.pid}`;
|
|
51058
51211
|
writeFileSync3(tmpPath, nextContent, "utf-8");
|
|
51059
|
-
|
|
51212
|
+
renameSync4(tmpPath, configPath);
|
|
51060
51213
|
logger.log(`Migrated config at ${configPath}: removed ${oldKeys.join(", ")}`);
|
|
51061
51214
|
return { migrated: true, oldKeys };
|
|
51062
51215
|
} catch (err) {
|
|
@@ -51076,10 +51229,10 @@ ${serialized}` : serialized;
|
|
|
51076
51229
|
function detectConfigFile(basePath) {
|
|
51077
51230
|
const jsoncPath = `${basePath}.jsonc`;
|
|
51078
51231
|
const jsonPath = `${basePath}.json`;
|
|
51079
|
-
if (
|
|
51232
|
+
if (existsSync7(jsoncPath)) {
|
|
51080
51233
|
return { format: "jsonc", path: jsoncPath };
|
|
51081
51234
|
}
|
|
51082
|
-
if (
|
|
51235
|
+
if (existsSync7(jsonPath)) {
|
|
51083
51236
|
return { format: "json", path: jsonPath };
|
|
51084
51237
|
}
|
|
51085
51238
|
return { format: "none", path: jsonPath };
|
|
@@ -51112,10 +51265,10 @@ function parseConfigPartially(rawConfig) {
|
|
|
51112
51265
|
}
|
|
51113
51266
|
function loadConfigFromPath(configPath) {
|
|
51114
51267
|
try {
|
|
51115
|
-
if (!
|
|
51268
|
+
if (!existsSync7(configPath)) {
|
|
51116
51269
|
return null;
|
|
51117
51270
|
}
|
|
51118
|
-
const content =
|
|
51271
|
+
const content = readFileSync4(configPath, "utf-8");
|
|
51119
51272
|
const rawConfig = import_comment_json.parse(content);
|
|
51120
51273
|
migrateRawConfig(rawConfig, configPath, { log: log2, warn: warn2 });
|
|
51121
51274
|
const result = AftConfigSchema.safeParse(rawConfig);
|
|
@@ -51276,17 +51429,17 @@ function getOpenCodeConfigDir() {
|
|
|
51276
51429
|
if (envDir) {
|
|
51277
51430
|
return envDir;
|
|
51278
51431
|
}
|
|
51279
|
-
const xdgConfig = process.env.XDG_CONFIG_HOME ||
|
|
51280
|
-
return
|
|
51432
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME || join9(homedir5(), ".config");
|
|
51433
|
+
return join9(xdgConfig, "opencode");
|
|
51281
51434
|
}
|
|
51282
51435
|
function loadAftConfig(projectDirectory) {
|
|
51283
51436
|
const configDir = getOpenCodeConfigDir();
|
|
51284
|
-
const userBasePath =
|
|
51437
|
+
const userBasePath = join9(configDir, "aft");
|
|
51285
51438
|
migrateAftConfigFile(`${userBasePath}.jsonc`);
|
|
51286
51439
|
migrateAftConfigFile(`${userBasePath}.json`);
|
|
51287
51440
|
const userDetected = detectConfigFile(userBasePath);
|
|
51288
51441
|
const userConfigPath = userDetected.format !== "none" ? userDetected.path : `${userBasePath}.json`;
|
|
51289
|
-
const projectBasePath =
|
|
51442
|
+
const projectBasePath = join9(projectDirectory, ".opencode", "aft");
|
|
51290
51443
|
migrateAftConfigFile(`${projectBasePath}.jsonc`);
|
|
51291
51444
|
migrateAftConfigFile(`${projectBasePath}.json`);
|
|
51292
51445
|
const projectDetected = detectConfigFile(projectBasePath);
|
|
@@ -51311,44 +51464,53 @@ function loadAftConfig(projectDirectory) {
|
|
|
51311
51464
|
}
|
|
51312
51465
|
|
|
51313
51466
|
// src/hooks/auto-update-checker/index.ts
|
|
51314
|
-
import {
|
|
51315
|
-
|
|
51467
|
+
import {
|
|
51468
|
+
closeSync as closeSync3,
|
|
51469
|
+
existsSync as existsSync10,
|
|
51470
|
+
mkdirSync as mkdirSync7,
|
|
51471
|
+
openSync as openSync3,
|
|
51472
|
+
readFileSync as readFileSync7,
|
|
51473
|
+
renameSync as renameSync5,
|
|
51474
|
+
rmSync as rmSync4,
|
|
51475
|
+
writeFileSync as writeFileSync6
|
|
51476
|
+
} from "node:fs";
|
|
51477
|
+
import { dirname as dirname6 } from "node:path";
|
|
51316
51478
|
|
|
51317
51479
|
// src/hooks/auto-update-checker/cache.ts
|
|
51318
51480
|
var import_comment_json3 = __toESM(require_src2(), 1);
|
|
51319
51481
|
import { spawn as spawn2 } from "node:child_process";
|
|
51320
|
-
import { cpSync, existsSync as
|
|
51482
|
+
import { cpSync, existsSync as existsSync9, mkdtempSync, readFileSync as readFileSync6, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "node:fs";
|
|
51321
51483
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
51322
|
-
import { basename, dirname as
|
|
51484
|
+
import { basename as basename2, dirname as dirname5, join as join12 } from "node:path";
|
|
51323
51485
|
|
|
51324
51486
|
// src/hooks/auto-update-checker/checker.ts
|
|
51325
51487
|
var import_comment_json2 = __toESM(require_src2(), 1);
|
|
51326
|
-
import { existsSync as
|
|
51488
|
+
import { existsSync as existsSync8, readFileSync as readFileSync5, statSync as statSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
51327
51489
|
import { homedir as homedir7 } from "node:os";
|
|
51328
|
-
import { dirname as
|
|
51490
|
+
import { dirname as dirname4, isAbsolute, join as join11, resolve as resolve2 } from "node:path";
|
|
51329
51491
|
import { fileURLToPath } from "node:url";
|
|
51330
51492
|
|
|
51331
51493
|
// src/hooks/auto-update-checker/constants.ts
|
|
51332
51494
|
import { homedir as homedir6, platform } from "node:os";
|
|
51333
|
-
import { join as
|
|
51495
|
+
import { join as join10 } from "node:path";
|
|
51334
51496
|
var PACKAGE_NAME = "@cortexkit/aft-opencode";
|
|
51335
51497
|
var NPM_REGISTRY_URL = "https://registry.npmjs.org";
|
|
51336
51498
|
var NPM_FETCH_TIMEOUT = 1e4;
|
|
51337
51499
|
function getOpenCodeCacheRoot() {
|
|
51338
51500
|
if (platform() === "win32") {
|
|
51339
|
-
return
|
|
51501
|
+
return join10(process.env.LOCALAPPDATA ?? homedir6(), "opencode");
|
|
51340
51502
|
}
|
|
51341
|
-
return
|
|
51503
|
+
return join10(homedir6(), ".cache", "opencode");
|
|
51342
51504
|
}
|
|
51343
51505
|
function getOpenCodeConfigRoot() {
|
|
51344
51506
|
if (platform() === "win32") {
|
|
51345
|
-
return
|
|
51507
|
+
return join10(process.env.APPDATA ?? join10(homedir6(), "AppData", "Roaming"), "opencode");
|
|
51346
51508
|
}
|
|
51347
|
-
return
|
|
51509
|
+
return join10(process.env.XDG_CONFIG_HOME ?? join10(homedir6(), ".config"), "opencode");
|
|
51348
51510
|
}
|
|
51349
|
-
var CACHE_DIR =
|
|
51350
|
-
var USER_OPENCODE_CONFIG =
|
|
51351
|
-
var USER_OPENCODE_CONFIG_JSONC =
|
|
51511
|
+
var CACHE_DIR = join10(getOpenCodeCacheRoot(), "packages");
|
|
51512
|
+
var USER_OPENCODE_CONFIG = join10(getOpenCodeConfigRoot(), "opencode.json");
|
|
51513
|
+
var USER_OPENCODE_CONFIG_JSONC = join10(getOpenCodeConfigRoot(), "opencode.jsonc");
|
|
51352
51514
|
|
|
51353
51515
|
// src/hooks/auto-update-checker/types.ts
|
|
51354
51516
|
var NpmPackageEnvelopeSchema = exports_external.object({
|
|
@@ -51406,8 +51568,8 @@ function extractChannel(version2) {
|
|
|
51406
51568
|
}
|
|
51407
51569
|
function getConfigPaths(directory) {
|
|
51408
51570
|
return [
|
|
51409
|
-
|
|
51410
|
-
|
|
51571
|
+
join11(directory, ".opencode", "opencode.json"),
|
|
51572
|
+
join11(directory, ".opencode", "opencode.jsonc"),
|
|
51411
51573
|
USER_OPENCODE_CONFIG,
|
|
51412
51574
|
USER_OPENCODE_CONFIG_JSONC
|
|
51413
51575
|
];
|
|
@@ -51422,14 +51584,14 @@ function resolvePathPluginSpec(spec, configPath) {
|
|
|
51422
51584
|
}
|
|
51423
51585
|
if (isAbsolute(spec) || /^[A-Za-z]:[\\/]/.test(spec))
|
|
51424
51586
|
return spec;
|
|
51425
|
-
return resolve2(
|
|
51587
|
+
return resolve2(dirname4(configPath), spec);
|
|
51426
51588
|
}
|
|
51427
51589
|
function getLocalDevPath(directory) {
|
|
51428
51590
|
for (const configPath of getConfigPaths(directory)) {
|
|
51429
51591
|
try {
|
|
51430
|
-
if (!
|
|
51592
|
+
if (!existsSync8(configPath))
|
|
51431
51593
|
continue;
|
|
51432
|
-
const rawConfig = parseJsonConfig(
|
|
51594
|
+
const rawConfig = parseJsonConfig(readFileSync5(configPath, "utf-8"));
|
|
51433
51595
|
const plugins = getPluginEntries(rawConfig);
|
|
51434
51596
|
for (const entry of plugins) {
|
|
51435
51597
|
if (entry === PACKAGE_NAME || entry.startsWith(`${PACKAGE_NAME}@`))
|
|
@@ -51439,7 +51601,7 @@ function getLocalDevPath(directory) {
|
|
|
51439
51601
|
const pkgPath = findPackageJsonUp(localPath);
|
|
51440
51602
|
if (!pkgPath)
|
|
51441
51603
|
continue;
|
|
51442
|
-
const pkg = PackageJsonSchema.safeParse(JSON.parse(
|
|
51604
|
+
const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync5(pkgPath, "utf-8")));
|
|
51443
51605
|
if (pkg.success && pkg.data.name === PACKAGE_NAME)
|
|
51444
51606
|
return localPath;
|
|
51445
51607
|
}
|
|
@@ -51451,17 +51613,17 @@ function getLocalDevPath(directory) {
|
|
|
51451
51613
|
function findPackageJsonUp(startPath) {
|
|
51452
51614
|
try {
|
|
51453
51615
|
const stat = statSync3(startPath);
|
|
51454
|
-
let dir = stat.isDirectory() ? startPath :
|
|
51616
|
+
let dir = stat.isDirectory() ? startPath : dirname4(startPath);
|
|
51455
51617
|
for (let i = 0;i < 10; i++) {
|
|
51456
|
-
const pkgPath =
|
|
51457
|
-
if (
|
|
51618
|
+
const pkgPath = join11(dir, "package.json");
|
|
51619
|
+
if (existsSync8(pkgPath)) {
|
|
51458
51620
|
try {
|
|
51459
|
-
const pkg = PackageJsonSchema.safeParse(JSON.parse(
|
|
51621
|
+
const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync5(pkgPath, "utf-8")));
|
|
51460
51622
|
if (pkg.success && pkg.data.name === PACKAGE_NAME)
|
|
51461
51623
|
return pkgPath;
|
|
51462
51624
|
} catch {}
|
|
51463
51625
|
}
|
|
51464
|
-
const parent =
|
|
51626
|
+
const parent = dirname4(dir);
|
|
51465
51627
|
if (parent === dir)
|
|
51466
51628
|
break;
|
|
51467
51629
|
dir = parent;
|
|
@@ -51477,7 +51639,7 @@ function getLocalDevVersion(directory) {
|
|
|
51477
51639
|
const pkgPath = findPackageJsonUp(localPath);
|
|
51478
51640
|
if (!pkgPath)
|
|
51479
51641
|
return null;
|
|
51480
|
-
const pkg = PackageJsonSchema.safeParse(JSON.parse(
|
|
51642
|
+
const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync5(pkgPath, "utf-8")));
|
|
51481
51643
|
return pkg.success ? pkg.data.version ?? null : null;
|
|
51482
51644
|
} catch {
|
|
51483
51645
|
return null;
|
|
@@ -51485,7 +51647,7 @@ function getLocalDevVersion(directory) {
|
|
|
51485
51647
|
}
|
|
51486
51648
|
function getCurrentRuntimePackageJsonPath(currentModuleUrl = import.meta.url) {
|
|
51487
51649
|
try {
|
|
51488
|
-
return findPackageJsonUp(
|
|
51650
|
+
return findPackageJsonUp(dirname4(fileURLToPath(currentModuleUrl)));
|
|
51489
51651
|
} catch (err) {
|
|
51490
51652
|
warn2(`[auto-update-checker] Failed to resolve runtime package path: ${String(err)}`);
|
|
51491
51653
|
return null;
|
|
@@ -51494,9 +51656,9 @@ function getCurrentRuntimePackageJsonPath(currentModuleUrl = import.meta.url) {
|
|
|
51494
51656
|
function findPluginEntry(directory) {
|
|
51495
51657
|
for (const configPath of getConfigPaths(directory)) {
|
|
51496
51658
|
try {
|
|
51497
|
-
if (!
|
|
51659
|
+
if (!existsSync8(configPath))
|
|
51498
51660
|
continue;
|
|
51499
|
-
const rawConfig = parseJsonConfig(
|
|
51661
|
+
const rawConfig = parseJsonConfig(readFileSync5(configPath, "utf-8"));
|
|
51500
51662
|
const plugins = getPluginEntries(rawConfig);
|
|
51501
51663
|
for (const entry of plugins) {
|
|
51502
51664
|
if (entry === PACKAGE_NAME) {
|
|
@@ -51514,7 +51676,7 @@ function findPluginEntry(directory) {
|
|
|
51514
51676
|
}
|
|
51515
51677
|
var cachedPackageVersion = null;
|
|
51516
51678
|
function getSpecCachePackageJsonPath(spec) {
|
|
51517
|
-
return
|
|
51679
|
+
return join11(CACHE_DIR, spec, "node_modules", PACKAGE_NAME, "package.json");
|
|
51518
51680
|
}
|
|
51519
51681
|
function getCachedVersion(spec) {
|
|
51520
51682
|
if (!spec && cachedPackageVersion)
|
|
@@ -51523,13 +51685,13 @@ function getCachedVersion(spec) {
|
|
|
51523
51685
|
getCurrentRuntimePackageJsonPath(),
|
|
51524
51686
|
spec ? getSpecCachePackageJsonPath(spec) : null,
|
|
51525
51687
|
getSpecCachePackageJsonPath(`${PACKAGE_NAME}@latest`),
|
|
51526
|
-
|
|
51688
|
+
join11(homedir7(), ".cache", "opencode", "node_modules", PACKAGE_NAME, "package.json")
|
|
51527
51689
|
].filter(isString);
|
|
51528
51690
|
for (const packageJsonPath of candidates) {
|
|
51529
51691
|
try {
|
|
51530
|
-
if (!
|
|
51692
|
+
if (!existsSync8(packageJsonPath))
|
|
51531
51693
|
continue;
|
|
51532
|
-
const pkg = PackageJsonSchema.safeParse(JSON.parse(
|
|
51694
|
+
const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync5(packageJsonPath, "utf-8")));
|
|
51533
51695
|
if (pkg.success && pkg.data.version) {
|
|
51534
51696
|
if (!spec)
|
|
51535
51697
|
cachedPackageVersion = pkg.data.version;
|
|
@@ -51571,17 +51733,17 @@ async function getLatestVersion(channel = "latest", options = {}) {
|
|
|
51571
51733
|
// src/hooks/auto-update-checker/cache.ts
|
|
51572
51734
|
var pendingSnapshots = new Map;
|
|
51573
51735
|
function createAutoUpdateSnapshot(installDir, packageJsonPath, packageName) {
|
|
51574
|
-
const packageDir =
|
|
51575
|
-
const lockfilePath =
|
|
51576
|
-
const tempDir = mkdtempSync(
|
|
51577
|
-
const stagedPackageDir =
|
|
51736
|
+
const packageDir = join12(installDir, "node_modules", packageName);
|
|
51737
|
+
const lockfilePath = join12(installDir, "package-lock.json");
|
|
51738
|
+
const tempDir = mkdtempSync(join12(tmpdir3(), "aft-auto-update-"));
|
|
51739
|
+
const stagedPackageDir = existsSync9(packageDir) ? join12(tempDir, "package") : null;
|
|
51578
51740
|
if (stagedPackageDir)
|
|
51579
51741
|
cpSync(packageDir, stagedPackageDir, { recursive: true });
|
|
51580
51742
|
return {
|
|
51581
51743
|
packageJsonPath,
|
|
51582
|
-
packageJson:
|
|
51744
|
+
packageJson: existsSync9(packageJsonPath) ? readFileSync6(packageJsonPath, "utf-8") : null,
|
|
51583
51745
|
lockfilePath,
|
|
51584
|
-
lockfile:
|
|
51746
|
+
lockfile: existsSync9(lockfilePath) ? readFileSync6(lockfilePath, "utf-8") : null,
|
|
51585
51747
|
packageDir,
|
|
51586
51748
|
stagedPackageDir,
|
|
51587
51749
|
tempDir
|
|
@@ -51608,18 +51770,18 @@ function restoreAutoUpdateSnapshot(snapshot) {
|
|
|
51608
51770
|
function stripPackageNameFromPath(pathValue, packageName) {
|
|
51609
51771
|
let current = pathValue;
|
|
51610
51772
|
for (const segment of [...packageName.split("/")].reverse()) {
|
|
51611
|
-
if (
|
|
51773
|
+
if (basename2(current) !== segment)
|
|
51612
51774
|
return null;
|
|
51613
|
-
current =
|
|
51775
|
+
current = dirname5(current);
|
|
51614
51776
|
}
|
|
51615
51777
|
return current;
|
|
51616
51778
|
}
|
|
51617
51779
|
function removeFromPackageLock(installDir, packageName) {
|
|
51618
|
-
const lockPath =
|
|
51619
|
-
if (!
|
|
51780
|
+
const lockPath = join12(installDir, "package-lock.json");
|
|
51781
|
+
if (!existsSync9(lockPath))
|
|
51620
51782
|
return false;
|
|
51621
51783
|
try {
|
|
51622
|
-
const lock = import_comment_json3.parse(
|
|
51784
|
+
const lock = import_comment_json3.parse(readFileSync6(lockPath, "utf-8"));
|
|
51623
51785
|
let modified = false;
|
|
51624
51786
|
if (lock.packages) {
|
|
51625
51787
|
const key = `node_modules/${packageName}`;
|
|
@@ -51642,10 +51804,10 @@ function removeFromPackageLock(installDir, packageName) {
|
|
|
51642
51804
|
}
|
|
51643
51805
|
}
|
|
51644
51806
|
function ensureDependencyVersion(packageJsonPath, packageName, version2) {
|
|
51645
|
-
if (!
|
|
51807
|
+
if (!existsSync9(packageJsonPath))
|
|
51646
51808
|
return false;
|
|
51647
51809
|
try {
|
|
51648
|
-
const raw = import_comment_json3.parse(
|
|
51810
|
+
const raw = import_comment_json3.parse(readFileSync6(packageJsonPath, "utf-8"));
|
|
51649
51811
|
const pkgJson = PackageJsonSchema.safeParse(raw);
|
|
51650
51812
|
if (!pkgJson.success)
|
|
51651
51813
|
return false;
|
|
@@ -51664,8 +51826,8 @@ function ensureDependencyVersion(packageJsonPath, packageName, version2) {
|
|
|
51664
51826
|
}
|
|
51665
51827
|
}
|
|
51666
51828
|
function removeInstalledPackage(installDir, packageName) {
|
|
51667
|
-
const packageDir =
|
|
51668
|
-
if (!
|
|
51829
|
+
const packageDir = join12(installDir, "node_modules", packageName);
|
|
51830
|
+
if (!existsSync9(packageDir))
|
|
51669
51831
|
return false;
|
|
51670
51832
|
rmSync3(packageDir, { recursive: true, force: true });
|
|
51671
51833
|
log2(`[auto-update-checker] Package removed: ${packageDir}`);
|
|
@@ -51673,19 +51835,19 @@ function removeInstalledPackage(installDir, packageName) {
|
|
|
51673
51835
|
}
|
|
51674
51836
|
function resolveInstallContext(runtimePackageJsonPath = getCurrentRuntimePackageJsonPath()) {
|
|
51675
51837
|
if (runtimePackageJsonPath) {
|
|
51676
|
-
const packageDir =
|
|
51838
|
+
const packageDir = dirname5(runtimePackageJsonPath);
|
|
51677
51839
|
const nodeModulesDir = stripPackageNameFromPath(packageDir, PACKAGE_NAME);
|
|
51678
|
-
if (nodeModulesDir &&
|
|
51679
|
-
const installDir =
|
|
51680
|
-
const packageJsonPath =
|
|
51681
|
-
if (
|
|
51840
|
+
if (nodeModulesDir && basename2(nodeModulesDir) === "node_modules") {
|
|
51841
|
+
const installDir = dirname5(nodeModulesDir);
|
|
51842
|
+
const packageJsonPath = join12(installDir, "package.json");
|
|
51843
|
+
if (existsSync9(packageJsonPath))
|
|
51682
51844
|
return { installDir, packageJsonPath };
|
|
51683
51845
|
}
|
|
51684
51846
|
return null;
|
|
51685
51847
|
}
|
|
51686
|
-
const legacyPackageJsonPath =
|
|
51687
|
-
if (
|
|
51688
|
-
return { installDir:
|
|
51848
|
+
const legacyPackageJsonPath = join12(dirname5(CACHE_DIR), "package.json");
|
|
51849
|
+
if (existsSync9(legacyPackageJsonPath)) {
|
|
51850
|
+
return { installDir: dirname5(CACHE_DIR), packageJsonPath: legacyPackageJsonPath };
|
|
51689
51851
|
}
|
|
51690
51852
|
return null;
|
|
51691
51853
|
}
|
|
@@ -51714,15 +51876,25 @@ function preparePackageUpdate(version2, packageName = PACKAGE_NAME, runtimePacka
|
|
|
51714
51876
|
return null;
|
|
51715
51877
|
}
|
|
51716
51878
|
}
|
|
51879
|
+
var STDERR_TAIL_BYTES = 16 * 1024;
|
|
51717
51880
|
async function runNpmInstallSafe(installDir, options = {}) {
|
|
51718
51881
|
let timeout = null;
|
|
51882
|
+
let stderrTail = "";
|
|
51719
51883
|
try {
|
|
51720
51884
|
if (options.signal?.aborted)
|
|
51721
|
-
return false;
|
|
51722
|
-
const
|
|
51885
|
+
return { ok: false, reason: "aborted" };
|
|
51886
|
+
const npmBin = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
51887
|
+
const proc = spawn2(npmBin, ["install", "--no-audit", "--no-fund", "--no-progress", "--ignore-scripts"], {
|
|
51723
51888
|
cwd: installDir,
|
|
51724
|
-
stdio: "ignore"
|
|
51889
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
51725
51890
|
});
|
|
51891
|
+
proc.stderr?.on("data", (chunk) => {
|
|
51892
|
+
stderrTail += chunk.toString("utf8");
|
|
51893
|
+
if (stderrTail.length > STDERR_TAIL_BYTES) {
|
|
51894
|
+
stderrTail = stderrTail.slice(-STDERR_TAIL_BYTES);
|
|
51895
|
+
}
|
|
51896
|
+
});
|
|
51897
|
+
proc.stdout?.on("data", () => {});
|
|
51726
51898
|
const abortProcess = () => {
|
|
51727
51899
|
try {
|
|
51728
51900
|
proc.kill();
|
|
@@ -51730,8 +51902,8 @@ async function runNpmInstallSafe(installDir, options = {}) {
|
|
|
51730
51902
|
};
|
|
51731
51903
|
options.signal?.addEventListener("abort", abortProcess, { once: true });
|
|
51732
51904
|
const exitPromise = new Promise((resolveExit) => {
|
|
51733
|
-
proc.on("error", () => resolveExit(false));
|
|
51734
|
-
proc.on("exit", (code) => resolveExit(code === 0));
|
|
51905
|
+
proc.on("error", (err) => resolveExit({ ok: false, reason: `spawn error: ${String(err)}` }));
|
|
51906
|
+
proc.on("exit", (code) => resolveExit(code === 0 ? { ok: true } : { ok: false, reason: `npm install exited with code ${code ?? "signal/unknown"}` }));
|
|
51735
51907
|
});
|
|
51736
51908
|
const timeoutPromise = new Promise((resolveTimeout) => {
|
|
51737
51909
|
timeout = setTimeout(() => resolveTimeout("timeout"), options.timeoutMs ?? 60000);
|
|
@@ -51745,29 +51917,41 @@ async function runNpmInstallSafe(installDir, options = {}) {
|
|
|
51745
51917
|
pendingSnapshots.delete(installDir);
|
|
51746
51918
|
restoreAutoUpdateSnapshot(snapshot2);
|
|
51747
51919
|
}
|
|
51748
|
-
|
|
51920
|
+
const reason = options.signal?.aborted ? "aborted" : "timeout";
|
|
51921
|
+
warnNpmInstallFailure(reason, stderrTail);
|
|
51922
|
+
return { ok: false, reason, stderrTail: stderrTail || undefined };
|
|
51749
51923
|
}
|
|
51750
51924
|
const snapshot = pendingSnapshots.get(installDir);
|
|
51751
51925
|
pendingSnapshots.delete(installDir);
|
|
51752
|
-
if (!result && snapshot) {
|
|
51926
|
+
if (!result.ok && snapshot) {
|
|
51753
51927
|
restoreAutoUpdateSnapshot(snapshot);
|
|
51754
51928
|
} else if (snapshot) {
|
|
51755
51929
|
rmSync3(snapshot.tempDir, { recursive: true, force: true });
|
|
51756
51930
|
}
|
|
51757
|
-
|
|
51931
|
+
if (!result.ok) {
|
|
51932
|
+
warnNpmInstallFailure(result.reason ?? "npm install failed", stderrTail);
|
|
51933
|
+
}
|
|
51934
|
+
return { ...result, stderrTail: stderrTail || undefined };
|
|
51758
51935
|
} catch (err) {
|
|
51759
51936
|
const snapshot = pendingSnapshots.get(installDir);
|
|
51760
51937
|
if (snapshot) {
|
|
51761
51938
|
pendingSnapshots.delete(installDir);
|
|
51762
51939
|
restoreAutoUpdateSnapshot(snapshot);
|
|
51763
51940
|
}
|
|
51764
|
-
|
|
51765
|
-
|
|
51941
|
+
const reason = `exception: ${String(err)}`;
|
|
51942
|
+
warnNpmInstallFailure(reason, stderrTail);
|
|
51943
|
+
return { ok: false, reason, stderrTail: stderrTail || undefined };
|
|
51766
51944
|
} finally {
|
|
51767
51945
|
if (timeout)
|
|
51768
51946
|
clearTimeout(timeout);
|
|
51769
51947
|
}
|
|
51770
51948
|
}
|
|
51949
|
+
function warnNpmInstallFailure(reason, stderrTail) {
|
|
51950
|
+
const tail2 = stderrTail ? `
|
|
51951
|
+
stderr tail:
|
|
51952
|
+
${stderrTail}` : "";
|
|
51953
|
+
warn2(`[auto-update-checker] npm install failed (${reason})${tail2}`);
|
|
51954
|
+
}
|
|
51771
51955
|
|
|
51772
51956
|
// src/hooks/auto-update-checker/index.ts
|
|
51773
51957
|
var DEFAULT_CHECK_INTERVAL_MS = 60 * 60 * 1000;
|
|
@@ -51813,36 +51997,76 @@ function createAutoUpdateCheckerHook(ctx, options = {}) {
|
|
|
51813
51997
|
async function maybeRunCheck(ctx, options) {
|
|
51814
51998
|
if (options.signal.aborted)
|
|
51815
51999
|
return;
|
|
51816
|
-
|
|
52000
|
+
const checkSlot = claimCheckSlot(options.storageDir, options.checkIntervalMs);
|
|
52001
|
+
if (!checkSlot) {
|
|
51817
52002
|
log2("[auto-update-checker] Skipping check (another instance ran one recently)");
|
|
51818
52003
|
return;
|
|
51819
52004
|
}
|
|
51820
|
-
|
|
52005
|
+
try {
|
|
52006
|
+
await runStartupCheck(ctx, options);
|
|
52007
|
+
} finally {
|
|
52008
|
+
checkSlot.release();
|
|
52009
|
+
}
|
|
51821
52010
|
}
|
|
51822
52011
|
function claimCheckSlot(storageDir, intervalMs) {
|
|
51823
52012
|
if (!storageDir)
|
|
51824
|
-
return
|
|
52013
|
+
return { release: () => {} };
|
|
52014
|
+
const file2 = repairRootScopedStorageFile(storageDir, "opencode", TIMESTAMP_FILENAME);
|
|
51825
52015
|
try {
|
|
51826
|
-
|
|
51827
|
-
|
|
51828
|
-
|
|
51829
|
-
|
|
51830
|
-
|
|
51831
|
-
|
|
51832
|
-
|
|
51833
|
-
|
|
51834
|
-
|
|
52016
|
+
if (hasRecentCheckTimestamp(file2, intervalMs))
|
|
52017
|
+
return null;
|
|
52018
|
+
mkdirSync7(dirname6(file2), { recursive: true });
|
|
52019
|
+
const lockPath = `${file2}.lock`;
|
|
52020
|
+
let lockFd;
|
|
52021
|
+
try {
|
|
52022
|
+
lockFd = openSync3(lockPath, "wx");
|
|
52023
|
+
writeFileSync6(lockFd, JSON.stringify({ pid: process.pid, startedMs: Date.now() }));
|
|
52024
|
+
} catch (err) {
|
|
52025
|
+
if (err.code !== "EEXIST") {
|
|
52026
|
+
warn2(`[auto-update-checker] Could not acquire update lock: ${String(err)}`);
|
|
52027
|
+
}
|
|
52028
|
+
return null;
|
|
52029
|
+
}
|
|
52030
|
+
const lock = {
|
|
52031
|
+
release: () => {
|
|
52032
|
+
try {
|
|
52033
|
+
closeSync3(lockFd);
|
|
52034
|
+
} catch {}
|
|
52035
|
+
rmSync4(lockPath, { force: true });
|
|
52036
|
+
}
|
|
52037
|
+
};
|
|
52038
|
+
try {
|
|
52039
|
+
if (hasRecentCheckTimestamp(file2, intervalMs)) {
|
|
52040
|
+
lock.release();
|
|
52041
|
+
return null;
|
|
52042
|
+
}
|
|
52043
|
+
writeCheckTimestamp(file2);
|
|
52044
|
+
return lock;
|
|
52045
|
+
} catch (err) {
|
|
52046
|
+
lock.release();
|
|
52047
|
+
throw err;
|
|
51835
52048
|
}
|
|
51836
|
-
mkdirSync6(dirname5(file2), { recursive: true });
|
|
51837
|
-
const tmp = `${file2}.tmp.${process.pid}`;
|
|
51838
|
-
writeFileSync6(tmp, JSON.stringify({ lastCheckedMs: Date.now() }), "utf-8");
|
|
51839
|
-
renameSync4(tmp, file2);
|
|
51840
|
-
return true;
|
|
51841
52049
|
} catch (err) {
|
|
51842
52050
|
warn2(`[auto-update-checker] Could not coordinate via timestamp file: ${String(err)}`);
|
|
51843
|
-
return
|
|
52051
|
+
return null;
|
|
51844
52052
|
}
|
|
51845
52053
|
}
|
|
52054
|
+
function hasRecentCheckTimestamp(file2, intervalMs) {
|
|
52055
|
+
if (!existsSync10(file2))
|
|
52056
|
+
return false;
|
|
52057
|
+
try {
|
|
52058
|
+
const raw = JSON.parse(readFileSync7(file2, "utf-8"));
|
|
52059
|
+
const last = typeof raw.lastCheckedMs === "number" ? raw.lastCheckedMs : 0;
|
|
52060
|
+
return Number.isFinite(last) && Date.now() - last < intervalMs;
|
|
52061
|
+
} catch {
|
|
52062
|
+
return false;
|
|
52063
|
+
}
|
|
52064
|
+
}
|
|
52065
|
+
function writeCheckTimestamp(file2) {
|
|
52066
|
+
const tmp = `${file2}.tmp.${process.pid}`;
|
|
52067
|
+
writeFileSync6(tmp, JSON.stringify({ lastCheckedMs: Date.now() }), "utf-8");
|
|
52068
|
+
renameSync5(tmp, file2);
|
|
52069
|
+
}
|
|
51846
52070
|
async function runStartupCheck(ctx, options) {
|
|
51847
52071
|
if (options.signal.aborted)
|
|
51848
52072
|
return;
|
|
@@ -51907,32 +52131,39 @@ async function runBackgroundUpdateCheck(ctx, options) {
|
|
|
51907
52131
|
warn2("[auto-update-checker] Failed to prepare install root for auto-update");
|
|
51908
52132
|
return;
|
|
51909
52133
|
}
|
|
51910
|
-
const
|
|
51911
|
-
if (
|
|
52134
|
+
const installResult = await runNpmInstallSafe(installDir, { signal: options.signal });
|
|
52135
|
+
if (installResult.ok) {
|
|
51912
52136
|
showToast(ctx, "AFT Updated!", `v${currentVersion} → v${latestVersion}
|
|
51913
52137
|
Restart OpenCode to apply.`, "success", 8000);
|
|
51914
52138
|
log2(`[auto-update-checker] Update installed: ${currentVersion} → ${latestVersion}`);
|
|
51915
52139
|
return;
|
|
51916
52140
|
}
|
|
51917
52141
|
showToast(ctx, `AFT ${latestVersion}`, `v${latestVersion} available, but auto-update failed to install it. Check logs or retry manually.`, "error", 8000);
|
|
51918
|
-
|
|
52142
|
+
const failureDetail = installResult.reason ? `: ${installResult.reason}` : "";
|
|
52143
|
+
const stderrDetail = installResult.stderrTail ? `
|
|
52144
|
+
stderr tail:
|
|
52145
|
+
${installResult.stderrTail}` : "";
|
|
52146
|
+
warn2(`[auto-update-checker] npm install failed; update not installed${failureDetail}${stderrDetail}`);
|
|
51919
52147
|
}
|
|
51920
52148
|
function showToast(ctx, title, message, variant = "info", duration3 = 3000) {
|
|
51921
|
-
ctx.client.tui
|
|
52149
|
+
const tui = ctx.client.tui;
|
|
52150
|
+
if (typeof tui?.showToast !== "function")
|
|
52151
|
+
return;
|
|
52152
|
+
tui.showToast({ body: { title, message, variant, duration: duration3 } }).catch(() => {});
|
|
51922
52153
|
}
|
|
51923
52154
|
|
|
51924
52155
|
// src/lsp-auto-install.ts
|
|
51925
52156
|
import { spawn as spawn3 } from "node:child_process";
|
|
51926
52157
|
import { createHash as createHash5 } from "node:crypto";
|
|
51927
|
-
import { createReadStream, mkdirSync as
|
|
52158
|
+
import { createReadStream, mkdirSync as mkdirSync9, readFileSync as readFileSync10, renameSync as renameSync6, rmSync as rmSync5, statSync as statSync5 } from "node:fs";
|
|
51928
52159
|
import { join as join15 } from "node:path";
|
|
51929
52160
|
|
|
51930
52161
|
// src/lsp-cache.ts
|
|
51931
52162
|
import {
|
|
51932
|
-
closeSync as
|
|
51933
|
-
mkdirSync as
|
|
51934
|
-
openSync as
|
|
51935
|
-
readFileSync as
|
|
52163
|
+
closeSync as closeSync4,
|
|
52164
|
+
mkdirSync as mkdirSync8,
|
|
52165
|
+
openSync as openSync4,
|
|
52166
|
+
readFileSync as readFileSync8,
|
|
51936
52167
|
statSync as statSync4,
|
|
51937
52168
|
unlinkSync as unlinkSync5,
|
|
51938
52169
|
writeFileSync as writeFileSync7
|
|
@@ -51980,7 +52211,7 @@ function lspBinaryCandidates(binary) {
|
|
|
51980
52211
|
var INSTALLED_META_FILE = ".aft-installed";
|
|
51981
52212
|
function writeInstalledMetaIn(installDir, version2, sha256) {
|
|
51982
52213
|
try {
|
|
51983
|
-
|
|
52214
|
+
mkdirSync8(installDir, { recursive: true });
|
|
51984
52215
|
const meta3 = {
|
|
51985
52216
|
version: version2,
|
|
51986
52217
|
installedAt: new Date().toISOString(),
|
|
@@ -51996,7 +52227,7 @@ function readInstalledMetaIn(installDir) {
|
|
|
51996
52227
|
try {
|
|
51997
52228
|
if (!statSync4(path2).isFile())
|
|
51998
52229
|
return null;
|
|
51999
|
-
const raw =
|
|
52230
|
+
const raw = readFileSync8(path2, "utf8");
|
|
52000
52231
|
const parsed = JSON.parse(raw);
|
|
52001
52232
|
if (typeof parsed.version !== "string" || parsed.version.length === 0)
|
|
52002
52233
|
return null;
|
|
@@ -52020,17 +52251,17 @@ function lockPath(npmPackage) {
|
|
|
52020
52251
|
}
|
|
52021
52252
|
var STALE_LOCK_MS2 = 30 * 60 * 1000;
|
|
52022
52253
|
function acquireInstallLock(lockKey) {
|
|
52023
|
-
|
|
52254
|
+
mkdirSync8(lspPackageDir(lockKey), { recursive: true });
|
|
52024
52255
|
const lock = lockPath(lockKey);
|
|
52025
52256
|
const tryClaim = () => {
|
|
52026
52257
|
try {
|
|
52027
|
-
const fd =
|
|
52258
|
+
const fd = openSync4(lock, "wx");
|
|
52028
52259
|
try {
|
|
52029
52260
|
writeFileSync7(fd, `${process.pid}
|
|
52030
52261
|
${new Date().toISOString()}
|
|
52031
52262
|
`);
|
|
52032
52263
|
} finally {
|
|
52033
|
-
|
|
52264
|
+
closeSync4(fd);
|
|
52034
52265
|
}
|
|
52035
52266
|
return true;
|
|
52036
52267
|
} catch (err) {
|
|
@@ -52046,7 +52277,7 @@ ${new Date().toISOString()}
|
|
|
52046
52277
|
let owningPid = null;
|
|
52047
52278
|
let lockMtimeMs = 0;
|
|
52048
52279
|
try {
|
|
52049
|
-
const raw =
|
|
52280
|
+
const raw = readFileSync8(lock, "utf8");
|
|
52050
52281
|
const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
|
|
52051
52282
|
const parsed = Number.parseInt(firstLine, 10);
|
|
52052
52283
|
if (Number.isFinite(parsed) && parsed > 0)
|
|
@@ -52084,7 +52315,7 @@ function releaseInstallLock(lockKey) {
|
|
|
52084
52315
|
try {
|
|
52085
52316
|
let owningPid = null;
|
|
52086
52317
|
try {
|
|
52087
|
-
const raw =
|
|
52318
|
+
const raw = readFileSync8(lock, "utf8");
|
|
52088
52319
|
const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
|
|
52089
52320
|
const parsed = Number.parseInt(firstLine, 10);
|
|
52090
52321
|
if (Number.isFinite(parsed) && parsed > 0)
|
|
@@ -52125,7 +52356,7 @@ var VERSION_CHECK_FILE = ".aft-version-check";
|
|
|
52125
52356
|
function readVersionCheck(npmPackage) {
|
|
52126
52357
|
const file2 = join13(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
|
|
52127
52358
|
try {
|
|
52128
|
-
const raw =
|
|
52359
|
+
const raw = readFileSync8(file2, "utf8");
|
|
52129
52360
|
const parsed = JSON.parse(raw);
|
|
52130
52361
|
if (typeof parsed.last_checked === "string") {
|
|
52131
52362
|
return {
|
|
@@ -52139,7 +52370,7 @@ function readVersionCheck(npmPackage) {
|
|
|
52139
52370
|
}
|
|
52140
52371
|
}
|
|
52141
52372
|
function writeVersionCheck(npmPackage, latest) {
|
|
52142
|
-
|
|
52373
|
+
mkdirSync8(lspPackageDir(npmPackage), { recursive: true });
|
|
52143
52374
|
const file2 = join13(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
|
|
52144
52375
|
const record2 = {
|
|
52145
52376
|
last_checked: new Date().toISOString(),
|
|
@@ -52302,7 +52533,7 @@ var NPM_LSP_TABLE = [
|
|
|
52302
52533
|
];
|
|
52303
52534
|
|
|
52304
52535
|
// src/lsp-project-relevance.ts
|
|
52305
|
-
import { existsSync as
|
|
52536
|
+
import { existsSync as existsSync11, readdirSync as readdirSync3, readFileSync as readFileSync9 } from "node:fs";
|
|
52306
52537
|
import { join as join14 } from "node:path";
|
|
52307
52538
|
var MAX_WALK_DIRS = 200;
|
|
52308
52539
|
var MAX_WALK_DEPTH = 4;
|
|
@@ -52320,7 +52551,7 @@ function hasRootMarker(projectRoot, rootMarkers) {
|
|
|
52320
52551
|
if (!rootMarkers)
|
|
52321
52552
|
return false;
|
|
52322
52553
|
for (const marker of rootMarkers) {
|
|
52323
|
-
if (
|
|
52554
|
+
if (existsSync11(join14(projectRoot, marker)))
|
|
52324
52555
|
return true;
|
|
52325
52556
|
}
|
|
52326
52557
|
return false;
|
|
@@ -52344,7 +52575,7 @@ function hasPackageJsonDep(projectRoot, depNames) {
|
|
|
52344
52575
|
}
|
|
52345
52576
|
function readPackageJson(projectRoot) {
|
|
52346
52577
|
try {
|
|
52347
|
-
const raw =
|
|
52578
|
+
const raw = readFileSync9(join14(projectRoot, "package.json"), "utf8");
|
|
52348
52579
|
const parsed = JSON.parse(raw);
|
|
52349
52580
|
if (typeof parsed !== "object" || parsed === null)
|
|
52350
52581
|
return null;
|
|
@@ -52671,16 +52902,16 @@ function installedBinaryPath(spec) {
|
|
|
52671
52902
|
return null;
|
|
52672
52903
|
}
|
|
52673
52904
|
function sha256OfFileSync(path2) {
|
|
52674
|
-
return createHash5("sha256").update(
|
|
52905
|
+
return createHash5("sha256").update(readFileSync10(path2)).digest("hex");
|
|
52675
52906
|
}
|
|
52676
52907
|
function quarantineCachedNpmInstall(spec, reason) {
|
|
52677
52908
|
const packageDir = lspPackageDir(spec.npm);
|
|
52678
52909
|
const dest = join15(packageDir, "..", ".quarantine", encodeURIComponent(spec.npm), `${Date.now()}`);
|
|
52679
52910
|
warn2(`[lsp] tofu_mismatch ${spec.npm}: ${reason}; quarantining ${packageDir} -> ${dest}`);
|
|
52680
52911
|
try {
|
|
52681
|
-
|
|
52682
|
-
|
|
52683
|
-
|
|
52912
|
+
mkdirSync9(join15(dest, ".."), { recursive: true });
|
|
52913
|
+
rmSync5(dest, { recursive: true, force: true });
|
|
52914
|
+
renameSync6(packageDir, dest);
|
|
52684
52915
|
} catch (err) {
|
|
52685
52916
|
warn2(`[lsp] tofu_mismatch ${spec.npm}: failed to quarantine cache entry: ${err}`);
|
|
52686
52917
|
}
|
|
@@ -52756,25 +52987,25 @@ function runAutoInstall(projectRoot, config2, fetchImpl2 = fetch) {
|
|
|
52756
52987
|
|
|
52757
52988
|
// src/lsp-github-install.ts
|
|
52758
52989
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
52759
|
-
import { createHash as createHash6, randomBytes } from "node:crypto";
|
|
52990
|
+
import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
|
|
52760
52991
|
import {
|
|
52761
52992
|
copyFileSync as copyFileSync3,
|
|
52762
52993
|
createReadStream as createReadStream2,
|
|
52763
52994
|
createWriteStream as createWriteStream3,
|
|
52764
|
-
existsSync as
|
|
52995
|
+
existsSync as existsSync12,
|
|
52765
52996
|
lstatSync as lstatSync2,
|
|
52766
|
-
mkdirSync as
|
|
52997
|
+
mkdirSync as mkdirSync10,
|
|
52767
52998
|
readdirSync as readdirSync4,
|
|
52768
|
-
readFileSync as
|
|
52999
|
+
readFileSync as readFileSync11,
|
|
52769
53000
|
readlinkSync as readlinkSync2,
|
|
52770
53001
|
realpathSync as realpathSync3,
|
|
52771
|
-
renameSync as
|
|
52772
|
-
rmSync as
|
|
53002
|
+
renameSync as renameSync7,
|
|
53003
|
+
rmSync as rmSync6,
|
|
52773
53004
|
statSync as statSync6,
|
|
52774
53005
|
unlinkSync as unlinkSync6,
|
|
52775
53006
|
writeFileSync as writeFileSync8
|
|
52776
53007
|
} from "node:fs";
|
|
52777
|
-
import { dirname as
|
|
53008
|
+
import { dirname as dirname7, join as join16, relative as relative2, resolve as resolve3 } from "node:path";
|
|
52778
53009
|
import { Readable as Readable3 } from "node:stream";
|
|
52779
53010
|
import { pipeline as pipeline3 } from "node:stream/promises";
|
|
52780
53011
|
|
|
@@ -52899,7 +53130,7 @@ function readGithubInstalledMetaIn(installDir) {
|
|
|
52899
53130
|
const path2 = join16(installDir, INSTALLED_META_FILE2);
|
|
52900
53131
|
if (!statSync6(path2).isFile())
|
|
52901
53132
|
return null;
|
|
52902
|
-
const parsed = JSON.parse(
|
|
53133
|
+
const parsed = JSON.parse(readFileSync11(path2, "utf8"));
|
|
52903
53134
|
if (typeof parsed.version !== "string" || parsed.version.length === 0)
|
|
52904
53135
|
return null;
|
|
52905
53136
|
return {
|
|
@@ -52915,7 +53146,7 @@ function readGithubInstalledMetaIn(installDir) {
|
|
|
52915
53146
|
}
|
|
52916
53147
|
function writeGithubInstalledMetaIn(installDir, version2, binarySha256, archiveSha256) {
|
|
52917
53148
|
try {
|
|
52918
|
-
|
|
53149
|
+
mkdirSync10(installDir, { recursive: true });
|
|
52919
53150
|
const meta3 = {
|
|
52920
53151
|
version: version2,
|
|
52921
53152
|
installedAt: new Date().toISOString(),
|
|
@@ -52940,7 +53171,7 @@ function sha256OfFile(path2) {
|
|
|
52940
53171
|
});
|
|
52941
53172
|
}
|
|
52942
53173
|
function sha256OfFileSync2(path2) {
|
|
52943
|
-
return createHash6("sha256").update(
|
|
53174
|
+
return createHash6("sha256").update(readFileSync11(path2)).digest("hex");
|
|
52944
53175
|
}
|
|
52945
53176
|
async function fetchReleaseByTag(githubRepo, tag, fetchImpl2, signal) {
|
|
52946
53177
|
const candidates = [];
|
|
@@ -53116,7 +53347,7 @@ async function downloadFile(url2, destPath, fetchImpl2, assetSize, signal) {
|
|
|
53116
53347
|
if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES3) {
|
|
53117
53348
|
throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES3}`);
|
|
53118
53349
|
}
|
|
53119
|
-
|
|
53350
|
+
mkdirSync10(dirname7(destPath), { recursive: true });
|
|
53120
53351
|
let bytesWritten = 0;
|
|
53121
53352
|
const guard = new TransformStream({
|
|
53122
53353
|
transform(chunk, controller) {
|
|
@@ -53237,23 +53468,23 @@ function precheckArchiveContents(archivePath, archiveType) {
|
|
|
53237
53468
|
}
|
|
53238
53469
|
}
|
|
53239
53470
|
function extractArchiveSafely(archivePath, destDir, archiveType) {
|
|
53240
|
-
const suffix =
|
|
53471
|
+
const suffix = randomBytes2(8).toString("hex");
|
|
53241
53472
|
const stagingDir = `${destDir}.staging-${suffix}`;
|
|
53242
53473
|
try {
|
|
53243
|
-
|
|
53474
|
+
rmSync6(stagingDir, { recursive: true, force: true });
|
|
53244
53475
|
} catch {}
|
|
53245
|
-
|
|
53476
|
+
mkdirSync10(stagingDir, { recursive: true });
|
|
53246
53477
|
try {
|
|
53247
53478
|
precheckArchiveContents(archivePath, archiveType);
|
|
53248
53479
|
runPlatformExtractor(archivePath, stagingDir, archiveType);
|
|
53249
53480
|
validateExtraction(stagingDir);
|
|
53250
53481
|
try {
|
|
53251
|
-
|
|
53482
|
+
rmSync6(destDir, { recursive: true, force: true });
|
|
53252
53483
|
} catch {}
|
|
53253
|
-
|
|
53484
|
+
renameSync7(stagingDir, destDir);
|
|
53254
53485
|
} catch (err) {
|
|
53255
53486
|
try {
|
|
53256
|
-
|
|
53487
|
+
rmSync6(stagingDir, { recursive: true, force: true });
|
|
53257
53488
|
} catch {}
|
|
53258
53489
|
throw err;
|
|
53259
53490
|
}
|
|
@@ -53263,9 +53494,9 @@ function quarantineCachedGithubInstall(spec, reason) {
|
|
|
53263
53494
|
const dest = join16(ghCacheRoot(), ".quarantine", encodeURIComponent(spec.id), `${Date.now()}`);
|
|
53264
53495
|
warn2(`[lsp] tofu_mismatch ${spec.id}: ${reason}; quarantining ${packageDir} -> ${dest}`);
|
|
53265
53496
|
try {
|
|
53266
|
-
|
|
53267
|
-
|
|
53268
|
-
|
|
53497
|
+
mkdirSync10(dirname7(dest), { recursive: true });
|
|
53498
|
+
rmSync6(dest, { recursive: true, force: true });
|
|
53499
|
+
renameSync7(packageDir, dest);
|
|
53269
53500
|
} catch (err) {
|
|
53270
53501
|
warn2(`[lsp] tofu_mismatch ${spec.id}: failed to quarantine cache entry: ${err}`);
|
|
53271
53502
|
}
|
|
@@ -53392,12 +53623,12 @@ async function downloadAndInstall(spec, tag, assets, platform2, arch, fetchImpl2
|
|
|
53392
53623
|
} catch {}
|
|
53393
53624
|
}
|
|
53394
53625
|
const innerBinaryPath = join16(extractDir, spec.binaryPathInArchive(platform2, arch, version2));
|
|
53395
|
-
if (!
|
|
53626
|
+
if (!existsSync12(innerBinaryPath)) {
|
|
53396
53627
|
error2(`[lsp] ${spec.id}: extracted binary not found at ${innerBinaryPath}`);
|
|
53397
53628
|
return null;
|
|
53398
53629
|
}
|
|
53399
53630
|
const targetBinary = ghBinaryPath(spec, platform2);
|
|
53400
|
-
|
|
53631
|
+
mkdirSync10(dirname7(targetBinary), { recursive: true });
|
|
53401
53632
|
try {
|
|
53402
53633
|
copyFileSync3(innerBinaryPath, targetBinary);
|
|
53403
53634
|
if (platform2 !== "win32") {
|
|
@@ -53476,7 +53707,7 @@ function runGithubAutoInstall(relevantServers, config2, fetchImpl2 = fetch) {
|
|
|
53476
53707
|
if (!host) {
|
|
53477
53708
|
for (const spec of GITHUB_LSP_TABLE) {
|
|
53478
53709
|
try {
|
|
53479
|
-
if (
|
|
53710
|
+
if (existsSync12(ghBinDir(spec))) {
|
|
53480
53711
|
cachedBinDirs.push(ghBinDir(spec));
|
|
53481
53712
|
}
|
|
53482
53713
|
} catch {}
|
|
@@ -53639,9 +53870,9 @@ function normalizeToolMap(tools) {
|
|
|
53639
53870
|
}
|
|
53640
53871
|
|
|
53641
53872
|
// src/notifications.ts
|
|
53642
|
-
import { existsSync as
|
|
53873
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync11, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "node:fs";
|
|
53643
53874
|
import { homedir as homedir9, platform as platform2 } from "node:os";
|
|
53644
|
-
import { join as join17 } from "node:path";
|
|
53875
|
+
import { dirname as dirname8, join as join17 } from "node:path";
|
|
53645
53876
|
function isTuiMode() {
|
|
53646
53877
|
return process.env.OPENCODE_CLIENT === "cli";
|
|
53647
53878
|
}
|
|
@@ -53676,13 +53907,13 @@ function getDesktopStatePath() {
|
|
|
53676
53907
|
}
|
|
53677
53908
|
return null;
|
|
53678
53909
|
}
|
|
53679
|
-
function readDesktopState(
|
|
53910
|
+
function readDesktopState() {
|
|
53680
53911
|
const statePath = getDesktopStatePath();
|
|
53681
|
-
if (!statePath || !
|
|
53682
|
-
return {
|
|
53912
|
+
if (!statePath || !existsSync13(statePath)) {
|
|
53913
|
+
return { serverUrl: null };
|
|
53683
53914
|
}
|
|
53684
53915
|
try {
|
|
53685
|
-
const raw =
|
|
53916
|
+
const raw = readFileSync12(statePath, "utf-8");
|
|
53686
53917
|
const state = JSON.parse(raw);
|
|
53687
53918
|
let serverUrl = null;
|
|
53688
53919
|
const serverStr = state.server;
|
|
@@ -53694,19 +53925,43 @@ function readDesktopState(directory) {
|
|
|
53694
53925
|
}
|
|
53695
53926
|
} catch {}
|
|
53696
53927
|
}
|
|
53697
|
-
|
|
53698
|
-
const layoutPage = state["layout.page"];
|
|
53699
|
-
if (typeof layoutPage === "string") {
|
|
53700
|
-
const parsed = JSON.parse(layoutPage);
|
|
53701
|
-
const lastProjectSession = parsed.lastProjectSession;
|
|
53702
|
-
if (lastProjectSession) {
|
|
53703
|
-
const entry = lastProjectSession[directory];
|
|
53704
|
-
sessionId = entry?.id ?? null;
|
|
53705
|
-
}
|
|
53706
|
-
}
|
|
53707
|
-
return { sessionId, serverUrl };
|
|
53928
|
+
return { serverUrl };
|
|
53708
53929
|
} catch {
|
|
53709
|
-
return {
|
|
53930
|
+
return { serverUrl: null };
|
|
53931
|
+
}
|
|
53932
|
+
}
|
|
53933
|
+
var MAX_PENDING_DESKTOP_NOTIFICATIONS = 20;
|
|
53934
|
+
var pendingDesktopNotifications = new Map;
|
|
53935
|
+
function getExplicitSessionId(opts) {
|
|
53936
|
+
const sessionId = opts.sessionId?.trim();
|
|
53937
|
+
return sessionId ? sessionId : null;
|
|
53938
|
+
}
|
|
53939
|
+
function enqueuePendingDesktopNotification(directory, notification) {
|
|
53940
|
+
if (!directory)
|
|
53941
|
+
return;
|
|
53942
|
+
const pending = pendingDesktopNotifications.get(directory) ?? [];
|
|
53943
|
+
if (pending.some((item) => item.key === notification.key))
|
|
53944
|
+
return;
|
|
53945
|
+
pending.push(notification);
|
|
53946
|
+
if (pending.length > MAX_PENDING_DESKTOP_NOTIFICATIONS) {
|
|
53947
|
+
pending.splice(0, pending.length - MAX_PENDING_DESKTOP_NOTIFICATIONS);
|
|
53948
|
+
}
|
|
53949
|
+
pendingDesktopNotifications.set(directory, pending);
|
|
53950
|
+
}
|
|
53951
|
+
async function flushPendingDesktopNotifications(opts, sessionId) {
|
|
53952
|
+
const pending = pendingDesktopNotifications.get(opts.directory);
|
|
53953
|
+
if (!pending?.length)
|
|
53954
|
+
return;
|
|
53955
|
+
pendingDesktopNotifications.delete(opts.directory);
|
|
53956
|
+
for (const notification of pending) {
|
|
53957
|
+
if (notification.shouldSkip?.())
|
|
53958
|
+
continue;
|
|
53959
|
+
const delivered = await sendIgnoredMessage(opts.client, sessionId, notification.text);
|
|
53960
|
+
if (delivered) {
|
|
53961
|
+
notification.onDelivered?.();
|
|
53962
|
+
} else {
|
|
53963
|
+
enqueuePendingDesktopNotification(opts.directory, notification);
|
|
53964
|
+
}
|
|
53710
53965
|
}
|
|
53711
53966
|
}
|
|
53712
53967
|
function getServerAuth() {
|
|
@@ -53771,52 +54026,72 @@ async function sendWarning(opts, message) {
|
|
|
53771
54026
|
const toastSent = await showTuiToast(opts.client, "AFT Warning", message, "warning", 1e4);
|
|
53772
54027
|
if (toastSent)
|
|
53773
54028
|
return;
|
|
53774
|
-
const { sessionId } = readDesktopState(opts.directory);
|
|
53775
|
-
if (!sessionId)
|
|
53776
|
-
return;
|
|
53777
54029
|
const text = `${WARNING_MARKER} ${message}`;
|
|
54030
|
+
const sessionId = getExplicitSessionId(opts);
|
|
54031
|
+
if (!sessionId) {
|
|
54032
|
+
enqueuePendingDesktopNotification(opts.directory, { key: `warning:${message}`, text });
|
|
54033
|
+
return;
|
|
54034
|
+
}
|
|
54035
|
+
await flushPendingDesktopNotifications(opts, sessionId);
|
|
53778
54036
|
sessionLog(sessionId, `[aft-plugin] sending warning to session ${sessionId}`);
|
|
53779
54037
|
await sendIgnoredMessage(opts.client, sessionId, text);
|
|
53780
54038
|
}
|
|
53781
54039
|
async function sendFeatureAnnouncement(opts, version2, features, footer, storageDir) {
|
|
53782
|
-
if (storageDir)
|
|
53783
|
-
|
|
53784
|
-
try {
|
|
53785
|
-
if (existsSync12(versionFile)) {
|
|
53786
|
-
const lastVersion = readFileSync11(versionFile, "utf-8").trim();
|
|
53787
|
-
if (lastVersion === version2)
|
|
53788
|
-
return;
|
|
53789
|
-
}
|
|
53790
|
-
} catch {}
|
|
53791
|
-
}
|
|
54040
|
+
if (hasAnnouncedVersion(storageDir, version2))
|
|
54041
|
+
return;
|
|
53792
54042
|
const hasFooter = typeof footer === "string" && footer.trim().length > 0;
|
|
53793
54043
|
const featureText = hasFooter ? [features.map((f) => `• ${f}`).join(`
|
|
53794
54044
|
`), "", footer].join(`
|
|
53795
54045
|
`) : features.map((f) => `• ${f}`).join(`
|
|
53796
54046
|
`);
|
|
53797
54047
|
const toastSent = await showTuiToast(opts.client, `AFT v${version2}`, featureText, "info", 12000);
|
|
53798
|
-
if (
|
|
53799
|
-
|
|
53800
|
-
|
|
53801
|
-
|
|
53802
|
-
|
|
53803
|
-
|
|
53804
|
-
|
|
53805
|
-
|
|
53806
|
-
if (hasFooter)
|
|
53807
|
-
sections.push("", footer);
|
|
53808
|
-
const text = sections.join(`
|
|
54048
|
+
if (toastSent) {
|
|
54049
|
+
persistAnnouncedVersion(storageDir, version2);
|
|
54050
|
+
return;
|
|
54051
|
+
}
|
|
54052
|
+
const sections = [`${FEATURE_MARKER} v${version2}:`, ...features.map((f) => ` • ${f}`)];
|
|
54053
|
+
if (hasFooter)
|
|
54054
|
+
sections.push("", footer);
|
|
54055
|
+
const text = sections.join(`
|
|
53809
54056
|
`);
|
|
53810
|
-
|
|
53811
|
-
|
|
54057
|
+
const pending = {
|
|
54058
|
+
key: `feature:${version2}`,
|
|
54059
|
+
text,
|
|
54060
|
+
shouldSkip: () => hasAnnouncedVersion(storageDir, version2),
|
|
54061
|
+
onDelivered: () => persistAnnouncedVersion(storageDir, version2)
|
|
54062
|
+
};
|
|
54063
|
+
const sessionId = getExplicitSessionId(opts);
|
|
54064
|
+
if (!sessionId) {
|
|
54065
|
+
enqueuePendingDesktopNotification(opts.directory, pending);
|
|
54066
|
+
return;
|
|
53812
54067
|
}
|
|
53813
|
-
|
|
53814
|
-
|
|
53815
|
-
|
|
53816
|
-
|
|
53817
|
-
|
|
54068
|
+
await flushPendingDesktopNotifications(opts, sessionId);
|
|
54069
|
+
if (hasAnnouncedVersion(storageDir, version2))
|
|
54070
|
+
return;
|
|
54071
|
+
sessionLog(sessionId, `[aft-plugin] sending feature announcement for v${version2}`);
|
|
54072
|
+
if (await sendIgnoredMessage(opts.client, sessionId, text)) {
|
|
54073
|
+
persistAnnouncedVersion(storageDir, version2);
|
|
53818
54074
|
}
|
|
53819
54075
|
}
|
|
54076
|
+
function hasAnnouncedVersion(storageDir, version2) {
|
|
54077
|
+
if (!storageDir)
|
|
54078
|
+
return false;
|
|
54079
|
+
const versionFile = repairRootScopedStorageFile(storageDir, "opencode", "last_announced_version");
|
|
54080
|
+
try {
|
|
54081
|
+
return existsSync13(versionFile) && readFileSync12(versionFile, "utf-8").trim() === version2;
|
|
54082
|
+
} catch {
|
|
54083
|
+
return false;
|
|
54084
|
+
}
|
|
54085
|
+
}
|
|
54086
|
+
function persistAnnouncedVersion(storageDir, version2) {
|
|
54087
|
+
if (!storageDir)
|
|
54088
|
+
return;
|
|
54089
|
+
try {
|
|
54090
|
+
const versionFile = repairRootScopedStorageFile(storageDir, "opencode", "last_announced_version");
|
|
54091
|
+
mkdirSync11(dirname8(versionFile), { recursive: true });
|
|
54092
|
+
writeFileSync9(versionFile, version2);
|
|
54093
|
+
} catch {}
|
|
54094
|
+
}
|
|
53820
54095
|
async function readWarnedTools(bridge) {
|
|
53821
54096
|
try {
|
|
53822
54097
|
const resp = await bridge.send("db_get_state", { key: "warned_tools" });
|
|
@@ -53883,6 +54158,9 @@ function formatConfigureWarning(warning) {
|
|
|
53883
54158
|
${warning.hint}`;
|
|
53884
54159
|
}
|
|
53885
54160
|
async function deliverConfigureWarnings(opts, warnings) {
|
|
54161
|
+
if (opts.projectRoot) {
|
|
54162
|
+
await flushPendingDesktopNotifications({ client: opts.client, directory: opts.projectRoot }, opts.sessionId);
|
|
54163
|
+
}
|
|
53886
54164
|
if (warnings.length === 0)
|
|
53887
54165
|
return;
|
|
53888
54166
|
for (const warning of warnings) {
|
|
@@ -53898,10 +54176,10 @@ async function deliverConfigureWarnings(opts, warnings) {
|
|
|
53898
54176
|
async function cleanupWarnings(opts) {
|
|
53899
54177
|
if (isTuiMode())
|
|
53900
54178
|
return;
|
|
53901
|
-
const
|
|
54179
|
+
const sessionId = getExplicitSessionId(opts);
|
|
53902
54180
|
if (!sessionId)
|
|
53903
54181
|
return;
|
|
53904
|
-
const effectiveServerUrl = opts.serverUrl ||
|
|
54182
|
+
const effectiveServerUrl = opts.serverUrl || readDesktopState().serverUrl;
|
|
53905
54183
|
if (!effectiveServerUrl)
|
|
53906
54184
|
return;
|
|
53907
54185
|
const messages = await getSessionMessages(opts.client, sessionId);
|
|
@@ -54035,10 +54313,10 @@ function writeTerminal(terminal, data) {
|
|
|
54035
54313
|
}
|
|
54036
54314
|
|
|
54037
54315
|
// src/shared/rpc-server.ts
|
|
54038
|
-
import { randomBytes as
|
|
54039
|
-
import { mkdirSync as
|
|
54316
|
+
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
54317
|
+
import { mkdirSync as mkdirSync12, renameSync as renameSync8, unlinkSync as unlinkSync7, writeFileSync as writeFileSync10 } from "node:fs";
|
|
54040
54318
|
import { createServer } from "node:http";
|
|
54041
|
-
import { dirname as
|
|
54319
|
+
import { dirname as dirname9, join as join19 } from "node:path";
|
|
54042
54320
|
|
|
54043
54321
|
// src/shared/rpc-utils.ts
|
|
54044
54322
|
import { createHash as createHash7 } from "node:crypto";
|
|
@@ -54063,7 +54341,7 @@ class AftRpcServer {
|
|
|
54063
54341
|
instanceId;
|
|
54064
54342
|
constructor(storageDir, directory) {
|
|
54065
54343
|
this.portsDir = rpcPortFileDir(storageDir, directory);
|
|
54066
|
-
this.instanceId =
|
|
54344
|
+
this.instanceId = randomBytes3(8).toString("hex");
|
|
54067
54345
|
this.portFilePath = join19(this.portsDir, `${this.instanceId}.json`);
|
|
54068
54346
|
}
|
|
54069
54347
|
handle(method, handler) {
|
|
@@ -54083,17 +54361,17 @@ class AftRpcServer {
|
|
|
54083
54361
|
return;
|
|
54084
54362
|
}
|
|
54085
54363
|
this.port = addr.port;
|
|
54086
|
-
this.token =
|
|
54364
|
+
this.token = randomBytes3(32).toString("hex");
|
|
54087
54365
|
this.server = server;
|
|
54088
54366
|
try {
|
|
54089
|
-
const dir =
|
|
54090
|
-
|
|
54367
|
+
const dir = dirname9(this.portFilePath);
|
|
54368
|
+
mkdirSync12(dir, { recursive: true, mode: 448 });
|
|
54091
54369
|
const tmpPath = `${this.portFilePath}.tmp`;
|
|
54092
54370
|
writeFileSync10(tmpPath, JSON.stringify({ port: this.port, token: this.token }), {
|
|
54093
54371
|
encoding: "utf-8",
|
|
54094
54372
|
mode: 384
|
|
54095
54373
|
});
|
|
54096
|
-
|
|
54374
|
+
renameSync8(tmpPath, this.portFilePath);
|
|
54097
54375
|
log2(`RPC server listening on 127.0.0.1:${this.port}`);
|
|
54098
54376
|
} catch (err) {
|
|
54099
54377
|
warn2(`Failed to write RPC port file: ${err}`);
|
|
@@ -54402,8 +54680,8 @@ function formatStatusMarkdown(status) {
|
|
|
54402
54680
|
|
|
54403
54681
|
// src/shared/tui-config.ts
|
|
54404
54682
|
var import_comment_json4 = __toESM(require_src2(), 1);
|
|
54405
|
-
import { existsSync as
|
|
54406
|
-
import { dirname as
|
|
54683
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync13, readFileSync as readFileSync13, writeFileSync as writeFileSync11 } from "node:fs";
|
|
54684
|
+
import { dirname as dirname10, join as join21 } from "node:path";
|
|
54407
54685
|
|
|
54408
54686
|
// src/shared/opencode-config-dir.ts
|
|
54409
54687
|
import { homedir as homedir10 } from "node:os";
|
|
@@ -54439,9 +54717,9 @@ function resolveTuiConfigPath() {
|
|
|
54439
54717
|
const configDir = getOpenCodeConfigPaths({ binary: "opencode" }).configDir;
|
|
54440
54718
|
const jsoncPath = join21(configDir, "tui.jsonc");
|
|
54441
54719
|
const jsonPath = join21(configDir, "tui.json");
|
|
54442
|
-
if (
|
|
54720
|
+
if (existsSync14(jsoncPath))
|
|
54443
54721
|
return jsoncPath;
|
|
54444
|
-
if (
|
|
54722
|
+
if (existsSync14(jsonPath))
|
|
54445
54723
|
return jsonPath;
|
|
54446
54724
|
return jsonPath;
|
|
54447
54725
|
}
|
|
@@ -54449,8 +54727,8 @@ function ensureTuiPluginEntry() {
|
|
|
54449
54727
|
try {
|
|
54450
54728
|
const configPath = resolveTuiConfigPath();
|
|
54451
54729
|
let config2 = {};
|
|
54452
|
-
if (
|
|
54453
|
-
config2 = import_comment_json4.parse(
|
|
54730
|
+
if (existsSync14(configPath)) {
|
|
54731
|
+
config2 = import_comment_json4.parse(readFileSync13(configPath, "utf-8")) ?? {};
|
|
54454
54732
|
}
|
|
54455
54733
|
const plugins = Array.isArray(config2.plugin) ? config2.plugin.filter((value) => typeof value === "string") : [];
|
|
54456
54734
|
if (plugins.some((plugin) => plugin === PLUGIN_NAME || plugin.startsWith(`${PLUGIN_NAME}@`) || plugin.includes("opencode-plugin") || plugin.includes("aft-opencode"))) {
|
|
@@ -54458,7 +54736,7 @@ function ensureTuiPluginEntry() {
|
|
|
54458
54736
|
}
|
|
54459
54737
|
plugins.push(PLUGIN_ENTRY);
|
|
54460
54738
|
config2.plugin = plugins;
|
|
54461
|
-
|
|
54739
|
+
mkdirSync13(dirname10(configPath), { recursive: true });
|
|
54462
54740
|
writeFileSync11(configPath, `${import_comment_json4.stringify(config2, null, 2)}
|
|
54463
54741
|
`);
|
|
54464
54742
|
log2(`[aft-plugin] added TUI plugin entry to ${configPath}`);
|
|
@@ -54522,11 +54800,14 @@ function registerShutdownCleanup(fn) {
|
|
|
54522
54800
|
}
|
|
54523
54801
|
|
|
54524
54802
|
// src/tools/ast.ts
|
|
54525
|
-
import { tool as
|
|
54803
|
+
import { tool as tool3 } from "@opencode-ai/plugin";
|
|
54526
54804
|
|
|
54527
54805
|
// src/tools/_shared.ts
|
|
54528
54806
|
import * as fs3 from "node:fs";
|
|
54529
54807
|
import * as path2 from "node:path";
|
|
54808
|
+
import { tool as tool2 } from "@opencode-ai/plugin";
|
|
54809
|
+
var z2 = tool2.schema;
|
|
54810
|
+
var optionalInt = (min, max) => z2.number().int().min(min).max(max).optional();
|
|
54530
54811
|
var LONG_RUNNING_COMMAND_TIMEOUT_MS = {
|
|
54531
54812
|
callers: 60000,
|
|
54532
54813
|
trace_to: 60000,
|
|
@@ -54739,7 +55020,7 @@ function permissionDeniedResponse(message) {
|
|
|
54739
55020
|
}
|
|
54740
55021
|
|
|
54741
55022
|
// src/tools/ast.ts
|
|
54742
|
-
var
|
|
55023
|
+
var z3 = tool3.schema;
|
|
54743
55024
|
function showOutputToUser(context, output) {
|
|
54744
55025
|
const ctx = context;
|
|
54745
55026
|
ctx.metadata?.({ metadata: { output } });
|
|
@@ -54772,11 +55053,11 @@ function astTools(ctx) {
|
|
|
54772
55053
|
|
|
54773
55054
|
` + "Returns: Text summary — 'Found N match(es) across M file(s)' followed by file:line blocks with matched text and captured meta-variables.",
|
|
54774
55055
|
args: {
|
|
54775
|
-
pattern:
|
|
54776
|
-
lang:
|
|
54777
|
-
paths:
|
|
54778
|
-
globs:
|
|
54779
|
-
contextLines:
|
|
55056
|
+
pattern: z3.string().describe("AST pattern with meta-variables ($VAR, $$$). Must be complete AST node."),
|
|
55057
|
+
lang: z3.enum(SUPPORTED_LANGS).describe("Target language"),
|
|
55058
|
+
paths: z3.array(z3.string()).optional().describe("Paths to search (default: ['.'])"),
|
|
55059
|
+
globs: z3.array(z3.string()).optional().describe("Include/exclude globs (prefix ! to exclude)"),
|
|
55060
|
+
contextLines: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("Number of context lines to show around each match")
|
|
54780
55061
|
},
|
|
54781
55062
|
execute: async (args, context) => {
|
|
54782
55063
|
const externalDenied = await checkAstPathsPermission(context, args.paths);
|
|
@@ -54866,12 +55147,12 @@ ${hint}`;
|
|
|
54866
55147
|
|
|
54867
55148
|
` + "Returns: Text summary — 'Replaced N match(es) across M file(s)' (or '[DRY RUN] Would replace...') followed by per-file output. In dry-run mode, each file is shown with its unified diff so you can verify the rewrite before applying (e.g. catch literal $$$ from anonymous-variadic typos). Diff preview is capped at 8KB total; remaining files are summarized.",
|
|
54868
55149
|
args: {
|
|
54869
|
-
pattern:
|
|
54870
|
-
rewrite:
|
|
54871
|
-
lang:
|
|
54872
|
-
paths:
|
|
54873
|
-
globs:
|
|
54874
|
-
dryRun:
|
|
55150
|
+
pattern: z3.string().describe("AST pattern with meta-variables ($VAR, $$$). Must be complete AST node."),
|
|
55151
|
+
rewrite: z3.string().describe("Replacement pattern (can use $VAR from pattern)"),
|
|
55152
|
+
lang: z3.enum(SUPPORTED_LANGS).describe("Target language"),
|
|
55153
|
+
paths: z3.array(z3.string()).optional().describe("Paths to search (default: ['.'])"),
|
|
55154
|
+
globs: z3.array(z3.string()).optional().describe("Include/exclude globs (prefix ! to exclude)"),
|
|
55155
|
+
dryRun: z3.boolean().optional().describe("Preview changes without applying (default: false)")
|
|
54875
55156
|
},
|
|
54876
55157
|
execute: async (args, context) => {
|
|
54877
55158
|
const isDryRun = args.dryRun === true;
|
|
@@ -55036,7 +55317,7 @@ function conflictTools(ctx) {
|
|
|
55036
55317
|
// src/tools/hoisted.ts
|
|
55037
55318
|
import * as fs6 from "node:fs";
|
|
55038
55319
|
import * as path4 from "node:path";
|
|
55039
|
-
import { tool as
|
|
55320
|
+
import { tool as tool7 } from "@opencode-ai/plugin";
|
|
55040
55321
|
|
|
55041
55322
|
// src/patch-parser.ts
|
|
55042
55323
|
function stripHeredoc(input) {
|
|
@@ -55330,7 +55611,7 @@ ${chunk.old_lines.join(`
|
|
|
55330
55611
|
}
|
|
55331
55612
|
|
|
55332
55613
|
// src/tools/bash.ts
|
|
55333
|
-
import { tool as
|
|
55614
|
+
import { tool as tool4 } from "@opencode-ai/plugin";
|
|
55334
55615
|
|
|
55335
55616
|
// src/shared/subagent-detect.ts
|
|
55336
55617
|
var CACHE_MAX_ENTRIES2 = 200;
|
|
@@ -55383,7 +55664,7 @@ function setCache2(sessionId, isSubagent) {
|
|
|
55383
55664
|
}
|
|
55384
55665
|
|
|
55385
55666
|
// src/tools/bash.ts
|
|
55386
|
-
var
|
|
55667
|
+
var z4 = tool4.schema;
|
|
55387
55668
|
var METADATA_PREVIEW_LIMIT = 30 * 1024;
|
|
55388
55669
|
var FOREGROUND_WAIT_WINDOW_MS = 5000;
|
|
55389
55670
|
var FOREGROUND_POLL_INTERVAL_MS = 100;
|
|
@@ -55416,15 +55697,15 @@ function createBashTool(ctx) {
|
|
|
55416
55697
|
return {
|
|
55417
55698
|
description: BASH_DESCRIPTION,
|
|
55418
55699
|
args: {
|
|
55419
|
-
command:
|
|
55420
|
-
timeout:
|
|
55421
|
-
workdir:
|
|
55422
|
-
description:
|
|
55423
|
-
background:
|
|
55424
|
-
compressed:
|
|
55425
|
-
pty:
|
|
55426
|
-
ptyRows:
|
|
55427
|
-
ptyCols:
|
|
55700
|
+
command: z4.string().describe("Shell command to execute through AFT's unified bash schema. Supports normal shell syntax, pipes, redirection, and command rewriting to dedicated AFT tools when available."),
|
|
55701
|
+
timeout: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("Hard kill cap in milliseconds (positive integer). When omitted, the task can run up to 30 minutes. Foreground bash returns inline if the command finishes within ~5s; otherwise it's automatically promoted to background and a completion reminder is delivered when the task actually finishes."),
|
|
55702
|
+
workdir: z4.string().optional().describe("Working directory for command execution. Relative paths resolve through the bridge; defaults to the current tool context/project root when omitted."),
|
|
55703
|
+
description: z4.string().optional().describe("Short 5-10 word human-readable summary shown in OpenCode UI metadata instead of raw shell syntax."),
|
|
55704
|
+
background: z4.boolean().optional().describe("When true, spawn the command in the background and return a task_id for bash_status/bash_kill instead of waiting for completion. Defaults to false."),
|
|
55705
|
+
compressed: z4.boolean().optional().describe("When true or omitted, return compressed output with noisy terminal control sequences reduced. Set to false for raw output."),
|
|
55706
|
+
pty: z4.boolean().optional().describe('When true, spawn the command in a real PTY for interactive programs (python/node/bash REPLs, vim). Implies background: true automatically. Unavailable in subagent sessions. Inspect with bash_status({ taskId, outputMode: "screen" }) and drive interactively with bash_write — its input accepts either a string OR an array like [ "iHello", { key: "esc" }, ":wq", { key: "enter" } ] for atomic text+key sequences.'),
|
|
55707
|
+
ptyRows: optionalInt(1, 60).describe("PTY terminal height in rows — ignored when pty is false. Defaults to 24 when pty: true. Minimum 1, maximum 60."),
|
|
55708
|
+
ptyCols: optionalInt(1, 140).describe("PTY terminal width in columns — ignored when pty is false. Defaults to 80 when pty: true. Minimum 1, maximum 140.")
|
|
55428
55709
|
},
|
|
55429
55710
|
execute: async (args, context) => {
|
|
55430
55711
|
const bashCfg = resolveBashConfig(ctx.config);
|
|
@@ -55434,14 +55715,8 @@ function createBashTool(ctx) {
|
|
|
55434
55715
|
const command = args.command;
|
|
55435
55716
|
const cwd = args.workdir ?? context.directory;
|
|
55436
55717
|
const isSubagent = await resolveIsSubagent(ctx.client, context.sessionID, context.directory);
|
|
55437
|
-
const requestedBackground = args.background === true;
|
|
55438
55718
|
const requestedPty = args.pty === true;
|
|
55439
|
-
|
|
55440
|
-
throw new Error("invalid_request: ptyRows/ptyCols require pty: true");
|
|
55441
|
-
}
|
|
55442
|
-
if (requestedPty && !requestedBackground) {
|
|
55443
|
-
throw new Error("PTY mode requires background: true");
|
|
55444
|
-
}
|
|
55719
|
+
const requestedBackground = args.background === true || requestedPty;
|
|
55445
55720
|
if (requestedPty && isSubagent) {
|
|
55446
55721
|
throw new Error("PTY mode is not available in subagent sessions; subagents cannot drive interactive terminals.");
|
|
55447
55722
|
}
|
|
@@ -55515,6 +55790,10 @@ function createBashTool(ctx) {
|
|
|
55515
55790
|
return rendered2;
|
|
55516
55791
|
}
|
|
55517
55792
|
if (Date.now() - startedAt >= waitTimeoutMs) {
|
|
55793
|
+
if (subagentForcedForeground) {
|
|
55794
|
+
await sleep(FOREGROUND_POLL_INTERVAL_MS);
|
|
55795
|
+
continue;
|
|
55796
|
+
}
|
|
55518
55797
|
const promoted = await callBridge(ctx, context, "bash_promote", { task_id: taskId });
|
|
55519
55798
|
if (promoted.success === false) {
|
|
55520
55799
|
throw new Error(promoted.message ?? "bash_promote failed");
|
|
@@ -55578,8 +55857,8 @@ function createBashStatusTool(ctx) {
|
|
|
55578
55857
|
return {
|
|
55579
55858
|
description: "Read-only snapshot of a background or PTY bash task's current state and output. Returns immediately. Use bash_watch to block on or register for pattern matches and exit events.",
|
|
55580
55859
|
args: {
|
|
55581
|
-
taskId:
|
|
55582
|
-
outputMode:
|
|
55860
|
+
taskId: z4.string().describe("Background task ID returned by bash({ background: true }), e.g. bash-6b454047."),
|
|
55861
|
+
outputMode: z4.enum(["screen", "raw", "both"]).optional().describe("PTY output rendering mode. Defaults to screen for PTY tasks and preserves existing behavior for piped tasks when omitted.")
|
|
55583
55862
|
},
|
|
55584
55863
|
execute: async (args, context) => {
|
|
55585
55864
|
const taskId = args.taskId;
|
|
@@ -55593,7 +55872,7 @@ function createBashKillTool(ctx) {
|
|
|
55593
55872
|
return {
|
|
55594
55873
|
description: "Terminate a running background bash task spawned with bash({ background: true }). Returns confirmation of kill or an error if the task already finished.",
|
|
55595
55874
|
args: {
|
|
55596
|
-
taskId:
|
|
55875
|
+
taskId: z4.string().describe("Background task ID returned by bash({ background: true }), e.g. bash-6b454047.")
|
|
55597
55876
|
},
|
|
55598
55877
|
execute: async (args, context) => {
|
|
55599
55878
|
const data = await callBridge(ctx, context, "bash_kill", {
|
|
@@ -55741,21 +56020,22 @@ function shortenCommand(command) {
|
|
|
55741
56020
|
|
|
55742
56021
|
// src/tools/bash_watch.ts
|
|
55743
56022
|
import * as fs5 from "node:fs/promises";
|
|
55744
|
-
import { tool as
|
|
55745
|
-
var
|
|
56023
|
+
import { tool as tool5 } from "@opencode-ai/plugin";
|
|
56024
|
+
var z5 = tool5.schema;
|
|
55746
56025
|
var BASH_WAIT_POLL_INTERVAL_MS = 100;
|
|
55747
56026
|
var DEFAULT_BASH_STATUS_WAIT_TIMEOUT_MS = 30000;
|
|
55748
56027
|
var MAX_BASH_STATUS_WAIT_TIMEOUT_MS = 300000;
|
|
55749
56028
|
var BASH_TRANSPORT_TIMEOUT_MS2 = 30000;
|
|
56029
|
+
var REGEX_WAIT_SCAN_WINDOW_BYTES = 64 * 1024;
|
|
55750
56030
|
function createBashWatchTool(ctx) {
|
|
55751
56031
|
return {
|
|
55752
56032
|
description: "Block on a background bash task until a pattern matches, it exits, or timeout elapses; or register an async pattern notification with background:true.",
|
|
55753
56033
|
args: {
|
|
55754
|
-
taskId:
|
|
55755
|
-
pattern:
|
|
55756
|
-
background:
|
|
55757
|
-
timeoutMs:
|
|
55758
|
-
once:
|
|
56034
|
+
taskId: z5.string().describe("Background task ID returned by bash({ background: true })."),
|
|
56035
|
+
pattern: z5.union([z5.string(), z5.object({ regex: z5.string() })]).optional().describe("Substring or regex pattern. Optional in sync mode; required with background:true. Sync substring watches keep only the overlap tail needed for boundary matches; sync regex watches use a 64 KB rolling output window."),
|
|
56036
|
+
background: z5.boolean().optional().describe("When true, register an async watch and return immediately. Defaults to false (sync wait)."),
|
|
56037
|
+
timeoutMs: optionalInt(1, MAX_BASH_STATUS_WAIT_TIMEOUT_MS).describe("Sync-only timeout in milliseconds. Default 30000, max 300000."),
|
|
56038
|
+
once: z5.boolean().optional().describe("Async-only. Defaults true; false keeps the watch sticky until task exit.")
|
|
55759
56039
|
},
|
|
55760
56040
|
execute: async (args, context) => {
|
|
55761
56041
|
const taskId = args.taskId;
|
|
@@ -55844,7 +56124,7 @@ async function bashStatusSnapshot2(ctx, runtime, taskId, outputMode, options) {
|
|
|
55844
56124
|
async function waitForBashStatus(ctx, runtime, taskId, outputMode, waitFor, effectiveWaitMs) {
|
|
55845
56125
|
const startedAt = Date.now();
|
|
55846
56126
|
const deadline = startedAt + effectiveWaitMs;
|
|
55847
|
-
let spillCursor = 0;
|
|
56127
|
+
let spillCursor = { output: 0, stderr: 0, combined: 0 };
|
|
55848
56128
|
let scanText = "";
|
|
55849
56129
|
let scanBaseOffset = 0;
|
|
55850
56130
|
const bridgeOptions = {
|
|
@@ -55852,14 +56132,11 @@ async function waitForBashStatus(ctx, runtime, taskId, outputMode, waitFor, effe
|
|
|
55852
56132
|
transportTimeoutMs: BASH_TRANSPORT_TIMEOUT_MS2
|
|
55853
56133
|
};
|
|
55854
56134
|
markTaskWaiting(runtime.sessionID, taskId);
|
|
56135
|
+
let sawTerminal = false;
|
|
55855
56136
|
try {
|
|
55856
56137
|
while (true) {
|
|
55857
56138
|
const data = await bashStatusSnapshot2(ctx, runtime, taskId, outputMode, bridgeOptions);
|
|
55858
|
-
|
|
55859
|
-
consumeBgCompletion(runtime.sessionID, taskId);
|
|
55860
|
-
await markBgCompletionDelivered({ ctx, directory: projectRootFor(runtime), sessionID: runtime.sessionID }, taskId);
|
|
55861
|
-
return withWaited(data, { reason: "exited", elapsed_ms: Date.now() - startedAt });
|
|
55862
|
-
}
|
|
56139
|
+
const terminal = isTerminalStatus2(data.status);
|
|
55863
56140
|
if (waitFor) {
|
|
55864
56141
|
const scan = await readNewTaskOutput(runtime, taskId, data, spillCursor);
|
|
55865
56142
|
if (scan) {
|
|
@@ -55869,7 +56146,11 @@ async function waitForBashStatus(ctx, runtime, taskId, outputMode, waitFor, effe
|
|
|
55869
56146
|
scanText += scan.text;
|
|
55870
56147
|
const match = findWaitMatch(scanText, waitFor);
|
|
55871
56148
|
if (match) {
|
|
55872
|
-
|
|
56149
|
+
if (terminal) {
|
|
56150
|
+
sawTerminal = true;
|
|
56151
|
+
consumeBgCompletion(runtime.sessionID, taskId);
|
|
56152
|
+
await markBgCompletionDelivered({ ctx, directory: projectRootFor(runtime), sessionID: runtime.sessionID }, taskId);
|
|
56153
|
+
}
|
|
55873
56154
|
return withWaited(data, {
|
|
55874
56155
|
reason: "matched",
|
|
55875
56156
|
elapsed_ms: Date.now() - startedAt,
|
|
@@ -55877,31 +56158,61 @@ async function waitForBashStatus(ctx, runtime, taskId, outputMode, waitFor, effe
|
|
|
55877
56158
|
match_offset: scanBaseOffset + Buffer.byteLength(scanText.slice(0, match.index), "utf8")
|
|
55878
56159
|
});
|
|
55879
56160
|
}
|
|
56161
|
+
const trimmed = trimWaitScanBuffer(scanText, scanBaseOffset, waitFor);
|
|
56162
|
+
scanText = trimmed.text;
|
|
56163
|
+
scanBaseOffset = trimmed.baseOffset;
|
|
55880
56164
|
}
|
|
55881
56165
|
}
|
|
56166
|
+
if (terminal) {
|
|
56167
|
+
sawTerminal = true;
|
|
56168
|
+
consumeBgCompletion(runtime.sessionID, taskId);
|
|
56169
|
+
await markBgCompletionDelivered({ ctx, directory: projectRootFor(runtime), sessionID: runtime.sessionID }, taskId);
|
|
56170
|
+
return withWaited(data, { reason: "exited", elapsed_ms: Date.now() - startedAt });
|
|
56171
|
+
}
|
|
55882
56172
|
if (Date.now() >= deadline) {
|
|
55883
|
-
unmarkTaskWaiting(runtime.sessionID, taskId);
|
|
55884
56173
|
return withWaited(data, { reason: "timeout", elapsed_ms: Date.now() - startedAt });
|
|
55885
56174
|
}
|
|
55886
56175
|
await sleep2(Math.min(BASH_WAIT_POLL_INTERVAL_MS, Math.max(0, deadline - Date.now())));
|
|
55887
56176
|
}
|
|
55888
|
-
}
|
|
55889
|
-
|
|
55890
|
-
|
|
56177
|
+
} finally {
|
|
56178
|
+
if (!sawTerminal)
|
|
56179
|
+
unmarkTaskWaiting(runtime.sessionID, taskId);
|
|
56180
|
+
await disposePtyTerminal(watchPtyCacheKey(runtime, taskId));
|
|
55891
56181
|
}
|
|
55892
56182
|
}
|
|
55893
56183
|
async function readNewTaskOutput(runtime, taskId, data, cursor) {
|
|
55894
56184
|
const outputPath = data.output_path;
|
|
55895
|
-
if (!outputPath)
|
|
55896
|
-
return;
|
|
55897
56185
|
if (data.mode === "pty") {
|
|
55898
|
-
|
|
56186
|
+
if (!outputPath)
|
|
56187
|
+
return;
|
|
56188
|
+
const state = await getOrCreatePtyTerminal(watchPtyCacheKey(runtime, taskId), outputPath);
|
|
55899
56189
|
const baseOffset = state.offset;
|
|
55900
|
-
const
|
|
55901
|
-
|
|
56190
|
+
const bytes = await readPtyBytes(state);
|
|
56191
|
+
if (bytes.length === 0)
|
|
56192
|
+
return;
|
|
56193
|
+
return {
|
|
56194
|
+
text: bytes.toString("utf8"),
|
|
56195
|
+
baseOffset,
|
|
56196
|
+
nextCursor: { output: state.offset, stderr: 0, combined: state.offset }
|
|
56197
|
+
};
|
|
55902
56198
|
}
|
|
55903
|
-
const
|
|
55904
|
-
|
|
56199
|
+
const stderrPath = data.stderr_path;
|
|
56200
|
+
if (!outputPath && !stderrPath)
|
|
56201
|
+
return;
|
|
56202
|
+
const stdoutBytes = outputPath ? await readFileBytesFrom(outputPath, cursor.output) : Buffer.alloc(0);
|
|
56203
|
+
const stderrBytes = stderrPath ? await readFileBytesFrom(stderrPath, cursor.stderr) : Buffer.alloc(0);
|
|
56204
|
+
const bytesRead = stdoutBytes.length + stderrBytes.length;
|
|
56205
|
+
if (bytesRead === 0)
|
|
56206
|
+
return;
|
|
56207
|
+
return {
|
|
56208
|
+
text: Buffer.concat([stdoutBytes, stderrBytes]).toString("utf8"),
|
|
56209
|
+
baseOffset: cursor.combined,
|
|
56210
|
+
nextCursor: {
|
|
56211
|
+
output: cursor.output + stdoutBytes.length,
|
|
56212
|
+
stderr: cursor.stderr + stderrBytes.length,
|
|
56213
|
+
combined: cursor.combined + bytesRead
|
|
56214
|
+
}
|
|
56215
|
+
};
|
|
55905
56216
|
}
|
|
55906
56217
|
async function readFileBytesFrom(outputPath, cursor) {
|
|
55907
56218
|
const handle = await fs5.open(outputPath, "r");
|
|
@@ -55942,6 +56253,34 @@ function findWaitMatch(text, pattern) {
|
|
|
55942
56253
|
const match = pattern.value.exec(text);
|
|
55943
56254
|
return match ? { text: match[0], index: match.index } : undefined;
|
|
55944
56255
|
}
|
|
56256
|
+
function trimWaitScanBuffer(text, baseOffset, pattern) {
|
|
56257
|
+
const keepFrom = pattern.kind === "substring" ? substringKeepStart(text, pattern.value) : regexKeepStart(text, REGEX_WAIT_SCAN_WINDOW_BYTES);
|
|
56258
|
+
if (keepFrom <= 0)
|
|
56259
|
+
return { text, baseOffset };
|
|
56260
|
+
return {
|
|
56261
|
+
text: text.slice(keepFrom),
|
|
56262
|
+
baseOffset: baseOffset + Buffer.byteLength(text.slice(0, keepFrom), "utf8")
|
|
56263
|
+
};
|
|
56264
|
+
}
|
|
56265
|
+
function substringKeepStart(text, pattern) {
|
|
56266
|
+
const keepChars = Math.max(0, pattern.length - 1);
|
|
56267
|
+
return text.length > keepChars ? text.length - keepChars : 0;
|
|
56268
|
+
}
|
|
56269
|
+
function regexKeepStart(text, maxBytes) {
|
|
56270
|
+
if (Buffer.byteLength(text, "utf8") <= maxBytes)
|
|
56271
|
+
return 0;
|
|
56272
|
+
let low = 0;
|
|
56273
|
+
let high = text.length;
|
|
56274
|
+
while (low < high) {
|
|
56275
|
+
const mid = Math.floor((low + high) / 2);
|
|
56276
|
+
if (Buffer.byteLength(text.slice(mid), "utf8") > maxBytes) {
|
|
56277
|
+
low = mid + 1;
|
|
56278
|
+
} else {
|
|
56279
|
+
high = mid;
|
|
56280
|
+
}
|
|
56281
|
+
}
|
|
56282
|
+
return low;
|
|
56283
|
+
}
|
|
55945
56284
|
function withWaited(data, waited) {
|
|
55946
56285
|
return { ...data, waited };
|
|
55947
56286
|
}
|
|
@@ -55951,24 +56290,27 @@ function isTerminalStatus2(status) {
|
|
|
55951
56290
|
function ptyCacheKey2(runtime, taskId) {
|
|
55952
56291
|
return `${projectRootFor(runtime)}::${runtime.sessionID ?? "__default__"}::${taskId}`;
|
|
55953
56292
|
}
|
|
56293
|
+
function watchPtyCacheKey(runtime, taskId) {
|
|
56294
|
+
return `${ptyCacheKey2(runtime, taskId)}::watch`;
|
|
56295
|
+
}
|
|
55954
56296
|
function sleep2(ms) {
|
|
55955
56297
|
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
55956
56298
|
}
|
|
55957
56299
|
|
|
55958
56300
|
// src/tools/bash_write.ts
|
|
55959
|
-
import { tool as
|
|
55960
|
-
var
|
|
56301
|
+
import { tool as tool6 } from "@opencode-ai/plugin";
|
|
56302
|
+
var z6 = tool6.schema;
|
|
55961
56303
|
function createBashWriteTool(ctx) {
|
|
55962
56304
|
return {
|
|
55963
56305
|
description: 'Write input bytes to a running PTY bash task. PTY-only; check bash_status reports mode: "pty" first. ' + 'Input is either a string (verbatim bytes) or an array mixing strings and { key: "esc" | "enter" | "up" | "ctrl-c" | ... } objects ' + 'for atomic text+key sequences such as [ "iHello", { key: "esc" }, ":wq", { key: "enter" } ]. ' + "Named keys cover enter/return/tab/space/backspace/esc/escape, arrows, home/end/page-up/page-down/delete/insert, f1..f12, and ctrl-a..ctrl-z. " + "Maximum 1 MiB per call (post-expansion).",
|
|
55964
56306
|
args: {
|
|
55965
|
-
taskId:
|
|
55966
|
-
input:
|
|
55967
|
-
|
|
55968
|
-
|
|
55969
|
-
|
|
55970
|
-
|
|
55971
|
-
key:
|
|
56307
|
+
taskId: z6.string().describe("Background PTY task ID returned by bash({ pty: true, background: true })."),
|
|
56308
|
+
input: z6.union([
|
|
56309
|
+
z6.string(),
|
|
56310
|
+
z6.array(z6.union([
|
|
56311
|
+
z6.string(),
|
|
56312
|
+
z6.object({
|
|
56313
|
+
key: z6.string().describe("Named control key, e.g. 'esc', 'enter', 'up', 'ctrl-c'. Case-insensitive.")
|
|
55972
56314
|
})
|
|
55973
56315
|
]))
|
|
55974
56316
|
]).describe("Either a string of verbatim bytes (e.g. 'print(1)\\n') OR an array mixing strings " + "and { key: '<name>' } objects for atomic text+key sequences. " + "Example: [ 'iHello', { key: 'esc' }, ':wq', { key: 'enter' } ].")
|
|
@@ -56195,7 +56537,7 @@ function inferBeforeStart(ops, from, beforeLen) {
|
|
|
56195
56537
|
}
|
|
56196
56538
|
return beforeLen;
|
|
56197
56539
|
}
|
|
56198
|
-
var
|
|
56540
|
+
var z7 = tool7.schema;
|
|
56199
56541
|
var READ_DESCRIPTION = `Read file contents or list directory entries.
|
|
56200
56542
|
|
|
56201
56543
|
Use either startLine/endLine OR offset/limit to read a section of a file.
|
|
@@ -56219,11 +56561,11 @@ function createReadTool(ctx) {
|
|
|
56219
56561
|
return {
|
|
56220
56562
|
description: READ_DESCRIPTION,
|
|
56221
56563
|
args: {
|
|
56222
|
-
filePath:
|
|
56223
|
-
startLine:
|
|
56224
|
-
endLine:
|
|
56225
|
-
limit:
|
|
56226
|
-
offset:
|
|
56564
|
+
filePath: z7.string().describe("Path to file or directory (absolute or relative to project root)"),
|
|
56565
|
+
startLine: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based line to start reading from"),
|
|
56566
|
+
endLine: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based line to stop reading at (inclusive)"),
|
|
56567
|
+
limit: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("Max lines to return (default: 2000)"),
|
|
56568
|
+
offset: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based line number to start reading from (use with limit). Ignored if startLine is provided")
|
|
56227
56569
|
},
|
|
56228
56570
|
execute: async (args, context) => {
|
|
56229
56571
|
const file2 = args.filePath;
|
|
@@ -56355,8 +56697,8 @@ function createWriteTool(ctx, editToolName = "edit") {
|
|
|
56355
56697
|
return {
|
|
56356
56698
|
description: getWriteDescription(editToolName),
|
|
56357
56699
|
args: {
|
|
56358
|
-
filePath:
|
|
56359
|
-
content:
|
|
56700
|
+
filePath: z7.string().describe("Path to the file to write (absolute or relative to project root)"),
|
|
56701
|
+
content: z7.string().describe("The full content to write to the file")
|
|
56360
56702
|
},
|
|
56361
56703
|
execute: async (args, context) => {
|
|
56362
56704
|
const file2 = args.filePath;
|
|
@@ -56501,16 +56843,16 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
56501
56843
|
return {
|
|
56502
56844
|
description: getEditDescription(writeToolName),
|
|
56503
56845
|
args: {
|
|
56504
|
-
filePath:
|
|
56505
|
-
oldString:
|
|
56506
|
-
newString:
|
|
56507
|
-
replaceAll:
|
|
56508
|
-
occurrence:
|
|
56509
|
-
symbol:
|
|
56510
|
-
content:
|
|
56511
|
-
appendContent:
|
|
56512
|
-
edits:
|
|
56513
|
-
operations:
|
|
56846
|
+
filePath: z7.string().optional().describe("Path to the file to edit (absolute or relative to project root). Required for all modes except 'operations' multi-file transactions"),
|
|
56847
|
+
oldString: z7.string().optional().describe("Text to find (exact match, with fuzzy fallback)"),
|
|
56848
|
+
newString: z7.string().optional().describe("Text to replace with (omit or set to empty string to delete the matched text)"),
|
|
56849
|
+
replaceAll: z7.boolean().optional().describe("Replace all occurrences"),
|
|
56850
|
+
occurrence: optionalInt(0, Number.MAX_SAFE_INTEGER).describe("0-indexed occurrence to replace when multiple matches exist"),
|
|
56851
|
+
symbol: z7.string().optional().describe("Named symbol to replace (function, class, type)"),
|
|
56852
|
+
content: z7.string().optional().describe("New content for symbol replace or file write"),
|
|
56853
|
+
appendContent: z7.string().optional().describe("Text to append to the end of filePath; creates the file if needed"),
|
|
56854
|
+
edits: z7.array(z7.record(z7.string(), z7.unknown())).optional().describe("Batch edits — array of { oldString: string, newString: string } or { startLine: number (1-based), endLine: number (1-based, inclusive), content: string }"),
|
|
56855
|
+
operations: z7.array(z7.record(z7.string(), z7.unknown())).optional().describe("Transaction — array of { file: string, command: 'edit_match' | 'write', match?: string, replacement?: string, content?: string } for multi-file edits with rollback. Note: uses 'file'/'match'/'replacement' (not filePath/oldString/newString)")
|
|
56514
56856
|
},
|
|
56515
56857
|
execute: async (args, context) => {
|
|
56516
56858
|
const argsRecord = args;
|
|
@@ -56726,7 +57068,7 @@ function createApplyPatchTool(ctx) {
|
|
|
56726
57068
|
return {
|
|
56727
57069
|
description: APPLY_PATCH_DESCRIPTION,
|
|
56728
57070
|
args: {
|
|
56729
|
-
patchText:
|
|
57071
|
+
patchText: z7.string().describe("The full patch text including Begin/End markers")
|
|
56730
57072
|
},
|
|
56731
57073
|
execute: async (args, context) => {
|
|
56732
57074
|
const patchText = args.patchText;
|
|
@@ -57002,8 +57344,8 @@ function createDeleteTool(ctx) {
|
|
|
57002
57344
|
return {
|
|
57003
57345
|
description: DELETE_DESCRIPTION,
|
|
57004
57346
|
args: {
|
|
57005
|
-
files:
|
|
57006
|
-
recursive:
|
|
57347
|
+
files: z7.array(z7.string()).min(1).describe("Paths to delete (one or more). May include directories when recursive=true."),
|
|
57348
|
+
recursive: z7.boolean().optional().describe("Required to delete a directory and its contents. Defaults to false; passing a directory without this returns an error.")
|
|
57007
57349
|
},
|
|
57008
57350
|
execute: async (args, context) => {
|
|
57009
57351
|
const inputs = args.files;
|
|
@@ -57056,8 +57398,8 @@ function createMoveTool(ctx) {
|
|
|
57056
57398
|
return {
|
|
57057
57399
|
description: MOVE_DESCRIPTION,
|
|
57058
57400
|
args: {
|
|
57059
|
-
filePath:
|
|
57060
|
-
destination:
|
|
57401
|
+
filePath: z7.string().describe("Source file path to move"),
|
|
57402
|
+
destination: z7.string().describe("Destination file path")
|
|
57061
57403
|
},
|
|
57062
57404
|
execute: async (args, context) => {
|
|
57063
57405
|
const filePath = path4.isAbsolute(args.filePath) ? args.filePath : path4.resolve(context.directory, args.filePath);
|
|
@@ -57167,8 +57509,8 @@ function aftPrefixedTools(ctx) {
|
|
|
57167
57509
|
}
|
|
57168
57510
|
|
|
57169
57511
|
// src/tools/imports.ts
|
|
57170
|
-
import { tool as
|
|
57171
|
-
var
|
|
57512
|
+
import { tool as tool8 } from "@opencode-ai/plugin";
|
|
57513
|
+
var z8 = tool8.schema;
|
|
57172
57514
|
function importTools(ctx) {
|
|
57173
57515
|
return {
|
|
57174
57516
|
aft_import: {
|
|
@@ -57184,14 +57526,14 @@ function importTools(ctx) {
|
|
|
57184
57526
|
` + `- remove: { file, removed, module, name?, formatted, syntax_valid?, format_skipped_reason?, validation_errors?, validate_skipped_reason?, backup_id?, lsp_diagnostics? }
|
|
57185
57527
|
` + "- organize: { file, groups: [{ name, count }], removed_duplicates, formatted?, syntax_valid?, format_skipped_reason?, validation_errors?, validate_skipped_reason?, backup_id?, lsp_diagnostics? }",
|
|
57186
57528
|
args: {
|
|
57187
|
-
op:
|
|
57188
|
-
filePath:
|
|
57189
|
-
module:
|
|
57190
|
-
names:
|
|
57191
|
-
defaultImport:
|
|
57192
|
-
typeOnly:
|
|
57193
|
-
removeName:
|
|
57194
|
-
validate:
|
|
57529
|
+
op: z8.enum(["add", "remove", "organize"]).describe("Import operation"),
|
|
57530
|
+
filePath: z8.string().describe("Path to the file (absolute or relative to project root)"),
|
|
57531
|
+
module: z8.string().optional().describe("Module path (required for add, remove — e.g. 'react', './utils', 'std::fmt')"),
|
|
57532
|
+
names: z8.array(z8.string()).optional().describe("Named imports to add (e.g. ['useState', 'useEffect'])"),
|
|
57533
|
+
defaultImport: z8.string().optional().describe("Default import name (e.g. 'React')"),
|
|
57534
|
+
typeOnly: z8.boolean().optional().describe("Type-only import (TS only, default: false)"),
|
|
57535
|
+
removeName: z8.string().optional().describe("Named import to remove for 'remove' op; omit to remove entire import"),
|
|
57536
|
+
validate: z8.enum(["syntax", "full"]).optional().describe("Validation level: 'syntax' (default) or 'full'. Syntax = tree-sitter parse check only. Full = also runs LSP type-checking (slower, catches more errors)")
|
|
57195
57537
|
},
|
|
57196
57538
|
execute: async (args, context) => {
|
|
57197
57539
|
const op = args.op;
|
|
@@ -57236,8 +57578,8 @@ function importTools(ctx) {
|
|
|
57236
57578
|
}
|
|
57237
57579
|
|
|
57238
57580
|
// src/tools/lsp.ts
|
|
57239
|
-
import { tool as
|
|
57240
|
-
var
|
|
57581
|
+
import { tool as tool9 } from "@opencode-ai/plugin";
|
|
57582
|
+
var z9 = tool9.schema;
|
|
57241
57583
|
function lspTools(ctx) {
|
|
57242
57584
|
const diagnosticsTool = {
|
|
57243
57585
|
description: "On-demand LSP file/scope check. Spawns the relevant language server (if " + "registered for the file's extension), opens the document, prefers LSP 3.17 " + "pull diagnostics when supported, and falls back to push + waitMs otherwise. " + "NOT a project-wide type checker — for full coverage run `tsc --noEmit`, " + "`cargo check`, `pyright`, etc.\n" + `
|
|
@@ -57259,10 +57601,10 @@ function lspTools(ctx) {
|
|
|
57259
57601
|
` + "- `total: 0, complete: true, lsp_servers_used: [{status: 'pull_ok'}]` → file is genuinely clean.\n" + "- `total: 0, lsp_servers_used: []` → **nothing was checked** (no server registered for this extension). Tell the user, don't claim 'no errors'.\n" + "- `lsp_servers_used: [{status: 'binary_not_installed: <name>'}]` → server matched the extension but its binary isn't on PATH. Tell the user to install it.\n" + "- `lsp_servers_used: [{status: 'no_root_marker (...)'}]` → server is registered but couldn't find a workspace root marker walking up from this file. The user's project layout doesn't match what the server expects.\n" + "- `complete: false` (directory mode) → some files in the directory weren't checked; see `unchecked_files`.\n" + `
|
|
57260
57602
|
` + "**When this tool gives an unhelpful answer**, run `npx @cortexkit/aft doctor lsp <filePath>` from a terminal to get a full per-server breakdown (registered servers, binary resolution, root-marker resolution, spawn outcome).",
|
|
57261
57603
|
args: {
|
|
57262
|
-
filePath:
|
|
57263
|
-
directory:
|
|
57264
|
-
severity:
|
|
57265
|
-
waitMs:
|
|
57604
|
+
filePath: z9.string().optional().describe("Path to a file to check. Mutually exclusive with 'directory'. Omit both to dump all cached diagnostics."),
|
|
57605
|
+
directory: z9.string().optional().describe("Path to a directory. Returns cached diagnostics + workspace pull from active servers; lists files we have no info for in 'unchecked_files'. Capped at 200 walked files."),
|
|
57606
|
+
severity: z9.enum(["error", "warning", "information", "hint", "all"]).optional().describe("Filter by severity (default: 'all')."),
|
|
57607
|
+
waitMs: optionalInt(1, 1e4).describe("Wait up to N ms (max 10000, default 0) for push diagnostics to arrive. Only matters for servers that don't support LSP 3.17 pull (bash-language-server, yaml-language-server). Use after an edit to let the server re-analyze.")
|
|
57266
57608
|
},
|
|
57267
57609
|
execute: async (args, context) => {
|
|
57268
57610
|
const filePath = args.filePath || undefined;
|
|
@@ -57293,8 +57635,8 @@ function lspTools(ctx) {
|
|
|
57293
57635
|
}
|
|
57294
57636
|
|
|
57295
57637
|
// src/tools/navigation.ts
|
|
57296
|
-
import { tool as
|
|
57297
|
-
var
|
|
57638
|
+
import { tool as tool10 } from "@opencode-ai/plugin";
|
|
57639
|
+
var z10 = tool10.schema;
|
|
57298
57640
|
function navigationTools(ctx) {
|
|
57299
57641
|
return {
|
|
57300
57642
|
aft_navigate: {
|
|
@@ -57311,11 +57653,11 @@ function navigationTools(ctx) {
|
|
|
57311
57653
|
|
|
57312
57654
|
`,
|
|
57313
57655
|
args: {
|
|
57314
|
-
op:
|
|
57315
|
-
filePath:
|
|
57316
|
-
symbol:
|
|
57317
|
-
depth:
|
|
57318
|
-
expression:
|
|
57656
|
+
op: z10.enum(["call_tree", "callers", "trace_to", "impact", "trace_data"]).describe("Navigation operation"),
|
|
57657
|
+
filePath: z10.string().describe("Path to the source file containing the symbol (absolute or relative to project root)"),
|
|
57658
|
+
symbol: z10.string().describe("Name of the symbol to analyze"),
|
|
57659
|
+
depth: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("Max traversal depth (default: call_tree=5, callers=1, trace_to=10, impact=5, trace_data=5)"),
|
|
57660
|
+
expression: z10.string().optional().describe("Expression to track through data flow (required for trace_data op)")
|
|
57319
57661
|
},
|
|
57320
57662
|
execute: async (args, context) => {
|
|
57321
57663
|
const params = {
|
|
@@ -57341,8 +57683,8 @@ function navigationTools(ctx) {
|
|
|
57341
57683
|
|
|
57342
57684
|
// src/tools/reading.ts
|
|
57343
57685
|
import { resolve as resolve8 } from "node:path";
|
|
57344
|
-
import { tool as
|
|
57345
|
-
var
|
|
57686
|
+
import { tool as tool11 } from "@opencode-ai/plugin";
|
|
57687
|
+
var z11 = tool11.schema;
|
|
57346
57688
|
function readingTools(ctx) {
|
|
57347
57689
|
return {
|
|
57348
57690
|
aft_outline: {
|
|
@@ -57353,7 +57695,7 @@ function readingTools(ctx) {
|
|
|
57353
57695
|
` + ` • URL (http:// or https://) → fetch and outline a remote HTML/Markdown document
|
|
57354
57696
|
` + " • array of paths → outline multiple files in one call",
|
|
57355
57697
|
args: {
|
|
57356
|
-
target:
|
|
57698
|
+
target: z11.union([z11.string(), z11.array(z11.string())]).describe("What to outline: a file path, directory path, URL, or array of file paths. The mode is auto-detected: URLs by `http://`/`https://` prefix, directories by stat, arrays as multi-file.")
|
|
57357
57699
|
},
|
|
57358
57700
|
execute: async (args, context) => {
|
|
57359
57701
|
const target = args.target;
|
|
@@ -57408,11 +57750,11 @@ function readingTools(ctx) {
|
|
|
57408
57750
|
|
|
57409
57751
|
Provide exactly ONE of 'filePath' or 'url'. Pass either 'symbol' for a single lookup or 'symbols' for multiple in one call.`,
|
|
57410
57752
|
args: {
|
|
57411
|
-
filePath:
|
|
57412
|
-
url:
|
|
57413
|
-
symbol:
|
|
57414
|
-
symbols:
|
|
57415
|
-
contextLines:
|
|
57753
|
+
filePath: z11.string().optional().describe("Path to file (absolute or relative to project root)"),
|
|
57754
|
+
url: z11.string().optional().describe("HTTP/HTTPS URL of an HTML or Markdown document to fetch and zoom into"),
|
|
57755
|
+
symbol: z11.string().optional().describe("Symbol name for code, or heading text for Markdown/HTML"),
|
|
57756
|
+
symbols: z11.array(z11.string()).optional().describe("Array of symbol names or heading texts for a single batched call"),
|
|
57757
|
+
contextLines: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("Lines of context before/after the symbol (default: 3)")
|
|
57416
57758
|
},
|
|
57417
57759
|
execute: async (args, context) => {
|
|
57418
57760
|
const hasFilePath = typeof args.filePath === "string" && args.filePath.length > 0;
|
|
@@ -57494,7 +57836,7 @@ ${lines}`;
|
|
|
57494
57836
|
}
|
|
57495
57837
|
|
|
57496
57838
|
// src/tools/refactoring.ts
|
|
57497
|
-
import { tool as
|
|
57839
|
+
import { tool as tool12 } from "@opencode-ai/plugin";
|
|
57498
57840
|
|
|
57499
57841
|
// src/lsp.ts
|
|
57500
57842
|
var LSP_SYMBOL_KIND_MAP = {
|
|
@@ -57553,7 +57895,7 @@ async function queryLspHints(client, symbolName, directory, sessionId) {
|
|
|
57553
57895
|
}
|
|
57554
57896
|
|
|
57555
57897
|
// src/tools/refactoring.ts
|
|
57556
|
-
var
|
|
57898
|
+
var z12 = tool12.schema;
|
|
57557
57899
|
function refactoringTools(ctx) {
|
|
57558
57900
|
return {
|
|
57559
57901
|
aft_refactor: {
|
|
@@ -57571,15 +57913,15 @@ function refactoringTools(ctx) {
|
|
|
57571
57913
|
|
|
57572
57914
|
` + "Returns: move { ok, files_modified, consumers_updated, checkpoint_name, results }. extract returns { file, name, parameters, return_type, syntax_valid, formatted, ... }. inline returns { file, symbol, call_context, substitutions, conflicts, syntax_valid, formatted, ... }.",
|
|
57573
57915
|
args: {
|
|
57574
|
-
op:
|
|
57575
|
-
filePath:
|
|
57576
|
-
symbol:
|
|
57577
|
-
destination:
|
|
57578
|
-
scope:
|
|
57579
|
-
name:
|
|
57580
|
-
startLine:
|
|
57581
|
-
endLine:
|
|
57582
|
-
callSiteLine:
|
|
57916
|
+
op: z12.enum(["move", "extract", "inline"]).describe("Refactoring operation"),
|
|
57917
|
+
filePath: z12.string().describe("Path to the source file (absolute or relative to project root)"),
|
|
57918
|
+
symbol: z12.string().optional().describe("Symbol name — required for 'move' and 'inline' ops"),
|
|
57919
|
+
destination: z12.string().optional().describe("Target file path — required for 'move' op"),
|
|
57920
|
+
scope: z12.string().optional().describe("Disambiguation scope for 'move' op — when multiple top-level symbols share the same name, specify the containing scope to disambiguate (e.g. 'MyClass'). Does NOT enable access to nested symbols or class methods."),
|
|
57921
|
+
name: z12.string().optional().describe("New function name — required for 'extract' op"),
|
|
57922
|
+
startLine: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based start line — required for 'extract' op"),
|
|
57923
|
+
endLine: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based end line (inclusive) — required for 'extract' op"),
|
|
57924
|
+
callSiteLine: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based call site line — required for 'inline' op")
|
|
57583
57925
|
},
|
|
57584
57926
|
execute: async (args, context) => {
|
|
57585
57927
|
const op = args.op;
|
|
@@ -57660,8 +58002,8 @@ function refactoringTools(ctx) {
|
|
|
57660
58002
|
|
|
57661
58003
|
// src/tools/safety.ts
|
|
57662
58004
|
import * as path5 from "node:path";
|
|
57663
|
-
import { tool as
|
|
57664
|
-
var
|
|
58005
|
+
import { tool as tool13 } from "@opencode-ai/plugin";
|
|
58006
|
+
var z13 = tool13.schema;
|
|
57665
58007
|
function safetyTools(ctx) {
|
|
57666
58008
|
return {
|
|
57667
58009
|
aft_safety: {
|
|
@@ -57682,10 +58024,10 @@ function safetyTools(ctx) {
|
|
|
57682
58024
|
|
|
57683
58025
|
` + "Returns: undo { path, backup_id }. history { file, entries }. checkpoint { ok, name }. restore { ok, name }. list { checkpoints }.",
|
|
57684
58026
|
args: {
|
|
57685
|
-
op:
|
|
57686
|
-
filePath:
|
|
57687
|
-
name:
|
|
57688
|
-
files:
|
|
58027
|
+
op: z13.enum(["undo", "history", "checkpoint", "restore", "list"]).describe("Safety operation"),
|
|
58028
|
+
filePath: z13.string().optional().describe("File path (required for history, optional for undo). Absolute or relative to project root"),
|
|
58029
|
+
name: z13.string().optional().describe("Checkpoint name (required for checkpoint, restore)"),
|
|
58030
|
+
files: z13.array(z13.string()).optional().describe("Specific files to include in checkpoint (optional, defaults to all tracked files)")
|
|
57689
58031
|
},
|
|
57690
58032
|
execute: async (args, context) => {
|
|
57691
58033
|
const op = args.op;
|
|
@@ -57793,6 +58135,39 @@ function normalizeGlob(pattern) {
|
|
|
57793
58135
|
}
|
|
57794
58136
|
return pattern;
|
|
57795
58137
|
}
|
|
58138
|
+
function absoluteSearchPath(context, target) {
|
|
58139
|
+
return path6.isAbsolute(target) ? target : path6.resolve(context.directory, target);
|
|
58140
|
+
}
|
|
58141
|
+
function searchPathExists(context, target) {
|
|
58142
|
+
return fs7.existsSync(absoluteSearchPath(context, target));
|
|
58143
|
+
}
|
|
58144
|
+
function splitSearchPathArg(context, raw) {
|
|
58145
|
+
if (searchPathExists(context, raw) || !/\s/.test(raw)) {
|
|
58146
|
+
return [raw];
|
|
58147
|
+
}
|
|
58148
|
+
const fragments = raw.trim().split(/\s+/).filter(Boolean);
|
|
58149
|
+
if (fragments.length < 2 || !fragments.every((fragment) => searchPathExists(context, fragment))) {
|
|
58150
|
+
return [raw];
|
|
58151
|
+
}
|
|
58152
|
+
return fragments;
|
|
58153
|
+
}
|
|
58154
|
+
function searchPathKind(context, target, defaultKind) {
|
|
58155
|
+
try {
|
|
58156
|
+
const stat = fs7.lstatSync(absoluteSearchPath(context, target));
|
|
58157
|
+
if (defaultKind === "file") {
|
|
58158
|
+
return stat.isDirectory() ? "directory" : "file";
|
|
58159
|
+
}
|
|
58160
|
+
return stat.isFile() ? "file" : "directory";
|
|
58161
|
+
} catch {
|
|
58162
|
+
return defaultKind;
|
|
58163
|
+
}
|
|
58164
|
+
}
|
|
58165
|
+
function searchPathTargets(context, raw, defaultKind) {
|
|
58166
|
+
return splitSearchPathArg(context, raw).map((target) => ({
|
|
58167
|
+
target,
|
|
58168
|
+
kind: searchPathKind(context, target, defaultKind)
|
|
58169
|
+
}));
|
|
58170
|
+
}
|
|
57796
58171
|
function splitIncludeArg(raw) {
|
|
57797
58172
|
const out = [];
|
|
57798
58173
|
let depth = 0;
|
|
@@ -57842,17 +58217,13 @@ function searchTools(ctx) {
|
|
|
57842
58217
|
if (grepDenied)
|
|
57843
58218
|
return permissionDeniedResponse(grepDenied);
|
|
57844
58219
|
if (pathArg) {
|
|
57845
|
-
|
|
57846
|
-
|
|
57847
|
-
|
|
57848
|
-
|
|
57849
|
-
|
|
57850
|
-
|
|
57851
|
-
|
|
57852
|
-
kind
|
|
57853
|
-
});
|
|
57854
|
-
if (externalDenied)
|
|
57855
|
-
return permissionDeniedResponse(externalDenied);
|
|
58220
|
+
for (const target of searchPathTargets(context, pathArg, "file")) {
|
|
58221
|
+
const externalDenied = await assertExternalDirectoryPermission(context, target.target, {
|
|
58222
|
+
kind: target.kind
|
|
58223
|
+
});
|
|
58224
|
+
if (externalDenied)
|
|
58225
|
+
return permissionDeniedResponse(externalDenied);
|
|
58226
|
+
}
|
|
57856
58227
|
}
|
|
57857
58228
|
const response = await callBridge(ctx, context, "grep", {
|
|
57858
58229
|
pattern,
|
|
@@ -57884,23 +58255,22 @@ function searchTools(ctx) {
|
|
|
57884
58255
|
globPath = globPattern.slice(0, lastSlash);
|
|
57885
58256
|
globPattern = `**/${globPattern.slice(lastSlash + 1)}`;
|
|
57886
58257
|
}
|
|
58258
|
+
} else if (metaIdx === -1) {
|
|
58259
|
+
globPath = path6.dirname(globPattern);
|
|
58260
|
+
globPattern = path6.basename(globPattern);
|
|
57887
58261
|
}
|
|
57888
58262
|
}
|
|
57889
58263
|
const globDenied = await askGlobPermission(context, globPattern, { path: globPath });
|
|
57890
58264
|
if (globDenied)
|
|
57891
58265
|
return permissionDeniedResponse(globDenied);
|
|
57892
58266
|
if (globPath) {
|
|
57893
|
-
|
|
57894
|
-
|
|
57895
|
-
|
|
57896
|
-
|
|
57897
|
-
|
|
57898
|
-
|
|
57899
|
-
|
|
57900
|
-
kind
|
|
57901
|
-
});
|
|
57902
|
-
if (externalDenied)
|
|
57903
|
-
return permissionDeniedResponse(externalDenied);
|
|
58267
|
+
for (const target of searchPathTargets(context, globPath, "directory")) {
|
|
58268
|
+
const externalDenied = await assertExternalDirectoryPermission(context, target.target, {
|
|
58269
|
+
kind: target.kind
|
|
58270
|
+
});
|
|
58271
|
+
if (externalDenied)
|
|
58272
|
+
return permissionDeniedResponse(externalDenied);
|
|
58273
|
+
}
|
|
57904
58274
|
}
|
|
57905
58275
|
const response = await callBridge(ctx, context, "glob", {
|
|
57906
58276
|
pattern: globPattern,
|
|
@@ -57927,6 +58297,8 @@ function searchTools(ctx) {
|
|
|
57927
58297
|
}
|
|
57928
58298
|
|
|
57929
58299
|
// src/tools/semantic.ts
|
|
58300
|
+
import { tool as tool14 } from "@opencode-ai/plugin";
|
|
58301
|
+
var z14 = tool14.schema;
|
|
57930
58302
|
function arg2(schema) {
|
|
57931
58303
|
return schema;
|
|
57932
58304
|
}
|
|
@@ -57951,8 +58323,8 @@ function semanticTools(ctx) {
|
|
|
57951
58323
|
].join(`
|
|
57952
58324
|
`),
|
|
57953
58325
|
args: {
|
|
57954
|
-
query: arg2(
|
|
57955
|
-
topK: arg2(
|
|
58326
|
+
query: arg2(z14.string().describe("Concept or capability to find, phrased as a programmer would describe the code. Examples: 'fuzzy match with whitespace tolerance', 'undo backup before edit', 'retry failed network request'.")),
|
|
58327
|
+
topK: arg2(optionalInt(1, 100).describe("Number of results (default: 10, max: 100)"))
|
|
57956
58328
|
},
|
|
57957
58329
|
execute: async (args, context) => {
|
|
57958
58330
|
const response = await callBridge(ctx, context, "semantic_search", {
|
|
@@ -57977,8 +58349,8 @@ function semanticTools(ctx) {
|
|
|
57977
58349
|
}
|
|
57978
58350
|
|
|
57979
58351
|
// src/tools/structure.ts
|
|
57980
|
-
import { tool as
|
|
57981
|
-
var
|
|
58352
|
+
import { tool as tool15 } from "@opencode-ai/plugin";
|
|
58353
|
+
var z15 = tool15.schema;
|
|
57982
58354
|
function structureTools(ctx) {
|
|
57983
58355
|
return {
|
|
57984
58356
|
aft_transform: {
|
|
@@ -58002,19 +58374,19 @@ function structureTools(ctx) {
|
|
|
58002
58374
|
` + `- add_decorator: { file, target, decorator, formatted, syntax_valid?, format_skipped_reason?, validation_errors?, validate_skipped_reason?, backup_id?, lsp_diagnostics? }
|
|
58003
58375
|
` + "- add_struct_tags: { file, target, field, tag_string, formatted, syntax_valid?, format_skipped_reason?, validation_errors?, validate_skipped_reason?, backup_id?, lsp_diagnostics? }",
|
|
58004
58376
|
args: {
|
|
58005
|
-
op:
|
|
58006
|
-
filePath:
|
|
58007
|
-
container:
|
|
58008
|
-
code:
|
|
58009
|
-
position:
|
|
58010
|
-
target:
|
|
58011
|
-
derives:
|
|
58012
|
-
catchBody:
|
|
58013
|
-
decorator:
|
|
58014
|
-
field:
|
|
58015
|
-
tag:
|
|
58016
|
-
value:
|
|
58017
|
-
validate:
|
|
58377
|
+
op: z15.enum(["add_member", "add_derive", "wrap_try_catch", "add_decorator", "add_struct_tags"]).describe("Transformation operation"),
|
|
58378
|
+
filePath: z15.string().describe("Path to the source file (absolute or relative to project root)"),
|
|
58379
|
+
container: z15.string().optional().describe("Container name for add_member — the class, struct, or impl block to insert into. Appears as 'scope' in the response."),
|
|
58380
|
+
code: z15.string().optional().describe("Member code to insert (add_member)"),
|
|
58381
|
+
position: z15.string().optional().describe("For add_member: 'first', 'last' (default), 'before:name', 'after:name'. For add_decorator: 'first' (default) or 'last' only."),
|
|
58382
|
+
target: z15.string().optional().describe("Target symbol name (add_derive: struct/enum, wrap_try_catch: function, add_decorator: function/class, add_struct_tags: struct)"),
|
|
58383
|
+
derives: z15.array(z15.string()).optional().describe("Derive macro names (add_derive — e.g. ['Clone', 'Debug'])"),
|
|
58384
|
+
catchBody: z15.string().optional().describe("Catch block body (wrap_try_catch — default: 'throw error;')"),
|
|
58385
|
+
decorator: z15.string().optional().describe("Decorator text without @ (add_decorator — e.g. 'staticmethod')"),
|
|
58386
|
+
field: z15.string().optional().describe("Struct field name (add_struct_tags)"),
|
|
58387
|
+
tag: z15.string().optional().describe("Tag key (add_struct_tags — e.g. 'json')"),
|
|
58388
|
+
value: z15.string().optional().describe("Tag value (add_struct_tags — e.g. 'user_name,omitempty')"),
|
|
58389
|
+
validate: z15.enum(["syntax", "full"]).optional().describe("Validation level: 'syntax' (default) or 'full'. Syntax = tree-sitter parse check only. Full = also runs LSP type-checking (slower, catches more errors)")
|
|
58018
58390
|
},
|
|
58019
58391
|
execute: async (args, context) => {
|
|
58020
58392
|
const op = args.op;
|
|
@@ -58482,10 +58854,10 @@ ${lines}
|
|
|
58482
58854
|
return { show: false };
|
|
58483
58855
|
}
|
|
58484
58856
|
if (storageDir) {
|
|
58485
|
-
const versionFile = join23(storageDir, "last_announced_version");
|
|
58486
58857
|
try {
|
|
58487
|
-
|
|
58488
|
-
|
|
58858
|
+
const versionFile = repairRootScopedStorageFile(storageDir, "opencode", "last_announced_version");
|
|
58859
|
+
if (existsSync17(versionFile)) {
|
|
58860
|
+
const lastVersion = readFileSync14(versionFile, "utf-8").trim();
|
|
58489
58861
|
if (lastVersion === ANNOUNCEMENT_VERSION)
|
|
58490
58862
|
return { show: false };
|
|
58491
58863
|
}
|
|
@@ -58501,8 +58873,9 @@ ${lines}
|
|
|
58501
58873
|
rpcServer.handle("mark-announced", async () => {
|
|
58502
58874
|
if (storageDir && ANNOUNCEMENT_VERSION) {
|
|
58503
58875
|
try {
|
|
58504
|
-
|
|
58505
|
-
|
|
58876
|
+
const versionFile = repairRootScopedStorageFile(storageDir, "opencode", "last_announced_version");
|
|
58877
|
+
mkdirSync14(dirname14(versionFile), { recursive: true });
|
|
58878
|
+
writeFileSync12(versionFile, ANNOUNCEMENT_VERSION);
|
|
58506
58879
|
} catch {}
|
|
58507
58880
|
}
|
|
58508
58881
|
return { success: true };
|