@esso0428/pi-subagents 0.17.15 → 0.17.17
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 +11 -0
- package/ROADMAP.md +87 -0
- package/docs/post-0.17.6-agents-roster-viewer-spec.md +42 -0
- package/docs/post-0.17.6-durable-history-spec.md +41 -0
- package/docs/post-0.17.6-feature-specs.md +50 -0
- package/docs/post-0.17.6-nested-agents-spec.md +38 -0
- package/docs/post-0.17.6-recovery-shutdown-spec.md +46 -0
- package/docs/post-0.17.6-ui-latency-baseline-spec.md +46 -0
- package/docs/post-0.17.6-workflow-rpc-lifecycle-spec.md +54 -0
- package/package.json +2 -3
- package/src/agent-history.ts +2 -54
- package/src/agent-manager.ts +406 -1288
- package/src/agent-runner.ts +27 -251
- package/src/agent-types.ts +32 -188
- package/src/cross-extension-rpc.ts +20 -96
- package/src/custom-agents.ts +13 -170
- package/src/index.ts +480 -1920
- package/src/invocation-config.ts +3 -118
- package/src/model-resolver.ts +0 -18
- package/src/output-file.ts +6 -61
- package/src/prompts.ts +2 -45
- package/src/schedule.ts +14 -35
- package/src/settings.ts +2 -301
- package/src/status-note.ts +1 -66
- package/src/types.ts +10 -177
- package/src/ui/agent-widget.ts +57 -278
- package/src/ui/conversation-blocks.ts +0 -6
- package/src/ui/conversation-timeline.ts +25 -139
- package/src/ui/conversation-viewer.ts +48 -212
- package/src/ui/schedule-menu.ts +8 -9
- package/src/usage.ts +2 -109
- package/src/worktree.ts +55 -69
- package/dist/abortable.d.ts +0 -13
- package/dist/abortable.d.ts.map +0 -1
- package/dist/abortable.js +0 -43
- package/dist/abortable.js.map +0 -1
- package/dist/agent-color.d.ts +0 -36
- package/dist/agent-color.d.ts.map +0 -1
- package/dist/agent-color.js +0 -124
- package/dist/agent-color.js.map +0 -1
- package/dist/agent-file-toggle.d.ts +0 -126
- package/dist/agent-file-toggle.d.ts.map +0 -1
- package/dist/agent-file-toggle.js +0 -259
- package/dist/agent-file-toggle.js.map +0 -1
- package/dist/agent-history-list.d.ts +0 -19
- package/dist/agent-history-list.d.ts.map +0 -1
- package/dist/agent-history-list.js +0 -69
- package/dist/agent-history-list.js.map +0 -1
- package/dist/agent-history.d.ts +0 -35
- package/dist/agent-history.d.ts.map +0 -1
- package/dist/agent-history.js +0 -188
- package/dist/agent-history.js.map +0 -1
- package/dist/agent-manager.d.ts +0 -503
- package/dist/agent-manager.d.ts.map +0 -1
- package/dist/agent-manager.js +0 -1572
- package/dist/agent-manager.js.map +0 -1
- package/dist/agent-recovery.d.ts +0 -37
- package/dist/agent-recovery.d.ts.map +0 -1
- package/dist/agent-recovery.js +0 -171
- package/dist/agent-recovery.js.map +0 -1
- package/dist/agent-runner.d.ts +0 -303
- package/dist/agent-runner.d.ts.map +0 -1
- package/dist/agent-runner.js +0 -1003
- package/dist/agent-runner.js.map +0 -1
- package/dist/agent-types.d.ts +0 -120
- package/dist/agent-types.d.ts.map +0 -1
- package/dist/agent-types.js +0 -301
- package/dist/agent-types.js.map +0 -1
- package/dist/child-context.d.ts +0 -3
- package/dist/child-context.d.ts.map +0 -1
- package/dist/child-context.js +0 -13
- package/dist/child-context.js.map +0 -1
- package/dist/context.d.ts +0 -13
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -57
- package/dist/context.js.map +0 -1
- package/dist/cross-extension-rpc.d.ts +0 -67
- package/dist/cross-extension-rpc.d.ts.map +0 -1
- package/dist/cross-extension-rpc.js +0 -139
- package/dist/cross-extension-rpc.js.map +0 -1
- package/dist/custom-agents.d.ts +0 -55
- package/dist/custom-agents.d.ts.map +0 -1
- package/dist/custom-agents.js +0 -309
- package/dist/custom-agents.js.map +0 -1
- package/dist/default-agents.d.ts +0 -8
- package/dist/default-agents.d.ts.map +0 -1
- package/dist/default-agents.js +0 -123
- package/dist/default-agents.js.map +0 -1
- package/dist/enabled-models.d.ts +0 -50
- package/dist/enabled-models.d.ts.map +0 -1
- package/dist/enabled-models.js +0 -146
- package/dist/enabled-models.js.map +0 -1
- package/dist/env.d.ts +0 -7
- package/dist/env.d.ts.map +0 -1
- package/dist/env.js +0 -29
- package/dist/env.js.map +0 -1
- package/dist/group-join.d.ts +0 -33
- package/dist/group-join.d.ts.map +0 -1
- package/dist/group-join.js +0 -117
- package/dist/group-join.js.map +0 -1
- package/dist/index.d.ts +0 -51
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -3687
- package/dist/index.js.map +0 -1
- package/dist/invocation-config.d.ts +0 -108
- package/dist/invocation-config.d.ts.map +0 -1
- package/dist/invocation-config.js +0 -84
- package/dist/invocation-config.js.map +0 -1
- package/dist/memory.d.ts +0 -54
- package/dist/memory.d.ts.map +0 -1
- package/dist/memory.js +0 -166
- package/dist/memory.js.map +0 -1
- package/dist/mention-clone.d.ts +0 -88
- package/dist/mention-clone.d.ts.map +0 -1
- package/dist/mention-clone.js +0 -154
- package/dist/mention-clone.js.map +0 -1
- package/dist/mention.d.ts +0 -82
- package/dist/mention.d.ts.map +0 -1
- package/dist/mention.js +0 -132
- package/dist/mention.js.map +0 -1
- package/dist/model-resolver.d.ts +0 -37
- package/dist/model-resolver.d.ts.map +0 -1
- package/dist/model-resolver.js +0 -96
- package/dist/model-resolver.js.map +0 -1
- package/dist/model-scope.d.ts +0 -50
- package/dist/model-scope.d.ts.map +0 -1
- package/dist/model-scope.js +0 -49
- package/dist/model-scope.js.map +0 -1
- package/dist/nested-tools.d.ts +0 -57
- package/dist/nested-tools.d.ts.map +0 -1
- package/dist/nested-tools.js +0 -301
- package/dist/nested-tools.js.map +0 -1
- package/dist/nico-overrides.d.ts +0 -54
- package/dist/nico-overrides.d.ts.map +0 -1
- package/dist/nico-overrides.js +0 -170
- package/dist/nico-overrides.js.map +0 -1
- package/dist/output-file.d.ts +0 -44
- package/dist/output-file.d.ts.map +0 -1
- package/dist/output-file.js +0 -156
- package/dist/output-file.js.map +0 -1
- package/dist/prompts.d.ts +0 -56
- package/dist/prompts.d.ts.map +0 -1
- package/dist/prompts.js +0 -92
- package/dist/prompts.js.map +0 -1
- package/dist/schedule-store.d.ts +0 -39
- package/dist/schedule-store.d.ts.map +0 -1
- package/dist/schedule-store.js +0 -156
- package/dist/schedule-store.js.map +0 -1
- package/dist/schedule.d.ts +0 -110
- package/dist/schedule.d.ts.map +0 -1
- package/dist/schedule.js +0 -360
- package/dist/schedule.js.map +0 -1
- package/dist/settings.d.ts +0 -354
- package/dist/settings.d.ts.map +0 -1
- package/dist/settings.js +0 -247
- package/dist/settings.js.map +0 -1
- package/dist/skill-loader.d.ts +0 -25
- package/dist/skill-loader.d.ts.map +0 -1
- package/dist/skill-loader.js +0 -94
- package/dist/skill-loader.js.map +0 -1
- package/dist/status-note.d.ts +0 -62
- package/dist/status-note.d.ts.map +0 -1
- package/dist/status-note.js +0 -86
- package/dist/status-note.js.map +0 -1
- package/dist/structured-output.d.ts +0 -62
- package/dist/structured-output.d.ts.map +0 -1
- package/dist/structured-output.js +0 -113
- package/dist/structured-output.js.map +0 -1
- package/dist/types.d.ts +0 -372
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -6
- package/dist/types.js.map +0 -1
- package/dist/ui/agent-mention.d.ts +0 -83
- package/dist/ui/agent-mention.d.ts.map +0 -1
- package/dist/ui/agent-mention.js +0 -188
- package/dist/ui/agent-mention.js.map +0 -1
- package/dist/ui/agent-widget.d.ts +0 -241
- package/dist/ui/agent-widget.d.ts.map +0 -1
- package/dist/ui/agent-widget.js +0 -992
- package/dist/ui/agent-widget.js.map +0 -1
- package/dist/ui/ccstyle/diff/ansi-utils.d.ts +0 -11
- package/dist/ui/ccstyle/diff/ansi-utils.d.ts.map +0 -1
- package/dist/ui/ccstyle/diff/ansi-utils.js +0 -145
- package/dist/ui/ccstyle/diff/ansi-utils.js.map +0 -1
- package/dist/ui/ccstyle/diff/diff-presentation.d.ts +0 -12
- package/dist/ui/ccstyle/diff/diff-presentation.d.ts.map +0 -1
- package/dist/ui/ccstyle/diff/diff-presentation.js +0 -48
- package/dist/ui/ccstyle/diff/diff-presentation.js.map +0 -1
- package/dist/ui/ccstyle/diff/diff-renderer.d.ts +0 -46
- package/dist/ui/ccstyle/diff/diff-renderer.d.ts.map +0 -1
- package/dist/ui/ccstyle/diff/diff-renderer.js +0 -2049
- package/dist/ui/ccstyle/diff/diff-renderer.js.map +0 -1
- package/dist/ui/ccstyle/diff/line-width-safety.d.ts +0 -12
- package/dist/ui/ccstyle/diff/line-width-safety.d.ts.map +0 -1
- package/dist/ui/ccstyle/diff/line-width-safety.js +0 -58
- package/dist/ui/ccstyle/diff/line-width-safety.js.map +0 -1
- package/dist/ui/ccstyle/diff/render-utils.d.ts +0 -6
- package/dist/ui/ccstyle/diff/render-utils.d.ts.map +0 -1
- package/dist/ui/ccstyle/diff/render-utils.js +0 -25
- package/dist/ui/ccstyle/diff/render-utils.js.map +0 -1
- package/dist/ui/ccstyle/diff/shiki-highlight.d.ts +0 -19
- package/dist/ui/ccstyle/diff/shiki-highlight.d.ts.map +0 -1
- package/dist/ui/ccstyle/diff/shiki-highlight.js +0 -85
- package/dist/ui/ccstyle/diff/shiki-highlight.js.map +0 -1
- package/dist/ui/ccstyle/diff/types.d.ts +0 -22
- package/dist/ui/ccstyle/diff/types.d.ts.map +0 -1
- package/dist/ui/ccstyle/diff/types.js +0 -10
- package/dist/ui/ccstyle/diff/types.js.map +0 -1
- package/dist/ui/ccstyle/diff/write-display-utils.d.ts +0 -2
- package/dist/ui/ccstyle/diff/write-display-utils.d.ts.map +0 -1
- package/dist/ui/ccstyle/diff/write-display-utils.js +0 -12
- package/dist/ui/ccstyle/diff/write-display-utils.js.map +0 -1
- package/dist/ui/ccstyle/tool-renderer.d.ts +0 -17
- package/dist/ui/ccstyle/tool-renderer.d.ts.map +0 -1
- package/dist/ui/ccstyle/tool-renderer.js +0 -74
- package/dist/ui/ccstyle/tool-renderer.js.map +0 -1
- package/dist/ui/ccstyle/tool-result.d.ts +0 -44
- package/dist/ui/ccstyle/tool-result.d.ts.map +0 -1
- package/dist/ui/ccstyle/tool-result.js +0 -423
- package/dist/ui/ccstyle/tool-result.js.map +0 -1
- package/dist/ui/conversation-blocks.d.ts +0 -46
- package/dist/ui/conversation-blocks.d.ts.map +0 -1
- package/dist/ui/conversation-blocks.js +0 -313
- package/dist/ui/conversation-blocks.js.map +0 -1
- package/dist/ui/conversation-nvim.d.ts +0 -8
- package/dist/ui/conversation-nvim.d.ts.map +0 -1
- package/dist/ui/conversation-nvim.js +0 -117
- package/dist/ui/conversation-nvim.js.map +0 -1
- package/dist/ui/conversation-role.d.ts +0 -13
- package/dist/ui/conversation-role.d.ts.map +0 -1
- package/dist/ui/conversation-role.js +0 -39
- package/dist/ui/conversation-role.js.map +0 -1
- package/dist/ui/conversation-search.d.ts +0 -39
- package/dist/ui/conversation-search.d.ts.map +0 -1
- package/dist/ui/conversation-search.js +0 -124
- package/dist/ui/conversation-search.js.map +0 -1
- package/dist/ui/conversation-timeline.d.ts +0 -102
- package/dist/ui/conversation-timeline.d.ts.map +0 -1
- package/dist/ui/conversation-timeline.js +0 -555
- package/dist/ui/conversation-timeline.js.map +0 -1
- package/dist/ui/conversation-viewer.d.ts +0 -138
- package/dist/ui/conversation-viewer.d.ts.map +0 -1
- package/dist/ui/conversation-viewer.js +0 -1175
- package/dist/ui/conversation-viewer.js.map +0 -1
- package/dist/ui/schedule-menu.d.ts +0 -17
- package/dist/ui/schedule-menu.d.ts.map +0 -1
- package/dist/ui/schedule-menu.js +0 -95
- package/dist/ui/schedule-menu.js.map +0 -1
- package/dist/ui/select-item.d.ts +0 -28
- package/dist/ui/select-item.d.ts.map +0 -1
- package/dist/ui/select-item.js +0 -35
- package/dist/ui/select-item.js.map +0 -1
- package/dist/ui/viewer-keys.d.ts +0 -21
- package/dist/ui/viewer-keys.d.ts.map +0 -1
- package/dist/ui/viewer-keys.js +0 -18
- package/dist/ui/viewer-keys.js.map +0 -1
- package/dist/ui/workflow-card.d.ts +0 -176
- package/dist/ui/workflow-card.d.ts.map +0 -1
- package/dist/ui/workflow-card.js +0 -333
- package/dist/ui/workflow-card.js.map +0 -1
- package/dist/ui/workflow-dialog.d.ts +0 -306
- package/dist/ui/workflow-dialog.d.ts.map +0 -1
- package/dist/ui/workflow-dialog.js +0 -844
- package/dist/ui/workflow-dialog.js.map +0 -1
- package/dist/ui/workflow-menu.d.ts +0 -42
- package/dist/ui/workflow-menu.d.ts.map +0 -1
- package/dist/ui/workflow-menu.js +0 -127
- package/dist/ui/workflow-menu.js.map +0 -1
- package/dist/usage.d.ts +0 -136
- package/dist/usage.d.ts.map +0 -1
- package/dist/usage.js +0 -121
- package/dist/usage.js.map +0 -1
- package/dist/workflow/collisions.d.ts +0 -96
- package/dist/workflow/collisions.d.ts.map +0 -1
- package/dist/workflow/collisions.js +0 -89
- package/dist/workflow/collisions.js.map +0 -1
- package/dist/workflow/entry.d.ts +0 -33
- package/dist/workflow/entry.d.ts.map +0 -1
- package/dist/workflow/entry.js +0 -30
- package/dist/workflow/entry.js.map +0 -1
- package/dist/workflow/host.d.ts +0 -63
- package/dist/workflow/host.d.ts.map +0 -1
- package/dist/workflow/host.js +0 -363
- package/dist/workflow/host.js.map +0 -1
- package/dist/workflow/journal.d.ts +0 -98
- package/dist/workflow/journal.d.ts.map +0 -1
- package/dist/workflow/journal.js +0 -121
- package/dist/workflow/journal.js.map +0 -1
- package/dist/workflow/json-schema.d.ts +0 -52
- package/dist/workflow/json-schema.d.ts.map +0 -1
- package/dist/workflow/json-schema.js +0 -112
- package/dist/workflow/json-schema.js.map +0 -1
- package/dist/workflow/meta.d.ts +0 -68
- package/dist/workflow/meta.d.ts.map +0 -1
- package/dist/workflow/meta.js +0 -318
- package/dist/workflow/meta.js.map +0 -1
- package/dist/workflow/progress.d.ts +0 -225
- package/dist/workflow/progress.d.ts.map +0 -1
- package/dist/workflow/progress.js +0 -362
- package/dist/workflow/progress.js.map +0 -1
- package/dist/workflow/runtime.d.ts +0 -335
- package/dist/workflow/runtime.d.ts.map +0 -1
- package/dist/workflow/runtime.js +0 -831
- package/dist/workflow/runtime.js.map +0 -1
- package/dist/workflow/saved.d.ts +0 -91
- package/dist/workflow/saved.d.ts.map +0 -1
- package/dist/workflow/saved.js +0 -204
- package/dist/workflow/saved.js.map +0 -1
- package/dist/workflow/task.d.ts +0 -137
- package/dist/workflow/task.d.ts.map +0 -1
- package/dist/workflow/task.js +0 -208
- package/dist/workflow/task.js.map +0 -1
- package/dist/workflow/tool-description.d.ts +0 -39
- package/dist/workflow/tool-description.d.ts.map +0 -1
- package/dist/workflow/tool-description.js +0 -200
- package/dist/workflow/tool-description.js.map +0 -1
- package/dist/workflow/worker-source.d.ts +0 -48
- package/dist/workflow/worker-source.d.ts.map +0 -1
- package/dist/workflow/worker-source.js +0 -779
- package/dist/workflow/worker-source.js.map +0 -1
- package/dist/worktree.d.ts +0 -53
- package/dist/worktree.d.ts.map +0 -1
- package/dist/worktree.js +0 -165
- package/dist/worktree.js.map +0 -1
- package/dist/write-execution.d.ts +0 -42
- package/dist/write-execution.d.ts.map +0 -1
- package/dist/write-execution.js +0 -138
- package/dist/write-execution.js.map +0 -1
- package/dist/xml.d.ts +0 -11
- package/dist/xml.d.ts.map +0 -1
- package/dist/xml.js +0 -13
- package/dist/xml.js.map +0 -1
- package/src/abortable.ts +0 -43
- package/src/agent-color.ts +0 -161
- package/src/agent-file-toggle.ts +0 -269
- package/src/child-context.ts +0 -15
- package/src/mention-clone.ts +0 -196
- package/src/mention.ts +0 -141
- package/src/model-scope.ts +0 -70
- package/src/nested-tools.ts +0 -424
- package/src/structured-output.ts +0 -130
- package/src/ui/agent-mention.ts +0 -216
- package/src/ui/select-item.ts +0 -45
- package/src/ui/workflow-card.ts +0 -470
- package/src/ui/workflow-dialog.ts +0 -1115
- package/src/ui/workflow-menu.ts +0 -166
- package/src/workflow/collisions.ts +0 -123
- package/src/workflow/entry.ts +0 -47
- package/src/workflow/host.ts +0 -403
- package/src/workflow/journal.ts +0 -164
- package/src/workflow/json-schema.ts +0 -128
- package/src/workflow/meta.ts +0 -325
- package/src/workflow/progress.ts +0 -550
- package/src/workflow/runtime.ts +0 -1219
- package/src/workflow/saved.ts +0 -217
- package/src/workflow/task.ts +0 -302
- package/src/workflow/tool-description.ts +0 -200
- package/src/workflow/worker-source.ts +0 -781
- package/src/xml.ts +0 -13
|
@@ -8,11 +8,10 @@
|
|
|
8
8
|
import type { AgentSession, ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
import { copyToClipboard } from "@earendil-works/pi-coding-agent";
|
|
10
10
|
import { type Component, Input, Key, matchesKey, type TUI, type TuiMouseEvent, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
11
|
-
import {
|
|
12
|
-
import
|
|
13
|
-
import { getLifetimeCost, getLifetimeTotal, getSessionContextPercent } from "../usage.js";
|
|
11
|
+
import type { AgentRecord } from "../types.js";
|
|
12
|
+
import { getLifetimeTotal, getSessionContextPercent } from "../usage.js";
|
|
14
13
|
import type { Theme } from "./agent-widget.js";
|
|
15
|
-
import { type AgentActivity, buildInvocationTags, describeActivity, fgPreservingNestedStyles,
|
|
14
|
+
import { type AgentActivity, buildInvocationTags, describeActivity, fgPreservingNestedStyles, formatDuration, formatSessionTokens, getDisplayName, getPromptModeLabel } from "./agent-widget.js";
|
|
16
15
|
import { createViewerCcstyleResult } from "./ccstyle/tool-result.js";
|
|
17
16
|
import { type ConversationBlock, formatConversationMessages } from "./conversation-blocks.js";
|
|
18
17
|
import { editLiveAssistantBlockInNvim, viewConversationBlockInNvim } from "./conversation-nvim.js";
|
|
@@ -26,16 +25,9 @@ const CHROME_LINES_BASE = 6;
|
|
|
26
25
|
const MIN_VIEWPORT = 3;
|
|
27
26
|
export const VIEWPORT_HEIGHT_PCT = 70;
|
|
28
27
|
const MAX_COPY_CHARS = 500_000;
|
|
29
|
-
export const RESULT_MAX_CHARS = 16_000;
|
|
30
28
|
const MATCH_BG = "\x1b[48;5;238m";
|
|
31
29
|
const MATCH_BG_CURRENT = "\x1b[48;5;220m\x1b[30m";
|
|
32
30
|
const BG_RESET = "\x1b[49m\x1b[39m";
|
|
33
|
-
const MARKDOWN_MODES: readonly ViewerMarkdownMode[] = ["off", "assistant", "all"];
|
|
34
|
-
const MARKDOWN_MODE_LABELS: Record<ViewerMarkdownMode, string> = {
|
|
35
|
-
off: "raw",
|
|
36
|
-
assistant: "md",
|
|
37
|
-
all: "md+",
|
|
38
|
-
};
|
|
39
31
|
|
|
40
32
|
function renderScrollbarCell(
|
|
41
33
|
theme: Theme,
|
|
@@ -86,42 +78,6 @@ export function createStaticConversationSource(
|
|
|
86
78
|
|
|
87
79
|
type RenderLine = TimelineRenderLine;
|
|
88
80
|
|
|
89
|
-
function makeRenderLine(fields: TimelineRenderLine): RenderLine {
|
|
90
|
-
return fields;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
class PublicRenderLines extends Array<string> {
|
|
94
|
-
static get [Symbol.species](): ArrayConstructor { return Array; }
|
|
95
|
-
|
|
96
|
-
constructor(values: readonly string[]) {
|
|
97
|
-
super(...values.map(value => Object.assign(new String(value), { text: value }) as unknown as string));
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
override includes(searchElement: string, fromIndex?: number): boolean {
|
|
101
|
-
return this.some((value, index) => {
|
|
102
|
-
if (index < (fromIndex ?? 0)) return false;
|
|
103
|
-
const text = String(value);
|
|
104
|
-
return text === searchElement || stripAnsi(text) === searchElement;
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
find(predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: unknown): string | undefined {
|
|
109
|
-
for (let index = 0; index < this.length; index++) {
|
|
110
|
-
const value = this[index]!;
|
|
111
|
-
if (predicate.call(thisArg, value, index, this)) return String(value);
|
|
112
|
-
}
|
|
113
|
-
return undefined;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function publicRenderLines(lines: readonly RenderLine[]): PublicRenderLines {
|
|
118
|
-
return new PublicRenderLines(lines.map(line => {
|
|
119
|
-
const plain = stripAnsi(line.text);
|
|
120
|
-
const truncationStart = plain.indexOf("... (truncated,");
|
|
121
|
-
return truncationStart >= 0 ? plain.slice(truncationStart).trimEnd() : line.text;
|
|
122
|
-
}));
|
|
123
|
-
}
|
|
124
|
-
|
|
125
81
|
const FULL_TOOL_PREVIEW_MAX_CHARS = 500_000;
|
|
126
82
|
|
|
127
83
|
class FullToolPreview implements Component {
|
|
@@ -256,7 +212,6 @@ export class ConversationViewer implements Component {
|
|
|
256
212
|
private cacheVersion = 0;
|
|
257
213
|
private cachedVersion = -1;
|
|
258
214
|
private sessionRefreshQueued = false;
|
|
259
|
-
private lastSessionKey = "";
|
|
260
215
|
private selectedBlockId: string | undefined;
|
|
261
216
|
private scrollOffset = 0;
|
|
262
217
|
private autoScroll = true;
|
|
@@ -279,11 +234,6 @@ export class ConversationViewer implements Component {
|
|
|
279
234
|
private steerHistoryDraft = "";
|
|
280
235
|
private timeline: ConversationTimeline;
|
|
281
236
|
private toolPreview: FullToolPreview | undefined;
|
|
282
|
-
private readonly operations: ConversationViewerOperations | undefined;
|
|
283
|
-
private readonly showCost: boolean;
|
|
284
|
-
private readonly viewerMarkdown: (() => ViewerMarkdownMode) | undefined;
|
|
285
|
-
private readonly onMarkdownMode: ((mode: ViewerMarkdownMode) => void) | undefined;
|
|
286
|
-
private markdownModeOverride: ViewerMarkdownMode | undefined;
|
|
287
237
|
|
|
288
238
|
constructor(
|
|
289
239
|
private tui: TUI,
|
|
@@ -298,22 +248,14 @@ export class ConversationViewer implements Component {
|
|
|
298
248
|
keybindings?: ViewerKeybindings,
|
|
299
249
|
/** Send a steering message to the agent. */
|
|
300
250
|
private onSteer?: (message: string) => void,
|
|
301
|
-
/**
|
|
302
|
-
|
|
303
|
-
viewerMarkdown?: () => ViewerMarkdownMode,
|
|
304
|
-
onMarkdownMode?: (mode: ViewerMarkdownMode) => void,
|
|
251
|
+
/** Optional clipboard/nvim operation context, appended for compatibility. */
|
|
252
|
+
private operations?: ConversationViewerOperations,
|
|
305
253
|
) {
|
|
306
|
-
this.operations = typeof showCostOrOperations === "object" ? showCostOrOperations : undefined;
|
|
307
|
-
this.showCost = typeof showCostOrOperations === "boolean" ? showCostOrOperations : false;
|
|
308
|
-
this.viewerMarkdown = viewerMarkdown;
|
|
309
|
-
this.onMarkdownMode = onMarkdownMode;
|
|
310
254
|
this.keys = createViewerKeys(keybindings);
|
|
311
255
|
this.blocks = formatConversationMessages(session.messages);
|
|
312
|
-
this.lastSessionKey = this.sessionKey();
|
|
313
256
|
this.timeline = new ConversationTimeline(tui, theme, {
|
|
314
|
-
cwd:
|
|
257
|
+
cwd: operations?.ctx.cwd,
|
|
315
258
|
record,
|
|
316
|
-
markdownMode: () => this.currentMarkdownMode(),
|
|
317
259
|
onChange: (change) => this.handleTimelineChange(change),
|
|
318
260
|
});
|
|
319
261
|
this.timeline.setBlocks(this.blocks);
|
|
@@ -343,11 +285,6 @@ export class ConversationViewer implements Component {
|
|
|
343
285
|
this.handleSearchInput(data);
|
|
344
286
|
return;
|
|
345
287
|
}
|
|
346
|
-
if (matchesKey(data, Key.ctrl("c"))) {
|
|
347
|
-
this.closed = true;
|
|
348
|
-
this.done(undefined);
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
288
|
if (matchesKey(data, "escape") || matchesKey(data, "q")) {
|
|
352
289
|
this.closed = true;
|
|
353
290
|
this.done(undefined);
|
|
@@ -359,11 +296,6 @@ export class ConversationViewer implements Component {
|
|
|
359
296
|
this.timeline.toggleFocusedTool();
|
|
360
297
|
return;
|
|
361
298
|
}
|
|
362
|
-
if (this.canSteer()) {
|
|
363
|
-
this.stopArmed = false;
|
|
364
|
-
this.openComposer();
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
367
299
|
}
|
|
368
300
|
if (data === "e" && this.canSteer()) {
|
|
369
301
|
this.stopArmed = false;
|
|
@@ -420,8 +352,6 @@ export class ConversationViewer implements Component {
|
|
|
420
352
|
this.gotoMatch(-1);
|
|
421
353
|
} else if (data === "M") {
|
|
422
354
|
void this.copyCurrentMessage();
|
|
423
|
-
} else if (data === "m") {
|
|
424
|
-
this.cycleMarkdownMode();
|
|
425
355
|
} else if (data === "o") {
|
|
426
356
|
void this.viewCurrentInNvim();
|
|
427
357
|
} else if (data === "O" && this.isLive()) {
|
|
@@ -464,13 +394,12 @@ export class ConversationViewer implements Component {
|
|
|
464
394
|
return previewChanged || closeChanged ? { handled: true, render: true } : undefined;
|
|
465
395
|
}
|
|
466
396
|
if (event.x === railX) return previewChanged || closeChanged ? { handled: true, render: true } : undefined;
|
|
467
|
-
this.buildContentLines(contentWidth);
|
|
468
|
-
const contentLines = this.cachedLines;
|
|
397
|
+
const contentLines = this.buildContentLines(contentWidth);
|
|
469
398
|
const viewportHeight = this.viewportHeight();
|
|
470
399
|
const maxScroll = Math.max(0, contentLines.length - viewportHeight);
|
|
471
400
|
if (this.autoScroll) this.scrollOffset = maxScroll;
|
|
472
401
|
const visibleStart = Math.min(this.scrollOffset, maxScroll);
|
|
473
|
-
const contentOrigin =
|
|
402
|
+
const contentOrigin = 3 + (this.invocationLine() ? 1 : 0);
|
|
474
403
|
const contentY = event.y - contentOrigin + visibleStart;
|
|
475
404
|
const timelineResult = this.timeline.handleMouse({ ...event, y: contentY });
|
|
476
405
|
return timelineResult ?? (previewChanged || closeChanged ? { handled: true, render: true } : undefined);
|
|
@@ -505,7 +434,7 @@ export class ConversationViewer implements Component {
|
|
|
505
434
|
const viewportHeight = this.viewportHeight();
|
|
506
435
|
const maxScroll = Math.max(0, contentLines.length - viewportHeight);
|
|
507
436
|
const visibleStart = Math.min(this.scrollOffset, maxScroll);
|
|
508
|
-
const contentOrigin =
|
|
437
|
+
const contentOrigin = 3 + (this.invocationLine() ? 1 : 0);
|
|
509
438
|
if (event.type === "wheel") {
|
|
510
439
|
if (event.y < contentOrigin || event.y >= contentOrigin + viewportHeight) return undefined;
|
|
511
440
|
this.scrollBy(Math.trunc(event.wheelDelta ?? 0));
|
|
@@ -562,12 +491,8 @@ export class ConversationViewer implements Component {
|
|
|
562
491
|
const pad = (s: string, len: number) => s + " ".repeat(Math.max(0, len - visibleWidth(s)));
|
|
563
492
|
const row = (content: string) => th.fg("border", "│") + " " + truncateToWidth(pad(content, innerW), innerW, "...", true) + " " + th.fg("border", "│");
|
|
564
493
|
const contentRow = (content: string, lineIndex: number, totalLines: number, viewportHeight: number, offset: number) => {
|
|
565
|
-
const
|
|
566
|
-
const text = isTruncationNote
|
|
567
|
-
? truncateToWidth(content, contentWidth, "...", true)
|
|
568
|
-
: truncateToWidth(pad(content, contentWidth), contentWidth, "...", true);
|
|
494
|
+
const text = truncateToWidth(pad(content, contentWidth), contentWidth, "...", true);
|
|
569
495
|
const rail = renderScrollbarCell(th, totalLines, viewportHeight, offset, lineIndex, false);
|
|
570
|
-
if (isTruncationNote) return th.fg("border", "│") + " " + text + th.fg("border", "│");
|
|
571
496
|
return th.fg("border", "│") + " " + text + rail + " " + th.fg("border", "│");
|
|
572
497
|
};
|
|
573
498
|
const hrTop = th.fg("border", `╭${"─".repeat(Math.max(0, width - 2))}╮`);
|
|
@@ -575,6 +500,7 @@ export class ConversationViewer implements Component {
|
|
|
575
500
|
const hrMid = row(th.fg("dim", "─".repeat(innerW)));
|
|
576
501
|
|
|
577
502
|
lines.push(hrTop);
|
|
503
|
+
const name = getDisplayName(this.record.type);
|
|
578
504
|
const modeLabel = getPromptModeLabel(this.record.type);
|
|
579
505
|
const modeTag = modeLabel ? ` ${th.fg("dim", `(${modeLabel})`)}` : "";
|
|
580
506
|
const statusIcon = this.record.status === "running" ? th.fg("accent", "●") : this.record.status === "completed" ? th.fg("success", "✓") : this.record.status === "error" ? th.fg("error", "✗") : th.fg("dim", "○");
|
|
@@ -582,16 +508,12 @@ export class ConversationViewer implements Component {
|
|
|
582
508
|
const headerParts: string[] = [duration];
|
|
583
509
|
const toolUses = this.activity?.toolUses ?? this.record.toolUses;
|
|
584
510
|
if (toolUses > 0) headerParts.unshift(`${toolUses} tool${toolUses === 1 ? "" : "s"}`);
|
|
585
|
-
const
|
|
586
|
-
const tokens = getLifetimeTotal(usage);
|
|
511
|
+
const tokens = getLifetimeTotal(this.activity?.lifetimeUsage);
|
|
587
512
|
if (tokens > 0) {
|
|
588
|
-
const percent = getSessionContextPercent(this.activity?.session
|
|
589
|
-
|
|
590
|
-
const costText = this.showCost ? formatCost(getLifetimeCost(usage)) : "";
|
|
591
|
-
headerParts.push(costText ? `${tokenText} ${costText}` : tokenText);
|
|
513
|
+
const percent = getSessionContextPercent(this.activity?.session);
|
|
514
|
+
headerParts.push(formatSessionTokens(tokens, percent, th, this.record.compactionCount));
|
|
592
515
|
}
|
|
593
|
-
const
|
|
594
|
-
const headerText = `${statusIcon} ${styledName}${modeTag} ${th.fg("muted", this.record.description)} ${th.fg("dim", "·")} ${fgPreservingNestedStyles(th, "dim", headerParts.join(" · "))}`;
|
|
516
|
+
const headerText = `${statusIcon} ${th.bold(name)}${modeTag} ${th.fg("muted", this.record.description)} ${th.fg("dim", "·")} ${fgPreservingNestedStyles(th, "dim", headerParts.join(" · "))}`;
|
|
595
517
|
const previewLabel = this.hoveredPreview ? th.fg("text", th.bold("[preview]")) : th.fg("dim", "[preview]");
|
|
596
518
|
const closeLabel = this.hoveredClose ? th.fg("text", th.bold("[Esc]")) : th.fg("dim", "[Esc]");
|
|
597
519
|
const actionsWidth = visibleWidth(previewLabel) + 1 + visibleWidth(closeLabel);
|
|
@@ -599,11 +521,10 @@ export class ConversationViewer implements Component {
|
|
|
599
521
|
const headerGap = Math.max(1, innerW - visibleWidth(headerLeft) - actionsWidth);
|
|
600
522
|
lines.push(row(headerLeft + " ".repeat(headerGap) + previewLabel + " " + closeLabel));
|
|
601
523
|
const invocationLine = this.invocationLine();
|
|
602
|
-
lines.push(row(invocationLine
|
|
524
|
+
if (invocationLine) lines.push(row(invocationLine));
|
|
603
525
|
lines.push(hrMid);
|
|
604
526
|
|
|
605
|
-
this.buildContentLines(contentWidth);
|
|
606
|
-
const contentLines = this.cachedLines;
|
|
527
|
+
const contentLines = this.buildContentLines(contentWidth);
|
|
607
528
|
const viewportHeight = this.viewportHeight();
|
|
608
529
|
const maxScroll = Math.max(0, contentLines.length - viewportHeight);
|
|
609
530
|
if (this.autoScroll) this.scrollOffset = maxScroll;
|
|
@@ -617,7 +538,7 @@ export class ConversationViewer implements Component {
|
|
|
617
538
|
const block = this.cachedMessageBlocks[this.currentMessageIndex()];
|
|
618
539
|
if (block) {
|
|
619
540
|
const header = renderConversationRoleHeader(block, this.theme);
|
|
620
|
-
displayed[0] =
|
|
541
|
+
displayed[0] = { text: ` ${header}`, plain: header, blockIndex: currentBlockIdx, railable: true };
|
|
621
542
|
}
|
|
622
543
|
}
|
|
623
544
|
for (let i = 0; i < displayed.length; i++) {
|
|
@@ -633,10 +554,7 @@ export class ConversationViewer implements Component {
|
|
|
633
554
|
const railGlyph = currentBlock?.kind === "tool" ? "▌" : "▎";
|
|
634
555
|
text = text.startsWith(" ") ? th.fg(railColor, railGlyph) + text.slice(1) : th.fg(railColor, railGlyph) + text;
|
|
635
556
|
}
|
|
636
|
-
|
|
637
|
-
lines.push(plainText.includes("... (truncated,")
|
|
638
|
-
? th.fg("border", "│") + " " + text + th.fg("border", "│")
|
|
639
|
-
: contentRow(text, i, contentLines.length, viewportHeight, visibleStart));
|
|
557
|
+
lines.push(contentRow(text, i, contentLines.length, viewportHeight, visibleStart));
|
|
640
558
|
}
|
|
641
559
|
for (let i = displayed.length; i < viewportHeight; i++) {
|
|
642
560
|
lines.push(contentRow("", i, contentLines.length, viewportHeight, visibleStart));
|
|
@@ -657,21 +575,14 @@ export class ConversationViewer implements Component {
|
|
|
657
575
|
lines.push(row(truncateToWidth(th.fg("accent", query) + th.fg("dim", matchCount) + hints, innerW, "...", true)));
|
|
658
576
|
} else {
|
|
659
577
|
const actions: string[] = [];
|
|
660
|
-
if (this.canSteer()) actions.push(th.fg("dim",
|
|
578
|
+
if (this.canSteer()) actions.push(th.fg("dim", "e steer"));
|
|
661
579
|
if (this.isStoppable()) actions.push(this.stopArmed ? th.fg("error", "x again to STOP") : th.fg("dim", "x stop"));
|
|
662
580
|
const editHint = this.isLive() ? " O" : "";
|
|
663
|
-
const
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
`${markdownHint} · M o${editHint} /n N · Esc close`,
|
|
669
|
-
]
|
|
670
|
-
: [
|
|
671
|
-
`j/k scroll · J/K messages · [/] tools · w preview · g/G t M o${editHint} /n N q`,
|
|
672
|
-
`[/] tools · w preview · M o${editHint} /n N q`,
|
|
673
|
-
`[/] tools · w · M o${editHint} /n N q`,
|
|
674
|
-
];
|
|
581
|
+
const shortcutHints = [
|
|
582
|
+
`j/k scroll · J/K messages · [/] tools · w preview · g/G t M o${editHint} /n N q`,
|
|
583
|
+
`[/] tools · w preview · M o${editHint} /n N q`,
|
|
584
|
+
`[/] tools · w · M o${editHint} /n N q`,
|
|
585
|
+
];
|
|
675
586
|
const scrollPct = contentLines.length <= viewportHeight ? "100%" : `${Math.round(((visibleStart + viewportHeight) / contentLines.length) * 100)}%`;
|
|
676
587
|
const count = th.fg("dim", `${contentLines.length} lines · ${scrollPct}`);
|
|
677
588
|
const countWidth = visibleWidth(count);
|
|
@@ -712,7 +623,6 @@ export class ConversationViewer implements Component {
|
|
|
712
623
|
if (this.closed) return;
|
|
713
624
|
if (this.hasFocusedBlock) this.selectedBlockId = this.currentBlock()?.id;
|
|
714
625
|
this.blocks = formatConversationMessages(this.session.messages);
|
|
715
|
-
this.lastSessionKey = this.sessionKey();
|
|
716
626
|
this.timeline.setBlocks(this.blocks);
|
|
717
627
|
this.cacheVersion++;
|
|
718
628
|
this.invalidate();
|
|
@@ -731,7 +641,7 @@ export class ConversationViewer implements Component {
|
|
|
731
641
|
const end = Math.min(this.cachedLines.length, start + Math.max(0, range.height));
|
|
732
642
|
for (let index = start; index < end; index++) {
|
|
733
643
|
const line = snapshotLines[index];
|
|
734
|
-
if (line) this.cachedLines[index] =
|
|
644
|
+
if (line) this.cachedLines[index] = { ...line };
|
|
735
645
|
}
|
|
736
646
|
}
|
|
737
647
|
}
|
|
@@ -755,119 +665,45 @@ export class ConversationViewer implements Component {
|
|
|
755
665
|
}
|
|
756
666
|
|
|
757
667
|
private chromeLines(): number {
|
|
758
|
-
return CHROME_LINES_BASE + 1 + (this.composer ? 1 : 0);
|
|
668
|
+
return CHROME_LINES_BASE + (this.invocationLine() ? 1 : 0) + (this.composer ? 1 : 0);
|
|
759
669
|
}
|
|
760
670
|
|
|
761
671
|
private invocationLine(): string | undefined {
|
|
762
672
|
const liveModel = this.isLive() ? this.record.session?.model : undefined;
|
|
763
|
-
const
|
|
764
|
-
effectiveModelName?: string;
|
|
765
|
-
effectiveModelId?: string;
|
|
766
|
-
effectiveThinking?: EffectiveThinkingLevel;
|
|
767
|
-
}) | undefined;
|
|
768
|
-
const invocation = captured
|
|
673
|
+
const invocation = this.record.invocation
|
|
769
674
|
? {
|
|
770
|
-
...
|
|
771
|
-
...(
|
|
772
|
-
...(
|
|
773
|
-
...(captured.thinking === undefined && captured.effectiveThinking !== undefined && { thinking: captured.effectiveThinking }),
|
|
774
|
-
...(liveModel && { modelName: liveModel.name ?? liveModel.id, modelId: `${liveModel.provider}/${liveModel.id}` }),
|
|
775
|
-
...(this.isLive() && { thinking: this.record.session?.thinkingLevel }),
|
|
675
|
+
...this.record.invocation,
|
|
676
|
+
...(liveModel && { effectiveModelName: liveModel.name ?? liveModel.id }),
|
|
677
|
+
...(this.isLive() && { effectiveThinking: this.record.session?.thinkingLevel }),
|
|
776
678
|
}
|
|
777
|
-
: liveModel
|
|
778
|
-
? {
|
|
679
|
+
: liveModel || (this.isLive() && this.record.session?.thinkingLevel)
|
|
680
|
+
? {
|
|
681
|
+
effectiveModelName: liveModel?.name ?? liveModel?.id,
|
|
682
|
+
effectiveThinking: this.record.session?.thinkingLevel,
|
|
683
|
+
}
|
|
779
684
|
: undefined;
|
|
780
|
-
if (!invocation) return
|
|
781
|
-
const { modelName,
|
|
782
|
-
const effectiveModelName =
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
? `model: ${captured.effectiveModelName}`
|
|
786
|
-
: effectiveModelName;
|
|
787
|
-
return this.theme.fg("dim", ` ↳ ${[displayModel, ...tags].filter(Boolean).join(" · ")}`);
|
|
685
|
+
if (!invocation) return this.theme.fg("dim", " ↳ model: unknown · thinking: unknown");
|
|
686
|
+
const { modelName, tags } = buildInvocationTags(invocation);
|
|
687
|
+
const effectiveModelName = invocation.effectiveModelName ?? modelName;
|
|
688
|
+
const parts = effectiveModelName ? [`model: ${effectiveModelName}`, ...tags] : ["model: inherit", ...tags];
|
|
689
|
+
return this.theme.fg("dim", ` ↳ ${parts.join(" · ")}`);
|
|
788
690
|
}
|
|
789
691
|
|
|
790
|
-
private
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
private cycleMarkdownMode(): void {
|
|
795
|
-
const current = this.currentMarkdownMode();
|
|
796
|
-
const next = MARKDOWN_MODES[(MARKDOWN_MODES.indexOf(current) + 1) % MARKDOWN_MODES.length]!;
|
|
797
|
-
this.markdownModeOverride = next;
|
|
798
|
-
this.onMarkdownMode?.(next);
|
|
799
|
-
this.timeline.setMarkdownMode(() => this.currentMarkdownMode());
|
|
800
|
-
this.invalidate();
|
|
801
|
-
this.tui.requestRender();
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
private sessionKey(): string {
|
|
805
|
-
return this.session.messages.map(message => {
|
|
806
|
-
const value = message as unknown as { role?: unknown; content?: unknown; output?: unknown; command?: unknown };
|
|
807
|
-
const content = value.content;
|
|
808
|
-
const summarize = (text: string): string => `${text.length}:${text.slice(0, 24)}:${text.slice(-24)}`;
|
|
809
|
-
const contentSummary = typeof content === "string"
|
|
810
|
-
? summarize(content)
|
|
811
|
-
: Array.isArray(content)
|
|
812
|
-
? content.map(part => typeof part === "object" && part && "text" in part && typeof part.text === "string" ? summarize(part.text) : "").join("|")
|
|
813
|
-
: "";
|
|
814
|
-
return `${String(value.role)}:${contentSummary}:${typeof value.output === "string" ? summarize(value.output) : ""}:${typeof value.command === "string" ? summarize(value.command) : ""}`;
|
|
815
|
-
}).join("\u0001");
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
private blockSignature(block: ConversationBlock): string {
|
|
819
|
-
let resultLength = 0;
|
|
820
|
-
const content = block.toolResult?.content;
|
|
821
|
-
if (typeof content === "string") resultLength = content.length;
|
|
822
|
-
else if (Array.isArray(content)) resultLength = content.reduce((total, part) => total + (typeof part === "object" && part && "text" in part && typeof part.text === "string" ? part.text.length : 0), 0);
|
|
823
|
-
return `${block.id}\u0000${block.markdown}\u0000${block.fullText}\u0000${block.toolLine ?? ""}\u0000${resultLength}`;
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
private syncBlocksFromSession(): void {
|
|
827
|
-
const sessionKey = this.sessionKey();
|
|
828
|
-
if (sessionKey === this.lastSessionKey) return;
|
|
829
|
-
this.lastSessionKey = sessionKey;
|
|
830
|
-
const refreshed = formatConversationMessages(this.session.messages);
|
|
831
|
-
const sameShape = refreshed.length === this.blocks.length && refreshed.every((block, index) => block.id === this.blocks[index]?.id);
|
|
832
|
-
if (!sameShape) {
|
|
833
|
-
this.blocks = refreshed;
|
|
834
|
-
this.timeline.setBlocks(this.blocks);
|
|
835
|
-
this.cacheVersion++;
|
|
836
|
-
this.cachedVersion = -1;
|
|
837
|
-
return;
|
|
838
|
-
}
|
|
839
|
-
let changed = false;
|
|
840
|
-
for (let index = 0; index < refreshed.length; index++) {
|
|
841
|
-
const current = this.blocks[index]!;
|
|
842
|
-
const next = refreshed[index]!;
|
|
843
|
-
if (this.blockSignature(current) !== this.blockSignature(next)) changed = true;
|
|
844
|
-
Object.assign(current, next);
|
|
845
|
-
}
|
|
846
|
-
if (changed) {
|
|
847
|
-
this.cacheVersion++;
|
|
848
|
-
this.cachedVersion = -1;
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
private buildContentLines(width: number): PublicRenderLines {
|
|
853
|
-
if (width <= 0) return new PublicRenderLines([]);
|
|
854
|
-
this.syncBlocksFromSession();
|
|
855
|
-
if (this.cachedVersion === this.cacheVersion && this.cachedWidth === width) {
|
|
856
|
-
this.timeline.render(width);
|
|
857
|
-
return publicRenderLines(this.cachedLines);
|
|
858
|
-
}
|
|
692
|
+
private buildContentLines(width: number): RenderLine[] {
|
|
693
|
+
if (width <= 0) return [];
|
|
694
|
+
if (this.cachedVersion === this.cacheVersion && this.cachedWidth === width) return this.cachedLines;
|
|
859
695
|
this.timeline.setShowTools(this.showTools);
|
|
860
696
|
this.timeline.render(width);
|
|
861
697
|
const timelineSnapshot = this.timeline.getSnapshot();
|
|
862
698
|
const lines: RenderLine[] = timelineSnapshot.lines.map((line) => {
|
|
863
699
|
const rawText = typeof line?.text === "string" ? line.text : "";
|
|
864
700
|
const text = truncateToWidth(rawText, width, "", true);
|
|
865
|
-
return
|
|
701
|
+
return {
|
|
866
702
|
text,
|
|
867
703
|
plain: stripAnsi(text),
|
|
868
704
|
blockIndex: typeof line?.blockIndex === "number" ? line.blockIndex : -1,
|
|
869
705
|
...(line?.railable ? { railable: true } : {}),
|
|
870
|
-
}
|
|
706
|
+
};
|
|
871
707
|
});
|
|
872
708
|
this.cachedMessageStarts = [...timelineSnapshot.messageStarts];
|
|
873
709
|
this.cachedMessageBlocks = [...timelineSnapshot.messageBlocks];
|
|
@@ -887,13 +723,13 @@ export class ConversationViewer implements Component {
|
|
|
887
723
|
if (this.record.status === "running" && this.activity) {
|
|
888
724
|
const act = describeActivity(this.activity.activeTools, this.activity.responseText);
|
|
889
725
|
const text = `▍ ${act}`;
|
|
890
|
-
lines.push(
|
|
726
|
+
lines.push({ text: truncateToWidth(this.theme.fg("accent", "▍ ") + this.theme.fg("dim", act), width), plain: text, blockIndex: -1 });
|
|
891
727
|
}
|
|
892
728
|
this.cachedWidth = width;
|
|
893
729
|
this.cachedVersion = this.cacheVersion;
|
|
894
730
|
this.cachedLines = lines;
|
|
895
731
|
if (this.searchMode || this.searchQuery) this.recomputeMatches();
|
|
896
|
-
return
|
|
732
|
+
return lines;
|
|
897
733
|
}
|
|
898
734
|
|
|
899
735
|
private currentMessageIndex(): number {
|
package/src/ui/schedule-menu.ts
CHANGED
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
12
12
|
import type { SubagentScheduler } from "../schedule.js";
|
|
13
13
|
import type { ScheduledSubagent } from "../types.js";
|
|
14
|
-
import { selectItem } from "./select-item.js";
|
|
15
14
|
|
|
16
15
|
/** Format an ISO timestamp as relative time ("in 4h", "2d ago", "—"). */
|
|
17
16
|
function relTime(iso: string | undefined, now = Date.now()): string {
|
|
@@ -86,16 +85,16 @@ export async function showSchedulesMenu(
|
|
|
86
85
|
return;
|
|
87
86
|
}
|
|
88
87
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
// against a parallel label array cancelled whichever came first.
|
|
92
|
-
const job = await selectItem(
|
|
93
|
-
ctx.ui,
|
|
88
|
+
const labels = jobs.map(j => formatJob(j, scheduler));
|
|
89
|
+
const choice = await ctx.ui.select(
|
|
94
90
|
`Scheduled jobs (${jobs.length}) — select to cancel`,
|
|
95
|
-
|
|
96
|
-
j => formatJob(j, scheduler),
|
|
91
|
+
labels,
|
|
97
92
|
);
|
|
98
|
-
if (!
|
|
93
|
+
if (!choice) return;
|
|
94
|
+
|
|
95
|
+
const idx = labels.indexOf(choice);
|
|
96
|
+
if (idx < 0) return;
|
|
97
|
+
const job = jobs[idx];
|
|
99
98
|
|
|
100
99
|
const ok = await ctx.ui.confirm(`Cancel "${job.name}"?`, formatDetails(job, scheduler));
|
|
101
100
|
if (!ok) return;
|
package/src/usage.ts
CHANGED
|
@@ -6,126 +6,19 @@
|
|
|
6
6
|
* stats-derived sum). cacheRead is excluded because each turn's cacheRead is
|
|
7
7
|
* the cumulative cached prefix re-read on that one call — summing across
|
|
8
8
|
* turns counts the prefix N times. See issue #38.
|
|
9
|
-
*
|
|
10
|
-
* That exclusion is about this *display* total, not about what was billed: the
|
|
11
|
-
* prefix really is re-read and re-charged on every call. So `cacheRead` is
|
|
12
|
-
* accumulated here anyway, kept out of `getLifetimeTotal` and used only where
|
|
13
|
-
* billing is the question — reporting to the parent session, whose own messages
|
|
14
|
-
* pi counts the same way (`addUsageToTotals`). Reporting 0 there would make a
|
|
15
|
-
* subagent's rows count differently from every other row in one total.
|
|
16
|
-
*
|
|
17
|
-
* `cost` is a plain sum for the same reason: it is what pi charged for that one
|
|
18
|
-
* message (`usage.cost.total`, priced from the model's rates), not a cumulative
|
|
19
|
-
* figure. Both are optional because a model with no pricing data reports no
|
|
20
|
-
* cost, and because every accumulator predates them; absent reads as 0.
|
|
21
9
|
*/
|
|
22
|
-
export type LifetimeUsage = { input: number; output: number; cacheWrite: number
|
|
10
|
+
export type LifetimeUsage = { input: number; output: number; cacheWrite: number };
|
|
23
11
|
|
|
24
|
-
/**
|
|
25
|
-
* Sum of lifetime *token* components for DISPLAY, or 0 if undefined.
|
|
26
|
-
* Deliberately excludes `cacheRead` (see above) and `cost` — that is money, not
|
|
27
|
-
* tokens, and lives on the same object only because it accumulates on the same
|
|
28
|
-
* events.
|
|
29
|
-
*/
|
|
12
|
+
/** Sum of lifetime usage components, or 0 if undefined. */
|
|
30
13
|
export function getLifetimeTotal(u?: LifetimeUsage): number {
|
|
31
14
|
return u ? u.input + u.output + u.cacheWrite : 0;
|
|
32
15
|
}
|
|
33
16
|
|
|
34
|
-
/** Accumulated cost in USD, or 0 when unpriced/undefined. */
|
|
35
|
-
export function getLifetimeCost(u?: LifetimeUsage): number {
|
|
36
|
-
return u?.cost ?? 0;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
17
|
/** Add a usage delta into a target accumulator (mutates target). */
|
|
40
18
|
export function addUsage(into: LifetimeUsage, delta: LifetimeUsage): void {
|
|
41
19
|
into.input += delta.input;
|
|
42
20
|
into.output += delta.output;
|
|
43
21
|
into.cacheWrite += delta.cacheWrite;
|
|
44
|
-
if (delta.cacheRead) into.cacheRead = (into.cacheRead ?? 0) + delta.cacheRead;
|
|
45
|
-
if (delta.cost) into.cost = (into.cost ?? 0) + delta.cost;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* A pi `Usage`. Rebuilt here rather than imported so this module stays
|
|
50
|
-
* dependency-free for tests; the fields are pi's, and every one of them must be
|
|
51
|
-
* present: pi's `addUsageToTotals` dereferences `usage.cost.total` with no
|
|
52
|
-
* guard, so a partial object throws inside pi rather than at the call site.
|
|
53
|
-
*
|
|
54
|
-
* This is pi's convention for spend in anything handed to a consumer — every
|
|
55
|
-
* extension-facing payload that carries it takes the whole object
|
|
56
|
-
* (`ToolResultEvent`, `ToolResultEventResult`, `AssistantMessage`, …), never a
|
|
57
|
-
* flattened cost. Pi flattens only in computed read APIs it expects you to
|
|
58
|
-
* render, like `SessionStats`. So both places we hand usage to someone else —
|
|
59
|
-
* `AgentToolResult.usage` and the `subagents:completed` / `subagents:failed`
|
|
60
|
-
* events — carry this, and gain whatever pi adds to `Usage` for free.
|
|
61
|
-
*/
|
|
62
|
-
export type ReportedUsage = {
|
|
63
|
-
input: number;
|
|
64
|
-
output: number;
|
|
65
|
-
cacheRead: number;
|
|
66
|
-
cacheWrite: number;
|
|
67
|
-
totalTokens: number;
|
|
68
|
-
cost: { input: number; output: number; cacheRead: number; cacheWrite: number; total: number };
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Render an accumulator as a pi `Usage`, or undefined when nothing was spent —
|
|
73
|
-
* callers attach nothing rather than a zero, so a consumer can tell "spent
|
|
74
|
-
* nothing" from "never ran".
|
|
75
|
-
*
|
|
76
|
-
* `cacheRead` IS included, unlike in `getLifetimeTotal`: pi sums it across a
|
|
77
|
-
* session's own assistant messages, and the prefix genuinely is re-read and
|
|
78
|
-
* re-billed on every call. Only `total` is populated on the cost breakdown; pi
|
|
79
|
-
* reads nothing else from it, and the per-kind split is not tracked.
|
|
80
|
-
*/
|
|
81
|
-
export function toReportedUsage(u: LifetimeUsage): ReportedUsage | undefined {
|
|
82
|
-
const { input, output, cacheWrite, cacheRead = 0, cost = 0 } = u;
|
|
83
|
-
if (input === 0 && output === 0 && cacheWrite === 0 && cacheRead === 0 && cost === 0) return undefined;
|
|
84
|
-
return {
|
|
85
|
-
input,
|
|
86
|
-
output,
|
|
87
|
-
cacheRead,
|
|
88
|
-
cacheWrite,
|
|
89
|
-
totalTokens: input + output + cacheRead + cacheWrite,
|
|
90
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: cost },
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Subagent spend that the parent session has not been told about yet.
|
|
96
|
-
*
|
|
97
|
-
* Subagents run in their own pi sessions, so none of what they spend appears in
|
|
98
|
-
* the parent's `getSessionStats()`. Pi does aggregate `toolResult.usage` into
|
|
99
|
-
* those stats, though — so the way back into the parent's footer and `/cost` is
|
|
100
|
-
* to hang the spend on a tool result. Background and scheduled agents finish
|
|
101
|
-
* between tool calls with nothing to hang it on, hence a pool: every assistant
|
|
102
|
-
* message lands here as it happens, and the next tool result we return carries
|
|
103
|
-
* whatever has accumulated.
|
|
104
|
-
*
|
|
105
|
-
* Drain empties it, so each message is reported exactly once no matter how many
|
|
106
|
-
* results are returned or how many agents were running.
|
|
107
|
-
*/
|
|
108
|
-
export class PendingUsagePool {
|
|
109
|
-
private pending: LifetimeUsage = { input: 0, output: 0, cacheWrite: 0, cacheRead: 0, cost: 0 };
|
|
110
|
-
private dirty = false;
|
|
111
|
-
|
|
112
|
-
add(delta: LifetimeUsage): void {
|
|
113
|
-
addUsage(this.pending, delta);
|
|
114
|
-
this.dirty = true;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Take everything accumulated so far as a pi `Usage`, resetting the pool.
|
|
119
|
-
* Returns undefined when nothing is pending, so callers can leave the tool
|
|
120
|
-
* result untouched rather than attaching a zero.
|
|
121
|
-
*/
|
|
122
|
-
drain(): ReportedUsage | undefined {
|
|
123
|
-
if (!this.dirty) return undefined;
|
|
124
|
-
const drained = toReportedUsage(this.pending);
|
|
125
|
-
this.pending = { input: 0, output: 0, cacheWrite: 0, cacheRead: 0, cost: 0 };
|
|
126
|
-
this.dirty = false;
|
|
127
|
-
return drained;
|
|
128
|
-
}
|
|
129
22
|
}
|
|
130
23
|
|
|
131
24
|
/** Minimal shape we read from upstream `getSessionStats()`. */
|