pilotswarm 0.5.20 → 0.5.21
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/mcp/dist/src/tools/debug.d.ts.map +1 -1
- package/mcp/dist/src/tools/debug.js +2 -0
- package/mcp/dist/src/tools/debug.js.map +1 -1
- package/mcp/dist/src/tools/observability.d.ts.map +1 -1
- package/mcp/dist/src/tools/observability.js +5 -2
- package/mcp/dist/src/tools/observability.js.map +1 -1
- package/mcp/dist/src/tools/sessions.d.ts.map +1 -1
- package/mcp/dist/src/tools/sessions.js +48 -3
- package/mcp/dist/src/tools/sessions.js.map +1 -1
- package/package.json +1 -1
- package/tui/src/app.js +5 -0
- package/tui/src/node-sdk-transport.js +8 -0
- package/ui/core/src/commands.js +1 -0
- package/ui/core/src/controller.js +95 -1
- package/ui/core/src/history.js +56 -0
- package/ui/core/src/selectors.js +89 -4
- package/ui/react/src/web-app.js +65 -13
- package/web/dist/assets/{index-4Y-kstkw.js → index-BDMVG8w6.js} +3 -3
- package/web/dist/assets/pilotswarm-CjMx44so.js +90 -0
- package/web/dist/index.html +2 -2
- package/web/runtime.js +4 -0
- package/web/dist/assets/pilotswarm-BAEO8V1c.js +0 -90
package/ui/core/src/selectors.js
CHANGED
|
@@ -513,8 +513,16 @@ function canPinSessionRow(session) {
|
|
|
513
513
|
|
|
514
514
|
function buildSelectedSessionMetaRuns(session, mode) {
|
|
515
515
|
const runs = [];
|
|
516
|
+
// Live regeneration chip: the orchestration publishes regenStage in
|
|
517
|
+
// customStatus while the pipeline runs (archiving → distilling →
|
|
518
|
+
// flipping) and getSession spreads it onto the session view. Magenta to
|
|
519
|
+
// match the epoch divider; disappears when the flip lands.
|
|
520
|
+
if (typeof session?.regenStage === "string" && session.regenStage) {
|
|
521
|
+
runs.push({ text: `↻ regen:${session.regenStage}`, color: "magenta" });
|
|
522
|
+
}
|
|
516
523
|
const statusLabel = getSessionRowStatusLabel(session);
|
|
517
524
|
if (statusLabel) {
|
|
525
|
+
if (runs.length > 0) runs.push({ text: " · ", color: "gray" });
|
|
518
526
|
runs.push({ text: statusLabel, color: sessionStatusColor(session, mode) });
|
|
519
527
|
}
|
|
520
528
|
|
|
@@ -584,6 +592,10 @@ function buildSessionRowView(entry, session, state, totalDescendantCounts, visib
|
|
|
584
592
|
prefixRuns.push({ text: "🗂 ", color: "cyan", bold: true });
|
|
585
593
|
} else if (session?.isSystem) {
|
|
586
594
|
prefixRuns.push({ text: "⚙ ", color: "yellow", bold: true });
|
|
595
|
+
} else if (session?.serviceKind) {
|
|
596
|
+
// Service session (tree-scoped machinery, e.g. the regen distiller):
|
|
597
|
+
// the alembic marks it as read-only distillation machinery.
|
|
598
|
+
prefixRuns.push({ text: "⚗ ", color: "magenta", bold: true });
|
|
587
599
|
} else {
|
|
588
600
|
const icon = sessionStatusIcon(session, mode);
|
|
589
601
|
prefixRuns.push({
|
|
@@ -2287,8 +2299,14 @@ export function selectChatLines(state, maxWidth = 80, options = {}) {
|
|
|
2287
2299
|
};
|
|
2288
2300
|
const lines = [];
|
|
2289
2301
|
for (const [index, message] of messages.entries()) {
|
|
2290
|
-
|
|
2291
|
-
|
|
2302
|
+
if (message?.kind === "epoch-divider") {
|
|
2303
|
+
lines.push(buildEpochDividerLine(message, maxWidth));
|
|
2304
|
+
} else if (message?.kind === "regen-refused") {
|
|
2305
|
+
lines.push(buildRegenRefusedLine(message, maxWidth));
|
|
2306
|
+
} else {
|
|
2307
|
+
const messageLines = buildChatMessageLines(message, maxWidth, buildOptions);
|
|
2308
|
+
appendChatBlockLines(lines, messageLines);
|
|
2309
|
+
}
|
|
2292
2310
|
const nextMessage = messages[index + 1];
|
|
2293
2311
|
if (
|
|
2294
2312
|
nextMessage
|
|
@@ -2301,6 +2319,55 @@ export function selectChatLines(state, maxWidth = 80, options = {}) {
|
|
|
2301
2319
|
return lines.length > 0 ? lines : [{ text: "No messages yet.", color: "gray" }];
|
|
2302
2320
|
}
|
|
2303
2321
|
|
|
2322
|
+
// A centered inline rule ("──── label ────") for transcript markers. The dash
|
|
2323
|
+
// runs are CAPPED (not stretched to maxWidth): the web portal wraps by pixel
|
|
2324
|
+
// width, and a maxWidth-long "─" run overflows a narrower pane and wraps the
|
|
2325
|
+
// rule mid-label. A short symmetric rule reads as a divider on every width, and
|
|
2326
|
+
// when the label alone will not fit it is rendered bare (wraps as plain text,
|
|
2327
|
+
// never as dangling dash fragments).
|
|
2328
|
+
function buildRuleLine(label, color, maxWidth) {
|
|
2329
|
+
const safeWidth = Math.max(24, Number(maxWidth) || 80);
|
|
2330
|
+
const room = safeWidth - label.length;
|
|
2331
|
+
if (room < 2) return [{ text: label.trim(), color, bold: true }];
|
|
2332
|
+
const perSide = Math.min(6, Math.floor(room / 2));
|
|
2333
|
+
if (perSide < 1) return [{ text: label.trim(), color, bold: true }];
|
|
2334
|
+
return [
|
|
2335
|
+
{ text: "─".repeat(perSide), color },
|
|
2336
|
+
{ text: label, color, bold: true },
|
|
2337
|
+
{ text: "─".repeat(perSide), color },
|
|
2338
|
+
];
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
// The inline transcript divider for a session-regeneration epoch flip — magenta,
|
|
2342
|
+
// with the new epoch and the count of archived turns (proposal M2).
|
|
2343
|
+
function buildEpochDividerLine(message, maxWidth) {
|
|
2344
|
+
const turns = Number.isFinite(message?.turnsArchived) ? message.turnsArchived : null;
|
|
2345
|
+
const label = ` ↻ context regenerated · epoch ${message?.epoch ?? "?"}`
|
|
2346
|
+
+ `${turns != null ? ` · ${turns} turn${turns === 1 ? "" : "s"} archived` : ""} `;
|
|
2347
|
+
return buildRuleLine(label, "magenta", maxWidth);
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
// Friendly (compact) text for the orchestration's regenerate_refused reasons
|
|
2351
|
+
// (lifecycle.ts). Kept short so the inline rule fits on one line.
|
|
2352
|
+
const REGEN_REFUSED_REASONS = {
|
|
2353
|
+
cooldown: "on cooldown (once per 6h)",
|
|
2354
|
+
too_young: "too soon (needs 5+ turns)",
|
|
2355
|
+
already_pending: "already in progress",
|
|
2356
|
+
is_system: "not allowed for system sessions",
|
|
2357
|
+
not_owner: "owner only",
|
|
2358
|
+
not_parent: "parent only",
|
|
2359
|
+
};
|
|
2360
|
+
|
|
2361
|
+
// The inline notice for a refused regeneration. Yellow (vs the magenta success
|
|
2362
|
+
// divider) so a no-op attempt reads as a warning, correcting the tool's
|
|
2363
|
+
// optimistic "regeneration accepted" acknowledgement.
|
|
2364
|
+
function buildRegenRefusedLine(message, maxWidth) {
|
|
2365
|
+
const reason = String(message?.reason || "unknown");
|
|
2366
|
+
const text = REGEN_REFUSED_REASONS[reason] || reason.replace(/_/g, " ");
|
|
2367
|
+
const label = ` ↻ regeneration refused · ${text} `;
|
|
2368
|
+
return buildRuleLine(label, "yellow", maxWidth);
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2304
2371
|
export function selectOutboxOverlayLines(state, maxWidth = 80, options = {}) {
|
|
2305
2372
|
const messages = selectActiveOutboxMessages(state);
|
|
2306
2373
|
if (!messages || messages.length === 0) return [];
|
|
@@ -3997,7 +4064,7 @@ function buildNodeMapCell(session, brandingTitle, width, active) {
|
|
|
3997
4064
|
? canonicalSystemTitle(session, brandingTitle)
|
|
3998
4065
|
: (session?.title || shortSessionId(session?.sessionId)))
|
|
3999
4066
|
: shortSessionId(session?.sessionId);
|
|
4000
|
-
const prefix = session?.isSystem ? "⚙ " : `${sessionStatusIcon(session) || "."} `;
|
|
4067
|
+
const prefix = session?.isSystem ? "⚙ " : session?.serviceKind ? "⚗ " : `${sessionStatusIcon(session) || "."} `;
|
|
4001
4068
|
const text = padDisplayText(`${prefix}${label}`, width);
|
|
4002
4069
|
|
|
4003
4070
|
if (active) {
|
|
@@ -5156,10 +5223,28 @@ function buildSessionStatsLines(state, session, maxWidth) {
|
|
|
5156
5223
|
}));
|
|
5157
5224
|
lines.push(plainInspectorLine(""));
|
|
5158
5225
|
|
|
5159
|
-
// Persistence card
|
|
5226
|
+
// Persistence card. Epoch (session-regeneration incarnation) is always
|
|
5227
|
+
// shown — 0 for a session that has never regenerated — so the current
|
|
5228
|
+
// epoch is visible at a glance; regen counters appear once it has.
|
|
5229
|
+
const regenCount = Number(summary.regenCount) || 0;
|
|
5230
|
+
const lastRegen = summary.lastRegenStats && typeof summary.lastRegenStats === "object" ? summary.lastRegenStats : null;
|
|
5231
|
+
const currentEpoch = Number.isFinite(lastRegen?.toEpoch) ? lastRegen.toEpoch : regenCount;
|
|
5232
|
+
// Distillation provenance: "fast" = deterministic package; otherwise the
|
|
5233
|
+
// distiller model label (strip the provider: prefix for width).
|
|
5234
|
+
const distillLabel = lastRegen
|
|
5235
|
+
? (lastRegen.distillMode === "deterministic" || (!lastRegen.distillMode && !lastRegen.distillerModel)
|
|
5236
|
+
? "fast"
|
|
5237
|
+
: String(lastRegen.distillerModel || "llm").replace(/^[^:]*:/, ""))
|
|
5238
|
+
: null;
|
|
5239
|
+
const lastRegenLabel = lastRegen
|
|
5240
|
+
? `${lastRegen.turnsArchived ?? 0} turn${lastRegen.turnsArchived === 1 ? "" : "s"} · ${(Number(lastRegen.totalMs) / 1000).toFixed(1)}s${distillLabel ? ` · ${distillLabel}` : ""}`
|
|
5241
|
+
: null;
|
|
5160
5242
|
lines.push(...buildMessageCardLines({
|
|
5161
5243
|
title: "Persistence",
|
|
5162
5244
|
body: formatKeyValueTable([
|
|
5245
|
+
["Epoch", String(currentEpoch)],
|
|
5246
|
+
["Regens", regenCount > 0 ? String(regenCount) : null],
|
|
5247
|
+
["Last Regen", lastRegenLabel],
|
|
5163
5248
|
["Snapshot", formatCompactBytes(summary.snapshotSizeBytes)],
|
|
5164
5249
|
["Uncompressed", summary.rawSizeBytes ? formatCompactBytes(summary.rawSizeBytes) : null],
|
|
5165
5250
|
["Compression", formatCompressionRatio(summary.rawSizeBytes, summary.snapshotSizeBytes)],
|
package/ui/react/src/web-app.js
CHANGED
|
@@ -2199,13 +2199,6 @@ function SessionPane({ controller, actions = null, panelClassName = "", structur
|
|
|
2199
2199
|
return s && !s.isSystem && !s.isGroup;
|
|
2200
2200
|
})
|
|
2201
2201
|
: activeSession?.isGroup ? true : Boolean(activeSession);
|
|
2202
|
-
const activeSessionActionLabel = activeSession?.isGroup
|
|
2203
|
-
? "Delete"
|
|
2204
|
-
: isBulkSelection
|
|
2205
|
-
? `Terminate (${selectedCount})`
|
|
2206
|
-
: activeSession?.isSystem
|
|
2207
|
-
? "Restart"
|
|
2208
|
-
: "Terminate";
|
|
2209
2202
|
const hasExplicitSelection = selectedCount > 0;
|
|
2210
2203
|
const groupableIds = hasExplicitSelection
|
|
2211
2204
|
? viewState.selectedIds.filter((id) => {
|
|
@@ -2300,7 +2293,7 @@ function SessionPane({ controller, actions = null, panelClassName = "", structur
|
|
|
2300
2293
|
}),
|
|
2301
2294
|
React.createElement(IconButton, {
|
|
2302
2295
|
className: "ps-mini-button",
|
|
2303
|
-
icon: activeSession?.isSystem ? "↻" : "⊗",
|
|
2296
|
+
icon: activeSession?.isSystem ? "↻" : activeSession?.isGroup ? "⊗" : React.createElement(LifecycleGlyph),
|
|
2304
2297
|
onClick: () => controller.handleCommand(activeSession?.isGroup ? UI_COMMANDS.DELETE_SESSION : UI_COMMANDS.OPEN_TERMINATE_PICKER).catch(() => {}),
|
|
2305
2298
|
disabled: !canTerminate,
|
|
2306
2299
|
label: isBulkSelection
|
|
@@ -2309,7 +2302,7 @@ function SessionPane({ controller, actions = null, panelClassName = "", structur
|
|
|
2309
2302
|
? (activeGroupCanDelete ? "Delete this empty group" : "This group cannot be deleted yet")
|
|
2310
2303
|
: activeSession?.isSystem
|
|
2311
2304
|
? "Restart this system session (complete, terminate, or hard delete)"
|
|
2312
|
-
:
|
|
2305
|
+
: "Lifecycle — regenerate context, mark completed, cancel, or delete",
|
|
2313
2306
|
}),
|
|
2314
2307
|
actions);
|
|
2315
2308
|
|
|
@@ -2476,6 +2469,20 @@ function LinkGlyph() {
|
|
|
2476
2469
|
React.createElement("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" }));
|
|
2477
2470
|
}
|
|
2478
2471
|
|
|
2472
|
+
// The "lifecycle" glyph (two curved arrows forming a cycle). Fronts the session
|
|
2473
|
+
// Lifecycle menu — Regenerate (rebirth) plus the terminal dispositions.
|
|
2474
|
+
function LifecycleGlyph() {
|
|
2475
|
+
return React.createElement("svg", {
|
|
2476
|
+
className: "ps-share-glyph", viewBox: "0 0 24 24", fill: "none",
|
|
2477
|
+
stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round",
|
|
2478
|
+
"aria-hidden": "true",
|
|
2479
|
+
},
|
|
2480
|
+
React.createElement("path", { d: "M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }),
|
|
2481
|
+
React.createElement("path", { d: "M3 3v5h5" }),
|
|
2482
|
+
React.createElement("path", { d: "M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16" }),
|
|
2483
|
+
React.createElement("path", { d: "M21 21v-5h-5" }));
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2479
2486
|
/**
|
|
2480
2487
|
* Fetch the caller's effective access to the active session (security model).
|
|
2481
2488
|
* Returns { access, loading, reload }. access is the getSessionAccess payload
|
|
@@ -2854,12 +2861,18 @@ function ChatPane({ controller, mobile = false, fullWidth = false, showComposer
|
|
|
2854
2861
|
);
|
|
2855
2862
|
const navigationError = useControllerSelector(controller, selectNavigationError, shallowEqualObject);
|
|
2856
2863
|
const composerBase = showComposer && !viewState.activeSessionIsGroup && viewState.chatViewMode !== "summary";
|
|
2857
|
-
|
|
2864
|
+
// Service sessions (⚗ tree-scoped machinery, e.g. the regen distiller) are
|
|
2865
|
+
// read-only BY KIND: their transcript is the trace of runtime machinery,
|
|
2866
|
+
// never a conversation surface — no prompt for anyone, owner included.
|
|
2867
|
+
const activeIsService = Boolean(viewState.sessionsById?.[viewState.activeSessionId]?.serviceKind);
|
|
2868
|
+
const readOnly = activeIsService || (Boolean(access) && access.canWrite === false);
|
|
2858
2869
|
const composer = composerBase
|
|
2859
2870
|
? React.createElement("div", { className: "ps-chat-composer" },
|
|
2860
2871
|
readOnly
|
|
2861
2872
|
? React.createElement("div", { className: "ps-composer-readonly" },
|
|
2862
|
-
|
|
2873
|
+
activeIsService
|
|
2874
|
+
? "⚗ Service session — runtime machinery. Its transcript is a read-only trace; it does not accept messages."
|
|
2875
|
+
: `You have view access to this session. Ask ${access.owner?.displayName || access.owner?.email || "the owner"} for write access to participate.`)
|
|
2863
2876
|
: React.createElement(PromptComposer, { controller, mobile, active: true }))
|
|
2864
2877
|
: null;
|
|
2865
2878
|
|
|
@@ -4499,6 +4512,10 @@ function ModalLayer({ controller }) {
|
|
|
4499
4512
|
if (modal.type === "confirm" && modalState.confirm) {
|
|
4500
4513
|
const isAlert = Boolean(modal.alert);
|
|
4501
4514
|
const isDestructive = !isAlert && modal.action === "deleteSession";
|
|
4515
|
+
// The regenerate confirm carries distillation inputs: a mode select and
|
|
4516
|
+
// an optional distilling-instructions textarea, bound to modal.extras.
|
|
4517
|
+
const isRegen = modal.action === "regenerateSession";
|
|
4518
|
+
const extras = modal.extras || {};
|
|
4502
4519
|
return React.createElement("div", { className: "ps-modal-backdrop", onClick: close },
|
|
4503
4520
|
React.createElement("div", { className: "ps-modal is-narrow", onClick: (event) => event.stopPropagation() },
|
|
4504
4521
|
React.createElement("div", { className: "ps-modal-header" },
|
|
@@ -4508,6 +4525,32 @@ function ModalLayer({ controller }) {
|
|
|
4508
4525
|
React.createElement("div", { className: "ps-modal-body", style: { padding: "16px 20px" } },
|
|
4509
4526
|
React.createElement("p", { style: { color: "#94a3b8", margin: 0 } }, modalState.confirm.message),
|
|
4510
4527
|
),
|
|
4528
|
+
isRegen
|
|
4529
|
+
? React.createElement("div", { className: "ps-modal-body", style: { padding: "0 20px 12px", display: "flex", flexDirection: "column", gap: 10 } },
|
|
4530
|
+
React.createElement("label", { style: { color: "#94a3b8", fontSize: "12px", display: "flex", alignItems: "center", gap: 8 } },
|
|
4531
|
+
"Distillation",
|
|
4532
|
+
React.createElement("select", {
|
|
4533
|
+
className: "ps-modal-input",
|
|
4534
|
+
style: { flex: "1", padding: "4px 8px" },
|
|
4535
|
+
value: extras.distillMode || "llm",
|
|
4536
|
+
onChange: (event) => controller.updateConfirmExtras({ distillMode: event.currentTarget.value }),
|
|
4537
|
+
},
|
|
4538
|
+
React.createElement("option", { value: "llm" }, "Intelligent (LLM reads the whole transcript)"),
|
|
4539
|
+
React.createElement("option", { value: "deterministic" }, "Fast (no LLM — tail + pointers)"),
|
|
4540
|
+
),
|
|
4541
|
+
),
|
|
4542
|
+
(extras.distillMode || "llm") === "llm"
|
|
4543
|
+
? React.createElement("textarea", {
|
|
4544
|
+
className: "ps-modal-input",
|
|
4545
|
+
style: { width: "100%", minHeight: "64px", resize: "vertical", fontFamily: "inherit", fontSize: "13px" },
|
|
4546
|
+
placeholder: "Distilling instructions (optional) — e.g. \"preserve every SQL snippet verbatim\"",
|
|
4547
|
+
value: extras.instructions || "",
|
|
4548
|
+
maxLength: 4000,
|
|
4549
|
+
onChange: (event) => controller.updateConfirmExtras({ instructions: event.currentTarget.value }),
|
|
4550
|
+
})
|
|
4551
|
+
: null,
|
|
4552
|
+
)
|
|
4553
|
+
: null,
|
|
4511
4554
|
React.createElement("div", { className: "ps-modal-footer" },
|
|
4512
4555
|
isAlert ? null : React.createElement("button", { type: "button", className: "ps-modal-button", onClick: close }, "Cancel"),
|
|
4513
4556
|
React.createElement("button", {
|
|
@@ -4657,7 +4700,7 @@ function ModalLayer({ controller }) {
|
|
|
4657
4700
|
return React.createElement("div", { className: "ps-modal-backdrop", onClick: close },
|
|
4658
4701
|
React.createElement("div", { className: "ps-modal is-narrow", onClick: (event) => event.stopPropagation() },
|
|
4659
4702
|
React.createElement("div", { className: "ps-modal-header" },
|
|
4660
|
-
React.createElement("div", { className: "ps-modal-title" }, modal.title || "
|
|
4703
|
+
React.createElement("div", { className: "ps-modal-title" }, modal.title || "Session Lifecycle"),
|
|
4661
4704
|
React.createElement("button", { type: "button", className: "ps-modal-close", onClick: close }, "Close"),
|
|
4662
4705
|
),
|
|
4663
4706
|
React.createElement("div", { className: "ps-modal-body", style: { padding: "12px 16px 4px" } },
|
|
@@ -4666,6 +4709,15 @@ function ModalLayer({ controller }) {
|
|
|
4666
4709
|
className: "ps-modal-body",
|
|
4667
4710
|
style: { padding: "10px 16px 16px", display: "flex", flexDirection: "column", gap: 8 },
|
|
4668
4711
|
},
|
|
4712
|
+
modal.canRegenerate
|
|
4713
|
+
? React.createElement("button", {
|
|
4714
|
+
type: "button",
|
|
4715
|
+
className: "ps-modal-button",
|
|
4716
|
+
style: { width: "100%", justifyContent: "flex-start" },
|
|
4717
|
+
title: "Archive and distill the transcript, then rebuild context fresh at the next turn boundary. Facts, artifacts, sub-agents, sharing, schedule, and chat history are preserved.",
|
|
4718
|
+
onClick: pick("regenerate"),
|
|
4719
|
+
}, "Regenerate Context")
|
|
4720
|
+
: null,
|
|
4669
4721
|
React.createElement("button", {
|
|
4670
4722
|
type: "button",
|
|
4671
4723
|
className: "ps-modal-button is-primary",
|
|
@@ -4771,7 +4823,7 @@ function useKeyboardShortcuts(controller, mobile) {
|
|
|
4771
4823
|
const handler = (event) => {
|
|
4772
4824
|
const target = event.target;
|
|
4773
4825
|
const editable = target instanceof HTMLElement
|
|
4774
|
-
&& (target.tagName === "TEXTAREA" || target.tagName === "INPUT" || target.isContentEditable);
|
|
4826
|
+
&& (target.tagName === "TEXTAREA" || target.tagName === "INPUT" || target.tagName === "SELECT" || target.isContentEditable);
|
|
4775
4827
|
const modal = controller.getState().ui.modal;
|
|
4776
4828
|
const visibleInspectorTabs = getVisibleInspectorTabs(controller);
|
|
4777
4829
|
const currentInspectorTab = controller.getState().ui.inspectorTab;
|