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.
Files changed (105) hide show
  1. package/.oesignore +17 -0
  2. package/CHANGELOG.md +95 -0
  3. package/LICENSE +21 -0
  4. package/README.md +235 -0
  5. package/oes.json +11 -0
  6. package/package.json +100 -0
  7. package/scripts/install-git-hooks.mjs +29 -0
  8. package/src/pware.oc.core/constants/index.ts +18 -0
  9. package/src/pware.oc.core/constants/pware.oc.core.constants.eventKind.ts +29 -0
  10. package/src/pware.oc.core/constants/pware.oc.core.constants.eventName.ts +13 -0
  11. package/src/pware.oc.core/constants/pware.oc.core.constants.eventType.ts +241 -0
  12. package/src/pware.oc.core/constants/pware.oc.core.constants.myWork.ts +55 -0
  13. package/src/pware.oc.core/constants/pware.oc.core.constants.partType.ts +65 -0
  14. package/src/pware.oc.core/constants/pware.oc.core.constants.phase.ts +58 -0
  15. package/src/pware.oc.core/constants/pware.oc.core.constants.pulse.ts +63 -0
  16. package/src/pware.oc.core/constants/pware.oc.core.constants.rowKind.ts +34 -0
  17. package/src/pware.oc.core/constants/pware.oc.core.constants.status.ts +74 -0
  18. package/src/pware.oc.core/constants/pware.oc.core.constants.toolName.ts +69 -0
  19. package/src/pware.oc.core/git/index.ts +7 -0
  20. package/src/pware.oc.core/git/pware.oc.core.git.ts +237 -0
  21. package/src/pware.oc.core/git/pware.oc.core.gitignore.ts +87 -0
  22. package/src/pware.oc.core/index.ts +18 -0
  23. package/src/pware.oc.core/pware.oc.core.bus.ts +42 -0
  24. package/src/pware.oc.core/pware.oc.core.cache.ts +48 -0
  25. package/src/pware.oc.core/pware.oc.core.clipboard.ts +97 -0
  26. package/src/pware.oc.core/pware.oc.core.debug.ts +280 -0
  27. package/src/pware.oc.core/pware.oc.core.events.ts +79 -0
  28. package/src/pware.oc.core/pware.oc.core.glyph.ts +135 -0
  29. package/src/pware.oc.core/pware.oc.core.layout.ts +234 -0
  30. package/src/pware.oc.core/pware.oc.core.oes.ts +89 -0
  31. package/src/pware.oc.core/pware.oc.core.paths.ts +240 -0
  32. package/src/pware.oc.core/pware.oc.core.preview.ts +74 -0
  33. package/src/pware.oc.core/pware.oc.core.pulse.ts +726 -0
  34. package/src/pware.oc.core/pware.oc.core.sqlite.ts +175 -0
  35. package/src/pware.oc.core/pware.oc.core.status.ts +214 -0
  36. package/src/pware.oc.core/pware.oc.core.timing.ts +63 -0
  37. package/src/pware.oc.core/pware.oc.core.width.ts +215 -0
  38. package/src/pware.oc.omo/constants/index.ts +15 -0
  39. package/src/pware.oc.omo/constants/pware.oc.omo.constants.backgroundTask.ts +18 -0
  40. package/src/pware.oc.omo/constants/pware.oc.omo.constants.boulderStatus.ts +49 -0
  41. package/src/pware.oc.omo/constants/pware.oc.omo.constants.docKind.ts +29 -0
  42. package/src/pware.oc.omo/constants/pware.oc.omo.constants.eventName.ts +11 -0
  43. package/src/pware.oc.omo/constants/pware.oc.omo.constants.planStatus.ts +60 -0
  44. package/src/pware.oc.omo/constants/pware.oc.omo.constants.reviewStatus.ts +49 -0
  45. package/src/pware.oc.omo/constants/pware.oc.omo.constants.startWork.ts +25 -0
  46. package/src/pware.oc.omo/constants/pware.oc.omo.constants.verdict.ts +38 -0
  47. package/src/pware.oc.omo/index.ts +7 -0
  48. package/src/pware.oc.omo/resolver/index.ts +19 -0
  49. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approval.ts +179 -0
  50. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approvalGroup.ts +116 -0
  51. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approvalState.ts +34 -0
  52. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.boulder.ts +503 -0
  53. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.config.ts +40 -0
  54. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.doc.ts +269 -0
  55. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.draftFile.ts +18 -0
  56. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.notepadsFile.ts +18 -0
  57. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.plan.ts +155 -0
  58. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.planFile.ts +361 -0
  59. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.proofFile.ts +18 -0
  60. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.rulesFile.ts +11 -0
  61. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.runContinuationFile.ts +14 -0
  62. package/src/pware.oc.opencode/constants/index.ts +10 -0
  63. package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.eventName.ts +15 -0
  64. package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.fileTouch.ts +18 -0
  65. package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.questionKind.ts +45 -0
  66. package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.sessionStatus.ts +58 -0
  67. package/src/pware.oc.opencode/index.ts +8 -0
  68. package/src/pware.oc.opencode/pware.oc.opencode.events.ts +182 -0
  69. package/src/pware.oc.opencode/pware.oc.opencode.files.ts +399 -0
  70. package/src/pware.oc.opencode/resolver/index.ts +170 -0
  71. package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.file.ts +162 -0
  72. package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.question.ts +185 -0
  73. package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.session.ts +211 -0
  74. package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.todo.ts +26 -0
  75. package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.tool.ts +236 -0
  76. package/src/pware.oc.perf/index.ts +14 -0
  77. package/src/pware.oc.perf/pware.oc.perf.asciichart.d.ts +15 -0
  78. package/src/pware.oc.perf/pware.oc.perf.charts.ts +284 -0
  79. package/src/pware.oc.perf/pware.oc.perf.reader.ts +927 -0
  80. package/src/pware.oc.perf/pware.oc.perf.realtime.ts +88 -0
  81. package/src/pware.oc.perf/pware.oc.perf.realtimeBlock.ts +118 -0
  82. package/src/pware.oc.perf/pware.oc.perf.realtimeCpuRam.ts +79 -0
  83. package/src/pware.oc.perf/pware.oc.perf.realtimeSampler.ts +71 -0
  84. package/src/pware.oc.perf/pware.oc.perf.realtimeTimeline.ts +214 -0
  85. package/src/pware.oc.perf/pware.oc.perf.self.ts +179 -0
  86. package/src/pware.oc.perf/pware.oc.perf.view.tsx +462 -0
  87. package/src/pware.oc.runtime/index.ts +13 -0
  88. package/src/pware.oc.runtime/pware.oc.runtime.monitor.ts +142 -0
  89. package/src/pware.oc.runtime/pware.oc.runtime.mywork-enrich.ts +79 -0
  90. package/src/pware.oc.runtime/pware.oc.runtime.mywork.ts +253 -0
  91. package/src/pware.oc.runtime/pware.oc.runtime.questions.ts +95 -0
  92. package/src/pware.oc.runtime/pware.oc.runtime.snapshotClient.ts +129 -0
  93. package/src/pware.oc.runtime/pware.oc.runtime.source.ts +105 -0
  94. package/src/pware.oc.runtime/pware.oc.runtime.worker.ts +43 -0
  95. package/src/pware.oc.runtime/resolver/index.ts +224 -0
  96. package/src/pware.oc.runtime/resolver/pware.oc.runtime.resolver.delegate.ts +146 -0
  97. package/src/pware.oc.ui/index.ts +12 -0
  98. package/src/pware.oc.ui/pware.oc.ui.chrome.tsx +339 -0
  99. package/src/pware.oc.ui/pware.oc.ui.glyphs.tsx +144 -0
  100. package/src/pware.oc.ui/pware.oc.ui.host.tsx +111 -0
  101. package/src/pware.oc.ui/pware.oc.ui.live.tsx +82 -0
  102. package/src/pware.oc.ui/pware.oc.ui.menudialogs.tsx +843 -0
  103. package/src/pware.oc.ui/pware.oc.ui.sections.tsx +526 -0
  104. package/src/pware.oc.ui/pware.oc.ui.sidebar.tsx +1629 -0
  105. package/src/pware.oc.ui.tsx +98 -0
@@ -0,0 +1,211 @@
1
+ /**
2
+ * pware.oc.core.opencode.resolver.session
3
+ *
4
+ * Session rows → SessionView plus the hierarchy queries (current, children,
5
+ * siblings, recent mains, lookups by id). Read-only against opencode.db.
6
+ */
7
+ import { uniqueIds, type SqlDb } from "../../pware.oc.core/pware.oc.core.sqlite.js"
8
+ import {
9
+ SESSION_STATE_ARCHIVED,
10
+ SESSION_STATE_AWAITING_BACKGROUND,
11
+ SESSION_STATE_IDLE,
12
+ SESSION_STATE_STREAMING,
13
+ SESSION_STATE_UNKNOWN,
14
+ SESSION_STATUS_ARCHIVED,
15
+ SESSION_STATUS_IDLE,
16
+ SESSION_STATUS_RUNNING,
17
+ SESSION_STATUS_UNKNOWN,
18
+ type AgentStatus,
19
+ type SessionState,
20
+ } from "../constants/pware.oc.opencode.constants.sessionStatus.js"
21
+
22
+ export type { AgentStatus, SessionState }
23
+
24
+ export type SessionRow = {
25
+ id: string
26
+ project_id: string
27
+ parent_id: string | null
28
+ directory: string
29
+ title: string
30
+ agent: string | null
31
+ model: string | null
32
+ cost: number
33
+ tokens_input: number
34
+ tokens_output: number
35
+ tokens_reasoning: number
36
+ time_created: number
37
+ time_updated: number
38
+ time_archived: number | null
39
+ /** 1 when the session has at least one part, 0 for an empty shell (ghost). */
40
+ has_content: number
41
+ }
42
+
43
+ /** Finer-grained "is this session still working" state for approval rows. */
44
+ export type SessionActivityState = {
45
+ running: boolean
46
+ state: SessionState
47
+ }
48
+
49
+ export type SessionView = {
50
+ id: string
51
+ title: string
52
+ agent: string
53
+ status: AgentStatus
54
+ isMain: boolean
55
+ parentId: string | null
56
+ directory: string
57
+ tokensTotal: number
58
+ cost: number
59
+ timeUpdated: number
60
+ ageMs: number
61
+ hasContent: boolean
62
+ }
63
+
64
+ const RUNNING_MS = 2 * 60 * 1000
65
+
66
+ const SESSION_SELECT = `
67
+ id, project_id, parent_id, directory, title, agent, model,
68
+ cost, tokens_input, tokens_output, tokens_reasoning,
69
+ time_created, time_updated, time_archived,
70
+ EXISTS (SELECT 1 FROM part WHERE part.session_id = session.id) AS has_content
71
+ `
72
+
73
+ export function inferStatus(row: SessionRow, now = Date.now()): AgentStatus {
74
+ if (row.time_archived) return SESSION_STATUS_ARCHIVED
75
+ const age = now - (row.time_updated || 0)
76
+ if (age < 0) return SESSION_STATUS_UNKNOWN
77
+ if (age <= RUNNING_MS) return SESSION_STATUS_RUNNING
78
+ return SESSION_STATUS_IDLE
79
+ }
80
+
81
+ export function toSessionView(row: SessionRow, now = Date.now()): SessionView {
82
+ const ti = row.tokens_input || 0
83
+ const to = row.tokens_output || 0
84
+ const tr = row.tokens_reasoning || 0
85
+ return {
86
+ id: row.id,
87
+ title: row.title?.trim() || "untitled",
88
+ agent: row.agent?.trim() || "unknown",
89
+ status: inferStatus(row, now),
90
+ isMain: !row.parent_id,
91
+ parentId: row.parent_id,
92
+ directory: row.directory,
93
+ tokensTotal: ti + to + tr,
94
+ cost: row.cost || 0,
95
+ timeUpdated: row.time_updated,
96
+ ageMs: Math.max(0, now - (row.time_updated || 0)),
97
+ hasContent: row.has_content === 1,
98
+ }
99
+ }
100
+
101
+ /** A session with real content (≥1 part). A ghost — created but never prompted — is false. */
102
+ export function isRealSession(view: Pick<SessionView, "hasContent"> | null | undefined): boolean {
103
+ return Boolean(view?.hasContent)
104
+ }
105
+
106
+ /**
107
+ * Recompute a `SessionView` status against a fresh clock. The live snapshot
108
+ * cache refreshes `ageMs` on every hit, so `status` (also age-derived) must be
109
+ * refreshed with it — otherwise a session that went idle keeps reading running.
110
+ */
111
+ export function refreshSessionStatus(view: SessionView, now: number): AgentStatus {
112
+ if (view.status === SESSION_STATUS_ARCHIVED) return SESSION_STATUS_ARCHIVED
113
+ const age = now - view.timeUpdated
114
+ if (age < 0) return SESSION_STATUS_UNKNOWN
115
+ if (age <= RUNNING_MS) return SESSION_STATUS_RUNNING
116
+ return SESSION_STATUS_IDLE
117
+ }
118
+
119
+ export function getSessionById(db: SqlDb, id: string): SessionRow | null {
120
+ return db.get<SessionRow>(
121
+ `SELECT ${SESSION_SELECT} FROM session WHERE id = ? LIMIT 1`,
122
+ id,
123
+ )
124
+ }
125
+
126
+ /**
127
+ * Is this session still working, and how? Streaming = a fresh `time_updated`;
128
+ * awaiting-background = stale in SQLite but the caller's already-resolved
129
+ * `backgroundTaskActive` (`.omo/run-continuation` marker) is true;
130
+ * idle/archived/unknown otherwise. The omo layer resolves the marker — this
131
+ * opencode function never reads omo files.
132
+ */
133
+ export function sessionActivityState(
134
+ db: SqlDb,
135
+ sessionId: string,
136
+ opts?: { backgroundTaskActive?: boolean | null; now?: number },
137
+ ): SessionActivityState {
138
+ const row = getSessionById(db, sessionId)
139
+ if (!row) return { running: false, state: SESSION_STATE_UNKNOWN }
140
+ if (row.time_archived) return { running: false, state: SESSION_STATE_ARCHIVED }
141
+ const now = opts?.now ?? Date.now()
142
+ if (now - row.time_updated <= RUNNING_MS) return { running: true, state: SESSION_STATE_STREAMING }
143
+ if (opts?.backgroundTaskActive) return { running: true, state: SESSION_STATE_AWAITING_BACKGROUND }
144
+ return { running: false, state: SESSION_STATE_IDLE }
145
+ }
146
+
147
+ export function listChildSessions(db: SqlDb, parentId: string): SessionRow[] {
148
+ return db.all<SessionRow>(
149
+ `SELECT ${SESSION_SELECT} FROM session WHERE parent_id = ? ORDER BY time_updated DESC LIMIT 40`,
150
+ parentId,
151
+ )
152
+ }
153
+
154
+ export function listSiblingSessions(db: SqlDb, parentId: string, excludeId: string): SessionRow[] {
155
+ return db.all<SessionRow>(
156
+ `SELECT ${SESSION_SELECT} FROM session
157
+ WHERE parent_id = ? AND id != ?
158
+ ORDER BY time_updated DESC LIMIT 40`,
159
+ parentId,
160
+ excludeId,
161
+ )
162
+ }
163
+
164
+ const RECENT_LIMIT = 4
165
+
166
+ export function listRecentMainSessions(
167
+ db: SqlDb,
168
+ opts: { projectId: string; limit?: number },
169
+ ): SessionRow[] {
170
+ const limit = Math.max(1, Math.min(opts.limit ?? RECENT_LIMIT, 20))
171
+ try {
172
+ return db.all<SessionRow>(
173
+ `SELECT ${SESSION_SELECT} FROM session
174
+ WHERE parent_id IS NULL
175
+ AND (time_archived IS NULL OR time_archived = 0)
176
+ AND project_id = ?
177
+ AND EXISTS (SELECT 1 FROM part WHERE part.session_id = session.id)
178
+ ORDER BY time_updated DESC
179
+ LIMIT ${limit}`,
180
+ opts.projectId,
181
+ )
182
+ } catch {
183
+ return []
184
+ }
185
+ }
186
+
187
+ export function getSessionsByIds(db: SqlDb, ids: string[]): SessionRow[] {
188
+ const clean = uniqueIds(ids)
189
+ if (clean.length === 0) return []
190
+ const placeholders = clean.map(() => "?").join(",")
191
+ return db.all<SessionRow>(
192
+ `SELECT ${SESSION_SELECT} FROM session WHERE id IN (${placeholders})`,
193
+ ...clean,
194
+ )
195
+ }
196
+
197
+ export function sessionScanStamp(db: SqlDb, sessionId: string): string {
198
+ try {
199
+ const s = db.get<{ time_updated: number }>(
200
+ `SELECT time_updated FROM session WHERE id = ? LIMIT 1`,
201
+ sessionId,
202
+ )
203
+ const p = db.get<{ m: number | null }>(
204
+ `SELECT MAX(time_updated) AS m FROM part WHERE session_id = ?`,
205
+ sessionId,
206
+ )
207
+ return `${s?.time_updated ?? 0}|${p?.m ?? 0}`
208
+ } catch {
209
+ return "x"
210
+ }
211
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * pware.oc.core.opencode.resolver.todo
3
+ *
4
+ * Todo rows for one session — content, status, priority, position.
5
+ */
6
+ import type { SqlDb } from "../../pware.oc.core/pware.oc.core.sqlite.js"
7
+
8
+ export type TodoRow = {
9
+ content: string
10
+ status: string
11
+ priority: string
12
+ position: number
13
+ }
14
+
15
+ export function listTodos(db: SqlDb, sessionId: string): TodoRow[] {
16
+ try {
17
+ return db.all<TodoRow>(
18
+ `SELECT content, status, priority, position
19
+ FROM todo WHERE session_id = ?
20
+ ORDER BY position ASC LIMIT 40`,
21
+ sessionId,
22
+ )
23
+ } catch {
24
+ return []
25
+ }
26
+ }
@@ -0,0 +1,236 @@
1
+ /**
2
+ * pware.oc.core.opencode.resolver.tool
3
+ *
4
+ * Tool-call parts → ToolView (name, status, timings). Metadata only — never
5
+ * args or outputs. Feeds the Current and Sessions tabs.
6
+ */
7
+ import { preferToolLabel, shortToolLabel, toEpochMs, type ToolHit } from "../../pware.oc.core/pware.oc.core.pulse.js"
8
+ import { str } from "../../pware.oc.core/pware.oc.core.paths.js"
9
+ import { uniqueIds, type SqlDb } from "../../pware.oc.core/pware.oc.core.sqlite.js"
10
+ import { toToolStatus, type ToolStatus } from "../../pware.oc.core/pware.oc.core.status.js"
11
+ import {
12
+ TOOL_STATUS_COMPLETED,
13
+ TOOL_STATUS_ERROR,
14
+ TOOL_STATUS_PENDING,
15
+ TOOL_STATUS_RUNNING,
16
+ } from "../../pware.oc.core/constants/pware.oc.core.constants.status.js"
17
+ import { PART_TYPE_TOOL } from "../../pware.oc.core/constants/pware.oc.core.constants.partType.js"
18
+
19
+ export type { ToolStatus }
20
+
21
+ export { toToolStatus as normalizeToolStatus }
22
+
23
+ export type ToolView = {
24
+ id: string
25
+ callId?: string | null
26
+ /** Display label: command / file / task description, not just "bash" or "task". */
27
+ name: string
28
+ tool: string
29
+ status: ToolStatus
30
+ startedAt: number | null
31
+ endedAt: number | null
32
+ durationMs: number | null
33
+ }
34
+
35
+ const TOOL_SCAN = 80
36
+ const TOOL_ROWS = 8
37
+
38
+ /** Metadata only — json_extract, never the part blob. */
39
+ type ToolEventRow = {
40
+ id: string
41
+ time_created: number
42
+ time_updated: number
43
+ tool: string | null
44
+ callID: string | null
45
+ status: string | null
46
+ tstart: number | null
47
+ tend: number | null
48
+ title: string | null
49
+ command: string | null
50
+ command2: string | null
51
+ filePath: string | null
52
+ filePath2: string | null
53
+ pattern: string | null
54
+ pattern2: string | null
55
+ description: string | null
56
+ description2: string | null
57
+ subagent: string | null
58
+ category: string | null
59
+ }
60
+
61
+ /** Shared tool-part column list — all columns from `part.data` via json_extract. */
62
+ function toolColumns(): string {
63
+ return [
64
+ `id`,
65
+ `time_created`,
66
+ `time_updated`,
67
+ `json_extract(data,'$.tool') AS tool`,
68
+ `json_extract(data,'$.callID') AS callID`,
69
+ `json_extract(data,'$.state.status') AS status`,
70
+ `json_extract(data,'$.state.time.start') AS tstart`,
71
+ `json_extract(data,'$.state.time.end') AS tend`,
72
+ `json_extract(data,'$.state.title') AS title`,
73
+ `json_extract(data,'$.state.input.command') AS command`,
74
+ `json_extract(data,'$.input.command') AS command2`,
75
+ `json_extract(data,'$.state.input.filePath') AS filePath`,
76
+ `json_extract(data,'$.input.filePath') AS filePath2`,
77
+ `json_extract(data,'$.state.input.pattern') AS pattern`,
78
+ `json_extract(data,'$.input.pattern') AS pattern2`,
79
+ `json_extract(data,'$.state.input.description') AS description`,
80
+ `json_extract(data,'$.input.description') AS description2`,
81
+ `json_extract(data,'$.state.input.subagent_type') AS subagent`,
82
+ `json_extract(data,'$.state.input.category') AS category`,
83
+ ].join(",\n ")
84
+ }
85
+
86
+ function toolViewsFromRows(rows: readonly ToolEventRow[], limit: number): ToolView[] {
87
+ const out: ToolView[] = []
88
+ for (const row of rows) {
89
+ const tool = str(row.tool) || "tool"
90
+ const start = toEpochMs(row.tstart)
91
+ const end = toEpochMs(row.tend)
92
+ const status = toToolStatus(
93
+ str(row.status) || (end != null ? TOOL_STATUS_COMPLETED : start != null ? TOOL_STATUS_RUNNING : null),
94
+ )
95
+ const startedAt = start ?? toEpochMs(row.time_created)
96
+ const endedAt =
97
+ end ??
98
+ (status === TOOL_STATUS_COMPLETED || status === TOOL_STATUS_ERROR ? toEpochMs(row.time_updated) : null)
99
+ const durationMs =
100
+ startedAt != null && endedAt != null && endedAt >= startedAt ? endedAt - startedAt : null
101
+ out.push({
102
+ id: String(row.id),
103
+ callId: str(row.callID),
104
+ tool,
105
+ name: shortToolLabel({
106
+ tool,
107
+ title: str(row.title),
108
+ command: str(row.command) || str(row.command2),
109
+ filePath: str(row.filePath) || str(row.filePath2),
110
+ pattern: str(row.pattern) || str(row.pattern2),
111
+ description: str(row.description) || str(row.description2),
112
+ subagent: str(row.subagent) || str(row.category),
113
+ }),
114
+ status,
115
+ startedAt,
116
+ endedAt,
117
+ durationMs,
118
+ })
119
+ if (out.length >= limit) break
120
+ }
121
+ return out
122
+ }
123
+
124
+ function listToolRows(db: SqlDb, where: string, params: string[], limit: number): ToolEventRow[] {
125
+ const candidateRows = db.all<{ id: string }>(
126
+ `SELECT id
127
+ FROM part
128
+ WHERE ${where}
129
+ ORDER BY time_created DESC
130
+ LIMIT ${TOOL_SCAN}`,
131
+ ...params,
132
+ )
133
+ const ids = candidateRows.map((r) => String(r.id))
134
+ if (ids.length === 0) return []
135
+ const saturated = ids.length === TOOL_SCAN
136
+
137
+ const placeholders = ids.map(() => "?").join(",")
138
+ const rows = db.all<ToolEventRow>(
139
+ `SELECT ${toolColumns()}
140
+ FROM part
141
+ WHERE id IN (${placeholders})
142
+ AND json_extract(data,'$.type') = '${PART_TYPE_TOOL}'
143
+ ORDER BY time_created DESC
144
+ LIMIT ${limit}`,
145
+ ...ids,
146
+ )
147
+
148
+ if (saturated && rows.length < limit) {
149
+ return db.all<ToolEventRow>(
150
+ `SELECT ${toolColumns()}
151
+ FROM part
152
+ WHERE ${where}
153
+ AND json_extract(data,'$.type') = '${PART_TYPE_TOOL}'
154
+ ORDER BY time_created DESC
155
+ LIMIT ${TOOL_SCAN}`,
156
+ ...params,
157
+ )
158
+ }
159
+ return rows
160
+ }
161
+
162
+ /** Newest tool parts of one session — the Current tab feed. */
163
+ export function listToolEvents(db: SqlDb, sessionId: string, limit = TOOL_ROWS): ToolView[] {
164
+ let rows: ToolEventRow[] = []
165
+ try {
166
+ rows = listToolRows(db, "session_id = ?", [sessionId], limit)
167
+ } catch {
168
+ return []
169
+ }
170
+ return toolViewsFromRows(rows, limit)
171
+ }
172
+
173
+ /** Newest tool parts across the given sessions — the Sessions tab feed. */
174
+ export function listRecentToolEvents(db: SqlDb, sessionIds: string[], limit = TOOL_ROWS): ToolView[] {
175
+ const clean = uniqueIds(sessionIds)
176
+ if (clean.length === 0) return []
177
+ const placeholders = clean.map(() => "?").join(",")
178
+ let rows: ToolEventRow[] = []
179
+ try {
180
+ rows = listToolRows(db, `session_id IN (${placeholders})`, clean, limit)
181
+ } catch {
182
+ return []
183
+ }
184
+ return toolViewsFromRows(rows, limit)
185
+ }
186
+
187
+ export function mergeTools(
188
+ dbTools: ToolView[],
189
+ live: Record<string, ToolHit>,
190
+ now: number,
191
+ limit: number,
192
+ ): ToolView[] {
193
+ // DB rows are keyed by the part id (`prt_…`) but carry the callID (`call_…`);
194
+ // live events reference only the callID. Key both sides by callID so a live
195
+ // hit lands on the same slot as its DB row, or the same call renders twice.
196
+ // A live hit fills in a running start it saw first, but once the DB row has
197
+ // the real endedAt/duration, the live event must not clobber them — the panel
198
+ // sorts finished calls by endedAt, and `now` would keep reordering them.
199
+ const byId = new Map<string, ToolView>()
200
+ const byCall = new Map<string, string>()
201
+ for (const t of dbTools) {
202
+ const key = t.callId || t.id
203
+ byId.set(key, t)
204
+ if (t.callId && t.callId !== t.id) byCall.set(t.id, key)
205
+ }
206
+ for (const hit of Object.values(live)) {
207
+ const key = byCall.get(hit.id) ?? hit.id
208
+ const prev = byId.get(key)
209
+ if (prev && (prev.status === TOOL_STATUS_COMPLETED || prev.status === TOOL_STATUS_ERROR) && hit.status === TOOL_STATUS_RUNNING) {
210
+ continue
211
+ }
212
+ byId.set(key, {
213
+ id: prev?.id ?? hit.id,
214
+ callId: prev?.callId ?? null,
215
+ name: preferToolLabel(hit.name, prev?.name),
216
+ tool: prev?.tool || "tool",
217
+ status: hit.status,
218
+ startedAt: prev?.startedAt ?? now,
219
+ endedAt: hit.status === TOOL_STATUS_RUNNING ? null : (prev?.endedAt ?? now),
220
+ durationMs:
221
+ hit.status === TOOL_STATUS_RUNNING
222
+ ? null
223
+ : prev?.durationMs ?? (prev?.startedAt != null ? Math.max(0, now - prev.startedAt) : null),
224
+ })
225
+ }
226
+ return [...byId.values()]
227
+ .sort((a, b) => {
228
+ const ar = a.status === TOOL_STATUS_RUNNING || a.status === TOOL_STATUS_PENDING ? 0 : 1
229
+ const br = b.status === TOOL_STATUS_RUNNING || b.status === TOOL_STATUS_PENDING ? 0 : 1
230
+ if (ar !== br) return ar - br
231
+ const at = ar === 0 ? (a.startedAt ?? 0) : (a.endedAt ?? a.startedAt ?? 0)
232
+ const bt = br === 0 ? (b.startedAt ?? 0) : (b.endedAt ?? b.startedAt ?? 0)
233
+ return bt - at
234
+ })
235
+ .slice(0, limit)
236
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * pware.oc.perf
3
+ *
4
+ * Turn-timing analysis: reader + TUI view.
5
+ */
6
+ export * from "./pware.oc.perf.reader.js"
7
+ export * from "./pware.oc.perf.self.js"
8
+ export * from "./pware.oc.perf.charts.js"
9
+ export * from "./pware.oc.perf.realtime.js"
10
+ export * from "./pware.oc.perf.realtimeBlock.js"
11
+ export * from "./pware.oc.perf.realtimeTimeline.js"
12
+ export * from "./pware.oc.perf.realtimeSampler.js"
13
+ export * from "./pware.oc.perf.realtimeCpuRam.js"
14
+ export * from "./pware.oc.perf.view.js"
@@ -0,0 +1,15 @@
1
+ declare module "asciichart" {
2
+ export function plot(
3
+ series: number[] | number[][],
4
+ config?: {
5
+ min?: number
6
+ max?: number
7
+ offset?: number
8
+ padding?: string
9
+ height?: number
10
+ colors?: string[]
11
+ symbols?: string[]
12
+ format?: (x: number, i: number) => string
13
+ },
14
+ ): string
15
+ }