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,162 +1,150 @@
1
- /**
2
- * pware.oc.core.opencode.resolver.file
3
- *
4
- * File-touch parts → FileView (basename + optional +/- diff stats). Patch
5
- * parts and edit/write/read tool parts count; never bodies or paths in the
6
- * panel. Feeds the Current and Sessions tabs.
7
- */
8
- import { fileHitFromExtracted, filesFromPatchJson, type FileFilter, type FileView } from "../pware.oc.opencode.files.js"
9
- import { str } from "../../pware.oc.core/pware.oc.core.paths.js"
10
- import { toEpochMs } from "../../pware.oc.core/pware.oc.core.pulse.js"
11
- import { uniqueIds, type SqlDb } from "../../pware.oc.core/pware.oc.core.sqlite.js"
12
- import { PART_TYPE_PATCH } from "../../pware.oc.core/constants/pware.oc.core.constants.partType.js"
13
-
14
- const FILE_SCAN = 80
15
-
16
- type SessionFileRow = {
17
- time_updated: number
18
- time_created: number
19
- kind: string | null
20
- tool: string | null
21
- filePath: string | null
22
- filePath2: string | null
23
- filePath3: string | null
24
- addMeta: number | null
25
- addMeta2: number | null
26
- addTop: number | null
27
- delMeta: number | null
28
- delMeta2: number | null
29
- delTop: number | null
30
- files: string | null
31
- }
32
-
33
- /** Shared file-part column list — all columns from `part.data` via json_extract. */
34
- function fileColumns(): string {
35
- return [
36
- `time_created`,
37
- `time_updated`,
38
- `json_extract(data,'$.type') AS kind`,
39
- `json_extract(data,'$.tool') AS tool`,
40
- `json_extract(data,'$.state.input.filePath') AS filePath`,
41
- `json_extract(data,'$.input.filePath') AS filePath2`,
42
- `json_extract(data,'$.filePath') AS filePath3`,
43
- `json_extract(data,'$.state.metadata.additions') AS addMeta`,
44
- `json_extract(data,'$.metadata.additions') AS addMeta2`,
45
- `json_extract(data,'$.additions') AS addTop`,
46
- `json_extract(data,'$.state.metadata.deletions') AS delMeta`,
47
- `json_extract(data,'$.metadata.deletions') AS delMeta2`,
48
- `json_extract(data,'$.deletions') AS delTop`,
49
- `json_extract(data,'$.files') AS files`,
50
- ].join(",\n ")
51
- }
52
-
53
- /** Patch parts + edit/write/read tool parts — what counts as a file touch. */
54
- function fileWhere(): string {
55
- return `(
56
- json_extract(data,'$.type') = 'patch'
57
- OR (
58
- json_extract(data,'$.type') = 'tool'
59
- AND json_extract(data,'$.tool') IN ('edit','write','multiedit','read','delete','apply_edit','applyedit','remove','view','read_file','readfile')
60
- )
61
- )`
62
- }
63
-
64
- /** Patch `files[]` + edit metadata +/- , aggregated by path. No bodies. */
65
- function fileViewsFromRows(rows: readonly SessionFileRow[], filter?: FileFilter): FileView[] {
66
- const byId = new Map<string, FileView>()
67
- const add = (f: FileView) => {
68
- const prev = byId.get(f.id)
69
- byId.set(f.id, {
70
- id: f.id,
71
- name: f.name,
72
- additions: (prev?.additions ?? 0) + f.additions,
73
- deletions: (prev?.deletions ?? 0) + f.deletions,
74
- at: Math.max(f.at, prev?.at ?? 0),
75
- touch: prev?.touch === "write" || f.touch === "write" ? "write" : "read",
76
- letter: f.letter ?? prev?.letter ?? null,
77
- })
78
- }
79
- for (const row of rows) {
80
- const at = toEpochMs(row.time_updated) ?? toEpochMs(row.time_created) ?? 0
81
- if (str(row.kind) === PART_TYPE_PATCH) {
82
- for (const f of filesFromPatchJson(row.files, at, filter)) add(f)
83
- continue
84
- }
85
- const hit = fileHitFromExtracted({
86
- tool: str(row.tool),
87
- filePath: str(row.filePath) || str(row.filePath2) || str(row.filePath3),
88
- additions: row.addMeta ?? row.addMeta2 ?? row.addTop,
89
- deletions: row.delMeta ?? row.delMeta2 ?? row.delTop,
90
- at,
91
- filter,
92
- })
93
- if (hit) add(hit)
94
- }
95
- return [...byId.values()].sort((a, b) => b.at - a.at)
96
- }
97
-
98
- function listFileRows(db: SqlDb, where: string, params: string[]): SessionFileRow[] {
99
- const candidateRows = db.all<{ id: string }>(
100
- `SELECT id
101
- FROM part
102
- WHERE ${where}
103
- ORDER BY time_updated DESC
104
- LIMIT ${FILE_SCAN}`,
105
- ...params,
106
- )
107
- const ids = candidateRows.map((r) => String(r.id))
108
- if (ids.length === 0) return []
109
- const saturated = ids.length === FILE_SCAN
110
-
111
- const placeholders = ids.map(() => "?").join(",")
112
- const rows = db.all<SessionFileRow>(
113
- `SELECT ${fileColumns()}
114
- FROM part
115
- WHERE id IN (${placeholders})
116
- AND ${fileWhere()}
117
- ORDER BY time_updated DESC`,
118
- ...ids,
119
- )
120
-
121
- if (saturated && rows.length < FILE_SCAN) {
122
- return db.all<SessionFileRow>(
123
- `SELECT ${fileColumns()}
124
- FROM part
125
- WHERE ${where}
126
- AND ${fileWhere()}
127
- ORDER BY time_updated DESC
128
- LIMIT ${FILE_SCAN}`,
129
- ...params,
130
- )
131
- }
132
- return rows
133
- }
134
-
135
- /** Patch `files[]` + edit metadata +/- for one session. No bodies. */
136
- export function listSessionFiles(db: SqlDb, sessionId: string, filter?: FileFilter): FileView[] {
137
- let rows: SessionFileRow[] = []
138
- try {
139
- rows = listFileRows(db, "session_id = ?", [sessionId])
140
- } catch {
141
- return []
142
- }
143
- return fileViewsFromRows(rows, filter)
144
- }
145
-
146
- /** Files touched by any of the given sessions, aggregated — the Sessions tab feed. */
147
- export function listRecentSessionFiles(
148
- db: SqlDb,
149
- sessionIds: string[],
150
- filter?: FileFilter,
151
- ): FileView[] {
152
- const clean = uniqueIds(sessionIds)
153
- if (clean.length === 0) return []
154
- const placeholders = clean.map(() => "?").join(",")
155
- let rows: SessionFileRow[] = []
156
- try {
157
- rows = listFileRows(db, `session_id IN (${placeholders})`, clean)
158
- } catch {
159
- return []
160
- }
161
- return fileViewsFromRows(rows, filter)
162
- }
1
+ /**
2
+ * pware.oc.core.opencode.resolver.file
3
+ *
4
+ * File-touch parts → FileView (basename + optional +/- diff stats). Patch
5
+ * parts and edit/write/read tool parts count; never bodies or paths in the
6
+ * panel. Feeds the Current and Sessions tabs.
7
+ */
8
+ import { fileHitFromExtracted, filesFromPatchJson, type FileFilter, type FileView } from "../pware.oc.opencode.files.js"
9
+ import { str } from "../../pware.oc.core/pware.oc.core.paths.js"
10
+ import { toEpochMs } from "../../pware.oc.core/pware.oc.core.pulse.js"
11
+ import { uniqueIds, type SqlDb } from "../../pware.oc.core/pware.oc.core.sqlite.js"
12
+ import { PART_TYPE_PATCH } from "../../pware.oc.core/constants/pware.oc.core.constants.partType.js"
13
+
14
+ const FILE_SCAN = 80
15
+
16
+ type SessionFileRow = {
17
+ time_updated: number
18
+ time_created: number
19
+ kind: string | null
20
+ tool: string | null
21
+ filePath: string | null
22
+ filePath2: string | null
23
+ filePath3: string | null
24
+ addMeta: number | null
25
+ addMeta2: number | null
26
+ addTop: number | null
27
+ delMeta: number | null
28
+ delMeta2: number | null
29
+ delTop: number | null
30
+ files: string | null
31
+ }
32
+
33
+ /** Shared file-part column list — all columns from `part.data` via json_extract. */
34
+ function fileColumns(): string {
35
+ return [
36
+ `time_created`,
37
+ `time_updated`,
38
+ `json_extract(data,'$.type') AS kind`,
39
+ `json_extract(data,'$.tool') AS tool`,
40
+ `json_extract(data,'$.state.input.filePath') AS filePath`,
41
+ `json_extract(data,'$.input.filePath') AS filePath2`,
42
+ `json_extract(data,'$.filePath') AS filePath3`,
43
+ `json_extract(data,'$.state.metadata.additions') AS addMeta`,
44
+ `json_extract(data,'$.metadata.additions') AS addMeta2`,
45
+ `json_extract(data,'$.additions') AS addTop`,
46
+ `json_extract(data,'$.state.metadata.deletions') AS delMeta`,
47
+ `json_extract(data,'$.metadata.deletions') AS delMeta2`,
48
+ `json_extract(data,'$.deletions') AS delTop`,
49
+ `json_extract(data,'$.files') AS files`,
50
+ ].join(",\n ")
51
+ }
52
+
53
+ /** Patch parts + edit/write/read tool parts — what counts as a file touch. */
54
+ function fileWhere(): string {
55
+ return `(
56
+ json_extract(data,'$.type') = 'patch'
57
+ OR (
58
+ json_extract(data,'$.type') = 'tool'
59
+ AND json_extract(data,'$.tool') IN ('edit','write','multiedit','read','delete','apply_edit','applyedit','remove','view','read_file','readfile')
60
+ )
61
+ )`
62
+ }
63
+
64
+ /** Patch `files[]` + edit metadata +/- , aggregated by path. No bodies. */
65
+ function fileViewsFromRows(rows: readonly SessionFileRow[], filter?: FileFilter): FileView[] {
66
+ const byId = new Map<string, FileView>()
67
+ const add = (f: FileView) => {
68
+ const prev = byId.get(f.id)
69
+ byId.set(f.id, {
70
+ id: f.id,
71
+ name: f.name,
72
+ additions: (prev?.additions ?? 0) + f.additions,
73
+ deletions: (prev?.deletions ?? 0) + f.deletions,
74
+ at: Math.max(f.at, prev?.at ?? 0),
75
+ touch: prev?.touch === "write" || f.touch === "write" ? "write" : "read",
76
+ letter: f.letter ?? prev?.letter ?? null,
77
+ })
78
+ }
79
+ for (const row of rows) {
80
+ const at = toEpochMs(row.time_updated) ?? toEpochMs(row.time_created) ?? 0
81
+ if (str(row.kind) === PART_TYPE_PATCH) {
82
+ for (const f of filesFromPatchJson(row.files, at, filter)) add(f)
83
+ continue
84
+ }
85
+ const hit = fileHitFromExtracted({
86
+ tool: str(row.tool),
87
+ filePath: str(row.filePath) || str(row.filePath2) || str(row.filePath3),
88
+ additions: row.addMeta ?? row.addMeta2 ?? row.addTop,
89
+ deletions: row.delMeta ?? row.delMeta2 ?? row.delTop,
90
+ at,
91
+ filter,
92
+ })
93
+ if (hit) add(hit)
94
+ }
95
+ return [...byId.values()].sort((a, b) => b.at - a.at)
96
+ }
97
+
98
+ function listFileRows(db: SqlDb, where: string, params: string[]): SessionFileRow[] {
99
+ const candidateRows = db.all<{ id: string }>(
100
+ `SELECT id
101
+ FROM part
102
+ WHERE ${where}
103
+ ORDER BY time_updated DESC
104
+ LIMIT ${FILE_SCAN}`,
105
+ ...params,
106
+ )
107
+ const ids = candidateRows.map((r) => String(r.id))
108
+ if (ids.length === 0) return []
109
+ const saturated = ids.length === FILE_SCAN
110
+
111
+ const placeholders = ids.map(() => "?").join(",")
112
+ const rows = db.all<SessionFileRow>(
113
+ `SELECT ${fileColumns()}
114
+ FROM part
115
+ WHERE id IN (${placeholders})
116
+ AND ${fileWhere()}
117
+ ORDER BY time_updated DESC`,
118
+ ...ids,
119
+ )
120
+
121
+ if (saturated && rows.length < FILE_SCAN) {
122
+ return db.all<SessionFileRow>(
123
+ `SELECT ${fileColumns()}
124
+ FROM part
125
+ WHERE ${where}
126
+ AND ${fileWhere()}
127
+ ORDER BY time_updated DESC
128
+ LIMIT ${FILE_SCAN}`,
129
+ ...params,
130
+ )
131
+ }
132
+ return rows
133
+ }
134
+
135
+ /** Patch `files[]` + edit metadata +/- for one session. No bodies. */
136
+ export function listSessionFiles(db: SqlDb, sessionId: string, filter?: FileFilter): FileView[] {
137
+ return fileViewsFromRows(listFileRows(db, "session_id = ?", [sessionId]), filter)
138
+ }
139
+
140
+ /** Files touched by any of the given sessions, aggregated — the Sessions tab feed. */
141
+ export function listRecentSessionFiles(
142
+ db: SqlDb,
143
+ sessionIds: string[],
144
+ filter?: FileFilter,
145
+ ): FileView[] {
146
+ const clean = uniqueIds(sessionIds)
147
+ if (clean.length === 0) return []
148
+ const placeholders = clean.map(() => "?").join(",")
149
+ return fileViewsFromRows(listFileRows(db, `session_id IN (${placeholders})`, clean), filter)
150
+ }
@@ -1,185 +1,149 @@
1
- /**
2
- * pware.oc.core.opencode.resolver.question
3
- *
4
- * Open `question` tool parts across a project — the "waiting on my answer"
5
- * queue. A question is open while its state has no end time and its status is
6
- * running or pending, or while it is a still-unanswered error/interrupted
7
- * part. Scanned **project-wide** (every non-archived session of the project),
8
- * not just the current+recent window, so an open question keeps showing after
9
- * a restart or a session switch. Lazy read, gated on the My work tab;
10
- * soft-fails to [] on a missing/locked DB. The question text and options stay
11
- * in the `part.data` blob — never read, never shown.
12
- */
13
- import fs from "node:fs"
14
- import { str } from "../../pware.oc.core/pware.oc.core.paths.js"
15
- import { toEpochMs } from "../../pware.oc.core/pware.oc.core.pulse.js"
16
- import { openReadonlyDb, withDbRead } from "../../pware.oc.core/pware.oc.core.sqlite.js"
17
- import { toToolStatus } from "../../pware.oc.core/pware.oc.core.status.js"
18
- import { PART_TYPE_TOOL } from "../../pware.oc.core/constants/pware.oc.core.constants.partType.js"
19
- import {
20
- TOOL_STATUS_COMPLETED,
21
- TOOL_STATUS_ERROR,
22
- TOOL_STATUS_PENDING,
23
- TOOL_STATUS_RUNNING,
24
- } from "../../pware.oc.core/constants/pware.oc.core.constants.status.js"
25
- import { TOOL_QUESTION } from "../../pware.oc.core/constants/pware.oc.core.constants.toolName.js"
26
- import {
27
- QUESTION_KIND_ERROR,
28
- QUESTION_KIND_INTERRUPTED,
29
- QUESTION_KIND_QUESTION,
30
- type OpenQuestionKind,
31
- } from "../constants/pware.oc.opencode.constants.questionKind.js"
32
-
33
- export type { OpenQuestionKind }
34
-
35
- export type OpenQuestion = {
36
- partId: string
37
- sessionId: string
38
- /** The session's own title — the question's session may sit outside the recent window. */
39
- title: string
40
- startedAt: number | null
41
- kind: OpenQuestionKind
42
- /** `state.error` text for interrupted/error parts; null for an open question. */
43
- reason: string | null
44
- /**
45
- * True when the part has a terminal end time. An ended plain error is kept in
46
- * the queue for the Errors group, but it is no longer "live" — the tab light
47
- * must not treat it as something happening right now. Absent = still live.
48
- */
49
- ended?: boolean
50
- }
51
-
52
- /** Raw SELECT row for an open-question scan. Metadata only — never part.data text. */
53
- export type OpenQuestionRow = {
54
- id: string
55
- session_id: string
56
- title: string | null
57
- time_created: number
58
- status: string | null
59
- tstart: number | null
60
- tend: number | null
61
- error: string | null
62
- interrupted: number | null
63
- }
64
-
65
- /**
66
- * Classify one SELECT row into an open question, or null when the part is not
67
- * part of the queue (resolved / completed / a still-terminated interrupt).
68
- * Pure — the same row always maps to the same answer.
69
- */
70
- export function classifyQuestionRow(row: OpenQuestionRow): OpenQuestion | null {
71
- const start = toEpochMs(row.tstart)
72
- const end = toEpochMs(row.tend)
73
- const status = toToolStatus(
74
- str(row.status) || (end != null ? TOOL_STATUS_COMPLETED : start != null ? TOOL_STATUS_RUNNING : null),
75
- )
76
- if (status !== TOOL_STATUS_RUNNING && status !== TOOL_STATUS_PENDING && status !== TOOL_STATUS_ERROR) return null
77
- const interrupted = row.interrupted === 1
78
- // An interrupted question that terminated (has an end time) is resolved:
79
- // the abort closed the tool and no answer is pending. Only a still-open
80
- // interrupted part (no end time) stays "your call".
81
- if (interrupted && end != null) return null
82
- const reason = str(row.error) || null
83
- const kind: OpenQuestionKind =
84
- status === TOOL_STATUS_ERROR
85
- ? interrupted
86
- ? QUESTION_KIND_INTERRUPTED
87
- : QUESTION_KIND_ERROR
88
- : QUESTION_KIND_QUESTION
89
- return {
90
- partId: row.id,
91
- sessionId: row.session_id,
92
- title: str(row.title) || "untitled",
93
- startedAt: start ?? toEpochMs(row.time_created),
94
- kind,
95
- reason: kind === QUESTION_KIND_QUESTION ? null : reason,
96
- ended: end != null,
97
- }
98
- }
99
-
100
- export function listOpenQuestions(opts: {
101
- dbPath: string
102
- projectId: string | null
103
- }): OpenQuestion[] {
104
- if (!opts.dbPath || !opts.projectId || !fs.existsSync(opts.dbPath)) return []
105
- return withDbRead(() => {
106
- const db = openReadonlyDb(opts.dbPath)
107
- if (!db) return []
108
- let rows: OpenQuestionRow[] = []
109
- try {
110
- rows = db.all<OpenQuestionRow>(
111
- `SELECT p.id AS id,
112
- p.session_id,
113
- s.title AS title,
114
- p.time_created,
115
- json_extract(p.data,'$.state.status') AS status,
116
- json_extract(p.data,'$.state.time.start') AS tstart,
117
- json_extract(p.data,'$.state.time.end') AS tend,
118
- json_extract(p.data,'$.state.error') AS error,
119
- json_extract(p.data,'$.state.metadata.interrupted') AS interrupted
120
- FROM part p
121
- JOIN session s ON s.id = p.session_id
122
- WHERE json_extract(p.data,'$.type') = '${PART_TYPE_TOOL}'
123
- AND json_extract(p.data,'$.tool') = '${TOOL_QUESTION}'
124
- AND (s.time_archived IS NULL OR s.time_archived = 0)
125
- AND s.project_id = ?
126
- ORDER BY p.time_created DESC
127
- LIMIT 80`,
128
- opts.projectId,
129
- )
130
- } catch {
131
- return []
132
- }
133
- return rows
134
- .map(classifyQuestionRow)
135
- .filter((q): q is OpenQuestion => q != null)
136
- }, () => [])
137
- }
138
-
139
- /**
140
- * Session-scoped open questions. Same SELECT columns and filters as
141
- * `listOpenQuestions`, but `WHERE p.session_id = ?` rides `part_session_idx`
142
- * for a cheap targeted re-read. Foundation for later event-driven invalidation
143
- * of the "My work" queue.
144
- */
145
- export function listSessionQuestions(opts: {
146
- dbPath: string
147
- sessionId: string
148
- projectId: string | null
149
- }): OpenQuestion[] {
150
- if (!opts.dbPath || !opts.sessionId || !opts.projectId || !fs.existsSync(opts.dbPath)) return []
151
- return withDbRead(() => {
152
- const db = openReadonlyDb(opts.dbPath)
153
- if (!db) return []
154
- let rows: OpenQuestionRow[] = []
155
- try {
156
- rows = db.all<OpenQuestionRow>(
157
- `SELECT p.id AS id,
158
- p.session_id,
159
- s.title AS title,
160
- p.time_created,
161
- json_extract(p.data,'$.state.status') AS status,
162
- json_extract(p.data,'$.state.time.start') AS tstart,
163
- json_extract(p.data,'$.state.time.end') AS tend,
164
- json_extract(p.data,'$.state.error') AS error,
165
- json_extract(p.data,'$.state.metadata.interrupted') AS interrupted
166
- FROM part p
167
- JOIN session s ON s.id = p.session_id
168
- WHERE p.session_id = ?
169
- AND json_extract(p.data,'$.type') = '${PART_TYPE_TOOL}'
170
- AND json_extract(p.data,'$.tool') = '${TOOL_QUESTION}'
171
- AND (s.time_archived IS NULL OR s.time_archived = 0)
172
- AND s.project_id = ?
173
- ORDER BY p.time_created DESC
174
- LIMIT 20`,
175
- opts.sessionId,
176
- opts.projectId,
177
- )
178
- } catch {
179
- return []
180
- }
181
- return rows
182
- .map(classifyQuestionRow)
183
- .filter((q): q is OpenQuestion => q != null)
184
- }, () => [])
185
- }
1
+ /**
2
+ * pware.oc.core.opencode.resolver.question
3
+ *
4
+ * Open `question` tool parts across a project — the "waiting on my answer"
5
+ * queue. A question is open while its state has no end time and its status is
6
+ * running or pending, or while it is a still-unanswered error/interrupted
7
+ * part. Scanned **project-wide** (every non-archived session of the project),
8
+ * not just the current+recent window, so an open question keeps showing after
9
+ * a restart or a session switch. Lazy read, gated on the My work tab;
10
+ * soft-fails to [] on a missing/locked DB. The question text and options stay
11
+ * in the `part.data` blob — never read, never shown.
12
+ */
13
+ import { str } from "../../pware.oc.core/pware.oc.core.paths.js"
14
+ import { toEpochMs } from "../../pware.oc.core/pware.oc.core.pulse.js"
15
+ import type { SqlDb } from "../../pware.oc.core/pware.oc.core.sqlite.js"
16
+ import { toToolStatus } from "../../pware.oc.core/pware.oc.core.status.js"
17
+ import { PART_TYPE_TOOL } from "../../pware.oc.core/constants/pware.oc.core.constants.partType.js"
18
+ import {
19
+ TOOL_STATUS_COMPLETED,
20
+ TOOL_STATUS_ERROR,
21
+ TOOL_STATUS_PENDING,
22
+ TOOL_STATUS_RUNNING,
23
+ } from "../../pware.oc.core/constants/pware.oc.core.constants.status.js"
24
+ import { TOOL_QUESTION } from "../../pware.oc.core/constants/pware.oc.core.constants.toolName.js"
25
+ import {
26
+ QUESTION_KIND_ERROR,
27
+ QUESTION_KIND_INTERRUPTED,
28
+ QUESTION_KIND_QUESTION,
29
+ type OpenQuestionKind,
30
+ } from "../constants/pware.oc.opencode.constants.questionKind.js"
31
+
32
+ export type { OpenQuestionKind }
33
+
34
+ export type OpenQuestion = {
35
+ partId: string
36
+ sessionId: string
37
+ /** The session's own title — the question's session may sit outside the recent window. */
38
+ title: string
39
+ startedAt: number | null
40
+ kind: OpenQuestionKind
41
+ /** `state.error` text for interrupted/error parts; null for an open question. */
42
+ reason: string | null
43
+ /**
44
+ * True when the part has a terminal end time. An ended plain error is kept in
45
+ * the queue for the Errors group, but it is no longer "live" the tab light
46
+ * must not treat it as something happening right now. Absent = still live.
47
+ */
48
+ ended?: boolean
49
+ }
50
+
51
+ /** Raw SELECT row for an open-question scan. Metadata only — never part.data text. */
52
+ export type OpenQuestionRow = {
53
+ id: string
54
+ session_id: string
55
+ title: string | null
56
+ time_created: number
57
+ status: string | null
58
+ tstart: number | null
59
+ tend: number | null
60
+ error: string | null
61
+ interrupted: number | null
62
+ }
63
+
64
+ /**
65
+ * Classify one SELECT row into an open question, or null when the part is not
66
+ * part of the queue (resolved / completed / a still-terminated interrupt).
67
+ * Pure the same row always maps to the same answer.
68
+ */
69
+ export function classifyQuestionRow(row: OpenQuestionRow): OpenQuestion | null {
70
+ const start = toEpochMs(row.tstart)
71
+ const end = toEpochMs(row.tend)
72
+ const status = toToolStatus(
73
+ str(row.status) || (end != null ? TOOL_STATUS_COMPLETED : start != null ? TOOL_STATUS_RUNNING : null),
74
+ )
75
+ if (status !== TOOL_STATUS_RUNNING && status !== TOOL_STATUS_PENDING && status !== TOOL_STATUS_ERROR) return null
76
+ const interrupted = row.interrupted === 1
77
+ // An interrupted question that terminated (has an end time) is resolved:
78
+ // the abort closed the tool and no answer is pending. Only a still-open
79
+ // interrupted part (no end time) stays "your call".
80
+ if (interrupted && end != null) return null
81
+ const reason = str(row.error) || null
82
+ const kind: OpenQuestionKind =
83
+ status === TOOL_STATUS_ERROR
84
+ ? interrupted
85
+ ? QUESTION_KIND_INTERRUPTED
86
+ : QUESTION_KIND_ERROR
87
+ : QUESTION_KIND_QUESTION
88
+ return {
89
+ partId: row.id,
90
+ sessionId: row.session_id,
91
+ title: str(row.title) || "untitled",
92
+ startedAt: start ?? toEpochMs(row.time_created),
93
+ kind,
94
+ reason: kind === QUESTION_KIND_QUESTION ? null : reason,
95
+ ended: end != null,
96
+ }
97
+ }
98
+
99
+ function queryQuestions(
100
+ db: SqlDb,
101
+ scope: "project" | "session",
102
+ projectId: string,
103
+ sessionId?: string,
104
+ ): OpenQuestion[] {
105
+ const sessionFilter = scope === "session" ? "p.session_id = ? AND" : ""
106
+ const params = scope === "session" && sessionId ? [sessionId, projectId] : [projectId]
107
+ const limit = scope === "session" ? 20 : 80
108
+ return db.all<OpenQuestionRow>(
109
+ `SELECT p.id AS id,
110
+ p.session_id,
111
+ s.title AS title,
112
+ p.time_created,
113
+ json_extract(p.data,'$.state.status') AS status,
114
+ json_extract(p.data,'$.state.time.start') AS tstart,
115
+ json_extract(p.data,'$.state.time.end') AS tend,
116
+ json_extract(p.data,'$.state.error') AS error,
117
+ json_extract(p.data,'$.state.metadata.interrupted') AS interrupted
118
+ FROM part p
119
+ JOIN session s ON s.id = p.session_id
120
+ WHERE ${sessionFilter}
121
+ json_extract(p.data,'$.type') = '${PART_TYPE_TOOL}'
122
+ AND json_extract(p.data,'$.tool') = '${TOOL_QUESTION}'
123
+ AND (s.time_archived IS NULL OR s.time_archived = 0)
124
+ AND s.project_id = ?
125
+ ORDER BY p.time_created DESC
126
+ LIMIT ${limit}`,
127
+ ...params,
128
+ )
129
+ .map(classifyQuestionRow)
130
+ .filter((question): question is OpenQuestion => question != null)
131
+ }
132
+
133
+ export function listOpenQuestions(db: SqlDb, projectId: string | null): OpenQuestion[] {
134
+ return projectId ? queryQuestions(db, "project", projectId) : []
135
+ }
136
+
137
+ /**
138
+ * Session-scoped open questions. Same SELECT columns and filters as
139
+ * `listOpenQuestions`, but `WHERE p.session_id = ?` rides `part_session_idx`
140
+ * for a cheap targeted re-read. Foundation for later event-driven invalidation
141
+ * of the "My work" queue.
142
+ */
143
+ export function listSessionQuestions(
144
+ db: SqlDb,
145
+ sessionId: string,
146
+ projectId: string | null,
147
+ ): OpenQuestion[] {
148
+ return sessionId && projectId ? queryQuestions(db, "session", projectId, sessionId) : []
149
+ }