autopilot-ai 1.11.0 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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, {
@@ -24005,6 +24138,7 @@ async function runCamouflageView(opts2) {
24005
24138
  resume: () => sdk.resumeTerminal?.(cam)
24006
24139
  });
24007
24140
  cam.on("userInput", (text) => {
24141
+ if (view.routeActivityCommand(text)) return;
24008
24142
  view.echoed.push(text.trim());
24009
24143
  view.actions?.submit(text);
24010
24144
  });
@@ -24020,10 +24154,15 @@ async function runCamouflageView(opts2) {
24020
24154
  });
24021
24155
  });
24022
24156
  cam.on("modeChangeRequested", () => view.actions?.cycleMode());
24157
+ cam.on("activityStopRequested", ({ id }) => view.stopActivity(id));
24158
+ cam.on("activityViewChanged", (change) => {
24159
+ view.activityViewChanged(change);
24160
+ });
24023
24161
  let closing = false;
24024
24162
  const finish = async () => {
24025
24163
  if (closing) return;
24026
24164
  closing = true;
24165
+ view.dispose();
24027
24166
  registerTerminalHandoff(null);
24028
24167
  await cam.close().catch(() => void 0);
24029
24168
  restoreConsole();
@@ -24243,6 +24382,8 @@ var init_camouflage_view = __esm({
24243
24382
  init_hooks_wizard();
24244
24383
  init_mode();
24245
24384
  init_logger();
24385
+ init_manager();
24386
+ init_activity();
24246
24387
  CamouflageUnavailable = class extends Error {
24247
24388
  };
24248
24389
  View = class {
@@ -24268,6 +24409,17 @@ var init_camouflage_view = __esm({
24268
24409
  permission = null;
24269
24410
  openPrompt = null;
24270
24411
  commandsKey = "";
24412
+ jobs = new JobManager();
24413
+ sentActivities = /* @__PURE__ */ new Map();
24414
+ workerActivities = /* @__PURE__ */ new Map();
24415
+ jobLogOffsets = /* @__PURE__ */ new Map();
24416
+ workerLogOffsets = /* @__PURE__ */ new Map();
24417
+ latestJobs = [];
24418
+ latestWorkers = [];
24419
+ activitySnapshotSent = false;
24420
+ activityDetailId = null;
24421
+ activityPoll = null;
24422
+ disposed = false;
24271
24423
  connect(actions) {
24272
24424
  this.actions = actions;
24273
24425
  }
@@ -24282,13 +24434,141 @@ var init_camouflage_view = __esm({
24282
24434
  }
24283
24435
  this.syncPermission(s);
24284
24436
  this.syncPrompts(s);
24437
+ this.syncActivity(s.workers);
24285
24438
  }
24286
24439
  /** Built-in plus the user's custom commands, re-sent when they change. */
24440
+ routeActivityCommand(text) {
24441
+ if (!/^\/(?:jobs|agents)$/i.test(text.trim())) return false;
24442
+ this.cam.send("ActivityBrowserOpen", {});
24443
+ return true;
24444
+ }
24445
+ activityViewChanged(change) {
24446
+ this.activityDetailId = change.view === "detail" ? change.id ?? null : null;
24447
+ this.jobLogOffsets.clear();
24448
+ this.workerLogOffsets.clear();
24449
+ if (this.activityDetailId) {
24450
+ if (this.activityDetailId.startsWith("agent:")) this.streamWorkerLogs(this.activityDetailId, true);
24451
+ else this.streamJobLogs(this.activityDetailId, true);
24452
+ }
24453
+ this.ensureActivityPolling();
24454
+ }
24455
+ stopActivity(id) {
24456
+ try {
24457
+ const job = this.jobs.get(id);
24458
+ if (!job || !isActiveJob(job)) return;
24459
+ this.jobs.cancel(id);
24460
+ this.refreshActivities();
24461
+ } catch (error) {
24462
+ logger.warn("camouflage.activity_stop_failed", { id, error: String(error) });
24463
+ this.cam.send("RuntimeError", { message: `Unable to stop background job: ${error.message}`, severity: "error" });
24464
+ }
24465
+ }
24466
+ dispose() {
24467
+ this.disposed = true;
24468
+ if (this.activityPoll) clearInterval(this.activityPoll);
24469
+ this.activityPoll = null;
24470
+ this.jobs.close();
24471
+ }
24472
+ syncActivity(workers) {
24473
+ this.latestWorkers = workers;
24474
+ try {
24475
+ const records = this.jobs.list({ limit: 100 });
24476
+ this.latestJobs = [
24477
+ ...records.filter(isActiveJob),
24478
+ ...records.filter((job) => !isActiveJob(job)).slice(0, 5)
24479
+ ];
24480
+ } catch (error) {
24481
+ logger.warn("camouflage.activity_jobs_read_failed", { error: String(error) });
24482
+ }
24483
+ const now2 = Date.now();
24484
+ const currentWorkers = /* @__PURE__ */ new Set();
24485
+ for (const worker of workers) {
24486
+ const id = `agent:${worker.id}`;
24487
+ currentWorkers.add(id);
24488
+ this.workerActivities.set(id, {
24489
+ item: activityFromWorker(worker),
24490
+ logs: [...worker.logs],
24491
+ active: true,
24492
+ expiresAt: Number.POSITIVE_INFINITY
24493
+ });
24494
+ }
24495
+ for (const [id, activity] of this.workerActivities) {
24496
+ if (currentWorkers.has(id) || !activity.active) continue;
24497
+ activity.active = false;
24498
+ activity.expiresAt = now2 + 10 * 6e4;
24499
+ if (activity.item.status === "running" || activity.item.status === "waiting") {
24500
+ const cancelled = activity.logs.some((line) => /cancelled by user|cancelling worker/i.test(line));
24501
+ const failed = activity.logs.some((line) => line.includes("[coordinator] Fetch failed:"));
24502
+ activity.item = {
24503
+ ...activity.item,
24504
+ status: cancelled ? "stopped" : failed ? "failed" : "done",
24505
+ summary: cancelled ? "Stopped" : failed ? activity.item.summary ?? "Worker failed" : "Worker finished",
24506
+ updated_at_ms: now2
24507
+ };
24508
+ }
24509
+ }
24510
+ for (const [id, activity] of this.workerActivities) {
24511
+ if (!activity.active && activity.expiresAt <= now2) this.workerActivities.delete(id);
24512
+ }
24513
+ 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));
24514
+ for (const [id] of finishedWorkers.slice(20)) this.workerActivities.delete(id);
24515
+ const next = /* @__PURE__ */ new Map();
24516
+ for (const job of this.latestJobs) next.set(job.id, activityFromJob(job));
24517
+ for (const [id, activity] of this.workerActivities) next.set(id, activity.item);
24518
+ for (const event of diffActivityItems(this.sentActivities, [...next.values()], this.activitySnapshotSent)) {
24519
+ this.cam.send(event.type, event.payload);
24520
+ }
24521
+ this.activitySnapshotSent = true;
24522
+ this.sentActivities.clear();
24523
+ for (const [id, item] of next) this.sentActivities.set(id, JSON.stringify(item));
24524
+ if (this.activityDetailId?.startsWith("agent:")) this.streamWorkerLogs(this.activityDetailId, false);
24525
+ this.ensureActivityPolling();
24526
+ }
24527
+ ensureActivityPolling() {
24528
+ const needsPolling = this.latestJobs.some(isActiveJob);
24529
+ if (needsPolling && !this.activityPoll && !this.disposed) {
24530
+ this.activityPoll = setInterval(() => this.refreshActivities(), 1e3);
24531
+ this.activityPoll.unref?.();
24532
+ } else if (!needsPolling && this.activityPoll) {
24533
+ clearInterval(this.activityPoll);
24534
+ this.activityPoll = null;
24535
+ }
24536
+ }
24537
+ refreshActivities() {
24538
+ if (this.disposed) return;
24539
+ this.syncActivity(this.latestWorkers);
24540
+ const id = this.activityDetailId;
24541
+ if (id && !id.startsWith("agent:")) this.streamJobLogs(id, false);
24542
+ }
24543
+ streamWorkerLogs(id, reset2) {
24544
+ const activity = this.workerActivities.get(id);
24545
+ if (!activity) return;
24546
+ const offset = reset2 ? 0 : this.workerLogOffsets.get(id) ?? 0;
24547
+ const chunk = activity.logs.slice(offset).join("\n");
24548
+ if (chunk) this.cam.send("ActivityLog", { id, chunk: `${chunk}
24549
+ `, stream: "stdout" });
24550
+ this.workerLogOffsets.set(id, activity.logs.length);
24551
+ }
24552
+ streamJobLogs(id, initial) {
24553
+ for (const stream of ["stdout", "stderr"]) {
24554
+ const key = `${id}:${stream}`;
24555
+ try {
24556
+ const result2 = initial ? this.jobs.logs(id, stream, { tail: 1e3 }) : this.jobs.logs(id, stream, { offset: this.jobLogOffsets.get(key) ?? 0 });
24557
+ this.jobLogOffsets.set(key, result2.offset);
24558
+ if (result2.content) this.cam.send("ActivityLog", { id, chunk: result2.content, stream });
24559
+ } catch {
24560
+ }
24561
+ }
24562
+ }
24287
24563
  syncCommands(custom) {
24288
24564
  const key = custom.map((c) => `${c.name}:${c.description ?? ""}`).join("|");
24289
24565
  if (key === this.commandsKey && this.commandsKey !== "") return;
24290
24566
  this.commandsKey = key || "(none)";
24291
- const builtin = BUILTIN_COMMANDS.map((c) => ({ name: c.name, description: c.description, args_hint: c.argHint }));
24567
+ const builtin = [
24568
+ ...BUILTIN_COMMANDS.map((c) => ({ name: c.name, description: c.description, args_hint: c.argHint })),
24569
+ { name: "jobs", description: "Browse background jobs and agents", args_hint: void 0 },
24570
+ { name: "agents", description: "Browse background agents and jobs", args_hint: void 0 }
24571
+ ];
24292
24572
  const names = new Set(builtin.map((c) => c.name));
24293
24573
  const mine = custom.filter((c) => !names.has(c.name)).map((c) => ({ name: c.name, description: c.description ? `${c.description} (custom)` : "Custom command" }));
24294
24574
  this.cam.send("SlashCommandsRegistered", { commands: [...builtin, ...mine] });
@@ -27359,39 +27639,6 @@ var init_queue_plan_picker = __esm({
27359
27639
  }
27360
27640
  });
27361
27641
 
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
27642
  // src/ui/task-list.tsx
27396
27643
  import { useEffect as useEffect6, useRef as useRef4, useState as useState14 } from "react";
27397
27644
  import { Box as Box18, Text as Text19 } from "ink";
@@ -36595,6 +36842,8 @@ function App({
36595
36842
  const [history, setHistory] = useState28([]);
36596
36843
  const [historyIndex, setHistoryIndex] = useState28(-1);
36597
36844
  const [draftInput, setDraftInput] = useState28("");
36845
+ const latestPromptRef = useRef9(null);
36846
+ const pendingPromptEditRef = useRef9(null);
36598
36847
  const modesEnabled = cfg?.modesEnabled === true;
36599
36848
  const [mode, setMode] = useState28(initialCfg?.modesEnabled ? "edit" : "auto");
36600
36849
  useEffect11(() => {
@@ -37624,10 +37873,21 @@ ${result2.output ?? "(output not captured)"}`,
37624
37873
  if (!cfg) return;
37625
37874
  let trimmed = text.trim();
37626
37875
  if (!trimmed) return;
37876
+ let display = displayText?.trim() || trimmed;
37877
+ const promptKey = opts2?.eventKey ?? opts2?.queuedKeys?.[0];
37878
+ if (promptKey && !trimmed.startsWith("/") && parseBangCommand(trimmed) === null) {
37879
+ const previousTarget = latestPromptRef.current;
37880
+ if (opts2?.eventKey || previousTarget?.key === promptKey) {
37881
+ latestPromptRef.current = {
37882
+ key: promptKey,
37883
+ historyText: opts2?.historyText ?? previousTarget?.historyText ?? display,
37884
+ messagesBefore: [...messagesRef.current]
37885
+ };
37886
+ }
37887
+ }
37627
37888
  beginTurn();
37628
37889
  let overrideModel;
37629
37890
  let overrideEffort;
37630
- let display = displayText?.trim() || trimmed;
37631
37891
  const bangCommand = parseBangCommand(trimmed);
37632
37892
  if (bangCommand !== null) {
37633
37893
  await runUserShellCommand(bangCommand, display, opts2?.queuedKeys?.[0]);
@@ -37714,7 +37974,7 @@ ${result2.output ?? "(output not captured)"}`,
37714
37974
  )
37715
37975
  );
37716
37976
  } else {
37717
- setEvents((e) => [...e, { kind: "user", key: mkKey(), text: display, images: images.length > 0 ? images : void 0 }]);
37977
+ setEvents((e) => [...e, { kind: "user", key: opts2?.eventKey ?? mkKey(), text: display, images: images.length > 0 ? images : void 0 }]);
37718
37978
  }
37719
37979
  const nudge = maybeLspNudge(display, cfg?.lspEnabled ?? false, cfg?.lspServers ?? {});
37720
37980
  if (nudge) {
@@ -38420,13 +38680,26 @@ ${conflicts.join("\n")}` }
38420
38680
  [cfg, handleSlash, updateAssistant, updateTool, saveSessionSafe, updateResponseMeta, setShowPlanCompletePicker, runUserShellCommand]
38421
38681
  );
38422
38682
  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
- });
38683
+ if (!busy && queuePlanDraft === null && supervisorRef.current.phase === "idle") {
38684
+ const pendingEdit = pendingPromptEditRef.current;
38685
+ if (pendingEdit) {
38686
+ pendingPromptEditRef.current = null;
38687
+ messagesRef.current = [...pendingEdit.messagesBefore];
38688
+ setEvents((events2) => replaceUserPromptEvent(events2, pendingEdit.key, pendingEdit.display, true));
38689
+ processMessage(pendingEdit.full, pendingEdit.display, {
38690
+ queuedKeys: [pendingEdit.key],
38691
+ historyText: pendingEdit.display
38692
+ });
38693
+ return;
38694
+ }
38695
+ if (queue2.length > 0) {
38696
+ const next = queue2[0];
38697
+ setQueue((q) => q.slice(1));
38698
+ processMessage(next.full, next.display, {
38699
+ queuedKeys: next.sourceKeys ?? [next.key],
38700
+ ...next.batchPrompts ? { userPromptForHooks: next.batchPrompts.join("\n\n") } : {}
38701
+ });
38702
+ }
38430
38703
  }
38431
38704
  }, [busy, queue2, queuePlanDraft, processMessage]);
38432
38705
  const handleQueuePlanPick = useCallback9(
@@ -38465,10 +38738,55 @@ ${conflicts.join("\n")}` }
38465
38738
  return;
38466
38739
  }
38467
38740
  const historyEntry = trimmedDisplay;
38741
+ const editTarget = latestPromptRef.current;
38742
+ const selectedHistoryEntry = history[historyIndex];
38743
+ const isEditedLatestPrompt = historyIndex === history.length - 1 && editTarget !== null && events.some((event) => event.kind === "user" && event.key === editTarget.key) && selectedHistoryEntry === editTarget.historyText;
38744
+ if (isEditedLatestPrompt && editTarget) {
38745
+ if (trimmedDisplay === selectedHistoryEntry) {
38746
+ setInput("");
38747
+ setHistoryIndex(-1);
38748
+ return;
38749
+ }
38750
+ const updatedQueue = replaceQueuedPrompt(queue2, editTarget.key, trimmedFull, trimmedDisplay);
38751
+ if (updatedQueue || editTarget.messagesBefore) {
38752
+ setHistory(
38753
+ (items) => items.length > 0 && items[items.length - 1] === editTarget.historyText ? [...items.slice(0, -1), trimmedDisplay] : items
38754
+ );
38755
+ setInput("");
38756
+ setHistoryIndex(-1);
38757
+ latestPromptRef.current = { ...editTarget, historyText: trimmedDisplay };
38758
+ setEvents((events2) => replaceUserPromptEvent(events2, editTarget.key, trimmedDisplay));
38759
+ if (updatedQueue) {
38760
+ setQueue(updatedQueue);
38761
+ return;
38762
+ }
38763
+ const edit = {
38764
+ key: editTarget.key,
38765
+ full: trimmedFull,
38766
+ display: trimmedDisplay,
38767
+ messagesBefore: editTarget.messagesBefore
38768
+ };
38769
+ if (busyRef.current || supervisorRef.current.isRunning) {
38770
+ pendingPromptEditRef.current = edit;
38771
+ interruptTurn(interruptDepsRef.current);
38772
+ } else {
38773
+ messagesRef.current = [...edit.messagesBefore];
38774
+ setEvents((events2) => replaceUserPromptEvent(events2, edit.key, edit.display, true));
38775
+ processMessage(edit.full, edit.display, {
38776
+ queuedKeys: [edit.key],
38777
+ historyText: edit.display
38778
+ });
38779
+ }
38780
+ return;
38781
+ }
38782
+ }
38468
38783
  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 }]);
38784
+ const key2 = mkKey();
38785
+ setEvents((e) => [...e, { kind: "user", key: key2, text: trimmedDisplay, queued: true }]);
38786
+ setQueue((q) => [...q, { full: trimmedFull, display: trimmedDisplay, key: key2 }]);
38787
+ if (!trimmedFull.startsWith("/") && parseBangCommand(trimmedFull) === null) {
38788
+ latestPromptRef.current = { key: key2, historyText: historyEntry, messagesBefore: null };
38789
+ }
38472
38790
  setHistory((h) => h.length > 0 && h[h.length - 1] === historyEntry ? h : [...h, historyEntry]);
38473
38791
  setInput("");
38474
38792
  setHistoryIndex(-1);
@@ -38477,9 +38795,13 @@ ${conflicts.join("\n")}` }
38477
38795
  setHistory((h) => h.length > 0 && h[h.length - 1] === historyEntry ? h : [...h, historyEntry]);
38478
38796
  setInput("");
38479
38797
  setHistoryIndex(-1);
38480
- processMessage(trimmedFull, trimmedDisplay !== trimmedFull ? trimmedDisplay : void 0);
38798
+ const key = mkKey();
38799
+ processMessage(trimmedFull, trimmedDisplay !== trimmedFull ? trimmedDisplay : void 0, {
38800
+ eventKey: key,
38801
+ historyText: historyEntry
38802
+ });
38481
38803
  },
38482
- [processMessage, queue2]
38804
+ [processMessage, queue2, history, historyIndex, events]
38483
38805
  );
38484
38806
  submitRef.current = submit;
38485
38807
  useEffect11(() => {
@@ -38719,6 +39041,7 @@ ${conflicts.join("\n")}` }
38719
39041
  bridge.sync({
38720
39042
  events,
38721
39043
  busy,
39044
+ workers: activeWorkers,
38722
39045
  mode,
38723
39046
  model: cfg?.model ?? "",
38724
39047
  usage,
@@ -38954,6 +39277,7 @@ ${conflicts.join("\n")}` }
38954
39277
  }
38955
39278
  ),
38956
39279
  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" }) }),
39280
+ 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
39281
  /* @__PURE__ */ jsxs40(Box41, { marginTop: parseBangCommand(input) !== null ? 0 : 1, children: [
38958
39282
  /* @__PURE__ */ jsx43(Text42, { color: theme.prompt ?? theme.accent, children: "\u203A " }),
38959
39283
  /* @__PURE__ */ jsx43(