opencode-extended-sidebar 0.2.90
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/.oesignore +17 -0
- package/CHANGELOG.md +95 -0
- package/LICENSE +21 -0
- package/README.md +235 -0
- package/oes.json +11 -0
- package/package.json +100 -0
- package/scripts/install-git-hooks.mjs +29 -0
- package/src/pware.oc.core/constants/index.ts +18 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.eventKind.ts +29 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.eventName.ts +13 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.eventType.ts +241 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.myWork.ts +55 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.partType.ts +65 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.phase.ts +58 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.pulse.ts +63 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.rowKind.ts +34 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.status.ts +74 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.toolName.ts +69 -0
- package/src/pware.oc.core/git/index.ts +7 -0
- package/src/pware.oc.core/git/pware.oc.core.git.ts +237 -0
- package/src/pware.oc.core/git/pware.oc.core.gitignore.ts +87 -0
- package/src/pware.oc.core/index.ts +18 -0
- package/src/pware.oc.core/pware.oc.core.bus.ts +42 -0
- package/src/pware.oc.core/pware.oc.core.cache.ts +48 -0
- package/src/pware.oc.core/pware.oc.core.clipboard.ts +97 -0
- package/src/pware.oc.core/pware.oc.core.debug.ts +280 -0
- package/src/pware.oc.core/pware.oc.core.events.ts +79 -0
- package/src/pware.oc.core/pware.oc.core.glyph.ts +135 -0
- package/src/pware.oc.core/pware.oc.core.layout.ts +234 -0
- package/src/pware.oc.core/pware.oc.core.oes.ts +89 -0
- package/src/pware.oc.core/pware.oc.core.paths.ts +240 -0
- package/src/pware.oc.core/pware.oc.core.preview.ts +74 -0
- package/src/pware.oc.core/pware.oc.core.pulse.ts +726 -0
- package/src/pware.oc.core/pware.oc.core.sqlite.ts +175 -0
- package/src/pware.oc.core/pware.oc.core.status.ts +214 -0
- package/src/pware.oc.core/pware.oc.core.timing.ts +63 -0
- package/src/pware.oc.core/pware.oc.core.width.ts +215 -0
- package/src/pware.oc.omo/constants/index.ts +15 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.backgroundTask.ts +18 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.boulderStatus.ts +49 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.docKind.ts +29 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.eventName.ts +11 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.planStatus.ts +60 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.reviewStatus.ts +49 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.startWork.ts +25 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.verdict.ts +38 -0
- package/src/pware.oc.omo/index.ts +7 -0
- package/src/pware.oc.omo/resolver/index.ts +19 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approval.ts +179 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approvalGroup.ts +116 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approvalState.ts +34 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.boulder.ts +503 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.config.ts +40 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.doc.ts +269 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.draftFile.ts +18 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.notepadsFile.ts +18 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.plan.ts +155 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.planFile.ts +361 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.proofFile.ts +18 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.rulesFile.ts +11 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.runContinuationFile.ts +14 -0
- package/src/pware.oc.opencode/constants/index.ts +10 -0
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.eventName.ts +15 -0
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.fileTouch.ts +18 -0
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.questionKind.ts +45 -0
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.sessionStatus.ts +58 -0
- package/src/pware.oc.opencode/index.ts +8 -0
- package/src/pware.oc.opencode/pware.oc.opencode.events.ts +182 -0
- package/src/pware.oc.opencode/pware.oc.opencode.files.ts +399 -0
- package/src/pware.oc.opencode/resolver/index.ts +170 -0
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.file.ts +162 -0
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.question.ts +185 -0
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.session.ts +211 -0
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.todo.ts +26 -0
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.tool.ts +236 -0
- package/src/pware.oc.perf/index.ts +14 -0
- package/src/pware.oc.perf/pware.oc.perf.asciichart.d.ts +15 -0
- package/src/pware.oc.perf/pware.oc.perf.charts.ts +284 -0
- package/src/pware.oc.perf/pware.oc.perf.reader.ts +927 -0
- package/src/pware.oc.perf/pware.oc.perf.realtime.ts +88 -0
- package/src/pware.oc.perf/pware.oc.perf.realtimeBlock.ts +118 -0
- package/src/pware.oc.perf/pware.oc.perf.realtimeCpuRam.ts +79 -0
- package/src/pware.oc.perf/pware.oc.perf.realtimeSampler.ts +71 -0
- package/src/pware.oc.perf/pware.oc.perf.realtimeTimeline.ts +214 -0
- package/src/pware.oc.perf/pware.oc.perf.self.ts +179 -0
- package/src/pware.oc.perf/pware.oc.perf.view.tsx +462 -0
- package/src/pware.oc.runtime/index.ts +13 -0
- package/src/pware.oc.runtime/pware.oc.runtime.monitor.ts +142 -0
- package/src/pware.oc.runtime/pware.oc.runtime.mywork-enrich.ts +79 -0
- package/src/pware.oc.runtime/pware.oc.runtime.mywork.ts +253 -0
- package/src/pware.oc.runtime/pware.oc.runtime.questions.ts +95 -0
- package/src/pware.oc.runtime/pware.oc.runtime.snapshotClient.ts +129 -0
- package/src/pware.oc.runtime/pware.oc.runtime.source.ts +105 -0
- package/src/pware.oc.runtime/pware.oc.runtime.worker.ts +43 -0
- package/src/pware.oc.runtime/resolver/index.ts +224 -0
- package/src/pware.oc.runtime/resolver/pware.oc.runtime.resolver.delegate.ts +146 -0
- package/src/pware.oc.ui/index.ts +12 -0
- package/src/pware.oc.ui/pware.oc.ui.chrome.tsx +339 -0
- package/src/pware.oc.ui/pware.oc.ui.glyphs.tsx +144 -0
- package/src/pware.oc.ui/pware.oc.ui.host.tsx +111 -0
- package/src/pware.oc.ui/pware.oc.ui.live.tsx +82 -0
- package/src/pware.oc.ui/pware.oc.ui.menudialogs.tsx +843 -0
- package/src/pware.oc.ui/pware.oc.ui.sections.tsx +526 -0
- package/src/pware.oc.ui/pware.oc.ui.sidebar.tsx +1629 -0
- package/src/pware.oc.ui.tsx +98 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin self-cost: event/scan/tick latency + renderer FPS. Pure accumulators, safe on any host.
|
|
3
|
+
*/
|
|
4
|
+
import {
|
|
5
|
+
SELF_PHASE_EVENT,
|
|
6
|
+
SELF_PHASE_SCAN,
|
|
7
|
+
SELF_PHASE_TICK,
|
|
8
|
+
type SelfPhase,
|
|
9
|
+
} from "../pware.oc.core/constants/pware.oc.core.constants.phase.js"
|
|
10
|
+
import { debugActive, profileActive } from "../pware.oc.core/pware.oc.core.debug.js"
|
|
11
|
+
|
|
12
|
+
export type { SelfPhase }
|
|
13
|
+
|
|
14
|
+
export type SelfBucket = { n: number; sum: number; max: number }
|
|
15
|
+
|
|
16
|
+
export type SelfStats = {
|
|
17
|
+
[SELF_PHASE_EVENT]: SelfBucket
|
|
18
|
+
[SELF_PHASE_SCAN]: SelfBucket
|
|
19
|
+
[SELF_PHASE_TICK]: SelfBucket
|
|
20
|
+
/** Last known renderer FPS, null until first read. */
|
|
21
|
+
fps: number | null
|
|
22
|
+
/** Last known average frame time in ms, null until first read. */
|
|
23
|
+
frameMs: number | null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const PHASES: SelfPhase[] = [SELF_PHASE_EVENT, SELF_PHASE_SCAN, SELF_PHASE_TICK]
|
|
27
|
+
|
|
28
|
+
const buckets: Record<SelfPhase, SelfBucket> = {
|
|
29
|
+
[SELF_PHASE_EVENT]: { n: 0, sum: 0, max: 0 },
|
|
30
|
+
[SELF_PHASE_SCAN]: { n: 0, sum: 0, max: 0 },
|
|
31
|
+
[SELF_PHASE_TICK]: { n: 0, sum: 0, max: 0 },
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let fps: number | null = null
|
|
35
|
+
let frameMs: number | null = null
|
|
36
|
+
|
|
37
|
+
/** Test override for the diagnostic gate — null delegates to the env-driven loggers. */
|
|
38
|
+
let selfDiagForced: boolean | null = null
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* True while a debug or profile logger is active. Self-cost instrumentation is
|
|
42
|
+
* purely diagnostic, so production (neither env var set) pays nothing for it.
|
|
43
|
+
*/
|
|
44
|
+
export function selfDiagActive(): boolean {
|
|
45
|
+
return selfDiagForced ?? (debugActive() || profileActive())
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Test hook — force the gate on/off without touching process.env. */
|
|
49
|
+
export function setSelfDiagForced(on: boolean | null): void {
|
|
50
|
+
selfDiagForced = on
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Measures fn with performance.now(), accumulates into bucket `label`, returns fn's result.
|
|
55
|
+
* When neither the debug nor the profile logger is active the fn runs bare — no clock read,
|
|
56
|
+
* no accumulator writes. Throws propagate.
|
|
57
|
+
*/
|
|
58
|
+
export function selfTime<T>(label: SelfPhase, fn: () => T): T {
|
|
59
|
+
if (!selfDiagActive()) return fn()
|
|
60
|
+
const start = performance.now()
|
|
61
|
+
try {
|
|
62
|
+
return fn()
|
|
63
|
+
} finally {
|
|
64
|
+
const elapsed = performance.now() - start
|
|
65
|
+
const b = buckets[label]
|
|
66
|
+
b.n += 1
|
|
67
|
+
b.sum += elapsed
|
|
68
|
+
if (elapsed > b.max) b.max = elapsed
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Shallow copy of current stats (never hand out the internal object). */
|
|
73
|
+
export function readSelfStats(): SelfStats {
|
|
74
|
+
return {
|
|
75
|
+
event: { ...buckets.event },
|
|
76
|
+
scan: { ...buckets.scan },
|
|
77
|
+
tick: { ...buckets.tick },
|
|
78
|
+
fps,
|
|
79
|
+
frameMs,
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Zero all buckets and fps/frameMs. Test helper + used on remount. */
|
|
84
|
+
export function resetSelfStats(): void {
|
|
85
|
+
for (const phase of PHASES) {
|
|
86
|
+
buckets[phase].n = 0
|
|
87
|
+
buckets[phase].sum = 0
|
|
88
|
+
buckets[phase].max = 0
|
|
89
|
+
}
|
|
90
|
+
fps = null
|
|
91
|
+
frameMs = null
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Record the latest renderer read. fps/frameMs null clears. */
|
|
95
|
+
export function setSelfFps(nextFps: number | null, nextFrameMs: number | null): void {
|
|
96
|
+
fps = nextFps
|
|
97
|
+
frameMs = nextFrameMs
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function finitePositive(v: unknown): v is number {
|
|
101
|
+
return typeof v === "number" && Number.isFinite(v) && v > 0
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
type RendererLike = {
|
|
105
|
+
getStats?: unknown
|
|
106
|
+
getNativeStats?: unknown
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Null-safe renderer stats read. Accepts any object (the host's CliRenderer).
|
|
111
|
+
* Tries getStats() first (has .fps), falls back to getNativeStats().averageFrameTime,
|
|
112
|
+
* returns { fps, frameMs } where both are numbers|null. Never throws — any missing
|
|
113
|
+
* method / exception yields { fps: null, frameMs: null }.
|
|
114
|
+
*/
|
|
115
|
+
export function readRendererFps(renderer: unknown): { fps: number | null; frameMs: number | null } {
|
|
116
|
+
let outFps: number | null = null
|
|
117
|
+
let outFrameMs: number | null = null
|
|
118
|
+
if (renderer === null || typeof renderer !== "object") return { fps: outFps, frameMs: outFrameMs }
|
|
119
|
+
|
|
120
|
+
const r = renderer as RendererLike
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
if (typeof r.getStats === "function") {
|
|
124
|
+
const stats: unknown = r.getStats.call(renderer)
|
|
125
|
+
if (stats !== null && typeof stats === "object") {
|
|
126
|
+
const raw = (stats as { fps?: unknown }).fps
|
|
127
|
+
if (finitePositive(raw)) outFps = Math.round(raw)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
} catch {
|
|
131
|
+
// getStats threw — leave outFps null and fall back below
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (outFps === null) {
|
|
135
|
+
try {
|
|
136
|
+
if (typeof r.getNativeStats === "function") {
|
|
137
|
+
const stats: unknown = r.getNativeStats.call(renderer)
|
|
138
|
+
if (stats !== null && typeof stats === "object") {
|
|
139
|
+
const avg = (stats as { averageFrameTime?: unknown }).averageFrameTime
|
|
140
|
+
if (finitePositive(avg)) outFrameMs = Math.round(avg * 10) / 10
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} catch {
|
|
144
|
+
// getNativeStats threw — leave outFrameMs null
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return { fps: outFps, frameMs: outFrameMs }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Round ms to 1 decimal for sub-second self timings (the reader's formatDuration is coarse). */
|
|
152
|
+
function ms1(x: number): string {
|
|
153
|
+
return String(Math.round(x * 10) / 10)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function phasePart(b: SelfBucket, suffix: string): string | null {
|
|
157
|
+
if (b.n === 0) return null
|
|
158
|
+
return `${ms1(b.sum / b.n)}ms/${suffix}`
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Pure line text for the status row, e.g. "self 0.4ms/ev · 1.2ms/sc · 59fps".
|
|
163
|
+
*/
|
|
164
|
+
export function formatSelfLine(stats: SelfStats): string {
|
|
165
|
+
const parts: string[] = []
|
|
166
|
+
const ev = phasePart(stats.event, "ev")
|
|
167
|
+
const sc = phasePart(stats.scan, "sc")
|
|
168
|
+
const tk = phasePart(stats.tick, "tk")
|
|
169
|
+
if (ev) parts.push(ev)
|
|
170
|
+
if (sc) parts.push(sc)
|
|
171
|
+
if (tk) parts.push(tk)
|
|
172
|
+
if (stats.fps != null) {
|
|
173
|
+
parts.push(`${stats.fps}fps`)
|
|
174
|
+
} else if (stats.frameMs != null) {
|
|
175
|
+
parts.push(`${String(Math.round(stats.frameMs))}ms/f`)
|
|
176
|
+
}
|
|
177
|
+
if (parts.length === 0) return "self —"
|
|
178
|
+
return `self ${parts.join(" · ")}`
|
|
179
|
+
}
|
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
/** @jsxImportSource @opentui/solid */
|
|
2
|
+
/** Perf tab: where a session's wall clock goes, per model and per tool. */
|
|
3
|
+
import { createMemo, For, Show, type JSX } from "solid-js"
|
|
4
|
+
import type { TuiPluginApi } from "@opencode-ai/plugin/tui"
|
|
5
|
+
import { ClickText, toneColor, type ThemeColors } from "../pware.oc.ui/pware.oc.ui.chrome.js"
|
|
6
|
+
import { FoldSection, RowList, useFold, useReveal } from "../pware.oc.ui/pware.oc.ui.sections.js"
|
|
7
|
+
import { openPerfCharts, openPerfLog } from "../pware.oc.ui/pware.oc.ui.menudialogs.js"
|
|
8
|
+
import type { PerfLogKind } from "./pware.oc.perf.reader.js"
|
|
9
|
+
import {
|
|
10
|
+
type ModelPerf,
|
|
11
|
+
type PerfSnapshot,
|
|
12
|
+
type SessionPerf,
|
|
13
|
+
type ToolPerf,
|
|
14
|
+
} from "./pware.oc.perf.reader.js"
|
|
15
|
+
import { THINK_GLYPH, directionGlyph, spinnerFrame, type GlyphSpec } from "../pware.oc.ui/pware.oc.ui.glyphs.js"
|
|
16
|
+
import {
|
|
17
|
+
PERF_LOG_KIND_MODELS,
|
|
18
|
+
PERF_LOG_KIND_TIME,
|
|
19
|
+
PERF_PHASE_IDLE,
|
|
20
|
+
PERF_PHASE_RECV,
|
|
21
|
+
PERF_PHASE_THINK,
|
|
22
|
+
PERF_PHASE_TOOL,
|
|
23
|
+
PERF_PHASE_WAIT,
|
|
24
|
+
type PerfPhase,
|
|
25
|
+
} from "../pware.oc.core/constants/pware.oc.core.constants.phase.js"
|
|
26
|
+
import {
|
|
27
|
+
FLOW_RECV,
|
|
28
|
+
FLOW_TOOL,
|
|
29
|
+
FLOW_WAIT,
|
|
30
|
+
} from "../pware.oc.core/constants/pware.oc.core.constants.pulse.js"
|
|
31
|
+
import {
|
|
32
|
+
formatDuration,
|
|
33
|
+
formatPercent,
|
|
34
|
+
formatRate,
|
|
35
|
+
formatSpan,
|
|
36
|
+
packChips,
|
|
37
|
+
packStackedRow,
|
|
38
|
+
timeSummary,
|
|
39
|
+
tokenSummary,
|
|
40
|
+
type Chip,
|
|
41
|
+
type FlowDir,
|
|
42
|
+
} from "../pware.oc.core/pware.oc.core.pulse.js"
|
|
43
|
+
import { clipMiddleWidth, strWidth } from "../pware.oc.core/pware.oc.core.width.js"
|
|
44
|
+
import { asciiTrend, shareBar } from "./pware.oc.perf.charts.js"
|
|
45
|
+
|
|
46
|
+
const KV_FOLD_MODELS = "oes.fold.perf.models"
|
|
47
|
+
const KV_FOLD_TIME = "oes.fold.perf.time"
|
|
48
|
+
const KV_FOLD_TOOLS = "oes.fold.perf.tools"
|
|
49
|
+
const KV_FOLD_TREND = "oes.fold.perf.trend"
|
|
50
|
+
const KV_FOLD_HISTORY = "oes.fold.perf.history"
|
|
51
|
+
|
|
52
|
+
const PHASES: Array<{ key: PerfPhase; spec: GlyphSpec; label: string }> = [
|
|
53
|
+
{ key: PERF_PHASE_WAIT, spec: directionGlyph(FLOW_WAIT)!, label: "wait" },
|
|
54
|
+
{ key: PERF_PHASE_THINK, spec: { char: THINK_GLYPH, tone: "primary" }, label: "think" },
|
|
55
|
+
{ key: PERF_PHASE_RECV, spec: directionGlyph(FLOW_RECV)!, label: "recv" },
|
|
56
|
+
{ key: PERF_PHASE_TOOL, spec: directionGlyph(FLOW_TOOL)!, label: "tools" },
|
|
57
|
+
{ key: PERF_PHASE_IDLE, spec: { char: "·", tone: "textMuted" }, label: "idle" },
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
function MetricRow(props: {
|
|
61
|
+
glyph: string
|
|
62
|
+
glyphFg: string
|
|
63
|
+
name: string
|
|
64
|
+
nameFg: string
|
|
65
|
+
chips: Chip[]
|
|
66
|
+
chipFg?: (chip: Chip) => string
|
|
67
|
+
lineMax: number
|
|
68
|
+
bold?: boolean
|
|
69
|
+
minName?: number
|
|
70
|
+
onSelect?: () => void
|
|
71
|
+
}): JSX.Element {
|
|
72
|
+
const body = createMemo(() => {
|
|
73
|
+
const room = Math.max(4, props.lineMax - 2)
|
|
74
|
+
const minName = props.minName ?? 6
|
|
75
|
+
const chips = packChips(minName, props.chips, room)
|
|
76
|
+
const used = chips.reduce((sum, c) => sum + strWidth(c.text) + 1, 0)
|
|
77
|
+
const name = clipMiddleWidth(props.name, Math.max(minName, room - used))
|
|
78
|
+
return { name, chips }
|
|
79
|
+
})
|
|
80
|
+
return (
|
|
81
|
+
<box flexDirection="row" onMouseUp={props.onSelect}>
|
|
82
|
+
<text fg={props.glyphFg}>{`${props.glyph} `}</text>
|
|
83
|
+
<ClickText fg={props.nameFg} bold={Boolean(props.bold)} underline={Boolean(props.onSelect)}>
|
|
84
|
+
{body().name}
|
|
85
|
+
</ClickText>
|
|
86
|
+
<For each={body().chips}>
|
|
87
|
+
{(chip) => (
|
|
88
|
+
<text fg={props.chipFg ? props.chipFg(chip) : props.nameFg}>{` ${chip.text}`}</text>
|
|
89
|
+
)}
|
|
90
|
+
</For>
|
|
91
|
+
</box>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function PhaseRow(props: {
|
|
96
|
+
spec: GlyphSpec
|
|
97
|
+
label: string
|
|
98
|
+
ms: number
|
|
99
|
+
share: number | null
|
|
100
|
+
colors: ThemeColors
|
|
101
|
+
lineMax: number
|
|
102
|
+
onSelect?: () => void
|
|
103
|
+
}): JSX.Element {
|
|
104
|
+
const fg = () => toneColor(props.spec.tone, props.colors)
|
|
105
|
+
// Fixed columns keep every bar starting and ending on the same screen column.
|
|
106
|
+
const barWidth = () => Math.max(4, props.lineMax - 18)
|
|
107
|
+
return (
|
|
108
|
+
<box flexDirection="row" onMouseUp={props.onSelect}>
|
|
109
|
+
<text fg={fg()}>{`${props.spec.char} `}</text>
|
|
110
|
+
<ClickText fg={props.colors.textMuted} underline={Boolean(props.onSelect)}>
|
|
111
|
+
{props.label.padEnd(5)}
|
|
112
|
+
</ClickText>
|
|
113
|
+
<text fg={fg()}>{` ${shareBar(props.share, barWidth())}`}</text>
|
|
114
|
+
<text fg={props.colors.textMuted}>
|
|
115
|
+
{` ${formatPercent(props.share).padStart(4)} ${formatSpan(props.ms).padStart(4)}`}
|
|
116
|
+
</text>
|
|
117
|
+
</box>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function ModelRow(props: {
|
|
122
|
+
model: ModelPerf
|
|
123
|
+
colors: ThemeColors
|
|
124
|
+
lineMax: number
|
|
125
|
+
/** Fast glyph tick — read only in the live glyph. */
|
|
126
|
+
glyphFrame: () => number
|
|
127
|
+
live: boolean
|
|
128
|
+
}): JSX.Element {
|
|
129
|
+
const m = () => props.model
|
|
130
|
+
const stacked = () =>
|
|
131
|
+
packStackedRow(
|
|
132
|
+
m().model,
|
|
133
|
+
[
|
|
134
|
+
{ text: `${m().turns}×`, rank: 2 },
|
|
135
|
+
{ text: m().waitMs != null ? `↑${formatDuration(m().waitMs)}` : "", rank: 0 },
|
|
136
|
+
{ text: m().thinkMs != null ? `∴${formatDuration(m().thinkMs)}` : "", rank: 3 },
|
|
137
|
+
{ text: m().recvMs != null ? `↓${formatDuration(m().recvMs)}` : "", rank: 1 },
|
|
138
|
+
{ text: formatRate(m().tokensPerSec), rank: 4 },
|
|
139
|
+
],
|
|
140
|
+
props.lineMax,
|
|
141
|
+
)
|
|
142
|
+
const name = () => stacked().name
|
|
143
|
+
const chips = () => stacked().chips
|
|
144
|
+
const chipFg = (_chip: Chip): string => props.colors.textMuted
|
|
145
|
+
return (
|
|
146
|
+
<box flexDirection="column">
|
|
147
|
+
<box flexDirection="row">
|
|
148
|
+
<text fg={props.live ? props.colors.success : props.colors.textMuted}>
|
|
149
|
+
{`${props.live ? spinnerFrame(props.glyphFrame()) : "•"} `}
|
|
150
|
+
</text>
|
|
151
|
+
<text fg={props.colors.text}>{name()}</text>
|
|
152
|
+
</box>
|
|
153
|
+
<box flexDirection="row" paddingLeft={2}>
|
|
154
|
+
<For each={chips()}>
|
|
155
|
+
{(chip, i) => (
|
|
156
|
+
<text fg={chipFg(chip)}>{i() === 0 ? chip.text : ` ${chip.text}`}</text>
|
|
157
|
+
)}
|
|
158
|
+
</For>
|
|
159
|
+
</box>
|
|
160
|
+
</box>
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function ToolRow(props: {
|
|
165
|
+
tool: ToolPerf
|
|
166
|
+
share: number | null
|
|
167
|
+
colors: ThemeColors
|
|
168
|
+
lineMax: number
|
|
169
|
+
onSelect?: () => void
|
|
170
|
+
}): JSX.Element {
|
|
171
|
+
const t = () => props.tool
|
|
172
|
+
const chips = (): Chip[] => [
|
|
173
|
+
{ text: `${t().count}×`, rank: 2 },
|
|
174
|
+
{ text: formatDuration(t().avgMs), rank: 0 },
|
|
175
|
+
{ text: t().errors > 0 ? `×${t().errors}` : "", rank: 1 },
|
|
176
|
+
{ text: shareBar(props.share, 4), rank: 3 },
|
|
177
|
+
]
|
|
178
|
+
const chipFg = (chip: Chip): string => {
|
|
179
|
+
if (chip.text.startsWith("×")) return props.colors.error || props.colors.text
|
|
180
|
+
return props.colors.textMuted
|
|
181
|
+
}
|
|
182
|
+
return (
|
|
183
|
+
<MetricRow
|
|
184
|
+
glyph={t().errors > 0 ? "×" : "→"}
|
|
185
|
+
glyphFg={t().errors > 0 ? props.colors.error || props.colors.text : props.colors.textMuted}
|
|
186
|
+
name={t().name}
|
|
187
|
+
nameFg={props.colors.text}
|
|
188
|
+
chips={chips()}
|
|
189
|
+
chipFg={chipFg}
|
|
190
|
+
lineMax={props.lineMax}
|
|
191
|
+
minName={6}
|
|
192
|
+
onSelect={props.onSelect}
|
|
193
|
+
/>
|
|
194
|
+
)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function HistoryRow(props: {
|
|
198
|
+
row: SessionPerf
|
|
199
|
+
colors: ThemeColors
|
|
200
|
+
lineMax: number
|
|
201
|
+
current: boolean
|
|
202
|
+
onSelect: () => void
|
|
203
|
+
}): JSX.Element {
|
|
204
|
+
const r = () => props.row
|
|
205
|
+
const chips = (): Chip[] => [
|
|
206
|
+
{ text: `${r().turns}×`, rank: 2 },
|
|
207
|
+
{ text: r().waitMs != null ? `↑${formatDuration(r().waitMs)}` : "", rank: 0 },
|
|
208
|
+
{ text: r().toolShare != null ? `→${formatPercent(r().toolShare)}` : "", rank: 1 },
|
|
209
|
+
]
|
|
210
|
+
const chipFg = (_chip: Chip): string => props.colors.textMuted
|
|
211
|
+
return (
|
|
212
|
+
<MetricRow
|
|
213
|
+
glyph="•"
|
|
214
|
+
glyphFg={props.colors.textMuted}
|
|
215
|
+
name={r().title}
|
|
216
|
+
nameFg={props.colors.text}
|
|
217
|
+
bold={props.current}
|
|
218
|
+
chips={chips()}
|
|
219
|
+
chipFg={chipFg}
|
|
220
|
+
lineMax={props.lineMax}
|
|
221
|
+
minName={8}
|
|
222
|
+
onSelect={props.onSelect}
|
|
223
|
+
/>
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function TrendChart(props: {
|
|
228
|
+
label: string
|
|
229
|
+
values: Array<number | null>
|
|
230
|
+
fg: string
|
|
231
|
+
colors: ThemeColors
|
|
232
|
+
lineMax: number
|
|
233
|
+
}): JSX.Element {
|
|
234
|
+
const lines = () =>
|
|
235
|
+
asciiTrend(props.values, { width: Math.max(8, props.lineMax - 10), height: 3 }).split("\n")
|
|
236
|
+
return (
|
|
237
|
+
<box flexDirection="column">
|
|
238
|
+
<text fg={props.colors.textMuted}>{props.label}</text>
|
|
239
|
+
<For each={lines()}>
|
|
240
|
+
{(line) => <text fg={props.fg}>{line || " "}</text>}
|
|
241
|
+
</For>
|
|
242
|
+
</box>
|
|
243
|
+
)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export type PerfPanelProps = {
|
|
247
|
+
api: TuiPluginApi
|
|
248
|
+
perf: PerfSnapshot
|
|
249
|
+
colors: ThemeColors
|
|
250
|
+
lineMax: number
|
|
251
|
+
rows: number
|
|
252
|
+
/** Fast glyph tick — read only in the live glyph, so the panel is not rebuilt per tick. */
|
|
253
|
+
glyphFrame: () => number
|
|
254
|
+
/** Phase the watched session is in right now, plus how long it has lasted. */
|
|
255
|
+
livePhase: FlowDir | null
|
|
256
|
+
livePhaseMs: number | null
|
|
257
|
+
currentSessionId: string
|
|
258
|
+
dbPath: string
|
|
259
|
+
turns: number
|
|
260
|
+
onSelect: (sessionId: string) => void
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function PerfPanel(props: PerfPanelProps): JSX.Element {
|
|
264
|
+
const foldModels = useFold(props.api, KV_FOLD_MODELS)
|
|
265
|
+
const foldTime = useFold(props.api, KV_FOLD_TIME)
|
|
266
|
+
const foldTools = useFold(props.api, KV_FOLD_TOOLS)
|
|
267
|
+
const foldTrend = useFold(props.api, KV_FOLD_TREND)
|
|
268
|
+
const foldHistory = useFold(props.api, KV_FOLD_HISTORY)
|
|
269
|
+
|
|
270
|
+
const totals = () => props.perf.totals
|
|
271
|
+
const wall = () => Math.max(1, totals().wallMs)
|
|
272
|
+
const activeTimeMs = () => Math.max(0, totals().wallMs - totals().phases.idle)
|
|
273
|
+
const phases = () =>
|
|
274
|
+
PHASES.map((p) => ({ ...p, ms: totals().phases[p.key] })).filter(
|
|
275
|
+
(p) => p.ms > 0 || p.key === PERF_PHASE_WAIT || p.key === PERF_PHASE_TOOL,
|
|
276
|
+
)
|
|
277
|
+
const modelsReveal = useReveal(props.rows)
|
|
278
|
+
const toolsReveal = useReveal(props.rows)
|
|
279
|
+
const historyReveal = useReveal(props.rows)
|
|
280
|
+
const toolTotal = () => props.perf.tools.reduce((sum, t) => sum + t.totalMs, 0)
|
|
281
|
+
const trendWait = () => props.perf.trend.map((p) => p.waitMs)
|
|
282
|
+
const trendRate = () => props.perf.trend.map((p) => p.tokensPerSec)
|
|
283
|
+
const hasTrend = () =>
|
|
284
|
+
props.perf.trend.length > 1 &&
|
|
285
|
+
(trendWait().some((v) => v != null) || trendRate().some((v) => v != null))
|
|
286
|
+
|
|
287
|
+
const openLog = (kind: PerfLogKind, toolFilter?: string) =>
|
|
288
|
+
openPerfLog(props.api, props.colors, {
|
|
289
|
+
dbPath: props.dbPath,
|
|
290
|
+
sessionId: props.currentSessionId,
|
|
291
|
+
turns: props.turns,
|
|
292
|
+
kind,
|
|
293
|
+
toolFilter,
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
const openCharts = () =>
|
|
297
|
+
openPerfCharts(props.api, props.colors, {
|
|
298
|
+
perf: props.perf,
|
|
299
|
+
currentSessionId: props.currentSessionId,
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
const liveLabel = () => {
|
|
303
|
+
const dir = props.livePhase
|
|
304
|
+
if (!dir) return null
|
|
305
|
+
const label = dir === FLOW_WAIT ? "wait" : dir === FLOW_RECV ? "recv" : "tool"
|
|
306
|
+
const fg = toneColor(directionGlyph(dir)!.tone, props.colors)
|
|
307
|
+
return { label, fg, ms: props.livePhaseMs }
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return (
|
|
311
|
+
<box flexDirection="column" gap={1}>
|
|
312
|
+
<Show when={liveLabel()}>
|
|
313
|
+
{(live) => (
|
|
314
|
+
<box flexDirection="row">
|
|
315
|
+
<text fg={live().fg}>{`${spinnerFrame(props.glyphFrame())} ${live().label}`}</text>
|
|
316
|
+
<text fg={props.colors.textMuted}>
|
|
317
|
+
{live().ms != null ? ` ${formatDuration(live().ms)}` : ""}
|
|
318
|
+
</text>
|
|
319
|
+
</box>
|
|
320
|
+
)}
|
|
321
|
+
</Show>
|
|
322
|
+
|
|
323
|
+
<Show when={props.perf.totals.turns > 0} fallback={null}>
|
|
324
|
+
<box flexDirection="column" gap={1}>
|
|
325
|
+
<FoldSection
|
|
326
|
+
title="Models"
|
|
327
|
+
open={foldModels.open()}
|
|
328
|
+
count={props.perf.models.length}
|
|
329
|
+
suffix={tokenSummary(totals())}
|
|
330
|
+
colors={props.colors}
|
|
331
|
+
onToggle={foldModels.toggle}
|
|
332
|
+
onDetail={() => openLog(PERF_LOG_KIND_MODELS)}
|
|
333
|
+
>
|
|
334
|
+
<RowList
|
|
335
|
+
items={props.perf.models}
|
|
336
|
+
budget={props.rows + modelsReveal.more()}
|
|
337
|
+
colors={props.colors}
|
|
338
|
+
renderItem={(m) => (
|
|
339
|
+
<ModelRow
|
|
340
|
+
model={m}
|
|
341
|
+
colors={props.colors}
|
|
342
|
+
lineMax={props.lineMax}
|
|
343
|
+
glyphFrame={props.glyphFrame}
|
|
344
|
+
live={Boolean(props.livePhase) && m === props.perf.models[0]}
|
|
345
|
+
/>
|
|
346
|
+
)}
|
|
347
|
+
more={{ onReveal: modelsReveal.reveal }}
|
|
348
|
+
/>
|
|
349
|
+
</FoldSection>
|
|
350
|
+
|
|
351
|
+
<FoldSection
|
|
352
|
+
title="Time"
|
|
353
|
+
open={foldTime.open()}
|
|
354
|
+
suffix={timeSummary({
|
|
355
|
+
turns: totals().turns,
|
|
356
|
+
durationMs: activeTimeMs(),
|
|
357
|
+
errors: totals().errors,
|
|
358
|
+
aborts: totals().aborts,
|
|
359
|
+
})}
|
|
360
|
+
colors={props.colors}
|
|
361
|
+
onToggle={foldTime.toggle}
|
|
362
|
+
onDetail={() => openLog(PERF_LOG_KIND_TIME)}
|
|
363
|
+
>
|
|
364
|
+
<For each={phases()}>
|
|
365
|
+
{(p) => (
|
|
366
|
+
<PhaseRow
|
|
367
|
+
spec={p.spec}
|
|
368
|
+
label={p.label}
|
|
369
|
+
ms={p.ms}
|
|
370
|
+
share={p.ms / wall()}
|
|
371
|
+
colors={props.colors}
|
|
372
|
+
lineMax={props.lineMax}
|
|
373
|
+
onSelect={() => openLog(p.key)}
|
|
374
|
+
/>
|
|
375
|
+
)}
|
|
376
|
+
</For>
|
|
377
|
+
</FoldSection>
|
|
378
|
+
|
|
379
|
+
<Show when={props.perf.tools.length > 0}>
|
|
380
|
+
<FoldSection
|
|
381
|
+
title="Slow tools"
|
|
382
|
+
open={foldTools.open()}
|
|
383
|
+
count={props.perf.tools.length}
|
|
384
|
+
suffix={formatSpan(toolTotal())}
|
|
385
|
+
colors={props.colors}
|
|
386
|
+
onToggle={foldTools.toggle}
|
|
387
|
+
onDetail={() => openLog(PERF_PHASE_TOOL)}
|
|
388
|
+
>
|
|
389
|
+
<RowList
|
|
390
|
+
items={props.perf.tools}
|
|
391
|
+
budget={props.rows + toolsReveal.more()}
|
|
392
|
+
colors={props.colors}
|
|
393
|
+
renderItem={(t) => (
|
|
394
|
+
<ToolRow
|
|
395
|
+
tool={t}
|
|
396
|
+
share={toolTotal() > 0 ? t.totalMs / toolTotal() : null}
|
|
397
|
+
colors={props.colors}
|
|
398
|
+
lineMax={props.lineMax}
|
|
399
|
+
onSelect={() => openLog(PERF_PHASE_TOOL, t.name)}
|
|
400
|
+
/>
|
|
401
|
+
)}
|
|
402
|
+
more={{ onReveal: toolsReveal.reveal }}
|
|
403
|
+
/>
|
|
404
|
+
</FoldSection>
|
|
405
|
+
</Show>
|
|
406
|
+
|
|
407
|
+
<Show when={hasTrend()}>
|
|
408
|
+
<FoldSection
|
|
409
|
+
title="Trend"
|
|
410
|
+
open={foldTrend.open()}
|
|
411
|
+
count={props.perf.trend.length}
|
|
412
|
+
colors={props.colors}
|
|
413
|
+
onToggle={foldTrend.toggle}
|
|
414
|
+
onDetail={() => openCharts()}
|
|
415
|
+
>
|
|
416
|
+
<TrendChart
|
|
417
|
+
label={PERF_PHASE_WAIT}
|
|
418
|
+
values={trendWait()}
|
|
419
|
+
fg={props.colors.warning || props.colors.text}
|
|
420
|
+
colors={props.colors}
|
|
421
|
+
lineMax={props.lineMax}
|
|
422
|
+
/>
|
|
423
|
+
<TrendChart
|
|
424
|
+
label="tok/s"
|
|
425
|
+
values={trendRate()}
|
|
426
|
+
fg={props.colors.success}
|
|
427
|
+
colors={props.colors}
|
|
428
|
+
lineMax={props.lineMax}
|
|
429
|
+
/>
|
|
430
|
+
</FoldSection>
|
|
431
|
+
</Show>
|
|
432
|
+
|
|
433
|
+
<Show when={props.perf.history.length > 0}>
|
|
434
|
+
<FoldSection
|
|
435
|
+
title="History"
|
|
436
|
+
open={foldHistory.open()}
|
|
437
|
+
count={props.perf.history.length}
|
|
438
|
+
colors={props.colors}
|
|
439
|
+
onToggle={foldHistory.toggle}
|
|
440
|
+
>
|
|
441
|
+
<RowList
|
|
442
|
+
items={props.perf.history}
|
|
443
|
+
budget={props.rows + historyReveal.more()}
|
|
444
|
+
colors={props.colors}
|
|
445
|
+
renderItem={(row) => (
|
|
446
|
+
<HistoryRow
|
|
447
|
+
row={row}
|
|
448
|
+
colors={props.colors}
|
|
449
|
+
lineMax={props.lineMax}
|
|
450
|
+
current={row.id === props.currentSessionId}
|
|
451
|
+
onSelect={() => props.onSelect(row.id)}
|
|
452
|
+
/>
|
|
453
|
+
)}
|
|
454
|
+
more={{ onReveal: historyReveal.reveal }}
|
|
455
|
+
/>
|
|
456
|
+
</FoldSection>
|
|
457
|
+
</Show>
|
|
458
|
+
</box>
|
|
459
|
+
</Show>
|
|
460
|
+
</box>
|
|
461
|
+
)
|
|
462
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pware.oc.runtime
|
|
3
|
+
*
|
|
4
|
+
* Runtime composition: the only layer that combines opencode + omo into one
|
|
5
|
+
* snapshot, plus the monitor that keeps it fresh and the My work queue.
|
|
6
|
+
*/
|
|
7
|
+
export * from "./pware.oc.runtime.monitor.js"
|
|
8
|
+
export * from "./pware.oc.runtime.source.js"
|
|
9
|
+
export * from "./pware.oc.runtime.snapshotClient.js"
|
|
10
|
+
export * from "./pware.oc.runtime.mywork.js"
|
|
11
|
+
export * from "./pware.oc.runtime.mywork-enrich.js"
|
|
12
|
+
export * from "./pware.oc.runtime.questions.js"
|
|
13
|
+
export * from "./resolver/index.js"
|