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,105 @@
1
+ import type { PwareEventBus } from "../pware.oc.core/pware.oc.core.bus.js"
2
+ import { EVENT_SCAN_DEBOUNCE_MS } from "../pware.oc.core/pware.oc.core.timing.js"
3
+ import {
4
+ EV_OES_QUESTION_HINT,
5
+ EV_OES_REFRESH_HINT,
6
+ } from "../pware.oc.core/constants/pware.oc.core.constants.eventName.js"
7
+ import {
8
+ EV_OMO_BOULDER_CHANGED,
9
+ EV_OMO_CONFIG_CHANGED,
10
+ EV_OMO_DOCS_CHANGED,
11
+ } from "../pware.oc.omo/constants/pware.oc.omo.constants.eventName.js"
12
+ import { startMonitor, type MonitorHandle } from "./pware.oc.runtime.monitor.js"
13
+ import { shutdownSnapshotWorker } from "./pware.oc.runtime.snapshotClient.js"
14
+
15
+ export type RuntimeSourceOptions = {
16
+ bus: PwareEventBus
17
+ sessionId: string
18
+ projectRoot: string | null
19
+ dbPath?: string
20
+ pollMs?: number
21
+ }
22
+
23
+ export type RuntimeSourceHandle = {
24
+ stop: () => void
25
+ refresh: () => void
26
+ setSession: (sessionId: string) => void
27
+ }
28
+
29
+ export function startRuntimeSource(opts: RuntimeSourceOptions): RuntimeSourceHandle {
30
+ let stopped = false
31
+ let watchedSessionId = opts.sessionId
32
+ let debounce: ReturnType<typeof setTimeout> | null = null
33
+ let questionDebounce: ReturnType<typeof setTimeout> | null = null
34
+ let pendingQuestionSession: string | null = null
35
+
36
+ const bindMonitor = (sessionId: string): MonitorHandle =>
37
+ startMonitor({
38
+ sessionId,
39
+ projectRoot: opts.projectRoot,
40
+ dbPath: opts.dbPath,
41
+ pollMs: opts.pollMs,
42
+ emit: opts.bus.emit,
43
+ })
44
+
45
+ let monitor = bindMonitor(watchedSessionId)
46
+
47
+ const scheduleRefresh = (): void => {
48
+ if (stopped) return
49
+ if (debounce) clearTimeout(debounce)
50
+ debounce = setTimeout(() => {
51
+ debounce = null
52
+ monitor.refresh()
53
+ }, EVENT_SCAN_DEBOUNCE_MS)
54
+ }
55
+
56
+ const scheduleQuestionHint = (sessionId: string): void => {
57
+ if (stopped) return
58
+ pendingQuestionSession = sessionId
59
+ if (questionDebounce) clearTimeout(questionDebounce)
60
+ questionDebounce = setTimeout(() => {
61
+ questionDebounce = null
62
+ const sid = pendingQuestionSession
63
+ pendingQuestionSession = null
64
+ if (sid) monitor.question(sid)
65
+ }, EVENT_SCAN_DEBOUNCE_MS)
66
+ }
67
+
68
+ const offRefreshHint = opts.bus.on(EV_OES_REFRESH_HINT, scheduleRefresh)
69
+ const offQuestionHint = opts.bus.on(EV_OES_QUESTION_HINT, (evt) => {
70
+ const data = evt.data as { sessionId?: string } | null | undefined
71
+ if (data?.sessionId) scheduleQuestionHint(data.sessionId)
72
+ })
73
+ const offBoulderChanged = opts.bus.on(EV_OMO_BOULDER_CHANGED, scheduleRefresh)
74
+ const offDocsChanged = opts.bus.on(EV_OMO_DOCS_CHANGED, scheduleRefresh)
75
+ const offConfigChanged = opts.bus.on(EV_OMO_CONFIG_CHANGED, scheduleRefresh)
76
+
77
+ return {
78
+ refresh: () => monitor.refresh(),
79
+ setSession: (sessionId: string) => {
80
+ if (!sessionId || sessionId === watchedSessionId) return
81
+ watchedSessionId = sessionId
82
+ if (debounce) {
83
+ clearTimeout(debounce)
84
+ debounce = null
85
+ }
86
+ monitor.stop()
87
+ monitor = bindMonitor(sessionId)
88
+ },
89
+ stop: () => {
90
+ stopped = true
91
+ if (debounce) clearTimeout(debounce)
92
+ debounce = null
93
+ if (questionDebounce) clearTimeout(questionDebounce)
94
+ questionDebounce = null
95
+ pendingQuestionSession = null
96
+ offRefreshHint()
97
+ offQuestionHint()
98
+ offBoulderChanged()
99
+ offDocsChanged()
100
+ offConfigChanged()
101
+ monitor.stop()
102
+ shutdownSnapshotWorker()
103
+ },
104
+ }
105
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * pware.oc.runtime.worker
3
+ *
4
+ * Runs `readRuntimeSnapshot` off the TUI main thread. A Bun Worker receiving
5
+ * `{type:"snapshot", id, opts}` replies `{type:"snapshot:done", id, ok, snap}`;
6
+ * `{type:"shutdown"}` closes the worker. The sync resolvers run unchanged in
7
+ * this thread, so the unit/snapshot test surface stays intact.
8
+ */
9
+ import { readRuntimeSnapshot } from "./resolver/index.js"
10
+
11
+ type SnapshotRequest = {
12
+ type: "snapshot"
13
+ id: number
14
+ opts: { sessionId: string; projectRoot: string | null; dbPath?: string; questionHint?: string }
15
+ }
16
+
17
+ type ShutdownRequest = { type: "shutdown" }
18
+
19
+ type WorkerScope = {
20
+ onmessage: ((event: MessageEvent<SnapshotRequest | ShutdownRequest>) => void) | null
21
+ postMessage: (message: unknown) => void
22
+ close: () => void
23
+ }
24
+
25
+ const scope = self as unknown as WorkerScope
26
+
27
+ scope.onmessage = (event) => {
28
+ const msg = event.data
29
+ if (msg.type === "shutdown") {
30
+ scope.close()
31
+ return
32
+ }
33
+ try {
34
+ scope.postMessage({ type: "snapshot:done", id: msg.id, ok: true, snap: readRuntimeSnapshot(msg.opts) })
35
+ } catch (err) {
36
+ scope.postMessage({
37
+ type: "snapshot:done",
38
+ id: msg.id,
39
+ ok: false,
40
+ error: err instanceof Error ? err.message : String(err),
41
+ })
42
+ }
43
+ }
@@ -0,0 +1,224 @@
1
+ /**
2
+ * pware.oc.core.live.resolver
3
+ *
4
+ * Unified live snapshot: OpenCode SQLite + OMO files.
5
+ * Fingerprint-driven — callers poll / watch and call readRuntimeSnapshot.
6
+ */
7
+ import { createStampCache } from "../../pware.oc.core/pware.oc.core.cache.js"
8
+ import { gitignoreStamp } from "../../pware.oc.core/git/pware.oc.core.gitignore.js"
9
+ import { oesStamp, getOes } from "../../pware.oc.core/pware.oc.core.oes.js"
10
+ import { dbStamp, getOpenCodeDbPath } from "../../pware.oc.core/pware.oc.core.paths.js"
11
+ import { openReadonlyDb, withDbRead, type SqlDb } from "../../pware.oc.core/pware.oc.core.sqlite.js"
12
+ import { profile } from "../../pware.oc.core/pware.oc.core.debug.js"
13
+ import {
14
+ emptyDb,
15
+ readDbSnapshot,
16
+ refreshSessionStatus,
17
+ sessionScanStamp,
18
+ type DbSnapshot,
19
+ type OpenQuestion,
20
+ type SessionView,
21
+ } from "../../pware.oc.opencode/resolver/index.js"
22
+ import { createQuestionCache } from "../pware.oc.runtime.questions.js"
23
+ import {
24
+ emptyOmo,
25
+ omoStamp,
26
+ readOmo,
27
+ readOmoConfig,
28
+ type OmoConfigView,
29
+ type OmoSnapshot,
30
+ } from "../../pware.oc.omo/resolver/index.js"
31
+ import { enrichDelegates, type DelegateView } from "./pware.oc.runtime.resolver.delegate.js"
32
+
33
+ export * from "./pware.oc.runtime.resolver.delegate.js"
34
+
35
+ export type RuntimeSnapshot = {
36
+ generatedAt: number
37
+ fingerprint: string
38
+ /** session.time_updated + MAX(part.time_updated) — Perf cache key, not git. */
39
+ scanStamp: string
40
+ db: DbSnapshot
41
+ omo: OmoSnapshot
42
+ omoConfig: OmoConfigView
43
+ delegates: DelegateView[]
44
+ /** Open `question` tools across the project — computed off the UI thread. */
45
+ openQuestions: OpenQuestion[]
46
+ }
47
+
48
+ /** Cheap poll key: WAL + omo/oes stamps. No git, no boulder JSON. */
49
+ export function computeFingerprint(opts: {
50
+ dbPath: string
51
+ projectRoot: string | null
52
+ sessionId: string
53
+ }): string {
54
+ return [
55
+ opts.sessionId,
56
+ dbStamp(opts.dbPath),
57
+ omoStamp(opts.projectRoot),
58
+ oesStamp(opts.projectRoot),
59
+ gitignoreStamp(opts.projectRoot),
60
+ ].join("::")
61
+ }
62
+
63
+ /**
64
+ * Visible-session-graph stamp: the current session row + its parts + its
65
+ * children + any non-archived main session. Covers everything readDbSnapshot
66
+ * displays, so the live cache only misses when shown data actually changed
67
+ * (sessionScanStamp alone would miss delegate/recent rows).
68
+ */
69
+ function snapshotGraphStamp(db: SqlDb, sessionId: string): { scan: string; graph: string } {
70
+ const scan = sessionScanStamp(db, sessionId)
71
+ const kids = db.get<{ m: number }>(
72
+ `SELECT MAX(time_updated) AS m FROM session WHERE parent_id = ?`,
73
+ sessionId,
74
+ )
75
+ const mains = db.get<{ m: number }>(
76
+ `SELECT MAX(time_updated) AS m FROM session
77
+ WHERE parent_id IS NULL AND (time_archived IS NULL OR time_archived = 0)`,
78
+ )
79
+ return { scan, graph: `${scan}|${kids?.m ?? 0}|${mains?.m ?? 0}` }
80
+ }
81
+
82
+ export function readRuntimeSnapshot(opts: {
83
+ sessionId: string
84
+ projectRoot: string | null
85
+ dbPath?: string
86
+ questionHint?: string
87
+ }): RuntimeSnapshot {
88
+ const dbPath = opts.dbPath || getOpenCodeDbPath(process.env, undefined, opts.projectRoot)
89
+ const cheap = computeFingerprint({
90
+ dbPath,
91
+ projectRoot: opts.projectRoot,
92
+ sessionId: opts.sessionId,
93
+ })
94
+ let scan = "0"
95
+ let graph = "0"
96
+ if (opts.sessionId) {
97
+ const stamps = withDbRead(() => {
98
+ const handle = openReadonlyDb(dbPath)
99
+ return handle ? snapshotGraphStamp(handle, opts.sessionId) : null
100
+ }, () => null)
101
+ if (stamps) {
102
+ scan = stamps.scan
103
+ graph = stamps.graph
104
+ }
105
+ }
106
+
107
+ const cacheId = `${cheap}::${graph}`
108
+ const hit = liveCache.peek(cacheId)
109
+ if (hit) {
110
+ const now = Date.now()
111
+ const questionHint = opts.questionHint
112
+ const projectId = hit.db.projectId
113
+ if (questionHint && projectId) {
114
+ profile("db.questions", () => questionCache.touch(dbPath, projectId, questionHint))
115
+ return {
116
+ ...hit,
117
+ generatedAt: now,
118
+ fingerprint: cacheId,
119
+ scanStamp: scan,
120
+ db: withAges(hit.db, now),
121
+ openQuestions: questionCache.get(),
122
+ }
123
+ }
124
+ return {
125
+ ...hit,
126
+ generatedAt: now,
127
+ fingerprint: cacheId,
128
+ scanStamp: scan,
129
+ db: withAges(hit.db, now),
130
+ }
131
+ }
132
+
133
+ const omo = readOmo(opts.projectRoot)
134
+ const extraIds = omo.delegates
135
+ .map((d) => d.sessionId)
136
+ .filter((id): id is string => Boolean(id))
137
+ const db = withDbRead(
138
+ () =>
139
+ opts.sessionId
140
+ ? readDbSnapshot({
141
+ dbPath,
142
+ sessionId: opts.sessionId,
143
+ extraIds,
144
+ projectRoot: opts.projectRoot,
145
+ })
146
+ : emptyDb(dbPath, "no session"),
147
+ () =>
148
+ lastGood && lastGood.db.current?.id === opts.sessionId
149
+ ? lastGood.db
150
+ : emptyDb(dbPath, "db read failed"),
151
+ )
152
+
153
+ const projectId = db.projectId
154
+ const questionHint = opts.questionHint
155
+ let openQuestions: OpenQuestion[] = []
156
+ if (projectId) {
157
+ if (projectId !== questionProjectId) {
158
+ questionCache.reset()
159
+ questionProjectId = projectId
160
+ questionLastReconcile = 0
161
+ }
162
+ if (questionHint) {
163
+ profile("db.questions", () => questionCache.touch(dbPath, projectId, questionHint))
164
+ } else if (Date.now() - questionLastReconcile >= getOes(opts.projectRoot).questionReconcileSec * 1000) {
165
+ profile("db.questions", () => questionCache.reconcile(dbPath, projectId))
166
+ questionLastReconcile = Date.now()
167
+ }
168
+ openQuestions = questionCache.get()
169
+ }
170
+
171
+ const snap: RuntimeSnapshot = {
172
+ generatedAt: Date.now(),
173
+ fingerprint: cacheId,
174
+ scanStamp: scan,
175
+ db,
176
+ omo,
177
+ omoConfig: readOmoConfig(),
178
+ delegates: enrichDelegates(omo, db),
179
+ openQuestions,
180
+ }
181
+ lastGood = snap
182
+ liveCache.set(cacheId, snap)
183
+ return snap
184
+ }
185
+
186
+ const liveCache = createStampCache<RuntimeSnapshot>()
187
+
188
+ /** Last successfully-read snapshot, served when a locked DB read fails. */
189
+ let lastGood: RuntimeSnapshot | null = null
190
+
191
+ /** Module-level open-question cache — seeded once, invalidated per-session via questionHint. */
192
+ const questionCache = createQuestionCache()
193
+ let questionLastReconcile = 0
194
+ let questionProjectId: string | null = null
195
+
196
+ /** Drop the in-memory live snapshot so the next read is a real load. */
197
+ export function resetRuntimeCache(): void {
198
+ liveCache.reset()
199
+ lastGood = null
200
+ questionCache.reset()
201
+ questionLastReconcile = 0
202
+ questionProjectId = null
203
+ }
204
+
205
+ function withAges(db: DbSnapshot, now: number): DbSnapshot {
206
+ const bump = (v: SessionView | null): SessionView | null =>
207
+ v
208
+ ? {
209
+ ...v,
210
+ ageMs: Math.max(0, now - v.timeUpdated),
211
+ status: refreshSessionStatus(v, now),
212
+ }
213
+ : null
214
+ const current = bump(db.current)
215
+ const parent = bump(db.parent)
216
+ const main = bump(db.main)
217
+ const children = db.children.map((v) => bump(v)!)
218
+ const recent = db.recent.map((v) => bump(v)!)
219
+ const byId: DbSnapshot["byId"] = {}
220
+ for (const v of [current, parent, main, ...children, ...recent, ...Object.values(db.byId).map((x) => bump(x)!)]) {
221
+ if (v) byId[v.id] = v
222
+ }
223
+ return { ...db, current, parent, main, children, recent, byId }
224
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * pware.oc.core.live.resolver.delegate
3
+ *
4
+ * Delegate resolution across providers: boulder tasks enriched with SQLite
5
+ * session rows, plus the grouping rules for the panel. `RuntimeSnapshot` is a
6
+ * type-only import from the live aggregate, so there is no runtime cycle.
7
+ */
8
+ import type { Delegate, OmoSnapshot } from "../../pware.oc.omo/resolver/pware.oc.omo.resolver.boulder.js"
9
+ import { normalizeStatus } from "../../pware.oc.core/pware.oc.core.status.js"
10
+ import {
11
+ STATUS_ARCHIVED,
12
+ STATUS_COMPLETED,
13
+ STATUS_ERROR,
14
+ STATUS_RUNNING,
15
+ STATUS_UNKNOWN,
16
+ } from "../../pware.oc.core/constants/pware.oc.core.constants.status.js"
17
+ import {
18
+ SESSION_STATUS_ARCHIVED,
19
+ SESSION_STATUS_IDLE,
20
+ SESSION_STATUS_RUNNING,
21
+ } from "../../pware.oc.opencode/constants/pware.oc.opencode.constants.sessionStatus.js"
22
+ import { isRealSession, type DbSnapshot, type SessionView } from "../../pware.oc.opencode/resolver/index.js"
23
+ import type { RuntimeSnapshot } from "./index.js"
24
+
25
+ export type DelegateView = Delegate & {
26
+ tokensTotal: number | null
27
+ timeUpdated: number | null
28
+ archived: boolean
29
+ }
30
+
31
+ /**
32
+ * Boulder often leaves `task_sessions` on `running` after the work (and the
33
+ * OpenCode session) has finished. SQLite session status wins.
34
+ */
35
+ export function reconcileDelegateStatus(
36
+ omoStatus: string,
37
+ sess?: Pick<SessionView, "status"> | null,
38
+ ): string {
39
+ const omo = (omoStatus || STATUS_UNKNOWN).toLowerCase()
40
+ if (!sess) return omo
41
+ if (sess.status === SESSION_STATUS_ARCHIVED) return STATUS_COMPLETED
42
+ const c = normalizeStatus(omoStatus)
43
+ const omoError = c === STATUS_ERROR
44
+ const omoDone = c === STATUS_COMPLETED
45
+ if (sess.status === SESSION_STATUS_IDLE) {
46
+ if (omoError) return omo
47
+ if (omoDone) return omo
48
+ return STATUS_COMPLETED
49
+ }
50
+ if (omoError || omoDone) return omo
51
+ // A live SQLite session is authoritative — a queued boulder entry (no status
52
+ // yet) must not show as waiting while its subagent is actually running.
53
+ if (sess.status === SESSION_STATUS_RUNNING) return STATUS_RUNNING
54
+ return omo === STATUS_UNKNOWN ? STATUS_RUNNING : omo
55
+ }
56
+
57
+ export function enrichDelegates(omo: OmoSnapshot, db: DbSnapshot): DelegateView[] {
58
+ return omo.delegates.map((d) => {
59
+ const sess = d.sessionId ? db.byId[d.sessionId] : undefined
60
+ return {
61
+ ...d,
62
+ status: reconcileDelegateStatus(d.status, sess),
63
+ tokensTotal: sess ? sess.tokensTotal : null,
64
+ timeUpdated: sess?.timeUpdated ?? d.updatedAt,
65
+ archived: sess?.status === SESSION_STATUS_ARCHIVED,
66
+ }
67
+ })
68
+ }
69
+
70
+ /** Display key — empty / missing agent collapses to `agent`. */
71
+ export function delegateAgentKey(d: Pick<DelegateView, "agent">): string {
72
+ const s = (d.agent || "").trim()
73
+ return s || "agent"
74
+ }
75
+
76
+ export type DelegateListItem =
77
+ | { kind: "header"; agent: string; count: number; members: DelegateView[] }
78
+ | { kind: "row"; grouped: boolean; delegate: DelegateView }
79
+
80
+ /**
81
+ * Cluster by agent when two or more names appear. Group order follows first
82
+ * appearance (keeps recency on Current). One agent → flat rows, no headers.
83
+ */
84
+ export function groupDelegates(list: DelegateView[]): DelegateListItem[] {
85
+ if (list.length === 0) return []
86
+ const seen = new Set<string>()
87
+ for (const d of list) seen.add(delegateAgentKey(d))
88
+ const grouped = seen.size >= 2
89
+ if (!grouped) {
90
+ return list.map((delegate) => ({ kind: "row" as const, grouped: false, delegate }))
91
+ }
92
+ const order: string[] = []
93
+ const buckets = new Map<string, DelegateView[]>()
94
+ for (const d of list) {
95
+ const key = delegateAgentKey(d)
96
+ let bucket = buckets.get(key)
97
+ if (!bucket) {
98
+ bucket = []
99
+ buckets.set(key, bucket)
100
+ order.push(key)
101
+ }
102
+ bucket.push(d)
103
+ }
104
+ const out: DelegateListItem[] = []
105
+ for (const agent of order) {
106
+ const rows = buckets.get(agent) ?? []
107
+ out.push({ kind: "header", agent, count: rows.length, members: rows })
108
+ for (const delegate of rows) out.push({ kind: "row", grouped: true, delegate })
109
+ }
110
+ return out
111
+ }
112
+
113
+ /** Delegates of this session only: SQLite children + OMO tasks whose parent is this session. */
114
+ export function delegatesForSession(snap: RuntimeSnapshot, sessionId: string): DelegateView[] {
115
+ const childIds = new Set(snap.db.children.map((c) => c.id))
116
+ const out: DelegateView[] = []
117
+ const seen = new Set<string>()
118
+ const push = (d: DelegateView) => {
119
+ const key = d.sessionId || d.taskKey
120
+ if (!key || seen.has(key) || key === sessionId) return
121
+ seen.add(key)
122
+ out.push(d)
123
+ }
124
+
125
+ // Boulder is project-wide. A new main session must not inherit another run's tasks.
126
+ for (const d of snap.delegates) {
127
+ const parent = d.sessionId ? snap.db.byId[d.sessionId]?.parentId : null
128
+ if (parent === sessionId || (d.sessionId && childIds.has(d.sessionId))) push(d)
129
+ }
130
+
131
+ for (const c of snap.db.children) {
132
+ if (!isRealSession(c)) continue
133
+ push({
134
+ taskKey: c.id,
135
+ title: c.title,
136
+ sessionId: c.id,
137
+ agent: c.agent,
138
+ status: c.status,
139
+ updatedAt: c.timeUpdated,
140
+ tokensTotal: c.tokensTotal,
141
+ timeUpdated: c.timeUpdated,
142
+ archived: c.status === SESSION_STATUS_ARCHIVED,
143
+ })
144
+ }
145
+ return out
146
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * pware.oc.ui
3
+ *
4
+ * TUI layer: chrome, shared sections, the sidebar panel, dialogs, glyphs.
5
+ */
6
+ export * from "./pware.oc.ui.chrome.js"
7
+ export * from "./pware.oc.ui.sections.js"
8
+ export * from "./pware.oc.ui.sidebar.js"
9
+ export * from "./pware.oc.ui.menudialogs.js"
10
+ export * from "./pware.oc.ui.glyphs.js"
11
+ export * from "./pware.oc.ui.live.js"
12
+ export * from "./pware.oc.ui.host.js"