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.
- package/.oesignore +17 -17
- package/CHANGELOG.md +96 -95
- package/LICENSE +21 -21
- package/README.md +150 -235
- package/oes.json +13 -11
- package/package.json +100 -100
- package/scripts/install-git-hooks.mjs +29 -29
- package/src/pware.oc.core/constants/index.ts +18 -18
- package/src/pware.oc.core/constants/pware.oc.core.constants.eventKind.ts +29 -29
- package/src/pware.oc.core/constants/pware.oc.core.constants.eventName.ts +15 -13
- package/src/pware.oc.core/constants/pware.oc.core.constants.eventType.ts +241 -241
- package/src/pware.oc.core/constants/pware.oc.core.constants.myWork.ts +55 -55
- package/src/pware.oc.core/constants/pware.oc.core.constants.partType.ts +65 -65
- package/src/pware.oc.core/constants/pware.oc.core.constants.phase.ts +58 -58
- package/src/pware.oc.core/constants/pware.oc.core.constants.pulse.ts +63 -63
- package/src/pware.oc.core/constants/pware.oc.core.constants.rowKind.ts +34 -34
- package/src/pware.oc.core/constants/pware.oc.core.constants.status.ts +74 -74
- package/src/pware.oc.core/constants/pware.oc.core.constants.toolName.ts +69 -69
- package/src/pware.oc.core/git/index.ts +7 -7
- package/src/pware.oc.core/git/pware.oc.core.git.ts +237 -237
- package/src/pware.oc.core/git/pware.oc.core.gitignore.ts +87 -87
- package/src/pware.oc.core/index.ts +19 -18
- package/src/pware.oc.core/pware.oc.core.bus.ts +42 -42
- package/src/pware.oc.core/pware.oc.core.cache.ts +48 -48
- package/src/pware.oc.core/pware.oc.core.clipboard.ts +97 -97
- package/src/pware.oc.core/pware.oc.core.debug.ts +280 -280
- package/src/pware.oc.core/pware.oc.core.events.ts +79 -79
- package/src/pware.oc.core/pware.oc.core.glyph.ts +135 -135
- package/src/pware.oc.core/pware.oc.core.layout.ts +234 -234
- package/src/pware.oc.core/pware.oc.core.oes.ts +99 -89
- package/src/pware.oc.core/pware.oc.core.paths.ts +240 -240
- package/src/pware.oc.core/pware.oc.core.preview.ts +74 -74
- package/src/pware.oc.core/pware.oc.core.pulse.ts +726 -726
- package/src/pware.oc.core/pware.oc.core.runtimeConfig.ts +41 -0
- package/src/pware.oc.core/pware.oc.core.sqlite.ts +164 -175
- package/src/pware.oc.core/pware.oc.core.sqliteGateway.ts +89 -0
- package/src/pware.oc.core/pware.oc.core.status.ts +214 -214
- package/src/pware.oc.core/pware.oc.core.timing.ts +63 -63
- package/src/pware.oc.core/pware.oc.core.width.ts +215 -215
- package/src/pware.oc.omo/constants/index.ts +15 -15
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.backgroundTask.ts +18 -18
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.boulderStatus.ts +49 -49
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.docKind.ts +29 -29
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.eventName.ts +11 -11
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.planStatus.ts +60 -60
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.reviewStatus.ts +49 -49
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.startWork.ts +25 -25
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.verdict.ts +38 -38
- package/src/pware.oc.omo/index.ts +7 -7
- package/src/pware.oc.omo/resolver/index.ts +19 -19
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approval.ts +179 -179
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approvalGroup.ts +116 -116
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approvalState.ts +34 -34
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.boulder.ts +503 -503
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.config.ts +40 -40
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.doc.ts +269 -269
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.draftFile.ts +18 -18
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.notepadsFile.ts +18 -18
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.plan.ts +155 -155
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.planFile.ts +363 -361
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.proofFile.ts +18 -18
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.rulesFile.ts +11 -11
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.runContinuationFile.ts +14 -14
- package/src/pware.oc.opencode/constants/index.ts +10 -10
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.eventName.ts +15 -15
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.fileTouch.ts +18 -18
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.questionKind.ts +45 -45
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.sessionStatus.ts +58 -58
- package/src/pware.oc.opencode/index.ts +8 -8
- package/src/pware.oc.opencode/pware.oc.opencode.events.ts +182 -182
- package/src/pware.oc.opencode/pware.oc.opencode.files.ts +399 -399
- package/src/pware.oc.opencode/resolver/index.ts +223 -170
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.file.ts +150 -162
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.question.ts +149 -185
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.session.ts +248 -211
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.todo.ts +22 -26
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.tool.ts +224 -236
- package/src/pware.oc.perf/index.ts +14 -14
- package/src/pware.oc.perf/pware.oc.perf.asciichart.d.ts +15 -15
- package/src/pware.oc.perf/pware.oc.perf.charts.ts +284 -284
- package/src/pware.oc.perf/pware.oc.perf.reader.ts +962 -927
- package/src/pware.oc.perf/pware.oc.perf.realtime.ts +88 -88
- package/src/pware.oc.perf/pware.oc.perf.realtimeBlock.ts +118 -118
- package/src/pware.oc.perf/pware.oc.perf.realtimeCpuRam.ts +79 -79
- package/src/pware.oc.perf/pware.oc.perf.realtimeSampler.ts +71 -71
- package/src/pware.oc.perf/pware.oc.perf.realtimeTimeline.ts +214 -214
- package/src/pware.oc.perf/pware.oc.perf.self.ts +179 -179
- package/src/pware.oc.perf/pware.oc.perf.view.tsx +463 -462
- package/src/pware.oc.runtime/index.ts +13 -13
- package/src/pware.oc.runtime/pware.oc.runtime.monitor.ts +142 -142
- package/src/pware.oc.runtime/pware.oc.runtime.mywork-enrich.ts +87 -79
- package/src/pware.oc.runtime/pware.oc.runtime.mywork.ts +282 -253
- package/src/pware.oc.runtime/pware.oc.runtime.omoRead.ts +37 -0
- package/src/pware.oc.runtime/pware.oc.runtime.questions.ts +83 -95
- package/src/pware.oc.runtime/pware.oc.runtime.snapshotClient.ts +130 -129
- package/src/pware.oc.runtime/pware.oc.runtime.source.ts +105 -105
- package/src/pware.oc.runtime/pware.oc.runtime.worker.ts +44 -43
- package/src/pware.oc.runtime/resolver/index.ts +268 -224
- package/src/pware.oc.runtime/resolver/pware.oc.runtime.resolver.delegate.ts +146 -146
- package/src/pware.oc.ui/index.ts +12 -12
- package/src/pware.oc.ui/pware.oc.ui.chrome.tsx +381 -339
- package/src/pware.oc.ui/pware.oc.ui.glyphs.tsx +144 -144
- package/src/pware.oc.ui/pware.oc.ui.host.tsx +111 -111
- package/src/pware.oc.ui/pware.oc.ui.live.tsx +82 -82
- package/src/pware.oc.ui/pware.oc.ui.menudialogs.tsx +843 -843
- package/src/pware.oc.ui/pware.oc.ui.sections.tsx +552 -526
- package/src/pware.oc.ui/pware.oc.ui.sidebar.tsx +1718 -1629
- 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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
} from "../../pware.oc.core/constants/pware.oc.core.constants.
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
kind,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
projectId: string
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
projectId
|
|
149
|
-
}
|
|
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
|
+
}
|