@keyou007/agent-bot 0.1.15-alpha.3 → 0.1.15-alpha.5

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 (35) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +4 -1
  3. package/README.zh.md +5 -2
  4. package/dist/cli/VerificationOutput.js +1 -6
  5. package/dist/cli/VerificationOutput.js.map +1 -1
  6. package/dist/cli.js +7 -13
  7. package/dist/cli.js.map +1 -1
  8. package/dist/commands/CommandRouter.js +7 -0
  9. package/dist/commands/CommandRouter.js.map +1 -1
  10. package/dist/feishu/CardRenderer.js +76 -9
  11. package/dist/feishu/CardRenderer.js.map +1 -1
  12. package/dist/feishu/FinalResponseBranding.js +1 -1
  13. package/dist/feishu/FinalResponseBranding.js.map +1 -1
  14. package/dist/proxy/ProxySessionController.js +161 -83
  15. package/dist/proxy/ProxySessionController.js.map +1 -1
  16. package/dist/shell/ShellCommandJobManager.js +264 -0
  17. package/dist/shell/ShellCommandJobManager.js.map +1 -0
  18. package/dist/shell/shellCommandRunner.js +157 -0
  19. package/dist/shell/shellCommandRunner.js.map +1 -0
  20. package/dist/utils/executeShellCommand.js +1 -1
  21. package/dist/utils/executeShellCommand.js.map +1 -1
  22. package/docs/technical-reference.md +5 -3
  23. package/docs/technical-reference.zh.md +5 -3
  24. package/npm-shrinkwrap.json +2 -19
  25. package/package.json +1 -3
  26. package/src/cli/VerificationOutput.ts +1 -12
  27. package/src/cli.ts +6 -22
  28. package/src/commands/CommandRouter.ts +6 -0
  29. package/src/commands/commandTypes.ts +1 -0
  30. package/src/feishu/CardRenderer.ts +91 -17
  31. package/src/feishu/FinalResponseBranding.ts +2 -1
  32. package/src/proxy/ProxySessionController.ts +196 -89
  33. package/src/shell/ShellCommandJobManager.ts +341 -0
  34. package/src/shell/shellCommandRunner.ts +163 -0
  35. package/src/utils/executeShellCommand.ts +1 -1
@@ -47,6 +47,12 @@ import type {
47
47
  RuntimePrompt,
48
48
  RuntimeSession,
49
49
  } from "../runtime/types.js";
50
+ import {
51
+ isActiveShellCommandJob,
52
+ ShellCommandJobManager,
53
+ type ShellCommandJobManagerLike,
54
+ type ShellCommandJobSnapshot,
55
+ } from "../shell/ShellCommandJobManager.js";
50
56
  import {
51
57
  StateStore,
52
58
  type CardActionBinding,
@@ -63,7 +69,6 @@ import { normalizeTaskTitle } from "../utils/taskTitle.js";
63
69
  import {
64
70
  executeShellCommand,
65
71
  type ShellCommandOptions,
66
- type ShellCommandOutputSnapshot,
67
72
  type ShellCommandResult,
68
73
  } from "../utils/executeShellCommand.js";
69
74
 
@@ -94,6 +99,12 @@ interface ForwardAttachmentReservation {
94
99
  registry: Map<string, PendingForwardAttachment>;
95
100
  }
96
101
 
102
+ interface ShellCommandJobMonitor {
103
+ scheduler: CardUpdateScheduler<ShellCommandCardView>;
104
+ timer: NodeJS.Timeout;
105
+ refreshing: boolean;
106
+ }
107
+
97
108
  const MESSAGE_RECEIVED_REACTION = "OnIt";
98
109
  const MESSAGE_COMPLETED_REACTION = "DONE";
99
110
  const MESSAGE_FAILED_REACTION = "ERROR";
@@ -105,7 +116,8 @@ const RESET_HISTORY_PAGE_SIZE = 10;
105
116
  const MAX_LLM_TURN_RETRIES = 3;
106
117
  const RECOVERY_ACTIVITY_WINDOW_MS = 5 * 60 * 1_000;
107
118
  const RECOVERY_HEARTBEAT_INTERVAL_MS = 60 * 1_000;
108
- const SHELL_COMMAND_ELAPSED_UPDATE_INTERVAL_MS = 3_000;
119
+ const SHELL_COMMAND_JOB_POLL_INTERVAL_MS = 1_000;
120
+ const SHELL_COMMAND_JOB_RELAUNCH_DELAY_MS = 5_000;
109
121
  const FORWARD_ATTACHMENT_WINDOW_MS = 800;
110
122
  const DEFAULT_MERGED_FORWARD_INSTRUCTION = "请参考以下内容回复用户";
111
123
  const DEFAULT_REFERENCED_MESSAGE_INSTRUCTION = "请参考引用消息回复用户";
@@ -150,6 +162,12 @@ const HELP_COMMAND_SECTIONS: Array<{
150
162
  usage: "[目录]",
151
163
  description: "浏览、发送文件,并从选定目录创建任务或群",
152
164
  },
165
+ {
166
+ command: "/file",
167
+ usage: "&#60;文件路径&#62;",
168
+ description: "将指定文件发送到当前飞书会话",
169
+ requiresArgument: true,
170
+ },
153
171
  {
154
172
  command: "/forkgroup",
155
173
  usage: "[title]",
@@ -404,6 +422,7 @@ export class ProxySessionController {
404
422
  private readonly pendingMergedForwards = new Map<string, PendingForwardAttachment>();
405
423
  private readonly pendingResourceForwards = new Map<string, PendingForwardAttachment>();
406
424
  private readonly relatedReactionMessageIds = new Map<string, string[]>();
425
+ private readonly shellCommandJobMonitors = new Map<string, ShellCommandJobMonitor>();
407
426
  private readonly unsubscribe: Array<() => void> = [];
408
427
  private readonly recoveryActivityHeartbeat: NodeJS.Timeout;
409
428
  private startupRecovery?: Promise<void>;
@@ -418,6 +437,9 @@ export class ProxySessionController {
418
437
  private readonly lifecycle?: ProxyLifecycle,
419
438
  private readonly shellCommandExecutor: ShellCommandExecutor = executeShellCommand,
420
439
  private readonly windowsDriveLister: WindowsDriveLister = listWindowsDriveRoots,
440
+ private readonly shellCommandJobs: ShellCommandJobManagerLike = new ShellCommandJobManager(
441
+ path.join(path.dirname(config.storage.sqlitePath), "command-jobs"),
442
+ ),
421
443
  ) {
422
444
  for (const [, runtime] of this.runtimes.entries()) {
423
445
  this.unsubscribe.push(
@@ -708,7 +730,9 @@ export class ProxySessionController {
708
730
  message.userId,
709
731
  message,
710
732
  );
711
- if (!isPromptCommand(command)) await this.finalizeStandaloneMessageReaction(message.messageId, "completed");
733
+ if (!commandDefersReactionFinalization(command)) {
734
+ await this.finalizeStandaloneMessageReaction(message.messageId, "completed");
735
+ }
712
736
  } catch (error) {
713
737
  await this.finalizeStandaloneMessageReaction(message.messageId, "failed");
714
738
  await this.sendError(message.contextKey, error);
@@ -731,7 +755,9 @@ export class ProxySessionController {
731
755
  message.userId,
732
756
  message,
733
757
  );
734
- if (!isPromptCommand(command)) await this.finalizeStandaloneMessageReaction(message.messageId, "completed");
758
+ if (!commandDefersReactionFinalization(command)) {
759
+ await this.finalizeStandaloneMessageReaction(message.messageId, "completed");
760
+ }
735
761
  } catch (error) {
736
762
  await this.finalizeStandaloneMessageReaction(message.messageId, "failed");
737
763
  await this.sendError(message.contextKey, error);
@@ -786,6 +812,11 @@ export class ProxySessionController {
786
812
  } else if (kind === "turn_cancel") {
787
813
  const sessionId = String(scopedAction.value.sessionId ?? "");
788
814
  await this.cancelSession(this.requireSession(contextKey, sessionId));
815
+ } else if (kind === "shell_command_cancel") {
816
+ await this.cancelShellCommandJob(
817
+ contextKey,
818
+ String(scopedAction.value.jobId ?? ""),
819
+ );
789
820
  } else if (kind === "turn_reset") {
790
821
  const sessionId = String(scopedAction.value.sessionId ?? "");
791
822
  await this.resetCurrentSessionToTurn(
@@ -1057,6 +1088,7 @@ export class ProxySessionController {
1057
1088
  this.relatedReactionMessageIds.clear();
1058
1089
  for (const timer of this.recoveryRetryTimers.values()) clearTimeout(timer);
1059
1090
  this.recoveryRetryTimers.clear();
1091
+ for (const [jobId] of this.shellCommandJobMonitors) this.stopShellCommandJobMonitor(jobId);
1060
1092
  clearInterval(this.recoveryActivityHeartbeat);
1061
1093
  }
1062
1094
 
@@ -1700,11 +1732,14 @@ export class ProxySessionController {
1700
1732
  }
1701
1733
  switch (command.type) {
1702
1734
  case "shell":
1703
- await this.runShellCommand(contextKey, command.command);
1735
+ await this.runShellCommand(contextKey, command.command, messageId);
1704
1736
  return;
1705
1737
  case "dir":
1706
1738
  await this.openDirectoryBrowser(contextKey, command.directory);
1707
1739
  return;
1740
+ case "file":
1741
+ await this.sendCurrentTaskFile(contextKey, command.filePath);
1742
+ return;
1708
1743
  case "new":
1709
1744
  if (command.projectless && this.ensureAgent(context.defaultAgent).kind !== "app-server") {
1710
1745
  throw new Error("/new --nodir 仅支持 App Server Agent。");
@@ -1872,6 +1907,7 @@ export class ProxySessionController {
1872
1907
  }
1873
1908
 
1874
1909
  private async runStartupRecovery(): Promise<void> {
1910
+ await this.recoverShellCommandJobs();
1875
1911
  this.backfillTurnAttemptsForUpgrade();
1876
1912
  const attempts = this.store.listIncompleteTurnAttempts();
1877
1913
  const recoveryCutoff = Date.now() - RECOVERY_ACTIVITY_WINDOW_MS;
@@ -4510,106 +4546,155 @@ export class ProxySessionController {
4510
4546
  }
4511
4547
  }
4512
4548
 
4513
- private async runShellCommand(contextKey: string, command: string): Promise<void> {
4549
+ private async runShellCommand(
4550
+ contextKey: string,
4551
+ command: string,
4552
+ sourceMessageId?: string,
4553
+ ): Promise<void> {
4514
4554
  const cwd = this.currentSession(contextKey)?.cwd ?? this.config.defaults.cwd;
4515
- const startedAt = Date.now();
4516
- let latestOutput: ShellCommandOutputSnapshot = {
4517
- stdout: "",
4518
- stderr: "",
4519
- outputTruncated: false,
4520
- };
4521
- let runningState: ShellCommandCardView = {
4555
+ const created = await this.shellCommandJobs.createJob({ contextKey, sourceMessageId, command, cwd });
4556
+ let cardMessageId: string | undefined;
4557
+ const initialState: ShellCommandCardView = {
4558
+ jobId: created.id,
4559
+ contextKey,
4522
4560
  command,
4523
4561
  cwd,
4524
- ...latestOutput,
4562
+ output: "",
4563
+ outputTruncated: false,
4525
4564
  status: "running",
4526
4565
  elapsedMs: 0,
4527
4566
  };
4528
- const cardMessageId = await this.outbound.sendInteractiveCard(
4529
- contextKey,
4530
- this.cardRenderer.renderShellCommandCard(runningState),
4531
- );
4532
- const scheduler = cardMessageId
4533
- ? new CardUpdateScheduler<ShellCommandCardView>({
4534
- render: (state) => this.cardRenderer.renderShellCommandCard(state),
4535
- write: (card) => this.outbound.updateInteractiveCard(contextKey, cardMessageId, card),
4536
- onError: (error) => this.logger.warn(
4537
- { error, contextKey, messageId: cardMessageId },
4538
- "Failed to update a shell command card.",
4539
- ),
4540
- })
4541
- : undefined;
4542
- scheduler?.seed(runningState);
4543
- const elapsedTimer = scheduler ? setInterval(() => {
4544
- runningState = { ...runningState, elapsedMs: Date.now() - startedAt };
4545
- scheduler.update(runningState);
4546
- }, SHELL_COMMAND_ELAPSED_UPDATE_INTERVAL_MS) : undefined;
4547
- elapsedTimer?.unref?.();
4548
-
4549
- const updateRunningOutput = (snapshot: ShellCommandOutputSnapshot): void => {
4550
- latestOutput = snapshot;
4551
- runningState = {
4552
- ...runningState,
4553
- ...snapshot,
4554
- elapsedMs: Date.now() - startedAt,
4555
- };
4556
- scheduler?.update(runningState);
4557
- };
4558
-
4559
- let result: ShellCommandResult;
4560
4567
  try {
4561
- result = await this.shellCommandExecutor(command, cwd, { onOutput: updateRunningOutput });
4568
+ cardMessageId = await this.outbound.sendInteractiveCard(
4569
+ contextKey,
4570
+ this.cardRenderer.renderShellCommandCard(initialState),
4571
+ );
4572
+ if (!cardMessageId) throw new Error("Failed to create the shell command card.");
4573
+ const bound = await this.shellCommandJobs.bindCard(created.id, cardMessageId);
4574
+ await this.shellCommandJobs.startJob(created.id);
4575
+ this.monitorShellCommandJob(bound, initialState);
4562
4576
  } catch (error) {
4563
- if (elapsedTimer) clearInterval(elapsedTimer);
4564
- const failedCard = this.cardRenderer.renderShellCommandCard({
4565
- command,
4566
- cwd,
4567
- ...latestOutput,
4568
- stderr: [latestOutput.stderr.trimEnd(), runtimeErrorMessage(error)].filter(Boolean).join("\n"),
4569
- status: "failed",
4570
- elapsedMs: Date.now() - startedAt,
4571
- });
4572
- if (scheduler) {
4577
+ await this.shellCommandJobs.failJob(created.id, runtimeErrorMessage(error)).catch(() => undefined);
4578
+ if (cardMessageId) {
4573
4579
  try {
4574
- await scheduler.flush({
4575
- ...runningState,
4576
- stderr: [latestOutput.stderr.trimEnd(), runtimeErrorMessage(error)].filter(Boolean).join("\n"),
4577
- status: "failed",
4578
- elapsedMs: Date.now() - startedAt,
4579
- });
4580
+ const failed = await this.shellCommandJobs.readJob(created.id);
4581
+ await this.outbound.updateInteractiveCard(
4582
+ contextKey,
4583
+ cardMessageId,
4584
+ this.cardRenderer.renderShellCommandCard(shellCommandJobCardView(failed)),
4585
+ );
4586
+ await this.shellCommandJobs.markPresented(created.id);
4580
4587
  } catch (cardError) {
4581
4588
  this.logger.warn(
4582
- { error: cardError, contextKey, messageId: cardMessageId },
4583
- "Failed to finalize a shell command card.",
4589
+ { error: cardError, jobId: created.id, contextKey, messageId: cardMessageId },
4590
+ "Failed to finalize a shell command card after the runner failed to start.",
4584
4591
  );
4585
- } finally {
4586
- scheduler.dispose();
4587
4592
  }
4588
4593
  } else {
4589
- await this.outbound.sendInteractiveCard(contextKey, failedCard);
4594
+ await this.shellCommandJobs.markPresented(created.id).catch(() => undefined);
4590
4595
  }
4591
4596
  throw error;
4592
4597
  }
4598
+ }
4593
4599
 
4594
- if (elapsedTimer) clearInterval(elapsedTimer);
4595
- const finalState: ShellCommandCardView = {
4596
- command,
4597
- cwd,
4598
- stdout: result.stdout,
4599
- stderr: result.stderr,
4600
- status: result.timedOut ? "timed_out" : result.exitCode === 0 ? "completed" : "failed",
4601
- exitCode: result.exitCode,
4602
- elapsedMs: Date.now() - startedAt,
4603
- outputTruncated: result.outputTruncated,
4600
+ private monitorShellCommandJob(
4601
+ job: ShellCommandJobSnapshot,
4602
+ seededState?: ShellCommandCardView,
4603
+ ): void {
4604
+ if (!job.cardMessageId || this.shellCommandJobMonitors.has(job.id)) return;
4605
+ const scheduler = new CardUpdateScheduler<ShellCommandCardView>({
4606
+ render: (state) => this.cardRenderer.renderShellCommandCard(state),
4607
+ write: (card) => this.outbound.updateInteractiveCard(job.contextKey, job.cardMessageId!, card),
4608
+ onError: (error) => this.logger.warn(
4609
+ { error, jobId: job.id, contextKey: job.contextKey, messageId: job.cardMessageId },
4610
+ "Failed to update a background shell command card.",
4611
+ ),
4612
+ });
4613
+ if (seededState) scheduler.seed(seededState);
4614
+ const monitor: ShellCommandJobMonitor = {
4615
+ scheduler,
4616
+ refreshing: false,
4617
+ timer: setInterval(() => {
4618
+ void this.refreshShellCommandJob(job.id);
4619
+ }, SHELL_COMMAND_JOB_POLL_INTERVAL_MS),
4604
4620
  };
4605
- if (scheduler) {
4606
- try {
4607
- await scheduler.flush(finalState);
4608
- } finally {
4609
- scheduler.dispose();
4621
+ monitor.timer.unref?.();
4622
+ this.shellCommandJobMonitors.set(job.id, monitor);
4623
+ void this.refreshShellCommandJob(job.id, seededState === undefined);
4624
+ }
4625
+
4626
+ private async refreshShellCommandJob(jobId: string, critical = false): Promise<void> {
4627
+ const monitor = this.shellCommandJobMonitors.get(jobId);
4628
+ if (!monitor || monitor.refreshing) return;
4629
+ monitor.refreshing = true;
4630
+ try {
4631
+ const job = await this.shellCommandJobs.readJob(jobId);
4632
+ const view = shellCommandJobCardView(job);
4633
+ if (isActiveShellCommandJob(job.status)) {
4634
+ monitor.scheduler.update(view, critical ? "critical" : "normal");
4635
+ return;
4610
4636
  }
4611
- } else {
4612
- await this.outbound.sendInteractiveCard(contextKey, this.cardRenderer.renderShellCommandCard(finalState));
4637
+ await monitor.scheduler.flush(view);
4638
+ if (job.sourceMessageId) {
4639
+ await this.finalizeStandaloneMessageReaction(
4640
+ job.sourceMessageId,
4641
+ job.status === "completed" ? "completed" : job.status === "cancelled" ? "cancelled" : "failed",
4642
+ );
4643
+ }
4644
+ await this.shellCommandJobs.markPresented(jobId);
4645
+ this.stopShellCommandJobMonitor(jobId);
4646
+ } catch (error) {
4647
+ this.logger.warn({ error, jobId }, "Failed to refresh a background shell command job.");
4648
+ } finally {
4649
+ const current = this.shellCommandJobMonitors.get(jobId);
4650
+ if (current) current.refreshing = false;
4651
+ }
4652
+ }
4653
+
4654
+ private stopShellCommandJobMonitor(jobId: string): void {
4655
+ const monitor = this.shellCommandJobMonitors.get(jobId);
4656
+ if (!monitor) return;
4657
+ clearInterval(monitor.timer);
4658
+ monitor.scheduler.dispose();
4659
+ this.shellCommandJobMonitors.delete(jobId);
4660
+ }
4661
+
4662
+ private async cancelShellCommandJob(contextKey: string, jobId: string): Promise<void> {
4663
+ if (!jobId) throw new Error("命令任务无效,请使用最新的命令卡片。");
4664
+ const job = await this.shellCommandJobs.readJob(jobId);
4665
+ if (job.contextKey !== contextKey) throw new Error("该命令不属于当前会话。");
4666
+ const requested = await this.shellCommandJobs.requestCancellation(jobId);
4667
+ if (!requested) {
4668
+ await this.outbound.sendText(contextKey, "该命令已经结束。");
4669
+ return;
4670
+ }
4671
+ if (!this.shellCommandJobMonitors.has(jobId)) this.monitorShellCommandJob(job);
4672
+ await this.refreshShellCommandJob(jobId, true);
4673
+ }
4674
+
4675
+ private async recoverShellCommandJobs(): Promise<void> {
4676
+ const jobs = await this.shellCommandJobs.listRecoverableJobs();
4677
+ if (jobs.length > 0) {
4678
+ this.logger.warn(
4679
+ { jobIds: jobs.map((job) => job.id) },
4680
+ "Recovering background shell command jobs after startup.",
4681
+ );
4682
+ }
4683
+ for (const job of jobs) {
4684
+ if (!job.cardMessageId || !this.outbound.canRoute(job.contextKey)) continue;
4685
+ if (
4686
+ job.status === "starting"
4687
+ && job.runnerPid === undefined
4688
+ && Date.now() - job.updatedAt >= SHELL_COMMAND_JOB_RELAUNCH_DELAY_MS
4689
+ ) {
4690
+ try {
4691
+ await this.shellCommandJobs.startJob(job.id);
4692
+ } catch (error) {
4693
+ await this.shellCommandJobs.failJob(job.id, runtimeErrorMessage(error)).catch(() => undefined);
4694
+ }
4695
+ }
4696
+ const refreshed = await this.shellCommandJobs.readJob(job.id);
4697
+ this.monitorShellCommandJob(refreshed);
4613
4698
  }
4614
4699
  }
4615
4700
 
@@ -4990,6 +5075,13 @@ export class ProxySessionController {
4990
5075
  if (!stats.isFile()) throw new Error(`这不是普通文件:${filePath}`);
4991
5076
  }
4992
5077
 
5078
+ private async sendCurrentTaskFile(contextKey: string, requestedFilePath: string): Promise<void> {
5079
+ const record = this.requireCurrentSession(contextKey);
5080
+ const filePath = resolveUserPath(requestedFilePath, record.cwd);
5081
+ await this.assertSendableFile(filePath);
5082
+ await this.outbound.sendFile(contextKey, filePath);
5083
+ }
5084
+
4993
5085
  private async listSessions(
4994
5086
  contextKey: string,
4995
5087
  searchTerm?: string,
@@ -6505,14 +6597,29 @@ function isBotOwnedActiveTurn(record: SessionRecord, remote: RemoteSessionSummar
6505
6597
  }
6506
6598
 
6507
6599
  function isQueueIndependentCommand(command: Command): boolean {
6508
- if (["archive", "dismiss", "stop", "status", "restart", "mute", "help", "sessions", "dir", "goal", "nosteer"].includes(command.type)) return true;
6600
+ if (["archive", "dismiss", "stop", "status", "restart", "mute", "help", "sessions", "dir", "file", "goal", "nosteer", "shell"].includes(command.type)) return true;
6509
6601
  if (command.type === "agent") return command.agent === undefined;
6510
6602
  if (["model", "provider", "thinking", "permissions"].includes(command.type)) return true;
6511
6603
  return false;
6512
6604
  }
6513
6605
 
6606
+ function shellCommandJobCardView(job: ShellCommandJobSnapshot): ShellCommandCardView {
6607
+ const status = job.status === "starting" ? "running" : job.status;
6608
+ return {
6609
+ jobId: job.id,
6610
+ contextKey: job.contextKey,
6611
+ command: job.command,
6612
+ cwd: job.cwd,
6613
+ output: [job.output.trimEnd(), job.error].filter(Boolean).join("\n"),
6614
+ status,
6615
+ exitCode: job.exitCode,
6616
+ elapsedMs: Math.max(0, (job.completedAt ?? Date.now()) - job.startedAt),
6617
+ outputTruncated: job.outputTruncated,
6618
+ };
6619
+ }
6620
+
6514
6621
  function commandRequiresCurrentSession(command: Command): boolean {
6515
- if (["dismiss", "stop", "title", "turns", "model", "provider", "thinking", "permissions", "nosteer"].includes(command.type)) {
6622
+ if (["dismiss", "stop", "title", "turns", "file", "model", "provider", "thinking", "permissions", "nosteer"].includes(command.type)) {
6516
6623
  return true;
6517
6624
  }
6518
6625
  if (command.type === "archive" || command.type === "fork") return command.sessionId === undefined;
@@ -6696,8 +6803,8 @@ async function listWindowsDriveRoots(): Promise<WindowsDriveInfo[]> {
6696
6803
  return drives;
6697
6804
  }
6698
6805
 
6699
- function isPromptCommand(command: Command): boolean {
6700
- return command.type === "prompt" || command.type === "nosteer";
6806
+ function commandDefersReactionFinalization(command: Command): boolean {
6807
+ return command.type === "prompt" || command.type === "nosteer" || command.type === "shell";
6701
6808
  }
6702
6809
 
6703
6810
  function runtimePrompt(text: string, localImagePaths?: string[]): RuntimePrompt {