pilotswarm-cli 0.1.6 → 0.1.7

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/cli/tui.js +125 -30
  2. package/package.json +1 -1
package/cli/tui.js CHANGED
@@ -1099,6 +1099,58 @@ function appendActivity(text, orchId) {
1099
1099
  }
1100
1100
  }
1101
1101
 
1102
+ function formatToolArgValue(value) {
1103
+ if (typeof value === "string") return JSON.stringify(value);
1104
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
1105
+ if (value === null) return "null";
1106
+ if (Array.isArray(value)) return `[${value.map(formatToolArgValue).join(", ")}]`;
1107
+ try {
1108
+ return JSON.stringify(value);
1109
+ } catch {
1110
+ return String(value);
1111
+ }
1112
+ }
1113
+
1114
+ function formatToolArgsSummary(toolName, args) {
1115
+ if (!args || typeof args !== "object") return "";
1116
+ if (toolName === "wait") {
1117
+ const seconds = args.seconds != null ? `${args.seconds}s` : "?";
1118
+ const preserve = args.preserveWorkerAffinity === true ? " preserve=true" : "";
1119
+ const reason = typeof args.reason === "string" && args.reason
1120
+ ? ` reason=${JSON.stringify(args.reason)}`
1121
+ : "";
1122
+ return ` ${seconds}${preserve}${reason}`;
1123
+ }
1124
+
1125
+ const entries = Object.entries(args)
1126
+ .slice(0, 4)
1127
+ .map(([key, value]) => `${key}=${formatToolArgValue(value)}`);
1128
+ if (entries.length === 0) return "";
1129
+ const suffix = Object.keys(args).length > entries.length ? ", ..." : "";
1130
+ return ` ${entries.join(", ")}${suffix}`;
1131
+ }
1132
+
1133
+ function formatToolActivityLine(timeStr, evt, phase = "start") {
1134
+ const toolName = evt.data?.toolName || evt.data?.name || "tool";
1135
+ const args = evt.data?.arguments || evt.data?.args;
1136
+ const dsid = evt.data?.durableSessionId ? ` {gray-fg}[${shortId(evt.data.durableSessionId)}]{/gray-fg}` : "";
1137
+ const summary = formatToolArgsSummary(toolName, args);
1138
+ if (phase === "start") {
1139
+ return `{white-fg}[${timeStr}]{/white-fg} {yellow-fg}▶ ${toolName}${summary}{/yellow-fg}${dsid}`;
1140
+ }
1141
+ return `{white-fg}[${timeStr}]{/white-fg} {green-fg}✓ ${toolName}{/green-fg}${dsid}`;
1142
+ }
1143
+
1144
+ function summarizeActivityPreview(text, maxLen = 100) {
1145
+ const compact = String(text || "")
1146
+ .replace(/\s+/g, " ")
1147
+ .trim();
1148
+ if (!compact) return "(no content)";
1149
+ return compact.length > maxLen
1150
+ ? `${compact.slice(0, maxLen - 1)}...`
1151
+ : compact;
1152
+ }
1153
+
1102
1154
  function ensureSessionSplashBuffer(orchId) {
1103
1155
  const existing = sessionChatBuffers.get(orchId) || [];
1104
1156
  const splashText = systemSplashText.get(orchId);
@@ -2907,12 +2959,9 @@ async function loadCmsHistory(orchId, options = {}) {
2907
2959
  lines.push("");
2908
2960
  }
2909
2961
  } else if (type === "tool.execution_start") {
2910
- const toolName = evt.data?.toolName || "tool";
2911
- const dsid = evt.data?.durableSessionId ? ` {gray-fg}[${shortId(evt.data.durableSessionId)}]{/gray-fg}` : "";
2912
- activityLines.push(`{white-fg}[${timeStr}]{/white-fg} {yellow-fg}▶ ${toolName}{/yellow-fg}${dsid}`);
2962
+ activityLines.push(formatToolActivityLine(timeStr, evt, "start"));
2913
2963
  } else if (type === "tool.execution_complete") {
2914
- const toolName = evt.data?.toolName || "tool";
2915
- activityLines.push(`{white-fg}[${timeStr}]{/white-fg} {green-fg}✓ ${toolName}{/green-fg}`);
2964
+ activityLines.push(formatToolActivityLine(timeStr, evt, "complete"));
2916
2965
  } else if (type === "abort" || type === "session.info" || type === "session.idle"
2917
2966
  || type === "session.usage_info" || type === "pending_messages.modified"
2918
2967
  || type === "assistant.usage") {
@@ -2971,7 +3020,45 @@ async function loadCmsHistory(orchId, options = {}) {
2971
3020
  }
2972
3021
 
2973
3022
  const maxRenderedSeq = (events || []).reduce((max, evt) => Math.max(max, evt.seq || 0), 0);
2974
- sessionChatBuffers.set(orchId, lines);
3023
+
3024
+ // Append pending question so it survives the history buffer swap.
3025
+ // The observer may have written it into the old buffer, but this
3026
+ // replacement would nuke it without this check.
3027
+ const pendingQ = sessionPendingQuestions.get(orchId);
3028
+ if (pendingQ) {
3029
+ lines.push(`{cyan-fg}{bold}Copilot:{/bold}{/cyan-fg}`);
3030
+ const renderedQ = renderMarkdown(pendingQ);
3031
+ for (const line of renderedQ.split("\n")) {
3032
+ lines.push(line);
3033
+ }
3034
+ lines.push("");
3035
+ }
3036
+
3037
+ // If CMS history produced no chat-visible lines (only the footer),
3038
+ // but the observer previously wrote real content into the buffer,
3039
+ // keep the existing buffer. This handles the case where assistant
3040
+ // response text comes via the observer (customStatus streaming) but
3041
+ // hasn't been persisted as CMS assistant.message events yet.
3042
+ const chatContentLines = lines.filter(l =>
3043
+ l && !/^{(?:white|gray)-fg}──/.test(l) && l.trim() !== "",
3044
+ );
3045
+ const existing = sessionChatBuffers.get(orchId);
3046
+ const existingHasContent = existing && existing.length > 1
3047
+ && existing.some(l => l && !/Loading/.test(l) && !/no recent/.test(l));
3048
+
3049
+ if (chatContentLines.length === 0 && existingHasContent) {
3050
+ // CMS has no chat-worthy content but observer buffer does —
3051
+ // append the footer to the existing buffer instead of replacing.
3052
+ if (eventCount > 0) {
3053
+ const footerIdx = lines.findIndex(l => /recent history loaded/.test(l));
3054
+ if (footerIdx >= 0) {
3055
+ existing.push(lines[footerIdx]);
3056
+ existing.push("");
3057
+ }
3058
+ }
3059
+ } else {
3060
+ sessionChatBuffers.set(orchId, lines);
3061
+ }
2975
3062
  sessionActivityBuffers.set(orchId, activityLines);
2976
3063
  sessionHistoryLoadedAt.set(orchId, Date.now());
2977
3064
  sessionRenderedCmsSeq.set(orchId, maxRenderedSeq);
@@ -3134,8 +3221,11 @@ if (!isRemote) {
3134
3221
  ...(workerModuleConfig.mcpServers && { mcpServers: workerModuleConfig.mcpServers }),
3135
3222
  });
3136
3223
  // Register custom tools from worker module
3137
- if (workerModuleConfig.tools?.length) {
3138
- w.registerTools(workerModuleConfig.tools);
3224
+ const workerTools = typeof workerModuleConfig.createTools === "function"
3225
+ ? await workerModuleConfig.createTools({ workerNodeId: `local-rt-${i}`, workerIndex: i })
3226
+ : workerModuleConfig.tools;
3227
+ if (workerTools?.length) {
3228
+ w.registerTools(workerTools);
3139
3229
  }
3140
3230
  await w.start();
3141
3231
  workers.push(w);
@@ -5128,12 +5218,8 @@ function startObserver(orchId) {
5128
5218
  }
5129
5219
  if (response.type === "wait" && response.content) {
5130
5220
  appendActivity(`{green-fg}[obs] ✓ SHOWING ${source}: version=${response.version} type=wait content=${response.content.slice(0, 80)}{/green-fg}`, orchId);
5131
- const prefix = `{white-fg}[${ts()}]{/white-fg} {gray-fg}[intermediate]{/gray-fg}`;
5132
- appendActivity(prefix, orchId);
5133
- const rendered = renderMarkdown(response.content);
5134
- for (const line of rendered.split("\n")) {
5135
- appendActivity(line, orchId);
5136
- }
5221
+ const preview = summarizeActivityPreview(response.content);
5222
+ appendActivity(`{white-fg}[${ts()}]{/white-fg} {gray-fg}[intermediate]{/gray-fg} ${preview}`, orchId);
5137
5223
  promoteIntermediateContent(response.content, orchId);
5138
5224
  setStatusIfActive(`Waiting (${cs.waitReason || response.waitReason || "timer"})…`);
5139
5225
  return;
@@ -5533,13 +5619,12 @@ function startCmsPoller(orchId) {
5533
5619
 
5534
5620
  if (type === "tool.execution_start") {
5535
5621
  const toolName = evt.data?.toolName || "tool";
5536
- const dsid = evt.data?.durableSessionId ? ` {gray-fg}[${shortId(evt.data.durableSessionId)}]{/gray-fg}` : "";
5537
5622
  // Track last tool name so we can show it on completion too
5538
5623
  sess._lastToolName = toolName;
5539
- appendActivity(`{white-fg}[${t}]{/white-fg} {yellow-fg}▶ ${toolName}{/yellow-fg}${dsid}`, orchId);
5624
+ appendActivity(formatToolActivityLine(t, evt, "start"), orchId);
5540
5625
  } else if (type === "tool.execution_complete") {
5541
5626
  const toolName = evt.data?.toolName || sess._lastToolName || "tool";
5542
- appendActivity(`{white-fg}[${t}]{/white-fg} {green-fg} ${toolName}{/green-fg}`, orchId);
5627
+ appendActivity(formatToolActivityLine(t, { ...evt, data: { ...(evt.data || {}), toolName } }, "complete"), orchId);
5543
5628
  } else if (type === "assistant.reasoning") {
5544
5629
  appendActivity(`{white-fg}[${t}]{/white-fg} {gray-fg}[reasoning]{/gray-fg}`, orchId);
5545
5630
  } else if (type === "assistant.turn_start") {
@@ -6396,33 +6481,43 @@ screen.on("keypress", (ch, key) => {
6396
6481
  picker.key(["escape", "q", "a"], closePicker);
6397
6482
 
6398
6483
  picker.on("select", async (_el, idx) => {
6399
- closePicker();
6400
6484
  const art = artifacts[idx];
6401
6485
  if (!art) return;
6402
6486
 
6403
- setStatus(`Downloading ${art.filename}...`);
6404
- screen.render();
6405
- const localPath = await downloadArtifact(art.sessionId, art.filename);
6406
- if (localPath) {
6407
- art.downloaded = true;
6408
- art.localPath = localPath;
6409
-
6410
- // Open markdown viewer with this file selected
6487
+ if (art.downloaded) {
6488
+ // Already downloaded — close picker and open viewer
6489
+ closePicker();
6411
6490
  mdViewActive = true;
6412
6491
  refreshMarkdownViewer();
6413
-
6414
- // Find and select the downloaded file in the viewer
6415
6492
  const files = scanExportFiles();
6416
- const matchIdx = files.findIndex(f => f.localPath === localPath);
6493
+ const matchIdx = files.findIndex(f => f.localPath === art.localPath);
6417
6494
  if (matchIdx >= 0) {
6418
6495
  mdViewerSelectedIdx = matchIdx;
6419
6496
  mdFileListPane.select(matchIdx);
6420
6497
  refreshMarkdownViewer();
6421
6498
  }
6422
-
6423
6499
  screen.realloc();
6424
6500
  relayoutAll();
6425
6501
  setStatus("Markdown Viewer (v to exit)");
6502
+ screen.render();
6503
+ return;
6504
+ }
6505
+
6506
+ setStatus(`Downloading ${art.filename}...`);
6507
+ screen.render();
6508
+ const localPath = await downloadArtifact(art.sessionId, art.filename);
6509
+ if (localPath) {
6510
+ art.downloaded = true;
6511
+ art.localPath = localPath;
6512
+
6513
+ // Update picker item to show downloaded state
6514
+ const updatedItems = artifacts.map((a) => {
6515
+ const icon = a.downloaded ? "✓" : "↓";
6516
+ return ` ${icon} ${a.filename}`;
6517
+ });
6518
+ picker.setItems(updatedItems);
6519
+ picker.select(idx);
6520
+ setStatus(`Downloaded ${art.filename}`);
6426
6521
  } else {
6427
6522
  setStatus("Download failed — check logs");
6428
6523
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pilotswarm-cli",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Terminal UI for pilotswarm — interactive durable agent orchestration.",
5
5
  "type": "module",
6
6
  "bin": {