perstack 0.0.83 → 0.0.85

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,36 +1,111 @@
1
1
  #!/usr/bin/env node
2
- import { getPerstackConfig, getEnv, resolveRunContext, parseInteractiveToolCallResultJson, parseInteractiveToolCallResult, createInitialJob, retrieveJob, storeJob, defaultRetrieveCheckpoint, defaultStoreEvent, defaultStoreCheckpoint, startCommand, getAllRuns, getEventContents, getCheckpointsByJobId, getAllJobs } from '../chunk-FOHDMSVR.js';
3
2
  import { Command } from 'commander';
4
- import { writeFile, readFile } from 'fs/promises';
5
- import path from 'path';
3
+ import { writeFile, readFile, mkdir } from 'fs/promises';
4
+ import path2 from 'path';
6
5
  import { createApiClient } from '@perstack/api-client';
7
- import { parseWithFriendlyError, runCommandInputSchema, validateEventFilter, createFilteredEventListener, defaultPerstackApiBaseUrl, expertSchema } from '@perstack/core';
8
- import { collectToolDefinitionsForExpert, findLockfile, loadLockfile, run } from '@perstack/runtime';
6
+ import { parseWithFriendlyError, runCommandInputSchema, validateEventFilter, createFilteredEventListener, defaultPerstackApiBaseUrl, jobSchema, checkpointSchema, startCommandInputSchema, defaultMaxRetries, defaultTimeout, perstackConfigSchema, expertSchema, runSettingSchema, BASE_SKILL_PREFIX, createBaseToolActivity, createGeneralToolActivity } from '@perstack/core';
7
+ import { collectToolDefinitionsForExpert, findLockfile, loadLockfile, run, runtimeVersion } from '@perstack/runtime';
8
+ import dotenv from 'dotenv';
9
9
  import TOML from 'smol-toml';
10
- import { existsSync, readdirSync, statSync } from 'fs';
10
+ import { existsSync, readdirSync, readFileSync, mkdirSync, writeFileSync, statSync } from 'fs';
11
11
  import { createId } from '@paralleldrive/cuid2';
12
+ import { render, useApp, useInput, Box, Text } from 'ink';
13
+ import { createContext, useMemo, useReducer, useState, useEffect, useCallback, useRef, useInsertionEffect, useContext } from 'react';
14
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
12
15
 
13
16
  // package.json
14
17
  var package_default = {
15
18
  name: "perstack",
16
- version: "0.0.83",
19
+ version: "0.0.85",
17
20
  description: "PerStack CLI"};
21
+ function getEnv(envPath) {
22
+ const env = Object.fromEntries(
23
+ Object.entries(process.env).filter(([_, value]) => !!value).map(([key, value]) => [key, value])
24
+ );
25
+ dotenv.config({ path: envPath, processEnv: env, quiet: true });
26
+ return env;
27
+ }
28
+ var ALLOWED_CONFIG_HOSTS = ["raw.githubusercontent.com"];
29
+ async function getPerstackConfig(configPath) {
30
+ const configString = await findPerstackConfigString(configPath);
31
+ if (configString === null) {
32
+ throw new Error("perstack.toml not found. Create one or specify --config path.");
33
+ }
34
+ return await parsePerstackConfig(configString);
35
+ }
36
+ function isRemoteUrl(configPath) {
37
+ const lower = configPath.toLowerCase();
38
+ return lower.startsWith("https://") || lower.startsWith("http://");
39
+ }
40
+ async function fetchRemoteConfig(url) {
41
+ let parsed;
42
+ try {
43
+ parsed = new URL(url);
44
+ } catch {
45
+ throw new Error(`Invalid remote config URL: ${url}`);
46
+ }
47
+ if (parsed.protocol !== "https:") {
48
+ throw new Error("Remote config requires HTTPS");
49
+ }
50
+ if (!ALLOWED_CONFIG_HOSTS.includes(parsed.hostname)) {
51
+ throw new Error(`Remote config only allowed from: ${ALLOWED_CONFIG_HOSTS.join(", ")}`);
52
+ }
53
+ try {
54
+ const response = await fetch(url, { redirect: "error" });
55
+ if (!response.ok) {
56
+ throw new Error(`${response.status} ${response.statusText}`);
57
+ }
58
+ return await response.text();
59
+ } catch (error) {
60
+ const message = error instanceof Error ? error.message : String(error);
61
+ throw new Error(`Failed to fetch remote config: ${message}`);
62
+ }
63
+ }
64
+ async function findPerstackConfigString(configPath) {
65
+ if (configPath) {
66
+ if (isRemoteUrl(configPath)) {
67
+ return await fetchRemoteConfig(configPath);
68
+ }
69
+ try {
70
+ const tomlString = await readFile(path2.resolve(process.cwd(), configPath), "utf-8");
71
+ return tomlString;
72
+ } catch {
73
+ throw new Error(`Given config path "${configPath}" is not found`);
74
+ }
75
+ }
76
+ return await findPerstackConfigStringRecursively(path2.resolve(process.cwd()));
77
+ }
78
+ async function findPerstackConfigStringRecursively(cwd) {
79
+ try {
80
+ const tomlString = await readFile(path2.resolve(cwd, "perstack.toml"), "utf-8");
81
+ return tomlString;
82
+ } catch {
83
+ if (cwd === path2.parse(cwd).root) {
84
+ return null;
85
+ }
86
+ return await findPerstackConfigStringRecursively(path2.dirname(cwd));
87
+ }
88
+ }
89
+ async function parsePerstackConfig(config) {
90
+ const toml = TOML.parse(config ?? "");
91
+ return parseWithFriendlyError(perstackConfigSchema, toml, "perstack.toml");
92
+ }
18
93
  async function findConfigPath(configPath) {
19
94
  if (configPath) {
20
- return path.resolve(process.cwd(), configPath);
95
+ return path2.resolve(process.cwd(), configPath);
21
96
  }
22
97
  return await findConfigPathRecursively(process.cwd());
23
98
  }
24
99
  async function findConfigPathRecursively(cwd) {
25
- const configPath = path.resolve(cwd, "perstack.toml");
100
+ const configPath = path2.resolve(cwd, "perstack.toml");
26
101
  try {
27
102
  await readFile(configPath);
28
103
  return configPath;
29
104
  } catch {
30
- if (cwd === path.parse(cwd).root) {
105
+ if (cwd === path2.parse(cwd).root) {
31
106
  throw new Error("perstack.toml not found. Create one or specify --config path.");
32
107
  }
33
- return await findConfigPathRecursively(path.dirname(cwd));
108
+ return await findConfigPathRecursively(path2.dirname(cwd));
34
109
  }
35
110
  }
36
111
  function toRuntimeExpert(key, expert) {
@@ -209,10 +284,10 @@ var installCommand = new Command().command("install").description("Generate pers
209
284
  const lockfile = {
210
285
  version: "1",
211
286
  generatedAt: Date.now(),
212
- configPath: path.basename(configPath),
287
+ configPath: path2.basename(configPath),
213
288
  experts: lockfileExperts
214
289
  };
215
- const lockfilePath = path.join(path.dirname(configPath), "perstack.lock");
290
+ const lockfilePath = path2.join(path2.dirname(configPath), "perstack.lock");
216
291
  const lockfileContent = generateLockfileToml(lockfile);
217
292
  await writeFile(lockfilePath, lockfileContent, "utf-8");
218
293
  console.log(`Generated ${lockfilePath}`);
@@ -225,6 +300,178 @@ var installCommand = new Command().command("install").description("Generate pers
225
300
  process.exit(1);
226
301
  }
227
302
  });
303
+ function getJobsDir() {
304
+ return `${process.cwd()}/perstack/jobs`;
305
+ }
306
+ function getJobDir(jobId) {
307
+ return `${getJobsDir()}/${jobId}`;
308
+ }
309
+ function storeJob(job) {
310
+ const jobDir = getJobDir(job.id);
311
+ if (!existsSync(jobDir)) {
312
+ mkdirSync(jobDir, { recursive: true });
313
+ }
314
+ const jobPath = path2.resolve(jobDir, "job.json");
315
+ writeFileSync(jobPath, JSON.stringify(job, null, 2));
316
+ }
317
+ function retrieveJob(jobId) {
318
+ const jobDir = getJobDir(jobId);
319
+ const jobPath = path2.resolve(jobDir, "job.json");
320
+ if (!existsSync(jobPath)) {
321
+ return void 0;
322
+ }
323
+ const content = readFileSync(jobPath, "utf-8");
324
+ return jobSchema.parse(JSON.parse(content));
325
+ }
326
+ function getAllJobs() {
327
+ const jobsDir = getJobsDir();
328
+ if (!existsSync(jobsDir)) {
329
+ return [];
330
+ }
331
+ const jobDirNames = readdirSync(jobsDir, { withFileTypes: true }).filter((dir) => dir.isDirectory()).map((dir) => dir.name);
332
+ if (jobDirNames.length === 0) {
333
+ return [];
334
+ }
335
+ const jobs = [];
336
+ for (const jobDirName of jobDirNames) {
337
+ const jobPath = path2.resolve(jobsDir, jobDirName, "job.json");
338
+ if (!existsSync(jobPath)) {
339
+ continue;
340
+ }
341
+ try {
342
+ const content = readFileSync(jobPath, "utf-8");
343
+ jobs.push(jobSchema.parse(JSON.parse(content)));
344
+ } catch {
345
+ }
346
+ }
347
+ return jobs.sort((a, b) => b.startedAt - a.startedAt);
348
+ }
349
+ function createInitialJob(jobId, expertKey, maxSteps) {
350
+ return {
351
+ id: jobId,
352
+ status: "running",
353
+ coordinatorExpertKey: expertKey,
354
+ runtimeVersion: "v1.0",
355
+ totalSteps: 0,
356
+ maxSteps,
357
+ usage: {
358
+ inputTokens: 0,
359
+ outputTokens: 0,
360
+ reasoningTokens: 0,
361
+ totalTokens: 0,
362
+ cachedInputTokens: 0
363
+ },
364
+ startedAt: Date.now()
365
+ };
366
+ }
367
+
368
+ // ../../packages/filesystem/src/checkpoint.ts
369
+ function getCheckpointDir(jobId) {
370
+ return `${getJobDir(jobId)}/checkpoints`;
371
+ }
372
+ function getCheckpointPath(jobId, checkpointId) {
373
+ return `${getCheckpointDir(jobId)}/${checkpointId}.json`;
374
+ }
375
+ async function defaultRetrieveCheckpoint(jobId, checkpointId) {
376
+ const checkpointPath = getCheckpointPath(jobId, checkpointId);
377
+ if (!existsSync(checkpointPath)) {
378
+ throw new Error(`checkpoint not found: ${checkpointId}`);
379
+ }
380
+ const checkpoint = await readFile(checkpointPath, "utf8");
381
+ return checkpointSchema.parse(JSON.parse(checkpoint));
382
+ }
383
+ async function defaultStoreCheckpoint(checkpoint) {
384
+ const { id, jobId } = checkpoint;
385
+ const checkpointDir = getCheckpointDir(jobId);
386
+ await mkdir(checkpointDir, { recursive: true });
387
+ await writeFile(getCheckpointPath(jobId, id), JSON.stringify(checkpoint));
388
+ }
389
+ function getCheckpointsByJobId(jobId) {
390
+ const checkpointDir = getCheckpointDir(jobId);
391
+ if (!existsSync(checkpointDir)) {
392
+ return [];
393
+ }
394
+ const files = readdirSync(checkpointDir).filter((file) => file.endsWith(".json"));
395
+ const checkpoints = [];
396
+ for (const file of files) {
397
+ try {
398
+ const content = readFileSync(path2.resolve(checkpointDir, file), "utf-8");
399
+ checkpoints.push(checkpointSchema.parse(JSON.parse(content)));
400
+ } catch {
401
+ }
402
+ }
403
+ return checkpoints.sort((a, b) => a.stepNumber - b.stepNumber);
404
+ }
405
+ function defaultGetRunDir(jobId, runId) {
406
+ return `${process.cwd()}/perstack/jobs/${jobId}/runs/${runId}`;
407
+ }
408
+ function getAllRuns() {
409
+ const jobsDir = getJobsDir();
410
+ if (!existsSync(jobsDir)) {
411
+ return [];
412
+ }
413
+ const jobDirNames = readdirSync(jobsDir, { withFileTypes: true }).filter((dir) => dir.isDirectory()).map((dir) => dir.name);
414
+ if (jobDirNames.length === 0) {
415
+ return [];
416
+ }
417
+ const runs = [];
418
+ for (const jobDirName of jobDirNames) {
419
+ const runsDir = path2.resolve(jobsDir, jobDirName, "runs");
420
+ if (!existsSync(runsDir)) {
421
+ continue;
422
+ }
423
+ const runDirNames = readdirSync(runsDir, { withFileTypes: true }).filter((dir) => dir.isDirectory()).map((dir) => dir.name);
424
+ for (const runDirName of runDirNames) {
425
+ const runSettingPath = path2.resolve(runsDir, runDirName, "run-setting.json");
426
+ if (!existsSync(runSettingPath)) {
427
+ continue;
428
+ }
429
+ try {
430
+ const content = readFileSync(runSettingPath, "utf-8");
431
+ runs.push(runSettingSchema.parse(JSON.parse(content)));
432
+ } catch {
433
+ }
434
+ }
435
+ }
436
+ return runs.sort((a, b) => b.updatedAt - a.updatedAt);
437
+ }
438
+
439
+ // ../../packages/filesystem/src/event.ts
440
+ async function defaultStoreEvent(event) {
441
+ const { timestamp, jobId, runId, stepNumber, type } = event;
442
+ const runDir = defaultGetRunDir(jobId, runId);
443
+ const eventPath = `${runDir}/event-${timestamp}-${stepNumber}-${type}.json`;
444
+ await mkdir(runDir, { recursive: true });
445
+ await writeFile(eventPath, JSON.stringify(event));
446
+ }
447
+ function getEventContents(jobId, runId, maxStepNumber) {
448
+ const runDir = defaultGetRunDir(jobId, runId);
449
+ if (!existsSync(runDir)) {
450
+ return [];
451
+ }
452
+ const eventFiles = readdirSync(runDir).filter((file) => file.startsWith("event-")).map((file) => {
453
+ const [_, timestamp, step, type] = file.split(".")[0].split("-");
454
+ return { file, timestamp: Number(timestamp), stepNumber: Number(step), type };
455
+ }).filter((e) => maxStepNumber === void 0 || e.stepNumber <= maxStepNumber).sort((a, b) => a.timestamp - b.timestamp);
456
+ const events = [];
457
+ for (const { file } of eventFiles) {
458
+ try {
459
+ const content = readFileSync(path2.resolve(runDir, file), "utf-8");
460
+ events.push(JSON.parse(content));
461
+ } catch {
462
+ }
463
+ }
464
+ return events;
465
+ }
466
+ function getRunIdsByJobId(jobId) {
467
+ const runsDir = path2.resolve(getJobDir(jobId), "runs");
468
+ if (!existsSync(runsDir)) {
469
+ return [];
470
+ }
471
+ return readdirSync(runsDir, { withFileTypes: true }).filter((dir) => dir.isDirectory()).map((dir) => dir.name);
472
+ }
473
+
474
+ // src/lib/log/data-fetcher.ts
228
475
  function createLogDataFetcher(storage) {
229
476
  return {
230
477
  async getJob(jobId) {
@@ -285,7 +532,7 @@ function createLogDataFetcher(storage) {
285
532
  }
286
533
  function getJobDirMtime(basePath, jobId) {
287
534
  try {
288
- const jobDir = path.join(basePath, "jobs", jobId);
535
+ const jobDir = path2.join(basePath, "jobs", jobId);
289
536
  const stats = statSync(jobDir);
290
537
  return stats.mtimeMs;
291
538
  } catch {
@@ -301,7 +548,7 @@ function createStorageAdapter(basePath) {
301
548
  getEventContents: async (jobId, runId, maxStep) => getEventContents(jobId, runId, maxStep),
302
549
  getAllRuns: async () => getAllRuns(),
303
550
  getJobIds: () => {
304
- const jobsDir = path.join(basePath, "jobs");
551
+ const jobsDir = path2.join(basePath, "jobs");
305
552
  if (!existsSync(jobsDir)) return [];
306
553
  return readdirSync(jobsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
307
554
  },
@@ -365,18 +612,18 @@ function parseFilterExpression(expression) {
365
612
  const value = parseValue(trimmedValue);
366
613
  return { field, operator, value };
367
614
  }
368
- function parseFieldPath(path3) {
615
+ function parseFieldPath(path8) {
369
616
  const parts = [];
370
617
  let current = "";
371
618
  let i = 0;
372
- while (i < path3.length) {
373
- if (path3[i] === ".") {
619
+ while (i < path8.length) {
620
+ if (path8[i] === ".") {
374
621
  if (current) {
375
622
  parts.push(current);
376
623
  current = "";
377
624
  }
378
625
  i++;
379
- } else if (path3[i] === "[" && path3[i + 1] === "]") {
626
+ } else if (path8[i] === "[" && path8[i + 1] === "]") {
380
627
  if (current) {
381
628
  parts.push(current);
382
629
  current = "";
@@ -384,7 +631,7 @@ function parseFieldPath(path3) {
384
631
  parts.push("*");
385
632
  i += 2;
386
633
  } else {
387
- current += path3[i];
634
+ current += path8[i];
388
635
  i++;
389
636
  }
390
637
  }
@@ -419,10 +666,10 @@ function evaluateCondition(event, condition) {
419
666
  }
420
667
  return compareValues(actualValue, operator, value);
421
668
  }
422
- function getFieldValue(obj, path3) {
669
+ function getFieldValue(obj, path8) {
423
670
  let current = obj;
424
- for (let i = 0; i < path3.length; i++) {
425
- const segment = path3[i];
671
+ for (let i = 0; i < path8.length; i++) {
672
+ const segment = path8[i];
426
673
  if (current === null || current === void 0) {
427
674
  return void 0;
428
675
  }
@@ -430,7 +677,7 @@ function getFieldValue(obj, path3) {
430
677
  if (!Array.isArray(current)) {
431
678
  return void 0;
432
679
  }
433
- const remainingPath = path3.slice(i + 1);
680
+ const remainingPath = path8.slice(i + 1);
434
681
  if (remainingPath.length === 0) {
435
682
  return current;
436
683
  }
@@ -937,6 +1184,260 @@ async function buildOutput(fetcher, options, filterOptions, storagePath) {
937
1184
  matchedAfterPagination: result.matchedAfterPagination
938
1185
  };
939
1186
  }
1187
+
1188
+ // ../../packages/tui/src/lib/provider-config.ts
1189
+ function getProviderConfig(provider, env, providerTable) {
1190
+ const setting = providerTable?.setting ?? {};
1191
+ switch (provider) {
1192
+ case "anthropic": {
1193
+ const apiKey = env.ANTHROPIC_API_KEY;
1194
+ if (!apiKey) throw new Error("ANTHROPIC_API_KEY is not set");
1195
+ return {
1196
+ providerName: "anthropic",
1197
+ apiKey,
1198
+ baseUrl: setting.baseUrl ?? env.ANTHROPIC_BASE_URL,
1199
+ headers: setting.headers
1200
+ };
1201
+ }
1202
+ case "google": {
1203
+ const apiKey = env.GOOGLE_GENERATIVE_AI_API_KEY;
1204
+ if (!apiKey) throw new Error("GOOGLE_GENERATIVE_AI_API_KEY is not set");
1205
+ return {
1206
+ providerName: "google",
1207
+ apiKey,
1208
+ baseUrl: setting.baseUrl ?? env.GOOGLE_GENERATIVE_AI_BASE_URL,
1209
+ headers: setting.headers
1210
+ };
1211
+ }
1212
+ case "openai": {
1213
+ const apiKey = env.OPENAI_API_KEY;
1214
+ if (!apiKey) throw new Error("OPENAI_API_KEY is not set");
1215
+ return {
1216
+ providerName: "openai",
1217
+ apiKey,
1218
+ baseUrl: setting.baseUrl ?? env.OPENAI_BASE_URL,
1219
+ organization: setting.organization ?? env.OPENAI_ORGANIZATION,
1220
+ project: setting.project ?? env.OPENAI_PROJECT,
1221
+ name: setting.name,
1222
+ headers: setting.headers
1223
+ };
1224
+ }
1225
+ case "ollama": {
1226
+ return {
1227
+ providerName: "ollama",
1228
+ baseUrl: setting.baseUrl ?? env.OLLAMA_BASE_URL,
1229
+ headers: setting.headers
1230
+ };
1231
+ }
1232
+ case "azure-openai": {
1233
+ const apiKey = env.AZURE_API_KEY;
1234
+ if (!apiKey) throw new Error("AZURE_API_KEY is not set");
1235
+ const resourceName = setting.resourceName ?? env.AZURE_RESOURCE_NAME;
1236
+ const baseUrl = setting.baseUrl ?? env.AZURE_BASE_URL;
1237
+ if (!resourceName && !baseUrl) throw new Error("AZURE_RESOURCE_NAME or baseUrl is not set");
1238
+ return {
1239
+ providerName: "azure-openai",
1240
+ apiKey,
1241
+ resourceName,
1242
+ apiVersion: setting.apiVersion ?? env.AZURE_API_VERSION,
1243
+ baseUrl,
1244
+ headers: setting.headers,
1245
+ useDeploymentBasedUrls: setting.useDeploymentBasedUrls
1246
+ };
1247
+ }
1248
+ case "amazon-bedrock": {
1249
+ const accessKeyId = env.AWS_ACCESS_KEY_ID;
1250
+ const secretAccessKey = env.AWS_SECRET_ACCESS_KEY;
1251
+ const sessionToken = env.AWS_SESSION_TOKEN;
1252
+ if (!accessKeyId) throw new Error("AWS_ACCESS_KEY_ID is not set");
1253
+ if (!secretAccessKey) throw new Error("AWS_SECRET_ACCESS_KEY is not set");
1254
+ const region = setting.region ?? env.AWS_REGION;
1255
+ if (!region) throw new Error("AWS_REGION is not set");
1256
+ return {
1257
+ providerName: "amazon-bedrock",
1258
+ accessKeyId,
1259
+ secretAccessKey,
1260
+ region,
1261
+ sessionToken
1262
+ };
1263
+ }
1264
+ case "google-vertex": {
1265
+ return {
1266
+ providerName: "google-vertex",
1267
+ project: setting.project ?? env.GOOGLE_VERTEX_PROJECT,
1268
+ location: setting.location ?? env.GOOGLE_VERTEX_LOCATION,
1269
+ baseUrl: setting.baseUrl ?? env.GOOGLE_VERTEX_BASE_URL,
1270
+ headers: setting.headers
1271
+ };
1272
+ }
1273
+ case "deepseek": {
1274
+ const apiKey = env.DEEPSEEK_API_KEY;
1275
+ if (!apiKey) throw new Error("DEEPSEEK_API_KEY is not set");
1276
+ return {
1277
+ providerName: "deepseek",
1278
+ apiKey,
1279
+ baseUrl: setting.baseUrl ?? env.DEEPSEEK_BASE_URL,
1280
+ headers: setting.headers
1281
+ };
1282
+ }
1283
+ }
1284
+ }
1285
+ function getAllJobs2() {
1286
+ return getAllJobs();
1287
+ }
1288
+ function getAllRuns2() {
1289
+ return getAllRuns();
1290
+ }
1291
+ function getMostRecentRun() {
1292
+ const runs = getAllRuns2();
1293
+ if (runs.length === 0) {
1294
+ throw new Error("No runs found");
1295
+ }
1296
+ return runs[0];
1297
+ }
1298
+ function getCheckpointsByJobId2(jobId) {
1299
+ return getCheckpointsByJobId(jobId);
1300
+ }
1301
+ function getMostRecentCheckpoint(jobId) {
1302
+ const targetJobId = jobId ?? getMostRecentRun().jobId;
1303
+ const checkpoints = getCheckpointsByJobId2(targetJobId);
1304
+ if (checkpoints.length === 0) {
1305
+ throw new Error(`No checkpoints found for job ${targetJobId}`);
1306
+ }
1307
+ return checkpoints[checkpoints.length - 1];
1308
+ }
1309
+ function getRecentExperts(limit) {
1310
+ const runs = getAllRuns2();
1311
+ const expertMap = /* @__PURE__ */ new Map();
1312
+ for (const setting of runs) {
1313
+ const expertKey = setting.expertKey;
1314
+ if (!expertMap.has(expertKey) || expertMap.get(expertKey).lastUsed < setting.updatedAt) {
1315
+ expertMap.set(expertKey, {
1316
+ key: expertKey,
1317
+ name: expertKey,
1318
+ lastUsed: setting.updatedAt
1319
+ });
1320
+ }
1321
+ }
1322
+ return Array.from(expertMap.values()).sort((a, b) => b.lastUsed - a.lastUsed).slice(0, limit);
1323
+ }
1324
+ function getCheckpointById(jobId, checkpointId) {
1325
+ const checkpointPath = getCheckpointPath(jobId, checkpointId);
1326
+ if (!existsSync(checkpointPath)) {
1327
+ throw new Error(`Checkpoint ${checkpointId} not found in job ${jobId}`);
1328
+ }
1329
+ const checkpoint = readFileSync(checkpointPath, "utf-8");
1330
+ return checkpointSchema.parse(JSON.parse(checkpoint));
1331
+ }
1332
+ function getCheckpointsWithDetails(jobId) {
1333
+ return getCheckpointsByJobId2(jobId).map((cp) => ({
1334
+ id: cp.id,
1335
+ runId: cp.runId,
1336
+ stepNumber: cp.stepNumber,
1337
+ contextWindowUsage: cp.contextWindowUsage ?? 0
1338
+ })).sort((a, b) => b.stepNumber - a.stepNumber);
1339
+ }
1340
+ function getAllEventContentsForJob(jobId, maxStepNumber) {
1341
+ const runIds = getRunIdsByJobId(jobId);
1342
+ const allEvents = [];
1343
+ for (const runId of runIds) {
1344
+ const events = getEventContents(jobId, runId, maxStepNumber);
1345
+ allEvents.push(...events);
1346
+ }
1347
+ return allEvents.sort((a, b) => a.timestamp - b.timestamp);
1348
+ }
1349
+
1350
+ // ../../packages/tui/src/lib/context.ts
1351
+ var defaultProvider = "anthropic";
1352
+ var defaultModel = "claude-sonnet-4-5";
1353
+ async function resolveRunContext(input) {
1354
+ const perstackConfig = input.perstackConfig ?? await getPerstackConfig(input.configPath);
1355
+ let checkpoint;
1356
+ if (input.resumeFrom) {
1357
+ if (!input.continueJob) {
1358
+ throw new Error("--resume-from requires --continue-job");
1359
+ }
1360
+ checkpoint = getCheckpointById(input.continueJob, input.resumeFrom);
1361
+ } else if (input.continueJob) {
1362
+ checkpoint = getMostRecentCheckpoint(input.continueJob);
1363
+ } else if (input.continue) {
1364
+ checkpoint = getMostRecentCheckpoint();
1365
+ }
1366
+ if ((input.continue || input.continueJob || input.resumeFrom) && !checkpoint) {
1367
+ throw new Error("No checkpoint found");
1368
+ }
1369
+ if (checkpoint && input.expertKey && checkpoint.expert.key !== input.expertKey) {
1370
+ throw new Error(
1371
+ `Checkpoint expert key ${checkpoint.expert.key} does not match input expert key ${input.expertKey}`
1372
+ );
1373
+ }
1374
+ const envPath = input.envPath && input.envPath.length > 0 ? input.envPath : perstackConfig.envPath ?? [".env", ".env.local"];
1375
+ const env = getEnv(envPath);
1376
+ const provider = input.provider ?? perstackConfig.provider?.providerName ?? defaultProvider;
1377
+ const model = input.model ?? perstackConfig.model ?? defaultModel;
1378
+ const providerConfig = getProviderConfig(provider, env, perstackConfig.provider);
1379
+ const experts = Object.fromEntries(
1380
+ Object.entries(perstackConfig.experts ?? {}).map(([name, expert]) => {
1381
+ return [
1382
+ name,
1383
+ {
1384
+ name,
1385
+ version: expert.version ?? "1.0.0",
1386
+ description: expert.description,
1387
+ instruction: expert.instruction,
1388
+ skills: expert.skills,
1389
+ delegates: expert.delegates,
1390
+ tags: expert.tags
1391
+ }
1392
+ ];
1393
+ })
1394
+ );
1395
+ return {
1396
+ perstackConfig,
1397
+ checkpoint,
1398
+ env,
1399
+ providerConfig,
1400
+ model,
1401
+ experts
1402
+ };
1403
+ }
1404
+
1405
+ // ../../packages/tui/src/lib/interactive.ts
1406
+ function parseInteractiveToolCallResult(query, checkpoint) {
1407
+ const lastMessage = checkpoint.messages[checkpoint.messages.length - 1];
1408
+ if (lastMessage.type !== "expertMessage") {
1409
+ throw new Error("Last message is not a expert message");
1410
+ }
1411
+ const content = lastMessage.contents.find((c) => c.type === "toolCallPart");
1412
+ if (!content || content.type !== "toolCallPart") {
1413
+ throw new Error("Last message content is not a tool call part");
1414
+ }
1415
+ const toolCallId = content.toolCallId;
1416
+ const toolName = content.toolName;
1417
+ const pendingToolCall = checkpoint.pendingToolCalls?.find((tc) => tc.id === toolCallId);
1418
+ const skillName = pendingToolCall?.skillName ?? "";
1419
+ return {
1420
+ interactiveToolCallResult: {
1421
+ toolCallId,
1422
+ toolName,
1423
+ skillName,
1424
+ text: query
1425
+ }
1426
+ };
1427
+ }
1428
+ function parseInteractiveToolCallResultJson(query) {
1429
+ try {
1430
+ const parsed = JSON.parse(query);
1431
+ if (typeof parsed === "object" && parsed !== null && "toolCallId" in parsed && "toolName" in parsed && "skillName" in parsed && "text" in parsed) {
1432
+ return {
1433
+ interactiveToolCallResult: parsed
1434
+ };
1435
+ }
1436
+ return null;
1437
+ } catch {
1438
+ return null;
1439
+ }
1440
+ }
940
1441
  var defaultEventListener = (event) => console.log(JSON.stringify(event));
941
1442
  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(
942
1443
  "--reasoning-budget <budget>",
@@ -1034,6 +1535,2160 @@ var runCommand = new Command().command("run").description("Run Perstack with JSO
1034
1535
  }
1035
1536
  });
1036
1537
 
1538
+ // ../../packages/tui-components/src/utils/event-queue.ts
1539
+ var defaultErrorLogger = (message, error) => {
1540
+ console.error(message, error);
1541
+ };
1542
+ var EventQueue = class _EventQueue {
1543
+ static MAX_PENDING_EVENTS = 1e3;
1544
+ pendingEvents = [];
1545
+ handler = null;
1546
+ onError;
1547
+ errorLogger;
1548
+ constructor(options) {
1549
+ this.onError = options?.onError;
1550
+ this.errorLogger = options?.errorLogger ?? defaultErrorLogger;
1551
+ }
1552
+ setHandler(fn) {
1553
+ this.handler = fn;
1554
+ for (const event of this.pendingEvents) {
1555
+ this.safeHandle(event);
1556
+ }
1557
+ this.pendingEvents.length = 0;
1558
+ }
1559
+ emit(event) {
1560
+ if (this.handler) {
1561
+ this.safeHandle(event);
1562
+ } else {
1563
+ if (this.pendingEvents.length >= _EventQueue.MAX_PENDING_EVENTS) {
1564
+ this.pendingEvents.shift();
1565
+ }
1566
+ this.pendingEvents.push(event);
1567
+ }
1568
+ }
1569
+ safeHandle(event) {
1570
+ try {
1571
+ this.handler?.(event);
1572
+ } catch (error) {
1573
+ if (this.onError) {
1574
+ this.onError(error);
1575
+ } else {
1576
+ this.errorLogger("EventQueue handler error:", error);
1577
+ }
1578
+ }
1579
+ }
1580
+ };
1581
+ var InputAreaContext = createContext(null);
1582
+ var InputAreaProvider = InputAreaContext.Provider;
1583
+ var useInputAreaContext = () => {
1584
+ const context = useContext(InputAreaContext);
1585
+ if (!context) {
1586
+ throw new Error("useInputAreaContext must be used within InputAreaProvider");
1587
+ }
1588
+ return context;
1589
+ };
1590
+
1591
+ // ../../packages/tui-components/src/helpers.ts
1592
+ var truncateText = (text, maxLength) => {
1593
+ if (text.length <= maxLength) return text;
1594
+ return `${text.slice(0, maxLength)}...`;
1595
+ };
1596
+ var assertNever = (x) => {
1597
+ throw new Error(`Unexpected value: ${x}`);
1598
+ };
1599
+ var formatTimestamp2 = (timestamp) => new Date(timestamp).toLocaleString();
1600
+ var shortenPath = (fullPath, maxLength = 60) => {
1601
+ if (fullPath.length <= maxLength) return fullPath;
1602
+ const parts = fullPath.split("/");
1603
+ if (parts.length <= 2) return fullPath;
1604
+ const filename = parts[parts.length - 1] ?? "";
1605
+ const parentDir = parts[parts.length - 2] ?? "";
1606
+ const shortened = `.../${parentDir}/${filename}`;
1607
+ if (shortened.length <= maxLength) return shortened;
1608
+ return `.../${filename}`;
1609
+ };
1610
+ var summarizeOutput = (lines, maxLines) => {
1611
+ const filtered = lines.filter((l) => l.trim());
1612
+ const visible = filtered.slice(0, maxLines);
1613
+ const remaining = filtered.length - visible.length;
1614
+ return { visible, remaining };
1615
+ };
1616
+
1617
+ // ../../packages/tui-components/src/constants.ts
1618
+ var UI_CONSTANTS = {
1619
+ MAX_VISIBLE_LIST_ITEMS: 25,
1620
+ TRUNCATE_TEXT_MEDIUM: 80,
1621
+ TRUNCATE_TEXT_DEFAULT: 100};
1622
+ var RENDER_CONSTANTS = {
1623
+ EXEC_OUTPUT_MAX_LINES: 4,
1624
+ NEW_TODO_MAX_PREVIEW: 3,
1625
+ LIST_DIR_MAX_ITEMS: 4
1626
+ };
1627
+ var INDICATOR = {
1628
+ BULLET: "\u25CF",
1629
+ TREE: "\u2514",
1630
+ ELLIPSIS: "..."
1631
+ };
1632
+ var STOP_EVENT_TYPES = [
1633
+ "stopRunByInteractiveTool",
1634
+ "stopRunByDelegate",
1635
+ "stopRunByExceededMaxSteps"
1636
+ ];
1637
+ var KEY_BINDINGS = {
1638
+ NAVIGATE_UP: "\u2191",
1639
+ NAVIGATE_DOWN: "\u2193",
1640
+ SELECT: "Enter",
1641
+ BACK: "b",
1642
+ ESCAPE: "Esc",
1643
+ INPUT_MODE: "i",
1644
+ HISTORY: "h",
1645
+ NEW: "n",
1646
+ CHECKPOINTS: "c",
1647
+ EVENTS: "e",
1648
+ RESUME: "Enter"};
1649
+ var KEY_HINTS = {
1650
+ NAVIGATE: `${KEY_BINDINGS.NAVIGATE_UP}${KEY_BINDINGS.NAVIGATE_DOWN}:Navigate`,
1651
+ SELECT: `${KEY_BINDINGS.SELECT}:Select`,
1652
+ RESUME: `${KEY_BINDINGS.RESUME}:Resume`,
1653
+ BACK: `${KEY_BINDINGS.BACK}:Back`,
1654
+ CANCEL: `${KEY_BINDINGS.ESCAPE}:Cancel`,
1655
+ INPUT: `${KEY_BINDINGS.INPUT_MODE}:Input`,
1656
+ HISTORY: `${KEY_BINDINGS.HISTORY}:History`,
1657
+ NEW: `${KEY_BINDINGS.NEW}:New Run`,
1658
+ CHECKPOINTS: `${KEY_BINDINGS.CHECKPOINTS}:Checkpoints`,
1659
+ EVENTS: `${KEY_BINDINGS.EVENTS}:Events`};
1660
+ var useListNavigation = (options) => {
1661
+ const { items, onSelect, onBack } = options;
1662
+ const [selectedIndex, setSelectedIndex] = useState(0);
1663
+ useEffect(() => {
1664
+ if (selectedIndex >= items.length && items.length > 0) {
1665
+ setSelectedIndex(items.length - 1);
1666
+ }
1667
+ }, [items.length, selectedIndex]);
1668
+ const handleNavigation = useCallback(
1669
+ (inputChar, key) => {
1670
+ if (key.upArrow && items.length > 0) {
1671
+ setSelectedIndex((prev) => Math.max(0, prev - 1));
1672
+ return true;
1673
+ }
1674
+ if (key.downArrow && items.length > 0) {
1675
+ setSelectedIndex((prev) => Math.min(items.length - 1, prev + 1));
1676
+ return true;
1677
+ }
1678
+ if (key.return && items[selectedIndex]) {
1679
+ onSelect?.(items[selectedIndex]);
1680
+ return true;
1681
+ }
1682
+ if ((key.escape || inputChar === "b") && onBack) {
1683
+ onBack();
1684
+ return true;
1685
+ }
1686
+ return false;
1687
+ },
1688
+ [items, selectedIndex, onSelect, onBack]
1689
+ );
1690
+ return { selectedIndex, handleNavigation };
1691
+ };
1692
+ var ListBrowser = ({
1693
+ title,
1694
+ items,
1695
+ renderItem,
1696
+ onSelect,
1697
+ emptyMessage = "No items found",
1698
+ extraKeyHandler,
1699
+ onBack,
1700
+ maxItems = UI_CONSTANTS.MAX_VISIBLE_LIST_ITEMS
1701
+ }) => {
1702
+ const { selectedIndex, handleNavigation } = useListNavigation({
1703
+ items,
1704
+ onSelect,
1705
+ onBack
1706
+ });
1707
+ useInput((inputChar, key) => {
1708
+ if (extraKeyHandler?.(inputChar, key, items[selectedIndex])) return;
1709
+ handleNavigation(inputChar, key);
1710
+ });
1711
+ const { scrollOffset, displayItems } = useMemo(() => {
1712
+ const offset = Math.max(0, Math.min(selectedIndex - maxItems + 1, items.length - maxItems));
1713
+ return {
1714
+ scrollOffset: offset,
1715
+ displayItems: items.slice(offset, offset + maxItems)
1716
+ };
1717
+ }, [items, selectedIndex, maxItems]);
1718
+ const hasMoreAbove = scrollOffset > 0;
1719
+ const hasMoreBelow = scrollOffset + maxItems < items.length;
1720
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
1721
+ /* @__PURE__ */ jsxs(Box, { children: [
1722
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: title }),
1723
+ items.length > maxItems && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
1724
+ " ",
1725
+ "(",
1726
+ selectedIndex + 1,
1727
+ "/",
1728
+ items.length,
1729
+ ")"
1730
+ ] })
1731
+ ] }),
1732
+ hasMoreAbove && /* @__PURE__ */ jsx(Text, { color: "gray", children: INDICATOR.ELLIPSIS }),
1733
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: displayItems.length === 0 ? /* @__PURE__ */ jsx(Text, { color: "gray", children: emptyMessage }) : displayItems.map((item, index) => {
1734
+ const actualIndex = scrollOffset + index;
1735
+ return renderItem(item, actualIndex === selectedIndex, actualIndex);
1736
+ }) }),
1737
+ hasMoreBelow && /* @__PURE__ */ jsx(Text, { color: "gray", children: INDICATOR.ELLIPSIS })
1738
+ ] });
1739
+ };
1740
+ var BrowsingCheckpointsInput = ({
1741
+ job,
1742
+ checkpoints,
1743
+ onCheckpointSelect,
1744
+ onCheckpointResume,
1745
+ onBack,
1746
+ showEventsHint = true
1747
+ }) => /* @__PURE__ */ jsx(
1748
+ ListBrowser,
1749
+ {
1750
+ title: `Checkpoints for ${job.expertKey} ${KEY_HINTS.NAVIGATE} ${KEY_HINTS.RESUME} ${showEventsHint ? KEY_HINTS.EVENTS : ""} ${KEY_HINTS.BACK}`.trim(),
1751
+ items: checkpoints,
1752
+ onSelect: onCheckpointResume,
1753
+ onBack,
1754
+ emptyMessage: "No checkpoints found",
1755
+ extraKeyHandler: (char, _key, selected) => {
1756
+ if (char === "e" && selected) {
1757
+ onCheckpointSelect(selected);
1758
+ return true;
1759
+ }
1760
+ return false;
1761
+ },
1762
+ renderItem: (cp, isSelected) => /* @__PURE__ */ jsxs(Text, { color: isSelected ? "cyan" : "gray", children: [
1763
+ isSelected ? ">" : " ",
1764
+ " Step ",
1765
+ cp.stepNumber,
1766
+ " (",
1767
+ cp.id,
1768
+ ")"
1769
+ ] }, cp.id)
1770
+ }
1771
+ );
1772
+ var BrowsingEventDetailInput = ({ event, onBack }) => {
1773
+ useInput((input, key) => {
1774
+ if (input === "b" || key.escape) {
1775
+ onBack();
1776
+ }
1777
+ });
1778
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
1779
+ /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
1780
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Event Detail" }),
1781
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1782
+ " ",
1783
+ KEY_HINTS.BACK
1784
+ ] })
1785
+ ] }),
1786
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [
1787
+ /* @__PURE__ */ jsxs(Text, { children: [
1788
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Type: " }),
1789
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: event.type })
1790
+ ] }),
1791
+ /* @__PURE__ */ jsxs(Text, { children: [
1792
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Step: " }),
1793
+ /* @__PURE__ */ jsx(Text, { children: event.stepNumber })
1794
+ ] }),
1795
+ /* @__PURE__ */ jsxs(Text, { children: [
1796
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Timestamp: " }),
1797
+ /* @__PURE__ */ jsx(Text, { children: formatTimestamp2(event.timestamp) })
1798
+ ] }),
1799
+ /* @__PURE__ */ jsxs(Text, { children: [
1800
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "ID: " }),
1801
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: event.id })
1802
+ ] }),
1803
+ /* @__PURE__ */ jsxs(Text, { children: [
1804
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Run ID: " }),
1805
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: event.runId })
1806
+ ] })
1807
+ ] })
1808
+ ] });
1809
+ };
1810
+ var BrowsingEventsInput = ({
1811
+ checkpoint,
1812
+ events,
1813
+ onEventSelect,
1814
+ onBack
1815
+ }) => /* @__PURE__ */ jsx(
1816
+ ListBrowser,
1817
+ {
1818
+ title: `Events for Step ${checkpoint.stepNumber} ${KEY_HINTS.NAVIGATE} ${KEY_HINTS.SELECT} ${KEY_HINTS.BACK}`,
1819
+ items: events,
1820
+ onSelect: onEventSelect,
1821
+ onBack,
1822
+ emptyMessage: "No events found",
1823
+ renderItem: (ev, isSelected) => /* @__PURE__ */ jsxs(Text, { color: isSelected ? "cyan" : "gray", children: [
1824
+ isSelected ? ">" : " ",
1825
+ " [",
1826
+ ev.type,
1827
+ "] Step ",
1828
+ ev.stepNumber,
1829
+ " (",
1830
+ formatTimestamp2(ev.timestamp),
1831
+ ")"
1832
+ ] }, ev.id)
1833
+ }
1834
+ );
1835
+ var TOOL_RESULT_EVENT_TYPES = /* @__PURE__ */ new Set(["resolveToolResults", "attemptCompletion"]);
1836
+ function toolToActivity(toolCall, toolResult, reasoning, meta) {
1837
+ const { skillName, toolName } = toolCall;
1838
+ const baseActivity = skillName.startsWith(BASE_SKILL_PREFIX) ? createBaseToolActivity(toolName, toolCall, toolResult, reasoning) : createGeneralToolActivity(skillName, toolName, toolCall, toolResult, reasoning);
1839
+ return {
1840
+ ...baseActivity,
1841
+ id: meta.id,
1842
+ expertKey: meta.expertKey,
1843
+ runId: meta.runId,
1844
+ previousActivityId: meta.previousActivityId,
1845
+ delegatedBy: meta.delegatedBy
1846
+ };
1847
+ }
1848
+ function createInitialActivityProcessState() {
1849
+ return {
1850
+ tools: /* @__PURE__ */ new Map(),
1851
+ runStates: /* @__PURE__ */ new Map()
1852
+ };
1853
+ }
1854
+ function getOrCreateRunState(state, runId, expertKey) {
1855
+ let runState = state.runStates.get(runId);
1856
+ if (!runState) {
1857
+ runState = {
1858
+ expertKey,
1859
+ queryLogged: false,
1860
+ completionLogged: false,
1861
+ isComplete: false,
1862
+ pendingDelegateCount: 0
1863
+ };
1864
+ state.runStates.set(runId, runState);
1865
+ }
1866
+ return runState;
1867
+ }
1868
+ var isRunEvent = (event) => "type" in event && "expertKey" in event;
1869
+ var isCompleteStreamingReasoningEvent = (event) => "type" in event && event.type === "completeStreamingReasoning";
1870
+ var isToolCallsEvent = (event) => event.type === "callTools" && "toolCalls" in event;
1871
+ var isStopRunByDelegateEvent = (event) => event.type === "stopRunByDelegate" && "checkpoint" in event;
1872
+ var isStopRunByInteractiveToolEvent = (event) => event.type === "stopRunByInteractiveTool" && "checkpoint" in event;
1873
+ var isToolResultsEvent = (event) => event.type === "resolveToolResults" && "toolResults" in event;
1874
+ var isToolResultEvent = (event) => TOOL_RESULT_EVENT_TYPES.has(event.type) && "toolResult" in event;
1875
+ function wrapInGroupIfParallel(activities, reasoning, meta) {
1876
+ if (activities.length <= 1) {
1877
+ return activities;
1878
+ }
1879
+ const activitiesWithoutReasoning = activities.map((a) => {
1880
+ const { reasoning: _, ...rest } = a;
1881
+ return rest;
1882
+ });
1883
+ const group = {
1884
+ type: "parallelGroup",
1885
+ id: `parallel-${activities[0].id}`,
1886
+ expertKey: meta.expertKey,
1887
+ runId: meta.runId,
1888
+ reasoning,
1889
+ activities: activitiesWithoutReasoning
1890
+ };
1891
+ return [group];
1892
+ }
1893
+ function processRunEventToActivity(state, event, addActivity) {
1894
+ if (isCompleteStreamingReasoningEvent(event)) {
1895
+ const reasoningEvent = event;
1896
+ const { runId, text, expertKey } = reasoningEvent;
1897
+ const runState2 = state.runStates.get(runId);
1898
+ if (runState2) {
1899
+ runState2.completedReasoning = text;
1900
+ } else {
1901
+ state.runStates.set(runId, {
1902
+ expertKey,
1903
+ queryLogged: false,
1904
+ completionLogged: false,
1905
+ isComplete: false,
1906
+ pendingDelegateCount: 0,
1907
+ completedReasoning: text
1908
+ });
1909
+ }
1910
+ return;
1911
+ }
1912
+ if (!isRunEvent(event)) {
1913
+ return;
1914
+ }
1915
+ const runState = getOrCreateRunState(state, event.runId, event.expertKey);
1916
+ if (event.type === "startRun") {
1917
+ const startRunEvent = event;
1918
+ const userMessage = startRunEvent.inputMessages.find((m) => m.type === "userMessage");
1919
+ const queryText = userMessage?.contents?.find((c) => c.type === "textPart")?.text;
1920
+ if (!runState.delegatedBy) {
1921
+ const delegatedByInfo = startRunEvent.initialCheckpoint?.delegatedBy;
1922
+ if (delegatedByInfo) {
1923
+ runState.delegatedBy = {
1924
+ expertKey: delegatedByInfo.expert.key,
1925
+ runId: delegatedByInfo.runId
1926
+ };
1927
+ }
1928
+ }
1929
+ const isDelegationReturn = startRunEvent.initialCheckpoint?.status === "stoppedByDelegate" || startRunEvent.initialCheckpoint?.status === "stoppedByInteractiveTool";
1930
+ if (queryText && !runState.queryLogged && !isDelegationReturn) {
1931
+ const activityId = `query-${event.runId}`;
1932
+ addActivity({
1933
+ type: "query",
1934
+ id: activityId,
1935
+ expertKey: event.expertKey,
1936
+ runId: event.runId,
1937
+ previousActivityId: runState.lastActivityId,
1938
+ delegatedBy: runState.delegatedBy,
1939
+ text: queryText
1940
+ });
1941
+ runState.lastActivityId = activityId;
1942
+ runState.queryLogged = true;
1943
+ }
1944
+ return;
1945
+ }
1946
+ if (event.type === "resumeFromStop") {
1947
+ if (!runState.delegatedBy) {
1948
+ const resumeEvent = event;
1949
+ const delegatedByInfo = resumeEvent.checkpoint?.delegatedBy;
1950
+ if (delegatedByInfo) {
1951
+ runState.delegatedBy = {
1952
+ expertKey: delegatedByInfo.expert.key,
1953
+ runId: delegatedByInfo.runId
1954
+ };
1955
+ }
1956
+ }
1957
+ return;
1958
+ }
1959
+ if (event.type === "retry") {
1960
+ const retryEvent = event;
1961
+ const activityId = `retry-${event.id}`;
1962
+ addActivity({
1963
+ type: "retry",
1964
+ id: activityId,
1965
+ expertKey: event.expertKey,
1966
+ runId: event.runId,
1967
+ previousActivityId: runState.lastActivityId,
1968
+ delegatedBy: runState.delegatedBy,
1969
+ reasoning: runState.completedReasoning,
1970
+ error: retryEvent.reason,
1971
+ message: ""
1972
+ });
1973
+ runState.lastActivityId = activityId;
1974
+ runState.completedReasoning = void 0;
1975
+ return;
1976
+ }
1977
+ if (event.type === "completeRun") {
1978
+ if (!runState.completionLogged) {
1979
+ const text = event.text ?? "";
1980
+ const activityId = `completion-${event.runId}`;
1981
+ addActivity({
1982
+ type: "complete",
1983
+ id: activityId,
1984
+ expertKey: event.expertKey,
1985
+ runId: event.runId,
1986
+ previousActivityId: runState.lastActivityId,
1987
+ delegatedBy: runState.delegatedBy,
1988
+ reasoning: runState.completedReasoning,
1989
+ text
1990
+ });
1991
+ runState.lastActivityId = activityId;
1992
+ runState.completionLogged = true;
1993
+ runState.isComplete = true;
1994
+ runState.completedReasoning = void 0;
1995
+ }
1996
+ return;
1997
+ }
1998
+ if (event.type === "stopRunByError") {
1999
+ const errorEvent = event;
2000
+ const activityId = `error-${event.id}`;
2001
+ addActivity({
2002
+ type: "error",
2003
+ id: activityId,
2004
+ expertKey: event.expertKey,
2005
+ runId: event.runId,
2006
+ previousActivityId: runState.lastActivityId,
2007
+ delegatedBy: runState.delegatedBy,
2008
+ errorName: errorEvent.error.name,
2009
+ error: errorEvent.error.message,
2010
+ isRetryable: errorEvent.error.isRetryable
2011
+ });
2012
+ runState.lastActivityId = activityId;
2013
+ runState.isComplete = true;
2014
+ runState.completedReasoning = void 0;
2015
+ return;
2016
+ }
2017
+ if (isToolCallsEvent(event)) {
2018
+ for (const toolCall of event.toolCalls) {
2019
+ if (!state.tools.has(toolCall.id)) {
2020
+ state.tools.set(toolCall.id, { id: toolCall.id, toolCall, logged: false });
2021
+ }
2022
+ }
2023
+ }
2024
+ if (isStopRunByDelegateEvent(event)) {
2025
+ const reasoning = runState.completedReasoning;
2026
+ const delegations = event.checkpoint.delegateTo ?? [];
2027
+ runState.pendingDelegateCount += delegations.length;
2028
+ const delegateActivities = [];
2029
+ for (const delegation of delegations) {
2030
+ const existingTool = state.tools.get(delegation.toolCallId);
2031
+ if (!existingTool || !existingTool.logged) {
2032
+ if (existingTool) {
2033
+ existingTool.logged = true;
2034
+ }
2035
+ const activityId = `delegate-${delegation.toolCallId}`;
2036
+ delegateActivities.push({
2037
+ type: "delegate",
2038
+ id: activityId,
2039
+ expertKey: event.expertKey,
2040
+ runId: event.runId,
2041
+ previousActivityId: runState.lastActivityId,
2042
+ delegatedBy: runState.delegatedBy,
2043
+ delegateExpertKey: delegation.expert.key,
2044
+ query: delegation.query,
2045
+ reasoning
2046
+ });
2047
+ runState.lastActivityId = activityId;
2048
+ }
2049
+ }
2050
+ const wrapped = wrapInGroupIfParallel(delegateActivities, reasoning, {
2051
+ expertKey: event.expertKey,
2052
+ runId: event.runId,
2053
+ delegatedBy: runState.delegatedBy
2054
+ });
2055
+ for (const item of wrapped) {
2056
+ addActivity(item);
2057
+ }
2058
+ runState.completedReasoning = void 0;
2059
+ return;
2060
+ }
2061
+ if (isStopRunByInteractiveToolEvent(event)) {
2062
+ const reasoning = runState.completedReasoning;
2063
+ const pendingToolCalls = event.checkpoint.pendingToolCalls ?? [];
2064
+ const interactiveActivities = [];
2065
+ for (const toolCall of pendingToolCalls) {
2066
+ const existingTool = state.tools.get(toolCall.id);
2067
+ if (!existingTool || !existingTool.logged) {
2068
+ if (existingTool) {
2069
+ existingTool.logged = true;
2070
+ }
2071
+ const activityId = `interactive-${toolCall.id}`;
2072
+ interactiveActivities.push({
2073
+ type: "interactiveTool",
2074
+ id: activityId,
2075
+ expertKey: event.expertKey,
2076
+ runId: event.runId,
2077
+ previousActivityId: runState.lastActivityId,
2078
+ delegatedBy: runState.delegatedBy,
2079
+ skillName: toolCall.skillName,
2080
+ toolName: toolCall.toolName,
2081
+ args: toolCall.args,
2082
+ reasoning
2083
+ });
2084
+ runState.lastActivityId = activityId;
2085
+ }
2086
+ }
2087
+ const wrapped = wrapInGroupIfParallel(interactiveActivities, reasoning, {
2088
+ expertKey: event.expertKey,
2089
+ runId: event.runId,
2090
+ delegatedBy: runState.delegatedBy
2091
+ });
2092
+ for (const item of wrapped) {
2093
+ addActivity(item);
2094
+ }
2095
+ runState.completedReasoning = void 0;
2096
+ return;
2097
+ }
2098
+ if (isToolResultsEvent(event)) {
2099
+ const reasoning = runState.completedReasoning;
2100
+ const toolActivities = [];
2101
+ for (const toolResult of event.toolResults) {
2102
+ const tool = state.tools.get(toolResult.id);
2103
+ if (tool && !tool.logged) {
2104
+ const activityId = `action-${tool.id}`;
2105
+ const activity = toolToActivity(tool.toolCall, toolResult, reasoning, {
2106
+ id: activityId,
2107
+ expertKey: event.expertKey,
2108
+ runId: event.runId,
2109
+ previousActivityId: runState.lastActivityId,
2110
+ delegatedBy: runState.delegatedBy
2111
+ });
2112
+ toolActivities.push(activity);
2113
+ runState.lastActivityId = activityId;
2114
+ tool.logged = true;
2115
+ }
2116
+ }
2117
+ if (toolActivities.length > 0) {
2118
+ const wrapped = wrapInGroupIfParallel(toolActivities, reasoning, {
2119
+ expertKey: event.expertKey,
2120
+ runId: event.runId,
2121
+ delegatedBy: runState.delegatedBy
2122
+ });
2123
+ for (const item of wrapped) {
2124
+ addActivity(item);
2125
+ }
2126
+ runState.completedReasoning = void 0;
2127
+ }
2128
+ } else if (isToolResultEvent(event)) {
2129
+ const { toolResult } = event;
2130
+ const tool = state.tools.get(toolResult.id);
2131
+ if (tool && !tool.logged) {
2132
+ const activityId = `action-${tool.id}`;
2133
+ const activity = toolToActivity(tool.toolCall, toolResult, runState.completedReasoning, {
2134
+ id: activityId,
2135
+ expertKey: event.expertKey,
2136
+ runId: event.runId,
2137
+ previousActivityId: runState.lastActivityId,
2138
+ delegatedBy: runState.delegatedBy
2139
+ });
2140
+ addActivity(activity);
2141
+ runState.lastActivityId = activityId;
2142
+ tool.logged = true;
2143
+ runState.completedReasoning = void 0;
2144
+ }
2145
+ }
2146
+ }
2147
+
2148
+ // ../../packages/react/src/hooks/use-run.ts
2149
+ var STREAMING_EVENT_TYPES = /* @__PURE__ */ new Set([
2150
+ "startStreamingReasoning",
2151
+ "streamReasoning",
2152
+ "completeStreamingReasoning",
2153
+ "startStreamingRunResult",
2154
+ "streamRunResult",
2155
+ "completeStreamingRunResult"
2156
+ ]);
2157
+ var isStreamingEvent = (event) => "type" in event && "expertKey" in event && STREAMING_EVENT_TYPES.has(event.type);
2158
+ function processStreamingEvent(event, prevState) {
2159
+ const { runId, expertKey } = event;
2160
+ switch (event.type) {
2161
+ case "startStreamingReasoning": {
2162
+ return {
2163
+ newState: {
2164
+ ...prevState,
2165
+ runs: {
2166
+ ...prevState.runs,
2167
+ [runId]: {
2168
+ expertKey,
2169
+ reasoning: "",
2170
+ isReasoningActive: true
2171
+ }
2172
+ }
2173
+ },
2174
+ handled: true
2175
+ };
2176
+ }
2177
+ case "streamReasoning": {
2178
+ return {
2179
+ newState: {
2180
+ ...prevState,
2181
+ runs: {
2182
+ ...prevState.runs,
2183
+ [runId]: {
2184
+ ...prevState.runs[runId],
2185
+ expertKey,
2186
+ reasoning: (prevState.runs[runId]?.reasoning ?? "") + event.delta
2187
+ }
2188
+ }
2189
+ },
2190
+ handled: true
2191
+ };
2192
+ }
2193
+ case "completeStreamingReasoning": {
2194
+ return {
2195
+ newState: {
2196
+ ...prevState,
2197
+ runs: {
2198
+ ...prevState.runs,
2199
+ [runId]: {
2200
+ ...prevState.runs[runId],
2201
+ expertKey,
2202
+ isReasoningActive: false
2203
+ }
2204
+ }
2205
+ },
2206
+ handled: false
2207
+ };
2208
+ }
2209
+ case "startStreamingRunResult": {
2210
+ return {
2211
+ newState: {
2212
+ ...prevState,
2213
+ runs: {
2214
+ ...prevState.runs,
2215
+ [runId]: {
2216
+ expertKey,
2217
+ reasoning: void 0,
2218
+ isReasoningActive: false,
2219
+ runResult: "",
2220
+ isRunResultActive: true
2221
+ }
2222
+ }
2223
+ },
2224
+ handled: true
2225
+ };
2226
+ }
2227
+ case "streamRunResult": {
2228
+ return {
2229
+ newState: {
2230
+ ...prevState,
2231
+ runs: {
2232
+ ...prevState.runs,
2233
+ [runId]: {
2234
+ ...prevState.runs[runId],
2235
+ expertKey,
2236
+ runResult: (prevState.runs[runId]?.runResult ?? "") + event.delta
2237
+ }
2238
+ }
2239
+ },
2240
+ handled: true
2241
+ };
2242
+ }
2243
+ case "completeStreamingRunResult": {
2244
+ return {
2245
+ newState: {
2246
+ ...prevState,
2247
+ runs: {
2248
+ ...prevState.runs,
2249
+ [runId]: {
2250
+ ...prevState.runs[runId],
2251
+ expertKey,
2252
+ isRunResultActive: false
2253
+ }
2254
+ }
2255
+ },
2256
+ handled: true
2257
+ };
2258
+ }
2259
+ default:
2260
+ return { newState: prevState, handled: false };
2261
+ }
2262
+ }
2263
+ function useRun() {
2264
+ const [activities, setActivities] = useState([]);
2265
+ const [streaming, setStreaming] = useState({ runs: {} });
2266
+ const [eventCount, setEventCount] = useState(0);
2267
+ const [isComplete, setIsComplete] = useState(false);
2268
+ const stateRef = useRef(createInitialActivityProcessState());
2269
+ const clearStreaming = useCallback(() => {
2270
+ setStreaming({ runs: {} });
2271
+ }, []);
2272
+ const handleStreamingEvent = useCallback((event) => {
2273
+ let handled = false;
2274
+ setStreaming((prev) => {
2275
+ const result = processStreamingEvent(event, prev);
2276
+ handled = result.handled;
2277
+ return result.newState;
2278
+ });
2279
+ return handled;
2280
+ }, []);
2281
+ const processEvent = useCallback((event) => {
2282
+ const newActivities = [];
2283
+ const addActivity = (activity) => newActivities.push(activity);
2284
+ processRunEventToActivity(stateRef.current, event, addActivity);
2285
+ if (newActivities.length > 0) {
2286
+ setActivities((prev) => [...prev, ...newActivities]);
2287
+ }
2288
+ const rootRunComplete = Array.from(stateRef.current.runStates.values()).some(
2289
+ (rs) => rs.isComplete && !rs.delegatedBy
2290
+ );
2291
+ setIsComplete(rootRunComplete);
2292
+ }, []);
2293
+ const clearRunStreaming = useCallback((runId) => {
2294
+ setStreaming((prev) => {
2295
+ const { [runId]: _, ...rest } = prev.runs;
2296
+ return { runs: rest };
2297
+ });
2298
+ }, []);
2299
+ const addEvent = useCallback(
2300
+ (event) => {
2301
+ if (isStreamingEvent(event)) {
2302
+ const handled = handleStreamingEvent(event);
2303
+ if (handled) {
2304
+ setEventCount((prev) => prev + 1);
2305
+ return;
2306
+ }
2307
+ }
2308
+ if ("type" in event && "runId" in event && (event.type === "completeRun" || event.type === "stopRunByError")) {
2309
+ clearRunStreaming(event.runId);
2310
+ }
2311
+ processEvent(event);
2312
+ setEventCount((prev) => prev + 1);
2313
+ },
2314
+ [handleStreamingEvent, clearRunStreaming, processEvent]
2315
+ );
2316
+ const appendHistoricalEvents = useCallback((historicalEvents) => {
2317
+ const newActivities = [];
2318
+ const addActivity = (activity) => newActivities.push(activity);
2319
+ for (const event of historicalEvents) {
2320
+ processRunEventToActivity(stateRef.current, event, addActivity);
2321
+ }
2322
+ if (newActivities.length > 0) {
2323
+ setActivities((prev) => [...prev, ...newActivities]);
2324
+ }
2325
+ setEventCount((prev) => prev + historicalEvents.length);
2326
+ const rootRunComplete = Array.from(stateRef.current.runStates.values()).some(
2327
+ (rs) => rs.isComplete && !rs.delegatedBy
2328
+ );
2329
+ setIsComplete(rootRunComplete);
2330
+ }, []);
2331
+ return {
2332
+ activities,
2333
+ streaming,
2334
+ isComplete,
2335
+ eventCount,
2336
+ addEvent,
2337
+ appendHistoricalEvents,
2338
+ clearStreaming
2339
+ };
2340
+ }
2341
+ var useRuntimeInfo = (options) => {
2342
+ const [runtimeInfo, setRuntimeInfo] = useState({
2343
+ status: "initializing",
2344
+ runtimeVersion: options.initialConfig.runtimeVersion,
2345
+ expertName: options.initialExpertName,
2346
+ model: options.initialConfig.model,
2347
+ maxSteps: options.initialConfig.maxSteps,
2348
+ maxRetries: options.initialConfig.maxRetries,
2349
+ timeout: options.initialConfig.timeout,
2350
+ activeSkills: [],
2351
+ contextWindowUsage: options.initialConfig.contextWindowUsage
2352
+ });
2353
+ const handleEvent = useCallback((event) => {
2354
+ if (event.type === "initializeRuntime") {
2355
+ setRuntimeInfo((prev) => ({
2356
+ runtimeVersion: event.runtimeVersion,
2357
+ expertName: event.expertName,
2358
+ model: event.model,
2359
+ maxSteps: event.maxSteps,
2360
+ maxRetries: event.maxRetries,
2361
+ timeout: event.timeout,
2362
+ currentStep: 1,
2363
+ status: "running",
2364
+ query: event.query,
2365
+ statusChangedAt: Date.now(),
2366
+ activeSkills: [],
2367
+ contextWindowUsage: prev.contextWindowUsage
2368
+ }));
2369
+ return { initialized: true };
2370
+ }
2371
+ if (event.type === "skillConnected") {
2372
+ setRuntimeInfo((prev) => ({
2373
+ ...prev,
2374
+ activeSkills: [...prev.activeSkills, event.skillName]
2375
+ }));
2376
+ return null;
2377
+ }
2378
+ if (event.type === "skillDisconnected") {
2379
+ setRuntimeInfo((prev) => ({
2380
+ ...prev,
2381
+ activeSkills: prev.activeSkills.filter((s) => s !== event.skillName)
2382
+ }));
2383
+ return null;
2384
+ }
2385
+ if ("stepNumber" in event) {
2386
+ const isComplete = event.type === "completeRun";
2387
+ const isStopped = STOP_EVENT_TYPES.includes(event.type);
2388
+ const checkpoint = "nextCheckpoint" in event ? event.nextCheckpoint : "checkpoint" in event ? event.checkpoint : void 0;
2389
+ setRuntimeInfo((prev) => ({
2390
+ ...prev,
2391
+ currentStep: event.stepNumber,
2392
+ status: isComplete ? "completed" : isStopped ? "stopped" : "running",
2393
+ ...isComplete || isStopped ? { statusChangedAt: Date.now() } : {},
2394
+ ...checkpoint?.contextWindowUsage !== void 0 ? { contextWindowUsage: checkpoint.contextWindowUsage } : {}
2395
+ }));
2396
+ if (isComplete) return { completed: true };
2397
+ if (isStopped) return { stopped: true };
2398
+ }
2399
+ return null;
2400
+ }, []);
2401
+ const setExpertName = useCallback((expertName) => {
2402
+ setRuntimeInfo((prev) => ({ ...prev, expertName }));
2403
+ }, []);
2404
+ const setQuery = useCallback((query) => {
2405
+ setRuntimeInfo((prev) => ({ ...prev, query }));
2406
+ }, []);
2407
+ const setCurrentStep = useCallback((step) => {
2408
+ setRuntimeInfo((prev) => ({ ...prev, currentStep: step }));
2409
+ }, []);
2410
+ const setContextWindowUsage = useCallback((contextWindowUsage) => {
2411
+ setRuntimeInfo((prev) => ({ ...prev, contextWindowUsage }));
2412
+ }, []);
2413
+ return {
2414
+ runtimeInfo,
2415
+ handleEvent,
2416
+ setExpertName,
2417
+ setQuery,
2418
+ setCurrentStep,
2419
+ setContextWindowUsage
2420
+ };
2421
+ };
2422
+ var useLatestRef = (value) => {
2423
+ const ref = useRef(value);
2424
+ useInsertionEffect(() => {
2425
+ ref.current = value;
2426
+ });
2427
+ return ref;
2428
+ };
2429
+
2430
+ // ../../packages/tui-components/src/hooks/use-text-input.ts
2431
+ var useTextInput = (options) => {
2432
+ const [input, setInput] = useState("");
2433
+ const inputRef = useLatestRef(input);
2434
+ const onSubmitRef = useLatestRef(options.onSubmit);
2435
+ const onCancelRef = useLatestRef(options.onCancel);
2436
+ const handleInput = useCallback(
2437
+ (inputChar, key) => {
2438
+ if (key.escape) {
2439
+ setInput("");
2440
+ onCancelRef.current?.();
2441
+ return;
2442
+ }
2443
+ if (key.return && inputRef.current.trim()) {
2444
+ onSubmitRef.current(inputRef.current.trim());
2445
+ setInput("");
2446
+ return;
2447
+ }
2448
+ if (key.backspace || key.delete) {
2449
+ setInput((prev) => prev.slice(0, -1));
2450
+ return;
2451
+ }
2452
+ if (!key.ctrl && !key.meta && inputChar) {
2453
+ setInput((prev) => prev + inputChar);
2454
+ }
2455
+ },
2456
+ [inputRef, onSubmitRef, onCancelRef]
2457
+ );
2458
+ const reset = useCallback(() => {
2459
+ setInput("");
2460
+ }, []);
2461
+ return { input, handleInput, reset };
2462
+ };
2463
+
2464
+ // ../../packages/tui-components/src/hooks/ui/use-expert-selector.ts
2465
+ var useExpertSelector = (options) => {
2466
+ const { experts, onExpertSelect, extraKeyHandler } = options;
2467
+ const [inputMode, setInputMode] = useState(false);
2468
+ const { selectedIndex, handleNavigation } = useListNavigation({
2469
+ items: experts,
2470
+ onSelect: (expert) => onExpertSelect(expert.key)
2471
+ });
2472
+ const { input, handleInput, reset } = useTextInput({
2473
+ onSubmit: (value) => {
2474
+ onExpertSelect(value);
2475
+ setInputMode(false);
2476
+ },
2477
+ onCancel: () => setInputMode(false)
2478
+ });
2479
+ const handleKeyInput = useCallback(
2480
+ (inputChar, key) => {
2481
+ if (inputMode) {
2482
+ handleInput(inputChar, key);
2483
+ return;
2484
+ }
2485
+ if (handleNavigation(inputChar, key)) return;
2486
+ if (extraKeyHandler?.(inputChar, key)) return;
2487
+ if (inputChar === "i") {
2488
+ reset();
2489
+ setInputMode(true);
2490
+ }
2491
+ },
2492
+ [inputMode, handleInput, handleNavigation, extraKeyHandler, reset]
2493
+ );
2494
+ return {
2495
+ inputMode,
2496
+ input,
2497
+ selectedIndex,
2498
+ handleKeyInput
2499
+ };
2500
+ };
2501
+ var ExpertList = ({
2502
+ experts,
2503
+ selectedIndex,
2504
+ showSource = false,
2505
+ inline = false,
2506
+ maxItems
2507
+ }) => {
2508
+ const displayExperts = maxItems ? experts.slice(0, maxItems) : experts;
2509
+ if (displayExperts.length === 0) {
2510
+ return /* @__PURE__ */ jsx(Text, { color: "gray", children: "No experts found." });
2511
+ }
2512
+ const items = displayExperts.map((expert, index) => /* @__PURE__ */ jsxs(Text, { color: index === selectedIndex ? "cyan" : "gray", children: [
2513
+ index === selectedIndex ? ">" : " ",
2514
+ " ",
2515
+ showSource ? expert.key : expert.name,
2516
+ showSource && expert.source && /* @__PURE__ */ jsxs(Fragment, { children: [
2517
+ " ",
2518
+ /* @__PURE__ */ jsxs(Text, { color: expert.source === "configured" ? "green" : "yellow", children: [
2519
+ "[",
2520
+ expert.source === "configured" ? "config" : "recent",
2521
+ "]"
2522
+ ] })
2523
+ ] }),
2524
+ inline ? " " : ""
2525
+ ] }, expert.key));
2526
+ return inline ? /* @__PURE__ */ jsx(Box, { children: items }) : /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: items });
2527
+ };
2528
+ var ExpertSelectorBase = ({
2529
+ experts,
2530
+ hint,
2531
+ onExpertSelect,
2532
+ showSource = false,
2533
+ inline = false,
2534
+ maxItems = UI_CONSTANTS.MAX_VISIBLE_LIST_ITEMS,
2535
+ extraKeyHandler
2536
+ }) => {
2537
+ const { inputMode, input, selectedIndex, handleKeyInput } = useExpertSelector({
2538
+ experts,
2539
+ onExpertSelect,
2540
+ extraKeyHandler
2541
+ });
2542
+ useInput(handleKeyInput);
2543
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: inline ? "row" : "column", children: [
2544
+ !inputMode && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2545
+ /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { color: "cyan", children: hint }) }),
2546
+ /* @__PURE__ */ jsx(
2547
+ ExpertList,
2548
+ {
2549
+ experts,
2550
+ selectedIndex,
2551
+ showSource,
2552
+ inline,
2553
+ maxItems
2554
+ }
2555
+ )
2556
+ ] }),
2557
+ inputMode && /* @__PURE__ */ jsxs(Box, { children: [
2558
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Expert: " }),
2559
+ /* @__PURE__ */ jsx(Text, { color: "white", children: input }),
2560
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: "_" })
2561
+ ] })
2562
+ ] });
2563
+ };
2564
+ var BrowsingExpertsInput = ({
2565
+ experts,
2566
+ onExpertSelect,
2567
+ onSwitchToHistory
2568
+ }) => {
2569
+ const extraKeyHandler = useCallback(
2570
+ (inputChar) => {
2571
+ if (inputChar === "h") {
2572
+ onSwitchToHistory();
2573
+ return true;
2574
+ }
2575
+ return false;
2576
+ },
2577
+ [onSwitchToHistory]
2578
+ );
2579
+ const hint = `Experts ${KEY_HINTS.NAVIGATE} ${KEY_HINTS.SELECT} ${KEY_HINTS.INPUT} ${KEY_HINTS.HISTORY} ${KEY_HINTS.CANCEL}`;
2580
+ return /* @__PURE__ */ jsx(
2581
+ ExpertSelectorBase,
2582
+ {
2583
+ experts,
2584
+ hint,
2585
+ onExpertSelect,
2586
+ showSource: true,
2587
+ maxItems: UI_CONSTANTS.MAX_VISIBLE_LIST_ITEMS,
2588
+ extraKeyHandler
2589
+ }
2590
+ );
2591
+ };
2592
+ var BrowsingHistoryInput = ({
2593
+ jobs,
2594
+ onJobSelect,
2595
+ onJobResume,
2596
+ onSwitchToExperts
2597
+ }) => /* @__PURE__ */ jsx(
2598
+ ListBrowser,
2599
+ {
2600
+ title: `Job History ${KEY_HINTS.NAVIGATE} ${KEY_HINTS.RESUME} ${KEY_HINTS.CHECKPOINTS} ${KEY_HINTS.NEW}`,
2601
+ items: jobs,
2602
+ onSelect: onJobResume,
2603
+ emptyMessage: "No jobs found. Press n to start a new job.",
2604
+ extraKeyHandler: (char, _key, selected) => {
2605
+ if (char === "c" && selected) {
2606
+ onJobSelect(selected);
2607
+ return true;
2608
+ }
2609
+ if (char === "n") {
2610
+ onSwitchToExperts();
2611
+ return true;
2612
+ }
2613
+ return false;
2614
+ },
2615
+ renderItem: (job, isSelected) => /* @__PURE__ */ jsxs(Text, { color: isSelected ? "cyan" : "gray", children: [
2616
+ isSelected ? ">" : " ",
2617
+ " ",
2618
+ job.expertKey,
2619
+ " - ",
2620
+ job.totalSteps,
2621
+ " steps (",
2622
+ job.jobId,
2623
+ ") (",
2624
+ formatTimestamp2(job.startedAt),
2625
+ ")"
2626
+ ] }, job.jobId)
2627
+ }
2628
+ );
2629
+ var BrowserRouter = ({ inputState, showEventsHint = true }) => {
2630
+ const ctx = useInputAreaContext();
2631
+ const handleEventSelect = useCallback(
2632
+ (event) => {
2633
+ if (inputState.type === "browsingEvents") {
2634
+ ctx.onEventSelect(inputState, event);
2635
+ }
2636
+ },
2637
+ [ctx.onEventSelect, inputState]
2638
+ );
2639
+ switch (inputState.type) {
2640
+ case "browsingHistory":
2641
+ return /* @__PURE__ */ jsx(
2642
+ BrowsingHistoryInput,
2643
+ {
2644
+ jobs: inputState.jobs,
2645
+ onJobSelect: ctx.onJobSelect,
2646
+ onJobResume: ctx.onJobResume,
2647
+ onSwitchToExperts: ctx.onSwitchToExperts
2648
+ }
2649
+ );
2650
+ case "browsingExperts":
2651
+ return /* @__PURE__ */ jsx(
2652
+ BrowsingExpertsInput,
2653
+ {
2654
+ experts: inputState.experts,
2655
+ onExpertSelect: ctx.onExpertSelect,
2656
+ onSwitchToHistory: ctx.onSwitchToHistory
2657
+ }
2658
+ );
2659
+ case "browsingCheckpoints":
2660
+ return /* @__PURE__ */ jsx(
2661
+ BrowsingCheckpointsInput,
2662
+ {
2663
+ job: inputState.job,
2664
+ checkpoints: inputState.checkpoints,
2665
+ onCheckpointSelect: ctx.onCheckpointSelect,
2666
+ onCheckpointResume: ctx.onCheckpointResume,
2667
+ onBack: ctx.onBack,
2668
+ showEventsHint
2669
+ }
2670
+ );
2671
+ case "browsingEvents":
2672
+ return /* @__PURE__ */ jsx(
2673
+ BrowsingEventsInput,
2674
+ {
2675
+ checkpoint: inputState.checkpoint,
2676
+ events: inputState.events,
2677
+ onEventSelect: handleEventSelect,
2678
+ onBack: ctx.onBack
2679
+ }
2680
+ );
2681
+ case "browsingEventDetail":
2682
+ return /* @__PURE__ */ jsx(BrowsingEventDetailInput, { event: inputState.selectedEvent, onBack: ctx.onBack });
2683
+ default:
2684
+ return assertNever(inputState);
2685
+ }
2686
+ };
2687
+ var ActionRowSimple = ({
2688
+ indicatorColor,
2689
+ text,
2690
+ textDimColor = false
2691
+ }) => /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
2692
+ /* @__PURE__ */ jsx(Box, { paddingRight: 1, children: /* @__PURE__ */ jsx(Text, { color: indicatorColor, children: INDICATOR.BULLET }) }),
2693
+ /* @__PURE__ */ jsx(Text, { color: "white", dimColor: textDimColor, children: text })
2694
+ ] }) });
2695
+ var ActionRow = ({ indicatorColor, label, summary, children }) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2696
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
2697
+ /* @__PURE__ */ jsx(Text, { color: indicatorColor, children: INDICATOR.BULLET }),
2698
+ /* @__PURE__ */ jsx(Text, { color: "white", children: label }),
2699
+ summary && /* @__PURE__ */ jsx(Text, { color: "white", dimColor: true, children: summary })
2700
+ ] }),
2701
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
2702
+ /* @__PURE__ */ jsx(Box, { paddingRight: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: INDICATOR.TREE }) }),
2703
+ children
2704
+ ] })
2705
+ ] });
2706
+ var CheckpointActionRow = ({ action }) => {
2707
+ if (action.type === "parallelGroup") {
2708
+ return renderParallelGroup(action);
2709
+ }
2710
+ const actionElement = renderAction(action);
2711
+ if (!actionElement) return null;
2712
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2713
+ action.reasoning && renderReasoning(action.reasoning),
2714
+ actionElement
2715
+ ] });
2716
+ };
2717
+ function renderParallelGroup(group) {
2718
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2719
+ group.reasoning && renderReasoning(group.reasoning),
2720
+ group.activities.map((activity, index) => {
2721
+ const actionElement = renderAction(activity);
2722
+ if (!actionElement) return null;
2723
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: actionElement }, activity.id || `${activity.type}-${index}`);
2724
+ })
2725
+ ] });
2726
+ }
2727
+ function renderReasoning(text) {
2728
+ const lines = text.split("\n");
2729
+ 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}`)) }) });
2730
+ }
2731
+ function renderAction(action) {
2732
+ const color = action.type === "error" || "error" in action && action.error ? "red" : "green";
2733
+ switch (action.type) {
2734
+ case "query":
2735
+ return renderQuery(action.text, action.runId);
2736
+ case "retry":
2737
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "yellow", label: "Retry", children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: action.message || action.error }) });
2738
+ case "complete":
2739
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "green", label: "Run Results", children: /* @__PURE__ */ jsx(Text, { children: action.text }) });
2740
+ case "attemptCompletion": {
2741
+ if (action.error) {
2742
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "red", label: "Completion Failed", children: /* @__PURE__ */ jsx(Text, { color: "red", children: action.error }) });
2743
+ }
2744
+ const remaining = action.remainingTodos?.filter((t) => !t.completed) ?? [];
2745
+ if (remaining.length > 0) {
2746
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "yellow", label: "Completion Blocked", children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2747
+ remaining.length,
2748
+ " remaining task",
2749
+ remaining.length > 1 ? "s" : ""
2750
+ ] }) });
2751
+ }
2752
+ return /* @__PURE__ */ jsx(ActionRowSimple, { indicatorColor: "green", text: "Completion Accepted" });
2753
+ }
2754
+ case "todo":
2755
+ return renderTodo(action, color);
2756
+ case "clearTodo":
2757
+ return /* @__PURE__ */ jsx(ActionRowSimple, { indicatorColor: color, text: "Todo Cleared" });
2758
+ case "readTextFile":
2759
+ return renderReadTextFile(action, color);
2760
+ case "writeTextFile":
2761
+ return renderWriteTextFile(action, color);
2762
+ case "editTextFile":
2763
+ return renderEditTextFile(action, color);
2764
+ case "appendTextFile":
2765
+ return renderAppendTextFile(action, color);
2766
+ case "deleteFile":
2767
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Delete", summary: shortenPath(action.path), children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Removed" }) });
2768
+ case "deleteDirectory":
2769
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Delete Dir", summary: shortenPath(action.path), children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2770
+ "Removed",
2771
+ action.recursive ? " (recursive)" : ""
2772
+ ] }) });
2773
+ case "moveFile":
2774
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Move", summary: shortenPath(action.source), children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2775
+ "\u2192 ",
2776
+ shortenPath(action.destination, 30)
2777
+ ] }) });
2778
+ case "createDirectory":
2779
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Mkdir", summary: shortenPath(action.path), children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Created" }) });
2780
+ case "listDirectory":
2781
+ return renderListDirectory(action, color);
2782
+ case "getFileInfo":
2783
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Info", summary: shortenPath(action.path), children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Fetched" }) });
2784
+ case "readPdfFile":
2785
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "PDF", summary: shortenPath(action.path), children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Read" }) });
2786
+ case "readImageFile":
2787
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Image", summary: shortenPath(action.path), children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Read" }) });
2788
+ case "exec":
2789
+ return renderExec(action, color);
2790
+ case "delegate":
2791
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "yellow", label: action.delegateExpertKey, children: /* @__PURE__ */ jsx(
2792
+ Text,
2793
+ {
2794
+ dimColor: true,
2795
+ children: `{"query":"${truncateText(action.query, UI_CONSTANTS.TRUNCATE_TEXT_MEDIUM)}"}`
2796
+ }
2797
+ ) });
2798
+ case "delegationComplete":
2799
+ return /* @__PURE__ */ jsx(
2800
+ ActionRowSimple,
2801
+ {
2802
+ indicatorColor: "green",
2803
+ text: `Delegation Complete (${action.count} delegate${action.count > 1 ? "s" : ""} returned)`
2804
+ }
2805
+ );
2806
+ case "interactiveTool":
2807
+ 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) }) });
2808
+ case "generalTool":
2809
+ 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) }) });
2810
+ case "error":
2811
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: "red", label: action.errorName ?? "Error", children: /* @__PURE__ */ jsx(Text, { color: "red", children: action.error ?? "Unknown error" }) });
2812
+ default: {
2813
+ return null;
2814
+ }
2815
+ }
2816
+ }
2817
+ function renderTodo(action, color) {
2818
+ const { newTodos, completedTodos, todos } = action;
2819
+ const hasNewTodos = newTodos && newTodos.length > 0;
2820
+ const hasCompletedTodos = completedTodos && completedTodos.length > 0;
2821
+ if (!hasNewTodos && !hasCompletedTodos) {
2822
+ return /* @__PURE__ */ jsx(ActionRowSimple, { indicatorColor: color, text: "Todo No changes" });
2823
+ }
2824
+ const labelParts = [];
2825
+ if (hasNewTodos) {
2826
+ labelParts.push(`Added ${newTodos.length} task${newTodos.length > 1 ? "s" : ""}`);
2827
+ }
2828
+ if (hasCompletedTodos) {
2829
+ labelParts.push(
2830
+ `Completed ${completedTodos.length} task${completedTodos.length > 1 ? "s" : ""}`
2831
+ );
2832
+ }
2833
+ const label = `Todo ${labelParts.join(", ")}`;
2834
+ const completedTitles = hasCompletedTodos ? completedTodos.map((id) => todos.find((t) => t.id === id)?.title).filter((t) => t !== void 0) : [];
2835
+ if (!hasNewTodos && completedTitles.length === 0) {
2836
+ return /* @__PURE__ */ jsx(ActionRowSimple, { indicatorColor: color, text: label });
2837
+ }
2838
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label, children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2839
+ hasNewTodos && newTodos.slice(0, RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW).map((todo, idx) => /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2840
+ "\u25CB ",
2841
+ todo
2842
+ ] }, `todo-${idx}`)),
2843
+ hasNewTodos && newTodos.length > RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2844
+ "... +",
2845
+ newTodos.length - RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW,
2846
+ " more"
2847
+ ] }),
2848
+ completedTitles.slice(0, RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW).map((title, idx) => /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2849
+ "\u2713 ",
2850
+ title
2851
+ ] }, `completed-${idx}`)),
2852
+ completedTitles.length > RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2853
+ "... +",
2854
+ completedTitles.length - RENDER_CONSTANTS.NEW_TODO_MAX_PREVIEW,
2855
+ " more"
2856
+ ] })
2857
+ ] }) });
2858
+ }
2859
+ function renderReadTextFile(action, color) {
2860
+ const { path: path8, content, from, to } = action;
2861
+ const lineRange = from !== void 0 && to !== void 0 ? `#${from}-${to}` : "";
2862
+ const lines = content?.split("\n") ?? [];
2863
+ return /* @__PURE__ */ jsx(
2864
+ ActionRow,
2865
+ {
2866
+ indicatorColor: color,
2867
+ label: "Read Text File",
2868
+ summary: `${shortenPath(path8)}${lineRange}`,
2869
+ 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}`)) })
2870
+ }
2871
+ );
2872
+ }
2873
+ function renderWriteTextFile(action, color) {
2874
+ const { path: path8, text } = action;
2875
+ const lines = text.split("\n");
2876
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Write Text File", summary: shortenPath(path8), children: /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: lines.map((line, idx) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
2877
+ /* @__PURE__ */ jsx(Text, { color: "green", dimColor: true, children: "+" }),
2878
+ /* @__PURE__ */ jsx(Text, { color: "white", dimColor: true, children: line })
2879
+ ] }, `write-${idx}`)) }) });
2880
+ }
2881
+ function renderEditTextFile(action, color) {
2882
+ const { path: path8, oldText, newText } = action;
2883
+ const oldLines = oldText.split("\n");
2884
+ const newLines = newText.split("\n");
2885
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Edit Text File", summary: shortenPath(path8), children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2886
+ oldLines.map((line, idx) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
2887
+ /* @__PURE__ */ jsx(Text, { color: "red", dimColor: true, children: "-" }),
2888
+ /* @__PURE__ */ jsx(Text, { color: "white", dimColor: true, children: line })
2889
+ ] }, `old-${idx}`)),
2890
+ newLines.map((line, idx) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
2891
+ /* @__PURE__ */ jsx(Text, { color: "green", dimColor: true, children: "+" }),
2892
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: line })
2893
+ ] }, `new-${idx}`))
2894
+ ] }) });
2895
+ }
2896
+ function renderAppendTextFile(action, color) {
2897
+ const { path: path8, text } = action;
2898
+ const lines = text.split("\n");
2899
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "Append Text File", summary: shortenPath(path8), children: /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: lines.map((line, idx) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
2900
+ /* @__PURE__ */ jsx(Text, { color: "green", dimColor: true, children: "+" }),
2901
+ /* @__PURE__ */ jsx(Text, { color: "white", dimColor: true, children: line })
2902
+ ] }, `append-${idx}`)) }) });
2903
+ }
2904
+ function renderListDirectory(action, color) {
2905
+ const { path: path8, items } = action;
2906
+ const itemLines = items?.map((item) => `${item.type === "directory" ? "\u{1F4C1}" : "\u{1F4C4}"} ${item.name}`) ?? [];
2907
+ const { visible, remaining } = summarizeOutput(itemLines, RENDER_CONSTANTS.LIST_DIR_MAX_ITEMS);
2908
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: "List", summary: shortenPath(path8), children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2909
+ visible.map((line, idx) => /* @__PURE__ */ jsx(Text, { dimColor: true, children: truncateText(line, UI_CONSTANTS.TRUNCATE_TEXT_DEFAULT) }, `dir-${idx}`)),
2910
+ remaining > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2911
+ "... +",
2912
+ remaining,
2913
+ " more"
2914
+ ] })
2915
+ ] }) });
2916
+ }
2917
+ function renderExec(action, color) {
2918
+ const { command, args, cwd, output } = action;
2919
+ const cwdPart = cwd ? ` ${shortenPath(cwd, 40)}` : "";
2920
+ const cmdLine = truncateText(
2921
+ `${command} ${args.join(" ")}${cwdPart}`,
2922
+ UI_CONSTANTS.TRUNCATE_TEXT_DEFAULT
2923
+ );
2924
+ const outputLines = output?.split("\n") ?? [];
2925
+ const { visible, remaining } = summarizeOutput(
2926
+ outputLines,
2927
+ RENDER_CONSTANTS.EXEC_OUTPUT_MAX_LINES
2928
+ );
2929
+ return /* @__PURE__ */ jsx(ActionRow, { indicatorColor: color, label: `Bash ${cmdLine}`, children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2930
+ visible.map((line, idx) => /* @__PURE__ */ jsx(Text, { dimColor: true, children: truncateText(line, UI_CONSTANTS.TRUNCATE_TEXT_DEFAULT) }, `out-${idx}`)),
2931
+ remaining > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2932
+ "... +",
2933
+ remaining,
2934
+ " more"
2935
+ ] })
2936
+ ] }) });
2937
+ }
2938
+ function renderQuery(text, runId) {
2939
+ const lines = text.split("\n");
2940
+ const shortRunId = runId.slice(0, 8);
2941
+ 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}`)) }) });
2942
+ }
2943
+ var RunSetting = ({
2944
+ info,
2945
+ eventCount,
2946
+ isEditing,
2947
+ expertName,
2948
+ onQuerySubmit
2949
+ }) => {
2950
+ const { input, handleInput } = useTextInput({
2951
+ onSubmit: onQuerySubmit ?? (() => {
2952
+ })
2953
+ });
2954
+ useInput(handleInput, { isActive: isEditing });
2955
+ const displayExpertName = expertName ?? info.expertName;
2956
+ const skills = info.activeSkills.length > 0 ? info.activeSkills.join(", ") : "";
2957
+ const step = info.currentStep !== void 0 ? String(info.currentStep) : "";
2958
+ const usagePercent = (info.contextWindowUsage * 100).toFixed(1);
2959
+ return /* @__PURE__ */ jsxs(
2960
+ Box,
2961
+ {
2962
+ flexDirection: "column",
2963
+ borderStyle: "single",
2964
+ borderColor: "gray",
2965
+ borderTop: true,
2966
+ borderBottom: false,
2967
+ borderLeft: false,
2968
+ borderRight: false,
2969
+ children: [
2970
+ /* @__PURE__ */ jsxs(Text, { children: [
2971
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "Perstack" }),
2972
+ info.runtimeVersion && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
2973
+ " (v",
2974
+ info.runtimeVersion,
2975
+ ")"
2976
+ ] })
2977
+ ] }),
2978
+ /* @__PURE__ */ jsxs(Text, { children: [
2979
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Expert: " }),
2980
+ /* @__PURE__ */ jsx(Text, { color: "white", children: displayExpertName }),
2981
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: " / Skills: " }),
2982
+ /* @__PURE__ */ jsx(Text, { color: "green", children: skills })
2983
+ ] }),
2984
+ /* @__PURE__ */ jsxs(Text, { children: [
2985
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Status: " }),
2986
+ /* @__PURE__ */ jsx(
2987
+ Text,
2988
+ {
2989
+ color: info.status === "running" ? "green" : info.status === "completed" ? "cyan" : "yellow",
2990
+ children: info.status
2991
+ }
2992
+ ),
2993
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: " / Step: " }),
2994
+ /* @__PURE__ */ jsx(Text, { color: "white", children: step }),
2995
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: " / Events: " }),
2996
+ /* @__PURE__ */ jsx(Text, { color: "white", children: eventCount }),
2997
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: " / Usage: " }),
2998
+ /* @__PURE__ */ jsxs(Text, { color: "white", children: [
2999
+ usagePercent,
3000
+ "%"
3001
+ ] })
3002
+ ] }),
3003
+ /* @__PURE__ */ jsxs(Text, { children: [
3004
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Model: " }),
3005
+ /* @__PURE__ */ jsx(Text, { color: "white", children: info.model })
3006
+ ] }),
3007
+ /* @__PURE__ */ jsxs(Box, { children: [
3008
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Query: " }),
3009
+ isEditing ? /* @__PURE__ */ jsxs(Fragment, { children: [
3010
+ /* @__PURE__ */ jsx(Text, { color: "white", children: input }),
3011
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: "_" })
3012
+ ] }) : /* @__PURE__ */ jsx(Text, { color: "white", children: info.query })
3013
+ ] })
3014
+ ]
3015
+ }
3016
+ );
3017
+ };
3018
+ var StreamingDisplay = ({ streaming }) => {
3019
+ const activeRuns = Object.entries(streaming.runs).filter(
3020
+ ([, run]) => run.isReasoningActive || run.isRunResultActive
3021
+ );
3022
+ if (activeRuns.length === 0) return null;
3023
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginY: 1, children: activeRuns.map(([runId, run]) => /* @__PURE__ */ jsx(StreamingRunSection, { run }, runId)) });
3024
+ };
3025
+ function StreamingRunSection({ run }) {
3026
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3027
+ run.isReasoningActive && run.reasoning !== void 0 && /* @__PURE__ */ jsx(StreamingReasoning, { expertKey: run.expertKey, text: run.reasoning }),
3028
+ run.isRunResultActive && run.runResult !== void 0 && /* @__PURE__ */ jsx(StreamingRunResult, { expertKey: run.expertKey, text: run.runResult })
3029
+ ] });
3030
+ }
3031
+ function StreamingReasoning({
3032
+ expertKey,
3033
+ text
3034
+ }) {
3035
+ const lines = text.split("\n");
3036
+ const label = `[${formatExpertKey(expertKey)}] Reasoning...`;
3037
+ 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}`)) }) });
3038
+ }
3039
+ function StreamingRunResult({
3040
+ expertKey,
3041
+ text
3042
+ }) {
3043
+ const lines = text.split("\n");
3044
+ const label = `[${formatExpertKey(expertKey)}] Generating...`;
3045
+ 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}`)) }) });
3046
+ }
3047
+ function formatExpertKey(expertKey) {
3048
+ const atIndex = expertKey.lastIndexOf("@");
3049
+ if (atIndex > 0) {
3050
+ return expertKey.substring(0, atIndex);
3051
+ }
3052
+ return expertKey;
3053
+ }
3054
+ function getActivityKey(activityOrGroup, index) {
3055
+ return activityOrGroup.id || `activity-${index}`;
3056
+ }
3057
+ var RunBox = ({ node, isRoot }) => {
3058
+ if (isRoot) {
3059
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3060
+ node.activities.map((activity, index) => /* @__PURE__ */ jsx(CheckpointActionRow, { action: activity }, getActivityKey(activity, index))),
3061
+ node.children.map((child) => /* @__PURE__ */ jsx(RunBox, { node: child, isRoot: false }, child.runId))
3062
+ ] });
3063
+ }
3064
+ const shortRunId = node.runId.slice(0, 8);
3065
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", marginLeft: 1, children: [
3066
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, bold: true, children: [
3067
+ "[",
3068
+ node.expertKey,
3069
+ "] ",
3070
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
3071
+ "(",
3072
+ shortRunId,
3073
+ ")"
3074
+ ] })
3075
+ ] }),
3076
+ node.activities.map((activity, index) => /* @__PURE__ */ jsx(CheckpointActionRow, { action: activity }, getActivityKey(activity, index))),
3077
+ node.children.map((child) => /* @__PURE__ */ jsx(RunBox, { node: child, isRoot: false }, child.runId))
3078
+ ] });
3079
+ };
3080
+ function getActivityProps(activityOrGroup) {
3081
+ if (activityOrGroup.type === "parallelGroup") {
3082
+ const group = activityOrGroup;
3083
+ const firstActivity = group.activities[0];
3084
+ return {
3085
+ runId: group.runId,
3086
+ expertKey: group.expertKey,
3087
+ delegatedBy: firstActivity?.delegatedBy
3088
+ };
3089
+ }
3090
+ return activityOrGroup;
3091
+ }
3092
+ var ActivityLogPanel = ({ activities }) => {
3093
+ const rootNodes = useMemo(() => {
3094
+ const nodeMap = /* @__PURE__ */ new Map();
3095
+ const roots = [];
3096
+ const orphanChildren = /* @__PURE__ */ new Map();
3097
+ for (const activityOrGroup of activities) {
3098
+ const { runId, expertKey, delegatedBy } = getActivityProps(activityOrGroup);
3099
+ let node = nodeMap.get(runId);
3100
+ if (!node) {
3101
+ node = {
3102
+ runId,
3103
+ expertKey,
3104
+ activities: [],
3105
+ children: []
3106
+ };
3107
+ nodeMap.set(runId, node);
3108
+ const waitingChildren = orphanChildren.get(runId);
3109
+ if (waitingChildren) {
3110
+ for (const child of waitingChildren) {
3111
+ node.children.push(child);
3112
+ const rootIndex = roots.indexOf(child);
3113
+ if (rootIndex !== -1) {
3114
+ roots.splice(rootIndex, 1);
3115
+ }
3116
+ }
3117
+ orphanChildren.delete(runId);
3118
+ }
3119
+ if (delegatedBy) {
3120
+ const parentNode = nodeMap.get(delegatedBy.runId);
3121
+ if (parentNode) {
3122
+ parentNode.children.push(node);
3123
+ } else {
3124
+ const orphans = orphanChildren.get(delegatedBy.runId) ?? [];
3125
+ orphans.push(node);
3126
+ orphanChildren.set(delegatedBy.runId, orphans);
3127
+ roots.push(node);
3128
+ }
3129
+ } else {
3130
+ roots.push(node);
3131
+ }
3132
+ }
3133
+ node.activities.push(activityOrGroup);
3134
+ }
3135
+ return roots;
3136
+ }, [activities]);
3137
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: rootNodes.map((node) => /* @__PURE__ */ jsx(RunBox, { node, isRoot: true }, node.runId)) });
3138
+ };
3139
+ var ContinueInputPanel = ({
3140
+ isActive,
3141
+ runStatus,
3142
+ onSubmit
3143
+ }) => {
3144
+ const { input, handleInput } = useTextInput({
3145
+ onSubmit: (newQuery) => {
3146
+ if (isActive && newQuery.trim()) {
3147
+ onSubmit(newQuery.trim());
3148
+ }
3149
+ }
3150
+ });
3151
+ useInput(handleInput, { isActive });
3152
+ if (runStatus === "running") {
3153
+ return null;
3154
+ }
3155
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", children: [
3156
+ /* @__PURE__ */ jsxs(Text, { children: [
3157
+ /* @__PURE__ */ jsx(Text, { color: runStatus === "completed" ? "green" : "yellow", bold: true, children: runStatus === "completed" ? "Completed" : "Stopped" }),
3158
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: " - Enter a follow-up query or wait to exit" })
3159
+ ] }),
3160
+ /* @__PURE__ */ jsxs(Box, { children: [
3161
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Continue: " }),
3162
+ /* @__PURE__ */ jsx(Text, { children: input }),
3163
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: "_" })
3164
+ ] })
3165
+ ] });
3166
+ };
3167
+ var StatusPanel = ({
3168
+ runtimeInfo,
3169
+ eventCount,
3170
+ runStatus
3171
+ }) => {
3172
+ if (runStatus !== "running") {
3173
+ return null;
3174
+ }
3175
+ return /* @__PURE__ */ jsx(RunSetting, { info: runtimeInfo, eventCount, isEditing: false });
3176
+ };
3177
+ var useExecutionState = (options) => {
3178
+ const { expertKey, query, config, continueTimeoutMs, historicalEvents, onReady, onComplete } = options;
3179
+ const { exit } = useApp();
3180
+ const runState = useRun();
3181
+ const { runtimeInfo, handleEvent, setQuery } = useRuntimeInfo({
3182
+ initialExpertName: expertKey,
3183
+ initialConfig: config
3184
+ });
3185
+ const [runStatus, setRunStatus] = useState("running");
3186
+ const [isAcceptingContinue, setIsAcceptingContinue] = useState(false);
3187
+ const timeoutRef = useRef(null);
3188
+ const clearTimeoutIfExists = useCallback(() => {
3189
+ if (timeoutRef.current) {
3190
+ clearTimeout(timeoutRef.current);
3191
+ timeoutRef.current = null;
3192
+ }
3193
+ }, []);
3194
+ const startExitTimeout = useCallback(() => {
3195
+ clearTimeoutIfExists();
3196
+ timeoutRef.current = setTimeout(() => {
3197
+ onComplete({ nextQuery: null });
3198
+ exit();
3199
+ }, continueTimeoutMs);
3200
+ }, [clearTimeoutIfExists, continueTimeoutMs, onComplete, exit]);
3201
+ useEffect(() => {
3202
+ setQuery(query);
3203
+ }, [query, setQuery]);
3204
+ useEffect(() => {
3205
+ if (historicalEvents && historicalEvents.length > 0) {
3206
+ runState.appendHistoricalEvents(historicalEvents);
3207
+ }
3208
+ }, [historicalEvents, runState.appendHistoricalEvents]);
3209
+ useEffect(() => {
3210
+ onReady((event) => {
3211
+ runState.addEvent(event);
3212
+ const result = handleEvent(event);
3213
+ if (result?.completed) {
3214
+ setRunStatus("completed");
3215
+ setIsAcceptingContinue(true);
3216
+ startExitTimeout();
3217
+ } else if (result?.stopped) {
3218
+ setRunStatus("stopped");
3219
+ setIsAcceptingContinue(true);
3220
+ startExitTimeout();
3221
+ }
3222
+ });
3223
+ }, [onReady, runState.addEvent, handleEvent, startExitTimeout]);
3224
+ useEffect(() => {
3225
+ return () => {
3226
+ clearTimeoutIfExists();
3227
+ };
3228
+ }, [clearTimeoutIfExists]);
3229
+ const handleContinueSubmit = useCallback(
3230
+ (newQuery) => {
3231
+ if (isAcceptingContinue && newQuery.trim()) {
3232
+ clearTimeoutIfExists();
3233
+ onComplete({ nextQuery: newQuery.trim() });
3234
+ exit();
3235
+ }
3236
+ },
3237
+ [isAcceptingContinue, clearTimeoutIfExists, onComplete, exit]
3238
+ );
3239
+ return {
3240
+ activities: runState.activities,
3241
+ streaming: runState.streaming,
3242
+ eventCount: runState.eventCount,
3243
+ runtimeInfo,
3244
+ runStatus,
3245
+ isAcceptingContinue,
3246
+ handleContinueSubmit,
3247
+ clearTimeout: clearTimeoutIfExists
3248
+ };
3249
+ };
3250
+ var ExecutionApp = (props) => {
3251
+ const { expertKey, query, config, continueTimeoutMs, historicalEvents, onReady, onComplete } = props;
3252
+ const { exit } = useApp();
3253
+ const state = useExecutionState({
3254
+ expertKey,
3255
+ query,
3256
+ config,
3257
+ continueTimeoutMs,
3258
+ historicalEvents,
3259
+ onReady,
3260
+ onComplete
3261
+ });
3262
+ useInput((input, key) => {
3263
+ if (key.ctrl && input === "c") {
3264
+ state.clearTimeout();
3265
+ exit();
3266
+ }
3267
+ });
3268
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3269
+ /* @__PURE__ */ jsx(ActivityLogPanel, { activities: state.activities }),
3270
+ /* @__PURE__ */ jsx(StreamingDisplay, { streaming: state.streaming }),
3271
+ /* @__PURE__ */ jsx(
3272
+ StatusPanel,
3273
+ {
3274
+ runtimeInfo: state.runtimeInfo,
3275
+ eventCount: state.eventCount,
3276
+ runStatus: state.runStatus
3277
+ }
3278
+ ),
3279
+ /* @__PURE__ */ jsx(
3280
+ ContinueInputPanel,
3281
+ {
3282
+ isActive: state.isAcceptingContinue,
3283
+ runStatus: state.runStatus,
3284
+ onSubmit: state.handleContinueSubmit
3285
+ }
3286
+ )
3287
+ ] });
3288
+ };
3289
+ function renderExecution(params) {
3290
+ const eventQueue = new EventQueue();
3291
+ const result = new Promise((resolve, reject) => {
3292
+ let resolved = false;
3293
+ const { waitUntilExit } = render(
3294
+ /* @__PURE__ */ jsx(
3295
+ ExecutionApp,
3296
+ {
3297
+ ...params,
3298
+ onReady: (handler) => {
3299
+ eventQueue.setHandler(handler);
3300
+ },
3301
+ onComplete: (result2) => {
3302
+ resolved = true;
3303
+ resolve(result2);
3304
+ }
3305
+ }
3306
+ )
3307
+ );
3308
+ waitUntilExit().then(() => {
3309
+ if (!resolved) {
3310
+ reject(new Error("Execution cancelled"));
3311
+ }
3312
+ }).catch(reject);
3313
+ });
3314
+ return {
3315
+ result,
3316
+ eventListener: (event) => {
3317
+ eventQueue.emit(event);
3318
+ }
3319
+ };
3320
+ }
3321
+ var selectionReducer = (_state, action) => {
3322
+ switch (action.type) {
3323
+ case "BROWSE_HISTORY":
3324
+ return { type: "browsingHistory", jobs: action.jobs };
3325
+ case "BROWSE_EXPERTS":
3326
+ return { type: "browsingExperts", experts: action.experts };
3327
+ case "SELECT_EXPERT":
3328
+ return { type: "enteringQuery", expertKey: action.expertKey };
3329
+ case "SELECT_JOB":
3330
+ return { type: "browsingCheckpoints", job: action.job, checkpoints: action.checkpoints };
3331
+ case "GO_BACK_FROM_CHECKPOINTS":
3332
+ return { type: "browsingHistory", jobs: action.jobs };
3333
+ default:
3334
+ return assertNever(action);
3335
+ }
3336
+ };
3337
+ var SelectionApp = (props) => {
3338
+ const {
3339
+ showHistory,
3340
+ initialExpertKey,
3341
+ initialQuery,
3342
+ initialCheckpoint,
3343
+ configuredExperts,
3344
+ recentExperts,
3345
+ historyJobs,
3346
+ onLoadCheckpoints,
3347
+ onComplete
3348
+ } = props;
3349
+ const { exit } = useApp();
3350
+ const allExperts = useMemo(() => {
3351
+ const configured = configuredExperts.map((e) => ({ ...e, source: "configured" }));
3352
+ const recent = recentExperts.filter((e) => !configured.some((c) => c.key === e.key)).map((e) => ({ ...e, source: "recent" }));
3353
+ return [...configured, ...recent];
3354
+ }, [configuredExperts, recentExperts]);
3355
+ const getInitialState = () => {
3356
+ if (initialExpertKey && !initialQuery) {
3357
+ return { type: "enteringQuery", expertKey: initialExpertKey };
3358
+ }
3359
+ if (showHistory && historyJobs.length > 0) {
3360
+ return { type: "browsingHistory", jobs: historyJobs };
3361
+ }
3362
+ return { type: "browsingExperts", experts: allExperts };
3363
+ };
3364
+ const [state, dispatch] = useReducer(selectionReducer, void 0, getInitialState);
3365
+ const [selectedCheckpoint, setSelectedCheckpoint] = useState(
3366
+ initialCheckpoint
3367
+ );
3368
+ useEffect(() => {
3369
+ if (initialExpertKey && initialQuery) {
3370
+ onComplete({
3371
+ expertKey: initialExpertKey,
3372
+ query: initialQuery,
3373
+ checkpoint: initialCheckpoint
3374
+ });
3375
+ exit();
3376
+ }
3377
+ }, [initialExpertKey, initialQuery, initialCheckpoint, onComplete, exit]);
3378
+ const { input: queryInput, handleInput: handleQueryInput } = useTextInput({
3379
+ onSubmit: (query) => {
3380
+ if (state.type === "enteringQuery" && query.trim()) {
3381
+ onComplete({
3382
+ expertKey: state.expertKey,
3383
+ query: query.trim(),
3384
+ checkpoint: selectedCheckpoint
3385
+ });
3386
+ exit();
3387
+ }
3388
+ }
3389
+ });
3390
+ useInput(handleQueryInput, { isActive: state.type === "enteringQuery" });
3391
+ const handleExpertSelect = useCallback((expertKey) => {
3392
+ dispatch({ type: "SELECT_EXPERT", expertKey });
3393
+ }, []);
3394
+ const handleJobSelect = useCallback(
3395
+ async (job) => {
3396
+ try {
3397
+ const checkpoints = await onLoadCheckpoints(job);
3398
+ dispatch({ type: "SELECT_JOB", job, checkpoints });
3399
+ } catch {
3400
+ dispatch({ type: "SELECT_JOB", job, checkpoints: [] });
3401
+ }
3402
+ },
3403
+ [onLoadCheckpoints]
3404
+ );
3405
+ const handleJobResume = useCallback(
3406
+ async (job) => {
3407
+ try {
3408
+ const checkpoints = await onLoadCheckpoints(job);
3409
+ const latestCheckpoint = checkpoints[0];
3410
+ if (latestCheckpoint) {
3411
+ setSelectedCheckpoint(latestCheckpoint);
3412
+ }
3413
+ dispatch({ type: "SELECT_EXPERT", expertKey: job.expertKey });
3414
+ } catch {
3415
+ dispatch({ type: "SELECT_EXPERT", expertKey: job.expertKey });
3416
+ }
3417
+ },
3418
+ [onLoadCheckpoints]
3419
+ );
3420
+ const handleCheckpointResume = useCallback(
3421
+ (checkpoint) => {
3422
+ setSelectedCheckpoint(checkpoint);
3423
+ if (state.type === "browsingCheckpoints") {
3424
+ dispatch({ type: "SELECT_EXPERT", expertKey: state.job.expertKey });
3425
+ }
3426
+ },
3427
+ [state]
3428
+ );
3429
+ const handleBack = useCallback(() => {
3430
+ if (state.type === "browsingCheckpoints") {
3431
+ dispatch({ type: "GO_BACK_FROM_CHECKPOINTS", jobs: historyJobs });
3432
+ }
3433
+ }, [state, historyJobs]);
3434
+ const handleSwitchToExperts = useCallback(() => {
3435
+ dispatch({ type: "BROWSE_EXPERTS", experts: allExperts });
3436
+ }, [allExperts]);
3437
+ const handleSwitchToHistory = useCallback(() => {
3438
+ dispatch({ type: "BROWSE_HISTORY", jobs: historyJobs });
3439
+ }, [historyJobs]);
3440
+ useInput((input, key) => {
3441
+ if (key.ctrl && input === "c") {
3442
+ exit();
3443
+ }
3444
+ });
3445
+ const contextValue = useMemo(
3446
+ () => ({
3447
+ onExpertSelect: handleExpertSelect,
3448
+ onQuerySubmit: () => {
3449
+ },
3450
+ // Not used in browser
3451
+ onJobSelect: handleJobSelect,
3452
+ onJobResume: handleJobResume,
3453
+ onCheckpointSelect: () => {
3454
+ },
3455
+ // Not used in selection (no event browsing)
3456
+ onCheckpointResume: handleCheckpointResume,
3457
+ onEventSelect: () => {
3458
+ },
3459
+ // Not used in selection
3460
+ onBack: handleBack,
3461
+ onSwitchToExperts: handleSwitchToExperts,
3462
+ onSwitchToHistory: handleSwitchToHistory
3463
+ }),
3464
+ [
3465
+ handleExpertSelect,
3466
+ handleJobSelect,
3467
+ handleJobResume,
3468
+ handleCheckpointResume,
3469
+ handleBack,
3470
+ handleSwitchToExperts,
3471
+ handleSwitchToHistory
3472
+ ]
3473
+ );
3474
+ if (initialExpertKey && initialQuery) {
3475
+ return null;
3476
+ }
3477
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3478
+ /* @__PURE__ */ jsx(InputAreaProvider, { value: contextValue, children: (state.type === "browsingHistory" || state.type === "browsingExperts" || state.type === "browsingCheckpoints") && /* @__PURE__ */ jsx(
3479
+ BrowserRouter,
3480
+ {
3481
+ inputState: state,
3482
+ showEventsHint: false
3483
+ }
3484
+ ) }),
3485
+ state.type === "enteringQuery" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", children: [
3486
+ /* @__PURE__ */ jsxs(Text, { children: [
3487
+ /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "Expert:" }),
3488
+ " ",
3489
+ /* @__PURE__ */ jsx(Text, { children: state.expertKey }),
3490
+ selectedCheckpoint && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
3491
+ " (resuming from step ",
3492
+ selectedCheckpoint.stepNumber,
3493
+ ")"
3494
+ ] })
3495
+ ] }),
3496
+ /* @__PURE__ */ jsxs(Box, { children: [
3497
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Query: " }),
3498
+ /* @__PURE__ */ jsx(Text, { children: queryInput }),
3499
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: "_" })
3500
+ ] }),
3501
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Press Enter to start" })
3502
+ ] })
3503
+ ] });
3504
+ };
3505
+ async function renderSelection(params) {
3506
+ return new Promise((resolve, reject) => {
3507
+ let resolved = false;
3508
+ const { waitUntilExit } = render(
3509
+ /* @__PURE__ */ jsx(
3510
+ SelectionApp,
3511
+ {
3512
+ ...params,
3513
+ onComplete: (result) => {
3514
+ resolved = true;
3515
+ resolve(result);
3516
+ }
3517
+ }
3518
+ )
3519
+ );
3520
+ waitUntilExit().then(() => {
3521
+ if (!resolved) {
3522
+ reject(new Error("Selection cancelled"));
3523
+ }
3524
+ }).catch(reject);
3525
+ });
3526
+ }
3527
+
3528
+ // ../../packages/tui/src/start-handler.ts
3529
+ var CONTINUE_TIMEOUT_MS = 6e4;
3530
+ async function startHandler(expertKey, query, options, handlerOptions) {
3531
+ const input = parseWithFriendlyError(startCommandInputSchema, { expertKey, query, options });
3532
+ try {
3533
+ const { perstackConfig, checkpoint, env, providerConfig, model, experts } = await resolveRunContext({
3534
+ configPath: input.options.config,
3535
+ provider: input.options.provider,
3536
+ model: input.options.model,
3537
+ envPath: input.options.envPath,
3538
+ continue: input.options.continue,
3539
+ continueJob: input.options.continueJob,
3540
+ resumeFrom: input.options.resumeFrom,
3541
+ expertKey: input.expertKey,
3542
+ perstackConfig: handlerOptions?.perstackConfig
3543
+ });
3544
+ if (handlerOptions?.additionalEnv) ;
3545
+ const maxSteps = input.options.maxSteps ?? perstackConfig.maxSteps;
3546
+ const maxRetries = input.options.maxRetries ?? perstackConfig.maxRetries ?? defaultMaxRetries;
3547
+ const timeout = input.options.timeout ?? perstackConfig.timeout ?? defaultTimeout;
3548
+ const configuredExperts = Object.keys(perstackConfig.experts ?? {}).map((key) => ({
3549
+ key,
3550
+ name: key
3551
+ }));
3552
+ const recentExperts = getRecentExperts(10);
3553
+ const showHistory = !input.expertKey && !input.query && !checkpoint;
3554
+ const historyJobs = showHistory ? getAllJobs2().map((j) => ({
3555
+ jobId: j.id,
3556
+ status: j.status,
3557
+ expertKey: j.coordinatorExpertKey,
3558
+ totalSteps: j.totalSteps,
3559
+ startedAt: j.startedAt,
3560
+ finishedAt: j.finishedAt
3561
+ })) : [];
3562
+ const selection = await renderSelection({
3563
+ showHistory,
3564
+ initialExpertKey: input.expertKey,
3565
+ initialQuery: input.query,
3566
+ initialCheckpoint: checkpoint ? {
3567
+ id: checkpoint.id,
3568
+ jobId: checkpoint.jobId,
3569
+ runId: checkpoint.runId,
3570
+ stepNumber: checkpoint.stepNumber,
3571
+ contextWindowUsage: checkpoint.contextWindowUsage ?? 0
3572
+ } : void 0,
3573
+ configuredExperts,
3574
+ recentExperts,
3575
+ historyJobs,
3576
+ onLoadCheckpoints: async (j) => {
3577
+ const checkpoints = getCheckpointsWithDetails(j.jobId);
3578
+ return checkpoints.map((cp) => ({ ...cp, jobId: j.jobId }));
3579
+ }
3580
+ });
3581
+ if (!selection.expertKey) {
3582
+ console.error("Expert key is required");
3583
+ return;
3584
+ }
3585
+ if (!selection.query && !selection.checkpoint) {
3586
+ console.error("Query is required");
3587
+ return;
3588
+ }
3589
+ let currentCheckpoint = selection.checkpoint ? getCheckpointById(selection.checkpoint.jobId, selection.checkpoint.id) : checkpoint;
3590
+ if (currentCheckpoint && currentCheckpoint.expert.key !== selection.expertKey) {
3591
+ console.error(
3592
+ `Checkpoint expert key ${currentCheckpoint.expert.key} does not match input expert key ${selection.expertKey}`
3593
+ );
3594
+ return;
3595
+ }
3596
+ const lockfilePath = findLockfile();
3597
+ const lockfile = lockfilePath ? loadLockfile(lockfilePath) ?? void 0 : void 0;
3598
+ let currentQuery = selection.query;
3599
+ let currentJobId = currentCheckpoint?.jobId ?? input.options.jobId ?? createId();
3600
+ let isNextQueryInteractiveToolResult = input.options.interactiveToolCallResult ?? false;
3601
+ let isFirstIteration = true;
3602
+ const initialHistoricalEvents = currentCheckpoint ? getAllEventContentsForJob(currentCheckpoint.jobId, currentCheckpoint.stepNumber) : void 0;
3603
+ while (currentQuery !== null) {
3604
+ const historicalEvents = isFirstIteration ? initialHistoricalEvents : void 0;
3605
+ const runId = createId();
3606
+ const { result: executionResult, eventListener } = renderExecution({
3607
+ expertKey: selection.expertKey,
3608
+ query: currentQuery,
3609
+ config: {
3610
+ runtimeVersion,
3611
+ model,
3612
+ maxSteps,
3613
+ maxRetries,
3614
+ timeout,
3615
+ contextWindowUsage: currentCheckpoint?.contextWindowUsage ?? 0
3616
+ },
3617
+ continueTimeoutMs: CONTINUE_TIMEOUT_MS,
3618
+ historicalEvents
3619
+ });
3620
+ const runResult = await run(
3621
+ {
3622
+ setting: {
3623
+ jobId: currentJobId,
3624
+ runId,
3625
+ expertKey: selection.expertKey,
3626
+ input: isNextQueryInteractiveToolResult && currentCheckpoint ? parseInteractiveToolCallResult(currentQuery, currentCheckpoint) : { text: currentQuery },
3627
+ experts,
3628
+ model,
3629
+ providerConfig,
3630
+ reasoningBudget: input.options.reasoningBudget ?? perstackConfig.reasoningBudget,
3631
+ maxSteps: input.options.maxSteps ?? perstackConfig.maxSteps,
3632
+ maxRetries: input.options.maxRetries ?? perstackConfig.maxRetries,
3633
+ timeout: input.options.timeout ?? perstackConfig.timeout,
3634
+ perstackApiBaseUrl: perstackConfig.perstackApiBaseUrl,
3635
+ perstackApiKey: env.PERSTACK_API_KEY,
3636
+ perstackBaseSkillCommand: perstackConfig.perstackBaseSkillCommand,
3637
+ env,
3638
+ verbose: input.options.verbose
3639
+ },
3640
+ checkpoint: currentCheckpoint
3641
+ },
3642
+ {
3643
+ eventListener,
3644
+ storeCheckpoint: defaultStoreCheckpoint,
3645
+ storeEvent: defaultStoreEvent,
3646
+ retrieveCheckpoint: defaultRetrieveCheckpoint,
3647
+ storeJob,
3648
+ retrieveJob,
3649
+ createJob: createInitialJob,
3650
+ lockfile
3651
+ }
3652
+ );
3653
+ const result = await executionResult;
3654
+ const canContinue = runResult.status === "completed" || runResult.status === "stoppedByExceededMaxSteps" || runResult.status === "stoppedByError" || runResult.status === "stoppedByInteractiveTool";
3655
+ if (result.nextQuery && canContinue) {
3656
+ currentQuery = result.nextQuery;
3657
+ currentCheckpoint = runResult;
3658
+ currentJobId = runResult.jobId;
3659
+ isNextQueryInteractiveToolResult = runResult.status === "stoppedByInteractiveTool";
3660
+ isFirstIteration = false;
3661
+ } else {
3662
+ currentQuery = null;
3663
+ }
3664
+ }
3665
+ } catch (error) {
3666
+ if (error instanceof Error) {
3667
+ console.error(error.message);
3668
+ } else {
3669
+ console.error(error);
3670
+ }
3671
+ }
3672
+ }
3673
+ 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(
3674
+ "--reasoning-budget <budget>",
3675
+ "Reasoning budget for native LLM reasoning (minimal, low, medium, high, or token count)"
3676
+ ).option(
3677
+ "--max-steps <maxSteps>",
3678
+ "Maximum number of steps to run, default is undefined (no limit)"
3679
+ ).option("--max-retries <maxRetries>", "Maximum number of generation retries, default is 5").option(
3680
+ "--timeout <timeout>",
3681
+ "Timeout for each generation in milliseconds, default is 300000 (5 minutes)"
3682
+ ).option("--job-id <jobId>", "Job ID for identifying the job").option(
3683
+ "--env-path <path>",
3684
+ "Path to the environment file (can be specified multiple times), default is .env and .env.local",
3685
+ (value, previous) => previous.concat(value),
3686
+ []
3687
+ ).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(
3688
+ "--resume-from <checkpointId>",
3689
+ "Resume from a specific checkpoint (requires --continue or --continue-job)"
3690
+ ).option("-i, --interactive-tool-call-result", "Query is interactive tool call result").action((expertKey, query, options) => startHandler(expertKey, query, options));
3691
+
1037
3692
  // bin/cli.ts
1038
3693
  var program = new Command().name(package_default.name).description(package_default.description).version(package_default.version).addCommand(startCommand).addCommand(runCommand).addCommand(logCommand).addCommand(installCommand);
1039
3694
  program.parse();