@pantheon.ai/agents 0.0.15 → 0.0.17

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 +718 -227
  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.17";
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
  }
@@ -18099,11 +18140,11 @@ function createLlmExplainCommand(version) {
18099
18140
  projectId: task.project_id,
18100
18141
  branchId: task.branch_id
18101
18142
  });
18102
- if (!branch.branch.latest_snap?.event_stream_id) {
18143
+ if (!branch.branch?.latest_snap?.event_stream_id) {
18103
18144
  console.error(`Branch ${task.branch_id} has no latest snap.`);
18104
18145
  process$1.exit(1);
18105
18146
  }
18106
- const stream = await getRawStream(branch.branch.latest_snap.event_stream_id);
18147
+ const stream = await getRawStream(branch.branch?.latest_snap.event_stream_id);
18107
18148
  const text = await llmExplain(task.task, stream, {
18108
18149
  timeout: 3e3,
18109
18150
  characters
@@ -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);
@@ -31886,6 +31927,52 @@ function createClaudeCodeNormalizer(options = {}) {
31886
31927
  });
31887
31928
  return out;
31888
31929
  }
31930
+ function formatToolResultOutput(content, toolUseResult) {
31931
+ if (toolUseResult == null) return content ?? null;
31932
+ if (content == null) return toolUseResult;
31933
+ if (typeof content === "string" && content.length === 0) return toolUseResult;
31934
+ return {
31935
+ content,
31936
+ tool_use_result: toolUseResult
31937
+ };
31938
+ }
31939
+ function getToolResultErrorText(content, toolUseResult) {
31940
+ if (typeof content === "string" && content.trim().length > 0) return content;
31941
+ if (isRecord(toolUseResult) && typeof toolUseResult.stderr === "string") {
31942
+ const stderr = toolUseResult.stderr.trim();
31943
+ if (stderr.length > 0) return stderr;
31944
+ }
31945
+ try {
31946
+ if (toolUseResult != null) return JSON.stringify(toolUseResult);
31947
+ } catch {}
31948
+ return "Tool execution failed";
31949
+ }
31950
+ function handleUserMessage(message, toolUseResult) {
31951
+ const blocks = Array.isArray(message.content) ? message.content : [];
31952
+ const out = [];
31953
+ blocks.forEach((block, index) => {
31954
+ if (!isRecord(block) || block.type !== "tool_result") return;
31955
+ const toolCallId = typeof block.tool_use_id === "string" ? block.tool_use_id : `claude-tool-result-${index}`;
31956
+ const content = block.content;
31957
+ const interrupted = isRecord(toolUseResult) && toolUseResult.interrupted === true;
31958
+ if (block.is_error === true || interrupted) {
31959
+ out.push({
31960
+ type: "tool-output-error",
31961
+ toolCallId,
31962
+ errorText: getToolResultErrorText(content, toolUseResult),
31963
+ dynamic: true
31964
+ });
31965
+ return;
31966
+ }
31967
+ out.push({
31968
+ type: "tool-output-available",
31969
+ toolCallId,
31970
+ output: formatToolResultOutput(content, toolUseResult),
31971
+ dynamic: true
31972
+ });
31973
+ });
31974
+ return out;
31975
+ }
31889
31976
  function push(chunk) {
31890
31977
  const parsed = topLevelSchema.safeParse(chunk);
31891
31978
  if (!parsed.success) return {
@@ -31917,7 +32004,6 @@ function createClaudeCodeNormalizer(options = {}) {
31917
32004
  };
31918
32005
  }
31919
32006
  case "stream_event": {
31920
- sawStreamEvents = true;
31921
32007
  const event = streamEventSchema.safeParse(msg.event);
31922
32008
  if (!event.success) return {
31923
32009
  recognized: false,
@@ -31939,6 +32025,17 @@ function createClaudeCodeNormalizer(options = {}) {
31939
32025
  chunks: handleAssistantMessage(message)
31940
32026
  };
31941
32027
  }
32028
+ case "user": {
32029
+ const message = msg.message;
32030
+ if (!isRecord(message)) return {
32031
+ recognized: false,
32032
+ chunks: []
32033
+ };
32034
+ return {
32035
+ recognized: true,
32036
+ chunks: handleUserMessage(message, msg.tool_use_result)
32037
+ };
32038
+ }
31942
32039
  case "result": return {
31943
32040
  recognized: true,
31944
32041
  chunks: maybeMeta({
@@ -32085,36 +32182,150 @@ function tryExtractBashCommand(command) {
32085
32182
  }
32086
32183
  return rest.trim();
32087
32184
  }
32088
- function formatCommandExecutionDisplayCommand(command) {
32185
+ function normalizeCommand(command) {
32089
32186
  const trimmed = command.trim();
32090
32187
  return tryExtractBashCommand(trimmed)?.trim() || trimmed;
32091
32188
  }
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}`;
32189
+ function splitCommandLines(command) {
32190
+ return command.replaceAll("\r\n", "\n").split("\n").map((line) => line.trim()).filter(Boolean);
32191
+ }
32192
+ function stripCommentPrefix(line) {
32193
+ const trimmed = line.trim();
32194
+ if (trimmed.startsWith("//")) return trimmed.slice(2).trimStart();
32195
+ return trimmed;
32196
+ }
32197
+ function commandBasename(value) {
32198
+ const lastSlash = value.lastIndexOf("/");
32199
+ return lastSlash === -1 ? value : value.slice(lastSlash + 1);
32200
+ }
32201
+ function parseShellEntries(line) {
32202
+ try {
32203
+ return parse(line);
32204
+ } catch {
32205
+ return;
32206
+ }
32207
+ }
32208
+ function takeMainTokens(entries) {
32209
+ const stopSet = new Set([
32210
+ "|",
32211
+ "||",
32212
+ "&&",
32213
+ ";",
32214
+ "|&",
32215
+ ";;",
32216
+ "&",
32217
+ "(",
32218
+ ")",
32219
+ "<",
32220
+ ">",
32221
+ ">>",
32222
+ ">&",
32223
+ "<("
32224
+ ]);
32225
+ const out = [];
32226
+ for (const entry of entries) {
32227
+ if (typeof entry === "string") {
32228
+ out.push(entry);
32229
+ continue;
32230
+ }
32231
+ if ("op" in entry) {
32232
+ if (stopSet.has(entry.op)) break;
32233
+ continue;
32234
+ }
32235
+ if ("comment" in entry) break;
32236
+ }
32237
+ return out;
32238
+ }
32239
+ function pickLastNonOptionArg(tokens) {
32240
+ const values = tokens.filter((token) => !token.startsWith("-"));
32241
+ return values.length > 0 ? values[values.length - 1] : void 0;
32242
+ }
32243
+ function parseExploredItem(line) {
32244
+ const entries = parseShellEntries(normalizeCommand(line));
32245
+ if (!entries) return void 0;
32246
+ const tokens = takeMainTokens(entries);
32247
+ if (tokens.length === 0) return void 0;
32248
+ const command = commandBasename(tokens[0] ?? "");
32249
+ if (command === "rg") {
32250
+ const args = tokens.slice(1);
32251
+ if (args.includes("--files")) return {
32252
+ kind: "list",
32253
+ target: pickLastNonOptionArg(args)
32254
+ };
32255
+ const firstValueIndex = args.findIndex((token) => !token.startsWith("-"));
32256
+ if (firstValueIndex === -1) return void 0;
32257
+ const terms = args[firstValueIndex];
32258
+ const file = args[firstValueIndex + 1];
32259
+ if (!terms || !file) return void 0;
32260
+ return {
32261
+ kind: "search",
32262
+ terms,
32263
+ file
32264
+ };
32265
+ }
32266
+ if (command === "ls") return {
32267
+ kind: "list",
32268
+ target: pickLastNonOptionArg(tokens.slice(1))
32269
+ };
32270
+ if (command === "sed" || command === "nl" || command === "cat") {
32271
+ if (tokens.length < 2) return void 0;
32272
+ const file = tokens[tokens.length - 1];
32273
+ if (!file || file.startsWith("-")) return void 0;
32274
+ return {
32275
+ kind: "read",
32276
+ file
32277
+ };
32278
+ }
32279
+ }
32280
+ function parseExploredItemsFromCommand(command) {
32281
+ const rawLines = splitCommandLines(normalizeCommand(command));
32282
+ const items = [];
32283
+ for (const rawLine of rawLines) {
32284
+ const line = stripCommentPrefix(rawLine);
32285
+ if (!line) continue;
32286
+ const item = parseExploredItem(normalizeCommand(line));
32287
+ if (!item) return void 0;
32288
+ items.push(item);
32289
+ }
32290
+ if (items.length === 0) return void 0;
32291
+ return items;
32292
+ }
32293
+ function formatExploredItemsLines(items) {
32294
+ const lines = ["Explored"];
32295
+ for (const item of items) {
32296
+ if (item.kind === "read") {
32297
+ lines.push(`Read ${item.file}`);
32298
+ continue;
32299
+ }
32300
+ if (item.kind === "list") {
32301
+ lines.push(item.target ? `List ${item.target}` : "List");
32302
+ continue;
32303
+ }
32304
+ lines.push(`Search ${item.terms} in ${item.file}`);
32305
+ }
32306
+ return lines;
32307
+ }
32308
+ function getExploredCommandLines(command) {
32309
+ const explored = parseExploredItemsFromCommand(command);
32310
+ if (!explored) return void 0;
32311
+ return formatExploredItemsLines(explored);
32312
+ }
32313
+ function isExploredCommandExecution(command) {
32314
+ return parseExploredItemsFromCommand(command) != null;
32315
+ }
32316
+ function formatCommandExecutionDisplayCommand(command) {
32317
+ const exploredLines = getExploredCommandLines(command);
32318
+ if (exploredLines) return exploredLines.join("\n");
32319
+ return normalizeCommand(command);
32099
32320
  }
32100
32321
 
32101
32322
  //#endregion
32102
32323
  //#region src/cli/commands/watch.ts
32103
32324
  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
- };
32325
+ function parseListWidth(value) {
32326
+ const width = Number(value);
32327
+ if (!Number.isInteger(width) || width <= 0) throw new InvalidArgumentError("list-width requires a positive integer, e.g. 32");
32328
+ return width;
32118
32329
  }
32119
32330
  function ensureEnvOrExit(keys) {
32120
32331
  const missing = keys.filter((key) => !process.env[key]);
@@ -32122,33 +32333,6 @@ function ensureEnvOrExit(keys) {
32122
32333
  for (const key of missing) console.error(`${key} environment variable is not set.`);
32123
32334
  process.exit(1);
32124
32335
  }
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
32336
  function toStreamSourceFromExecuteAgent(executeAgent) {
32153
32337
  if (!executeAgent) return void 0;
32154
32338
  const normalized = executeAgent.toLowerCase();
@@ -32296,42 +32480,36 @@ function yellow(text, enabled) {
32296
32480
  function cyan(text, enabled) {
32297
32481
  return sgrWrap(text, ANSI.cyan, enabled);
32298
32482
  }
32483
+ function dimGray(text, enabled) {
32484
+ return sgrWrap(text, `${ANSI.dim}${ANSI.gray}`, enabled);
32485
+ }
32299
32486
  function kv(label, value, style) {
32300
32487
  return `${dim(`${label}:`, style)} ${value}`;
32301
32488
  }
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
- }
32489
+ function getCommandExecutionDisplayCommand(tool) {
32490
+ const raw = getCommandExecutionRawCommand(tool);
32491
+ if (!raw) return void 0;
32492
+ return formatCommandExecutionDisplayCommand(raw) || void 0;
32310
32493
  }
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
- }
32494
+ function colorizeCommandProgram(display, style) {
32495
+ const match = display.match(/^(\s*)(\S+)([\s\S]*)$/);
32496
+ if (!match) return display;
32497
+ const [, leading, program, rest] = match;
32498
+ return `${leading}${cyan(program ?? "", style)}${rest ?? ""}`;
32322
32499
  }
32323
- function getCommandExecutionDisplayCommand(tool) {
32500
+ function getCommandExecutionRawCommand(tool) {
32324
32501
  const title = typeof tool.title === "string" ? tool.title : "";
32325
32502
  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;
32503
+ return title || inputCommand || void 0;
32329
32504
  }
32330
32505
  function formatToolLabel(tool, style) {
32331
32506
  const name = tool.toolName ?? "<tool>";
32332
32507
  if (name === "command_execution") {
32508
+ const rawCommand = getCommandExecutionRawCommand(tool);
32509
+ const exploredLines = rawCommand ? getExploredCommandLines(rawCommand) : void 0;
32510
+ if (exploredLines && exploredLines.length > 1) return cyan(exploredLines[0] ?? "Explored", style);
32333
32511
  const display = getCommandExecutionDisplayCommand(tool) ?? "(unknown command)";
32334
- return `${bold("$", style)} ${cyan(display, style)}`;
32512
+ return `${bold("$", style)} ${colorizeCommandProgram(display, style)}`;
32335
32513
  }
32336
32514
  const renderedName = bold(name, style);
32337
32515
  if (!tool.title) return renderedName;
@@ -32346,45 +32524,56 @@ function formatToolSummaryLine(tool, style) {
32346
32524
  default: return `${yellow("…", style)} ${label} (${id})`;
32347
32525
  }
32348
32526
  }
32349
- function toDisplayLines(text) {
32350
- return text.replaceAll("\r\n", "\n").split("\n");
32527
+ function getToolStatus(value) {
32528
+ if (value === "completed" || value === "failed") return value;
32529
+ return "in_progress";
32351
32530
  }
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;
32531
+ function combineToolStatuses(statuses) {
32532
+ const normalized = statuses.map((status) => getToolStatus(status));
32533
+ if (normalized.some((status) => status === "failed")) return "failed";
32534
+ if (normalized.some((status) => status === "in_progress")) return "in_progress";
32535
+ return "completed";
32536
+ }
32537
+ function formatToolStatusIcon(status, style) {
32538
+ switch (getToolStatus(status)) {
32539
+ case "completed": return green("✓", style);
32540
+ case "failed": return red("✗", style);
32541
+ default: return yellow("…", style);
32542
+ }
32543
+ }
32544
+ function getExploredSubCommandLines(tool) {
32545
+ if (tool.toolName !== "command_execution") return void 0;
32546
+ const rawCommand = getCommandExecutionRawCommand(tool);
32547
+ if (!rawCommand) return void 0;
32548
+ const exploredLines = getExploredCommandLines(rawCommand);
32549
+ if (!exploredLines || exploredLines.length <= 1) return void 0;
32550
+ return exploredLines.slice(1);
32551
+ }
32552
+ function formatExploredSubCommandLine(line, style) {
32553
+ if (line.startsWith("Search ")) {
32554
+ const payload = line.slice(7);
32555
+ const at = payload.lastIndexOf(" in ");
32556
+ if (at !== -1) {
32557
+ const terms = payload.slice(0, at);
32558
+ const file = payload.slice(at + 4);
32559
+ return `${cyan("Search", style)} ${terms} ${dimGray("in", style)} ${file}`;
32375
32560
  }
32376
- lines.push(current);
32377
- current = "";
32378
- if (word.length <= width) current = word;
32379
- else pushLongWord(word);
32561
+ return `${cyan("Search", style)} ${payload}`;
32380
32562
  }
32381
- if (current) lines.push(current);
32382
- return lines;
32563
+ if (line.startsWith("Read ")) return `${cyan("Read", style)} ${line.slice(5)}`;
32564
+ if (line === "List") return cyan("List", style);
32565
+ if (line.startsWith("List ")) return `${cyan("List", style)} ${line.slice(5)}`;
32566
+ return line;
32567
+ }
32568
+ function toDisplayLines(text) {
32569
+ return text.replaceAll("\r\n", "\n").split("\n");
32383
32570
  }
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);
32571
+ function wrapRawLineToWidth(line, width) {
32572
+ if (width <= 0) return [line];
32573
+ if (line.length === 0) return [""];
32574
+ const out = [];
32575
+ for (let i = 0; i < line.length; i += width) out.push(line.slice(i, i + width));
32576
+ return out;
32388
32577
  }
32389
32578
  function trimLinesWithEllipsis(lines, maxLines) {
32390
32579
  if (maxLines <= 0) return [];
@@ -32395,14 +32584,34 @@ function trimLinesWithEllipsis(lines, maxLines) {
32395
32584
  kept[lastIndex] = last ? `${last}…` : "…";
32396
32585
  return kept;
32397
32586
  }
32587
+ function trimCommandOutputTopBottom(lines) {
32588
+ if (lines.length <= 4) return lines;
32589
+ const hidden = lines.length - 4;
32590
+ return [
32591
+ lines[0],
32592
+ lines[1],
32593
+ `… +${hidden} lines`,
32594
+ lines[lines.length - 2],
32595
+ lines[lines.length - 1]
32596
+ ];
32597
+ }
32398
32598
  function formatToolResultPreviewLines(tool, options) {
32399
32599
  if (tool.status === "in_progress") return [];
32400
32600
  if (options.maxLines <= 0) return [];
32401
- if (tool.status === "failed") return trimLinesWithEllipsis(toDisplayLines(tool.errorText ?? "(no error text)"), options.maxLines);
32601
+ if (tool.toolName === "command_execution") {
32602
+ const rawCommand = getCommandExecutionRawCommand(tool);
32603
+ if (rawCommand && isExploredCommandExecution(rawCommand)) return [];
32604
+ }
32605
+ if (tool.status === "failed") {
32606
+ const lines = toDisplayLines(tool.errorText ?? "(no error text)");
32607
+ return trimLinesWithEllipsis(tool.toolName === "command_execution" ? trimCommandOutputTopBottom(lines) : lines, options.maxLines);
32608
+ }
32402
32609
  if (tool.output == null) return ["(none)"];
32403
32610
  if (tool.toolName === "command_execution") {
32404
32611
  const output = tool.output;
32405
- return trimLinesWithEllipsis(toDisplayLines((typeof output?.aggregated_output === "string" ? output.aggregated_output : "").trimEnd()), options.maxLines);
32612
+ const allLines = toDisplayLines((typeof output?.aggregated_output === "string" ? output.aggregated_output : "").trimEnd());
32613
+ if (!allLines.some((line) => line.length > 0)) return ["(none)"];
32614
+ return trimLinesWithEllipsis(trimCommandOutputTopBottom(allLines), options.maxLines);
32406
32615
  }
32407
32616
  return trimLinesWithEllipsis(toDisplayLines(inspect(tool.output, {
32408
32617
  depth: 6,
@@ -32442,8 +32651,59 @@ function clip(value, maxVisible) {
32442
32651
  const sliced = sliceSgrByVisibleWidth(value, maxVisible - 1);
32443
32652
  return `${sliced}…${sliced.includes("\x1B[") ? ANSI.reset : ""}`;
32444
32653
  }
32654
+ const TODO_PREVIEW_MAX_ITEMS = 3;
32655
+ const TODO_PREVIEW_MAX_PENDING = 2;
32656
+ function selectTodoPreviewItems(todoItems, options) {
32657
+ const selected = [];
32658
+ let pendingCount = 0;
32659
+ for (let index = todoItems.length - 1; index >= 0; index--) {
32660
+ const item = todoItems[index];
32661
+ if (selected.length >= options.maxItems) break;
32662
+ if (!item.completed && pendingCount >= options.maxPending) continue;
32663
+ selected.push({
32664
+ ordinal: index + 1,
32665
+ item
32666
+ });
32667
+ if (!item.completed) pendingCount += 1;
32668
+ }
32669
+ selected.reverse();
32670
+ return selected;
32671
+ }
32672
+ function wrapTodoItemLine(entry, widthHint) {
32673
+ const { ordinal, item } = entry;
32674
+ const normalizedText = item.text.trim().replaceAll(/\s+/g, " ") || "(empty)";
32675
+ const prefix = `${ordinal}. ${item.completed ? "[x]" : "[ ]"} `;
32676
+ return wrapRawLineToWidth(normalizedText, Math.max(1, widthHint - prefix.length)).map((chunk, chunkIndex) => chunkIndex === 0 ? `${prefix}${chunk}` : `${" ".repeat(prefix.length)}${chunk}`);
32677
+ }
32678
+ function trimLinesToHeight(lines, maxLines, style) {
32679
+ if (maxLines <= 0) return [];
32680
+ if (lines.length <= maxLines) return lines;
32681
+ if (maxLines === 1) return [dimGray(`… +${lines.length} more lines`, style)];
32682
+ const kept = lines.slice(0, maxLines - 1);
32683
+ const hidden = lines.length - kept.length;
32684
+ kept.push(dimGray(`… +${hidden} more lines`, style));
32685
+ return kept;
32686
+ }
32687
+ function formatTodoPanelLines(options) {
32688
+ if (options.todoItems.length === 0) return [];
32689
+ const selectedEntries = options.fullView ? options.todoItems.map((item, index) => ({
32690
+ ordinal: index + 1,
32691
+ item
32692
+ })) : selectTodoPreviewItems(options.todoItems, {
32693
+ maxItems: TODO_PREVIEW_MAX_ITEMS,
32694
+ maxPending: TODO_PREVIEW_MAX_PENDING
32695
+ });
32696
+ const lines = [bold("todos", options.style)];
32697
+ selectedEntries.forEach((entry) => lines.push(...wrapTodoItemLine(entry, Math.max(1, options.widthHint))));
32698
+ if (!options.fullView && selectedEntries.length < options.todoItems.length) {
32699
+ const hidden = options.todoItems.length - selectedEntries.length;
32700
+ lines.push(dimGray(`… +${hidden} more todos (t for full)`, options.style));
32701
+ }
32702
+ lines.push(dimGray("─".repeat(Math.max(1, options.widthHint)), options.style));
32703
+ return lines;
32704
+ }
32445
32705
  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() {
32706
+ 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
32707
  ensureEnvOrExit(["PANTHEON_API_KEY", "DATABASE_URL"]);
32448
32708
  const options = this.opts();
32449
32709
  if (options.tasks.length > 0 && options.agents.length > 0) {
@@ -32471,9 +32731,20 @@ function createWatchCommand(version) {
32471
32731
  process.on("SIGINT", onSigInt);
32472
32732
  const runtime = targets.map((target) => ({
32473
32733
  target,
32474
- aggregator: new WatchStepAggregator({ maxLogLines: 120 }),
32734
+ aggregator: new WatchStepAggregator({ maxLogLines: Number.POSITIVE_INFINITY }),
32475
32735
  streamDone: false
32476
32736
  }));
32737
+ const logViewportByTaskId = /* @__PURE__ */ new Map();
32738
+ function getLogViewport(taskId) {
32739
+ const existing = logViewportByTaskId.get(taskId);
32740
+ if (existing) return existing;
32741
+ const created = {
32742
+ autoFollow: true,
32743
+ scrollOffset: 0
32744
+ };
32745
+ logViewportByTaskId.set(taskId, created);
32746
+ return created;
32747
+ }
32477
32748
  async function startStream(rt, streamId) {
32478
32749
  rt.streamAbort?.abort();
32479
32750
  rt.streamAbort = new AbortController();
@@ -32509,7 +32780,7 @@ function createWatchCommand(version) {
32509
32780
  normalizer = createAgentStreamNormalizer({
32510
32781
  source: rt.source,
32511
32782
  emitStartChunk: false,
32512
- includeMetaEvents: false,
32783
+ includeMetaEvents: true,
32513
32784
  unknownChunkPolicy: "emit"
32514
32785
  });
32515
32786
  }
@@ -32544,7 +32815,10 @@ function createWatchCommand(version) {
32544
32815
  const nextStreamId = branch.latest_snap?.event_stream_id ?? void 0;
32545
32816
  if (nextStreamId && nextStreamId !== rt.streamId) {
32546
32817
  rt.streamId = nextStreamId;
32547
- rt.aggregator = new WatchStepAggregator({ maxLogLines: 120 });
32818
+ rt.aggregator = new WatchStepAggregator({ maxLogLines: Number.POSITIVE_INFINITY });
32819
+ const viewport = getLogViewport(rt.target.task.id);
32820
+ viewport.autoFollow = true;
32821
+ viewport.scrollOffset = 0;
32548
32822
  await startStream(rt, nextStreamId);
32549
32823
  }
32550
32824
  } catch (error) {
@@ -32557,7 +32831,7 @@ function createWatchCommand(version) {
32557
32831
  title: "pantheon-agents watch"
32558
32832
  });
32559
32833
  screen.key(["q", "C-c"], () => abortController.abort());
32560
- const footer = blessed.box({
32834
+ const globalFooter = blessed.box({
32561
32835
  parent: screen,
32562
32836
  bottom: 0,
32563
32837
  left: 0,
@@ -32567,108 +32841,325 @@ function createWatchCommand(version) {
32567
32841
  content: "Ctrl+C to exit • q to quit",
32568
32842
  style: { fg: "gray" }
32569
32843
  });
32570
- const panes = runtime.map(() => blessed.box({
32844
+ const taskListPane = blessed.list({
32571
32845
  parent: screen,
32572
32846
  top: 0,
32573
32847
  left: 0,
32574
32848
  height: "100%-1",
32575
- width: "100%",
32849
+ width: options.listWidth,
32850
+ border: "line",
32851
+ tags: false,
32852
+ wrap: false,
32853
+ keys: false,
32854
+ vi: false,
32855
+ mouse: false,
32856
+ style: {
32857
+ border: { fg: "cyan" },
32858
+ selected: {
32859
+ fg: "black",
32860
+ bg: "cyan"
32861
+ },
32862
+ item: { fg: "white" }
32863
+ }
32864
+ });
32865
+ const rightPane = blessed.box({
32866
+ parent: screen,
32867
+ top: 0,
32868
+ left: options.listWidth,
32869
+ height: "100%-1",
32870
+ width: `100%-${options.listWidth}`,
32576
32871
  border: "line",
32577
32872
  tags: false,
32578
- scrollable: false,
32579
32873
  wrap: false,
32580
32874
  style: { border: { fg: "gray" } }
32581
- }));
32875
+ });
32876
+ const rightHeader = blessed.box({
32877
+ parent: rightPane,
32878
+ top: 0,
32879
+ left: 1,
32880
+ height: 2,
32881
+ width: "100%-2",
32882
+ tags: false,
32883
+ wrap: false
32884
+ });
32885
+ const rightBody = blessed.box({
32886
+ parent: rightPane,
32887
+ top: 2,
32888
+ left: 1,
32889
+ width: "100%-2",
32890
+ height: 1,
32891
+ tags: false,
32892
+ wrap: false,
32893
+ scrollable: false
32894
+ });
32895
+ const rightFooter = blessed.box({
32896
+ parent: rightPane,
32897
+ left: 1,
32898
+ height: 1,
32899
+ width: "100%-2",
32900
+ tags: false,
32901
+ wrap: false,
32902
+ style: { fg: "gray" }
32903
+ });
32582
32904
  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();
32905
+ let selectedIndex = 0;
32906
+ let focus = "list";
32907
+ let showFullTodoList = false;
32908
+ function normalizeTaskText(taskText) {
32909
+ return taskText.trim().replaceAll(/\s+/g, " ");
32910
+ }
32911
+ function getSelectedRuntimeTask() {
32912
+ if (selectedIndex < 0) selectedIndex = 0;
32913
+ if (selectedIndex >= runtime.length) selectedIndex = runtime.length - 1;
32914
+ return runtime[selectedIndex];
32915
+ }
32916
+ function getFormattedLogLines(rt, options) {
32917
+ const step = rt.aggregator.snapshot();
32918
+ const blocks = [];
32919
+ const reasoningWrapWidth = Math.max(1, options.widthHint);
32920
+ const messageWrapWidth = Math.max(1, options.widthHint);
32921
+ const toolById = new Map(step.toolActions.map((tool) => [tool.toolCallId, tool]));
32922
+ function pushBlock(order, lines) {
32923
+ if (lines.length === 0) return;
32924
+ blocks.push({
32925
+ order: order ?? Number.MAX_SAFE_INTEGER,
32926
+ lines
32927
+ });
32928
+ }
32929
+ for (let timelineIndex = 0; timelineIndex < step.timeline.length; timelineIndex++) {
32930
+ const entry = step.timeline[timelineIndex];
32931
+ if (entry.kind === "reasoning") {
32932
+ const reasoningBlock = [];
32933
+ const hasTrailingLineFeed = entry.text.endsWith("\n");
32934
+ const rawReasoningLines = toDisplayLines(entry.text);
32935
+ for (const rawLine of rawReasoningLines) wrapRawLineToWidth(rawLine, reasoningWrapWidth).forEach((chunk) => reasoningBlock.push(gray(chunk, options.style)));
32936
+ if (!hasTrailingLineFeed) reasoningBlock.push("");
32937
+ pushBlock(entry.order, reasoningBlock);
32598
32938
  continue;
32599
32939
  }
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();
32940
+ if (entry.kind === "message") {
32941
+ const messageBlock = [];
32942
+ const rawMessageLines = toDisplayLines(entry.text);
32943
+ for (const rawLine of rawMessageLines) wrapRawLineToWidth(rawLine, messageWrapWidth).forEach((chunk) => messageBlock.push(chunk));
32944
+ pushBlock(entry.order, messageBlock);
32945
+ continue;
32946
+ }
32947
+ if (entry.kind === "warning") {
32948
+ pushBlock(entry.order, [kv("warning", yellow(entry.message, options.style), options.style)]);
32949
+ continue;
32950
+ }
32951
+ const tool = toolById.get(entry.toolCallId);
32952
+ if (!tool) continue;
32953
+ const exploredSubLines = getExploredSubCommandLines(tool);
32954
+ if (exploredSubLines != null) {
32955
+ const groupedTools = [tool];
32956
+ const groupedSubLines = [...exploredSubLines];
32957
+ let lookahead = timelineIndex + 1;
32958
+ while (lookahead < step.timeline.length) {
32959
+ const next = step.timeline[lookahead];
32960
+ if (next.kind !== "tool") break;
32961
+ const nextTool = toolById.get(next.toolCallId);
32962
+ if (!nextTool) break;
32963
+ const nextSubLines = getExploredSubCommandLines(nextTool);
32964
+ if (nextSubLines == null) break;
32965
+ groupedTools.push(nextTool);
32966
+ groupedSubLines.push(...nextSubLines);
32967
+ lookahead += 1;
32968
+ }
32969
+ const groupedBlock = [` ${[
32970
+ formatToolStatusIcon(combineToolStatuses(groupedTools.map((groupedTool) => groupedTool.status)), options.style),
32971
+ cyan("Explored", options.style),
32972
+ groupedTools.length > 1 ? gray(`x${groupedTools.length}`, options.style) : void 0
32973
+ ].filter(Boolean).join(" ")}`];
32974
+ groupedSubLines.forEach((subLine, subIndex) => groupedBlock.push(subIndex === 0 ? ` ${dimGray("└", options.style)} ${formatExploredSubCommandLine(subLine, options.style)}` : ` ${formatExploredSubCommandLine(subLine, options.style)}`));
32975
+ pushBlock(entry.order, groupedBlock);
32976
+ timelineIndex = lookahead - 1;
32977
+ continue;
32978
+ }
32979
+ const toolBlock = [];
32980
+ toDisplayLines(formatToolSummaryLine(tool, options.style)).forEach((line, index) => toolBlock.push(index === 0 ? ` ${line}` : ` ${line}`));
32981
+ if (tool.toolName === "command_execution") formatToolResultPreviewLines(tool, {
32982
+ maxLines: Number.MAX_SAFE_INTEGER,
32983
+ widthHint: Math.max(20, options.widthHint - 10),
32984
+ style: options.style
32985
+ }).forEach((line, index) => toolBlock.push(index === 0 ? ` ${dimGray("└", options.style)} ${dimGray(line, options.style)}` : ` ${dimGray(line, options.style)}`));
32986
+ pushBlock(entry.order, toolBlock);
32611
32987
  }
32612
- return {
32613
- visibleCount,
32614
- hiddenCount: n - visibleCount
32615
- };
32988
+ if (rt.lastError) pushBlock(void 0, [kv("error", red(rt.lastError, options.style), options.style)]);
32989
+ blocks.sort((a, b) => a.order - b.order);
32990
+ const lines = [];
32991
+ for (const block of blocks) {
32992
+ if (lines.length > 0 && lines[lines.length - 1] !== "") lines.push("");
32993
+ lines.push(...block.lines);
32994
+ }
32995
+ if (lines.length === 0) lines.push(gray("(waiting for formatted stream output)", options.style));
32996
+ return lines;
32997
+ }
32998
+ function moveTaskSelection(delta) {
32999
+ if (runtime.length === 0) return;
33000
+ selectedIndex = Math.max(0, Math.min(runtime.length - 1, selectedIndex + delta));
33001
+ }
33002
+ function scrollSelectedLog(delta) {
33003
+ const selected = getSelectedRuntimeTask();
33004
+ const viewport = getLogViewport(selected.target.task.id);
33005
+ const bodyHeight = typeof rightBody.height === "number" ? rightBody.height : 0;
33006
+ if (bodyHeight <= 0) return;
33007
+ const bodyWidth = typeof rightBody.width === "number" ? rightBody.width : 80;
33008
+ const lines = getFormattedLogLines(selected, {
33009
+ style: useAnsiStyles(),
33010
+ widthHint: Math.max(20, bodyWidth)
33011
+ });
33012
+ const maxOffset = Math.max(0, lines.length - bodyHeight);
33013
+ viewport.autoFollow = false;
33014
+ viewport.scrollOffset = Math.max(0, Math.min(maxOffset, viewport.scrollOffset + delta));
33015
+ }
33016
+ function scrollSelectedLogToTop() {
33017
+ const viewport = getLogViewport(getSelectedRuntimeTask().target.task.id);
33018
+ viewport.autoFollow = false;
33019
+ viewport.scrollOffset = 0;
33020
+ }
33021
+ function scrollSelectedLogToBottom() {
33022
+ const selected = getSelectedRuntimeTask();
33023
+ const viewport = getLogViewport(selected.target.task.id);
33024
+ const bodyHeight = typeof rightBody.height === "number" ? rightBody.height : 0;
33025
+ if (bodyHeight <= 0) return;
33026
+ const bodyWidth = typeof rightBody.width === "number" ? rightBody.width : 80;
33027
+ const lines = getFormattedLogLines(selected, {
33028
+ style: useAnsiStyles(),
33029
+ widthHint: Math.max(20, bodyWidth)
33030
+ });
33031
+ const maxOffset = Math.max(0, lines.length - bodyHeight);
33032
+ viewport.autoFollow = false;
33033
+ viewport.scrollOffset = maxOffset;
33034
+ }
33035
+ function getHalfScreenScrollStep() {
33036
+ const bodyHeight = typeof rightBody.height === "number" ? rightBody.height : 0;
33037
+ return Math.max(1, Math.floor(bodyHeight / 2));
32616
33038
  }
33039
+ function resumeAutoFollowForSelectedLog() {
33040
+ const viewport = getLogViewport(getSelectedRuntimeTask().target.task.id);
33041
+ viewport.autoFollow = true;
33042
+ }
33043
+ screen.key(["left"], () => {
33044
+ focus = "list";
33045
+ });
33046
+ screen.key(["right"], () => {
33047
+ focus = "log";
33048
+ });
33049
+ screen.key(["up"], () => {
33050
+ if (focus === "list") moveTaskSelection(-1);
33051
+ else scrollSelectedLog(-getHalfScreenScrollStep());
33052
+ });
33053
+ screen.key(["down"], () => {
33054
+ if (focus === "list") moveTaskSelection(1);
33055
+ else scrollSelectedLog(getHalfScreenScrollStep());
33056
+ });
33057
+ screen.key(["g", "home"], () => {
33058
+ if (focus !== "log") return;
33059
+ scrollSelectedLogToTop();
33060
+ });
33061
+ screen.key([
33062
+ "G",
33063
+ "S-g",
33064
+ "end"
33065
+ ], () => {
33066
+ if (focus !== "log") return;
33067
+ scrollSelectedLogToBottom();
33068
+ });
33069
+ screen.key(["f"], () => {
33070
+ resumeAutoFollowForSelectedLog();
33071
+ });
33072
+ screen.key(["t"], () => {
33073
+ showFullTodoList = !showFullTodoList;
33074
+ });
32617
33075
  const renderTimer = setInterval(() => {
32618
33076
  const style = useAnsiStyles();
32619
33077
  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"));
33078
+ const rows = Math.max(8, screen.height ?? 24);
33079
+ const contentHeight = Math.max(1, rows - 1);
33080
+ const minListWidth = 12;
33081
+ const maxListWidth = Math.max(minListWidth, columns - 24);
33082
+ const listWidth = Math.min(Math.max(minListWidth, options.listWidth), maxListWidth);
33083
+ const rightPaneWidth = Math.max(1, columns - listWidth);
33084
+ taskListPane.top = 0;
33085
+ taskListPane.left = 0;
33086
+ taskListPane.width = listWidth;
33087
+ taskListPane.height = contentHeight;
33088
+ taskListPane.setLabel(" Tasks ");
33089
+ taskListPane.style.border = { fg: focus === "list" ? "cyan" : "gray" };
33090
+ rightPane.top = 0;
33091
+ rightPane.left = listWidth;
33092
+ rightPane.width = rightPaneWidth;
33093
+ rightPane.height = contentHeight;
33094
+ rightPane.style.border = { fg: focus === "log" ? "cyan" : "gray" };
33095
+ const selected = getSelectedRuntimeTask();
33096
+ const selectedTaskId = selected.target.task.id;
33097
+ const viewport = getLogViewport(selectedTaskId);
33098
+ const snapshot = selected.aggregator.snapshot();
33099
+ const paneInnerWidth = Math.max(1, rightPaneWidth - 2);
33100
+ const paneInnerHeight = Math.max(1, contentHeight - 2);
33101
+ const headerHeight = Math.min(2, paneInnerHeight);
33102
+ const maxRightFooterHeight = Math.max(0, paneInnerHeight - headerHeight);
33103
+ const visibleTodoPanelLines = trimLinesToHeight(formatTodoPanelLines({
33104
+ todoItems: snapshot.todoItems,
33105
+ fullView: showFullTodoList,
33106
+ widthHint: paneInnerWidth,
33107
+ style
33108
+ }), maxRightFooterHeight, style);
33109
+ const rightFooterHeight = visibleTodoPanelLines.length;
33110
+ const bodyHeight = Math.max(0, paneInnerHeight - headerHeight - rightFooterHeight);
33111
+ const listInnerWidth = Math.max(1, listWidth - 2);
33112
+ const taskListItems = runtime.map((rt) => clip(`${rt.target.agent}: ${normalizeTaskText(rt.target.task.task)}`, listInnerWidth));
33113
+ taskListPane.setItems(taskListItems);
33114
+ taskListPane.select(selectedIndex);
33115
+ rightPane.setLabel(` ${selected.target.agent} `);
33116
+ rightHeader.top = 0;
33117
+ rightHeader.left = 1;
33118
+ rightHeader.width = paneInnerWidth;
33119
+ rightHeader.height = headerHeight;
33120
+ rightHeader.show();
33121
+ const latestSnapId = selected.branch?.latest_snap_id ?? "(none)";
33122
+ const streamId = selected.streamId ?? selected.branch?.latest_snap?.event_stream_id ?? "(none)";
33123
+ 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)];
33124
+ rightHeader.setContent(headerLines.slice(0, headerHeight).join("\n"));
33125
+ const formattedLogLines = getFormattedLogLines(selected, {
33126
+ style,
33127
+ widthHint: paneInnerWidth
32671
33128
  });
33129
+ const maxOffset = Math.max(0, formattedLogLines.length - bodyHeight);
33130
+ if (viewport.autoFollow) viewport.scrollOffset = maxOffset;
33131
+ else viewport.scrollOffset = Math.max(0, Math.min(maxOffset, viewport.scrollOffset));
33132
+ rightBody.top = headerHeight + rightFooterHeight;
33133
+ rightBody.left = 1;
33134
+ rightBody.width = paneInnerWidth;
33135
+ rightBody.height = bodyHeight;
33136
+ if (bodyHeight <= 0) rightBody.hide();
33137
+ else {
33138
+ const visibleLines = formattedLogLines.slice(viewport.scrollOffset, viewport.scrollOffset + bodyHeight);
33139
+ rightBody.setContent(visibleLines.map((line) => clip(line, paneInnerWidth)).join("\n"));
33140
+ rightBody.show();
33141
+ }
33142
+ rightFooter.top = headerHeight;
33143
+ rightFooter.left = 1;
33144
+ rightFooter.width = paneInnerWidth;
33145
+ rightFooter.height = rightFooterHeight;
33146
+ if (rightFooterHeight <= 0) rightFooter.hide();
33147
+ else {
33148
+ rightFooter.setContent(visibleTodoPanelLines.map((line) => clip(line, paneInnerWidth)).join("\n"));
33149
+ rightFooter.show();
33150
+ }
33151
+ const footerParts = [
33152
+ "Ctrl+C to exit",
33153
+ "q to quit",
33154
+ "←/→ focus",
33155
+ focus === "list" ? "↑/↓ select task" : "↑/↓ half-page",
33156
+ "g/G top/bottom",
33157
+ "f resume auto-scroll",
33158
+ showFullTodoList ? "t todo preview" : "t todo full"
33159
+ ];
33160
+ if (!viewport.autoFollow) footerParts.push("auto-scroll paused");
33161
+ if (lastGlobalError) footerParts.push(`error: ${clip(lastGlobalError, 40)}`);
33162
+ globalFooter.setContent(footerParts.join(" • "));
32672
33163
  screen.render();
32673
33164
  }, 300);
32674
33165
  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.17",
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
  },