perstack 0.0.124 → 0.0.127

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
@@ -5,7 +5,7 @@ import { t as require_token_error } from "../token-error-Bru5BVnt.js";
5
5
  import fs, { constants, lstat, mkdir, open, readFile, stat, writeFile } from "node:fs/promises";
6
6
  import path, { dirname, extname } from "node:path";
7
7
  import * as fs$2 from "node:fs";
8
- import fs$1, { existsSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
8
+ import fs$1, { closeSync, existsSync, openSync, readFileSync, readSync, readdirSync, realpathSync, statSync } from "node:fs";
9
9
  import os from "node:os";
10
10
  import { execFile, execFileSync } from "node:child_process";
11
11
  import { promisify } from "node:util";
@@ -20157,20 +20157,59 @@ function getCheckpointsByJobId$1(jobId) {
20157
20157
  const checkpointDir = getCheckpointDir(jobId);
20158
20158
  if (!existsSync(checkpointDir)) return [];
20159
20159
  const files = readdirSync(checkpointDir).filter((file) => file.endsWith(".json"));
20160
- const checkpoints = [];
20160
+ const fileEntries = [];
20161
20161
  for (const file of files) try {
20162
+ const mtime = statSync(path.resolve(checkpointDir, file)).mtimeMs;
20163
+ fileEntries.push({
20164
+ file,
20165
+ mtime
20166
+ });
20167
+ } catch {
20168
+ fileEntries.push({
20169
+ file,
20170
+ mtime: 0
20171
+ });
20172
+ }
20173
+ fileEntries.sort((a, b) => a.mtime - b.mtime);
20174
+ const checkpoints = [];
20175
+ for (const { file } of fileEntries) try {
20162
20176
  const content = readFileSync(path.resolve(checkpointDir, file), "utf-8");
20163
20177
  checkpoints.push(checkpointSchema.parse(JSON.parse(content)));
20164
20178
  } catch {}
20165
- return checkpoints.sort((a, b) => {
20179
+ return checkpoints;
20180
+ }
20181
+ function getCheckpointsByRunId(jobId, runId) {
20182
+ const checkpointDir = getCheckpointDir(jobId);
20183
+ if (!existsSync(checkpointDir)) return [];
20184
+ const files = readdirSync(checkpointDir).filter((file) => file.endsWith(".json"));
20185
+ const runIdPattern = `"runId":"${runId}"`;
20186
+ const matchingFiles = [];
20187
+ const headerBuf = Buffer.alloc(256);
20188
+ for (const file of files) {
20189
+ const filePath = path.resolve(checkpointDir, file);
20166
20190
  try {
20167
- const aStat = statSync(path.resolve(checkpointDir, `${a.id}.json`));
20168
- const bStat = statSync(path.resolve(checkpointDir, `${b.id}.json`));
20169
- return aStat.mtimeMs - bStat.mtimeMs;
20170
- } catch {
20171
- return 0;
20172
- }
20173
- });
20191
+ const fd = openSync(filePath, "r");
20192
+ try {
20193
+ const bytesRead = readSync(fd, headerBuf, 0, 256, 0);
20194
+ if (headerBuf.toString("utf-8", 0, bytesRead).includes(runIdPattern)) {
20195
+ const mtime = statSync(filePath).mtimeMs;
20196
+ matchingFiles.push({
20197
+ file,
20198
+ mtime
20199
+ });
20200
+ }
20201
+ } finally {
20202
+ closeSync(fd);
20203
+ }
20204
+ } catch {}
20205
+ }
20206
+ matchingFiles.sort((a, b) => a.mtime - b.mtime);
20207
+ const checkpoints = [];
20208
+ for (const { file } of matchingFiles) try {
20209
+ const content = readFileSync(path.resolve(checkpointDir, file), "utf-8");
20210
+ checkpoints.push(checkpointSchema.parse(JSON.parse(content)));
20211
+ } catch {}
20212
+ return checkpoints;
20174
20213
  }
20175
20214
  //#endregion
20176
20215
  //#region ../../packages/filesystem/src/run-setting.ts
@@ -20207,7 +20246,7 @@ async function defaultStoreEvent(event) {
20207
20246
  await mkdir(runDir, { recursive: true });
20208
20247
  await writeFile(eventPath, JSON.stringify(event));
20209
20248
  }
20210
- function getEventContents(jobId, runId, maxStepNumber) {
20249
+ function getEventContents(jobId, runId, maxStepNumber, typeFilter) {
20211
20250
  const runDir = defaultGetRunDir(jobId, runId);
20212
20251
  if (!existsSync(runDir)) return [];
20213
20252
  const eventFiles = readdirSync(runDir).filter((file) => file.startsWith("event-")).map((file) => {
@@ -20218,7 +20257,7 @@ function getEventContents(jobId, runId, maxStepNumber) {
20218
20257
  stepNumber: Number(step),
20219
20258
  type
20220
20259
  };
20221
- }).filter((e) => maxStepNumber === void 0 || e.stepNumber <= maxStepNumber).sort((a, b) => a.timestamp - b.timestamp);
20260
+ }).filter((e) => maxStepNumber === void 0 || e.stepNumber <= maxStepNumber).filter((e) => !typeFilter || typeFilter.has(e.type)).sort((a, b) => a.timestamp - b.timestamp);
20222
20261
  const events = [];
20223
20262
  for (const { file } of eventFiles) try {
20224
20263
  const content = readFileSync(path.resolve(runDir, file), "utf-8");
@@ -20226,6 +20265,44 @@ function getEventContents(jobId, runId, maxStepNumber) {
20226
20265
  } catch {}
20227
20266
  return events;
20228
20267
  }
20268
+ /**
20269
+ * Get a single event by type from a run (returns the first match by timestamp).
20270
+ * Much faster than loading all events when you only need one.
20271
+ */
20272
+ function getFirstEvent(jobId, runId, typeFilter) {
20273
+ const runDir = defaultGetRunDir(jobId, runId);
20274
+ if (!existsSync(runDir)) return;
20275
+ const files = readdirSync(runDir).filter((file) => file.startsWith("event-") && file.includes(`-${typeFilter}.json`)).sort();
20276
+ if (files.length === 0) return void 0;
20277
+ try {
20278
+ const content = readFileSync(path.resolve(runDir, files[0]), "utf-8");
20279
+ return JSON.parse(content);
20280
+ } catch {
20281
+ return;
20282
+ }
20283
+ }
20284
+ /**
20285
+ * Get event stats from filenames only (no file content reading).
20286
+ * Returns total event count and max step number.
20287
+ */
20288
+ function getEventStats(jobId, runId) {
20289
+ const runDir = defaultGetRunDir(jobId, runId);
20290
+ if (!existsSync(runDir)) return {
20291
+ eventCount: 0,
20292
+ maxStep: 0
20293
+ };
20294
+ const eventFiles = readdirSync(runDir).filter((file) => file.startsWith("event-"));
20295
+ let maxStep = 0;
20296
+ for (const file of eventFiles) {
20297
+ const parts = file.split(".")[0].split("-");
20298
+ const step = Number(parts[2]);
20299
+ if (step > maxStep) maxStep = step;
20300
+ }
20301
+ return {
20302
+ eventCount: eventFiles.length,
20303
+ maxStep
20304
+ };
20305
+ }
20229
20306
  function getRunIdsByJobId(jobId) {
20230
20307
  const runsDir = path.resolve(getJobDir(jobId), "runs");
20231
20308
  if (!existsSync(runsDir)) return [];
@@ -20284,6 +20361,9 @@ function createLogDataFetcher(storage) {
20284
20361
  async getCheckpoints(jobId) {
20285
20362
  return storage.getCheckpointsByJobId(jobId);
20286
20363
  },
20364
+ async getCheckpointsForRun(jobId, runId) {
20365
+ return storage.getCheckpointsByRunId(jobId, runId);
20366
+ },
20287
20367
  async getCheckpoint(jobId, checkpointId) {
20288
20368
  return storage.retrieveCheckpoint(jobId, checkpointId);
20289
20369
  },
@@ -20292,12 +20372,23 @@ function createLogDataFetcher(storage) {
20292
20372
  },
20293
20373
  async getAllEventsForJob(jobId) {
20294
20374
  const runs = await this.getRuns(jobId);
20295
- const allEvents = [];
20296
- for (const run of runs) {
20297
- const events = await storage.getEventContents(jobId, run.runId);
20298
- allEvents.push(...events);
20299
- }
20300
- return allEvents.sort((a, b) => a.timestamp - b.timestamp);
20375
+ return (await Promise.all(runs.map((run) => storage.getEventContents(jobId, run.runId)))).flat().sort((a, b) => a.timestamp - b.timestamp);
20376
+ },
20377
+ async getTreeEventsForJob(jobId) {
20378
+ const treeEventTypes = new Set([
20379
+ "startRun",
20380
+ "resumeFromStop",
20381
+ "stopRunByDelegate",
20382
+ "callTools",
20383
+ "completeRun",
20384
+ "stopRunByError",
20385
+ "retry"
20386
+ ]);
20387
+ const runIds = storage.getRunIdsByJobId(jobId);
20388
+ return (await Promise.all(runIds.map((runId) => storage.getEventContents(jobId, runId, void 0, treeEventTypes)))).flat().sort((a, b) => a.timestamp - b.timestamp);
20389
+ },
20390
+ async getFirstEventForRun(jobId, runId, typeFilter) {
20391
+ return storage.getFirstEvent(jobId, runId, typeFilter);
20301
20392
  }
20302
20393
  };
20303
20394
  }
@@ -20316,9 +20407,13 @@ function createStorageAdapter(basePath) {
20316
20407
  getAllJobs: async () => getAllJobs$1(),
20317
20408
  retrieveJob: async (jobId) => retrieveJob(jobId),
20318
20409
  getCheckpointsByJobId: async (jobId) => getCheckpointsByJobId$1(jobId),
20410
+ getCheckpointsByRunId: async (jobId, runId) => getCheckpointsByRunId(jobId, runId),
20319
20411
  retrieveCheckpoint: async (jobId, checkpointId) => defaultRetrieveCheckpoint(jobId, checkpointId),
20320
- getEventContents: async (jobId, runId, maxStep) => getEventContents(jobId, runId, maxStep),
20412
+ getEventContents: async (jobId, runId, maxStep, typeFilter) => getEventContents(jobId, runId, maxStep, typeFilter),
20413
+ getFirstEvent: async (jobId, runId, typeFilter) => getFirstEvent(jobId, runId, typeFilter),
20414
+ getEventStats: (jobId, runId) => getEventStats(jobId, runId),
20321
20415
  getAllRuns: async () => getAllRuns$1(),
20416
+ getRunIdsByJobId: (jobId) => getRunIdsByJobId(jobId),
20322
20417
  getJobIds: () => {
20323
20418
  const jobsDir = path.join(basePath, "jobs");
20324
20419
  if (!existsSync(jobsDir)) return [];
@@ -20613,7 +20708,7 @@ function formatSummarySection(summary) {
20613
20708
  }
20614
20709
  function formatEvent(event, verbose) {
20615
20710
  const lines = [];
20616
- const time = formatTime(event.timestamp);
20711
+ const time = formatTime$1(event.timestamp);
20617
20712
  const header = `[Step ${event.stepNumber}] ${event.type}${" ".repeat(Math.max(0, 24 - event.type.length))}${time}`;
20618
20713
  lines.push(header);
20619
20714
  if (event.type === "startRun" && "inputMessages" in event) {
@@ -20674,7 +20769,7 @@ function extractTextContent$1(contents) {
20674
20769
  function formatTimestamp$2(ts) {
20675
20770
  return new Date(ts).toISOString().replace("T", " ").replace(/\.\d{3}Z$/, "");
20676
20771
  }
20677
- function formatTime(ts) {
20772
+ function formatTime$1(ts) {
20678
20773
  return new Date(ts).toISOString().slice(11, 19);
20679
20774
  }
20680
20775
  //#endregion
@@ -20997,7 +21092,7 @@ async function expertVersionsHandler(scopeName, options) {
20997
21092
  }
20998
21093
  //#endregion
20999
21094
  //#region ../../packages/runtime/package.json
21000
- var version$1 = "0.0.136";
21095
+ var version$1 = "0.0.138";
21001
21096
  //#endregion
21002
21097
  //#region ../../packages/runtime/src/helpers/usage.ts
21003
21098
  function createEmptyUsage() {
@@ -92386,22 +92481,22 @@ var import_dist = (/* @__PURE__ */ __commonJSMin(((exports) => {
92386
92481
  }
92387
92482
  exports.dedent = dedent;
92388
92483
  })))();
92389
- function getEnvironmentSection(startedAt) {
92390
- const lines = [`- Current time: ${new Date(startedAt).toISOString()}`, `- Working directory: ${process.cwd()}`];
92484
+ function getEnvironmentSection() {
92485
+ const lines = [`- Platform: ${os.platform()} ${os.release()} (${os.arch()})`];
92391
92486
  if (process.env.PERSTACK_SANDBOX === "1") lines.push("- Sandbox: This is an isolated container environment (Ubuntu). You can freely install packages with `sudo apt-get install` and run arbitrary commands without affecting the host system.");
92392
92487
  return `Environment:\n${lines.join("\n")}`;
92393
92488
  }
92394
- function getDelegateMetaInstruction(startedAt) {
92489
+ function getDelegateMetaInstruction() {
92395
92490
  return import_dist.dedent`
92396
92491
  Before starting work, investigate the workspace and understand the current state. Then use the todo tool to create a plan of action. Work through the todos step by step, marking each completed as you go.
92397
92492
 
92398
92493
  When the task is complete, call attemptCompletion with a result parameter containing your final response.
92399
92494
  When you cannot help, call attemptCompletion without a result.
92400
92495
 
92401
- ${getEnvironmentSection(startedAt)}
92496
+ ${getEnvironmentSection()}
92402
92497
  `;
92403
92498
  }
92404
- function getCoordinatorMetaInstruction(startedAt) {
92499
+ function getCoordinatorMetaInstruction() {
92405
92500
  return import_dist.dedent`
92406
92501
  Your role:
92407
92502
  - Act as the coordinator for the given task: define the task, set goals, and delegate.
@@ -92437,14 +92532,14 @@ function getCoordinatorMetaInstruction(startedAt) {
92437
92532
  When the task is complete, call attemptCompletion with a result parameter containing your final response.
92438
92533
  When you cannot help, call attemptCompletion without a result.
92439
92534
 
92440
- ${getEnvironmentSection(startedAt)}
92535
+ ${getEnvironmentSection()}
92441
92536
  `;
92442
92537
  }
92443
- function createInstructionMessage(expert, startedAt) {
92538
+ function createInstructionMessage(expert) {
92444
92539
  const preamble = import_dist.dedent`
92445
92540
  You are Perstack, an AI expert that tackles tasks requested by users by utilizing all available tools.
92446
92541
 
92447
- ${isCoordinatorExpert(expert.name) ? getCoordinatorMetaInstruction(startedAt) : getDelegateMetaInstruction(startedAt)}
92542
+ ${isCoordinatorExpert(expert.name) ? getCoordinatorMetaInstruction() : getDelegateMetaInstruction()}
92448
92543
  `;
92449
92544
  const contents = [{
92450
92545
  id: createId(),
@@ -92502,7 +92597,7 @@ async function initLogic({ setting, checkpoint }) {
92502
92597
  if (!setting.model) throw new Error("Model is not resolved");
92503
92598
  return startRun(setting, checkpoint, {
92504
92599
  initialCheckpoint: checkpoint,
92505
- inputMessages: [createInstructionMessage(expert, setting.startedAt), createUserMessage([{
92600
+ inputMessages: [createInstructionMessage(expert), createUserMessage([{
92506
92601
  type: "textPart",
92507
92602
  text: setting.input.text
92508
92603
  }])],
@@ -93264,8 +93359,12 @@ var DelegationExecutor = class {
93264
93359
  }
93265
93360
  });
93266
93361
  if (parentOptions?.storeEvent) {
93267
- await parentOptions.storeEvent(startRunEvent).catch(() => {});
93268
- await parentOptions.storeEvent(stopRunByErrorEvent).catch(() => {});
93362
+ await parentOptions.storeEvent(startRunEvent).catch((e) => {
93363
+ console.warn(`Failed to store startRun event for ${expert.key}: ${e}`);
93364
+ });
93365
+ await parentOptions.storeEvent(stopRunByErrorEvent).catch((e) => {
93366
+ console.warn(`Failed to store stopRunByError event for ${expert.key}: ${e}`);
93367
+ });
93269
93368
  }
93270
93369
  if (parentOptions?.eventListener) {
93271
93370
  parentOptions.eventListener(startRunEvent);
@@ -93280,14 +93379,17 @@ var DelegationExecutor = class {
93280
93379
  deltaUsage: createEmptyUsage()
93281
93380
  };
93282
93381
  }
93283
- if (resultCheckpoint.status !== "completed") return {
93284
- toolCallId,
93285
- toolName,
93286
- expertKey: expert.key,
93287
- text: `Delegation to ${expert.key} ended with status: ${resultCheckpoint.status}`,
93288
- stepNumber: resultCheckpoint.stepNumber,
93289
- deltaUsage: resultCheckpoint.usage
93290
- };
93382
+ if (resultCheckpoint.status !== "completed") {
93383
+ const errorDetail = resultCheckpoint.error ? `: ${resultCheckpoint.error.name}: ${resultCheckpoint.error.message}` : "";
93384
+ return {
93385
+ toolCallId,
93386
+ toolName,
93387
+ expertKey: expert.key,
93388
+ text: `Delegation to ${expert.key} ended with status: ${resultCheckpoint.status}${errorDetail}`,
93389
+ stepNumber: resultCheckpoint.stepNumber,
93390
+ deltaUsage: resultCheckpoint.usage
93391
+ };
93392
+ }
93291
93393
  return this.extractDelegationResult(resultCheckpoint, toolCallId, toolName, expert.key);
93292
93394
  }
93293
93395
  extractDelegationResult(checkpoint, toolCallId, toolName, expertKey) {
@@ -118997,6 +119099,12 @@ const useInput = (inputHandler, options = {}) => {
118997
119099
  */
118998
119100
  const useApp = () => (0, import_react.useContext)(AppContext);
118999
119101
  //#endregion
119102
+ //#region ../../node_modules/.bun/ink@6.8.0+26a211c426f3f87c/node_modules/ink/build/hooks/use-stdout.js
119103
+ /**
119104
+ `useStdout` is a React hook that exposes the stdout stream where Ink renders your app.
119105
+ */
119106
+ const useStdout = () => (0, import_react.useContext)(StdoutContext);
119107
+ //#endregion
119000
119108
  //#region ../../packages/tui-components/src/utils/event-queue.ts
119001
119109
  const defaultErrorLogger = (message, error) => {
119002
119110
  console.error(message, error);
@@ -120979,7 +121087,10 @@ function processDelegationTreeEvent(state, event) {
120979
121087
  model: event.model,
120980
121088
  parentRunId,
120981
121089
  childRunIds: [],
120982
- totalTokens: 0
121090
+ totalTokens: 0,
121091
+ inputTokens: 0,
121092
+ outputTokens: 0,
121093
+ cachedInputTokens: 0
120983
121094
  };
120984
121095
  state.nodes.set(event.runId, node);
120985
121096
  if (parentRunId) {
@@ -121035,6 +121146,9 @@ function processDelegationTreeEvent(state, event) {
121035
121146
  node.actionLabel = label;
121036
121147
  node.actionFileArg = fileArg;
121037
121148
  node.totalTokens += event.usage.totalTokens;
121149
+ node.inputTokens += event.usage.inputTokens;
121150
+ node.outputTokens += event.usage.outputTokens;
121151
+ node.cachedInputTokens += event.usage.cachedInputTokens;
121038
121152
  state.jobTotalTokens += event.usage.totalTokens;
121039
121153
  state.jobReasoningTokens += event.usage.reasoningTokens;
121040
121154
  state.jobInputTokens += event.usage.inputTokens;
@@ -121062,6 +121176,9 @@ function processDelegationTreeEvent(state, event) {
121062
121176
  node.actionLabel = "Completed";
121063
121177
  node.actionFileArg = void 0;
121064
121178
  node.totalTokens += event.usage.totalTokens;
121179
+ node.inputTokens += event.usage.inputTokens;
121180
+ node.outputTokens += event.usage.outputTokens;
121181
+ node.cachedInputTokens += event.usage.cachedInputTokens;
121065
121182
  state.jobTotalTokens += event.usage.totalTokens;
121066
121183
  state.jobReasoningTokens += event.usage.reasoningTokens;
121067
121184
  state.jobInputTokens += event.usage.inputTokens;
@@ -121095,6 +121212,9 @@ function processDelegationTreeEvent(state, event) {
121095
121212
  const node = state.nodes.get(nodeId);
121096
121213
  if (node) {
121097
121214
  node.totalTokens += event.usage.totalTokens;
121215
+ node.inputTokens += event.usage.inputTokens;
121216
+ node.outputTokens += event.usage.outputTokens;
121217
+ node.cachedInputTokens += event.usage.cachedInputTokens;
121098
121218
  state.jobTotalTokens += event.usage.totalTokens;
121099
121219
  state.jobReasoningTokens += event.usage.reasoningTokens;
121100
121220
  state.jobInputTokens += event.usage.inputTokens;
@@ -121123,7 +121243,7 @@ function useDelegationTree() {
121123
121243
  formattedCachedInputTokens: formatTokenCount(state.jobCachedInputTokens),
121124
121244
  formattedOutputTokens: formatTokenCount(state.jobOutputTokens),
121125
121245
  providerName: state.providerName,
121126
- cacheHitRate: state.jobInputTokens + state.jobCachedInputTokens > 0 ? (state.jobCachedInputTokens / (state.jobInputTokens + state.jobCachedInputTokens) * 100).toFixed(2) : "0.00"
121246
+ cacheHitRate: state.jobInputTokens > 0 ? (state.jobCachedInputTokens / state.jobInputTokens * 100).toFixed(2) : "0.00"
121127
121247
  };
121128
121248
  }
121129
121249
  //#endregion
@@ -121154,7 +121274,7 @@ const useSpinner = ({ isActive }) => {
121154
121274
  };
121155
121275
  //#endregion
121156
121276
  //#region ../../packages/tui-components/src/execution/components/delegation-tree.tsx
121157
- function getUsageIcon(percent) {
121277
+ function getUsageIcon$2(percent) {
121158
121278
  if (percent >= 75) return USAGE_INDICATORS.FULL;
121159
121279
  if (percent >= 50) return USAGE_INDICATORS.HIGH;
121160
121280
  if (percent >= 25) return USAGE_INDICATORS.MEDIUM;
@@ -121199,7 +121319,7 @@ function TreeNodeLine({ flatNode, spinner }) {
121199
121319
  break;
121200
121320
  }
121201
121321
  const usagePercent = (node.contextWindowUsage * 100).toFixed(1);
121202
- const usageIcon = getUsageIcon(node.contextWindowUsage * 100);
121322
+ const usageIcon = getUsageIcon$2(node.contextWindowUsage * 100);
121203
121323
  const showUsage = node.status !== "completed";
121204
121324
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
121205
121325
  wrap: "truncate",
@@ -121252,14 +121372,13 @@ const DelegationTree = ({ state }) => {
121252
121372
  }, flatNode.node.runId)) });
121253
121373
  };
121254
121374
  //#endregion
121255
- //#region ../../packages/tui-components/src/execution/components/interface-panel.tsx
121256
- const InterfacePanel = ({ query, runStatus, onSubmit, delegationTreeState, runningCount, waitingCount, formattedReasoningTokens, formattedInputTokens, formattedCachedInputTokens, formattedOutputTokens, providerName, cacheHitRate, elapsedTime }) => {
121375
+ //#region ../../packages/tui-components/src/components/bottom-panel.tsx
121376
+ const BottomPanel = ({ children, onSubmit, canSubmit = true, inputPlaceholder }) => {
121257
121377
  const { input, handleInput } = useTextInput({
121258
121378
  onSubmit,
121259
- canSubmit: runStatus !== "running"
121379
+ canSubmit
121260
121380
  });
121261
121381
  useInput(handleInput);
121262
- const isWaiting = runStatus === "waiting";
121263
121382
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
121264
121383
  flexDirection: "column",
121265
121384
  borderStyle: "single",
@@ -121268,64 +121387,75 @@ const InterfacePanel = ({ query, runStatus, onSubmit, delegationTreeState, runni
121268
121387
  borderBottom: false,
121269
121388
  borderLeft: false,
121270
121389
  borderRight: false,
121271
- children: [
121272
- isWaiting ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
121390
+ children: [children, /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
121391
+ color: colors.muted,
121392
+ children: "> "
121393
+ }), input ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: input }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
121394
+ color: colors.accent,
121395
+ children: "_"
121396
+ })] }) : inputPlaceholder ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
121397
+ color: colors.muted,
121398
+ children: [inputPlaceholder, /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
121273
121399
  color: colors.accent,
121274
- children: "Waiting for query..."
121275
- }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
121276
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
121277
- color: colors.success,
121278
- wrap: "truncate",
121279
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
121280
- bold: true,
121281
- children: "Query: "
121282
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: query || "–" })]
121283
- }),
121284
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
121285
- dimColor: true,
121286
- children: [
121287
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [runningCount, " running"] }),
121288
- waitingCount > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [
121289
- " · ",
121290
- waitingCount,
121291
- " waiting"
121292
- ] }) : null,
121293
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: " · " }),
121294
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: elapsedTime }),
121295
- providerName ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [" · ", providerName] }) : null
121296
- ]
121297
- }),
121298
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
121299
- dimColor: true,
121300
- children: [
121301
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: "Tokens: " }),
121302
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: ["In ", formattedInputTokens] }),
121303
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [" · Out ", formattedOutputTokens] }),
121304
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [
121305
- " ",
121306
- "· Cache ",
121307
- formattedCachedInputTokens,
121308
- "/",
121309
- cacheHitRate,
121310
- "%"
121311
- ] }),
121312
- formattedReasoningTokens !== "0" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [" · Reasoning ", formattedReasoningTokens] }) : null
121313
- ]
121314
- })
121315
- ] }),
121316
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DelegationTree, { state: delegationTreeState }),
121317
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [
121318
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
121319
- color: colors.muted,
121320
- children: "> "
121321
- }),
121322
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: input }),
121323
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
121324
- color: colors.accent,
121325
- children: "_"
121326
- })
121327
- ] })
121328
- ]
121400
+ children: "_"
121401
+ })]
121402
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
121403
+ color: colors.accent,
121404
+ children: "_"
121405
+ })] })]
121406
+ });
121407
+ };
121408
+ //#endregion
121409
+ //#region ../../packages/tui-components/src/execution/components/interface-panel.tsx
121410
+ const InterfacePanel = ({ query, runStatus, onSubmit, delegationTreeState, runningCount, waitingCount, formattedReasoningTokens, formattedInputTokens, formattedCachedInputTokens, formattedOutputTokens, providerName, cacheHitRate, elapsedTime }) => {
121411
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(BottomPanel, {
121412
+ onSubmit,
121413
+ canSubmit: runStatus !== "running",
121414
+ children: [runStatus === "waiting" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
121415
+ color: colors.accent,
121416
+ children: "Waiting for query..."
121417
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
121418
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
121419
+ color: colors.success,
121420
+ wrap: "truncate",
121421
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
121422
+ bold: true,
121423
+ children: "Query: "
121424
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: query || "–" })]
121425
+ }),
121426
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
121427
+ dimColor: true,
121428
+ children: [
121429
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [runningCount, " running"] }),
121430
+ waitingCount > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [
121431
+ " · ",
121432
+ waitingCount,
121433
+ " waiting"
121434
+ ] }) : null,
121435
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: " · " }),
121436
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: elapsedTime }),
121437
+ providerName ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [" · ", providerName] }) : null
121438
+ ]
121439
+ }),
121440
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
121441
+ dimColor: true,
121442
+ children: [
121443
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: "Tokens: " }),
121444
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [
121445
+ "In ",
121446
+ formattedInputTokens,
121447
+ " (Cached ",
121448
+ formattedCachedInputTokens,
121449
+ ", Cache Hit",
121450
+ " ",
121451
+ cacheHitRate,
121452
+ "%)"
121453
+ ] }),
121454
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [" · Out ", formattedOutputTokens] }),
121455
+ formattedReasoningTokens !== "0" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [" · Reasoning ", formattedReasoningTokens] }) : null
121456
+ ]
121457
+ })
121458
+ ] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DelegationTree, { state: delegationTreeState })]
121329
121459
  });
121330
121460
  };
121331
121461
  //#endregion
@@ -121585,6 +121715,2500 @@ function renderExecution(params) {
121585
121715
  };
121586
121716
  }
121587
121717
  //#endregion
121718
+ //#region ../../packages/tui-components/src/log-viewer/build-run-tree.ts
121719
+ function parseExpertName(expertKey) {
121720
+ try {
121721
+ return parseExpertKey(expertKey).name;
121722
+ } catch {
121723
+ return expertKey;
121724
+ }
121725
+ }
121726
+ function createTreeNode(runId, expertKey, model, parentRunId) {
121727
+ return {
121728
+ runId,
121729
+ expertName: parseExpertName(expertKey),
121730
+ expertKey,
121731
+ status: "running",
121732
+ actionLabel: "",
121733
+ actionFileArg: void 0,
121734
+ contextWindowUsage: 0,
121735
+ model,
121736
+ parentRunId,
121737
+ childRunIds: [],
121738
+ totalTokens: 0,
121739
+ inputTokens: 0,
121740
+ outputTokens: 0,
121741
+ cachedInputTokens: 0
121742
+ };
121743
+ }
121744
+ function addNodeToTree(state, node) {
121745
+ state.nodes.set(node.runId, node);
121746
+ if (node.parentRunId) {
121747
+ const parent = state.nodes.get(node.parentRunId);
121748
+ if (parent && !parent.childRunIds.includes(node.runId)) parent.childRunIds.push(node.runId);
121749
+ } else if (!state.rootRunId) state.rootRunId = node.runId;
121750
+ }
121751
+ function accumulateTokens(node, usage) {
121752
+ node.totalTokens += usage.totalTokens;
121753
+ node.inputTokens += usage.inputTokens;
121754
+ node.outputTokens += usage.outputTokens;
121755
+ node.cachedInputTokens += usage.cachedInputTokens;
121756
+ }
121757
+ function extractQueryFromStartRun(event) {
121758
+ if (event.type !== "startRun") return void 0;
121759
+ for (const msg of event.inputMessages) if (msg.type === "userMessage") {
121760
+ for (const part of msg.contents) if ("text" in part && part.text) return part.text.replace(/\n/g, " ");
121761
+ }
121762
+ }
121763
+ /**
121764
+ * Build a delegation tree from a list of RunEvents.
121765
+ *
121766
+ * Unlike the live execution tree (use-delegation-tree.ts), this does NOT merge
121767
+ * resume runs into their original runs. Each resume run becomes a separate node
121768
+ * in the tree, so that its checkpoints and events are individually accessible.
121769
+ *
121770
+ * The tree structure for a delegate-and-resume cycle looks like:
121771
+ *
121772
+ * Coordinator (initial run)
121773
+ * ├── Worker-A (delegate)
121774
+ * └── Worker-B (delegate)
121775
+ * Coordinator (resumed run) ← separate node, same expertKey
121776
+ *
121777
+ * Resume nodes are tracked in `resumeNodes` so the graph renderer can draw
121778
+ * merge lines (`|/`) before them.
121779
+ */
121780
+ function buildRunTreeFromEvents(events) {
121781
+ const treeState = createInitialDelegationTreeState();
121782
+ const runQueries = /* @__PURE__ */ new Map();
121783
+ const runStats = /* @__PURE__ */ new Map();
121784
+ const delegationGroups = /* @__PURE__ */ new Map();
121785
+ const currentDelegatingParent = /* @__PURE__ */ new Map();
121786
+ const parentToChildren = /* @__PURE__ */ new Map();
121787
+ const parentCompletedChildren = /* @__PURE__ */ new Map();
121788
+ const awaitingResume = /* @__PURE__ */ new Set();
121789
+ const resumeNodes = /* @__PURE__ */ new Map();
121790
+ const runIdToNodeId = /* @__PURE__ */ new Map();
121791
+ function resolveParentNodeId(runId) {
121792
+ return runIdToNodeId.get(runId) ?? runId;
121793
+ }
121794
+ function recordStats(runId, stepNumber) {
121795
+ const stats = runStats.get(runId) ?? {
121796
+ eventCount: 0,
121797
+ stepCount: 0
121798
+ };
121799
+ stats.eventCount++;
121800
+ if (stepNumber > stats.stepCount) stats.stepCount = stepNumber;
121801
+ runStats.set(runId, stats);
121802
+ }
121803
+ function onChildCompleted(childRunId) {
121804
+ const childNode = treeState.nodes.get(childRunId);
121805
+ if (!childNode?.parentRunId) return;
121806
+ const parentRunId = childNode.parentRunId;
121807
+ const count = (parentCompletedChildren.get(parentRunId) ?? 0) + 1;
121808
+ parentCompletedChildren.set(parentRunId, count);
121809
+ if (count >= (parentToChildren.get(parentRunId)?.size ?? 0)) awaitingResume.add(parentRunId);
121810
+ }
121811
+ for (const event of events) {
121812
+ recordStats(event.runId, event.stepNumber);
121813
+ switch (event.type) {
121814
+ case "startRun": {
121815
+ const parentRawRunId = event.initialCheckpoint.delegatedBy?.runId;
121816
+ const parentRunId = parentRawRunId ? resolveParentNodeId(parentRawRunId) : void 0;
121817
+ const node = createTreeNode(event.runId, event.expertKey, event.model, parentRunId);
121818
+ node.contextWindowUsage = event.initialCheckpoint.contextWindowUsage ?? 0;
121819
+ addNodeToTree(treeState, node);
121820
+ runIdToNodeId.set(event.runId, event.runId);
121821
+ if (parentRunId) {
121822
+ const children = parentToChildren.get(parentRunId) ?? /* @__PURE__ */ new Set();
121823
+ children.add(event.runId);
121824
+ parentToChildren.set(parentRunId, children);
121825
+ const currentGroup = currentDelegatingParent.get(parentRunId);
121826
+ if (currentGroup) currentGroup.push(event.runId);
121827
+ }
121828
+ const query = extractQueryFromStartRun(event);
121829
+ if (query) runQueries.set(event.runId, query);
121830
+ break;
121831
+ }
121832
+ case "resumeFromStop": {
121833
+ let originalRunId;
121834
+ for (const candidateRunId of awaitingResume) {
121835
+ const candidateNode = treeState.nodes.get(candidateRunId);
121836
+ if (candidateNode && candidateNode.expertKey === event.expertKey) {
121837
+ originalRunId = candidateRunId;
121838
+ break;
121839
+ }
121840
+ }
121841
+ if (!originalRunId) {
121842
+ for (const [nodeId, node] of treeState.nodes) if (node.expertKey === event.expertKey && node.status === "suspending") {
121843
+ originalRunId = nodeId;
121844
+ break;
121845
+ }
121846
+ }
121847
+ if (originalRunId) awaitingResume.delete(originalRunId);
121848
+ const parentRunId = (originalRunId ? treeState.nodes.get(originalRunId) : void 0)?.parentRunId;
121849
+ const node = createTreeNode(event.runId, event.expertKey, event.model, parentRunId);
121850
+ if (event.checkpoint.contextWindowUsage !== void 0) node.contextWindowUsage = event.checkpoint.contextWindowUsage;
121851
+ addNodeToTree(treeState, node);
121852
+ if (!parentRunId && originalRunId) {
121853
+ const origNode = treeState.nodes.get(originalRunId);
121854
+ if (origNode && !origNode.childRunIds.includes(event.runId)) origNode.childRunIds.push(event.runId);
121855
+ }
121856
+ runIdToNodeId.set(event.runId, event.runId);
121857
+ if (originalRunId) resumeNodes.set(event.runId, originalRunId);
121858
+ if (originalRunId) currentDelegatingParent.delete(originalRunId);
121859
+ break;
121860
+ }
121861
+ case "stopRunByDelegate": {
121862
+ const node = treeState.nodes.get(event.runId);
121863
+ if (node) {
121864
+ node.status = "suspending";
121865
+ if (event.checkpoint.contextWindowUsage !== void 0) node.contextWindowUsage = event.checkpoint.contextWindowUsage;
121866
+ const newGroup = [];
121867
+ const groups = delegationGroups.get(event.runId) ?? [];
121868
+ groups.push(newGroup);
121869
+ delegationGroups.set(event.runId, groups);
121870
+ currentDelegatingParent.set(event.runId, newGroup);
121871
+ }
121872
+ break;
121873
+ }
121874
+ case "callTools": {
121875
+ const node = treeState.nodes.get(event.runId);
121876
+ if (node) accumulateTokens(node, event.usage);
121877
+ break;
121878
+ }
121879
+ case "completeRun": {
121880
+ const node = treeState.nodes.get(event.runId);
121881
+ if (node) {
121882
+ node.status = "completed";
121883
+ accumulateTokens(node, event.usage);
121884
+ if (event.checkpoint.contextWindowUsage !== void 0) node.contextWindowUsage = event.checkpoint.contextWindowUsage;
121885
+ }
121886
+ onChildCompleted(event.runId);
121887
+ break;
121888
+ }
121889
+ case "stopRunByError": {
121890
+ const node = treeState.nodes.get(event.runId);
121891
+ if (node) {
121892
+ node.status = "error";
121893
+ if (event.checkpoint.contextWindowUsage !== void 0) node.contextWindowUsage = event.checkpoint.contextWindowUsage;
121894
+ }
121895
+ onChildCompleted(event.runId);
121896
+ break;
121897
+ }
121898
+ case "retry": {
121899
+ const node = treeState.nodes.get(event.runId);
121900
+ if (node) accumulateTokens(node, event.usage);
121901
+ break;
121902
+ }
121903
+ case "continueToNextStep": {
121904
+ const node = treeState.nodes.get(event.runId);
121905
+ if (node && event.nextCheckpoint.contextWindowUsage !== void 0) node.contextWindowUsage = event.nextCheckpoint.contextWindowUsage;
121906
+ break;
121907
+ }
121908
+ case "resolveToolResults": break;
121909
+ }
121910
+ }
121911
+ return {
121912
+ treeState,
121913
+ runQueries,
121914
+ runStats,
121915
+ delegationGroups,
121916
+ resumeNodes
121917
+ };
121918
+ }
121919
+ //#endregion
121920
+ //#region ../../packages/tui-components/src/log-viewer/components/log-info-content.tsx
121921
+ function formatDuration$1(startedAt, finishedAt) {
121922
+ const end = finishedAt ?? Date.now();
121923
+ const seconds = Math.floor((end - startedAt) / 1e3);
121924
+ if (seconds < 60) return `${seconds}s`;
121925
+ const minutes = Math.floor(seconds / 60);
121926
+ const remainingSeconds = seconds % 60;
121927
+ if (minutes < 60) return `${minutes}m ${remainingSeconds}s`;
121928
+ return `${Math.floor(minutes / 60)}h ${minutes % 60}m`;
121929
+ }
121930
+ function formatShortDate$1(timestamp) {
121931
+ const d = new Date(timestamp);
121932
+ return `${d.getMonth() + 1}/${d.getDate()} ${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
121933
+ }
121934
+ function getUsageIcon$1(ratio) {
121935
+ const percent = ratio * 100;
121936
+ if (percent >= 75) return USAGE_INDICATORS.FULL;
121937
+ if (percent >= 50) return USAGE_INDICATORS.HIGH;
121938
+ if (percent >= 25) return USAGE_INDICATORS.MEDIUM;
121939
+ if (percent >= 5) return USAGE_INDICATORS.LOW;
121940
+ return USAGE_INDICATORS.EMPTY;
121941
+ }
121942
+ function statusColor$3(status) {
121943
+ if (status === "completed") return colors.primary;
121944
+ if (status === "error") return colors.destructive;
121945
+ if (status === "suspending") return colors.primary;
121946
+ return colors.muted;
121947
+ }
121948
+ function JobInfoContent({ job }) {
121949
+ const duration = formatDuration$1(job.startedAt, job.finishedAt);
121950
+ const cacheRate = job.usage.inputTokens > 0 ? (job.usage.cachedInputTokens ?? 0) / job.usage.inputTokens * 100 : 0;
121951
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
121952
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
121953
+ wrap: "truncate",
121954
+ children: [
121955
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
121956
+ bold: true,
121957
+ children: job.coordinatorExpertKey
121958
+ }),
121959
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
121960
+ dimColor: true,
121961
+ children: " · "
121962
+ }),
121963
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
121964
+ color: statusColor$3(job.status),
121965
+ children: job.status
121966
+ }),
121967
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
121968
+ dimColor: true,
121969
+ children: " · "
121970
+ }),
121971
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: job.id })
121972
+ ]
121973
+ }),
121974
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
121975
+ dimColor: true,
121976
+ wrap: "truncate",
121977
+ children: [
121978
+ job.totalSteps,
121979
+ " steps · ",
121980
+ duration,
121981
+ " · Started ",
121982
+ formatShortDate$1(job.startedAt),
121983
+ job.finishedAt ? ` · Finished ${formatShortDate$1(job.finishedAt)}` : ""
121984
+ ]
121985
+ }),
121986
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
121987
+ dimColor: true,
121988
+ wrap: "truncate",
121989
+ children: [
121990
+ "Tokens: In ",
121991
+ formatTokenCount(job.usage.inputTokens),
121992
+ (job.usage.cachedInputTokens ?? 0) > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
121993
+ " ",
121994
+ "(Cached ",
121995
+ formatTokenCount(job.usage.cachedInputTokens ?? 0),
121996
+ ", Cache Hit",
121997
+ " ",
121998
+ cacheRate.toFixed(2),
121999
+ "%)"
122000
+ ] }) : null,
122001
+ " · Out ",
122002
+ formatTokenCount(job.usage.outputTokens)
122003
+ ]
122004
+ })
122005
+ ] });
122006
+ }
122007
+ function RunInfoContent({ run, treeNode, providerName, checkpointCount, eventCount }) {
122008
+ const parts = [];
122009
+ if (providerName) parts.push(providerName);
122010
+ if (treeNode?.model) parts.push(treeNode.model);
122011
+ if (checkpointCount !== void 0) parts.push(`${checkpointCount} steps`);
122012
+ if (eventCount !== void 0) parts.push(`${eventCount} events`);
122013
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
122014
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
122015
+ wrap: "truncate",
122016
+ children: [
122017
+ run.expertKey ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122018
+ bold: true,
122019
+ children: run.expertKey
122020
+ }) : null,
122021
+ run.expertKey ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122022
+ dimColor: true,
122023
+ children: " · "
122024
+ }) : null,
122025
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122026
+ color: treeNode ? statusColor$3(treeNode.status) : void 0,
122027
+ children: treeNode ? treeNode.status : "unknown"
122028
+ })
122029
+ ]
122030
+ }),
122031
+ parts.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122032
+ dimColor: true,
122033
+ wrap: "truncate",
122034
+ children: parts.join(" · ")
122035
+ }) : null,
122036
+ treeNode ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
122037
+ dimColor: true,
122038
+ wrap: "truncate",
122039
+ children: [
122040
+ "Tokens: In ",
122041
+ formatTokenCount(treeNode.inputTokens),
122042
+ treeNode.cachedInputTokens > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
122043
+ " ",
122044
+ "(Cached ",
122045
+ formatTokenCount(treeNode.cachedInputTokens),
122046
+ ", Cache Hit",
122047
+ " ",
122048
+ (treeNode.inputTokens > 0 ? treeNode.cachedInputTokens / treeNode.inputTokens * 100 : 0).toFixed(2),
122049
+ "%)"
122050
+ ] }) : null,
122051
+ " · Out ",
122052
+ formatTokenCount(treeNode.outputTokens)
122053
+ ]
122054
+ }) : null
122055
+ ] });
122056
+ }
122057
+ function CheckpointInfoContent({ checkpoint }) {
122058
+ const cp = checkpoint;
122059
+ const usageIcon = cp.contextWindowUsage !== void 0 ? ` ${getUsageIcon$1(cp.contextWindowUsage)}` : "";
122060
+ const usagePercent = cp.contextWindowUsage !== void 0 ? ` ${(cp.contextWindowUsage * 100).toFixed(1)}%` : "";
122061
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
122062
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
122063
+ wrap: "truncate",
122064
+ children: [
122065
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
122066
+ bold: true,
122067
+ children: ["Step ", cp.stepNumber]
122068
+ }),
122069
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122070
+ dimColor: true,
122071
+ children: " · "
122072
+ }),
122073
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122074
+ color: statusColor$3(cp.status),
122075
+ children: cp.status
122076
+ }),
122077
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122078
+ dimColor: true,
122079
+ children: " · "
122080
+ }),
122081
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: cp.expert.key }),
122082
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122083
+ dimColor: true,
122084
+ children: " · "
122085
+ }),
122086
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: cp.id })
122087
+ ]
122088
+ }),
122089
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
122090
+ dimColor: true,
122091
+ wrap: "truncate",
122092
+ children: [
122093
+ cp.messages.length,
122094
+ " msgs",
122095
+ usageIcon ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
122096
+ " ·",
122097
+ usageIcon,
122098
+ usagePercent
122099
+ ] }) : null,
122100
+ " · ",
122101
+ "Tokens: In ",
122102
+ formatTokenCount(cp.usage.inputTokens),
122103
+ " · Out",
122104
+ " ",
122105
+ formatTokenCount(cp.usage.outputTokens)
122106
+ ]
122107
+ }),
122108
+ cp.error ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
122109
+ color: colors.destructive,
122110
+ wrap: "truncate",
122111
+ children: [
122112
+ "Error: ",
122113
+ cp.error.name,
122114
+ ": ",
122115
+ cp.error.message
122116
+ ]
122117
+ }) : null,
122118
+ cp.delegateTo && cp.delegateTo.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
122119
+ dimColor: true,
122120
+ wrap: "truncate",
122121
+ children: ["Delegates: ", cp.delegateTo.map((d) => d.expert.key).join(", ")]
122122
+ }) : null
122123
+ ] });
122124
+ }
122125
+ function EventInfoContent({ event }) {
122126
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
122127
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
122128
+ wrap: "truncate",
122129
+ children: [
122130
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122131
+ bold: true,
122132
+ children: event.type
122133
+ }),
122134
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122135
+ dimColor: true,
122136
+ children: " · "
122137
+ }),
122138
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: ["Step ", event.stepNumber] }),
122139
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122140
+ dimColor: true,
122141
+ children: " · "
122142
+ }),
122143
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: event.expertKey }),
122144
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122145
+ dimColor: true,
122146
+ children: " · "
122147
+ }),
122148
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: event.id })
122149
+ ]
122150
+ }),
122151
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
122152
+ dimColor: true,
122153
+ wrap: "truncate",
122154
+ children: [
122155
+ formatShortDate$1(event.timestamp),
122156
+ " · run:",
122157
+ event.runId
122158
+ ]
122159
+ }),
122160
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EventSummaryLine, { event })
122161
+ ] });
122162
+ }
122163
+ function EventSummaryLine({ event }) {
122164
+ switch (event.type) {
122165
+ case "callTools": return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
122166
+ dimColor: true,
122167
+ wrap: "truncate",
122168
+ children: ["Tools: ", event.toolCalls.map((tc) => `${tc.skillName}/${tc.toolName}`).join(", ")]
122169
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
122170
+ dimColor: true,
122171
+ wrap: "truncate",
122172
+ children: [
122173
+ "Tokens: In ",
122174
+ formatTokenCount(event.usage.inputTokens),
122175
+ " · Out",
122176
+ " ",
122177
+ formatTokenCount(event.usage.outputTokens)
122178
+ ]
122179
+ })] });
122180
+ case "completeRun": return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
122181
+ dimColor: true,
122182
+ wrap: "truncate",
122183
+ children: ["Result: ", event.text]
122184
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
122185
+ dimColor: true,
122186
+ wrap: "truncate",
122187
+ children: [
122188
+ "Tokens: In ",
122189
+ formatTokenCount(event.usage.inputTokens),
122190
+ " · Out",
122191
+ " ",
122192
+ formatTokenCount(event.usage.outputTokens)
122193
+ ]
122194
+ })] });
122195
+ case "stopRunByError": return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
122196
+ color: colors.destructive,
122197
+ wrap: "truncate",
122198
+ children: [
122199
+ "Error: ",
122200
+ event.error.name,
122201
+ ": ",
122202
+ event.error.message
122203
+ ]
122204
+ });
122205
+ case "retry": return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
122206
+ color: colors.warn,
122207
+ wrap: "truncate",
122208
+ children: ["Reason: ", event.reason]
122209
+ });
122210
+ case "startRun": return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
122211
+ dimColor: true,
122212
+ wrap: "truncate",
122213
+ children: [
122214
+ "Model: ",
122215
+ event.model,
122216
+ " · ",
122217
+ event.inputMessages.length,
122218
+ " input messages"
122219
+ ]
122220
+ });
122221
+ case "stopRunByDelegate": return event.checkpoint.delegateTo ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
122222
+ dimColor: true,
122223
+ wrap: "truncate",
122224
+ children: ["Delegates: ", event.checkpoint.delegateTo.map((d) => d.expert.key).join(", ")]
122225
+ }) : null;
122226
+ case "startGeneration": return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
122227
+ dimColor: true,
122228
+ wrap: "truncate",
122229
+ children: [event.messages.length, " messages"]
122230
+ });
122231
+ default: return null;
122232
+ }
122233
+ }
122234
+ //#endregion
122235
+ //#region ../../packages/tui-components/src/log-viewer/screens/checkpoint-detail.tsx
122236
+ const CheckpointDetailScreen = ({ checkpoint, onBack }) => {
122237
+ useInput((char, key) => {
122238
+ if (key.escape || char === "b") onBack();
122239
+ });
122240
+ const cp = checkpoint;
122241
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122242
+ flexDirection: "column",
122243
+ flexGrow: 1,
122244
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122245
+ color: colors.accent,
122246
+ bold: true,
122247
+ children: "Checkpoint Detail"
122248
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122249
+ color: colors.muted,
122250
+ children: " b:Back q:Quit"
122251
+ })] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122252
+ flexDirection: "column",
122253
+ paddingX: 1,
122254
+ children: [
122255
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122256
+ gap: 1,
122257
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122258
+ color: colors.accent,
122259
+ children: "ID:"
122260
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: cp.id })]
122261
+ }),
122262
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122263
+ gap: 1,
122264
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122265
+ color: colors.accent,
122266
+ children: "Status:"
122267
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122268
+ color: cp.status === "completed" ? colors.success : cp.status === "proceeding" ? colors.accent : colors.destructive,
122269
+ children: cp.status
122270
+ })]
122271
+ }),
122272
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122273
+ gap: 1,
122274
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122275
+ color: colors.accent,
122276
+ children: "Step:"
122277
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: cp.stepNumber })]
122278
+ }),
122279
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122280
+ gap: 1,
122281
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122282
+ color: colors.accent,
122283
+ children: "Expert:"
122284
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [
122285
+ cp.expert.name,
122286
+ " (",
122287
+ cp.expert.key,
122288
+ "@",
122289
+ cp.expert.version,
122290
+ ")"
122291
+ ] })]
122292
+ }),
122293
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122294
+ gap: 1,
122295
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122296
+ color: colors.accent,
122297
+ children: "Messages:"
122298
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: cp.messages.length })]
122299
+ }),
122300
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122301
+ gap: 1,
122302
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122303
+ color: colors.accent,
122304
+ children: "Input Tokens:"
122305
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: cp.usage.inputTokens.toLocaleString() })]
122306
+ }),
122307
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122308
+ gap: 1,
122309
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122310
+ color: colors.accent,
122311
+ children: "Output Tokens:"
122312
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: cp.usage.outputTokens.toLocaleString() })]
122313
+ }),
122314
+ cp.contextWindow && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122315
+ gap: 1,
122316
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122317
+ color: colors.accent,
122318
+ children: "Context Window:"
122319
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: cp.contextWindow.toLocaleString() })]
122320
+ }),
122321
+ cp.contextWindowUsage !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122322
+ gap: 1,
122323
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122324
+ color: colors.accent,
122325
+ children: "Context Usage:"
122326
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [(cp.contextWindowUsage * 100).toFixed(1), "%"] })]
122327
+ }),
122328
+ cp.error && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122329
+ flexDirection: "column",
122330
+ children: [
122331
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122332
+ color: colors.destructive,
122333
+ bold: true,
122334
+ children: "Error:"
122335
+ }),
122336
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
122337
+ color: colors.destructive,
122338
+ children: [
122339
+ cp.error.name,
122340
+ ": ",
122341
+ truncateText(cp.error.message, 100)
122342
+ ]
122343
+ }),
122344
+ cp.error.statusCode && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
122345
+ color: colors.muted,
122346
+ children: ["Status Code: ", cp.error.statusCode]
122347
+ }),
122348
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
122349
+ color: colors.muted,
122350
+ children: ["Retryable: ", cp.error.isRetryable ? "yes" : "no"]
122351
+ })
122352
+ ]
122353
+ }),
122354
+ cp.delegateTo && cp.delegateTo.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122355
+ flexDirection: "column",
122356
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122357
+ color: colors.accent,
122358
+ bold: true,
122359
+ children: "Delegating to:"
122360
+ }), cp.delegateTo.map((d) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
122361
+ color: colors.muted,
122362
+ children: [
122363
+ "- ",
122364
+ d.expert.key,
122365
+ ": ",
122366
+ truncateText(d.query, 60)
122367
+ ]
122368
+ }, d.toolCallId))]
122369
+ }),
122370
+ cp.delegatedBy && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122371
+ flexDirection: "column",
122372
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122373
+ color: colors.accent,
122374
+ bold: true,
122375
+ children: "Delegated by:"
122376
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
122377
+ color: colors.muted,
122378
+ children: [
122379
+ cp.delegatedBy.expert.key,
122380
+ " via ",
122381
+ cp.delegatedBy.toolName
122382
+ ]
122383
+ })]
122384
+ }),
122385
+ cp.pendingToolCalls && cp.pendingToolCalls.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122386
+ gap: 1,
122387
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122388
+ color: colors.accent,
122389
+ children: "Pending Tool Calls:"
122390
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: cp.pendingToolCalls.length })]
122391
+ })
122392
+ ]
122393
+ })]
122394
+ });
122395
+ };
122396
+ //#endregion
122397
+ //#region ../../packages/tui-components/src/log-viewer/screens/event-detail.tsx
122398
+ const EventDetailScreen = ({ event, onBack }) => {
122399
+ useInput((char, key) => {
122400
+ if (key.escape || char === "b") onBack();
122401
+ });
122402
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122403
+ flexDirection: "column",
122404
+ flexGrow: 1,
122405
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122406
+ color: colors.accent,
122407
+ bold: true,
122408
+ children: "Event Detail"
122409
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122410
+ color: colors.muted,
122411
+ children: " b:Back q:Quit"
122412
+ })] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122413
+ flexDirection: "column",
122414
+ paddingX: 1,
122415
+ children: [
122416
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122417
+ gap: 1,
122418
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122419
+ color: colors.accent,
122420
+ children: "ID:"
122421
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: event.id })]
122422
+ }),
122423
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122424
+ gap: 1,
122425
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122426
+ color: colors.accent,
122427
+ children: "Type:"
122428
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122429
+ bold: true,
122430
+ children: event.type
122431
+ })]
122432
+ }),
122433
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122434
+ gap: 1,
122435
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122436
+ color: colors.accent,
122437
+ children: "Step:"
122438
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: event.stepNumber })]
122439
+ }),
122440
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122441
+ gap: 1,
122442
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122443
+ color: colors.accent,
122444
+ children: "Expert:"
122445
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: event.expertKey })]
122446
+ }),
122447
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122448
+ gap: 1,
122449
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122450
+ color: colors.accent,
122451
+ children: "Run:"
122452
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: event.runId })]
122453
+ }),
122454
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122455
+ gap: 1,
122456
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122457
+ color: colors.accent,
122458
+ children: "Job:"
122459
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: event.jobId })]
122460
+ }),
122461
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122462
+ gap: 1,
122463
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122464
+ color: colors.accent,
122465
+ children: "Timestamp:"
122466
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: formatTimestamp$1(event.timestamp) })]
122467
+ }),
122468
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, {
122469
+ marginTop: 1,
122470
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122471
+ color: colors.accent,
122472
+ bold: true,
122473
+ children: "Payload:"
122474
+ })
122475
+ }),
122476
+ renderEventPayload(event)
122477
+ ]
122478
+ })]
122479
+ });
122480
+ };
122481
+ function renderEventPayload(event) {
122482
+ switch (event.type) {
122483
+ case "startRun": return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122484
+ flexDirection: "column",
122485
+ children: [
122486
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122487
+ gap: 1,
122488
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122489
+ color: colors.muted,
122490
+ children: "Model:"
122491
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: event.model })]
122492
+ }),
122493
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122494
+ gap: 1,
122495
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122496
+ color: colors.muted,
122497
+ children: "Input Messages:"
122498
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: event.inputMessages.length })]
122499
+ }),
122500
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122501
+ gap: 1,
122502
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122503
+ color: colors.muted,
122504
+ children: "Checkpoint Status:"
122505
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: event.initialCheckpoint.status })]
122506
+ })
122507
+ ]
122508
+ });
122509
+ case "callTools": return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122510
+ flexDirection: "column",
122511
+ children: [
122512
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122513
+ gap: 1,
122514
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122515
+ color: colors.muted,
122516
+ children: "Tool Calls:"
122517
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: event.toolCalls.length })]
122518
+ }),
122519
+ event.toolCalls.map((tc) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122520
+ flexDirection: "column",
122521
+ marginLeft: 2,
122522
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
122523
+ color: colors.primary,
122524
+ children: [
122525
+ tc.skillName,
122526
+ "/",
122527
+ tc.toolName,
122528
+ " (",
122529
+ tc.id,
122530
+ ")"
122531
+ ]
122532
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122533
+ color: colors.muted,
122534
+ children: truncateText(JSON.stringify(tc.args), 120)
122535
+ })]
122536
+ }, tc.id)),
122537
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122538
+ gap: 1,
122539
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122540
+ color: colors.muted,
122541
+ children: "Input Tokens:"
122542
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: event.usage.inputTokens.toLocaleString() })]
122543
+ }),
122544
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122545
+ gap: 1,
122546
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122547
+ color: colors.muted,
122548
+ children: "Output Tokens:"
122549
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: event.usage.outputTokens.toLocaleString() })]
122550
+ })
122551
+ ]
122552
+ });
122553
+ case "completeRun": return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122554
+ flexDirection: "column",
122555
+ children: [
122556
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122557
+ gap: 1,
122558
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122559
+ color: colors.muted,
122560
+ children: "Result:"
122561
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: truncateText(event.text, 200) })]
122562
+ }),
122563
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122564
+ gap: 1,
122565
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122566
+ color: colors.muted,
122567
+ children: "Input Tokens:"
122568
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: event.usage.inputTokens.toLocaleString() })]
122569
+ }),
122570
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122571
+ gap: 1,
122572
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122573
+ color: colors.muted,
122574
+ children: "Output Tokens:"
122575
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: event.usage.outputTokens.toLocaleString() })]
122576
+ })
122577
+ ]
122578
+ });
122579
+ case "stopRunByError": return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122580
+ flexDirection: "column",
122581
+ children: [
122582
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122583
+ gap: 1,
122584
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122585
+ color: colors.destructive,
122586
+ children: "Error:"
122587
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
122588
+ color: colors.destructive,
122589
+ children: [
122590
+ event.error.name,
122591
+ ": ",
122592
+ event.error.message
122593
+ ]
122594
+ })]
122595
+ }),
122596
+ event.error.statusCode && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122597
+ gap: 1,
122598
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122599
+ color: colors.muted,
122600
+ children: "Status Code:"
122601
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: event.error.statusCode })]
122602
+ }),
122603
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122604
+ gap: 1,
122605
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122606
+ color: colors.muted,
122607
+ children: "Retryable:"
122608
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: event.error.isRetryable ? "yes" : "no" })]
122609
+ })
122610
+ ]
122611
+ });
122612
+ case "retry": return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, {
122613
+ flexDirection: "column",
122614
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122615
+ gap: 1,
122616
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122617
+ color: colors.warn,
122618
+ children: "Reason:"
122619
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: event.reason })]
122620
+ })
122621
+ });
122622
+ case "startGeneration": return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, {
122623
+ flexDirection: "column",
122624
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122625
+ gap: 1,
122626
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122627
+ color: colors.muted,
122628
+ children: "Messages:"
122629
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: event.messages.length })]
122630
+ })
122631
+ });
122632
+ case "stopRunByDelegate": return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122633
+ flexDirection: "column",
122634
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122635
+ gap: 1,
122636
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122637
+ color: colors.muted,
122638
+ children: "Step:"
122639
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: event.step.stepNumber })]
122640
+ }), event.checkpoint.delegateTo && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122641
+ flexDirection: "column",
122642
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122643
+ color: colors.muted,
122644
+ children: "Delegates:"
122645
+ }), event.checkpoint.delegateTo.map((d) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
122646
+ color: colors.primary,
122647
+ children: [
122648
+ "- ",
122649
+ d.expert.key,
122650
+ ": ",
122651
+ truncateText(d.query, 60)
122652
+ ]
122653
+ }, d.toolCallId))]
122654
+ })]
122655
+ });
122656
+ default: return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122657
+ color: colors.muted,
122658
+ children: truncateText(JSON.stringify(event, null, 2), 500)
122659
+ });
122660
+ }
122661
+ }
122662
+ //#endregion
122663
+ //#region ../../packages/tui-components/src/log-viewer/screens/job-detail.tsx
122664
+ const JobDetailScreen = ({ job, onBack, onViewRuns }) => {
122665
+ useInput((char, key) => {
122666
+ if (key.escape || char === "b") {
122667
+ onBack();
122668
+ return;
122669
+ }
122670
+ if (key.return || char === "r") onViewRuns(job);
122671
+ });
122672
+ const statusColor = job.status === "completed" ? colors.success : job.status === "running" ? colors.accent : colors.destructive;
122673
+ const duration = job.finishedAt && job.startedAt ? `${((job.finishedAt - job.startedAt) / 1e3).toFixed(1)}s` : "running...";
122674
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122675
+ flexDirection: "column",
122676
+ flexGrow: 1,
122677
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122678
+ color: colors.accent,
122679
+ bold: true,
122680
+ children: "Job Detail"
122681
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122682
+ color: colors.muted,
122683
+ children: " Enter/r:View Runs b:Back q:Quit"
122684
+ })] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122685
+ flexDirection: "column",
122686
+ paddingX: 1,
122687
+ children: [
122688
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122689
+ gap: 1,
122690
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122691
+ color: colors.accent,
122692
+ children: "ID:"
122693
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: job.id })]
122694
+ }),
122695
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122696
+ gap: 1,
122697
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122698
+ color: colors.accent,
122699
+ children: "Status:"
122700
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122701
+ color: statusColor,
122702
+ children: job.status
122703
+ })]
122704
+ }),
122705
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122706
+ gap: 1,
122707
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122708
+ color: colors.accent,
122709
+ children: "Expert:"
122710
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: job.coordinatorExpertKey })]
122711
+ }),
122712
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122713
+ gap: 1,
122714
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122715
+ color: colors.accent,
122716
+ children: "Version:"
122717
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: job.runtimeVersion })]
122718
+ }),
122719
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122720
+ gap: 1,
122721
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122722
+ color: colors.accent,
122723
+ children: "Steps:"
122724
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: job.totalSteps })]
122725
+ }),
122726
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122727
+ gap: 1,
122728
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122729
+ color: colors.accent,
122730
+ children: "Duration:"
122731
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: duration })]
122732
+ }),
122733
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122734
+ gap: 1,
122735
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122736
+ color: colors.accent,
122737
+ children: "Started:"
122738
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: formatTimestamp$1(job.startedAt) })]
122739
+ }),
122740
+ job.finishedAt && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122741
+ gap: 1,
122742
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122743
+ color: colors.accent,
122744
+ children: "Finished:"
122745
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: formatTimestamp$1(job.finishedAt) })]
122746
+ }),
122747
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122748
+ gap: 1,
122749
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122750
+ color: colors.accent,
122751
+ children: "Input Tokens:"
122752
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: job.usage.inputTokens.toLocaleString() })]
122753
+ }),
122754
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122755
+ gap: 1,
122756
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122757
+ color: colors.accent,
122758
+ children: "Output Tokens:"
122759
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: job.usage.outputTokens.toLocaleString() })]
122760
+ })
122761
+ ]
122762
+ })]
122763
+ });
122764
+ };
122765
+ //#endregion
122766
+ //#region ../../packages/tui-components/src/log-viewer/screens/job-list.tsx
122767
+ const MAX_VISIBLE = 10;
122768
+ function formatDuration(startedAt, finishedAt) {
122769
+ const end = finishedAt ?? Date.now();
122770
+ const seconds = Math.floor((end - startedAt) / 1e3);
122771
+ if (seconds < 60) return `${seconds}s`;
122772
+ const minutes = Math.floor(seconds / 60);
122773
+ const remainingSeconds = seconds % 60;
122774
+ if (minutes < 60) return `${minutes}m ${remainingSeconds}s`;
122775
+ return `${Math.floor(minutes / 60)}h ${minutes % 60}m`;
122776
+ }
122777
+ function formatShortDate(timestamp) {
122778
+ const d = new Date(timestamp);
122779
+ return `${d.getMonth() + 1}/${d.getDate()} ${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
122780
+ }
122781
+ function statusColor$2(status) {
122782
+ if (status === "completed") return colors.success;
122783
+ if (status === "running" || status === "proceeding" || status === "init") return colors.accent;
122784
+ return colors.destructive;
122785
+ }
122786
+ const JobListScreen = ({ items, onSelectJob, onViewJobDetail, onSelectedChange }) => {
122787
+ const { selectedIndex, handleNavigation } = useListNavigation({
122788
+ items,
122789
+ onSelect: (item) => onSelectJob(item.job)
122790
+ });
122791
+ useInput((char, key) => {
122792
+ if (char === "d" && items[selectedIndex]) {
122793
+ onViewJobDetail(items[selectedIndex].job);
122794
+ return;
122795
+ }
122796
+ handleNavigation(char, key);
122797
+ });
122798
+ const selectedItem = items[selectedIndex];
122799
+ (0, import_react.useEffect)(() => {
122800
+ onSelectedChange(selectedItem?.job);
122801
+ }, [selectedItem, onSelectedChange]);
122802
+ const { scrollOffset, displayItems } = (0, import_react.useMemo)(() => {
122803
+ const offset = Math.max(0, Math.min(selectedIndex - MAX_VISIBLE + 1, items.length - MAX_VISIBLE));
122804
+ return {
122805
+ scrollOffset: offset,
122806
+ displayItems: items.slice(offset, offset + MAX_VISIBLE)
122807
+ };
122808
+ }, [items, selectedIndex]);
122809
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122810
+ flexDirection: "column",
122811
+ flexGrow: 1,
122812
+ children: [
122813
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, { children: [
122814
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122815
+ color: colors.accent,
122816
+ bold: true,
122817
+ children: "Jobs"
122818
+ }),
122819
+ items.length > MAX_VISIBLE && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
122820
+ color: colors.muted,
122821
+ children: [
122822
+ " ",
122823
+ "(",
122824
+ selectedIndex + 1,
122825
+ "/",
122826
+ items.length,
122827
+ ")"
122828
+ ]
122829
+ }),
122830
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122831
+ color: colors.muted,
122832
+ children: " Enter:Select d:Detail q:Quit"
122833
+ })
122834
+ ] }),
122835
+ scrollOffset > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122836
+ color: colors.muted,
122837
+ children: "..."
122838
+ }),
122839
+ displayItems.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122840
+ color: colors.muted,
122841
+ children: "No jobs found"
122842
+ }) : displayItems.map((item, i) => {
122843
+ const isSelected = scrollOffset + i === selectedIndex;
122844
+ const { job, query } = item;
122845
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
122846
+ flexDirection: "column",
122847
+ children: [
122848
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122849
+ color: isSelected ? colors.accent : colors.muted,
122850
+ children: isSelected ? " ▸ " : " "
122851
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
122852
+ wrap: "truncate",
122853
+ children: [
122854
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122855
+ bold: true,
122856
+ children: job.coordinatorExpertKey
122857
+ }),
122858
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122859
+ dimColor: true,
122860
+ children: " · "
122861
+ }),
122862
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122863
+ color: statusColor$2(job.status),
122864
+ children: job.status
122865
+ }),
122866
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122867
+ dimColor: true,
122868
+ children: " · "
122869
+ }),
122870
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [job.totalSteps, " steps"] }),
122871
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122872
+ dimColor: true,
122873
+ children: " · "
122874
+ }),
122875
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: formatDuration(job.startedAt, job.finishedAt) }),
122876
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122877
+ dimColor: true,
122878
+ children: " "
122879
+ }),
122880
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122881
+ dimColor: true,
122882
+ children: formatShortDate(job.startedAt)
122883
+ })
122884
+ ]
122885
+ })] }),
122886
+ query ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, {
122887
+ paddingLeft: 3,
122888
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
122889
+ dimColor: true,
122890
+ wrap: "truncate",
122891
+ children: [
122892
+ ">",
122893
+ " ",
122894
+ query
122895
+ ]
122896
+ })
122897
+ }) : null,
122898
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, {
122899
+ paddingLeft: 3,
122900
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
122901
+ dimColor: true,
122902
+ wrap: "truncate",
122903
+ children: [
122904
+ "Tokens: In ",
122905
+ formatTokenCount(job.usage.inputTokens),
122906
+ " · Out",
122907
+ " ",
122908
+ formatTokenCount(job.usage.outputTokens)
122909
+ ]
122910
+ })
122911
+ })
122912
+ ]
122913
+ }, job.id);
122914
+ }),
122915
+ scrollOffset + MAX_VISIBLE < items.length && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
122916
+ color: colors.muted,
122917
+ children: "..."
122918
+ })
122919
+ ]
122920
+ });
122921
+ };
122922
+ //#endregion
122923
+ //#region ../../packages/tui-components/src/hooks/use-terminal-height.ts
122924
+ /**
122925
+ * Returns the current terminal height in rows and updates on resize.
122926
+ */
122927
+ function useTerminalHeight() {
122928
+ const { stdout } = useStdout();
122929
+ const [height, setHeight] = (0, import_react.useState)(() => stdout?.rows ?? 24);
122930
+ (0, import_react.useEffect)(() => {
122931
+ if (!stdout) return;
122932
+ const onResize = () => {
122933
+ setHeight(stdout.rows);
122934
+ };
122935
+ stdout.on("resize", onResize);
122936
+ return () => {
122937
+ stdout.off("resize", onResize);
122938
+ };
122939
+ }, [stdout]);
122940
+ return height;
122941
+ }
122942
+ /**
122943
+ * Compute the number of list items that fit on screen.
122944
+ *
122945
+ * @param terminalHeight - Total terminal rows
122946
+ * @param reservedLines - Lines used by headers, tab bars, bottom panel, etc.
122947
+ * @param linesPerItem - How many terminal lines each list item occupies
122948
+ * @param minItems - Minimum items to show regardless of terminal size
122949
+ */
122950
+ function computeVisibleItems(terminalHeight, reservedLines, linesPerItem, minItems = 3) {
122951
+ const available = terminalHeight - reservedLines;
122952
+ return Math.max(minItems, Math.floor(available / linesPerItem));
122953
+ }
122954
+ //#endregion
122955
+ //#region ../../packages/tui-components/src/log-viewer/screens/activity-list.tsx
122956
+ function buildActivities(events) {
122957
+ const activities = [];
122958
+ for (const event of events) switch (event.type) {
122959
+ case "startRun":
122960
+ activities.push({
122961
+ label: `Started run with model ${event.model}`,
122962
+ detail: `${event.inputMessages.length} input messages`,
122963
+ color: colors.accent,
122964
+ timestamp: event.timestamp,
122965
+ event
122966
+ });
122967
+ break;
122968
+ case "callTools": {
122969
+ const toolNames = event.toolCalls.map((tc) => tc.toolName).join(", ");
122970
+ const totalTokens = event.usage.inputTokens + event.usage.outputTokens;
122971
+ activities.push({
122972
+ label: `Called tools: ${toolNames}`,
122973
+ detail: `${event.toolCalls.length} call(s) · ${formatTokenCount(totalTokens)} tokens`,
122974
+ color: "magenta",
122975
+ timestamp: event.timestamp,
122976
+ event
122977
+ });
122978
+ break;
122979
+ }
122980
+ case "completeRun": {
122981
+ const resultPreview = event.text.length > 80 ? `${event.text.slice(0, 80)}...` : event.text;
122982
+ activities.push({
122983
+ label: "Run completed",
122984
+ detail: resultPreview,
122985
+ color: colors.success,
122986
+ timestamp: event.timestamp,
122987
+ event
122988
+ });
122989
+ break;
122990
+ }
122991
+ case "stopRunByError":
122992
+ activities.push({
122993
+ label: `Error: ${event.error.name}`,
122994
+ detail: event.error.message,
122995
+ color: colors.destructive,
122996
+ timestamp: event.timestamp,
122997
+ event
122998
+ });
122999
+ break;
123000
+ case "stopRunByDelegate": {
123001
+ const delegates = event.checkpoint.delegateTo?.map((d) => d.expert.key).join(", ");
123002
+ activities.push({
123003
+ label: `Delegated to ${delegates ?? "unknown"}`,
123004
+ color: colors.warn,
123005
+ timestamp: event.timestamp,
123006
+ event
123007
+ });
123008
+ break;
123009
+ }
123010
+ case "retry":
123011
+ activities.push({
123012
+ label: `Retried: ${event.reason}`,
123013
+ color: colors.warn,
123014
+ timestamp: event.timestamp,
123015
+ event
123016
+ });
123017
+ break;
123018
+ case "resumeFromStop":
123019
+ activities.push({
123020
+ label: "Resumed from delegation",
123021
+ detail: `Step ${event.checkpoint.stepNumber}`,
123022
+ color: colors.accent,
123023
+ timestamp: event.timestamp,
123024
+ event
123025
+ });
123026
+ break;
123027
+ case "resolveToolResults":
123028
+ activities.push({
123029
+ label: `Resolved ${event.toolResults.length} tool result(s)`,
123030
+ color: colors.primary,
123031
+ timestamp: event.timestamp,
123032
+ event
123033
+ });
123034
+ break;
123035
+ default: break;
123036
+ }
123037
+ return activities;
123038
+ }
123039
+ //#endregion
123040
+ //#region ../../packages/tui-components/src/log-viewer/screens/run-detail.tsx
123041
+ const TAB_ORDER = [
123042
+ "activities",
123043
+ "checkpoints",
123044
+ "events"
123045
+ ];
123046
+ const TAB_LABELS = {
123047
+ activities: "Activities",
123048
+ checkpoints: "Checkpoints",
123049
+ events: "Events"
123050
+ };
123051
+ const RESERVED_LINES$1 = 11;
123052
+ const LINES_PER_ITEM$1 = 2;
123053
+ function statusColor$1(status) {
123054
+ if (status === "completed") return colors.success;
123055
+ if (status === "proceeding" || status === "init") return colors.accent;
123056
+ return colors.destructive;
123057
+ }
123058
+ function getUsageIcon(ratio) {
123059
+ const percent = ratio * 100;
123060
+ if (percent >= 75) return USAGE_INDICATORS.FULL;
123061
+ if (percent >= 50) return USAGE_INDICATORS.HIGH;
123062
+ if (percent >= 25) return USAGE_INDICATORS.MEDIUM;
123063
+ if (percent >= 5) return USAGE_INDICATORS.LOW;
123064
+ return USAGE_INDICATORS.EMPTY;
123065
+ }
123066
+ function eventTypeColor(type) {
123067
+ switch (type) {
123068
+ case "startRun": return colors.accent;
123069
+ case "completeRun": return colors.success;
123070
+ case "stopRunByError": return colors.destructive;
123071
+ case "stopRunByDelegate":
123072
+ case "stopRunByInteractiveTool": return colors.warn;
123073
+ case "callTools": return "magenta";
123074
+ case "retry": return colors.destructive;
123075
+ default: return colors.muted;
123076
+ }
123077
+ }
123078
+ function formatTime(timestamp) {
123079
+ const d = new Date(timestamp);
123080
+ return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}:${String(d.getSeconds()).padStart(2, "0")}`;
123081
+ }
123082
+ function getEventSummary(event) {
123083
+ switch (event.type) {
123084
+ case "callTools": return `Tools: ${event.toolCalls.map((tc) => `${tc.skillName}/${tc.toolName}`).join(", ")}`;
123085
+ case "completeRun": return `Result: ${event.text}`;
123086
+ case "stopRunByError": return `${event.error.name}: ${event.error.message}`;
123087
+ case "retry": return `Reason: ${event.reason}`;
123088
+ case "startRun": return `Model: ${event.model}`;
123089
+ case "stopRunByDelegate": return event.checkpoint.delegateTo ? `Delegates: ${event.checkpoint.delegateTo.map((d) => d.expert.key).join(", ")}` : void 0;
123090
+ default: return;
123091
+ }
123092
+ }
123093
+ const RunDetailScreen = ({ run, checkpoints, events, initialTab, onSelectCheckpoint, onSelectEvent, onBack, onTabChange, onSelectedCheckpointChange, onSelectedEventChange }) => {
123094
+ const [activeTab, setActiveTab] = (0, import_react.useState)(initialTab);
123095
+ const maxVisible = computeVisibleItems(useTerminalHeight(), RESERVED_LINES$1, LINES_PER_ITEM$1);
123096
+ const activities = (0, import_react.useMemo)(() => buildActivities(events), [events]);
123097
+ const checkpointNav = useListNavigation({
123098
+ items: checkpoints,
123099
+ onSelect: onSelectCheckpoint,
123100
+ onBack
123101
+ });
123102
+ const eventNav = useListNavigation({
123103
+ items: events,
123104
+ onSelect: onSelectEvent,
123105
+ onBack
123106
+ });
123107
+ const activityNav = useListNavigation({
123108
+ items: activities,
123109
+ onSelect: (activity) => onSelectEvent(activity.event),
123110
+ onBack
123111
+ });
123112
+ const selectedCheckpoint = checkpoints[checkpointNav.selectedIndex];
123113
+ const selectedEvent = events[eventNav.selectedIndex];
123114
+ const selectedActivity = activities[activityNav.selectedIndex];
123115
+ (0, import_react.useEffect)(() => {
123116
+ if (activeTab === "checkpoints") {
123117
+ onSelectedCheckpointChange(selectedCheckpoint);
123118
+ onSelectedEventChange(void 0);
123119
+ } else if (activeTab === "events") {
123120
+ onSelectedEventChange(selectedEvent);
123121
+ onSelectedCheckpointChange(void 0);
123122
+ } else {
123123
+ onSelectedEventChange(selectedActivity?.event);
123124
+ onSelectedCheckpointChange(void 0);
123125
+ }
123126
+ }, [
123127
+ activeTab,
123128
+ selectedCheckpoint,
123129
+ selectedEvent,
123130
+ selectedActivity,
123131
+ onSelectedCheckpointChange,
123132
+ onSelectedEventChange
123133
+ ]);
123134
+ const switchTab = (0, import_react.useCallback)((tab) => {
123135
+ setActiveTab(tab);
123136
+ onTabChange(tab);
123137
+ }, [onTabChange]);
123138
+ useInput((char, key) => {
123139
+ if (char === "1") {
123140
+ switchTab("activities");
123141
+ return;
123142
+ }
123143
+ if (char === "2") {
123144
+ switchTab("checkpoints");
123145
+ return;
123146
+ }
123147
+ if (char === "3") {
123148
+ switchTab("events");
123149
+ return;
123150
+ }
123151
+ if (key.tab) {
123152
+ const currentIndex = TAB_ORDER.indexOf(activeTab);
123153
+ switchTab(TAB_ORDER[key.shift ? (currentIndex - 1 + TAB_ORDER.length) % TAB_ORDER.length : (currentIndex + 1) % TAB_ORDER.length]);
123154
+ return;
123155
+ }
123156
+ if (activeTab === "checkpoints") checkpointNav.handleNavigation(char, key);
123157
+ else if (activeTab === "events") eventNav.handleNavigation(char, key);
123158
+ else activityNav.handleNavigation(char, key);
123159
+ });
123160
+ const shortRunId = run.runId.length > 12 ? `${run.runId.slice(0, 12)}...` : run.runId;
123161
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
123162
+ flexDirection: "column",
123163
+ flexGrow: 1,
123164
+ children: [
123165
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, { children: [
123166
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123167
+ color: colors.accent,
123168
+ bold: true,
123169
+ children: "Run"
123170
+ }),
123171
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
123172
+ color: colors.muted,
123173
+ children: [
123174
+ " (",
123175
+ shortRunId,
123176
+ ")"
123177
+ ]
123178
+ }),
123179
+ run.expertKey ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
123180
+ color: colors.muted,
123181
+ children: [" ", run.expertKey]
123182
+ }) : null
123183
+ ] }),
123184
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, { children: [TAB_ORDER.map((tab, i) => {
123185
+ const isActive = tab === activeTab;
123186
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, { children: [i > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123187
+ color: colors.muted,
123188
+ children: " | "
123189
+ }) : null, /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
123190
+ color: isActive ? colors.accent : colors.muted,
123191
+ bold: isActive,
123192
+ children: [
123193
+ i + 1,
123194
+ ":",
123195
+ TAB_LABELS[tab]
123196
+ ]
123197
+ })] }, tab);
123198
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123199
+ color: colors.muted,
123200
+ children: " Tab:Switch Enter:Select b:Back q:Quit"
123201
+ })] }),
123202
+ activeTab === "activities" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ActivitiesContent, {
123203
+ activities,
123204
+ selectedIndex: activityNav.selectedIndex,
123205
+ maxVisible
123206
+ }),
123207
+ activeTab === "checkpoints" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CheckpointsContent, {
123208
+ checkpoints,
123209
+ selectedIndex: checkpointNav.selectedIndex,
123210
+ maxVisible
123211
+ }),
123212
+ activeTab === "events" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EventsContent, {
123213
+ events,
123214
+ selectedIndex: eventNav.selectedIndex,
123215
+ maxVisible
123216
+ })
123217
+ ]
123218
+ });
123219
+ };
123220
+ function ActivitiesContent({ activities, selectedIndex, maxVisible }) {
123221
+ const { scrollOffset, displayItems } = (0, import_react.useMemo)(() => {
123222
+ const offset = Math.max(0, Math.min(selectedIndex - maxVisible + 1, activities.length - maxVisible));
123223
+ return {
123224
+ scrollOffset: offset,
123225
+ displayItems: activities.slice(offset, offset + maxVisible)
123226
+ };
123227
+ }, [
123228
+ activities,
123229
+ selectedIndex,
123230
+ maxVisible
123231
+ ]);
123232
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
123233
+ flexDirection: "column",
123234
+ flexGrow: 1,
123235
+ children: [
123236
+ activities.length > maxVisible && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
123237
+ color: colors.muted,
123238
+ children: [
123239
+ "[",
123240
+ selectedIndex + 1,
123241
+ "/",
123242
+ activities.length,
123243
+ "]"
123244
+ ]
123245
+ }),
123246
+ scrollOffset > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123247
+ color: colors.muted,
123248
+ children: "..."
123249
+ }),
123250
+ displayItems.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123251
+ color: colors.muted,
123252
+ children: "No activities found"
123253
+ }) : displayItems.map((activity, i) => {
123254
+ const isSelected = scrollOffset + i === selectedIndex;
123255
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
123256
+ flexDirection: "column",
123257
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123258
+ color: isSelected ? colors.accent : colors.muted,
123259
+ children: isSelected ? " ▸ " : " "
123260
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
123261
+ wrap: "truncate",
123262
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123263
+ color: activity.color,
123264
+ bold: isSelected,
123265
+ children: activity.label
123266
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
123267
+ dimColor: true,
123268
+ children: [" ", formatTime(activity.timestamp)]
123269
+ })]
123270
+ })] }), activity.detail ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, {
123271
+ paddingLeft: 3,
123272
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123273
+ dimColor: true,
123274
+ wrap: "truncate",
123275
+ children: activity.detail
123276
+ })
123277
+ }) : null]
123278
+ }, activity.event.id);
123279
+ }),
123280
+ scrollOffset + maxVisible < activities.length && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123281
+ color: colors.muted,
123282
+ children: "..."
123283
+ })
123284
+ ]
123285
+ });
123286
+ }
123287
+ function CheckpointsContent({ checkpoints, selectedIndex, maxVisible }) {
123288
+ const { scrollOffset, displayItems } = (0, import_react.useMemo)(() => {
123289
+ const offset = Math.max(0, Math.min(selectedIndex - maxVisible + 1, checkpoints.length - maxVisible));
123290
+ return {
123291
+ scrollOffset: offset,
123292
+ displayItems: checkpoints.slice(offset, offset + maxVisible)
123293
+ };
123294
+ }, [
123295
+ checkpoints,
123296
+ selectedIndex,
123297
+ maxVisible
123298
+ ]);
123299
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
123300
+ flexDirection: "column",
123301
+ flexGrow: 1,
123302
+ children: [
123303
+ checkpoints.length > maxVisible && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
123304
+ color: colors.muted,
123305
+ children: [
123306
+ "[",
123307
+ selectedIndex + 1,
123308
+ "/",
123309
+ checkpoints.length,
123310
+ "]"
123311
+ ]
123312
+ }),
123313
+ scrollOffset > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123314
+ color: colors.muted,
123315
+ children: "..."
123316
+ }),
123317
+ displayItems.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123318
+ color: colors.muted,
123319
+ children: "No checkpoints found"
123320
+ }) : displayItems.map((cp, i) => {
123321
+ const isSelected = scrollOffset + i === selectedIndex;
123322
+ const hasUsage = cp.contextWindowUsage !== void 0;
123323
+ const hasDelegates = cp.delegateTo && cp.delegateTo.length > 0;
123324
+ const hasError = !!cp.error;
123325
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
123326
+ flexDirection: "column",
123327
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123328
+ color: isSelected ? colors.accent : colors.muted,
123329
+ children: isSelected ? " ▸ " : " "
123330
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
123331
+ wrap: "truncate",
123332
+ children: [
123333
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
123334
+ bold: true,
123335
+ children: ["Step ", cp.stepNumber]
123336
+ }),
123337
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123338
+ dimColor: true,
123339
+ children: " · "
123340
+ }),
123341
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123342
+ color: statusColor$1(cp.status),
123343
+ children: cp.status
123344
+ }),
123345
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123346
+ dimColor: true,
123347
+ children: " · "
123348
+ }),
123349
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: cp.expert.key }),
123350
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123351
+ dimColor: true,
123352
+ children: " · "
123353
+ }),
123354
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [cp.messages.length, " msgs"] }),
123355
+ hasUsage ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123356
+ dimColor: true,
123357
+ children: " · "
123358
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [
123359
+ getUsageIcon(cp.contextWindowUsage),
123360
+ " ",
123361
+ (cp.contextWindowUsage * 100).toFixed(0),
123362
+ "%"
123363
+ ] })] }) : null
123364
+ ]
123365
+ })] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, {
123366
+ paddingLeft: 3,
123367
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
123368
+ dimColor: true,
123369
+ wrap: "truncate",
123370
+ children: [
123371
+ "Tokens: In ",
123372
+ formatTokenCount(cp.usage.inputTokens),
123373
+ " · Out",
123374
+ " ",
123375
+ formatTokenCount(cp.usage.outputTokens),
123376
+ hasDelegates ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [" · Delegates: ", cp.delegateTo.map((d) => d.expert.key).join(", ")] }) : null,
123377
+ hasError ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
123378
+ color: colors.destructive,
123379
+ children: [" · ", cp.error.name]
123380
+ }) : null
123381
+ ]
123382
+ })
123383
+ })]
123384
+ }, cp.id);
123385
+ }),
123386
+ scrollOffset + maxVisible < checkpoints.length && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123387
+ color: colors.muted,
123388
+ children: "..."
123389
+ })
123390
+ ]
123391
+ });
123392
+ }
123393
+ function EventsContent({ events, selectedIndex, maxVisible }) {
123394
+ const { scrollOffset, displayItems } = (0, import_react.useMemo)(() => {
123395
+ const offset = Math.max(0, Math.min(selectedIndex - maxVisible + 1, events.length - maxVisible));
123396
+ return {
123397
+ scrollOffset: offset,
123398
+ displayItems: events.slice(offset, offset + maxVisible)
123399
+ };
123400
+ }, [
123401
+ events,
123402
+ selectedIndex,
123403
+ maxVisible
123404
+ ]);
123405
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
123406
+ flexDirection: "column",
123407
+ flexGrow: 1,
123408
+ children: [
123409
+ events.length > maxVisible && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
123410
+ color: colors.muted,
123411
+ children: [
123412
+ "[",
123413
+ selectedIndex + 1,
123414
+ "/",
123415
+ events.length,
123416
+ "]"
123417
+ ]
123418
+ }),
123419
+ scrollOffset > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123420
+ color: colors.muted,
123421
+ children: "..."
123422
+ }),
123423
+ displayItems.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123424
+ color: colors.muted,
123425
+ children: "No events found"
123426
+ }) : displayItems.map((ev, i) => {
123427
+ const isSelected = scrollOffset + i === selectedIndex;
123428
+ const summary = getEventSummary(ev);
123429
+ const hasUsage = "usage" in ev && ev.usage;
123430
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
123431
+ flexDirection: "column",
123432
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123433
+ color: isSelected ? colors.accent : colors.muted,
123434
+ children: isSelected ? " ▸ " : " "
123435
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
123436
+ wrap: "truncate",
123437
+ children: [
123438
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
123439
+ bold: true,
123440
+ children: ["Step ", ev.stepNumber]
123441
+ }),
123442
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123443
+ dimColor: true,
123444
+ children: " · "
123445
+ }),
123446
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123447
+ color: eventTypeColor(ev.type),
123448
+ children: ev.type
123449
+ }),
123450
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123451
+ dimColor: true,
123452
+ children: " · "
123453
+ }),
123454
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: ev.expertKey }),
123455
+ hasUsage ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123456
+ dimColor: true,
123457
+ children: " · "
123458
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
123459
+ dimColor: true,
123460
+ children: [
123461
+ "In",
123462
+ " ",
123463
+ formatTokenCount(ev.usage.inputTokens),
123464
+ " ",
123465
+ "· Out",
123466
+ " ",
123467
+ formatTokenCount(ev.usage.outputTokens)
123468
+ ]
123469
+ })] }) : null,
123470
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123471
+ dimColor: true,
123472
+ children: " "
123473
+ }),
123474
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123475
+ dimColor: true,
123476
+ children: formatTime(ev.timestamp)
123477
+ })
123478
+ ]
123479
+ })] }), summary ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, {
123480
+ paddingLeft: 3,
123481
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123482
+ dimColor: true,
123483
+ wrap: "truncate",
123484
+ children: summary
123485
+ })
123486
+ }) : null]
123487
+ }, ev.id);
123488
+ }),
123489
+ scrollOffset + maxVisible < events.length && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123490
+ color: colors.muted,
123491
+ children: "..."
123492
+ })
123493
+ ]
123494
+ });
123495
+ }
123496
+ //#endregion
123497
+ //#region ../../packages/tui-components/src/log-viewer/build-graph-lines.ts
123498
+ /**
123499
+ * Build git-graph style lines from a DelegationTreeState.
123500
+ *
123501
+ * Uses delegation groups to distinguish sequential vs parallel delegations.
123502
+ * Uses resumeNodes to show resume runs as separate nodes after merge points.
123503
+ * Each column/lane gets a stable `colorIndex` so the renderer can color
123504
+ * parent and child lanes differently.
123505
+ */
123506
+ function buildGraphLines(treeState, runQueries, delegationGroups, resumeNodes) {
123507
+ if (!treeState.rootRunId) return [];
123508
+ const root = treeState.nodes.get(treeState.rootRunId);
123509
+ if (!root) return [];
123510
+ const resumeSet = resumeNodes ? new Set(resumeNodes.keys()) : /* @__PURE__ */ new Set();
123511
+ const lines = [];
123512
+ let nextCol = 0;
123513
+ const freePool = [];
123514
+ function allocCol() {
123515
+ if (freePool.length > 0) {
123516
+ freePool.sort((a, b) => a - b);
123517
+ return freePool.shift();
123518
+ }
123519
+ return nextCol++;
123520
+ }
123521
+ function freeCol(col) {
123522
+ freePool.push(col);
123523
+ }
123524
+ const activeCols = /* @__PURE__ */ new Set();
123525
+ const colColors = /* @__PURE__ */ new Map();
123526
+ let nextColorIndex = 0;
123527
+ function assignColor(col) {
123528
+ const ci = nextColorIndex++;
123529
+ colColors.set(col, ci);
123530
+ return ci;
123531
+ }
123532
+ function getColor(col) {
123533
+ return colColors.get(col) ?? -1;
123534
+ }
123535
+ /** Get the max column index we need to render. */
123536
+ function maxCol(extraCols) {
123537
+ let max = -1;
123538
+ for (const c of activeCols) if (c > max) max = c;
123539
+ if (extraCols) {
123540
+ for (const c of extraCols) if (c > max) max = c;
123541
+ }
123542
+ return max;
123543
+ }
123544
+ /** Render a line given active columns, with optional special markers. */
123545
+ function render(markers) {
123546
+ const mc = maxCol(markers?.keys());
123547
+ if (mc < 0) return {
123548
+ graph: "",
123549
+ segments: []
123550
+ };
123551
+ let graph = "";
123552
+ const segments = [];
123553
+ for (let c = 0; c <= mc; c++) {
123554
+ const marker = markers?.get(c);
123555
+ if (marker) {
123556
+ const text = `${marker} `;
123557
+ graph += text;
123558
+ segments.push({
123559
+ text,
123560
+ colorIndex: getColor(c)
123561
+ });
123562
+ } else if (activeCols.has(c)) {
123563
+ const text = "| ";
123564
+ graph += text;
123565
+ segments.push({
123566
+ text,
123567
+ colorIndex: getColor(c)
123568
+ });
123569
+ } else {
123570
+ const text = " ";
123571
+ graph += text;
123572
+ segments.push({
123573
+ text,
123574
+ colorIndex: -1
123575
+ });
123576
+ }
123577
+ }
123578
+ return {
123579
+ graph,
123580
+ segments
123581
+ };
123582
+ }
123583
+ /**
123584
+ * Split a node's children into:
123585
+ * - delegateGroups: groups of children delegated together (from delegationGroups)
123586
+ * - resumeChildren: children that are resume runs (process in order after delegate groups)
123587
+ * - otherChildren: children that are neither delegated nor resume (ungrouped)
123588
+ */
123589
+ const resumeChildrenOf = /* @__PURE__ */ new Map();
123590
+ if (resumeNodes) for (const [resumeId, originalId] of resumeNodes) {
123591
+ const list = resumeChildrenOf.get(originalId) ?? [];
123592
+ list.push(resumeId);
123593
+ resumeChildrenOf.set(originalId, list);
123594
+ }
123595
+ function splitChildren(node) {
123596
+ const allGrouped = /* @__PURE__ */ new Set();
123597
+ const delegateGroups = [];
123598
+ if (delegationGroups) {
123599
+ const groups = delegationGroups.get(node.runId);
123600
+ if (groups) for (const group of groups) {
123601
+ delegateGroups.push(group);
123602
+ for (const id of group) allGrouped.add(id);
123603
+ }
123604
+ }
123605
+ const resumeChildren = resumeChildrenOf.get(node.runId) ?? [];
123606
+ const allResumeIds = new Set(resumeSet);
123607
+ const otherChildren = [];
123608
+ for (const childId of node.childRunIds) {
123609
+ if (allGrouped.has(childId)) continue;
123610
+ if (allResumeIds.has(childId)) continue;
123611
+ otherChildren.push(childId);
123612
+ }
123613
+ if (delegateGroups.length === 0 && otherChildren.length > 0) {
123614
+ delegateGroups.push(otherChildren);
123615
+ return {
123616
+ delegateGroups,
123617
+ resumeChildren,
123618
+ otherChildren: []
123619
+ };
123620
+ }
123621
+ return {
123622
+ delegateGroups,
123623
+ resumeChildren,
123624
+ otherChildren
123625
+ };
123626
+ }
123627
+ /**
123628
+ * Emit lines for a node on a shared lane without freeing the column afterward.
123629
+ * Used when multiple children share a single lane (commit-chain style).
123630
+ */
123631
+ function emitSubtreeInline(node, col) {
123632
+ activeCols.add(col);
123633
+ const isResume = resumeSet.has(node.runId);
123634
+ const { graph, segments } = render(new Map([[col, "*"]]));
123635
+ lines.push({
123636
+ kind: "node",
123637
+ node,
123638
+ graph,
123639
+ graphSegments: segments,
123640
+ query: runQueries.get(node.runId),
123641
+ isResume
123642
+ });
123643
+ const { delegateGroups, resumeChildren, otherChildren } = splitChildren(node);
123644
+ if (!(delegateGroups.some((g) => g.length > 0) || resumeChildren.length > 0 || otherChildren.length > 0)) return;
123645
+ emitChildGroups(node, col, delegateGroups, resumeChildren, otherChildren);
123646
+ }
123647
+ /**
123648
+ * Shared logic for processing delegate groups, resume children, and other children.
123649
+ */
123650
+ function emitChildGroups(_node, col, delegateGroups, resumeChildren, otherChildren) {
123651
+ for (const group of delegateGroups) {
123652
+ const children = group.map((id) => treeState.nodes.get(id)).filter((n) => n !== void 0);
123653
+ if (children.length === 0) continue;
123654
+ const cc = allocCol();
123655
+ assignColor(cc);
123656
+ activeCols.add(cc);
123657
+ const forkMarkers = /* @__PURE__ */ new Map();
123658
+ forkMarkers.set(cc, "\\");
123659
+ const fork = render(forkMarkers);
123660
+ lines.push({
123661
+ kind: "connector",
123662
+ graph: fork.graph,
123663
+ graphSegments: fork.segments
123664
+ });
123665
+ for (const child of children) emitSubtreeInline(child, cc);
123666
+ activeCols.add(cc);
123667
+ const mergeMarkers = /* @__PURE__ */ new Map();
123668
+ mergeMarkers.set(cc, "/");
123669
+ const merge = render(mergeMarkers);
123670
+ lines.push({
123671
+ kind: "connector",
123672
+ graph: merge.graph,
123673
+ graphSegments: merge.segments
123674
+ });
123675
+ activeCols.delete(cc);
123676
+ if (!freePool.includes(cc)) freeCol(cc);
123677
+ }
123678
+ for (const resumeId of resumeChildren) {
123679
+ const resumeNode = treeState.nodes.get(resumeId);
123680
+ if (!resumeNode) continue;
123681
+ emitSubtreeInline(resumeNode, col);
123682
+ }
123683
+ if (otherChildren.length > 0) {
123684
+ const children = otherChildren.map((id) => treeState.nodes.get(id)).filter((n) => n !== void 0);
123685
+ const cc = allocCol();
123686
+ assignColor(cc);
123687
+ activeCols.add(cc);
123688
+ const forkMarkers = /* @__PURE__ */ new Map();
123689
+ forkMarkers.set(cc, "\\");
123690
+ const fork = render(forkMarkers);
123691
+ lines.push({
123692
+ kind: "connector",
123693
+ graph: fork.graph,
123694
+ graphSegments: fork.segments
123695
+ });
123696
+ for (const child of children) emitSubtreeInline(child, cc);
123697
+ activeCols.add(cc);
123698
+ const mergeMarkers = /* @__PURE__ */ new Map();
123699
+ mergeMarkers.set(cc, "/");
123700
+ const merge = render(mergeMarkers);
123701
+ lines.push({
123702
+ kind: "connector",
123703
+ graph: merge.graph,
123704
+ graphSegments: merge.segments
123705
+ });
123706
+ activeCols.delete(cc);
123707
+ if (!freePool.includes(cc)) freeCol(cc);
123708
+ }
123709
+ }
123710
+ /**
123711
+ * Recursively emit lines for a subtree (top-level entry point that frees column on exit).
123712
+ */
123713
+ function emitSubtree(node, col) {
123714
+ emitSubtreeInline(node, col);
123715
+ activeCols.delete(col);
123716
+ if (!freePool.includes(col)) freeCol(col);
123717
+ }
123718
+ const rootCol = allocCol();
123719
+ assignColor(rootCol);
123720
+ emitSubtree(root, rootCol);
123721
+ for (const [nodeId, node] of treeState.nodes) if (!lines.some((l) => l.kind === "node" && l.node.runId === nodeId)) lines.push({
123722
+ kind: "node",
123723
+ node,
123724
+ graph: "* ",
123725
+ graphSegments: [{
123726
+ text: "* ",
123727
+ colorIndex: -1
123728
+ }],
123729
+ query: runQueries.get(nodeId),
123730
+ isResume: resumeSet.has(nodeId)
123731
+ });
123732
+ return lines;
123733
+ }
123734
+ //#endregion
123735
+ //#region ../../packages/tui-components/src/log-viewer/screens/run-list.tsx
123736
+ const RESERVED_LINES = 9;
123737
+ const LINES_PER_ITEM = 1;
123738
+ /**
123739
+ * Color palette for graph lanes. Each lane gets a color by its colorIndex mod palette length.
123740
+ * These are chosen to be distinct and readable on both dark and light terminals.
123741
+ */
123742
+ const LANE_COLORS = [
123743
+ "cyan",
123744
+ "magenta",
123745
+ "yellow",
123746
+ "blue",
123747
+ "green",
123748
+ "red",
123749
+ "white"
123750
+ ];
123751
+ function laneColor(colorIndex) {
123752
+ if (colorIndex < 0) return colors.muted;
123753
+ return LANE_COLORS[colorIndex % LANE_COLORS.length];
123754
+ }
123755
+ function statusIcon(status) {
123756
+ switch (status) {
123757
+ case "completed": return "✓";
123758
+ case "error": return "✗";
123759
+ case "running": return "○";
123760
+ case "suspending": return "⎇";
123761
+ default: return " ";
123762
+ }
123763
+ }
123764
+ function statusColor(status) {
123765
+ switch (status) {
123766
+ case "completed": return colors.primary;
123767
+ case "error": return colors.destructive;
123768
+ case "suspending": return colors.primary;
123769
+ default: return colors.muted;
123770
+ }
123771
+ }
123772
+ /** Render graph segments as colored <Text> elements. */
123773
+ function GraphPrefix({ segments, nodeStatus }) {
123774
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: segments.map((seg, i) => {
123775
+ if (seg.text.startsWith("*") && nodeStatus) {
123776
+ const icon = statusIcon(nodeStatus);
123777
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
123778
+ color: statusColor(nodeStatus),
123779
+ children: [icon, seg.text.slice(1)]
123780
+ }, i);
123781
+ }
123782
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123783
+ color: laneColor(seg.colorIndex),
123784
+ children: seg.text
123785
+ }, i);
123786
+ }) });
123787
+ }
123788
+ const RunListScreen = ({ job, treeState, runQueries, delegationGroups, resumeNodes, onSelectRun, onBack, onSelectedChange }) => {
123789
+ const maxVisible = computeVisibleItems(useTerminalHeight(), RESERVED_LINES, LINES_PER_ITEM);
123790
+ const graphLines = (0, import_react.useMemo)(() => buildGraphLines(treeState, runQueries, delegationGroups, resumeNodes), [
123791
+ treeState,
123792
+ runQueries,
123793
+ delegationGroups,
123794
+ resumeNodes
123795
+ ]);
123796
+ const selectableIndices = (0, import_react.useMemo)(() => graphLines.map((line, idx) => line.kind === "node" ? idx : -1).filter((idx) => idx >= 0), [graphLines]);
123797
+ const [selectedPos, setSelectedPos] = (0, import_react.useState)(0);
123798
+ const selectedLineIndex = selectableIndices[selectedPos];
123799
+ const selectedLine = selectedLineIndex !== void 0 ? graphLines[selectedLineIndex] : void 0;
123800
+ const selectedNode = selectedLine?.kind === "node" ? selectedLine.node : void 0;
123801
+ (0, import_react.useEffect)(() => {
123802
+ onSelectedChange(selectedNode);
123803
+ }, [selectedNode, onSelectedChange]);
123804
+ const handleSelect = (0, import_react.useCallback)(() => {
123805
+ if (selectedNode) onSelectRun(selectedNode);
123806
+ }, [selectedNode, onSelectRun]);
123807
+ useInput((char, key) => {
123808
+ if (key.upArrow && selectableIndices.length > 0) {
123809
+ setSelectedPos((prev) => Math.max(0, prev - 1));
123810
+ return;
123811
+ }
123812
+ if (key.downArrow && selectableIndices.length > 0) {
123813
+ setSelectedPos((prev) => Math.min(selectableIndices.length - 1, prev + 1));
123814
+ return;
123815
+ }
123816
+ if (key.return) {
123817
+ handleSelect();
123818
+ return;
123819
+ }
123820
+ if (key.escape || char === "b") onBack();
123821
+ });
123822
+ const { scrollOffset, displayLines } = (0, import_react.useMemo)(() => {
123823
+ if (graphLines.length <= maxVisible) return {
123824
+ scrollOffset: 0,
123825
+ displayLines: graphLines
123826
+ };
123827
+ const rawOffset = (selectedLineIndex ?? 0) - Math.floor(maxVisible / 2);
123828
+ const offset = Math.max(0, Math.min(rawOffset, graphLines.length - maxVisible));
123829
+ return {
123830
+ scrollOffset: offset,
123831
+ displayLines: graphLines.slice(offset, offset + maxVisible)
123832
+ };
123833
+ }, [
123834
+ graphLines,
123835
+ selectedLineIndex,
123836
+ maxVisible
123837
+ ]);
123838
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
123839
+ flexDirection: "column",
123840
+ flexGrow: 1,
123841
+ children: [
123842
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, { children: [
123843
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123844
+ color: colors.accent,
123845
+ bold: true,
123846
+ children: "Runs"
123847
+ }),
123848
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
123849
+ color: colors.muted,
123850
+ children: [
123851
+ " (",
123852
+ job.coordinatorExpertKey,
123853
+ ")"
123854
+ ]
123855
+ }),
123856
+ selectableIndices.length > maxVisible && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
123857
+ color: colors.muted,
123858
+ children: [
123859
+ " ",
123860
+ "[",
123861
+ selectedPos + 1,
123862
+ "/",
123863
+ selectableIndices.length,
123864
+ "]"
123865
+ ]
123866
+ }),
123867
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123868
+ color: colors.muted,
123869
+ children: " Enter:Select b:Back q:Quit"
123870
+ })
123871
+ ] }),
123872
+ scrollOffset > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123873
+ color: colors.muted,
123874
+ children: "..."
123875
+ }),
123876
+ displayLines.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123877
+ color: colors.muted,
123878
+ children: "No runs found"
123879
+ }) : displayLines.map((line, i) => {
123880
+ const actualIndex = scrollOffset + i;
123881
+ if (line.kind === "connector") return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
123882
+ wrap: "truncate",
123883
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: " " }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GraphPrefix, { segments: line.graphSegments })]
123884
+ }, `c-${actualIndex}`);
123885
+ const isSelected = actualIndex === selectedLineIndex;
123886
+ const nameColor = statusColor(line.node.status);
123887
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
123888
+ wrap: "truncate",
123889
+ children: [
123890
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123891
+ color: isSelected ? colors.accent : colors.muted,
123892
+ children: isSelected ? " > " : " "
123893
+ }),
123894
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GraphPrefix, {
123895
+ segments: line.graphSegments,
123896
+ nodeStatus: line.node.status
123897
+ }),
123898
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123899
+ bold: true,
123900
+ color: nameColor,
123901
+ children: line.node.expertName
123902
+ }),
123903
+ line.isResume ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123904
+ color: colors.muted,
123905
+ children: " (resumed)"
123906
+ }) : null,
123907
+ line.query ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
123908
+ dimColor: true,
123909
+ children: [" ", line.query]
123910
+ }) : null
123911
+ ]
123912
+ }, line.node.runId);
123913
+ }),
123914
+ scrollOffset + maxVisible < graphLines.length && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
123915
+ color: colors.muted,
123916
+ children: "..."
123917
+ })
123918
+ ]
123919
+ });
123920
+ };
123921
+ //#endregion
123922
+ //#region ../../packages/tui-components/src/log-viewer/app.tsx
123923
+ async function extractJobQuery(fetcher, job) {
123924
+ try {
123925
+ const runs = await fetcher.getRuns(job.id);
123926
+ if (runs.length === 0) return void 0;
123927
+ const firstRun = runs[0];
123928
+ const startRunEvent = await fetcher.getFirstEventForRun(job.id, firstRun.runId, "startRun");
123929
+ if (!startRunEvent) return void 0;
123930
+ return extractQueryFromStartRun(startRunEvent);
123931
+ } catch {
123932
+ return;
123933
+ }
123934
+ }
123935
+ async function buildRunTree(fetcher, jobId) {
123936
+ return buildRunTreeFromEvents(await fetcher.getTreeEventsForJob(jobId));
123937
+ }
123938
+ const LogViewerApp = ({ fetcher, initialJobId, initialRunId }) => {
123939
+ const { exit } = useApp();
123940
+ const [screen, setScreen] = (0, import_react.useState)({ type: "jobList" });
123941
+ const [jobItems, setJobItems] = (0, import_react.useState)([]);
123942
+ const [loading, setLoading] = (0, import_react.useState)(true);
123943
+ const [error, setError] = (0, import_react.useState)();
123944
+ const [selectedJob, setSelectedJob] = (0, import_react.useState)();
123945
+ const [selectedRun, setSelectedRun] = (0, import_react.useState)();
123946
+ const [selectedTreeNode, setSelectedTreeNode] = (0, import_react.useState)();
123947
+ const [selectedCheckpoint, setSelectedCheckpoint] = (0, import_react.useState)();
123948
+ const [selectedEvent, setSelectedEvent] = (0, import_react.useState)();
123949
+ useInput((char) => {
123950
+ if (char === "q") exit();
123951
+ });
123952
+ (0, import_react.useEffect)(() => {
123953
+ const loadInitial = async () => {
123954
+ try {
123955
+ setLoading(true);
123956
+ if (initialJobId && initialRunId) {
123957
+ const job = await fetcher.getJob(initialJobId);
123958
+ if (!job) {
123959
+ setError(`Job ${initialJobId} not found`);
123960
+ setLoading(false);
123961
+ return;
123962
+ }
123963
+ const [runCheckpoints, runEvents] = await Promise.all([fetcher.getCheckpointsForRun(initialJobId, initialRunId), fetcher.getEvents(initialJobId, initialRunId)]);
123964
+ setScreen({
123965
+ type: "runDetail",
123966
+ job,
123967
+ run: {
123968
+ jobId: initialJobId,
123969
+ runId: initialRunId
123970
+ },
123971
+ checkpoints: runCheckpoints,
123972
+ events: runEvents,
123973
+ tab: "activities"
123974
+ });
123975
+ setLoading(false);
123976
+ return;
123977
+ }
123978
+ if (initialJobId) {
123979
+ const job = await fetcher.getJob(initialJobId);
123980
+ if (!job) {
123981
+ setError(`Job ${initialJobId} not found`);
123982
+ setLoading(false);
123983
+ return;
123984
+ }
123985
+ const { treeState, runQueries, runStats, delegationGroups, resumeNodes } = await buildRunTree(fetcher, initialJobId);
123986
+ setScreen({
123987
+ type: "runList",
123988
+ job,
123989
+ treeState,
123990
+ runQueries,
123991
+ runStats,
123992
+ delegationGroups,
123993
+ resumeNodes
123994
+ });
123995
+ setLoading(false);
123996
+ return;
123997
+ }
123998
+ const latest = await fetcher.getLatestJob();
123999
+ if (latest) setJobItems([{
124000
+ job: latest,
124001
+ query: await extractJobQuery(fetcher, latest)
124002
+ }]);
124003
+ else setJobItems([]);
124004
+ setLoading(false);
124005
+ } catch (err) {
124006
+ setError(err instanceof Error ? err.message : String(err));
124007
+ setLoading(false);
124008
+ }
124009
+ };
124010
+ loadInitial();
124011
+ }, [
124012
+ fetcher,
124013
+ initialJobId,
124014
+ initialRunId
124015
+ ]);
124016
+ const handleChatSubmit = (0, import_react.useCallback)((_query) => {}, []);
124017
+ const navigateToJobDetail = (0, import_react.useCallback)((job) => {
124018
+ setScreen({
124019
+ type: "jobDetail",
124020
+ job
124021
+ });
124022
+ }, []);
124023
+ const navigateToRunList = (0, import_react.useCallback)(async (job) => {
124024
+ setLoading(true);
124025
+ try {
124026
+ const { treeState, runQueries, runStats, delegationGroups, resumeNodes } = await buildRunTree(fetcher, job.id);
124027
+ setScreen({
124028
+ type: "runList",
124029
+ job,
124030
+ treeState,
124031
+ runQueries,
124032
+ runStats,
124033
+ delegationGroups,
124034
+ resumeNodes
124035
+ });
124036
+ } catch (err) {
124037
+ setError(err instanceof Error ? err.message : String(err));
124038
+ }
124039
+ setLoading(false);
124040
+ }, [fetcher]);
124041
+ const navigateToRunDetail = (0, import_react.useCallback)(async (job, run, tab = "activities") => {
124042
+ setLoading(true);
124043
+ try {
124044
+ const [runCheckpoints, runEvents] = await Promise.all([fetcher.getCheckpointsForRun(job.id, run.runId), fetcher.getEvents(job.id, run.runId)]);
124045
+ setScreen({
124046
+ type: "runDetail",
124047
+ job,
124048
+ run,
124049
+ checkpoints: runCheckpoints,
124050
+ events: runEvents,
124051
+ tab
124052
+ });
124053
+ } catch (err) {
124054
+ setError(err instanceof Error ? err.message : String(err));
124055
+ }
124056
+ setLoading(false);
124057
+ }, [fetcher]);
124058
+ const navigateToCheckpointDetail = (0, import_react.useCallback)((job, run, checkpoint) => {
124059
+ setScreen({
124060
+ type: "checkpointDetail",
124061
+ job,
124062
+ run,
124063
+ checkpoint
124064
+ });
124065
+ }, []);
124066
+ const navigateToEventDetail = (0, import_react.useCallback)((job, run, event) => {
124067
+ setScreen({
124068
+ type: "eventDetail",
124069
+ job,
124070
+ run,
124071
+ event
124072
+ });
124073
+ }, []);
124074
+ const screenRef = (0, import_react.useRef)(screen);
124075
+ screenRef.current = screen;
124076
+ const handleRunSelectedChange = (0, import_react.useCallback)((node) => {
124077
+ const currentScreen = screenRef.current;
124078
+ if (currentScreen.type !== "runList") return;
124079
+ setSelectedTreeNode(node);
124080
+ setSelectedRun(node ? {
124081
+ jobId: currentScreen.job.id,
124082
+ runId: node.runId,
124083
+ expertKey: node.expertKey
124084
+ } : void 0);
124085
+ }, []);
124086
+ const renderInfoContent = () => {
124087
+ switch (screen.type) {
124088
+ case "jobList": return selectedJob ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(JobInfoContent, { job: selectedJob }) : null;
124089
+ case "jobDetail": return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(JobInfoContent, { job: screen.job });
124090
+ case "runList": {
124091
+ if (!selectedTreeNode) return null;
124092
+ const stats = screen.runStats.get(selectedTreeNode.runId);
124093
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RunInfoContent, {
124094
+ job: screen.job,
124095
+ run: selectedRun,
124096
+ treeNode: selectedTreeNode,
124097
+ providerName: screen.treeState.providerName,
124098
+ checkpointCount: stats?.stepCount,
124099
+ eventCount: stats?.eventCount
124100
+ });
124101
+ }
124102
+ case "runDetail":
124103
+ if (screen.tab === "checkpoints" && selectedCheckpoint) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CheckpointInfoContent, { checkpoint: selectedCheckpoint });
124104
+ if ((screen.tab === "events" || screen.tab === "activities") && selectedEvent) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EventInfoContent, { event: selectedEvent });
124105
+ return null;
124106
+ case "checkpointDetail": return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CheckpointInfoContent, { checkpoint: screen.checkpoint });
124107
+ case "eventDetail": return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EventInfoContent, { event: screen.event });
124108
+ default: return null;
124109
+ }
124110
+ };
124111
+ if (error) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
124112
+ flexDirection: "column",
124113
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
124114
+ color: colors.destructive,
124115
+ children: ["Error: ", error]
124116
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
124117
+ color: colors.muted,
124118
+ children: "Press q to quit"
124119
+ })]
124120
+ });
124121
+ if (loading) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
124122
+ color: colors.muted,
124123
+ children: "Loading..."
124124
+ });
124125
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
124126
+ flexDirection: "column",
124127
+ flexGrow: 1,
124128
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
124129
+ flexDirection: "column",
124130
+ flexGrow: 1,
124131
+ children: [
124132
+ screen.type === "jobList" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(JobListScreen, {
124133
+ items: jobItems,
124134
+ onSelectJob: (job) => navigateToRunList(job),
124135
+ onViewJobDetail: navigateToJobDetail,
124136
+ onSelectedChange: setSelectedJob
124137
+ }),
124138
+ screen.type === "jobDetail" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(JobDetailScreen, {
124139
+ job: screen.job,
124140
+ onBack: () => setScreen({ type: "jobList" }),
124141
+ onViewRuns: navigateToRunList
124142
+ }),
124143
+ screen.type === "runList" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RunListScreen, {
124144
+ job: screen.job,
124145
+ treeState: screen.treeState,
124146
+ runQueries: screen.runQueries,
124147
+ delegationGroups: screen.delegationGroups,
124148
+ resumeNodes: screen.resumeNodes,
124149
+ onSelectRun: (node) => {
124150
+ const run = {
124151
+ jobId: screen.job.id,
124152
+ runId: node.runId,
124153
+ expertKey: node.expertKey
124154
+ };
124155
+ navigateToRunDetail(screen.job, run);
124156
+ },
124157
+ onBack: () => setScreen({ type: "jobList" }),
124158
+ onSelectedChange: handleRunSelectedChange
124159
+ }),
124160
+ screen.type === "runDetail" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RunDetailScreen, {
124161
+ job: screen.job,
124162
+ run: screen.run,
124163
+ checkpoints: screen.checkpoints,
124164
+ events: screen.events,
124165
+ initialTab: screen.tab,
124166
+ onSelectCheckpoint: (cp) => navigateToCheckpointDetail(screen.job, screen.run, cp),
124167
+ onSelectEvent: (ev) => navigateToEventDetail(screen.job, screen.run, ev),
124168
+ onBack: () => navigateToRunList(screen.job),
124169
+ onTabChange: (tab) => {
124170
+ setScreen({
124171
+ ...screen,
124172
+ tab
124173
+ });
124174
+ },
124175
+ onSelectedCheckpointChange: setSelectedCheckpoint,
124176
+ onSelectedEventChange: setSelectedEvent
124177
+ }),
124178
+ screen.type === "checkpointDetail" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CheckpointDetailScreen, {
124179
+ job: screen.job,
124180
+ run: screen.run,
124181
+ checkpoint: screen.checkpoint,
124182
+ onBack: () => navigateToRunDetail(screen.job, screen.run, "checkpoints")
124183
+ }),
124184
+ screen.type === "eventDetail" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EventDetailScreen, {
124185
+ job: screen.job,
124186
+ run: screen.run,
124187
+ event: screen.event,
124188
+ onBack: () => navigateToRunDetail(screen.job, screen.run, "events")
124189
+ })
124190
+ ]
124191
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BottomPanel, {
124192
+ onSubmit: handleChatSubmit,
124193
+ inputPlaceholder: "Ask AI about this log...",
124194
+ children: renderInfoContent() ?? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
124195
+ color: colors.muted,
124196
+ children: "Select an item to view details"
124197
+ })
124198
+ })]
124199
+ });
124200
+ };
124201
+ //#endregion
124202
+ //#region ../../packages/tui-components/src/log-viewer/render.tsx
124203
+ async function renderLogViewer(params) {
124204
+ const { waitUntilExit } = render(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(LogViewerApp, {
124205
+ fetcher: params.fetcher,
124206
+ initialJobId: params.initialJobId,
124207
+ initialRunId: params.initialRunId
124208
+ }));
124209
+ await waitUntilExit();
124210
+ }
124211
+ //#endregion
121588
124212
  //#region ../../packages/tui-components/src/selection/app.tsx
121589
124213
  const selectionReducer = (_state, action) => {
121590
124214
  switch (action.type) {
@@ -121891,7 +124515,7 @@ async function startHandler(expertKey, query, options, handlerOptions) {
121891
124515
  //#endregion
121892
124516
  //#region package.json
121893
124517
  var name = "perstack";
121894
- var version = "0.0.124";
124518
+ var version = "0.0.127";
121895
124519
  var description = "PerStack CLI";
121896
124520
  //#endregion
121897
124521
  //#region bin/cli.ts
@@ -121926,7 +124550,16 @@ program.command("run").description("Run Perstack with JSON output").argument("<e
121926
124550
  lockfile
121927
124551
  });
121928
124552
  });
121929
- program.command("log").description("View execution history and events for debugging").option("--job <jobId>", "Show events for a specific job").option("--run <runId>", "Show events for a specific run").option("--checkpoint <checkpointId>", "Show checkpoint details").option("--step <step>", "Filter by step number (e.g., 5, >5, 1-10)").option("--type <type>", "Filter by event type").option("--errors", "Show error-related events only").option("--tools", "Show tool call events only").option("--delegations", "Show delegation events only").option("--filter <expression>", "Simple filter expression").option("--json", "Output as JSON").option("--pretty", "Pretty-print JSON output").option("--verbose", "Show full event details").option("--take <n>", "Number of events to display (default: 100, use 0 for all)", (val) => parsePositiveInt(val, "--take")).option("--offset <n>", "Number of events to skip (default: 0)", (val) => parsePositiveInt(val, "--offset")).option("--context <n>", "Include N events before/after matches", (val) => parsePositiveInt(val, "--context")).option("--messages", "Show message history for checkpoint").option("--summary", "Show summarized view").action((options) => logHandler(options));
124553
+ program.command("log").description("View execution history and events for debugging").option("--job <jobId>", "Show events for a specific job").option("--run <runId>", "Show events for a specific run").option("--checkpoint <checkpointId>", "Show checkpoint details").option("--step <step>", "Filter by step number (e.g., 5, >5, 1-10)").option("--type <type>", "Filter by event type").option("--errors", "Show error-related events only").option("--tools", "Show tool call events only").option("--delegations", "Show delegation events only").option("--filter <expression>", "Simple filter expression").option("--json", "Output as JSON").option("--pretty", "Pretty-print JSON output").option("--verbose", "Show full event details").option("--take <n>", "Number of events to display (default: 100, use 0 for all)", (val) => parsePositiveInt(val, "--take")).option("--offset <n>", "Number of events to skip (default: 0)", (val) => parsePositiveInt(val, "--offset")).option("--context <n>", "Include N events before/after matches", (val) => parsePositiveInt(val, "--context")).option("--messages", "Show message history for checkpoint").option("--summary", "Show summarized view").option("--text", "Force text output mode (skip interactive TUI)").action(async (options) => {
124554
+ const hasOutputFlags = options.json || options.pretty || options.summary || options.messages || options.text;
124555
+ const hasFilterFlags = options.step || options.type || options.errors || options.tools || options.delegations || options.filter || options.verbose || options.take !== void 0 || options.offset !== void 0 || options.context !== void 0;
124556
+ if (hasOutputFlags || hasFilterFlags) await logHandler(options);
124557
+ else await renderLogViewer({
124558
+ fetcher: createLogDataFetcher(createStorageAdapter(process.env.PERSTACK_STORAGE_PATH ?? `${process.cwd()}/perstack`)),
124559
+ initialJobId: options.job,
124560
+ initialRunId: options.run
124561
+ });
124562
+ });
121930
124563
  program.command("install").description("Generate perstack.lock with tool definitions for faster startup").option("--config <configPath>", "Path to perstack.toml config file").option("--env-path <path>", "Path to the environment file (can be specified multiple times)", (value, previous) => previous.concat(value), []).action(async (options) => {
121931
124564
  await installHandler({
121932
124565
  configPath: await findConfigPath(options.config),