@cortexkit/aft-opencode 0.58.1 → 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/config-live-reload.d.ts +8 -0
- package/dist/config-live-reload.d.ts.map +1 -1
- package/dist/config.d.ts +13 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/entry/server.js +1603 -839
- package/dist/hashline-tag-sources.d.ts +18 -0
- package/dist/hashline-tag-sources.d.ts.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +980 -605
- 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 +10 -0
- 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/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-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 +4 -4
- 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/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 +21 -5
- package/src/shared/session-directory.ts +92 -0
- package/src/shared/subagent-detect.ts +89 -28
package/dist/index.js
CHANGED
|
@@ -7878,29 +7878,84 @@ function isTerminalStatus(status) {
|
|
|
7878
7878
|
function sleep(ms) {
|
|
7879
7879
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
7880
7880
|
}
|
|
7881
|
-
function monotonicNowMs() {
|
|
7882
|
-
return performance.now();
|
|
7883
|
-
}
|
|
7884
7881
|
// ../aft-bridge/dist/bash-hints.js
|
|
7885
7882
|
import * as fs from "node:fs";
|
|
7886
7883
|
import * as os from "node:os";
|
|
7887
7884
|
import * as path from "node:path";
|
|
7888
7885
|
var DEFAULT_PRIMARY_WATCH_TIMEOUT_MS = 30000;
|
|
7889
|
-
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.";
|
|
7890
7890
|
function resolveWatchTimeoutMs(requestedMs, role, capMs) {
|
|
7891
|
-
|
|
7892
|
-
|
|
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
|
+
});
|
|
7893
7923
|
}
|
|
7894
7924
|
function formatWatchWaited(elapsedMs, limitMs, capMs) {
|
|
7895
|
-
|
|
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`;
|
|
7896
7932
|
return `Waited ${Math.round(elapsedMs)}ms (${limit})`;
|
|
7897
7933
|
}
|
|
7898
|
-
function watchTimeoutSteer(role,
|
|
7934
|
+
function watchTimeoutSteer(role, timeoutParam = "timeoutMs") {
|
|
7899
7935
|
if (role === "worker") {
|
|
7900
|
-
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.`;
|
|
7901
7937
|
}
|
|
7902
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.";
|
|
7903
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
|
+
}
|
|
7904
7959
|
var CONFLICT_HINT = `
|
|
7905
7960
|
|
|
7906
7961
|
[Hint] Use aft_conflicts to see all conflict regions across files in a single call.`;
|
|
@@ -8759,6 +8814,9 @@ function isPassiveCommand(command) {
|
|
|
8759
8814
|
return PASSIVE_COMMANDS.has(command);
|
|
8760
8815
|
}
|
|
8761
8816
|
|
|
8817
|
+
// ../aft-bridge/dist/transport.js
|
|
8818
|
+
var WORKER_SESSION_FIELD = "worker_session";
|
|
8819
|
+
|
|
8762
8820
|
// ../aft-bridge/dist/bridge.js
|
|
8763
8821
|
var DEFAULT_BRIDGE_TIMEOUT_MS = 30000;
|
|
8764
8822
|
var BRIDGE_HANG_TIMEOUT_THRESHOLD = 2;
|
|
@@ -9177,9 +9235,11 @@ class BinaryBridge {
|
|
|
9177
9235
|
params.edit_slot_survives = editSlotSurvives;
|
|
9178
9236
|
this.logHashlineRegistrationCarrier("tool_call", sessionId, editSlotSurvives);
|
|
9179
9237
|
}
|
|
9180
|
-
const { preview, ...sendOptions } = options ?? {};
|
|
9238
|
+
const { preview, workerSession, ...sendOptions } = options ?? {};
|
|
9181
9239
|
if (preview === true)
|
|
9182
9240
|
params.preview = true;
|
|
9241
|
+
if (workerSession === true)
|
|
9242
|
+
params[WORKER_SESSION_FIELD] = true;
|
|
9183
9243
|
return await this.send("tool_call", params, Object.keys(sendOptions).length > 0 ? sendOptions : undefined);
|
|
9184
9244
|
}
|
|
9185
9245
|
async sendWithVersionMismatchRetry(command, params, options, canRetryAfterVersionSwap) {
|
|
@@ -9248,7 +9308,7 @@ class BinaryBridge {
|
|
|
9248
9308
|
if (command === "cancel_request" || Object.hasOwn(params, "command") || Object.hasOwn(params, "method")) {
|
|
9249
9309
|
const nested = { ...params };
|
|
9250
9310
|
const reserved = {};
|
|
9251
|
-
for (const key of ["session_id", "lsp_hints"]) {
|
|
9311
|
+
for (const key of ["session_id", "lsp_hints", WORKER_SESSION_FIELD]) {
|
|
9252
9312
|
if (Object.hasOwn(nested, key)) {
|
|
9253
9313
|
reserved[key] = nested[key];
|
|
9254
9314
|
delete nested[key];
|
|
@@ -10026,6 +10086,35 @@ function coerceJsonCollectionParam(value, field) {
|
|
|
10026
10086
|
}
|
|
10027
10087
|
throw new Error(`${field} must be ${shape} (got an invalid type)`);
|
|
10028
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
|
+
}
|
|
10029
10118
|
// ../aft-bridge/dist/config-error.js
|
|
10030
10119
|
import { homedir as homedir5 } from "node:os";
|
|
10031
10120
|
import { isAbsolute as isAbsolute2, join as join6 } from "node:path";
|
|
@@ -13288,6 +13377,13 @@ class SubcTransportShuttingDownError extends SubcCallError {
|
|
|
13288
13377
|
this.name = "SubcTransportShuttingDownError";
|
|
13289
13378
|
}
|
|
13290
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
|
+
}
|
|
13291
13387
|
function isSubcClientClosedError(error) {
|
|
13292
13388
|
return error instanceof SubcError && error.message === "client closed";
|
|
13293
13389
|
}
|
|
@@ -13790,6 +13886,8 @@ class SubcTransport {
|
|
|
13790
13886
|
body.edit_slot_survives = editSlotSurvives;
|
|
13791
13887
|
if (preview === true)
|
|
13792
13888
|
body.preview = true;
|
|
13889
|
+
if (options?.workerSession === true)
|
|
13890
|
+
body[WORKER_SESSION_FIELD] = true;
|
|
13793
13891
|
const reply = await this.pool.routeRequest(this.identityFor(sessionId), body, timeoutMs, onProgress, this.generation, abortSignal);
|
|
13794
13892
|
return reliftReply(reply);
|
|
13795
13893
|
}
|
|
@@ -13800,7 +13898,10 @@ class SubcTransport {
|
|
|
13800
13898
|
}
|
|
13801
13899
|
const { timeoutMs, onProgress, abortSignal } = this.splitOptions(options);
|
|
13802
13900
|
const session = typeof params.session_id === "string" ? params.session_id : undefined;
|
|
13803
|
-
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;
|
|
13804
13905
|
const editSlotSurvives = this.pool.getEditSlotSurvives();
|
|
13805
13906
|
if (editSlotSurvives !== undefined)
|
|
13806
13907
|
body.edit_slot_survives = editSlotSurvives;
|
|
@@ -13837,6 +13938,7 @@ class SubcTransportPool {
|
|
|
13837
13938
|
lifecycleRegistration = null;
|
|
13838
13939
|
outerFacadeEvictor;
|
|
13839
13940
|
client = null;
|
|
13941
|
+
hadWorkingClient = false;
|
|
13840
13942
|
connecting = null;
|
|
13841
13943
|
routeReopenRetryDelayMs = RECONNECT_RETRY_FLOOR_MS;
|
|
13842
13944
|
routeReopenRetry = null;
|
|
@@ -14281,6 +14383,10 @@ class SubcTransportPool {
|
|
|
14281
14383
|
}
|
|
14282
14384
|
async routeRequest(identity, body, timeoutMs, onProgress, expectedGeneration, abortSignal) {
|
|
14283
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();
|
|
14284
14390
|
const root = asCanonicalRootPath(identity.project_root);
|
|
14285
14391
|
let generation = expectedGeneration;
|
|
14286
14392
|
if (this.lifecycleEnabled()) {
|
|
@@ -14296,7 +14402,49 @@ class SubcTransportPool {
|
|
|
14296
14402
|
try {
|
|
14297
14403
|
let client;
|
|
14298
14404
|
try {
|
|
14299
|
-
|
|
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
|
+
}
|
|
14300
14448
|
this.assertRecordLive(record);
|
|
14301
14449
|
} catch (error) {
|
|
14302
14450
|
throw this.annotateReapError(error, record);
|
|
@@ -14331,12 +14479,8 @@ class SubcTransportPool {
|
|
|
14331
14479
|
throw error;
|
|
14332
14480
|
}
|
|
14333
14481
|
};
|
|
14334
|
-
const callDeadlineMs = timeoutMs ?? SUBC_DEFAULT_REQUEST_TIMEOUT_MS;
|
|
14335
14482
|
const reloadWaitBudgetMs = Math.min(callDeadlineMs, ROUTE_OPEN_RELOAD_WAIT_CEILING_MS);
|
|
14336
|
-
let scheduledDelayMs = 0;
|
|
14337
14483
|
let lastRefusal;
|
|
14338
|
-
const spentMs = () => Math.max(performance.now() - callStartedAt, scheduledDelayMs);
|
|
14339
|
-
const remainingMs = () => callDeadlineMs - spentMs();
|
|
14340
14484
|
const openRouteWithinDeadline = async () => {
|
|
14341
14485
|
const remaining = remainingMs();
|
|
14342
14486
|
if (remaining <= 0)
|
|
@@ -14539,10 +14683,14 @@ class SubcTransportPool {
|
|
|
14539
14683
|
throw new SubcTransportShuttingDownError;
|
|
14540
14684
|
}
|
|
14541
14685
|
this.client = client;
|
|
14686
|
+
this.hadWorkingClient = true;
|
|
14542
14687
|
this.transportFailures = 0;
|
|
14543
14688
|
return client;
|
|
14544
14689
|
}).catch((error) => {
|
|
14545
14690
|
this.connecting = null;
|
|
14691
|
+
if (this.hadWorkingClient && error instanceof AuthError) {
|
|
14692
|
+
throw new ReconnectKeyChangedError;
|
|
14693
|
+
}
|
|
14546
14694
|
throw error;
|
|
14547
14695
|
});
|
|
14548
14696
|
return this.connecting;
|
|
@@ -16234,6 +16382,7 @@ async function acquireBridge(locationDirectory, options, dependencies = {}) {
|
|
|
16234
16382
|
let owner = state.standalone;
|
|
16235
16383
|
if (!owner) {
|
|
16236
16384
|
const configByRoot = new Map;
|
|
16385
|
+
const versionMismatchHandlers = new Map;
|
|
16237
16386
|
const configuredLoader = options.poolOptions.projectConfigLoader;
|
|
16238
16387
|
const pool = await createPool({
|
|
16239
16388
|
...options,
|
|
@@ -16243,21 +16392,29 @@ async function acquireBridge(locationDirectory, options, dependencies = {}) {
|
|
|
16243
16392
|
projectConfigLoader: (projectRoot) => ({
|
|
16244
16393
|
...configuredLoader?.(projectRoot) ?? {},
|
|
16245
16394
|
...configByRoot.get(canonicalizeProjectRoot(projectRoot)) ?? {}
|
|
16246
|
-
})
|
|
16395
|
+
}),
|
|
16396
|
+
onVersionMismatch: (binaryVersion, minVersion) => [...versionMismatchHandlers.values()].at(-1)?.(binaryVersion, minVersion) ?? null
|
|
16247
16397
|
}
|
|
16248
16398
|
});
|
|
16249
|
-
owner = {
|
|
16399
|
+
owner = {
|
|
16400
|
+
pool,
|
|
16401
|
+
configByRoot,
|
|
16402
|
+
versionMismatchHandlers,
|
|
16403
|
+
referencesByRoot: new Map,
|
|
16404
|
+
references: 0
|
|
16405
|
+
};
|
|
16250
16406
|
state.standalone = owner;
|
|
16251
16407
|
}
|
|
16252
16408
|
owner.references += 1;
|
|
16253
16409
|
owner.referencesByRoot.set(directory, (owner.referencesByRoot.get(directory) ?? 0) + 1);
|
|
16254
16410
|
owner.configByRoot.set(directory, { ...options.configOverrides });
|
|
16255
16411
|
const acquiredOwner = owner;
|
|
16256
|
-
|
|
16412
|
+
const lease = new LocationBridgeLease(directory, mode, owner.pool, async () => {
|
|
16257
16413
|
await withLock(directory, () => withLock(OWNER_LOCK, async () => {
|
|
16258
16414
|
const current = lifecycleState().standalone;
|
|
16259
16415
|
if (current !== acquiredOwner)
|
|
16260
16416
|
return;
|
|
16417
|
+
current.versionMismatchHandlers?.delete(lease);
|
|
16261
16418
|
current.references -= 1;
|
|
16262
16419
|
const rootReferences = (current.referencesByRoot.get(directory) ?? 1) - 1;
|
|
16263
16420
|
if (rootReferences > 0)
|
|
@@ -16272,6 +16429,10 @@ async function acquireBridge(locationDirectory, options, dependencies = {}) {
|
|
|
16272
16429
|
await current.pool.shutdown();
|
|
16273
16430
|
}));
|
|
16274
16431
|
});
|
|
16432
|
+
const onVersionMismatch = options.poolOptions.onVersionMismatch;
|
|
16433
|
+
if (onVersionMismatch)
|
|
16434
|
+
owner.versionMismatchHandlers?.set(lease, onVersionMismatch);
|
|
16435
|
+
return lease;
|
|
16275
16436
|
});
|
|
16276
16437
|
});
|
|
16277
16438
|
}
|
|
@@ -16292,6 +16453,14 @@ var STANDALONE_DETACH_KEYWORDS_PATTERN = new RegExp(STANDALONE_DETACH_KEYWORD_SO
|
|
|
16292
16453
|
function containsStandaloneDetachKeyword(messageText) {
|
|
16293
16454
|
return STANDALONE_DETACH_KEYWORD_PATTERN.test(messageText);
|
|
16294
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
|
+
}
|
|
16295
16464
|
function stripStandaloneDetachKeywords(messageText) {
|
|
16296
16465
|
return messageText.replace(STANDALONE_DETACH_KEYWORDS_PATTERN, "$1");
|
|
16297
16466
|
}
|
|
@@ -17683,31 +17852,36 @@ var ORT_PLATFORM_MAP = {
|
|
|
17683
17852
|
arm64: {
|
|
17684
17853
|
assetName: `onnxruntime-osx-arm64-${ORT_VERSION}`,
|
|
17685
17854
|
libName: "libonnxruntime.dylib",
|
|
17686
|
-
archiveType: "tgz"
|
|
17855
|
+
archiveType: "tgz",
|
|
17856
|
+
librarySha256: "872533f130f1839a5bc01788ddb4f75c83a189763441ba1178788ed965449289"
|
|
17687
17857
|
}
|
|
17688
17858
|
},
|
|
17689
17859
|
linux: {
|
|
17690
17860
|
x64: {
|
|
17691
17861
|
assetName: `onnxruntime-linux-x64-${ORT_VERSION}`,
|
|
17692
17862
|
libName: "libonnxruntime.so",
|
|
17693
|
-
archiveType: "tgz"
|
|
17863
|
+
archiveType: "tgz",
|
|
17864
|
+
librarySha256: "d132535d051344ff5c64c9c200004150559049a81ed330eb4422c1962fb6b7e4"
|
|
17694
17865
|
},
|
|
17695
17866
|
arm64: {
|
|
17696
17867
|
assetName: `onnxruntime-linux-aarch64-${ORT_VERSION}`,
|
|
17697
17868
|
libName: "libonnxruntime.so",
|
|
17698
|
-
archiveType: "tgz"
|
|
17869
|
+
archiveType: "tgz",
|
|
17870
|
+
librarySha256: "52b2a0e75e79468404284fec38ad5ee1a7a996232274f5e1b84f3e793fb07554"
|
|
17699
17871
|
}
|
|
17700
17872
|
},
|
|
17701
17873
|
win32: {
|
|
17702
17874
|
x64: {
|
|
17703
17875
|
assetName: `onnxruntime-win-x64-${ORT_VERSION}`,
|
|
17704
17876
|
libName: "onnxruntime.dll",
|
|
17705
|
-
archiveType: "zip"
|
|
17877
|
+
archiveType: "zip",
|
|
17878
|
+
librarySha256: "b95efb2113b603bbbf3f191061c5516a871ed546893c820e4f3b7b6c358dbf2a"
|
|
17706
17879
|
},
|
|
17707
17880
|
arm64: {
|
|
17708
17881
|
assetName: `onnxruntime-win-arm64-${ORT_VERSION}`,
|
|
17709
17882
|
libName: "onnxruntime.dll",
|
|
17710
|
-
archiveType: "zip"
|
|
17883
|
+
archiveType: "zip",
|
|
17884
|
+
librarySha256: "724d81ac50b11bfaa01ab3ce01b99fb6734a4b762259a8be4395ca662ce99fe6"
|
|
17711
17885
|
}
|
|
17712
17886
|
}
|
|
17713
17887
|
};
|
|
@@ -18197,6 +18371,9 @@ async function downloadOnnxRuntime(info, targetDir) {
|
|
|
18197
18371
|
copyOnnxLibraries(info, extractedDir, stagedInstallDir, realFiles, symlinks);
|
|
18198
18372
|
const libPath = join14(stagedInstallDir, info.libName);
|
|
18199
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
|
+
}
|
|
18200
18377
|
writeOnnxInstalledMeta(stagedInstallDir, ORT_VERSION, libHash, archiveSha256);
|
|
18201
18378
|
publishOnnxRuntime(stagedInstallDir, targetDir);
|
|
18202
18379
|
rmSync3(tmpDir, { recursive: true, force: true });
|
|
@@ -18539,12 +18716,13 @@ function normalizeZoomTargets(record) {
|
|
|
18539
18716
|
throw new InvalidRequestError(`'targets[${index}].path' must be a non-empty string`);
|
|
18540
18717
|
}
|
|
18541
18718
|
const source = target;
|
|
18542
|
-
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);
|
|
18543
18721
|
if (emptyTarget)
|
|
18544
18722
|
return { ...source };
|
|
18545
18723
|
const normalized = { ...source };
|
|
18546
18724
|
try {
|
|
18547
|
-
normalizeAliasPair(normalized, "path", "filePath",
|
|
18725
|
+
normalizeAliasPair(normalized, "path", "filePath", false);
|
|
18548
18726
|
} catch (error) {
|
|
18549
18727
|
if (error instanceof InvalidRequestError) {
|
|
18550
18728
|
throw new InvalidRequestError(error.message.replace("'filePath'", `'targets[${index}].filePath'`).replace("'path'", `'targets[${index}].path'`));
|
|
@@ -23990,6 +24168,10 @@ var SubcConfigSchema = object({
|
|
|
23990
24168
|
connection_file: string2().optional(),
|
|
23991
24169
|
client_reaper: boolean2().optional()
|
|
23992
24170
|
});
|
|
24171
|
+
var OpenCodeHostConfigSchema = object({
|
|
24172
|
+
server_url: string2().optional(),
|
|
24173
|
+
server_password_env: string2().optional()
|
|
24174
|
+
});
|
|
23993
24175
|
var GhShimConfigSchema = object({
|
|
23994
24176
|
binary_path: string2().trim().refine(isAbsolute7, "gh_shim.binary_path must be absolute").optional()
|
|
23995
24177
|
});
|
|
@@ -24079,6 +24261,7 @@ var AftConfigFieldsSchema = object({
|
|
|
24079
24261
|
auto_update: boolean2().optional(),
|
|
24080
24262
|
bridge: BridgeConfigSchema.optional(),
|
|
24081
24263
|
subc: SubcConfigSchema.optional(),
|
|
24264
|
+
opencode: OpenCodeHostConfigSchema.optional(),
|
|
24082
24265
|
github: GithubConfigSchema.optional(),
|
|
24083
24266
|
gh_shim: GhShimConfigSchema.optional(),
|
|
24084
24267
|
git: GitConfigSchema.optional(),
|
|
@@ -24668,6 +24851,8 @@ function getStrippedTopLevelKeys(override) {
|
|
|
24668
24851
|
stripped.push("sandbox.write_allow");
|
|
24669
24852
|
if (override.subc !== undefined)
|
|
24670
24853
|
stripped.push("subc");
|
|
24854
|
+
if (override.opencode !== undefined)
|
|
24855
|
+
stripped.push("opencode");
|
|
24671
24856
|
if (override.github !== undefined)
|
|
24672
24857
|
stripped.push("github");
|
|
24673
24858
|
if (override.gh_shim !== undefined)
|
|
@@ -38527,6 +38712,14 @@ function formatDuration(completion) {
|
|
|
38527
38712
|
}
|
|
38528
38713
|
|
|
38529
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;
|
|
38530
38723
|
var CACHE_MAX_ENTRIES = 200;
|
|
38531
38724
|
var cache = new Map;
|
|
38532
38725
|
async function getSessionDirectory(client, sessionId, fallbackDirectory) {
|
|
@@ -38538,6 +38731,8 @@ async function getSessionDirectory(client, sessionId, fallbackDirectory) {
|
|
|
38538
38731
|
cache.set(sessionId, cached);
|
|
38539
38732
|
return cached.directory;
|
|
38540
38733
|
}
|
|
38734
|
+
if (isV2PluginContext(client))
|
|
38735
|
+
return lookupV2SessionDirectory(client, sessionId);
|
|
38541
38736
|
const c = client;
|
|
38542
38737
|
const sessionApi = c?.session;
|
|
38543
38738
|
if (!sessionApi || typeof sessionApi.get !== "function") {
|
|
@@ -38558,6 +38753,25 @@ async function getSessionDirectory(client, sessionId, fallbackDirectory) {
|
|
|
38558
38753
|
setCache(sessionId, dir);
|
|
38559
38754
|
return dir;
|
|
38560
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
|
+
}
|
|
38561
38775
|
function setCache(sessionId, directory) {
|
|
38562
38776
|
if (cache.has(sessionId))
|
|
38563
38777
|
cache.delete(sessionId);
|
|
@@ -38616,6 +38830,108 @@ async function verifySessionDirectory(client, sessionId) {
|
|
|
38616
38830
|
return dir;
|
|
38617
38831
|
}
|
|
38618
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
|
+
|
|
38619
38935
|
// src/tool-perf.ts
|
|
38620
38936
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
38621
38937
|
var perfStore = new AsyncLocalStorage;
|
|
@@ -38691,7 +39007,7 @@ function projectRootFor(runtime) {
|
|
|
38691
39007
|
if (typeof cached === "string" && cached.length > 0) {
|
|
38692
39008
|
return canonicalizeDirectory(cached);
|
|
38693
39009
|
}
|
|
38694
|
-
const raw = runtime.worktree ?? runtime.directory;
|
|
39010
|
+
const raw = runtime.directoryIsSessionRoot ? runtime.directory : runtime.worktree ?? runtime.directory;
|
|
38695
39011
|
return canonicalizeDirectory(raw);
|
|
38696
39012
|
}
|
|
38697
39013
|
async function resolveProjectRoot(ctx, runtime) {
|
|
@@ -38727,6 +39043,11 @@ function bridgeFor(ctx, runtime) {
|
|
|
38727
39043
|
}
|
|
38728
39044
|
return ctx.pool.getBridge(projectRoot);
|
|
38729
39045
|
}
|
|
39046
|
+
async function isWorkerRuntime(ctx, runtime) {
|
|
39047
|
+
if (!runtime.sessionID)
|
|
39048
|
+
return false;
|
|
39049
|
+
return resolveIsSubagent(ctx.client, runtime.sessionID, runtime.directory);
|
|
39050
|
+
}
|
|
38730
39051
|
async function callBridge(ctx, runtime, command, params = {}, options) {
|
|
38731
39052
|
if (runtime.sessionID && getSessionDirectoryCached(runtime.sessionID) === undefined) {
|
|
38732
39053
|
await getSessionDirectory(ctx.client, runtime.sessionID, runtime.directory);
|
|
@@ -38735,6 +39056,9 @@ async function callBridge(ctx, runtime, command, params = {}, options) {
|
|
|
38735
39056
|
if (runtime.sessionID) {
|
|
38736
39057
|
merged.session_id = runtime.sessionID;
|
|
38737
39058
|
}
|
|
39059
|
+
if (await isWorkerRuntime(ctx, runtime)) {
|
|
39060
|
+
merged[WORKER_SESSION_FIELD] = true;
|
|
39061
|
+
}
|
|
38738
39062
|
const timeoutMs = timeoutForCommand(command);
|
|
38739
39063
|
const sendOptions = {
|
|
38740
39064
|
...timeoutMs !== undefined ? { timeoutMs } : {},
|
|
@@ -38761,10 +39085,12 @@ async function callToolCall(ctx, runtime, name, rawArgs = {}, options) {
|
|
|
38761
39085
|
await getSessionDirectory(ctx.client, runtime.sessionID, runtime.directory);
|
|
38762
39086
|
}
|
|
38763
39087
|
const timeoutMs = timeoutForCommand(name);
|
|
39088
|
+
const workerSession = await isWorkerRuntime(ctx, runtime);
|
|
38764
39089
|
const sendOptions = {
|
|
38765
39090
|
...timeoutMs !== undefined ? { timeoutMs } : {},
|
|
38766
39091
|
configureWarningClient: ctx.client,
|
|
38767
39092
|
...runtime.effectAbort ? { abortSignal: runtime.effectAbort } : {},
|
|
39093
|
+
...workerSession ? { workerSession: true } : {},
|
|
38768
39094
|
...options
|
|
38769
39095
|
};
|
|
38770
39096
|
markBridgeStart();
|
|
@@ -39315,7 +39641,8 @@ var NPM_LSP_TABLE = [
|
|
|
39315
39641
|
npm: "typescript",
|
|
39316
39642
|
binary: "tsserver",
|
|
39317
39643
|
extensions: ["ts", "tsx", "js", "jsx", "mjs", "cjs", "mts", "cts"],
|
|
39318
|
-
rootMarkers: ["tsconfig.json", "jsconfig.json", "package.json"]
|
|
39644
|
+
rootMarkers: ["tsconfig.json", "jsconfig.json", "package.json"],
|
|
39645
|
+
supportedMajor: 5
|
|
39319
39646
|
},
|
|
39320
39647
|
{
|
|
39321
39648
|
id: "python",
|
|
@@ -39389,6 +39716,15 @@ var NPM_LSP_TABLE = [
|
|
|
39389
39716
|
rootMarkers: ["biome.json", "biome.jsonc"]
|
|
39390
39717
|
}
|
|
39391
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
|
+
}
|
|
39392
39728
|
|
|
39393
39729
|
// src/lsp-project-relevance.ts
|
|
39394
39730
|
import { existsSync as existsSync14, readdirSync as readdirSync4, readFileSync as readFileSync12 } from "node:fs";
|
|
@@ -39485,10 +39821,11 @@ function extensionOf(fileName) {
|
|
|
39485
39821
|
|
|
39486
39822
|
// src/lsp-registry-probe.ts
|
|
39487
39823
|
var NPM_REGISTRY_BASE = "https://registry.npmjs.org";
|
|
39488
|
-
function pickEligibleVersion(response, graceDays, now = Date.now()) {
|
|
39824
|
+
function pickEligibleVersion(response, graceDays, now = Date.now(), accept = () => true) {
|
|
39489
39825
|
const times = response.time || {};
|
|
39490
39826
|
const cutoff = now - graceDays * 24 * 60 * 60 * 1000;
|
|
39491
39827
|
const candidates = [];
|
|
39828
|
+
let releaseCount = 0;
|
|
39492
39829
|
for (const [version, publishedAt] of Object.entries(times)) {
|
|
39493
39830
|
if (version === "created" || version === "modified")
|
|
39494
39831
|
continue;
|
|
@@ -39499,6 +39836,9 @@ function pickEligibleVersion(response, graceDays, now = Date.now()) {
|
|
|
39499
39836
|
const ts = Date.parse(publishedAt);
|
|
39500
39837
|
if (Number.isNaN(ts))
|
|
39501
39838
|
continue;
|
|
39839
|
+
releaseCount += 1;
|
|
39840
|
+
if (!accept(version))
|
|
39841
|
+
continue;
|
|
39502
39842
|
candidates.push({ version, publishedAt, ts });
|
|
39503
39843
|
}
|
|
39504
39844
|
candidates.sort((a, b) => b.ts - a.ts);
|
|
@@ -39507,10 +39847,11 @@ function pickEligibleVersion(response, graceDays, now = Date.now()) {
|
|
|
39507
39847
|
return {
|
|
39508
39848
|
version: eligible[0]?.version ?? null,
|
|
39509
39849
|
blockedByGrace,
|
|
39850
|
+
outsideSupportedRange: releaseCount > 0 && candidates.length === 0,
|
|
39510
39851
|
eligible: eligible.map(({ version, publishedAt }) => ({ version, publishedAt }))
|
|
39511
39852
|
};
|
|
39512
39853
|
}
|
|
39513
|
-
async function probeRegistry(npmPackage, graceDays, fetchImpl = fetch) {
|
|
39854
|
+
async function probeRegistry(npmPackage, graceDays, fetchImpl = fetch, accept) {
|
|
39514
39855
|
const encoded = encodeURIComponent(npmPackage).replace(/^%40/, "@");
|
|
39515
39856
|
const url = `${NPM_REGISTRY_BASE}/${encoded}`;
|
|
39516
39857
|
try {
|
|
@@ -39523,7 +39864,7 @@ async function probeRegistry(npmPackage, graceDays, fetchImpl = fetch) {
|
|
|
39523
39864
|
return null;
|
|
39524
39865
|
}
|
|
39525
39866
|
const json = await res.json();
|
|
39526
|
-
return pickEligibleVersion(json, graceDays);
|
|
39867
|
+
return pickEligibleVersion(json, graceDays, Date.now(), accept);
|
|
39527
39868
|
} catch (err) {
|
|
39528
39869
|
warn2(`[lsp] registry probe failed for ${npmPackage}: ${err}`);
|
|
39529
39870
|
return null;
|
|
@@ -39574,11 +39915,11 @@ async function resolveTargetVersion(spec, config, fetchImpl = fetch) {
|
|
|
39574
39915
|
}
|
|
39575
39916
|
const cached = readVersionCheck(spec.npm);
|
|
39576
39917
|
const weeklyMs = config.graceDays * 24 * 60 * 60 * 1000;
|
|
39577
|
-
const cachedSafe = isSafeVersion(cached?.latest_eligible ?? null);
|
|
39918
|
+
const cachedSafe = isSafeVersion(cached?.latest_eligible ?? null) && isVersionSupported(spec, cached?.latest_eligible);
|
|
39578
39919
|
if (cached && !shouldRecheckVersion(cached, weeklyMs) && cachedSafe) {
|
|
39579
39920
|
return { version: cached.latest_eligible, pinned: false, probe: null };
|
|
39580
39921
|
}
|
|
39581
|
-
const probe = await probeRegistry(spec.npm, config.graceDays, fetchImpl);
|
|
39922
|
+
const probe = await probeRegistry(spec.npm, config.graceDays, fetchImpl, (candidate) => isVersionSupported(spec, candidate));
|
|
39582
39923
|
if (!probe) {
|
|
39583
39924
|
return {
|
|
39584
39925
|
version: cachedSafe ? cached?.latest_eligible ?? null : null,
|
|
@@ -39713,11 +40054,16 @@ async function ensureServerInstalled(spec, config, fetchImpl, signal) {
|
|
|
39713
40054
|
const { version, probe } = await resolveTargetVersion(spec, config, fetchImpl);
|
|
39714
40055
|
if (!version) {
|
|
39715
40056
|
const installed = isInstalled(spec.npm, spec.binary);
|
|
39716
|
-
if (installed) {
|
|
40057
|
+
if (installed && installedVersionAllowed(spec, config)) {
|
|
39717
40058
|
warn2(`[lsp] no eligible version of ${spec.npm} (grace=${config.graceDays}d); keeping existing install`);
|
|
39718
40059
|
return { started: false, reason: "kept existing install" };
|
|
39719
40060
|
}
|
|
39720
|
-
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
|
+
}
|
|
39721
40067
|
warn2(`[lsp] skipping ${spec.npm}: ${blocked}`);
|
|
39722
40068
|
return { started: false, reason: blocked };
|
|
39723
40069
|
}
|
|
@@ -39842,6 +40188,15 @@ function validateCachedNpmInstall(spec) {
|
|
|
39842
40188
|
}
|
|
39843
40189
|
return true;
|
|
39844
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
|
+
}
|
|
39845
40200
|
function runAutoInstall(projectRoot, config, fetchImpl = fetch) {
|
|
39846
40201
|
const cachedBinDirs = [];
|
|
39847
40202
|
const skipped = [];
|
|
@@ -39854,7 +40209,7 @@ function runAutoInstall(projectRoot, config, fetchImpl = fetch) {
|
|
|
39854
40209
|
return projectExtensions;
|
|
39855
40210
|
};
|
|
39856
40211
|
for (const spec of NPM_LSP_TABLE) {
|
|
39857
|
-
if (
|
|
40212
|
+
if (cachedInstallUsable(spec, config)) {
|
|
39858
40213
|
cachedBinDirs.push(lspBinDir(spec.npm));
|
|
39859
40214
|
}
|
|
39860
40215
|
if (config.disabled.has(spec.id) || spec.id === "typescript-sdk" && config.disabled.has("typescript")) {
|
|
@@ -39894,7 +40249,7 @@ function runAutoInstall(projectRoot, config, fetchImpl = fetch) {
|
|
|
39894
40249
|
},
|
|
39895
40250
|
skipped,
|
|
39896
40251
|
installsComplete: Promise.all(installPromises).then(() => {}),
|
|
39897
|
-
getCachedBinDirs: () => NPM_LSP_TABLE.filter((spec) =>
|
|
40252
|
+
getCachedBinDirs: () => NPM_LSP_TABLE.filter((spec) => cachedInstallUsable(spec, config)).map((spec) => lspBinDir(spec.npm))
|
|
39898
40253
|
};
|
|
39899
40254
|
}
|
|
39900
40255
|
async function pushLspPathsAfterAutoInstall(pool, projectRoot, cachedBinDirs) {
|
|
@@ -41445,391 +41800,8 @@ function astTools(ctx) {
|
|
|
41445
41800
|
};
|
|
41446
41801
|
}
|
|
41447
41802
|
|
|
41448
|
-
// src/tools/conflicts.ts
|
|
41449
|
-
var z3 = tool.schema;
|
|
41450
|
-
function conflictTools(ctx) {
|
|
41451
|
-
return {
|
|
41452
|
-
aft_conflicts: {
|
|
41453
|
-
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).",
|
|
41454
|
-
args: {
|
|
41455
|
-
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()
|
|
41456
|
-
},
|
|
41457
|
-
execute: async (args, context) => {
|
|
41458
|
-
const rawArgs = {};
|
|
41459
|
-
if (!isEmptyParam(args?.path)) {
|
|
41460
|
-
const expanded = expandTilde2(String(args.path));
|
|
41461
|
-
const projectRoot = await resolveProjectRoot(ctx, context);
|
|
41462
|
-
const resolved = resolvePathFromProjectRoot(projectRoot, expanded);
|
|
41463
|
-
const denied = await assertExternalDirectoryPermission(ctx, context, resolved, {
|
|
41464
|
-
kind: "directory"
|
|
41465
|
-
});
|
|
41466
|
-
if (denied)
|
|
41467
|
-
return permissionDeniedResponse(denied);
|
|
41468
|
-
rawArgs.path = resolved;
|
|
41469
|
-
}
|
|
41470
|
-
const response = await callToolCall(ctx, context, "conflicts", rawArgs);
|
|
41471
|
-
if (response.success === false) {
|
|
41472
|
-
throw new Error(response.message || "git_conflicts failed");
|
|
41473
|
-
}
|
|
41474
|
-
return response.text;
|
|
41475
|
-
}
|
|
41476
|
-
}
|
|
41477
|
-
};
|
|
41478
|
-
}
|
|
41479
|
-
|
|
41480
|
-
// src/tools/definitions/v2.ts
|
|
41481
|
-
import { Effect } from "effect";
|
|
41482
|
-
var V2_TOOL_NAME = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
|
|
41483
|
-
var V2_PATH_HEADER_TOOLS = new Set(["read", "write", "edit"]);
|
|
41484
|
-
var V2_BASH_TOOLS = new Set(["bash"]);
|
|
41485
|
-
var V2_SHELL_PERMISSION = "shell";
|
|
41486
|
-
var ROOT_COMBINATORS = ["anyOf", "oneOf", "allOf"];
|
|
41487
|
-
function failure(error) {
|
|
41488
|
-
if (error instanceof Error)
|
|
41489
|
-
return error;
|
|
41490
|
-
const detail = (() => {
|
|
41491
|
-
try {
|
|
41492
|
-
return JSON.stringify(error) ?? String(error);
|
|
41493
|
-
} catch {
|
|
41494
|
-
return String(error);
|
|
41495
|
-
}
|
|
41496
|
-
})();
|
|
41497
|
-
return new Error(`V2 tool execution rejected with a non-Error value: ${detail}`);
|
|
41498
|
-
}
|
|
41499
|
-
function isPlainObject3(value) {
|
|
41500
|
-
if (typeof value !== "object" || value === null)
|
|
41501
|
-
return false;
|
|
41502
|
-
const prototype = Object.getPrototypeOf(value);
|
|
41503
|
-
return prototype === Object.prototype || prototype === null;
|
|
41504
|
-
}
|
|
41505
|
-
function jsonSafeMetadata(value) {
|
|
41506
|
-
if (Array.isArray(value)) {
|
|
41507
|
-
return value.map((entry) => entry === undefined ? null : jsonSafeMetadata(entry));
|
|
41508
|
-
}
|
|
41509
|
-
if (!isPlainObject3(value))
|
|
41510
|
-
return value;
|
|
41511
|
-
const cleaned = {};
|
|
41512
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
41513
|
-
if (entry === undefined)
|
|
41514
|
-
continue;
|
|
41515
|
-
cleaned[key] = jsonSafeMetadata(entry);
|
|
41516
|
-
}
|
|
41517
|
-
return cleaned;
|
|
41518
|
-
}
|
|
41519
|
-
function resultContent(result) {
|
|
41520
|
-
if (typeof result === "string")
|
|
41521
|
-
return { content: result };
|
|
41522
|
-
const attachments = (result.attachments ?? []).map((attachment) => ({
|
|
41523
|
-
type: "file",
|
|
41524
|
-
uri: attachment.url,
|
|
41525
|
-
mime: attachment.mime,
|
|
41526
|
-
...attachment.filename ? { name: attachment.filename } : {}
|
|
41527
|
-
}));
|
|
41528
|
-
const content = attachments.length ? [{ type: "text", text: result.output }, ...attachments] : result.output;
|
|
41529
|
-
const metadata = jsonSafeMetadata({
|
|
41530
|
-
...result.metadata ?? {},
|
|
41531
|
-
...result.title ? { title: result.title } : {}
|
|
41532
|
-
});
|
|
41533
|
-
return {
|
|
41534
|
-
content,
|
|
41535
|
-
...Object.keys(metadata).length ? { metadata } : {}
|
|
41536
|
-
};
|
|
41537
|
-
}
|
|
41538
|
-
function failureEnvelopeMessage(result) {
|
|
41539
|
-
const output = typeof result === "string" ? result : result.output;
|
|
41540
|
-
if (typeof output !== "string")
|
|
41541
|
-
return;
|
|
41542
|
-
const trimmed = output.trim();
|
|
41543
|
-
if (!trimmed.startsWith("{") || !trimmed.includes('"success"'))
|
|
41544
|
-
return;
|
|
41545
|
-
let parsed;
|
|
41546
|
-
try {
|
|
41547
|
-
parsed = JSON.parse(trimmed);
|
|
41548
|
-
} catch {
|
|
41549
|
-
return;
|
|
41550
|
-
}
|
|
41551
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
41552
|
-
return;
|
|
41553
|
-
const envelope = parsed;
|
|
41554
|
-
if (envelope.success !== false)
|
|
41555
|
-
return;
|
|
41556
|
-
for (const field of [envelope.message, envelope.error, envelope.code]) {
|
|
41557
|
-
if (typeof field === "string" && field.length > 0)
|
|
41558
|
-
return field;
|
|
41559
|
-
}
|
|
41560
|
-
return "The tool call failed.";
|
|
41561
|
-
}
|
|
41562
|
-
function bareToolName3(name) {
|
|
41563
|
-
return name.startsWith("aft_") ? name.slice(4) : name;
|
|
41564
|
-
}
|
|
41565
|
-
function projectArguments(name, definition) {
|
|
41566
|
-
if (!V2_PATH_HEADER_TOOLS.has(bareToolName3(name)) || !("filePath" in definition.args)) {
|
|
41567
|
-
return { ...definition.args };
|
|
41568
|
-
}
|
|
41569
|
-
const { filePath, ...rest } = definition.args;
|
|
41570
|
-
return { path: filePath, ...rest };
|
|
41571
|
-
}
|
|
41572
|
-
function executionArguments(name, input) {
|
|
41573
|
-
if (!V2_PATH_HEADER_TOOLS.has(bareToolName3(name)) || !("path" in input))
|
|
41574
|
-
return input;
|
|
41575
|
-
const { path, ...rest } = input;
|
|
41576
|
-
return { filePath: path, ...rest };
|
|
41577
|
-
}
|
|
41578
|
-
function hostPermission(name) {
|
|
41579
|
-
const bare = bareToolName3(name);
|
|
41580
|
-
if (new Set(["read", "glob", "grep"]).has(bare))
|
|
41581
|
-
return bare;
|
|
41582
|
-
if (V2_BASH_TOOLS.has(name))
|
|
41583
|
-
return V2_SHELL_PERMISSION;
|
|
41584
|
-
if (new Set([
|
|
41585
|
-
"write",
|
|
41586
|
-
"edit",
|
|
41587
|
-
"apply_patch",
|
|
41588
|
-
"delete",
|
|
41589
|
-
"move",
|
|
41590
|
-
"ast_grep_replace",
|
|
41591
|
-
"import",
|
|
41592
|
-
"safety"
|
|
41593
|
-
]).has(bare)) {
|
|
41594
|
-
return "edit";
|
|
41595
|
-
}
|
|
41596
|
-
return;
|
|
41597
|
-
}
|
|
41598
|
-
function assertV2Contract(name, input) {
|
|
41599
|
-
if (!V2_TOOL_NAME.test(name)) {
|
|
41600
|
-
throw new Error(`Invalid V2 tool name ${JSON.stringify(name)}; expected ${V2_TOOL_NAME.source}`);
|
|
41601
|
-
}
|
|
41602
|
-
const schema = tool.schema.toJSONSchema(input, { io: "input" });
|
|
41603
|
-
if (schema.type !== "object") {
|
|
41604
|
-
throw new Error(`V2 tool ${name} must have an object-rooted input schema`);
|
|
41605
|
-
}
|
|
41606
|
-
for (const keyword of ROOT_COMBINATORS) {
|
|
41607
|
-
if (keyword in schema) {
|
|
41608
|
-
throw new Error(`V2 tool ${name} input schema cannot use root ${keyword}`);
|
|
41609
|
-
}
|
|
41610
|
-
}
|
|
41611
|
-
}
|
|
41612
|
-
function v2PermissionRequest(name, request) {
|
|
41613
|
-
if (!V2_BASH_TOOLS.has(name) || request.permission !== "bash")
|
|
41614
|
-
return request;
|
|
41615
|
-
return { ...request, permission: V2_SHELL_PERMISSION };
|
|
41616
|
-
}
|
|
41617
|
-
function runtimeFor(name, location, context, signal, consumers) {
|
|
41618
|
-
const directory = location.directory;
|
|
41619
|
-
const worktree = location.project?.canonical ?? location.project?.directory ?? directory;
|
|
41620
|
-
return {
|
|
41621
|
-
sessionID: context.sessionID,
|
|
41622
|
-
messageID: context.messageID,
|
|
41623
|
-
agent: context.agent,
|
|
41624
|
-
directory,
|
|
41625
|
-
worktree,
|
|
41626
|
-
abort: signal,
|
|
41627
|
-
effectAbort: signal,
|
|
41628
|
-
metadata: (update) => {
|
|
41629
|
-
Effect.runPromise(context.progress(jsonSafeMetadata({
|
|
41630
|
-
...update.metadata ?? {},
|
|
41631
|
-
...update.title ? { title: update.title } : {}
|
|
41632
|
-
})));
|
|
41633
|
-
},
|
|
41634
|
-
ask: (asked) => {
|
|
41635
|
-
const request = v2PermissionRequest(name, asked);
|
|
41636
|
-
if (consumers.requestPermission)
|
|
41637
|
-
return consumers.requestPermission(request, context);
|
|
41638
|
-
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.`));
|
|
41639
|
-
},
|
|
41640
|
-
progress: context.progress
|
|
41641
|
-
};
|
|
41642
|
-
}
|
|
41643
|
-
function projectV2Tool(name, definition, location, consumers = {}) {
|
|
41644
|
-
const input = tool.schema.object(projectArguments(name, definition));
|
|
41645
|
-
assertV2Contract(name, input);
|
|
41646
|
-
const sharedOptions = definition.options;
|
|
41647
|
-
const permission = hostPermission(name);
|
|
41648
|
-
return {
|
|
41649
|
-
name,
|
|
41650
|
-
description: definition.description,
|
|
41651
|
-
input,
|
|
41652
|
-
options: {
|
|
41653
|
-
...sharedOptions,
|
|
41654
|
-
...permission ? { permission } : {},
|
|
41655
|
-
codemode: false
|
|
41656
|
-
},
|
|
41657
|
-
execute: (rawInput, context) => Effect.tryPromise({
|
|
41658
|
-
try: async (signal) => {
|
|
41659
|
-
const runtime = runtimeFor(name, location, context, signal, consumers);
|
|
41660
|
-
const input = executionArguments(name, rawInput);
|
|
41661
|
-
const result = consumers.executeBash && V2_BASH_TOOLS.has(name) ? await consumers.executeBash({
|
|
41662
|
-
name,
|
|
41663
|
-
input,
|
|
41664
|
-
context: runtime,
|
|
41665
|
-
definition
|
|
41666
|
-
}) : await definition.execute(input, runtime);
|
|
41667
|
-
const refusal = failureEnvelopeMessage(result);
|
|
41668
|
-
if (refusal)
|
|
41669
|
-
throw new Error(refusal);
|
|
41670
|
-
return resultContent(result);
|
|
41671
|
-
},
|
|
41672
|
-
catch: failure
|
|
41673
|
-
})
|
|
41674
|
-
};
|
|
41675
|
-
}
|
|
41676
|
-
|
|
41677
|
-
// src/tools/hoisted.ts
|
|
41678
|
-
import { stat as stat2 } from "node:fs/promises";
|
|
41679
|
-
import * as path4 from "node:path";
|
|
41680
|
-
|
|
41681
|
-
// src/shared/read-vision.ts
|
|
41682
|
-
var VISION_HOST_TIMEOUT_MS = 1500;
|
|
41683
|
-
var states = new WeakMap;
|
|
41684
|
-
function stateFor2(client) {
|
|
41685
|
-
let state = states.get(client);
|
|
41686
|
-
if (!state) {
|
|
41687
|
-
state = { models: new Map, history: new Map, capabilities: new Map };
|
|
41688
|
-
states.set(client, state);
|
|
41689
|
-
}
|
|
41690
|
-
return state;
|
|
41691
|
-
}
|
|
41692
|
-
function rememberReadModel(client, sessionID, model) {
|
|
41693
|
-
stateFor2(client).models.set(sessionID, model);
|
|
41694
|
-
}
|
|
41695
|
-
async function bounded(call) {
|
|
41696
|
-
let timer;
|
|
41697
|
-
try {
|
|
41698
|
-
return await Promise.race([
|
|
41699
|
-
Promise.resolve().then(call),
|
|
41700
|
-
new Promise((resolve) => {
|
|
41701
|
-
timer = setTimeout(() => resolve(undefined), VISION_HOST_TIMEOUT_MS);
|
|
41702
|
-
})
|
|
41703
|
-
]);
|
|
41704
|
-
} catch {
|
|
41705
|
-
return;
|
|
41706
|
-
} finally {
|
|
41707
|
-
clearTimeout(timer);
|
|
41708
|
-
}
|
|
41709
|
-
}
|
|
41710
|
-
function record3(value) {
|
|
41711
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
41712
|
-
}
|
|
41713
|
-
async function currentSessionVisionCapability(client, sessionID) {
|
|
41714
|
-
if (!sessionID)
|
|
41715
|
-
return;
|
|
41716
|
-
const state = stateFor2(client);
|
|
41717
|
-
let model = state.models.get(sessionID);
|
|
41718
|
-
if (!model) {
|
|
41719
|
-
let pending = state.history.get(sessionID);
|
|
41720
|
-
if (!pending) {
|
|
41721
|
-
pending = bounded(() => resolvePromptContext(client, sessionID)).then((context) => context?.model);
|
|
41722
|
-
state.history.set(sessionID, pending);
|
|
41723
|
-
pending.finally(() => state.history.delete(sessionID));
|
|
41724
|
-
}
|
|
41725
|
-
const historical = await pending;
|
|
41726
|
-
model = state.models.get(sessionID) ?? historical;
|
|
41727
|
-
}
|
|
41728
|
-
if (!model)
|
|
41729
|
-
return;
|
|
41730
|
-
const currentModel = model;
|
|
41731
|
-
const key = JSON.stringify([model.providerID, model.modelID]);
|
|
41732
|
-
let capability = state.capabilities.get(key);
|
|
41733
|
-
if (!capability) {
|
|
41734
|
-
capability = (async () => {
|
|
41735
|
-
const api = client.provider;
|
|
41736
|
-
if (!api?.list)
|
|
41737
|
-
return;
|
|
41738
|
-
const list = api.list.bind(api);
|
|
41739
|
-
const listed = record3(await bounded(list));
|
|
41740
|
-
const catalog = record3(listed?.data) ?? listed;
|
|
41741
|
-
const providers = catalog?.all ?? catalog?.providers;
|
|
41742
|
-
if (!Array.isArray(providers))
|
|
41743
|
-
return;
|
|
41744
|
-
const provider = providers.map(record3).find((entry) => entry?.id === currentModel.providerID);
|
|
41745
|
-
const models = provider?.models;
|
|
41746
|
-
const entry = record3(Array.isArray(models) ? models.find((entry) => record3(entry)?.id === currentModel.modelID) : record3(models)?.[currentModel.modelID]);
|
|
41747
|
-
const inputs = record3(entry?.modalities)?.input;
|
|
41748
|
-
return Array.isArray(inputs) ? inputs.includes("image") : typeof entry?.attachment === "boolean" ? entry.attachment : undefined;
|
|
41749
|
-
})();
|
|
41750
|
-
state.capabilities.set(key, capability);
|
|
41751
|
-
capability.then((value) => {
|
|
41752
|
-
if (value === undefined)
|
|
41753
|
-
state.capabilities.delete(key);
|
|
41754
|
-
});
|
|
41755
|
-
}
|
|
41756
|
-
return capability;
|
|
41757
|
-
}
|
|
41758
|
-
|
|
41759
|
-
// src/shared/subagent-detect.ts
|
|
41760
|
-
import { Effect as Effect2 } from "effect";
|
|
41761
|
-
var CACHE_MAX_ENTRIES2 = 200;
|
|
41762
|
-
var cache2 = new Map;
|
|
41763
|
-
async function resolveIsSubagent(client, sessionId, _fallbackDirectory) {
|
|
41764
|
-
if (!sessionId) {
|
|
41765
|
-
sessionLog(undefined, "[subagent-detect] no sessionId provided → primary");
|
|
41766
|
-
return false;
|
|
41767
|
-
}
|
|
41768
|
-
const cached = cache2.get(sessionId);
|
|
41769
|
-
if (cached) {
|
|
41770
|
-
cache2.delete(sessionId);
|
|
41771
|
-
cache2.set(sessionId, cached);
|
|
41772
|
-
return cached.isSubagent;
|
|
41773
|
-
}
|
|
41774
|
-
if (isV2PluginContext(client))
|
|
41775
|
-
return resolveV2(client, sessionId);
|
|
41776
|
-
const c = client;
|
|
41777
|
-
const sessionApi = c?.session;
|
|
41778
|
-
if (!sessionApi || typeof sessionApi.get !== "function") {
|
|
41779
|
-
sessionLog(sessionId, `[subagent-detect] client.session.get unavailable (client=${typeof client}, session=${typeof sessionApi}, get=${typeof sessionApi?.get}) → caching as primary`);
|
|
41780
|
-
setCache2(sessionId, false);
|
|
41781
|
-
return false;
|
|
41782
|
-
}
|
|
41783
|
-
sessionLog(sessionId, `[subagent-detect] cache miss, calling client.session.get(id=${sessionId})`);
|
|
41784
|
-
let isSubagent = false;
|
|
41785
|
-
let parentIdRaw;
|
|
41786
|
-
try {
|
|
41787
|
-
const result = await sessionApi.get({
|
|
41788
|
-
path: { id: sessionId }
|
|
41789
|
-
});
|
|
41790
|
-
const session = result?.data ?? result;
|
|
41791
|
-
parentIdRaw = session?.parentID;
|
|
41792
|
-
isSubagent = session !== undefined && typeof session.parentID === "string" && session.parentID.length > 0;
|
|
41793
|
-
sessionLog(sessionId, `[subagent-detect] SDK returned session=${session !== undefined ? "present" : "undefined"}, parentID=${JSON.stringify(parentIdRaw)} → isSubagent=${isSubagent}`);
|
|
41794
|
-
} catch (err) {
|
|
41795
|
-
sessionWarn(sessionId, `[subagent-detect] SDK lookup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
41796
|
-
return false;
|
|
41797
|
-
}
|
|
41798
|
-
setCache2(sessionId, isSubagent);
|
|
41799
|
-
return isSubagent;
|
|
41800
|
-
}
|
|
41801
|
-
function isV2PluginContext(client) {
|
|
41802
|
-
if (!client || typeof client !== "object")
|
|
41803
|
-
return false;
|
|
41804
|
-
const candidate = client;
|
|
41805
|
-
return typeof candidate.location?.directory === "string" && typeof candidate.session?.get === "function";
|
|
41806
|
-
}
|
|
41807
|
-
async function resolveV2(context, sessionId) {
|
|
41808
|
-
let session;
|
|
41809
|
-
try {
|
|
41810
|
-
session = await Effect2.runPromise(context.session.get({ sessionID: sessionId }));
|
|
41811
|
-
} catch (err) {
|
|
41812
|
-
sessionWarn(sessionId, `[subagent-detect] OpenCode 2 session lookup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
41813
|
-
return false;
|
|
41814
|
-
}
|
|
41815
|
-
const isSubagent = typeof session?.parentID === "string" && session.parentID.length > 0;
|
|
41816
|
-
sessionLog(sessionId, `[subagent-detect] OpenCode 2 session parentID=${JSON.stringify(session?.parentID)} → isSubagent=${isSubagent}`);
|
|
41817
|
-
setCache2(sessionId, isSubagent);
|
|
41818
|
-
return isSubagent;
|
|
41819
|
-
}
|
|
41820
|
-
function setCache2(sessionId, isSubagent) {
|
|
41821
|
-
if (cache2.has(sessionId))
|
|
41822
|
-
cache2.delete(sessionId);
|
|
41823
|
-
cache2.set(sessionId, { isSubagent, recordedAt: Date.now() });
|
|
41824
|
-
if (cache2.size > CACHE_MAX_ENTRIES2) {
|
|
41825
|
-
const oldest = cache2.keys().next().value;
|
|
41826
|
-
if (oldest !== undefined)
|
|
41827
|
-
cache2.delete(oldest);
|
|
41828
|
-
}
|
|
41829
|
-
}
|
|
41830
|
-
|
|
41831
41803
|
// src/tools/bash.ts
|
|
41832
|
-
var
|
|
41804
|
+
var z3 = tool.schema;
|
|
41833
41805
|
var METADATA_PREVIEW_LIMIT = 30 * 1024;
|
|
41834
41806
|
var DEFAULT_HARD_TIMEOUT_MS = 30 * 60 * 1000;
|
|
41835
41807
|
var BASH_TRANSPORT_MARGIN_MS = 1e4;
|
|
@@ -41845,7 +41817,9 @@ function resolveForegroundWaitMs(configured) {
|
|
|
41845
41817
|
}
|
|
41846
41818
|
return configured;
|
|
41847
41819
|
}
|
|
41848
|
-
function orchestratedTransportTimeoutMs(blockToCompletion, wait, effectiveTimeout, foregroundWaitMs) {
|
|
41820
|
+
function orchestratedTransportTimeoutMs(blockToCompletion, wait, effectiveTimeout, foregroundWaitMs, noHardKill = false) {
|
|
41821
|
+
if (noHardKill)
|
|
41822
|
+
return LONGEST_TIMER_DELAY_MS;
|
|
41849
41823
|
const waitBudget = blockToCompletion || wait ? effectiveTimeout ?? DEFAULT_HARD_TIMEOUT_MS : foregroundWaitMs;
|
|
41850
41824
|
return waitBudget + BASH_TRANSPORT_MARGIN_MS;
|
|
41851
41825
|
}
|
|
@@ -41922,13 +41896,38 @@ function listenForForegroundAbort(ctx, runtime, enabled) {
|
|
|
41922
41896
|
function userMessageDetachDescription(detachOnUserMessage) {
|
|
41923
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)`.";
|
|
41924
41898
|
}
|
|
41925
|
-
function
|
|
41926
|
-
return
|
|
41899
|
+
function bashCompanionRegistered(config, companion) {
|
|
41900
|
+
return resolveBashConfig(config).background && toolEnabled(config, companion);
|
|
41927
41901
|
}
|
|
41928
|
-
function
|
|
41929
|
-
|
|
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`;
|
|
41930
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." : "";
|
|
41931
|
-
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).";
|
|
41932
41931
|
return `Execute shell commands.${compression}${tasks}
|
|
41933
41932
|
|
|
41934
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.`;
|
|
@@ -42005,30 +42004,63 @@ async function withPermissionLoop(ctx, runtime, params, bridgeCall, options) {
|
|
|
42005
42004
|
}
|
|
42006
42005
|
throw new Error("bash permission retry failed: too many rounds");
|
|
42007
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
|
+
}
|
|
42008
42021
|
function createBashTool(ctx, aftSearchRegisteredOverride) {
|
|
42009
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
|
+
} : {};
|
|
42010
42038
|
const backgroundFlagArg = initialBashCfg.background ? {
|
|
42011
|
-
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.`)
|
|
42012
42040
|
} : {};
|
|
42013
42041
|
const ptyArgs = initialBashCfg.background ? {
|
|
42014
|
-
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)}`),
|
|
42015
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."),
|
|
42016
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.")
|
|
42017
42045
|
} : {};
|
|
42018
42046
|
const args = {
|
|
42019
|
-
command:
|
|
42047
|
+
command: z3.string().describe("Shell command to execute. Supports pipes, redirection, and normal shell syntax."),
|
|
42020
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."),
|
|
42021
|
-
workdir:
|
|
42022
|
-
description:
|
|
42023
|
-
|
|
42024
|
-
|
|
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,
|
|
42025
42053
|
...backgroundFlagArg,
|
|
42026
|
-
|
|
42054
|
+
...compressedArg,
|
|
42027
42055
|
...ptyArgs
|
|
42028
42056
|
};
|
|
42029
42057
|
let hostFallbackActive = false;
|
|
42030
42058
|
return {
|
|
42031
|
-
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
|
+
}),
|
|
42032
42064
|
args,
|
|
42033
42065
|
execute: async (args, context) => {
|
|
42034
42066
|
const bashCfg = resolveBashConfig(ctx.config);
|
|
@@ -42042,7 +42074,7 @@ function createBashTool(ctx, aftSearchRegisteredOverride) {
|
|
|
42042
42074
|
const cwd = args.workdir ?? context.directory;
|
|
42043
42075
|
const isSubagent = await resolveIsSubagent(ctx.client, context.sessionID, context.directory);
|
|
42044
42076
|
const backgroundDisabled = !bashCfg.background;
|
|
42045
|
-
const requestedWait = coerceBoolean(args.wait);
|
|
42077
|
+
const requestedWait = !backgroundDisabled && coerceBoolean(args.wait);
|
|
42046
42078
|
const rawRequestedPty = coerceBoolean(args.pty);
|
|
42047
42079
|
const rawRequestedBackground = coerceBoolean(args.background);
|
|
42048
42080
|
if (requestedWait && rawRequestedPty) {
|
|
@@ -42053,14 +42085,15 @@ function createBashTool(ctx, aftSearchRegisteredOverride) {
|
|
|
42053
42085
|
}
|
|
42054
42086
|
const requestedPty = !backgroundDisabled && rawRequestedPty;
|
|
42055
42087
|
const requestedBackground = !backgroundDisabled && (rawRequestedBackground || requestedPty);
|
|
42056
|
-
if (requestedPty && isSubagent) {
|
|
42057
|
-
throw new Error("PTY mode is not available in subagent sessions; subagents cannot drive interactive terminals.");
|
|
42058
|
-
}
|
|
42059
42088
|
const allowSubagentBg = bashCfg.subagent_background;
|
|
42060
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
|
+
}
|
|
42061
42093
|
const blockToCompletion = subagentForcedForeground || backgroundDisabled || requestedWait;
|
|
42062
42094
|
const effectiveBackground = blockToCompletion ? false : requestedBackground;
|
|
42063
42095
|
const rawTimeout = coerceOptionalInt(args.timeout, "timeout", 1, Number.MAX_SAFE_INTEGER);
|
|
42096
|
+
const workerUnboundedWait = isSubagent && requestedWait && rawTimeout === undefined;
|
|
42064
42097
|
const ptyRows = coerceOptionalInt(args.ptyRows, "ptyRows", 1, 60);
|
|
42065
42098
|
const ptyCols = coerceOptionalInt(args.ptyCols, "ptyCols", 1, 140);
|
|
42066
42099
|
const compressed = coerceBoolean(args.compressed, true);
|
|
@@ -42091,7 +42124,7 @@ function createBashTool(ctx, aftSearchRegisteredOverride) {
|
|
|
42091
42124
|
wait: requestedWait,
|
|
42092
42125
|
sandbox: args.sandbox
|
|
42093
42126
|
}, callBashBridge, {
|
|
42094
|
-
transportTimeoutMs: orchestratedTransportTimeoutMs(blockToCompletion, requestedWait, rawTimeout, foregroundWaitMs),
|
|
42127
|
+
transportTimeoutMs: orchestratedTransportTimeoutMs(blockToCompletion, requestedWait, rawTimeout, foregroundWaitMs, workerUnboundedWait),
|
|
42095
42128
|
onProgress: ({ text }) => {
|
|
42096
42129
|
accumulatedOutput = preview(accumulatedOutput + text);
|
|
42097
42130
|
metadata?.({ output: accumulatedOutput, description });
|
|
@@ -42101,10 +42134,10 @@ function createBashTool(ctx, aftSearchRegisteredOverride) {
|
|
|
42101
42134
|
const fallbackCause = classifyBashHostFallbackError(error);
|
|
42102
42135
|
if (!bashCfg.host_fallback || fallbackCause === undefined)
|
|
42103
42136
|
throw error;
|
|
42104
|
-
if (rawRequestedBackground) {
|
|
42137
|
+
if (!backgroundDisabled && rawRequestedBackground) {
|
|
42105
42138
|
throw new Error(`${BASH_HOST_FALLBACK_REFUSAL}; background:true is unsupported.`);
|
|
42106
42139
|
}
|
|
42107
|
-
if (
|
|
42140
|
+
if (requestedPty) {
|
|
42108
42141
|
throw new Error(`${BASH_HOST_FALLBACK_REFUSAL}; pty:true is unsupported.`);
|
|
42109
42142
|
}
|
|
42110
42143
|
const projectRoot = projectRootFor(context);
|
|
@@ -42138,7 +42171,7 @@ function createBashTool(ctx, aftSearchRegisteredOverride) {
|
|
|
42138
42171
|
trackBgTask(context.sessionID, taskId);
|
|
42139
42172
|
let rendered = data.output ?? "";
|
|
42140
42173
|
if (isSubagent && allowSubagentBg) {
|
|
42141
|
-
rendered +=
|
|
42174
|
+
rendered += workerBackgroundTaskNote(taskId);
|
|
42142
42175
|
}
|
|
42143
42176
|
const metadataPayload = { description, output: rendered, status: "running", taskId };
|
|
42144
42177
|
metadata?.(metadataPayload);
|
|
@@ -42157,17 +42190,19 @@ function createBashTool(ctx, aftSearchRegisteredOverride) {
|
|
|
42157
42190
|
};
|
|
42158
42191
|
}
|
|
42159
42192
|
function createBashStatusTool(ctx) {
|
|
42193
|
+
const waitSteer = bashCompanionRegistered(ctx.config, "bash_watch") ? " To wait, use bash_watch." : "";
|
|
42160
42194
|
return {
|
|
42161
|
-
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}`,
|
|
42162
42196
|
args: {
|
|
42163
|
-
taskId:
|
|
42164
|
-
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.")
|
|
42165
42199
|
},
|
|
42166
42200
|
execute: async (args, context) => {
|
|
42167
42201
|
const taskId = args.taskId;
|
|
42168
42202
|
const outputMode = args.outputMode;
|
|
42169
42203
|
const data = await bashStatusSnapshot(ctx, context, taskId, outputMode);
|
|
42170
|
-
|
|
42204
|
+
const isSubagent = await resolveIsSubagent(ctx.client, context.sessionID, context.directory);
|
|
42205
|
+
return await formatBashStatusText(context, taskId, data, outputMode, isSubagent ? "worker" : "primary");
|
|
42171
42206
|
}
|
|
42172
42207
|
};
|
|
42173
42208
|
}
|
|
@@ -42175,7 +42210,7 @@ function createBashKillTool(ctx) {
|
|
|
42175
42210
|
return {
|
|
42176
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.",
|
|
42177
42212
|
args: {
|
|
42178
|
-
taskId:
|
|
42213
|
+
taskId: z3.string().describe("Background task ID returned by bash({ background: true }), e.g. bash-6b454047a1c39ded.")
|
|
42179
42214
|
},
|
|
42180
42215
|
execute: async (args, context) => {
|
|
42181
42216
|
const data = await callBashBridge(ctx, context, "bash_kill", {
|
|
@@ -42198,7 +42233,7 @@ async function bashStatusSnapshot(ctx, runtime, taskId, outputMode, options) {
|
|
|
42198
42233
|
}
|
|
42199
42234
|
return data;
|
|
42200
42235
|
}
|
|
42201
|
-
async function formatBashStatusText(runtime, taskId, data, requestedOutputMode) {
|
|
42236
|
+
async function formatBashStatusText(runtime, taskId, data, requestedOutputMode, role) {
|
|
42202
42237
|
const status = data.status;
|
|
42203
42238
|
const exit = typeof data.exit_code === "number" ? ` (exit ${data.exit_code})` : "";
|
|
42204
42239
|
const dur = typeof data.duration_ms === "number" ? ` ${Math.round(data.duration_ms / 1000)}s` : "";
|
|
@@ -42216,7 +42251,7 @@ ${preview}`;
|
|
|
42216
42251
|
}
|
|
42217
42252
|
if (status === "running") {
|
|
42218
42253
|
text += `
|
|
42219
|
-
|
|
42254
|
+
${runningTaskStatusHint(role)}`;
|
|
42220
42255
|
}
|
|
42221
42256
|
}
|
|
42222
42257
|
return text;
|
|
@@ -42245,11 +42280,6 @@ PTY task is still running. Use bash_status({ taskId: "${taskId}", outputMode: "s
|
|
|
42245
42280
|
function preview(output) {
|
|
42246
42281
|
return output.length <= METADATA_PREVIEW_LIMIT ? output : output.slice(-METADATA_PREVIEW_LIMIT);
|
|
42247
42282
|
}
|
|
42248
|
-
function subagentGuidance(taskId, watchSyncMaxMs) {
|
|
42249
|
-
return `
|
|
42250
|
-
|
|
42251
|
-
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.`;
|
|
42252
|
-
}
|
|
42253
42283
|
function foregroundMetadata(description, data, rendered) {
|
|
42254
42284
|
const outputPath = data.output_path;
|
|
42255
42285
|
const truncated = typeof data.truncated === "boolean" ? data.truncated : data.output_truncated;
|
|
@@ -42270,24 +42300,355 @@ function shortenCommand(command) {
|
|
|
42270
42300
|
return collapsed.length <= 80 ? collapsed : `${collapsed.slice(0, 77)}...`;
|
|
42271
42301
|
}
|
|
42272
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
|
+
|
|
42273
42632
|
// src/tools/bash_watch.ts
|
|
42274
42633
|
var z5 = tool.schema;
|
|
42275
|
-
var BASH_WAIT_POLL_INTERVAL_MS = 100;
|
|
42276
42634
|
var REGEX_WAIT_SCAN_WINDOW_BYTES = 64 * 1024;
|
|
42277
42635
|
function unavailableSnapshot() {
|
|
42278
42636
|
return { status: "unknown" };
|
|
42279
42637
|
}
|
|
42280
|
-
function coerceConfiguredWatchTimeout(value, cap) {
|
|
42638
|
+
function coerceConfiguredWatchTimeout(value, role, cap) {
|
|
42281
42639
|
try {
|
|
42282
|
-
return coerceOptionalInt(value, "timeoutMs", 1, cap);
|
|
42640
|
+
return coerceOptionalInt(value, "timeoutMs", 1, maxWatchTimeoutMs(role, cap));
|
|
42283
42641
|
} catch (error) {
|
|
42284
42642
|
const message = error instanceof Error ? error.message : String(error);
|
|
42643
|
+
if (role === "worker")
|
|
42644
|
+
throw error;
|
|
42285
42645
|
throw new Error(`${message} (bash.watch_sync_max_ms)`);
|
|
42286
42646
|
}
|
|
42287
42647
|
}
|
|
42288
42648
|
function createBashWatchTool(ctx) {
|
|
42649
|
+
const noPolling = bashCompanionRegistered(ctx.config, "bash_status") ? " Never loop bash_status to wait." : "";
|
|
42289
42650
|
return {
|
|
42290
|
-
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}`,
|
|
42291
42652
|
args: {
|
|
42292
42653
|
taskId: z5.string().describe("Background task ID returned by bash({ background: true })."),
|
|
42293
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."),
|
|
@@ -42338,11 +42699,11 @@ A notification will fire when the pattern matches or the task exits.`;
|
|
|
42338
42699
|
}
|
|
42339
42700
|
const syncWaitCap = bashCfg.watch_sync_max_ms;
|
|
42340
42701
|
const role = isSubagent ? "worker" : "primary";
|
|
42341
|
-
const effectiveWaitMs = subagentForcedSync ?
|
|
42702
|
+
const effectiveWaitMs = subagentForcedSync ? undefined : resolveWatchTimeoutMs(coerceConfiguredWatchTimeout(args.timeoutMs, role, syncWaitCap), role, syncWaitCap);
|
|
42342
42703
|
const data = await waitForBashStatus(ctx, context, taskId, undefined, waitFor, effectiveWaitMs);
|
|
42343
42704
|
const waited = data.waited;
|
|
42344
42705
|
if (waited?.reason === "user_message") {
|
|
42345
|
-
const convertedText = await convertToAsyncWatchOnAbort(ctx, context, taskId, waitFor, coerceBoolean(args.once, true), waited.elapsed_ms);
|
|
42706
|
+
const convertedText = await convertToAsyncWatchOnAbort(ctx, context, taskId, waitFor, coerceBoolean(args.once, true), waited.elapsed_ms, role);
|
|
42346
42707
|
const metadata = context.metadata;
|
|
42347
42708
|
metadata?.({ taskId, status: data.status, waited, convertedToAsync: true });
|
|
42348
42709
|
return convertedText;
|
|
@@ -42354,10 +42715,11 @@ A notification will fire when the pattern matches or the task exits.`;
|
|
|
42354
42715
|
}
|
|
42355
42716
|
};
|
|
42356
42717
|
}
|
|
42357
|
-
async function convertToAsyncWatchOnAbort(ctx, context, taskId, waitFor, once, elapsedMs) {
|
|
42718
|
+
async function convertToAsyncWatchOnAbort(ctx, context, taskId, waitFor, once, elapsedMs, role) {
|
|
42358
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.`);
|
|
42359
42721
|
if (!waitFor) {
|
|
42360
|
-
return interrupted + `The task is still running in the background. A completion reminder will be ` + `delivered automatically when the task exits; don't poll bash_status
|
|
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.`);
|
|
42361
42723
|
}
|
|
42362
42724
|
const notifyParams = {
|
|
42363
42725
|
task_id: taskId,
|
|
@@ -42372,12 +42734,12 @@ async function convertToAsyncWatchOnAbort(ctx, context, taskId, waitFor, once, e
|
|
|
42372
42734
|
const registered = await callBashBridge(ctx, context, "bash_notify", notifyParams);
|
|
42373
42735
|
if (registered.success === false) {
|
|
42374
42736
|
unmarkExplicitControl(context.sessionID, taskId);
|
|
42375
|
-
return interrupted + `Auto-registering an async watch failed (${String(registered.message ?? "unknown error")}). ` +
|
|
42737
|
+
return interrupted + `Auto-registering an async watch failed (${String(registered.message ?? "unknown error")}). ` + reminderFallback;
|
|
42376
42738
|
}
|
|
42377
|
-
return interrupted + `The wait has been converted to an async watch (${registered.watch_id}). ` + `A notification will fire when the pattern matches or the task exits
|
|
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.`);
|
|
42378
42740
|
} catch (err) {
|
|
42379
42741
|
unmarkExplicitControl(context.sessionID, taskId);
|
|
42380
|
-
return interrupted + `Auto-registering an async watch failed (${err instanceof Error ? err.message : String(err)}). ` +
|
|
42742
|
+
return interrupted + `Auto-registering an async watch failed (${err instanceof Error ? err.message : String(err)}). ` + reminderFallback;
|
|
42381
42743
|
}
|
|
42382
42744
|
}
|
|
42383
42745
|
function formatWatchResultText(taskId, data, waited, role, syncWaitCap) {
|
|
@@ -42386,17 +42748,20 @@ function formatWatchResultText(taskId, data, waited, role, syncWaitCap) {
|
|
|
42386
42748
|
const dur = typeof data.duration_ms === "number" ? ` ${Math.round(data.duration_ms / 1000)}s` : "";
|
|
42387
42749
|
let text = `Task ${taskId}: ${status}${exit}${dur}`;
|
|
42388
42750
|
if (waited) {
|
|
42389
|
-
const waitedText = formatWatchWaited(waited.elapsed_ms, waited.limit_ms, syncWaitCap);
|
|
42751
|
+
const waitedText = formatWatchWaited(waited.elapsed_ms, waited.limit_ms, role === "primary" ? syncWaitCap : undefined);
|
|
42390
42752
|
if (waited.reason === "matched") {
|
|
42391
42753
|
const stream = waited.match_stream ? ` in ${waited.match_stream}` : "";
|
|
42392
42754
|
text += `
|
|
42393
42755
|
${waitedText}; matched ${JSON.stringify(waited.match ?? "")}${stream} at offset ${waited.match_offset ?? 0}.`;
|
|
42394
42756
|
} else if (waited.reason === "timeout") {
|
|
42395
42757
|
text += `
|
|
42396
|
-
${waitedText}; timeout reached without match. ${watchTimeoutSteer(role
|
|
42758
|
+
${waitedText}; timeout reached without match. ${watchTimeoutSteer(role)}`;
|
|
42397
42759
|
} else if (waited.reason === "unavailable") {
|
|
42398
42760
|
text += `
|
|
42399
|
-
${waitedText};
|
|
42761
|
+
${waitedText}; ${watchUnavailableSteer(role)}`;
|
|
42762
|
+
} else if (waited.reason === "aborted") {
|
|
42763
|
+
text += `
|
|
42764
|
+
${waitedText}; the watch was cancelled. The task keeps running.`;
|
|
42400
42765
|
} else {
|
|
42401
42766
|
const stat = String(data.status ?? "unknown");
|
|
42402
42767
|
const e = typeof data.exit_code === "number" ? `, exit ${data.exit_code}` : "";
|
|
@@ -42423,9 +42788,17 @@ async function bashStatusSnapshot2(ctx, runtime, taskId, outputMode, cursor, opt
|
|
|
42423
42788
|
return data;
|
|
42424
42789
|
}
|
|
42425
42790
|
async function waitForBashStatus(ctx, runtime, taskId, outputMode, waitFor, effectiveWaitMs) {
|
|
42426
|
-
const startedAt =
|
|
42427
|
-
const deadline = startedAt + effectiveWaitMs;
|
|
42428
|
-
const elapsedMs = () => Math.round(
|
|
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
|
+
});
|
|
42429
42802
|
let spillCursor = { output: 0, stderr: 0 };
|
|
42430
42803
|
const scanState = {
|
|
42431
42804
|
output: { text: "", baseOffset: 0 },
|
|
@@ -42439,31 +42812,29 @@ async function waitForBashStatus(ctx, runtime, taskId, outputMode, waitFor, effe
|
|
|
42439
42812
|
markTaskWaiting(runtime.sessionID, taskId);
|
|
42440
42813
|
let sawTerminal = false;
|
|
42441
42814
|
let lastData;
|
|
42815
|
+
let busySince;
|
|
42442
42816
|
try {
|
|
42443
42817
|
while (true) {
|
|
42818
|
+
if (abortSignal?.aborted) {
|
|
42819
|
+
return withWaited(lastData ?? unavailableSnapshot(), waited("aborted"));
|
|
42820
|
+
}
|
|
42444
42821
|
let data;
|
|
42445
42822
|
try {
|
|
42446
42823
|
data = await bashStatusSnapshot2(ctx, runtime, taskId, outputMode, waitFor ? spillCursor : undefined, bridgeOptions);
|
|
42447
42824
|
} catch (err) {
|
|
42448
42825
|
if (!isBridgeTransportTimeout(err))
|
|
42449
42826
|
throw err;
|
|
42827
|
+
busySince ??= watchClock.now();
|
|
42450
42828
|
if (isSyncWatchAborted(runtime.sessionID)) {
|
|
42451
|
-
return withWaited(lastData ?? unavailableSnapshot(),
|
|
42452
|
-
reason: "user_message",
|
|
42453
|
-
elapsed_ms: elapsedMs(),
|
|
42454
|
-
limit_ms: effectiveWaitMs
|
|
42455
|
-
});
|
|
42829
|
+
return withWaited(lastData ?? unavailableSnapshot(), waited("user_message"));
|
|
42456
42830
|
}
|
|
42457
|
-
if (
|
|
42458
|
-
return withWaited(lastData ?? unavailableSnapshot(),
|
|
42459
|
-
reason: "unavailable",
|
|
42460
|
-
elapsed_ms: elapsedMs(),
|
|
42461
|
-
limit_ms: effectiveWaitMs
|
|
42462
|
-
});
|
|
42831
|
+
if (watchClock.now() >= deadline || watchClock.now() - busySince >= WATCH_UNAVAILABLE_GIVE_UP_MS) {
|
|
42832
|
+
return withWaited(lastData ?? unavailableSnapshot(), waited("unavailable"));
|
|
42463
42833
|
}
|
|
42464
|
-
await
|
|
42834
|
+
await pause();
|
|
42465
42835
|
continue;
|
|
42466
42836
|
}
|
|
42837
|
+
busySince = undefined;
|
|
42467
42838
|
lastData = data;
|
|
42468
42839
|
const terminal = isTerminalStatus(data.status);
|
|
42469
42840
|
if (waitFor) {
|
|
@@ -42488,14 +42859,11 @@ async function waitForBashStatus(ctx, runtime, taskId, outputMode, waitFor, effe
|
|
|
42488
42859
|
await markBgCompletionDelivered({ ctx, directory: projectRootFor(runtime), sessionID: runtime.sessionID }, taskId);
|
|
42489
42860
|
}
|
|
42490
42861
|
const matchStream = data.mode === "pty" ? undefined : chunk.stream === "output" ? "stdout" : "stderr";
|
|
42491
|
-
return withWaited(data, {
|
|
42492
|
-
reason: "matched",
|
|
42493
|
-
elapsed_ms: elapsedMs(),
|
|
42494
|
-
limit_ms: effectiveWaitMs,
|
|
42862
|
+
return withWaited(data, waited("matched", {
|
|
42495
42863
|
match: match.text,
|
|
42496
42864
|
match_offset: state.baseOffset + match.byteOffset,
|
|
42497
42865
|
match_stream: matchStream
|
|
42498
|
-
});
|
|
42866
|
+
}));
|
|
42499
42867
|
}
|
|
42500
42868
|
if (waitFor.kind === "substring") {
|
|
42501
42869
|
const trimmed = trimWaitScanBuffer(state.text, state.baseOffset, waitFor);
|
|
@@ -42509,27 +42877,15 @@ async function waitForBashStatus(ctx, runtime, taskId, outputMode, waitFor, effe
|
|
|
42509
42877
|
sawTerminal = true;
|
|
42510
42878
|
consumeBgCompletion(runtime.sessionID, taskId);
|
|
42511
42879
|
await markBgCompletionDelivered({ ctx, directory: projectRootFor(runtime), sessionID: runtime.sessionID }, taskId);
|
|
42512
|
-
return withWaited(data,
|
|
42513
|
-
reason: "exited",
|
|
42514
|
-
elapsed_ms: elapsedMs(),
|
|
42515
|
-
limit_ms: effectiveWaitMs
|
|
42516
|
-
});
|
|
42880
|
+
return withWaited(data, waited("exited"));
|
|
42517
42881
|
}
|
|
42518
42882
|
if (isSyncWatchAborted(runtime.sessionID)) {
|
|
42519
|
-
return withWaited(data,
|
|
42520
|
-
reason: "user_message",
|
|
42521
|
-
elapsed_ms: elapsedMs(),
|
|
42522
|
-
limit_ms: effectiveWaitMs
|
|
42523
|
-
});
|
|
42883
|
+
return withWaited(data, waited("user_message"));
|
|
42524
42884
|
}
|
|
42525
|
-
if (
|
|
42526
|
-
return withWaited(data,
|
|
42527
|
-
reason: "timeout",
|
|
42528
|
-
elapsed_ms: elapsedMs(),
|
|
42529
|
-
limit_ms: effectiveWaitMs
|
|
42530
|
-
});
|
|
42885
|
+
if (watchClock.now() >= deadline) {
|
|
42886
|
+
return withWaited(data, waited("timeout"));
|
|
42531
42887
|
}
|
|
42532
|
-
await
|
|
42888
|
+
await pause();
|
|
42533
42889
|
}
|
|
42534
42890
|
} finally {
|
|
42535
42891
|
if (!sawTerminal)
|
|
@@ -42639,8 +42995,9 @@ function withWaited(data, waited) {
|
|
|
42639
42995
|
// src/tools/bash_write.ts
|
|
42640
42996
|
var z6 = tool.schema;
|
|
42641
42997
|
function createBashWriteTool(ctx) {
|
|
42998
|
+
const modeCheck = bashCompanionRegistered(ctx.config, "bash_status") ? '; check bash_status reports mode: "pty" first.' : ".";
|
|
42642
42999
|
return {
|
|
42643
|
-
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).",
|
|
42644
43001
|
args: {
|
|
42645
43002
|
taskId: z6.string().describe("Background PTY task ID returned by bash({ pty: true, background: true })."),
|
|
42646
43003
|
input: z6.union([
|
|
@@ -42908,25 +43265,19 @@ function createReadTool(ctx) {
|
|
|
42908
43265
|
const failure = classifyPermissionError(error);
|
|
42909
43266
|
return permissionDeniedResponse(failure.kind === "rule_denied" ? permissionRuleDenial("read") : failure.message);
|
|
42910
43267
|
}
|
|
42911
|
-
const
|
|
42912
|
-
const
|
|
42913
|
-
const
|
|
42914
|
-
const
|
|
42915
|
-
let startLine = rawStartLine;
|
|
42916
|
-
let endLine = rawEndLine;
|
|
42917
|
-
if (startLine === undefined && rawOffset !== undefined) {
|
|
42918
|
-
startLine = rawOffset;
|
|
42919
|
-
if (rawLimit !== undefined) {
|
|
42920
|
-
endLine = rawOffset + rawLimit - 1;
|
|
42921
|
-
}
|
|
42922
|
-
}
|
|
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);
|
|
42923
43272
|
const rawArgs = { filePath: file };
|
|
42924
43273
|
if (startLine !== undefined)
|
|
42925
43274
|
rawArgs.startLine = startLine;
|
|
42926
43275
|
if (endLine !== undefined)
|
|
42927
43276
|
rawArgs.endLine = endLine;
|
|
42928
|
-
if (
|
|
42929
|
-
rawArgs.
|
|
43277
|
+
if (offset !== undefined)
|
|
43278
|
+
rawArgs.offset = offset;
|
|
43279
|
+
if (limit !== undefined)
|
|
43280
|
+
rawArgs.limit = limit;
|
|
42930
43281
|
const mediaCandidate = isGithubResourcePath(file) || /\.(png|jpe?g|gif|webp|pdf)$/i.test(filePath) && await stat2(filePath).then((info) => info.isFile(), () => false);
|
|
42931
43282
|
const visionCapability = mediaCandidate ? await measurePreStage("capability", () => currentSessionVisionCapability(ctx.client, context.sessionID)) : undefined;
|
|
42932
43283
|
if (visionCapability !== undefined)
|
|
@@ -42982,7 +43333,7 @@ function createWriteTool(ctx, editToolName = "edit") {
|
|
|
42982
43333
|
description: getWriteDescription(ctx, editToolName),
|
|
42983
43334
|
args: {
|
|
42984
43335
|
filePath: z7.string().describe("Path to the file to write (absolute or relative to project root)"),
|
|
42985
|
-
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")
|
|
42986
43337
|
},
|
|
42987
43338
|
execute: async (args, context) => {
|
|
42988
43339
|
const argsRecord = args;
|
|
@@ -43099,7 +43450,7 @@ ${backupBehavior}
|
|
|
43099
43450
|
- Symbol replace includes decorators, attributes, and doc comments in range
|
|
43100
43451
|
- Response is a compact server-rendered summary; before/after diff details are attached as UI metadata when available.`;
|
|
43101
43452
|
}
|
|
43102
|
-
var
|
|
43453
|
+
var HASHLINE_EDIT_GRAMMAR = [
|
|
43103
43454
|
"Apply a hashline patch. Arguments are exactly `{patch}` where `patch` is a non-empty string. Server-owned preview control is outside this schema.",
|
|
43104
43455
|
"",
|
|
43105
43456
|
"Quick reference:",
|
|
@@ -43107,13 +43458,18 @@ var HASHLINE_EDIT_DESCRIPTION = [
|
|
|
43107
43458
|
"- Same canonical path: multiple sections compose in patch order against pre-request coordinates.",
|
|
43108
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.",
|
|
43109
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 `-`.",
|
|
43110
|
-
"- 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."
|
|
43111
|
-
"- 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."
|
|
43112
43462
|
].join(`
|
|
43113
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
|
+
}
|
|
43114
43470
|
function createHashlineEditTool(ctx) {
|
|
43115
43471
|
return {
|
|
43116
|
-
description:
|
|
43472
|
+
description: hashlineEditDescription(ctx.config),
|
|
43117
43473
|
args: {
|
|
43118
43474
|
patch: z7.string().min(1).describe("Hashline patch text with one or more [path#TAG] sections and PUT/CUT/REM/MV operations")
|
|
43119
43475
|
},
|
|
@@ -43188,8 +43544,7 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
43188
43544
|
throw new Error("'path' parameter is required");
|
|
43189
43545
|
if (isGithubResourcePath(file)) {
|
|
43190
43546
|
const edits = argsRecord.edits;
|
|
43191
|
-
|
|
43192
|
-
if (!onlyFindReplace) {
|
|
43547
|
+
if (!isFindReplaceOnlyEdit(argsRecord)) {
|
|
43193
43548
|
throw new Error("edit: GitHub resources support only edits[] find/replace entries with oldString and optional newString");
|
|
43194
43549
|
}
|
|
43195
43550
|
const rawArgs = { path: file, edits };
|
|
@@ -43476,6 +43831,12 @@ function createMoveTool(ctx) {
|
|
|
43476
43831
|
};
|
|
43477
43832
|
}
|
|
43478
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
|
+
} : {};
|
|
43479
43840
|
const tools = {
|
|
43480
43841
|
read: createReadTool(ctx),
|
|
43481
43842
|
write: createWriteTool(ctx, "edit"),
|
|
@@ -43484,10 +43845,7 @@ function hoistedTools(ctx) {
|
|
|
43484
43845
|
aft_delete: createDeleteTool(ctx),
|
|
43485
43846
|
aft_move: createMoveTool(ctx),
|
|
43486
43847
|
bash: createBashTool(ctx),
|
|
43487
|
-
|
|
43488
|
-
bash_write: createBashWriteTool(ctx),
|
|
43489
|
-
bash_watch: createBashWatchTool(ctx),
|
|
43490
|
-
bash_kill: createBashKillTool(ctx)
|
|
43848
|
+
...backgroundCompanions
|
|
43491
43849
|
};
|
|
43492
43850
|
return prepareToolMap(tools, { hashlineEffective: ctx.hashlineEffective });
|
|
43493
43851
|
}
|
|
@@ -43883,6 +44241,9 @@ function navigationTools(ctx) {
|
|
|
43883
44241
|
|
|
43884
44242
|
// src/tools/reading.ts
|
|
43885
44243
|
var z11 = tool.schema;
|
|
44244
|
+
function isGithubPathArg(value) {
|
|
44245
|
+
return typeof value === "string" && (value.startsWith("issue://") || value.startsWith("pr://"));
|
|
44246
|
+
}
|
|
43886
44247
|
function buildZoomTitle(args) {
|
|
43887
44248
|
const targets = args.targets;
|
|
43888
44249
|
if (!isEmptyParam(targets)) {
|
|
@@ -43906,6 +44267,13 @@ function buildZoomTitle(args) {
|
|
|
43906
44267
|
}
|
|
43907
44268
|
return path || "(no target)";
|
|
43908
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
|
+
}
|
|
43909
44277
|
function readingTools(ctx) {
|
|
43910
44278
|
const zoomEnabled = toolEnabled(ctx.config, "aft_zoom");
|
|
43911
44279
|
const ghReadEnabled = resolveGithubConfig(ctx.config).read;
|
|
@@ -43913,9 +44281,9 @@ function readingTools(ctx) {
|
|
|
43913
44281
|
const githubZoomDescription = whenGhReadEnabled(ghReadEnabled, "GitHub issue and pull-request discussion ordinals can be zoomed with `path: issue://…` or `path: pr://…` and `symbols`.");
|
|
43914
44282
|
const tools = prepareToolMap({
|
|
43915
44283
|
aft_outline: {
|
|
43916
|
-
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) + `
|
|
43917
44285
|
|
|
43918
|
-
`
|
|
44286
|
+
` + "Pass a single `target`:\n" + ` • file path → outline that file (with signatures)
|
|
43919
44287
|
` + ` • directory path → outline source files under it
|
|
43920
44288
|
` + ` • URL (http:// or https://) → fetch and outline a remote HTML/Markdown document
|
|
43921
44289
|
` + " • array of paths → outline multiple files in one call; with files:true, every path must be a directory" + (githubOutlineDescription ? `
|
|
@@ -43964,7 +44332,7 @@ ${githubOutlineDescription}` : "") + "\n\nWhen a list is cut, the reply ends wit
|
|
|
43964
44332
|
}
|
|
43965
44333
|
},
|
|
43966
44334
|
aft_zoom: {
|
|
43967
|
-
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 ? `
|
|
43968
44336
|
|
|
43969
44337
|
${githubZoomDescription}` : ""),
|
|
43970
44338
|
args: {
|
|
@@ -43980,37 +44348,22 @@ ${githubZoomDescription}` : ""),
|
|
|
43980
44348
|
path: z11.string().describe("Path to file (absolute or relative to project root)"),
|
|
43981
44349
|
symbol: z11.string().describe("Symbol name in that file")
|
|
43982
44350
|
}))
|
|
43983
|
-
]).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."),
|
|
43984
44352
|
contextLines: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("Lines of context before/after the symbol (default: 3)"),
|
|
43985
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.")
|
|
43986
44354
|
},
|
|
43987
44355
|
execute: async (args, context) => {
|
|
43988
|
-
const hasTargetsProvided = (t) => {
|
|
43989
|
-
if (isEmptyParam(t))
|
|
43990
|
-
return false;
|
|
43991
|
-
const entryEmpty = (entry) => {
|
|
43992
|
-
if (!entry || typeof entry !== "object")
|
|
43993
|
-
return true;
|
|
43994
|
-
const fp = entry.path;
|
|
43995
|
-
const sym = entry.symbol;
|
|
43996
|
-
const fpEmpty = typeof fp !== "string" || fp.length === 0;
|
|
43997
|
-
const symEmpty = typeof sym !== "string" || sym.length === 0;
|
|
43998
|
-
return fpEmpty && symEmpty;
|
|
43999
|
-
};
|
|
44000
|
-
if (Array.isArray(t))
|
|
44001
|
-
return !t.every(entryEmpty);
|
|
44002
|
-
return !entryEmpty(t);
|
|
44003
|
-
};
|
|
44004
44356
|
const targetsInput = coerceJsonCollectionParam(args.targets, "targets");
|
|
44005
|
-
const
|
|
44006
|
-
const
|
|
44007
|
-
const
|
|
44008
|
-
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);
|
|
44009
44362
|
const wantCallgraph = coerceBoolean(args.callgraph);
|
|
44010
44363
|
const contextLines = coerceOptionalInt(args.contextLines, "contextLines", 1, Number.MAX_SAFE_INTEGER);
|
|
44011
44364
|
const zoomTitle = buildZoomTitle({
|
|
44012
44365
|
...args,
|
|
44013
|
-
targets:
|
|
44366
|
+
targets: hasTargets ? targetEntries : undefined
|
|
44014
44367
|
});
|
|
44015
44368
|
const zoomDisplay = { title: zoomTitle };
|
|
44016
44369
|
if (hasFilePath)
|
|
@@ -44021,7 +44374,7 @@ ${githubZoomDescription}` : ""),
|
|
|
44021
44374
|
zoomDisplay.symbols = typeof args.symbols === "string" ? args.symbols : JSON.stringify(args.symbols);
|
|
44022
44375
|
}
|
|
44023
44376
|
if (hasTargets)
|
|
44024
|
-
zoomDisplay.targets = JSON.stringify(
|
|
44377
|
+
zoomDisplay.targets = JSON.stringify(targetEntries);
|
|
44025
44378
|
if (contextLines !== undefined)
|
|
44026
44379
|
zoomDisplay.contextLines = contextLines;
|
|
44027
44380
|
if (wantCallgraph)
|
|
@@ -44032,23 +44385,24 @@ ${githubZoomDescription}` : ""),
|
|
|
44032
44385
|
metadata: zoomDisplay
|
|
44033
44386
|
});
|
|
44034
44387
|
if (hasTargets) {
|
|
44035
|
-
if (hasFilePath
|
|
44036
|
-
throw new Error("
|
|
44037
|
-
}
|
|
44038
|
-
const targets = Array.isArray(targetsInput) ? targetsInput : [targetsInput];
|
|
44039
|
-
if (targets.length === 0) {
|
|
44040
|
-
throw new Error("'targets' must be a non-empty object or array");
|
|
44388
|
+
if (hasFilePath && hasUrl) {
|
|
44389
|
+
throw new Error("Provide exactly ONE of 'path' or 'url' — not both");
|
|
44041
44390
|
}
|
|
44042
|
-
|
|
44043
|
-
|
|
44044
|
-
if (typeof targetPath !== "string" || targetPath.length === 0) {
|
|
44391
|
+
const targets = targetEntries.map((entry, i) => {
|
|
44392
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
44045
44393
|
throw new Error(`targets[${i}].path must be a non-empty string`);
|
|
44046
44394
|
}
|
|
44047
|
-
|
|
44048
|
-
|
|
44049
|
-
|
|
44050
|
-
|
|
44051
|
-
|
|
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)));
|
|
44052
44406
|
const permissionDenied = await assertPathExternalPermissions(ctx, context, resolvedTargets);
|
|
44053
44407
|
if (permissionDenied)
|
|
44054
44408
|
return permissionDeniedResponse(permissionDenied);
|
|
@@ -44058,6 +44412,12 @@ ${githubZoomDescription}` : ""),
|
|
|
44058
44412
|
symbol: target.symbol
|
|
44059
44413
|
}))
|
|
44060
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;
|
|
44061
44421
|
if (contextLines !== undefined)
|
|
44062
44422
|
rawArgs.contextLines = contextLines;
|
|
44063
44423
|
if (wantCallgraph)
|
|
@@ -44074,7 +44434,7 @@ ${githubZoomDescription}` : ""),
|
|
|
44074
44434
|
if (hasFilePath && hasUrl) {
|
|
44075
44435
|
throw new Error("Provide exactly ONE of 'path' or 'url' — not both");
|
|
44076
44436
|
}
|
|
44077
|
-
const githubPath = hasFilePath && (
|
|
44437
|
+
const githubPath = hasFilePath && isGithubPathArg(args.path) || hasUrl && isGithubPathArg(args.url);
|
|
44078
44438
|
if (!hasUrl && !githubPath) {
|
|
44079
44439
|
const file = await resolvePathArg(ctx, context, args.path);
|
|
44080
44440
|
const permissionDenied = await assertPathExternalPermissions(ctx, context, file);
|
|
@@ -44165,7 +44525,7 @@ function safetyTools(ctx) {
|
|
|
44165
44525
|
` + "Use checkpoint before risky multi-file changes. Use undo for quick single-file rollback.",
|
|
44166
44526
|
args: {
|
|
44167
44527
|
op: z12.enum(["undo", "history", "checkpoint", "restore", "list"]).describe("Safety operation"),
|
|
44168
|
-
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"),
|
|
44169
44529
|
name: z12.string().optional().describe("Checkpoint name (required for checkpoint, restore)"),
|
|
44170
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)")
|
|
44171
44531
|
},
|
|
@@ -44601,8 +44961,20 @@ function buildAftToolDefinitions(ctx, config, onUnknownDisabled) {
|
|
|
44601
44961
|
const unknown = unknownDisabledTools(disabled);
|
|
44602
44962
|
if (unknown.length > 0)
|
|
44603
44963
|
onUnknownDisabled?.(unknown);
|
|
44964
|
+
describeBashForRegisteredSurface(allTools, config);
|
|
44604
44965
|
return allTools;
|
|
44605
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
|
+
}
|
|
44606
44978
|
function registerAftTools(context, location, definitions, consumers = {}) {
|
|
44607
44979
|
const projected = Object.entries(definitions).map(([name, definition]) => projectV2Tool(name, definition, location, consumers));
|
|
44608
44980
|
return context.tool.transform((editor) => {
|
|
@@ -47313,7 +47685,10 @@ function registerShutdownCleanup(fn) {
|
|
|
47313
47685
|
|
|
47314
47686
|
// src/workflow-hints.ts
|
|
47315
47687
|
var HEADING = "## IMPORTANT NOTICE about your tools";
|
|
47316
|
-
|
|
47688
|
+
function hashlineTagSourceHint(bashRewritesMintTags, isRegistered = () => true) {
|
|
47689
|
+
return `**Hashline edit tags**: ${hashlineTagSourceSentence(bashRewritesMintTags, isRegistered)}`;
|
|
47690
|
+
}
|
|
47691
|
+
var HASHLINE_TAG_SOURCE_HINT = hashlineTagSourceHint(true);
|
|
47317
47692
|
function buildWorkflowHints(opts) {
|
|
47318
47693
|
const sections = [];
|
|
47319
47694
|
const grepName = "grep";
|
|
@@ -47330,6 +47705,8 @@ function buildWorkflowHints(opts) {
|
|
|
47330
47705
|
const hasInspect = !opts.disabledTools.has("aft_inspect");
|
|
47331
47706
|
const hasBash = !opts.disabledTools.has(bashName);
|
|
47332
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);
|
|
47333
47710
|
if (hasBash && opts.bashCompressionEnabled) {
|
|
47334
47711
|
sections.push([
|
|
47335
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:",
|
|
@@ -47371,17 +47748,19 @@ function buildWorkflowHints(opts) {
|
|
|
47371
47748
|
].join(`
|
|
47372
47749
|
`));
|
|
47373
47750
|
}
|
|
47374
|
-
if (
|
|
47751
|
+
if (hasBgBash && hasWatch) {
|
|
47375
47752
|
sections.push([
|
|
47376
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.`,
|
|
47377
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.",
|
|
47378
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."
|
|
47379
47756
|
].join(`
|
|
47380
47757
|
`));
|
|
47758
|
+
}
|
|
47759
|
+
if (hasBgBash && hasWrite) {
|
|
47381
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: "..." })\`.`);
|
|
47382
47761
|
}
|
|
47383
47762
|
if (opts.hashlineEffective === true) {
|
|
47384
|
-
sections.push(
|
|
47763
|
+
sections.push(hashlineTagSourceHint(hasBash && opts.bashRewriteEnabled !== false, (name) => !opts.disabledTools.has(name)));
|
|
47385
47764
|
}
|
|
47386
47765
|
if (sections.length === 0) {
|
|
47387
47766
|
return null;
|
|
@@ -47396,13 +47775,32 @@ ${sections.join(`
|
|
|
47396
47775
|
`)}`;
|
|
47397
47776
|
}
|
|
47398
47777
|
function buildHintsFromConfig(config, disabledTools, hashlineEffective = false) {
|
|
47778
|
+
const bashCfg = resolveBashConfig(config);
|
|
47399
47779
|
return buildWorkflowHints({
|
|
47400
|
-
bashBackgroundEnabled:
|
|
47401
|
-
bashCompressionEnabled:
|
|
47780
|
+
bashBackgroundEnabled: bashCfg.background,
|
|
47781
|
+
bashCompressionEnabled: bashCfg.compress,
|
|
47782
|
+
bashRewriteEnabled: bashCfg.rewrite,
|
|
47402
47783
|
disabledTools,
|
|
47403
47784
|
hashlineEffective
|
|
47404
47785
|
});
|
|
47405
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
|
+
}
|
|
47406
47804
|
function appendHintsToSystem(system, hintsBlock) {
|
|
47407
47805
|
if (!hintsBlock)
|
|
47408
47806
|
return;
|
|
@@ -47773,36 +48171,13 @@ Install: ${getManualInstallHint()}`);
|
|
|
47773
48171
|
signal: autoUpdateAbort.signal,
|
|
47774
48172
|
storageDir: ctx.storageDir
|
|
47775
48173
|
});
|
|
47776
|
-
const HINTS_TOOL_NAMES = [
|
|
47777
|
-
"aft_outline",
|
|
47778
|
-
"aft_zoom",
|
|
47779
|
-
"aft_search",
|
|
47780
|
-
"aft_callgraph",
|
|
47781
|
-
"aft_inspect",
|
|
47782
|
-
"grep",
|
|
47783
|
-
"read",
|
|
47784
|
-
"bash",
|
|
47785
|
-
"bash_status"
|
|
47786
|
-
];
|
|
47787
48174
|
const registeredTools = new Set(Object.keys(allTools));
|
|
47788
48175
|
const { hashlineEditRegistered, aftSearchRegistered } = applyToolSurfaceOverrides(pool, aftConfig, registeredTools);
|
|
47789
48176
|
ctx.hashlineEffective = hashlineEditRegistered;
|
|
47790
48177
|
const hashlineDowngrade = reportHashlineDowngrade(aftConfig, registeredTools, (message) => deliverConfigMigrationWarnings(registrationRoot, [message]));
|
|
47791
48178
|
log2(`hashline activation decision requested=${aftConfig.edit_mode === "hashline"} ` + `edit_slot_survives=${hashlineEditRegistered} effective=${ctx.hashlineEffective}` + (hashlineDowngrade ? ` downgraded=${hashlineDowngrade.code}` : ""));
|
|
47792
48179
|
ctx.aftSearchRegistered = aftSearchRegistered;
|
|
47793
|
-
|
|
47794
|
-
const def = allTools[name];
|
|
47795
|
-
if (def) {
|
|
47796
|
-
const bashCfg = resolveBashConfig(aftConfig);
|
|
47797
|
-
def.description = bashToolDescription(aftSearchRegistered, bashCfg.compress, bashCfg.background, bashCfg.detach_on_user_message);
|
|
47798
|
-
}
|
|
47799
|
-
}
|
|
47800
|
-
const hintsAbsentTools = new Set;
|
|
47801
|
-
for (const name of HINTS_TOOL_NAMES) {
|
|
47802
|
-
if (!registeredTools.has(name))
|
|
47803
|
-
hintsAbsentTools.add(name);
|
|
47804
|
-
}
|
|
47805
|
-
const hintsBlock = buildHintsFromConfig(aftConfig, hintsAbsentTools, hashlineEditRegistered);
|
|
48180
|
+
const hintsBlock = buildHintsForRegisteredTools(aftConfig, registeredTools, hashlineEditRegistered);
|
|
47806
48181
|
if (hintsBlock) {
|
|
47807
48182
|
log2(`Workflow hints injected (${hintsBlock.length} chars)`);
|
|
47808
48183
|
}
|