@agimon-ai/po-runner 0.0.0-alpha.21 → 0.0.0-alpha.22

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 (49) hide show
  1. package/dist/{bashRunService-Ci0cGdYO.d.mts → bashRunService-BL4JIUi2.d.mts} +6 -1
  2. package/dist/bashRunService-BL4JIUi2.d.mts.map +1 -0
  3. package/dist/{bashRunService-DjOH5kye.cjs → bashRunService-CJhMNrPz.cjs} +8 -4
  4. package/dist/bashRunService-CJhMNrPz.cjs.map +1 -0
  5. package/dist/{bashRunService-Cl3pYccr.mjs → bashRunService-DbYsj1zc.mjs} +8 -4
  6. package/dist/bashRunService-DbYsj1zc.mjs.map +1 -0
  7. package/dist/bashRunService.cjs +1 -1
  8. package/dist/bashRunService.d.mts +1 -1
  9. package/dist/bashRunService.d.mts.map +1 -1
  10. package/dist/bashRunService.mjs +1 -1
  11. package/dist/bashTool.cjs +1 -1
  12. package/dist/bashTool.d.mts +2 -2
  13. package/dist/bashTool.mjs +1 -1
  14. package/dist/bin/cli.cjs +1 -1
  15. package/dist/bin/cli.mjs +1 -1
  16. package/dist/extensions/native.mjs +212 -203
  17. package/dist/extensions/native.mjs.map +1 -1
  18. package/dist/extensions/server.cjs +2 -2
  19. package/dist/extensions/server.mjs +2 -2
  20. package/dist/extensions/web.mjs +334 -500
  21. package/dist/extensions/web.mjs.map +1 -1
  22. package/dist/{headless-Dko_yBiy.cjs → headless-C4Bvl2B-.cjs} +8 -4
  23. package/dist/headless-C4Bvl2B-.cjs.map +1 -0
  24. package/dist/{headless-DOlCixyf.mjs → headless-DidUBSSf.mjs} +8 -4
  25. package/dist/headless-DidUBSSf.mjs.map +1 -0
  26. package/dist/{index--xF7DvsE.d.mts → index-CXbcmf-G.d.mts} +2 -2
  27. package/dist/{index--xF7DvsE.d.mts.map → index-CXbcmf-G.d.mts.map} +1 -1
  28. package/dist/{index-BzeXJk6V.d.mts → index-DJkjYgir.d.mts} +3 -3
  29. package/dist/{index-BzeXJk6V.d.mts.map → index-DJkjYgir.d.mts.map} +1 -1
  30. package/dist/index.cjs +2 -2
  31. package/dist/index.d.mts +2 -2
  32. package/dist/index.mjs +2 -2
  33. package/dist/responseEnvelope.d.mts +1 -1
  34. package/dist/{runnerDependencies-7h_cxOva.mjs → runnerDependencies-CamKwphq.mjs} +2 -2
  35. package/dist/{runnerDependencies-7h_cxOva.mjs.map → runnerDependencies-CamKwphq.mjs.map} +1 -1
  36. package/dist/{runnerDependencies-DSun3xO-.cjs → runnerDependencies-vkpJfxZZ.cjs} +2 -2
  37. package/dist/{runnerDependencies-DSun3xO-.cjs.map → runnerDependencies-vkpJfxZZ.cjs.map} +1 -1
  38. package/dist/runnerDependencies.cjs +1 -1
  39. package/dist/runnerDependencies.d.mts +1 -1
  40. package/dist/runnerDependencies.mjs +1 -1
  41. package/dist/{type-Do0YxwVQ.d.mts → type-BQ2shEB5.d.mts} +2 -2
  42. package/dist/{type-Do0YxwVQ.d.mts.map → type-BQ2shEB5.d.mts.map} +1 -1
  43. package/dist/types.d.mts +1 -1
  44. package/package.json +17 -17
  45. package/dist/bashRunService-Ci0cGdYO.d.mts.map +0 -1
  46. package/dist/bashRunService-Cl3pYccr.mjs.map +0 -1
  47. package/dist/bashRunService-DjOH5kye.cjs.map +0 -1
  48. package/dist/headless-DOlCixyf.mjs.map +0 -1
  49. package/dist/headless-Dko_yBiy.cjs.map +0 -1
@@ -2,7 +2,7 @@ import { createApiClient, defineActivityGroup, defineChannelFile, defineFill, de
2
2
  import { View } from "react-native-unistyles/components/native/View";
3
3
  import { Text } from "react-native-unistyles/components/native/Text";
4
4
  import { ScrollView } from "react-native-unistyles/components/native/ScrollView";
5
- import { Button, Dot, Input, Label, ListRow, Sheet, SheetBody, SheetContent, SheetHeader, SheetTitle, Textarea, fontOf } from "@agimon-ai/po-native-components";
5
+ import { Button, Dot, Input, Label, ListRow, StatusBadge, fontOf } from "@agimon-ai/po-native-components";
6
6
  import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
7
7
  import { StyleSheet } from "react-native-unistyles";
8
8
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -54,7 +54,7 @@ function tailLineNumbers(lastLineNumber, count) {
54
54
  //#endregion
55
55
  //#region src/extensions/workspaces/sessions/(frontend)/_lib/launchLine.ts
56
56
  /**
57
- * The `/runners start` line the launch form sends.
57
+ * The `/runners start` line the runners panel's command bar sends.
58
58
  *
59
59
  * WHY THE BUILDER LIVES HERE AND THE PARSER DOES NOT:
60
60
  * Cockpit code is compiled into the host bundle and may import only this
@@ -70,6 +70,21 @@ const RUNNER_SHELL_REQUEST = {
70
70
  name: "shell",
71
71
  interactive: true
72
72
  };
73
+ /**
74
+ * What the runners panel's Run sends: the typed command on a terminal when
75
+ * one is available, or a login shell when nothing was typed.
76
+ */
77
+ function runnerRunRequest(command, cwd) {
78
+ const where = cwd.trim() === "" ? {} : { cwd };
79
+ return command.trim() === "" ? {
80
+ ...RUNNER_SHELL_REQUEST,
81
+ ...where
82
+ } : {
83
+ command,
84
+ ...where,
85
+ preferInteractive: true
86
+ };
87
+ }
73
88
  /** Quotes a value that carries whitespace, which is how a value survives tokenizing. */
74
89
  function quote(value) {
75
90
  return /\s/u.test(value) ? `"${value}"` : value;
@@ -80,7 +95,7 @@ function runnerLaunchLine(request) {
80
95
  const pairs = [
81
96
  ...name === "" ? [] : [`name=${quote(name)}`],
82
97
  ...cwd === "" ? [] : [`cwd=${quote(cwd)}`],
83
- ...request.interactive === true ? ["interactive=true"] : []
98
+ ...request.interactive === true ? ["interactive=true"] : request.preferInteractive === true ? ["interactive=prefer"] : []
84
99
  ];
85
100
  return [
86
101
  RUNNERS_START_LINE,
@@ -89,12 +104,6 @@ function runnerLaunchLine(request) {
89
104
  request.command.trim()
90
105
  ].join(" ");
91
106
  }
92
- /** What the form will not let a reader send, worded the way the session would answer. */
93
- function launchProblems(request) {
94
- const problems = [];
95
- if (request.command.trim() === "") problems.push("A runner needs a command to run.");
96
- return problems;
97
- }
98
107
  //#endregion
99
108
  //#region src/constants/webRunners.ts
100
109
  const RUNNER_RUNS_TYPE = "runner_runs";
@@ -164,115 +173,6 @@ const runnerRunsChannel = runners.channel({
164
173
  }
165
174
  });
166
175
  //#endregion
167
- //#region src/extensions/workspaces/sessions/(frontend)/_components/LaunchRunnerSheet.native.tsx
168
- function LaunchRunnerSheet({ sessionId, send, defaultCwd, onClose }) {
169
- const [command, setCommand] = useState("");
170
- const [cwd, setCwd] = useState(defaultCwd ?? "");
171
- const [name, setName] = useState("");
172
- const [interactive, setInteractive] = useState(false);
173
- const request = {
174
- command,
175
- cwd,
176
- name,
177
- interactive
178
- };
179
- const problems = launchProblems(request);
180
- return /* @__PURE__ */ jsx(Sheet, {
181
- open: true,
182
- onOpenChange: (open) => {
183
- if (!open) onClose();
184
- },
185
- children: /* @__PURE__ */ jsxs(SheetContent, { children: [/* @__PURE__ */ jsx(SheetHeader, { children: /* @__PURE__ */ jsx(SheetTitle, { children: "Launch a runner" }) }), /* @__PURE__ */ jsx(SheetBody, { children: /* @__PURE__ */ jsxs(View, {
186
- style: styles$3.form,
187
- children: [
188
- /* @__PURE__ */ jsx(Label, { children: "Command" }),
189
- /* @__PURE__ */ jsx(Textarea, {
190
- testID: "runner-launch-command",
191
- value: command,
192
- onChangeText: setCommand,
193
- placeholder: "pnpm test"
194
- }),
195
- /* @__PURE__ */ jsx(Label, { children: "Cwd" }),
196
- /* @__PURE__ */ jsx(Input, {
197
- testID: "runner-launch-cwd",
198
- value: cwd,
199
- onChangeText: setCwd,
200
- placeholder: "the session's own directory"
201
- }),
202
- /* @__PURE__ */ jsx(Label, { children: "Name" }),
203
- /* @__PURE__ */ jsx(Input, {
204
- testID: "runner-launch-name",
205
- value: name,
206
- onChangeText: setName,
207
- placeholder: "chosen for you when empty"
208
- }),
209
- /* @__PURE__ */ jsx(Label, { children: "Needs a terminal, for a command that prompts (requires rmux or tmux)" }),
210
- /* @__PURE__ */ jsx(Button, {
211
- testID: "runner-launch-interactive",
212
- variant: "outline",
213
- accessibilityState: { checked: interactive },
214
- onPress: () => setInteractive((value) => !value),
215
- children: interactive ? "Terminal enabled" : "Terminal off"
216
- }),
217
- /* @__PURE__ */ jsx(Label, { children: "Sent to the session" }),
218
- /* @__PURE__ */ jsx(Text, {
219
- selectable: true,
220
- testID: "runner-launch-line",
221
- style: styles$3.line,
222
- children: runnerLaunchLine(request)
223
- }),
224
- problems.length > 0 ? /* @__PURE__ */ jsx(Text, {
225
- style: styles$3.error,
226
- children: problems.join(" ")
227
- }) : null,
228
- /* @__PURE__ */ jsx(Button, {
229
- variant: "outline",
230
- onPress: onClose,
231
- children: "Cancel"
232
- }),
233
- /* @__PURE__ */ jsx(Button, {
234
- testID: "runner-launch-submit",
235
- disabled: problems.length > 0,
236
- onPress: () => {
237
- if (problems.length > 0) return;
238
- requestRunnerStart(send, sessionId, request);
239
- onClose();
240
- },
241
- children: "Launch"
242
- }),
243
- /* @__PURE__ */ jsx(Text, {
244
- style: styles$3.meta,
245
- children: "It appears above once the runtime starts it."
246
- })
247
- ]
248
- }) })] })
249
- });
250
- }
251
- const styles$3 = StyleSheet.create((theme) => ({
252
- form: {
253
- gap: theme.spacing(2),
254
- uni__dependencies: [0]
255
- },
256
- line: {
257
- color: theme.colors.green,
258
- fontFamily: fontOf(theme.fontFamily.mono),
259
- fontSize: theme.fontSize.sm,
260
- uni__dependencies: [0]
261
- },
262
- error: {
263
- color: theme.colors.red,
264
- fontFamily: fontOf(theme.fontFamily.sans),
265
- fontSize: theme.fontSize.base,
266
- uni__dependencies: [0]
267
- },
268
- meta: {
269
- color: theme.colors.dim,
270
- fontFamily: fontOf(theme.fontFamily.sans),
271
- fontSize: theme.fontSize.sm,
272
- uni__dependencies: [0]
273
- }
274
- }));
275
- //#endregion
276
176
  //#region src/constants/webRunnerLog.ts
277
177
  const RUN_ID_PARAM = "runId";
278
178
  //#endregion
@@ -672,26 +572,110 @@ function runnerShellTab(run) {
672
572
  })
673
573
  };
674
574
  }
675
- function RunnerShellPanel({ sessionId, runId, renderRunnerTerminal }) {
575
+ /**
576
+ * One terminal runner, full screen. A phone keyboard has no Ctrl key, so
577
+ * Ctrl-C is a button; Search opens the runner's log, which the sink keeps
578
+ * scrubbed beside the raw bytes for exactly that.
579
+ */
580
+ function RunnerShellPanel({ sessionId, runId, renderRunnerTerminal, sendSessionFrame, openTransientTab }) {
581
+ const session = useRunners(sessionId);
582
+ const run = session.runs.find((candidate) => candidate.id === runId);
583
+ const live = run?.state === "running";
584
+ const stopping = session.stopRequested.includes(runId);
676
585
  const request = useMemo(() => sessionId === null ? void 0 : {
677
586
  key: `${sessionId}:${runId}`,
678
587
  read: (from, generation, signal) => readRunnerScreen(sessionId, runId, from, generation, signal),
679
588
  write: (text) => sendRunnerInput(sessionId, runId, text)
680
589
  }, [sessionId, runId]);
681
- return /* @__PURE__ */ jsx(View, {
590
+ return /* @__PURE__ */ jsxs(View, {
682
591
  style: styles$1.panel,
683
- children: request === void 0 ? /* @__PURE__ */ jsx(Text, {
684
- style: styles$1.meta,
685
- children: "Select a session to attach to this runner."
686
- }) : renderRunnerTerminal?.(request)
592
+ children: [
593
+ /* @__PURE__ */ jsxs(View, {
594
+ style: styles$1.header,
595
+ children: [
596
+ /* @__PURE__ */ jsx(Text, {
597
+ numberOfLines: 1,
598
+ style: styles$1.title,
599
+ children: run?.name ?? runId
600
+ }),
601
+ /* @__PURE__ */ jsx(StatusBadge, {
602
+ tone: live ? "running" : "neutral",
603
+ children: live ? "Running" : "Ended"
604
+ }),
605
+ /* @__PURE__ */ jsx(View, { style: styles$1.grow }),
606
+ live && sessionId !== null ? /* @__PURE__ */ jsx(Button, {
607
+ testID: "runner-shell-stop",
608
+ variant: stopping ? "outline" : "danger-outline",
609
+ disabled: stopping,
610
+ onPress: () => requestRunnerStop(sendSessionFrame, sessionId, runId),
611
+ children: stopping ? "Stopping…" : "Stop"
612
+ }) : null
613
+ ]
614
+ }),
615
+ run ? /* @__PURE__ */ jsx(Text, {
616
+ numberOfLines: 1,
617
+ style: styles$1.command,
618
+ children: run.command
619
+ }) : null,
620
+ /* @__PURE__ */ jsx(View, {
621
+ style: styles$1.terminal,
622
+ children: request === void 0 ? /* @__PURE__ */ jsx(Text, {
623
+ style: styles$1.meta,
624
+ children: "Select a session to attach to this runner."
625
+ }) : renderRunnerTerminal?.(request)
626
+ }),
627
+ /* @__PURE__ */ jsxs(View, {
628
+ style: styles$1.actions,
629
+ children: [live && sessionId !== null ? /* @__PURE__ */ jsx(Button, {
630
+ testID: "runner-shell-interrupt",
631
+ variant: "outline",
632
+ onPress: () => void sendRunnerInput(sessionId, runId, ""),
633
+ children: "Ctrl-C"
634
+ }) : null, run ? /* @__PURE__ */ jsx(Button, {
635
+ testID: "runner-shell-search",
636
+ variant: "outline",
637
+ onPress: () => openTransientTab(runnerLogTab(run)),
638
+ children: "Search output"
639
+ }) : null]
640
+ })
641
+ ]
687
642
  });
688
643
  }
689
644
  const styles$1 = StyleSheet.create((theme) => ({
690
645
  panel: {
691
646
  flex: 1,
647
+ gap: theme.spacing(2),
648
+ padding: theme.spacing(3),
692
649
  backgroundColor: theme.colors.bg,
693
650
  uni__dependencies: [0]
694
651
  },
652
+ header: {
653
+ flexDirection: "row",
654
+ alignItems: "center",
655
+ gap: theme.spacing(2),
656
+ uni__dependencies: [0]
657
+ },
658
+ actions: {
659
+ flexDirection: "row",
660
+ gap: theme.spacing(2),
661
+ uni__dependencies: [0]
662
+ },
663
+ grow: { flex: 1 },
664
+ terminal: { flex: 1 },
665
+ title: {
666
+ flexShrink: 1,
667
+ color: theme.colors.hi,
668
+ fontFamily: fontOf(theme.fontFamily.sans),
669
+ fontSize: theme.fontSize.lg,
670
+ fontWeight: "600",
671
+ uni__dependencies: [0]
672
+ },
673
+ command: {
674
+ color: theme.colors.dim,
675
+ fontFamily: fontOf(theme.fontFamily.mono),
676
+ fontSize: theme.fontSize.sm,
677
+ uni__dependencies: [0]
678
+ },
695
679
  meta: {
696
680
  color: theme.colors.dim,
697
681
  fontFamily: fontOf(theme.fontFamily.sans),
@@ -701,6 +685,10 @@ const styles$1 = StyleSheet.create((theme) => ({
701
685
  }));
702
686
  //#endregion
703
687
  //#region src/extensions/workspaces/sessions/(frontend)/_components/RunnersPanel.native.tsx
688
+ /** A runner's own screen: its terminal when it has one, its log when it does not. */
689
+ function runnerTab(run) {
690
+ return run.interactive ? runnerShellTab(run) : runnerLogTab(run);
691
+ }
704
692
  function runnersTab() {
705
693
  return {
706
694
  id: "runner-runs",
@@ -708,7 +696,7 @@ function runnersTab() {
708
696
  panel: RunnersPanel
709
697
  };
710
698
  }
711
- function RunnerRow({ run, props, stopping, now, detail = false }) {
699
+ function RunnerRow({ run, props, stopping, now }) {
712
700
  const tail = useRunnerTail(props.sessionId, run.id, run.state === "running");
713
701
  return /* @__PURE__ */ jsxs(View, {
714
702
  testID: `activity-runner-${run.id}`,
@@ -720,47 +708,27 @@ function RunnerRow({ run, props, stopping, now, detail = false }) {
720
708
  pulse: true
721
709
  }),
722
710
  title: run.name,
723
- description: `${formatRunnerUptime(run.startedAt, now)}${run.interactive ? " · tty" : ""}`,
724
- onPress: () => props.openTransientTab(runnerLogTab(run))
711
+ description: `${run.command} · ${formatRunnerUptime(run.startedAt, now)}`,
712
+ disclosure: true,
713
+ onPress: () => props.openTransientTab(runnerTab(run))
725
714
  }),
726
- /* @__PURE__ */ jsx(Text, {
727
- numberOfLines: detail ? void 0 : 1,
715
+ tail === void 0 ? null : /* @__PURE__ */ jsx(Text, {
716
+ numberOfLines: 1,
728
717
  style: styles.meta,
729
- children: tail ?? run.command
718
+ children: tail
730
719
  }),
731
- detail ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Text, {
732
- selectable: true,
733
- style: styles.meta,
734
- children: run.command
735
- }), /* @__PURE__ */ jsx(Text, {
736
- selectable: true,
737
- style: styles.meta,
738
- children: run.cwd
739
- })] }) : null,
740
- /* @__PURE__ */ jsxs(View, {
720
+ props.sessionId !== null ? /* @__PURE__ */ jsx(View, {
741
721
  style: styles.actions,
742
- children: [
743
- detail ? /* @__PURE__ */ jsx(Button, {
744
- variant: "outline",
745
- onPress: () => props.openTransientTab(runnerLogTab(run)),
746
- children: "Log"
747
- }) : null,
748
- detail && run.interactive && props.sessionId !== null ? /* @__PURE__ */ jsx(Button, {
749
- variant: "outline",
750
- onPress: () => props.openTransientTab(runnerShellTab(run)),
751
- children: "Shell"
752
- }) : null,
753
- props.sessionId !== null ? /* @__PURE__ */ jsx(Button, {
754
- testID: `activity-runner-stop-${run.id}`,
755
- variant: stopping ? "outline" : "danger-outline",
756
- disabled: stopping,
757
- onPress: () => {
758
- if (props.sessionId !== null) requestRunnerStop(props.sendSessionFrame, props.sessionId, run.id);
759
- },
760
- children: stopping ? "Stopping…" : "Stop"
761
- }) : null
762
- ]
763
- })
722
+ children: /* @__PURE__ */ jsx(Button, {
723
+ testID: `activity-runner-stop-${run.id}`,
724
+ variant: stopping ? "outline" : "danger-outline",
725
+ disabled: stopping,
726
+ onPress: () => {
727
+ if (props.sessionId !== null) requestRunnerStop(props.sendSessionFrame, props.sessionId, run.id);
728
+ },
729
+ children: stopping ? "Stopping…" : "Stop"
730
+ })
731
+ }) : null
764
732
  ]
765
733
  });
766
734
  }
@@ -790,11 +758,17 @@ function RunnersActivitySection(props) {
790
758
  stopping: session.stopRequested.includes(run.id)
791
759
  }, run.id)) });
792
760
  }
761
+ /**
762
+ * Every running runner and the one place to start another: type a command
763
+ * and Run hosts it on a terminal (plain output when the host has no rmux or
764
+ * tmux), or leave it empty for a login shell. The started runner opens on
765
+ * its own screen.
766
+ */
793
767
  function RunnersPanel(props) {
794
768
  const { openTransientTab } = props;
795
769
  const session = useRunners(props.sessionId);
796
770
  const running = session.runs.filter((run) => run.state === "running");
797
- const [launching, setLaunching] = useState(false);
771
+ const [command, setCommand] = useState("");
798
772
  const [now, setNow] = useState(() => Date.now());
799
773
  const baseline = useRef(null);
800
774
  const timeout = useRef(void 0);
@@ -804,66 +778,101 @@ function RunnersPanel(props) {
804
778
  }, []);
805
779
  useEffect(() => {
806
780
  if (baseline.current === null) return;
807
- const shell = session.runs.find((run) => run.state === "running" && run.interactive && !baseline.current?.has(run.id));
808
- if (shell === void 0) return;
781
+ const started = session.runs.find((run) => run.state === "running" && !baseline.current?.has(run.id));
782
+ if (started === void 0) return;
809
783
  baseline.current = null;
810
784
  clearTimeout(timeout.current);
811
- openTransientTab(runnerShellTab(shell));
785
+ openTransientTab(runnerTab(started));
812
786
  }, [session.runs, openTransientTab]);
813
787
  useEffect(() => () => clearTimeout(timeout.current), []);
788
+ const launch = () => {
789
+ if (props.sessionId === null) return;
790
+ baseline.current = new Set(session.runs.map((run) => run.id));
791
+ clearTimeout(timeout.current);
792
+ timeout.current = setTimeout(() => {
793
+ baseline.current = null;
794
+ }, 3e4);
795
+ requestRunnerStart(props.sendSessionFrame, props.sessionId, runnerRunRequest(command, ""));
796
+ setCommand("");
797
+ };
814
798
  return /* @__PURE__ */ jsxs(View, {
815
799
  style: styles.panel,
816
800
  children: [
817
- /* @__PURE__ */ jsx(Text, {
818
- style: styles.title,
819
- children: running.length === 0 ? "Nothing running" : `${running.length} running`
801
+ /* @__PURE__ */ jsxs(View, {
802
+ style: styles.header,
803
+ children: [/* @__PURE__ */ jsx(Text, {
804
+ style: styles.title,
805
+ children: "Runners"
806
+ }), /* @__PURE__ */ jsx(Text, {
807
+ style: styles.meta,
808
+ children: running.length === 0 ? "Nothing running" : `${running.length} running`
809
+ })]
820
810
  }),
821
811
  props.sessionId !== null ? /* @__PURE__ */ jsxs(View, {
822
- style: styles.actions,
823
- children: [/* @__PURE__ */ jsx(Button, {
824
- variant: "outline",
825
- onPress: () => {
826
- if (props.sessionId === null) return;
827
- baseline.current = new Set(session.runs.map((run) => run.id));
828
- clearTimeout(timeout.current);
829
- timeout.current = setTimeout(() => {
830
- baseline.current = null;
831
- }, 3e4);
832
- requestRunnerStart(props.sendSessionFrame, props.sessionId, RUNNER_SHELL_REQUEST);
833
- },
834
- children: "Start shell"
835
- }), /* @__PURE__ */ jsx(Button, {
836
- onPress: () => setLaunching(true),
837
- children: "Run command"
812
+ style: styles.bar,
813
+ children: [/* @__PURE__ */ jsxs(View, {
814
+ style: styles.actions,
815
+ children: [/* @__PURE__ */ jsx(View, {
816
+ style: styles.grow,
817
+ children: /* @__PURE__ */ jsx(Input, {
818
+ testID: "runners-panel-command",
819
+ accessibilityLabel: "Command to run",
820
+ size: "lg",
821
+ value: command,
822
+ onChangeText: setCommand,
823
+ onSubmitEditing: launch,
824
+ placeholder: "a command, like pnpm dev",
825
+ autoCapitalize: "none",
826
+ autoCorrect: false,
827
+ returnKeyType: "go"
828
+ })
829
+ }), /* @__PURE__ */ jsx(Button, {
830
+ testID: "runners-panel-run",
831
+ variant: "primary",
832
+ size: "lg",
833
+ onPress: launch,
834
+ children: command.trim() === "" ? "Open shell" : "Run"
835
+ })]
836
+ }), /* @__PURE__ */ jsx(Text, {
837
+ style: styles.meta,
838
+ children: "Runs in the session's directory. Leave it empty for a plain shell."
838
839
  })]
839
840
  }) : null,
840
841
  /* @__PURE__ */ jsx(ScrollView, { children: running.length === 0 ? /* @__PURE__ */ jsx(Text, {
841
842
  style: styles.meta,
842
- children: "A command that outlives its turn moves to a runner and appears here while it works."
843
- }) : running.map((run) => /* @__PURE__ */ jsx(RunnerRow, {
844
- run,
845
- props,
846
- now,
847
- stopping: session.stopRequested.includes(run.id),
848
- detail: true
849
- }, run.id)) }),
850
- launching && props.sessionId !== null ? /* @__PURE__ */ jsx(LaunchRunnerSheet, {
851
- sessionId: props.sessionId,
852
- send: props.sendSessionFrame,
853
- defaultCwd: running[0]?.cwd,
854
- onClose: () => setLaunching(false)
855
- }) : null
843
+ 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."
844
+ }) : running.map((run) => /* @__PURE__ */ jsx(ListRow, {
845
+ leading: /* @__PURE__ */ jsx(Dot, {
846
+ tone: "yellow",
847
+ pulse: true
848
+ }),
849
+ title: run.name,
850
+ description: `${run.command} · ${formatRunnerUptime(run.startedAt, now)}`,
851
+ disclosure: true,
852
+ onPress: () => openTransientTab(runnerTab(run))
853
+ }, run.id)) })
856
854
  ]
857
855
  });
858
856
  }
859
857
  const styles = StyleSheet.create((theme) => ({
860
858
  panel: {
861
859
  flex: 1,
862
- gap: theme.spacing(2),
863
- padding: theme.spacing(3),
860
+ gap: theme.spacing(3),
861
+ padding: theme.spacing(4),
864
862
  backgroundColor: theme.colors.bg,
865
863
  uni__dependencies: [0]
866
864
  },
865
+ header: {
866
+ flexDirection: "row",
867
+ alignItems: "center",
868
+ gap: theme.spacing(2),
869
+ uni__dependencies: [0]
870
+ },
871
+ bar: {
872
+ gap: theme.spacing(1.5),
873
+ uni__dependencies: [0]
874
+ },
875
+ grow: { flex: 1 },
867
876
  row: {
868
877
  gap: theme.spacing(1),
869
878
  paddingVertical: theme.spacing(2),
@@ -871,14 +880,14 @@ const styles = StyleSheet.create((theme) => ({
871
880
  },
872
881
  actions: {
873
882
  flexDirection: "row",
874
- flexWrap: "wrap",
883
+ alignItems: "center",
875
884
  gap: theme.spacing(2),
876
885
  uni__dependencies: [0]
877
886
  },
878
887
  title: {
879
888
  color: theme.colors.hi,
880
889
  fontFamily: fontOf(theme.fontFamily.sans),
881
- fontSize: theme.fontSize.base,
890
+ fontSize: theme.fontSize.lg,
882
891
  fontWeight: "600",
883
892
  uni__dependencies: [0]
884
893
  },