@cortexkit/aft-pi 0.61.0 → 0.62.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config-live-reload.d.ts +2 -0
- package/dist/config-live-reload.d.ts.map +1 -1
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +521 -198
- package/dist/tool-registration.d.ts +2 -0
- package/dist/tool-registration.d.ts.map +1 -1
- package/dist/tools/_shared.d.ts.map +1 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/hoisted.d.ts +10 -0
- package/dist/tools/hoisted.d.ts.map +1 -1
- package/dist/tools/reading.d.ts +4 -10
- package/dist/tools/reading.d.ts.map +1 -1
- package/dist/tools/safety.d.ts.map +1 -1
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -8783,7 +8783,7 @@ function resolvePluginLogPath(context = {}) {
|
|
|
8783
8783
|
var BASH_HOST_FALLBACK_BANNER = "[AFT host fallback - module transport down; no rewrites/compression/background]";
|
|
8784
8784
|
var BASH_HOST_FALLBACK_MAX_OUTPUT_BYTES = 100 * 1024;
|
|
8785
8785
|
var BASH_HOST_FALLBACK_MAX_TIMEOUT_MS = 10 * 60 * 1000;
|
|
8786
|
-
var BASH_HOST_FALLBACK_REFUSAL = "AFT
|
|
8786
|
+
var BASH_HOST_FALLBACK_REFUSAL = "AFT module connection is down";
|
|
8787
8787
|
var BASH_RUNON_DESCRIPTION = 'Run on a remote build server: `linux` (2 vCPUs), or `linux,Nc` for N vCPUs such as `linux,8c`; add ",net" for outbound internet (offline by default). `windows` runs the command in Git Bash on a Windows build machine (no vCPU or network options).';
|
|
8788
8788
|
var BASH_RUNON_GUIDANCE = 'When remote runs are available, put `runon: "linux"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs macOS (Seatbelt, codesign, launchd, TCC, AppKit) or runs binaries built on this machine: a remote build leaves no binaries or target/ output in the local worktree. Add `,Nc` to request N vCPUs (`linux,4c`); without it the job gets 2 vCPUs. The job sees exactly the CPUs it gets, so ask for what the command uses: 2c (the default) for a single test binary or script, 4c for `cargo check`/clippy on one crate, 8c for a workspace test suite, and 16c for a large release build.';
|
|
8789
8789
|
function pathKeyForPlatform(env, platform) {
|
|
@@ -10877,7 +10877,7 @@ function isEmptyParam(value) {
|
|
|
10877
10877
|
return false;
|
|
10878
10878
|
}
|
|
10879
10879
|
function coerceJsonCollectionParam(value, field) {
|
|
10880
|
-
const shape = field
|
|
10880
|
+
const shape = field !== "sections" ? "an array of {target, symbols}" : "a string or array of strings";
|
|
10881
10881
|
if (typeof value === "string") {
|
|
10882
10882
|
const trimmed = value.trim();
|
|
10883
10883
|
if (trimmed.startsWith("[") || trimmed.startsWith("{")) {
|
|
@@ -10886,13 +10886,13 @@ function coerceJsonCollectionParam(value, field) {
|
|
|
10886
10886
|
} catch {
|
|
10887
10887
|
throw new Error(`${field} must be ${shape} (got a string that is not valid JSON)`);
|
|
10888
10888
|
}
|
|
10889
|
-
} else if (field
|
|
10889
|
+
} else if (field !== "sections") {
|
|
10890
10890
|
throw new Error(`${field} must be ${shape} (got a string that is not valid JSON)`);
|
|
10891
10891
|
}
|
|
10892
10892
|
}
|
|
10893
10893
|
if (value === undefined || value === null)
|
|
10894
10894
|
return value;
|
|
10895
|
-
if (field
|
|
10895
|
+
if (field !== "sections") {
|
|
10896
10896
|
if (Array.isArray(value) || typeof value === "object")
|
|
10897
10897
|
return value;
|
|
10898
10898
|
} else if (typeof value === "string" || Array.isArray(value) && value.every((entry) => typeof entry === "string")) {
|
|
@@ -10915,7 +10915,7 @@ function usableZoomTargets(targets) {
|
|
|
10915
10915
|
if (typeof entry !== "object")
|
|
10916
10916
|
return true;
|
|
10917
10917
|
const record = entry;
|
|
10918
|
-
return !(isBlankParam(record.path) && isBlankParam(record.filePath) && isBlankParam(record.symbol));
|
|
10918
|
+
return !(isBlankParam(record.target) && isBlankParam(record.url) && isBlankParam(record.symbols) && isBlankParam(record.path) && isBlankParam(record.filePath) && isBlankParam(record.symbol));
|
|
10919
10919
|
});
|
|
10920
10920
|
}
|
|
10921
10921
|
function isFindReplaceOnlyEdit(args) {
|
|
@@ -11090,6 +11090,7 @@ function aftLiveConfigKeys(resolveBash) {
|
|
|
11090
11090
|
return [
|
|
11091
11091
|
pathKey("configure_warnings_delivery"),
|
|
11092
11092
|
pathKey("restrict_to_project_root"),
|
|
11093
|
+
pathKey("revision_reads"),
|
|
11093
11094
|
pathKey("inspect.diagnostics_timeout_ms"),
|
|
11094
11095
|
pathKey("inspect.tier2_idle_minutes"),
|
|
11095
11096
|
pathKey("lsp.idle_minutes"),
|
|
@@ -11107,7 +11108,8 @@ function aftLiveConfigKeys(resolveBash) {
|
|
|
11107
11108
|
bashKey("runon_enabled", resolveBash),
|
|
11108
11109
|
bashKey("subagent_background", resolveBash),
|
|
11109
11110
|
bashKey("watch_sync_max_ms", resolveBash),
|
|
11110
|
-
bashKey("worker_wait_max_ms", resolveBash)
|
|
11111
|
+
bashKey("worker_wait_max_ms", resolveBash),
|
|
11112
|
+
bashKey("env", resolveBash)
|
|
11111
11113
|
];
|
|
11112
11114
|
}
|
|
11113
11115
|
function sameValue(a, b) {
|
|
@@ -15035,6 +15037,47 @@ function isSocketDeathError(error, depth = 0) {
|
|
|
15035
15037
|
}
|
|
15036
15038
|
return isConsumerReconnectTransient(error) || isSubcClientClosedError(error);
|
|
15037
15039
|
}
|
|
15040
|
+
var REQUEST_CONNECTION_LOSS_CODES = new Set(["ECONNABORTED", "ECONNRESET", "EPIPE"]);
|
|
15041
|
+
function hasRequestConnectionLossCode(error) {
|
|
15042
|
+
if (typeof error !== "object" || error === null || !("code" in error))
|
|
15043
|
+
return false;
|
|
15044
|
+
return typeof error.code === "string" && REQUEST_CONNECTION_LOSS_CODES.has(error.code);
|
|
15045
|
+
}
|
|
15046
|
+
function hasRequestConnectionLossEvidence(error, depth = 0) {
|
|
15047
|
+
if (error instanceof SocketClosedError)
|
|
15048
|
+
return true;
|
|
15049
|
+
if (error instanceof SubcError && error.closeReason === "connection_lost")
|
|
15050
|
+
return true;
|
|
15051
|
+
if (hasRequestConnectionLossCode(error))
|
|
15052
|
+
return true;
|
|
15053
|
+
if (!(error instanceof SubcCallError) || error.kind !== "outcome_unknown" || depth >= 4 || error.cause === undefined || error.cause === error) {
|
|
15054
|
+
return false;
|
|
15055
|
+
}
|
|
15056
|
+
return hasRequestConnectionLossEvidence(error.cause, depth + 1);
|
|
15057
|
+
}
|
|
15058
|
+
function connectionClosedAfterDispatch(error) {
|
|
15059
|
+
if (error instanceof SubcCallError && error.kind === "not_sent")
|
|
15060
|
+
return error;
|
|
15061
|
+
if (typeof error === "object" && error !== null && "kind" in error && error.kind === "not_sent") {
|
|
15062
|
+
return error;
|
|
15063
|
+
}
|
|
15064
|
+
if (!hasRequestConnectionLossEvidence(error))
|
|
15065
|
+
return error;
|
|
15066
|
+
if (error instanceof SubcCallError)
|
|
15067
|
+
return error;
|
|
15068
|
+
if (!(error instanceof Error))
|
|
15069
|
+
return error;
|
|
15070
|
+
return Object.assign(error, { kind: "outcome_unknown" });
|
|
15071
|
+
}
|
|
15072
|
+
var dispatchedRequestDeadlines = new WeakSet;
|
|
15073
|
+
function noteDispatchedRequestDeadline(error) {
|
|
15074
|
+
if (error instanceof SubcError && error.code === REQUEST_DEADLINE_CODE) {
|
|
15075
|
+
dispatchedRequestDeadlines.add(error);
|
|
15076
|
+
}
|
|
15077
|
+
}
|
|
15078
|
+
function isDispatchedRequestDeadlineError(error) {
|
|
15079
|
+
return typeof error === "object" && error !== null && dispatchedRequestDeadlines.has(error);
|
|
15080
|
+
}
|
|
15038
15081
|
function isUnansweredCallError(error) {
|
|
15039
15082
|
if (isSocketDeathError(error))
|
|
15040
15083
|
return false;
|
|
@@ -15641,7 +15684,24 @@ class SubcTransport {
|
|
|
15641
15684
|
return {};
|
|
15642
15685
|
const preview = options.preview;
|
|
15643
15686
|
const timeoutMs = options.transportTimeoutMs ?? options.timeoutMs;
|
|
15644
|
-
const
|
|
15687
|
+
const callback = options.onProgress;
|
|
15688
|
+
const onProgress = callback ? (body) => {
|
|
15689
|
+
let frame;
|
|
15690
|
+
try {
|
|
15691
|
+
frame = JSON.parse(new TextDecoder().decode(body));
|
|
15692
|
+
} catch {
|
|
15693
|
+
return;
|
|
15694
|
+
}
|
|
15695
|
+
if (typeof frame !== "object" || frame === null)
|
|
15696
|
+
return;
|
|
15697
|
+
const progress = frame;
|
|
15698
|
+
if (progress.type !== "progress" || typeof progress.chunk !== "string")
|
|
15699
|
+
return;
|
|
15700
|
+
callback({
|
|
15701
|
+
kind: progress.kind === "stderr" ? "stderr" : "stdout",
|
|
15702
|
+
text: progress.chunk
|
|
15703
|
+
});
|
|
15704
|
+
} : undefined;
|
|
15645
15705
|
return { preview, timeoutMs, onProgress, abortSignal: options.abortSignal };
|
|
15646
15706
|
}
|
|
15647
15707
|
}
|
|
@@ -15653,7 +15713,7 @@ class SubcTransportPool {
|
|
|
15653
15713
|
consumerIdentity;
|
|
15654
15714
|
hostScopes;
|
|
15655
15715
|
hostScopeCache = new Map;
|
|
15656
|
-
hostScopeRefusals = new
|
|
15716
|
+
hostScopeRefusals = new Map;
|
|
15657
15717
|
hostScopeOutcomes = new Map;
|
|
15658
15718
|
connectFn;
|
|
15659
15719
|
onBgEventsNudge;
|
|
@@ -16540,7 +16600,7 @@ class SubcTransportPool {
|
|
|
16540
16600
|
this.resetRouteReopenBackoff();
|
|
16541
16601
|
return reply;
|
|
16542
16602
|
} catch (requestError) {
|
|
16543
|
-
const error = frameReadTimeoutError(routeClosedMidCallError(requestError, routeAndEntry.entry, record));
|
|
16603
|
+
const error = frameReadTimeoutError(routeClosedMidCallError(connectionClosedAfterDispatch(requestError), routeAndEntry.entry, record));
|
|
16544
16604
|
if (this.isReapInduced(record))
|
|
16545
16605
|
throw this.annotateReapError(error, record);
|
|
16546
16606
|
const ownsRoute = this.isCurrentSession(key, record) && this.client === client;
|
|
@@ -16574,6 +16634,7 @@ class SubcTransportPool {
|
|
|
16574
16634
|
continue;
|
|
16575
16635
|
}
|
|
16576
16636
|
handleRequestFailure(error, routeAndEntry.entry);
|
|
16637
|
+
noteDispatchedRequestDeadline(error);
|
|
16577
16638
|
throw error;
|
|
16578
16639
|
}
|
|
16579
16640
|
}
|
|
@@ -16707,20 +16768,38 @@ class SubcTransportPool {
|
|
|
16707
16768
|
return { route: await opening, entry };
|
|
16708
16769
|
}
|
|
16709
16770
|
hostScope(client, identity) {
|
|
16710
|
-
|
|
16711
|
-
|
|
16771
|
+
let lookupReason = "cache_miss";
|
|
16772
|
+
const refused = this.hostScopeRefusals.get(identity.session);
|
|
16773
|
+
if (refused) {
|
|
16774
|
+
const previous = this.hostScopeCache.get(identity.session);
|
|
16775
|
+
if (previous)
|
|
16776
|
+
this.cancelHostScopeRetry(previous);
|
|
16777
|
+
this.hostScopeCache.delete(identity.session);
|
|
16778
|
+
this.hostScopeRefusals.delete(identity.session);
|
|
16779
|
+
lookupReason = `previous_bind_refusal code=${refused}`;
|
|
16780
|
+
}
|
|
16712
16781
|
let state = this.hostScopeCache.get(identity.session);
|
|
16713
16782
|
if (!state) {
|
|
16714
16783
|
state = { retryAt: 0, retryMs: HOST_SCOPE_RETRY_FLOOR_MS };
|
|
16715
16784
|
this.hostScopeCache.set(identity.session, state);
|
|
16716
16785
|
}
|
|
16717
16786
|
if (state.answer) {
|
|
16718
|
-
|
|
16787
|
+
const reason = state.scopeBindPending ? "scope_sync_pending" : `cached_${state.answer.result}`;
|
|
16788
|
+
log(`subc head scope: session=${identity.session} lookup=skipped reason=${reason}`);
|
|
16789
|
+
return Promise.resolve({
|
|
16790
|
+
scope: state.answer.result === "scope" && !state.scopeBindPending ? state.answer.scope : undefined,
|
|
16791
|
+
reason
|
|
16792
|
+
});
|
|
16719
16793
|
}
|
|
16720
|
-
if (state.pending)
|
|
16794
|
+
if (state.pending) {
|
|
16795
|
+
log(`subc head scope: session=${identity.session} lookup=skipped reason=inflight_lookup`);
|
|
16721
16796
|
return state.pending;
|
|
16722
|
-
|
|
16723
|
-
|
|
16797
|
+
}
|
|
16798
|
+
if (Date.now() < state.retryAt) {
|
|
16799
|
+
log(`subc head scope: session=${identity.session} lookup=skipped reason=backoff retry_in=${state.retryAt - Date.now()}ms`);
|
|
16800
|
+
return Promise.resolve({ reason: state.unscopedReason ?? "lookup_backoff" });
|
|
16801
|
+
}
|
|
16802
|
+
log(`subc head scope: session=${identity.session} lookup=started reason=${lookupReason}`);
|
|
16724
16803
|
const current = state;
|
|
16725
16804
|
clearTimeout(current.retryTimer);
|
|
16726
16805
|
current.retryTimer = undefined;
|
|
@@ -16728,24 +16807,32 @@ class SubcTransportPool {
|
|
|
16728
16807
|
current.lookupAbort = abort;
|
|
16729
16808
|
const lookup = this.readHostScope(client, identity, abort.signal).then((outcome) => {
|
|
16730
16809
|
if (this.hostScopeCache.get(identity.session) !== current)
|
|
16731
|
-
return;
|
|
16810
|
+
return { reason: "lookup_invalidated" };
|
|
16732
16811
|
current.pending = undefined;
|
|
16733
16812
|
current.lookupAbort = undefined;
|
|
16734
16813
|
if (abort.signal.aborted)
|
|
16735
|
-
return;
|
|
16814
|
+
return { reason: "lookup_cancelled" };
|
|
16736
16815
|
this.logHostScopeOutcome(identity.session, outcome);
|
|
16816
|
+
const reason = outcome.result === "error" ? `lookup_error code=${outcome.code}` : `lookup_${outcome.result}`;
|
|
16737
16817
|
if (outcome.result === "scope" || outcome.result === "no_scope") {
|
|
16738
16818
|
current.answer = outcome;
|
|
16739
16819
|
current.retryAt = 0;
|
|
16740
16820
|
current.retryMs = HOST_SCOPE_RETRY_FLOOR_MS;
|
|
16741
|
-
if (outcome.result === "scope")
|
|
16742
|
-
|
|
16821
|
+
if (outcome.result === "scope") {
|
|
16822
|
+
current.promoteAfterBind = Boolean(refused);
|
|
16823
|
+
if (!current.promoteAfterBind)
|
|
16824
|
+
this.promoteHostScope(identity.session);
|
|
16825
|
+
}
|
|
16743
16826
|
} else {
|
|
16827
|
+
current.unscopedReason = reason;
|
|
16744
16828
|
current.retryAt = Date.now() + current.retryMs;
|
|
16745
16829
|
current.retryMs = Math.min(current.retryMs * 2, HOST_SCOPE_RETRY_CAP_MS);
|
|
16746
16830
|
this.scheduleHostScopeRetry(identity.session, current);
|
|
16747
16831
|
}
|
|
16748
|
-
return
|
|
16832
|
+
return {
|
|
16833
|
+
scope: outcome.result === "scope" ? outcome.scope : undefined,
|
|
16834
|
+
reason
|
|
16835
|
+
};
|
|
16749
16836
|
});
|
|
16750
16837
|
current.pending = lookup;
|
|
16751
16838
|
return lookup;
|
|
@@ -16792,7 +16879,7 @@ class SubcTransportPool {
|
|
|
16792
16879
|
retry = this.hostScope(client, record.identity);
|
|
16793
16880
|
}
|
|
16794
16881
|
retry.catch((error) => {
|
|
16795
|
-
warn(`subc head scope: background retry failed code=${error instanceof Error ? error.name : "Error"}`);
|
|
16882
|
+
warn(`subc head scope: session=${session} background retry failed code=${error instanceof Error ? error.name : "Error"}`);
|
|
16796
16883
|
});
|
|
16797
16884
|
}, Math.max(0, state.retryAt - Date.now()));
|
|
16798
16885
|
state.retryTimer.unref();
|
|
@@ -16808,8 +16895,8 @@ class SubcTransportPool {
|
|
|
16808
16895
|
}
|
|
16809
16896
|
refuseHostScopeBind(session, state, code) {
|
|
16810
16897
|
if (!this.hostScopeRefusals.has(session)) {
|
|
16811
|
-
this.hostScopeRefusals.
|
|
16812
|
-
warn(`subc head scope: session=${session} scoped bind refused code=${code};
|
|
16898
|
+
this.hostScopeRefusals.set(session, code);
|
|
16899
|
+
warn(`subc head scope: session=${session} scoped bind refused code=${code}; retry stopped until next bind lookup`);
|
|
16813
16900
|
}
|
|
16814
16901
|
if (state) {
|
|
16815
16902
|
state.scopeBindPending = false;
|
|
@@ -16818,11 +16905,12 @@ class SubcTransportPool {
|
|
|
16818
16905
|
}
|
|
16819
16906
|
async retryHostScopeBind(client, identity, state, scope) {
|
|
16820
16907
|
try {
|
|
16821
|
-
const route = await
|
|
16908
|
+
const route = await this.bindHostToolRoute(client, identity, "scope_probe", "scope_sync_retry", scope);
|
|
16822
16909
|
safeCloseRoute(client, route);
|
|
16823
16910
|
if (this.hostScopeCache.get(identity.session) !== state || !state.scopeBindPending || this.shuttingDown || client !== this.client || !this.unscopedHostRecord(identity.session))
|
|
16824
16911
|
return;
|
|
16825
16912
|
state.scopeBindPending = false;
|
|
16913
|
+
state.promoteAfterBind = false;
|
|
16826
16914
|
state.retryAt = 0;
|
|
16827
16915
|
state.retryMs = HOST_SCOPE_RETRY_FLOOR_MS;
|
|
16828
16916
|
log(`subc head scope: session=${identity.session} scoped bind recovered ref=${scope.ref.slice(0, 8)} epoch=${scope.scopeEpoch}`);
|
|
@@ -16948,24 +17036,39 @@ class SubcTransportPool {
|
|
|
16948
17036
|
signal.removeEventListener("abort", cancel);
|
|
16949
17037
|
}
|
|
16950
17038
|
}
|
|
16951
|
-
openHostToolRoute(client, identity, record, entry) {
|
|
17039
|
+
openHostToolRoute(client, identity, record, entry, channel = "tool") {
|
|
16952
17040
|
if (!this.hostScopes || !["opencode", "pi"].includes(identity.harness) || identity.session === DEFAULT_SESSION_ID) {
|
|
16953
|
-
|
|
16954
|
-
|
|
16955
|
-
|
|
17041
|
+
const reason = !this.hostScopes ? "host_scopes_disabled" : identity.session === DEFAULT_SESSION_ID ? "default_session" : "unsupported_harness";
|
|
17042
|
+
log(`subc head scope: session=${identity.session} lookup=skipped reason=${reason}`);
|
|
17043
|
+
return this.bindHostToolRoute(client, identity, channel, reason);
|
|
16956
17044
|
}
|
|
16957
|
-
return this.openScopedHostToolRoute(client, identity, record, entry);
|
|
17045
|
+
return this.openScopedHostToolRoute(client, identity, record, entry, channel);
|
|
16958
17046
|
}
|
|
16959
|
-
|
|
16960
|
-
|
|
17047
|
+
bindHostToolRoute(client, identity, channel, reason, scope) {
|
|
17048
|
+
log(`subc head scope: session=${identity.session} bind=${scope ? "scoped" : "unscoped"} channel=${channel} root=${identity.project_root} reason=${reason}` + (scope ? ` ref=${scope.ref.slice(0, 8)} epoch=${scope.scopeEpoch}` : ""));
|
|
17049
|
+
return client.routeOpen({ kind: "tool_provider", module_id: AFT_MODULE_ID }, identity, {
|
|
17050
|
+
consumerIdentity: this.consumerIdentity,
|
|
17051
|
+
...scope ? { scope } : {}
|
|
17052
|
+
});
|
|
17053
|
+
}
|
|
17054
|
+
async openScopedHostToolRoute(client, identity, record, entry, channel = "tool") {
|
|
17055
|
+
const decision = await this.hostScope(client, identity);
|
|
17056
|
+
const scope = decision.scope;
|
|
17057
|
+
let reason = decision.reason;
|
|
16961
17058
|
this.assertRecordLive(record);
|
|
16962
17059
|
if (scope) {
|
|
16963
17060
|
try {
|
|
16964
|
-
const route = await
|
|
17061
|
+
const route = await this.bindHostToolRoute(client, identity, channel, reason, scope);
|
|
16965
17062
|
if (entry)
|
|
16966
17063
|
entry.hostScope = scope;
|
|
17064
|
+
const state = this.hostScopeCache.get(identity.session);
|
|
17065
|
+
if (state?.promoteAfterBind && state.answer?.result === "scope" && state.answer.scope === scope) {
|
|
17066
|
+
state.promoteAfterBind = false;
|
|
17067
|
+
this.promoteHostScope(identity.session);
|
|
17068
|
+
}
|
|
16967
17069
|
return route;
|
|
16968
17070
|
} catch (error) {
|
|
17071
|
+
log(`subc head scope: session=${identity.session} bind=failed channel=${channel} reason=scoped_open_error code=${error instanceof SubcError ? error.code ?? error.name : "Error"}`);
|
|
16969
17072
|
if (!(error instanceof SubcError) || !error.code || isSocketDeathError(error))
|
|
16970
17073
|
throw error;
|
|
16971
17074
|
if (isRouteOpenReloadWindowError(error))
|
|
@@ -16974,18 +17077,21 @@ class SubcTransportPool {
|
|
|
16974
17077
|
const state = this.hostScopeCache.get(identity.session);
|
|
16975
17078
|
if (state && isHostScopeSyncRefusal(error.code)) {
|
|
16976
17079
|
this.deferHostScopeBind(identity.session, state, error.code);
|
|
17080
|
+
reason = `scope_sync_pending code=${error.code}`;
|
|
16977
17081
|
} else {
|
|
16978
17082
|
this.refuseHostScopeBind(identity.session, state, error.code);
|
|
17083
|
+
if (decision.reason === "cached_scope") {
|
|
17084
|
+
return this.openScopedHostToolRoute(client, identity, record, entry, channel);
|
|
17085
|
+
}
|
|
17086
|
+
reason = `scoped_bind_refused code=${error.code}`;
|
|
16979
17087
|
}
|
|
16980
17088
|
}
|
|
16981
17089
|
}
|
|
16982
17090
|
this.assertRecordLive(record);
|
|
16983
|
-
const route = await
|
|
16984
|
-
consumerIdentity: this.consumerIdentity
|
|
16985
|
-
});
|
|
17091
|
+
const route = await this.bindHostToolRoute(client, identity, channel, reason);
|
|
16986
17092
|
if (this.hostScopeCache.get(identity.session)?.answer?.result === "scope" && !this.hostScopeCache.get(identity.session)?.scopeBindPending && !this.hostScopeRefusals.has(identity.session)) {
|
|
16987
17093
|
safeCloseRoute(client, route);
|
|
16988
|
-
return this.openScopedHostToolRoute(client, identity, record, entry);
|
|
17094
|
+
return this.openScopedHostToolRoute(client, identity, record, entry, channel);
|
|
16989
17095
|
}
|
|
16990
17096
|
return route;
|
|
16991
17097
|
}
|
|
@@ -17082,7 +17188,7 @@ class SubcTransportPool {
|
|
|
17082
17188
|
};
|
|
17083
17189
|
sub = new BgSubscription(identity, () => this.ensureClient(), (client, cause) => this.dropClient(client, cause), (client) => {
|
|
17084
17190
|
binding.hostScope = undefined;
|
|
17085
|
-
return this.openHostToolRoute(client, identity, record, binding);
|
|
17191
|
+
return this.openHostToolRoute(client, identity, record, binding, "bg_events");
|
|
17086
17192
|
}, onNudge, this.bgBackoffSleep, () => this.rootCanAttach(record.canonicalRoot), (error) => {
|
|
17087
17193
|
if (!isAbsentRootRouteError(error))
|
|
17088
17194
|
return false;
|
|
@@ -17375,11 +17481,54 @@ function shellTimeoutDisposition(outcome) {
|
|
|
17375
17481
|
var SUBC_MODULE_RESTART_DISPOSITION = "The AFT daemon module restarted while this call was in flight, so its outcome is UNKNOWN: it may or may not have executed. Verify actual state before re-running, and never blind-retry a mutation.";
|
|
17376
17482
|
var BRIDGE_TRANSPORT_UNKNOWN_OUTCOME_DISPOSITION = "The standalone AFT transport failed after this call may have been sent, so its outcome is UNKNOWN: it may or may not have executed. Verify actual state before re-running, and never blind-retry a mutation.";
|
|
17377
17483
|
var SUBC_ROUTE_CLOSED_MID_CALL_DISPOSITION = "The plugin closed this call's route while the call was in flight, so its outcome is UNKNOWN: it may or may not have executed. Verify actual state before re-running, and never blind-retry a mutation.";
|
|
17484
|
+
var SUBC_CONNECTION_CLOSED_MID_CALL_DISPOSITION = "The AFT module connection closed while this request was in flight, so its outcome is UNKNOWN: it may or may not have executed. Verify actual state before retrying, and never blindly retry a mutation.";
|
|
17485
|
+
function bashConnectionClosedDisposition(taskId) {
|
|
17486
|
+
const taskFollowup = taskId ? `Use bash_status or bash_watch with task ID ${taskId} to check it.` : "If a task ID is available, use it with bash_status or bash_watch; otherwise wait for the completion notice.";
|
|
17487
|
+
return `The command may have run or may still be running. AFT keeps commands running when the connection drops; a completion notice will follow if it ran. ${taskFollowup} Do not rerun it.`;
|
|
17488
|
+
}
|
|
17489
|
+
|
|
17490
|
+
class AftModuleUnavailableError extends SubcCallError {
|
|
17491
|
+
constructor(message, cause) {
|
|
17492
|
+
super("not_sent", message, "aft_module_unavailable", cause);
|
|
17493
|
+
this.name = "AftModuleUnavailableError";
|
|
17494
|
+
}
|
|
17495
|
+
}
|
|
17496
|
+
function refuseAftModuleOptionWithoutFallback(error, option, refusalPrefix) {
|
|
17497
|
+
return new AftModuleUnavailableError(`${refusalPrefix}; ${option} has no host fallback. Nothing was started, so it is safe to retry after the AFT module connection is restored.`, error);
|
|
17498
|
+
}
|
|
17378
17499
|
var BASH_FRAME_READ_TIMEOUT_DISPOSITION = "The command's state is UNKNOWN: the transport timed out while reading the response frame, so it may or may not have executed. Check bash_status if a task ID could have been assigned, and do not re-run side-effecting commands blindly.";
|
|
17379
17500
|
var SUBC_FRAME_READ_TIMEOUT_DISPOSITION = "The transport timed out while reading the response frame for this call, so its outcome is UNKNOWN: it may or may not have executed. Verify actual state before re-running, and never blind-retry a mutation.";
|
|
17380
17501
|
function isRouteClosedMidCallError(error) {
|
|
17381
17502
|
return error instanceof SubcRouteClosedMidCallError || isLocalRouteCloseRejection(error);
|
|
17382
17503
|
}
|
|
17504
|
+
function isSubcConnectionClosedMidCallError(error) {
|
|
17505
|
+
if (!(error instanceof Error))
|
|
17506
|
+
return false;
|
|
17507
|
+
const fields = error;
|
|
17508
|
+
if (fields.kind !== "outcome_unknown")
|
|
17509
|
+
return false;
|
|
17510
|
+
if (error instanceof SocketClosedError)
|
|
17511
|
+
return true;
|
|
17512
|
+
if (error instanceof SubcError)
|
|
17513
|
+
return error.closeReason === "connection_lost";
|
|
17514
|
+
if (error instanceof SubcCallError) {
|
|
17515
|
+
return error.closeReason === "connection_lost" || hasConnectionLossCause(error);
|
|
17516
|
+
}
|
|
17517
|
+
return hasConnectionLossCause(error);
|
|
17518
|
+
}
|
|
17519
|
+
var CONNECTION_LOSS_ERROR_CODES = new Set(["ECONNABORTED", "ECONNRESET", "EPIPE"]);
|
|
17520
|
+
function hasConnectionLossCause(error, depth = 0) {
|
|
17521
|
+
if (error instanceof SocketClosedError)
|
|
17522
|
+
return true;
|
|
17523
|
+
if (error instanceof SubcError && error.closeReason === "connection_lost")
|
|
17524
|
+
return true;
|
|
17525
|
+
if (typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" && CONNECTION_LOSS_ERROR_CODES.has(error.code)) {
|
|
17526
|
+
return true;
|
|
17527
|
+
}
|
|
17528
|
+
if (!(error instanceof Error) || depth >= 4 || error.cause === undefined)
|
|
17529
|
+
return false;
|
|
17530
|
+
return hasConnectionLossCause(error.cause, depth + 1);
|
|
17531
|
+
}
|
|
17383
17532
|
function isRouteGoodbyeError(error) {
|
|
17384
17533
|
if (!(error instanceof SubcError))
|
|
17385
17534
|
return false;
|
|
@@ -17434,6 +17583,8 @@ function classifyBashHostFallbackError(error) {
|
|
|
17434
17583
|
if (!(error instanceof Error) || hasEngineResponse(error) || isRouteGoodbyeError(error)) {
|
|
17435
17584
|
return;
|
|
17436
17585
|
}
|
|
17586
|
+
if (isSubcConnectionClosedMidCallError(error))
|
|
17587
|
+
return;
|
|
17437
17588
|
if (error instanceof BridgeTransportUnknownOutcomeError)
|
|
17438
17589
|
return;
|
|
17439
17590
|
if (isRouteClosedMidCallError(error))
|
|
@@ -17460,6 +17611,8 @@ function isBashTransportDeadError(error) {
|
|
|
17460
17611
|
function adaptToolError(command, error) {
|
|
17461
17612
|
if (!(error instanceof Error))
|
|
17462
17613
|
return error;
|
|
17614
|
+
if (error instanceof AftModuleUnavailableError)
|
|
17615
|
+
return error;
|
|
17463
17616
|
if (isSubcFrameReadTimeoutError(error)) {
|
|
17464
17617
|
const typedError = error instanceof SubcFrameReadTimeoutError ? error : new SubcFrameReadTimeoutError(error);
|
|
17465
17618
|
const disposition = command === "bash" ? BASH_FRAME_READ_TIMEOUT_DISPOSITION : SUBC_FRAME_READ_TIMEOUT_DISPOSITION;
|
|
@@ -17474,6 +17627,13 @@ function adaptToolError(command, error) {
|
|
|
17474
17627
|
error.message = error.message ? `${error.message} ${BRIDGE_TRANSPORT_UNKNOWN_OUTCOME_DISPOSITION}` : BRIDGE_TRANSPORT_UNKNOWN_OUTCOME_DISPOSITION;
|
|
17475
17628
|
return error;
|
|
17476
17629
|
}
|
|
17630
|
+
if (isSubcConnectionClosedMidCallError(error)) {
|
|
17631
|
+
const disposition = command === "bash" ? bashConnectionClosedDisposition(taskIdFromError(error)) : SUBC_CONNECTION_CLOSED_MID_CALL_DISPOSITION;
|
|
17632
|
+
if (error.message.includes(disposition))
|
|
17633
|
+
return error;
|
|
17634
|
+
error.message = error.message ? `${error.message} ${disposition}` : disposition;
|
|
17635
|
+
return error;
|
|
17636
|
+
}
|
|
17477
17637
|
if (isRouteGoodbyeError(error)) {
|
|
17478
17638
|
if (error.message.includes(SUBC_MODULE_RESTART_DISPOSITION))
|
|
17479
17639
|
return error;
|
|
@@ -17486,6 +17646,13 @@ function adaptToolError(command, error) {
|
|
|
17486
17646
|
error.message = error.message ? `${error.message} ${SUBC_ROUTE_CLOSED_MID_CALL_DISPOSITION}` : SUBC_ROUTE_CLOSED_MID_CALL_DISPOSITION;
|
|
17487
17647
|
return error;
|
|
17488
17648
|
}
|
|
17649
|
+
if (command === "bash" && isDispatchedRequestDeadlineError(error)) {
|
|
17650
|
+
const disposition = bashConnectionClosedDisposition(taskIdFromError(error));
|
|
17651
|
+
if (error.message.includes(disposition))
|
|
17652
|
+
return error;
|
|
17653
|
+
error.message = error.message ? `${error.message} ${disposition}` : disposition;
|
|
17654
|
+
return error;
|
|
17655
|
+
}
|
|
17489
17656
|
if ((command === "bash" || command === "powershell") && isBridgeTransportTimeout(error)) {
|
|
17490
17657
|
const outcome = error.shellOutcome;
|
|
17491
17658
|
const disposition = shellTimeoutDisposition(outcome);
|
|
@@ -17494,6 +17661,9 @@ function adaptToolError(command, error) {
|
|
|
17494
17661
|
error.message = error.message ? `${error.message} ${disposition}` : disposition;
|
|
17495
17662
|
return error;
|
|
17496
17663
|
}
|
|
17664
|
+
if (command !== "bash" && isBashTransportDeadError(error)) {
|
|
17665
|
+
return new AftModuleUnavailableError("The AFT module connection is down or unavailable; this AFT-backed option has no host fallback. Nothing was started, so it is safe to retry after the connection is restored.", error);
|
|
17666
|
+
}
|
|
17497
17667
|
if (command !== "bash" || !isBashTransportDeadError(error))
|
|
17498
17668
|
return error;
|
|
17499
17669
|
if (error.message.includes(BASH_TRANSPORT_DISPOSITION))
|
|
@@ -17501,6 +17671,15 @@ function adaptToolError(command, error) {
|
|
|
17501
17671
|
error.message = error.message ? `${error.message} ${BASH_TRANSPORT_DISPOSITION}` : BASH_TRANSPORT_DISPOSITION;
|
|
17502
17672
|
return error;
|
|
17503
17673
|
}
|
|
17674
|
+
function taskIdFromError(error) {
|
|
17675
|
+
for (let current = error, depth = 0;current instanceof Error && depth < 5; current = current.cause, depth += 1) {
|
|
17676
|
+
const fields = current;
|
|
17677
|
+
const taskId = fields.taskId ?? fields.task_id;
|
|
17678
|
+
if (typeof taskId === "string" && taskId.length > 0)
|
|
17679
|
+
return taskId;
|
|
17680
|
+
}
|
|
17681
|
+
return;
|
|
17682
|
+
}
|
|
17504
17683
|
// ../aft-bridge/dist/feature-config.js
|
|
17505
17684
|
import { createHash as createHash3 } from "node:crypto";
|
|
17506
17685
|
var CANONICAL_TOOLS = [
|
|
@@ -22521,39 +22700,70 @@ function validateOptionalCanonicalPath(record, property) {
|
|
|
22521
22700
|
}
|
|
22522
22701
|
pathValue(record, property);
|
|
22523
22702
|
}
|
|
22524
|
-
function
|
|
22525
|
-
|
|
22526
|
-
|
|
22527
|
-
|
|
22528
|
-
|
|
22529
|
-
|
|
22530
|
-
|
|
22531
|
-
}
|
|
22532
|
-
const source = target;
|
|
22533
|
-
const blank = (value) => value === undefined || value === null || typeof value === "string" && !value.trim();
|
|
22534
|
-
const emptyTarget = blank(source.symbol) && blank(source.path) && blank(source.filePath);
|
|
22535
|
-
if (emptyTarget)
|
|
22536
|
-
return { ...source };
|
|
22537
|
-
const normalized = { ...source };
|
|
22538
|
-
try {
|
|
22539
|
-
normalizeAliasPair(normalized, "path", "filePath", false);
|
|
22540
|
-
} catch (error) {
|
|
22541
|
-
if (error instanceof InvalidRequestError) {
|
|
22542
|
-
throw new InvalidRequestError(error.message.replace("'filePath'", `'targets[${index}].filePath'`).replace("'path'", `'targets[${index}].path'`));
|
|
22543
|
-
}
|
|
22544
|
-
throw error;
|
|
22703
|
+
function normalizeZoomLocation(record, prefix = "") {
|
|
22704
|
+
let first;
|
|
22705
|
+
for (const key of ["target", "path", "filePath", "url"]) {
|
|
22706
|
+
const value = record[key];
|
|
22707
|
+
if (value === undefined || value === null || typeof value === "string" && !value.trim()) {
|
|
22708
|
+
delete record[key];
|
|
22709
|
+
continue;
|
|
22545
22710
|
}
|
|
22546
|
-
|
|
22547
|
-
|
|
22548
|
-
|
|
22549
|
-
|
|
22550
|
-
|
|
22551
|
-
record.targets = targets.map(normalizeTarget);
|
|
22552
|
-
return;
|
|
22553
|
-
}
|
|
22554
|
-
if (targets && typeof targets === "object") {
|
|
22555
|
-
record.targets = normalizeTarget(targets, 0);
|
|
22711
|
+
const location = pathValue(record, key);
|
|
22712
|
+
if (first && first.value !== location) {
|
|
22713
|
+
throw new InvalidRequestError(`Conflicting '${prefix}${first.key}' location '${first.value}' and '${prefix}${key}' '${location}': provide the same location or only one target`);
|
|
22714
|
+
}
|
|
22715
|
+
first ??= { key, value: location };
|
|
22556
22716
|
}
|
|
22717
|
+
for (const alias of ["path", "filePath", "url"])
|
|
22718
|
+
delete record[alias];
|
|
22719
|
+
if (first)
|
|
22720
|
+
record.target = first.value;
|
|
22721
|
+
}
|
|
22722
|
+
function normalizeZoomTargets(record) {
|
|
22723
|
+
const normalizeEntry = (value, prefix) => {
|
|
22724
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
22725
|
+
throw new InvalidRequestError(`'${prefix}target' must be a non-empty string`);
|
|
22726
|
+
}
|
|
22727
|
+
const entry = { ...value };
|
|
22728
|
+
normalizeZoomLocation(entry, prefix);
|
|
22729
|
+
if (hasOwn(entry, "symbol")) {
|
|
22730
|
+
if (!isBlankParam(entry.symbols) && JSON.stringify(entry.symbols) !== JSON.stringify(entry.symbol)) {
|
|
22731
|
+
throw new InvalidRequestError(`Conflicting '${prefix}symbols' and '${prefix}symbol': provide the same symbols or only one spelling`);
|
|
22732
|
+
}
|
|
22733
|
+
if (isBlankParam(entry.symbols))
|
|
22734
|
+
entry.symbols = entry.symbol;
|
|
22735
|
+
delete entry.symbol;
|
|
22736
|
+
}
|
|
22737
|
+
entry.target ??= "";
|
|
22738
|
+
entry.symbols ??= "";
|
|
22739
|
+
return entry;
|
|
22740
|
+
};
|
|
22741
|
+
const batch = [];
|
|
22742
|
+
if (hasOwn(record, "batch")) {
|
|
22743
|
+
const entries = coerceJsonCollectionParam(record.batch, "batch");
|
|
22744
|
+
if (!Array.isArray(entries) || entries.length === 0) {
|
|
22745
|
+
throw new InvalidRequestError("'batch' must be a non-empty array of {target, symbols}");
|
|
22746
|
+
}
|
|
22747
|
+
batch.push(...entries.map((entry, index) => normalizeEntry(entry, `batch[${index}].`)));
|
|
22748
|
+
}
|
|
22749
|
+
normalizeZoomLocation(record);
|
|
22750
|
+
if (!isBlankParam(record.target) || !isBlankParam(record.symbols)) {
|
|
22751
|
+
const entry = {};
|
|
22752
|
+
if (record.target !== undefined)
|
|
22753
|
+
entry.target = record.target;
|
|
22754
|
+
if (record.symbols !== undefined)
|
|
22755
|
+
entry.symbols = record.symbols;
|
|
22756
|
+
batch.unshift(normalizeEntry(entry, ""));
|
|
22757
|
+
}
|
|
22758
|
+
if (hasOwn(record, "targets")) {
|
|
22759
|
+
const targets = coerceJsonCollectionParam(record.targets, "targets");
|
|
22760
|
+
batch.push(...usableZoomTargets(targets).map((entry, index) => normalizeEntry(entry, `targets[${index}].`)));
|
|
22761
|
+
}
|
|
22762
|
+
for (const key of ["target", "symbols", "targets"])
|
|
22763
|
+
delete record[key];
|
|
22764
|
+
if (!batch.length)
|
|
22765
|
+
throw new InvalidRequestError("'batch' must be a non-empty array of {target, symbols}");
|
|
22766
|
+
record.batch = batch;
|
|
22557
22767
|
}
|
|
22558
22768
|
function bareToolName(toolName) {
|
|
22559
22769
|
const bare = toolName.startsWith("aft_") ? toolName.slice(4) : toolName;
|
|
@@ -22580,7 +22790,6 @@ function prepareCanonicalPathArguments(toolName, rawArguments) {
|
|
|
22580
22790
|
normalizeAliasPair(record, "path", "filePath", true);
|
|
22581
22791
|
break;
|
|
22582
22792
|
case "zoom":
|
|
22583
|
-
normalizeAliasPair(record, "path", "filePath", false);
|
|
22584
22793
|
normalizeZoomTargets(record);
|
|
22585
22794
|
break;
|
|
22586
22795
|
case "callgraph":
|
|
@@ -23001,6 +23210,19 @@ function copyOwnProperties(source) {
|
|
|
23001
23210
|
copy[key] = source[key];
|
|
23002
23211
|
return copy;
|
|
23003
23212
|
}
|
|
23213
|
+
// ../aft-bridge/dist/revision-reads.js
|
|
23214
|
+
var REVISION_REF_DESCRIPTION = "Git revision to read the file at instead of the working tree: a branch, tag, full or short commit SHA, or a form like HEAD~2. Content comes from git's object store; the working tree, index and HEAD are not touched.";
|
|
23215
|
+
var REVISION_REF_TOOL_NOTE = "Pass `ref` to read the file as it is at a git revision (branch, tag, SHA, HEAD~2); the reply's first line names the commit it resolved to.";
|
|
23216
|
+
function revisionReadsOffered(config) {
|
|
23217
|
+
return config.revision_reads === true;
|
|
23218
|
+
}
|
|
23219
|
+
function revisionRefArg(value) {
|
|
23220
|
+
if (value === undefined || value === null)
|
|
23221
|
+
return;
|
|
23222
|
+
if (typeof value === "string" && value.trim().length === 0)
|
|
23223
|
+
return;
|
|
23224
|
+
return value;
|
|
23225
|
+
}
|
|
23004
23226
|
// ../aft-bridge/dist/semantic-status.js
|
|
23005
23227
|
var MISSING_ONNX_RUNTIME_MARKER = "ONNX Runtime not found.";
|
|
23006
23228
|
var MISSING_ONNX_RUNTIME_LABEL = "unavailable — ONNX Runtime missing (npx @cortexkit/aft doctor --fix)";
|
|
@@ -24142,7 +24364,7 @@ import {
|
|
|
24142
24364
|
// package.json
|
|
24143
24365
|
var package_default = {
|
|
24144
24366
|
name: "@cortexkit/aft-pi",
|
|
24145
|
-
version: "0.
|
|
24367
|
+
version: "0.62.0",
|
|
24146
24368
|
type: "module",
|
|
24147
24369
|
description: "Pi coding agent extension for Agent File Tools (AFT) — tree-sitter and LSP-powered code analysis",
|
|
24148
24370
|
main: "dist/index.js",
|
|
@@ -24166,18 +24388,18 @@ var package_default = {
|
|
|
24166
24388
|
"test:e2e": "bun test src/__tests__/e2e/"
|
|
24167
24389
|
},
|
|
24168
24390
|
dependencies: {
|
|
24169
|
-
"@cortexkit/aft-bridge": "0.
|
|
24391
|
+
"@cortexkit/aft-bridge": "0.62.0",
|
|
24170
24392
|
"comment-json": "^5.0.0",
|
|
24171
24393
|
diff: "^8.0.4",
|
|
24172
24394
|
zod: "^4.4.3"
|
|
24173
24395
|
},
|
|
24174
24396
|
optionalDependencies: {
|
|
24175
|
-
"@cortexkit/aft-darwin-arm64": "0.
|
|
24176
|
-
"@cortexkit/aft-darwin-x64": "0.
|
|
24177
|
-
"@cortexkit/aft-linux-arm64": "0.
|
|
24178
|
-
"@cortexkit/aft-linux-x64": "0.
|
|
24179
|
-
"@cortexkit/aft-win32-arm64": "0.
|
|
24180
|
-
"@cortexkit/aft-win32-x64": "0.
|
|
24397
|
+
"@cortexkit/aft-darwin-arm64": "0.62.0",
|
|
24398
|
+
"@cortexkit/aft-darwin-x64": "0.62.0",
|
|
24399
|
+
"@cortexkit/aft-linux-arm64": "0.62.0",
|
|
24400
|
+
"@cortexkit/aft-linux-x64": "0.62.0",
|
|
24401
|
+
"@cortexkit/aft-win32-arm64": "0.62.0",
|
|
24402
|
+
"@cortexkit/aft-win32-x64": "0.62.0"
|
|
24181
24403
|
},
|
|
24182
24404
|
devDependencies: {
|
|
24183
24405
|
"@earendil-works/pi-coding-agent": "^0.80.2",
|
|
@@ -29280,7 +29502,8 @@ function resolveBashConfig(config) {
|
|
|
29280
29502
|
foreground_wait_window_ms: foregroundWaitWindowMs,
|
|
29281
29503
|
watch_sync_max_ms: watchSyncMaxMs,
|
|
29282
29504
|
worker_wait_max_ms: (typeof top === "object" && top !== null ? top.worker_wait_max_ms : undefined) ?? DEFAULT_WORKER_WAIT_MAX_MS,
|
|
29283
|
-
powershell_tool: typeof top === "object" && top !== null ? top.powershell_tool ?? false : false
|
|
29505
|
+
powershell_tool: typeof top === "object" && top !== null ? top.powershell_tool ?? false : false,
|
|
29506
|
+
env: typeof top === "object" && top !== null ? { ...top.env ?? {} } : {}
|
|
29284
29507
|
};
|
|
29285
29508
|
if (top === false)
|
|
29286
29509
|
return base;
|
|
@@ -29417,6 +29640,41 @@ var ExperimentalConfigSchema = object({
|
|
|
29417
29640
|
}).optional(),
|
|
29418
29641
|
lsp_ty: boolean2().optional()
|
|
29419
29642
|
});
|
|
29643
|
+
var BLOCKED_BASH_ENV_NAMES = new Set([
|
|
29644
|
+
"LD_PRELOAD",
|
|
29645
|
+
"LD_LIBRARY_PATH",
|
|
29646
|
+
"LD_AUDIT",
|
|
29647
|
+
"DYLD_INSERT_LIBRARIES",
|
|
29648
|
+
"DYLD_LIBRARY_PATH",
|
|
29649
|
+
"DYLD_FALLBACK_LIBRARY_PATH",
|
|
29650
|
+
"BASH_ENV",
|
|
29651
|
+
"ENV",
|
|
29652
|
+
"IFS",
|
|
29653
|
+
"PATH",
|
|
29654
|
+
"SHELLOPTS",
|
|
29655
|
+
"BASHOPTS",
|
|
29656
|
+
"PROMPT_COMMAND",
|
|
29657
|
+
"PS4",
|
|
29658
|
+
"PYTHONSTARTUP",
|
|
29659
|
+
"PYTHONPATH",
|
|
29660
|
+
"PYTHONHOME",
|
|
29661
|
+
"PERL5OPT",
|
|
29662
|
+
"PERL5LIB",
|
|
29663
|
+
"PERLLIB",
|
|
29664
|
+
"PERL5DB",
|
|
29665
|
+
"RUBYOPT",
|
|
29666
|
+
"RUBYLIB",
|
|
29667
|
+
"NODE_OPTIONS",
|
|
29668
|
+
"GCONV_PATH",
|
|
29669
|
+
"LOCPATH",
|
|
29670
|
+
"NLSPATH",
|
|
29671
|
+
"HOSTALIASES",
|
|
29672
|
+
"RESOLV_HOST_CONF"
|
|
29673
|
+
]);
|
|
29674
|
+
function isBlockedBashEnvKey(key) {
|
|
29675
|
+
const normalized = process.platform === "win32" ? key.toUpperCase() : key;
|
|
29676
|
+
return BLOCKED_BASH_ENV_NAMES.has(normalized) || normalized.startsWith("LD_") || normalized.startsWith("DYLD_") || normalized.startsWith("BASH_FUNC_");
|
|
29677
|
+
}
|
|
29420
29678
|
var BashFeaturesSchema = object({
|
|
29421
29679
|
enabled: boolean2().optional(),
|
|
29422
29680
|
rewrite: boolean2().optional(),
|
|
@@ -29436,7 +29694,18 @@ var BashFeaturesSchema = object({
|
|
|
29436
29694
|
}).optional(),
|
|
29437
29695
|
linux_scope: boolean2().optional(),
|
|
29438
29696
|
disclaim_privacy: boolean2().optional(),
|
|
29439
|
-
powershell_tool: boolean2().optional()
|
|
29697
|
+
powershell_tool: boolean2().optional(),
|
|
29698
|
+
env: record(string2(), string2()).superRefine((env, context) => {
|
|
29699
|
+
for (const key of Object.keys(env)) {
|
|
29700
|
+
if (isBlockedBashEnvKey(key)) {
|
|
29701
|
+
context.addIssue({
|
|
29702
|
+
code: "custom",
|
|
29703
|
+
path: [key],
|
|
29704
|
+
message: `bash.env key ${key} is blocked because it can alter startup or protected process state`
|
|
29705
|
+
});
|
|
29706
|
+
}
|
|
29707
|
+
}
|
|
29708
|
+
}).optional()
|
|
29440
29709
|
});
|
|
29441
29710
|
var BashConfigSchema = union([boolean2(), BashFeaturesSchema]);
|
|
29442
29711
|
var SandboxConfigSchema = object({
|
|
@@ -29515,6 +29784,7 @@ var AftConfigFieldsSchema = object({
|
|
|
29515
29784
|
$schema: string2().optional(),
|
|
29516
29785
|
edit_mode: _enum(["default", "hashline"]).optional(),
|
|
29517
29786
|
format_on_edit: boolean2().optional(),
|
|
29787
|
+
revision_reads: boolean2().optional(),
|
|
29518
29788
|
formatter_timeout_secs: number2().int().min(1).max(600).optional(),
|
|
29519
29789
|
validate_on_edit: _enum(["syntax", "full"]).optional(),
|
|
29520
29790
|
formatter: record(string2(), FormatterEnum).optional(),
|
|
@@ -29858,6 +30128,7 @@ function getProjectLspStrippedKeys(lsp) {
|
|
|
29858
30128
|
var PROJECT_SAFE_TOP_LEVEL_FIELDS = new Set([
|
|
29859
30129
|
"edit_mode",
|
|
29860
30130
|
"format_on_edit",
|
|
30131
|
+
"revision_reads",
|
|
29861
30132
|
"validate_on_edit",
|
|
29862
30133
|
"configure_warnings_delivery",
|
|
29863
30134
|
"views",
|
|
@@ -29910,6 +30181,8 @@ function getStrippedTopLevelKeys(override, base) {
|
|
|
29910
30181
|
}
|
|
29911
30182
|
if (typeof override.bash === "object" && override.bash.disclaim_privacy === false)
|
|
29912
30183
|
stripped.push("bash.disclaim_privacy");
|
|
30184
|
+
if (typeof override.bash === "object" && override.bash.env !== undefined)
|
|
30185
|
+
stripped.push("bash.env");
|
|
29913
30186
|
if (override.sandbox?.write_allow !== undefined)
|
|
29914
30187
|
stripped.push("sandbox.write_allow");
|
|
29915
30188
|
if (override.subc !== undefined)
|
|
@@ -29957,8 +30230,10 @@ function mergeConfigs(base, override) {
|
|
|
29957
30230
|
const projectBash = typeof override.bash === "object" ? { ...override.bash } : override.bash;
|
|
29958
30231
|
if (typeof projectBash === "object" && projectBash.disclaim_privacy !== true)
|
|
29959
30232
|
delete projectBash.disclaim_privacy;
|
|
29960
|
-
if (typeof projectBash === "object")
|
|
30233
|
+
if (typeof projectBash === "object") {
|
|
29961
30234
|
delete projectBash.runon_enabled;
|
|
30235
|
+
delete projectBash.env;
|
|
30236
|
+
}
|
|
29962
30237
|
const bash = mergeBashConfig(base.bash, projectBash);
|
|
29963
30238
|
const inspect = mergeInspectConfig(base.inspect, override.inspect);
|
|
29964
30239
|
const worktree = mergeWorktreeConfig(base.worktree, override.worktree);
|
|
@@ -30424,6 +30699,10 @@ async function callToolCallForSession(bridge, name, rawArgs, sessionId, extCtx,
|
|
|
30424
30699
|
await forwardPendingBashWaitDetach(pool, bridge, sessionId, name === "bash");
|
|
30425
30700
|
response = await bridge.toolCall(sessionId, name, rawArgs, Object.keys(sendOptions).length > 0 ? sendOptions : undefined);
|
|
30426
30701
|
} catch (error) {
|
|
30702
|
+
const noFallbackOption = name === "bash" ? rawArgs.runon !== undefined ? "runon" : rawArgs.pty === true ? "pty:true" : rawArgs.background === true ? "background:true" : undefined : undefined;
|
|
30703
|
+
if (noFallbackOption && isBashTransportDeadError(error)) {
|
|
30704
|
+
throw refuseAftModuleOptionWithoutFallback(error, noFallbackOption, BASH_HOST_FALLBACK_REFUSAL);
|
|
30705
|
+
}
|
|
30427
30706
|
throw adaptToolError(name, error);
|
|
30428
30707
|
}
|
|
30429
30708
|
ingestBgCompletions(sessionId, response.bg_completions);
|
|
@@ -32605,6 +32884,22 @@ async function assertExternalReadPermission(ctx, extCtx, target, restrictToProje
|
|
|
32605
32884
|
} catch {}
|
|
32606
32885
|
await assertExternalDirectoryPermission(extCtx, absoluteTarget, { restrictToProjectRoot });
|
|
32607
32886
|
}
|
|
32887
|
+
var RevisionRefParam = {
|
|
32888
|
+
ref: Type3.Optional(Type3.String({ description: REVISION_REF_DESCRIPTION }))
|
|
32889
|
+
};
|
|
32890
|
+
function withRevisionRefParam(params, config) {
|
|
32891
|
+
if (!revisionReadsOffered(config))
|
|
32892
|
+
return params;
|
|
32893
|
+
return Type3.Object({ ...params.properties, ...RevisionRefParam });
|
|
32894
|
+
}
|
|
32895
|
+
function withRevisionRefNote(description, config, extra = "") {
|
|
32896
|
+
if (!revisionReadsOffered(config))
|
|
32897
|
+
return description;
|
|
32898
|
+
const note = `${REVISION_REF_TOOL_NOTE}${extra ? ` ${extra}` : ""}`;
|
|
32899
|
+
return `${description}
|
|
32900
|
+
|
|
32901
|
+
${note}`;
|
|
32902
|
+
}
|
|
32608
32903
|
var ReadParams = Type3.Object({
|
|
32609
32904
|
path: Type3.String({
|
|
32610
32905
|
description: "Path to the file or directory to read (absolute or relative to project root)"
|
|
@@ -32753,10 +33048,14 @@ function registerHoistedTools(pi, ctx, surface) {
|
|
|
32753
33048
|
pi.registerTool(withPathAliasPreparation({
|
|
32754
33049
|
name: readName,
|
|
32755
33050
|
label: readName,
|
|
32756
|
-
description
|
|
33051
|
+
get description() {
|
|
33052
|
+
return withRevisionRefNote(readDescription(resolveGithubConfig(ctx.config).read), ctx.config);
|
|
33053
|
+
},
|
|
32757
33054
|
promptSnippet: "Read files or directories (supports offset/limit for either)",
|
|
32758
33055
|
promptGuidelines: [`Use ${readName} to examine files instead of cat or sed.`],
|
|
32759
|
-
parameters
|
|
33056
|
+
get parameters() {
|
|
33057
|
+
return withRevisionRefParam(ReadParams, ctx.config);
|
|
33058
|
+
},
|
|
32760
33059
|
async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
|
|
32761
33060
|
const bridge = bridgeFor(ctx, extCtx.cwd);
|
|
32762
33061
|
const pathArg = readPathArg(params);
|
|
@@ -32778,6 +33077,9 @@ function registerHoistedTools(pi, ctx, surface) {
|
|
|
32778
33077
|
rawArgs.offset = offset;
|
|
32779
33078
|
if (limit !== undefined)
|
|
32780
33079
|
rawArgs.limit = limit;
|
|
33080
|
+
const ref = revisionRefArg(params.ref);
|
|
33081
|
+
if (ref !== undefined)
|
|
33082
|
+
rawArgs.ref = ref;
|
|
32781
33083
|
const visionCapability = visionCapabilityForPiModel(extCtx);
|
|
32782
33084
|
if (visionCapability !== undefined)
|
|
32783
33085
|
rawArgs.vision_capability = visionCapability;
|
|
@@ -33885,17 +34187,22 @@ DO NOT use bash for code search or code exploration. If you are about to run gre
|
|
|
33885
34187
|
}, isPowerShell ? "powershell" : "bash");
|
|
33886
34188
|
} catch (error) {
|
|
33887
34189
|
const fallbackCause = classifyBashHostFallbackError(error);
|
|
33888
|
-
if (
|
|
34190
|
+
if (fallbackCause === undefined)
|
|
33889
34191
|
throw error;
|
|
34192
|
+
if (isPowerShell) {
|
|
34193
|
+
throw refuseAftModuleOptionWithoutFallback(error, "PowerShell", BASH_HOST_FALLBACK_REFUSAL);
|
|
34194
|
+
}
|
|
33890
34195
|
if (!backgroundDisabled && rawRequestedBackground) {
|
|
33891
|
-
throw
|
|
34196
|
+
throw refuseAftModuleOptionWithoutFallback(error, "background:true", BASH_HOST_FALLBACK_REFUSAL);
|
|
33892
34197
|
}
|
|
33893
34198
|
if (requestedPty) {
|
|
33894
|
-
throw
|
|
34199
|
+
throw refuseAftModuleOptionWithoutFallback(error, "pty:true", BASH_HOST_FALLBACK_REFUSAL);
|
|
33895
34200
|
}
|
|
33896
34201
|
if (params.runon !== undefined) {
|
|
33897
|
-
throw
|
|
34202
|
+
throw refuseAftModuleOptionWithoutFallback(error, "runon", BASH_HOST_FALLBACK_REFUSAL);
|
|
33898
34203
|
}
|
|
34204
|
+
if (!bashCfg.host_fallback)
|
|
34205
|
+
throw error;
|
|
33899
34206
|
if (!extCtx.hasUI || typeof extCtx.ui?.confirm !== "function") {
|
|
33900
34207
|
throw new BridgeError("Permission denied: host fallback execution requires an interactive UI.", "permission_denied");
|
|
33901
34208
|
}
|
|
@@ -35418,24 +35725,22 @@ var OutlineParams = Type11.Object({
|
|
|
35418
35725
|
description: "Directory outline only: include test files. Defaults to false; tests are hidden."
|
|
35419
35726
|
}))
|
|
35420
35727
|
});
|
|
35421
|
-
var
|
|
35422
|
-
|
|
35423
|
-
|
|
35728
|
+
var ZoomEntry = Type11.Object({
|
|
35729
|
+
target: Type11.String({
|
|
35730
|
+
description: "File path (absolute or project-relative) or HTTP/HTTPS URL, auto-detected; issue://N or pr://N when GitHub reads are enabled"
|
|
35731
|
+
}),
|
|
35732
|
+
symbols: Type11.Union([Type11.String(), Type11.Array(Type11.String())], {
|
|
35733
|
+
description: "Symbol name for code or heading text for Markdown/HTML; a string or array of strings"
|
|
35734
|
+
})
|
|
35424
35735
|
});
|
|
35425
35736
|
var ZoomParams = Type11.Object({
|
|
35426
|
-
|
|
35427
|
-
|
|
35428
|
-
description: "
|
|
35429
|
-
})
|
|
35430
|
-
|
|
35431
|
-
description: "Symbol name for code, or heading text for Markdown/HTML. Pass a string or array for batched lookups in the same file/URL."
|
|
35432
|
-
})),
|
|
35433
|
-
targets: Type11.Optional(Type11.Union([ZoomTarget, Type11.Array(ZoomTarget)], {
|
|
35434
|
-
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."
|
|
35435
|
-
})),
|
|
35436
|
-
contextLines: optionalInt(1, Number.MAX_SAFE_INTEGER, "Lines of context before/after (default: 3)"),
|
|
35737
|
+
batch: Type11.Array(ZoomEntry, {
|
|
35738
|
+
minItems: 1,
|
|
35739
|
+
description: "One or more lookups; entries may mix file paths and URLs"
|
|
35740
|
+
}),
|
|
35741
|
+
contextLines: optionalInt(1, Number.MAX_SAFE_INTEGER, "Lines of context before/after each symbol (default: 3)"),
|
|
35437
35742
|
callgraph: Type11.Optional(Type11.Boolean({
|
|
35438
|
-
description: "Include call-graph annotations
|
|
35743
|
+
description: "Include call-graph annotations for every entry (default: false)"
|
|
35439
35744
|
}))
|
|
35440
35745
|
});
|
|
35441
35746
|
function isUrl(s) {
|
|
@@ -35453,12 +35758,27 @@ async function assertReadPathPermissions(extCtx, ctx, paths) {
|
|
|
35453
35758
|
}
|
|
35454
35759
|
function zoomTargetLabel(args) {
|
|
35455
35760
|
const safeArgs = asRecordOrEmpty(args);
|
|
35456
|
-
|
|
35761
|
+
if (Array.isArray(safeArgs.batch)) {
|
|
35762
|
+
if (safeArgs.batch.length > 1)
|
|
35763
|
+
return `${safeArgs.batch.length} locations`;
|
|
35764
|
+
return asString2(asRecord3(safeArgs.batch[0])?.target) ?? "(no target)";
|
|
35765
|
+
}
|
|
35766
|
+
return asString2(safeArgs.target) ?? asString2(safeArgs.path) ?? asString2(safeArgs.url) ?? "(no target)";
|
|
35457
35767
|
}
|
|
35458
35768
|
function zoomExtraCallSites(call) {
|
|
35459
35769
|
const extraCount = call.extra_count;
|
|
35460
35770
|
return typeof extraCount === "number" && Number.isInteger(extraCount) && extraCount > 0 ? ` +${extraCount}` : "";
|
|
35461
35771
|
}
|
|
35772
|
+
var ZOOM_CALL_ANNOTATION_LIMIT = 15;
|
|
35773
|
+
function renderZoomAnnotationSection(label, calls, theme) {
|
|
35774
|
+
const rendered = calls.slice(0, ZOOM_CALL_ANNOTATION_LIMIT).map((call) => ` ↳ ${asString2(call.name) ?? "(unknown)"}${typeof call.line === "number" ? `:${call.line}` : ""}${zoomExtraCallSites(call)}`);
|
|
35775
|
+
if (calls.length > ZOOM_CALL_ANNOTATION_LIMIT) {
|
|
35776
|
+
rendered.push(` +${calls.length - ZOOM_CALL_ANNOTATION_LIMIT} more`);
|
|
35777
|
+
}
|
|
35778
|
+
return `${theme.fg("muted", label)}
|
|
35779
|
+
${rendered.join(`
|
|
35780
|
+
`)}`;
|
|
35781
|
+
}
|
|
35462
35782
|
function buildOutlineSections(text, theme) {
|
|
35463
35783
|
const trimmed = text.trim();
|
|
35464
35784
|
if (!trimmed)
|
|
@@ -35488,12 +35808,10 @@ function renderZoomRecord(record, targetLabel, theme) {
|
|
|
35488
35808
|
const callsOut = annotations ? asRecords2(annotations.calls_out) : [];
|
|
35489
35809
|
const calledBy = annotations ? asRecords2(annotations.called_by) : [];
|
|
35490
35810
|
if (callsOut.length > 0) {
|
|
35491
|
-
lines.push(
|
|
35492
|
-
`));
|
|
35811
|
+
lines.push(renderZoomAnnotationSection("calls out", callsOut, theme));
|
|
35493
35812
|
}
|
|
35494
35813
|
if (calledBy.length > 0) {
|
|
35495
|
-
lines.push(
|
|
35496
|
-
`));
|
|
35814
|
+
lines.push(renderZoomAnnotationSection("called_by (this file)", calledBy, theme));
|
|
35497
35815
|
}
|
|
35498
35816
|
return lines.join(`
|
|
35499
35817
|
`);
|
|
@@ -35562,9 +35880,10 @@ function renderOutlineResult(result, theme, context, options = { expanded: true
|
|
|
35562
35880
|
}
|
|
35563
35881
|
function renderZoomCall(args, theme, context) {
|
|
35564
35882
|
const safeArgs = asRecordOrEmpty(args);
|
|
35565
|
-
const
|
|
35566
|
-
const
|
|
35567
|
-
const
|
|
35883
|
+
const entries = Array.isArray(safeArgs.batch) ? safeArgs.batch : [];
|
|
35884
|
+
const hasExplicitTarget = entries.length > 0 || typeof safeArgs.target === "string" || typeof safeArgs.path === "string" || typeof safeArgs.url === "string";
|
|
35885
|
+
const symbols = entries.length === 1 ? asRecord3(entries[0])?.symbols : safeArgs.symbols;
|
|
35886
|
+
const targets = entries.length > 1 ? entries : safeArgs.targets;
|
|
35568
35887
|
let summary;
|
|
35569
35888
|
if (typeof symbols === "string") {
|
|
35570
35889
|
summary = theme.fg("toolOutput", symbols);
|
|
@@ -35608,12 +35927,8 @@ function registerReadingTools(pi, ctx, surface) {
|
|
|
35608
35927
|
const zoomEnabled = toolEnabled(ctx.config, "aft_zoom");
|
|
35609
35928
|
const ghReadEnabled = resolveGithubConfig(ctx.config).read;
|
|
35610
35929
|
const githubOutlineDescription = whenGhReadEnabled(ghReadEnabled, "GitHub issues and pull requests can be outlined with `issue://NUMBER` or `pr://NUMBER` (including `OWNER/REPO` forms).");
|
|
35611
|
-
const githubZoomDescription = whenGhReadEnabled(ghReadEnabled, "
|
|
35612
|
-
|
|
35613
|
-
const outlineTool = withPathAliasPreparation({
|
|
35614
|
-
name: "aft_outline",
|
|
35615
|
-
label: "outline",
|
|
35616
|
-
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.
|
|
35930
|
+
const githubZoomDescription = whenGhReadEnabled(ghReadEnabled, "Batch targets may use `issue://NUMBER` or `pr://NUMBER` (including `OWNER/REPO` forms) to inspect GitHub discussion ordinals.");
|
|
35931
|
+
const outlineDescription = `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.
|
|
35617
35932
|
|
|
35618
35933
|
${outlineFocusSteer(ctx.config)}
|
|
35619
35934
|
|
|
@@ -35625,18 +35940,32 @@ Pass a single \`target\`:
|
|
|
35625
35940
|
|
|
35626
35941
|
${githubOutlineDescription}` : ""}
|
|
35627
35942
|
|
|
35628
|
-
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
|
|
35629
|
-
|
|
35943
|
+
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.`;
|
|
35944
|
+
const zoomDescription = 'Inspect code symbols or documentation sections. Pass lookups in `batch: [{ target, symbols }]`; a single lookup is a one-entry batch. Mix files and URLs in one batch, for example `batch: [{ target: "src/app.ts", symbols: "run" }, { target: "https://example.com/docs", symbols: "Overview" }]`. Each target is auto-detected as a file path or an HTTP/HTTPS URL. `symbols` is a string or string array. Markdown and HTML lookups return the section under the heading. Top-level `contextLines` and `callgraph` apply to every entry; callgraph adds calls-out and called-by annotations. Each failed lookup reports its own error line.' + (githubZoomDescription ? `
|
|
35945
|
+
|
|
35946
|
+
${githubZoomDescription}` : "");
|
|
35947
|
+
if (surface.outline) {
|
|
35948
|
+
const outlineTool = withPathAliasPreparation({
|
|
35949
|
+
name: "aft_outline",
|
|
35950
|
+
label: "outline",
|
|
35951
|
+
get description() {
|
|
35952
|
+
return withRevisionRefNote(outlineDescription, ctx.config, "Only a single file can be outlined at a revision.");
|
|
35953
|
+
},
|
|
35954
|
+
get parameters() {
|
|
35955
|
+
return withRevisionRefParam(OutlineParams, ctx.config);
|
|
35956
|
+
},
|
|
35630
35957
|
async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
|
|
35631
35958
|
const bridge = bridgeFor(ctx, extCtx.cwd);
|
|
35632
35959
|
const target = coerceTargetParam(params.target);
|
|
35633
35960
|
const filesMode = coerceBoolean(params.files);
|
|
35634
35961
|
const hasIncludeTests = !isEmptyParam(params.includeTests);
|
|
35635
35962
|
const includeTests = coerceBoolean(params.includeTests);
|
|
35963
|
+
const ref = revisionRefArg(params.ref);
|
|
35636
35964
|
const rawArgs = {
|
|
35637
35965
|
target,
|
|
35638
35966
|
...filesMode ? { files: true } : {},
|
|
35639
|
-
...hasIncludeTests ? { includeTests } : {}
|
|
35967
|
+
...hasIncludeTests ? { includeTests } : {},
|
|
35968
|
+
...ref !== undefined ? { ref } : {}
|
|
35640
35969
|
};
|
|
35641
35970
|
if (Array.isArray(target)) {
|
|
35642
35971
|
if (target.length === 0) {
|
|
@@ -35682,81 +36011,33 @@ When a list is cut, the reply ends with \`shown N of M <unit> (<reason>) · narr
|
|
|
35682
36011
|
pi.registerTool(withPathAliasPreparation({
|
|
35683
36012
|
name: "aft_zoom",
|
|
35684
36013
|
label: "zoom",
|
|
35685
|
-
description
|
|
35686
|
-
|
|
35687
|
-
|
|
35688
|
-
parameters
|
|
36014
|
+
get description() {
|
|
36015
|
+
return withRevisionRefNote(zoomDescription, ctx.config, "Every file of the call is read at that revision; URLs do not have revisions.");
|
|
36016
|
+
},
|
|
36017
|
+
get parameters() {
|
|
36018
|
+
return withRevisionRefParam(ZoomParams, ctx.config);
|
|
36019
|
+
},
|
|
35689
36020
|
async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
|
|
35690
36021
|
const bridge = bridgeFor(ctx, extCtx.cwd);
|
|
35691
|
-
|
|
35692
|
-
|
|
35693
|
-
const
|
|
35694
|
-
|
|
35695
|
-
|
|
35696
|
-
const
|
|
35697
|
-
const
|
|
36022
|
+
if (!Array.isArray(params.batch) || params.batch.length === 0)
|
|
36023
|
+
throw new Error("'batch' must be a non-empty array of {target, symbols}");
|
|
36024
|
+
const ref = revisionRefArg(params.ref);
|
|
36025
|
+
if (ref !== undefined && params.batch.some((entry) => isUrl(entry.target)))
|
|
36026
|
+
throw new Error("'ref' cannot be used with a URL target: a URL has no revision");
|
|
36027
|
+
const paths = params.batch.map((entry) => entry.target).filter((target) => target && !isGithubPathArg(target) && !isUrl(target));
|
|
36028
|
+
const resolved = await Promise.all(paths.map((target) => resolvePathArg(extCtx.cwd, target)));
|
|
36029
|
+
await assertReadPathPermissions(extCtx, ctx, resolved);
|
|
36030
|
+
const rawArgs = { batch: params.batch };
|
|
35698
36031
|
const contextLines = coerceOptionalInt(params.contextLines, "contextLines", 1, Number.MAX_SAFE_INTEGER);
|
|
35699
|
-
if (hasTargets) {
|
|
35700
|
-
if (hasPath && hasUrl) {
|
|
35701
|
-
throw new Error("Provide exactly ONE of 'path' or 'url' — not both");
|
|
35702
|
-
}
|
|
35703
|
-
const targets = targetEntries.map((entry, i) => {
|
|
35704
|
-
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
35705
|
-
throw new Error(`targets[${i}].path must be a non-empty string`);
|
|
35706
|
-
}
|
|
35707
|
-
const record = entry;
|
|
35708
|
-
const targetPath = isBlankParam(record.path) ? record.filePath : record.path;
|
|
35709
|
-
return {
|
|
35710
|
-
path: typeof targetPath === "string" ? targetPath : "",
|
|
35711
|
-
symbol: typeof record.symbol === "string" ? record.symbol : ""
|
|
35712
|
-
};
|
|
35713
|
-
});
|
|
35714
|
-
const localPaths = targets.map((target) => target.path).filter((path) => path.trim().length > 0 && !isGithubPathArg(path));
|
|
35715
|
-
if (hasPath && !isGithubPathArg(params.path))
|
|
35716
|
-
localPaths.push(params.path);
|
|
35717
|
-
const resolvedTargets = await Promise.all(localPaths.map((path) => resolvePathArg(extCtx.cwd, path)));
|
|
35718
|
-
await assertReadPathPermissions(extCtx, ctx, resolvedTargets);
|
|
35719
|
-
const rawArgs = {
|
|
35720
|
-
targets: targets.map((target) => ({ filePath: target.path, symbol: target.symbol }))
|
|
35721
|
-
};
|
|
35722
|
-
if (hasPath)
|
|
35723
|
-
rawArgs.filePath = params.path;
|
|
35724
|
-
else if (hasUrl)
|
|
35725
|
-
rawArgs.url = params.url;
|
|
35726
|
-
if (hasSymbols)
|
|
35727
|
-
rawArgs.symbols = params.symbols;
|
|
35728
|
-
if (contextLines !== undefined)
|
|
35729
|
-
rawArgs.contextLines = contextLines;
|
|
35730
|
-
if (wantCallgraph)
|
|
35731
|
-
rawArgs.callgraph = true;
|
|
35732
|
-
const response = await callToolCall(bridge, "zoom", rawArgs, extCtx);
|
|
35733
|
-
if (response.success === false) {
|
|
35734
|
-
throw new Error(response.text || response.message || "zoom failed");
|
|
35735
|
-
}
|
|
35736
|
-
return textResult(response.text, response);
|
|
35737
|
-
}
|
|
35738
|
-
if (!hasPath && !hasUrl) {
|
|
35739
|
-
throw new Error("Provide exactly one of 'path', 'url', or 'targets'");
|
|
35740
|
-
}
|
|
35741
|
-
if (hasPath && hasUrl) {
|
|
35742
|
-
throw new Error("Provide exactly ONE of 'path' or 'url' — not both");
|
|
35743
|
-
}
|
|
35744
|
-
const githubPath = hasPath && isGithubPathArg(params.path) || hasUrl && isGithubPathArg(params.url);
|
|
35745
|
-
if (!hasUrl && !githubPath) {
|
|
35746
|
-
const file = await resolvePathArg(extCtx.cwd, params.path);
|
|
35747
|
-
await assertReadPathPermissions(extCtx, ctx, file);
|
|
35748
|
-
}
|
|
35749
|
-
const rawArgs = hasUrl ? { url: params.url } : { filePath: params.path };
|
|
35750
|
-
if (hasSymbols)
|
|
35751
|
-
rawArgs.symbols = params.symbols;
|
|
35752
36032
|
if (contextLines !== undefined)
|
|
35753
36033
|
rawArgs.contextLines = contextLines;
|
|
35754
|
-
if (
|
|
36034
|
+
if (coerceBoolean(params.callgraph))
|
|
35755
36035
|
rawArgs.callgraph = true;
|
|
36036
|
+
if (ref !== undefined)
|
|
36037
|
+
rawArgs.ref = ref;
|
|
35756
36038
|
const response = await callToolCall(bridge, "zoom", rawArgs, extCtx);
|
|
35757
|
-
if (response.success === false)
|
|
36039
|
+
if (response.success === false)
|
|
35758
36040
|
throw new Error(response.text || response.message || "zoom failed");
|
|
35759
|
-
}
|
|
35760
36041
|
return textResult(response.text, response);
|
|
35761
36042
|
},
|
|
35762
36043
|
renderCall(args, theme, context) {
|
|
@@ -35776,11 +36057,11 @@ function responsePaths(response) {
|
|
|
35776
36057
|
return Array.isArray(response.paths) ? response.paths.filter((path) => typeof path === "string" && path.length > 0) : [];
|
|
35777
36058
|
}
|
|
35778
36059
|
var SafetyParams = Type12.Object({
|
|
35779
|
-
op: StringEnum4(["undo", "history", "checkpoint", "restore", "list"], {
|
|
36060
|
+
op: StringEnum4(["undo", "history", "checkpoint", "restore", "list", "purge"], {
|
|
35780
36061
|
description: "Safety operation"
|
|
35781
36062
|
}),
|
|
35782
36063
|
path: Type12.Optional(Type12.String({
|
|
35783
|
-
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."
|
|
36064
|
+
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, required for purge — a file or directory). Absolute or relative to project root."
|
|
35784
36065
|
})),
|
|
35785
36066
|
name: Type12.Optional(Type12.String({ description: "Checkpoint name (required for checkpoint, restore)" })),
|
|
35786
36067
|
files: Type12.Optional(Type12.Array(Type12.String(), {
|
|
@@ -35866,6 +36147,21 @@ ${skipped.map((entry) => ` ↳ ${shortenHomePath(asString2(entry.file) ?? "(fil
|
|
|
35866
36147
|
sections.push(theme.fg("muted", durability));
|
|
35867
36148
|
return sections;
|
|
35868
36149
|
}
|
|
36150
|
+
if (op === "purge") {
|
|
36151
|
+
const pruned = asRecords2(response.checkpoints_pruned).map((entry) => asString2(entry.name)).filter((name) => Boolean(name));
|
|
36152
|
+
const dropped = Array.isArray(response.checkpoints_dropped) ? response.checkpoints_dropped.filter((name) => typeof name === "string") : [];
|
|
36153
|
+
const failures = asRecords2(response.failures);
|
|
36154
|
+
return [
|
|
36155
|
+
`${theme.fg("success", "purged")} ${theme.fg("accent", shortenHomePath(asString2(response.path) ?? path ?? "(path)"))}`,
|
|
36156
|
+
`${theme.fg("muted", "undo backups removed")} ${asNumber2(response.undo_entries_removed) ?? 0}`,
|
|
36157
|
+
pruned.length > 0 ? `${theme.fg("muted", "checkpoints pruned")} ${pruned.join(", ")}` : "",
|
|
36158
|
+
dropped.length > 0 ? `${theme.fg("muted", "checkpoints dropped")} ${dropped.join(", ")}` : "",
|
|
36159
|
+
failures.length > 0 ? `${theme.fg("warning", "not removed")}
|
|
36160
|
+
${failures.map((entry) => ` ↳ ${asString2(entry.target) ?? "(unknown)"}: ${asString2(entry.error) ?? "unknown error"}`).join(`
|
|
36161
|
+
`)}` : "",
|
|
36162
|
+
theme.fg("warning", "This cannot be undone.")
|
|
36163
|
+
].filter((section) => section.length > 0);
|
|
36164
|
+
}
|
|
35869
36165
|
const knownDetails = [path, name].filter((value) => Boolean(value));
|
|
35870
36166
|
return [theme.fg("muted", "Safety result available."), ...knownDetails];
|
|
35871
36167
|
}
|
|
@@ -35895,10 +36191,10 @@ function registerSafetyTool(pi, ctx) {
|
|
|
35895
36191
|
pi.registerTool(withPathAliasPreparation({
|
|
35896
36192
|
name: "aft_safety",
|
|
35897
36193
|
label: "safety",
|
|
35898
|
-
description: "
|
|
36194
|
+
description: "`undo` reverts a tool call (omit path) or latest file snapshot (irreversible); `history` lists snapshots; `checkpoint` saves named snapshots on disk across restarts, expiring after 14 days (contents may include secrets); `restore` restores a checkpoint, optionally scoped by path/files; `list` lists checkpoints; `purge` permanently deletes this session's undo backups and checkpoint copies of a file or directory `path` (cannot be undone). Undo keeps 20 snapshots/file (oldest evicted).",
|
|
35899
36195
|
parameters: SafetyParams,
|
|
35900
36196
|
async execute(_toolCallId, params, _signal, _onUpdate, extCtx) {
|
|
35901
|
-
if (params.op === "history" && !params.path) {
|
|
36197
|
+
if ((params.op === "history" || params.op === "purge") && !params.path) {
|
|
35902
36198
|
throw new Error(`op='${params.op}' requires 'path'`);
|
|
35903
36199
|
}
|
|
35904
36200
|
if ((params.op === "checkpoint" || params.op === "restore") && !params.name) {
|
|
@@ -35942,6 +36238,16 @@ function registerSafetyTool(pi, ctx) {
|
|
|
35942
36238
|
});
|
|
35943
36239
|
}
|
|
35944
36240
|
}
|
|
36241
|
+
if (params.op === "purge" && params.path) {
|
|
36242
|
+
const purgeTarget = await resolvePathArg(extCtx.cwd, params.path);
|
|
36243
|
+
await assertExternalDirectoryPermission(extCtx, purgeTarget, { restrictToProjectRoot });
|
|
36244
|
+
const preview = await callBridge(bridge, "safety_purge_paths", { file: purgeTarget }, extCtx);
|
|
36245
|
+
for (const file of new Set(responsePaths(preview))) {
|
|
36246
|
+
await assertExternalDirectoryPermission(extCtx, file, {
|
|
36247
|
+
restrictToProjectRoot
|
|
36248
|
+
});
|
|
36249
|
+
}
|
|
36250
|
+
}
|
|
35945
36251
|
const rawArgs = { op: params.op };
|
|
35946
36252
|
if (filePath)
|
|
35947
36253
|
rawArgs.filePath = filePath;
|
|
@@ -36043,7 +36349,7 @@ var SearchParams2 = Type13.Object({
|
|
|
36043
36349
|
description: "Include test files (*.test.*, *_test.rs, __tests__/, …) plus test-support, fixture, mock, snapshot, and corpus files. Defaults to false."
|
|
36044
36350
|
})),
|
|
36045
36351
|
path: Type13.Optional(Type13.String({
|
|
36046
|
-
description: "Only set this to search a different
|
|
36352
|
+
description: "Only set this to search a different project or plain folder (absolute or ~ path). Omit it for the current configured workspace, including non-Git workspace roots; this is not a subdirectory filter. Unindexed external folders use a bounded lexical fallback; filesystem roots, home and its ancestors are refused."
|
|
36047
36353
|
}))
|
|
36048
36354
|
});
|
|
36049
36355
|
function buildSemanticSections(args, payload, theme) {
|
|
@@ -36335,6 +36641,17 @@ function registerPiToolSurface(pi, ctx, loadedSurface, harness) {
|
|
|
36335
36641
|
if (surface.delete || surface.move)
|
|
36336
36642
|
registerFsTools(boundPi, ctx, surface);
|
|
36337
36643
|
}
|
|
36644
|
+
function refreshPiRevisionReadTools(pi, ctx, loadedSurface) {
|
|
36645
|
+
const surface = withLiveRestriction({
|
|
36646
|
+
...loadedSurface,
|
|
36647
|
+
hoistWrite: false,
|
|
36648
|
+
hoistEdit: false,
|
|
36649
|
+
hoistGrep: false
|
|
36650
|
+
}, ctx);
|
|
36651
|
+
const boundPi = bindToolRegistrationFunnel(pi, ctx);
|
|
36652
|
+
registerHoistedTools(boundPi, ctx, surface);
|
|
36653
|
+
registerReadingTools(boundPi, ctx, surface);
|
|
36654
|
+
}
|
|
36338
36655
|
|
|
36339
36656
|
// src/config-error-state.ts
|
|
36340
36657
|
var CONFIG_ERROR_STATUS_KEY = "aft";
|
|
@@ -36459,7 +36776,12 @@ function startPiLiveConfigReload(options) {
|
|
|
36459
36776
|
securityKeys: aftLiveSecurityKeys(PI_LIVE_CONFIG_KEYS),
|
|
36460
36777
|
keys: PI_LIVE_CONFIG_KEYS,
|
|
36461
36778
|
getConfig: options.getConfig,
|
|
36462
|
-
setConfig:
|
|
36779
|
+
setConfig: (next) => {
|
|
36780
|
+
const revisionReadsChanged = options.getConfig().revision_reads !== next.revision_reads;
|
|
36781
|
+
options.setConfig(next);
|
|
36782
|
+
if (revisionReadsChanged)
|
|
36783
|
+
options.refreshRevisionReadTools?.();
|
|
36784
|
+
},
|
|
36463
36785
|
log: (message) => log2(`${message} (${options.directory})`),
|
|
36464
36786
|
reportError: (message) => {
|
|
36465
36787
|
error2(message);
|
|
@@ -36582,7 +36904,7 @@ function buildWorkflowHints(opts) {
|
|
|
36582
36904
|
`));
|
|
36583
36905
|
}
|
|
36584
36906
|
if (hasOutline && hasZoom) {
|
|
36585
|
-
sections.push(`**Web/URL access**: \`aft_outline({ target: "<url>" })\` first for structure, then \`aft_zoom({
|
|
36907
|
+
sections.push(`**Web/URL access**: \`aft_outline({ target: "<url>" })\` first for structure, then \`aft_zoom({ batch: [{ target: "<url>", symbols: "<heading>" }] })\` for the specific section.`);
|
|
36586
36908
|
}
|
|
36587
36909
|
if (hasOutline && (hasGrep || hasSearch) && (hasZoom || hasRead)) {
|
|
36588
36910
|
const searchName = hasSearch ? "aft_search" : grepName;
|
|
@@ -36748,12 +37070,12 @@ var PLUGIN_VERSION = (() => {
|
|
|
36748
37070
|
return "0.0.0";
|
|
36749
37071
|
}
|
|
36750
37072
|
})();
|
|
36751
|
-
var ANNOUNCEMENT_VERSION = "0.
|
|
37073
|
+
var ANNOUNCEMENT_VERSION = "0.62.0";
|
|
36752
37074
|
var ANNOUNCEMENT_FEATURES = [
|
|
36753
|
-
"
|
|
36754
|
-
"
|
|
36755
|
-
"
|
|
36756
|
-
"
|
|
37075
|
+
"Linux dev containers work again: a project at a bind-mount target such as `/workspace` is no longer refused as too broad.",
|
|
37076
|
+
"Read a file as it is at any git revision with the opt-in `ref` argument on `read`, `aft_outline` and `aft_zoom` (set `revision_reads: true`).",
|
|
37077
|
+
"`aft_safety` has a `purge` op that permanently removes a file from this session's undo backups and checkpoints, such as a secret written by mistake.",
|
|
37078
|
+
"`aft_zoom` takes one `batch` of lookups mixing files and URLs, and many reported issues are fixed."
|
|
36757
37079
|
];
|
|
36758
37080
|
var ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/DSa65w8wuf";
|
|
36759
37081
|
var pendingEagerWarnings = new Map;
|
|
@@ -37025,7 +37347,8 @@ async function src_default(pi) {
|
|
|
37025
37347
|
registerBashTool(pi, ctx, surface.semantic, "bash", false, "bash");
|
|
37026
37348
|
}
|
|
37027
37349
|
},
|
|
37028
|
-
notify: (message) => deliverConfigMigrationWarnings([message])
|
|
37350
|
+
notify: (message) => deliverConfigMigrationWarnings([message]),
|
|
37351
|
+
refreshRevisionReadTools: () => refreshPiRevisionReadTools(pi, ctx, surface)
|
|
37029
37352
|
});
|
|
37030
37353
|
const startLspDiscovery = () => {
|
|
37031
37354
|
try {
|