@cortexkit/aft-opencode 0.13.0 → 0.14.0
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/bridge.d.ts +11 -0
- package/dist/bridge.d.ts.map +1 -1
- package/dist/cli.js +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +223 -113
- package/dist/pool.d.ts +31 -9
- package/dist/pool.d.ts.map +1 -1
- package/dist/shared/status.d.ts +8 -0
- package/dist/shared/status.d.ts.map +1 -1
- package/dist/shutdown-hooks.d.ts +24 -0
- package/dist/shutdown-hooks.d.ts.map +1 -0
- package/dist/tools/_shared.d.ts +63 -0
- package/dist/tools/_shared.d.ts.map +1 -0
- package/dist/tools/ast.d.ts.map +1 -1
- package/dist/tools/conflicts.d.ts.map +1 -1
- package/dist/tools/hoisted.d.ts.map +1 -1
- package/dist/tools/imports.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/refactoring.d.ts.map +1 -1
- package/dist/tools/safety.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/tools/structure.d.ts.map +1 -1
- package/dist/tui.js +9 -1
- package/package.json +6 -6
- package/src/shared/status.ts +33 -0
package/dist/index.js
CHANGED
|
@@ -22233,12 +22233,14 @@ function clampSemanticTimeout(configOverrides, bridgeTimeoutMs) {
|
|
|
22233
22233
|
|
|
22234
22234
|
class BinaryBridge {
|
|
22235
22235
|
static RESTART_RESET_MS = 5 * 60 * 1000;
|
|
22236
|
+
static STDERR_TAIL_MAX = 20;
|
|
22236
22237
|
binaryPath;
|
|
22237
22238
|
cwd;
|
|
22238
22239
|
process = null;
|
|
22239
22240
|
pending = new Map;
|
|
22240
22241
|
nextId = 1;
|
|
22241
22242
|
stdoutBuffer = "";
|
|
22243
|
+
stderrTail = [];
|
|
22242
22244
|
_restartCount = 0;
|
|
22243
22245
|
_shuttingDown = false;
|
|
22244
22246
|
timeoutMs;
|
|
@@ -22405,22 +22407,48 @@ class BinaryBridge {
|
|
|
22405
22407
|
const lines = chunk.toString("utf-8").trimEnd().split(`
|
|
22406
22408
|
`);
|
|
22407
22409
|
for (const line of lines) {
|
|
22410
|
+
if (!line)
|
|
22411
|
+
continue;
|
|
22408
22412
|
const stripped = line.replace(/^\[aft\]\s*/, "");
|
|
22409
22413
|
log(`[aft] ${stripped}`);
|
|
22414
|
+
this.pushStderrLine(stripped);
|
|
22410
22415
|
}
|
|
22411
22416
|
});
|
|
22412
22417
|
child.on("error", (err) => {
|
|
22413
|
-
error48(`Process error: ${err.message}`);
|
|
22418
|
+
error48(`Process error: ${err.message}${this.formatStderrTail()}`);
|
|
22414
22419
|
this.handleCrash();
|
|
22415
22420
|
});
|
|
22416
22421
|
child.on("exit", (code, signal) => {
|
|
22417
22422
|
if (this._shuttingDown)
|
|
22418
22423
|
return;
|
|
22419
22424
|
log(`Process exited: code=${code}, signal=${signal}`);
|
|
22425
|
+
if (signal === "SIGTERM" || signal === "SIGKILL" || signal === "SIGHUP" || signal === "SIGINT") {
|
|
22426
|
+
this.process = null;
|
|
22427
|
+
this.configured = false;
|
|
22428
|
+
this.clearRestartResetTimer();
|
|
22429
|
+
this.rejectAllPending(new Error(`[aft-plugin] Binary killed by ${signal}`));
|
|
22430
|
+
return;
|
|
22431
|
+
}
|
|
22420
22432
|
this.handleCrash();
|
|
22421
22433
|
});
|
|
22422
22434
|
this.process = child;
|
|
22423
22435
|
this.stdoutBuffer = "";
|
|
22436
|
+
this.stderrTail = [];
|
|
22437
|
+
}
|
|
22438
|
+
pushStderrLine(line) {
|
|
22439
|
+
this.stderrTail.push(line);
|
|
22440
|
+
if (this.stderrTail.length > BinaryBridge.STDERR_TAIL_MAX) {
|
|
22441
|
+
this.stderrTail.shift();
|
|
22442
|
+
}
|
|
22443
|
+
}
|
|
22444
|
+
formatStderrTail() {
|
|
22445
|
+
if (this.stderrTail.length === 0)
|
|
22446
|
+
return "";
|
|
22447
|
+
const tail = this.stderrTail.join(`
|
|
22448
|
+
`);
|
|
22449
|
+
return `
|
|
22450
|
+
--- last ${this.stderrTail.length} stderr lines ---
|
|
22451
|
+
${tail}`;
|
|
22424
22452
|
}
|
|
22425
22453
|
onStdoutData(data) {
|
|
22426
22454
|
this.stdoutBuffer += data;
|
|
@@ -22455,13 +22483,16 @@ class BinaryBridge {
|
|
|
22455
22483
|
}
|
|
22456
22484
|
this.clearRestartResetTimer();
|
|
22457
22485
|
this.configured = false;
|
|
22458
|
-
this.
|
|
22486
|
+
const tail = this.formatStderrTail();
|
|
22487
|
+
this.stderrTail = [];
|
|
22488
|
+
this.rejectAllPending(new Error(`[aft-plugin] Bridge restarted after timeout${tail}`));
|
|
22459
22489
|
}
|
|
22460
22490
|
handleCrash() {
|
|
22461
22491
|
this.process = null;
|
|
22462
22492
|
this.clearRestartResetTimer();
|
|
22463
22493
|
this.configured = false;
|
|
22464
|
-
|
|
22494
|
+
const tail = this.formatStderrTail();
|
|
22495
|
+
this.rejectAllPending(new Error(`[aft-plugin] Binary crashed (restarts: ${this._restartCount})${tail}`));
|
|
22465
22496
|
if (this._restartCount < this.maxRestarts) {
|
|
22466
22497
|
const delay = 100 * 2 ** this._restartCount;
|
|
22467
22498
|
this._restartCount++;
|
|
@@ -22476,7 +22507,7 @@ class BinaryBridge {
|
|
|
22476
22507
|
}
|
|
22477
22508
|
}, delay);
|
|
22478
22509
|
} else {
|
|
22479
|
-
error48(`Max restarts (${this.maxRestarts}) reached, giving up. Logs: ${getLogFilePath()}`);
|
|
22510
|
+
error48(`Max restarts (${this.maxRestarts}) reached, giving up. Logs: ${getLogFilePath()}${tail}`);
|
|
22480
22511
|
}
|
|
22481
22512
|
}
|
|
22482
22513
|
rejectAllPending(error49) {
|
|
@@ -22530,8 +22561,14 @@ class BridgePool {
|
|
|
22530
22561
|
this.cleanupTimer.unref();
|
|
22531
22562
|
}
|
|
22532
22563
|
}
|
|
22533
|
-
getAnyActiveBridge(
|
|
22534
|
-
|
|
22564
|
+
getAnyActiveBridge(projectRoot) {
|
|
22565
|
+
const key = normalizeKey(projectRoot);
|
|
22566
|
+
const preferred = this.bridges.get(key);
|
|
22567
|
+
if (preferred?.bridge.isAlive()) {
|
|
22568
|
+
preferred.lastUsed = Date.now();
|
|
22569
|
+
return preferred.bridge;
|
|
22570
|
+
}
|
|
22571
|
+
for (const entry of this.bridges.values()) {
|
|
22535
22572
|
if (entry.bridge.isAlive()) {
|
|
22536
22573
|
entry.lastUsed = Date.now();
|
|
22537
22574
|
return entry.bridge;
|
|
@@ -22539,8 +22576,8 @@ class BridgePool {
|
|
|
22539
22576
|
}
|
|
22540
22577
|
return null;
|
|
22541
22578
|
}
|
|
22542
|
-
getBridge(
|
|
22543
|
-
const key =
|
|
22579
|
+
getBridge(projectRoot) {
|
|
22580
|
+
const key = normalizeKey(projectRoot);
|
|
22544
22581
|
const existing = this.bridges.get(key);
|
|
22545
22582
|
if (existing) {
|
|
22546
22583
|
existing.lastUsed = Date.now();
|
|
@@ -22549,8 +22586,7 @@ class BridgePool {
|
|
|
22549
22586
|
if (this.bridges.size >= this.maxPoolSize) {
|
|
22550
22587
|
this.evictLRU();
|
|
22551
22588
|
}
|
|
22552
|
-
const
|
|
22553
|
-
const bridge = new BinaryBridge(this.binaryPath, normalized, this.bridgeOptions, this.configOverrides);
|
|
22589
|
+
const bridge = new BinaryBridge(this.binaryPath, key, this.bridgeOptions, this.configOverrides);
|
|
22554
22590
|
this.bridges.set(key, { bridge, lastUsed: Date.now() });
|
|
22555
22591
|
return bridge;
|
|
22556
22592
|
}
|
|
@@ -22601,6 +22637,9 @@ class BridgePool {
|
|
|
22601
22637
|
return this.bridges.size;
|
|
22602
22638
|
}
|
|
22603
22639
|
}
|
|
22640
|
+
function normalizeKey(projectRoot) {
|
|
22641
|
+
return projectRoot.replace(/[/\\]+$/, "");
|
|
22642
|
+
}
|
|
22604
22643
|
|
|
22605
22644
|
// src/resolver.ts
|
|
22606
22645
|
import { execSync, spawnSync } from "child_process";
|
|
@@ -22902,6 +22941,7 @@ function coerceAftStatus(response) {
|
|
|
22902
22941
|
};
|
|
22903
22942
|
const disk = asRecord(response.disk);
|
|
22904
22943
|
const symbolCache = asRecord(response.symbol_cache);
|
|
22944
|
+
const session = asRecord(response.session);
|
|
22905
22945
|
return {
|
|
22906
22946
|
version: readString(response.version, "unknown"),
|
|
22907
22947
|
project_root: readNullableString(response.project_root),
|
|
@@ -22939,7 +22979,13 @@ function coerceAftStatus(response) {
|
|
|
22939
22979
|
local_entries: readNumber(symbolCache.local_entries),
|
|
22940
22980
|
warm_entries: readNumber(symbolCache.warm_entries)
|
|
22941
22981
|
},
|
|
22942
|
-
storage_dir: readNullableString(response.storage_dir)
|
|
22982
|
+
storage_dir: readNullableString(response.storage_dir),
|
|
22983
|
+
checkpoints_total: readNumber(response.checkpoints_total),
|
|
22984
|
+
session: {
|
|
22985
|
+
id: readString(session.id, "__default__"),
|
|
22986
|
+
tracked_files: readNumber(session.tracked_files),
|
|
22987
|
+
checkpoints: readNumber(session.checkpoints)
|
|
22988
|
+
}
|
|
22943
22989
|
};
|
|
22944
22990
|
}
|
|
22945
22991
|
function formatStatusMarkdown(status) {
|
|
@@ -22988,6 +23034,7 @@ function formatStatusMarkdown(status) {
|
|
|
22988
23034
|
if (status.storage_dir ?? status.disk.storage_dir) {
|
|
22989
23035
|
lines.push(`- **Storage dir:** \`${status.storage_dir ?? status.disk.storage_dir}\``);
|
|
22990
23036
|
}
|
|
23037
|
+
lines.push("", "### Current session", `- **ID:** \`${status.session.id}\``, `- **Tracked files:** ${formatCount(status.session.tracked_files)}`, `- **Checkpoints:** ${formatCount(status.session.checkpoints)}`, `- **Project checkpoints (all sessions):** ${formatCount(status.checkpoints_total)}`);
|
|
22991
23038
|
return lines.join(`
|
|
22992
23039
|
`);
|
|
22993
23040
|
}
|
|
@@ -23232,16 +23279,91 @@ function cleanupUrlCache(storageDir) {
|
|
|
23232
23279
|
}
|
|
23233
23280
|
}
|
|
23234
23281
|
|
|
23282
|
+
// src/shutdown-hooks.ts
|
|
23283
|
+
var GLOBAL_KEY2 = "__aftShutdownHooks__";
|
|
23284
|
+
function getState() {
|
|
23285
|
+
const g = globalThis;
|
|
23286
|
+
if (!g[GLOBAL_KEY2]) {
|
|
23287
|
+
g[GLOBAL_KEY2] = { cleanups: new Set, installed: false };
|
|
23288
|
+
}
|
|
23289
|
+
return g[GLOBAL_KEY2];
|
|
23290
|
+
}
|
|
23291
|
+
var shuttingDown = false;
|
|
23292
|
+
async function runCleanups(reason) {
|
|
23293
|
+
if (shuttingDown)
|
|
23294
|
+
return;
|
|
23295
|
+
shuttingDown = true;
|
|
23296
|
+
const state = getState();
|
|
23297
|
+
if (state.cleanups.size === 0)
|
|
23298
|
+
return;
|
|
23299
|
+
log(`Shutdown triggered by ${reason} \u2014 running ${state.cleanups.size} cleanup(s)`);
|
|
23300
|
+
const cleanups = Array.from(state.cleanups);
|
|
23301
|
+
state.cleanups.clear();
|
|
23302
|
+
await Promise.allSettled(cleanups.map(async (fn) => {
|
|
23303
|
+
try {
|
|
23304
|
+
await fn();
|
|
23305
|
+
} catch (err) {
|
|
23306
|
+
log(`Cleanup error: ${err.message}`);
|
|
23307
|
+
}
|
|
23308
|
+
}));
|
|
23309
|
+
}
|
|
23310
|
+
function installProcessHandlers() {
|
|
23311
|
+
const state = getState();
|
|
23312
|
+
if (state.installed)
|
|
23313
|
+
return;
|
|
23314
|
+
state.installed = true;
|
|
23315
|
+
const signals = ["SIGTERM", "SIGINT", "SIGHUP"];
|
|
23316
|
+
for (const sig of signals) {
|
|
23317
|
+
process.on(sig, () => {
|
|
23318
|
+
runCleanups(sig);
|
|
23319
|
+
});
|
|
23320
|
+
}
|
|
23321
|
+
process.on("beforeExit", () => {
|
|
23322
|
+
runCleanups("beforeExit");
|
|
23323
|
+
});
|
|
23324
|
+
}
|
|
23325
|
+
function registerShutdownCleanup(fn) {
|
|
23326
|
+
installProcessHandlers();
|
|
23327
|
+
const state = getState();
|
|
23328
|
+
state.cleanups.add(fn);
|
|
23329
|
+
return () => {
|
|
23330
|
+
state.cleanups.delete(fn);
|
|
23331
|
+
};
|
|
23332
|
+
}
|
|
23333
|
+
|
|
23235
23334
|
// src/tools/ast.ts
|
|
23236
23335
|
import { tool as tool2 } from "@opencode-ai/plugin";
|
|
23237
23336
|
|
|
23238
|
-
// src/tools/
|
|
23337
|
+
// src/tools/_shared.ts
|
|
23338
|
+
import * as fs2 from "fs";
|
|
23239
23339
|
import * as path2 from "path";
|
|
23340
|
+
function projectRootFor(runtime) {
|
|
23341
|
+
const raw = runtime.worktree ?? runtime.directory;
|
|
23342
|
+
const trimmed = raw.replace(/[/\\]+$/, "");
|
|
23343
|
+
try {
|
|
23344
|
+
return fs2.realpathSync(trimmed);
|
|
23345
|
+
} catch {
|
|
23346
|
+
return path2.resolve(trimmed);
|
|
23347
|
+
}
|
|
23348
|
+
}
|
|
23349
|
+
function bridgeFor(ctx, runtime) {
|
|
23350
|
+
return ctx.pool.getBridge(projectRootFor(runtime));
|
|
23351
|
+
}
|
|
23352
|
+
function callBridge(ctx, runtime, command, params = {}) {
|
|
23353
|
+
const merged = { ...params };
|
|
23354
|
+
if (runtime.sessionID) {
|
|
23355
|
+
merged.session_id = runtime.sessionID;
|
|
23356
|
+
}
|
|
23357
|
+
return bridgeFor(ctx, runtime).send(command, merged);
|
|
23358
|
+
}
|
|
23359
|
+
|
|
23360
|
+
// src/tools/permissions.ts
|
|
23361
|
+
import * as path3 from "path";
|
|
23240
23362
|
function resolveAbsolutePath(context, target) {
|
|
23241
|
-
return
|
|
23363
|
+
return path3.isAbsolute(target) ? target : path3.resolve(context.directory, target);
|
|
23242
23364
|
}
|
|
23243
23365
|
function resolveRelativePattern(context, target) {
|
|
23244
|
-
return
|
|
23366
|
+
return path3.relative(context.worktree, resolveAbsolutePath(context, target)) || ".";
|
|
23245
23367
|
}
|
|
23246
23368
|
function resolveRelativePatterns(context, targets) {
|
|
23247
23369
|
const seen = new Set;
|
|
@@ -23329,7 +23451,6 @@ function astTools(ctx) {
|
|
|
23329
23451
|
contextLines: z2.number().optional().describe("Number of context lines to show around each match")
|
|
23330
23452
|
},
|
|
23331
23453
|
execute: async (args, context) => {
|
|
23332
|
-
const bridge = ctx.pool.getBridge(context.directory, context.sessionID);
|
|
23333
23454
|
const params = {
|
|
23334
23455
|
pattern: args.pattern,
|
|
23335
23456
|
lang: args.lang
|
|
@@ -23340,7 +23461,7 @@ function astTools(ctx) {
|
|
|
23340
23461
|
params.globs = args.globs;
|
|
23341
23462
|
if (args.contextLines !== undefined)
|
|
23342
23463
|
params.context = Number(args.contextLines);
|
|
23343
|
-
const response = await
|
|
23464
|
+
const response = await callBridge(ctx, context, "ast_search", params);
|
|
23344
23465
|
if (response.success === false) {
|
|
23345
23466
|
throw new Error(response.message || "ast_search failed");
|
|
23346
23467
|
}
|
|
@@ -23406,7 +23527,6 @@ ${hint}`;
|
|
|
23406
23527
|
dryRun: z2.boolean().optional().describe("Preview changes without applying (default: false)")
|
|
23407
23528
|
},
|
|
23408
23529
|
execute: async (args, context) => {
|
|
23409
|
-
const bridge = ctx.pool.getBridge(context.directory, context.sessionID);
|
|
23410
23530
|
const isDryRun = args.dryRun === true;
|
|
23411
23531
|
if (!isDryRun) {
|
|
23412
23532
|
const explicitPaths = Array.isArray(args.paths) ? resolveRelativePatterns(context, args.paths) : [];
|
|
@@ -23430,7 +23550,7 @@ ${hint}`;
|
|
|
23430
23550
|
if (args.globs)
|
|
23431
23551
|
params.globs = args.globs;
|
|
23432
23552
|
params.dry_run = args.dryRun === true;
|
|
23433
|
-
const response = await
|
|
23553
|
+
const response = await callBridge(ctx, context, "ast_replace", params);
|
|
23434
23554
|
if (response.success === false) {
|
|
23435
23555
|
throw new Error(response.message || "ast_replace failed");
|
|
23436
23556
|
}
|
|
@@ -23482,8 +23602,7 @@ function conflictTools(ctx) {
|
|
|
23482
23602
|
description: "Show all git merge conflicts across the repository \u2014 returns line-numbered conflict regions with context for every conflicted file in a single call.",
|
|
23483
23603
|
args: {},
|
|
23484
23604
|
execute: async (_args, context) => {
|
|
23485
|
-
const
|
|
23486
|
-
const response = await bridge.send("git_conflicts", {});
|
|
23605
|
+
const response = await callBridge(ctx, context, "git_conflicts", {});
|
|
23487
23606
|
if (response.success === false) {
|
|
23488
23607
|
throw new Error(response.message || "git_conflicts failed");
|
|
23489
23608
|
}
|
|
@@ -23494,8 +23613,8 @@ function conflictTools(ctx) {
|
|
|
23494
23613
|
}
|
|
23495
23614
|
|
|
23496
23615
|
// src/tools/hoisted.ts
|
|
23497
|
-
import * as
|
|
23498
|
-
import * as
|
|
23616
|
+
import * as fs3 from "fs";
|
|
23617
|
+
import * as path4 from "path";
|
|
23499
23618
|
import { tool as tool3 } from "@opencode-ai/plugin";
|
|
23500
23619
|
|
|
23501
23620
|
// src/patch-parser.ts
|
|
@@ -23732,7 +23851,7 @@ function getCallID(ctx) {
|
|
|
23732
23851
|
return c.callID ?? c.callId ?? c.call_id;
|
|
23733
23852
|
}
|
|
23734
23853
|
function relativeToWorktree(fp, worktree) {
|
|
23735
|
-
return
|
|
23854
|
+
return path4.relative(worktree, fp);
|
|
23736
23855
|
}
|
|
23737
23856
|
function buildUnifiedDiff(fp, before, after) {
|
|
23738
23857
|
const SIZE_CAP = 100 * 1024;
|
|
@@ -23814,16 +23933,15 @@ function createReadTool(ctx) {
|
|
|
23814
23933
|
offset: z3.number().optional().describe("1-based line number to start reading from (use with limit). Ignored if startLine is provided")
|
|
23815
23934
|
},
|
|
23816
23935
|
execute: async (args, context) => {
|
|
23817
|
-
const bridge = ctx.pool.getBridge(context.directory, context.sessionID);
|
|
23818
23936
|
const file2 = args.filePath;
|
|
23819
|
-
const filePath =
|
|
23937
|
+
const filePath = path4.isAbsolute(file2) ? file2 : path4.resolve(context.directory, file2);
|
|
23820
23938
|
await context.ask({
|
|
23821
23939
|
permission: "read",
|
|
23822
23940
|
patterns: [filePath],
|
|
23823
23941
|
always: ["*"],
|
|
23824
23942
|
metadata: {}
|
|
23825
23943
|
});
|
|
23826
|
-
const ext =
|
|
23944
|
+
const ext = path4.extname(filePath).toLowerCase();
|
|
23827
23945
|
const mimeMap = {
|
|
23828
23946
|
".png": "image/png",
|
|
23829
23947
|
".jpg": "image/jpeg",
|
|
@@ -23845,7 +23963,7 @@ function createReadTool(ctx) {
|
|
|
23845
23963
|
const label = isImage ? "Image" : "PDF";
|
|
23846
23964
|
let fileSize = 0;
|
|
23847
23965
|
try {
|
|
23848
|
-
const stat = await import("fs/promises").then((
|
|
23966
|
+
const stat = await import("fs/promises").then((fs4) => fs4.stat(filePath));
|
|
23849
23967
|
fileSize = stat.size;
|
|
23850
23968
|
} catch {}
|
|
23851
23969
|
const sizeStr = fileSize > 1048576 ? `${(fileSize / 1048576).toFixed(1)}MB` : fileSize > 1024 ? `${(fileSize / 1024).toFixed(0)}KB` : `${fileSize} bytes`;
|
|
@@ -23853,7 +23971,7 @@ function createReadTool(ctx) {
|
|
|
23853
23971
|
const imgCallID = getCallID(context);
|
|
23854
23972
|
if (imgCallID) {
|
|
23855
23973
|
storeToolMetadata(context.sessionID, imgCallID, {
|
|
23856
|
-
title:
|
|
23974
|
+
title: path4.relative(context.worktree, filePath),
|
|
23857
23975
|
metadata: {
|
|
23858
23976
|
preview: msg,
|
|
23859
23977
|
filepath: filePath,
|
|
@@ -23879,7 +23997,7 @@ function createReadTool(ctx) {
|
|
|
23879
23997
|
params.end_line = endLine;
|
|
23880
23998
|
if (args.limit !== undefined && args.offset === undefined)
|
|
23881
23999
|
params.limit = args.limit;
|
|
23882
|
-
const data = await
|
|
24000
|
+
const data = await callBridge(ctx, context, "read", params);
|
|
23883
24001
|
if (data.success === false) {
|
|
23884
24002
|
throw new Error(data.message || "read failed");
|
|
23885
24003
|
}
|
|
@@ -23937,18 +24055,17 @@ function createWriteTool(ctx, editToolName = "edit") {
|
|
|
23937
24055
|
content: z3.string().describe("The full content to write to the file")
|
|
23938
24056
|
},
|
|
23939
24057
|
execute: async (args, context) => {
|
|
23940
|
-
const bridge = ctx.pool.getBridge(context.directory, context.sessionID);
|
|
23941
24058
|
const file2 = args.filePath;
|
|
23942
24059
|
const content = args.content;
|
|
23943
|
-
const filePath =
|
|
23944
|
-
const relPath =
|
|
24060
|
+
const filePath = path4.isAbsolute(file2) ? file2 : path4.resolve(context.directory, file2);
|
|
24061
|
+
const relPath = path4.relative(context.worktree, filePath);
|
|
23945
24062
|
await context.ask({
|
|
23946
24063
|
permission: "edit",
|
|
23947
24064
|
patterns: [relPath],
|
|
23948
24065
|
always: ["*"],
|
|
23949
24066
|
metadata: { filepath: filePath }
|
|
23950
24067
|
});
|
|
23951
|
-
const data = await
|
|
24068
|
+
const data = await callBridge(ctx, context, "write", {
|
|
23952
24069
|
file: filePath,
|
|
23953
24070
|
content,
|
|
23954
24071
|
create_dirs: true,
|
|
@@ -24068,30 +24185,29 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
24068
24185
|
dryRun: z3.boolean().optional().describe("Preview changes without applying (returns diff, default: false)")
|
|
24069
24186
|
},
|
|
24070
24187
|
execute: async (args, context) => {
|
|
24071
|
-
const bridge = ctx.pool.getBridge(context.directory, context.sessionID);
|
|
24072
24188
|
if (Array.isArray(args.operations)) {
|
|
24073
24189
|
const ops = args.operations;
|
|
24074
24190
|
const files = ops.map((op) => op.file).filter(Boolean);
|
|
24075
24191
|
await context.ask({
|
|
24076
24192
|
permission: "edit",
|
|
24077
|
-
patterns: files.map((f) =>
|
|
24193
|
+
patterns: files.map((f) => path4.relative(context.worktree, path4.resolve(context.directory, f))),
|
|
24078
24194
|
always: ["*"],
|
|
24079
24195
|
metadata: {}
|
|
24080
24196
|
});
|
|
24081
24197
|
const resolvedOps = ops.map((op) => ({
|
|
24082
24198
|
...op,
|
|
24083
|
-
file:
|
|
24199
|
+
file: path4.isAbsolute(op.file) ? op.file : path4.resolve(context.directory, op.file)
|
|
24084
24200
|
}));
|
|
24085
24201
|
const params2 = { operations: resolvedOps };
|
|
24086
24202
|
params2.dry_run = args.dryRun === true;
|
|
24087
|
-
const data2 = await
|
|
24203
|
+
const data2 = await callBridge(ctx, context, "transaction", params2);
|
|
24088
24204
|
return JSON.stringify(data2);
|
|
24089
24205
|
}
|
|
24090
24206
|
const file2 = args.filePath;
|
|
24091
24207
|
if (!file2)
|
|
24092
24208
|
throw new Error("'filePath' parameter is required");
|
|
24093
|
-
const filePath =
|
|
24094
|
-
const relPath =
|
|
24209
|
+
const filePath = path4.isAbsolute(file2) ? file2 : path4.resolve(context.directory, file2);
|
|
24210
|
+
const relPath = path4.relative(context.worktree, filePath);
|
|
24095
24211
|
await context.ask({
|
|
24096
24212
|
permission: "edit",
|
|
24097
24213
|
patterns: [relPath],
|
|
@@ -24144,7 +24260,7 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
24144
24260
|
params.diagnostics = true;
|
|
24145
24261
|
if (!args.dryRun)
|
|
24146
24262
|
params.include_diff = true;
|
|
24147
|
-
const data = await
|
|
24263
|
+
const data = await callBridge(ctx, context, command, params);
|
|
24148
24264
|
if (!args.dryRun && data.success && data.diff) {
|
|
24149
24265
|
const diff = data.diff;
|
|
24150
24266
|
const callID = getCallID(context);
|
|
@@ -24237,7 +24353,6 @@ function createApplyPatchTool(ctx) {
|
|
|
24237
24353
|
patchText: z3.string().describe("The full patch text including Begin/End markers")
|
|
24238
24354
|
},
|
|
24239
24355
|
execute: async (args, context) => {
|
|
24240
|
-
const bridge = ctx.pool.getBridge(context.directory, context.sessionID);
|
|
24241
24356
|
const patchText = args.patchText;
|
|
24242
24357
|
if (!patchText)
|
|
24243
24358
|
throw new Error("'patchText' is required");
|
|
@@ -24250,7 +24365,7 @@ function createApplyPatchTool(ctx) {
|
|
|
24250
24365
|
if (hunks.length === 0) {
|
|
24251
24366
|
throw new Error("Empty patch: no file operations found");
|
|
24252
24367
|
}
|
|
24253
|
-
const allPaths = hunks.map((h) =>
|
|
24368
|
+
const allPaths = hunks.map((h) => path4.relative(context.worktree, path4.resolve(context.directory, h.path)));
|
|
24254
24369
|
await context.ask({
|
|
24255
24370
|
permission: "edit",
|
|
24256
24371
|
patterns: allPaths,
|
|
@@ -24260,9 +24375,9 @@ function createApplyPatchTool(ctx) {
|
|
|
24260
24375
|
const checkpointName = `apply_patch_${Date.now()}`;
|
|
24261
24376
|
let checkpointCreated = false;
|
|
24262
24377
|
try {
|
|
24263
|
-
await
|
|
24378
|
+
await callBridge(ctx, context, "checkpoint", {
|
|
24264
24379
|
name: checkpointName,
|
|
24265
|
-
files: allPaths.map((p) =>
|
|
24380
|
+
files: allPaths.map((p) => path4.resolve(context.directory, p))
|
|
24266
24381
|
});
|
|
24267
24382
|
checkpointCreated = true;
|
|
24268
24383
|
} catch {}
|
|
@@ -24270,11 +24385,11 @@ function createApplyPatchTool(ctx) {
|
|
|
24270
24385
|
const perFileDiffs = [];
|
|
24271
24386
|
let patchFailed = false;
|
|
24272
24387
|
for (const hunk of hunks) {
|
|
24273
|
-
const filePath =
|
|
24388
|
+
const filePath = path4.resolve(context.directory, hunk.path);
|
|
24274
24389
|
switch (hunk.type) {
|
|
24275
24390
|
case "add": {
|
|
24276
24391
|
try {
|
|
24277
|
-
await
|
|
24392
|
+
await callBridge(ctx, context, "write", {
|
|
24278
24393
|
file: filePath,
|
|
24279
24394
|
content: hunk.contents.endsWith(`
|
|
24280
24395
|
`) ? hunk.contents : `${hunk.contents}
|
|
@@ -24292,8 +24407,8 @@ function createApplyPatchTool(ctx) {
|
|
|
24292
24407
|
}
|
|
24293
24408
|
case "delete": {
|
|
24294
24409
|
try {
|
|
24295
|
-
const before = await
|
|
24296
|
-
await
|
|
24410
|
+
const before = await fs3.promises.readFile(filePath, "utf-8").catch(() => "");
|
|
24411
|
+
await callBridge(ctx, context, "delete_file", { file: filePath });
|
|
24297
24412
|
perFileDiffs.push({ filePath, before, after: "" });
|
|
24298
24413
|
results.push(`Deleted ${hunk.path}`);
|
|
24299
24414
|
} catch (e) {
|
|
@@ -24304,10 +24419,10 @@ function createApplyPatchTool(ctx) {
|
|
|
24304
24419
|
}
|
|
24305
24420
|
case "update": {
|
|
24306
24421
|
try {
|
|
24307
|
-
const original = await
|
|
24422
|
+
const original = await fs3.promises.readFile(filePath, "utf-8");
|
|
24308
24423
|
const newContent = applyUpdateChunks(original, filePath, hunk.chunks);
|
|
24309
|
-
const targetPath = hunk.move_path ?
|
|
24310
|
-
const writeResult = await
|
|
24424
|
+
const targetPath = hunk.move_path ? path4.resolve(context.directory, hunk.move_path) : filePath;
|
|
24425
|
+
const writeResult = await callBridge(ctx, context, "write", {
|
|
24311
24426
|
file: targetPath,
|
|
24312
24427
|
content: newContent,
|
|
24313
24428
|
create_dirs: true,
|
|
@@ -24317,7 +24432,7 @@ function createApplyPatchTool(ctx) {
|
|
|
24317
24432
|
if (diags && diags.length > 0) {
|
|
24318
24433
|
const errors3 = diags.filter((d) => d.severity === "error");
|
|
24319
24434
|
if (errors3.length > 0) {
|
|
24320
|
-
const relPath =
|
|
24435
|
+
const relPath = path4.relative(context.worktree, targetPath);
|
|
24321
24436
|
const diagLines = errors3.map((d) => ` Line ${d.line}: ${d.message}`).join(`
|
|
24322
24437
|
`);
|
|
24323
24438
|
results.push(`
|
|
@@ -24327,7 +24442,7 @@ ${diagLines}`);
|
|
|
24327
24442
|
}
|
|
24328
24443
|
perFileDiffs.push({ filePath, before: original, after: newContent });
|
|
24329
24444
|
if (hunk.move_path) {
|
|
24330
|
-
await
|
|
24445
|
+
await callBridge(ctx, context, "delete_file", { file: filePath });
|
|
24331
24446
|
results.push(`Updated and moved ${hunk.path} \u2192 ${hunk.move_path}`);
|
|
24332
24447
|
} else {
|
|
24333
24448
|
results.push(`Updated ${hunk.path}`);
|
|
@@ -24344,7 +24459,7 @@ ${diagLines}`);
|
|
|
24344
24459
|
if (patchFailed) {
|
|
24345
24460
|
if (checkpointCreated) {
|
|
24346
24461
|
try {
|
|
24347
|
-
await
|
|
24462
|
+
await callBridge(ctx, context, "restore_checkpoint", { name: checkpointName });
|
|
24348
24463
|
results.push("Patch failed \u2014 restored files to pre-patch state.");
|
|
24349
24464
|
} catch {
|
|
24350
24465
|
results.push("Patch failed \u2014 checkpoint restore also failed, files may be inconsistent.");
|
|
@@ -24358,9 +24473,9 @@ ${diagLines}`);
|
|
|
24358
24473
|
const callID = getCallID(context);
|
|
24359
24474
|
if (callID) {
|
|
24360
24475
|
const files = hunks.map((h) => {
|
|
24361
|
-
const relPath =
|
|
24476
|
+
const relPath = path4.relative(context.worktree, path4.resolve(context.directory, h.path));
|
|
24362
24477
|
return {
|
|
24363
|
-
filePath:
|
|
24478
|
+
filePath: path4.resolve(context.directory, h.path),
|
|
24364
24479
|
relativePath: relPath,
|
|
24365
24480
|
type: h.type
|
|
24366
24481
|
};
|
|
@@ -24397,15 +24512,14 @@ function createDeleteTool(ctx) {
|
|
|
24397
24512
|
filePath: z3.string().describe("Path to file to delete")
|
|
24398
24513
|
},
|
|
24399
24514
|
execute: async (args, context) => {
|
|
24400
|
-
const
|
|
24401
|
-
const filePath = path3.isAbsolute(args.filePath) ? args.filePath : path3.resolve(context.directory, args.filePath);
|
|
24515
|
+
const filePath = path4.isAbsolute(args.filePath) ? args.filePath : path4.resolve(context.directory, args.filePath);
|
|
24402
24516
|
await context.ask({
|
|
24403
24517
|
permission: "edit",
|
|
24404
24518
|
patterns: [filePath],
|
|
24405
24519
|
always: ["*"],
|
|
24406
24520
|
metadata: { action: "delete" }
|
|
24407
24521
|
});
|
|
24408
|
-
const result = await
|
|
24522
|
+
const result = await callBridge(ctx, context, "delete_file", { file: filePath });
|
|
24409
24523
|
if (result.success === false) {
|
|
24410
24524
|
throw new Error(result.message || "delete failed");
|
|
24411
24525
|
}
|
|
@@ -24423,16 +24537,15 @@ function createMoveTool(ctx) {
|
|
|
24423
24537
|
destination: z3.string().describe("Destination file path")
|
|
24424
24538
|
},
|
|
24425
24539
|
execute: async (args, context) => {
|
|
24426
|
-
const
|
|
24427
|
-
const
|
|
24428
|
-
const destPath = path3.isAbsolute(args.destination) ? args.destination : path3.resolve(context.directory, args.destination);
|
|
24540
|
+
const filePath = path4.isAbsolute(args.filePath) ? args.filePath : path4.resolve(context.directory, args.filePath);
|
|
24541
|
+
const destPath = path4.isAbsolute(args.destination) ? args.destination : path4.resolve(context.directory, args.destination);
|
|
24429
24542
|
await context.ask({
|
|
24430
24543
|
permission: "edit",
|
|
24431
24544
|
patterns: [filePath, destPath],
|
|
24432
24545
|
always: ["*"],
|
|
24433
24546
|
metadata: { action: "move" }
|
|
24434
24547
|
});
|
|
24435
|
-
const result = await
|
|
24548
|
+
const result = await callBridge(ctx, context, "move_file", {
|
|
24436
24549
|
file: filePath,
|
|
24437
24550
|
destination: destPath
|
|
24438
24551
|
});
|
|
@@ -24502,7 +24615,6 @@ function importTools(ctx) {
|
|
|
24502
24615
|
dryRun: z4.boolean().optional().describe("Preview without modifying the file (default: false)")
|
|
24503
24616
|
},
|
|
24504
24617
|
execute: async (args, context) => {
|
|
24505
|
-
const bridge = ctx.pool.getBridge(context.directory, context.sessionID);
|
|
24506
24618
|
const op = args.op;
|
|
24507
24619
|
const isDryRun = args.dryRun === true;
|
|
24508
24620
|
if ((op === "add" || op === "remove") && typeof args.module !== "string") {
|
|
@@ -24534,7 +24646,7 @@ function importTools(ctx) {
|
|
|
24534
24646
|
params.validate = args.validate;
|
|
24535
24647
|
if (args.dryRun !== undefined)
|
|
24536
24648
|
params.dry_run = args.dryRun;
|
|
24537
|
-
const response = await
|
|
24649
|
+
const response = await callBridge(ctx, context, commandMap[op], params);
|
|
24538
24650
|
if (response.success === false) {
|
|
24539
24651
|
throw new Error(response.message || `${op} failed`);
|
|
24540
24652
|
}
|
|
@@ -24557,7 +24669,6 @@ function lspTools(ctx) {
|
|
|
24557
24669
|
waitMs: z5.number().optional().describe("Wait N ms for fresh diagnostics before returning (max 10000, default: 0). Use after edits to let the server re-analyze.")
|
|
24558
24670
|
},
|
|
24559
24671
|
execute: async (args, context) => {
|
|
24560
|
-
const bridge = ctx.pool.getBridge(context.directory, context.sessionID);
|
|
24561
24672
|
const filePath = args.filePath || undefined;
|
|
24562
24673
|
const directory = args.directory || undefined;
|
|
24563
24674
|
if (filePath !== undefined && directory !== undefined) {
|
|
@@ -24572,7 +24683,7 @@ function lspTools(ctx) {
|
|
|
24572
24683
|
params.severity = args.severity;
|
|
24573
24684
|
if (args.waitMs !== undefined)
|
|
24574
24685
|
params.wait_ms = args.waitMs;
|
|
24575
|
-
const result = await
|
|
24686
|
+
const result = await callBridge(ctx, context, "lsp_diagnostics", params);
|
|
24576
24687
|
if (result.success === false) {
|
|
24577
24688
|
throw new Error(result.message || "lsp_diagnostics failed");
|
|
24578
24689
|
}
|
|
@@ -24611,7 +24722,6 @@ function navigationTools(ctx) {
|
|
|
24611
24722
|
expression: z6.string().optional().describe("Expression to track through data flow (required for trace_data op)")
|
|
24612
24723
|
},
|
|
24613
24724
|
execute: async (args, context) => {
|
|
24614
|
-
const bridge = ctx.pool.getBridge(context.directory, context.sessionID);
|
|
24615
24725
|
const params = {
|
|
24616
24726
|
file: args.filePath,
|
|
24617
24727
|
symbol: args.symbol
|
|
@@ -24623,7 +24733,7 @@ function navigationTools(ctx) {
|
|
|
24623
24733
|
if (args.op === "trace_data" && typeof args.expression !== "string") {
|
|
24624
24734
|
throw new Error("'expression' is required for 'trace_data' op");
|
|
24625
24735
|
}
|
|
24626
|
-
const response = await
|
|
24736
|
+
const response = await callBridge(ctx, context, args.op, params);
|
|
24627
24737
|
if (response.success === false) {
|
|
24628
24738
|
throw new Error(response.message || `${args.op} failed`);
|
|
24629
24739
|
}
|
|
@@ -24635,7 +24745,7 @@ function navigationTools(ctx) {
|
|
|
24635
24745
|
|
|
24636
24746
|
// src/tools/reading.ts
|
|
24637
24747
|
import { readdir } from "fs/promises";
|
|
24638
|
-
import { extname as extname2, join as join12, resolve as
|
|
24748
|
+
import { extname as extname2, join as join12, resolve as resolve5 } from "path";
|
|
24639
24749
|
import { tool as tool7 } from "@opencode-ai/plugin";
|
|
24640
24750
|
var OUTLINE_EXTENSIONS = new Set([
|
|
24641
24751
|
".ts",
|
|
@@ -24687,7 +24797,6 @@ function readingTools(ctx) {
|
|
|
24687
24797
|
url: z7.string().optional().describe("HTTP/HTTPS URL of an HTML or Markdown document to fetch and outline")
|
|
24688
24798
|
},
|
|
24689
24799
|
execute: async (args, context) => {
|
|
24690
|
-
const bridge = ctx.pool.getBridge(context.directory, context.sessionID);
|
|
24691
24800
|
const filesArg = Array.isArray(args.files) ? args.files : undefined;
|
|
24692
24801
|
const hasFilePath = typeof args.filePath === "string" && args.filePath.length > 0;
|
|
24693
24802
|
const hasFiles = (filesArg?.length ?? 0) > 0;
|
|
@@ -24702,7 +24811,7 @@ function readingTools(ctx) {
|
|
|
24702
24811
|
}
|
|
24703
24812
|
if (hasUrl) {
|
|
24704
24813
|
const cachedPath = await fetchUrlToTempFile(args.url, ctx.storageDir);
|
|
24705
|
-
const response2 = await
|
|
24814
|
+
const response2 = await callBridge(ctx, context, "outline", { file: cachedPath });
|
|
24706
24815
|
if (response2.success === false) {
|
|
24707
24816
|
throw new Error(response2.message || "outline failed");
|
|
24708
24817
|
}
|
|
@@ -24712,7 +24821,7 @@ function readingTools(ctx) {
|
|
|
24712
24821
|
if (!dirArg && typeof args.filePath === "string" && !Array.isArray(args.files)) {
|
|
24713
24822
|
try {
|
|
24714
24823
|
const { stat } = await import("fs/promises");
|
|
24715
|
-
const resolved =
|
|
24824
|
+
const resolved = resolve5(context.directory, args.filePath);
|
|
24716
24825
|
const st = await stat(resolved);
|
|
24717
24826
|
if (st.isDirectory()) {
|
|
24718
24827
|
dirArg = args.filePath;
|
|
@@ -24720,7 +24829,7 @@ function readingTools(ctx) {
|
|
|
24720
24829
|
} catch {}
|
|
24721
24830
|
}
|
|
24722
24831
|
if (dirArg) {
|
|
24723
|
-
const dirPath =
|
|
24832
|
+
const dirPath = resolve5(context.directory, dirArg);
|
|
24724
24833
|
const files = await discoverSourceFiles(dirPath);
|
|
24725
24834
|
if (files.length === 0) {
|
|
24726
24835
|
return JSON.stringify({
|
|
@@ -24728,20 +24837,20 @@ function readingTools(ctx) {
|
|
|
24728
24837
|
message: `No source files found under ${dirArg}`
|
|
24729
24838
|
});
|
|
24730
24839
|
}
|
|
24731
|
-
const response2 = await
|
|
24840
|
+
const response2 = await callBridge(ctx, context, "outline", { files });
|
|
24732
24841
|
if (response2.success === false) {
|
|
24733
24842
|
throw new Error(response2.message || "outline failed");
|
|
24734
24843
|
}
|
|
24735
24844
|
return response2.text;
|
|
24736
24845
|
}
|
|
24737
24846
|
if (Array.isArray(args.files) && args.files.length > 0) {
|
|
24738
|
-
const response2 = await
|
|
24847
|
+
const response2 = await callBridge(ctx, context, "outline", { files: args.files });
|
|
24739
24848
|
if (response2.success === false) {
|
|
24740
24849
|
throw new Error(response2.message || "outline failed");
|
|
24741
24850
|
}
|
|
24742
24851
|
return response2.text;
|
|
24743
24852
|
}
|
|
24744
|
-
const response = await
|
|
24853
|
+
const response = await callBridge(ctx, context, "outline", { file: args.filePath });
|
|
24745
24854
|
if (response.success === false) {
|
|
24746
24855
|
throw new Error(response.message || "outline failed");
|
|
24747
24856
|
}
|
|
@@ -24760,7 +24869,6 @@ Provide exactly ONE of 'filePath' or 'url'. Pass either 'symbol' for a single lo
|
|
|
24760
24869
|
contextLines: z7.number().optional().describe("Lines of context before/after the symbol (default: 3)")
|
|
24761
24870
|
},
|
|
24762
24871
|
execute: async (args, context) => {
|
|
24763
|
-
const bridge = ctx.pool.getBridge(context.directory, context.sessionID);
|
|
24764
24872
|
const hasFilePath = typeof args.filePath === "string" && args.filePath.length > 0;
|
|
24765
24873
|
const hasUrl = typeof args.url === "string" && args.url.length > 0;
|
|
24766
24874
|
if (!hasFilePath && !hasUrl) {
|
|
@@ -24775,7 +24883,7 @@ Provide exactly ONE of 'filePath' or 'url'. Pass either 'symbol' for a single lo
|
|
|
24775
24883
|
const params2 = { file: file2, symbol: sym };
|
|
24776
24884
|
if (args.contextLines !== undefined)
|
|
24777
24885
|
params2.context_lines = args.contextLines;
|
|
24778
|
-
return
|
|
24886
|
+
return callBridge(ctx, context, "zoom", params2);
|
|
24779
24887
|
}));
|
|
24780
24888
|
return JSON.stringify(results);
|
|
24781
24889
|
}
|
|
@@ -24784,7 +24892,7 @@ Provide exactly ONE of 'filePath' or 'url'. Pass either 'symbol' for a single lo
|
|
|
24784
24892
|
params.symbol = args.symbol;
|
|
24785
24893
|
if (args.contextLines !== undefined)
|
|
24786
24894
|
params.context_lines = args.contextLines;
|
|
24787
|
-
const data = await
|
|
24895
|
+
const data = await callBridge(ctx, context, "zoom", params);
|
|
24788
24896
|
if (data.success === false) {
|
|
24789
24897
|
throw new Error(data.message || "zoom failed");
|
|
24790
24898
|
}
|
|
@@ -24932,7 +25040,6 @@ function refactoringTools(ctx) {
|
|
|
24932
25040
|
dryRun: z8.boolean().optional().describe("Preview changes as diff without modifying files (default: false)")
|
|
24933
25041
|
},
|
|
24934
25042
|
execute: async (args, context) => {
|
|
24935
|
-
const bridge = ctx.pool.getBridge(context.directory, context.sessionID);
|
|
24936
25043
|
const op = args.op;
|
|
24937
25044
|
if ((op === "move" || op === "inline") && typeof args.symbol !== "string") {
|
|
24938
25045
|
throw new Error(`'symbol' is required for '${op}' op`);
|
|
@@ -24989,7 +25096,7 @@ function refactoringTools(ctx) {
|
|
|
24989
25096
|
const hints = await queryLspHints(ctx.client, args.symbol ?? args.name);
|
|
24990
25097
|
if (hints)
|
|
24991
25098
|
params.lsp_hints = hints;
|
|
24992
|
-
const response = await
|
|
25099
|
+
const response = await callBridge(ctx, context, commandMap[op], params);
|
|
24993
25100
|
if (response.success === false) {
|
|
24994
25101
|
throw new Error(response.message || `${op} failed`);
|
|
24995
25102
|
}
|
|
@@ -25028,7 +25135,6 @@ function safetyTools(ctx) {
|
|
|
25028
25135
|
files: z9.array(z9.string()).optional().describe("Specific files to include in checkpoint (optional, defaults to all tracked files)")
|
|
25029
25136
|
},
|
|
25030
25137
|
execute: async (args, context) => {
|
|
25031
|
-
const bridge = ctx.pool.getBridge(context.directory, context.sessionID);
|
|
25032
25138
|
const op = args.op;
|
|
25033
25139
|
if ((op === "undo" || op === "history") && typeof args.filePath !== "string") {
|
|
25034
25140
|
throw new Error(`'filePath' is required for '${op}' op`);
|
|
@@ -25063,7 +25169,7 @@ function safetyTools(ctx) {
|
|
|
25063
25169
|
params.name = args.name;
|
|
25064
25170
|
if (args.files !== undefined)
|
|
25065
25171
|
params.files = args.files;
|
|
25066
|
-
const response = await
|
|
25172
|
+
const response = await callBridge(ctx, context, commandMap[op], params);
|
|
25067
25173
|
if (response.success === false) {
|
|
25068
25174
|
throw new Error(response.message || `${op} failed`);
|
|
25069
25175
|
}
|
|
@@ -25113,8 +25219,7 @@ function searchTools(ctx) {
|
|
|
25113
25219
|
path: arg(exports_external.string().optional().describe("Directory to search in, relative to project root"))
|
|
25114
25220
|
},
|
|
25115
25221
|
execute: async (args, context) => {
|
|
25116
|
-
const
|
|
25117
|
-
const response = await bridge.send("grep", {
|
|
25222
|
+
const response = await callBridge(ctx, context, "grep", {
|
|
25118
25223
|
pattern: args.pattern,
|
|
25119
25224
|
case_sensitive: true,
|
|
25120
25225
|
include: args.include ? String(args.include).split(",").map((s) => normalizeGlob(s.trim())).filter(Boolean) : undefined,
|
|
@@ -25134,7 +25239,6 @@ function searchTools(ctx) {
|
|
|
25134
25239
|
path: arg(exports_external.string().optional().describe("Directory to search in, relative to project root"))
|
|
25135
25240
|
},
|
|
25136
25241
|
execute: async (args, context) => {
|
|
25137
|
-
const bridge = ctx.pool.getBridge(context.directory, context.sessionID);
|
|
25138
25242
|
let globPattern = String(args.pattern);
|
|
25139
25243
|
let globPath = args.path ? String(args.path) : undefined;
|
|
25140
25244
|
if (!globPath && globPattern.startsWith("/")) {
|
|
@@ -25147,7 +25251,7 @@ function searchTools(ctx) {
|
|
|
25147
25251
|
}
|
|
25148
25252
|
}
|
|
25149
25253
|
}
|
|
25150
|
-
const response = await
|
|
25254
|
+
const response = await callBridge(ctx, context, "glob", {
|
|
25151
25255
|
pattern: globPattern,
|
|
25152
25256
|
path: globPath
|
|
25153
25257
|
});
|
|
@@ -25183,8 +25287,7 @@ function semanticTools(ctx) {
|
|
|
25183
25287
|
topK: arg2(exports_external.number().optional().describe("Number of results (default: 10)"))
|
|
25184
25288
|
},
|
|
25185
25289
|
execute: async (args, context) => {
|
|
25186
|
-
const
|
|
25187
|
-
const response = await bridge.send("semantic_search", {
|
|
25290
|
+
const response = await callBridge(ctx, context, "semantic_search", {
|
|
25188
25291
|
query: args.query,
|
|
25189
25292
|
top_k: args.topK ?? 10
|
|
25190
25293
|
});
|
|
@@ -25248,7 +25351,6 @@ function structureTools(ctx) {
|
|
|
25248
25351
|
dryRun: z10.boolean().optional().describe("Preview without modifying the file (default: false)")
|
|
25249
25352
|
},
|
|
25250
25353
|
execute: async (args, context) => {
|
|
25251
|
-
const bridge = ctx.pool.getBridge(context.directory, context.sessionID);
|
|
25252
25354
|
const op = args.op;
|
|
25253
25355
|
if (op === "add_member") {
|
|
25254
25356
|
if (typeof args.container !== "string")
|
|
@@ -25312,7 +25414,7 @@ function structureTools(ctx) {
|
|
|
25312
25414
|
params.value = args.value;
|
|
25313
25415
|
break;
|
|
25314
25416
|
}
|
|
25315
|
-
const response = await
|
|
25417
|
+
const response = await callBridge(ctx, context, op, params);
|
|
25316
25418
|
if (response.success === false) {
|
|
25317
25419
|
throw new Error(response.message || `${op} failed`);
|
|
25318
25420
|
}
|
|
@@ -25358,6 +25460,8 @@ var PLUGIN_VERSION = (() => {
|
|
|
25358
25460
|
return "0.0.0";
|
|
25359
25461
|
}
|
|
25360
25462
|
})();
|
|
25463
|
+
var ANNOUNCEMENT_VERSION = "";
|
|
25464
|
+
var ANNOUNCEMENT_FEATURES = [];
|
|
25361
25465
|
var plugin = async (input) => {
|
|
25362
25466
|
const binaryPath = await findBinary();
|
|
25363
25467
|
const aftConfig = loadAftConfig(input.directory);
|
|
@@ -25401,10 +25505,10 @@ var plugin = async (input) => {
|
|
|
25401
25505
|
}
|
|
25402
25506
|
versionUpgradeAttempted = binaryVersion;
|
|
25403
25507
|
warn(`WARNING: aft binary v${binaryVersion} is older than plugin v${minVersion}. ` + "Some features may not work. Attempting to download a compatible binary...");
|
|
25404
|
-
ensureBinary(`v${minVersion}`).then((
|
|
25405
|
-
if (
|
|
25406
|
-
log(`Found/downloaded compatible binary at ${
|
|
25407
|
-
pool.replaceBinary(
|
|
25508
|
+
ensureBinary(`v${minVersion}`).then((path5) => {
|
|
25509
|
+
if (path5) {
|
|
25510
|
+
log(`Found/downloaded compatible binary at ${path5}. Replacing running bridges...`);
|
|
25511
|
+
pool.replaceBinary(path5).then(() => {
|
|
25408
25512
|
log("Binary replaced successfully. New bridges will use the updated binary.");
|
|
25409
25513
|
}, (err) => error48("Failed to replace binary:", err));
|
|
25410
25514
|
} else {
|
|
@@ -25423,36 +25527,39 @@ var plugin = async (input) => {
|
|
|
25423
25527
|
};
|
|
25424
25528
|
setSharedBridgePool(pool);
|
|
25425
25529
|
const rpcServer = new AftRpcServer(configOverrides.storage_dir, input.directory);
|
|
25530
|
+
const unregisterShutdown = registerShutdownCleanup(async () => {
|
|
25531
|
+
try {
|
|
25532
|
+
rpcServer.stop();
|
|
25533
|
+
} catch {}
|
|
25534
|
+
await pool.shutdown();
|
|
25535
|
+
});
|
|
25426
25536
|
rpcServer.handle("status", async (params) => {
|
|
25427
25537
|
const sessionID = params.sessionID || "rpc";
|
|
25428
|
-
const bridge = pool.getAnyActiveBridge(input.directory) ?? pool.getBridge(input.directory
|
|
25429
|
-
return await bridge.send("status", {});
|
|
25538
|
+
const bridge = pool.getAnyActiveBridge(input.directory) ?? pool.getBridge(input.directory);
|
|
25539
|
+
return await bridge.send("status", { session_id: sessionID });
|
|
25430
25540
|
});
|
|
25431
25541
|
const storageDir = configOverrides.storage_dir;
|
|
25432
|
-
const featureList = [
|
|
25433
|
-
"Semantic code search (`aft_search`) \u2014 enable with `experimental_semantic_search: true` in aft.jsonc",
|
|
25434
|
-
"/aft-status command \u2014 live index health, disk usage, and runtime details",
|
|
25435
|
-
"HTML outline and zoom \u2014 heading hierarchy for .html/.htm files",
|
|
25436
|
-
"And many bugfixes"
|
|
25437
|
-
];
|
|
25438
25542
|
rpcServer.handle("get-announcement", async () => {
|
|
25543
|
+
if (!ANNOUNCEMENT_VERSION || ANNOUNCEMENT_FEATURES.length === 0) {
|
|
25544
|
+
return { show: false };
|
|
25545
|
+
}
|
|
25439
25546
|
if (storageDir) {
|
|
25440
25547
|
const versionFile = join13(storageDir, "last_announced_version");
|
|
25441
25548
|
try {
|
|
25442
25549
|
if (existsSync8(versionFile)) {
|
|
25443
25550
|
const lastVersion = readFileSync5(versionFile, "utf-8").trim();
|
|
25444
|
-
if (lastVersion ===
|
|
25551
|
+
if (lastVersion === ANNOUNCEMENT_VERSION)
|
|
25445
25552
|
return { show: false };
|
|
25446
25553
|
}
|
|
25447
25554
|
} catch {}
|
|
25448
25555
|
}
|
|
25449
|
-
return { show: true, version:
|
|
25556
|
+
return { show: true, version: ANNOUNCEMENT_VERSION, features: ANNOUNCEMENT_FEATURES };
|
|
25450
25557
|
});
|
|
25451
25558
|
rpcServer.handle("mark-announced", async () => {
|
|
25452
|
-
if (storageDir) {
|
|
25559
|
+
if (storageDir && ANNOUNCEMENT_VERSION) {
|
|
25453
25560
|
try {
|
|
25454
25561
|
mkdirSync8(storageDir, { recursive: true });
|
|
25455
|
-
writeFileSync5(join13(storageDir, "last_announced_version"),
|
|
25562
|
+
writeFileSync5(join13(storageDir, "last_announced_version"), ANNOUNCEMENT_VERSION);
|
|
25456
25563
|
} catch {}
|
|
25457
25564
|
}
|
|
25458
25565
|
return { success: true };
|
|
@@ -25478,9 +25585,11 @@ Install: ${getManualInstallHint()}`);
|
|
|
25478
25585
|
client: input.client,
|
|
25479
25586
|
directory: input.directory
|
|
25480
25587
|
};
|
|
25481
|
-
|
|
25482
|
-
|
|
25483
|
-
|
|
25588
|
+
if (ANNOUNCEMENT_VERSION && ANNOUNCEMENT_FEATURES.length > 0) {
|
|
25589
|
+
setTimeout(() => {
|
|
25590
|
+
sendFeatureAnnouncement(notifyOpts, ANNOUNCEMENT_VERSION, ANNOUNCEMENT_FEATURES, storageDir).catch(() => {});
|
|
25591
|
+
}, 8000);
|
|
25592
|
+
}
|
|
25484
25593
|
if (aftConfig.experimental_semantic_search && isFastembedSemanticBackend && !configOverrides._ort_dylib_dir) {
|
|
25485
25594
|
setTimeout(() => {
|
|
25486
25595
|
if (!configOverrides._ort_dylib_dir && !isOrtAutoDownloadSupported()) {
|
|
@@ -25537,8 +25646,8 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
25537
25646
|
if (isTuiMode2() || commandInput.command !== STATUS_COMMAND) {
|
|
25538
25647
|
return;
|
|
25539
25648
|
}
|
|
25540
|
-
const bridge = ctx.pool.getAnyActiveBridge(input.directory) ?? ctx.pool.getBridge(input.directory
|
|
25541
|
-
const response = await bridge.send("status", {});
|
|
25649
|
+
const bridge = ctx.pool.getAnyActiveBridge(input.directory) ?? ctx.pool.getBridge(input.directory);
|
|
25650
|
+
const response = await bridge.send("status", { session_id: commandInput.sessionID });
|
|
25542
25651
|
if (response.success === false) {
|
|
25543
25652
|
throw new Error(response.message || "status failed");
|
|
25544
25653
|
}
|
|
@@ -25581,6 +25690,7 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
25581
25690
|
};
|
|
25582
25691
|
},
|
|
25583
25692
|
dispose: () => {
|
|
25693
|
+
unregisterShutdown();
|
|
25584
25694
|
rpcServer.stop();
|
|
25585
25695
|
clearSharedBridgePool();
|
|
25586
25696
|
return pool.shutdown();
|