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,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"
|