opencode-extended-sidebar 0.2.90 → 0.2.91

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/.oesignore +17 -17
  2. package/CHANGELOG.md +96 -95
  3. package/LICENSE +21 -21
  4. package/README.md +150 -235
  5. package/oes.json +13 -11
  6. package/package.json +100 -100
  7. package/scripts/install-git-hooks.mjs +29 -29
  8. package/src/pware.oc.core/constants/index.ts +18 -18
  9. package/src/pware.oc.core/constants/pware.oc.core.constants.eventKind.ts +29 -29
  10. package/src/pware.oc.core/constants/pware.oc.core.constants.eventName.ts +15 -13
  11. package/src/pware.oc.core/constants/pware.oc.core.constants.eventType.ts +241 -241
  12. package/src/pware.oc.core/constants/pware.oc.core.constants.myWork.ts +55 -55
  13. package/src/pware.oc.core/constants/pware.oc.core.constants.partType.ts +65 -65
  14. package/src/pware.oc.core/constants/pware.oc.core.constants.phase.ts +58 -58
  15. package/src/pware.oc.core/constants/pware.oc.core.constants.pulse.ts +63 -63
  16. package/src/pware.oc.core/constants/pware.oc.core.constants.rowKind.ts +34 -34
  17. package/src/pware.oc.core/constants/pware.oc.core.constants.status.ts +74 -74
  18. package/src/pware.oc.core/constants/pware.oc.core.constants.toolName.ts +69 -69
  19. package/src/pware.oc.core/git/index.ts +7 -7
  20. package/src/pware.oc.core/git/pware.oc.core.git.ts +237 -237
  21. package/src/pware.oc.core/git/pware.oc.core.gitignore.ts +87 -87
  22. package/src/pware.oc.core/index.ts +19 -18
  23. package/src/pware.oc.core/pware.oc.core.bus.ts +42 -42
  24. package/src/pware.oc.core/pware.oc.core.cache.ts +48 -48
  25. package/src/pware.oc.core/pware.oc.core.clipboard.ts +97 -97
  26. package/src/pware.oc.core/pware.oc.core.debug.ts +280 -280
  27. package/src/pware.oc.core/pware.oc.core.events.ts +79 -79
  28. package/src/pware.oc.core/pware.oc.core.glyph.ts +135 -135
  29. package/src/pware.oc.core/pware.oc.core.layout.ts +234 -234
  30. package/src/pware.oc.core/pware.oc.core.oes.ts +99 -89
  31. package/src/pware.oc.core/pware.oc.core.paths.ts +240 -240
  32. package/src/pware.oc.core/pware.oc.core.preview.ts +74 -74
  33. package/src/pware.oc.core/pware.oc.core.pulse.ts +726 -726
  34. package/src/pware.oc.core/pware.oc.core.runtimeConfig.ts +41 -0
  35. package/src/pware.oc.core/pware.oc.core.sqlite.ts +164 -175
  36. package/src/pware.oc.core/pware.oc.core.sqliteGateway.ts +89 -0
  37. package/src/pware.oc.core/pware.oc.core.status.ts +214 -214
  38. package/src/pware.oc.core/pware.oc.core.timing.ts +63 -63
  39. package/src/pware.oc.core/pware.oc.core.width.ts +215 -215
  40. package/src/pware.oc.omo/constants/index.ts +15 -15
  41. package/src/pware.oc.omo/constants/pware.oc.omo.constants.backgroundTask.ts +18 -18
  42. package/src/pware.oc.omo/constants/pware.oc.omo.constants.boulderStatus.ts +49 -49
  43. package/src/pware.oc.omo/constants/pware.oc.omo.constants.docKind.ts +29 -29
  44. package/src/pware.oc.omo/constants/pware.oc.omo.constants.eventName.ts +11 -11
  45. package/src/pware.oc.omo/constants/pware.oc.omo.constants.planStatus.ts +60 -60
  46. package/src/pware.oc.omo/constants/pware.oc.omo.constants.reviewStatus.ts +49 -49
  47. package/src/pware.oc.omo/constants/pware.oc.omo.constants.startWork.ts +25 -25
  48. package/src/pware.oc.omo/constants/pware.oc.omo.constants.verdict.ts +38 -38
  49. package/src/pware.oc.omo/index.ts +7 -7
  50. package/src/pware.oc.omo/resolver/index.ts +19 -19
  51. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approval.ts +179 -179
  52. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approvalGroup.ts +116 -116
  53. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approvalState.ts +34 -34
  54. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.boulder.ts +503 -503
  55. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.config.ts +40 -40
  56. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.doc.ts +269 -269
  57. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.draftFile.ts +18 -18
  58. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.notepadsFile.ts +18 -18
  59. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.plan.ts +155 -155
  60. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.planFile.ts +363 -361
  61. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.proofFile.ts +18 -18
  62. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.rulesFile.ts +11 -11
  63. package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.runContinuationFile.ts +14 -14
  64. package/src/pware.oc.opencode/constants/index.ts +10 -10
  65. package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.eventName.ts +15 -15
  66. package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.fileTouch.ts +18 -18
  67. package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.questionKind.ts +45 -45
  68. package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.sessionStatus.ts +58 -58
  69. package/src/pware.oc.opencode/index.ts +8 -8
  70. package/src/pware.oc.opencode/pware.oc.opencode.events.ts +182 -182
  71. package/src/pware.oc.opencode/pware.oc.opencode.files.ts +399 -399
  72. package/src/pware.oc.opencode/resolver/index.ts +223 -170
  73. package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.file.ts +150 -162
  74. package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.question.ts +149 -185
  75. package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.session.ts +248 -211
  76. package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.todo.ts +22 -26
  77. package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.tool.ts +224 -236
  78. package/src/pware.oc.perf/index.ts +14 -14
  79. package/src/pware.oc.perf/pware.oc.perf.asciichart.d.ts +15 -15
  80. package/src/pware.oc.perf/pware.oc.perf.charts.ts +284 -284
  81. package/src/pware.oc.perf/pware.oc.perf.reader.ts +962 -927
  82. package/src/pware.oc.perf/pware.oc.perf.realtime.ts +88 -88
  83. package/src/pware.oc.perf/pware.oc.perf.realtimeBlock.ts +118 -118
  84. package/src/pware.oc.perf/pware.oc.perf.realtimeCpuRam.ts +79 -79
  85. package/src/pware.oc.perf/pware.oc.perf.realtimeSampler.ts +71 -71
  86. package/src/pware.oc.perf/pware.oc.perf.realtimeTimeline.ts +214 -214
  87. package/src/pware.oc.perf/pware.oc.perf.self.ts +179 -179
  88. package/src/pware.oc.perf/pware.oc.perf.view.tsx +463 -462
  89. package/src/pware.oc.runtime/index.ts +13 -13
  90. package/src/pware.oc.runtime/pware.oc.runtime.monitor.ts +142 -142
  91. package/src/pware.oc.runtime/pware.oc.runtime.mywork-enrich.ts +87 -79
  92. package/src/pware.oc.runtime/pware.oc.runtime.mywork.ts +282 -253
  93. package/src/pware.oc.runtime/pware.oc.runtime.omoRead.ts +37 -0
  94. package/src/pware.oc.runtime/pware.oc.runtime.questions.ts +83 -95
  95. package/src/pware.oc.runtime/pware.oc.runtime.snapshotClient.ts +130 -129
  96. package/src/pware.oc.runtime/pware.oc.runtime.source.ts +105 -105
  97. package/src/pware.oc.runtime/pware.oc.runtime.worker.ts +44 -43
  98. package/src/pware.oc.runtime/resolver/index.ts +268 -224
  99. package/src/pware.oc.runtime/resolver/pware.oc.runtime.resolver.delegate.ts +146 -146
  100. package/src/pware.oc.ui/index.ts +12 -12
  101. package/src/pware.oc.ui/pware.oc.ui.chrome.tsx +381 -339
  102. package/src/pware.oc.ui/pware.oc.ui.glyphs.tsx +144 -144
  103. package/src/pware.oc.ui/pware.oc.ui.host.tsx +111 -111
  104. package/src/pware.oc.ui/pware.oc.ui.live.tsx +82 -82
  105. package/src/pware.oc.ui/pware.oc.ui.menudialogs.tsx +843 -843
  106. package/src/pware.oc.ui/pware.oc.ui.sections.tsx +552 -526
  107. package/src/pware.oc.ui/pware.oc.ui.sidebar.tsx +1718 -1629
  108. package/src/pware.oc.ui.tsx +98 -98
@@ -1,95 +1,83 @@
1
- /**
2
- * pware.oc.runtime.questions
3
- *
4
- * In-memory, per-session cache of open questions for the "My work" queue. A
5
- * later wiring task feeds it from cheap host-event hints (targeted
6
- * `listSessionQuestions` on the session a `message.part.updated` just touched)
7
- * and a periodic `listOpenQuestions` backstop, replacing the expensive
8
- * project-wide scan that used to run on every snapshot.
9
- *
10
- * `createQuestionCache()` returns a fresh instance — no module-level mutable
11
- * singleton — so tests build their own and the wiring task owns the one real
12
- * instance. State is `bySession`, a Map keyed by `sessionId`; `get` flattens
13
- * and dedupes it. The two read functions below already soft-fail to `[]`, so
14
- * this cache only holds/merges their results and needs no try/catch of its own.
15
- */
16
- import {
17
- listOpenQuestions,
18
- listSessionQuestions,
19
- type OpenQuestion,
20
- } from "../pware.oc.opencode/resolver/pware.oc.opencode.resolver.question.js"
21
-
22
- export type QuestionCache = {
23
- /** Project-wide open questions, deduped by partId, sorted `startedAt` DESC (nulls last). */
24
- get: () => OpenQuestion[]
25
- /** Full `listOpenQuestions` scan; replaces the whole map (drop gone sessions). */
26
- seed: (dbPath: string, projectId: string | null) => void
27
- /** One session's `listSessionQuestions`; merges in, or removes on empty. */
28
- touch: (dbPath: string, projectId: string | null, sessionId: string) => void
29
- /** Full `listOpenQuestions` scan semantic alias of `seed` for the backstop call site. */
30
- reconcile: (dbPath: string, projectId: string | null) => void
31
- /** Drop every cached session. */
32
- reset: () => void
33
- }
34
-
35
- /**
36
- * Flatten all per-session slices, dedupe by `partId` (a question may briefly
37
- * appear in two sessions' slices — keep the first), and sort by `startedAt`
38
- * descending with nulls last. Pure — never mutates its input.
39
- */
40
- export function mergeQuestions(
41
- bySession: ReadonlyMap<string, readonly OpenQuestion[]>,
42
- ): OpenQuestion[] {
43
- const seen = new Set<string>()
44
- const out: OpenQuestion[] = []
45
- for (const list of bySession.values()) {
46
- for (const q of list) {
47
- if (seen.has(q.partId)) continue
48
- seen.add(q.partId)
49
- out.push(q)
50
- }
51
- }
52
- out.sort((a, b) => {
53
- const x = a.startedAt
54
- const y = b.startedAt
55
- if (x === y) return 0
56
- if (x == null) return 1
57
- if (y == null) return -1
58
- return y - x
59
- })
60
- return out
61
- }
62
-
63
- export function createQuestionCache(): QuestionCache {
64
- let bySession = new Map<string, OpenQuestion[]>()
65
-
66
- function refill(dbPath: string, projectId: string | null): void {
67
- const next = new Map<string, OpenQuestion[]>()
68
- for (const q of listOpenQuestions({ dbPath, projectId })) {
69
- const bucket = next.get(q.sessionId)
70
- if (bucket) bucket.push(q)
71
- else next.set(q.sessionId, [q])
72
- }
73
- bySession = next
74
- }
75
-
76
- return {
77
- get() {
78
- return mergeQuestions(bySession)
79
- },
80
- seed(dbPath, projectId) {
81
- refill(dbPath, projectId)
82
- },
83
- touch(dbPath, projectId, sessionId) {
84
- const result = listSessionQuestions({ dbPath, sessionId, projectId })
85
- if (result.length > 0) bySession.set(sessionId, result)
86
- else bySession.delete(sessionId)
87
- },
88
- reconcile(dbPath, projectId) {
89
- refill(dbPath, projectId)
90
- },
91
- reset() {
92
- bySession = new Map()
93
- },
94
- }
95
- }
1
+ /**
2
+ * pware.oc.runtime.questions
3
+ *
4
+ * In-memory, per-session cache of open questions for the "My work" queue. A
5
+ * later wiring task feeds it from cheap host-event hints (targeted
6
+ * `listSessionQuestions` on the session a `message.part.updated` just touched)
7
+ * and a periodic `listOpenQuestions` backstop, replacing the expensive
8
+ * project-wide scan that used to run on every snapshot.
9
+ *
10
+ * `createQuestionCache()` returns a fresh instance — no module-level mutable
11
+ * singleton — so tests build their own and the wiring task owns the one real
12
+ * instance. State is `bySession`, a Map keyed by `sessionId`; `get` flattens
13
+ * and dedupes it. The two read functions below already soft-fail to `[]`, so
14
+ * this cache only holds/merges their results and needs no try/catch of its own.
15
+ */
16
+ import type { OpenQuestion } from "../pware.oc.opencode/resolver/pware.oc.opencode.resolver.question.js"
17
+
18
+ export type QuestionCache = {
19
+ /** Project-wide open questions, deduped by partId, sorted `startedAt` DESC (nulls last). */
20
+ get: () => OpenQuestion[]
21
+ seed: (questions: readonly OpenQuestion[]) => void
22
+ touch: (sessionId: string, questions: readonly OpenQuestion[]) => void
23
+ /** Drop every cached session. */
24
+ reset: () => void
25
+ }
26
+
27
+ /**
28
+ * Flatten all per-session slices, dedupe by `partId` (a question may briefly
29
+ * appear in two sessions' slices keep the first), and sort by `startedAt`
30
+ * descending with nulls last. Pure never mutates its input.
31
+ */
32
+ export function mergeQuestions(
33
+ bySession: ReadonlyMap<string, readonly OpenQuestion[]>,
34
+ ): OpenQuestion[] {
35
+ const seen = new Set<string>()
36
+ const out: OpenQuestion[] = []
37
+ for (const list of bySession.values()) {
38
+ for (const q of list) {
39
+ if (seen.has(q.partId)) continue
40
+ seen.add(q.partId)
41
+ out.push(q)
42
+ }
43
+ }
44
+ out.sort((a, b) => {
45
+ const x = a.startedAt
46
+ const y = b.startedAt
47
+ if (x === y) return 0
48
+ if (x == null) return 1
49
+ if (y == null) return -1
50
+ return y - x
51
+ })
52
+ return out
53
+ }
54
+
55
+ export function createQuestionCache(): QuestionCache {
56
+ let bySession = new Map<string, OpenQuestion[]>()
57
+
58
+ function refill(questions: readonly OpenQuestion[]): void {
59
+ const next = new Map<string, OpenQuestion[]>()
60
+ for (const q of questions) {
61
+ const bucket = next.get(q.sessionId)
62
+ if (bucket) bucket.push(q)
63
+ else next.set(q.sessionId, [q])
64
+ }
65
+ bySession = next
66
+ }
67
+
68
+ return {
69
+ get() {
70
+ return mergeQuestions(bySession)
71
+ },
72
+ seed(questions) {
73
+ refill(questions)
74
+ },
75
+ touch(sessionId, questions) {
76
+ if (questions.length > 0) bySession.set(sessionId, [...questions])
77
+ else bySession.delete(sessionId)
78
+ },
79
+ reset() {
80
+ bySession = new Map()
81
+ },
82
+ }
83
+ }
@@ -1,129 +1,130 @@
1
- /**
2
- * pware.oc.runtime.snapshotClient
3
- *
4
- * Async facade over the snapshot worker. `readRuntimeSnapshotAsync` posts a
5
- * snapshot request and resolves with the composed snapshot; when the worker
6
- * cannot be spawned, errors, or fails to answer within the timeout it falls
7
- * back to the synchronous `readRuntimeSnapshot` — so the panel never hard
8
- * depends on worker availability.
9
- */
10
- import { dbg } from "../pware.oc.core/pware.oc.core.debug.js"
11
- import { readRuntimeSnapshot, type RuntimeSnapshot } from "./resolver/index.js"
12
-
13
- export type SnapshotRequestOpts = {
14
- sessionId: string
15
- projectRoot: string | null
16
- dbPath?: string
17
- questionHint?: string
18
- }
19
-
20
- type SnapshotDoneMessage = {
21
- type: "snapshot:done"
22
- id: number
23
- ok: boolean
24
- snap?: RuntimeSnapshot
25
- error?: string
26
- }
27
-
28
- /** Bound on a single worker round-trip before the sync path takes over. */
29
- const WORKER_TIMEOUT_MS = 4_000
30
-
31
- type PendingEntry = {
32
- opts: SnapshotRequestOpts
33
- resolve: (snap: RuntimeSnapshot) => void
34
- timer: ReturnType<typeof setTimeout>
35
- }
36
-
37
- let worker: Worker | null = null
38
- let workerFailed = false
39
- let nextId = 0
40
- const pending = new Map<number, PendingEntry>()
41
-
42
- function syncFallback(opts: SnapshotRequestOpts): RuntimeSnapshot {
43
- return readRuntimeSnapshot(opts)
44
- }
45
-
46
- function settle(id: number, snap: RuntimeSnapshot): void {
47
- const entry = pending.get(id)
48
- if (!entry) return
49
- pending.delete(id)
50
- clearTimeout(entry.timer)
51
- entry.resolve(snap)
52
- }
53
-
54
- function failAll(reason: string): void {
55
- dbg("snapshot.worker", "failed", { reason })
56
- workerFailed = true
57
- for (const id of [...pending.keys()]) {
58
- const entry = pending.get(id)
59
- if (entry) settle(id, syncFallback(entry.opts))
60
- }
61
- }
62
-
63
- function ensureWorker(): Worker | null {
64
- if (workerFailed) return null
65
- if (worker) return worker
66
- try {
67
- const w = new Worker(new URL("./pware.oc.runtime.worker.ts", import.meta.url), { type: "module" })
68
- w.onmessage = (event: MessageEvent<SnapshotDoneMessage>) => {
69
- const msg = event.data
70
- if (!msg || msg.type !== "snapshot:done") return
71
- const entry = pending.get(msg.id)
72
- if (!entry) return
73
- settle(msg.id, msg.ok && msg.snap ? msg.snap : syncFallback(entry.opts))
74
- }
75
- w.onerror = (event) => {
76
- const message =
77
- typeof event === "object" && event !== null && "message" in event
78
- ? String((event as { message: unknown }).message)
79
- : String(event)
80
- failAll(message)
81
- try {
82
- w.terminate()
83
- } catch {
84
- // ignore
85
- }
86
- worker = null
87
- }
88
- worker = w
89
- dbg("snapshot.worker", "spawned", {})
90
- return w
91
- } catch (err) {
92
- dbg("snapshot.worker", "spawn failed", { error: err instanceof Error ? err.message : String(err) })
93
- workerFailed = true
94
- return null
95
- }
96
- }
97
-
98
- /** Off-main-thread snapshot read, falling back to the sync path on any failure. */
99
- export function readRuntimeSnapshotAsync(opts: SnapshotRequestOpts): Promise<RuntimeSnapshot> {
100
- const w = ensureWorker()
101
- if (!w) return Promise.resolve(syncFallback(opts))
102
- const id = ++nextId
103
- return new Promise<RuntimeSnapshot>((resolve) => {
104
- const timer = setTimeout(() => settle(id, syncFallback(opts)), WORKER_TIMEOUT_MS)
105
- pending.set(id, { opts, resolve, timer })
106
- try {
107
- w.postMessage({ type: "snapshot", id, opts })
108
- } catch (err) {
109
- dbg("snapshot.worker", "post failed", { error: err instanceof Error ? err.message : String(err) })
110
- settle(id, syncFallback(opts))
111
- }
112
- })
113
- }
114
-
115
- /** Terminate the shared worker (idempotent). Safe to call on plugin shutdown. */
116
- export function shutdownSnapshotWorker(): void {
117
- if (!worker) return
118
- try {
119
- worker.postMessage({ type: "shutdown" })
120
- } catch {
121
- // ignore
122
- }
123
- try {
124
- worker.terminate()
125
- } catch {
126
- // ignore
127
- }
128
- worker = null
129
- }
1
+ /**
2
+ * pware.oc.runtime.snapshotClient
3
+ *
4
+ * Async facade over the snapshot worker. `readRuntimeSnapshotAsync` posts a
5
+ * snapshot request and resolves with the composed snapshot; when the worker
6
+ * cannot be spawned, errors, or fails to answer within the timeout it runs the
7
+ * same paced request gateway in the host process.
8
+ */
9
+ import { dbg } from "../pware.oc.core/pware.oc.core.debug.js"
10
+ import { readRuntimeSnapshot, type RuntimeSnapshot } from "./resolver/index.js"
11
+
12
+ export type SnapshotRequestOpts = {
13
+ sessionId: string
14
+ projectRoot: string | null
15
+ dbPath?: string
16
+ questionHint?: string
17
+ }
18
+
19
+ type SnapshotDoneMessage = {
20
+ type: "snapshot:done"
21
+ id: number
22
+ ok: boolean
23
+ snap?: RuntimeSnapshot
24
+ error?: string
25
+ }
26
+
27
+ /** Bound on a single worker round-trip before the sync path takes over. */
28
+ const WORKER_TIMEOUT_MS = 4_000
29
+
30
+ type PendingEntry = {
31
+ opts: SnapshotRequestOpts
32
+ resolve: (snap: RuntimeSnapshot) => void
33
+ timer: ReturnType<typeof setTimeout>
34
+ }
35
+
36
+ let worker: Worker | null = null
37
+ let workerFailed = false
38
+ let nextId = 0
39
+ const pending = new Map<number, PendingEntry>()
40
+
41
+ function fallbackRead(opts: SnapshotRequestOpts): Promise<RuntimeSnapshot> {
42
+ return readRuntimeSnapshot(opts)
43
+ }
44
+
45
+ function settle(id: number, snap: RuntimeSnapshot): void {
46
+ const entry = pending.get(id)
47
+ if (!entry) return
48
+ pending.delete(id)
49
+ clearTimeout(entry.timer)
50
+ entry.resolve(snap)
51
+ }
52
+
53
+ function failAll(reason: string): void {
54
+ dbg("snapshot.worker", "failed", { reason })
55
+ workerFailed = true
56
+ for (const id of [...pending.keys()]) {
57
+ const entry = pending.get(id)
58
+ if (entry) void fallbackRead(entry.opts).then((snap) => settle(id, snap))
59
+ }
60
+ }
61
+
62
+ function ensureWorker(): Worker | null {
63
+ if (workerFailed) return null
64
+ if (worker) return worker
65
+ try {
66
+ const w = new Worker(new URL("./pware.oc.runtime.worker.ts", import.meta.url), { type: "module" })
67
+ w.onmessage = (event: MessageEvent<SnapshotDoneMessage>) => {
68
+ const msg = event.data
69
+ if (!msg || msg.type !== "snapshot:done") return
70
+ const entry = pending.get(msg.id)
71
+ if (!entry) return
72
+ if (msg.ok && msg.snap) settle(msg.id, msg.snap)
73
+ else void fallbackRead(entry.opts).then((snap) => settle(msg.id, snap))
74
+ }
75
+ w.onerror = (event) => {
76
+ const message =
77
+ typeof event === "object" && event !== null && "message" in event
78
+ ? String((event as { message: unknown }).message)
79
+ : String(event)
80
+ failAll(message)
81
+ try {
82
+ w.terminate()
83
+ } catch {
84
+ // ignore
85
+ }
86
+ worker = null
87
+ }
88
+ worker = w
89
+ dbg("snapshot.worker", "spawned", {})
90
+ return w
91
+ } catch (err) {
92
+ dbg("snapshot.worker", "spawn failed", { error: err instanceof Error ? err.message : String(err) })
93
+ workerFailed = true
94
+ return null
95
+ }
96
+ }
97
+
98
+ export function readRuntimeSnapshotAsync(opts: SnapshotRequestOpts): Promise<RuntimeSnapshot> {
99
+ const w = ensureWorker()
100
+ if (!w) return fallbackRead(opts)
101
+ const id = ++nextId
102
+ return new Promise<RuntimeSnapshot>((resolve) => {
103
+ const timer = setTimeout(() => {
104
+ void fallbackRead(opts).then((snap) => settle(id, snap))
105
+ }, WORKER_TIMEOUT_MS)
106
+ pending.set(id, { opts, resolve, timer })
107
+ try {
108
+ w.postMessage({ type: "snapshot", id, opts })
109
+ } catch (err) {
110
+ dbg("snapshot.worker", "post failed", { error: err instanceof Error ? err.message : String(err) })
111
+ void fallbackRead(opts).then((snap) => settle(id, snap))
112
+ }
113
+ })
114
+ }
115
+
116
+ /** Terminate the shared worker (idempotent). Safe to call on plugin shutdown. */
117
+ export function shutdownSnapshotWorker(): void {
118
+ if (!worker) return
119
+ try {
120
+ worker.postMessage({ type: "shutdown" })
121
+ } catch {
122
+ // ignore
123
+ }
124
+ try {
125
+ worker.terminate()
126
+ } catch {
127
+ // ignore
128
+ }
129
+ worker = null
130
+ }
@@ -1,105 +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
- }
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
+ }