@awak-app/simy-cli 0.2.3 → 0.3.3

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,11 +1,50 @@
1
- export const CLI_VERSION = "0.2.3";
2
- export const CLI_API_CONTRACT_VERSION = 3;
1
+ import { localTaskFileCapabilityMetadata } from "./local-task-file-capabilities.js";
2
+ import { localTaskScenarioPackMetadata } from "./local-task-scenario-packs.js";
3
+ import {
4
+ boundedLocalTaskSubtasksCapabilityMetadata,
5
+ } from "./bounded-local-task-subtasks-contract.js";
6
+ import {
7
+ DURABLE_LOCAL_TASK_STEP_CLAIM_SCHEMA,
8
+ LOCAL_TASK_PLAN_ORCHESTRATION_SCHEMA,
9
+ LOCAL_TASK_PLAN_SCHEMA,
10
+ LOCAL_TASK_STEP_CONTEXT_SCHEMA,
11
+ LOCAL_TASK_STEP_SCHEMA,
12
+ MAX_LOCAL_TASK_PLAN_STEPS,
13
+ } from "./durable-local-task-steps-contract.js";
14
+ import { buildExecutionCapabilityContract } from "./execution-capability-contract.js";
3
15
 
4
- export function withCliContract(capabilities = {}, installMode = null) {
16
+ export const CLI_VERSION = "0.3.3";
17
+ export const CLI_API_CONTRACT_VERSION = 10;
18
+
19
+ export function withCliContract(
20
+ capabilities = {},
21
+ installMode = null,
22
+ { authenticated = false } = {},
23
+ ) {
5
24
  return {
6
25
  ...capabilities,
7
26
  cli_version: CLI_VERSION,
8
27
  api_contract_version: CLI_API_CONTRACT_VERSION,
28
+ local_task_file_capabilities: localTaskFileCapabilityMetadata(),
29
+ local_task_scenario_packs: localTaskScenarioPackMetadata(),
30
+ bounded_local_task_subtasks:
31
+ boundedLocalTaskSubtasksCapabilityMetadata(),
32
+ durable_local_task_steps: {
33
+ orchestration_schema_version: LOCAL_TASK_PLAN_ORCHESTRATION_SCHEMA,
34
+ claim_schema_version: DURABLE_LOCAL_TASK_STEP_CLAIM_SCHEMA,
35
+ plan_schema_version: LOCAL_TASK_PLAN_SCHEMA,
36
+ step_schema_version: LOCAL_TASK_STEP_SCHEMA,
37
+ context_schema_version: LOCAL_TASK_STEP_CONTEXT_SCHEMA,
38
+ max_steps: MAX_LOCAL_TASK_PLAN_STEPS,
39
+ },
40
+ execution_capabilities: buildExecutionCapabilityContract({
41
+ cliVersion: CLI_VERSION,
42
+ apiContractVersion: CLI_API_CONTRACT_VERSION,
43
+ installMode,
44
+ backends: capabilities.backends,
45
+ backendVersions: capabilities.backend_versions,
46
+ authenticated,
47
+ }),
9
48
  ...(installMode ? { install_mode: installMode } : {}),
10
49
  };
11
50
  }
@@ -68,14 +68,12 @@ export function AgenticLoopConsole({ agent, onQuit = () => {} }) {
68
68
  const selectedRun = runs.find((run) => run.id === selectedId) ?? null;
69
69
  const selectableIds = [NEW_TASK, ...runs.map((run) => run.id)];
70
70
  const selectedIndex = Math.max(0, selectableIds.indexOf(selectedId));
71
- const waitingForHuman = Boolean(
72
- selectedRun &&
73
- (selectedRun.status === "waiting_human" || selectedRun.controlState === "waiting_human"),
71
+ const hasNextAction = Boolean(
72
+ selectedRun && nextActionForRun(selectedRun, isRepositoryMissing(selectedRun)),
74
73
  );
75
74
  const actionRows =
76
75
  1 +
77
- (waitingForHuman ? 1 : 0) +
78
- (!selectedRun ? 1 : 0) +
76
+ (hasNextAction ? 1 : 0) +
79
77
  (notice || busy ? 1 : 0) +
80
78
  (visibleUpdateNotice(cliUpdate) ? 1 : 0);
81
79
  const mainHeight = Math.max(8, terminal.rows - 5 - actionRows);
@@ -486,7 +484,7 @@ export function AgenticLoopConsole({ agent, onQuit = () => {} }) {
486
484
  flexDirection: "column",
487
485
  width: terminal.columns,
488
486
  children: [
489
- jsx(Header, { agent, runCount: runs.length }),
487
+ jsx(Header, { agent, runCount: runs.length, width: terminal.columns }),
490
488
  jsx(Box, {
491
489
  marginTop: 1,
492
490
  height: mainHeight,
@@ -535,10 +533,10 @@ export function AgenticLoopConsole({ agent, onQuit = () => {} }) {
535
533
  busy,
536
534
  notice,
537
535
  confirmStop,
538
- draft,
539
536
  repositoryMissing: isRepositoryMissing(selectedRun),
540
537
  modalOpen: Boolean(scanPrompt || repositoryPicker),
541
538
  cliUpdate,
539
+ width: terminal.columns,
542
540
  }),
543
541
  ],
544
542
  });
@@ -546,48 +544,77 @@ export function AgenticLoopConsole({ agent, onQuit = () => {} }) {
546
544
 
547
545
  export const CodingLoopConsole = AgenticLoopConsole;
548
546
 
549
- function Header({ agent, runCount }) {
547
+ function Header({ agent, runCount, width }) {
548
+ const compact = width < 84;
549
+ const version = agent.updates?.snapshot?.().current_version;
550
550
  return jsxs(Box, {
551
551
  borderStyle: "single",
552
552
  borderColor: "cyan",
553
553
  paddingX: 1,
554
+ flexDirection: compact ? "column" : "row",
554
555
  children: [
555
- jsx(Text, { bold: true, color: "cyan", children: "SIMY" }),
556
- jsx(Text, { bold: true, children: " Coding Chat" }),
557
- agent.updates?.snapshot?.().current_version
558
- ? jsx(Text, {
556
+ jsxs(Box, {
557
+ children: [
558
+ jsx(Text, { bold: true, color: "cyan", children: "SIMY" }),
559
+ jsx(Text, { bold: true, children: " Local Agent" }),
560
+ version ? jsx(Text, { dimColor: true, children: ` v${version}` }) : null,
561
+ ],
562
+ }),
563
+ compact
564
+ ? jsxs(Text, {
559
565
  dimColor: true,
560
- children: ` v${agent.updates.snapshot().current_version}`,
566
+ children: [
567
+ `Connected · localhost:${agent.port}`,
568
+ jsx(Text, { children: ` · ${runCount} ${runCount === 1 ? "run" : "runs"}` }),
569
+ ],
561
570
  })
562
- : null,
563
- jsx(Text, { dimColor: true, children: ` localhost:${agent.port}` }),
564
- jsx(Spacer, {}),
565
- jsx(Text, { color: runCount > 0 ? "green" : "yellow", children: `${runCount} runs` }),
566
- jsx(Text, { dimColor: true, children: ` ${agent.webOrigin}` }),
571
+ : jsx(Text, { dimColor: true, children: ` Connected · localhost:${agent.port}` }),
572
+ compact ? null : jsx(Spacer, {}),
573
+ compact
574
+ ? null
575
+ : jsx(Text, {
576
+ color: runCount > 0 ? "green" : undefined,
577
+ children: `${runCount} ${runCount === 1 ? "run" : "runs"}`,
578
+ }),
579
+ compact ? null : jsx(Text, { dimColor: true, children: ` · ${agent.webOrigin}` }),
567
580
  ],
568
581
  });
569
582
  }
570
583
 
571
584
  function NewTask({ draft, loginUrl, width }) {
585
+ const compact = width < 72;
572
586
  return jsxs(Box, {
573
587
  width,
588
+ alignSelf: "flex-start",
574
589
  flexDirection: "column",
575
590
  borderStyle: "single",
576
591
  borderColor: "cyan",
577
592
  paddingX: 1,
578
593
  children: [
579
- jsx(Text, { bold: true, color: "cyan", children: "What do you want to build?" }),
594
+ jsx(Text, { bold: true, color: "cyan", children: "NEW TASK" }),
595
+ jsx(Text, { bold: true, children: "What should SIMY do?" }),
580
596
  jsx(Text, {
581
597
  dimColor: true,
582
- children: "Describe the change in the composer. SIMY will create the ledger and run it here.",
598
+ wrap: "wrap",
599
+ children:
600
+ "Describe the outcome. SIMY will choose a one-time Local Task or an Agentic Loop.",
601
+ }),
602
+ jsx(Text, { children: " " }),
603
+ jsx(Text, {
604
+ children: `${compact ? "Repo" : "Repository"} ${draft.repository || "Not selected"}`,
583
605
  }),
584
- jsx(Text, { children: `Repository ${draft.repository || "not set"}` }),
585
606
  jsx(Text, { children: `Branch ${draft.branch}` }),
586
- jsx(Text, { children: `Executor ${executorLabel(draft.backend)}` }),
607
+ jsx(Text, { children: `Agent ${executorLabel(draft.backend)}` }),
587
608
  jsx(Text, {
588
- children: `Attachments ${draft.attachmentPaths.length === 0 ? "none" : draft.attachmentPaths.join(", ")}`,
609
+ wrap: "wrap",
610
+ children: `Files ${draft.attachmentPaths.length === 0 ? "None" : draft.attachmentPaths.join(", ")}`,
611
+ }),
612
+ jsx(Text, { children: " " }),
613
+ jsx(Text, {
614
+ bold: true,
615
+ children: "Next Type your request below, then press Enter.",
589
616
  }),
590
- loginUrl ? jsx(Text, { color: "cyan", children: `Sign in: ${loginUrl}` }) : null,
617
+ loginUrl ? jsx(Text, { color: "cyan", wrap: "wrap", children: `Sign in ${loginUrl}` }) : null,
591
618
  ],
592
619
  });
593
620
  }
@@ -601,9 +628,10 @@ function RepositoryScanConsent({ root, width, height }) {
601
628
  borderColor: "yellow",
602
629
  paddingX: 1,
603
630
  children: [
604
- jsx(Text, { bold: true, color: "yellow", children: "Allow local repository scan?" }),
631
+ jsx(Text, { bold: true, color: "yellow", children: "REPOSITORY ACCESS" }),
632
+ jsx(Text, { bold: true, children: "Allow SIMY to find local Git repositories?" }),
605
633
  jsx(Text, { children: " " }),
606
- jsx(Text, { bold: true, children: "Authorized root" }),
634
+ jsx(Text, { dimColor: true, children: "Folder to scan" }),
607
635
  jsx(Text, { color: "cyan", wrap: "wrap", children: root }),
608
636
  jsx(Text, { children: " " }),
609
637
  jsx(Text, {
@@ -617,8 +645,12 @@ function RepositoryScanConsent({ root, width, height }) {
617
645
  "Source files and file contents are not indexed or uploaded. Hidden, dependency, cache, and symlink directories are skipped.",
618
646
  }),
619
647
  jsx(Text, { children: " " }),
620
- jsx(Text, { bold: true, color: "green", children: "Press y to allow this scan" }),
621
- jsx(Text, { dimColor: true, children: "Press n or Esc to cancel. Use /scan <path> to choose a narrower root." }),
648
+ jsx(Text, { bold: true, children: "Next Press y to allow this scan." }),
649
+ jsx(Text, {
650
+ dimColor: true,
651
+ wrap: "wrap",
652
+ children: "Press n or Esc to cancel. Use /scan <path> to choose a narrower folder.",
653
+ }),
622
654
  ],
623
655
  });
624
656
  }
@@ -640,7 +672,7 @@ function RepositoryPicker({ repositories, selected, requiredRepository, width, h
640
672
  children: [
641
673
  jsxs(Box, {
642
674
  children: [
643
- jsx(Text, { bold: true, color: "cyan", children: "LOCAL GIT REPOSITORIES" }),
675
+ jsx(Text, { bold: true, color: "cyan", children: "CHOOSE A REPOSITORY" }),
644
676
  jsx(Spacer, {}),
645
677
  jsx(Text, { dimColor: true, children: `${repositories.length} indexed` }),
646
678
  ],
@@ -650,7 +682,7 @@ function RepositoryPicker({ repositories, selected, requiredRepository, width, h
650
682
  color: "yellow",
651
683
  children: `This run requires ${requiredRepository}`,
652
684
  })
653
- : jsx(Text, { dimColor: true, children: "Choose the repository for the next coding task." }),
685
+ : jsx(Text, { dimColor: true, children: "Choose the workspace for the next task." }),
654
686
  ...visible.map((repository, offset) => {
655
687
  const index = start + offset;
656
688
  const active = index === selected;
@@ -672,12 +704,13 @@ function RepositoryPicker({ repositories, selected, requiredRepository, width, h
672
704
  ],
673
705
  }, repository.local_path);
674
706
  }),
675
- jsx(Text, { dimColor: true, children: "Up/Down select Enter use repository Esc cancel" }),
707
+ jsx(Text, { dimColor: true, children: "↑/↓ select Enter choose Esc cancel" }),
676
708
  ],
677
709
  });
678
710
  }
679
711
 
680
712
  function RunList({ runs, selectedId, width, height }) {
713
+ const compact = width < 84;
681
714
  return jsxs(Box, {
682
715
  width,
683
716
  height,
@@ -690,33 +723,37 @@ function RunList({ runs, selectedId, width, height }) {
690
723
  children: [
691
724
  jsx(Text, { bold: true, children: "RUNS" }),
692
725
  jsx(Spacer, {}),
693
- jsx(Text, { dimColor: true, children: "Enter opens the selected run full screen" }),
726
+ jsx(Text, { dimColor: true, children: compact ? "Enter open" : "Enter opens details" }),
694
727
  ],
695
728
  }),
696
- jsxs(Box, {
697
- borderStyle: selectedId === NEW_TASK ? "round" : undefined,
698
- borderColor: selectedId === NEW_TASK ? "cyan" : undefined,
699
- paddingX: selectedId === NEW_TASK ? 1 : 2,
700
- children: [
701
- jsx(Text, { color: "cyan", bold: selectedId === NEW_TASK, children: "+ New task" }),
702
- ],
729
+ jsx(Text, {
730
+ color: "cyan",
731
+ bold: selectedId === NEW_TASK,
732
+ children: `${selectedId === NEW_TASK ? "> " : " "}+ New task`,
703
733
  }),
704
734
  ...runs.slice(0, 10).map((run) => {
705
735
  const selected = run.id === selectedId;
706
736
  return jsxs(Box, {
707
737
  flexDirection: "column",
708
- borderStyle: selected ? "round" : undefined,
709
- borderColor: selected ? "cyan" : undefined,
710
- paddingX: selected ? 1 : 2,
738
+ paddingLeft: 0,
711
739
  children: [
712
740
  jsxs(Text, {
713
741
  children: [
742
+ jsx(Text, { color: selected ? "cyan" : undefined, children: selected ? "> " : " " }),
714
743
  jsx(Text, { color: statusColor(run), children: `${statusGlyph(run)} ` }),
715
744
  jsx(Text, { bold: selected, children: shortId(run.id) }),
716
- jsx(Text, { dimColor: true, children: ` ${run.request.backend}` }),
745
+ jsx(Text, {
746
+ dimColor: true,
747
+ children: compact
748
+ ? ` ${statusLabel(run)}`
749
+ : ` ${statusLabel(run)} · ${executorLabel(run.request.backend)}`,
750
+ }),
717
751
  ],
718
752
  }),
719
- jsx(Text, { dimColor: true, children: truncate(run.request.requirement, width - 6) }),
753
+ jsx(Text, {
754
+ dimColor: true,
755
+ children: ` ${truncate(run.request.requirement, Math.max(12, width - 8))}`,
756
+ }),
720
757
  ],
721
758
  }, run.id);
722
759
  }),
@@ -744,8 +781,9 @@ function RunDetail({ run, rows, scrollFromEnd, width, height }) {
744
781
  children: [
745
782
  jsxs(Text, {
746
783
  children: [
747
- jsx(Text, { bold: true, children: run.id }),
748
- jsx(Text, { color: statusColor(run), children: ` ${statusLabel(run)}` }),
784
+ jsx(Text, { color: statusColor(run), children: `${statusGlyph(run)} ` }),
785
+ jsx(Text, { bold: true, children: statusLabel(run) }),
786
+ jsx(Text, { dimColor: true, children: ` ${shortId(run.id)}` }),
749
787
  ],
750
788
  }),
751
789
  jsx(Spacer, {}),
@@ -755,7 +793,7 @@ function RunDetail({ run, rows, scrollFromEnd, width, height }) {
755
793
  }),
756
794
  jsx(Text, {
757
795
  color: scroll > 0 ? "yellow" : run.child ? "green" : "gray",
758
- children: scroll > 0 ? " scroll paused" : run.child ? " ● live" : " ○ idle",
796
+ children: scroll > 0 ? " history" : run.child ? " ● live" : " ○ idle",
759
797
  }),
760
798
  ],
761
799
  }),
@@ -772,6 +810,10 @@ function RunDetail({ run, rows, scrollFromEnd, width, height }) {
772
810
  }
773
811
 
774
812
  function Help({ width }) {
813
+ const groups = [
814
+ ["TASK", consoleHelpLines().slice(0, 8)],
815
+ ["RUN", consoleHelpLines().slice(8, 15)],
816
+ ];
775
817
  return jsxs(Box, {
776
818
  width,
777
819
  borderStyle: "single",
@@ -779,8 +821,12 @@ function Help({ width }) {
779
821
  paddingX: 1,
780
822
  flexDirection: "column",
781
823
  children: [
782
- jsx(Text, { bold: true, color: "blue", children: "COMMANDS" }),
783
- ...consoleHelpLines().map((line) => jsx(Text, { children: line }, line)),
824
+ jsx(Text, { bold: true, color: "blue", children: "HELP" }),
825
+ ...groups.flatMap(([label, lines]) => [
826
+ jsx(Text, { bold: true, children: label }, `${label}-heading`),
827
+ ...lines.map((line) => jsx(Text, { children: line }, line)),
828
+ ]),
829
+ jsx(Text, { dimColor: true, children: "Esc closes help" }),
784
830
  ],
785
831
  });
786
832
  }
@@ -793,23 +839,23 @@ function ActionBar({
793
839
  busy,
794
840
  notice,
795
841
  confirmStop,
796
- draft,
797
842
  repositoryMissing,
798
843
  modalOpen,
799
844
  cliUpdate,
845
+ width,
800
846
  }) {
801
- const waiting = run && (run.controlState === "waiting_human" || run.status === "waiting_human");
847
+ const compact = width < 84;
848
+ const nextAction = run ? nextActionForRun(run, repositoryMissing) : null;
802
849
  return jsxs(Box, {
803
850
  flexDirection: "column",
804
851
  marginTop: 1,
805
852
  children: [
806
- waiting
853
+ nextAction && !modalOpen && !inputMode
807
854
  ? jsx(Text, {
808
855
  bold: true,
809
- color: "yellow",
810
- children: repositoryMissing
811
- ? "LOCAL REPOSITORY REQUIRED Press s to review and authorize a local Git repository scan."
812
- : "HUMAN INPUT REQUIRED Press i, type guidance, then Enter.",
856
+ color: nextAction.tone,
857
+ wrap: "wrap",
858
+ children: `NEXT ACTION ${nextAction.text}`,
813
859
  })
814
860
  : null,
815
861
  modalOpen
@@ -825,27 +871,39 @@ function ActionBar({
825
871
  : jsx(Text, {
826
872
  dimColor: true,
827
873
  children: detailOpen
828
- ? "↑/↓ scroll PgUp/PgDn page Home/End Esc runs i guidance / command s scan repos p pause/resume r recheck x stop ? help q quit"
829
- : "↑/↓ select Enter open i guidance / command s scan repos ? help q quit",
874
+ ? compact
875
+ ? "↑/↓ scroll Esc runs i act ? help q quit"
876
+ : "↑/↓ scroll PgUp/PgDn page Home/End Esc runs i act / command ? help q quit"
877
+ : compact
878
+ ? "↑/↓ select Enter open i act ? help q quit"
879
+ : "↑/↓ select Enter open i act / command s repositories ? help q quit",
830
880
  }),
831
- !run
832
- ? jsx(Text, {
833
- dimColor: true,
834
- children: `${draft.repository || "repo not set"} ${draft.branch} ${executorLabel(draft.backend)}`,
835
- })
836
- : null,
837
881
  notice
838
882
  ? jsx(Text, {
839
883
  color: confirmStop ? "yellow" : notice.includes("could not") || notice.includes("requires") ? "red" : "cyan",
840
- children: `${busy ? "Working... " : ""}${notice}`,
884
+ wrap: "wrap",
885
+ children: `${busy ? "WORKING " : "STATUS "}${notice}`,
841
886
  })
842
887
  : busy
843
- ? jsx(Text, { color: "cyan", children: "Working..." })
888
+ ? jsx(Text, { color: "cyan", children: "WORKING Please wait…" })
844
889
  : null,
845
890
  visibleUpdateNotice(cliUpdate)
846
- ? jsx(Text, {
847
- color: cliUpdate.state === "failed" ? "red" : "yellow",
848
- children: visibleUpdateNotice(cliUpdate),
891
+ ? jsxs(Box, {
892
+ flexDirection: "column",
893
+ children: [
894
+ jsx(Text, {
895
+ bold: true,
896
+ color: cliUpdate.state === "failed" ? "red" : "yellow",
897
+ wrap: "wrap",
898
+ children: `UPDATE ${cliUpdate.message || "SIMY CLI update status changed."}`,
899
+ }),
900
+ cliUpdate.action?.command
901
+ ? jsx(Text, {
902
+ wrap: "wrap",
903
+ children: `Next ${cliUpdate.action.command}`,
904
+ })
905
+ : null,
906
+ ],
849
907
  })
850
908
  : null,
851
909
  ],
@@ -861,8 +919,7 @@ function visibleUpdateNotice(update) {
861
919
  ) {
862
920
  return "";
863
921
  }
864
- const command = update.action?.command ? ` Run: ${update.action.command}` : "";
865
- return `${update.message || "SIMY CLI update status changed."}${command}`;
922
+ return update.message || "SIMY CLI update status changed.";
866
923
  }
867
924
 
868
925
  function initialDraft(agent) {
@@ -914,33 +971,40 @@ function buildRunDetailRows(run, width) {
914
971
  for (const text of wrapTerminalText(value, width)) rows.push({ text, ...style });
915
972
  };
916
973
 
974
+ const attemptNumber = run.snapshot.attempts.length + (run.child ? 1 : 0);
975
+ const maxAttempts = run.snapshot.charter?.max_attempts;
917
976
  add(
918
- `PID ${run.child?.pid ?? "-"} attempt ${run.snapshot.attempts.length + (run.child ? 1 : 0)}`,
977
+ `${executorLabel(run.request.backend)} · Attempt ${attemptNumber}${maxAttempts ? `/${maxAttempts}` : ""} · PID ${run.child?.pid ?? "—"}`,
919
978
  { dimColor: true },
920
979
  );
921
- add(`Repository: ${run.request.repository}`, { dimColor: true });
980
+ add(`${run.request.repository} · ${run.request.base_branch || run.snapshot.charter?.base_branch || "dev"}`, {
981
+ dimColor: true,
982
+ });
922
983
  if (run.request.attachments?.length) {
923
- add(`Attachments: ${run.request.attachments.map((item) => item.name).join(", ")}`, {
984
+ add(`Files · ${run.request.attachments.map((item) => item.name).join(", ")}`, {
924
985
  dimColor: true,
925
986
  });
926
987
  }
927
988
  add();
928
- add("YOU", { bold: true, color: "cyan" });
989
+ add("GOAL", { bold: true, color: "cyan" });
929
990
  add(run.request.requirement);
930
- add();
931
- add("SIMY", { bold: true, color: "green" });
932
- add(statusLabel(run), { color: statusColor(run) });
933
991
 
934
992
  if (presentation) {
935
993
  add();
936
- add("STEP SUMMARY", { bold: true, color: "cyan" });
994
+ add("CURRENT STEP", { bold: true, color: "cyan" });
937
995
  add(presentation.summary);
938
- if (presentation.result) add(`Result: ${presentation.result}`);
939
- if (presentation.next) add(`Next: ${presentation.next}`);
996
+ if (presentation.result) add(`Result · ${presentation.result}`, { dimColor: true });
997
+ }
998
+
999
+ const nextAction = nextActionForRun(run, isRepositoryMissing(run));
1000
+ if (!nextAction && presentation?.next) {
1001
+ add();
1002
+ add("NEXT ACTION", { bold: true });
1003
+ add(presentation.next);
940
1004
  }
941
1005
 
942
1006
  add();
943
- add(`LIVE TRANSCRIPT ${run.child ? "● streaming" : "○ idle"}`, {
1007
+ add(`OUTPUT ${run.child ? "● live" : "○ idle"}`, {
944
1008
  bold: true,
945
1009
  color: run.child ? "green" : undefined,
946
1010
  });
@@ -1036,7 +1100,7 @@ function isFullWidthCodePoint(codePoint) {
1036
1100
 
1037
1101
  function terminalSize(stdout) {
1038
1102
  return {
1039
- columns: Math.max(72, stdout.columns || 120),
1103
+ columns: Math.max(52, stdout.columns || 120),
1040
1104
  rows: Math.max(24, stdout.rows || 32),
1041
1105
  };
1042
1106
  }
@@ -1061,15 +1125,15 @@ function statusGlyph(run) {
1061
1125
  }
1062
1126
 
1063
1127
  function statusLabel(run) {
1064
- if (run.controlState === "paused") return "PAUSED";
1065
- if (run.controlState === "stopping") return "STOPPING";
1066
- if (run.controlState === "stopped" || run.status === "stopped") return "STOPPED";
1067
- if (run.status === "pr_ready_for_review") return "PR READY - HUMAN REVIEW";
1128
+ if (run.controlState === "paused") return "Paused";
1129
+ if (run.controlState === "stopping") return "Stopping";
1130
+ if (run.controlState === "stopped" || run.status === "stopped") return "Stopped";
1131
+ if (run.status === "pr_ready_for_review") return "PR ready · Review needed";
1068
1132
  if (run.controlState === "waiting_human" || run.status === "waiting_human") {
1069
- return "WAITING FOR HUMAN";
1133
+ return "Waiting for you";
1070
1134
  }
1071
- if (run.status === "merge_ready") return "READY TO MERGE";
1072
- return String(run.status || "queued").replaceAll("_", " ").toUpperCase();
1135
+ if (run.status === "merge_ready") return "Ready to merge";
1136
+ return titleCase(String(run.status || "queued").replaceAll("_", " "));
1073
1137
  }
1074
1138
 
1075
1139
  function statusColor(run) {
@@ -1078,3 +1142,61 @@ function statusColor(run) {
1078
1142
  if (run.controlState === "complete") return "green";
1079
1143
  return "cyan";
1080
1144
  }
1145
+
1146
+ function nextActionForRun(run, repositoryMissing = false) {
1147
+ if (!run) return null;
1148
+ if (repositoryMissing) {
1149
+ return {
1150
+ tone: "yellow",
1151
+ text: "Press s to choose and authorize the local repository.",
1152
+ };
1153
+ }
1154
+ if (run.controlState === "paused") {
1155
+ return { tone: "yellow", text: "Press p to resume the local agent." };
1156
+ }
1157
+ if (run.status === "pr_ready_for_review") {
1158
+ return { tone: "yellow", text: "Review the pull request, then press r to refresh its evidence." };
1159
+ }
1160
+ if (run.status === "merge_ready") {
1161
+ return {
1162
+ tone: "green",
1163
+ text: "Review the final evidence and merge the pull request when ready.",
1164
+ };
1165
+ }
1166
+ if (run.controlState === "waiting_human" || run.status === "waiting_human") {
1167
+ return { tone: "yellow", text: "Press i, type your guidance or approval, then press Enter." };
1168
+ }
1169
+ const recoveryAction = primaryRecoveryAction(run);
1170
+ if (recoveryAction) {
1171
+ const command = recoveryAction.command ? ` Run: ${recoveryAction.command}` : "";
1172
+ return {
1173
+ tone: ["blocked", "failed"].includes(run.status) ? "red" : "yellow",
1174
+ text: `${recoveryAction.label || "Resolve the reported issue."}${command}`,
1175
+ };
1176
+ }
1177
+ if (["blocked", "failed"].includes(run.status)) {
1178
+ return { tone: "red", text: "Press i to revise the request or provide recovery guidance." };
1179
+ }
1180
+ if (run.controlState === "stopped" || run.status === "stopped") {
1181
+ return { tone: undefined, text: "Press / and enter /new to start another task." };
1182
+ }
1183
+ return null;
1184
+ }
1185
+
1186
+ function primaryRecoveryAction(run) {
1187
+ const events = Array.isArray(run.snapshot?.events) ? [...run.snapshot.events].reverse() : [];
1188
+ for (const event of events) {
1189
+ const recovery = event?.detail?.recovery;
1190
+ if (!recovery || !Array.isArray(recovery.actions)) continue;
1191
+ return (
1192
+ recovery.actions.find((action) => action.id === recovery.primary_action_id) ??
1193
+ recovery.actions[0] ??
1194
+ null
1195
+ );
1196
+ }
1197
+ return null;
1198
+ }
1199
+
1200
+ function titleCase(value) {
1201
+ return value.replace(/\b\p{L}/gu, (character) => character.toUpperCase());
1202
+ }
@@ -47,6 +47,7 @@ export function desktopExecutorCapability(inspection) {
47
47
 
48
48
  export async function resolveDesktopExecutorCommand({
49
49
  backend,
50
+ model = null,
50
51
  instruction,
51
52
  repositoryPath,
52
53
  executionKind = "agentic_loop",
@@ -61,6 +62,13 @@ export async function resolveDesktopExecutorCommand({
61
62
  throw new Error("Desktop execution kind must be agentic_loop or direct.");
62
63
  }
63
64
  const normalizedPermissionMode = normalizePermissionMode(permissionMode);
65
+ const normalizedModel =
66
+ model === null || model === undefined
67
+ ? null
68
+ : String(model).trim();
69
+ if (normalizedModel !== null && !/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,159}$/.test(normalizedModel)) {
70
+ throw new Error("Desktop executor model is invalid.");
71
+ }
64
72
  const target = normalizeDesktopExecutionTarget(DESKTOP_EXECUTION_TARGET);
65
73
  const override =
66
74
  provider === "claude" ? environment.SIMY_CLAUDE_COMMAND : environment.SIMY_CODEX_COMMAND;
@@ -87,14 +95,20 @@ export async function resolveDesktopExecutorCommand({
87
95
  args:
88
96
  provider === "claude"
89
97
  ? directExecution
90
- ? claudeDirectBackendArgs(instruction, normalizedPermissionMode)
98
+ ? claudeDirectBackendArgs(
99
+ instruction,
100
+ normalizedPermissionMode,
101
+ normalizedModel,
102
+ )
91
103
  : claudeBackendArgs(instruction)
92
104
  : directExecution
93
105
  ? [
94
106
  "exec",
95
107
  "--json",
108
+ "--skip-git-repo-check",
96
109
  "--sandbox",
97
110
  normalizedPermissionMode === "read_only" ? "read-only" : "workspace-write",
111
+ ...(normalizedModel ? ["--model", normalizedModel] : []),
98
112
  instruction,
99
113
  ]
100
114
  : ["exec", "--json", instruction],
@@ -123,7 +137,11 @@ export function claudeBackendArgs(instruction) {
123
137
  ];
124
138
  }
125
139
 
126
- export function claudeDirectBackendArgs(instruction, permissionMode = "read_only") {
140
+ export function claudeDirectBackendArgs(
141
+ instruction,
142
+ permissionMode = "read_only",
143
+ model = null,
144
+ ) {
127
145
  const normalized = normalizePermissionMode(permissionMode);
128
146
  return [
129
147
  "-p",
@@ -131,6 +149,7 @@ export function claudeDirectBackendArgs(instruction, permissionMode = "read_only
131
149
  "--output-format",
132
150
  "stream-json",
133
151
  "--verbose",
152
+ ...(model ? ["--model", model] : []),
134
153
  ...(normalized === "read_only"
135
154
  ? ["--permission-mode", "plan"]
136
155
  : ["--dangerously-skip-permissions"]),