@cortexkit/aft-opencode 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/bg-notifications.d.ts +7 -1
- package/dist/bg-notifications.d.ts.map +1 -1
- package/dist/config-live-reload.d.ts +4 -0
- package/dist/config-live-reload.d.ts.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/entry/server.js +971 -357
- package/dist/index.js +789 -326
- package/dist/subc-tool-schemas.d.ts +13 -2
- package/dist/subc-tool-schemas.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/definitions/v2.d.ts.map +1 -1
- package/dist/tools/hoisted.d.ts +8 -1
- package/dist/tools/hoisted.d.ts.map +1 -1
- package/dist/tools/reading.d.ts.map +1 -1
- package/dist/tools/safety.d.ts +1 -1
- package/dist/tools/safety.d.ts.map +1 -1
- package/dist/v2-tool-hooks.d.ts +1 -0
- package/dist/v2-tool-hooks.d.ts.map +1 -1
- package/dist/wakes/runtime-consumer.d.ts +6 -1
- package/dist/wakes/runtime-consumer.d.ts.map +1 -1
- package/dist/wakes/session-delivery.d.ts +2 -0
- package/dist/wakes/session-delivery.d.ts.map +1 -1
- package/dist/wakes/subc-recovery.d.ts +8 -0
- package/dist/wakes/subc-recovery.d.ts.map +1 -0
- package/package.json +10 -10
- package/src/entry/server-runtime.mjs +14 -1
package/dist/entry/server.js
CHANGED
|
@@ -9402,7 +9402,7 @@ function resolvePluginLogPath(context = {}) {
|
|
|
9402
9402
|
var BASH_HOST_FALLBACK_BANNER = "[AFT host fallback - module transport down; no rewrites/compression/background]";
|
|
9403
9403
|
var BASH_HOST_FALLBACK_MAX_OUTPUT_BYTES = 100 * 1024;
|
|
9404
9404
|
var BASH_HOST_FALLBACK_MAX_TIMEOUT_MS = 10 * 60 * 1000;
|
|
9405
|
-
var BASH_HOST_FALLBACK_REFUSAL = "AFT
|
|
9405
|
+
var BASH_HOST_FALLBACK_REFUSAL = "AFT module connection is down";
|
|
9406
9406
|
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).';
|
|
9407
9407
|
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.';
|
|
9408
9408
|
function pathKeyForPlatform(env, platform) {
|
|
@@ -11226,7 +11226,7 @@ function isEmptyParam(value) {
|
|
|
11226
11226
|
return false;
|
|
11227
11227
|
}
|
|
11228
11228
|
function coerceJsonCollectionParam(value, field) {
|
|
11229
|
-
const shape = field
|
|
11229
|
+
const shape = field !== "sections" ? "an array of {target, symbols}" : "a string or array of strings";
|
|
11230
11230
|
if (typeof value === "string") {
|
|
11231
11231
|
const trimmed = value.trim();
|
|
11232
11232
|
if (trimmed.startsWith("[") || trimmed.startsWith("{")) {
|
|
@@ -11235,13 +11235,13 @@ function coerceJsonCollectionParam(value, field) {
|
|
|
11235
11235
|
} catch {
|
|
11236
11236
|
throw new Error(`${field} must be ${shape} (got a string that is not valid JSON)`);
|
|
11237
11237
|
}
|
|
11238
|
-
} else if (field
|
|
11238
|
+
} else if (field !== "sections") {
|
|
11239
11239
|
throw new Error(`${field} must be ${shape} (got a string that is not valid JSON)`);
|
|
11240
11240
|
}
|
|
11241
11241
|
}
|
|
11242
11242
|
if (value === undefined || value === null)
|
|
11243
11243
|
return value;
|
|
11244
|
-
if (field
|
|
11244
|
+
if (field !== "sections") {
|
|
11245
11245
|
if (Array.isArray(value) || typeof value === "object")
|
|
11246
11246
|
return value;
|
|
11247
11247
|
} else if (typeof value === "string" || Array.isArray(value) && value.every((entry) => typeof entry === "string")) {
|
|
@@ -11264,7 +11264,7 @@ function usableZoomTargets(targets) {
|
|
|
11264
11264
|
if (typeof entry !== "object")
|
|
11265
11265
|
return true;
|
|
11266
11266
|
const record = entry;
|
|
11267
|
-
return !(isBlankParam(record.path) && isBlankParam(record.filePath) && isBlankParam(record.symbol));
|
|
11267
|
+
return !(isBlankParam(record.target) && isBlankParam(record.url) && isBlankParam(record.symbols) && isBlankParam(record.path) && isBlankParam(record.filePath) && isBlankParam(record.symbol));
|
|
11268
11268
|
});
|
|
11269
11269
|
}
|
|
11270
11270
|
function isFindReplaceOnlyEdit(args) {
|
|
@@ -11448,6 +11448,7 @@ function aftLiveConfigKeys(resolveBash) {
|
|
|
11448
11448
|
return [
|
|
11449
11449
|
pathKey("configure_warnings_delivery"),
|
|
11450
11450
|
pathKey("restrict_to_project_root"),
|
|
11451
|
+
pathKey("revision_reads"),
|
|
11451
11452
|
pathKey("inspect.diagnostics_timeout_ms"),
|
|
11452
11453
|
pathKey("inspect.tier2_idle_minutes"),
|
|
11453
11454
|
pathKey("lsp.idle_minutes"),
|
|
@@ -11465,7 +11466,8 @@ function aftLiveConfigKeys(resolveBash) {
|
|
|
11465
11466
|
bashKey("runon_enabled", resolveBash),
|
|
11466
11467
|
bashKey("subagent_background", resolveBash),
|
|
11467
11468
|
bashKey("watch_sync_max_ms", resolveBash),
|
|
11468
|
-
bashKey("worker_wait_max_ms", resolveBash)
|
|
11469
|
+
bashKey("worker_wait_max_ms", resolveBash),
|
|
11470
|
+
bashKey("env", resolveBash)
|
|
11469
11471
|
];
|
|
11470
11472
|
}
|
|
11471
11473
|
function sameValue(a, b) {
|
|
@@ -15342,6 +15344,47 @@ function isSocketDeathError(error, depth = 0) {
|
|
|
15342
15344
|
}
|
|
15343
15345
|
return isConsumerReconnectTransient(error) || isSubcClientClosedError(error);
|
|
15344
15346
|
}
|
|
15347
|
+
var REQUEST_CONNECTION_LOSS_CODES = new Set(["ECONNABORTED", "ECONNRESET", "EPIPE"]);
|
|
15348
|
+
function hasRequestConnectionLossCode(error) {
|
|
15349
|
+
if (typeof error !== "object" || error === null || !("code" in error))
|
|
15350
|
+
return false;
|
|
15351
|
+
return typeof error.code === "string" && REQUEST_CONNECTION_LOSS_CODES.has(error.code);
|
|
15352
|
+
}
|
|
15353
|
+
function hasRequestConnectionLossEvidence(error, depth = 0) {
|
|
15354
|
+
if (error instanceof SocketClosedError)
|
|
15355
|
+
return true;
|
|
15356
|
+
if (error instanceof SubcError && error.closeReason === "connection_lost")
|
|
15357
|
+
return true;
|
|
15358
|
+
if (hasRequestConnectionLossCode(error))
|
|
15359
|
+
return true;
|
|
15360
|
+
if (!(error instanceof SubcCallError) || error.kind !== "outcome_unknown" || depth >= 4 || error.cause === undefined || error.cause === error) {
|
|
15361
|
+
return false;
|
|
15362
|
+
}
|
|
15363
|
+
return hasRequestConnectionLossEvidence(error.cause, depth + 1);
|
|
15364
|
+
}
|
|
15365
|
+
function connectionClosedAfterDispatch(error) {
|
|
15366
|
+
if (error instanceof SubcCallError && error.kind === "not_sent")
|
|
15367
|
+
return error;
|
|
15368
|
+
if (typeof error === "object" && error !== null && "kind" in error && error.kind === "not_sent") {
|
|
15369
|
+
return error;
|
|
15370
|
+
}
|
|
15371
|
+
if (!hasRequestConnectionLossEvidence(error))
|
|
15372
|
+
return error;
|
|
15373
|
+
if (error instanceof SubcCallError)
|
|
15374
|
+
return error;
|
|
15375
|
+
if (!(error instanceof Error))
|
|
15376
|
+
return error;
|
|
15377
|
+
return Object.assign(error, { kind: "outcome_unknown" });
|
|
15378
|
+
}
|
|
15379
|
+
var dispatchedRequestDeadlines = new WeakSet;
|
|
15380
|
+
function noteDispatchedRequestDeadline(error) {
|
|
15381
|
+
if (error instanceof SubcError && error.code === REQUEST_DEADLINE_CODE) {
|
|
15382
|
+
dispatchedRequestDeadlines.add(error);
|
|
15383
|
+
}
|
|
15384
|
+
}
|
|
15385
|
+
function isDispatchedRequestDeadlineError(error) {
|
|
15386
|
+
return typeof error === "object" && error !== null && dispatchedRequestDeadlines.has(error);
|
|
15387
|
+
}
|
|
15345
15388
|
function isUnansweredCallError(error) {
|
|
15346
15389
|
if (isSocketDeathError(error))
|
|
15347
15390
|
return false;
|
|
@@ -15948,7 +15991,24 @@ class SubcTransport {
|
|
|
15948
15991
|
return {};
|
|
15949
15992
|
const preview = options.preview;
|
|
15950
15993
|
const timeoutMs = options.transportTimeoutMs ?? options.timeoutMs;
|
|
15951
|
-
const
|
|
15994
|
+
const callback = options.onProgress;
|
|
15995
|
+
const onProgress = callback ? (body) => {
|
|
15996
|
+
let frame;
|
|
15997
|
+
try {
|
|
15998
|
+
frame = JSON.parse(new TextDecoder().decode(body));
|
|
15999
|
+
} catch {
|
|
16000
|
+
return;
|
|
16001
|
+
}
|
|
16002
|
+
if (typeof frame !== "object" || frame === null)
|
|
16003
|
+
return;
|
|
16004
|
+
const progress = frame;
|
|
16005
|
+
if (progress.type !== "progress" || typeof progress.chunk !== "string")
|
|
16006
|
+
return;
|
|
16007
|
+
callback({
|
|
16008
|
+
kind: progress.kind === "stderr" ? "stderr" : "stdout",
|
|
16009
|
+
text: progress.chunk
|
|
16010
|
+
});
|
|
16011
|
+
} : undefined;
|
|
15952
16012
|
return { preview, timeoutMs, onProgress, abortSignal: options.abortSignal };
|
|
15953
16013
|
}
|
|
15954
16014
|
}
|
|
@@ -15960,7 +16020,7 @@ class SubcTransportPool {
|
|
|
15960
16020
|
consumerIdentity;
|
|
15961
16021
|
hostScopes;
|
|
15962
16022
|
hostScopeCache = new Map;
|
|
15963
|
-
hostScopeRefusals = new
|
|
16023
|
+
hostScopeRefusals = new Map;
|
|
15964
16024
|
hostScopeOutcomes = new Map;
|
|
15965
16025
|
connectFn;
|
|
15966
16026
|
onBgEventsNudge;
|
|
@@ -16847,7 +16907,7 @@ class SubcTransportPool {
|
|
|
16847
16907
|
this.resetRouteReopenBackoff();
|
|
16848
16908
|
return reply;
|
|
16849
16909
|
} catch (requestError) {
|
|
16850
|
-
const error = frameReadTimeoutError(routeClosedMidCallError(requestError, routeAndEntry.entry, record));
|
|
16910
|
+
const error = frameReadTimeoutError(routeClosedMidCallError(connectionClosedAfterDispatch(requestError), routeAndEntry.entry, record));
|
|
16851
16911
|
if (this.isReapInduced(record))
|
|
16852
16912
|
throw this.annotateReapError(error, record);
|
|
16853
16913
|
const ownsRoute = this.isCurrentSession(key, record) && this.client === client;
|
|
@@ -16881,6 +16941,7 @@ class SubcTransportPool {
|
|
|
16881
16941
|
continue;
|
|
16882
16942
|
}
|
|
16883
16943
|
handleRequestFailure(error, routeAndEntry.entry);
|
|
16944
|
+
noteDispatchedRequestDeadline(error);
|
|
16884
16945
|
throw error;
|
|
16885
16946
|
}
|
|
16886
16947
|
}
|
|
@@ -17014,20 +17075,38 @@ class SubcTransportPool {
|
|
|
17014
17075
|
return { route: await opening, entry };
|
|
17015
17076
|
}
|
|
17016
17077
|
hostScope(client, identity) {
|
|
17017
|
-
|
|
17018
|
-
|
|
17078
|
+
let lookupReason = "cache_miss";
|
|
17079
|
+
const refused = this.hostScopeRefusals.get(identity.session);
|
|
17080
|
+
if (refused) {
|
|
17081
|
+
const previous = this.hostScopeCache.get(identity.session);
|
|
17082
|
+
if (previous)
|
|
17083
|
+
this.cancelHostScopeRetry(previous);
|
|
17084
|
+
this.hostScopeCache.delete(identity.session);
|
|
17085
|
+
this.hostScopeRefusals.delete(identity.session);
|
|
17086
|
+
lookupReason = `previous_bind_refusal code=${refused}`;
|
|
17087
|
+
}
|
|
17019
17088
|
let state = this.hostScopeCache.get(identity.session);
|
|
17020
17089
|
if (!state) {
|
|
17021
17090
|
state = { retryAt: 0, retryMs: HOST_SCOPE_RETRY_FLOOR_MS };
|
|
17022
17091
|
this.hostScopeCache.set(identity.session, state);
|
|
17023
17092
|
}
|
|
17024
17093
|
if (state.answer) {
|
|
17025
|
-
|
|
17094
|
+
const reason = state.scopeBindPending ? "scope_sync_pending" : `cached_${state.answer.result}`;
|
|
17095
|
+
log(`subc head scope: session=${identity.session} lookup=skipped reason=${reason}`);
|
|
17096
|
+
return Promise.resolve({
|
|
17097
|
+
scope: state.answer.result === "scope" && !state.scopeBindPending ? state.answer.scope : undefined,
|
|
17098
|
+
reason
|
|
17099
|
+
});
|
|
17026
17100
|
}
|
|
17027
|
-
if (state.pending)
|
|
17101
|
+
if (state.pending) {
|
|
17102
|
+
log(`subc head scope: session=${identity.session} lookup=skipped reason=inflight_lookup`);
|
|
17028
17103
|
return state.pending;
|
|
17029
|
-
|
|
17030
|
-
|
|
17104
|
+
}
|
|
17105
|
+
if (Date.now() < state.retryAt) {
|
|
17106
|
+
log(`subc head scope: session=${identity.session} lookup=skipped reason=backoff retry_in=${state.retryAt - Date.now()}ms`);
|
|
17107
|
+
return Promise.resolve({ reason: state.unscopedReason ?? "lookup_backoff" });
|
|
17108
|
+
}
|
|
17109
|
+
log(`subc head scope: session=${identity.session} lookup=started reason=${lookupReason}`);
|
|
17031
17110
|
const current = state;
|
|
17032
17111
|
clearTimeout(current.retryTimer);
|
|
17033
17112
|
current.retryTimer = undefined;
|
|
@@ -17035,24 +17114,32 @@ class SubcTransportPool {
|
|
|
17035
17114
|
current.lookupAbort = abort;
|
|
17036
17115
|
const lookup = this.readHostScope(client, identity, abort.signal).then((outcome) => {
|
|
17037
17116
|
if (this.hostScopeCache.get(identity.session) !== current)
|
|
17038
|
-
return;
|
|
17117
|
+
return { reason: "lookup_invalidated" };
|
|
17039
17118
|
current.pending = undefined;
|
|
17040
17119
|
current.lookupAbort = undefined;
|
|
17041
17120
|
if (abort.signal.aborted)
|
|
17042
|
-
return;
|
|
17121
|
+
return { reason: "lookup_cancelled" };
|
|
17043
17122
|
this.logHostScopeOutcome(identity.session, outcome);
|
|
17123
|
+
const reason = outcome.result === "error" ? `lookup_error code=${outcome.code}` : `lookup_${outcome.result}`;
|
|
17044
17124
|
if (outcome.result === "scope" || outcome.result === "no_scope") {
|
|
17045
17125
|
current.answer = outcome;
|
|
17046
17126
|
current.retryAt = 0;
|
|
17047
17127
|
current.retryMs = HOST_SCOPE_RETRY_FLOOR_MS;
|
|
17048
|
-
if (outcome.result === "scope")
|
|
17049
|
-
|
|
17128
|
+
if (outcome.result === "scope") {
|
|
17129
|
+
current.promoteAfterBind = Boolean(refused);
|
|
17130
|
+
if (!current.promoteAfterBind)
|
|
17131
|
+
this.promoteHostScope(identity.session);
|
|
17132
|
+
}
|
|
17050
17133
|
} else {
|
|
17134
|
+
current.unscopedReason = reason;
|
|
17051
17135
|
current.retryAt = Date.now() + current.retryMs;
|
|
17052
17136
|
current.retryMs = Math.min(current.retryMs * 2, HOST_SCOPE_RETRY_CAP_MS);
|
|
17053
17137
|
this.scheduleHostScopeRetry(identity.session, current);
|
|
17054
17138
|
}
|
|
17055
|
-
return
|
|
17139
|
+
return {
|
|
17140
|
+
scope: outcome.result === "scope" ? outcome.scope : undefined,
|
|
17141
|
+
reason
|
|
17142
|
+
};
|
|
17056
17143
|
});
|
|
17057
17144
|
current.pending = lookup;
|
|
17058
17145
|
return lookup;
|
|
@@ -17099,7 +17186,7 @@ class SubcTransportPool {
|
|
|
17099
17186
|
retry = this.hostScope(client, record.identity);
|
|
17100
17187
|
}
|
|
17101
17188
|
retry.catch((error) => {
|
|
17102
|
-
warn(`subc head scope: background retry failed code=${error instanceof Error ? error.name : "Error"}`);
|
|
17189
|
+
warn(`subc head scope: session=${session} background retry failed code=${error instanceof Error ? error.name : "Error"}`);
|
|
17103
17190
|
});
|
|
17104
17191
|
}, Math.max(0, state.retryAt - Date.now()));
|
|
17105
17192
|
state.retryTimer.unref();
|
|
@@ -17115,8 +17202,8 @@ class SubcTransportPool {
|
|
|
17115
17202
|
}
|
|
17116
17203
|
refuseHostScopeBind(session, state, code) {
|
|
17117
17204
|
if (!this.hostScopeRefusals.has(session)) {
|
|
17118
|
-
this.hostScopeRefusals.
|
|
17119
|
-
warn(`subc head scope: session=${session} scoped bind refused code=${code};
|
|
17205
|
+
this.hostScopeRefusals.set(session, code);
|
|
17206
|
+
warn(`subc head scope: session=${session} scoped bind refused code=${code}; retry stopped until next bind lookup`);
|
|
17120
17207
|
}
|
|
17121
17208
|
if (state) {
|
|
17122
17209
|
state.scopeBindPending = false;
|
|
@@ -17125,11 +17212,12 @@ class SubcTransportPool {
|
|
|
17125
17212
|
}
|
|
17126
17213
|
async retryHostScopeBind(client, identity, state, scope) {
|
|
17127
17214
|
try {
|
|
17128
|
-
const route = await
|
|
17215
|
+
const route = await this.bindHostToolRoute(client, identity, "scope_probe", "scope_sync_retry", scope);
|
|
17129
17216
|
safeCloseRoute(client, route);
|
|
17130
17217
|
if (this.hostScopeCache.get(identity.session) !== state || !state.scopeBindPending || this.shuttingDown || client !== this.client || !this.unscopedHostRecord(identity.session))
|
|
17131
17218
|
return;
|
|
17132
17219
|
state.scopeBindPending = false;
|
|
17220
|
+
state.promoteAfterBind = false;
|
|
17133
17221
|
state.retryAt = 0;
|
|
17134
17222
|
state.retryMs = HOST_SCOPE_RETRY_FLOOR_MS;
|
|
17135
17223
|
log(`subc head scope: session=${identity.session} scoped bind recovered ref=${scope.ref.slice(0, 8)} epoch=${scope.scopeEpoch}`);
|
|
@@ -17255,24 +17343,39 @@ class SubcTransportPool {
|
|
|
17255
17343
|
signal.removeEventListener("abort", cancel);
|
|
17256
17344
|
}
|
|
17257
17345
|
}
|
|
17258
|
-
openHostToolRoute(client, identity, record, entry) {
|
|
17346
|
+
openHostToolRoute(client, identity, record, entry, channel = "tool") {
|
|
17259
17347
|
if (!this.hostScopes || !["opencode", "pi"].includes(identity.harness) || identity.session === DEFAULT_SESSION_ID) {
|
|
17260
|
-
|
|
17261
|
-
|
|
17262
|
-
|
|
17348
|
+
const reason = !this.hostScopes ? "host_scopes_disabled" : identity.session === DEFAULT_SESSION_ID ? "default_session" : "unsupported_harness";
|
|
17349
|
+
log(`subc head scope: session=${identity.session} lookup=skipped reason=${reason}`);
|
|
17350
|
+
return this.bindHostToolRoute(client, identity, channel, reason);
|
|
17263
17351
|
}
|
|
17264
|
-
return this.openScopedHostToolRoute(client, identity, record, entry);
|
|
17352
|
+
return this.openScopedHostToolRoute(client, identity, record, entry, channel);
|
|
17265
17353
|
}
|
|
17266
|
-
|
|
17267
|
-
|
|
17354
|
+
bindHostToolRoute(client, identity, channel, reason, scope) {
|
|
17355
|
+
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}` : ""));
|
|
17356
|
+
return client.routeOpen({ kind: "tool_provider", module_id: AFT_MODULE_ID }, identity, {
|
|
17357
|
+
consumerIdentity: this.consumerIdentity,
|
|
17358
|
+
...scope ? { scope } : {}
|
|
17359
|
+
});
|
|
17360
|
+
}
|
|
17361
|
+
async openScopedHostToolRoute(client, identity, record, entry, channel = "tool") {
|
|
17362
|
+
const decision = await this.hostScope(client, identity);
|
|
17363
|
+
const scope = decision.scope;
|
|
17364
|
+
let reason = decision.reason;
|
|
17268
17365
|
this.assertRecordLive(record);
|
|
17269
17366
|
if (scope) {
|
|
17270
17367
|
try {
|
|
17271
|
-
const route = await
|
|
17368
|
+
const route = await this.bindHostToolRoute(client, identity, channel, reason, scope);
|
|
17272
17369
|
if (entry)
|
|
17273
17370
|
entry.hostScope = scope;
|
|
17371
|
+
const state = this.hostScopeCache.get(identity.session);
|
|
17372
|
+
if (state?.promoteAfterBind && state.answer?.result === "scope" && state.answer.scope === scope) {
|
|
17373
|
+
state.promoteAfterBind = false;
|
|
17374
|
+
this.promoteHostScope(identity.session);
|
|
17375
|
+
}
|
|
17274
17376
|
return route;
|
|
17275
17377
|
} catch (error) {
|
|
17378
|
+
log(`subc head scope: session=${identity.session} bind=failed channel=${channel} reason=scoped_open_error code=${error instanceof SubcError ? error.code ?? error.name : "Error"}`);
|
|
17276
17379
|
if (!(error instanceof SubcError) || !error.code || isSocketDeathError(error))
|
|
17277
17380
|
throw error;
|
|
17278
17381
|
if (isRouteOpenReloadWindowError(error))
|
|
@@ -17281,18 +17384,21 @@ class SubcTransportPool {
|
|
|
17281
17384
|
const state = this.hostScopeCache.get(identity.session);
|
|
17282
17385
|
if (state && isHostScopeSyncRefusal(error.code)) {
|
|
17283
17386
|
this.deferHostScopeBind(identity.session, state, error.code);
|
|
17387
|
+
reason = `scope_sync_pending code=${error.code}`;
|
|
17284
17388
|
} else {
|
|
17285
17389
|
this.refuseHostScopeBind(identity.session, state, error.code);
|
|
17390
|
+
if (decision.reason === "cached_scope") {
|
|
17391
|
+
return this.openScopedHostToolRoute(client, identity, record, entry, channel);
|
|
17392
|
+
}
|
|
17393
|
+
reason = `scoped_bind_refused code=${error.code}`;
|
|
17286
17394
|
}
|
|
17287
17395
|
}
|
|
17288
17396
|
}
|
|
17289
17397
|
this.assertRecordLive(record);
|
|
17290
|
-
const route = await
|
|
17291
|
-
consumerIdentity: this.consumerIdentity
|
|
17292
|
-
});
|
|
17398
|
+
const route = await this.bindHostToolRoute(client, identity, channel, reason);
|
|
17293
17399
|
if (this.hostScopeCache.get(identity.session)?.answer?.result === "scope" && !this.hostScopeCache.get(identity.session)?.scopeBindPending && !this.hostScopeRefusals.has(identity.session)) {
|
|
17294
17400
|
safeCloseRoute(client, route);
|
|
17295
|
-
return this.openScopedHostToolRoute(client, identity, record, entry);
|
|
17401
|
+
return this.openScopedHostToolRoute(client, identity, record, entry, channel);
|
|
17296
17402
|
}
|
|
17297
17403
|
return route;
|
|
17298
17404
|
}
|
|
@@ -17389,7 +17495,7 @@ class SubcTransportPool {
|
|
|
17389
17495
|
};
|
|
17390
17496
|
sub = new BgSubscription(identity, () => this.ensureClient(), (client, cause) => this.dropClient(client, cause), (client) => {
|
|
17391
17497
|
binding.hostScope = undefined;
|
|
17392
|
-
return this.openHostToolRoute(client, identity, record, binding);
|
|
17498
|
+
return this.openHostToolRoute(client, identity, record, binding, "bg_events");
|
|
17393
17499
|
}, onNudge, this.bgBackoffSleep, () => this.rootCanAttach(record.canonicalRoot), (error) => {
|
|
17394
17500
|
if (!isAbsentRootRouteError(error))
|
|
17395
17501
|
return false;
|
|
@@ -17682,11 +17788,54 @@ function shellTimeoutDisposition(outcome) {
|
|
|
17682
17788
|
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.";
|
|
17683
17789
|
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.";
|
|
17684
17790
|
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.";
|
|
17791
|
+
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.";
|
|
17792
|
+
function bashConnectionClosedDisposition(taskId) {
|
|
17793
|
+
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.";
|
|
17794
|
+
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.`;
|
|
17795
|
+
}
|
|
17796
|
+
|
|
17797
|
+
class AftModuleUnavailableError extends SubcCallError {
|
|
17798
|
+
constructor(message, cause) {
|
|
17799
|
+
super("not_sent", message, "aft_module_unavailable", cause);
|
|
17800
|
+
this.name = "AftModuleUnavailableError";
|
|
17801
|
+
}
|
|
17802
|
+
}
|
|
17803
|
+
function refuseAftModuleOptionWithoutFallback(error, option, refusalPrefix) {
|
|
17804
|
+
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);
|
|
17805
|
+
}
|
|
17685
17806
|
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.";
|
|
17686
17807
|
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.";
|
|
17687
17808
|
function isRouteClosedMidCallError(error) {
|
|
17688
17809
|
return error instanceof SubcRouteClosedMidCallError || isLocalRouteCloseRejection(error);
|
|
17689
17810
|
}
|
|
17811
|
+
function isSubcConnectionClosedMidCallError(error) {
|
|
17812
|
+
if (!(error instanceof Error))
|
|
17813
|
+
return false;
|
|
17814
|
+
const fields = error;
|
|
17815
|
+
if (fields.kind !== "outcome_unknown")
|
|
17816
|
+
return false;
|
|
17817
|
+
if (error instanceof SocketClosedError)
|
|
17818
|
+
return true;
|
|
17819
|
+
if (error instanceof SubcError)
|
|
17820
|
+
return error.closeReason === "connection_lost";
|
|
17821
|
+
if (error instanceof SubcCallError) {
|
|
17822
|
+
return error.closeReason === "connection_lost" || hasConnectionLossCause(error);
|
|
17823
|
+
}
|
|
17824
|
+
return hasConnectionLossCause(error);
|
|
17825
|
+
}
|
|
17826
|
+
var CONNECTION_LOSS_ERROR_CODES = new Set(["ECONNABORTED", "ECONNRESET", "EPIPE"]);
|
|
17827
|
+
function hasConnectionLossCause(error, depth = 0) {
|
|
17828
|
+
if (error instanceof SocketClosedError)
|
|
17829
|
+
return true;
|
|
17830
|
+
if (error instanceof SubcError && error.closeReason === "connection_lost")
|
|
17831
|
+
return true;
|
|
17832
|
+
if (typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" && CONNECTION_LOSS_ERROR_CODES.has(error.code)) {
|
|
17833
|
+
return true;
|
|
17834
|
+
}
|
|
17835
|
+
if (!(error instanceof Error) || depth >= 4 || error.cause === undefined)
|
|
17836
|
+
return false;
|
|
17837
|
+
return hasConnectionLossCause(error.cause, depth + 1);
|
|
17838
|
+
}
|
|
17690
17839
|
function isRouteGoodbyeError(error) {
|
|
17691
17840
|
if (!(error instanceof SubcError))
|
|
17692
17841
|
return false;
|
|
@@ -17741,6 +17890,8 @@ function classifyBashHostFallbackError(error) {
|
|
|
17741
17890
|
if (!(error instanceof Error) || hasEngineResponse(error) || isRouteGoodbyeError(error)) {
|
|
17742
17891
|
return;
|
|
17743
17892
|
}
|
|
17893
|
+
if (isSubcConnectionClosedMidCallError(error))
|
|
17894
|
+
return;
|
|
17744
17895
|
if (error instanceof BridgeTransportUnknownOutcomeError)
|
|
17745
17896
|
return;
|
|
17746
17897
|
if (isRouteClosedMidCallError(error))
|
|
@@ -17767,6 +17918,8 @@ function isBashTransportDeadError(error) {
|
|
|
17767
17918
|
function adaptToolError(command, error) {
|
|
17768
17919
|
if (!(error instanceof Error))
|
|
17769
17920
|
return error;
|
|
17921
|
+
if (error instanceof AftModuleUnavailableError)
|
|
17922
|
+
return error;
|
|
17770
17923
|
if (isSubcFrameReadTimeoutError(error)) {
|
|
17771
17924
|
const typedError = error instanceof SubcFrameReadTimeoutError ? error : new SubcFrameReadTimeoutError(error);
|
|
17772
17925
|
const disposition = command === "bash" ? BASH_FRAME_READ_TIMEOUT_DISPOSITION : SUBC_FRAME_READ_TIMEOUT_DISPOSITION;
|
|
@@ -17781,6 +17934,13 @@ function adaptToolError(command, error) {
|
|
|
17781
17934
|
error.message = error.message ? `${error.message} ${BRIDGE_TRANSPORT_UNKNOWN_OUTCOME_DISPOSITION}` : BRIDGE_TRANSPORT_UNKNOWN_OUTCOME_DISPOSITION;
|
|
17782
17935
|
return error;
|
|
17783
17936
|
}
|
|
17937
|
+
if (isSubcConnectionClosedMidCallError(error)) {
|
|
17938
|
+
const disposition = command === "bash" ? bashConnectionClosedDisposition(taskIdFromError(error)) : SUBC_CONNECTION_CLOSED_MID_CALL_DISPOSITION;
|
|
17939
|
+
if (error.message.includes(disposition))
|
|
17940
|
+
return error;
|
|
17941
|
+
error.message = error.message ? `${error.message} ${disposition}` : disposition;
|
|
17942
|
+
return error;
|
|
17943
|
+
}
|
|
17784
17944
|
if (isRouteGoodbyeError(error)) {
|
|
17785
17945
|
if (error.message.includes(SUBC_MODULE_RESTART_DISPOSITION))
|
|
17786
17946
|
return error;
|
|
@@ -17793,6 +17953,13 @@ function adaptToolError(command, error) {
|
|
|
17793
17953
|
error.message = error.message ? `${error.message} ${SUBC_ROUTE_CLOSED_MID_CALL_DISPOSITION}` : SUBC_ROUTE_CLOSED_MID_CALL_DISPOSITION;
|
|
17794
17954
|
return error;
|
|
17795
17955
|
}
|
|
17956
|
+
if (command === "bash" && isDispatchedRequestDeadlineError(error)) {
|
|
17957
|
+
const disposition = bashConnectionClosedDisposition(taskIdFromError(error));
|
|
17958
|
+
if (error.message.includes(disposition))
|
|
17959
|
+
return error;
|
|
17960
|
+
error.message = error.message ? `${error.message} ${disposition}` : disposition;
|
|
17961
|
+
return error;
|
|
17962
|
+
}
|
|
17796
17963
|
if ((command === "bash" || command === "powershell") && isBridgeTransportTimeout(error)) {
|
|
17797
17964
|
const outcome = error.shellOutcome;
|
|
17798
17965
|
const disposition = shellTimeoutDisposition(outcome);
|
|
@@ -17801,6 +17968,9 @@ function adaptToolError(command, error) {
|
|
|
17801
17968
|
error.message = error.message ? `${error.message} ${disposition}` : disposition;
|
|
17802
17969
|
return error;
|
|
17803
17970
|
}
|
|
17971
|
+
if (command !== "bash" && isBashTransportDeadError(error)) {
|
|
17972
|
+
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);
|
|
17973
|
+
}
|
|
17804
17974
|
if (command !== "bash" || !isBashTransportDeadError(error))
|
|
17805
17975
|
return error;
|
|
17806
17976
|
if (error.message.includes(BASH_TRANSPORT_DISPOSITION))
|
|
@@ -17808,6 +17978,15 @@ function adaptToolError(command, error) {
|
|
|
17808
17978
|
error.message = error.message ? `${error.message} ${BASH_TRANSPORT_DISPOSITION}` : BASH_TRANSPORT_DISPOSITION;
|
|
17809
17979
|
return error;
|
|
17810
17980
|
}
|
|
17981
|
+
function taskIdFromError(error) {
|
|
17982
|
+
for (let current = error, depth = 0;current instanceof Error && depth < 5; current = current.cause, depth += 1) {
|
|
17983
|
+
const fields = current;
|
|
17984
|
+
const taskId = fields.taskId ?? fields.task_id;
|
|
17985
|
+
if (typeof taskId === "string" && taskId.length > 0)
|
|
17986
|
+
return taskId;
|
|
17987
|
+
}
|
|
17988
|
+
return;
|
|
17989
|
+
}
|
|
17811
17990
|
// ../aft-bridge/dist/feature-config.js
|
|
17812
17991
|
import { createHash as createHash4 } from "node:crypto";
|
|
17813
17992
|
var CANONICAL_TOOLS = [
|
|
@@ -22993,39 +23172,70 @@ function validateOptionalCanonicalPath(record, property) {
|
|
|
22993
23172
|
}
|
|
22994
23173
|
pathValue(record, property);
|
|
22995
23174
|
}
|
|
22996
|
-
function
|
|
22997
|
-
|
|
22998
|
-
|
|
22999
|
-
|
|
23000
|
-
|
|
23001
|
-
|
|
23002
|
-
|
|
23003
|
-
}
|
|
23004
|
-
const source = target;
|
|
23005
|
-
const blank = (value) => value === undefined || value === null || typeof value === "string" && !value.trim();
|
|
23006
|
-
const emptyTarget = blank(source.symbol) && blank(source.path) && blank(source.filePath);
|
|
23007
|
-
if (emptyTarget)
|
|
23008
|
-
return { ...source };
|
|
23009
|
-
const normalized = { ...source };
|
|
23010
|
-
try {
|
|
23011
|
-
normalizeAliasPair(normalized, "path", "filePath", false);
|
|
23012
|
-
} catch (error) {
|
|
23013
|
-
if (error instanceof InvalidRequestError) {
|
|
23014
|
-
throw new InvalidRequestError(error.message.replace("'filePath'", `'targets[${index}].filePath'`).replace("'path'", `'targets[${index}].path'`));
|
|
23015
|
-
}
|
|
23016
|
-
throw error;
|
|
23175
|
+
function normalizeZoomLocation(record, prefix = "") {
|
|
23176
|
+
let first;
|
|
23177
|
+
for (const key of ["target", "path", "filePath", "url"]) {
|
|
23178
|
+
const value = record[key];
|
|
23179
|
+
if (value === undefined || value === null || typeof value === "string" && !value.trim()) {
|
|
23180
|
+
delete record[key];
|
|
23181
|
+
continue;
|
|
23017
23182
|
}
|
|
23018
|
-
|
|
23019
|
-
|
|
23020
|
-
|
|
23021
|
-
|
|
23022
|
-
|
|
23023
|
-
record.targets = targets.map(normalizeTarget);
|
|
23024
|
-
return;
|
|
23025
|
-
}
|
|
23026
|
-
if (targets && typeof targets === "object") {
|
|
23027
|
-
record.targets = normalizeTarget(targets, 0);
|
|
23183
|
+
const location = pathValue(record, key);
|
|
23184
|
+
if (first && first.value !== location) {
|
|
23185
|
+
throw new InvalidRequestError(`Conflicting '${prefix}${first.key}' location '${first.value}' and '${prefix}${key}' '${location}': provide the same location or only one target`);
|
|
23186
|
+
}
|
|
23187
|
+
first ??= { key, value: location };
|
|
23028
23188
|
}
|
|
23189
|
+
for (const alias of ["path", "filePath", "url"])
|
|
23190
|
+
delete record[alias];
|
|
23191
|
+
if (first)
|
|
23192
|
+
record.target = first.value;
|
|
23193
|
+
}
|
|
23194
|
+
function normalizeZoomTargets(record) {
|
|
23195
|
+
const normalizeEntry = (value, prefix) => {
|
|
23196
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
23197
|
+
throw new InvalidRequestError(`'${prefix}target' must be a non-empty string`);
|
|
23198
|
+
}
|
|
23199
|
+
const entry = { ...value };
|
|
23200
|
+
normalizeZoomLocation(entry, prefix);
|
|
23201
|
+
if (hasOwn(entry, "symbol")) {
|
|
23202
|
+
if (!isBlankParam(entry.symbols) && JSON.stringify(entry.symbols) !== JSON.stringify(entry.symbol)) {
|
|
23203
|
+
throw new InvalidRequestError(`Conflicting '${prefix}symbols' and '${prefix}symbol': provide the same symbols or only one spelling`);
|
|
23204
|
+
}
|
|
23205
|
+
if (isBlankParam(entry.symbols))
|
|
23206
|
+
entry.symbols = entry.symbol;
|
|
23207
|
+
delete entry.symbol;
|
|
23208
|
+
}
|
|
23209
|
+
entry.target ??= "";
|
|
23210
|
+
entry.symbols ??= "";
|
|
23211
|
+
return entry;
|
|
23212
|
+
};
|
|
23213
|
+
const batch = [];
|
|
23214
|
+
if (hasOwn(record, "batch")) {
|
|
23215
|
+
const entries = coerceJsonCollectionParam(record.batch, "batch");
|
|
23216
|
+
if (!Array.isArray(entries) || entries.length === 0) {
|
|
23217
|
+
throw new InvalidRequestError("'batch' must be a non-empty array of {target, symbols}");
|
|
23218
|
+
}
|
|
23219
|
+
batch.push(...entries.map((entry, index) => normalizeEntry(entry, `batch[${index}].`)));
|
|
23220
|
+
}
|
|
23221
|
+
normalizeZoomLocation(record);
|
|
23222
|
+
if (!isBlankParam(record.target) || !isBlankParam(record.symbols)) {
|
|
23223
|
+
const entry = {};
|
|
23224
|
+
if (record.target !== undefined)
|
|
23225
|
+
entry.target = record.target;
|
|
23226
|
+
if (record.symbols !== undefined)
|
|
23227
|
+
entry.symbols = record.symbols;
|
|
23228
|
+
batch.unshift(normalizeEntry(entry, ""));
|
|
23229
|
+
}
|
|
23230
|
+
if (hasOwn(record, "targets")) {
|
|
23231
|
+
const targets = coerceJsonCollectionParam(record.targets, "targets");
|
|
23232
|
+
batch.push(...usableZoomTargets(targets).map((entry, index) => normalizeEntry(entry, `targets[${index}].`)));
|
|
23233
|
+
}
|
|
23234
|
+
for (const key of ["target", "symbols", "targets"])
|
|
23235
|
+
delete record[key];
|
|
23236
|
+
if (!batch.length)
|
|
23237
|
+
throw new InvalidRequestError("'batch' must be a non-empty array of {target, symbols}");
|
|
23238
|
+
record.batch = batch;
|
|
23029
23239
|
}
|
|
23030
23240
|
function bareToolName(toolName) {
|
|
23031
23241
|
const bare = toolName.startsWith("aft_") ? toolName.slice(4) : toolName;
|
|
@@ -23052,7 +23262,6 @@ function prepareCanonicalPathArguments(toolName, rawArguments) {
|
|
|
23052
23262
|
normalizeAliasPair(record, "path", "filePath", true);
|
|
23053
23263
|
break;
|
|
23054
23264
|
case "zoom":
|
|
23055
|
-
normalizeAliasPair(record, "path", "filePath", false);
|
|
23056
23265
|
normalizeZoomTargets(record);
|
|
23057
23266
|
break;
|
|
23058
23267
|
case "callgraph":
|
|
@@ -23473,6 +23682,19 @@ function copyOwnProperties(source) {
|
|
|
23473
23682
|
copy[key] = source[key];
|
|
23474
23683
|
return copy;
|
|
23475
23684
|
}
|
|
23685
|
+
// ../aft-bridge/dist/revision-reads.js
|
|
23686
|
+
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.";
|
|
23687
|
+
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.";
|
|
23688
|
+
function revisionReadsOffered(config) {
|
|
23689
|
+
return config.revision_reads === true;
|
|
23690
|
+
}
|
|
23691
|
+
function revisionRefArg(value) {
|
|
23692
|
+
if (value === undefined || value === null)
|
|
23693
|
+
return;
|
|
23694
|
+
if (typeof value === "string" && value.trim().length === 0)
|
|
23695
|
+
return;
|
|
23696
|
+
return value;
|
|
23697
|
+
}
|
|
23476
23698
|
// ../aft-bridge/dist/semantic-status.js
|
|
23477
23699
|
var MISSING_ONNX_RUNTIME_MARKER = "ONNX Runtime not found.";
|
|
23478
23700
|
var MISSING_ONNX_RUNTIME_LABEL = "unavailable — ONNX Runtime missing (npx @cortexkit/aft doctor --fix)";
|
|
@@ -28495,6 +28717,41 @@ var ExperimentalConfigSchema = object({
|
|
|
28495
28717
|
}).optional(),
|
|
28496
28718
|
lsp_ty: boolean2().optional()
|
|
28497
28719
|
});
|
|
28720
|
+
var BLOCKED_BASH_ENV_NAMES = new Set([
|
|
28721
|
+
"LD_PRELOAD",
|
|
28722
|
+
"LD_LIBRARY_PATH",
|
|
28723
|
+
"LD_AUDIT",
|
|
28724
|
+
"DYLD_INSERT_LIBRARIES",
|
|
28725
|
+
"DYLD_LIBRARY_PATH",
|
|
28726
|
+
"DYLD_FALLBACK_LIBRARY_PATH",
|
|
28727
|
+
"BASH_ENV",
|
|
28728
|
+
"ENV",
|
|
28729
|
+
"IFS",
|
|
28730
|
+
"PATH",
|
|
28731
|
+
"SHELLOPTS",
|
|
28732
|
+
"BASHOPTS",
|
|
28733
|
+
"PROMPT_COMMAND",
|
|
28734
|
+
"PS4",
|
|
28735
|
+
"PYTHONSTARTUP",
|
|
28736
|
+
"PYTHONPATH",
|
|
28737
|
+
"PYTHONHOME",
|
|
28738
|
+
"PERL5OPT",
|
|
28739
|
+
"PERL5LIB",
|
|
28740
|
+
"PERLLIB",
|
|
28741
|
+
"PERL5DB",
|
|
28742
|
+
"RUBYOPT",
|
|
28743
|
+
"RUBYLIB",
|
|
28744
|
+
"NODE_OPTIONS",
|
|
28745
|
+
"GCONV_PATH",
|
|
28746
|
+
"LOCPATH",
|
|
28747
|
+
"NLSPATH",
|
|
28748
|
+
"HOSTALIASES",
|
|
28749
|
+
"RESOLV_HOST_CONF"
|
|
28750
|
+
]);
|
|
28751
|
+
function isBlockedBashEnvKey(key) {
|
|
28752
|
+
const normalized = process.platform === "win32" ? key.toUpperCase() : key;
|
|
28753
|
+
return BLOCKED_BASH_ENV_NAMES.has(normalized) || normalized.startsWith("LD_") || normalized.startsWith("DYLD_") || normalized.startsWith("BASH_FUNC_");
|
|
28754
|
+
}
|
|
28498
28755
|
var BashFeaturesSchema = object({
|
|
28499
28756
|
enabled: boolean2().optional(),
|
|
28500
28757
|
rewrite: boolean2().optional(),
|
|
@@ -28514,7 +28771,18 @@ var BashFeaturesSchema = object({
|
|
|
28514
28771
|
}).optional(),
|
|
28515
28772
|
linux_scope: boolean2().optional(),
|
|
28516
28773
|
disclaim_privacy: boolean2().optional(),
|
|
28517
|
-
powershell_tool: boolean2().optional()
|
|
28774
|
+
powershell_tool: boolean2().optional(),
|
|
28775
|
+
env: record(string2(), string2()).superRefine((env, context) => {
|
|
28776
|
+
for (const key of Object.keys(env)) {
|
|
28777
|
+
if (isBlockedBashEnvKey(key)) {
|
|
28778
|
+
context.addIssue({
|
|
28779
|
+
code: "custom",
|
|
28780
|
+
path: [key],
|
|
28781
|
+
message: `bash.env key ${key} is blocked because it can alter startup or protected process state`
|
|
28782
|
+
});
|
|
28783
|
+
}
|
|
28784
|
+
}
|
|
28785
|
+
}).optional()
|
|
28518
28786
|
});
|
|
28519
28787
|
var BashConfigSchema = union([boolean2(), BashFeaturesSchema]);
|
|
28520
28788
|
var SandboxConfigSchema = object({
|
|
@@ -28603,6 +28871,7 @@ var AftConfigFieldsSchema = object({
|
|
|
28603
28871
|
$schema: string2().optional(),
|
|
28604
28872
|
edit_mode: _enum(["default", "hashline"]).optional(),
|
|
28605
28873
|
format_on_edit: boolean2().optional(),
|
|
28874
|
+
revision_reads: boolean2().optional(),
|
|
28606
28875
|
formatter_timeout_secs: number2().int().min(1).max(600).optional(),
|
|
28607
28876
|
validate_on_edit: _enum(["syntax", "full"]).optional(),
|
|
28608
28877
|
formatter: record(string2(), FormatterEnum).optional(),
|
|
@@ -28727,7 +28996,8 @@ function resolveBashConfig(config) {
|
|
|
28727
28996
|
foreground_wait_window_ms: foregroundWaitWindowMs,
|
|
28728
28997
|
watch_sync_max_ms: watchSyncMaxMs,
|
|
28729
28998
|
worker_wait_max_ms: (typeof top === "object" && top !== null ? top.worker_wait_max_ms : undefined) ?? DEFAULT_WORKER_WAIT_MAX_MS,
|
|
28730
|
-
powershell_tool: typeof top === "object" && top !== null ? top.powershell_tool ?? false : false
|
|
28999
|
+
powershell_tool: typeof top === "object" && top !== null ? top.powershell_tool ?? false : false,
|
|
29000
|
+
env: typeof top === "object" && top !== null ? { ...top.env ?? {} } : {}
|
|
28731
29001
|
};
|
|
28732
29002
|
if (top === false) {
|
|
28733
29003
|
return base;
|
|
@@ -29057,6 +29327,7 @@ function getProjectLspStrippedKeys(lsp) {
|
|
|
29057
29327
|
var PROJECT_SAFE_TOP_LEVEL_FIELDS = new Set([
|
|
29058
29328
|
"edit_mode",
|
|
29059
29329
|
"format_on_edit",
|
|
29330
|
+
"revision_reads",
|
|
29060
29331
|
"validate_on_edit",
|
|
29061
29332
|
"configure_warnings_delivery",
|
|
29062
29333
|
"views",
|
|
@@ -29111,6 +29382,8 @@ function getStrippedTopLevelKeys(override, base) {
|
|
|
29111
29382
|
}
|
|
29112
29383
|
if (typeof override.bash === "object" && override.bash.disclaim_privacy === false)
|
|
29113
29384
|
stripped.push("bash.disclaim_privacy");
|
|
29385
|
+
if (typeof override.bash === "object" && override.bash.env !== undefined)
|
|
29386
|
+
stripped.push("bash.env");
|
|
29114
29387
|
if (override.sandbox?.write_allow !== undefined)
|
|
29115
29388
|
stripped.push("sandbox.write_allow");
|
|
29116
29389
|
if (override.subc !== undefined)
|
|
@@ -29158,8 +29431,10 @@ function mergeConfigs(base, override) {
|
|
|
29158
29431
|
const projectBash = typeof override.bash === "object" ? { ...override.bash } : override.bash;
|
|
29159
29432
|
if (typeof projectBash === "object" && projectBash.disclaim_privacy !== true)
|
|
29160
29433
|
delete projectBash.disclaim_privacy;
|
|
29161
|
-
if (typeof projectBash === "object")
|
|
29434
|
+
if (typeof projectBash === "object") {
|
|
29162
29435
|
delete projectBash.runon_enabled;
|
|
29436
|
+
delete projectBash.env;
|
|
29437
|
+
}
|
|
29163
29438
|
const bash = mergeBashConfig(base.bash, projectBash);
|
|
29164
29439
|
const inspect = mergeInspectConfig(base.inspect, override.inspect);
|
|
29165
29440
|
const worktree = mergeWorktreeConfig(base.worktree, override.worktree);
|
|
@@ -29699,7 +29974,7 @@ var wakeConfirmationWindowMs = DEFAULT_WAKE_CONFIRMATION_WINDOW_MS;
|
|
|
29699
29974
|
var busyWakeDeferCeilingMs = BUSY_WAKE_DEFER_CEILING_MS;
|
|
29700
29975
|
var lastWakeMessageTimestamp = 0;
|
|
29701
29976
|
var wakeMessageCounter = 0;
|
|
29702
|
-
var subcNudgesInFlight = new
|
|
29977
|
+
var subcNudgesInFlight = new WeakMap;
|
|
29703
29978
|
var subcNudgeLogState = new Map;
|
|
29704
29979
|
var deferredBusyWakes = new Map;
|
|
29705
29980
|
function consumeBgCompletion(sessionID, taskId) {
|
|
@@ -29781,7 +30056,7 @@ function markExplicitControl(sessionID, taskId, trackOutstanding = true) {
|
|
|
29781
30056
|
if (idx >= 0) {
|
|
29782
30057
|
const completion = state.pendingCompletions[idx];
|
|
29783
30058
|
state.pendingCompletions.splice(idx, 1);
|
|
29784
|
-
queuePendingPatternMatch(state, completionToExitPattern(completion, true));
|
|
30059
|
+
queuePendingPatternMatch(state, completionToExitPattern(completion, sessionID, true));
|
|
29785
30060
|
state.wakeDeferredTaskIds.delete(taskId);
|
|
29786
30061
|
}
|
|
29787
30062
|
}
|
|
@@ -29818,7 +30093,7 @@ function queuePendingPatternMatch(state, entry) {
|
|
|
29818
30093
|
}
|
|
29819
30094
|
state.pendingPatternMatches.push(normalized);
|
|
29820
30095
|
}
|
|
29821
|
-
function routeExplicitControlCompletions(state) {
|
|
30096
|
+
function routeExplicitControlCompletions(state, sessionID) {
|
|
29822
30097
|
if (state.pendingCompletions.length === 0)
|
|
29823
30098
|
return;
|
|
29824
30099
|
const remaining = [];
|
|
@@ -29827,7 +30102,7 @@ function routeExplicitControlCompletions(state) {
|
|
|
29827
30102
|
state.outstandingTaskIds.delete(completion.task_id);
|
|
29828
30103
|
state.explicitControlTasks.delete(completion.task_id);
|
|
29829
30104
|
state.wakeDeferredTaskIds.delete(completion.task_id);
|
|
29830
|
-
queuePendingPatternMatch(state, completionToExitPattern(completion, true));
|
|
30105
|
+
queuePendingPatternMatch(state, completionToExitPattern(completion, sessionID, true));
|
|
29831
30106
|
} else {
|
|
29832
30107
|
remaining.push(completion);
|
|
29833
30108
|
}
|
|
@@ -29859,7 +30134,7 @@ function ingestBgCompletions(sessionID, completions) {
|
|
|
29859
30134
|
if (state.explicitControlTasks.has(completion.task_id)) {
|
|
29860
30135
|
state.outstandingTaskIds.delete(completion.task_id);
|
|
29861
30136
|
state.explicitControlTasks.delete(completion.task_id);
|
|
29862
|
-
queuePendingPatternMatch(state, completionToExitPattern(completion, true));
|
|
30137
|
+
queuePendingPatternMatch(state, completionToExitPattern(completion, sessionID, true));
|
|
29863
30138
|
continue;
|
|
29864
30139
|
}
|
|
29865
30140
|
if (isDeliveringOrAwaitingAck(state, completion.task_id)) {
|
|
@@ -29901,7 +30176,7 @@ async function appendInTurnBgCompletions(drainContext, output) {
|
|
|
29901
30176
|
if (state.outstandingTaskIds.size > 0 || !state.forcedDrainCompleted) {
|
|
29902
30177
|
await drainCompletions(drainContext);
|
|
29903
30178
|
}
|
|
29904
|
-
routeExplicitControlCompletions(state);
|
|
30179
|
+
routeExplicitControlCompletions(state, drainContext.sessionID);
|
|
29905
30180
|
if (state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0 && state.pendingPatternMatches.length === 0)
|
|
29906
30181
|
return;
|
|
29907
30182
|
const deliveredCompletions = [...state.pendingCompletions];
|
|
@@ -29917,6 +30192,9 @@ async function appendInTurnBgCompletions(drainContext, output) {
|
|
|
29917
30192
|
reminder_chars: reminder.length
|
|
29918
30193
|
});
|
|
29919
30194
|
state.pendingCompletions = [];
|
|
30195
|
+
for (const completion of deliveredCompletions) {
|
|
30196
|
+
consumeBgCompletion(drainContext.sessionID, completion.task_id);
|
|
30197
|
+
}
|
|
29920
30198
|
state.heldWorkerCompletions = false;
|
|
29921
30199
|
for (const completion of deliveredCompletions) {
|
|
29922
30200
|
state.wakeDeferredTaskIds.delete(completion.task_id);
|
|
@@ -29940,17 +30218,22 @@ async function handleIdleBgCompletions(drainContext) {
|
|
|
29940
30218
|
}
|
|
29941
30219
|
function handleSubcBgEventsNudge(drainContext) {
|
|
29942
30220
|
const key = `${drainContext.directory}\x00${drainContext.sessionID}`;
|
|
29943
|
-
|
|
30221
|
+
let poolNudges = subcNudgesInFlight.get(drainContext.ctx.pool);
|
|
30222
|
+
if (!poolNudges) {
|
|
30223
|
+
poolNudges = new Map;
|
|
30224
|
+
subcNudgesInFlight.set(drainContext.ctx.pool, poolNudges);
|
|
30225
|
+
}
|
|
30226
|
+
const inFlight = poolNudges.get(key);
|
|
29944
30227
|
if (inFlight) {
|
|
29945
30228
|
logSubcNudgeLifecycle(drainContext, "coalesced-in-flight", "nudge coalesced cause=handler-already-in-flight");
|
|
29946
30229
|
return inFlight;
|
|
29947
30230
|
}
|
|
29948
30231
|
logSubcNudgeLifecycle(drainContext, "handler-entry", "nudge handler entered");
|
|
29949
30232
|
const handling = handleSubcBgEventsNudgeOnce(drainContext);
|
|
29950
|
-
|
|
30233
|
+
poolNudges.set(key, handling);
|
|
29951
30234
|
const clear = () => {
|
|
29952
|
-
if (
|
|
29953
|
-
|
|
30235
|
+
if (poolNudges.get(key) === handling)
|
|
30236
|
+
poolNudges.delete(key);
|
|
29954
30237
|
};
|
|
29955
30238
|
handling.then(clear, clear);
|
|
29956
30239
|
return handling;
|
|
@@ -29959,9 +30242,62 @@ async function handleSubcBgEventsNudgeOnce(drainContext) {
|
|
|
29959
30242
|
bridgeForDrain(drainContext);
|
|
29960
30243
|
const state = stateFor(drainContext.sessionID);
|
|
29961
30244
|
state.wakeDeferredTaskIds.clear();
|
|
30245
|
+
if (drainContext.delivery) {
|
|
30246
|
+
await drainCompletions(drainContext);
|
|
30247
|
+
await deliverDrainedNotifications(drainContext, drainContext.delivery);
|
|
30248
|
+
return;
|
|
30249
|
+
}
|
|
29962
30250
|
rearmHardStoppedWake(state, drainContext);
|
|
29963
30251
|
await triggerWakeIfPending(drainContext, false, true, true, true);
|
|
29964
30252
|
}
|
|
30253
|
+
async function deliverDrainedNotifications(drainContext, delivery) {
|
|
30254
|
+
const state = stateFor(drainContext.sessionID);
|
|
30255
|
+
routeExplicitControlCompletions(state, drainContext.sessionID);
|
|
30256
|
+
const completions = state.pendingCompletions.splice(0);
|
|
30257
|
+
const matches = state.pendingPatternMatches.splice(0);
|
|
30258
|
+
const taskIds = [...new Set([...completions, ...matches].map((item) => item.task_id))];
|
|
30259
|
+
markDelivering(state, taskIds);
|
|
30260
|
+
const bridge = bridgeForDrain(drainContext);
|
|
30261
|
+
const items = [...completions, ...matches];
|
|
30262
|
+
for (const [index, item] of items.entries()) {
|
|
30263
|
+
const isMatch = isPatternMatchEntry(item);
|
|
30264
|
+
try {
|
|
30265
|
+
if (!isMatch && state.consumedTaskIds.has(item.task_id))
|
|
30266
|
+
continue;
|
|
30267
|
+
const admitted = isMatch ? await delivery.patternMatch(item, bridge) : await delivery.completion(item, bridge);
|
|
30268
|
+
if (!admitted) {
|
|
30269
|
+
if (isMatch)
|
|
30270
|
+
queuePendingPatternMatch(state, item);
|
|
30271
|
+
else if (!state.consumedTaskIds.has(item.task_id))
|
|
30272
|
+
state.pendingCompletions.push(item);
|
|
30273
|
+
continue;
|
|
30274
|
+
}
|
|
30275
|
+
if (!isMatch)
|
|
30276
|
+
consumeBgCompletion(drainContext.sessionID, item.task_id);
|
|
30277
|
+
markDeliveredAwaitingAck(state, [item.task_id]);
|
|
30278
|
+
const acked = await ackCompletions(drainContext, [
|
|
30279
|
+
{ task_id: item.task_id, status: "unknown", exit_code: null, command: "" }
|
|
30280
|
+
]);
|
|
30281
|
+
if (acked)
|
|
30282
|
+
confirmAcked(state, [item.task_id]);
|
|
30283
|
+
} catch (error) {
|
|
30284
|
+
if (isMatch)
|
|
30285
|
+
queuePendingPatternMatch(state, item);
|
|
30286
|
+
else
|
|
30287
|
+
state.pendingCompletions.push(item);
|
|
30288
|
+
for (const remaining of items.slice(index + 1)) {
|
|
30289
|
+
if (isPatternMatchEntry(remaining))
|
|
30290
|
+
queuePendingPatternMatch(state, remaining);
|
|
30291
|
+
else
|
|
30292
|
+
state.pendingCompletions.push(remaining);
|
|
30293
|
+
}
|
|
30294
|
+
clearDelivering(state, taskIds);
|
|
30295
|
+
throw error;
|
|
30296
|
+
} finally {
|
|
30297
|
+
clearDelivering(state, [item.task_id]);
|
|
30298
|
+
}
|
|
30299
|
+
}
|
|
30300
|
+
}
|
|
29965
30301
|
function logSubcNudgeLifecycle(drainContext, kind, message) {
|
|
29966
30302
|
logDeliveryHop(drainContext, kind, message, {
|
|
29967
30303
|
event: "subc_bg_nudge_delivery",
|
|
@@ -30275,7 +30611,7 @@ async function triggerWakeIfPending(drainContext, skipDrain, includeDeferredComp
|
|
|
30275
30611
|
if (!skipDrain && (forceDrain || state.outstandingTaskIds.size > 0 || !state.forcedDrainCompleted) && (forceDrain || hasTrackedOrPendingWork || idleDrainMayFindWork(state, drainContext))) {
|
|
30276
30612
|
await drainCompletions(drainContext);
|
|
30277
30613
|
}
|
|
30278
|
-
routeExplicitControlCompletions(state);
|
|
30614
|
+
routeExplicitControlCompletions(state, drainContext.sessionID);
|
|
30279
30615
|
if (!hasWakeEligiblePending(state, includeDeferredCompletions))
|
|
30280
30616
|
return;
|
|
30281
30617
|
if (deferWhileBusy && state.hostStatus === "busy") {
|
|
@@ -30768,7 +31104,7 @@ function ingestDrainedBgCompletions(sessionID, completions) {
|
|
|
30768
31104
|
state.outstandingTaskIds.delete(completion.task_id);
|
|
30769
31105
|
if (state.explicitControlTasks.has(completion.task_id)) {
|
|
30770
31106
|
state.explicitControlTasks.delete(completion.task_id);
|
|
30771
|
-
queuePendingPatternMatch(state, completionToExitPattern(completion, true));
|
|
31107
|
+
queuePendingPatternMatch(state, completionToExitPattern(completion, sessionID, true));
|
|
30772
31108
|
continue;
|
|
30773
31109
|
}
|
|
30774
31110
|
if (state.consumedTaskIds.has(completion.task_id))
|
|
@@ -30809,12 +31145,12 @@ function bufferUnknownCompletion(state, completion) {
|
|
|
30809
31145
|
function pruneUnknownCompletions(state, now) {
|
|
30810
31146
|
state.unknownCompletions = state.unknownCompletions.filter((entry) => now - entry.receivedAt <= UNKNOWN_COMPLETION_TTL_MS);
|
|
30811
31147
|
}
|
|
30812
|
-
function completionToExitPattern(completion, ackCompletionOnDelivery = false) {
|
|
31148
|
+
function completionToExitPattern(completion, sessionID, ackCompletionOnDelivery = false) {
|
|
30813
31149
|
const status = formatStatus(completion);
|
|
30814
31150
|
const preview = formatOutputPreview(completion).replace(/^ {4}/gm, "").slice(-300);
|
|
30815
31151
|
const entry = {
|
|
30816
31152
|
task_id: completion.task_id,
|
|
30817
|
-
session_id:
|
|
31153
|
+
session_id: sessionID || DEFAULT_SESSION_ID2,
|
|
30818
31154
|
watch_id: "exit",
|
|
30819
31155
|
match_text: "",
|
|
30820
31156
|
match_offset: 0,
|
|
@@ -44086,6 +44422,10 @@ async function callToolCall(ctx, runtime, name, rawArgs = {}, options) {
|
|
|
44086
44422
|
const opts = Object.keys(sendOptions).length > 0 ? sendOptions : undefined;
|
|
44087
44423
|
response = ctx.pool.toolCallForCanonicalRoot ? await ctx.pool.toolCallForCanonicalRoot(root, runtime.sessionID, name, rawArgs, opts) : await ctx.pool.getBridge(root).toolCall(runtime.sessionID, name, rawArgs, opts);
|
|
44088
44424
|
} catch (error) {
|
|
44425
|
+
const noFallbackOption = name === "bash" ? rawArgs.runon !== undefined ? "runon" : rawArgs.pty === true ? "pty:true" : rawArgs.background === true ? "background:true" : undefined : undefined;
|
|
44426
|
+
if (noFallbackOption && isBashTransportDeadError(error)) {
|
|
44427
|
+
throw refuseAftModuleOptionWithoutFallback(error, noFallbackOption, BASH_HOST_FALLBACK_REFUSAL);
|
|
44428
|
+
}
|
|
44089
44429
|
throw adaptToolError(name, error);
|
|
44090
44430
|
} finally {
|
|
44091
44431
|
markBridgeEnd();
|
|
@@ -45343,17 +45683,19 @@ ${BASH_RUNON_GUIDANCE}`, "");
|
|
|
45343
45683
|
});
|
|
45344
45684
|
} catch (error) {
|
|
45345
45685
|
const fallbackCause = classifyBashHostFallbackError(error);
|
|
45346
|
-
if (
|
|
45686
|
+
if (fallbackCause === undefined)
|
|
45347
45687
|
throw error;
|
|
45348
45688
|
if (!backgroundDisabled && rawRequestedBackground) {
|
|
45349
|
-
throw
|
|
45689
|
+
throw refuseAftModuleOptionWithoutFallback(error, "background:true", BASH_HOST_FALLBACK_REFUSAL);
|
|
45350
45690
|
}
|
|
45351
45691
|
if (requestedPty) {
|
|
45352
|
-
throw
|
|
45692
|
+
throw refuseAftModuleOptionWithoutFallback(error, "pty:true", BASH_HOST_FALLBACK_REFUSAL);
|
|
45353
45693
|
}
|
|
45354
45694
|
if (runon !== undefined) {
|
|
45355
|
-
throw
|
|
45695
|
+
throw refuseAftModuleOptionWithoutFallback(error, "runon", BASH_HOST_FALLBACK_REFUSAL);
|
|
45356
45696
|
}
|
|
45697
|
+
if (!bashCfg.host_fallback)
|
|
45698
|
+
throw error;
|
|
45357
45699
|
const projectRoot = projectRootFor(context);
|
|
45358
45700
|
const pattern = bashHostFallbackAskPattern(command, projectRoot, fallbackCause);
|
|
45359
45701
|
await runAsk(context.ask({
|
|
@@ -45761,14 +46103,22 @@ function runtimeFor(name, location, context, signal, consumers, onStepAbort) {
|
|
|
45761
46103
|
}
|
|
45762
46104
|
function projectV2Tool(name, definition, location, consumers = {}) {
|
|
45763
46105
|
const hostName = v2HostToolName(name);
|
|
45764
|
-
const
|
|
45765
|
-
|
|
46106
|
+
const currentInput = () => {
|
|
46107
|
+
const input = tool.schema.object(projectArguments(name, definition));
|
|
46108
|
+
assertV2Contract(hostName, input);
|
|
46109
|
+
return input;
|
|
46110
|
+
};
|
|
46111
|
+
currentInput();
|
|
45766
46112
|
const sharedOptions = definition.options;
|
|
45767
46113
|
const permission = hostPermission(name);
|
|
45768
46114
|
return {
|
|
45769
46115
|
name: hostName,
|
|
45770
|
-
description
|
|
45771
|
-
|
|
46116
|
+
get description() {
|
|
46117
|
+
return renameV2ToolReferences(["read", "write", "edit"].includes(name) ? definition.description.replaceAll("filePath", "path") : definition.description);
|
|
46118
|
+
},
|
|
46119
|
+
get input() {
|
|
46120
|
+
return currentInput();
|
|
46121
|
+
},
|
|
45772
46122
|
options: {
|
|
45773
46123
|
...sharedOptions,
|
|
45774
46124
|
...permission ? { permission } : {},
|
|
@@ -46369,14 +46719,12 @@ function buildUnifiedDiff(fp, before, after) {
|
|
|
46369
46719
|
`);
|
|
46370
46720
|
const afterLines = after.split(`
|
|
46371
46721
|
`);
|
|
46372
|
-
const
|
|
46373
|
-
if (
|
|
46374
|
-
const limit = Math.max(beforeLines.length, afterLines.length);
|
|
46722
|
+
const ops = diffLines(beforeLines, afterLines);
|
|
46723
|
+
if (ops === null) {
|
|
46375
46724
|
return `Index: ${fp}
|
|
46376
|
-
(diff skipped:
|
|
46725
|
+
(diff skipped: changed region exceeds the diff-work budget)
|
|
46377
46726
|
`;
|
|
46378
46727
|
}
|
|
46379
|
-
const ops = diffLines(beforeLines, afterLines);
|
|
46380
46728
|
if (ops.every((op) => op.tag === "eq")) {
|
|
46381
46729
|
return `Index: ${fp}
|
|
46382
46730
|
===================================================================
|
|
@@ -46402,6 +46750,9 @@ function buildUnifiedDiff(fp, before, after) {
|
|
|
46402
46750
|
}
|
|
46403
46751
|
return diff;
|
|
46404
46752
|
}
|
|
46753
|
+
var LCS_TABLE_CELL_BUDGET = 25010001;
|
|
46754
|
+
var MYERS_TRACE_CELL_BUDGET = 1500000;
|
|
46755
|
+
var MYERS_WORK_BUDGET = 25000000;
|
|
46405
46756
|
function diffLines(a, b) {
|
|
46406
46757
|
const n = a.length;
|
|
46407
46758
|
const m = b.length;
|
|
@@ -46417,6 +46768,26 @@ function diffLines(a, b) {
|
|
|
46417
46768
|
const innerM = m - prefix - suffix;
|
|
46418
46769
|
const w = innerM + 1;
|
|
46419
46770
|
const cells = (innerN + 1) * w;
|
|
46771
|
+
if (cells > LCS_TABLE_CELL_BUDGET) {
|
|
46772
|
+
const middle = myersDiff(a.slice(prefix, n - suffix), b.slice(prefix, m - suffix), prefix);
|
|
46773
|
+
if (middle === null)
|
|
46774
|
+
return null;
|
|
46775
|
+
const ops = [];
|
|
46776
|
+
for (let i = 0;i < prefix; i++) {
|
|
46777
|
+
ops.push({ tag: "eq", beforeIdx: i, afterIdx: i, line: a[i] });
|
|
46778
|
+
}
|
|
46779
|
+
for (const op of middle)
|
|
46780
|
+
ops.push(op);
|
|
46781
|
+
for (let i = 0;i < suffix; i++) {
|
|
46782
|
+
ops.push({
|
|
46783
|
+
tag: "eq",
|
|
46784
|
+
beforeIdx: n - suffix + i,
|
|
46785
|
+
afterIdx: m - suffix + i,
|
|
46786
|
+
line: a[n - suffix + i]
|
|
46787
|
+
});
|
|
46788
|
+
}
|
|
46789
|
+
return ops;
|
|
46790
|
+
}
|
|
46420
46791
|
diffTableCells += cells;
|
|
46421
46792
|
const inner = Math.min(innerN, innerM) < 65535 ? new Uint16Array(cells) : new Uint32Array(cells);
|
|
46422
46793
|
for (let i = 1;i <= innerN; i++) {
|
|
@@ -46459,6 +46830,103 @@ function diffLines(a, b) {
|
|
|
46459
46830
|
ops.reverse();
|
|
46460
46831
|
return ops;
|
|
46461
46832
|
}
|
|
46833
|
+
function myersDiff(a, b, baseIndex) {
|
|
46834
|
+
const n = a.length;
|
|
46835
|
+
const m = b.length;
|
|
46836
|
+
const max = n + m;
|
|
46837
|
+
const maxDepth = Math.min(max, Math.floor((Math.sqrt(1 + 8 * MYERS_TRACE_CELL_BUDGET) - 1) / 2));
|
|
46838
|
+
const offset = maxDepth + 1;
|
|
46839
|
+
const frontier = new Int32Array(2 * maxDepth + 3);
|
|
46840
|
+
frontier[offset + 1] = 0;
|
|
46841
|
+
const trace = [];
|
|
46842
|
+
let traceCells = 0;
|
|
46843
|
+
let work = 0;
|
|
46844
|
+
for (let d = 0;d <= maxDepth; d++) {
|
|
46845
|
+
let reachedEnd = false;
|
|
46846
|
+
for (let k = -d;k <= d; k += 2) {
|
|
46847
|
+
if (++work > MYERS_WORK_BUDGET)
|
|
46848
|
+
return null;
|
|
46849
|
+
let x;
|
|
46850
|
+
if (k === -d || k !== d && frontier[offset + k - 1] < frontier[offset + k + 1]) {
|
|
46851
|
+
x = frontier[offset + k + 1];
|
|
46852
|
+
} else {
|
|
46853
|
+
x = frontier[offset + k - 1] + 1;
|
|
46854
|
+
}
|
|
46855
|
+
let y = x - k;
|
|
46856
|
+
while (x < n && y < m && a[x] === b[y]) {
|
|
46857
|
+
if (++work > MYERS_WORK_BUDGET)
|
|
46858
|
+
return null;
|
|
46859
|
+
x++;
|
|
46860
|
+
y++;
|
|
46861
|
+
}
|
|
46862
|
+
frontier[offset + k] = x;
|
|
46863
|
+
if (x >= n && y >= m) {
|
|
46864
|
+
reachedEnd = true;
|
|
46865
|
+
break;
|
|
46866
|
+
}
|
|
46867
|
+
}
|
|
46868
|
+
if (reachedEnd) {
|
|
46869
|
+
const reversed = [];
|
|
46870
|
+
let x = n;
|
|
46871
|
+
let y = m;
|
|
46872
|
+
for (let depth = d;depth > 0; depth--) {
|
|
46873
|
+
const previous = trace[depth - 1];
|
|
46874
|
+
const k = x - y;
|
|
46875
|
+
const insert = k === -depth || k !== depth && previous[(k - 1 + depth - 1) / 2] < previous[(k + 1 + depth - 1) / 2];
|
|
46876
|
+
const previousK = insert ? k + 1 : k - 1;
|
|
46877
|
+
const previousX = previous[(previousK + depth - 1) / 2];
|
|
46878
|
+
const previousY = previousX - previousK;
|
|
46879
|
+
while (x > previousX && y > previousY) {
|
|
46880
|
+
reversed.push({
|
|
46881
|
+
tag: "eq",
|
|
46882
|
+
beforeIdx: baseIndex + x - 1,
|
|
46883
|
+
afterIdx: baseIndex + y - 1,
|
|
46884
|
+
line: a[x - 1]
|
|
46885
|
+
});
|
|
46886
|
+
x--;
|
|
46887
|
+
y--;
|
|
46888
|
+
}
|
|
46889
|
+
if (x === previousX) {
|
|
46890
|
+
reversed.push({ tag: "ins", afterIdx: baseIndex + y - 1, line: b[y - 1] });
|
|
46891
|
+
y--;
|
|
46892
|
+
} else {
|
|
46893
|
+
reversed.push({ tag: "del", beforeIdx: baseIndex + x - 1, line: a[x - 1] });
|
|
46894
|
+
x--;
|
|
46895
|
+
}
|
|
46896
|
+
}
|
|
46897
|
+
while (x > 0 && y > 0 && a[x - 1] === b[y - 1]) {
|
|
46898
|
+
reversed.push({
|
|
46899
|
+
tag: "eq",
|
|
46900
|
+
beforeIdx: baseIndex + x - 1,
|
|
46901
|
+
afterIdx: baseIndex + y - 1,
|
|
46902
|
+
line: a[x - 1]
|
|
46903
|
+
});
|
|
46904
|
+
x--;
|
|
46905
|
+
y--;
|
|
46906
|
+
}
|
|
46907
|
+
while (x > 0) {
|
|
46908
|
+
reversed.push({ tag: "del", beforeIdx: baseIndex + x - 1, line: a[x - 1] });
|
|
46909
|
+
x--;
|
|
46910
|
+
}
|
|
46911
|
+
while (y > 0) {
|
|
46912
|
+
reversed.push({ tag: "ins", afterIdx: baseIndex + y - 1, line: b[y - 1] });
|
|
46913
|
+
y--;
|
|
46914
|
+
}
|
|
46915
|
+
reversed.reverse();
|
|
46916
|
+
return reversed;
|
|
46917
|
+
}
|
|
46918
|
+
const snapshotSize = d + 1;
|
|
46919
|
+
if (traceCells + snapshotSize > MYERS_TRACE_CELL_BUDGET)
|
|
46920
|
+
return null;
|
|
46921
|
+
const snapshot = new Array(snapshotSize);
|
|
46922
|
+
let index = 0;
|
|
46923
|
+
for (let k = -d;k <= d; k += 2)
|
|
46924
|
+
snapshot[index++] = frontier[offset + k];
|
|
46925
|
+
trace.push(snapshot);
|
|
46926
|
+
traceCells += snapshotSize;
|
|
46927
|
+
}
|
|
46928
|
+
return null;
|
|
46929
|
+
}
|
|
46462
46930
|
function groupIntoHunks(ops, context, gap, beforeLen, afterLen) {
|
|
46463
46931
|
const changeIdx = [];
|
|
46464
46932
|
for (let k = 0;k < ops.length; k++) {
|
|
@@ -46559,6 +47027,9 @@ Examples:
|
|
|
46559
47027
|
Read 30 lines from line 200: { "path": "src/app.ts", "offset": 200, "limit": 30 }
|
|
46560
47028
|
List directory: { "path": "src/" }
|
|
46561
47029
|
`;
|
|
47030
|
+
function v1FileParameterDescription(description) {
|
|
47031
|
+
return description.replaceAll("`path`", "`filePath`").replaceAll('"path":', '"filePath":');
|
|
47032
|
+
}
|
|
46562
47033
|
function readDescription(ghReadEnabled) {
|
|
46563
47034
|
const githubDescription = whenGhReadEnabled(ghReadEnabled, ISSUE_AND_PR_READ_DESCRIPTION);
|
|
46564
47035
|
if (!githubDescription)
|
|
@@ -46569,16 +47040,38 @@ ${githubDescription}
|
|
|
46569
47040
|
|
|
46570
47041
|
Examples:`);
|
|
46571
47042
|
}
|
|
47043
|
+
function withRevisionNote(description) {
|
|
47044
|
+
if (description.includes(`
|
|
47045
|
+
Examples:`)) {
|
|
47046
|
+
return description.replace(`
|
|
47047
|
+
Examples:`, `
|
|
47048
|
+
${REVISION_REF_TOOL_NOTE}
|
|
47049
|
+
|
|
47050
|
+
Examples:`);
|
|
47051
|
+
}
|
|
47052
|
+
return `${description}
|
|
47053
|
+
|
|
47054
|
+
${REVISION_REF_TOOL_NOTE}`;
|
|
47055
|
+
}
|
|
47056
|
+
var revisionRefArgSchema = {
|
|
47057
|
+
ref: z7.string().optional().describe(REVISION_REF_DESCRIPTION)
|
|
47058
|
+
};
|
|
46572
47059
|
function createReadTool(ctx) {
|
|
47060
|
+
const baseDescription = v1FileParameterDescription(readDescription(resolveGithubConfig(ctx.config).read));
|
|
47061
|
+
const baseArgs = {
|
|
47062
|
+
filePath: z7.string().describe("Path to file or directory (absolute or relative to project root)"),
|
|
47063
|
+
startLine: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based line or directory entry to start reading from"),
|
|
47064
|
+
endLine: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based line or directory entry to stop reading at (inclusive)"),
|
|
47065
|
+
limit: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("Max lines (default: 2000) or directory entries (default and cap: 1000) to return"),
|
|
47066
|
+
offset: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based line or directory entry to start reading from (use with limit). Ignored if startLine is provided")
|
|
47067
|
+
};
|
|
46573
47068
|
return prepareToolMap({
|
|
46574
47069
|
read: {
|
|
46575
|
-
description
|
|
46576
|
-
|
|
46577
|
-
|
|
46578
|
-
|
|
46579
|
-
|
|
46580
|
-
limit: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("Max lines (default: 2000) or directory entries (default and cap: 1000) to return"),
|
|
46581
|
-
offset: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("1-based line or directory entry to start reading from (use with limit). Ignored if startLine is provided")
|
|
47070
|
+
get description() {
|
|
47071
|
+
return revisionReadsOffered(ctx.config) ? withRevisionNote(baseDescription) : baseDescription;
|
|
47072
|
+
},
|
|
47073
|
+
get args() {
|
|
47074
|
+
return revisionReadsOffered(ctx.config) ? { ...baseArgs, ...revisionRefArgSchema } : baseArgs;
|
|
46582
47075
|
},
|
|
46583
47076
|
execute: async (args, context) => {
|
|
46584
47077
|
const file = args.path;
|
|
@@ -46616,6 +47109,9 @@ function createReadTool(ctx) {
|
|
|
46616
47109
|
rawArgs.offset = offset;
|
|
46617
47110
|
if (limit !== undefined)
|
|
46618
47111
|
rawArgs.limit = limit;
|
|
47112
|
+
const ref = revisionRefArg(args.ref);
|
|
47113
|
+
if (ref !== undefined)
|
|
47114
|
+
rawArgs.ref = ref;
|
|
46619
47115
|
const mediaCandidate = isGithubResourcePath(file) || /\.(png|jpe?g|gif|webp|pdf)$/i.test(filePath) && await stat2(filePath).then((info) => info.isFile(), () => false);
|
|
46620
47116
|
const visionCapability = mediaCandidate ? await measurePreStage("capability", () => currentSessionVisionCapability(ctx.client, context.sessionID)) : undefined;
|
|
46621
47117
|
if (visionCapability !== undefined)
|
|
@@ -46664,11 +47160,11 @@ function githubWriteDisabledError(tool2, address) {
|
|
|
46664
47160
|
function getWriteDescription(ctx, editToolName) {
|
|
46665
47161
|
const backupText = ctx.config.backup?.enabled === false ? "Backup capture is disabled by user config." : "Existing files are backed up before overwriting (undo via aft_safety).";
|
|
46666
47162
|
const githubText = resolveGithubConfig(ctx.config).write ? ' When enabled, `write("issue://N", content)` or `write("pr://N", content)` publishes a new comment.' : "";
|
|
46667
|
-
return `Write content to a file, creating it and parent directories automatically. ${backupText} Auto-formats when the project has a formatter configured. Use it to create files or replace whole contents; for partial edits, use the \`${editToolName}\` tool. Write very large new files in parts: a single call can exceed the model's output limit and is then aborted with empty arguments.${githubText}`;
|
|
47163
|
+
return `Write content to a file, creating it and parent directories automatically. Use \`path\` for the file location. ${backupText} Auto-formats when the project has a formatter configured. Use it to create files or replace whole contents; for partial edits, use the \`${editToolName}\` tool. Write very large new files in parts: a single call can exceed the model's output limit and is then aborted with empty arguments.${githubText}`;
|
|
46668
47164
|
}
|
|
46669
47165
|
function createWriteTool(ctx, editToolName = "edit") {
|
|
46670
47166
|
return {
|
|
46671
|
-
description: getWriteDescription(ctx, editToolName),
|
|
47167
|
+
description: v1FileParameterDescription(getWriteDescription(ctx, editToolName)),
|
|
46672
47168
|
args: {
|
|
46673
47169
|
filePath: z7.string().describe("Path to the file to write (absolute or relative to project root)"),
|
|
46674
47170
|
content: z7.string().describe("The full content to write to the file; for issue://N / pr://N, the comment body to post")
|
|
@@ -46862,7 +47358,7 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
46862
47358
|
if (ctx.hashlineEffective === true)
|
|
46863
47359
|
return createHashlineEditTool(ctx);
|
|
46864
47360
|
return {
|
|
46865
|
-
description: getEditDescription(ctx, writeToolName),
|
|
47361
|
+
description: v1FileParameterDescription(getEditDescription(ctx, writeToolName)),
|
|
46866
47362
|
args: {
|
|
46867
47363
|
filePath: z7.string().describe("Path to the file to edit (absolute or relative to project root)"),
|
|
46868
47364
|
symbol: z7.string().optional().describe("Named symbol to replace; only meaningful together with `content`. A call carrying one without the other is incomplete."),
|
|
@@ -47623,28 +48119,14 @@ var MAX_OUTLINE_READ_ROUNDS = 8;
|
|
|
47623
48119
|
function isGithubPathArg(value) {
|
|
47624
48120
|
return typeof value === "string" && (value.startsWith("issue://") || value.startsWith("pr://"));
|
|
47625
48121
|
}
|
|
48122
|
+
function isUrl(value) {
|
|
48123
|
+
return value.startsWith("http://") || value.startsWith("https://");
|
|
48124
|
+
}
|
|
47626
48125
|
function buildZoomTitle(args) {
|
|
47627
|
-
|
|
47628
|
-
|
|
47629
|
-
|
|
47630
|
-
|
|
47631
|
-
return `${targets[0].path}#${targets[0].symbol}`;
|
|
47632
|
-
}
|
|
47633
|
-
return `${targets.length} targets across files`;
|
|
47634
|
-
}
|
|
47635
|
-
if (targets && typeof targets === "object") {
|
|
47636
|
-
return `${targets.path}#${targets.symbol}`;
|
|
47637
|
-
}
|
|
47638
|
-
}
|
|
47639
|
-
const path = args.path ?? args.url ?? "";
|
|
47640
|
-
if (typeof args.symbols === "string")
|
|
47641
|
-
return path ? `${path}#${args.symbols}` : args.symbols;
|
|
47642
|
-
if (Array.isArray(args.symbols) && args.symbols.length > 0) {
|
|
47643
|
-
if (args.symbols.length === 1)
|
|
47644
|
-
return path ? `${path}#${args.symbols[0]}` : args.symbols[0];
|
|
47645
|
-
return path ? `${path} (${args.symbols.length} symbols)` : `${args.symbols.length} symbols`;
|
|
47646
|
-
}
|
|
47647
|
-
return path || "(no target)";
|
|
48126
|
+
if (args.batch.length !== 1)
|
|
48127
|
+
return `${args.batch.length} locations`;
|
|
48128
|
+
const entry = args.batch[0];
|
|
48129
|
+
return `${entry.target}#${Array.isArray(entry.symbols) ? entry.symbols.join(", ") : entry.symbols}`;
|
|
47648
48130
|
}
|
|
47649
48131
|
function outlineFocusSteer(config) {
|
|
47650
48132
|
const zoomEnabled = toolEnabled(config, "aft_zoom");
|
|
@@ -47657,31 +48139,53 @@ function readingTools(ctx) {
|
|
|
47657
48139
|
const zoomEnabled = toolEnabled(ctx.config, "aft_zoom");
|
|
47658
48140
|
const ghReadEnabled = resolveGithubConfig(ctx.config).read;
|
|
47659
48141
|
const githubOutlineDescription = whenGhReadEnabled(ghReadEnabled, "GitHub issues and pull requests can be outlined with `issue://NUMBER` or `pr://NUMBER` (including `OWNER/REPO` forms).");
|
|
47660
|
-
const githubZoomDescription = whenGhReadEnabled(ghReadEnabled, "
|
|
47661
|
-
const
|
|
47662
|
-
aft_outline: {
|
|
47663
|
-
description: "Structural outline of source code, documentation files, or remote URLs. For code, returns symbols (functions, classes, types) with line ranges. For Markdown and HTML, returns heading hierarchy. Use this to explore structure before reading specific sections with " + (zoomEnabled ? "aft_zoom" : "read") + ". With `files: true`, the outline is breadth-first with directory rollups; drill in by outlining a subdirectory. Rows show language, symbol count, and line count.\n\n" + outlineFocusSteer(ctx.config) + `
|
|
48142
|
+
const githubZoomDescription = whenGhReadEnabled(ghReadEnabled, "Batch targets may use `issue://NUMBER` or `pr://NUMBER` (including `OWNER/REPO` forms) to inspect GitHub discussion ordinals.");
|
|
48143
|
+
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.\n\n" + outlineFocusSteer(ctx.config) + `
|
|
47664
48144
|
|
|
47665
48145
|
` + "Pass a single `target`:\n" + ` • file path → outline that file (with signatures)
|
|
47666
48146
|
` + ` • directory path → outline source files under it
|
|
47667
48147
|
` + ` • URL (http:// or https://) → fetch and outline a remote HTML/Markdown document
|
|
47668
48148
|
` + " • array of paths → outline multiple files in one call; with files:true, every path must be a directory" + (githubOutlineDescription ? `
|
|
47669
48149
|
|
|
47670
|
-
${githubOutlineDescription}` : "") + "\n\nWhen 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."
|
|
47671
|
-
|
|
47672
|
-
|
|
47673
|
-
|
|
47674
|
-
|
|
48150
|
+
${githubOutlineDescription}` : "") + "\n\nWhen 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.";
|
|
48151
|
+
const outlineArgs = {
|
|
48152
|
+
target: z11.union([z11.string(), z11.array(z11.string())]).describe("What to outline: a file path, directory path, URL, or array of paths. The mode is auto-detected: URLs by `http://`/`https://` prefix, directories by stat, arrays as multi-file."),
|
|
48153
|
+
files: z11.boolean().optional().describe("Directory-only mode: when true, target must be a directory or array of directories and the result is a breadth-first file tree with directory rollups plus language, symbol, and line counts."),
|
|
48154
|
+
includeTests: z11.boolean().optional().describe("Directory outline only: include test files. Defaults to false; tests are hidden.")
|
|
48155
|
+
};
|
|
48156
|
+
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 ? `
|
|
48157
|
+
|
|
48158
|
+
${githubZoomDescription}` : "");
|
|
48159
|
+
const zoomArgs = {
|
|
48160
|
+
batch: z11.array(z11.object({
|
|
48161
|
+
target: z11.string().describe("File path (absolute or project-relative) or HTTP/HTTPS URL, auto-detected; issue://N or pr://N when GitHub reads are enabled"),
|
|
48162
|
+
symbols: z11.union([z11.string(), z11.array(z11.string())]).describe("Symbol name for code or heading text for Markdown/HTML; a string or array of strings")
|
|
48163
|
+
})).min(1).describe("One or more lookups; entries may mix file paths and URLs"),
|
|
48164
|
+
contextLines: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("Lines of context before/after each symbol (default: 3)"),
|
|
48165
|
+
callgraph: z11.boolean().optional().describe("Include call-graph annotations for every entry (default: false)")
|
|
48166
|
+
};
|
|
48167
|
+
const offered = () => revisionReadsOffered(ctx.config);
|
|
48168
|
+
const tools = prepareToolMap({
|
|
48169
|
+
aft_outline: {
|
|
48170
|
+
get description() {
|
|
48171
|
+
return offered() ? `${outlineDescription}
|
|
48172
|
+
|
|
48173
|
+
${REVISION_REF_TOOL_NOTE} Only a single file can be outlined at a revision.` : outlineDescription;
|
|
48174
|
+
},
|
|
48175
|
+
get args() {
|
|
48176
|
+
return offered() ? { ...outlineArgs, ...revisionRefArgSchema } : outlineArgs;
|
|
47675
48177
|
},
|
|
47676
48178
|
execute: async (args, context) => {
|
|
47677
48179
|
const target = coerceTargetParam(args.target);
|
|
47678
48180
|
const filesMode = coerceBoolean(args.files);
|
|
47679
48181
|
const hasIncludeTests = !isEmptyParam(args.includeTests);
|
|
47680
48182
|
const includeTests = coerceBoolean(args.includeTests);
|
|
48183
|
+
const ref = revisionRefArg(args.ref);
|
|
47681
48184
|
const rawArgs = {
|
|
47682
48185
|
target,
|
|
47683
48186
|
...filesMode ? { files: true } : {},
|
|
47684
|
-
...hasIncludeTests ? { includeTests } : {}
|
|
48187
|
+
...hasIncludeTests ? { includeTests } : {},
|
|
48188
|
+
...ref !== undefined ? { ref } : {}
|
|
47685
48189
|
};
|
|
47686
48190
|
const checkedReads = new Map;
|
|
47687
48191
|
let directoryOutline = filesMode;
|
|
@@ -47753,152 +48257,73 @@ ${excluded.size} files left out by read rules.` : "");
|
|
|
47753
48257
|
}
|
|
47754
48258
|
},
|
|
47755
48259
|
aft_zoom: {
|
|
47756
|
-
description
|
|
48260
|
+
get description() {
|
|
48261
|
+
return offered() ? `${zoomDescription}
|
|
47757
48262
|
|
|
47758
|
-
${
|
|
47759
|
-
|
|
47760
|
-
|
|
47761
|
-
|
|
47762
|
-
symbols: z11.union([z11.string(), z11.array(z11.string())]).optional().describe("Symbol name for code, or heading text for Markdown/HTML. Pass a string for one lookup or an array for batched lookups in the same file/URL."),
|
|
47763
|
-
targets: z11.union([
|
|
47764
|
-
z11.object({
|
|
47765
|
-
path: z11.string().describe("Path to file (absolute or relative to project root)"),
|
|
47766
|
-
symbol: z11.string().describe("Symbol name in that file")
|
|
47767
|
-
}),
|
|
47768
|
-
z11.array(z11.object({
|
|
47769
|
-
path: z11.string().describe("Path to file (absolute or relative to project root)"),
|
|
47770
|
-
symbol: z11.string().describe("Symbol name in that file")
|
|
47771
|
-
}))
|
|
47772
|
-
]).optional().describe("Cross-file batch: `{ path, symbol }` or an array of them. May be combined with path/url + symbols; all lookups are answered in one batch."),
|
|
47773
|
-
contextLines: optionalInt(1, Number.MAX_SAFE_INTEGER).describe("Lines of context before/after the symbol (default: 3)"),
|
|
47774
|
-
callgraph: z11.boolean().optional().describe("Include call-graph annotations (calls-out / called-by within the same file). Default false; off keeps zoom output minimal.")
|
|
48263
|
+
${REVISION_REF_TOOL_NOTE} Every file of the call is read at that revision; URLs do not have revisions.` : zoomDescription;
|
|
48264
|
+
},
|
|
48265
|
+
get args() {
|
|
48266
|
+
return offered() ? { ...zoomArgs, ...revisionRefArgSchema } : zoomArgs;
|
|
47775
48267
|
},
|
|
47776
48268
|
execute: async (args, context) => {
|
|
47777
|
-
|
|
47778
|
-
|
|
47779
|
-
const
|
|
47780
|
-
|
|
47781
|
-
|
|
47782
|
-
const
|
|
47783
|
-
const
|
|
47784
|
-
const
|
|
47785
|
-
const
|
|
47786
|
-
|
|
47787
|
-
|
|
48269
|
+
if (!Array.isArray(args.batch) || args.batch.length === 0)
|
|
48270
|
+
throw new Error("'batch' must be a non-empty array of {target, symbols}");
|
|
48271
|
+
const ref = revisionRefArg(args.ref);
|
|
48272
|
+
if (ref !== undefined && args.batch.some((entry) => isUrl(entry.target)))
|
|
48273
|
+
throw new Error("'ref' cannot be used with a URL target: a URL has no revision");
|
|
48274
|
+
const localPaths = args.batch.map((entry) => entry.target).filter((target) => target && !isGithubPathArg(target) && !isUrl(target));
|
|
48275
|
+
const resolved = await Promise.all(localPaths.map((target) => resolvePathArg(ctx, context, target)));
|
|
48276
|
+
const resolvedByInput = new Map(localPaths.map((input, index) => [input, resolved[index]]));
|
|
48277
|
+
const deniedPaths = new Map;
|
|
48278
|
+
const distinctFiles = [...new Set(resolved)];
|
|
48279
|
+
if (context.hostVersion === 2) {
|
|
48280
|
+
const outcomes = await checkReadPaths(context, distinctFiles, (file, batchContext) => assertPathReadPermissions(ctx, batchContext, file));
|
|
48281
|
+
for (const [file, denial] of outcomes)
|
|
48282
|
+
if (denial)
|
|
48283
|
+
deniedPaths.set(file, denial);
|
|
48284
|
+
} else {
|
|
48285
|
+
for (const file of distinctFiles) {
|
|
48286
|
+
const denial = await assertPathReadPermissions(ctx, context, file);
|
|
48287
|
+
if (denial)
|
|
48288
|
+
return permissionDeniedResponse(denial);
|
|
48289
|
+
}
|
|
48290
|
+
}
|
|
48291
|
+
const allowed = args.batch.filter((entry) => {
|
|
48292
|
+
const file = resolvedByInput.get(entry.target);
|
|
48293
|
+
return file === undefined || !deniedPaths.has(file);
|
|
47788
48294
|
});
|
|
47789
|
-
const
|
|
47790
|
-
|
|
47791
|
-
|
|
47792
|
-
|
|
47793
|
-
|
|
47794
|
-
|
|
47795
|
-
|
|
47796
|
-
|
|
47797
|
-
|
|
47798
|
-
|
|
48295
|
+
const deniedText = args.batch.flatMap((entry) => {
|
|
48296
|
+
const file = resolvedByInput.get(entry.target);
|
|
48297
|
+
const denial = file === undefined ? undefined : deniedPaths.get(file);
|
|
48298
|
+
return denial ? [`${entry.target}: ${denial}`] : [];
|
|
48299
|
+
}).join(`
|
|
48300
|
+
`);
|
|
48301
|
+
const rawArgs = { batch: allowed };
|
|
48302
|
+
if (ref !== undefined)
|
|
48303
|
+
rawArgs.ref = ref;
|
|
48304
|
+
const contextLines = coerceOptionalInt(args.contextLines, "contextLines", 1, Number.MAX_SAFE_INTEGER);
|
|
47799
48305
|
if (contextLines !== undefined)
|
|
47800
|
-
|
|
47801
|
-
if (
|
|
47802
|
-
|
|
48306
|
+
rawArgs.contextLines = contextLines;
|
|
48307
|
+
if (coerceBoolean(args.callgraph))
|
|
48308
|
+
rawArgs.callgraph = true;
|
|
48309
|
+
const title = buildZoomTitle({ batch: args.batch });
|
|
47803
48310
|
const withMeta = (output) => ({
|
|
47804
48311
|
output,
|
|
47805
|
-
title
|
|
47806
|
-
metadata:
|
|
48312
|
+
title,
|
|
48313
|
+
metadata: { title, ...rawArgs, batch: JSON.stringify(args.batch) }
|
|
47807
48314
|
});
|
|
47808
|
-
if (
|
|
47809
|
-
|
|
47810
|
-
|
|
47811
|
-
|
|
47812
|
-
|
|
47813
|
-
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
47814
|
-
throw new Error(`targets[${i}].path must be a non-empty string`);
|
|
47815
|
-
}
|
|
47816
|
-
const record = entry;
|
|
47817
|
-
const targetPath = isBlankParam(record.path) ? record.filePath : record.path;
|
|
47818
|
-
return {
|
|
47819
|
-
path: typeof targetPath === "string" ? targetPath : "",
|
|
47820
|
-
symbol: typeof record.symbol === "string" ? record.symbol : ""
|
|
47821
|
-
};
|
|
47822
|
-
});
|
|
47823
|
-
const localPaths = targets.map((target) => target.path).filter((path) => path.trim().length > 0 && !isGithubPathArg(path));
|
|
47824
|
-
if (hasFilePath && !isGithubPathArg(args.path))
|
|
47825
|
-
localPaths.push(args.path);
|
|
47826
|
-
const resolvedTargets = await Promise.all(localPaths.map((path) => resolvePathArg(ctx, context, path)));
|
|
47827
|
-
const deniedPaths = new Map;
|
|
47828
|
-
const resolvedByInput = new Map(localPaths.map((input, index) => [input, resolvedTargets[index]]));
|
|
47829
|
-
const isDenied = (input) => {
|
|
47830
|
-
const resolved = resolvedByInput.get(input);
|
|
47831
|
-
return resolved !== undefined && deniedPaths.has(resolved);
|
|
47832
|
-
};
|
|
47833
|
-
const distinctFiles = [...new Set(resolvedTargets)];
|
|
47834
|
-
if (context.hostVersion === 2) {
|
|
47835
|
-
const outcomes = await checkReadPaths(context, distinctFiles, (file, batchContext) => assertPathReadPermissions(ctx, batchContext, file));
|
|
47836
|
-
for (const [file, denial] of outcomes)
|
|
47837
|
-
if (denial)
|
|
47838
|
-
deniedPaths.set(file, denial);
|
|
47839
|
-
} else {
|
|
47840
|
-
for (const file of distinctFiles) {
|
|
47841
|
-
const denial = await assertPathReadPermissions(ctx, context, file);
|
|
47842
|
-
if (denial)
|
|
47843
|
-
return permissionDeniedResponse(denial);
|
|
47844
|
-
}
|
|
47845
|
-
}
|
|
47846
|
-
const rawArgs = {
|
|
47847
|
-
targets: targets.filter((target) => !isDenied(target.path)).map((target) => ({
|
|
47848
|
-
filePath: target.path,
|
|
47849
|
-
symbol: target.symbol
|
|
47850
|
-
}))
|
|
47851
|
-
};
|
|
47852
|
-
const deniedPrimary = hasFilePath && isDenied(args.path);
|
|
47853
|
-
if (!deniedPrimary) {
|
|
47854
|
-
if (hasFilePath)
|
|
47855
|
-
rawArgs.filePath = args.path;
|
|
47856
|
-
else if (hasUrl)
|
|
47857
|
-
rawArgs.url = args.url;
|
|
47858
|
-
}
|
|
47859
|
-
if (hasSymbols && !deniedPrimary)
|
|
47860
|
-
rawArgs.symbols = args.symbols;
|
|
47861
|
-
if (contextLines !== undefined)
|
|
47862
|
-
rawArgs.contextLines = contextLines;
|
|
47863
|
-
if (wantCallgraph)
|
|
47864
|
-
rawArgs.callgraph = true;
|
|
47865
|
-
const deniedText = [...deniedPaths].map(([file, denial]) => `${file}: ${denial}`).join(`
|
|
47866
|
-
`);
|
|
47867
|
-
if (!rawArgs.targets.length && !(hasFilePath && !deniedPrimary) && !hasUrl)
|
|
47868
|
-
return withMeta(deniedText);
|
|
47869
|
-
const response = await callToolCall(ctx, context, "zoom", rawArgs);
|
|
47870
|
-
if (response.success === false) {
|
|
47871
|
-
throw new Error(response.text || response.message || "zoom failed");
|
|
47872
|
-
}
|
|
47873
|
-
return withMeta([response.text, deniedText].filter(Boolean).join(`
|
|
47874
|
-
|
|
47875
|
-
`));
|
|
47876
|
-
}
|
|
47877
|
-
if (!hasFilePath && !hasUrl) {
|
|
47878
|
-
throw new Error("Provide exactly one of 'path', 'url', or 'targets'");
|
|
47879
|
-
}
|
|
47880
|
-
if (hasFilePath && hasUrl) {
|
|
47881
|
-
throw new Error("Provide exactly ONE of 'path' or 'url' — not both");
|
|
47882
|
-
}
|
|
47883
|
-
const githubPath = hasFilePath && isGithubPathArg(args.path) || hasUrl && isGithubPathArg(args.url);
|
|
47884
|
-
if (!hasUrl && !githubPath) {
|
|
47885
|
-
const file = await resolvePathArg(ctx, context, args.path);
|
|
47886
|
-
const permissionDenied = await assertPathReadPermissions(ctx, context, file);
|
|
47887
|
-
if (permissionDenied)
|
|
47888
|
-
return permissionDeniedResponse(permissionDenied);
|
|
48315
|
+
if (!allowed.length) {
|
|
48316
|
+
const denial = deniedPaths.values().next().value;
|
|
48317
|
+
if (args.batch.length === 1 && denial)
|
|
48318
|
+
return permissionDeniedResponse(denial);
|
|
48319
|
+
return withMeta(deniedText);
|
|
47889
48320
|
}
|
|
47890
|
-
const rawArgs = hasUrl ? { url: args.url } : { filePath: args.path };
|
|
47891
|
-
if (hasSymbols)
|
|
47892
|
-
rawArgs.symbols = args.symbols;
|
|
47893
|
-
if (contextLines !== undefined)
|
|
47894
|
-
rawArgs.contextLines = contextLines;
|
|
47895
|
-
if (wantCallgraph)
|
|
47896
|
-
rawArgs.callgraph = true;
|
|
47897
48321
|
const response = await callToolCall(ctx, context, "zoom", rawArgs);
|
|
47898
|
-
if (response.success === false)
|
|
48322
|
+
if (response.success === false)
|
|
47899
48323
|
throw new Error(response.text || response.message || "zoom failed");
|
|
47900
|
-
|
|
47901
|
-
|
|
48324
|
+
return withMeta([response.text, deniedText].filter(Boolean).join(`
|
|
48325
|
+
|
|
48326
|
+
`));
|
|
47902
48327
|
}
|
|
47903
48328
|
}
|
|
47904
48329
|
});
|
|
@@ -47984,6 +48409,7 @@ async function assertPathReadPermissions(ctx, context, target, kind = "file", ch
|
|
|
47984
48409
|
}
|
|
47985
48410
|
|
|
47986
48411
|
// src/tools/safety.ts
|
|
48412
|
+
import * as fs5 from "node:fs";
|
|
47987
48413
|
import * as path5 from "node:path";
|
|
47988
48414
|
var z12 = tool.schema;
|
|
47989
48415
|
function responsePaths(response) {
|
|
@@ -48015,22 +48441,23 @@ function safetyTools(ctx) {
|
|
|
48015
48441
|
` + `Ops:
|
|
48016
48442
|
` + `- 'undo': Undo the entire last tool call when 'path' is omitted (typical), or undo the last edit to one file when 'path' is provided. Note: pops from the undo stack (irreversible, no redo). Use 'history' to inspect per-file history before undoing.
|
|
48017
48443
|
` + `- 'history': List all edit snapshots for a file. Requires 'path'.
|
|
48018
|
-
` + `- 'checkpoint': Save a named snapshot. Explicit
|
|
48444
|
+
` + `- 'checkpoint': Save a named snapshot (on disk across restarts; expires in 14 days; may contain secrets). Explicit files may be untracked/gitignored; omit for backup-tracked files. Requires 'name'.
|
|
48019
48445
|
` + `- 'restore': Restore files to a previously saved checkpoint. Optional 'path'/'files' scopes the restore; omitted scope restores the whole checkpoint. Requires 'name'.
|
|
48020
48446
|
` + `- 'list': List all available named checkpoints. No extra params needed.
|
|
48447
|
+
` + `- 'purge': Permanently delete this session's undo backups and checkpoint copies of 'path' (a file, or everything under a directory); cannot be undone.
|
|
48021
48448
|
|
|
48022
48449
|
` + `Each op requires specific parameters — see parameter descriptions for requirements.
|
|
48023
48450
|
|
|
48024
48451
|
` + "Use checkpoint before risky multi-file changes. Use undo for quick single-file rollback.",
|
|
48025
48452
|
args: {
|
|
48026
|
-
op: z12.enum(["undo", "history", "checkpoint", "restore", "list"]).describe("Safety operation"),
|
|
48027
|
-
path: z12.string().optional().describe("File path (required for history, optional for undo, optional for checkpoint — snapshots only that file when files is omitted, optional for restore — restores only that file). Absolute or relative to project root"),
|
|
48453
|
+
op: z12.enum(["undo", "history", "checkpoint", "restore", "list", "purge"]).describe("Safety operation"),
|
|
48454
|
+
path: z12.string().optional().describe("File path (required for history, optional for undo, optional for checkpoint — snapshots only that file when files is omitted, optional for restore — restores only that file, required for purge — a file or directory). Absolute or relative to project root"),
|
|
48028
48455
|
name: z12.string().optional().describe("Checkpoint name (required for checkpoint, restore)"),
|
|
48029
48456
|
files: z12.array(z12.string()).optional().describe("Specific files to include in checkpoint, or to restore from a checkpoint (optional; restore without path/files restores the whole checkpoint; explicit checkpoint files may be untracked or gitignored)")
|
|
48030
48457
|
},
|
|
48031
48458
|
execute: async (args, context) => {
|
|
48032
48459
|
const op = args.op;
|
|
48033
|
-
if (op === "history" && typeof args.path !== "string") {
|
|
48460
|
+
if ((op === "history" || op === "purge") && typeof args.path !== "string") {
|
|
48034
48461
|
throw new Error(`'path' is required for '${op}' op`);
|
|
48035
48462
|
}
|
|
48036
48463
|
if ((op === "checkpoint" || op === "restore") && typeof args.name !== "string") {
|
|
@@ -48100,6 +48527,42 @@ function safetyTools(ctx) {
|
|
|
48100
48527
|
if (permissionError)
|
|
48101
48528
|
return permissionDeniedResponse(permissionError);
|
|
48102
48529
|
}
|
|
48530
|
+
if (op === "purge") {
|
|
48531
|
+
const absolutePath = resolveAbsolutePath(context, args.path);
|
|
48532
|
+
let isDirectory = false;
|
|
48533
|
+
try {
|
|
48534
|
+
isDirectory = fs5.statSync(absolutePath).isDirectory();
|
|
48535
|
+
} catch {}
|
|
48536
|
+
const denial = await assertExternalDirectoryPermission(ctx, context, absolutePath, {
|
|
48537
|
+
kind: isDirectory ? "directory" : "file"
|
|
48538
|
+
});
|
|
48539
|
+
if (denial)
|
|
48540
|
+
return permissionDeniedResponse(denial);
|
|
48541
|
+
const preview = await callBridge(ctx, context, "safety_purge_paths", {
|
|
48542
|
+
file: expandTilde2(args.path)
|
|
48543
|
+
});
|
|
48544
|
+
if (preview.success === false) {
|
|
48545
|
+
throw new Error(bridgeErrorMessage(preview, "purge path preview failed"));
|
|
48546
|
+
}
|
|
48547
|
+
const previewPaths = Array.from(new Set(responsePaths(preview)));
|
|
48548
|
+
for (const savedPath of previewPaths) {
|
|
48549
|
+
const previewDenial = await assertExternalDirectoryPermission(ctx, context, savedPath);
|
|
48550
|
+
if (previewDenial)
|
|
48551
|
+
return permissionDeniedResponse(previewDenial);
|
|
48552
|
+
}
|
|
48553
|
+
const pattern = resolveRelativePatternFromAbsolute(context, absolutePath);
|
|
48554
|
+
const patterns = isDirectory ? [pattern, path5.join(pattern, "*")] : [pattern];
|
|
48555
|
+
for (const previewPattern of relativePatternsFromPaths(context, previewPaths)) {
|
|
48556
|
+
if (!patterns.includes(previewPattern))
|
|
48557
|
+
patterns.push(previewPattern);
|
|
48558
|
+
}
|
|
48559
|
+
const permissionError = await askEditPermission(context, patterns, {
|
|
48560
|
+
filepath: absolutePath,
|
|
48561
|
+
operation: "purge"
|
|
48562
|
+
});
|
|
48563
|
+
if (permissionError)
|
|
48564
|
+
return permissionDeniedResponse(permissionError);
|
|
48565
|
+
}
|
|
48103
48566
|
const rawArgs = { op };
|
|
48104
48567
|
if (args.name !== undefined)
|
|
48105
48568
|
rawArgs.name = args.name;
|
|
@@ -48120,7 +48583,7 @@ function safetyTools(ctx) {
|
|
|
48120
48583
|
}
|
|
48121
48584
|
|
|
48122
48585
|
// src/tools/search.ts
|
|
48123
|
-
import * as
|
|
48586
|
+
import * as fs6 from "node:fs";
|
|
48124
48587
|
import * as path6 from "node:path";
|
|
48125
48588
|
function arg2(schema) {
|
|
48126
48589
|
return schema;
|
|
@@ -48129,7 +48592,7 @@ function absoluteSearchPath(projectRoot, target) {
|
|
|
48129
48592
|
return resolvePathFromProjectRoot(projectRoot, expandTilde2(target));
|
|
48130
48593
|
}
|
|
48131
48594
|
function searchPathExists(projectRoot, target) {
|
|
48132
|
-
return
|
|
48595
|
+
return fs6.existsSync(absoluteSearchPath(projectRoot, target));
|
|
48133
48596
|
}
|
|
48134
48597
|
function splitSearchPathArg(projectRoot, raw) {
|
|
48135
48598
|
if (searchPathExists(projectRoot, raw) || !/\s/.test(raw)) {
|
|
@@ -48172,7 +48635,7 @@ ${note}` : note;
|
|
|
48172
48635
|
}
|
|
48173
48636
|
function searchPathKind(projectRoot, target, defaultKind) {
|
|
48174
48637
|
try {
|
|
48175
|
-
const stat =
|
|
48638
|
+
const stat = fs6.lstatSync(absoluteSearchPath(projectRoot, target));
|
|
48176
48639
|
if (defaultKind === "file") {
|
|
48177
48640
|
return stat.isDirectory() ? "directory" : "file";
|
|
48178
48641
|
}
|
|
@@ -48379,7 +48842,7 @@ function semanticTools(ctx) {
|
|
|
48379
48842
|
topK: arg3(optionalInt(1, 50).describe("Number of results (default: 10, max: 50)")),
|
|
48380
48843
|
offset: arg3(optionalInt(0, 1e5).describe("Zero-based result offset (default: 0, max: 100000).")),
|
|
48381
48844
|
includeTests: arg3(z13.boolean().optional().describe("Include test files (*.test.*, *_test.rs, __tests__/, …) plus test-support, fixture, mock, snapshot, and corpus files. Defaults to false.")),
|
|
48382
|
-
path: arg3(z13.string().optional().describe("Only set this to search a different
|
|
48845
|
+
path: arg3(z13.string().optional().describe("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."))
|
|
48383
48846
|
},
|
|
48384
48847
|
execute: async (args, context) => {
|
|
48385
48848
|
const hasQuery = typeof args.query === "string" && args.query.trim().length > 0;
|
|
@@ -48974,7 +49437,7 @@ function startOpenCodeLiveConfigReload(options) {
|
|
|
48974
49437
|
// src/notifications.ts
|
|
48975
49438
|
import { existsSync as existsSync17, readFileSync as readFileSync14 } from "node:fs";
|
|
48976
49439
|
import { homedir as homedir14, platform } from "node:os";
|
|
48977
|
-
import { join as
|
|
49440
|
+
import { join as join23 } from "node:path";
|
|
48978
49441
|
function isTuiMode() {
|
|
48979
49442
|
return process.env.OPENCODE_CLIENT === "cli";
|
|
48980
49443
|
}
|
|
@@ -48996,15 +49459,15 @@ function getDesktopStatePath() {
|
|
|
48996
49459
|
const os = platform();
|
|
48997
49460
|
const home = homedir14();
|
|
48998
49461
|
if (os === "darwin") {
|
|
48999
|
-
return
|
|
49462
|
+
return join23(home, "Library", "Application Support", "ai.opencode.desktop", "opencode.global.dat");
|
|
49000
49463
|
}
|
|
49001
49464
|
if (os === "linux") {
|
|
49002
|
-
const xdgConfig = process.env.XDG_CONFIG_HOME ||
|
|
49003
|
-
return
|
|
49465
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME || join23(home, ".config");
|
|
49466
|
+
return join23(xdgConfig, "ai.opencode.desktop", "opencode.global.dat");
|
|
49004
49467
|
}
|
|
49005
49468
|
if (os === "win32") {
|
|
49006
|
-
const appData = process.env.APPDATA ||
|
|
49007
|
-
return
|
|
49469
|
+
const appData = process.env.APPDATA || join23(home, "AppData", "Roaming");
|
|
49470
|
+
return join23(appData, "ai.opencode.desktop", "opencode.global.dat");
|
|
49008
49471
|
}
|
|
49009
49472
|
return null;
|
|
49010
49473
|
}
|
|
@@ -49522,7 +49985,7 @@ import {
|
|
|
49522
49985
|
readFileSync as readFileSync17,
|
|
49523
49986
|
renameSync as renameSync10,
|
|
49524
49987
|
rmSync as rmSync8,
|
|
49525
|
-
statSync as
|
|
49988
|
+
statSync as statSync17,
|
|
49526
49989
|
writeFileSync as writeFileSync11
|
|
49527
49990
|
} from "node:fs";
|
|
49528
49991
|
import { dirname as dirname16 } from "node:path";
|
|
@@ -49530,30 +49993,30 @@ import { dirname as dirname16 } from "node:path";
|
|
|
49530
49993
|
// src/hooks/auto-update-checker/cache.ts
|
|
49531
49994
|
import { cpSync, existsSync as existsSync19, mkdtempSync, readFileSync as readFileSync16, rmSync as rmSync7, writeFileSync as writeFileSync10 } from "node:fs";
|
|
49532
49995
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
49533
|
-
import { basename as basename6, dirname as dirname15, join as
|
|
49996
|
+
import { basename as basename6, dirname as dirname15, join as join26 } from "node:path";
|
|
49534
49997
|
var import_comment_json3 = __toESM(require_src2(), 1);
|
|
49535
49998
|
|
|
49536
49999
|
// src/hooks/auto-update-checker/checker.ts
|
|
49537
|
-
import { existsSync as existsSync18, readFileSync as readFileSync15, statSync as
|
|
50000
|
+
import { existsSync as existsSync18, readFileSync as readFileSync15, statSync as statSync16, writeFileSync as writeFileSync9 } from "node:fs";
|
|
49538
50001
|
import { readFile } from "node:fs/promises";
|
|
49539
50002
|
import { homedir as homedir15 } from "node:os";
|
|
49540
|
-
import { dirname as dirname14, isAbsolute as isAbsolute13, join as
|
|
50003
|
+
import { dirname as dirname14, isAbsolute as isAbsolute13, join as join25, resolve as resolve15 } from "node:path";
|
|
49541
50004
|
import { fileURLToPath } from "node:url";
|
|
49542
50005
|
var import_comment_json2 = __toESM(require_src2(), 1);
|
|
49543
50006
|
|
|
49544
50007
|
// src/hooks/auto-update-checker/constants.ts
|
|
49545
|
-
import { join as
|
|
50008
|
+
import { join as join24 } from "node:path";
|
|
49546
50009
|
var PACKAGE_NAME = "@cortexkit/aft-opencode";
|
|
49547
50010
|
var NPM_REGISTRY_URL = "https://registry.npmjs.org";
|
|
49548
50011
|
var NPM_FETCH_TIMEOUT = 1e4;
|
|
49549
50012
|
function cacheDir() {
|
|
49550
|
-
return
|
|
50013
|
+
return join24(getOpenCodeCacheRoot(), "packages");
|
|
49551
50014
|
}
|
|
49552
50015
|
function userOpenCodeConfig() {
|
|
49553
|
-
return
|
|
50016
|
+
return join24(getOpenCodeConfigRoot(), "opencode.json");
|
|
49554
50017
|
}
|
|
49555
50018
|
function userOpenCodeConfigJsonc() {
|
|
49556
|
-
return
|
|
50019
|
+
return join24(getOpenCodeConfigRoot(), "opencode.jsonc");
|
|
49557
50020
|
}
|
|
49558
50021
|
|
|
49559
50022
|
// src/hooks/auto-update-checker/types.ts
|
|
@@ -49612,8 +50075,8 @@ function extractChannel(version) {
|
|
|
49612
50075
|
}
|
|
49613
50076
|
function getConfigPaths(directory) {
|
|
49614
50077
|
return [
|
|
49615
|
-
|
|
49616
|
-
|
|
50078
|
+
join25(directory, ".opencode", "opencode.json"),
|
|
50079
|
+
join25(directory, ".opencode", "opencode.jsonc"),
|
|
49617
50080
|
userOpenCodeConfig(),
|
|
49618
50081
|
userOpenCodeConfigJsonc()
|
|
49619
50082
|
];
|
|
@@ -49656,10 +50119,10 @@ function getLocalDevPath(directory) {
|
|
|
49656
50119
|
}
|
|
49657
50120
|
function findPackageJsonUp(startPath) {
|
|
49658
50121
|
try {
|
|
49659
|
-
const stat =
|
|
50122
|
+
const stat = statSync16(startPath);
|
|
49660
50123
|
let dir = stat.isDirectory() ? startPath : dirname14(startPath);
|
|
49661
50124
|
for (let i = 0;i < 10; i++) {
|
|
49662
|
-
const pkgPath =
|
|
50125
|
+
const pkgPath = join25(dir, "package.json");
|
|
49663
50126
|
if (existsSync18(pkgPath)) {
|
|
49664
50127
|
try {
|
|
49665
50128
|
const pkg = PackageJsonSchema.safeParse(JSON.parse(readFileSync15(pkgPath, "utf-8")));
|
|
@@ -49725,7 +50188,7 @@ function findPluginEntry(directory) {
|
|
|
49725
50188
|
}
|
|
49726
50189
|
var cachedPackageVersion = null;
|
|
49727
50190
|
function getSpecCachePackageJsonPath(spec) {
|
|
49728
|
-
return
|
|
50191
|
+
return join25(cacheDir(), spec, "node_modules", PACKAGE_NAME, "package.json");
|
|
49729
50192
|
}
|
|
49730
50193
|
function getCachedVersion(spec) {
|
|
49731
50194
|
if (!spec && cachedPackageVersion)
|
|
@@ -49734,7 +50197,7 @@ function getCachedVersion(spec) {
|
|
|
49734
50197
|
getCurrentRuntimePackageJsonPath(),
|
|
49735
50198
|
spec ? getSpecCachePackageJsonPath(spec) : null,
|
|
49736
50199
|
getSpecCachePackageJsonPath(`${PACKAGE_NAME}@latest`),
|
|
49737
|
-
|
|
50200
|
+
join25(getOpenCodeCacheRoot(), "node_modules", PACKAGE_NAME, "package.json")
|
|
49738
50201
|
].filter(isString);
|
|
49739
50202
|
for (const packageJsonPath of candidates) {
|
|
49740
50203
|
try {
|
|
@@ -49805,7 +50268,7 @@ async function registryWithoutNpm(directory) {
|
|
|
49805
50268
|
for (let current = directory;; current = dirname14(current)) {
|
|
49806
50269
|
if (scoped)
|
|
49807
50270
|
break;
|
|
49808
|
-
const path =
|
|
50271
|
+
const path = join25(current, ".npmrc");
|
|
49809
50272
|
visited.add(path);
|
|
49810
50273
|
const settings = await readRegistrySettings(path);
|
|
49811
50274
|
scoped ??= settings["@cortexkit:registry"];
|
|
@@ -49815,7 +50278,7 @@ async function registryWithoutNpm(directory) {
|
|
|
49815
50278
|
}
|
|
49816
50279
|
const explicitUserConfig = process.env.npm_config_userconfig ?? process.env.NPM_CONFIG_USERCONFIG;
|
|
49817
50280
|
const home = (process.platform === "win32" ? process.env.USERPROFILE : process.env.HOME) ?? homedir15();
|
|
49818
|
-
const userConfig = explicitUserConfig ??
|
|
50281
|
+
const userConfig = explicitUserConfig ?? join25(home, ".npmrc");
|
|
49819
50282
|
if (!scoped && !visited.has(userConfig)) {
|
|
49820
50283
|
const settings = await readRegistrySettings(userConfig, explicitUserConfig !== undefined);
|
|
49821
50284
|
scoped = settings["@cortexkit:registry"];
|
|
@@ -49901,10 +50364,10 @@ async function getLatestVersion(channel = "latest", options = {}) {
|
|
|
49901
50364
|
var pendingSnapshots = new Map;
|
|
49902
50365
|
var quarantinedInstallDirs = new Set;
|
|
49903
50366
|
function createAutoUpdateSnapshot(installDir, packageJsonPath, packageName) {
|
|
49904
|
-
const packageDir =
|
|
49905
|
-
const lockfilePath =
|
|
49906
|
-
const tempDir = mkdtempSync(
|
|
49907
|
-
const stagedPackageDir = existsSync19(packageDir) ?
|
|
50367
|
+
const packageDir = join26(installDir, "node_modules", packageName);
|
|
50368
|
+
const lockfilePath = join26(installDir, "package-lock.json");
|
|
50369
|
+
const tempDir = mkdtempSync(join26(tmpdir5(), "aft-auto-update-"));
|
|
50370
|
+
const stagedPackageDir = existsSync19(packageDir) ? join26(tempDir, "package") : null;
|
|
49908
50371
|
if (stagedPackageDir)
|
|
49909
50372
|
cpSync(packageDir, stagedPackageDir, { recursive: true });
|
|
49910
50373
|
return {
|
|
@@ -49945,7 +50408,7 @@ function stripPackageNameFromPath(pathValue, packageName) {
|
|
|
49945
50408
|
return current;
|
|
49946
50409
|
}
|
|
49947
50410
|
function removeFromPackageLock(installDir, packageName) {
|
|
49948
|
-
const lockPath =
|
|
50411
|
+
const lockPath = join26(installDir, "package-lock.json");
|
|
49949
50412
|
if (!existsSync19(lockPath))
|
|
49950
50413
|
return false;
|
|
49951
50414
|
try {
|
|
@@ -50001,7 +50464,7 @@ function restorePendingSnapshot(installDir) {
|
|
|
50001
50464
|
restoreAutoUpdateSnapshot(snapshot);
|
|
50002
50465
|
}
|
|
50003
50466
|
function removeInstalledPackage(installDir, packageName) {
|
|
50004
|
-
const packageDir =
|
|
50467
|
+
const packageDir = join26(installDir, "node_modules", packageName);
|
|
50005
50468
|
if (!existsSync19(packageDir))
|
|
50006
50469
|
return false;
|
|
50007
50470
|
rmSync7(packageDir, { recursive: true, force: true });
|
|
@@ -50014,14 +50477,14 @@ function resolveInstallContext(runtimePackageJsonPath = getCurrentRuntimePackage
|
|
|
50014
50477
|
const nodeModulesDir = stripPackageNameFromPath(packageDir, PACKAGE_NAME);
|
|
50015
50478
|
if (nodeModulesDir && basename6(nodeModulesDir) === "node_modules") {
|
|
50016
50479
|
const installDir = dirname15(nodeModulesDir);
|
|
50017
|
-
const packageJsonPath =
|
|
50480
|
+
const packageJsonPath = join26(installDir, "package.json");
|
|
50018
50481
|
if (existsSync19(packageJsonPath))
|
|
50019
50482
|
return { installDir, packageJsonPath };
|
|
50020
50483
|
}
|
|
50021
50484
|
return null;
|
|
50022
50485
|
}
|
|
50023
50486
|
const cacheRoot = dirname15(cacheDir());
|
|
50024
|
-
const legacyPackageJsonPath =
|
|
50487
|
+
const legacyPackageJsonPath = join26(cacheRoot, "package.json");
|
|
50025
50488
|
if (existsSync19(legacyPackageJsonPath)) {
|
|
50026
50489
|
return { installDir: cacheRoot, packageJsonPath: legacyPackageJsonPath };
|
|
50027
50490
|
}
|
|
@@ -50294,7 +50757,7 @@ function isOrphanedCheckLock(lockPath) {
|
|
|
50294
50757
|
}
|
|
50295
50758
|
}
|
|
50296
50759
|
function readCheckLockState(lockPath) {
|
|
50297
|
-
const fallbackStartedMs =
|
|
50760
|
+
const fallbackStartedMs = statSync17(lockPath).mtimeMs;
|
|
50298
50761
|
try {
|
|
50299
50762
|
const parsed = JSON.parse(readFileSync17(lockPath, "utf-8"));
|
|
50300
50763
|
const pid = Number.isSafeInteger(parsed.pid) && (parsed.pid ?? 0) > 0 ? parsed.pid ?? null : null;
|
|
@@ -50535,16 +50998,16 @@ import {
|
|
|
50535
50998
|
writeFileSync as writeFileSync12
|
|
50536
50999
|
} from "node:fs";
|
|
50537
51000
|
import { createServer } from "node:http";
|
|
50538
|
-
import { dirname as dirname17, join as
|
|
51001
|
+
import { dirname as dirname17, join as join28 } from "node:path";
|
|
50539
51002
|
|
|
50540
51003
|
// src/shared/rpc-utils.ts
|
|
50541
|
-
import { join as
|
|
51004
|
+
import { join as join27 } from "node:path";
|
|
50542
51005
|
function projectHash(directory) {
|
|
50543
51006
|
return projectRootKeyHash(directory);
|
|
50544
51007
|
}
|
|
50545
51008
|
function rpcPortFileDir(storageDir, directory) {
|
|
50546
51009
|
const hash = projectHash(directory);
|
|
50547
|
-
return
|
|
51010
|
+
return join27(storageDir, "rpc", hash, "ports");
|
|
50548
51011
|
}
|
|
50549
51012
|
function isPidAlive(pid) {
|
|
50550
51013
|
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0)
|
|
@@ -50618,7 +51081,7 @@ class AftRpcServer {
|
|
|
50618
51081
|
constructor(storageDir, directory) {
|
|
50619
51082
|
this.portsDir = rpcPortFileDir(storageDir, directory);
|
|
50620
51083
|
this.instanceId = randomBytes5(8).toString("hex");
|
|
50621
|
-
this.portFilePath =
|
|
51084
|
+
this.portFilePath = join28(this.portsDir, `${this.instanceId}.json`);
|
|
50622
51085
|
}
|
|
50623
51086
|
handle(method, handler) {
|
|
50624
51087
|
this.handlers.set(method, handler);
|
|
@@ -50706,7 +51169,7 @@ class AftRpcServer {
|
|
|
50706
51169
|
for (const entry of entries) {
|
|
50707
51170
|
if (!entry.endsWith(".json"))
|
|
50708
51171
|
continue;
|
|
50709
|
-
const filePath =
|
|
51172
|
+
const filePath = join28(this.portsDir, entry);
|
|
50710
51173
|
if (filePath === this.portFilePath)
|
|
50711
51174
|
continue;
|
|
50712
51175
|
try {
|
|
@@ -51335,7 +51798,7 @@ function buildWorkflowHints(opts) {
|
|
|
51335
51798
|
`));
|
|
51336
51799
|
}
|
|
51337
51800
|
if (hasOutline && hasZoom) {
|
|
51338
|
-
sections.push(`**Web/URL access**: \`aft_outline({ target: url })\` first for structure, then \`aft_zoom({ url, symbols: "<heading>" })\` for the specific section.`);
|
|
51801
|
+
sections.push(`**Web/URL access**: \`aft_outline({ target: url })\` first for structure, then \`aft_zoom({ batch: [{ target: url, symbols: "<heading>" }] })\` for the specific section.`);
|
|
51339
51802
|
}
|
|
51340
51803
|
if (hasOutline && (hasGrep || hasSearch) && (hasZoom || hasRead)) {
|
|
51341
51804
|
const searchName = hasSearch ? "aft_search" : grepName;
|
|
@@ -51452,13 +51915,13 @@ function throwSentinel(command) {
|
|
|
51452
51915
|
throw sentinel;
|
|
51453
51916
|
}
|
|
51454
51917
|
var PLUGIN_VERSION = resolvePluginVersion(import.meta.url);
|
|
51455
|
-
var ANNOUNCEMENT_VERSION = "0.
|
|
51918
|
+
var ANNOUNCEMENT_VERSION = "0.62.0";
|
|
51456
51919
|
var ANNOUNCEMENT_FEATURES = [
|
|
51457
|
-
"
|
|
51458
|
-
"
|
|
51459
|
-
"
|
|
51460
|
-
"
|
|
51461
|
-
"
|
|
51920
|
+
"Linux dev containers work again: a project at a bind-mount target such as `/workspace` is no longer refused as too broad.",
|
|
51921
|
+
"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`).",
|
|
51922
|
+
"`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.",
|
|
51923
|
+
"`aft_zoom` takes one `batch` of lookups mixing files and URLs, and OpenCode shows diffs for edits to files over 5,000 lines.",
|
|
51924
|
+
"Idle OpenCode 2 sessions wake again when a background command finishes, and many reported issues are fixed."
|
|
51462
51925
|
];
|
|
51463
51926
|
var ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/DSa65w8wuf";
|
|
51464
51927
|
var plugin = async (input) => initializePluginForDirectory(input);
|
|
@@ -51951,7 +52414,7 @@ Install: ${getManualInstallHint()}`);
|
|
|
51951
52414
|
var src_default = plugin;
|
|
51952
52415
|
|
|
51953
52416
|
// src/entry/server-runtime.mjs
|
|
51954
|
-
import { Effect as
|
|
52417
|
+
import { Effect as Effect15 } from "effect";
|
|
51955
52418
|
|
|
51956
52419
|
// src/rpc/register.ts
|
|
51957
52420
|
import { Effect as Effect8 } from "effect";
|
|
@@ -52220,7 +52683,7 @@ class V2SessionDelivery {
|
|
|
52220
52683
|
return Effect9.suspend(() => {
|
|
52221
52684
|
if (this.disposed)
|
|
52222
52685
|
return Effect9.succeed(false);
|
|
52223
|
-
const keys = [...new Set(input.taskIDs)].map((taskID) => `${input.sessionID}\x00${taskID}`);
|
|
52686
|
+
const keys = [...new Set(input.taskIDs)].map((taskID) => `${input.sessionID}\x00${taskID}\x00${input.notificationID ?? "completion"}`);
|
|
52224
52687
|
if (keys.length === 0 || keys.every((key) => this.admittedCompletions.has(key))) {
|
|
52225
52688
|
return Effect9.succeed(false);
|
|
52226
52689
|
}
|
|
@@ -52379,6 +52842,11 @@ function registerV2ToolHooks(context, runtime, registeredTools, bashDescription)
|
|
|
52379
52842
|
return Effect11.gen(function* () {
|
|
52380
52843
|
if (typeof host.tool?.hook === "function") {
|
|
52381
52844
|
yield* host.tool.hook("execute.after", (event) => Effect11.promise(async () => {
|
|
52845
|
+
if (runtime.drainBgNotifications && event.status === "completed" && !hasHeldBgCompletions(event.sessionID)) {
|
|
52846
|
+
await runtime.drainBgNotifications(event.sessionID);
|
|
52847
|
+
if (!hasHeldBgCompletions(event.sessionID))
|
|
52848
|
+
return;
|
|
52849
|
+
}
|
|
52382
52850
|
if (!runtime.pool || event.status !== "completed" || !hasHeldBgCompletions(event.sessionID))
|
|
52383
52851
|
return;
|
|
52384
52852
|
const content = event.result.content;
|
|
@@ -52527,6 +52995,17 @@ function holdUnrouted(completion, bridge) {
|
|
|
52527
52995
|
tasks.set(completion.task_id, { completion, bridge });
|
|
52528
52996
|
unrouted.set(completion.session_id, tasks);
|
|
52529
52997
|
}
|
|
52998
|
+
function forgetUnroutedCompletion(sessionID, taskID) {
|
|
52999
|
+
const state = wakeRouteState();
|
|
53000
|
+
const held = state.unrouted.get(sessionID);
|
|
53001
|
+
if (!held)
|
|
53002
|
+
return;
|
|
53003
|
+
held.delete(taskID);
|
|
53004
|
+
if (held.size === 0) {
|
|
53005
|
+
state.unrouted.delete(sessionID);
|
|
53006
|
+
state.warnedUnrouted.delete(sessionID);
|
|
53007
|
+
}
|
|
53008
|
+
}
|
|
52530
53009
|
function warnUnroutedOnce(sessionID, message) {
|
|
52531
53010
|
const { warnedUnrouted } = wakeRouteState();
|
|
52532
53011
|
if (warnedUnrouted.has(sessionID))
|
|
@@ -52534,20 +53013,25 @@ function warnUnroutedOnce(sessionID, message) {
|
|
|
52534
53013
|
warnedUnrouted.add(sessionID);
|
|
52535
53014
|
warn2(message);
|
|
52536
53015
|
}
|
|
52537
|
-
async function routeCompletion(completion, bridge) {
|
|
53016
|
+
async function routeCompletion(completion, bridge, acknowledge = true) {
|
|
52538
53017
|
const route = wakeRouteState().routes.get(completion.session_id);
|
|
52539
53018
|
if (route) {
|
|
52540
53019
|
try {
|
|
52541
|
-
await route.completion(completion, bridge);
|
|
52542
|
-
|
|
53020
|
+
const admitted = await route.completion(completion, bridge, acknowledge) === true;
|
|
53021
|
+
if (admitted)
|
|
53022
|
+
forgetUnroutedCompletion(completion.session_id, completion.task_id);
|
|
53023
|
+
return admitted;
|
|
52543
53024
|
} catch (error) {
|
|
52544
|
-
|
|
53025
|
+
if (acknowledge)
|
|
53026
|
+
holdUnrouted(completion, bridge);
|
|
52545
53027
|
warn2(`[bash_completion] delivery of task ${completion.task_id} to session ${completion.session_id} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
52546
|
-
return;
|
|
53028
|
+
return false;
|
|
52547
53029
|
}
|
|
52548
53030
|
}
|
|
52549
|
-
|
|
53031
|
+
if (acknowledge)
|
|
53032
|
+
holdUnrouted(completion, bridge);
|
|
52550
53033
|
warnUnroutedOnce(completion.session_id, `[bash_completion] no OpenCode Location has session ${completion.session_id} registered; completion of task ${completion.task_id} is held unacknowledged until the session registers`);
|
|
53034
|
+
return false;
|
|
52551
53035
|
}
|
|
52552
53036
|
async function routeLongRunning(reminder, _bridge) {
|
|
52553
53037
|
const route = wakeRouteState().routes.get(reminder.session_id);
|
|
@@ -52555,7 +53039,7 @@ async function routeLongRunning(reminder, _bridge) {
|
|
|
52555
53039
|
return route.longRunning(reminder);
|
|
52556
53040
|
warnUnroutedOnce(reminder.session_id, `[bash_long_running] no OpenCode Location has session ${reminder.session_id} registered; long-running reminder for task ${reminder.task_id} was not delivered`);
|
|
52557
53041
|
}
|
|
52558
|
-
function deliverUnrouted(sessionID) {
|
|
53042
|
+
function deliverUnrouted(sessionID, replacementBridge) {
|
|
52559
53043
|
const state = wakeRouteState();
|
|
52560
53044
|
state.warnedUnrouted.delete(sessionID);
|
|
52561
53045
|
const held = state.unrouted.get(sessionID);
|
|
@@ -52563,11 +53047,15 @@ function deliverUnrouted(sessionID) {
|
|
|
52563
53047
|
return;
|
|
52564
53048
|
state.unrouted.delete(sessionID);
|
|
52565
53049
|
for (const { completion, bridge } of held.values()) {
|
|
52566
|
-
|
|
53050
|
+
if (replacementBridge && bridge.getCwd() !== replacementBridge.getCwd())
|
|
53051
|
+
continue;
|
|
53052
|
+
routeCompletion(completion, replacementBridge ?? bridge);
|
|
52567
53053
|
}
|
|
52568
53054
|
}
|
|
52569
53055
|
var sharedBridgeOptions = {
|
|
52570
|
-
onBashCompletion:
|
|
53056
|
+
onBashCompletion: async (completion, bridge) => {
|
|
53057
|
+
await routeCompletion(completion, bridge);
|
|
53058
|
+
},
|
|
52571
53059
|
onBashLongRunning: routeLongRunning
|
|
52572
53060
|
};
|
|
52573
53061
|
function detachWaitsForCompletion(sessionID, text, bridge, config) {
|
|
@@ -52582,15 +53070,17 @@ function detachWaitsForCompletion(sessionID, text, bridge, config) {
|
|
|
52582
53070
|
function createV2RuntimeConsumer(context, getConfig = () => ({})) {
|
|
52583
53071
|
const delivery = new V2SessionDelivery(context.session);
|
|
52584
53072
|
const sessions = new Set;
|
|
53073
|
+
let disposed = false;
|
|
53074
|
+
let subcPool;
|
|
52585
53075
|
const route = {
|
|
52586
|
-
completion: async (completion, bridge) => {
|
|
53076
|
+
completion: async (completion, bridge, acknowledge = true) => {
|
|
52587
53077
|
if (await resolveIsSubagent(context, completion.session_id)) {
|
|
52588
53078
|
await awaitV2ExecutionSettlement(context, completion.session_id);
|
|
52589
53079
|
}
|
|
52590
53080
|
if (await resolveIsSubagent(context, completion.session_id) && !v2WatchWakeAllowed(context, completion.session_id, completion.task_id)) {
|
|
52591
53081
|
holdBgCompletion(completion.session_id, completion, v2SubagentStopped(context, completion.session_id));
|
|
52592
53082
|
forgetV2Watch(context, completion.session_id, completion.task_id);
|
|
52593
|
-
return;
|
|
53083
|
+
return false;
|
|
52594
53084
|
}
|
|
52595
53085
|
const text = formatSystemReminder([completion]);
|
|
52596
53086
|
const admitted = await Effect13.runPromise(delivery.completion({
|
|
@@ -52605,13 +53095,52 @@ function createV2RuntimeConsumer(context, getConfig = () => ({})) {
|
|
|
52605
53095
|
}
|
|
52606
53096
|
}));
|
|
52607
53097
|
if (!admitted)
|
|
52608
|
-
return;
|
|
53098
|
+
return false;
|
|
52609
53099
|
detachWaitsForCompletion(completion.session_id, text, bridge, getConfig());
|
|
52610
|
-
|
|
52611
|
-
|
|
52612
|
-
|
|
52613
|
-
|
|
53100
|
+
if (acknowledge) {
|
|
53101
|
+
if (subcPool) {
|
|
53102
|
+
consumeBgCompletion(completion.session_id, completion.task_id);
|
|
53103
|
+
await markBgCompletionDelivered({
|
|
53104
|
+
ctx: { pool: subcPool() },
|
|
53105
|
+
directory: bridge.getCwd(),
|
|
53106
|
+
sessionID: completion.session_id,
|
|
53107
|
+
resolvedBridge: bridge
|
|
53108
|
+
}, completion.task_id);
|
|
53109
|
+
} else {
|
|
53110
|
+
await bridge.send("bash_ack_completions", {
|
|
53111
|
+
session_id: completion.session_id,
|
|
53112
|
+
task_ids: [completion.task_id]
|
|
53113
|
+
});
|
|
53114
|
+
}
|
|
53115
|
+
}
|
|
52614
53116
|
forgetV2Watch(context, completion.session_id, completion.task_id);
|
|
53117
|
+
return true;
|
|
53118
|
+
},
|
|
53119
|
+
patternMatch: async (match, bridge) => {
|
|
53120
|
+
if (await resolveIsSubagent(context, match.session_id)) {
|
|
53121
|
+
await awaitV2ExecutionSettlement(context, match.session_id);
|
|
53122
|
+
if (!v2WatchWakeAllowed(context, match.session_id, match.task_id))
|
|
53123
|
+
return false;
|
|
53124
|
+
}
|
|
53125
|
+
const text = formatPatternMatchReminder([match]);
|
|
53126
|
+
const admitted = await Effect13.runPromise(delivery.completion({
|
|
53127
|
+
sessionID: match.session_id,
|
|
53128
|
+
taskIDs: [match.task_id],
|
|
53129
|
+
notificationID: `match\x00${match.watch_id}\x00${match.match_offset}`,
|
|
53130
|
+
text,
|
|
53131
|
+
metadata: {
|
|
53132
|
+
source: "aft",
|
|
53133
|
+
kind: "bash_pattern_match",
|
|
53134
|
+
task_ids: [match.task_id],
|
|
53135
|
+
watch_id: match.watch_id
|
|
53136
|
+
}
|
|
53137
|
+
}));
|
|
53138
|
+
if (admitted) {
|
|
53139
|
+
detachWaitsForCompletion(match.session_id, text, bridge, getConfig());
|
|
53140
|
+
if (match.reason === "task_exit")
|
|
53141
|
+
forgetUnroutedCompletion(match.session_id, match.task_id);
|
|
53142
|
+
}
|
|
53143
|
+
return admitted;
|
|
52615
53144
|
},
|
|
52616
53145
|
longRunning: (reminder) => Effect13.runPromise(delivery.status({
|
|
52617
53146
|
sessionID: reminder.session_id,
|
|
@@ -52624,23 +53153,74 @@ function createV2RuntimeConsumer(context, getConfig = () => ({})) {
|
|
|
52624
53153
|
}
|
|
52625
53154
|
}))
|
|
52626
53155
|
};
|
|
52627
|
-
const registerSession = (sessionID) => {
|
|
52628
|
-
if (!sessionID)
|
|
53156
|
+
const registerSession = (sessionID, bridge) => {
|
|
53157
|
+
if (!sessionID || disposed)
|
|
52629
53158
|
return;
|
|
52630
53159
|
sessions.add(sessionID);
|
|
52631
53160
|
const { routes } = wakeRouteState();
|
|
52632
53161
|
routes.set(sessionID, route);
|
|
52633
|
-
deliverUnrouted(sessionID);
|
|
53162
|
+
deliverUnrouted(sessionID, bridge);
|
|
52634
53163
|
};
|
|
52635
53164
|
return {
|
|
52636
53165
|
bridgeOptions: sharedBridgeOptions,
|
|
52637
53166
|
registerSession,
|
|
53167
|
+
subcWakes: (getPool, directory) => {
|
|
53168
|
+
subcPool = getPool;
|
|
53169
|
+
const root = canonicalizeProjectRoot(directory);
|
|
53170
|
+
const drain = (sessionID, nudgeRef, resolvedBridge) => {
|
|
53171
|
+
if (disposed)
|
|
53172
|
+
return Promise.resolve();
|
|
53173
|
+
return handleSubcBgEventsNudge({
|
|
53174
|
+
ctx: { pool: getPool() },
|
|
53175
|
+
directory: root,
|
|
53176
|
+
sessionID,
|
|
53177
|
+
client: context,
|
|
53178
|
+
nudgeRef,
|
|
53179
|
+
resolvedBridge,
|
|
53180
|
+
delivery: {
|
|
53181
|
+
completion: (completion, bridge) => routeCompletion({ ...completion, type: "bash_completed", session_id: sessionID }, bridge, false),
|
|
53182
|
+
patternMatch: async (match, bridge) => {
|
|
53183
|
+
if (match.session_id !== sessionID)
|
|
53184
|
+
return false;
|
|
53185
|
+
return await wakeRouteState().routes.get(sessionID)?.patternMatch?.(match, bridge) ?? false;
|
|
53186
|
+
}
|
|
53187
|
+
}
|
|
53188
|
+
});
|
|
53189
|
+
};
|
|
53190
|
+
return {
|
|
53191
|
+
handlers: {
|
|
53192
|
+
onBgEventsNudge: (projectRoot, sessionID) => {
|
|
53193
|
+
if (canonicalizeProjectRoot(projectRoot) === root)
|
|
53194
|
+
return drain(sessionID);
|
|
53195
|
+
},
|
|
53196
|
+
onBgEventsNudgeRef: (ref) => {
|
|
53197
|
+
if (ref.canonicalRoot === root)
|
|
53198
|
+
return drain(ref.session, ref);
|
|
53199
|
+
}
|
|
53200
|
+
},
|
|
53201
|
+
drainSession: (sessionID) => {
|
|
53202
|
+
if (disposed)
|
|
53203
|
+
return Promise.resolve();
|
|
53204
|
+
const bridge = getPool().getBridge(root);
|
|
53205
|
+
registerSession(sessionID, bridge);
|
|
53206
|
+
return drain(sessionID, undefined, bridge);
|
|
53207
|
+
},
|
|
53208
|
+
restoreSession: async (sessionID) => {
|
|
53209
|
+
if (disposed)
|
|
53210
|
+
return;
|
|
53211
|
+
const bridge = getPool().getBridge(root);
|
|
53212
|
+
registerSession(sessionID, bridge);
|
|
53213
|
+
await drain(sessionID, undefined, bridge);
|
|
53214
|
+
}
|
|
53215
|
+
};
|
|
53216
|
+
},
|
|
52638
53217
|
onToolAbort: (sessionID) => noteV2ToolAbort(context, sessionID),
|
|
52639
53218
|
executeBash: (execution) => {
|
|
52640
53219
|
registerSession(execution.context.sessionID);
|
|
52641
53220
|
return executeV2Bash(execution);
|
|
52642
53221
|
},
|
|
52643
53222
|
dispose: () => {
|
|
53223
|
+
disposed = true;
|
|
52644
53224
|
delivery.dispose();
|
|
52645
53225
|
const { routes } = wakeRouteState();
|
|
52646
53226
|
for (const sessionID of sessions) {
|
|
@@ -52652,6 +53232,34 @@ function createV2RuntimeConsumer(context, getConfig = () => ({})) {
|
|
|
52652
53232
|
};
|
|
52653
53233
|
}
|
|
52654
53234
|
|
|
53235
|
+
// src/wakes/subc-recovery.ts
|
|
53236
|
+
import { DateTime, Effect as Effect14 } from "effect";
|
|
53237
|
+
var RECENT_SESSION_LIMIT = 20;
|
|
53238
|
+
var RECENT_SESSION_WINDOW_MS = 24 * 60 * 60 * 1000;
|
|
53239
|
+
function recoverV2SubcSessions(context, directory, restoreSession) {
|
|
53240
|
+
const host = context;
|
|
53241
|
+
return Effect14.gen(function* () {
|
|
53242
|
+
if (typeof host.session?.list !== "function")
|
|
53243
|
+
return;
|
|
53244
|
+
yield* Effect14.sleep("10 millis");
|
|
53245
|
+
const sessions = yield* host.session.list({
|
|
53246
|
+
directory,
|
|
53247
|
+
order: "desc",
|
|
53248
|
+
limit: RECENT_SESSION_LIMIT
|
|
53249
|
+
});
|
|
53250
|
+
const cutoff = Date.now() - RECENT_SESSION_WINDOW_MS;
|
|
53251
|
+
for (const session of sessions.data.slice(0, RECENT_SESSION_LIMIT)) {
|
|
53252
|
+
if (session.location.directory !== directory || DateTime.toEpochMillis(session.time.updated) < cutoff)
|
|
53253
|
+
continue;
|
|
53254
|
+
yield* Effect14.tryPromise({
|
|
53255
|
+
try: () => restoreSession(session.id),
|
|
53256
|
+
catch: (error) => error
|
|
53257
|
+
}).pipe(Effect14.catch((error) => Effect14.sync(() => warn2(`[bash_completion] session recovery failed: ${String(error)}`))));
|
|
53258
|
+
yield* Effect14.sleep("10 millis");
|
|
53259
|
+
}
|
|
53260
|
+
}).pipe(Effect14.timeout("30 seconds"), Effect14.catch((error) => Effect14.sync(() => warn2(`[bash_completion] recent session discovery failed: ${String(error)}`))));
|
|
53261
|
+
}
|
|
53262
|
+
|
|
52655
53263
|
// src/entry/server-runtime.mjs
|
|
52656
53264
|
var defaults = {
|
|
52657
53265
|
...defaultBridgeBootstrapDependencies,
|
|
@@ -52692,6 +53300,7 @@ async function bootLocation(context, location, dependencies) {
|
|
|
52692
53300
|
let liveConfig = config;
|
|
52693
53301
|
const consumers = dependencies.toolConsumers(context, config, () => liveConfig);
|
|
52694
53302
|
const isProjectEnabled = createProjectAcceptance(directory, dependencies);
|
|
53303
|
+
const subcWakes = config.subc?.connection_file?.trim() ? consumers.subcWakes?.(() => pool, directory) : undefined;
|
|
52695
53304
|
const pool = await dependencies.acquireBridge(canonicalDirectory, {
|
|
52696
53305
|
harness: "opencode",
|
|
52697
53306
|
binaryPath: environment.binaryPath,
|
|
@@ -52707,7 +53316,8 @@ async function bootLocation(context, location, dependencies) {
|
|
|
52707
53316
|
...consumers.bridgeOptions
|
|
52708
53317
|
},
|
|
52709
53318
|
configOverrides: environment.configOverrides,
|
|
52710
|
-
subcConnectionFile: config.subc?.connection_file
|
|
53319
|
+
subcConnectionFile: config.subc?.connection_file,
|
|
53320
|
+
...subcWakes?.handlers
|
|
52711
53321
|
});
|
|
52712
53322
|
environment.attach(pool);
|
|
52713
53323
|
const toolContext = {
|
|
@@ -52717,7 +53327,8 @@ async function bootLocation(context, location, dependencies) {
|
|
|
52717
53327
|
hashlineEffective: openCodeHashlineEffective(config),
|
|
52718
53328
|
hostToolNames: V2_HOST_TOOL_NAMES,
|
|
52719
53329
|
storageDir: environment.storageDir,
|
|
52720
|
-
isProjectEnabled
|
|
53330
|
+
isProjectEnabled,
|
|
53331
|
+
drainBgNotifications: subcWakes?.drainSession
|
|
52721
53332
|
};
|
|
52722
53333
|
const tools = dependencies.buildToolMap(toolContext, config, unknownDisabledToolsReporter(notify));
|
|
52723
53334
|
const registeredTools = new Set(Object.keys(tools));
|
|
@@ -52737,7 +53348,7 @@ async function bootLocation(context, location, dependencies) {
|
|
|
52737
53348
|
},
|
|
52738
53349
|
notify
|
|
52739
53350
|
});
|
|
52740
|
-
return { consumers, pool, tools, liveConfigReload, toolContext, directory, hintsBlock };
|
|
53351
|
+
return { consumers, pool, tools, liveConfigReload, toolContext, directory, hintsBlock, subcWakes };
|
|
52741
53352
|
}
|
|
52742
53353
|
var runningByDirectory = new Map;
|
|
52743
53354
|
var startsInProcess = 0;
|
|
@@ -52764,30 +53375,30 @@ function makeServerEffect(overrides = {}) {
|
|
|
52764
53375
|
const location = context.location;
|
|
52765
53376
|
if (typeof location?.directory !== "string") {
|
|
52766
53377
|
debug2("V2 effect skipped: host context has no location");
|
|
52767
|
-
return
|
|
53378
|
+
return Effect15.void;
|
|
52768
53379
|
}
|
|
52769
53380
|
if (typeof context.permission?.assert !== "function") {
|
|
52770
53381
|
throw new Error("AFT requires OpenCode 2.0.29 or newer: this V2 host has no permission.assert. Upgrade OpenCode before loading AFT.");
|
|
52771
53382
|
}
|
|
52772
|
-
return
|
|
53383
|
+
return Effect15.gen(function* () {
|
|
52773
53384
|
const logRuntimeStop = logRuntimeStart(location);
|
|
52774
|
-
yield*
|
|
52775
|
-
const runtime = yield*
|
|
53385
|
+
yield* Effect15.addFinalizer(() => Effect15.sync(logRuntimeStop));
|
|
53386
|
+
const runtime = yield* Effect15.promise(() => bootLocation(context, location, dependencies));
|
|
52776
53387
|
if (!runtime)
|
|
52777
53388
|
return;
|
|
52778
53389
|
if (runtime.configError !== undefined) {
|
|
52779
53390
|
const rpc = yield* dependencies.registerConfigErrorRpc(context, runtime.configError);
|
|
52780
|
-
yield*
|
|
53391
|
+
yield* Effect15.addFinalizer(() => Effect15.promise(() => rpc.dispose()));
|
|
52781
53392
|
yield* dependencies.registerTools(context, location, runtime.tools, runtime.consumers);
|
|
52782
53393
|
return;
|
|
52783
53394
|
}
|
|
52784
|
-
yield*
|
|
53395
|
+
yield* Effect15.addFinalizer(() => Effect15.promise(async () => {
|
|
52785
53396
|
runtime.liveConfigReload?.stop();
|
|
52786
53397
|
runtime.consumers.dispose?.();
|
|
52787
53398
|
await dependencies.releaseBridge(runtime.pool);
|
|
52788
53399
|
}));
|
|
52789
53400
|
const rpc = yield* dependencies.registerRpc(context, location, runtime.pool);
|
|
52790
|
-
yield*
|
|
53401
|
+
yield* Effect15.addFinalizer(() => Effect15.promise(() => rpc.dispose()));
|
|
52791
53402
|
yield* registerV2SessionLifecycle(context, runtime.toolContext);
|
|
52792
53403
|
yield* dependencies.registerPromptHook(context, {
|
|
52793
53404
|
pool: runtime.pool,
|
|
@@ -52798,6 +53409,9 @@ function makeServerEffect(overrides = {}) {
|
|
|
52798
53409
|
yield* dependencies.registerTools(context, location, runtime.tools, runtime.consumers);
|
|
52799
53410
|
yield* dependencies.registerToolHooks(context, runtime.toolContext, new Set(Object.keys(runtime.tools)), runtime.tools.bash?.description);
|
|
52800
53411
|
yield* dependencies.registerWorkflowHints(context, runtime.hintsBlock);
|
|
53412
|
+
if (runtime.subcWakes) {
|
|
53413
|
+
yield* Effect15.forkScoped(recoverV2SubcSessions(context, runtime.directory, runtime.subcWakes.restoreSession));
|
|
53414
|
+
}
|
|
52801
53415
|
});
|
|
52802
53416
|
};
|
|
52803
53417
|
}
|