@cortexkit/aft-opencode 0.39.2 → 0.39.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bg-notifications.d.ts +5 -20
- package/dist/bg-notifications.d.ts.map +1 -1
- package/dist/config.d.ts +11 -10
- package/dist/config.d.ts.map +1 -1
- package/dist/configure-warnings.d.ts +4 -1
- package/dist/configure-warnings.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +438 -257
- package/dist/notifications.d.ts +1 -1
- package/dist/notifications.d.ts.map +1 -1
- package/dist/tools/bash.d.ts +3 -5
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/hoisted.d.ts +5 -12
- package/dist/tools/hoisted.d.ts.map +1 -1
- package/dist/tools/navigation.d.ts.map +1 -1
- package/dist/tui.js +18 -14
- package/dist/workflow-hints.d.ts +3 -4
- package/dist/workflow-hints.d.ts.map +1 -1
- package/package.json +11 -11
- package/src/tui/index.tsx +17 -14
- package/src/tui/types/opencode-plugin-tui.d.ts +7 -0
- package/dist/shared/live-server-client.d.ts +0 -81
- package/dist/shared/live-server-client.d.ts.map +0 -1
- package/src/shared/live-server-client.ts +0 -206
package/dist/index.js
CHANGED
|
@@ -12864,6 +12864,208 @@ class BinaryBridge {
|
|
|
12864
12864
|
}
|
|
12865
12865
|
}
|
|
12866
12866
|
}
|
|
12867
|
+
// ../aft-bridge/dist/callgraph-format.js
|
|
12868
|
+
import { homedir as homedir3 } from "node:os";
|
|
12869
|
+
var PLAIN_CALLGRAPH_THEME = {
|
|
12870
|
+
fg: (_role, text) => text
|
|
12871
|
+
};
|
|
12872
|
+
function asRecord(value) {
|
|
12873
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
12874
|
+
return;
|
|
12875
|
+
return value;
|
|
12876
|
+
}
|
|
12877
|
+
function asRecords(value) {
|
|
12878
|
+
return Array.isArray(value) ? value.map(asRecord).filter(Boolean) : [];
|
|
12879
|
+
}
|
|
12880
|
+
function asString(value) {
|
|
12881
|
+
return typeof value === "string" ? value : undefined;
|
|
12882
|
+
}
|
|
12883
|
+
function asNumber(value) {
|
|
12884
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
12885
|
+
}
|
|
12886
|
+
function asBoolean(value) {
|
|
12887
|
+
return typeof value === "boolean" ? value : undefined;
|
|
12888
|
+
}
|
|
12889
|
+
function shortenPath(path2) {
|
|
12890
|
+
const home = homedir3();
|
|
12891
|
+
if (path2.startsWith(home))
|
|
12892
|
+
return `~${path2.slice(home.length)}`;
|
|
12893
|
+
return path2;
|
|
12894
|
+
}
|
|
12895
|
+
function joinNonEmpty(parts, separator = " · ") {
|
|
12896
|
+
return parts.filter((part) => Boolean(part && part.length > 0)).join(separator);
|
|
12897
|
+
}
|
|
12898
|
+
function treeLine(depth, text) {
|
|
12899
|
+
return `${" ".repeat(depth)}${depth === 0 ? "" : "↳ "}${text}`;
|
|
12900
|
+
}
|
|
12901
|
+
function renderCallTreeNode(node, depth, lines, theme) {
|
|
12902
|
+
const name = asString(node.name) ?? "(unknown)";
|
|
12903
|
+
const file = shortenPath(asString(node.file) ?? "(unknown file)");
|
|
12904
|
+
const line = asNumber(node.line);
|
|
12905
|
+
const unresolved = node.resolved === false ? ` ${theme.fg("warning", "[unresolved]")}` : "";
|
|
12906
|
+
const location = line !== undefined ? `[${file}:${line}]` : `[${file}]`;
|
|
12907
|
+
lines.push(treeLine(depth, `${name} ${location}${unresolved}`));
|
|
12908
|
+
asRecords(node.children).forEach((child) => {
|
|
12909
|
+
renderCallTreeNode(child, depth + 1, lines, theme);
|
|
12910
|
+
});
|
|
12911
|
+
}
|
|
12912
|
+
function depthWarning(response, theme, depthField = "depth_limited", truncatedField = "truncated") {
|
|
12913
|
+
const limited = asBoolean(response[depthField]);
|
|
12914
|
+
const truncated = asNumber(response[truncatedField]) ?? 0;
|
|
12915
|
+
if (!limited && truncated === 0)
|
|
12916
|
+
return "";
|
|
12917
|
+
const detail = truncated > 0 ? `, ${truncated} truncated` : "";
|
|
12918
|
+
return theme.fg("warning", `(depth limited${detail})`);
|
|
12919
|
+
}
|
|
12920
|
+
function renderTracePath(path2, index, lines) {
|
|
12921
|
+
lines.push(`Path ${index + 1}`);
|
|
12922
|
+
asRecords(path2.hops).forEach((hop, hopIndex) => {
|
|
12923
|
+
const symbol = asString(hop.symbol) ?? "(unknown)";
|
|
12924
|
+
const file = shortenPath(asString(hop.file) ?? "(unknown file)");
|
|
12925
|
+
const line = asNumber(hop.line);
|
|
12926
|
+
const entry = hop.is_entry_point === true ? " [entry]" : "";
|
|
12927
|
+
lines.push(treeLine(hopIndex + 1, `${symbol}${entry} ${line !== undefined ? `[${file}:${line}]` : `[${file}]`}`));
|
|
12928
|
+
});
|
|
12929
|
+
}
|
|
12930
|
+
function renderCallersGroupLines(group, theme) {
|
|
12931
|
+
const file = shortenPath(asString(group.file) ?? "(unknown file)");
|
|
12932
|
+
const lines = [theme.fg("accent", file)];
|
|
12933
|
+
const callers = asRecords(group.callers);
|
|
12934
|
+
const bySymbol = new Map;
|
|
12935
|
+
for (const caller of callers) {
|
|
12936
|
+
const symbol = asString(caller.symbol) ?? "(unknown)";
|
|
12937
|
+
const line = asNumber(caller.line);
|
|
12938
|
+
const bucket = bySymbol.get(symbol) ?? [];
|
|
12939
|
+
if (line !== undefined)
|
|
12940
|
+
bucket.push(line);
|
|
12941
|
+
bySymbol.set(symbol, bucket);
|
|
12942
|
+
}
|
|
12943
|
+
const symbols = [...bySymbol.keys()].sort((a, b) => a.localeCompare(b));
|
|
12944
|
+
for (const symbol of symbols) {
|
|
12945
|
+
const lineNums = (bySymbol.get(symbol) ?? []).sort((a, b) => a - b);
|
|
12946
|
+
const linePart = lineNums.length > 0 ? lineNums.map(String).join(", ") : "?";
|
|
12947
|
+
lines.push(` ↳ ${symbol}:${linePart}`);
|
|
12948
|
+
}
|
|
12949
|
+
return lines;
|
|
12950
|
+
}
|
|
12951
|
+
function formatCallgraphSections(op, response, theme = PLAIN_CALLGRAPH_THEME) {
|
|
12952
|
+
const record = asRecord(response);
|
|
12953
|
+
if (!record)
|
|
12954
|
+
return [theme.fg("muted", "No navigation result.")];
|
|
12955
|
+
if (op === "call_tree") {
|
|
12956
|
+
const lines = [];
|
|
12957
|
+
renderCallTreeNode(record, 0, lines, theme);
|
|
12958
|
+
const warning = depthWarning(record, theme);
|
|
12959
|
+
if (warning)
|
|
12960
|
+
lines.push(warning);
|
|
12961
|
+
return lines.length > 0 ? lines : [theme.fg("muted", "No call tree available.")];
|
|
12962
|
+
}
|
|
12963
|
+
if (op === "callers") {
|
|
12964
|
+
const groups = asRecords(record.callers);
|
|
12965
|
+
const warning = depthWarning(record, theme);
|
|
12966
|
+
const total = asNumber(record.total_callers) ?? 0;
|
|
12967
|
+
const sections2 = [
|
|
12968
|
+
joinNonEmpty([
|
|
12969
|
+
theme.fg("success", `${total} caller${total === 1 ? "" : "s"}`),
|
|
12970
|
+
theme.fg("muted", `${groups.length} file group${groups.length === 1 ? "" : "s"}`),
|
|
12971
|
+
warning
|
|
12972
|
+
])
|
|
12973
|
+
];
|
|
12974
|
+
groups.forEach((group) => {
|
|
12975
|
+
sections2.push(renderCallersGroupLines(group, theme).join(`
|
|
12976
|
+
`));
|
|
12977
|
+
});
|
|
12978
|
+
return sections2;
|
|
12979
|
+
}
|
|
12980
|
+
if (op === "trace_to_symbol") {
|
|
12981
|
+
const path2 = asRecords(record.path);
|
|
12982
|
+
const complete = asBoolean(record.complete);
|
|
12983
|
+
const reason = asString(record.reason);
|
|
12984
|
+
if (path2.length === 0) {
|
|
12985
|
+
const prefix = complete === false ? theme.fg("warning", "No complete path") : theme.fg("muted", "No path");
|
|
12986
|
+
return [`${prefix}${reason ? ` (${reason})` : ""}`];
|
|
12987
|
+
}
|
|
12988
|
+
const lines = [theme.fg("success", `${path2.length} hop${path2.length === 1 ? "" : "s"}`)];
|
|
12989
|
+
path2.forEach((hop, index) => {
|
|
12990
|
+
const symbol = asString(hop.symbol) ?? "(unknown)";
|
|
12991
|
+
const file = shortenPath(asString(hop.file) ?? "(unknown file)");
|
|
12992
|
+
const line = asNumber(hop.line);
|
|
12993
|
+
lines.push(treeLine(index + 1, `${symbol} ${line !== undefined ? `[${file}:${line}]` : `[${file}]`}`));
|
|
12994
|
+
});
|
|
12995
|
+
return lines;
|
|
12996
|
+
}
|
|
12997
|
+
if (op === "trace_to") {
|
|
12998
|
+
const paths = asRecords(record.paths);
|
|
12999
|
+
const warning = depthWarning(record, theme, "max_depth_reached", "truncated_paths");
|
|
13000
|
+
const totalPaths = asNumber(record.total_paths) ?? paths.length;
|
|
13001
|
+
const entryPoints = asNumber(record.entry_points_found) ?? 0;
|
|
13002
|
+
const sections2 = [
|
|
13003
|
+
joinNonEmpty([
|
|
13004
|
+
theme.fg("success", `${totalPaths} path${totalPaths === 1 ? "" : "s"}`),
|
|
13005
|
+
theme.fg("muted", `${entryPoints} entry point${entryPoints === 1 ? "" : "s"}`),
|
|
13006
|
+
warning
|
|
13007
|
+
])
|
|
13008
|
+
];
|
|
13009
|
+
if (paths.length === 0)
|
|
13010
|
+
sections2.push(theme.fg("muted", "No entry paths found."));
|
|
13011
|
+
paths.forEach((path2, index) => {
|
|
13012
|
+
const lines = [];
|
|
13013
|
+
renderTracePath(path2, index, lines);
|
|
13014
|
+
sections2.push(lines.join(`
|
|
13015
|
+
`));
|
|
13016
|
+
});
|
|
13017
|
+
return sections2;
|
|
13018
|
+
}
|
|
13019
|
+
if (op === "impact") {
|
|
13020
|
+
const callers = asRecords(record.callers);
|
|
13021
|
+
const warning = depthWarning(record, theme);
|
|
13022
|
+
const totalAffected = asNumber(record.total_affected) ?? callers.length;
|
|
13023
|
+
const affectedFiles = asNumber(record.affected_files) ?? 0;
|
|
13024
|
+
const sections2 = [
|
|
13025
|
+
joinNonEmpty([
|
|
13026
|
+
theme.fg("warning", `${totalAffected} affected call site${totalAffected === 1 ? "" : "s"}`),
|
|
13027
|
+
theme.fg("muted", `${affectedFiles} file${affectedFiles === 1 ? "" : "s"}`),
|
|
13028
|
+
warning
|
|
13029
|
+
])
|
|
13030
|
+
];
|
|
13031
|
+
if (callers.length === 0)
|
|
13032
|
+
sections2.push(theme.fg("muted", "No impacted callers found."));
|
|
13033
|
+
callers.forEach((caller) => {
|
|
13034
|
+
const file = shortenPath(asString(caller.caller_file) ?? "(unknown file)");
|
|
13035
|
+
const symbol = asString(caller.caller_symbol) ?? "(unknown)";
|
|
13036
|
+
const line = asNumber(caller.line) ?? 0;
|
|
13037
|
+
const entry = caller.is_entry_point === true ? ` ${theme.fg("warning", "[entry]")}` : "";
|
|
13038
|
+
const expression = asString(caller.call_expression);
|
|
13039
|
+
const params = Array.isArray(caller.parameters) ? caller.parameters.map(String).join(", ") : "";
|
|
13040
|
+
sections2.push([
|
|
13041
|
+
`${theme.fg("accent", file)}:${line}`,
|
|
13042
|
+
` ↳ ${symbol}${entry}`,
|
|
13043
|
+
expression ? ` ${theme.fg("muted", expression)}` : undefined,
|
|
13044
|
+
params ? ` ${theme.fg("muted", `params: ${params}`)}` : undefined
|
|
13045
|
+
].filter(Boolean).join(`
|
|
13046
|
+
`));
|
|
13047
|
+
});
|
|
13048
|
+
return sections2;
|
|
13049
|
+
}
|
|
13050
|
+
const hops = asRecords(record.hops);
|
|
13051
|
+
const sections = [
|
|
13052
|
+
joinNonEmpty([
|
|
13053
|
+
theme.fg("success", `${hops.length} hop${hops.length === 1 ? "" : "s"}`),
|
|
13054
|
+
asBoolean(record.depth_limited) ? theme.fg("warning", "(depth limited)") : undefined
|
|
13055
|
+
])
|
|
13056
|
+
];
|
|
13057
|
+
if (hops.length === 0)
|
|
13058
|
+
sections.push(theme.fg("muted", "No data-flow hops found."));
|
|
13059
|
+
hops.forEach((hop, index) => {
|
|
13060
|
+
const file = shortenPath(asString(hop.file) ?? "(unknown file)");
|
|
13061
|
+
const symbol = asString(hop.symbol) ?? "(unknown)";
|
|
13062
|
+
const variable = asString(hop.variable) ?? "(unknown)";
|
|
13063
|
+
const line = asNumber(hop.line) ?? 0;
|
|
13064
|
+
const approximate = hop.approximate === true ? ` ${theme.fg("warning", "[approx]")}` : "";
|
|
13065
|
+
sections.push(treeLine(index, `${variable} ${theme.fg("muted", `${asString(hop.flow_type) ?? "flow"}`)} ${symbol} [${file}:${line}]${approximate}`));
|
|
13066
|
+
});
|
|
13067
|
+
return sections;
|
|
13068
|
+
}
|
|
12867
13069
|
// ../aft-bridge/dist/coerce.js
|
|
12868
13070
|
function coerceStringArray(value) {
|
|
12869
13071
|
if (Array.isArray(value)) {
|
|
@@ -12900,7 +13102,7 @@ function isEmptyParam(value) {
|
|
|
12900
13102
|
import { spawnSync } from "node:child_process";
|
|
12901
13103
|
import { createHash, randomUUID } from "node:crypto";
|
|
12902
13104
|
import { chmodSync, closeSync, copyFileSync, createWriteStream, existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, statSync, unlinkSync, writeSync } from "node:fs";
|
|
12903
|
-
import { homedir as
|
|
13105
|
+
import { homedir as homedir4 } from "node:os";
|
|
12904
13106
|
import { join as join3 } from "node:path";
|
|
12905
13107
|
import { Readable } from "node:stream";
|
|
12906
13108
|
import { pipeline } from "node:stream/promises";
|
|
@@ -12958,10 +13160,10 @@ function isExpectedCachedBinary(binaryPath, tag) {
|
|
|
12958
13160
|
function getCacheDir() {
|
|
12959
13161
|
if (process.platform === "win32") {
|
|
12960
13162
|
const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
|
|
12961
|
-
const base2 = localAppData || join3(
|
|
13163
|
+
const base2 = localAppData || join3(homedir4(), "AppData", "Local");
|
|
12962
13164
|
return join3(base2, "aft", "bin");
|
|
12963
13165
|
}
|
|
12964
|
-
const base = process.env.XDG_CACHE_HOME || join3(
|
|
13166
|
+
const base = process.env.XDG_CACHE_HOME || join3(homedir4(), ".cache");
|
|
12965
13167
|
return join3(base, "aft", "bin");
|
|
12966
13168
|
}
|
|
12967
13169
|
function getBinaryName() {
|
|
@@ -13234,7 +13436,7 @@ function stripJsoncSymbols(value) {
|
|
|
13234
13436
|
// ../aft-bridge/dist/migration.js
|
|
13235
13437
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
13236
13438
|
import { existsSync as existsSync4, mkdirSync as mkdirSync4 } from "node:fs";
|
|
13237
|
-
import { homedir as
|
|
13439
|
+
import { homedir as homedir6, tmpdir } from "node:os";
|
|
13238
13440
|
import { dirname as dirname2, join as join6 } from "node:path";
|
|
13239
13441
|
|
|
13240
13442
|
// ../aft-bridge/dist/paths.js
|
|
@@ -13291,7 +13493,7 @@ function markAnnouncementSeen(storageRoot, harness, currentVersion) {
|
|
|
13291
13493
|
import { execSync } from "node:child_process";
|
|
13292
13494
|
import { chmodSync as chmodSync2, closeSync as closeSync2, copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync3, openSync as openSync2, readSync, renameSync as renameSync3, unlinkSync as unlinkSync2 } from "node:fs";
|
|
13293
13495
|
import { createRequire as createRequire2 } from "node:module";
|
|
13294
|
-
import { homedir as
|
|
13496
|
+
import { homedir as homedir5 } from "node:os";
|
|
13295
13497
|
import { join as join5 } from "node:path";
|
|
13296
13498
|
var ensureBinaryForResolver = ensureBinary;
|
|
13297
13499
|
function copyToVersionedCache(npmBinaryPath, knownVersion) {
|
|
@@ -13333,7 +13535,7 @@ function normalizeBareVersion(version) {
|
|
|
13333
13535
|
return version.startsWith("v") ? version.slice(1) : version;
|
|
13334
13536
|
}
|
|
13335
13537
|
function homeDirFromEnv(env) {
|
|
13336
|
-
return (process.platform === "win32" ? env.USERPROFILE || env.HOME : env.HOME) ||
|
|
13538
|
+
return (process.platform === "win32" ? env.USERPROFILE || env.HOME : env.HOME) || homedir5();
|
|
13337
13539
|
}
|
|
13338
13540
|
function cacheDirFromEnv(env) {
|
|
13339
13541
|
if (process.platform === "win32") {
|
|
@@ -13515,8 +13717,8 @@ function dataHome() {
|
|
|
13515
13717
|
}
|
|
13516
13718
|
function homeDir() {
|
|
13517
13719
|
if (process.platform === "win32")
|
|
13518
|
-
return process.env.USERPROFILE || process.env.HOME ||
|
|
13519
|
-
return process.env.HOME ||
|
|
13720
|
+
return process.env.USERPROFILE || process.env.HOME || homedir6();
|
|
13721
|
+
return process.env.HOME || homedir6();
|
|
13520
13722
|
}
|
|
13521
13723
|
function resolveLegacyStorageRoot(harness) {
|
|
13522
13724
|
if (harness === "pi")
|
|
@@ -13606,13 +13808,13 @@ async function ensureStorageMigrated(opts) {
|
|
|
13606
13808
|
}
|
|
13607
13809
|
// ../aft-bridge/dist/npm-resolver.js
|
|
13608
13810
|
import { readdirSync, statSync as statSync2 } from "node:fs";
|
|
13609
|
-
import { homedir as
|
|
13811
|
+
import { homedir as homedir7 } from "node:os";
|
|
13610
13812
|
import { delimiter, dirname as dirname3, isAbsolute as isAbsolute2, join as join7 } from "node:path";
|
|
13611
13813
|
function defaultDeps() {
|
|
13612
13814
|
return {
|
|
13613
13815
|
platform: process.platform,
|
|
13614
13816
|
env: process.env,
|
|
13615
|
-
home:
|
|
13817
|
+
home: homedir7(),
|
|
13616
13818
|
execPath: process.execPath
|
|
13617
13819
|
};
|
|
13618
13820
|
}
|
|
@@ -15379,9 +15581,6 @@ function write(level, message, data, sessionId) {
|
|
|
15379
15581
|
function log2(message, data) {
|
|
15380
15582
|
write("INFO", message, data);
|
|
15381
15583
|
}
|
|
15382
|
-
function debug(message, data) {
|
|
15383
|
-
write("DEBUG", message, data);
|
|
15384
|
-
}
|
|
15385
15584
|
function warn2(message, data) {
|
|
15386
15585
|
write("WARN", message, data);
|
|
15387
15586
|
}
|
|
@@ -15391,9 +15590,6 @@ function error2(message, data) {
|
|
|
15391
15590
|
function sessionLog(sessionId, message, data) {
|
|
15392
15591
|
write("INFO", message, data, sessionId);
|
|
15393
15592
|
}
|
|
15394
|
-
function sessionDebug(sessionId, message, data) {
|
|
15395
|
-
write("DEBUG", message, data, sessionId);
|
|
15396
|
-
}
|
|
15397
15593
|
function sessionWarn(sessionId, message, data) {
|
|
15398
15594
|
write("WARN", message, data, sessionId);
|
|
15399
15595
|
}
|
|
@@ -15487,84 +15683,6 @@ async function resolvePromptContext(client, sessionId) {
|
|
|
15487
15683
|
return result;
|
|
15488
15684
|
}
|
|
15489
15685
|
|
|
15490
|
-
// src/shared/live-server-client.ts
|
|
15491
|
-
import { createOpencodeClient } from "@opencode-ai/sdk";
|
|
15492
|
-
var clientCache = new Map;
|
|
15493
|
-
function cacheKey(serverUrl, directory) {
|
|
15494
|
-
return `${serverUrl}|${directory}`;
|
|
15495
|
-
}
|
|
15496
|
-
function normalizeServerUrl(serverUrl) {
|
|
15497
|
-
try {
|
|
15498
|
-
return new URL(serverUrl).toString();
|
|
15499
|
-
} catch {
|
|
15500
|
-
return serverUrl;
|
|
15501
|
-
}
|
|
15502
|
-
}
|
|
15503
|
-
function serverAuthHeaders() {
|
|
15504
|
-
const password = process.env.OPENCODE_SERVER_PASSWORD;
|
|
15505
|
-
if (!password)
|
|
15506
|
-
return;
|
|
15507
|
-
const username = process.env.OPENCODE_SERVER_USERNAME ?? "opencode";
|
|
15508
|
-
return {
|
|
15509
|
-
Authorization: `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`
|
|
15510
|
-
};
|
|
15511
|
-
}
|
|
15512
|
-
function getLiveServerClient(serverUrl, directory) {
|
|
15513
|
-
const key = cacheKey(serverUrl, directory);
|
|
15514
|
-
const cached = clientCache.get(key);
|
|
15515
|
-
if (cached)
|
|
15516
|
-
return cached;
|
|
15517
|
-
const client = createOpencodeClient({
|
|
15518
|
-
baseUrl: serverUrl,
|
|
15519
|
-
directory,
|
|
15520
|
-
headers: serverAuthHeaders(),
|
|
15521
|
-
fetch: globalThis.fetch
|
|
15522
|
-
});
|
|
15523
|
-
clientCache.set(key, client);
|
|
15524
|
-
return client;
|
|
15525
|
-
}
|
|
15526
|
-
var liveServerWakeAvailableByServerUrl = new Map;
|
|
15527
|
-
var legacyLiveServerWakeAvailable = false;
|
|
15528
|
-
async function probeServerReachable(serverUrl, timeoutMs = 1500) {
|
|
15529
|
-
if (!serverUrl)
|
|
15530
|
-
return false;
|
|
15531
|
-
const normalizedServerUrl = normalizeServerUrl(serverUrl);
|
|
15532
|
-
const controller = new AbortController;
|
|
15533
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
15534
|
-
let reachable = false;
|
|
15535
|
-
try {
|
|
15536
|
-
const probeUrl = new URL("/session", serverUrl).toString();
|
|
15537
|
-
const res = await globalThis.fetch(probeUrl, {
|
|
15538
|
-
method: "GET",
|
|
15539
|
-
headers: serverAuthHeaders(),
|
|
15540
|
-
signal: controller.signal
|
|
15541
|
-
});
|
|
15542
|
-
reachable = res.ok || res.status === 401 || res.status === 403;
|
|
15543
|
-
} catch {
|
|
15544
|
-
reachable = false;
|
|
15545
|
-
} finally {
|
|
15546
|
-
clearTimeout(timer);
|
|
15547
|
-
liveServerWakeAvailableByServerUrl.set(normalizedServerUrl, reachable);
|
|
15548
|
-
}
|
|
15549
|
-
return reachable;
|
|
15550
|
-
}
|
|
15551
|
-
function setLiveServerWakeAvailable(serverUrlOrAvailable, available) {
|
|
15552
|
-
if (typeof serverUrlOrAvailable === "boolean") {
|
|
15553
|
-
legacyLiveServerWakeAvailable = serverUrlOrAvailable;
|
|
15554
|
-
return;
|
|
15555
|
-
}
|
|
15556
|
-
if (!serverUrlOrAvailable) {
|
|
15557
|
-
legacyLiveServerWakeAvailable = available ?? false;
|
|
15558
|
-
return;
|
|
15559
|
-
}
|
|
15560
|
-
liveServerWakeAvailableByServerUrl.set(normalizeServerUrl(serverUrlOrAvailable), available ?? false);
|
|
15561
|
-
}
|
|
15562
|
-
function useLiveServerWake(serverUrl) {
|
|
15563
|
-
if (!serverUrl)
|
|
15564
|
-
return legacyLiveServerWakeAvailable;
|
|
15565
|
-
return liveServerWakeAvailableByServerUrl.get(normalizeServerUrl(serverUrl)) ?? false;
|
|
15566
|
-
}
|
|
15567
|
-
|
|
15568
15686
|
// src/bg-notifications.ts
|
|
15569
15687
|
function hashReminder(text) {
|
|
15570
15688
|
return createHash3("sha256").update(text).digest("hex").slice(0, 16);
|
|
@@ -15791,15 +15909,15 @@ async function triggerWakeIfPending(drainContext, skipDrain, includeDeferredComp
|
|
|
15791
15909
|
directory: drainContext.directory,
|
|
15792
15910
|
attempt: state.wakeRetryAttempts + 1
|
|
15793
15911
|
});
|
|
15794
|
-
throw new Error("
|
|
15912
|
+
throw new Error("in-process wake client unavailable: input.client absent");
|
|
15795
15913
|
}
|
|
15796
15914
|
return drainContext.client;
|
|
15797
15915
|
};
|
|
15798
|
-
const sendPrompt = async (
|
|
15799
|
-
if (typeof
|
|
15800
|
-
throw new Error(
|
|
15916
|
+
const sendPrompt = async (client2) => {
|
|
15917
|
+
if (typeof client2.session?.promptAsync !== "function") {
|
|
15918
|
+
throw new Error("wake client.session.promptAsync is unavailable");
|
|
15801
15919
|
}
|
|
15802
|
-
const promptContext = await resolvePromptContext(
|
|
15920
|
+
const promptContext = await resolvePromptContext(client2, drainContext.sessionID);
|
|
15803
15921
|
const body = {
|
|
15804
15922
|
noReply: false,
|
|
15805
15923
|
parts: [{ type: "text", text: reminder, synthetic: true }]
|
|
@@ -15822,7 +15940,6 @@ async function triggerWakeIfPending(drainContext, skipDrain, includeDeferredComp
|
|
|
15822
15940
|
directory: drainContext.directory,
|
|
15823
15941
|
reminder_sha256: hashReminder(reminder),
|
|
15824
15942
|
reminder_chars: reminder.length,
|
|
15825
|
-
wake_client_path: clientPath,
|
|
15826
15943
|
prompt_context: promptContext ? {
|
|
15827
15944
|
agent: promptContext.agent,
|
|
15828
15945
|
model: promptContext.model ? {
|
|
@@ -15837,18 +15954,16 @@ async function triggerWakeIfPending(drainContext, skipDrain, includeDeferredComp
|
|
|
15837
15954
|
...wakeMeta
|
|
15838
15955
|
});
|
|
15839
15956
|
try {
|
|
15840
|
-
await
|
|
15957
|
+
await client2.session.promptAsync({
|
|
15841
15958
|
path: { id: drainContext.sessionID },
|
|
15842
15959
|
body
|
|
15843
15960
|
});
|
|
15844
15961
|
} catch (err) {
|
|
15845
|
-
|
|
15846
|
-
logPromptError(drainContext.sessionID, `${LOG_PREFIX} wake promptAsync error`, {
|
|
15962
|
+
sessionWarn(drainContext.sessionID, `${LOG_PREFIX} wake promptAsync error`, {
|
|
15847
15963
|
event: "bash_completion_wake_prompt_async_error",
|
|
15848
15964
|
delivery_id: deliveryID2,
|
|
15849
15965
|
attempt: state.wakeRetryAttempts + 1,
|
|
15850
15966
|
task_ids: taskIDs,
|
|
15851
|
-
wake_client_path: clientPath,
|
|
15852
15967
|
error: err instanceof Error ? err.message : String(err)
|
|
15853
15968
|
});
|
|
15854
15969
|
throw err;
|
|
@@ -15857,38 +15972,12 @@ async function triggerWakeIfPending(drainContext, skipDrain, includeDeferredComp
|
|
|
15857
15972
|
event: "bash_completion_wake_prompt_async_ok",
|
|
15858
15973
|
delivery_id: deliveryID2,
|
|
15859
15974
|
attempt: state.wakeRetryAttempts + 1,
|
|
15860
|
-
task_ids: taskIDs
|
|
15861
|
-
wake_client_path: clientPath
|
|
15975
|
+
task_ids: taskIDs
|
|
15862
15976
|
});
|
|
15863
15977
|
return deliveryID2;
|
|
15864
15978
|
};
|
|
15865
|
-
|
|
15866
|
-
|
|
15867
|
-
const liveClient = getLiveServerClient(drainContext.serverUrl, drainContext.directory);
|
|
15868
|
-
const deliveryID2 = await sendPrompt(liveClient, "live-server");
|
|
15869
|
-
await ackCompletions(drainContext, deliveredCompletions, deliveryID2);
|
|
15870
|
-
return;
|
|
15871
|
-
} catch (err) {
|
|
15872
|
-
setLiveServerWakeAvailable(drainContext.serverUrl, false);
|
|
15873
|
-
sessionDebug(drainContext.sessionID, `${LOG_PREFIX} live-server wake failed; falling back`, {
|
|
15874
|
-
event: "bash_completion_wake_live_server_fallback",
|
|
15875
|
-
task_ids: taskIDs,
|
|
15876
|
-
directory: drainContext.directory,
|
|
15877
|
-
server_url: drainContext.serverUrl,
|
|
15878
|
-
attempt: state.wakeRetryAttempts + 1,
|
|
15879
|
-
error: err instanceof Error ? err.message : String(err)
|
|
15880
|
-
});
|
|
15881
|
-
const fallbackClient2 = getInProcessClient();
|
|
15882
|
-
const deliveryID2 = await sendPrompt(fallbackClient2, "in-process-fallback");
|
|
15883
|
-
state.retryDelayMs = null;
|
|
15884
|
-
state.wakeRetryAttempts = 0;
|
|
15885
|
-
state.wakeHardStopped = false;
|
|
15886
|
-
await ackCompletions(drainContext, deliveredCompletions, deliveryID2);
|
|
15887
|
-
return;
|
|
15888
|
-
}
|
|
15889
|
-
}
|
|
15890
|
-
const fallbackClient = getInProcessClient();
|
|
15891
|
-
const deliveryID = await sendPrompt(fallbackClient, "in-process-fallback");
|
|
15979
|
+
const client = getInProcessClient();
|
|
15980
|
+
const deliveryID = await sendPrompt(client);
|
|
15892
15981
|
await ackCompletions(drainContext, deliveredCompletions, deliveryID);
|
|
15893
15982
|
}, (err, hardStopped) => {
|
|
15894
15983
|
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)}`);
|
|
@@ -16271,7 +16360,7 @@ function formatDuration(completion) {
|
|
|
16271
16360
|
|
|
16272
16361
|
// src/config.ts
|
|
16273
16362
|
import { existsSync as existsSync6, readFileSync as readFileSync4, renameSync as renameSync4, unlinkSync as unlinkSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
16274
|
-
import { homedir as
|
|
16363
|
+
import { homedir as homedir8 } from "node:os";
|
|
16275
16364
|
import { join as join10 } from "node:path";
|
|
16276
16365
|
var import_comment_json = __toESM(require_src2(), 1);
|
|
16277
16366
|
|
|
@@ -29922,6 +30011,8 @@ var AftConfigSchema = exports_external.object({
|
|
|
29922
30011
|
restrict_to_project_root: exports_external.boolean().optional(),
|
|
29923
30012
|
search_index: exports_external.boolean().optional(),
|
|
29924
30013
|
semantic_search: exports_external.boolean().optional(),
|
|
30014
|
+
callgraph_store: exports_external.boolean().optional(),
|
|
30015
|
+
callgraph_chunk_size: exports_external.number().optional(),
|
|
29925
30016
|
inspect: InspectConfigSchema.optional(),
|
|
29926
30017
|
bash: BashConfigSchema.optional(),
|
|
29927
30018
|
experimental: ExperimentalConfigSchema.optional(),
|
|
@@ -30000,6 +30091,10 @@ function resolveProjectOverridesForConfigure(config2) {
|
|
|
30000
30091
|
overrides.search_index = config2.search_index;
|
|
30001
30092
|
if (config2.semantic_search !== undefined)
|
|
30002
30093
|
overrides.semantic_search = config2.semantic_search;
|
|
30094
|
+
if (config2.callgraph_store !== undefined)
|
|
30095
|
+
overrides.callgraph_store = config2.callgraph_store;
|
|
30096
|
+
if (config2.callgraph_chunk_size !== undefined)
|
|
30097
|
+
overrides.callgraph_chunk_size = config2.callgraph_chunk_size;
|
|
30003
30098
|
Object.assign(overrides, resolveExperimentalConfigForConfigure(config2));
|
|
30004
30099
|
Object.assign(overrides, resolveLspConfigForConfigure(config2));
|
|
30005
30100
|
if (config2.semantic !== undefined)
|
|
@@ -30274,6 +30369,17 @@ function parseConfigPartially(rawConfig) {
|
|
|
30274
30369
|
}
|
|
30275
30370
|
return partialConfig;
|
|
30276
30371
|
}
|
|
30372
|
+
var configLoadErrors = [];
|
|
30373
|
+
function getConfigLoadErrors() {
|
|
30374
|
+
return configLoadErrors;
|
|
30375
|
+
}
|
|
30376
|
+
function formatConfigParseFailureMessage(configPath, errorMessage) {
|
|
30377
|
+
return `AFT config at ${configPath} failed to parse and was ignored (running on defaults): ${errorMessage}. ` + "Fix the syntax or run `npx @cortexkit/aft doctor`.";
|
|
30378
|
+
}
|
|
30379
|
+
function recordConfigParseFailure(configPath, errorMessage) {
|
|
30380
|
+
configLoadErrors.push({ path: configPath, message: errorMessage });
|
|
30381
|
+
warn2(formatConfigParseFailureMessage(configPath, errorMessage));
|
|
30382
|
+
}
|
|
30277
30383
|
function loadConfigFromPath(configPath) {
|
|
30278
30384
|
try {
|
|
30279
30385
|
if (!existsSync6(configPath)) {
|
|
@@ -30294,6 +30400,7 @@ function loadConfigFromPath(configPath) {
|
|
|
30294
30400
|
} catch (err) {
|
|
30295
30401
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
30296
30402
|
error2(`Error loading config from ${configPath}: ${errorMsg}`);
|
|
30403
|
+
recordConfigParseFailure(configPath, errorMsg);
|
|
30297
30404
|
return null;
|
|
30298
30405
|
}
|
|
30299
30406
|
}
|
|
@@ -30417,6 +30524,8 @@ var PROJECT_SAFE_TOP_LEVEL_FIELDS = new Set([
|
|
|
30417
30524
|
"configure_warnings_delivery",
|
|
30418
30525
|
"search_index",
|
|
30419
30526
|
"semantic_search",
|
|
30527
|
+
"callgraph_store",
|
|
30528
|
+
"callgraph_chunk_size",
|
|
30420
30529
|
"inspect",
|
|
30421
30530
|
"experimental",
|
|
30422
30531
|
"bash"
|
|
@@ -30484,10 +30593,11 @@ function getOpenCodeConfigDir() {
|
|
|
30484
30593
|
if (envDir) {
|
|
30485
30594
|
return envDir;
|
|
30486
30595
|
}
|
|
30487
|
-
const xdgConfig = process.env.XDG_CONFIG_HOME || join10(
|
|
30596
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME || join10(homedir8(), ".config");
|
|
30488
30597
|
return join10(xdgConfig, "opencode");
|
|
30489
30598
|
}
|
|
30490
30599
|
function loadAftConfig(projectDirectory) {
|
|
30600
|
+
configLoadErrors = [];
|
|
30491
30601
|
const configDir = getOpenCodeConfigDir();
|
|
30492
30602
|
const userBasePath = join10(configDir, "aft");
|
|
30493
30603
|
migrateAftConfigFile(`${userBasePath}.jsonc`);
|
|
@@ -30520,7 +30630,7 @@ function loadAftConfig(projectDirectory) {
|
|
|
30520
30630
|
|
|
30521
30631
|
// src/notifications.ts
|
|
30522
30632
|
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
|
|
30523
|
-
import { homedir as
|
|
30633
|
+
import { homedir as homedir9, platform } from "node:os";
|
|
30524
30634
|
import { join as join11 } from "node:path";
|
|
30525
30635
|
function isTuiMode() {
|
|
30526
30636
|
return process.env.OPENCODE_CLIENT === "cli";
|
|
@@ -30542,7 +30652,7 @@ var WARNING_MARKER = `${AFT_MARKER} ⚠️`;
|
|
|
30542
30652
|
var STATUS_MARKER = `${AFT_MARKER} ✅`;
|
|
30543
30653
|
function getDesktopStatePath() {
|
|
30544
30654
|
const os3 = platform();
|
|
30545
|
-
const home =
|
|
30655
|
+
const home = homedir9();
|
|
30546
30656
|
if (os3 === "darwin") {
|
|
30547
30657
|
return join11(home, "Library", "Application Support", "ai.opencode.desktop", "opencode.global.dat");
|
|
30548
30658
|
}
|
|
@@ -30821,6 +30931,8 @@ function warningTitle(warning) {
|
|
|
30821
30931
|
return "Checker is not installed";
|
|
30822
30932
|
case "lsp_binary_missing":
|
|
30823
30933
|
return "LSP binary is missing";
|
|
30934
|
+
case "config_parse_failed":
|
|
30935
|
+
return "Config failed to parse";
|
|
30824
30936
|
}
|
|
30825
30937
|
}
|
|
30826
30938
|
function formatConfigureWarningLine(warning) {
|
|
@@ -30942,12 +31054,36 @@ async function cleanupWarnings(opts) {
|
|
|
30942
31054
|
|
|
30943
31055
|
// src/configure-warnings.ts
|
|
30944
31056
|
var pendingEagerWarnings = new Map;
|
|
31057
|
+
var pendingConfigParseWarnings = new Map;
|
|
30945
31058
|
var pendingBySession = new Map;
|
|
30946
31059
|
function isConfigureWarning(value) {
|
|
30947
31060
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
30948
31061
|
return false;
|
|
30949
31062
|
const warning = value;
|
|
30950
|
-
return (warning.kind === "formatter_not_installed" || warning.kind === "checker_not_installed" || warning.kind === "lsp_binary_missing") && typeof warning.hint === "string";
|
|
31063
|
+
return (warning.kind === "formatter_not_installed" || warning.kind === "checker_not_installed" || warning.kind === "lsp_binary_missing" || warning.kind === "config_parse_failed") && typeof warning.hint === "string";
|
|
31064
|
+
}
|
|
31065
|
+
function configParseWarningsFromErrors(errors3) {
|
|
31066
|
+
return errors3.map((entry) => ({
|
|
31067
|
+
kind: "config_parse_failed",
|
|
31068
|
+
hint: formatConfigParseFailureMessage(entry.path, entry.message)
|
|
31069
|
+
}));
|
|
31070
|
+
}
|
|
31071
|
+
function enqueueConfigParseWarnings(projectRoot, errors3) {
|
|
31072
|
+
if (!projectRoot || errors3.length === 0)
|
|
31073
|
+
return;
|
|
31074
|
+
const incoming = configParseWarningsFromErrors(errors3);
|
|
31075
|
+
const existing = pendingConfigParseWarnings.get(projectRoot) ?? [];
|
|
31076
|
+
for (const warning of incoming) {
|
|
31077
|
+
if (!existing.some((item) => item.hint === warning.hint)) {
|
|
31078
|
+
existing.push(warning);
|
|
31079
|
+
}
|
|
31080
|
+
}
|
|
31081
|
+
pendingConfigParseWarnings.set(projectRoot, existing);
|
|
31082
|
+
}
|
|
31083
|
+
function drainPendingConfigParseWarnings(projectRoot) {
|
|
31084
|
+
const pending = pendingConfigParseWarnings.get(projectRoot) ?? [];
|
|
31085
|
+
pendingConfigParseWarnings.delete(projectRoot);
|
|
31086
|
+
return pending;
|
|
30951
31087
|
}
|
|
30952
31088
|
function coerceConfigureWarnings(warnings) {
|
|
30953
31089
|
return warnings.filter(isConfigureWarning);
|
|
@@ -30958,7 +31094,10 @@ function drainPendingEagerWarnings(projectRoot) {
|
|
|
30958
31094
|
return pending;
|
|
30959
31095
|
}
|
|
30960
31096
|
function enqueueConfigureWarningsForSession(context) {
|
|
30961
|
-
const validWarnings =
|
|
31097
|
+
const validWarnings = [
|
|
31098
|
+
...drainPendingConfigParseWarnings(context.projectRoot),
|
|
31099
|
+
...coerceConfigureWarnings(context.warnings)
|
|
31100
|
+
];
|
|
30962
31101
|
if (!context.sessionId) {
|
|
30963
31102
|
if (validWarnings.length === 0)
|
|
30964
31103
|
return;
|
|
@@ -31028,27 +31167,27 @@ var import_comment_json3 = __toESM(require_src2(), 1);
|
|
|
31028
31167
|
// src/hooks/auto-update-checker/checker.ts
|
|
31029
31168
|
var import_comment_json2 = __toESM(require_src2(), 1);
|
|
31030
31169
|
import { existsSync as existsSync8, readFileSync as readFileSync6, statSync as statSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
31031
|
-
import { homedir as
|
|
31170
|
+
import { homedir as homedir11 } from "node:os";
|
|
31032
31171
|
import { dirname as dirname5, isAbsolute as isAbsolute4, join as join13, resolve as resolve3 } from "node:path";
|
|
31033
31172
|
import { fileURLToPath } from "node:url";
|
|
31034
31173
|
|
|
31035
31174
|
// src/hooks/auto-update-checker/constants.ts
|
|
31036
|
-
import { homedir as
|
|
31175
|
+
import { homedir as homedir10, platform as platform2 } from "node:os";
|
|
31037
31176
|
import { join as join12 } from "node:path";
|
|
31038
31177
|
var PACKAGE_NAME = "@cortexkit/aft-opencode";
|
|
31039
31178
|
var NPM_REGISTRY_URL = "https://registry.npmjs.org";
|
|
31040
31179
|
var NPM_FETCH_TIMEOUT = 1e4;
|
|
31041
31180
|
function getOpenCodeCacheRoot() {
|
|
31042
31181
|
if (platform2() === "win32") {
|
|
31043
|
-
return join12(process.env.LOCALAPPDATA ??
|
|
31182
|
+
return join12(process.env.LOCALAPPDATA ?? homedir10(), "opencode");
|
|
31044
31183
|
}
|
|
31045
|
-
return join12(
|
|
31184
|
+
return join12(homedir10(), ".cache", "opencode");
|
|
31046
31185
|
}
|
|
31047
31186
|
function getOpenCodeConfigRoot() {
|
|
31048
31187
|
if (platform2() === "win32") {
|
|
31049
|
-
return join12(process.env.APPDATA ?? join12(
|
|
31188
|
+
return join12(process.env.APPDATA ?? join12(homedir10(), "AppData", "Roaming"), "opencode");
|
|
31050
31189
|
}
|
|
31051
|
-
return join12(process.env.XDG_CONFIG_HOME ?? join12(
|
|
31190
|
+
return join12(process.env.XDG_CONFIG_HOME ?? join12(homedir10(), ".config"), "opencode");
|
|
31052
31191
|
}
|
|
31053
31192
|
var CACHE_DIR = join12(getOpenCodeCacheRoot(), "packages");
|
|
31054
31193
|
var USER_OPENCODE_CONFIG = join12(getOpenCodeConfigRoot(), "opencode.json");
|
|
@@ -31227,7 +31366,7 @@ function getCachedVersion(spec) {
|
|
|
31227
31366
|
getCurrentRuntimePackageJsonPath(),
|
|
31228
31367
|
spec ? getSpecCachePackageJsonPath(spec) : null,
|
|
31229
31368
|
getSpecCachePackageJsonPath(`${PACKAGE_NAME}@latest`),
|
|
31230
|
-
join13(
|
|
31369
|
+
join13(homedir11(), ".cache", "opencode", "node_modules", PACKAGE_NAME, "package.json")
|
|
31231
31370
|
].filter(isString);
|
|
31232
31371
|
for (const packageJsonPath of candidates) {
|
|
31233
31372
|
try {
|
|
@@ -31730,7 +31869,7 @@ import {
|
|
|
31730
31869
|
unlinkSync as unlinkSync5,
|
|
31731
31870
|
writeFileSync as writeFileSync7
|
|
31732
31871
|
} from "node:fs";
|
|
31733
|
-
import { homedir as
|
|
31872
|
+
import { homedir as homedir12 } from "node:os";
|
|
31734
31873
|
import { join as join15 } from "node:path";
|
|
31735
31874
|
function aftCacheBase() {
|
|
31736
31875
|
const override = process.env.AFT_CACHE_DIR;
|
|
@@ -31738,10 +31877,10 @@ function aftCacheBase() {
|
|
|
31738
31877
|
return override;
|
|
31739
31878
|
if (process.platform === "win32") {
|
|
31740
31879
|
const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
|
|
31741
|
-
const base2 = localAppData || join15(
|
|
31880
|
+
const base2 = localAppData || join15(homedir12(), "AppData", "Local");
|
|
31742
31881
|
return join15(base2, "aft");
|
|
31743
31882
|
}
|
|
31744
|
-
const base = process.env.XDG_CACHE_HOME || join15(
|
|
31883
|
+
const base = process.env.XDG_CACHE_HOME || join15(homedir12(), ".cache");
|
|
31745
31884
|
return join15(base, "aft");
|
|
31746
31885
|
}
|
|
31747
31886
|
function lspCacheRoot() {
|
|
@@ -33824,7 +33963,7 @@ async function verifySessionDirectory(client, sessionId) {
|
|
|
33824
33963
|
}
|
|
33825
33964
|
|
|
33826
33965
|
// src/shared/status.ts
|
|
33827
|
-
function
|
|
33966
|
+
function asRecord2(value) {
|
|
33828
33967
|
return typeof value === "object" && value !== null ? value : {};
|
|
33829
33968
|
}
|
|
33830
33969
|
function readString(value, fallback = "") {
|
|
@@ -33843,7 +33982,7 @@ function readOptionalNumber(value) {
|
|
|
33843
33982
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
33844
33983
|
}
|
|
33845
33984
|
function readCompressionAggregate(value) {
|
|
33846
|
-
const aggregate =
|
|
33985
|
+
const aggregate = asRecord2(value);
|
|
33847
33986
|
return {
|
|
33848
33987
|
events: readNumber(aggregate.events),
|
|
33849
33988
|
original_tokens: readNumber(aggregate.original_tokens),
|
|
@@ -33854,7 +33993,7 @@ function readCompressionAggregate(value) {
|
|
|
33854
33993
|
function readCompression(value) {
|
|
33855
33994
|
if (typeof value !== "object" || value === null)
|
|
33856
33995
|
return;
|
|
33857
|
-
const compression =
|
|
33996
|
+
const compression = asRecord2(value);
|
|
33858
33997
|
return {
|
|
33859
33998
|
project: readCompressionAggregate(compression.project),
|
|
33860
33999
|
session: readCompressionAggregate(compression.session)
|
|
@@ -33863,7 +34002,7 @@ function readCompression(value) {
|
|
|
33863
34002
|
function readStatusBar(value) {
|
|
33864
34003
|
if (typeof value !== "object" || value === null)
|
|
33865
34004
|
return;
|
|
33866
|
-
const bar =
|
|
34005
|
+
const bar = asRecord2(value);
|
|
33867
34006
|
return {
|
|
33868
34007
|
errors: readNumber(bar.errors),
|
|
33869
34008
|
warnings: readNumber(bar.warnings),
|
|
@@ -33907,16 +34046,16 @@ function formatBytes(bytes) {
|
|
|
33907
34046
|
return `${value.toFixed(decimals)} ${units[unitIndex]}`;
|
|
33908
34047
|
}
|
|
33909
34048
|
function coerceAftStatus(response) {
|
|
33910
|
-
const features =
|
|
33911
|
-
const searchIndex =
|
|
33912
|
-
const semanticIndex =
|
|
34049
|
+
const features = asRecord2(response.features);
|
|
34050
|
+
const searchIndex = asRecord2(response.search_index);
|
|
34051
|
+
const semanticIndex = asRecord2(response.semantic_index);
|
|
33913
34052
|
const semanticConfig = {
|
|
33914
|
-
...
|
|
33915
|
-
...
|
|
34053
|
+
...asRecord2(response.semantic),
|
|
34054
|
+
...asRecord2(response.semantic_config)
|
|
33916
34055
|
};
|
|
33917
|
-
const disk =
|
|
33918
|
-
const symbolCache =
|
|
33919
|
-
const session =
|
|
34056
|
+
const disk = asRecord2(response.disk);
|
|
34057
|
+
const symbolCache = asRecord2(response.symbol_cache);
|
|
34058
|
+
const session = asRecord2(response.session);
|
|
33920
34059
|
return {
|
|
33921
34060
|
version: readString(response.version, "unknown"),
|
|
33922
34061
|
project_root: readNullableString(response.project_root),
|
|
@@ -34038,7 +34177,7 @@ import { existsSync as existsSync15, mkdirSync as mkdirSync11, readFileSync as r
|
|
|
34038
34177
|
import { dirname as dirname10, join as join22 } from "node:path";
|
|
34039
34178
|
|
|
34040
34179
|
// src/shared/opencode-config-dir.ts
|
|
34041
|
-
import { homedir as
|
|
34180
|
+
import { homedir as homedir13 } from "node:os";
|
|
34042
34181
|
import { join as join21, resolve as resolve5 } from "node:path";
|
|
34043
34182
|
function getCliConfigDir() {
|
|
34044
34183
|
const envConfigDir = process.env.OPENCODE_CONFIG_DIR?.trim();
|
|
@@ -34046,9 +34185,9 @@ function getCliConfigDir() {
|
|
|
34046
34185
|
return resolve5(envConfigDir);
|
|
34047
34186
|
}
|
|
34048
34187
|
if (process.platform === "win32") {
|
|
34049
|
-
return join21(
|
|
34188
|
+
return join21(homedir13(), ".config", "opencode");
|
|
34050
34189
|
}
|
|
34051
|
-
return join21(process.env.XDG_CONFIG_HOME || join21(
|
|
34190
|
+
return join21(process.env.XDG_CONFIG_HOME || join21(homedir13(), ".config"), "opencode");
|
|
34052
34191
|
}
|
|
34053
34192
|
function getOpenCodeConfigDir2(_options) {
|
|
34054
34193
|
return getCliConfigDir();
|
|
@@ -34888,18 +35027,43 @@ function resolveForegroundWaitMs(configured) {
|
|
|
34888
35027
|
function bashToolDescription(aftSearchRegistered, compressionOn, backgroundOn) {
|
|
34889
35028
|
const searchSteer = aftSearchRegistered ? "use aft_search (concepts, identifiers, regex, literals), read, aft_outline, or aft_zoom instead" : "use the grep tool, read, aft_outline, or aft_zoom instead";
|
|
34890
35029
|
const compression = compressionOn ? " Output is compressed by default; pass compressed: false for raw output." : "";
|
|
34891
|
-
const tasks = backgroundOn ? ' Pass background: true to run in the background and get a taskId for bash_status/bash_kill. Pass pty: true for interactive programs (REPLs, TUIs) and drive them with bash_status({ outputMode: "screen" }) plus bash_write (pty implies background automatically).' : " Commands
|
|
34892
|
-
return `Execute shell commands.${compression}${tasks}
|
|
35030
|
+
const tasks = backgroundOn ? ' Pass background: true to run in the background and get a taskId for bash_status/bash_watch/bash_kill. Pass pty: true for interactive programs (REPLs, TUIs) and drive them with bash_status({ outputMode: "screen" }) plus bash_write (pty implies background automatically). Use bash_watch to wait for exit or output patterns (sync blocks, async notifies). Do not loop bash_status to wait.' : " Commands run in the foreground to completion; timeout is the hard kill cap (default 30 minutes).";
|
|
35031
|
+
return `Execute shell commands.${compression}${tasks}
|
|
34893
35032
|
|
|
34894
35033
|
DO NOT use bash for code search or code exploration. If you are about to run grep, rg, sed, awk, find, or cat through bash to locate or read code: STOP — ${searchSteer}.`;
|
|
34895
35034
|
}
|
|
35035
|
+
function pushUnique(target, values) {
|
|
35036
|
+
for (const value of values) {
|
|
35037
|
+
if (!target.includes(value))
|
|
35038
|
+
target.push(value);
|
|
35039
|
+
}
|
|
35040
|
+
}
|
|
35041
|
+
function groupBashPermissionAsks(asks) {
|
|
35042
|
+
const grouped = [];
|
|
35043
|
+
let bashAsk;
|
|
35044
|
+
for (const ask of asks) {
|
|
35045
|
+
if (ask.kind === "bash") {
|
|
35046
|
+
if (!bashAsk) {
|
|
35047
|
+
bashAsk = { kind: "bash", patterns: [], always: [] };
|
|
35048
|
+
grouped.push(bashAsk);
|
|
35049
|
+
}
|
|
35050
|
+
pushUnique(bashAsk.patterns, ask.patterns);
|
|
35051
|
+
pushUnique(bashAsk.always, ask.always);
|
|
35052
|
+
continue;
|
|
35053
|
+
}
|
|
35054
|
+
grouped.push(ask);
|
|
35055
|
+
}
|
|
35056
|
+
return grouped;
|
|
35057
|
+
}
|
|
35058
|
+
function permissionsGrantedForRetry(asks) {
|
|
35059
|
+
return asks.flatMap((ask) => ask.always.length > 0 ? ask.always : ask.patterns);
|
|
35060
|
+
}
|
|
34896
35061
|
async function withPermissionLoop(ctx, runtime, params, bridgeCall, options) {
|
|
34897
35062
|
const first = await bridgeCall(ctx, runtime, "bash", params, options);
|
|
34898
35063
|
if (first.success !== false || first.code !== "permission_required")
|
|
34899
35064
|
return first;
|
|
34900
35065
|
const asks = Array.isArray(first.asks) ? first.asks : [];
|
|
34901
|
-
const
|
|
34902
|
-
for (const ask of asks) {
|
|
35066
|
+
for (const ask of groupBashPermissionAsks(asks)) {
|
|
34903
35067
|
const permission = ask.kind === "external_directory" ? "external_directory" : "bash";
|
|
34904
35068
|
await runAsk(runtime.ask({
|
|
34905
35069
|
permission,
|
|
@@ -34907,54 +35071,60 @@ async function withPermissionLoop(ctx, runtime, params, bridgeCall, options) {
|
|
|
34907
35071
|
always: ask.always,
|
|
34908
35072
|
metadata: {}
|
|
34909
35073
|
}));
|
|
34910
|
-
permissionsGranted.push(...ask.always.length > 0 ? ask.always : ask.patterns);
|
|
34911
35074
|
}
|
|
34912
|
-
const second = await bridgeCall(ctx, runtime, "bash", { ...params, permissions_granted:
|
|
35075
|
+
const second = await bridgeCall(ctx, runtime, "bash", { ...params, permissions_granted: permissionsGrantedForRetry(asks) }, options);
|
|
34913
35076
|
if (second.success === false && second.code === "permission_required") {
|
|
34914
35077
|
throw new Error("bash permission retry failed");
|
|
34915
35078
|
}
|
|
34916
35079
|
return second;
|
|
34917
35080
|
}
|
|
34918
35081
|
function createBashTool(ctx, aftSearchRegisteredOverride) {
|
|
35082
|
+
const initialBashCfg = resolveBashConfig(ctx.config);
|
|
35083
|
+
const backgroundFlagArg = initialBashCfg.background ? {
|
|
35084
|
+
background: z4.boolean().optional().describe("When true, spawn the command in the background and return a taskId for bash_status/bash_kill instead of waiting for completion. Defaults to false.")
|
|
35085
|
+
} : {};
|
|
35086
|
+
const ptyArgs = initialBashCfg.background ? {
|
|
35087
|
+
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.'),
|
|
35088
|
+
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."),
|
|
35089
|
+
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.")
|
|
35090
|
+
} : {};
|
|
35091
|
+
const args = {
|
|
35092
|
+
command: z4.string().describe("Shell command to execute. Supports pipes, redirection, and normal shell syntax."),
|
|
35093
|
+
timeout: optionalInt(1, Number.MAX_SAFE_INTEGER).describe(initialBashCfg.background ? "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 ~8s (configurable via bash.foreground_wait_window_ms); otherwise it's automatically promoted to background and a completion reminder is delivered when the task actually finishes." : "Hard kill cap in milliseconds (positive integer). When omitted, the foreground command can run up to 30 minutes and returns inline when it finishes."),
|
|
35094
|
+
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."),
|
|
35095
|
+
description: z4.string().optional().describe("Short 5-10 word human-readable summary shown in OpenCode UI metadata instead of raw shell syntax."),
|
|
35096
|
+
...backgroundFlagArg,
|
|
35097
|
+
compressed: z4.boolean().optional().describe("When true or omitted, return compressed output with noisy terminal control sequences reduced. Set to false for raw output."),
|
|
35098
|
+
...ptyArgs
|
|
35099
|
+
};
|
|
34919
35100
|
return {
|
|
34920
|
-
description: (
|
|
34921
|
-
|
|
34922
|
-
|
|
34923
|
-
})(),
|
|
34924
|
-
args: {
|
|
34925
|
-
command: z4.string().describe("Shell command to execute. Supports pipes, redirection, and normal shell syntax."),
|
|
34926
|
-
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 ~8s (configurable via bash.foreground_wait_window_ms); otherwise it's automatically promoted to background and a completion reminder is delivered when the task actually finishes."),
|
|
34927
|
-
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."),
|
|
34928
|
-
description: z4.string().optional().describe("Short 5-10 word human-readable summary shown in OpenCode UI metadata instead of raw shell syntax."),
|
|
34929
|
-
background: z4.boolean().optional().describe("When true, spawn the command in the background and return a taskId for bash_status/bash_kill instead of waiting for completion. Defaults to false."),
|
|
34930
|
-
compressed: z4.boolean().optional().describe("When true or omitted, return compressed output with noisy terminal control sequences reduced. Set to false for raw output."),
|
|
34931
|
-
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.'),
|
|
34932
|
-
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."),
|
|
34933
|
-
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.")
|
|
34934
|
-
},
|
|
34935
|
-
execute: async (args, context) => {
|
|
35101
|
+
description: bashToolDescription(false, initialBashCfg.compress, initialBashCfg.background),
|
|
35102
|
+
args,
|
|
35103
|
+
execute: async (args2, context) => {
|
|
34936
35104
|
const bashCfg = resolveBashConfig(ctx.config);
|
|
34937
35105
|
const ctxAftSearchRegistered = ctx.aftSearchRegistered === true;
|
|
34938
35106
|
const aftSearchRegistered = aftSearchRegisteredOverride ?? ctxAftSearchRegistered;
|
|
34939
35107
|
let accumulatedOutput = "";
|
|
34940
|
-
const description =
|
|
35108
|
+
const description = args2.description;
|
|
34941
35109
|
const metadata = context.metadata;
|
|
34942
|
-
const rawCommand =
|
|
34943
|
-
const compressionEnabled = bashCfg.compress &&
|
|
35110
|
+
const rawCommand = args2.command;
|
|
35111
|
+
const compressionEnabled = bashCfg.compress && args2.compressed !== false;
|
|
34944
35112
|
const pipeStrip = maybeStripCompressorPipe(rawCommand, compressionEnabled);
|
|
34945
35113
|
const command = pipeStrip.command;
|
|
34946
|
-
const cwd =
|
|
35114
|
+
const cwd = args2.workdir ?? context.directory;
|
|
34947
35115
|
const isSubagent = await resolveIsSubagent(ctx.client, context.sessionID, context.directory);
|
|
34948
|
-
const
|
|
34949
|
-
const
|
|
35116
|
+
const backgroundDisabled = !bashCfg.background;
|
|
35117
|
+
const requestedPty = !backgroundDisabled && args2.pty === true;
|
|
35118
|
+
const requestedBackground = !backgroundDisabled && (args2.background === true || requestedPty);
|
|
34950
35119
|
if (requestedPty && isSubagent) {
|
|
34951
35120
|
throw new Error("PTY mode is not available in subagent sessions; subagents cannot drive interactive terminals.");
|
|
34952
35121
|
}
|
|
34953
35122
|
const allowSubagentBg = bashCfg.subagent_background;
|
|
34954
35123
|
const subagentForcedForeground = isSubagent && !allowSubagentBg;
|
|
34955
|
-
const
|
|
34956
|
-
const
|
|
34957
|
-
const
|
|
35124
|
+
const blockToCompletion = subagentForcedForeground || backgroundDisabled;
|
|
35125
|
+
const effectiveBackground = blockToCompletion ? false : requestedBackground;
|
|
35126
|
+
const rawTimeout = args2.timeout;
|
|
35127
|
+
const effectiveTimeout = effectiveBackground || backgroundDisabled ? rawTimeout : resolveBashKillTimeout(rawTimeout, bashCfg.foreground_wait_window_ms);
|
|
34958
35128
|
if (subagentForcedForeground && requestedBackground) {
|
|
34959
35129
|
sessionLog(context.sessionID, "[bash] subagent + background:true → converting to foreground (subagent would lose task_id)");
|
|
34960
35130
|
}
|
|
@@ -34962,15 +35132,15 @@ function createBashTool(ctx, aftSearchRegisteredOverride) {
|
|
|
34962
35132
|
const data = await withPermissionLoop(ctx, context, {
|
|
34963
35133
|
command,
|
|
34964
35134
|
timeout: effectiveTimeout,
|
|
34965
|
-
workdir:
|
|
35135
|
+
workdir: args2.workdir,
|
|
34966
35136
|
env: shellEnv?.env ?? {},
|
|
34967
35137
|
description,
|
|
34968
35138
|
background: effectiveBackground,
|
|
34969
35139
|
notify_on_completion: effectiveBackground,
|
|
34970
|
-
compressed:
|
|
35140
|
+
compressed: args2.compressed,
|
|
34971
35141
|
pty: requestedPty,
|
|
34972
|
-
pty_rows:
|
|
34973
|
-
pty_cols:
|
|
35142
|
+
pty_rows: args2.ptyRows,
|
|
35143
|
+
pty_cols: args2.ptyCols,
|
|
34974
35144
|
permissions_requested: true
|
|
34975
35145
|
}, callBashBridge, {
|
|
34976
35146
|
onProgress: ({ text }) => {
|
|
@@ -34995,7 +35165,7 @@ function createBashTool(ctx, aftSearchRegisteredOverride) {
|
|
|
34995
35165
|
return { output: startedLine, title: uiTitle, metadata: metadataPayload2 };
|
|
34996
35166
|
}
|
|
34997
35167
|
const foregroundWaitMs = resolveForegroundWaitMs(bashCfg.foreground_wait_window_ms);
|
|
34998
|
-
const waitTimeoutMs =
|
|
35168
|
+
const waitTimeoutMs = blockToCompletion ? effectiveTimeout ?? DEFAULT_HARD_TIMEOUT_MS : effectiveTimeout !== undefined ? Math.min(effectiveTimeout, foregroundWaitMs) : foregroundWaitMs;
|
|
34999
35169
|
const startedAt = Date.now();
|
|
35000
35170
|
while (true) {
|
|
35001
35171
|
const status = await callBashBridge(ctx, context, "bash_status", { task_id: taskId });
|
|
@@ -35009,7 +35179,7 @@ function createBashTool(ctx, aftSearchRegisteredOverride) {
|
|
|
35009
35179
|
return { output: rendered2, title: uiTitle, metadata: metadataPayload2 };
|
|
35010
35180
|
}
|
|
35011
35181
|
if (Date.now() - startedAt >= waitTimeoutMs) {
|
|
35012
|
-
if (
|
|
35182
|
+
if (blockToCompletion) {
|
|
35013
35183
|
await sleep(FOREGROUND_POLL_INTERVAL_MS);
|
|
35014
35184
|
continue;
|
|
35015
35185
|
}
|
|
@@ -35069,7 +35239,7 @@ function createBashTool(ctx, aftSearchRegisteredOverride) {
|
|
|
35069
35239
|
}
|
|
35070
35240
|
function createBashStatusTool(ctx) {
|
|
35071
35241
|
return {
|
|
35072
|
-
description: "Read-only snapshot of a background or PTY bash task's current state and output. Returns immediately. Never waits.
|
|
35242
|
+
description: "Read-only snapshot of a background or PTY bash task's current state and output. Returns immediately. Never waits. One look to check on a task is fine — never loop it to wait for completion. To wait, use bash_watch.",
|
|
35073
35243
|
args: {
|
|
35074
35244
|
taskId: z4.string().describe("Background task ID returned by bash({ background: true }), e.g. bash-6b454047."),
|
|
35075
35245
|
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.")
|
|
@@ -35547,16 +35717,16 @@ import { tool as tool6 } from "@opencode-ai/plugin";
|
|
|
35547
35717
|
var z6 = tool6.schema;
|
|
35548
35718
|
var BASH_WAIT_POLL_INTERVAL_MS = 100;
|
|
35549
35719
|
var DEFAULT_BASH_STATUS_WAIT_TIMEOUT_MS = 30000;
|
|
35550
|
-
var MAX_BASH_STATUS_WAIT_TIMEOUT_MS =
|
|
35720
|
+
var MAX_BASH_STATUS_WAIT_TIMEOUT_MS = 30 * 60 * 1000;
|
|
35551
35721
|
var REGEX_WAIT_SCAN_WINDOW_BYTES = 64 * 1024;
|
|
35552
35722
|
function createBashWatchTool(ctx) {
|
|
35553
35723
|
return {
|
|
35554
|
-
description: "Watch a background bash task.
|
|
35724
|
+
description: "Watch a background bash task. Sync (default) blocks until a pattern matches, the task exits, or timeout — use it when the result is the next thing you need, even for long builds/tests/installs (pass timeoutMs up to 30 min for those). The user can interrupt anytime; the wait auto-converts to an async notification. Async (background:true, requires pattern) registers a non-blocking notification and returns immediately — use when you have parallel work or want to end your turn. Never loop bash_status to wait.",
|
|
35555
35725
|
args: {
|
|
35556
35726
|
taskId: z6.string().describe("Background task ID returned by bash({ background: true })."),
|
|
35557
35727
|
pattern: z6.union([z6.string(), z6.object({ regex: z6.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."),
|
|
35558
35728
|
background: z6.boolean().optional().describe("When true, register an async watch and return immediately. Defaults to false (sync wait)."),
|
|
35559
|
-
timeoutMs: optionalInt(1, MAX_BASH_STATUS_WAIT_TIMEOUT_MS).describe("Sync-only timeout in milliseconds. Default 30000, max
|
|
35729
|
+
timeoutMs: optionalInt(1, MAX_BASH_STATUS_WAIT_TIMEOUT_MS).describe("Sync-only timeout in milliseconds. Default 30000, max 1800000 (30 min)."),
|
|
35560
35730
|
once: z6.boolean().optional().describe("Async-only. Defaults true; false keeps the watch sticky until task exit.")
|
|
35561
35731
|
},
|
|
35562
35732
|
execute: async (args, context) => {
|
|
@@ -37116,12 +37286,15 @@ function hoistedTools(ctx) {
|
|
|
37116
37286
|
aft_delete: createDeleteTool(ctx),
|
|
37117
37287
|
aft_move: createMoveTool(ctx)
|
|
37118
37288
|
};
|
|
37119
|
-
|
|
37289
|
+
const bashCfg = resolveBashConfig(ctx.config);
|
|
37290
|
+
if (bashCfg.enabled) {
|
|
37120
37291
|
tools.bash = createBashTool(ctx);
|
|
37121
|
-
|
|
37122
|
-
|
|
37123
|
-
|
|
37124
|
-
|
|
37292
|
+
if (bashCfg.background) {
|
|
37293
|
+
tools.bash_status = createBashStatusTool(ctx);
|
|
37294
|
+
tools.bash_write = createBashWriteTool(ctx);
|
|
37295
|
+
tools.bash_watch = createBashWatchTool(ctx);
|
|
37296
|
+
tools.bash_kill = createBashKillTool(ctx);
|
|
37297
|
+
}
|
|
37125
37298
|
}
|
|
37126
37299
|
return tools;
|
|
37127
37300
|
}
|
|
@@ -37175,12 +37348,15 @@ function aftPrefixedTools(ctx) {
|
|
|
37175
37348
|
aft_delete: createDeleteTool(ctx),
|
|
37176
37349
|
aft_move: createMoveTool(ctx)
|
|
37177
37350
|
};
|
|
37178
|
-
|
|
37351
|
+
const bashCfg = resolveBashConfig(ctx.config);
|
|
37352
|
+
if (bashCfg.enabled) {
|
|
37179
37353
|
tools.aft_bash = createBashTool(ctx);
|
|
37180
|
-
|
|
37181
|
-
|
|
37182
|
-
|
|
37183
|
-
|
|
37354
|
+
if (bashCfg.background) {
|
|
37355
|
+
tools.bash_status = createBashStatusTool(ctx);
|
|
37356
|
+
tools.bash_write = createBashWriteTool(ctx);
|
|
37357
|
+
tools.bash_watch = createBashWatchTool(ctx);
|
|
37358
|
+
tools.bash_kill = createBashKillTool(ctx);
|
|
37359
|
+
}
|
|
37184
37360
|
}
|
|
37185
37361
|
return tools;
|
|
37186
37362
|
}
|
|
@@ -37264,15 +37440,15 @@ function importTools(ctx) {
|
|
|
37264
37440
|
// src/tools/inspect.ts
|
|
37265
37441
|
import { tool as tool10 } from "@opencode-ai/plugin";
|
|
37266
37442
|
var z10 = tool10.schema;
|
|
37267
|
-
function
|
|
37443
|
+
function asRecord3(value) {
|
|
37268
37444
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
37269
37445
|
return;
|
|
37270
37446
|
return value;
|
|
37271
37447
|
}
|
|
37272
|
-
function
|
|
37448
|
+
function asNumber2(value) {
|
|
37273
37449
|
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
37274
37450
|
}
|
|
37275
|
-
function
|
|
37451
|
+
function asString2(value) {
|
|
37276
37452
|
return typeof value === "string" ? value : undefined;
|
|
37277
37453
|
}
|
|
37278
37454
|
function asStringArray(value) {
|
|
@@ -37289,16 +37465,16 @@ function diagnosticsServerSummary(section) {
|
|
|
37289
37465
|
return parts.length > 0 ? parts.join("; ") : "none reported";
|
|
37290
37466
|
}
|
|
37291
37467
|
function formatDiagnosticsSummary(summary) {
|
|
37292
|
-
const section =
|
|
37468
|
+
const section = asRecord3(summary?.diagnostics);
|
|
37293
37469
|
if (!section)
|
|
37294
37470
|
return;
|
|
37295
|
-
const errors3 =
|
|
37296
|
-
const warnings =
|
|
37297
|
-
const info =
|
|
37298
|
-
const hints =
|
|
37471
|
+
const errors3 = asNumber2(section.errors);
|
|
37472
|
+
const warnings = asNumber2(section.warnings);
|
|
37473
|
+
const info = asNumber2(section.info);
|
|
37474
|
+
const hints = asNumber2(section.hints);
|
|
37299
37475
|
const hasCounts = [errors3, warnings, info, hints].some((value) => value !== undefined);
|
|
37300
37476
|
const counts = `${errors3 ?? 0} errors, ${warnings ?? 0} warnings, ${info ?? 0} info, ${hints ?? 0} hints`;
|
|
37301
|
-
const status =
|
|
37477
|
+
const status = asString2(section.status);
|
|
37302
37478
|
if (status === "pending") {
|
|
37303
37479
|
return hasCounts ? `diagnostics: ${counts} so far — still pending (servers: ${diagnosticsServerSummary(section)})` : `diagnostics: pending (servers: ${diagnosticsServerSummary(section)})`;
|
|
37304
37480
|
}
|
|
@@ -37311,9 +37487,9 @@ function formatDiagnosticsSummary(summary) {
|
|
|
37311
37487
|
return;
|
|
37312
37488
|
}
|
|
37313
37489
|
function formatDiagnosticLocation(diagnostic) {
|
|
37314
|
-
const file2 =
|
|
37315
|
-
const line =
|
|
37316
|
-
const column =
|
|
37490
|
+
const file2 = asString2(diagnostic.file) ?? "(unknown file)";
|
|
37491
|
+
const line = asNumber2(diagnostic.line);
|
|
37492
|
+
const column = asNumber2(diagnostic.column);
|
|
37317
37493
|
if (line === undefined)
|
|
37318
37494
|
return file2;
|
|
37319
37495
|
if (column === undefined)
|
|
@@ -37321,21 +37497,21 @@ function formatDiagnosticLocation(diagnostic) {
|
|
|
37321
37497
|
return `${file2}:${line}:${column}`;
|
|
37322
37498
|
}
|
|
37323
37499
|
function formatDiagnosticsDetails(details) {
|
|
37324
|
-
const diagnostics = Array.isArray(details?.diagnostics) ? details.diagnostics.map(
|
|
37500
|
+
const diagnostics = Array.isArray(details?.diagnostics) ? details.diagnostics.map(asRecord3).filter(Boolean) : [];
|
|
37325
37501
|
return diagnostics.map((diagnostic) => {
|
|
37326
|
-
const severity =
|
|
37327
|
-
const message =
|
|
37328
|
-
const source =
|
|
37502
|
+
const severity = asString2(diagnostic.severity) ?? "information";
|
|
37503
|
+
const message = asString2(diagnostic.message) ?? "(no message)";
|
|
37504
|
+
const source = asString2(diagnostic.source);
|
|
37329
37505
|
const suffix = source ? ` [${source}]` : "";
|
|
37330
37506
|
return `${formatDiagnosticLocation(diagnostic)} ${severity} ${message}${suffix}`;
|
|
37331
37507
|
});
|
|
37332
37508
|
}
|
|
37333
37509
|
function renderInspectDiagnostics(response) {
|
|
37334
37510
|
const lines = [];
|
|
37335
|
-
const summaryLine = formatDiagnosticsSummary(
|
|
37511
|
+
const summaryLine = formatDiagnosticsSummary(asRecord3(response.summary));
|
|
37336
37512
|
if (summaryLine)
|
|
37337
37513
|
lines.push(summaryLine);
|
|
37338
|
-
const detailLines = formatDiagnosticsDetails(
|
|
37514
|
+
const detailLines = formatDiagnosticsDetails(asRecord3(response.details));
|
|
37339
37515
|
if (detailLines.length > 0) {
|
|
37340
37516
|
lines.push("diagnostics details:", ...detailLines.map((line) => `- ${line}`));
|
|
37341
37517
|
}
|
|
@@ -37520,7 +37696,8 @@ function navigationTools(ctx) {
|
|
|
37520
37696
|
}
|
|
37521
37697
|
throw new Error(message);
|
|
37522
37698
|
}
|
|
37523
|
-
return
|
|
37699
|
+
return formatCallgraphSections(args.op, response).join(`
|
|
37700
|
+
`);
|
|
37524
37701
|
}
|
|
37525
37702
|
}
|
|
37526
37703
|
};
|
|
@@ -38568,11 +38745,11 @@ function buildWorkflowHints(opts) {
|
|
|
38568
38745
|
}
|
|
38569
38746
|
if (hasBash && hasBgBash) {
|
|
38570
38747
|
sections.push([
|
|
38571
|
-
`**Long-running commands** (builds, installs, full test suites): \`${bashName}({ background: true })\` returns a \`taskId\` immediately
|
|
38572
|
-
"1.
|
|
38573
|
-
"2.
|
|
38574
|
-
|
|
38575
|
-
|
|
38748
|
+
`**Long-running commands** (builds, installs, full test suites): \`${bashName}({ background: true })\` returns a \`taskId\` immediately.`,
|
|
38749
|
+
"1. Nothing else useful to do (a build/test/validation whose result is the next thing you need) → sync `bash_watch` to block until it exits (pass a longer timeoutMs for long commands; the user can interrupt).",
|
|
38750
|
+
"2. Useful parallel work available → end your turn; the completion reminder delivers the result. (Or spawn a subagent for the side work.)",
|
|
38751
|
+
"3. Want to react to a specific early output line → async `bash_watch` (background:true + pattern).",
|
|
38752
|
+
"Never loop `bash_status` to wait — it's a one-shot inspector, not a wait primitive."
|
|
38576
38753
|
].join(`
|
|
38577
38754
|
`));
|
|
38578
38755
|
sections.push(`**PTY / interactive commands**: PTY mode is for interactive REPLs and terminal apps (python, node, bash itself, vim). Start with \`${bashName}({ command: "python", pty: true, background: true })\`, read the screen with \`${bashStatusName}({ taskId, outputMode: "screen" })\`, and send input with \`${bashWriteName}({ taskId, input: "..." })\`.`);
|
|
@@ -38657,6 +38834,7 @@ async function initializePluginForDirectory(input) {
|
|
|
38657
38834
|
const binaryPath = await findBinary(PLUGIN_VERSION);
|
|
38658
38835
|
await ensureStorageMigrated({ harness: "opencode", binaryPath, logger: bridgeLogger });
|
|
38659
38836
|
const aftConfig = loadAftConfig(input.directory);
|
|
38837
|
+
enqueueConfigParseWarnings(input.directory, getConfigLoadErrors());
|
|
38660
38838
|
const autoUpdateAbort = new AbortController;
|
|
38661
38839
|
const configOverrides = {
|
|
38662
38840
|
...resolveProjectOverridesForConfigure(aftConfig),
|
|
@@ -38747,6 +38925,7 @@ ${lines}
|
|
|
38747
38925
|
projectConfigLoader: (projectRoot) => {
|
|
38748
38926
|
try {
|
|
38749
38927
|
const projectConfig = loadAftConfig(projectRoot);
|
|
38928
|
+
enqueueConfigParseWarnings(projectRoot, getConfigLoadErrors());
|
|
38750
38929
|
return resolveProjectOverridesForConfigure(projectConfig);
|
|
38751
38930
|
} catch (err) {
|
|
38752
38931
|
warn2(`loadAftConfig(${projectRoot}) failed; falling back to plugin-init config: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -38805,8 +38984,7 @@ ${lines}
|
|
|
38805
38984
|
ctx,
|
|
38806
38985
|
directory: sessionDir,
|
|
38807
38986
|
sessionID: completion.session_id,
|
|
38808
|
-
client: input.client
|
|
38809
|
-
serverUrl: input.serverUrl?.toString()
|
|
38987
|
+
client: input.client
|
|
38810
38988
|
}, completion);
|
|
38811
38989
|
},
|
|
38812
38990
|
onBashLongRunning: (reminder, bridge) => {
|
|
@@ -38815,8 +38993,7 @@ ${lines}
|
|
|
38815
38993
|
ctx,
|
|
38816
38994
|
directory: sessionDir,
|
|
38817
38995
|
sessionID: reminder.session_id,
|
|
38818
|
-
client: input.client
|
|
38819
|
-
serverUrl: input.serverUrl?.toString()
|
|
38996
|
+
client: input.client
|
|
38820
38997
|
}, reminder);
|
|
38821
38998
|
},
|
|
38822
38999
|
onBashPatternMatch: (frame, bridge) => {
|
|
@@ -38825,8 +39002,7 @@ ${lines}
|
|
|
38825
39002
|
ctx,
|
|
38826
39003
|
directory: sessionDir,
|
|
38827
39004
|
sessionID: frame.session_id,
|
|
38828
|
-
client: input.client
|
|
38829
|
-
serverUrl: input.serverUrl?.toString()
|
|
39005
|
+
client: input.client
|
|
38830
39006
|
}, frame);
|
|
38831
39007
|
}
|
|
38832
39008
|
};
|
|
@@ -38839,16 +39015,6 @@ ${lines}
|
|
|
38839
39015
|
config: aftConfig,
|
|
38840
39016
|
storageDir: configOverrides.storage_dir
|
|
38841
39017
|
};
|
|
38842
|
-
probeServerReachable(input.serverUrl?.toString()).then((reachable) => {
|
|
38843
|
-
setLiveServerWakeAvailable(reachable);
|
|
38844
|
-
if (reachable) {
|
|
38845
|
-
log2("Live OpenCode HTTP listener reachable; bg-notifications wake path = live-server (anomalyco/opencode#28202 workaround active).");
|
|
38846
|
-
} else {
|
|
38847
|
-
debug("Live OpenCode HTTP listener unreachable; bg-notifications wake path = in-process-fallback. Wakes will still arrive but the upstream duplicate-runner bug (anomalyco/opencode#28202) is not worked around. Launch with `opencode --port 0` in TUI mode to activate the workaround.");
|
|
38848
|
-
}
|
|
38849
|
-
}).catch(() => {
|
|
38850
|
-
setLiveServerWakeAvailable(false);
|
|
38851
|
-
});
|
|
38852
39018
|
if (onnxRuntimePromise) {
|
|
38853
39019
|
onnxRuntimePromise.then((ortDylibDir) => {
|
|
38854
39020
|
if (ortDylibDir) {
|
|
@@ -39083,9 +39249,24 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
39083
39249
|
ctx,
|
|
39084
39250
|
directory: sessionDir,
|
|
39085
39251
|
sessionID,
|
|
39086
|
-
client: input.client
|
|
39087
|
-
serverUrl: input.serverUrl?.toString()
|
|
39252
|
+
client: input.client
|
|
39088
39253
|
});
|
|
39254
|
+
const configParseWarnings = drainPendingConfigParseWarnings(sessionDir);
|
|
39255
|
+
if (configParseWarnings.length > 0) {
|
|
39256
|
+
const bridge = pool.getActiveBridgeForRoot(sessionDir) ?? pool.getBridge(sessionDir);
|
|
39257
|
+
enqueueConfigureWarningsForSession({
|
|
39258
|
+
projectRoot: sessionDir,
|
|
39259
|
+
sessionId: sessionID,
|
|
39260
|
+
client: input.client,
|
|
39261
|
+
bridge,
|
|
39262
|
+
warnings: configParseWarnings,
|
|
39263
|
+
fallbackClient: input.client,
|
|
39264
|
+
storageDir: configOverrides.storage_dir,
|
|
39265
|
+
pluginVersion: PLUGIN_VERSION,
|
|
39266
|
+
serverUrl: input.serverUrl?.toString(),
|
|
39267
|
+
delivery: aftConfig.configure_warnings_delivery ?? "toast"
|
|
39268
|
+
});
|
|
39269
|
+
}
|
|
39089
39270
|
await flushConfigureWarningsOnIdle(sessionID);
|
|
39090
39271
|
},
|
|
39091
39272
|
"chat.message": async (messageInput) => {
|