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.
- package/cli/tui.js +125 -30
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3138
|
-
|
|
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
|
|
5132
|
-
appendActivity(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
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
|
}
|