@oh-my-pi/pi-coding-agent 15.10.12 → 15.11.1
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 +90 -4
- package/dist/cli.js +869 -825
- package/dist/types/async/index.d.ts +0 -1
- package/dist/types/capability/mcp.d.ts +1 -0
- package/dist/types/cli/gallery-fixtures/types.d.ts +5 -0
- package/dist/types/config/keybindings.d.ts +6 -1
- package/dist/types/config/settings-schema.d.ts +66 -34
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
- package/dist/types/extensibility/shared-events.d.ts +2 -2
- package/dist/types/internal-urls/history-protocol.d.ts +14 -0
- package/dist/types/internal-urls/index.d.ts +1 -0
- package/dist/types/internal-urls/types.d.ts +1 -1
- package/dist/types/irc/bus.d.ts +66 -0
- package/dist/types/mcp/oauth-discovery.d.ts +2 -0
- package/dist/types/mcp/oauth-flow.d.ts +6 -1
- package/dist/types/mcp/transports/stdio.d.ts +1 -0
- package/dist/types/mcp/types.d.ts +2 -0
- package/dist/types/modes/components/agent-hub.d.ts +30 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -0
- package/dist/types/modes/components/compaction-summary-message.d.ts +10 -4
- package/dist/types/modes/components/custom-editor.d.ts +2 -0
- package/dist/types/modes/components/mcp-add-wizard.d.ts +2 -1
- package/dist/types/modes/components/settings-selector.d.ts +1 -0
- package/dist/types/modes/components/status-line/types.d.ts +3 -0
- package/dist/types/modes/components/tool-execution.d.ts +8 -0
- package/dist/types/modes/components/transcript-container.d.ts +3 -2
- package/dist/types/modes/components/ttsr-notification.d.ts +5 -1
- package/dist/types/modes/components/welcome.d.ts +3 -9
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
- package/dist/types/modes/controllers/tool-args-reveal.d.ts +43 -0
- package/dist/types/modes/interactive-mode.d.ts +3 -2
- package/dist/types/modes/theme/theme.d.ts +3 -1
- package/dist/types/modes/types.d.ts +3 -2
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -1
- package/dist/types/registry/agent-lifecycle.d.ts +51 -0
- package/dist/types/registry/agent-registry.d.ts +16 -5
- package/dist/types/session/agent-session.d.ts +35 -30
- package/dist/types/session/messages.d.ts +2 -4
- package/dist/types/session/session-history-format.d.ts +12 -0
- package/dist/types/session/session-manager.d.ts +21 -3
- package/dist/types/session/streaming-output.d.ts +23 -0
- package/dist/types/task/executor.d.ts +11 -2
- package/dist/types/task/index.d.ts +11 -4
- package/dist/types/task/output-manager.d.ts +0 -7
- package/dist/types/task/repair-args.d.ts +8 -7
- package/dist/types/task/types.d.ts +55 -51
- package/dist/types/tools/browser/tab-worker.d.ts +3 -1
- package/dist/types/tools/find.d.ts +0 -11
- package/dist/types/tools/grouped-file-output.d.ts +0 -49
- package/dist/types/tools/index.d.ts +1 -3
- package/dist/types/tools/irc.d.ts +76 -38
- package/dist/types/tools/job.d.ts +7 -1
- package/dist/types/tools/render-utils.d.ts +22 -0
- package/examples/extensions/with-deps/package.json +1 -0
- package/package.json +11 -10
- package/scripts/bundle-dist.ts +28 -19
- package/src/async/index.ts +0 -1
- package/src/capability/mcp.ts +1 -0
- package/src/cli/gallery-cli.ts +6 -5
- package/src/cli/gallery-fixtures/agentic.ts +230 -115
- package/src/cli/gallery-fixtures/types.ts +5 -0
- package/src/cli.ts +20 -6
- package/src/commit/agentic/tools/analyze-file.ts +38 -19
- package/src/config/keybindings.ts +6 -1
- package/src/config/mcp-schema.json +4 -0
- package/src/config/settings-schema.ts +68 -41
- package/src/config/settings.ts +7 -0
- package/src/edit/renderer.ts +96 -46
- package/src/eval/__tests__/agent-bridge.test.ts +5 -3
- package/src/eval/agent-bridge.ts +3 -16
- package/src/eval/js/shared/prelude.txt +1 -1
- package/src/eval/py/prelude.py +5 -6
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +44 -14
- package/src/extensibility/custom-tools/types.ts +2 -2
- package/src/extensibility/shared-events.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +9 -9
- package/src/internal-urls/history-protocol.ts +113 -0
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/router.ts +3 -1
- package/src/internal-urls/types.ts +1 -1
- package/src/irc/bus.ts +292 -0
- package/src/main.ts +8 -60
- package/src/mcp/manager.ts +3 -0
- package/src/mcp/oauth-discovery.ts +27 -2
- package/src/mcp/oauth-flow.ts +47 -1
- package/src/mcp/transports/stdio.ts +3 -0
- package/src/mcp/types.ts +2 -0
- package/src/modes/components/{session-observer-overlay.ts → agent-hub.ts} +586 -367
- package/src/modes/components/assistant-message.ts +15 -0
- package/src/modes/components/btw-panel.ts +5 -1
- package/src/modes/components/compaction-summary-message.ts +68 -32
- package/src/modes/components/custom-editor.ts +10 -0
- package/src/modes/components/mcp-add-wizard.ts +13 -0
- package/src/modes/components/settings-selector.ts +2 -0
- package/src/modes/components/status-line/component.ts +22 -12
- package/src/modes/components/status-line/types.ts +3 -0
- package/src/modes/components/tool-execution.ts +31 -1
- package/src/modes/components/transcript-container.ts +99 -18
- package/src/modes/components/tree-selector.ts +6 -1
- package/src/modes/components/ttsr-notification.ts +72 -30
- package/src/modes/components/welcome.ts +9 -33
- package/src/modes/controllers/event-controller.ts +93 -4
- package/src/modes/controllers/extension-ui-controller.ts +8 -8
- package/src/modes/controllers/input-controller.ts +18 -2
- package/src/modes/controllers/mcp-command-controller.ts +34 -2
- package/src/modes/controllers/selector-controller.ts +25 -17
- package/src/modes/controllers/tool-args-reveal.ts +174 -0
- package/src/modes/interactive-mode.ts +17 -15
- package/src/modes/theme/theme.ts +24 -5
- package/src/modes/types.ts +3 -5
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +51 -49
- package/src/prompts/system/irc-incoming.md +3 -4
- package/src/prompts/system/orchestrate-notice.md +2 -2
- package/src/prompts/system/subagent-system-prompt.md +0 -5
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/prompts/system/workflow-notice.md +2 -2
- package/src/prompts/tools/eval.md +3 -3
- package/src/prompts/tools/irc.md +29 -19
- package/src/prompts/tools/read.md +2 -2
- package/src/prompts/tools/task-summary.md +5 -16
- package/src/prompts/tools/task.md +43 -29
- package/src/registry/agent-lifecycle.ts +218 -0
- package/src/registry/agent-registry.ts +16 -5
- package/src/sdk.ts +29 -9
- package/src/session/agent-session.ts +268 -241
- package/src/session/messages.ts +11 -78
- package/src/session/session-history-format.ts +246 -0
- package/src/session/session-manager.ts +59 -5
- package/src/session/streaming-output.ts +60 -0
- package/src/task/executor.ts +855 -466
- package/src/task/index.ts +723 -794
- package/src/task/output-manager.ts +0 -11
- package/src/task/render.ts +142 -66
- package/src/task/repair-args.ts +21 -9
- package/src/task/types.ts +73 -66
- package/src/tools/ask.ts +4 -2
- package/src/tools/bash.ts +15 -5
- package/src/tools/browser/tab-worker.ts +26 -7
- package/src/tools/browser.ts +28 -1
- package/src/tools/find.ts +2 -27
- package/src/tools/grouped-file-output.ts +1 -118
- package/src/tools/index.ts +4 -12
- package/src/tools/irc.ts +596 -171
- package/src/tools/job.ts +41 -7
- package/src/tools/read.ts +57 -1
- package/src/tools/render-utils.ts +56 -0
- package/src/tools/renderers.ts +2 -0
- package/src/tools/resolve.ts +4 -1
- package/src/tools/write.ts +65 -47
- package/src/web/search/providers/anthropic.ts +29 -4
- package/dist/types/async/support.d.ts +0 -2
- package/dist/types/modes/components/session-observer-overlay.d.ts +0 -11
- package/dist/types/task/simple-mode.d.ts +0 -8
- package/src/async/support.ts +0 -5
- package/src/task/simple-mode.ts +0 -27
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type Component,
|
|
3
|
+
Container,
|
|
4
|
+
type NativeScrollbackCommittedRows,
|
|
5
|
+
type NativeScrollbackLiveRegion,
|
|
6
|
+
type RenderStablePrefix,
|
|
7
|
+
} from "@oh-my-pi/pi-tui";
|
|
2
8
|
|
|
3
9
|
const kSnapshot = Symbol("transcript.liveDiffSnapshot");
|
|
4
10
|
|
|
5
11
|
/**
|
|
6
|
-
* Per-block
|
|
7
|
-
* derived append-only state.
|
|
8
|
-
*
|
|
9
|
-
*
|
|
12
|
+
* Per-block render cache: the block's previous stripped contribution plus the
|
|
13
|
+
* derived append-only state. Still-live blocks use it as input to
|
|
14
|
+
* {@link deriveLiveCommitState}; finalized blocks wholly inside already
|
|
15
|
+
* committed native scrollback can replay it without calling render().
|
|
10
16
|
*/
|
|
11
17
|
interface LiveDiffSnapshot {
|
|
12
18
|
width: number;
|
|
@@ -47,6 +53,16 @@ interface SnapshotCarrier {
|
|
|
47
53
|
*/
|
|
48
54
|
interface FinalizableBlock {
|
|
49
55
|
isTranscriptBlockFinalized?(): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Monotonic content version for blocks that can still mutate *after*
|
|
58
|
+
* reporting finalized (e.g. `AssistantMessageComponent`: the inline error
|
|
59
|
+
* restored at the next turn's `agent_start`, late tool-result images). The
|
|
60
|
+
* committed-scrollback render bypass only replays a block's previous rows
|
|
61
|
+
* when the version is unchanged; without this signal a post-finalize
|
|
62
|
+
* mutation would stay invisible until a global invalidation. Blocks that
|
|
63
|
+
* never mutate post-finalize simply omit the method.
|
|
64
|
+
*/
|
|
65
|
+
getTranscriptBlockVersion?(): number;
|
|
50
66
|
}
|
|
51
67
|
|
|
52
68
|
function isBlockFinalized(child: Component): boolean {
|
|
@@ -54,6 +70,11 @@ function isBlockFinalized(child: Component): boolean {
|
|
|
54
70
|
return fn ? fn.call(child) : true;
|
|
55
71
|
}
|
|
56
72
|
|
|
73
|
+
function getBlockVersion(child: Component): number | undefined {
|
|
74
|
+
const fn = (child as Component & FinalizableBlock).getTranscriptBlockVersion;
|
|
75
|
+
return fn ? fn.call(child) : undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
57
78
|
// A "plain blank" row is empty or whitespace-only with no ANSI bytes. It marks
|
|
58
79
|
// separation padding (a `Spacer`, or a no-background `paddingY` row) as opposed
|
|
59
80
|
// to a background-colored padding row, whose escape sequences contain `\S` and
|
|
@@ -87,11 +108,16 @@ interface BlockSegment {
|
|
|
87
108
|
rawRef: readonly string[];
|
|
88
109
|
contribution: readonly string[];
|
|
89
110
|
width: number;
|
|
111
|
+
generation: number;
|
|
90
112
|
/** Frame row of this block's first emitted row (the separator when present). */
|
|
91
113
|
startRow: number;
|
|
92
114
|
/** Rows emitted: separator + contribution (0 for empty contributions). */
|
|
93
115
|
rowCount: number;
|
|
94
116
|
sep: number;
|
|
117
|
+
/** Whether the block reported finalized when this segment was rendered. */
|
|
118
|
+
finalized: boolean;
|
|
119
|
+
/** Block version observed when this segment was rendered (see {@link FinalizableBlock}). */
|
|
120
|
+
version: number | undefined;
|
|
95
121
|
}
|
|
96
122
|
|
|
97
123
|
const EMPTY_SEGMENTS: BlockSegment[] = [];
|
|
@@ -369,7 +395,10 @@ function deriveLiveCommitState(
|
|
|
369
395
|
* through {@link RenderStablePrefix} so the engine can skip marker scanning,
|
|
370
396
|
* line preparation, and the committed-prefix audit for those rows.
|
|
371
397
|
*/
|
|
372
|
-
export class TranscriptContainer
|
|
398
|
+
export class TranscriptContainer
|
|
399
|
+
extends Container
|
|
400
|
+
implements NativeScrollbackLiveRegion, NativeScrollbackCommittedRows, RenderStablePrefix
|
|
401
|
+
{
|
|
373
402
|
// Bumped to retire every block's diff snapshot at once (theme change /
|
|
374
403
|
// clear); a snapshot is only honored when its stored generation matches.
|
|
375
404
|
#generation = 0;
|
|
@@ -390,6 +419,10 @@ export class TranscriptContainer extends Container implements NativeScrollbackLi
|
|
|
390
419
|
#lines: string[] = [];
|
|
391
420
|
#segments: BlockSegment[] = EMPTY_SEGMENTS;
|
|
392
421
|
#renderWidth = -1;
|
|
422
|
+
// Local rows already committed to native scrollback by the previous frame.
|
|
423
|
+
// Finalized blocks wholly before this boundary are immutable on-screen history;
|
|
424
|
+
// their previous contribution can be replayed without calling render().
|
|
425
|
+
#committedRows = 0;
|
|
393
426
|
// Stable-prefix floor accumulated across renders since the last
|
|
394
427
|
// getRenderStablePrefixRows() read (see RenderStablePrefix: reading
|
|
395
428
|
// consumes the report and re-bases the baseline). Out-of-band renders
|
|
@@ -407,6 +440,10 @@ export class TranscriptContainer extends Container implements NativeScrollbackLi
|
|
|
407
440
|
super.clear();
|
|
408
441
|
}
|
|
409
442
|
|
|
443
|
+
setNativeScrollbackCommittedRows(rows: number): void {
|
|
444
|
+
this.#committedRows = Number.isFinite(rows) ? Math.max(0, Math.trunc(rows)) : 0;
|
|
445
|
+
}
|
|
446
|
+
|
|
410
447
|
getRenderStablePrefixRows(): number {
|
|
411
448
|
const value = Math.min(this.#stableRowsFloor, this.#lines.length);
|
|
412
449
|
this.#stableRowsFloor = this.#lines.length;
|
|
@@ -497,21 +534,43 @@ export class TranscriptContainer extends Container implements NativeScrollbackLi
|
|
|
497
534
|
|
|
498
535
|
// This child's contribution: its current render with plain-blank
|
|
499
536
|
// top/bottom edges stripped (the container owns inter-block gaps).
|
|
500
|
-
//
|
|
501
|
-
//
|
|
502
|
-
//
|
|
503
|
-
//
|
|
504
|
-
//
|
|
537
|
+
// Finalized blocks wholly inside committed native scrollback can reuse
|
|
538
|
+
// their previous contribution without calling render(): those rows are
|
|
539
|
+
// immutable terminal history for the current width/generation. Blocks
|
|
540
|
+
// outside committed history still render normally so late results,
|
|
541
|
+
// post-finalize re-layouts, and expand toggles remain visible.
|
|
505
542
|
const previousSnapshot = child[kSnapshot];
|
|
506
|
-
const raw = child.render(width);
|
|
507
543
|
const previous = previousSegments[i];
|
|
508
|
-
const
|
|
544
|
+
const finalized = isBlockFinalized(child);
|
|
545
|
+
const version = getBlockVersion(child);
|
|
546
|
+
const committedReusable =
|
|
509
547
|
previous !== undefined &&
|
|
510
548
|
previous.component === child &&
|
|
511
|
-
previous.
|
|
512
|
-
previous.
|
|
549
|
+
previous.width === width &&
|
|
550
|
+
previous.generation === this.#generation &&
|
|
551
|
+
previous.startRow === row &&
|
|
552
|
+
previous.startRow + previous.rowCount <= this.#committedRows &&
|
|
553
|
+
finalized &&
|
|
554
|
+
// Only replay bytes that were themselves produced by a finalized
|
|
555
|
+
// render: a block finalizing between frames may have changed content
|
|
556
|
+
// while its rows were already committed via the append-only live
|
|
557
|
+
// path, so the first post-transition frame must render. Defense in
|
|
558
|
+
// depth on the transcript side — the TUI commit policy should keep
|
|
559
|
+
// that window closed, but the safety must not live there alone.
|
|
560
|
+
previous.finalized &&
|
|
561
|
+
// Post-finalize mutations (inline error restore, late tool images)
|
|
562
|
+
// bump the block version; a mismatch forces a real render so the
|
|
563
|
+
// committed-prefix audit can observe and re-anchor the change.
|
|
564
|
+
previous.version === version;
|
|
565
|
+
const raw = committedReusable ? previous.rawRef : child.render(width);
|
|
566
|
+
const reusable =
|
|
567
|
+
committedReusable ||
|
|
568
|
+
(previous !== undefined &&
|
|
569
|
+
previous.component === child &&
|
|
570
|
+
previous.rawRef === raw &&
|
|
571
|
+
previous.width === width &&
|
|
572
|
+
previous.generation === this.#generation);
|
|
513
573
|
const contribution = reusable ? previous.contribution : stripPlainBlankEdges(raw);
|
|
514
|
-
const finalized = isBlockFinalized(child);
|
|
515
574
|
let liveCommitState: LiveCommitState | undefined;
|
|
516
575
|
if (i >= liveStartIndex && !finalized) {
|
|
517
576
|
liveCommitState = deriveLiveCommitState(previousSnapshot, contribution, width, this.#generation);
|
|
@@ -540,7 +599,18 @@ export class TranscriptContainer extends Container implements NativeScrollbackLi
|
|
|
540
599
|
lines.length = row;
|
|
541
600
|
}
|
|
542
601
|
if (chainStable) stableRows = row;
|
|
543
|
-
segments[i] = {
|
|
602
|
+
segments[i] = {
|
|
603
|
+
component: child,
|
|
604
|
+
rawRef: raw,
|
|
605
|
+
contribution,
|
|
606
|
+
width,
|
|
607
|
+
generation: this.#generation,
|
|
608
|
+
startRow: row,
|
|
609
|
+
rowCount: 0,
|
|
610
|
+
sep: 0,
|
|
611
|
+
finalized,
|
|
612
|
+
version,
|
|
613
|
+
};
|
|
544
614
|
continue;
|
|
545
615
|
}
|
|
546
616
|
|
|
@@ -584,7 +654,18 @@ export class TranscriptContainer extends Container implements NativeScrollbackLi
|
|
|
584
654
|
if (!(finalized && safeLength >= contribution.length)) commitSafeOpen = false;
|
|
585
655
|
}
|
|
586
656
|
|
|
587
|
-
segments[i] = {
|
|
657
|
+
segments[i] = {
|
|
658
|
+
component: child,
|
|
659
|
+
rawRef: raw,
|
|
660
|
+
contribution,
|
|
661
|
+
width,
|
|
662
|
+
generation: this.#generation,
|
|
663
|
+
startRow: row,
|
|
664
|
+
rowCount,
|
|
665
|
+
sep,
|
|
666
|
+
finalized,
|
|
667
|
+
version,
|
|
668
|
+
};
|
|
588
669
|
row += rowCount;
|
|
589
670
|
}
|
|
590
671
|
// Trailing shrink: blocks removed from the tail leave stale rows behind
|
|
@@ -518,6 +518,7 @@ class TreeList implements Component {
|
|
|
518
518
|
const renderedIndent = Math.min(displayIndent, maxIndentLevels);
|
|
519
519
|
const scrollOffset = displayIndent - renderedIndent;
|
|
520
520
|
const connectorPositionDisplay = hasConnector ? renderedIndent - 1 : -1;
|
|
521
|
+
const chainGutter = !hasConnector ? flatNode.gutters[flatNode.gutters.length - 1] : undefined;
|
|
521
522
|
|
|
522
523
|
// Build prefix char by char, placing gutters and connector at their positions
|
|
523
524
|
const totalChars = renderedIndent * 3;
|
|
@@ -530,8 +531,12 @@ class TreeList implements Component {
|
|
|
530
531
|
// Check if there's a gutter at this level (translated to original tree depth)
|
|
531
532
|
const gutter = flatNode.gutters.find(g => g.position === originalLevel);
|
|
532
533
|
if (gutter) {
|
|
534
|
+
// Chain rows (no connector of their own) extend only their
|
|
535
|
+
// nearest connector gutter so the flattened conversation flow
|
|
536
|
+
// stays anchored without reviving unrelated `└─` ancestors (#2298).
|
|
537
|
+
const showVertical = gutter.show || gutter === chainGutter;
|
|
533
538
|
if (posInLevel === 0) {
|
|
534
|
-
prefixChars.push(
|
|
539
|
+
prefixChars.push(showVertical ? theme.tree.vertical : " ");
|
|
535
540
|
} else {
|
|
536
541
|
prefixChars.push(" ");
|
|
537
542
|
}
|
|
@@ -2,16 +2,24 @@ import { Box, Container, Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
|
2
2
|
import type { Rule } from "../../capability/rule";
|
|
3
3
|
import { theme } from "../../modes/theme/theme";
|
|
4
4
|
|
|
5
|
+
/** Collapsed view shows at most this many rules before eliding the rest. */
|
|
6
|
+
const MAX_COLLAPSED_RULES = 4;
|
|
7
|
+
|
|
5
8
|
/**
|
|
6
9
|
* Component that renders a TTSR (Time Traveling Stream Rules) notification.
|
|
7
10
|
* Shows when a rule violation is detected and the stream is being rewound.
|
|
11
|
+
* One block can carry several rules: a single event may match multiple rules,
|
|
12
|
+
* and consecutive notifications merge into the previous block via
|
|
13
|
+
* {@link addRules} while it is still the live transcript tail.
|
|
8
14
|
*/
|
|
9
15
|
export class TtsrNotificationComponent extends Container {
|
|
10
16
|
#box: Box;
|
|
11
17
|
#expanded = false;
|
|
18
|
+
#rules: Rule[];
|
|
12
19
|
|
|
13
|
-
constructor(
|
|
20
|
+
constructor(rules: Rule[]) {
|
|
14
21
|
super();
|
|
22
|
+
this.#rules = [...rules];
|
|
15
23
|
|
|
16
24
|
this.addChild(new Spacer(1));
|
|
17
25
|
|
|
@@ -22,6 +30,17 @@ export class TtsrNotificationComponent extends Container {
|
|
|
22
30
|
this.#rebuild();
|
|
23
31
|
}
|
|
24
32
|
|
|
33
|
+
/** Merge additional rules into this block (deduped by rule name). */
|
|
34
|
+
addRules(rules: Rule[]): void {
|
|
35
|
+
let changed = false;
|
|
36
|
+
for (const rule of rules) {
|
|
37
|
+
if (this.#rules.some(existing => existing.name === rule.name)) continue;
|
|
38
|
+
this.#rules.push(rule);
|
|
39
|
+
changed = true;
|
|
40
|
+
}
|
|
41
|
+
if (changed) this.#rebuild();
|
|
42
|
+
}
|
|
43
|
+
|
|
25
44
|
setExpanded(expanded: boolean): void {
|
|
26
45
|
if (this.#expanded !== expanded) {
|
|
27
46
|
this.#expanded = expanded;
|
|
@@ -35,46 +54,69 @@ export class TtsrNotificationComponent extends Container {
|
|
|
35
54
|
|
|
36
55
|
#rebuild(): void {
|
|
37
56
|
this.#box.clear();
|
|
57
|
+
// fg colors conflict with inverse, so styling inside the block is limited
|
|
58
|
+
// to bold (names) and italic (descriptions).
|
|
59
|
+
if (this.#rules.length === 1) {
|
|
60
|
+
this.#rebuildSingle(this.#rules[0]!);
|
|
61
|
+
} else {
|
|
62
|
+
this.#rebuildMulti();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
38
65
|
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
const header = `${theme.icon.warning} Injecting ${label}: ${ruleNames}`;
|
|
66
|
+
#rebuildSingle(rule: Rule): void {
|
|
67
|
+
const header = `${theme.icon.warning} Injecting rule: ${theme.bold(rule.name)} ${theme.icon.rewind}`;
|
|
68
|
+
this.#box.addChild(new Text(header, 0, 0));
|
|
43
69
|
|
|
44
|
-
|
|
45
|
-
|
|
70
|
+
const desc = (rule.description || rule.content)?.trim();
|
|
71
|
+
if (!desc) return;
|
|
46
72
|
|
|
47
|
-
|
|
73
|
+
let displayText = desc;
|
|
74
|
+
let truncated = false;
|
|
75
|
+
if (!this.#expanded) {
|
|
76
|
+
const lines = desc.split("\n");
|
|
77
|
+
if (lines.length > 2) {
|
|
78
|
+
displayText = `${lines.slice(0, 2).join("\n")}…`;
|
|
79
|
+
truncated = true;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
48
82
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
83
|
+
this.#box.addChild(new Spacer(1));
|
|
84
|
+
this.#box.addChild(new Text(theme.italic(displayText), 0, 0));
|
|
85
|
+
if (truncated) {
|
|
86
|
+
this.#box.addChild(new Text(theme.italic(" (ctrl+o to expand)"), 0, 0));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#rebuildMulti(): void {
|
|
91
|
+
const header = `${theme.icon.warning} Injecting ${this.#rules.length} rules: ${theme.icon.rewind}`;
|
|
92
|
+
this.#box.addChild(new Text(header, 0, 0));
|
|
93
|
+
this.#box.addChild(new Spacer(1));
|
|
54
94
|
|
|
55
|
-
|
|
95
|
+
const visible = this.#expanded ? this.#rules : this.#rules.slice(0, MAX_COLLAPSED_RULES);
|
|
96
|
+
let elidedDetail = false;
|
|
97
|
+
for (const rule of visible) {
|
|
98
|
+
const desc = (rule.description || rule.content)?.trim();
|
|
99
|
+
let line = theme.bold(rule.name);
|
|
100
|
+
if (desc) {
|
|
101
|
+
let displayText = desc;
|
|
56
102
|
if (!this.#expanded) {
|
|
57
|
-
//
|
|
58
|
-
const
|
|
59
|
-
if (
|
|
60
|
-
displayText = `${
|
|
103
|
+
// One line per rule when collapsed; full description when expanded.
|
|
104
|
+
const newline = desc.indexOf("\n");
|
|
105
|
+
if (newline !== -1) {
|
|
106
|
+
displayText = `${desc.slice(0, newline).trimEnd()}…`;
|
|
107
|
+
elidedDetail = true;
|
|
61
108
|
}
|
|
62
109
|
}
|
|
63
|
-
|
|
64
|
-
// Use italic for subtle distinction (fg colors conflict with inverse)
|
|
65
|
-
this.#box.addChild(new Text(theme.italic(displayText), 0, 0));
|
|
110
|
+
line += `: ${theme.italic(displayText)}`;
|
|
66
111
|
}
|
|
112
|
+
this.#box.addChild(new Text(line, 0, 0));
|
|
67
113
|
}
|
|
68
114
|
|
|
69
|
-
|
|
70
|
-
if (
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
});
|
|
75
|
-
if (hasMoreContent) {
|
|
76
|
-
this.#box.addChild(new Text(theme.italic(" (ctrl+o to expand)"), 0, 0));
|
|
77
|
-
}
|
|
115
|
+
const hidden = this.#rules.length - visible.length;
|
|
116
|
+
if (hidden > 0) {
|
|
117
|
+
this.#box.addChild(new Text(theme.italic(`… +${hidden} more (ctrl+o to expand)`), 0, 0));
|
|
118
|
+
} else if (elidedDetail) {
|
|
119
|
+
this.#box.addChild(new Text(theme.italic(" (ctrl+o to expand)"), 0, 0));
|
|
78
120
|
}
|
|
79
121
|
}
|
|
80
122
|
}
|
|
@@ -18,14 +18,8 @@ const TIPS: readonly string[] = tipsText
|
|
|
18
18
|
.filter(line => line.length > 0);
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*/
|
|
24
|
-
const PROCESS_TIP: string | undefined = TIPS.length > 0 ? TIPS[Math.floor(Math.random() * TIPS.length)] : undefined;
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Fixed number of session rows in the welcome box so its height doesn't shift
|
|
28
|
-
* between the pre-TUI splash (loading placeholder) and the loaded state.
|
|
21
|
+
* Fixed number of session rows in the welcome box so its height stays stable
|
|
22
|
+
* across recent-session updates.
|
|
29
23
|
*/
|
|
30
24
|
export const WELCOME_SESSION_SLOTS = 4;
|
|
31
25
|
|
|
@@ -76,10 +70,8 @@ export interface LspServerInfo {
|
|
|
76
70
|
export class WelcomeComponent implements Component {
|
|
77
71
|
#animStart: number | null = null;
|
|
78
72
|
#animTimer: ReturnType<typeof setInterval> | null = null;
|
|
79
|
-
/**
|
|
80
|
-
#
|
|
81
|
-
/** Per-process tip so re-renders (intro, LSP updates, splash swap) don't shuffle it. */
|
|
82
|
-
readonly #tip: string | undefined = PROCESS_TIP;
|
|
73
|
+
/** Tip chosen once per instance so re-renders (intro, LSP updates) don't shuffle it. */
|
|
74
|
+
readonly #tip: string | undefined = TIPS.length > 0 ? TIPS[Math.floor(Math.random() * TIPS.length)] : undefined;
|
|
83
75
|
// Render cache: the welcome box is the first transcript-area component, so
|
|
84
76
|
// returning a stable array reference keeps the whole frame prefix stable.
|
|
85
77
|
// Bypassed while the intro animation runs (every frame differs).
|
|
@@ -90,7 +82,7 @@ export class WelcomeComponent implements Component {
|
|
|
90
82
|
private readonly version: string,
|
|
91
83
|
private modelName: string,
|
|
92
84
|
private providerName: string,
|
|
93
|
-
private recentSessions: RecentSession[]
|
|
85
|
+
private recentSessions: RecentSession[] = [],
|
|
94
86
|
private lspServers: LspServerInfo[] = [],
|
|
95
87
|
) {}
|
|
96
88
|
|
|
@@ -99,16 +91,6 @@ export class WelcomeComponent implements Component {
|
|
|
99
91
|
this.#cachedLines = undefined;
|
|
100
92
|
}
|
|
101
93
|
|
|
102
|
-
/**
|
|
103
|
-
* Freeze the logo on the intro animation's first frame. The pre-TUI startup
|
|
104
|
-
* splash uses this so the in-TUI intro — which starts at that exact frame —
|
|
105
|
-
* picks up seamlessly from the splash's static box.
|
|
106
|
-
*/
|
|
107
|
-
holdIntroFirstFrame(): void {
|
|
108
|
-
this.#holdIntroFirstFrame = true;
|
|
109
|
-
this.invalidate();
|
|
110
|
-
}
|
|
111
|
-
|
|
112
94
|
/**
|
|
113
95
|
* Play a one-shot intro that sweeps the gradient through every phase
|
|
114
96
|
* before settling on the resting frame. Safe to call multiple times —
|
|
@@ -116,7 +98,6 @@ export class WelcomeComponent implements Component {
|
|
|
116
98
|
*/
|
|
117
99
|
playIntro(requestRender: () => void): void {
|
|
118
100
|
this.#stopAnimation();
|
|
119
|
-
this.#holdIntroFirstFrame = false;
|
|
120
101
|
this.#animStart = performance.now();
|
|
121
102
|
requestRender();
|
|
122
103
|
this.#animTimer = setInterval(() => {
|
|
@@ -217,9 +198,7 @@ export class WelcomeComponent implements Component {
|
|
|
217
198
|
|
|
218
199
|
// Recent sessions content
|
|
219
200
|
const sessionLines: string[] = [];
|
|
220
|
-
if (this.recentSessions ===
|
|
221
|
-
sessionLines.push(` ${theme.fg("dim", "Loading…")}`);
|
|
222
|
-
} else if (this.recentSessions.length === 0) {
|
|
201
|
+
if (this.recentSessions.length === 0) {
|
|
223
202
|
sessionLines.push(` ${theme.fg("dim", "No recent sessions")}`);
|
|
224
203
|
} else {
|
|
225
204
|
// Reserve width for the bullet prefix (" • ") and the trailing " (timeAgo)"
|
|
@@ -238,7 +217,7 @@ export class WelcomeComponent implements Component {
|
|
|
238
217
|
);
|
|
239
218
|
}
|
|
240
219
|
}
|
|
241
|
-
// Pad to the fixed slot count so the box doesn't
|
|
220
|
+
// Pad to the fixed slot count so the box height doesn't depend on session count.
|
|
242
221
|
while (sessionLines.length < WELCOME_SESSION_SLOTS) {
|
|
243
222
|
sessionLines.push("");
|
|
244
223
|
}
|
|
@@ -377,9 +356,9 @@ export class WelcomeComponent implements Component {
|
|
|
377
356
|
return str + padding(width - visLen);
|
|
378
357
|
}
|
|
379
358
|
|
|
380
|
-
/** Pick the logo frame for the current intro phase, or the resting
|
|
359
|
+
/** Pick the logo frame for the current intro phase, or the resting frame. */
|
|
381
360
|
#currentLogoFrame(): readonly string[] {
|
|
382
|
-
if (this.#animStart == null) return
|
|
361
|
+
if (this.#animStart == null) return REST_FRAME;
|
|
383
362
|
const elapsed = performance.now() - this.#animStart;
|
|
384
363
|
if (elapsed >= INTRO_MS) return REST_FRAME;
|
|
385
364
|
return introLogoFrame(elapsed / INTRO_MS);
|
|
@@ -510,8 +489,5 @@ function introLogoFrame(progress: number): string[] {
|
|
|
510
489
|
return gradientLogo(PI_LOGO, phase, { strength: shineStrength, pos: shinePos });
|
|
511
490
|
}
|
|
512
491
|
|
|
513
|
-
/** First intro frame, cached for splash-held renders (resize re-renders reuse it). */
|
|
514
|
-
const INTRO_FIRST_FRAME = introLogoFrame(0);
|
|
515
|
-
|
|
516
492
|
/** Resting gradient frame, cached for re-renders outside of the intro. */
|
|
517
493
|
const REST_FRAME = gradientLogo(PI_LOGO, 0);
|