perstack 0.0.53 → 0.0.55

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.
package/dist/bin/cli.js CHANGED
@@ -4,22 +4,24 @@ import { writeFile, mkdir, readFile } from 'fs/promises';
4
4
  import * as path10 from 'path';
5
5
  import path10__default from 'path';
6
6
  import { createApiClient } from '@perstack/api-client';
7
- import { registerAdapter, parseWithFriendlyError, runCommandInputSchema, startCommandInputSchema, defaultMaxRetries, defaultTimeout, defaultPerstackApiBaseUrl, checkpointSchema, jobSchema, runSettingSchema, BaseAdapter, createRuntimeInitEvent, createEmptyUsage, createStartRunEvent, createCompleteRunEvent, getFilteredEnv, createStreamingTextEvent, createCallToolsEvent, createResolveToolResultsEvent, createRuntimeEvent, perstackConfigSchema, expertSchema, isAdapterAvailable as isAdapterAvailable$1, getRegisteredRuntimes as getRegisteredRuntimes$1, getAdapter as getAdapter$1 } from '@perstack/core';
7
+ import { registerAdapter, parseWithFriendlyError, runCommandInputSchema, validateEventFilter, createFilteredEventListener, startCommandInputSchema, defaultMaxRetries, defaultTimeout, defaultPerstackApiBaseUrl, checkpointSchema, jobSchema, runSettingSchema, BaseAdapter, createRuntimeInitEvent, createEmptyUsage, createStartRunEvent, createCompleteRunEvent, getFilteredEnv, createCallToolsEvent, createResolveToolResultsEvent, createRuntimeEvent, perstackConfigSchema, expertSchema, isAdapterAvailable as isAdapterAvailable$1, getRegisteredRuntimes as getRegisteredRuntimes$1, getAdapter as getAdapter$1 } from '@perstack/core';
8
8
  import { collectToolDefinitionsForExpert } from '@perstack/runtime';
9
9
  import TOML from 'smol-toml';
10
10
  import dotenv from 'dotenv';
11
11
  import * as fs from 'fs';
12
12
  import { existsSync, readdirSync, readFileSync, statSync } from 'fs';
13
- import { render, useApp, Box, Static, Text, useInput } from 'ink';
14
- import { createContext, useState, useEffect, useCallback, useMemo, useRef, useReducer, useContext, useInsertionEffect } from 'react';
13
+ import { render, useApp, useInput, Box, Text } from 'ink';
14
+ import { createContext, useMemo, useReducer, useState, useEffect, useCallback, useRef, useContext } from 'react';
15
+ import { useTextInput, SelectableList, useListNavigation } from '@perstack/tui-components';
15
16
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
17
+ import { useRun } from '@perstack/react';
16
18
  import { spawn, execSync } from 'child_process';
17
19
  import * as os from 'os';
18
20
 
19
21
  // package.json
20
22
  var package_default = {
21
23
  name: "perstack",
22
- version: "0.0.53",
24
+ version: "0.0.55",
23
25
  description: "PerStack CLI"};
24
26
  function getEnv(envPath) {
25
27
  const env = Object.fromEntries(
@@ -350,16 +352,6 @@ async function defaultStoreEvent(event) {
350
352
  await mkdir(runDir, { recursive: true });
351
353
  await writeFile(eventPath, JSON.stringify(event));
352
354
  }
353
- function getEventsByRun(jobId, runId) {
354
- const runDir = defaultGetRunDir(jobId, runId);
355
- if (!existsSync(runDir)) {
356
- return [];
357
- }
358
- return readdirSync(runDir).filter((file) => file.startsWith("event-")).map((file) => {
359
- const [_, timestamp, stepNumber, type] = file.split(".")[0].split("-");
360
- return { timestamp: Number(timestamp), stepNumber: Number(stepNumber), type };
361
- }).sort((a, b) => a.stepNumber - b.stepNumber);
362
- }
363
355
  function getEventContents(jobId, runId, maxStepNumber) {
364
356
  const runDir = defaultGetRunDir(jobId, runId);
365
357
  if (!existsSync(runDir)) {
@@ -650,8 +642,8 @@ function createStorageAdapter(storage, basePath) {
650
642
 
651
643
  // src/lib/log/filter.ts
652
644
  var ERROR_EVENT_TYPES = /* @__PURE__ */ new Set(["stopRunByError", "retry"]);
653
- var TOOL_EVENT_TYPES = /* @__PURE__ */ new Set(["callTools", "resolveToolResults", "callInteractiveTool"]);
654
- var DELEGATION_EVENT_TYPES = /* @__PURE__ */ new Set(["callDelegate", "stopRunByDelegate"]);
645
+ var TOOL_EVENT_TYPES = /* @__PURE__ */ new Set(["callTools", "resolveToolResults", "stopRunByInteractiveTool"]);
646
+ var DELEGATION_EVENT_TYPES = /* @__PURE__ */ new Set(["stopRunByDelegate"]);
655
647
  function parseStepFilter(step) {
656
648
  const trimmed = step.trim();
657
649
  const rangeMatch = trimmed.match(/^(\d+)-(\d+)$/);
@@ -867,8 +859,8 @@ function addContextEvents(allEvents, matchedEvents, contextSize) {
867
859
 
868
860
  // src/lib/log/formatter.ts
869
861
  var ERROR_EVENT_TYPES2 = /* @__PURE__ */ new Set(["stopRunByError", "retry"]);
870
- var TOOL_EVENT_TYPES2 = /* @__PURE__ */ new Set(["callTools", "resolveToolResults", "callInteractiveTool"]);
871
- var DELEGATION_EVENT_TYPES2 = /* @__PURE__ */ new Set(["callDelegate", "stopRunByDelegate"]);
862
+ var TOOL_EVENT_TYPES2 = /* @__PURE__ */ new Set(["callTools", "resolveToolResults", "stopRunByInteractiveTool"]);
863
+ var DELEGATION_EVENT_TYPES2 = /* @__PURE__ */ new Set(["stopRunByDelegate"]);
872
864
  function createSummary(events) {
873
865
  if (events.length === 0) {
874
866
  return {
@@ -1048,11 +1040,10 @@ function formatEvent(event, verbose) {
1048
1040
  lines.push(` Message: ${errorEvent.error.message}`);
1049
1041
  lines.push(` Retryable: ${errorEvent.error.isRetryable}`);
1050
1042
  }
1051
- if (event.type === "callDelegate" && "toolCalls" in event) {
1043
+ if (event.type === "stopRunByDelegate" && "checkpoint" in event) {
1052
1044
  const delegateEvent = event;
1053
- for (const tc of delegateEvent.toolCalls) {
1054
- const expertKey = tc.args.expertKey ?? tc.args.expert ?? "unknown";
1055
- lines.push(` Delegate to: ${expertKey}`);
1045
+ for (const d of delegateEvent.checkpoint.delegateTo ?? []) {
1046
+ lines.push(` Delegate to: ${d.expert.key}`);
1056
1047
  }
1057
1048
  }
1058
1049
  if (event.type === "completeRun" && "text" in event) {
@@ -1363,7 +1354,6 @@ var summarizeOutput = (lines, maxLines) => {
1363
1354
  // src/tui/constants.ts
1364
1355
  var UI_CONSTANTS = {
1365
1356
  MAX_VISIBLE_LIST_ITEMS: 25,
1366
- MAX_EVENTS: 1e3,
1367
1357
  TRUNCATE_TEXT_MEDIUM: 80,
1368
1358
  TRUNCATE_TEXT_DEFAULT: 100};
1369
1359
  var RENDER_CONSTANTS = {
@@ -1372,7 +1362,6 @@ var RENDER_CONSTANTS = {
1372
1362
  LIST_DIR_MAX_ITEMS: 4
1373
1363
  };
1374
1364
  var INDICATOR = {
1375
- CHEVRON_RIGHT: ">",
1376
1365
  BULLET: "\u25CF",
1377
1366
  TREE: "\u2514",
1378
1367
  ELLIPSIS: "..."
@@ -1413,654 +1402,149 @@ var KEY_HINTS = {
1413
1402
  CTRL_QUIT: `${KEY_BINDINGS.CTRL_QUIT}:Quit`,
1414
1403
  CONFIRM: `${KEY_BINDINGS.SELECT}:Confirm`
1415
1404
  };
1416
- var useExpertActions = (options) => {
1417
- const {
1418
- needsQueryInput,
1419
- inputState,
1420
- dispatch,
1421
- setExpertName,
1422
- onComplete,
1423
- markAsStarted,
1424
- configuredExperts,
1425
- recentExperts
1426
- } = options;
1427
- const allExperts = useMemo(() => {
1428
- const configured = (configuredExperts || []).map((e) => ({
1429
- ...e,
1430
- source: "configured"
1431
- }));
1432
- const recent = (recentExperts || []).filter((e) => !configured.some((c) => c.key === e.key)).map((e) => ({ ...e, source: "recent" }));
1433
- return [...configured, ...recent];
1434
- }, [configuredExperts, recentExperts]);
1435
- const handleExpertSelect = useCallback(
1436
- (expertKey) => {
1437
- setExpertName(expertKey);
1438
- const needsQuery = needsQueryInput || inputState.type === "browsingExperts";
1439
- dispatch({ type: "SELECT_EXPERT", expertKey, needsQuery });
1440
- if (!needsQuery) {
1441
- markAsStarted();
1442
- onComplete(expertKey, "");
1443
- }
1444
- },
1445
- [needsQueryInput, inputState.type, dispatch, setExpertName, onComplete, markAsStarted]
1446
- );
1447
- return { allExperts, handleExpertSelect };
1448
- };
1449
-
1450
- // src/tui/utils/error-handling.ts
1451
- var createErrorHandler = (onError) => (error, context) => {
1452
- const err = error instanceof Error ? error : new Error(`${context}: ${String(error)}`);
1453
- onError?.(err);
1454
- };
1455
-
1456
- // src/tui/hooks/core/use-error-handler.ts
1457
- var useErrorHandler = (onError) => {
1458
- return useMemo(() => createErrorHandler(onError), [onError]);
1459
- };
1460
-
1461
- // src/tui/hooks/actions/use-history-actions.ts
1462
- var useHistoryActions = (options) => {
1463
- const {
1464
- allExperts,
1465
- historyJobs,
1466
- onLoadCheckpoints,
1467
- onLoadEvents,
1468
- onResumeFromCheckpoint,
1469
- onLoadHistoricalEvents,
1470
- setHistoricalEvents,
1471
- setCurrentStep,
1472
- setContextWindowUsage,
1473
- dispatch,
1474
- setExpertName,
1475
- onError
1476
- } = options;
1477
- const [selectedJob, setSelectedJob] = useState(null);
1478
- const handleError = useErrorHandler(onError);
1479
- const handleJobSelect = useCallback(
1480
- async (job) => {
1481
- try {
1482
- setSelectedJob(job);
1483
- setExpertName(job.expertKey);
1484
- if (onLoadCheckpoints) {
1485
- const checkpoints = await onLoadCheckpoints(job);
1486
- dispatch({ type: "SELECT_JOB", job, checkpoints });
1487
- }
1488
- } catch (error) {
1489
- handleError(error, "Failed to load checkpoints");
1490
- }
1491
- },
1492
- [onLoadCheckpoints, dispatch, setExpertName, handleError]
1493
- );
1494
- const handleJobResume = useCallback(
1495
- async (job) => {
1496
- try {
1497
- setSelectedJob(job);
1498
- setExpertName(job.expertKey);
1499
- if (onLoadCheckpoints && onResumeFromCheckpoint) {
1500
- const checkpoints = await onLoadCheckpoints(job);
1501
- const latestCheckpoint = checkpoints[0];
1502
- if (latestCheckpoint) {
1503
- if (onLoadHistoricalEvents) {
1504
- const events = await onLoadHistoricalEvents(latestCheckpoint);
1505
- setHistoricalEvents(events);
1506
- }
1507
- setCurrentStep(latestCheckpoint.stepNumber);
1508
- setContextWindowUsage(latestCheckpoint.contextWindowUsage);
1509
- dispatch({ type: "RESUME_CHECKPOINT", expertKey: job.expertKey });
1510
- onResumeFromCheckpoint(latestCheckpoint);
1511
- }
1512
- }
1513
- } catch (error) {
1514
- handleError(error, "Failed to resume job");
1515
- }
1516
- },
1517
- [
1518
- onLoadCheckpoints,
1519
- onResumeFromCheckpoint,
1520
- onLoadHistoricalEvents,
1521
- setHistoricalEvents,
1522
- setCurrentStep,
1523
- setContextWindowUsage,
1524
- dispatch,
1525
- setExpertName,
1526
- handleError
1527
- ]
1528
- );
1529
- const handleCheckpointSelect = useCallback(
1530
- async (checkpoint) => {
1531
- try {
1532
- if (selectedJob && onLoadEvents) {
1533
- const eventsData = await onLoadEvents(selectedJob, checkpoint);
1534
- dispatch({ type: "SELECT_CHECKPOINT", job: selectedJob, checkpoint, events: eventsData });
1535
- }
1536
- } catch (error) {
1537
- handleError(error, "Failed to load events");
1538
- }
1539
- },
1540
- [selectedJob, onLoadEvents, dispatch, handleError]
1541
- );
1542
- const handleCheckpointResume = useCallback(
1543
- async (checkpoint) => {
1544
- if (onResumeFromCheckpoint) {
1545
- if (onLoadHistoricalEvents) {
1546
- const events = await onLoadHistoricalEvents(checkpoint);
1547
- setHistoricalEvents(events);
1548
- }
1549
- setCurrentStep(checkpoint.stepNumber);
1550
- setContextWindowUsage(checkpoint.contextWindowUsage);
1551
- dispatch({ type: "RESUME_CHECKPOINT", expertKey: selectedJob?.expertKey || "" });
1552
- onResumeFromCheckpoint(checkpoint);
1553
- }
1554
- },
1555
- [
1556
- onResumeFromCheckpoint,
1557
- onLoadHistoricalEvents,
1558
- setHistoricalEvents,
1559
- setCurrentStep,
1560
- setContextWindowUsage,
1561
- dispatch,
1562
- selectedJob?.expertKey
1563
- ]
1564
- );
1565
- const handleBackFromEvents = useCallback(async () => {
1566
- try {
1567
- if (selectedJob && onLoadCheckpoints) {
1568
- const checkpoints = await onLoadCheckpoints(selectedJob);
1569
- dispatch({ type: "GO_BACK_FROM_EVENTS", job: selectedJob, checkpoints });
1570
- }
1571
- } catch (error) {
1572
- handleError(error, "Failed to go back from events");
1573
- }
1574
- }, [selectedJob, onLoadCheckpoints, dispatch, handleError]);
1575
- const handleBackFromCheckpoints = useCallback(() => {
1576
- if (historyJobs) {
1577
- setSelectedJob(null);
1578
- dispatch({ type: "GO_BACK_FROM_CHECKPOINTS", historyJobs });
1579
- }
1580
- }, [historyJobs, dispatch]);
1581
- const handleEventSelect = useCallback(
1582
- (state, event) => {
1583
- dispatch({
1584
- type: "SELECT_EVENT",
1585
- checkpoint: state.checkpoint,
1586
- events: state.events,
1587
- selectedEvent: event
1588
- });
1589
- },
1590
- [dispatch]
1591
- );
1592
- const handleBackFromEventDetail = useCallback(
1593
- (state) => {
1594
- dispatch({
1595
- type: "GO_BACK_FROM_EVENT_DETAIL",
1596
- checkpoint: state.checkpoint,
1597
- events: state.events
1598
- });
1599
- },
1600
- [dispatch]
1601
- );
1602
- const handleBack = useCallback(
1603
- (currentState) => {
1604
- switch (currentState.type) {
1605
- case "browsingEventDetail":
1606
- handleBackFromEventDetail(currentState);
1607
- break;
1608
- case "browsingEvents":
1609
- handleBackFromEvents();
1610
- break;
1611
- case "browsingCheckpoints":
1612
- handleBackFromCheckpoints();
1613
- break;
1614
- }
1615
- },
1616
- [handleBackFromEventDetail, handleBackFromEvents, handleBackFromCheckpoints]
1617
- );
1618
- const handleSwitchToExperts = useCallback(() => {
1619
- dispatch({ type: "BROWSE_EXPERTS", experts: allExperts });
1620
- }, [dispatch, allExperts]);
1621
- const handleSwitchToHistory = useCallback(() => {
1622
- if (historyJobs) {
1623
- dispatch({ type: "BROWSE_HISTORY", jobs: historyJobs });
1624
- }
1625
- }, [dispatch, historyJobs]);
1626
- return {
1627
- selectedJob,
1628
- handleJobSelect,
1629
- handleJobResume,
1630
- handleCheckpointSelect,
1631
- handleCheckpointResume,
1632
- handleEventSelect,
1633
- handleBack,
1634
- handleSwitchToExperts,
1635
- handleSwitchToHistory
1636
- };
1637
- };
1638
- var useLatestRef = (value) => {
1639
- const ref = useRef(value);
1640
- useInsertionEffect(() => {
1641
- ref.current = value;
1405
+ var ListBrowser = ({
1406
+ title,
1407
+ items,
1408
+ renderItem,
1409
+ onSelect,
1410
+ emptyMessage = "No items found",
1411
+ extraKeyHandler,
1412
+ onBack,
1413
+ maxItems = UI_CONSTANTS.MAX_VISIBLE_LIST_ITEMS
1414
+ }) => {
1415
+ const { selectedIndex, handleNavigation } = useListNavigation({
1416
+ items,
1417
+ onSelect,
1418
+ onBack
1419
+ });
1420
+ useInput((inputChar, key) => {
1421
+ if (extraKeyHandler?.(inputChar, key, items[selectedIndex])) return;
1422
+ handleNavigation(inputChar, key);
1642
1423
  });
1643
- return ref;
1424
+ const { scrollOffset, displayItems } = useMemo(() => {
1425
+ const offset = Math.max(0, Math.min(selectedIndex - maxItems + 1, items.length - maxItems));
1426
+ return {
1427
+ scrollOffset: offset,
1428
+ displayItems: items.slice(offset, offset + maxItems)
1429
+ };
1430
+ }, [items, selectedIndex, maxItems]);
1431
+ const hasMoreAbove = scrollOffset > 0;
1432
+ const hasMoreBelow = scrollOffset + maxItems < items.length;
1433
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
1434
+ /* @__PURE__ */ jsxs(Box, { children: [
1435
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: title }),
1436
+ items.length > maxItems && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
1437
+ " ",
1438
+ "(",
1439
+ selectedIndex + 1,
1440
+ "/",
1441
+ items.length,
1442
+ ")"
1443
+ ] })
1444
+ ] }),
1445
+ hasMoreAbove && /* @__PURE__ */ jsx(Text, { color: "gray", children: INDICATOR.ELLIPSIS }),
1446
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: displayItems.length === 0 ? /* @__PURE__ */ jsx(Text, { color: "gray", children: emptyMessage }) : displayItems.map((item, index) => {
1447
+ const actualIndex = scrollOffset + index;
1448
+ return renderItem(item, actualIndex === selectedIndex, actualIndex);
1449
+ }) }),
1450
+ hasMoreBelow && /* @__PURE__ */ jsx(Text, { color: "gray", children: INDICATOR.ELLIPSIS })
1451
+ ] });
1644
1452
  };
1645
-
1646
- // src/tui/hooks/actions/use-run-actions.ts
1647
- var useRunActions = (options) => {
1648
- const {
1649
- expertName,
1650
- dispatch,
1651
- setQuery,
1652
- onComplete,
1653
- onContinue,
1654
- onReady,
1655
- stepStoreAddEvent,
1656
- handleEvent
1657
- } = options;
1658
- const [hasStarted, setHasStarted] = useState(false);
1659
- const markAsStarted = useCallback(() => setHasStarted(true), []);
1660
- const expertNameRef = useLatestRef(expertName);
1661
- const onContinueRef = useLatestRef(onContinue);
1662
- useEffect(() => {
1663
- onReady((event) => {
1664
- stepStoreAddEvent(event);
1665
- const result = handleEvent(event);
1666
- if (result?.initialized) {
1667
- dispatch({ type: "INITIALIZE_RUNTIME" });
1668
- } else if (result?.completed && onContinueRef.current) {
1669
- dispatch({ type: "END_RUN", expertName: expertNameRef.current || "", reason: "completed" });
1670
- } else if (result?.stopped && onContinueRef.current) {
1671
- dispatch({ type: "END_RUN", expertName: expertNameRef.current || "", reason: "stopped" });
1672
- }
1673
- });
1674
- }, [onReady, stepStoreAddEvent, handleEvent, dispatch, onContinueRef, expertNameRef]);
1675
- const handleQuerySubmit = useCallback(
1676
- (query) => {
1677
- setQuery(query);
1678
- dispatch({ type: "START_RUN" });
1679
- if (hasStarted && onContinue) {
1680
- onContinue(query);
1681
- } else {
1682
- markAsStarted();
1683
- onComplete(expertName || "", query);
1453
+ var BrowsingCheckpointsInput = ({
1454
+ job,
1455
+ checkpoints,
1456
+ onCheckpointSelect,
1457
+ onCheckpointResume,
1458
+ onBack,
1459
+ showEventsHint = true
1460
+ }) => /* @__PURE__ */ jsx(
1461
+ ListBrowser,
1462
+ {
1463
+ title: `Checkpoints for ${job.expertKey} ${KEY_HINTS.NAVIGATE} ${KEY_HINTS.RESUME} ${showEventsHint ? KEY_HINTS.EVENTS : ""} ${KEY_HINTS.BACK}`.trim(),
1464
+ items: checkpoints,
1465
+ onSelect: onCheckpointResume,
1466
+ onBack,
1467
+ emptyMessage: "No checkpoints found",
1468
+ extraKeyHandler: (char, _key, selected) => {
1469
+ if (char === "e" && selected) {
1470
+ onCheckpointSelect(selected);
1471
+ return true;
1684
1472
  }
1473
+ return false;
1685
1474
  },
1686
- [hasStarted, onContinue, onComplete, expertName, setQuery, dispatch, markAsStarted]
1687
- );
1688
- return { hasStarted, markAsStarted, handleQuerySubmit };
1689
- };
1690
- var TOOL_RESULT_EVENT_TYPES = /* @__PURE__ */ new Set(["resolveToolResults", "attemptCompletion"]);
1691
- var STREAMING_EVENT_TYPES = /* @__PURE__ */ new Set([
1692
- "startReasoning",
1693
- "streamReasoning",
1694
- "startRunResult",
1695
- "streamRunResult"
1696
- ]);
1697
- var isToolCallsEvent = (event) => "type" in event && event.type === "callTools" && "toolCalls" in event;
1698
- var isToolCallEvent = (event) => "type" in event && (event.type === "callInteractiveTool" || event.type === "callDelegate") && "toolCall" in event;
1699
- var isToolResultsEvent = (event) => "type" in event && event.type === "resolveToolResults" && "toolResults" in event;
1700
- var isToolResultEvent = (event) => "type" in event && TOOL_RESULT_EVENT_TYPES.has(event.type) && "toolResult" in event;
1701
- var checkIsSuccess = (result) => {
1702
- const textPart = result.find((r) => r.type === "textPart");
1703
- if (!textPart || typeof textPart.text !== "string") return true;
1704
- const text = textPart.text.toLowerCase();
1705
- return !text.startsWith("error") && !text.includes("failed");
1706
- };
1707
- var extractQuery = (event) => {
1708
- const userMessage = event.inputMessages.find((m) => m.type === "userMessage");
1709
- if (userMessage?.type !== "userMessage") return void 0;
1710
- return userMessage.contents.find((c) => c.type === "textPart")?.text;
1711
- };
1712
- var formatRuntimeName = (runtime) => {
1713
- if (runtime === "claude-code") return "Claude Code";
1714
- if (runtime === "local") return "Local";
1715
- return runtime.charAt(0).toUpperCase() + runtime.slice(1);
1716
- };
1717
- var processEventToLogs = (state, event, addLog) => {
1718
- if (!("runId" in event)) return;
1719
- const runId = event.runId;
1720
- if (event.type === "dockerBuildProgress") {
1721
- const buildEvent = event;
1722
- addLog({
1723
- id: `docker-build-${event.id}`,
1724
- type: "docker-build",
1725
- stage: buildEvent.stage,
1726
- service: buildEvent.service,
1727
- message: buildEvent.message,
1728
- progress: buildEvent.progress
1729
- });
1730
- return;
1731
- }
1732
- if (event.type === "dockerContainerStatus") {
1733
- const containerEvent = event;
1734
- addLog({
1735
- id: `docker-container-${event.id}`,
1736
- type: "docker-container",
1737
- status: containerEvent.status,
1738
- service: containerEvent.service,
1739
- message: containerEvent.message
1740
- });
1741
- return;
1742
- }
1743
- if (event.type === "proxyAccess") {
1744
- const proxyEvent = event;
1745
- addLog({
1746
- id: `proxy-access-${event.id}`,
1747
- type: "proxy-access",
1748
- action: proxyEvent.action,
1749
- domain: proxyEvent.domain,
1750
- port: proxyEvent.port,
1751
- reason: proxyEvent.reason
1752
- });
1753
- return;
1754
- }
1755
- if (event.type === "completeReasoning") {
1756
- const reasoningEvent = event;
1757
- addLog({
1758
- id: `completeReasoning-${event.id}`,
1759
- type: "completeReasoning",
1760
- text: reasoningEvent.text
1761
- });
1762
- state.isReasoningStreaming = false;
1763
- state.streamingReasoning = void 0;
1764
- return;
1765
- }
1766
- if (event.type === "retry") {
1767
- const retryEvent = event;
1768
- addLog({
1769
- id: `retry-${event.id}`,
1770
- type: "retry",
1771
- reason: retryEvent.reason
1772
- });
1773
- state.streamingText = void 0;
1774
- state.streamingReasoning = void 0;
1775
- return;
1776
- }
1777
- if (event.type === "initializeRuntime") {
1778
- if (!state.rootRunId) {
1779
- state.rootRunId = runId;
1780
- } else if (runId !== state.rootRunId) {
1781
- const expertName = event.expertName ?? "unknown";
1782
- const runtime = formatRuntimeName(
1783
- event.runtime ?? event.model?.split(":")[0] ?? "unknown"
1784
- );
1785
- const version = event.runtimeVersion ?? "unknown";
1786
- const query = event.query;
1787
- state.pendingDelegations.set(runId, { expertName, runtime, version, query });
1788
- addLog({
1789
- id: `delegation-started-${runId}`,
1790
- type: "delegation-started",
1791
- expertName,
1792
- runtime,
1793
- version,
1794
- query
1795
- });
1796
- }
1797
- return;
1798
- }
1799
- const isDelegation = state.rootRunId && runId !== state.rootRunId;
1800
- if (event.type === "streamingText" && "text" in event) {
1801
- if (!isDelegation) {
1802
- state.streamingText = event.text;
1803
- }
1804
- return;
1805
- }
1806
- if (event.type === "completeRun") {
1807
- if (isDelegation) {
1808
- const pending = state.pendingDelegations.get(runId);
1809
- if (pending) {
1810
- addLog({
1811
- id: `delegation-completed-${runId}`,
1812
- type: "delegation-completed",
1813
- expertName: pending.expertName,
1814
- runtime: pending.runtime,
1815
- version: pending.version,
1816
- result: event.text
1817
- });
1818
- state.pendingDelegations.delete(runId);
1819
- }
1820
- } else if (!state.completionLogged) {
1821
- const text = event.text || state.streamingText;
1822
- if (text) {
1823
- addLog({ id: `completion-${runId}`, type: "completion", text });
1824
- }
1825
- state.completionLogged = true;
1826
- state.isComplete = true;
1827
- state.streamingText = void 0;
1828
- state.streamingReasoning = void 0;
1829
- }
1830
- return;
1475
+ renderItem: (cp, isSelected) => /* @__PURE__ */ jsxs(Text, { color: isSelected ? "cyan" : "gray", children: [
1476
+ isSelected ? ">" : " ",
1477
+ " Step ",
1478
+ cp.stepNumber,
1479
+ " (",
1480
+ cp.id,
1481
+ ")"
1482
+ ] }, cp.id)
1831
1483
  }
1832
- if (event.type === "stopRunByError") {
1833
- const errorEvent = event;
1834
- addLog({
1835
- id: `error-${event.id}`,
1836
- type: "error",
1837
- errorName: errorEvent.error.name,
1838
- message: errorEvent.error.message,
1839
- statusCode: errorEvent.error.statusCode
1840
- });
1841
- state.isComplete = true;
1842
- state.streamingText = void 0;
1843
- state.streamingReasoning = void 0;
1844
- return;
1845
- }
1846
- if (isDelegation) return;
1847
- if (event.type === "startRun") {
1848
- const query = extractQuery(event);
1849
- if (query) {
1850
- if (state.completionLogged) {
1851
- state.completionLogged = false;
1852
- state.isComplete = false;
1853
- state.queryLogged = false;
1854
- }
1855
- if (!state.queryLogged) {
1856
- addLog({ id: `query-${runId}`, type: "query", text: query });
1857
- state.queryLogged = true;
1858
- }
1859
- }
1860
- } else if (isToolCallsEvent(event)) {
1861
- for (const toolCall of event.toolCalls) {
1862
- if (!state.tools.has(toolCall.id)) {
1863
- state.tools.set(toolCall.id, {
1864
- id: toolCall.id,
1865
- toolName: toolCall.toolName,
1866
- args: toolCall.args,
1867
- logged: false
1868
- });
1869
- }
1870
- }
1871
- } else if (isToolCallEvent(event)) {
1872
- const { toolCall } = event;
1873
- if (!state.tools.has(toolCall.id)) {
1874
- state.tools.set(toolCall.id, {
1875
- id: toolCall.id,
1876
- toolName: toolCall.toolName,
1877
- args: toolCall.args,
1878
- logged: false
1879
- });
1880
- }
1881
- } else if (isToolResultsEvent(event)) {
1882
- for (const toolResult of event.toolResults) {
1883
- const tool = state.tools.get(toolResult.id);
1884
- if (tool && !tool.logged && Array.isArray(toolResult.result)) {
1885
- tool.result = toolResult.result;
1886
- tool.isSuccess = checkIsSuccess(toolResult.result);
1887
- addLog({
1888
- id: `tool-${tool.id}`,
1889
- type: "tool",
1890
- toolName: tool.toolName,
1891
- args: tool.args,
1892
- result: tool.result,
1893
- isSuccess: tool.isSuccess
1894
- });
1895
- tool.logged = true;
1896
- }
1897
- }
1898
- } else if (isToolResultEvent(event)) {
1899
- const { toolResult } = event;
1900
- const tool = state.tools.get(toolResult.id);
1901
- if (tool && !tool.logged && Array.isArray(toolResult.result)) {
1902
- tool.result = toolResult.result;
1903
- tool.isSuccess = checkIsSuccess(toolResult.result);
1904
- addLog({
1905
- id: `tool-${tool.id}`,
1906
- type: "tool",
1907
- toolName: tool.toolName,
1908
- args: tool.args,
1909
- result: tool.result,
1910
- isSuccess: tool.isSuccess
1911
- });
1912
- tool.logged = true;
1484
+ );
1485
+ var BrowsingEventDetailInput = ({ event, onBack }) => {
1486
+ useInput((input, key) => {
1487
+ if (input === "b" || key.escape) {
1488
+ onBack();
1913
1489
  }
1914
- }
1915
- };
1916
- var useEventStore = () => {
1917
- const [events, setEvents] = useState([]);
1918
- const [logs, setLogs] = useState([]);
1919
- const [streamingText, setStreamingText] = useState();
1920
- const [streamingReasoning, setStreamingReasoning] = useState();
1921
- const [isComplete, setIsComplete] = useState(false);
1922
- const stateRef = useRef({
1923
- tools: /* @__PURE__ */ new Map(),
1924
- pendingDelegations: /* @__PURE__ */ new Map(),
1925
- queryLogged: false,
1926
- completionLogged: false,
1927
- isComplete: false
1928
1490
  });
1929
- const processedCountRef = useRef(0);
1930
- const needsRebuildRef = useRef(false);
1931
- const addEvent = useCallback((event) => {
1932
- if ("type" in event && STREAMING_EVENT_TYPES.has(event.type)) {
1933
- if (event.type === "startReasoning") {
1934
- stateRef.current.streamingReasoning = "";
1935
- stateRef.current.isReasoningStreaming = true;
1936
- setStreamingReasoning("");
1937
- } else if (event.type === "streamReasoning") {
1938
- const delta = event.delta;
1939
- stateRef.current.streamingReasoning = (stateRef.current.streamingReasoning ?? "") + delta;
1940
- setStreamingReasoning(stateRef.current.streamingReasoning);
1941
- } else if (event.type === "startRunResult") {
1942
- stateRef.current.isRunResultStreaming = true;
1943
- stateRef.current.streamingText = "";
1944
- setStreamingText("");
1945
- } else if (event.type === "streamRunResult") {
1946
- const delta = event.delta;
1947
- stateRef.current.streamingText = (stateRef.current.streamingText ?? "") + delta;
1948
- setStreamingText(stateRef.current.streamingText);
1949
- }
1950
- return;
1951
- }
1952
- setEvents((prev) => {
1953
- const newEvents = [...prev, event];
1954
- if (newEvents.length > UI_CONSTANTS.MAX_EVENTS) {
1955
- needsRebuildRef.current = true;
1956
- return newEvents.slice(-1e3);
1957
- }
1958
- return newEvents;
1959
- });
1960
- }, []);
1961
- const setHistoricalEvents = useCallback((historicalEvents) => {
1962
- needsRebuildRef.current = true;
1963
- setEvents(
1964
- historicalEvents.length > UI_CONSTANTS.MAX_EVENTS ? historicalEvents.slice(-1e3) : historicalEvents
1965
- );
1966
- }, []);
1967
- useEffect(() => {
1968
- if (needsRebuildRef.current) {
1969
- stateRef.current = {
1970
- tools: /* @__PURE__ */ new Map(),
1971
- pendingDelegations: /* @__PURE__ */ new Map(),
1972
- queryLogged: false,
1973
- completionLogged: false,
1974
- isComplete: false
1975
- };
1976
- setLogs([]);
1977
- processedCountRef.current = 0;
1978
- needsRebuildRef.current = false;
1979
- }
1980
- const newEvents = events.slice(processedCountRef.current);
1981
- const newLogs = [];
1982
- const addLog = (entry) => newLogs.push(entry);
1983
- for (const event of newEvents) {
1984
- processEventToLogs(stateRef.current, event, addLog);
1985
- }
1986
- processedCountRef.current = events.length;
1987
- if (newLogs.length > 0) {
1988
- setLogs((prev) => [...prev, ...newLogs]);
1989
- }
1990
- setStreamingText(stateRef.current.streamingText);
1991
- setStreamingReasoning(stateRef.current.streamingReasoning);
1992
- setIsComplete(stateRef.current.isComplete);
1993
- }, [events]);
1994
- return {
1995
- logs,
1996
- streamingText,
1997
- streamingReasoning,
1998
- isComplete,
1999
- eventCount: events.length,
2000
- addEvent,
2001
- setHistoricalEvents
2002
- };
1491
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
1492
+ /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
1493
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Event Detail" }),
1494
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1495
+ " ",
1496
+ KEY_HINTS.BACK
1497
+ ] })
1498
+ ] }),
1499
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [
1500
+ /* @__PURE__ */ jsxs(Text, { children: [
1501
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Type: " }),
1502
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: event.type })
1503
+ ] }),
1504
+ /* @__PURE__ */ jsxs(Text, { children: [
1505
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Step: " }),
1506
+ /* @__PURE__ */ jsx(Text, { children: event.stepNumber })
1507
+ ] }),
1508
+ /* @__PURE__ */ jsxs(Text, { children: [
1509
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Timestamp: " }),
1510
+ /* @__PURE__ */ jsx(Text, { children: formatTimestamp2(event.timestamp) })
1511
+ ] }),
1512
+ /* @__PURE__ */ jsxs(Text, { children: [
1513
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "ID: " }),
1514
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: event.id })
1515
+ ] }),
1516
+ /* @__PURE__ */ jsxs(Text, { children: [
1517
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Run ID: " }),
1518
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: event.runId })
1519
+ ] })
1520
+ ] })
1521
+ ] });
2003
1522
  };
2004
- var inputReducer = (_state, action) => {
2005
- switch (action.type) {
2006
- case "SELECT_EXPERT":
2007
- if (action.needsQuery) {
2008
- return { type: "enteringQuery", expertName: action.expertKey };
2009
- }
2010
- return { type: "running" };
2011
- case "START_RUN":
2012
- case "INITIALIZE_RUNTIME":
2013
- return { type: "running" };
2014
- case "END_RUN":
2015
- return { type: "enteringQuery", expertName: action.expertName };
2016
- case "BROWSE_HISTORY":
2017
- return { type: "browsingHistory", jobs: action.jobs };
2018
- case "BROWSE_EXPERTS":
2019
- return { type: "browsingExperts", experts: action.experts };
2020
- case "SELECT_JOB":
2021
- return { type: "browsingCheckpoints", job: action.job, checkpoints: action.checkpoints };
2022
- case "SELECT_CHECKPOINT":
2023
- return {
2024
- type: "browsingEvents",
2025
- checkpoint: action.checkpoint,
2026
- events: action.events
2027
- };
2028
- case "RESUME_CHECKPOINT":
2029
- return { type: "enteringQuery", expertName: action.expertKey };
2030
- case "GO_BACK_FROM_EVENTS":
2031
- return { type: "browsingCheckpoints", job: action.job, checkpoints: action.checkpoints };
2032
- case "GO_BACK_FROM_CHECKPOINTS":
2033
- return { type: "browsingHistory", jobs: action.historyJobs };
2034
- case "SELECT_EVENT":
2035
- return {
2036
- type: "browsingEventDetail",
2037
- checkpoint: action.checkpoint,
2038
- events: action.events,
2039
- selectedEvent: action.selectedEvent
2040
- };
2041
- case "GO_BACK_FROM_EVENT_DETAIL":
2042
- return {
2043
- type: "browsingEvents",
2044
- checkpoint: action.checkpoint,
2045
- events: action.events
2046
- };
2047
- default:
2048
- return assertNever(action);
2049
- }
2050
- };
2051
- var getInitialState = (options) => {
2052
- if (options.showHistory && options.historyJobs) {
2053
- return { type: "browsingHistory", jobs: options.historyJobs };
2054
- }
2055
- if (options.needsQueryInput) {
2056
- return { type: "enteringQuery", expertName: options.initialExpertName || "" };
1523
+ var BrowsingEventsInput = ({
1524
+ checkpoint,
1525
+ events,
1526
+ onEventSelect,
1527
+ onBack
1528
+ }) => /* @__PURE__ */ jsx(
1529
+ ListBrowser,
1530
+ {
1531
+ title: `Events for Step ${checkpoint.stepNumber} ${KEY_HINTS.NAVIGATE} ${KEY_HINTS.SELECT} ${KEY_HINTS.BACK}`,
1532
+ items: events,
1533
+ onSelect: onEventSelect,
1534
+ onBack,
1535
+ emptyMessage: "No events found",
1536
+ renderItem: (ev, isSelected) => /* @__PURE__ */ jsxs(Text, { color: isSelected ? "cyan" : "gray", children: [
1537
+ isSelected ? ">" : " ",
1538
+ " [",
1539
+ ev.type,
1540
+ "] Step ",
1541
+ ev.stepNumber,
1542
+ " (",
1543
+ formatTimestamp2(ev.timestamp),
1544
+ ")"
1545
+ ] }, ev.id)
2057
1546
  }
2058
- return { type: "running" };
2059
- };
2060
- var useInputState = (options) => {
2061
- const [state, dispatch] = useReducer(inputReducer, options, getInitialState);
2062
- return { state, dispatch };
2063
- };
1547
+ );
2064
1548
  var useRuntimeInfo = (options) => {
2065
1549
  const [runtimeInfo, setRuntimeInfo] = useState({
2066
1550
  status: "initializing",
@@ -2126,13 +1610,6 @@ var useRuntimeInfo = (options) => {
2126
1610
  }));
2127
1611
  return null;
2128
1612
  }
2129
- if (event.type === "streamingText") {
2130
- setRuntimeInfo((prev) => ({
2131
- ...prev,
2132
- streamingText: (prev.streamingText ?? "") + event.text
2133
- }));
2134
- return null;
2135
- }
2136
1613
  if ("stepNumber" in event) {
2137
1614
  const isComplete = event.type === "completeRun";
2138
1615
  const isStopped = STOP_EVENT_TYPES.includes(event.type);
@@ -2170,72 +1647,6 @@ var useRuntimeInfo = (options) => {
2170
1647
  setContextWindowUsage
2171
1648
  };
2172
1649
  };
2173
- var useTextInput = (options) => {
2174
- const [input, setInput] = useState("");
2175
- const inputRef = useLatestRef(input);
2176
- const onSubmitRef = useLatestRef(options.onSubmit);
2177
- const onCancelRef = useLatestRef(options.onCancel);
2178
- const handleInput = useCallback(
2179
- (inputChar, key) => {
2180
- if (key.escape) {
2181
- setInput("");
2182
- onCancelRef.current?.();
2183
- return;
2184
- }
2185
- if (key.return && inputRef.current.trim()) {
2186
- onSubmitRef.current(inputRef.current.trim());
2187
- setInput("");
2188
- return;
2189
- }
2190
- if (key.backspace || key.delete) {
2191
- setInput((prev) => prev.slice(0, -1));
2192
- return;
2193
- }
2194
- if (!key.ctrl && !key.meta && inputChar) {
2195
- setInput((prev) => prev + inputChar);
2196
- }
2197
- },
2198
- [inputRef, onSubmitRef, onCancelRef]
2199
- );
2200
- const reset = useCallback(() => {
2201
- setInput("");
2202
- }, []);
2203
- return { input, handleInput, reset };
2204
- };
2205
- var useListNavigation = (options) => {
2206
- const { items, onSelect, onBack } = options;
2207
- const [selectedIndex, setSelectedIndex] = useState(0);
2208
- useEffect(() => {
2209
- if (selectedIndex >= items.length && items.length > 0) {
2210
- setSelectedIndex(items.length - 1);
2211
- }
2212
- }, [items.length, selectedIndex]);
2213
- const handleNavigation = useCallback(
2214
- (inputChar, key) => {
2215
- if (key.upArrow && items.length > 0) {
2216
- setSelectedIndex((prev) => Math.max(0, prev - 1));
2217
- return true;
2218
- }
2219
- if (key.downArrow && items.length > 0) {
2220
- setSelectedIndex((prev) => Math.min(items.length - 1, prev + 1));
2221
- return true;
2222
- }
2223
- if (key.return && items[selectedIndex]) {
2224
- onSelect?.(items[selectedIndex]);
2225
- return true;
2226
- }
2227
- if ((key.escape || inputChar === "b") && onBack) {
2228
- onBack();
2229
- return true;
2230
- }
2231
- return false;
2232
- },
2233
- [items, selectedIndex, onSelect, onBack]
2234
- );
2235
- return { selectedIndex, handleNavigation };
2236
- };
2237
-
2238
- // src/tui/hooks/ui/use-expert-selector.ts
2239
1650
  var useExpertSelector = (options) => {
2240
1651
  const { experts, onExpertSelect, extraKeyHandler } = options;
2241
1652
  const [inputMode, setInputMode] = useState(false);
@@ -2272,321 +1683,162 @@ var useExpertSelector = (options) => {
2272
1683
  handleKeyInput
2273
1684
  };
2274
1685
  };
2275
- var useAppState = (props) => {
2276
- const {
2277
- needsQueryInput,
2278
- showHistory,
2279
- initialExpertName,
2280
- initialQuery,
2281
- initialConfig,
2282
- configuredExperts,
2283
- recentExperts,
2284
- historyJobs,
2285
- onComplete,
2286
- onContinue,
2287
- onResumeFromCheckpoint,
2288
- onLoadCheckpoints,
2289
- onLoadEvents,
2290
- onLoadHistoricalEvents,
2291
- onReady
2292
- } = props;
2293
- const eventStore = useEventStore();
2294
- const {
2295
- runtimeInfo,
2296
- handleEvent,
2297
- setExpertName,
2298
- setQuery,
2299
- setCurrentStep,
2300
- setContextWindowUsage
2301
- } = useRuntimeInfo({ initialExpertName, initialConfig });
2302
- const { state: inputState, dispatch } = useInputState({
2303
- showHistory,
2304
- needsQueryInput,
2305
- initialExpertName,
2306
- configuredExperts,
2307
- recentExperts,
2308
- historyJobs
2309
- });
2310
- const { markAsStarted, handleQuerySubmit } = useRunActions({
2311
- expertName: runtimeInfo.expertName,
2312
- dispatch,
2313
- setQuery,
2314
- onComplete,
2315
- onContinue,
2316
- onReady,
2317
- stepStoreAddEvent: eventStore.addEvent,
2318
- handleEvent
2319
- });
2320
- useEffect(() => {
2321
- if (initialExpertName && initialQuery) {
2322
- setQuery(initialQuery);
2323
- dispatch({ type: "START_RUN" });
2324
- markAsStarted();
2325
- onComplete(initialExpertName, initialQuery);
2326
- }
2327
- }, [markAsStarted, initialExpertName, onComplete, setQuery, initialQuery, dispatch]);
2328
- const { allExperts, handleExpertSelect } = useExpertActions({
2329
- needsQueryInput,
2330
- inputState,
2331
- dispatch,
2332
- setExpertName,
2333
- onComplete,
2334
- markAsStarted,
2335
- configuredExperts,
2336
- recentExperts
2337
- });
2338
- const history = useHistoryActions({
2339
- allExperts,
2340
- historyJobs,
2341
- onLoadCheckpoints,
2342
- onLoadEvents,
2343
- onResumeFromCheckpoint,
2344
- onLoadHistoricalEvents,
2345
- setHistoricalEvents: eventStore.setHistoricalEvents,
2346
- setCurrentStep,
2347
- setContextWindowUsage,
2348
- dispatch,
2349
- setExpertName
2350
- });
2351
- const handleBack = useCallback(() => {
2352
- history.handleBack(inputState);
2353
- }, [history.handleBack, inputState]);
2354
- const inputAreaContextValue = useMemo(
2355
- () => ({
2356
- onExpertSelect: handleExpertSelect,
2357
- onQuerySubmit: handleQuerySubmit,
2358
- onJobSelect: history.handleJobSelect,
2359
- onJobResume: history.handleJobResume,
2360
- onCheckpointSelect: history.handleCheckpointSelect,
2361
- onCheckpointResume: history.handleCheckpointResume,
2362
- onEventSelect: history.handleEventSelect,
2363
- onBack: handleBack,
2364
- onSwitchToExperts: history.handleSwitchToExperts,
2365
- onSwitchToHistory: history.handleSwitchToHistory
2366
- }),
2367
- [
2368
- handleExpertSelect,
2369
- handleQuerySubmit,
2370
- history.handleJobSelect,
2371
- history.handleJobResume,
2372
- history.handleCheckpointSelect,
2373
- history.handleCheckpointResume,
2374
- history.handleEventSelect,
2375
- handleBack,
2376
- history.handleSwitchToExperts,
2377
- history.handleSwitchToHistory
2378
- ]
2379
- );
1686
+ function isBaseStep(step) {
1687
+ if (typeof step !== "object" || step === null) return false;
1688
+ const stepType = step.type;
1689
+ return stepType === "selectExpert" || stepType === "loadingVersions" || stepType === "selectVersion" || stepType === "error";
1690
+ }
1691
+ function useExpertVersionWizard({
1692
+ experts,
1693
+ onFetchVersions,
1694
+ customStepFromVersion
1695
+ }) {
1696
+ const [step, setStep] = useState({ type: "selectExpert" });
1697
+ const handleExpertSelect = async (expertName) => {
1698
+ setStep({ type: "loadingVersions", expertName });
1699
+ try {
1700
+ const versions = await onFetchVersions(expertName);
1701
+ if (versions.length === 0) {
1702
+ setStep({ type: "error", message: `No versions found for ${expertName}` });
1703
+ return;
1704
+ }
1705
+ setStep({ type: "selectVersion", expertName, versions });
1706
+ } catch (error) {
1707
+ setStep({
1708
+ type: "error",
1709
+ message: error instanceof Error ? error.message : "Failed to fetch versions"
1710
+ });
1711
+ }
1712
+ };
1713
+ const handleVersionSelect = (version) => {
1714
+ const customStep = customStepFromVersion(version);
1715
+ setStep(customStep);
1716
+ };
1717
+ const handleBack = (customBackHandler) => {
1718
+ if (isBaseStep(step)) {
1719
+ switch (step.type) {
1720
+ case "selectVersion":
1721
+ setStep({ type: "selectExpert" });
1722
+ break;
1723
+ case "error":
1724
+ setStep({ type: "selectExpert" });
1725
+ break;
1726
+ }
1727
+ } else if (customBackHandler) {
1728
+ customBackHandler(step);
1729
+ }
1730
+ };
1731
+ const renderBaseStep = (options) => {
1732
+ if (!isBaseStep(step)) {
1733
+ return null;
1734
+ }
1735
+ switch (step.type) {
1736
+ case "selectExpert":
1737
+ return /* @__PURE__ */ jsx(
1738
+ WizardExpertSelector,
1739
+ {
1740
+ title: options?.title ?? "Select an Expert:",
1741
+ experts,
1742
+ onSelect: handleExpertSelect
1743
+ }
1744
+ );
1745
+ case "loadingVersions":
1746
+ return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { children: [
1747
+ "Loading versions for ",
1748
+ step.expertName,
1749
+ "..."
1750
+ ] }) });
1751
+ case "selectVersion":
1752
+ return /* @__PURE__ */ jsx(
1753
+ VersionSelector,
1754
+ {
1755
+ expertName: step.expertName,
1756
+ versions: step.versions,
1757
+ onSelect: handleVersionSelect,
1758
+ onBack: handleBack,
1759
+ title: options?.versionSelectorTitle
1760
+ }
1761
+ );
1762
+ case "error":
1763
+ return /* @__PURE__ */ jsx(ErrorStep, { message: step.message, onBack: handleBack });
1764
+ default:
1765
+ return null;
1766
+ }
1767
+ };
1768
+ const handlers = {
1769
+ handleExpertSelect,
1770
+ handleVersionSelect,
1771
+ handleBack
1772
+ };
2380
1773
  return {
2381
- eventStore,
2382
- runtimeInfo,
2383
- inputState,
2384
- inputAreaContextValue
1774
+ step,
1775
+ setStep,
1776
+ handlers,
1777
+ renderBaseStep,
1778
+ isBaseStep: isBaseStep(step)
2385
1779
  };
1780
+ }
1781
+ var ExpertList = ({
1782
+ experts,
1783
+ selectedIndex,
1784
+ showSource = false,
1785
+ inline = false,
1786
+ maxItems
1787
+ }) => {
1788
+ const displayExperts = maxItems ? experts.slice(0, maxItems) : experts;
1789
+ if (displayExperts.length === 0) {
1790
+ return /* @__PURE__ */ jsx(Text, { color: "gray", children: "No experts found." });
1791
+ }
1792
+ const items = displayExperts.map((expert, index) => /* @__PURE__ */ jsxs(Text, { color: index === selectedIndex ? "cyan" : "gray", children: [
1793
+ index === selectedIndex ? ">" : " ",
1794
+ " ",
1795
+ showSource ? expert.key : expert.name,
1796
+ showSource && expert.source && /* @__PURE__ */ jsxs(Fragment, { children: [
1797
+ " ",
1798
+ /* @__PURE__ */ jsxs(Text, { color: expert.source === "configured" ? "green" : "yellow", children: [
1799
+ "[",
1800
+ expert.source === "configured" ? "config" : "recent",
1801
+ "]"
1802
+ ] })
1803
+ ] }),
1804
+ inline ? " " : ""
1805
+ ] }, expert.key));
1806
+ return inline ? /* @__PURE__ */ jsx(Box, { children: items }) : /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: items });
2386
1807
  };
2387
- var ListBrowser = ({
2388
- title,
2389
- items,
2390
- renderItem,
2391
- onSelect,
2392
- emptyMessage = "No items found",
2393
- extraKeyHandler,
2394
- onBack,
2395
- maxItems = UI_CONSTANTS.MAX_VISIBLE_LIST_ITEMS
1808
+ var ExpertSelectorBase = ({
1809
+ experts,
1810
+ hint,
1811
+ onExpertSelect,
1812
+ showSource = false,
1813
+ inline = false,
1814
+ maxItems = UI_CONSTANTS.MAX_VISIBLE_LIST_ITEMS,
1815
+ extraKeyHandler
2396
1816
  }) => {
2397
- const { selectedIndex, handleNavigation } = useListNavigation({
2398
- items,
2399
- onSelect,
2400
- onBack
2401
- });
2402
- useInput((inputChar, key) => {
2403
- if (extraKeyHandler?.(inputChar, key, items[selectedIndex])) return;
2404
- handleNavigation(inputChar, key);
1817
+ const { inputMode, input, selectedIndex, handleKeyInput } = useExpertSelector({
1818
+ experts,
1819
+ onExpertSelect,
1820
+ extraKeyHandler
2405
1821
  });
2406
- const { scrollOffset, displayItems } = useMemo(() => {
2407
- const offset = Math.max(0, Math.min(selectedIndex - maxItems + 1, items.length - maxItems));
2408
- return {
2409
- scrollOffset: offset,
2410
- displayItems: items.slice(offset, offset + maxItems)
2411
- };
2412
- }, [items, selectedIndex, maxItems]);
2413
- const hasMoreAbove = scrollOffset > 0;
2414
- const hasMoreBelow = scrollOffset + maxItems < items.length;
2415
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2416
- /* @__PURE__ */ jsxs(Box, { children: [
2417
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: title }),
2418
- items.length > maxItems && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
2419
- " ",
2420
- "(",
2421
- selectedIndex + 1,
2422
- "/",
2423
- items.length,
2424
- ")"
2425
- ] })
1822
+ useInput(handleKeyInput);
1823
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: inline ? "row" : "column", children: [
1824
+ !inputMode && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
1825
+ /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { color: "cyan", children: hint }) }),
1826
+ /* @__PURE__ */ jsx(
1827
+ ExpertList,
1828
+ {
1829
+ experts,
1830
+ selectedIndex,
1831
+ showSource,
1832
+ inline,
1833
+ maxItems
1834
+ }
1835
+ )
2426
1836
  ] }),
2427
- hasMoreAbove && /* @__PURE__ */ jsx(Text, { color: "gray", children: INDICATOR.ELLIPSIS }),
2428
- /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: displayItems.length === 0 ? /* @__PURE__ */ jsx(Text, { color: "gray", children: emptyMessage }) : displayItems.map((item, index) => {
2429
- const actualIndex = scrollOffset + index;
2430
- return renderItem(item, actualIndex === selectedIndex, actualIndex);
2431
- }) }),
2432
- hasMoreBelow && /* @__PURE__ */ jsx(Text, { color: "gray", children: INDICATOR.ELLIPSIS })
2433
- ] });
2434
- };
2435
- var BrowsingCheckpointsInput = ({
2436
- job,
2437
- checkpoints,
2438
- onCheckpointSelect,
2439
- onCheckpointResume,
2440
- onBack
2441
- }) => /* @__PURE__ */ jsx(
2442
- ListBrowser,
2443
- {
2444
- title: `Checkpoints for ${job.expertKey} ${KEY_HINTS.NAVIGATE} ${KEY_HINTS.RESUME} ${KEY_HINTS.EVENTS} ${KEY_HINTS.BACK}`,
2445
- items: checkpoints,
2446
- onSelect: onCheckpointResume,
2447
- onBack,
2448
- emptyMessage: "No checkpoints found",
2449
- extraKeyHandler: (char, _key, selected) => {
2450
- if (char === "e" && selected) {
2451
- onCheckpointSelect(selected);
2452
- return true;
2453
- }
2454
- return false;
2455
- },
2456
- renderItem: (cp, isSelected) => /* @__PURE__ */ jsxs(Text, { color: isSelected ? "cyan" : "gray", children: [
2457
- isSelected ? ">" : " ",
2458
- " Step ",
2459
- cp.stepNumber,
2460
- " (",
2461
- cp.id,
2462
- ")"
2463
- ] }, cp.id)
2464
- }
2465
- );
2466
- var BrowsingEventDetailInput = ({ event, onBack }) => {
2467
- useInput((input, key) => {
2468
- if (input === "b" || key.escape) {
2469
- onBack();
2470
- }
2471
- });
2472
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
2473
- /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
2474
- /* @__PURE__ */ jsx(Text, { bold: true, children: "Event Detail" }),
2475
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2476
- " ",
2477
- KEY_HINTS.BACK
2478
- ] })
2479
- ] }),
2480
- /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [
2481
- /* @__PURE__ */ jsxs(Text, { children: [
2482
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Type: " }),
2483
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: event.type })
2484
- ] }),
2485
- /* @__PURE__ */ jsxs(Text, { children: [
2486
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Step: " }),
2487
- /* @__PURE__ */ jsx(Text, { children: event.stepNumber })
2488
- ] }),
2489
- /* @__PURE__ */ jsxs(Text, { children: [
2490
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Timestamp: " }),
2491
- /* @__PURE__ */ jsx(Text, { children: formatTimestamp2(event.timestamp) })
2492
- ] }),
2493
- /* @__PURE__ */ jsxs(Text, { children: [
2494
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "ID: " }),
2495
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: event.id })
2496
- ] }),
2497
- /* @__PURE__ */ jsxs(Text, { children: [
2498
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Run ID: " }),
2499
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: event.runId })
2500
- ] })
2501
- ] })
2502
- ] });
2503
- };
2504
- var BrowsingEventsInput = ({
2505
- checkpoint,
2506
- events,
2507
- onEventSelect,
2508
- onBack
2509
- }) => /* @__PURE__ */ jsx(
2510
- ListBrowser,
2511
- {
2512
- title: `Events for Step ${checkpoint.stepNumber} ${KEY_HINTS.NAVIGATE} ${KEY_HINTS.SELECT} ${KEY_HINTS.BACK}`,
2513
- items: events,
2514
- onSelect: onEventSelect,
2515
- onBack,
2516
- emptyMessage: "No events found",
2517
- renderItem: (ev, isSelected) => /* @__PURE__ */ jsxs(Text, { color: isSelected ? "cyan" : "gray", children: [
2518
- isSelected ? ">" : " ",
2519
- " [",
2520
- ev.type,
2521
- "] Step ",
2522
- ev.stepNumber,
2523
- " (",
2524
- formatTimestamp2(ev.timestamp),
2525
- ")"
2526
- ] }, ev.id)
2527
- }
2528
- );
2529
- var ExpertList = ({
2530
- experts,
2531
- selectedIndex,
2532
- showSource = false,
2533
- inline = false,
2534
- maxItems
2535
- }) => {
2536
- const displayExperts = maxItems ? experts.slice(0, maxItems) : experts;
2537
- if (displayExperts.length === 0) {
2538
- return /* @__PURE__ */ jsx(Text, { color: "gray", children: "No experts found." });
2539
- }
2540
- const items = displayExperts.map((expert, index) => /* @__PURE__ */ jsxs(Text, { color: index === selectedIndex ? "cyan" : "gray", children: [
2541
- index === selectedIndex ? ">" : " ",
2542
- " ",
2543
- showSource ? expert.key : expert.name,
2544
- showSource && expert.source && /* @__PURE__ */ jsxs(Fragment, { children: [
2545
- " ",
2546
- /* @__PURE__ */ jsxs(Text, { color: expert.source === "configured" ? "green" : "yellow", children: [
2547
- "[",
2548
- expert.source === "configured" ? "config" : "recent",
2549
- "]"
2550
- ] })
2551
- ] }),
2552
- inline ? " " : ""
2553
- ] }, expert.key));
2554
- return inline ? /* @__PURE__ */ jsx(Box, { children: items }) : /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: items });
2555
- };
2556
- var ExpertSelectorBase = ({
2557
- experts,
2558
- hint,
2559
- onExpertSelect,
2560
- showSource = false,
2561
- inline = false,
2562
- maxItems = UI_CONSTANTS.MAX_VISIBLE_LIST_ITEMS,
2563
- extraKeyHandler
2564
- }) => {
2565
- const { inputMode, input, selectedIndex, handleKeyInput } = useExpertSelector({
2566
- experts,
2567
- onExpertSelect,
2568
- extraKeyHandler
2569
- });
2570
- useInput(handleKeyInput);
2571
- return /* @__PURE__ */ jsxs(Box, { flexDirection: inline ? "row" : "column", children: [
2572
- !inputMode && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2573
- /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { color: "cyan", children: hint }) }),
2574
- /* @__PURE__ */ jsx(
2575
- ExpertList,
2576
- {
2577
- experts,
2578
- selectedIndex,
2579
- showSource,
2580
- inline,
2581
- maxItems
2582
- }
2583
- )
2584
- ] }),
2585
- inputMode && /* @__PURE__ */ jsxs(Box, { children: [
2586
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Expert: " }),
2587
- /* @__PURE__ */ jsx(Text, { color: "white", children: input }),
2588
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: "_" })
2589
- ] })
1837
+ inputMode && /* @__PURE__ */ jsxs(Box, { children: [
1838
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Expert: " }),
1839
+ /* @__PURE__ */ jsx(Text, { color: "white", children: input }),
1840
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: "_" })
1841
+ ] })
2590
1842
  ] });
2591
1843
  };
2592
1844
  var BrowsingExpertsInput = ({
@@ -2654,7 +1906,7 @@ var BrowsingHistoryInput = ({
2654
1906
  ] }, job.jobId)
2655
1907
  }
2656
1908
  );
2657
- var BrowserRouter = ({ inputState }) => {
1909
+ var BrowserRouter = ({ inputState, showEventsHint = true }) => {
2658
1910
  const ctx = useInputAreaContext();
2659
1911
  const handleEventSelect = useCallback(
2660
1912
  (event) => {
@@ -2692,7 +1944,8 @@ var BrowserRouter = ({ inputState }) => {
2692
1944
  checkpoints: inputState.checkpoints,
2693
1945
  onCheckpointSelect: ctx.onCheckpointSelect,
2694
1946
  onCheckpointResume: ctx.onCheckpointResume,
2695
- onBack: ctx.onBack
1947
+ onBack: ctx.onBack,
1948
+ showEventsHint
2696
1949
  }
2697
1950
  );
2698
1951
  case "browsingEvents":
@@ -2711,256 +1964,180 @@ var BrowserRouter = ({ inputState }) => {
2711
1964
  return assertNever(inputState);
2712
1965
  }
2713
1966
  };
2714
- function ErrorStep({ message, onBack }) {
2715
- useInput(() => onBack());
1967
+ var ActionRowSimple = ({
1968
+ indicatorColor,
1969
+ text,
1970
+ textDimColor = false
1971
+ }) => /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
1972
+ /* @__PURE__ */ jsx(Box, { paddingRight: 1, children: /* @__PURE__ */ jsx(Text, { color: indicatorColor, children: INDICATOR.BULLET }) }),
1973
+ /* @__PURE__ */ jsx(Text, { color: "white", dimColor: textDimColor, children: text })
1974
+ ] }) });
1975
+ var ActionRow = ({ indicatorColor, label, summary, children }) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
1976
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
1977
+ /* @__PURE__ */ jsx(Text, { color: indicatorColor, children: INDICATOR.BULLET }),
1978
+ /* @__PURE__ */ jsx(Text, { color: "white", children: label }),
1979
+ summary && /* @__PURE__ */ jsx(Text, { color: "white", dimColor: true, children: summary })
1980
+ ] }),
1981
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
1982
+ /* @__PURE__ */ jsx(Box, { paddingRight: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: INDICATOR.TREE }) }),
1983
+ children
1984
+ ] })
1985
+ ] });
1986
+ var CheckpointActionRow = ({ action }) => {
1987
+ if (action.type === "parallelGroup") {
1988
+ return renderParallelGroup(action);
1989
+ }
1990
+ const actionElement = renderAction(action);
1991
+ if (!actionElement) return null;
2716
1992
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2717
- /* @__PURE__ */ jsxs(Text, { color: "red", children: [
2718
- "Error: ",
2719
- message
2720
- ] }),
2721
- /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Press any key to go back" }) })
1993
+ action.reasoning && renderReasoning(action.reasoning),
1994
+ actionElement
2722
1995
  ] });
2723
- }
2724
- var RunSetting = ({
2725
- info,
2726
- eventCount,
2727
- isEditing,
2728
- expertName,
2729
- onQuerySubmit
2730
- }) => {
2731
- const { input, handleInput } = useTextInput({
2732
- onSubmit: onQuerySubmit ?? (() => {
1996
+ };
1997
+ function renderParallelGroup(group) {
1998
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
1999
+ group.reasoning && renderReasoning(group.reasoning),
2000
+ group.activities.map((activity, index) => {
2001
+ const actionElement = renderAction(activity);
2002
+ if (!actionElement) return null;
2003
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: actionElement }, activity.id || `${activity.type}-${index}`);
2733
2004
  })
2734
- });
2735
- useInput(handleInput, { isActive: isEditing });
2736
- const displayExpertName = expertName ?? info.expertName;
2737
- const skills = info.activeSkills.length > 0 ? info.activeSkills.join(", ") : "";
2738
- const step = info.currentStep !== void 0 ? String(info.currentStep) : "";
2739
- const usagePercent = (info.contextWindowUsage * 100).toFixed(1);
2740
- return /* @__PURE__ */ jsxs(
2741
- Box,
2742
- {
2743
- flexDirection: "column",
2744
- borderStyle: "single",
2745
- borderColor: "gray",
2746
- borderTop: true,
2747
- borderBottom: false,
2748
- borderLeft: false,
2749
- borderRight: false,
2750
- children: [
2751
- /* @__PURE__ */ jsxs(Text, { children: [
2752
- /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: info.runtime === "local" ? "Local" : info.runtime === "claude-code" ? "Claude Code" : (info.runtime ?? "docker").charAt(0).toUpperCase() + (info.runtime ?? "docker").slice(1) }),
2753
- info.runtime === "docker" || !info.runtime ? /* @__PURE__ */ jsxs(Text, { color: info.dockerState === "building" ? "yellow" : "gray", children: [
2754
- " ",
2755
- "(",
2756
- info.dockerState ?? "initializing",
2757
- ")"
2758
- ] }) : info.runtimeVersion && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
2759
- " ",
2760
- "(",
2761
- info.runtime === "local" ? `v${info.runtimeVersion}` : info.runtimeVersion,
2762
- ")"
2763
- ] })
2764
- ] }),
2765
- /* @__PURE__ */ jsxs(Text, { children: [
2766
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Expert: " }),
2767
- /* @__PURE__ */ jsx(Text, { color: "white", children: displayExpertName }),
2768
- /* @__PURE__ */ jsx(Text, { color: "gray", children: " / Skills: " }),
2769
- /* @__PURE__ */ jsx(Text, { color: "green", children: skills })
2770
- ] }),
2771
- /* @__PURE__ */ jsxs(Text, { children: [
2772
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Status: " }),
2773
- /* @__PURE__ */ jsx(
2774
- Text,
2775
- {
2776
- color: info.status === "running" ? "green" : info.status === "completed" ? "cyan" : "yellow",
2777
- children: info.status
2778
- }
2779
- ),
2780
- /* @__PURE__ */ jsx(Text, { color: "gray", children: " / Step: " }),
2781
- /* @__PURE__ */ jsx(Text, { color: "white", children: step }),
2782
- /* @__PURE__ */ jsx(Text, { color: "gray", children: " / Events: " }),
2783
- /* @__PURE__ */ jsx(Text, { color: "white", children: eventCount }),
2784
- /* @__PURE__ */ jsx(Text, { color: "gray", children: " / Usage: " }),
2785
- /* @__PURE__ */ jsxs(Text, { color: "white", children: [
2786
- usagePercent,
2787
- "%"
2788
- ] })
2789
- ] }),
2790
- /* @__PURE__ */ jsxs(Text, { children: [
2791
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Model: " }),
2792
- /* @__PURE__ */ jsx(Text, { color: "white", children: info.model })
2793
- ] }),
2794
- /* @__PURE__ */ jsxs(Box, { children: [
2795
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Query: " }),
2796
- isEditing ? /* @__PURE__ */ jsxs(Fragment, { children: [
2797
- /* @__PURE__ */ jsx(Text, { color: "white", children: input }),
2798
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: "_" })
2799
- ] }) : /* @__PURE__ */ jsx(Text, { color: "white", children: info.query })
2800
- ] })
2801
- ]
2802
- }
2803
- );
2804
- };
2805
- var QueryRow = ({ text }) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
2806
- /* @__PURE__ */ jsx(Box, { paddingRight: 1, children: /* @__PURE__ */ jsx(Text, { color: "cyan", children: INDICATOR.CHEVRON_RIGHT }) }),
2807
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: text })
2808
- ] });
2809
- var CompletionRow = ({ text }) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2810
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
2811
- /* @__PURE__ */ jsx(Text, { color: "white", children: INDICATOR.BULLET }),
2812
- /* @__PURE__ */ jsx(Text, { color: "white", children: "Run Results" })
2813
- ] }),
2814
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
2815
- /* @__PURE__ */ jsx(Box, { paddingRight: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: INDICATOR.TREE }) }),
2816
- /* @__PURE__ */ jsx(Text, { color: "white", children: text })
2817
- ] })
2818
- ] });
2819
- var ErrorRow = ({ errorName, message, statusCode }) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2820
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
2821
- /* @__PURE__ */ jsx(Text, { color: "red", children: INDICATOR.BULLET }),
2822
- /* @__PURE__ */ jsxs(Text, { color: "red", bold: true, children: [
2823
- "Error: ",
2824
- errorName,
2825
- statusCode ? ` (${statusCode})` : ""
2826
- ] })
2827
- ] }),
2828
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
2829
- /* @__PURE__ */ jsx(Box, { paddingRight: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: INDICATOR.TREE }) }),
2830
- /* @__PURE__ */ jsx(Text, { color: "red", children: message })
2831
- ] })
2832
- ] });
2833
- var ActionRowSimple = ({
2834
- indicatorColor,
2835
- text,
2836
- textDimColor = false
2837
- }) => /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
2838
- /* @__PURE__ */ jsx(Box, { paddingRight: 1, children: /* @__PURE__ */ jsx(Text, { color: indicatorColor, children: INDICATOR.BULLET }) }),
2839
- /* @__PURE__ */ jsx(Text, { color: "white", dimColor: textDimColor, children: text })
2840
- ] }) });
2841
- var ActionRow = ({ indicatorColor, label, summary, children }) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2842
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
2843
- /* @__PURE__ */ jsx(Text, { color: indicatorColor, children: INDICATOR.BULLET }),
2844
- /* @__PURE__ */ jsx(Text, { color: "white", children: label }),
2845
- summary && /* @__PURE__ */ jsx(Text, { color: "white", dimColor: true, children: summary })
2846
- ] }),
2847
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
2848
- /* @__PURE__ */ jsx(Box, { paddingRight: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: INDICATOR.TREE }) }),
2849
- children
2850
- ] })
2851
- ] });
2852
- var getResultText = (result) => {
2853
- if (!result) return "";
2854
- return result.find((r) => r.type === "textPart")?.text ?? "";
2855
- };
2856
- var getString = (args, key) => {
2857
- const value = args[key];
2858
- return typeof value === "string" ? value : "";
2859
- };
2860
- var getStringArray = (args, key) => {
2861
- const value = args[key];
2862
- return Array.isArray(value) ? value : [];
2863
- };
2864
- var getNumberArray = (args, key) => {
2865
- const value = args[key];
2866
- return Array.isArray(value) ? value : [];
2867
- };
2868
- var renderThink = (args) => {
2869
- const thought = getString(args, "thought");
2870
- return /* @__PURE__ */ jsx(ActionRowSimple, { indicatorColor: "white", text: thought, textDimColor: true });
2871
- };
2872
- var renderCompleteReasoning = (text) => {
2873
- const label = "Reasoning";
2005
+ ] });
2006
+ }
2007
+ function renderReasoning(text) {
2874
2008
  const lines = text.split("\n");
2875
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "white", label, children: /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: lines.map((line, idx) => /* @__PURE__ */ jsx(Text, { dimColor: true, wrap: "wrap", children: line }, `reasoning-${idx}`)) }) });
2876
- };
2877
- var renderAttemptCompletion = () => null;
2878
- var getTodosFromResult = (result) => {
2879
- if (!result) return [];
2880
- const text = result.find((r) => r.type === "textPart")?.text;
2881
- if (!text) return [];
2882
- try {
2883
- const parsed = JSON.parse(text);
2884
- return parsed.todos ?? [];
2885
- } catch {
2886
- return [];
2009
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "white", label: "Reasoning", children: /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: lines.map((line, idx) => /* @__PURE__ */ jsx(Text, { dimColor: true, wrap: "wrap", children: line }, `reasoning-${idx}`)) }) });
2010
+ }
2011
+ function renderAction(action) {
2012
+ const color = action.type === "error" || "error" in action && action.error ? "red" : "green";
2013
+ switch (action.type) {
2014
+ case "query":
2015
+ return renderQuery(action.text, action.runId);
2016
+ case "retry":
2017
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "yellow", label: "Retry", children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: action.message || action.error }) });
2018
+ case "complete":
2019
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "green", label: "Run Results", children: /* @__PURE__ */ jsx(Text, { children: action.text }) });
2020
+ case "attemptCompletion": {
2021
+ if (action.error) {
2022
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "red", label: "Completion Failed", children: /* @__PURE__ */ jsx(Text, { color: "red", children: action.error }) });
2023
+ }
2024
+ const remaining = action.remainingTodos?.filter((t) => !t.completed) ?? [];
2025
+ if (remaining.length > 0) {
2026
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "yellow", label: "Completion Blocked", children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2027
+ remaining.length,
2028
+ " remaining task",
2029
+ remaining.length > 1 ? "s" : ""
2030
+ ] }) });
2031
+ }
2032
+ return /* @__PURE__ */ jsx(ActionRowSimple, { indicatorColor: "green", text: "Completion Accepted" });
2033
+ }
2034
+ case "todo":
2035
+ return renderTodo(action, color);
2036
+ case "clearTodo":
2037
+ return /* @__PURE__ */ jsx(ActionRowSimple, { indicatorColor: color, text: "Todo Cleared" });
2038
+ case "readTextFile":
2039
+ return renderReadTextFile(action, color);
2040
+ case "writeTextFile":
2041
+ return renderWriteTextFile(action, color);
2042
+ case "editTextFile":
2043
+ return renderEditTextFile(action, color);
2044
+ case "appendTextFile":
2045
+ return renderAppendTextFile(action, color);
2046
+ case "deleteFile":
2047
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Delete", summary: shortenPath(action.path), children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Removed" }) });
2048
+ case "deleteDirectory":
2049
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Delete Dir", summary: shortenPath(action.path), children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2050
+ "Removed",
2051
+ action.recursive ? " (recursive)" : ""
2052
+ ] }) });
2053
+ case "moveFile":
2054
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Move", summary: shortenPath(action.source), children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2055
+ "\u2192 ",
2056
+ shortenPath(action.destination, 30)
2057
+ ] }) });
2058
+ case "createDirectory":
2059
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Mkdir", summary: shortenPath(action.path), children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Created" }) });
2060
+ case "listDirectory":
2061
+ return renderListDirectory(action, color);
2062
+ case "getFileInfo":
2063
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Info", summary: shortenPath(action.path), children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Fetched" }) });
2064
+ case "readPdfFile":
2065
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "PDF", summary: shortenPath(action.path), children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Read" }) });
2066
+ case "readImageFile":
2067
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Image", summary: shortenPath(action.path), children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Read" }) });
2068
+ case "exec":
2069
+ return renderExec(action, color);
2070
+ case "delegate":
2071
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "yellow", label: action.delegateExpertKey, children: /* @__PURE__ */ jsx(
2072
+ Text,
2073
+ {
2074
+ dimColor: true,
2075
+ children: `{"query":"${truncateText(action.query, UI_CONSTANTS.TRUNCATE_TEXT_MEDIUM)}"}`
2076
+ }
2077
+ ) });
2078
+ case "delegationComplete":
2079
+ return /* @__PURE__ */ jsx(
2080
+ ActionRowSimple,
2081
+ {
2082
+ indicatorColor: "green",
2083
+ text: `Delegation Complete (${action.count} delegate${action.count > 1 ? "s" : ""} returned)`
2084
+ }
2085
+ );
2086
+ case "interactiveTool":
2087
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "yellow", label: `Interactive: ${action.toolName}`, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: truncateText(JSON.stringify(action.args), UI_CONSTANTS.TRUNCATE_TEXT_MEDIUM) }) });
2088
+ case "generalTool":
2089
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: action.toolName, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: truncateText(JSON.stringify(action.args), UI_CONSTANTS.TRUNCATE_TEXT_MEDIUM) }) });
2090
+ case "error":
2091
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "red", label: action.errorName ?? "Error", children: /* @__PURE__ */ jsx(Text, { color: "red", children: action.error ?? "Unknown error" }) });
2092
+ default: {
2093
+ return null;
2094
+ }
2887
2095
  }
2888
- };
2889
- var renderTodo = (args, result) => {
2890
- const newTodos = getStringArray(args, "newTodos");
2891
- const completedTodoIds = getNumberArray(args, "completedTodos");
2892
- if (newTodos.length === 0 && completedTodoIds.length === 0) return null;
2893
- if (newTodos.length > 0) {
2894
- const label2 = `Todo Added ${newTodos.length} task${newTodos.length > 1 ? "s" : ""}`;
2895
- const preview2 = newTodos.slice(0, RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW);
2896
- const remaining2 = newTodos.length - preview2.length;
2897
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "white", label: label2, children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2898
- preview2.map((todo, idx) => /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2899
- "\u25CB ",
2900
- todo
2901
- ] }, `todo-${idx}`)),
2902
- remaining2 > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2903
- "... +",
2904
- remaining2,
2905
- " more"
2906
- ] })
2907
- ] }) });
2908
- }
2909
- const todos = getTodosFromResult(result);
2910
- const completedTitles = completedTodoIds.map((id) => todos.find((t) => t.id === id)?.title).filter((t) => t !== void 0);
2911
- const label = `Todo Completed ${completedTodoIds.length} task${completedTodoIds.length > 1 ? "s" : ""}`;
2912
- if (completedTitles.length === 0) {
2913
- return /* @__PURE__ */ jsx(ActionRowSimple, { indicatorColor: "white", text: label });
2914
- }
2915
- const preview = completedTitles.slice(0, RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW);
2916
- const remaining = completedTitles.length - preview.length;
2917
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "white", label, children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2918
- preview.map((title, idx) => /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2096
+ }
2097
+ function renderTodo(action, color) {
2098
+ const { newTodos, completedTodos, todos } = action;
2099
+ const hasNewTodos = newTodos && newTodos.length > 0;
2100
+ const hasCompletedTodos = completedTodos && completedTodos.length > 0;
2101
+ if (!hasNewTodos && !hasCompletedTodos) {
2102
+ return /* @__PURE__ */ jsx(ActionRowSimple, { indicatorColor: color, text: "Todo No changes" });
2103
+ }
2104
+ const labelParts = [];
2105
+ if (hasNewTodos) {
2106
+ labelParts.push(`Added ${newTodos.length} task${newTodos.length > 1 ? "s" : ""}`);
2107
+ }
2108
+ if (hasCompletedTodos) {
2109
+ labelParts.push(
2110
+ `Completed ${completedTodos.length} task${completedTodos.length > 1 ? "s" : ""}`
2111
+ );
2112
+ }
2113
+ const label = `Todo ${labelParts.join(", ")}`;
2114
+ const completedTitles = hasCompletedTodos ? completedTodos.map((id) => todos.find((t) => t.id === id)?.title).filter((t) => t !== void 0) : [];
2115
+ if (!hasNewTodos && completedTitles.length === 0) {
2116
+ return /* @__PURE__ */ jsx(ActionRowSimple, { indicatorColor: color, text: label });
2117
+ }
2118
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label, children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2119
+ hasNewTodos && newTodos.slice(0, RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW).map((todo, idx) => /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2120
+ "\u25CB ",
2121
+ todo
2122
+ ] }, `todo-${idx}`)),
2123
+ hasNewTodos && newTodos.length > RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2124
+ "... +",
2125
+ newTodos.length - RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW,
2126
+ " more"
2127
+ ] }),
2128
+ completedTitles.slice(0, RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW).map((title, idx) => /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2919
2129
  "\u2713 ",
2920
2130
  title
2921
2131
  ] }, `completed-${idx}`)),
2922
- remaining > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2923
- "... +",
2924
- remaining,
2925
- " more"
2926
- ] })
2927
- ] }) });
2928
- };
2929
- var renderExec = (args, result, color) => {
2930
- const command = getString(args, "command");
2931
- const cmdArgs = getStringArray(args, "args");
2932
- const cwd = getString(args, "cwd");
2933
- const cwdPart = cwd ? ` ${shortenPath(cwd, 40)}` : "";
2934
- const cmdLine = truncateText(
2935
- `${command} ${cmdArgs.join(" ")}${cwdPart}`,
2936
- UI_CONSTANTS.TRUNCATE_TEXT_DEFAULT
2937
- );
2938
- const text = getResultText(result);
2939
- const { visible, remaining } = summarizeOutput(
2940
- text.split("\n"),
2941
- RENDER_CONSTANTS.EXEC_OUTPUT_MAX_LINES
2942
- );
2943
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: `Bash ${cmdLine}`, children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2944
- visible.map((line, idx) => /* @__PURE__ */ jsx(Text, { dimColor: true, children: truncateText(line, UI_CONSTANTS.TRUNCATE_TEXT_DEFAULT) }, `out-${idx}`)),
2945
- remaining > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2132
+ completedTitles.length > RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2946
2133
  "... +",
2947
- remaining,
2134
+ completedTitles.length - RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW,
2948
2135
  " more"
2949
2136
  ] })
2950
2137
  ] }) });
2951
- };
2952
- var parseReadTextFileResult = (result) => {
2953
- const text = getResultText(result);
2954
- if (!text) return { content: "" };
2955
- try {
2956
- return JSON.parse(text);
2957
- } catch {
2958
- return { content: "" };
2959
- }
2960
- };
2961
- var renderReadTextFile = (args, result, color) => {
2962
- const path11 = getString(args, "path");
2963
- const { content, from, to } = parseReadTextFileResult(result);
2138
+ }
2139
+ function renderReadTextFile(action, color) {
2140
+ const { path: path11, content, from, to } = action;
2964
2141
  const lineRange = from !== void 0 && to !== void 0 ? `#${from}-${to}` : "";
2965
2142
  const lines = content?.split("\n") ?? [];
2966
2143
  return /* @__PURE__ */ jsx(
@@ -2972,20 +2149,17 @@ var renderReadTextFile = (args, result, color) => {
2972
2149
  children: /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: lines.map((line, idx) => /* @__PURE__ */ jsx(Box, { flexDirection: "row", gap: 1, children: /* @__PURE__ */ jsx(Text, { color: "white", dimColor: true, children: line }) }, `read-${idx}`)) })
2973
2150
  }
2974
2151
  );
2975
- };
2976
- var renderWriteTextFile = (args, color) => {
2977
- const path11 = getString(args, "path");
2978
- const text = getString(args, "text");
2152
+ }
2153
+ function renderWriteTextFile(action, color) {
2154
+ const { path: path11, text } = action;
2979
2155
  const lines = text.split("\n");
2980
2156
  return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Write Text File", summary: shortenPath(path11), children: /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: lines.map((line, idx) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
2981
2157
  /* @__PURE__ */ jsx(Text, { color: "green", dimColor: true, children: "+" }),
2982
2158
  /* @__PURE__ */ jsx(Text, { color: "white", dimColor: true, children: line })
2983
2159
  ] }, `write-${idx}`)) }) });
2984
- };
2985
- var renderEditTextFile = (args, color) => {
2986
- const path11 = getString(args, "path");
2987
- const oldText = getString(args, "oldText");
2988
- const newText = getString(args, "newText");
2160
+ }
2161
+ function renderEditTextFile(action, color) {
2162
+ const { path: path11, oldText, newText } = action;
2989
2163
  const oldLines = oldText.split("\n");
2990
2164
  const newLines = newText.split("\n");
2991
2165
  return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Edit Text File", summary: shortenPath(path11), children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
@@ -2998,23 +2172,19 @@ var renderEditTextFile = (args, color) => {
2998
2172
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: line })
2999
2173
  ] }, `new-${idx}`))
3000
2174
  ] }) });
3001
- };
3002
- var renderAppendTextFile = (args, color) => {
3003
- const path11 = getString(args, "path");
3004
- const text = getString(args, "text");
2175
+ }
2176
+ function renderAppendTextFile(action, color) {
2177
+ const { path: path11, text } = action;
3005
2178
  const lines = text.split("\n");
3006
2179
  return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Append Text File", summary: shortenPath(path11), children: /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: lines.map((line, idx) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
3007
2180
  /* @__PURE__ */ jsx(Text, { color: "green", dimColor: true, children: "+" }),
3008
2181
  /* @__PURE__ */ jsx(Text, { color: "white", dimColor: true, children: line })
3009
2182
  ] }, `append-${idx}`)) }) });
3010
- };
3011
- var renderListDirectory = (args, result, color) => {
3012
- const path11 = getString(args, "path");
3013
- const text = getResultText(result);
3014
- const { visible, remaining } = summarizeOutput(
3015
- text.split("\n"),
3016
- RENDER_CONSTANTS.LIST_DIR_MAX_ITEMS
3017
- );
2183
+ }
2184
+ function renderListDirectory(action, color) {
2185
+ const { path: path11, items } = action;
2186
+ const itemLines = items?.map((item) => `${item.type === "directory" ? "\u{1F4C1}" : "\u{1F4C4}"} ${item.name}`) ?? [];
2187
+ const { visible, remaining } = summarizeOutput(itemLines, RENDER_CONSTANTS.LIST_DIR_MAX_ITEMS);
3018
2188
  return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "List", summary: shortenPath(path11), children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3019
2189
  visible.map((line, idx) => /* @__PURE__ */ jsx(Text, { dimColor: true, children: truncateText(line, UI_CONSTANTS.TRUNCATE_TEXT_DEFAULT) }, `dir-${idx}`)),
3020
2190
  remaining > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
@@ -3023,159 +2193,160 @@ var renderListDirectory = (args, result, color) => {
3023
2193
  " more"
3024
2194
  ] })
3025
2195
  ] }) });
3026
- };
3027
- var renderDeleteFile = (args, color) => {
3028
- const path11 = getString(args, "path");
3029
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Delete", summary: shortenPath(path11), children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Removed" }) });
3030
- };
3031
- var renderMoveFile = (args, color) => {
3032
- const sourcePath = getString(args, "sourcePath");
3033
- const destinationPath = getString(args, "destinationPath");
3034
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Move", summary: shortenPath(sourcePath), children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
3035
- "\u2192 ",
3036
- shortenPath(destinationPath, 30)
3037
- ] }) });
3038
- };
3039
- var renderCreateDirectory = (args, color) => {
3040
- const path11 = getString(args, "path");
3041
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Mkdir", summary: shortenPath(path11), children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Created" }) });
3042
- };
3043
- var renderGetFileInfo = (args, color) => {
3044
- const path11 = getString(args, "path");
3045
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Info", summary: shortenPath(path11), children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Fetched" }) });
3046
- };
3047
- var renderReadPdfFile = (args, color) => {
3048
- const path11 = getString(args, "path");
3049
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "PDF", summary: shortenPath(path11), children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Read" }) });
3050
- };
3051
- var renderReadImageFile = (args, color) => {
3052
- const path11 = getString(args, "path");
3053
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Image", summary: shortenPath(path11), children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Read" }) });
3054
- };
3055
- var renderTestUrl = (args, color) => {
3056
- const urls = getStringArray(args, "urls");
3057
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "TestURL", children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
3058
- urls.length,
3059
- " URL(s)"
3060
- ] }) });
3061
- };
3062
- var renderDefault = (toolName, args, color) => /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: toolName, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: truncateText(JSON.stringify(args), UI_CONSTANTS.TRUNCATE_TEXT_MEDIUM) }) });
3063
- var renderDelegationStarted = (expertName, runtime, version, query) => {
3064
- const label = `Delegation Started (${expertName}, ${runtime}, v${version})`;
3065
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "yellow", label, children: query && /* @__PURE__ */ jsx(Text, { dimColor: true, children: truncateText(query, UI_CONSTANTS.TRUNCATE_TEXT_MEDIUM) }) });
3066
- };
3067
- var renderDelegationCompleted = (expertName, runtime, version, result) => {
3068
- const label = `Delegation Completed (${expertName}, ${runtime}, v${version})`;
3069
- const trimmedResult = result?.trim();
3070
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "green", label, children: trimmedResult && /* @__PURE__ */ jsx(Text, { dimColor: true, children: truncateText(trimmedResult, UI_CONSTANTS.TRUNCATE_TEXT_MEDIUM) }) });
3071
- };
3072
- var renderDockerBuild = (stage, service, message) => {
3073
- const stageColors = {
3074
- pulling: "yellow",
3075
- building: "yellow",
3076
- complete: "green",
3077
- error: "red"
3078
- };
3079
- const color = stageColors[stage] ?? "yellow";
3080
- const stageLabel = stage.charAt(0).toUpperCase() + stage.slice(1);
3081
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: `Docker Build [${service}] ${stageLabel}`, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: truncateText(message, UI_CONSTANTS.TRUNCATE_TEXT_DEFAULT) }) });
3082
- };
3083
- var renderDockerContainer = (status, service, message) => {
3084
- const statusColors = {
3085
- starting: "yellow",
3086
- running: "green",
3087
- healthy: "green",
3088
- unhealthy: "red",
3089
- stopped: "white",
3090
- error: "red"
3091
- };
3092
- const color = statusColors[status] ?? "white";
3093
- const statusLabel = status.charAt(0).toUpperCase() + status.slice(1);
3094
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: `Docker [${service}] ${statusLabel}`, children: message && /* @__PURE__ */ jsx(Text, { dimColor: true, children: truncateText(message, UI_CONSTANTS.TRUNCATE_TEXT_DEFAULT) }) });
3095
- };
3096
- var renderProxyAccess = (action, domain, port, reason) => {
3097
- const isAllowed = action === "allowed";
3098
- const color = isAllowed ? "green" : "red";
3099
- const icon = isAllowed ? "\u2713" : "\u2717";
3100
- const detail = reason ? `${domain}:${port} (${reason})` : `${domain}:${port}`;
3101
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Proxy", children: /* @__PURE__ */ jsxs(Text, { color, children: [
3102
- icon,
3103
- " ",
3104
- detail
2196
+ }
2197
+ function renderExec(action, color) {
2198
+ const { command, args, cwd, output } = action;
2199
+ const cwdPart = cwd ? ` ${shortenPath(cwd, 40)}` : "";
2200
+ const cmdLine = truncateText(
2201
+ `${command} ${args.join(" ")}${cwdPart}`,
2202
+ UI_CONSTANTS.TRUNCATE_TEXT_DEFAULT
2203
+ );
2204
+ const outputLines = output?.split("\n") ?? [];
2205
+ const { visible, remaining } = summarizeOutput(
2206
+ outputLines,
2207
+ RENDER_CONSTANTS.EXEC_OUTPUT_MAX_LINES
2208
+ );
2209
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: `Bash ${cmdLine}`, children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2210
+ visible.map((line, idx) => /* @__PURE__ */ jsx(Text, { dimColor: true, children: truncateText(line, UI_CONSTANTS.TRUNCATE_TEXT_DEFAULT) }, `out-${idx}`)),
2211
+ remaining > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2212
+ "... +",
2213
+ remaining,
2214
+ " more"
2215
+ ] })
3105
2216
  ] }) });
2217
+ }
2218
+ function renderQuery(text, runId) {
2219
+ const lines = text.split("\n");
2220
+ const shortRunId = runId.slice(0, 8);
2221
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "cyan", label: "Query", summary: `(${shortRunId})`, children: /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: lines.map((line, idx) => /* @__PURE__ */ jsx(Text, { dimColor: true, wrap: "wrap", children: line }, `query-${idx}`)) }) });
2222
+ }
2223
+ function ErrorStep({ message, onBack }) {
2224
+ useInput(() => onBack());
2225
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2226
+ /* @__PURE__ */ jsxs(Text, { color: "red", children: [
2227
+ "Error: ",
2228
+ message
2229
+ ] }),
2230
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Press any key to go back" }) })
2231
+ ] });
2232
+ }
2233
+ var RunSetting = ({
2234
+ info,
2235
+ eventCount,
2236
+ isEditing,
2237
+ expertName,
2238
+ onQuerySubmit
2239
+ }) => {
2240
+ const { input, handleInput } = useTextInput({
2241
+ onSubmit: onQuerySubmit ?? (() => {
2242
+ })
2243
+ });
2244
+ useInput(handleInput, { isActive: isEditing });
2245
+ const displayExpertName = expertName ?? info.expertName;
2246
+ const skills = info.activeSkills.length > 0 ? info.activeSkills.join(", ") : "";
2247
+ const step = info.currentStep !== void 0 ? String(info.currentStep) : "";
2248
+ const usagePercent = (info.contextWindowUsage * 100).toFixed(1);
2249
+ return /* @__PURE__ */ jsxs(
2250
+ Box,
2251
+ {
2252
+ flexDirection: "column",
2253
+ borderStyle: "single",
2254
+ borderColor: "gray",
2255
+ borderTop: true,
2256
+ borderBottom: false,
2257
+ borderLeft: false,
2258
+ borderRight: false,
2259
+ children: [
2260
+ /* @__PURE__ */ jsxs(Text, { children: [
2261
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: info.runtime === "local" ? "Local" : info.runtime === "claude-code" ? "Claude Code" : (info.runtime ?? "docker").charAt(0).toUpperCase() + (info.runtime ?? "docker").slice(1) }),
2262
+ info.runtime === "docker" || !info.runtime ? /* @__PURE__ */ jsxs(Text, { color: info.dockerState === "building" ? "yellow" : "gray", children: [
2263
+ " ",
2264
+ "(",
2265
+ info.dockerState ?? "initializing",
2266
+ ")"
2267
+ ] }) : info.runtimeVersion && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
2268
+ " ",
2269
+ "(",
2270
+ info.runtime === "local" ? `v${info.runtimeVersion}` : info.runtimeVersion,
2271
+ ")"
2272
+ ] })
2273
+ ] }),
2274
+ /* @__PURE__ */ jsxs(Text, { children: [
2275
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Expert: " }),
2276
+ /* @__PURE__ */ jsx(Text, { color: "white", children: displayExpertName }),
2277
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: " / Skills: " }),
2278
+ /* @__PURE__ */ jsx(Text, { color: "green", children: skills })
2279
+ ] }),
2280
+ /* @__PURE__ */ jsxs(Text, { children: [
2281
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Status: " }),
2282
+ /* @__PURE__ */ jsx(
2283
+ Text,
2284
+ {
2285
+ color: info.status === "running" ? "green" : info.status === "completed" ? "cyan" : "yellow",
2286
+ children: info.status
2287
+ }
2288
+ ),
2289
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: " / Step: " }),
2290
+ /* @__PURE__ */ jsx(Text, { color: "white", children: step }),
2291
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: " / Events: " }),
2292
+ /* @__PURE__ */ jsx(Text, { color: "white", children: eventCount }),
2293
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: " / Usage: " }),
2294
+ /* @__PURE__ */ jsxs(Text, { color: "white", children: [
2295
+ usagePercent,
2296
+ "%"
2297
+ ] })
2298
+ ] }),
2299
+ /* @__PURE__ */ jsxs(Text, { children: [
2300
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Model: " }),
2301
+ /* @__PURE__ */ jsx(Text, { color: "white", children: info.model })
2302
+ ] }),
2303
+ /* @__PURE__ */ jsxs(Box, { children: [
2304
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Query: " }),
2305
+ isEditing ? /* @__PURE__ */ jsxs(Fragment, { children: [
2306
+ /* @__PURE__ */ jsx(Text, { color: "white", children: input }),
2307
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: "_" })
2308
+ ] }) : /* @__PURE__ */ jsx(Text, { color: "white", children: info.query })
2309
+ ] })
2310
+ ]
2311
+ }
2312
+ );
3106
2313
  };
3107
- var renderToolFromLog = (toolName, args, result, isSuccess) => {
3108
- const color = isSuccess === void 0 ? "yellow" : isSuccess ? "green" : "red";
3109
- switch (toolName) {
3110
- case "think":
3111
- return renderThink(args);
3112
- case "attemptCompletion":
3113
- return renderAttemptCompletion();
3114
- case "todo":
3115
- return renderTodo(args, result);
3116
- case "exec":
3117
- return renderExec(args, result, color);
3118
- case "readTextFile":
3119
- return renderReadTextFile(args, result, color);
3120
- case "writeTextFile":
3121
- return renderWriteTextFile(args, color);
3122
- case "editTextFile":
3123
- return renderEditTextFile(args, color);
3124
- case "appendTextFile":
3125
- return renderAppendTextFile(args, color);
3126
- case "listDirectory":
3127
- return renderListDirectory(args, result, color);
3128
- case "deleteFile":
3129
- return renderDeleteFile(args, color);
3130
- case "moveFile":
3131
- return renderMoveFile(args, color);
3132
- case "createDirectory":
3133
- return renderCreateDirectory(args, color);
3134
- case "getFileInfo":
3135
- return renderGetFileInfo(args, color);
3136
- case "readPdfFile":
3137
- return renderReadPdfFile(args, color);
3138
- case "readImageFile":
3139
- return renderReadImageFile(args, color);
3140
- case "testUrl":
3141
- return renderTestUrl(args, color);
3142
- default:
3143
- return renderDefault(toolName, args, color);
3144
- }
2314
+ var StreamingDisplay = ({ streaming }) => {
2315
+ const activeRuns = Object.entries(streaming.runs).filter(
2316
+ ([, run]) => run.isReasoningActive || run.isRunResultActive
2317
+ );
2318
+ if (activeRuns.length === 0) return null;
2319
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginY: 1, children: activeRuns.map(([runId, run]) => /* @__PURE__ */ jsx(StreamingRunSection, { run }, runId)) });
3145
2320
  };
3146
- var LogEntryRow = ({ entry }) => {
3147
- switch (entry.type) {
3148
- case "query":
3149
- return /* @__PURE__ */ jsx(QueryRow, { text: entry.text });
3150
- case "tool":
3151
- return /* @__PURE__ */ jsx(Box, { children: renderToolFromLog(entry.toolName, entry.args, entry.result, entry.isSuccess) });
3152
- case "delegation-started":
3153
- return /* @__PURE__ */ jsx(Box, { children: renderDelegationStarted(entry.expertName, entry.runtime, entry.version, entry.query) });
3154
- case "delegation-completed":
3155
- return /* @__PURE__ */ jsx(Box, { children: renderDelegationCompleted(entry.expertName, entry.runtime, entry.version, entry.result) });
3156
- case "completion":
3157
- return /* @__PURE__ */ jsx(CompletionRow, { text: entry.text });
3158
- case "docker-build":
3159
- return /* @__PURE__ */ jsx(Box, { children: renderDockerBuild(entry.stage, entry.service, entry.message) });
3160
- case "docker-container":
3161
- return /* @__PURE__ */ jsx(Box, { children: renderDockerContainer(entry.status, entry.service, entry.message) });
3162
- case "proxy-access":
3163
- return /* @__PURE__ */ jsx(Box, { children: renderProxyAccess(entry.action, entry.domain, entry.port, entry.reason) });
3164
- case "error":
3165
- return /* @__PURE__ */ jsx(
3166
- ErrorRow,
3167
- {
3168
- errorName: entry.errorName,
3169
- message: entry.message,
3170
- statusCode: entry.statusCode
3171
- }
3172
- );
3173
- case "completeReasoning":
3174
- return /* @__PURE__ */ jsx(Box, { children: renderCompleteReasoning(entry.text) });
3175
- case "retry":
3176
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "yellow", label: "Retry", children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: entry.reason }) });
2321
+ function StreamingRunSection({ run }) {
2322
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2323
+ run.isReasoningActive && run.reasoning !== void 0 && /* @__PURE__ */ jsx(StreamingReasoning, { expertKey: run.expertKey, text: run.reasoning }),
2324
+ run.isRunResultActive && run.runResult !== void 0 && /* @__PURE__ */ jsx(StreamingRunResult, { expertKey: run.expertKey, text: run.runResult })
2325
+ ] });
2326
+ }
2327
+ function StreamingReasoning({
2328
+ expertKey,
2329
+ text
2330
+ }) {
2331
+ const lines = text.split("\n");
2332
+ const label = `[${formatExpertKey(expertKey)}] Reasoning...`;
2333
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "cyan", label, children: /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: lines.map((line, idx) => /* @__PURE__ */ jsx(Text, { dimColor: true, wrap: "wrap", children: line }, `streaming-reasoning-${idx}`)) }) });
2334
+ }
2335
+ function StreamingRunResult({
2336
+ expertKey,
2337
+ text
2338
+ }) {
2339
+ const lines = text.split("\n");
2340
+ const label = `[${formatExpertKey(expertKey)}] Generating...`;
2341
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "green", label, children: /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: lines.map((line, idx) => /* @__PURE__ */ jsx(Text, { wrap: "wrap", children: line }, `streaming-run-result-${idx}`)) }) });
2342
+ }
2343
+ function formatExpertKey(expertKey) {
2344
+ const atIndex = expertKey.lastIndexOf("@");
2345
+ if (atIndex > 0) {
2346
+ return expertKey.substring(0, atIndex);
3177
2347
  }
3178
- };
2348
+ return expertKey;
2349
+ }
3179
2350
 
3180
2351
  // src/tui/utils/status-color.ts
3181
2352
  var getStatusColor = (status) => {
@@ -3236,6 +2407,10 @@ function VersionSelector({
3236
2407
  function WizardExpertSelector({ title, experts, onSelect }) {
3237
2408
  const { exit } = useApp();
3238
2409
  const [selectedIndex, setSelectedIndex] = useState(0);
2410
+ const expertItems = experts.map((expert) => ({
2411
+ key: expert.name,
2412
+ expert
2413
+ }));
3239
2414
  useInput((input, key) => {
3240
2415
  if (key.upArrow) {
3241
2416
  setSelectedIndex((prev) => prev > 0 ? prev - 1 : experts.length - 1);
@@ -3252,17 +2427,291 @@ function WizardExpertSelector({ title, experts, onSelect }) {
3252
2427
  });
3253
2428
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3254
2429
  /* @__PURE__ */ jsx(Text, { bold: true, children: title }),
3255
- /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginTop: 1, children: experts.map((expert, index) => /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: index === selectedIndex ? "cyan" : void 0, children: [
3256
- index === selectedIndex ? "\u276F " : " ",
3257
- expert.name,
3258
- expert.description && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
3259
- " - ",
3260
- expert.description
3261
- ] })
3262
- ] }) }, expert.name)) }),
2430
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginTop: 1, children: /* @__PURE__ */ jsx(
2431
+ SelectableList,
2432
+ {
2433
+ items: expertItems,
2434
+ selectedIndex,
2435
+ renderItem: (item, isSelected) => /* @__PURE__ */ jsxs(Text, { color: isSelected ? "cyan" : void 0, children: [
2436
+ isSelected ? "\u276F " : " ",
2437
+ item.expert.name,
2438
+ item.expert.description && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2439
+ " - ",
2440
+ item.expert.description
2441
+ ] })
2442
+ ] })
2443
+ }
2444
+ ) }),
3263
2445
  /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191\u2193 navigate \xB7 enter select \xB7 q quit" }) })
3264
2446
  ] });
3265
2447
  }
2448
+ function getActivityKey(activityOrGroup, index) {
2449
+ return activityOrGroup.id || `activity-${index}`;
2450
+ }
2451
+ var RunBox = ({ node, isRoot }) => {
2452
+ if (isRoot) {
2453
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2454
+ node.activities.map((activity, index) => /* @__PURE__ */ jsx(CheckpointActionRow, { action: activity }, getActivityKey(activity, index))),
2455
+ node.children.map((child) => /* @__PURE__ */ jsx(RunBox, { node: child, isRoot: false }, child.runId))
2456
+ ] });
2457
+ }
2458
+ const shortRunId = node.runId.slice(0, 8);
2459
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", marginLeft: 1, children: [
2460
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, bold: true, children: [
2461
+ "[",
2462
+ node.expertKey,
2463
+ "] ",
2464
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2465
+ "(",
2466
+ shortRunId,
2467
+ ")"
2468
+ ] })
2469
+ ] }),
2470
+ node.activities.map((activity, index) => /* @__PURE__ */ jsx(CheckpointActionRow, { action: activity }, getActivityKey(activity, index))),
2471
+ node.children.map((child) => /* @__PURE__ */ jsx(RunBox, { node: child, isRoot: false }, child.runId))
2472
+ ] });
2473
+ };
2474
+ function getActivityProps(activityOrGroup) {
2475
+ if (activityOrGroup.type === "parallelGroup") {
2476
+ const group = activityOrGroup;
2477
+ const firstActivity = group.activities[0];
2478
+ return {
2479
+ runId: group.runId,
2480
+ expertKey: group.expertKey,
2481
+ delegatedBy: firstActivity?.delegatedBy
2482
+ };
2483
+ }
2484
+ return activityOrGroup;
2485
+ }
2486
+ var ActivityLogPanel = ({ activities }) => {
2487
+ const rootNodes = useMemo(() => {
2488
+ const nodeMap = /* @__PURE__ */ new Map();
2489
+ const roots = [];
2490
+ const orphanChildren = /* @__PURE__ */ new Map();
2491
+ for (const activityOrGroup of activities) {
2492
+ const { runId, expertKey, delegatedBy } = getActivityProps(activityOrGroup);
2493
+ let node = nodeMap.get(runId);
2494
+ if (!node) {
2495
+ node = {
2496
+ runId,
2497
+ expertKey,
2498
+ activities: [],
2499
+ children: []
2500
+ };
2501
+ nodeMap.set(runId, node);
2502
+ const waitingChildren = orphanChildren.get(runId);
2503
+ if (waitingChildren) {
2504
+ for (const child of waitingChildren) {
2505
+ node.children.push(child);
2506
+ const rootIndex = roots.indexOf(child);
2507
+ if (rootIndex !== -1) {
2508
+ roots.splice(rootIndex, 1);
2509
+ }
2510
+ }
2511
+ orphanChildren.delete(runId);
2512
+ }
2513
+ if (delegatedBy) {
2514
+ const parentNode = nodeMap.get(delegatedBy.runId);
2515
+ if (parentNode) {
2516
+ parentNode.children.push(node);
2517
+ } else {
2518
+ const orphans = orphanChildren.get(delegatedBy.runId) ?? [];
2519
+ orphans.push(node);
2520
+ orphanChildren.set(delegatedBy.runId, orphans);
2521
+ roots.push(node);
2522
+ }
2523
+ } else {
2524
+ roots.push(node);
2525
+ }
2526
+ }
2527
+ node.activities.push(activityOrGroup);
2528
+ }
2529
+ return roots;
2530
+ }, [activities]);
2531
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: rootNodes.map((node) => /* @__PURE__ */ jsx(RunBox, { node, isRoot: true }, node.runId)) });
2532
+ };
2533
+ var ContinueInputPanel = ({
2534
+ isActive,
2535
+ runStatus,
2536
+ onSubmit
2537
+ }) => {
2538
+ const { input, handleInput } = useTextInput({
2539
+ onSubmit: (newQuery) => {
2540
+ if (isActive && newQuery.trim()) {
2541
+ onSubmit(newQuery.trim());
2542
+ }
2543
+ }
2544
+ });
2545
+ useInput(handleInput, { isActive });
2546
+ if (runStatus === "running") {
2547
+ return null;
2548
+ }
2549
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", children: [
2550
+ /* @__PURE__ */ jsxs(Text, { children: [
2551
+ /* @__PURE__ */ jsx(Text, { color: runStatus === "completed" ? "green" : "yellow", bold: true, children: runStatus === "completed" ? "Completed" : "Stopped" }),
2552
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: " - Enter a follow-up query or wait to exit" })
2553
+ ] }),
2554
+ /* @__PURE__ */ jsxs(Box, { children: [
2555
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Continue: " }),
2556
+ /* @__PURE__ */ jsx(Text, { children: input }),
2557
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: "_" })
2558
+ ] })
2559
+ ] });
2560
+ };
2561
+ var StatusPanel = ({
2562
+ runtimeInfo,
2563
+ eventCount,
2564
+ runStatus
2565
+ }) => {
2566
+ if (runStatus !== "running") {
2567
+ return null;
2568
+ }
2569
+ return /* @__PURE__ */ jsx(RunSetting, { info: runtimeInfo, eventCount, isEditing: false });
2570
+ };
2571
+ var useExecutionState = (options) => {
2572
+ const { expertKey, query, config, continueTimeoutMs, historicalEvents, onReady, onComplete } = options;
2573
+ const { exit } = useApp();
2574
+ const runState = useRun();
2575
+ const { runtimeInfo, handleEvent, setQuery } = useRuntimeInfo({
2576
+ initialExpertName: expertKey,
2577
+ initialConfig: config
2578
+ });
2579
+ const [runStatus, setRunStatus] = useState("running");
2580
+ const [isAcceptingContinue, setIsAcceptingContinue] = useState(false);
2581
+ const timeoutRef = useRef(null);
2582
+ const clearTimeoutIfExists = useCallback(() => {
2583
+ if (timeoutRef.current) {
2584
+ clearTimeout(timeoutRef.current);
2585
+ timeoutRef.current = null;
2586
+ }
2587
+ }, []);
2588
+ const startExitTimeout = useCallback(() => {
2589
+ clearTimeoutIfExists();
2590
+ timeoutRef.current = setTimeout(() => {
2591
+ onComplete({ nextQuery: null });
2592
+ exit();
2593
+ }, continueTimeoutMs);
2594
+ }, [clearTimeoutIfExists, continueTimeoutMs, onComplete, exit]);
2595
+ useEffect(() => {
2596
+ setQuery(query);
2597
+ }, [query, setQuery]);
2598
+ useEffect(() => {
2599
+ if (historicalEvents && historicalEvents.length > 0) {
2600
+ runState.appendHistoricalEvents(historicalEvents);
2601
+ }
2602
+ }, [historicalEvents, runState.appendHistoricalEvents]);
2603
+ useEffect(() => {
2604
+ onReady((event) => {
2605
+ runState.addEvent(event);
2606
+ const result = handleEvent(event);
2607
+ if (result?.completed) {
2608
+ setRunStatus("completed");
2609
+ setIsAcceptingContinue(true);
2610
+ startExitTimeout();
2611
+ } else if (result?.stopped) {
2612
+ setRunStatus("stopped");
2613
+ setIsAcceptingContinue(true);
2614
+ startExitTimeout();
2615
+ }
2616
+ });
2617
+ }, [onReady, runState.addEvent, handleEvent, startExitTimeout]);
2618
+ useEffect(() => {
2619
+ return () => {
2620
+ clearTimeoutIfExists();
2621
+ };
2622
+ }, [clearTimeoutIfExists]);
2623
+ const handleContinueSubmit = useCallback(
2624
+ (newQuery) => {
2625
+ if (isAcceptingContinue && newQuery.trim()) {
2626
+ clearTimeoutIfExists();
2627
+ onComplete({ nextQuery: newQuery.trim() });
2628
+ exit();
2629
+ }
2630
+ },
2631
+ [isAcceptingContinue, clearTimeoutIfExists, onComplete, exit]
2632
+ );
2633
+ return {
2634
+ activities: runState.activities,
2635
+ streaming: runState.streaming,
2636
+ eventCount: runState.eventCount,
2637
+ runtimeInfo,
2638
+ runStatus,
2639
+ isAcceptingContinue,
2640
+ handleContinueSubmit,
2641
+ clearTimeout: clearTimeoutIfExists
2642
+ };
2643
+ };
2644
+ var ExecutionApp = (props) => {
2645
+ const { expertKey, query, config, continueTimeoutMs, historicalEvents, onReady, onComplete } = props;
2646
+ const { exit } = useApp();
2647
+ const state = useExecutionState({
2648
+ expertKey,
2649
+ query,
2650
+ config,
2651
+ continueTimeoutMs,
2652
+ historicalEvents,
2653
+ onReady,
2654
+ onComplete
2655
+ });
2656
+ useInput((input, key) => {
2657
+ if (key.ctrl && input === "c") {
2658
+ state.clearTimeout();
2659
+ exit();
2660
+ }
2661
+ });
2662
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2663
+ /* @__PURE__ */ jsx(ActivityLogPanel, { activities: state.activities }),
2664
+ /* @__PURE__ */ jsx(StreamingDisplay, { streaming: state.streaming }),
2665
+ /* @__PURE__ */ jsx(
2666
+ StatusPanel,
2667
+ {
2668
+ runtimeInfo: state.runtimeInfo,
2669
+ eventCount: state.eventCount,
2670
+ runStatus: state.runStatus
2671
+ }
2672
+ ),
2673
+ /* @__PURE__ */ jsx(
2674
+ ContinueInputPanel,
2675
+ {
2676
+ isActive: state.isAcceptingContinue,
2677
+ runStatus: state.runStatus,
2678
+ onSubmit: state.handleContinueSubmit
2679
+ }
2680
+ )
2681
+ ] });
2682
+ };
2683
+ function renderExecution(params) {
2684
+ const eventQueue = new EventQueue();
2685
+ const result = new Promise((resolve2, reject) => {
2686
+ let resolved = false;
2687
+ const { waitUntilExit } = render(
2688
+ /* @__PURE__ */ jsx(
2689
+ ExecutionApp,
2690
+ {
2691
+ ...params,
2692
+ onReady: (handler) => {
2693
+ eventQueue.setHandler(handler);
2694
+ },
2695
+ onComplete: (result2) => {
2696
+ resolved = true;
2697
+ resolve2(result2);
2698
+ }
2699
+ }
2700
+ )
2701
+ );
2702
+ waitUntilExit().then(() => {
2703
+ if (!resolved) {
2704
+ reject(new Error("Execution cancelled"));
2705
+ }
2706
+ }).catch(reject);
2707
+ });
2708
+ return {
2709
+ result,
2710
+ eventListener: (event) => {
2711
+ eventQueue.emit(event);
2712
+ }
2713
+ };
2714
+ }
3266
2715
  function PublishApp({ experts, onSelect }) {
3267
2716
  const { exit } = useApp();
3268
2717
  const handleSelect = (name) => {
@@ -3297,93 +2746,210 @@ async function renderPublish(options) {
3297
2746
  });
3298
2747
  });
3299
2748
  }
3300
- var App = (props) => {
3301
- const { eventStore, runtimeInfo, inputState, inputAreaContextValue } = useAppState(props);
3302
- const isBrowsing = inputState.type === "browsingHistory" || inputState.type === "browsingExperts" || inputState.type === "browsingCheckpoints" || inputState.type === "browsingEvents";
3303
- const isEditing = inputState.type === "enteringQuery";
3304
- const showRunSetting = isEditing || inputState.type === "running";
3305
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3306
- /* @__PURE__ */ jsx(Static, { items: eventStore.logs, style: { flexDirection: "column", gap: 1, paddingBottom: 1 }, children: (entry) => /* @__PURE__ */ jsx(LogEntryRow, { entry }, entry.id) }),
3307
- eventStore.streamingReasoning && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
3308
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u25CF Reasoning" }),
3309
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
3310
- "\u2514 ",
3311
- eventStore.streamingReasoning
3312
- ] })
3313
- ] }),
3314
- eventStore.streamingText && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3315
- /* @__PURE__ */ jsx(Text, { color: "green", children: "\u25CF Run Results" }),
3316
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
3317
- "\u2514 ",
3318
- eventStore.streamingText
3319
- ] })
3320
- ] }),
3321
- /* @__PURE__ */ jsx(InputAreaProvider, { value: inputAreaContextValue, children: isBrowsing && /* @__PURE__ */ jsx(
2749
+ var selectionReducer = (_state, action) => {
2750
+ switch (action.type) {
2751
+ case "BROWSE_HISTORY":
2752
+ return { type: "browsingHistory", jobs: action.jobs };
2753
+ case "BROWSE_EXPERTS":
2754
+ return { type: "browsingExperts", experts: action.experts };
2755
+ case "SELECT_EXPERT":
2756
+ return { type: "enteringQuery", expertKey: action.expertKey };
2757
+ case "SELECT_JOB":
2758
+ return { type: "browsingCheckpoints", job: action.job, checkpoints: action.checkpoints };
2759
+ case "GO_BACK_FROM_CHECKPOINTS":
2760
+ return { type: "browsingHistory", jobs: action.jobs };
2761
+ default:
2762
+ return assertNever(action);
2763
+ }
2764
+ };
2765
+ var SelectionApp = (props) => {
2766
+ const {
2767
+ showHistory,
2768
+ initialExpertKey,
2769
+ initialQuery,
2770
+ initialCheckpoint,
2771
+ configuredExperts,
2772
+ recentExperts,
2773
+ historyJobs,
2774
+ onLoadCheckpoints,
2775
+ onComplete
2776
+ } = props;
2777
+ const { exit } = useApp();
2778
+ const allExperts = useMemo(() => {
2779
+ const configured = configuredExperts.map((e) => ({ ...e, source: "configured" }));
2780
+ const recent = recentExperts.filter((e) => !configured.some((c) => c.key === e.key)).map((e) => ({ ...e, source: "recent" }));
2781
+ return [...configured, ...recent];
2782
+ }, [configuredExperts, recentExperts]);
2783
+ const getInitialState = () => {
2784
+ if (initialExpertKey && !initialQuery) {
2785
+ return { type: "enteringQuery", expertKey: initialExpertKey };
2786
+ }
2787
+ if (showHistory && historyJobs.length > 0) {
2788
+ return { type: "browsingHistory", jobs: historyJobs };
2789
+ }
2790
+ return { type: "browsingExperts", experts: allExperts };
2791
+ };
2792
+ const [state, dispatch] = useReducer(selectionReducer, void 0, getInitialState);
2793
+ const [selectedCheckpoint, setSelectedCheckpoint] = useState(
2794
+ initialCheckpoint
2795
+ );
2796
+ useEffect(() => {
2797
+ if (initialExpertKey && initialQuery) {
2798
+ onComplete({
2799
+ expertKey: initialExpertKey,
2800
+ query: initialQuery,
2801
+ checkpoint: initialCheckpoint
2802
+ });
2803
+ exit();
2804
+ }
2805
+ }, [initialExpertKey, initialQuery, initialCheckpoint, onComplete, exit]);
2806
+ const { input: queryInput, handleInput: handleQueryInput } = useTextInput({
2807
+ onSubmit: (query) => {
2808
+ if (state.type === "enteringQuery" && query.trim()) {
2809
+ onComplete({
2810
+ expertKey: state.expertKey,
2811
+ query: query.trim(),
2812
+ checkpoint: selectedCheckpoint
2813
+ });
2814
+ exit();
2815
+ }
2816
+ }
2817
+ });
2818
+ useInput(handleQueryInput, { isActive: state.type === "enteringQuery" });
2819
+ const handleExpertSelect = useCallback((expertKey) => {
2820
+ dispatch({ type: "SELECT_EXPERT", expertKey });
2821
+ }, []);
2822
+ const handleJobSelect = useCallback(
2823
+ async (job) => {
2824
+ try {
2825
+ const checkpoints = await onLoadCheckpoints(job);
2826
+ dispatch({ type: "SELECT_JOB", job, checkpoints });
2827
+ } catch {
2828
+ dispatch({ type: "SELECT_JOB", job, checkpoints: [] });
2829
+ }
2830
+ },
2831
+ [onLoadCheckpoints]
2832
+ );
2833
+ const handleJobResume = useCallback(
2834
+ async (job) => {
2835
+ try {
2836
+ const checkpoints = await onLoadCheckpoints(job);
2837
+ const latestCheckpoint = checkpoints[0];
2838
+ if (latestCheckpoint) {
2839
+ setSelectedCheckpoint(latestCheckpoint);
2840
+ }
2841
+ dispatch({ type: "SELECT_EXPERT", expertKey: job.expertKey });
2842
+ } catch {
2843
+ dispatch({ type: "SELECT_EXPERT", expertKey: job.expertKey });
2844
+ }
2845
+ },
2846
+ [onLoadCheckpoints]
2847
+ );
2848
+ const handleCheckpointResume = useCallback(
2849
+ (checkpoint) => {
2850
+ setSelectedCheckpoint(checkpoint);
2851
+ if (state.type === "browsingCheckpoints") {
2852
+ dispatch({ type: "SELECT_EXPERT", expertKey: state.job.expertKey });
2853
+ }
2854
+ },
2855
+ [state]
2856
+ );
2857
+ const handleBack = useCallback(() => {
2858
+ if (state.type === "browsingCheckpoints") {
2859
+ dispatch({ type: "GO_BACK_FROM_CHECKPOINTS", jobs: historyJobs });
2860
+ }
2861
+ }, [state, historyJobs]);
2862
+ const handleSwitchToExperts = useCallback(() => {
2863
+ dispatch({ type: "BROWSE_EXPERTS", experts: allExperts });
2864
+ }, [allExperts]);
2865
+ const handleSwitchToHistory = useCallback(() => {
2866
+ dispatch({ type: "BROWSE_HISTORY", jobs: historyJobs });
2867
+ }, [historyJobs]);
2868
+ useInput((input, key) => {
2869
+ if (key.ctrl && input === "c") {
2870
+ exit();
2871
+ }
2872
+ });
2873
+ const contextValue = useMemo(
2874
+ () => ({
2875
+ onExpertSelect: handleExpertSelect,
2876
+ onQuerySubmit: () => {
2877
+ },
2878
+ // Not used in browser
2879
+ onJobSelect: handleJobSelect,
2880
+ onJobResume: handleJobResume,
2881
+ onCheckpointSelect: () => {
2882
+ },
2883
+ // Not used in selection (no event browsing)
2884
+ onCheckpointResume: handleCheckpointResume,
2885
+ onEventSelect: () => {
2886
+ },
2887
+ // Not used in selection
2888
+ onBack: handleBack,
2889
+ onSwitchToExperts: handleSwitchToExperts,
2890
+ onSwitchToHistory: handleSwitchToHistory
2891
+ }),
2892
+ [
2893
+ handleExpertSelect,
2894
+ handleJobSelect,
2895
+ handleJobResume,
2896
+ handleCheckpointResume,
2897
+ handleBack,
2898
+ handleSwitchToExperts,
2899
+ handleSwitchToHistory
2900
+ ]
2901
+ );
2902
+ if (initialExpertKey && initialQuery) {
2903
+ return null;
2904
+ }
2905
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2906
+ /* @__PURE__ */ jsx(InputAreaProvider, { value: contextValue, children: (state.type === "browsingHistory" || state.type === "browsingExperts" || state.type === "browsingCheckpoints") && /* @__PURE__ */ jsx(
3322
2907
  BrowserRouter,
3323
2908
  {
3324
- inputState
2909
+ inputState: state,
2910
+ showEventsHint: false
3325
2911
  }
3326
2912
  ) }),
3327
- showRunSetting && /* @__PURE__ */ jsx(
3328
- RunSetting,
3329
- {
3330
- info: runtimeInfo,
3331
- eventCount: eventStore.eventCount,
3332
- isEditing,
3333
- expertName: isEditing ? inputState.expertName : void 0,
3334
- onQuerySubmit: isEditing ? inputAreaContextValue.onQuerySubmit : void 0
3335
- }
3336
- )
2913
+ state.type === "enteringQuery" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", children: [
2914
+ /* @__PURE__ */ jsxs(Text, { children: [
2915
+ /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "Expert:" }),
2916
+ " ",
2917
+ /* @__PURE__ */ jsx(Text, { children: state.expertKey }),
2918
+ selectedCheckpoint && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
2919
+ " (resuming from step ",
2920
+ selectedCheckpoint.stepNumber,
2921
+ ")"
2922
+ ] })
2923
+ ] }),
2924
+ /* @__PURE__ */ jsxs(Box, { children: [
2925
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Query: " }),
2926
+ /* @__PURE__ */ jsx(Text, { children: queryInput }),
2927
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: "_" })
2928
+ ] }),
2929
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Press Enter to start" })
2930
+ ] })
3337
2931
  ] });
3338
2932
  };
3339
- var createEventEmitter = () => {
3340
- const eventQueue = new EventQueue();
3341
- return {
3342
- setHandler: (fn) => eventQueue.setHandler(fn),
3343
- emit: (event) => eventQueue.emit(event)
3344
- };
3345
- };
3346
- async function renderStart(options) {
2933
+ async function renderSelection(params) {
3347
2934
  return new Promise((resolve2, reject) => {
3348
- const emitter = createEventEmitter();
3349
2935
  let resolved = false;
3350
2936
  const { waitUntilExit } = render(
3351
2937
  /* @__PURE__ */ jsx(
3352
- App,
2938
+ SelectionApp,
3353
2939
  {
3354
- needsQueryInput: options.needsQueryInput,
3355
- showHistory: options.showHistory,
3356
- initialExpertName: options.initialExpertName,
3357
- initialQuery: options.initialQuery,
3358
- initialConfig: options.initialConfig,
3359
- configuredExperts: options.configuredExperts,
3360
- recentExperts: options.recentExperts,
3361
- historyJobs: options.historyJobs,
3362
- onComplete: (expertKey, query) => {
2940
+ ...params,
2941
+ onComplete: (result) => {
3363
2942
  resolved = true;
3364
- resolve2({
3365
- expertKey,
3366
- query,
3367
- eventListener: emitter.emit
3368
- });
3369
- },
3370
- onContinue: options.onContinue,
3371
- onResumeFromCheckpoint: options.onResumeFromCheckpoint,
3372
- onLoadCheckpoints: options.onLoadCheckpoints,
3373
- onLoadEvents: options.onLoadEvents,
3374
- onLoadHistoricalEvents: options.onLoadHistoricalEvents,
3375
- onReady: emitter.setHandler
2943
+ resolve2(result);
2944
+ }
3376
2945
  }
3377
2946
  )
3378
2947
  );
3379
2948
  waitUntilExit().then(() => {
3380
2949
  if (!resolved) {
3381
- reject(new Error("TUI exited without completing selection"));
2950
+ reject(new Error("Selection cancelled"));
3382
2951
  }
3383
- }).catch((error) => {
3384
- options.onError?.(error);
3385
- reject(error);
3386
- });
2952
+ }).catch(reject);
3387
2953
  });
3388
2954
  }
3389
2955
  function getAvailableStatusTransitions(currentStatus) {
@@ -3509,30 +3075,15 @@ function ConfirmStep({
3509
3075
  }
3510
3076
  function StatusApp({ experts, onFetchVersions, onComplete, onCancel }) {
3511
3077
  const { exit } = useApp();
3512
- const [step, setStep] = useState({ type: "selectExpert" });
3513
- const handleExpertSelect = async (expertName) => {
3514
- setStep({ type: "loadingVersions", expertName });
3515
- try {
3516
- const versions = await onFetchVersions(expertName);
3517
- if (versions.length === 0) {
3518
- setStep({ type: "error", message: `No versions found for ${expertName}` });
3519
- return;
3520
- }
3521
- setStep({ type: "selectVersion", expertName, versions });
3522
- } catch (error) {
3523
- setStep({
3524
- type: "error",
3525
- message: error instanceof Error ? error.message : "Failed to fetch versions"
3526
- });
3527
- }
3528
- };
3529
- const handleVersionSelect = (version) => {
3530
- setStep({
3078
+ const { step, setStep, handlers, renderBaseStep, isBaseStep: isBaseStep2 } = useExpertVersionWizard({
3079
+ experts,
3080
+ onFetchVersions,
3081
+ customStepFromVersion: (version) => ({
3531
3082
  type: "selectStatus",
3532
3083
  expertKey: version.key,
3533
3084
  currentStatus: version.status
3534
- });
3535
- };
3085
+ })
3086
+ });
3536
3087
  const handleStatusSelect = (status) => {
3537
3088
  if (step.type === "selectStatus") {
3538
3089
  setStep({
@@ -3552,81 +3103,54 @@ function StatusApp({ experts, onFetchVersions, onComplete, onCancel }) {
3552
3103
  exit();
3553
3104
  }
3554
3105
  };
3555
- const handleBack = () => {
3556
- switch (step.type) {
3557
- case "selectVersion":
3558
- setStep({ type: "selectExpert" });
3559
- break;
3106
+ const handleCustomBack = (currentStep) => {
3107
+ switch (currentStep.type) {
3560
3108
  case "selectStatus":
3561
3109
  setStep({ type: "selectExpert" });
3562
3110
  break;
3563
3111
  case "confirm":
3564
3112
  setStep({
3565
3113
  type: "selectStatus",
3566
- expertKey: step.expertKey,
3567
- currentStatus: step.currentStatus
3114
+ expertKey: currentStep.expertKey,
3115
+ currentStatus: currentStep.currentStatus
3568
3116
  });
3569
3117
  break;
3570
- case "error":
3571
- setStep({ type: "selectExpert" });
3572
- break;
3573
3118
  default:
3574
3119
  onCancel();
3575
3120
  exit();
3576
3121
  }
3577
3122
  };
3578
- switch (step.type) {
3579
- case "selectExpert":
3580
- return /* @__PURE__ */ jsx(
3581
- WizardExpertSelector,
3582
- {
3583
- title: "Select an Expert to change status:",
3584
- experts,
3585
- onSelect: handleExpertSelect
3586
- }
3587
- );
3588
- case "loadingVersions":
3589
- return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { children: [
3590
- "Loading versions for ",
3591
- step.expertName,
3592
- "..."
3593
- ] }) });
3594
- case "selectVersion":
3595
- return /* @__PURE__ */ jsx(
3596
- VersionSelector,
3597
- {
3598
- expertName: step.expertName,
3599
- versions: step.versions,
3600
- onSelect: handleVersionSelect,
3601
- onBack: handleBack
3602
- }
3603
- );
3604
- case "selectStatus":
3605
- return /* @__PURE__ */ jsx(
3606
- StatusSelector,
3607
- {
3608
- expertKey: step.expertKey,
3609
- currentStatus: step.currentStatus,
3610
- onSelect: handleStatusSelect,
3611
- onBack: handleBack
3612
- }
3613
- );
3614
- case "confirm":
3615
- return /* @__PURE__ */ jsx(
3616
- ConfirmStep,
3617
- {
3618
- expertKey: step.expertKey,
3619
- status: step.status,
3620
- currentStatus: step.currentStatus,
3621
- onConfirm: handleConfirm,
3622
- onBack: handleBack
3623
- }
3624
- );
3625
- case "error":
3626
- return /* @__PURE__ */ jsx(ErrorStep, { message: step.message, onBack: handleBack });
3627
- default:
3628
- return null;
3123
+ const baseStepRender = renderBaseStep({ title: "Select an Expert to change status:" });
3124
+ if (baseStepRender) {
3125
+ return baseStepRender;
3126
+ }
3127
+ if (!isBaseStep2) {
3128
+ const customStep = step;
3129
+ switch (customStep.type) {
3130
+ case "selectStatus":
3131
+ return /* @__PURE__ */ jsx(
3132
+ StatusSelector,
3133
+ {
3134
+ expertKey: customStep.expertKey,
3135
+ currentStatus: customStep.currentStatus,
3136
+ onSelect: handleStatusSelect,
3137
+ onBack: () => handlers.handleBack(handleCustomBack)
3138
+ }
3139
+ );
3140
+ case "confirm":
3141
+ return /* @__PURE__ */ jsx(
3142
+ ConfirmStep,
3143
+ {
3144
+ expertKey: customStep.expertKey,
3145
+ status: customStep.status,
3146
+ currentStatus: customStep.currentStatus,
3147
+ onConfirm: handleConfirm,
3148
+ onBack: () => handlers.handleBack(handleCustomBack)
3149
+ }
3150
+ );
3151
+ }
3629
3152
  }
3153
+ return null;
3630
3154
  }
3631
3155
  async function renderStatus(options) {
3632
3156
  return new Promise((resolve2) => {
@@ -3776,30 +3300,15 @@ function ConfirmStep2({
3776
3300
  }
3777
3301
  function TagApp({ experts, onFetchVersions, onComplete, onCancel }) {
3778
3302
  const { exit } = useApp();
3779
- const [step, setStep] = useState({ type: "selectExpert" });
3780
- const handleExpertSelect = async (expertName) => {
3781
- setStep({ type: "loadingVersions", expertName });
3782
- try {
3783
- const versions = await onFetchVersions(expertName);
3784
- if (versions.length === 0) {
3785
- setStep({ type: "error", message: `No versions found for ${expertName}` });
3786
- return;
3787
- }
3788
- setStep({ type: "selectVersion", expertName, versions });
3789
- } catch (error) {
3790
- setStep({
3791
- type: "error",
3792
- message: error instanceof Error ? error.message : "Failed to fetch versions"
3793
- });
3794
- }
3795
- };
3796
- const handleVersionSelect = (version) => {
3797
- setStep({
3303
+ const { step, setStep, handlers, renderBaseStep, isBaseStep: isBaseStep2 } = useExpertVersionWizard({
3304
+ experts,
3305
+ onFetchVersions,
3306
+ customStepFromVersion: (version) => ({
3798
3307
  type: "inputTags",
3799
3308
  expertKey: version.key,
3800
3309
  currentTags: version.tags
3801
- });
3802
- };
3310
+ })
3311
+ });
3803
3312
  const handleTagsSubmit = (tags) => {
3804
3313
  if (step.type === "inputTags") {
3805
3314
  setStep({
@@ -3819,81 +3328,54 @@ function TagApp({ experts, onFetchVersions, onComplete, onCancel }) {
3819
3328
  exit();
3820
3329
  }
3821
3330
  };
3822
- const handleBack = () => {
3823
- switch (step.type) {
3824
- case "selectVersion":
3825
- setStep({ type: "selectExpert" });
3826
- break;
3331
+ const handleCustomBack = (currentStep) => {
3332
+ switch (currentStep.type) {
3827
3333
  case "inputTags":
3828
3334
  setStep({ type: "selectExpert" });
3829
3335
  break;
3830
3336
  case "confirm":
3831
3337
  setStep({
3832
3338
  type: "inputTags",
3833
- expertKey: step.expertKey,
3834
- currentTags: step.currentTags
3339
+ expertKey: currentStep.expertKey,
3340
+ currentTags: currentStep.currentTags
3835
3341
  });
3836
3342
  break;
3837
- case "error":
3838
- setStep({ type: "selectExpert" });
3839
- break;
3840
3343
  default:
3841
3344
  onCancel();
3842
3345
  exit();
3843
3346
  }
3844
3347
  };
3845
- switch (step.type) {
3846
- case "selectExpert":
3847
- return /* @__PURE__ */ jsx(
3848
- WizardExpertSelector,
3849
- {
3850
- title: "Select an Expert to tag:",
3851
- experts,
3852
- onSelect: handleExpertSelect
3853
- }
3854
- );
3855
- case "loadingVersions":
3856
- return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { children: [
3857
- "Loading versions for ",
3858
- step.expertName,
3859
- "..."
3860
- ] }) });
3861
- case "selectVersion":
3862
- return /* @__PURE__ */ jsx(
3863
- VersionSelector,
3864
- {
3865
- expertName: step.expertName,
3866
- versions: step.versions,
3867
- onSelect: handleVersionSelect,
3868
- onBack: handleBack
3869
- }
3870
- );
3871
- case "inputTags":
3872
- return /* @__PURE__ */ jsx(
3873
- TagInput,
3874
- {
3875
- expertKey: step.expertKey,
3876
- currentTags: step.currentTags,
3877
- onSubmit: handleTagsSubmit,
3878
- onBack: handleBack
3879
- }
3880
- );
3881
- case "confirm":
3882
- return /* @__PURE__ */ jsx(
3883
- ConfirmStep2,
3884
- {
3885
- expertKey: step.expertKey,
3886
- tags: step.tags,
3887
- currentTags: step.currentTags,
3888
- onConfirm: handleConfirm,
3889
- onBack: handleBack
3890
- }
3891
- );
3892
- case "error":
3893
- return /* @__PURE__ */ jsx(ErrorStep, { message: step.message, onBack: handleBack });
3894
- default:
3895
- return null;
3348
+ const baseStepRender = renderBaseStep({ title: "Select an Expert to tag:" });
3349
+ if (baseStepRender) {
3350
+ return baseStepRender;
3351
+ }
3352
+ if (!isBaseStep2) {
3353
+ const customStep = step;
3354
+ switch (customStep.type) {
3355
+ case "inputTags":
3356
+ return /* @__PURE__ */ jsx(
3357
+ TagInput,
3358
+ {
3359
+ expertKey: customStep.expertKey,
3360
+ currentTags: customStep.currentTags,
3361
+ onSubmit: handleTagsSubmit,
3362
+ onBack: () => handlers.handleBack(handleCustomBack)
3363
+ }
3364
+ );
3365
+ case "confirm":
3366
+ return /* @__PURE__ */ jsx(
3367
+ ConfirmStep2,
3368
+ {
3369
+ expertKey: customStep.expertKey,
3370
+ tags: customStep.tags,
3371
+ currentTags: customStep.currentTags,
3372
+ onConfirm: handleConfirm,
3373
+ onBack: () => handlers.handleBack(handleCustomBack)
3374
+ }
3375
+ );
3376
+ }
3896
3377
  }
3378
+ return null;
3897
3379
  }
3898
3380
  async function renderTag(options) {
3899
3381
  return new Promise((resolve2) => {
@@ -3984,94 +3466,50 @@ function UnpublishApp({
3984
3466
  onCancel
3985
3467
  }) {
3986
3468
  const { exit } = useApp();
3987
- const [step, setStep] = useState({ type: "selectExpert" });
3988
- const handleExpertSelect = async (expertName) => {
3989
- setStep({ type: "loadingVersions", expertName });
3990
- try {
3991
- const versions = await onFetchVersions(expertName);
3992
- if (versions.length === 0) {
3993
- setStep({ type: "error", message: `No versions found for ${expertName}` });
3994
- return;
3995
- }
3996
- setStep({ type: "selectVersion", expertName, versions });
3997
- } catch (error) {
3998
- setStep({
3999
- type: "error",
4000
- message: error instanceof Error ? error.message : "Failed to fetch versions"
4001
- });
4002
- }
4003
- };
4004
- const handleVersionSelect = (version) => {
4005
- setStep({
3469
+ const { step, setStep, handlers, renderBaseStep, isBaseStep: isBaseStep2 } = useExpertVersionWizard({
3470
+ experts,
3471
+ onFetchVersions,
3472
+ customStepFromVersion: (version) => ({
4006
3473
  type: "confirm",
4007
3474
  expertKey: version.key,
4008
3475
  version: version.version
4009
- });
4010
- };
3476
+ })
3477
+ });
4011
3478
  const handleConfirm = () => {
4012
3479
  if (step.type === "confirm") {
4013
3480
  onComplete({ expertKey: step.expertKey });
4014
3481
  exit();
4015
3482
  }
4016
3483
  };
4017
- const handleBack = () => {
4018
- switch (step.type) {
4019
- case "selectVersion":
4020
- setStep({ type: "selectExpert" });
4021
- break;
3484
+ const handleCustomBack = (currentStep) => {
3485
+ switch (currentStep.type) {
4022
3486
  case "confirm":
4023
3487
  setStep({ type: "selectExpert" });
4024
3488
  break;
4025
- case "error":
4026
- setStep({ type: "selectExpert" });
4027
- break;
4028
3489
  default:
4029
3490
  onCancel();
4030
3491
  exit();
4031
3492
  }
4032
3493
  };
4033
- switch (step.type) {
4034
- case "selectExpert":
4035
- return /* @__PURE__ */ jsx(
4036
- WizardExpertSelector,
4037
- {
4038
- title: "Select an Expert to unpublish:",
4039
- experts,
4040
- onSelect: handleExpertSelect
4041
- }
4042
- );
4043
- case "loadingVersions":
4044
- return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { children: [
4045
- "Loading versions for ",
4046
- step.expertName,
4047
- "..."
4048
- ] }) });
4049
- case "selectVersion":
4050
- return /* @__PURE__ */ jsx(
4051
- VersionSelector,
4052
- {
4053
- expertName: step.expertName,
4054
- versions: step.versions,
4055
- onSelect: handleVersionSelect,
4056
- onBack: handleBack,
4057
- title: `Select a version of ${step.expertName} to unpublish:`
4058
- }
4059
- );
4060
- case "confirm":
4061
- return /* @__PURE__ */ jsx(
4062
- ConfirmStep3,
4063
- {
4064
- expertKey: step.expertKey,
4065
- version: step.version,
4066
- onConfirm: handleConfirm,
4067
- onBack: handleBack
4068
- }
4069
- );
4070
- case "error":
4071
- return /* @__PURE__ */ jsx(ErrorStep, { message: step.message, onBack: handleBack });
4072
- default:
4073
- return null;
3494
+ const baseStepRender = renderBaseStep({
3495
+ title: "Select an Expert to unpublish:",
3496
+ versionSelectorTitle: `Select a version to unpublish:`
3497
+ });
3498
+ if (baseStepRender) {
3499
+ return baseStepRender;
3500
+ }
3501
+ if (!isBaseStep2 && step.type === "confirm") {
3502
+ return /* @__PURE__ */ jsx(
3503
+ ConfirmStep3,
3504
+ {
3505
+ expertKey: step.expertKey,
3506
+ version: step.version,
3507
+ onConfirm: handleConfirm,
3508
+ onBack: () => handlers.handleBack(handleCustomBack)
3509
+ }
3510
+ );
4074
3511
  }
3512
+ return null;
4075
3513
  }
4076
3514
  async function renderUnpublish(options) {
4077
3515
  return new Promise((resolve2) => {
@@ -4337,6 +3775,9 @@ function getMostRecentRun() {
4337
3775
  }
4338
3776
  return runs[0];
4339
3777
  }
3778
+ function getRunsByJobId(jobId) {
3779
+ return getAllRuns2().filter((r) => r.jobId === jobId);
3780
+ }
4340
3781
  function getCheckpointsByJobId2(jobId) {
4341
3782
  return getCheckpointsByJobId(jobId);
4342
3783
  }
@@ -4379,18 +3820,18 @@ function getCheckpointsWithDetails(jobId) {
4379
3820
  contextWindowUsage: cp.contextWindowUsage ?? 0
4380
3821
  })).sort((a, b) => b.stepNumber - a.stepNumber);
4381
3822
  }
4382
- function getEventsWithDetails(jobId, runId, stepNumber) {
4383
- return getEventsByRun(jobId, runId).map((e) => ({
4384
- id: `${e.timestamp}-${e.stepNumber}-${e.type}`,
4385
- runId,
4386
- stepNumber: e.stepNumber,
4387
- type: e.type,
4388
- timestamp: e.timestamp
4389
- })).filter((event) => stepNumber === void 0 || event.stepNumber === stepNumber).sort((a, b) => a.timestamp - b.timestamp);
4390
- }
4391
3823
  function getEventContents2(jobId, runId, maxStepNumber) {
4392
3824
  return getEventContents(jobId, runId, maxStepNumber);
4393
3825
  }
3826
+ function getAllEventContentsForJob(jobId, maxStepNumber) {
3827
+ const runs = getRunsByJobId(jobId);
3828
+ const allEvents = [];
3829
+ for (const run of runs) {
3830
+ const events = getEventContents(jobId, run.runId, maxStepNumber);
3831
+ allEvents.push(...events);
3832
+ }
3833
+ return allEvents.sort((a, b) => a.timestamp - b.timestamp);
3834
+ }
4394
3835
 
4395
3836
  // src/lib/context.ts
4396
3837
  var defaultProvider = "anthropic";
@@ -4402,11 +3843,11 @@ async function resolveRunContext(input) {
4402
3843
  if (!input.continueJob) {
4403
3844
  throw new Error("--resume-from requires --continue-job");
4404
3845
  }
4405
- checkpoint = await getCheckpointById(input.continueJob, input.resumeFrom);
3846
+ checkpoint = getCheckpointById(input.continueJob, input.resumeFrom);
4406
3847
  } else if (input.continueJob) {
4407
- checkpoint = await getMostRecentCheckpoint(input.continueJob);
3848
+ checkpoint = getMostRecentCheckpoint(input.continueJob);
4408
3849
  } else if (input.continue) {
4409
- checkpoint = await getMostRecentCheckpoint();
3850
+ checkpoint = getMostRecentCheckpoint();
4410
3851
  }
4411
3852
  if ((input.continue || input.continueJob || input.resumeFrom) && !checkpoint) {
4412
3853
  throw new Error("No checkpoint found");
@@ -6357,9 +5798,6 @@ var ClaudeCodeAdapter = class extends BaseAdapter {
6357
5798
  const text = content.text?.trim();
6358
5799
  if (text && text !== state.lastStreamingText) {
6359
5800
  state.lastStreamingText = text;
6360
- const event = createStreamingTextEvent(jobId, runId, text);
6361
- state.events.push(event);
6362
- eventListener?.(event);
6363
5801
  }
6364
5802
  } else if (content.type === "tool_use") {
6365
5803
  const toolCall = {
@@ -6433,11 +5871,7 @@ var ClaudeCodeAdapter = class extends BaseAdapter {
6433
5871
  } else if (parsed.type === "content_block_delta" && parsed.delta) {
6434
5872
  const delta = parsed.delta;
6435
5873
  const text = delta.text?.trim();
6436
- if (delta.type === "text_delta" && text) {
6437
- const event = createStreamingTextEvent(jobId, runId, text);
6438
- state.events.push(event);
6439
- eventListener?.(event);
6440
- }
5874
+ if (delta.type === "text_delta" && text) ;
6441
5875
  }
6442
5876
  }
6443
5877
  };
@@ -6646,9 +6080,6 @@ ${query}`;
6646
6080
  const text = content.text?.trim();
6647
6081
  if (content.type === "text" && text && text !== state.lastStreamingText) {
6648
6082
  state.lastStreamingText = text;
6649
- const event = createStreamingTextEvent(jobId, runId, text);
6650
- state.events.push(event);
6651
- eventListener?.(event);
6652
6083
  }
6653
6084
  }
6654
6085
  }
@@ -7040,10 +6471,16 @@ function validateWorkspacePath(path11) {
7040
6471
  }
7041
6472
  }
7042
6473
  function generateComposeFile(options) {
7043
- const { proxyEnabled, networkName, envKeys, workspacePath } = options;
6474
+ const { proxyEnabled, networkName, envKeys, workspacePath, additionalVolumes } = options;
7044
6475
  if (workspacePath) {
7045
6476
  validateWorkspacePath(workspacePath);
7046
6477
  }
6478
+ for (const volume of additionalVolumes ?? []) {
6479
+ const hostPath = volume.split(":")[0];
6480
+ if (hostPath) {
6481
+ validateWorkspacePath(hostPath);
6482
+ }
6483
+ }
7047
6484
  const internalNetworkName = `${networkName}-internal`;
7048
6485
  const lines = [];
7049
6486
  lines.push("services:");
@@ -7066,9 +6503,15 @@ function generateComposeFile(options) {
7066
6503
  lines.push(` - ${key}`);
7067
6504
  }
7068
6505
  }
7069
- if (workspacePath) {
6506
+ const hasVolumes = workspacePath || additionalVolumes && additionalVolumes.length > 0;
6507
+ if (hasVolumes) {
7070
6508
  lines.push(" volumes:");
7071
- lines.push(` - ${workspacePath}:/workspace:rw`);
6509
+ if (workspacePath) {
6510
+ lines.push(` - ${workspacePath}:/workspace:rw`);
6511
+ }
6512
+ for (const volume of additionalVolumes ?? []) {
6513
+ lines.push(` - ${volume}`);
6514
+ }
7072
6515
  }
7073
6516
  lines.push(" stdin_open: true");
7074
6517
  lines.push(" tty: true");
@@ -7119,7 +6562,12 @@ function generateComposeFile(options) {
7119
6562
  return lines.join("\n");
7120
6563
  }
7121
6564
  function generateBuildContext(config, expertKey, options) {
7122
- const { workspacePath, verbose, additionalEnvKeys } = typeof options === "string" || options === void 0 ? { workspacePath: options, verbose: false, additionalEnvKeys: [] } : { additionalEnvKeys: [], ...options };
6565
+ const { workspacePath, verbose, additionalEnvKeys, additionalVolumes } = typeof options === "string" || options === void 0 ? {
6566
+ workspacePath: options,
6567
+ verbose: false,
6568
+ additionalEnvKeys: [],
6569
+ additionalVolumes: []
6570
+ } : { additionalEnvKeys: [], additionalVolumes: [], ...options };
7123
6571
  const allowedDomains = collectAllowedDomains(config, expertKey);
7124
6572
  const hasAllowlist = allowedDomains.length > 0;
7125
6573
  const dockerfile = generateDockerfile(config, expertKey, { proxyEnabled: hasAllowlist });
@@ -7143,7 +6591,8 @@ function generateBuildContext(config, expertKey, options) {
7143
6591
  proxyEnabled: hasAllowlist,
7144
6592
  networkName: "perstack-net",
7145
6593
  envKeys: allEnvKeys,
7146
- workspacePath: resolvedWorkspacePath
6594
+ workspacePath: resolvedWorkspacePath,
6595
+ additionalVolumes
7147
6596
  });
7148
6597
  return {
7149
6598
  dockerfile,
@@ -7437,7 +6886,7 @@ var DockerAdapter = class extends BaseAdapter {
7437
6886
  return { instruction: expert.instruction };
7438
6887
  }
7439
6888
  async run(params) {
7440
- const { setting, config, eventListener, workspace, additionalEnvKeys } = params;
6889
+ const { setting, config, eventListener, workspace, additionalEnvKeys, additionalVolumes } = params;
7441
6890
  if (!config) {
7442
6891
  throw new Error("DockerAdapter requires config in AdapterRunParams");
7443
6892
  }
@@ -7452,7 +6901,8 @@ var DockerAdapter = class extends BaseAdapter {
7452
6901
  expertKey,
7453
6902
  resolvedWorkspace,
7454
6903
  setting.verbose,
7455
- additionalEnvKeys
6904
+ additionalEnvKeys,
6905
+ additionalVolumes
7456
6906
  );
7457
6907
  let signalReceived = false;
7458
6908
  const signalHandler = async (signal) => {
@@ -7538,12 +6988,13 @@ var DockerAdapter = class extends BaseAdapter {
7538
6988
  }
7539
6989
  return resolved;
7540
6990
  }
7541
- async prepareBuildContext(config, expertKey, workspace, verbose, additionalEnvKeys) {
6991
+ async prepareBuildContext(config, expertKey, workspace, verbose, additionalEnvKeys, additionalVolumes) {
7542
6992
  const buildDir = fs.mkdtempSync(path10.join(os.tmpdir(), "perstack-docker-"));
7543
6993
  const context = generateBuildContext(config, expertKey, {
7544
6994
  workspacePath: workspace,
7545
6995
  verbose,
7546
- additionalEnvKeys
6996
+ additionalEnvKeys,
6997
+ additionalVolumes
7547
6998
  });
7548
6999
  fs.writeFileSync(path10.join(buildDir, "Dockerfile"), context.dockerfile);
7549
7000
  fs.writeFileSync(path10.join(buildDir, "perstack.toml"), context.configToml);
@@ -7910,9 +7361,6 @@ ${query}`;
7910
7361
  state.finalOutput = state.accumulatedText;
7911
7362
  if (content !== state.lastStreamingText) {
7912
7363
  state.lastStreamingText = content;
7913
- const event = createStreamingTextEvent(jobId, runId, state.accumulatedText);
7914
- state.events.push(event);
7915
- eventListener?.(event);
7916
7364
  }
7917
7365
  }
7918
7366
  } else if (parsed.type === "tool_use") {
@@ -8004,12 +7452,14 @@ async function dispatchToRuntime(params) {
8004
7452
  storeEvent,
8005
7453
  retrieveCheckpoint,
8006
7454
  workspace,
8007
- additionalEnvKeys
7455
+ additionalEnvKeys,
7456
+ additionalVolumes
8008
7457
  } = params;
8009
7458
  const setting = {
8010
7459
  ...params.setting,
8011
7460
  jobId: params.setting.jobId ?? createId(),
8012
- runId: params.setting.runId ?? createId()
7461
+ runId: createId()
7462
+ // runId is always generated internally, never from external input
8013
7463
  };
8014
7464
  if (!isAdapterAvailable(runtime)) {
8015
7465
  const available = getRegisteredRuntimes().join(", ");
@@ -8035,7 +7485,8 @@ See: ${error.helpUrl}`;
8035
7485
  storeEvent: storeEvent ?? defaultStoreEvent,
8036
7486
  retrieveCheckpoint: retrieveCheckpoint ?? defaultRetrieveCheckpoint,
8037
7487
  workspace,
8038
- additionalEnvKeys
7488
+ additionalEnvKeys,
7489
+ additionalVolumes
8039
7490
  });
8040
7491
  return { checkpoint: result.checkpoint };
8041
7492
  }
@@ -8068,7 +7519,7 @@ var runCommand = new Command().command("run").description("Run Perstack with JSO
8068
7519
  ).option("--max-retries <maxRetries>", "Maximum number of generation retries, default is 5").option(
8069
7520
  "--timeout <timeout>",
8070
7521
  "Timeout for each generation in milliseconds, default is 60000 (1 minute)"
8071
- ).option("--job-id <jobId>", "Job ID for identifying the job").option("--run-id <runId>", "Run ID for identifying the run").option(
7522
+ ).option("--job-id <jobId>", "Job ID for identifying the job").option(
8072
7523
  "--env-path <path>",
8073
7524
  "Path to the environment file (can be specified multiple times), default is .env and .env.local",
8074
7525
  (value, previous) => previous.concat(value),
@@ -8081,8 +7532,31 @@ var runCommand = new Command().command("run").description("Run Perstack with JSO
8081
7532
  ).option("--verbose", "Enable verbose logging").option("--continue", "Continue the most recent job with new query").option("--continue-job <jobId>", "Continue the specified job with new query").option(
8082
7533
  "--resume-from <checkpointId>",
8083
7534
  "Resume from a specific checkpoint (requires --continue or --continue-job)"
8084
- ).option("-i, --interactive-tool-call-result", "Query is interactive tool call result").option("--runtime <runtime>", "Execution runtime (docker, local, cursor, claude-code, gemini)").option("--workspace <workspace>", "Workspace directory for Docker runtime").action(async (expertKey, query, options) => {
7535
+ ).option("-i, --interactive-tool-call-result", "Query is interactive tool call result").option("--runtime <runtime>", "Execution runtime (docker, local, cursor, claude-code, gemini)").option("--workspace <workspace>", "Workspace directory for Docker runtime").option(
7536
+ "--volume <volume>",
7537
+ "Additional volume mount for Docker runtime (format: hostPath:containerPath:mode, can be specified multiple times)",
7538
+ (value, previous) => previous.concat(value),
7539
+ []
7540
+ ).option(
7541
+ "--filter <types>",
7542
+ "Filter events by type (comma-separated, e.g., completeRun,stopRunByError)"
7543
+ ).action(async (expertKey, query, options) => {
8085
7544
  const input = parseWithFriendlyError(runCommandInputSchema, { expertKey, query, options });
7545
+ let eventListener = defaultEventListener;
7546
+ if (input.options.filter && input.options.filter.length > 0) {
7547
+ try {
7548
+ const validatedTypes = validateEventFilter(input.options.filter);
7549
+ const allowedTypes = new Set(validatedTypes);
7550
+ eventListener = createFilteredEventListener(defaultEventListener, allowedTypes);
7551
+ } catch (error) {
7552
+ if (error instanceof Error) {
7553
+ console.error(error.message);
7554
+ } else {
7555
+ console.error(error);
7556
+ }
7557
+ process.exit(1);
7558
+ }
7559
+ }
8086
7560
  try {
8087
7561
  const { perstackConfig, checkpoint, env, providerConfig, model, experts } = await resolveRunContext({
8088
7562
  configPath: input.options.config,
@@ -8098,7 +7572,6 @@ var runCommand = new Command().command("run").description("Run Perstack with JSO
8098
7572
  await dispatchToRuntime({
8099
7573
  setting: {
8100
7574
  jobId: checkpoint?.jobId ?? input.options.jobId,
8101
- runId: checkpoint?.runId ?? input.options.runId,
8102
7575
  expertKey: input.expertKey,
8103
7576
  input: input.options.interactiveToolCallResult ? parseInteractiveToolCallResultJson(input.query) ?? (checkpoint ? parseInteractiveToolCallResult(input.query, checkpoint) : { text: input.query }) : { text: input.query },
8104
7577
  experts,
@@ -8118,9 +7591,10 @@ var runCommand = new Command().command("run").description("Run Perstack with JSO
8118
7591
  checkpoint,
8119
7592
  runtime,
8120
7593
  config: perstackConfig,
8121
- eventListener: defaultEventListener,
7594
+ eventListener,
8122
7595
  workspace: input.options.workspace,
8123
- additionalEnvKeys: input.options.env
7596
+ additionalEnvKeys: input.options.env,
7597
+ additionalVolumes: input.options.volume
8124
7598
  });
8125
7599
  } catch (error) {
8126
7600
  if (error instanceof Error) {
@@ -8141,7 +7615,7 @@ var startCommand = new Command().command("start").description("Start Perstack wi
8141
7615
  ).option("--max-retries <maxRetries>", "Maximum number of generation retries, default is 5").option(
8142
7616
  "--timeout <timeout>",
8143
7617
  "Timeout for each generation in milliseconds, default is 60000 (1 minute)"
8144
- ).option("--job-id <jobId>", "Job ID for identifying the job").option("--run-id <runId>", "Run ID for identifying the run").option(
7618
+ ).option("--job-id <jobId>", "Job ID for identifying the job").option(
8145
7619
  "--env-path <path>",
8146
7620
  "Path to the environment file (can be specified multiple times), default is .env and .env.local",
8147
7621
  (value, previous) => previous.concat(value),
@@ -8168,14 +7642,16 @@ var startCommand = new Command().command("start").description("Start Perstack wi
8168
7642
  expertKey: input.expertKey
8169
7643
  });
8170
7644
  const runtime = input.options.runtime ?? perstackConfig.runtime ?? "docker";
8171
- const showHistory = !input.expertKey && !input.query && !checkpoint;
8172
- const needsQueryInput = !input.query && !checkpoint;
7645
+ const maxSteps = input.options.maxSteps ?? perstackConfig.maxSteps;
7646
+ const maxRetries = input.options.maxRetries ?? perstackConfig.maxRetries ?? defaultMaxRetries;
7647
+ const timeout = input.options.timeout ?? perstackConfig.timeout ?? defaultTimeout;
8173
7648
  const configuredExperts = Object.keys(perstackConfig.experts ?? {}).map((key) => ({
8174
7649
  key,
8175
7650
  name: key
8176
7651
  }));
8177
- const recentExperts = await getRecentExperts(10);
8178
- const historyJobs = showHistory ? (await getAllJobs2()).map((j) => ({
7652
+ const recentExperts = getRecentExperts(10);
7653
+ const showHistory = !input.expertKey && !input.query && !checkpoint;
7654
+ const historyJobs = showHistory ? getAllJobs2().map((j) => ({
8179
7655
  jobId: j.id,
8180
7656
  status: j.status,
8181
7657
  expertKey: j.coordinatorExpertKey,
@@ -8183,75 +7659,65 @@ var startCommand = new Command().command("start").description("Start Perstack wi
8183
7659
  startedAt: j.startedAt,
8184
7660
  finishedAt: j.finishedAt
8185
7661
  })) : [];
8186
- const resumeState = { checkpoint: null };
8187
- let resolveContinueQuery = null;
8188
- const maxSteps = input.options.maxSteps ?? perstackConfig.maxSteps;
8189
- const maxRetries = input.options.maxRetries ?? perstackConfig.maxRetries ?? defaultMaxRetries;
8190
- const timeout = input.options.timeout ?? perstackConfig.timeout ?? defaultTimeout;
8191
- const result = await renderStart({
7662
+ const selection = await renderSelection({
8192
7663
  showHistory,
8193
- needsQueryInput,
8194
- initialExpertName: input.expertKey,
7664
+ initialExpertKey: input.expertKey,
8195
7665
  initialQuery: input.query,
8196
- initialConfig: {
8197
- runtimeVersion: getRuntimeVersion(),
8198
- model,
8199
- maxSteps,
8200
- maxRetries,
8201
- timeout,
8202
- contextWindowUsage: checkpoint?.contextWindowUsage ?? 0,
8203
- runtime
8204
- },
7666
+ initialCheckpoint: checkpoint ? {
7667
+ id: checkpoint.id,
7668
+ jobId: checkpoint.jobId,
7669
+ runId: checkpoint.runId,
7670
+ stepNumber: checkpoint.stepNumber,
7671
+ contextWindowUsage: checkpoint.contextWindowUsage ?? 0
7672
+ } : void 0,
8205
7673
  configuredExperts,
8206
7674
  recentExperts,
8207
7675
  historyJobs,
8208
- onContinue: (query2) => {
8209
- if (resolveContinueQuery) {
8210
- resolveContinueQuery(query2);
8211
- resolveContinueQuery = null;
8212
- }
8213
- },
8214
- onResumeFromCheckpoint: (cp) => {
8215
- resumeState.checkpoint = cp;
8216
- },
8217
7676
  onLoadCheckpoints: async (j) => {
8218
- const checkpoints = await getCheckpointsWithDetails(j.jobId);
7677
+ const checkpoints = getCheckpointsWithDetails(j.jobId);
8219
7678
  return checkpoints.map((cp) => ({ ...cp, jobId: j.jobId }));
8220
- },
8221
- onLoadEvents: async (j, cp) => {
8222
- const events = await getEventsWithDetails(j.jobId, cp.runId, cp.stepNumber);
8223
- return events.map((e) => ({ ...e, jobId: j.jobId }));
8224
- },
8225
- onLoadHistoricalEvents: async (cp) => {
8226
- return await getEventContents2(cp.jobId, cp.runId, cp.stepNumber);
8227
7679
  }
8228
7680
  });
8229
- const finalExpertKey = result.expertKey || input.expertKey;
8230
- let finalQuery = result.query || input.query;
8231
- if (!finalExpertKey) {
7681
+ if (!selection.expertKey) {
8232
7682
  console.error("Expert key is required");
8233
7683
  return;
8234
7684
  }
8235
- let currentCheckpoint = resumeState.checkpoint !== null ? await getCheckpointById(resumeState.checkpoint.jobId, resumeState.checkpoint.id) : checkpoint;
8236
- if (currentCheckpoint && currentCheckpoint.expert.key !== finalExpertKey) {
8237
- console.error(
8238
- `Checkpoint expert key ${currentCheckpoint.expert.key} does not match input expert key ${finalExpertKey}`
8239
- );
7685
+ if (!selection.query && !selection.checkpoint) {
7686
+ console.error("Query is required");
8240
7687
  return;
8241
7688
  }
8242
- if (!finalQuery && !currentCheckpoint) {
8243
- console.error("Query is required");
7689
+ let currentCheckpoint = selection.checkpoint ? getCheckpointById(selection.checkpoint.jobId, selection.checkpoint.id) : checkpoint;
7690
+ if (currentCheckpoint && currentCheckpoint.expert.key !== selection.expertKey) {
7691
+ console.error(
7692
+ `Checkpoint expert key ${currentCheckpoint.expert.key} does not match input expert key ${selection.expertKey}`
7693
+ );
8244
7694
  return;
8245
7695
  }
7696
+ let currentQuery = selection.query;
8246
7697
  let currentJobId = currentCheckpoint?.jobId ?? input.options.jobId;
8247
- let currentRunId = currentCheckpoint?.runId ?? input.options.runId;
8248
- while (true) {
7698
+ let accumulatedEvents = currentCheckpoint ? getAllEventContentsForJob(currentCheckpoint.jobId, currentCheckpoint.stepNumber) : void 0;
7699
+ while (currentQuery !== null) {
7700
+ const historicalEvents = accumulatedEvents;
7701
+ const { result: executionResult, eventListener } = renderExecution({
7702
+ expertKey: selection.expertKey,
7703
+ query: currentQuery,
7704
+ config: {
7705
+ runtimeVersion: getRuntimeVersion(),
7706
+ model,
7707
+ maxSteps,
7708
+ maxRetries,
7709
+ timeout,
7710
+ contextWindowUsage: currentCheckpoint?.contextWindowUsage ?? 0,
7711
+ runtime
7712
+ },
7713
+ continueTimeoutMs: CONTINUE_TIMEOUT_MS,
7714
+ historicalEvents
7715
+ });
8249
7716
  const { checkpoint: runResult } = await dispatchToRuntime({
8250
7717
  setting: {
8251
7718
  jobId: currentJobId,
8252
- runId: currentRunId,
8253
- expertKey: finalExpertKey,
8254
- input: input.options.interactiveToolCallResult && currentCheckpoint ? parseInteractiveToolCallResult(finalQuery || "", currentCheckpoint) : { text: finalQuery },
7719
+ expertKey: selection.expertKey,
7720
+ input: input.options.interactiveToolCallResult && currentCheckpoint ? parseInteractiveToolCallResult(currentQuery, currentCheckpoint) : { text: currentQuery },
8255
7721
  experts,
8256
7722
  model,
8257
7723
  providerConfig,
@@ -8268,30 +7734,23 @@ var startCommand = new Command().command("start").description("Start Perstack wi
8268
7734
  checkpoint: currentCheckpoint,
8269
7735
  runtime,
8270
7736
  config: perstackConfig,
8271
- eventListener: result.eventListener,
7737
+ eventListener,
8272
7738
  workspace: input.options.workspace,
8273
7739
  additionalEnvKeys: input.options.env
8274
7740
  });
8275
- if (runResult.status === "completed" || runResult.status === "stoppedByExceededMaxSteps" || runResult.status === "stoppedByError") {
8276
- const nextQuery = await new Promise((resolve2) => {
8277
- resolveContinueQuery = resolve2;
8278
- setTimeout(() => {
8279
- if (resolveContinueQuery === resolve2) {
8280
- resolveContinueQuery = null;
8281
- resolve2(null);
8282
- }
8283
- }, CONTINUE_TIMEOUT_MS);
8284
- });
8285
- if (nextQuery) {
8286
- finalQuery = nextQuery;
8287
- currentCheckpoint = runResult;
8288
- currentJobId = runResult.jobId;
8289
- currentRunId = runResult.runId;
7741
+ const result = await executionResult;
7742
+ if (result.nextQuery && (runResult.status === "completed" || runResult.status === "stoppedByExceededMaxSteps" || runResult.status === "stoppedByError")) {
7743
+ currentQuery = result.nextQuery;
7744
+ currentCheckpoint = runResult;
7745
+ currentJobId = runResult.jobId;
7746
+ const newRunEvents = getEventContents2(runResult.jobId, runResult.runId);
7747
+ if (accumulatedEvents) {
7748
+ accumulatedEvents = [...accumulatedEvents, ...newRunEvents];
8290
7749
  } else {
8291
- break;
7750
+ accumulatedEvents = newRunEvents;
8292
7751
  }
8293
7752
  } else {
8294
- break;
7753
+ currentQuery = null;
8295
7754
  }
8296
7755
  }
8297
7756
  } catch (error) {