autopilot-ai 1.11.0 → 1.12.1

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 CHANGED
@@ -20836,6 +20836,39 @@ var init_help_menu = __esm({
20836
20836
  }
20837
20837
  });
20838
20838
 
20839
+ // src/agent/queue-batch.ts
20840
+ function createQueueBatch(items) {
20841
+ if (items.length < 2) return null;
20842
+ if (items.some((item) => isCommand(item.full))) return null;
20843
+ const prompts = items.flatMap((item) => item.batchPrompts ?? [item.full.trim()]);
20844
+ const sourceKeys = items.flatMap((item) => item.sourceKeys ?? [item.key]);
20845
+ const numberedPrompts = prompts.map((prompt, index) => `${index + 1}. ${prompt}`).join("\n\n");
20846
+ const full = [
20847
+ `The user chose to group ${prompts.length} queued follow-ups into one coordinated task.`,
20848
+ "First identify dependencies and an efficient order. Treat every follow-up as a requirement. Delegate bounded, independent research to subagents when it materially helps; worker calls still require the normal user permission. Keep dependent changes coordinated in this session, and do not have multiple workers edit overlapping files.",
20849
+ "",
20850
+ "Queued follow-ups:",
20851
+ numberedPrompts
20852
+ ].join("\n");
20853
+ const display = `Coordinate ${prompts.length} queued follow-ups as one task. Use subagents to research independent parts where useful; keep dependent changes coordinated.`;
20854
+ return {
20855
+ full,
20856
+ display,
20857
+ key: sourceKeys[0],
20858
+ batchPrompts: prompts,
20859
+ sourceKeys
20860
+ };
20861
+ }
20862
+ function isCommand(prompt) {
20863
+ const trimmed = prompt.trimStart();
20864
+ return trimmed.startsWith("/") || trimmed.startsWith("!");
20865
+ }
20866
+ var init_queue_batch = __esm({
20867
+ "src/agent/queue-batch.ts"() {
20868
+ "use strict";
20869
+ }
20870
+ });
20871
+
20839
20872
  // src/ui/app-helpers.ts
20840
20873
  var app_helpers_exports = {};
20841
20874
  __export(app_helpers_exports, {
@@ -20858,12 +20891,50 @@ __export(app_helpers_exports, {
20858
20891
  mkKey: () => mkKey,
20859
20892
  openBrowser: () => openBrowser,
20860
20893
  rebuildSystemPromptForMode: () => rebuildSystemPromptForMode,
20894
+ replaceQueuedPrompt: () => replaceQueuedPrompt,
20895
+ replaceUserPromptEvent: () => replaceUserPromptEvent,
20861
20896
  trackRecentFile: () => trackRecentFile
20862
20897
  });
20863
20898
  import { execSync as execSync3, spawn as spawn6 } from "child_process";
20864
20899
  import { existsSync as existsSync5, readFileSync as readFileSync5, statSync as statSync5 } from "fs";
20865
20900
  import { join as join32 } from "path";
20866
20901
  import { platform as platform3 } from "os";
20902
+ function replaceQueuedPrompt(queue2, key, full, display) {
20903
+ const index = queue2.findIndex((item) => item.key === key);
20904
+ if (index >= 0) {
20905
+ const next2 = [...queue2];
20906
+ next2[index] = { ...next2[index], full, display };
20907
+ return next2;
20908
+ }
20909
+ const groupedIndex = queue2.findIndex((item) => item.sourceKeys?.includes(key));
20910
+ if (groupedIndex < 0) return null;
20911
+ const grouped = queue2[groupedIndex];
20912
+ if (!grouped.sourceKeys || !grouped.batchPrompts) return null;
20913
+ const promptIndex = grouped.sourceKeys.indexOf(key);
20914
+ const replacement = createQueueBatch(
20915
+ grouped.batchPrompts.map((prompt, index2) => ({
20916
+ key: grouped.sourceKeys[index2],
20917
+ full: index2 === promptIndex ? full : prompt,
20918
+ display: index2 === promptIndex ? display : prompt
20919
+ }))
20920
+ );
20921
+ if (!replacement) return null;
20922
+ const next = [...queue2];
20923
+ next[groupedIndex] = replacement;
20924
+ return next;
20925
+ }
20926
+ function replaceUserPromptEvent(events, key, text, discardTurnOutput = false) {
20927
+ const index = events.findIndex((event2) => event2.kind === "user" && event2.key === key);
20928
+ if (index < 0) return events;
20929
+ const event = events[index];
20930
+ if (event.kind !== "user") return events;
20931
+ const replacement = { ...event, text, images: void 0 };
20932
+ if (!discardTurnOutput) {
20933
+ return events.map((item, i) => i === index ? replacement : item);
20934
+ }
20935
+ const queuedFollowUps = events.slice(index + 1).filter((item) => item.kind === "user" && item.queued);
20936
+ return [...events.slice(0, index), replacement, ...queuedFollowUps];
20937
+ }
20867
20938
  function buildFilePickerIgnoreList(cwd) {
20868
20939
  const hardcoded = [
20869
20940
  // Dependencies
@@ -21095,6 +21166,7 @@ var init_app_helpers = __esm({
21095
21166
  init_custom_endpoint();
21096
21167
  init_system_prompt();
21097
21168
  init_image();
21169
+ init_queue_batch();
21098
21170
  MAX_GITIGNORE_SIZE = 1 * 1024 * 1024;
21099
21171
  CONTEXT_LIMIT = 262e3;
21100
21172
  AUTO_COMPACT_THRESHOLD = 0.8;
@@ -23944,6 +24016,67 @@ var init_hooks_wizard = __esm({
23944
24016
  }
23945
24017
  });
23946
24018
 
24019
+ // src/ui/activity.ts
24020
+ function activityFromJob(job) {
24021
+ const active = job.status === "starting" || job.status === "running" || job.status === "cancelling";
24022
+ const status = active ? "running" : job.status === "completed" ? "done" : job.status === "cancelled" ? "stopped" : "failed";
24023
+ const summary = job.status === "cancelling" ? "Stopping\u2026" : job.status === "timed_out" ? "Timed out" : job.status === "unknown" ? "Process state could not be verified" : job.exitCode !== null && job.exitCode !== 0 ? `Exited with code ${job.exitCode}` : void 0;
24024
+ return {
24025
+ id: job.id,
24026
+ kind: "job",
24027
+ title: job.command,
24028
+ status,
24029
+ stoppable: job.status === "starting" || job.status === "running",
24030
+ ...summary ? { summary } : {},
24031
+ started_at_ms: job.createdAt,
24032
+ ...job.finishedAt !== null ? { updated_at_ms: job.finishedAt } : {}
24033
+ };
24034
+ }
24035
+ function activityFromWorker(worker) {
24036
+ const status = worker.status === "pending" ? "waiting" : worker.status === "running" ? "running" : worker.status === "completed" ? "done" : worker.status === "budget_exhausted" ? "needs_attention" : "failed";
24037
+ const steps = worker.steps?.map((step) => ({
24038
+ title: step.label,
24039
+ status: step.status === "active" ? "running" : step.status === "completed" ? "done" : step.status === "failed" ? "failed" : "pending"
24040
+ }));
24041
+ const doneCount = steps?.filter((step) => step.status === "done" || step.status === "failed").length ?? 0;
24042
+ const summary = worker.error ?? worker.steps?.find((step) => step.status === "active")?.label ?? (worker.status === "budget_exhausted" ? "Budget exhausted; inspect partial results" : void 0);
24043
+ return {
24044
+ id: `agent:${worker.id}`,
24045
+ kind: "agent",
24046
+ title: `${worker.mode}: ${worker.task}`,
24047
+ status,
24048
+ // A single worker cannot currently be cancelled independently; the only
24049
+ // available AbortSignal cancels the entire multi-agent batch.
24050
+ stoppable: false,
24051
+ ...summary ? { summary } : {},
24052
+ ...steps && steps.length > 0 ? {
24053
+ progress: worker.status === "completed" ? 1 : doneCount / steps.length,
24054
+ steps
24055
+ } : {},
24056
+ started_at_ms: worker.startedAt
24057
+ };
24058
+ }
24059
+ function diffActivityItems(previous, current, snapshotSent) {
24060
+ if (!snapshotSent) return [{ type: "ActivitySnapshot", payload: { items: [...current] } }];
24061
+ const next = new Map(current.map((item) => [item.id, item]));
24062
+ const events = [];
24063
+ for (const [id, item] of next) {
24064
+ if (previous.get(id) !== JSON.stringify(item)) events.push({ type: "ActivityUpdate", payload: item });
24065
+ }
24066
+ for (const id of previous.keys()) {
24067
+ if (!next.has(id)) events.push({ type: "ActivityRemoved", payload: { id } });
24068
+ }
24069
+ return events;
24070
+ }
24071
+ function isActiveJob(job) {
24072
+ return job.status === "starting" || job.status === "running" || job.status === "cancelling";
24073
+ }
24074
+ var init_activity = __esm({
24075
+ "src/ui/activity.ts"() {
24076
+ "use strict";
24077
+ }
24078
+ });
24079
+
23947
24080
  // src/camouflage-view.ts
23948
24081
  var camouflage_view_exports = {};
23949
24082
  __export(camouflage_view_exports, {
@@ -23952,6 +24085,9 @@ __export(camouflage_view_exports, {
23952
24085
  listMentionDir: () => listMentionDir,
23953
24086
  modelOptions: () => modelOptions,
23954
24087
  runCamouflageView: () => runCamouflageView,
24088
+ shouldSendSubagentPolicyNotice: () => shouldSendSubagentPolicyNotice,
24089
+ splitBangResult: () => splitBangResult,
24090
+ syncSlashCommands: () => syncSlashCommands,
23955
24091
  toolRow: () => toolRow,
23956
24092
  toolSummary: () => toolSummary,
23957
24093
  validateCommandName: () => validateCommandName
@@ -24005,6 +24141,7 @@ async function runCamouflageView(opts2) {
24005
24141
  resume: () => sdk.resumeTerminal?.(cam)
24006
24142
  });
24007
24143
  cam.on("userInput", (text) => {
24144
+ if (view.routeActivityCommand(text)) return;
24008
24145
  view.echoed.push(text.trim());
24009
24146
  view.actions?.submit(text);
24010
24147
  });
@@ -24020,10 +24157,15 @@ async function runCamouflageView(opts2) {
24020
24157
  });
24021
24158
  });
24022
24159
  cam.on("modeChangeRequested", () => view.actions?.cycleMode());
24160
+ cam.on("activityStopRequested", ({ id }) => view.stopActivity(id));
24161
+ cam.on("activityViewChanged", (change) => {
24162
+ view.activityViewChanged(change);
24163
+ });
24023
24164
  let closing = false;
24024
24165
  const finish = async () => {
24025
24166
  if (closing) return;
24026
24167
  closing = true;
24168
+ view.dispose();
24027
24169
  registerTerminalHandoff(null);
24028
24170
  await cam.close().catch(() => void 0);
24029
24171
  restoreConsole();
@@ -24052,6 +24194,38 @@ function runShell(command) {
24052
24194
  child.on("error", (err) => resolve15({ ok: false, output: err.message }));
24053
24195
  });
24054
24196
  }
24197
+ function syncSlashCommands(custom, lastKey, send) {
24198
+ const builtin = [
24199
+ ...BUILTIN_COMMANDS.map((c) => ({ name: c.name, description: c.description, args_hint: c.argHint })),
24200
+ { name: "jobs", description: "Browse background jobs and agents" },
24201
+ { name: "agents", description: "Browse background agents and jobs" }
24202
+ ];
24203
+ const names = new Set(builtin.map((c) => c.name));
24204
+ const commands = [
24205
+ ...builtin,
24206
+ ...custom.filter((c) => !names.has(c.name)).map((c) => ({ name: c.name, description: c.description ? `${c.description} (custom)` : "Custom command" }))
24207
+ ];
24208
+ const key = JSON.stringify(commands);
24209
+ if (key !== lastKey) send(commands);
24210
+ return key;
24211
+ }
24212
+ function shouldSendSubagentPolicyNotice(message2, sent) {
24213
+ if (!message2.startsWith("Subagent policy:") || message2.startsWith("Subagent policy: respecting your ")) return true;
24214
+ if (sent.has(message2)) return false;
24215
+ sent.add(message2);
24216
+ return true;
24217
+ }
24218
+ function splitBangResult(title, content) {
24219
+ if (!title?.startsWith("! ")) return { output: content };
24220
+ const match = /^(exit=\d+|signal=[^\r\n]+|exit=\?)(?:\r?\n)/.exec(content);
24221
+ if (!match) return { output: content };
24222
+ const exit = /^exit=(\d+)$/.exec(match[1]);
24223
+ return {
24224
+ output: content.slice(match[0].length),
24225
+ status: match[1],
24226
+ ...exit ? { exitCode: Number(exit[1]) } : {}
24227
+ };
24228
+ }
24055
24229
  function validateCommandName(name, existing) {
24056
24230
  if (!name) return "a name is required";
24057
24231
  if (!/^[a-zA-Z][a-zA-Z0-9_\-/]*$/.test(name)) return "use letters, numbers, _ - / and start with a letter";
@@ -24243,6 +24417,8 @@ var init_camouflage_view = __esm({
24243
24417
  init_hooks_wizard();
24244
24418
  init_mode();
24245
24419
  init_logger();
24420
+ init_manager();
24421
+ init_activity();
24246
24422
  CamouflageUnavailable = class extends Error {
24247
24423
  };
24248
24424
  View = class {
@@ -24267,7 +24443,19 @@ var init_camouflage_view = __esm({
24267
24443
  todos = "";
24268
24444
  permission = null;
24269
24445
  openPrompt = null;
24270
- commandsKey = "";
24446
+ commandsKey;
24447
+ sentSubagentPolicyNotices = /* @__PURE__ */ new Set();
24448
+ jobs = new JobManager();
24449
+ sentActivities = /* @__PURE__ */ new Map();
24450
+ workerActivities = /* @__PURE__ */ new Map();
24451
+ jobLogOffsets = /* @__PURE__ */ new Map();
24452
+ workerLogOffsets = /* @__PURE__ */ new Map();
24453
+ latestJobs = [];
24454
+ latestWorkers = [];
24455
+ activitySnapshotSent = false;
24456
+ activityDetailId = null;
24457
+ activityPoll = null;
24458
+ disposed = false;
24271
24459
  connect(actions) {
24272
24460
  this.actions = actions;
24273
24461
  }
@@ -24282,16 +24470,136 @@ var init_camouflage_view = __esm({
24282
24470
  }
24283
24471
  this.syncPermission(s);
24284
24472
  this.syncPrompts(s);
24473
+ this.syncActivity(s.workers);
24285
24474
  }
24286
24475
  /** Built-in plus the user's custom commands, re-sent when they change. */
24476
+ routeActivityCommand(text) {
24477
+ if (!/^\/(?:jobs|agents)$/i.test(text.trim())) return false;
24478
+ this.cam.send("ActivityBrowserOpen", {});
24479
+ return true;
24480
+ }
24481
+ activityViewChanged(change) {
24482
+ this.activityDetailId = change.view === "detail" ? change.id ?? null : null;
24483
+ this.jobLogOffsets.clear();
24484
+ this.workerLogOffsets.clear();
24485
+ if (this.activityDetailId) {
24486
+ if (this.activityDetailId.startsWith("agent:")) this.streamWorkerLogs(this.activityDetailId, true);
24487
+ else this.streamJobLogs(this.activityDetailId, true);
24488
+ }
24489
+ this.ensureActivityPolling();
24490
+ }
24491
+ stopActivity(id) {
24492
+ try {
24493
+ const job = this.jobs.get(id);
24494
+ if (!job || !isActiveJob(job)) return;
24495
+ this.jobs.cancel(id);
24496
+ this.refreshActivities();
24497
+ } catch (error) {
24498
+ logger.warn("camouflage.activity_stop_failed", { id, error: String(error) });
24499
+ this.cam.send("RuntimeError", { message: `Unable to stop background job: ${error.message}`, severity: "error" });
24500
+ }
24501
+ }
24502
+ dispose() {
24503
+ this.disposed = true;
24504
+ if (this.activityPoll) clearInterval(this.activityPoll);
24505
+ this.activityPoll = null;
24506
+ this.jobs.close();
24507
+ }
24508
+ syncActivity(workers) {
24509
+ this.latestWorkers = workers;
24510
+ try {
24511
+ const records = this.jobs.list({ limit: 100 });
24512
+ this.latestJobs = [
24513
+ ...records.filter(isActiveJob),
24514
+ ...records.filter((job) => !isActiveJob(job)).slice(0, 5)
24515
+ ];
24516
+ } catch (error) {
24517
+ logger.warn("camouflage.activity_jobs_read_failed", { error: String(error) });
24518
+ }
24519
+ const now2 = Date.now();
24520
+ const currentWorkers = /* @__PURE__ */ new Set();
24521
+ for (const worker of workers) {
24522
+ const id = `agent:${worker.id}`;
24523
+ currentWorkers.add(id);
24524
+ this.workerActivities.set(id, {
24525
+ item: activityFromWorker(worker),
24526
+ logs: [...worker.logs],
24527
+ active: true,
24528
+ expiresAt: Number.POSITIVE_INFINITY
24529
+ });
24530
+ }
24531
+ for (const [id, activity] of this.workerActivities) {
24532
+ if (currentWorkers.has(id) || !activity.active) continue;
24533
+ activity.active = false;
24534
+ activity.expiresAt = now2 + 10 * 6e4;
24535
+ if (activity.item.status === "running" || activity.item.status === "waiting") {
24536
+ const cancelled = activity.logs.some((line) => /cancelled by user|cancelling worker/i.test(line));
24537
+ const failed = activity.logs.some((line) => line.includes("[coordinator] Fetch failed:"));
24538
+ activity.item = {
24539
+ ...activity.item,
24540
+ status: cancelled ? "stopped" : failed ? "failed" : "done",
24541
+ summary: cancelled ? "Stopped" : failed ? activity.item.summary ?? "Worker failed" : "Worker finished",
24542
+ updated_at_ms: now2
24543
+ };
24544
+ }
24545
+ }
24546
+ for (const [id, activity] of this.workerActivities) {
24547
+ if (!activity.active && activity.expiresAt <= now2) this.workerActivities.delete(id);
24548
+ }
24549
+ const finishedWorkers = [...this.workerActivities.entries()].filter(([, activity]) => !activity.active).sort((a, b) => (b[1].item.updated_at_ms ?? b[1].item.started_at_ms ?? 0) - (a[1].item.updated_at_ms ?? a[1].item.started_at_ms ?? 0));
24550
+ for (const [id] of finishedWorkers.slice(20)) this.workerActivities.delete(id);
24551
+ const next = /* @__PURE__ */ new Map();
24552
+ for (const job of this.latestJobs) next.set(job.id, activityFromJob(job));
24553
+ for (const [id, activity] of this.workerActivities) next.set(id, activity.item);
24554
+ for (const event of diffActivityItems(this.sentActivities, [...next.values()], this.activitySnapshotSent)) {
24555
+ this.cam.send(event.type, event.payload);
24556
+ }
24557
+ this.activitySnapshotSent = true;
24558
+ this.sentActivities.clear();
24559
+ for (const [id, item] of next) this.sentActivities.set(id, JSON.stringify(item));
24560
+ if (this.activityDetailId?.startsWith("agent:")) this.streamWorkerLogs(this.activityDetailId, false);
24561
+ this.ensureActivityPolling();
24562
+ }
24563
+ ensureActivityPolling() {
24564
+ const needsPolling = this.latestJobs.some(isActiveJob);
24565
+ if (needsPolling && !this.activityPoll && !this.disposed) {
24566
+ this.activityPoll = setInterval(() => this.refreshActivities(), 1e3);
24567
+ this.activityPoll.unref?.();
24568
+ } else if (!needsPolling && this.activityPoll) {
24569
+ clearInterval(this.activityPoll);
24570
+ this.activityPoll = null;
24571
+ }
24572
+ }
24573
+ refreshActivities() {
24574
+ if (this.disposed) return;
24575
+ this.syncActivity(this.latestWorkers);
24576
+ const id = this.activityDetailId;
24577
+ if (id && !id.startsWith("agent:")) this.streamJobLogs(id, false);
24578
+ }
24579
+ streamWorkerLogs(id, reset2) {
24580
+ const activity = this.workerActivities.get(id);
24581
+ if (!activity) return;
24582
+ const offset = reset2 ? 0 : this.workerLogOffsets.get(id) ?? 0;
24583
+ const chunk = activity.logs.slice(offset).join("\n");
24584
+ if (chunk) this.cam.send("ActivityLog", { id, chunk: `${chunk}
24585
+ `, stream: "stdout" });
24586
+ this.workerLogOffsets.set(id, activity.logs.length);
24587
+ }
24588
+ streamJobLogs(id, initial) {
24589
+ for (const stream of ["stdout", "stderr"]) {
24590
+ const key = `${id}:${stream}`;
24591
+ try {
24592
+ const result2 = initial ? this.jobs.logs(id, stream, { tail: 1e3 }) : this.jobs.logs(id, stream, { offset: this.jobLogOffsets.get(key) ?? 0 });
24593
+ this.jobLogOffsets.set(key, result2.offset);
24594
+ if (result2.content) this.cam.send("ActivityLog", { id, chunk: result2.content, stream });
24595
+ } catch {
24596
+ }
24597
+ }
24598
+ }
24287
24599
  syncCommands(custom) {
24288
- const key = custom.map((c) => `${c.name}:${c.description ?? ""}`).join("|");
24289
- if (key === this.commandsKey && this.commandsKey !== "") return;
24290
- this.commandsKey = key || "(none)";
24291
- const builtin = BUILTIN_COMMANDS.map((c) => ({ name: c.name, description: c.description, args_hint: c.argHint }));
24292
- const names = new Set(builtin.map((c) => c.name));
24293
- const mine = custom.filter((c) => !names.has(c.name)).map((c) => ({ name: c.name, description: c.description ? `${c.description} (custom)` : "Custom command" }));
24294
- this.cam.send("SlashCommandsRegistered", { commands: [...builtin, ...mine] });
24600
+ this.commandsKey = syncSlashCommands(custom, this.commandsKey, (commands) => {
24601
+ this.cam.send("SlashCommandsRegistered", { commands });
24602
+ });
24295
24603
  }
24296
24604
  syncEvents(events) {
24297
24605
  if (events.length < this.settled || this.firstKey !== void 0 && events[0]?.key !== this.firstKey) {
@@ -24342,6 +24650,9 @@ var init_camouflage_view = __esm({
24342
24650
  return this.syncTool(e, prev);
24343
24651
  case "info":
24344
24652
  case "memory":
24653
+ if (e.kind === "info" && !shouldSendSubagentPolicyNotice(e.text, this.sentSubagentPolicyNotices)) {
24654
+ return this.mark(e.key, 0, true);
24655
+ }
24345
24656
  return this.notice(e.key, e.text, "info");
24346
24657
  case "error":
24347
24658
  return this.notice(e.key, e.text, "error");
@@ -24364,12 +24675,14 @@ var init_camouflage_view = __esm({
24364
24675
  if (e.status === "queued" || e.status === "running") return this.mark(e.key, 0, false);
24365
24676
  const ok = e.status === "done";
24366
24677
  const content = e.result ?? "";
24678
+ const bang = splitBangResult(e.render?.title, content);
24679
+ const output = e.status === "rejected" ? "" : bang.output;
24367
24680
  this.cam.send("ToolExecutionFinished", {
24368
24681
  tool_id: e.key,
24369
- exit_code: ok ? 0 : 1,
24682
+ exit_code: bang.exitCode ?? (ok ? 0 : 1),
24370
24683
  status: e.status === "rejected" ? "rejected" : e.status === "cancelled" ? "cancelled" : ok ? "done" : "error",
24371
- summary: toolSummary(e.name, ok, e.status === "rejected", content),
24372
- output: e.status === "rejected" ? "" : content,
24684
+ summary: bang.status ?? toolSummary(e.name, ok, e.status === "rejected", output),
24685
+ output,
24373
24686
  ...ok && e.render?.diff ? { diff: e.render.diff } : {}
24374
24687
  });
24375
24688
  return this.mark(e.key, 0, true);
@@ -26601,10 +26914,18 @@ var init_chat = __esm({
26601
26914
  return /* @__PURE__ */ jsx13(ToolView, { evt, verbose, isRepeated, intentTier });
26602
26915
  }
26603
26916
  if (evt.kind === "info") {
26604
- return /* @__PURE__ */ jsxs10(Text12, { color: theme.info.color, children: [
26605
- "\xB7 ",
26606
- humanizeInfo(evt.text, intentTier)
26607
- ] });
26917
+ const isSubagentHint = /^(Subagent policy|Subagent suggestion):/.test(evt.text);
26918
+ return /* @__PURE__ */ jsxs10(
26919
+ Text12,
26920
+ {
26921
+ color: isSubagentHint ? theme.muted?.color ?? theme.info.color : theme.info.color,
26922
+ dimColor: isSubagentHint || void 0,
26923
+ children: [
26924
+ "\xB7 ",
26925
+ humanizeInfo(evt.text, intentTier)
26926
+ ]
26927
+ }
26928
+ );
26608
26929
  }
26609
26930
  if (evt.kind === "jev") {
26610
26931
  const resultColor = evt.tone === "yes" ? theme.palette.success : evt.tone === "no" ? theme.warn : theme.accent;
@@ -27359,39 +27680,6 @@ var init_queue_plan_picker = __esm({
27359
27680
  }
27360
27681
  });
27361
27682
 
27362
- // src/agent/queue-batch.ts
27363
- function createQueueBatch(items) {
27364
- if (items.length < 2) return null;
27365
- if (items.some((item) => isCommand(item.full))) return null;
27366
- const prompts = items.flatMap((item) => item.batchPrompts ?? [item.full.trim()]);
27367
- const sourceKeys = items.flatMap((item) => item.sourceKeys ?? [item.key]);
27368
- const numberedPrompts = prompts.map((prompt, index) => `${index + 1}. ${prompt}`).join("\n\n");
27369
- const full = [
27370
- `The user chose to group ${prompts.length} queued follow-ups into one coordinated task.`,
27371
- "First identify dependencies and an efficient order. Treat every follow-up as a requirement. Delegate bounded, independent research to subagents when it materially helps; worker calls still require the normal user permission. Keep dependent changes coordinated in this session, and do not have multiple workers edit overlapping files.",
27372
- "",
27373
- "Queued follow-ups:",
27374
- numberedPrompts
27375
- ].join("\n");
27376
- const display = `Coordinate ${prompts.length} queued follow-ups as one task. Use subagents to research independent parts where useful; keep dependent changes coordinated.`;
27377
- return {
27378
- full,
27379
- display,
27380
- key: sourceKeys[0],
27381
- batchPrompts: prompts,
27382
- sourceKeys
27383
- };
27384
- }
27385
- function isCommand(prompt) {
27386
- const trimmed = prompt.trimStart();
27387
- return trimmed.startsWith("/") || trimmed.startsWith("!");
27388
- }
27389
- var init_queue_batch = __esm({
27390
- "src/agent/queue-batch.ts"() {
27391
- "use strict";
27392
- }
27393
- });
27394
-
27395
27683
  // src/ui/task-list.tsx
27396
27684
  import { useEffect as useEffect6, useRef as useRef4, useState as useState14 } from "react";
27397
27685
  import { Box as Box18, Text as Text19 } from "ink";
@@ -36595,6 +36883,8 @@ function App({
36595
36883
  const [history, setHistory] = useState28([]);
36596
36884
  const [historyIndex, setHistoryIndex] = useState28(-1);
36597
36885
  const [draftInput, setDraftInput] = useState28("");
36886
+ const latestPromptRef = useRef9(null);
36887
+ const pendingPromptEditRef = useRef9(null);
36598
36888
  const modesEnabled = cfg?.modesEnabled === true;
36599
36889
  const [mode, setMode] = useState28(initialCfg?.modesEnabled ? "edit" : "auto");
36600
36890
  useEffect11(() => {
@@ -37624,10 +37914,21 @@ ${result2.output ?? "(output not captured)"}`,
37624
37914
  if (!cfg) return;
37625
37915
  let trimmed = text.trim();
37626
37916
  if (!trimmed) return;
37917
+ let display = displayText?.trim() || trimmed;
37918
+ const promptKey = opts2?.eventKey ?? opts2?.queuedKeys?.[0];
37919
+ if (promptKey && !trimmed.startsWith("/") && parseBangCommand(trimmed) === null) {
37920
+ const previousTarget = latestPromptRef.current;
37921
+ if (opts2?.eventKey || previousTarget?.key === promptKey) {
37922
+ latestPromptRef.current = {
37923
+ key: promptKey,
37924
+ historyText: opts2?.historyText ?? previousTarget?.historyText ?? display,
37925
+ messagesBefore: [...messagesRef.current]
37926
+ };
37927
+ }
37928
+ }
37627
37929
  beginTurn();
37628
37930
  let overrideModel;
37629
37931
  let overrideEffort;
37630
- let display = displayText?.trim() || trimmed;
37631
37932
  const bangCommand = parseBangCommand(trimmed);
37632
37933
  if (bangCommand !== null) {
37633
37934
  await runUserShellCommand(bangCommand, display, opts2?.queuedKeys?.[0]);
@@ -37714,7 +38015,7 @@ ${result2.output ?? "(output not captured)"}`,
37714
38015
  )
37715
38016
  );
37716
38017
  } else {
37717
- setEvents((e) => [...e, { kind: "user", key: mkKey(), text: display, images: images.length > 0 ? images : void 0 }]);
38018
+ setEvents((e) => [...e, { kind: "user", key: opts2?.eventKey ?? mkKey(), text: display, images: images.length > 0 ? images : void 0 }]);
37718
38019
  }
37719
38020
  const nudge = maybeLspNudge(display, cfg?.lspEnabled ?? false, cfg?.lspServers ?? {});
37720
38021
  if (nudge) {
@@ -38420,13 +38721,26 @@ ${conflicts.join("\n")}` }
38420
38721
  [cfg, handleSlash, updateAssistant, updateTool, saveSessionSafe, updateResponseMeta, setShowPlanCompletePicker, runUserShellCommand]
38421
38722
  );
38422
38723
  useEffect11(() => {
38423
- if (!busy && queuePlanDraft === null && queue2.length > 0 && supervisorRef.current.phase === "idle") {
38424
- const next = queue2[0];
38425
- setQueue((q) => q.slice(1));
38426
- processMessage(next.full, next.display, {
38427
- queuedKeys: next.sourceKeys ?? [next.key],
38428
- ...next.batchPrompts ? { userPromptForHooks: next.batchPrompts.join("\n\n") } : {}
38429
- });
38724
+ if (!busy && queuePlanDraft === null && supervisorRef.current.phase === "idle") {
38725
+ const pendingEdit = pendingPromptEditRef.current;
38726
+ if (pendingEdit) {
38727
+ pendingPromptEditRef.current = null;
38728
+ messagesRef.current = [...pendingEdit.messagesBefore];
38729
+ setEvents((events2) => replaceUserPromptEvent(events2, pendingEdit.key, pendingEdit.display, true));
38730
+ processMessage(pendingEdit.full, pendingEdit.display, {
38731
+ queuedKeys: [pendingEdit.key],
38732
+ historyText: pendingEdit.display
38733
+ });
38734
+ return;
38735
+ }
38736
+ if (queue2.length > 0) {
38737
+ const next = queue2[0];
38738
+ setQueue((q) => q.slice(1));
38739
+ processMessage(next.full, next.display, {
38740
+ queuedKeys: next.sourceKeys ?? [next.key],
38741
+ ...next.batchPrompts ? { userPromptForHooks: next.batchPrompts.join("\n\n") } : {}
38742
+ });
38743
+ }
38430
38744
  }
38431
38745
  }, [busy, queue2, queuePlanDraft, processMessage]);
38432
38746
  const handleQueuePlanPick = useCallback9(
@@ -38465,10 +38779,55 @@ ${conflicts.join("\n")}` }
38465
38779
  return;
38466
38780
  }
38467
38781
  const historyEntry = trimmedDisplay;
38782
+ const editTarget = latestPromptRef.current;
38783
+ const selectedHistoryEntry = history[historyIndex];
38784
+ const isEditedLatestPrompt = historyIndex === history.length - 1 && editTarget !== null && events.some((event) => event.kind === "user" && event.key === editTarget.key) && selectedHistoryEntry === editTarget.historyText;
38785
+ if (isEditedLatestPrompt && editTarget) {
38786
+ if (trimmedDisplay === selectedHistoryEntry) {
38787
+ setInput("");
38788
+ setHistoryIndex(-1);
38789
+ return;
38790
+ }
38791
+ const updatedQueue = replaceQueuedPrompt(queue2, editTarget.key, trimmedFull, trimmedDisplay);
38792
+ if (updatedQueue || editTarget.messagesBefore) {
38793
+ setHistory(
38794
+ (items) => items.length > 0 && items[items.length - 1] === editTarget.historyText ? [...items.slice(0, -1), trimmedDisplay] : items
38795
+ );
38796
+ setInput("");
38797
+ setHistoryIndex(-1);
38798
+ latestPromptRef.current = { ...editTarget, historyText: trimmedDisplay };
38799
+ setEvents((events2) => replaceUserPromptEvent(events2, editTarget.key, trimmedDisplay));
38800
+ if (updatedQueue) {
38801
+ setQueue(updatedQueue);
38802
+ return;
38803
+ }
38804
+ const edit = {
38805
+ key: editTarget.key,
38806
+ full: trimmedFull,
38807
+ display: trimmedDisplay,
38808
+ messagesBefore: editTarget.messagesBefore
38809
+ };
38810
+ if (busyRef.current || supervisorRef.current.isRunning) {
38811
+ pendingPromptEditRef.current = edit;
38812
+ interruptTurn(interruptDepsRef.current);
38813
+ } else {
38814
+ messagesRef.current = [...edit.messagesBefore];
38815
+ setEvents((events2) => replaceUserPromptEvent(events2, edit.key, edit.display, true));
38816
+ processMessage(edit.full, edit.display, {
38817
+ queuedKeys: [edit.key],
38818
+ historyText: edit.display
38819
+ });
38820
+ }
38821
+ return;
38822
+ }
38823
+ }
38468
38824
  if (busyRef.current || supervisorRef.current.isRunning) {
38469
- const key = mkKey();
38470
- setEvents((e) => [...e, { kind: "user", key, text: trimmedDisplay, queued: true }]);
38471
- setQueue((q) => [...q, { full: trimmedFull, display: trimmedDisplay, key }]);
38825
+ const key2 = mkKey();
38826
+ setEvents((e) => [...e, { kind: "user", key: key2, text: trimmedDisplay, queued: true }]);
38827
+ setQueue((q) => [...q, { full: trimmedFull, display: trimmedDisplay, key: key2 }]);
38828
+ if (!trimmedFull.startsWith("/") && parseBangCommand(trimmedFull) === null) {
38829
+ latestPromptRef.current = { key: key2, historyText: historyEntry, messagesBefore: null };
38830
+ }
38472
38831
  setHistory((h) => h.length > 0 && h[h.length - 1] === historyEntry ? h : [...h, historyEntry]);
38473
38832
  setInput("");
38474
38833
  setHistoryIndex(-1);
@@ -38477,9 +38836,13 @@ ${conflicts.join("\n")}` }
38477
38836
  setHistory((h) => h.length > 0 && h[h.length - 1] === historyEntry ? h : [...h, historyEntry]);
38478
38837
  setInput("");
38479
38838
  setHistoryIndex(-1);
38480
- processMessage(trimmedFull, trimmedDisplay !== trimmedFull ? trimmedDisplay : void 0);
38839
+ const key = mkKey();
38840
+ processMessage(trimmedFull, trimmedDisplay !== trimmedFull ? trimmedDisplay : void 0, {
38841
+ eventKey: key,
38842
+ historyText: historyEntry
38843
+ });
38481
38844
  },
38482
- [processMessage, queue2]
38845
+ [processMessage, queue2, history, historyIndex, events]
38483
38846
  );
38484
38847
  submitRef.current = submit;
38485
38848
  useEffect11(() => {
@@ -38719,6 +39082,7 @@ ${conflicts.join("\n")}` }
38719
39082
  bridge.sync({
38720
39083
  events,
38721
39084
  busy,
39085
+ workers: activeWorkers,
38722
39086
  mode,
38723
39087
  model: cfg?.model ?? "",
38724
39088
  usage,
@@ -38954,6 +39318,7 @@ ${conflicts.join("\n")}` }
38954
39318
  }
38955
39319
  ),
38956
39320
  parseBangCommand(input) !== null && /* @__PURE__ */ jsx43(Box41, { marginTop: 1, children: /* @__PURE__ */ jsx43(Text42, { dimColor: true, children: "! shell command \u2014 runs in your terminal (logins and prompts work); output is shared with the agent" }) }),
39321
+ historyIndex === history.length - 1 && latestPromptRef.current?.historyText === history[history.length - 1] && /* @__PURE__ */ jsx43(Box41, { marginTop: 1, children: /* @__PURE__ */ jsx43(Text42, { dimColor: true, children: "editing last message \u2014 Enter replaces it" }) }),
38957
39322
  /* @__PURE__ */ jsxs40(Box41, { marginTop: parseBangCommand(input) !== null ? 0 : 1, children: [
38958
39323
  /* @__PURE__ */ jsx43(Text42, { color: theme.prompt ?? theme.accent, children: "\u203A " }),
38959
39324
  /* @__PURE__ */ jsx43(