@cortexkit/aft-opencode 0.58.0 → 0.58.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bridge-bootstrap.d.ts +8 -0
- package/dist/bridge-bootstrap.d.ts.map +1 -1
- package/dist/config-live-reload.d.ts +323 -0
- package/dist/config-live-reload.d.ts.map +1 -0
- package/dist/config.d.ts +59 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/entry/server.js +2543 -989
- package/dist/hashline-tag-sources.d.ts +18 -0
- package/dist/hashline-tag-sources.d.ts.map +1 -0
- package/dist/hooks/auto-update-checker/checker.d.ts +1 -0
- package/dist/hooks/auto-update-checker/checker.d.ts.map +1 -1
- package/dist/hooks/auto-update-checker/index.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1901 -748
- package/dist/lsp-auto-install.d.ts +30 -0
- package/dist/lsp-auto-install.d.ts.map +1 -1
- package/dist/lsp-github-table.d.ts +4 -3
- package/dist/lsp-github-table.d.ts.map +1 -1
- package/dist/lsp-npm-table.d.ts +11 -1
- package/dist/lsp-npm-table.d.ts.map +1 -1
- package/dist/lsp-registry-probe.d.ts +8 -4
- package/dist/lsp-registry-probe.d.ts.map +1 -1
- package/dist/permissions/v2-service.d.ts +110 -5
- package/dist/permissions/v2-service.d.ts.map +1 -1
- package/dist/rpc/register.d.ts +0 -4
- package/dist/rpc/register.d.ts.map +1 -1
- package/dist/shared/read-vision.d.ts +10 -0
- package/dist/shared/read-vision.d.ts.map +1 -0
- package/dist/shared/session-directory.d.ts +7 -0
- package/dist/shared/session-directory.d.ts.map +1 -1
- package/dist/shared/subagent-detect.d.ts +11 -4
- package/dist/shared/subagent-detect.d.ts.map +1 -1
- package/dist/tool-perf.d.ts +1 -0
- package/dist/tool-perf.d.ts.map +1 -1
- package/dist/tool-registration.d.ts +8 -5
- package/dist/tool-registration.d.ts.map +1 -1
- package/dist/tools/_shared.d.ts +16 -15
- package/dist/tools/_shared.d.ts.map +1 -1
- package/dist/tools/bash.d.ts +24 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash_watch.d.ts +5 -2
- package/dist/tools/bash_watch.d.ts.map +1 -1
- package/dist/tools/bash_write.d.ts.map +1 -1
- package/dist/tools/definitions/v2.d.ts +2 -0
- package/dist/tools/definitions/v2.d.ts.map +1 -1
- package/dist/tools/hoisted/v2.d.ts +8 -1
- package/dist/tools/hoisted/v2.d.ts.map +1 -1
- package/dist/tools/hoisted.d.ts +9 -7
- package/dist/tools/hoisted.d.ts.map +1 -1
- package/dist/tools/permissions.d.ts.map +1 -1
- package/dist/tools/reading.d.ts +0 -3
- package/dist/tools/reading.d.ts.map +1 -1
- package/dist/v2-prompt-detach.d.ts +69 -0
- package/dist/v2-prompt-detach.d.ts.map +1 -0
- package/dist/wakes/runtime-consumer.d.ts +2 -0
- package/dist/wakes/runtime-consumer.d.ts.map +1 -1
- package/dist/wakes/session-delivery.d.ts +9 -0
- package/dist/wakes/session-delivery.d.ts.map +1 -1
- package/dist/workflow-hints.d.ts +20 -7
- package/dist/workflow-hints.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/entry/server-runtime.mjs +36 -5
- package/src/shared/read-vision.ts +100 -0
- package/src/shared/session-directory.ts +92 -0
- package/src/shared/subagent-detect.ts +89 -28
package/dist/index.js
CHANGED
|
@@ -7883,17 +7883,79 @@ import * as fs from "node:fs";
|
|
|
7883
7883
|
import * as os from "node:os";
|
|
7884
7884
|
import * as path from "node:path";
|
|
7885
7885
|
var DEFAULT_PRIMARY_WATCH_TIMEOUT_MS = 30000;
|
|
7886
|
-
var
|
|
7886
|
+
var MAX_WATCH_TIMEOUT_MS = 1800000;
|
|
7887
|
+
var LONGEST_TIMER_DELAY_MS = 2147483647;
|
|
7888
|
+
var WATCH_SYNC_DEFAULTS_DESCRIPTION = "Sync waits default to 30s in a main session (max `bash.watch_sync_max_ms`, 120s by default); in a delegated session a sync wait without a timeout waits until the command finishes";
|
|
7889
|
+
var WATCH_TIMEOUT_PARAM_DESCRIPTION = "Sync-only timeout in milliseconds. In a main session: default 30000, max `bash.watch_sync_max_ms` (120000 by default). In a delegated session: omit it to wait until the command finishes; a value you pass is used as given.";
|
|
7887
7890
|
function resolveWatchTimeoutMs(requestedMs, role, capMs) {
|
|
7888
|
-
|
|
7889
|
-
|
|
7891
|
+
if (role === "worker")
|
|
7892
|
+
return requestedMs;
|
|
7893
|
+
return Math.min(requestedMs ?? DEFAULT_PRIMARY_WATCH_TIMEOUT_MS, capMs);
|
|
7894
|
+
}
|
|
7895
|
+
function maxWatchTimeoutMs(role, capMs) {
|
|
7896
|
+
return role === "worker" ? MAX_WATCH_TIMEOUT_MS : capMs;
|
|
7897
|
+
}
|
|
7898
|
+
function watchPollDelayMs(elapsedMs) {
|
|
7899
|
+
if (elapsedMs < 5000)
|
|
7900
|
+
return 100;
|
|
7901
|
+
if (elapsedMs < 30000)
|
|
7902
|
+
return 250;
|
|
7903
|
+
if (elapsedMs < 120000)
|
|
7904
|
+
return 500;
|
|
7905
|
+
return 1000;
|
|
7906
|
+
}
|
|
7907
|
+
var watchClock = {
|
|
7908
|
+
now: () => performance.now(),
|
|
7909
|
+
sleep: abortableSleep
|
|
7910
|
+
};
|
|
7911
|
+
function abortableSleep(ms, signal) {
|
|
7912
|
+
if (signal?.aborted)
|
|
7913
|
+
return Promise.resolve();
|
|
7914
|
+
return new Promise((resolve) => {
|
|
7915
|
+
const done = () => {
|
|
7916
|
+
clearTimeout(timer);
|
|
7917
|
+
signal?.removeEventListener("abort", done);
|
|
7918
|
+
resolve();
|
|
7919
|
+
};
|
|
7920
|
+
const timer = setTimeout(done, ms);
|
|
7921
|
+
signal?.addEventListener("abort", done, { once: true });
|
|
7922
|
+
});
|
|
7890
7923
|
}
|
|
7891
|
-
function
|
|
7924
|
+
function formatWatchWaited(elapsedMs, limitMs, capMs) {
|
|
7925
|
+
let limit;
|
|
7926
|
+
if (limitMs === undefined)
|
|
7927
|
+
limit = "no limit: waits until the command finishes";
|
|
7928
|
+
else if (capMs !== undefined && limitMs >= capMs)
|
|
7929
|
+
limit = `limit ${limitMs}ms, the bash.watch_sync_max_ms cap`;
|
|
7930
|
+
else
|
|
7931
|
+
limit = `limit ${limitMs}ms`;
|
|
7932
|
+
return `Waited ${Math.round(elapsedMs)}ms (${limit})`;
|
|
7933
|
+
}
|
|
7934
|
+
function watchTimeoutSteer(role, timeoutParam = "timeoutMs") {
|
|
7892
7935
|
if (role === "worker") {
|
|
7893
|
-
return `The command is still running; this is not a failure. Watch again
|
|
7936
|
+
return `The command is still running; this is not a failure. Watch again and don't report a result until it finishes; a bash_watch without ${timeoutParam} waits until the command finishes.`;
|
|
7894
7937
|
}
|
|
7895
7938
|
return "The command is still running; this is not a failure. Watch again, do other work, or end your turn: the completion reminder wakes you.";
|
|
7896
7939
|
}
|
|
7940
|
+
var WATCH_UNAVAILABLE_GIVE_UP_MS = 120000;
|
|
7941
|
+
var WORKER_KEEP_WAITING = "Call bash_watch again to keep waiting; don't report a result until the command finishes.";
|
|
7942
|
+
function interruptedWatchTail(role, primaryTail) {
|
|
7943
|
+
return role === "worker" ? `The task is still running. ${WORKER_KEEP_WAITING}` : primaryTail;
|
|
7944
|
+
}
|
|
7945
|
+
function workerBackgroundTaskNote(taskId, taskIdArg = "taskId") {
|
|
7946
|
+
return `
|
|
7947
|
+
|
|
7948
|
+
NOTE (subagent session): Continue with other work if you have it. If you don't, call bash_watch({ ${taskIdArg}: "${taskId}" }) to wait for completion before returning to the parent; without a timeout it waits until the command finishes. Subagents don't survive turn-end and won't be woken when the command finishes.`;
|
|
7949
|
+
}
|
|
7950
|
+
function runningTaskStatusHint(role) {
|
|
7951
|
+
return role === "worker" ? "To wait for it, call bash_watch; don't poll." : "A completion reminder will be delivered automatically; don't poll.";
|
|
7952
|
+
}
|
|
7953
|
+
function watchUnavailableSteer(role) {
|
|
7954
|
+
const unknown = "the bridge was busy, so task state is unknown.";
|
|
7955
|
+
if (role === "worker")
|
|
7956
|
+
return `${unknown} ${WORKER_KEEP_WAITING}`;
|
|
7957
|
+
return `${unknown} Do not poll; let the task's completion notification wake the session, or use one bash_status snapshot on the next normal tool call.`;
|
|
7958
|
+
}
|
|
7897
7959
|
var CONFLICT_HINT = `
|
|
7898
7960
|
|
|
7899
7961
|
[Hint] Use aft_conflicts to see all conflict regions across files in a single call.`;
|
|
@@ -8274,7 +8336,7 @@ import { existsSync } from "node:fs";
|
|
|
8274
8336
|
import { join as join3 } from "node:path";
|
|
8275
8337
|
|
|
8276
8338
|
// ../aft-bridge/dist/child-process.js
|
|
8277
|
-
import { execFileSync as nodeExecFileSync, execSync as nodeExecSync, spawn as nodeSpawn, spawnSync as nodeSpawnSync } from "node:child_process";
|
|
8339
|
+
import { execFile as nodeExecFile, execFileSync as nodeExecFileSync, execSync as nodeExecSync, spawn as nodeSpawn, spawnSync as nodeSpawnSync } from "node:child_process";
|
|
8278
8340
|
var HIDDEN = { windowsHide: true };
|
|
8279
8341
|
function withWindowsHidden(args, takesArgv) {
|
|
8280
8342
|
const out = [...args];
|
|
@@ -8296,6 +8358,7 @@ function hidden(resolve, takesArgv) {
|
|
|
8296
8358
|
}
|
|
8297
8359
|
var spawn = hidden(() => nodeSpawn, true);
|
|
8298
8360
|
var spawnSync = hidden(() => nodeSpawnSync, true);
|
|
8361
|
+
var execFile = hidden(() => nodeExecFile, true);
|
|
8299
8362
|
var execFileSync = hidden(() => nodeExecFileSync, true);
|
|
8300
8363
|
var execSync = hidden(() => nodeExecSync, false);
|
|
8301
8364
|
|
|
@@ -8751,6 +8814,9 @@ function isPassiveCommand(command) {
|
|
|
8751
8814
|
return PASSIVE_COMMANDS.has(command);
|
|
8752
8815
|
}
|
|
8753
8816
|
|
|
8817
|
+
// ../aft-bridge/dist/transport.js
|
|
8818
|
+
var WORKER_SESSION_FIELD = "worker_session";
|
|
8819
|
+
|
|
8754
8820
|
// ../aft-bridge/dist/bridge.js
|
|
8755
8821
|
var DEFAULT_BRIDGE_TIMEOUT_MS = 30000;
|
|
8756
8822
|
var BRIDGE_HANG_TIMEOUT_THRESHOLD = 2;
|
|
@@ -9169,9 +9235,11 @@ class BinaryBridge {
|
|
|
9169
9235
|
params.edit_slot_survives = editSlotSurvives;
|
|
9170
9236
|
this.logHashlineRegistrationCarrier("tool_call", sessionId, editSlotSurvives);
|
|
9171
9237
|
}
|
|
9172
|
-
const { preview, ...sendOptions } = options ?? {};
|
|
9238
|
+
const { preview, workerSession, ...sendOptions } = options ?? {};
|
|
9173
9239
|
if (preview === true)
|
|
9174
9240
|
params.preview = true;
|
|
9241
|
+
if (workerSession === true)
|
|
9242
|
+
params[WORKER_SESSION_FIELD] = true;
|
|
9175
9243
|
return await this.send("tool_call", params, Object.keys(sendOptions).length > 0 ? sendOptions : undefined);
|
|
9176
9244
|
}
|
|
9177
9245
|
async sendWithVersionMismatchRetry(command, params, options, canRetryAfterVersionSwap) {
|
|
@@ -9240,7 +9308,7 @@ class BinaryBridge {
|
|
|
9240
9308
|
if (command === "cancel_request" || Object.hasOwn(params, "command") || Object.hasOwn(params, "method")) {
|
|
9241
9309
|
const nested = { ...params };
|
|
9242
9310
|
const reserved = {};
|
|
9243
|
-
for (const key of ["session_id", "lsp_hints"]) {
|
|
9311
|
+
for (const key of ["session_id", "lsp_hints", WORKER_SESSION_FIELD]) {
|
|
9244
9312
|
if (Object.hasOwn(nested, key)) {
|
|
9245
9313
|
reserved[key] = nested[key];
|
|
9246
9314
|
delete nested[key];
|
|
@@ -9622,17 +9690,30 @@ class BinaryBridge {
|
|
|
9622
9690
|
}
|
|
9623
9691
|
}
|
|
9624
9692
|
onStderrData(data) {
|
|
9625
|
-
|
|
9626
|
-
let
|
|
9627
|
-
while (
|
|
9628
|
-
|
|
9629
|
-
|
|
9630
|
-
|
|
9631
|
-
|
|
9632
|
-
|
|
9633
|
-
|
|
9634
|
-
|
|
9635
|
-
|
|
9693
|
+
const partialLimit = 64 * 1024;
|
|
9694
|
+
let offset = 0;
|
|
9695
|
+
while (offset < data.length) {
|
|
9696
|
+
const newline = data.indexOf(`
|
|
9697
|
+
`, offset);
|
|
9698
|
+
const end = newline < 0 ? data.length : newline;
|
|
9699
|
+
while (offset < end) {
|
|
9700
|
+
const count = Math.min(partialLimit - this.stderrBuffer.length, end - offset);
|
|
9701
|
+
this.stderrBuffer += data.slice(offset, offset + count);
|
|
9702
|
+
offset += count;
|
|
9703
|
+
if (this.stderrBuffer.length === partialLimit) {
|
|
9704
|
+
const last = this.stderrBuffer.charCodeAt(this.stderrBuffer.length - 1);
|
|
9705
|
+
const carry = last >= 55296 && last <= 56319 ? this.stderrBuffer.slice(-1) : "";
|
|
9706
|
+
const fragment = carry ? this.stderrBuffer.slice(0, -1) : this.stderrBuffer;
|
|
9707
|
+
const tagged = tagStderrLine(`${fragment} [stderr record truncated into bounded fragments; ${Buffer.byteLength(fragment)} bytes shown, continuation follows]`);
|
|
9708
|
+
this.stderrBuffer = carry;
|
|
9709
|
+
this.logVia(tagged);
|
|
9710
|
+
this.pushStderrLine(tagged);
|
|
9711
|
+
}
|
|
9712
|
+
}
|
|
9713
|
+
if (newline < 0)
|
|
9714
|
+
break;
|
|
9715
|
+
this.flushStderrBuffer();
|
|
9716
|
+
offset = newline + 1;
|
|
9636
9717
|
}
|
|
9637
9718
|
}
|
|
9638
9719
|
flushStderrBuffer() {
|
|
@@ -10005,6 +10086,35 @@ function coerceJsonCollectionParam(value, field) {
|
|
|
10005
10086
|
}
|
|
10006
10087
|
throw new Error(`${field} must be ${shape} (got an invalid type)`);
|
|
10007
10088
|
}
|
|
10089
|
+
function isBlankParam(value) {
|
|
10090
|
+
if (typeof value === "string")
|
|
10091
|
+
return value.trim().length === 0;
|
|
10092
|
+
return isEmptyParam(value);
|
|
10093
|
+
}
|
|
10094
|
+
function usableZoomTargets(targets) {
|
|
10095
|
+
if (isEmptyParam(targets))
|
|
10096
|
+
return [];
|
|
10097
|
+
const entries = Array.isArray(targets) ? targets : [targets];
|
|
10098
|
+
return entries.filter((entry) => {
|
|
10099
|
+
if (entry === null || entry === undefined)
|
|
10100
|
+
return false;
|
|
10101
|
+
if (typeof entry !== "object")
|
|
10102
|
+
return true;
|
|
10103
|
+
const record = entry;
|
|
10104
|
+
return !(isBlankParam(record.path) && isBlankParam(record.filePath) && isBlankParam(record.symbol));
|
|
10105
|
+
});
|
|
10106
|
+
}
|
|
10107
|
+
function isFindReplaceOnlyEdit(args) {
|
|
10108
|
+
const absent = (value) => value === undefined || value === null || value === "";
|
|
10109
|
+
const absentLine = (value) => absent(value) || value === 0;
|
|
10110
|
+
const edits = args.edits;
|
|
10111
|
+
return Array.isArray(edits) && edits.length > 0 && absent(args.appendContent) && absent(args.symbol) && absent(args.content) && edits.every((entry) => {
|
|
10112
|
+
if (entry === null || typeof entry !== "object")
|
|
10113
|
+
return false;
|
|
10114
|
+
const item = entry;
|
|
10115
|
+
return typeof item.oldString === "string" && (absent(item.newString) || typeof item.newString === "string") && absentLine(item.startLine) && absentLine(item.endLine) && absent(item.content);
|
|
10116
|
+
});
|
|
10117
|
+
}
|
|
10008
10118
|
// ../aft-bridge/dist/config-error.js
|
|
10009
10119
|
import { homedir as homedir5 } from "node:os";
|
|
10010
10120
|
import { isAbsolute as isAbsolute2, join as join6 } from "node:path";
|
|
@@ -10121,9 +10231,366 @@ function formatDroppedKeyWarnings(dropped) {
|
|
|
10121
10231
|
}
|
|
10122
10232
|
return dropped.map((item) => `Ignoring ${item.key} from ${item.tier} config (${item.reason})`);
|
|
10123
10233
|
}
|
|
10234
|
+
// ../aft-bridge/dist/config-watch.js
|
|
10235
|
+
import { readFileSync as readFileSync3, statSync as statSync3, watch } from "node:fs";
|
|
10236
|
+
import { basename, dirname } from "node:path";
|
|
10237
|
+
var CONFIG_WATCH_DEBOUNCE_MS = 150;
|
|
10238
|
+
var CONFIG_LIVE_KEEP_NOTE = "AFT keeps using the last valid configuration until the file is fixed.";
|
|
10239
|
+
function pathKey(name) {
|
|
10240
|
+
const segments = name.split(".");
|
|
10241
|
+
return {
|
|
10242
|
+
name,
|
|
10243
|
+
read(config) {
|
|
10244
|
+
let value = config;
|
|
10245
|
+
for (const segment of segments) {
|
|
10246
|
+
if (value === null || typeof value !== "object")
|
|
10247
|
+
return;
|
|
10248
|
+
value = value[segment];
|
|
10249
|
+
}
|
|
10250
|
+
return value;
|
|
10251
|
+
},
|
|
10252
|
+
write(config, value) {
|
|
10253
|
+
const set = (target, index) => {
|
|
10254
|
+
const copy = target !== null && typeof target === "object" && !Array.isArray(target) ? { ...target } : {};
|
|
10255
|
+
const segment = segments[index];
|
|
10256
|
+
if (index === segments.length - 1) {
|
|
10257
|
+
if (value === undefined)
|
|
10258
|
+
delete copy[segment];
|
|
10259
|
+
else
|
|
10260
|
+
copy[segment] = value;
|
|
10261
|
+
} else {
|
|
10262
|
+
copy[segment] = set(copy[segment], index + 1);
|
|
10263
|
+
}
|
|
10264
|
+
return copy;
|
|
10265
|
+
};
|
|
10266
|
+
return set(config, 0);
|
|
10267
|
+
}
|
|
10268
|
+
};
|
|
10269
|
+
}
|
|
10270
|
+
function bashKey(name, resolveBash) {
|
|
10271
|
+
return {
|
|
10272
|
+
name: `bash.${name}`,
|
|
10273
|
+
read: (config) => resolveBash(config)[name],
|
|
10274
|
+
write(config, value) {
|
|
10275
|
+
const resolved = { ...resolveBash(config), [name]: value };
|
|
10276
|
+
for (const key of Object.keys(resolved)) {
|
|
10277
|
+
if (resolved[key] === undefined)
|
|
10278
|
+
delete resolved[key];
|
|
10279
|
+
}
|
|
10280
|
+
return { ...config, bash: resolved };
|
|
10281
|
+
}
|
|
10282
|
+
};
|
|
10283
|
+
}
|
|
10284
|
+
function aftLiveConfigKeys(resolveBash) {
|
|
10285
|
+
return [
|
|
10286
|
+
pathKey("configure_warnings_delivery"),
|
|
10287
|
+
pathKey("restrict_to_project_root"),
|
|
10288
|
+
pathKey("inspect.diagnostics_timeout_ms"),
|
|
10289
|
+
pathKey("inspect.tier2_idle_minutes"),
|
|
10290
|
+
bashKey("foreground_wait_window_ms", resolveBash),
|
|
10291
|
+
bashKey("host_fallback", resolveBash),
|
|
10292
|
+
bashKey("subagent_background", resolveBash),
|
|
10293
|
+
bashKey("watch_sync_max_ms", resolveBash)
|
|
10294
|
+
];
|
|
10295
|
+
}
|
|
10296
|
+
function sameValue(a, b) {
|
|
10297
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
10298
|
+
}
|
|
10299
|
+
function flattenLeaves(value, prefix, out) {
|
|
10300
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
10301
|
+
const entries = Object.entries(value);
|
|
10302
|
+
if (entries.length === 0 && prefix)
|
|
10303
|
+
out.set(prefix, "{}");
|
|
10304
|
+
for (const [key, child] of entries) {
|
|
10305
|
+
flattenLeaves(child, prefix ? `${prefix}.${key}` : key, out);
|
|
10306
|
+
}
|
|
10307
|
+
return;
|
|
10308
|
+
}
|
|
10309
|
+
if (prefix)
|
|
10310
|
+
out.set(prefix, JSON.stringify(value));
|
|
10311
|
+
}
|
|
10312
|
+
function applyLiveConfigKeys(current, next, keys, baseline = current) {
|
|
10313
|
+
let config = current;
|
|
10314
|
+
const applied = [];
|
|
10315
|
+
for (const key of keys) {
|
|
10316
|
+
const value = key.read(next);
|
|
10317
|
+
if (sameValue(key.read(current), value))
|
|
10318
|
+
continue;
|
|
10319
|
+
config = key.write(config, value);
|
|
10320
|
+
applied.push(key.name);
|
|
10321
|
+
}
|
|
10322
|
+
const before = new Map;
|
|
10323
|
+
const after = new Map;
|
|
10324
|
+
flattenLeaves(baseline, "", before);
|
|
10325
|
+
flattenLeaves(next, "", after);
|
|
10326
|
+
const liveNames = keys.map((key) => key.name);
|
|
10327
|
+
const isLive = (leaf) => liveNames.some((name) => leaf === name || leaf.startsWith(`${name}.`));
|
|
10328
|
+
const deferred = new Set;
|
|
10329
|
+
for (const leaf of new Set([...before.keys(), ...after.keys()])) {
|
|
10330
|
+
if (before.get(leaf) === after.get(leaf) || isLive(leaf))
|
|
10331
|
+
continue;
|
|
10332
|
+
deferred.add(leaf);
|
|
10333
|
+
}
|
|
10334
|
+
return { config, applied, deferred: [...deferred].sort() };
|
|
10335
|
+
}
|
|
10336
|
+
function readTextOrNull(path) {
|
|
10337
|
+
try {
|
|
10338
|
+
return readFileSync3(path, "utf8");
|
|
10339
|
+
} catch {
|
|
10340
|
+
return null;
|
|
10341
|
+
}
|
|
10342
|
+
}
|
|
10343
|
+
var CONFIG_WATCH_REVALIDATE_MS = 2000;
|
|
10344
|
+
var CONFIG_WATCH_REJECTED_RETRIES = 3;
|
|
10345
|
+
var CONFIG_WATCH_MAX_DELAY_MS = 1000;
|
|
10346
|
+
function identityOf(dir) {
|
|
10347
|
+
try {
|
|
10348
|
+
const stat = statSync3(dir, { bigint: true });
|
|
10349
|
+
const base = `${stat.dev}:${stat.ino}`;
|
|
10350
|
+
return process.platform === "win32" ? base : `${base}:${stat.birthtimeNs}`;
|
|
10351
|
+
} catch {
|
|
10352
|
+
return null;
|
|
10353
|
+
}
|
|
10354
|
+
}
|
|
10355
|
+
function watchDirFor(file) {
|
|
10356
|
+
let dir = dirname(file);
|
|
10357
|
+
while (dir !== dirname(dir) && identityOf(dir) === null)
|
|
10358
|
+
dir = dirname(dir);
|
|
10359
|
+
return dir;
|
|
10360
|
+
}
|
|
10361
|
+
function watchAftConfigFiles(options) {
|
|
10362
|
+
const debounceMs = options.debounceMs ?? CONFIG_WATCH_DEBOUNCE_MS;
|
|
10363
|
+
const lastSeen = new Map;
|
|
10364
|
+
for (const path of options.paths) {
|
|
10365
|
+
if (options.initialTexts)
|
|
10366
|
+
lastSeen.set(path, options.initialTexts[path] ?? null);
|
|
10367
|
+
else
|
|
10368
|
+
lastSeen.set(path, options.checkAtStart ? undefined : readTextOrNull(path));
|
|
10369
|
+
}
|
|
10370
|
+
let timer = null;
|
|
10371
|
+
let firstPendingAt = null;
|
|
10372
|
+
let stopped = false;
|
|
10373
|
+
const watchers = new Map;
|
|
10374
|
+
const watchImpl = options.watchImpl ?? watch;
|
|
10375
|
+
let rejectedRetries = 0;
|
|
10376
|
+
const check = () => {
|
|
10377
|
+
timer = null;
|
|
10378
|
+
firstPendingAt = null;
|
|
10379
|
+
if (stopped)
|
|
10380
|
+
return;
|
|
10381
|
+
revalidate();
|
|
10382
|
+
const seen = new Map;
|
|
10383
|
+
for (const path of options.paths) {
|
|
10384
|
+
const text = readTextOrNull(path);
|
|
10385
|
+
if (text !== lastSeen.get(path))
|
|
10386
|
+
seen.set(path, text);
|
|
10387
|
+
}
|
|
10388
|
+
if (seen.size === 0)
|
|
10389
|
+
return;
|
|
10390
|
+
const outcome = options.onChange();
|
|
10391
|
+
if (outcome === false) {
|
|
10392
|
+
if (rejectedRetries < CONFIG_WATCH_REJECTED_RETRIES) {
|
|
10393
|
+
rejectedRetries += 1;
|
|
10394
|
+
retryTimer = setTimeout(check, CONFIG_WATCH_MAX_DELAY_MS);
|
|
10395
|
+
retryTimer.unref?.();
|
|
10396
|
+
}
|
|
10397
|
+
return;
|
|
10398
|
+
}
|
|
10399
|
+
rejectedRetries = 0;
|
|
10400
|
+
if (outcome instanceof Map) {
|
|
10401
|
+
for (const path of options.paths)
|
|
10402
|
+
lastSeen.set(path, outcome.get(path) ?? null);
|
|
10403
|
+
} else {
|
|
10404
|
+
for (const [path, text] of seen)
|
|
10405
|
+
lastSeen.set(path, text);
|
|
10406
|
+
}
|
|
10407
|
+
};
|
|
10408
|
+
let retryTimer = null;
|
|
10409
|
+
const schedule = () => {
|
|
10410
|
+
if (stopped)
|
|
10411
|
+
return;
|
|
10412
|
+
const now = Date.now();
|
|
10413
|
+
firstPendingAt ??= now;
|
|
10414
|
+
if (timer)
|
|
10415
|
+
clearTimeout(timer);
|
|
10416
|
+
const delay = Math.max(0, Math.min(debounceMs, firstPendingAt + CONFIG_WATCH_MAX_DELAY_MS - now));
|
|
10417
|
+
timer = setTimeout(check, delay);
|
|
10418
|
+
timer.unref?.();
|
|
10419
|
+
};
|
|
10420
|
+
function revalidate() {
|
|
10421
|
+
const wanted = new Set(options.paths.map(watchDirFor));
|
|
10422
|
+
let moved = false;
|
|
10423
|
+
for (const [dir, entry] of watchers) {
|
|
10424
|
+
if (!wanted.has(dir) || entry.stale || identityOf(dir) !== entry.identity) {
|
|
10425
|
+
entry.watcher.close();
|
|
10426
|
+
watchers.delete(dir);
|
|
10427
|
+
moved = true;
|
|
10428
|
+
}
|
|
10429
|
+
}
|
|
10430
|
+
for (const dir of wanted) {
|
|
10431
|
+
if (watchers.has(dir))
|
|
10432
|
+
continue;
|
|
10433
|
+
try {
|
|
10434
|
+
const identity = identityOf(dir);
|
|
10435
|
+
options.beforeWatchForTest?.(dir);
|
|
10436
|
+
const name = basename(dir);
|
|
10437
|
+
const watcher = watchImpl(dir, { persistent: false }, (event, filename) => {
|
|
10438
|
+
if (event === "rename" && filename === name) {
|
|
10439
|
+
const current = watchers.get(dir);
|
|
10440
|
+
if (current && current.watcher === watcher)
|
|
10441
|
+
current.stale = true;
|
|
10442
|
+
}
|
|
10443
|
+
schedule();
|
|
10444
|
+
});
|
|
10445
|
+
if (identityOf(dir) !== identity) {
|
|
10446
|
+
watcher.close();
|
|
10447
|
+
moved = true;
|
|
10448
|
+
continue;
|
|
10449
|
+
}
|
|
10450
|
+
const entry = { watcher, identity, stale: false };
|
|
10451
|
+
entry.watcher.on("error", () => {
|
|
10452
|
+
entry.watcher.close();
|
|
10453
|
+
if (watchers.get(dir) === entry)
|
|
10454
|
+
watchers.delete(dir);
|
|
10455
|
+
schedule();
|
|
10456
|
+
});
|
|
10457
|
+
watchers.set(dir, entry);
|
|
10458
|
+
moved = true;
|
|
10459
|
+
} catch {}
|
|
10460
|
+
}
|
|
10461
|
+
return moved;
|
|
10462
|
+
}
|
|
10463
|
+
revalidate();
|
|
10464
|
+
if (options.checkAtStart)
|
|
10465
|
+
check();
|
|
10466
|
+
const revalidateTimer = setInterval(() => {
|
|
10467
|
+
if (!stopped && revalidate())
|
|
10468
|
+
schedule();
|
|
10469
|
+
}, CONFIG_WATCH_REVALIDATE_MS);
|
|
10470
|
+
revalidateTimer.unref?.();
|
|
10471
|
+
return () => {
|
|
10472
|
+
stopped = true;
|
|
10473
|
+
if (timer)
|
|
10474
|
+
clearTimeout(timer);
|
|
10475
|
+
if (retryTimer)
|
|
10476
|
+
clearTimeout(retryTimer);
|
|
10477
|
+
clearInterval(revalidateTimer);
|
|
10478
|
+
for (const entry of watchers.values())
|
|
10479
|
+
entry.watcher.close();
|
|
10480
|
+
watchers.clear();
|
|
10481
|
+
};
|
|
10482
|
+
}
|
|
10483
|
+
function aftLiveSecurityKeys(keys) {
|
|
10484
|
+
const byName = new Map(keys.map((key) => [key.name, key]));
|
|
10485
|
+
const pick = (name) => {
|
|
10486
|
+
const key = byName.get(name);
|
|
10487
|
+
if (!key)
|
|
10488
|
+
throw new Error(`unknown live config key ${name}`);
|
|
10489
|
+
return key;
|
|
10490
|
+
};
|
|
10491
|
+
return [
|
|
10492
|
+
{
|
|
10493
|
+
key: pick("restrict_to_project_root"),
|
|
10494
|
+
loosens: (current, next) => current === true && next !== true
|
|
10495
|
+
},
|
|
10496
|
+
{
|
|
10497
|
+
key: pick("bash.host_fallback"),
|
|
10498
|
+
loosens: (current, next) => current !== true && next === true
|
|
10499
|
+
}
|
|
10500
|
+
];
|
|
10501
|
+
}
|
|
10502
|
+
function liveConfigReloadLogLine(applied, deferred) {
|
|
10503
|
+
let line = "config reload";
|
|
10504
|
+
if (applied.length > 0)
|
|
10505
|
+
line += ` applied=[${applied.join(",")}]`;
|
|
10506
|
+
if (deferred.length > 0) {
|
|
10507
|
+
line += ` deferred=[${deferred.join(",")}] (deferred keys apply on next connect/restart)`;
|
|
10508
|
+
}
|
|
10509
|
+
return line;
|
|
10510
|
+
}
|
|
10511
|
+
function startLiveConfigReload(options) {
|
|
10512
|
+
const baseline = options.getConfig();
|
|
10513
|
+
let lastError = null;
|
|
10514
|
+
let acceptedSources = new Set(options.initialSources);
|
|
10515
|
+
let acceptedProjectText = options.initialProjectText ?? null;
|
|
10516
|
+
let acceptedTexts;
|
|
10517
|
+
const reload = () => {
|
|
10518
|
+
let loaded = options.load();
|
|
10519
|
+
if (loaded.ok) {
|
|
10520
|
+
const read = new Set(loaded.sources);
|
|
10521
|
+
const deleted = [...acceptedSources].find((path) => !read.has(path));
|
|
10522
|
+
if (deleted !== undefined) {
|
|
10523
|
+
loaded = { ok: false, message: `AFT config at ${deleted} was deleted` };
|
|
10524
|
+
} else {
|
|
10525
|
+
acceptedSources = read;
|
|
10526
|
+
}
|
|
10527
|
+
}
|
|
10528
|
+
if (!loaded.ok) {
|
|
10529
|
+
const message = `${loaded.message.trim().replace(/[.!?]?$/, ".")} ${CONFIG_LIVE_KEEP_NOTE}`;
|
|
10530
|
+
if (message !== lastError) {
|
|
10531
|
+
lastError = message;
|
|
10532
|
+
options.reportError(message);
|
|
10533
|
+
}
|
|
10534
|
+
return null;
|
|
10535
|
+
}
|
|
10536
|
+
lastError = null;
|
|
10537
|
+
acceptedTexts = loaded.texts;
|
|
10538
|
+
const current = options.getConfig();
|
|
10539
|
+
let next = loaded.config;
|
|
10540
|
+
const held = [];
|
|
10541
|
+
const projectText = loaded.projectText ?? null;
|
|
10542
|
+
if (loaded.projectText !== undefined && projectText !== acceptedProjectText) {
|
|
10543
|
+
for (const security of options.securityKeys ?? []) {
|
|
10544
|
+
const now = security.key.read(current);
|
|
10545
|
+
if (security.loosens(now, security.key.read(next))) {
|
|
10546
|
+
next = security.key.write(next, now);
|
|
10547
|
+
held.push(security.key.name);
|
|
10548
|
+
}
|
|
10549
|
+
}
|
|
10550
|
+
if (held.length === 0)
|
|
10551
|
+
acceptedProjectText = projectText;
|
|
10552
|
+
}
|
|
10553
|
+
const result = applyLiveConfigKeys(current, next, options.keys, baseline);
|
|
10554
|
+
if (result.applied.length > 0)
|
|
10555
|
+
options.setConfig(result.config);
|
|
10556
|
+
if (result.applied.length > 0 || result.deferred.length > 0 || held.length > 0) {
|
|
10557
|
+
let line = liveConfigReloadLogLine(result.applied, result.deferred);
|
|
10558
|
+
if (held.length > 0) {
|
|
10559
|
+
line += ` held=[${held.join(",")}] (not loosened while the project file differs from the one the published values came from; the next restart applies them)`;
|
|
10560
|
+
}
|
|
10561
|
+
options.log(line);
|
|
10562
|
+
}
|
|
10563
|
+
return { ...result, held };
|
|
10564
|
+
};
|
|
10565
|
+
const safeReload = () => {
|
|
10566
|
+
try {
|
|
10567
|
+
acceptedTexts = undefined;
|
|
10568
|
+
if (reload() === null)
|
|
10569
|
+
return false;
|
|
10570
|
+
const texts = acceptedTexts;
|
|
10571
|
+
if (!texts)
|
|
10572
|
+
return true;
|
|
10573
|
+
return new Map(options.paths.map((path) => [path, texts[path] ?? null]));
|
|
10574
|
+
} catch (err) {
|
|
10575
|
+
options.reportError(`AFT config reload failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
10576
|
+
return false;
|
|
10577
|
+
}
|
|
10578
|
+
};
|
|
10579
|
+
if (options.watch === false) {
|
|
10580
|
+
return { reload, stop: () => {} };
|
|
10581
|
+
}
|
|
10582
|
+
const stop = watchAftConfigFiles({
|
|
10583
|
+
paths: options.paths,
|
|
10584
|
+
debounceMs: options.debounceMs,
|
|
10585
|
+
onChange: safeReload,
|
|
10586
|
+
initialTexts: options.initialTexts,
|
|
10587
|
+
checkAtStart: true
|
|
10588
|
+
});
|
|
10589
|
+
return { reload, stop };
|
|
10590
|
+
}
|
|
10124
10591
|
// ../aft-bridge/dist/downloader.js
|
|
10125
10592
|
import { createHash as createHash2, randomUUID } from "node:crypto";
|
|
10126
|
-
import { chmodSync, closeSync, copyFileSync, createWriteStream, existsSync as existsSync3, mkdirSync, openSync, readdirSync, readFileSync as
|
|
10593
|
+
import { chmodSync, closeSync, copyFileSync, createWriteStream, existsSync as existsSync3, mkdirSync, openSync, readdirSync, readFileSync as readFileSync4, renameSync as renameSync2, rmSync, statSync as statSync4, unlinkSync as unlinkSync2, writeSync } from "node:fs";
|
|
10127
10594
|
import { hostname } from "node:os";
|
|
10128
10595
|
import { join as join7 } from "node:path";
|
|
10129
10596
|
import { Readable } from "node:stream";
|
|
@@ -10475,7 +10942,7 @@ function isReclaimableDownloadLock(owner, ageMs, staleMs) {
|
|
|
10475
10942
|
}
|
|
10476
10943
|
function reclaimDownloadLock(lockPath, expectedOwner) {
|
|
10477
10944
|
try {
|
|
10478
|
-
if (
|
|
10945
|
+
if (readFileSync4(lockPath, "utf-8") !== expectedOwner)
|
|
10479
10946
|
return false;
|
|
10480
10947
|
rmSync(lockPath, { force: true });
|
|
10481
10948
|
return true;
|
|
@@ -10493,7 +10960,7 @@ function sweepStaleDownloadTemps(versionedCacheDir, binaryName) {
|
|
|
10493
10960
|
continue;
|
|
10494
10961
|
}
|
|
10495
10962
|
const tempPath = join7(versionedCacheDir, entry.name);
|
|
10496
|
-
const ageMs = Date.now() -
|
|
10963
|
+
const ageMs = Date.now() - statSync4(tempPath).mtimeMs;
|
|
10497
10964
|
if (Math.abs(ageMs) > DOWNLOAD_LOCK_STALE_MS)
|
|
10498
10965
|
unlinkSync2(tempPath);
|
|
10499
10966
|
}
|
|
@@ -10515,7 +10982,7 @@ async function acquireDownloadLock(lockPath, timing = {}) {
|
|
|
10515
10982
|
}
|
|
10516
10983
|
return () => {
|
|
10517
10984
|
try {
|
|
10518
|
-
if (
|
|
10985
|
+
if (readFileSync4(lockPath, "utf-8") === owner) {
|
|
10519
10986
|
rmSync(lockPath, { force: true });
|
|
10520
10987
|
}
|
|
10521
10988
|
} catch {}
|
|
@@ -10527,8 +10994,8 @@ async function acquireDownloadLock(lockPath, timing = {}) {
|
|
|
10527
10994
|
let existingOwner;
|
|
10528
10995
|
let ageMs;
|
|
10529
10996
|
try {
|
|
10530
|
-
existingOwner =
|
|
10531
|
-
ageMs = Date.now() -
|
|
10997
|
+
existingOwner = readFileSync4(lockPath, "utf-8");
|
|
10998
|
+
ageMs = Date.now() - statSync4(lockPath).mtimeMs;
|
|
10532
10999
|
} catch (readErr) {
|
|
10533
11000
|
if (readErr.code === "ENOENT")
|
|
10534
11001
|
continue;
|
|
@@ -10578,7 +11045,7 @@ async function fetchLatestTag() {
|
|
|
10578
11045
|
}
|
|
10579
11046
|
// ../aft-bridge/dist/durable-log.js
|
|
10580
11047
|
import { appendFile, chmod, mkdir, rename, rm, stat } from "node:fs/promises";
|
|
10581
|
-
import { dirname } from "node:path";
|
|
11048
|
+
import { dirname as dirname2 } from "node:path";
|
|
10582
11049
|
var DEFAULT_LOG_BYTES = 32 * 1024 * 1024;
|
|
10583
11050
|
var DEFAULT_LOG_GENERATIONS = 1;
|
|
10584
11051
|
var LOG_DIR_MODE = 448;
|
|
@@ -10591,6 +11058,10 @@ class RotatingLogSink {
|
|
|
10591
11058
|
queue = Promise.resolve();
|
|
10592
11059
|
disabled = false;
|
|
10593
11060
|
failureReported = false;
|
|
11061
|
+
queuedBytes = 0;
|
|
11062
|
+
queuedRecords = 0;
|
|
11063
|
+
droppedBytes = 0;
|
|
11064
|
+
overflowQueued = false;
|
|
10594
11065
|
constructor(path, options = {}) {
|
|
10595
11066
|
this.path = path;
|
|
10596
11067
|
this.maxBytes = options.maxBytes ?? DEFAULT_LOG_BYTES;
|
|
@@ -10599,7 +11070,34 @@ class RotatingLogSink {
|
|
|
10599
11070
|
append(data) {
|
|
10600
11071
|
if (this.disabled || data.length === 0)
|
|
10601
11072
|
return;
|
|
10602
|
-
|
|
11073
|
+
const bytes = Buffer.byteLength(data);
|
|
11074
|
+
const overflow = this.queuedBytes + bytes > 1024 * 1024 || this.queuedRecords >= 4096;
|
|
11075
|
+
if (overflow) {
|
|
11076
|
+
this.droppedBytes += bytes;
|
|
11077
|
+
if (this.overflowQueued)
|
|
11078
|
+
return;
|
|
11079
|
+
this.overflowQueued = true;
|
|
11080
|
+
data = "";
|
|
11081
|
+
} else {
|
|
11082
|
+
this.queuedBytes += bytes;
|
|
11083
|
+
this.queuedRecords += 1;
|
|
11084
|
+
}
|
|
11085
|
+
this.queue = this.queue.then(async () => {
|
|
11086
|
+
if (overflow) {
|
|
11087
|
+
const dropped = this.droppedBytes;
|
|
11088
|
+
this.droppedBytes = 0;
|
|
11089
|
+
this.overflowQueued = false;
|
|
11090
|
+
await this.write(`[aft-plugin] durable log queue overflow: dropped ${dropped} bytes (1MiB/4096-record pending limit); reduce diagnostic volume.
|
|
11091
|
+
`);
|
|
11092
|
+
} else {
|
|
11093
|
+
try {
|
|
11094
|
+
await this.write(data);
|
|
11095
|
+
} finally {
|
|
11096
|
+
this.queuedBytes -= bytes;
|
|
11097
|
+
this.queuedRecords -= 1;
|
|
11098
|
+
}
|
|
11099
|
+
}
|
|
11100
|
+
}).catch((error) => {
|
|
10603
11101
|
this.disabled = true;
|
|
10604
11102
|
if (!this.failureReported) {
|
|
10605
11103
|
this.failureReported = true;
|
|
@@ -10614,8 +11112,8 @@ class RotatingLogSink {
|
|
|
10614
11112
|
await this.queue;
|
|
10615
11113
|
}
|
|
10616
11114
|
async write(data) {
|
|
10617
|
-
const dir =
|
|
10618
|
-
await mkdir(
|
|
11115
|
+
const dir = dirname2(this.path);
|
|
11116
|
+
await mkdir(dirname2(dir), { recursive: true });
|
|
10619
11117
|
try {
|
|
10620
11118
|
await mkdir(dir, { mode: LOG_DIR_MODE });
|
|
10621
11119
|
} catch (error) {
|
|
@@ -12403,7 +12901,7 @@ function reverseRequestMethod(body) {
|
|
|
12403
12901
|
}
|
|
12404
12902
|
}
|
|
12405
12903
|
// ../aft-bridge/dist/subc-transport.js
|
|
12406
|
-
import { existsSync as existsSync4, statSync as
|
|
12904
|
+
import { existsSync as existsSync4, statSync as statSync5 } from "node:fs";
|
|
12407
12905
|
|
|
12408
12906
|
// ../aft-bridge/dist/lifecycle-registry.js
|
|
12409
12907
|
import { lstat } from "node:fs/promises";
|
|
@@ -12879,6 +13377,13 @@ class SubcTransportShuttingDownError extends SubcCallError {
|
|
|
12879
13377
|
this.name = "SubcTransportShuttingDownError";
|
|
12880
13378
|
}
|
|
12881
13379
|
}
|
|
13380
|
+
|
|
13381
|
+
class ReconnectKeyChangedError extends Error {
|
|
13382
|
+
constructor() {
|
|
13383
|
+
super("the subc daemon restarted and its connection key changed; the reconnect did not complete within the deadline, retry");
|
|
13384
|
+
this.name = "ReconnectKeyChangedError";
|
|
13385
|
+
}
|
|
13386
|
+
}
|
|
12882
13387
|
function isSubcClientClosedError(error) {
|
|
12883
13388
|
return error instanceof SubcError && error.message === "client closed";
|
|
12884
13389
|
}
|
|
@@ -12930,6 +13435,7 @@ var RECONNECT_RETRY_FLOOR_MS = 100;
|
|
|
12930
13435
|
var RECONNECT_RETRY_CAP_MS = 2000;
|
|
12931
13436
|
var SUBC_DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
12932
13437
|
var ROUTE_OPEN_RELOAD_WAIT_CEILING_MS = 45000;
|
|
13438
|
+
var CALL_DEADLINE_EXPIRED = Symbol("call deadline expired");
|
|
12933
13439
|
function reloadWaitExhaustedSuffix(callDeadlineMs) {
|
|
12934
13440
|
if (callDeadlineMs > ROUTE_OPEN_RELOAD_WAIT_CEILING_MS) {
|
|
12935
13441
|
const ceilingSeconds = ROUTE_OPEN_RELOAD_WAIT_CEILING_MS / 1000;
|
|
@@ -13282,7 +13788,16 @@ function absentRootError(root) {
|
|
|
13282
13788
|
return new SubcError(`invalid route project root: project root does not exist: ${root}`, "config_divergence");
|
|
13283
13789
|
}
|
|
13284
13790
|
function reloadWindowExhaustedError(error, callDeadlineMs) {
|
|
13285
|
-
|
|
13791
|
+
return withDeadlineSuffix(error, reloadWaitExhaustedSuffix(callDeadlineMs));
|
|
13792
|
+
}
|
|
13793
|
+
function callDeadlinePassedError(lastError, callDeadlineMs) {
|
|
13794
|
+
const seconds = Math.round(callDeadlineMs / 100) / 10;
|
|
13795
|
+
const suffix = ` The AFT daemon module did not return within this call's ${seconds}s deadline.`;
|
|
13796
|
+
if (lastError !== undefined)
|
|
13797
|
+
return withDeadlineSuffix(lastError, suffix);
|
|
13798
|
+
return new SubcError(`route to module '${AFT_MODULE_ID}' was not ready before the call ran out of time.${suffix}`, REQUEST_DEADLINE_CODE);
|
|
13799
|
+
}
|
|
13800
|
+
function withDeadlineSuffix(error, suffix) {
|
|
13286
13801
|
if (error instanceof Error) {
|
|
13287
13802
|
try {
|
|
13288
13803
|
error.message += suffix;
|
|
@@ -13371,6 +13886,8 @@ class SubcTransport {
|
|
|
13371
13886
|
body.edit_slot_survives = editSlotSurvives;
|
|
13372
13887
|
if (preview === true)
|
|
13373
13888
|
body.preview = true;
|
|
13889
|
+
if (options?.workerSession === true)
|
|
13890
|
+
body[WORKER_SESSION_FIELD] = true;
|
|
13374
13891
|
const reply = await this.pool.routeRequest(this.identityFor(sessionId), body, timeoutMs, onProgress, this.generation, abortSignal);
|
|
13375
13892
|
return reliftReply(reply);
|
|
13376
13893
|
}
|
|
@@ -13381,7 +13898,10 @@ class SubcTransport {
|
|
|
13381
13898
|
}
|
|
13382
13899
|
const { timeoutMs, onProgress, abortSignal } = this.splitOptions(options);
|
|
13383
13900
|
const session = typeof params.session_id === "string" ? params.session_id : undefined;
|
|
13384
|
-
const
|
|
13901
|
+
const { [WORKER_SESSION_FIELD]: workerSession, ...args } = params;
|
|
13902
|
+
const body = { name: command, arguments: args };
|
|
13903
|
+
if (workerSession === true)
|
|
13904
|
+
body[WORKER_SESSION_FIELD] = true;
|
|
13385
13905
|
const editSlotSurvives = this.pool.getEditSlotSurvives();
|
|
13386
13906
|
if (editSlotSurvives !== undefined)
|
|
13387
13907
|
body.edit_slot_survives = editSlotSurvives;
|
|
@@ -13418,6 +13938,7 @@ class SubcTransportPool {
|
|
|
13418
13938
|
lifecycleRegistration = null;
|
|
13419
13939
|
outerFacadeEvictor;
|
|
13420
13940
|
client = null;
|
|
13941
|
+
hadWorkingClient = false;
|
|
13421
13942
|
connecting = null;
|
|
13422
13943
|
routeReopenRetryDelayMs = RECONNECT_RETRY_FLOOR_MS;
|
|
13423
13944
|
routeReopenRetry = null;
|
|
@@ -13521,7 +14042,7 @@ class SubcTransportPool {
|
|
|
13521
14042
|
rootCanAttach(root) {
|
|
13522
14043
|
let directoryExists = false;
|
|
13523
14044
|
try {
|
|
13524
|
-
directoryExists =
|
|
14045
|
+
directoryExists = statSync5(root).isDirectory();
|
|
13525
14046
|
} catch {}
|
|
13526
14047
|
if (directoryExists) {
|
|
13527
14048
|
this.dormantRoots.delete(root);
|
|
@@ -13861,6 +14382,11 @@ class SubcTransportPool {
|
|
|
13861
14382
|
return this.rootReapedError(record);
|
|
13862
14383
|
}
|
|
13863
14384
|
async routeRequest(identity, body, timeoutMs, onProgress, expectedGeneration, abortSignal) {
|
|
14385
|
+
const callStartedAt = performance.now();
|
|
14386
|
+
const callDeadlineMs = timeoutMs ?? SUBC_DEFAULT_REQUEST_TIMEOUT_MS;
|
|
14387
|
+
let scheduledDelayMs = 0;
|
|
14388
|
+
const spentMs = () => Math.max(performance.now() - callStartedAt, scheduledDelayMs);
|
|
14389
|
+
const remainingMs = () => callDeadlineMs - spentMs();
|
|
13864
14390
|
const root = asCanonicalRootPath(identity.project_root);
|
|
13865
14391
|
let generation = expectedGeneration;
|
|
13866
14392
|
if (this.lifecycleEnabled()) {
|
|
@@ -13876,7 +14402,49 @@ class SubcTransportPool {
|
|
|
13876
14402
|
try {
|
|
13877
14403
|
let client;
|
|
13878
14404
|
try {
|
|
13879
|
-
|
|
14405
|
+
let keyChanged;
|
|
14406
|
+
while (true) {
|
|
14407
|
+
const remaining = remainingMs();
|
|
14408
|
+
if (remaining <= 0)
|
|
14409
|
+
throw keyChanged ?? callDeadlinePassedError(undefined, callDeadlineMs);
|
|
14410
|
+
let timer;
|
|
14411
|
+
try {
|
|
14412
|
+
const acquired = await Promise.race([
|
|
14413
|
+
this.ensureClient(),
|
|
14414
|
+
new Promise((resolve) => {
|
|
14415
|
+
timer = setTimeout(() => resolve(CALL_DEADLINE_EXPIRED), remaining);
|
|
14416
|
+
})
|
|
14417
|
+
]);
|
|
14418
|
+
if (acquired === CALL_DEADLINE_EXPIRED) {
|
|
14419
|
+
throw keyChanged ?? callDeadlinePassedError(undefined, callDeadlineMs);
|
|
14420
|
+
}
|
|
14421
|
+
client = acquired;
|
|
14422
|
+
break;
|
|
14423
|
+
} catch (error) {
|
|
14424
|
+
if (!(error instanceof ReconnectKeyChangedError))
|
|
14425
|
+
throw error;
|
|
14426
|
+
keyChanged = error;
|
|
14427
|
+
if (spentMs() + this.nextRouteReopenDelayMs() >= callDeadlineMs)
|
|
14428
|
+
throw error;
|
|
14429
|
+
} finally {
|
|
14430
|
+
clearTimeout(timer);
|
|
14431
|
+
}
|
|
14432
|
+
const { delayMs, wait } = this.waitForRouteReopenBackoff();
|
|
14433
|
+
scheduledDelayMs = spentMs() + delayMs;
|
|
14434
|
+
let retryTimer;
|
|
14435
|
+
try {
|
|
14436
|
+
const waited = await Promise.race([
|
|
14437
|
+
wait,
|
|
14438
|
+
new Promise((resolve) => {
|
|
14439
|
+
retryTimer = setTimeout(() => resolve(CALL_DEADLINE_EXPIRED), callDeadlineMs - (performance.now() - callStartedAt));
|
|
14440
|
+
})
|
|
14441
|
+
]);
|
|
14442
|
+
if (waited === CALL_DEADLINE_EXPIRED)
|
|
14443
|
+
throw keyChanged;
|
|
14444
|
+
} finally {
|
|
14445
|
+
clearTimeout(retryTimer);
|
|
14446
|
+
}
|
|
14447
|
+
}
|
|
13880
14448
|
this.assertRecordLive(record);
|
|
13881
14449
|
} catch (error) {
|
|
13882
14450
|
throw this.annotateReapError(error, record);
|
|
@@ -13911,21 +14479,39 @@ class SubcTransportPool {
|
|
|
13911
14479
|
throw error;
|
|
13912
14480
|
}
|
|
13913
14481
|
};
|
|
13914
|
-
const callDeadlineMs = timeoutMs ?? SUBC_DEFAULT_REQUEST_TIMEOUT_MS;
|
|
13915
14482
|
const reloadWaitBudgetMs = Math.min(callDeadlineMs, ROUTE_OPEN_RELOAD_WAIT_CEILING_MS);
|
|
13916
|
-
let
|
|
14483
|
+
let lastRefusal;
|
|
14484
|
+
const openRouteWithinDeadline = async () => {
|
|
14485
|
+
const remaining = remainingMs();
|
|
14486
|
+
if (remaining <= 0)
|
|
14487
|
+
throw callDeadlinePassedError(lastRefusal, callDeadlineMs);
|
|
14488
|
+
let timer;
|
|
14489
|
+
const expired = new Promise((resolve) => {
|
|
14490
|
+
timer = setTimeout(() => resolve(CALL_DEADLINE_EXPIRED), remaining);
|
|
14491
|
+
});
|
|
14492
|
+
try {
|
|
14493
|
+
const opened = await Promise.race([openRoute(), expired]);
|
|
14494
|
+
if (opened === CALL_DEADLINE_EXPIRED) {
|
|
14495
|
+
throw callDeadlinePassedError(lastRefusal, callDeadlineMs);
|
|
14496
|
+
}
|
|
14497
|
+
return opened;
|
|
14498
|
+
} finally {
|
|
14499
|
+
clearTimeout(timer);
|
|
14500
|
+
}
|
|
14501
|
+
};
|
|
13917
14502
|
const openRouteAfterReloadWindow = async () => {
|
|
13918
14503
|
while (true) {
|
|
13919
14504
|
try {
|
|
13920
|
-
return await
|
|
14505
|
+
return await openRouteWithinDeadline();
|
|
13921
14506
|
} catch (error) {
|
|
13922
14507
|
if (!isRouteOpenReloadWindowError(error))
|
|
13923
14508
|
throw error;
|
|
13924
|
-
|
|
14509
|
+
lastRefusal = error;
|
|
14510
|
+
if (spentMs() + this.nextRouteReopenDelayMs() >= reloadWaitBudgetMs) {
|
|
13925
14511
|
throw reloadWindowExhaustedError(error, callDeadlineMs);
|
|
13926
14512
|
}
|
|
13927
14513
|
const { delayMs, wait } = this.waitForRouteReopenBackoff();
|
|
13928
|
-
|
|
14514
|
+
scheduledDelayMs = spentMs() + delayMs;
|
|
13929
14515
|
await wait;
|
|
13930
14516
|
}
|
|
13931
14517
|
}
|
|
@@ -13961,7 +14547,7 @@ class SubcTransportPool {
|
|
|
13961
14547
|
route: routeLabel(entry.handle)
|
|
13962
14548
|
});
|
|
13963
14549
|
};
|
|
13964
|
-
const requestOnRoute = async (route, entry) => {
|
|
14550
|
+
const requestOnRoute = async (route, entry, requestTimeoutMs) => {
|
|
13965
14551
|
this.assertRecordLive(record);
|
|
13966
14552
|
if (abortSignal?.aborted) {
|
|
13967
14553
|
clearRouteEntry(entry);
|
|
@@ -13977,8 +14563,10 @@ class SubcTransportPool {
|
|
|
13977
14563
|
};
|
|
13978
14564
|
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
13979
14565
|
try {
|
|
13980
|
-
const
|
|
13981
|
-
|
|
14566
|
+
const request = client.request(route, body, {
|
|
14567
|
+
timeoutMs: Math.ceil(requestTimeoutMs),
|
|
14568
|
+
onProgress
|
|
14569
|
+
});
|
|
13982
14570
|
const reply = abortSignal ? await Promise.race([request, aborted]) : await request;
|
|
13983
14571
|
if (!this.isCurrentSession(key, record)) {
|
|
13984
14572
|
if (this.isReapInduced(record))
|
|
@@ -13999,8 +14587,11 @@ class SubcTransportPool {
|
|
|
13999
14587
|
let reopened = false;
|
|
14000
14588
|
let retriedAbsentRoute = false;
|
|
14001
14589
|
while (true) {
|
|
14590
|
+
const requestTimeoutMs = remainingMs();
|
|
14591
|
+
if (requestTimeoutMs <= 0)
|
|
14592
|
+
throw callDeadlinePassedError(lastRefusal, callDeadlineMs);
|
|
14002
14593
|
try {
|
|
14003
|
-
const reply = await requestOnRoute(routeAndEntry.route, routeAndEntry.entry);
|
|
14594
|
+
const reply = await requestOnRoute(routeAndEntry.route, routeAndEntry.entry, requestTimeoutMs);
|
|
14004
14595
|
if (reopened)
|
|
14005
14596
|
this.resetRouteReopenBackoff();
|
|
14006
14597
|
return reply;
|
|
@@ -14010,11 +14601,12 @@ class SubcTransportPool {
|
|
|
14010
14601
|
const ownsRoute = this.isCurrentSession(key, record) && this.client === client;
|
|
14011
14602
|
if (ownsRoute && isRouteRequestReloadRefusal(error)) {
|
|
14012
14603
|
clearRouteEntry(routeAndEntry.entry);
|
|
14013
|
-
|
|
14604
|
+
lastRefusal = error;
|
|
14605
|
+
if (spentMs() + this.nextRouteReopenDelayMs() >= reloadWaitBudgetMs) {
|
|
14014
14606
|
throw reloadWindowExhaustedError(error, callDeadlineMs);
|
|
14015
14607
|
}
|
|
14016
14608
|
const { delayMs, wait } = this.waitForRouteReopenBackoff();
|
|
14017
|
-
|
|
14609
|
+
scheduledDelayMs = spentMs() + delayMs;
|
|
14018
14610
|
await wait;
|
|
14019
14611
|
routeAndEntry = await openRouteAfterReloadWindow();
|
|
14020
14612
|
reopened = true;
|
|
@@ -14023,7 +14615,12 @@ class SubcTransportPool {
|
|
|
14023
14615
|
if (ownsRoute && !retriedAbsentRoute && isRouteProvenAbsentError(error)) {
|
|
14024
14616
|
retriedAbsentRoute = true;
|
|
14025
14617
|
clearRouteEntry(routeAndEntry.entry);
|
|
14026
|
-
|
|
14618
|
+
if (spentMs() + this.nextRouteReopenDelayMs() >= callDeadlineMs) {
|
|
14619
|
+
throw callDeadlinePassedError(error, callDeadlineMs);
|
|
14620
|
+
}
|
|
14621
|
+
const { delayMs, wait } = this.waitForRouteReopenBackoff();
|
|
14622
|
+
scheduledDelayMs = spentMs() + delayMs;
|
|
14623
|
+
await wait;
|
|
14027
14624
|
routeAndEntry = await openRouteAfterReloadWindow();
|
|
14028
14625
|
reopened = true;
|
|
14029
14626
|
continue;
|
|
@@ -14086,10 +14683,14 @@ class SubcTransportPool {
|
|
|
14086
14683
|
throw new SubcTransportShuttingDownError;
|
|
14087
14684
|
}
|
|
14088
14685
|
this.client = client;
|
|
14686
|
+
this.hadWorkingClient = true;
|
|
14089
14687
|
this.transportFailures = 0;
|
|
14090
14688
|
return client;
|
|
14091
14689
|
}).catch((error) => {
|
|
14092
14690
|
this.connecting = null;
|
|
14691
|
+
if (this.hadWorkingClient && error instanceof AuthError) {
|
|
14692
|
+
throw new ReconnectKeyChangedError;
|
|
14693
|
+
}
|
|
14093
14694
|
throw error;
|
|
14094
14695
|
});
|
|
14095
14696
|
return this.connecting;
|
|
@@ -15781,6 +16382,7 @@ async function acquireBridge(locationDirectory, options, dependencies = {}) {
|
|
|
15781
16382
|
let owner = state.standalone;
|
|
15782
16383
|
if (!owner) {
|
|
15783
16384
|
const configByRoot = new Map;
|
|
16385
|
+
const versionMismatchHandlers = new Map;
|
|
15784
16386
|
const configuredLoader = options.poolOptions.projectConfigLoader;
|
|
15785
16387
|
const pool = await createPool({
|
|
15786
16388
|
...options,
|
|
@@ -15790,21 +16392,29 @@ async function acquireBridge(locationDirectory, options, dependencies = {}) {
|
|
|
15790
16392
|
projectConfigLoader: (projectRoot) => ({
|
|
15791
16393
|
...configuredLoader?.(projectRoot) ?? {},
|
|
15792
16394
|
...configByRoot.get(canonicalizeProjectRoot(projectRoot)) ?? {}
|
|
15793
|
-
})
|
|
16395
|
+
}),
|
|
16396
|
+
onVersionMismatch: (binaryVersion, minVersion) => [...versionMismatchHandlers.values()].at(-1)?.(binaryVersion, minVersion) ?? null
|
|
15794
16397
|
}
|
|
15795
16398
|
});
|
|
15796
|
-
owner = {
|
|
16399
|
+
owner = {
|
|
16400
|
+
pool,
|
|
16401
|
+
configByRoot,
|
|
16402
|
+
versionMismatchHandlers,
|
|
16403
|
+
referencesByRoot: new Map,
|
|
16404
|
+
references: 0
|
|
16405
|
+
};
|
|
15797
16406
|
state.standalone = owner;
|
|
15798
16407
|
}
|
|
15799
16408
|
owner.references += 1;
|
|
15800
16409
|
owner.referencesByRoot.set(directory, (owner.referencesByRoot.get(directory) ?? 0) + 1);
|
|
15801
16410
|
owner.configByRoot.set(directory, { ...options.configOverrides });
|
|
15802
16411
|
const acquiredOwner = owner;
|
|
15803
|
-
|
|
16412
|
+
const lease = new LocationBridgeLease(directory, mode, owner.pool, async () => {
|
|
15804
16413
|
await withLock(directory, () => withLock(OWNER_LOCK, async () => {
|
|
15805
16414
|
const current = lifecycleState().standalone;
|
|
15806
16415
|
if (current !== acquiredOwner)
|
|
15807
16416
|
return;
|
|
16417
|
+
current.versionMismatchHandlers?.delete(lease);
|
|
15808
16418
|
current.references -= 1;
|
|
15809
16419
|
const rootReferences = (current.referencesByRoot.get(directory) ?? 1) - 1;
|
|
15810
16420
|
if (rootReferences > 0)
|
|
@@ -15819,6 +16429,10 @@ async function acquireBridge(locationDirectory, options, dependencies = {}) {
|
|
|
15819
16429
|
await current.pool.shutdown();
|
|
15820
16430
|
}));
|
|
15821
16431
|
});
|
|
16432
|
+
const onVersionMismatch = options.poolOptions.onVersionMismatch;
|
|
16433
|
+
if (onVersionMismatch)
|
|
16434
|
+
owner.versionMismatchHandlers?.set(lease, onVersionMismatch);
|
|
16435
|
+
return lease;
|
|
15822
16436
|
});
|
|
15823
16437
|
});
|
|
15824
16438
|
}
|
|
@@ -15839,6 +16453,14 @@ var STANDALONE_DETACH_KEYWORDS_PATTERN = new RegExp(STANDALONE_DETACH_KEYWORD_SO
|
|
|
15839
16453
|
function containsStandaloneDetachKeyword(messageText) {
|
|
15840
16454
|
return STANDALONE_DETACH_KEYWORD_PATTERN.test(messageText);
|
|
15841
16455
|
}
|
|
16456
|
+
function standaloneDetachKeywordRanges(messageText) {
|
|
16457
|
+
const ranges = [];
|
|
16458
|
+
for (const match of messageText.matchAll(STANDALONE_DETACH_KEYWORDS_PATTERN)) {
|
|
16459
|
+
const start = (match.index ?? 0) + (match[1]?.length ?? 0);
|
|
16460
|
+
ranges.push([start, start + BASH_WAIT_DETACH_MAGIC_KEYWORD.length]);
|
|
16461
|
+
}
|
|
16462
|
+
return ranges;
|
|
16463
|
+
}
|
|
15842
16464
|
function stripStandaloneDetachKeywords(messageText) {
|
|
15843
16465
|
return messageText.replace(STANDALONE_DETACH_KEYWORDS_PATTERN, "$1");
|
|
15844
16466
|
}
|
|
@@ -15846,14 +16468,14 @@ function shouldInterruptWaitsForMessage(detachOnUserMessage, operatorText) {
|
|
|
15846
16468
|
return detachOnUserMessage || containsStandaloneDetachKeyword(operatorText);
|
|
15847
16469
|
}
|
|
15848
16470
|
// ../aft-bridge/dist/migration.js
|
|
15849
|
-
import { closeSync as closeSync3, existsSync as existsSync8, mkdirSync as mkdirSync3, openSync as openSync3, readFileSync as
|
|
16471
|
+
import { closeSync as closeSync3, existsSync as existsSync8, mkdirSync as mkdirSync3, openSync as openSync3, readFileSync as readFileSync7, renameSync as renameSync4, rmSync as rmSync2, statSync as statSync6, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
15850
16472
|
import { homedir as homedir8, tmpdir as tmpdir2 } from "node:os";
|
|
15851
|
-
import { basename, dirname as
|
|
16473
|
+
import { basename as basename2, dirname as dirname5, join as join10, resolve as resolve6 } from "node:path";
|
|
15852
16474
|
|
|
15853
16475
|
// ../aft-bridge/dist/paths.js
|
|
15854
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as
|
|
16476
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync5, renameSync as renameSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
15855
16477
|
import { homedir as homedir6 } from "node:os";
|
|
15856
|
-
import { dirname as
|
|
16478
|
+
import { dirname as dirname3, isAbsolute as isAbsolute3, join as join8, resolve as resolve5 } from "node:path";
|
|
15857
16479
|
function homeDir3() {
|
|
15858
16480
|
if (process.platform === "win32")
|
|
15859
16481
|
return process.env.USERPROFILE || process.env.HOME || homedir6();
|
|
@@ -15913,7 +16535,7 @@ function repairRootScopedStorageFile(storageRoot, harness, fileName) {
|
|
|
15913
16535
|
if (existsSync6(harnessPath) || !existsSync6(rootPath))
|
|
15914
16536
|
return harnessPath;
|
|
15915
16537
|
try {
|
|
15916
|
-
mkdirSync2(
|
|
16538
|
+
mkdirSync2(dirname3(harnessPath), { recursive: true });
|
|
15917
16539
|
renameSync3(rootPath, harnessPath);
|
|
15918
16540
|
} catch {}
|
|
15919
16541
|
return harnessPath;
|
|
@@ -15925,7 +16547,7 @@ function shouldShowAnnouncement(storageRoot, harness, currentVersion) {
|
|
|
15925
16547
|
let lastVersion = "";
|
|
15926
16548
|
try {
|
|
15927
16549
|
if (existsSync6(versionFile)) {
|
|
15928
|
-
lastVersion =
|
|
16550
|
+
lastVersion = readFileSync5(versionFile, "utf-8").trim();
|
|
15929
16551
|
}
|
|
15930
16552
|
} catch {
|
|
15931
16553
|
return false;
|
|
@@ -15934,7 +16556,7 @@ function shouldShowAnnouncement(storageRoot, harness, currentVersion) {
|
|
|
15934
16556
|
return false;
|
|
15935
16557
|
if (!lastVersion) {
|
|
15936
16558
|
try {
|
|
15937
|
-
mkdirSync2(
|
|
16559
|
+
mkdirSync2(dirname3(versionFile), { recursive: true });
|
|
15938
16560
|
writeFileSync2(versionFile, currentVersion);
|
|
15939
16561
|
} catch {}
|
|
15940
16562
|
return false;
|
|
@@ -15946,7 +16568,7 @@ function markAnnouncementSeen(storageRoot, harness, currentVersion) {
|
|
|
15946
16568
|
return;
|
|
15947
16569
|
const versionFile = repairRootScopedStorageFile(storageRoot, harness, "last_announced_version");
|
|
15948
16570
|
try {
|
|
15949
|
-
mkdirSync2(
|
|
16571
|
+
mkdirSync2(dirname3(versionFile), { recursive: true });
|
|
15950
16572
|
writeFileSync2(versionFile, currentVersion);
|
|
15951
16573
|
} catch {}
|
|
15952
16574
|
}
|
|
@@ -15995,10 +16617,10 @@ function decodeFileUrl(target) {
|
|
|
15995
16617
|
}
|
|
15996
16618
|
|
|
15997
16619
|
// ../aft-bridge/dist/resolver.js
|
|
15998
|
-
import { closeSync as closeSync2, existsSync as existsSync7, promises as fsPromises, openSync as openSync2, readFileSync as
|
|
16620
|
+
import { closeSync as closeSync2, existsSync as existsSync7, promises as fsPromises, openSync as openSync2, readFileSync as readFileSync6, readSync } from "node:fs";
|
|
15999
16621
|
import { createRequire } from "node:module";
|
|
16000
16622
|
import { homedir as homedir7 } from "node:os";
|
|
16001
|
-
import { delimiter, dirname as
|
|
16623
|
+
import { delimiter, dirname as dirname4, isAbsolute as isAbsolute4, join as join9 } from "node:path";
|
|
16002
16624
|
var ensureBinaryForResolver = ensureBinary;
|
|
16003
16625
|
var npmPlatformPackageReader = null;
|
|
16004
16626
|
var cacheCopiesInFlight = new Map;
|
|
@@ -16151,8 +16773,8 @@ function npmPlatformPackage(ext) {
|
|
|
16151
16773
|
try {
|
|
16152
16774
|
const req = createRequire(import.meta.url);
|
|
16153
16775
|
const manifestPath = req.resolve(`@cortexkit/aft-${platformKey()}/package.json`);
|
|
16154
|
-
const manifest = JSON.parse(
|
|
16155
|
-
const binaryPath = join9(
|
|
16776
|
+
const manifest = JSON.parse(readFileSync6(manifestPath, "utf8"));
|
|
16777
|
+
const binaryPath = join9(dirname4(manifestPath), "bin", `aft${ext}`);
|
|
16156
16778
|
if (typeof manifest.version !== "string" || !existsSync7(binaryPath))
|
|
16157
16779
|
return null;
|
|
16158
16780
|
return { binaryPath, version: normalizeBareVersion(manifest.version) };
|
|
@@ -16406,7 +17028,7 @@ function acquireConfigMigrationLock(lockDir) {
|
|
|
16406
17028
|
if (code !== "EEXIST")
|
|
16407
17029
|
throw err;
|
|
16408
17030
|
try {
|
|
16409
|
-
const ageMs = Date.now() -
|
|
17031
|
+
const ageMs = Date.now() - statSync6(lockDir).mtimeMs;
|
|
16410
17032
|
if (ageMs > 60000) {
|
|
16411
17033
|
rmSync2(lockDir, { recursive: true, force: true });
|
|
16412
17034
|
continue;
|
|
@@ -16420,12 +17042,12 @@ function acquireConfigMigrationLock(lockDir) {
|
|
|
16420
17042
|
}
|
|
16421
17043
|
}
|
|
16422
17044
|
function atomicCopyConfigFile(sourcePath, targetPath) {
|
|
16423
|
-
mkdirSync3(
|
|
16424
|
-
const tmpPath = join10(
|
|
17045
|
+
mkdirSync3(dirname5(targetPath), { recursive: true });
|
|
17046
|
+
const tmpPath = join10(dirname5(targetPath), `.${basename2(targetPath)}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
|
|
16425
17047
|
let fd = null;
|
|
16426
17048
|
try {
|
|
16427
17049
|
fd = openSync3(tmpPath, "wx", 384);
|
|
16428
|
-
writeFileSync3(fd,
|
|
17050
|
+
writeFileSync3(fd, readFileSync7(sourcePath));
|
|
16429
17051
|
closeSync3(fd);
|
|
16430
17052
|
fd = null;
|
|
16431
17053
|
renameSync4(tmpPath, targetPath);
|
|
@@ -16442,8 +17064,8 @@ function atomicCopyConfigFile(sourcePath, targetPath) {
|
|
|
16442
17064
|
}
|
|
16443
17065
|
}
|
|
16444
17066
|
function atomicWriteConfigFile(targetPath, content) {
|
|
16445
|
-
mkdirSync3(
|
|
16446
|
-
const tmpPath = join10(
|
|
17067
|
+
mkdirSync3(dirname5(targetPath), { recursive: true });
|
|
17068
|
+
const tmpPath = join10(dirname5(targetPath), `.${basename2(targetPath)}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
|
|
16447
17069
|
let fd = null;
|
|
16448
17070
|
try {
|
|
16449
17071
|
fd = openSync3(tmpPath, "wx", 384);
|
|
@@ -16504,11 +17126,11 @@ function markLegacySourcesMovedAside(sources, targetPath, logger) {
|
|
|
16504
17126
|
const desiredMarkerPath = `${source.path}${MOVED_MARKER_SUFFIX}`;
|
|
16505
17127
|
const markerPath = nonClobberingSidecarPath(desiredMarkerPath);
|
|
16506
17128
|
try {
|
|
16507
|
-
const original =
|
|
17129
|
+
const original = readFileSync7(source.path, "utf-8");
|
|
16508
17130
|
if (markerPath !== desiredMarkerPath) {
|
|
16509
17131
|
logger?.warn?.(`Preserving existing legacy AFT marker ${desiredMarkerPath}; writing new marker to ${markerPath}`);
|
|
16510
17132
|
}
|
|
16511
|
-
atomicWriteConfigFile(markerPath, movedMarkerContent(targetPath,
|
|
17133
|
+
atomicWriteConfigFile(markerPath, movedMarkerContent(targetPath, basename2(source.path), original));
|
|
16512
17134
|
unlinkSync3(source.path);
|
|
16513
17135
|
info?.(`Moved legacy AFT config ${source.path} aside to ${markerPath}; AFT now reads ${targetPath}`);
|
|
16514
17136
|
} catch (err) {
|
|
@@ -16568,15 +17190,15 @@ function migrateAftConfigFile(opts) {
|
|
|
16568
17190
|
if (existingSources.length === 0) {
|
|
16569
17191
|
return { migrated: false, conflict: false, targetPath: opts.targetPath, warnings };
|
|
16570
17192
|
}
|
|
16571
|
-
mkdirSync3(
|
|
17193
|
+
mkdirSync3(dirname5(opts.targetPath), { recursive: true });
|
|
16572
17194
|
const release = acquireConfigMigrationLock(`${opts.targetPath}.lock`);
|
|
16573
17195
|
try {
|
|
16574
17196
|
const sources = existingSources.map((source) => ({
|
|
16575
17197
|
...source,
|
|
16576
|
-
content:
|
|
17198
|
+
content: readFileSync7(source.path, "utf-8")
|
|
16577
17199
|
}));
|
|
16578
17200
|
if (existsSync8(opts.targetPath)) {
|
|
16579
|
-
const targetContent =
|
|
17201
|
+
const targetContent = readFileSync7(opts.targetPath, "utf-8");
|
|
16580
17202
|
for (const source of sources) {
|
|
16581
17203
|
if (fileSemanticsMatch(source.content, targetContent))
|
|
16582
17204
|
continue;
|
|
@@ -16627,11 +17249,11 @@ function spawnErrorLabel(error) {
|
|
|
16627
17249
|
function migrationLogPath(newRoot, harness, logger) {
|
|
16628
17250
|
const desired = join10(newRoot, "logs", "migration", `${harness}-${Date.now()}.jsonl`);
|
|
16629
17251
|
try {
|
|
16630
|
-
mkdirSync3(
|
|
17252
|
+
mkdirSync3(dirname5(desired), { recursive: true });
|
|
16631
17253
|
return desired;
|
|
16632
17254
|
} catch (err) {
|
|
16633
17255
|
const fallback = join10(tmpdir2(), `aft-migration-${harness}-${Date.now()}.jsonl`);
|
|
16634
|
-
logger?.warn?.(`Failed to create AFT migration log directory ${
|
|
17256
|
+
logger?.warn?.(`Failed to create AFT migration log directory ${dirname5(desired)}: ${err instanceof Error ? err.message : String(err)}. ` + `Using fallback log path ${fallback}.`);
|
|
16635
17257
|
return fallback;
|
|
16636
17258
|
}
|
|
16637
17259
|
}
|
|
@@ -16692,9 +17314,9 @@ async function ensureStorageMigrated(opts) {
|
|
|
16692
17314
|
throw new Error(`AFT storage migration failed (${detail}). ` + `Harness: ${opts.harness}. Legacy: ${legacyRoot}. Target: ${newRoot}. ` + `See log: ${logPath}. ` + `Plugin load aborted to prevent legacy/new state divergence.` + (stderrTail ? ` Stderr tail: ${stderrTail}` : "") + (stdoutTail ? ` Stdout tail: ${stdoutTail}` : ""));
|
|
16693
17315
|
}
|
|
16694
17316
|
// ../aft-bridge/dist/migration-notices.js
|
|
16695
|
-
import { mkdirSync as mkdirSync4, readFileSync as
|
|
17317
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync8, renameSync as renameSync5, rmdirSync, statSync as statSync7, writeFileSync as writeFileSync4 } from "node:fs";
|
|
16696
17318
|
import { homedir as homedir9 } from "node:os";
|
|
16697
|
-
import { dirname as
|
|
17319
|
+
import { dirname as dirname6, join as join11, resolve as resolve7 } from "node:path";
|
|
16698
17320
|
function migrationNoticeStorePath(env = process.env) {
|
|
16699
17321
|
const stateHome = env.XDG_STATE_HOME && env.XDG_STATE_HOME.length > 0 ? env.XDG_STATE_HOME : join11(homedir9(), ".local", "state");
|
|
16700
17322
|
return join11(stateHome, "cortexkit", "aft", "migration-notices.json");
|
|
@@ -16704,7 +17326,7 @@ function noticeKey(configPath, digest) {
|
|
|
16704
17326
|
}
|
|
16705
17327
|
function readRecords(storePath) {
|
|
16706
17328
|
try {
|
|
16707
|
-
const parsed = JSON.parse(
|
|
17329
|
+
const parsed = JSON.parse(readFileSync8(storePath, "utf8"));
|
|
16708
17330
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
16709
17331
|
} catch {
|
|
16710
17332
|
return {};
|
|
@@ -16713,7 +17335,7 @@ function readRecords(storePath) {
|
|
|
16713
17335
|
var LOCK_STALE_MS = 1e4;
|
|
16714
17336
|
function withStoreLock(storePath, action) {
|
|
16715
17337
|
const lockPath = `${storePath}.lock`;
|
|
16716
|
-
mkdirSync4(
|
|
17338
|
+
mkdirSync4(dirname6(storePath), { recursive: true });
|
|
16717
17339
|
const deadline = Date.now() + 2000;
|
|
16718
17340
|
for (;; ) {
|
|
16719
17341
|
try {
|
|
@@ -16723,7 +17345,7 @@ function withStoreLock(storePath, action) {
|
|
|
16723
17345
|
if (error.code !== "EEXIST")
|
|
16724
17346
|
throw error;
|
|
16725
17347
|
try {
|
|
16726
|
-
if (Date.now() -
|
|
17348
|
+
if (Date.now() - statSync7(lockPath).mtimeMs > LOCK_STALE_MS)
|
|
16727
17349
|
rmdirSync(lockPath);
|
|
16728
17350
|
} catch {}
|
|
16729
17351
|
if (Date.now() > deadline)
|
|
@@ -16764,9 +17386,9 @@ function deliverMigrationNoticeOnce(options) {
|
|
|
16764
17386
|
}
|
|
16765
17387
|
}
|
|
16766
17388
|
// ../aft-bridge/dist/npm-resolver.js
|
|
16767
|
-
import { readdirSync as readdirSync2, statSync as
|
|
17389
|
+
import { readdirSync as readdirSync2, statSync as statSync8 } from "node:fs";
|
|
16768
17390
|
import { homedir as homedir10 } from "node:os";
|
|
16769
|
-
import { dirname as
|
|
17391
|
+
import { dirname as dirname7, isAbsolute as isAbsolute5, join as join12 } from "node:path";
|
|
16770
17392
|
function defaultDeps() {
|
|
16771
17393
|
return {
|
|
16772
17394
|
platform: process.platform,
|
|
@@ -16780,7 +17402,7 @@ function npmBinaryName(platform) {
|
|
|
16780
17402
|
}
|
|
16781
17403
|
function isFile(p) {
|
|
16782
17404
|
try {
|
|
16783
|
-
return
|
|
17405
|
+
return statSync8(p).isFile();
|
|
16784
17406
|
} catch {
|
|
16785
17407
|
return false;
|
|
16786
17408
|
}
|
|
@@ -16801,7 +17423,7 @@ function npmFromPath(deps) {
|
|
|
16801
17423
|
return null;
|
|
16802
17424
|
}
|
|
16803
17425
|
function npmAdjacentToNode(deps) {
|
|
16804
|
-
const dir =
|
|
17426
|
+
const dir = dirname7(deps.execPath);
|
|
16805
17427
|
return isFile(join12(dir, npmBinaryName(deps.platform))) ? dir : null;
|
|
16806
17428
|
}
|
|
16807
17429
|
function highestVersionedNodeBin(installsDir, name) {
|
|
@@ -17189,8 +17811,8 @@ function probeOnnxRuntimeLoadable(libPath, arch = process.arch) {
|
|
|
17189
17811
|
}
|
|
17190
17812
|
// ../aft-bridge/dist/onnx-runtime.js
|
|
17191
17813
|
import { createHash as createHash5, randomBytes as randomBytes2 } from "node:crypto";
|
|
17192
|
-
import { chmodSync as chmodSync2, closeSync as closeSync5, copyFileSync as copyFileSync2, createWriteStream as createWriteStream2, existsSync as existsSync11, lstatSync, mkdirSync as mkdirSync5, openSync as openSync5, readdirSync as readdirSync3, readFileSync as
|
|
17193
|
-
import { basename as
|
|
17814
|
+
import { chmodSync as chmodSync2, closeSync as closeSync5, copyFileSync as copyFileSync2, createWriteStream as createWriteStream2, existsSync as existsSync11, lstatSync, mkdirSync as mkdirSync5, openSync as openSync5, readdirSync as readdirSync3, readFileSync as readFileSync9, readlinkSync, realpathSync as realpathSync3, renameSync as renameSync6, rmSync as rmSync3, statSync as statSync9, symlinkSync, unlinkSync as unlinkSync4, writeFileSync as writeFileSync5 } from "node:fs";
|
|
17815
|
+
import { basename as basename3, dirname as dirname8, isAbsolute as isAbsolute6, join as join14, relative as relative2, resolve as resolve8, win32 } from "node:path";
|
|
17194
17816
|
import { Readable as Readable2 } from "node:stream";
|
|
17195
17817
|
import { pipeline as pipeline2 } from "node:stream/promises";
|
|
17196
17818
|
|
|
@@ -17230,31 +17852,36 @@ var ORT_PLATFORM_MAP = {
|
|
|
17230
17852
|
arm64: {
|
|
17231
17853
|
assetName: `onnxruntime-osx-arm64-${ORT_VERSION}`,
|
|
17232
17854
|
libName: "libonnxruntime.dylib",
|
|
17233
|
-
archiveType: "tgz"
|
|
17855
|
+
archiveType: "tgz",
|
|
17856
|
+
librarySha256: "872533f130f1839a5bc01788ddb4f75c83a189763441ba1178788ed965449289"
|
|
17234
17857
|
}
|
|
17235
17858
|
},
|
|
17236
17859
|
linux: {
|
|
17237
17860
|
x64: {
|
|
17238
17861
|
assetName: `onnxruntime-linux-x64-${ORT_VERSION}`,
|
|
17239
17862
|
libName: "libonnxruntime.so",
|
|
17240
|
-
archiveType: "tgz"
|
|
17863
|
+
archiveType: "tgz",
|
|
17864
|
+
librarySha256: "d132535d051344ff5c64c9c200004150559049a81ed330eb4422c1962fb6b7e4"
|
|
17241
17865
|
},
|
|
17242
17866
|
arm64: {
|
|
17243
17867
|
assetName: `onnxruntime-linux-aarch64-${ORT_VERSION}`,
|
|
17244
17868
|
libName: "libonnxruntime.so",
|
|
17245
|
-
archiveType: "tgz"
|
|
17869
|
+
archiveType: "tgz",
|
|
17870
|
+
librarySha256: "52b2a0e75e79468404284fec38ad5ee1a7a996232274f5e1b84f3e793fb07554"
|
|
17246
17871
|
}
|
|
17247
17872
|
},
|
|
17248
17873
|
win32: {
|
|
17249
17874
|
x64: {
|
|
17250
17875
|
assetName: `onnxruntime-win-x64-${ORT_VERSION}`,
|
|
17251
17876
|
libName: "onnxruntime.dll",
|
|
17252
|
-
archiveType: "zip"
|
|
17877
|
+
archiveType: "zip",
|
|
17878
|
+
librarySha256: "b95efb2113b603bbbf3f191061c5516a871ed546893c820e4f3b7b6c358dbf2a"
|
|
17253
17879
|
},
|
|
17254
17880
|
arm64: {
|
|
17255
17881
|
assetName: `onnxruntime-win-arm64-${ORT_VERSION}`,
|
|
17256
17882
|
libName: "onnxruntime.dll",
|
|
17257
|
-
archiveType: "zip"
|
|
17883
|
+
archiveType: "zip",
|
|
17884
|
+
librarySha256: "724d81ac50b11bfaa01ab3ce01b99fb6734a4b762259a8be4395ca662ce99fe6"
|
|
17258
17885
|
}
|
|
17259
17886
|
}
|
|
17260
17887
|
};
|
|
@@ -17397,7 +18024,7 @@ function cleanupAbandonedStagingDirs(onnxBaseDir) {
|
|
|
17397
18024
|
abandoned = true;
|
|
17398
18025
|
} else {
|
|
17399
18026
|
try {
|
|
17400
|
-
const ageMs = Date.now() -
|
|
18027
|
+
const ageMs = Date.now() - statSync9(stagingDir).mtimeMs;
|
|
17401
18028
|
abandoned = ageMs > STALE_LOCK_MS;
|
|
17402
18029
|
} catch {
|
|
17403
18030
|
abandoned = true;
|
|
@@ -17434,7 +18061,7 @@ var REQUIRED_ORT_MAJOR = 1;
|
|
|
17434
18061
|
var REQUIRED_ORT_MIN_MINOR = 20;
|
|
17435
18062
|
var INVALID_ORT_VERSION = "<invalid>";
|
|
17436
18063
|
function parseOnnxVersionFromPath(value) {
|
|
17437
|
-
const name =
|
|
18064
|
+
const name = basename3(value);
|
|
17438
18065
|
const semverish = name.match(/(?:^|[._-])(\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?)(?:\.(?:dylib|dll))?$/);
|
|
17439
18066
|
if (semverish)
|
|
17440
18067
|
return semverish[1].split(/[-+]/, 1)[0];
|
|
@@ -17639,7 +18266,7 @@ async function downloadFileWithCap(url, destPath) {
|
|
|
17639
18266
|
if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES2) {
|
|
17640
18267
|
throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES2}`);
|
|
17641
18268
|
}
|
|
17642
|
-
mkdirSync5(
|
|
18269
|
+
mkdirSync5(dirname8(destPath), { recursive: true });
|
|
17643
18270
|
let bytesWritten = 0;
|
|
17644
18271
|
const guard = new TransformStream({
|
|
17645
18272
|
transform(chunk, transformController) {
|
|
@@ -17673,7 +18300,7 @@ function validateExtractedTree(stagingRoot) {
|
|
|
17673
18300
|
const lst = lstatSync(fullPath);
|
|
17674
18301
|
if (lst.isSymbolicLink()) {
|
|
17675
18302
|
const linkTarget = readlinkSync(fullPath);
|
|
17676
|
-
const resolvedTarget = resolve8(
|
|
18303
|
+
const resolvedTarget = resolve8(dirname8(fullPath), linkTarget);
|
|
17677
18304
|
const rel = relative2(realRoot, resolvedTarget);
|
|
17678
18305
|
if (rel.startsWith("..") || isAbsolute6(rel) || win32.isAbsolute(rel)) {
|
|
17679
18306
|
throw new Error(`extracted symlink ${fullPath} points outside staging root: ${linkTarget}`);
|
|
@@ -17744,6 +18371,9 @@ async function downloadOnnxRuntime(info, targetDir) {
|
|
|
17744
18371
|
copyOnnxLibraries(info, extractedDir, stagedInstallDir, realFiles, symlinks);
|
|
17745
18372
|
const libPath = join14(stagedInstallDir, info.libName);
|
|
17746
18373
|
const libHash = sha256File2(libPath);
|
|
18374
|
+
if (libHash !== info.librarySha256) {
|
|
18375
|
+
throw new Error(`onnx runtime library hash mismatch at ${libPath}: sha256 ${libHash}, ` + `expected the pinned ${info.librarySha256} for ${info.assetName}`);
|
|
18376
|
+
}
|
|
17747
18377
|
writeOnnxInstalledMeta(stagedInstallDir, ORT_VERSION, libHash, archiveSha256);
|
|
17748
18378
|
publishOnnxRuntime(stagedInstallDir, targetDir);
|
|
17749
18379
|
rmSync3(tmpDir, { recursive: true, force: true });
|
|
@@ -17809,7 +18439,7 @@ function copyOnnxLibraries(info, extractedDir, targetDir, realFiles, symlinks, c
|
|
|
17809
18439
|
unlinkSync4(dst);
|
|
17810
18440
|
} catch {}
|
|
17811
18441
|
const dstForContainment = join14(targetRoot, link.name);
|
|
17812
|
-
const resolvedTarget = resolve8(
|
|
18442
|
+
const resolvedTarget = resolve8(dirname8(dstForContainment), link.target);
|
|
17813
18443
|
if (!isPathInsideRoot(targetRoot, resolvedTarget)) {
|
|
17814
18444
|
const message = `ONNX Runtime symlink ${link.name} points outside install dir: ${link.target}`;
|
|
17815
18445
|
if (requiredLibs.has(link.name)) {
|
|
@@ -17861,9 +18491,9 @@ function writeOnnxInstalledMeta(installDir, version, sha256, archiveSha256) {
|
|
|
17861
18491
|
function readOnnxInstalledMeta(installDir) {
|
|
17862
18492
|
const path = join14(installDir, ONNX_INSTALLED_META_FILE);
|
|
17863
18493
|
try {
|
|
17864
|
-
if (!
|
|
18494
|
+
if (!statSync9(path).isFile())
|
|
17865
18495
|
return null;
|
|
17866
|
-
const raw =
|
|
18496
|
+
const raw = readFileSync9(path, "utf8");
|
|
17867
18497
|
const parsed = JSON.parse(raw);
|
|
17868
18498
|
if (typeof parsed.version !== "string" || parsed.version.length === 0)
|
|
17869
18499
|
return null;
|
|
@@ -17879,7 +18509,7 @@ function readOnnxInstalledMeta(installDir) {
|
|
|
17879
18509
|
}
|
|
17880
18510
|
function sha256File2(path) {
|
|
17881
18511
|
const hash = createHash5("sha256");
|
|
17882
|
-
hash.update(
|
|
18512
|
+
hash.update(readFileSync9(path));
|
|
17883
18513
|
return hash.digest("hex");
|
|
17884
18514
|
}
|
|
17885
18515
|
function acquireLock(lockPath) {
|
|
@@ -17907,12 +18537,12 @@ ${new Date().toISOString()}
|
|
|
17907
18537
|
let owningPid = null;
|
|
17908
18538
|
let lockMtimeMs = 0;
|
|
17909
18539
|
try {
|
|
17910
|
-
const raw =
|
|
18540
|
+
const raw = readFileSync9(lockPath, "utf8");
|
|
17911
18541
|
const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
|
|
17912
18542
|
const parsed = Number.parseInt(firstLine, 10);
|
|
17913
18543
|
if (Number.isFinite(parsed) && parsed > 0)
|
|
17914
18544
|
owningPid = parsed;
|
|
17915
|
-
lockMtimeMs =
|
|
18545
|
+
lockMtimeMs = statSync9(lockPath).mtimeMs;
|
|
17916
18546
|
} catch {
|
|
17917
18547
|
return tryClaim();
|
|
17918
18548
|
}
|
|
@@ -17932,7 +18562,7 @@ function releaseLock(lockPath) {
|
|
|
17932
18562
|
try {
|
|
17933
18563
|
let owningPid = null;
|
|
17934
18564
|
try {
|
|
17935
|
-
const raw =
|
|
18565
|
+
const raw = readFileSync9(lockPath, "utf8");
|
|
17936
18566
|
const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
|
|
17937
18567
|
const parsed = Number.parseInt(firstLine, 10);
|
|
17938
18568
|
if (Number.isFinite(parsed) && parsed > 0)
|
|
@@ -18086,12 +18716,13 @@ function normalizeZoomTargets(record) {
|
|
|
18086
18716
|
throw new InvalidRequestError(`'targets[${index}].path' must be a non-empty string`);
|
|
18087
18717
|
}
|
|
18088
18718
|
const source = target;
|
|
18089
|
-
const
|
|
18719
|
+
const blank = (value) => value === undefined || value === null || typeof value === "string" && !value.trim();
|
|
18720
|
+
const emptyTarget = blank(source.symbol) && blank(source.path) && blank(source.filePath);
|
|
18090
18721
|
if (emptyTarget)
|
|
18091
18722
|
return { ...source };
|
|
18092
18723
|
const normalized = { ...source };
|
|
18093
18724
|
try {
|
|
18094
|
-
normalizeAliasPair(normalized, "path", "filePath",
|
|
18725
|
+
normalizeAliasPair(normalized, "path", "filePath", false);
|
|
18095
18726
|
} catch (error) {
|
|
18096
18727
|
if (error instanceof InvalidRequestError) {
|
|
18097
18728
|
throw new InvalidRequestError(error.message.replace("'filePath'", `'targets[${index}].filePath'`).replace("'path'", `'targets[${index}].path'`));
|
|
@@ -18623,7 +19254,7 @@ function formatSemanticIndexStatus(status, stage, error, backend) {
|
|
|
18623
19254
|
return status;
|
|
18624
19255
|
}
|
|
18625
19256
|
// src/config.ts
|
|
18626
|
-
import { existsSync as existsSync12, readFileSync as
|
|
19257
|
+
import { existsSync as existsSync12, readFileSync as readFileSync10, renameSync as renameSync7, unlinkSync as unlinkSync5, writeFileSync as writeFileSync6 } from "node:fs";
|
|
18627
19258
|
import { homedir as homedir11 } from "node:os";
|
|
18628
19259
|
import { isAbsolute as isAbsolute7, parse as parsePath, resolve as resolvePath } from "node:path";
|
|
18629
19260
|
var import_comment_json = __toESM(require_src2(), 1);
|
|
@@ -23466,6 +24097,17 @@ var SemanticConfigSchema = object({
|
|
|
23466
24097
|
max_input_tokens: number2().int().positive().optional(),
|
|
23467
24098
|
max_files: number2().int().positive().optional()
|
|
23468
24099
|
});
|
|
24100
|
+
var RerankConfigSchema = object({
|
|
24101
|
+
backend: _enum(["off", "onnx", "remote", "synapse"]).optional(),
|
|
24102
|
+
model: string2().trim().min(1).optional(),
|
|
24103
|
+
endpoint: string2().trim().min(1).optional(),
|
|
24104
|
+
api_key_env: string2().trim().min(1).optional(),
|
|
24105
|
+
top_n: number2().int().positive().optional(),
|
|
24106
|
+
timeout_ms: number2().int().positive().optional()
|
|
24107
|
+
});
|
|
24108
|
+
var SearchConfigSchema = object({
|
|
24109
|
+
rerank: RerankConfigSchema.optional()
|
|
24110
|
+
});
|
|
23469
24111
|
var LspExtensionSchema = string2().trim().min(1).refine((value) => value.replace(/^\.+/, "").length > 0, {
|
|
23470
24112
|
message: "Extension must include characters other than leading dots"
|
|
23471
24113
|
});
|
|
@@ -23526,6 +24168,10 @@ var SubcConfigSchema = object({
|
|
|
23526
24168
|
connection_file: string2().optional(),
|
|
23527
24169
|
client_reaper: boolean2().optional()
|
|
23528
24170
|
});
|
|
24171
|
+
var OpenCodeHostConfigSchema = object({
|
|
24172
|
+
server_url: string2().optional(),
|
|
24173
|
+
server_password_env: string2().optional()
|
|
24174
|
+
});
|
|
23529
24175
|
var GhShimConfigSchema = object({
|
|
23530
24176
|
binary_path: string2().trim().refine(isAbsolute7, "gh_shim.binary_path must be absolute").optional()
|
|
23531
24177
|
});
|
|
@@ -23611,9 +24257,11 @@ var AftConfigFieldsSchema = object({
|
|
|
23611
24257
|
lsp: LspConfigSchema.optional(),
|
|
23612
24258
|
url_fetch_allow_private: boolean2().optional(),
|
|
23613
24259
|
semantic: SemanticConfigSchema.optional(),
|
|
24260
|
+
search: SearchConfigSchema.optional(),
|
|
23614
24261
|
auto_update: boolean2().optional(),
|
|
23615
24262
|
bridge: BridgeConfigSchema.optional(),
|
|
23616
24263
|
subc: SubcConfigSchema.optional(),
|
|
24264
|
+
opencode: OpenCodeHostConfigSchema.optional(),
|
|
23617
24265
|
github: GithubConfigSchema.optional(),
|
|
23618
24266
|
gh_shim: GhShimConfigSchema.optional(),
|
|
23619
24267
|
git: GitConfigSchema.optional(),
|
|
@@ -23855,7 +24503,7 @@ function migrateAftConfigFile2(configPath, logger = { log: log2, warn: warn2 })
|
|
|
23855
24503
|
let tmpPath = null;
|
|
23856
24504
|
let oldKeys = [];
|
|
23857
24505
|
try {
|
|
23858
|
-
const content =
|
|
24506
|
+
const content = readFileSync10(configPath, "utf-8");
|
|
23859
24507
|
const rawConfig = import_comment_json.parse(content);
|
|
23860
24508
|
if (!rawConfig || typeof rawConfig !== "object" || Array.isArray(rawConfig)) {
|
|
23861
24509
|
return { migrated: false, oldKeys: [] };
|
|
@@ -23922,6 +24570,18 @@ function parseConfigPartially(rawConfig) {
|
|
|
23922
24570
|
return partialConfig;
|
|
23923
24571
|
}
|
|
23924
24572
|
var configLoadErrors = [];
|
|
24573
|
+
var configValidationErrors = [];
|
|
24574
|
+
var configLoadSources = [];
|
|
24575
|
+
var configLoadTexts = new Map;
|
|
24576
|
+
function getConfigLoadSources() {
|
|
24577
|
+
return configLoadSources;
|
|
24578
|
+
}
|
|
24579
|
+
function getConfigLoadTexts() {
|
|
24580
|
+
return configLoadTexts;
|
|
24581
|
+
}
|
|
24582
|
+
function getConfigValidationErrors() {
|
|
24583
|
+
return configValidationErrors;
|
|
24584
|
+
}
|
|
23925
24585
|
function getConfigLoadErrors() {
|
|
23926
24586
|
return configLoadErrors;
|
|
23927
24587
|
}
|
|
@@ -23954,7 +24614,9 @@ function loadConfigFromPath(configPath, tier) {
|
|
|
23954
24614
|
if (!existsSync12(configPath)) {
|
|
23955
24615
|
return null;
|
|
23956
24616
|
}
|
|
23957
|
-
const content =
|
|
24617
|
+
const content = readFileSync10(configPath, "utf-8");
|
|
24618
|
+
configLoadSources.push(configPath);
|
|
24619
|
+
configLoadTexts.set(configPath, content);
|
|
23958
24620
|
const rawConfig = import_comment_json.parse(content);
|
|
23959
24621
|
migrateRawConfig(rawConfig, configPath, { log: log2, warn: warn2 });
|
|
23960
24622
|
cleanConfig = stripJsoncSymbols(rawConfig);
|
|
@@ -23999,6 +24661,7 @@ function loadConfigFromPath(configPath, tier) {
|
|
|
23999
24661
|
} else {
|
|
24000
24662
|
const errorMsg = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ");
|
|
24001
24663
|
warn2(`Config validation error in ${configPath}: ${errorMsg}`);
|
|
24664
|
+
configValidationErrors.push({ path: configPath, message: errorMsg });
|
|
24002
24665
|
parsed = parseConfigPartially(cleanConfig);
|
|
24003
24666
|
}
|
|
24004
24667
|
if (!parsed)
|
|
@@ -24188,6 +24851,8 @@ function getStrippedTopLevelKeys(override) {
|
|
|
24188
24851
|
stripped.push("sandbox.write_allow");
|
|
24189
24852
|
if (override.subc !== undefined)
|
|
24190
24853
|
stripped.push("subc");
|
|
24854
|
+
if (override.opencode !== undefined)
|
|
24855
|
+
stripped.push("opencode");
|
|
24191
24856
|
if (override.github !== undefined)
|
|
24192
24857
|
stripped.push("github");
|
|
24193
24858
|
if (override.gh_shim !== undefined)
|
|
@@ -24195,14 +24860,33 @@ function getStrippedTopLevelKeys(override) {
|
|
|
24195
24860
|
for (const tool of partitionProjectDisables(override.disabled_tools).ignored) {
|
|
24196
24861
|
stripped.push(`disabled_tools.${tool}`);
|
|
24197
24862
|
}
|
|
24863
|
+
stripped.push(...projectRerankStrippedKeys(override.search?.rerank));
|
|
24864
|
+
return stripped;
|
|
24865
|
+
}
|
|
24866
|
+
function projectRerankStrippedKeys(rerank) {
|
|
24867
|
+
if (rerank === undefined)
|
|
24868
|
+
return [];
|
|
24869
|
+
const stripped = [];
|
|
24870
|
+
if (rerank.backend !== undefined && rerank.backend !== "off")
|
|
24871
|
+
stripped.push("search.rerank.backend");
|
|
24872
|
+
for (const key of ["model", "endpoint", "api_key_env", "top_n", "timeout_ms"]) {
|
|
24873
|
+
if (rerank[key] !== undefined)
|
|
24874
|
+
stripped.push(`search.rerank.${key}`);
|
|
24875
|
+
}
|
|
24198
24876
|
return stripped;
|
|
24199
24877
|
}
|
|
24878
|
+
function mergeProjectSearchConfig(base, project) {
|
|
24879
|
+
if (project?.rerank?.backend !== "off")
|
|
24880
|
+
return base;
|
|
24881
|
+
return { ...base, rerank: { ...base?.rerank, backend: "off" } };
|
|
24882
|
+
}
|
|
24200
24883
|
function mergeConfigs(base, override) {
|
|
24201
24884
|
const disabledTools = unionDisabledTools(base.disabled_tools, partitionProjectDisables(override.disabled_tools).accepted);
|
|
24202
24885
|
const indexes = mergeIndexes(base.indexes, override.indexes, true);
|
|
24203
24886
|
const formatter = { ...base.formatter, ...override.formatter };
|
|
24204
24887
|
const checker = { ...base.checker, ...override.checker };
|
|
24205
24888
|
const semantic = mergeSemanticConfig(base.semantic, override.semantic);
|
|
24889
|
+
const search = mergeProjectSearchConfig(base.search, override.search);
|
|
24206
24890
|
const lsp = mergeLspConfig(base.lsp, override.lsp);
|
|
24207
24891
|
const experimental = mergeExperimentalConfig(base.experimental, override.experimental);
|
|
24208
24892
|
const bash = mergeBashConfig(base.bash, override.bash);
|
|
@@ -24232,6 +24916,7 @@ function mergeConfigs(base, override) {
|
|
|
24232
24916
|
...pi !== undefined ? { pi } : {},
|
|
24233
24917
|
experimental,
|
|
24234
24918
|
semantic,
|
|
24919
|
+
search,
|
|
24235
24920
|
...bridge !== undefined ? { bridge } : {},
|
|
24236
24921
|
...indexes !== undefined ? { indexes } : {},
|
|
24237
24922
|
...disabledTools !== undefined ? { disabled_tools: disabledTools } : {}
|
|
@@ -24296,6 +24981,9 @@ function buildConfigTierConfigureParams(projectDirectory, processState = {}) {
|
|
|
24296
24981
|
}
|
|
24297
24982
|
function loadAftConfig(projectDirectory) {
|
|
24298
24983
|
configLoadErrors = [];
|
|
24984
|
+
configValidationErrors = [];
|
|
24985
|
+
configLoadSources = [];
|
|
24986
|
+
configLoadTexts = new Map;
|
|
24299
24987
|
configLoadNotices = [];
|
|
24300
24988
|
semanticInputSupplied = false;
|
|
24301
24989
|
const { userConfigPath, projectConfigPath } = resolveAftConfigPaths(projectDirectory);
|
|
@@ -38024,6 +38712,14 @@ function formatDuration(completion) {
|
|
|
38024
38712
|
}
|
|
38025
38713
|
|
|
38026
38714
|
// src/shared/session-directory.ts
|
|
38715
|
+
import { Effect } from "effect";
|
|
38716
|
+
function isV2PluginContext(client) {
|
|
38717
|
+
if (!client || typeof client !== "object")
|
|
38718
|
+
return false;
|
|
38719
|
+
const candidate = client;
|
|
38720
|
+
return typeof candidate.location?.directory === "string" && typeof candidate.session?.get === "function";
|
|
38721
|
+
}
|
|
38722
|
+
var V2_SESSION_LOOKUP_TIMEOUT_MS = 3000;
|
|
38027
38723
|
var CACHE_MAX_ENTRIES = 200;
|
|
38028
38724
|
var cache = new Map;
|
|
38029
38725
|
async function getSessionDirectory(client, sessionId, fallbackDirectory) {
|
|
@@ -38035,6 +38731,8 @@ async function getSessionDirectory(client, sessionId, fallbackDirectory) {
|
|
|
38035
38731
|
cache.set(sessionId, cached);
|
|
38036
38732
|
return cached.directory;
|
|
38037
38733
|
}
|
|
38734
|
+
if (isV2PluginContext(client))
|
|
38735
|
+
return lookupV2SessionDirectory(client, sessionId);
|
|
38038
38736
|
const c = client;
|
|
38039
38737
|
const sessionApi = c?.session;
|
|
38040
38738
|
if (!sessionApi || typeof sessionApi.get !== "function") {
|
|
@@ -38055,6 +38753,25 @@ async function getSessionDirectory(client, sessionId, fallbackDirectory) {
|
|
|
38055
38753
|
setCache(sessionId, dir);
|
|
38056
38754
|
return dir;
|
|
38057
38755
|
}
|
|
38756
|
+
async function lookupV2SessionDirectory(context, sessionId) {
|
|
38757
|
+
let dir = null;
|
|
38758
|
+
let failure;
|
|
38759
|
+
try {
|
|
38760
|
+
const session = await Effect.runPromise(Effect.timeout(context.session.get({ sessionID: sessionId }), V2_SESSION_LOOKUP_TIMEOUT_MS));
|
|
38761
|
+
const directory = session?.location?.directory;
|
|
38762
|
+
if (typeof directory === "string" && directory.length > 0)
|
|
38763
|
+
dir = directory;
|
|
38764
|
+
else
|
|
38765
|
+
failure = "the session record has no location.directory";
|
|
38766
|
+
} catch (err) {
|
|
38767
|
+
failure = err instanceof Error ? err.message || err.name : String(err);
|
|
38768
|
+
}
|
|
38769
|
+
if (failure !== undefined) {
|
|
38770
|
+
sessionWarn(sessionId, `[aft-plugin] OpenCode 2 session lookup failed (${failure}); tool paths and bash use the Location directory ${context.location.directory} for this session`);
|
|
38771
|
+
}
|
|
38772
|
+
setCache(sessionId, dir);
|
|
38773
|
+
return dir;
|
|
38774
|
+
}
|
|
38058
38775
|
function setCache(sessionId, directory) {
|
|
38059
38776
|
if (cache.has(sessionId))
|
|
38060
38777
|
cache.delete(sessionId);
|
|
@@ -38113,6 +38830,108 @@ async function verifySessionDirectory(client, sessionId) {
|
|
|
38113
38830
|
return dir;
|
|
38114
38831
|
}
|
|
38115
38832
|
|
|
38833
|
+
// src/shared/subagent-detect.ts
|
|
38834
|
+
import { Effect as Effect2 } from "effect";
|
|
38835
|
+
var CACHE_MAX_ENTRIES2 = 200;
|
|
38836
|
+
var LOOKUP_TIMEOUT_MS = 1000;
|
|
38837
|
+
var FAILED_LOOKUP_RETRY_MS = 30000;
|
|
38838
|
+
var cache2 = new Map;
|
|
38839
|
+
var inflight = new Map;
|
|
38840
|
+
var now = () => Date.now();
|
|
38841
|
+
async function resolveIsSubagent(client, sessionId, _fallbackDirectory) {
|
|
38842
|
+
if (!sessionId) {
|
|
38843
|
+
sessionLog(undefined, "[subagent-detect] no sessionId provided → primary");
|
|
38844
|
+
return false;
|
|
38845
|
+
}
|
|
38846
|
+
const cached = cache2.get(sessionId);
|
|
38847
|
+
if (cached && (cached.retryAfter === undefined || now() < cached.retryAfter)) {
|
|
38848
|
+
cache2.delete(sessionId);
|
|
38849
|
+
cache2.set(sessionId, cached);
|
|
38850
|
+
return cached.isSubagent;
|
|
38851
|
+
}
|
|
38852
|
+
const pending = inflight.get(sessionId);
|
|
38853
|
+
if (pending)
|
|
38854
|
+
return pending;
|
|
38855
|
+
const lookup = boundedLookup(client, sessionId).finally(() => {
|
|
38856
|
+
inflight.delete(sessionId);
|
|
38857
|
+
});
|
|
38858
|
+
inflight.set(sessionId, lookup);
|
|
38859
|
+
return lookup;
|
|
38860
|
+
}
|
|
38861
|
+
async function boundedLookup(client, sessionId) {
|
|
38862
|
+
let timer;
|
|
38863
|
+
const timedOut = new Promise((resolve) => {
|
|
38864
|
+
timer = setTimeout(() => resolve("timeout"), LOOKUP_TIMEOUT_MS);
|
|
38865
|
+
});
|
|
38866
|
+
let answer;
|
|
38867
|
+
try {
|
|
38868
|
+
answer = await Promise.race([lookupSession(client, sessionId), timedOut]);
|
|
38869
|
+
} finally {
|
|
38870
|
+
clearTimeout(timer);
|
|
38871
|
+
}
|
|
38872
|
+
if (typeof answer === "boolean") {
|
|
38873
|
+
setCache2(sessionId, answer);
|
|
38874
|
+
return answer;
|
|
38875
|
+
}
|
|
38876
|
+
if (answer === "timeout") {
|
|
38877
|
+
sessionWarn(sessionId, `[subagent-detect] session lookup did not answer within ${LOOKUP_TIMEOUT_MS}ms → primary for now`);
|
|
38878
|
+
}
|
|
38879
|
+
setCache2(sessionId, false, now() + FAILED_LOOKUP_RETRY_MS);
|
|
38880
|
+
return false;
|
|
38881
|
+
}
|
|
38882
|
+
async function lookupSession(client, sessionId) {
|
|
38883
|
+
if (isV2PluginContext2(client))
|
|
38884
|
+
return resolveV2(client, sessionId);
|
|
38885
|
+
const c = client;
|
|
38886
|
+
const sessionApi = c?.session;
|
|
38887
|
+
if (!sessionApi || typeof sessionApi.get !== "function") {
|
|
38888
|
+
sessionLog(sessionId, `[subagent-detect] client.session.get unavailable (client=${typeof client}, session=${typeof sessionApi}, get=${typeof sessionApi?.get}) → caching as primary`);
|
|
38889
|
+
return false;
|
|
38890
|
+
}
|
|
38891
|
+
sessionLog(sessionId, `[subagent-detect] cache miss, calling client.session.get(id=${sessionId})`);
|
|
38892
|
+
try {
|
|
38893
|
+
const result = await sessionApi.get({
|
|
38894
|
+
path: { id: sessionId }
|
|
38895
|
+
});
|
|
38896
|
+
const session = result?.data ?? result;
|
|
38897
|
+
const parentIdRaw = session?.parentID;
|
|
38898
|
+
const isSubagent = session !== undefined && typeof session.parentID === "string" && session.parentID.length > 0;
|
|
38899
|
+
sessionLog(sessionId, `[subagent-detect] SDK returned session=${session !== undefined ? "present" : "undefined"}, parentID=${JSON.stringify(parentIdRaw)} → isSubagent=${isSubagent}`);
|
|
38900
|
+
return isSubagent;
|
|
38901
|
+
} catch (err) {
|
|
38902
|
+
sessionWarn(sessionId, `[subagent-detect] SDK lookup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
38903
|
+
return "failed";
|
|
38904
|
+
}
|
|
38905
|
+
}
|
|
38906
|
+
function isV2PluginContext2(client) {
|
|
38907
|
+
if (!client || typeof client !== "object")
|
|
38908
|
+
return false;
|
|
38909
|
+
const candidate = client;
|
|
38910
|
+
return typeof candidate.location?.directory === "string" && typeof candidate.session?.get === "function";
|
|
38911
|
+
}
|
|
38912
|
+
async function resolveV2(context, sessionId) {
|
|
38913
|
+
let session;
|
|
38914
|
+
try {
|
|
38915
|
+
session = await Effect2.runPromise(context.session.get({ sessionID: sessionId }));
|
|
38916
|
+
} catch (err) {
|
|
38917
|
+
sessionWarn(sessionId, `[subagent-detect] OpenCode 2 session lookup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
38918
|
+
return "failed";
|
|
38919
|
+
}
|
|
38920
|
+
const isSubagent = typeof session?.parentID === "string" && session.parentID.length > 0;
|
|
38921
|
+
sessionLog(sessionId, `[subagent-detect] OpenCode 2 session parentID=${JSON.stringify(session?.parentID)} → isSubagent=${isSubagent}`);
|
|
38922
|
+
return isSubagent;
|
|
38923
|
+
}
|
|
38924
|
+
function setCache2(sessionId, isSubagent, retryAfter) {
|
|
38925
|
+
if (cache2.has(sessionId))
|
|
38926
|
+
cache2.delete(sessionId);
|
|
38927
|
+
cache2.set(sessionId, retryAfter === undefined ? { isSubagent } : { isSubagent, retryAfter });
|
|
38928
|
+
if (cache2.size > CACHE_MAX_ENTRIES2) {
|
|
38929
|
+
const oldest = cache2.keys().next().value;
|
|
38930
|
+
if (oldest !== undefined)
|
|
38931
|
+
cache2.delete(oldest);
|
|
38932
|
+
}
|
|
38933
|
+
}
|
|
38934
|
+
|
|
38116
38935
|
// src/tool-perf.ts
|
|
38117
38936
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
38118
38937
|
var perfStore = new AsyncLocalStorage;
|
|
@@ -38128,16 +38947,29 @@ function markBridgeEnd() {
|
|
|
38128
38947
|
slot.bridgeEnd = performance.now();
|
|
38129
38948
|
}
|
|
38130
38949
|
}
|
|
38950
|
+
async function measurePreStage(stage, operation) {
|
|
38951
|
+
const start = performance.now();
|
|
38952
|
+
try {
|
|
38953
|
+
return await operation();
|
|
38954
|
+
} finally {
|
|
38955
|
+
const slot = perfStore.getStore();
|
|
38956
|
+
if (slot) {
|
|
38957
|
+
slot.stages ??= {};
|
|
38958
|
+
slot.stages[stage] = (slot.stages[stage] ?? 0) + performance.now() - start;
|
|
38959
|
+
}
|
|
38960
|
+
}
|
|
38961
|
+
}
|
|
38131
38962
|
function emit(slot) {
|
|
38963
|
+
const stages = slot.stages ? ` directory=${Math.round(slot.stages.directory ?? 0)}ms permission=${Math.round(slot.stages.permission ?? 0)}ms capability=${Math.round(slot.stages.capability ?? 0)}ms` : "";
|
|
38132
38964
|
const t3 = performance.now();
|
|
38133
38965
|
const total = Math.round(t3 - slot.t0);
|
|
38134
38966
|
if (slot.bridgeStart !== undefined && slot.bridgeEnd !== undefined) {
|
|
38135
38967
|
const pre = Math.round(slot.bridgeStart - slot.t0);
|
|
38136
38968
|
const bridge = Math.round(slot.bridgeEnd - slot.bridgeStart);
|
|
38137
38969
|
const post = Math.round(t3 - slot.bridgeEnd);
|
|
38138
|
-
sessionLog(slot.sessionID, `perf tool=${slot.tool} total=${total}ms pre=${pre}ms bridge=${bridge}ms post=${post}ms`);
|
|
38970
|
+
sessionLog(slot.sessionID, `perf tool=${slot.tool} total=${total}ms pre=${pre}ms bridge=${bridge}ms post=${post}ms${stages}`);
|
|
38139
38971
|
} else {
|
|
38140
|
-
sessionLog(slot.sessionID, `perf tool=${slot.tool} total=${total}ms (no bridge call)`);
|
|
38972
|
+
sessionLog(slot.sessionID, `perf tool=${slot.tool} total=${total}ms (no bridge call)${stages}`);
|
|
38141
38973
|
}
|
|
38142
38974
|
}
|
|
38143
38975
|
function instrumentToolMap(tools) {
|
|
@@ -38175,7 +39007,7 @@ function projectRootFor(runtime) {
|
|
|
38175
39007
|
if (typeof cached === "string" && cached.length > 0) {
|
|
38176
39008
|
return canonicalizeDirectory(cached);
|
|
38177
39009
|
}
|
|
38178
|
-
const raw = runtime.worktree ?? runtime.directory;
|
|
39010
|
+
const raw = runtime.directoryIsSessionRoot ? runtime.directory : runtime.worktree ?? runtime.directory;
|
|
38179
39011
|
return canonicalizeDirectory(raw);
|
|
38180
39012
|
}
|
|
38181
39013
|
async function resolveProjectRoot(ctx, runtime) {
|
|
@@ -38211,6 +39043,11 @@ function bridgeFor(ctx, runtime) {
|
|
|
38211
39043
|
}
|
|
38212
39044
|
return ctx.pool.getBridge(projectRoot);
|
|
38213
39045
|
}
|
|
39046
|
+
async function isWorkerRuntime(ctx, runtime) {
|
|
39047
|
+
if (!runtime.sessionID)
|
|
39048
|
+
return false;
|
|
39049
|
+
return resolveIsSubagent(ctx.client, runtime.sessionID, runtime.directory);
|
|
39050
|
+
}
|
|
38214
39051
|
async function callBridge(ctx, runtime, command, params = {}, options) {
|
|
38215
39052
|
if (runtime.sessionID && getSessionDirectoryCached(runtime.sessionID) === undefined) {
|
|
38216
39053
|
await getSessionDirectory(ctx.client, runtime.sessionID, runtime.directory);
|
|
@@ -38219,6 +39056,9 @@ async function callBridge(ctx, runtime, command, params = {}, options) {
|
|
|
38219
39056
|
if (runtime.sessionID) {
|
|
38220
39057
|
merged.session_id = runtime.sessionID;
|
|
38221
39058
|
}
|
|
39059
|
+
if (await isWorkerRuntime(ctx, runtime)) {
|
|
39060
|
+
merged[WORKER_SESSION_FIELD] = true;
|
|
39061
|
+
}
|
|
38222
39062
|
const timeoutMs = timeoutForCommand(command);
|
|
38223
39063
|
const sendOptions = {
|
|
38224
39064
|
...timeoutMs !== undefined ? { timeoutMs } : {},
|
|
@@ -38245,10 +39085,12 @@ async function callToolCall(ctx, runtime, name, rawArgs = {}, options) {
|
|
|
38245
39085
|
await getSessionDirectory(ctx.client, runtime.sessionID, runtime.directory);
|
|
38246
39086
|
}
|
|
38247
39087
|
const timeoutMs = timeoutForCommand(name);
|
|
39088
|
+
const workerSession = await isWorkerRuntime(ctx, runtime);
|
|
38248
39089
|
const sendOptions = {
|
|
38249
39090
|
...timeoutMs !== undefined ? { timeoutMs } : {},
|
|
38250
39091
|
configureWarningClient: ctx.client,
|
|
38251
39092
|
...runtime.effectAbort ? { abortSignal: runtime.effectAbort } : {},
|
|
39093
|
+
...workerSession ? { workerSession: true } : {},
|
|
38252
39094
|
...options
|
|
38253
39095
|
};
|
|
38254
39096
|
markBridgeStart();
|
|
@@ -38367,10 +39209,10 @@ import {
|
|
|
38367
39209
|
createReadStream as createReadStream2,
|
|
38368
39210
|
existsSync as existsSync15,
|
|
38369
39211
|
mkdirSync as mkdirSync7,
|
|
38370
|
-
readFileSync as
|
|
39212
|
+
readFileSync as readFileSync13,
|
|
38371
39213
|
renameSync as renameSync9,
|
|
38372
39214
|
rmSync as rmSync5,
|
|
38373
|
-
statSync as
|
|
39215
|
+
statSync as statSync11,
|
|
38374
39216
|
writeFileSync as writeFileSync8
|
|
38375
39217
|
} from "node:fs";
|
|
38376
39218
|
import { join as join17 } from "node:path";
|
|
@@ -38381,10 +39223,10 @@ import {
|
|
|
38381
39223
|
closeSync as closeSync6,
|
|
38382
39224
|
mkdirSync as mkdirSync6,
|
|
38383
39225
|
openSync as openSync6,
|
|
38384
|
-
readFileSync as
|
|
39226
|
+
readFileSync as readFileSync11,
|
|
38385
39227
|
renameSync as renameSync8,
|
|
38386
39228
|
rmSync as rmSync4,
|
|
38387
|
-
statSync as
|
|
39229
|
+
statSync as statSync10,
|
|
38388
39230
|
unlinkSync as unlinkSync6,
|
|
38389
39231
|
writeFileSync as writeFileSync7
|
|
38390
39232
|
} from "node:fs";
|
|
@@ -38407,7 +39249,7 @@ function lspBinDir(npmPackage) {
|
|
|
38407
39249
|
function isInstalled(npmPackage, binary) {
|
|
38408
39250
|
for (const candidate of lspBinaryCandidates(binary)) {
|
|
38409
39251
|
try {
|
|
38410
|
-
if (
|
|
39252
|
+
if (statSync10(join15(lspBinDir(npmPackage), candidate)).isFile())
|
|
38411
39253
|
return true;
|
|
38412
39254
|
} catch {}
|
|
38413
39255
|
}
|
|
@@ -38435,9 +39277,9 @@ function writeInstalledMetaIn(installDir, version, sha256) {
|
|
|
38435
39277
|
function readInstalledMetaIn(installDir) {
|
|
38436
39278
|
const path = join15(installDir, INSTALLED_META_FILE);
|
|
38437
39279
|
try {
|
|
38438
|
-
if (!
|
|
39280
|
+
if (!statSync10(path).isFile())
|
|
38439
39281
|
return null;
|
|
38440
|
-
const raw =
|
|
39282
|
+
const raw = readFileSync11(path, "utf8");
|
|
38441
39283
|
const parsed = JSON.parse(raw);
|
|
38442
39284
|
if (typeof parsed.version !== "string" || parsed.version.length === 0)
|
|
38443
39285
|
return null;
|
|
@@ -38506,12 +39348,12 @@ ${token}
|
|
|
38506
39348
|
let owningPid = null;
|
|
38507
39349
|
let lockMtimeMs = 0;
|
|
38508
39350
|
try {
|
|
38509
|
-
const raw =
|
|
39351
|
+
const raw = readFileSync11(lock, "utf8");
|
|
38510
39352
|
const firstLine = raw.split(/\r?\n/, 1)[0]?.trim() ?? "";
|
|
38511
39353
|
const parsed = Number.parseInt(firstLine, 10);
|
|
38512
39354
|
if (Number.isFinite(parsed) && parsed > 0)
|
|
38513
39355
|
owningPid = parsed;
|
|
38514
|
-
lockMtimeMs =
|
|
39356
|
+
lockMtimeMs = statSync10(lock).mtimeMs;
|
|
38515
39357
|
} catch {
|
|
38516
39358
|
return tryClaim();
|
|
38517
39359
|
}
|
|
@@ -38545,7 +39387,7 @@ function releaseInstallLock(lockKey, acquisitionToken) {
|
|
|
38545
39387
|
let owningPid = null;
|
|
38546
39388
|
let owningToken = "";
|
|
38547
39389
|
try {
|
|
38548
|
-
const raw =
|
|
39390
|
+
const raw = readFileSync11(lock, "utf8");
|
|
38549
39391
|
const lines = raw.split(/\r?\n/);
|
|
38550
39392
|
const parsed = Number.parseInt(lines[0]?.trim() ?? "", 10);
|
|
38551
39393
|
if (Number.isFinite(parsed) && parsed > 0)
|
|
@@ -38592,7 +39434,7 @@ function autoInstallStampPath() {
|
|
|
38592
39434
|
}
|
|
38593
39435
|
function hasRecentAutoInstallStamp(path) {
|
|
38594
39436
|
try {
|
|
38595
|
-
const parsed = JSON.parse(
|
|
39437
|
+
const parsed = JSON.parse(readFileSync11(path, "utf8"));
|
|
38596
39438
|
return typeof parsed.lastRunMs === "number" && Number.isFinite(parsed.lastRunMs) && Math.abs(Date.now() - parsed.lastRunMs) < AUTO_INSTALL_WINDOW_MS;
|
|
38597
39439
|
} catch {
|
|
38598
39440
|
return false;
|
|
@@ -38600,7 +39442,7 @@ function hasRecentAutoInstallStamp(path) {
|
|
|
38600
39442
|
}
|
|
38601
39443
|
function readAutoInstallLockOwner(path) {
|
|
38602
39444
|
try {
|
|
38603
|
-
const parsed = JSON.parse(
|
|
39445
|
+
const parsed = JSON.parse(readFileSync11(path, "utf8"));
|
|
38604
39446
|
if (typeof parsed.pid !== "number" || typeof parsed.startedMs !== "number" || typeof parsed.token !== "string") {
|
|
38605
39447
|
return null;
|
|
38606
39448
|
}
|
|
@@ -38613,7 +39455,7 @@ function autoInstallLockIsStale(path) {
|
|
|
38613
39455
|
const owner = readAutoInstallLockOwner(path);
|
|
38614
39456
|
let age = Number.POSITIVE_INFINITY;
|
|
38615
39457
|
try {
|
|
38616
|
-
age = Math.abs(Date.now() -
|
|
39458
|
+
age = Math.abs(Date.now() - statSync10(path).mtimeMs);
|
|
38617
39459
|
} catch {
|
|
38618
39460
|
return true;
|
|
38619
39461
|
}
|
|
@@ -38691,7 +39533,7 @@ var VERSION_CHECK_FILE = ".aft-version-check";
|
|
|
38691
39533
|
function readVersionCheck(npmPackage) {
|
|
38692
39534
|
const file = join15(lspPackageDir(npmPackage), VERSION_CHECK_FILE);
|
|
38693
39535
|
try {
|
|
38694
|
-
const raw =
|
|
39536
|
+
const raw = readFileSync11(file, "utf8");
|
|
38695
39537
|
const parsed = JSON.parse(raw);
|
|
38696
39538
|
if (typeof parsed.last_checked === "string") {
|
|
38697
39539
|
return {
|
|
@@ -38794,6 +39636,14 @@ var NPM_LSP_TABLE = [
|
|
|
38794
39636
|
extensions: ["ts", "tsx", "js", "jsx", "mjs", "cjs", "mts", "cts"],
|
|
38795
39637
|
rootMarkers: ["tsconfig.json", "jsconfig.json", "package.json"]
|
|
38796
39638
|
},
|
|
39639
|
+
{
|
|
39640
|
+
id: "typescript-sdk",
|
|
39641
|
+
npm: "typescript",
|
|
39642
|
+
binary: "tsserver",
|
|
39643
|
+
extensions: ["ts", "tsx", "js", "jsx", "mjs", "cjs", "mts", "cts"],
|
|
39644
|
+
rootMarkers: ["tsconfig.json", "jsconfig.json", "package.json"],
|
|
39645
|
+
supportedMajor: 5
|
|
39646
|
+
},
|
|
38797
39647
|
{
|
|
38798
39648
|
id: "python",
|
|
38799
39649
|
npm: "pyright",
|
|
@@ -38866,9 +39716,18 @@ var NPM_LSP_TABLE = [
|
|
|
38866
39716
|
rootMarkers: ["biome.json", "biome.jsonc"]
|
|
38867
39717
|
}
|
|
38868
39718
|
];
|
|
39719
|
+
function majorVersionOf(version) {
|
|
39720
|
+
const match = /^v?(\d+)(?:\.|$)/.exec(version.trim());
|
|
39721
|
+
return match ? Number(match[1]) : null;
|
|
39722
|
+
}
|
|
39723
|
+
function isVersionSupported(spec, version) {
|
|
39724
|
+
if (spec.supportedMajor === undefined)
|
|
39725
|
+
return true;
|
|
39726
|
+
return majorVersionOf(version) === spec.supportedMajor;
|
|
39727
|
+
}
|
|
38869
39728
|
|
|
38870
39729
|
// src/lsp-project-relevance.ts
|
|
38871
|
-
import { existsSync as existsSync14, readdirSync as readdirSync4, readFileSync as
|
|
39730
|
+
import { existsSync as existsSync14, readdirSync as readdirSync4, readFileSync as readFileSync12 } from "node:fs";
|
|
38872
39731
|
import { join as join16 } from "node:path";
|
|
38873
39732
|
var MAX_WALK_DIRS = 200;
|
|
38874
39733
|
var MAX_WALK_DEPTH = 4;
|
|
@@ -38910,7 +39769,7 @@ function hasPackageJsonDep(projectRoot, depNames) {
|
|
|
38910
39769
|
}
|
|
38911
39770
|
function readPackageJson(projectRoot) {
|
|
38912
39771
|
try {
|
|
38913
|
-
const raw =
|
|
39772
|
+
const raw = readFileSync12(join16(projectRoot, "package.json"), "utf8");
|
|
38914
39773
|
const parsed = JSON.parse(raw);
|
|
38915
39774
|
if (typeof parsed !== "object" || parsed === null)
|
|
38916
39775
|
return null;
|
|
@@ -38962,10 +39821,11 @@ function extensionOf(fileName) {
|
|
|
38962
39821
|
|
|
38963
39822
|
// src/lsp-registry-probe.ts
|
|
38964
39823
|
var NPM_REGISTRY_BASE = "https://registry.npmjs.org";
|
|
38965
|
-
function pickEligibleVersion(response, graceDays, now = Date.now()) {
|
|
39824
|
+
function pickEligibleVersion(response, graceDays, now = Date.now(), accept = () => true) {
|
|
38966
39825
|
const times = response.time || {};
|
|
38967
39826
|
const cutoff = now - graceDays * 24 * 60 * 60 * 1000;
|
|
38968
39827
|
const candidates = [];
|
|
39828
|
+
let releaseCount = 0;
|
|
38969
39829
|
for (const [version, publishedAt] of Object.entries(times)) {
|
|
38970
39830
|
if (version === "created" || version === "modified")
|
|
38971
39831
|
continue;
|
|
@@ -38976,6 +39836,9 @@ function pickEligibleVersion(response, graceDays, now = Date.now()) {
|
|
|
38976
39836
|
const ts = Date.parse(publishedAt);
|
|
38977
39837
|
if (Number.isNaN(ts))
|
|
38978
39838
|
continue;
|
|
39839
|
+
releaseCount += 1;
|
|
39840
|
+
if (!accept(version))
|
|
39841
|
+
continue;
|
|
38979
39842
|
candidates.push({ version, publishedAt, ts });
|
|
38980
39843
|
}
|
|
38981
39844
|
candidates.sort((a, b) => b.ts - a.ts);
|
|
@@ -38984,10 +39847,11 @@ function pickEligibleVersion(response, graceDays, now = Date.now()) {
|
|
|
38984
39847
|
return {
|
|
38985
39848
|
version: eligible[0]?.version ?? null,
|
|
38986
39849
|
blockedByGrace,
|
|
39850
|
+
outsideSupportedRange: releaseCount > 0 && candidates.length === 0,
|
|
38987
39851
|
eligible: eligible.map(({ version, publishedAt }) => ({ version, publishedAt }))
|
|
38988
39852
|
};
|
|
38989
39853
|
}
|
|
38990
|
-
async function probeRegistry(npmPackage, graceDays, fetchImpl = fetch) {
|
|
39854
|
+
async function probeRegistry(npmPackage, graceDays, fetchImpl = fetch, accept) {
|
|
38991
39855
|
const encoded = encodeURIComponent(npmPackage).replace(/^%40/, "@");
|
|
38992
39856
|
const url = `${NPM_REGISTRY_BASE}/${encoded}`;
|
|
38993
39857
|
try {
|
|
@@ -39000,7 +39864,7 @@ async function probeRegistry(npmPackage, graceDays, fetchImpl = fetch) {
|
|
|
39000
39864
|
return null;
|
|
39001
39865
|
}
|
|
39002
39866
|
const json = await res.json();
|
|
39003
|
-
return pickEligibleVersion(json, graceDays);
|
|
39867
|
+
return pickEligibleVersion(json, graceDays, Date.now(), accept);
|
|
39004
39868
|
} catch (err) {
|
|
39005
39869
|
warn2(`[lsp] registry probe failed for ${npmPackage}: ${err}`);
|
|
39006
39870
|
return null;
|
|
@@ -39051,11 +39915,11 @@ async function resolveTargetVersion(spec, config, fetchImpl = fetch) {
|
|
|
39051
39915
|
}
|
|
39052
39916
|
const cached = readVersionCheck(spec.npm);
|
|
39053
39917
|
const weeklyMs = config.graceDays * 24 * 60 * 60 * 1000;
|
|
39054
|
-
const cachedSafe = isSafeVersion(cached?.latest_eligible ?? null);
|
|
39918
|
+
const cachedSafe = isSafeVersion(cached?.latest_eligible ?? null) && isVersionSupported(spec, cached?.latest_eligible);
|
|
39055
39919
|
if (cached && !shouldRecheckVersion(cached, weeklyMs) && cachedSafe) {
|
|
39056
39920
|
return { version: cached.latest_eligible, pinned: false, probe: null };
|
|
39057
39921
|
}
|
|
39058
|
-
const probe = await probeRegistry(spec.npm, config.graceDays, fetchImpl);
|
|
39922
|
+
const probe = await probeRegistry(spec.npm, config.graceDays, fetchImpl, (candidate) => isVersionSupported(spec, candidate));
|
|
39059
39923
|
if (!probe) {
|
|
39060
39924
|
return {
|
|
39061
39925
|
version: cachedSafe ? cached?.latest_eligible ?? null : null,
|
|
@@ -39190,11 +40054,16 @@ async function ensureServerInstalled(spec, config, fetchImpl, signal) {
|
|
|
39190
40054
|
const { version, probe } = await resolveTargetVersion(spec, config, fetchImpl);
|
|
39191
40055
|
if (!version) {
|
|
39192
40056
|
const installed = isInstalled(spec.npm, spec.binary);
|
|
39193
|
-
if (installed) {
|
|
40057
|
+
if (installed && installedVersionAllowed(spec, config)) {
|
|
39194
40058
|
warn2(`[lsp] no eligible version of ${spec.npm} (grace=${config.graceDays}d); keeping existing install`);
|
|
39195
40059
|
return { started: false, reason: "kept existing install" };
|
|
39196
40060
|
}
|
|
39197
|
-
const blocked = probe?.blockedByGrace ? `all versions are within ${config.graceDays}-day grace window` : "registry probe failed";
|
|
40061
|
+
const blocked = probe?.blockedByGrace ? `all versions are within ${config.graceDays}-day grace window` : probe?.outsideSupportedRange ? `no ${spec.supportedMajor}.x release on the registry` : "registry probe failed";
|
|
40062
|
+
if (installed) {
|
|
40063
|
+
const reason = `cached ${spec.npm} ${readInstalledMeta(spec.npm)?.version ?? "(unknown version)"} is outside the supported ${spec.supportedMajor}.x line and no replacement is available: ${blocked}`;
|
|
40064
|
+
warn2(`[lsp] ${reason}`);
|
|
40065
|
+
return { started: false, reason };
|
|
40066
|
+
}
|
|
39198
40067
|
warn2(`[lsp] skipping ${spec.npm}: ${blocked}`);
|
|
39199
40068
|
return { started: false, reason: blocked };
|
|
39200
40069
|
}
|
|
@@ -39258,7 +40127,7 @@ function hashInstalledBinary(spec) {
|
|
|
39258
40127
|
let pathToHash = null;
|
|
39259
40128
|
for (const p of candidates) {
|
|
39260
40129
|
try {
|
|
39261
|
-
if (
|
|
40130
|
+
if (statSync11(p).isFile()) {
|
|
39262
40131
|
pathToHash = p;
|
|
39263
40132
|
break;
|
|
39264
40133
|
}
|
|
@@ -39284,14 +40153,14 @@ function installedBinaryPath(spec) {
|
|
|
39284
40153
|
] : [lspBinaryPath(spec.npm, spec.binary)];
|
|
39285
40154
|
for (const candidate of candidates) {
|
|
39286
40155
|
try {
|
|
39287
|
-
if (
|
|
40156
|
+
if (statSync11(candidate).isFile())
|
|
39288
40157
|
return candidate;
|
|
39289
40158
|
} catch {}
|
|
39290
40159
|
}
|
|
39291
40160
|
return null;
|
|
39292
40161
|
}
|
|
39293
40162
|
function sha256OfFileSync(path) {
|
|
39294
|
-
return createHash7("sha256").update(
|
|
40163
|
+
return createHash7("sha256").update(readFileSync13(path)).digest("hex");
|
|
39295
40164
|
}
|
|
39296
40165
|
function quarantineCachedNpmInstall(spec, reason) {
|
|
39297
40166
|
const packageDir = lspPackageDir(spec.npm);
|
|
@@ -39319,6 +40188,15 @@ function validateCachedNpmInstall(spec) {
|
|
|
39319
40188
|
}
|
|
39320
40189
|
return true;
|
|
39321
40190
|
}
|
|
40191
|
+
function installedVersionAllowed(spec, config) {
|
|
40192
|
+
const installed = readInstalledMeta(spec.npm)?.version;
|
|
40193
|
+
if (!installed)
|
|
40194
|
+
return spec.supportedMajor === undefined;
|
|
40195
|
+
return isVersionSupported(spec, installed) || config.versions[spec.npm] === installed;
|
|
40196
|
+
}
|
|
40197
|
+
function cachedInstallUsable(spec, config) {
|
|
40198
|
+
return isInstalled(spec.npm, spec.binary) && validateCachedNpmInstall(spec) && installedVersionAllowed(spec, config);
|
|
40199
|
+
}
|
|
39322
40200
|
function runAutoInstall(projectRoot, config, fetchImpl = fetch) {
|
|
39323
40201
|
const cachedBinDirs = [];
|
|
39324
40202
|
const skipped = [];
|
|
@@ -39331,10 +40209,10 @@ function runAutoInstall(projectRoot, config, fetchImpl = fetch) {
|
|
|
39331
40209
|
return projectExtensions;
|
|
39332
40210
|
};
|
|
39333
40211
|
for (const spec of NPM_LSP_TABLE) {
|
|
39334
|
-
if (
|
|
40212
|
+
if (cachedInstallUsable(spec, config)) {
|
|
39335
40213
|
cachedBinDirs.push(lspBinDir(spec.npm));
|
|
39336
40214
|
}
|
|
39337
|
-
if (config.disabled.has(spec.id)) {
|
|
40215
|
+
if (config.disabled.has(spec.id) || spec.id === "typescript-sdk" && config.disabled.has("typescript")) {
|
|
39338
40216
|
skipped.push({ id: spec.id, reason: "disabled by config" });
|
|
39339
40217
|
continue;
|
|
39340
40218
|
}
|
|
@@ -39371,7 +40249,7 @@ function runAutoInstall(projectRoot, config, fetchImpl = fetch) {
|
|
|
39371
40249
|
},
|
|
39372
40250
|
skipped,
|
|
39373
40251
|
installsComplete: Promise.all(installPromises).then(() => {}),
|
|
39374
|
-
getCachedBinDirs: () => NPM_LSP_TABLE.filter((spec) =>
|
|
40252
|
+
getCachedBinDirs: () => NPM_LSP_TABLE.filter((spec) => cachedInstallUsable(spec, config)).map((spec) => lspBinDir(spec.npm))
|
|
39375
40253
|
};
|
|
39376
40254
|
}
|
|
39377
40255
|
async function pushLspPathsAfterAutoInstall(pool, projectRoot, cachedBinDirs) {
|
|
@@ -39390,16 +40268,16 @@ import {
|
|
|
39390
40268
|
lstatSync as lstatSync2,
|
|
39391
40269
|
mkdirSync as mkdirSync8,
|
|
39392
40270
|
readdirSync as readdirSync5,
|
|
39393
|
-
readFileSync as
|
|
40271
|
+
readFileSync as readFileSync14,
|
|
39394
40272
|
readlinkSync as readlinkSync2,
|
|
39395
40273
|
realpathSync as realpathSync4,
|
|
39396
40274
|
renameSync as renameSync10,
|
|
39397
40275
|
rmSync as rmSync6,
|
|
39398
|
-
statSync as
|
|
40276
|
+
statSync as statSync12,
|
|
39399
40277
|
unlinkSync as unlinkSync7,
|
|
39400
40278
|
writeFileSync as writeFileSync9
|
|
39401
40279
|
} from "node:fs";
|
|
39402
|
-
import { dirname as
|
|
40280
|
+
import { dirname as dirname9, join as join18, relative as relative3, resolve as resolve10 } from "node:path";
|
|
39403
40281
|
import { Readable as Readable3 } from "node:stream";
|
|
39404
40282
|
import { pipeline as pipeline3 } from "node:stream/promises";
|
|
39405
40283
|
|
|
@@ -39508,7 +40386,7 @@ function installFailureMarkerPath(spec) {
|
|
|
39508
40386
|
}
|
|
39509
40387
|
function readInstallFailureMarker(spec) {
|
|
39510
40388
|
try {
|
|
39511
|
-
const marker = JSON.parse(
|
|
40389
|
+
const marker = JSON.parse(readFileSync14(installFailureMarkerPath(spec), "utf8"));
|
|
39512
40390
|
if (typeof marker.release !== "string" || typeof marker.sha256 !== "string" || typeof marker.error !== "string" || typeof marker.at !== "string" || !Number.isFinite(Date.parse(marker.at))) {
|
|
39513
40391
|
return null;
|
|
39514
40392
|
}
|
|
@@ -39522,7 +40400,7 @@ function writeInstallFailureMarker(spec, release, sha256, installError) {
|
|
|
39522
40400
|
const markerError = installError.replace(/\s+/g, " ").trim();
|
|
39523
40401
|
const tempPath = `${path}.tmp-${process.pid}-${randomBytes4(6).toString("hex")}`;
|
|
39524
40402
|
try {
|
|
39525
|
-
mkdirSync8(
|
|
40403
|
+
mkdirSync8(dirname9(path), { recursive: true });
|
|
39526
40404
|
writeFileSync9(tempPath, `${JSON.stringify({ release, sha256, error: markerError, at: new Date().toISOString() })}
|
|
39527
40405
|
`, { encoding: "utf8", mode: 384 });
|
|
39528
40406
|
renameSync10(tempPath, path);
|
|
@@ -39551,7 +40429,7 @@ function ghBinaryPath(spec, platform) {
|
|
|
39551
40429
|
function isGithubInstalled(spec, platform) {
|
|
39552
40430
|
for (const candidate of ghBinaryCandidates(spec, platform)) {
|
|
39553
40431
|
try {
|
|
39554
|
-
if (
|
|
40432
|
+
if (statSync12(join18(ghBinDir(spec), candidate)).isFile())
|
|
39555
40433
|
return true;
|
|
39556
40434
|
} catch {}
|
|
39557
40435
|
}
|
|
@@ -39565,9 +40443,9 @@ function ghBinaryCandidates(spec, platform) {
|
|
|
39565
40443
|
function readGithubInstalledMetaIn(installDir) {
|
|
39566
40444
|
try {
|
|
39567
40445
|
const path = join18(installDir, INSTALLED_META_FILE2);
|
|
39568
|
-
if (!
|
|
40446
|
+
if (!statSync12(path).isFile())
|
|
39569
40447
|
return null;
|
|
39570
|
-
const parsed = JSON.parse(
|
|
40448
|
+
const parsed = JSON.parse(readFileSync14(path, "utf8"));
|
|
39571
40449
|
if (typeof parsed.version !== "string" || parsed.version.length === 0)
|
|
39572
40450
|
return null;
|
|
39573
40451
|
return {
|
|
@@ -39608,7 +40486,7 @@ function sha256OfFile(path) {
|
|
|
39608
40486
|
});
|
|
39609
40487
|
}
|
|
39610
40488
|
function sha256OfFileSync2(path) {
|
|
39611
|
-
return createHash8("sha256").update(
|
|
40489
|
+
return createHash8("sha256").update(readFileSync14(path)).digest("hex");
|
|
39612
40490
|
}
|
|
39613
40491
|
async function fetchReleaseByTag(githubRepo, tag, fetchImpl, signal) {
|
|
39614
40492
|
const candidates = [];
|
|
@@ -39784,7 +40662,7 @@ async function downloadFile(url, destPath, fetchImpl, assetSize, signal) {
|
|
|
39784
40662
|
if (Number.isFinite(advertised) && advertised > MAX_DOWNLOAD_BYTES3) {
|
|
39785
40663
|
throw new Error(`Content-Length ${advertised} exceeds max ${MAX_DOWNLOAD_BYTES3}`);
|
|
39786
40664
|
}
|
|
39787
|
-
mkdirSync8(
|
|
40665
|
+
mkdirSync8(dirname9(destPath), { recursive: true });
|
|
39788
40666
|
let bytesWritten = 0;
|
|
39789
40667
|
const guard = new TransformStream({
|
|
39790
40668
|
transform(chunk, controller) {
|
|
@@ -39954,7 +40832,7 @@ function quarantineCachedGithubInstall(spec, reason) {
|
|
|
39954
40832
|
const dest = join18(ghCacheRoot(), ".quarantine", encodeURIComponent(spec.id), `${Date.now()}`);
|
|
39955
40833
|
warn2(`[lsp] tofu_mismatch ${spec.id}: ${reason}; quarantining ${packageDir} -> ${dest}`);
|
|
39956
40834
|
try {
|
|
39957
|
-
mkdirSync8(
|
|
40835
|
+
mkdirSync8(dirname9(dest), { recursive: true });
|
|
39958
40836
|
rmSync6(dest, { recursive: true, force: true });
|
|
39959
40837
|
renameSync10(packageDir, dest);
|
|
39960
40838
|
} catch (err) {
|
|
@@ -39966,7 +40844,7 @@ function validateCachedGithubInstall(spec, platform) {
|
|
|
39966
40844
|
const meta = readGithubInstalledMetaIn(packageDir);
|
|
39967
40845
|
const binaryPath = ghBinaryCandidates(spec, platform).map((candidate) => join18(ghBinDir(spec), candidate)).find((candidate) => {
|
|
39968
40846
|
try {
|
|
39969
|
-
return
|
|
40847
|
+
return statSync12(candidate).isFile();
|
|
39970
40848
|
} catch {
|
|
39971
40849
|
return false;
|
|
39972
40850
|
}
|
|
@@ -40084,7 +40962,7 @@ async function downloadAndInstall(spec, tag, assets, platform, arch, fetchImpl,
|
|
|
40084
40962
|
return { ok: false, sha256: archiveSha256, error: message };
|
|
40085
40963
|
}
|
|
40086
40964
|
const targetBinary = ghBinaryPath(spec, platform);
|
|
40087
|
-
mkdirSync8(
|
|
40965
|
+
mkdirSync8(dirname9(targetBinary), { recursive: true });
|
|
40088
40966
|
try {
|
|
40089
40967
|
copyFileSync3(innerBinaryPath, targetBinary);
|
|
40090
40968
|
if (platform !== "win32") {
|
|
@@ -40613,14 +41491,16 @@ async function assertExternalDirectoryPermission(ctx, context, target, options)
|
|
|
40613
41491
|
const directory = root ? normalizePath(root) : root;
|
|
40614
41492
|
const rawWorktree = context.worktree;
|
|
40615
41493
|
const worktree = rawWorktree && rawWorktree !== "/" ? normalizePath(rawWorktree) : rawWorktree;
|
|
41494
|
+
const restrictToProjectRoot = ctx.config.restrict_to_project_root === true;
|
|
40616
41495
|
if (directory && containsPath(directory, absoluteTarget))
|
|
40617
41496
|
return;
|
|
40618
|
-
if (worktree && worktree !== "/" && worktree !== directory && containsPath(worktree, absoluteTarget)) {
|
|
41497
|
+
if (!restrictToProjectRoot && worktree && worktree !== "/" && worktree !== directory && containsPath(worktree, absoluteTarget)) {
|
|
40619
41498
|
return;
|
|
40620
41499
|
}
|
|
40621
|
-
if (
|
|
40622
|
-
if (options?.serverValidatedRead === true)
|
|
41500
|
+
if (restrictToProjectRoot) {
|
|
41501
|
+
if (options?.serverValidatedRead === true && await isSessionOwnedBashArtifact(ctx, context, absoluteTarget)) {
|
|
40623
41502
|
return;
|
|
41503
|
+
}
|
|
40624
41504
|
notifyRestrictBlocked(ctx, context, absoluteTarget);
|
|
40625
41505
|
return restrictDenialMessage(absoluteTarget);
|
|
40626
41506
|
}
|
|
@@ -40920,312 +41800,8 @@ function astTools(ctx) {
|
|
|
40920
41800
|
};
|
|
40921
41801
|
}
|
|
40922
41802
|
|
|
40923
|
-
// src/tools/conflicts.ts
|
|
40924
|
-
var z3 = tool.schema;
|
|
40925
|
-
function conflictTools(ctx) {
|
|
40926
|
-
return {
|
|
40927
|
-
aft_conflicts: {
|
|
40928
|
-
description: "Show all git merge conflicts across the repository — returns line-numbered conflict regions with context for every conflicted file in a single call. Conflicts are discovered from the git repository's top level. By default it inspects the session's project repository; pass `path` to inspect a different repository or git worktree (e.g. where a rebase/merge is running).",
|
|
40929
|
-
args: {
|
|
40930
|
-
path: z3.string().describe("Optional path inside the git repository or worktree to inspect (absolute or relative to project root). Conflicts are discovered from that repository's top level. Defaults to the session project root.").optional()
|
|
40931
|
-
},
|
|
40932
|
-
execute: async (args, context) => {
|
|
40933
|
-
const rawArgs = {};
|
|
40934
|
-
if (!isEmptyParam(args?.path)) {
|
|
40935
|
-
const expanded = expandTilde2(String(args.path));
|
|
40936
|
-
const projectRoot = await resolveProjectRoot(ctx, context);
|
|
40937
|
-
const resolved = resolvePathFromProjectRoot(projectRoot, expanded);
|
|
40938
|
-
const denied = await assertExternalDirectoryPermission(ctx, context, resolved, {
|
|
40939
|
-
kind: "directory"
|
|
40940
|
-
});
|
|
40941
|
-
if (denied)
|
|
40942
|
-
return permissionDeniedResponse(denied);
|
|
40943
|
-
rawArgs.path = resolved;
|
|
40944
|
-
}
|
|
40945
|
-
const response = await callToolCall(ctx, context, "conflicts", rawArgs);
|
|
40946
|
-
if (response.success === false) {
|
|
40947
|
-
throw new Error(response.message || "git_conflicts failed");
|
|
40948
|
-
}
|
|
40949
|
-
return response.text;
|
|
40950
|
-
}
|
|
40951
|
-
}
|
|
40952
|
-
};
|
|
40953
|
-
}
|
|
40954
|
-
|
|
40955
|
-
// src/tools/definitions/v2.ts
|
|
40956
|
-
import { Effect } from "effect";
|
|
40957
|
-
var V2_TOOL_NAME = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
|
|
40958
|
-
var V2_PATH_HEADER_TOOLS = new Set(["read", "write", "edit"]);
|
|
40959
|
-
var V2_BASH_TOOLS = new Set(["bash"]);
|
|
40960
|
-
var V2_SHELL_PERMISSION = "shell";
|
|
40961
|
-
var ROOT_COMBINATORS = ["anyOf", "oneOf", "allOf"];
|
|
40962
|
-
function failure(error) {
|
|
40963
|
-
if (error instanceof Error)
|
|
40964
|
-
return error;
|
|
40965
|
-
const detail = (() => {
|
|
40966
|
-
try {
|
|
40967
|
-
return JSON.stringify(error) ?? String(error);
|
|
40968
|
-
} catch {
|
|
40969
|
-
return String(error);
|
|
40970
|
-
}
|
|
40971
|
-
})();
|
|
40972
|
-
return new Error(`V2 tool execution rejected with a non-Error value: ${detail}`);
|
|
40973
|
-
}
|
|
40974
|
-
function isPlainObject3(value) {
|
|
40975
|
-
if (typeof value !== "object" || value === null)
|
|
40976
|
-
return false;
|
|
40977
|
-
const prototype = Object.getPrototypeOf(value);
|
|
40978
|
-
return prototype === Object.prototype || prototype === null;
|
|
40979
|
-
}
|
|
40980
|
-
function jsonSafeMetadata(value) {
|
|
40981
|
-
if (Array.isArray(value)) {
|
|
40982
|
-
return value.map((entry) => entry === undefined ? null : jsonSafeMetadata(entry));
|
|
40983
|
-
}
|
|
40984
|
-
if (!isPlainObject3(value))
|
|
40985
|
-
return value;
|
|
40986
|
-
const cleaned = {};
|
|
40987
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
40988
|
-
if (entry === undefined)
|
|
40989
|
-
continue;
|
|
40990
|
-
cleaned[key] = jsonSafeMetadata(entry);
|
|
40991
|
-
}
|
|
40992
|
-
return cleaned;
|
|
40993
|
-
}
|
|
40994
|
-
function resultContent(result) {
|
|
40995
|
-
if (typeof result === "string")
|
|
40996
|
-
return { content: result };
|
|
40997
|
-
const attachments = (result.attachments ?? []).map((attachment) => ({
|
|
40998
|
-
type: "file",
|
|
40999
|
-
uri: attachment.url,
|
|
41000
|
-
mime: attachment.mime,
|
|
41001
|
-
...attachment.filename ? { name: attachment.filename } : {}
|
|
41002
|
-
}));
|
|
41003
|
-
const content = attachments.length ? [{ type: "text", text: result.output }, ...attachments] : result.output;
|
|
41004
|
-
const metadata = jsonSafeMetadata({
|
|
41005
|
-
...result.metadata ?? {},
|
|
41006
|
-
...result.title ? { title: result.title } : {}
|
|
41007
|
-
});
|
|
41008
|
-
return {
|
|
41009
|
-
content,
|
|
41010
|
-
...Object.keys(metadata).length ? { metadata } : {}
|
|
41011
|
-
};
|
|
41012
|
-
}
|
|
41013
|
-
function failureEnvelopeMessage(result) {
|
|
41014
|
-
const output = typeof result === "string" ? result : result.output;
|
|
41015
|
-
if (typeof output !== "string")
|
|
41016
|
-
return;
|
|
41017
|
-
const trimmed = output.trim();
|
|
41018
|
-
if (!trimmed.startsWith("{") || !trimmed.includes('"success"'))
|
|
41019
|
-
return;
|
|
41020
|
-
let parsed;
|
|
41021
|
-
try {
|
|
41022
|
-
parsed = JSON.parse(trimmed);
|
|
41023
|
-
} catch {
|
|
41024
|
-
return;
|
|
41025
|
-
}
|
|
41026
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
41027
|
-
return;
|
|
41028
|
-
const envelope = parsed;
|
|
41029
|
-
if (envelope.success !== false)
|
|
41030
|
-
return;
|
|
41031
|
-
for (const field of [envelope.message, envelope.error, envelope.code]) {
|
|
41032
|
-
if (typeof field === "string" && field.length > 0)
|
|
41033
|
-
return field;
|
|
41034
|
-
}
|
|
41035
|
-
return "The tool call failed.";
|
|
41036
|
-
}
|
|
41037
|
-
function bareToolName3(name) {
|
|
41038
|
-
return name.startsWith("aft_") ? name.slice(4) : name;
|
|
41039
|
-
}
|
|
41040
|
-
function projectArguments(name, definition) {
|
|
41041
|
-
if (!V2_PATH_HEADER_TOOLS.has(bareToolName3(name)) || !("filePath" in definition.args)) {
|
|
41042
|
-
return { ...definition.args };
|
|
41043
|
-
}
|
|
41044
|
-
const { filePath, ...rest } = definition.args;
|
|
41045
|
-
return { path: filePath, ...rest };
|
|
41046
|
-
}
|
|
41047
|
-
function executionArguments(name, input) {
|
|
41048
|
-
if (!V2_PATH_HEADER_TOOLS.has(bareToolName3(name)) || !("path" in input))
|
|
41049
|
-
return input;
|
|
41050
|
-
const { path, ...rest } = input;
|
|
41051
|
-
return { filePath: path, ...rest };
|
|
41052
|
-
}
|
|
41053
|
-
function hostPermission(name) {
|
|
41054
|
-
const bare = bareToolName3(name);
|
|
41055
|
-
if (new Set(["read", "glob", "grep"]).has(bare))
|
|
41056
|
-
return bare;
|
|
41057
|
-
if (V2_BASH_TOOLS.has(name))
|
|
41058
|
-
return V2_SHELL_PERMISSION;
|
|
41059
|
-
if (new Set([
|
|
41060
|
-
"write",
|
|
41061
|
-
"edit",
|
|
41062
|
-
"apply_patch",
|
|
41063
|
-
"delete",
|
|
41064
|
-
"move",
|
|
41065
|
-
"ast_grep_replace",
|
|
41066
|
-
"import",
|
|
41067
|
-
"safety"
|
|
41068
|
-
]).has(bare)) {
|
|
41069
|
-
return "edit";
|
|
41070
|
-
}
|
|
41071
|
-
return;
|
|
41072
|
-
}
|
|
41073
|
-
function assertV2Contract(name, input) {
|
|
41074
|
-
if (!V2_TOOL_NAME.test(name)) {
|
|
41075
|
-
throw new Error(`Invalid V2 tool name ${JSON.stringify(name)}; expected ${V2_TOOL_NAME.source}`);
|
|
41076
|
-
}
|
|
41077
|
-
const schema = tool.schema.toJSONSchema(input, { io: "input" });
|
|
41078
|
-
if (schema.type !== "object") {
|
|
41079
|
-
throw new Error(`V2 tool ${name} must have an object-rooted input schema`);
|
|
41080
|
-
}
|
|
41081
|
-
for (const keyword of ROOT_COMBINATORS) {
|
|
41082
|
-
if (keyword in schema) {
|
|
41083
|
-
throw new Error(`V2 tool ${name} input schema cannot use root ${keyword}`);
|
|
41084
|
-
}
|
|
41085
|
-
}
|
|
41086
|
-
}
|
|
41087
|
-
function v2PermissionRequest(name, request) {
|
|
41088
|
-
if (!V2_BASH_TOOLS.has(name) || request.permission !== "bash")
|
|
41089
|
-
return request;
|
|
41090
|
-
return { ...request, permission: V2_SHELL_PERMISSION };
|
|
41091
|
-
}
|
|
41092
|
-
function runtimeFor(name, location, context, signal, consumers) {
|
|
41093
|
-
const directory = location.directory;
|
|
41094
|
-
const worktree = location.project?.canonical ?? location.project?.directory ?? directory;
|
|
41095
|
-
return {
|
|
41096
|
-
sessionID: context.sessionID,
|
|
41097
|
-
messageID: context.messageID,
|
|
41098
|
-
agent: context.agent,
|
|
41099
|
-
directory,
|
|
41100
|
-
worktree,
|
|
41101
|
-
abort: signal,
|
|
41102
|
-
effectAbort: signal,
|
|
41103
|
-
metadata: (update) => {
|
|
41104
|
-
Effect.runPromise(context.progress(jsonSafeMetadata({
|
|
41105
|
-
...update.metadata ?? {},
|
|
41106
|
-
...update.title ? { title: update.title } : {}
|
|
41107
|
-
})));
|
|
41108
|
-
},
|
|
41109
|
-
ask: (asked) => {
|
|
41110
|
-
const request = v2PermissionRequest(name, asked);
|
|
41111
|
-
if (consumers.requestPermission)
|
|
41112
|
-
return consumers.requestPermission(request, context);
|
|
41113
|
-
return Promise.reject(new Error(`The "${request.permission}" operation was refused because this AFT runtime has no permission evaluator bound, so the host's permission rules for it could not be consulted.`));
|
|
41114
|
-
},
|
|
41115
|
-
progress: context.progress
|
|
41116
|
-
};
|
|
41117
|
-
}
|
|
41118
|
-
function projectV2Tool(name, definition, location, consumers = {}) {
|
|
41119
|
-
const input = tool.schema.object(projectArguments(name, definition));
|
|
41120
|
-
assertV2Contract(name, input);
|
|
41121
|
-
const sharedOptions = definition.options;
|
|
41122
|
-
const permission = hostPermission(name);
|
|
41123
|
-
return {
|
|
41124
|
-
name,
|
|
41125
|
-
description: definition.description,
|
|
41126
|
-
input,
|
|
41127
|
-
options: {
|
|
41128
|
-
...sharedOptions,
|
|
41129
|
-
...permission ? { permission } : {},
|
|
41130
|
-
codemode: false
|
|
41131
|
-
},
|
|
41132
|
-
execute: (rawInput, context) => Effect.tryPromise({
|
|
41133
|
-
try: async (signal) => {
|
|
41134
|
-
const runtime = runtimeFor(name, location, context, signal, consumers);
|
|
41135
|
-
const input = executionArguments(name, rawInput);
|
|
41136
|
-
const result = consumers.executeBash && V2_BASH_TOOLS.has(name) ? await consumers.executeBash({
|
|
41137
|
-
name,
|
|
41138
|
-
input,
|
|
41139
|
-
context: runtime,
|
|
41140
|
-
definition
|
|
41141
|
-
}) : await definition.execute(input, runtime);
|
|
41142
|
-
const refusal = failureEnvelopeMessage(result);
|
|
41143
|
-
if (refusal)
|
|
41144
|
-
throw new Error(refusal);
|
|
41145
|
-
return resultContent(result);
|
|
41146
|
-
},
|
|
41147
|
-
catch: failure
|
|
41148
|
-
})
|
|
41149
|
-
};
|
|
41150
|
-
}
|
|
41151
|
-
|
|
41152
|
-
// src/tools/hoisted.ts
|
|
41153
|
-
import * as path4 from "node:path";
|
|
41154
|
-
|
|
41155
|
-
// src/shared/subagent-detect.ts
|
|
41156
|
-
import { Effect as Effect2 } from "effect";
|
|
41157
|
-
var CACHE_MAX_ENTRIES2 = 200;
|
|
41158
|
-
var cache2 = new Map;
|
|
41159
|
-
async function resolveIsSubagent(client, sessionId, _fallbackDirectory) {
|
|
41160
|
-
if (!sessionId) {
|
|
41161
|
-
sessionLog(undefined, "[subagent-detect] no sessionId provided → primary");
|
|
41162
|
-
return false;
|
|
41163
|
-
}
|
|
41164
|
-
const cached = cache2.get(sessionId);
|
|
41165
|
-
if (cached) {
|
|
41166
|
-
cache2.delete(sessionId);
|
|
41167
|
-
cache2.set(sessionId, cached);
|
|
41168
|
-
return cached.isSubagent;
|
|
41169
|
-
}
|
|
41170
|
-
if (isV2PluginContext(client))
|
|
41171
|
-
return resolveV2(client, sessionId);
|
|
41172
|
-
const c = client;
|
|
41173
|
-
const sessionApi = c?.session;
|
|
41174
|
-
if (!sessionApi || typeof sessionApi.get !== "function") {
|
|
41175
|
-
sessionLog(sessionId, `[subagent-detect] client.session.get unavailable (client=${typeof client}, session=${typeof sessionApi}, get=${typeof sessionApi?.get}) → caching as primary`);
|
|
41176
|
-
setCache2(sessionId, false);
|
|
41177
|
-
return false;
|
|
41178
|
-
}
|
|
41179
|
-
sessionLog(sessionId, `[subagent-detect] cache miss, calling client.session.get(id=${sessionId})`);
|
|
41180
|
-
let isSubagent = false;
|
|
41181
|
-
let parentIdRaw;
|
|
41182
|
-
try {
|
|
41183
|
-
const result = await sessionApi.get({
|
|
41184
|
-
path: { id: sessionId }
|
|
41185
|
-
});
|
|
41186
|
-
const session = result?.data ?? result;
|
|
41187
|
-
parentIdRaw = session?.parentID;
|
|
41188
|
-
isSubagent = session !== undefined && typeof session.parentID === "string" && session.parentID.length > 0;
|
|
41189
|
-
sessionLog(sessionId, `[subagent-detect] SDK returned session=${session !== undefined ? "present" : "undefined"}, parentID=${JSON.stringify(parentIdRaw)} → isSubagent=${isSubagent}`);
|
|
41190
|
-
} catch (err) {
|
|
41191
|
-
sessionWarn(sessionId, `[subagent-detect] SDK lookup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
41192
|
-
return false;
|
|
41193
|
-
}
|
|
41194
|
-
setCache2(sessionId, isSubagent);
|
|
41195
|
-
return isSubagent;
|
|
41196
|
-
}
|
|
41197
|
-
function isV2PluginContext(client) {
|
|
41198
|
-
if (!client || typeof client !== "object")
|
|
41199
|
-
return false;
|
|
41200
|
-
const candidate = client;
|
|
41201
|
-
return typeof candidate.location?.directory === "string" && typeof candidate.session?.get === "function";
|
|
41202
|
-
}
|
|
41203
|
-
async function resolveV2(context, sessionId) {
|
|
41204
|
-
let session;
|
|
41205
|
-
try {
|
|
41206
|
-
session = await Effect2.runPromise(context.session.get({ sessionID: sessionId }));
|
|
41207
|
-
} catch (err) {
|
|
41208
|
-
sessionWarn(sessionId, `[subagent-detect] OpenCode 2 session lookup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
41209
|
-
return false;
|
|
41210
|
-
}
|
|
41211
|
-
const isSubagent = typeof session?.parentID === "string" && session.parentID.length > 0;
|
|
41212
|
-
sessionLog(sessionId, `[subagent-detect] OpenCode 2 session parentID=${JSON.stringify(session?.parentID)} → isSubagent=${isSubagent}`);
|
|
41213
|
-
setCache2(sessionId, isSubagent);
|
|
41214
|
-
return isSubagent;
|
|
41215
|
-
}
|
|
41216
|
-
function setCache2(sessionId, isSubagent) {
|
|
41217
|
-
if (cache2.has(sessionId))
|
|
41218
|
-
cache2.delete(sessionId);
|
|
41219
|
-
cache2.set(sessionId, { isSubagent, recordedAt: Date.now() });
|
|
41220
|
-
if (cache2.size > CACHE_MAX_ENTRIES2) {
|
|
41221
|
-
const oldest = cache2.keys().next().value;
|
|
41222
|
-
if (oldest !== undefined)
|
|
41223
|
-
cache2.delete(oldest);
|
|
41224
|
-
}
|
|
41225
|
-
}
|
|
41226
|
-
|
|
41227
41803
|
// src/tools/bash.ts
|
|
41228
|
-
var
|
|
41804
|
+
var z3 = tool.schema;
|
|
41229
41805
|
var METADATA_PREVIEW_LIMIT = 30 * 1024;
|
|
41230
41806
|
var DEFAULT_HARD_TIMEOUT_MS = 30 * 60 * 1000;
|
|
41231
41807
|
var BASH_TRANSPORT_MARGIN_MS = 1e4;
|
|
@@ -41241,7 +41817,9 @@ function resolveForegroundWaitMs(configured) {
|
|
|
41241
41817
|
}
|
|
41242
41818
|
return configured;
|
|
41243
41819
|
}
|
|
41244
|
-
function orchestratedTransportTimeoutMs(blockToCompletion, wait, effectiveTimeout, foregroundWaitMs) {
|
|
41820
|
+
function orchestratedTransportTimeoutMs(blockToCompletion, wait, effectiveTimeout, foregroundWaitMs, noHardKill = false) {
|
|
41821
|
+
if (noHardKill)
|
|
41822
|
+
return LONGEST_TIMER_DELAY_MS;
|
|
41245
41823
|
const waitBudget = blockToCompletion || wait ? effectiveTimeout ?? DEFAULT_HARD_TIMEOUT_MS : foregroundWaitMs;
|
|
41246
41824
|
return waitBudget + BASH_TRANSPORT_MARGIN_MS;
|
|
41247
41825
|
}
|
|
@@ -41318,13 +41896,38 @@ function listenForForegroundAbort(ctx, runtime, enabled) {
|
|
|
41318
41896
|
function userMessageDetachDescription(detachOnUserMessage) {
|
|
41319
41897
|
return detachOnUserMessage ? "Any new message detaches this wait. Set `bash.detach_on_user_message: false` to keep it blocking; even then, a message containing the literal `&detach` forces detachment, and the token is stripped before delivery and the rest of the message is preserved; a token-only message becomes `(requested background detach)`." : "Because `bash.detach_on_user_message` is false, a new message leaves this wait blocking; include the literal `&detach` anywhere to force detachment, and the token is stripped before delivery and the rest of the message is preserved; a token-only message becomes `(requested background detach)`.";
|
|
41320
41898
|
}
|
|
41321
|
-
function
|
|
41322
|
-
return
|
|
41899
|
+
function bashCompanionRegistered(config, companion) {
|
|
41900
|
+
return resolveBashConfig(config).background && toolEnabled(config, companion);
|
|
41323
41901
|
}
|
|
41324
|
-
function
|
|
41325
|
-
|
|
41902
|
+
function backgroundWaitDescription(watchToolRegistered, statusRegistered) {
|
|
41903
|
+
if (watchToolRegistered) {
|
|
41904
|
+
const noPolling = statusRegistered ? ", and never loop bash_status to wait" : "";
|
|
41905
|
+
return `then bash_watch handles only a short remaining wait (in a main session a watch defaults to 30s, max bash.watch_sync_max_ms, 120s by default; in a delegated session a watch without a timeout waits until the command finishes); for anything longer end the turn and let the completion reminder wake you, or use bash({wait:true}) when the result is needed before anything else — never background a command and immediately bash_watch it (that wastes a turn for what foreground returns in one)${noPolling}.`;
|
|
41906
|
+
}
|
|
41907
|
+
return statusRegistered ? "the task keeps running after the call returns, a completion reminder arrives when it exits, and bash_status reports its state and output. Use bash({wait:true}) instead when the result is needed before anything else." : "the task keeps running after the call returns and a completion reminder arrives when it exits. Use bash({wait:true}) instead when the result is needed before anything else.";
|
|
41908
|
+
}
|
|
41909
|
+
function ptyDriveClause(statusRegistered, writeRegistered) {
|
|
41910
|
+
if (statusRegistered && writeRegistered) {
|
|
41911
|
+
return ', and is driven with bash_status({ outputMode: "screen" }) plus bash_write';
|
|
41912
|
+
}
|
|
41913
|
+
if (statusRegistered)
|
|
41914
|
+
return ', and its screen is read with bash_status({ outputMode: "screen" })';
|
|
41915
|
+
if (writeRegistered)
|
|
41916
|
+
return ", and is driven with bash_write";
|
|
41917
|
+
return "";
|
|
41918
|
+
}
|
|
41919
|
+
function bashToolDescription(aftSearchRegistered, compressionOn, backgroundOn, detachOnUserMessage = true, zoomEnabled = true, watchToolRegistered = true, surface = {}) {
|
|
41920
|
+
const outline = surface.outline !== false;
|
|
41921
|
+
const status = surface.status !== false;
|
|
41922
|
+
const write = surface.write !== false;
|
|
41923
|
+
const steerTools = [
|
|
41924
|
+
aftSearchRegistered ? "aft_search (concepts, identifiers, regex, literals)" : "the grep tool",
|
|
41925
|
+
"read",
|
|
41926
|
+
...outline ? ["aft_outline"] : []
|
|
41927
|
+
].join(", ");
|
|
41928
|
+
const searchSteer = `use ${steerTools}${zoomEnabled ? ", or aft_zoom" : ""} instead`;
|
|
41326
41929
|
const compression = compressionOn ? " Output is compressed by default; pass compressed: false for raw output. Piped commands run verbatim and show the pipeline's output; for AFT's test/build summary, run the runner without | head, | tail, or | grep. Pipeline-failure notes cover single top-level pipelines only; multi-statement commands (`a; b | c; d`) are not instrumented, so masked failures inside them still need explicit exit-code checks." : "";
|
|
41327
|
-
const tasks = backgroundOn ? ` Commands run in the foreground and return inline; wait: true blocks until a long command finishes instead of auto-promoting; ${userMessageDetachDescription(detachOnUserMessage)} Use it when you need the result before doing anything else; keep it off otherwise so auto-promote can remind you while you work. Use background: true yourself ONLY when you have other useful work to do while it runs; ${backgroundWaitDescription(watchToolRegistered)} A \`nohup … &\` launch still holds the call if the child keeps stdout/stderr; redirect both or use background:true. pty: true runs interactive programs (REPLs, TUIs), implies background,
|
|
41930
|
+
const tasks = backgroundOn ? ` Commands run in the foreground and return inline; wait: true blocks until a long command finishes instead of auto-promoting; ${userMessageDetachDescription(detachOnUserMessage)} Use it when you need the result before doing anything else; keep it off otherwise so auto-promote can remind you while you work. Use background: true yourself ONLY when you have other useful work to do while it runs; ${backgroundWaitDescription(watchToolRegistered, status)} A \`nohup … &\` launch still holds the call if the child keeps stdout/stderr; redirect both or use background:true. pty: true runs interactive programs (REPLs, TUIs), implies background${ptyDriveClause(status, write)}.` : " Commands run in the foreground to completion; timeout is the hard kill cap (default 30 minutes).";
|
|
41328
41931
|
return `Execute shell commands.${compression}${tasks}
|
|
41329
41932
|
|
|
41330
41933
|
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}. When a list is cut, the reply ends with \`shown N of M <unit> (<reason>) · narrow: <knobs>\`; absence of that line means the list is complete.`;
|
|
@@ -41401,30 +42004,63 @@ async function withPermissionLoop(ctx, runtime, params, bridgeCall, options) {
|
|
|
41401
42004
|
}
|
|
41402
42005
|
throw new Error("bash permission retry failed: too many rounds");
|
|
41403
42006
|
}
|
|
42007
|
+
function ptyDriveParamSentence(statusRegistered, writeRegistered) {
|
|
42008
|
+
const writeInput = 'its input accepts either a string OR an array like [ "iHello", { key: "esc" }, ":wq", { key: "enter" } ] for atomic text+key sequences.';
|
|
42009
|
+
if (statusRegistered && writeRegistered) {
|
|
42010
|
+
return ` Inspect with bash_status({ taskId, outputMode: "screen" }) and drive interactively with bash_write — ${writeInput}`;
|
|
42011
|
+
}
|
|
42012
|
+
if (statusRegistered)
|
|
42013
|
+
return ' Inspect with bash_status({ taskId, outputMode: "screen" }).';
|
|
42014
|
+
if (writeRegistered)
|
|
42015
|
+
return ` Drive interactively with bash_write — ${writeInput}`;
|
|
42016
|
+
return "";
|
|
42017
|
+
}
|
|
42018
|
+
function nativeSandboxEnabled(config) {
|
|
42019
|
+
return config.sandbox?.enabled === true;
|
|
42020
|
+
}
|
|
41404
42021
|
function createBashTool(ctx, aftSearchRegisteredOverride) {
|
|
41405
42022
|
const initialBashCfg = resolveBashConfig(ctx.config);
|
|
42023
|
+
const statusRegistered = bashCompanionRegistered(ctx.config, "bash_status");
|
|
42024
|
+
const writeRegistered = bashCompanionRegistered(ctx.config, "bash_write");
|
|
42025
|
+
const taskControls = [
|
|
42026
|
+
...statusRegistered ? ["bash_status"] : [],
|
|
42027
|
+
...bashCompanionRegistered(ctx.config, "bash_kill") ? ["bash_kill"] : []
|
|
42028
|
+
];
|
|
42029
|
+
const waitArg = initialBashCfg.background ? {
|
|
42030
|
+
wait: z3.boolean().optional().describe(`When true, run in the foreground without auto-promoting and wait until the command finishes or reaches its timeout; ${userMessageDetachDescription(initialBashCfg.detach_on_user_message)} Use only when you know the result is required before doing anything else.`)
|
|
42031
|
+
} : {};
|
|
42032
|
+
const sandboxArg = nativeSandboxEnabled(ctx.config) ? {
|
|
42033
|
+
sandbox: z3.literal("host").optional().describe("Request one-command approval to run unsandboxed on the host; use only when native sandboxing blocks required work, and note that it is a no-op when sandboxing is disabled.")
|
|
42034
|
+
} : {};
|
|
42035
|
+
const compressedArg = initialBashCfg.compress ? {
|
|
42036
|
+
compressed: z3.boolean().optional().describe("When true or omitted, return compressed output with noisy terminal control sequences reduced. Set to false for raw output.")
|
|
42037
|
+
} : {};
|
|
41406
42038
|
const backgroundFlagArg = initialBashCfg.background ? {
|
|
41407
|
-
background:
|
|
42039
|
+
background: z3.boolean().optional().describe(`When true, spawn the command in the background and return a taskId${taskControls.length > 0 ? ` for ${taskControls.join("/")}` : ""} instead of waiting for completion. Defaults to false.`)
|
|
41408
42040
|
} : {};
|
|
41409
42041
|
const ptyArgs = initialBashCfg.background ? {
|
|
41410
|
-
pty:
|
|
42042
|
+
pty: z3.boolean().optional().describe(`When true, spawn the command in a real PTY for interactive programs (python/node/bash REPLs, vim). Implies background: true automatically.${initialBashCfg.subagent_background ? "" : " Unavailable in subagent sessions because bash.subagent_background is false."}${ptyDriveParamSentence(statusRegistered, writeRegistered)}`),
|
|
41411
42043
|
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."),
|
|
41412
42044
|
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.")
|
|
41413
42045
|
} : {};
|
|
41414
42046
|
const args = {
|
|
41415
|
-
command:
|
|
42047
|
+
command: z3.string().describe("Shell command to execute. Supports pipes, redirection, and normal shell syntax."),
|
|
41416
42048
|
timeout: optionalInt(1, Number.MAX_SAFE_INTEGER).describe(initialBashCfg.background ? "Hard kill cap in milliseconds (positive integer). In the default foreground mode when wait is false, a command that exceeds the configured wait window is promoted to background and gets a completion reminder when it exits; wait:true disables promotion and remains inline until completion or timeout." : "Hard kill cap in milliseconds (positive integer). When omitted, the foreground command can run up to 30 minutes and returns inline when it finishes."),
|
|
41417
|
-
workdir:
|
|
41418
|
-
description:
|
|
41419
|
-
|
|
41420
|
-
|
|
42049
|
+
workdir: z3.string().optional().describe("Working directory for command execution. Relative paths resolve through the bridge; defaults to the current tool context/project root when omitted."),
|
|
42050
|
+
description: z3.string().optional().describe("Short 5-10 word human-readable summary shown in OpenCode UI metadata instead of raw shell syntax."),
|
|
42051
|
+
...waitArg,
|
|
42052
|
+
...sandboxArg,
|
|
41421
42053
|
...backgroundFlagArg,
|
|
41422
|
-
|
|
42054
|
+
...compressedArg,
|
|
41423
42055
|
...ptyArgs
|
|
41424
42056
|
};
|
|
41425
42057
|
let hostFallbackActive = false;
|
|
41426
42058
|
return {
|
|
41427
|
-
description: bashToolDescription(false, initialBashCfg.compress, initialBashCfg.background, true, toolEnabled(ctx.config, "aft_zoom")),
|
|
42059
|
+
description: bashToolDescription(false, initialBashCfg.compress, initialBashCfg.background, true, toolEnabled(ctx.config, "aft_zoom"), bashCompanionRegistered(ctx.config, "bash_watch"), {
|
|
42060
|
+
outline: toolEnabled(ctx.config, "aft_outline"),
|
|
42061
|
+
status: statusRegistered,
|
|
42062
|
+
write: writeRegistered
|
|
42063
|
+
}),
|
|
41428
42064
|
args,
|
|
41429
42065
|
execute: async (args, context) => {
|
|
41430
42066
|
const bashCfg = resolveBashConfig(ctx.config);
|
|
@@ -41438,7 +42074,7 @@ function createBashTool(ctx, aftSearchRegisteredOverride) {
|
|
|
41438
42074
|
const cwd = args.workdir ?? context.directory;
|
|
41439
42075
|
const isSubagent = await resolveIsSubagent(ctx.client, context.sessionID, context.directory);
|
|
41440
42076
|
const backgroundDisabled = !bashCfg.background;
|
|
41441
|
-
const requestedWait = coerceBoolean(args.wait);
|
|
42077
|
+
const requestedWait = !backgroundDisabled && coerceBoolean(args.wait);
|
|
41442
42078
|
const rawRequestedPty = coerceBoolean(args.pty);
|
|
41443
42079
|
const rawRequestedBackground = coerceBoolean(args.background);
|
|
41444
42080
|
if (requestedWait && rawRequestedPty) {
|
|
@@ -41449,14 +42085,15 @@ function createBashTool(ctx, aftSearchRegisteredOverride) {
|
|
|
41449
42085
|
}
|
|
41450
42086
|
const requestedPty = !backgroundDisabled && rawRequestedPty;
|
|
41451
42087
|
const requestedBackground = !backgroundDisabled && (rawRequestedBackground || requestedPty);
|
|
41452
|
-
if (requestedPty && isSubagent) {
|
|
41453
|
-
throw new Error("PTY mode is not available in subagent sessions; subagents cannot drive interactive terminals.");
|
|
41454
|
-
}
|
|
41455
42088
|
const allowSubagentBg = bashCfg.subagent_background;
|
|
41456
42089
|
const subagentForcedForeground = isSubagent && !allowSubagentBg;
|
|
42090
|
+
if (requestedPty && subagentForcedForeground) {
|
|
42091
|
+
throw new Error("pty:true is unavailable in this subagent session because bash.subagent_background is false; run the command without pty.");
|
|
42092
|
+
}
|
|
41457
42093
|
const blockToCompletion = subagentForcedForeground || backgroundDisabled || requestedWait;
|
|
41458
42094
|
const effectiveBackground = blockToCompletion ? false : requestedBackground;
|
|
41459
42095
|
const rawTimeout = coerceOptionalInt(args.timeout, "timeout", 1, Number.MAX_SAFE_INTEGER);
|
|
42096
|
+
const workerUnboundedWait = isSubagent && requestedWait && rawTimeout === undefined;
|
|
41460
42097
|
const ptyRows = coerceOptionalInt(args.ptyRows, "ptyRows", 1, 60);
|
|
41461
42098
|
const ptyCols = coerceOptionalInt(args.ptyCols, "ptyCols", 1, 140);
|
|
41462
42099
|
const compressed = coerceBoolean(args.compressed, true);
|
|
@@ -41487,7 +42124,7 @@ function createBashTool(ctx, aftSearchRegisteredOverride) {
|
|
|
41487
42124
|
wait: requestedWait,
|
|
41488
42125
|
sandbox: args.sandbox
|
|
41489
42126
|
}, callBashBridge, {
|
|
41490
|
-
transportTimeoutMs: orchestratedTransportTimeoutMs(blockToCompletion, requestedWait, rawTimeout, foregroundWaitMs),
|
|
42127
|
+
transportTimeoutMs: orchestratedTransportTimeoutMs(blockToCompletion, requestedWait, rawTimeout, foregroundWaitMs, workerUnboundedWait),
|
|
41491
42128
|
onProgress: ({ text }) => {
|
|
41492
42129
|
accumulatedOutput = preview(accumulatedOutput + text);
|
|
41493
42130
|
metadata?.({ output: accumulatedOutput, description });
|
|
@@ -41497,10 +42134,10 @@ function createBashTool(ctx, aftSearchRegisteredOverride) {
|
|
|
41497
42134
|
const fallbackCause = classifyBashHostFallbackError(error);
|
|
41498
42135
|
if (!bashCfg.host_fallback || fallbackCause === undefined)
|
|
41499
42136
|
throw error;
|
|
41500
|
-
if (rawRequestedBackground) {
|
|
42137
|
+
if (!backgroundDisabled && rawRequestedBackground) {
|
|
41501
42138
|
throw new Error(`${BASH_HOST_FALLBACK_REFUSAL}; background:true is unsupported.`);
|
|
41502
42139
|
}
|
|
41503
|
-
if (
|
|
42140
|
+
if (requestedPty) {
|
|
41504
42141
|
throw new Error(`${BASH_HOST_FALLBACK_REFUSAL}; pty:true is unsupported.`);
|
|
41505
42142
|
}
|
|
41506
42143
|
const projectRoot = projectRootFor(context);
|
|
@@ -41534,7 +42171,7 @@ function createBashTool(ctx, aftSearchRegisteredOverride) {
|
|
|
41534
42171
|
trackBgTask(context.sessionID, taskId);
|
|
41535
42172
|
let rendered = data.output ?? "";
|
|
41536
42173
|
if (isSubagent && allowSubagentBg) {
|
|
41537
|
-
rendered +=
|
|
42174
|
+
rendered += workerBackgroundTaskNote(taskId);
|
|
41538
42175
|
}
|
|
41539
42176
|
const metadataPayload = { description, output: rendered, status: "running", taskId };
|
|
41540
42177
|
metadata?.(metadataPayload);
|
|
@@ -41553,17 +42190,19 @@ function createBashTool(ctx, aftSearchRegisteredOverride) {
|
|
|
41553
42190
|
};
|
|
41554
42191
|
}
|
|
41555
42192
|
function createBashStatusTool(ctx) {
|
|
42193
|
+
const waitSteer = bashCompanionRegistered(ctx.config, "bash_watch") ? " To wait, use bash_watch." : "";
|
|
41556
42194
|
return {
|
|
41557
|
-
description:
|
|
42195
|
+
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.${waitSteer}`,
|
|
41558
42196
|
args: {
|
|
41559
|
-
taskId:
|
|
41560
|
-
outputMode:
|
|
42197
|
+
taskId: z3.string().describe("Background task ID returned by bash({ background: true }), e.g. bash-6b454047a1c39ded."),
|
|
42198
|
+
outputMode: z3.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.")
|
|
41561
42199
|
},
|
|
41562
42200
|
execute: async (args, context) => {
|
|
41563
42201
|
const taskId = args.taskId;
|
|
41564
42202
|
const outputMode = args.outputMode;
|
|
41565
42203
|
const data = await bashStatusSnapshot(ctx, context, taskId, outputMode);
|
|
41566
|
-
|
|
42204
|
+
const isSubagent = await resolveIsSubagent(ctx.client, context.sessionID, context.directory);
|
|
42205
|
+
return await formatBashStatusText(context, taskId, data, outputMode, isSubagent ? "worker" : "primary");
|
|
41567
42206
|
}
|
|
41568
42207
|
};
|
|
41569
42208
|
}
|
|
@@ -41571,7 +42210,7 @@ function createBashKillTool(ctx) {
|
|
|
41571
42210
|
return {
|
|
41572
42211
|
description: "Terminate a running background bash task spawned with bash({ background: true }). Returns confirmation of kill or an error if the task already finished.",
|
|
41573
42212
|
args: {
|
|
41574
|
-
taskId:
|
|
42213
|
+
taskId: z3.string().describe("Background task ID returned by bash({ background: true }), e.g. bash-6b454047a1c39ded.")
|
|
41575
42214
|
},
|
|
41576
42215
|
execute: async (args, context) => {
|
|
41577
42216
|
const data = await callBashBridge(ctx, context, "bash_kill", {
|
|
@@ -41594,7 +42233,7 @@ async function bashStatusSnapshot(ctx, runtime, taskId, outputMode, options) {
|
|
|
41594
42233
|
}
|
|
41595
42234
|
return data;
|
|
41596
42235
|
}
|
|
41597
|
-
async function formatBashStatusText(runtime, taskId, data, requestedOutputMode) {
|
|
42236
|
+
async function formatBashStatusText(runtime, taskId, data, requestedOutputMode, role) {
|
|
41598
42237
|
const status = data.status;
|
|
41599
42238
|
const exit = typeof data.exit_code === "number" ? ` (exit ${data.exit_code})` : "";
|
|
41600
42239
|
const dur = typeof data.duration_ms === "number" ? ` ${Math.round(data.duration_ms / 1000)}s` : "";
|
|
@@ -41612,7 +42251,7 @@ ${preview}`;
|
|
|
41612
42251
|
}
|
|
41613
42252
|
if (status === "running") {
|
|
41614
42253
|
text += `
|
|
41615
|
-
|
|
42254
|
+
${runningTaskStatusHint(role)}`;
|
|
41616
42255
|
}
|
|
41617
42256
|
}
|
|
41618
42257
|
return text;
|
|
@@ -41641,11 +42280,6 @@ PTY task is still running. Use bash_status({ taskId: "${taskId}", outputMode: "s
|
|
|
41641
42280
|
function preview(output) {
|
|
41642
42281
|
return output.length <= METADATA_PREVIEW_LIMIT ? output : output.slice(-METADATA_PREVIEW_LIMIT);
|
|
41643
42282
|
}
|
|
41644
|
-
function subagentGuidance(taskId, watchSyncMaxMs) {
|
|
41645
|
-
return `
|
|
41646
|
-
|
|
41647
|
-
NOTE (subagent session): Continue with other work if you have it. If you don't, call bash_watch({ taskId: "${taskId}" }) to wait for completion before returning to the parent; without timeoutMs it waits up to ${watchSyncMaxMs} ms, the maximum. Subagents don't survive turn-end and won't receive the completion reminder.`;
|
|
41648
|
-
}
|
|
41649
42283
|
function foregroundMetadata(description, data, rendered) {
|
|
41650
42284
|
const outputPath = data.output_path;
|
|
41651
42285
|
const truncated = typeof data.truncated === "boolean" ? data.truncated : data.output_truncated;
|
|
@@ -41666,24 +42300,355 @@ function shortenCommand(command) {
|
|
|
41666
42300
|
return collapsed.length <= 80 ? collapsed : `${collapsed.slice(0, 77)}...`;
|
|
41667
42301
|
}
|
|
41668
42302
|
|
|
42303
|
+
// src/tools/conflicts.ts
|
|
42304
|
+
var z4 = tool.schema;
|
|
42305
|
+
function conflictTools(ctx) {
|
|
42306
|
+
return {
|
|
42307
|
+
aft_conflicts: {
|
|
42308
|
+
description: "Show all git merge conflicts across the repository — returns line-numbered conflict regions with context for every conflicted file in a single call. Conflicts are discovered from the git repository's top level. By default it inspects the session's project repository; pass `path` to inspect a different repository or git worktree (e.g. where a rebase/merge is running).",
|
|
42309
|
+
args: {
|
|
42310
|
+
path: z4.string().describe("Optional path inside the git repository or worktree to inspect (absolute or relative to project root). Conflicts are discovered from that repository's top level. Defaults to the session project root.").optional()
|
|
42311
|
+
},
|
|
42312
|
+
execute: async (args, context) => {
|
|
42313
|
+
const rawArgs = {};
|
|
42314
|
+
if (!isEmptyParam(args?.path)) {
|
|
42315
|
+
const expanded = expandTilde2(String(args.path));
|
|
42316
|
+
const projectRoot = await resolveProjectRoot(ctx, context);
|
|
42317
|
+
const resolved = resolvePathFromProjectRoot(projectRoot, expanded);
|
|
42318
|
+
const denied = await assertExternalDirectoryPermission(ctx, context, resolved, {
|
|
42319
|
+
kind: "directory"
|
|
42320
|
+
});
|
|
42321
|
+
if (denied)
|
|
42322
|
+
return permissionDeniedResponse(denied);
|
|
42323
|
+
rawArgs.path = resolved;
|
|
42324
|
+
}
|
|
42325
|
+
const response = await callToolCall(ctx, context, "conflicts", rawArgs);
|
|
42326
|
+
if (response.success === false) {
|
|
42327
|
+
throw new Error(response.message || "git_conflicts failed");
|
|
42328
|
+
}
|
|
42329
|
+
return response.text;
|
|
42330
|
+
}
|
|
42331
|
+
}
|
|
42332
|
+
};
|
|
42333
|
+
}
|
|
42334
|
+
|
|
42335
|
+
// src/tools/definitions/v2.ts
|
|
42336
|
+
import { Effect as Effect3 } from "effect";
|
|
42337
|
+
var V2_TOOL_NAME = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
|
|
42338
|
+
var V2_PATH_HEADER_TOOLS = new Set(["read", "write", "edit"]);
|
|
42339
|
+
var V2_BASH_TOOLS = new Set(["bash"]);
|
|
42340
|
+
var V2_SHELL_PERMISSION = "shell";
|
|
42341
|
+
var ROOT_COMBINATORS = ["anyOf", "oneOf", "allOf"];
|
|
42342
|
+
function failure(error) {
|
|
42343
|
+
if (error instanceof Error)
|
|
42344
|
+
return error;
|
|
42345
|
+
const detail = (() => {
|
|
42346
|
+
try {
|
|
42347
|
+
return JSON.stringify(error) ?? String(error);
|
|
42348
|
+
} catch {
|
|
42349
|
+
return String(error);
|
|
42350
|
+
}
|
|
42351
|
+
})();
|
|
42352
|
+
return new Error(`V2 tool execution rejected with a non-Error value: ${detail}`);
|
|
42353
|
+
}
|
|
42354
|
+
function isPlainObject3(value) {
|
|
42355
|
+
if (typeof value !== "object" || value === null)
|
|
42356
|
+
return false;
|
|
42357
|
+
const prototype = Object.getPrototypeOf(value);
|
|
42358
|
+
return prototype === Object.prototype || prototype === null;
|
|
42359
|
+
}
|
|
42360
|
+
function jsonSafeMetadata(value) {
|
|
42361
|
+
if (Array.isArray(value)) {
|
|
42362
|
+
return value.map((entry) => entry === undefined ? null : jsonSafeMetadata(entry));
|
|
42363
|
+
}
|
|
42364
|
+
if (!isPlainObject3(value))
|
|
42365
|
+
return value;
|
|
42366
|
+
const cleaned = {};
|
|
42367
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
42368
|
+
if (entry === undefined)
|
|
42369
|
+
continue;
|
|
42370
|
+
cleaned[key] = jsonSafeMetadata(entry);
|
|
42371
|
+
}
|
|
42372
|
+
return cleaned;
|
|
42373
|
+
}
|
|
42374
|
+
function resultContent(result) {
|
|
42375
|
+
if (typeof result === "string")
|
|
42376
|
+
return { content: result };
|
|
42377
|
+
const attachments = (result.attachments ?? []).map((attachment) => ({
|
|
42378
|
+
type: "file",
|
|
42379
|
+
uri: attachment.url,
|
|
42380
|
+
mime: attachment.mime,
|
|
42381
|
+
...attachment.filename ? { name: attachment.filename } : {}
|
|
42382
|
+
}));
|
|
42383
|
+
const content = attachments.length ? [{ type: "text", text: result.output }, ...attachments] : result.output;
|
|
42384
|
+
const metadata = jsonSafeMetadata({
|
|
42385
|
+
...result.metadata ?? {},
|
|
42386
|
+
...result.title ? { title: result.title } : {}
|
|
42387
|
+
});
|
|
42388
|
+
return {
|
|
42389
|
+
content,
|
|
42390
|
+
...Object.keys(metadata).length ? { metadata } : {}
|
|
42391
|
+
};
|
|
42392
|
+
}
|
|
42393
|
+
function failureEnvelopeMessage(result) {
|
|
42394
|
+
const output = typeof result === "string" ? result : result.output;
|
|
42395
|
+
if (typeof output !== "string")
|
|
42396
|
+
return;
|
|
42397
|
+
const trimmed = output.trim();
|
|
42398
|
+
if (!trimmed.startsWith("{") || !trimmed.includes('"success"'))
|
|
42399
|
+
return;
|
|
42400
|
+
let parsed;
|
|
42401
|
+
try {
|
|
42402
|
+
parsed = JSON.parse(trimmed);
|
|
42403
|
+
} catch {
|
|
42404
|
+
return;
|
|
42405
|
+
}
|
|
42406
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
42407
|
+
return;
|
|
42408
|
+
const envelope = parsed;
|
|
42409
|
+
if (envelope.success !== false)
|
|
42410
|
+
return;
|
|
42411
|
+
for (const field of [envelope.message, envelope.error, envelope.code]) {
|
|
42412
|
+
if (typeof field === "string" && field.length > 0)
|
|
42413
|
+
return field;
|
|
42414
|
+
}
|
|
42415
|
+
return "The tool call failed.";
|
|
42416
|
+
}
|
|
42417
|
+
function bareToolName3(name) {
|
|
42418
|
+
return name.startsWith("aft_") ? name.slice(4) : name;
|
|
42419
|
+
}
|
|
42420
|
+
function projectArguments(name, definition) {
|
|
42421
|
+
if (!V2_PATH_HEADER_TOOLS.has(bareToolName3(name)) || !("filePath" in definition.args)) {
|
|
42422
|
+
return { ...definition.args };
|
|
42423
|
+
}
|
|
42424
|
+
const { filePath, ...rest } = definition.args;
|
|
42425
|
+
return { path: filePath, ...rest };
|
|
42426
|
+
}
|
|
42427
|
+
function executionArguments(name, input) {
|
|
42428
|
+
if (!V2_PATH_HEADER_TOOLS.has(bareToolName3(name)) || !("path" in input))
|
|
42429
|
+
return input;
|
|
42430
|
+
const { path, ...rest } = input;
|
|
42431
|
+
return { filePath: path, ...rest };
|
|
42432
|
+
}
|
|
42433
|
+
function hostPermission(name) {
|
|
42434
|
+
const bare = bareToolName3(name);
|
|
42435
|
+
if (new Set(["read", "glob", "grep"]).has(bare))
|
|
42436
|
+
return bare;
|
|
42437
|
+
if (V2_BASH_TOOLS.has(name))
|
|
42438
|
+
return V2_SHELL_PERMISSION;
|
|
42439
|
+
if (new Set([
|
|
42440
|
+
"write",
|
|
42441
|
+
"edit",
|
|
42442
|
+
"apply_patch",
|
|
42443
|
+
"delete",
|
|
42444
|
+
"move",
|
|
42445
|
+
"ast_grep_replace",
|
|
42446
|
+
"import",
|
|
42447
|
+
"safety"
|
|
42448
|
+
]).has(bare)) {
|
|
42449
|
+
return "edit";
|
|
42450
|
+
}
|
|
42451
|
+
return;
|
|
42452
|
+
}
|
|
42453
|
+
function assertV2Contract(name, input) {
|
|
42454
|
+
if (!V2_TOOL_NAME.test(name)) {
|
|
42455
|
+
throw new Error(`Invalid V2 tool name ${JSON.stringify(name)}; expected ${V2_TOOL_NAME.source}`);
|
|
42456
|
+
}
|
|
42457
|
+
const schema = tool.schema.toJSONSchema(input, { io: "input" });
|
|
42458
|
+
if (schema.type !== "object") {
|
|
42459
|
+
throw new Error(`V2 tool ${name} must have an object-rooted input schema`);
|
|
42460
|
+
}
|
|
42461
|
+
for (const keyword of ROOT_COMBINATORS) {
|
|
42462
|
+
if (keyword in schema) {
|
|
42463
|
+
throw new Error(`V2 tool ${name} input schema cannot use root ${keyword}`);
|
|
42464
|
+
}
|
|
42465
|
+
}
|
|
42466
|
+
}
|
|
42467
|
+
function v2PermissionRequest(name, request) {
|
|
42468
|
+
if (!V2_BASH_TOOLS.has(name) || request.permission !== "bash")
|
|
42469
|
+
return request;
|
|
42470
|
+
return { ...request, permission: V2_SHELL_PERMISSION };
|
|
42471
|
+
}
|
|
42472
|
+
function runtimeFor(name, location, context, signal, consumers) {
|
|
42473
|
+
const directory = location.directory;
|
|
42474
|
+
const worktree = location.project?.canonical ?? location.project?.directory ?? directory;
|
|
42475
|
+
return {
|
|
42476
|
+
sessionID: context.sessionID,
|
|
42477
|
+
messageID: context.messageID,
|
|
42478
|
+
agent: context.agent,
|
|
42479
|
+
directory,
|
|
42480
|
+
directoryIsSessionRoot: true,
|
|
42481
|
+
worktree,
|
|
42482
|
+
abort: signal,
|
|
42483
|
+
effectAbort: signal,
|
|
42484
|
+
metadata: (update) => {
|
|
42485
|
+
Effect3.runPromise(context.progress(jsonSafeMetadata({
|
|
42486
|
+
...update.metadata ?? {},
|
|
42487
|
+
...update.title ? { title: update.title } : {}
|
|
42488
|
+
})));
|
|
42489
|
+
},
|
|
42490
|
+
ask: (asked) => {
|
|
42491
|
+
const request = v2PermissionRequest(name, asked);
|
|
42492
|
+
if (consumers.requestPermission)
|
|
42493
|
+
return consumers.requestPermission(request, context);
|
|
42494
|
+
return Promise.reject(new Error(`The "${request.permission}" operation was refused because this AFT runtime has no permission evaluator bound, so the host's permission rules for it could not be consulted.`));
|
|
42495
|
+
},
|
|
42496
|
+
progress: context.progress
|
|
42497
|
+
};
|
|
42498
|
+
}
|
|
42499
|
+
function projectV2Tool(name, definition, location, consumers = {}) {
|
|
42500
|
+
const input = tool.schema.object(projectArguments(name, definition));
|
|
42501
|
+
assertV2Contract(name, input);
|
|
42502
|
+
const sharedOptions = definition.options;
|
|
42503
|
+
const permission = hostPermission(name);
|
|
42504
|
+
return {
|
|
42505
|
+
name,
|
|
42506
|
+
description: definition.description,
|
|
42507
|
+
input,
|
|
42508
|
+
options: {
|
|
42509
|
+
...sharedOptions,
|
|
42510
|
+
...permission ? { permission } : {},
|
|
42511
|
+
codemode: false
|
|
42512
|
+
},
|
|
42513
|
+
execute: (rawInput, context) => Effect3.tryPromise({
|
|
42514
|
+
try: async (signal) => {
|
|
42515
|
+
const runtime = runtimeFor(name, location, context, signal, consumers);
|
|
42516
|
+
const input = executionArguments(name, rawInput);
|
|
42517
|
+
const result = consumers.executeBash && V2_BASH_TOOLS.has(name) ? await consumers.executeBash({
|
|
42518
|
+
name,
|
|
42519
|
+
input,
|
|
42520
|
+
context: runtime,
|
|
42521
|
+
definition
|
|
42522
|
+
}) : await definition.execute(input, runtime);
|
|
42523
|
+
const refusal = failureEnvelopeMessage(result);
|
|
42524
|
+
if (refusal)
|
|
42525
|
+
throw new Error(refusal);
|
|
42526
|
+
return resultContent(result);
|
|
42527
|
+
},
|
|
42528
|
+
catch: failure
|
|
42529
|
+
})
|
|
42530
|
+
};
|
|
42531
|
+
}
|
|
42532
|
+
|
|
42533
|
+
// src/tools/hoisted.ts
|
|
42534
|
+
import { stat as stat2 } from "node:fs/promises";
|
|
42535
|
+
import * as path4 from "node:path";
|
|
42536
|
+
|
|
42537
|
+
// src/hashline-tag-sources.ts
|
|
42538
|
+
function hashlineTagSourceSentence(bashRewritesMintTags, isRegistered = () => true) {
|
|
42539
|
+
const rewrites = bashRewritesMintTags ? " (and accepted AFT `cat`/`head`/`tail` rewrites)" : "";
|
|
42540
|
+
const lookAlikes = [
|
|
42541
|
+
...["aft_zoom", "aft_outline"].filter(isRegistered),
|
|
42542
|
+
"grep",
|
|
42543
|
+
...isRegistered("aft_search") ? ["aft_search"] : []
|
|
42544
|
+
].map((name) => `\`${name}\``);
|
|
42545
|
+
lookAlikes.push("conflict snippets");
|
|
42546
|
+
return `Only \`read\`${rewrites} mint hashline tags. ${joinList(lookAlikes)} do not. After navigation, call \`read\` on every file and range the patch addresses.`;
|
|
42547
|
+
}
|
|
42548
|
+
function joinList(items) {
|
|
42549
|
+
if (items.length <= 2)
|
|
42550
|
+
return items.join(" and ");
|
|
42551
|
+
return `${items.slice(0, -1).join(", ")}, and ${items[items.length - 1]}`;
|
|
42552
|
+
}
|
|
42553
|
+
|
|
42554
|
+
// src/shared/read-vision.ts
|
|
42555
|
+
var VISION_HOST_TIMEOUT_MS = 1500;
|
|
42556
|
+
var states = new WeakMap;
|
|
42557
|
+
function stateFor2(client) {
|
|
42558
|
+
let state = states.get(client);
|
|
42559
|
+
if (!state) {
|
|
42560
|
+
state = { models: new Map, history: new Map, capabilities: new Map };
|
|
42561
|
+
states.set(client, state);
|
|
42562
|
+
}
|
|
42563
|
+
return state;
|
|
42564
|
+
}
|
|
42565
|
+
function rememberReadModel(client, sessionID, model) {
|
|
42566
|
+
stateFor2(client).models.set(sessionID, model);
|
|
42567
|
+
}
|
|
42568
|
+
async function bounded(call) {
|
|
42569
|
+
let timer;
|
|
42570
|
+
try {
|
|
42571
|
+
return await Promise.race([
|
|
42572
|
+
Promise.resolve().then(call),
|
|
42573
|
+
new Promise((resolve) => {
|
|
42574
|
+
timer = setTimeout(() => resolve(undefined), VISION_HOST_TIMEOUT_MS);
|
|
42575
|
+
})
|
|
42576
|
+
]);
|
|
42577
|
+
} catch {
|
|
42578
|
+
return;
|
|
42579
|
+
} finally {
|
|
42580
|
+
clearTimeout(timer);
|
|
42581
|
+
}
|
|
42582
|
+
}
|
|
42583
|
+
function record3(value) {
|
|
42584
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
42585
|
+
}
|
|
42586
|
+
async function currentSessionVisionCapability(client, sessionID) {
|
|
42587
|
+
if (!sessionID)
|
|
42588
|
+
return;
|
|
42589
|
+
const state = stateFor2(client);
|
|
42590
|
+
let model = state.models.get(sessionID);
|
|
42591
|
+
if (!model) {
|
|
42592
|
+
let pending = state.history.get(sessionID);
|
|
42593
|
+
if (!pending) {
|
|
42594
|
+
pending = bounded(() => resolvePromptContext(client, sessionID)).then((context) => context?.model);
|
|
42595
|
+
state.history.set(sessionID, pending);
|
|
42596
|
+
pending.finally(() => state.history.delete(sessionID));
|
|
42597
|
+
}
|
|
42598
|
+
const historical = await pending;
|
|
42599
|
+
model = state.models.get(sessionID) ?? historical;
|
|
42600
|
+
}
|
|
42601
|
+
if (!model)
|
|
42602
|
+
return;
|
|
42603
|
+
const currentModel = model;
|
|
42604
|
+
const key = JSON.stringify([model.providerID, model.modelID]);
|
|
42605
|
+
let capability = state.capabilities.get(key);
|
|
42606
|
+
if (!capability) {
|
|
42607
|
+
capability = (async () => {
|
|
42608
|
+
const api = client.provider;
|
|
42609
|
+
if (!api?.list)
|
|
42610
|
+
return;
|
|
42611
|
+
const list = api.list.bind(api);
|
|
42612
|
+
const listed = record3(await bounded(list));
|
|
42613
|
+
const catalog = record3(listed?.data) ?? listed;
|
|
42614
|
+
const providers = catalog?.all ?? catalog?.providers;
|
|
42615
|
+
if (!Array.isArray(providers))
|
|
42616
|
+
return;
|
|
42617
|
+
const provider = providers.map(record3).find((entry) => entry?.id === currentModel.providerID);
|
|
42618
|
+
const models = provider?.models;
|
|
42619
|
+
const entry = record3(Array.isArray(models) ? models.find((entry) => record3(entry)?.id === currentModel.modelID) : record3(models)?.[currentModel.modelID]);
|
|
42620
|
+
const inputs = record3(entry?.modalities)?.input;
|
|
42621
|
+
return Array.isArray(inputs) ? inputs.includes("image") : typeof entry?.attachment === "boolean" ? entry.attachment : undefined;
|
|
42622
|
+
})();
|
|
42623
|
+
state.capabilities.set(key, capability);
|
|
42624
|
+
capability.then((value) => {
|
|
42625
|
+
if (value === undefined)
|
|
42626
|
+
state.capabilities.delete(key);
|
|
42627
|
+
});
|
|
42628
|
+
}
|
|
42629
|
+
return capability;
|
|
42630
|
+
}
|
|
42631
|
+
|
|
41669
42632
|
// src/tools/bash_watch.ts
|
|
41670
42633
|
var z5 = tool.schema;
|
|
41671
|
-
var BASH_WAIT_POLL_INTERVAL_MS = 100;
|
|
41672
42634
|
var REGEX_WAIT_SCAN_WINDOW_BYTES = 64 * 1024;
|
|
41673
42635
|
function unavailableSnapshot() {
|
|
41674
42636
|
return { status: "unknown" };
|
|
41675
42637
|
}
|
|
41676
|
-
function coerceConfiguredWatchTimeout(value, cap) {
|
|
42638
|
+
function coerceConfiguredWatchTimeout(value, role, cap) {
|
|
41677
42639
|
try {
|
|
41678
|
-
return coerceOptionalInt(value, "timeoutMs", 1, cap);
|
|
42640
|
+
return coerceOptionalInt(value, "timeoutMs", 1, maxWatchTimeoutMs(role, cap));
|
|
41679
42641
|
} catch (error) {
|
|
41680
42642
|
const message = error instanceof Error ? error.message : String(error);
|
|
42643
|
+
if (role === "worker")
|
|
42644
|
+
throw error;
|
|
41681
42645
|
throw new Error(`${message} (bash.watch_sync_max_ms)`);
|
|
41682
42646
|
}
|
|
41683
42647
|
}
|
|
41684
42648
|
function createBashWatchTool(ctx) {
|
|
42649
|
+
const noPolling = bashCompanionRegistered(ctx.config, "bash_status") ? " Never loop bash_status to wait." : "";
|
|
41685
42650
|
return {
|
|
41686
|
-
description:
|
|
42651
|
+
description: `Watch a background bash task. ${WATCH_SYNC_DEFAULTS_DESCRIPTION}. In a main session sync waits are for a short remaining wait on a task; for anything longer end the turn on \`bash({background:true})\` and let the completion reminder wake you, or use \`bash({wait:true})\` when the result is needed before anything else. 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.${noPolling}`,
|
|
41687
42652
|
args: {
|
|
41688
42653
|
taskId: z5.string().describe("Background task ID returned by bash({ background: true })."),
|
|
41689
42654
|
pattern: z5.union([z5.string(), z5.object({ regex: z5.string() })]).optional().describe("Substring or regex pattern. Optional in sync mode; required with background:true. Sync substring watches keep only the overlap tail needed for boundary matches; sync regex watches use a 64 KB rolling output window."),
|
|
@@ -41734,11 +42699,11 @@ A notification will fire when the pattern matches or the task exits.`;
|
|
|
41734
42699
|
}
|
|
41735
42700
|
const syncWaitCap = bashCfg.watch_sync_max_ms;
|
|
41736
42701
|
const role = isSubagent ? "worker" : "primary";
|
|
41737
|
-
const effectiveWaitMs = subagentForcedSync ?
|
|
42702
|
+
const effectiveWaitMs = subagentForcedSync ? undefined : resolveWatchTimeoutMs(coerceConfiguredWatchTimeout(args.timeoutMs, role, syncWaitCap), role, syncWaitCap);
|
|
41738
42703
|
const data = await waitForBashStatus(ctx, context, taskId, undefined, waitFor, effectiveWaitMs);
|
|
41739
42704
|
const waited = data.waited;
|
|
41740
42705
|
if (waited?.reason === "user_message") {
|
|
41741
|
-
const convertedText = await convertToAsyncWatchOnAbort(ctx, context, taskId, waitFor, coerceBoolean(args.once, true));
|
|
42706
|
+
const convertedText = await convertToAsyncWatchOnAbort(ctx, context, taskId, waitFor, coerceBoolean(args.once, true), waited.elapsed_ms, role);
|
|
41742
42707
|
const metadata = context.metadata;
|
|
41743
42708
|
metadata?.({ taskId, status: data.status, waited, convertedToAsync: true });
|
|
41744
42709
|
return convertedText;
|
|
@@ -41750,9 +42715,11 @@ A notification will fire when the pattern matches or the task exits.`;
|
|
|
41750
42715
|
}
|
|
41751
42716
|
};
|
|
41752
42717
|
}
|
|
41753
|
-
async function convertToAsyncWatchOnAbort(ctx, context, taskId, waitFor, once) {
|
|
42718
|
+
async function convertToAsyncWatchOnAbort(ctx, context, taskId, waitFor, once, elapsedMs, role) {
|
|
42719
|
+
const interrupted = `Sync watch for task ${taskId} was interrupted because you sent a message after ${elapsedMs}ms of waiting. `;
|
|
42720
|
+
const reminderFallback = interruptedWatchTail(role, `The task is still running in the background. A completion reminder will be ` + `delivered automatically when the task exits.`);
|
|
41754
42721
|
if (!waitFor) {
|
|
41755
|
-
return
|
|
42722
|
+
return interrupted + interruptedWatchTail(role, `The task is still running in the background. A completion reminder will be ` + `delivered automatically when the task exits; don't poll bash_status.`);
|
|
41756
42723
|
}
|
|
41757
42724
|
const notifyParams = {
|
|
41758
42725
|
task_id: taskId,
|
|
@@ -41767,12 +42734,12 @@ async function convertToAsyncWatchOnAbort(ctx, context, taskId, waitFor, once) {
|
|
|
41767
42734
|
const registered = await callBashBridge(ctx, context, "bash_notify", notifyParams);
|
|
41768
42735
|
if (registered.success === false) {
|
|
41769
42736
|
unmarkExplicitControl(context.sessionID, taskId);
|
|
41770
|
-
return
|
|
42737
|
+
return interrupted + `Auto-registering an async watch failed (${String(registered.message ?? "unknown error")}). ` + reminderFallback;
|
|
41771
42738
|
}
|
|
41772
|
-
return
|
|
42739
|
+
return interrupted + `The wait has been converted to an async watch (${registered.watch_id}). ` + interruptedWatchTail(role, `A notification will fire when the pattern matches or the task exits.`);
|
|
41773
42740
|
} catch (err) {
|
|
41774
42741
|
unmarkExplicitControl(context.sessionID, taskId);
|
|
41775
|
-
return
|
|
42742
|
+
return interrupted + `Auto-registering an async watch failed (${err instanceof Error ? err.message : String(err)}). ` + reminderFallback;
|
|
41776
42743
|
}
|
|
41777
42744
|
}
|
|
41778
42745
|
function formatWatchResultText(taskId, data, waited, role, syncWaitCap) {
|
|
@@ -41781,21 +42748,25 @@ function formatWatchResultText(taskId, data, waited, role, syncWaitCap) {
|
|
|
41781
42748
|
const dur = typeof data.duration_ms === "number" ? ` ${Math.round(data.duration_ms / 1000)}s` : "";
|
|
41782
42749
|
let text = `Task ${taskId}: ${status}${exit}${dur}`;
|
|
41783
42750
|
if (waited) {
|
|
42751
|
+
const waitedText = formatWatchWaited(waited.elapsed_ms, waited.limit_ms, role === "primary" ? syncWaitCap : undefined);
|
|
41784
42752
|
if (waited.reason === "matched") {
|
|
41785
42753
|
const stream = waited.match_stream ? ` in ${waited.match_stream}` : "";
|
|
41786
42754
|
text += `
|
|
41787
|
-
|
|
42755
|
+
${waitedText}; matched ${JSON.stringify(waited.match ?? "")}${stream} at offset ${waited.match_offset ?? 0}.`;
|
|
41788
42756
|
} else if (waited.reason === "timeout") {
|
|
41789
42757
|
text += `
|
|
41790
|
-
|
|
42758
|
+
${waitedText}; timeout reached without match. ${watchTimeoutSteer(role)}`;
|
|
41791
42759
|
} else if (waited.reason === "unavailable") {
|
|
41792
42760
|
text += `
|
|
41793
|
-
|
|
42761
|
+
${waitedText}; ${watchUnavailableSteer(role)}`;
|
|
42762
|
+
} else if (waited.reason === "aborted") {
|
|
42763
|
+
text += `
|
|
42764
|
+
${waitedText}; the watch was cancelled. The task keeps running.`;
|
|
41794
42765
|
} else {
|
|
41795
42766
|
const stat = String(data.status ?? "unknown");
|
|
41796
42767
|
const e = typeof data.exit_code === "number" ? `, exit ${data.exit_code}` : "";
|
|
41797
42768
|
text += `
|
|
41798
|
-
|
|
42769
|
+
${waitedText}; task exited (${stat}${e}).`;
|
|
41799
42770
|
}
|
|
41800
42771
|
}
|
|
41801
42772
|
const preview = data.output_preview;
|
|
@@ -41817,8 +42788,17 @@ async function bashStatusSnapshot2(ctx, runtime, taskId, outputMode, cursor, opt
|
|
|
41817
42788
|
return data;
|
|
41818
42789
|
}
|
|
41819
42790
|
async function waitForBashStatus(ctx, runtime, taskId, outputMode, waitFor, effectiveWaitMs) {
|
|
41820
|
-
const startedAt =
|
|
41821
|
-
const deadline = startedAt + effectiveWaitMs;
|
|
42791
|
+
const startedAt = watchClock.now();
|
|
42792
|
+
const deadline = effectiveWaitMs === undefined ? Number.POSITIVE_INFINITY : startedAt + effectiveWaitMs;
|
|
42793
|
+
const elapsedMs = () => Math.round(watchClock.now() - startedAt);
|
|
42794
|
+
const abortSignal = runtime.abort;
|
|
42795
|
+
const pause = () => watchClock.sleep(Math.min(watchPollDelayMs(elapsedMs()), Math.max(0, deadline - watchClock.now())), abortSignal);
|
|
42796
|
+
const waited = (reason, extra = {}) => ({
|
|
42797
|
+
reason,
|
|
42798
|
+
elapsed_ms: elapsedMs(),
|
|
42799
|
+
...effectiveWaitMs === undefined ? {} : { limit_ms: effectiveWaitMs },
|
|
42800
|
+
...extra
|
|
42801
|
+
});
|
|
41822
42802
|
let spillCursor = { output: 0, stderr: 0 };
|
|
41823
42803
|
const scanState = {
|
|
41824
42804
|
output: { text: "", baseOffset: 0 },
|
|
@@ -41832,29 +42812,29 @@ async function waitForBashStatus(ctx, runtime, taskId, outputMode, waitFor, effe
|
|
|
41832
42812
|
markTaskWaiting(runtime.sessionID, taskId);
|
|
41833
42813
|
let sawTerminal = false;
|
|
41834
42814
|
let lastData;
|
|
42815
|
+
let busySince;
|
|
41835
42816
|
try {
|
|
41836
42817
|
while (true) {
|
|
42818
|
+
if (abortSignal?.aborted) {
|
|
42819
|
+
return withWaited(lastData ?? unavailableSnapshot(), waited("aborted"));
|
|
42820
|
+
}
|
|
41837
42821
|
let data;
|
|
41838
42822
|
try {
|
|
41839
42823
|
data = await bashStatusSnapshot2(ctx, runtime, taskId, outputMode, waitFor ? spillCursor : undefined, bridgeOptions);
|
|
41840
42824
|
} catch (err) {
|
|
41841
42825
|
if (!isBridgeTransportTimeout(err))
|
|
41842
42826
|
throw err;
|
|
42827
|
+
busySince ??= watchClock.now();
|
|
41843
42828
|
if (isSyncWatchAborted(runtime.sessionID)) {
|
|
41844
|
-
return withWaited(lastData ?? unavailableSnapshot(),
|
|
41845
|
-
reason: "user_message",
|
|
41846
|
-
elapsed_ms: Date.now() - startedAt
|
|
41847
|
-
});
|
|
42829
|
+
return withWaited(lastData ?? unavailableSnapshot(), waited("user_message"));
|
|
41848
42830
|
}
|
|
41849
|
-
if (
|
|
41850
|
-
return withWaited(lastData ?? unavailableSnapshot(),
|
|
41851
|
-
reason: "unavailable",
|
|
41852
|
-
elapsed_ms: Date.now() - startedAt
|
|
41853
|
-
});
|
|
42831
|
+
if (watchClock.now() >= deadline || watchClock.now() - busySince >= WATCH_UNAVAILABLE_GIVE_UP_MS) {
|
|
42832
|
+
return withWaited(lastData ?? unavailableSnapshot(), waited("unavailable"));
|
|
41854
42833
|
}
|
|
41855
|
-
await
|
|
42834
|
+
await pause();
|
|
41856
42835
|
continue;
|
|
41857
42836
|
}
|
|
42837
|
+
busySince = undefined;
|
|
41858
42838
|
lastData = data;
|
|
41859
42839
|
const terminal = isTerminalStatus(data.status);
|
|
41860
42840
|
if (waitFor) {
|
|
@@ -41879,13 +42859,11 @@ async function waitForBashStatus(ctx, runtime, taskId, outputMode, waitFor, effe
|
|
|
41879
42859
|
await markBgCompletionDelivered({ ctx, directory: projectRootFor(runtime), sessionID: runtime.sessionID }, taskId);
|
|
41880
42860
|
}
|
|
41881
42861
|
const matchStream = data.mode === "pty" ? undefined : chunk.stream === "output" ? "stdout" : "stderr";
|
|
41882
|
-
return withWaited(data, {
|
|
41883
|
-
reason: "matched",
|
|
41884
|
-
elapsed_ms: Date.now() - startedAt,
|
|
42862
|
+
return withWaited(data, waited("matched", {
|
|
41885
42863
|
match: match.text,
|
|
41886
42864
|
match_offset: state.baseOffset + match.byteOffset,
|
|
41887
42865
|
match_stream: matchStream
|
|
41888
|
-
});
|
|
42866
|
+
}));
|
|
41889
42867
|
}
|
|
41890
42868
|
if (waitFor.kind === "substring") {
|
|
41891
42869
|
const trimmed = trimWaitScanBuffer(state.text, state.baseOffset, waitFor);
|
|
@@ -41899,15 +42877,15 @@ async function waitForBashStatus(ctx, runtime, taskId, outputMode, waitFor, effe
|
|
|
41899
42877
|
sawTerminal = true;
|
|
41900
42878
|
consumeBgCompletion(runtime.sessionID, taskId);
|
|
41901
42879
|
await markBgCompletionDelivered({ ctx, directory: projectRootFor(runtime), sessionID: runtime.sessionID }, taskId);
|
|
41902
|
-
return withWaited(data,
|
|
42880
|
+
return withWaited(data, waited("exited"));
|
|
41903
42881
|
}
|
|
41904
42882
|
if (isSyncWatchAborted(runtime.sessionID)) {
|
|
41905
|
-
return withWaited(data,
|
|
42883
|
+
return withWaited(data, waited("user_message"));
|
|
41906
42884
|
}
|
|
41907
|
-
if (
|
|
41908
|
-
return withWaited(data,
|
|
42885
|
+
if (watchClock.now() >= deadline) {
|
|
42886
|
+
return withWaited(data, waited("timeout"));
|
|
41909
42887
|
}
|
|
41910
|
-
await
|
|
42888
|
+
await pause();
|
|
41911
42889
|
}
|
|
41912
42890
|
} finally {
|
|
41913
42891
|
if (!sawTerminal)
|
|
@@ -42017,8 +42995,9 @@ function withWaited(data, waited) {
|
|
|
42017
42995
|
// src/tools/bash_write.ts
|
|
42018
42996
|
var z6 = tool.schema;
|
|
42019
42997
|
function createBashWriteTool(ctx) {
|
|
42998
|
+
const modeCheck = bashCompanionRegistered(ctx.config, "bash_status") ? '; check bash_status reports mode: "pty" first.' : ".";
|
|
42020
42999
|
return {
|
|
42021
|
-
description:
|
|
43000
|
+
description: `Write input bytes to a running PTY bash task. PTY-only${modeCheck} ` + 'Input is either a string (verbatim bytes) or an array mixing strings and { key: "esc" | "enter" | "up" | "ctrl-c" | ... } objects ' + 'for atomic text+key sequences such as [ "iHello", { key: "esc" }, ":wq", { key: "enter" } ]. ' + "Named keys cover enter/return/tab/space/backspace/esc/escape, arrows, home/end/page-up/page-down/delete/insert, f1..f12, and ctrl-a..ctrl-z. " + "Maximum 1 MiB per call (post-expansion).",
|
|
42022
43001
|
args: {
|
|
42023
43002
|
taskId: z6.string().describe("Background PTY task ID returned by bash({ pty: true, background: true })."),
|
|
42024
43003
|
input: z6.union([
|
|
@@ -42055,47 +43034,6 @@ var ISSUE_AND_PR_READ_DESCRIPTION = "GitHub issues and pull requests can be read
|
|
|
42055
43034
|
function whenGhReadEnabled(enabled, description) {
|
|
42056
43035
|
return enabled ? description : "";
|
|
42057
43036
|
}
|
|
42058
|
-
function asRecord(value) {
|
|
42059
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
42060
|
-
}
|
|
42061
|
-
function visionCapabilityForOpenCodeModel(model) {
|
|
42062
|
-
const entry = model;
|
|
42063
|
-
if (Array.isArray(entry?.modalities?.input)) {
|
|
42064
|
-
return entry.modalities.input.includes("image");
|
|
42065
|
-
}
|
|
42066
|
-
return typeof entry?.attachment === "boolean" ? entry.attachment : undefined;
|
|
42067
|
-
}
|
|
42068
|
-
function modelFromOpenCodeProvider(provider, modelID) {
|
|
42069
|
-
const models = provider.models;
|
|
42070
|
-
if (Array.isArray(models)) {
|
|
42071
|
-
return models.find((model) => asRecord(model)?.id === modelID);
|
|
42072
|
-
}
|
|
42073
|
-
return asRecord(models)?.[modelID];
|
|
42074
|
-
}
|
|
42075
|
-
async function currentSessionVisionCapability(client, sessionID) {
|
|
42076
|
-
if (!sessionID)
|
|
42077
|
-
return;
|
|
42078
|
-
const promptContext = await resolvePromptContext(client, sessionID);
|
|
42079
|
-
const currentModel = promptContext?.model;
|
|
42080
|
-
if (!currentModel)
|
|
42081
|
-
return;
|
|
42082
|
-
const providerApi = client.provider;
|
|
42083
|
-
if (typeof providerApi?.list !== "function")
|
|
42084
|
-
return;
|
|
42085
|
-
let listed;
|
|
42086
|
-
try {
|
|
42087
|
-
listed = await providerApi.list();
|
|
42088
|
-
} catch {
|
|
42089
|
-
return;
|
|
42090
|
-
}
|
|
42091
|
-
const result = asRecord(listed);
|
|
42092
|
-
const catalog = asRecord(result?.data) ?? result;
|
|
42093
|
-
const providers = Array.isArray(catalog?.all) ? catalog.all : Array.isArray(catalog?.providers) ? catalog.providers : [];
|
|
42094
|
-
const provider = providers.map(asRecord).find((entry) => entry?.id === currentModel.providerID);
|
|
42095
|
-
if (!provider)
|
|
42096
|
-
return;
|
|
42097
|
-
return visionCapabilityForOpenCodeModel(modelFromOpenCodeProvider(provider, currentModel.modelID));
|
|
42098
|
-
}
|
|
42099
43037
|
function persistFilePathAlias(args, context) {
|
|
42100
43038
|
if (typeof args.path === "string" && !Object.hasOwn(args, "filePath")) {
|
|
42101
43039
|
args.filePath = args.path;
|
|
@@ -42267,7 +43205,7 @@ function inferBeforeStart(ops, from, beforeLen) {
|
|
|
42267
43205
|
var z7 = tool.schema;
|
|
42268
43206
|
var READ_DESCRIPTION = `Read file contents or list directory entries.
|
|
42269
43207
|
|
|
42270
|
-
Use either startLine/endLine OR offset/limit to read a section of a file.
|
|
43208
|
+
Use either startLine/endLine OR offset/limit to read a section of a file or sorted directory listing.
|
|
42271
43209
|
|
|
42272
43210
|
Behavior:
|
|
42273
43211
|
- Returns line-numbered content (e.g., "1: const x = 1")
|
|
@@ -42275,7 +43213,7 @@ Behavior:
|
|
|
42275
43213
|
- Output capped at 50KB
|
|
42276
43214
|
- Binary files are auto-detected and return a size-only message
|
|
42277
43215
|
- Supported images (PNG, JPEG, GIF, WebP) and PDFs are returned as tool attachments; range arguments are ignored for media
|
|
42278
|
-
- Directories return sorted entries with trailing / for subdirectories
|
|
43216
|
+
- Directories return sorted entries with trailing / for subdirectories; offset is 1-based, limit defaults to and is capped at 1000 entries. Enumeration stops at 10,000 entries; partial listings carry a shown/total trailer.
|
|
42279
43217
|
|
|
42280
43218
|
Examples:
|
|
42281
43219
|
Read full file: { "path": "src/app.ts" }
|
|
@@ -42299,54 +43237,49 @@ function createReadTool(ctx) {
|
|
|
42299
43237
|
description: readDescription(resolveGithubConfig(ctx.config).read),
|
|
42300
43238
|
args: {
|
|
42301
43239
|
filePath: z7.string().describe("Path to file or directory (absolute or relative to project root)"),
|
|
42302
|
-
startLine: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based line to start reading from"),
|
|
42303
|
-
endLine: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based line to stop reading at (inclusive)"),
|
|
42304
|
-
limit: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("Max lines
|
|
42305
|
-
offset: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based line
|
|
43240
|
+
startLine: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based line or directory entry to start reading from"),
|
|
43241
|
+
endLine: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based line or directory entry to stop reading at (inclusive)"),
|
|
43242
|
+
limit: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("Max lines (default: 2000) or directory entries (default and cap: 1000) to return"),
|
|
43243
|
+
offset: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based line or directory entry to start reading from (use with limit). Ignored if startLine is provided")
|
|
42306
43244
|
},
|
|
42307
43245
|
execute: async (args, context) => {
|
|
42308
43246
|
const file = args.path;
|
|
42309
|
-
const projectRoot = await resolveProjectRoot(ctx, context);
|
|
43247
|
+
const projectRoot = await measurePreStage("directory", () => resolveProjectRoot(ctx, context));
|
|
42310
43248
|
const filePath = resolvePathFromProjectRoot(projectRoot, file);
|
|
42311
43249
|
persistFilePathAlias(args, context);
|
|
42312
43250
|
{
|
|
42313
|
-
const denial = await assertExternalDirectoryPermission(ctx, context, filePath, {
|
|
43251
|
+
const denial = await measurePreStage("permission", () => assertExternalDirectoryPermission(ctx, context, filePath, {
|
|
42314
43252
|
serverValidatedRead: true
|
|
42315
|
-
});
|
|
43253
|
+
}));
|
|
42316
43254
|
if (denial)
|
|
42317
43255
|
return permissionDeniedResponse(denial);
|
|
42318
43256
|
}
|
|
42319
43257
|
try {
|
|
42320
|
-
await runAsk(context.ask({
|
|
43258
|
+
await measurePreStage("permission", () => runAsk(context.ask({
|
|
42321
43259
|
permission: "read",
|
|
42322
43260
|
patterns: [permissionPath(context, filePath)],
|
|
42323
43261
|
always: ["*"],
|
|
42324
43262
|
metadata: {}
|
|
42325
|
-
}));
|
|
43263
|
+
})));
|
|
42326
43264
|
} catch (error) {
|
|
42327
43265
|
const failure = classifyPermissionError(error);
|
|
42328
43266
|
return permissionDeniedResponse(failure.kind === "rule_denied" ? permissionRuleDenial("read") : failure.message);
|
|
42329
43267
|
}
|
|
42330
|
-
const
|
|
42331
|
-
const
|
|
42332
|
-
const
|
|
42333
|
-
const
|
|
42334
|
-
let startLine = rawStartLine;
|
|
42335
|
-
let endLine = rawEndLine;
|
|
42336
|
-
if (startLine === undefined && rawOffset !== undefined) {
|
|
42337
|
-
startLine = rawOffset;
|
|
42338
|
-
if (rawLimit !== undefined) {
|
|
42339
|
-
endLine = rawOffset + rawLimit - 1;
|
|
42340
|
-
}
|
|
42341
|
-
}
|
|
43268
|
+
const startLine = coerceOptionalInt(args.startLine, "startLine", 1, Number.MAX_SAFE_INTEGER);
|
|
43269
|
+
const endLine = coerceOptionalInt(args.endLine, "endLine", 1, Number.MAX_SAFE_INTEGER);
|
|
43270
|
+
const limit = coerceOptionalInt(args.limit, "limit", 1, Number.MAX_SAFE_INTEGER);
|
|
43271
|
+
const offset = coerceOptionalInt(args.offset, "offset", 1, Number.MAX_SAFE_INTEGER);
|
|
42342
43272
|
const rawArgs = { filePath: file };
|
|
42343
43273
|
if (startLine !== undefined)
|
|
42344
43274
|
rawArgs.startLine = startLine;
|
|
42345
43275
|
if (endLine !== undefined)
|
|
42346
43276
|
rawArgs.endLine = endLine;
|
|
42347
|
-
if (
|
|
42348
|
-
rawArgs.
|
|
42349
|
-
|
|
43277
|
+
if (offset !== undefined)
|
|
43278
|
+
rawArgs.offset = offset;
|
|
43279
|
+
if (limit !== undefined)
|
|
43280
|
+
rawArgs.limit = limit;
|
|
43281
|
+
const mediaCandidate = isGithubResourcePath(file) || /\.(png|jpe?g|gif|webp|pdf)$/i.test(filePath) && await stat2(filePath).then((info) => info.isFile(), () => false);
|
|
43282
|
+
const visionCapability = mediaCandidate ? await measurePreStage("capability", () => currentSessionVisionCapability(ctx.client, context.sessionID)) : undefined;
|
|
42350
43283
|
if (visionCapability !== undefined)
|
|
42351
43284
|
rawArgs.vision_capability = visionCapability;
|
|
42352
43285
|
const response = await callToolCall(ctx, context, "read", rawArgs);
|
|
@@ -42400,7 +43333,7 @@ function createWriteTool(ctx, editToolName = "edit") {
|
|
|
42400
43333
|
description: getWriteDescription(ctx, editToolName),
|
|
42401
43334
|
args: {
|
|
42402
43335
|
filePath: z7.string().describe("Path to the file to write (absolute or relative to project root)"),
|
|
42403
|
-
content: z7.string().describe("The full content to write to the file")
|
|
43336
|
+
content: z7.string().describe("The full content to write to the file; for issue://N / pr://N, the comment body to post")
|
|
42404
43337
|
},
|
|
42405
43338
|
execute: async (args, context) => {
|
|
42406
43339
|
const argsRecord = args;
|
|
@@ -42517,7 +43450,7 @@ ${backupBehavior}
|
|
|
42517
43450
|
- Symbol replace includes decorators, attributes, and doc comments in range
|
|
42518
43451
|
- Response is a compact server-rendered summary; before/after diff details are attached as UI metadata when available.`;
|
|
42519
43452
|
}
|
|
42520
|
-
var
|
|
43453
|
+
var HASHLINE_EDIT_GRAMMAR = [
|
|
42521
43454
|
"Apply a hashline patch. Arguments are exactly `{patch}` where `patch` is a non-empty string. Server-owned preview control is outside this schema.",
|
|
42522
43455
|
"",
|
|
42523
43456
|
"Quick reference:",
|
|
@@ -42525,13 +43458,18 @@ var HASHLINE_EDIT_DESCRIPTION = [
|
|
|
42525
43458
|
"- Same canonical path: multiple sections compose in patch order against pre-request coordinates.",
|
|
42526
43459
|
"- Addresses: `0` (BOF), `N` (one line), `N.=M` (range; `N..=M`/`N..M` also work), `<N`/`>N` (gap before/after), `N*`/`<N*`/`>N*` (block), and `$`/`$-K` (EOF-relative). A plain `N` PUT replaces; use `<N` or `>N` to insert.",
|
|
42527
43460
|
"- PUT text: `PUT <address>:` followed by one or more `+` body rows (`+` alone is blank). A final patch newline is allowed. PUT without `:` copies `@name` (or the anonymous register) and takes no body; names use `@` plus ASCII letters, digits, `_`, or `-`.",
|
|
42528
|
-
"- CUT: `CUT <address> [@name]`. REM: bare `REM` only, removing the whole file. MV: `MV <destination>` (one whitespace-free path, optional matching quotes), once and after any line operations. `*** Begin Patch`/`*** End Patch` is an optional envelope."
|
|
42529
|
-
"- Only `read` (and accepted AFT `cat`/`head`/`tail` rewrites) mint hashline tags. `aft_zoom`, `aft_outline`, `grep`, `aft_search`, and conflict snippets do not. After navigation, call `read` on every file and range the patch addresses."
|
|
43461
|
+
"- CUT: `CUT <address> [@name]`. REM: bare `REM` only, removing the whole file. MV: `MV <destination>` (one whitespace-free path, optional matching quotes), once and after any line operations. `*** Begin Patch`/`*** End Patch` is an optional envelope."
|
|
42530
43462
|
].join(`
|
|
42531
43463
|
`);
|
|
43464
|
+
function hashlineEditDescription(config) {
|
|
43465
|
+
const bashRewrites = toolEnabled(config, "bash") && resolveBashConfig(config).rewrite;
|
|
43466
|
+
const tagSources = hashlineTagSourceSentence(bashRewrites, (name) => toolEnabled(config, name));
|
|
43467
|
+
return `${HASHLINE_EDIT_GRAMMAR}
|
|
43468
|
+
- ${tagSources}`;
|
|
43469
|
+
}
|
|
42532
43470
|
function createHashlineEditTool(ctx) {
|
|
42533
43471
|
return {
|
|
42534
|
-
description:
|
|
43472
|
+
description: hashlineEditDescription(ctx.config),
|
|
42535
43473
|
args: {
|
|
42536
43474
|
patch: z7.string().min(1).describe("Hashline patch text with one or more [path#TAG] sections and PUT/CUT/REM/MV operations")
|
|
42537
43475
|
},
|
|
@@ -42606,8 +43544,7 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
42606
43544
|
throw new Error("'path' parameter is required");
|
|
42607
43545
|
if (isGithubResourcePath(file)) {
|
|
42608
43546
|
const edits = argsRecord.edits;
|
|
42609
|
-
|
|
42610
|
-
if (!onlyFindReplace) {
|
|
43547
|
+
if (!isFindReplaceOnlyEdit(argsRecord)) {
|
|
42611
43548
|
throw new Error("edit: GitHub resources support only edits[] find/replace entries with oldString and optional newString");
|
|
42612
43549
|
}
|
|
42613
43550
|
const rawArgs = { path: file, edits };
|
|
@@ -42790,7 +43727,7 @@ function createApplyPatchTool(ctx) {
|
|
|
42790
43727
|
};
|
|
42791
43728
|
}
|
|
42792
43729
|
function deleteDescription(ctx) {
|
|
42793
|
-
const backupText = ctx.config.backup?.enabled === false ? "Backup capture is disabled by user config, so this tool does not create undo snapshots. " : "Each file is backed up before deletion — use aft_safety undo to recover any of them.
|
|
43730
|
+
const backupText = ctx.config.backup?.enabled === false ? "Backup capture is disabled by user config, so this tool does not create undo snapshots. A directory tree containing a mount point of another filesystem is still refused. " : "Each file is backed up before deletion — use aft_safety undo to recover any of them. A recursive delete backs up the whole tree: directories (empty ones too, with their permissions), file contents, hard links (relinked by undo) and symlinks (the link itself, never its target; restored exactly, even when dangling). Sockets are deleted but undo does not restore them, and a file hard-linked to paths outside the tree comes back as an independent copy; both are reported as warnings. Refused before anything is deleted: mount points of another filesystem, named pipes, device nodes, and symlinks undo cannot recreate exactly (non-UTF-8 target, or on Windows). Paths under the system temp directory are never backed up, so only mount points are refused there. The deleted file's contents stay in the undo store until its retention expires. A recursive delete whose backup would record more than 2,000 entries (files, directories and links) or copy 100 MiB in one call is refused before anything is deleted; delete such a tree in smaller pieces, or use bash `rm -rf` when no undo is needed. ";
|
|
42794
43731
|
return `Delete one or more files (or directories).
|
|
42795
43732
|
|
|
42796
43733
|
` + backupText + `Directory deletion requires recursive: true. Without it, passing a directory returns an error.
|
|
@@ -42894,6 +43831,12 @@ function createMoveTool(ctx) {
|
|
|
42894
43831
|
};
|
|
42895
43832
|
}
|
|
42896
43833
|
function hoistedTools(ctx) {
|
|
43834
|
+
const backgroundCompanions = resolveBashConfig(ctx.config).background ? {
|
|
43835
|
+
bash_status: createBashStatusTool(ctx),
|
|
43836
|
+
bash_write: createBashWriteTool(ctx),
|
|
43837
|
+
bash_watch: createBashWatchTool(ctx),
|
|
43838
|
+
bash_kill: createBashKillTool(ctx)
|
|
43839
|
+
} : {};
|
|
42897
43840
|
const tools = {
|
|
42898
43841
|
read: createReadTool(ctx),
|
|
42899
43842
|
write: createWriteTool(ctx, "edit"),
|
|
@@ -42902,10 +43845,7 @@ function hoistedTools(ctx) {
|
|
|
42902
43845
|
aft_delete: createDeleteTool(ctx),
|
|
42903
43846
|
aft_move: createMoveTool(ctx),
|
|
42904
43847
|
bash: createBashTool(ctx),
|
|
42905
|
-
|
|
42906
|
-
bash_write: createBashWriteTool(ctx),
|
|
42907
|
-
bash_watch: createBashWatchTool(ctx),
|
|
42908
|
-
bash_kill: createBashKillTool(ctx)
|
|
43848
|
+
...backgroundCompanions
|
|
42909
43849
|
};
|
|
42910
43850
|
return prepareToolMap(tools, { hashlineEffective: ctx.hashlineEffective });
|
|
42911
43851
|
}
|
|
@@ -43006,7 +43946,7 @@ async function resolveAndGateScope(ctx, context, scope) {
|
|
|
43006
43946
|
}
|
|
43007
43947
|
return { scope: Array.isArray(scope) ? resolved : resolved[0] };
|
|
43008
43948
|
}
|
|
43009
|
-
function
|
|
43949
|
+
function asRecord(value) {
|
|
43010
43950
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
43011
43951
|
}
|
|
43012
43952
|
function asString(value) {
|
|
@@ -43014,7 +43954,7 @@ function asString(value) {
|
|
|
43014
43954
|
}
|
|
43015
43955
|
function firstRecord(...values) {
|
|
43016
43956
|
for (const value of values) {
|
|
43017
|
-
const record =
|
|
43957
|
+
const record = asRecord(value);
|
|
43018
43958
|
if (record)
|
|
43019
43959
|
return record;
|
|
43020
43960
|
}
|
|
@@ -43049,7 +43989,7 @@ function parseInspectPhaseEntries(value) {
|
|
|
43049
43989
|
if (!Array.isArray(value))
|
|
43050
43990
|
return [];
|
|
43051
43991
|
return value.flatMap((candidate) => {
|
|
43052
|
-
const entry =
|
|
43992
|
+
const entry = asRecord(candidate);
|
|
43053
43993
|
const id = asString(entry?.id);
|
|
43054
43994
|
if (!id)
|
|
43055
43995
|
return [];
|
|
@@ -43065,7 +44005,7 @@ function parseInspectPhaseEntries(value) {
|
|
|
43065
44005
|
});
|
|
43066
44006
|
}
|
|
43067
44007
|
function parseInspectTerminal(payload) {
|
|
43068
|
-
const response =
|
|
44008
|
+
const response = asRecord(payload);
|
|
43069
44009
|
if (!response)
|
|
43070
44010
|
return;
|
|
43071
44011
|
const kind = terminalKind(response);
|
|
@@ -43080,7 +44020,7 @@ function parseInspectTerminal(payload) {
|
|
|
43080
44020
|
waitStampText: kind === "FRESH" ? firstString(waitStamp?.text, waitStamp?.human_text) : undefined
|
|
43081
44021
|
};
|
|
43082
44022
|
}
|
|
43083
|
-
const failedPhaseRecord =
|
|
44023
|
+
const failedPhaseRecord = asRecord(response.failed_phase);
|
|
43084
44024
|
const failedPhaseId = firstString(failedPhaseRecord?.id, response.failed_phase);
|
|
43085
44025
|
const failedPhase = failedPhaseId ? parseInspectPhaseEntries([
|
|
43086
44026
|
{
|
|
@@ -43301,6 +44241,9 @@ function navigationTools(ctx) {
|
|
|
43301
44241
|
|
|
43302
44242
|
// src/tools/reading.ts
|
|
43303
44243
|
var z11 = tool.schema;
|
|
44244
|
+
function isGithubPathArg(value) {
|
|
44245
|
+
return typeof value === "string" && (value.startsWith("issue://") || value.startsWith("pr://"));
|
|
44246
|
+
}
|
|
43304
44247
|
function buildZoomTitle(args) {
|
|
43305
44248
|
const targets = args.targets;
|
|
43306
44249
|
if (!isEmptyParam(targets)) {
|
|
@@ -43324,6 +44267,13 @@ function buildZoomTitle(args) {
|
|
|
43324
44267
|
}
|
|
43325
44268
|
return path || "(no target)";
|
|
43326
44269
|
}
|
|
44270
|
+
function outlineFocusSteer(config) {
|
|
44271
|
+
const zoomEnabled = toolEnabled(config, "aft_zoom");
|
|
44272
|
+
const reader = zoomEnabled ? "aft_zoom" : "read";
|
|
44273
|
+
const locate = toolEnabled(config, "aft_search") ? `aft_search + ${reader}` : reader;
|
|
44274
|
+
const callgraph = zoomEnabled ? toolEnabled(config, "aft_callgraph") ? " aft_zoom with `callgraph:true` gives one-level forward calls-out; use aft_callgraph only for reverse callers or multi-level traces." : " aft_zoom with `callgraph:true` gives one-level forward calls-out." : "";
|
|
44275
|
+
return `For understanding a specific feature, prefer ${locate} on named symbols; use aft_outline on a whole directory only for high-level structure mapping.${callgraph}`;
|
|
44276
|
+
}
|
|
43327
44277
|
function readingTools(ctx) {
|
|
43328
44278
|
const zoomEnabled = toolEnabled(ctx.config, "aft_zoom");
|
|
43329
44279
|
const ghReadEnabled = resolveGithubConfig(ctx.config).read;
|
|
@@ -43331,9 +44281,9 @@ function readingTools(ctx) {
|
|
|
43331
44281
|
const githubZoomDescription = whenGhReadEnabled(ghReadEnabled, "GitHub issue and pull-request discussion ordinals can be zoomed with `path: issue://…` or `path: pr://…` and `symbols`.");
|
|
43332
44282
|
const tools = prepareToolMap({
|
|
43333
44283
|
aft_outline: {
|
|
43334
|
-
description: "Structural outline of source code, documentation files, or remote URLs. For code, returns symbols (functions, classes, types) with line ranges. For Markdown and HTML, returns heading hierarchy. Use this to explore structure before reading specific sections with " + (zoomEnabled ? "aft_zoom" : "read") + ". With `files: true`, the outline is breadth-first with directory rollups; drill in by outlining a subdirectory. Rows show language, symbol count, and line count.\n\n" + (
|
|
44284
|
+
description: "Structural outline of source code, documentation files, or remote URLs. For code, returns symbols (functions, classes, types) with line ranges. For Markdown and HTML, returns heading hierarchy. Use this to explore structure before reading specific sections with " + (zoomEnabled ? "aft_zoom" : "read") + ". With `files: true`, the outline is breadth-first with directory rollups; drill in by outlining a subdirectory. Rows show language, symbol count, and line count.\n\n" + outlineFocusSteer(ctx.config) + `
|
|
43335
44285
|
|
|
43336
|
-
`
|
|
44286
|
+
` + "Pass a single `target`:\n" + ` • file path → outline that file (with signatures)
|
|
43337
44287
|
` + ` • directory path → outline source files under it
|
|
43338
44288
|
` + ` • URL (http:// or https://) → fetch and outline a remote HTML/Markdown document
|
|
43339
44289
|
` + " • array of paths → outline multiple files in one call; with files:true, every path must be a directory" + (githubOutlineDescription ? `
|
|
@@ -43382,7 +44332,7 @@ ${githubOutlineDescription}` : "") + "\n\nWhen a list is cut, the reply ends wit
|
|
|
43382
44332
|
}
|
|
43383
44333
|
},
|
|
43384
44334
|
aft_zoom: {
|
|
43385
|
-
description: "Inspect code symbols or documentation sections. For code, returns the full source of a symbol. Pass `callgraph: true` to also include call-graph annotations (calls-out / called-by within the same file). For Markdown and HTML, returns the section content under the given heading.\n\nUse
|
|
44335
|
+
description: "Inspect code symbols or documentation sections. For code, returns the full source of a symbol. Pass `callgraph: true` to also include call-graph annotations (calls-out / called-by within the same file). For Markdown and HTML, returns the section content under the given heading.\n\nUse `{ path, symbols }` or `{ url, symbols }` for one file/URL. `symbols` can be a string or array (one or many lookups in the same file/URL). Use `targets` for cross-file batches: `{ path, symbol }` or an array of them. Sending both merges them into one batch; a lookup that fails reports its own error line." + (githubZoomDescription ? `
|
|
43386
44336
|
|
|
43387
44337
|
${githubZoomDescription}` : ""),
|
|
43388
44338
|
args: {
|
|
@@ -43398,37 +44348,22 @@ ${githubZoomDescription}` : ""),
|
|
|
43398
44348
|
path: z11.string().describe("Path to file (absolute or relative to project root)"),
|
|
43399
44349
|
symbol: z11.string().describe("Symbol name in that file")
|
|
43400
44350
|
}))
|
|
43401
|
-
]).optional().describe("Cross-file batch: `{ path, symbol }` or an array of them.
|
|
44351
|
+
]).optional().describe("Cross-file batch: `{ path, symbol }` or an array of them. May be combined with path/url + symbols; all lookups are answered in one batch."),
|
|
43402
44352
|
contextLines: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("Lines of context before/after the symbol (default: 3)"),
|
|
43403
44353
|
callgraph: z11.boolean().optional().describe("Include call-graph annotations (calls-out / called-by within the same file). Default false; off keeps zoom output minimal.")
|
|
43404
44354
|
},
|
|
43405
44355
|
execute: async (args, context) => {
|
|
43406
|
-
const hasTargetsProvided = (t) => {
|
|
43407
|
-
if (isEmptyParam(t))
|
|
43408
|
-
return false;
|
|
43409
|
-
const entryEmpty = (entry) => {
|
|
43410
|
-
if (!entry || typeof entry !== "object")
|
|
43411
|
-
return true;
|
|
43412
|
-
const fp = entry.path;
|
|
43413
|
-
const sym = entry.symbol;
|
|
43414
|
-
const fpEmpty = typeof fp !== "string" || fp.length === 0;
|
|
43415
|
-
const symEmpty = typeof sym !== "string" || sym.length === 0;
|
|
43416
|
-
return fpEmpty && symEmpty;
|
|
43417
|
-
};
|
|
43418
|
-
if (Array.isArray(t))
|
|
43419
|
-
return !t.every(entryEmpty);
|
|
43420
|
-
return !entryEmpty(t);
|
|
43421
|
-
};
|
|
43422
44356
|
const targetsInput = coerceJsonCollectionParam(args.targets, "targets");
|
|
43423
|
-
const
|
|
43424
|
-
const
|
|
43425
|
-
const
|
|
43426
|
-
const
|
|
44357
|
+
const targetEntries = usableZoomTargets(targetsInput);
|
|
44358
|
+
const hasFilePath = !isBlankParam(args.path);
|
|
44359
|
+
const hasUrl = !isBlankParam(args.url);
|
|
44360
|
+
const hasTargets = targetEntries.length > 0;
|
|
44361
|
+
const hasSymbols = !isBlankParam(args.symbols);
|
|
43427
44362
|
const wantCallgraph = coerceBoolean(args.callgraph);
|
|
43428
44363
|
const contextLines = coerceOptionalInt(args.contextLines, "contextLines", 1, Number.MAX_SAFE_INTEGER);
|
|
43429
44364
|
const zoomTitle = buildZoomTitle({
|
|
43430
44365
|
...args,
|
|
43431
|
-
targets:
|
|
44366
|
+
targets: hasTargets ? targetEntries : undefined
|
|
43432
44367
|
});
|
|
43433
44368
|
const zoomDisplay = { title: zoomTitle };
|
|
43434
44369
|
if (hasFilePath)
|
|
@@ -43439,7 +44374,7 @@ ${githubZoomDescription}` : ""),
|
|
|
43439
44374
|
zoomDisplay.symbols = typeof args.symbols === "string" ? args.symbols : JSON.stringify(args.symbols);
|
|
43440
44375
|
}
|
|
43441
44376
|
if (hasTargets)
|
|
43442
|
-
zoomDisplay.targets = JSON.stringify(
|
|
44377
|
+
zoomDisplay.targets = JSON.stringify(targetEntries);
|
|
43443
44378
|
if (contextLines !== undefined)
|
|
43444
44379
|
zoomDisplay.contextLines = contextLines;
|
|
43445
44380
|
if (wantCallgraph)
|
|
@@ -43450,23 +44385,24 @@ ${githubZoomDescription}` : ""),
|
|
|
43450
44385
|
metadata: zoomDisplay
|
|
43451
44386
|
});
|
|
43452
44387
|
if (hasTargets) {
|
|
43453
|
-
if (hasFilePath
|
|
43454
|
-
throw new Error("
|
|
44388
|
+
if (hasFilePath && hasUrl) {
|
|
44389
|
+
throw new Error("Provide exactly ONE of 'path' or 'url' — not both");
|
|
43455
44390
|
}
|
|
43456
|
-
const targets =
|
|
43457
|
-
|
|
43458
|
-
throw new Error("'targets' must be a non-empty object or array");
|
|
43459
|
-
}
|
|
43460
|
-
for (const [i, entry] of targets.entries()) {
|
|
43461
|
-
const targetPath = entry?.path;
|
|
43462
|
-
if (typeof targetPath !== "string" || targetPath.length === 0) {
|
|
44391
|
+
const targets = targetEntries.map((entry, i) => {
|
|
44392
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
43463
44393
|
throw new Error(`targets[${i}].path must be a non-empty string`);
|
|
43464
44394
|
}
|
|
43465
|
-
|
|
43466
|
-
|
|
43467
|
-
|
|
43468
|
-
|
|
43469
|
-
|
|
44395
|
+
const record = entry;
|
|
44396
|
+
const targetPath = isBlankParam(record.path) ? record.filePath : record.path;
|
|
44397
|
+
return {
|
|
44398
|
+
path: typeof targetPath === "string" ? targetPath : "",
|
|
44399
|
+
symbol: typeof record.symbol === "string" ? record.symbol : ""
|
|
44400
|
+
};
|
|
44401
|
+
});
|
|
44402
|
+
const localPaths = targets.map((target) => target.path).filter((path) => path.trim().length > 0 && !isGithubPathArg(path));
|
|
44403
|
+
if (hasFilePath && !isGithubPathArg(args.path))
|
|
44404
|
+
localPaths.push(args.path);
|
|
44405
|
+
const resolvedTargets = await Promise.all(localPaths.map((path) => resolvePathArg(ctx, context, path)));
|
|
43470
44406
|
const permissionDenied = await assertPathExternalPermissions(ctx, context, resolvedTargets);
|
|
43471
44407
|
if (permissionDenied)
|
|
43472
44408
|
return permissionDeniedResponse(permissionDenied);
|
|
@@ -43476,6 +44412,12 @@ ${githubZoomDescription}` : ""),
|
|
|
43476
44412
|
symbol: target.symbol
|
|
43477
44413
|
}))
|
|
43478
44414
|
};
|
|
44415
|
+
if (hasFilePath)
|
|
44416
|
+
rawArgs.filePath = args.path;
|
|
44417
|
+
else if (hasUrl)
|
|
44418
|
+
rawArgs.url = args.url;
|
|
44419
|
+
if (hasSymbols)
|
|
44420
|
+
rawArgs.symbols = args.symbols;
|
|
43479
44421
|
if (contextLines !== undefined)
|
|
43480
44422
|
rawArgs.contextLines = contextLines;
|
|
43481
44423
|
if (wantCallgraph)
|
|
@@ -43492,7 +44434,7 @@ ${githubZoomDescription}` : ""),
|
|
|
43492
44434
|
if (hasFilePath && hasUrl) {
|
|
43493
44435
|
throw new Error("Provide exactly ONE of 'path' or 'url' — not both");
|
|
43494
44436
|
}
|
|
43495
|
-
const githubPath = hasFilePath && (
|
|
44437
|
+
const githubPath = hasFilePath && isGithubPathArg(args.path) || hasUrl && isGithubPathArg(args.url);
|
|
43496
44438
|
if (!hasUrl && !githubPath) {
|
|
43497
44439
|
const file = await resolvePathArg(ctx, context, args.path);
|
|
43498
44440
|
const permissionDenied = await assertPathExternalPermissions(ctx, context, file);
|
|
@@ -43583,7 +44525,7 @@ function safetyTools(ctx) {
|
|
|
43583
44525
|
` + "Use checkpoint before risky multi-file changes. Use undo for quick single-file rollback.",
|
|
43584
44526
|
args: {
|
|
43585
44527
|
op: z12.enum(["undo", "history", "checkpoint", "restore", "list"]).describe("Safety operation"),
|
|
43586
|
-
path: z12.string().optional().describe("File path (required for history, optional for undo, optional for restore — restores only that file). Absolute or relative to project root"),
|
|
44528
|
+
path: z12.string().optional().describe("File path (required for history, optional for undo, optional for checkpoint — snapshots only that file when files is omitted, optional for restore — restores only that file). Absolute or relative to project root"),
|
|
43587
44529
|
name: z12.string().optional().describe("Checkpoint name (required for checkpoint, restore)"),
|
|
43588
44530
|
files: z12.array(z12.string()).optional().describe("Specific files to include in checkpoint, or to restore from a checkpoint (optional; restore without path/files restores the whole checkpoint; explicit checkpoint files may be untracked or gitignored)")
|
|
43589
44531
|
},
|
|
@@ -43853,7 +44795,7 @@ function hasMeaningfulProjectSearchFallback(query, pathArg, externalRoot) {
|
|
|
43853
44795
|
return trimmedQuery.length > 0 && trimmedQuery !== pathArg && trimmedQuery !== externalRoot;
|
|
43854
44796
|
}
|
|
43855
44797
|
var GENERATION_CHANGED_DISCLOSURE = "index changed - order re-derived";
|
|
43856
|
-
function
|
|
44798
|
+
function asRecord2(value) {
|
|
43857
44799
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
43858
44800
|
}
|
|
43859
44801
|
function normalizeContinuityQuery(query) {
|
|
@@ -43868,14 +44810,14 @@ function continuityKey(projectRoot, query, includeTests, topK) {
|
|
|
43868
44810
|
]);
|
|
43869
44811
|
}
|
|
43870
44812
|
function responseGeneration(response) {
|
|
43871
|
-
const envelope =
|
|
43872
|
-
const structured =
|
|
43873
|
-
const plan =
|
|
44813
|
+
const envelope = asRecord2(response);
|
|
44814
|
+
const structured = asRecord2(envelope?.structuredContent) ?? asRecord2(envelope?.structured_content);
|
|
44815
|
+
const plan = asRecord2(envelope?.plan) ?? asRecord2(structured?.plan);
|
|
43874
44816
|
const generation = plan?.snapshot_generation ?? envelope?.snapshot_generation;
|
|
43875
44817
|
return typeof generation === "string" ? generation : undefined;
|
|
43876
44818
|
}
|
|
43877
44819
|
function surfaceSearchText(response, continuity, sessionId, key) {
|
|
43878
|
-
const envelope =
|
|
44820
|
+
const envelope = asRecord2(response);
|
|
43879
44821
|
const text = typeof envelope?.text === "string" ? envelope.text : "";
|
|
43880
44822
|
const generation = responseGeneration(response);
|
|
43881
44823
|
if (generation === undefined)
|
|
@@ -44019,8 +44961,20 @@ function buildAftToolDefinitions(ctx, config, onUnknownDisabled) {
|
|
|
44019
44961
|
const unknown = unknownDisabledTools(disabled);
|
|
44020
44962
|
if (unknown.length > 0)
|
|
44021
44963
|
onUnknownDisabled?.(unknown);
|
|
44964
|
+
describeBashForRegisteredSurface(allTools, config);
|
|
44022
44965
|
return allTools;
|
|
44023
44966
|
}
|
|
44967
|
+
function describeBashForRegisteredSurface(allTools, config) {
|
|
44968
|
+
const bash = allTools.bash;
|
|
44969
|
+
if (!bash)
|
|
44970
|
+
return;
|
|
44971
|
+
const bashCfg = resolveBashConfig(config);
|
|
44972
|
+
bash.description = bashToolDescription("aft_search" in allTools, bashCfg.compress, bashCfg.background, bashCfg.detach_on_user_message, "aft_zoom" in allTools, "bash_watch" in allTools, {
|
|
44973
|
+
outline: "aft_outline" in allTools,
|
|
44974
|
+
status: "bash_status" in allTools,
|
|
44975
|
+
write: "bash_write" in allTools
|
|
44976
|
+
});
|
|
44977
|
+
}
|
|
44024
44978
|
function registerAftTools(context, location, definitions, consumers = {}) {
|
|
44025
44979
|
const projected = Object.entries(definitions).map(([name, definition]) => projectV2Tool(name, definition, location, consumers));
|
|
44026
44980
|
return context.tool.transform((editor) => {
|
|
@@ -44153,6 +45107,8 @@ var defaultBridgeBootstrapDependencies = {
|
|
|
44153
45107
|
loadConfig: loadAftConfig,
|
|
44154
45108
|
deliverLoadNotices: (notify) => deliverConfigLoadNotices(notify, getConfigLoadNotices()),
|
|
44155
45109
|
configLoadErrors: getConfigLoadErrors,
|
|
45110
|
+
configLoadSources: getConfigLoadSources,
|
|
45111
|
+
configLoadTexts: getConfigLoadTexts,
|
|
44156
45112
|
subcConnectionFileError,
|
|
44157
45113
|
migrateConfigLocations: (directory) => migrateAftConfigLocations(directory, bridgeLogger).flatMap((result) => result.warnings),
|
|
44158
45114
|
resolveBinary: resolveBinaryWithWarmup,
|
|
@@ -44194,8 +45150,12 @@ function loadBootstrapConfig(directory, notify, dependencies = defaultBridgeBoot
|
|
|
44194
45150
|
const failure = parseFailure(dependencies);
|
|
44195
45151
|
if (failure)
|
|
44196
45152
|
return configErrorState(failure, notify);
|
|
45153
|
+
const provenance = dependencies.configLoadSources ? {
|
|
45154
|
+
sources: [...dependencies.configLoadSources()],
|
|
45155
|
+
sourceTexts: Object.fromEntries(dependencies.configLoadTexts?.() ?? [])
|
|
45156
|
+
} : {};
|
|
44197
45157
|
dependencies.deliverLoadNotices?.(notify);
|
|
44198
|
-
return { ok: true, config };
|
|
45158
|
+
return { ok: true, config, ...provenance };
|
|
44199
45159
|
} catch (err) {
|
|
44200
45160
|
return configErrorState(err instanceof Error ? err.message : String(err), notify);
|
|
44201
45161
|
}
|
|
@@ -44387,8 +45347,60 @@ function buildConfigErrorToolMap(config, message, client, buildToolMap = buildAf
|
|
|
44387
45347
|
return failEveryToolCall(buildToolMap(ctx, config), message);
|
|
44388
45348
|
}
|
|
44389
45349
|
|
|
45350
|
+
// src/config-live-reload.ts
|
|
45351
|
+
var OPENCODE_LIVE_CONFIG_KEYS = aftLiveConfigKeys((config) => resolveBashConfig(config));
|
|
45352
|
+
function projectTextOf(directory) {
|
|
45353
|
+
const { projectConfigPath } = resolveCortexKitConfigPaths(directory);
|
|
45354
|
+
return getConfigLoadTexts().get(projectConfigPath) ?? null;
|
|
45355
|
+
}
|
|
45356
|
+
function loadOpenCodeConfigForLiveReload(directory) {
|
|
45357
|
+
try {
|
|
45358
|
+
const config = loadAftConfig(directory);
|
|
45359
|
+
const [failure] = getConfigLoadErrors();
|
|
45360
|
+
if (failure) {
|
|
45361
|
+
return { ok: false, message: formatConfigParseErrorMessage(failure.path, failure.message) };
|
|
45362
|
+
}
|
|
45363
|
+
const [invalid] = getConfigValidationErrors();
|
|
45364
|
+
if (invalid) {
|
|
45365
|
+
return {
|
|
45366
|
+
ok: false,
|
|
45367
|
+
message: `AFT config at ${invalid.path} has an invalid setting: ${invalid.message}`
|
|
45368
|
+
};
|
|
45369
|
+
}
|
|
45370
|
+
return {
|
|
45371
|
+
ok: true,
|
|
45372
|
+
config,
|
|
45373
|
+
sources: [...getConfigLoadSources()],
|
|
45374
|
+
projectText: projectTextOf(directory),
|
|
45375
|
+
texts: Object.fromEntries(getConfigLoadTexts())
|
|
45376
|
+
};
|
|
45377
|
+
} catch (err) {
|
|
45378
|
+
return { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
45379
|
+
}
|
|
45380
|
+
}
|
|
45381
|
+
function startOpenCodeLiveConfigReload(options) {
|
|
45382
|
+
const { userConfigPath, projectConfigPath } = resolveCortexKitConfigPaths(options.directory);
|
|
45383
|
+
return startLiveConfigReload({
|
|
45384
|
+
paths: [userConfigPath, projectConfigPath],
|
|
45385
|
+
load: () => loadOpenCodeConfigForLiveReload(options.directory),
|
|
45386
|
+
initialSources: options.initialSources,
|
|
45387
|
+
initialProjectText: options.initialSourceTexts?.[projectConfigPath] ?? null,
|
|
45388
|
+
initialTexts: options.initialSourceTexts,
|
|
45389
|
+
securityKeys: aftLiveSecurityKeys(OPENCODE_LIVE_CONFIG_KEYS),
|
|
45390
|
+
keys: OPENCODE_LIVE_CONFIG_KEYS,
|
|
45391
|
+
getConfig: options.getConfig,
|
|
45392
|
+
setConfig: options.setConfig,
|
|
45393
|
+
log: (message) => log2(`${message} (${options.directory})`),
|
|
45394
|
+
reportError: (message) => {
|
|
45395
|
+
error2(message);
|
|
45396
|
+
options.notify(message);
|
|
45397
|
+
},
|
|
45398
|
+
watch: options.watch
|
|
45399
|
+
});
|
|
45400
|
+
}
|
|
45401
|
+
|
|
44390
45402
|
// src/notifications.ts
|
|
44391
|
-
import { existsSync as existsSync18, readFileSync as
|
|
45403
|
+
import { existsSync as existsSync18, readFileSync as readFileSync15 } from "node:fs";
|
|
44392
45404
|
import { homedir as homedir13, platform } from "node:os";
|
|
44393
45405
|
import { join as join20 } from "node:path";
|
|
44394
45406
|
function isTuiMode() {
|
|
@@ -44430,7 +45442,7 @@ function readDesktopState() {
|
|
|
44430
45442
|
return { serverUrl: null };
|
|
44431
45443
|
}
|
|
44432
45444
|
try {
|
|
44433
|
-
const raw =
|
|
45445
|
+
const raw = readFileSync15(statePath, "utf-8");
|
|
44434
45446
|
const state = JSON.parse(raw);
|
|
44435
45447
|
let serverUrl = null;
|
|
44436
45448
|
const serverStr = state.server;
|
|
@@ -44929,23 +45941,25 @@ import {
|
|
|
44929
45941
|
existsSync as existsSync21,
|
|
44930
45942
|
mkdirSync as mkdirSync9,
|
|
44931
45943
|
openSync as openSync7,
|
|
44932
|
-
readFileSync as
|
|
45944
|
+
readFileSync as readFileSync18,
|
|
44933
45945
|
renameSync as renameSync11,
|
|
44934
45946
|
rmSync as rmSync8,
|
|
44935
|
-
statSync as
|
|
45947
|
+
statSync as statSync15,
|
|
44936
45948
|
writeFileSync as writeFileSync12
|
|
44937
45949
|
} from "node:fs";
|
|
44938
|
-
import { dirname as
|
|
45950
|
+
import { dirname as dirname15 } from "node:path";
|
|
44939
45951
|
|
|
44940
45952
|
// src/hooks/auto-update-checker/cache.ts
|
|
44941
|
-
import { cpSync, existsSync as existsSync20, mkdtempSync, readFileSync as
|
|
45953
|
+
import { cpSync, existsSync as existsSync20, mkdtempSync, readFileSync as readFileSync17, rmSync as rmSync7, writeFileSync as writeFileSync11 } from "node:fs";
|
|
44942
45954
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
44943
|
-
import { basename as
|
|
45955
|
+
import { basename as basename6, dirname as dirname14, join as join23 } from "node:path";
|
|
44944
45956
|
var import_comment_json3 = __toESM(require_src2(), 1);
|
|
44945
45957
|
|
|
44946
45958
|
// src/hooks/auto-update-checker/checker.ts
|
|
44947
|
-
import { existsSync as existsSync19, readFileSync as
|
|
44948
|
-
import {
|
|
45959
|
+
import { existsSync as existsSync19, readFileSync as readFileSync16, statSync as statSync14, writeFileSync as writeFileSync10 } from "node:fs";
|
|
45960
|
+
import { readFile } from "node:fs/promises";
|
|
45961
|
+
import { homedir as homedir14 } from "node:os";
|
|
45962
|
+
import { dirname as dirname13, isAbsolute as isAbsolute11, join as join22, resolve as resolve13 } from "node:path";
|
|
44949
45963
|
import { fileURLToPath } from "node:url";
|
|
44950
45964
|
var import_comment_json2 = __toESM(require_src2(), 1);
|
|
44951
45965
|
|
|
@@ -45036,14 +46050,14 @@ function resolvePathPluginSpec(spec, configPath) {
|
|
|
45036
46050
|
}
|
|
45037
46051
|
if (isAbsolute11(spec) || /^[A-Za-z]:[\\/]/.test(spec))
|
|
45038
46052
|
return spec;
|
|
45039
|
-
return resolve13(
|
|
46053
|
+
return resolve13(dirname13(configPath), spec);
|
|
45040
46054
|
}
|
|
45041
46055
|
function getLocalDevPath(directory) {
|
|
45042
46056
|
for (const configPath of getConfigPaths(directory)) {
|
|
45043
46057
|
try {
|
|
45044
46058
|
if (!existsSync19(configPath))
|
|
45045
46059
|
continue;
|
|
45046
|
-
const rawConfig = parseJsonConfig(
|
|
46060
|
+
const rawConfig = parseJsonConfig(readFileSync16(configPath, "utf-8"));
|
|
45047
46061
|
const plugins = getPluginEntries(rawConfig);
|
|
45048
46062
|
for (const entry of plugins) {
|
|
45049
46063
|
if (entry === PACKAGE_NAME || entry.startsWith(`${PACKAGE_NAME}@`))
|
|
@@ -45053,7 +46067,7 @@ function getLocalDevPath(directory) {
|
|
|
45053
46067
|
const pkgPath = findPackageJsonUp(localPath);
|
|
45054
46068
|
if (!pkgPath)
|
|
45055
46069
|
continue;
|
|
45056
|
-
const pkg = PackageJsonSchema.safeParse(JSON.parse(
|
|
46070
|
+
const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync16(pkgPath, "utf-8")));
|
|
45057
46071
|
if (pkg.success && pkg.data.name === PACKAGE_NAME)
|
|
45058
46072
|
return localPath;
|
|
45059
46073
|
}
|
|
@@ -45064,18 +46078,18 @@ function getLocalDevPath(directory) {
|
|
|
45064
46078
|
}
|
|
45065
46079
|
function findPackageJsonUp(startPath) {
|
|
45066
46080
|
try {
|
|
45067
|
-
const stat =
|
|
45068
|
-
let dir = stat.isDirectory() ? startPath :
|
|
46081
|
+
const stat = statSync14(startPath);
|
|
46082
|
+
let dir = stat.isDirectory() ? startPath : dirname13(startPath);
|
|
45069
46083
|
for (let i = 0;i < 10; i++) {
|
|
45070
46084
|
const pkgPath = join22(dir, "package.json");
|
|
45071
46085
|
if (existsSync19(pkgPath)) {
|
|
45072
46086
|
try {
|
|
45073
|
-
const pkg = PackageJsonSchema.safeParse(JSON.parse(
|
|
46087
|
+
const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync16(pkgPath, "utf-8")));
|
|
45074
46088
|
if (pkg.success && pkg.data.name === PACKAGE_NAME)
|
|
45075
46089
|
return pkgPath;
|
|
45076
46090
|
} catch {}
|
|
45077
46091
|
}
|
|
45078
|
-
const parent =
|
|
46092
|
+
const parent = dirname13(dir);
|
|
45079
46093
|
if (parent === dir)
|
|
45080
46094
|
break;
|
|
45081
46095
|
dir = parent;
|
|
@@ -45091,7 +46105,7 @@ function getLocalDevVersion(directory) {
|
|
|
45091
46105
|
const pkgPath = findPackageJsonUp(localPath);
|
|
45092
46106
|
if (!pkgPath)
|
|
45093
46107
|
return null;
|
|
45094
|
-
const pkg = PackageJsonSchema.safeParse(JSON.parse(
|
|
46108
|
+
const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync16(pkgPath, "utf-8")));
|
|
45095
46109
|
return pkg.success ? pkg.data.version ?? null : null;
|
|
45096
46110
|
} catch {
|
|
45097
46111
|
return null;
|
|
@@ -45099,7 +46113,7 @@ function getLocalDevVersion(directory) {
|
|
|
45099
46113
|
}
|
|
45100
46114
|
function getCurrentRuntimePackageJsonPath(currentModuleUrl = import.meta.url) {
|
|
45101
46115
|
try {
|
|
45102
|
-
return findPackageJsonUp(
|
|
46116
|
+
return findPackageJsonUp(dirname13(fileURLToPath(currentModuleUrl)));
|
|
45103
46117
|
} catch (err) {
|
|
45104
46118
|
warn2(`[auto-update-checker] Failed to resolve runtime package path: ${String(err)}`);
|
|
45105
46119
|
return null;
|
|
@@ -45110,7 +46124,7 @@ function findPluginEntry(directory) {
|
|
|
45110
46124
|
try {
|
|
45111
46125
|
if (!existsSync19(configPath))
|
|
45112
46126
|
continue;
|
|
45113
|
-
const rawConfig = parseJsonConfig(
|
|
46127
|
+
const rawConfig = parseJsonConfig(readFileSync16(configPath, "utf-8"));
|
|
45114
46128
|
const plugins = getPluginEntries(rawConfig);
|
|
45115
46129
|
for (const entry of plugins) {
|
|
45116
46130
|
if (entry === PACKAGE_NAME) {
|
|
@@ -45143,7 +46157,7 @@ function getCachedVersion(spec) {
|
|
|
45143
46157
|
try {
|
|
45144
46158
|
if (!existsSync19(packageJsonPath))
|
|
45145
46159
|
continue;
|
|
45146
|
-
const pkg = PackageJsonSchema.safeParse(JSON.parse(
|
|
46160
|
+
const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync16(packageJsonPath, "utf-8")));
|
|
45147
46161
|
if (pkg.success && pkg.data.version) {
|
|
45148
46162
|
if (!spec)
|
|
45149
46163
|
cachedPackageVersion = pkg.data.version;
|
|
@@ -45153,6 +46167,117 @@ function getCachedVersion(spec) {
|
|
|
45153
46167
|
}
|
|
45154
46168
|
return null;
|
|
45155
46169
|
}
|
|
46170
|
+
var registryCache = new Map;
|
|
46171
|
+
function npmConfigGet(key, directory) {
|
|
46172
|
+
return new Promise((resolve, reject) => {
|
|
46173
|
+
execFile(process.platform === "win32" ? "npm.cmd" : "npm", ["config", "get", key], {
|
|
46174
|
+
cwd: directory,
|
|
46175
|
+
timeout: NPM_FETCH_TIMEOUT,
|
|
46176
|
+
windowsHide: true,
|
|
46177
|
+
shell: process.platform === "win32"
|
|
46178
|
+
}, (error, stdout) => {
|
|
46179
|
+
if (error)
|
|
46180
|
+
reject(error);
|
|
46181
|
+
else
|
|
46182
|
+
resolve(stdout.trim());
|
|
46183
|
+
});
|
|
46184
|
+
});
|
|
46185
|
+
}
|
|
46186
|
+
function configuredRegistry(value) {
|
|
46187
|
+
return value && value !== "undefined" && value !== "null" ? value : undefined;
|
|
46188
|
+
}
|
|
46189
|
+
async function readRegistrySettings(path, required = false) {
|
|
46190
|
+
let text;
|
|
46191
|
+
try {
|
|
46192
|
+
text = await readFile(path, "utf8");
|
|
46193
|
+
} catch (error) {
|
|
46194
|
+
if (!required && error.code === "ENOENT")
|
|
46195
|
+
return {};
|
|
46196
|
+
throw error;
|
|
46197
|
+
}
|
|
46198
|
+
const settings = {};
|
|
46199
|
+
for (const line of text.split(/\r?\n/)) {
|
|
46200
|
+
const trimmed = line.trim();
|
|
46201
|
+
if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith(";"))
|
|
46202
|
+
continue;
|
|
46203
|
+
const separator = trimmed.indexOf("=");
|
|
46204
|
+
if (separator < 0)
|
|
46205
|
+
continue;
|
|
46206
|
+
const key = trimmed.slice(0, separator).trim();
|
|
46207
|
+
if (key === "registry" || key === "@cortexkit:registry") {
|
|
46208
|
+
settings[key] = configuredRegistry(trimmed.slice(separator + 1).trim());
|
|
46209
|
+
}
|
|
46210
|
+
}
|
|
46211
|
+
return settings;
|
|
46212
|
+
}
|
|
46213
|
+
function registryEnvironment(key) {
|
|
46214
|
+
const variable = `npm_config_${key}`;
|
|
46215
|
+
const value = process.env[variable] ?? Object.entries(process.env).find(([name]) => name.toLowerCase() === variable)?.[1];
|
|
46216
|
+
return configuredRegistry(value ?? "");
|
|
46217
|
+
}
|
|
46218
|
+
async function registryWithoutNpm(directory) {
|
|
46219
|
+
let scoped = registryEnvironment("@cortexkit:registry");
|
|
46220
|
+
let registry = registryEnvironment("registry");
|
|
46221
|
+
const visited = new Set;
|
|
46222
|
+
for (let current = directory;; current = dirname13(current)) {
|
|
46223
|
+
if (scoped)
|
|
46224
|
+
break;
|
|
46225
|
+
const path = join22(current, ".npmrc");
|
|
46226
|
+
visited.add(path);
|
|
46227
|
+
const settings = await readRegistrySettings(path);
|
|
46228
|
+
scoped ??= settings["@cortexkit:registry"];
|
|
46229
|
+
registry ??= settings.registry;
|
|
46230
|
+
if (dirname13(current) === current)
|
|
46231
|
+
break;
|
|
46232
|
+
}
|
|
46233
|
+
const explicitUserConfig = process.env.npm_config_userconfig ?? process.env.NPM_CONFIG_USERCONFIG;
|
|
46234
|
+
const home = (process.platform === "win32" ? process.env.USERPROFILE : process.env.HOME) ?? homedir14();
|
|
46235
|
+
const userConfig = explicitUserConfig ?? join22(home, ".npmrc");
|
|
46236
|
+
if (!scoped && !visited.has(userConfig)) {
|
|
46237
|
+
const settings = await readRegistrySettings(userConfig, explicitUserConfig !== undefined);
|
|
46238
|
+
scoped = settings["@cortexkit:registry"];
|
|
46239
|
+
registry ??= settings.registry;
|
|
46240
|
+
}
|
|
46241
|
+
const selected = scoped ?? registry ?? NPM_REGISTRY_URL;
|
|
46242
|
+
return selected.replace(/\$\{([^}]+)\}/g, (_match, name) => {
|
|
46243
|
+
const value = process.env[name];
|
|
46244
|
+
if (value === undefined)
|
|
46245
|
+
throw new Error("Unresolved registry environment variable");
|
|
46246
|
+
return value;
|
|
46247
|
+
});
|
|
46248
|
+
}
|
|
46249
|
+
function validateRegistry(registry) {
|
|
46250
|
+
const url = new URL(registry);
|
|
46251
|
+
if (url.protocol !== "https:" && url.protocol !== "http:")
|
|
46252
|
+
throw new Error("Unsupported registry protocol");
|
|
46253
|
+
return registry;
|
|
46254
|
+
}
|
|
46255
|
+
var reportedRegistryFailures = new Set;
|
|
46256
|
+
function reportRegistryFailure(directory) {
|
|
46257
|
+
const key = resolve13(directory);
|
|
46258
|
+
if (reportedRegistryFailures.has(key))
|
|
46259
|
+
return;
|
|
46260
|
+
reportedRegistryFailures.add(key);
|
|
46261
|
+
debug2("[auto-update-checker] Configured npm registry could not be resolved or used; skipping update check.");
|
|
46262
|
+
}
|
|
46263
|
+
function resolveRegistry(directory) {
|
|
46264
|
+
const key = resolve13(directory);
|
|
46265
|
+
let pending = registryCache.get(key);
|
|
46266
|
+
if (!pending) {
|
|
46267
|
+
pending = (async () => {
|
|
46268
|
+
let registry;
|
|
46269
|
+
try {
|
|
46270
|
+
const scoped = configuredRegistry(await npmConfigGet("@cortexkit:registry", key));
|
|
46271
|
+
registry = scoped ?? configuredRegistry(await npmConfigGet("registry", key)) ?? NPM_REGISTRY_URL;
|
|
46272
|
+
} catch {
|
|
46273
|
+
registry = await registryWithoutNpm(key);
|
|
46274
|
+
}
|
|
46275
|
+
return validateRegistry(registry);
|
|
46276
|
+
})();
|
|
46277
|
+
registryCache.set(key, pending);
|
|
46278
|
+
}
|
|
46279
|
+
return pending;
|
|
46280
|
+
}
|
|
45156
46281
|
function buildRegistryUrl(registryUrl) {
|
|
45157
46282
|
return `${registryUrl.replace(/\/+$/, "")}/${encodeURIComponent(PACKAGE_NAME).replace("%2F", "/")}`;
|
|
45158
46283
|
}
|
|
@@ -45164,17 +46289,24 @@ async function getLatestVersion(channel = "latest", options = {}) {
|
|
|
45164
46289
|
try {
|
|
45165
46290
|
if (options.signal?.aborted)
|
|
45166
46291
|
return null;
|
|
45167
|
-
const
|
|
46292
|
+
const registry = validateRegistry(options.registryUrl ?? await resolveRegistry(options.directory ?? process.cwd()));
|
|
46293
|
+
if (controller.signal.aborted)
|
|
46294
|
+
return null;
|
|
46295
|
+
const response = await fetch(buildRegistryUrl(registry), {
|
|
45168
46296
|
signal: controller.signal,
|
|
45169
46297
|
headers: { Accept: "application/json" }
|
|
45170
46298
|
});
|
|
45171
|
-
if (!response.ok)
|
|
46299
|
+
if (!response.ok) {
|
|
46300
|
+
reportRegistryFailure(options.directory ?? process.cwd());
|
|
45172
46301
|
return null;
|
|
46302
|
+
}
|
|
45173
46303
|
const data = NpmPackageEnvelopeSchema.safeParse(await response.json());
|
|
45174
46304
|
if (!data.success)
|
|
45175
46305
|
return null;
|
|
45176
46306
|
return data.data["dist-tags"][channel] ?? data.data["dist-tags"].latest ?? null;
|
|
45177
46307
|
} catch {
|
|
46308
|
+
if (!controller.signal.aborted)
|
|
46309
|
+
reportRegistryFailure(options.directory ?? process.cwd());
|
|
45178
46310
|
return null;
|
|
45179
46311
|
} finally {
|
|
45180
46312
|
options.signal?.removeEventListener("abort", abortHandler);
|
|
@@ -45194,9 +46326,9 @@ function createAutoUpdateSnapshot(installDir, packageJsonPath, packageName) {
|
|
|
45194
46326
|
cpSync(packageDir, stagedPackageDir, { recursive: true });
|
|
45195
46327
|
return {
|
|
45196
46328
|
packageJsonPath,
|
|
45197
|
-
packageJson: existsSync20(packageJsonPath) ?
|
|
46329
|
+
packageJson: existsSync20(packageJsonPath) ? readFileSync17(packageJsonPath, "utf-8") : null,
|
|
45198
46330
|
lockfilePath,
|
|
45199
|
-
lockfile: existsSync20(lockfilePath) ?
|
|
46331
|
+
lockfile: existsSync20(lockfilePath) ? readFileSync17(lockfilePath, "utf-8") : null,
|
|
45200
46332
|
packageDir,
|
|
45201
46333
|
stagedPackageDir,
|
|
45202
46334
|
tempDir
|
|
@@ -45223,9 +46355,9 @@ function restoreAutoUpdateSnapshot(snapshot) {
|
|
|
45223
46355
|
function stripPackageNameFromPath(pathValue, packageName) {
|
|
45224
46356
|
let current = pathValue;
|
|
45225
46357
|
for (const segment of [...packageName.split("/")].reverse()) {
|
|
45226
|
-
if (
|
|
46358
|
+
if (basename6(current) !== segment)
|
|
45227
46359
|
return null;
|
|
45228
|
-
current =
|
|
46360
|
+
current = dirname14(current);
|
|
45229
46361
|
}
|
|
45230
46362
|
return current;
|
|
45231
46363
|
}
|
|
@@ -45234,7 +46366,7 @@ function removeFromPackageLock(installDir, packageName) {
|
|
|
45234
46366
|
if (!existsSync20(lockPath))
|
|
45235
46367
|
return false;
|
|
45236
46368
|
try {
|
|
45237
|
-
const lock = import_comment_json3.parse(
|
|
46369
|
+
const lock = import_comment_json3.parse(readFileSync17(lockPath, "utf-8"));
|
|
45238
46370
|
let modified = false;
|
|
45239
46371
|
if (lock.packages) {
|
|
45240
46372
|
const key = `node_modules/${packageName}`;
|
|
@@ -45260,7 +46392,7 @@ function ensureDependencyVersion(packageJsonPath, packageName, version) {
|
|
|
45260
46392
|
if (!existsSync20(packageJsonPath))
|
|
45261
46393
|
return false;
|
|
45262
46394
|
try {
|
|
45263
|
-
const raw = import_comment_json3.parse(
|
|
46395
|
+
const raw = import_comment_json3.parse(readFileSync17(packageJsonPath, "utf-8"));
|
|
45264
46396
|
const pkgJson = PackageJsonSchema.safeParse(raw);
|
|
45265
46397
|
if (!pkgJson.success)
|
|
45266
46398
|
return false;
|
|
@@ -45295,17 +46427,17 @@ function removeInstalledPackage(installDir, packageName) {
|
|
|
45295
46427
|
}
|
|
45296
46428
|
function resolveInstallContext(runtimePackageJsonPath = getCurrentRuntimePackageJsonPath()) {
|
|
45297
46429
|
if (runtimePackageJsonPath) {
|
|
45298
|
-
const packageDir =
|
|
46430
|
+
const packageDir = dirname14(runtimePackageJsonPath);
|
|
45299
46431
|
const nodeModulesDir = stripPackageNameFromPath(packageDir, PACKAGE_NAME);
|
|
45300
|
-
if (nodeModulesDir &&
|
|
45301
|
-
const installDir =
|
|
46432
|
+
if (nodeModulesDir && basename6(nodeModulesDir) === "node_modules") {
|
|
46433
|
+
const installDir = dirname14(nodeModulesDir);
|
|
45302
46434
|
const packageJsonPath = join23(installDir, "package.json");
|
|
45303
46435
|
if (existsSync20(packageJsonPath))
|
|
45304
46436
|
return { installDir, packageJsonPath };
|
|
45305
46437
|
}
|
|
45306
46438
|
return null;
|
|
45307
46439
|
}
|
|
45308
|
-
const cacheRoot =
|
|
46440
|
+
const cacheRoot = dirname14(cacheDir());
|
|
45309
46441
|
const legacyPackageJsonPath = join23(cacheRoot, "package.json");
|
|
45310
46442
|
if (existsSync20(legacyPackageJsonPath)) {
|
|
45311
46443
|
return { installDir: cacheRoot, packageJsonPath: legacyPackageJsonPath };
|
|
@@ -45508,7 +46640,7 @@ function claimCheckSlot(storageDir, intervalMs) {
|
|
|
45508
46640
|
try {
|
|
45509
46641
|
if (hasRecentCheckTimestamp(file, intervalMs))
|
|
45510
46642
|
return null;
|
|
45511
|
-
mkdirSync9(
|
|
46643
|
+
mkdirSync9(dirname15(file), { recursive: true });
|
|
45512
46644
|
const lockPath = `${file}.lock`;
|
|
45513
46645
|
const owner = { pid: process.pid, startedMs: Date.now() };
|
|
45514
46646
|
let lockFd = null;
|
|
@@ -45579,9 +46711,9 @@ function isOrphanedCheckLock(lockPath) {
|
|
|
45579
46711
|
}
|
|
45580
46712
|
}
|
|
45581
46713
|
function readCheckLockState(lockPath) {
|
|
45582
|
-
const fallbackStartedMs =
|
|
46714
|
+
const fallbackStartedMs = statSync15(lockPath).mtimeMs;
|
|
45583
46715
|
try {
|
|
45584
|
-
const parsed = JSON.parse(
|
|
46716
|
+
const parsed = JSON.parse(readFileSync18(lockPath, "utf-8"));
|
|
45585
46717
|
const pid = Number.isSafeInteger(parsed.pid) && (parsed.pid ?? 0) > 0 ? parsed.pid ?? null : null;
|
|
45586
46718
|
const startedMs = typeof parsed.startedMs === "number" && Number.isFinite(parsed.startedMs) ? parsed.startedMs : fallbackStartedMs;
|
|
45587
46719
|
return { pid, startedMs };
|
|
@@ -45591,7 +46723,7 @@ function readCheckLockState(lockPath) {
|
|
|
45591
46723
|
}
|
|
45592
46724
|
function checkLockIsOwnedBy(lockPath, owner) {
|
|
45593
46725
|
try {
|
|
45594
|
-
const parsed = JSON.parse(
|
|
46726
|
+
const parsed = JSON.parse(readFileSync18(lockPath, "utf-8"));
|
|
45595
46727
|
return parsed.pid === owner.pid && parsed.startedMs === owner.startedMs;
|
|
45596
46728
|
} catch {
|
|
45597
46729
|
return false;
|
|
@@ -45601,7 +46733,7 @@ function hasRecentCheckTimestamp(file, intervalMs) {
|
|
|
45601
46733
|
if (!existsSync21(file))
|
|
45602
46734
|
return false;
|
|
45603
46735
|
try {
|
|
45604
|
-
const raw = JSON.parse(
|
|
46736
|
+
const raw = JSON.parse(readFileSync18(file, "utf-8"));
|
|
45605
46737
|
const last = typeof raw.lastCheckedMs === "number" ? raw.lastCheckedMs : 0;
|
|
45606
46738
|
return Number.isFinite(last) && Date.now() - last < intervalMs;
|
|
45607
46739
|
} catch {
|
|
@@ -45657,6 +46789,7 @@ async function runBackgroundUpdateCheck(ctx, options) {
|
|
|
45657
46789
|
const channel = extractChannel(pluginInfo.pinnedVersion ?? currentVersion);
|
|
45658
46790
|
const latestVersion = await getLatestVersion(channel, {
|
|
45659
46791
|
registryUrl: options.npmRegistryUrl,
|
|
46792
|
+
directory: ctx.directory,
|
|
45660
46793
|
timeoutMs: options.fetchTimeoutMs,
|
|
45661
46794
|
signal: options.signal
|
|
45662
46795
|
});
|
|
@@ -45797,13 +46930,13 @@ import {
|
|
|
45797
46930
|
existsSync as existsSync22,
|
|
45798
46931
|
mkdirSync as mkdirSync10,
|
|
45799
46932
|
readdirSync as readdirSync6,
|
|
45800
|
-
readFileSync as
|
|
46933
|
+
readFileSync as readFileSync19,
|
|
45801
46934
|
renameSync as renameSync12,
|
|
45802
46935
|
unlinkSync as unlinkSync8,
|
|
45803
46936
|
writeFileSync as writeFileSync13
|
|
45804
46937
|
} from "node:fs";
|
|
45805
46938
|
import { createServer } from "node:http";
|
|
45806
|
-
import { dirname as
|
|
46939
|
+
import { dirname as dirname16, join as join25 } from "node:path";
|
|
45807
46940
|
|
|
45808
46941
|
// src/shared/rpc-utils.ts
|
|
45809
46942
|
import { join as join24 } from "node:path";
|
|
@@ -45953,7 +47086,7 @@ class AftRpcServer {
|
|
|
45953
47086
|
}
|
|
45954
47087
|
}
|
|
45955
47088
|
writePortFile() {
|
|
45956
|
-
const dir =
|
|
47089
|
+
const dir = dirname16(this.portFilePath);
|
|
45957
47090
|
mkdirSync10(dir, { recursive: true, mode: 448 });
|
|
45958
47091
|
const tmpPath = `${this.portFilePath}.tmp`;
|
|
45959
47092
|
writeFileSync13(tmpPath, JSON.stringify({
|
|
@@ -45978,7 +47111,7 @@ class AftRpcServer {
|
|
|
45978
47111
|
if (filePath === this.portFilePath)
|
|
45979
47112
|
continue;
|
|
45980
47113
|
try {
|
|
45981
|
-
const record = parseRpcPortRecord(
|
|
47114
|
+
const record = parseRpcPortRecord(readFileSync19(filePath, "utf-8"));
|
|
45982
47115
|
if (record === null) {
|
|
45983
47116
|
unlinkSync8(filePath);
|
|
45984
47117
|
continue;
|
|
@@ -46193,7 +47326,7 @@ function formatSemanticIndexLabel(semantic) {
|
|
|
46193
47326
|
backendUrl: semantic.backend_url
|
|
46194
47327
|
});
|
|
46195
47328
|
}
|
|
46196
|
-
function
|
|
47329
|
+
function asRecord3(value) {
|
|
46197
47330
|
return typeof value === "object" && value !== null ? value : {};
|
|
46198
47331
|
}
|
|
46199
47332
|
function readString(value, fallback = "") {
|
|
@@ -46212,7 +47345,7 @@ function readOptionalNumber(value) {
|
|
|
46212
47345
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
46213
47346
|
}
|
|
46214
47347
|
function readCompressionAggregate(value) {
|
|
46215
|
-
const aggregate =
|
|
47348
|
+
const aggregate = asRecord3(value);
|
|
46216
47349
|
return {
|
|
46217
47350
|
events: readNumber(aggregate.events),
|
|
46218
47351
|
original_tokens: readNumber(aggregate.original_tokens),
|
|
@@ -46223,7 +47356,7 @@ function readCompressionAggregate(value) {
|
|
|
46223
47356
|
function readCompression(value) {
|
|
46224
47357
|
if (typeof value !== "object" || value === null)
|
|
46225
47358
|
return;
|
|
46226
|
-
const compression =
|
|
47359
|
+
const compression = asRecord3(value);
|
|
46227
47360
|
return {
|
|
46228
47361
|
project: readCompressionAggregate(compression.project),
|
|
46229
47362
|
session: readCompressionAggregate(compression.session)
|
|
@@ -46232,7 +47365,7 @@ function readCompression(value) {
|
|
|
46232
47365
|
function readStatusBar(value) {
|
|
46233
47366
|
if (typeof value !== "object" || value === null)
|
|
46234
47367
|
return;
|
|
46235
|
-
const bar =
|
|
47368
|
+
const bar = asRecord3(value);
|
|
46236
47369
|
const errors = readOptionalNumber(bar.errors);
|
|
46237
47370
|
const warnings = readOptionalNumber(bar.warnings);
|
|
46238
47371
|
const deadCode = readOptionalNumber(bar.dead_code);
|
|
@@ -46290,17 +47423,17 @@ function formatBytes(bytes) {
|
|
|
46290
47423
|
return `${value.toFixed(decimals)} ${units[unitIndex]}`;
|
|
46291
47424
|
}
|
|
46292
47425
|
function coerceAftStatus(response) {
|
|
46293
|
-
const features =
|
|
46294
|
-
const searchIndex =
|
|
46295
|
-
const semanticIndex =
|
|
47426
|
+
const features = asRecord3(response.features);
|
|
47427
|
+
const searchIndex = asRecord3(response.search_index);
|
|
47428
|
+
const semanticIndex = asRecord3(response.semantic_index);
|
|
46296
47429
|
const semanticConfig = {
|
|
46297
|
-
...
|
|
46298
|
-
...
|
|
47430
|
+
...asRecord3(response.semantic),
|
|
47431
|
+
...asRecord3(response.semantic_config)
|
|
46299
47432
|
};
|
|
46300
|
-
const disk =
|
|
46301
|
-
const symbolCache =
|
|
46302
|
-
const runtime =
|
|
46303
|
-
const session =
|
|
47433
|
+
const disk = asRecord3(response.disk);
|
|
47434
|
+
const symbolCache = asRecord3(response.symbol_cache);
|
|
47435
|
+
const runtime = asRecord3(response.runtime);
|
|
47436
|
+
const session = asRecord3(response.session);
|
|
46304
47437
|
return {
|
|
46305
47438
|
version: readString(response.version, "unknown"),
|
|
46306
47439
|
project_root: readNullableString(response.project_root),
|
|
@@ -46552,7 +47685,10 @@ function registerShutdownCleanup(fn) {
|
|
|
46552
47685
|
|
|
46553
47686
|
// src/workflow-hints.ts
|
|
46554
47687
|
var HEADING = "## IMPORTANT NOTICE about your tools";
|
|
46555
|
-
|
|
47688
|
+
function hashlineTagSourceHint(bashRewritesMintTags, isRegistered = () => true) {
|
|
47689
|
+
return `**Hashline edit tags**: ${hashlineTagSourceSentence(bashRewritesMintTags, isRegistered)}`;
|
|
47690
|
+
}
|
|
47691
|
+
var HASHLINE_TAG_SOURCE_HINT = hashlineTagSourceHint(true);
|
|
46556
47692
|
function buildWorkflowHints(opts) {
|
|
46557
47693
|
const sections = [];
|
|
46558
47694
|
const grepName = "grep";
|
|
@@ -46569,6 +47705,8 @@ function buildWorkflowHints(opts) {
|
|
|
46569
47705
|
const hasInspect = !opts.disabledTools.has("aft_inspect");
|
|
46570
47706
|
const hasBash = !opts.disabledTools.has(bashName);
|
|
46571
47707
|
const hasBgBash = hasBash && opts.bashBackgroundEnabled && !opts.disabledTools.has(bashStatusName);
|
|
47708
|
+
const hasWatch = !opts.disabledTools.has("bash_watch");
|
|
47709
|
+
const hasWrite = !opts.disabledTools.has(bashWriteName);
|
|
46572
47710
|
if (hasBash && opts.bashCompressionEnabled) {
|
|
46573
47711
|
sections.push([
|
|
46574
47712
|
"**Test/build output**: bash output is auto-compressed for non-piped commands. Piped commands run verbatim and show the pipeline's output. For AFT's test/build summary, run the runner without filters:",
|
|
@@ -46610,17 +47748,19 @@ function buildWorkflowHints(opts) {
|
|
|
46610
47748
|
].join(`
|
|
46611
47749
|
`));
|
|
46612
47750
|
}
|
|
46613
|
-
if (
|
|
47751
|
+
if (hasBgBash && hasWatch) {
|
|
46614
47752
|
sections.push([
|
|
46615
47753
|
`**Long-running commands** (builds, installs, full test suites): run them in the FOREGROUND — use \`${bashName}({ command, wait: true })\` when you know it is long and need the result before anything else; if you send a new message, the wait detaches to background; otherwise omit \`wait\` so auto-promote can hand you a reminder while you work.`,
|
|
46616
47754
|
"- `background: true` is ONLY for when you have OTHER useful work to do while it runs: start it, do the other work, and the completion reminder delivers the result (or spawn a subagent for the side work). Do NOT background a command and then immediately `bash_watch` it — that spends a whole extra turn waiting for something foreground returns in one.",
|
|
46617
47755
|
"- `bash_watch` is for blocking on an ALREADY-backgrounded task once you've run out of parallel work (sync — the user can interrupt), or reacting to a specific early output line (async: background:true + pattern). Never loop `bash_status` to wait — it's a one-shot inspector."
|
|
46618
47756
|
].join(`
|
|
46619
47757
|
`));
|
|
47758
|
+
}
|
|
47759
|
+
if (hasBgBash && hasWrite) {
|
|
46620
47760
|
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: "..." })\`.`);
|
|
46621
47761
|
}
|
|
46622
47762
|
if (opts.hashlineEffective === true) {
|
|
46623
|
-
sections.push(
|
|
47763
|
+
sections.push(hashlineTagSourceHint(hasBash && opts.bashRewriteEnabled !== false, (name) => !opts.disabledTools.has(name)));
|
|
46624
47764
|
}
|
|
46625
47765
|
if (sections.length === 0) {
|
|
46626
47766
|
return null;
|
|
@@ -46635,13 +47775,32 @@ ${sections.join(`
|
|
|
46635
47775
|
`)}`;
|
|
46636
47776
|
}
|
|
46637
47777
|
function buildHintsFromConfig(config, disabledTools, hashlineEffective = false) {
|
|
47778
|
+
const bashCfg = resolveBashConfig(config);
|
|
46638
47779
|
return buildWorkflowHints({
|
|
46639
|
-
bashBackgroundEnabled:
|
|
46640
|
-
bashCompressionEnabled:
|
|
47780
|
+
bashBackgroundEnabled: bashCfg.background,
|
|
47781
|
+
bashCompressionEnabled: bashCfg.compress,
|
|
47782
|
+
bashRewriteEnabled: bashCfg.rewrite,
|
|
46641
47783
|
disabledTools,
|
|
46642
47784
|
hashlineEffective
|
|
46643
47785
|
});
|
|
46644
47786
|
}
|
|
47787
|
+
var WORKFLOW_HINT_TOOL_NAMES = [
|
|
47788
|
+
"aft_outline",
|
|
47789
|
+
"aft_zoom",
|
|
47790
|
+
"aft_search",
|
|
47791
|
+
"aft_callgraph",
|
|
47792
|
+
"aft_inspect",
|
|
47793
|
+
"grep",
|
|
47794
|
+
"read",
|
|
47795
|
+
"bash",
|
|
47796
|
+
"bash_status",
|
|
47797
|
+
"bash_watch",
|
|
47798
|
+
"bash_write"
|
|
47799
|
+
];
|
|
47800
|
+
function buildHintsForRegisteredTools(config, registeredTools, hashlineEffective = false) {
|
|
47801
|
+
const absent = new Set(WORKFLOW_HINT_TOOL_NAMES.filter((name) => !registeredTools.has(name)));
|
|
47802
|
+
return buildHintsFromConfig(config, absent, hashlineEffective);
|
|
47803
|
+
}
|
|
46645
47804
|
function appendHintsToSystem(system, hintsBlock) {
|
|
46646
47805
|
if (!hintsBlock)
|
|
46647
47806
|
return;
|
|
@@ -46841,6 +48000,16 @@ async function initializePluginForDirectory(input) {
|
|
|
46841
48000
|
storageDir: configOverrides.storage_dir,
|
|
46842
48001
|
isProjectEnabled
|
|
46843
48002
|
};
|
|
48003
|
+
const liveConfigReload = startOpenCodeLiveConfigReload({
|
|
48004
|
+
directory: registrationRoot,
|
|
48005
|
+
initialSources: bootstrap.sources ?? [],
|
|
48006
|
+
initialSourceTexts: bootstrap.sourceTexts,
|
|
48007
|
+
getConfig: () => ctx.config,
|
|
48008
|
+
setConfig: (config) => {
|
|
48009
|
+
ctx.config = config;
|
|
48010
|
+
},
|
|
48011
|
+
notify: (message) => deliverConfigMigrationWarnings(registrationRoot, [message])
|
|
48012
|
+
});
|
|
46844
48013
|
const statusSubscribedBridges = new WeakSet;
|
|
46845
48014
|
const statusUnsubscribes = new Set;
|
|
46846
48015
|
const subscribeBridgeStatus = (bridge) => {
|
|
@@ -46878,6 +48047,7 @@ async function initializePluginForDirectory(input) {
|
|
|
46878
48047
|
let clearInspectTier2Idle = () => {};
|
|
46879
48048
|
const shutdownCleanup = registerShutdownCleanup(async (reason) => {
|
|
46880
48049
|
autoUpdateAbort.abort();
|
|
48050
|
+
liveConfigReload.stop();
|
|
46881
48051
|
clearInspectTier2Idle();
|
|
46882
48052
|
for (const unsubscribe of statusUnsubscribes) {
|
|
46883
48053
|
try {
|
|
@@ -47001,42 +48171,19 @@ Install: ${getManualInstallHint()}`);
|
|
|
47001
48171
|
signal: autoUpdateAbort.signal,
|
|
47002
48172
|
storageDir: ctx.storageDir
|
|
47003
48173
|
});
|
|
47004
|
-
const HINTS_TOOL_NAMES = [
|
|
47005
|
-
"aft_outline",
|
|
47006
|
-
"aft_zoom",
|
|
47007
|
-
"aft_search",
|
|
47008
|
-
"aft_callgraph",
|
|
47009
|
-
"aft_inspect",
|
|
47010
|
-
"grep",
|
|
47011
|
-
"read",
|
|
47012
|
-
"bash",
|
|
47013
|
-
"bash_status"
|
|
47014
|
-
];
|
|
47015
48174
|
const registeredTools = new Set(Object.keys(allTools));
|
|
47016
48175
|
const { hashlineEditRegistered, aftSearchRegistered } = applyToolSurfaceOverrides(pool, aftConfig, registeredTools);
|
|
47017
48176
|
ctx.hashlineEffective = hashlineEditRegistered;
|
|
47018
48177
|
const hashlineDowngrade = reportHashlineDowngrade(aftConfig, registeredTools, (message) => deliverConfigMigrationWarnings(registrationRoot, [message]));
|
|
47019
48178
|
log2(`hashline activation decision requested=${aftConfig.edit_mode === "hashline"} ` + `edit_slot_survives=${hashlineEditRegistered} effective=${ctx.hashlineEffective}` + (hashlineDowngrade ? ` downgraded=${hashlineDowngrade.code}` : ""));
|
|
47020
48179
|
ctx.aftSearchRegistered = aftSearchRegistered;
|
|
47021
|
-
|
|
47022
|
-
const def = allTools[name];
|
|
47023
|
-
if (def) {
|
|
47024
|
-
const bashCfg = resolveBashConfig(aftConfig);
|
|
47025
|
-
def.description = bashToolDescription(aftSearchRegistered, bashCfg.compress, bashCfg.background, bashCfg.detach_on_user_message);
|
|
47026
|
-
}
|
|
47027
|
-
}
|
|
47028
|
-
const hintsAbsentTools = new Set;
|
|
47029
|
-
for (const name of HINTS_TOOL_NAMES) {
|
|
47030
|
-
if (!registeredTools.has(name))
|
|
47031
|
-
hintsAbsentTools.add(name);
|
|
47032
|
-
}
|
|
47033
|
-
const hintsBlock = buildHintsFromConfig(aftConfig, hintsAbsentTools, hashlineEditRegistered);
|
|
48180
|
+
const hintsBlock = buildHintsForRegisteredTools(aftConfig, registeredTools, hashlineEditRegistered);
|
|
47034
48181
|
if (hintsBlock) {
|
|
47035
48182
|
log2(`Workflow hints injected (${hintsBlock.length} chars)`);
|
|
47036
48183
|
}
|
|
47037
48184
|
const inspectTier2Idle = createInspectTier2IdleScheduler({
|
|
47038
48185
|
isEnabled: () => registeredTools.has("aft_inspect"),
|
|
47039
|
-
idleMinutes: () =>
|
|
48186
|
+
idleMinutes: () => ctx.config.inspect?.tier2_idle_minutes,
|
|
47040
48187
|
warn: warn2,
|
|
47041
48188
|
run: async (sessionID) => {
|
|
47042
48189
|
const sessionDir = await getSessionDirectory(input.client, sessionID, input.directory) ?? input.directory;
|
|
@@ -47094,11 +48241,17 @@ Install: ${getManualInstallHint()}`);
|
|
|
47094
48241
|
storageDir: configOverrides.storage_dir,
|
|
47095
48242
|
pluginVersion: PLUGIN_VERSION,
|
|
47096
48243
|
serverUrl: input.serverUrl?.toString(),
|
|
47097
|
-
delivery:
|
|
48244
|
+
delivery: ctx.config.configure_warnings_delivery ?? "toast"
|
|
47098
48245
|
});
|
|
47099
48246
|
}
|
|
47100
48247
|
await flushConfigureWarningsOnIdle(sessionID);
|
|
47101
48248
|
},
|
|
48249
|
+
"chat.params": async (params) => {
|
|
48250
|
+
rememberReadModel(input.client, params.sessionID, {
|
|
48251
|
+
providerID: params.model.providerID,
|
|
48252
|
+
modelID: params.model.id
|
|
48253
|
+
});
|
|
48254
|
+
},
|
|
47102
48255
|
"chat.message": async (messageInput, messageOutput) => {
|
|
47103
48256
|
const sid = messageInput.sessionID ?? messageInput.sessionId ?? messageInput.id;
|
|
47104
48257
|
warmSessionDirectory(input.client, sid, input.directory);
|