perstack 0.0.78 → 0.0.80

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,115 +1,37 @@
1
1
  #!/usr/bin/env node
2
+ import { getPerstackConfig, getEnv, resolveRunContext, parseInteractiveToolCallResultJson, parseInteractiveToolCallResult, startCommand } from '../chunk-7D4GKG4H.js';
2
3
  import { Command } from 'commander';
3
4
  import { writeFile, readFile } from 'fs/promises';
4
- import path2 from 'path';
5
+ import path from 'path';
5
6
  import { createApiClient } from '@perstack/api-client';
6
- import { parseWithFriendlyError, runCommandInputSchema, validateEventFilter, createFilteredEventListener, startCommandInputSchema, defaultMaxRetries, defaultTimeout, defaultPerstackApiBaseUrl, checkpointSchema, perstackConfigSchema, expertSchema } from '@perstack/core';
7
- import { collectToolDefinitionsForExpert, findLockfile, loadLockfile, run, runtimeVersion } from '@perstack/runtime';
7
+ import { parseWithFriendlyError, runCommandInputSchema, validateEventFilter, createFilteredEventListener, defaultPerstackApiBaseUrl, expertSchema } from '@perstack/core';
8
+ import { collectToolDefinitionsForExpert, findLockfile, loadLockfile, run } from '@perstack/runtime';
8
9
  import TOML from 'smol-toml';
9
- import dotenv from 'dotenv';
10
- import { existsSync, readdirSync, readFileSync, statSync } from 'fs';
11
- import { createInitialJob, retrieveJob, storeJob, defaultRetrieveCheckpoint, defaultStoreEvent, defaultStoreCheckpoint, getAllRuns, getEventContents, getCheckpointsByJobId, getAllJobs, getCheckpointPath, getRunIdsByJobId } from '@perstack/filesystem-storage';
10
+ import { existsSync, readdirSync, statSync } from 'fs';
11
+ import { createInitialJob, retrieveJob, storeJob, defaultRetrieveCheckpoint, defaultStoreEvent, defaultStoreCheckpoint, getAllRuns, getEventContents, getCheckpointsByJobId, getAllJobs } from '@perstack/filesystem-storage';
12
12
  import { createId } from '@paralleldrive/cuid2';
13
- import { render, useApp, useInput, Box, Text } from 'ink';
14
- import { createContext, useMemo, useReducer, useState, useEffect, useCallback, useRef, useInsertionEffect, useContext } from 'react';
15
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
16
- import { useRun } from '@perstack/react';
17
13
 
18
14
  // package.json
19
15
  var package_default = {
20
16
  name: "perstack",
21
- version: "0.0.78",
17
+ version: "0.0.80",
22
18
  description: "PerStack CLI"};
23
- function getEnv(envPath) {
24
- const env = Object.fromEntries(
25
- Object.entries(process.env).filter(([_, value]) => !!value).map(([key, value]) => [key, value])
26
- );
27
- dotenv.config({ path: envPath, processEnv: env, quiet: true });
28
- return env;
29
- }
30
- var ALLOWED_CONFIG_HOSTS = ["raw.githubusercontent.com"];
31
- async function getPerstackConfig(configPath) {
32
- const configString = await findPerstackConfigString(configPath);
33
- if (configString === null) {
34
- throw new Error("perstack.toml not found. Create one or specify --config path.");
35
- }
36
- return await parsePerstackConfig(configString);
37
- }
38
- function isRemoteUrl(configPath) {
39
- const lower = configPath.toLowerCase();
40
- return lower.startsWith("https://") || lower.startsWith("http://");
41
- }
42
- async function fetchRemoteConfig(url) {
43
- let parsed;
44
- try {
45
- parsed = new URL(url);
46
- } catch {
47
- throw new Error(`Invalid remote config URL: ${url}`);
48
- }
49
- if (parsed.protocol !== "https:") {
50
- throw new Error("Remote config requires HTTPS");
51
- }
52
- if (!ALLOWED_CONFIG_HOSTS.includes(parsed.hostname)) {
53
- throw new Error(`Remote config only allowed from: ${ALLOWED_CONFIG_HOSTS.join(", ")}`);
54
- }
55
- try {
56
- const response = await fetch(url, { redirect: "error" });
57
- if (!response.ok) {
58
- throw new Error(`${response.status} ${response.statusText}`);
59
- }
60
- return await response.text();
61
- } catch (error) {
62
- const message = error instanceof Error ? error.message : String(error);
63
- throw new Error(`Failed to fetch remote config: ${message}`);
64
- }
65
- }
66
- async function findPerstackConfigString(configPath) {
67
- if (configPath) {
68
- if (isRemoteUrl(configPath)) {
69
- return await fetchRemoteConfig(configPath);
70
- }
71
- try {
72
- const tomlString = await readFile(path2.resolve(process.cwd(), configPath), "utf-8");
73
- return tomlString;
74
- } catch {
75
- throw new Error(`Given config path "${configPath}" is not found`);
76
- }
77
- }
78
- return await findPerstackConfigStringRecursively(path2.resolve(process.cwd()));
79
- }
80
- async function findPerstackConfigStringRecursively(cwd) {
81
- try {
82
- const tomlString = await readFile(path2.resolve(cwd, "perstack.toml"), "utf-8");
83
- return tomlString;
84
- } catch {
85
- if (cwd === path2.parse(cwd).root) {
86
- return null;
87
- }
88
- return await findPerstackConfigStringRecursively(path2.dirname(cwd));
89
- }
90
- }
91
- async function parsePerstackConfig(config) {
92
- const toml = TOML.parse(config ?? "");
93
- return parseWithFriendlyError(perstackConfigSchema, toml, "perstack.toml");
94
- }
95
-
96
- // src/install.ts
97
19
  async function findConfigPath(configPath) {
98
20
  if (configPath) {
99
- return path2.resolve(process.cwd(), configPath);
21
+ return path.resolve(process.cwd(), configPath);
100
22
  }
101
23
  return await findConfigPathRecursively(process.cwd());
102
24
  }
103
25
  async function findConfigPathRecursively(cwd) {
104
- const configPath = path2.resolve(cwd, "perstack.toml");
26
+ const configPath = path.resolve(cwd, "perstack.toml");
105
27
  try {
106
28
  await readFile(configPath);
107
29
  return configPath;
108
30
  } catch {
109
- if (cwd === path2.parse(cwd).root) {
31
+ if (cwd === path.parse(cwd).root) {
110
32
  throw new Error("perstack.toml not found. Create one or specify --config path.");
111
33
  }
112
- return await findConfigPathRecursively(path2.dirname(cwd));
34
+ return await findConfigPathRecursively(path.dirname(cwd));
113
35
  }
114
36
  }
115
37
  function toRuntimeExpert(key, expert) {
@@ -288,10 +210,10 @@ var installCommand = new Command().command("install").description("Generate pers
288
210
  const lockfile = {
289
211
  version: "1",
290
212
  generatedAt: Date.now(),
291
- configPath: path2.basename(configPath),
213
+ configPath: path.basename(configPath),
292
214
  experts: lockfileExperts
293
215
  };
294
- const lockfilePath = path2.join(path2.dirname(configPath), "perstack.lock");
216
+ const lockfilePath = path.join(path.dirname(configPath), "perstack.lock");
295
217
  const lockfileContent = generateLockfileToml(lockfile);
296
218
  await writeFile(lockfilePath, lockfileContent, "utf-8");
297
219
  console.log(`Generated ${lockfilePath}`);
@@ -364,7 +286,7 @@ function createLogDataFetcher(storage) {
364
286
  }
365
287
  function getJobDirMtime(basePath, jobId) {
366
288
  try {
367
- const jobDir = path2.join(basePath, "jobs", jobId);
289
+ const jobDir = path.join(basePath, "jobs", jobId);
368
290
  const stats = statSync(jobDir);
369
291
  return stats.mtimeMs;
370
292
  } catch {
@@ -380,7 +302,7 @@ function createStorageAdapter(basePath) {
380
302
  getEventContents: async (jobId, runId, maxStep) => getEventContents(jobId, runId, maxStep),
381
303
  getAllRuns: async () => getAllRuns(),
382
304
  getJobIds: () => {
383
- const jobsDir = path2.join(basePath, "jobs");
305
+ const jobsDir = path.join(basePath, "jobs");
384
306
  if (!existsSync(jobsDir)) return [];
385
307
  return readdirSync(jobsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
386
308
  },
@@ -444,18 +366,18 @@ function parseFilterExpression(expression) {
444
366
  const value = parseValue(trimmedValue);
445
367
  return { field, operator, value };
446
368
  }
447
- function parseFieldPath(path4) {
369
+ function parseFieldPath(path3) {
448
370
  const parts = [];
449
371
  let current = "";
450
372
  let i = 0;
451
- while (i < path4.length) {
452
- if (path4[i] === ".") {
373
+ while (i < path3.length) {
374
+ if (path3[i] === ".") {
453
375
  if (current) {
454
376
  parts.push(current);
455
377
  current = "";
456
378
  }
457
379
  i++;
458
- } else if (path4[i] === "[" && path4[i + 1] === "]") {
380
+ } else if (path3[i] === "[" && path3[i + 1] === "]") {
459
381
  if (current) {
460
382
  parts.push(current);
461
383
  current = "";
@@ -463,7 +385,7 @@ function parseFieldPath(path4) {
463
385
  parts.push("*");
464
386
  i += 2;
465
387
  } else {
466
- current += path4[i];
388
+ current += path3[i];
467
389
  i++;
468
390
  }
469
391
  }
@@ -498,10 +420,10 @@ function evaluateCondition(event, condition) {
498
420
  }
499
421
  return compareValues(actualValue, operator, value);
500
422
  }
501
- function getFieldValue(obj, path4) {
423
+ function getFieldValue(obj, path3) {
502
424
  let current = obj;
503
- for (let i = 0; i < path4.length; i++) {
504
- const segment = path4[i];
425
+ for (let i = 0; i < path3.length; i++) {
426
+ const segment = path3[i];
505
427
  if (current === null || current === void 0) {
506
428
  return void 0;
507
429
  }
@@ -509,7 +431,7 @@ function getFieldValue(obj, path4) {
509
431
  if (!Array.isArray(current)) {
510
432
  return void 0;
511
433
  }
512
- const remainingPath = path4.slice(i + 1);
434
+ const remainingPath = path3.slice(i + 1);
513
435
  if (remainingPath.length === 0) {
514
436
  return current;
515
437
  }
@@ -1016,262 +938,6 @@ async function buildOutput(fetcher, options, filterOptions, storagePath) {
1016
938
  matchedAfterPagination: result.matchedAfterPagination
1017
939
  };
1018
940
  }
1019
-
1020
- // src/lib/provider-config.ts
1021
- function getProviderConfig(provider, env, providerTable) {
1022
- const setting = providerTable?.setting ?? {};
1023
- switch (provider) {
1024
- case "anthropic": {
1025
- const apiKey = env.ANTHROPIC_API_KEY;
1026
- if (!apiKey) throw new Error("ANTHROPIC_API_KEY is not set");
1027
- return {
1028
- providerName: "anthropic",
1029
- apiKey,
1030
- baseUrl: setting.baseUrl ?? env.ANTHROPIC_BASE_URL,
1031
- headers: setting.headers
1032
- };
1033
- }
1034
- case "google": {
1035
- const apiKey = env.GOOGLE_GENERATIVE_AI_API_KEY;
1036
- if (!apiKey) throw new Error("GOOGLE_GENERATIVE_AI_API_KEY is not set");
1037
- return {
1038
- providerName: "google",
1039
- apiKey,
1040
- baseUrl: setting.baseUrl ?? env.GOOGLE_GENERATIVE_AI_BASE_URL,
1041
- headers: setting.headers
1042
- };
1043
- }
1044
- case "openai": {
1045
- const apiKey = env.OPENAI_API_KEY;
1046
- if (!apiKey) throw new Error("OPENAI_API_KEY is not set");
1047
- return {
1048
- providerName: "openai",
1049
- apiKey,
1050
- baseUrl: setting.baseUrl ?? env.OPENAI_BASE_URL,
1051
- organization: setting.organization ?? env.OPENAI_ORGANIZATION,
1052
- project: setting.project ?? env.OPENAI_PROJECT,
1053
- name: setting.name,
1054
- headers: setting.headers
1055
- };
1056
- }
1057
- case "ollama": {
1058
- return {
1059
- providerName: "ollama",
1060
- baseUrl: setting.baseUrl ?? env.OLLAMA_BASE_URL,
1061
- headers: setting.headers
1062
- };
1063
- }
1064
- case "azure-openai": {
1065
- const apiKey = env.AZURE_API_KEY;
1066
- if (!apiKey) throw new Error("AZURE_API_KEY is not set");
1067
- const resourceName = setting.resourceName ?? env.AZURE_RESOURCE_NAME;
1068
- const baseUrl = setting.baseUrl ?? env.AZURE_BASE_URL;
1069
- if (!resourceName && !baseUrl) throw new Error("AZURE_RESOURCE_NAME or baseUrl is not set");
1070
- return {
1071
- providerName: "azure-openai",
1072
- apiKey,
1073
- resourceName,
1074
- apiVersion: setting.apiVersion ?? env.AZURE_API_VERSION,
1075
- baseUrl,
1076
- headers: setting.headers,
1077
- useDeploymentBasedUrls: setting.useDeploymentBasedUrls
1078
- };
1079
- }
1080
- case "amazon-bedrock": {
1081
- const accessKeyId = env.AWS_ACCESS_KEY_ID;
1082
- const secretAccessKey = env.AWS_SECRET_ACCESS_KEY;
1083
- const sessionToken = env.AWS_SESSION_TOKEN;
1084
- if (!accessKeyId) throw new Error("AWS_ACCESS_KEY_ID is not set");
1085
- if (!secretAccessKey) throw new Error("AWS_SECRET_ACCESS_KEY is not set");
1086
- const region = setting.region ?? env.AWS_REGION;
1087
- if (!region) throw new Error("AWS_REGION is not set");
1088
- return {
1089
- providerName: "amazon-bedrock",
1090
- accessKeyId,
1091
- secretAccessKey,
1092
- region,
1093
- sessionToken
1094
- };
1095
- }
1096
- case "google-vertex": {
1097
- return {
1098
- providerName: "google-vertex",
1099
- project: setting.project ?? env.GOOGLE_VERTEX_PROJECT,
1100
- location: setting.location ?? env.GOOGLE_VERTEX_LOCATION,
1101
- baseUrl: setting.baseUrl ?? env.GOOGLE_VERTEX_BASE_URL,
1102
- headers: setting.headers
1103
- };
1104
- }
1105
- case "deepseek": {
1106
- const apiKey = env.DEEPSEEK_API_KEY;
1107
- if (!apiKey) throw new Error("DEEPSEEK_API_KEY is not set");
1108
- return {
1109
- providerName: "deepseek",
1110
- apiKey,
1111
- baseUrl: setting.baseUrl ?? env.DEEPSEEK_BASE_URL,
1112
- headers: setting.headers
1113
- };
1114
- }
1115
- }
1116
- }
1117
- function getAllJobs2() {
1118
- return getAllJobs();
1119
- }
1120
- function getAllRuns2() {
1121
- return getAllRuns();
1122
- }
1123
- function getMostRecentRun() {
1124
- const runs = getAllRuns2();
1125
- if (runs.length === 0) {
1126
- throw new Error("No runs found");
1127
- }
1128
- return runs[0];
1129
- }
1130
- function getCheckpointsByJobId2(jobId) {
1131
- return getCheckpointsByJobId(jobId);
1132
- }
1133
- function getMostRecentCheckpoint(jobId) {
1134
- const targetJobId = jobId ?? getMostRecentRun().jobId;
1135
- const checkpoints = getCheckpointsByJobId2(targetJobId);
1136
- if (checkpoints.length === 0) {
1137
- throw new Error(`No checkpoints found for job ${targetJobId}`);
1138
- }
1139
- return checkpoints[checkpoints.length - 1];
1140
- }
1141
- function getRecentExperts(limit) {
1142
- const runs = getAllRuns2();
1143
- const expertMap = /* @__PURE__ */ new Map();
1144
- for (const setting of runs) {
1145
- const expertKey = setting.expertKey;
1146
- if (!expertMap.has(expertKey) || expertMap.get(expertKey).lastUsed < setting.updatedAt) {
1147
- expertMap.set(expertKey, {
1148
- key: expertKey,
1149
- name: expertKey,
1150
- lastUsed: setting.updatedAt
1151
- });
1152
- }
1153
- }
1154
- return Array.from(expertMap.values()).sort((a, b) => b.lastUsed - a.lastUsed).slice(0, limit);
1155
- }
1156
- function getCheckpointById(jobId, checkpointId) {
1157
- const checkpointPath = getCheckpointPath(jobId, checkpointId);
1158
- if (!existsSync(checkpointPath)) {
1159
- throw new Error(`Checkpoint ${checkpointId} not found in job ${jobId}`);
1160
- }
1161
- const checkpoint = readFileSync(checkpointPath, "utf-8");
1162
- return checkpointSchema.parse(JSON.parse(checkpoint));
1163
- }
1164
- function getCheckpointsWithDetails(jobId) {
1165
- return getCheckpointsByJobId2(jobId).map((cp) => ({
1166
- id: cp.id,
1167
- runId: cp.runId,
1168
- stepNumber: cp.stepNumber,
1169
- contextWindowUsage: cp.contextWindowUsage ?? 0
1170
- })).sort((a, b) => b.stepNumber - a.stepNumber);
1171
- }
1172
- function getAllEventContentsForJob(jobId, maxStepNumber) {
1173
- const runIds = getRunIdsByJobId(jobId);
1174
- const allEvents = [];
1175
- for (const runId of runIds) {
1176
- const events = getEventContents(jobId, runId, maxStepNumber);
1177
- allEvents.push(...events);
1178
- }
1179
- return allEvents.sort((a, b) => a.timestamp - b.timestamp);
1180
- }
1181
-
1182
- // src/lib/context.ts
1183
- var defaultProvider = "anthropic";
1184
- var defaultModel = "claude-sonnet-4-5";
1185
- async function resolveRunContext(input) {
1186
- const perstackConfig = await getPerstackConfig(input.configPath);
1187
- let checkpoint;
1188
- if (input.resumeFrom) {
1189
- if (!input.continueJob) {
1190
- throw new Error("--resume-from requires --continue-job");
1191
- }
1192
- checkpoint = getCheckpointById(input.continueJob, input.resumeFrom);
1193
- } else if (input.continueJob) {
1194
- checkpoint = getMostRecentCheckpoint(input.continueJob);
1195
- } else if (input.continue) {
1196
- checkpoint = getMostRecentCheckpoint();
1197
- }
1198
- if ((input.continue || input.continueJob || input.resumeFrom) && !checkpoint) {
1199
- throw new Error("No checkpoint found");
1200
- }
1201
- if (checkpoint && input.expertKey && checkpoint.expert.key !== input.expertKey) {
1202
- throw new Error(
1203
- `Checkpoint expert key ${checkpoint.expert.key} does not match input expert key ${input.expertKey}`
1204
- );
1205
- }
1206
- const envPath = input.envPath && input.envPath.length > 0 ? input.envPath : perstackConfig.envPath ?? [".env", ".env.local"];
1207
- const env = getEnv(envPath);
1208
- const provider = input.provider ?? perstackConfig.provider?.providerName ?? defaultProvider;
1209
- const model = input.model ?? perstackConfig.model ?? defaultModel;
1210
- const providerConfig = getProviderConfig(provider, env, perstackConfig.provider);
1211
- const experts = Object.fromEntries(
1212
- Object.entries(perstackConfig.experts ?? {}).map(([name, expert]) => {
1213
- return [
1214
- name,
1215
- {
1216
- name,
1217
- version: expert.version ?? "1.0.0",
1218
- description: expert.description,
1219
- instruction: expert.instruction,
1220
- skills: expert.skills,
1221
- delegates: expert.delegates,
1222
- tags: expert.tags
1223
- }
1224
- ];
1225
- })
1226
- );
1227
- return {
1228
- perstackConfig,
1229
- checkpoint,
1230
- env,
1231
- providerConfig,
1232
- model,
1233
- experts
1234
- };
1235
- }
1236
-
1237
- // src/lib/interactive.ts
1238
- function parseInteractiveToolCallResult(query, checkpoint) {
1239
- const lastMessage = checkpoint.messages[checkpoint.messages.length - 1];
1240
- if (lastMessage.type !== "expertMessage") {
1241
- throw new Error("Last message is not a expert message");
1242
- }
1243
- const content = lastMessage.contents.find((c) => c.type === "toolCallPart");
1244
- if (!content || content.type !== "toolCallPart") {
1245
- throw new Error("Last message content is not a tool call part");
1246
- }
1247
- const toolCallId = content.toolCallId;
1248
- const toolName = content.toolName;
1249
- const pendingToolCall = checkpoint.pendingToolCalls?.find((tc) => tc.id === toolCallId);
1250
- const skillName = pendingToolCall?.skillName ?? "";
1251
- return {
1252
- interactiveToolCallResult: {
1253
- toolCallId,
1254
- toolName,
1255
- skillName,
1256
- text: query
1257
- }
1258
- };
1259
- }
1260
- function parseInteractiveToolCallResultJson(query) {
1261
- try {
1262
- const parsed = JSON.parse(query);
1263
- if (typeof parsed === "object" && parsed !== null && "toolCallId" in parsed && "toolName" in parsed && "skillName" in parsed && "text" in parsed) {
1264
- return {
1265
- interactiveToolCallResult: parsed
1266
- };
1267
- }
1268
- return null;
1269
- } catch {
1270
- return null;
1271
- }
1272
- }
1273
-
1274
- // src/run.ts
1275
941
  var defaultEventListener = (event) => console.log(JSON.stringify(event));
1276
942
  var runCommand = new Command().command("run").description("Run Perstack with JSON output").argument("<expertKey>", "Expert key to run").argument("<query>", "Query to run").option("--config <configPath>", "Path to perstack.toml config file").option("--provider <provider>", "Provider to use").option("--model <model>", "Model to use").option(
1277
943
  "--reasoning-budget <budget>",
@@ -1369,1647 +1035,6 @@ var runCommand = new Command().command("run").description("Run Perstack with JSO
1369
1035
  }
1370
1036
  });
1371
1037
 
1372
- // src/tui/utils/event-queue.ts
1373
- var defaultErrorLogger = (message, error) => {
1374
- console.error(message, error);
1375
- };
1376
- var EventQueue = class _EventQueue {
1377
- static MAX_PENDING_EVENTS = 1e3;
1378
- pendingEvents = [];
1379
- handler = null;
1380
- onError;
1381
- errorLogger;
1382
- constructor(options) {
1383
- this.onError = options?.onError;
1384
- this.errorLogger = options?.errorLogger ?? defaultErrorLogger;
1385
- }
1386
- setHandler(fn) {
1387
- this.handler = fn;
1388
- for (const event of this.pendingEvents) {
1389
- this.safeHandle(event);
1390
- }
1391
- this.pendingEvents.length = 0;
1392
- }
1393
- emit(event) {
1394
- if (this.handler) {
1395
- this.safeHandle(event);
1396
- } else {
1397
- if (this.pendingEvents.length >= _EventQueue.MAX_PENDING_EVENTS) {
1398
- this.pendingEvents.shift();
1399
- }
1400
- this.pendingEvents.push(event);
1401
- }
1402
- }
1403
- safeHandle(event) {
1404
- try {
1405
- this.handler?.(event);
1406
- } catch (error) {
1407
- if (this.onError) {
1408
- this.onError(error);
1409
- } else {
1410
- this.errorLogger("EventQueue handler error:", error);
1411
- }
1412
- }
1413
- }
1414
- };
1415
- var InputAreaContext = createContext(null);
1416
- var InputAreaProvider = InputAreaContext.Provider;
1417
- var useInputAreaContext = () => {
1418
- const context = useContext(InputAreaContext);
1419
- if (!context) {
1420
- throw new Error("useInputAreaContext must be used within InputAreaProvider");
1421
- }
1422
- return context;
1423
- };
1424
-
1425
- // src/tui/helpers.ts
1426
- var truncateText = (text, maxLength) => {
1427
- if (text.length <= maxLength) return text;
1428
- return `${text.slice(0, maxLength)}...`;
1429
- };
1430
- var assertNever = (x) => {
1431
- throw new Error(`Unexpected value: ${x}`);
1432
- };
1433
- var formatTimestamp2 = (timestamp) => new Date(timestamp).toLocaleString();
1434
- var shortenPath = (fullPath, maxLength = 60) => {
1435
- if (fullPath.length <= maxLength) return fullPath;
1436
- const parts = fullPath.split("/");
1437
- if (parts.length <= 2) return fullPath;
1438
- const filename = parts[parts.length - 1] ?? "";
1439
- const parentDir = parts[parts.length - 2] ?? "";
1440
- const shortened = `.../${parentDir}/${filename}`;
1441
- if (shortened.length <= maxLength) return shortened;
1442
- return `.../${filename}`;
1443
- };
1444
- var summarizeOutput = (lines, maxLines) => {
1445
- const filtered = lines.filter((l) => l.trim());
1446
- const visible = filtered.slice(0, maxLines);
1447
- const remaining = filtered.length - visible.length;
1448
- return { visible, remaining };
1449
- };
1450
-
1451
- // src/tui/constants.ts
1452
- var UI_CONSTANTS = {
1453
- MAX_VISIBLE_LIST_ITEMS: 25,
1454
- TRUNCATE_TEXT_MEDIUM: 80,
1455
- TRUNCATE_TEXT_DEFAULT: 100};
1456
- var RENDER_CONSTANTS = {
1457
- EXEC_OUTPUT_MAX_LINES: 4,
1458
- NEW_TODO_MAX_PREVIEW: 3,
1459
- LIST_DIR_MAX_ITEMS: 4
1460
- };
1461
- var INDICATOR = {
1462
- BULLET: "\u25CF",
1463
- TREE: "\u2514",
1464
- ELLIPSIS: "..."
1465
- };
1466
- var STOP_EVENT_TYPES = [
1467
- "stopRunByInteractiveTool",
1468
- "stopRunByDelegate",
1469
- "stopRunByExceededMaxSteps"
1470
- ];
1471
- var KEY_BINDINGS = {
1472
- NAVIGATE_UP: "\u2191",
1473
- NAVIGATE_DOWN: "\u2193",
1474
- SELECT: "Enter",
1475
- BACK: "b",
1476
- ESCAPE: "Esc",
1477
- INPUT_MODE: "i",
1478
- HISTORY: "h",
1479
- NEW: "n",
1480
- CHECKPOINTS: "c",
1481
- EVENTS: "e",
1482
- RESUME: "Enter"};
1483
- var KEY_HINTS = {
1484
- NAVIGATE: `${KEY_BINDINGS.NAVIGATE_UP}${KEY_BINDINGS.NAVIGATE_DOWN}:Navigate`,
1485
- SELECT: `${KEY_BINDINGS.SELECT}:Select`,
1486
- RESUME: `${KEY_BINDINGS.RESUME}:Resume`,
1487
- BACK: `${KEY_BINDINGS.BACK}:Back`,
1488
- CANCEL: `${KEY_BINDINGS.ESCAPE}:Cancel`,
1489
- INPUT: `${KEY_BINDINGS.INPUT_MODE}:Input`,
1490
- HISTORY: `${KEY_BINDINGS.HISTORY}:History`,
1491
- NEW: `${KEY_BINDINGS.NEW}:New Run`,
1492
- CHECKPOINTS: `${KEY_BINDINGS.CHECKPOINTS}:Checkpoints`,
1493
- EVENTS: `${KEY_BINDINGS.EVENTS}:Events`};
1494
- var useLatestRef = (value) => {
1495
- const ref = useRef(value);
1496
- useInsertionEffect(() => {
1497
- ref.current = value;
1498
- });
1499
- return ref;
1500
- };
1501
- var useListNavigation = (options) => {
1502
- const { items, onSelect, onBack } = options;
1503
- const [selectedIndex, setSelectedIndex] = useState(0);
1504
- useEffect(() => {
1505
- if (selectedIndex >= items.length && items.length > 0) {
1506
- setSelectedIndex(items.length - 1);
1507
- }
1508
- }, [items.length, selectedIndex]);
1509
- const handleNavigation = useCallback(
1510
- (inputChar, key) => {
1511
- if (key.upArrow && items.length > 0) {
1512
- setSelectedIndex((prev) => Math.max(0, prev - 1));
1513
- return true;
1514
- }
1515
- if (key.downArrow && items.length > 0) {
1516
- setSelectedIndex((prev) => Math.min(items.length - 1, prev + 1));
1517
- return true;
1518
- }
1519
- if (key.return && items[selectedIndex]) {
1520
- onSelect?.(items[selectedIndex]);
1521
- return true;
1522
- }
1523
- if ((key.escape || inputChar === "b") && onBack) {
1524
- onBack();
1525
- return true;
1526
- }
1527
- return false;
1528
- },
1529
- [items, selectedIndex, onSelect, onBack]
1530
- );
1531
- return { selectedIndex, handleNavigation };
1532
- };
1533
- var useTextInput = (options) => {
1534
- const [input, setInput] = useState("");
1535
- const inputRef = useLatestRef(input);
1536
- const onSubmitRef = useLatestRef(options.onSubmit);
1537
- const onCancelRef = useLatestRef(options.onCancel);
1538
- const handleInput = useCallback(
1539
- (inputChar, key) => {
1540
- if (key.escape) {
1541
- setInput("");
1542
- onCancelRef.current?.();
1543
- return;
1544
- }
1545
- if (key.return && inputRef.current.trim()) {
1546
- onSubmitRef.current(inputRef.current.trim());
1547
- setInput("");
1548
- return;
1549
- }
1550
- if (key.backspace || key.delete) {
1551
- setInput((prev) => prev.slice(0, -1));
1552
- return;
1553
- }
1554
- if (!key.ctrl && !key.meta && inputChar) {
1555
- setInput((prev) => prev + inputChar);
1556
- }
1557
- },
1558
- [inputRef, onSubmitRef, onCancelRef]
1559
- );
1560
- const reset = useCallback(() => {
1561
- setInput("");
1562
- }, []);
1563
- return { input, handleInput, reset };
1564
- };
1565
- var ListBrowser = ({
1566
- title,
1567
- items,
1568
- renderItem,
1569
- onSelect,
1570
- emptyMessage = "No items found",
1571
- extraKeyHandler,
1572
- onBack,
1573
- maxItems = UI_CONSTANTS.MAX_VISIBLE_LIST_ITEMS
1574
- }) => {
1575
- const { selectedIndex, handleNavigation } = useListNavigation({
1576
- items,
1577
- onSelect,
1578
- onBack
1579
- });
1580
- useInput((inputChar, key) => {
1581
- if (extraKeyHandler?.(inputChar, key, items[selectedIndex])) return;
1582
- handleNavigation(inputChar, key);
1583
- });
1584
- const { scrollOffset, displayItems } = useMemo(() => {
1585
- const offset = Math.max(0, Math.min(selectedIndex - maxItems + 1, items.length - maxItems));
1586
- return {
1587
- scrollOffset: offset,
1588
- displayItems: items.slice(offset, offset + maxItems)
1589
- };
1590
- }, [items, selectedIndex, maxItems]);
1591
- const hasMoreAbove = scrollOffset > 0;
1592
- const hasMoreBelow = scrollOffset + maxItems < items.length;
1593
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
1594
- /* @__PURE__ */ jsxs(Box, { children: [
1595
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: title }),
1596
- items.length > maxItems && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
1597
- " ",
1598
- "(",
1599
- selectedIndex + 1,
1600
- "/",
1601
- items.length,
1602
- ")"
1603
- ] })
1604
- ] }),
1605
- hasMoreAbove && /* @__PURE__ */ jsx(Text, { color: "gray", children: INDICATOR.ELLIPSIS }),
1606
- /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: displayItems.length === 0 ? /* @__PURE__ */ jsx(Text, { color: "gray", children: emptyMessage }) : displayItems.map((item, index) => {
1607
- const actualIndex = scrollOffset + index;
1608
- return renderItem(item, actualIndex === selectedIndex, actualIndex);
1609
- }) }),
1610
- hasMoreBelow && /* @__PURE__ */ jsx(Text, { color: "gray", children: INDICATOR.ELLIPSIS })
1611
- ] });
1612
- };
1613
- var BrowsingCheckpointsInput = ({
1614
- job,
1615
- checkpoints,
1616
- onCheckpointSelect,
1617
- onCheckpointResume,
1618
- onBack,
1619
- showEventsHint = true
1620
- }) => /* @__PURE__ */ jsx(
1621
- ListBrowser,
1622
- {
1623
- title: `Checkpoints for ${job.expertKey} ${KEY_HINTS.NAVIGATE} ${KEY_HINTS.RESUME} ${showEventsHint ? KEY_HINTS.EVENTS : ""} ${KEY_HINTS.BACK}`.trim(),
1624
- items: checkpoints,
1625
- onSelect: onCheckpointResume,
1626
- onBack,
1627
- emptyMessage: "No checkpoints found",
1628
- extraKeyHandler: (char, _key, selected) => {
1629
- if (char === "e" && selected) {
1630
- onCheckpointSelect(selected);
1631
- return true;
1632
- }
1633
- return false;
1634
- },
1635
- renderItem: (cp, isSelected) => /* @__PURE__ */ jsxs(Text, { color: isSelected ? "cyan" : "gray", children: [
1636
- isSelected ? ">" : " ",
1637
- " Step ",
1638
- cp.stepNumber,
1639
- " (",
1640
- cp.id,
1641
- ")"
1642
- ] }, cp.id)
1643
- }
1644
- );
1645
- var BrowsingEventDetailInput = ({ event, onBack }) => {
1646
- useInput((input, key) => {
1647
- if (input === "b" || key.escape) {
1648
- onBack();
1649
- }
1650
- });
1651
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
1652
- /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
1653
- /* @__PURE__ */ jsx(Text, { bold: true, children: "Event Detail" }),
1654
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1655
- " ",
1656
- KEY_HINTS.BACK
1657
- ] })
1658
- ] }),
1659
- /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [
1660
- /* @__PURE__ */ jsxs(Text, { children: [
1661
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Type: " }),
1662
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: event.type })
1663
- ] }),
1664
- /* @__PURE__ */ jsxs(Text, { children: [
1665
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Step: " }),
1666
- /* @__PURE__ */ jsx(Text, { children: event.stepNumber })
1667
- ] }),
1668
- /* @__PURE__ */ jsxs(Text, { children: [
1669
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Timestamp: " }),
1670
- /* @__PURE__ */ jsx(Text, { children: formatTimestamp2(event.timestamp) })
1671
- ] }),
1672
- /* @__PURE__ */ jsxs(Text, { children: [
1673
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "ID: " }),
1674
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: event.id })
1675
- ] }),
1676
- /* @__PURE__ */ jsxs(Text, { children: [
1677
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Run ID: " }),
1678
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: event.runId })
1679
- ] })
1680
- ] })
1681
- ] });
1682
- };
1683
- var BrowsingEventsInput = ({
1684
- checkpoint,
1685
- events,
1686
- onEventSelect,
1687
- onBack
1688
- }) => /* @__PURE__ */ jsx(
1689
- ListBrowser,
1690
- {
1691
- title: `Events for Step ${checkpoint.stepNumber} ${KEY_HINTS.NAVIGATE} ${KEY_HINTS.SELECT} ${KEY_HINTS.BACK}`,
1692
- items: events,
1693
- onSelect: onEventSelect,
1694
- onBack,
1695
- emptyMessage: "No events found",
1696
- renderItem: (ev, isSelected) => /* @__PURE__ */ jsxs(Text, { color: isSelected ? "cyan" : "gray", children: [
1697
- isSelected ? ">" : " ",
1698
- " [",
1699
- ev.type,
1700
- "] Step ",
1701
- ev.stepNumber,
1702
- " (",
1703
- formatTimestamp2(ev.timestamp),
1704
- ")"
1705
- ] }, ev.id)
1706
- }
1707
- );
1708
- var useRuntimeInfo = (options) => {
1709
- const [runtimeInfo, setRuntimeInfo] = useState({
1710
- status: "initializing",
1711
- runtimeVersion: options.initialConfig.runtimeVersion,
1712
- expertName: options.initialExpertName,
1713
- model: options.initialConfig.model,
1714
- maxSteps: options.initialConfig.maxSteps,
1715
- maxRetries: options.initialConfig.maxRetries,
1716
- timeout: options.initialConfig.timeout,
1717
- activeSkills: [],
1718
- contextWindowUsage: options.initialConfig.contextWindowUsage
1719
- });
1720
- const handleEvent = useCallback((event) => {
1721
- if (event.type === "initializeRuntime") {
1722
- setRuntimeInfo((prev) => ({
1723
- runtimeVersion: event.runtimeVersion,
1724
- expertName: event.expertName,
1725
- model: event.model,
1726
- maxSteps: event.maxSteps,
1727
- maxRetries: event.maxRetries,
1728
- timeout: event.timeout,
1729
- currentStep: 1,
1730
- status: "running",
1731
- query: event.query,
1732
- statusChangedAt: Date.now(),
1733
- activeSkills: [],
1734
- contextWindowUsage: prev.contextWindowUsage
1735
- }));
1736
- return { initialized: true };
1737
- }
1738
- if (event.type === "skillConnected") {
1739
- setRuntimeInfo((prev) => ({
1740
- ...prev,
1741
- activeSkills: [...prev.activeSkills, event.skillName]
1742
- }));
1743
- return null;
1744
- }
1745
- if (event.type === "skillDisconnected") {
1746
- setRuntimeInfo((prev) => ({
1747
- ...prev,
1748
- activeSkills: prev.activeSkills.filter((s) => s !== event.skillName)
1749
- }));
1750
- return null;
1751
- }
1752
- if ("stepNumber" in event) {
1753
- const isComplete = event.type === "completeRun";
1754
- const isStopped = STOP_EVENT_TYPES.includes(event.type);
1755
- const checkpoint = "nextCheckpoint" in event ? event.nextCheckpoint : "checkpoint" in event ? event.checkpoint : void 0;
1756
- setRuntimeInfo((prev) => ({
1757
- ...prev,
1758
- currentStep: event.stepNumber,
1759
- status: isComplete ? "completed" : isStopped ? "stopped" : "running",
1760
- ...isComplete || isStopped ? { statusChangedAt: Date.now() } : {},
1761
- ...checkpoint?.contextWindowUsage !== void 0 ? { contextWindowUsage: checkpoint.contextWindowUsage } : {}
1762
- }));
1763
- if (isComplete) return { completed: true };
1764
- if (isStopped) return { stopped: true };
1765
- }
1766
- return null;
1767
- }, []);
1768
- const setExpertName = useCallback((expertName) => {
1769
- setRuntimeInfo((prev) => ({ ...prev, expertName }));
1770
- }, []);
1771
- const setQuery = useCallback((query) => {
1772
- setRuntimeInfo((prev) => ({ ...prev, query }));
1773
- }, []);
1774
- const setCurrentStep = useCallback((step) => {
1775
- setRuntimeInfo((prev) => ({ ...prev, currentStep: step }));
1776
- }, []);
1777
- const setContextWindowUsage = useCallback((contextWindowUsage) => {
1778
- setRuntimeInfo((prev) => ({ ...prev, contextWindowUsage }));
1779
- }, []);
1780
- return {
1781
- runtimeInfo,
1782
- handleEvent,
1783
- setExpertName,
1784
- setQuery,
1785
- setCurrentStep,
1786
- setContextWindowUsage
1787
- };
1788
- };
1789
- var useExpertSelector = (options) => {
1790
- const { experts, onExpertSelect, extraKeyHandler } = options;
1791
- const [inputMode, setInputMode] = useState(false);
1792
- const { selectedIndex, handleNavigation } = useListNavigation({
1793
- items: experts,
1794
- onSelect: (expert) => onExpertSelect(expert.key)
1795
- });
1796
- const { input, handleInput, reset } = useTextInput({
1797
- onSubmit: (value) => {
1798
- onExpertSelect(value);
1799
- setInputMode(false);
1800
- },
1801
- onCancel: () => setInputMode(false)
1802
- });
1803
- const handleKeyInput = useCallback(
1804
- (inputChar, key) => {
1805
- if (inputMode) {
1806
- handleInput(inputChar, key);
1807
- return;
1808
- }
1809
- if (handleNavigation(inputChar, key)) return;
1810
- if (extraKeyHandler?.(inputChar, key)) return;
1811
- if (inputChar === "i") {
1812
- reset();
1813
- setInputMode(true);
1814
- }
1815
- },
1816
- [inputMode, handleInput, handleNavigation, extraKeyHandler, reset]
1817
- );
1818
- return {
1819
- inputMode,
1820
- input,
1821
- selectedIndex,
1822
- handleKeyInput
1823
- };
1824
- };
1825
- var ExpertList = ({
1826
- experts,
1827
- selectedIndex,
1828
- showSource = false,
1829
- inline = false,
1830
- maxItems
1831
- }) => {
1832
- const displayExperts = maxItems ? experts.slice(0, maxItems) : experts;
1833
- if (displayExperts.length === 0) {
1834
- return /* @__PURE__ */ jsx(Text, { color: "gray", children: "No experts found." });
1835
- }
1836
- const items = displayExperts.map((expert, index) => /* @__PURE__ */ jsxs(Text, { color: index === selectedIndex ? "cyan" : "gray", children: [
1837
- index === selectedIndex ? ">" : " ",
1838
- " ",
1839
- showSource ? expert.key : expert.name,
1840
- showSource && expert.source && /* @__PURE__ */ jsxs(Fragment, { children: [
1841
- " ",
1842
- /* @__PURE__ */ jsxs(Text, { color: expert.source === "configured" ? "green" : "yellow", children: [
1843
- "[",
1844
- expert.source === "configured" ? "config" : "recent",
1845
- "]"
1846
- ] })
1847
- ] }),
1848
- inline ? " " : ""
1849
- ] }, expert.key));
1850
- return inline ? /* @__PURE__ */ jsx(Box, { children: items }) : /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: items });
1851
- };
1852
- var ExpertSelectorBase = ({
1853
- experts,
1854
- hint,
1855
- onExpertSelect,
1856
- showSource = false,
1857
- inline = false,
1858
- maxItems = UI_CONSTANTS.MAX_VISIBLE_LIST_ITEMS,
1859
- extraKeyHandler
1860
- }) => {
1861
- const { inputMode, input, selectedIndex, handleKeyInput } = useExpertSelector({
1862
- experts,
1863
- onExpertSelect,
1864
- extraKeyHandler
1865
- });
1866
- useInput(handleKeyInput);
1867
- return /* @__PURE__ */ jsxs(Box, { flexDirection: inline ? "row" : "column", children: [
1868
- !inputMode && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
1869
- /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { color: "cyan", children: hint }) }),
1870
- /* @__PURE__ */ jsx(
1871
- ExpertList,
1872
- {
1873
- experts,
1874
- selectedIndex,
1875
- showSource,
1876
- inline,
1877
- maxItems
1878
- }
1879
- )
1880
- ] }),
1881
- inputMode && /* @__PURE__ */ jsxs(Box, { children: [
1882
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Expert: " }),
1883
- /* @__PURE__ */ jsx(Text, { color: "white", children: input }),
1884
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: "_" })
1885
- ] })
1886
- ] });
1887
- };
1888
- var BrowsingExpertsInput = ({
1889
- experts,
1890
- onExpertSelect,
1891
- onSwitchToHistory
1892
- }) => {
1893
- const extraKeyHandler = useCallback(
1894
- (inputChar) => {
1895
- if (inputChar === "h") {
1896
- onSwitchToHistory();
1897
- return true;
1898
- }
1899
- return false;
1900
- },
1901
- [onSwitchToHistory]
1902
- );
1903
- const hint = `Experts ${KEY_HINTS.NAVIGATE} ${KEY_HINTS.SELECT} ${KEY_HINTS.INPUT} ${KEY_HINTS.HISTORY} ${KEY_HINTS.CANCEL}`;
1904
- return /* @__PURE__ */ jsx(
1905
- ExpertSelectorBase,
1906
- {
1907
- experts,
1908
- hint,
1909
- onExpertSelect,
1910
- showSource: true,
1911
- maxItems: UI_CONSTANTS.MAX_VISIBLE_LIST_ITEMS,
1912
- extraKeyHandler
1913
- }
1914
- );
1915
- };
1916
- var BrowsingHistoryInput = ({
1917
- jobs,
1918
- onJobSelect,
1919
- onJobResume,
1920
- onSwitchToExperts
1921
- }) => /* @__PURE__ */ jsx(
1922
- ListBrowser,
1923
- {
1924
- title: `Job History ${KEY_HINTS.NAVIGATE} ${KEY_HINTS.RESUME} ${KEY_HINTS.CHECKPOINTS} ${KEY_HINTS.NEW}`,
1925
- items: jobs,
1926
- onSelect: onJobResume,
1927
- emptyMessage: "No jobs found. Press n to start a new job.",
1928
- extraKeyHandler: (char, _key, selected) => {
1929
- if (char === "c" && selected) {
1930
- onJobSelect(selected);
1931
- return true;
1932
- }
1933
- if (char === "n") {
1934
- onSwitchToExperts();
1935
- return true;
1936
- }
1937
- return false;
1938
- },
1939
- renderItem: (job, isSelected) => /* @__PURE__ */ jsxs(Text, { color: isSelected ? "cyan" : "gray", children: [
1940
- isSelected ? ">" : " ",
1941
- " ",
1942
- job.expertKey,
1943
- " - ",
1944
- job.totalSteps,
1945
- " steps (",
1946
- job.jobId,
1947
- ") (",
1948
- formatTimestamp2(job.startedAt),
1949
- ")"
1950
- ] }, job.jobId)
1951
- }
1952
- );
1953
- var BrowserRouter = ({ inputState, showEventsHint = true }) => {
1954
- const ctx = useInputAreaContext();
1955
- const handleEventSelect = useCallback(
1956
- (event) => {
1957
- if (inputState.type === "browsingEvents") {
1958
- ctx.onEventSelect(inputState, event);
1959
- }
1960
- },
1961
- [ctx.onEventSelect, inputState]
1962
- );
1963
- switch (inputState.type) {
1964
- case "browsingHistory":
1965
- return /* @__PURE__ */ jsx(
1966
- BrowsingHistoryInput,
1967
- {
1968
- jobs: inputState.jobs,
1969
- onJobSelect: ctx.onJobSelect,
1970
- onJobResume: ctx.onJobResume,
1971
- onSwitchToExperts: ctx.onSwitchToExperts
1972
- }
1973
- );
1974
- case "browsingExperts":
1975
- return /* @__PURE__ */ jsx(
1976
- BrowsingExpertsInput,
1977
- {
1978
- experts: inputState.experts,
1979
- onExpertSelect: ctx.onExpertSelect,
1980
- onSwitchToHistory: ctx.onSwitchToHistory
1981
- }
1982
- );
1983
- case "browsingCheckpoints":
1984
- return /* @__PURE__ */ jsx(
1985
- BrowsingCheckpointsInput,
1986
- {
1987
- job: inputState.job,
1988
- checkpoints: inputState.checkpoints,
1989
- onCheckpointSelect: ctx.onCheckpointSelect,
1990
- onCheckpointResume: ctx.onCheckpointResume,
1991
- onBack: ctx.onBack,
1992
- showEventsHint
1993
- }
1994
- );
1995
- case "browsingEvents":
1996
- return /* @__PURE__ */ jsx(
1997
- BrowsingEventsInput,
1998
- {
1999
- checkpoint: inputState.checkpoint,
2000
- events: inputState.events,
2001
- onEventSelect: handleEventSelect,
2002
- onBack: ctx.onBack
2003
- }
2004
- );
2005
- case "browsingEventDetail":
2006
- return /* @__PURE__ */ jsx(BrowsingEventDetailInput, { event: inputState.selectedEvent, onBack: ctx.onBack });
2007
- default:
2008
- return assertNever(inputState);
2009
- }
2010
- };
2011
- var ActionRowSimple = ({
2012
- indicatorColor,
2013
- text,
2014
- textDimColor = false
2015
- }) => /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
2016
- /* @__PURE__ */ jsx(Box, { paddingRight: 1, children: /* @__PURE__ */ jsx(Text, { color: indicatorColor, children: INDICATOR.BULLET }) }),
2017
- /* @__PURE__ */ jsx(Text, { color: "white", dimColor: textDimColor, children: text })
2018
- ] }) });
2019
- var ActionRow = ({ indicatorColor, label, summary, children }) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2020
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
2021
- /* @__PURE__ */ jsx(Text, { color: indicatorColor, children: INDICATOR.BULLET }),
2022
- /* @__PURE__ */ jsx(Text, { color: "white", children: label }),
2023
- summary && /* @__PURE__ */ jsx(Text, { color: "white", dimColor: true, children: summary })
2024
- ] }),
2025
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
2026
- /* @__PURE__ */ jsx(Box, { paddingRight: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: INDICATOR.TREE }) }),
2027
- children
2028
- ] })
2029
- ] });
2030
- var CheckpointActionRow = ({ action }) => {
2031
- if (action.type === "parallelGroup") {
2032
- return renderParallelGroup(action);
2033
- }
2034
- const actionElement = renderAction(action);
2035
- if (!actionElement) return null;
2036
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2037
- action.reasoning && renderReasoning(action.reasoning),
2038
- actionElement
2039
- ] });
2040
- };
2041
- function renderParallelGroup(group) {
2042
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2043
- group.reasoning && renderReasoning(group.reasoning),
2044
- group.activities.map((activity, index) => {
2045
- const actionElement = renderAction(activity);
2046
- if (!actionElement) return null;
2047
- return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: actionElement }, activity.id || `${activity.type}-${index}`);
2048
- })
2049
- ] });
2050
- }
2051
- function renderReasoning(text) {
2052
- const lines = text.split("\n");
2053
- 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}`)) }) });
2054
- }
2055
- function renderAction(action) {
2056
- const color = action.type === "error" || "error" in action && action.error ? "red" : "green";
2057
- switch (action.type) {
2058
- case "query":
2059
- return renderQuery(action.text, action.runId);
2060
- case "retry":
2061
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "yellow", label: "Retry", children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: action.message || action.error }) });
2062
- case "complete":
2063
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "green", label: "Run Results", children: /* @__PURE__ */ jsx(Text, { children: action.text }) });
2064
- case "attemptCompletion": {
2065
- if (action.error) {
2066
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "red", label: "Completion Failed", children: /* @__PURE__ */ jsx(Text, { color: "red", children: action.error }) });
2067
- }
2068
- const remaining = action.remainingTodos?.filter((t) => !t.completed) ?? [];
2069
- if (remaining.length > 0) {
2070
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "yellow", label: "Completion Blocked", children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2071
- remaining.length,
2072
- " remaining task",
2073
- remaining.length > 1 ? "s" : ""
2074
- ] }) });
2075
- }
2076
- return /* @__PURE__ */ jsx(ActionRowSimple, { indicatorColor: "green", text: "Completion Accepted" });
2077
- }
2078
- case "todo":
2079
- return renderTodo(action, color);
2080
- case "clearTodo":
2081
- return /* @__PURE__ */ jsx(ActionRowSimple, { indicatorColor: color, text: "Todo Cleared" });
2082
- case "readTextFile":
2083
- return renderReadTextFile(action, color);
2084
- case "writeTextFile":
2085
- return renderWriteTextFile(action, color);
2086
- case "editTextFile":
2087
- return renderEditTextFile(action, color);
2088
- case "appendTextFile":
2089
- return renderAppendTextFile(action, color);
2090
- case "deleteFile":
2091
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Delete", summary: shortenPath(action.path), children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Removed" }) });
2092
- case "deleteDirectory":
2093
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Delete Dir", summary: shortenPath(action.path), children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2094
- "Removed",
2095
- action.recursive ? " (recursive)" : ""
2096
- ] }) });
2097
- case "moveFile":
2098
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Move", summary: shortenPath(action.source), children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2099
- "\u2192 ",
2100
- shortenPath(action.destination, 30)
2101
- ] }) });
2102
- case "createDirectory":
2103
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Mkdir", summary: shortenPath(action.path), children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Created" }) });
2104
- case "listDirectory":
2105
- return renderListDirectory(action, color);
2106
- case "getFileInfo":
2107
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Info", summary: shortenPath(action.path), children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Fetched" }) });
2108
- case "readPdfFile":
2109
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "PDF", summary: shortenPath(action.path), children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Read" }) });
2110
- case "readImageFile":
2111
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Image", summary: shortenPath(action.path), children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Read" }) });
2112
- case "exec":
2113
- return renderExec(action, color);
2114
- case "delegate":
2115
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "yellow", label: action.delegateExpertKey, children: /* @__PURE__ */ jsx(
2116
- Text,
2117
- {
2118
- dimColor: true,
2119
- children: `{"query":"${truncateText(action.query, UI_CONSTANTS.TRUNCATE_TEXT_MEDIUM)}"}`
2120
- }
2121
- ) });
2122
- case "delegationComplete":
2123
- return /* @__PURE__ */ jsx(
2124
- ActionRowSimple,
2125
- {
2126
- indicatorColor: "green",
2127
- text: `Delegation Complete (${action.count} delegate${action.count > 1 ? "s" : ""} returned)`
2128
- }
2129
- );
2130
- case "interactiveTool":
2131
- 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) }) });
2132
- case "generalTool":
2133
- 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) }) });
2134
- case "error":
2135
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "red", label: action.errorName ?? "Error", children: /* @__PURE__ */ jsx(Text, { color: "red", children: action.error ?? "Unknown error" }) });
2136
- default: {
2137
- return null;
2138
- }
2139
- }
2140
- }
2141
- function renderTodo(action, color) {
2142
- const { newTodos, completedTodos, todos } = action;
2143
- const hasNewTodos = newTodos && newTodos.length > 0;
2144
- const hasCompletedTodos = completedTodos && completedTodos.length > 0;
2145
- if (!hasNewTodos && !hasCompletedTodos) {
2146
- return /* @__PURE__ */ jsx(ActionRowSimple, { indicatorColor: color, text: "Todo No changes" });
2147
- }
2148
- const labelParts = [];
2149
- if (hasNewTodos) {
2150
- labelParts.push(`Added ${newTodos.length} task${newTodos.length > 1 ? "s" : ""}`);
2151
- }
2152
- if (hasCompletedTodos) {
2153
- labelParts.push(
2154
- `Completed ${completedTodos.length} task${completedTodos.length > 1 ? "s" : ""}`
2155
- );
2156
- }
2157
- const label = `Todo ${labelParts.join(", ")}`;
2158
- const completedTitles = hasCompletedTodos ? completedTodos.map((id) => todos.find((t) => t.id === id)?.title).filter((t) => t !== void 0) : [];
2159
- if (!hasNewTodos && completedTitles.length === 0) {
2160
- return /* @__PURE__ */ jsx(ActionRowSimple, { indicatorColor: color, text: label });
2161
- }
2162
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label, children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2163
- hasNewTodos && newTodos.slice(0, RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW).map((todo, idx) => /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2164
- "\u25CB ",
2165
- todo
2166
- ] }, `todo-${idx}`)),
2167
- hasNewTodos && newTodos.length > RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2168
- "... +",
2169
- newTodos.length - RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW,
2170
- " more"
2171
- ] }),
2172
- completedTitles.slice(0, RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW).map((title, idx) => /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2173
- "\u2713 ",
2174
- title
2175
- ] }, `completed-${idx}`)),
2176
- completedTitles.length > RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2177
- "... +",
2178
- completedTitles.length - RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW,
2179
- " more"
2180
- ] })
2181
- ] }) });
2182
- }
2183
- function renderReadTextFile(action, color) {
2184
- const { path: path4, content, from, to } = action;
2185
- const lineRange = from !== void 0 && to !== void 0 ? `#${from}-${to}` : "";
2186
- const lines = content?.split("\n") ?? [];
2187
- return /* @__PURE__ */ jsx(
2188
- ActionRow,
2189
- {
2190
- indicatorColor: color,
2191
- label: "Read Text File",
2192
- summary: `${shortenPath(path4)}${lineRange}`,
2193
- 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}`)) })
2194
- }
2195
- );
2196
- }
2197
- function renderWriteTextFile(action, color) {
2198
- const { path: path4, text } = action;
2199
- const lines = text.split("\n");
2200
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Write Text File", summary: shortenPath(path4), children: /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: lines.map((line, idx) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
2201
- /* @__PURE__ */ jsx(Text, { color: "green", dimColor: true, children: "+" }),
2202
- /* @__PURE__ */ jsx(Text, { color: "white", dimColor: true, children: line })
2203
- ] }, `write-${idx}`)) }) });
2204
- }
2205
- function renderEditTextFile(action, color) {
2206
- const { path: path4, oldText, newText } = action;
2207
- const oldLines = oldText.split("\n");
2208
- const newLines = newText.split("\n");
2209
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Edit Text File", summary: shortenPath(path4), children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2210
- oldLines.map((line, idx) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
2211
- /* @__PURE__ */ jsx(Text, { color: "red", dimColor: true, children: "-" }),
2212
- /* @__PURE__ */ jsx(Text, { color: "white", dimColor: true, children: line })
2213
- ] }, `old-${idx}`)),
2214
- newLines.map((line, idx) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
2215
- /* @__PURE__ */ jsx(Text, { color: "green", dimColor: true, children: "+" }),
2216
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: line })
2217
- ] }, `new-${idx}`))
2218
- ] }) });
2219
- }
2220
- function renderAppendTextFile(action, color) {
2221
- const { path: path4, text } = action;
2222
- const lines = text.split("\n");
2223
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Append Text File", summary: shortenPath(path4), children: /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: lines.map((line, idx) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
2224
- /* @__PURE__ */ jsx(Text, { color: "green", dimColor: true, children: "+" }),
2225
- /* @__PURE__ */ jsx(Text, { color: "white", dimColor: true, children: line })
2226
- ] }, `append-${idx}`)) }) });
2227
- }
2228
- function renderListDirectory(action, color) {
2229
- const { path: path4, items } = action;
2230
- const itemLines = items?.map((item) => `${item.type === "directory" ? "\u{1F4C1}" : "\u{1F4C4}"} ${item.name}`) ?? [];
2231
- const { visible, remaining } = summarizeOutput(itemLines, RENDER_CONSTANTS.LIST_DIR_MAX_ITEMS);
2232
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "List", summary: shortenPath(path4), children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2233
- visible.map((line, idx) => /* @__PURE__ */ jsx(Text, { dimColor: true, children: truncateText(line, UI_CONSTANTS.TRUNCATE_TEXT_DEFAULT) }, `dir-${idx}`)),
2234
- remaining > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2235
- "... +",
2236
- remaining,
2237
- " more"
2238
- ] })
2239
- ] }) });
2240
- }
2241
- function renderExec(action, color) {
2242
- const { command, args, cwd, output } = action;
2243
- const cwdPart = cwd ? ` ${shortenPath(cwd, 40)}` : "";
2244
- const cmdLine = truncateText(
2245
- `${command} ${args.join(" ")}${cwdPart}`,
2246
- UI_CONSTANTS.TRUNCATE_TEXT_DEFAULT
2247
- );
2248
- const outputLines = output?.split("\n") ?? [];
2249
- const { visible, remaining } = summarizeOutput(
2250
- outputLines,
2251
- RENDER_CONSTANTS.EXEC_OUTPUT_MAX_LINES
2252
- );
2253
- return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: `Bash ${cmdLine}`, children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2254
- visible.map((line, idx) => /* @__PURE__ */ jsx(Text, { dimColor: true, children: truncateText(line, UI_CONSTANTS.TRUNCATE_TEXT_DEFAULT) }, `out-${idx}`)),
2255
- remaining > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2256
- "... +",
2257
- remaining,
2258
- " more"
2259
- ] })
2260
- ] }) });
2261
- }
2262
- function renderQuery(text, runId) {
2263
- const lines = text.split("\n");
2264
- const shortRunId = runId.slice(0, 8);
2265
- 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}`)) }) });
2266
- }
2267
- var RunSetting = ({
2268
- info,
2269
- eventCount,
2270
- isEditing,
2271
- expertName,
2272
- onQuerySubmit
2273
- }) => {
2274
- const { input, handleInput } = useTextInput({
2275
- onSubmit: onQuerySubmit ?? (() => {
2276
- })
2277
- });
2278
- useInput(handleInput, { isActive: isEditing });
2279
- const displayExpertName = expertName ?? info.expertName;
2280
- const skills = info.activeSkills.length > 0 ? info.activeSkills.join(", ") : "";
2281
- const step = info.currentStep !== void 0 ? String(info.currentStep) : "";
2282
- const usagePercent = (info.contextWindowUsage * 100).toFixed(1);
2283
- return /* @__PURE__ */ jsxs(
2284
- Box,
2285
- {
2286
- flexDirection: "column",
2287
- borderStyle: "single",
2288
- borderColor: "gray",
2289
- borderTop: true,
2290
- borderBottom: false,
2291
- borderLeft: false,
2292
- borderRight: false,
2293
- children: [
2294
- /* @__PURE__ */ jsxs(Text, { children: [
2295
- /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "Perstack" }),
2296
- info.runtimeVersion && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
2297
- " (v",
2298
- info.runtimeVersion,
2299
- ")"
2300
- ] })
2301
- ] }),
2302
- /* @__PURE__ */ jsxs(Text, { children: [
2303
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Expert: " }),
2304
- /* @__PURE__ */ jsx(Text, { color: "white", children: displayExpertName }),
2305
- /* @__PURE__ */ jsx(Text, { color: "gray", children: " / Skills: " }),
2306
- /* @__PURE__ */ jsx(Text, { color: "green", children: skills })
2307
- ] }),
2308
- /* @__PURE__ */ jsxs(Text, { children: [
2309
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Status: " }),
2310
- /* @__PURE__ */ jsx(
2311
- Text,
2312
- {
2313
- color: info.status === "running" ? "green" : info.status === "completed" ? "cyan" : "yellow",
2314
- children: info.status
2315
- }
2316
- ),
2317
- /* @__PURE__ */ jsx(Text, { color: "gray", children: " / Step: " }),
2318
- /* @__PURE__ */ jsx(Text, { color: "white", children: step }),
2319
- /* @__PURE__ */ jsx(Text, { color: "gray", children: " / Events: " }),
2320
- /* @__PURE__ */ jsx(Text, { color: "white", children: eventCount }),
2321
- /* @__PURE__ */ jsx(Text, { color: "gray", children: " / Usage: " }),
2322
- /* @__PURE__ */ jsxs(Text, { color: "white", children: [
2323
- usagePercent,
2324
- "%"
2325
- ] })
2326
- ] }),
2327
- /* @__PURE__ */ jsxs(Text, { children: [
2328
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Model: " }),
2329
- /* @__PURE__ */ jsx(Text, { color: "white", children: info.model })
2330
- ] }),
2331
- /* @__PURE__ */ jsxs(Box, { children: [
2332
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Query: " }),
2333
- isEditing ? /* @__PURE__ */ jsxs(Fragment, { children: [
2334
- /* @__PURE__ */ jsx(Text, { color: "white", children: input }),
2335
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: "_" })
2336
- ] }) : /* @__PURE__ */ jsx(Text, { color: "white", children: info.query })
2337
- ] })
2338
- ]
2339
- }
2340
- );
2341
- };
2342
- var StreamingDisplay = ({ streaming }) => {
2343
- const activeRuns = Object.entries(streaming.runs).filter(
2344
- ([, run]) => run.isReasoningActive || run.isRunResultActive
2345
- );
2346
- if (activeRuns.length === 0) return null;
2347
- return /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginY: 1, children: activeRuns.map(([runId, run]) => /* @__PURE__ */ jsx(StreamingRunSection, { run }, runId)) });
2348
- };
2349
- function StreamingRunSection({ run }) {
2350
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2351
- run.isReasoningActive && run.reasoning !== void 0 && /* @__PURE__ */ jsx(StreamingReasoning, { expertKey: run.expertKey, text: run.reasoning }),
2352
- run.isRunResultActive && run.runResult !== void 0 && /* @__PURE__ */ jsx(StreamingRunResult, { expertKey: run.expertKey, text: run.runResult })
2353
- ] });
2354
- }
2355
- function StreamingReasoning({
2356
- expertKey,
2357
- text
2358
- }) {
2359
- const lines = text.split("\n");
2360
- const label = `[${formatExpertKey(expertKey)}] Reasoning...`;
2361
- 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}`)) }) });
2362
- }
2363
- function StreamingRunResult({
2364
- expertKey,
2365
- text
2366
- }) {
2367
- const lines = text.split("\n");
2368
- const label = `[${formatExpertKey(expertKey)}] Generating...`;
2369
- 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}`)) }) });
2370
- }
2371
- function formatExpertKey(expertKey) {
2372
- const atIndex = expertKey.lastIndexOf("@");
2373
- if (atIndex > 0) {
2374
- return expertKey.substring(0, atIndex);
2375
- }
2376
- return expertKey;
2377
- }
2378
- function getActivityKey(activityOrGroup, index) {
2379
- return activityOrGroup.id || `activity-${index}`;
2380
- }
2381
- var RunBox = ({ node, isRoot }) => {
2382
- if (isRoot) {
2383
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2384
- node.activities.map((activity, index) => /* @__PURE__ */ jsx(CheckpointActionRow, { action: activity }, getActivityKey(activity, index))),
2385
- node.children.map((child) => /* @__PURE__ */ jsx(RunBox, { node: child, isRoot: false }, child.runId))
2386
- ] });
2387
- }
2388
- const shortRunId = node.runId.slice(0, 8);
2389
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", marginLeft: 1, children: [
2390
- /* @__PURE__ */ jsxs(Text, { dimColor: true, bold: true, children: [
2391
- "[",
2392
- node.expertKey,
2393
- "] ",
2394
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2395
- "(",
2396
- shortRunId,
2397
- ")"
2398
- ] })
2399
- ] }),
2400
- node.activities.map((activity, index) => /* @__PURE__ */ jsx(CheckpointActionRow, { action: activity }, getActivityKey(activity, index))),
2401
- node.children.map((child) => /* @__PURE__ */ jsx(RunBox, { node: child, isRoot: false }, child.runId))
2402
- ] });
2403
- };
2404
- function getActivityProps(activityOrGroup) {
2405
- if (activityOrGroup.type === "parallelGroup") {
2406
- const group = activityOrGroup;
2407
- const firstActivity = group.activities[0];
2408
- return {
2409
- runId: group.runId,
2410
- expertKey: group.expertKey,
2411
- delegatedBy: firstActivity?.delegatedBy
2412
- };
2413
- }
2414
- return activityOrGroup;
2415
- }
2416
- var ActivityLogPanel = ({ activities }) => {
2417
- const rootNodes = useMemo(() => {
2418
- const nodeMap = /* @__PURE__ */ new Map();
2419
- const roots = [];
2420
- const orphanChildren = /* @__PURE__ */ new Map();
2421
- for (const activityOrGroup of activities) {
2422
- const { runId, expertKey, delegatedBy } = getActivityProps(activityOrGroup);
2423
- let node = nodeMap.get(runId);
2424
- if (!node) {
2425
- node = {
2426
- runId,
2427
- expertKey,
2428
- activities: [],
2429
- children: []
2430
- };
2431
- nodeMap.set(runId, node);
2432
- const waitingChildren = orphanChildren.get(runId);
2433
- if (waitingChildren) {
2434
- for (const child of waitingChildren) {
2435
- node.children.push(child);
2436
- const rootIndex = roots.indexOf(child);
2437
- if (rootIndex !== -1) {
2438
- roots.splice(rootIndex, 1);
2439
- }
2440
- }
2441
- orphanChildren.delete(runId);
2442
- }
2443
- if (delegatedBy) {
2444
- const parentNode = nodeMap.get(delegatedBy.runId);
2445
- if (parentNode) {
2446
- parentNode.children.push(node);
2447
- } else {
2448
- const orphans = orphanChildren.get(delegatedBy.runId) ?? [];
2449
- orphans.push(node);
2450
- orphanChildren.set(delegatedBy.runId, orphans);
2451
- roots.push(node);
2452
- }
2453
- } else {
2454
- roots.push(node);
2455
- }
2456
- }
2457
- node.activities.push(activityOrGroup);
2458
- }
2459
- return roots;
2460
- }, [activities]);
2461
- return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: rootNodes.map((node) => /* @__PURE__ */ jsx(RunBox, { node, isRoot: true }, node.runId)) });
2462
- };
2463
- var ContinueInputPanel = ({
2464
- isActive,
2465
- runStatus,
2466
- onSubmit
2467
- }) => {
2468
- const { input, handleInput } = useTextInput({
2469
- onSubmit: (newQuery) => {
2470
- if (isActive && newQuery.trim()) {
2471
- onSubmit(newQuery.trim());
2472
- }
2473
- }
2474
- });
2475
- useInput(handleInput, { isActive });
2476
- if (runStatus === "running") {
2477
- return null;
2478
- }
2479
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", children: [
2480
- /* @__PURE__ */ jsxs(Text, { children: [
2481
- /* @__PURE__ */ jsx(Text, { color: runStatus === "completed" ? "green" : "yellow", bold: true, children: runStatus === "completed" ? "Completed" : "Stopped" }),
2482
- /* @__PURE__ */ jsx(Text, { color: "gray", children: " - Enter a follow-up query or wait to exit" })
2483
- ] }),
2484
- /* @__PURE__ */ jsxs(Box, { children: [
2485
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Continue: " }),
2486
- /* @__PURE__ */ jsx(Text, { children: input }),
2487
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: "_" })
2488
- ] })
2489
- ] });
2490
- };
2491
- var StatusPanel = ({
2492
- runtimeInfo,
2493
- eventCount,
2494
- runStatus
2495
- }) => {
2496
- if (runStatus !== "running") {
2497
- return null;
2498
- }
2499
- return /* @__PURE__ */ jsx(RunSetting, { info: runtimeInfo, eventCount, isEditing: false });
2500
- };
2501
- var useExecutionState = (options) => {
2502
- const { expertKey, query, config, continueTimeoutMs, historicalEvents, onReady, onComplete } = options;
2503
- const { exit } = useApp();
2504
- const runState = useRun();
2505
- const { runtimeInfo, handleEvent, setQuery } = useRuntimeInfo({
2506
- initialExpertName: expertKey,
2507
- initialConfig: config
2508
- });
2509
- const [runStatus, setRunStatus] = useState("running");
2510
- const [isAcceptingContinue, setIsAcceptingContinue] = useState(false);
2511
- const timeoutRef = useRef(null);
2512
- const clearTimeoutIfExists = useCallback(() => {
2513
- if (timeoutRef.current) {
2514
- clearTimeout(timeoutRef.current);
2515
- timeoutRef.current = null;
2516
- }
2517
- }, []);
2518
- const startExitTimeout = useCallback(() => {
2519
- clearTimeoutIfExists();
2520
- timeoutRef.current = setTimeout(() => {
2521
- onComplete({ nextQuery: null });
2522
- exit();
2523
- }, continueTimeoutMs);
2524
- }, [clearTimeoutIfExists, continueTimeoutMs, onComplete, exit]);
2525
- useEffect(() => {
2526
- setQuery(query);
2527
- }, [query, setQuery]);
2528
- useEffect(() => {
2529
- if (historicalEvents && historicalEvents.length > 0) {
2530
- runState.appendHistoricalEvents(historicalEvents);
2531
- }
2532
- }, [historicalEvents, runState.appendHistoricalEvents]);
2533
- useEffect(() => {
2534
- onReady((event) => {
2535
- runState.addEvent(event);
2536
- const result = handleEvent(event);
2537
- if (result?.completed) {
2538
- setRunStatus("completed");
2539
- setIsAcceptingContinue(true);
2540
- startExitTimeout();
2541
- } else if (result?.stopped) {
2542
- setRunStatus("stopped");
2543
- setIsAcceptingContinue(true);
2544
- startExitTimeout();
2545
- }
2546
- });
2547
- }, [onReady, runState.addEvent, handleEvent, startExitTimeout]);
2548
- useEffect(() => {
2549
- return () => {
2550
- clearTimeoutIfExists();
2551
- };
2552
- }, [clearTimeoutIfExists]);
2553
- const handleContinueSubmit = useCallback(
2554
- (newQuery) => {
2555
- if (isAcceptingContinue && newQuery.trim()) {
2556
- clearTimeoutIfExists();
2557
- onComplete({ nextQuery: newQuery.trim() });
2558
- exit();
2559
- }
2560
- },
2561
- [isAcceptingContinue, clearTimeoutIfExists, onComplete, exit]
2562
- );
2563
- return {
2564
- activities: runState.activities,
2565
- streaming: runState.streaming,
2566
- eventCount: runState.eventCount,
2567
- runtimeInfo,
2568
- runStatus,
2569
- isAcceptingContinue,
2570
- handleContinueSubmit,
2571
- clearTimeout: clearTimeoutIfExists
2572
- };
2573
- };
2574
- var ExecutionApp = (props) => {
2575
- const { expertKey, query, config, continueTimeoutMs, historicalEvents, onReady, onComplete } = props;
2576
- const { exit } = useApp();
2577
- const state = useExecutionState({
2578
- expertKey,
2579
- query,
2580
- config,
2581
- continueTimeoutMs,
2582
- historicalEvents,
2583
- onReady,
2584
- onComplete
2585
- });
2586
- useInput((input, key) => {
2587
- if (key.ctrl && input === "c") {
2588
- state.clearTimeout();
2589
- exit();
2590
- }
2591
- });
2592
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2593
- /* @__PURE__ */ jsx(ActivityLogPanel, { activities: state.activities }),
2594
- /* @__PURE__ */ jsx(StreamingDisplay, { streaming: state.streaming }),
2595
- /* @__PURE__ */ jsx(
2596
- StatusPanel,
2597
- {
2598
- runtimeInfo: state.runtimeInfo,
2599
- eventCount: state.eventCount,
2600
- runStatus: state.runStatus
2601
- }
2602
- ),
2603
- /* @__PURE__ */ jsx(
2604
- ContinueInputPanel,
2605
- {
2606
- isActive: state.isAcceptingContinue,
2607
- runStatus: state.runStatus,
2608
- onSubmit: state.handleContinueSubmit
2609
- }
2610
- )
2611
- ] });
2612
- };
2613
- function renderExecution(params) {
2614
- const eventQueue = new EventQueue();
2615
- const result = new Promise((resolve, reject) => {
2616
- let resolved = false;
2617
- const { waitUntilExit } = render(
2618
- /* @__PURE__ */ jsx(
2619
- ExecutionApp,
2620
- {
2621
- ...params,
2622
- onReady: (handler) => {
2623
- eventQueue.setHandler(handler);
2624
- },
2625
- onComplete: (result2) => {
2626
- resolved = true;
2627
- resolve(result2);
2628
- }
2629
- }
2630
- )
2631
- );
2632
- waitUntilExit().then(() => {
2633
- if (!resolved) {
2634
- reject(new Error("Execution cancelled"));
2635
- }
2636
- }).catch(reject);
2637
- });
2638
- return {
2639
- result,
2640
- eventListener: (event) => {
2641
- eventQueue.emit(event);
2642
- }
2643
- };
2644
- }
2645
- var selectionReducer = (_state, action) => {
2646
- switch (action.type) {
2647
- case "BROWSE_HISTORY":
2648
- return { type: "browsingHistory", jobs: action.jobs };
2649
- case "BROWSE_EXPERTS":
2650
- return { type: "browsingExperts", experts: action.experts };
2651
- case "SELECT_EXPERT":
2652
- return { type: "enteringQuery", expertKey: action.expertKey };
2653
- case "SELECT_JOB":
2654
- return { type: "browsingCheckpoints", job: action.job, checkpoints: action.checkpoints };
2655
- case "GO_BACK_FROM_CHECKPOINTS":
2656
- return { type: "browsingHistory", jobs: action.jobs };
2657
- default:
2658
- return assertNever(action);
2659
- }
2660
- };
2661
- var SelectionApp = (props) => {
2662
- const {
2663
- showHistory,
2664
- initialExpertKey,
2665
- initialQuery,
2666
- initialCheckpoint,
2667
- configuredExperts,
2668
- recentExperts,
2669
- historyJobs,
2670
- onLoadCheckpoints,
2671
- onComplete
2672
- } = props;
2673
- const { exit } = useApp();
2674
- const allExperts = useMemo(() => {
2675
- const configured = configuredExperts.map((e) => ({ ...e, source: "configured" }));
2676
- const recent = recentExperts.filter((e) => !configured.some((c) => c.key === e.key)).map((e) => ({ ...e, source: "recent" }));
2677
- return [...configured, ...recent];
2678
- }, [configuredExperts, recentExperts]);
2679
- const getInitialState = () => {
2680
- if (initialExpertKey && !initialQuery) {
2681
- return { type: "enteringQuery", expertKey: initialExpertKey };
2682
- }
2683
- if (showHistory && historyJobs.length > 0) {
2684
- return { type: "browsingHistory", jobs: historyJobs };
2685
- }
2686
- return { type: "browsingExperts", experts: allExperts };
2687
- };
2688
- const [state, dispatch] = useReducer(selectionReducer, void 0, getInitialState);
2689
- const [selectedCheckpoint, setSelectedCheckpoint] = useState(
2690
- initialCheckpoint
2691
- );
2692
- useEffect(() => {
2693
- if (initialExpertKey && initialQuery) {
2694
- onComplete({
2695
- expertKey: initialExpertKey,
2696
- query: initialQuery,
2697
- checkpoint: initialCheckpoint
2698
- });
2699
- exit();
2700
- }
2701
- }, [initialExpertKey, initialQuery, initialCheckpoint, onComplete, exit]);
2702
- const { input: queryInput, handleInput: handleQueryInput } = useTextInput({
2703
- onSubmit: (query) => {
2704
- if (state.type === "enteringQuery" && query.trim()) {
2705
- onComplete({
2706
- expertKey: state.expertKey,
2707
- query: query.trim(),
2708
- checkpoint: selectedCheckpoint
2709
- });
2710
- exit();
2711
- }
2712
- }
2713
- });
2714
- useInput(handleQueryInput, { isActive: state.type === "enteringQuery" });
2715
- const handleExpertSelect = useCallback((expertKey) => {
2716
- dispatch({ type: "SELECT_EXPERT", expertKey });
2717
- }, []);
2718
- const handleJobSelect = useCallback(
2719
- async (job) => {
2720
- try {
2721
- const checkpoints = await onLoadCheckpoints(job);
2722
- dispatch({ type: "SELECT_JOB", job, checkpoints });
2723
- } catch {
2724
- dispatch({ type: "SELECT_JOB", job, checkpoints: [] });
2725
- }
2726
- },
2727
- [onLoadCheckpoints]
2728
- );
2729
- const handleJobResume = useCallback(
2730
- async (job) => {
2731
- try {
2732
- const checkpoints = await onLoadCheckpoints(job);
2733
- const latestCheckpoint = checkpoints[0];
2734
- if (latestCheckpoint) {
2735
- setSelectedCheckpoint(latestCheckpoint);
2736
- }
2737
- dispatch({ type: "SELECT_EXPERT", expertKey: job.expertKey });
2738
- } catch {
2739
- dispatch({ type: "SELECT_EXPERT", expertKey: job.expertKey });
2740
- }
2741
- },
2742
- [onLoadCheckpoints]
2743
- );
2744
- const handleCheckpointResume = useCallback(
2745
- (checkpoint) => {
2746
- setSelectedCheckpoint(checkpoint);
2747
- if (state.type === "browsingCheckpoints") {
2748
- dispatch({ type: "SELECT_EXPERT", expertKey: state.job.expertKey });
2749
- }
2750
- },
2751
- [state]
2752
- );
2753
- const handleBack = useCallback(() => {
2754
- if (state.type === "browsingCheckpoints") {
2755
- dispatch({ type: "GO_BACK_FROM_CHECKPOINTS", jobs: historyJobs });
2756
- }
2757
- }, [state, historyJobs]);
2758
- const handleSwitchToExperts = useCallback(() => {
2759
- dispatch({ type: "BROWSE_EXPERTS", experts: allExperts });
2760
- }, [allExperts]);
2761
- const handleSwitchToHistory = useCallback(() => {
2762
- dispatch({ type: "BROWSE_HISTORY", jobs: historyJobs });
2763
- }, [historyJobs]);
2764
- useInput((input, key) => {
2765
- if (key.ctrl && input === "c") {
2766
- exit();
2767
- }
2768
- });
2769
- const contextValue = useMemo(
2770
- () => ({
2771
- onExpertSelect: handleExpertSelect,
2772
- onQuerySubmit: () => {
2773
- },
2774
- // Not used in browser
2775
- onJobSelect: handleJobSelect,
2776
- onJobResume: handleJobResume,
2777
- onCheckpointSelect: () => {
2778
- },
2779
- // Not used in selection (no event browsing)
2780
- onCheckpointResume: handleCheckpointResume,
2781
- onEventSelect: () => {
2782
- },
2783
- // Not used in selection
2784
- onBack: handleBack,
2785
- onSwitchToExperts: handleSwitchToExperts,
2786
- onSwitchToHistory: handleSwitchToHistory
2787
- }),
2788
- [
2789
- handleExpertSelect,
2790
- handleJobSelect,
2791
- handleJobResume,
2792
- handleCheckpointResume,
2793
- handleBack,
2794
- handleSwitchToExperts,
2795
- handleSwitchToHistory
2796
- ]
2797
- );
2798
- if (initialExpertKey && initialQuery) {
2799
- return null;
2800
- }
2801
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2802
- /* @__PURE__ */ jsx(InputAreaProvider, { value: contextValue, children: (state.type === "browsingHistory" || state.type === "browsingExperts" || state.type === "browsingCheckpoints") && /* @__PURE__ */ jsx(
2803
- BrowserRouter,
2804
- {
2805
- inputState: state,
2806
- showEventsHint: false
2807
- }
2808
- ) }),
2809
- state.type === "enteringQuery" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", children: [
2810
- /* @__PURE__ */ jsxs(Text, { children: [
2811
- /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "Expert:" }),
2812
- " ",
2813
- /* @__PURE__ */ jsx(Text, { children: state.expertKey }),
2814
- selectedCheckpoint && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
2815
- " (resuming from step ",
2816
- selectedCheckpoint.stepNumber,
2817
- ")"
2818
- ] })
2819
- ] }),
2820
- /* @__PURE__ */ jsxs(Box, { children: [
2821
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "Query: " }),
2822
- /* @__PURE__ */ jsx(Text, { children: queryInput }),
2823
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: "_" })
2824
- ] }),
2825
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Press Enter to start" })
2826
- ] })
2827
- ] });
2828
- };
2829
- async function renderSelection(params) {
2830
- return new Promise((resolve, reject) => {
2831
- let resolved = false;
2832
- const { waitUntilExit } = render(
2833
- /* @__PURE__ */ jsx(
2834
- SelectionApp,
2835
- {
2836
- ...params,
2837
- onComplete: (result) => {
2838
- resolved = true;
2839
- resolve(result);
2840
- }
2841
- }
2842
- )
2843
- );
2844
- waitUntilExit().then(() => {
2845
- if (!resolved) {
2846
- reject(new Error("Selection cancelled"));
2847
- }
2848
- }).catch(reject);
2849
- });
2850
- }
2851
-
2852
- // src/start.ts
2853
- var CONTINUE_TIMEOUT_MS = 6e4;
2854
- var startCommand = new Command().command("start").description("Start Perstack with interactive TUI").argument("[expertKey]", "Expert key to run (optional, will prompt if not provided)").argument("[query]", "Query to run (optional, will prompt if not provided)").option("--config <configPath>", "Path to perstack.toml config file").option("--provider <provider>", "Provider to use").option("--model <model>", "Model to use").option(
2855
- "--reasoning-budget <budget>",
2856
- "Reasoning budget for native LLM reasoning (minimal, low, medium, high, or token count)"
2857
- ).option(
2858
- "--max-steps <maxSteps>",
2859
- "Maximum number of steps to run, default is undefined (no limit)"
2860
- ).option("--max-retries <maxRetries>", "Maximum number of generation retries, default is 5").option(
2861
- "--timeout <timeout>",
2862
- "Timeout for each generation in milliseconds, default is 300000 (5 minutes)"
2863
- ).option("--job-id <jobId>", "Job ID for identifying the job").option(
2864
- "--env-path <path>",
2865
- "Path to the environment file (can be specified multiple times), default is .env and .env.local",
2866
- (value, previous) => previous.concat(value),
2867
- []
2868
- ).option("--verbose", "Enable verbose logging").option("--continue", "Continue the most recent job with new query").option("--continue-job <jobId>", "Continue the specified job with new query").option(
2869
- "--resume-from <checkpointId>",
2870
- "Resume from a specific checkpoint (requires --continue or --continue-job)"
2871
- ).option("-i, --interactive-tool-call-result", "Query is interactive tool call result").action(async (expertKey, query, options) => {
2872
- const input = parseWithFriendlyError(startCommandInputSchema, { expertKey, query, options });
2873
- try {
2874
- const { perstackConfig, checkpoint, env, providerConfig, model, experts } = await resolveRunContext({
2875
- configPath: input.options.config,
2876
- provider: input.options.provider,
2877
- model: input.options.model,
2878
- envPath: input.options.envPath,
2879
- continue: input.options.continue,
2880
- continueJob: input.options.continueJob,
2881
- resumeFrom: input.options.resumeFrom,
2882
- expertKey: input.expertKey
2883
- });
2884
- const maxSteps = input.options.maxSteps ?? perstackConfig.maxSteps;
2885
- const maxRetries = input.options.maxRetries ?? perstackConfig.maxRetries ?? defaultMaxRetries;
2886
- const timeout = input.options.timeout ?? perstackConfig.timeout ?? defaultTimeout;
2887
- const configuredExperts = Object.keys(perstackConfig.experts ?? {}).map((key) => ({
2888
- key,
2889
- name: key
2890
- }));
2891
- const recentExperts = getRecentExperts(10);
2892
- const showHistory = !input.expertKey && !input.query && !checkpoint;
2893
- const historyJobs = showHistory ? getAllJobs2().map((j) => ({
2894
- jobId: j.id,
2895
- status: j.status,
2896
- expertKey: j.coordinatorExpertKey,
2897
- totalSteps: j.totalSteps,
2898
- startedAt: j.startedAt,
2899
- finishedAt: j.finishedAt
2900
- })) : [];
2901
- const selection = await renderSelection({
2902
- showHistory,
2903
- initialExpertKey: input.expertKey,
2904
- initialQuery: input.query,
2905
- initialCheckpoint: checkpoint ? {
2906
- id: checkpoint.id,
2907
- jobId: checkpoint.jobId,
2908
- runId: checkpoint.runId,
2909
- stepNumber: checkpoint.stepNumber,
2910
- contextWindowUsage: checkpoint.contextWindowUsage ?? 0
2911
- } : void 0,
2912
- configuredExperts,
2913
- recentExperts,
2914
- historyJobs,
2915
- onLoadCheckpoints: async (j) => {
2916
- const checkpoints = getCheckpointsWithDetails(j.jobId);
2917
- return checkpoints.map((cp) => ({ ...cp, jobId: j.jobId }));
2918
- }
2919
- });
2920
- if (!selection.expertKey) {
2921
- console.error("Expert key is required");
2922
- return;
2923
- }
2924
- if (!selection.query && !selection.checkpoint) {
2925
- console.error("Query is required");
2926
- return;
2927
- }
2928
- let currentCheckpoint = selection.checkpoint ? getCheckpointById(selection.checkpoint.jobId, selection.checkpoint.id) : checkpoint;
2929
- if (currentCheckpoint && currentCheckpoint.expert.key !== selection.expertKey) {
2930
- console.error(
2931
- `Checkpoint expert key ${currentCheckpoint.expert.key} does not match input expert key ${selection.expertKey}`
2932
- );
2933
- return;
2934
- }
2935
- const lockfilePath = findLockfile();
2936
- const lockfile = lockfilePath ? loadLockfile(lockfilePath) ?? void 0 : void 0;
2937
- let currentQuery = selection.query;
2938
- let currentJobId = currentCheckpoint?.jobId ?? input.options.jobId ?? createId();
2939
- let isNextQueryInteractiveToolResult = input.options.interactiveToolCallResult ?? false;
2940
- let isFirstIteration = true;
2941
- const initialHistoricalEvents = currentCheckpoint ? getAllEventContentsForJob(currentCheckpoint.jobId, currentCheckpoint.stepNumber) : void 0;
2942
- while (currentQuery !== null) {
2943
- const historicalEvents = isFirstIteration ? initialHistoricalEvents : void 0;
2944
- const runId = createId();
2945
- const { result: executionResult, eventListener } = renderExecution({
2946
- expertKey: selection.expertKey,
2947
- query: currentQuery,
2948
- config: {
2949
- runtimeVersion,
2950
- model,
2951
- maxSteps,
2952
- maxRetries,
2953
- timeout,
2954
- contextWindowUsage: currentCheckpoint?.contextWindowUsage ?? 0
2955
- },
2956
- continueTimeoutMs: CONTINUE_TIMEOUT_MS,
2957
- historicalEvents
2958
- });
2959
- const runResult = await run(
2960
- {
2961
- setting: {
2962
- jobId: currentJobId,
2963
- runId,
2964
- expertKey: selection.expertKey,
2965
- input: isNextQueryInteractiveToolResult && currentCheckpoint ? parseInteractiveToolCallResult(currentQuery, currentCheckpoint) : { text: currentQuery },
2966
- experts,
2967
- model,
2968
- providerConfig,
2969
- reasoningBudget: input.options.reasoningBudget ?? perstackConfig.reasoningBudget,
2970
- maxSteps: input.options.maxSteps ?? perstackConfig.maxSteps,
2971
- maxRetries: input.options.maxRetries ?? perstackConfig.maxRetries,
2972
- timeout: input.options.timeout ?? perstackConfig.timeout,
2973
- perstackApiBaseUrl: perstackConfig.perstackApiBaseUrl,
2974
- perstackApiKey: env.PERSTACK_API_KEY,
2975
- perstackBaseSkillCommand: perstackConfig.perstackBaseSkillCommand,
2976
- env,
2977
- verbose: input.options.verbose
2978
- },
2979
- checkpoint: currentCheckpoint
2980
- },
2981
- {
2982
- eventListener,
2983
- storeCheckpoint: defaultStoreCheckpoint,
2984
- storeEvent: defaultStoreEvent,
2985
- retrieveCheckpoint: defaultRetrieveCheckpoint,
2986
- storeJob: storeJob,
2987
- retrieveJob: retrieveJob,
2988
- createJob: createInitialJob,
2989
- lockfile
2990
- }
2991
- );
2992
- const result = await executionResult;
2993
- const canContinue = runResult.status === "completed" || runResult.status === "stoppedByExceededMaxSteps" || runResult.status === "stoppedByError" || runResult.status === "stoppedByInteractiveTool";
2994
- if (result.nextQuery && canContinue) {
2995
- currentQuery = result.nextQuery;
2996
- currentCheckpoint = runResult;
2997
- currentJobId = runResult.jobId;
2998
- isNextQueryInteractiveToolResult = runResult.status === "stoppedByInteractiveTool";
2999
- isFirstIteration = false;
3000
- } else {
3001
- currentQuery = null;
3002
- }
3003
- }
3004
- } catch (error) {
3005
- if (error instanceof Error) {
3006
- console.error(error.message);
3007
- } else {
3008
- console.error(error);
3009
- }
3010
- }
3011
- });
3012
-
3013
1038
  // bin/cli.ts
3014
1039
  var program = new Command().name(package_default.name).description(package_default.description).version(package_default.version).addCommand(startCommand).addCommand(runCommand).addCommand(logCommand).addCommand(installCommand);
3015
1040
  program.parse();