@pantheon.ai/agents 0.0.15 → 0.0.16

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.
Files changed (2) hide show
  1. package/dist/index.js +659 -224
  2. package/package.json +3 -1
package/dist/index.js CHANGED
@@ -16,6 +16,7 @@ import { Http2ServerRequest } from "http2";
16
16
  import { Readable } from "stream";
17
17
  import blessed from "reblessed";
18
18
  import { inspect } from "node:util";
19
+ import { parse } from "shell-quote";
19
20
 
20
21
  //#region ../../node_modules/dotenv/package.json
21
22
  var require_package = /* @__PURE__ */ __commonJSMin(((exports, module) => {
@@ -394,7 +395,7 @@ var require_cli_options = /* @__PURE__ */ __commonJSMin(((exports, module) => {
394
395
 
395
396
  //#endregion
396
397
  //#region package.json
397
- var version$1 = "0.0.15";
398
+ var version$1 = "0.0.16";
398
399
 
399
400
  //#endregion
400
401
  //#region src/schemas/task-list.ts
@@ -2344,7 +2345,7 @@ const _parse$1 = (_Err) => (schema, value, _ctx, _params) => {
2344
2345
  }
2345
2346
  return result.value;
2346
2347
  };
2347
- const parse$1 = /* @__PURE__ */ _parse$1($ZodRealError);
2348
+ const parse$2 = /* @__PURE__ */ _parse$1($ZodRealError);
2348
2349
  const _parseAsync = (_Err) => async (schema, value, _ctx, params) => {
2349
2350
  const ctx = _ctx ? Object.assign(_ctx, { async: true }) : { async: true };
2350
2351
  let result = schema._zod.run({
@@ -5521,7 +5522,7 @@ const ZodRealError = $constructor("ZodError", initializer, { Parent: Error });
5521
5522
 
5522
5523
  //#endregion
5523
5524
  //#region ../../node_modules/zod/v4/classic/parse.js
5524
- const parse = /* @__PURE__ */ _parse$1(ZodRealError);
5525
+ const parse$1 = /* @__PURE__ */ _parse$1(ZodRealError);
5525
5526
  const parseAsync = /* @__PURE__ */ _parseAsync(ZodRealError);
5526
5527
  const safeParse$1 = /* @__PURE__ */ _safeParse(ZodRealError);
5527
5528
  const safeParseAsync$1 = /* @__PURE__ */ _safeParseAsync(ZodRealError);
@@ -5560,7 +5561,7 @@ const ZodType$1 = /* @__PURE__ */ $constructor("ZodType", (inst, def) => {
5560
5561
  reg.add(inst, meta);
5561
5562
  return inst;
5562
5563
  });
5563
- inst.parse = (data, params) => parse(inst, data, params, { callee: inst.parse });
5564
+ inst.parse = (data, params) => parse$1(inst, data, params, { callee: inst.parse });
5564
5565
  inst.safeParse = (data, params) => safeParse$1(inst, data, params);
5565
5566
  inst.parseAsync = async (data, params) => parseAsync(inst, data, params, { callee: inst.parseAsync });
5566
5567
  inst.safeParseAsync = async (data, params) => safeParseAsync$1(inst, data, params);
@@ -17342,32 +17343,40 @@ async function consumeOpaqueSseStream(options) {
17342
17343
  };
17343
17344
  }
17344
17345
  var WatchStepAggregator = class {
17345
- maxLeadingTextChars;
17346
- maxLeadingReasoningChars;
17347
17346
  maxLogLines;
17348
17347
  maxWarnings;
17349
- leadingText = "";
17350
- leadingReasoning = "";
17351
17348
  toolActions = /* @__PURE__ */ new Map();
17349
+ timeline = [];
17352
17350
  logLines = [];
17351
+ todoItems = [];
17353
17352
  warnings = [];
17353
+ warningItems = [];
17354
17354
  unknownEventCount = 0;
17355
17355
  orderCounter = 0;
17356
17356
  constructor(options = {}) {
17357
- this.maxLeadingTextChars = options.maxLeadingTextChars ?? 800;
17358
- this.maxLeadingReasoningChars = options.maxLeadingReasoningChars ?? 800;
17359
17357
  this.maxLogLines = options.maxLogLines ?? 200;
17360
17358
  this.maxWarnings = options.maxWarnings ?? 50;
17361
17359
  }
17362
17360
  pushLog(line) {
17363
17361
  if (!line) return;
17364
17362
  this.logLines.push(line);
17365
- if (this.logLines.length > this.maxLogLines) this.logLines.splice(0, this.logLines.length - this.maxLogLines);
17363
+ if (Number.isFinite(this.maxLogLines) && this.maxLogLines > 0 && this.logLines.length > this.maxLogLines) this.logLines.splice(0, this.logLines.length - this.maxLogLines);
17366
17364
  }
17367
17365
  pushWarning(message) {
17368
17366
  if (!message) return;
17367
+ const order = this.orderCounter++;
17369
17368
  this.warnings.push(message);
17369
+ this.warningItems.push({
17370
+ message,
17371
+ order
17372
+ });
17373
+ this.timeline.push({
17374
+ kind: "warning",
17375
+ message,
17376
+ order
17377
+ });
17370
17378
  if (this.warnings.length > this.maxWarnings) this.warnings.splice(0, this.warnings.length - this.maxWarnings);
17379
+ if (this.warningItems.length > this.maxWarnings) this.warningItems.splice(0, this.warningItems.length - this.maxWarnings);
17371
17380
  }
17372
17381
  pushCommandExecutionOutput(output) {
17373
17382
  const out = output;
@@ -17377,6 +17386,31 @@ var WatchStepAggregator = class {
17377
17386
  if (trimmed) trimmed.split("\n").forEach((line) => this.pushLog(line));
17378
17387
  if (exitCode != null && exitCode !== 0) this.pushLog(`exit_code=${exitCode}`);
17379
17388
  }
17389
+ setTodoItems(items) {
17390
+ if (!Array.isArray(items)) return;
17391
+ const normalized = [];
17392
+ for (const item of items) {
17393
+ if (typeof item !== "object" || item === null) continue;
17394
+ const text = typeof item.text === "string" ? item.text : "";
17395
+ const completed = Boolean(item.completed);
17396
+ if (!text) continue;
17397
+ normalized.push({
17398
+ text,
17399
+ completed
17400
+ });
17401
+ }
17402
+ this.todoItems = normalized;
17403
+ }
17404
+ pushTimelineText(kind, delta) {
17405
+ if (!delta) return this.orderCounter;
17406
+ const order = this.orderCounter++;
17407
+ this.timeline.push({
17408
+ kind,
17409
+ order,
17410
+ text: delta
17411
+ });
17412
+ return order;
17413
+ }
17380
17414
  getOrCreateToolAction(toolCallId) {
17381
17415
  const existing = this.toolActions.get(toolCallId);
17382
17416
  if (existing) return existing;
@@ -17386,6 +17420,11 @@ var WatchStepAggregator = class {
17386
17420
  _order: this.orderCounter++
17387
17421
  };
17388
17422
  this.toolActions.set(toolCallId, created);
17423
+ this.timeline.push({
17424
+ kind: "tool",
17425
+ order: created._order,
17426
+ toolCallId
17427
+ });
17389
17428
  return created;
17390
17429
  }
17391
17430
  pushUiChunk(chunk) {
@@ -17396,19 +17435,13 @@ var WatchStepAggregator = class {
17396
17435
  case "text-delta": {
17397
17436
  const delta = typeof chunk.delta === "string" ? chunk.delta : "";
17398
17437
  if (!delta) return;
17399
- if (this.leadingText.length < this.maxLeadingTextChars) {
17400
- const remaining = this.maxLeadingTextChars - this.leadingText.length;
17401
- this.leadingText += delta.slice(0, remaining);
17402
- }
17438
+ this.pushTimelineText("message", delta);
17403
17439
  return;
17404
17440
  }
17405
17441
  case "reasoning-delta": {
17406
17442
  const delta = typeof chunk.delta === "string" ? chunk.delta : "";
17407
17443
  if (!delta) return;
17408
- if (this.leadingReasoning.length < this.maxLeadingReasoningChars) {
17409
- const remaining = this.maxLeadingReasoningChars - this.leadingReasoning.length;
17410
- this.leadingReasoning += delta.slice(0, remaining);
17411
- }
17444
+ this.pushTimelineText("reasoning", delta);
17412
17445
  return;
17413
17446
  }
17414
17447
  case "tool-input-start": {
@@ -17476,6 +17509,11 @@ var WatchStepAggregator = class {
17476
17509
  this.pushLog(`! unknown: ${reason}`);
17477
17510
  return;
17478
17511
  }
17512
+ case "data-agent-event": {
17513
+ const data = chunk.data;
17514
+ if (data?.kind === "codex.todo_list") this.setTodoItems(data.items);
17515
+ return;
17516
+ }
17479
17517
  default: return;
17480
17518
  }
17481
17519
  }
@@ -17483,13 +17521,16 @@ var WatchStepAggregator = class {
17483
17521
  chunks.forEach((chunk) => this.pushUiChunk(chunk));
17484
17522
  }
17485
17523
  snapshot() {
17486
- const actions = Array.from(this.toolActions.values()).sort((a, b) => a._order - b._order).map(({ _order: _unused, ...rest }) => rest);
17487
17524
  return {
17488
- leadingText: this.leadingText,
17489
- leadingReasoning: this.leadingReasoning,
17490
- toolActions: actions,
17525
+ toolActions: Array.from(this.toolActions.values()).sort((a, b) => a._order - b._order).map(({ _order, ...rest }) => ({
17526
+ ...rest,
17527
+ order: _order
17528
+ })),
17529
+ timeline: this.timeline.map((item) => ({ ...item })),
17491
17530
  logLines: [...this.logLines],
17531
+ todoItems: this.todoItems.map((item) => ({ ...item })),
17492
17532
  warnings: [...this.warnings],
17533
+ warningItems: this.warningItems.map((item) => ({ ...item })),
17493
17534
  unknownEventCount: this.unknownEventCount
17494
17535
  };
17495
17536
  }
@@ -21138,7 +21179,7 @@ const ZodMiniType = /* @__PURE__ */ $constructor("ZodMiniType", (inst, def) => {
21138
21179
  $ZodType.init(inst, def);
21139
21180
  inst.def = def;
21140
21181
  inst.type = def.type;
21141
- inst.parse = (data, params) => parse$1(inst, data, params, { callee: inst.parse });
21182
+ inst.parse = (data, params) => parse$2(inst, data, params, { callee: inst.parse });
21142
21183
  inst.safeParse = (data, params) => safeParse$2(inst, data, params);
21143
21184
  inst.parseAsync = async (data, params) => parseAsync$1(inst, data, params, { callee: inst.parseAsync });
21144
21185
  inst.safeParseAsync = async (data, params) => safeParseAsync$2(inst, data, params);
@@ -32085,36 +32126,150 @@ function tryExtractBashCommand(command) {
32085
32126
  }
32086
32127
  return rest.trim();
32087
32128
  }
32088
- function formatCommandExecutionDisplayCommand(command) {
32129
+ function normalizeCommand(command) {
32089
32130
  const trimmed = command.trim();
32090
32131
  return tryExtractBashCommand(trimmed)?.trim() || trimmed;
32091
32132
  }
32092
- function formatPreviewLine(label, text, options = {}) {
32093
- const trimmed = text.trim();
32094
- if (!trimmed) return void 0;
32095
- const oneLine = trimmed.replaceAll(/\s+/g, " ");
32096
- const max = options.maxChars ?? 220;
32097
- const preview = oneLine.length > max ? oneLine.slice(0, max - 1) + "…" : oneLine;
32098
- return `${label}: ${options.formatValue ? options.formatValue(preview) : preview}`;
32133
+ function splitCommandLines(command) {
32134
+ return command.replaceAll("\r\n", "\n").split("\n").map((line) => line.trim()).filter(Boolean);
32135
+ }
32136
+ function stripCommentPrefix(line) {
32137
+ const trimmed = line.trim();
32138
+ if (trimmed.startsWith("//")) return trimmed.slice(2).trimStart();
32139
+ return trimmed;
32140
+ }
32141
+ function commandBasename(value) {
32142
+ const lastSlash = value.lastIndexOf("/");
32143
+ return lastSlash === -1 ? value : value.slice(lastSlash + 1);
32144
+ }
32145
+ function parseShellEntries(line) {
32146
+ try {
32147
+ return parse(line);
32148
+ } catch {
32149
+ return;
32150
+ }
32151
+ }
32152
+ function takeMainTokens(entries) {
32153
+ const stopSet = new Set([
32154
+ "|",
32155
+ "||",
32156
+ "&&",
32157
+ ";",
32158
+ "|&",
32159
+ ";;",
32160
+ "&",
32161
+ "(",
32162
+ ")",
32163
+ "<",
32164
+ ">",
32165
+ ">>",
32166
+ ">&",
32167
+ "<("
32168
+ ]);
32169
+ const out = [];
32170
+ for (const entry of entries) {
32171
+ if (typeof entry === "string") {
32172
+ out.push(entry);
32173
+ continue;
32174
+ }
32175
+ if ("op" in entry) {
32176
+ if (stopSet.has(entry.op)) break;
32177
+ continue;
32178
+ }
32179
+ if ("comment" in entry) break;
32180
+ }
32181
+ return out;
32182
+ }
32183
+ function pickLastNonOptionArg(tokens) {
32184
+ const values = tokens.filter((token) => !token.startsWith("-"));
32185
+ return values.length > 0 ? values[values.length - 1] : void 0;
32186
+ }
32187
+ function parseExploredItem(line) {
32188
+ const entries = parseShellEntries(normalizeCommand(line));
32189
+ if (!entries) return void 0;
32190
+ const tokens = takeMainTokens(entries);
32191
+ if (tokens.length === 0) return void 0;
32192
+ const command = commandBasename(tokens[0] ?? "");
32193
+ if (command === "rg") {
32194
+ const args = tokens.slice(1);
32195
+ if (args.includes("--files")) return {
32196
+ kind: "list",
32197
+ target: pickLastNonOptionArg(args)
32198
+ };
32199
+ const firstValueIndex = args.findIndex((token) => !token.startsWith("-"));
32200
+ if (firstValueIndex === -1) return void 0;
32201
+ const terms = args[firstValueIndex];
32202
+ const file = args[firstValueIndex + 1];
32203
+ if (!terms || !file) return void 0;
32204
+ return {
32205
+ kind: "search",
32206
+ terms,
32207
+ file
32208
+ };
32209
+ }
32210
+ if (command === "ls") return {
32211
+ kind: "list",
32212
+ target: pickLastNonOptionArg(tokens.slice(1))
32213
+ };
32214
+ if (command === "sed" || command === "nl" || command === "cat") {
32215
+ if (tokens.length < 2) return void 0;
32216
+ const file = tokens[tokens.length - 1];
32217
+ if (!file || file.startsWith("-")) return void 0;
32218
+ return {
32219
+ kind: "read",
32220
+ file
32221
+ };
32222
+ }
32223
+ }
32224
+ function parseExploredItemsFromCommand(command) {
32225
+ const rawLines = splitCommandLines(normalizeCommand(command));
32226
+ const items = [];
32227
+ for (const rawLine of rawLines) {
32228
+ const line = stripCommentPrefix(rawLine);
32229
+ if (!line) continue;
32230
+ const item = parseExploredItem(normalizeCommand(line));
32231
+ if (!item) return void 0;
32232
+ items.push(item);
32233
+ }
32234
+ if (items.length === 0) return void 0;
32235
+ return items;
32236
+ }
32237
+ function formatExploredItemsLines(items) {
32238
+ const lines = ["Explored"];
32239
+ for (const item of items) {
32240
+ if (item.kind === "read") {
32241
+ lines.push(`Read ${item.file}`);
32242
+ continue;
32243
+ }
32244
+ if (item.kind === "list") {
32245
+ lines.push(item.target ? `List ${item.target}` : "List");
32246
+ continue;
32247
+ }
32248
+ lines.push(`Search ${item.terms} in ${item.file}`);
32249
+ }
32250
+ return lines;
32251
+ }
32252
+ function getExploredCommandLines(command) {
32253
+ const explored = parseExploredItemsFromCommand(command);
32254
+ if (!explored) return void 0;
32255
+ return formatExploredItemsLines(explored);
32256
+ }
32257
+ function isExploredCommandExecution(command) {
32258
+ return parseExploredItemsFromCommand(command) != null;
32259
+ }
32260
+ function formatCommandExecutionDisplayCommand(command) {
32261
+ const exploredLines = getExploredCommandLines(command);
32262
+ if (exploredLines) return exploredLines.join("\n");
32263
+ return normalizeCommand(command);
32099
32264
  }
32100
32265
 
32101
32266
  //#endregion
32102
32267
  //#region src/cli/commands/watch.ts
32103
32268
  const PANTHEON_BASE_URL = "https://pantheon-ai.tidb.ai";
32104
- const DEFAULT_SCREEN_GRID = {
32105
- cols: 3,
32106
- rows: 1
32107
- };
32108
- function parseScreenGrid(value) {
32109
- const match = value.trim().toLowerCase().match(/^(\d+)x(\d+)$/);
32110
- if (!match) throw new InvalidArgumentError("screen must be <cols>x<rows>, e.g. 3x1 or 5x4");
32111
- const cols = Number(match[1]);
32112
- const rows = Number(match[2]);
32113
- if (!Number.isInteger(cols) || !Number.isInteger(rows) || cols <= 0 || rows <= 0) throw new InvalidArgumentError("screen requires positive integer cols/rows, e.g. 3x1");
32114
- return {
32115
- cols,
32116
- rows
32117
- };
32269
+ function parseListWidth(value) {
32270
+ const width = Number(value);
32271
+ if (!Number.isInteger(width) || width <= 0) throw new InvalidArgumentError("list-width requires a positive integer, e.g. 32");
32272
+ return width;
32118
32273
  }
32119
32274
  function ensureEnvOrExit(keys) {
32120
32275
  const missing = keys.filter((key) => !process.env[key]);
@@ -32122,33 +32277,6 @@ function ensureEnvOrExit(keys) {
32122
32277
  for (const key of missing) console.error(`${key} environment variable is not set.`);
32123
32278
  process.exit(1);
32124
32279
  }
32125
- function formatDurationMs(ms) {
32126
- const seconds = Math.max(0, Math.floor(ms / 1e3));
32127
- const minutes = Math.floor(seconds / 60);
32128
- const hours = Math.floor(minutes / 60);
32129
- if (hours > 0) return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
32130
- if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
32131
- return `${seconds}s`;
32132
- }
32133
- function getTaskStartTime(task) {
32134
- switch (task.status) {
32135
- case "pending": return task.queued_at;
32136
- case "running": return task.started_at;
32137
- case "completed":
32138
- case "failed": return task.started_at ?? task.queued_at;
32139
- case "cancelled": return task.queued_at;
32140
- }
32141
- }
32142
- function getTaskElapsedMs(task, now = /* @__PURE__ */ new Date()) {
32143
- const start = getTaskStartTime(task);
32144
- if (!start) return 0;
32145
- switch (task.status) {
32146
- case "completed": return task.ended_at.getTime() - start.getTime();
32147
- case "failed": return (task.ended_at ?? now).getTime() - start.getTime();
32148
- case "cancelled": return task.cancelled_at.getTime() - start.getTime();
32149
- default: return now.getTime() - start.getTime();
32150
- }
32151
- }
32152
32280
  function toStreamSourceFromExecuteAgent(executeAgent) {
32153
32281
  if (!executeAgent) return void 0;
32154
32282
  const normalized = executeAgent.toLowerCase();
@@ -32296,42 +32424,36 @@ function yellow(text, enabled) {
32296
32424
  function cyan(text, enabled) {
32297
32425
  return sgrWrap(text, ANSI.cyan, enabled);
32298
32426
  }
32427
+ function dimGray(text, enabled) {
32428
+ return sgrWrap(text, `${ANSI.dim}${ANSI.gray}`, enabled);
32429
+ }
32299
32430
  function kv(label, value, style) {
32300
32431
  return `${dim(`${label}:`, style)} ${value}`;
32301
32432
  }
32302
- function formatTaskStatus(status, style) {
32303
- switch (status) {
32304
- case "running": return yellow(status, style);
32305
- case "completed": return green(status, style);
32306
- case "failed": return red(status, style);
32307
- case "pending": return cyan(status, style);
32308
- case "cancelled": return gray(status, style);
32309
- }
32433
+ function getCommandExecutionDisplayCommand(tool) {
32434
+ const raw = getCommandExecutionRawCommand(tool);
32435
+ if (!raw) return void 0;
32436
+ return formatCommandExecutionDisplayCommand(raw) || void 0;
32310
32437
  }
32311
- function formatBranchStatus(status, style) {
32312
- if (!status) return gray("(no branch)", style);
32313
- switch (status) {
32314
- case "succeed": return green(status, style);
32315
- case "failed": return red(status, style);
32316
- case "running":
32317
- case "pending": return yellow(status, style);
32318
- case "manifesting":
32319
- case "ready_for_manifest": return cyan(status, style);
32320
- default: return gray(status, style);
32321
- }
32438
+ function colorizeCommandProgram(display, style) {
32439
+ const match = display.match(/^(\s*)(\S+)([\s\S]*)$/);
32440
+ if (!match) return display;
32441
+ const [, leading, program, rest] = match;
32442
+ return `${leading}${cyan(program ?? "", style)}${rest ?? ""}`;
32322
32443
  }
32323
- function getCommandExecutionDisplayCommand(tool) {
32444
+ function getCommandExecutionRawCommand(tool) {
32324
32445
  const title = typeof tool.title === "string" ? tool.title : "";
32325
32446
  const inputCommand = typeof tool.input?.command === "string" ? tool.input.command : "";
32326
- const raw = title || inputCommand;
32327
- if (!raw) return void 0;
32328
- return formatCommandExecutionDisplayCommand(raw) || void 0;
32447
+ return title || inputCommand || void 0;
32329
32448
  }
32330
32449
  function formatToolLabel(tool, style) {
32331
32450
  const name = tool.toolName ?? "<tool>";
32332
32451
  if (name === "command_execution") {
32452
+ const rawCommand = getCommandExecutionRawCommand(tool);
32453
+ const exploredLines = rawCommand ? getExploredCommandLines(rawCommand) : void 0;
32454
+ if (exploredLines && exploredLines.length > 1) return cyan(exploredLines[0] ?? "Explored", style);
32333
32455
  const display = getCommandExecutionDisplayCommand(tool) ?? "(unknown command)";
32334
- return `${bold("$", style)} ${cyan(display, style)}`;
32456
+ return `${bold("$", style)} ${colorizeCommandProgram(display, style)}`;
32335
32457
  }
32336
32458
  const renderedName = bold(name, style);
32337
32459
  if (!tool.title) return renderedName;
@@ -32346,45 +32468,56 @@ function formatToolSummaryLine(tool, style) {
32346
32468
  default: return `${yellow("…", style)} ${label} (${id})`;
32347
32469
  }
32348
32470
  }
32349
- function toDisplayLines(text) {
32350
- return text.replaceAll("\r\n", "\n").split("\n");
32471
+ function getToolStatus(value) {
32472
+ if (value === "completed" || value === "failed") return value;
32473
+ return "in_progress";
32351
32474
  }
32352
- function wrapCollapsedWhitespaceToWidth(text, width) {
32353
- const collapsed = text.trim().replaceAll(/\s+/g, " ");
32354
- if (!collapsed) return [];
32355
- if (width <= 0) return [collapsed];
32356
- const words = collapsed.split(" ");
32357
- const lines = [];
32358
- let current = "";
32359
- function pushLongWord(word) {
32360
- for (let i = 0; i < word.length; i += width) {
32361
- const chunk = word.slice(i, i + width);
32362
- if (chunk.length === width) lines.push(chunk);
32363
- else current = chunk;
32364
- }
32365
- }
32366
- for (const word of words) {
32367
- if (!current) {
32368
- if (word.length <= width) current = word;
32369
- else pushLongWord(word);
32370
- continue;
32371
- }
32372
- if (current.length + 1 + word.length <= width) {
32373
- current += ` ${word}`;
32374
- continue;
32475
+ function combineToolStatuses(statuses) {
32476
+ const normalized = statuses.map((status) => getToolStatus(status));
32477
+ if (normalized.some((status) => status === "failed")) return "failed";
32478
+ if (normalized.some((status) => status === "in_progress")) return "in_progress";
32479
+ return "completed";
32480
+ }
32481
+ function formatToolStatusIcon(status, style) {
32482
+ switch (getToolStatus(status)) {
32483
+ case "completed": return green("✓", style);
32484
+ case "failed": return red("✗", style);
32485
+ default: return yellow("…", style);
32486
+ }
32487
+ }
32488
+ function getExploredSubCommandLines(tool) {
32489
+ if (tool.toolName !== "command_execution") return void 0;
32490
+ const rawCommand = getCommandExecutionRawCommand(tool);
32491
+ if (!rawCommand) return void 0;
32492
+ const exploredLines = getExploredCommandLines(rawCommand);
32493
+ if (!exploredLines || exploredLines.length <= 1) return void 0;
32494
+ return exploredLines.slice(1);
32495
+ }
32496
+ function formatExploredSubCommandLine(line, style) {
32497
+ if (line.startsWith("Search ")) {
32498
+ const payload = line.slice(7);
32499
+ const at = payload.lastIndexOf(" in ");
32500
+ if (at !== -1) {
32501
+ const terms = payload.slice(0, at);
32502
+ const file = payload.slice(at + 4);
32503
+ return `${cyan("Search", style)} ${terms} ${dimGray("in", style)} ${file}`;
32375
32504
  }
32376
- lines.push(current);
32377
- current = "";
32378
- if (word.length <= width) current = word;
32379
- else pushLongWord(word);
32505
+ return `${cyan("Search", style)} ${payload}`;
32380
32506
  }
32381
- if (current) lines.push(current);
32382
- return lines;
32507
+ if (line.startsWith("Read ")) return `${cyan("Read", style)} ${line.slice(5)}`;
32508
+ if (line === "List") return cyan("List", style);
32509
+ if (line.startsWith("List ")) return `${cyan("List", style)} ${line.slice(5)}`;
32510
+ return line;
32383
32511
  }
32384
- function formatWrappedTextLines(text, options) {
32385
- const trimmed = text.trim();
32386
- if (!trimmed) return [];
32387
- return trimLinesWithEllipsis(wrapCollapsedWhitespaceToWidth(trimmed, Math.max(10, options.widthHint)), options.maxLines).map((line) => options.formatValue ? options.formatValue(line) : line);
32512
+ function toDisplayLines(text) {
32513
+ return text.replaceAll("\r\n", "\n").split("\n");
32514
+ }
32515
+ function wrapRawLineToWidth(line, width) {
32516
+ if (width <= 0) return [line];
32517
+ if (line.length === 0) return [""];
32518
+ const out = [];
32519
+ for (let i = 0; i < line.length; i += width) out.push(line.slice(i, i + width));
32520
+ return out;
32388
32521
  }
32389
32522
  function trimLinesWithEllipsis(lines, maxLines) {
32390
32523
  if (maxLines <= 0) return [];
@@ -32395,14 +32528,34 @@ function trimLinesWithEllipsis(lines, maxLines) {
32395
32528
  kept[lastIndex] = last ? `${last}…` : "…";
32396
32529
  return kept;
32397
32530
  }
32531
+ function trimCommandOutputTopBottom(lines) {
32532
+ if (lines.length <= 4) return lines;
32533
+ const hidden = lines.length - 4;
32534
+ return [
32535
+ lines[0],
32536
+ lines[1],
32537
+ `… +${hidden} lines`,
32538
+ lines[lines.length - 2],
32539
+ lines[lines.length - 1]
32540
+ ];
32541
+ }
32398
32542
  function formatToolResultPreviewLines(tool, options) {
32399
32543
  if (tool.status === "in_progress") return [];
32400
32544
  if (options.maxLines <= 0) return [];
32401
- if (tool.status === "failed") return trimLinesWithEllipsis(toDisplayLines(tool.errorText ?? "(no error text)"), options.maxLines);
32545
+ if (tool.toolName === "command_execution") {
32546
+ const rawCommand = getCommandExecutionRawCommand(tool);
32547
+ if (rawCommand && isExploredCommandExecution(rawCommand)) return [];
32548
+ }
32549
+ if (tool.status === "failed") {
32550
+ const lines = toDisplayLines(tool.errorText ?? "(no error text)");
32551
+ return trimLinesWithEllipsis(tool.toolName === "command_execution" ? trimCommandOutputTopBottom(lines) : lines, options.maxLines);
32552
+ }
32402
32553
  if (tool.output == null) return ["(none)"];
32403
32554
  if (tool.toolName === "command_execution") {
32404
32555
  const output = tool.output;
32405
- return trimLinesWithEllipsis(toDisplayLines((typeof output?.aggregated_output === "string" ? output.aggregated_output : "").trimEnd()), options.maxLines);
32556
+ const allLines = toDisplayLines((typeof output?.aggregated_output === "string" ? output.aggregated_output : "").trimEnd());
32557
+ if (!allLines.some((line) => line.length > 0)) return ["(none)"];
32558
+ return trimLinesWithEllipsis(trimCommandOutputTopBottom(allLines), options.maxLines);
32406
32559
  }
32407
32560
  return trimLinesWithEllipsis(toDisplayLines(inspect(tool.output, {
32408
32561
  depth: 6,
@@ -32442,8 +32595,59 @@ function clip(value, maxVisible) {
32442
32595
  const sliced = sliceSgrByVisibleWidth(value, maxVisible - 1);
32443
32596
  return `${sliced}…${sliced.includes("\x1B[") ? ANSI.reset : ""}`;
32444
32597
  }
32598
+ const TODO_PREVIEW_MAX_ITEMS = 3;
32599
+ const TODO_PREVIEW_MAX_PENDING = 2;
32600
+ function selectTodoPreviewItems(todoItems, options) {
32601
+ const selected = [];
32602
+ let pendingCount = 0;
32603
+ for (let index = todoItems.length - 1; index >= 0; index--) {
32604
+ const item = todoItems[index];
32605
+ if (selected.length >= options.maxItems) break;
32606
+ if (!item.completed && pendingCount >= options.maxPending) continue;
32607
+ selected.push({
32608
+ ordinal: index + 1,
32609
+ item
32610
+ });
32611
+ if (!item.completed) pendingCount += 1;
32612
+ }
32613
+ selected.reverse();
32614
+ return selected;
32615
+ }
32616
+ function wrapTodoItemLine(entry, widthHint) {
32617
+ const { ordinal, item } = entry;
32618
+ const normalizedText = item.text.trim().replaceAll(/\s+/g, " ") || "(empty)";
32619
+ const prefix = `${ordinal}. ${item.completed ? "[x]" : "[ ]"} `;
32620
+ return wrapRawLineToWidth(normalizedText, Math.max(1, widthHint - prefix.length)).map((chunk, chunkIndex) => chunkIndex === 0 ? `${prefix}${chunk}` : `${" ".repeat(prefix.length)}${chunk}`);
32621
+ }
32622
+ function trimLinesToHeight(lines, maxLines, style) {
32623
+ if (maxLines <= 0) return [];
32624
+ if (lines.length <= maxLines) return lines;
32625
+ if (maxLines === 1) return [dimGray(`… +${lines.length} more lines`, style)];
32626
+ const kept = lines.slice(0, maxLines - 1);
32627
+ const hidden = lines.length - kept.length;
32628
+ kept.push(dimGray(`… +${hidden} more lines`, style));
32629
+ return kept;
32630
+ }
32631
+ function formatTodoPanelLines(options) {
32632
+ if (options.todoItems.length === 0) return [];
32633
+ const selectedEntries = options.fullView ? options.todoItems.map((item, index) => ({
32634
+ ordinal: index + 1,
32635
+ item
32636
+ })) : selectTodoPreviewItems(options.todoItems, {
32637
+ maxItems: TODO_PREVIEW_MAX_ITEMS,
32638
+ maxPending: TODO_PREVIEW_MAX_PENDING
32639
+ });
32640
+ const lines = [bold("todos", options.style)];
32641
+ selectedEntries.forEach((entry) => lines.push(...wrapTodoItemLine(entry, Math.max(1, options.widthHint))));
32642
+ if (!options.fullView && selectedEntries.length < options.todoItems.length) {
32643
+ const hidden = options.todoItems.length - selectedEntries.length;
32644
+ lines.push(dimGray(`… +${hidden} more todos (t for full)`, options.style));
32645
+ }
32646
+ lines.push(dimGray("─".repeat(Math.max(1, options.widthHint)), options.style));
32647
+ return lines;
32648
+ }
32445
32649
  function createWatchCommand(version) {
32446
- return createCommand("watch").version(version).description("Inspect task progress via live stream data (internal tooling).").option("--tasks <ids>", "Comma-separated task IDs to watch.", parseCommaList, []).option("--agents <names>", "Comma-separated agent names to watch.", parseCommaList, []).option("--screen <cols>x<rows>", "Pane grid in follow UI, e.g. 3x1 (default) or 5x4.", parseScreenGrid, DEFAULT_SCREEN_GRID).action(async function() {
32650
+ return createCommand("watch").version(version).description("Inspect task progress via live stream data (internal tooling).").option("--tasks <ids>", "Comma-separated task IDs to watch.", parseCommaList, []).option("--agents <names>", "Comma-separated agent names to watch.", parseCommaList, []).option("--list-width <cols>", "Left task-list width in columns (default: 32).", parseListWidth, 32).action(async function() {
32447
32651
  ensureEnvOrExit(["PANTHEON_API_KEY", "DATABASE_URL"]);
32448
32652
  const options = this.opts();
32449
32653
  if (options.tasks.length > 0 && options.agents.length > 0) {
@@ -32471,9 +32675,20 @@ function createWatchCommand(version) {
32471
32675
  process.on("SIGINT", onSigInt);
32472
32676
  const runtime = targets.map((target) => ({
32473
32677
  target,
32474
- aggregator: new WatchStepAggregator({ maxLogLines: 120 }),
32678
+ aggregator: new WatchStepAggregator({ maxLogLines: Number.POSITIVE_INFINITY }),
32475
32679
  streamDone: false
32476
32680
  }));
32681
+ const logViewportByTaskId = /* @__PURE__ */ new Map();
32682
+ function getLogViewport(taskId) {
32683
+ const existing = logViewportByTaskId.get(taskId);
32684
+ if (existing) return existing;
32685
+ const created = {
32686
+ autoFollow: true,
32687
+ scrollOffset: 0
32688
+ };
32689
+ logViewportByTaskId.set(taskId, created);
32690
+ return created;
32691
+ }
32477
32692
  async function startStream(rt, streamId) {
32478
32693
  rt.streamAbort?.abort();
32479
32694
  rt.streamAbort = new AbortController();
@@ -32509,7 +32724,7 @@ function createWatchCommand(version) {
32509
32724
  normalizer = createAgentStreamNormalizer({
32510
32725
  source: rt.source,
32511
32726
  emitStartChunk: false,
32512
- includeMetaEvents: false,
32727
+ includeMetaEvents: true,
32513
32728
  unknownChunkPolicy: "emit"
32514
32729
  });
32515
32730
  }
@@ -32544,7 +32759,10 @@ function createWatchCommand(version) {
32544
32759
  const nextStreamId = branch.latest_snap?.event_stream_id ?? void 0;
32545
32760
  if (nextStreamId && nextStreamId !== rt.streamId) {
32546
32761
  rt.streamId = nextStreamId;
32547
- rt.aggregator = new WatchStepAggregator({ maxLogLines: 120 });
32762
+ rt.aggregator = new WatchStepAggregator({ maxLogLines: Number.POSITIVE_INFINITY });
32763
+ const viewport = getLogViewport(rt.target.task.id);
32764
+ viewport.autoFollow = true;
32765
+ viewport.scrollOffset = 0;
32548
32766
  await startStream(rt, nextStreamId);
32549
32767
  }
32550
32768
  } catch (error) {
@@ -32557,7 +32775,7 @@ function createWatchCommand(version) {
32557
32775
  title: "pantheon-agents watch"
32558
32776
  });
32559
32777
  screen.key(["q", "C-c"], () => abortController.abort());
32560
- const footer = blessed.box({
32778
+ const globalFooter = blessed.box({
32561
32779
  parent: screen,
32562
32780
  bottom: 0,
32563
32781
  left: 0,
@@ -32567,108 +32785,325 @@ function createWatchCommand(version) {
32567
32785
  content: "Ctrl+C to exit • q to quit",
32568
32786
  style: { fg: "gray" }
32569
32787
  });
32570
- const panes = runtime.map(() => blessed.box({
32788
+ const taskListPane = blessed.list({
32571
32789
  parent: screen,
32572
32790
  top: 0,
32573
32791
  left: 0,
32574
32792
  height: "100%-1",
32575
- width: "100%",
32793
+ width: options.listWidth,
32794
+ border: "line",
32795
+ tags: false,
32796
+ wrap: false,
32797
+ keys: false,
32798
+ vi: false,
32799
+ mouse: false,
32800
+ style: {
32801
+ border: { fg: "cyan" },
32802
+ selected: {
32803
+ fg: "black",
32804
+ bg: "cyan"
32805
+ },
32806
+ item: { fg: "white" }
32807
+ }
32808
+ });
32809
+ const rightPane = blessed.box({
32810
+ parent: screen,
32811
+ top: 0,
32812
+ left: options.listWidth,
32813
+ height: "100%-1",
32814
+ width: `100%-${options.listWidth}`,
32576
32815
  border: "line",
32577
32816
  tags: false,
32578
- scrollable: false,
32579
32817
  wrap: false,
32580
32818
  style: { border: { fg: "gray" } }
32581
- }));
32819
+ });
32820
+ const rightHeader = blessed.box({
32821
+ parent: rightPane,
32822
+ top: 0,
32823
+ left: 1,
32824
+ height: 2,
32825
+ width: "100%-2",
32826
+ tags: false,
32827
+ wrap: false
32828
+ });
32829
+ const rightBody = blessed.box({
32830
+ parent: rightPane,
32831
+ top: 2,
32832
+ left: 1,
32833
+ width: "100%-2",
32834
+ height: 1,
32835
+ tags: false,
32836
+ wrap: false,
32837
+ scrollable: false
32838
+ });
32839
+ const rightFooter = blessed.box({
32840
+ parent: rightPane,
32841
+ left: 1,
32842
+ height: 1,
32843
+ width: "100%-2",
32844
+ tags: false,
32845
+ wrap: false,
32846
+ style: { fg: "gray" }
32847
+ });
32582
32848
  let lastGlobalError;
32583
- function layoutPanes(columns, rows) {
32584
- const n = panes.length;
32585
- if (n === 0) return {
32586
- visibleCount: 0,
32587
- hiddenCount: 0
32588
- };
32589
- const gridCols = Math.max(1, options.screen.cols);
32590
- const gridRows = Math.max(1, options.screen.rows);
32591
- const maxVisible = gridCols * gridRows;
32592
- const visibleCount = Math.min(n, maxVisible);
32593
- const paneAreaHeight = Math.max(1, rows - 1);
32594
- for (let i = 0; i < n; i++) {
32595
- const pane = panes[i];
32596
- if (i >= visibleCount) {
32597
- pane.hide();
32849
+ let selectedIndex = 0;
32850
+ let focus = "list";
32851
+ let showFullTodoList = false;
32852
+ function normalizeTaskText(taskText) {
32853
+ return taskText.trim().replaceAll(/\s+/g, " ");
32854
+ }
32855
+ function getSelectedRuntimeTask() {
32856
+ if (selectedIndex < 0) selectedIndex = 0;
32857
+ if (selectedIndex >= runtime.length) selectedIndex = runtime.length - 1;
32858
+ return runtime[selectedIndex];
32859
+ }
32860
+ function getFormattedLogLines(rt, options) {
32861
+ const step = rt.aggregator.snapshot();
32862
+ const blocks = [];
32863
+ const reasoningWrapWidth = Math.max(1, options.widthHint);
32864
+ const messageWrapWidth = Math.max(1, options.widthHint);
32865
+ const toolById = new Map(step.toolActions.map((tool) => [tool.toolCallId, tool]));
32866
+ function pushBlock(order, lines) {
32867
+ if (lines.length === 0) return;
32868
+ blocks.push({
32869
+ order: order ?? Number.MAX_SAFE_INTEGER,
32870
+ lines
32871
+ });
32872
+ }
32873
+ for (let timelineIndex = 0; timelineIndex < step.timeline.length; timelineIndex++) {
32874
+ const entry = step.timeline[timelineIndex];
32875
+ if (entry.kind === "reasoning") {
32876
+ const reasoningBlock = [];
32877
+ const hasTrailingLineFeed = entry.text.endsWith("\n");
32878
+ const rawReasoningLines = toDisplayLines(entry.text);
32879
+ for (const rawLine of rawReasoningLines) wrapRawLineToWidth(rawLine, reasoningWrapWidth).forEach((chunk) => reasoningBlock.push(gray(chunk, options.style)));
32880
+ if (!hasTrailingLineFeed) reasoningBlock.push("");
32881
+ pushBlock(entry.order, reasoningBlock);
32882
+ continue;
32883
+ }
32884
+ if (entry.kind === "message") {
32885
+ const messageBlock = [];
32886
+ const rawMessageLines = toDisplayLines(entry.text);
32887
+ for (const rawLine of rawMessageLines) wrapRawLineToWidth(rawLine, messageWrapWidth).forEach((chunk) => messageBlock.push(chunk));
32888
+ pushBlock(entry.order, messageBlock);
32598
32889
  continue;
32599
32890
  }
32600
- const rowIndex = Math.floor(i / gridCols);
32601
- const colIndex = i % gridCols;
32602
- const left = Math.floor(colIndex * columns / gridCols);
32603
- const right = Math.floor((colIndex + 1) * columns / gridCols);
32604
- const top = Math.floor(rowIndex * paneAreaHeight / gridRows);
32605
- const bottom = Math.floor((rowIndex + 1) * paneAreaHeight / gridRows);
32606
- pane.top = top;
32607
- pane.left = left;
32608
- pane.width = Math.max(1, right - left);
32609
- pane.height = Math.max(1, bottom - top);
32610
- pane.show();
32891
+ if (entry.kind === "warning") {
32892
+ pushBlock(entry.order, [kv("warning", yellow(entry.message, options.style), options.style)]);
32893
+ continue;
32894
+ }
32895
+ const tool = toolById.get(entry.toolCallId);
32896
+ if (!tool) continue;
32897
+ const exploredSubLines = getExploredSubCommandLines(tool);
32898
+ if (exploredSubLines != null) {
32899
+ const groupedTools = [tool];
32900
+ const groupedSubLines = [...exploredSubLines];
32901
+ let lookahead = timelineIndex + 1;
32902
+ while (lookahead < step.timeline.length) {
32903
+ const next = step.timeline[lookahead];
32904
+ if (next.kind !== "tool") break;
32905
+ const nextTool = toolById.get(next.toolCallId);
32906
+ if (!nextTool) break;
32907
+ const nextSubLines = getExploredSubCommandLines(nextTool);
32908
+ if (nextSubLines == null) break;
32909
+ groupedTools.push(nextTool);
32910
+ groupedSubLines.push(...nextSubLines);
32911
+ lookahead += 1;
32912
+ }
32913
+ const groupedBlock = [` ${[
32914
+ formatToolStatusIcon(combineToolStatuses(groupedTools.map((groupedTool) => groupedTool.status)), options.style),
32915
+ cyan("Explored", options.style),
32916
+ groupedTools.length > 1 ? gray(`x${groupedTools.length}`, options.style) : void 0
32917
+ ].filter(Boolean).join(" ")}`];
32918
+ groupedSubLines.forEach((subLine, subIndex) => groupedBlock.push(subIndex === 0 ? ` ${dimGray("└", options.style)} ${formatExploredSubCommandLine(subLine, options.style)}` : ` ${formatExploredSubCommandLine(subLine, options.style)}`));
32919
+ pushBlock(entry.order, groupedBlock);
32920
+ timelineIndex = lookahead - 1;
32921
+ continue;
32922
+ }
32923
+ const toolBlock = [];
32924
+ toDisplayLines(formatToolSummaryLine(tool, options.style)).forEach((line, index) => toolBlock.push(index === 0 ? ` ${line}` : ` ${line}`));
32925
+ if (tool.toolName === "command_execution") formatToolResultPreviewLines(tool, {
32926
+ maxLines: Number.MAX_SAFE_INTEGER,
32927
+ widthHint: Math.max(20, options.widthHint - 10),
32928
+ style: options.style
32929
+ }).forEach((line, index) => toolBlock.push(index === 0 ? ` ${dimGray("└", options.style)} ${dimGray(line, options.style)}` : ` ${dimGray(line, options.style)}`));
32930
+ pushBlock(entry.order, toolBlock);
32611
32931
  }
32612
- return {
32613
- visibleCount,
32614
- hiddenCount: n - visibleCount
32615
- };
32932
+ if (rt.lastError) pushBlock(void 0, [kv("error", red(rt.lastError, options.style), options.style)]);
32933
+ blocks.sort((a, b) => a.order - b.order);
32934
+ const lines = [];
32935
+ for (const block of blocks) {
32936
+ if (lines.length > 0 && lines[lines.length - 1] !== "") lines.push("");
32937
+ lines.push(...block.lines);
32938
+ }
32939
+ if (lines.length === 0) lines.push(gray("(waiting for formatted stream output)", options.style));
32940
+ return lines;
32941
+ }
32942
+ function moveTaskSelection(delta) {
32943
+ if (runtime.length === 0) return;
32944
+ selectedIndex = Math.max(0, Math.min(runtime.length - 1, selectedIndex + delta));
32945
+ }
32946
+ function scrollSelectedLog(delta) {
32947
+ const selected = getSelectedRuntimeTask();
32948
+ const viewport = getLogViewport(selected.target.task.id);
32949
+ const bodyHeight = typeof rightBody.height === "number" ? rightBody.height : 0;
32950
+ if (bodyHeight <= 0) return;
32951
+ const bodyWidth = typeof rightBody.width === "number" ? rightBody.width : 80;
32952
+ const lines = getFormattedLogLines(selected, {
32953
+ style: useAnsiStyles(),
32954
+ widthHint: Math.max(20, bodyWidth)
32955
+ });
32956
+ const maxOffset = Math.max(0, lines.length - bodyHeight);
32957
+ viewport.autoFollow = false;
32958
+ viewport.scrollOffset = Math.max(0, Math.min(maxOffset, viewport.scrollOffset + delta));
32959
+ }
32960
+ function scrollSelectedLogToTop() {
32961
+ const viewport = getLogViewport(getSelectedRuntimeTask().target.task.id);
32962
+ viewport.autoFollow = false;
32963
+ viewport.scrollOffset = 0;
32964
+ }
32965
+ function scrollSelectedLogToBottom() {
32966
+ const selected = getSelectedRuntimeTask();
32967
+ const viewport = getLogViewport(selected.target.task.id);
32968
+ const bodyHeight = typeof rightBody.height === "number" ? rightBody.height : 0;
32969
+ if (bodyHeight <= 0) return;
32970
+ const bodyWidth = typeof rightBody.width === "number" ? rightBody.width : 80;
32971
+ const lines = getFormattedLogLines(selected, {
32972
+ style: useAnsiStyles(),
32973
+ widthHint: Math.max(20, bodyWidth)
32974
+ });
32975
+ const maxOffset = Math.max(0, lines.length - bodyHeight);
32976
+ viewport.autoFollow = false;
32977
+ viewport.scrollOffset = maxOffset;
32978
+ }
32979
+ function getHalfScreenScrollStep() {
32980
+ const bodyHeight = typeof rightBody.height === "number" ? rightBody.height : 0;
32981
+ return Math.max(1, Math.floor(bodyHeight / 2));
32616
32982
  }
32983
+ function resumeAutoFollowForSelectedLog() {
32984
+ const viewport = getLogViewport(getSelectedRuntimeTask().target.task.id);
32985
+ viewport.autoFollow = true;
32986
+ }
32987
+ screen.key(["left"], () => {
32988
+ focus = "list";
32989
+ });
32990
+ screen.key(["right"], () => {
32991
+ focus = "log";
32992
+ });
32993
+ screen.key(["up"], () => {
32994
+ if (focus === "list") moveTaskSelection(-1);
32995
+ else scrollSelectedLog(-getHalfScreenScrollStep());
32996
+ });
32997
+ screen.key(["down"], () => {
32998
+ if (focus === "list") moveTaskSelection(1);
32999
+ else scrollSelectedLog(getHalfScreenScrollStep());
33000
+ });
33001
+ screen.key(["g", "home"], () => {
33002
+ if (focus !== "log") return;
33003
+ scrollSelectedLogToTop();
33004
+ });
33005
+ screen.key([
33006
+ "G",
33007
+ "S-g",
33008
+ "end"
33009
+ ], () => {
33010
+ if (focus !== "log") return;
33011
+ scrollSelectedLogToBottom();
33012
+ });
33013
+ screen.key(["f"], () => {
33014
+ resumeAutoFollowForSelectedLog();
33015
+ });
33016
+ screen.key(["t"], () => {
33017
+ showFullTodoList = !showFullTodoList;
33018
+ });
32617
33019
  const renderTimer = setInterval(() => {
32618
33020
  const style = useAnsiStyles();
32619
33021
  const columns = Math.max(40, screen.width ?? 120);
32620
- const layout = layoutPanes(columns, Math.max(8, screen.height ?? 24));
32621
- const nextFooterParts = ["Ctrl+C to exit", "q to quit"];
32622
- if (layout.hiddenCount > 0) nextFooterParts.push(`showing ${layout.visibleCount}/${runtime.length}`);
32623
- if (lastGlobalError) nextFooterParts.push(`error: ${lastGlobalError}`);
32624
- footer.setContent(nextFooterParts.join(" "));
32625
- runtime.slice(0, layout.visibleCount).forEach((rt, idx) => {
32626
- const pane = panes[idx];
32627
- const paneWidth = typeof pane.width === "number" ? pane.width : Math.max(1, Math.floor(columns / Math.max(1, options.screen.cols)));
32628
- const paneInnerWidth = Math.max(0, paneWidth - 2);
32629
- const { task } = rt.target;
32630
- const step = rt.aggregator.snapshot();
32631
- const elapsed = formatDurationMs(getTaskElapsedMs(task, /* @__PURE__ */ new Date()));
32632
- const title = [
32633
- rt.target.agent,
32634
- `task ${task.id}`,
32635
- formatTaskStatus(task.status, style),
32636
- formatBranchStatus(rt.branch?.status, style),
32637
- rt.stepIndex != null ? `step ${rt.stepIndex}` : void 0,
32638
- `elapsed ${elapsed}`
32639
- ].filter(Boolean).join(gray(" ", style));
32640
- const branchId = "branch_id" in task ? task.branch_id ?? void 0 : void 0;
32641
- const headerLines = [kv("project", task.project_id, style), kv("branch", branchId ? branchId : gray("(no branch)", style), style)];
32642
- const reasoningLines = formatWrappedTextLines(step.leadingReasoning, {
32643
- maxLines: 6,
32644
- widthHint: paneInnerWidth,
32645
- formatValue: (value) => gray(value, style)
32646
- });
32647
- const messageLine = formatPreviewLine(dim("message", style), step.leadingText);
32648
- const extraLines = [];
32649
- if (rt.lastError) extraLines.push(kv("error", red(rt.lastError, style), style));
32650
- if (step.warnings.length > 0) extraLines.push(kv("warnings", yellow(`${step.warnings.length} (unknown=${step.unknownEventCount})`, style), style));
32651
- const toolLines = [];
32652
- const followTools = step.toolActions.slice(-3);
32653
- for (const tool of followTools) {
32654
- toolLines.push(formatToolSummaryLine(tool, style));
32655
- if (tool.toolName !== "command_execution") continue;
32656
- formatToolResultPreviewLines(tool, {
32657
- maxLines: 2,
32658
- widthHint: Math.max(20, paneInnerWidth - 2),
32659
- style
32660
- }).forEach((line) => toolLines.push(` ${line}`));
32661
- }
32662
- const baseLines = [
32663
- ...headerLines,
32664
- ...reasoningLines,
32665
- ...toolLines,
32666
- ...messageLine ? [messageLine] : [],
32667
- ...extraLines
32668
- ];
32669
- pane.setLabel(title);
32670
- pane.setContent(baseLines.map((line) => clip(line, paneInnerWidth)).join("\n"));
33022
+ const rows = Math.max(8, screen.height ?? 24);
33023
+ const contentHeight = Math.max(1, rows - 1);
33024
+ const minListWidth = 12;
33025
+ const maxListWidth = Math.max(minListWidth, columns - 24);
33026
+ const listWidth = Math.min(Math.max(minListWidth, options.listWidth), maxListWidth);
33027
+ const rightPaneWidth = Math.max(1, columns - listWidth);
33028
+ taskListPane.top = 0;
33029
+ taskListPane.left = 0;
33030
+ taskListPane.width = listWidth;
33031
+ taskListPane.height = contentHeight;
33032
+ taskListPane.setLabel(" Tasks ");
33033
+ taskListPane.style.border = { fg: focus === "list" ? "cyan" : "gray" };
33034
+ rightPane.top = 0;
33035
+ rightPane.left = listWidth;
33036
+ rightPane.width = rightPaneWidth;
33037
+ rightPane.height = contentHeight;
33038
+ rightPane.style.border = { fg: focus === "log" ? "cyan" : "gray" };
33039
+ const selected = getSelectedRuntimeTask();
33040
+ const selectedTaskId = selected.target.task.id;
33041
+ const viewport = getLogViewport(selectedTaskId);
33042
+ const snapshot = selected.aggregator.snapshot();
33043
+ const paneInnerWidth = Math.max(1, rightPaneWidth - 2);
33044
+ const paneInnerHeight = Math.max(1, contentHeight - 2);
33045
+ const headerHeight = Math.min(2, paneInnerHeight);
33046
+ const maxRightFooterHeight = Math.max(0, paneInnerHeight - headerHeight);
33047
+ const visibleTodoPanelLines = trimLinesToHeight(formatTodoPanelLines({
33048
+ todoItems: snapshot.todoItems,
33049
+ fullView: showFullTodoList,
33050
+ widthHint: paneInnerWidth,
33051
+ style
33052
+ }), maxRightFooterHeight, style);
33053
+ const rightFooterHeight = visibleTodoPanelLines.length;
33054
+ const bodyHeight = Math.max(0, paneInnerHeight - headerHeight - rightFooterHeight);
33055
+ const listInnerWidth = Math.max(1, listWidth - 2);
33056
+ const taskListItems = runtime.map((rt) => clip(`${rt.target.agent}: ${normalizeTaskText(rt.target.task.task)}`, listInnerWidth));
33057
+ taskListPane.setItems(taskListItems);
33058
+ taskListPane.select(selectedIndex);
33059
+ rightPane.setLabel(` ${selected.target.agent} `);
33060
+ rightHeader.top = 0;
33061
+ rightHeader.left = 1;
33062
+ rightHeader.width = paneInnerWidth;
33063
+ rightHeader.height = headerHeight;
33064
+ rightHeader.show();
33065
+ const latestSnapId = selected.branch?.latest_snap_id ?? "(none)";
33066
+ const streamId = selected.streamId ?? selected.branch?.latest_snap?.event_stream_id ?? "(none)";
33067
+ const headerLines = [clip(`project id: ${selected.target.task.project_id} • task id: ${selected.target.task.id} • latest snap id: ${latestSnapId}`, paneInnerWidth), clip(`stream id: ${streamId}`, paneInnerWidth)];
33068
+ rightHeader.setContent(headerLines.slice(0, headerHeight).join("\n"));
33069
+ const formattedLogLines = getFormattedLogLines(selected, {
33070
+ style,
33071
+ widthHint: paneInnerWidth
32671
33072
  });
33073
+ const maxOffset = Math.max(0, formattedLogLines.length - bodyHeight);
33074
+ if (viewport.autoFollow) viewport.scrollOffset = maxOffset;
33075
+ else viewport.scrollOffset = Math.max(0, Math.min(maxOffset, viewport.scrollOffset));
33076
+ rightBody.top = headerHeight + rightFooterHeight;
33077
+ rightBody.left = 1;
33078
+ rightBody.width = paneInnerWidth;
33079
+ rightBody.height = bodyHeight;
33080
+ if (bodyHeight <= 0) rightBody.hide();
33081
+ else {
33082
+ const visibleLines = formattedLogLines.slice(viewport.scrollOffset, viewport.scrollOffset + bodyHeight);
33083
+ rightBody.setContent(visibleLines.map((line) => clip(line, paneInnerWidth)).join("\n"));
33084
+ rightBody.show();
33085
+ }
33086
+ rightFooter.top = headerHeight;
33087
+ rightFooter.left = 1;
33088
+ rightFooter.width = paneInnerWidth;
33089
+ rightFooter.height = rightFooterHeight;
33090
+ if (rightFooterHeight <= 0) rightFooter.hide();
33091
+ else {
33092
+ rightFooter.setContent(visibleTodoPanelLines.map((line) => clip(line, paneInnerWidth)).join("\n"));
33093
+ rightFooter.show();
33094
+ }
33095
+ const footerParts = [
33096
+ "Ctrl+C to exit",
33097
+ "q to quit",
33098
+ "←/→ focus",
33099
+ focus === "list" ? "↑/↓ select task" : "↑/↓ half-page",
33100
+ "g/G top/bottom",
33101
+ "f resume auto-scroll",
33102
+ showFullTodoList ? "t todo preview" : "t todo full"
33103
+ ];
33104
+ if (!viewport.autoFollow) footerParts.push("auto-scroll paused");
33105
+ if (lastGlobalError) footerParts.push(`error: ${clip(lastGlobalError, 40)}`);
33106
+ globalFooter.setContent(footerParts.join(" • "));
32672
33107
  screen.render();
32673
33108
  }, 300);
32674
33109
  let pollTimer;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pantheon.ai/agents",
3
3
  "type": "module",
4
- "version": "0.0.15",
4
+ "version": "0.0.16",
5
5
  "bin": {
6
6
  "pantheon-agents": "dist/index.js"
7
7
  },
@@ -21,6 +21,7 @@
21
21
  "@modelcontextprotocol/sdk": "^1.26.0",
22
22
  "@openrouter/ai-sdk-provider": "^2.2.3",
23
23
  "@types/express": "^5.0.6",
24
+ "@types/shell-quote": "^1.7.5",
24
25
  "commander": "^14.0.3",
25
26
  "dotenv": "^17.2.4",
26
27
  "expand-tilde": "^2.0.2",
@@ -31,6 +32,7 @@
31
32
  "pino-pretty": "^13.1.3",
32
33
  "pino-roll": "^4.0.0",
33
34
  "reblessed": "^0.2.1",
35
+ "shell-quote": "^1.8.3",
34
36
  "smol-toml": "^1.6.0",
35
37
  "zod": "^4.3.6"
36
38
  },