opencode-extended-sidebar 0.2.90 → 0.2.91
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 -17
- package/CHANGELOG.md +96 -95
- package/LICENSE +21 -21
- package/README.md +150 -235
- package/oes.json +13 -11
- package/package.json +100 -100
- package/scripts/install-git-hooks.mjs +29 -29
- package/src/pware.oc.core/constants/index.ts +18 -18
- package/src/pware.oc.core/constants/pware.oc.core.constants.eventKind.ts +29 -29
- package/src/pware.oc.core/constants/pware.oc.core.constants.eventName.ts +15 -13
- package/src/pware.oc.core/constants/pware.oc.core.constants.eventType.ts +241 -241
- package/src/pware.oc.core/constants/pware.oc.core.constants.myWork.ts +55 -55
- package/src/pware.oc.core/constants/pware.oc.core.constants.partType.ts +65 -65
- package/src/pware.oc.core/constants/pware.oc.core.constants.phase.ts +58 -58
- package/src/pware.oc.core/constants/pware.oc.core.constants.pulse.ts +63 -63
- package/src/pware.oc.core/constants/pware.oc.core.constants.rowKind.ts +34 -34
- package/src/pware.oc.core/constants/pware.oc.core.constants.status.ts +74 -74
- package/src/pware.oc.core/constants/pware.oc.core.constants.toolName.ts +69 -69
- package/src/pware.oc.core/git/index.ts +7 -7
- package/src/pware.oc.core/git/pware.oc.core.git.ts +237 -237
- package/src/pware.oc.core/git/pware.oc.core.gitignore.ts +87 -87
- package/src/pware.oc.core/index.ts +19 -18
- package/src/pware.oc.core/pware.oc.core.bus.ts +42 -42
- package/src/pware.oc.core/pware.oc.core.cache.ts +48 -48
- package/src/pware.oc.core/pware.oc.core.clipboard.ts +97 -97
- package/src/pware.oc.core/pware.oc.core.debug.ts +280 -280
- package/src/pware.oc.core/pware.oc.core.events.ts +79 -79
- package/src/pware.oc.core/pware.oc.core.glyph.ts +135 -135
- package/src/pware.oc.core/pware.oc.core.layout.ts +234 -234
- package/src/pware.oc.core/pware.oc.core.oes.ts +99 -89
- package/src/pware.oc.core/pware.oc.core.paths.ts +240 -240
- package/src/pware.oc.core/pware.oc.core.preview.ts +74 -74
- package/src/pware.oc.core/pware.oc.core.pulse.ts +726 -726
- package/src/pware.oc.core/pware.oc.core.runtimeConfig.ts +41 -0
- package/src/pware.oc.core/pware.oc.core.sqlite.ts +164 -175
- package/src/pware.oc.core/pware.oc.core.sqliteGateway.ts +89 -0
- package/src/pware.oc.core/pware.oc.core.status.ts +214 -214
- package/src/pware.oc.core/pware.oc.core.timing.ts +63 -63
- package/src/pware.oc.core/pware.oc.core.width.ts +215 -215
- package/src/pware.oc.omo/constants/index.ts +15 -15
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.backgroundTask.ts +18 -18
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.boulderStatus.ts +49 -49
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.docKind.ts +29 -29
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.eventName.ts +11 -11
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.planStatus.ts +60 -60
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.reviewStatus.ts +49 -49
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.startWork.ts +25 -25
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.verdict.ts +38 -38
- package/src/pware.oc.omo/index.ts +7 -7
- package/src/pware.oc.omo/resolver/index.ts +19 -19
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approval.ts +179 -179
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approvalGroup.ts +116 -116
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approvalState.ts +34 -34
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.boulder.ts +503 -503
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.config.ts +40 -40
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.doc.ts +269 -269
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.draftFile.ts +18 -18
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.notepadsFile.ts +18 -18
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.plan.ts +155 -155
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.planFile.ts +363 -361
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.proofFile.ts +18 -18
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.rulesFile.ts +11 -11
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.runContinuationFile.ts +14 -14
- package/src/pware.oc.opencode/constants/index.ts +10 -10
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.eventName.ts +15 -15
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.fileTouch.ts +18 -18
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.questionKind.ts +45 -45
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.sessionStatus.ts +58 -58
- package/src/pware.oc.opencode/index.ts +8 -8
- package/src/pware.oc.opencode/pware.oc.opencode.events.ts +182 -182
- package/src/pware.oc.opencode/pware.oc.opencode.files.ts +399 -399
- package/src/pware.oc.opencode/resolver/index.ts +223 -170
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.file.ts +150 -162
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.question.ts +149 -185
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.session.ts +248 -211
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.todo.ts +22 -26
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.tool.ts +224 -236
- package/src/pware.oc.perf/index.ts +14 -14
- package/src/pware.oc.perf/pware.oc.perf.asciichart.d.ts +15 -15
- package/src/pware.oc.perf/pware.oc.perf.charts.ts +284 -284
- package/src/pware.oc.perf/pware.oc.perf.reader.ts +962 -927
- package/src/pware.oc.perf/pware.oc.perf.realtime.ts +88 -88
- package/src/pware.oc.perf/pware.oc.perf.realtimeBlock.ts +118 -118
- package/src/pware.oc.perf/pware.oc.perf.realtimeCpuRam.ts +79 -79
- package/src/pware.oc.perf/pware.oc.perf.realtimeSampler.ts +71 -71
- package/src/pware.oc.perf/pware.oc.perf.realtimeTimeline.ts +214 -214
- package/src/pware.oc.perf/pware.oc.perf.self.ts +179 -179
- package/src/pware.oc.perf/pware.oc.perf.view.tsx +463 -462
- package/src/pware.oc.runtime/index.ts +13 -13
- package/src/pware.oc.runtime/pware.oc.runtime.monitor.ts +142 -142
- package/src/pware.oc.runtime/pware.oc.runtime.mywork-enrich.ts +87 -79
- package/src/pware.oc.runtime/pware.oc.runtime.mywork.ts +282 -253
- package/src/pware.oc.runtime/pware.oc.runtime.omoRead.ts +37 -0
- package/src/pware.oc.runtime/pware.oc.runtime.questions.ts +83 -95
- package/src/pware.oc.runtime/pware.oc.runtime.snapshotClient.ts +130 -129
- package/src/pware.oc.runtime/pware.oc.runtime.source.ts +105 -105
- package/src/pware.oc.runtime/pware.oc.runtime.worker.ts +44 -43
- package/src/pware.oc.runtime/resolver/index.ts +268 -224
- package/src/pware.oc.runtime/resolver/pware.oc.runtime.resolver.delegate.ts +146 -146
- package/src/pware.oc.ui/index.ts +12 -12
- package/src/pware.oc.ui/pware.oc.ui.chrome.tsx +381 -339
- package/src/pware.oc.ui/pware.oc.ui.glyphs.tsx +144 -144
- package/src/pware.oc.ui/pware.oc.ui.host.tsx +111 -111
- package/src/pware.oc.ui/pware.oc.ui.live.tsx +82 -82
- package/src/pware.oc.ui/pware.oc.ui.menudialogs.tsx +843 -843
- package/src/pware.oc.ui/pware.oc.ui.sections.tsx +552 -526
- package/src/pware.oc.ui/pware.oc.ui.sidebar.tsx +1718 -1629
- package/src/pware.oc.ui.tsx +98 -98
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* pware.oc.perf.realtimeSampler
|
|
3
|
-
*
|
|
4
|
-
* Event-driven sampler: subscribes to `session.updated` events, extracts the
|
|
5
|
-
* per-session cumulative token totals, and feeds them to a
|
|
6
|
-
* `StatRealtimeTimeline`. The subscription is injected so the class stays
|
|
7
|
-
* host-free; the event parsing is a pure exported function.
|
|
8
|
-
*/
|
|
9
|
-
import type { StatRealtimeEventTokens, StatRealtimeTimeline } from "./pware.oc.perf.realtimeTimeline.js"
|
|
10
|
-
|
|
11
|
-
/** Subscribe to realtime events: returns a dispose function. */
|
|
12
|
-
export type RealtimeEventSubscribe = (handler: (evt: unknown) => void) => () => void
|
|
13
|
-
|
|
14
|
-
/** Pull `sessionID` + cumulative `tokens` out of a `session.updated` event; null otherwise. */
|
|
15
|
-
export function extractSessionTokens(evt: unknown): { sessionId: string; tokens: StatRealtimeEventTokens } | null {
|
|
16
|
-
if (!evt || typeof evt !== "object") return null
|
|
17
|
-
const o = evt as Record<string, unknown>
|
|
18
|
-
const props = (
|
|
19
|
-
o.properties && typeof o.properties === "object" ? o.properties : o
|
|
20
|
-
) as Record<string, unknown>
|
|
21
|
-
const sessionId = props.sessionID ?? props.sessionId ?? props.session_id
|
|
22
|
-
const info = props.info as Record<string, unknown> | undefined
|
|
23
|
-
const tokens = info && typeof info === "object" ? (info.tokens as Record<string, unknown> | undefined) : undefined
|
|
24
|
-
if (typeof sessionId !== "string" || !sessionId || !tokens || typeof tokens !== "object") return null
|
|
25
|
-
const num = (v: unknown): number => (typeof v === "number" && Number.isFinite(v) ? v : 0)
|
|
26
|
-
const cache = (
|
|
27
|
-
tokens.cache && typeof tokens.cache === "object" ? tokens.cache : {}
|
|
28
|
-
) as Record<string, unknown>
|
|
29
|
-
return {
|
|
30
|
-
sessionId,
|
|
31
|
-
tokens: {
|
|
32
|
-
input: num(tokens.input),
|
|
33
|
-
output: num(tokens.output),
|
|
34
|
-
reasoning: num(tokens.reasoning),
|
|
35
|
-
cacheRead: num(cache.read),
|
|
36
|
-
cacheWrite: num(cache.write),
|
|
37
|
-
},
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export class EventDriverSampler {
|
|
42
|
-
static create(
|
|
43
|
-
timeline: StatRealtimeTimeline,
|
|
44
|
-
subscribe: RealtimeEventSubscribe,
|
|
45
|
-
now: () => number = Date.now,
|
|
46
|
-
): EventDriverSampler {
|
|
47
|
-
return new EventDriverSampler(timeline, subscribe, now)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
private off: (() => void) | null = null
|
|
51
|
-
|
|
52
|
-
private constructor(
|
|
53
|
-
private readonly timeline: StatRealtimeTimeline,
|
|
54
|
-
private readonly subscribe: RealtimeEventSubscribe,
|
|
55
|
-
private readonly now: () => number,
|
|
56
|
-
) {}
|
|
57
|
-
|
|
58
|
-
start(): void {
|
|
59
|
-
if (this.off) return
|
|
60
|
-
this.off = this.subscribe((evt) => {
|
|
61
|
-
const hit = extractSessionTokens(evt)
|
|
62
|
-
if (!hit) return
|
|
63
|
-
this.timeline.ingest(hit.sessionId, hit.tokens, this.now())
|
|
64
|
-
})
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
stop(): void {
|
|
68
|
-
this.off?.()
|
|
69
|
-
this.off = null
|
|
70
|
-
}
|
|
71
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* pware.oc.perf.realtimeSampler
|
|
3
|
+
*
|
|
4
|
+
* Event-driven sampler: subscribes to `session.updated` events, extracts the
|
|
5
|
+
* per-session cumulative token totals, and feeds them to a
|
|
6
|
+
* `StatRealtimeTimeline`. The subscription is injected so the class stays
|
|
7
|
+
* host-free; the event parsing is a pure exported function.
|
|
8
|
+
*/
|
|
9
|
+
import type { StatRealtimeEventTokens, StatRealtimeTimeline } from "./pware.oc.perf.realtimeTimeline.js"
|
|
10
|
+
|
|
11
|
+
/** Subscribe to realtime events: returns a dispose function. */
|
|
12
|
+
export type RealtimeEventSubscribe = (handler: (evt: unknown) => void) => () => void
|
|
13
|
+
|
|
14
|
+
/** Pull `sessionID` + cumulative `tokens` out of a `session.updated` event; null otherwise. */
|
|
15
|
+
export function extractSessionTokens(evt: unknown): { sessionId: string; tokens: StatRealtimeEventTokens } | null {
|
|
16
|
+
if (!evt || typeof evt !== "object") return null
|
|
17
|
+
const o = evt as Record<string, unknown>
|
|
18
|
+
const props = (
|
|
19
|
+
o.properties && typeof o.properties === "object" ? o.properties : o
|
|
20
|
+
) as Record<string, unknown>
|
|
21
|
+
const sessionId = props.sessionID ?? props.sessionId ?? props.session_id
|
|
22
|
+
const info = props.info as Record<string, unknown> | undefined
|
|
23
|
+
const tokens = info && typeof info === "object" ? (info.tokens as Record<string, unknown> | undefined) : undefined
|
|
24
|
+
if (typeof sessionId !== "string" || !sessionId || !tokens || typeof tokens !== "object") return null
|
|
25
|
+
const num = (v: unknown): number => (typeof v === "number" && Number.isFinite(v) ? v : 0)
|
|
26
|
+
const cache = (
|
|
27
|
+
tokens.cache && typeof tokens.cache === "object" ? tokens.cache : {}
|
|
28
|
+
) as Record<string, unknown>
|
|
29
|
+
return {
|
|
30
|
+
sessionId,
|
|
31
|
+
tokens: {
|
|
32
|
+
input: num(tokens.input),
|
|
33
|
+
output: num(tokens.output),
|
|
34
|
+
reasoning: num(tokens.reasoning),
|
|
35
|
+
cacheRead: num(cache.read),
|
|
36
|
+
cacheWrite: num(cache.write),
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class EventDriverSampler {
|
|
42
|
+
static create(
|
|
43
|
+
timeline: StatRealtimeTimeline,
|
|
44
|
+
subscribe: RealtimeEventSubscribe,
|
|
45
|
+
now: () => number = Date.now,
|
|
46
|
+
): EventDriverSampler {
|
|
47
|
+
return new EventDriverSampler(timeline, subscribe, now)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private off: (() => void) | null = null
|
|
51
|
+
|
|
52
|
+
private constructor(
|
|
53
|
+
private readonly timeline: StatRealtimeTimeline,
|
|
54
|
+
private readonly subscribe: RealtimeEventSubscribe,
|
|
55
|
+
private readonly now: () => number,
|
|
56
|
+
) {}
|
|
57
|
+
|
|
58
|
+
start(): void {
|
|
59
|
+
if (this.off) return
|
|
60
|
+
this.off = this.subscribe((evt) => {
|
|
61
|
+
const hit = extractSessionTokens(evt)
|
|
62
|
+
if (!hit) return
|
|
63
|
+
this.timeline.ingest(hit.sessionId, hit.tokens, this.now())
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
stop(): void {
|
|
68
|
+
this.off?.()
|
|
69
|
+
this.off = null
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -1,214 +1,214 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* pware.oc.perf.realtimeTimeline
|
|
3
|
-
*
|
|
4
|
-
* One shared realtime history for every category the OES realtime chart
|
|
5
|
-
* renders (Tokens / Cache / CPU·RAM / Network). A single wall-clock loop calls
|
|
6
|
-
* `tick(at)` on the UI heartbeat cadence (`TICK_MS`); every tick folds the
|
|
7
|
-
* current per-session cumulative token state and the raw CPU ring into one grid
|
|
8
|
-
* sample, so the chart's right edge always reaches "now" instead of waiting for
|
|
9
|
-
* a host event. Pure state + injectable timestamps — no host, no database.
|
|
10
|
-
*
|
|
11
|
-
* Token/cache rates are a windowed derivative over the trailing
|
|
12
|
-
* `REALTIME_RATE_WINDOW_MS`: `ingest` (exact `session.updated` totals) and
|
|
13
|
-
* `ingestEstimate` (estimated stream deltas for out/reasoning) record level
|
|
14
|
-
* changes, and `tick` reports the average over the window — it falls back to
|
|
15
|
-
* zero when nothing new arrives instead of holding a stale rate. Estimated
|
|
16
|
-
* out/reasoning deltas are reconciled against the exact totals on every
|
|
17
|
-
* `session.updated`, so a streamed token is never counted twice: the exact
|
|
18
|
-
* delta only adds what the estimate has not covered yet.
|
|
19
|
-
*/
|
|
20
|
-
import { cpuCores, cpuPercentOverWindow, ramMb, type CpuRamReading } from "./pware.oc.perf.realtimeCpuRam.js"
|
|
21
|
-
import {
|
|
22
|
-
REALTIME_RATE_WINDOW_MS,
|
|
23
|
-
REALTIME_WINDOW_MS,
|
|
24
|
-
} from "../pware.oc.core/pware.oc.core.timing.js"
|
|
25
|
-
import {
|
|
26
|
-
emptyStatRealtimeSnapshot,
|
|
27
|
-
pushStatRealtimeHistory,
|
|
28
|
-
sumSeries,
|
|
29
|
-
tokenRateToKbit,
|
|
30
|
-
type StatRealtimeSnapshot,
|
|
31
|
-
type StatRealtimeSnapshotHistory,
|
|
32
|
-
} from "./pware.oc.perf.realtime.js"
|
|
33
|
-
|
|
34
|
-
/** Cumulative token totals as reported by one `session.updated` event. */
|
|
35
|
-
export type StatRealtimeEventTokens = {
|
|
36
|
-
input: number
|
|
37
|
-
output: number
|
|
38
|
-
reasoning: number
|
|
39
|
-
cacheRead: number
|
|
40
|
-
cacheWrite: number
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** Which token channel an estimated stream delta feeds. */
|
|
44
|
-
export type StatRealtimeEstKind = "out" | "reasoning"
|
|
45
|
-
|
|
46
|
-
/** A single cumulative-token or streamed-token level change at `at`. */
|
|
47
|
-
type Step = { at: number; n: number }
|
|
48
|
-
|
|
49
|
-
/** Per-session state: last exact totals + stream estimates not yet confirmed. */
|
|
50
|
-
type SessionState = {
|
|
51
|
-
exact: StatRealtimeEventTokens | null
|
|
52
|
-
estOut: number
|
|
53
|
-
estReasoning: number
|
|
54
|
-
lastAt: number
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** Channel names that map 1:1 onto a `StatRealtimeSnapshot` leaf. */
|
|
58
|
-
type TokenChannel = "in" | "out" | "reasoning" | "cacheRead" | "cacheWrite"
|
|
59
|
-
|
|
60
|
-
const TOKEN_CHANNELS: readonly TokenChannel[] = ["in", "out", "reasoning", "cacheRead", "cacheWrite"]
|
|
61
|
-
|
|
62
|
-
function pos(n: number): number {
|
|
63
|
-
return Number.isFinite(n) && n > 0 ? n : 0
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export class StatRealtimeTimeline {
|
|
67
|
-
static build(scope: string | null = null, cores: number = cpuCores()): StatRealtimeTimeline {
|
|
68
|
-
return new StatRealtimeTimeline(scope, cores)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
private readonly scope: string | null
|
|
72
|
-
private readonly cores: number
|
|
73
|
-
private readonly bySession = new Map<string, SessionState>()
|
|
74
|
-
private readonly steps = new Map<TokenChannel, Step[]>()
|
|
75
|
-
private cpuReadings: Array<CpuRamReading & { at: number }> = []
|
|
76
|
-
private history: StatRealtimeSnapshotHistory[] = []
|
|
77
|
-
|
|
78
|
-
private constructor(scope: string | null, cores: number) {
|
|
79
|
-
this.scope = scope
|
|
80
|
-
this.cores = cores
|
|
81
|
-
for (const c of TOKEN_CHANNELS) this.steps.set(c, [])
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/** Exact cumulative totals from one `session.updated` event. */
|
|
85
|
-
ingest(sessionId: string, totals: StatRealtimeEventTokens, at: number): void {
|
|
86
|
-
if (this.scope !== null && sessionId !== this.scope) return
|
|
87
|
-
const st = this.bySession.get(sessionId) ?? {
|
|
88
|
-
exact: null,
|
|
89
|
-
estOut: 0,
|
|
90
|
-
estReasoning: 0,
|
|
91
|
-
lastAt: 0,
|
|
92
|
-
}
|
|
93
|
-
st.lastAt = at
|
|
94
|
-
this.bySession.set(sessionId, st)
|
|
95
|
-
|
|
96
|
-
const prev = st.exact
|
|
97
|
-
if (!prev) {
|
|
98
|
-
// First report is the baseline: estimates seen before it are already
|
|
99
|
-
// inside the reported cumulative, so they must not suppress the first
|
|
100
|
-
// post-baseline delta.
|
|
101
|
-
st.exact = totals
|
|
102
|
-
st.estOut = 0
|
|
103
|
-
st.estReasoning = 0
|
|
104
|
-
return
|
|
105
|
-
}
|
|
106
|
-
this.push("in", totals.input - prev.input, at)
|
|
107
|
-
this.push("cacheRead", totals.cacheRead - prev.cacheRead, at)
|
|
108
|
-
this.push("cacheWrite", totals.cacheWrite - prev.cacheWrite, at)
|
|
109
|
-
this.rebase("out", "estOut", prev.output, totals.output, st, at)
|
|
110
|
-
this.rebase("reasoning", "estReasoning", prev.reasoning, totals.reasoning, st, at)
|
|
111
|
-
st.exact = totals
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/** Estimated streamed delta (text → out, thinking → reasoning). */
|
|
115
|
-
ingestEstimate(sessionId: string, kind: StatRealtimeEstKind, n: number, at: number): void {
|
|
116
|
-
if (this.scope !== null && sessionId !== this.scope) return
|
|
117
|
-
const amount = pos(n)
|
|
118
|
-
if (amount === 0) return
|
|
119
|
-
this.push(kind, amount, at)
|
|
120
|
-
const st = this.bySession.get(sessionId) ?? {
|
|
121
|
-
exact: null,
|
|
122
|
-
estOut: 0,
|
|
123
|
-
estReasoning: 0,
|
|
124
|
-
lastAt: 0,
|
|
125
|
-
}
|
|
126
|
-
st.lastAt = at
|
|
127
|
-
if (kind === "out") st.estOut += amount
|
|
128
|
-
else st.estReasoning += amount
|
|
129
|
-
this.bySession.set(sessionId, st)
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/** A raw process CPU/RAM reading, taken on the same cadence as `tick`. */
|
|
133
|
-
ingestCpuRam(raw: CpuRamReading, at: number): void {
|
|
134
|
-
this.cpuReadings.push({ ...raw, at })
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/** Advance the grid: fold all state into one sample at `at`, prune stale state. */
|
|
138
|
-
tick(at: number): void {
|
|
139
|
-
const cut = at - REALTIME_RATE_WINDOW_MS
|
|
140
|
-
const snap = emptyStatRealtimeSnapshot(at)
|
|
141
|
-
|
|
142
|
-
for (const c of TOKEN_CHANNELS) {
|
|
143
|
-
const kept: Step[] = []
|
|
144
|
-
let sum = 0
|
|
145
|
-
for (const s of this.steps.get(c)!) {
|
|
146
|
-
if (s.at <= cut) continue
|
|
147
|
-
kept.push(s)
|
|
148
|
-
sum += s.n
|
|
149
|
-
}
|
|
150
|
-
this.steps.set(c, kept)
|
|
151
|
-
this.setChannel(snap, c, sum === 0 ? null : (sum / REALTIME_RATE_WINDOW_MS) * 1000)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Network is estimated from the same token flow: what the session sends to
|
|
155
|
-
// the model (input + cache) is `out`, what streams back (output + reasoning)
|
|
156
|
-
// is `in`. Real socket bytes never reach this plugin (model traffic lives in
|
|
157
|
-
// the server process), so kbit/s here means "≈ tokens × 4 bytes", not a meter.
|
|
158
|
-
snap.network.out = tokenRateToKbit(sumSeries(snap.tokens.in, sumSeries(snap.cache.read, snap.cache.write)))
|
|
159
|
-
snap.network.in = tokenRateToKbit(sumSeries(snap.tokens.out, snap.tokens.reasoning))
|
|
160
|
-
|
|
161
|
-
this.cpuReadings = this.cpuReadings.filter((r) => r.at > cut)
|
|
162
|
-
snap.cpuRam.cpu = cpuPercentOverWindow(this.cpuReadings, this.cores, REALTIME_RATE_WINDOW_MS)
|
|
163
|
-
const last = this.cpuReadings[this.cpuReadings.length - 1]
|
|
164
|
-
snap.cpuRam.ram = last ? ramMb(last.rss) : null
|
|
165
|
-
|
|
166
|
-
this.history = pushStatRealtimeHistory(this.history, snap, REALTIME_WINDOW_MS)
|
|
167
|
-
// Drop est-only transient states once idle — a session with an exact
|
|
168
|
-
// baseline stays, so its first post-idle burst still diffs correctly.
|
|
169
|
-
const stale = at - REALTIME_WINDOW_MS
|
|
170
|
-
for (const [sid, st] of this.bySession) {
|
|
171
|
-
if (st.exact === null && st.lastAt < stale) this.bySession.delete(sid)
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/** The rolling grid samples in time order — already on the fixed grid. */
|
|
176
|
-
getTimeline(): StatRealtimeSnapshotHistory[] {
|
|
177
|
-
return this.history
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
reset(): void {
|
|
181
|
-
this.bySession.clear()
|
|
182
|
-
for (const c of TOKEN_CHANNELS) this.steps.set(c, [])
|
|
183
|
-
this.cpuReadings = []
|
|
184
|
-
this.history = []
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
private push(channel: TokenChannel, n: number, at: number): void {
|
|
188
|
-
const amount = pos(n)
|
|
189
|
-
if (amount === 0) return
|
|
190
|
-
this.steps.get(channel)!.push({ at, n: amount })
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
private rebase(
|
|
194
|
-
channel: "out" | "reasoning",
|
|
195
|
-
estKey: "estOut" | "estReasoning",
|
|
196
|
-
prevVal: number,
|
|
197
|
-
nextVal: number,
|
|
198
|
-
st: SessionState,
|
|
199
|
-
at: number,
|
|
200
|
-
): void {
|
|
201
|
-
const delta = pos(nextVal - prevVal)
|
|
202
|
-
const uncovered = Math.max(0, delta - st[estKey])
|
|
203
|
-
st[estKey] = 0
|
|
204
|
-
this.push(channel, uncovered, at)
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
private setChannel(snap: StatRealtimeSnapshot, c: TokenChannel, rate: number | null): void {
|
|
208
|
-
if (c === "in") snap.tokens.in = rate
|
|
209
|
-
else if (c === "out") snap.tokens.out = rate
|
|
210
|
-
else if (c === "reasoning") snap.tokens.reasoning = rate
|
|
211
|
-
else if (c === "cacheRead") snap.cache.read = rate
|
|
212
|
-
else snap.cache.write = rate
|
|
213
|
-
}
|
|
214
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* pware.oc.perf.realtimeTimeline
|
|
3
|
+
*
|
|
4
|
+
* One shared realtime history for every category the OES realtime chart
|
|
5
|
+
* renders (Tokens / Cache / CPU·RAM / Network). A single wall-clock loop calls
|
|
6
|
+
* `tick(at)` on the UI heartbeat cadence (`TICK_MS`); every tick folds the
|
|
7
|
+
* current per-session cumulative token state and the raw CPU ring into one grid
|
|
8
|
+
* sample, so the chart's right edge always reaches "now" instead of waiting for
|
|
9
|
+
* a host event. Pure state + injectable timestamps — no host, no database.
|
|
10
|
+
*
|
|
11
|
+
* Token/cache rates are a windowed derivative over the trailing
|
|
12
|
+
* `REALTIME_RATE_WINDOW_MS`: `ingest` (exact `session.updated` totals) and
|
|
13
|
+
* `ingestEstimate` (estimated stream deltas for out/reasoning) record level
|
|
14
|
+
* changes, and `tick` reports the average over the window — it falls back to
|
|
15
|
+
* zero when nothing new arrives instead of holding a stale rate. Estimated
|
|
16
|
+
* out/reasoning deltas are reconciled against the exact totals on every
|
|
17
|
+
* `session.updated`, so a streamed token is never counted twice: the exact
|
|
18
|
+
* delta only adds what the estimate has not covered yet.
|
|
19
|
+
*/
|
|
20
|
+
import { cpuCores, cpuPercentOverWindow, ramMb, type CpuRamReading } from "./pware.oc.perf.realtimeCpuRam.js"
|
|
21
|
+
import {
|
|
22
|
+
REALTIME_RATE_WINDOW_MS,
|
|
23
|
+
REALTIME_WINDOW_MS,
|
|
24
|
+
} from "../pware.oc.core/pware.oc.core.timing.js"
|
|
25
|
+
import {
|
|
26
|
+
emptyStatRealtimeSnapshot,
|
|
27
|
+
pushStatRealtimeHistory,
|
|
28
|
+
sumSeries,
|
|
29
|
+
tokenRateToKbit,
|
|
30
|
+
type StatRealtimeSnapshot,
|
|
31
|
+
type StatRealtimeSnapshotHistory,
|
|
32
|
+
} from "./pware.oc.perf.realtime.js"
|
|
33
|
+
|
|
34
|
+
/** Cumulative token totals as reported by one `session.updated` event. */
|
|
35
|
+
export type StatRealtimeEventTokens = {
|
|
36
|
+
input: number
|
|
37
|
+
output: number
|
|
38
|
+
reasoning: number
|
|
39
|
+
cacheRead: number
|
|
40
|
+
cacheWrite: number
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Which token channel an estimated stream delta feeds. */
|
|
44
|
+
export type StatRealtimeEstKind = "out" | "reasoning"
|
|
45
|
+
|
|
46
|
+
/** A single cumulative-token or streamed-token level change at `at`. */
|
|
47
|
+
type Step = { at: number; n: number }
|
|
48
|
+
|
|
49
|
+
/** Per-session state: last exact totals + stream estimates not yet confirmed. */
|
|
50
|
+
type SessionState = {
|
|
51
|
+
exact: StatRealtimeEventTokens | null
|
|
52
|
+
estOut: number
|
|
53
|
+
estReasoning: number
|
|
54
|
+
lastAt: number
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Channel names that map 1:1 onto a `StatRealtimeSnapshot` leaf. */
|
|
58
|
+
type TokenChannel = "in" | "out" | "reasoning" | "cacheRead" | "cacheWrite"
|
|
59
|
+
|
|
60
|
+
const TOKEN_CHANNELS: readonly TokenChannel[] = ["in", "out", "reasoning", "cacheRead", "cacheWrite"]
|
|
61
|
+
|
|
62
|
+
function pos(n: number): number {
|
|
63
|
+
return Number.isFinite(n) && n > 0 ? n : 0
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export class StatRealtimeTimeline {
|
|
67
|
+
static build(scope: string | null = null, cores: number = cpuCores()): StatRealtimeTimeline {
|
|
68
|
+
return new StatRealtimeTimeline(scope, cores)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private readonly scope: string | null
|
|
72
|
+
private readonly cores: number
|
|
73
|
+
private readonly bySession = new Map<string, SessionState>()
|
|
74
|
+
private readonly steps = new Map<TokenChannel, Step[]>()
|
|
75
|
+
private cpuReadings: Array<CpuRamReading & { at: number }> = []
|
|
76
|
+
private history: StatRealtimeSnapshotHistory[] = []
|
|
77
|
+
|
|
78
|
+
private constructor(scope: string | null, cores: number) {
|
|
79
|
+
this.scope = scope
|
|
80
|
+
this.cores = cores
|
|
81
|
+
for (const c of TOKEN_CHANNELS) this.steps.set(c, [])
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Exact cumulative totals from one `session.updated` event. */
|
|
85
|
+
ingest(sessionId: string, totals: StatRealtimeEventTokens, at: number): void {
|
|
86
|
+
if (this.scope !== null && sessionId !== this.scope) return
|
|
87
|
+
const st = this.bySession.get(sessionId) ?? {
|
|
88
|
+
exact: null,
|
|
89
|
+
estOut: 0,
|
|
90
|
+
estReasoning: 0,
|
|
91
|
+
lastAt: 0,
|
|
92
|
+
}
|
|
93
|
+
st.lastAt = at
|
|
94
|
+
this.bySession.set(sessionId, st)
|
|
95
|
+
|
|
96
|
+
const prev = st.exact
|
|
97
|
+
if (!prev) {
|
|
98
|
+
// First report is the baseline: estimates seen before it are already
|
|
99
|
+
// inside the reported cumulative, so they must not suppress the first
|
|
100
|
+
// post-baseline delta.
|
|
101
|
+
st.exact = totals
|
|
102
|
+
st.estOut = 0
|
|
103
|
+
st.estReasoning = 0
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
this.push("in", totals.input - prev.input, at)
|
|
107
|
+
this.push("cacheRead", totals.cacheRead - prev.cacheRead, at)
|
|
108
|
+
this.push("cacheWrite", totals.cacheWrite - prev.cacheWrite, at)
|
|
109
|
+
this.rebase("out", "estOut", prev.output, totals.output, st, at)
|
|
110
|
+
this.rebase("reasoning", "estReasoning", prev.reasoning, totals.reasoning, st, at)
|
|
111
|
+
st.exact = totals
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Estimated streamed delta (text → out, thinking → reasoning). */
|
|
115
|
+
ingestEstimate(sessionId: string, kind: StatRealtimeEstKind, n: number, at: number): void {
|
|
116
|
+
if (this.scope !== null && sessionId !== this.scope) return
|
|
117
|
+
const amount = pos(n)
|
|
118
|
+
if (amount === 0) return
|
|
119
|
+
this.push(kind, amount, at)
|
|
120
|
+
const st = this.bySession.get(sessionId) ?? {
|
|
121
|
+
exact: null,
|
|
122
|
+
estOut: 0,
|
|
123
|
+
estReasoning: 0,
|
|
124
|
+
lastAt: 0,
|
|
125
|
+
}
|
|
126
|
+
st.lastAt = at
|
|
127
|
+
if (kind === "out") st.estOut += amount
|
|
128
|
+
else st.estReasoning += amount
|
|
129
|
+
this.bySession.set(sessionId, st)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** A raw process CPU/RAM reading, taken on the same cadence as `tick`. */
|
|
133
|
+
ingestCpuRam(raw: CpuRamReading, at: number): void {
|
|
134
|
+
this.cpuReadings.push({ ...raw, at })
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Advance the grid: fold all state into one sample at `at`, prune stale state. */
|
|
138
|
+
tick(at: number): void {
|
|
139
|
+
const cut = at - REALTIME_RATE_WINDOW_MS
|
|
140
|
+
const snap = emptyStatRealtimeSnapshot(at)
|
|
141
|
+
|
|
142
|
+
for (const c of TOKEN_CHANNELS) {
|
|
143
|
+
const kept: Step[] = []
|
|
144
|
+
let sum = 0
|
|
145
|
+
for (const s of this.steps.get(c)!) {
|
|
146
|
+
if (s.at <= cut) continue
|
|
147
|
+
kept.push(s)
|
|
148
|
+
sum += s.n
|
|
149
|
+
}
|
|
150
|
+
this.steps.set(c, kept)
|
|
151
|
+
this.setChannel(snap, c, sum === 0 ? null : (sum / REALTIME_RATE_WINDOW_MS) * 1000)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Network is estimated from the same token flow: what the session sends to
|
|
155
|
+
// the model (input + cache) is `out`, what streams back (output + reasoning)
|
|
156
|
+
// is `in`. Real socket bytes never reach this plugin (model traffic lives in
|
|
157
|
+
// the server process), so kbit/s here means "≈ tokens × 4 bytes", not a meter.
|
|
158
|
+
snap.network.out = tokenRateToKbit(sumSeries(snap.tokens.in, sumSeries(snap.cache.read, snap.cache.write)))
|
|
159
|
+
snap.network.in = tokenRateToKbit(sumSeries(snap.tokens.out, snap.tokens.reasoning))
|
|
160
|
+
|
|
161
|
+
this.cpuReadings = this.cpuReadings.filter((r) => r.at > cut)
|
|
162
|
+
snap.cpuRam.cpu = cpuPercentOverWindow(this.cpuReadings, this.cores, REALTIME_RATE_WINDOW_MS)
|
|
163
|
+
const last = this.cpuReadings[this.cpuReadings.length - 1]
|
|
164
|
+
snap.cpuRam.ram = last ? ramMb(last.rss) : null
|
|
165
|
+
|
|
166
|
+
this.history = pushStatRealtimeHistory(this.history, snap, REALTIME_WINDOW_MS)
|
|
167
|
+
// Drop est-only transient states once idle — a session with an exact
|
|
168
|
+
// baseline stays, so its first post-idle burst still diffs correctly.
|
|
169
|
+
const stale = at - REALTIME_WINDOW_MS
|
|
170
|
+
for (const [sid, st] of this.bySession) {
|
|
171
|
+
if (st.exact === null && st.lastAt < stale) this.bySession.delete(sid)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** The rolling grid samples in time order — already on the fixed grid. */
|
|
176
|
+
getTimeline(): StatRealtimeSnapshotHistory[] {
|
|
177
|
+
return this.history
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
reset(): void {
|
|
181
|
+
this.bySession.clear()
|
|
182
|
+
for (const c of TOKEN_CHANNELS) this.steps.set(c, [])
|
|
183
|
+
this.cpuReadings = []
|
|
184
|
+
this.history = []
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private push(channel: TokenChannel, n: number, at: number): void {
|
|
188
|
+
const amount = pos(n)
|
|
189
|
+
if (amount === 0) return
|
|
190
|
+
this.steps.get(channel)!.push({ at, n: amount })
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private rebase(
|
|
194
|
+
channel: "out" | "reasoning",
|
|
195
|
+
estKey: "estOut" | "estReasoning",
|
|
196
|
+
prevVal: number,
|
|
197
|
+
nextVal: number,
|
|
198
|
+
st: SessionState,
|
|
199
|
+
at: number,
|
|
200
|
+
): void {
|
|
201
|
+
const delta = pos(nextVal - prevVal)
|
|
202
|
+
const uncovered = Math.max(0, delta - st[estKey])
|
|
203
|
+
st[estKey] = 0
|
|
204
|
+
this.push(channel, uncovered, at)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private setChannel(snap: StatRealtimeSnapshot, c: TokenChannel, rate: number | null): void {
|
|
208
|
+
if (c === "in") snap.tokens.in = rate
|
|
209
|
+
else if (c === "out") snap.tokens.out = rate
|
|
210
|
+
else if (c === "reasoning") snap.tokens.reasoning = rate
|
|
211
|
+
else if (c === "cacheRead") snap.cache.read = rate
|
|
212
|
+
else snap.cache.write = rate
|
|
213
|
+
}
|
|
214
|
+
}
|