nexrall-code 0.5.203 → 0.5.204

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.
Files changed (2) hide show
  1. package/dist/index.js +366 -19
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3364,6 +3364,109 @@ var require_auth = __commonJS({
3364
3364
  }
3365
3365
  });
3366
3366
 
3367
+ // ../core/dist/auth/device.js
3368
+ var require_device = __commonJS({
3369
+ "../core/dist/auth/device.js"(exports) {
3370
+ "use strict";
3371
+ var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m2, k, k2) {
3372
+ if (k2 === void 0)
3373
+ k2 = k;
3374
+ var desc = Object.getOwnPropertyDescriptor(m2, k);
3375
+ if (!desc || ("get" in desc ? !m2.__esModule : desc.writable || desc.configurable)) {
3376
+ desc = { enumerable: true, get: function() {
3377
+ return m2[k];
3378
+ } };
3379
+ }
3380
+ Object.defineProperty(o, k2, desc);
3381
+ } : function(o, m2, k, k2) {
3382
+ if (k2 === void 0)
3383
+ k2 = k;
3384
+ o[k2] = m2[k];
3385
+ });
3386
+ var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? function(o, v) {
3387
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
3388
+ } : function(o, v) {
3389
+ o["default"] = v;
3390
+ });
3391
+ var __importStar = exports && exports.__importStar || /* @__PURE__ */ function() {
3392
+ var ownKeys = function(o) {
3393
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
3394
+ var ar = [];
3395
+ for (var k in o2)
3396
+ if (Object.prototype.hasOwnProperty.call(o2, k))
3397
+ ar[ar.length] = k;
3398
+ return ar;
3399
+ };
3400
+ return ownKeys(o);
3401
+ };
3402
+ return function(mod) {
3403
+ if (mod && mod.__esModule)
3404
+ return mod;
3405
+ var result = {};
3406
+ if (mod != null) {
3407
+ for (var k = ownKeys(mod), i2 = 0; i2 < k.length; i2++)
3408
+ if (k[i2] !== "default")
3409
+ __createBinding(result, mod, k[i2]);
3410
+ }
3411
+ __setModuleDefault(result, mod);
3412
+ return result;
3413
+ };
3414
+ }();
3415
+ Object.defineProperty(exports, "__esModule", { value: true });
3416
+ exports.setDeviceIdSource = setDeviceIdSource;
3417
+ exports.getDeviceId = getDeviceId;
3418
+ exports._resetDeviceIdForTests = _resetDeviceIdForTests;
3419
+ var crypto6 = __importStar(__require("crypto"));
3420
+ var fs33 = __importStar(__require("fs"));
3421
+ var os25 = __importStar(__require("os"));
3422
+ var path36 = __importStar(__require("path"));
3423
+ var DEVICE_DIR = path36.join(os25.homedir(), ".nexrall");
3424
+ var DEVICE_FILE = path36.join(DEVICE_DIR, "device-id");
3425
+ var injectedSource = null;
3426
+ var cached = null;
3427
+ function setDeviceIdSource(value) {
3428
+ const trimmed = typeof value === "string" ? value.trim() : "";
3429
+ injectedSource = trimmed || null;
3430
+ cached = null;
3431
+ }
3432
+ function readOrCreateMachineValue() {
3433
+ const env3 = process.env.NEXRALL_DEVICE_ID;
3434
+ if (env3 && env3.trim())
3435
+ return env3.trim();
3436
+ try {
3437
+ if (fs33.existsSync(DEVICE_FILE)) {
3438
+ const existing = fs33.readFileSync(DEVICE_FILE, "utf-8").trim();
3439
+ if (existing)
3440
+ return existing;
3441
+ }
3442
+ const fresh = crypto6.randomBytes(24).toString("hex");
3443
+ fs33.mkdirSync(DEVICE_DIR, { recursive: true });
3444
+ fs33.writeFileSync(DEVICE_FILE, fresh + "\n", { mode: 384 });
3445
+ return fresh;
3446
+ } catch {
3447
+ return null;
3448
+ }
3449
+ }
3450
+ function getDeviceId() {
3451
+ if (cached)
3452
+ return cached;
3453
+ try {
3454
+ const source2 = injectedSource ?? readOrCreateMachineValue();
3455
+ if (!source2)
3456
+ return null;
3457
+ cached = crypto6.createHash("sha256").update(source2).digest("hex");
3458
+ return cached;
3459
+ } catch {
3460
+ return null;
3461
+ }
3462
+ }
3463
+ function _resetDeviceIdForTests() {
3464
+ injectedSource = null;
3465
+ cached = null;
3466
+ }
3467
+ }
3468
+ });
3469
+
3367
3470
  // ../../node_modules/.pnpm/eventsource-parser@1.1.2/node_modules/eventsource-parser/dist/index.cjs
3368
3471
  var require_dist = __commonJS({
3369
3472
  "../../node_modules/.pnpm/eventsource-parser@1.1.2/node_modules/eventsource-parser/dist/index.cjs"(exports) {
@@ -10029,7 +10132,12 @@ var require_client = __commonJS({
10029
10132
  var crypto_1 = __require("crypto");
10030
10133
  var zlib_1 = __require("zlib");
10031
10134
  var index_1 = require_auth();
10135
+ var device_1 = require_device();
10032
10136
  var DEFAULT_API_BASE = "https://api.nexrall.com";
10137
+ function deviceHeader() {
10138
+ const device = (0, device_1.getDeviceId)();
10139
+ return device ? { "X-Device-Id": device } : {};
10140
+ }
10033
10141
  function resolveApiBase() {
10034
10142
  const raw = process.env.NEXRALL_API_BASE?.trim();
10035
10143
  if (!raw)
@@ -10067,10 +10175,15 @@ var require_client = __commonJS({
10067
10175
  if (!token) {
10068
10176
  throw new Error("Not authenticated. Run `nexrall-code login` first.");
10069
10177
  }
10070
- return {
10178
+ const headers = {
10071
10179
  "Content-Type": "application/json",
10072
- Authorization: `Bearer ${token}`
10180
+ Authorization: `Bearer ${token}`,
10181
+ // Device evidence (migration 169): sha256 of this machine's value, sent on
10182
+ // every authenticated call so the backend can cluster sessions by machine.
10183
+ // Omitted — never fabricated — when nothing can be resolved.
10184
+ ...deviceHeader()
10073
10185
  };
10186
+ return headers;
10074
10187
  }
10075
10188
  var _refreshInFlight = null;
10076
10189
  async function refreshAccessToken3() {
@@ -10083,7 +10196,7 @@ var require_client = __commonJS({
10083
10196
  try {
10084
10197
  const res = await (0, node_fetch_1.default)(`${exports.API_BASE}/api/auth/refresh`, {
10085
10198
  method: "POST",
10086
- headers: { "Content-Type": "application/json" },
10199
+ headers: { "Content-Type": "application/json", ...deviceHeader() },
10087
10200
  body: JSON.stringify({ refreshToken })
10088
10201
  });
10089
10202
  if (!res.ok)
@@ -10862,7 +10975,7 @@ var require_client = __commonJS({
10862
10975
  try {
10863
10976
  await (0, node_fetch_1.default)(`${exports.API_BASE}/api/auth/logout`, {
10864
10977
  method: "POST",
10865
- headers: { "Content-Type": "application/json" },
10978
+ headers: { "Content-Type": "application/json", ...deviceHeader() },
10866
10979
  body: JSON.stringify({ refreshToken })
10867
10980
  });
10868
10981
  } catch {
@@ -11010,7 +11123,7 @@ var require_client = __commonJS({
11010
11123
  async function exchangeVscodeCode(code) {
11011
11124
  const response = await (0, node_fetch_1.default)(`${exports.API_BASE}/api/auth/vscode-exchange`, {
11012
11125
  method: "POST",
11013
- headers: { "Content-Type": "application/json" },
11126
+ headers: { "Content-Type": "application/json", ...deviceHeader() },
11014
11127
  body: JSON.stringify({ code })
11015
11128
  });
11016
11129
  if (!response.ok) {
@@ -11029,7 +11142,7 @@ var require_client = __commonJS({
11029
11142
  async function login2(email, password) {
11030
11143
  const response = await (0, node_fetch_1.default)(`${exports.API_BASE}/api/auth/login/email`, {
11031
11144
  method: "POST",
11032
- headers: { "Content-Type": "application/json" },
11145
+ headers: { "Content-Type": "application/json", ...deviceHeader() },
11033
11146
  body: JSON.stringify({ email, password })
11034
11147
  });
11035
11148
  if (!response.ok) {
@@ -12781,10 +12894,11 @@ var require_planMode = __commonJS({
12781
12894
  "../core/dist/agent/planMode.js"(exports) {
12782
12895
  "use strict";
12783
12896
  Object.defineProperty(exports, "__esModule", { value: true });
12784
- exports.PLAN_MODE_INSTRUCTIONS = void 0;
12897
+ exports.PLAN_MODE_INSTRUCTIONS = exports.PLAN_PROPOSAL_MAX_CHARS = exports.PLAN_PROPOSAL_MIN_CHARS = exports.EXIT_PLAN_MODE_TOOL_SCHEMA = exports.EXIT_PLAN_MODE_TOOL_NAME = void 0;
12785
12898
  exports.isReadOnlyCommand = isReadOnlyCommand;
12786
12899
  exports.isReadOnlyPipeline = isReadOnlyPipeline;
12787
12900
  exports.checkPlanMode = checkPlanMode;
12901
+ exports.parsePlanProposal = parsePlanProposal;
12788
12902
  var READ_ONLY_TOOLS3 = /* @__PURE__ */ new Set([
12789
12903
  "read_file",
12790
12904
  "search_files",
@@ -12824,7 +12938,13 @@ var require_planMode = __commonJS({
12824
12938
  "get_diagnostics",
12825
12939
  // Reading output from an ALREADY-running background shell. Starting one is
12826
12940
  // gated with the rest of bash below; draining a buffer is not a new effect.
12827
- "bash_output"
12941
+ "bash_output",
12942
+ // Talking to the USER is not a side effect on the repo. Plan mode used to refuse
12943
+ // these, so a model facing an ambiguous request could neither ask nor stop — it
12944
+ // guessed and wrote the plan in one go (user report). ask_user_question is for
12945
+ // clarifying requirements; exit_plan_mode is how the finished plan is presented.
12946
+ "ask_user_question",
12947
+ "exit_plan_mode"
12828
12948
  ]);
12829
12949
  var READ_ONLY_BASH = /* @__PURE__ */ new Set([
12830
12950
  "ls",
@@ -13168,27 +13288,54 @@ var require_planMode = __commonJS({
13168
13288
  message: `Plan mode is ON, so \`${tool}\` is unavailable \u2014 this session may not modify anything yet. Do NOT ask for approval: no answer the user gives at this prompt can unlock it, because it is a session-wide lock they control directly. Research with read-only tools and finish by proposing a concrete plan (files to change, in order, and how to verify). The user will exit plan mode to let you carry it out.`
13169
13289
  };
13170
13290
  }
13291
+ exports.EXIT_PLAN_MODE_TOOL_NAME = "exit_plan_mode";
13292
+ exports.EXIT_PLAN_MODE_TOOL_SCHEMA = {
13293
+ name: exports.EXIT_PLAN_MODE_TOOL_NAME,
13294
+ description: "Present your finished plan to the user for approval and END the turn. Call this once, when research is done and any open questions are answered (use ask_user_question for those first). `plan` is the full markdown plan: findings with file:line, the changes file by file in order, risks, and the exact verify command. The user will approve, adjust or reject it; you cannot continue after calling this. Do not use it to ask a question.",
13295
+ input_schema: {
13296
+ type: "object",
13297
+ properties: {
13298
+ plan: { type: "string", description: "The complete plan, in markdown." }
13299
+ },
13300
+ required: ["plan"]
13301
+ }
13302
+ };
13303
+ exports.PLAN_PROPOSAL_MIN_CHARS = 20;
13304
+ exports.PLAN_PROPOSAL_MAX_CHARS = 12e3;
13305
+ function parsePlanProposal(input) {
13306
+ const plan = typeof input.plan === "string" ? input.plan.trim() : "";
13307
+ if (plan.length < exports.PLAN_PROPOSAL_MIN_CHARS)
13308
+ return { error: "exit_plan_mode needs the full plan in `plan` (an empty or one-word plan cannot be approved). Write it out and call again." };
13309
+ return { plan: plan.slice(0, exports.PLAN_PROPOSAL_MAX_CHARS) };
13310
+ }
13171
13311
  exports.PLAN_MODE_INSTRUCTIONS = [
13172
13312
  "# PLAN MODE IS ACTIVE",
13173
13313
  "",
13174
13314
  "This session is READ-ONLY. You cannot edit files, write files, run mutating commands, or start",
13175
- "background processes. This is enforced outside your control \u2014 it is not a permission prompt, and",
13176
- "the user cannot approve an exception from inside this turn.",
13315
+ "background processes. This is enforced outside your control \u2014 it is not a permission prompt.",
13177
13316
  "",
13178
13317
  "Therefore:",
13179
- '- NEVER ask "shall I go ahead and make this change?" \u2014 you cannot, whatever the answer.',
13180
13318
  '- NEVER attempt a mutating tool "just to see" \u2014 it will be refused and wastes the turn.',
13181
13319
  "- NEVER try to reach a blocked command another way (a different flag, a script, an interpreter).",
13320
+ '- NEVER ask "shall I go ahead?" in prose \u2014 approval is requested by calling `exit_plan_mode`.',
13182
13321
  "",
13183
- "Your job is to research thoroughly, then deliver a plan:",
13184
- "1. What you found \u2014 the specific files, functions and line numbers that matter.",
13185
- "2. What you propose to change \u2014 file by file, in dependency order.",
13186
- "3. Risks and unknowns \u2014 what could break, what you could not verify, what you assumed.",
13187
- "4. How it will be verified \u2014 the exact build/test/lint command that proves it works.",
13322
+ "How to work:",
13323
+ "1. Research with read-only tools.",
13324
+ "2. If the request is ambiguous or there is a real fork between approaches that only the user can",
13325
+ " decide, call `ask_user_question` (2-4 options each) BEFORE planning \u2014 do not guess. Skip it",
13326
+ " for anything you can find out yourself.",
13327
+ "3. When ready, call `exit_plan_mode` with the plan. That ends your turn and shows the user an",
13328
+ " approval prompt. Do not write the plan only as chat text and stop.",
13329
+ "",
13330
+ "The plan must contain:",
13331
+ "- What you found \u2014 the specific files, functions and line numbers that matter.",
13332
+ "- What you propose to change \u2014 file by file, in dependency order.",
13333
+ "- Risks and unknowns \u2014 what could break, what you could not verify, what you assumed.",
13334
+ "- How it will be verified \u2014 the exact build/test/lint command that proves it works.",
13188
13335
  "",
13189
13336
  'Be concrete. "Update the auth logic" is not a plan; "add a `refreshToken` field to AuthConfig in',
13190
- 'auth/index.ts:42, then thread it through client.ts:118" is. The user will review the plan and',
13191
- "leave plan mode to have it carried out."
13337
+ 'auth/index.ts:42, then thread it through client.ts:118" is. If the user only asked a question,',
13338
+ "answer it directly and do not call `exit_plan_mode`."
13192
13339
  ].join("\n");
13193
13340
  }
13194
13341
  });
@@ -13412,7 +13559,12 @@ var require_modePolicy = __commonJS({
13412
13559
  "get_symbols",
13413
13560
  "get_workspace_symbols",
13414
13561
  "get_hover",
13415
- "task"
13562
+ "task",
13563
+ // Asking the user / presenting a plan touch no files. Without these, `ask` and
13564
+ // `edit` modes fell through to the final `return 'ask'` (a permission prompt before
13565
+ // the question itself) and plan mode denied them outright.
13566
+ "ask_user_question",
13567
+ "exit_plan_mode"
13416
13568
  ]);
13417
13569
  exports.FILE_WRITE_TOOLS = /* @__PURE__ */ new Set([
13418
13570
  "write_file",
@@ -25327,6 +25479,7 @@ var require_iterationPolicy = __commonJS({
25327
25479
  case "clean":
25328
25480
  case "aborted":
25329
25481
  case "reported-elsewhere":
25482
+ case "plan-proposed":
25330
25483
  return null;
25331
25484
  case "empty-response":
25332
25485
  return `
@@ -27277,6 +27430,10 @@ ${ctx}
27277
27430
  extraTools: [
27278
27431
  ...(options.mcpManager?.getAnthropicTools() ?? []).filter((t2) => !options._mcpServerAllowlist || options._mcpServerAllowlist.has(String(t2.name).split("__")[0])),
27279
27432
  ...options._agentMemory ? [iterationPolicy_1.AGENT_MEMORY_TOOL_SCHEMA] : [],
27433
+ // Main agent in plan mode only, and only for a client that HAS an approval gate
27434
+ // (onPlanProposed): sub-agents report to their parent, and a headless run or a
27435
+ // background fork has nobody to approve.
27436
+ ...options.planMode && depth === 0 && options.onPlanProposed ? [planMode_1.EXIT_PLAN_MODE_TOOL_SCHEMA] : [],
27280
27437
  ...options.extraToolSchemas ?? []
27281
27438
  ],
27282
27439
  // Derived from the run's ACTUAL allowlist rather than asserted separately,
@@ -27361,6 +27518,7 @@ ${extra}` : extra;
27361
27518
  let compacting = false;
27362
27519
  const hardCap = autoContinue ? Math.max(maxIterations, iterationPolicy_1.MAX_ITERATIONS_CEILING) : maxIterations;
27363
27520
  let stopReason = "unknown";
27521
+ let planProposed = false;
27364
27522
  let completedRounds = 0;
27365
27523
  let consecutiveErrorRounds = 0;
27366
27524
  let repeatedErrorRounds = 0;
@@ -27724,6 +27882,20 @@ ${extra}` : extra;
27724
27882
  options.onToolResult(name, result, void 0, toolMeta);
27725
27883
  return { block: { ...block, id }, result };
27726
27884
  }
27885
+ if (options.planMode && depth === 0 && name === planMode_1.EXIT_PLAN_MODE_TOOL_NAME) {
27886
+ const parsed = (0, planMode_1.parsePlanProposal)(input);
27887
+ if ("error" in parsed)
27888
+ result = { error: parsed.error };
27889
+ else {
27890
+ planProposed = true;
27891
+ options.onPlanProposed?.(parsed.plan);
27892
+ result = {
27893
+ output: "Plan presented to the user for approval. This turn is over \u2014 do not write anything further; their decision arrives as the next message."
27894
+ };
27895
+ }
27896
+ options.onToolResult(name, result, void 0, toolMeta);
27897
+ return { block: { ...block, id }, result };
27898
+ }
27727
27899
  if (options.planMode) {
27728
27900
  const refusal = (0, planMode_1.checkPlanMode)(name, input);
27729
27901
  if (refusal) {
@@ -28050,6 +28222,10 @@ ${r2.output}` : `Error: ${r2.error}` : r2.output ?? ""
28050
28222
  messages.push(toolResultMessage);
28051
28223
  completedRounds++;
28052
28224
  options.onProgress?.(messages);
28225
+ if (planProposed) {
28226
+ stopReason = "plan-proposed";
28227
+ break;
28228
+ }
28053
28229
  if (postBatchStop) {
28054
28230
  stopReason = "hook-blocked";
28055
28231
  hookBlockedReason = postBatchStop;
@@ -30721,6 +30897,100 @@ var require_effortCap = __commonJS({
30721
30897
  }
30722
30898
  });
30723
30899
 
30900
+ // ../core/dist/agent/askUser.js
30901
+ var require_askUser = __commonJS({
30902
+ "../core/dist/agent/askUser.js"(exports) {
30903
+ "use strict";
30904
+ Object.defineProperty(exports, "__esModule", { value: true });
30905
+ exports.ASK_USER_TOOL_SCHEMA = exports.ASK_USER_TOOL_NAME = void 0;
30906
+ exports.parseAskUserQuestions = parseAskUserQuestions;
30907
+ exports.formatAskUserResult = formatAskUserResult;
30908
+ exports.ASK_USER_TOOL_NAME = "ask_user_question";
30909
+ exports.ASK_USER_TOOL_SCHEMA = {
30910
+ name: exports.ASK_USER_TOOL_NAME,
30911
+ description: "Ask the user 1-4 multiple-choice questions when a decision is theirs to make (ambiguous requirements, a fork between approaches). Each question has 2-4 options; the user can also type their own answer. Do not use it for things you can find out yourself, or for plan approval.",
30912
+ input_schema: {
30913
+ type: "object",
30914
+ properties: {
30915
+ questions: {
30916
+ type: "array",
30917
+ minItems: 1,
30918
+ maxItems: 4,
30919
+ items: {
30920
+ type: "object",
30921
+ properties: {
30922
+ question: { type: "string", description: "The full question, ending with a question mark." },
30923
+ header: { type: "string", description: "Very short label (max ~12 chars)." },
30924
+ options: {
30925
+ type: "array",
30926
+ minItems: 2,
30927
+ maxItems: 4,
30928
+ items: {
30929
+ type: "object",
30930
+ properties: { label: { type: "string" }, description: { type: "string" } },
30931
+ required: ["label"]
30932
+ }
30933
+ }
30934
+ },
30935
+ required: ["question", "options"]
30936
+ }
30937
+ }
30938
+ },
30939
+ required: ["questions"]
30940
+ }
30941
+ };
30942
+ function parseAskUserQuestions(input) {
30943
+ const raw = input.questions;
30944
+ if (!Array.isArray(raw) || raw.length < 1 || raw.length > 4)
30945
+ return "questions must be an array of 1-4 items";
30946
+ const out = [];
30947
+ for (const q of raw) {
30948
+ const o = q;
30949
+ const question = typeof o?.question === "string" ? o.question.trim() : "";
30950
+ const opts = Array.isArray(o?.options) ? o.options : [];
30951
+ if (!question)
30952
+ return "every question needs non-empty text";
30953
+ if (opts.length < 2 || opts.length > 4)
30954
+ return `"${question}" needs 2-4 options`;
30955
+ const options = [];
30956
+ for (const x2 of opts) {
30957
+ const label = typeof x2?.label === "string" ? x2.label.trim() : "";
30958
+ if (!label)
30959
+ return `"${question}" has an option without a label`;
30960
+ const d = x2.description;
30961
+ options.push({ label, description: typeof d === "string" && d.trim() ? d.trim() : void 0 });
30962
+ }
30963
+ out.push({ question, header: typeof o.header === "string" ? o.header.trim().slice(0, 14) : void 0, options });
30964
+ }
30965
+ return out;
30966
+ }
30967
+ function formatAskUserResult(qs, answers) {
30968
+ const dismissed = {
30969
+ error: "The user dismissed the question without answering. Do not assume an answer; proceed on your best judgement or stop and say what you need."
30970
+ };
30971
+ if (!answers || answers.length !== qs.length)
30972
+ return dismissed;
30973
+ const parts = [];
30974
+ for (let i2 = 0; i2 < qs.length; i2++) {
30975
+ const a = answers[i2];
30976
+ const q = qs[i2];
30977
+ if ("text" in a) {
30978
+ const t2 = String(a.text ?? "").trim();
30979
+ if (!t2)
30980
+ return dismissed;
30981
+ parts.push(`"${q.question}" = "${t2}" (user's own answer)`);
30982
+ } else {
30983
+ const opt = q.options[a.index];
30984
+ if (!opt)
30985
+ return dismissed;
30986
+ parts.push(`"${q.question}" = "${opt.label}"`);
30987
+ }
30988
+ }
30989
+ return { output: "User answered: " + parts.join("; ") };
30990
+ }
30991
+ }
30992
+ });
30993
+
30724
30994
  // ../core/dist/permissions/autoMode.js
30725
30995
  var require_autoMode = __commonJS({
30726
30996
  "../core/dist/permissions/autoMode.js"(exports) {
@@ -47845,6 +48115,7 @@ var require_dist2 = __commonJS({
47845
48115
  __exportStar(require_safeSlice(), exports);
47846
48116
  __exportStar(require_cellWidth(), exports);
47847
48117
  __exportStar(require_auth(), exports);
48118
+ __exportStar(require_device(), exports);
47848
48119
  __exportStar(require_client(), exports);
47849
48120
  __exportStar(require_executor(), exports);
47850
48121
  __exportStar(require_loop(), exports);
@@ -47872,6 +48143,7 @@ var require_dist2 = __commonJS({
47872
48143
  __exportStar(require_agentTypes(), exports);
47873
48144
  __exportStar(require_effortCap(), exports);
47874
48145
  __exportStar(require_planMode(), exports);
48146
+ __exportStar(require_askUser(), exports);
47875
48147
  __exportStar(require_agentRegistry(), exports);
47876
48148
  __exportStar(require_backgroundAgents(), exports);
47877
48149
  __exportStar(require_rules(), exports);
@@ -104573,6 +104845,7 @@ function nextBackgroundChange() {
104573
104845
  }
104574
104846
  var _mcpManager = null;
104575
104847
  var _askIO = null;
104848
+ var _proposedPlan = null;
104576
104849
  var _elicitationHandler = null;
104577
104850
  var _mcpConfig = null;
104578
104851
  var sessionTouched = [];
@@ -105091,6 +105364,9 @@ async function runTurn(messages, modelAlias, workDir, abortSignal, env3, nexrall
105091
105364
  // makes it real: the loop refuses mutating tools before the permission
105092
105365
  // prompt, and sub-agents inherit the lock.
105093
105366
  planMode: mode === "plan",
105367
+ onPlanProposed: (plan) => {
105368
+ _proposedPlan = plan;
105369
+ },
105094
105370
  // Same "enforced, not just hinted" reasoning as planMode directly above:
105095
105371
  // when set, core's checkWorktreeIsolation refuses any write/bash-cwd/git-
105096
105372
  // redirect that targets outside this worktree, BEFORE the permission
@@ -110880,6 +111156,8 @@ ${text}
110880
111156
  agentRunning = false;
110881
111157
  commitCheckpoint();
110882
111158
  }
111159
+ if (_proposedPlan !== null && !abortSignal.aborted)
111160
+ await resolveProposedPlan();
110883
111161
  if (activeGoal && !abortSignal.aborted) {
110884
111162
  rl.pause();
110885
111163
  await runGoalLoop();
@@ -110890,6 +111168,75 @@ ${text}
110890
111168
  deliverHookWakeWhenIdle();
110891
111169
  void maybeSuggestNextPrompt(turnOk && !abortSignal.aborted);
110892
111170
  };
111171
+ async function resolveProposedPlan() {
111172
+ while (_proposedPlan !== null) {
111173
+ const plan = _proposedPlan;
111174
+ _proposedPlan = null;
111175
+ console.log();
111176
+ console.log(paint.bold(" Proposed plan"));
111177
+ for (const l of plan.split("\n"))
111178
+ console.log(" " + l);
111179
+ console.log();
111180
+ if (modeLocked || !interactive || !(rl instanceof InkReadlineAdapter))
111181
+ return;
111182
+ const pick = await rl.askChoice({
111183
+ title: "Ready to implement this plan?",
111184
+ detail: "plan mode is read-only \xB7 approving leaves it",
111185
+ options: [
111186
+ { key: "1", label: "Yes \u2014 auto-accept edits (edit mode; still asks before shell commands)" },
111187
+ { key: "2", label: "Yes \u2014 ask before each edit (ask mode)" },
111188
+ { key: "3", label: "No \u2014 keep planning (tell me what to change)" }
111189
+ ],
111190
+ risk: "low"
111191
+ });
111192
+ let prompt2;
111193
+ if (pick === 0 || pick === 1) {
111194
+ agentMode = pick === 0 ? "edit" : "ask";
111195
+ updateFooter();
111196
+ console.log(paint.green(` Mode \u2192 ${paint.bold(agentMode)}`));
111197
+ prompt2 = "The user approved this plan. Implement it now, following the steps in order and running the verification at the end.\n\n" + plan;
111198
+ } else {
111199
+ const fb = (await new Promise((res) => rl.question(" What should change? (Enter to skip) ", res))).trim();
111200
+ if (!fb)
111201
+ return;
111202
+ prompt2 = fb;
111203
+ }
111204
+ messages.push({ role: "user", content: [{ type: "text", text: prompt2 }] });
111205
+ rl.pause();
111206
+ abortSignal.aborted = false;
111207
+ agentRunning = true;
111208
+ try {
111209
+ const result = await runTurn(
111210
+ messages,
111211
+ modelAlias,
111212
+ workDir,
111213
+ abortSignal,
111214
+ env3,
111215
+ nexrallMd,
111216
+ agentMode,
111217
+ effortLevel,
111218
+ checkpoints,
111219
+ saveProgress,
111220
+ takePendingInput,
111221
+ worktreeState,
111222
+ peerCtx
111223
+ );
111224
+ messages = result.messages;
111225
+ updateTitle();
111226
+ saveSession2(sessionId, sessionTitle, workDir, messages);
111227
+ } catch (err) {
111228
+ messages = recoverTurn(err, messages);
111229
+ updateTitle();
111230
+ saveSession2(sessionId, sessionTitle || "Untitled", workDir, messages);
111231
+ _proposedPlan = null;
111232
+ } finally {
111233
+ agentRunning = false;
111234
+ commitCheckpoint();
111235
+ }
111236
+ if (abortSignal.aborted)
111237
+ _proposedPlan = null;
111238
+ }
111239
+ }
110893
111240
  rl.on("line", (rawLine) => {
110894
111241
  void handleLine(rawLine);
110895
111242
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexrall-code",
3
- "version": "0.5.203",
3
+ "version": "0.5.204",
4
4
  "description": "Nexrall Code \u2014 AI coding assistant for your terminal (headless agent for scripts, CI and automation)",
5
5
  "keywords": [
6
6
  "ai",