nexrall-code 0.5.2 → 0.5.18
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/index.js +1855 -615
- package/package.json +10 -10
package/dist/index.js
CHANGED
|
@@ -3070,6 +3070,27 @@ var require_types = __commonJS({
|
|
|
3070
3070
|
"../core/dist/types.js"(exports2) {
|
|
3071
3071
|
"use strict";
|
|
3072
3072
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
3073
|
+
exports2.AgentTurnError = void 0;
|
|
3074
|
+
exports2.isAgentTurnError = isAgentTurnError;
|
|
3075
|
+
exports2.salvageHistory = salvageHistory2;
|
|
3076
|
+
var AgentTurnError = class extends Error {
|
|
3077
|
+
constructor(message, messages, progressCount, cause) {
|
|
3078
|
+
super(message);
|
|
3079
|
+
this.name = "AgentTurnError";
|
|
3080
|
+
this.messages = messages;
|
|
3081
|
+
this.progressCount = Math.max(0, progressCount);
|
|
3082
|
+
this.cause = cause;
|
|
3083
|
+
}
|
|
3084
|
+
};
|
|
3085
|
+
exports2.AgentTurnError = AgentTurnError;
|
|
3086
|
+
function isAgentTurnError(err) {
|
|
3087
|
+
return err instanceof AgentTurnError;
|
|
3088
|
+
}
|
|
3089
|
+
function salvageHistory2(err) {
|
|
3090
|
+
if (!isAgentTurnError(err))
|
|
3091
|
+
return null;
|
|
3092
|
+
return err.progressCount > 0 ? err.messages : null;
|
|
3093
|
+
}
|
|
3073
3094
|
}
|
|
3074
3095
|
});
|
|
3075
3096
|
|
|
@@ -9810,14 +9831,49 @@ var require_client = __commonJS({
|
|
|
9810
9831
|
};
|
|
9811
9832
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
9812
9833
|
exports2.API_BASE = void 0;
|
|
9834
|
+
exports2.chooseFinalContent = chooseFinalContent;
|
|
9813
9835
|
exports2.streamChat = streamChat;
|
|
9836
|
+
exports2.cancelTurn = cancelTurn;
|
|
9814
9837
|
exports2.getBalance = getBalance3;
|
|
9815
9838
|
exports2.exchangeVscodeCode = exchangeVscodeCode;
|
|
9816
9839
|
exports2.login = login2;
|
|
9817
9840
|
var eventsource_parser_1 = require_dist();
|
|
9818
9841
|
var node_fetch_1 = __importDefault((init_src(), __toCommonJS(src_exports)));
|
|
9842
|
+
var crypto_1 = require("crypto");
|
|
9819
9843
|
var index_1 = require_auth();
|
|
9820
|
-
|
|
9844
|
+
var DEFAULT_API_BASE = "https://api.nexrall.com";
|
|
9845
|
+
function resolveApiBase() {
|
|
9846
|
+
const raw = process.env.NEXRALL_API_BASE?.trim();
|
|
9847
|
+
if (!raw)
|
|
9848
|
+
return DEFAULT_API_BASE;
|
|
9849
|
+
const isLoopback = /^http:\/\/(127\.0\.0\.1|\[::1\]|localhost)(:\d+)?(\/|$)/i.test(raw);
|
|
9850
|
+
if (isLoopback) {
|
|
9851
|
+
console.error(`\u26A0\uFE0F Nexrall API override in effect (loopback): ${raw.replace(/\/+$/, "")}`);
|
|
9852
|
+
return raw.replace(/\/+$/, "");
|
|
9853
|
+
}
|
|
9854
|
+
if (process.env.NEXRALL_ALLOW_API_OVERRIDE !== "1") {
|
|
9855
|
+
console.error(`\u26A0\uFE0F Ignoring NEXRALL_API_BASE="${raw}": redirecting to a remote host also requires NEXRALL_ALLOW_API_OVERRIDE=1, because this variable receives your auth token and source code. Falling back to ${DEFAULT_API_BASE}.`);
|
|
9856
|
+
return DEFAULT_API_BASE;
|
|
9857
|
+
}
|
|
9858
|
+
if (!/^https:\/\//i.test(raw)) {
|
|
9859
|
+
console.error(`\u26A0\uFE0F Ignoring NEXRALL_API_BASE="${raw}": a remote override must be https:// so your token is never sent in plaintext. Falling back to ${DEFAULT_API_BASE}.`);
|
|
9860
|
+
return DEFAULT_API_BASE;
|
|
9861
|
+
}
|
|
9862
|
+
const base = raw.replace(/\/+$/, "");
|
|
9863
|
+
console.error(`\u26A0\uFE0F Nexrall API override in effect: ${base}`);
|
|
9864
|
+
return base;
|
|
9865
|
+
}
|
|
9866
|
+
exports2.API_BASE = resolveApiBase();
|
|
9867
|
+
function chooseFinalContent(rebuilt, rawContent) {
|
|
9868
|
+
if (!Array.isArray(rawContent))
|
|
9869
|
+
return rebuilt;
|
|
9870
|
+
const hasServerSideBlocks = rawContent.some((b) => b && b.type !== "text" && b.type !== "tool_use");
|
|
9871
|
+
if (hasServerSideBlocks)
|
|
9872
|
+
return rawContent;
|
|
9873
|
+
if (rebuilt.length === 0 && rawContent.length > 0)
|
|
9874
|
+
return rawContent;
|
|
9875
|
+
return rebuilt;
|
|
9876
|
+
}
|
|
9821
9877
|
function authHeaders() {
|
|
9822
9878
|
const token = (0, index_1.getToken)();
|
|
9823
9879
|
if (!token) {
|
|
@@ -9828,103 +9884,265 @@ var require_client = __commonJS({
|
|
|
9828
9884
|
Authorization: `Bearer ${token}`
|
|
9829
9885
|
};
|
|
9830
9886
|
}
|
|
9831
|
-
var MAX_RETRIES =
|
|
9887
|
+
var MAX_RETRIES = 5;
|
|
9832
9888
|
var RETRY_BASE_MS = 1e3;
|
|
9889
|
+
var RETRY_MAX_MS = 3e4;
|
|
9890
|
+
var MAX_TOTAL_RETRY_MS = (() => {
|
|
9891
|
+
const raw = Number(process.env.NEXRALL_MAX_RETRY_MS);
|
|
9892
|
+
return Number.isFinite(raw) && raw > 0 ? raw : 5 * 6e4;
|
|
9893
|
+
})();
|
|
9894
|
+
function backoffMs(attempt) {
|
|
9895
|
+
const exp = Math.min(RETRY_MAX_MS, RETRY_BASE_MS * Math.pow(2, attempt));
|
|
9896
|
+
return Math.round(exp / 2 + Math.random() * (exp / 2));
|
|
9897
|
+
}
|
|
9833
9898
|
function sleep(ms) {
|
|
9834
9899
|
return new Promise((r2) => setTimeout(r2, ms));
|
|
9835
9900
|
}
|
|
9901
|
+
var MAX_TOTAL_ATTEMPTS = (MAX_RETRIES + 1) * (MAX_RETRIES + 1);
|
|
9836
9902
|
async function streamChat(messages, options, onEvent) {
|
|
9837
|
-
const { model, env: env2, editorContext, nexrallMd, mode, effort, clientType, abortSignal, extraTools, agents } = options;
|
|
9903
|
+
const { model, env: env2, editorContext, nexrallMd, mode, effort, clientType, abortSignal, extraTools, agents, skills, allowRestartAfterRender } = options;
|
|
9904
|
+
let turnId = (0, crypto_1.randomUUID)();
|
|
9838
9905
|
const controller = new AbortController();
|
|
9839
9906
|
if (abortSignal?.aborted)
|
|
9840
9907
|
throw Object.assign(new Error("Aborted"), { name: "AbortError" });
|
|
9841
9908
|
let abortPoll;
|
|
9909
|
+
let cancelSent = false;
|
|
9842
9910
|
if (abortSignal) {
|
|
9843
9911
|
abortPoll = setInterval(() => {
|
|
9844
|
-
if (abortSignal.aborted)
|
|
9845
|
-
|
|
9912
|
+
if (!abortSignal.aborted)
|
|
9913
|
+
return;
|
|
9914
|
+
if (cancelSent)
|
|
9915
|
+
return;
|
|
9916
|
+
cancelSent = true;
|
|
9917
|
+
void cancelTurn(turnId);
|
|
9918
|
+
controller.abort();
|
|
9846
9919
|
}, 50);
|
|
9847
9920
|
}
|
|
9848
|
-
|
|
9849
|
-
|
|
9850
|
-
|
|
9851
|
-
|
|
9852
|
-
|
|
9853
|
-
|
|
9854
|
-
|
|
9921
|
+
let serverResumable = false;
|
|
9922
|
+
let lastEventId = 0;
|
|
9923
|
+
let resuming = false;
|
|
9924
|
+
let carryText = [];
|
|
9925
|
+
let carryToolUse = [];
|
|
9926
|
+
let emittedAnythingAcrossAttempts = false;
|
|
9927
|
+
let emittedCharsAcrossAttempts = 0;
|
|
9928
|
+
const buildFetchArgs = () => {
|
|
9929
|
+
if (resuming) {
|
|
9930
|
+
return [
|
|
9931
|
+
`${exports2.API_BASE}/api/code/chat/resume?turnId=${encodeURIComponent(turnId)}`,
|
|
9932
|
+
{
|
|
9933
|
+
method: "GET",
|
|
9934
|
+
headers: { ...authHeaders(), "Last-Event-ID": String(lastEventId) },
|
|
9935
|
+
signal: controller.signal
|
|
9936
|
+
}
|
|
9937
|
+
];
|
|
9855
9938
|
}
|
|
9856
|
-
|
|
9939
|
+
return [
|
|
9940
|
+
`${exports2.API_BASE}/api/code/chat`,
|
|
9941
|
+
{
|
|
9942
|
+
method: "POST",
|
|
9943
|
+
headers: authHeaders(),
|
|
9944
|
+
body: JSON.stringify({ messages, model, env: env2, editorContext, nexrallMd, mode, effort, clientType, extraTools, agents, skills, turnId }),
|
|
9945
|
+
signal: controller.signal
|
|
9946
|
+
}
|
|
9947
|
+
];
|
|
9948
|
+
};
|
|
9857
9949
|
const isRetryableStreamMsg = (m2) => /overloaded|rate.?limit|temporarily|unavailable|try again|internal server error/i.test(String(m2 ?? ""));
|
|
9950
|
+
let didRetry = false;
|
|
9951
|
+
let totalAttemptsMade = 0;
|
|
9952
|
+
let retryDeadline = 0;
|
|
9953
|
+
const retryBudgetLeft = () => retryDeadline === 0 ? MAX_TOTAL_RETRY_MS : retryDeadline - Date.now();
|
|
9954
|
+
const canRetry = () => retryBudgetLeft() > RETRY_BASE_MS;
|
|
9955
|
+
const sleepWithinBudget = (ms) => sleep(Math.max(0, Math.min(ms, retryBudgetLeft())));
|
|
9956
|
+
const reportRetry = (reason) => {
|
|
9957
|
+
if (retryDeadline === 0)
|
|
9958
|
+
retryDeadline = Date.now() + MAX_TOTAL_RETRY_MS;
|
|
9959
|
+
totalAttemptsMade++;
|
|
9960
|
+
didRetry = true;
|
|
9961
|
+
onEvent({ type: "retry", attempt: totalAttemptsMade, maxAttempts: MAX_TOTAL_ATTEMPTS, reason });
|
|
9962
|
+
};
|
|
9963
|
+
const clearRetryIfNeeded = () => {
|
|
9964
|
+
if (didRetry) {
|
|
9965
|
+
didRetry = false;
|
|
9966
|
+
onEvent({ type: "retry_resolved" });
|
|
9967
|
+
}
|
|
9968
|
+
};
|
|
9858
9969
|
async function runAttempt() {
|
|
9859
9970
|
let response;
|
|
9860
9971
|
let lastErr;
|
|
9972
|
+
let inFlightWaits = 0;
|
|
9973
|
+
let errorBodyOverride = null;
|
|
9974
|
+
let forbiddenExhausted = false;
|
|
9861
9975
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
9862
9976
|
try {
|
|
9863
|
-
response = await (0, node_fetch_1.default)(...
|
|
9864
|
-
if (response.status === 429 && attempt < MAX_RETRIES) {
|
|
9977
|
+
response = await (0, node_fetch_1.default)(...buildFetchArgs());
|
|
9978
|
+
if (response.status === 429 && attempt < MAX_RETRIES && canRetry()) {
|
|
9979
|
+
reportRetry("Rate limited by the API \u2014 retrying");
|
|
9865
9980
|
const retryAfter = parseInt(response.headers.get("retry-after") ?? "0", 10);
|
|
9866
|
-
await
|
|
9981
|
+
await sleepWithinBudget(retryAfter > 0 ? retryAfter * 1e3 : backoffMs(attempt));
|
|
9867
9982
|
continue;
|
|
9868
9983
|
}
|
|
9869
|
-
if (response.status >= 500 && response.status < 600 && attempt < MAX_RETRIES) {
|
|
9870
|
-
|
|
9984
|
+
if (response.status >= 500 && response.status < 600 && attempt < MAX_RETRIES && canRetry()) {
|
|
9985
|
+
reportRetry(`Server error (${response.status}) \u2014 retrying`);
|
|
9986
|
+
await sleepWithinBudget(backoffMs(attempt));
|
|
9871
9987
|
continue;
|
|
9872
9988
|
}
|
|
9989
|
+
const FORBIDDEN_RETRY_LIMIT = 2;
|
|
9990
|
+
if (response.status === 403) {
|
|
9991
|
+
const bodyText = await response.text().catch(() => "");
|
|
9992
|
+
let isUpstreamForbidden = false;
|
|
9993
|
+
try {
|
|
9994
|
+
const parsed = JSON.parse(bodyText);
|
|
9995
|
+
isUpstreamForbidden = parsed?.error?.type === "forbidden";
|
|
9996
|
+
} catch {
|
|
9997
|
+
}
|
|
9998
|
+
if (isUpstreamForbidden) {
|
|
9999
|
+
if (attempt < FORBIDDEN_RETRY_LIMIT && canRetry()) {
|
|
10000
|
+
reportRetry("Upstream rejected the request \u2014 retrying");
|
|
10001
|
+
await sleepWithinBudget(backoffMs(attempt));
|
|
10002
|
+
continue;
|
|
10003
|
+
}
|
|
10004
|
+
forbiddenExhausted = true;
|
|
10005
|
+
errorBodyOverride = bodyText;
|
|
10006
|
+
break;
|
|
10007
|
+
}
|
|
10008
|
+
errorBodyOverride = bodyText;
|
|
10009
|
+
break;
|
|
10010
|
+
}
|
|
10011
|
+
if (response.status === 410 && resuming) {
|
|
10012
|
+
await response.text().catch(() => "");
|
|
10013
|
+
await cancelTurn(turnId);
|
|
10014
|
+
turnId = (0, crypto_1.randomUUID)();
|
|
10015
|
+
resuming = false;
|
|
10016
|
+
serverResumable = false;
|
|
10017
|
+
lastEventId = 0;
|
|
10018
|
+
carryText = [];
|
|
10019
|
+
carryToolUse = [];
|
|
10020
|
+
const hadRendered = emittedAnythingAcrossAttempts;
|
|
10021
|
+
const renderedChars = emittedCharsAcrossAttempts;
|
|
10022
|
+
emittedAnythingAcrossAttempts = false;
|
|
10023
|
+
emittedCharsAcrossAttempts = 0;
|
|
10024
|
+
if (hadRendered && !allowRestartAfterRender) {
|
|
10025
|
+
throw new Error("Connection lost and this turn could no longer be resumed. Send your message again.");
|
|
10026
|
+
}
|
|
10027
|
+
if (hadRendered) {
|
|
10028
|
+
onEvent({
|
|
10029
|
+
type: "stream_restart",
|
|
10030
|
+
reason: "Connection lost too long to resume",
|
|
10031
|
+
discardedChars: renderedChars
|
|
10032
|
+
});
|
|
10033
|
+
}
|
|
10034
|
+
if (attempt < MAX_RETRIES && canRetry()) {
|
|
10035
|
+
reportRetry("Could not resume \u2014 restarting this turn");
|
|
10036
|
+
await sleepWithinBudget(backoffMs(attempt));
|
|
10037
|
+
continue;
|
|
10038
|
+
}
|
|
10039
|
+
errorBodyOverride = JSON.stringify({ error: "Could not resume this turn \u2014 send your message again." });
|
|
10040
|
+
break;
|
|
10041
|
+
}
|
|
10042
|
+
if (response.status === 409) {
|
|
10043
|
+
const body = await response.text();
|
|
10044
|
+
let inFlight = false;
|
|
10045
|
+
try {
|
|
10046
|
+
inFlight = JSON.parse(body)?.turnInFlight === true;
|
|
10047
|
+
} catch {
|
|
10048
|
+
}
|
|
10049
|
+
if (inFlight && canRetry()) {
|
|
10050
|
+
inFlightWaits++;
|
|
10051
|
+
reportRetry("This turn is already being processed \u2014 waiting for it");
|
|
10052
|
+
const retryAfter = parseInt(response.headers.get("retry-after") ?? "0", 10);
|
|
10053
|
+
await sleepWithinBudget(Math.max(retryAfter * 1e3, backoffMs(inFlightWaits)));
|
|
10054
|
+
attempt--;
|
|
10055
|
+
continue;
|
|
10056
|
+
}
|
|
10057
|
+
errorBodyOverride = body;
|
|
10058
|
+
break;
|
|
10059
|
+
}
|
|
9873
10060
|
break;
|
|
9874
10061
|
} catch (err) {
|
|
9875
10062
|
if (err.name === "AbortError")
|
|
9876
10063
|
throw err;
|
|
9877
10064
|
lastErr = err;
|
|
9878
|
-
if (attempt < MAX_RETRIES) {
|
|
9879
|
-
|
|
10065
|
+
if (attempt < MAX_RETRIES && canRetry()) {
|
|
10066
|
+
reportRetry("Connection lost \u2014 attempting to reconnect");
|
|
10067
|
+
await sleepWithinBudget(backoffMs(attempt));
|
|
9880
10068
|
continue;
|
|
9881
10069
|
}
|
|
10070
|
+
if (!canRetry() && lastErr && typeof lastErr === "object") {
|
|
10071
|
+
Object.assign(lastErr, { retryable: true });
|
|
10072
|
+
}
|
|
10073
|
+
break;
|
|
9882
10074
|
}
|
|
9883
10075
|
}
|
|
9884
10076
|
if (!response)
|
|
9885
10077
|
throw lastErr ?? new Error("Request failed after max retries");
|
|
9886
10078
|
if (!response.ok) {
|
|
9887
|
-
const errText = await response.text();
|
|
10079
|
+
const errText = errorBodyOverride ?? await response.text();
|
|
9888
10080
|
let errMsg = `API error ${response.status}`;
|
|
10081
|
+
let balance;
|
|
9889
10082
|
try {
|
|
9890
10083
|
const parsed = JSON.parse(errText);
|
|
9891
10084
|
if (parsed.error)
|
|
9892
10085
|
errMsg = parsed.error;
|
|
10086
|
+
if (typeof parsed.balance === "number")
|
|
10087
|
+
balance = parsed.balance;
|
|
9893
10088
|
} catch {
|
|
9894
10089
|
errMsg = errText || errMsg;
|
|
9895
10090
|
}
|
|
9896
|
-
throw new Error(errMsg);
|
|
10091
|
+
throw Object.assign(new Error(errMsg), { status: response.status, balance, ...forbiddenExhausted ? { retryable: true } : {} });
|
|
9897
10092
|
}
|
|
9898
10093
|
if (!response.body) {
|
|
9899
10094
|
throw new Error("Response body is null");
|
|
9900
10095
|
}
|
|
9901
|
-
const textParts = [];
|
|
9902
|
-
const toolUseBlocks = [];
|
|
10096
|
+
const textParts = resuming ? [...carryText] : [];
|
|
10097
|
+
const toolUseBlocks = resuming ? [...carryToolUse] : [];
|
|
9903
10098
|
let completedMessage = null;
|
|
10099
|
+
carryText = textParts;
|
|
10100
|
+
carryToolUse = toolUseBlocks;
|
|
9904
10101
|
let emittedToCaller = false;
|
|
10102
|
+
let emittedChars = 0;
|
|
10103
|
+
const tagTransient = (err) => {
|
|
10104
|
+
if (!emittedToCaller)
|
|
10105
|
+
return Object.assign(err, { retryable: true });
|
|
10106
|
+
if (allowRestartAfterRender) {
|
|
10107
|
+
return Object.assign(err, { retryable: true, needsRestart: true, discardedChars: emittedChars });
|
|
10108
|
+
}
|
|
10109
|
+
return err;
|
|
10110
|
+
};
|
|
10111
|
+
const haveCompleteMessage = () => completedMessage !== null;
|
|
9905
10112
|
const partialInputs = {};
|
|
9906
10113
|
await new Promise((resolve3, reject) => {
|
|
9907
10114
|
const stream = response.body;
|
|
9908
|
-
const HEARTBEAT_TIMEOUT_MS =
|
|
9909
|
-
const FIRST_EVENT_TIMEOUT_MS = 3e5;
|
|
9910
|
-
const PROGRESS_TIMEOUT_MS = 15e4;
|
|
10115
|
+
const HEARTBEAT_TIMEOUT_MS = 9e4;
|
|
10116
|
+
const FIRST_EVENT_TIMEOUT_MS = Number(process.env.NEXRALL_FIRST_EVENT_TIMEOUT_MS) || 3e5;
|
|
10117
|
+
const PROGRESS_TIMEOUT_MS = Number(process.env.NEXRALL_PROGRESS_TIMEOUT_MS) || 15e4;
|
|
9911
10118
|
let sawModelEvent = false;
|
|
9912
10119
|
let lastDataAt = Date.now();
|
|
9913
10120
|
let lastProgressAt = Date.now();
|
|
9914
10121
|
const heartbeatWatchdog = setInterval(() => {
|
|
9915
10122
|
const now = Date.now();
|
|
10123
|
+
if (haveCompleteMessage() && now - lastDataAt > HEARTBEAT_TIMEOUT_MS) {
|
|
10124
|
+
clearInterval(heartbeatWatchdog);
|
|
10125
|
+
stream.destroy?.();
|
|
10126
|
+
resolve3();
|
|
10127
|
+
return;
|
|
10128
|
+
}
|
|
9916
10129
|
if (now - lastDataAt > HEARTBEAT_TIMEOUT_MS) {
|
|
9917
10130
|
clearInterval(heartbeatWatchdog);
|
|
9918
10131
|
stream.destroy?.();
|
|
9919
|
-
|
|
10132
|
+
const recoverable = !emittedToCaller || !!allowRestartAfterRender;
|
|
10133
|
+
const secs = Math.round(HEARTBEAT_TIMEOUT_MS / 1e3);
|
|
10134
|
+
reject(tagTransient(new Error(recoverable ? `Connection lost \u2014 no data received for ${secs} s. Reconnecting\u2026` : `Connection lost \u2014 no data received for ${secs} s. Retry your message.`)));
|
|
9920
10135
|
return;
|
|
9921
10136
|
}
|
|
9922
10137
|
const stallLimitMs = sawModelEvent ? PROGRESS_TIMEOUT_MS : FIRST_EVENT_TIMEOUT_MS;
|
|
9923
10138
|
if (now - lastProgressAt > stallLimitMs) {
|
|
9924
10139
|
clearInterval(heartbeatWatchdog);
|
|
9925
10140
|
stream.destroy?.();
|
|
9926
|
-
|
|
9927
|
-
|
|
10141
|
+
if (haveCompleteMessage()) {
|
|
10142
|
+
resolve3();
|
|
10143
|
+
return;
|
|
10144
|
+
}
|
|
10145
|
+
reject(tagTransient(new Error(sawModelEvent ? `The model stopped responding mid-stream (no output for ${Math.round(stallLimitMs / 1e3)} s).` : `The model did not start responding within ${Math.round(stallLimitMs / 1e3)} s (large context can take a while to process).`)));
|
|
9928
10146
|
return;
|
|
9929
10147
|
}
|
|
9930
10148
|
}, 5e3);
|
|
@@ -9934,6 +10152,12 @@ var require_client = __commonJS({
|
|
|
9934
10152
|
return;
|
|
9935
10153
|
}
|
|
9936
10154
|
{
|
|
10155
|
+
const frameId = event.id;
|
|
10156
|
+
if (frameId) {
|
|
10157
|
+
const n = Number(frameId);
|
|
10158
|
+
if (Number.isFinite(n) && n > lastEventId)
|
|
10159
|
+
lastEventId = n;
|
|
10160
|
+
}
|
|
9937
10161
|
const raw = event.data;
|
|
9938
10162
|
if (!raw || raw === "[DONE]") {
|
|
9939
10163
|
resolve3();
|
|
@@ -9951,11 +10175,15 @@ var require_client = __commonJS({
|
|
|
9951
10175
|
const evt = parsed;
|
|
9952
10176
|
lastProgressAt = Date.now();
|
|
9953
10177
|
sawModelEvent = true;
|
|
10178
|
+
clearRetryIfNeeded();
|
|
9954
10179
|
switch (evt.type) {
|
|
9955
10180
|
case "text": {
|
|
9956
10181
|
const text = typeof evt.text === "string" ? evt.text : "";
|
|
9957
10182
|
textParts.push(text);
|
|
9958
10183
|
emittedToCaller = true;
|
|
10184
|
+
emittedChars += text.length;
|
|
10185
|
+
emittedAnythingAcrossAttempts = true;
|
|
10186
|
+
emittedCharsAcrossAttempts += text.length;
|
|
9959
10187
|
onEvent({ type: "text", text });
|
|
9960
10188
|
break;
|
|
9961
10189
|
}
|
|
@@ -9976,19 +10204,25 @@ var require_client = __commonJS({
|
|
|
9976
10204
|
}
|
|
9977
10205
|
toolUseBlocks.push(block);
|
|
9978
10206
|
emittedToCaller = true;
|
|
10207
|
+
emittedAnythingAcrossAttempts = true;
|
|
9979
10208
|
onEvent({ type: "tool_use", id: block.id, name: block.name, input: block.input });
|
|
9980
10209
|
break;
|
|
9981
10210
|
}
|
|
9982
10211
|
case "message_complete": {
|
|
10212
|
+
const nestedMessage = evt.message;
|
|
10213
|
+
const stopReason = typeof nestedMessage?.stop_reason === "string" ? nestedMessage.stop_reason : null;
|
|
9983
10214
|
const contentBlocks2 = [];
|
|
9984
10215
|
const fullText2 = textParts.join("");
|
|
9985
10216
|
if (fullText2) {
|
|
9986
10217
|
contentBlocks2.push({ type: "text", text: fullText2 });
|
|
9987
10218
|
}
|
|
9988
10219
|
contentBlocks2.push(...toolUseBlocks);
|
|
10220
|
+
const rawContent = Array.isArray(nestedMessage?.content) ? nestedMessage.content : null;
|
|
10221
|
+
const finalContent = chooseFinalContent(contentBlocks2, rawContent);
|
|
9989
10222
|
completedMessage = {
|
|
9990
10223
|
role: "assistant",
|
|
9991
|
-
content:
|
|
10224
|
+
content: finalContent,
|
|
10225
|
+
stopReason
|
|
9992
10226
|
};
|
|
9993
10227
|
onEvent({ type: "message_complete", message: completedMessage });
|
|
9994
10228
|
break;
|
|
@@ -9997,6 +10231,17 @@ var require_client = __commonJS({
|
|
|
9997
10231
|
if (typeof evt.input_tokens === "number" && typeof evt.output_tokens === "number") {
|
|
9998
10232
|
onEvent({
|
|
9999
10233
|
type: "usage",
|
|
10234
|
+
// Forwarded, not dropped: the backend tags the usage of an attempt it
|
|
10235
|
+
// billed but never completed (its stream `abort` path). Without this
|
|
10236
|
+
// the flag dies here and every consumer that sums usage silently folds
|
|
10237
|
+
// a discarded attempt's tokens into the successful turn's total.
|
|
10238
|
+
...evt.partial === true ? { partial: true } : {},
|
|
10239
|
+
// `replayed` means these tokens are being reported a SECOND time: the
|
|
10240
|
+
// turn completed and was billed on an earlier attempt whose `done` never
|
|
10241
|
+
// reached us, and the backend served this one from its idempotency cache
|
|
10242
|
+
// instead of re-running the model. Nothing new was charged, so a cost
|
|
10243
|
+
// display must not add them again.
|
|
10244
|
+
...evt.replayed === true ? { replayed: true } : {},
|
|
10000
10245
|
usage: {
|
|
10001
10246
|
input_tokens: evt.input_tokens,
|
|
10002
10247
|
output_tokens: evt.output_tokens,
|
|
@@ -10017,6 +10262,9 @@ var require_client = __commonJS({
|
|
|
10017
10262
|
const text = typeof evt.text === "string" ? evt.text : "";
|
|
10018
10263
|
if (text) {
|
|
10019
10264
|
emittedToCaller = true;
|
|
10265
|
+
emittedChars += text.length;
|
|
10266
|
+
emittedAnythingAcrossAttempts = true;
|
|
10267
|
+
emittedCharsAcrossAttempts += text.length;
|
|
10020
10268
|
onEvent({ type: "thinking", text });
|
|
10021
10269
|
}
|
|
10022
10270
|
break;
|
|
@@ -10030,10 +10278,23 @@ var require_client = __commonJS({
|
|
|
10030
10278
|
const text = typeof evt.text === "string" ? evt.text : "";
|
|
10031
10279
|
if (text) {
|
|
10032
10280
|
emittedToCaller = true;
|
|
10281
|
+
emittedChars += text.length;
|
|
10282
|
+
emittedAnythingAcrossAttempts = true;
|
|
10283
|
+
emittedCharsAcrossAttempts += text.length;
|
|
10033
10284
|
onEvent({ type: "thinking_delta", text });
|
|
10034
10285
|
}
|
|
10035
10286
|
break;
|
|
10036
10287
|
}
|
|
10288
|
+
case "resumable": {
|
|
10289
|
+
serverResumable = true;
|
|
10290
|
+
break;
|
|
10291
|
+
}
|
|
10292
|
+
case "balance_status": {
|
|
10293
|
+
const balance = typeof evt.balance === "number" ? evt.balance : 0;
|
|
10294
|
+
const zero = !!evt.zero;
|
|
10295
|
+
onEvent({ type: "balance_status", balance, zero });
|
|
10296
|
+
break;
|
|
10297
|
+
}
|
|
10037
10298
|
case "done": {
|
|
10038
10299
|
onEvent({ type: "done" });
|
|
10039
10300
|
resolve3();
|
|
@@ -10041,10 +10302,19 @@ var require_client = __commonJS({
|
|
|
10041
10302
|
}
|
|
10042
10303
|
case "error": {
|
|
10043
10304
|
const message = typeof evt.message === "string" ? evt.message : typeof evt.error === "string" ? evt.error : "Unknown SSE error";
|
|
10044
|
-
|
|
10305
|
+
const notResumable = evt.notResumable === true;
|
|
10306
|
+
if (haveCompleteMessage()) {
|
|
10307
|
+
clearInterval(heartbeatWatchdog);
|
|
10308
|
+
onEvent({ type: "error", message });
|
|
10309
|
+
resolve3();
|
|
10310
|
+
} else if (notResumable && (!emittedToCaller || allowRestartAfterRender)) {
|
|
10045
10311
|
clearInterval(heartbeatWatchdog);
|
|
10046
10312
|
stream.destroy?.();
|
|
10047
|
-
reject(Object.assign(new Error(message), {
|
|
10313
|
+
reject(Object.assign(tagTransient(new Error(message)), { forceRestart: true }));
|
|
10314
|
+
} else if (isRetryableStreamMsg(message) && (!emittedToCaller || allowRestartAfterRender)) {
|
|
10315
|
+
clearInterval(heartbeatWatchdog);
|
|
10316
|
+
stream.destroy?.();
|
|
10317
|
+
reject(tagTransient(new Error(message)));
|
|
10048
10318
|
} else {
|
|
10049
10319
|
onEvent({ type: "error", message });
|
|
10050
10320
|
reject(new Error(message));
|
|
@@ -10076,11 +10346,11 @@ var require_client = __commonJS({
|
|
|
10076
10346
|
resolve3();
|
|
10077
10347
|
return;
|
|
10078
10348
|
}
|
|
10079
|
-
if (
|
|
10080
|
-
|
|
10349
|
+
if (haveCompleteMessage()) {
|
|
10350
|
+
resolve3();
|
|
10081
10351
|
return;
|
|
10082
10352
|
}
|
|
10083
|
-
reject(err);
|
|
10353
|
+
reject(tagTransient(err));
|
|
10084
10354
|
});
|
|
10085
10355
|
});
|
|
10086
10356
|
if (completedMessage) {
|
|
@@ -10097,14 +10367,60 @@ var require_client = __commonJS({
|
|
|
10097
10367
|
try {
|
|
10098
10368
|
for (let sAttempt = 0; sAttempt <= MAX_RETRIES; sAttempt++) {
|
|
10099
10369
|
try {
|
|
10100
|
-
|
|
10370
|
+
const result = await runAttempt();
|
|
10371
|
+
clearRetryIfNeeded();
|
|
10372
|
+
return result;
|
|
10101
10373
|
} catch (err) {
|
|
10102
10374
|
if (abortSignal?.aborted || controller.signal.aborted || err.name === "AbortError")
|
|
10103
10375
|
throw err;
|
|
10104
|
-
|
|
10105
|
-
|
|
10376
|
+
const e2 = err;
|
|
10377
|
+
if (e2.retryable && sAttempt < MAX_RETRIES && canRetry()) {
|
|
10378
|
+
if (e2.forceRestart) {
|
|
10379
|
+
await cancelTurn(turnId);
|
|
10380
|
+
turnId = (0, crypto_1.randomUUID)();
|
|
10381
|
+
resuming = false;
|
|
10382
|
+
serverResumable = false;
|
|
10383
|
+
lastEventId = 0;
|
|
10384
|
+
carryText = [];
|
|
10385
|
+
carryToolUse = [];
|
|
10386
|
+
const hadRendered = emittedAnythingAcrossAttempts;
|
|
10387
|
+
const renderedChars = emittedCharsAcrossAttempts;
|
|
10388
|
+
emittedAnythingAcrossAttempts = false;
|
|
10389
|
+
emittedCharsAcrossAttempts = 0;
|
|
10390
|
+
if (hadRendered && !allowRestartAfterRender) {
|
|
10391
|
+
throw new Error("Connection lost and this turn could no longer be resumed. Send your message again.");
|
|
10392
|
+
}
|
|
10393
|
+
if (hadRendered) {
|
|
10394
|
+
onEvent({
|
|
10395
|
+
type: "stream_restart",
|
|
10396
|
+
reason: err.message || "Could not resume \u2014 restarting this turn",
|
|
10397
|
+
discardedChars: renderedChars
|
|
10398
|
+
});
|
|
10399
|
+
}
|
|
10400
|
+
reportRetry("Could not resume \u2014 restarting this turn");
|
|
10401
|
+
await sleepWithinBudget(backoffMs(sAttempt));
|
|
10402
|
+
continue;
|
|
10403
|
+
}
|
|
10404
|
+
if (serverResumable && lastEventId > 0) {
|
|
10405
|
+
resuming = true;
|
|
10406
|
+
reportRetry(err.message || "Connection interrupted \u2014 resuming");
|
|
10407
|
+
await sleepWithinBudget(backoffMs(sAttempt));
|
|
10408
|
+
continue;
|
|
10409
|
+
}
|
|
10410
|
+
if (e2.needsRestart) {
|
|
10411
|
+
onEvent({
|
|
10412
|
+
type: "stream_restart",
|
|
10413
|
+
reason: err.message || "Connection interrupted",
|
|
10414
|
+
discardedChars: e2.discardedChars ?? 0
|
|
10415
|
+
});
|
|
10416
|
+
}
|
|
10417
|
+
reportRetry(err.message || "Connection interrupted \u2014 reconnecting");
|
|
10418
|
+
await sleepWithinBudget(backoffMs(sAttempt));
|
|
10106
10419
|
continue;
|
|
10107
10420
|
}
|
|
10421
|
+
if (e2.retryable && !canRetry()) {
|
|
10422
|
+
throw Object.assign(new Error(`${err.message} \u2014 gave up after ${Math.round(MAX_TOTAL_RETRY_MS / 6e4)} minutes of reconnect attempts. Check your connection and resend; completed work in this turn is preserved.`), { retryBudgetExhausted: true });
|
|
10423
|
+
}
|
|
10108
10424
|
throw err;
|
|
10109
10425
|
}
|
|
10110
10426
|
}
|
|
@@ -10114,6 +10430,16 @@ var require_client = __commonJS({
|
|
|
10114
10430
|
clearInterval(abortPoll);
|
|
10115
10431
|
}
|
|
10116
10432
|
}
|
|
10433
|
+
async function cancelTurn(turnId) {
|
|
10434
|
+
try {
|
|
10435
|
+
await (0, node_fetch_1.default)(`${exports2.API_BASE}/api/code/chat/cancel`, {
|
|
10436
|
+
method: "POST",
|
|
10437
|
+
headers: { ...authHeaders(), "Content-Type": "application/json" },
|
|
10438
|
+
body: JSON.stringify({ turnId })
|
|
10439
|
+
});
|
|
10440
|
+
} catch {
|
|
10441
|
+
}
|
|
10442
|
+
}
|
|
10117
10443
|
async function getBalance3() {
|
|
10118
10444
|
const response = await (0, node_fetch_1.default)(`${exports2.API_BASE}/api/code/balance`, {
|
|
10119
10445
|
method: "GET",
|
|
@@ -10360,123 +10686,887 @@ var require_editCompleteness = __commonJS({
|
|
|
10360
10686
|
}
|
|
10361
10687
|
});
|
|
10362
10688
|
|
|
10363
|
-
// ../core/dist/agent/
|
|
10364
|
-
var
|
|
10365
|
-
"../core/dist/agent/
|
|
10689
|
+
// ../core/dist/agent/memory.js
|
|
10690
|
+
var require_memory = __commonJS({
|
|
10691
|
+
"../core/dist/agent/memory.js"(exports2) {
|
|
10366
10692
|
"use strict";
|
|
10693
|
+
var __createBinding = exports2 && exports2.__createBinding || (Object.create ? function(o, m2, k, k2) {
|
|
10694
|
+
if (k2 === void 0)
|
|
10695
|
+
k2 = k;
|
|
10696
|
+
var desc = Object.getOwnPropertyDescriptor(m2, k);
|
|
10697
|
+
if (!desc || ("get" in desc ? !m2.__esModule : desc.writable || desc.configurable)) {
|
|
10698
|
+
desc = { enumerable: true, get: function() {
|
|
10699
|
+
return m2[k];
|
|
10700
|
+
} };
|
|
10701
|
+
}
|
|
10702
|
+
Object.defineProperty(o, k2, desc);
|
|
10703
|
+
} : function(o, m2, k, k2) {
|
|
10704
|
+
if (k2 === void 0)
|
|
10705
|
+
k2 = k;
|
|
10706
|
+
o[k2] = m2[k];
|
|
10707
|
+
});
|
|
10708
|
+
var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? function(o, v) {
|
|
10709
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
10710
|
+
} : function(o, v) {
|
|
10711
|
+
o["default"] = v;
|
|
10712
|
+
});
|
|
10713
|
+
var __importStar = exports2 && exports2.__importStar || /* @__PURE__ */ function() {
|
|
10714
|
+
var ownKeys = function(o) {
|
|
10715
|
+
ownKeys = Object.getOwnPropertyNames || function(o2) {
|
|
10716
|
+
var ar = [];
|
|
10717
|
+
for (var k in o2)
|
|
10718
|
+
if (Object.prototype.hasOwnProperty.call(o2, k))
|
|
10719
|
+
ar[ar.length] = k;
|
|
10720
|
+
return ar;
|
|
10721
|
+
};
|
|
10722
|
+
return ownKeys(o);
|
|
10723
|
+
};
|
|
10724
|
+
return function(mod) {
|
|
10725
|
+
if (mod && mod.__esModule)
|
|
10726
|
+
return mod;
|
|
10727
|
+
var result = {};
|
|
10728
|
+
if (mod != null) {
|
|
10729
|
+
for (var k = ownKeys(mod), i2 = 0; i2 < k.length; i2++)
|
|
10730
|
+
if (k[i2] !== "default")
|
|
10731
|
+
__createBinding(result, mod, k[i2]);
|
|
10732
|
+
}
|
|
10733
|
+
__setModuleDefault(result, mod);
|
|
10734
|
+
return result;
|
|
10735
|
+
};
|
|
10736
|
+
}();
|
|
10367
10737
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
10368
|
-
exports2.
|
|
10369
|
-
exports2.
|
|
10370
|
-
exports2.
|
|
10371
|
-
exports2.
|
|
10372
|
-
exports2.
|
|
10373
|
-
exports2.
|
|
10374
|
-
|
|
10375
|
-
|
|
10376
|
-
|
|
10377
|
-
|
|
10378
|
-
|
|
10379
|
-
|
|
10380
|
-
var
|
|
10381
|
-
|
|
10382
|
-
|
|
10383
|
-
|
|
10384
|
-
|
|
10385
|
-
|
|
10386
|
-
|
|
10387
|
-
|
|
10388
|
-
|
|
10389
|
-
|
|
10390
|
-
|
|
10391
|
-
|
|
10392
|
-
|
|
10393
|
-
|
|
10394
|
-
|
|
10395
|
-
|
|
10396
|
-
|
|
10397
|
-
|
|
10398
|
-
|
|
10399
|
-
|
|
10400
|
-
|
|
10401
|
-
|
|
10402
|
-
|
|
10403
|
-
|
|
10404
|
-
|
|
10405
|
-
|
|
10406
|
-
|
|
10407
|
-
|
|
10408
|
-
/\bfunc\s+Test\w+/g,
|
|
10409
|
-
// go
|
|
10410
|
-
/@Test\b/g,
|
|
10411
|
-
// junit
|
|
10412
|
-
/\bfn\s+\w*test\w*\s*\(/gi
|
|
10413
|
-
// rust (best-effort)
|
|
10414
|
-
];
|
|
10415
|
-
var SKIP_RES = [
|
|
10416
|
-
/\b(?:it|test|describe|context|suite)\s*\.\s*(?:skip|only)\b/g,
|
|
10417
|
-
/\bx(?:it|describe|test|context)\s*\(/g,
|
|
10418
|
-
// xit / xdescribe
|
|
10419
|
-
/\bf(?:it|describe)\s*\(/g,
|
|
10420
|
-
// fit / fdescribe (focus)
|
|
10421
|
-
/@pytest\.mark\.skip\b|@pytest\.mark\.skipif\b|@unittest\.skip\b|@skip\b/g,
|
|
10422
|
-
/\bpytest\.skip\s*\(/g,
|
|
10423
|
-
/\bt\.Skip\s*\(|\bt\.SkipNow\s*\(/g,
|
|
10424
|
-
// go
|
|
10425
|
-
/@Disabled\b|@Ignore\b/g,
|
|
10426
|
-
// junit / kotlin
|
|
10427
|
-
/\.only\s*\(/g
|
|
10428
|
-
// test.only leaks CI coverage
|
|
10429
|
-
];
|
|
10430
|
-
var TAUTOLOGY_RES = [
|
|
10431
|
-
/\bassert\s+True\b|\bassert\s+1\b|\bassert\s+not\s+False\b/g,
|
|
10432
|
-
// python
|
|
10433
|
-
/\bassert\s*\(\s*true\s*\)|\bassert\.ok\s*\(\s*true\s*\)/gi,
|
|
10434
|
-
// node
|
|
10435
|
-
/\bexpect\s*\(\s*true\s*\)\s*\.\s*to(?:Be|Equal|BeTruthy)\s*\(\s*true\s*\)?/gi,
|
|
10436
|
-
// jest
|
|
10437
|
-
/\bexpect\s*\(\s*(\w+)\s*\)\s*\.\s*toBe\s*\(\s*\1\s*\)/g,
|
|
10438
|
-
// expect(x).toBe(x)
|
|
10439
|
-
/\bassert_eq!\s*\(\s*true\s*,\s*true\s*\)/g,
|
|
10440
|
-
// rust
|
|
10441
|
-
/\bassert!\s*\(\s*true\s*\)/g
|
|
10442
|
-
];
|
|
10443
|
-
function countMatches(res, text) {
|
|
10444
|
-
let n = 0;
|
|
10445
|
-
for (const re of res) {
|
|
10446
|
-
re.lastIndex = 0;
|
|
10447
|
-
const m2 = text.match(re);
|
|
10448
|
-
if (m2)
|
|
10449
|
-
n += m2.length;
|
|
10738
|
+
exports2.MEMORY_HARD_CAP_BYTES = exports2.MEMORY_COMPACT_TRIGGER_BYTES = exports2.MEMORY_MAX_BYTES = exports2.MEMORY_ENTRY_MAX_CHARS = void 0;
|
|
10739
|
+
exports2.memoryFilePath = memoryFilePath;
|
|
10740
|
+
exports2.writeMemory = writeMemory;
|
|
10741
|
+
exports2.readMemory = readMemory2;
|
|
10742
|
+
exports2.readAllMemory = readAllMemory2;
|
|
10743
|
+
exports2.clearMemory = clearMemory2;
|
|
10744
|
+
exports2.memoryStats = memoryStats2;
|
|
10745
|
+
exports2.compactMemoryIfNeeded = compactMemoryIfNeeded2;
|
|
10746
|
+
var fs6 = __importStar(require("fs"));
|
|
10747
|
+
var os5 = __importStar(require("os"));
|
|
10748
|
+
var path5 = __importStar(require("path"));
|
|
10749
|
+
var crypto2 = __importStar(require("crypto"));
|
|
10750
|
+
var MEMORY_ROOT = process.env.NEXRALL_MEMORY_DIR || path5.join(os5.homedir(), ".nexrall", "memory");
|
|
10751
|
+
var GLOBAL_FILE = path5.join(MEMORY_ROOT, "global.md");
|
|
10752
|
+
function memoryFilePath(scope, workDir) {
|
|
10753
|
+
if (scope === "global" || !workDir)
|
|
10754
|
+
return GLOBAL_FILE;
|
|
10755
|
+
const key = crypto2.createHash("sha1").update(path5.resolve(workDir)).digest("hex").slice(0, 16);
|
|
10756
|
+
return path5.join(MEMORY_ROOT, `project-${key}.md`);
|
|
10757
|
+
}
|
|
10758
|
+
exports2.MEMORY_ENTRY_MAX_CHARS = 400;
|
|
10759
|
+
exports2.MEMORY_MAX_BYTES = 12e3;
|
|
10760
|
+
exports2.MEMORY_COMPACT_TRIGGER_BYTES = 16e3;
|
|
10761
|
+
exports2.MEMORY_HARD_CAP_BYTES = 4 * exports2.MEMORY_COMPACT_TRIGGER_BYTES;
|
|
10762
|
+
var FINGERPRINT_LEN = 60;
|
|
10763
|
+
var _locks = /* @__PURE__ */ new Map();
|
|
10764
|
+
async function withLock(key, fn) {
|
|
10765
|
+
const prev = _locks.get(key) ?? Promise.resolve();
|
|
10766
|
+
let release2;
|
|
10767
|
+
const next = new Promise((res) => {
|
|
10768
|
+
release2 = res;
|
|
10769
|
+
});
|
|
10770
|
+
_locks.set(key, prev.then(() => next));
|
|
10771
|
+
try {
|
|
10772
|
+
await prev;
|
|
10773
|
+
return await fn();
|
|
10774
|
+
} finally {
|
|
10775
|
+
release2();
|
|
10776
|
+
if (_locks.get(key) === next)
|
|
10777
|
+
_locks.delete(key);
|
|
10450
10778
|
}
|
|
10451
|
-
return n;
|
|
10452
10779
|
}
|
|
10453
|
-
function
|
|
10454
|
-
|
|
10455
|
-
|
|
10456
|
-
|
|
10457
|
-
|
|
10458
|
-
const isComment = /^(?:\/\/|#|\/\*|\*)/.test(line);
|
|
10459
|
-
if (!isComment)
|
|
10460
|
-
continue;
|
|
10461
|
-
const body = line.replace(/^(?:\/\/+|#+|\/\*+|\*+)\s?/, "");
|
|
10462
|
-
const looksLikeTest = /\b(expect|assert|it\(|test\(|def test_|func Test|EXPECT_|ASSERT_)\b/.test(body);
|
|
10463
|
-
if (looksLikeTest && oldLines.has(body))
|
|
10464
|
-
n += 1;
|
|
10780
|
+
function readMemoryFile(file) {
|
|
10781
|
+
try {
|
|
10782
|
+
return fs6.readFileSync(file, "utf-8");
|
|
10783
|
+
} catch {
|
|
10784
|
+
return "";
|
|
10465
10785
|
}
|
|
10466
|
-
return n;
|
|
10467
10786
|
}
|
|
10468
|
-
function
|
|
10469
|
-
|
|
10470
|
-
|
|
10787
|
+
function writeMemoryFile(file, content) {
|
|
10788
|
+
fs6.mkdirSync(path5.dirname(file), { recursive: true });
|
|
10789
|
+
fs6.writeFileSync(file, content, "utf-8");
|
|
10790
|
+
}
|
|
10791
|
+
function evictOldest(content, maxBytes) {
|
|
10792
|
+
if (Buffer.byteLength(content, "utf-8") <= maxBytes)
|
|
10793
|
+
return content;
|
|
10794
|
+
const lines = content.split("\n").filter((l) => l.trim().length > 0);
|
|
10795
|
+
while (lines.length > 1 && Buffer.byteLength(lines.join("\n"), "utf-8") > maxBytes)
|
|
10796
|
+
lines.shift();
|
|
10797
|
+
return "\n" + lines.join("\n");
|
|
10798
|
+
}
|
|
10799
|
+
async function writeMemory(content, scope, workDir) {
|
|
10800
|
+
let trimmed = content.trim();
|
|
10801
|
+
if (!trimmed)
|
|
10802
|
+
return { ok: false, already: false, scope, file: "" };
|
|
10803
|
+
if (trimmed.length > exports2.MEMORY_ENTRY_MAX_CHARS)
|
|
10804
|
+
trimmed = trimmed.slice(0, exports2.MEMORY_ENTRY_MAX_CHARS - 1).trimEnd() + "\u2026";
|
|
10805
|
+
const file = memoryFilePath(scope, workDir);
|
|
10806
|
+
return withLock(file, async () => {
|
|
10807
|
+
const existing = readMemoryFile(file);
|
|
10808
|
+
const fingerprint = trimmed.toLowerCase().slice(0, FINGERPRINT_LEN);
|
|
10809
|
+
if (existing.toLowerCase().includes(fingerprint)) {
|
|
10810
|
+
return { ok: true, already: true, scope, file };
|
|
10811
|
+
}
|
|
10812
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
10813
|
+
let next = existing + `
|
|
10814
|
+
- [${today}] ${trimmed}`;
|
|
10815
|
+
if (Buffer.byteLength(next, "utf-8") > exports2.MEMORY_HARD_CAP_BYTES) {
|
|
10816
|
+
next = evictOldest(next, exports2.MEMORY_MAX_BYTES);
|
|
10817
|
+
}
|
|
10818
|
+
writeMemoryFile(file, next);
|
|
10819
|
+
return { ok: true, already: false, scope, file };
|
|
10820
|
+
});
|
|
10821
|
+
}
|
|
10822
|
+
function readMemory2(scope, workDir) {
|
|
10823
|
+
const file = memoryFilePath(scope, workDir);
|
|
10824
|
+
return readMemoryFile(file).trim();
|
|
10825
|
+
}
|
|
10826
|
+
function readAllMemory2(workDir) {
|
|
10827
|
+
const parts = [];
|
|
10828
|
+
const globalMem = readMemory2("global");
|
|
10829
|
+
if (globalMem)
|
|
10830
|
+
parts.push(`[Global memories \u2014 apply to every project]
|
|
10831
|
+
${globalMem}`);
|
|
10832
|
+
if (workDir) {
|
|
10833
|
+
const projMem = readMemory2("project", workDir);
|
|
10834
|
+
if (projMem)
|
|
10835
|
+
parts.push(`[Project memories \u2014 ${path5.basename(path5.resolve(workDir))}]
|
|
10836
|
+
${projMem}`);
|
|
10837
|
+
}
|
|
10838
|
+
return parts.join("\n\n");
|
|
10839
|
+
}
|
|
10840
|
+
function clearMemory2(scope, workDir) {
|
|
10841
|
+
const file = memoryFilePath(scope, workDir);
|
|
10842
|
+
try {
|
|
10843
|
+
fs6.rmSync(file, { force: true });
|
|
10844
|
+
} catch {
|
|
10471
10845
|
}
|
|
10472
|
-
|
|
10473
|
-
|
|
10474
|
-
const
|
|
10475
|
-
|
|
10476
|
-
|
|
10477
|
-
|
|
10478
|
-
|
|
10479
|
-
|
|
10846
|
+
}
|
|
10847
|
+
function memoryStats2(scope, workDir) {
|
|
10848
|
+
const file = memoryFilePath(scope, workDir);
|
|
10849
|
+
const content = readMemoryFile(file);
|
|
10850
|
+
const entries = content.split("\n").filter((l) => /^-\s\[\d{4}-\d{2}-\d{2}\]/.test(l)).length;
|
|
10851
|
+
return { file, bytes: Buffer.byteLength(content, "utf-8"), entries };
|
|
10852
|
+
}
|
|
10853
|
+
async function compactMemoryIfNeeded2(scope, workDir, summarize) {
|
|
10854
|
+
const file = memoryFilePath(scope, workDir);
|
|
10855
|
+
return withLock(file, async () => {
|
|
10856
|
+
const content = readMemoryFile(file);
|
|
10857
|
+
if (Buffer.byteLength(content, "utf-8") <= exports2.MEMORY_COMPACT_TRIGGER_BYTES)
|
|
10858
|
+
return false;
|
|
10859
|
+
const lines = content.split("\n").filter((l) => l.trim().length > 0);
|
|
10860
|
+
if (lines.length < 8)
|
|
10861
|
+
return false;
|
|
10862
|
+
const cut = Math.floor(lines.length / 2);
|
|
10863
|
+
const older = lines.slice(0, cut);
|
|
10864
|
+
const recent = lines.slice(cut);
|
|
10865
|
+
const prompt2 = `Consolidate these persistent-memory bullet entries into a shorter set of bullets, merging duplicates/near-duplicates and dropping anything clearly stale or superseded by a later entry. Keep each resulting bullet under ${exports2.MEMORY_ENTRY_MAX_CHARS} characters, one fact per line, prefixed "- [YYYY-MM-DD] " using the LATEST date among the entries it draws from. Output ONLY the bullet lines, nothing else.
|
|
10866
|
+
|
|
10867
|
+
${older.join("\n")}`;
|
|
10868
|
+
let summarized;
|
|
10869
|
+
try {
|
|
10870
|
+
summarized = (await summarize(prompt2)).trim();
|
|
10871
|
+
} catch {
|
|
10872
|
+
return false;
|
|
10873
|
+
}
|
|
10874
|
+
if (!summarized)
|
|
10875
|
+
return false;
|
|
10876
|
+
const next = summarized + "\n" + recent.join("\n");
|
|
10877
|
+
writeMemoryFile(file, Buffer.byteLength(next, "utf-8") > exports2.MEMORY_MAX_BYTES ? evictOldest(next, exports2.MEMORY_MAX_BYTES) : next);
|
|
10878
|
+
return true;
|
|
10879
|
+
});
|
|
10880
|
+
}
|
|
10881
|
+
}
|
|
10882
|
+
});
|
|
10883
|
+
|
|
10884
|
+
// ../core/dist/plugins/index.js
|
|
10885
|
+
var require_plugins = __commonJS({
|
|
10886
|
+
"../core/dist/plugins/index.js"(exports2) {
|
|
10887
|
+
"use strict";
|
|
10888
|
+
var __createBinding = exports2 && exports2.__createBinding || (Object.create ? function(o, m2, k, k2) {
|
|
10889
|
+
if (k2 === void 0)
|
|
10890
|
+
k2 = k;
|
|
10891
|
+
var desc = Object.getOwnPropertyDescriptor(m2, k);
|
|
10892
|
+
if (!desc || ("get" in desc ? !m2.__esModule : desc.writable || desc.configurable)) {
|
|
10893
|
+
desc = { enumerable: true, get: function() {
|
|
10894
|
+
return m2[k];
|
|
10895
|
+
} };
|
|
10896
|
+
}
|
|
10897
|
+
Object.defineProperty(o, k2, desc);
|
|
10898
|
+
} : function(o, m2, k, k2) {
|
|
10899
|
+
if (k2 === void 0)
|
|
10900
|
+
k2 = k;
|
|
10901
|
+
o[k2] = m2[k];
|
|
10902
|
+
});
|
|
10903
|
+
var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? function(o, v) {
|
|
10904
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
10905
|
+
} : function(o, v) {
|
|
10906
|
+
o["default"] = v;
|
|
10907
|
+
});
|
|
10908
|
+
var __importStar = exports2 && exports2.__importStar || /* @__PURE__ */ function() {
|
|
10909
|
+
var ownKeys = function(o) {
|
|
10910
|
+
ownKeys = Object.getOwnPropertyNames || function(o2) {
|
|
10911
|
+
var ar = [];
|
|
10912
|
+
for (var k in o2)
|
|
10913
|
+
if (Object.prototype.hasOwnProperty.call(o2, k))
|
|
10914
|
+
ar[ar.length] = k;
|
|
10915
|
+
return ar;
|
|
10916
|
+
};
|
|
10917
|
+
return ownKeys(o);
|
|
10918
|
+
};
|
|
10919
|
+
return function(mod) {
|
|
10920
|
+
if (mod && mod.__esModule)
|
|
10921
|
+
return mod;
|
|
10922
|
+
var result = {};
|
|
10923
|
+
if (mod != null) {
|
|
10924
|
+
for (var k = ownKeys(mod), i2 = 0; i2 < k.length; i2++)
|
|
10925
|
+
if (k[i2] !== "default")
|
|
10926
|
+
__createBinding(result, mod, k[i2]);
|
|
10927
|
+
}
|
|
10928
|
+
__setModuleDefault(result, mod);
|
|
10929
|
+
return result;
|
|
10930
|
+
};
|
|
10931
|
+
}();
|
|
10932
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
10933
|
+
exports2.loadPlugins = loadPlugins3;
|
|
10934
|
+
exports2.pluginAssetDirs = pluginAssetDirs;
|
|
10935
|
+
exports2.pluginHooks = pluginHooks;
|
|
10936
|
+
exports2.pluginMcpServers = pluginMcpServers;
|
|
10937
|
+
var fs6 = __importStar(require("fs"));
|
|
10938
|
+
var path5 = __importStar(require("path"));
|
|
10939
|
+
var os5 = __importStar(require("os"));
|
|
10940
|
+
function readMeta(dir) {
|
|
10941
|
+
try {
|
|
10942
|
+
const raw = fs6.readFileSync(path5.join(dir, "plugin.json"), "utf-8");
|
|
10943
|
+
const j = JSON.parse(raw);
|
|
10944
|
+
return {
|
|
10945
|
+
name: typeof j.name === "string" ? j.name : void 0,
|
|
10946
|
+
version: typeof j.version === "string" ? j.version : void 0,
|
|
10947
|
+
description: typeof j.description === "string" ? j.description : void 0
|
|
10948
|
+
};
|
|
10949
|
+
} catch {
|
|
10950
|
+
return {};
|
|
10951
|
+
}
|
|
10952
|
+
}
|
|
10953
|
+
function scanRoot(root, scope, into) {
|
|
10954
|
+
let entries;
|
|
10955
|
+
try {
|
|
10956
|
+
entries = fs6.readdirSync(root, { withFileTypes: true });
|
|
10957
|
+
} catch {
|
|
10958
|
+
return;
|
|
10959
|
+
}
|
|
10960
|
+
for (const entry of entries) {
|
|
10961
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
10962
|
+
continue;
|
|
10963
|
+
const dir = path5.join(root, entry.name);
|
|
10964
|
+
try {
|
|
10965
|
+
if (!fs6.statSync(dir).isDirectory())
|
|
10966
|
+
continue;
|
|
10967
|
+
} catch {
|
|
10968
|
+
continue;
|
|
10969
|
+
}
|
|
10970
|
+
const meta = readMeta(dir);
|
|
10971
|
+
const name = meta.name || entry.name;
|
|
10972
|
+
if (into.has(name))
|
|
10973
|
+
continue;
|
|
10974
|
+
into.set(name, { name, version: meta.version, description: meta.description, dir, scope });
|
|
10975
|
+
}
|
|
10976
|
+
}
|
|
10977
|
+
function loadPlugins3(workDir) {
|
|
10978
|
+
const out = /* @__PURE__ */ new Map();
|
|
10979
|
+
scanRoot(path5.join(workDir, ".nexrall", "plugins"), "project", out);
|
|
10980
|
+
scanRoot(path5.join(os5.homedir(), ".nexrall", "plugins"), "global", out);
|
|
10981
|
+
return [...out.values()];
|
|
10982
|
+
}
|
|
10983
|
+
function pluginAssetDirs(workDir, kind) {
|
|
10984
|
+
return loadPlugins3(workDir).map((p) => path5.join(p.dir, kind)).filter((d) => {
|
|
10985
|
+
try {
|
|
10986
|
+
return fs6.statSync(d).isDirectory();
|
|
10987
|
+
} catch {
|
|
10988
|
+
return false;
|
|
10989
|
+
}
|
|
10990
|
+
});
|
|
10991
|
+
}
|
|
10992
|
+
function pluginHooks(workDir) {
|
|
10993
|
+
const merged = {};
|
|
10994
|
+
for (const p of loadPlugins3(workDir)) {
|
|
10995
|
+
try {
|
|
10996
|
+
const raw = fs6.readFileSync(path5.join(p.dir, "hooks.json"), "utf-8");
|
|
10997
|
+
const j = JSON.parse(raw);
|
|
10998
|
+
const hooks = j.hooks ?? j;
|
|
10999
|
+
for (const [phase, entries] of Object.entries(hooks)) {
|
|
11000
|
+
if (!Array.isArray(entries))
|
|
11001
|
+
continue;
|
|
11002
|
+
merged[phase] = [...merged[phase] ?? [], ...entries];
|
|
11003
|
+
}
|
|
11004
|
+
} catch {
|
|
11005
|
+
}
|
|
11006
|
+
}
|
|
11007
|
+
return merged;
|
|
11008
|
+
}
|
|
11009
|
+
function pluginMcpServers(workDir) {
|
|
11010
|
+
const merged = {};
|
|
11011
|
+
for (const p of loadPlugins3(workDir)) {
|
|
11012
|
+
try {
|
|
11013
|
+
const raw = fs6.readFileSync(path5.join(p.dir, "mcp.json"), "utf-8");
|
|
11014
|
+
const j = JSON.parse(raw);
|
|
11015
|
+
const servers = j.mcpServers ?? j;
|
|
11016
|
+
for (const [name, cfg] of Object.entries(servers)) {
|
|
11017
|
+
if (!(name in merged))
|
|
11018
|
+
merged[name] = cfg;
|
|
11019
|
+
}
|
|
11020
|
+
} catch {
|
|
11021
|
+
}
|
|
11022
|
+
}
|
|
11023
|
+
return merged;
|
|
11024
|
+
}
|
|
11025
|
+
}
|
|
11026
|
+
});
|
|
11027
|
+
|
|
11028
|
+
// ../core/dist/commands/loader.js
|
|
11029
|
+
var require_loader = __commonJS({
|
|
11030
|
+
"../core/dist/commands/loader.js"(exports2) {
|
|
11031
|
+
"use strict";
|
|
11032
|
+
var __createBinding = exports2 && exports2.__createBinding || (Object.create ? function(o, m2, k, k2) {
|
|
11033
|
+
if (k2 === void 0)
|
|
11034
|
+
k2 = k;
|
|
11035
|
+
var desc = Object.getOwnPropertyDescriptor(m2, k);
|
|
11036
|
+
if (!desc || ("get" in desc ? !m2.__esModule : desc.writable || desc.configurable)) {
|
|
11037
|
+
desc = { enumerable: true, get: function() {
|
|
11038
|
+
return m2[k];
|
|
11039
|
+
} };
|
|
11040
|
+
}
|
|
11041
|
+
Object.defineProperty(o, k2, desc);
|
|
11042
|
+
} : function(o, m2, k, k2) {
|
|
11043
|
+
if (k2 === void 0)
|
|
11044
|
+
k2 = k;
|
|
11045
|
+
o[k2] = m2[k];
|
|
11046
|
+
});
|
|
11047
|
+
var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? function(o, v) {
|
|
11048
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
11049
|
+
} : function(o, v) {
|
|
11050
|
+
o["default"] = v;
|
|
11051
|
+
});
|
|
11052
|
+
var __importStar = exports2 && exports2.__importStar || /* @__PURE__ */ function() {
|
|
11053
|
+
var ownKeys = function(o) {
|
|
11054
|
+
ownKeys = Object.getOwnPropertyNames || function(o2) {
|
|
11055
|
+
var ar = [];
|
|
11056
|
+
for (var k in o2)
|
|
11057
|
+
if (Object.prototype.hasOwnProperty.call(o2, k))
|
|
11058
|
+
ar[ar.length] = k;
|
|
11059
|
+
return ar;
|
|
11060
|
+
};
|
|
11061
|
+
return ownKeys(o);
|
|
11062
|
+
};
|
|
11063
|
+
return function(mod) {
|
|
11064
|
+
if (mod && mod.__esModule)
|
|
11065
|
+
return mod;
|
|
11066
|
+
var result = {};
|
|
11067
|
+
if (mod != null) {
|
|
11068
|
+
for (var k = ownKeys(mod), i2 = 0; i2 < k.length; i2++)
|
|
11069
|
+
if (k[i2] !== "default")
|
|
11070
|
+
__createBinding(result, mod, k[i2]);
|
|
11071
|
+
}
|
|
11072
|
+
__setModuleDefault(result, mod);
|
|
11073
|
+
return result;
|
|
11074
|
+
};
|
|
11075
|
+
}();
|
|
11076
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
11077
|
+
exports2.loadSlashCommands = loadSlashCommands;
|
|
11078
|
+
exports2.findSlashCommand = findSlashCommand;
|
|
11079
|
+
exports2.expandBody = expandBody;
|
|
11080
|
+
exports2.expandCommand = expandCommand;
|
|
11081
|
+
var fs6 = __importStar(require("fs"));
|
|
11082
|
+
var path5 = __importStar(require("path"));
|
|
11083
|
+
var os5 = __importStar(require("os"));
|
|
11084
|
+
var child_process_1 = require("child_process");
|
|
11085
|
+
var index_1 = require_plugins();
|
|
11086
|
+
var BUILTIN_COMMANDS = [
|
|
11087
|
+
{
|
|
11088
|
+
name: "review",
|
|
11089
|
+
description: "Review uncommitted changes (or a PR/branch diff) for bugs and risks",
|
|
11090
|
+
source: "builtin",
|
|
11091
|
+
body: [
|
|
11092
|
+
"Review the following diff like a meticulous senior engineer. Target: $ARGUMENTS",
|
|
11093
|
+
"(If no target given, review the uncommitted working-tree changes below. If a branch or PR",
|
|
11094
|
+
"number is given, run the appropriate `git diff <base>...` or `gh pr diff <n>` yourself first.)",
|
|
11095
|
+
"",
|
|
11096
|
+
"Branch: !`git branch --show-current`",
|
|
11097
|
+
"Status: !`git status --short`",
|
|
11098
|
+
"",
|
|
11099
|
+
"Diff (uncommitted):",
|
|
11100
|
+
"```diff",
|
|
11101
|
+
"!`git diff HEAD --unified=5 --no-color | head -4000`",
|
|
11102
|
+
"```",
|
|
11103
|
+
"",
|
|
11104
|
+
"Review methodology:",
|
|
11105
|
+
"1. Read the surrounding code of every changed hunk (read_file with offset/limit) \u2014 never judge a hunk in isolation.",
|
|
11106
|
+
"2. Look for: correctness bugs, edge cases (empty/null/unicode/concurrency), security issues",
|
|
11107
|
+
" (injection, path traversal, secrets), breaking API changes (find_references / search callers),",
|
|
11108
|
+
" silent behaviour changes, and missing error handling.",
|
|
11109
|
+
"3. Check tests: do existing tests cover the change? Are assertions weakened?",
|
|
11110
|
+
"",
|
|
11111
|
+
"Output format:",
|
|
11112
|
+
"- \u{1F534} Critical (must fix before merge) \u2014 with file:line and a concrete fix",
|
|
11113
|
+
"- \u{1F7E1} Warning (should fix) \u2014 with file:line",
|
|
11114
|
+
"- \u{1F7E2} Suggestion (nice to have)",
|
|
11115
|
+
"- Verdict: APPROVE / REQUEST CHANGES with a one-paragraph summary.",
|
|
11116
|
+
"Do NOT modify any files \u2014 this is a read-only review."
|
|
11117
|
+
].join("\n")
|
|
11118
|
+
}
|
|
11119
|
+
];
|
|
11120
|
+
function parseFrontmatter(raw) {
|
|
11121
|
+
const m2 = /^\s*---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/.exec(raw);
|
|
11122
|
+
if (!m2)
|
|
11123
|
+
return { meta: {}, body: raw.trim() };
|
|
11124
|
+
const meta = {};
|
|
11125
|
+
for (const line of m2[1].split(/\r?\n/)) {
|
|
11126
|
+
const kv = /^([A-Za-z0-9_-]+)\s*:\s*(.*)$/.exec(line.trim());
|
|
11127
|
+
if (kv)
|
|
11128
|
+
meta[kv[1].toLowerCase()] = kv[2].trim().replace(/^["']|["']$/g, "");
|
|
11129
|
+
}
|
|
11130
|
+
return { meta, body: (m2[2] ?? "").trim() };
|
|
11131
|
+
}
|
|
11132
|
+
function loadDir(dir, source, into) {
|
|
11133
|
+
let files;
|
|
11134
|
+
try {
|
|
11135
|
+
files = fs6.readdirSync(dir).filter((f3) => f3.endsWith(".md"));
|
|
11136
|
+
} catch {
|
|
11137
|
+
return;
|
|
11138
|
+
}
|
|
11139
|
+
for (const file of files) {
|
|
11140
|
+
try {
|
|
11141
|
+
const raw = fs6.readFileSync(path5.join(dir, file), "utf-8");
|
|
11142
|
+
const { meta, body } = parseFrontmatter(raw);
|
|
11143
|
+
const name = (meta.name || path5.basename(file, ".md")).trim().toLowerCase();
|
|
11144
|
+
if (!name)
|
|
11145
|
+
continue;
|
|
11146
|
+
if (source !== "project" && into.has(name))
|
|
11147
|
+
continue;
|
|
11148
|
+
const model = ["turbo", "pro", "ultra"].find((x2) => x2 === (meta.model ?? "").toLowerCase());
|
|
11149
|
+
into.set(name, {
|
|
11150
|
+
name,
|
|
11151
|
+
description: meta.description || `Custom /${name} command`,
|
|
11152
|
+
model,
|
|
11153
|
+
mode: meta.mode || void 0,
|
|
11154
|
+
body,
|
|
11155
|
+
source
|
|
11156
|
+
});
|
|
11157
|
+
} catch {
|
|
11158
|
+
}
|
|
11159
|
+
}
|
|
11160
|
+
}
|
|
11161
|
+
function loadSlashCommands(workDir) {
|
|
11162
|
+
const out = /* @__PURE__ */ new Map();
|
|
11163
|
+
loadDir(path5.join(workDir, ".nexrall", "commands"), "project", out);
|
|
11164
|
+
loadDir(path5.join(os5.homedir(), ".nexrall", "commands"), "global", out);
|
|
11165
|
+
for (const dir of (0, index_1.pluginAssetDirs)(workDir, "commands"))
|
|
11166
|
+
loadDir(dir, "plugin", out);
|
|
11167
|
+
for (const cmd of BUILTIN_COMMANDS) {
|
|
11168
|
+
if (!out.has(cmd.name))
|
|
11169
|
+
out.set(cmd.name, cmd);
|
|
11170
|
+
}
|
|
11171
|
+
return [...out.values()];
|
|
11172
|
+
}
|
|
11173
|
+
function findSlashCommand(cmds, name) {
|
|
11174
|
+
const want = name.replace(/^\//, "").trim().toLowerCase();
|
|
11175
|
+
return cmds.find((c) => c.name === want);
|
|
11176
|
+
}
|
|
11177
|
+
function expandBody(body, argString, workDir) {
|
|
11178
|
+
const args = argString.trim();
|
|
11179
|
+
const positional = args ? args.split(/\s+/) : [];
|
|
11180
|
+
let out = body;
|
|
11181
|
+
out = out.replace(/!`([^`]+)`/g, (_m, c) => {
|
|
11182
|
+
try {
|
|
11183
|
+
const stdout = (0, child_process_1.execSync)(c, { cwd: workDir, encoding: "utf-8", timeout: 15e3, stdio: ["ignore", "pipe", "pipe"] });
|
|
11184
|
+
return stdout.trim();
|
|
11185
|
+
} catch (err) {
|
|
11186
|
+
return `[command failed: ${c} \u2014 ${err.message}]`;
|
|
11187
|
+
}
|
|
11188
|
+
});
|
|
11189
|
+
out = out.replace(/(^|\s)@([^\s]+)/g, (_m, lead, rel) => {
|
|
11190
|
+
const abs = path5.isAbsolute(rel) ? rel : path5.join(workDir, rel);
|
|
11191
|
+
try {
|
|
11192
|
+
const content = fs6.readFileSync(abs, "utf-8").slice(0, 12e3);
|
|
11193
|
+
return `${lead}
|
|
11194
|
+
[File: ${rel}]
|
|
11195
|
+
\`\`\`
|
|
11196
|
+
${content}
|
|
11197
|
+
\`\`\`
|
|
11198
|
+
`;
|
|
11199
|
+
} catch {
|
|
11200
|
+
return `${lead}[missing file: ${rel}]`;
|
|
11201
|
+
}
|
|
11202
|
+
});
|
|
11203
|
+
out = out.replace(/\$(\d+)/g, (_m, n) => positional[Number(n) - 1] ?? "");
|
|
11204
|
+
const hadArgsToken = /\$ARGUMENTS/.test(out);
|
|
11205
|
+
out = out.replace(/\$ARGUMENTS/g, args);
|
|
11206
|
+
if (!hadArgsToken && !/\$\d+/.test(body) && args) {
|
|
11207
|
+
out = `${out}
|
|
11208
|
+
|
|
11209
|
+
${args}`;
|
|
11210
|
+
}
|
|
11211
|
+
return out.trim();
|
|
11212
|
+
}
|
|
11213
|
+
function expandCommand(cmd, argString, workDir) {
|
|
11214
|
+
return expandBody(cmd.body, argString, workDir);
|
|
11215
|
+
}
|
|
11216
|
+
}
|
|
11217
|
+
});
|
|
11218
|
+
|
|
11219
|
+
// ../core/dist/agent/skills.js
|
|
11220
|
+
var require_skills = __commonJS({
|
|
11221
|
+
"../core/dist/agent/skills.js"(exports2) {
|
|
11222
|
+
"use strict";
|
|
11223
|
+
var __createBinding = exports2 && exports2.__createBinding || (Object.create ? function(o, m2, k, k2) {
|
|
11224
|
+
if (k2 === void 0)
|
|
11225
|
+
k2 = k;
|
|
11226
|
+
var desc = Object.getOwnPropertyDescriptor(m2, k);
|
|
11227
|
+
if (!desc || ("get" in desc ? !m2.__esModule : desc.writable || desc.configurable)) {
|
|
11228
|
+
desc = { enumerable: true, get: function() {
|
|
11229
|
+
return m2[k];
|
|
11230
|
+
} };
|
|
11231
|
+
}
|
|
11232
|
+
Object.defineProperty(o, k2, desc);
|
|
11233
|
+
} : function(o, m2, k, k2) {
|
|
11234
|
+
if (k2 === void 0)
|
|
11235
|
+
k2 = k;
|
|
11236
|
+
o[k2] = m2[k];
|
|
11237
|
+
});
|
|
11238
|
+
var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? function(o, v) {
|
|
11239
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
11240
|
+
} : function(o, v) {
|
|
11241
|
+
o["default"] = v;
|
|
11242
|
+
});
|
|
11243
|
+
var __importStar = exports2 && exports2.__importStar || /* @__PURE__ */ function() {
|
|
11244
|
+
var ownKeys = function(o) {
|
|
11245
|
+
ownKeys = Object.getOwnPropertyNames || function(o2) {
|
|
11246
|
+
var ar = [];
|
|
11247
|
+
for (var k in o2)
|
|
11248
|
+
if (Object.prototype.hasOwnProperty.call(o2, k))
|
|
11249
|
+
ar[ar.length] = k;
|
|
11250
|
+
return ar;
|
|
11251
|
+
};
|
|
11252
|
+
return ownKeys(o);
|
|
11253
|
+
};
|
|
11254
|
+
return function(mod) {
|
|
11255
|
+
if (mod && mod.__esModule)
|
|
11256
|
+
return mod;
|
|
11257
|
+
var result = {};
|
|
11258
|
+
if (mod != null) {
|
|
11259
|
+
for (var k = ownKeys(mod), i2 = 0; i2 < k.length; i2++)
|
|
11260
|
+
if (k[i2] !== "default")
|
|
11261
|
+
__createBinding(result, mod, k[i2]);
|
|
11262
|
+
}
|
|
11263
|
+
__setModuleDefault(result, mod);
|
|
11264
|
+
return result;
|
|
11265
|
+
};
|
|
11266
|
+
}();
|
|
11267
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
11268
|
+
exports2.loadSkills = loadSkills2;
|
|
11269
|
+
exports2.findSkill = findSkill2;
|
|
11270
|
+
exports2.autoInvokableSkills = autoInvokableSkills;
|
|
11271
|
+
exports2.userInvokableSkills = userInvokableSkills2;
|
|
11272
|
+
exports2.summariseSkills = summariseSkills;
|
|
11273
|
+
exports2.expandSkill = expandSkill2;
|
|
11274
|
+
var fs6 = __importStar(require("fs"));
|
|
11275
|
+
var path5 = __importStar(require("path"));
|
|
11276
|
+
var os5 = __importStar(require("os"));
|
|
11277
|
+
var index_1 = require_plugins();
|
|
11278
|
+
var loader_1 = require_loader();
|
|
11279
|
+
function parseFrontmatter(raw) {
|
|
11280
|
+
const m2 = /^\s*---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/.exec(raw);
|
|
11281
|
+
if (!m2)
|
|
11282
|
+
return { meta: {}, body: raw.trim() };
|
|
11283
|
+
const meta = {};
|
|
11284
|
+
for (const line of m2[1].split(/\r?\n/)) {
|
|
11285
|
+
const kv = /^([A-Za-z0-9_-]+)\s*:\s*(.*)$/.exec(line.trim());
|
|
11286
|
+
if (kv)
|
|
11287
|
+
meta[kv[1].toLowerCase()] = kv[2].trim().replace(/^["']|["']$/g, "");
|
|
11288
|
+
}
|
|
11289
|
+
return { meta, body: (m2[2] ?? "").trim() };
|
|
11290
|
+
}
|
|
11291
|
+
function parseBool(v, fallback) {
|
|
11292
|
+
if (v === void 0)
|
|
11293
|
+
return fallback;
|
|
11294
|
+
const s2 = v.trim().toLowerCase();
|
|
11295
|
+
if (s2 === "true" || s2 === "1" || s2 === "yes")
|
|
11296
|
+
return true;
|
|
11297
|
+
if (s2 === "false" || s2 === "0" || s2 === "no")
|
|
11298
|
+
return false;
|
|
11299
|
+
return fallback;
|
|
11300
|
+
}
|
|
11301
|
+
function toSkill(meta, body, name, source, dir) {
|
|
11302
|
+
const model = ["turbo", "pro", "ultra"].find((x2) => x2 === (meta.model ?? "").toLowerCase());
|
|
11303
|
+
return {
|
|
11304
|
+
name,
|
|
11305
|
+
description: meta.description || `Custom /${name} skill`,
|
|
11306
|
+
model,
|
|
11307
|
+
mode: meta.mode || void 0,
|
|
11308
|
+
body,
|
|
11309
|
+
source,
|
|
11310
|
+
dir,
|
|
11311
|
+
disableModelInvocation: parseBool(meta["disable-model-invocation"], false),
|
|
11312
|
+
userInvocable: parseBool(meta["user-invocable"], true)
|
|
11313
|
+
};
|
|
11314
|
+
}
|
|
11315
|
+
function loadFlatCommandDir(dir, source, into) {
|
|
11316
|
+
let files;
|
|
11317
|
+
try {
|
|
11318
|
+
files = fs6.readdirSync(dir).filter((f3) => f3.endsWith(".md"));
|
|
11319
|
+
} catch {
|
|
11320
|
+
return;
|
|
11321
|
+
}
|
|
11322
|
+
for (const file of files) {
|
|
11323
|
+
try {
|
|
11324
|
+
const raw = fs6.readFileSync(path5.join(dir, file), "utf-8");
|
|
11325
|
+
const { meta, body } = parseFrontmatter(raw);
|
|
11326
|
+
const name = (meta.name || path5.basename(file, ".md")).trim().toLowerCase();
|
|
11327
|
+
if (!name)
|
|
11328
|
+
continue;
|
|
11329
|
+
if (source !== "project" && into.has(name))
|
|
11330
|
+
continue;
|
|
11331
|
+
into.set(name, toSkill(meta, body, name, source));
|
|
11332
|
+
} catch {
|
|
11333
|
+
}
|
|
11334
|
+
}
|
|
11335
|
+
}
|
|
11336
|
+
function loadSkillDir(root, source, into) {
|
|
11337
|
+
let entries;
|
|
11338
|
+
try {
|
|
11339
|
+
entries = fs6.readdirSync(root, { withFileTypes: true });
|
|
11340
|
+
} catch {
|
|
11341
|
+
return;
|
|
11342
|
+
}
|
|
11343
|
+
for (const entry of entries) {
|
|
11344
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
11345
|
+
continue;
|
|
11346
|
+
const skillDir = path5.join(root, entry.name);
|
|
11347
|
+
const skillFile = path5.join(skillDir, "SKILL.md");
|
|
11348
|
+
try {
|
|
11349
|
+
if (!fs6.statSync(skillFile).isFile())
|
|
11350
|
+
continue;
|
|
11351
|
+
} catch {
|
|
11352
|
+
continue;
|
|
11353
|
+
}
|
|
11354
|
+
try {
|
|
11355
|
+
const raw = fs6.readFileSync(skillFile, "utf-8");
|
|
11356
|
+
const { meta, body } = parseFrontmatter(raw);
|
|
11357
|
+
const name = (meta.name || entry.name).trim().toLowerCase();
|
|
11358
|
+
if (!name)
|
|
11359
|
+
continue;
|
|
11360
|
+
if (source !== "project" && into.has(name))
|
|
11361
|
+
continue;
|
|
11362
|
+
into.set(name, toSkill(meta, body, name, source, skillDir));
|
|
11363
|
+
} catch {
|
|
11364
|
+
}
|
|
11365
|
+
}
|
|
11366
|
+
}
|
|
11367
|
+
var BUILTIN_SKILLS = [
|
|
11368
|
+
{
|
|
11369
|
+
name: "review",
|
|
11370
|
+
description: "Review uncommitted changes (or a PR/branch diff) for correctness bugs, edge cases, and security issues. Use when the user asks to review a diff, check their changes before committing, or audit a PR.",
|
|
11371
|
+
source: "builtin",
|
|
11372
|
+
disableModelInvocation: false,
|
|
11373
|
+
userInvocable: true,
|
|
11374
|
+
body: [
|
|
11375
|
+
"Review the following diff like a meticulous senior engineer. Target: $ARGUMENTS",
|
|
11376
|
+
"(If no target given, review the uncommitted working-tree changes below. If a branch or PR",
|
|
11377
|
+
"number is given, run the appropriate `git diff <base>...` or `gh pr diff <n>` yourself first.)",
|
|
11378
|
+
"",
|
|
11379
|
+
"Branch: !`git branch --show-current`",
|
|
11380
|
+
"Status: !`git status --short`",
|
|
11381
|
+
"",
|
|
11382
|
+
"Diff (uncommitted):",
|
|
11383
|
+
"```diff",
|
|
11384
|
+
"!`git diff HEAD --unified=5 --no-color | head -4000`",
|
|
11385
|
+
"```",
|
|
11386
|
+
"",
|
|
11387
|
+
"Review methodology:",
|
|
11388
|
+
"1. Read the surrounding code of every changed hunk (read_file with offset/limit) \u2014 never judge a hunk in isolation.",
|
|
11389
|
+
"2. Look for: correctness bugs, edge cases (empty/null/unicode/concurrency), security issues",
|
|
11390
|
+
" (injection, path traversal, secrets), breaking API changes (find_references / search callers),",
|
|
11391
|
+
" silent behaviour changes, and missing error handling.",
|
|
11392
|
+
"3. Check tests: do existing tests cover the change? Are assertions weakened?",
|
|
11393
|
+
"",
|
|
11394
|
+
"Output format:",
|
|
11395
|
+
"- \u{1F534} Critical (must fix before merge) \u2014 with file:line and a concrete fix",
|
|
11396
|
+
"- \u{1F7E1} Warning (should fix) \u2014 with file:line",
|
|
11397
|
+
"- \u{1F7E2} Suggestion (nice to have)",
|
|
11398
|
+
"- Verdict: APPROVE / REQUEST CHANGES with a one-paragraph summary.",
|
|
11399
|
+
"Do NOT modify any files \u2014 this is a read-only review."
|
|
11400
|
+
].join("\n")
|
|
11401
|
+
}
|
|
11402
|
+
];
|
|
11403
|
+
function loadSkills2(workDir) {
|
|
11404
|
+
const out = /* @__PURE__ */ new Map();
|
|
11405
|
+
loadFlatCommandDir(path5.join(workDir, ".nexrall", "commands"), "project", out);
|
|
11406
|
+
loadSkillDir(path5.join(workDir, ".nexrall", "skills"), "project", out);
|
|
11407
|
+
loadFlatCommandDir(path5.join(os5.homedir(), ".nexrall", "commands"), "global", out);
|
|
11408
|
+
loadSkillDir(path5.join(os5.homedir(), ".nexrall", "skills"), "global", out);
|
|
11409
|
+
for (const dir of (0, index_1.pluginAssetDirs)(workDir, "commands"))
|
|
11410
|
+
loadFlatCommandDir(dir, "plugin", out);
|
|
11411
|
+
for (const dir of (0, index_1.pluginAssetDirs)(workDir, "skills"))
|
|
11412
|
+
loadSkillDir(dir, "plugin", out);
|
|
11413
|
+
for (const skill of BUILTIN_SKILLS) {
|
|
11414
|
+
if (!out.has(skill.name))
|
|
11415
|
+
out.set(skill.name, skill);
|
|
11416
|
+
}
|
|
11417
|
+
return [...out.values()];
|
|
11418
|
+
}
|
|
11419
|
+
function findSkill2(skills, name) {
|
|
11420
|
+
const want = name.replace(/^\//, "").trim().toLowerCase();
|
|
11421
|
+
return skills.find((s2) => s2.name === want);
|
|
11422
|
+
}
|
|
11423
|
+
function autoInvokableSkills(skills) {
|
|
11424
|
+
return skills.filter((s2) => !s2.disableModelInvocation);
|
|
11425
|
+
}
|
|
11426
|
+
function userInvokableSkills2(skills) {
|
|
11427
|
+
return skills.filter((s2) => s2.userInvocable);
|
|
11428
|
+
}
|
|
11429
|
+
function summariseSkills(skills) {
|
|
11430
|
+
const list = autoInvokableSkills(skills);
|
|
11431
|
+
if (!list.length)
|
|
11432
|
+
return "";
|
|
11433
|
+
return list.map((s2) => `- ${s2.name}: ${s2.description}`).join("\n");
|
|
11434
|
+
}
|
|
11435
|
+
function expandSkill2(skill, argString, workDir) {
|
|
11436
|
+
const expanded = (0, loader_1.expandBody)(skill.body, argString, workDir);
|
|
11437
|
+
if (!skill.dir)
|
|
11438
|
+
return expanded;
|
|
11439
|
+
let siblings = [];
|
|
11440
|
+
try {
|
|
11441
|
+
siblings = fs6.readdirSync(skill.dir).filter((f3) => f3 !== "SKILL.md");
|
|
11442
|
+
} catch {
|
|
11443
|
+
}
|
|
11444
|
+
if (!siblings.length)
|
|
11445
|
+
return expanded;
|
|
11446
|
+
return `[Skill "${skill.name}" \u2014 supporting files available in ${skill.dir} (use read_file with an absolute path to load any of these only if the instructions below need them): ${siblings.join(", ")}]
|
|
11447
|
+
|
|
11448
|
+
${expanded}`;
|
|
11449
|
+
}
|
|
11450
|
+
}
|
|
11451
|
+
});
|
|
11452
|
+
|
|
11453
|
+
// ../core/dist/agent/testIntegrity.js
|
|
11454
|
+
var require_testIntegrity = __commonJS({
|
|
11455
|
+
"../core/dist/agent/testIntegrity.js"(exports2) {
|
|
11456
|
+
"use strict";
|
|
11457
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
11458
|
+
exports2.isTestFile = isTestFile;
|
|
11459
|
+
exports2.analyzeTestEdit = analyzeTestEdit;
|
|
11460
|
+
exports2.analyzeWriteToolForTestIntegrity = analyzeWriteToolForTestIntegrity;
|
|
11461
|
+
exports2.encodeTestIntegrityMarker = encodeTestIntegrityMarker;
|
|
11462
|
+
exports2.decodeTestIntegrityMarker = decodeTestIntegrityMarker;
|
|
11463
|
+
exports2.stripTestIntegrityMarker = stripTestIntegrityMarker;
|
|
11464
|
+
var TEST_PATH_RE = /(?:^|[\/\\])(?:tests?|spec|__tests__|testing)[\/\\]|(?:\.|_|-)(?:test|spec|tests)\.[a-z]+$|(?:^|[\/\\])test_[^\/\\]+\.py$|(?:^|[\/\\])[^\/\\]+_test\.(?:go|py|rb)$|Test[^\/\\]*\.(?:java|kt|cs)$|(?:^|[\/\\])[^\/\\]*Tests?\.(?:java|kt|cs)$/i;
|
|
11465
|
+
function isTestFile(path5) {
|
|
11466
|
+
if (!path5 || typeof path5 !== "string")
|
|
11467
|
+
return false;
|
|
11468
|
+
return TEST_PATH_RE.test(path5);
|
|
11469
|
+
}
|
|
11470
|
+
var ASSERTION_RES = [
|
|
11471
|
+
/\bexpect\s*\(/g,
|
|
11472
|
+
// jest / chai / vitest / jasmine
|
|
11473
|
+
/\bassert(?:Equals?|True|False|That|Null|NotNull|Same)?\b/g,
|
|
11474
|
+
// junit / python unittest / generic
|
|
11475
|
+
/\bassert\s*[.(]/g,
|
|
11476
|
+
// node:assert (assert.equal / assert( ) )
|
|
11477
|
+
/\.should\b/g,
|
|
11478
|
+
// chai should / rspec should
|
|
11479
|
+
/\bshould\s*[.(]/g,
|
|
11480
|
+
/\bEXPECT_[A-Z]+\s*\(/g,
|
|
11481
|
+
// gtest
|
|
11482
|
+
/\bASSERT_[A-Z]+\s*\(/g,
|
|
11483
|
+
// gtest
|
|
11484
|
+
/\brequire\.\w+\s*\(/g,
|
|
11485
|
+
// testify require
|
|
11486
|
+
/\bassert\.\w+\s*\(/g,
|
|
11487
|
+
// testify assert
|
|
11488
|
+
/\bt\.(?:Error|Fatal|Errorf|Fatalf)\b/g,
|
|
11489
|
+
// go testing
|
|
11490
|
+
/\bassert!\s*\(|\bassert_eq!\s*\(|\bassert_ne!\s*\(/g
|
|
11491
|
+
// rust
|
|
11492
|
+
];
|
|
11493
|
+
var TESTCASE_RES = [
|
|
11494
|
+
/\b(?:it|test)\s*\(/g,
|
|
11495
|
+
// jest / mocha / jasmine
|
|
11496
|
+
/\bdef\s+test_\w+/g,
|
|
11497
|
+
// python
|
|
11498
|
+
/\bfunc\s+Test\w+/g,
|
|
11499
|
+
// go
|
|
11500
|
+
/@Test\b/g,
|
|
11501
|
+
// junit
|
|
11502
|
+
/\bfn\s+\w*test\w*\s*\(/gi
|
|
11503
|
+
// rust (best-effort)
|
|
11504
|
+
];
|
|
11505
|
+
var SKIP_RES = [
|
|
11506
|
+
/\b(?:it|test|describe|context|suite)\s*\.\s*(?:skip|only)\b/g,
|
|
11507
|
+
/\bx(?:it|describe|test|context)\s*\(/g,
|
|
11508
|
+
// xit / xdescribe
|
|
11509
|
+
/\bf(?:it|describe)\s*\(/g,
|
|
11510
|
+
// fit / fdescribe (focus)
|
|
11511
|
+
/@pytest\.mark\.skip\b|@pytest\.mark\.skipif\b|@unittest\.skip\b|@skip\b/g,
|
|
11512
|
+
/\bpytest\.skip\s*\(/g,
|
|
11513
|
+
/\bt\.Skip\s*\(|\bt\.SkipNow\s*\(/g,
|
|
11514
|
+
// go
|
|
11515
|
+
/@Disabled\b|@Ignore\b/g,
|
|
11516
|
+
// junit / kotlin
|
|
11517
|
+
/\.only\s*\(/g
|
|
11518
|
+
// test.only leaks CI coverage
|
|
11519
|
+
];
|
|
11520
|
+
var TAUTOLOGY_RES = [
|
|
11521
|
+
/\bassert\s+True\b|\bassert\s+1\b|\bassert\s+not\s+False\b/g,
|
|
11522
|
+
// python
|
|
11523
|
+
/\bassert\s*\(\s*true\s*\)|\bassert\.ok\s*\(\s*true\s*\)/gi,
|
|
11524
|
+
// node
|
|
11525
|
+
/\bexpect\s*\(\s*true\s*\)\s*\.\s*to(?:Be|Equal|BeTruthy)\s*\(\s*true\s*\)?/gi,
|
|
11526
|
+
// jest
|
|
11527
|
+
/\bexpect\s*\(\s*(\w+)\s*\)\s*\.\s*toBe\s*\(\s*\1\s*\)/g,
|
|
11528
|
+
// expect(x).toBe(x)
|
|
11529
|
+
/\bassert_eq!\s*\(\s*true\s*,\s*true\s*\)/g,
|
|
11530
|
+
// rust
|
|
11531
|
+
/\bassert!\s*\(\s*true\s*\)/g
|
|
11532
|
+
];
|
|
11533
|
+
function countMatches(res, text) {
|
|
11534
|
+
let n = 0;
|
|
11535
|
+
for (const re of res) {
|
|
11536
|
+
re.lastIndex = 0;
|
|
11537
|
+
const m2 = text.match(re);
|
|
11538
|
+
if (m2)
|
|
11539
|
+
n += m2.length;
|
|
11540
|
+
}
|
|
11541
|
+
return n;
|
|
11542
|
+
}
|
|
11543
|
+
function countCommentedOutTestLines(oldText, newText) {
|
|
11544
|
+
const oldLines = new Set(oldText.split("\n").map((l) => l.trim()));
|
|
11545
|
+
let n = 0;
|
|
11546
|
+
for (const raw of newText.split("\n")) {
|
|
11547
|
+
const line = raw.trim();
|
|
11548
|
+
const isComment = /^(?:\/\/|#|\/\*|\*)/.test(line);
|
|
11549
|
+
if (!isComment)
|
|
11550
|
+
continue;
|
|
11551
|
+
const body = line.replace(/^(?:\/\/+|#+|\/\*+|\*+)\s?/, "");
|
|
11552
|
+
const looksLikeTest = /\b(expect|assert|it\(|test\(|def test_|func Test|EXPECT_|ASSERT_)\b/.test(body);
|
|
11553
|
+
if (looksLikeTest && oldLines.has(body))
|
|
11554
|
+
n += 1;
|
|
11555
|
+
}
|
|
11556
|
+
return n;
|
|
11557
|
+
}
|
|
11558
|
+
function analyzeTestEdit(path5, oldText, newText) {
|
|
11559
|
+
if (!isTestFile(path5)) {
|
|
11560
|
+
return { isTestFile: false, suspicious: false, findings: [] };
|
|
11561
|
+
}
|
|
11562
|
+
const findings = [];
|
|
11563
|
+
const skipsBefore = countMatches(SKIP_RES, oldText);
|
|
11564
|
+
const skipsAfter = countMatches(SKIP_RES, newText);
|
|
11565
|
+
if (skipsAfter > skipsBefore) {
|
|
11566
|
+
findings.push({
|
|
11567
|
+
kind: "skip-added",
|
|
11568
|
+
reason: `added ${skipsAfter - skipsBefore} skip/only/disabled marker(s) \u2014 tests are being skipped, not fixed`
|
|
11569
|
+
});
|
|
10480
11570
|
}
|
|
10481
11571
|
const tautoBefore = countMatches(TAUTOLOGY_RES, oldText);
|
|
10482
11572
|
const tautoAfter = countMatches(TAUTOLOGY_RES, newText);
|
|
@@ -11259,6 +12349,8 @@ var require_executor = __commonJS({
|
|
|
11259
12349
|
var sandbox_1 = require_sandbox();
|
|
11260
12350
|
var auth_1 = require_auth();
|
|
11261
12351
|
var editCompleteness_1 = require_editCompleteness();
|
|
12352
|
+
var memory_1 = require_memory();
|
|
12353
|
+
var skills_1 = require_skills();
|
|
11262
12354
|
var testIntegrity_1 = require_testIntegrity();
|
|
11263
12355
|
var crossFile_1 = require_crossFile();
|
|
11264
12356
|
var client_1 = require_client();
|
|
@@ -11268,6 +12360,10 @@ var require_executor = __commonJS({
|
|
|
11268
12360
|
var MAX_FETCH_BYTES = 200 * 1024;
|
|
11269
12361
|
var MAX_READ_BYTES = 500 * 1024;
|
|
11270
12362
|
var MAX_OUTPUT_CHARS = 1e5;
|
|
12363
|
+
var MAX_READ_LINES = 2e3;
|
|
12364
|
+
var MAX_READ_TOKENS = 25e3;
|
|
12365
|
+
var READ_CHARS_PER_TOKEN = 4;
|
|
12366
|
+
var MAX_READ_CHARS = MAX_READ_TOKENS * READ_CHARS_PER_TOKEN;
|
|
11271
12367
|
var MAX_SPILL_BYTES = 20 * 1024 * 1024;
|
|
11272
12368
|
var SPILL_DIR = path5.join(os5.tmpdir(), "nexrall-code", "bash-output");
|
|
11273
12369
|
var BLOCKED_REGEXES = [
|
|
@@ -11362,24 +12458,50 @@ var require_executor = __commonJS({
|
|
|
11362
12458
|
return false;
|
|
11363
12459
|
}
|
|
11364
12460
|
}
|
|
11365
|
-
|
|
12461
|
+
function formatNumberedLine(offset, indexInKept, line) {
|
|
12462
|
+
return `${String(offset + indexInKept + 1).padStart(4, " ")} ${line}`;
|
|
12463
|
+
}
|
|
12464
|
+
async function readFileWindowed(resolved, offset, requestedLimit) {
|
|
11366
12465
|
return new Promise((resolve3) => {
|
|
11367
|
-
const
|
|
12466
|
+
const explicitLimit = requestedLimit > 0;
|
|
12467
|
+
const lineScanCap = explicitLimit ? Math.min(requestedLimit, MAX_READ_LINES) : MAX_READ_LINES;
|
|
12468
|
+
const hardEndLine = offset + lineScanCap;
|
|
11368
12469
|
const kept = [];
|
|
12470
|
+
let keptChars = 0;
|
|
11369
12471
|
let lineNo = 0;
|
|
11370
12472
|
let carry = "";
|
|
11371
12473
|
let stopped = false;
|
|
12474
|
+
let hitTokenCap = false;
|
|
12475
|
+
let sawEof = false;
|
|
11372
12476
|
const stream = fs6.createReadStream(resolved, { encoding: "utf-8", highWaterMark: 256 * 1024 });
|
|
12477
|
+
const tryPushLine = (line) => {
|
|
12478
|
+
const formatted = formatNumberedLine(offset, kept.length, line);
|
|
12479
|
+
const added = formatted.length + 1;
|
|
12480
|
+
if (keptChars + added > MAX_READ_CHARS) {
|
|
12481
|
+
hitTokenCap = true;
|
|
12482
|
+
return false;
|
|
12483
|
+
}
|
|
12484
|
+
kept.push(line);
|
|
12485
|
+
keptChars += added;
|
|
12486
|
+
return true;
|
|
12487
|
+
};
|
|
11373
12488
|
const finish = () => {
|
|
11374
12489
|
if (stopped)
|
|
11375
12490
|
return;
|
|
11376
12491
|
stopped = true;
|
|
11377
12492
|
stream.destroy();
|
|
12493
|
+
if (hitTokenCap && explicitLimit) {
|
|
12494
|
+
resolve3({
|
|
12495
|
+
error: `Requested range (offset:${offset}, limit:${requestedLimit}) is too large \u2014 exceeds ${MAX_READ_TOKENS} tokens. Pass a smaller limit, or use search_files to locate the relevant part first.`
|
|
12496
|
+
});
|
|
12497
|
+
return;
|
|
12498
|
+
}
|
|
11378
12499
|
const first = offset + 1;
|
|
11379
12500
|
const last = offset + kept.length;
|
|
11380
|
-
const numbered = kept.map((l, i2) =>
|
|
11381
|
-
const
|
|
11382
|
-
|
|
12501
|
+
const numbered = kept.map((l, i2) => formatNumberedLine(offset, i2, l)).join("\n");
|
|
12502
|
+
const totalNote = sawEof ? `/${lineNo}` : "";
|
|
12503
|
+
const note = sawEof ? ` (reached end of file at line ${last})` : ` (more lines follow \u2014 pass offset:${last} to continue, or search_files to locate the relevant part)`;
|
|
12504
|
+
resolve3({ output: `[File: ${resolved} \u2014 lines ${first}-${last}${totalNote}${note}]
|
|
11383
12505
|
${numbered}` });
|
|
11384
12506
|
};
|
|
11385
12507
|
stream.on("data", (chunk) => {
|
|
@@ -11387,18 +12509,30 @@ ${numbered}` });
|
|
|
11387
12509
|
const lines = text.split("\n");
|
|
11388
12510
|
carry = lines.pop() ?? "";
|
|
11389
12511
|
for (const line of lines) {
|
|
11390
|
-
if (lineNo >= offset
|
|
11391
|
-
|
|
12512
|
+
if (lineNo >= offset) {
|
|
12513
|
+
if (lineNo >= hardEndLine) {
|
|
12514
|
+
finish();
|
|
12515
|
+
return;
|
|
12516
|
+
}
|
|
12517
|
+
if (!tryPushLine(line)) {
|
|
12518
|
+
finish();
|
|
12519
|
+
return;
|
|
12520
|
+
}
|
|
12521
|
+
}
|
|
11392
12522
|
lineNo++;
|
|
11393
|
-
|
|
12523
|
+
}
|
|
12524
|
+
});
|
|
12525
|
+
stream.on("end", () => {
|
|
12526
|
+
if (stopped)
|
|
12527
|
+
return;
|
|
12528
|
+
if (carry !== "" && lineNo >= offset && lineNo < hardEndLine) {
|
|
12529
|
+
if (!tryPushLine(carry)) {
|
|
11394
12530
|
finish();
|
|
11395
12531
|
return;
|
|
11396
12532
|
}
|
|
12533
|
+
lineNo++;
|
|
11397
12534
|
}
|
|
11398
|
-
|
|
11399
|
-
stream.on("end", () => {
|
|
11400
|
-
if (!stopped && carry !== "" && lineNo >= offset && lineNo < endLine)
|
|
11401
|
-
kept.push(carry);
|
|
12535
|
+
sawEof = true;
|
|
11402
12536
|
finish();
|
|
11403
12537
|
});
|
|
11404
12538
|
stream.on("error", (err) => {
|
|
@@ -11417,31 +12551,12 @@ ${numbered}` });
|
|
|
11417
12551
|
return { error: "Missing required parameter: path" };
|
|
11418
12552
|
try {
|
|
11419
12553
|
const resolved = resolvePath(filePath, workDir);
|
|
11420
|
-
|
|
12554
|
+
fs6.statSync(resolved);
|
|
11421
12555
|
if (isBinaryFile(resolved)) {
|
|
11422
12556
|
const ext = path5.extname(resolved).toLowerCase();
|
|
11423
12557
|
return { error: `Cannot read binary file: ${resolved} (${ext || "no extension"}). Use a text-based tool or convert it first.` };
|
|
11424
12558
|
}
|
|
11425
|
-
|
|
11426
|
-
const kb = (stat2.size / 1024).toFixed(0);
|
|
11427
|
-
if (limit <= 0) {
|
|
11428
|
-
return { error: `File too large (${kb} KB, max ${MAX_READ_BYTES / 1024} KB). Pass offset + limit to read a section (e.g. {offset:0, limit:500}), or search_files to locate the relevant part first.` };
|
|
11429
|
-
}
|
|
11430
|
-
return await readLargeFileWindow(resolved, offset, limit, kb);
|
|
11431
|
-
}
|
|
11432
|
-
const content = fs6.readFileSync(resolved, "utf-8");
|
|
11433
|
-
const allLines = content.split("\n");
|
|
11434
|
-
const totalLines = allLines.length;
|
|
11435
|
-
const startLine = offset;
|
|
11436
|
-
const endLine = limit > 0 ? Math.min(startLine + limit, totalLines) : totalLines;
|
|
11437
|
-
const slice = allLines.slice(startLine, endLine);
|
|
11438
|
-
const numbered = slice.map((l, i2) => {
|
|
11439
|
-
const lineNo = String(startLine + i2 + 1).padStart(4, " ");
|
|
11440
|
-
return `${lineNo} ${l}`;
|
|
11441
|
-
}).join("\n");
|
|
11442
|
-
const rangeNote = offset > 0 || limit > 0 ? ` lines ${startLine + 1}-${endLine}/${totalLines}` : ` ${totalLines} lines`;
|
|
11443
|
-
return { output: `[File: ${resolved} (${rangeNote})]
|
|
11444
|
-
${numbered}` };
|
|
12559
|
+
return await readFileWindowed(resolved, offset, limit);
|
|
11445
12560
|
} catch (err) {
|
|
11446
12561
|
return { error: err.message };
|
|
11447
12562
|
}
|
|
@@ -11977,7 +13092,9 @@ Update these call-sites (or restore the symbol), then run a build/typecheck to c
|
|
|
11977
13092
|
walkDir(resolved, (filePath) => {
|
|
11978
13093
|
const name = path5.basename(filePath);
|
|
11979
13094
|
const nameLower = name.toLowerCase();
|
|
11980
|
-
|
|
13095
|
+
const rel = path5.relative(resolved, filePath).replace(/\\/g, "/");
|
|
13096
|
+
const relLower = rel.toLowerCase();
|
|
13097
|
+
if (nameLower.includes(patternLower) || matchesGlob(name, pattern) || matchesPattern(name, pattern) || relLower.includes(patternLower) || matchesGlob(rel, pattern) || matchesPattern(rel, pattern)) {
|
|
11981
13098
|
matches.push(filePath);
|
|
11982
13099
|
}
|
|
11983
13100
|
});
|
|
@@ -12002,7 +13119,7 @@ Update these call-sites (or restore the symbol), then run a build/typecheck to c
|
|
|
12002
13119
|
args.push("-m", "200", "--regexp", pattern, resolved);
|
|
12003
13120
|
result = (0, child_process_1.spawnSync)("rg", args, spawnOpts);
|
|
12004
13121
|
} else {
|
|
12005
|
-
const args = ["-
|
|
13122
|
+
const args = ["-rnE", "--binary-files=without-match", "--color=never"];
|
|
12006
13123
|
if (ignoreCase)
|
|
12007
13124
|
args.push("-i");
|
|
12008
13125
|
if (contextLines > 0)
|
|
@@ -12031,8 +13148,10 @@ ${globalCapMatches(output)}` : "";
|
|
|
12031
13148
|
}
|
|
12032
13149
|
if (result.status === 1 && !output)
|
|
12033
13150
|
return { output: "No matches found." };
|
|
12034
|
-
if (result.status !== 0 && result.status !== 1)
|
|
12035
|
-
|
|
13151
|
+
if (result.status !== 0 && result.status !== 1) {
|
|
13152
|
+
const hint = /parenthes|bracket|brace|Unmatched|repetition-operator|invalid regex/i.test(stderr) ? ' \u2014 the pattern has invalid/unbalanced regex syntax. If you meant to match literal parentheses/brackets, escape them (e.g. "\\(", "\\)"), or simplify the pattern.' : "";
|
|
13153
|
+
return { error: (stderr || "search failed").trim() + hint };
|
|
13154
|
+
}
|
|
12036
13155
|
output = globalCapMatches(output);
|
|
12037
13156
|
return { output: output || "No matches found." };
|
|
12038
13157
|
}
|
|
@@ -12373,6 +13492,14 @@ ${diff2}${xfile}` };
|
|
|
12373
13492
|
return callback(null, address, family);
|
|
12374
13493
|
});
|
|
12375
13494
|
};
|
|
13495
|
+
let settled = false;
|
|
13496
|
+
let selfAborted = false;
|
|
13497
|
+
const finish = (result) => {
|
|
13498
|
+
if (settled)
|
|
13499
|
+
return;
|
|
13500
|
+
settled = true;
|
|
13501
|
+
resolve3(result);
|
|
13502
|
+
};
|
|
12376
13503
|
const req = transport.get(url, {
|
|
12377
13504
|
timeout: DEFAULT_TIMEOUT_MS,
|
|
12378
13505
|
lookup: guardedLookup,
|
|
@@ -12385,15 +13512,16 @@ ${diff2}${xfile}` };
|
|
|
12385
13512
|
}, (res) => {
|
|
12386
13513
|
const status = res.statusCode ?? 0;
|
|
12387
13514
|
if ((status === 301 || status === 302 || status === 307 || status === 308) && res.headers.location) {
|
|
13515
|
+
selfAborted = true;
|
|
12388
13516
|
req.destroy();
|
|
12389
13517
|
let nextUrl;
|
|
12390
13518
|
try {
|
|
12391
13519
|
nextUrl = new URL(res.headers.location, url).href;
|
|
12392
13520
|
} catch {
|
|
12393
|
-
|
|
13521
|
+
finish({ error: `Invalid redirect location: ${res.headers.location}` });
|
|
12394
13522
|
return;
|
|
12395
13523
|
}
|
|
12396
|
-
fetchUrl({ url: nextUrl }, void 0, _redirectCount + 1).then(
|
|
13524
|
+
fetchUrl({ url: nextUrl }, void 0, _redirectCount + 1).then(finish);
|
|
12397
13525
|
return;
|
|
12398
13526
|
}
|
|
12399
13527
|
const contentType = res.headers["content-type"] ?? "";
|
|
@@ -12401,6 +13529,19 @@ ${diff2}${xfile}` };
|
|
|
12401
13529
|
const chunks = [];
|
|
12402
13530
|
let totalBytes = 0;
|
|
12403
13531
|
let truncated = false;
|
|
13532
|
+
const emitBody = () => {
|
|
13533
|
+
let body = Buffer.concat(chunks).toString("utf-8");
|
|
13534
|
+
if (isHtml)
|
|
13535
|
+
body = stripHtml(body);
|
|
13536
|
+
const truncNote = truncated ? `
|
|
13537
|
+
|
|
13538
|
+
[Truncated at ${MAX_FETCH_BYTES / 1024}KB]` : "";
|
|
13539
|
+
finish({ output: `HTTP ${status}
|
|
13540
|
+
|
|
13541
|
+
<untrusted_web_content url="${url}">
|
|
13542
|
+
${body}${truncNote}
|
|
13543
|
+
</untrusted_web_content>` });
|
|
13544
|
+
};
|
|
12404
13545
|
res.on("data", (chunk) => {
|
|
12405
13546
|
if (totalBytes + chunk.length > MAX_FETCH_BYTES) {
|
|
12406
13547
|
const remaining = MAX_FETCH_BYTES - totalBytes;
|
|
@@ -12408,31 +13549,28 @@ ${diff2}${xfile}` };
|
|
|
12408
13549
|
chunks.push(chunk.subarray(0, remaining));
|
|
12409
13550
|
totalBytes = MAX_FETCH_BYTES;
|
|
12410
13551
|
truncated = true;
|
|
13552
|
+
selfAborted = true;
|
|
12411
13553
|
req.destroy();
|
|
13554
|
+
emitBody();
|
|
12412
13555
|
} else {
|
|
12413
13556
|
chunks.push(chunk);
|
|
12414
13557
|
totalBytes += chunk.length;
|
|
12415
13558
|
}
|
|
12416
13559
|
});
|
|
12417
|
-
res.on("end",
|
|
12418
|
-
|
|
12419
|
-
if (
|
|
12420
|
-
|
|
12421
|
-
const truncNote = truncated ? `
|
|
12422
|
-
|
|
12423
|
-
[Truncated at ${MAX_FETCH_BYTES / 1024}KB]` : "";
|
|
12424
|
-
resolve3({ output: `HTTP ${status}
|
|
12425
|
-
|
|
12426
|
-
<untrusted_web_content url="${url}">
|
|
12427
|
-
${body}${truncNote}
|
|
12428
|
-
</untrusted_web_content>` });
|
|
13560
|
+
res.on("end", emitBody);
|
|
13561
|
+
res.on("error", (err) => {
|
|
13562
|
+
if (!selfAborted)
|
|
13563
|
+
finish({ error: err.message });
|
|
12429
13564
|
});
|
|
12430
|
-
res.on("error", (err) => resolve3({ error: err.message }));
|
|
12431
13565
|
});
|
|
12432
|
-
req.on("error", (err) =>
|
|
13566
|
+
req.on("error", (err) => {
|
|
13567
|
+
if (!selfAborted)
|
|
13568
|
+
finish({ error: err.message });
|
|
13569
|
+
});
|
|
12433
13570
|
req.on("timeout", () => {
|
|
13571
|
+
selfAborted = true;
|
|
12434
13572
|
req.destroy();
|
|
12435
|
-
|
|
13573
|
+
finish({ error: `Request timed out after ${DEFAULT_TIMEOUT_MS}ms` });
|
|
12436
13574
|
});
|
|
12437
13575
|
});
|
|
12438
13576
|
}
|
|
@@ -12860,36 +13998,48 @@ ${lines.join("\n")}`;
|
|
|
12860
13998
|
return { output: `Found ${photos.length} photo(s) for "${query}":
|
|
12861
13999
|
${lines.join("\n")}` };
|
|
12862
14000
|
}
|
|
12863
|
-
|
|
12864
|
-
|
|
14001
|
+
function memoryScopeOf(input) {
|
|
14002
|
+
return input.scope === "global" ? "global" : "project";
|
|
14003
|
+
}
|
|
14004
|
+
async function memoryWrite(input, workDir) {
|
|
12865
14005
|
const content = typeof input.content === "string" ? input.content.trim() : "";
|
|
12866
14006
|
if (!content)
|
|
12867
14007
|
return { error: "content is required" };
|
|
12868
|
-
const
|
|
12869
|
-
|
|
12870
|
-
if (
|
|
12871
|
-
|
|
12872
|
-
|
|
12873
|
-
|
|
12874
|
-
|
|
12875
|
-
}
|
|
12876
|
-
}
|
|
12877
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
12878
|
-
const entry = `
|
|
12879
|
-
- [${timestamp}] ${content}`;
|
|
12880
|
-
fs6.appendFileSync(MEMORY_FILE, entry, "utf-8");
|
|
12881
|
-
return { output: `Memory saved: ${content}` };
|
|
12882
|
-
}
|
|
12883
|
-
async function memoryRead(_input) {
|
|
14008
|
+
const scope = memoryScopeOf(input);
|
|
14009
|
+
const r2 = await (0, memory_1.writeMemory)(content, scope, workDir);
|
|
14010
|
+
if (!r2.ok)
|
|
14011
|
+
return { error: "failed to save memory" };
|
|
14012
|
+
return { output: r2.already ? `Memory already recorded (skipped duplicate): ${content}` : `Memory saved (${scope}): ${content}` };
|
|
14013
|
+
}
|
|
14014
|
+
async function memoryRead(input, workDir) {
|
|
12884
14015
|
try {
|
|
12885
|
-
if (
|
|
12886
|
-
|
|
12887
|
-
|
|
12888
|
-
|
|
14016
|
+
if (input && (input.scope === "project" || input.scope === "global")) {
|
|
14017
|
+
const mem2 = (0, memory_1.readMemory)(input.scope, workDir);
|
|
14018
|
+
return { output: mem2 || "No memories saved yet." };
|
|
14019
|
+
}
|
|
14020
|
+
const mem = (0, memory_1.readAllMemory)(workDir);
|
|
14021
|
+
return { output: mem || "No memories saved yet." };
|
|
12889
14022
|
} catch (err) {
|
|
12890
14023
|
return { error: err.message };
|
|
12891
14024
|
}
|
|
12892
14025
|
}
|
|
14026
|
+
async function useSkill(input, workDir) {
|
|
14027
|
+
const name = typeof input.name === "string" ? input.name.trim() : "";
|
|
14028
|
+
if (!name)
|
|
14029
|
+
return { error: "name is required (the skill to invoke)" };
|
|
14030
|
+
const dir = workDir ?? process.cwd();
|
|
14031
|
+
const skills = (0, skills_1.loadSkills)(dir);
|
|
14032
|
+
const skill = (0, skills_1.findSkill)(skills, name);
|
|
14033
|
+
if (!skill) {
|
|
14034
|
+
const known = skills.map((s2) => s2.name).join(", ") || "(none defined)";
|
|
14035
|
+
return { error: `Unknown skill "${name}". Available: ${known}.` };
|
|
14036
|
+
}
|
|
14037
|
+
const args = typeof input.args === "string" ? input.args : "";
|
|
14038
|
+
const expanded = (0, skills_1.expandSkill)(skill, args, dir);
|
|
14039
|
+
return { output: `[Skill "${skill.name}" loaded \u2014 follow these instructions:]
|
|
14040
|
+
|
|
14041
|
+
${expanded}` };
|
|
14042
|
+
}
|
|
12893
14043
|
function semanticPreamble(input, workDir) {
|
|
12894
14044
|
const p = typeof input.path === "string" ? input.path : "";
|
|
12895
14045
|
const line = typeof input.line === "number" ? Math.floor(input.line) : 0;
|
|
@@ -12951,6 +14101,7 @@ ${lines.join("\n")}` };
|
|
|
12951
14101
|
notebook_edit: notebookEdit,
|
|
12952
14102
|
memory_write: memoryWrite,
|
|
12953
14103
|
memory_read: memoryRead,
|
|
14104
|
+
use_skill: useSkill,
|
|
12954
14105
|
bash_output: bashOutput,
|
|
12955
14106
|
kill_shell: killShell,
|
|
12956
14107
|
// LSP-lite fallback (regex-based). In VS Code these names are intercepted by
|
|
@@ -12983,7 +14134,10 @@ ${lines.join("\n")}` };
|
|
|
12983
14134
|
"get_workspace_symbols",
|
|
12984
14135
|
"go_to_definition",
|
|
12985
14136
|
"find_references",
|
|
12986
|
-
"get_hover"
|
|
14137
|
+
"get_hover",
|
|
14138
|
+
"memory_write",
|
|
14139
|
+
"memory_read",
|
|
14140
|
+
"use_skill"
|
|
12987
14141
|
]);
|
|
12988
14142
|
async function executeTool(name, input, abortSignal, sandbox, workDir, agentScope) {
|
|
12989
14143
|
if (!TOOL_MAP[name])
|
|
@@ -13006,150 +14160,6 @@ ${lines.join("\n")}` };
|
|
|
13006
14160
|
}
|
|
13007
14161
|
});
|
|
13008
14162
|
|
|
13009
|
-
// ../core/dist/plugins/index.js
|
|
13010
|
-
var require_plugins = __commonJS({
|
|
13011
|
-
"../core/dist/plugins/index.js"(exports2) {
|
|
13012
|
-
"use strict";
|
|
13013
|
-
var __createBinding = exports2 && exports2.__createBinding || (Object.create ? function(o, m2, k, k2) {
|
|
13014
|
-
if (k2 === void 0)
|
|
13015
|
-
k2 = k;
|
|
13016
|
-
var desc = Object.getOwnPropertyDescriptor(m2, k);
|
|
13017
|
-
if (!desc || ("get" in desc ? !m2.__esModule : desc.writable || desc.configurable)) {
|
|
13018
|
-
desc = { enumerable: true, get: function() {
|
|
13019
|
-
return m2[k];
|
|
13020
|
-
} };
|
|
13021
|
-
}
|
|
13022
|
-
Object.defineProperty(o, k2, desc);
|
|
13023
|
-
} : function(o, m2, k, k2) {
|
|
13024
|
-
if (k2 === void 0)
|
|
13025
|
-
k2 = k;
|
|
13026
|
-
o[k2] = m2[k];
|
|
13027
|
-
});
|
|
13028
|
-
var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? function(o, v) {
|
|
13029
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
13030
|
-
} : function(o, v) {
|
|
13031
|
-
o["default"] = v;
|
|
13032
|
-
});
|
|
13033
|
-
var __importStar = exports2 && exports2.__importStar || /* @__PURE__ */ function() {
|
|
13034
|
-
var ownKeys = function(o) {
|
|
13035
|
-
ownKeys = Object.getOwnPropertyNames || function(o2) {
|
|
13036
|
-
var ar = [];
|
|
13037
|
-
for (var k in o2)
|
|
13038
|
-
if (Object.prototype.hasOwnProperty.call(o2, k))
|
|
13039
|
-
ar[ar.length] = k;
|
|
13040
|
-
return ar;
|
|
13041
|
-
};
|
|
13042
|
-
return ownKeys(o);
|
|
13043
|
-
};
|
|
13044
|
-
return function(mod) {
|
|
13045
|
-
if (mod && mod.__esModule)
|
|
13046
|
-
return mod;
|
|
13047
|
-
var result = {};
|
|
13048
|
-
if (mod != null) {
|
|
13049
|
-
for (var k = ownKeys(mod), i2 = 0; i2 < k.length; i2++)
|
|
13050
|
-
if (k[i2] !== "default")
|
|
13051
|
-
__createBinding(result, mod, k[i2]);
|
|
13052
|
-
}
|
|
13053
|
-
__setModuleDefault(result, mod);
|
|
13054
|
-
return result;
|
|
13055
|
-
};
|
|
13056
|
-
}();
|
|
13057
|
-
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
13058
|
-
exports2.loadPlugins = loadPlugins3;
|
|
13059
|
-
exports2.pluginAssetDirs = pluginAssetDirs;
|
|
13060
|
-
exports2.pluginHooks = pluginHooks;
|
|
13061
|
-
exports2.pluginMcpServers = pluginMcpServers;
|
|
13062
|
-
var fs6 = __importStar(require("fs"));
|
|
13063
|
-
var path5 = __importStar(require("path"));
|
|
13064
|
-
var os5 = __importStar(require("os"));
|
|
13065
|
-
function readMeta(dir) {
|
|
13066
|
-
try {
|
|
13067
|
-
const raw = fs6.readFileSync(path5.join(dir, "plugin.json"), "utf-8");
|
|
13068
|
-
const j = JSON.parse(raw);
|
|
13069
|
-
return {
|
|
13070
|
-
name: typeof j.name === "string" ? j.name : void 0,
|
|
13071
|
-
version: typeof j.version === "string" ? j.version : void 0,
|
|
13072
|
-
description: typeof j.description === "string" ? j.description : void 0
|
|
13073
|
-
};
|
|
13074
|
-
} catch {
|
|
13075
|
-
return {};
|
|
13076
|
-
}
|
|
13077
|
-
}
|
|
13078
|
-
function scanRoot(root, scope, into) {
|
|
13079
|
-
let entries;
|
|
13080
|
-
try {
|
|
13081
|
-
entries = fs6.readdirSync(root, { withFileTypes: true });
|
|
13082
|
-
} catch {
|
|
13083
|
-
return;
|
|
13084
|
-
}
|
|
13085
|
-
for (const entry of entries) {
|
|
13086
|
-
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
13087
|
-
continue;
|
|
13088
|
-
const dir = path5.join(root, entry.name);
|
|
13089
|
-
try {
|
|
13090
|
-
if (!fs6.statSync(dir).isDirectory())
|
|
13091
|
-
continue;
|
|
13092
|
-
} catch {
|
|
13093
|
-
continue;
|
|
13094
|
-
}
|
|
13095
|
-
const meta = readMeta(dir);
|
|
13096
|
-
const name = meta.name || entry.name;
|
|
13097
|
-
if (into.has(name))
|
|
13098
|
-
continue;
|
|
13099
|
-
into.set(name, { name, version: meta.version, description: meta.description, dir, scope });
|
|
13100
|
-
}
|
|
13101
|
-
}
|
|
13102
|
-
function loadPlugins3(workDir) {
|
|
13103
|
-
const out = /* @__PURE__ */ new Map();
|
|
13104
|
-
scanRoot(path5.join(workDir, ".nexrall", "plugins"), "project", out);
|
|
13105
|
-
scanRoot(path5.join(os5.homedir(), ".nexrall", "plugins"), "global", out);
|
|
13106
|
-
return [...out.values()];
|
|
13107
|
-
}
|
|
13108
|
-
function pluginAssetDirs(workDir, kind) {
|
|
13109
|
-
return loadPlugins3(workDir).map((p) => path5.join(p.dir, kind)).filter((d) => {
|
|
13110
|
-
try {
|
|
13111
|
-
return fs6.statSync(d).isDirectory();
|
|
13112
|
-
} catch {
|
|
13113
|
-
return false;
|
|
13114
|
-
}
|
|
13115
|
-
});
|
|
13116
|
-
}
|
|
13117
|
-
function pluginHooks(workDir) {
|
|
13118
|
-
const merged = {};
|
|
13119
|
-
for (const p of loadPlugins3(workDir)) {
|
|
13120
|
-
try {
|
|
13121
|
-
const raw = fs6.readFileSync(path5.join(p.dir, "hooks.json"), "utf-8");
|
|
13122
|
-
const j = JSON.parse(raw);
|
|
13123
|
-
const hooks = j.hooks ?? j;
|
|
13124
|
-
for (const [phase, entries] of Object.entries(hooks)) {
|
|
13125
|
-
if (!Array.isArray(entries))
|
|
13126
|
-
continue;
|
|
13127
|
-
merged[phase] = [...merged[phase] ?? [], ...entries];
|
|
13128
|
-
}
|
|
13129
|
-
} catch {
|
|
13130
|
-
}
|
|
13131
|
-
}
|
|
13132
|
-
return merged;
|
|
13133
|
-
}
|
|
13134
|
-
function pluginMcpServers(workDir) {
|
|
13135
|
-
const merged = {};
|
|
13136
|
-
for (const p of loadPlugins3(workDir)) {
|
|
13137
|
-
try {
|
|
13138
|
-
const raw = fs6.readFileSync(path5.join(p.dir, "mcp.json"), "utf-8");
|
|
13139
|
-
const j = JSON.parse(raw);
|
|
13140
|
-
const servers = j.mcpServers ?? j;
|
|
13141
|
-
for (const [name, cfg] of Object.entries(servers)) {
|
|
13142
|
-
if (!(name in merged))
|
|
13143
|
-
merged[name] = cfg;
|
|
13144
|
-
}
|
|
13145
|
-
} catch {
|
|
13146
|
-
}
|
|
13147
|
-
}
|
|
13148
|
-
return merged;
|
|
13149
|
-
}
|
|
13150
|
-
}
|
|
13151
|
-
});
|
|
13152
|
-
|
|
13153
14163
|
// ../core/dist/agent/agentTypes.js
|
|
13154
14164
|
var require_agentTypes = __commonJS({
|
|
13155
14165
|
"../core/dist/agent/agentTypes.js"(exports2) {
|
|
@@ -13706,16 +14716,22 @@ var require_loop = __commonJS({
|
|
|
13706
14716
|
exports2.ledgerRecord = ledgerRecord;
|
|
13707
14717
|
exports2.ledgerSummary = ledgerSummary;
|
|
13708
14718
|
exports2.pruneOldToolResults = pruneOldToolResults;
|
|
14719
|
+
exports2.estimateTokensRough = estimateTokensRough;
|
|
14720
|
+
exports2.compactMessagesForResume = compactMessagesForResume2;
|
|
13709
14721
|
exports2.runAgentLoop = runAgentLoop2;
|
|
14722
|
+
exports2.trimToResumableBoundary = trimToResumableBoundary;
|
|
14723
|
+
var types_1 = require_types();
|
|
13710
14724
|
var client_1 = require_client();
|
|
13711
14725
|
var executor_1 = require_executor();
|
|
13712
14726
|
var agentTypes_1 = require_agentTypes();
|
|
14727
|
+
var skills_1 = require_skills();
|
|
13713
14728
|
var rules_1 = require_rules();
|
|
13714
14729
|
var sandbox_1 = require_sandbox();
|
|
13715
14730
|
var index_1 = require_plugins();
|
|
13716
14731
|
var testIntegrity_1 = require_testIntegrity();
|
|
13717
14732
|
var flaky_1 = require_flaky();
|
|
13718
14733
|
var claimEvidence_1 = require_claimEvidence();
|
|
14734
|
+
var memory_1 = require_memory();
|
|
13719
14735
|
var fs6 = __importStar(require("fs"));
|
|
13720
14736
|
var path5 = __importStar(require("path"));
|
|
13721
14737
|
var child_process_1 = require("child_process");
|
|
@@ -13914,6 +14930,8 @@ var require_loop = __commonJS({
|
|
|
13914
14930
|
const preview = typeof input.prompt === "string" ? input.prompt.slice(0, 60) : "";
|
|
13915
14931
|
return `Sub-task: ${desc || preview}${!desc && preview.length === 60 ? "\u2026" : ""}`;
|
|
13916
14932
|
}
|
|
14933
|
+
case "use_skill":
|
|
14934
|
+
return `Use skill: /${input.name ?? "(unknown)"}`;
|
|
13917
14935
|
case "get_diagnostics":
|
|
13918
14936
|
return input.path ? `Get diagnostics: ${input.path}` : "Get workspace diagnostics";
|
|
13919
14937
|
case "go_to_definition":
|
|
@@ -13977,6 +14995,22 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
|
|
|
13977
14995
|
onText: () => {
|
|
13978
14996
|
},
|
|
13979
14997
|
// sub-agent text is returned as the tool result, not streamed live
|
|
14998
|
+
// Forwarded DELIBERATELY, and it must be a real handler rather than a no-op.
|
|
14999
|
+
//
|
|
15000
|
+
// A sub-agent streams no text (onText above is a no-op) but it DOES stream thinking
|
|
15001
|
+
// through the parent's UI (see onThinking/onThinkingDelta below), and thinking sets
|
|
15002
|
+
// `emittedToCaller`. So a sub-agent stream that dies after reasoning genuinely has
|
|
15003
|
+
// rendered output to discard — a no-op here would let the restart proceed and then
|
|
15004
|
+
// re-stream that reasoning on top of the copy still on screen, the exact duplication
|
|
15005
|
+
// the opt-in exists to prevent.
|
|
15006
|
+
//
|
|
15007
|
+
// Forwarding is safe because the parent's own output is already closed by this
|
|
15008
|
+
// point: dispatching the `task` tool goes through options.onToolUse, which finalizes
|
|
15009
|
+
// the parent's bubble (VS Code) / flushes the renderer (CLI) before the sub-agent
|
|
15010
|
+
// starts. The only live, discardable element at restart time is the sub-agent's own
|
|
15011
|
+
// thinking block. Completed tool rows are left alone — those are real side effects
|
|
15012
|
+
// that actually happened.
|
|
15013
|
+
onStreamRestart: (reason, chars) => options.onStreamRestart?.(reason, chars),
|
|
13980
15014
|
// Forward tool events with isSubTask=true so the UI can render a badge
|
|
13981
15015
|
// instead of prepending "[sub-task]" to the tool name (which caused double-prefix
|
|
13982
15016
|
// when the name was already labelled, and mixed display concerns into the data layer).
|
|
@@ -14009,7 +15043,13 @@ ${tail}`;
|
|
|
14009
15043
|
pro: 1e6,
|
|
14010
15044
|
ultra: 1e6
|
|
14011
15045
|
};
|
|
14012
|
-
|
|
15046
|
+
function envFraction(name, fallback) {
|
|
15047
|
+
const v = Number(process.env[name]);
|
|
15048
|
+
return Number.isFinite(v) && v > 0 && v < 1 ? v : fallback;
|
|
15049
|
+
}
|
|
15050
|
+
var AUTO_PRUNE_THRESHOLD = envFraction("NEXRALL_PRUNE_THRESHOLD", 0.35);
|
|
15051
|
+
var AUTO_COMPACT_THRESHOLD = envFraction("NEXRALL_COMPACT_THRESHOLD", 0.8);
|
|
15052
|
+
var PRUNE_MIN_RECLAIM_BYTES = 256 * 1024;
|
|
14013
15053
|
var COMPACT_KEEP_MIN = 6;
|
|
14014
15054
|
var MAX_BODY_BYTES = 8 * 1024 * 1024;
|
|
14015
15055
|
function estimateBodyBytes(messages) {
|
|
@@ -14160,11 +15200,12 @@ ${tail}`;
|
|
|
14160
15200
|
var PRUNE_STUB_KEEP_CHARS = 400;
|
|
14161
15201
|
var PRUNE_MARKER = "\n\n[\u2026 ";
|
|
14162
15202
|
var PRUNE_MARKER_TAIL = " pruned to conserve context. Re-run the tool if you need the full result.]";
|
|
14163
|
-
function pruneOldToolResults(messages) {
|
|
15203
|
+
function pruneOldToolResults(messages, minReclaimBytes = 0) {
|
|
14164
15204
|
const cutoff = messages.length - PRUNE_KEEP_RECENT;
|
|
14165
15205
|
if (cutoff <= 1)
|
|
14166
15206
|
return 0;
|
|
14167
|
-
|
|
15207
|
+
const targets = [];
|
|
15208
|
+
let total = 0;
|
|
14168
15209
|
for (let i2 = 0; i2 < cutoff; i2++) {
|
|
14169
15210
|
const m2 = messages[i2];
|
|
14170
15211
|
if (!Array.isArray(m2.content))
|
|
@@ -14179,10 +15220,17 @@ ${tail}`;
|
|
|
14179
15220
|
continue;
|
|
14180
15221
|
const head = text.slice(0, PRUNE_STUB_KEEP_CHARS);
|
|
14181
15222
|
const omitted = text.length - head.length;
|
|
14182
|
-
|
|
14183
|
-
|
|
15223
|
+
targets.push({ block: b, head, omitted });
|
|
15224
|
+
total += omitted;
|
|
14184
15225
|
}
|
|
14185
15226
|
}
|
|
15227
|
+
if (total < minReclaimBytes)
|
|
15228
|
+
return 0;
|
|
15229
|
+
let reclaimed = 0;
|
|
15230
|
+
for (const { block, head, omitted } of targets) {
|
|
15231
|
+
block.content = `${head}${PRUNE_MARKER}${omitted} chars of earlier tool output${PRUNE_MARKER_TAIL}`;
|
|
15232
|
+
reclaimed += omitted;
|
|
15233
|
+
}
|
|
14186
15234
|
return reclaimed;
|
|
14187
15235
|
}
|
|
14188
15236
|
function originalTaskText(messages) {
|
|
@@ -14204,12 +15252,24 @@ ${tail}`;
|
|
|
14204
15252
|
let summary = "";
|
|
14205
15253
|
try {
|
|
14206
15254
|
const reply = await (0, client_1.streamChat)([{ role: "user", content: [{ type: "text", text: summaryPrompt }] }], {
|
|
14207
|
-
|
|
15255
|
+
// COST: the summariser is a mechanical "bullet-point this transcript" task —
|
|
15256
|
+
// its quality is indistinguishable across model tiers, so there is no reason
|
|
15257
|
+
// to run it on the user's (possibly expensive) tier. Force 'turbo' (Sonnet 5):
|
|
15258
|
+
// it is the cheapest 1M-context tier (in $2.0/1M vs Opus $2.5, Fable $3.0;
|
|
15259
|
+
// out $15/1M vs $25 / $50), so this is always ≤ the user's cost, and its 1M
|
|
15260
|
+
// window comfortably holds the transcript (capped at ~170K tokens by
|
|
15261
|
+
// transcriptOf) even on the largest sessions.
|
|
15262
|
+
model: "turbo",
|
|
14208
15263
|
mode: "ask",
|
|
14209
15264
|
// summariser must not call tools; ask-mode discourages action
|
|
14210
15265
|
env: options.env,
|
|
14211
15266
|
clientType: options.clientType,
|
|
14212
|
-
abortSignal: options.abortSignal
|
|
15267
|
+
abortSignal: options.abortSignal,
|
|
15268
|
+
// The summariser renders NOTHING (onEvent below is a no-op) and its result is
|
|
15269
|
+
// read only from the returned message, so a restart has nothing to roll back —
|
|
15270
|
+
// always safe. Worth enabling: a blip here used to abandon compaction entirely,
|
|
15271
|
+
// which then let the very next turn hit the context wall it was meant to prevent.
|
|
15272
|
+
allowRestartAfterRender: true
|
|
14213
15273
|
}, () => {
|
|
14214
15274
|
});
|
|
14215
15275
|
summary = reply.content.filter((b) => b.type === "text").map((b) => b.text ?? "").join("").trim();
|
|
@@ -14235,6 +15295,88 @@ ${summary}
|
|
|
14235
15295
|
Continue the work from here.` }] });
|
|
14236
15296
|
return true;
|
|
14237
15297
|
}
|
|
15298
|
+
async function maybeCompactMemory(scope, options) {
|
|
15299
|
+
try {
|
|
15300
|
+
await (0, memory_1.compactMemoryIfNeeded)(scope, options.workDir, async (prompt2) => {
|
|
15301
|
+
const reply = await (0, client_1.streamChat)([{ role: "user", content: [{ type: "text", text: prompt2 }] }], {
|
|
15302
|
+
// Same reasoning as autoCompactMessages' summariser: a mechanical
|
|
15303
|
+
// consolidation task, always run on the cheapest 1M-context tier
|
|
15304
|
+
// regardless of the user's chosen model for the actual conversation.
|
|
15305
|
+
model: "turbo",
|
|
15306
|
+
mode: "ask",
|
|
15307
|
+
env: options.env,
|
|
15308
|
+
clientType: options.clientType,
|
|
15309
|
+
abortSignal: options.abortSignal,
|
|
15310
|
+
// Same as the transcript summariser: no rendered output, result read only from
|
|
15311
|
+
// the returned message, so restarting on a blip is always safe.
|
|
15312
|
+
allowRestartAfterRender: true
|
|
15313
|
+
}, () => {
|
|
15314
|
+
});
|
|
15315
|
+
return reply.content.filter((b) => b.type === "text").map((b) => b.text ?? "").join("");
|
|
15316
|
+
});
|
|
15317
|
+
} catch {
|
|
15318
|
+
}
|
|
15319
|
+
}
|
|
15320
|
+
var RESUME_CHARS_PER_TOKEN = 4;
|
|
15321
|
+
function estimateTokensRough(messages) {
|
|
15322
|
+
return Math.ceil(estimateBodyBytes(messages) / RESUME_CHARS_PER_TOKEN);
|
|
15323
|
+
}
|
|
15324
|
+
async function compactMessagesForResume2(messages, opts) {
|
|
15325
|
+
if (messages.length <= COMPACT_KEEP_MIN + 2)
|
|
15326
|
+
return false;
|
|
15327
|
+
const settings = (0, rules_1.loadSettings)(opts.workDir);
|
|
15328
|
+
if (!resolveAutoCompact(void 0, settings.raw))
|
|
15329
|
+
return false;
|
|
15330
|
+
const contextWindow = MODEL_CONTEXT_TOKENS[opts.model ?? "turbo"] ?? 1e6;
|
|
15331
|
+
let bodyBytes = estimateBodyBytes(messages);
|
|
15332
|
+
let tokenGuess = estimateTokensRough(messages);
|
|
15333
|
+
const overPruneThreshold = () => tokenGuess > contextWindow * AUTO_PRUNE_THRESHOLD || bodyBytes > MAX_BODY_BYTES;
|
|
15334
|
+
const overCompactThreshold = () => tokenGuess > contextWindow * AUTO_COMPACT_THRESHOLD || bodyBytes > MAX_BODY_BYTES;
|
|
15335
|
+
if (!overPruneThreshold())
|
|
15336
|
+
return false;
|
|
15337
|
+
let compacted = false;
|
|
15338
|
+
if (messages.length > PRUNE_KEEP_RECENT + 2) {
|
|
15339
|
+
const reclaimed = pruneOldToolResults(messages, PRUNE_MIN_RECLAIM_BYTES);
|
|
15340
|
+
if (reclaimed > 0) {
|
|
15341
|
+
bodyBytes = estimateBodyBytes(messages);
|
|
15342
|
+
tokenGuess = estimateTokensRough(messages);
|
|
15343
|
+
compacted = true;
|
|
15344
|
+
opts.onNotice?.(`
|
|
15345
|
+
\u267B\uFE0F Trimmed ~${(reclaimed / (1024 * 1024)).toFixed(1)}MB of older tool output before resuming this chat.
|
|
15346
|
+
`);
|
|
15347
|
+
}
|
|
15348
|
+
}
|
|
15349
|
+
let guard = 0;
|
|
15350
|
+
while (overCompactThreshold() && messages.length > COMPACT_KEEP_MIN + 2 && guard < 5) {
|
|
15351
|
+
guard += 1;
|
|
15352
|
+
const did = await autoCompactMessages(messages, {
|
|
15353
|
+
workDir: opts.workDir,
|
|
15354
|
+
model: opts.model,
|
|
15355
|
+
clientType: opts.clientType,
|
|
15356
|
+
env: opts.env,
|
|
15357
|
+
onText: () => {
|
|
15358
|
+
},
|
|
15359
|
+
onToolUse: () => {
|
|
15360
|
+
},
|
|
15361
|
+
onToolResult: () => {
|
|
15362
|
+
},
|
|
15363
|
+
onUsage: () => {
|
|
15364
|
+
},
|
|
15365
|
+
requestPermission: async () => false
|
|
15366
|
+
});
|
|
15367
|
+
if (!did)
|
|
15368
|
+
break;
|
|
15369
|
+
compacted = true;
|
|
15370
|
+
bodyBytes = estimateBodyBytes(messages);
|
|
15371
|
+
tokenGuess = estimateTokensRough(messages);
|
|
15372
|
+
}
|
|
15373
|
+
if (compacted) {
|
|
15374
|
+
opts.onNotice?.(`
|
|
15375
|
+
\u267B\uFE0F Auto-compacted this chat's earlier history before resuming, to avoid resending it at full cost.
|
|
15376
|
+
`);
|
|
15377
|
+
}
|
|
15378
|
+
return compacted;
|
|
15379
|
+
}
|
|
14238
15380
|
async function runAgentLoop2(initialMessages, options) {
|
|
14239
15381
|
const messages = [...initialMessages];
|
|
14240
15382
|
const model = options.model ?? "turbo";
|
|
@@ -14243,6 +15385,7 @@ Continue the work from here.` }] });
|
|
|
14243
15385
|
const agentScope = options._agentScope ?? "root";
|
|
14244
15386
|
const agentTypes = (0, agentTypes_1.loadAgentTypes)(options.workDir);
|
|
14245
15387
|
const agentsCatalogue = depth === 0 ? (0, agentTypes_1.summariseAgents)(agentTypes) : "";
|
|
15388
|
+
const skillsCatalogue = (0, skills_1.summariseSkills)((0, skills_1.loadSkills)(options.workDir));
|
|
14246
15389
|
const settings = (0, rules_1.loadSettings)(options.workDir);
|
|
14247
15390
|
const sandboxCfg = (0, sandbox_1.parseSandboxConfig)(settings.raw.sandbox) ?? void 0;
|
|
14248
15391
|
const maxIterations = resolveMaxIterations(options.maxIterations, settings.raw);
|
|
@@ -14254,6 +15397,7 @@ Continue the work from here.` }] });
|
|
|
14254
15397
|
let compacting = false;
|
|
14255
15398
|
const hardCap = autoContinue ? Math.max(maxIterations, MAX_ITERATIONS_CEILING) : maxIterations;
|
|
14256
15399
|
let completedCleanly = false;
|
|
15400
|
+
let completedRounds = 0;
|
|
14257
15401
|
let stalledOut = false;
|
|
14258
15402
|
let consecutiveErrorRounds = 0;
|
|
14259
15403
|
let budget = maxIterations;
|
|
@@ -14270,16 +15414,15 @@ Continue the work from here.` }] });
|
|
|
14270
15414
|
if (options.abortSignal?.aborted)
|
|
14271
15415
|
break;
|
|
14272
15416
|
let bodyBytes = estimateBodyBytes(messages);
|
|
15417
|
+
const prunePressure = lastPromptTokens > contextWindow * AUTO_PRUNE_THRESHOLD;
|
|
14273
15418
|
const tokenPressure = lastPromptTokens > contextWindow * AUTO_COMPACT_THRESHOLD;
|
|
14274
15419
|
let bytePressure = bodyBytes > MAX_BODY_BYTES;
|
|
14275
|
-
if (autoCompact && !compacting && bytePressure && messages.length > PRUNE_KEEP_RECENT + 2) {
|
|
14276
|
-
const reclaimed = pruneOldToolResults(messages);
|
|
15420
|
+
if (autoCompact && !compacting && (prunePressure || bytePressure) && messages.length > PRUNE_KEEP_RECENT + 2) {
|
|
15421
|
+
const reclaimed = pruneOldToolResults(messages, PRUNE_MIN_RECLAIM_BYTES);
|
|
14277
15422
|
if (reclaimed > 0) {
|
|
14278
15423
|
bodyBytes = estimateBodyBytes(messages);
|
|
14279
15424
|
bytePressure = bodyBytes > MAX_BODY_BYTES;
|
|
14280
|
-
options.onText(
|
|
14281
|
-
\u267B\uFE0F Trimmed ~${(reclaimed / (1024 * 1024)).toFixed(1)}MB of older tool output to conserve context.
|
|
14282
|
-
`);
|
|
15425
|
+
(options.onNotice ?? options.onText)(`\u267B\uFE0F Trimmed ~${(reclaimed / (1024 * 1024)).toFixed(1)}MB of already-processed tool output to keep this chat cheap to continue.`);
|
|
14283
15426
|
}
|
|
14284
15427
|
}
|
|
14285
15428
|
if (autoCompact && !compacting && (tokenPressure || bytePressure) && messages.length > COMPACT_KEEP_MIN + 2) {
|
|
@@ -14289,9 +15432,7 @@ Continue the work from here.` }] });
|
|
|
14289
15432
|
if (did) {
|
|
14290
15433
|
lastPromptTokens = 0;
|
|
14291
15434
|
const reason = bytePressure ? `body ~${(bodyBytes / (1024 * 1024)).toFixed(1)}MB` : "context window";
|
|
14292
|
-
options.onText(
|
|
14293
|
-
\u267B\uFE0F Auto-compacted earlier conversation to stay within the ${reason}.
|
|
14294
|
-
`);
|
|
15435
|
+
(options.onNotice ?? options.onText)(`\u267B\uFE0F Auto-compacted earlier conversation to stay within the ${reason}.`);
|
|
14295
15436
|
}
|
|
14296
15437
|
} finally {
|
|
14297
15438
|
compacting = false;
|
|
@@ -14302,7 +15443,6 @@ Continue the work from here.` }] });
|
|
|
14302
15443
|
\u26A0\uFE0F Approaching the ${budget}-step limit (step ${iteration + 1}). Please wrap up and summarise what has been done.
|
|
14303
15444
|
`);
|
|
14304
15445
|
}
|
|
14305
|
-
const pendingToolUse = [];
|
|
14306
15446
|
const onEvent = (event) => {
|
|
14307
15447
|
switch (event.type) {
|
|
14308
15448
|
case "text":
|
|
@@ -14318,21 +15458,31 @@ Continue the work from here.` }] });
|
|
|
14318
15458
|
options.onThinkingProgress?.(event.tokens);
|
|
14319
15459
|
break;
|
|
14320
15460
|
case "tool_use":
|
|
14321
|
-
pendingToolUse.push({
|
|
14322
|
-
type: "tool_use",
|
|
14323
|
-
id: event.id,
|
|
14324
|
-
name: event.name,
|
|
14325
|
-
input: event.input
|
|
14326
|
-
});
|
|
14327
15461
|
break;
|
|
14328
15462
|
case "usage":
|
|
14329
|
-
|
|
14330
|
-
|
|
15463
|
+
if (!event.partial) {
|
|
15464
|
+
lastPromptTokens = (event.usage.input_tokens ?? 0) + (event.usage.cache_creation_input_tokens ?? 0) + (event.usage.cache_read_input_tokens ?? 0);
|
|
15465
|
+
}
|
|
15466
|
+
options.onUsage(event.usage, event.partial === true || event.replayed === true);
|
|
14331
15467
|
break;
|
|
14332
15468
|
case "message_complete":
|
|
14333
15469
|
break;
|
|
15470
|
+
case "retry":
|
|
15471
|
+
options.onRetry?.(event.attempt, event.maxAttempts, event.reason);
|
|
15472
|
+
break;
|
|
15473
|
+
case "retry_resolved":
|
|
15474
|
+
options.onRetryResolved?.();
|
|
15475
|
+
break;
|
|
15476
|
+
case "stream_restart":
|
|
15477
|
+
options.onStreamRestart?.(event.reason, event.discardedChars);
|
|
15478
|
+
break;
|
|
15479
|
+
case "balance_status":
|
|
15480
|
+
options.onBalanceStatus?.(event.balance, event.zero);
|
|
15481
|
+
break;
|
|
14334
15482
|
case "done":
|
|
15483
|
+
break;
|
|
14335
15484
|
case "error":
|
|
15485
|
+
options.onNotice?.(`\u26A0\uFE0F ${event.message}`);
|
|
14336
15486
|
break;
|
|
14337
15487
|
}
|
|
14338
15488
|
};
|
|
@@ -14348,13 +15498,25 @@ Continue the work from here.` }] });
|
|
|
14348
15498
|
clientType: options.clientType,
|
|
14349
15499
|
abortSignal: options.abortSignal,
|
|
14350
15500
|
extraTools: options.mcpManager?.getAnthropicTools(),
|
|
14351
|
-
agents: agentsCatalogue || void 0
|
|
15501
|
+
agents: agentsCatalogue || void 0,
|
|
15502
|
+
skills: skillsCatalogue || void 0,
|
|
15503
|
+
// Only allow a post-render restart when the caller actually implements the
|
|
15504
|
+
// rollback. Without a handler the partial output can't be un-rendered, so we
|
|
15505
|
+
// keep the old conservative behaviour (fail the turn) rather than duplicate
|
|
15506
|
+
// text on screen.
|
|
15507
|
+
allowRestartAfterRender: !!options.onStreamRestart
|
|
14352
15508
|
}, onEvent);
|
|
14353
15509
|
} catch (err) {
|
|
14354
15510
|
if (options.abortSignal?.aborted || err.name === "AbortError")
|
|
14355
15511
|
break;
|
|
15512
|
+
const status = err.status;
|
|
15513
|
+
if (status === 402) {
|
|
15514
|
+
const balance = err.balance ?? 0;
|
|
15515
|
+
options.onBalanceStatus?.(balance, true);
|
|
15516
|
+
break;
|
|
15517
|
+
}
|
|
14356
15518
|
runSimpleHooks(hooks.OnError, options.workDir);
|
|
14357
|
-
throw new
|
|
15519
|
+
throw new types_1.AgentTurnError(`Stream failed: ${err.message}`, trimToResumableBoundary(messages), completedRounds, err);
|
|
14358
15520
|
}
|
|
14359
15521
|
if (options.abortSignal?.aborted)
|
|
14360
15522
|
break;
|
|
@@ -14371,9 +15533,12 @@ Continue the work from here.` }] });
|
|
|
14371
15533
|
completedCleanly = true;
|
|
14372
15534
|
break;
|
|
14373
15535
|
}
|
|
14374
|
-
|
|
15536
|
+
const { stopReason: _stopReason, ...historyMessage } = assistantMessage;
|
|
15537
|
+
messages.push(historyMessage);
|
|
14375
15538
|
const serverSideResultIds = new Set(assistantMessage.content.filter((b) => b.type === "tool_result").map((b) => b.tool_use_id).filter((id) => !!id));
|
|
14376
15539
|
const toolUseBlocks = assistantMessage.content.filter((block) => block.type === "tool_use" && !serverSideResultIds.has(block.id));
|
|
15540
|
+
const lastBlock = assistantMessage.content[assistantMessage.content.length - 1];
|
|
15541
|
+
const truncatedToolUseId = assistantMessage.stopReason === "max_tokens" && lastBlock?.type === "tool_use" ? lastBlock.id : void 0;
|
|
14377
15542
|
if (toolUseBlocks.length === 0) {
|
|
14378
15543
|
const queued = options.takePendingInput?.() ?? [];
|
|
14379
15544
|
if (queued.length) {
|
|
@@ -14441,8 +15606,15 @@ Continue the work from here.` }] });
|
|
|
14441
15606
|
const toolResults = await Promise.all(toolUseBlocks.map(async (block) => {
|
|
14442
15607
|
const { id, name, input } = block;
|
|
14443
15608
|
options.onToolUse(name, input);
|
|
14444
|
-
const description = humanDescription(name, input);
|
|
14445
15609
|
let result;
|
|
15610
|
+
if (id === truncatedToolUseId) {
|
|
15611
|
+
result = {
|
|
15612
|
+
error: `This tool call (${name}) was CUT OFF because the response hit the model's output-token limit mid-generation (stop_reason: max_tokens) \u2014 its arguments may be incomplete or missing fields entirely, so it was NOT executed to avoid a silent partial edit. Retry with a SMALLER call: ` + (name === "multi_edit" || name === "edit_file" ? "split this into fewer edits per call (or call edit_file once per change instead of one large multi_edit), " : name === "write_file" ? "write the file in smaller chunks via write_file + edit_file follow-ups instead of one large write_file, " : "") + `so the full response fits comfortably under the per-turn output budget.`
|
|
15613
|
+
};
|
|
15614
|
+
options.onToolResult(name, result);
|
|
15615
|
+
return { block: { ...block, id }, result };
|
|
15616
|
+
}
|
|
15617
|
+
const description = humanDescription(name, input);
|
|
14446
15618
|
let permitted;
|
|
14447
15619
|
try {
|
|
14448
15620
|
permitted = await options.requestPermission({ tool: name, input, description });
|
|
@@ -14479,6 +15651,10 @@ Continue the work from here.` }] });
|
|
|
14479
15651
|
} catch (err) {
|
|
14480
15652
|
result = { error: `Tool execution failed: ${err.message}` };
|
|
14481
15653
|
}
|
|
15654
|
+
if (name === "memory_write" && result.error === void 0) {
|
|
15655
|
+
const scope = input.scope === "global" ? "global" : "project";
|
|
15656
|
+
void maybeCompactMemory(scope, options);
|
|
15657
|
+
}
|
|
14482
15658
|
const post = runToolHooks(hooks.PostToolUse, "PostToolUse", name, input, options.workDir, result);
|
|
14483
15659
|
const injected = [pre.context, post.context].filter(Boolean).join("\n");
|
|
14484
15660
|
if (injected) {
|
|
@@ -14540,6 +15716,7 @@ ${result.output}` : `Error: ${result.error}` : result.output ?? "",
|
|
|
14540
15716
|
content: toolResultContent
|
|
14541
15717
|
};
|
|
14542
15718
|
messages.push(toolResultMessage);
|
|
15719
|
+
completedRounds++;
|
|
14543
15720
|
options.onProgress?.(messages);
|
|
14544
15721
|
const allErrored = toolResults.length > 0 && toolResults.every(({ result }) => result.error !== void 0);
|
|
14545
15722
|
consecutiveErrorRounds = allErrored ? consecutiveErrorRounds + 1 : 0;
|
|
@@ -14565,11 +15742,32 @@ ${result.output}` : `Error: ${result.error}` : result.output ?? "",
|
|
|
14565
15742
|
`);
|
|
14566
15743
|
}
|
|
14567
15744
|
}
|
|
15745
|
+
} catch (err) {
|
|
15746
|
+
if (err instanceof types_1.AgentTurnError)
|
|
15747
|
+
throw err;
|
|
15748
|
+
if (options.abortSignal?.aborted || err.name === "AbortError")
|
|
15749
|
+
throw err;
|
|
15750
|
+
throw new types_1.AgentTurnError(err?.message || "The turn ended unexpectedly", trimToResumableBoundary(messages), completedRounds, err);
|
|
14568
15751
|
} finally {
|
|
14569
15752
|
runSimpleHooks(hooks.OnStop, options.workDir);
|
|
14570
15753
|
}
|
|
14571
15754
|
return messages;
|
|
14572
15755
|
}
|
|
15756
|
+
function trimToResumableBoundary(messages) {
|
|
15757
|
+
const isUnansweredInvocation = (m2) => {
|
|
15758
|
+
if (m2?.role !== "assistant" || !Array.isArray(m2.content))
|
|
15759
|
+
return false;
|
|
15760
|
+
const blocks = m2.content;
|
|
15761
|
+
if (blocks.some((b) => b?.type === "tool_use"))
|
|
15762
|
+
return true;
|
|
15763
|
+
const answeredIds = new Set(blocks.filter((b) => /_tool_result$/.test(b?.type ?? "")).map((b) => b?.tool_use_id));
|
|
15764
|
+
return blocks.some((b) => b?.type === "server_tool_use" && !answeredIds.has(b?.id));
|
|
15765
|
+
};
|
|
15766
|
+
let end = messages.length;
|
|
15767
|
+
while (end > 0 && isUnansweredInvocation(messages[end - 1]))
|
|
15768
|
+
end--;
|
|
15769
|
+
return end === messages.length ? messages : messages.slice(0, end);
|
|
15770
|
+
}
|
|
14573
15771
|
}
|
|
14574
15772
|
});
|
|
14575
15773
|
|
|
@@ -15668,193 +16866,6 @@ var require_manager2 = __commonJS({
|
|
|
15668
16866
|
}
|
|
15669
16867
|
});
|
|
15670
16868
|
|
|
15671
|
-
// ../core/dist/commands/loader.js
|
|
15672
|
-
var require_loader = __commonJS({
|
|
15673
|
-
"../core/dist/commands/loader.js"(exports2) {
|
|
15674
|
-
"use strict";
|
|
15675
|
-
var __createBinding = exports2 && exports2.__createBinding || (Object.create ? function(o, m2, k, k2) {
|
|
15676
|
-
if (k2 === void 0)
|
|
15677
|
-
k2 = k;
|
|
15678
|
-
var desc = Object.getOwnPropertyDescriptor(m2, k);
|
|
15679
|
-
if (!desc || ("get" in desc ? !m2.__esModule : desc.writable || desc.configurable)) {
|
|
15680
|
-
desc = { enumerable: true, get: function() {
|
|
15681
|
-
return m2[k];
|
|
15682
|
-
} };
|
|
15683
|
-
}
|
|
15684
|
-
Object.defineProperty(o, k2, desc);
|
|
15685
|
-
} : function(o, m2, k, k2) {
|
|
15686
|
-
if (k2 === void 0)
|
|
15687
|
-
k2 = k;
|
|
15688
|
-
o[k2] = m2[k];
|
|
15689
|
-
});
|
|
15690
|
-
var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? function(o, v) {
|
|
15691
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15692
|
-
} : function(o, v) {
|
|
15693
|
-
o["default"] = v;
|
|
15694
|
-
});
|
|
15695
|
-
var __importStar = exports2 && exports2.__importStar || /* @__PURE__ */ function() {
|
|
15696
|
-
var ownKeys = function(o) {
|
|
15697
|
-
ownKeys = Object.getOwnPropertyNames || function(o2) {
|
|
15698
|
-
var ar = [];
|
|
15699
|
-
for (var k in o2)
|
|
15700
|
-
if (Object.prototype.hasOwnProperty.call(o2, k))
|
|
15701
|
-
ar[ar.length] = k;
|
|
15702
|
-
return ar;
|
|
15703
|
-
};
|
|
15704
|
-
return ownKeys(o);
|
|
15705
|
-
};
|
|
15706
|
-
return function(mod) {
|
|
15707
|
-
if (mod && mod.__esModule)
|
|
15708
|
-
return mod;
|
|
15709
|
-
var result = {};
|
|
15710
|
-
if (mod != null) {
|
|
15711
|
-
for (var k = ownKeys(mod), i2 = 0; i2 < k.length; i2++)
|
|
15712
|
-
if (k[i2] !== "default")
|
|
15713
|
-
__createBinding(result, mod, k[i2]);
|
|
15714
|
-
}
|
|
15715
|
-
__setModuleDefault(result, mod);
|
|
15716
|
-
return result;
|
|
15717
|
-
};
|
|
15718
|
-
}();
|
|
15719
|
-
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
15720
|
-
exports2.loadSlashCommands = loadSlashCommands2;
|
|
15721
|
-
exports2.findSlashCommand = findSlashCommand2;
|
|
15722
|
-
exports2.expandCommand = expandCommand2;
|
|
15723
|
-
var fs6 = __importStar(require("fs"));
|
|
15724
|
-
var path5 = __importStar(require("path"));
|
|
15725
|
-
var os5 = __importStar(require("os"));
|
|
15726
|
-
var child_process_1 = require("child_process");
|
|
15727
|
-
var index_1 = require_plugins();
|
|
15728
|
-
var BUILTIN_COMMANDS = [
|
|
15729
|
-
{
|
|
15730
|
-
name: "review",
|
|
15731
|
-
description: "Review uncommitted changes (or a PR/branch diff) for bugs and risks",
|
|
15732
|
-
source: "builtin",
|
|
15733
|
-
body: [
|
|
15734
|
-
"Review the following diff like a meticulous senior engineer. Target: $ARGUMENTS",
|
|
15735
|
-
"(If no target given, review the uncommitted working-tree changes below. If a branch or PR",
|
|
15736
|
-
"number is given, run the appropriate `git diff <base>...` or `gh pr diff <n>` yourself first.)",
|
|
15737
|
-
"",
|
|
15738
|
-
"Branch: !`git branch --show-current`",
|
|
15739
|
-
"Status: !`git status --short`",
|
|
15740
|
-
"",
|
|
15741
|
-
"Diff (uncommitted):",
|
|
15742
|
-
"```diff",
|
|
15743
|
-
"!`git diff HEAD --unified=5 --no-color | head -4000`",
|
|
15744
|
-
"```",
|
|
15745
|
-
"",
|
|
15746
|
-
"Review methodology:",
|
|
15747
|
-
"1. Read the surrounding code of every changed hunk (read_file with offset/limit) \u2014 never judge a hunk in isolation.",
|
|
15748
|
-
"2. Look for: correctness bugs, edge cases (empty/null/unicode/concurrency), security issues",
|
|
15749
|
-
" (injection, path traversal, secrets), breaking API changes (find_references / search callers),",
|
|
15750
|
-
" silent behaviour changes, and missing error handling.",
|
|
15751
|
-
"3. Check tests: do existing tests cover the change? Are assertions weakened?",
|
|
15752
|
-
"",
|
|
15753
|
-
"Output format:",
|
|
15754
|
-
"- \u{1F534} Critical (must fix before merge) \u2014 with file:line and a concrete fix",
|
|
15755
|
-
"- \u{1F7E1} Warning (should fix) \u2014 with file:line",
|
|
15756
|
-
"- \u{1F7E2} Suggestion (nice to have)",
|
|
15757
|
-
"- Verdict: APPROVE / REQUEST CHANGES with a one-paragraph summary.",
|
|
15758
|
-
"Do NOT modify any files \u2014 this is a read-only review."
|
|
15759
|
-
].join("\n")
|
|
15760
|
-
}
|
|
15761
|
-
];
|
|
15762
|
-
function parseFrontmatter(raw) {
|
|
15763
|
-
const m2 = /^\s*---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/.exec(raw);
|
|
15764
|
-
if (!m2)
|
|
15765
|
-
return { meta: {}, body: raw.trim() };
|
|
15766
|
-
const meta = {};
|
|
15767
|
-
for (const line of m2[1].split(/\r?\n/)) {
|
|
15768
|
-
const kv = /^([A-Za-z0-9_-]+)\s*:\s*(.*)$/.exec(line.trim());
|
|
15769
|
-
if (kv)
|
|
15770
|
-
meta[kv[1].toLowerCase()] = kv[2].trim().replace(/^["']|["']$/g, "");
|
|
15771
|
-
}
|
|
15772
|
-
return { meta, body: (m2[2] ?? "").trim() };
|
|
15773
|
-
}
|
|
15774
|
-
function loadDir(dir, source, into) {
|
|
15775
|
-
let files;
|
|
15776
|
-
try {
|
|
15777
|
-
files = fs6.readdirSync(dir).filter((f3) => f3.endsWith(".md"));
|
|
15778
|
-
} catch {
|
|
15779
|
-
return;
|
|
15780
|
-
}
|
|
15781
|
-
for (const file of files) {
|
|
15782
|
-
try {
|
|
15783
|
-
const raw = fs6.readFileSync(path5.join(dir, file), "utf-8");
|
|
15784
|
-
const { meta, body } = parseFrontmatter(raw);
|
|
15785
|
-
const name = (meta.name || path5.basename(file, ".md")).trim().toLowerCase();
|
|
15786
|
-
if (!name)
|
|
15787
|
-
continue;
|
|
15788
|
-
if (source !== "project" && into.has(name))
|
|
15789
|
-
continue;
|
|
15790
|
-
const model = ["turbo", "pro", "ultra"].find((x2) => x2 === (meta.model ?? "").toLowerCase());
|
|
15791
|
-
into.set(name, {
|
|
15792
|
-
name,
|
|
15793
|
-
description: meta.description || `Custom /${name} command`,
|
|
15794
|
-
model,
|
|
15795
|
-
mode: meta.mode || void 0,
|
|
15796
|
-
body,
|
|
15797
|
-
source
|
|
15798
|
-
});
|
|
15799
|
-
} catch {
|
|
15800
|
-
}
|
|
15801
|
-
}
|
|
15802
|
-
}
|
|
15803
|
-
function loadSlashCommands2(workDir) {
|
|
15804
|
-
const out = /* @__PURE__ */ new Map();
|
|
15805
|
-
loadDir(path5.join(workDir, ".nexrall", "commands"), "project", out);
|
|
15806
|
-
loadDir(path5.join(os5.homedir(), ".nexrall", "commands"), "global", out);
|
|
15807
|
-
for (const dir of (0, index_1.pluginAssetDirs)(workDir, "commands"))
|
|
15808
|
-
loadDir(dir, "plugin", out);
|
|
15809
|
-
for (const cmd of BUILTIN_COMMANDS) {
|
|
15810
|
-
if (!out.has(cmd.name))
|
|
15811
|
-
out.set(cmd.name, cmd);
|
|
15812
|
-
}
|
|
15813
|
-
return [...out.values()];
|
|
15814
|
-
}
|
|
15815
|
-
function findSlashCommand2(cmds, name) {
|
|
15816
|
-
const want = name.replace(/^\//, "").trim().toLowerCase();
|
|
15817
|
-
return cmds.find((c) => c.name === want);
|
|
15818
|
-
}
|
|
15819
|
-
function expandCommand2(cmd, argString, workDir) {
|
|
15820
|
-
const args = argString.trim();
|
|
15821
|
-
const positional = args ? args.split(/\s+/) : [];
|
|
15822
|
-
let out = cmd.body;
|
|
15823
|
-
out = out.replace(/!`([^`]+)`/g, (_m, c) => {
|
|
15824
|
-
try {
|
|
15825
|
-
const stdout = (0, child_process_1.execSync)(c, { cwd: workDir, encoding: "utf-8", timeout: 15e3, stdio: ["ignore", "pipe", "pipe"] });
|
|
15826
|
-
return stdout.trim();
|
|
15827
|
-
} catch (err) {
|
|
15828
|
-
return `[command failed: ${c} \u2014 ${err.message}]`;
|
|
15829
|
-
}
|
|
15830
|
-
});
|
|
15831
|
-
out = out.replace(/(^|\s)@([^\s]+)/g, (_m, lead, rel) => {
|
|
15832
|
-
const abs = path5.isAbsolute(rel) ? rel : path5.join(workDir, rel);
|
|
15833
|
-
try {
|
|
15834
|
-
const content = fs6.readFileSync(abs, "utf-8").slice(0, 12e3);
|
|
15835
|
-
return `${lead}
|
|
15836
|
-
[File: ${rel}]
|
|
15837
|
-
\`\`\`
|
|
15838
|
-
${content}
|
|
15839
|
-
\`\`\`
|
|
15840
|
-
`;
|
|
15841
|
-
} catch {
|
|
15842
|
-
return `${lead}[missing file: ${rel}]`;
|
|
15843
|
-
}
|
|
15844
|
-
});
|
|
15845
|
-
out = out.replace(/\$(\d+)/g, (_m, n) => positional[Number(n) - 1] ?? "");
|
|
15846
|
-
const hadArgsToken = /\$ARGUMENTS/.test(out);
|
|
15847
|
-
out = out.replace(/\$ARGUMENTS/g, args);
|
|
15848
|
-
if (!hadArgsToken && !/\$\d+/.test(cmd.body) && args) {
|
|
15849
|
-
out = `${out}
|
|
15850
|
-
|
|
15851
|
-
${args}`;
|
|
15852
|
-
}
|
|
15853
|
-
return out.trim();
|
|
15854
|
-
}
|
|
15855
|
-
}
|
|
15856
|
-
});
|
|
15857
|
-
|
|
15858
16869
|
// ../core/dist/permissions/destructive.js
|
|
15859
16870
|
var require_destructive = __commonJS({
|
|
15860
16871
|
"../core/dist/permissions/destructive.js"(exports2) {
|
|
@@ -16401,6 +17412,8 @@ var require_dist2 = __commonJS({
|
|
|
16401
17412
|
__exportStar(require_crossFile(), exports2);
|
|
16402
17413
|
__exportStar(require_flaky(), exports2);
|
|
16403
17414
|
__exportStar(require_claimEvidence(), exports2);
|
|
17415
|
+
__exportStar(require_memory(), exports2);
|
|
17416
|
+
__exportStar(require_skills(), exports2);
|
|
16404
17417
|
__exportStar(require_client2(), exports2);
|
|
16405
17418
|
__exportStar(require_httpClient(), exports2);
|
|
16406
17419
|
__exportStar(require_manager(), exports2);
|
|
@@ -22186,6 +23199,25 @@ var MarkdownStreamRenderer = class {
|
|
|
22186
23199
|
for (const line of parts)
|
|
22187
23200
|
this._line(line);
|
|
22188
23201
|
}
|
|
23202
|
+
/**
|
|
23203
|
+
* Drop all buffered/partial state without emitting it.
|
|
23204
|
+
*
|
|
23205
|
+
* Used when a stream died mid-render and the same turn is being restarted: the
|
|
23206
|
+
* replacement attempt re-sends the response from the beginning, so any half-parsed
|
|
23207
|
+
* line, unterminated code fence or partially accumulated table from the dead attempt
|
|
23208
|
+
* must be discarded. Flushing instead would print a fragment and then leave the
|
|
23209
|
+
* renderer in `_inCode`/`_tbl` state that corrupts everything the retry prints.
|
|
23210
|
+
*
|
|
23211
|
+
* Note this only resets the PARSER — characters already written to stdout have
|
|
23212
|
+
* scrolled away and cannot be unprinted; the caller prints a visible restart marker
|
|
23213
|
+
* so the duplicated prefix is explained rather than silently confusing.
|
|
23214
|
+
*/
|
|
23215
|
+
reset() {
|
|
23216
|
+
this._buf = "";
|
|
23217
|
+
this._inCode = false;
|
|
23218
|
+
this._codeLang = "";
|
|
23219
|
+
this._tbl = [];
|
|
23220
|
+
}
|
|
22189
23221
|
flush() {
|
|
22190
23222
|
if (this._buf) {
|
|
22191
23223
|
this._line(this._buf);
|
|
@@ -22883,11 +23915,15 @@ var import_code_core2 = __toESM(require_dist2());
|
|
|
22883
23915
|
var autoApproved = /* @__PURE__ */ new Set();
|
|
22884
23916
|
var _rules = { allow: [], ask: [], deny: [] };
|
|
22885
23917
|
var _workDir = process.cwd();
|
|
23918
|
+
var _mode = "auto";
|
|
22886
23919
|
function initPermissions(workDir) {
|
|
22887
23920
|
_workDir = workDir;
|
|
22888
23921
|
_rules = (0, import_code_core2.loadSettings)(workDir).permissions;
|
|
22889
23922
|
return _rules;
|
|
22890
23923
|
}
|
|
23924
|
+
function setMode(mode) {
|
|
23925
|
+
_mode = mode || "auto";
|
|
23926
|
+
}
|
|
22891
23927
|
function setAutoApprove(category) {
|
|
22892
23928
|
autoApproved.add(category);
|
|
22893
23929
|
}
|
|
@@ -22944,6 +23980,7 @@ async function requestPermission(req) {
|
|
|
22944
23980
|
"todo_write",
|
|
22945
23981
|
"memory_read",
|
|
22946
23982
|
"memory_write",
|
|
23983
|
+
"use_skill",
|
|
22947
23984
|
"notebook_read",
|
|
22948
23985
|
"get_diagnostics",
|
|
22949
23986
|
"go_to_definition",
|
|
@@ -22955,6 +23992,10 @@ async function requestPermission(req) {
|
|
|
22955
23992
|
];
|
|
22956
23993
|
if (readOnlyTools.includes(tool))
|
|
22957
23994
|
return true;
|
|
23995
|
+
if (_mode === "plan") {
|
|
23996
|
+
console.error(source_default.yellow(` \u2298 Plan mode \u2014 refused ${tool} (read-only until you switch mode).`));
|
|
23997
|
+
return false;
|
|
23998
|
+
}
|
|
22958
23999
|
if (tool === "write_file" || tool === "create_file") {
|
|
22959
24000
|
if (isAutoApproved("write"))
|
|
22960
24001
|
return true;
|
|
@@ -23356,6 +24397,23 @@ function tryExec(cmd, cwd) {
|
|
|
23356
24397
|
return void 0;
|
|
23357
24398
|
}
|
|
23358
24399
|
}
|
|
24400
|
+
var BILLING_URL = "https://app.nexrall.com/?settings=billing";
|
|
24401
|
+
function hyperlink(label, url) {
|
|
24402
|
+
return `\x1B]8;;${url}\x1B\\${label}\x1B]8;;\x1B\\`;
|
|
24403
|
+
}
|
|
24404
|
+
var lastBalanceNoticeState = null;
|
|
24405
|
+
function printBalanceNotice(balance, zero) {
|
|
24406
|
+
const state = zero ? "zero" : "low";
|
|
24407
|
+
if (lastBalanceNoticeState === state)
|
|
24408
|
+
return;
|
|
24409
|
+
lastBalanceNoticeState = state;
|
|
24410
|
+
const title = zero ? "Out of balance" : "Balance running low";
|
|
24411
|
+
const detail = zero ? "You're out of funds \u2014 top up to keep the agent working." : `Your wallet is under $5${typeof balance === "number" ? ` ($${balance.toFixed(2)} left)` : ""}. Top up before it runs out.`;
|
|
24412
|
+
console.log();
|
|
24413
|
+
console.log(source_default.bgYellow.black(` ${title} `) + " " + source_default.dim(detail));
|
|
24414
|
+
console.log(" " + source_default.yellow(hyperlink("\u2192 Top up", BILLING_URL)) + source_default.dim(` (${BILLING_URL})`));
|
|
24415
|
+
console.log();
|
|
24416
|
+
}
|
|
23359
24417
|
function collectEnv(workDir) {
|
|
23360
24418
|
return {
|
|
23361
24419
|
cwd: workDir,
|
|
@@ -23383,10 +24441,9 @@ var tryRead = (p) => {
|
|
|
23383
24441
|
};
|
|
23384
24442
|
function readNexrallMd(workDir) {
|
|
23385
24443
|
const parts = [];
|
|
23386
|
-
const memContent =
|
|
24444
|
+
const memContent = (0, import_code_core3.readAllMemory)(workDir);
|
|
23387
24445
|
if (memContent)
|
|
23388
|
-
parts.push(
|
|
23389
|
-
${memContent}`);
|
|
24446
|
+
parts.push(memContent);
|
|
23390
24447
|
const globalContent = tryRead(path3.join(os4.homedir(), ".nexrall", "nexrall.md"));
|
|
23391
24448
|
if (globalContent)
|
|
23392
24449
|
parts.push(`[Global instructions]
|
|
@@ -23430,6 +24487,7 @@ async function runTurn(messages, modelAlias, workDir, abortSignal, env2, nexrall
|
|
|
23430
24487
|
let toolStartTime = 0;
|
|
23431
24488
|
let lastToolName = "";
|
|
23432
24489
|
let thinkingTokens = 0;
|
|
24490
|
+
setMode(mode);
|
|
23433
24491
|
process.stdout.write("\n");
|
|
23434
24492
|
const result = await (0, import_code_core3.runAgentLoop)(messages, {
|
|
23435
24493
|
workDir,
|
|
@@ -23456,6 +24514,18 @@ async function runTurn(messages, modelAlias, workDir, abortSignal, env2, nexrall
|
|
|
23456
24514
|
spinner.stop();
|
|
23457
24515
|
mdRender.feed(text);
|
|
23458
24516
|
},
|
|
24517
|
+
// System notices (mid-run auto-prune/auto-compact housekeeping) are NOT part
|
|
24518
|
+
// of the model's own reply — used to go through onText, which spliced
|
|
24519
|
+
// "♻️ Trimmed ~0.3MB…" straight into the markdown stream renderer as if the
|
|
24520
|
+
// model itself had said it. Print it as its own dim line instead (same
|
|
24521
|
+
// treatment as the resume-time compaction notice below).
|
|
24522
|
+
onNotice: (text) => {
|
|
24523
|
+
if (abortSignal.aborted)
|
|
24524
|
+
return;
|
|
24525
|
+
mdRender.flush();
|
|
24526
|
+
spinner.stop();
|
|
24527
|
+
console.log(source_default.dim(` ${text}`));
|
|
24528
|
+
},
|
|
23459
24529
|
onToolUse: (name, input) => {
|
|
23460
24530
|
if (abortSignal.aborted)
|
|
23461
24531
|
return;
|
|
@@ -23473,8 +24543,50 @@ async function runTurn(messages, modelAlias, workDir, abortSignal, env2, nexrall
|
|
|
23473
24543
|
console.log(formatToolResult(lastToolName, res, durationMs));
|
|
23474
24544
|
toolStartTime = 0;
|
|
23475
24545
|
},
|
|
23476
|
-
|
|
23477
|
-
|
|
24546
|
+
// Ignore a `partial` report: it belongs to a cut-short attempt that was restarted,
|
|
24547
|
+
// and the replacement attempt reports the turn's real totals. Letting it through
|
|
24548
|
+
// would print a token count for output the user never saw.
|
|
24549
|
+
onUsage: (u, partial) => {
|
|
24550
|
+
if (!partial)
|
|
24551
|
+
lastUsage = u;
|
|
24552
|
+
},
|
|
24553
|
+
// A transient disconnect (network drop, machine sleep/wake, overloaded upstream)
|
|
24554
|
+
// is retried transparently by the network layer — without this, that pause was
|
|
24555
|
+
// completely invisible: the CLI just appeared to freeze and then resume with no
|
|
24556
|
+
// explanation. Reuse the same spinner to show what's actually happening.
|
|
24557
|
+
onRetry: (attempt, _maxAttempts, reason) => {
|
|
24558
|
+
if (abortSignal.aborted)
|
|
24559
|
+
return;
|
|
24560
|
+
spinner.start(`${reason}\u2026 (attempt ${attempt})`);
|
|
24561
|
+
},
|
|
24562
|
+
onRetryResolved: () => {
|
|
24563
|
+
spinner.stop();
|
|
24564
|
+
},
|
|
24565
|
+
// The stream died after part of the answer had already been printed, and the
|
|
24566
|
+
// turn is being restarted from the top. Providing this handler is what OPTS US
|
|
24567
|
+
// IN to post-render restarts at all (see AgentLoopOptions.onStreamRestart) —
|
|
24568
|
+
// without it a mid-answer disconnect kills the whole turn.
|
|
24569
|
+
//
|
|
24570
|
+
// A terminal can't unprint scrolled-away output, so instead of pretending the
|
|
24571
|
+
// fragment never happened we (a) reset the markdown parser so the dead attempt's
|
|
24572
|
+
// half-open code fence/table can't corrupt everything the retry prints, and
|
|
24573
|
+
// (b) draw an explicit marker so the user understands why the answer restarts.
|
|
24574
|
+
onStreamRestart: (reason, discardedChars) => {
|
|
24575
|
+
if (abortSignal.aborted)
|
|
24576
|
+
return;
|
|
24577
|
+
spinner.stop();
|
|
24578
|
+
mdRender.reset();
|
|
24579
|
+
if (discardedChars > 0) {
|
|
24580
|
+
console.log(
|
|
24581
|
+
"\n" + source_default.yellow(" \u21BA Connection dropped mid-answer \u2014 restarting this response.") + source_default.dim(`
|
|
24582
|
+
(${reason}. The ${discardedChars} characters above are incomplete; the full answer follows.)`) + "\n"
|
|
24583
|
+
);
|
|
24584
|
+
}
|
|
24585
|
+
},
|
|
24586
|
+
onBalanceStatus: (balance, zero) => {
|
|
24587
|
+
spinner.stop();
|
|
24588
|
+
mdRender.flush();
|
|
24589
|
+
printBalanceNotice(balance, zero);
|
|
23478
24590
|
},
|
|
23479
24591
|
requestPermission,
|
|
23480
24592
|
checkpointManager,
|
|
@@ -23508,6 +24620,13 @@ async function runTurnHeadless(messages, modelAlias, workDir, _abortSignal, env2
|
|
|
23508
24620
|
resultText += text;
|
|
23509
24621
|
emit({ type: "text", text });
|
|
23510
24622
|
},
|
|
24623
|
+
// System notice (mid-run auto-prune/auto-compact) — emit as its own event
|
|
24624
|
+
// type instead of falling through to onText, so a stream-json consumer
|
|
24625
|
+
// doesn't see compaction housekeeping text mixed into the model's `text`
|
|
24626
|
+
// events or accumulated into resultText.
|
|
24627
|
+
onNotice: (text) => {
|
|
24628
|
+
emit({ type: "notice", text });
|
|
24629
|
+
},
|
|
23511
24630
|
onToolUse: (name, input) => {
|
|
23512
24631
|
emit({ type: "tool_use", tool: name, input });
|
|
23513
24632
|
},
|
|
@@ -23515,9 +24634,26 @@ async function runTurnHeadless(messages, modelAlias, workDir, _abortSignal, env2
|
|
|
23515
24634
|
toolCallCount++;
|
|
23516
24635
|
emit({ type: "tool_result", tool: name, ok: res.error === void 0, ...res.error ? { error: res.error } : {} });
|
|
23517
24636
|
},
|
|
23518
|
-
|
|
23519
|
-
|
|
23520
|
-
|
|
24637
|
+
// Headless consumers get the partial report too — tagged, so a script can account
|
|
24638
|
+
// for the real cost of a restarted turn — but it never becomes `lastUsage`, which
|
|
24639
|
+
// represents the turn's actual output.
|
|
24640
|
+
onUsage: (u, partial) => {
|
|
24641
|
+
if (!partial)
|
|
24642
|
+
lastUsage = u;
|
|
24643
|
+
emit({ type: "usage", usage: u, ...partial ? { partial: true } : {} });
|
|
24644
|
+
},
|
|
24645
|
+
onRetry: (attempt, maxAttempts, reason) => {
|
|
24646
|
+
emit({ type: "retry", attempt, max_attempts: maxAttempts, reason });
|
|
24647
|
+
},
|
|
24648
|
+
// Headless consumers parse NDJSON, so a restart is trivially clean for them:
|
|
24649
|
+
// they simply drop every `text`/`thinking` event seen since the turn started.
|
|
24650
|
+
// Emitting it also opts headless mode into post-render restarts, so a scripted
|
|
24651
|
+
// /CI run survives a blip instead of exiting non-zero halfway through.
|
|
24652
|
+
onStreamRestart: (reason, discardedChars) => {
|
|
24653
|
+
emit({ type: "stream_restart", reason, discarded_chars: discardedChars });
|
|
24654
|
+
},
|
|
24655
|
+
onBalanceStatus: (balance, zero) => {
|
|
24656
|
+
emit({ type: "balance_status", balance, zero, billing_url: BILLING_URL });
|
|
23521
24657
|
},
|
|
23522
24658
|
// Headless → auto-approve (no TTY to ask on). Destructive/irreversible
|
|
23523
24659
|
// commands (DB drops, force-push, terraform destroy…) fail CLOSED here: with
|
|
@@ -23559,6 +24695,7 @@ function printHelp() {
|
|
|
23559
24695
|
["/rewind [id]", "List file checkpoints, or roll back to one"],
|
|
23560
24696
|
["/compact", "Summarize conversation to save tokens"],
|
|
23561
24697
|
["/init", "Generate nexrall.md for this project"],
|
|
24698
|
+
["/memory [global] [clear]", "View persistent memory (project or global); /memory clear to wipe"],
|
|
23562
24699
|
["/help", "Show this help"],
|
|
23563
24700
|
["/model [turbo|pro|ultra]", "Switch model"],
|
|
23564
24701
|
["/mode [ask|edit|plan|auto]", "Set agent mode"],
|
|
@@ -23566,7 +24703,7 @@ function printHelp() {
|
|
|
23566
24703
|
["/yolo", "Auto-approve all permissions"],
|
|
23567
24704
|
["/balance", "Show wallet balance"],
|
|
23568
24705
|
["/add <filepath>", "Add a file to conversation context"],
|
|
23569
|
-
["/
|
|
24706
|
+
["/skills", "List skills (.nexrall/skills/<name>/SKILL.md or .nexrall/commands/*.md) \u2014 the model can also auto-invoke these"],
|
|
23570
24707
|
["/plugins", "List installed plugins (.nexrall/plugins/)"],
|
|
23571
24708
|
["/update", "Update nex to the latest version"]
|
|
23572
24709
|
];
|
|
@@ -23625,9 +24762,9 @@ async function startChatSession(options) {
|
|
|
23625
24762
|
const ruleCount = permRules.allow.length + permRules.ask.length + permRules.deny.length;
|
|
23626
24763
|
if (ruleCount && !headless)
|
|
23627
24764
|
console.log(source_default.dim(` ${ruleCount} permission rule(s) loaded`));
|
|
23628
|
-
let
|
|
23629
|
-
if (
|
|
23630
|
-
console.log(source_default.dim(` ${
|
|
24765
|
+
let skills = (0, import_code_core3.loadSkills)(workDir);
|
|
24766
|
+
if (skills.length && !headless)
|
|
24767
|
+
console.log(source_default.dim(` ${skills.length} skill(s) loaded`));
|
|
23631
24768
|
if (headless && !options.prompt && !options.stdinText) {
|
|
23632
24769
|
process.stdout.write(JSON.stringify({ type: "error", error: "--output-format json/stream-json requires a one-shot prompt (or piped stdin)." }) + "\n");
|
|
23633
24770
|
process.exit(1);
|
|
@@ -23642,9 +24779,23 @@ async function startChatSession(options) {
|
|
|
23642
24779
|
messages = stored.messages;
|
|
23643
24780
|
sessionId = stored.id;
|
|
23644
24781
|
sessionTitle = stored.title;
|
|
23645
|
-
|
|
23646
|
-
|
|
23647
|
-
|
|
24782
|
+
try {
|
|
24783
|
+
const compacted = await (0, import_code_core3.compactMessagesForResume)(messages, {
|
|
24784
|
+
workDir,
|
|
24785
|
+
model: modelAlias,
|
|
24786
|
+
clientType: "cli",
|
|
24787
|
+
onNotice: (text) => {
|
|
24788
|
+
if (!headless)
|
|
24789
|
+
console.log(source_default.dim(text.trim()));
|
|
24790
|
+
}
|
|
24791
|
+
});
|
|
24792
|
+
} catch {
|
|
24793
|
+
}
|
|
24794
|
+
if (!headless) {
|
|
24795
|
+
console.log(source_default.green(` Resumed session: ${source_default.bold(stored.title.slice(0, 60))}`));
|
|
24796
|
+
console.log(source_default.dim(` ${messages.length} messages restored`));
|
|
24797
|
+
console.log();
|
|
24798
|
+
}
|
|
23648
24799
|
}
|
|
23649
24800
|
const checkpoints = new import_code_core3.CheckpointManager(workDir, sessionId);
|
|
23650
24801
|
const updateTitle = () => {
|
|
@@ -23654,6 +24805,19 @@ async function startChatSession(options) {
|
|
|
23654
24805
|
const raw = first?.content[0]?.text ?? "";
|
|
23655
24806
|
sessionTitle = raw.length > 60 ? raw.slice(0, 60) + "\u2026" : raw;
|
|
23656
24807
|
};
|
|
24808
|
+
const recoverTurn = (err, current) => {
|
|
24809
|
+
const salvaged = (0, import_code_core3.salvageHistory)(err);
|
|
24810
|
+
console.error("\n" + source_default.red("Error: ") + String(err.message));
|
|
24811
|
+
if (!salvaged)
|
|
24812
|
+
return current;
|
|
24813
|
+
const rounds = err.progressCount ?? 1;
|
|
24814
|
+
console.error(
|
|
24815
|
+
source_default.dim(
|
|
24816
|
+
` Kept the ${rounds} step${rounds === 1 ? "" : "s"} completed before the interruption \u2014 send "continue" to pick up where it stopped.`
|
|
24817
|
+
)
|
|
24818
|
+
);
|
|
24819
|
+
return salvaged;
|
|
24820
|
+
};
|
|
23657
24821
|
let lastProgressSave = 0;
|
|
23658
24822
|
const saveProgress = (live) => {
|
|
23659
24823
|
const now = Date.now();
|
|
@@ -23689,10 +24853,27 @@ ${text}` : "");
|
|
|
23689
24853
|
updateTitle();
|
|
23690
24854
|
saveSession(sessionId, sessionTitle, workDir, messages);
|
|
23691
24855
|
} catch (err) {
|
|
24856
|
+
const salvaged = (0, import_code_core3.salvageHistory)(err);
|
|
24857
|
+
if (salvaged) {
|
|
24858
|
+
messages = salvaged;
|
|
24859
|
+
try {
|
|
24860
|
+
updateTitle();
|
|
24861
|
+
saveSession(sessionId, sessionTitle || "Untitled", workDir, messages);
|
|
24862
|
+
checkpoints.commitTurn();
|
|
24863
|
+
} catch {
|
|
24864
|
+
}
|
|
24865
|
+
}
|
|
23692
24866
|
if (options.outputFormat === "json" || options.outputFormat === "stream-json") {
|
|
23693
|
-
process.stdout.write(JSON.stringify({
|
|
24867
|
+
process.stdout.write(JSON.stringify({
|
|
24868
|
+
type: "error",
|
|
24869
|
+
error: String(err.message),
|
|
24870
|
+
...salvaged ? { resumable: true, messagesCompleted: salvaged.length } : {}
|
|
24871
|
+
}) + "\n");
|
|
23694
24872
|
} else {
|
|
23695
24873
|
console.error(source_default.red("\nError: ") + String(err.message));
|
|
24874
|
+
if (salvaged) {
|
|
24875
|
+
console.error(source_default.dim(" Progress was saved \u2014 run `nex --continue` to resume this session."));
|
|
24876
|
+
}
|
|
23696
24877
|
}
|
|
23697
24878
|
process.exit(1);
|
|
23698
24879
|
} finally {
|
|
@@ -23789,6 +24970,15 @@ ${text}` : "");
|
|
|
23789
24970
|
messages = stored.messages;
|
|
23790
24971
|
sessionId = stored.id;
|
|
23791
24972
|
sessionTitle = stored.title;
|
|
24973
|
+
try {
|
|
24974
|
+
await (0, import_code_core3.compactMessagesForResume)(messages, {
|
|
24975
|
+
workDir,
|
|
24976
|
+
model: modelAlias,
|
|
24977
|
+
clientType: "cli",
|
|
24978
|
+
onNotice: (text) => console.log(source_default.dim(text.trim()))
|
|
24979
|
+
});
|
|
24980
|
+
} catch {
|
|
24981
|
+
}
|
|
23792
24982
|
console.log(source_default.green(` Resumed: ${source_default.bold(stored.title.slice(0, 60))}`));
|
|
23793
24983
|
console.log(source_default.dim(` ${messages.length} messages restored`));
|
|
23794
24984
|
rl.prompt();
|
|
@@ -23945,6 +25135,49 @@ ${dirList}`;
|
|
|
23945
25135
|
rl.prompt();
|
|
23946
25136
|
return;
|
|
23947
25137
|
}
|
|
25138
|
+
case "/memory": {
|
|
25139
|
+
const memArgs = arg.toLowerCase().split(/\s+/).filter(Boolean);
|
|
25140
|
+
const wantsGlobalOnly = memArgs.includes("global");
|
|
25141
|
+
const wantsClear = memArgs.includes("clear");
|
|
25142
|
+
const memScope = wantsGlobalOnly ? "global" : "project";
|
|
25143
|
+
if (wantsClear) {
|
|
25144
|
+
rl.pause();
|
|
25145
|
+
const rl2 = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
25146
|
+
const label = memScope === "global" ? "GLOBAL" : "this PROJECT's";
|
|
25147
|
+
const answer = await new Promise((resolve3) => rl2.question(source_default.yellow(` Clear ${label} memory? This cannot be undone. [y/n] `), (a) => {
|
|
25148
|
+
rl2.close();
|
|
25149
|
+
resolve3(a.trim());
|
|
25150
|
+
}));
|
|
25151
|
+
if (answer === "y" || answer === "yes") {
|
|
25152
|
+
(0, import_code_core3.clearMemory)(memScope, workDir);
|
|
25153
|
+
console.log(source_default.green(` ${memScope === "global" ? "Global" : "Project"} memory cleared.`));
|
|
25154
|
+
} else {
|
|
25155
|
+
console.log(source_default.dim(" Cancelled."));
|
|
25156
|
+
}
|
|
25157
|
+
rl.resume();
|
|
25158
|
+
rl.prompt();
|
|
25159
|
+
return;
|
|
25160
|
+
}
|
|
25161
|
+
console.log();
|
|
25162
|
+
if (wantsGlobalOnly) {
|
|
25163
|
+
const stats = (0, import_code_core3.memoryStats)("global");
|
|
25164
|
+
const content = (0, import_code_core3.readMemory)("global");
|
|
25165
|
+
console.log(source_default.bold(" Global memory ") + source_default.dim(`(${stats.entries} entries, ${(stats.bytes / 1024).toFixed(1)}KB) \u2014 ${stats.file}`));
|
|
25166
|
+
console.log();
|
|
25167
|
+
console.log(content || source_default.dim(" No global memories saved yet."));
|
|
25168
|
+
} else {
|
|
25169
|
+
const projStats = (0, import_code_core3.memoryStats)("project", workDir);
|
|
25170
|
+
const globalStats = (0, import_code_core3.memoryStats)("global");
|
|
25171
|
+
console.log(source_default.bold(" Memory") + source_default.dim(` \u2014 project: ${projStats.entries} entries (${(projStats.bytes / 1024).toFixed(1)}KB) \xB7 global: ${globalStats.entries} entries (${(globalStats.bytes / 1024).toFixed(1)}KB)`));
|
|
25172
|
+
console.log();
|
|
25173
|
+
const merged = (0, import_code_core3.readAllMemory)(workDir);
|
|
25174
|
+
console.log(merged || source_default.dim(" No memories saved yet."));
|
|
25175
|
+
}
|
|
25176
|
+
console.log();
|
|
25177
|
+
console.log(source_default.dim(" Tip: /memory global \xB7 /memory clear \xB7 /memory global clear"));
|
|
25178
|
+
rl.prompt();
|
|
25179
|
+
return;
|
|
25180
|
+
}
|
|
23948
25181
|
case "/help":
|
|
23949
25182
|
printHelp();
|
|
23950
25183
|
rl.prompt();
|
|
@@ -24048,20 +25281,23 @@ ${content}
|
|
|
24048
25281
|
}
|
|
24049
25282
|
console.log();
|
|
24050
25283
|
}
|
|
24051
|
-
|
|
25284
|
+
skills = (0, import_code_core3.loadSkills)(workDir);
|
|
24052
25285
|
rl.prompt();
|
|
24053
25286
|
return;
|
|
24054
25287
|
}
|
|
25288
|
+
case "/skills":
|
|
24055
25289
|
case "/commands": {
|
|
24056
|
-
|
|
24057
|
-
|
|
24058
|
-
|
|
25290
|
+
skills = (0, import_code_core3.loadSkills)(workDir);
|
|
25291
|
+
const invocable = (0, import_code_core3.userInvokableSkills)(skills);
|
|
25292
|
+
if (!invocable.length) {
|
|
25293
|
+
console.log(source_default.dim(" No skills. Add .nexrall/skills/<name>/SKILL.md (or .nexrall/commands/<name>.md) to create one."));
|
|
24059
25294
|
} else {
|
|
24060
25295
|
console.log();
|
|
24061
|
-
console.log(source_default.bold("
|
|
24062
|
-
for (const
|
|
24063
|
-
const scope =
|
|
24064
|
-
|
|
25296
|
+
console.log(source_default.bold(" Skills:"));
|
|
25297
|
+
for (const s2 of invocable) {
|
|
25298
|
+
const scope = s2.source === "project" ? "" : source_default.dim(` (${s2.source})`);
|
|
25299
|
+
const auto = s2.disableModelInvocation ? source_default.dim(" [manual only]") : "";
|
|
25300
|
+
console.log(" " + source_default.cyan(`/${s2.name}`.padEnd(20)) + source_default.dim(s2.description) + scope + auto);
|
|
24065
25301
|
}
|
|
24066
25302
|
console.log();
|
|
24067
25303
|
}
|
|
@@ -24069,14 +25305,14 @@ ${content}
|
|
|
24069
25305
|
return;
|
|
24070
25306
|
}
|
|
24071
25307
|
default: {
|
|
24072
|
-
const custom = (0, import_code_core3.
|
|
25308
|
+
const custom = (0, import_code_core3.findSkill)(skills, cmd);
|
|
24073
25309
|
if (!custom) {
|
|
24074
25310
|
console.log(source_default.red(` Unknown command: ${cmd}`));
|
|
24075
|
-
console.log(source_default.dim(" Type /help for built-ins or /
|
|
25311
|
+
console.log(source_default.dim(" Type /help for built-ins or /skills for custom ones."));
|
|
24076
25312
|
rl.prompt();
|
|
24077
25313
|
return;
|
|
24078
25314
|
}
|
|
24079
|
-
const expanded = (0, import_code_core3.
|
|
25315
|
+
const expanded = (0, import_code_core3.expandSkill)(custom, arg, workDir);
|
|
24080
25316
|
const turnModel = custom.model ?? modelAlias;
|
|
24081
25317
|
const turnMode = custom.mode ?? agentMode;
|
|
24082
25318
|
console.log(source_default.dim(` Running /${custom.name}\u2026`));
|
|
@@ -24091,7 +25327,9 @@ ${content}
|
|
|
24091
25327
|
updateTitle();
|
|
24092
25328
|
saveSession(sessionId, sessionTitle, workDir, messages);
|
|
24093
25329
|
} catch (err) {
|
|
24094
|
-
|
|
25330
|
+
messages = recoverTurn(err, messages);
|
|
25331
|
+
updateTitle();
|
|
25332
|
+
saveSession(sessionId, sessionTitle || "Untitled", workDir, messages);
|
|
24095
25333
|
} finally {
|
|
24096
25334
|
agentRunning = false;
|
|
24097
25335
|
checkpoints.commitTurn();
|
|
@@ -24113,7 +25351,9 @@ ${content}
|
|
|
24113
25351
|
updateTitle();
|
|
24114
25352
|
saveSession(sessionId, sessionTitle, workDir, messages);
|
|
24115
25353
|
} catch (err) {
|
|
24116
|
-
|
|
25354
|
+
messages = recoverTurn(err, messages);
|
|
25355
|
+
updateTitle();
|
|
25356
|
+
saveSession(sessionId, sessionTitle || "Untitled", workDir, messages);
|
|
24117
25357
|
} finally {
|
|
24118
25358
|
agentRunning = false;
|
|
24119
25359
|
checkpoints.commitTurn();
|
|
@@ -24335,7 +25575,7 @@ function pluginListCommand() {
|
|
|
24335
25575
|
|
|
24336
25576
|
// src/index.ts
|
|
24337
25577
|
var program2 = new Command();
|
|
24338
|
-
program2.name("nex").description("Nexrall Code \u2014 AI coding assistant (powered by Nexrall)").version("0.5.
|
|
25578
|
+
program2.name("nex").description("Nexrall Code \u2014 AI coding assistant (powered by Nexrall)").version("0.5.17");
|
|
24339
25579
|
program2.command("auth").description("Login to your Nexrall account").action(authCommand);
|
|
24340
25580
|
program2.command("logout").description("Log out of your Nexrall account").action(logoutCommand);
|
|
24341
25581
|
program2.command("update").description("Update nex to the latest version").option("-c, --check", "Check for updates without installing").option("-y, --yes", "Skip confirmation prompt").action(async (opts) => {
|