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,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
|
+
}
|