@esso0428/pi-subagents 0.15.5 → 0.15.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/CHANGELOG.md +10 -19
- package/README.md +5 -5
- package/dist/agent-history-list.d.ts +19 -0
- package/dist/agent-history-list.d.ts.map +1 -0
- package/dist/agent-history-list.js +69 -0
- package/dist/agent-history-list.js.map +1 -0
- package/dist/agent-history.d.ts +2 -1
- package/dist/agent-history.d.ts.map +1 -0
- package/dist/agent-history.js +32 -34
- package/dist/agent-history.js.map +1 -0
- package/dist/agent-manager.d.ts +24 -8
- package/dist/agent-manager.d.ts.map +1 -0
- package/dist/agent-manager.js +171 -93
- package/dist/agent-manager.js.map +1 -0
- package/dist/agent-runner.d.ts +1 -0
- package/dist/agent-runner.d.ts.map +1 -0
- package/dist/agent-runner.js +75 -86
- package/dist/agent-runner.js.map +1 -0
- package/dist/agent-types.d.ts +1 -0
- package/dist/agent-types.d.ts.map +1 -0
- package/dist/agent-types.js +28 -45
- package/dist/agent-types.js.map +1 -0
- package/dist/context.d.ts +1 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +3 -6
- package/dist/context.js.map +1 -0
- package/dist/cross-extension-rpc.d.ts +1 -0
- package/dist/cross-extension-rpc.d.ts.map +1 -0
- package/dist/cross-extension-rpc.js +6 -9
- package/dist/cross-extension-rpc.js.map +1 -0
- package/dist/custom-agents.d.ts +1 -0
- package/dist/custom-agents.d.ts.map +1 -0
- package/dist/custom-agents.js +16 -18
- package/dist/custom-agents.js.map +1 -0
- package/dist/default-agents.d.ts +1 -0
- package/dist/default-agents.d.ts.map +1 -0
- package/dist/default-agents.js +2 -4
- package/dist/default-agents.js.map +1 -0
- package/dist/enabled-models.d.ts +1 -0
- package/dist/enabled-models.d.ts.map +1 -0
- package/dist/enabled-models.js +12 -16
- package/dist/enabled-models.js.map +1 -0
- package/dist/env.d.ts +1 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +2 -4
- package/dist/env.js.map +1 -0
- package/dist/group-join.d.ts +1 -0
- package/dist/group-join.d.ts.map +1 -0
- package/dist/group-join.js +2 -5
- package/dist/group-join.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +306 -292
- package/dist/index.js.map +1 -0
- package/dist/invocation-config.d.ts +1 -0
- package/dist/invocation-config.d.ts.map +1 -0
- package/dist/invocation-config.js +3 -6
- package/dist/invocation-config.js.map +1 -0
- package/dist/memory.d.ts +1 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/memory.js +25 -34
- package/dist/memory.js.map +1 -0
- package/dist/model-resolver.d.ts +1 -0
- package/dist/model-resolver.d.ts.map +1 -0
- package/dist/model-resolver.js +2 -4
- package/dist/model-resolver.js.map +1 -0
- package/dist/nico-overrides.d.ts +1 -0
- package/dist/nico-overrides.d.ts.map +1 -0
- package/dist/nico-overrides.js +15 -20
- package/dist/nico-overrides.js.map +1 -0
- package/dist/output-file.d.ts +1 -0
- package/dist/output-file.d.ts.map +1 -0
- package/dist/output-file.js +17 -22
- package/dist/output-file.js.map +1 -0
- package/dist/prompts.d.ts +1 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +2 -4
- package/dist/prompts.js.map +1 -0
- package/dist/schedule-store.d.ts +1 -0
- package/dist/schedule-store.d.ts.map +1 -0
- package/dist/schedule-store.js +17 -21
- package/dist/schedule-store.js.map +1 -0
- package/dist/schedule.d.ts +1 -0
- package/dist/schedule.d.ts.map +1 -0
- package/dist/schedule.js +9 -12
- package/dist/schedule.js.map +1 -0
- package/dist/settings.d.ts +1 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +16 -23
- package/dist/settings.js.map +1 -0
- package/dist/skill-loader.d.ts +1 -0
- package/dist/skill-loader.d.ts.map +1 -0
- package/dist/skill-loader.js +21 -23
- package/dist/skill-loader.js.map +1 -0
- package/dist/status-note.d.ts +1 -0
- package/dist/status-note.d.ts.map +1 -0
- package/dist/status-note.js +2 -4
- package/dist/status-note.js.map +1 -0
- package/dist/types.d.ts +5 -1
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -4
- package/dist/types.js.map +1 -0
- package/dist/ui/agent-widget.d.ts +2 -5
- package/dist/ui/agent-widget.d.ts.map +1 -0
- package/dist/ui/agent-widget.js +29 -51
- package/dist/ui/agent-widget.js.map +1 -0
- package/dist/ui/ccstyle/diff/ansi-utils.d.ts +11 -0
- package/dist/ui/ccstyle/diff/ansi-utils.d.ts.map +1 -0
- package/dist/ui/ccstyle/diff/ansi-utils.js +145 -0
- package/dist/ui/ccstyle/diff/ansi-utils.js.map +1 -0
- package/dist/ui/ccstyle/diff/diff-presentation.d.ts +12 -0
- package/dist/ui/ccstyle/diff/diff-presentation.d.ts.map +1 -0
- package/dist/ui/ccstyle/diff/diff-presentation.js +49 -0
- package/dist/ui/ccstyle/diff/diff-presentation.js.map +1 -0
- package/dist/ui/ccstyle/diff/diff-renderer.d.ts +46 -0
- package/dist/ui/ccstyle/diff/diff-renderer.d.ts.map +1 -0
- package/dist/ui/ccstyle/diff/diff-renderer.js +2044 -0
- package/dist/ui/ccstyle/diff/diff-renderer.js.map +1 -0
- package/dist/ui/ccstyle/diff/line-width-safety.d.ts +12 -0
- package/dist/ui/ccstyle/diff/line-width-safety.d.ts.map +1 -0
- package/dist/ui/ccstyle/diff/line-width-safety.js +59 -0
- package/dist/ui/ccstyle/diff/line-width-safety.js.map +1 -0
- package/dist/ui/ccstyle/diff/render-utils.d.ts +6 -0
- package/dist/ui/ccstyle/diff/render-utils.d.ts.map +1 -0
- package/dist/ui/ccstyle/diff/render-utils.js +25 -0
- package/dist/ui/ccstyle/diff/render-utils.js.map +1 -0
- package/dist/ui/ccstyle/diff/types.d.ts +22 -0
- package/dist/ui/ccstyle/diff/types.d.ts.map +1 -0
- package/dist/ui/ccstyle/diff/types.js +10 -0
- package/dist/ui/ccstyle/diff/types.js.map +1 -0
- package/dist/ui/ccstyle/diff/write-display-utils.d.ts +2 -0
- package/dist/ui/ccstyle/diff/write-display-utils.d.ts.map +1 -0
- package/dist/ui/ccstyle/diff/write-display-utils.js +12 -0
- package/dist/ui/ccstyle/diff/write-display-utils.js.map +1 -0
- package/dist/ui/ccstyle/tool-renderer.d.ts +17 -0
- package/dist/ui/ccstyle/tool-renderer.d.ts.map +1 -0
- package/dist/ui/ccstyle/tool-renderer.js +58 -0
- package/dist/ui/ccstyle/tool-renderer.js.map +1 -0
- package/dist/ui/ccstyle/tool-result.d.ts +24 -0
- package/dist/ui/ccstyle/tool-result.d.ts.map +1 -0
- package/dist/ui/ccstyle/tool-result.js +193 -0
- package/dist/ui/ccstyle/tool-result.js.map +1 -0
- package/dist/ui/conversation-blocks.d.ts +46 -0
- package/dist/ui/conversation-blocks.d.ts.map +1 -0
- package/dist/ui/conversation-blocks.js +306 -0
- package/dist/ui/conversation-blocks.js.map +1 -0
- package/dist/ui/conversation-nvim.d.ts +8 -0
- package/dist/ui/conversation-nvim.d.ts.map +1 -0
- package/dist/ui/conversation-nvim.js +117 -0
- package/dist/ui/conversation-nvim.js.map +1 -0
- package/dist/ui/conversation-role.d.ts +13 -0
- package/dist/ui/conversation-role.d.ts.map +1 -0
- package/dist/ui/conversation-role.js +39 -0
- package/dist/ui/conversation-role.js.map +1 -0
- package/dist/ui/conversation-search.d.ts +39 -0
- package/dist/ui/conversation-search.d.ts.map +1 -0
- package/dist/ui/conversation-search.js +124 -0
- package/dist/ui/conversation-search.js.map +1 -0
- package/dist/ui/conversation-timeline.d.ts +62 -0
- package/dist/ui/conversation-timeline.d.ts.map +1 -0
- package/dist/ui/conversation-timeline.js +263 -0
- package/dist/ui/conversation-timeline.js.map +1 -0
- package/dist/ui/conversation-viewer.d.ts +62 -20
- package/dist/ui/conversation-viewer.d.ts.map +1 -0
- package/dist/ui/conversation-viewer.js +449 -224
- package/dist/ui/conversation-viewer.js.map +1 -0
- package/dist/ui/fleet-list.d.ts +24 -11
- package/dist/ui/fleet-list.d.ts.map +1 -0
- package/dist/ui/fleet-list.js +86 -78
- package/dist/ui/fleet-list.js.map +1 -0
- package/dist/ui/schedule-menu.d.ts +1 -0
- package/dist/ui/schedule-menu.d.ts.map +1 -0
- package/dist/ui/schedule-menu.js +2 -4
- package/dist/ui/schedule-menu.js.map +1 -0
- package/dist/ui/viewer-keys.d.ts +1 -0
- package/dist/ui/viewer-keys.d.ts.map +1 -0
- package/dist/ui/viewer-keys.js +8 -10
- package/dist/ui/viewer-keys.js.map +1 -0
- package/dist/usage.d.ts +1 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +5 -10
- package/dist/usage.js.map +1 -0
- package/dist/worktree.d.ts +1 -0
- package/dist/worktree.d.ts.map +1 -0
- package/dist/worktree.js +27 -31
- package/dist/worktree.js.map +1 -0
- package/dist/write-execution.d.ts +42 -0
- package/dist/write-execution.d.ts.map +1 -0
- package/dist/write-execution.js +138 -0
- package/dist/write-execution.js.map +1 -0
- package/package.json +1 -1
- package/src/agent-history-list.ts +91 -0
- package/src/agent-history.ts +12 -17
- package/src/agent-manager.ts +169 -81
- package/src/agent-runner.ts +12 -13
- package/src/index.ts +117 -98
- package/src/output-file.ts +1 -1
- package/src/types.ts +4 -1
- package/src/ui/agent-widget.ts +3 -9
- package/src/ui/ccstyle/ATTRIBUTION.md +37 -0
- package/src/ui/ccstyle/diff/ansi-utils.ts +168 -0
- package/src/ui/ccstyle/diff/diff-presentation.ts +70 -0
- package/src/ui/ccstyle/diff/diff-renderer.ts +3148 -0
- package/src/ui/ccstyle/diff/line-width-safety.ts +92 -0
- package/src/ui/ccstyle/diff/render-utils.ts +32 -0
- package/src/ui/ccstyle/diff/types.ts +33 -0
- package/src/ui/ccstyle/diff/write-display-utils.ts +12 -0
- package/src/ui/ccstyle/tool-renderer.ts +71 -0
- package/src/ui/ccstyle/tool-result.ts +238 -0
- package/src/ui/conversation-blocks.ts +404 -0
- package/src/ui/conversation-nvim.ts +127 -0
- package/src/ui/conversation-role.ts +45 -0
- package/src/ui/conversation-search.ts +161 -0
- package/src/ui/conversation-timeline.ts +315 -0
- package/src/ui/conversation-viewer.ts +418 -210
- package/src/ui/fleet-list.ts +73 -52
- package/src/write-execution.ts +180 -0
- package/test/agent-history-list.test.ts +121 -0
- package/test/agent-manager-history.test.ts +59 -58
- package/test/agent-menu-history-navigation.test.ts +86 -0
- package/test/output-file-history.test.ts +50 -0
- package/test/ui/conversation-blocks.test.ts +103 -0
- package/test/ui/conversation-diff.test.ts +91 -0
- package/test/ui/conversation-search.test.ts +55 -0
- package/test/ui/conversation-viewer.test.ts +182 -0
- package/test/ui/fleet-list.test.ts +131 -0
- package/test/write-execution.test.ts +83 -0
- package/tsconfig.json +16 -0
- package/vitest.temp.config.mts +6 -0
- package/dist/ui/markdown-result.d.ts +0 -3
- package/dist/ui/markdown-result.js +0 -53
- package/src/ui/markdown-result.ts +0 -56
- package/test/agent-history.test.ts +0 -63
- package/test/ui/markdown-result.test.ts +0 -45
package/src/index.ts
CHANGED
|
@@ -24,7 +24,8 @@ import { isModelInScope, readEnabledModels, resolveEnabledModels } from "./enabl
|
|
|
24
24
|
import { GroupJoinManager } from "./group-join.js";
|
|
25
25
|
import { resolveAgentInvocationConfig, resolveJoinMode } from "./invocation-config.js";
|
|
26
26
|
import { type ModelRegistry, resolveModel } from "./model-resolver.js";
|
|
27
|
-
import { agentHistoryLocator, createAgentHistoryPath,
|
|
27
|
+
import { agentHistoryLocator, createAgentHistoryPath, readAgentHistory } from "./agent-history.js";
|
|
28
|
+
import { buildAgentStatusMenuEntries, canOpenActiveAgent, canOpenAgentHistory, formatAgentHistoryOption, splitAgentRecords } from "./agent-history-list.js";
|
|
28
29
|
import { createOutputFilePath, streamToOutputFile, writeInitialEntry } from "./output-file.js";
|
|
29
30
|
import { SubagentScheduler } from "./schedule.js";
|
|
30
31
|
import { resolveStorePath, ScheduleStore } from "./schedule-store.js";
|
|
@@ -49,7 +50,6 @@ import {
|
|
|
49
50
|
type UICtx,
|
|
50
51
|
} from "./ui/agent-widget.js";
|
|
51
52
|
import { FleetList, type FleetUICtx } from "./ui/fleet-list.js";
|
|
52
|
-
import { createMarkdownResult } from "./ui/markdown-result.js";
|
|
53
53
|
import { showSchedulesMenu } from "./ui/schedule-menu.js";
|
|
54
54
|
import { addUsage, getLifetimeTotal, getSessionContextPercent, type LifetimeUsage } from "./usage.js";
|
|
55
55
|
|
|
@@ -242,9 +242,6 @@ function buildDetails(
|
|
|
242
242
|
};
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
-
/** Maximum expanded notification body retained in message details. */
|
|
246
|
-
const MAX_NOTIFICATION_RESULT_TEXT_LENGTH = 20_000;
|
|
247
|
-
|
|
248
245
|
/** Build notification details for the custom message renderer. */
|
|
249
246
|
function buildNotificationDetails(record: AgentRecord, resultMaxLen: number, activity?: AgentActivity): NotificationDetails {
|
|
250
247
|
const totalTokens = getLifetimeTotal(record.lifetimeUsage);
|
|
@@ -260,11 +257,6 @@ function buildNotificationDetails(record: AgentRecord, resultMaxLen: number, act
|
|
|
260
257
|
durationMs: record.completedAt ? record.completedAt - record.startedAt : 0,
|
|
261
258
|
outputFile: record.outputFile,
|
|
262
259
|
error: record.error,
|
|
263
|
-
resultText: record.result
|
|
264
|
-
? record.result.length > MAX_NOTIFICATION_RESULT_TEXT_LENGTH
|
|
265
|
-
? record.result.slice(0, MAX_NOTIFICATION_RESULT_TEXT_LENGTH) + "…"
|
|
266
|
-
: record.result
|
|
267
|
-
: undefined,
|
|
268
260
|
resultPreview: record.result
|
|
269
261
|
? record.result.length > resultMaxLen
|
|
270
262
|
? record.result.slice(0, resultMaxLen) + "…"
|
|
@@ -281,7 +273,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
281
273
|
const d = message.details;
|
|
282
274
|
if (!d) return undefined;
|
|
283
275
|
|
|
284
|
-
function renderOne(d: NotificationDetails): string
|
|
276
|
+
function renderOne(d: NotificationDetails): string {
|
|
285
277
|
const isError = d.status === "error" || d.status === "stopped" || d.status === "aborted";
|
|
286
278
|
const icon = isError ? theme.fg("error", "✗") : theme.fg("success", "✓");
|
|
287
279
|
const statusText = isError ? d.status
|
|
@@ -301,34 +293,25 @@ export default function (pi: ExtensionAPI) {
|
|
|
301
293
|
line += "\n " + parts.map(p => theme.fg("dim", p)).join(" " + theme.fg("dim", "·") + " ");
|
|
302
294
|
}
|
|
303
295
|
|
|
304
|
-
|
|
305
|
-
|
|
296
|
+
// Line 3: result preview (collapsed) or full (expanded)
|
|
297
|
+
if (expanded) {
|
|
298
|
+
const lines = d.resultPreview.split("\n").slice(0, 30);
|
|
299
|
+
for (const l of lines) line += "\n" + theme.fg("dim", ` ${l}`);
|
|
300
|
+
} else {
|
|
306
301
|
const preview = d.resultPreview.split("\n")[0]?.slice(0, 80) ?? "";
|
|
307
302
|
line += "\n " + theme.fg("dim", `⎿ ${preview}`);
|
|
308
|
-
if (d.outputFile) {
|
|
309
|
-
line += "\n " + theme.fg("muted", `transcript: ${d.outputFile}`);
|
|
310
|
-
}
|
|
311
|
-
return line;
|
|
312
303
|
}
|
|
313
304
|
|
|
314
|
-
|
|
315
|
-
container.addChild(new Text(line, 0, 0));
|
|
316
|
-
container.addChild(createMarkdownResult(d.resultText ?? d.resultPreview, theme, 30));
|
|
305
|
+
// Line 4: output file link (if present)
|
|
317
306
|
if (d.outputFile) {
|
|
318
|
-
|
|
307
|
+
line += "\n " + theme.fg("muted", `transcript: ${d.outputFile}`);
|
|
319
308
|
}
|
|
320
|
-
|
|
309
|
+
|
|
310
|
+
return line;
|
|
321
311
|
}
|
|
322
312
|
|
|
323
313
|
const all = [d, ...(d.others ?? [])];
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
const container = new Container();
|
|
327
|
-
all.forEach((item, index) => {
|
|
328
|
-
if (index > 0) container.addChild(new Spacer(1));
|
|
329
|
-
container.addChild(renderOne(item) as Container);
|
|
330
|
-
});
|
|
331
|
-
return container;
|
|
314
|
+
return new Text(all.map(renderOne).join("\n"), 0, 0);
|
|
332
315
|
}
|
|
333
316
|
);
|
|
334
317
|
|
|
@@ -463,13 +446,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
463
446
|
pi.events.emit("subagents:completed", eventData);
|
|
464
447
|
}
|
|
465
448
|
|
|
466
|
-
// Persist final record for cross-extension history reconstruction
|
|
467
|
-
// the transcript body in a project-local file rather than inflating the
|
|
468
|
-
// parent session JSONL; only its project-relative locator is persisted.
|
|
449
|
+
// Persist final record for cross-extension history reconstruction
|
|
469
450
|
pi.appendEntry("subagents:record", {
|
|
470
451
|
id: record.id, type: record.type, description: record.description,
|
|
471
452
|
status: record.status, result: record.result, error: record.error,
|
|
472
453
|
startedAt: record.startedAt, completedAt: record.completedAt,
|
|
454
|
+
toolUses: record.toolUses,
|
|
455
|
+
lifetimeUsage: record.lifetimeUsage,
|
|
456
|
+
invocation: record.invocation,
|
|
473
457
|
transcriptPath: record.transcriptPath,
|
|
474
458
|
});
|
|
475
459
|
|
|
@@ -574,22 +558,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
574
558
|
// bound session_start, so a filtered-out activation never advertises (#142).
|
|
575
559
|
pi.on("session_start", async (_event, ctx) => {
|
|
576
560
|
currentCtx = ctx;
|
|
577
|
-
try {
|
|
578
|
-
ensureSubagentsGitignore(ctx.cwd);
|
|
579
|
-
} catch (err) {
|
|
580
|
-
console.warn("[pi-subagents] Failed to protect project-local history:", err);
|
|
581
|
-
}
|
|
582
561
|
manager.clearCompleted(true);
|
|
583
|
-
const
|
|
584
|
-
|
|
585
|
-
.filter((entry: any) => entry?.type === "custom" && entry
|
|
586
|
-
.map((entry: any) => entry.data);
|
|
587
|
-
manager.restoreCompleted(historicalRecords);
|
|
588
|
-
// Bind the UI here as well as on tool execution so rehydrated terminal
|
|
589
|
-
// records are immediately visible and openable from FleetView after reload.
|
|
590
|
-
widget.setUICtx(ctx.ui as UICtx);
|
|
591
|
-
fleet.setUICtx(ctx.ui as unknown as FleetUICtx);
|
|
592
|
-
fleet.update();
|
|
562
|
+
const branch = ctx.sessionManager?.getBranch?.() ?? [];
|
|
563
|
+
manager.restoreCompleted(branch
|
|
564
|
+
.filter((entry: any) => entry?.type === "custom" && entry?.customType === "subagents:record")
|
|
565
|
+
.map((entry: any) => entry.data));
|
|
593
566
|
// Guard mirrors the `!scheduler.isActive()` pattern below: session_start
|
|
594
567
|
// fires once per activation, but a double-bind must not leak listeners.
|
|
595
568
|
if (!rpcHandle) {
|
|
@@ -640,11 +613,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
640
613
|
// everything else; "off" = hide the widget entirely. Read live at render time.
|
|
641
614
|
let widgetMode: WidgetMode = "background";
|
|
642
615
|
function getWidgetMode(): WidgetMode { return widgetMode; }
|
|
643
|
-
const widget = new AgentWidget(manager, agentActivity, getWidgetMode
|
|
616
|
+
const widget = new AgentWidget(manager, agentActivity, getWidgetMode);
|
|
644
617
|
function setWidgetMode(m: WidgetMode): void { widgetMode = m; widget.update(); }
|
|
645
618
|
|
|
646
619
|
// Claude Code-style FleetView: navigable list of main + subagents below the editor.
|
|
647
|
-
const fleet = new FleetList(manager, agentActivity, () => currentCtx?.cwd);
|
|
620
|
+
const fleet = new FleetList(manager, agentActivity, () => currentCtx?.cwd, pi, () => currentCtx);
|
|
648
621
|
let fleetViewEnabled = true;
|
|
649
622
|
function isFleetViewEnabled(): boolean { return fleetViewEnabled; }
|
|
650
623
|
function setFleetViewEnabled(b: boolean): void { fleetViewEnabled = b; fleet.setEnabled(b); }
|
|
@@ -1061,15 +1034,15 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1061
1034
|
|
|
1062
1035
|
if (expanded) {
|
|
1063
1036
|
const resultText = result.content[0]?.type === "text" ? result.content[0].text : "";
|
|
1064
|
-
const container = new Container();
|
|
1065
|
-
container.addChild(new Text(line, 0, 0));
|
|
1066
1037
|
if (resultText) {
|
|
1067
|
-
|
|
1038
|
+
const lines = resultText.split("\n").slice(0, 50);
|
|
1039
|
+
for (const l of lines) {
|
|
1040
|
+
line += "\n" + theme.fg("dim", ` ${l}`);
|
|
1041
|
+
}
|
|
1068
1042
|
if (resultText.split("\n").length > 50) {
|
|
1069
|
-
|
|
1043
|
+
line += "\n" + theme.fg("muted", " ... (use get_subagent_result with verbose for full output)");
|
|
1070
1044
|
}
|
|
1071
1045
|
}
|
|
1072
|
-
return container;
|
|
1073
1046
|
} else {
|
|
1074
1047
|
const doneText = isSteered ? "Wrapped up (turn limit)" : "Done";
|
|
1075
1048
|
line += "\n" + theme.fg("dim", ` ⎿ ${doneText}`);
|
|
@@ -1176,17 +1149,17 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1176
1149
|
rec.outputFile = createOutputFilePath(ctx.cwd, agentId, ctx.sessionManager.getSessionId());
|
|
1177
1150
|
writeInitialEntry(rec.outputFile, agentId, params.prompt, ctx.cwd);
|
|
1178
1151
|
|
|
1179
|
-
// Keep the existing /tmp output for compatibility, while also writing a
|
|
1180
|
-
// durable project-local copy for reload-safe, read-only history viewing.
|
|
1181
1152
|
try {
|
|
1182
|
-
rec.historyFile = createAgentHistoryPath(ctx.cwd, agentId
|
|
1153
|
+
rec.historyFile = createAgentHistoryPath(ctx.cwd, agentId);
|
|
1183
1154
|
rec.transcriptPath = agentHistoryLocator(ctx.cwd, rec.historyFile);
|
|
1184
1155
|
writeInitialEntry(rec.historyFile, agentId, params.prompt, ctx.cwd);
|
|
1185
1156
|
} catch (err) {
|
|
1186
|
-
// History is best effort; never disable the existing output transcript.
|
|
1187
1157
|
rec.historyFile = undefined;
|
|
1188
1158
|
rec.transcriptPath = undefined;
|
|
1189
|
-
|
|
1159
|
+
ctx.ui.notify(
|
|
1160
|
+
`Could not create durable transcript for agent ${agentId}: ${err instanceof Error ? err.message : String(err)}`,
|
|
1161
|
+
"warning",
|
|
1162
|
+
);
|
|
1190
1163
|
}
|
|
1191
1164
|
};
|
|
1192
1165
|
|
|
@@ -1198,7 +1171,9 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1198
1171
|
const effectiveMaxTurns = normalizeMaxTurns(resolvedConfig.maxTurns ?? getDefaultMaxTurns());
|
|
1199
1172
|
const agentInvocation: AgentInvocation = {
|
|
1200
1173
|
modelName,
|
|
1174
|
+
effectiveModelName: model?.name ?? model?.id ?? ctx.model?.name ?? ctx.model?.id,
|
|
1201
1175
|
thinking,
|
|
1176
|
+
effectiveThinking: thinking,
|
|
1202
1177
|
// Explicit value only — the default fallback would just add noise.
|
|
1203
1178
|
// Normalize so `0` (unlimited) doesn't surface as a misleading "max turns: 0".
|
|
1204
1179
|
maxTurns: normalizeMaxTurns(resolvedConfig.maxTurns),
|
|
@@ -1667,17 +1642,10 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1667
1642
|
// Build select options
|
|
1668
1643
|
const options: string[] = [];
|
|
1669
1644
|
|
|
1670
|
-
//
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
record.session !== undefined || hasAgentHistory(ctx.cwd, record.transcriptPath),
|
|
1675
|
-
);
|
|
1676
|
-
if (agents.length > 0) {
|
|
1677
|
-
const running = agents.filter(a => a.status === "running" || a.status === "queued").length;
|
|
1678
|
-
const done = agents.filter(a => a.status === "completed" || a.status === "steered").length;
|
|
1679
|
-
options.push(`Running agents (${agents.length}) — ${running} running, ${done} done`);
|
|
1680
|
-
}
|
|
1645
|
+
// Keep active agents and terminal history in separate menu entries.
|
|
1646
|
+
const records = manager.listAgents();
|
|
1647
|
+
const { active, history } = splitAgentRecords(records, ctx.cwd);
|
|
1648
|
+
options.push(...buildAgentStatusMenuEntries(records, ctx.cwd));
|
|
1681
1649
|
|
|
1682
1650
|
// Agent types list
|
|
1683
1651
|
if (allNames.length > 0) {
|
|
@@ -1694,7 +1662,7 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1694
1662
|
options.push("Create new agent");
|
|
1695
1663
|
options.push("Settings");
|
|
1696
1664
|
|
|
1697
|
-
const noAgentsMsg = allNames.length === 0 &&
|
|
1665
|
+
const noAgentsMsg = allNames.length === 0 && active.length === 0 && history.length === 0
|
|
1698
1666
|
? "No agents found. Create specialized subagents that can be delegated to.\n\n" +
|
|
1699
1667
|
"Each subagent has its own context window, custom system prompt, and specific tools.\n\n" +
|
|
1700
1668
|
"Try creating: Code Reviewer, Security Auditor, Test Writer, or Documentation Writer.\n\n"
|
|
@@ -1710,6 +1678,9 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1710
1678
|
if (choice.startsWith("Running agents (")) {
|
|
1711
1679
|
await showRunningAgents(ctx);
|
|
1712
1680
|
await showAgentsMenu(ctx);
|
|
1681
|
+
} else if (choice.startsWith("Agent history (")) {
|
|
1682
|
+
await showAgentHistory(ctx);
|
|
1683
|
+
await showAgentsMenu(ctx);
|
|
1713
1684
|
} else if (choice.startsWith("Agent types (")) {
|
|
1714
1685
|
await showAllAgentsList(ctx);
|
|
1715
1686
|
await showAgentsMenu(ctx);
|
|
@@ -1792,54 +1763,101 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1792
1763
|
}
|
|
1793
1764
|
}
|
|
1794
1765
|
|
|
1766
|
+
function makeUniqueAgentOptionLabels(pairs: Array<{ record: AgentRecord; label: string }>): string[] {
|
|
1767
|
+
const counts = new Map<string, number>();
|
|
1768
|
+
for (const pair of pairs) counts.set(pair.label, (counts.get(pair.label) ?? 0) + 1);
|
|
1769
|
+
const used = new Set<string>();
|
|
1770
|
+
return pairs.map((pair) => {
|
|
1771
|
+
const { record, label } = pair;
|
|
1772
|
+
if ((counts.get(label) ?? 0) === 1) {
|
|
1773
|
+
used.add(label);
|
|
1774
|
+
return label;
|
|
1775
|
+
}
|
|
1776
|
+
const suffix = ` · #${record.id.slice(-8)}`;
|
|
1777
|
+
let candidate = `${label}${suffix}`;
|
|
1778
|
+
let n = 2;
|
|
1779
|
+
while (used.has(candidate)) candidate = `${label}${suffix}-${n++}`;
|
|
1780
|
+
used.add(candidate);
|
|
1781
|
+
pair.label = candidate;
|
|
1782
|
+
return candidate;
|
|
1783
|
+
});
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1795
1786
|
async function showRunningAgents(ctx: ExtensionCommandContext) {
|
|
1796
|
-
const agents = manager.listAgents().
|
|
1797
|
-
record.session !== undefined || hasAgentHistory(ctx.cwd, record.transcriptPath),
|
|
1798
|
-
);
|
|
1787
|
+
const { active: agents } = splitAgentRecords(manager.listAgents(), ctx.cwd);
|
|
1799
1788
|
if (agents.length === 0) {
|
|
1800
1789
|
ctx.ui.notify("No agents.", "info");
|
|
1801
1790
|
return;
|
|
1802
1791
|
}
|
|
1803
1792
|
|
|
1804
|
-
const
|
|
1805
|
-
const dn = getDisplayName(
|
|
1806
|
-
const dur = formatDuration(
|
|
1807
|
-
return `${dn} (${
|
|
1793
|
+
const pairs = agents.map((record) => {
|
|
1794
|
+
const dn = getDisplayName(record.type);
|
|
1795
|
+
const dur = formatDuration(record.startedAt, record.completedAt);
|
|
1796
|
+
return { record, label: `${dn} (${record.description}) · ${record.toolUses} tools · ${record.status} · ${dur}` };
|
|
1808
1797
|
});
|
|
1798
|
+
const options = makeUniqueAgentOptionLabels(pairs);
|
|
1809
1799
|
|
|
1810
1800
|
const choice = await ctx.ui.select("Running agents", options);
|
|
1811
1801
|
if (!choice) return;
|
|
1802
|
+
const record = pairs.find((pair) => pair.label === choice)?.record;
|
|
1803
|
+
if (!record) return;
|
|
1812
1804
|
|
|
1813
|
-
|
|
1814
|
-
const idx = options.indexOf(choice);
|
|
1815
|
-
if (idx < 0) return;
|
|
1816
|
-
const record = agents[idx];
|
|
1817
|
-
|
|
1818
|
-
await viewAgentConversation(ctx, record);
|
|
1805
|
+
await viewAgentConversation(ctx, record, "live");
|
|
1819
1806
|
// Back-navigation: re-show the list
|
|
1820
1807
|
await showRunningAgents(ctx);
|
|
1821
1808
|
}
|
|
1822
1809
|
|
|
1823
|
-
async function
|
|
1824
|
-
|
|
1825
|
-
|
|
1810
|
+
async function showAgentHistory(ctx: ExtensionCommandContext) {
|
|
1811
|
+
const { history } = splitAgentRecords(manager.listAgents(), ctx.cwd);
|
|
1812
|
+
if (history.length === 0) {
|
|
1813
|
+
ctx.ui.notify("No agent history.", "info");
|
|
1814
|
+
return;
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
const pairs = history.map((record) => ({ record, label: formatAgentHistoryOption(record, Date.now()) }));
|
|
1818
|
+
const options = makeUniqueAgentOptionLabels(pairs);
|
|
1819
|
+
const choice = await ctx.ui.select("Agent history", options);
|
|
1820
|
+
if (!choice) return;
|
|
1821
|
+
const record = pairs.find((pair) => pair.label === choice)?.record;
|
|
1822
|
+
if (!record) return;
|
|
1823
|
+
|
|
1824
|
+
await viewAgentConversation(ctx, record, "history");
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
async function viewAgentConversation(
|
|
1828
|
+
ctx: ExtensionCommandContext,
|
|
1829
|
+
record: AgentRecord,
|
|
1830
|
+
mode: "live" | "history",
|
|
1831
|
+
) {
|
|
1832
|
+
if (mode === "live" && !canOpenActiveAgent(record)) {
|
|
1833
|
+
ctx.ui.notify(`Agent is ${record.status === "queued" ? "queued" : "expired"} — no history available.`, "info");
|
|
1834
|
+
return;
|
|
1835
|
+
}
|
|
1836
|
+
if (mode === "history" && !canOpenAgentHistory(record, ctx.cwd)) {
|
|
1837
|
+
ctx.ui.notify("No agent history.", "info");
|
|
1826
1838
|
return;
|
|
1827
1839
|
}
|
|
1828
1840
|
|
|
1829
1841
|
const { ConversationViewer, VIEWPORT_HEIGHT_PCT, createStaticConversationSource } = await import("./ui/conversation-viewer.js");
|
|
1830
|
-
const session =
|
|
1831
|
-
?
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1842
|
+
const session = mode === "live"
|
|
1843
|
+
? record.session
|
|
1844
|
+
: (() => {
|
|
1845
|
+
const messages = record.transcriptPath
|
|
1846
|
+
? readAgentHistory(ctx.cwd, record.transcriptPath)
|
|
1847
|
+
: undefined;
|
|
1848
|
+
return messages
|
|
1849
|
+
? createStaticConversationSource(messages)
|
|
1850
|
+
: record.session
|
|
1851
|
+
? createStaticConversationSource(record.session.messages)
|
|
1852
|
+
: undefined;
|
|
1853
|
+
})();
|
|
1836
1854
|
if (!session) {
|
|
1837
|
-
ctx.ui.notify(
|
|
1855
|
+
ctx.ui.notify("No agent history.", "info");
|
|
1838
1856
|
return;
|
|
1839
1857
|
}
|
|
1840
|
-
const activity = agentActivity.get(record.id);
|
|
1841
|
-
const isLive = record.session !== undefined;
|
|
1842
1858
|
|
|
1859
|
+
const activity = agentActivity.get(record.id);
|
|
1860
|
+
const isLive = mode === "live";
|
|
1843
1861
|
await ctx.ui.custom<undefined>(
|
|
1844
1862
|
(tui, theme, keybindings, done) => {
|
|
1845
1863
|
return new ConversationViewer(tui, session, record, activity, theme, done,
|
|
@@ -1849,7 +1867,8 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1849
1867
|
}
|
|
1850
1868
|
} : undefined,
|
|
1851
1869
|
keybindings,
|
|
1852
|
-
isLive ? (message: string) => manager.steer(record.id, message) : undefined
|
|
1870
|
+
isLive ? (message: string) => manager.steer(record.id, message) : undefined,
|
|
1871
|
+
mode === "history" ? { pi, ctx, readOnly: true } : { pi, ctx });
|
|
1853
1872
|
},
|
|
1854
1873
|
{
|
|
1855
1874
|
overlay: true,
|
package/src/output-file.ts
CHANGED
|
@@ -65,8 +65,8 @@ export function streamToOutputFile(
|
|
|
65
65
|
cwd: string,
|
|
66
66
|
historyPath?: string,
|
|
67
67
|
): () => void {
|
|
68
|
-
const outputPaths = historyPath && historyPath !== path ? [path, historyPath] : [path];
|
|
69
68
|
let writtenCount = 1; // initial user prompt already written
|
|
69
|
+
const outputPaths = historyPath && historyPath !== path ? [path, historyPath] : [path];
|
|
70
70
|
|
|
71
71
|
const flush = () => {
|
|
72
72
|
const messages = session.messages;
|
package/src/types.ts
CHANGED
|
@@ -137,6 +137,10 @@ export interface AgentRecord {
|
|
|
137
137
|
export interface AgentInvocation {
|
|
138
138
|
/** Short display name, e.g. "haiku" — only set when different from parent. */
|
|
139
139
|
modelName?: string;
|
|
140
|
+
/** Effective model captured from the child session, including inherited models. */
|
|
141
|
+
effectiveModelName?: string;
|
|
142
|
+
/** Requested/ effective thinking level. */
|
|
143
|
+
effectiveThinking?: AgentSession["thinkingLevel"];
|
|
140
144
|
thinking?: ThinkingLevel;
|
|
141
145
|
maxTurns?: number;
|
|
142
146
|
isolated?: boolean;
|
|
@@ -158,7 +162,6 @@ export interface NotificationDetails {
|
|
|
158
162
|
outputFile?: string;
|
|
159
163
|
error?: string;
|
|
160
164
|
resultPreview: string;
|
|
161
|
-
resultText?: string;
|
|
162
165
|
/** Additional agents in a group notification. */
|
|
163
166
|
others?: NotificationDetails[];
|
|
164
167
|
}
|
package/src/ui/agent-widget.ts
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
9
|
-
import { hasAgentHistory } from "../agent-history.js";
|
|
10
9
|
import type { AgentManager } from "../agent-manager.js";
|
|
11
10
|
import { getConfig } from "../agent-types.js";
|
|
12
11
|
import type { AgentInvocation, SubagentType, WidgetMode } from "../types.js";
|
|
@@ -167,7 +166,8 @@ export function buildInvocationTags(
|
|
|
167
166
|
): { modelName?: string; tags: string[] } {
|
|
168
167
|
const tags: string[] = [];
|
|
169
168
|
if (!invocation) return { tags };
|
|
170
|
-
|
|
169
|
+
const thinking = invocation.effectiveThinking ?? invocation.thinking;
|
|
170
|
+
if (thinking) tags.push(`thinking: ${thinking}`);
|
|
171
171
|
if (invocation.isolated) tags.push("isolated");
|
|
172
172
|
if (invocation.isolation === "worktree") tags.push("worktree");
|
|
173
173
|
if (invocation.inheritContext) tags.push("inherit context");
|
|
@@ -238,8 +238,6 @@ export class AgentWidget {
|
|
|
238
238
|
* extension supplies one defaulting to `"background"`.
|
|
239
239
|
*/
|
|
240
240
|
private mode: () => WidgetMode = () => "all",
|
|
241
|
-
/** Current project cwd used to validate reload-restored transcripts. */
|
|
242
|
-
private getCwd: () => string | undefined = () => undefined,
|
|
243
241
|
) {}
|
|
244
242
|
|
|
245
243
|
/**
|
|
@@ -254,11 +252,7 @@ export class AgentWidget {
|
|
|
254
252
|
* - `all`: every agent.
|
|
255
253
|
*/
|
|
256
254
|
private widgetAgents() {
|
|
257
|
-
const all = this.manager.listAgents()
|
|
258
|
-
if (record.status === "running" || record.status === "queued") return true;
|
|
259
|
-
const cwd = this.getCwd();
|
|
260
|
-
return cwd !== undefined && hasAgentHistory(cwd, record.transcriptPath);
|
|
261
|
-
});
|
|
255
|
+
const all = this.manager.listAgents();
|
|
262
256
|
switch (this.mode()) {
|
|
263
257
|
case "off": return [];
|
|
264
258
|
case "background": return all.filter(a => a.isBackground !== false);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Attribution
|
|
2
|
+
|
|
3
|
+
The viewer-local ccstyle tool-card and diff renderer are adapted from
|
|
4
|
+
`@herbertgao/pi-cc-extensions` version `0.9.0`, specifically its
|
|
5
|
+
`extensions/renderer/tool/result.ts` and
|
|
6
|
+
`extensions/renderer/tool/diff/` modules.
|
|
7
|
+
|
|
8
|
+
The adaptation is local to this package and changes the configuration boundary,
|
|
9
|
+
write payload handling, width guards, and syntax highlighting fallback. It does
|
|
10
|
+
not install `@shikijs/cli`, patch Pi prototypes, or register global terminal
|
|
11
|
+
handlers. The local copy uses Pi's public `highlightCode` API and plain text
|
|
12
|
+
when highlighting is unavailable.
|
|
13
|
+
|
|
14
|
+
The source package is distributed under the MIT License. The relevant license
|
|
15
|
+
text is reproduced below:
|
|
16
|
+
|
|
17
|
+
> MIT License
|
|
18
|
+
>
|
|
19
|
+
> Copyright (c) 2026 Herbert Gao and pi-cc-extensions contributors
|
|
20
|
+
>
|
|
21
|
+
> Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
22
|
+
> of this software and associated documentation files (the "Software"), to deal
|
|
23
|
+
> in the Software without restriction, including without limitation the rights
|
|
24
|
+
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
25
|
+
> copies of the Software, and to permit persons to whom the Software is
|
|
26
|
+
> furnished to do so, subject to the following conditions:
|
|
27
|
+
>
|
|
28
|
+
> The above copyright notice and this permission notice shall be included in all
|
|
29
|
+
> copies or substantial portions of the Software.
|
|
30
|
+
>
|
|
31
|
+
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
32
|
+
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
33
|
+
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
34
|
+
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
35
|
+
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
36
|
+
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
37
|
+
> SOFTWARE.
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
const ANSI_SGR_PATTERN = /\x1b\[([0-9;]*)m/g;
|
|
2
|
+
const ANSI_SGR_WITH_COLON_PATTERN = /\x1b\[([0-9;:]*)m/g;
|
|
3
|
+
const STYLE_RESET_PARAMS = [39, 22, 23, 24, 25, 27, 28, 29, 59] as const;
|
|
4
|
+
|
|
5
|
+
export { ANSI_SGR_PATTERN, STYLE_RESET_PARAMS };
|
|
6
|
+
|
|
7
|
+
export function expandSgrReset(param: number): readonly number[] | undefined {
|
|
8
|
+
return param === 0 ? STYLE_RESET_PARAMS : undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function toSgrParams(rawParams: string): number[] {
|
|
12
|
+
if (!rawParams.trim()) {
|
|
13
|
+
return [0];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const parsed = rawParams
|
|
17
|
+
.split(";")
|
|
18
|
+
.map((token) => Number.parseInt(token, 10))
|
|
19
|
+
.filter((value) => Number.isFinite(value));
|
|
20
|
+
|
|
21
|
+
return parsed.length > 0 ? parsed : [];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function isFiniteSgrParam(value: number | undefined): value is number {
|
|
25
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function readSgrColorSequence(params: number[], index: number): number[] | undefined {
|
|
29
|
+
const param = params[index];
|
|
30
|
+
if (param !== 38 && param !== 48) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const colorMode = params[index + 1];
|
|
35
|
+
if (colorMode === 5) {
|
|
36
|
+
const colorValue = params[index + 2];
|
|
37
|
+
return isFiniteSgrParam(colorValue) ? [param, colorMode, colorValue] : undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (colorMode === 2) {
|
|
41
|
+
const red = params[index + 2];
|
|
42
|
+
const green = params[index + 3];
|
|
43
|
+
const blue = params[index + 4];
|
|
44
|
+
return isFiniteSgrParam(red) && isFiniteSgrParam(green) && isFiniteSgrParam(blue)
|
|
45
|
+
? [param, colorMode, red, green, blue]
|
|
46
|
+
: undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function stripBackgroundSgrParams(params: readonly number[]): number[] {
|
|
53
|
+
const sanitized: number[] = [];
|
|
54
|
+
|
|
55
|
+
for (let index = 0; index < params.length; index++) {
|
|
56
|
+
const param = params[index] ?? 0;
|
|
57
|
+
|
|
58
|
+
if (param === 0) {
|
|
59
|
+
sanitized.push(...expandSgrReset(param)!);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (param === 49 || (param >= 40 && param <= 47) || (param >= 100 && param <= 107)) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (param === 38 || param === 48) {
|
|
68
|
+
const sequence = readSgrColorSequence(params as number[], index);
|
|
69
|
+
if (sequence) {
|
|
70
|
+
if (param === 38) {
|
|
71
|
+
sanitized.push(...sequence);
|
|
72
|
+
}
|
|
73
|
+
index += sequence.length - 1;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const advance = params[index + 1] === 5 ? 2 : params[index + 1] === 2 ? 4 : 0;
|
|
77
|
+
if (advance > 0) {
|
|
78
|
+
index += advance;
|
|
79
|
+
if (param === 38) {
|
|
80
|
+
sanitized.push(param);
|
|
81
|
+
}
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
sanitized.push(param);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return sanitized;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function filterSgrSequences(text: string, filter: (params: number[]) => number[]): string {
|
|
93
|
+
if (!text || !text.includes("\x1b[")) {
|
|
94
|
+
return text;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return text.replace(ANSI_SGR_PATTERN, (_sequence, rawParams: string) => {
|
|
98
|
+
const parsed = toSgrParams(rawParams);
|
|
99
|
+
if (parsed.length === 0) {
|
|
100
|
+
return "";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const sanitized = filter(parsed);
|
|
104
|
+
if (sanitized.length === 0) {
|
|
105
|
+
return "";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return `\x1b[${sanitized.join(";")}m`;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function isValidColonForeground(segment: string): boolean {
|
|
113
|
+
const params = segment.split(":");
|
|
114
|
+
const isByte = (value: string | undefined) =>
|
|
115
|
+
value !== undefined && /^\d+$/.test(value) && Number(value) <= 255;
|
|
116
|
+
if (params[0] !== "38") return false;
|
|
117
|
+
if (params[1] === "5") return params.length === 3 && isByte(params[2]);
|
|
118
|
+
return (
|
|
119
|
+
params[1] === "2" &&
|
|
120
|
+
params.length === 6 &&
|
|
121
|
+
(params[2] === "" || /^\d+$/.test(params[2] ?? "")) &&
|
|
122
|
+
params.slice(3).every(isByte)
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function sanitizeColonSgr(rawParams: string): string {
|
|
127
|
+
const segments = rawParams.split(";");
|
|
128
|
+
const sanitized: string[] = [];
|
|
129
|
+
for (let index = 0; index < segments.length; index++) {
|
|
130
|
+
const segment = segments[index] ?? "";
|
|
131
|
+
if (segment.includes(":")) {
|
|
132
|
+
const code = Number.parseInt(segment, 10);
|
|
133
|
+
if (code !== 48 && (code !== 38 || isValidColonForeground(segment))) {
|
|
134
|
+
sanitized.push(segment);
|
|
135
|
+
}
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const param = Number.parseInt(segment, 10);
|
|
139
|
+
if (!Number.isFinite(param)) continue;
|
|
140
|
+
if (param === 0) {
|
|
141
|
+
sanitized.push(...STYLE_RESET_PARAMS.map(String));
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (param === 49 || (param >= 40 && param <= 47) || (param >= 100 && param <= 107)) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (param === 38 || param === 48) {
|
|
148
|
+
const mode = Number.parseInt(segments[index + 1] ?? "", 10);
|
|
149
|
+
const advance = mode === 5 ? 2 : mode === 2 ? 4 : 0;
|
|
150
|
+
if (advance > 0) {
|
|
151
|
+
if (param === 38) sanitized.push(...segments.slice(index, index + advance + 1));
|
|
152
|
+
index += advance;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
sanitized.push(segment);
|
|
157
|
+
}
|
|
158
|
+
return sanitized.length > 0 ? `\x1b[${sanitized.join(";")}m` : "";
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function sanitizeAnsiForThemedOutput(text: string): string {
|
|
162
|
+
if (!text || !text.includes("\x1b[")) return text;
|
|
163
|
+
return text.replace(ANSI_SGR_WITH_COLON_PATTERN, (sequence, rawParams: string) =>
|
|
164
|
+
rawParams.includes(":")
|
|
165
|
+
? sanitizeColonSgr(rawParams)
|
|
166
|
+
: filterSgrSequences(sequence, stripBackgroundSgrParams),
|
|
167
|
+
);
|
|
168
|
+
}
|