@linxiraos/pi-tui 1.1.27 → 1.2.0
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 +47 -463
- package/README.md +7 -5
- package/dist/types/app-keybindings.d.ts +1 -1
- package/dist/types/apps/cleanse-board.d.ts +1 -1
- package/dist/types/apps/debug/log-viewer.d.ts +4 -0
- package/dist/types/apps/debug/raw-sse-buffer.d.ts +2 -0
- package/dist/types/apps/git/git-tui.d.ts +2 -2
- package/dist/types/apps/git/help.d.ts +3 -1
- package/dist/types/apps/if-bench-board.d.ts +2 -2
- package/dist/types/apps/setup-model-picker.d.ts +1 -1
- package/dist/types/autocomplete.d.ts +5 -2
- package/dist/types/charts/chart-plan.d.ts +674 -0
- package/dist/types/charts/chart-svg.d.ts +59 -0
- package/dist/types/charts/table-data.d.ts +175 -0
- package/dist/types/chat/assistant-message.d.ts +5 -0
- package/dist/types/chat/chat-transcript-builder.d.ts +2 -0
- package/dist/types/chat/compaction-summary-message.d.ts +1 -0
- package/dist/types/chat/execution-shared.d.ts +6 -5
- package/dist/types/chat/fence-figure.d.ts +42 -0
- package/dist/types/chat/figure-markdown.d.ts +42 -0
- package/dist/types/chat/messages.d.ts +1 -1
- package/dist/types/chat/svg-figure.d.ts +33 -0
- package/dist/types/chat/svg-source.d.ts +49 -0
- package/dist/types/chat/table-chart.d.ts +89 -0
- package/dist/types/chat/tool-execution.d.ts +2 -0
- package/dist/types/chat/user-message.d.ts +15 -2
- package/dist/types/chrome/shared.d.ts +9 -0
- package/dist/types/chrome/transcript-container.d.ts +6 -0
- package/dist/types/components/box.d.ts +1 -0
- package/dist/types/components/disclosure.d.ts +1 -0
- package/dist/types/components/editor.d.ts +15 -4
- package/dist/types/components/form.d.ts +1 -0
- package/dist/types/components/image.d.ts +12 -44
- package/dist/types/components/key-value-list.d.ts +1 -0
- package/dist/types/components/layout/row.d.ts +1 -0
- package/dist/types/components/layout/stack.d.ts +1 -0
- package/dist/types/components/markdown.d.ts +36 -3
- package/dist/types/components/scroll-view.d.ts +1 -0
- package/dist/types/components/section.d.ts +1 -0
- package/dist/types/components/table.d.ts +1 -0
- package/dist/types/components/text.d.ts +1 -0
- package/dist/types/components/tree-view.d.ts +7 -0
- package/dist/types/components/truncated-text.d.ts +1 -0
- package/dist/types/fuzzy.d.ts +29 -1
- package/dist/types/key-hint-format.d.ts +1 -1
- package/dist/types/keybinding-matchers.d.ts +3 -0
- package/dist/types/keybindings.d.ts +1 -1
- package/dist/types/kitty-graphics.d.ts +2 -2
- package/dist/types/loop-watchdog.d.ts +8 -7
- package/dist/types/multiplexers/cmux.d.ts +16 -0
- package/dist/types/multiplexers/herdr.d.ts +26 -0
- package/dist/types/multiplexers/notify.d.ts +13 -0
- package/dist/types/multiplexers/orca.d.ts +12 -0
- package/dist/types/multiplexers/screen.d.ts +9 -0
- package/dist/types/multiplexers/tmux.d.ts +29 -0
- package/dist/types/multiplexers/types.d.ts +100 -0
- package/dist/types/multiplexers/wmux.d.ts +12 -0
- package/dist/types/multiplexers/zellij.d.ts +12 -0
- package/dist/types/native/backend.d.ts +3 -2
- package/dist/types/native/blobs.d.ts +7 -3
- package/dist/types/native/encode.d.ts +3 -4
- package/dist/types/native/overlay.d.ts +5 -0
- package/dist/types/overlays/agent-hub-projection.d.ts +6 -5
- package/dist/types/overlays/agent-hub-types.d.ts +9 -0
- package/dist/types/overlays/agent-hub.d.ts +2 -0
- package/dist/types/overlays/agent-transcript-viewer.d.ts +11 -2
- package/dist/types/overlays/agents-hub.d.ts +9 -0
- package/dist/types/overlays/annotation-overlay.d.ts +19 -2
- package/dist/types/overlays/annotation-types.d.ts +2 -0
- package/dist/types/overlays/ask-dialog.d.ts +5 -2
- package/dist/types/overlays/history-search.d.ts +10 -0
- package/dist/types/overlays/hook-selector.d.ts +9 -4
- package/dist/types/overlays/jobs-panel.d.ts +1 -7
- package/dist/types/overlays/model-browser.d.ts +54 -7
- package/dist/types/overlays/model-hub.d.ts +23 -0
- package/dist/types/overlays/oauth-selector.d.ts +0 -1
- package/dist/types/overlays/plan-review-overlay.d.ts +3 -2
- package/dist/types/overlays/session-observer-registry.d.ts +2 -0
- package/dist/types/overlays/session-selector.d.ts +6 -2
- package/dist/types/overlays/settings-defs.d.ts +1 -0
- package/dist/types/overlays/usage-dashboard.d.ts +8 -4
- package/dist/types/overlays/usage-display.d.ts +3 -1
- package/dist/types/prompt/composer.d.ts +1 -1
- package/dist/types/prompt/custom-editor.d.ts +11 -8
- package/dist/types/prompt/image-source.d.ts +2 -1
- package/dist/types/prompt/interactive-context-helpers.d.ts +4 -1
- package/dist/types/prompt/model-mention-autocomplete.d.ts +1 -1
- package/dist/types/prompt/welcome.d.ts +6 -1
- package/dist/types/render/composite.d.ts +2 -0
- package/dist/types/render/output-pane.d.ts +1 -0
- package/dist/types/render/render-utils.d.ts +1 -0
- package/dist/types/render/tool-card.d.ts +6 -0
- package/dist/types/render/width-aware-text.d.ts +2 -0
- package/dist/types/setup/scenes/types.d.ts +1 -0
- package/dist/types/status-line/component.d.ts +3 -0
- package/dist/types/status-line/host.d.ts +4 -1
- package/dist/types/status-line/segments.d.ts +2 -0
- package/dist/types/status-line/types.d.ts +14 -3
- package/dist/types/terminal-capabilities.d.ts +27 -35
- package/dist/types/terminal-multiplexer.d.ts +127 -14
- package/dist/types/terminal.d.ts +22 -6
- package/dist/types/theme/active-symbols.d.ts +1 -1
- package/dist/types/theme/schema.d.ts +11 -2
- package/dist/types/theme/symbols.d.ts +1 -1
- package/dist/types/theme/theme-class.d.ts +7 -0
- package/dist/types/theme/theme.d.ts +1 -1
- package/dist/types/theme/tui-adapters.d.ts +3 -0
- package/dist/types/tmux.d.ts +11 -4
- package/dist/types/tools/daemon.d.ts +1 -1
- package/dist/types/tools/eval.d.ts +22 -3
- package/dist/types/tools/glob.d.ts +4 -0
- package/dist/types/tools/native-view.d.ts +47 -0
- package/dist/types/tools/output-meta.d.ts +11 -1
- package/dist/types/tools/renderer.d.ts +37 -2
- package/dist/types/tools/streaming-output.d.ts +20 -1
- package/dist/types/tools/task.d.ts +0 -4
- package/dist/types/tools/write.d.ts +9 -2
- package/dist/types/tools/yield.d.ts +22 -0
- package/dist/types/tui.d.ts +31 -0
- package/dist/types/utils.d.ts +6 -0
- package/dist/types/vim.d.ts +19 -5
- package/package.json +13 -9
- package/src/app-keybindings.d.ts +1 -1
- package/src/app-keybindings.ts +1 -1
- package/src/apps/autoresearch-dashboard.ts +7 -3
- package/src/apps/cleanse-board.ts +2 -2
- package/src/apps/cleanse-picker.ts +1 -1
- package/src/apps/debug/log-viewer.ts +188 -72
- package/src/apps/debug/raw-sse-buffer.ts +8 -0
- package/src/apps/debug/raw-sse.ts +39 -5
- package/src/apps/git/diff-pane.ts +26 -5
- package/src/apps/git/git-tui.ts +2 -2
- package/src/apps/git/help.ts +29 -20
- package/src/apps/if-bench-board.ts +2 -2
- package/src/apps/ps-top.ts +2 -2
- package/src/apps/setup-model-picker.ts +1 -1
- package/src/autocomplete.d.ts +5 -2
- package/src/autocomplete.ts +67 -43
- package/src/charts/chart-plan.d.ts +674 -0
- package/src/charts/chart-plan.ts +5081 -0
- package/src/charts/chart-svg.d.ts +59 -0
- package/src/charts/chart-svg.ts +4657 -0
- package/src/charts/table-data.d.ts +175 -0
- package/src/charts/table-data.ts +1478 -0
- package/src/chat/advisor-message.ts +11 -0
- package/src/chat/assistant-message.d.ts +5 -0
- package/src/chat/assistant-message.ts +219 -50
- package/src/chat/chat-transcript-builder.d.ts +2 -0
- package/src/chat/chat-transcript-builder.ts +5 -3
- package/src/chat/compaction-summary-message.d.ts +1 -0
- package/src/chat/compaction-summary-message.ts +8 -0
- package/src/chat/execution-shared.d.ts +6 -5
- package/src/chat/execution-shared.ts +42 -25
- package/src/chat/fence-figure.d.ts +42 -0
- package/src/chat/fence-figure.ts +80 -0
- package/src/chat/figure-markdown.d.ts +42 -0
- package/src/chat/figure-markdown.ts +174 -0
- package/src/chat/messages.d.ts +1 -1
- package/src/chat/messages.ts +1 -1
- package/src/chat/read-tool-group.ts +6 -4
- package/src/chat/skill-message.ts +6 -0
- package/src/chat/svg-figure.d.ts +33 -0
- package/src/chat/svg-figure.ts +399 -0
- package/src/chat/svg-source.d.ts +49 -0
- package/src/chat/svg-source.ts +188 -0
- package/src/chat/table-chart.d.ts +89 -0
- package/src/chat/table-chart.ts +294 -0
- package/src/chat/tool-execution.d.ts +2 -0
- package/src/chat/tool-execution.ts +80 -6
- package/src/chat/user-message.d.ts +15 -2
- package/src/chat/user-message.ts +55 -3
- package/src/chrome/shared.ts +53 -0
- package/src/chrome/transcript-container.d.ts +6 -0
- package/src/chrome/transcript-container.ts +93 -13
- package/src/components/box.d.ts +1 -0
- package/src/components/box.ts +5 -0
- package/src/components/disclosure.d.ts +1 -0
- package/src/components/disclosure.ts +8 -0
- package/src/components/editor.d.ts +15 -4
- package/src/components/editor.ts +294 -46
- package/src/components/form.d.ts +1 -0
- package/src/components/form.ts +8 -0
- package/src/components/image.d.ts +12 -44
- package/src/components/image.ts +47 -151
- package/src/components/key-value-list.d.ts +1 -0
- package/src/components/key-value-list.ts +4 -0
- package/src/components/layout/row.d.ts +1 -0
- package/src/components/layout/row.ts +7 -0
- package/src/components/layout/stack.d.ts +1 -0
- package/src/components/layout/stack.ts +7 -0
- package/src/components/markdown.d.ts +36 -3
- package/src/components/markdown.ts +433 -318
- package/src/components/scroll-view.d.ts +1 -0
- package/src/components/scroll-view.ts +5 -0
- package/src/components/section.d.ts +1 -0
- package/src/components/section.ts +5 -0
- package/src/components/settings-list.ts +2 -0
- package/src/components/table.d.ts +1 -0
- package/src/components/table.ts +4 -0
- package/src/components/text.d.ts +1 -0
- package/src/components/text.ts +7 -0
- package/src/components/tree-view.d.ts +7 -0
- package/src/components/tree-view.ts +38 -8
- package/src/components/truncated-text.d.ts +1 -0
- package/src/components/truncated-text.ts +5 -0
- package/src/desktop-notify.ts +1 -1
- package/src/fuzzy.d.ts +29 -1
- package/src/fuzzy.ts +115 -30
- package/src/key-hint-format.d.ts +1 -1
- package/src/key-hint-format.ts +1 -1
- package/src/keybinding-matchers.d.ts +3 -0
- package/src/keybinding-matchers.ts +6 -2
- package/src/keybindings.d.ts +1 -1
- package/src/keybindings.ts +1 -1
- package/src/kitty-graphics.d.ts +2 -2
- package/src/kitty-graphics.ts +4 -4
- package/src/loop-watchdog.d.ts +8 -7
- package/src/loop-watchdog.ts +18 -13
- package/src/multiplexers/cmux.d.ts +16 -0
- package/src/multiplexers/cmux.ts +29 -0
- package/src/multiplexers/herdr.d.ts +26 -0
- package/src/multiplexers/herdr.ts +64 -0
- package/src/multiplexers/notify.d.ts +13 -0
- package/src/multiplexers/notify.ts +24 -0
- package/src/multiplexers/orca.d.ts +12 -0
- package/src/multiplexers/orca.ts +16 -0
- package/src/multiplexers/screen.d.ts +9 -0
- package/src/multiplexers/screen.ts +15 -0
- package/src/multiplexers/tmux.d.ts +29 -0
- package/src/multiplexers/tmux.ts +40 -0
- package/src/multiplexers/types.d.ts +100 -0
- package/src/multiplexers/types.ts +110 -0
- package/src/multiplexers/wmux.d.ts +12 -0
- package/src/multiplexers/wmux.ts +16 -0
- package/src/multiplexers/zellij.d.ts +12 -0
- package/src/multiplexers/zellij.ts +24 -0
- package/src/native/backend.d.ts +3 -2
- package/src/native/backend.ts +174 -18
- package/src/native/blobs.d.ts +7 -3
- package/src/native/blobs.ts +47 -12
- package/src/native/encode.d.ts +3 -4
- package/src/native/encode.ts +58 -31
- package/src/native/overlay.d.ts +5 -0
- package/src/native/overlay.ts +13 -0
- package/src/native/reconcile.ts +4 -0
- package/src/overlays/agent-hub-projection.d.ts +6 -5
- package/src/overlays/agent-hub-projection.ts +124 -13
- package/src/overlays/agent-hub-types.d.ts +9 -0
- package/src/overlays/agent-hub-types.ts +6 -0
- package/src/overlays/agent-hub.ts +44 -29
- package/src/overlays/agent-transcript-viewer.ts +91 -23
- package/src/overlays/agents-hub.ts +28 -8
- package/src/overlays/annotation-overlay.ts +293 -90
- package/src/overlays/annotation-types.ts +2 -0
- package/src/overlays/ask-dialog.ts +7 -10
- package/src/overlays/btw-history-panel.ts +12 -1
- package/src/overlays/copy-selector.ts +30 -8
- package/src/overlays/extensions/state-manager.ts +52 -12
- package/src/overlays/history-search.ts +100 -7
- package/src/overlays/hook-selector.ts +12 -5
- package/src/overlays/jobs-panel.ts +28 -46
- package/src/overlays/model-browser.ts +341 -110
- package/src/overlays/model-hub.ts +300 -49
- package/src/overlays/move-overlay.ts +28 -14
- package/src/overlays/oauth-selector.ts +54 -70
- package/src/overlays/plan-review-overlay.ts +194 -70
- package/src/overlays/plugin-settings.ts +3 -3
- package/src/overlays/report-panel.ts +2 -2
- package/src/overlays/rewind-selector.ts +86 -17
- package/src/overlays/session-observer-registry.d.ts +2 -0
- package/src/overlays/session-observer-registry.ts +10 -2
- package/src/overlays/session-selector.ts +64 -26
- package/src/overlays/settings-defs.ts +4 -3
- package/src/overlays/settings-selector.ts +33 -8
- package/src/overlays/snapcompact-shape-preview.ts +2 -3
- package/src/overlays/stats-notice.ts +1 -1
- package/src/overlays/tree-selector.ts +41 -20
- package/src/overlays/usage-dashboard.ts +100 -50
- package/src/overlays/usage-display.ts +10 -4
- package/src/prompt/attachment-chips.ts +15 -11
- package/src/prompt/composer-attachments.ts +6 -4
- package/src/prompt/composer.ts +4 -15
- package/src/prompt/custom-editor.ts +54 -26
- package/src/prompt/image-source.ts +2 -1
- package/src/prompt/interactive-context-helpers.ts +30 -18
- package/src/prompt/macos-spelling.ts +7 -0
- package/src/prompt/magic-keywords.ts +11 -2
- package/src/prompt/model-mention-autocomplete.ts +14 -14
- package/src/prompt/tips.txt +6 -5
- package/src/prompt/welcome.ts +55 -12
- package/src/prompt/word-completion.ts +38 -5
- package/src/render/composite.d.ts +2 -0
- package/src/render/composite.ts +64 -0
- package/src/render/output-pane.d.ts +1 -0
- package/src/render/output-pane.ts +6 -0
- package/src/render/render-utils.d.ts +1 -0
- package/src/render/render-utils.ts +11 -0
- package/src/render/tool-card.d.ts +6 -0
- package/src/render/tool-card.ts +15 -0
- package/src/render/width-aware-text.d.ts +2 -0
- package/src/render/width-aware-text.ts +5 -0
- package/src/setup/scenes/model.ts +2 -1
- package/src/setup/scenes/types.ts +1 -0
- package/src/status-line/component.ts +292 -78
- package/src/status-line/host.ts +4 -1
- package/src/status-line/segments.ts +40 -22
- package/src/status-line/types.ts +15 -3
- package/src/terminal-capabilities.d.ts +27 -35
- package/src/terminal-capabilities.ts +75 -189
- package/src/terminal-multiplexer.d.ts +127 -14
- package/src/terminal-multiplexer.ts +112 -33
- package/src/terminal.d.ts +22 -6
- package/src/terminal.ts +139 -37
- package/src/theme/active-symbols.d.ts +1 -1
- package/src/theme/active-symbols.ts +1 -1
- package/src/theme/schema-validation.ts +1 -0
- package/src/theme/schema.d.ts +11 -2
- package/src/theme/schema.ts +14 -1
- package/src/theme/session-color.ts +1 -7
- package/src/theme/symbols.d.ts +1 -1
- package/src/theme/symbols.ts +6 -1
- package/src/theme/theme-class.d.ts +7 -0
- package/src/theme/theme-class.ts +26 -1
- package/src/theme/theme-schema.json +5 -1
- package/src/theme/theme.d.ts +1 -1
- package/src/theme/theme.ts +8 -4
- package/src/theme/tui-adapters.d.ts +3 -0
- package/src/theme/tui-adapters.ts +68 -0
- package/src/tmux.d.ts +11 -4
- package/src/tmux.ts +47 -11
- package/src/tools/ast-edit.ts +59 -35
- package/src/tools/ast-grep.ts +35 -16
- package/src/tools/bash.ts +92 -42
- package/src/tools/daemon.d.ts +1 -1
- package/src/tools/daemon.ts +1 -1
- package/src/tools/default-renderer.ts +35 -19
- package/src/tools/edit.ts +142 -120
- package/src/tools/eval-format/index.ts +28 -2
- package/src/tools/eval-format/javascript.ts +7 -5
- package/src/tools/eval.d.ts +22 -3
- package/src/tools/eval.ts +364 -208
- package/src/tools/fetch.ts +5 -6
- package/src/tools/glob.d.ts +4 -0
- package/src/tools/glob.ts +33 -65
- package/src/tools/grep.ts +81 -48
- package/src/tools/index.ts +2 -0
- package/src/tools/lsp.ts +81 -59
- package/src/tools/mcp.ts +111 -68
- package/src/tools/native-view.d.ts +47 -0
- package/src/tools/native-view.ts +102 -1
- package/src/tools/output-meta.d.ts +11 -1
- package/src/tools/output-meta.ts +23 -3
- package/src/tools/read.ts +7 -5
- package/src/tools/renderer.d.ts +37 -2
- package/src/tools/renderer.ts +42 -2
- package/src/tools/streaming-output.d.ts +20 -1
- package/src/tools/streaming-output.ts +149 -50
- package/src/tools/task.d.ts +0 -4
- package/src/tools/task.ts +112 -59
- package/src/tools/todo.ts +119 -30
- package/src/tools/wait.ts +34 -24
- package/src/tools/web-search.ts +1 -1
- package/src/tools/write.d.ts +9 -2
- package/src/tools/write.ts +147 -95
- package/src/tools/yield.d.ts +22 -0
- package/src/tools/yield.ts +225 -0
- package/src/tui.d.ts +31 -0
- package/src/tui.ts +309 -160
- package/src/utils.d.ts +6 -0
- package/src/utils.ts +19 -0
- package/src/vim.d.ts +19 -5
- package/src/vim.ts +333 -18
- package/src/windows-input-mode.ts +8 -0
|
@@ -15,11 +15,12 @@ interface AgentTreeProjection<TRecord extends AgentRecordLike> {
|
|
|
15
15
|
export declare const STATUS_ORDER: Record<AgentStatus, number>;
|
|
16
16
|
/** Exact observer usage for one roster entry. */
|
|
17
17
|
export declare function progressMetrics(observed: ObservableSession | undefined): AgentMetrics | undefined;
|
|
18
|
-
/** Live session
|
|
18
|
+
/** Live session used to cache a row's direct assistant usage, including with observer progress. */
|
|
19
19
|
export declare function hubFallbackStatsSession<TRecord extends AgentRecordLike>(ref: TRecord, observed: ObservableSession | undefined): NonNullable<TRecord["session"]> | undefined;
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
21
|
+
* Preserve progress/history for non-cost metrics, overlaying cumulative direct-session cost.
|
|
22
|
+
* Persisted `history.metrics.cost` is a leaf-scoped snapshot, so it never overrides live
|
|
23
|
+
* progress; only the cumulative direct-cost sources do.
|
|
23
24
|
*/
|
|
24
25
|
export declare function hubRowMetrics<TRecord extends AgentRecordLike>(ref: TRecord, observed: ObservableSession | undefined, sessionMetrics: WeakMap<object, {
|
|
25
26
|
metrics: AgentMetrics | undefined;
|
|
@@ -37,6 +38,6 @@ export declare function aggregateMetrics<TRecord extends AgentRecordLike>(args:
|
|
|
37
38
|
metrics: AggregateMetrics;
|
|
38
39
|
hasFallbackLiveSessions: boolean;
|
|
39
40
|
};
|
|
40
|
-
/** Parent-before-child projection preserving
|
|
41
|
-
export declare function projectAgentTree<TRecord extends AgentRecordLike>(refs: readonly TRecord[]): AgentTreeProjection<TRecord>;
|
|
41
|
+
/** Parent-before-child projection preserving initial subtree rank and prepending new siblings. */
|
|
42
|
+
export declare function projectAgentTree<TRecord extends AgentRecordLike>(refs: readonly TRecord[], rosterRank: ReadonlyMap<TRecord, number> | undefined): AgentTreeProjection<TRecord>;
|
|
42
43
|
export {};
|
|
@@ -23,6 +23,68 @@ function finiteMetric(value: number | undefined): number {
|
|
|
23
23
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/** Message list a fallback metrics read was taken from; unchanged list ⇒ unchanged metrics. */
|
|
27
|
+
interface FallbackReadStamp {
|
|
28
|
+
messages: readonly unknown[];
|
|
29
|
+
length: number;
|
|
30
|
+
last: unknown;
|
|
31
|
+
/** Usage and block count of the last message, which a streaming turn mutates in place. */
|
|
32
|
+
lastShape: string;
|
|
33
|
+
/** Session model, whose context window the context gauge reports. */
|
|
34
|
+
model: unknown;
|
|
35
|
+
contextWindow: number | null | undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Stamp of each cached fallback read, keyed by the cache entry so it cannot outlive that read. */
|
|
39
|
+
const fallbackReadStamps = new WeakMap<object, FallbackReadStamp>();
|
|
40
|
+
|
|
41
|
+
function lastMessageShape(last: unknown): string {
|
|
42
|
+
if (!last || typeof last !== "object") return "";
|
|
43
|
+
const { usage, content } = last as {
|
|
44
|
+
usage?: { input?: number; output?: number; cacheWrite?: number; cost?: { total?: number } };
|
|
45
|
+
content?: unknown;
|
|
46
|
+
};
|
|
47
|
+
const blocks = Array.isArray(content) ? content.length : -1;
|
|
48
|
+
return `${usage?.input}:${usage?.output}:${usage?.cacheWrite}:${usage?.cost?.total}:${blocks}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function fallbackReadStamp(session: NonNullable<AgentRecordLike["session"]>): FallbackReadStamp | undefined {
|
|
52
|
+
try {
|
|
53
|
+
const messages = session.agent?.state?.messages;
|
|
54
|
+
if (!Array.isArray(messages)) return undefined;
|
|
55
|
+
const last = messages[messages.length - 1];
|
|
56
|
+
return {
|
|
57
|
+
messages,
|
|
58
|
+
length: messages.length,
|
|
59
|
+
last,
|
|
60
|
+
lastShape: lastMessageShape(last),
|
|
61
|
+
model: session.model,
|
|
62
|
+
contextWindow: session.model?.contextWindow,
|
|
63
|
+
};
|
|
64
|
+
} catch {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Whether a cached fallback read still matches the session's message list
|
|
71
|
+
* (append, compaction, rewrite, streaming tail) and model (context window).
|
|
72
|
+
*/
|
|
73
|
+
function fallbackReadCurrent(entry: object, session: NonNullable<AgentRecordLike["session"]>): boolean {
|
|
74
|
+
const stamp = fallbackReadStamps.get(entry);
|
|
75
|
+
if (!stamp) return false;
|
|
76
|
+
const current = fallbackReadStamp(session);
|
|
77
|
+
return (
|
|
78
|
+
current !== undefined &&
|
|
79
|
+
current.messages === stamp.messages &&
|
|
80
|
+
current.length === stamp.length &&
|
|
81
|
+
current.last === stamp.last &&
|
|
82
|
+
current.lastShape === stamp.lastShape &&
|
|
83
|
+
current.model === stamp.model &&
|
|
84
|
+
current.contextWindow === stamp.contextWindow
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
26
88
|
/** Exact observer usage for one roster entry. */
|
|
27
89
|
export function progressMetrics(observed: ObservableSession | undefined): AgentMetrics | undefined {
|
|
28
90
|
const progress = observed?.progress;
|
|
@@ -60,12 +122,33 @@ export function progressMetrics(observed: ObservableSession | undefined): AgentM
|
|
|
60
122
|
};
|
|
61
123
|
}
|
|
62
124
|
|
|
125
|
+
/** Lifetime cost from the authoritative session index, excluding task-result child billing. */
|
|
126
|
+
function readSessionDirectCost(session: NonNullable<AgentRecordLike["session"]>): number | undefined {
|
|
127
|
+
try {
|
|
128
|
+
const usage = session.sessionManager?.getUsageStatistics();
|
|
129
|
+
if (
|
|
130
|
+
usage &&
|
|
131
|
+
Number.isFinite(usage.cost) &&
|
|
132
|
+
Number.isFinite(usage.subagentCost) &&
|
|
133
|
+
usage.cost >= usage.subagentCost
|
|
134
|
+
) {
|
|
135
|
+
return usage.cost - usage.subagentCost;
|
|
136
|
+
}
|
|
137
|
+
} catch {
|
|
138
|
+
// A session stopping during render may have already closed its stats host.
|
|
139
|
+
}
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
|
|
63
143
|
/**
|
|
64
144
|
* Read direct assistant usage from a live session. SessionStats also includes
|
|
65
145
|
* usage embedded in completed `task` tool results, so using it for a parent
|
|
66
146
|
* row would double-count child rows in the aggregate.
|
|
67
147
|
*/
|
|
68
|
-
function readSessionMetrics(
|
|
148
|
+
function readSessionMetrics(
|
|
149
|
+
session: NonNullable<AgentRecordLike["session"]>,
|
|
150
|
+
directCost: number | undefined,
|
|
151
|
+
): AgentMetrics | undefined {
|
|
69
152
|
try {
|
|
70
153
|
const stats = session.getSessionStats();
|
|
71
154
|
const messages = session.agent?.state?.messages;
|
|
@@ -74,7 +157,7 @@ function readSessionMetrics(session: NonNullable<AgentRecordLike["session"]>): A
|
|
|
74
157
|
tokens: stats.tokens.input + stats.tokens.output + stats.tokens.cacheWrite,
|
|
75
158
|
requests: stats.assistantMessages,
|
|
76
159
|
tools: stats.toolCalls,
|
|
77
|
-
cost: stats.cost,
|
|
160
|
+
cost: directCost ?? stats.cost,
|
|
78
161
|
durationMs: 0,
|
|
79
162
|
durationKind: "unknown",
|
|
80
163
|
contextTokens: stats.contextUsage?.tokens,
|
|
@@ -97,7 +180,7 @@ function readSessionMetrics(session: NonNullable<AgentRecordLike["session"]>): A
|
|
|
97
180
|
tokens,
|
|
98
181
|
requests,
|
|
99
182
|
tools,
|
|
100
|
-
cost,
|
|
183
|
+
cost: directCost ?? cost,
|
|
101
184
|
durationMs: 0,
|
|
102
185
|
durationKind: "unknown",
|
|
103
186
|
contextTokens: stats.contextUsage?.tokens,
|
|
@@ -110,29 +193,39 @@ function readSessionMetrics(session: NonNullable<AgentRecordLike["session"]>): A
|
|
|
110
193
|
}
|
|
111
194
|
}
|
|
112
195
|
|
|
113
|
-
/** Live session
|
|
196
|
+
/** Live session used to cache a row's direct assistant usage, including with observer progress. */
|
|
114
197
|
export function hubFallbackStatsSession<TRecord extends AgentRecordLike>(
|
|
115
198
|
ref: TRecord,
|
|
116
199
|
observed: ObservableSession | undefined,
|
|
117
200
|
): NonNullable<TRecord["session"]> | undefined {
|
|
118
|
-
|
|
201
|
+
void observed;
|
|
119
202
|
const session = ref.session;
|
|
120
203
|
return session && typeof session.getSessionStats === "function" ? session : undefined;
|
|
121
204
|
}
|
|
122
205
|
|
|
206
|
+
function withDirectCost(metrics: AgentMetrics | undefined, cost: number | undefined): AgentMetrics | undefined {
|
|
207
|
+
return metrics && cost !== undefined ? { ...metrics, cost } : metrics;
|
|
208
|
+
}
|
|
209
|
+
|
|
123
210
|
/**
|
|
124
|
-
*
|
|
125
|
-
*
|
|
211
|
+
* Preserve progress/history for non-cost metrics, overlaying cumulative direct-session cost.
|
|
212
|
+
* Persisted `history.metrics.cost` is a leaf-scoped snapshot, so it never overrides live
|
|
213
|
+
* progress; only the cumulative direct-cost sources do.
|
|
126
214
|
*/
|
|
127
215
|
export function hubRowMetrics<TRecord extends AgentRecordLike>(
|
|
128
216
|
ref: TRecord,
|
|
129
217
|
observed: ObservableSession | undefined,
|
|
130
218
|
sessionMetrics: WeakMap<object, { metrics: AgentMetrics | undefined }>,
|
|
131
219
|
): AgentMetrics | undefined {
|
|
132
|
-
if (observed?.progress) return progressMetrics(observed);
|
|
133
|
-
if (ref.history?.metrics) return ref.history.metrics;
|
|
134
220
|
const session = hubFallbackStatsSession(ref, observed);
|
|
135
|
-
|
|
221
|
+
const liveEntry = session ? sessionMetrics.get(session) : undefined;
|
|
222
|
+
const retainedEntry = sessionMetrics.get(ref);
|
|
223
|
+
const historyMetrics = ref.history?.metrics;
|
|
224
|
+
const cost = liveEntry?.metrics?.cost ?? ref.history?.directCost ?? retainedEntry?.metrics?.cost;
|
|
225
|
+
if (observed?.progress) return withDirectCost(progressMetrics(observed), cost);
|
|
226
|
+
if (historyMetrics) return withDirectCost(historyMetrics, cost);
|
|
227
|
+
if (!session && retainedEntry?.metrics) return withDirectCost(retainedEntry.metrics, cost);
|
|
228
|
+
return withDirectCost(liveEntry?.metrics, cost);
|
|
136
229
|
}
|
|
137
230
|
|
|
138
231
|
export function aggregateMetrics<TRecord extends AgentRecordLike>(args: {
|
|
@@ -163,9 +256,21 @@ export function aggregateMetrics<TRecord extends AgentRecordLike>(args: {
|
|
|
163
256
|
const fallbackSession = args.fallbackStatsSession(ref, observed);
|
|
164
257
|
if (fallbackSession) {
|
|
165
258
|
hasFallbackLiveSessions = true;
|
|
166
|
-
|
|
167
|
-
|
|
259
|
+
let cached = args.sessionMetrics.get(fallbackSession);
|
|
260
|
+
const directCost = readSessionDirectCost(fallbackSession);
|
|
261
|
+
// Refresh cost independently from the message scan: model_usage entries
|
|
262
|
+
// are cumulative but do not alter the agent's live message list.
|
|
263
|
+
if (!cached || (args.refreshFallback && !fallbackReadCurrent(cached, fallbackSession))) {
|
|
264
|
+
const stamp = fallbackReadStamp(fallbackSession);
|
|
265
|
+
const entry = { metrics: readSessionMetrics(fallbackSession, directCost) };
|
|
266
|
+
if (stamp) fallbackReadStamps.set(entry, stamp);
|
|
267
|
+
args.sessionMetrics.set(fallbackSession, entry);
|
|
268
|
+
cached = entry;
|
|
269
|
+
} else if (cached.metrics && directCost !== undefined) {
|
|
270
|
+
cached.metrics.cost = directCost;
|
|
168
271
|
}
|
|
272
|
+
// Preserve valid direct-session snapshots through parking or a failed teardown read.
|
|
273
|
+
if (cached.metrics) args.sessionMetrics.set(ref, cached);
|
|
169
274
|
}
|
|
170
275
|
const metrics = args.metricsFor(ref, observed);
|
|
171
276
|
if (!metrics || (fallbackSession && countedFallbackSessions.has(fallbackSession))) continue;
|
|
@@ -183,9 +288,10 @@ export function aggregateMetrics<TRecord extends AgentRecordLike>(args: {
|
|
|
183
288
|
return { metrics: total, hasFallbackLiveSessions };
|
|
184
289
|
}
|
|
185
290
|
|
|
186
|
-
/** Parent-before-child projection preserving
|
|
291
|
+
/** Parent-before-child projection preserving initial subtree rank and prepending new siblings. */
|
|
187
292
|
export function projectAgentTree<TRecord extends AgentRecordLike>(
|
|
188
293
|
refs: readonly TRecord[],
|
|
294
|
+
rosterRank: ReadonlyMap<TRecord, number> | undefined,
|
|
189
295
|
): AgentTreeProjection<TRecord> {
|
|
190
296
|
const ids = new Set<string>();
|
|
191
297
|
const operationalIndex = new Map<string, number>();
|
|
@@ -218,7 +324,12 @@ export function projectAgentTree<TRecord extends AgentRecordLike>(
|
|
|
218
324
|
if (!current) continue;
|
|
219
325
|
if (current.expanded) {
|
|
220
326
|
let order = operationalIndex.get(current.ref.id) ?? Number.MAX_SAFE_INTEGER;
|
|
327
|
+
const parentRank = rosterRank?.get(current.ref);
|
|
221
328
|
for (const child of children.get(current.ref.id) ?? []) {
|
|
329
|
+
const childRank = rosterRank?.get(child);
|
|
330
|
+
// A child spawned later cannot move an existing parent's group.
|
|
331
|
+
if (parentRank !== undefined && childRank !== undefined && childRank < 0 && childRank < parentRank)
|
|
332
|
+
continue;
|
|
222
333
|
order = Math.min(order, subtreeOrder.get(child.id) ?? Number.MAX_SAFE_INTEGER);
|
|
223
334
|
}
|
|
224
335
|
subtreeOrder.set(current.ref.id, order);
|
|
@@ -28,6 +28,13 @@ export interface AgentHubSession {
|
|
|
28
28
|
messages: AgentMessage[];
|
|
29
29
|
};
|
|
30
30
|
};
|
|
31
|
+
/** Existing session ledger includes off-transcript model usage and nested-task attribution. */
|
|
32
|
+
readonly sessionManager?: {
|
|
33
|
+
getUsageStatistics(): {
|
|
34
|
+
cost: number;
|
|
35
|
+
subagentCost: number;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
31
38
|
getSessionStats(): {
|
|
32
39
|
tokens: {
|
|
33
40
|
input: number;
|
|
@@ -64,6 +71,8 @@ export interface AgentRecordLike {
|
|
|
64
71
|
resolvedModel?: string;
|
|
65
72
|
resolvedModelIsFallback?: boolean;
|
|
66
73
|
metrics?: AgentMetricsSummary;
|
|
74
|
+
/** Cumulative own-session assistant and model_usage cost, excluding nested task results. */
|
|
75
|
+
directCost?: number;
|
|
67
76
|
readOnly?: boolean;
|
|
68
77
|
outputPath?: string;
|
|
69
78
|
patchPath?: string;
|
|
@@ -22,6 +22,10 @@ export interface AgentHubSession {
|
|
|
22
22
|
readonly model: Model | undefined;
|
|
23
23
|
readonly servingModel?: { selector: string; isFallback: boolean };
|
|
24
24
|
readonly agent?: { state: { messages: AgentMessage[] } };
|
|
25
|
+
/** Existing session ledger includes off-transcript model usage and nested-task attribution. */
|
|
26
|
+
readonly sessionManager?: {
|
|
27
|
+
getUsageStatistics(): { cost: number; subagentCost: number };
|
|
28
|
+
};
|
|
25
29
|
getSessionStats(): {
|
|
26
30
|
tokens: { input: number; output: number; cacheWrite: number };
|
|
27
31
|
assistantMessages: number;
|
|
@@ -49,6 +53,8 @@ export interface AgentRecordLike {
|
|
|
49
53
|
resolvedModel?: string;
|
|
50
54
|
resolvedModelIsFallback?: boolean;
|
|
51
55
|
metrics?: AgentMetricsSummary;
|
|
56
|
+
/** Cumulative own-session assistant and model_usage cost, excluding nested task results. */
|
|
57
|
+
directCost?: number;
|
|
52
58
|
readOnly?: boolean;
|
|
53
59
|
outputPath?: string;
|
|
54
60
|
patchPath?: string;
|
|
@@ -200,6 +200,12 @@ const DATA_CHANGE_RENDER_COALESCE_MS = 100;
|
|
|
200
200
|
/** Double-tap window for the table's left-left "close hub" gesture. */
|
|
201
201
|
const LEFT_TAP_WINDOW_MS = 500;
|
|
202
202
|
|
|
203
|
+
function compareRosterAgents(a: AgentRecordLike, b: AgentRecordLike): number {
|
|
204
|
+
return (
|
|
205
|
+
STATUS_ORDER[a.status] - STATUS_ORDER[b.status] || b.lastActivity - a.lastActivity || a.id.localeCompare(b.id)
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
203
209
|
function activityGlyph(row: AgentActivityRow): string {
|
|
204
210
|
if (row.status === "error") return theme.fg("error", theme.status.error);
|
|
205
211
|
if (row.status === "aborted") return theme.fg("warning", theme.status.aborted);
|
|
@@ -358,11 +364,10 @@ export class AgentHubOverlayComponent<TRecord extends AgentRecordLike = AgentRec
|
|
|
358
364
|
#rows: TRecord[] = [];
|
|
359
365
|
#statusCounts: Record<AgentStatus, number> = { running: 0, idle: 0, parked: 0, aborted: 0 };
|
|
360
366
|
#selectedRow = 0;
|
|
361
|
-
/**
|
|
362
|
-
*
|
|
363
|
-
* while the hub is open; newly appearing generations append at the end. */
|
|
367
|
+
/** Initial status/recency rank stays fixed while open; new generations
|
|
368
|
+
* prepend without changing the relative order of existing rows. */
|
|
364
369
|
#rowOrder: Map<TRecord, number> | undefined;
|
|
365
|
-
#
|
|
370
|
+
#nextNewRowOrder = 0;
|
|
366
371
|
#hoveredRow: number | null = null;
|
|
367
372
|
/** Per-render screen-line to agent-row map, shared by click and hover routing. */
|
|
368
373
|
#hitRows: Array<number | undefined> = [];
|
|
@@ -616,6 +621,11 @@ export class AgentHubOverlayComponent<TRecord extends AgentRecordLike = AgentRec
|
|
|
616
621
|
this.#lastLeftTap = Date.now();
|
|
617
622
|
}
|
|
618
623
|
|
|
624
|
+
/** Show `section`, as a slash-command deep link into an already-open hub does. */
|
|
625
|
+
showSection(section: AgentHubSection): void {
|
|
626
|
+
this.#switchSection(section);
|
|
627
|
+
}
|
|
628
|
+
|
|
619
629
|
/**
|
|
620
630
|
* Open the fullscreen transcript viewer for an agent id (public for table Enter
|
|
621
631
|
* and tests). Mounts {@link AgentTranscriptViewer} as a `fullscreen` overlay so it
|
|
@@ -941,12 +951,16 @@ export class AgentHubOverlayComponent<TRecord extends AgentRecordLike = AgentRec
|
|
|
941
951
|
return { ...item, depth, open: this.#childrenByParent.has(ref.id) ? true : undefined };
|
|
942
952
|
}
|
|
943
953
|
if (!metrics) return item;
|
|
954
|
+
// A running agent's active time ticks terminal-side from the age at send; a settled one
|
|
955
|
+
// is frozen text. Progress `durationMs` was current when its frame arrived, so age it to now
|
|
956
|
+
// — re-sending the stale value on every repaint rewinds Tern's clock. A first frame can
|
|
957
|
+
// report 0 ms, so a progress timestamp alone makes the row clockable.
|
|
958
|
+
const progressAt = observed?.progress ? observed.progressAt : undefined;
|
|
944
959
|
const facts: Record<string, string | number> = {
|
|
945
960
|
cost: formatCost(metrics.cost),
|
|
946
|
-
// A running agent's active time ticks terminal-side; a settled one is frozen text.
|
|
947
961
|
time:
|
|
948
|
-
ref.status === "running" && metrics.durationMs > 0
|
|
949
|
-
? metrics.durationMs
|
|
962
|
+
ref.status === "running" && (metrics.durationMs > 0 || progressAt !== undefined)
|
|
963
|
+
? metrics.durationMs + (progressAt === undefined ? 0 : Math.max(0, Date.now() - progressAt))
|
|
950
964
|
: formatDuration(metrics.durationMs),
|
|
951
965
|
req: metrics.requests,
|
|
952
966
|
tools: metrics.tools,
|
|
@@ -1519,35 +1533,35 @@ export class AgentHubOverlayComponent<TRecord extends AgentRecordLike = AgentRec
|
|
|
1519
1533
|
const refs = this.#registry.list().filter(ref => ref.id !== MAIN_AGENT_ID);
|
|
1520
1534
|
this.#observedById = new Map();
|
|
1521
1535
|
for (const session of this.#observers.getSessions()) this.#observedById.set(session.id, session);
|
|
1522
|
-
//
|
|
1523
|
-
//
|
|
1524
|
-
//
|
|
1525
|
-
// Defer the capture until persisted-subagent discovery settles so a
|
|
1526
|
-
// mid-scan refresh cannot freeze a partial roster (the remaining agents
|
|
1527
|
-
// would otherwise append in readdir order instead of being ranked).
|
|
1536
|
+
// Capture the status+recency ranking once so keyboard navigation does
|
|
1537
|
+
// not jump on heartbeats. Defer until persisted-subagent discovery
|
|
1538
|
+
// settles so a partial roster is not frozen in readdir order.
|
|
1528
1539
|
const rowOrder = this.#rowOrder;
|
|
1529
1540
|
let ordered: TRecord[];
|
|
1530
1541
|
if (!rowOrder) {
|
|
1531
|
-
ordered = refs.sort(
|
|
1532
|
-
(a, b) =>
|
|
1533
|
-
STATUS_ORDER[a.status] - STATUS_ORDER[b.status] ||
|
|
1534
|
-
b.lastActivity - a.lastActivity ||
|
|
1535
|
-
a.id.localeCompare(b.id),
|
|
1536
|
-
);
|
|
1542
|
+
ordered = refs.sort(compareRosterAgents);
|
|
1537
1543
|
if (!this.#loadingPersistedSubagents && ordered.length > 0) {
|
|
1538
|
-
|
|
1539
|
-
for (const ref of ordered)
|
|
1544
|
+
const rowOrder = new Map<TRecord, number>();
|
|
1545
|
+
for (const [index, ref] of ordered.entries()) rowOrder.set(ref, index);
|
|
1546
|
+
this.#rowOrder = rowOrder;
|
|
1540
1547
|
}
|
|
1541
1548
|
} else {
|
|
1542
1549
|
for (const rankedRef of rowOrder.keys()) {
|
|
1543
1550
|
if (!refs.includes(rankedRef)) rowOrder.delete(rankedRef);
|
|
1544
1551
|
}
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
for (const ref of
|
|
1549
|
-
if (!rowOrder.has(ref))
|
|
1552
|
+
// Each batch of newly appearing generations uses the initial sort,
|
|
1553
|
+
// then takes ranks above every agent already on screen.
|
|
1554
|
+
let newcomers: TRecord[] | undefined;
|
|
1555
|
+
for (const ref of refs) {
|
|
1556
|
+
if (!rowOrder.has(ref)) (newcomers ??= []).push(ref);
|
|
1557
|
+
}
|
|
1558
|
+
if (newcomers) {
|
|
1559
|
+
newcomers.sort(compareRosterAgents);
|
|
1560
|
+
for (let i = newcomers.length - 1; i >= 0; i--) {
|
|
1561
|
+
rowOrder.set(newcomers[i]!, --this.#nextNewRowOrder);
|
|
1562
|
+
}
|
|
1550
1563
|
}
|
|
1564
|
+
ordered = refs.sort((a, b) => rowOrder.get(a)! - rowOrder.get(b)!);
|
|
1551
1565
|
}
|
|
1552
1566
|
const query = this.#agentFilter.getValue().trim();
|
|
1553
1567
|
const rosterRows =
|
|
@@ -1556,7 +1570,7 @@ export class AgentHubOverlayComponent<TRecord extends AgentRecordLike = AgentRec
|
|
|
1556
1570
|
: ordered;
|
|
1557
1571
|
|
|
1558
1572
|
if (this.#viewMode === "tree") {
|
|
1559
|
-
const tree = projectAgentTree(rosterRows);
|
|
1573
|
+
const tree = projectAgentTree(rosterRows, rowOrder);
|
|
1560
1574
|
this.#rows = tree.rows;
|
|
1561
1575
|
this.#treeDepthById = tree.depthById;
|
|
1562
1576
|
this.#treeParentById = tree.parentById;
|
|
@@ -1589,7 +1603,8 @@ export class AgentHubOverlayComponent<TRecord extends AgentRecordLike = AgentRec
|
|
|
1589
1603
|
for (const ref of rosterRows) this.#statusCounts[ref.status]++;
|
|
1590
1604
|
this.#refreshAggregate();
|
|
1591
1605
|
this.#refreshActivityData(rosterRows);
|
|
1592
|
-
|
|
1606
|
+
// The 2,000-row activity query only feeds the Activity tab; switching to it refreshes.
|
|
1607
|
+
if (this.#section === "activity") this.#refreshActivityRows();
|
|
1593
1608
|
}
|
|
1594
1609
|
|
|
1595
1610
|
#refreshActivityData(refs: readonly TRecord[]): void {
|
|
@@ -1620,7 +1635,7 @@ export class AgentHubOverlayComponent<TRecord extends AgentRecordLike = AgentRec
|
|
|
1620
1635
|
void Promise.all(pending)
|
|
1621
1636
|
.then(() => {
|
|
1622
1637
|
if (this.#disposed || generation !== this.#activitySyncGeneration) return;
|
|
1623
|
-
this.#refreshActivityRows();
|
|
1638
|
+
if (this.#section === "activity") this.#refreshActivityRows();
|
|
1624
1639
|
this.#requestRender();
|
|
1625
1640
|
})
|
|
1626
1641
|
.catch(() => {
|
|
@@ -41,15 +41,22 @@ import { sanitizeErrorLine } from "../chrome/error-block";
|
|
|
41
41
|
import type { ScrollRangeAnchor } from "../components/scroll-view";
|
|
42
42
|
import { formatContextUsage } from "../chrome/context-thresholds";
|
|
43
43
|
import { node, span, text } from "../native/describe";
|
|
44
|
-
import type { DescribeContext, NativeChild, NativeNode } from "../native/node";
|
|
45
|
-
import { actionHint, hintsRow, overlayCard } from "../native/overlay";
|
|
44
|
+
import type { DescribeContext, NativeChild, NativeNode, NativeUiEvent } from "../native/node";
|
|
45
|
+
import { actionHint, escCloseButton, hintsRow, overlayCard } from "../native/overlay";
|
|
46
46
|
|
|
47
47
|
/** Parsed message and model metadata relevant to a transcript viewer. */
|
|
48
48
|
export type AgentTranscriptEntry = SessionMessageEntryLike | { type: "model_change"; model: string };
|
|
49
49
|
|
|
50
50
|
/** Local filesystem and session parsing capabilities supplied by the host. */
|
|
51
51
|
export interface AgentTranscriptSource {
|
|
52
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Sync primitives drive the first paint and the incremental tail. When the
|
|
54
|
+
* host also supplies `promises.readFile` (e.g. passes `node:fs` itself), full
|
|
55
|
+
* reloads after rotation/rewrite read the file off the UI thread.
|
|
56
|
+
*/
|
|
57
|
+
fs: Pick<typeof fs, "openSync" | "closeSync" | "readSync" | "readFileSync" | "statSync"> & {
|
|
58
|
+
promises?: Pick<typeof fs.promises, "readFile">;
|
|
59
|
+
};
|
|
53
60
|
parseEntries(text: string): AgentTranscriptEntry[];
|
|
54
61
|
}
|
|
55
62
|
|
|
@@ -86,6 +93,8 @@ export interface AgentTranscriptViewerDeps {
|
|
|
86
93
|
|
|
87
94
|
/** How often to re-stat a file-backed transcript for growth (advisor/live tail). */
|
|
88
95
|
const POLL_MS = 250;
|
|
96
|
+
/** Every Nth idle poll re-verifies sentinels, catching same-size/mtime rewrites. */
|
|
97
|
+
const IDLE_SENTINEL_CHECK_EVERY = 5;
|
|
89
98
|
|
|
90
99
|
const SENTINEL_BYTES = 4096;
|
|
91
100
|
|
|
@@ -183,6 +192,10 @@ export class AgentTranscriptViewer implements Component {
|
|
|
183
192
|
#model: string | undefined;
|
|
184
193
|
#pollTimer: NodeJS.Timeout | undefined;
|
|
185
194
|
#disposed = false;
|
|
195
|
+
/** Async full reload in flight; polls are skipped until it lands. */
|
|
196
|
+
#localLoadInFlight = false;
|
|
197
|
+
/** Idle polls since the sentinels were last verified. */
|
|
198
|
+
#idlePolls = 0;
|
|
186
199
|
#initialEntryId: string | undefined;
|
|
187
200
|
/** Last described node and the visible inputs it was built from. */
|
|
188
201
|
#nativeCache: { signature: string; node: NativeNode } | undefined;
|
|
@@ -201,6 +214,8 @@ export class AgentTranscriptViewer implements Component {
|
|
|
201
214
|
hideThinkingBlock: deps.hideThinkingBlock,
|
|
202
215
|
proseOnlyThinking: deps.proseOnlyThinking,
|
|
203
216
|
expandThinkingBlocks: deps.expandThinkingBlocks,
|
|
217
|
+
// Charts are for the main session's answers, not parked subagent, advisor, or guest transcripts.
|
|
218
|
+
tableCharts: false,
|
|
204
219
|
requestRender: deps.requestRender,
|
|
205
220
|
});
|
|
206
221
|
this.#browser = new TranscriptBrowser({
|
|
@@ -213,8 +228,10 @@ export class AgentTranscriptViewer implements Component {
|
|
|
213
228
|
this.#editor.setMaxHeight(4);
|
|
214
229
|
this.#editor.onSubmit = text => this.#submit(text);
|
|
215
230
|
}
|
|
216
|
-
|
|
217
|
-
|
|
231
|
+
// First paint loads synchronously so the initial entry can be revealed
|
|
232
|
+
// immediately; later full reloads from the poll may go async.
|
|
233
|
+
this.#refresh(false);
|
|
234
|
+
this.#pollTimer = setInterval(() => this.#refresh(true), POLL_MS);
|
|
218
235
|
this.#pollTimer.unref?.();
|
|
219
236
|
}
|
|
220
237
|
|
|
@@ -243,12 +260,13 @@ export class AgentTranscriptViewer implements Component {
|
|
|
243
260
|
// ========================================================================
|
|
244
261
|
|
|
245
262
|
/** Refresh the transcript from a local file or remote host. */
|
|
246
|
-
#refresh(): void {
|
|
263
|
+
#refresh(allowAsync: boolean): void {
|
|
247
264
|
if (this.#disposed) return;
|
|
248
265
|
if (this.#deps.remote) {
|
|
249
266
|
this.#fetchRemote();
|
|
250
267
|
return;
|
|
251
268
|
}
|
|
269
|
+
if (this.#localLoadInFlight) return;
|
|
252
270
|
const sessionFile = this.#deps.registry.get(this.#deps.agentId)?.sessionFile;
|
|
253
271
|
if (!sessionFile) {
|
|
254
272
|
this.#clearLocal("none");
|
|
@@ -262,14 +280,29 @@ export class AgentTranscriptViewer implements Component {
|
|
|
262
280
|
return;
|
|
263
281
|
}
|
|
264
282
|
const state = this.#localState;
|
|
265
|
-
if (state
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
283
|
+
if (state) {
|
|
284
|
+
// Idle fast path: an unchanged identity/size/mtime costs one stat;
|
|
285
|
+
// sentinels are re-read only every Nth idle poll so a rewrite that
|
|
286
|
+
// keeps size and mtime is still noticed.
|
|
287
|
+
if (
|
|
288
|
+
state.path === sessionFile &&
|
|
289
|
+
state.dev === stat.dev &&
|
|
290
|
+
state.ino === stat.ino &&
|
|
291
|
+
stat.size === state.size &&
|
|
292
|
+
stat.mtimeMs === state.mtimeMs
|
|
293
|
+
) {
|
|
294
|
+
if (++this.#idlePolls < IDLE_SENTINEL_CHECK_EVERY) return;
|
|
295
|
+
this.#idlePolls = 0;
|
|
296
|
+
if (this.#canAppendLocal(sessionFile, stat, state)) return;
|
|
297
|
+
this.#loadLocalFull(sessionFile, stat, allowAsync);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (stat.size > state.size && this.#canAppendLocal(sessionFile, stat, state)) {
|
|
301
|
+
this.#appendLocal(sessionFile, stat, state, allowAsync);
|
|
269
302
|
return;
|
|
270
303
|
}
|
|
271
304
|
}
|
|
272
|
-
this.#loadLocalFull(sessionFile, stat);
|
|
305
|
+
this.#loadLocalFull(sessionFile, stat, allowAsync);
|
|
273
306
|
}
|
|
274
307
|
|
|
275
308
|
#clearLocal(reason: string): void {
|
|
@@ -303,7 +336,23 @@ export class AgentTranscriptViewer implements Component {
|
|
|
303
336
|
return true;
|
|
304
337
|
}
|
|
305
338
|
|
|
306
|
-
#loadLocalFull(sessionFile: string, stat: fs.Stats): void {
|
|
339
|
+
#loadLocalFull(sessionFile: string, stat: fs.Stats, allowAsync: boolean): void {
|
|
340
|
+
const promises = allowAsync ? this.#deps.transcript.fs.promises : undefined;
|
|
341
|
+
if (promises) {
|
|
342
|
+
this.#localLoadInFlight = true;
|
|
343
|
+
void promises
|
|
344
|
+
.readFile(sessionFile)
|
|
345
|
+
.then(data => {
|
|
346
|
+
this.#localLoadInFlight = false;
|
|
347
|
+
if (!this.#disposed) this.#applyLocalFull(sessionFile, stat, data);
|
|
348
|
+
})
|
|
349
|
+
.catch((err: unknown) => {
|
|
350
|
+
// Leave #localState unchanged so a transient read error retries next poll.
|
|
351
|
+
this.#localLoadInFlight = false;
|
|
352
|
+
logger.debug("transcript viewer: read failed", { err: String(err) });
|
|
353
|
+
});
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
307
356
|
let data: Buffer;
|
|
308
357
|
try {
|
|
309
358
|
data = this.#deps.transcript.fs.readFileSync(sessionFile);
|
|
@@ -312,6 +361,10 @@ export class AgentTranscriptViewer implements Component {
|
|
|
312
361
|
logger.debug("transcript viewer: read failed", { err: String(err) });
|
|
313
362
|
return;
|
|
314
363
|
}
|
|
364
|
+
this.#applyLocalFull(sessionFile, stat, data);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
#applyLocalFull(sessionFile: string, stat: fs.Stats, data: Buffer): void {
|
|
315
368
|
// The file may have grown between the earlier `statSync` and this read.
|
|
316
369
|
// Anchor the tail cursor to what we actually consumed so the next poll's
|
|
317
370
|
// `#appendLocal` never re-renders bytes already in the rebuilt transcript;
|
|
@@ -345,7 +398,7 @@ export class AgentTranscriptViewer implements Component {
|
|
|
345
398
|
this.#rebuild(this.#extractMessages(this.#deps.transcript.parseEntries(complete)));
|
|
346
399
|
}
|
|
347
400
|
|
|
348
|
-
#appendLocal(sessionFile: string, stat: fs.Stats, state: LocalTranscriptState): void {
|
|
401
|
+
#appendLocal(sessionFile: string, stat: fs.Stats, state: LocalTranscriptState, allowAsync: boolean): void {
|
|
349
402
|
let chunk: string;
|
|
350
403
|
try {
|
|
351
404
|
chunk = readFileRangeSync(
|
|
@@ -356,7 +409,7 @@ export class AgentTranscriptViewer implements Component {
|
|
|
356
409
|
).toString("utf-8");
|
|
357
410
|
} catch (err) {
|
|
358
411
|
logger.debug("transcript viewer: tail read failed", { err: String(err) });
|
|
359
|
-
this.#loadLocalFull(sessionFile, stat);
|
|
412
|
+
this.#loadLocalFull(sessionFile, stat, allowAsync);
|
|
360
413
|
return;
|
|
361
414
|
}
|
|
362
415
|
const combined = state.pending + chunk;
|
|
@@ -371,7 +424,7 @@ export class AgentTranscriptViewer implements Component {
|
|
|
371
424
|
// File unlinked/rotated mid-poll: fall back to a guarded full reload
|
|
372
425
|
// instead of letting the open escape the poll timer.
|
|
373
426
|
logger.debug("transcript viewer: sentinel recompute failed", { err: String(err) });
|
|
374
|
-
this.#loadLocalFull(sessionFile, stat);
|
|
427
|
+
this.#loadLocalFull(sessionFile, stat, allowAsync);
|
|
375
428
|
return;
|
|
376
429
|
}
|
|
377
430
|
this.#localState = {
|
|
@@ -506,12 +559,7 @@ export class AgentTranscriptViewer implements Component {
|
|
|
506
559
|
}
|
|
507
560
|
|
|
508
561
|
if (matchesKey(data, "escape")) {
|
|
509
|
-
|
|
510
|
-
this.#editor.setText("");
|
|
511
|
-
this.#deps.requestRender();
|
|
512
|
-
return;
|
|
513
|
-
}
|
|
514
|
-
this.#deps.onClose();
|
|
562
|
+
this.#escape();
|
|
515
563
|
return;
|
|
516
564
|
}
|
|
517
565
|
|
|
@@ -594,6 +642,21 @@ export class AgentTranscriptViewer implements Component {
|
|
|
594
642
|
return lines;
|
|
595
643
|
}
|
|
596
644
|
|
|
645
|
+
/** The top-right `esc` runs Esc. */
|
|
646
|
+
handleNativeEvent(event: NativeUiEvent): void {
|
|
647
|
+
if (event.type === "action" && event.act === "close") this.#escape();
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/** Esc: clear a non-empty draft first, else close the viewer. */
|
|
651
|
+
#escape(): void {
|
|
652
|
+
if (this.#editor && this.#editor.getText().trim() !== "") {
|
|
653
|
+
this.#editor.setText("");
|
|
654
|
+
this.#deps.requestRender();
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
this.#deps.onClose();
|
|
658
|
+
}
|
|
659
|
+
|
|
597
660
|
/**
|
|
598
661
|
* Header, transcript body (the builder's container, which describes its own
|
|
599
662
|
* blocks), notice, message editor, stats and key hints. Scrolling is the
|
|
@@ -622,6 +685,8 @@ export class AgentTranscriptViewer implements Component {
|
|
|
622
685
|
|
|
623
686
|
const id = this.#deps.agentId;
|
|
624
687
|
const children: NativeChild[] = [];
|
|
688
|
+
// Top row: the agent's meta on the left, a clickable `esc` (close) on the right.
|
|
689
|
+
const top: NativeChild[] = [];
|
|
625
690
|
if (ref) {
|
|
626
691
|
const kindTag = ref.parentId ? `${ref.kind} ${theme.sep.dot} of ${ref.parentId}` : ref.kind;
|
|
627
692
|
const meta: NativeChild[] = [
|
|
@@ -630,8 +695,12 @@ export class AgentTranscriptViewer implements Component {
|
|
|
630
695
|
text([span(kindTag, "dim")], { truncate: "end" }),
|
|
631
696
|
];
|
|
632
697
|
if (this.#model) meta.push(text([span(this.#model, "muted")], { truncate: "end" }));
|
|
633
|
-
|
|
698
|
+
top.push(node("row", { gap: "sm", align: "center", grow: 1, min: { w: 0 } }, meta, "meta"));
|
|
699
|
+
} else {
|
|
700
|
+
top.push(node("spacer", { grow: 1 }));
|
|
634
701
|
}
|
|
702
|
+
top.push(escCloseButton());
|
|
703
|
+
children.push(node("row", { gap: "md", align: "center" }, top, "top"));
|
|
635
704
|
children.push(
|
|
636
705
|
placeholder === undefined
|
|
637
706
|
? node("col", { grow: 1 }, [this.#builder.container], "transcript")
|
|
@@ -694,7 +763,6 @@ export class AgentTranscriptViewer implements Component {
|
|
|
694
763
|
children.push(
|
|
695
764
|
hintsRow([
|
|
696
765
|
this.#editor ? actionHint("tui.input.submit", "send") : undefined,
|
|
697
|
-
{ keys: ["escape"], label: "close" },
|
|
698
766
|
{ keys: [this.#deps.expandKeys[0] ?? "ctrl+o"], label: "expand" },
|
|
699
767
|
]),
|
|
700
768
|
);
|