@robota-sdk/agent-cli 3.0.0-beta.61 → 3.0.0-beta.62

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.
@@ -28,8 +28,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
 
30
30
  // src/index.ts
31
- var index_exports = {};
32
- __export(index_exports, {
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
33
  ChildProcessSubagentRunner: () => ChildProcessSubagentRunner,
34
34
  GitWorktreeIsolationAdapter: () => GitWorktreeIsolationAdapter,
35
35
  createChildProcessSubagentRunnerFactory: () => createChildProcessSubagentRunnerFactory,
@@ -37,7 +37,7 @@ __export(index_exports, {
37
37
  createManagedShellProcessRunner: () => createManagedShellProcessRunner,
38
38
  startCli: () => startCli
39
39
  });
40
- module.exports = __toCommonJS(index_exports);
40
+ module.exports = __toCommonJS(src_exports);
41
41
 
42
42
  // src/cli.ts
43
43
  var import_node_fs8 = require("fs");
@@ -60,6 +60,7 @@ var import_agent_command_rewind = require("@robota-sdk/agent-command-rewind");
60
60
  var import_agent_command_statusline = require("@robota-sdk/agent-command-statusline");
61
61
  var import_agent_command_session = require("@robota-sdk/agent-command-session");
62
62
  var import_agent_command_skills = require("@robota-sdk/agent-command-skills");
63
+ var import_agent_command_user_local2 = require("@robota-sdk/agent-command-user-local");
63
64
  var import_agent_sdk15 = require("@robota-sdk/agent-sdk");
64
65
 
65
66
  // src/utils/cli-args.ts
@@ -98,6 +99,9 @@ function parseCliArgs() {
98
99
  "fork-session": { type: "boolean", default: false },
99
100
  name: { type: "string", short: "n" },
100
101
  "output-format": { type: "string" },
102
+ format: { type: "string" },
103
+ summary: { type: "string" },
104
+ source: { type: "string" },
101
105
  "system-prompt": { type: "string" },
102
106
  "append-system-prompt": { type: "string" },
103
107
  "task-file": { type: "string" },
@@ -117,7 +121,10 @@ function parseCliArgs() {
117
121
  "set-current": { type: "boolean", default: false },
118
122
  "settings-scope": { type: "string" },
119
123
  "check-update": { type: "boolean", default: false },
120
- "disable-update-check": { type: "boolean", default: false }
124
+ "disable-update-check": { type: "boolean", default: false },
125
+ web: { type: "boolean", default: false },
126
+ "web-port": { type: "string" },
127
+ "no-open": { type: "boolean", default: false }
121
128
  }
122
129
  });
123
130
  return {
@@ -132,6 +139,9 @@ function parseCliArgs() {
132
139
  forkSession: values["fork-session"] ?? false,
133
140
  sessionName: values["name"],
134
141
  outputFormat: values["output-format"],
142
+ format: values["format"],
143
+ summary: values["summary"],
144
+ source: values["source"],
135
145
  systemPrompt: values["system-prompt"],
136
146
  appendSystemPrompt: values["append-system-prompt"],
137
147
  taskFile: values["task-file"],
@@ -151,9 +161,23 @@ function parseCliArgs() {
151
161
  setCurrent: values["set-current"] ?? false,
152
162
  settingsScope: values["settings-scope"],
153
163
  checkUpdate: values["check-update"] ?? false,
154
- disableUpdateCheck: values["disable-update-check"] ?? false
164
+ disableUpdateCheck: values["disable-update-check"] ?? false,
165
+ web: values["web"] ?? false,
166
+ webPort: parseWebPort(values["web-port"]),
167
+ noOpen: values["no-open"] ?? false
155
168
  };
156
169
  }
170
+ var DEFAULT_WEB_PORT = 7070;
171
+ function parseWebPort(raw) {
172
+ if (raw === void 0) return DEFAULT_WEB_PORT;
173
+ const n = parseInt(raw, 10);
174
+ if (isNaN(n) || n < 1 || n > 65535) {
175
+ process.stderr.write(`Invalid --web-port "${raw}". Must be 1\u201365535.
176
+ `);
177
+ process.exit(1);
178
+ }
179
+ return n;
180
+ }
157
181
 
158
182
  // src/utils/settings-io.ts
159
183
  var import_node_fs = require("fs");
@@ -658,67 +682,60 @@ function formatConfigureProviderExample(definition) {
658
682
  var import_agent_transport_headless = require("@robota-sdk/agent-transport-headless");
659
683
 
660
684
  // src/ui/render.tsx
661
- var import_ink22 = require("ink");
685
+ var import_ink24 = require("ink");
662
686
 
663
687
  // src/ui/App.tsx
664
- var import_react19 = require("react");
665
- var import_ink21 = require("ink");
688
+ var import_react20 = require("react");
689
+ var import_ink23 = require("ink");
666
690
  var import_agent_sdk9 = require("@robota-sdk/agent-sdk");
667
691
  var import_agent_core11 = require("@robota-sdk/agent-core");
668
692
 
669
693
  // src/ui/hooks/useInteractiveSession.ts
670
694
  var import_react2 = require("react");
695
+ var import_open = __toESM(require("open"), 1);
671
696
  var import_agent_sdk5 = require("@robota-sdk/agent-sdk");
672
- var import_agent_core3 = require("@robota-sdk/agent-core");
673
697
 
674
- // src/ui/background-task-view-model.ts
675
- var BACKGROUND_PREVIEW_LENGTH = 120;
676
- var BACKGROUND_PREVIEW_WHITESPACE = /\s+/g;
677
- var BACKGROUND_PREVIEW_SEPARATOR = " ";
678
- var SUCCESS_EXIT_CODE = 0;
679
- function toBackgroundTaskViewModel(state, partialText) {
680
- return {
681
- id: state.id,
682
- kind: state.kind,
683
- label: state.label,
684
- status: state.status,
685
- statusLabel: getBackgroundTaskStatusLabel(state),
686
- mode: state.mode,
687
- currentAction: state.currentAction,
688
- unread: state.unread,
689
- preview: trimBackgroundPreview(state.promptPreview ?? state.commandPreview) ?? "",
690
- resultPreview: trimBackgroundPreview(state.result?.output ?? partialText),
691
- errorPreview: trimBackgroundPreview(state.error?.message),
692
- startedAt: state.startedAt,
693
- lastActivityAt: state.lastActivityAt,
694
- timeoutReason: state.timeoutReason,
695
- exitCode: state.result?.exitCode,
696
- signalCode: state.result?.signalCode,
697
- worktreePath: state.worktreePath,
698
- branchName: state.branchName,
699
- worktreeStatus: state.worktreeStatus,
700
- worktreeNextAction: state.worktreeNextAction
701
- };
702
- }
703
- function getBackgroundTaskStatusLabel(state) {
704
- if (state.status === "failed" && state.timeoutReason) {
705
- if (state.timeoutReason === "idle" || state.timeoutReason === "max_runtime") {
706
- return "timed out";
707
- }
708
- return state.timeoutReason.replace(/_/g, " ");
709
- }
710
- return state.status;
711
- }
712
- function shouldHideAtNextUserTurn(state) {
713
- return state.status === "completed" && !state.error && (state.result?.exitCode === void 0 || state.result.exitCode === SUCCESS_EXIT_CODE) && !state.result?.signalCode && !state.worktreePath && !state.branchName;
714
- }
715
- function trimBackgroundPreview(value) {
716
- if (!value) return void 0;
717
- const preview = value.trim().replace(BACKGROUND_PREVIEW_WHITESPACE, BACKGROUND_PREVIEW_SEPARATOR);
718
- if (!preview) return void 0;
719
- return preview.length > BACKGROUND_PREVIEW_LENGTH ? `${preview.slice(0, BACKGROUND_PREVIEW_LENGTH)}...` : preview;
698
+ // src/web-sidecar/web-sidecar-server.ts
699
+ var import_node_http = require("http");
700
+ var import_ws = require("ws");
701
+ var import_agent_transport_ws = require("@robota-sdk/agent-transport-ws");
702
+ function startWebSidecarServer(session, port) {
703
+ return new Promise((resolve3, reject) => {
704
+ const httpServer = (0, import_node_http.createServer)((_req, res) => {
705
+ res.writeHead(200, { "Content-Type": "text/plain" });
706
+ res.end("Robota web monitor sidecar");
707
+ });
708
+ const wss = new import_ws.WebSocketServer({ server: httpServer });
709
+ wss.on("connection", (ws) => {
710
+ const send = (message) => {
711
+ if (ws.readyState === import_ws.WebSocket.OPEN) {
712
+ ws.send(JSON.stringify(message));
713
+ }
714
+ };
715
+ const { onMessage, cleanup } = (0, import_agent_transport_ws.createWsHandler)({ session, send });
716
+ ws.on("message", (data) => onMessage(String(data)));
717
+ ws.on("close", cleanup);
718
+ ws.on("error", cleanup);
719
+ const messages = session.getMessages();
720
+ send({ type: "messages", messages });
721
+ });
722
+ httpServer.on("error", reject);
723
+ httpServer.listen(port, "127.0.0.1", () => {
724
+ resolve3({
725
+ port,
726
+ stop: () => new Promise((res) => {
727
+ wss.close(() => {
728
+ httpServer.close(() => res());
729
+ });
730
+ })
731
+ });
732
+ });
733
+ });
720
734
  }
721
735
 
736
+ // src/ui/hooks/useInteractiveSession.ts
737
+ var import_agent_core3 = require("@robota-sdk/agent-core");
738
+
722
739
  // src/ui/tui-state-manager.ts
723
740
  var MAX_RENDERED_MESSAGES = 100;
724
741
  var STREAMING_DEBOUNCE_MS = 300;
@@ -750,13 +767,12 @@ var TuiStateManager = class {
750
767
  isAborting = false;
751
768
  pendingPrompt = null;
752
769
  contextState = { percentage: 0, usedTokens: 0, maxTokens: 0 };
753
- backgroundTasks = [];
770
+ executionWorkspaceSnapshot = null;
771
+ selectedExecutionEntryId;
754
772
  /** Called after any state change. React hook sets this to trigger re-render. */
755
773
  onChange = null;
756
774
  // ── Internal ──────────────────────────────────────────────────
757
775
  streamBuf = "";
758
- backgroundTextBuffers = /* @__PURE__ */ new Map();
759
- backgroundTasksHiddenOnNextTurn = /* @__PURE__ */ new Set();
760
776
  debouncedStreamNotify = createDebouncedNotify(() => this.notify(), STREAMING_DEBOUNCE_MS);
761
777
  notify() {
762
778
  this.onChange?.();
@@ -825,30 +841,6 @@ var TuiStateManager = class {
825
841
  maxTokens: state.maxTokens
826
842
  });
827
843
  };
828
- onBackgroundTaskEvent = (event) => {
829
- if ("task" in event) {
830
- this.upsertBackgroundTask(event.task);
831
- return;
832
- }
833
- if (event.type === "background_task_closed") {
834
- this.backgroundTextBuffers.delete(event.taskId);
835
- this.backgroundTasksHiddenOnNextTurn.delete(event.taskId);
836
- this.backgroundTasks = this.backgroundTasks.filter((task) => task.id !== event.taskId);
837
- this.notify();
838
- return;
839
- }
840
- if (event.type === "background_task_text_delta") {
841
- this.appendBackgroundTaskText(event.taskId, event.delta);
842
- return;
843
- }
844
- if (event.type === "background_task_tool_start") {
845
- this.updateBackgroundTaskAction(event.taskId, event.firstArg ?? event.toolName);
846
- return;
847
- }
848
- if (event.type === "background_task_tool_end") {
849
- this.updateBackgroundTaskAction(event.taskId, event.success ? void 0 : event.error);
850
- }
851
- };
852
844
  // ── State updates from external sources ───────────────────────
853
845
  /** Sync history from InteractiveSession */
854
846
  syncHistory(entries) {
@@ -885,49 +877,24 @@ var TuiStateManager = class {
885
877
  this.contextState = state;
886
878
  this.notify();
887
879
  }
888
- onUserTurnAccepted() {
889
- if (this.backgroundTasksHiddenOnNextTurn.size === 0) return;
890
- const visible = this.backgroundTasks.filter(
891
- (task) => !this.backgroundTasksHiddenOnNextTurn.has(task.id)
892
- );
893
- this.backgroundTasksHiddenOnNextTurn.clear();
894
- if (visible.length === this.backgroundTasks.length) return;
895
- this.backgroundTasks = visible;
896
- this.notify();
897
- }
898
- upsertBackgroundTask(state) {
899
- const partialText = state.result ? void 0 : this.backgroundTextBuffers.get(state.id);
900
- const viewModel = toBackgroundTaskViewModel(state, partialText);
901
- const index = this.backgroundTasks.findIndex((task) => task.id === state.id);
902
- if (index === -1) {
903
- this.backgroundTasks = [...this.backgroundTasks, viewModel];
904
- } else {
905
- const updated = [...this.backgroundTasks];
906
- updated[index] = viewModel;
907
- this.backgroundTasks = updated;
908
- }
909
- if (state.status === "completed" || state.status === "failed" || state.status === "cancelled") {
910
- this.backgroundTextBuffers.delete(state.id);
911
- }
912
- if (shouldHideAtNextUserTurn(state)) {
913
- this.backgroundTasksHiddenOnNextTurn.add(state.id);
914
- } else {
915
- this.backgroundTasksHiddenOnNextTurn.delete(state.id);
916
- }
917
- this.notify();
918
- }
919
- appendBackgroundTaskText(taskId, delta) {
920
- const nextText = `${this.backgroundTextBuffers.get(taskId) ?? ""}${delta}`;
921
- this.backgroundTextBuffers.set(taskId, nextText);
922
- this.backgroundTasks = this.backgroundTasks.map(
923
- (task) => task.id === taskId ? { ...task, resultPreview: trimBackgroundPreview(nextText) } : task
924
- );
880
+ syncExecutionWorkspaceSnapshot(snapshot) {
881
+ const currentSelection = this.selectedExecutionEntryId;
882
+ const hasCurrentSelection = currentSelection !== void 0 && snapshot.entries.some((entry) => entry.id === currentSelection);
883
+ const selectedExecutionEntryId = hasCurrentSelection ? currentSelection : snapshot.selectedEntryId ?? snapshot.entries[0]?.id;
884
+ this.executionWorkspaceSnapshot = {
885
+ ...snapshot,
886
+ ...selectedExecutionEntryId ? { selectedEntryId: selectedExecutionEntryId } : {}
887
+ };
888
+ this.selectedExecutionEntryId = selectedExecutionEntryId;
925
889
  this.notify();
926
890
  }
927
- updateBackgroundTaskAction(taskId, currentAction) {
928
- this.backgroundTasks = this.backgroundTasks.map(
929
- (task) => task.id === taskId ? { ...task, currentAction } : task
930
- );
891
+ selectExecutionWorkspaceEntry(entryId) {
892
+ if (!this.executionWorkspaceSnapshot?.entries.some((entry) => entry.id === entryId)) return;
893
+ this.selectedExecutionEntryId = entryId;
894
+ this.executionWorkspaceSnapshot = {
895
+ ...this.executionWorkspaceSnapshot,
896
+ selectedEntryId: entryId
897
+ };
931
898
  this.notify();
932
899
  }
933
900
  };
@@ -965,7 +932,6 @@ function reloadPluginCommandSource(registry) {
965
932
  function useSlashRouting(interactiveSession, registry, manager, commandEffectQueue) {
966
933
  return (0, import_react.useCallback)(
967
934
  async (input) => {
968
- manager.onUserTurnAccepted();
969
935
  if (!input.startsWith("/")) {
970
936
  await interactiveSession.submit(input);
971
937
  manager.setPendingPrompt(interactiveSession.getPendingPrompt());
@@ -1050,6 +1016,16 @@ function applyCompactEventToManager(interactiveSession, manager) {
1050
1016
  function applySkillActivationEventToManager(interactiveSession, manager) {
1051
1017
  manager.syncHistory(interactiveSession.getFullHistory());
1052
1018
  }
1019
+ function syncExecutionWorkspaceFromSession(interactiveSession, manager) {
1020
+ try {
1021
+ manager.syncExecutionWorkspaceSnapshot(
1022
+ interactiveSession.getExecutionWorkspaceSnapshot({
1023
+ selectedEntryId: manager.selectedExecutionEntryId
1024
+ })
1025
+ );
1026
+ } catch {
1027
+ }
1028
+ }
1053
1029
  function initializeSession(props, permissionHandler) {
1054
1030
  const interactiveSession = new import_agent_sdk5.InteractiveSession({
1055
1031
  cwd: props.cwd,
@@ -1120,9 +1096,32 @@ function useInteractiveSession(props) {
1120
1096
  manager.syncHistory(restored);
1121
1097
  }
1122
1098
  }
1099
+ (0, import_react2.useEffect)(() => {
1100
+ if (!props.webPort) return;
1101
+ const port = props.webPort;
1102
+ let stopped = false;
1103
+ let stopFn = null;
1104
+ startWebSidecarServer(interactiveSession, port).then((server) => {
1105
+ stopFn = server.stop;
1106
+ if (stopped) {
1107
+ server.stop().catch(() => void 0);
1108
+ return;
1109
+ }
1110
+ const shouldOpen = !props.noOpen && !process.env["ROBOTA_NO_OPEN"];
1111
+ const monitorUrl = process.env["ROBOTA_MONITOR_URL"] ?? "http://localhost:7071/monitor";
1112
+ if (shouldOpen) {
1113
+ (0, import_open.default)(monitorUrl).catch(() => void 0);
1114
+ }
1115
+ }).catch(() => void 0);
1116
+ return () => {
1117
+ stopped = true;
1118
+ if (stopFn) stopFn().catch(() => void 0);
1119
+ };
1120
+ }, [interactiveSession, props.webPort, props.noOpen]);
1123
1121
  (0, import_react2.useEffect)(() => {
1124
1122
  const onCompact = () => applyCompactEventToManager(interactiveSession, manager);
1125
1123
  const onSkillActivation = () => applySkillActivationEventToManager(interactiveSession, manager);
1124
+ const onExecutionWorkspaceEvent = (event) => manager.syncExecutionWorkspaceSnapshot(event.snapshot);
1126
1125
  interactiveSession.on("text_delta", manager.onTextDelta);
1127
1126
  interactiveSession.on("tool_start", manager.onToolStart);
1128
1127
  interactiveSession.on("tool_end", manager.onToolEnd);
@@ -1133,7 +1132,7 @@ function useInteractiveSession(props) {
1133
1132
  interactiveSession.on("context_update", manager.onContextUpdate);
1134
1133
  interactiveSession.on("compact", onCompact);
1135
1134
  interactiveSession.on("skill_activation", onSkillActivation);
1136
- interactiveSession.on("background_task_event", manager.onBackgroundTaskEvent);
1135
+ interactiveSession.on("execution_workspace_event", onExecutionWorkspaceEvent);
1137
1136
  const initCheck = setInterval(() => {
1138
1137
  try {
1139
1138
  const ctx = interactiveSession.getContextState();
@@ -1146,6 +1145,7 @@ function useInteractiveSession(props) {
1146
1145
  if (restored.length > 0) {
1147
1146
  manager.syncHistory(restored);
1148
1147
  }
1148
+ syncExecutionWorkspaceFromSession(interactiveSession, manager);
1149
1149
  clearInterval(initCheck);
1150
1150
  } catch {
1151
1151
  }
@@ -1162,11 +1162,12 @@ function useInteractiveSession(props) {
1162
1162
  interactiveSession.off("context_update", manager.onContextUpdate);
1163
1163
  interactiveSession.off("compact", onCompact);
1164
1164
  interactiveSession.off("skill_activation", onSkillActivation);
1165
- interactiveSession.off("background_task_event", manager.onBackgroundTaskEvent);
1165
+ interactiveSession.off("execution_workspace_event", onExecutionWorkspaceEvent);
1166
1166
  };
1167
1167
  }, [interactiveSession, manager]);
1168
1168
  (0, import_react2.useEffect)(() => {
1169
1169
  manager.syncHistory(interactiveSession.getFullHistory());
1170
+ syncExecutionWorkspaceFromSession(interactiveSession, manager);
1170
1171
  if (!manager.isThinking) {
1171
1172
  manager.setPendingPrompt(interactiveSession.getPendingPrompt());
1172
1173
  }
@@ -1189,6 +1190,14 @@ function useInteractiveSession(props) {
1189
1190
  },
1190
1191
  [interactiveSession, manager, isShuttingDown]
1191
1192
  );
1193
+ const selectExecutionWorkspaceEntry = (0, import_react2.useCallback)(
1194
+ (entryId) => manager.selectExecutionWorkspaceEntry(entryId),
1195
+ [manager]
1196
+ );
1197
+ const readExecutionWorkspaceDetail = (0, import_react2.useCallback)(
1198
+ (entryId) => interactiveSession.readExecutionWorkspaceDetail(entryId),
1199
+ [interactiveSession]
1200
+ );
1192
1201
  return {
1193
1202
  interactiveSession,
1194
1203
  registry,
@@ -1201,13 +1210,16 @@ function useInteractiveSession(props) {
1201
1210
  isAborting: manager.isAborting,
1202
1211
  isShuttingDown,
1203
1212
  pendingPrompt: manager.pendingPrompt,
1204
- backgroundTasks: manager.backgroundTasks,
1213
+ executionWorkspaceSnapshot: manager.executionWorkspaceSnapshot,
1214
+ selectedExecutionEntryId: manager.selectedExecutionEntryId,
1205
1215
  permissionRequest,
1206
1216
  contextState: manager.contextState,
1207
1217
  handleSubmit,
1208
1218
  handleAbort,
1209
1219
  handleCancelQueue,
1210
- handleShutdown
1220
+ handleShutdown,
1221
+ selectExecutionWorkspaceEntry,
1222
+ readExecutionWorkspaceDetail
1211
1223
  };
1212
1224
  }
1213
1225
 
@@ -1889,7 +1901,7 @@ function formatUsageTokenCount(tokens) {
1889
1901
 
1890
1902
  // src/ui/command-output-summary.ts
1891
1903
  var MAX_PREVIEW_LINES = 4;
1892
- var SUCCESS_EXIT_CODE2 = 0;
1904
+ var SUCCESS_EXIT_CODE = 0;
1893
1905
  var COMMAND_TOOL_NAMES = /* @__PURE__ */ new Set(["Bash", "BackgroundProcess"]);
1894
1906
  function formatCommandOutputSummary(tool) {
1895
1907
  if (!COMMAND_TOOL_NAMES.has(tool.toolName) || !tool.toolResultData) return void 0;
@@ -1900,7 +1912,7 @@ function formatCommandOutputSummary(tool) {
1900
1912
  const lines = trimTrailingBlankLines(splitOutputLines(output));
1901
1913
  const previewLines = lines.slice(0, MAX_PREVIEW_LINES);
1902
1914
  const omittedLineCount = Math.max(0, lines.length - previewLines.length);
1903
- const isFailed = tool.result === "error" || successValue === false || exitCode !== void 0 && exitCode !== SUCCESS_EXIT_CODE2;
1915
+ const isFailed = tool.result === "error" || successValue === false || exitCode !== void 0 && exitCode !== SUCCESS_EXIT_CODE;
1904
1916
  return {
1905
1917
  status: isFailed ? "error" : "success",
1906
1918
  statusLabel: formatStatusLabel(isFailed, exitCode),
@@ -1930,7 +1942,7 @@ function buildOutputText(raw, parsed) {
1930
1942
  return lines.join("\n");
1931
1943
  }
1932
1944
  function formatStatusLabel(isFailed, exitCode) {
1933
- if (exitCode !== void 0 && exitCode !== SUCCESS_EXIT_CODE2) return `exit ${exitCode}`;
1945
+ if (exitCode !== void 0 && exitCode !== SUCCESS_EXIT_CODE) return `exit ${exitCode}`;
1934
1946
  return isFailed ? "error" : "ok";
1935
1947
  }
1936
1948
  function splitOutputLines(output) {
@@ -4182,115 +4194,281 @@ function SessionPicker({
4182
4194
  // src/ui/BackgroundTaskPanel.tsx
4183
4195
  var import_ink19 = require("ink");
4184
4196
 
4185
- // src/ui/background-task-row-format.ts
4186
- var MS_PER_SECOND = 1e3;
4187
- var SECONDS_PER_MINUTE = 60;
4188
- var MINUTES_PER_HOUR = 60;
4189
- var SUCCESS_EXIT_CODE3 = 0;
4190
- function formatBackgroundTaskRow(task, options = {}) {
4191
- const row = {
4192
- connector: options.isLast === false ? "\u251C" : "\u2514",
4193
- marker: getStatusMarker(task),
4194
- color: getStatusColor(task),
4195
- label: getTaskLabel(task),
4196
- segments: getTaskSegments(task, options.now ?? Date.now()),
4197
- preview: getTaskPreview(task)
4198
- };
4199
- return {
4200
- ...row,
4201
- accessibleText: formatAccessibleText(row)
4202
- };
4197
+ // src/ui/execution-workspace-view-model.ts
4198
+ var ACTIVE_STATUSES = [
4199
+ "active",
4200
+ "queued",
4201
+ "running",
4202
+ "waiting_permission"
4203
+ ];
4204
+ var DETAIL_RECORD_TEXT_LIMIT = 160;
4205
+ var PREVIEW_WHITESPACE = /\s+/g;
4206
+ var PREVIEW_SEPARATOR = " ";
4207
+ function getDefaultBackgroundWorkspaceEntries(snapshot) {
4208
+ return (snapshot?.entries ?? []).filter(
4209
+ (entry) => entry.kind === "background_task" && entry.visibility === "default"
4210
+ );
4203
4211
  }
4204
- function getStatusColor(task) {
4205
- if (isFailedTask(task)) return "red";
4206
- if (task.status === "completed") return "green";
4207
- if (task.status === "cancelled") return "yellow";
4208
- return "cyan";
4212
+ function countActiveBackgroundWorkspaceEntries(snapshot) {
4213
+ return getDefaultBackgroundWorkspaceEntries(snapshot).filter(
4214
+ (entry) => ACTIVE_STATUSES.includes(entry.status)
4215
+ ).length;
4209
4216
  }
4210
- function getStatusMarker(task) {
4211
- if (task.status === "queued" || task.status === "running") return "\u25A1";
4212
- return "\u25A0";
4217
+ function formatExecutionWorkspaceEntryRow(entry, options = {}) {
4218
+ const isSelected = entry.id === options.selectedEntryId;
4219
+ const row = {
4220
+ id: entry.id,
4221
+ radio: isSelected ? "\u25CF" : "\u25CB",
4222
+ title: formatEntryTitle(entry),
4223
+ subtitle: formatEntrySubtitle(entry),
4224
+ statusLabel: formatStatusLabel2(entry.status),
4225
+ preview: trimPreview(entry.preview ?? entry.currentAction),
4226
+ color: getEntryColor(entry),
4227
+ isSelected
4228
+ };
4229
+ return { ...row, accessibleText: formatAccessibleText(row) };
4230
+ }
4231
+ function formatExecutionDetailRecord(record) {
4232
+ const text = record.text.trim().replace(PREVIEW_WHITESPACE, PREVIEW_SEPARATOR);
4233
+ if (!text) return record.kind;
4234
+ return text.length > DETAIL_RECORD_TEXT_LIMIT ? `${text.slice(0, DETAIL_RECORD_TEXT_LIMIT)}...` : text;
4235
+ }
4236
+ function formatEntryTitle(entry) {
4237
+ if (entry.kind === "main_thread") return entry.title;
4238
+ if (entry.kind === "background_group") return `${entry.title} group`;
4239
+ if (entry.taskKind === "agent") return `${entry.title} agent`;
4240
+ if (entry.taskKind === "process") return entry.title || "Process";
4241
+ return entry.title;
4242
+ }
4243
+ function formatEntrySubtitle(entry) {
4244
+ if (entry.kind === "main_thread") return entry.subtitle;
4245
+ const parts = [
4246
+ entry.taskKind,
4247
+ entry.subtitle,
4248
+ entry.attention === "none" ? void 0 : entry.attention
4249
+ ];
4250
+ return parts.filter((part) => typeof part === "string" && part.length > 0).join(" \xB7 ") || void 0;
4213
4251
  }
4214
- function getTaskLabel(task) {
4215
- if (task.kind === "agent") return `${task.label} agent`;
4216
- if (task.kind === "process") return task.label || "Process";
4217
- return task.label;
4252
+ function formatStatusLabel2(status) {
4253
+ return status.replace(/_/g, " ");
4218
4254
  }
4219
- function getTaskSegments(task, now) {
4220
- const segments = [];
4221
- if (task.status === "running") {
4222
- const idle = formatAge(task.lastActivityAt, now);
4223
- if (idle) segments.push(`idle ${idle}`);
4224
- }
4225
- if (task.status === "failed") {
4226
- segments.push(task.statusLabel === "timed out" ? "timed out" : "failed");
4227
- }
4228
- if (task.status === "cancelled") {
4229
- segments.push("cancelled");
4230
- }
4231
- if (task.timeoutReason) {
4232
- segments.push(task.timeoutReason);
4233
- }
4234
- if (task.status === "completed" && task.exitCode !== void 0 && task.exitCode !== SUCCESS_EXIT_CODE3) {
4235
- segments.push(`exit ${task.exitCode}`);
4236
- }
4237
- if (task.signalCode) {
4238
- segments.push(`signal ${task.signalCode}`);
4239
- }
4240
- if (task.worktreePath || task.branchName) {
4241
- segments.push("worktree");
4242
- }
4243
- return segments;
4255
+ function getEntryColor(entry) {
4256
+ if (entry.attention === "failed" || entry.status === "failed") return "red";
4257
+ if (entry.attention === "permission" || entry.status === "waiting_permission") return "yellow";
4258
+ if (entry.status === "completed") return "green";
4259
+ if (entry.status === "cancelled") return "yellow";
4260
+ if (ACTIVE_STATUSES.includes(entry.status)) return "cyan";
4261
+ return "white";
4244
4262
  }
4245
- function getTaskPreview(task) {
4246
- if (task.worktreeNextAction) return task.worktreeNextAction;
4247
- if (task.worktreePath) return task.worktreePath;
4248
- const preview = task.errorPreview ?? task.resultPreview ?? task.currentAction ?? task.preview;
4263
+ function trimPreview(value) {
4264
+ const preview = value?.trim().replace(PREVIEW_WHITESPACE, PREVIEW_SEPARATOR);
4249
4265
  return preview || void 0;
4250
4266
  }
4251
- function formatAge(iso, now) {
4252
- if (!iso) return void 0;
4253
- const timestamp = Date.parse(iso);
4254
- if (Number.isNaN(timestamp)) return void 0;
4255
- const seconds = Math.max(0, Math.floor((now - timestamp) / MS_PER_SECOND));
4256
- if (seconds < SECONDS_PER_MINUTE) return `${seconds}s`;
4257
- const minutes = Math.floor(seconds / SECONDS_PER_MINUTE);
4258
- if (minutes < MINUTES_PER_HOUR) return `${minutes}m`;
4259
- return `${Math.floor(minutes / MINUTES_PER_HOUR)}h`;
4267
+ function formatAccessibleText(row) {
4268
+ const parts = [row.radio, row.title, row.statusLabel, row.subtitle, row.preview];
4269
+ return parts.filter((part) => typeof part === "string" && part.length > 0).join(" \xB7 ");
4260
4270
  }
4261
- function isFailedTask(task) {
4262
- return task.status === "failed" || task.status === "completed" && (task.exitCode !== void 0 && task.exitCode !== SUCCESS_EXIT_CODE3 || !!task.signalCode);
4271
+
4272
+ // src/ui/background-task-row-format.ts
4273
+ function formatBackgroundTaskRow(entry, options = {}) {
4274
+ const row = formatExecutionWorkspaceEntryRow(entry);
4275
+ const marker = isActiveEntry(entry) ? "\u25A1" : "\u25A0";
4276
+ const segments = [row.statusLabel, row.subtitle].filter(
4277
+ (segment) => typeof segment === "string" && segment.length > 0
4278
+ );
4279
+ return {
4280
+ connector: options.isLast === false ? "\u251C" : "\u2514",
4281
+ marker,
4282
+ color: row.color,
4283
+ label: row.title,
4284
+ segments,
4285
+ preview: row.preview,
4286
+ accessibleText: [
4287
+ `${options.isLast === false ? "\u251C" : "\u2514"} ${marker} ${row.title}`,
4288
+ ...segments,
4289
+ row.preview
4290
+ ].filter((part) => typeof part === "string" && part.length > 0).join(" \xB7 ")
4291
+ };
4263
4292
  }
4264
- function formatAccessibleText(row) {
4265
- const parts = [`${row.connector} ${row.marker} ${row.label}`, ...row.segments];
4266
- if (row.preview) parts.push(row.preview);
4267
- return parts.join(" \xB7 ");
4293
+ function isActiveEntry(entry) {
4294
+ return entry.status === "active" || entry.status === "queued" || entry.status === "running" || entry.status === "waiting_permission";
4268
4295
  }
4269
4296
 
4270
4297
  // src/ui/BackgroundTaskPanel.tsx
4271
4298
  var import_jsx_runtime20 = require("react/jsx-runtime");
4272
- function BackgroundTaskPanel({ tasks }) {
4273
- if (tasks.length === 0) return null;
4299
+ function BackgroundTaskPanel({ entries }) {
4300
+ if (entries.length === 0) return null;
4274
4301
  return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_ink19.Box, { flexDirection: "column", marginBottom: 1, children: [
4275
4302
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_ink19.Text, { color: "cyan", bold: true, children: "Background work" }),
4276
- tasks.map((task, index) => {
4277
- const row = formatBackgroundTaskRow(task, { isLast: index === tasks.length - 1 });
4303
+ entries.map((entry, index) => {
4304
+ const row = formatBackgroundTaskRow(entry, { isLast: index === entries.length - 1 });
4278
4305
  return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_ink19.Text, { children: [
4279
4306
  `${row.connector} `,
4280
4307
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_ink19.Text, { color: row.color, children: row.marker }),
4281
4308
  ` ${row.label}`,
4282
4309
  row.segments.map((segment, segmentIndex) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_ink19.Text, { dimColor: true, children: ` \xB7 ${segment}` }, `${segment}-${segmentIndex}`)),
4283
4310
  row.preview ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_ink19.Text, { dimColor: true, children: ` \xB7 ${row.preview}` }) : null
4284
- ] }, task.id);
4311
+ ] }, entry.id);
4285
4312
  })
4286
4313
  ] });
4287
4314
  }
4288
4315
 
4289
- // src/ui/UpdateNotice.tsx
4316
+ // src/ui/ExecutionWorkspaceSwitcher.tsx
4317
+ var import_react19 = require("react");
4290
4318
  var import_ink20 = require("ink");
4291
4319
  var import_jsx_runtime21 = require("react/jsx-runtime");
4320
+ var MAX_VISIBLE_WORKSPACE_ENTRIES = 8;
4321
+ function ExecutionWorkspaceSwitcher({
4322
+ snapshot,
4323
+ selectedEntryId,
4324
+ onSelect,
4325
+ onClose
4326
+ }) {
4327
+ const entries = [...snapshot?.entries ?? []];
4328
+ const { normalized, visibleEntries, applyAction } = useWorkspaceSwitcherSelection({
4329
+ entries,
4330
+ selectedEntryId,
4331
+ onSelect,
4332
+ onClose
4333
+ });
4334
+ (0, import_ink20.useInput)((_input, key) => {
4335
+ const action = getVerticalSelectionInputAction(key);
4336
+ if (action !== void 0) applyAction(action);
4337
+ });
4338
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_ink20.Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
4339
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_ink20.Text, { color: "cyan", bold: true, children: "Execution workspace" }),
4340
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_ink20.Box, { flexDirection: "column", marginTop: 1, children: visibleEntries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_ink20.Text, { dimColor: true, children: "No workspace entries" }) : visibleEntries.map((entry, index) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4341
+ ExecutionWorkspaceSwitcherRow,
4342
+ {
4343
+ entry,
4344
+ isFocused: normalized.scrollOffset + index === normalized.selectedIndex,
4345
+ selectedEntryId
4346
+ },
4347
+ entry.id
4348
+ )) }),
4349
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_ink20.Text, { dimColor: true, children: "Ctrl+B Close \u2191\u2193 Navigate Enter Switch Esc Close" })
4350
+ ] });
4351
+ }
4352
+ function useWorkspaceSwitcherSelection({
4353
+ entries,
4354
+ selectedEntryId,
4355
+ onSelect,
4356
+ onClose
4357
+ }) {
4358
+ const [state, setState] = (0, import_react19.useState)(() => createSelectionFlowState());
4359
+ const stateRef = (0, import_react19.useRef)(state);
4360
+ (0, import_react19.useEffect)(() => {
4361
+ const selectedIndex = Math.max(
4362
+ 0,
4363
+ entries.findIndex((entry) => entry.id === selectedEntryId)
4364
+ );
4365
+ const nextState = createNormalizedSelection({ selectedIndex, itemCount: entries.length });
4366
+ stateRef.current = nextState;
4367
+ setState(nextState);
4368
+ }, [entries.length, selectedEntryId]);
4369
+ const normalized = createNormalizedSelection({
4370
+ selectedIndex: state.selectedIndex,
4371
+ scrollOffset: state.scrollOffset,
4372
+ itemCount: entries.length
4373
+ });
4374
+ if (normalized !== state) stateRef.current = normalized;
4375
+ return {
4376
+ normalized,
4377
+ visibleEntries: entries.slice(
4378
+ normalized.scrollOffset,
4379
+ normalized.scrollOffset + MAX_VISIBLE_WORKSPACE_ENTRIES
4380
+ ),
4381
+ applyAction: createApplyAction({ entries, stateRef, setState, onSelect, onClose })
4382
+ };
4383
+ }
4384
+ function createApplyAction({
4385
+ entries,
4386
+ stateRef,
4387
+ setState,
4388
+ onSelect,
4389
+ onClose
4390
+ }) {
4391
+ return (action) => {
4392
+ const result = applySelectionInput(stateRef.current, action, {
4393
+ itemCount: entries.length,
4394
+ maxVisible: MAX_VISIBLE_WORKSPACE_ENTRIES
4395
+ });
4396
+ const nextState = result.effect.type === "select" || result.effect.type === "cancel" ? { ...result.state, resolved: false } : result.state;
4397
+ stateRef.current = nextState;
4398
+ setState(nextState);
4399
+ if (result.effect.type === "cancel") {
4400
+ onClose();
4401
+ } else if (result.effect.type === "select") {
4402
+ const entry = entries[result.effect.index];
4403
+ if (entry) onSelect(entry.id);
4404
+ }
4405
+ };
4406
+ }
4407
+ function createNormalizedSelection(input) {
4408
+ return normalizeSelectionState(
4409
+ {
4410
+ selectedIndex: input.selectedIndex,
4411
+ scrollOffset: input.scrollOffset ?? 0,
4412
+ resolved: false
4413
+ },
4414
+ { itemCount: input.itemCount, maxVisible: MAX_VISIBLE_WORKSPACE_ENTRIES }
4415
+ );
4416
+ }
4417
+ function ExecutionWorkspaceSwitcherRow({
4418
+ entry,
4419
+ isFocused,
4420
+ selectedEntryId
4421
+ }) {
4422
+ const row = formatExecutionWorkspaceEntryRow(entry, { selectedEntryId });
4423
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_ink20.Text, { children: [
4424
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_ink20.Text, { color: isFocused ? "cyan" : void 0, bold: isFocused, children: isFocused ? "> " : " " }),
4425
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_ink20.Text, { color: row.color, children: row.radio }),
4426
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_ink20.Text, { color: isFocused ? "cyan" : void 0, bold: isFocused, children: ` ${row.title}` }),
4427
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_ink20.Text, { dimColor: true, children: ` \xB7 ${row.statusLabel}` }),
4428
+ row.subtitle ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_ink20.Text, { dimColor: true, children: ` \xB7 ${row.subtitle}` }) : null,
4429
+ row.preview ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_ink20.Text, { dimColor: true, children: ` \xB7 ${row.preview}` }) : null
4430
+ ] });
4431
+ }
4432
+
4433
+ // src/ui/ExecutionWorkspaceDetailPane.tsx
4434
+ var import_ink21 = require("ink");
4435
+ var import_jsx_runtime22 = require("react/jsx-runtime");
4436
+ var MAX_VISIBLE_DETAIL_RECORDS = 12;
4437
+ function ExecutionWorkspaceDetailPane({
4438
+ entry,
4439
+ page,
4440
+ loading,
4441
+ error
4442
+ }) {
4443
+ const row = formatExecutionWorkspaceEntryRow(entry, { selectedEntryId: entry.id });
4444
+ const records = page?.records.slice(-MAX_VISIBLE_DETAIL_RECORDS) ?? [];
4445
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_ink21.Box, { flexDirection: "column", marginBottom: 1, children: [
4446
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ink21.Text, { color: "cyan", bold: true, children: `Viewing ${row.title}` }),
4447
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_ink21.Text, { dimColor: true, children: [
4448
+ row.statusLabel,
4449
+ row.subtitle ? ` \xB7 ${row.subtitle}` : "",
4450
+ row.preview ? ` \xB7 ${row.preview}` : ""
4451
+ ] }),
4452
+ loading ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ink21.Text, { dimColor: true, children: "Loading workspace detail..." }) : null,
4453
+ error ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ink21.Text, { color: "red", children: error }) : null,
4454
+ !loading && !error && records.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ink21.Text, { dimColor: true, children: "No detail yet" }) : null,
4455
+ !loading && !error && records.map((record) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ink21.Text, { color: getDetailRecordColor(record.kind), children: formatExecutionDetailRecord(record) }, record.id)),
4456
+ page?.nextCursor ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ink21.Text, { dimColor: true, children: "... more detail available" }) : null
4457
+ ] });
4458
+ }
4459
+ function getDetailRecordColor(kind) {
4460
+ if (kind === "error") return "red";
4461
+ if (kind === "result") return "green";
4462
+ if (kind === "process_output") return "white";
4463
+ if (kind === "group_summary") return "cyan";
4464
+ return void 0;
4465
+ }
4466
+
4467
+ // src/ui/UpdateNotice.tsx
4468
+ var import_ink22 = require("ink");
4469
+ var import_jsx_runtime23 = require("react/jsx-runtime");
4292
4470
  function UpdateNotice({ message }) {
4293
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_ink20.Box, { paddingX: 1, marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_ink20.Text, { color: "yellow", children: message }) });
4471
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ink22.Box, { paddingX: 1, marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ink22.Text, { color: "yellow", children: message }) });
4294
4472
  }
4295
4473
 
4296
4474
  // src/utils/update-check.ts
@@ -4385,10 +4563,10 @@ function comparePrereleaseIdentifier(left, right) {
4385
4563
  var CLI_UPDATE_PACKAGE_NAME = "@robota-sdk/agent-cli";
4386
4564
  var CLI_UPDATE_REGISTRY_URL = "https://registry.npmjs.org";
4387
4565
  var HOURS_PER_DAY = 24;
4388
- var MINUTES_PER_HOUR2 = 60;
4389
- var SECONDS_PER_MINUTE2 = 60;
4390
- var MS_PER_SECOND2 = 1e3;
4391
- var CLI_UPDATE_CACHE_TTL_MS = HOURS_PER_DAY * MINUTES_PER_HOUR2 * SECONDS_PER_MINUTE2 * MS_PER_SECOND2;
4566
+ var MINUTES_PER_HOUR = 60;
4567
+ var SECONDS_PER_MINUTE = 60;
4568
+ var MS_PER_SECOND = 1e3;
4569
+ var CLI_UPDATE_CACHE_TTL_MS = HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MS_PER_SECOND;
4392
4570
  var CLI_UPDATE_TIMEOUT_MS = 1500;
4393
4571
  var DEFAULT_INSTALL_COMMAND = "npm install -g '@robota-sdk/agent-cli@latest'";
4394
4572
  function getUserUpdateCheckCachePath(home = process.env.HOME ?? process.env.USERPROFILE ?? "/") {
@@ -4557,13 +4735,13 @@ function isJsonObject(value) {
4557
4735
  }
4558
4736
 
4559
4737
  // src/ui/App.tsx
4560
- var import_jsx_runtime22 = require("react/jsx-runtime");
4738
+ var import_jsx_runtime24 = require("react/jsx-runtime");
4561
4739
  function App(props) {
4562
- const [activeSessionId, setActiveSessionId] = (0, import_react19.useState)(props.resumeSessionId);
4563
- const [showInitialSessionPicker, setShowInitialSessionPicker] = (0, import_react19.useState)(
4740
+ const [activeSessionId, setActiveSessionId] = (0, import_react20.useState)(props.resumeSessionId);
4741
+ const [showInitialSessionPicker, setShowInitialSessionPicker] = (0, import_react20.useState)(
4564
4742
  props.showSessionPickerOnStart ?? false
4565
4743
  );
4566
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
4744
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
4567
4745
  AppInner,
4568
4746
  {
4569
4747
  ...props,
@@ -4591,7 +4769,10 @@ function AppInner(props) {
4591
4769
  isAborting,
4592
4770
  isShuttingDown,
4593
4771
  pendingPrompt,
4594
- backgroundTasks,
4772
+ executionWorkspaceSnapshot,
4773
+ selectedExecutionEntryId,
4774
+ selectExecutionWorkspaceEntry,
4775
+ readExecutionWorkspaceDetail,
4595
4776
  permissionRequest,
4596
4777
  contextState,
4597
4778
  handleSubmit: baseHandleSubmit,
@@ -4610,17 +4791,31 @@ function AppInner(props) {
4610
4791
  backgroundTaskRunners: props.backgroundTaskRunners,
4611
4792
  subagentRunnerFactory: props.subagentRunnerFactory,
4612
4793
  commandModules: props.commandModules,
4613
- commandHostAdapters: props.commandHostAdapters
4794
+ commandHostAdapters: props.commandHostAdapters,
4795
+ webPort: props.webPort,
4796
+ noOpen: props.noOpen
4614
4797
  });
4615
4798
  const fallbackPluginCallbacks = usePluginCallbacks(cwd);
4616
4799
  const pluginCallbacks = props.commandHostAdapters?.plugin ?? fallbackPluginCallbacks;
4617
- const { exit } = (0, import_ink21.useApp)();
4618
- const [sessionName, setSessionName] = (0, import_react19.useState)(props.sessionName);
4619
- const [updateNotice, setUpdateNotice] = (0, import_react19.useState)();
4800
+ const { exit } = (0, import_ink23.useApp)();
4801
+ const [sessionName, setSessionName] = (0, import_react20.useState)(props.sessionName);
4802
+ const [updateNotice, setUpdateNotice] = (0, import_react20.useState)();
4803
+ const [showExecutionWorkspaceSwitcher, setShowExecutionWorkspaceSwitcher] = (0, import_react20.useState)(false);
4804
+ const [executionDetailPage, setExecutionDetailPage] = (0, import_react20.useState)(null);
4805
+ const [executionDetailError, setExecutionDetailError] = (0, import_react20.useState)();
4806
+ const [isExecutionDetailLoading, setIsExecutionDetailLoading] = (0, import_react20.useState)(false);
4620
4807
  const [statusLineSettings, setStatusLineSettings] = useStatusLineSettings();
4621
- const activeBackgroundTaskCount = backgroundTasks.filter(
4622
- (task) => task.status === "queued" || task.status === "running"
4623
- ).length;
4808
+ const backgroundWorkspaceEntries = (0, import_react20.useMemo)(
4809
+ () => getDefaultBackgroundWorkspaceEntries(executionWorkspaceSnapshot),
4810
+ [executionWorkspaceSnapshot]
4811
+ );
4812
+ const activeBackgroundTaskCount = countActiveBackgroundWorkspaceEntries(
4813
+ executionWorkspaceSnapshot
4814
+ );
4815
+ const selectedExecutionEntry = (0, import_react20.useMemo)(
4816
+ () => executionWorkspaceSnapshot?.entries.find((entry) => entry.id === selectedExecutionEntryId),
4817
+ [executionWorkspaceSnapshot, selectedExecutionEntryId]
4818
+ );
4624
4819
  const {
4625
4820
  handleSubmit,
4626
4821
  pendingModelId,
@@ -4643,11 +4838,11 @@ function AppInner(props) {
4643
4838
  setStatusLineSettings,
4644
4839
  showSessionPickerOnStart: props.showSessionPickerOnStart
4645
4840
  });
4646
- (0, import_react19.useEffect)(() => {
4841
+ (0, import_react20.useEffect)(() => {
4647
4842
  const name = interactiveSession?.getName?.();
4648
4843
  if (name && !sessionName) setSessionName(name);
4649
4844
  }, [interactiveSession, sessionName]);
4650
- (0, import_react19.useEffect)(() => {
4845
+ (0, import_react20.useEffect)(() => {
4651
4846
  let isMounted = true;
4652
4847
  props.startupUpdateNoticePromise?.then((notice) => {
4653
4848
  if (isMounted && notice !== void 0) {
@@ -4659,20 +4854,27 @@ function AppInner(props) {
4659
4854
  isMounted = false;
4660
4855
  };
4661
4856
  }, [props.startupUpdateNoticePromise]);
4662
- (0, import_react19.useEffect)(() => {
4857
+ (0, import_react20.useEffect)(() => {
4663
4858
  const title = sessionName ? `Robota \u2014 ${sessionName}` : "Robota";
4664
4859
  process.stdout.write(`\x1B]0;${title}\x07`);
4665
4860
  }, [sessionName]);
4666
- (0, import_ink21.useInput)((_input, key) => {
4861
+ (0, import_ink23.useInput)((_input, key) => {
4667
4862
  if (!key.escape || !isThinking) return;
4668
- if (permissionRequest || showPluginTUI || showSessionPicker) return;
4863
+ if (permissionRequest || showPluginTUI || showSessionPicker || showExecutionWorkspaceSwitcher) {
4864
+ return;
4865
+ }
4669
4866
  handleAbort();
4670
4867
  });
4671
- (0, import_ink21.useInput)((input, key) => {
4868
+ (0, import_ink23.useInput)((input, key) => {
4869
+ if (!key.ctrl || input !== "b") return;
4870
+ if (permissionRequest || showPluginTUI || showSessionPicker || isShuttingDown) return;
4871
+ setShowExecutionWorkspaceSwitcher((shown) => !shown);
4872
+ });
4873
+ (0, import_ink23.useInput)((input, key) => {
4672
4874
  if (!key.ctrl || input !== "c" || isShuttingDown) return;
4673
4875
  void handleShutdown("prompt_input_exit").finally(() => exit());
4674
4876
  });
4675
- (0, import_react19.useEffect)(() => {
4877
+ (0, import_react20.useEffect)(() => {
4676
4878
  const onSigterm = () => {
4677
4879
  if (isShuttingDown) return;
4678
4880
  void handleShutdown("other").finally(() => exit());
@@ -4684,6 +4886,29 @@ function AppInner(props) {
4684
4886
  process.off("SIGTERM", onSigterm);
4685
4887
  };
4686
4888
  }, [handleShutdown, exit, isShuttingDown]);
4889
+ (0, import_react20.useEffect)(() => {
4890
+ if (!selectedExecutionEntry || selectedExecutionEntry.kind === "main_thread") {
4891
+ setExecutionDetailPage(null);
4892
+ setExecutionDetailError(void 0);
4893
+ setIsExecutionDetailLoading(false);
4894
+ return;
4895
+ }
4896
+ let isCurrent = true;
4897
+ setIsExecutionDetailLoading(true);
4898
+ setExecutionDetailError(void 0);
4899
+ readExecutionWorkspaceDetail(selectedExecutionEntry.id).then((page) => {
4900
+ if (!isCurrent) return;
4901
+ setExecutionDetailPage(page);
4902
+ setIsExecutionDetailLoading(false);
4903
+ }).catch((error) => {
4904
+ if (!isCurrent) return;
4905
+ setExecutionDetailError(error.message);
4906
+ setIsExecutionDetailLoading(false);
4907
+ });
4908
+ return () => {
4909
+ isCurrent = false;
4910
+ };
4911
+ }, [executionWorkspaceSnapshot, readExecutionWorkspaceDetail, selectedExecutionEntry]);
4687
4912
  let permissionMode = props.permissionMode ?? "default";
4688
4913
  let sessionId = "";
4689
4914
  try {
@@ -4692,25 +4917,33 @@ function AppInner(props) {
4692
4917
  sessionId = session.getSessionId();
4693
4918
  } catch {
4694
4919
  }
4695
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_ink21.Box, { flexDirection: "column", children: [
4696
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_ink21.Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
4697
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ink21.Text, { color: "cyan", bold: true, children: `
4920
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_ink23.Box, { flexDirection: "column", children: [
4921
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_ink23.Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
4922
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ink23.Text, { color: "cyan", bold: true, children: `
4698
4923
  ____ ___ ____ ___ _____ _
4699
4924
  | _ \\ / _ \\| __ ) / _ \\_ _|/ \\
4700
4925
  | |_) | | | | _ \\| | | || | / _ \\
4701
4926
  | _ <| |_| | |_) | |_| || |/ ___ \\
4702
4927
  |_| \\_\\\\___/|____/ \\___/ |_/_/ \\_\\
4703
4928
  ` }),
4704
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_ink21.Text, { dimColor: true, children: [
4929
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_ink23.Text, { dimColor: true, children: [
4705
4930
  " v",
4706
4931
  props.version ?? "0.0.0"
4707
4932
  ] })
4708
4933
  ] }),
4709
- updateNotice && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(UpdateNotice, { message: formatCliUpdateNotice(updateNotice) }),
4710
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_ink21.Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
4711
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(MessageList, { history }),
4712
- isShuttingDown && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ink21.Box, { marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ink21.Text, { color: "yellow", children: "Shutting down..." }) }),
4713
- (isThinking || activeTools.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ink21.Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
4934
+ updateNotice && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(UpdateNotice, { message: formatCliUpdateNotice(updateNotice) }),
4935
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_ink23.Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
4936
+ selectedExecutionEntry && selectedExecutionEntry.kind !== "main_thread" ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
4937
+ ExecutionWorkspaceDetailPane,
4938
+ {
4939
+ entry: selectedExecutionEntry,
4940
+ page: executionDetailPage,
4941
+ loading: isExecutionDetailLoading,
4942
+ error: executionDetailError
4943
+ }
4944
+ ) : /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(MessageList, { history }),
4945
+ isShuttingDown && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ink23.Box, { marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ink23.Text, { color: "yellow", children: "Shutting down..." }) }),
4946
+ (isThinking || activeTools.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ink23.Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
4714
4947
  StreamingIndicator,
4715
4948
  {
4716
4949
  text: streamingText,
@@ -4718,17 +4951,26 @@ function AppInner(props) {
4718
4951
  isThinking
4719
4952
  }
4720
4953
  ) }),
4721
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(BackgroundTaskPanel, { tasks: backgroundTasks })
4954
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(BackgroundTaskPanel, { entries: backgroundWorkspaceEntries })
4722
4955
  ] }),
4723
- permissionRequest && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PermissionPrompt, { request: permissionRequest }),
4724
- pendingModelId && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
4956
+ showExecutionWorkspaceSwitcher && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
4957
+ ExecutionWorkspaceSwitcher,
4958
+ {
4959
+ snapshot: executionWorkspaceSnapshot,
4960
+ selectedEntryId: selectedExecutionEntryId,
4961
+ onSelect: selectExecutionWorkspaceEntry,
4962
+ onClose: () => setShowExecutionWorkspaceSwitcher(false)
4963
+ }
4964
+ ),
4965
+ permissionRequest && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(PermissionPrompt, { request: permissionRequest }),
4966
+ pendingModelId && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
4725
4967
  ConfirmPrompt,
4726
4968
  {
4727
4969
  message: formatModelChangeConfirmationMessage(pendingModelId),
4728
4970
  onSelect: handleModelConfirm
4729
4971
  }
4730
4972
  ),
4731
- pendingInteractionPrompt && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
4973
+ pendingInteractionPrompt && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
4732
4974
  InteractivePrompt,
4733
4975
  {
4734
4976
  prompt: pendingInteractionPrompt,
@@ -4736,7 +4978,7 @@ function AppInner(props) {
4736
4978
  onCancel: handleInteractionCancel
4737
4979
  }
4738
4980
  ),
4739
- showPluginTUI && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
4981
+ showPluginTUI && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
4740
4982
  PluginTUI,
4741
4983
  {
4742
4984
  callbacks: pluginCallbacks,
@@ -4744,7 +4986,7 @@ function AppInner(props) {
4744
4986
  addMessage: (msg) => addEntry((0, import_agent_core11.messageToHistoryEntry)((0, import_agent_core11.createSystemMessage)(msg.content)))
4745
4987
  }
4746
4988
  ),
4747
- showSessionPicker && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
4989
+ showSessionPicker && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
4748
4990
  SessionPicker,
4749
4991
  {
4750
4992
  sessions: (0, import_agent_sdk9.listResumableSessionSummaries)(props.sessionStore, props.cwd),
@@ -4758,7 +5000,7 @@ function AppInner(props) {
4758
5000
  }
4759
5001
  }
4760
5002
  ),
4761
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
5003
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
4762
5004
  SessionStatusBar,
4763
5005
  {
4764
5006
  cwd,
@@ -4776,12 +5018,12 @@ function AppInner(props) {
4776
5018
  settings: statusLineSettings
4777
5019
  }
4778
5020
  ),
4779
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
5021
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
4780
5022
  InputArea,
4781
5023
  {
4782
5024
  onSubmit: handleSubmit,
4783
5025
  onCancelQueue: handleCancelQueue,
4784
- isDisabled: !!permissionRequest || showPluginTUI || showSessionPicker || isShuttingDown || pendingInteractionPrompt !== null || isThinking && !!pendingPrompt,
5026
+ isDisabled: !!permissionRequest || showPluginTUI || showSessionPicker || showExecutionWorkspaceSwitcher || isShuttingDown || pendingInteractionPrompt !== null || isThinking && !!pendingPrompt,
4785
5027
  isAborting,
4786
5028
  pendingPrompt,
4787
5029
  registry,
@@ -4789,12 +5031,12 @@ function AppInner(props) {
4789
5031
  history
4790
5032
  }
4791
5033
  ),
4792
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ink21.Text, { children: " " })
5034
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ink23.Text, { children: " " })
4793
5035
  ] });
4794
5036
  }
4795
5037
 
4796
5038
  // src/ui/render.tsx
4797
- var import_jsx_runtime23 = require("react/jsx-runtime");
5039
+ var import_jsx_runtime25 = require("react/jsx-runtime");
4798
5040
  function renderApp(options) {
4799
5041
  process.on("unhandledRejection", (reason) => {
4800
5042
  process.stderr.write(`
@@ -4805,7 +5047,7 @@ function renderApp(options) {
4805
5047
  `);
4806
5048
  }
4807
5049
  });
4808
- const instance = (0, import_ink22.render)(/* @__PURE__ */ (0, import_jsx_runtime23.jsx)(App, { ...options }), {
5050
+ const instance = (0, import_ink24.render)(/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(App, { ...options }), {
4809
5051
  exitOnCtrlC: false
4810
5052
  });
4811
5053
  instance.waitUntilExit().then(() => {
@@ -5449,6 +5691,29 @@ function readTranscriptLog(jobId, transcriptPath, cursor) {
5449
5691
  return (0, import_agent_sdk14.createBackgroundTaskLogPage)(jobId, lines, cursor);
5450
5692
  }
5451
5693
 
5694
+ // src/user-local-direct-command.ts
5695
+ var import_agent_command_user_local = require("@robota-sdk/agent-command-user-local");
5696
+ async function runUserLocalDirectCommandIfRequested(args, cwd) {
5697
+ if (args.positional[0] !== "user-local") {
5698
+ return false;
5699
+ }
5700
+ const result = await (0, import_agent_command_user_local.executeUserLocalDirectCommand)({
5701
+ cwd,
5702
+ argv: args.positional.slice(1),
5703
+ format: args.format,
5704
+ summary: args.summary,
5705
+ source: args.source
5706
+ });
5707
+ const output = result.message.endsWith("\n") ? result.message : `${result.message}
5708
+ `;
5709
+ if (!result.success) {
5710
+ process.stderr.write(output);
5711
+ process.exit(1);
5712
+ }
5713
+ process.stdout.write(output);
5714
+ return true;
5715
+ }
5716
+
5452
5717
  // src/cli.ts
5453
5718
  var import_meta = {};
5454
5719
  function readVersion() {
@@ -5472,11 +5737,19 @@ function readVersion() {
5472
5737
  }
5473
5738
  }
5474
5739
  function promptInput(label, masked = false) {
5475
- return new Promise((resolve3) => {
5740
+ return new Promise((resolve3, reject) => {
5476
5741
  process.stdout.write(label);
5477
5742
  let input = "";
5478
5743
  const stdin = process.stdin;
5479
5744
  const wasRaw = stdin.isRaw;
5745
+ if (!stdin.isTTY) {
5746
+ reject(
5747
+ new Error(
5748
+ "Cannot prompt for input: stdin is not a TTY.\nSet your API key via environment variable instead:\n ANTHROPIC_API_KEY=<key> robota\n OPENAI_API_KEY=<key> robota"
5749
+ )
5750
+ );
5751
+ return;
5752
+ }
5480
5753
  stdin.setRawMode(true);
5481
5754
  stdin.resume();
5482
5755
  stdin.setEncoding("utf8");
@@ -5542,6 +5815,7 @@ function createDefaultCliCommandModules({
5542
5815
  (0, import_agent_command_language.createLanguageCommandModule)(),
5543
5816
  (0, import_agent_command_background.createBackgroundCommandModule)(),
5544
5817
  (0, import_agent_command_memory.createMemoryCommandModule)(),
5818
+ (0, import_agent_command_user_local2.createUserLocalCommandModule)(),
5545
5819
  (0, import_agent_command_compact.createCompactCommandModule)(),
5546
5820
  (0, import_agent_command_context.createContextCommandModule)(),
5547
5821
  (0, import_agent_command_exit.createExitCommandModule)(),
@@ -5585,6 +5859,9 @@ async function startCli(options = {}) {
5585
5859
  return;
5586
5860
  }
5587
5861
  const cwd = process.cwd();
5862
+ if (await runUserLocalDirectCommandIfRequested(args, cwd)) {
5863
+ return;
5864
+ }
5588
5865
  const commandHostAdapters = {
5589
5866
  settings: {
5590
5867
  read: () => readSettings(getUserSettingsPath()),
@@ -5671,6 +5948,9 @@ async function startCli(options = {}) {
5671
5948
  ${args.jsonSchema}`
5672
5949
  );
5673
5950
  const appendSystemPrompt = appendParts.length > 0 ? appendParts.join("\n\n") : void 0;
5951
+ if (args.systemPrompt) {
5952
+ process.stderr.write("Warning: --system-prompt is not yet functional and will be ignored.\n");
5953
+ }
5674
5954
  const session = new import_agent_sdk15.InteractiveSession({
5675
5955
  cwd,
5676
5956
  provider,
@@ -5715,7 +5995,9 @@ ${args.jsonSchema}`
5715
5995
  subagentRunnerFactory,
5716
5996
  commandModules,
5717
5997
  commandHostAdapters,
5718
- startupUpdateNoticePromise
5998
+ startupUpdateNoticePromise,
5999
+ webPort: args.web ? args.webPort : void 0,
6000
+ noOpen: args.noOpen
5719
6001
  });
5720
6002
  }
5721
6003
  // Annotate the CommonJS export names for ESM import in node: