@cortexkit/aft-pi 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.d.ts +19 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/hashline-tag-sources.d.ts +18 -0
- package/dist/hashline-tag-sources.d.ts.map +1 -0
- package/dist/index.js +521 -198
- 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/tool-registration.d.ts +4 -3
- package/dist/tool-registration.d.ts.map +1 -1
- package/dist/tools/_shared.d.ts.map +1 -1
- package/dist/tools/bash.d.ts +18 -6
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/hoisted.d.ts.map +1 -1
- package/dist/tools/reading.d.ts.map +1 -1
- package/dist/workflow-hints.d.ts +15 -6
- package/dist/workflow-hints.d.ts.map +1 -1
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -7935,32 +7935,79 @@ function error(message, meta) {
|
|
|
7935
7935
|
function isTerminalStatus(status) {
|
|
7936
7936
|
return status === "completed" || status === "failed" || status === "killed" || status === "timed_out" || status === "fate_unknown";
|
|
7937
7937
|
}
|
|
7938
|
-
function sleep(ms) {
|
|
7939
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
7940
|
-
}
|
|
7941
|
-
function monotonicNowMs() {
|
|
7942
|
-
return performance.now();
|
|
7943
|
-
}
|
|
7944
7938
|
// ../aft-bridge/dist/bash-hints.js
|
|
7945
7939
|
import * as fs from "node:fs";
|
|
7946
7940
|
import * as os from "node:os";
|
|
7947
7941
|
import * as path from "node:path";
|
|
7948
7942
|
var DEFAULT_PRIMARY_WATCH_TIMEOUT_MS = 30000;
|
|
7949
|
-
var
|
|
7943
|
+
var MAX_WATCH_TIMEOUT_MS = 1800000;
|
|
7944
|
+
var LONGEST_TIMER_DELAY_MS = 2147483647;
|
|
7945
|
+
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";
|
|
7946
|
+
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.";
|
|
7950
7947
|
function resolveWatchTimeoutMs(requestedMs, role, capMs) {
|
|
7951
|
-
|
|
7952
|
-
|
|
7948
|
+
if (role === "worker")
|
|
7949
|
+
return requestedMs;
|
|
7950
|
+
return Math.min(requestedMs ?? DEFAULT_PRIMARY_WATCH_TIMEOUT_MS, capMs);
|
|
7951
|
+
}
|
|
7952
|
+
function maxWatchTimeoutMs(role, capMs) {
|
|
7953
|
+
return role === "worker" ? MAX_WATCH_TIMEOUT_MS : capMs;
|
|
7954
|
+
}
|
|
7955
|
+
function watchPollDelayMs(elapsedMs) {
|
|
7956
|
+
if (elapsedMs < 5000)
|
|
7957
|
+
return 100;
|
|
7958
|
+
if (elapsedMs < 30000)
|
|
7959
|
+
return 250;
|
|
7960
|
+
if (elapsedMs < 120000)
|
|
7961
|
+
return 500;
|
|
7962
|
+
return 1000;
|
|
7963
|
+
}
|
|
7964
|
+
var watchClock = {
|
|
7965
|
+
now: () => performance.now(),
|
|
7966
|
+
sleep: abortableSleep
|
|
7967
|
+
};
|
|
7968
|
+
function abortableSleep(ms, signal) {
|
|
7969
|
+
if (signal?.aborted)
|
|
7970
|
+
return Promise.resolve();
|
|
7971
|
+
return new Promise((resolve) => {
|
|
7972
|
+
const done = () => {
|
|
7973
|
+
clearTimeout(timer);
|
|
7974
|
+
signal?.removeEventListener("abort", done);
|
|
7975
|
+
resolve();
|
|
7976
|
+
};
|
|
7977
|
+
const timer = setTimeout(done, ms);
|
|
7978
|
+
signal?.addEventListener("abort", done, { once: true });
|
|
7979
|
+
});
|
|
7953
7980
|
}
|
|
7954
7981
|
function formatWatchWaited(elapsedMs, limitMs, capMs) {
|
|
7955
|
-
|
|
7982
|
+
let limit;
|
|
7983
|
+
if (limitMs === undefined)
|
|
7984
|
+
limit = "no limit: waits until the command finishes";
|
|
7985
|
+
else if (capMs !== undefined && limitMs >= capMs)
|
|
7986
|
+
limit = `limit ${limitMs}ms, the bash.watch_sync_max_ms cap`;
|
|
7987
|
+
else
|
|
7988
|
+
limit = `limit ${limitMs}ms`;
|
|
7956
7989
|
return `Waited ${Math.round(elapsedMs)}ms (${limit})`;
|
|
7957
7990
|
}
|
|
7958
|
-
function watchTimeoutSteer(role,
|
|
7991
|
+
function watchTimeoutSteer(role, timeoutParam = "timeoutMs") {
|
|
7959
7992
|
if (role === "worker") {
|
|
7960
|
-
return `The command is still running; this is not a failure. Watch again
|
|
7993
|
+
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.`;
|
|
7961
7994
|
}
|
|
7962
7995
|
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.";
|
|
7963
7996
|
}
|
|
7997
|
+
var WATCH_UNAVAILABLE_GIVE_UP_MS = 120000;
|
|
7998
|
+
var WORKER_KEEP_WAITING = "Call bash_watch again to keep waiting; don't report a result until the command finishes.";
|
|
7999
|
+
function interruptedWatchTail(role, primaryTail) {
|
|
8000
|
+
return role === "worker" ? `The task is still running. ${WORKER_KEEP_WAITING}` : primaryTail;
|
|
8001
|
+
}
|
|
8002
|
+
function runningTaskStatusHint(role) {
|
|
8003
|
+
return role === "worker" ? "To wait for it, call bash_watch; don't poll." : "A completion reminder will be delivered automatically; don't poll.";
|
|
8004
|
+
}
|
|
8005
|
+
function watchUnavailableSteer(role) {
|
|
8006
|
+
const unknown = "the bridge was busy, so task state is unknown.";
|
|
8007
|
+
if (role === "worker")
|
|
8008
|
+
return `${unknown} ${WORKER_KEEP_WAITING}`;
|
|
8009
|
+
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.`;
|
|
8010
|
+
}
|
|
7964
8011
|
var CONFLICT_HINT = `
|
|
7965
8012
|
|
|
7966
8013
|
[Hint] Use aft_conflicts to see all conflict regions across files in a single call.`;
|
|
@@ -8819,6 +8866,9 @@ function isPassiveCommand(command) {
|
|
|
8819
8866
|
return PASSIVE_COMMANDS.has(command);
|
|
8820
8867
|
}
|
|
8821
8868
|
|
|
8869
|
+
// ../aft-bridge/dist/transport.js
|
|
8870
|
+
var WORKER_SESSION_FIELD = "worker_session";
|
|
8871
|
+
|
|
8822
8872
|
// ../aft-bridge/dist/bridge.js
|
|
8823
8873
|
var DEFAULT_BRIDGE_TIMEOUT_MS = 30000;
|
|
8824
8874
|
var BRIDGE_HANG_TIMEOUT_THRESHOLD = 2;
|
|
@@ -9237,9 +9287,11 @@ class BinaryBridge {
|
|
|
9237
9287
|
params.edit_slot_survives = editSlotSurvives;
|
|
9238
9288
|
this.logHashlineRegistrationCarrier("tool_call", sessionId, editSlotSurvives);
|
|
9239
9289
|
}
|
|
9240
|
-
const { preview, ...sendOptions } = options ?? {};
|
|
9290
|
+
const { preview, workerSession, ...sendOptions } = options ?? {};
|
|
9241
9291
|
if (preview === true)
|
|
9242
9292
|
params.preview = true;
|
|
9293
|
+
if (workerSession === true)
|
|
9294
|
+
params[WORKER_SESSION_FIELD] = true;
|
|
9243
9295
|
return await this.send("tool_call", params, Object.keys(sendOptions).length > 0 ? sendOptions : undefined);
|
|
9244
9296
|
}
|
|
9245
9297
|
async sendWithVersionMismatchRetry(command, params, options, canRetryAfterVersionSwap) {
|
|
@@ -9308,7 +9360,7 @@ class BinaryBridge {
|
|
|
9308
9360
|
if (command === "cancel_request" || Object.hasOwn(params, "command") || Object.hasOwn(params, "method")) {
|
|
9309
9361
|
const nested = { ...params };
|
|
9310
9362
|
const reserved = {};
|
|
9311
|
-
for (const key of ["session_id", "lsp_hints"]) {
|
|
9363
|
+
for (const key of ["session_id", "lsp_hints", WORKER_SESSION_FIELD]) {
|
|
9312
9364
|
if (Object.hasOwn(nested, key)) {
|
|
9313
9365
|
reserved[key] = nested[key];
|
|
9314
9366
|
delete nested[key];
|
|
@@ -10091,7 +10143,8 @@ function renderCallersGroupLines(group, theme) {
|
|
|
10091
10143
|
const bySymbolProvenance = new Map;
|
|
10092
10144
|
for (const caller of callers) {
|
|
10093
10145
|
const symbol = asString(caller.symbol) ?? "(unknown)";
|
|
10094
|
-
const
|
|
10146
|
+
const via = asString(caller.via) ?? "";
|
|
10147
|
+
const provenanceKey = asString(caller.resolved_by) === "name_match" ? `${symbol}\x00${via}\x00name_match` : `${symbol}\x00${via}\x00exact`;
|
|
10095
10148
|
const line = asNumber(caller.line);
|
|
10096
10149
|
const bucket = bySymbolProvenance.get(provenanceKey) ?? [];
|
|
10097
10150
|
if (line !== undefined)
|
|
@@ -10100,12 +10153,13 @@ function renderCallersGroupLines(group, theme) {
|
|
|
10100
10153
|
}
|
|
10101
10154
|
const keys = [...bySymbolProvenance.keys()].sort((a, b) => a.localeCompare(b));
|
|
10102
10155
|
for (const key of keys) {
|
|
10103
|
-
const symbol = key.split("\x00")
|
|
10156
|
+
const [symbol = "(unknown)", via = ""] = key.split("\x00");
|
|
10104
10157
|
const isNameMatch = key.endsWith("\x00name_match");
|
|
10105
10158
|
const lineNums = (bySymbolProvenance.get(key) ?? []).sort((a, b) => a - b);
|
|
10106
10159
|
const linePart = lineNums.length > 0 ? lineNums.map(String).join(", ") : "?";
|
|
10107
10160
|
const marker = isNameMatch ? ` ${theme.fg("warning", "~")}` : "";
|
|
10108
|
-
|
|
10161
|
+
const viaPart = via ? ` via ${via}` : "";
|
|
10162
|
+
lines.push(` ↳ ${symbol}:${linePart}${marker}${viaPart}`);
|
|
10109
10163
|
}
|
|
10110
10164
|
return lines;
|
|
10111
10165
|
}
|
|
@@ -10139,6 +10193,9 @@ function formatCallgraphSections(op, response, theme = PLAIN_CALLGRAPH_THEME, op
|
|
|
10139
10193
|
sections.push(renderCallersGroupLines(group, theme).join(`
|
|
10140
10194
|
`));
|
|
10141
10195
|
});
|
|
10196
|
+
const macroNote = asString(asRecord(record.macro_note)?.message);
|
|
10197
|
+
if (macroNote)
|
|
10198
|
+
sections.push(theme.fg("warning", macroNote));
|
|
10142
10199
|
return sections;
|
|
10143
10200
|
}
|
|
10144
10201
|
if (op === "trace_to_symbol") {
|
|
@@ -10352,6 +10409,35 @@ function coerceJsonCollectionParam(value, field) {
|
|
|
10352
10409
|
}
|
|
10353
10410
|
throw new Error(`${field} must be ${shape} (got an invalid type)`);
|
|
10354
10411
|
}
|
|
10412
|
+
function isBlankParam(value) {
|
|
10413
|
+
if (typeof value === "string")
|
|
10414
|
+
return value.trim().length === 0;
|
|
10415
|
+
return isEmptyParam(value);
|
|
10416
|
+
}
|
|
10417
|
+
function usableZoomTargets(targets) {
|
|
10418
|
+
if (isEmptyParam(targets))
|
|
10419
|
+
return [];
|
|
10420
|
+
const entries = Array.isArray(targets) ? targets : [targets];
|
|
10421
|
+
return entries.filter((entry) => {
|
|
10422
|
+
if (entry === null || entry === undefined)
|
|
10423
|
+
return false;
|
|
10424
|
+
if (typeof entry !== "object")
|
|
10425
|
+
return true;
|
|
10426
|
+
const record = entry;
|
|
10427
|
+
return !(isBlankParam(record.path) && isBlankParam(record.filePath) && isBlankParam(record.symbol));
|
|
10428
|
+
});
|
|
10429
|
+
}
|
|
10430
|
+
function isFindReplaceOnlyEdit(args) {
|
|
10431
|
+
const absent = (value) => value === undefined || value === null || value === "";
|
|
10432
|
+
const absentLine = (value) => absent(value) || value === 0;
|
|
10433
|
+
const edits = args.edits;
|
|
10434
|
+
return Array.isArray(edits) && edits.length > 0 && absent(args.appendContent) && absent(args.symbol) && absent(args.content) && edits.every((entry) => {
|
|
10435
|
+
if (entry === null || typeof entry !== "object")
|
|
10436
|
+
return false;
|
|
10437
|
+
const item = entry;
|
|
10438
|
+
return typeof item.oldString === "string" && (absent(item.newString) || typeof item.newString === "string") && absentLine(item.startLine) && absentLine(item.endLine) && absent(item.content);
|
|
10439
|
+
});
|
|
10440
|
+
}
|
|
10355
10441
|
// ../aft-bridge/dist/config-error.js
|
|
10356
10442
|
import { homedir as homedir6 } from "node:os";
|
|
10357
10443
|
import { isAbsolute as isAbsolute2, join as join6 } from "node:path";
|
|
@@ -13656,6 +13742,13 @@ class SubcTransportShuttingDownError extends SubcCallError {
|
|
|
13656
13742
|
this.name = "SubcTransportShuttingDownError";
|
|
13657
13743
|
}
|
|
13658
13744
|
}
|
|
13745
|
+
|
|
13746
|
+
class ReconnectKeyChangedError extends Error {
|
|
13747
|
+
constructor() {
|
|
13748
|
+
super("the subc daemon restarted and its connection key changed; the reconnect did not complete within the deadline, retry");
|
|
13749
|
+
this.name = "ReconnectKeyChangedError";
|
|
13750
|
+
}
|
|
13751
|
+
}
|
|
13659
13752
|
function isSubcClientClosedError(error) {
|
|
13660
13753
|
return error instanceof SubcError && error.message === "client closed";
|
|
13661
13754
|
}
|
|
@@ -14158,6 +14251,8 @@ class SubcTransport {
|
|
|
14158
14251
|
body.edit_slot_survives = editSlotSurvives;
|
|
14159
14252
|
if (preview === true)
|
|
14160
14253
|
body.preview = true;
|
|
14254
|
+
if (options?.workerSession === true)
|
|
14255
|
+
body[WORKER_SESSION_FIELD] = true;
|
|
14161
14256
|
const reply = await this.pool.routeRequest(this.identityFor(sessionId), body, timeoutMs, onProgress, this.generation, abortSignal);
|
|
14162
14257
|
return reliftReply(reply);
|
|
14163
14258
|
}
|
|
@@ -14168,7 +14263,10 @@ class SubcTransport {
|
|
|
14168
14263
|
}
|
|
14169
14264
|
const { timeoutMs, onProgress, abortSignal } = this.splitOptions(options);
|
|
14170
14265
|
const session = typeof params.session_id === "string" ? params.session_id : undefined;
|
|
14171
|
-
const
|
|
14266
|
+
const { [WORKER_SESSION_FIELD]: workerSession, ...args } = params;
|
|
14267
|
+
const body = { name: command, arguments: args };
|
|
14268
|
+
if (workerSession === true)
|
|
14269
|
+
body[WORKER_SESSION_FIELD] = true;
|
|
14172
14270
|
const editSlotSurvives = this.pool.getEditSlotSurvives();
|
|
14173
14271
|
if (editSlotSurvives !== undefined)
|
|
14174
14272
|
body.edit_slot_survives = editSlotSurvives;
|
|
@@ -14205,6 +14303,7 @@ class SubcTransportPool {
|
|
|
14205
14303
|
lifecycleRegistration = null;
|
|
14206
14304
|
outerFacadeEvictor;
|
|
14207
14305
|
client = null;
|
|
14306
|
+
hadWorkingClient = false;
|
|
14208
14307
|
connecting = null;
|
|
14209
14308
|
routeReopenRetryDelayMs = RECONNECT_RETRY_FLOOR_MS;
|
|
14210
14309
|
routeReopenRetry = null;
|
|
@@ -14649,6 +14748,10 @@ class SubcTransportPool {
|
|
|
14649
14748
|
}
|
|
14650
14749
|
async routeRequest(identity, body, timeoutMs, onProgress, expectedGeneration, abortSignal) {
|
|
14651
14750
|
const callStartedAt = performance.now();
|
|
14751
|
+
const callDeadlineMs = timeoutMs ?? SUBC_DEFAULT_REQUEST_TIMEOUT_MS;
|
|
14752
|
+
let scheduledDelayMs = 0;
|
|
14753
|
+
const spentMs = () => Math.max(performance.now() - callStartedAt, scheduledDelayMs);
|
|
14754
|
+
const remainingMs = () => callDeadlineMs - spentMs();
|
|
14652
14755
|
const root = asCanonicalRootPath(identity.project_root);
|
|
14653
14756
|
let generation = expectedGeneration;
|
|
14654
14757
|
if (this.lifecycleEnabled()) {
|
|
@@ -14664,7 +14767,49 @@ class SubcTransportPool {
|
|
|
14664
14767
|
try {
|
|
14665
14768
|
let client;
|
|
14666
14769
|
try {
|
|
14667
|
-
|
|
14770
|
+
let keyChanged;
|
|
14771
|
+
while (true) {
|
|
14772
|
+
const remaining = remainingMs();
|
|
14773
|
+
if (remaining <= 0)
|
|
14774
|
+
throw keyChanged ?? callDeadlinePassedError(undefined, callDeadlineMs);
|
|
14775
|
+
let timer;
|
|
14776
|
+
try {
|
|
14777
|
+
const acquired = await Promise.race([
|
|
14778
|
+
this.ensureClient(),
|
|
14779
|
+
new Promise((resolve) => {
|
|
14780
|
+
timer = setTimeout(() => resolve(CALL_DEADLINE_EXPIRED), remaining);
|
|
14781
|
+
})
|
|
14782
|
+
]);
|
|
14783
|
+
if (acquired === CALL_DEADLINE_EXPIRED) {
|
|
14784
|
+
throw keyChanged ?? callDeadlinePassedError(undefined, callDeadlineMs);
|
|
14785
|
+
}
|
|
14786
|
+
client = acquired;
|
|
14787
|
+
break;
|
|
14788
|
+
} catch (error) {
|
|
14789
|
+
if (!(error instanceof ReconnectKeyChangedError))
|
|
14790
|
+
throw error;
|
|
14791
|
+
keyChanged = error;
|
|
14792
|
+
if (spentMs() + this.nextRouteReopenDelayMs() >= callDeadlineMs)
|
|
14793
|
+
throw error;
|
|
14794
|
+
} finally {
|
|
14795
|
+
clearTimeout(timer);
|
|
14796
|
+
}
|
|
14797
|
+
const { delayMs, wait } = this.waitForRouteReopenBackoff();
|
|
14798
|
+
scheduledDelayMs = spentMs() + delayMs;
|
|
14799
|
+
let retryTimer;
|
|
14800
|
+
try {
|
|
14801
|
+
const waited = await Promise.race([
|
|
14802
|
+
wait,
|
|
14803
|
+
new Promise((resolve) => {
|
|
14804
|
+
retryTimer = setTimeout(() => resolve(CALL_DEADLINE_EXPIRED), callDeadlineMs - (performance.now() - callStartedAt));
|
|
14805
|
+
})
|
|
14806
|
+
]);
|
|
14807
|
+
if (waited === CALL_DEADLINE_EXPIRED)
|
|
14808
|
+
throw keyChanged;
|
|
14809
|
+
} finally {
|
|
14810
|
+
clearTimeout(retryTimer);
|
|
14811
|
+
}
|
|
14812
|
+
}
|
|
14668
14813
|
this.assertRecordLive(record);
|
|
14669
14814
|
} catch (error) {
|
|
14670
14815
|
throw this.annotateReapError(error, record);
|
|
@@ -14699,12 +14844,8 @@ class SubcTransportPool {
|
|
|
14699
14844
|
throw error;
|
|
14700
14845
|
}
|
|
14701
14846
|
};
|
|
14702
|
-
const callDeadlineMs = timeoutMs ?? SUBC_DEFAULT_REQUEST_TIMEOUT_MS;
|
|
14703
14847
|
const reloadWaitBudgetMs = Math.min(callDeadlineMs, ROUTE_OPEN_RELOAD_WAIT_CEILING_MS);
|
|
14704
|
-
let scheduledDelayMs = 0;
|
|
14705
14848
|
let lastRefusal;
|
|
14706
|
-
const spentMs = () => Math.max(performance.now() - callStartedAt, scheduledDelayMs);
|
|
14707
|
-
const remainingMs = () => callDeadlineMs - spentMs();
|
|
14708
14849
|
const openRouteWithinDeadline = async () => {
|
|
14709
14850
|
const remaining = remainingMs();
|
|
14710
14851
|
if (remaining <= 0)
|
|
@@ -14907,10 +15048,14 @@ class SubcTransportPool {
|
|
|
14907
15048
|
throw new SubcTransportShuttingDownError;
|
|
14908
15049
|
}
|
|
14909
15050
|
this.client = client;
|
|
15051
|
+
this.hadWorkingClient = true;
|
|
14910
15052
|
this.transportFailures = 0;
|
|
14911
15053
|
return client;
|
|
14912
15054
|
}).catch((error) => {
|
|
14913
15055
|
this.connecting = null;
|
|
15056
|
+
if (this.hadWorkingClient && error instanceof AuthError) {
|
|
15057
|
+
throw new ReconnectKeyChangedError;
|
|
15058
|
+
}
|
|
14914
15059
|
throw error;
|
|
14915
15060
|
});
|
|
14916
15061
|
return this.connecting;
|
|
@@ -17903,31 +18048,36 @@ var ORT_PLATFORM_MAP = {
|
|
|
17903
18048
|
arm64: {
|
|
17904
18049
|
assetName: `onnxruntime-osx-arm64-${ORT_VERSION}`,
|
|
17905
18050
|
libName: "libonnxruntime.dylib",
|
|
17906
|
-
archiveType: "tgz"
|
|
18051
|
+
archiveType: "tgz",
|
|
18052
|
+
librarySha256: "872533f130f1839a5bc01788ddb4f75c83a189763441ba1178788ed965449289"
|
|
17907
18053
|
}
|
|
17908
18054
|
},
|
|
17909
18055
|
linux: {
|
|
17910
18056
|
x64: {
|
|
17911
18057
|
assetName: `onnxruntime-linux-x64-${ORT_VERSION}`,
|
|
17912
18058
|
libName: "libonnxruntime.so",
|
|
17913
|
-
archiveType: "tgz"
|
|
18059
|
+
archiveType: "tgz",
|
|
18060
|
+
librarySha256: "d132535d051344ff5c64c9c200004150559049a81ed330eb4422c1962fb6b7e4"
|
|
17914
18061
|
},
|
|
17915
18062
|
arm64: {
|
|
17916
18063
|
assetName: `onnxruntime-linux-aarch64-${ORT_VERSION}`,
|
|
17917
18064
|
libName: "libonnxruntime.so",
|
|
17918
|
-
archiveType: "tgz"
|
|
18065
|
+
archiveType: "tgz",
|
|
18066
|
+
librarySha256: "52b2a0e75e79468404284fec38ad5ee1a7a996232274f5e1b84f3e793fb07554"
|
|
17919
18067
|
}
|
|
17920
18068
|
},
|
|
17921
18069
|
win32: {
|
|
17922
18070
|
x64: {
|
|
17923
18071
|
assetName: `onnxruntime-win-x64-${ORT_VERSION}`,
|
|
17924
18072
|
libName: "onnxruntime.dll",
|
|
17925
|
-
archiveType: "zip"
|
|
18073
|
+
archiveType: "zip",
|
|
18074
|
+
librarySha256: "b95efb2113b603bbbf3f191061c5516a871ed546893c820e4f3b7b6c358dbf2a"
|
|
17926
18075
|
},
|
|
17927
18076
|
arm64: {
|
|
17928
18077
|
assetName: `onnxruntime-win-arm64-${ORT_VERSION}`,
|
|
17929
18078
|
libName: "onnxruntime.dll",
|
|
17930
|
-
archiveType: "zip"
|
|
18079
|
+
archiveType: "zip",
|
|
18080
|
+
librarySha256: "724d81ac50b11bfaa01ab3ce01b99fb6734a4b762259a8be4395ca662ce99fe6"
|
|
17931
18081
|
}
|
|
17932
18082
|
}
|
|
17933
18083
|
};
|
|
@@ -18414,6 +18564,9 @@ async function downloadOnnxRuntime(info, targetDir) {
|
|
|
18414
18564
|
copyOnnxLibraries(info, extractedDir, stagedInstallDir, realFiles, symlinks);
|
|
18415
18565
|
const libPath = join14(stagedInstallDir, info.libName);
|
|
18416
18566
|
const libHash = sha256File2(libPath);
|
|
18567
|
+
if (libHash !== info.librarySha256) {
|
|
18568
|
+
throw new Error(`onnx runtime library hash mismatch at ${libPath}: sha256 ${libHash}, ` + `expected the pinned ${info.librarySha256} for ${info.assetName}`);
|
|
18569
|
+
}
|
|
18417
18570
|
writeOnnxInstalledMeta(stagedInstallDir, ORT_VERSION, libHash, archiveSha256);
|
|
18418
18571
|
publishOnnxRuntime(stagedInstallDir, targetDir);
|
|
18419
18572
|
rmSync3(tmpDir, { recursive: true, force: true });
|
|
@@ -18756,12 +18909,13 @@ function normalizeZoomTargets(record) {
|
|
|
18756
18909
|
throw new InvalidRequestError(`'targets[${index}].path' must be a non-empty string`);
|
|
18757
18910
|
}
|
|
18758
18911
|
const source = target;
|
|
18759
|
-
const
|
|
18912
|
+
const blank = (value) => value === undefined || value === null || typeof value === "string" && !value.trim();
|
|
18913
|
+
const emptyTarget = blank(source.symbol) && blank(source.path) && blank(source.filePath);
|
|
18760
18914
|
if (emptyTarget)
|
|
18761
18915
|
return { ...source };
|
|
18762
18916
|
const normalized = { ...source };
|
|
18763
18917
|
try {
|
|
18764
|
-
normalizeAliasPair(normalized, "path", "filePath",
|
|
18918
|
+
normalizeAliasPair(normalized, "path", "filePath", false);
|
|
18765
18919
|
} catch (error) {
|
|
18766
18920
|
if (error instanceof InvalidRequestError) {
|
|
18767
18921
|
throw new InvalidRequestError(error.message.replace("'filePath'", `'targets[${index}].filePath'`).replace("'path'", `'targets[${index}].path'`));
|
|
@@ -20342,7 +20496,7 @@ import {
|
|
|
20342
20496
|
// package.json
|
|
20343
20497
|
var package_default = {
|
|
20344
20498
|
name: "@cortexkit/aft-pi",
|
|
20345
|
-
version: "0.58.
|
|
20499
|
+
version: "0.58.2",
|
|
20346
20500
|
type: "module",
|
|
20347
20501
|
description: "Pi coding agent extension for Agent File Tools (AFT) — tree-sitter and LSP-powered code analysis",
|
|
20348
20502
|
main: "dist/index.js",
|
|
@@ -20365,18 +20519,18 @@ var package_default = {
|
|
|
20365
20519
|
"test:unit": "bun test src/__tests__ --path-ignore-patterns 'src/__tests__/e2e/**'"
|
|
20366
20520
|
},
|
|
20367
20521
|
dependencies: {
|
|
20368
|
-
"@cortexkit/aft-bridge": "0.58.
|
|
20522
|
+
"@cortexkit/aft-bridge": "0.58.2",
|
|
20369
20523
|
"comment-json": "^5.0.0",
|
|
20370
20524
|
diff: "^8.0.4",
|
|
20371
20525
|
zod: "^4.4.3"
|
|
20372
20526
|
},
|
|
20373
20527
|
optionalDependencies: {
|
|
20374
|
-
"@cortexkit/aft-darwin-arm64": "0.58.
|
|
20375
|
-
"@cortexkit/aft-darwin-x64": "0.58.
|
|
20376
|
-
"@cortexkit/aft-linux-arm64": "0.58.
|
|
20377
|
-
"@cortexkit/aft-linux-x64": "0.58.
|
|
20378
|
-
"@cortexkit/aft-win32-arm64": "0.58.
|
|
20379
|
-
"@cortexkit/aft-win32-x64": "0.58.
|
|
20528
|
+
"@cortexkit/aft-darwin-arm64": "0.58.2",
|
|
20529
|
+
"@cortexkit/aft-darwin-x64": "0.58.2",
|
|
20530
|
+
"@cortexkit/aft-linux-arm64": "0.58.2",
|
|
20531
|
+
"@cortexkit/aft-linux-x64": "0.58.2",
|
|
20532
|
+
"@cortexkit/aft-win32-arm64": "0.58.2",
|
|
20533
|
+
"@cortexkit/aft-win32-x64": "0.58.2"
|
|
20380
20534
|
},
|
|
20381
20535
|
devDependencies: {
|
|
20382
20536
|
"@earendil-works/pi-coding-agent": "^0.80.2",
|
|
@@ -20656,6 +20810,24 @@ function appendDegradedStatus(lines, status, markdown) {
|
|
|
20656
20810
|
// src/tools/_shared.ts
|
|
20657
20811
|
import { existsSync as existsSync12 } from "node:fs";
|
|
20658
20812
|
import { Type } from "typebox";
|
|
20813
|
+
|
|
20814
|
+
// src/session-kind.ts
|
|
20815
|
+
var MAGIC_CONTEXT_SUBAGENT_ENV = "MAGIC_CONTEXT_PI_SUBAGENT";
|
|
20816
|
+
function piWorkerKind(extCtx, env = process.env) {
|
|
20817
|
+
if (env[MAGIC_CONTEXT_SUBAGENT_ENV] === "1")
|
|
20818
|
+
return "delegated";
|
|
20819
|
+
if (extCtx?.hasUI === false)
|
|
20820
|
+
return "headless";
|
|
20821
|
+
return;
|
|
20822
|
+
}
|
|
20823
|
+
function isPiWorkerSession(extCtx, env = process.env) {
|
|
20824
|
+
return piWorkerKind(extCtx, env) !== undefined;
|
|
20825
|
+
}
|
|
20826
|
+
function skipsEagerStartup(env = process.env) {
|
|
20827
|
+
return piWorkerKind(undefined, env) === "delegated";
|
|
20828
|
+
}
|
|
20829
|
+
|
|
20830
|
+
// src/tools/_shared.ts
|
|
20659
20831
|
var optionalInt = (min, max, description = "(integer)") => Type.Optional(Type.Union([Type.Integer({ minimum: min, maximum: max }), Type.String()], {
|
|
20660
20832
|
description
|
|
20661
20833
|
}));
|
|
@@ -20741,6 +20913,9 @@ async function callBridge(bridge, command, params = {}, extCtx, options) {
|
|
|
20741
20913
|
if (sessionId) {
|
|
20742
20914
|
merged.session_id = sessionId;
|
|
20743
20915
|
}
|
|
20916
|
+
if (isPiWorkerSession(extCtx)) {
|
|
20917
|
+
merged[WORKER_SESSION_FIELD] = true;
|
|
20918
|
+
}
|
|
20744
20919
|
const sendOptions = {
|
|
20745
20920
|
...timeoutMs !== undefined ? { timeoutMs } : {},
|
|
20746
20921
|
configureWarningClient: extCtx,
|
|
@@ -20768,6 +20943,7 @@ async function callToolCallForSession(bridge, name, rawArgs, sessionId, extCtx,
|
|
|
20768
20943
|
const sendOptions = {
|
|
20769
20944
|
...timeoutMs !== undefined ? { timeoutMs } : {},
|
|
20770
20945
|
configureWarningClient: extCtx,
|
|
20946
|
+
...isPiWorkerSession(extCtx) ? { workerSession: true } : {},
|
|
20771
20947
|
...options
|
|
20772
20948
|
};
|
|
20773
20949
|
let response;
|
|
@@ -25985,6 +26161,10 @@ var SubcConfigSchema = object({
|
|
|
25985
26161
|
var GhShimConfigSchema = object({
|
|
25986
26162
|
binary_path: string2().trim().refine(isAbsolute7, "gh_shim.binary_path must be absolute").optional()
|
|
25987
26163
|
});
|
|
26164
|
+
var OpenCodeHostConfigSchema = object({
|
|
26165
|
+
server_url: string2().optional(),
|
|
26166
|
+
server_password_env: string2().optional()
|
|
26167
|
+
});
|
|
25988
26168
|
var GithubConfigSchema = object({
|
|
25989
26169
|
shim: boolean2().optional(),
|
|
25990
26170
|
read: boolean2().optional(),
|
|
@@ -26060,6 +26240,7 @@ var AftConfigFieldsSchema = object({
|
|
|
26060
26240
|
search: SearchConfigSchema.optional(),
|
|
26061
26241
|
bridge: BridgeConfigSchema.optional(),
|
|
26062
26242
|
subc: SubcConfigSchema.optional(),
|
|
26243
|
+
opencode: OpenCodeHostConfigSchema.optional(),
|
|
26063
26244
|
github: GithubConfigSchema.optional(),
|
|
26064
26245
|
gh_shim: GhShimConfigSchema.optional(),
|
|
26065
26246
|
git: GitConfigSchema.optional()
|
|
@@ -26552,6 +26733,8 @@ function getStrippedTopLevelKeys(override) {
|
|
|
26552
26733
|
stripped.push("sandbox.write_allow");
|
|
26553
26734
|
if (override.subc !== undefined)
|
|
26554
26735
|
stripped.push("subc");
|
|
26736
|
+
if (override.opencode !== undefined)
|
|
26737
|
+
stripped.push("opencode");
|
|
26555
26738
|
if (override.github !== undefined)
|
|
26556
26739
|
stripped.push("github");
|
|
26557
26740
|
if (override.gh_shim !== undefined)
|
|
@@ -27159,7 +27342,8 @@ var NPM_LSP_TABLE = [
|
|
|
27159
27342
|
npm: "typescript",
|
|
27160
27343
|
binary: "tsserver",
|
|
27161
27344
|
extensions: ["ts", "tsx", "js", "jsx", "mjs", "cjs", "mts", "cts"],
|
|
27162
|
-
rootMarkers: ["tsconfig.json", "jsconfig.json", "package.json"]
|
|
27345
|
+
rootMarkers: ["tsconfig.json", "jsconfig.json", "package.json"],
|
|
27346
|
+
supportedMajor: 5
|
|
27163
27347
|
},
|
|
27164
27348
|
{
|
|
27165
27349
|
id: "python",
|
|
@@ -27233,6 +27417,15 @@ var NPM_LSP_TABLE = [
|
|
|
27233
27417
|
rootMarkers: ["biome.json", "biome.jsonc"]
|
|
27234
27418
|
}
|
|
27235
27419
|
];
|
|
27420
|
+
function majorVersionOf(version) {
|
|
27421
|
+
const match = /^v?(\d+)(?:\.|$)/.exec(version.trim());
|
|
27422
|
+
return match ? Number(match[1]) : null;
|
|
27423
|
+
}
|
|
27424
|
+
function isVersionSupported(spec, version) {
|
|
27425
|
+
if (spec.supportedMajor === undefined)
|
|
27426
|
+
return true;
|
|
27427
|
+
return majorVersionOf(version) === spec.supportedMajor;
|
|
27428
|
+
}
|
|
27236
27429
|
|
|
27237
27430
|
// src/lsp-project-relevance.ts
|
|
27238
27431
|
import { existsSync as existsSync14, readdirSync as readdirSync4, readFileSync as readFileSync12 } from "node:fs";
|
|
@@ -27329,10 +27522,11 @@ function extensionOf(fileName) {
|
|
|
27329
27522
|
|
|
27330
27523
|
// src/lsp-registry-probe.ts
|
|
27331
27524
|
var NPM_REGISTRY_BASE = "https://registry.npmjs.org";
|
|
27332
|
-
function pickEligibleVersion(response, graceDays, now = Date.now()) {
|
|
27525
|
+
function pickEligibleVersion(response, graceDays, now = Date.now(), accept = () => true) {
|
|
27333
27526
|
const times = response.time || {};
|
|
27334
27527
|
const cutoff = now - graceDays * 24 * 60 * 60 * 1000;
|
|
27335
27528
|
const candidates = [];
|
|
27529
|
+
let releaseCount = 0;
|
|
27336
27530
|
for (const [version, publishedAt] of Object.entries(times)) {
|
|
27337
27531
|
if (version === "created" || version === "modified")
|
|
27338
27532
|
continue;
|
|
@@ -27343,6 +27537,9 @@ function pickEligibleVersion(response, graceDays, now = Date.now()) {
|
|
|
27343
27537
|
const ts = Date.parse(publishedAt);
|
|
27344
27538
|
if (Number.isNaN(ts))
|
|
27345
27539
|
continue;
|
|
27540
|
+
releaseCount += 1;
|
|
27541
|
+
if (!accept(version))
|
|
27542
|
+
continue;
|
|
27346
27543
|
candidates.push({ version, publishedAt, ts });
|
|
27347
27544
|
}
|
|
27348
27545
|
candidates.sort((a, b) => b.ts - a.ts);
|
|
@@ -27351,10 +27548,11 @@ function pickEligibleVersion(response, graceDays, now = Date.now()) {
|
|
|
27351
27548
|
return {
|
|
27352
27549
|
version: eligible[0]?.version ?? null,
|
|
27353
27550
|
blockedByGrace,
|
|
27551
|
+
outsideSupportedRange: releaseCount > 0 && candidates.length === 0,
|
|
27354
27552
|
eligible: eligible.map(({ version, publishedAt }) => ({ version, publishedAt }))
|
|
27355
27553
|
};
|
|
27356
27554
|
}
|
|
27357
|
-
async function probeRegistry(npmPackage, graceDays, fetchImpl = fetch) {
|
|
27555
|
+
async function probeRegistry(npmPackage, graceDays, fetchImpl = fetch, accept) {
|
|
27358
27556
|
const encoded = encodeURIComponent(npmPackage).replace(/^%40/, "@");
|
|
27359
27557
|
const url = `${NPM_REGISTRY_BASE}/${encoded}`;
|
|
27360
27558
|
try {
|
|
@@ -27367,7 +27565,7 @@ async function probeRegistry(npmPackage, graceDays, fetchImpl = fetch) {
|
|
|
27367
27565
|
return null;
|
|
27368
27566
|
}
|
|
27369
27567
|
const json = await res.json();
|
|
27370
|
-
return pickEligibleVersion(json, graceDays);
|
|
27568
|
+
return pickEligibleVersion(json, graceDays, Date.now(), accept);
|
|
27371
27569
|
} catch (err) {
|
|
27372
27570
|
warn2(`[lsp] registry probe failed for ${npmPackage}: ${err}`);
|
|
27373
27571
|
return null;
|
|
@@ -27418,11 +27616,11 @@ async function resolveTargetVersion(spec, config, fetchImpl = fetch) {
|
|
|
27418
27616
|
}
|
|
27419
27617
|
const cached = readVersionCheck(spec.npm);
|
|
27420
27618
|
const weeklyMs = config.graceDays * 24 * 60 * 60 * 1000;
|
|
27421
|
-
const cachedSafe = isSafeVersion(cached?.latest_eligible ?? null);
|
|
27619
|
+
const cachedSafe = isSafeVersion(cached?.latest_eligible ?? null) && isVersionSupported(spec, cached?.latest_eligible);
|
|
27422
27620
|
if (cached && !shouldRecheckVersion(cached, weeklyMs) && cachedSafe) {
|
|
27423
27621
|
return { version: cached.latest_eligible, pinned: false, probe: null };
|
|
27424
27622
|
}
|
|
27425
|
-
const probe = await probeRegistry(spec.npm, config.graceDays, fetchImpl);
|
|
27623
|
+
const probe = await probeRegistry(spec.npm, config.graceDays, fetchImpl, (candidate) => isVersionSupported(spec, candidate));
|
|
27426
27624
|
if (!probe) {
|
|
27427
27625
|
return {
|
|
27428
27626
|
version: cachedSafe ? cached?.latest_eligible ?? null : null,
|
|
@@ -27557,11 +27755,16 @@ async function ensureServerInstalled(spec, config, fetchImpl, signal) {
|
|
|
27557
27755
|
const { version, probe } = await resolveTargetVersion(spec, config, fetchImpl);
|
|
27558
27756
|
if (!version) {
|
|
27559
27757
|
const installed = isInstalled(spec.npm, spec.binary);
|
|
27560
|
-
if (installed) {
|
|
27758
|
+
if (installed && installedVersionAllowed(spec, config)) {
|
|
27561
27759
|
warn2(`[lsp] no eligible version of ${spec.npm} (grace=${config.graceDays}d); keeping existing install`);
|
|
27562
27760
|
return { started: false, reason: "kept existing install" };
|
|
27563
27761
|
}
|
|
27564
|
-
const blocked = probe?.blockedByGrace ? `all versions are within ${config.graceDays}-day grace window` : "registry probe failed";
|
|
27762
|
+
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";
|
|
27763
|
+
if (installed) {
|
|
27764
|
+
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}`;
|
|
27765
|
+
warn2(`[lsp] ${reason}`);
|
|
27766
|
+
return { started: false, reason };
|
|
27767
|
+
}
|
|
27565
27768
|
warn2(`[lsp] skipping ${spec.npm}: ${blocked}`);
|
|
27566
27769
|
return { started: false, reason: blocked };
|
|
27567
27770
|
}
|
|
@@ -27686,6 +27889,15 @@ function validateCachedNpmInstall(spec) {
|
|
|
27686
27889
|
}
|
|
27687
27890
|
return true;
|
|
27688
27891
|
}
|
|
27892
|
+
function installedVersionAllowed(spec, config) {
|
|
27893
|
+
const installed = readInstalledMeta(spec.npm)?.version;
|
|
27894
|
+
if (!installed)
|
|
27895
|
+
return spec.supportedMajor === undefined;
|
|
27896
|
+
return isVersionSupported(spec, installed) || config.versions[spec.npm] === installed;
|
|
27897
|
+
}
|
|
27898
|
+
function cachedInstallUsable(spec, config) {
|
|
27899
|
+
return isInstalled(spec.npm, spec.binary) && validateCachedNpmInstall(spec) && installedVersionAllowed(spec, config);
|
|
27900
|
+
}
|
|
27689
27901
|
function runAutoInstall(projectRoot, config, fetchImpl = fetch) {
|
|
27690
27902
|
const cachedBinDirs = [];
|
|
27691
27903
|
const skipped = [];
|
|
@@ -27698,7 +27910,7 @@ function runAutoInstall(projectRoot, config, fetchImpl = fetch) {
|
|
|
27698
27910
|
return projectExtensions;
|
|
27699
27911
|
};
|
|
27700
27912
|
for (const spec of NPM_LSP_TABLE) {
|
|
27701
|
-
if (
|
|
27913
|
+
if (cachedInstallUsable(spec, config)) {
|
|
27702
27914
|
cachedBinDirs.push(lspBinDir(spec.npm));
|
|
27703
27915
|
}
|
|
27704
27916
|
if (config.disabled.has(spec.id) || spec.id === "typescript-sdk" && config.disabled.has("typescript")) {
|
|
@@ -27737,7 +27949,7 @@ function runAutoInstall(projectRoot, config, fetchImpl = fetch) {
|
|
|
27737
27949
|
},
|
|
27738
27950
|
skipped,
|
|
27739
27951
|
installsComplete: Promise.all(installPromises).then(() => {}),
|
|
27740
|
-
getCachedBinDirs: () => NPM_LSP_TABLE.filter((spec) =>
|
|
27952
|
+
getCachedBinDirs: () => NPM_LSP_TABLE.filter((spec) => cachedInstallUsable(spec, config)).map((spec) => lspBinDir(spec.npm))
|
|
27741
27953
|
};
|
|
27742
27954
|
}
|
|
27743
27955
|
async function pushLspPathsAfterAutoInstall(pool, projectRoot, cachedBinDirs) {
|
|
@@ -28868,6 +29080,23 @@ import {
|
|
|
28868
29080
|
import { Container as Container2, Spacer as Spacer2, Text as Text2 } from "@earendil-works/pi-tui";
|
|
28869
29081
|
import { Type as Type2 } from "typebox";
|
|
28870
29082
|
|
|
29083
|
+
// src/hashline-tag-sources.ts
|
|
29084
|
+
function hashlineTagSourceSentence(bashRewritesMintTags, isRegistered = () => true) {
|
|
29085
|
+
const rewrites = bashRewritesMintTags ? " (and accepted AFT `cat`/`head`/`tail` rewrites)" : "";
|
|
29086
|
+
const lookAlikes = [
|
|
29087
|
+
...["aft_zoom", "aft_outline"].filter(isRegistered),
|
|
29088
|
+
"grep",
|
|
29089
|
+
...isRegistered("aft_search") ? ["aft_search"] : []
|
|
29090
|
+
].map((name) => `\`${name}\``);
|
|
29091
|
+
lookAlikes.push("conflict snippets");
|
|
29092
|
+
return `Only \`read\`${rewrites} mint hashline tags. ${joinList(lookAlikes)} do not. After navigation, call \`read\` on every file and range the patch addresses.`;
|
|
29093
|
+
}
|
|
29094
|
+
function joinList(items) {
|
|
29095
|
+
if (items.length <= 2)
|
|
29096
|
+
return items.join(" and ");
|
|
29097
|
+
return `${items.slice(0, -1).join(", ")}, and ${items[items.length - 1]}`;
|
|
29098
|
+
}
|
|
29099
|
+
|
|
28871
29100
|
// src/tools/diff-format.ts
|
|
28872
29101
|
import { diffLines } from "diff";
|
|
28873
29102
|
|
|
@@ -29322,7 +29551,9 @@ var WriteParams = Type2.Object({
|
|
|
29322
29551
|
path: Type2.String({
|
|
29323
29552
|
description: "Path to the file to write (absolute or relative to project root)"
|
|
29324
29553
|
}),
|
|
29325
|
-
content: Type2.String({
|
|
29554
|
+
content: Type2.String({
|
|
29555
|
+
description: "Full file contents to write; for issue://N / pr://N, the comment body to post"
|
|
29556
|
+
})
|
|
29326
29557
|
});
|
|
29327
29558
|
var BatchEditParams = Type2.Object({
|
|
29328
29559
|
oldString: Type2.Optional(Type2.String({ description: "Text to find for a batch find/replace edit" })),
|
|
@@ -29346,14 +29577,14 @@ var EditParams = Type2.Object({
|
|
|
29346
29577
|
description: "Replacement content for symbol mode; only meaningful together with `symbol`. A call carrying one without the other is incomplete. For whole-file writes, use the `write` tool."
|
|
29347
29578
|
})),
|
|
29348
29579
|
appendContent: Type2.Optional(Type2.String({
|
|
29349
|
-
description: "Append text to the end of the file (creates the file if missing, parent dirs auto-created).
|
|
29580
|
+
description: "Append text to the end of the file (creates the file if missing, parent dirs auto-created). Mixing with other edit modes is rejected."
|
|
29350
29581
|
})),
|
|
29351
29582
|
edits: Type2.Optional(Type2.Array(BatchEditParams, {
|
|
29352
29583
|
minItems: 1,
|
|
29353
29584
|
description: "Batch edits — non-empty array of { oldString, newString }, { oldString, newString, replaceAll: true }, or { startLine, endLine, content } objects applied atomically."
|
|
29354
29585
|
}))
|
|
29355
29586
|
});
|
|
29356
|
-
var
|
|
29587
|
+
var HASHLINE_EDIT_GRAMMAR = [
|
|
29357
29588
|
"Apply a hashline patch. Arguments are exactly `{patch}` where `patch` is a non-empty string. Server-owned preview control is outside this schema.",
|
|
29358
29589
|
"",
|
|
29359
29590
|
"Quick reference:",
|
|
@@ -29361,10 +29592,15 @@ var HASHLINE_EDIT_DESCRIPTION = [
|
|
|
29361
29592
|
"- Same canonical path: multiple sections compose in patch order against pre-request coordinates.",
|
|
29362
29593
|
"- 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.",
|
|
29363
29594
|
"- 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 `-`.",
|
|
29364
|
-
"- 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."
|
|
29365
|
-
"- 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."
|
|
29595
|
+
"- 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."
|
|
29366
29596
|
].join(`
|
|
29367
29597
|
`);
|
|
29598
|
+
function hashlineEditDescription(config) {
|
|
29599
|
+
const bashRewrites = toolEnabled(config, "bash") && resolveBashConfig(config).rewrite;
|
|
29600
|
+
const tagSources = hashlineTagSourceSentence(bashRewrites, (name) => toolEnabled(config, name));
|
|
29601
|
+
return `${HASHLINE_EDIT_GRAMMAR}
|
|
29602
|
+
- ${tagSources}`;
|
|
29603
|
+
}
|
|
29368
29604
|
var HashlineEditParams = Type2.Object({
|
|
29369
29605
|
patch: Type2.String({
|
|
29370
29606
|
minLength: 1,
|
|
@@ -29578,7 +29814,7 @@ PDFs aren't supported on the Pi harness yet.`, response);
|
|
|
29578
29814
|
pi.registerTool({
|
|
29579
29815
|
name: editName,
|
|
29580
29816
|
label: editName,
|
|
29581
|
-
description:
|
|
29817
|
+
description: hashlineEditDescription(ctx.config),
|
|
29582
29818
|
promptSnippet: `Apply a tagged hashline patch through the ${editName} tool`,
|
|
29583
29819
|
promptGuidelines: [
|
|
29584
29820
|
"Read a file to obtain its current hashline tag before editing it.",
|
|
@@ -29636,8 +29872,7 @@ PDFs aren't supported on the Pi harness yet.`, response);
|
|
|
29636
29872
|
}
|
|
29637
29873
|
if (isGithubResourcePath(filePathArg)) {
|
|
29638
29874
|
const edits = params.edits;
|
|
29639
|
-
|
|
29640
|
-
if (!onlyFindReplace) {
|
|
29875
|
+
if (!isFindReplaceOnlyEdit(argsRecord)) {
|
|
29641
29876
|
throw new Error("edit: GitHub resources support only edits[] find/replace entries with oldString and optional newString");
|
|
29642
29877
|
}
|
|
29643
29878
|
const bridge = bridgeFor(ctx, extCtx.cwd);
|
|
@@ -30165,34 +30400,17 @@ function registerAstTools(pi, ctx, surface) {
|
|
|
30165
30400
|
// src/tools/bash.ts
|
|
30166
30401
|
import { Container as Container3, Spacer as Spacer3, Text as Text3 } from "@earendil-works/pi-tui";
|
|
30167
30402
|
import { Type as Type4 } from "typebox";
|
|
30168
|
-
|
|
30169
|
-
// src/session-kind.ts
|
|
30170
|
-
var MAGIC_CONTEXT_SUBAGENT_ENV = "MAGIC_CONTEXT_PI_SUBAGENT";
|
|
30171
|
-
function piWorkerKind(extCtx, env = process.env) {
|
|
30172
|
-
if (env[MAGIC_CONTEXT_SUBAGENT_ENV] === "1")
|
|
30173
|
-
return "delegated";
|
|
30174
|
-
if (extCtx?.hasUI === false)
|
|
30175
|
-
return "headless";
|
|
30176
|
-
return;
|
|
30177
|
-
}
|
|
30178
|
-
function isPiWorkerSession(extCtx, env = process.env) {
|
|
30179
|
-
return piWorkerKind(extCtx, env) !== undefined;
|
|
30180
|
-
}
|
|
30181
|
-
function skipsEagerStartup(env = process.env) {
|
|
30182
|
-
return piWorkerKind(undefined, env) === "delegated";
|
|
30183
|
-
}
|
|
30184
|
-
|
|
30185
|
-
// src/tools/bash.ts
|
|
30186
|
-
var BASH_WAIT_POLL_INTERVAL_MS = 100;
|
|
30187
30403
|
var REGEX_WAIT_SCAN_WINDOW_BYTES = 64 * 1024;
|
|
30188
30404
|
function watchCallerRole(extCtx, env = process.env) {
|
|
30189
30405
|
return isPiWorkerSession(extCtx, env) ? "worker" : "primary";
|
|
30190
30406
|
}
|
|
30191
|
-
function coerceConfiguredWatchTimeout(value, cap) {
|
|
30407
|
+
function coerceConfiguredWatchTimeout(value, role, cap) {
|
|
30192
30408
|
try {
|
|
30193
|
-
return coerceOptionalInt(value, "timeoutMs", 1, cap);
|
|
30409
|
+
return coerceOptionalInt(value, "timeoutMs", 1, maxWatchTimeoutMs(role, cap));
|
|
30194
30410
|
} catch (error) {
|
|
30195
30411
|
const message = error instanceof Error ? error.message : String(error);
|
|
30412
|
+
if (role === "worker")
|
|
30413
|
+
throw error;
|
|
30196
30414
|
throw new Error(`${message} (bash.watch_sync_max_ms)`);
|
|
30197
30415
|
}
|
|
30198
30416
|
}
|
|
@@ -30208,7 +30426,9 @@ function resolveForegroundWaitMs(configured) {
|
|
|
30208
30426
|
var BASH_TRANSPORT_TIMEOUT_MS2 = 30000;
|
|
30209
30427
|
var DEFAULT_HARD_TIMEOUT_MS = 30 * 60 * 1000;
|
|
30210
30428
|
var BASH_TRANSPORT_MARGIN_MS = 1e4;
|
|
30211
|
-
function orchestratedTransportTimeoutMs(blockToCompletion, wait, effectiveTimeout, foregroundWaitMs) {
|
|
30429
|
+
function orchestratedTransportTimeoutMs(blockToCompletion, wait, effectiveTimeout, foregroundWaitMs, noHardKill = false) {
|
|
30430
|
+
if (noHardKill)
|
|
30431
|
+
return LONGEST_TIMER_DELAY_MS;
|
|
30212
30432
|
const waitBudget = blockToCompletion || wait ? effectiveTimeout ?? DEFAULT_HARD_TIMEOUT_MS : foregroundWaitMs;
|
|
30213
30433
|
return waitBudget + BASH_TRANSPORT_MARGIN_MS;
|
|
30214
30434
|
}
|
|
@@ -30222,10 +30442,14 @@ var BashBaseParams = {
|
|
|
30222
30442
|
})),
|
|
30223
30443
|
description: Type4.Optional(Type4.String({
|
|
30224
30444
|
description: "Human-readable description shown in UI logs. Helps users understand what the command does without reading shell syntax."
|
|
30225
|
-
}))
|
|
30445
|
+
}))
|
|
30446
|
+
};
|
|
30447
|
+
var BashWaitParam = {
|
|
30226
30448
|
wait: Type4.Optional(Type4.Boolean({
|
|
30227
30449
|
description: "When true, run in the foreground without auto-promoting and wait until the command finishes or reaches its timeout; any new message detaches by default, while `bash.detach_on_user_message: false` keeps it blocking unless the message contains the literal `&detach`. The token is stripped before delivery; the rest of the message is preserved, and a token-only message becomes `(requested background detach)`. Use only when you know the result is required before doing anything else."
|
|
30228
|
-
}))
|
|
30450
|
+
}))
|
|
30451
|
+
};
|
|
30452
|
+
var BashSandboxParam = {
|
|
30229
30453
|
sandbox: Type4.Optional(Type4.Literal("host", {
|
|
30230
30454
|
description: "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."
|
|
30231
30455
|
}))
|
|
@@ -30249,16 +30473,65 @@ var BashPtyParams = {
|
|
|
30249
30473
|
};
|
|
30250
30474
|
var BashParams = Type4.Object({
|
|
30251
30475
|
...BashBaseParams,
|
|
30476
|
+
...BashWaitParam,
|
|
30477
|
+
...BashSandboxParam,
|
|
30252
30478
|
...BashBackgroundFlagParam,
|
|
30253
30479
|
...BashCompressionParam,
|
|
30254
30480
|
...BashPtyParams
|
|
30255
30481
|
});
|
|
30256
|
-
|
|
30257
|
-
|
|
30258
|
-
|
|
30259
|
-
}
|
|
30260
|
-
|
|
30261
|
-
|
|
30482
|
+
function bashParamsForConfig(features, companions) {
|
|
30483
|
+
return Type4.Object({
|
|
30484
|
+
...BashBaseParams,
|
|
30485
|
+
...features.background ? BashWaitParam : {},
|
|
30486
|
+
...features.sandbox ? BashSandboxParam : {},
|
|
30487
|
+
...features.background ? {
|
|
30488
|
+
background: Type4.Optional(Type4.Boolean({ description: backgroundParamDescription(companions) }))
|
|
30489
|
+
} : {},
|
|
30490
|
+
...features.compress ? BashCompressionParam : {},
|
|
30491
|
+
...features.background ? {
|
|
30492
|
+
...BashPtyParams,
|
|
30493
|
+
pty: Type4.Optional(Type4.Boolean({
|
|
30494
|
+
description: ptyParamDescription(companions, features.subagentBackground)
|
|
30495
|
+
}))
|
|
30496
|
+
} : {}
|
|
30497
|
+
});
|
|
30498
|
+
}
|
|
30499
|
+
function bashCompanionRegistered(config, companion) {
|
|
30500
|
+
return resolveBashConfig(config).background && toolEnabled(config, companion);
|
|
30501
|
+
}
|
|
30502
|
+
function registeredCompanions(config) {
|
|
30503
|
+
return {
|
|
30504
|
+
status: bashCompanionRegistered(config, "bash_status"),
|
|
30505
|
+
watch: bashCompanionRegistered(config, "bash_watch"),
|
|
30506
|
+
write: bashCompanionRegistered(config, "bash_write"),
|
|
30507
|
+
kill: bashCompanionRegistered(config, "bash_kill")
|
|
30508
|
+
};
|
|
30509
|
+
}
|
|
30510
|
+
function backgroundParamDescription(c) {
|
|
30511
|
+
let controls = "";
|
|
30512
|
+
if (c.watch) {
|
|
30513
|
+
controls += ` Use bash_watch to wait for completion or output patterns${c.status ? "; bash_status for a one-shot snapshot only." : "."}`;
|
|
30514
|
+
} else if (c.status) {
|
|
30515
|
+
controls += " Use bash_status for a one-shot snapshot.";
|
|
30516
|
+
}
|
|
30517
|
+
if (c.kill)
|
|
30518
|
+
controls += " Use bash_kill to terminate.";
|
|
30519
|
+
return `Spawn command in background and return immediately with a task_id.${controls} Ideal for long-running tasks like builds or dev servers.`;
|
|
30520
|
+
}
|
|
30521
|
+
function ptyParamDescription(c, subagentBackground) {
|
|
30522
|
+
let drive = "";
|
|
30523
|
+
if (c.status && c.write) {
|
|
30524
|
+
drive = ' Inspect with bash_status({ task_id, output_mode: "screen" }) and send input with bash_write.';
|
|
30525
|
+
} else if (c.status) {
|
|
30526
|
+
drive = ' Inspect with bash_status({ task_id, output_mode: "screen" }).';
|
|
30527
|
+
} else if (c.write) {
|
|
30528
|
+
drive = " Send input with bash_write.";
|
|
30529
|
+
}
|
|
30530
|
+
const workers = subagentBackground ? "" : " Unavailable in worker sessions because bash.subagent_background is false.";
|
|
30531
|
+
return `Spawn the command in a real PTY for interactive programs. Implies background: true automatically.${workers}${drive}`;
|
|
30532
|
+
}
|
|
30533
|
+
function nativeSandboxEnabled(config) {
|
|
30534
|
+
return config.sandbox?.enabled === true;
|
|
30262
30535
|
}
|
|
30263
30536
|
var BashTaskParams = Type4.Object({
|
|
30264
30537
|
task_id: Type4.String({
|
|
@@ -30390,18 +30663,47 @@ function getBashSpawnHook(pi) {
|
|
|
30390
30663
|
}
|
|
30391
30664
|
return api.hooks?.bashSpawn;
|
|
30392
30665
|
}
|
|
30666
|
+
function backgroundWaitSentence(c) {
|
|
30667
|
+
if (c.watch) {
|
|
30668
|
+
const noPolling = c.status ? ", and never loop `bash_status` to wait" : "";
|
|
30669
|
+
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}.`;
|
|
30670
|
+
}
|
|
30671
|
+
return c.status ? "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.";
|
|
30672
|
+
}
|
|
30673
|
+
function ptyDriveClause(c) {
|
|
30674
|
+
if (c.status && c.write) {
|
|
30675
|
+
return ', and is driven with `bash_status({ output_mode: "screen" })` plus `bash_write`';
|
|
30676
|
+
}
|
|
30677
|
+
if (c.status)
|
|
30678
|
+
return ', and its screen is read with `bash_status({ output_mode: "screen" })`';
|
|
30679
|
+
if (c.write)
|
|
30680
|
+
return ", and is driven with `bash_write`";
|
|
30681
|
+
return "";
|
|
30682
|
+
}
|
|
30393
30683
|
function registerBashTool(pi, ctx, aftSearchRegistered = false, registeredName = "bash", registerCompanions = true, shell = "bash") {
|
|
30394
30684
|
const isPowerShell = shell === "powershell";
|
|
30395
30685
|
const spawnHook = isPowerShell ? undefined : getBashSpawnHook(pi);
|
|
30396
30686
|
const readToolName = "read";
|
|
30397
30687
|
const grepToolName = "grep";
|
|
30398
30688
|
const zoomSteer = toolEnabled(ctx.config, "aft_zoom") ? ", or `aft_zoom`" : "";
|
|
30399
|
-
const
|
|
30689
|
+
const steerTools = [
|
|
30690
|
+
aftSearchRegistered ? "`aft_search` (concepts, identifiers, regex, literals)" : `the \`${grepToolName}\` tool`,
|
|
30691
|
+
`\`${readToolName}\``,
|
|
30692
|
+
...toolEnabled(ctx.config, "aft_outline") ? ["`aft_outline`"] : []
|
|
30693
|
+
].join(", ");
|
|
30694
|
+
const searchSteer = `use ${steerTools}${zoomSteer} instead`;
|
|
30695
|
+
const companions = registeredCompanions(ctx.config);
|
|
30400
30696
|
const bashCfg = resolveBashConfig(ctx.config);
|
|
30401
30697
|
let hostFallbackActive = false;
|
|
30402
30698
|
const compressionSentence = bashCfg.compress ? " 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`." : "";
|
|
30699
|
+
const supported = bashCfg.background ? [
|
|
30700
|
+
"workdir",
|
|
30701
|
+
"background tasks",
|
|
30702
|
+
...bashCfg.compress ? ["compressed output"] : [],
|
|
30703
|
+
"PTY mode"
|
|
30704
|
+
].join(", ") : bashCfg.compress ? "workdir and compressed output" : "workdir";
|
|
30403
30705
|
const detachSentence = bashCfg.detach_on_user_message ? "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; the rest of the message is preserved, while 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; the rest of the message is preserved, while a token-only message becomes `(requested background detach)`.";
|
|
30404
|
-
const tasksSentence = bashCfg.background ? ` Commands run in the foreground and return inline; \`wait: true\` blocks until a long command finishes instead of auto-promoting; ${detachSentence} 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;
|
|
30706
|
+
const tasksSentence = bashCfg.background ? ` Commands run in the foreground and return inline; \`wait: true\` blocks until a long command finishes instead of auto-promoting; ${detachSentence} 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; ${backgroundWaitSentence(companions)} 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(companions)}.` : " Commands run in the foreground to completion; `timeout` is the hard kill cap (default 30 minutes).";
|
|
30405
30707
|
pi.registerTool({
|
|
30406
30708
|
name: registeredName,
|
|
30407
30709
|
label: registeredName,
|
|
@@ -30410,13 +30712,18 @@ function registerBashTool(pi, ctx, aftSearchRegistered = false, registeredName =
|
|
|
30410
30712
|
PowerShell syntax is not analyzed as POSIX shell. Each command requires explicit approval so syntax AFT cannot safely interpret is never auto-allowed.` : `Execute shell commands.${compressionSentence}${tasksSentence}
|
|
30411
30713
|
|
|
30412
30714
|
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.`,
|
|
30413
|
-
promptSnippet: isPowerShell ?
|
|
30715
|
+
promptSnippet: isPowerShell ? `Run PowerShell commands (timeout in milliseconds; supports ${supported})` : `Run shell commands (timeout in milliseconds; supports ${supported})`,
|
|
30414
30716
|
promptGuidelines: isPowerShell ? ["Use PowerShell syntax. Every command requires explicit approval."] : [
|
|
30415
30717
|
`DO NOT use bash for code search or exploration — ${searchSteer}.`,
|
|
30416
|
-
"Set compressed: false when you need ANSI color codes in the output.",
|
|
30718
|
+
...bashCfg.compress ? ["Set compressed: false when you need ANSI color codes in the output."] : [],
|
|
30417
30719
|
"Piped commands run verbatim and show the pipeline's output; run test/build tools without pipes when you need AFT's summary."
|
|
30418
30720
|
],
|
|
30419
|
-
parameters: bashParamsForConfig(
|
|
30721
|
+
parameters: bashParamsForConfig({
|
|
30722
|
+
background: bashCfg.background,
|
|
30723
|
+
compress: bashCfg.compress,
|
|
30724
|
+
sandbox: nativeSandboxEnabled(ctx.config),
|
|
30725
|
+
subagentBackground: bashCfg.subagent_background
|
|
30726
|
+
}, companions),
|
|
30420
30727
|
async execute(_toolCallId, params, signal, onUpdate, extCtx) {
|
|
30421
30728
|
const bridge = bridgeFor(ctx, extCtx.cwd);
|
|
30422
30729
|
const bashCfg = resolveBashConfig(ctx.config);
|
|
@@ -30426,7 +30733,7 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
|
|
|
30426
30733
|
const ptyRows = backgroundDisabled ? undefined : coerceOptionalInt(params.ptyRows, "ptyRows", 1, 60);
|
|
30427
30734
|
const ptyCols = backgroundDisabled ? undefined : coerceOptionalInt(params.ptyCols, "ptyCols", 1, 140);
|
|
30428
30735
|
const compressed = coerceBoolean(params.compressed, true);
|
|
30429
|
-
const requestedWait = coerceBoolean(params.wait);
|
|
30736
|
+
const requestedWait = !backgroundDisabled && coerceBoolean(params.wait);
|
|
30430
30737
|
const rawRequestedPty = coerceBoolean(params.pty);
|
|
30431
30738
|
const rawRequestedBackground = coerceBoolean(params.background);
|
|
30432
30739
|
if (requestedWait && rawRequestedPty) {
|
|
@@ -30442,6 +30749,8 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
|
|
|
30442
30749
|
}
|
|
30443
30750
|
const blockToCompletion = backgroundDisabled || requestedWait || workerForcedForeground;
|
|
30444
30751
|
const effectiveBackground = !blockToCompletion && (rawRequestedBackground || requestedPty);
|
|
30752
|
+
const isWorker = isPiWorkerSession(extCtx);
|
|
30753
|
+
const workerUnboundedWait = isWorker && requestedWait && timeout === undefined;
|
|
30445
30754
|
let spawnContext = {
|
|
30446
30755
|
command: params.command,
|
|
30447
30756
|
cwd: params.workdir
|
|
@@ -30476,7 +30785,7 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
|
|
|
30476
30785
|
sandbox: params.sandbox,
|
|
30477
30786
|
...isPowerShell ? { shell: "powershell" } : {}
|
|
30478
30787
|
}, extCtx, {
|
|
30479
|
-
transportTimeoutMs: orchestratedTransportTimeoutMs(blockToCompletion, requestedWait, timeout, foregroundWaitMs),
|
|
30788
|
+
transportTimeoutMs: orchestratedTransportTimeoutMs(blockToCompletion, requestedWait, timeout, foregroundWaitMs, workerUnboundedWait),
|
|
30480
30789
|
onProgress: ({ text }) => {
|
|
30481
30790
|
streamed += text;
|
|
30482
30791
|
const displayText = truncateToVisualLines(streamed, 100);
|
|
@@ -30487,10 +30796,10 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
|
|
|
30487
30796
|
const fallbackCause = classifyBashHostFallbackError(error);
|
|
30488
30797
|
if (isPowerShell || !bashCfg.host_fallback || fallbackCause === undefined)
|
|
30489
30798
|
throw error;
|
|
30490
|
-
if (rawRequestedBackground) {
|
|
30799
|
+
if (!backgroundDisabled && rawRequestedBackground) {
|
|
30491
30800
|
throw new Error(`${BASH_HOST_FALLBACK_REFUSAL}; background:true is unsupported.`);
|
|
30492
30801
|
}
|
|
30493
|
-
if (
|
|
30802
|
+
if (requestedPty) {
|
|
30494
30803
|
throw new Error(`${BASH_HOST_FALLBACK_REFUSAL}; pty:true is unsupported.`);
|
|
30495
30804
|
}
|
|
30496
30805
|
if (!extCtx.hasUI || typeof extCtx.ui?.confirm !== "function") {
|
|
@@ -30545,6 +30854,8 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
|
|
|
30545
30854
|
registerBashCompanionTools(pi, ctx);
|
|
30546
30855
|
}
|
|
30547
30856
|
function registerBashCompanionTools(pi, ctx, enabled = { bashStatus: true, bashWatch: true, bashWrite: true, bashKill: true }) {
|
|
30857
|
+
if (!resolveBashConfig(ctx.config).background)
|
|
30858
|
+
return;
|
|
30548
30859
|
if (enabled.bashStatus) {
|
|
30549
30860
|
pi.registerTool(createBashStatusTool(ctx));
|
|
30550
30861
|
}
|
|
@@ -30565,7 +30876,7 @@ function createBashStatusTool(ctx) {
|
|
|
30565
30876
|
return {
|
|
30566
30877
|
name: "bash_status",
|
|
30567
30878
|
label: "bash_status",
|
|
30568
|
-
description:
|
|
30879
|
+
description: `Read-only snapshot of a background bash task. Returns immediately. Never waits. One look to check on a task is fine — never loop it to wait for completion.${bashCompanionRegistered(ctx.config, "bash_watch") ? " To wait, use bash_watch." : ""}`,
|
|
30569
30880
|
promptSnippet: "Inspect a background bash task by task_id",
|
|
30570
30881
|
parameters: BashStatusParams,
|
|
30571
30882
|
async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
|
|
@@ -30583,10 +30894,10 @@ function createBashWatchTool(ctx) {
|
|
|
30583
30894
|
return {
|
|
30584
30895
|
name: "bash_watch",
|
|
30585
30896
|
label: "bash_watch",
|
|
30586
|
-
description:
|
|
30897
|
+
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.${bashCompanionRegistered(ctx.config, "bash_status") ? " Never loop bash_status to wait." : ""}`,
|
|
30587
30898
|
promptSnippet: "Wait for or watch a background bash task",
|
|
30588
30899
|
parameters: BashWatchParams,
|
|
30589
|
-
async execute(_toolCallId, params,
|
|
30900
|
+
async execute(_toolCallId, params, signal, _onUpdate, extCtx) {
|
|
30590
30901
|
const bridge = bridgeFor(ctx, extCtx.cwd);
|
|
30591
30902
|
const waitFor = parseWaitPattern(params.pattern);
|
|
30592
30903
|
const bashCfg = resolveBashConfig(ctx.config);
|
|
@@ -30623,10 +30934,10 @@ A notification will fire when the pattern matches or the task exits.`, watchDeta
|
|
|
30623
30934
|
}
|
|
30624
30935
|
const syncWaitCap = bashCfg.watch_sync_max_ms;
|
|
30625
30936
|
const role = watchCallerRole(extCtx);
|
|
30626
|
-
const effectiveWaitMs = workerForcedSync ?
|
|
30627
|
-
const data = await waitForBashStatus(ctx, bridge, extCtx, params.task_id, undefined, waitFor, true, effectiveWaitMs);
|
|
30937
|
+
const effectiveWaitMs = workerForcedSync ? undefined : resolveWatchTimeoutMs(coerceConfiguredWatchTimeout(params.timeout_ms, role, syncWaitCap), role, syncWaitCap);
|
|
30938
|
+
const data = await waitForBashStatus(ctx, bridge, extCtx, params.task_id, undefined, waitFor, true, effectiveWaitMs, signal);
|
|
30628
30939
|
if (data.waited?.reason === "user_message") {
|
|
30629
|
-
const convertedText = await convertToAsyncWatchOnAbort(bridge, extCtx, params.task_id, waitFor, coerceBoolean(params.once, true), data.waited.elapsed_ms);
|
|
30940
|
+
const convertedText = await convertToAsyncWatchOnAbort(bridge, extCtx, params.task_id, waitFor, coerceBoolean(params.once, true), data.waited.elapsed_ms, role);
|
|
30630
30941
|
return textResult(convertedText, { waited: data.waited });
|
|
30631
30942
|
}
|
|
30632
30943
|
const text = await formatBashStatus(extCtx, params.task_id, data, undefined, { role, capMs: syncWaitCap });
|
|
@@ -30634,10 +30945,11 @@ A notification will fire when the pattern matches or the task exits.`, watchDeta
|
|
|
30634
30945
|
}
|
|
30635
30946
|
};
|
|
30636
30947
|
}
|
|
30637
|
-
async function convertToAsyncWatchOnAbort(bridge, extCtx, taskId, waitFor, once, elapsedMs) {
|
|
30948
|
+
async function convertToAsyncWatchOnAbort(bridge, extCtx, taskId, waitFor, once, elapsedMs, role) {
|
|
30638
30949
|
const interrupted = `Sync watch for task ${taskId} was interrupted because you sent a message after ${elapsedMs}ms of waiting. `;
|
|
30950
|
+
const reminderFallback = interruptedWatchTail(role, `The task is still running in the background. A completion reminder will be ` + `delivered automatically when the task exits.`);
|
|
30639
30951
|
if (!waitFor) {
|
|
30640
|
-
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
|
|
30952
|
+
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.`);
|
|
30641
30953
|
}
|
|
30642
30954
|
const notifyParams = {
|
|
30643
30955
|
task_id: taskId,
|
|
@@ -30653,19 +30965,19 @@ async function convertToAsyncWatchOnAbort(bridge, extCtx, taskId, waitFor, once,
|
|
|
30653
30965
|
const registered = await callBashBridge(bridge, "bash_notify", notifyParams, extCtx);
|
|
30654
30966
|
if (registered.success === false) {
|
|
30655
30967
|
unmarkExplicitControl(sessionId, taskId);
|
|
30656
|
-
return interrupted + `Auto-registering an async watch failed (${String(registered.message ?? "unknown error")}). ` +
|
|
30968
|
+
return interrupted + `Auto-registering an async watch failed (${String(registered.message ?? "unknown error")}). ` + reminderFallback;
|
|
30657
30969
|
}
|
|
30658
|
-
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
|
|
30970
|
+
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.`);
|
|
30659
30971
|
} catch (err) {
|
|
30660
30972
|
unmarkExplicitControl(sessionId, taskId);
|
|
30661
|
-
return interrupted + `Auto-registering an async watch failed (${err instanceof Error ? err.message : String(err)}). ` +
|
|
30973
|
+
return interrupted + `Auto-registering an async watch failed (${err instanceof Error ? err.message : String(err)}). ` + reminderFallback;
|
|
30662
30974
|
}
|
|
30663
30975
|
}
|
|
30664
30976
|
function createBashWriteTool(ctx) {
|
|
30665
30977
|
return {
|
|
30666
30978
|
name: "bash_write",
|
|
30667
30979
|
label: "bash_write",
|
|
30668
|
-
description:
|
|
30980
|
+
description: `Write input bytes to a running PTY bash task. PTY-only${bashCompanionRegistered(ctx.config, "bash_status") ? '; check bash_status reports mode: "pty" first.' : "."} ` + 'Input is either a string (verbatim bytes) or an array mixing strings and { key: "esc" | "enter" | "up" | "ctrl-c" | ... } objects ' + 'for atomic text+key sequences such as [ "iHello", { key: "esc" }, ":wq", { key: "enter" } ]. ' + "Named keys cover enter/return/tab/space/backspace/esc/escape, arrows, home/end/page-up/page-down/delete/insert, f1..f12, and ctrl-a..ctrl-z. " + "Maximum 1 MiB per call (post-expansion).",
|
|
30669
30981
|
promptSnippet: "Write keystrokes/input to a PTY bash task",
|
|
30670
30982
|
parameters: BashWriteParams,
|
|
30671
30983
|
async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
|
|
@@ -30729,10 +31041,17 @@ async function bashStatusSnapshot(bridge, extCtx, taskId, outputMode, options, c
|
|
|
30729
31041
|
stderr_offset: cursor?.stderr
|
|
30730
31042
|
}, extCtx, options);
|
|
30731
31043
|
}
|
|
30732
|
-
async function waitForBashStatus(ctx, bridge, extCtx, taskId, outputMode, waitFor, waitForExit, effectiveWaitMs) {
|
|
30733
|
-
const startedAt =
|
|
30734
|
-
const deadline = startedAt + effectiveWaitMs;
|
|
30735
|
-
const elapsedMs = () => Math.round(
|
|
31044
|
+
async function waitForBashStatus(ctx, bridge, extCtx, taskId, outputMode, waitFor, waitForExit, effectiveWaitMs, abortSignal) {
|
|
31045
|
+
const startedAt = watchClock.now();
|
|
31046
|
+
const deadline = effectiveWaitMs === undefined ? Number.POSITIVE_INFINITY : startedAt + effectiveWaitMs;
|
|
31047
|
+
const elapsedMs = () => Math.round(watchClock.now() - startedAt);
|
|
31048
|
+
const pause = () => watchClock.sleep(Math.min(watchPollDelayMs(elapsedMs()), Math.max(0, deadline - watchClock.now())), abortSignal);
|
|
31049
|
+
const waited = (reason, extra = {}) => ({
|
|
31050
|
+
reason,
|
|
31051
|
+
elapsed_ms: elapsedMs(),
|
|
31052
|
+
...effectiveWaitMs === undefined ? {} : { limit_ms: effectiveWaitMs },
|
|
31053
|
+
...extra
|
|
31054
|
+
});
|
|
30736
31055
|
let spillCursor = { output: 0, stderr: 0 };
|
|
30737
31056
|
const scanState = {
|
|
30738
31057
|
output: { text: "", baseOffset: 0 },
|
|
@@ -30751,31 +31070,29 @@ async function waitForBashStatus(ctx, bridge, extCtx, taskId, outputMode, waitFo
|
|
|
30751
31070
|
markTaskWaiting(sessionId, taskId);
|
|
30752
31071
|
let sawTerminal = false;
|
|
30753
31072
|
let lastData;
|
|
31073
|
+
let busySince;
|
|
30754
31074
|
try {
|
|
30755
31075
|
while (true) {
|
|
31076
|
+
if (abortSignal?.aborted) {
|
|
31077
|
+
return withWaited(lastData ?? unavailableSnapshot(), waited("aborted"));
|
|
31078
|
+
}
|
|
30756
31079
|
let data;
|
|
30757
31080
|
try {
|
|
30758
31081
|
data = await bashStatusSnapshot(bridge, extCtx, taskId, outputMode, bridgeOptions, waitFor ? spillCursor : undefined);
|
|
30759
31082
|
} catch (err) {
|
|
30760
31083
|
if (!isBridgeTransportTimeout(err))
|
|
30761
31084
|
throw err;
|
|
31085
|
+
busySince ??= watchClock.now();
|
|
30762
31086
|
if (isSyncWatchAborted(sessionId)) {
|
|
30763
|
-
return withWaited(lastData ?? unavailableSnapshot(),
|
|
30764
|
-
reason: "user_message",
|
|
30765
|
-
elapsed_ms: elapsedMs(),
|
|
30766
|
-
limit_ms: effectiveWaitMs
|
|
30767
|
-
});
|
|
31087
|
+
return withWaited(lastData ?? unavailableSnapshot(), waited("user_message"));
|
|
30768
31088
|
}
|
|
30769
|
-
if (
|
|
30770
|
-
return withWaited(lastData ?? unavailableSnapshot(),
|
|
30771
|
-
reason: "unavailable",
|
|
30772
|
-
elapsed_ms: elapsedMs(),
|
|
30773
|
-
limit_ms: effectiveWaitMs
|
|
30774
|
-
});
|
|
31089
|
+
if (watchClock.now() >= deadline || watchClock.now() - busySince >= WATCH_UNAVAILABLE_GIVE_UP_MS) {
|
|
31090
|
+
return withWaited(lastData ?? unavailableSnapshot(), waited("unavailable"));
|
|
30775
31091
|
}
|
|
30776
|
-
await
|
|
31092
|
+
await pause();
|
|
30777
31093
|
continue;
|
|
30778
31094
|
}
|
|
31095
|
+
busySince = undefined;
|
|
30779
31096
|
lastData = data;
|
|
30780
31097
|
const terminal = isTerminalStatus(data.status);
|
|
30781
31098
|
if (waitFor) {
|
|
@@ -30800,14 +31117,11 @@ async function waitForBashStatus(ctx, bridge, extCtx, taskId, outputMode, waitFo
|
|
|
30800
31117
|
await markBgCompletionDelivered({ ctx, directory: extCtx.cwd, sessionID: sessionId }, taskId);
|
|
30801
31118
|
}
|
|
30802
31119
|
const matchStream = data.mode === "pty" ? undefined : chunk.stream === "output" ? "stdout" : "stderr";
|
|
30803
|
-
return withWaited(data, {
|
|
30804
|
-
reason: "matched",
|
|
30805
|
-
elapsed_ms: elapsedMs(),
|
|
30806
|
-
limit_ms: effectiveWaitMs,
|
|
31120
|
+
return withWaited(data, waited("matched", {
|
|
30807
31121
|
match: match.text,
|
|
30808
31122
|
match_offset: state.baseOffset + match.byteOffset,
|
|
30809
31123
|
match_stream: matchStream
|
|
30810
|
-
});
|
|
31124
|
+
}));
|
|
30811
31125
|
}
|
|
30812
31126
|
if (waitFor.kind === "substring") {
|
|
30813
31127
|
const trimmed = trimWaitScanBuffer(state.text, state.baseOffset, waitFor);
|
|
@@ -30823,27 +31137,15 @@ async function waitForBashStatus(ctx, bridge, extCtx, taskId, outputMode, waitFo
|
|
|
30823
31137
|
consumeBgCompletion(sessionId, taskId);
|
|
30824
31138
|
await markBgCompletionDelivered({ ctx, directory: extCtx.cwd, sessionID: sessionId }, taskId);
|
|
30825
31139
|
}
|
|
30826
|
-
return withWaited(data,
|
|
30827
|
-
reason: "exited",
|
|
30828
|
-
elapsed_ms: elapsedMs(),
|
|
30829
|
-
limit_ms: effectiveWaitMs
|
|
30830
|
-
});
|
|
31140
|
+
return withWaited(data, waited("exited"));
|
|
30831
31141
|
}
|
|
30832
31142
|
if (isSyncWatchAborted(sessionId)) {
|
|
30833
|
-
return withWaited(data,
|
|
30834
|
-
reason: "user_message",
|
|
30835
|
-
elapsed_ms: elapsedMs(),
|
|
30836
|
-
limit_ms: effectiveWaitMs
|
|
30837
|
-
});
|
|
31143
|
+
return withWaited(data, waited("user_message"));
|
|
30838
31144
|
}
|
|
30839
|
-
if (
|
|
30840
|
-
return withWaited(data,
|
|
30841
|
-
reason: "timeout",
|
|
30842
|
-
elapsed_ms: elapsedMs(),
|
|
30843
|
-
limit_ms: effectiveWaitMs
|
|
30844
|
-
});
|
|
31145
|
+
if (watchClock.now() >= deadline) {
|
|
31146
|
+
return withWaited(data, waited("timeout"));
|
|
30845
31147
|
}
|
|
30846
|
-
await
|
|
31148
|
+
await pause();
|
|
30847
31149
|
}
|
|
30848
31150
|
} finally {
|
|
30849
31151
|
if (waitForExit && !sawTerminal)
|
|
@@ -30950,16 +31252,19 @@ function withWaited(data, waited) {
|
|
|
30950
31252
|
return { ...data, waited };
|
|
30951
31253
|
}
|
|
30952
31254
|
function formatWaitSummary(waited, details, watchRole) {
|
|
30953
|
-
const waitedText = formatWatchWaited(waited.elapsed_ms, waited.limit_ms, watchRole.capMs);
|
|
31255
|
+
const waitedText = formatWatchWaited(waited.elapsed_ms, waited.limit_ms, watchRole.role === "primary" ? watchRole.capMs : undefined);
|
|
30954
31256
|
if (waited.reason === "matched") {
|
|
30955
31257
|
const stream = waited.match_stream ? ` in ${waited.match_stream}` : "";
|
|
30956
31258
|
return `${waitedText}; matched ${JSON.stringify(waited.match ?? "")}${stream} at offset ${waited.match_offset ?? 0}.`;
|
|
30957
31259
|
}
|
|
30958
31260
|
if (waited.reason === "timeout") {
|
|
30959
|
-
return `${waitedText}; timeout reached without match. ${watchTimeoutSteer(watchRole.role,
|
|
31261
|
+
return `${waitedText}; timeout reached without match. ${watchTimeoutSteer(watchRole.role, "timeout_ms")}`;
|
|
30960
31262
|
}
|
|
30961
31263
|
if (waited.reason === "unavailable") {
|
|
30962
|
-
return `${waitedText};
|
|
31264
|
+
return `${waitedText}; ${watchUnavailableSteer(watchRole.role)}`;
|
|
31265
|
+
}
|
|
31266
|
+
if (waited.reason === "aborted") {
|
|
31267
|
+
return `${waitedText}; the watch was cancelled. The task keeps running.`;
|
|
30963
31268
|
}
|
|
30964
31269
|
const exit = typeof details.exit_code === "number" ? `, exit ${details.exit_code}` : "";
|
|
30965
31270
|
return `${waitedText}; task exited (${details.status}${exit}).`;
|
|
@@ -30981,10 +31286,10 @@ ${formatWaitSummary(details.waited, details, watchRole)}`;
|
|
|
30981
31286
|
text += `
|
|
30982
31287
|
${details.output_preview}`;
|
|
30983
31288
|
}
|
|
30984
|
-
const
|
|
30985
|
-
if (!isTerminalStatus(details.status) && !
|
|
31289
|
+
const workerWatchReturned = watchRole.role === "worker" && details.waited !== undefined;
|
|
31290
|
+
if (!isTerminalStatus(details.status) && !workerWatchReturned) {
|
|
30986
31291
|
text += `
|
|
30987
|
-
|
|
31292
|
+
${runningTaskStatusHint(watchRole.role)}`;
|
|
30988
31293
|
}
|
|
30989
31294
|
}
|
|
30990
31295
|
return text;
|
|
@@ -31928,6 +32233,9 @@ function registerNavigateTool(pi, ctx) {
|
|
|
31928
32233
|
|
|
31929
32234
|
// src/tools/reading.ts
|
|
31930
32235
|
import { Type as Type10 } from "typebox";
|
|
32236
|
+
function isGithubPathArg(value) {
|
|
32237
|
+
return typeof value === "string" && (value.startsWith("issue://") || value.startsWith("pr://"));
|
|
32238
|
+
}
|
|
31931
32239
|
var OutlineParams = Type10.Object({
|
|
31932
32240
|
target: Type10.Union([Type10.String(), Type10.Array(Type10.String())], {
|
|
31933
32241
|
description: "What to outline: a file path, directory path, URL (http:// or https://), or array of file paths. The mode is auto-detected: URLs by `http://`/`https://` prefix, directories by stat, arrays as multi-file."
|
|
@@ -31952,7 +32260,7 @@ var ZoomParams = Type10.Object({
|
|
|
31952
32260
|
description: "Symbol name for code, or heading text for Markdown/HTML. Pass a string or array for batched lookups in the same file/URL."
|
|
31953
32261
|
})),
|
|
31954
32262
|
targets: Type10.Optional(Type10.Union([ZoomTarget, Type10.Array(ZoomTarget)], {
|
|
31955
|
-
description: "Cross-file batch: `{ path, symbol }` or an array of them.
|
|
32263
|
+
description: "Cross-file batch: `{ path, symbol }` or an array of them. May be combined with path/url + symbols; all lookups are answered in one batch."
|
|
31956
32264
|
})),
|
|
31957
32265
|
contextLines: optionalInt(1, Number.MAX_SAFE_INTEGER, "Lines of context before/after (default: 3)"),
|
|
31958
32266
|
callgraph: Type10.Optional(Type10.Boolean({
|
|
@@ -32106,6 +32414,13 @@ function renderZoomResult(result, args, theme, context, options = { expanded: tr
|
|
|
32106
32414
|
context
|
|
32107
32415
|
});
|
|
32108
32416
|
}
|
|
32417
|
+
function outlineFocusSteer(config) {
|
|
32418
|
+
const zoomEnabled = toolEnabled(config, "aft_zoom");
|
|
32419
|
+
const reader = zoomEnabled ? "aft_zoom" : "read";
|
|
32420
|
+
const locate = toolEnabled(config, "aft_search") ? `aft_search + ${reader}` : reader;
|
|
32421
|
+
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." : "";
|
|
32422
|
+
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}`;
|
|
32423
|
+
}
|
|
32109
32424
|
function registerReadingTools(pi, ctx, surface) {
|
|
32110
32425
|
const zoomEnabled = toolEnabled(ctx.config, "aft_zoom");
|
|
32111
32426
|
const ghReadEnabled = resolveGithubConfig(ctx.config).read;
|
|
@@ -32117,7 +32432,7 @@ function registerReadingTools(pi, ctx, surface) {
|
|
|
32117
32432
|
label: "outline",
|
|
32118
32433
|
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.
|
|
32119
32434
|
|
|
32120
|
-
${
|
|
32435
|
+
${outlineFocusSteer(ctx.config)}
|
|
32121
32436
|
|
|
32122
32437
|
Pass a single \`target\`:
|
|
32123
32438
|
• file path → outline that file (with signatures)
|
|
@@ -32184,56 +32499,49 @@ When a list is cut, the reply ends with \`shown N of M <unit> (<reason>) · narr
|
|
|
32184
32499
|
pi.registerTool(withPathAliasPreparation({
|
|
32185
32500
|
name: "aft_zoom",
|
|
32186
32501
|
label: "zoom",
|
|
32187
|
-
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
|
|
32502
|
+
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 ? `
|
|
32188
32503
|
|
|
32189
32504
|
${githubZoomDescription}` : ""),
|
|
32190
32505
|
parameters: ZoomParams,
|
|
32191
32506
|
async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
|
|
32192
32507
|
const bridge = bridgeFor(ctx, extCtx.cwd);
|
|
32193
|
-
const hasTargetsProvided = (t) => {
|
|
32194
|
-
if (isEmptyParam(t))
|
|
32195
|
-
return false;
|
|
32196
|
-
const entryEmpty = (entry) => {
|
|
32197
|
-
if (!entry || typeof entry !== "object")
|
|
32198
|
-
return true;
|
|
32199
|
-
const fp = entry.path;
|
|
32200
|
-
const sym = entry.symbol;
|
|
32201
|
-
const fpEmpty = typeof fp !== "string" || fp.length === 0;
|
|
32202
|
-
const symEmpty = typeof sym !== "string" || sym.length === 0;
|
|
32203
|
-
return fpEmpty && symEmpty;
|
|
32204
|
-
};
|
|
32205
|
-
if (Array.isArray(t))
|
|
32206
|
-
return !t.every(entryEmpty);
|
|
32207
|
-
return !entryEmpty(t);
|
|
32208
|
-
};
|
|
32209
32508
|
const targetsInput = coerceJsonCollectionParam(params.targets, "targets");
|
|
32210
|
-
const
|
|
32211
|
-
const
|
|
32212
|
-
const
|
|
32213
|
-
const
|
|
32509
|
+
const targetEntries = usableZoomTargets(targetsInput);
|
|
32510
|
+
const hasPath = !isBlankParam(params.path);
|
|
32511
|
+
const hasUrl = !isBlankParam(params.url);
|
|
32512
|
+
const hasTargets = targetEntries.length > 0;
|
|
32513
|
+
const hasSymbols = !isBlankParam(params.symbols);
|
|
32214
32514
|
const wantCallgraph = coerceBoolean(params.callgraph);
|
|
32215
32515
|
const contextLines = coerceOptionalInt(params.contextLines, "contextLines", 1, Number.MAX_SAFE_INTEGER);
|
|
32216
32516
|
if (hasTargets) {
|
|
32217
|
-
if (hasPath
|
|
32218
|
-
throw new Error("
|
|
32517
|
+
if (hasPath && hasUrl) {
|
|
32518
|
+
throw new Error("Provide exactly ONE of 'path' or 'url' — not both");
|
|
32219
32519
|
}
|
|
32220
|
-
const targets =
|
|
32221
|
-
|
|
32222
|
-
throw new Error("'targets' must be a non-empty object or array");
|
|
32223
|
-
}
|
|
32224
|
-
for (const [i, entry] of targets.entries()) {
|
|
32225
|
-
if (!entry || typeof entry.path !== "string" || entry.path.length === 0) {
|
|
32520
|
+
const targets = targetEntries.map((entry, i) => {
|
|
32521
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
32226
32522
|
throw new Error(`targets[${i}].path must be a non-empty string`);
|
|
32227
32523
|
}
|
|
32228
|
-
|
|
32229
|
-
|
|
32230
|
-
|
|
32231
|
-
|
|
32232
|
-
|
|
32524
|
+
const record = entry;
|
|
32525
|
+
const targetPath = isBlankParam(record.path) ? record.filePath : record.path;
|
|
32526
|
+
return {
|
|
32527
|
+
path: typeof targetPath === "string" ? targetPath : "",
|
|
32528
|
+
symbol: typeof record.symbol === "string" ? record.symbol : ""
|
|
32529
|
+
};
|
|
32530
|
+
});
|
|
32531
|
+
const localPaths = targets.map((target) => target.path).filter((path) => path.trim().length > 0 && !isGithubPathArg(path));
|
|
32532
|
+
if (hasPath && !isGithubPathArg(params.path))
|
|
32533
|
+
localPaths.push(params.path);
|
|
32534
|
+
const resolvedTargets = await Promise.all(localPaths.map((path) => resolvePathArg(extCtx.cwd, path)));
|
|
32233
32535
|
await assertReadPathPermissions(extCtx, ctx, resolvedTargets);
|
|
32234
32536
|
const rawArgs = {
|
|
32235
32537
|
targets: targets.map((target) => ({ filePath: target.path, symbol: target.symbol }))
|
|
32236
32538
|
};
|
|
32539
|
+
if (hasPath)
|
|
32540
|
+
rawArgs.filePath = params.path;
|
|
32541
|
+
else if (hasUrl)
|
|
32542
|
+
rawArgs.url = params.url;
|
|
32543
|
+
if (hasSymbols)
|
|
32544
|
+
rawArgs.symbols = params.symbols;
|
|
32237
32545
|
if (contextLines !== undefined)
|
|
32238
32546
|
rawArgs.contextLines = contextLines;
|
|
32239
32547
|
if (wantCallgraph)
|
|
@@ -32250,7 +32558,7 @@ ${githubZoomDescription}` : ""),
|
|
|
32250
32558
|
if (hasPath && hasUrl) {
|
|
32251
32559
|
throw new Error("Provide exactly ONE of 'path' or 'url' — not both");
|
|
32252
32560
|
}
|
|
32253
|
-
const githubPath = hasPath && (
|
|
32561
|
+
const githubPath = hasPath && isGithubPathArg(params.path) || hasUrl && isGithubPathArg(params.url);
|
|
32254
32562
|
if (!hasUrl && !githubPath) {
|
|
32255
32563
|
const file = await resolvePathArg(extCtx.cwd, params.path);
|
|
32256
32564
|
await assertReadPathPermissions(extCtx, ctx, file);
|
|
@@ -32289,7 +32597,7 @@ var SafetyParams = Type11.Object({
|
|
|
32289
32597
|
description: "Safety operation"
|
|
32290
32598
|
}),
|
|
32291
32599
|
path: Type11.Optional(Type11.String({
|
|
32292
|
-
description: "File path (required for history, optional for undo, optional for restore — restores only that file). Absolute or relative to project root."
|
|
32600
|
+
description: "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."
|
|
32293
32601
|
})),
|
|
32294
32602
|
name: Type11.Optional(Type11.String({ description: "Checkpoint name (required for checkpoint, restore)" })),
|
|
32295
32603
|
files: Type11.Optional(Type11.Array(Type11.String(), {
|
|
@@ -32722,6 +33030,8 @@ function withLiveRestriction(surface, ctx) {
|
|
|
32722
33030
|
function resolvePiToolSurface(config, pi) {
|
|
32723
33031
|
const disabled = new Set(resolvedDisabledTools(config));
|
|
32724
33032
|
const ok = (name) => !disabled.has(name);
|
|
33033
|
+
const background = resolveBashConfig(config).background;
|
|
33034
|
+
const companion = (name) => background && ok(name);
|
|
32725
33035
|
const powershellEnabled = (pi ? piPowerShellEnabledFromHost(pi) : undefined) ?? resolvePiPowerShellFallback(config);
|
|
32726
33036
|
return {
|
|
32727
33037
|
hoistBash: ok("bash"),
|
|
@@ -32743,10 +33053,10 @@ function resolvePiToolSurface(config, pi) {
|
|
|
32743
33053
|
move: ok("aft_move"),
|
|
32744
33054
|
astSearch: ok("ast_grep_search"),
|
|
32745
33055
|
astReplace: ok("ast_grep_replace"),
|
|
32746
|
-
bashStatus:
|
|
32747
|
-
bashWatch:
|
|
32748
|
-
bashWrite:
|
|
32749
|
-
bashKill:
|
|
33056
|
+
bashStatus: companion("bash_status"),
|
|
33057
|
+
bashWatch: companion("bash_watch"),
|
|
33058
|
+
bashWrite: companion("bash_write"),
|
|
33059
|
+
bashKill: companion("bash_kill")
|
|
32750
33060
|
};
|
|
32751
33061
|
}
|
|
32752
33062
|
var FUNNEL_BOUND = Symbol.for("aft.pi.registration_funnel_bound");
|
|
@@ -33016,7 +33326,10 @@ function registerShutdownCleanup(fn) {
|
|
|
33016
33326
|
|
|
33017
33327
|
// src/workflow-hints.ts
|
|
33018
33328
|
var HEADING = "## IMPORTANT NOTICE about your tools";
|
|
33019
|
-
|
|
33329
|
+
function hashlineTagSourceHint(bashRewritesMintTags, isRegistered = () => true) {
|
|
33330
|
+
return `**Hashline edit tags**: ${hashlineTagSourceSentence(bashRewritesMintTags, isRegistered)}`;
|
|
33331
|
+
}
|
|
33332
|
+
var HASHLINE_TAG_SOURCE_HINT = hashlineTagSourceHint(true);
|
|
33020
33333
|
function buildWorkflowHints(opts) {
|
|
33021
33334
|
const sections = [];
|
|
33022
33335
|
const grepName = "grep";
|
|
@@ -33031,6 +33344,8 @@ function buildWorkflowHints(opts) {
|
|
|
33031
33344
|
const hasInspect = !opts.absentTools.has("aft_inspect");
|
|
33032
33345
|
const hasBash = !opts.absentTools.has(bashName);
|
|
33033
33346
|
const hasBgBash = opts.bashBackgroundEnabled && hasBash && !opts.absentTools.has("bash_status");
|
|
33347
|
+
const hasWatch = !opts.absentTools.has("bash_watch");
|
|
33348
|
+
const hasWrite = !opts.absentTools.has("bash_write");
|
|
33034
33349
|
if (hasBash && opts.bashCompressionEnabled) {
|
|
33035
33350
|
sections.push([
|
|
33036
33351
|
"**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:",
|
|
@@ -33072,17 +33387,19 @@ function buildWorkflowHints(opts) {
|
|
|
33072
33387
|
].join(`
|
|
33073
33388
|
`));
|
|
33074
33389
|
}
|
|
33075
|
-
if (hasBgBash) {
|
|
33390
|
+
if (hasBgBash && hasWatch) {
|
|
33076
33391
|
sections.push([
|
|
33077
33392
|
`**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.`,
|
|
33078
33393
|
"- `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.",
|
|
33079
33394
|
"- `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."
|
|
33080
33395
|
].join(`
|
|
33081
33396
|
`));
|
|
33397
|
+
}
|
|
33398
|
+
if (hasBgBash && hasWrite) {
|
|
33082
33399
|
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 \`bash_status({ task_id, output_mode: "screen" })\`, and send input with \`bash_write({ task_id, input: "..." })\`.`);
|
|
33083
33400
|
}
|
|
33084
33401
|
if (opts.hashlineEffective === true) {
|
|
33085
|
-
sections.push(
|
|
33402
|
+
sections.push(hashlineTagSourceHint(hasBash && opts.bashRewriteEnabled !== false, (name) => !opts.absentTools.has(name)));
|
|
33086
33403
|
}
|
|
33087
33404
|
if (sections.length === 0) {
|
|
33088
33405
|
return null;
|
|
@@ -33097,9 +33414,11 @@ ${sections.join(`
|
|
|
33097
33414
|
`)}`;
|
|
33098
33415
|
}
|
|
33099
33416
|
function buildHintsFromConfig(config, absentTools, hashlineEffective = false) {
|
|
33417
|
+
const bashCfg = resolveBashConfig(config);
|
|
33100
33418
|
return buildWorkflowHints({
|
|
33101
|
-
bashBackgroundEnabled:
|
|
33102
|
-
bashCompressionEnabled:
|
|
33419
|
+
bashBackgroundEnabled: bashCfg.background,
|
|
33420
|
+
bashCompressionEnabled: bashCfg.compress,
|
|
33421
|
+
bashRewriteEnabled: bashCfg.rewrite,
|
|
33103
33422
|
absentTools,
|
|
33104
33423
|
hashlineEffective
|
|
33105
33424
|
});
|
|
@@ -33126,6 +33445,10 @@ function registerWorkflowHints(pi, config, surface) {
|
|
|
33126
33445
|
absent.add("bash");
|
|
33127
33446
|
if (!surface.bashStatus)
|
|
33128
33447
|
absent.add("bash_status");
|
|
33448
|
+
if (!surface.bashWatch)
|
|
33449
|
+
absent.add("bash_watch");
|
|
33450
|
+
if (!surface.bashWrite)
|
|
33451
|
+
absent.add("bash_write");
|
|
33129
33452
|
const hintsBlock = buildHintsFromConfig(config, absent, piHashlineEffective(config, surface));
|
|
33130
33453
|
if (!hintsBlock)
|
|
33131
33454
|
return;
|