@agimon-ai/po-runner 0.0.0-alpha.72 → 0.0.0-alpha.74

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.
@@ -1,5 +1,5 @@
1
- import { createApiClient, defineActivityGroup, defineChannelFile, defineFill, defineNativePlugin, defineSessionStore, nativeApiTransport, sessionApiAddress } from "@agimon-ai/po-core/native";
2
- import { ArrowRightIcon, Button, Dot, FolderIcon, Input, ListRow, LiveDuration, PlayCircleIcon, SearchIcon, Sheet, SheetBody, SheetContent, SheetFooter, SheetHeader, SheetTitle, StatusBadge, fontOf } from "@agimon-ai/po-native-components";
1
+ import { createApiClient, defineActivityGroup, defineChannelFile, defineFill, defineNativePlugin, defineSessionStore, defineToolRenderer, nativeApiTransport, sessionApiAddress } from "@agimon-ai/po-core/native";
2
+ import { ArrowRightIcon, Button, Dot, FileIcon, FolderIcon, Input, ListRow, LiveDuration, MessageItem, MessageItemBody, MessageItemHeader, MessageItemStatus, PlayCircleIcon, SearchIcon, Sheet, SheetBody, SheetContent, SheetFooter, SheetHeader, SheetTitle, StatusBadge, SyntaxLine, ansiSpans, fontOf, toolTone, useSyntaxLines } from "@agimon-ai/po-native-components";
3
3
  import { createElement, memo, useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
4
4
  import { View } from "react-native-unistyles/components/native/View";
5
5
  import { Text } from "react-native-unistyles/components/native/Text";
@@ -111,7 +111,7 @@ const runnerActivitySource = {
111
111
  return runners.select(runners.store.state, sessionId).runs.some((run) => isRunnerActivityVisible(run));
112
112
  }
113
113
  };
114
- function isRecord$1(value) {
114
+ function isRecord$2(value) {
115
115
  return typeof value === "object" && value !== null && !Array.isArray(value);
116
116
  }
117
117
  /**
@@ -147,12 +147,12 @@ function requestRunnerStart(send, sessionId, request) {
147
147
  const runnerRunsChannel = runners.channel({
148
148
  channel: RUNNER_RUNS_TYPE,
149
149
  parse(input) {
150
- if (!isRecord$1(input) || !Array.isArray(input.runs)) return null;
150
+ if (!isRecord$2(input) || !Array.isArray(input.runs)) return null;
151
151
  const failure = input.startFailure;
152
152
  return {
153
- runs: input.runs.filter(isRecord$1),
153
+ runs: input.runs.filter(isRecord$2),
154
154
  ...typeof input.sessionDirectory === "string" ? { sessionDirectory: input.sessionDirectory } : {},
155
- ...isRecord$1(failure) && typeof failure.id === "string" && typeof failure.message === "string" ? { startFailure: {
155
+ ...isRecord$2(failure) && typeof failure.id === "string" && typeof failure.message === "string" ? { startFailure: {
156
156
  id: failure.id,
157
157
  message: failure.message
158
158
  } } : {}
@@ -259,12 +259,12 @@ function RunnerCommandBar({ command, onCommandChange, cwd, onCwdChange, sessionD
259
259
  };
260
260
  const choices = ["", ...runnerFolders(folders, cwd, sessionDirectory)];
261
261
  return /* @__PURE__ */ jsxs(View, {
262
- style: styles$3.bar,
262
+ style: styles$4.bar,
263
263
  children: [
264
264
  /* @__PURE__ */ jsxs(View, {
265
- style: styles$3.line,
265
+ style: styles$4.line,
266
266
  children: [/* @__PURE__ */ jsx(View, {
267
- style: styles$3.grow,
267
+ style: styles$4.grow,
268
268
  children: /* @__PURE__ */ jsx(Input, {
269
269
  testID: "runners-panel-command",
270
270
  accessibilityLabel: "Command to run",
@@ -297,12 +297,12 @@ function RunnerCommandBar({ command, onCommandChange, cwd, onCwdChange, sessionD
297
297
  onPress: () => setFolderOpen(true)
298
298
  }),
299
299
  problem === void 0 ? /* @__PURE__ */ jsx(Text, {
300
- style: styles$3.meta,
300
+ style: styles$4.meta,
301
301
  children: "Leave the command empty for a plain shell."
302
302
  }) : /* @__PURE__ */ jsx(Text, {
303
303
  accessibilityRole: "alert",
304
304
  testID: "runners-panel-problem",
305
- style: styles$3.problem,
305
+ style: styles$4.problem,
306
306
  children: problem
307
307
  }),
308
308
  /* @__PURE__ */ jsx(Sheet, {
@@ -337,7 +337,7 @@ function RunnerCommandBar({ command, onCommandChange, cwd, onCwdChange, sessionD
337
337
  ]
338
338
  });
339
339
  }
340
- const styles$3 = StyleSheet.create((theme) => ({
340
+ const styles$4 = StyleSheet.create((theme) => ({
341
341
  bar: {
342
342
  gap: theme.spacing(1.5),
343
343
  uni__dependencies: [0]
@@ -462,7 +462,7 @@ const api = createApiClient(routes, {
462
462
  //#endregion
463
463
  //#region src/extensions/workspaces/sessions/(frontend)/_lib/logApi.native.ts
464
464
  const UNREACHABLE = "The cockpit hub is unreachable.";
465
- function isRecord(value) {
465
+ function isRecord$1(value) {
466
466
  return typeof value === "object" && value !== null && !Array.isArray(value);
467
467
  }
468
468
  /** Reads one runner's log: the last N lines, or the lines matching a substring with context. */
@@ -481,7 +481,7 @@ async function fetchRunnerLog(sessionId, runId, params = {}, signal) {
481
481
  if (result.status === 0) return { error: signal?.aborted === true ? "" : UNREACHABLE };
482
482
  return { error: result.error === "" ? `The hub answered ${String(result.status)}.` : result.error };
483
483
  }
484
- if (!isRecord(result.data) || typeof result.data.text !== "string") return { error: `The hub answered ${String(result.status)}.` };
484
+ if (!isRecord$1(result.data) || typeof result.data.text !== "string") return { error: `The hub answered ${String(result.status)}.` };
485
485
  return { slice: result.data };
486
486
  }
487
487
  //#endregion
@@ -545,18 +545,18 @@ function RunnerRunHeader({ run, name, sessionDirectory, action }) {
545
545
  /** A long command would eat a phone screen, so it starts as one truncated line. */
546
546
  const [commandOpen, setCommandOpen] = useState(false);
547
547
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(View, {
548
- style: styles$2.header,
548
+ style: styles$3.header,
549
549
  children: [
550
550
  /* @__PURE__ */ jsx(Text, {
551
551
  numberOfLines: 1,
552
- style: styles$2.title,
552
+ style: styles$3.title,
553
553
  children: name
554
554
  }),
555
555
  /* @__PURE__ */ jsx(StatusBadge, {
556
556
  tone: badge.tone,
557
557
  children: badge.label
558
558
  }),
559
- /* @__PURE__ */ jsx(View, { style: styles$2.grow }),
559
+ /* @__PURE__ */ jsx(View, { style: styles$3.grow }),
560
560
  action
561
561
  ]
562
562
  }), run ? /* @__PURE__ */ jsxs(Pressable, {
@@ -564,23 +564,23 @@ function RunnerRunHeader({ run, name, sessionDirectory, action }) {
564
564
  accessibilityRole: "button",
565
565
  accessibilityState: { expanded: commandOpen },
566
566
  accessibilityHint: commandOpen ? "Collapses the command" : "Shows the full command",
567
- style: styles$2.commandBox,
567
+ style: styles$3.commandBox,
568
568
  onPress: () => setCommandOpen((open) => !open),
569
569
  children: [/* @__PURE__ */ jsxs(Text, {
570
570
  selectable: commandOpen,
571
571
  numberOfLines: commandOpen ? void 0 : 1,
572
- style: styles$2.command,
572
+ style: styles$3.command,
573
573
  children: [/* @__PURE__ */ jsx(Text, {
574
- style: styles$2.prompt,
574
+ style: styles$3.prompt,
575
575
  children: "$ "
576
576
  }), run.command]
577
577
  }), commandOpen ? /* @__PURE__ */ jsxs(Text, {
578
578
  numberOfLines: 1,
579
- style: styles$2.folder,
579
+ style: styles$3.folder,
580
580
  children: ["in ", runnerFolderLabel(run.cwd, sessionDirectory)]
581
581
  }) : null]
582
582
  }) : /* @__PURE__ */ jsx(Text, {
583
- style: styles$2.meta,
583
+ style: styles$3.meta,
584
584
  children: "No longer listed; its log stays readable."
585
585
  })] });
586
586
  }
@@ -660,7 +660,7 @@ function RunnerLogPanel({ sessionId, runId, sendSessionFrame }) {
660
660
  const live = following && query === "" && slice?.running === true;
661
661
  return /* @__PURE__ */ jsxs(View, {
662
662
  testID: "runner-log-panel",
663
- style: styles$2.panel,
663
+ style: styles$3.panel,
664
664
  children: [
665
665
  /* @__PURE__ */ jsx(RunnerRunHeader, {
666
666
  run,
@@ -684,7 +684,7 @@ function RunnerLogPanel({ sessionId, runId, sendSessionFrame }) {
684
684
  returnKeyType: "search"
685
685
  }),
686
686
  /* @__PURE__ */ jsxs(View, {
687
- style: styles$2.actions,
687
+ style: styles$3.actions,
688
688
  children: [
689
689
  /* @__PURE__ */ jsx(Button, {
690
690
  variant: ignoreCase ? "subtle" : "outline",
@@ -718,7 +718,7 @@ function RunnerLogPanel({ sessionId, runId, sendSessionFrame }) {
718
718
  selectable: true,
719
719
  numberOfLines: 1,
720
720
  ellipsizeMode: "middle",
721
- style: styles$2.path,
721
+ style: styles$3.path,
722
722
  children: run?.logPath ?? slice?.path ?? ""
723
723
  })
724
724
  ]
@@ -742,30 +742,30 @@ const RunnerLogOutput = memo(function RunnerLogOutput({ slice, error, query, liv
742
742
  ]);
743
743
  const renderItem = useCallback(({ item }) => /* @__PURE__ */ jsxs(Text, {
744
744
  selectable: true,
745
- style: styles$2.line,
745
+ style: styles$3.line,
746
746
  children: [/* @__PURE__ */ jsx(Text, {
747
- style: styles$2.number,
747
+ style: styles$3.number,
748
748
  children: item.number
749
749
  }), item.text]
750
750
  }), []);
751
751
  const keyExtractor = useCallback((item) => item.key, []);
752
752
  const empty = error !== "" ? /* @__PURE__ */ jsx(Text, {
753
753
  accessibilityRole: "alert",
754
- style: styles$2.error,
754
+ style: styles$3.error,
755
755
  children: error
756
756
  }) : slice?.exists === false ? /* @__PURE__ */ jsx(Text, {
757
- style: styles$2.meta,
757
+ style: styles$3.meta,
758
758
  children: "This runner has not written a log yet."
759
759
  }) : slice ? /* @__PURE__ */ jsx(Text, {
760
- style: styles$2.meta,
760
+ style: styles$3.meta,
761
761
  children: query === "" ? "The log is empty." : "Nothing matched."
762
762
  }) : null;
763
763
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Text, {
764
- style: styles$2.meta,
764
+ style: styles$3.meta,
765
765
  children: slice === void 0 ? error === "" ? "Reading…" : "" : query !== "" ? `${slice.lineCount} matching of ${slice.totalLines} lines` : `${live ? "Tailing" : "Showing"} last ${lines.length} of ${slice.totalLines} lines`
766
766
  }), /* @__PURE__ */ jsx(FlatList, {
767
- style: styles$2.log,
768
- contentContainerStyle: styles$2.logBody,
767
+ style: styles$3.log,
768
+ contentContainerStyle: styles$3.logBody,
769
769
  data: error !== "" || slice?.exists === false ? [] : rows,
770
770
  renderItem,
771
771
  keyExtractor,
@@ -776,7 +776,7 @@ const RunnerLogOutput = memo(function RunnerLogOutput({ slice, error, query, liv
776
776
  accessibilityLabel: "Runner log output"
777
777
  })] });
778
778
  });
779
- const styles$2 = StyleSheet.create((theme) => ({
779
+ const styles$3 = StyleSheet.create((theme) => ({
780
780
  panel: {
781
781
  flex: 1,
782
782
  gap: theme.spacing(2),
@@ -922,7 +922,7 @@ function RunnerShellPanel({ sessionId, runId, renderRunnerTerminal, sendSessionF
922
922
  write: (text) => sendRunnerInput(sessionId, runId, text)
923
923
  }, [sessionId, runId]);
924
924
  return /* @__PURE__ */ jsxs(View, {
925
- style: styles$1.panel,
925
+ style: styles$2.panel,
926
926
  children: [
927
927
  /* @__PURE__ */ jsx(RunnerRunHeader, {
928
928
  run,
@@ -938,32 +938,32 @@ function RunnerShellPanel({ sessionId, runId, renderRunnerTerminal, sendSessionF
938
938
  }) : null
939
939
  }),
940
940
  /* @__PURE__ */ jsx(View, {
941
- style: styles$1.terminal,
941
+ style: styles$2.terminal,
942
942
  children: request === void 0 ? /* @__PURE__ */ jsx(Text, {
943
- style: styles$1.meta,
943
+ style: styles$2.meta,
944
944
  children: "Select a session to attach to this runner."
945
945
  }) : renderRunnerTerminal?.(request)
946
946
  }),
947
947
  /* @__PURE__ */ jsxs(View, {
948
- style: styles$1.actions,
948
+ style: styles$2.actions,
949
949
  children: [run ? /* @__PURE__ */ jsxs(Button, {
950
950
  testID: "runner-shell-search",
951
951
  variant: "outline",
952
952
  onPress: () => openTransientTab(runnerLogTab(run)),
953
- style: styles$1.action,
953
+ style: styles$2.action,
954
954
  children: [/* @__PURE__ */ jsx(SearchIcon, { size: 14 }), "Search output"]
955
955
  }) : null, live && sessionId !== null ? /* @__PURE__ */ jsx(Button, {
956
956
  testID: "runner-shell-interrupt",
957
957
  variant: "outline",
958
958
  onPress: () => void sendRunnerInput(sessionId, runId, ""),
959
- style: styles$1.action,
959
+ style: styles$2.action,
960
960
  children: "Ctrl-C"
961
961
  }) : null]
962
962
  })
963
963
  ]
964
964
  });
965
965
  }
966
- const styles$1 = StyleSheet.create((theme) => ({
966
+ const styles$2 = StyleSheet.create((theme) => ({
967
967
  panel: {
968
968
  flex: 1,
969
969
  gap: theme.spacing(2),
@@ -1005,9 +1005,9 @@ function RunnerRow({ run, props, stopping }) {
1005
1005
  accessibilityRole: "button",
1006
1006
  accessibilityLabel: `Open ${run.name}`,
1007
1007
  onPress: () => props.openTransientTab(runnerTab(run)),
1008
- style: ({ pressed }) => [styles.row, pressed && styles.pressed],
1008
+ style: ({ pressed }) => [styles$1.row, pressed && styles$1.pressed],
1009
1009
  children: [/* @__PURE__ */ jsxs(View, {
1010
- style: styles.line,
1010
+ style: styles$1.line,
1011
1011
  children: [
1012
1012
  /* @__PURE__ */ jsx(Dot, {
1013
1013
  tone: "yellow",
@@ -1015,7 +1015,7 @@ function RunnerRow({ run, props, stopping }) {
1015
1015
  }),
1016
1016
  /* @__PURE__ */ jsx(Text, {
1017
1017
  numberOfLines: 1,
1018
- style: styles.name,
1018
+ style: styles$1.name,
1019
1019
  children: run.name
1020
1020
  }),
1021
1021
  /* @__PURE__ */ jsx(RunnerUptime, { startedAt: run.startedAt }),
@@ -1032,7 +1032,7 @@ function RunnerRow({ run, props, stopping }) {
1032
1032
  ]
1033
1033
  }), /* @__PURE__ */ jsx(Text, {
1034
1034
  numberOfLines: 1,
1035
- style: styles.tail,
1035
+ style: styles$1.tail,
1036
1036
  children: tail ?? `$ ${run.command}`
1037
1037
  })]
1038
1038
  });
@@ -1042,18 +1042,18 @@ function RunnersActivitySection(props) {
1042
1042
  useSyncExternalStore(runnerActivitySource.subscribe, () => session.runs.filter((run) => isRunnerActivityVisible(run)).length);
1043
1043
  const running = session.runs.filter((run) => isRunnerActivityVisible(run));
1044
1044
  return running.length === 0 ? /* @__PURE__ */ jsxs(View, {
1045
- style: styles.idle,
1045
+ style: styles$1.idle,
1046
1046
  children: [/* @__PURE__ */ jsx(Text, {
1047
- style: styles.meta,
1047
+ style: styles$1.meta,
1048
1048
  children: "Idle"
1049
1049
  }), props.sessionId !== null ? /* @__PURE__ */ jsxs(Pressable, {
1050
1050
  accessibilityRole: "button",
1051
1051
  testID: "activity-runner-launch",
1052
1052
  hitSlop: 10,
1053
1053
  onPress: () => props.openTransientTab(runnersTab()),
1054
- style: styles.launch,
1054
+ style: styles$1.launch,
1055
1055
  children: [/* @__PURE__ */ jsx(Text, {
1056
- style: styles.launchText,
1056
+ style: styles$1.launchText,
1057
1057
  children: "Launch a runner"
1058
1058
  }), /* @__PURE__ */ jsx(ArrowRightIcon, {
1059
1059
  size: 14,
@@ -1110,15 +1110,15 @@ function RunnersPanel(props) {
1110
1110
  setCommand("");
1111
1111
  };
1112
1112
  return /* @__PURE__ */ jsxs(View, {
1113
- style: styles.panel,
1113
+ style: styles$1.panel,
1114
1114
  children: [
1115
1115
  /* @__PURE__ */ jsxs(View, {
1116
- style: styles.header,
1116
+ style: styles$1.header,
1117
1117
  children: [/* @__PURE__ */ jsx(Text, {
1118
- style: styles.title,
1118
+ style: styles$1.title,
1119
1119
  children: "Runners"
1120
1120
  }), /* @__PURE__ */ jsx(Text, {
1121
- style: styles.meta,
1121
+ style: styles$1.meta,
1122
1122
  children: running.length === 0 ? "Nothing running" : `${running.length} running`
1123
1123
  })]
1124
1124
  }),
@@ -1133,7 +1133,7 @@ function RunnersPanel(props) {
1133
1133
  onLaunch: launch
1134
1134
  }) : null,
1135
1135
  /* @__PURE__ */ jsx(ScrollView, { children: running.length === 0 ? /* @__PURE__ */ jsx(Text, {
1136
- style: styles.meta,
1136
+ style: styles$1.meta,
1137
1137
  children: props.sessionId === null ? "Open a session to start a runner." : "Nothing running yet. Type a command above, like pnpm dev, and it opens as a live terminal."
1138
1138
  }) : running.map((run) => /* @__PURE__ */ jsx(ListRow, {
1139
1139
  leading: /* @__PURE__ */ jsx(Dot, {
@@ -1142,9 +1142,9 @@ function RunnersPanel(props) {
1142
1142
  }),
1143
1143
  title: run.name,
1144
1144
  description: /* @__PURE__ */ jsxs(View, {
1145
- style: styles.line,
1145
+ style: styles$1.line,
1146
1146
  children: [/* @__PURE__ */ jsxs(Text, {
1147
- style: styles.meta,
1147
+ style: styles$1.meta,
1148
1148
  children: [run.command, " · "]
1149
1149
  }), /* @__PURE__ */ jsx(RunnerUptime, { startedAt: run.startedAt })]
1150
1150
  }),
@@ -1158,10 +1158,10 @@ function RunnerUptime({ startedAt }) {
1158
1158
  return /* @__PURE__ */ jsx(LiveDuration, {
1159
1159
  label: "running",
1160
1160
  since: Date.parse(startedAt),
1161
- style: styles.meta
1161
+ style: styles$1.meta
1162
1162
  });
1163
1163
  }
1164
- const styles = StyleSheet.create((theme) => ({
1164
+ const styles$1 = StyleSheet.create((theme) => ({
1165
1165
  panel: {
1166
1166
  flex: 1,
1167
1167
  gap: theme.spacing(3),
@@ -1261,6 +1261,421 @@ var Runners_activity_runners_native_default = defineFill({
1261
1261
  component: RunnersActivitySection
1262
1262
  });
1263
1263
  //#endregion
1264
+ //#region src/extensions/workspaces/sessions/(frontend)/tool/_lib/bashToolFormat.ts
1265
+ const ELLIPSIS = "…";
1266
+ /** Spaced so the join never glues two words into one unreadable token. */
1267
+ const GAP = " … ";
1268
+ /** Wide enough to keep a realistic command intact, short enough to stay on a few rows. */
1269
+ const MAX_COMMAND_LENGTH = 160;
1270
+ /** Live output is unbounded while a command runs, so the running state keeps only a window. */
1271
+ const STREAM_TAIL_LINES = 12;
1272
+ const BYTES_PER_KB = 1024;
1273
+ const BYTES_PER_MB = BYTES_PER_KB * BYTES_PER_KB;
1274
+ /** The user's home on the two platforms the runner ships for; the browser cannot ask the OS. */
1275
+ const HOME_PATH = /(?<![\w.-])(?:\/Users|\/home)\/[^/\s]+(?=\/|\s|$)/g;
1276
+ function isRecord(value) {
1277
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1278
+ }
1279
+ /** Narrows the result details to the fields the card reads; anything else is ignored. */
1280
+ function bashResultDetails(details) {
1281
+ if (!isRecord(details)) return {};
1282
+ const out = {};
1283
+ if (typeof details.id === "string") out.id = details.id;
1284
+ if (typeof details.runner === "string") out.runner = details.runner;
1285
+ if (typeof details.exitCode === "number" || details.exitCode === null) out.exitCode = details.exitCode;
1286
+ if (typeof details.logPath === "string") out.logPath = details.logPath;
1287
+ if (typeof details.fileSize === "number") out.fileSize = details.fileSize;
1288
+ if (typeof details.lines === "number") out.lines = details.lines;
1289
+ if (details.timedOut === true) out.timedOut = true;
1290
+ if (details.promoted === true) out.promoted = true;
1291
+ if (typeof details.reason === "string") out.reason = details.reason;
1292
+ if (typeof details.tail === "string") out.tail = details.tail;
1293
+ if (typeof details.tailLines === "number") out.tailLines = details.tailLines;
1294
+ if (typeof details.sequenceId === "string") {
1295
+ out.sequenceId = details.sequenceId;
1296
+ if (Array.isArray(details.stages)) out.stages = details.stages.flatMap((stage) => {
1297
+ if (!isRecord(stage) || !Number.isInteger(stage.index) || Number(stage.index) < 0 || typeof stage.state !== "string") return [];
1298
+ return [{
1299
+ index: Number(stage.index),
1300
+ state: stage.state,
1301
+ ...typeof stage.logPath === "string" ? { logPath: stage.logPath } : {}
1302
+ }];
1303
+ });
1304
+ }
1305
+ return out;
1306
+ }
1307
+ /** `/Users/me/x` becomes `~/x`. Absolute home paths are what push these lines onto extra rows. */
1308
+ function abbreviateHome(text) {
1309
+ return text.replace(HOME_PATH, "~");
1310
+ }
1311
+ /** Collapse a heredoc or multi-line script to one scannable line plus a count. */
1312
+ function collapseCommand(command) {
1313
+ const lines = command.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
1314
+ const first = lines[0] ?? "";
1315
+ return lines.length > 1 ? `${first} ${ELLIPSIS} +${lines.length - 1} lines` : first;
1316
+ }
1317
+ /** Drop the middle rather than the end, so a long pipeline keeps both its source and its sink. */
1318
+ function truncateMiddle(text, max = MAX_COMMAND_LENGTH) {
1319
+ if (text.length <= max) return text;
1320
+ const keep = max - 3;
1321
+ const head = Math.ceil(keep / 2);
1322
+ let start = text.slice(0, head);
1323
+ let end = text.slice(text.length - (keep - head));
1324
+ const lastSpace = start.lastIndexOf(" ");
1325
+ if (lastSpace > start.length / 2) start = start.slice(0, lastSpace);
1326
+ const firstSpace = end.indexOf(" ");
1327
+ if (firstSpace >= 0 && firstSpace < end.length / 2) end = end.slice(firstSpace + 1);
1328
+ return `${start}${GAP}${end}`;
1329
+ }
1330
+ /** Arrays are separate commands; the expanded body keeps their boundaries visible. */
1331
+ function bashCommandText(command) {
1332
+ if (typeof command === "string") return command;
1333
+ return Array.isArray(command) && command.every((entry) => typeof entry === "string") ? command.join("\n\n") : "";
1334
+ }
1335
+ function formatBashCommand(command) {
1336
+ const text = bashCommandText(command);
1337
+ if (text.length === 0) return "";
1338
+ const first = Array.isArray(command) ? bashCommandText(command[0]) : text;
1339
+ const suffix = Array.isArray(command) && command.length > 1 ? ` ${ELLIPSIS} +${command.length - 1} commands` : "";
1340
+ return truncateMiddle(`${abbreviateHome(collapseCommand(first))}${suffix}`);
1341
+ }
1342
+ /** Human-readable byte count, matching how pi reports output sizes. */
1343
+ function formatSize(bytes) {
1344
+ if (bytes < BYTES_PER_KB) return `${bytes} B`;
1345
+ if (bytes < BYTES_PER_MB) return `${(bytes / BYTES_PER_KB).toFixed(1)} KB`;
1346
+ return `${(bytes / BYTES_PER_MB).toFixed(1)} MB`;
1347
+ }
1348
+ /** The metadata footer, collapsed from five lines to one. */
1349
+ function formatResultSummary(details) {
1350
+ const parts = [];
1351
+ if (details.lines !== void 0) parts.push(`${details.lines.toLocaleString("en-US")} lines`);
1352
+ if (details.fileSize !== void 0) parts.push(formatSize(details.fileSize));
1353
+ if (details.runner !== void 0) parts.push(details.runner);
1354
+ return parts.join(" · ");
1355
+ }
1356
+ /** Last `max` lines, so a chatty command cannot push the transcript off screen. */
1357
+ function lastLines(text, max) {
1358
+ const all = text.split("\n");
1359
+ while (all.length > 0 && all[all.length - 1]?.trim() === "") all.pop();
1360
+ return all.length <= max ? all : all.slice(-max);
1361
+ }
1362
+ function bashResultView(input) {
1363
+ const details = bashResultDetails(input.details);
1364
+ const logs = (details.stages ?? []).flatMap((stage) => stage.logPath === void 0 ? [] : [`stage ${stage.index + 1} · ${stage.state} · ${stage.logPath}`]);
1365
+ if (input.isPartial) return {
1366
+ lines: [...logs, ...lastLines(input.output, STREAM_TAIL_LINES)],
1367
+ status: {
1368
+ glyph: "◐",
1369
+ tone: "running",
1370
+ text: "running"
1371
+ },
1372
+ hidden: 0
1373
+ };
1374
+ if (input.isError) {
1375
+ const all = lastLines(details.sequenceId === void 0 ? input.output : details.tail ?? input.output, Number.MAX_SAFE_INTEGER);
1376
+ const lines = [...logs, ...input.expanded ? all : all.slice(-12)];
1377
+ const summary = formatResultSummary(details);
1378
+ return {
1379
+ lines,
1380
+ status: {
1381
+ glyph: "✗",
1382
+ tone: "error",
1383
+ text: summary.length > 0 ? summary : "failed"
1384
+ },
1385
+ hidden: Math.max(0, all.length + logs.length - lines.length)
1386
+ };
1387
+ }
1388
+ if (details.promoted === true) return {
1389
+ lines: logs,
1390
+ status: {
1391
+ glyph: "●",
1392
+ tone: "background",
1393
+ text: `${details.runner ?? details.id ?? "runner"}${details.id === void 0 ? "" : ` · ${details.id}`} · background`
1394
+ },
1395
+ hidden: 0
1396
+ };
1397
+ const all = lastLines(details.tail ?? (details.sequenceId === void 0 ? "" : input.output), Number.MAX_SAFE_INTEGER);
1398
+ const lines = [...logs, ...input.expanded ? all : all.slice(-12)];
1399
+ const exited = details.exitCode !== void 0 && details.exitCode !== null;
1400
+ const failed = details.timedOut === true || exited && details.exitCode !== 0;
1401
+ const meta = [];
1402
+ if (details.timedOut === true) meta.push("timed out");
1403
+ else if (failed) meta.push(`exit ${details.exitCode}`);
1404
+ const summary = formatResultSummary(details);
1405
+ if (summary.length > 0) meta.push(summary);
1406
+ const text = meta.length > 0 ? meta.join(" · ") : failed ? "failed" : lines.length === 0 ? "done" : "";
1407
+ return {
1408
+ lines,
1409
+ status: text.length > 0 ? {
1410
+ glyph: failed ? "✗" : "✓",
1411
+ tone: failed ? "error" : "ok",
1412
+ text
1413
+ } : null,
1414
+ hidden: Math.max(0, all.length + logs.length - lines.length)
1415
+ };
1416
+ }
1417
+ /**
1418
+ * A promoted command's outcome, read from the runner the channel reports
1419
+ * rather than from the tool call, which returned when the run went to the
1420
+ * background. The card never reads Completed while the runner still runs.
1421
+ */
1422
+ function backgroundOutcome(run) {
1423
+ if (run === void 0) return {
1424
+ tone: "info",
1425
+ badge: "In background"
1426
+ };
1427
+ if (run.exit === void 0) return {
1428
+ tone: "running",
1429
+ badge: "In background"
1430
+ };
1431
+ const { reason, code } = run.exit;
1432
+ const exit = code === null ? "" : ` · exit ${code}`;
1433
+ if (reason === "completed") return {
1434
+ tone: "ok",
1435
+ badge: `Completed${exit}`
1436
+ };
1437
+ if (reason === "stopped") return {
1438
+ tone: "neutral",
1439
+ badge: "Stopped"
1440
+ };
1441
+ return {
1442
+ tone: "error",
1443
+ badge: `Failed${exit}`
1444
+ };
1445
+ }
1446
+ //#endregion
1447
+ //#region src/extensions/workspaces/sessions/(frontend)/tool/_components/BashToolMessage.native.tsx
1448
+ const STATUS_TONE = {
1449
+ running: "running",
1450
+ ok: "ok",
1451
+ error: "error",
1452
+ background: "info"
1453
+ };
1454
+ /** Lets the shell grammar colour the command; the path is only a grammar hint. */
1455
+ const SHELL_HINT = { path: "command.sh" };
1456
+ const ANSI_CLASS_PREFIX = "text-po-";
1457
+ /**
1458
+ * The ANSI span's named colour as a theme token. Exact 256 and truecolour
1459
+ * runs stay in the output's own ink, since the phone keeps to theme tokens.
1460
+ */
1461
+ function ansiToken(className) {
1462
+ return className?.startsWith(ANSI_CLASS_PREFIX) === true ? className.slice(8) : void 0;
1463
+ }
1464
+ /** The command as it was run, wrapped and coloured as the shell it is. */
1465
+ function CommandText({ text }) {
1466
+ const lines = text.split("\n");
1467
+ const spans = useSyntaxLines(text, SHELL_HINT);
1468
+ return /* @__PURE__ */ jsx(ScrollView, {
1469
+ testID: "tool-result-bash-command",
1470
+ nestedScrollEnabled: true,
1471
+ style: styles.commandBox,
1472
+ children: lines.map((line, index) => /* @__PURE__ */ jsx(SyntaxLine, {
1473
+ selectable: true,
1474
+ spans: spans?.[index],
1475
+ text: line,
1476
+ style: [styles.mono, styles.text]
1477
+ }, index))
1478
+ });
1479
+ }
1480
+ /** The tail with the colours the command wrote, bounded so one chatty command cannot own the card. */
1481
+ function OutputText({ text }) {
1482
+ return /* @__PURE__ */ jsx(ScrollView, {
1483
+ testID: "tool-result-bash-output",
1484
+ nestedScrollEnabled: true,
1485
+ style: styles.outputBox,
1486
+ children: /* @__PURE__ */ jsx(Text, {
1487
+ selectable: true,
1488
+ style: [styles.mono, styles.dim],
1489
+ children: ansiSpans(text).map((span, index) => {
1490
+ const token = ansiToken(span.className);
1491
+ return /* @__PURE__ */ jsx(Text, {
1492
+ style: [
1493
+ token !== void 0 && styles.ink(token),
1494
+ span.bold === true && styles.bold,
1495
+ span.italic === true && styles.italic,
1496
+ span.underline === true && styles.underline
1497
+ ],
1498
+ children: span.text
1499
+ }, index);
1500
+ })
1501
+ })
1502
+ });
1503
+ }
1504
+ /**
1505
+ * The bash tool's timeline item on native, as the web's: the command shortened
1506
+ * to one row in the header with the runner's log and stop beside it, and once
1507
+ * expanded the command as it was run, the tail, its status and the log path.
1508
+ * A failed call shows only the reason in red; the header carries the failure.
1509
+ */
1510
+ function BashToolMessage({ sessionId, args, result, output, running, isError, sendSessionFrame, openTransientTab }) {
1511
+ const details = bashResultDetails(result?.details);
1512
+ const processId = details.sequenceId === void 0 ? details.id : void 0;
1513
+ const runnerId = details.promoted === true ? processId : void 0;
1514
+ const session = useRunners(sessionId);
1515
+ const run = processId === void 0 ? void 0 : session.runs.find((entry) => entry.id === processId);
1516
+ const stopping = runnerId !== void 0 && session.stopRequested.includes(runnerId);
1517
+ const rawCommand = bashCommandText(args.command);
1518
+ const command = formatBashCommand(args.command);
1519
+ const stoppable = sessionId !== null && runnerId !== void 0 && run !== void 0 && run.exit === void 0;
1520
+ const background = runnerId === void 0 ? void 0 : backgroundOutcome(run);
1521
+ return /* @__PURE__ */ jsx(MessageItem, {
1522
+ tone: background?.tone ?? toolTone({
1523
+ running,
1524
+ isError
1525
+ }),
1526
+ expandable: true,
1527
+ children: ({ expanded }) => {
1528
+ const view = bashResultView({
1529
+ details: result?.details,
1530
+ output,
1531
+ expanded: true,
1532
+ isPartial: running,
1533
+ isError
1534
+ });
1535
+ const reason = view.lines.length > 0 ? view.lines.join("\n") : output;
1536
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(MessageItemHeader, {
1537
+ title: "bash",
1538
+ ...background ? { badge: background.badge } : {},
1539
+ children: /* @__PURE__ */ jsxs(View, {
1540
+ testID: "tool-call-bash",
1541
+ style: styles.summary,
1542
+ children: [
1543
+ /* @__PURE__ */ jsx(Text, {
1544
+ numberOfLines: 1,
1545
+ style: [styles.mono, styles.command],
1546
+ children: command
1547
+ }),
1548
+ run !== void 0 && sessionId !== null ? /* @__PURE__ */ jsx(Button, {
1549
+ variant: "outline",
1550
+ size: "xs",
1551
+ testID: "tool-result-bash-open-log",
1552
+ accessibilityLabel: "open this runner's full log",
1553
+ onPress: () => openTransientTab(runnerLogTab(run)),
1554
+ children: /* @__PURE__ */ jsxs(View, {
1555
+ style: styles.buttonLabel,
1556
+ children: [/* @__PURE__ */ jsx(FileIcon, { size: 10 }), /* @__PURE__ */ jsx(Text, {
1557
+ style: [styles.sans, styles.buttonText],
1558
+ children: "log"
1559
+ })]
1560
+ })
1561
+ }) : null,
1562
+ stoppable ? /* @__PURE__ */ jsx(Button, {
1563
+ size: "xs",
1564
+ variant: stopping ? "outline" : "danger-outline",
1565
+ disabled: stopping,
1566
+ testID: "tool-result-bash-stop",
1567
+ accessibilityLabel: stopping ? "stop requested; the runner reports its own exit" : "ask the runtime to stop this runner",
1568
+ onPress: () => {
1569
+ if (sessionId !== null && runnerId !== void 0) requestRunnerStop(sendSessionFrame, sessionId, runnerId);
1570
+ },
1571
+ children: stopping ? "stopping…" : "stop"
1572
+ }) : null
1573
+ ]
1574
+ })
1575
+ }), !expanded ? null : isError ? /* @__PURE__ */ jsx(MessageItemBody, {
1576
+ testID: "tool-result-bash",
1577
+ children: /* @__PURE__ */ jsx(Text, {
1578
+ selectable: true,
1579
+ style: [styles.mono, styles.error],
1580
+ children: reason
1581
+ })
1582
+ }) : /* @__PURE__ */ jsxs(MessageItemBody, {
1583
+ testID: "tool-result-bash",
1584
+ style: styles.body,
1585
+ children: [
1586
+ rawCommand.length > 0 ? /* @__PURE__ */ jsx(CommandText, { text: rawCommand }) : null,
1587
+ view.lines.length > 0 ? /* @__PURE__ */ jsx(OutputText, { text: view.lines.join("\n") }) : null,
1588
+ view.status ? /* @__PURE__ */ jsx(MessageItemStatus, {
1589
+ testID: "tool-result-bash-status",
1590
+ tone: STATUS_TONE[view.status.tone],
1591
+ glyph: view.status.glyph,
1592
+ children: view.status.text
1593
+ }) : null,
1594
+ details.sequenceId === void 0 && details.logPath !== void 0 ? /* @__PURE__ */ jsxs(Text, {
1595
+ testID: "tool-result-bash-log",
1596
+ selectable: true,
1597
+ style: [styles.sans, styles.dim],
1598
+ children: ["full log · ", details.logPath]
1599
+ }) : null
1600
+ ]
1601
+ })] });
1602
+ }
1603
+ });
1604
+ }
1605
+ const styles = StyleSheet.create((theme) => ({
1606
+ summary: {
1607
+ flex: 1,
1608
+ flexDirection: "row",
1609
+ alignItems: "center",
1610
+ gap: theme.spacing(2),
1611
+ minWidth: 0,
1612
+ uni__dependencies: [0]
1613
+ },
1614
+ sans: {
1615
+ fontFamily: fontOf(theme.fontFamily.sans),
1616
+ fontSize: theme.fontSize.base,
1617
+ lineHeight: theme.lineHeight.base,
1618
+ uni__dependencies: [0]
1619
+ },
1620
+ mono: {
1621
+ fontFamily: fontOf(theme.fontFamily.mono),
1622
+ fontSize: theme.fontSize.sm,
1623
+ lineHeight: theme.lineHeightRelaxed.sm,
1624
+ uni__dependencies: [0]
1625
+ },
1626
+ command: {
1627
+ flex: 1,
1628
+ minWidth: 0,
1629
+ color: theme.colors.text,
1630
+ uni__dependencies: [0]
1631
+ },
1632
+ buttonLabel: {
1633
+ flexDirection: "row",
1634
+ alignItems: "center",
1635
+ gap: theme.spacing(1),
1636
+ uni__dependencies: [0]
1637
+ },
1638
+ buttonText: {
1639
+ fontSize: theme.fontSize.xs,
1640
+ color: theme.colors.text,
1641
+ uni__dependencies: [0]
1642
+ },
1643
+ body: {
1644
+ gap: theme.spacing(2),
1645
+ uni__dependencies: [0]
1646
+ },
1647
+ commandBox: { maxHeight: 160 },
1648
+ outputBox: {
1649
+ maxHeight: 384,
1650
+ borderTopWidth: 1,
1651
+ borderTopColor: theme.colors["border-soft"],
1652
+ paddingTop: theme.spacing(2),
1653
+ uni__dependencies: [0]
1654
+ },
1655
+ text: {
1656
+ color: theme.colors.text,
1657
+ uni__dependencies: [0]
1658
+ },
1659
+ dim: {
1660
+ color: theme.colors.dim,
1661
+ uni__dependencies: [0]
1662
+ },
1663
+ error: {
1664
+ color: theme.colors.red,
1665
+ uni__dependencies: [0]
1666
+ },
1667
+ ink: (token) => ({
1668
+ color: theme.colors[token],
1669
+ uni__dependencies: [0]
1670
+ }),
1671
+ bold: { fontWeight: "bold" },
1672
+ italic: { fontStyle: "italic" },
1673
+ underline: { textDecorationLine: "underline" }
1674
+ }));
1675
+ //#endregion
1676
+ //#region src/extensions/workspaces/sessions/(frontend)/tool/bash.native.tsx
1677
+ var bash_native_default = defineToolRenderer({ message: BashToolMessage });
1678
+ //#endregion
1264
1679
  //#region generated/native.ts
1265
1680
  const via = (identity, value) => value === void 0 ? void 0 : {
1266
1681
  ...identity,
@@ -1280,6 +1695,9 @@ const nativePlugin = defineNativePlugin({
1280
1695
  slot: "activity.runners",
1281
1696
  id: "runners"
1282
1697
  }, Runners_activity_runners_native_default)];
1698
+ },
1699
+ get toolRenderers() {
1700
+ return [via({ tools: ["bash"] }, bash_native_default)];
1283
1701
  }
1284
1702
  }
1285
1703
  });