create-expert 0.0.21 → 0.0.23

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
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFileSync, existsSync, mkdirSync, writeFileSync, readdirSync } from 'fs';
3
- import { parseWithFriendlyError, perstackConfigSchema, startCommandInputSchema, defaultMaxRetries, defaultTimeout, checkpointSchema, jobSchema, runSettingSchema, BASE_SKILL_PREFIX, createBaseToolActivity, createGeneralToolActivity } from '@perstack/core';
3
+ import { parseWithFriendlyError, perstackConfigSchema, startCommandInputSchema, defaultMaxRetries, defaultTimeout, checkpointSchema, lockfileSchema, jobSchema, runSettingSchema, BASE_SKILL_PREFIX, createBaseToolActivity, createGeneralToolActivity } from '@perstack/core';
4
4
  import { readFile, mkdir, writeFile } from 'fs/promises';
5
- import path5 from 'path';
6
- import { findLockfile, loadLockfile, runtimeVersion, run } from '@perstack/runtime';
5
+ import path6 from 'path';
6
+ import { runtimeVersion, run } from '@perstack/runtime';
7
+ import dotenv from 'dotenv';
8
+ import TOML from 'smol-toml';
7
9
  import { render, useApp, useInput, Box, Text } from 'ink';
8
10
  import { createContext, useMemo, useReducer, useState, useEffect, useCallback, useRef, useInsertionEffect, useContext } from 'react';
9
11
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
10
- import dotenv from 'dotenv';
11
- import TOML from 'smol-toml';
12
12
  import { Command } from 'commander';
13
13
 
14
14
  // ../../node_modules/.pnpm/@noble+hashes@2.0.1/node_modules/@noble/hashes/_u64.js
@@ -1728,12 +1728,12 @@ function storeJob(job) {
1728
1728
  if (!existsSync(jobDir)) {
1729
1729
  mkdirSync(jobDir, { recursive: true });
1730
1730
  }
1731
- const jobPath = path5.resolve(jobDir, "job.json");
1731
+ const jobPath = path6.resolve(jobDir, "job.json");
1732
1732
  writeFileSync(jobPath, JSON.stringify(job, null, 2));
1733
1733
  }
1734
1734
  function retrieveJob(jobId) {
1735
1735
  const jobDir = getJobDir(jobId);
1736
- const jobPath = path5.resolve(jobDir, "job.json");
1736
+ const jobPath = path6.resolve(jobDir, "job.json");
1737
1737
  if (!existsSync(jobPath)) {
1738
1738
  return void 0;
1739
1739
  }
@@ -1751,7 +1751,7 @@ function getAllJobs() {
1751
1751
  }
1752
1752
  const jobs = [];
1753
1753
  for (const jobDirName of jobDirNames) {
1754
- const jobPath = path5.resolve(jobsDir, jobDirName, "job.json");
1754
+ const jobPath = path6.resolve(jobsDir, jobDirName, "job.json");
1755
1755
  if (!existsSync(jobPath)) {
1756
1756
  continue;
1757
1757
  }
@@ -1812,7 +1812,7 @@ function getCheckpointsByJobId(jobId) {
1812
1812
  const checkpoints = [];
1813
1813
  for (const file of files) {
1814
1814
  try {
1815
- const content = readFileSync(path5.resolve(checkpointDir, file), "utf-8");
1815
+ const content = readFileSync(path6.resolve(checkpointDir, file), "utf-8");
1816
1816
  checkpoints.push(checkpointSchema.parse(JSON.parse(content)));
1817
1817
  } catch {
1818
1818
  }
@@ -1833,13 +1833,13 @@ function getAllRuns() {
1833
1833
  }
1834
1834
  const runs = [];
1835
1835
  for (const jobDirName of jobDirNames) {
1836
- const runsDir = path5.resolve(jobsDir, jobDirName, "runs");
1836
+ const runsDir = path6.resolve(jobsDir, jobDirName, "runs");
1837
1837
  if (!existsSync(runsDir)) {
1838
1838
  continue;
1839
1839
  }
1840
1840
  const runDirNames = readdirSync(runsDir, { withFileTypes: true }).filter((dir) => dir.isDirectory()).map((dir) => dir.name);
1841
1841
  for (const runDirName of runDirNames) {
1842
- const runSettingPath = path5.resolve(runsDir, runDirName, "run-setting.json");
1842
+ const runSettingPath = path6.resolve(runsDir, runDirName, "run-setting.json");
1843
1843
  if (!existsSync(runSettingPath)) {
1844
1844
  continue;
1845
1845
  }
@@ -1873,7 +1873,7 @@ function getEventContents(jobId, runId, maxStepNumber) {
1873
1873
  const events = [];
1874
1874
  for (const { file } of eventFiles) {
1875
1875
  try {
1876
- const content = readFileSync(path5.resolve(runDir, file), "utf-8");
1876
+ const content = readFileSync(path6.resolve(runDir, file), "utf-8");
1877
1877
  events.push(JSON.parse(content));
1878
1878
  } catch {
1879
1879
  }
@@ -1881,2314 +1881,2349 @@ function getEventContents(jobId, runId, maxStepNumber) {
1881
1881
  return events;
1882
1882
  }
1883
1883
  function getRunIdsByJobId(jobId) {
1884
- const runsDir = path5.resolve(getJobDir(jobId), "runs");
1884
+ const runsDir = path6.resolve(getJobDir(jobId), "runs");
1885
1885
  if (!existsSync(runsDir)) {
1886
1886
  return [];
1887
1887
  }
1888
1888
  return readdirSync(runsDir, { withFileTypes: true }).filter((dir) => dir.isDirectory()).map((dir) => dir.name);
1889
1889
  }
1890
-
1891
- // ../../packages/tui-components/src/utils/event-queue.ts
1892
- var defaultErrorLogger = (message, error) => {
1893
- console.error(message, error);
1894
- };
1895
- var EventQueue = class _EventQueue {
1896
- static MAX_PENDING_EVENTS = 1e3;
1897
- pendingEvents = [];
1898
- handler = null;
1899
- onError;
1900
- errorLogger;
1901
- constructor(options) {
1902
- this.onError = options?.onError;
1903
- this.errorLogger = options?.errorLogger ?? defaultErrorLogger;
1890
+ function getEnv(envPath) {
1891
+ const env = Object.fromEntries(
1892
+ Object.entries(process.env).filter(([_, value]) => !!value).map(([key, value]) => [key, value])
1893
+ );
1894
+ dotenv.config({ path: envPath, processEnv: env, quiet: true });
1895
+ return env;
1896
+ }
1897
+ var ALLOWED_CONFIG_HOSTS = ["raw.githubusercontent.com"];
1898
+ async function getPerstackConfig(configPath) {
1899
+ const configString = await findPerstackConfigString(configPath);
1900
+ if (configString === null) {
1901
+ throw new Error("perstack.toml not found. Create one or specify --config path.");
1904
1902
  }
1905
- setHandler(fn) {
1906
- this.handler = fn;
1907
- for (const event of this.pendingEvents) {
1908
- this.safeHandle(event);
1909
- }
1910
- this.pendingEvents.length = 0;
1903
+ return await parsePerstackConfig(configString);
1904
+ }
1905
+ function isRemoteUrl(configPath) {
1906
+ const lower = configPath.toLowerCase();
1907
+ return lower.startsWith("https://") || lower.startsWith("http://");
1908
+ }
1909
+ async function fetchRemoteConfig(url) {
1910
+ let parsed;
1911
+ try {
1912
+ parsed = new URL(url);
1913
+ } catch {
1914
+ throw new Error(`Invalid remote config URL: ${url}`);
1911
1915
  }
1912
- emit(event) {
1913
- if (this.handler) {
1914
- this.safeHandle(event);
1915
- } else {
1916
- if (this.pendingEvents.length >= _EventQueue.MAX_PENDING_EVENTS) {
1917
- this.pendingEvents.shift();
1918
- }
1919
- this.pendingEvents.push(event);
1916
+ if (parsed.protocol !== "https:") {
1917
+ throw new Error("Remote config requires HTTPS");
1918
+ }
1919
+ if (!ALLOWED_CONFIG_HOSTS.includes(parsed.hostname)) {
1920
+ throw new Error(`Remote config only allowed from: ${ALLOWED_CONFIG_HOSTS.join(", ")}`);
1921
+ }
1922
+ try {
1923
+ const response = await fetch(url, { redirect: "error" });
1924
+ if (!response.ok) {
1925
+ throw new Error(`${response.status} ${response.statusText}`);
1920
1926
  }
1927
+ return await response.text();
1928
+ } catch (error) {
1929
+ const message = error instanceof Error ? error.message : String(error);
1930
+ throw new Error(`Failed to fetch remote config: ${message}`);
1921
1931
  }
1922
- safeHandle(event) {
1932
+ }
1933
+ async function findPerstackConfigString(configPath) {
1934
+ if (configPath) {
1935
+ if (isRemoteUrl(configPath)) {
1936
+ return await fetchRemoteConfig(configPath);
1937
+ }
1923
1938
  try {
1924
- this.handler?.(event);
1925
- } catch (error) {
1926
- if (this.onError) {
1927
- this.onError(error);
1928
- } else {
1929
- this.errorLogger("EventQueue handler error:", error);
1930
- }
1939
+ const tomlString = await readFile(path6.resolve(process.cwd(), configPath), "utf-8");
1940
+ return tomlString;
1941
+ } catch {
1942
+ throw new Error(`Given config path "${configPath}" is not found`);
1931
1943
  }
1932
1944
  }
1933
- };
1934
- var InputAreaContext = createContext(null);
1935
- var InputAreaProvider = InputAreaContext.Provider;
1936
- var useInputAreaContext = () => {
1937
- const context = useContext(InputAreaContext);
1938
- if (!context) {
1939
- throw new Error("useInputAreaContext must be used within InputAreaProvider");
1945
+ return await findPerstackConfigStringRecursively(path6.resolve(process.cwd()));
1946
+ }
1947
+ async function findPerstackConfigStringRecursively(cwd) {
1948
+ try {
1949
+ const tomlString = await readFile(path6.resolve(cwd, "perstack.toml"), "utf-8");
1950
+ return tomlString;
1951
+ } catch {
1952
+ if (cwd === path6.parse(cwd).root) {
1953
+ return null;
1954
+ }
1955
+ return await findPerstackConfigStringRecursively(path6.dirname(cwd));
1940
1956
  }
1941
- return context;
1942
- };
1943
-
1944
- // ../../packages/tui-components/src/helpers.ts
1945
- var truncateText = (text, maxLength) => {
1946
- if (text.length <= maxLength) return text;
1947
- return `${text.slice(0, maxLength)}...`;
1948
- };
1949
- var assertNever = (x) => {
1950
- throw new Error(`Unexpected value: ${x}`);
1951
- };
1952
- var formatTimestamp = (timestamp) => new Date(timestamp).toLocaleString();
1953
- var shortenPath = (fullPath, maxLength = 60) => {
1954
- if (fullPath.length <= maxLength) return fullPath;
1955
- const parts = fullPath.split("/");
1956
- if (parts.length <= 2) return fullPath;
1957
- const filename = parts[parts.length - 1] ?? "";
1958
- const parentDir = parts[parts.length - 2] ?? "";
1959
- const shortened = `.../${parentDir}/${filename}`;
1960
- if (shortened.length <= maxLength) return shortened;
1961
- return `.../${filename}`;
1962
- };
1963
- var summarizeOutput = (lines, maxLines) => {
1964
- const filtered = lines.filter((l) => l.trim());
1965
- const visible = filtered.slice(0, maxLines);
1966
- const remaining = filtered.length - visible.length;
1967
- return { visible, remaining };
1968
- };
1957
+ }
1958
+ async function parsePerstackConfig(config2) {
1959
+ const toml = TOML.parse(config2 ?? "");
1960
+ return parseWithFriendlyError(perstackConfigSchema, toml, "perstack.toml");
1961
+ }
1969
1962
 
1970
- // ../../packages/tui-components/src/constants.ts
1971
- var UI_CONSTANTS = {
1972
- MAX_VISIBLE_LIST_ITEMS: 25,
1973
- TRUNCATE_TEXT_MEDIUM: 80,
1974
- TRUNCATE_TEXT_DEFAULT: 100};
1975
- var RENDER_CONSTANTS = {
1976
- EXEC_OUTPUT_MAX_LINES: 4,
1977
- NEW_TODO_MAX_PREVIEW: 3,
1978
- LIST_DIR_MAX_ITEMS: 4
1979
- };
1980
- var INDICATOR = {
1981
- BULLET: "\u25CF",
1982
- TREE: "\u2514",
1983
- ELLIPSIS: "..."
1963
+ // ../../packages/tui/src/lib/provider-config.ts
1964
+ var PROVIDER_ENV_MAP = {
1965
+ anthropic: "ANTHROPIC_API_KEY",
1966
+ google: "GOOGLE_GENERATIVE_AI_API_KEY",
1967
+ openai: "OPENAI_API_KEY",
1968
+ deepseek: "DEEPSEEK_API_KEY",
1969
+ "azure-openai": "AZURE_API_KEY",
1970
+ "amazon-bedrock": "AWS_ACCESS_KEY_ID",
1971
+ "google-vertex": "GOOGLE_APPLICATION_CREDENTIALS",
1972
+ ollama: void 0
1984
1973
  };
1985
- var STOP_EVENT_TYPES = [
1986
- "stopRunByInteractiveTool",
1987
- "stopRunByDelegate",
1988
- "stopRunByExceededMaxSteps"
1989
- ];
1990
- var KEY_BINDINGS = {
1991
- NAVIGATE_UP: "\u2191",
1992
- NAVIGATE_DOWN: "\u2193",
1993
- SELECT: "Enter",
1994
- BACK: "b",
1995
- ESCAPE: "Esc",
1996
- INPUT_MODE: "i",
1997
- HISTORY: "h",
1998
- NEW: "n",
1999
- CHECKPOINTS: "c",
2000
- EVENTS: "e",
2001
- RESUME: "Enter"};
2002
- var KEY_HINTS = {
2003
- NAVIGATE: `${KEY_BINDINGS.NAVIGATE_UP}${KEY_BINDINGS.NAVIGATE_DOWN}:Navigate`,
2004
- SELECT: `${KEY_BINDINGS.SELECT}:Select`,
2005
- RESUME: `${KEY_BINDINGS.RESUME}:Resume`,
2006
- BACK: `${KEY_BINDINGS.BACK}:Back`,
2007
- CANCEL: `${KEY_BINDINGS.ESCAPE}:Cancel`,
2008
- INPUT: `${KEY_BINDINGS.INPUT_MODE}:Input`,
2009
- HISTORY: `${KEY_BINDINGS.HISTORY}:History`,
2010
- NEW: `${KEY_BINDINGS.NEW}:New Run`,
2011
- CHECKPOINTS: `${KEY_BINDINGS.CHECKPOINTS}:Checkpoints`,
2012
- EVENTS: `${KEY_BINDINGS.EVENTS}:Events`};
2013
- var useListNavigation = (options) => {
2014
- const { items, onSelect, onBack } = options;
2015
- const [selectedIndex, setSelectedIndex] = useState(0);
2016
- useEffect(() => {
2017
- if (selectedIndex >= items.length && items.length > 0) {
2018
- setSelectedIndex(items.length - 1);
1974
+ function getProviderConfig(provider, env, providerTable) {
1975
+ const setting = providerTable?.setting ?? {};
1976
+ switch (provider) {
1977
+ case "anthropic": {
1978
+ const apiKey = env.ANTHROPIC_API_KEY;
1979
+ if (!apiKey) throw new Error("ANTHROPIC_API_KEY is not set");
1980
+ return {
1981
+ providerName: "anthropic",
1982
+ apiKey,
1983
+ baseUrl: setting.baseUrl ?? env.ANTHROPIC_BASE_URL,
1984
+ headers: setting.headers
1985
+ };
2019
1986
  }
2020
- }, [items.length, selectedIndex]);
2021
- const handleNavigation = useCallback(
2022
- (inputChar, key) => {
2023
- if (key.upArrow && items.length > 0) {
2024
- setSelectedIndex((prev) => Math.max(0, prev - 1));
2025
- return true;
2026
- }
2027
- if (key.downArrow && items.length > 0) {
2028
- setSelectedIndex((prev) => Math.min(items.length - 1, prev + 1));
2029
- return true;
2030
- }
2031
- if (key.return && items[selectedIndex]) {
2032
- onSelect?.(items[selectedIndex]);
2033
- return true;
2034
- }
2035
- if ((key.escape || inputChar === "b") && onBack) {
2036
- onBack();
2037
- return true;
2038
- }
2039
- return false;
2040
- },
2041
- [items, selectedIndex, onSelect, onBack]
2042
- );
2043
- return { selectedIndex, handleNavigation };
2044
- };
2045
- var ListBrowser = ({
2046
- title,
2047
- items,
2048
- renderItem,
2049
- onSelect,
2050
- emptyMessage = "No items found",
2051
- extraKeyHandler,
2052
- onBack,
2053
- maxItems = UI_CONSTANTS.MAX_VISIBLE_LIST_ITEMS
2054
- }) => {
2055
- const { selectedIndex, handleNavigation } = useListNavigation({
2056
- items,
2057
- onSelect,
2058
- onBack
2059
- });
2060
- useInput((inputChar, key) => {
2061
- if (extraKeyHandler?.(inputChar, key, items[selectedIndex])) return;
2062
- handleNavigation(inputChar, key);
2063
- });
2064
- const { scrollOffset, displayItems } = useMemo(() => {
2065
- const offset = Math.max(0, Math.min(selectedIndex - maxItems + 1, items.length - maxItems));
2066
- return {
2067
- scrollOffset: offset,
2068
- displayItems: items.slice(offset, offset + maxItems)
2069
- };
2070
- }, [items, selectedIndex, maxItems]);
2071
- const hasMoreAbove = scrollOffset > 0;
2072
- const hasMoreBelow = scrollOffset + maxItems < items.length;
2073
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2074
- /* @__PURE__ */ jsxs(Box, { children: [
2075
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: title }),
2076
- items.length > maxItems && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
2077
- " ",
2078
- "(",
2079
- selectedIndex + 1,
2080
- "/",
2081
- items.length,
2082
- ")"
2083
- ] })
2084
- ] }),
2085
- hasMoreAbove && /* @__PURE__ */ jsx(Text, { color: "gray", children: INDICATOR.ELLIPSIS }),
2086
- /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: displayItems.length === 0 ? /* @__PURE__ */ jsx(Text, { color: "gray", children: emptyMessage }) : displayItems.map((item, index) => {
2087
- const actualIndex = scrollOffset + index;
2088
- return renderItem(item, actualIndex === selectedIndex, actualIndex);
2089
- }) }),
2090
- hasMoreBelow && /* @__PURE__ */ jsx(Text, { color: "gray", children: INDICATOR.ELLIPSIS })
2091
- ] });
2092
- };
2093
- var BrowsingCheckpointsInput = ({
2094
- job,
2095
- checkpoints,
2096
- onCheckpointSelect,
2097
- onCheckpointResume,
2098
- onBack,
2099
- showEventsHint = true
2100
- }) => /* @__PURE__ */ jsx(
2101
- ListBrowser,
2102
- {
2103
- title: `Checkpoints for ${job.expertKey} ${KEY_HINTS.NAVIGATE} ${KEY_HINTS.RESUME} ${showEventsHint ? KEY_HINTS.EVENTS : ""} ${KEY_HINTS.BACK}`.trim(),
2104
- items: checkpoints,
2105
- onSelect: onCheckpointResume,
2106
- onBack,
2107
- emptyMessage: "No checkpoints found",
2108
- extraKeyHandler: (char, _key, selected) => {
2109
- if (char === "e" && selected) {
2110
- onCheckpointSelect(selected);
2111
- return true;
2112
- }
2113
- return false;
2114
- },
2115
- renderItem: (cp, isSelected) => /* @__PURE__ */ jsxs(Text, { color: isSelected ? "cyan" : "gray", children: [
2116
- isSelected ? ">" : " ",
2117
- " Step ",
2118
- cp.stepNumber,
2119
- " (",
2120
- cp.id,
2121
- ")"
2122
- ] }, cp.id)
2123
- }
2124
- );
2125
- var BrowsingEventDetailInput = ({ event, onBack }) => {
2126
- useInput((input, key) => {
2127
- if (input === "b" || key.escape) {
2128
- onBack();
1987
+ case "google": {
1988
+ const apiKey = env.GOOGLE_GENERATIVE_AI_API_KEY;
1989
+ if (!apiKey) throw new Error("GOOGLE_GENERATIVE_AI_API_KEY is not set");
1990
+ return {
1991
+ providerName: "google",
1992
+ apiKey,
1993
+ baseUrl: setting.baseUrl ?? env.GOOGLE_GENERATIVE_AI_BASE_URL,
1994
+ headers: setting.headers
1995
+ };
1996
+ }
1997
+ case "openai": {
1998
+ const apiKey = env.OPENAI_API_KEY;
1999
+ if (!apiKey) throw new Error("OPENAI_API_KEY is not set");
2000
+ return {
2001
+ providerName: "openai",
2002
+ apiKey,
2003
+ baseUrl: setting.baseUrl ?? env.OPENAI_BASE_URL,
2004
+ organization: setting.organization ?? env.OPENAI_ORGANIZATION,
2005
+ project: setting.project ?? env.OPENAI_PROJECT,
2006
+ name: setting.name,
2007
+ headers: setting.headers
2008
+ };
2009
+ }
2010
+ case "ollama": {
2011
+ return {
2012
+ providerName: "ollama",
2013
+ baseUrl: setting.baseUrl ?? env.OLLAMA_BASE_URL,
2014
+ headers: setting.headers
2015
+ };
2016
+ }
2017
+ case "azure-openai": {
2018
+ const apiKey = env.AZURE_API_KEY;
2019
+ if (!apiKey) throw new Error("AZURE_API_KEY is not set");
2020
+ const resourceName = setting.resourceName ?? env.AZURE_RESOURCE_NAME;
2021
+ const baseUrl = setting.baseUrl ?? env.AZURE_BASE_URL;
2022
+ if (!resourceName && !baseUrl) throw new Error("AZURE_RESOURCE_NAME or baseUrl is not set");
2023
+ return {
2024
+ providerName: "azure-openai",
2025
+ apiKey,
2026
+ resourceName,
2027
+ apiVersion: setting.apiVersion ?? env.AZURE_API_VERSION,
2028
+ baseUrl,
2029
+ headers: setting.headers,
2030
+ useDeploymentBasedUrls: setting.useDeploymentBasedUrls
2031
+ };
2032
+ }
2033
+ case "amazon-bedrock": {
2034
+ const accessKeyId = env.AWS_ACCESS_KEY_ID;
2035
+ const secretAccessKey = env.AWS_SECRET_ACCESS_KEY;
2036
+ const sessionToken = env.AWS_SESSION_TOKEN;
2037
+ if (!accessKeyId) throw new Error("AWS_ACCESS_KEY_ID is not set");
2038
+ if (!secretAccessKey) throw new Error("AWS_SECRET_ACCESS_KEY is not set");
2039
+ const region = setting.region ?? env.AWS_REGION;
2040
+ if (!region) throw new Error("AWS_REGION is not set");
2041
+ return {
2042
+ providerName: "amazon-bedrock",
2043
+ accessKeyId,
2044
+ secretAccessKey,
2045
+ region,
2046
+ sessionToken
2047
+ };
2048
+ }
2049
+ case "google-vertex": {
2050
+ return {
2051
+ providerName: "google-vertex",
2052
+ project: setting.project ?? env.GOOGLE_VERTEX_PROJECT,
2053
+ location: setting.location ?? env.GOOGLE_VERTEX_LOCATION,
2054
+ baseUrl: setting.baseUrl ?? env.GOOGLE_VERTEX_BASE_URL,
2055
+ headers: setting.headers
2056
+ };
2057
+ }
2058
+ case "deepseek": {
2059
+ const apiKey = env.DEEPSEEK_API_KEY;
2060
+ if (!apiKey) throw new Error("DEEPSEEK_API_KEY is not set");
2061
+ return {
2062
+ providerName: "deepseek",
2063
+ apiKey,
2064
+ baseUrl: setting.baseUrl ?? env.DEEPSEEK_BASE_URL,
2065
+ headers: setting.headers
2066
+ };
2129
2067
  }
2130
- });
2131
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
2132
- /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
2133
- /* @__PURE__ */ jsx(Text, { bold: true, children: "Event Detail" }),
2134
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2135
- " ",
2136
- KEY_HINTS.BACK
2137
- ] })
2138
- ] }),
2139
- /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [
2140
- /* @__PURE__ */ jsxs(Text, { children: [
2141
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Type: " }),
2142
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: event.type })
2143
- ] }),
2144
- /* @__PURE__ */ jsxs(Text, { children: [
2145
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Step: " }),
2146
- /* @__PURE__ */ jsx(Text, { children: event.stepNumber })
2147
- ] }),
2148
- /* @__PURE__ */ jsxs(Text, { children: [
2149
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Timestamp: " }),
2150
- /* @__PURE__ */ jsx(Text, { children: formatTimestamp(event.timestamp) })
2151
- ] }),
2152
- /* @__PURE__ */ jsxs(Text, { children: [
2153
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "ID: " }),
2154
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: event.id })
2155
- ] }),
2156
- /* @__PURE__ */ jsxs(Text, { children: [
2157
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Run ID: " }),
2158
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: event.runId })
2159
- ] })
2160
- ] })
2161
- ] });
2162
- };
2163
- var BrowsingEventsInput = ({
2164
- checkpoint,
2165
- events,
2166
- onEventSelect,
2167
- onBack
2168
- }) => /* @__PURE__ */ jsx(
2169
- ListBrowser,
2170
- {
2171
- title: `Events for Step ${checkpoint.stepNumber} ${KEY_HINTS.NAVIGATE} ${KEY_HINTS.SELECT} ${KEY_HINTS.BACK}`,
2172
- items: events,
2173
- onSelect: onEventSelect,
2174
- onBack,
2175
- emptyMessage: "No events found",
2176
- renderItem: (ev, isSelected) => /* @__PURE__ */ jsxs(Text, { color: isSelected ? "cyan" : "gray", children: [
2177
- isSelected ? ">" : " ",
2178
- " [",
2179
- ev.type,
2180
- "] Step ",
2181
- ev.stepNumber,
2182
- " (",
2183
- formatTimestamp(ev.timestamp),
2184
- ")"
2185
- ] }, ev.id)
2186
2068
  }
2187
- );
2188
- var TOOL_RESULT_EVENT_TYPES = /* @__PURE__ */ new Set(["resolveToolResults", "attemptCompletion"]);
2189
- function toolToActivity(toolCall, toolResult, reasoning, meta) {
2190
- const { skillName, toolName } = toolCall;
2191
- const baseActivity = skillName.startsWith(BASE_SKILL_PREFIX) ? createBaseToolActivity(toolName, toolCall, toolResult, reasoning) : createGeneralToolActivity(skillName, toolName, toolCall, toolResult, reasoning);
2192
- return {
2193
- ...baseActivity,
2194
- id: meta.id,
2195
- expertKey: meta.expertKey,
2196
- runId: meta.runId,
2197
- previousActivityId: meta.previousActivityId,
2198
- delegatedBy: meta.delegatedBy
2199
- };
2200
2069
  }
2201
- function createInitialActivityProcessState() {
2202
- return {
2203
- tools: /* @__PURE__ */ new Map(),
2204
- runStates: /* @__PURE__ */ new Map()
2205
- };
2070
+ function getAllJobs2() {
2071
+ return getAllJobs();
2206
2072
  }
2207
- function getOrCreateRunState(state, runId, expertKey) {
2208
- let runState = state.runStates.get(runId);
2209
- if (!runState) {
2210
- runState = {
2211
- expertKey,
2212
- queryLogged: false,
2213
- completionLogged: false,
2214
- isComplete: false,
2215
- pendingDelegateCount: 0
2216
- };
2217
- state.runStates.set(runId, runState);
2218
- }
2219
- return runState;
2073
+ function getAllRuns2() {
2074
+ return getAllRuns();
2220
2075
  }
2221
- var isRunEvent = (event) => "type" in event && "expertKey" in event;
2222
- var isCompleteStreamingReasoningEvent = (event) => "type" in event && event.type === "completeStreamingReasoning";
2223
- var isToolCallsEvent = (event) => event.type === "callTools" && "toolCalls" in event;
2224
- var isStopRunByDelegateEvent = (event) => event.type === "stopRunByDelegate" && "checkpoint" in event;
2225
- var isStopRunByInteractiveToolEvent = (event) => event.type === "stopRunByInteractiveTool" && "checkpoint" in event;
2226
- var isToolResultsEvent = (event) => event.type === "resolveToolResults" && "toolResults" in event;
2227
- var isToolResultEvent = (event) => TOOL_RESULT_EVENT_TYPES.has(event.type) && "toolResult" in event;
2228
- function wrapInGroupIfParallel(activities, reasoning, meta) {
2229
- if (activities.length <= 1) {
2230
- return activities;
2076
+ function getMostRecentRun() {
2077
+ const runs = getAllRuns2();
2078
+ if (runs.length === 0) {
2079
+ throw new Error("No runs found");
2231
2080
  }
2232
- const activitiesWithoutReasoning = activities.map((a) => {
2233
- const { reasoning: _, ...rest } = a;
2234
- return rest;
2235
- });
2236
- const group = {
2237
- type: "parallelGroup",
2238
- id: `parallel-${activities[0].id}`,
2239
- expertKey: meta.expertKey,
2240
- runId: meta.runId,
2241
- reasoning,
2242
- activities: activitiesWithoutReasoning
2243
- };
2244
- return [group];
2081
+ return runs[0];
2245
2082
  }
2246
- function processRunEventToActivity(state, event, addActivity) {
2247
- if (isCompleteStreamingReasoningEvent(event)) {
2248
- const reasoningEvent = event;
2249
- const { runId, text, expertKey } = reasoningEvent;
2250
- const runState2 = state.runStates.get(runId);
2251
- if (runState2) {
2252
- runState2.completedReasoning = text;
2253
- } else {
2254
- state.runStates.set(runId, {
2255
- expertKey,
2256
- queryLogged: false,
2257
- completionLogged: false,
2258
- isComplete: false,
2259
- pendingDelegateCount: 0,
2260
- completedReasoning: text
2261
- });
2262
- }
2263
- return;
2264
- }
2265
- if (!isRunEvent(event)) {
2266
- return;
2083
+ function getCheckpointsByJobId2(jobId) {
2084
+ return getCheckpointsByJobId(jobId);
2085
+ }
2086
+ function getMostRecentCheckpoint(jobId) {
2087
+ const targetJobId = jobId ?? getMostRecentRun().jobId;
2088
+ const checkpoints = getCheckpointsByJobId2(targetJobId);
2089
+ if (checkpoints.length === 0) {
2090
+ throw new Error(`No checkpoints found for job ${targetJobId}`);
2267
2091
  }
2268
- const runState = getOrCreateRunState(state, event.runId, event.expertKey);
2269
- if (event.type === "startRun") {
2270
- const startRunEvent = event;
2271
- const userMessage = startRunEvent.inputMessages.find((m) => m.type === "userMessage");
2272
- const queryText = userMessage?.contents?.find((c) => c.type === "textPart")?.text;
2273
- if (!runState.delegatedBy) {
2274
- const delegatedByInfo = startRunEvent.initialCheckpoint?.delegatedBy;
2275
- if (delegatedByInfo) {
2276
- runState.delegatedBy = {
2277
- expertKey: delegatedByInfo.expert.key,
2278
- runId: delegatedByInfo.runId
2279
- };
2280
- }
2281
- }
2282
- const isDelegationReturn = startRunEvent.initialCheckpoint?.status === "stoppedByDelegate" || startRunEvent.initialCheckpoint?.status === "stoppedByInteractiveTool";
2283
- if (queryText && !runState.queryLogged && !isDelegationReturn) {
2284
- const activityId = `query-${event.runId}`;
2285
- addActivity({
2286
- type: "query",
2287
- id: activityId,
2288
- expertKey: event.expertKey,
2289
- runId: event.runId,
2290
- previousActivityId: runState.lastActivityId,
2291
- delegatedBy: runState.delegatedBy,
2292
- text: queryText
2092
+ return checkpoints[checkpoints.length - 1];
2093
+ }
2094
+ function getRecentExperts(limit) {
2095
+ const runs = getAllRuns2();
2096
+ const expertMap = /* @__PURE__ */ new Map();
2097
+ for (const setting of runs) {
2098
+ const expertKey = setting.expertKey;
2099
+ if (!expertMap.has(expertKey) || expertMap.get(expertKey).lastUsed < setting.updatedAt) {
2100
+ expertMap.set(expertKey, {
2101
+ key: expertKey,
2102
+ name: expertKey,
2103
+ lastUsed: setting.updatedAt
2293
2104
  });
2294
- runState.lastActivityId = activityId;
2295
- runState.queryLogged = true;
2296
2105
  }
2297
- return;
2298
2106
  }
2299
- if (event.type === "resumeFromStop") {
2300
- if (!runState.delegatedBy) {
2301
- const resumeEvent = event;
2302
- const delegatedByInfo = resumeEvent.checkpoint?.delegatedBy;
2303
- if (delegatedByInfo) {
2304
- runState.delegatedBy = {
2305
- expertKey: delegatedByInfo.expert.key,
2306
- runId: delegatedByInfo.runId
2307
- };
2308
- }
2309
- }
2310
- return;
2107
+ return Array.from(expertMap.values()).sort((a, b) => b.lastUsed - a.lastUsed).slice(0, limit);
2108
+ }
2109
+ function getCheckpointById(jobId, checkpointId) {
2110
+ const checkpointPath = getCheckpointPath(jobId, checkpointId);
2111
+ if (!existsSync(checkpointPath)) {
2112
+ throw new Error(`Checkpoint ${checkpointId} not found in job ${jobId}`);
2311
2113
  }
2312
- if (event.type === "retry") {
2313
- const retryEvent = event;
2314
- const activityId = `retry-${event.id}`;
2315
- addActivity({
2316
- type: "retry",
2317
- id: activityId,
2318
- expertKey: event.expertKey,
2319
- runId: event.runId,
2320
- previousActivityId: runState.lastActivityId,
2321
- delegatedBy: runState.delegatedBy,
2322
- reasoning: runState.completedReasoning,
2323
- error: retryEvent.reason,
2324
- message: ""
2325
- });
2326
- runState.lastActivityId = activityId;
2327
- runState.completedReasoning = void 0;
2328
- return;
2114
+ const checkpoint = readFileSync(checkpointPath, "utf-8");
2115
+ return checkpointSchema.parse(JSON.parse(checkpoint));
2116
+ }
2117
+ function getCheckpointsWithDetails(jobId) {
2118
+ return getCheckpointsByJobId2(jobId).map((cp) => ({
2119
+ id: cp.id,
2120
+ runId: cp.runId,
2121
+ stepNumber: cp.stepNumber,
2122
+ contextWindowUsage: cp.contextWindowUsage ?? 0
2123
+ })).sort((a, b) => b.stepNumber - a.stepNumber);
2124
+ }
2125
+ function getAllEventContentsForJob(jobId, maxStepNumber) {
2126
+ const runIds = getRunIdsByJobId(jobId);
2127
+ const allEvents = [];
2128
+ for (const runId of runIds) {
2129
+ const events = getEventContents(jobId, runId, maxStepNumber);
2130
+ allEvents.push(...events);
2329
2131
  }
2330
- if (event.type === "completeRun") {
2331
- if (!runState.completionLogged) {
2332
- const text = event.text ?? "";
2333
- const activityId = `completion-${event.runId}`;
2334
- addActivity({
2335
- type: "complete",
2336
- id: activityId,
2337
- expertKey: event.expertKey,
2338
- runId: event.runId,
2339
- previousActivityId: runState.lastActivityId,
2340
- delegatedBy: runState.delegatedBy,
2341
- reasoning: runState.completedReasoning,
2342
- text
2343
- });
2344
- runState.lastActivityId = activityId;
2345
- runState.completionLogged = true;
2346
- runState.isComplete = true;
2347
- runState.completedReasoning = void 0;
2132
+ return allEvents.sort((a, b) => a.timestamp - b.timestamp);
2133
+ }
2134
+
2135
+ // ../../packages/tui/src/lib/context.ts
2136
+ var defaultProvider = "anthropic";
2137
+ var defaultModel = "claude-sonnet-4-5";
2138
+ async function resolveRunContext(input) {
2139
+ const perstackConfig = input.perstackConfig ?? await getPerstackConfig(input.configPath);
2140
+ let checkpoint;
2141
+ if (input.resumeFrom) {
2142
+ if (!input.continueJob) {
2143
+ throw new Error("--resume-from requires --continue-job");
2348
2144
  }
2349
- return;
2145
+ checkpoint = getCheckpointById(input.continueJob, input.resumeFrom);
2146
+ } else if (input.continueJob) {
2147
+ checkpoint = getMostRecentCheckpoint(input.continueJob);
2148
+ } else if (input.continue) {
2149
+ checkpoint = getMostRecentCheckpoint();
2350
2150
  }
2351
- if (event.type === "stopRunByError") {
2352
- const errorEvent = event;
2353
- const activityId = `error-${event.id}`;
2354
- addActivity({
2355
- type: "error",
2356
- id: activityId,
2357
- expertKey: event.expertKey,
2358
- runId: event.runId,
2359
- previousActivityId: runState.lastActivityId,
2360
- delegatedBy: runState.delegatedBy,
2361
- errorName: errorEvent.error.name,
2362
- error: errorEvent.error.message,
2363
- isRetryable: errorEvent.error.isRetryable
2364
- });
2365
- runState.lastActivityId = activityId;
2366
- runState.isComplete = true;
2367
- runState.completedReasoning = void 0;
2368
- return;
2151
+ if ((input.continue || input.continueJob || input.resumeFrom) && !checkpoint) {
2152
+ throw new Error("No checkpoint found");
2369
2153
  }
2370
- if (isToolCallsEvent(event)) {
2371
- for (const toolCall of event.toolCalls) {
2372
- if (!state.tools.has(toolCall.id)) {
2373
- state.tools.set(toolCall.id, { id: toolCall.id, toolCall, logged: false });
2374
- }
2375
- }
2154
+ if (checkpoint && input.expertKey && checkpoint.expert.key !== input.expertKey) {
2155
+ throw new Error(
2156
+ `Checkpoint expert key ${checkpoint.expert.key} does not match input expert key ${input.expertKey}`
2157
+ );
2376
2158
  }
2377
- if (isStopRunByDelegateEvent(event)) {
2378
- const reasoning = runState.completedReasoning;
2379
- const delegations = event.checkpoint.delegateTo ?? [];
2380
- runState.pendingDelegateCount += delegations.length;
2381
- const delegateActivities = [];
2382
- for (const delegation of delegations) {
2383
- const existingTool = state.tools.get(delegation.toolCallId);
2384
- if (!existingTool || !existingTool.logged) {
2385
- if (existingTool) {
2386
- existingTool.logged = true;
2159
+ const envPath = input.envPath && input.envPath.length > 0 ? input.envPath : perstackConfig.envPath ?? [".env", ".env.local"];
2160
+ const env = getEnv(envPath);
2161
+ const provider = input.provider ?? perstackConfig.provider?.providerName ?? defaultProvider;
2162
+ const model = input.model ?? perstackConfig.model ?? defaultModel;
2163
+ const providerConfig = getProviderConfig(provider, env, perstackConfig.provider);
2164
+ const experts = Object.fromEntries(
2165
+ Object.entries(perstackConfig.experts ?? {}).map(([name, expert]) => {
2166
+ return [
2167
+ name,
2168
+ {
2169
+ name,
2170
+ version: expert.version ?? "1.0.0",
2171
+ minRuntimeVersion: expert.minRuntimeVersion,
2172
+ description: expert.description,
2173
+ instruction: expert.instruction,
2174
+ skills: expert.skills,
2175
+ delegates: expert.delegates,
2176
+ tags: expert.tags
2387
2177
  }
2388
- const activityId = `delegate-${delegation.toolCallId}`;
2389
- delegateActivities.push({
2390
- type: "delegate",
2391
- id: activityId,
2392
- expertKey: event.expertKey,
2393
- runId: event.runId,
2394
- previousActivityId: runState.lastActivityId,
2395
- delegatedBy: runState.delegatedBy,
2396
- delegateExpertKey: delegation.expert.key,
2397
- query: delegation.query,
2398
- reasoning
2399
- });
2400
- runState.lastActivityId = activityId;
2401
- }
2402
- }
2403
- const wrapped = wrapInGroupIfParallel(delegateActivities, reasoning, {
2404
- expertKey: event.expertKey,
2405
- runId: event.runId,
2406
- delegatedBy: runState.delegatedBy
2407
- });
2408
- for (const item of wrapped) {
2409
- addActivity(item);
2410
- }
2411
- runState.completedReasoning = void 0;
2412
- return;
2178
+ ];
2179
+ })
2180
+ );
2181
+ return {
2182
+ perstackConfig,
2183
+ checkpoint,
2184
+ env,
2185
+ providerConfig,
2186
+ model,
2187
+ experts
2188
+ };
2189
+ }
2190
+
2191
+ // ../../packages/tui/src/lib/interactive.ts
2192
+ function parseInteractiveToolCallResult(query, checkpoint) {
2193
+ const lastMessage = checkpoint.messages[checkpoint.messages.length - 1];
2194
+ if (lastMessage.type !== "expertMessage") {
2195
+ throw new Error("Last message is not a expert message");
2413
2196
  }
2414
- if (isStopRunByInteractiveToolEvent(event)) {
2415
- const reasoning = runState.completedReasoning;
2416
- const pendingToolCalls = event.checkpoint.pendingToolCalls ?? [];
2417
- const interactiveActivities = [];
2418
- for (const toolCall of pendingToolCalls) {
2419
- const existingTool = state.tools.get(toolCall.id);
2420
- if (!existingTool || !existingTool.logged) {
2421
- if (existingTool) {
2422
- existingTool.logged = true;
2423
- }
2424
- const activityId = `interactive-${toolCall.id}`;
2425
- interactiveActivities.push({
2426
- type: "interactiveTool",
2427
- id: activityId,
2428
- expertKey: event.expertKey,
2429
- runId: event.runId,
2430
- previousActivityId: runState.lastActivityId,
2431
- delegatedBy: runState.delegatedBy,
2432
- skillName: toolCall.skillName,
2433
- toolName: toolCall.toolName,
2434
- args: toolCall.args,
2435
- reasoning
2436
- });
2437
- runState.lastActivityId = activityId;
2438
- }
2439
- }
2440
- const wrapped = wrapInGroupIfParallel(interactiveActivities, reasoning, {
2441
- expertKey: event.expertKey,
2442
- runId: event.runId,
2443
- delegatedBy: runState.delegatedBy
2444
- });
2445
- for (const item of wrapped) {
2446
- addActivity(item);
2447
- }
2448
- runState.completedReasoning = void 0;
2449
- return;
2197
+ const content = lastMessage.contents.find((c) => c.type === "toolCallPart");
2198
+ if (!content || content.type !== "toolCallPart") {
2199
+ throw new Error("Last message content is not a tool call part");
2450
2200
  }
2451
- if (isToolResultsEvent(event)) {
2452
- const reasoning = runState.completedReasoning;
2453
- const toolActivities = [];
2454
- for (const toolResult of event.toolResults) {
2455
- const tool = state.tools.get(toolResult.id);
2456
- if (tool && !tool.logged) {
2457
- const activityId = `action-${tool.id}`;
2458
- const activity = toolToActivity(tool.toolCall, toolResult, reasoning, {
2459
- id: activityId,
2460
- expertKey: event.expertKey,
2461
- runId: event.runId,
2462
- previousActivityId: runState.lastActivityId,
2463
- delegatedBy: runState.delegatedBy
2464
- });
2465
- toolActivities.push(activity);
2466
- runState.lastActivityId = activityId;
2467
- tool.logged = true;
2468
- }
2469
- }
2470
- if (toolActivities.length > 0) {
2471
- const wrapped = wrapInGroupIfParallel(toolActivities, reasoning, {
2472
- expertKey: event.expertKey,
2473
- runId: event.runId,
2474
- delegatedBy: runState.delegatedBy
2475
- });
2476
- for (const item of wrapped) {
2477
- addActivity(item);
2478
- }
2479
- runState.completedReasoning = void 0;
2201
+ const toolCallId = content.toolCallId;
2202
+ const toolName = content.toolName;
2203
+ const pendingToolCall = checkpoint.pendingToolCalls?.find((tc) => tc.id === toolCallId);
2204
+ const skillName = pendingToolCall?.skillName ?? "";
2205
+ return {
2206
+ interactiveToolCallResult: {
2207
+ toolCallId,
2208
+ toolName,
2209
+ skillName,
2210
+ text: query
2480
2211
  }
2481
- } else if (isToolResultEvent(event)) {
2482
- const { toolResult } = event;
2483
- const tool = state.tools.get(toolResult.id);
2484
- if (tool && !tool.logged) {
2485
- const activityId = `action-${tool.id}`;
2486
- const activity = toolToActivity(tool.toolCall, toolResult, runState.completedReasoning, {
2487
- id: activityId,
2488
- expertKey: event.expertKey,
2489
- runId: event.runId,
2490
- previousActivityId: runState.lastActivityId,
2491
- delegatedBy: runState.delegatedBy
2492
- });
2493
- addActivity(activity);
2494
- runState.lastActivityId = activityId;
2495
- tool.logged = true;
2496
- runState.completedReasoning = void 0;
2212
+ };
2213
+ }
2214
+ function loadLockfile(lockfilePath) {
2215
+ try {
2216
+ const content = readFileSync(lockfilePath, "utf-8");
2217
+ const parsed = TOML.parse(content);
2218
+ return parseWithFriendlyError(lockfileSchema, parsed, "perstack.lock");
2219
+ } catch {
2220
+ return null;
2221
+ }
2222
+ }
2223
+ function findLockfile(configPath) {
2224
+ return findLockfileRecursively(process.cwd());
2225
+ }
2226
+ function findLockfileRecursively(cwd) {
2227
+ const lockfilePath = path6.resolve(cwd, "perstack.lock");
2228
+ try {
2229
+ readFileSync(lockfilePath);
2230
+ return lockfilePath;
2231
+ } catch {
2232
+ if (cwd === path6.parse(cwd).root) {
2233
+ return null;
2497
2234
  }
2235
+ return findLockfileRecursively(path6.dirname(cwd));
2498
2236
  }
2499
2237
  }
2500
2238
 
2501
- // ../../packages/react/src/hooks/use-run.ts
2502
- var STREAMING_EVENT_TYPES = /* @__PURE__ */ new Set([
2503
- "startStreamingReasoning",
2504
- "streamReasoning",
2505
- "completeStreamingReasoning",
2506
- "startStreamingRunResult",
2507
- "streamRunResult",
2508
- "completeStreamingRunResult"
2509
- ]);
2510
- var isStreamingEvent = (event) => "type" in event && "expertKey" in event && STREAMING_EVENT_TYPES.has(event.type);
2511
- function processStreamingEvent(event, prevState) {
2512
- const { runId, expertKey } = event;
2513
- switch (event.type) {
2514
- case "startStreamingReasoning": {
2515
- return {
2516
- newState: {
2517
- ...prevState,
2518
- runs: {
2519
- ...prevState.runs,
2520
- [runId]: {
2521
- expertKey,
2522
- reasoning: "",
2523
- isReasoningActive: true
2524
- }
2525
- }
2526
- },
2527
- handled: true
2528
- };
2529
- }
2530
- case "streamReasoning": {
2531
- return {
2532
- newState: {
2533
- ...prevState,
2534
- runs: {
2535
- ...prevState.runs,
2536
- [runId]: {
2537
- ...prevState.runs[runId],
2538
- expertKey,
2539
- reasoning: (prevState.runs[runId]?.reasoning ?? "") + event.delta
2540
- }
2541
- }
2542
- },
2543
- handled: true
2544
- };
2545
- }
2546
- case "completeStreamingReasoning": {
2547
- return {
2548
- newState: {
2549
- ...prevState,
2550
- runs: {
2551
- ...prevState.runs,
2552
- [runId]: {
2553
- ...prevState.runs[runId],
2554
- expertKey,
2555
- isReasoningActive: false
2556
- }
2557
- }
2558
- },
2559
- handled: false
2560
- };
2561
- }
2562
- case "startStreamingRunResult": {
2563
- return {
2564
- newState: {
2565
- ...prevState,
2566
- runs: {
2567
- ...prevState.runs,
2568
- [runId]: {
2569
- expertKey,
2570
- reasoning: void 0,
2571
- isReasoningActive: false,
2572
- runResult: "",
2573
- isRunResultActive: true
2574
- }
2575
- }
2576
- },
2577
- handled: true
2578
- };
2239
+ // ../../packages/tui-components/src/utils/event-queue.ts
2240
+ var defaultErrorLogger = (message, error) => {
2241
+ console.error(message, error);
2242
+ };
2243
+ var EventQueue = class _EventQueue {
2244
+ static MAX_PENDING_EVENTS = 1e3;
2245
+ pendingEvents = [];
2246
+ handler = null;
2247
+ onError;
2248
+ errorLogger;
2249
+ constructor(options) {
2250
+ this.onError = options?.onError;
2251
+ this.errorLogger = options?.errorLogger ?? defaultErrorLogger;
2252
+ }
2253
+ setHandler(fn) {
2254
+ this.handler = fn;
2255
+ for (const event of this.pendingEvents) {
2256
+ this.safeHandle(event);
2579
2257
  }
2580
- case "streamRunResult": {
2581
- return {
2582
- newState: {
2583
- ...prevState,
2584
- runs: {
2585
- ...prevState.runs,
2586
- [runId]: {
2587
- ...prevState.runs[runId],
2588
- expertKey,
2589
- runResult: (prevState.runs[runId]?.runResult ?? "") + event.delta
2590
- }
2591
- }
2592
- },
2593
- handled: true
2594
- };
2258
+ this.pendingEvents.length = 0;
2259
+ }
2260
+ emit(event) {
2261
+ if (this.handler) {
2262
+ this.safeHandle(event);
2263
+ } else {
2264
+ if (this.pendingEvents.length >= _EventQueue.MAX_PENDING_EVENTS) {
2265
+ this.pendingEvents.shift();
2266
+ }
2267
+ this.pendingEvents.push(event);
2595
2268
  }
2596
- case "completeStreamingRunResult": {
2597
- return {
2598
- newState: {
2599
- ...prevState,
2600
- runs: {
2601
- ...prevState.runs,
2602
- [runId]: {
2603
- ...prevState.runs[runId],
2604
- expertKey,
2605
- isRunResultActive: false
2606
- }
2607
- }
2608
- },
2609
- handled: true
2610
- };
2269
+ }
2270
+ safeHandle(event) {
2271
+ try {
2272
+ this.handler?.(event);
2273
+ } catch (error) {
2274
+ if (this.onError) {
2275
+ this.onError(error);
2276
+ } else {
2277
+ this.errorLogger("EventQueue handler error:", error);
2278
+ }
2611
2279
  }
2612
- default:
2613
- return { newState: prevState, handled: false };
2614
2280
  }
2615
- }
2616
- function useRun() {
2617
- const [activities, setActivities] = useState([]);
2618
- const [streaming, setStreaming] = useState({ runs: {} });
2619
- const [eventCount, setEventCount] = useState(0);
2620
- const [isComplete, setIsComplete] = useState(false);
2621
- const stateRef = useRef(createInitialActivityProcessState());
2622
- const clearStreaming = useCallback(() => {
2623
- setStreaming({ runs: {} });
2624
- }, []);
2625
- const handleStreamingEvent = useCallback((event) => {
2626
- let handled = false;
2627
- setStreaming((prev) => {
2628
- const result = processStreamingEvent(event, prev);
2629
- handled = result.handled;
2630
- return result.newState;
2631
- });
2632
- return handled;
2633
- }, []);
2634
- const processEvent = useCallback((event) => {
2635
- const newActivities = [];
2636
- const addActivity = (activity) => newActivities.push(activity);
2637
- processRunEventToActivity(stateRef.current, event, addActivity);
2638
- if (newActivities.length > 0) {
2639
- setActivities((prev) => [...prev, ...newActivities]);
2281
+ };
2282
+ var InputAreaContext = createContext(null);
2283
+ var InputAreaProvider = InputAreaContext.Provider;
2284
+ var useInputAreaContext = () => {
2285
+ const context = useContext(InputAreaContext);
2286
+ if (!context) {
2287
+ throw new Error("useInputAreaContext must be used within InputAreaProvider");
2288
+ }
2289
+ return context;
2290
+ };
2291
+
2292
+ // ../../packages/tui-components/src/helpers.ts
2293
+ var truncateText = (text, maxLength) => {
2294
+ if (text.length <= maxLength) return text;
2295
+ return `${text.slice(0, maxLength)}...`;
2296
+ };
2297
+ var assertNever = (x) => {
2298
+ throw new Error(`Unexpected value: ${x}`);
2299
+ };
2300
+ var formatTimestamp = (timestamp) => new Date(timestamp).toLocaleString();
2301
+ var shortenPath = (fullPath, maxLength = 60) => {
2302
+ if (fullPath.length <= maxLength) return fullPath;
2303
+ const parts = fullPath.split("/");
2304
+ if (parts.length <= 2) return fullPath;
2305
+ const filename = parts[parts.length - 1] ?? "";
2306
+ const parentDir = parts[parts.length - 2] ?? "";
2307
+ const shortened = `.../${parentDir}/${filename}`;
2308
+ if (shortened.length <= maxLength) return shortened;
2309
+ return `.../${filename}`;
2310
+ };
2311
+ var summarizeOutput = (lines, maxLines) => {
2312
+ const filtered = lines.filter((l) => l.trim());
2313
+ const visible = filtered.slice(0, maxLines);
2314
+ const remaining = filtered.length - visible.length;
2315
+ return { visible, remaining };
2316
+ };
2317
+
2318
+ // ../../packages/tui-components/src/constants.ts
2319
+ var UI_CONSTANTS = {
2320
+ MAX_VISIBLE_LIST_ITEMS: 25,
2321
+ TRUNCATE_TEXT_MEDIUM: 80,
2322
+ TRUNCATE_TEXT_DEFAULT: 100};
2323
+ var RENDER_CONSTANTS = {
2324
+ EXEC_OUTPUT_MAX_LINES: 4,
2325
+ NEW_TODO_MAX_PREVIEW: 3,
2326
+ LIST_DIR_MAX_ITEMS: 4
2327
+ };
2328
+ var INDICATOR = {
2329
+ BULLET: "\u25CF",
2330
+ TREE: "\u2514",
2331
+ ELLIPSIS: "..."
2332
+ };
2333
+ var STOP_EVENT_TYPES = [
2334
+ "stopRunByInteractiveTool",
2335
+ "stopRunByDelegate",
2336
+ "stopRunByExceededMaxSteps"
2337
+ ];
2338
+ var KEY_BINDINGS = {
2339
+ NAVIGATE_UP: "\u2191",
2340
+ NAVIGATE_DOWN: "\u2193",
2341
+ SELECT: "Enter",
2342
+ BACK: "b",
2343
+ ESCAPE: "Esc",
2344
+ INPUT_MODE: "i",
2345
+ HISTORY: "h",
2346
+ NEW: "n",
2347
+ CHECKPOINTS: "c",
2348
+ EVENTS: "e",
2349
+ RESUME: "Enter"};
2350
+ var KEY_HINTS = {
2351
+ NAVIGATE: `${KEY_BINDINGS.NAVIGATE_UP}${KEY_BINDINGS.NAVIGATE_DOWN}:Navigate`,
2352
+ SELECT: `${KEY_BINDINGS.SELECT}:Select`,
2353
+ RESUME: `${KEY_BINDINGS.RESUME}:Resume`,
2354
+ BACK: `${KEY_BINDINGS.BACK}:Back`,
2355
+ CANCEL: `${KEY_BINDINGS.ESCAPE}:Cancel`,
2356
+ INPUT: `${KEY_BINDINGS.INPUT_MODE}:Input`,
2357
+ HISTORY: `${KEY_BINDINGS.HISTORY}:History`,
2358
+ NEW: `${KEY_BINDINGS.NEW}:New Run`,
2359
+ CHECKPOINTS: `${KEY_BINDINGS.CHECKPOINTS}:Checkpoints`,
2360
+ EVENTS: `${KEY_BINDINGS.EVENTS}:Events`};
2361
+ var useListNavigation = (options) => {
2362
+ const { items, onSelect, onBack } = options;
2363
+ const [selectedIndex, setSelectedIndex] = useState(0);
2364
+ useEffect(() => {
2365
+ if (selectedIndex >= items.length && items.length > 0) {
2366
+ setSelectedIndex(items.length - 1);
2640
2367
  }
2641
- const rootRunComplete = Array.from(stateRef.current.runStates.values()).some(
2642
- (rs) => rs.isComplete && !rs.delegatedBy
2643
- );
2644
- setIsComplete(rootRunComplete);
2645
- }, []);
2646
- const clearRunStreaming = useCallback((runId) => {
2647
- setStreaming((prev) => {
2648
- const { [runId]: _, ...rest } = prev.runs;
2649
- return { runs: rest };
2650
- });
2651
- }, []);
2652
- const addEvent = useCallback(
2653
- (event) => {
2654
- if (isStreamingEvent(event)) {
2655
- const handled = handleStreamingEvent(event);
2656
- if (handled) {
2657
- setEventCount((prev) => prev + 1);
2658
- return;
2659
- }
2368
+ }, [items.length, selectedIndex]);
2369
+ const handleNavigation = useCallback(
2370
+ (inputChar, key) => {
2371
+ if (key.upArrow && items.length > 0) {
2372
+ setSelectedIndex((prev) => Math.max(0, prev - 1));
2373
+ return true;
2660
2374
  }
2661
- if ("type" in event && "runId" in event && (event.type === "completeRun" || event.type === "stopRunByError")) {
2662
- clearRunStreaming(event.runId);
2375
+ if (key.downArrow && items.length > 0) {
2376
+ setSelectedIndex((prev) => Math.min(items.length - 1, prev + 1));
2377
+ return true;
2663
2378
  }
2664
- processEvent(event);
2665
- setEventCount((prev) => prev + 1);
2379
+ if (key.return && items[selectedIndex]) {
2380
+ onSelect?.(items[selectedIndex]);
2381
+ return true;
2382
+ }
2383
+ if ((key.escape || inputChar === "b") && onBack) {
2384
+ onBack();
2385
+ return true;
2386
+ }
2387
+ return false;
2666
2388
  },
2667
- [handleStreamingEvent, clearRunStreaming, processEvent]
2389
+ [items, selectedIndex, onSelect, onBack]
2668
2390
  );
2669
- const appendHistoricalEvents = useCallback((historicalEvents) => {
2670
- const newActivities = [];
2671
- const addActivity = (activity) => newActivities.push(activity);
2672
- for (const event of historicalEvents) {
2673
- processRunEventToActivity(stateRef.current, event, addActivity);
2674
- }
2675
- if (newActivities.length > 0) {
2676
- setActivities((prev) => [...prev, ...newActivities]);
2677
- }
2678
- setEventCount((prev) => prev + historicalEvents.length);
2679
- const rootRunComplete = Array.from(stateRef.current.runStates.values()).some(
2680
- (rs) => rs.isComplete && !rs.delegatedBy
2681
- );
2682
- setIsComplete(rootRunComplete);
2683
- }, []);
2684
- return {
2685
- activities,
2686
- streaming,
2687
- isComplete,
2688
- eventCount,
2689
- addEvent,
2690
- appendHistoricalEvents,
2691
- clearStreaming
2692
- };
2693
- }
2694
- var useRuntimeInfo = (options) => {
2695
- const [runtimeInfo, setRuntimeInfo] = useState({
2696
- status: "initializing",
2697
- runtimeVersion: options.initialConfig.runtimeVersion,
2698
- expertName: options.initialExpertName,
2699
- model: options.initialConfig.model,
2700
- maxSteps: options.initialConfig.maxSteps,
2701
- maxRetries: options.initialConfig.maxRetries,
2702
- timeout: options.initialConfig.timeout,
2703
- activeSkills: [],
2704
- contextWindowUsage: options.initialConfig.contextWindowUsage
2705
- });
2706
- const handleEvent = useCallback((event) => {
2707
- if (event.type === "initializeRuntime") {
2708
- setRuntimeInfo((prev) => ({
2709
- runtimeVersion: event.runtimeVersion,
2710
- expertName: event.expertName,
2711
- model: event.model,
2712
- maxSteps: event.maxSteps,
2713
- maxRetries: event.maxRetries,
2714
- timeout: event.timeout,
2715
- currentStep: 1,
2716
- status: "running",
2717
- query: event.query,
2718
- statusChangedAt: Date.now(),
2719
- activeSkills: [],
2720
- contextWindowUsage: prev.contextWindowUsage
2721
- }));
2722
- return { initialized: true };
2723
- }
2724
- if (event.type === "skillConnected") {
2725
- setRuntimeInfo((prev) => ({
2726
- ...prev,
2727
- activeSkills: [...prev.activeSkills, event.skillName]
2728
- }));
2729
- return null;
2730
- }
2731
- if (event.type === "skillDisconnected") {
2732
- setRuntimeInfo((prev) => ({
2733
- ...prev,
2734
- activeSkills: prev.activeSkills.filter((s) => s !== event.skillName)
2735
- }));
2736
- return null;
2737
- }
2738
- if ("stepNumber" in event) {
2739
- const isComplete = event.type === "completeRun";
2740
- const isStopped = STOP_EVENT_TYPES.includes(event.type);
2741
- const checkpoint = "nextCheckpoint" in event ? event.nextCheckpoint : "checkpoint" in event ? event.checkpoint : void 0;
2742
- setRuntimeInfo((prev) => ({
2743
- ...prev,
2744
- currentStep: event.stepNumber,
2745
- status: isComplete ? "completed" : isStopped ? "stopped" : "running",
2746
- ...isComplete || isStopped ? { statusChangedAt: Date.now() } : {},
2747
- ...checkpoint?.contextWindowUsage !== void 0 ? { contextWindowUsage: checkpoint.contextWindowUsage } : {}
2748
- }));
2749
- if (isComplete) return { completed: true };
2750
- if (isStopped) return { stopped: true };
2751
- }
2752
- return null;
2753
- }, []);
2754
- const setExpertName = useCallback((expertName) => {
2755
- setRuntimeInfo((prev) => ({ ...prev, expertName }));
2756
- }, []);
2757
- const setQuery = useCallback((query) => {
2758
- setRuntimeInfo((prev) => ({ ...prev, query }));
2759
- }, []);
2760
- const setCurrentStep = useCallback((step) => {
2761
- setRuntimeInfo((prev) => ({ ...prev, currentStep: step }));
2762
- }, []);
2763
- const setContextWindowUsage = useCallback((contextWindowUsage) => {
2764
- setRuntimeInfo((prev) => ({ ...prev, contextWindowUsage }));
2765
- }, []);
2766
- return {
2767
- runtimeInfo,
2768
- handleEvent,
2769
- setExpertName,
2770
- setQuery,
2771
- setCurrentStep,
2772
- setContextWindowUsage
2773
- };
2391
+ return { selectedIndex, handleNavigation };
2774
2392
  };
2775
- var useLatestRef = (value) => {
2776
- const ref = useRef(value);
2777
- useInsertionEffect(() => {
2778
- ref.current = value;
2779
- });
2780
- return ref;
2781
- };
2782
-
2783
- // ../../packages/tui-components/src/hooks/use-text-input.ts
2784
- var useTextInput = (options) => {
2785
- const [input, setInput] = useState("");
2786
- const inputRef = useLatestRef(input);
2787
- const onSubmitRef = useLatestRef(options.onSubmit);
2788
- const onCancelRef = useLatestRef(options.onCancel);
2789
- const handleInput = useCallback(
2790
- (inputChar, key) => {
2791
- if (key.escape) {
2792
- setInput("");
2793
- onCancelRef.current?.();
2794
- return;
2795
- }
2796
- if (key.return && inputRef.current.trim()) {
2797
- onSubmitRef.current(inputRef.current.trim());
2798
- setInput("");
2799
- return;
2800
- }
2801
- if (key.backspace || key.delete) {
2802
- setInput((prev) => prev.slice(0, -1));
2803
- return;
2804
- }
2805
- if (!key.ctrl && !key.meta && inputChar) {
2806
- setInput((prev) => prev + inputChar);
2807
- }
2808
- },
2809
- [inputRef, onSubmitRef, onCancelRef]
2810
- );
2811
- const reset = useCallback(() => {
2812
- setInput("");
2813
- }, []);
2814
- return { input, handleInput, reset };
2815
- };
2816
-
2817
- // ../../packages/tui-components/src/hooks/ui/use-expert-selector.ts
2818
- var useExpertSelector = (options) => {
2819
- const { experts, onExpertSelect, extraKeyHandler } = options;
2820
- const [inputMode, setInputMode] = useState(false);
2393
+ var ListBrowser = ({
2394
+ title,
2395
+ items,
2396
+ renderItem,
2397
+ onSelect,
2398
+ emptyMessage = "No items found",
2399
+ extraKeyHandler,
2400
+ onBack,
2401
+ maxItems = UI_CONSTANTS.MAX_VISIBLE_LIST_ITEMS
2402
+ }) => {
2821
2403
  const { selectedIndex, handleNavigation } = useListNavigation({
2822
- items: experts,
2823
- onSelect: (expert) => onExpertSelect(expert.key)
2404
+ items,
2405
+ onSelect,
2406
+ onBack
2824
2407
  });
2825
- const { input, handleInput, reset } = useTextInput({
2826
- onSubmit: (value) => {
2827
- onExpertSelect(value);
2828
- setInputMode(false);
2829
- },
2830
- onCancel: () => setInputMode(false)
2408
+ useInput((inputChar, key) => {
2409
+ if (extraKeyHandler?.(inputChar, key, items[selectedIndex])) return;
2410
+ handleNavigation(inputChar, key);
2831
2411
  });
2832
- const handleKeyInput = useCallback(
2833
- (inputChar, key) => {
2834
- if (inputMode) {
2835
- handleInput(inputChar, key);
2836
- return;
2837
- }
2838
- if (handleNavigation(inputChar, key)) return;
2839
- if (extraKeyHandler?.(inputChar, key)) return;
2840
- if (inputChar === "i") {
2841
- reset();
2842
- setInputMode(true);
2843
- }
2844
- },
2845
- [inputMode, handleInput, handleNavigation, extraKeyHandler, reset]
2846
- );
2847
- return {
2848
- inputMode,
2849
- input,
2850
- selectedIndex,
2851
- handleKeyInput
2852
- };
2853
- };
2854
- var ExpertList = ({
2855
- experts,
2856
- selectedIndex,
2857
- showSource = false,
2858
- inline = false,
2859
- maxItems
2860
- }) => {
2861
- const displayExperts = maxItems ? experts.slice(0, maxItems) : experts;
2862
- if (displayExperts.length === 0) {
2863
- return /* @__PURE__ */ jsx(Text, { color: "gray", children: "No experts found." });
2864
- }
2865
- const items = displayExperts.map((expert, index) => /* @__PURE__ */ jsxs(Text, { color: index === selectedIndex ? "cyan" : "gray", children: [
2866
- index === selectedIndex ? ">" : " ",
2867
- " ",
2868
- showSource ? expert.key : expert.name,
2869
- showSource && expert.source && /* @__PURE__ */ jsxs(Fragment, { children: [
2870
- " ",
2871
- /* @__PURE__ */ jsxs(Text, { color: expert.source === "configured" ? "green" : "yellow", children: [
2872
- "[",
2873
- expert.source === "configured" ? "config" : "recent",
2874
- "]"
2412
+ const { scrollOffset, displayItems } = useMemo(() => {
2413
+ const offset = Math.max(0, Math.min(selectedIndex - maxItems + 1, items.length - maxItems));
2414
+ return {
2415
+ scrollOffset: offset,
2416
+ displayItems: items.slice(offset, offset + maxItems)
2417
+ };
2418
+ }, [items, selectedIndex, maxItems]);
2419
+ const hasMoreAbove = scrollOffset > 0;
2420
+ const hasMoreBelow = scrollOffset + maxItems < items.length;
2421
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2422
+ /* @__PURE__ */ jsxs(Box, { children: [
2423
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: title }),
2424
+ items.length > maxItems && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
2425
+ " ",
2426
+ "(",
2427
+ selectedIndex + 1,
2428
+ "/",
2429
+ items.length,
2430
+ ")"
2875
2431
  ] })
2876
2432
  ] }),
2877
- inline ? " " : ""
2878
- ] }, expert.key));
2879
- return inline ? /* @__PURE__ */ jsx(Box, { children: items }) : /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: items });
2880
- };
2881
- var ExpertSelectorBase = ({
2882
- experts,
2883
- hint,
2884
- onExpertSelect,
2885
- showSource = false,
2886
- inline = false,
2887
- maxItems = UI_CONSTANTS.MAX_VISIBLE_LIST_ITEMS,
2888
- extraKeyHandler
2889
- }) => {
2890
- const { inputMode, input, selectedIndex, handleKeyInput } = useExpertSelector({
2891
- experts,
2892
- onExpertSelect,
2893
- extraKeyHandler
2894
- });
2895
- useInput(handleKeyInput);
2896
- return /* @__PURE__ */ jsxs(Box, { flexDirection: inline ? "row" : "column", children: [
2897
- !inputMode && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2898
- /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { color: "cyan", children: hint }) }),
2899
- /* @__PURE__ */ jsx(
2900
- ExpertList,
2901
- {
2902
- experts,
2903
- selectedIndex,
2904
- showSource,
2905
- inline,
2906
- maxItems
2907
- }
2908
- )
2909
- ] }),
2910
- inputMode && /* @__PURE__ */ jsxs(Box, { children: [
2911
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Expert: " }),
2912
- /* @__PURE__ */ jsx(Text, { color: "white", children: input }),
2913
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: "_" })
2914
- ] })
2433
+ hasMoreAbove && /* @__PURE__ */ jsx(Text, { color: "gray", children: INDICATOR.ELLIPSIS }),
2434
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: displayItems.length === 0 ? /* @__PURE__ */ jsx(Text, { color: "gray", children: emptyMessage }) : displayItems.map((item, index) => {
2435
+ const actualIndex = scrollOffset + index;
2436
+ return renderItem(item, actualIndex === selectedIndex, actualIndex);
2437
+ }) }),
2438
+ hasMoreBelow && /* @__PURE__ */ jsx(Text, { color: "gray", children: INDICATOR.ELLIPSIS })
2915
2439
  ] });
2916
2440
  };
2917
- var BrowsingExpertsInput = ({
2918
- experts,
2919
- onExpertSelect,
2920
- onSwitchToHistory
2921
- }) => {
2922
- const extraKeyHandler = useCallback(
2923
- (inputChar) => {
2924
- if (inputChar === "h") {
2925
- onSwitchToHistory();
2926
- return true;
2927
- }
2928
- return false;
2929
- },
2930
- [onSwitchToHistory]
2931
- );
2932
- const hint = `Experts ${KEY_HINTS.NAVIGATE} ${KEY_HINTS.SELECT} ${KEY_HINTS.INPUT} ${KEY_HINTS.HISTORY} ${KEY_HINTS.CANCEL}`;
2933
- return /* @__PURE__ */ jsx(
2934
- ExpertSelectorBase,
2935
- {
2936
- experts,
2937
- hint,
2938
- onExpertSelect,
2939
- showSource: true,
2940
- maxItems: UI_CONSTANTS.MAX_VISIBLE_LIST_ITEMS,
2941
- extraKeyHandler
2942
- }
2943
- );
2944
- };
2945
- var BrowsingHistoryInput = ({
2946
- jobs,
2947
- onJobSelect,
2948
- onJobResume,
2949
- onSwitchToExperts
2441
+ var BrowsingCheckpointsInput = ({
2442
+ job,
2443
+ checkpoints,
2444
+ onCheckpointSelect,
2445
+ onCheckpointResume,
2446
+ onBack,
2447
+ showEventsHint = true
2950
2448
  }) => /* @__PURE__ */ jsx(
2951
2449
  ListBrowser,
2952
2450
  {
2953
- title: `Job History ${KEY_HINTS.NAVIGATE} ${KEY_HINTS.RESUME} ${KEY_HINTS.CHECKPOINTS} ${KEY_HINTS.NEW}`,
2954
- items: jobs,
2955
- onSelect: onJobResume,
2956
- emptyMessage: "No jobs found. Press n to start a new job.",
2451
+ title: `Checkpoints for ${job.expertKey} ${KEY_HINTS.NAVIGATE} ${KEY_HINTS.RESUME} ${showEventsHint ? KEY_HINTS.EVENTS : ""} ${KEY_HINTS.BACK}`.trim(),
2452
+ items: checkpoints,
2453
+ onSelect: onCheckpointResume,
2454
+ onBack,
2455
+ emptyMessage: "No checkpoints found",
2957
2456
  extraKeyHandler: (char, _key, selected) => {
2958
- if (char === "c" && selected) {
2959
- onJobSelect(selected);
2960
- return true;
2961
- }
2962
- if (char === "n") {
2963
- onSwitchToExperts();
2457
+ if (char === "e" && selected) {
2458
+ onCheckpointSelect(selected);
2964
2459
  return true;
2965
2460
  }
2966
2461
  return false;
2967
2462
  },
2968
- renderItem: (job, isSelected) => /* @__PURE__ */ jsxs(Text, { color: isSelected ? "cyan" : "gray", children: [
2463
+ renderItem: (cp, isSelected) => /* @__PURE__ */ jsxs(Text, { color: isSelected ? "cyan" : "gray", children: [
2969
2464
  isSelected ? ">" : " ",
2970
- " ",
2971
- job.expertKey,
2972
- " - ",
2973
- job.totalSteps,
2974
- " steps (",
2975
- job.jobId,
2976
- ") (",
2977
- formatTimestamp(job.startedAt),
2465
+ " Step ",
2466
+ cp.stepNumber,
2467
+ " (",
2468
+ cp.id,
2978
2469
  ")"
2979
- ] }, job.jobId)
2470
+ ] }, cp.id)
2980
2471
  }
2981
2472
  );
2982
- var BrowserRouter = ({ inputState, showEventsHint = true }) => {
2983
- const ctx = useInputAreaContext();
2984
- const handleEventSelect = useCallback(
2985
- (event) => {
2986
- if (inputState.type === "browsingEvents") {
2987
- ctx.onEventSelect(inputState, event);
2988
- }
2989
- },
2990
- [ctx.onEventSelect, inputState]
2991
- );
2992
- switch (inputState.type) {
2993
- case "browsingHistory":
2994
- return /* @__PURE__ */ jsx(
2995
- BrowsingHistoryInput,
2996
- {
2997
- jobs: inputState.jobs,
2998
- onJobSelect: ctx.onJobSelect,
2999
- onJobResume: ctx.onJobResume,
3000
- onSwitchToExperts: ctx.onSwitchToExperts
3001
- }
3002
- );
3003
- case "browsingExperts":
3004
- return /* @__PURE__ */ jsx(
3005
- BrowsingExpertsInput,
3006
- {
3007
- experts: inputState.experts,
3008
- onExpertSelect: ctx.onExpertSelect,
3009
- onSwitchToHistory: ctx.onSwitchToHistory
3010
- }
3011
- );
3012
- case "browsingCheckpoints":
3013
- return /* @__PURE__ */ jsx(
3014
- BrowsingCheckpointsInput,
3015
- {
3016
- job: inputState.job,
3017
- checkpoints: inputState.checkpoints,
3018
- onCheckpointSelect: ctx.onCheckpointSelect,
3019
- onCheckpointResume: ctx.onCheckpointResume,
3020
- onBack: ctx.onBack,
3021
- showEventsHint
3022
- }
3023
- );
3024
- case "browsingEvents":
3025
- return /* @__PURE__ */ jsx(
3026
- BrowsingEventsInput,
3027
- {
3028
- checkpoint: inputState.checkpoint,
3029
- events: inputState.events,
3030
- onEventSelect: handleEventSelect,
3031
- onBack: ctx.onBack
3032
- }
3033
- );
3034
- case "browsingEventDetail":
3035
- return /* @__PURE__ */ jsx(BrowsingEventDetailInput, { event: inputState.selectedEvent, onBack: ctx.onBack });
3036
- default:
3037
- return assertNever(inputState);
3038
- }
3039
- };
3040
- var ActionRowSimple = ({
3041
- indicatorColor,
3042
- text,
3043
- textDimColor = false
3044
- }) => /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
3045
- /* @__PURE__ */ jsx(Box, { paddingRight: 1, children: /* @__PURE__ */ jsx(Text, { color: indicatorColor, children: INDICATOR.BULLET }) }),
3046
- /* @__PURE__ */ jsx(Text, { color: "white", dimColor: textDimColor, children: text })
3047
- ] }) });
3048
- var ActionRow = ({ indicatorColor, label, summary, children }) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3049
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
3050
- /* @__PURE__ */ jsx(Text, { color: indicatorColor, children: INDICATOR.BULLET }),
3051
- /* @__PURE__ */ jsx(Text, { color: "white", children: label }),
3052
- summary && /* @__PURE__ */ jsx(Text, { color: "white", dimColor: true, children: summary })
3053
- ] }),
3054
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
3055
- /* @__PURE__ */ jsx(Box, { paddingRight: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: INDICATOR.TREE }) }),
3056
- children
3057
- ] })
3058
- ] });
3059
- var CheckpointActionRow = ({ action }) => {
3060
- if (action.type === "parallelGroup") {
3061
- return renderParallelGroup(action);
3062
- }
3063
- const actionElement = renderAction(action);
3064
- if (!actionElement) return null;
3065
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3066
- action.reasoning && renderReasoning(action.reasoning),
3067
- actionElement
2473
+ var BrowsingEventDetailInput = ({ event, onBack }) => {
2474
+ useInput((input, key) => {
2475
+ if (input === "b" || key.escape) {
2476
+ onBack();
2477
+ }
2478
+ });
2479
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
2480
+ /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
2481
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Event Detail" }),
2482
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2483
+ " ",
2484
+ KEY_HINTS.BACK
2485
+ ] })
2486
+ ] }),
2487
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [
2488
+ /* @__PURE__ */ jsxs(Text, { children: [
2489
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Type: " }),
2490
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: event.type })
2491
+ ] }),
2492
+ /* @__PURE__ */ jsxs(Text, { children: [
2493
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Step: " }),
2494
+ /* @__PURE__ */ jsx(Text, { children: event.stepNumber })
2495
+ ] }),
2496
+ /* @__PURE__ */ jsxs(Text, { children: [
2497
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Timestamp: " }),
2498
+ /* @__PURE__ */ jsx(Text, { children: formatTimestamp(event.timestamp) })
2499
+ ] }),
2500
+ /* @__PURE__ */ jsxs(Text, { children: [
2501
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "ID: " }),
2502
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: event.id })
2503
+ ] }),
2504
+ /* @__PURE__ */ jsxs(Text, { children: [
2505
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Run ID: " }),
2506
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: event.runId })
2507
+ ] })
2508
+ ] })
3068
2509
  ] });
3069
2510
  };
3070
- function renderParallelGroup(group) {
3071
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3072
- group.reasoning && renderReasoning(group.reasoning),
3073
- group.activities.map((activity, index) => {
3074
- const actionElement = renderAction(activity);
3075
- if (!actionElement) return null;
3076
- return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: actionElement }, activity.id || `${activity.type}-${index}`);
3077
- })
3078
- ] });
2511
+ var BrowsingEventsInput = ({
2512
+ checkpoint,
2513
+ events,
2514
+ onEventSelect,
2515
+ onBack
2516
+ }) => /* @__PURE__ */ jsx(
2517
+ ListBrowser,
2518
+ {
2519
+ title: `Events for Step ${checkpoint.stepNumber} ${KEY_HINTS.NAVIGATE} ${KEY_HINTS.SELECT} ${KEY_HINTS.BACK}`,
2520
+ items: events,
2521
+ onSelect: onEventSelect,
2522
+ onBack,
2523
+ emptyMessage: "No events found",
2524
+ renderItem: (ev, isSelected) => /* @__PURE__ */ jsxs(Text, { color: isSelected ? "cyan" : "gray", children: [
2525
+ isSelected ? ">" : " ",
2526
+ " [",
2527
+ ev.type,
2528
+ "] Step ",
2529
+ ev.stepNumber,
2530
+ " (",
2531
+ formatTimestamp(ev.timestamp),
2532
+ ")"
2533
+ ] }, ev.id)
2534
+ }
2535
+ );
2536
+ var TOOL_RESULT_EVENT_TYPES = /* @__PURE__ */ new Set(["resolveToolResults", "attemptCompletion"]);
2537
+ function toolToActivity(toolCall, toolResult, reasoning, meta) {
2538
+ const { skillName, toolName } = toolCall;
2539
+ const baseActivity = skillName.startsWith(BASE_SKILL_PREFIX) ? createBaseToolActivity(toolName, toolCall, toolResult, reasoning) : createGeneralToolActivity(skillName, toolName, toolCall, toolResult, reasoning);
2540
+ return {
2541
+ ...baseActivity,
2542
+ id: meta.id,
2543
+ expertKey: meta.expertKey,
2544
+ runId: meta.runId,
2545
+ previousActivityId: meta.previousActivityId,
2546
+ delegatedBy: meta.delegatedBy
2547
+ };
3079
2548
  }
3080
- function renderReasoning(text) {
3081
- const lines = text.split("\n");
3082
- 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}`)) }) });
2549
+ function createInitialActivityProcessState() {
2550
+ return {
2551
+ tools: /* @__PURE__ */ new Map(),
2552
+ runStates: /* @__PURE__ */ new Map()
2553
+ };
3083
2554
  }
3084
- function renderAction(action) {
3085
- const color = action.type === "error" || "error" in action && action.error ? "red" : "green";
3086
- switch (action.type) {
3087
- case "query":
3088
- return renderQuery(action.text, action.runId);
3089
- case "retry":
3090
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "yellow", label: "Retry", children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: action.message || action.error }) });
3091
- case "complete":
3092
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "green", label: "Run Results", children: /* @__PURE__ */ jsx(Text, { children: action.text }) });
3093
- case "attemptCompletion": {
3094
- if (action.error) {
3095
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "red", label: "Completion Failed", children: /* @__PURE__ */ jsx(Text, { color: "red", children: action.error }) });
3096
- }
3097
- const remaining = action.remainingTodos?.filter((t) => !t.completed) ?? [];
3098
- if (remaining.length > 0) {
3099
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "yellow", label: "Completion Blocked", children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
3100
- remaining.length,
3101
- " remaining task",
3102
- remaining.length > 1 ? "s" : ""
3103
- ] }) });
3104
- }
3105
- return /* @__PURE__ */ jsx(ActionRowSimple, { indicatorColor: "green", text: "Completion Accepted" });
3106
- }
3107
- case "todo":
3108
- return renderTodo(action, color);
3109
- case "clearTodo":
3110
- return /* @__PURE__ */ jsx(ActionRowSimple, { indicatorColor: color, text: "Todo Cleared" });
3111
- case "readTextFile":
3112
- return renderReadTextFile(action, color);
3113
- case "writeTextFile":
3114
- return renderWriteTextFile(action, color);
3115
- case "editTextFile":
3116
- return renderEditTextFile(action, color);
3117
- case "appendTextFile":
3118
- return renderAppendTextFile(action, color);
3119
- case "deleteFile":
3120
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Delete", summary: shortenPath(action.path), children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Removed" }) });
3121
- case "deleteDirectory":
3122
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Delete Dir", summary: shortenPath(action.path), children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
3123
- "Removed",
3124
- action.recursive ? " (recursive)" : ""
3125
- ] }) });
3126
- case "moveFile":
3127
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Move", summary: shortenPath(action.source), children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
3128
- "\u2192 ",
3129
- shortenPath(action.destination, 30)
3130
- ] }) });
3131
- case "createDirectory":
3132
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Mkdir", summary: shortenPath(action.path), children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Created" }) });
3133
- case "listDirectory":
3134
- return renderListDirectory(action, color);
3135
- case "getFileInfo":
3136
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Info", summary: shortenPath(action.path), children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Fetched" }) });
3137
- case "readPdfFile":
3138
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "PDF", summary: shortenPath(action.path), children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Read" }) });
3139
- case "readImageFile":
3140
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Image", summary: shortenPath(action.path), children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Read" }) });
3141
- case "exec":
3142
- return renderExec(action, color);
3143
- case "delegate":
3144
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "yellow", label: action.delegateExpertKey, children: /* @__PURE__ */ jsx(
3145
- Text,
3146
- {
3147
- dimColor: true,
3148
- children: `{"query":"${truncateText(action.query, UI_CONSTANTS.TRUNCATE_TEXT_MEDIUM)}"}`
3149
- }
3150
- ) });
3151
- case "delegationComplete":
3152
- return /* @__PURE__ */ jsx(
3153
- ActionRowSimple,
3154
- {
3155
- indicatorColor: "green",
3156
- text: `Delegation Complete (${action.count} delegate${action.count > 1 ? "s" : ""} returned)`
3157
- }
3158
- );
3159
- case "interactiveTool":
3160
- 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) }) });
3161
- case "generalTool":
3162
- 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) }) });
3163
- case "error":
3164
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "red", label: action.errorName ?? "Error", children: /* @__PURE__ */ jsx(Text, { color: "red", children: action.error ?? "Unknown error" }) });
3165
- default: {
3166
- return null;
2555
+ function getOrCreateRunState(state, runId, expertKey) {
2556
+ let runState = state.runStates.get(runId);
2557
+ if (!runState) {
2558
+ runState = {
2559
+ expertKey,
2560
+ queryLogged: false,
2561
+ completionLogged: false,
2562
+ isComplete: false,
2563
+ pendingDelegateCount: 0
2564
+ };
2565
+ state.runStates.set(runId, runState);
2566
+ }
2567
+ return runState;
2568
+ }
2569
+ var isRunEvent = (event) => "type" in event && "expertKey" in event;
2570
+ var isCompleteStreamingReasoningEvent = (event) => "type" in event && event.type === "completeStreamingReasoning";
2571
+ var isToolCallsEvent = (event) => event.type === "callTools" && "toolCalls" in event;
2572
+ var isStopRunByDelegateEvent = (event) => event.type === "stopRunByDelegate" && "checkpoint" in event;
2573
+ var isStopRunByInteractiveToolEvent = (event) => event.type === "stopRunByInteractiveTool" && "checkpoint" in event;
2574
+ var isToolResultsEvent = (event) => event.type === "resolveToolResults" && "toolResults" in event;
2575
+ var isToolResultEvent = (event) => TOOL_RESULT_EVENT_TYPES.has(event.type) && "toolResult" in event;
2576
+ function wrapInGroupIfParallel(activities, reasoning, meta) {
2577
+ if (activities.length <= 1) {
2578
+ return activities;
2579
+ }
2580
+ const activitiesWithoutReasoning = activities.map((a) => {
2581
+ const { reasoning: _, ...rest } = a;
2582
+ return rest;
2583
+ });
2584
+ const group = {
2585
+ type: "parallelGroup",
2586
+ id: `parallel-${activities[0].id}`,
2587
+ expertKey: meta.expertKey,
2588
+ runId: meta.runId,
2589
+ reasoning,
2590
+ activities: activitiesWithoutReasoning
2591
+ };
2592
+ return [group];
2593
+ }
2594
+ function processRunEventToActivity(state, event, addActivity) {
2595
+ if (isCompleteStreamingReasoningEvent(event)) {
2596
+ const reasoningEvent = event;
2597
+ const { runId, text, expertKey } = reasoningEvent;
2598
+ const runState2 = state.runStates.get(runId);
2599
+ if (runState2) {
2600
+ runState2.completedReasoning = text;
2601
+ } else {
2602
+ state.runStates.set(runId, {
2603
+ expertKey,
2604
+ queryLogged: false,
2605
+ completionLogged: false,
2606
+ isComplete: false,
2607
+ pendingDelegateCount: 0,
2608
+ completedReasoning: text
2609
+ });
3167
2610
  }
2611
+ return;
3168
2612
  }
3169
- }
3170
- function renderTodo(action, color) {
3171
- const { newTodos, completedTodos, todos } = action;
3172
- const hasNewTodos = newTodos && newTodos.length > 0;
3173
- const hasCompletedTodos = completedTodos && completedTodos.length > 0;
3174
- if (!hasNewTodos && !hasCompletedTodos) {
3175
- return /* @__PURE__ */ jsx(ActionRowSimple, { indicatorColor: color, text: "Todo No changes" });
2613
+ if (!isRunEvent(event)) {
2614
+ return;
3176
2615
  }
3177
- const labelParts = [];
3178
- if (hasNewTodos) {
3179
- labelParts.push(`Added ${newTodos.length} task${newTodos.length > 1 ? "s" : ""}`);
2616
+ const runState = getOrCreateRunState(state, event.runId, event.expertKey);
2617
+ if (event.type === "startRun") {
2618
+ const startRunEvent = event;
2619
+ const userMessage = startRunEvent.inputMessages.find((m) => m.type === "userMessage");
2620
+ const queryText = userMessage?.contents?.find((c) => c.type === "textPart")?.text;
2621
+ if (!runState.delegatedBy) {
2622
+ const delegatedByInfo = startRunEvent.initialCheckpoint?.delegatedBy;
2623
+ if (delegatedByInfo) {
2624
+ runState.delegatedBy = {
2625
+ expertKey: delegatedByInfo.expert.key,
2626
+ runId: delegatedByInfo.runId
2627
+ };
2628
+ }
2629
+ }
2630
+ const isDelegationReturn = startRunEvent.initialCheckpoint?.status === "stoppedByDelegate" || startRunEvent.initialCheckpoint?.status === "stoppedByInteractiveTool";
2631
+ if (queryText && !runState.queryLogged && !isDelegationReturn) {
2632
+ const activityId = `query-${event.runId}`;
2633
+ addActivity({
2634
+ type: "query",
2635
+ id: activityId,
2636
+ expertKey: event.expertKey,
2637
+ runId: event.runId,
2638
+ previousActivityId: runState.lastActivityId,
2639
+ delegatedBy: runState.delegatedBy,
2640
+ text: queryText
2641
+ });
2642
+ runState.lastActivityId = activityId;
2643
+ runState.queryLogged = true;
2644
+ }
2645
+ return;
3180
2646
  }
3181
- if (hasCompletedTodos) {
3182
- labelParts.push(
3183
- `Completed ${completedTodos.length} task${completedTodos.length > 1 ? "s" : ""}`
3184
- );
2647
+ if (event.type === "resumeFromStop") {
2648
+ if (!runState.delegatedBy) {
2649
+ const resumeEvent = event;
2650
+ const delegatedByInfo = resumeEvent.checkpoint?.delegatedBy;
2651
+ if (delegatedByInfo) {
2652
+ runState.delegatedBy = {
2653
+ expertKey: delegatedByInfo.expert.key,
2654
+ runId: delegatedByInfo.runId
2655
+ };
2656
+ }
2657
+ }
2658
+ return;
3185
2659
  }
3186
- const label = `Todo ${labelParts.join(", ")}`;
3187
- const completedTitles = hasCompletedTodos ? completedTodos.map((id) => todos.find((t) => t.id === id)?.title).filter((t) => t !== void 0) : [];
3188
- if (!hasNewTodos && completedTitles.length === 0) {
3189
- return /* @__PURE__ */ jsx(ActionRowSimple, { indicatorColor: color, text: label });
2660
+ if (event.type === "retry") {
2661
+ const retryEvent = event;
2662
+ const activityId = `retry-${event.id}`;
2663
+ addActivity({
2664
+ type: "retry",
2665
+ id: activityId,
2666
+ expertKey: event.expertKey,
2667
+ runId: event.runId,
2668
+ previousActivityId: runState.lastActivityId,
2669
+ delegatedBy: runState.delegatedBy,
2670
+ reasoning: runState.completedReasoning,
2671
+ error: retryEvent.reason,
2672
+ message: ""
2673
+ });
2674
+ runState.lastActivityId = activityId;
2675
+ runState.completedReasoning = void 0;
2676
+ return;
3190
2677
  }
3191
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label, children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3192
- hasNewTodos && newTodos.slice(0, RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW).map((todo, idx) => /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
3193
- "\u25CB ",
3194
- todo
3195
- ] }, `todo-${idx}`)),
3196
- hasNewTodos && newTodos.length > RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
3197
- "... +",
3198
- newTodos.length - RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW,
3199
- " more"
3200
- ] }),
3201
- completedTitles.slice(0, RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW).map((title, idx) => /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
3202
- "\u2713 ",
3203
- title
3204
- ] }, `completed-${idx}`)),
3205
- completedTitles.length > RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
3206
- "... +",
3207
- completedTitles.length - RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW,
3208
- " more"
3209
- ] })
3210
- ] }) });
3211
- }
3212
- function renderReadTextFile(action, color) {
3213
- const { path: path6, content, from, to } = action;
3214
- const lineRange = from !== void 0 && to !== void 0 ? `#${from}-${to}` : "";
3215
- const lines = content?.split("\n") ?? [];
3216
- return /* @__PURE__ */ jsx(
3217
- ActionRow,
3218
- {
3219
- indicatorColor: color,
3220
- label: "Read Text File",
3221
- summary: `${shortenPath(path6)}${lineRange}`,
3222
- 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}`)) })
2678
+ if (event.type === "completeRun") {
2679
+ if (!runState.completionLogged) {
2680
+ const text = event.text ?? "";
2681
+ const activityId = `completion-${event.runId}`;
2682
+ addActivity({
2683
+ type: "complete",
2684
+ id: activityId,
2685
+ expertKey: event.expertKey,
2686
+ runId: event.runId,
2687
+ previousActivityId: runState.lastActivityId,
2688
+ delegatedBy: runState.delegatedBy,
2689
+ reasoning: runState.completedReasoning,
2690
+ text
2691
+ });
2692
+ runState.lastActivityId = activityId;
2693
+ runState.completionLogged = true;
2694
+ runState.isComplete = true;
2695
+ runState.completedReasoning = void 0;
3223
2696
  }
3224
- );
3225
- }
3226
- function renderWriteTextFile(action, color) {
3227
- const { path: path6, text } = action;
3228
- const lines = text.split("\n");
3229
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Write Text File", summary: shortenPath(path6), children: /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: lines.map((line, idx) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
3230
- /* @__PURE__ */ jsx(Text, { color: "green", dimColor: true, children: "+" }),
3231
- /* @__PURE__ */ jsx(Text, { color: "white", dimColor: true, children: line })
3232
- ] }, `write-${idx}`)) }) });
3233
- }
3234
- function renderEditTextFile(action, color) {
3235
- const { path: path6, oldText, newText } = action;
3236
- const oldLines = oldText.split("\n");
3237
- const newLines = newText.split("\n");
3238
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Edit Text File", summary: shortenPath(path6), children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3239
- oldLines.map((line, idx) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
3240
- /* @__PURE__ */ jsx(Text, { color: "red", dimColor: true, children: "-" }),
3241
- /* @__PURE__ */ jsx(Text, { color: "white", dimColor: true, children: line })
3242
- ] }, `old-${idx}`)),
3243
- newLines.map((line, idx) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
3244
- /* @__PURE__ */ jsx(Text, { color: "green", dimColor: true, children: "+" }),
3245
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: line })
3246
- ] }, `new-${idx}`))
3247
- ] }) });
3248
- }
3249
- function renderAppendTextFile(action, color) {
3250
- const { path: path6, text } = action;
3251
- const lines = text.split("\n");
3252
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Append Text File", summary: shortenPath(path6), children: /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: lines.map((line, idx) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
3253
- /* @__PURE__ */ jsx(Text, { color: "green", dimColor: true, children: "+" }),
3254
- /* @__PURE__ */ jsx(Text, { color: "white", dimColor: true, children: line })
3255
- ] }, `append-${idx}`)) }) });
3256
- }
3257
- function renderListDirectory(action, color) {
3258
- const { path: path6, items } = action;
3259
- const itemLines = items?.map((item) => `${item.type === "directory" ? "\u{1F4C1}" : "\u{1F4C4}"} ${item.name}`) ?? [];
3260
- const { visible, remaining } = summarizeOutput(itemLines, RENDER_CONSTANTS.LIST_DIR_MAX_ITEMS);
3261
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "List", summary: shortenPath(path6), children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3262
- visible.map((line, idx) => /* @__PURE__ */ jsx(Text, { dimColor: true, children: truncateText(line, UI_CONSTANTS.TRUNCATE_TEXT_DEFAULT) }, `dir-${idx}`)),
3263
- remaining > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
3264
- "... +",
3265
- remaining,
3266
- " more"
3267
- ] })
3268
- ] }) });
3269
- }
3270
- function renderExec(action, color) {
3271
- const { command, args, cwd, output } = action;
3272
- const cwdPart = cwd ? ` ${shortenPath(cwd, 40)}` : "";
3273
- const cmdLine = truncateText(
3274
- `${command} ${args.join(" ")}${cwdPart}`,
3275
- UI_CONSTANTS.TRUNCATE_TEXT_DEFAULT
3276
- );
3277
- const outputLines = output?.split("\n") ?? [];
3278
- const { visible, remaining } = summarizeOutput(
3279
- outputLines,
3280
- RENDER_CONSTANTS.EXEC_OUTPUT_MAX_LINES
3281
- );
3282
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: `Bash ${cmdLine}`, children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3283
- visible.map((line, idx) => /* @__PURE__ */ jsx(Text, { dimColor: true, children: truncateText(line, UI_CONSTANTS.TRUNCATE_TEXT_DEFAULT) }, `out-${idx}`)),
3284
- remaining > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
3285
- "... +",
3286
- remaining,
3287
- " more"
3288
- ] })
3289
- ] }) });
3290
- }
3291
- function renderQuery(text, runId) {
3292
- const lines = text.split("\n");
3293
- const shortRunId = runId.slice(0, 8);
3294
- 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}`)) }) });
3295
- }
3296
- var RunSetting = ({
3297
- info,
3298
- eventCount,
3299
- isEditing,
3300
- expertName,
3301
- onQuerySubmit
3302
- }) => {
3303
- const { input, handleInput } = useTextInput({
3304
- onSubmit: onQuerySubmit ?? (() => {
3305
- })
3306
- });
3307
- useInput(handleInput, { isActive: isEditing });
3308
- const displayExpertName = expertName ?? info.expertName;
3309
- const skills = info.activeSkills.length > 0 ? info.activeSkills.join(", ") : "";
3310
- const step = info.currentStep !== void 0 ? String(info.currentStep) : "";
3311
- const usagePercent = (info.contextWindowUsage * 100).toFixed(1);
3312
- return /* @__PURE__ */ jsxs(
3313
- Box,
3314
- {
3315
- flexDirection: "column",
3316
- borderStyle: "single",
3317
- borderColor: "gray",
3318
- borderTop: true,
3319
- borderBottom: false,
3320
- borderLeft: false,
3321
- borderRight: false,
3322
- children: [
3323
- /* @__PURE__ */ jsxs(Text, { children: [
3324
- /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "Perstack" }),
3325
- info.runtimeVersion && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
3326
- " (v",
3327
- info.runtimeVersion,
3328
- ")"
3329
- ] })
3330
- ] }),
3331
- /* @__PURE__ */ jsxs(Text, { children: [
3332
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Expert: " }),
3333
- /* @__PURE__ */ jsx(Text, { color: "white", children: displayExpertName }),
3334
- /* @__PURE__ */ jsx(Text, { color: "gray", children: " / Skills: " }),
3335
- /* @__PURE__ */ jsx(Text, { color: "green", children: skills })
3336
- ] }),
3337
- /* @__PURE__ */ jsxs(Text, { children: [
3338
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Status: " }),
3339
- /* @__PURE__ */ jsx(
3340
- Text,
3341
- {
3342
- color: info.status === "running" ? "green" : info.status === "completed" ? "cyan" : "yellow",
3343
- children: info.status
3344
- }
3345
- ),
3346
- /* @__PURE__ */ jsx(Text, { color: "gray", children: " / Step: " }),
3347
- /* @__PURE__ */ jsx(Text, { color: "white", children: step }),
3348
- /* @__PURE__ */ jsx(Text, { color: "gray", children: " / Events: " }),
3349
- /* @__PURE__ */ jsx(Text, { color: "white", children: eventCount }),
3350
- /* @__PURE__ */ jsx(Text, { color: "gray", children: " / Usage: " }),
3351
- /* @__PURE__ */ jsxs(Text, { color: "white", children: [
3352
- usagePercent,
3353
- "%"
3354
- ] })
3355
- ] }),
3356
- /* @__PURE__ */ jsxs(Text, { children: [
3357
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Model: " }),
3358
- /* @__PURE__ */ jsx(Text, { color: "white", children: info.model })
3359
- ] }),
3360
- /* @__PURE__ */ jsxs(Box, { children: [
3361
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Query: " }),
3362
- isEditing ? /* @__PURE__ */ jsxs(Fragment, { children: [
3363
- /* @__PURE__ */ jsx(Text, { color: "white", children: input }),
3364
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: "_" })
3365
- ] }) : /* @__PURE__ */ jsx(Text, { color: "white", children: info.query })
3366
- ] })
3367
- ]
2697
+ return;
2698
+ }
2699
+ if (event.type === "stopRunByError") {
2700
+ const errorEvent = event;
2701
+ const activityId = `error-${event.id}`;
2702
+ addActivity({
2703
+ type: "error",
2704
+ id: activityId,
2705
+ expertKey: event.expertKey,
2706
+ runId: event.runId,
2707
+ previousActivityId: runState.lastActivityId,
2708
+ delegatedBy: runState.delegatedBy,
2709
+ errorName: errorEvent.error.name,
2710
+ error: errorEvent.error.message,
2711
+ isRetryable: errorEvent.error.isRetryable
2712
+ });
2713
+ runState.lastActivityId = activityId;
2714
+ runState.isComplete = true;
2715
+ runState.completedReasoning = void 0;
2716
+ return;
2717
+ }
2718
+ if (isToolCallsEvent(event)) {
2719
+ for (const toolCall of event.toolCalls) {
2720
+ if (!state.tools.has(toolCall.id)) {
2721
+ state.tools.set(toolCall.id, { id: toolCall.id, toolCall, logged: false });
2722
+ }
3368
2723
  }
3369
- );
3370
- };
3371
- var StreamingDisplay = ({ streaming }) => {
3372
- const activeRuns = Object.entries(streaming.runs).filter(
3373
- ([, run]) => run.isReasoningActive || run.isRunResultActive
3374
- );
3375
- if (activeRuns.length === 0) return null;
3376
- return /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginY: 1, children: activeRuns.map(([runId, run]) => /* @__PURE__ */ jsx(StreamingRunSection, { run }, runId)) });
3377
- };
3378
- function StreamingRunSection({ run }) {
3379
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3380
- run.isReasoningActive && run.reasoning !== void 0 && /* @__PURE__ */ jsx(StreamingReasoning, { expertKey: run.expertKey, text: run.reasoning }),
3381
- run.isRunResultActive && run.runResult !== void 0 && /* @__PURE__ */ jsx(StreamingRunResult, { expertKey: run.expertKey, text: run.runResult })
3382
- ] });
3383
- }
3384
- function StreamingReasoning({
3385
- expertKey,
3386
- text
3387
- }) {
3388
- const lines = text.split("\n");
3389
- const label = `[${formatExpertKey(expertKey)}] Reasoning...`;
3390
- 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}`)) }) });
3391
- }
3392
- function StreamingRunResult({
3393
- expertKey,
3394
- text
3395
- }) {
3396
- const lines = text.split("\n");
3397
- const label = `[${formatExpertKey(expertKey)}] Generating...`;
3398
- 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}`)) }) });
3399
- }
3400
- function formatExpertKey(expertKey) {
3401
- const atIndex = expertKey.lastIndexOf("@");
3402
- if (atIndex > 0) {
3403
- return expertKey.substring(0, atIndex);
3404
2724
  }
3405
- return expertKey;
3406
- }
3407
- function getActivityKey(activityOrGroup, index) {
3408
- return activityOrGroup.id || `activity-${index}`;
3409
- }
3410
- var RunBox = ({ node, isRoot }) => {
3411
- if (isRoot) {
3412
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3413
- node.activities.map((activity, index) => /* @__PURE__ */ jsx(CheckpointActionRow, { action: activity }, getActivityKey(activity, index))),
3414
- node.children.map((child) => /* @__PURE__ */ jsx(RunBox, { node: child, isRoot: false }, child.runId))
3415
- ] });
2725
+ if (isStopRunByDelegateEvent(event)) {
2726
+ const reasoning = runState.completedReasoning;
2727
+ const delegations = event.checkpoint.delegateTo ?? [];
2728
+ runState.pendingDelegateCount += delegations.length;
2729
+ const delegateActivities = [];
2730
+ for (const delegation of delegations) {
2731
+ const existingTool = state.tools.get(delegation.toolCallId);
2732
+ if (!existingTool || !existingTool.logged) {
2733
+ if (existingTool) {
2734
+ existingTool.logged = true;
2735
+ }
2736
+ const activityId = `delegate-${delegation.toolCallId}`;
2737
+ delegateActivities.push({
2738
+ type: "delegate",
2739
+ id: activityId,
2740
+ expertKey: event.expertKey,
2741
+ runId: event.runId,
2742
+ previousActivityId: runState.lastActivityId,
2743
+ delegatedBy: runState.delegatedBy,
2744
+ delegateExpertKey: delegation.expert.key,
2745
+ query: delegation.query,
2746
+ reasoning
2747
+ });
2748
+ runState.lastActivityId = activityId;
2749
+ }
2750
+ }
2751
+ const wrapped = wrapInGroupIfParallel(delegateActivities, reasoning, {
2752
+ expertKey: event.expertKey,
2753
+ runId: event.runId,
2754
+ delegatedBy: runState.delegatedBy
2755
+ });
2756
+ for (const item of wrapped) {
2757
+ addActivity(item);
2758
+ }
2759
+ runState.completedReasoning = void 0;
2760
+ return;
3416
2761
  }
3417
- const shortRunId = node.runId.slice(0, 8);
3418
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", marginLeft: 1, children: [
3419
- /* @__PURE__ */ jsxs(Text, { dimColor: true, bold: true, children: [
3420
- "[",
3421
- node.expertKey,
3422
- "] ",
3423
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
3424
- "(",
3425
- shortRunId,
3426
- ")"
3427
- ] })
3428
- ] }),
3429
- node.activities.map((activity, index) => /* @__PURE__ */ jsx(CheckpointActionRow, { action: activity }, getActivityKey(activity, index))),
3430
- node.children.map((child) => /* @__PURE__ */ jsx(RunBox, { node: child, isRoot: false }, child.runId))
3431
- ] });
3432
- };
3433
- function getActivityProps(activityOrGroup) {
3434
- if (activityOrGroup.type === "parallelGroup") {
3435
- const group = activityOrGroup;
3436
- const firstActivity = group.activities[0];
3437
- return {
3438
- runId: group.runId,
3439
- expertKey: group.expertKey,
3440
- delegatedBy: firstActivity?.delegatedBy
3441
- };
2762
+ if (isStopRunByInteractiveToolEvent(event)) {
2763
+ const reasoning = runState.completedReasoning;
2764
+ const pendingToolCalls = event.checkpoint.pendingToolCalls ?? [];
2765
+ const interactiveActivities = [];
2766
+ for (const toolCall of pendingToolCalls) {
2767
+ const existingTool = state.tools.get(toolCall.id);
2768
+ if (!existingTool || !existingTool.logged) {
2769
+ if (existingTool) {
2770
+ existingTool.logged = true;
2771
+ }
2772
+ const activityId = `interactive-${toolCall.id}`;
2773
+ interactiveActivities.push({
2774
+ type: "interactiveTool",
2775
+ id: activityId,
2776
+ expertKey: event.expertKey,
2777
+ runId: event.runId,
2778
+ previousActivityId: runState.lastActivityId,
2779
+ delegatedBy: runState.delegatedBy,
2780
+ skillName: toolCall.skillName,
2781
+ toolName: toolCall.toolName,
2782
+ args: toolCall.args,
2783
+ reasoning
2784
+ });
2785
+ runState.lastActivityId = activityId;
2786
+ }
2787
+ }
2788
+ const wrapped = wrapInGroupIfParallel(interactiveActivities, reasoning, {
2789
+ expertKey: event.expertKey,
2790
+ runId: event.runId,
2791
+ delegatedBy: runState.delegatedBy
2792
+ });
2793
+ for (const item of wrapped) {
2794
+ addActivity(item);
2795
+ }
2796
+ runState.completedReasoning = void 0;
2797
+ return;
2798
+ }
2799
+ if (isToolResultsEvent(event)) {
2800
+ const reasoning = runState.completedReasoning;
2801
+ const toolActivities = [];
2802
+ for (const toolResult of event.toolResults) {
2803
+ const tool = state.tools.get(toolResult.id);
2804
+ if (tool && !tool.logged) {
2805
+ const activityId = `action-${tool.id}`;
2806
+ const activity = toolToActivity(tool.toolCall, toolResult, reasoning, {
2807
+ id: activityId,
2808
+ expertKey: event.expertKey,
2809
+ runId: event.runId,
2810
+ previousActivityId: runState.lastActivityId,
2811
+ delegatedBy: runState.delegatedBy
2812
+ });
2813
+ toolActivities.push(activity);
2814
+ runState.lastActivityId = activityId;
2815
+ tool.logged = true;
2816
+ }
2817
+ }
2818
+ if (toolActivities.length > 0) {
2819
+ const wrapped = wrapInGroupIfParallel(toolActivities, reasoning, {
2820
+ expertKey: event.expertKey,
2821
+ runId: event.runId,
2822
+ delegatedBy: runState.delegatedBy
2823
+ });
2824
+ for (const item of wrapped) {
2825
+ addActivity(item);
2826
+ }
2827
+ runState.completedReasoning = void 0;
2828
+ }
2829
+ } else if (isToolResultEvent(event)) {
2830
+ const { toolResult } = event;
2831
+ const tool = state.tools.get(toolResult.id);
2832
+ if (tool && !tool.logged) {
2833
+ const activityId = `action-${tool.id}`;
2834
+ const activity = toolToActivity(tool.toolCall, toolResult, runState.completedReasoning, {
2835
+ id: activityId,
2836
+ expertKey: event.expertKey,
2837
+ runId: event.runId,
2838
+ previousActivityId: runState.lastActivityId,
2839
+ delegatedBy: runState.delegatedBy
2840
+ });
2841
+ addActivity(activity);
2842
+ runState.lastActivityId = activityId;
2843
+ tool.logged = true;
2844
+ runState.completedReasoning = void 0;
2845
+ }
3442
2846
  }
3443
- return activityOrGroup;
3444
2847
  }
3445
- var ActivityLogPanel = ({ activities }) => {
3446
- const rootNodes = useMemo(() => {
3447
- const nodeMap = /* @__PURE__ */ new Map();
3448
- const roots = [];
3449
- const orphanChildren = /* @__PURE__ */ new Map();
3450
- for (const activityOrGroup of activities) {
3451
- const { runId, expertKey, delegatedBy } = getActivityProps(activityOrGroup);
3452
- let node = nodeMap.get(runId);
3453
- if (!node) {
3454
- node = {
3455
- runId,
3456
- expertKey,
3457
- activities: [],
3458
- children: []
3459
- };
3460
- nodeMap.set(runId, node);
3461
- const waitingChildren = orphanChildren.get(runId);
3462
- if (waitingChildren) {
3463
- for (const child of waitingChildren) {
3464
- node.children.push(child);
3465
- const rootIndex = roots.indexOf(child);
3466
- if (rootIndex !== -1) {
3467
- roots.splice(rootIndex, 1);
2848
+
2849
+ // ../../packages/react/src/hooks/use-run.ts
2850
+ var STREAMING_EVENT_TYPES = /* @__PURE__ */ new Set([
2851
+ "startStreamingReasoning",
2852
+ "streamReasoning",
2853
+ "completeStreamingReasoning",
2854
+ "startStreamingRunResult",
2855
+ "streamRunResult",
2856
+ "completeStreamingRunResult"
2857
+ ]);
2858
+ var isStreamingEvent = (event) => "type" in event && "expertKey" in event && STREAMING_EVENT_TYPES.has(event.type);
2859
+ function processStreamingEvent(event, prevState) {
2860
+ const { runId, expertKey } = event;
2861
+ switch (event.type) {
2862
+ case "startStreamingReasoning": {
2863
+ return {
2864
+ newState: {
2865
+ ...prevState,
2866
+ runs: {
2867
+ ...prevState.runs,
2868
+ [runId]: {
2869
+ expertKey,
2870
+ reasoning: "",
2871
+ isReasoningActive: true
3468
2872
  }
3469
2873
  }
3470
- orphanChildren.delete(runId);
3471
- }
3472
- if (delegatedBy) {
3473
- const parentNode = nodeMap.get(delegatedBy.runId);
3474
- if (parentNode) {
3475
- parentNode.children.push(node);
3476
- } else {
3477
- const orphans = orphanChildren.get(delegatedBy.runId) ?? [];
3478
- orphans.push(node);
3479
- orphanChildren.set(delegatedBy.runId, orphans);
3480
- roots.push(node);
2874
+ },
2875
+ handled: true
2876
+ };
2877
+ }
2878
+ case "streamReasoning": {
2879
+ return {
2880
+ newState: {
2881
+ ...prevState,
2882
+ runs: {
2883
+ ...prevState.runs,
2884
+ [runId]: {
2885
+ ...prevState.runs[runId],
2886
+ expertKey,
2887
+ reasoning: (prevState.runs[runId]?.reasoning ?? "") + event.delta
2888
+ }
3481
2889
  }
3482
- } else {
3483
- roots.push(node);
2890
+ },
2891
+ handled: true
2892
+ };
2893
+ }
2894
+ case "completeStreamingReasoning": {
2895
+ return {
2896
+ newState: {
2897
+ ...prevState,
2898
+ runs: {
2899
+ ...prevState.runs,
2900
+ [runId]: {
2901
+ ...prevState.runs[runId],
2902
+ expertKey,
2903
+ isReasoningActive: false
2904
+ }
2905
+ }
2906
+ },
2907
+ handled: false
2908
+ };
2909
+ }
2910
+ case "startStreamingRunResult": {
2911
+ return {
2912
+ newState: {
2913
+ ...prevState,
2914
+ runs: {
2915
+ ...prevState.runs,
2916
+ [runId]: {
2917
+ expertKey,
2918
+ reasoning: void 0,
2919
+ isReasoningActive: false,
2920
+ runResult: "",
2921
+ isRunResultActive: true
2922
+ }
2923
+ }
2924
+ },
2925
+ handled: true
2926
+ };
2927
+ }
2928
+ case "streamRunResult": {
2929
+ return {
2930
+ newState: {
2931
+ ...prevState,
2932
+ runs: {
2933
+ ...prevState.runs,
2934
+ [runId]: {
2935
+ ...prevState.runs[runId],
2936
+ expertKey,
2937
+ runResult: (prevState.runs[runId]?.runResult ?? "") + event.delta
2938
+ }
2939
+ }
2940
+ },
2941
+ handled: true
2942
+ };
2943
+ }
2944
+ case "completeStreamingRunResult": {
2945
+ return {
2946
+ newState: {
2947
+ ...prevState,
2948
+ runs: {
2949
+ ...prevState.runs,
2950
+ [runId]: {
2951
+ ...prevState.runs[runId],
2952
+ expertKey,
2953
+ isRunResultActive: false
2954
+ }
2955
+ }
2956
+ },
2957
+ handled: true
2958
+ };
2959
+ }
2960
+ default:
2961
+ return { newState: prevState, handled: false };
2962
+ }
2963
+ }
2964
+ function useRun() {
2965
+ const [activities, setActivities] = useState([]);
2966
+ const [streaming, setStreaming] = useState({ runs: {} });
2967
+ const [eventCount, setEventCount] = useState(0);
2968
+ const [isComplete, setIsComplete] = useState(false);
2969
+ const stateRef = useRef(createInitialActivityProcessState());
2970
+ const clearStreaming = useCallback(() => {
2971
+ setStreaming({ runs: {} });
2972
+ }, []);
2973
+ const handleStreamingEvent = useCallback((event) => {
2974
+ let handled = false;
2975
+ setStreaming((prev) => {
2976
+ const result = processStreamingEvent(event, prev);
2977
+ handled = result.handled;
2978
+ return result.newState;
2979
+ });
2980
+ return handled;
2981
+ }, []);
2982
+ const processEvent = useCallback((event) => {
2983
+ const newActivities = [];
2984
+ const addActivity = (activity) => newActivities.push(activity);
2985
+ processRunEventToActivity(stateRef.current, event, addActivity);
2986
+ if (newActivities.length > 0) {
2987
+ setActivities((prev) => [...prev, ...newActivities]);
2988
+ }
2989
+ const rootRunComplete = Array.from(stateRef.current.runStates.values()).some(
2990
+ (rs) => rs.isComplete && !rs.delegatedBy
2991
+ );
2992
+ setIsComplete(rootRunComplete);
2993
+ }, []);
2994
+ const clearRunStreaming = useCallback((runId) => {
2995
+ setStreaming((prev) => {
2996
+ const { [runId]: _, ...rest } = prev.runs;
2997
+ return { runs: rest };
2998
+ });
2999
+ }, []);
3000
+ const addEvent = useCallback(
3001
+ (event) => {
3002
+ if (isStreamingEvent(event)) {
3003
+ const handled = handleStreamingEvent(event);
3004
+ if (handled) {
3005
+ setEventCount((prev) => prev + 1);
3006
+ return;
3484
3007
  }
3485
3008
  }
3486
- node.activities.push(activityOrGroup);
3487
- }
3488
- return roots;
3489
- }, [activities]);
3490
- return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: rootNodes.map((node) => /* @__PURE__ */ jsx(RunBox, { node, isRoot: true }, node.runId)) });
3491
- };
3492
- var ContinueInputPanel = ({
3493
- isActive,
3494
- runStatus,
3495
- onSubmit
3496
- }) => {
3497
- const { input, handleInput } = useTextInput({
3498
- onSubmit: (newQuery) => {
3499
- if (isActive && newQuery.trim()) {
3500
- onSubmit(newQuery.trim());
3009
+ if ("type" in event && "runId" in event && (event.type === "completeRun" || event.type === "stopRunByError")) {
3010
+ clearRunStreaming(event.runId);
3501
3011
  }
3012
+ processEvent(event);
3013
+ setEventCount((prev) => prev + 1);
3014
+ },
3015
+ [handleStreamingEvent, clearRunStreaming, processEvent]
3016
+ );
3017
+ const appendHistoricalEvents = useCallback((historicalEvents) => {
3018
+ const newActivities = [];
3019
+ const addActivity = (activity) => newActivities.push(activity);
3020
+ for (const event of historicalEvents) {
3021
+ processRunEventToActivity(stateRef.current, event, addActivity);
3502
3022
  }
3023
+ if (newActivities.length > 0) {
3024
+ setActivities((prev) => [...prev, ...newActivities]);
3025
+ }
3026
+ setEventCount((prev) => prev + historicalEvents.length);
3027
+ const rootRunComplete = Array.from(stateRef.current.runStates.values()).some(
3028
+ (rs) => rs.isComplete && !rs.delegatedBy
3029
+ );
3030
+ setIsComplete(rootRunComplete);
3031
+ }, []);
3032
+ return {
3033
+ activities,
3034
+ streaming,
3035
+ isComplete,
3036
+ eventCount,
3037
+ addEvent,
3038
+ appendHistoricalEvents,
3039
+ clearStreaming
3040
+ };
3041
+ }
3042
+ var useRuntimeInfo = (options) => {
3043
+ const [runtimeInfo, setRuntimeInfo] = useState({
3044
+ status: "initializing",
3045
+ runtimeVersion: options.initialConfig.runtimeVersion,
3046
+ expertName: options.initialExpertName,
3047
+ model: options.initialConfig.model,
3048
+ maxSteps: options.initialConfig.maxSteps,
3049
+ maxRetries: options.initialConfig.maxRetries,
3050
+ timeout: options.initialConfig.timeout,
3051
+ activeSkills: [],
3052
+ contextWindowUsage: options.initialConfig.contextWindowUsage
3503
3053
  });
3504
- useInput(handleInput, { isActive });
3505
- if (runStatus === "running") {
3506
- return null;
3507
- }
3508
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", children: [
3509
- /* @__PURE__ */ jsxs(Text, { children: [
3510
- /* @__PURE__ */ jsx(Text, { color: runStatus === "completed" ? "green" : "yellow", bold: true, children: runStatus === "completed" ? "Completed" : "Stopped" }),
3511
- /* @__PURE__ */ jsx(Text, { color: "gray", children: " - Enter a follow-up query or wait to exit" })
3512
- ] }),
3513
- /* @__PURE__ */ jsxs(Box, { children: [
3514
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Continue: " }),
3515
- /* @__PURE__ */ jsx(Text, { children: input }),
3516
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: "_" })
3517
- ] })
3518
- ] });
3519
- };
3520
- var StatusPanel = ({
3521
- runtimeInfo,
3522
- eventCount,
3523
- runStatus
3524
- }) => {
3525
- if (runStatus !== "running") {
3054
+ const handleEvent = useCallback((event) => {
3055
+ if (event.type === "initializeRuntime") {
3056
+ setRuntimeInfo((prev) => ({
3057
+ runtimeVersion: event.runtimeVersion,
3058
+ expertName: event.expertName,
3059
+ model: event.model,
3060
+ maxSteps: event.maxSteps,
3061
+ maxRetries: event.maxRetries,
3062
+ timeout: event.timeout,
3063
+ currentStep: 1,
3064
+ status: "running",
3065
+ query: event.query,
3066
+ statusChangedAt: Date.now(),
3067
+ activeSkills: [],
3068
+ contextWindowUsage: prev.contextWindowUsage
3069
+ }));
3070
+ return { initialized: true };
3071
+ }
3072
+ if (event.type === "skillConnected") {
3073
+ setRuntimeInfo((prev) => ({
3074
+ ...prev,
3075
+ activeSkills: [...prev.activeSkills, event.skillName]
3076
+ }));
3077
+ return null;
3078
+ }
3079
+ if (event.type === "skillDisconnected") {
3080
+ setRuntimeInfo((prev) => ({
3081
+ ...prev,
3082
+ activeSkills: prev.activeSkills.filter((s) => s !== event.skillName)
3083
+ }));
3084
+ return null;
3085
+ }
3086
+ if ("stepNumber" in event) {
3087
+ const isComplete = event.type === "completeRun";
3088
+ const isStopped = STOP_EVENT_TYPES.includes(event.type);
3089
+ const checkpoint = "nextCheckpoint" in event ? event.nextCheckpoint : "checkpoint" in event ? event.checkpoint : void 0;
3090
+ setRuntimeInfo((prev) => ({
3091
+ ...prev,
3092
+ currentStep: event.stepNumber,
3093
+ status: isComplete ? "completed" : isStopped ? "stopped" : "running",
3094
+ ...isComplete || isStopped ? { statusChangedAt: Date.now() } : {},
3095
+ ...checkpoint?.contextWindowUsage !== void 0 ? { contextWindowUsage: checkpoint.contextWindowUsage } : {}
3096
+ }));
3097
+ if (isComplete) return { completed: true };
3098
+ if (isStopped) return { stopped: true };
3099
+ }
3526
3100
  return null;
3527
- }
3528
- return /* @__PURE__ */ jsx(RunSetting, { info: runtimeInfo, eventCount, isEditing: false });
3101
+ }, []);
3102
+ const setExpertName = useCallback((expertName) => {
3103
+ setRuntimeInfo((prev) => ({ ...prev, expertName }));
3104
+ }, []);
3105
+ const setQuery = useCallback((query) => {
3106
+ setRuntimeInfo((prev) => ({ ...prev, query }));
3107
+ }, []);
3108
+ const setCurrentStep = useCallback((step) => {
3109
+ setRuntimeInfo((prev) => ({ ...prev, currentStep: step }));
3110
+ }, []);
3111
+ const setContextWindowUsage = useCallback((contextWindowUsage) => {
3112
+ setRuntimeInfo((prev) => ({ ...prev, contextWindowUsage }));
3113
+ }, []);
3114
+ return {
3115
+ runtimeInfo,
3116
+ handleEvent,
3117
+ setExpertName,
3118
+ setQuery,
3119
+ setCurrentStep,
3120
+ setContextWindowUsage
3121
+ };
3529
3122
  };
3530
- var useExecutionState = (options) => {
3531
- const { expertKey, query, config: config2, continueTimeoutMs, historicalEvents, onReady, onComplete } = options;
3532
- const { exit } = useApp();
3533
- const runState = useRun();
3534
- const { runtimeInfo, handleEvent, setQuery } = useRuntimeInfo({
3535
- initialExpertName: expertKey,
3536
- initialConfig: config2
3123
+ var useLatestRef = (value) => {
3124
+ const ref = useRef(value);
3125
+ useInsertionEffect(() => {
3126
+ ref.current = value;
3537
3127
  });
3538
- const [runStatus, setRunStatus] = useState("running");
3539
- const [isAcceptingContinue, setIsAcceptingContinue] = useState(false);
3540
- const timeoutRef = useRef(null);
3541
- const clearTimeoutIfExists = useCallback(() => {
3542
- if (timeoutRef.current) {
3543
- clearTimeout(timeoutRef.current);
3544
- timeoutRef.current = null;
3545
- }
3546
- }, []);
3547
- const startExitTimeout = useCallback(() => {
3548
- clearTimeoutIfExists();
3549
- timeoutRef.current = setTimeout(() => {
3550
- onComplete({ nextQuery: null });
3551
- exit();
3552
- }, continueTimeoutMs);
3553
- }, [clearTimeoutIfExists, continueTimeoutMs, onComplete, exit]);
3554
- useEffect(() => {
3555
- setQuery(query);
3556
- }, [query, setQuery]);
3557
- useEffect(() => {
3558
- if (historicalEvents && historicalEvents.length > 0) {
3559
- runState.appendHistoricalEvents(historicalEvents);
3560
- }
3561
- }, [historicalEvents, runState.appendHistoricalEvents]);
3562
- useEffect(() => {
3563
- onReady((event) => {
3564
- runState.addEvent(event);
3565
- const result = handleEvent(event);
3566
- if (result?.completed) {
3567
- setRunStatus("completed");
3568
- setIsAcceptingContinue(true);
3569
- startExitTimeout();
3570
- } else if (result?.stopped) {
3571
- setRunStatus("stopped");
3572
- setIsAcceptingContinue(true);
3573
- startExitTimeout();
3128
+ return ref;
3129
+ };
3130
+
3131
+ // ../../packages/tui-components/src/hooks/use-text-input.ts
3132
+ var useTextInput = (options) => {
3133
+ const [input, setInput] = useState("");
3134
+ const inputRef = useLatestRef(input);
3135
+ const onSubmitRef = useLatestRef(options.onSubmit);
3136
+ const onCancelRef = useLatestRef(options.onCancel);
3137
+ const handleInput = useCallback(
3138
+ (inputChar, key) => {
3139
+ if (key.escape) {
3140
+ setInput("");
3141
+ onCancelRef.current?.();
3142
+ return;
3574
3143
  }
3575
- });
3576
- }, [onReady, runState.addEvent, handleEvent, startExitTimeout]);
3577
- useEffect(() => {
3578
- return () => {
3579
- clearTimeoutIfExists();
3580
- };
3581
- }, [clearTimeoutIfExists]);
3582
- const handleContinueSubmit = useCallback(
3583
- (newQuery) => {
3584
- if (isAcceptingContinue && newQuery.trim()) {
3585
- clearTimeoutIfExists();
3586
- onComplete({ nextQuery: newQuery.trim() });
3587
- exit();
3144
+ if (key.return && inputRef.current.trim()) {
3145
+ onSubmitRef.current(inputRef.current.trim());
3146
+ setInput("");
3147
+ return;
3148
+ }
3149
+ if (key.backspace || key.delete) {
3150
+ setInput((prev) => prev.slice(0, -1));
3151
+ return;
3152
+ }
3153
+ if (!key.ctrl && !key.meta && inputChar) {
3154
+ setInput((prev) => prev + inputChar);
3155
+ }
3156
+ },
3157
+ [inputRef, onSubmitRef, onCancelRef]
3158
+ );
3159
+ const reset = useCallback(() => {
3160
+ setInput("");
3161
+ }, []);
3162
+ return { input, handleInput, reset };
3163
+ };
3164
+
3165
+ // ../../packages/tui-components/src/hooks/ui/use-expert-selector.ts
3166
+ var useExpertSelector = (options) => {
3167
+ const { experts, onExpertSelect, extraKeyHandler } = options;
3168
+ const [inputMode, setInputMode] = useState(false);
3169
+ const { selectedIndex, handleNavigation } = useListNavigation({
3170
+ items: experts,
3171
+ onSelect: (expert) => onExpertSelect(expert.key)
3172
+ });
3173
+ const { input, handleInput, reset } = useTextInput({
3174
+ onSubmit: (value) => {
3175
+ onExpertSelect(value);
3176
+ setInputMode(false);
3177
+ },
3178
+ onCancel: () => setInputMode(false)
3179
+ });
3180
+ const handleKeyInput = useCallback(
3181
+ (inputChar, key) => {
3182
+ if (inputMode) {
3183
+ handleInput(inputChar, key);
3184
+ return;
3185
+ }
3186
+ if (handleNavigation(inputChar, key)) return;
3187
+ if (extraKeyHandler?.(inputChar, key)) return;
3188
+ if (inputChar === "i") {
3189
+ reset();
3190
+ setInputMode(true);
3588
3191
  }
3589
3192
  },
3590
- [isAcceptingContinue, clearTimeoutIfExists, onComplete, exit]
3193
+ [inputMode, handleInput, handleNavigation, extraKeyHandler, reset]
3591
3194
  );
3592
3195
  return {
3593
- activities: runState.activities,
3594
- streaming: runState.streaming,
3595
- eventCount: runState.eventCount,
3596
- runtimeInfo,
3597
- runStatus,
3598
- isAcceptingContinue,
3599
- handleContinueSubmit,
3600
- clearTimeout: clearTimeoutIfExists
3196
+ inputMode,
3197
+ input,
3198
+ selectedIndex,
3199
+ handleKeyInput
3601
3200
  };
3602
3201
  };
3603
- var ExecutionApp = (props) => {
3604
- const { expertKey, query, config: config2, continueTimeoutMs, historicalEvents, onReady, onComplete } = props;
3605
- const { exit } = useApp();
3606
- const state = useExecutionState({
3607
- expertKey,
3608
- query,
3609
- config: config2,
3610
- continueTimeoutMs,
3611
- historicalEvents,
3612
- onReady,
3613
- onComplete
3614
- });
3615
- useInput((input, key) => {
3616
- if (key.ctrl && input === "c") {
3617
- state.clearTimeout();
3618
- exit();
3619
- }
3620
- });
3621
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3622
- /* @__PURE__ */ jsx(ActivityLogPanel, { activities: state.activities }),
3623
- /* @__PURE__ */ jsx(StreamingDisplay, { streaming: state.streaming }),
3624
- /* @__PURE__ */ jsx(
3625
- StatusPanel,
3626
- {
3627
- runtimeInfo: state.runtimeInfo,
3628
- eventCount: state.eventCount,
3629
- runStatus: state.runStatus
3630
- }
3631
- ),
3632
- /* @__PURE__ */ jsx(
3633
- ContinueInputPanel,
3634
- {
3635
- isActive: state.isAcceptingContinue,
3636
- runStatus: state.runStatus,
3637
- onSubmit: state.handleContinueSubmit
3638
- }
3639
- )
3640
- ] });
3202
+ var ExpertList = ({
3203
+ experts,
3204
+ selectedIndex,
3205
+ showSource = false,
3206
+ inline = false,
3207
+ maxItems
3208
+ }) => {
3209
+ const displayExperts = maxItems ? experts.slice(0, maxItems) : experts;
3210
+ if (displayExperts.length === 0) {
3211
+ return /* @__PURE__ */ jsx(Text, { color: "gray", children: "No experts found." });
3212
+ }
3213
+ const items = displayExperts.map((expert, index) => /* @__PURE__ */ jsxs(Text, { color: index === selectedIndex ? "cyan" : "gray", children: [
3214
+ index === selectedIndex ? ">" : " ",
3215
+ " ",
3216
+ showSource ? expert.key : expert.name,
3217
+ showSource && expert.source && /* @__PURE__ */ jsxs(Fragment, { children: [
3218
+ " ",
3219
+ /* @__PURE__ */ jsxs(Text, { color: expert.source === "configured" ? "green" : "yellow", children: [
3220
+ "[",
3221
+ expert.source === "configured" ? "config" : "recent",
3222
+ "]"
3223
+ ] })
3224
+ ] }),
3225
+ inline ? " " : ""
3226
+ ] }, expert.key));
3227
+ return inline ? /* @__PURE__ */ jsx(Box, { children: items }) : /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: items });
3641
3228
  };
3642
- function renderExecution(params) {
3643
- const eventQueue = new EventQueue();
3644
- const result = new Promise((resolve, reject) => {
3645
- let resolved = false;
3646
- const { waitUntilExit } = render(
3229
+ var ExpertSelectorBase = ({
3230
+ experts,
3231
+ hint,
3232
+ onExpertSelect,
3233
+ showSource = false,
3234
+ inline = false,
3235
+ maxItems = UI_CONSTANTS.MAX_VISIBLE_LIST_ITEMS,
3236
+ extraKeyHandler
3237
+ }) => {
3238
+ const { inputMode, input, selectedIndex, handleKeyInput } = useExpertSelector({
3239
+ experts,
3240
+ onExpertSelect,
3241
+ extraKeyHandler
3242
+ });
3243
+ useInput(handleKeyInput);
3244
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: inline ? "row" : "column", children: [
3245
+ !inputMode && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3246
+ /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { color: "cyan", children: hint }) }),
3647
3247
  /* @__PURE__ */ jsx(
3648
- ExecutionApp,
3248
+ ExpertList,
3649
3249
  {
3650
- ...params,
3651
- onReady: (handler) => {
3652
- eventQueue.setHandler(handler);
3653
- },
3654
- onComplete: (result2) => {
3655
- resolved = true;
3656
- resolve(result2);
3657
- }
3250
+ experts,
3251
+ selectedIndex,
3252
+ showSource,
3253
+ inline,
3254
+ maxItems
3658
3255
  }
3659
3256
  )
3660
- );
3661
- waitUntilExit().then(() => {
3662
- if (!resolved) {
3663
- reject(new Error("Execution cancelled"));
3664
- }
3665
- }).catch(reject);
3666
- });
3667
- return {
3668
- result,
3669
- eventListener: (event) => {
3670
- eventQueue.emit(event);
3671
- }
3672
- };
3673
- }
3674
- var selectionReducer = (_state, action) => {
3675
- switch (action.type) {
3676
- case "BROWSE_HISTORY":
3677
- return { type: "browsingHistory", jobs: action.jobs };
3678
- case "BROWSE_EXPERTS":
3679
- return { type: "browsingExperts", experts: action.experts };
3680
- case "SELECT_EXPERT":
3681
- return { type: "enteringQuery", expertKey: action.expertKey };
3682
- case "SELECT_JOB":
3683
- return { type: "browsingCheckpoints", job: action.job, checkpoints: action.checkpoints };
3684
- case "GO_BACK_FROM_CHECKPOINTS":
3685
- return { type: "browsingHistory", jobs: action.jobs };
3686
- default:
3687
- return assertNever(action);
3688
- }
3257
+ ] }),
3258
+ inputMode && /* @__PURE__ */ jsxs(Box, { children: [
3259
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Expert: " }),
3260
+ /* @__PURE__ */ jsx(Text, { color: "white", children: input }),
3261
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: "_" })
3262
+ ] })
3263
+ ] });
3689
3264
  };
3690
- var SelectionApp = (props) => {
3691
- const {
3692
- showHistory,
3693
- initialExpertKey,
3694
- initialQuery,
3695
- initialCheckpoint,
3696
- configuredExperts,
3697
- recentExperts,
3698
- historyJobs,
3699
- onLoadCheckpoints,
3700
- onComplete
3701
- } = props;
3702
- const { exit } = useApp();
3703
- const allExperts = useMemo(() => {
3704
- const configured = configuredExperts.map((e) => ({ ...e, source: "configured" }));
3705
- const recent = recentExperts.filter((e) => !configured.some((c) => c.key === e.key)).map((e) => ({ ...e, source: "recent" }));
3706
- return [...configured, ...recent];
3707
- }, [configuredExperts, recentExperts]);
3708
- const getInitialState = () => {
3709
- if (initialExpertKey && !initialQuery) {
3710
- return { type: "enteringQuery", expertKey: initialExpertKey };
3711
- }
3712
- if (showHistory && historyJobs.length > 0) {
3713
- return { type: "browsingHistory", jobs: historyJobs };
3714
- }
3715
- return { type: "browsingExperts", experts: allExperts };
3716
- };
3717
- const [state, dispatch] = useReducer(selectionReducer, void 0, getInitialState);
3718
- const [selectedCheckpoint, setSelectedCheckpoint] = useState(
3719
- initialCheckpoint
3720
- );
3721
- useEffect(() => {
3722
- if (initialExpertKey && initialQuery) {
3723
- onComplete({
3724
- expertKey: initialExpertKey,
3725
- query: initialQuery,
3726
- checkpoint: initialCheckpoint
3727
- });
3728
- exit();
3729
- }
3730
- }, [initialExpertKey, initialQuery, initialCheckpoint, onComplete, exit]);
3731
- const { input: queryInput, handleInput: handleQueryInput } = useTextInput({
3732
- onSubmit: (query) => {
3733
- if (state.type === "enteringQuery" && query.trim()) {
3734
- onComplete({
3735
- expertKey: state.expertKey,
3736
- query: query.trim(),
3737
- checkpoint: selectedCheckpoint
3738
- });
3739
- exit();
3740
- }
3741
- }
3742
- });
3743
- useInput(handleQueryInput, { isActive: state.type === "enteringQuery" });
3744
- const handleExpertSelect = useCallback((expertKey) => {
3745
- dispatch({ type: "SELECT_EXPERT", expertKey });
3746
- }, []);
3747
- const handleJobSelect = useCallback(
3748
- async (job) => {
3749
- try {
3750
- const checkpoints = await onLoadCheckpoints(job);
3751
- dispatch({ type: "SELECT_JOB", job, checkpoints });
3752
- } catch {
3753
- dispatch({ type: "SELECT_JOB", job, checkpoints: [] });
3754
- }
3755
- },
3756
- [onLoadCheckpoints]
3757
- );
3758
- const handleJobResume = useCallback(
3759
- async (job) => {
3760
- try {
3761
- const checkpoints = await onLoadCheckpoints(job);
3762
- const latestCheckpoint = checkpoints[0];
3763
- if (latestCheckpoint) {
3764
- setSelectedCheckpoint(latestCheckpoint);
3765
- }
3766
- dispatch({ type: "SELECT_EXPERT", expertKey: job.expertKey });
3767
- } catch {
3768
- dispatch({ type: "SELECT_EXPERT", expertKey: job.expertKey });
3265
+ var BrowsingExpertsInput = ({
3266
+ experts,
3267
+ onExpertSelect,
3268
+ onSwitchToHistory
3269
+ }) => {
3270
+ const extraKeyHandler = useCallback(
3271
+ (inputChar) => {
3272
+ if (inputChar === "h") {
3273
+ onSwitchToHistory();
3274
+ return true;
3769
3275
  }
3276
+ return false;
3770
3277
  },
3771
- [onLoadCheckpoints]
3278
+ [onSwitchToHistory]
3772
3279
  );
3773
- const handleCheckpointResume = useCallback(
3774
- (checkpoint) => {
3775
- setSelectedCheckpoint(checkpoint);
3776
- if (state.type === "browsingCheckpoints") {
3777
- dispatch({ type: "SELECT_EXPERT", expertKey: state.job.expertKey });
3280
+ const hint = `Experts ${KEY_HINTS.NAVIGATE} ${KEY_HINTS.SELECT} ${KEY_HINTS.INPUT} ${KEY_HINTS.HISTORY} ${KEY_HINTS.CANCEL}`;
3281
+ return /* @__PURE__ */ jsx(
3282
+ ExpertSelectorBase,
3283
+ {
3284
+ experts,
3285
+ hint,
3286
+ onExpertSelect,
3287
+ showSource: true,
3288
+ maxItems: UI_CONSTANTS.MAX_VISIBLE_LIST_ITEMS,
3289
+ extraKeyHandler
3290
+ }
3291
+ );
3292
+ };
3293
+ var BrowsingHistoryInput = ({
3294
+ jobs,
3295
+ onJobSelect,
3296
+ onJobResume,
3297
+ onSwitchToExperts
3298
+ }) => /* @__PURE__ */ jsx(
3299
+ ListBrowser,
3300
+ {
3301
+ title: `Job History ${KEY_HINTS.NAVIGATE} ${KEY_HINTS.RESUME} ${KEY_HINTS.CHECKPOINTS} ${KEY_HINTS.NEW}`,
3302
+ items: jobs,
3303
+ onSelect: onJobResume,
3304
+ emptyMessage: "No jobs found. Press n to start a new job.",
3305
+ extraKeyHandler: (char, _key, selected) => {
3306
+ if (char === "c" && selected) {
3307
+ onJobSelect(selected);
3308
+ return true;
3309
+ }
3310
+ if (char === "n") {
3311
+ onSwitchToExperts();
3312
+ return true;
3778
3313
  }
3314
+ return false;
3779
3315
  },
3780
- [state]
3781
- );
3782
- const handleBack = useCallback(() => {
3783
- if (state.type === "browsingCheckpoints") {
3784
- dispatch({ type: "GO_BACK_FROM_CHECKPOINTS", jobs: historyJobs });
3785
- }
3786
- }, [state, historyJobs]);
3787
- const handleSwitchToExperts = useCallback(() => {
3788
- dispatch({ type: "BROWSE_EXPERTS", experts: allExperts });
3789
- }, [allExperts]);
3790
- const handleSwitchToHistory = useCallback(() => {
3791
- dispatch({ type: "BROWSE_HISTORY", jobs: historyJobs });
3792
- }, [historyJobs]);
3793
- useInput((input, key) => {
3794
- if (key.ctrl && input === "c") {
3795
- exit();
3796
- }
3797
- });
3798
- const contextValue = useMemo(
3799
- () => ({
3800
- onExpertSelect: handleExpertSelect,
3801
- onQuerySubmit: () => {
3802
- },
3803
- // Not used in browser
3804
- onJobSelect: handleJobSelect,
3805
- onJobResume: handleJobResume,
3806
- onCheckpointSelect: () => {
3807
- },
3808
- // Not used in selection (no event browsing)
3809
- onCheckpointResume: handleCheckpointResume,
3810
- onEventSelect: () => {
3811
- },
3812
- // Not used in selection
3813
- onBack: handleBack,
3814
- onSwitchToExperts: handleSwitchToExperts,
3815
- onSwitchToHistory: handleSwitchToHistory
3816
- }),
3817
- [
3818
- handleExpertSelect,
3819
- handleJobSelect,
3820
- handleJobResume,
3821
- handleCheckpointResume,
3822
- handleBack,
3823
- handleSwitchToExperts,
3824
- handleSwitchToHistory
3825
- ]
3316
+ renderItem: (job, isSelected) => /* @__PURE__ */ jsxs(Text, { color: isSelected ? "cyan" : "gray", children: [
3317
+ isSelected ? ">" : " ",
3318
+ " ",
3319
+ job.expertKey,
3320
+ " - ",
3321
+ job.totalSteps,
3322
+ " steps (",
3323
+ job.jobId,
3324
+ ") (",
3325
+ formatTimestamp(job.startedAt),
3326
+ ")"
3327
+ ] }, job.jobId)
3328
+ }
3329
+ );
3330
+ var BrowserRouter = ({ inputState, showEventsHint = true }) => {
3331
+ const ctx = useInputAreaContext();
3332
+ const handleEventSelect = useCallback(
3333
+ (event) => {
3334
+ if (inputState.type === "browsingEvents") {
3335
+ ctx.onEventSelect(inputState, event);
3336
+ }
3337
+ },
3338
+ [ctx.onEventSelect, inputState]
3826
3339
  );
3827
- if (initialExpertKey && initialQuery) {
3828
- return null;
3340
+ switch (inputState.type) {
3341
+ case "browsingHistory":
3342
+ return /* @__PURE__ */ jsx(
3343
+ BrowsingHistoryInput,
3344
+ {
3345
+ jobs: inputState.jobs,
3346
+ onJobSelect: ctx.onJobSelect,
3347
+ onJobResume: ctx.onJobResume,
3348
+ onSwitchToExperts: ctx.onSwitchToExperts
3349
+ }
3350
+ );
3351
+ case "browsingExperts":
3352
+ return /* @__PURE__ */ jsx(
3353
+ BrowsingExpertsInput,
3354
+ {
3355
+ experts: inputState.experts,
3356
+ onExpertSelect: ctx.onExpertSelect,
3357
+ onSwitchToHistory: ctx.onSwitchToHistory
3358
+ }
3359
+ );
3360
+ case "browsingCheckpoints":
3361
+ return /* @__PURE__ */ jsx(
3362
+ BrowsingCheckpointsInput,
3363
+ {
3364
+ job: inputState.job,
3365
+ checkpoints: inputState.checkpoints,
3366
+ onCheckpointSelect: ctx.onCheckpointSelect,
3367
+ onCheckpointResume: ctx.onCheckpointResume,
3368
+ onBack: ctx.onBack,
3369
+ showEventsHint
3370
+ }
3371
+ );
3372
+ case "browsingEvents":
3373
+ return /* @__PURE__ */ jsx(
3374
+ BrowsingEventsInput,
3375
+ {
3376
+ checkpoint: inputState.checkpoint,
3377
+ events: inputState.events,
3378
+ onEventSelect: handleEventSelect,
3379
+ onBack: ctx.onBack
3380
+ }
3381
+ );
3382
+ case "browsingEventDetail":
3383
+ return /* @__PURE__ */ jsx(BrowsingEventDetailInput, { event: inputState.selectedEvent, onBack: ctx.onBack });
3384
+ default:
3385
+ return assertNever(inputState);
3386
+ }
3387
+ };
3388
+ var ActionRowSimple = ({
3389
+ indicatorColor,
3390
+ text,
3391
+ textDimColor = false
3392
+ }) => /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
3393
+ /* @__PURE__ */ jsx(Box, { paddingRight: 1, children: /* @__PURE__ */ jsx(Text, { color: indicatorColor, children: INDICATOR.BULLET }) }),
3394
+ /* @__PURE__ */ jsx(Text, { color: "white", dimColor: textDimColor, children: text })
3395
+ ] }) });
3396
+ var ActionRow = ({ indicatorColor, label, summary, children }) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3397
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
3398
+ /* @__PURE__ */ jsx(Text, { color: indicatorColor, children: INDICATOR.BULLET }),
3399
+ /* @__PURE__ */ jsx(Text, { color: "white", children: label }),
3400
+ summary && /* @__PURE__ */ jsx(Text, { color: "white", dimColor: true, children: summary })
3401
+ ] }),
3402
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
3403
+ /* @__PURE__ */ jsx(Box, { paddingRight: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: INDICATOR.TREE }) }),
3404
+ children
3405
+ ] })
3406
+ ] });
3407
+ var CheckpointActionRow = ({ action }) => {
3408
+ if (action.type === "parallelGroup") {
3409
+ return renderParallelGroup(action);
3829
3410
  }
3411
+ const actionElement = renderAction(action);
3412
+ if (!actionElement) return null;
3830
3413
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3831
- /* @__PURE__ */ jsx(InputAreaProvider, { value: contextValue, children: (state.type === "browsingHistory" || state.type === "browsingExperts" || state.type === "browsingCheckpoints") && /* @__PURE__ */ jsx(
3832
- BrowserRouter,
3833
- {
3834
- inputState: state,
3835
- showEventsHint: false
3836
- }
3837
- ) }),
3838
- state.type === "enteringQuery" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", children: [
3839
- /* @__PURE__ */ jsxs(Text, { children: [
3840
- /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "Expert:" }),
3841
- " ",
3842
- /* @__PURE__ */ jsx(Text, { children: state.expertKey }),
3843
- selectedCheckpoint && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
3844
- " (resuming from step ",
3845
- selectedCheckpoint.stepNumber,
3846
- ")"
3847
- ] })
3848
- ] }),
3849
- /* @__PURE__ */ jsxs(Box, { children: [
3850
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Query: " }),
3851
- /* @__PURE__ */ jsx(Text, { children: queryInput }),
3852
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: "_" })
3853
- ] }),
3854
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Press Enter to start" })
3855
- ] })
3414
+ action.reasoning && renderReasoning(action.reasoning),
3415
+ actionElement
3856
3416
  ] });
3857
3417
  };
3858
- async function renderSelection(params) {
3859
- return new Promise((resolve, reject) => {
3860
- let resolved = false;
3861
- const { waitUntilExit } = render(
3862
- /* @__PURE__ */ jsx(
3863
- SelectionApp,
3418
+ function renderParallelGroup(group) {
3419
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3420
+ group.reasoning && renderReasoning(group.reasoning),
3421
+ group.activities.map((activity, index) => {
3422
+ const actionElement = renderAction(activity);
3423
+ if (!actionElement) return null;
3424
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: actionElement }, activity.id || `${activity.type}-${index}`);
3425
+ })
3426
+ ] });
3427
+ }
3428
+ function renderReasoning(text) {
3429
+ const lines = text.split("\n");
3430
+ 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}`)) }) });
3431
+ }
3432
+ function renderAction(action) {
3433
+ const color = action.type === "error" || "error" in action && action.error ? "red" : "green";
3434
+ switch (action.type) {
3435
+ case "query":
3436
+ return renderQuery(action.text, action.runId);
3437
+ case "retry":
3438
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "yellow", label: "Retry", children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: action.message || action.error }) });
3439
+ case "complete":
3440
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "green", label: "Run Results", children: /* @__PURE__ */ jsx(Text, { children: action.text }) });
3441
+ case "attemptCompletion": {
3442
+ if (action.error) {
3443
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "red", label: "Completion Failed", children: /* @__PURE__ */ jsx(Text, { color: "red", children: action.error }) });
3444
+ }
3445
+ const remaining = action.remainingTodos?.filter((t) => !t.completed) ?? [];
3446
+ if (remaining.length > 0) {
3447
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "yellow", label: "Completion Blocked", children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
3448
+ remaining.length,
3449
+ " remaining task",
3450
+ remaining.length > 1 ? "s" : ""
3451
+ ] }) });
3452
+ }
3453
+ return /* @__PURE__ */ jsx(ActionRowSimple, { indicatorColor: "green", text: "Completion Accepted" });
3454
+ }
3455
+ case "todo":
3456
+ return renderTodo(action, color);
3457
+ case "clearTodo":
3458
+ return /* @__PURE__ */ jsx(ActionRowSimple, { indicatorColor: color, text: "Todo Cleared" });
3459
+ case "readTextFile":
3460
+ return renderReadTextFile(action, color);
3461
+ case "writeTextFile":
3462
+ return renderWriteTextFile(action, color);
3463
+ case "editTextFile":
3464
+ return renderEditTextFile(action, color);
3465
+ case "appendTextFile":
3466
+ return renderAppendTextFile(action, color);
3467
+ case "deleteFile":
3468
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Delete", summary: shortenPath(action.path), children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Removed" }) });
3469
+ case "deleteDirectory":
3470
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Delete Dir", summary: shortenPath(action.path), children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
3471
+ "Removed",
3472
+ action.recursive ? " (recursive)" : ""
3473
+ ] }) });
3474
+ case "moveFile":
3475
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Move", summary: shortenPath(action.source), children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
3476
+ "\u2192 ",
3477
+ shortenPath(action.destination, 30)
3478
+ ] }) });
3479
+ case "createDirectory":
3480
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Mkdir", summary: shortenPath(action.path), children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Created" }) });
3481
+ case "listDirectory":
3482
+ return renderListDirectory(action, color);
3483
+ case "getFileInfo":
3484
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Info", summary: shortenPath(action.path), children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Fetched" }) });
3485
+ case "readPdfFile":
3486
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "PDF", summary: shortenPath(action.path), children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Read" }) });
3487
+ case "readImageFile":
3488
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Image", summary: shortenPath(action.path), children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Read" }) });
3489
+ case "exec":
3490
+ return renderExec(action, color);
3491
+ case "delegate":
3492
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "yellow", label: action.delegateExpertKey, children: /* @__PURE__ */ jsx(
3493
+ Text,
3864
3494
  {
3865
- ...params,
3866
- onComplete: (result) => {
3867
- resolved = true;
3868
- resolve(result);
3869
- }
3495
+ dimColor: true,
3496
+ children: `{"query":"${truncateText(action.query, UI_CONSTANTS.TRUNCATE_TEXT_MEDIUM)}"}`
3870
3497
  }
3871
- )
3872
- );
3873
- waitUntilExit().then(() => {
3874
- if (!resolved) {
3875
- reject(new Error("Selection cancelled"));
3876
- }
3877
- }).catch(reject);
3878
- });
3879
- }
3880
- function getEnv(envPath) {
3881
- const env = Object.fromEntries(
3882
- Object.entries(process.env).filter(([_, value]) => !!value).map(([key, value]) => [key, value])
3883
- );
3884
- dotenv.config({ path: envPath, processEnv: env, quiet: true });
3885
- return env;
3886
- }
3887
- var ALLOWED_CONFIG_HOSTS = ["raw.githubusercontent.com"];
3888
- async function getPerstackConfig(configPath) {
3889
- const configString = await findPerstackConfigString(configPath);
3890
- if (configString === null) {
3891
- throw new Error("perstack.toml not found. Create one or specify --config path.");
3498
+ ) });
3499
+ case "delegationComplete":
3500
+ return /* @__PURE__ */ jsx(
3501
+ ActionRowSimple,
3502
+ {
3503
+ indicatorColor: "green",
3504
+ text: `Delegation Complete (${action.count} delegate${action.count > 1 ? "s" : ""} returned)`
3505
+ }
3506
+ );
3507
+ case "interactiveTool":
3508
+ 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) }) });
3509
+ case "generalTool":
3510
+ 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) }) });
3511
+ case "error":
3512
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "red", label: action.errorName ?? "Error", children: /* @__PURE__ */ jsx(Text, { color: "red", children: action.error ?? "Unknown error" }) });
3513
+ default: {
3514
+ return null;
3515
+ }
3892
3516
  }
3893
- return await parsePerstackConfig(configString);
3894
- }
3895
- function isRemoteUrl(configPath) {
3896
- const lower = configPath.toLowerCase();
3897
- return lower.startsWith("https://") || lower.startsWith("http://");
3898
3517
  }
3899
- async function fetchRemoteConfig(url) {
3900
- let parsed;
3901
- try {
3902
- parsed = new URL(url);
3903
- } catch {
3904
- throw new Error(`Invalid remote config URL: ${url}`);
3905
- }
3906
- if (parsed.protocol !== "https:") {
3907
- throw new Error("Remote config requires HTTPS");
3908
- }
3909
- if (!ALLOWED_CONFIG_HOSTS.includes(parsed.hostname)) {
3910
- throw new Error(`Remote config only allowed from: ${ALLOWED_CONFIG_HOSTS.join(", ")}`);
3518
+ function renderTodo(action, color) {
3519
+ const { newTodos, completedTodos, todos } = action;
3520
+ const hasNewTodos = newTodos && newTodos.length > 0;
3521
+ const hasCompletedTodos = completedTodos && completedTodos.length > 0;
3522
+ if (!hasNewTodos && !hasCompletedTodos) {
3523
+ return /* @__PURE__ */ jsx(ActionRowSimple, { indicatorColor: color, text: "Todo No changes" });
3911
3524
  }
3912
- try {
3913
- const response = await fetch(url, { redirect: "error" });
3914
- if (!response.ok) {
3915
- throw new Error(`${response.status} ${response.statusText}`);
3916
- }
3917
- return await response.text();
3918
- } catch (error) {
3919
- const message = error instanceof Error ? error.message : String(error);
3920
- throw new Error(`Failed to fetch remote config: ${message}`);
3525
+ const labelParts = [];
3526
+ if (hasNewTodos) {
3527
+ labelParts.push(`Added ${newTodos.length} task${newTodos.length > 1 ? "s" : ""}`);
3921
3528
  }
3922
- }
3923
- async function findPerstackConfigString(configPath) {
3924
- if (configPath) {
3925
- if (isRemoteUrl(configPath)) {
3926
- return await fetchRemoteConfig(configPath);
3927
- }
3928
- try {
3929
- const tomlString = await readFile(path5.resolve(process.cwd(), configPath), "utf-8");
3930
- return tomlString;
3931
- } catch {
3932
- throw new Error(`Given config path "${configPath}" is not found`);
3933
- }
3529
+ if (hasCompletedTodos) {
3530
+ labelParts.push(
3531
+ `Completed ${completedTodos.length} task${completedTodos.length > 1 ? "s" : ""}`
3532
+ );
3934
3533
  }
3935
- return await findPerstackConfigStringRecursively(path5.resolve(process.cwd()));
3936
- }
3937
- async function findPerstackConfigStringRecursively(cwd) {
3938
- try {
3939
- const tomlString = await readFile(path5.resolve(cwd, "perstack.toml"), "utf-8");
3940
- return tomlString;
3941
- } catch {
3942
- if (cwd === path5.parse(cwd).root) {
3943
- return null;
3944
- }
3945
- return await findPerstackConfigStringRecursively(path5.dirname(cwd));
3534
+ const label = `Todo ${labelParts.join(", ")}`;
3535
+ const completedTitles = hasCompletedTodos ? completedTodos.map((id) => todos.find((t) => t.id === id)?.title).filter((t) => t !== void 0) : [];
3536
+ if (!hasNewTodos && completedTitles.length === 0) {
3537
+ return /* @__PURE__ */ jsx(ActionRowSimple, { indicatorColor: color, text: label });
3946
3538
  }
3947
- }
3948
- async function parsePerstackConfig(config2) {
3949
- const toml = TOML.parse(config2 ?? "");
3950
- return parseWithFriendlyError(perstackConfigSchema, toml, "perstack.toml");
3951
- }
3952
-
3953
- // ../../packages/tui/src/lib/provider-config.ts
3954
- function getProviderConfig(provider, env, providerTable) {
3955
- const setting = providerTable?.setting ?? {};
3956
- switch (provider) {
3957
- case "anthropic": {
3958
- const apiKey = env.ANTHROPIC_API_KEY;
3959
- if (!apiKey) throw new Error("ANTHROPIC_API_KEY is not set");
3960
- return {
3961
- providerName: "anthropic",
3962
- apiKey,
3963
- baseUrl: setting.baseUrl ?? env.ANTHROPIC_BASE_URL,
3964
- headers: setting.headers
3965
- };
3966
- }
3967
- case "google": {
3968
- const apiKey = env.GOOGLE_GENERATIVE_AI_API_KEY;
3969
- if (!apiKey) throw new Error("GOOGLE_GENERATIVE_AI_API_KEY is not set");
3970
- return {
3971
- providerName: "google",
3972
- apiKey,
3973
- baseUrl: setting.baseUrl ?? env.GOOGLE_GENERATIVE_AI_BASE_URL,
3974
- headers: setting.headers
3975
- };
3976
- }
3977
- case "openai": {
3978
- const apiKey = env.OPENAI_API_KEY;
3979
- if (!apiKey) throw new Error("OPENAI_API_KEY is not set");
3980
- return {
3981
- providerName: "openai",
3982
- apiKey,
3983
- baseUrl: setting.baseUrl ?? env.OPENAI_BASE_URL,
3984
- organization: setting.organization ?? env.OPENAI_ORGANIZATION,
3985
- project: setting.project ?? env.OPENAI_PROJECT,
3986
- name: setting.name,
3987
- headers: setting.headers
3988
- };
3989
- }
3990
- case "ollama": {
3991
- return {
3992
- providerName: "ollama",
3993
- baseUrl: setting.baseUrl ?? env.OLLAMA_BASE_URL,
3994
- headers: setting.headers
3995
- };
3996
- }
3997
- case "azure-openai": {
3998
- const apiKey = env.AZURE_API_KEY;
3999
- if (!apiKey) throw new Error("AZURE_API_KEY is not set");
4000
- const resourceName = setting.resourceName ?? env.AZURE_RESOURCE_NAME;
4001
- const baseUrl = setting.baseUrl ?? env.AZURE_BASE_URL;
4002
- if (!resourceName && !baseUrl) throw new Error("AZURE_RESOURCE_NAME or baseUrl is not set");
4003
- return {
4004
- providerName: "azure-openai",
4005
- apiKey,
4006
- resourceName,
4007
- apiVersion: setting.apiVersion ?? env.AZURE_API_VERSION,
4008
- baseUrl,
4009
- headers: setting.headers,
4010
- useDeploymentBasedUrls: setting.useDeploymentBasedUrls
4011
- };
4012
- }
4013
- case "amazon-bedrock": {
4014
- const accessKeyId = env.AWS_ACCESS_KEY_ID;
4015
- const secretAccessKey = env.AWS_SECRET_ACCESS_KEY;
4016
- const sessionToken = env.AWS_SESSION_TOKEN;
4017
- if (!accessKeyId) throw new Error("AWS_ACCESS_KEY_ID is not set");
4018
- if (!secretAccessKey) throw new Error("AWS_SECRET_ACCESS_KEY is not set");
4019
- const region = setting.region ?? env.AWS_REGION;
4020
- if (!region) throw new Error("AWS_REGION is not set");
4021
- return {
4022
- providerName: "amazon-bedrock",
4023
- accessKeyId,
4024
- secretAccessKey,
4025
- region,
4026
- sessionToken
4027
- };
4028
- }
4029
- case "google-vertex": {
4030
- return {
4031
- providerName: "google-vertex",
4032
- project: setting.project ?? env.GOOGLE_VERTEX_PROJECT,
4033
- location: setting.location ?? env.GOOGLE_VERTEX_LOCATION,
4034
- baseUrl: setting.baseUrl ?? env.GOOGLE_VERTEX_BASE_URL,
4035
- headers: setting.headers
4036
- };
4037
- }
4038
- case "deepseek": {
4039
- const apiKey = env.DEEPSEEK_API_KEY;
4040
- if (!apiKey) throw new Error("DEEPSEEK_API_KEY is not set");
4041
- return {
4042
- providerName: "deepseek",
4043
- apiKey,
4044
- baseUrl: setting.baseUrl ?? env.DEEPSEEK_BASE_URL,
4045
- headers: setting.headers
4046
- };
3539
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label, children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3540
+ hasNewTodos && newTodos.slice(0, RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW).map((todo, idx) => /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
3541
+ "\u25CB ",
3542
+ todo
3543
+ ] }, `todo-${idx}`)),
3544
+ hasNewTodos && newTodos.length > RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
3545
+ "... +",
3546
+ newTodos.length - RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW,
3547
+ " more"
3548
+ ] }),
3549
+ completedTitles.slice(0, RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW).map((title, idx) => /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
3550
+ "\u2713 ",
3551
+ title
3552
+ ] }, `completed-${idx}`)),
3553
+ completedTitles.length > RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
3554
+ "... +",
3555
+ completedTitles.length - RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW,
3556
+ " more"
3557
+ ] })
3558
+ ] }) });
3559
+ }
3560
+ function renderReadTextFile(action, color) {
3561
+ const { path: path7, content, from, to } = action;
3562
+ const lineRange = from !== void 0 && to !== void 0 ? `#${from}-${to}` : "";
3563
+ const lines = content?.split("\n") ?? [];
3564
+ return /* @__PURE__ */ jsx(
3565
+ ActionRow,
3566
+ {
3567
+ indicatorColor: color,
3568
+ label: "Read Text File",
3569
+ summary: `${shortenPath(path7)}${lineRange}`,
3570
+ 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}`)) })
4047
3571
  }
4048
- }
3572
+ );
4049
3573
  }
4050
- function getAllJobs2() {
4051
- return getAllJobs();
3574
+ function renderWriteTextFile(action, color) {
3575
+ const { path: path7, text } = action;
3576
+ const lines = text.split("\n");
3577
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Write Text File", summary: shortenPath(path7), children: /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: lines.map((line, idx) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
3578
+ /* @__PURE__ */ jsx(Text, { color: "green", dimColor: true, children: "+" }),
3579
+ /* @__PURE__ */ jsx(Text, { color: "white", dimColor: true, children: line })
3580
+ ] }, `write-${idx}`)) }) });
4052
3581
  }
4053
- function getAllRuns2() {
4054
- return getAllRuns();
3582
+ function renderEditTextFile(action, color) {
3583
+ const { path: path7, oldText, newText } = action;
3584
+ const oldLines = oldText.split("\n");
3585
+ const newLines = newText.split("\n");
3586
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Edit Text File", summary: shortenPath(path7), children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3587
+ oldLines.map((line, idx) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
3588
+ /* @__PURE__ */ jsx(Text, { color: "red", dimColor: true, children: "-" }),
3589
+ /* @__PURE__ */ jsx(Text, { color: "white", dimColor: true, children: line })
3590
+ ] }, `old-${idx}`)),
3591
+ newLines.map((line, idx) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
3592
+ /* @__PURE__ */ jsx(Text, { color: "green", dimColor: true, children: "+" }),
3593
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: line })
3594
+ ] }, `new-${idx}`))
3595
+ ] }) });
4055
3596
  }
4056
- function getMostRecentRun() {
4057
- const runs = getAllRuns2();
4058
- if (runs.length === 0) {
4059
- throw new Error("No runs found");
4060
- }
4061
- return runs[0];
3597
+ function renderAppendTextFile(action, color) {
3598
+ const { path: path7, text } = action;
3599
+ const lines = text.split("\n");
3600
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Append Text File", summary: shortenPath(path7), children: /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: lines.map((line, idx) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
3601
+ /* @__PURE__ */ jsx(Text, { color: "green", dimColor: true, children: "+" }),
3602
+ /* @__PURE__ */ jsx(Text, { color: "white", dimColor: true, children: line })
3603
+ ] }, `append-${idx}`)) }) });
4062
3604
  }
4063
- function getCheckpointsByJobId2(jobId) {
4064
- return getCheckpointsByJobId(jobId);
3605
+ function renderListDirectory(action, color) {
3606
+ const { path: path7, items } = action;
3607
+ const itemLines = items?.map((item) => `${item.type === "directory" ? "\u{1F4C1}" : "\u{1F4C4}"} ${item.name}`) ?? [];
3608
+ const { visible, remaining } = summarizeOutput(itemLines, RENDER_CONSTANTS.LIST_DIR_MAX_ITEMS);
3609
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "List", summary: shortenPath(path7), children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3610
+ visible.map((line, idx) => /* @__PURE__ */ jsx(Text, { dimColor: true, children: truncateText(line, UI_CONSTANTS.TRUNCATE_TEXT_DEFAULT) }, `dir-${idx}`)),
3611
+ remaining > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
3612
+ "... +",
3613
+ remaining,
3614
+ " more"
3615
+ ] })
3616
+ ] }) });
4065
3617
  }
4066
- function getMostRecentCheckpoint(jobId) {
4067
- const targetJobId = jobId ?? getMostRecentRun().jobId;
4068
- const checkpoints = getCheckpointsByJobId2(targetJobId);
4069
- if (checkpoints.length === 0) {
4070
- throw new Error(`No checkpoints found for job ${targetJobId}`);
4071
- }
4072
- return checkpoints[checkpoints.length - 1];
3618
+ function renderExec(action, color) {
3619
+ const { command, args, cwd, output } = action;
3620
+ const cwdPart = cwd ? ` ${shortenPath(cwd, 40)}` : "";
3621
+ const cmdLine = truncateText(
3622
+ `${command} ${args.join(" ")}${cwdPart}`,
3623
+ UI_CONSTANTS.TRUNCATE_TEXT_DEFAULT
3624
+ );
3625
+ const outputLines = output?.split("\n") ?? [];
3626
+ const { visible, remaining } = summarizeOutput(
3627
+ outputLines,
3628
+ RENDER_CONSTANTS.EXEC_OUTPUT_MAX_LINES
3629
+ );
3630
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: `Bash ${cmdLine}`, children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3631
+ visible.map((line, idx) => /* @__PURE__ */ jsx(Text, { dimColor: true, children: truncateText(line, UI_CONSTANTS.TRUNCATE_TEXT_DEFAULT) }, `out-${idx}`)),
3632
+ remaining > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
3633
+ "... +",
3634
+ remaining,
3635
+ " more"
3636
+ ] })
3637
+ ] }) });
4073
3638
  }
4074
- function getRecentExperts(limit) {
4075
- const runs = getAllRuns2();
4076
- const expertMap = /* @__PURE__ */ new Map();
4077
- for (const setting of runs) {
4078
- const expertKey = setting.expertKey;
4079
- if (!expertMap.has(expertKey) || expertMap.get(expertKey).lastUsed < setting.updatedAt) {
4080
- expertMap.set(expertKey, {
4081
- key: expertKey,
4082
- name: expertKey,
4083
- lastUsed: setting.updatedAt
4084
- });
3639
+ function renderQuery(text, runId) {
3640
+ const lines = text.split("\n");
3641
+ const shortRunId = runId.slice(0, 8);
3642
+ 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}`)) }) });
3643
+ }
3644
+ var RunSetting = ({
3645
+ info,
3646
+ eventCount,
3647
+ isEditing,
3648
+ expertName,
3649
+ onQuerySubmit
3650
+ }) => {
3651
+ const { input, handleInput } = useTextInput({
3652
+ onSubmit: onQuerySubmit ?? (() => {
3653
+ })
3654
+ });
3655
+ useInput(handleInput, { isActive: isEditing });
3656
+ const displayExpertName = expertName ?? info.expertName;
3657
+ const skills = info.activeSkills.length > 0 ? info.activeSkills.join(", ") : "";
3658
+ const step = info.currentStep !== void 0 ? String(info.currentStep) : "";
3659
+ const usagePercent = (info.contextWindowUsage * 100).toFixed(1);
3660
+ return /* @__PURE__ */ jsxs(
3661
+ Box,
3662
+ {
3663
+ flexDirection: "column",
3664
+ borderStyle: "single",
3665
+ borderColor: "gray",
3666
+ borderTop: true,
3667
+ borderBottom: false,
3668
+ borderLeft: false,
3669
+ borderRight: false,
3670
+ children: [
3671
+ /* @__PURE__ */ jsxs(Text, { children: [
3672
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "Perstack" }),
3673
+ info.runtimeVersion && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
3674
+ " (v",
3675
+ info.runtimeVersion,
3676
+ ")"
3677
+ ] })
3678
+ ] }),
3679
+ /* @__PURE__ */ jsxs(Text, { children: [
3680
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Expert: " }),
3681
+ /* @__PURE__ */ jsx(Text, { color: "white", children: displayExpertName }),
3682
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: " / Skills: " }),
3683
+ /* @__PURE__ */ jsx(Text, { color: "green", children: skills })
3684
+ ] }),
3685
+ /* @__PURE__ */ jsxs(Text, { children: [
3686
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Status: " }),
3687
+ /* @__PURE__ */ jsx(
3688
+ Text,
3689
+ {
3690
+ color: info.status === "running" ? "green" : info.status === "completed" ? "cyan" : "yellow",
3691
+ children: info.status
3692
+ }
3693
+ ),
3694
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: " / Step: " }),
3695
+ /* @__PURE__ */ jsx(Text, { color: "white", children: step }),
3696
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: " / Events: " }),
3697
+ /* @__PURE__ */ jsx(Text, { color: "white", children: eventCount }),
3698
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: " / Usage: " }),
3699
+ /* @__PURE__ */ jsxs(Text, { color: "white", children: [
3700
+ usagePercent,
3701
+ "%"
3702
+ ] })
3703
+ ] }),
3704
+ /* @__PURE__ */ jsxs(Text, { children: [
3705
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Model: " }),
3706
+ /* @__PURE__ */ jsx(Text, { color: "white", children: info.model })
3707
+ ] }),
3708
+ /* @__PURE__ */ jsxs(Box, { children: [
3709
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Query: " }),
3710
+ isEditing ? /* @__PURE__ */ jsxs(Fragment, { children: [
3711
+ /* @__PURE__ */ jsx(Text, { color: "white", children: input }),
3712
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: "_" })
3713
+ ] }) : /* @__PURE__ */ jsx(Text, { color: "white", children: info.query })
3714
+ ] })
3715
+ ]
4085
3716
  }
4086
- }
4087
- return Array.from(expertMap.values()).sort((a, b) => b.lastUsed - a.lastUsed).slice(0, limit);
3717
+ );
3718
+ };
3719
+ var StreamingDisplay = ({ streaming }) => {
3720
+ const activeRuns = Object.entries(streaming.runs).filter(
3721
+ ([, run]) => run.isReasoningActive || run.isRunResultActive
3722
+ );
3723
+ if (activeRuns.length === 0) return null;
3724
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginY: 1, children: activeRuns.map(([runId, run]) => /* @__PURE__ */ jsx(StreamingRunSection, { run }, runId)) });
3725
+ };
3726
+ function StreamingRunSection({ run }) {
3727
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3728
+ run.isReasoningActive && run.reasoning !== void 0 && /* @__PURE__ */ jsx(StreamingReasoning, { expertKey: run.expertKey, text: run.reasoning }),
3729
+ run.isRunResultActive && run.runResult !== void 0 && /* @__PURE__ */ jsx(StreamingRunResult, { expertKey: run.expertKey, text: run.runResult })
3730
+ ] });
4088
3731
  }
4089
- function getCheckpointById(jobId, checkpointId) {
4090
- const checkpointPath = getCheckpointPath(jobId, checkpointId);
4091
- if (!existsSync(checkpointPath)) {
4092
- throw new Error(`Checkpoint ${checkpointId} not found in job ${jobId}`);
3732
+ function StreamingReasoning({
3733
+ expertKey,
3734
+ text
3735
+ }) {
3736
+ const lines = text.split("\n");
3737
+ const label = `[${formatExpertKey(expertKey)}] Reasoning...`;
3738
+ 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}`)) }) });
3739
+ }
3740
+ function StreamingRunResult({
3741
+ expertKey,
3742
+ text
3743
+ }) {
3744
+ const lines = text.split("\n");
3745
+ const label = `[${formatExpertKey(expertKey)}] Generating...`;
3746
+ 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}`)) }) });
3747
+ }
3748
+ function formatExpertKey(expertKey) {
3749
+ const atIndex = expertKey.lastIndexOf("@");
3750
+ if (atIndex > 0) {
3751
+ return expertKey.substring(0, atIndex);
4093
3752
  }
4094
- const checkpoint = readFileSync(checkpointPath, "utf-8");
4095
- return checkpointSchema.parse(JSON.parse(checkpoint));
3753
+ return expertKey;
4096
3754
  }
4097
- function getCheckpointsWithDetails(jobId) {
4098
- return getCheckpointsByJobId2(jobId).map((cp) => ({
4099
- id: cp.id,
4100
- runId: cp.runId,
4101
- stepNumber: cp.stepNumber,
4102
- contextWindowUsage: cp.contextWindowUsage ?? 0
4103
- })).sort((a, b) => b.stepNumber - a.stepNumber);
3755
+ function getActivityKey(activityOrGroup, index) {
3756
+ return activityOrGroup.id || `activity-${index}`;
4104
3757
  }
4105
- function getAllEventContentsForJob(jobId, maxStepNumber) {
4106
- const runIds = getRunIdsByJobId(jobId);
4107
- const allEvents = [];
4108
- for (const runId of runIds) {
4109
- const events = getEventContents(jobId, runId, maxStepNumber);
4110
- allEvents.push(...events);
3758
+ var RunBox = ({ node, isRoot }) => {
3759
+ if (isRoot) {
3760
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3761
+ node.activities.map((activity, index) => /* @__PURE__ */ jsx(CheckpointActionRow, { action: activity }, getActivityKey(activity, index))),
3762
+ node.children.map((child) => /* @__PURE__ */ jsx(RunBox, { node: child, isRoot: false }, child.runId))
3763
+ ] });
3764
+ }
3765
+ const shortRunId = node.runId.slice(0, 8);
3766
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", marginLeft: 1, children: [
3767
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, bold: true, children: [
3768
+ "[",
3769
+ node.expertKey,
3770
+ "] ",
3771
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
3772
+ "(",
3773
+ shortRunId,
3774
+ ")"
3775
+ ] })
3776
+ ] }),
3777
+ node.activities.map((activity, index) => /* @__PURE__ */ jsx(CheckpointActionRow, { action: activity }, getActivityKey(activity, index))),
3778
+ node.children.map((child) => /* @__PURE__ */ jsx(RunBox, { node: child, isRoot: false }, child.runId))
3779
+ ] });
3780
+ };
3781
+ function getActivityProps(activityOrGroup) {
3782
+ if (activityOrGroup.type === "parallelGroup") {
3783
+ const group = activityOrGroup;
3784
+ const firstActivity = group.activities[0];
3785
+ return {
3786
+ runId: group.runId,
3787
+ expertKey: group.expertKey,
3788
+ delegatedBy: firstActivity?.delegatedBy
3789
+ };
4111
3790
  }
4112
- return allEvents.sort((a, b) => a.timestamp - b.timestamp);
3791
+ return activityOrGroup;
4113
3792
  }
4114
-
4115
- // ../../packages/tui/src/lib/context.ts
4116
- var defaultProvider = "anthropic";
4117
- var defaultModel = "claude-sonnet-4-5";
4118
- async function resolveRunContext(input) {
4119
- const perstackConfig = input.perstackConfig ?? await getPerstackConfig(input.configPath);
4120
- let checkpoint;
4121
- if (input.resumeFrom) {
4122
- if (!input.continueJob) {
4123
- throw new Error("--resume-from requires --continue-job");
3793
+ var ActivityLogPanel = ({ activities }) => {
3794
+ const rootNodes = useMemo(() => {
3795
+ const nodeMap = /* @__PURE__ */ new Map();
3796
+ const roots = [];
3797
+ const orphanChildren = /* @__PURE__ */ new Map();
3798
+ for (const activityOrGroup of activities) {
3799
+ const { runId, expertKey, delegatedBy } = getActivityProps(activityOrGroup);
3800
+ let node = nodeMap.get(runId);
3801
+ if (!node) {
3802
+ node = {
3803
+ runId,
3804
+ expertKey,
3805
+ activities: [],
3806
+ children: []
3807
+ };
3808
+ nodeMap.set(runId, node);
3809
+ const waitingChildren = orphanChildren.get(runId);
3810
+ if (waitingChildren) {
3811
+ for (const child of waitingChildren) {
3812
+ node.children.push(child);
3813
+ const rootIndex = roots.indexOf(child);
3814
+ if (rootIndex !== -1) {
3815
+ roots.splice(rootIndex, 1);
3816
+ }
3817
+ }
3818
+ orphanChildren.delete(runId);
3819
+ }
3820
+ if (delegatedBy) {
3821
+ const parentNode = nodeMap.get(delegatedBy.runId);
3822
+ if (parentNode) {
3823
+ parentNode.children.push(node);
3824
+ } else {
3825
+ const orphans = orphanChildren.get(delegatedBy.runId) ?? [];
3826
+ orphans.push(node);
3827
+ orphanChildren.set(delegatedBy.runId, orphans);
3828
+ roots.push(node);
3829
+ }
3830
+ } else {
3831
+ roots.push(node);
3832
+ }
3833
+ }
3834
+ node.activities.push(activityOrGroup);
4124
3835
  }
4125
- checkpoint = getCheckpointById(input.continueJob, input.resumeFrom);
4126
- } else if (input.continueJob) {
4127
- checkpoint = getMostRecentCheckpoint(input.continueJob);
4128
- } else if (input.continue) {
4129
- checkpoint = getMostRecentCheckpoint();
4130
- }
4131
- if ((input.continue || input.continueJob || input.resumeFrom) && !checkpoint) {
4132
- throw new Error("No checkpoint found");
3836
+ return roots;
3837
+ }, [activities]);
3838
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: rootNodes.map((node) => /* @__PURE__ */ jsx(RunBox, { node, isRoot: true }, node.runId)) });
3839
+ };
3840
+ var ContinueInputPanel = ({
3841
+ isActive,
3842
+ runStatus,
3843
+ onSubmit
3844
+ }) => {
3845
+ const { input, handleInput } = useTextInput({
3846
+ onSubmit: (newQuery) => {
3847
+ if (isActive && newQuery.trim()) {
3848
+ onSubmit(newQuery.trim());
3849
+ }
3850
+ }
3851
+ });
3852
+ useInput(handleInput, { isActive });
3853
+ if (runStatus === "running") {
3854
+ return null;
4133
3855
  }
4134
- if (checkpoint && input.expertKey && checkpoint.expert.key !== input.expertKey) {
4135
- throw new Error(
4136
- `Checkpoint expert key ${checkpoint.expert.key} does not match input expert key ${input.expertKey}`
4137
- );
3856
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", children: [
3857
+ /* @__PURE__ */ jsxs(Text, { children: [
3858
+ /* @__PURE__ */ jsx(Text, { color: runStatus === "completed" ? "green" : "yellow", bold: true, children: runStatus === "completed" ? "Completed" : "Stopped" }),
3859
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: " - Enter a follow-up query or wait to exit" })
3860
+ ] }),
3861
+ /* @__PURE__ */ jsxs(Box, { children: [
3862
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Continue: " }),
3863
+ /* @__PURE__ */ jsx(Text, { children: input }),
3864
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: "_" })
3865
+ ] })
3866
+ ] });
3867
+ };
3868
+ var StatusPanel = ({
3869
+ runtimeInfo,
3870
+ eventCount,
3871
+ runStatus
3872
+ }) => {
3873
+ if (runStatus !== "running") {
3874
+ return null;
4138
3875
  }
4139
- const envPath = input.envPath && input.envPath.length > 0 ? input.envPath : perstackConfig.envPath ?? [".env", ".env.local"];
4140
- const env = getEnv(envPath);
4141
- const provider = input.provider ?? perstackConfig.provider?.providerName ?? defaultProvider;
4142
- const model = input.model ?? perstackConfig.model ?? defaultModel;
4143
- const providerConfig = getProviderConfig(provider, env, perstackConfig.provider);
4144
- const experts = Object.fromEntries(
4145
- Object.entries(perstackConfig.experts ?? {}).map(([name, expert]) => {
4146
- return [
4147
- name,
3876
+ return /* @__PURE__ */ jsx(RunSetting, { info: runtimeInfo, eventCount, isEditing: false });
3877
+ };
3878
+ var useExecutionState = (options) => {
3879
+ const { expertKey, query, config: config2, continueTimeoutMs, historicalEvents, onReady, onComplete } = options;
3880
+ const { exit } = useApp();
3881
+ const runState = useRun();
3882
+ const { runtimeInfo, handleEvent, setQuery } = useRuntimeInfo({
3883
+ initialExpertName: expertKey,
3884
+ initialConfig: config2
3885
+ });
3886
+ const [runStatus, setRunStatus] = useState("running");
3887
+ const [isAcceptingContinue, setIsAcceptingContinue] = useState(false);
3888
+ const timeoutRef = useRef(null);
3889
+ const clearTimeoutIfExists = useCallback(() => {
3890
+ if (timeoutRef.current) {
3891
+ clearTimeout(timeoutRef.current);
3892
+ timeoutRef.current = null;
3893
+ }
3894
+ }, []);
3895
+ const startExitTimeout = useCallback(() => {
3896
+ clearTimeoutIfExists();
3897
+ timeoutRef.current = setTimeout(() => {
3898
+ onComplete({ nextQuery: null });
3899
+ exit();
3900
+ }, continueTimeoutMs);
3901
+ }, [clearTimeoutIfExists, continueTimeoutMs, onComplete, exit]);
3902
+ useEffect(() => {
3903
+ setQuery(query);
3904
+ }, [query, setQuery]);
3905
+ useEffect(() => {
3906
+ if (historicalEvents && historicalEvents.length > 0) {
3907
+ runState.appendHistoricalEvents(historicalEvents);
3908
+ }
3909
+ }, [historicalEvents, runState.appendHistoricalEvents]);
3910
+ useEffect(() => {
3911
+ onReady((event) => {
3912
+ runState.addEvent(event);
3913
+ const result = handleEvent(event);
3914
+ if (result?.completed) {
3915
+ setRunStatus("completed");
3916
+ setIsAcceptingContinue(true);
3917
+ startExitTimeout();
3918
+ } else if (result?.stopped) {
3919
+ setRunStatus("stopped");
3920
+ setIsAcceptingContinue(true);
3921
+ startExitTimeout();
3922
+ }
3923
+ });
3924
+ }, [onReady, runState.addEvent, handleEvent, startExitTimeout]);
3925
+ useEffect(() => {
3926
+ return () => {
3927
+ clearTimeoutIfExists();
3928
+ };
3929
+ }, [clearTimeoutIfExists]);
3930
+ const handleContinueSubmit = useCallback(
3931
+ (newQuery) => {
3932
+ if (isAcceptingContinue && newQuery.trim()) {
3933
+ clearTimeoutIfExists();
3934
+ onComplete({ nextQuery: newQuery.trim() });
3935
+ exit();
3936
+ }
3937
+ },
3938
+ [isAcceptingContinue, clearTimeoutIfExists, onComplete, exit]
3939
+ );
3940
+ return {
3941
+ activities: runState.activities,
3942
+ streaming: runState.streaming,
3943
+ eventCount: runState.eventCount,
3944
+ runtimeInfo,
3945
+ runStatus,
3946
+ isAcceptingContinue,
3947
+ handleContinueSubmit,
3948
+ clearTimeout: clearTimeoutIfExists
3949
+ };
3950
+ };
3951
+ var ExecutionApp = (props) => {
3952
+ const { expertKey, query, config: config2, continueTimeoutMs, historicalEvents, onReady, onComplete } = props;
3953
+ const { exit } = useApp();
3954
+ const state = useExecutionState({
3955
+ expertKey,
3956
+ query,
3957
+ config: config2,
3958
+ continueTimeoutMs,
3959
+ historicalEvents,
3960
+ onReady,
3961
+ onComplete
3962
+ });
3963
+ useInput((input, key) => {
3964
+ if (key.ctrl && input === "c") {
3965
+ state.clearTimeout();
3966
+ exit();
3967
+ }
3968
+ });
3969
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3970
+ /* @__PURE__ */ jsx(ActivityLogPanel, { activities: state.activities }),
3971
+ /* @__PURE__ */ jsx(StreamingDisplay, { streaming: state.streaming }),
3972
+ /* @__PURE__ */ jsx(
3973
+ StatusPanel,
3974
+ {
3975
+ runtimeInfo: state.runtimeInfo,
3976
+ eventCount: state.eventCount,
3977
+ runStatus: state.runStatus
3978
+ }
3979
+ ),
3980
+ /* @__PURE__ */ jsx(
3981
+ ContinueInputPanel,
3982
+ {
3983
+ isActive: state.isAcceptingContinue,
3984
+ runStatus: state.runStatus,
3985
+ onSubmit: state.handleContinueSubmit
3986
+ }
3987
+ )
3988
+ ] });
3989
+ };
3990
+ function renderExecution(params) {
3991
+ const eventQueue = new EventQueue();
3992
+ const result = new Promise((resolve, reject) => {
3993
+ let resolved = false;
3994
+ const { waitUntilExit } = render(
3995
+ /* @__PURE__ */ jsx(
3996
+ ExecutionApp,
4148
3997
  {
4149
- name,
4150
- version: expert.version ?? "1.0.0",
4151
- description: expert.description,
4152
- instruction: expert.instruction,
4153
- skills: expert.skills,
4154
- delegates: expert.delegates,
4155
- tags: expert.tags
3998
+ ...params,
3999
+ onReady: (handler) => {
4000
+ eventQueue.setHandler(handler);
4001
+ },
4002
+ onComplete: (result2) => {
4003
+ resolved = true;
4004
+ resolve(result2);
4005
+ }
4156
4006
  }
4157
- ];
4158
- })
4159
- );
4007
+ )
4008
+ );
4009
+ waitUntilExit().then(() => {
4010
+ if (!resolved) {
4011
+ reject(new Error("Execution cancelled"));
4012
+ }
4013
+ }).catch(reject);
4014
+ });
4160
4015
  return {
4161
- perstackConfig,
4162
- checkpoint,
4163
- env,
4164
- providerConfig,
4165
- model,
4166
- experts
4016
+ result,
4017
+ eventListener: (event) => {
4018
+ eventQueue.emit(event);
4019
+ }
4167
4020
  };
4168
4021
  }
4169
-
4170
- // ../../packages/tui/src/lib/interactive.ts
4171
- function parseInteractiveToolCallResult(query, checkpoint) {
4172
- const lastMessage = checkpoint.messages[checkpoint.messages.length - 1];
4173
- if (lastMessage.type !== "expertMessage") {
4174
- throw new Error("Last message is not a expert message");
4175
- }
4176
- const content = lastMessage.contents.find((c) => c.type === "toolCallPart");
4177
- if (!content || content.type !== "toolCallPart") {
4178
- throw new Error("Last message content is not a tool call part");
4022
+ var selectionReducer = (_state, action) => {
4023
+ switch (action.type) {
4024
+ case "BROWSE_HISTORY":
4025
+ return { type: "browsingHistory", jobs: action.jobs };
4026
+ case "BROWSE_EXPERTS":
4027
+ return { type: "browsingExperts", experts: action.experts };
4028
+ case "SELECT_EXPERT":
4029
+ return { type: "enteringQuery", expertKey: action.expertKey };
4030
+ case "SELECT_JOB":
4031
+ return { type: "browsingCheckpoints", job: action.job, checkpoints: action.checkpoints };
4032
+ case "GO_BACK_FROM_CHECKPOINTS":
4033
+ return { type: "browsingHistory", jobs: action.jobs };
4034
+ default:
4035
+ return assertNever(action);
4179
4036
  }
4180
- const toolCallId = content.toolCallId;
4181
- const toolName = content.toolName;
4182
- const pendingToolCall = checkpoint.pendingToolCalls?.find((tc) => tc.id === toolCallId);
4183
- const skillName = pendingToolCall?.skillName ?? "";
4184
- return {
4185
- interactiveToolCallResult: {
4186
- toolCallId,
4187
- toolName,
4188
- skillName,
4189
- text: query
4037
+ };
4038
+ var SelectionApp = (props) => {
4039
+ const {
4040
+ showHistory,
4041
+ initialExpertKey,
4042
+ initialQuery,
4043
+ initialCheckpoint,
4044
+ configuredExperts,
4045
+ recentExperts,
4046
+ historyJobs,
4047
+ onLoadCheckpoints,
4048
+ onComplete
4049
+ } = props;
4050
+ const { exit } = useApp();
4051
+ const allExperts = useMemo(() => {
4052
+ const configured = configuredExperts.map((e) => ({ ...e, source: "configured" }));
4053
+ const recent = recentExperts.filter((e) => !configured.some((c) => c.key === e.key)).map((e) => ({ ...e, source: "recent" }));
4054
+ return [...configured, ...recent];
4055
+ }, [configuredExperts, recentExperts]);
4056
+ const getInitialState = () => {
4057
+ if (initialExpertKey && !initialQuery) {
4058
+ return { type: "enteringQuery", expertKey: initialExpertKey };
4190
4059
  }
4060
+ if (showHistory && historyJobs.length > 0) {
4061
+ return { type: "browsingHistory", jobs: historyJobs };
4062
+ }
4063
+ return { type: "browsingExperts", experts: allExperts };
4191
4064
  };
4065
+ const [state, dispatch] = useReducer(selectionReducer, void 0, getInitialState);
4066
+ const [selectedCheckpoint, setSelectedCheckpoint] = useState(
4067
+ initialCheckpoint
4068
+ );
4069
+ useEffect(() => {
4070
+ if (initialExpertKey && initialQuery) {
4071
+ onComplete({
4072
+ expertKey: initialExpertKey,
4073
+ query: initialQuery,
4074
+ checkpoint: initialCheckpoint
4075
+ });
4076
+ exit();
4077
+ }
4078
+ }, [initialExpertKey, initialQuery, initialCheckpoint, onComplete, exit]);
4079
+ const { input: queryInput, handleInput: handleQueryInput } = useTextInput({
4080
+ onSubmit: (query) => {
4081
+ if (state.type === "enteringQuery" && query.trim()) {
4082
+ onComplete({
4083
+ expertKey: state.expertKey,
4084
+ query: query.trim(),
4085
+ checkpoint: selectedCheckpoint
4086
+ });
4087
+ exit();
4088
+ }
4089
+ }
4090
+ });
4091
+ useInput(handleQueryInput, { isActive: state.type === "enteringQuery" });
4092
+ const handleExpertSelect = useCallback((expertKey) => {
4093
+ dispatch({ type: "SELECT_EXPERT", expertKey });
4094
+ }, []);
4095
+ const handleJobSelect = useCallback(
4096
+ async (job) => {
4097
+ try {
4098
+ const checkpoints = await onLoadCheckpoints(job);
4099
+ dispatch({ type: "SELECT_JOB", job, checkpoints });
4100
+ } catch {
4101
+ dispatch({ type: "SELECT_JOB", job, checkpoints: [] });
4102
+ }
4103
+ },
4104
+ [onLoadCheckpoints]
4105
+ );
4106
+ const handleJobResume = useCallback(
4107
+ async (job) => {
4108
+ try {
4109
+ const checkpoints = await onLoadCheckpoints(job);
4110
+ const latestCheckpoint = checkpoints[0];
4111
+ if (latestCheckpoint) {
4112
+ setSelectedCheckpoint(latestCheckpoint);
4113
+ }
4114
+ dispatch({ type: "SELECT_EXPERT", expertKey: job.expertKey });
4115
+ } catch {
4116
+ dispatch({ type: "SELECT_EXPERT", expertKey: job.expertKey });
4117
+ }
4118
+ },
4119
+ [onLoadCheckpoints]
4120
+ );
4121
+ const handleCheckpointResume = useCallback(
4122
+ (checkpoint) => {
4123
+ setSelectedCheckpoint(checkpoint);
4124
+ if (state.type === "browsingCheckpoints") {
4125
+ dispatch({ type: "SELECT_EXPERT", expertKey: state.job.expertKey });
4126
+ }
4127
+ },
4128
+ [state]
4129
+ );
4130
+ const handleBack = useCallback(() => {
4131
+ if (state.type === "browsingCheckpoints") {
4132
+ dispatch({ type: "GO_BACK_FROM_CHECKPOINTS", jobs: historyJobs });
4133
+ }
4134
+ }, [state, historyJobs]);
4135
+ const handleSwitchToExperts = useCallback(() => {
4136
+ dispatch({ type: "BROWSE_EXPERTS", experts: allExperts });
4137
+ }, [allExperts]);
4138
+ const handleSwitchToHistory = useCallback(() => {
4139
+ dispatch({ type: "BROWSE_HISTORY", jobs: historyJobs });
4140
+ }, [historyJobs]);
4141
+ useInput((input, key) => {
4142
+ if (key.ctrl && input === "c") {
4143
+ exit();
4144
+ }
4145
+ });
4146
+ const contextValue = useMemo(
4147
+ () => ({
4148
+ onExpertSelect: handleExpertSelect,
4149
+ onQuerySubmit: () => {
4150
+ },
4151
+ // Not used in browser
4152
+ onJobSelect: handleJobSelect,
4153
+ onJobResume: handleJobResume,
4154
+ onCheckpointSelect: () => {
4155
+ },
4156
+ // Not used in selection (no event browsing)
4157
+ onCheckpointResume: handleCheckpointResume,
4158
+ onEventSelect: () => {
4159
+ },
4160
+ // Not used in selection
4161
+ onBack: handleBack,
4162
+ onSwitchToExperts: handleSwitchToExperts,
4163
+ onSwitchToHistory: handleSwitchToHistory
4164
+ }),
4165
+ [
4166
+ handleExpertSelect,
4167
+ handleJobSelect,
4168
+ handleJobResume,
4169
+ handleCheckpointResume,
4170
+ handleBack,
4171
+ handleSwitchToExperts,
4172
+ handleSwitchToHistory
4173
+ ]
4174
+ );
4175
+ if (initialExpertKey && initialQuery) {
4176
+ return null;
4177
+ }
4178
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
4179
+ /* @__PURE__ */ jsx(InputAreaProvider, { value: contextValue, children: (state.type === "browsingHistory" || state.type === "browsingExperts" || state.type === "browsingCheckpoints") && /* @__PURE__ */ jsx(
4180
+ BrowserRouter,
4181
+ {
4182
+ inputState: state,
4183
+ showEventsHint: false
4184
+ }
4185
+ ) }),
4186
+ state.type === "enteringQuery" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", children: [
4187
+ /* @__PURE__ */ jsxs(Text, { children: [
4188
+ /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "Expert:" }),
4189
+ " ",
4190
+ /* @__PURE__ */ jsx(Text, { children: state.expertKey }),
4191
+ selectedCheckpoint && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
4192
+ " (resuming from step ",
4193
+ selectedCheckpoint.stepNumber,
4194
+ ")"
4195
+ ] })
4196
+ ] }),
4197
+ /* @__PURE__ */ jsxs(Box, { children: [
4198
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Query: " }),
4199
+ /* @__PURE__ */ jsx(Text, { children: queryInput }),
4200
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: "_" })
4201
+ ] }),
4202
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Press Enter to start" })
4203
+ ] })
4204
+ ] });
4205
+ };
4206
+ async function renderSelection(params) {
4207
+ return new Promise((resolve, reject) => {
4208
+ let resolved = false;
4209
+ const { waitUntilExit } = render(
4210
+ /* @__PURE__ */ jsx(
4211
+ SelectionApp,
4212
+ {
4213
+ ...params,
4214
+ onComplete: (result) => {
4215
+ resolved = true;
4216
+ resolve(result);
4217
+ }
4218
+ }
4219
+ )
4220
+ );
4221
+ waitUntilExit().then(() => {
4222
+ if (!resolved) {
4223
+ reject(new Error("Selection cancelled"));
4224
+ }
4225
+ }).catch(reject);
4226
+ });
4192
4227
  }
4193
4228
 
4194
4229
  // ../../packages/tui/src/start-handler.ts
@@ -4343,13 +4378,6 @@ var config = parseWithFriendlyError(
4343
4378
  perstackConfigSchema,
4344
4379
  TOML.parse(readFileSync(tomlPath, "utf-8"))
4345
4380
  );
4346
- var PROVIDER_ENV_MAP = {
4347
- anthropic: "ANTHROPIC_API_KEY",
4348
- google: "GOOGLE_GENERATIVE_AI_API_KEY",
4349
- openai: "OPENAI_API_KEY",
4350
- deepseek: "DEEPSEEK_API_KEY",
4351
- "azure-openai": "AZURE_API_KEY"
4352
- };
4353
4381
  new Command().name("create-expert").description("Create and modify Perstack expert definitions").argument("<query>", "Description of the expert to create or modify").action(async (query) => {
4354
4382
  await startHandler(
4355
4383
  "expert",