opencode-extended-sidebar 0.2.90
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.oesignore +17 -0
- package/CHANGELOG.md +95 -0
- package/LICENSE +21 -0
- package/README.md +235 -0
- package/oes.json +11 -0
- package/package.json +100 -0
- package/scripts/install-git-hooks.mjs +29 -0
- package/src/pware.oc.core/constants/index.ts +18 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.eventKind.ts +29 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.eventName.ts +13 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.eventType.ts +241 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.myWork.ts +55 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.partType.ts +65 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.phase.ts +58 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.pulse.ts +63 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.rowKind.ts +34 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.status.ts +74 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.toolName.ts +69 -0
- package/src/pware.oc.core/git/index.ts +7 -0
- package/src/pware.oc.core/git/pware.oc.core.git.ts +237 -0
- package/src/pware.oc.core/git/pware.oc.core.gitignore.ts +87 -0
- package/src/pware.oc.core/index.ts +18 -0
- package/src/pware.oc.core/pware.oc.core.bus.ts +42 -0
- package/src/pware.oc.core/pware.oc.core.cache.ts +48 -0
- package/src/pware.oc.core/pware.oc.core.clipboard.ts +97 -0
- package/src/pware.oc.core/pware.oc.core.debug.ts +280 -0
- package/src/pware.oc.core/pware.oc.core.events.ts +79 -0
- package/src/pware.oc.core/pware.oc.core.glyph.ts +135 -0
- package/src/pware.oc.core/pware.oc.core.layout.ts +234 -0
- package/src/pware.oc.core/pware.oc.core.oes.ts +89 -0
- package/src/pware.oc.core/pware.oc.core.paths.ts +240 -0
- package/src/pware.oc.core/pware.oc.core.preview.ts +74 -0
- package/src/pware.oc.core/pware.oc.core.pulse.ts +726 -0
- package/src/pware.oc.core/pware.oc.core.sqlite.ts +175 -0
- package/src/pware.oc.core/pware.oc.core.status.ts +214 -0
- package/src/pware.oc.core/pware.oc.core.timing.ts +63 -0
- package/src/pware.oc.core/pware.oc.core.width.ts +215 -0
- package/src/pware.oc.omo/constants/index.ts +15 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.backgroundTask.ts +18 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.boulderStatus.ts +49 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.docKind.ts +29 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.eventName.ts +11 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.planStatus.ts +60 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.reviewStatus.ts +49 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.startWork.ts +25 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.verdict.ts +38 -0
- package/src/pware.oc.omo/index.ts +7 -0
- package/src/pware.oc.omo/resolver/index.ts +19 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approval.ts +179 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approvalGroup.ts +116 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approvalState.ts +34 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.boulder.ts +503 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.config.ts +40 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.doc.ts +269 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.draftFile.ts +18 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.notepadsFile.ts +18 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.plan.ts +155 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.planFile.ts +361 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.proofFile.ts +18 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.rulesFile.ts +11 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.runContinuationFile.ts +14 -0
- package/src/pware.oc.opencode/constants/index.ts +10 -0
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.eventName.ts +15 -0
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.fileTouch.ts +18 -0
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.questionKind.ts +45 -0
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.sessionStatus.ts +58 -0
- package/src/pware.oc.opencode/index.ts +8 -0
- package/src/pware.oc.opencode/pware.oc.opencode.events.ts +182 -0
- package/src/pware.oc.opencode/pware.oc.opencode.files.ts +399 -0
- package/src/pware.oc.opencode/resolver/index.ts +170 -0
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.file.ts +162 -0
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.question.ts +185 -0
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.session.ts +211 -0
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.todo.ts +26 -0
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.tool.ts +236 -0
- package/src/pware.oc.perf/index.ts +14 -0
- package/src/pware.oc.perf/pware.oc.perf.asciichart.d.ts +15 -0
- package/src/pware.oc.perf/pware.oc.perf.charts.ts +284 -0
- package/src/pware.oc.perf/pware.oc.perf.reader.ts +927 -0
- package/src/pware.oc.perf/pware.oc.perf.realtime.ts +88 -0
- package/src/pware.oc.perf/pware.oc.perf.realtimeBlock.ts +118 -0
- package/src/pware.oc.perf/pware.oc.perf.realtimeCpuRam.ts +79 -0
- package/src/pware.oc.perf/pware.oc.perf.realtimeSampler.ts +71 -0
- package/src/pware.oc.perf/pware.oc.perf.realtimeTimeline.ts +214 -0
- package/src/pware.oc.perf/pware.oc.perf.self.ts +179 -0
- package/src/pware.oc.perf/pware.oc.perf.view.tsx +462 -0
- package/src/pware.oc.runtime/index.ts +13 -0
- package/src/pware.oc.runtime/pware.oc.runtime.monitor.ts +142 -0
- package/src/pware.oc.runtime/pware.oc.runtime.mywork-enrich.ts +79 -0
- package/src/pware.oc.runtime/pware.oc.runtime.mywork.ts +253 -0
- package/src/pware.oc.runtime/pware.oc.runtime.questions.ts +95 -0
- package/src/pware.oc.runtime/pware.oc.runtime.snapshotClient.ts +129 -0
- package/src/pware.oc.runtime/pware.oc.runtime.source.ts +105 -0
- package/src/pware.oc.runtime/pware.oc.runtime.worker.ts +43 -0
- package/src/pware.oc.runtime/resolver/index.ts +224 -0
- package/src/pware.oc.runtime/resolver/pware.oc.runtime.resolver.delegate.ts +146 -0
- package/src/pware.oc.ui/index.ts +12 -0
- package/src/pware.oc.ui/pware.oc.ui.chrome.tsx +339 -0
- package/src/pware.oc.ui/pware.oc.ui.glyphs.tsx +144 -0
- package/src/pware.oc.ui/pware.oc.ui.host.tsx +111 -0
- package/src/pware.oc.ui/pware.oc.ui.live.tsx +82 -0
- package/src/pware.oc.ui/pware.oc.ui.menudialogs.tsx +843 -0
- package/src/pware.oc.ui/pware.oc.ui.sections.tsx +526 -0
- package/src/pware.oc.ui/pware.oc.ui.sidebar.tsx +1629 -0
- package/src/pware.oc.ui.tsx +98 -0
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pware.oc.omo.resolver.planFile
|
|
3
|
+
*
|
|
4
|
+
* Omo-file → writer-session index. Reads the OpenCode SQLite database through
|
|
5
|
+
* a raw `SqlDb` handle to map `.omo/` / `.sisyphus/` document files to the
|
|
6
|
+
* session that last wrote them. Lives in the omo layer because omo may read
|
|
7
|
+
* opencode data (one-way relaxation): the index needs the session/part rows,
|
|
8
|
+
* not any opencode-layer code.
|
|
9
|
+
*
|
|
10
|
+
* One generic engine (`omoFileIndex` / `sessionForOmoFile`) serves every
|
|
11
|
+
* document kind; the plan/draft index (`planSessionIndex` /
|
|
12
|
+
* `sessionForPlanFile`) is a legacy specialisation that folds plans and drafts
|
|
13
|
+
* into one map so a session's latest plan is preferred over its latest draft.
|
|
14
|
+
*/
|
|
15
|
+
import { createStampCache } from "../../pware.oc.core/pware.oc.core.cache.js"
|
|
16
|
+
import { basenameOf, str } from "../../pware.oc.core/pware.oc.core.paths.js"
|
|
17
|
+
import type { SqlDb } from "../../pware.oc.core/pware.oc.core.sqlite.js"
|
|
18
|
+
import { DOC_KIND_PLAN, type DocKind } from "../constants/pware.oc.omo.constants.docKind.js"
|
|
19
|
+
import {
|
|
20
|
+
listOmoFiles,
|
|
21
|
+
type DocView,
|
|
22
|
+
type ListOmoFilesOptions,
|
|
23
|
+
} from "./pware.oc.omo.resolver.doc.js"
|
|
24
|
+
|
|
25
|
+
/** Every OMO on-disk document kind (the subdirectory under `.omo/` / `.sisyphus/`). */
|
|
26
|
+
export const OMO_FILE_KINDS = [
|
|
27
|
+
"plan",
|
|
28
|
+
"draft",
|
|
29
|
+
"notepad",
|
|
30
|
+
"proof",
|
|
31
|
+
"rule",
|
|
32
|
+
"run-continuation",
|
|
33
|
+
] as const
|
|
34
|
+
|
|
35
|
+
/** An OMO on-disk document kind. */
|
|
36
|
+
export type OmoFileKind = (typeof OMO_FILE_KINDS)[number]
|
|
37
|
+
|
|
38
|
+
/** One case-insensitive regex per kind matching the omo/sisyphus subdir. */
|
|
39
|
+
const OMO_FILE_KIND_RE: Record<OmoFileKind, RegExp> = {
|
|
40
|
+
plan: /\.(omo|sisyphus)\/plans\//i,
|
|
41
|
+
draft: /\.(omo|sisyphus)\/drafts\//i,
|
|
42
|
+
notepad: /\.(omo|sisyphus)\/notepads\//i,
|
|
43
|
+
proof: /\.(omo|sisyphus)\/evidence\//i,
|
|
44
|
+
rule: /\.(omo|sisyphus)\/rules\//i,
|
|
45
|
+
"run-continuation": /\.(omo|sisyphus)\/run-continuation\//i,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type PlanSessionIndex = {
|
|
49
|
+
fileWriter: Map<string, { sessionId: string; lastAt: number }>
|
|
50
|
+
sessionPlan: Map<string, { rel: string; lastAt: number; isPlan: boolean }>
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type OmoFileIndex = {
|
|
54
|
+
fileWriter: Map<string, { sessionId: string; lastAt: number }>
|
|
55
|
+
sessionFiles: Map<string, { rel: string; lastAt: number }>
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const PLAN_FILE_RE = /\.(omo|sisyphus)\/(drafts|plans)\//i
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Normalise `fp` to a project-relative posix path, match it against `regex`,
|
|
62
|
+
* and slice to the marker start. `projectRoot` strips the absolute prefix;
|
|
63
|
+
* without it the fp is used as-is and the marker may sit mid-path. Null when
|
|
64
|
+
* the path does not match `regex`.
|
|
65
|
+
*/
|
|
66
|
+
function omoRelOf(
|
|
67
|
+
fp: string,
|
|
68
|
+
projectRoot: string | null | undefined,
|
|
69
|
+
regex: RegExp,
|
|
70
|
+
): string | null {
|
|
71
|
+
let rel = fp.replace(/\\/g, "/").trim()
|
|
72
|
+
if (!rel) return null
|
|
73
|
+
if (projectRoot) {
|
|
74
|
+
const root = projectRoot.replace(/\\/g, "/").replace(/\/+$/, "")
|
|
75
|
+
if (rel.toLowerCase().startsWith(root.toLowerCase() + "/")) {
|
|
76
|
+
rel = rel.slice(root.length + 1)
|
|
77
|
+
} else if (rel.toLowerCase() === root.toLowerCase()) {
|
|
78
|
+
rel = ""
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const m = rel.match(regex)
|
|
82
|
+
if (!m) return null
|
|
83
|
+
const start = m.index ?? 0
|
|
84
|
+
return start > 0 ? rel.slice(start) : rel
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* If `fp` is a plan/draft target under `.omo/` or `.sisyphus/`, return its
|
|
89
|
+
* project-relative path and whether it is under `plans/`. `projectRoot`
|
|
90
|
+
* strips the absolute prefix; without it the fp is used as-is and the marker
|
|
91
|
+
* may sit mid-path. Null for anything else (plain source files included).
|
|
92
|
+
*/
|
|
93
|
+
function planFileOf(
|
|
94
|
+
fp: string,
|
|
95
|
+
projectRoot: string | null | undefined,
|
|
96
|
+
): { rel: string; isPlan: boolean } | null {
|
|
97
|
+
const rel = omoRelOf(fp, projectRoot, PLAN_FILE_RE)
|
|
98
|
+
if (!rel) return null
|
|
99
|
+
const m = rel.match(PLAN_FILE_RE)
|
|
100
|
+
return { rel, isPlan: m?.[2]?.toLowerCase() === "plans" }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** `MAX(part.time_updated)` over the part/session join, or null on error. */
|
|
104
|
+
function partsStampInfo(
|
|
105
|
+
db: SqlDb,
|
|
106
|
+
projectId: string | null | undefined,
|
|
107
|
+
): { id: string; max: number } | null {
|
|
108
|
+
try {
|
|
109
|
+
const where = projectId ? "session.project_id = ?" : "1=1"
|
|
110
|
+
const args = projectId ? [projectId] : []
|
|
111
|
+
const p = db.get<{ m: number | null }>(
|
|
112
|
+
`SELECT MAX(part.time_updated) AS m
|
|
113
|
+
FROM part JOIN session ON session.id = part.session_id
|
|
114
|
+
WHERE ${where}`,
|
|
115
|
+
...args,
|
|
116
|
+
)
|
|
117
|
+
return { id: projectId ?? "*", max: p?.m ?? 0 }
|
|
118
|
+
} catch {
|
|
119
|
+
return null
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function planIndexStamp(db: SqlDb, projectId: string | null | undefined): string {
|
|
124
|
+
const info = partsStampInfo(db, projectId)
|
|
125
|
+
return info ? `${info.id}|${info.max}` : "x"
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function omoFileStamp(db: SqlDb, projectId: string | null | undefined, kind: OmoFileKind): string {
|
|
129
|
+
const info = partsStampInfo(db, projectId)
|
|
130
|
+
return info ? `${info.id}|${kind}|${info.max}` : "x"
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** One write-ish part row: session, resolved filePath, latest write time, and
|
|
134
|
+
* whether the session ever `write`-created the file. */
|
|
135
|
+
type FilePartRow = {
|
|
136
|
+
session_id: string | null
|
|
137
|
+
fp: string | null
|
|
138
|
+
last: number | null
|
|
139
|
+
is_writer: number | null
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function queryFileParts(db: SqlDb, projectId: string | null | undefined): FilePartRow[] {
|
|
143
|
+
const where = projectId ? "session.project_id = ? AND" : ""
|
|
144
|
+
const args = projectId ? [projectId] : []
|
|
145
|
+
return db.all<FilePartRow>(
|
|
146
|
+
`SELECT part.session_id,
|
|
147
|
+
COALESCE(
|
|
148
|
+
json_extract(part.data,'$.state.input.filePath'),
|
|
149
|
+
json_extract(part.data,'$.input.filePath'),
|
|
150
|
+
json_extract(part.data,'$.filePath')
|
|
151
|
+
) AS fp,
|
|
152
|
+
MAX(part.time_updated) AS last,
|
|
153
|
+
MAX(CASE WHEN json_extract(part.data,'$.tool')='write' THEN 1 ELSE 0 END) AS is_writer
|
|
154
|
+
FROM part
|
|
155
|
+
JOIN session ON session.id = part.session_id
|
|
156
|
+
WHERE ${where} json_extract(part.data,'$.type')='tool'
|
|
157
|
+
AND json_extract(part.data,'$.tool') IN ('write','edit','multiedit','apply_edit','applyedit')
|
|
158
|
+
GROUP BY part.session_id, fp`,
|
|
159
|
+
...args,
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function buildPlanSessionIndex(
|
|
164
|
+
db: SqlDb,
|
|
165
|
+
projectId: string | null | undefined,
|
|
166
|
+
projectRoot: string | null | undefined,
|
|
167
|
+
): PlanSessionIndex {
|
|
168
|
+
const fileWriter = new Map<string, { sessionId: string; lastAt: number }>()
|
|
169
|
+
const writerPref = new Map<string, number>() // basename → is_writer of the current entry
|
|
170
|
+
const sessionPlan = new Map<string, { rel: string; lastAt: number; isPlan: boolean }>()
|
|
171
|
+
try {
|
|
172
|
+
for (const row of queryFileParts(db, projectId)) {
|
|
173
|
+
const sessionId = str(row.session_id)
|
|
174
|
+
const fp = str(row.fp)
|
|
175
|
+
const last = typeof row.last === "number" && row.last > 0 ? row.last : 0
|
|
176
|
+
if (!sessionId || !fp) continue
|
|
177
|
+
const info = planFileOf(fp, projectRoot)
|
|
178
|
+
if (!info) continue
|
|
179
|
+
const base = basenameOf(info.rel)
|
|
180
|
+
const isWriter = row.is_writer === 1 ? 1 : 0
|
|
181
|
+
const writer = fileWriter.get(base)
|
|
182
|
+
if (!writer) {
|
|
183
|
+
fileWriter.set(base, { sessionId, lastAt: last })
|
|
184
|
+
writerPref.set(base, isWriter)
|
|
185
|
+
} else {
|
|
186
|
+
const cur = writerPref.get(base) ?? 0
|
|
187
|
+
// A `write` creator beats a session that only edited the file, even
|
|
188
|
+
// when the edit came later; equal preference falls back to the later
|
|
189
|
+
// timestamp.
|
|
190
|
+
if (isWriter > cur || (isWriter === cur && last > writer.lastAt)) {
|
|
191
|
+
fileWriter.set(base, { sessionId, lastAt: last })
|
|
192
|
+
writerPref.set(base, isWriter)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const plan = sessionPlan.get(sessionId)
|
|
196
|
+
if (!plan) {
|
|
197
|
+
sessionPlan.set(sessionId, { rel: info.rel, lastAt: last, isPlan: info.isPlan })
|
|
198
|
+
} else if (last > plan.lastAt) {
|
|
199
|
+
sessionPlan.set(sessionId, { rel: info.rel, lastAt: last, isPlan: info.isPlan })
|
|
200
|
+
} else if (last === plan.lastAt && info.isPlan && !plan.isPlan) {
|
|
201
|
+
sessionPlan.set(sessionId, { rel: info.rel, lastAt: last, isPlan: info.isPlan })
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
} catch {
|
|
205
|
+
// soft-fail → empty index
|
|
206
|
+
}
|
|
207
|
+
return { fileWriter, sessionPlan }
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function buildOmoFileIndex(
|
|
211
|
+
db: SqlDb,
|
|
212
|
+
projectId: string | null | undefined,
|
|
213
|
+
projectRoot: string | null | undefined,
|
|
214
|
+
kind: OmoFileKind,
|
|
215
|
+
): OmoFileIndex {
|
|
216
|
+
const fileWriter = new Map<string, { sessionId: string; lastAt: number }>()
|
|
217
|
+
const writerPref = new Map<string, number>() // basename → is_writer of the current entry
|
|
218
|
+
const sessionFiles = new Map<string, { rel: string; lastAt: number }>()
|
|
219
|
+
const regex = OMO_FILE_KIND_RE[kind]
|
|
220
|
+
try {
|
|
221
|
+
for (const row of queryFileParts(db, projectId)) {
|
|
222
|
+
const sessionId = str(row.session_id)
|
|
223
|
+
const fp = str(row.fp)
|
|
224
|
+
const last = typeof row.last === "number" && row.last > 0 ? row.last : 0
|
|
225
|
+
if (!sessionId || !fp) continue
|
|
226
|
+
const rel = omoRelOf(fp, projectRoot, regex)
|
|
227
|
+
if (!rel) continue
|
|
228
|
+
const base = basenameOf(rel)
|
|
229
|
+
const isWriter = row.is_writer === 1 ? 1 : 0
|
|
230
|
+
const writer = fileWriter.get(base)
|
|
231
|
+
if (!writer) {
|
|
232
|
+
fileWriter.set(base, { sessionId, lastAt: last })
|
|
233
|
+
writerPref.set(base, isWriter)
|
|
234
|
+
} else {
|
|
235
|
+
const cur = writerPref.get(base) ?? 0
|
|
236
|
+
// A `write` creator beats a session that only edited the file, even
|
|
237
|
+
// when the edit came later; equal preference falls back to the later
|
|
238
|
+
// timestamp.
|
|
239
|
+
if (isWriter > cur || (isWriter === cur && last > writer.lastAt)) {
|
|
240
|
+
fileWriter.set(base, { sessionId, lastAt: last })
|
|
241
|
+
writerPref.set(base, isWriter)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
const curFile = sessionFiles.get(sessionId)
|
|
245
|
+
if (!curFile || last > curFile.lastAt) {
|
|
246
|
+
sessionFiles.set(sessionId, { rel, lastAt: last })
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
} catch {
|
|
250
|
+
// soft-fail → empty index
|
|
251
|
+
}
|
|
252
|
+
return { fileWriter, sessionFiles }
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const planIndexCache = createStampCache<PlanSessionIndex>()
|
|
256
|
+
const omoIndexCache = createStampCache<OmoFileIndex>()
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* In-memory bidirectional plan-file index for one project, built from a single
|
|
260
|
+
* write-ish `part` join. `fileWriter` maps a plan/draft basename to its latest
|
|
261
|
+
* writer session; `sessionPlan` maps a session to its latest plan/draft file,
|
|
262
|
+
* preferring `plans/` over `drafts/` on an equal timestamp. Stamp-cached on the
|
|
263
|
+
* project's `MAX(part.time_updated)` so it rebuilds only when parts change.
|
|
264
|
+
*/
|
|
265
|
+
export function planSessionIndex(
|
|
266
|
+
db: SqlDb,
|
|
267
|
+
projectId: string | null | undefined,
|
|
268
|
+
projectRoot: string | null | undefined,
|
|
269
|
+
): PlanSessionIndex {
|
|
270
|
+
return planIndexCache.get(planIndexStamp(db, projectId), () =>
|
|
271
|
+
buildPlanSessionIndex(db, projectId, projectRoot),
|
|
272
|
+
)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* The session that last wrote a `.omo/` plan/draft file, if any. Matches
|
|
277
|
+
* write-ish tool parts by the file's basename, so the planner session that
|
|
278
|
+
* created a pending approval is the one "Continue" should jump to.
|
|
279
|
+
*/
|
|
280
|
+
export function sessionForPlanFile(db: SqlDb, relPath: string | null | undefined): string | null {
|
|
281
|
+
const base = basenameOf(relPath ?? "")
|
|
282
|
+
if (!base || base === "file") return null
|
|
283
|
+
return planSessionIndex(db, null, null).fileWriter.get(base)?.sessionId ?? null
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* In-memory bidirectional index for one OMO document kind, built from a single
|
|
288
|
+
* write-ish `part` join. `fileWriter` maps a file basename to its latest writer
|
|
289
|
+
* session; `sessionFiles` maps a session to its latest file of that kind.
|
|
290
|
+
* Stamp-cached on the project's `MAX(part.time_updated)` so it rebuilds only
|
|
291
|
+
* when parts change.
|
|
292
|
+
*/
|
|
293
|
+
export function omoFileIndex(
|
|
294
|
+
db: SqlDb,
|
|
295
|
+
projectId: string | null | undefined,
|
|
296
|
+
projectRoot: string | null | undefined,
|
|
297
|
+
kind: OmoFileKind,
|
|
298
|
+
): OmoFileIndex {
|
|
299
|
+
return omoIndexCache.get(omoFileStamp(db, projectId, kind), () =>
|
|
300
|
+
buildOmoFileIndex(db, projectId, projectRoot, kind),
|
|
301
|
+
)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* The writer session for a rel path from an already-built index. The per-file
|
|
306
|
+
* half of `sessionForOmoFile`, split out so a caller can filter a whole list
|
|
307
|
+
* against one index build instead of one stamp lookup per file.
|
|
308
|
+
*/
|
|
309
|
+
export function omoWriterSession(
|
|
310
|
+
index: OmoFileIndex,
|
|
311
|
+
relPath: string | null | undefined,
|
|
312
|
+
): string | null {
|
|
313
|
+
const base = basenameOf(relPath ?? "")
|
|
314
|
+
if (!base || base === "file") return null
|
|
315
|
+
return index.fileWriter.get(base)?.sessionId ?? null
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* The session that last wrote a `.omo/` file of `kind`, if any. Matches
|
|
320
|
+
* write-ish tool parts by the file's basename.
|
|
321
|
+
*/
|
|
322
|
+
export function sessionForOmoFile(
|
|
323
|
+
db: SqlDb,
|
|
324
|
+
relPath: string | null | undefined,
|
|
325
|
+
kind: OmoFileKind,
|
|
326
|
+
): string | null {
|
|
327
|
+
return omoWriterSession(omoFileIndex(db, null, null, kind), relPath)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** A per-kind omo-file resolver: the index builder + the writer-session lookup. */
|
|
331
|
+
export function makeOmoFileResolver(kind: OmoFileKind): {
|
|
332
|
+
sessionIndex: (
|
|
333
|
+
db: SqlDb,
|
|
334
|
+
projectId: string | null | undefined,
|
|
335
|
+
projectRoot: string | null | undefined,
|
|
336
|
+
) => OmoFileIndex
|
|
337
|
+
sessionForFile: (db: SqlDb, relPath: string | null | undefined) => string | null
|
|
338
|
+
} {
|
|
339
|
+
return {
|
|
340
|
+
sessionIndex: (db, projectId, projectRoot) => omoFileIndex(db, projectId, projectRoot, kind),
|
|
341
|
+
sessionForFile: (db, relPath) => sessionForOmoFile(db, relPath, kind),
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/** A per-doc-kind `.list` wrapper — the `XxxFile.list` shape. */
|
|
346
|
+
export function makeOmoFileList(docKind: DocKind): {
|
|
347
|
+
list: (
|
|
348
|
+
projectRoot: string | null | undefined,
|
|
349
|
+
sessionId?: string | null,
|
|
350
|
+
opts?: ListOmoFilesOptions,
|
|
351
|
+
) => DocView[]
|
|
352
|
+
} {
|
|
353
|
+
return {
|
|
354
|
+
list(projectRoot, sessionId = null, opts = {}) {
|
|
355
|
+
return listOmoFiles(docKind, projectRoot, { ...opts, sessionId })
|
|
356
|
+
},
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** Plan files under `.omo/plans/`, optionally filtered by writer session. */
|
|
361
|
+
export const PlanFile = makeOmoFileList(DOC_KIND_PLAN)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pware.oc.omo.resolver.proofFile
|
|
3
|
+
*
|
|
4
|
+
* Evidence (proof) file → writer-session index. Thin re-export of the shared
|
|
5
|
+
* omo file engine (`planFile.ts`), pinned to the `.omo/evidence/` document kind.
|
|
6
|
+
*/
|
|
7
|
+
import { DOC_KIND_PROOF } from "../constants/pware.oc.omo.constants.docKind.js"
|
|
8
|
+
import {
|
|
9
|
+
makeOmoFileList,
|
|
10
|
+
makeOmoFileResolver,
|
|
11
|
+
} from "./pware.oc.omo.resolver.planFile.js"
|
|
12
|
+
|
|
13
|
+
/** Bidirectional evidence-file index for one project (see `omoFileIndex`). */
|
|
14
|
+
export const { sessionIndex: proofSessionIndex, sessionForFile: sessionForProofFile } =
|
|
15
|
+
makeOmoFileResolver("proof")
|
|
16
|
+
|
|
17
|
+
/** Evidence files under `.omo/evidence/`, optionally filtered by writer session. */
|
|
18
|
+
export const ProofFile = makeOmoFileList(DOC_KIND_PROOF)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pware.oc.omo.resolver.rulesFile
|
|
3
|
+
*
|
|
4
|
+
* Rule-file → writer-session index. Thin re-export of the shared omo file
|
|
5
|
+
* engine (`planFile.ts`), pinned to the `.omo/rules/` document kind.
|
|
6
|
+
*/
|
|
7
|
+
import { makeOmoFileResolver } from "./pware.oc.omo.resolver.planFile.js"
|
|
8
|
+
|
|
9
|
+
/** Bidirectional rule-file index for one project (see `omoFileIndex`). */
|
|
10
|
+
export const { sessionIndex: rulesSessionIndex, sessionForFile: sessionForRuleFile } =
|
|
11
|
+
makeOmoFileResolver("rule")
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pware.oc.omo.resolver.runContinuationFile
|
|
3
|
+
*
|
|
4
|
+
* Run-continuation file → writer-session index. Thin re-export of the shared
|
|
5
|
+
* omo file engine (`planFile.ts`), pinned to the `.omo/run-continuation/`
|
|
6
|
+
* document kind.
|
|
7
|
+
*/
|
|
8
|
+
import { makeOmoFileResolver } from "./pware.oc.omo.resolver.planFile.js"
|
|
9
|
+
|
|
10
|
+
/** Bidirectional run-continuation index for one project (see `omoFileIndex`). */
|
|
11
|
+
export const {
|
|
12
|
+
sessionIndex: runContinuationSessionIndex,
|
|
13
|
+
sessionForFile: sessionForRunContinuationFile,
|
|
14
|
+
} = makeOmoFileResolver("run-continuation")
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pware.oc.opencode.constants
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for the OpenCode-domain string literals: session
|
|
5
|
+
* statuses and activity states, question-tool kinds, and file-touch kinds.
|
|
6
|
+
*/
|
|
7
|
+
export * from "./pware.oc.opencode.constants.sessionStatus.js"
|
|
8
|
+
export * from "./pware.oc.opencode.constants.questionKind.js"
|
|
9
|
+
export * from "./pware.oc.opencode.constants.fileTouch.js"
|
|
10
|
+
export * from "./pware.oc.opencode.constants.eventName.js"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const EV_OC_SESSION_ACTIVITY = "pware.oc.session.activity"
|
|
2
|
+
export const EV_OC_FLOW = "pware.oc.flow"
|
|
3
|
+
export const EV_OC_TOOL_HIT = "pware.oc.tool.hit"
|
|
4
|
+
export const EV_OC_FILES_TOUCHED = "pware.oc.files.touched"
|
|
5
|
+
export const EV_OC_TOKENS_DELTA = "pware.oc.tokens.delta"
|
|
6
|
+
|
|
7
|
+
export const OC_EVENT_NAMES = [
|
|
8
|
+
EV_OC_SESSION_ACTIVITY,
|
|
9
|
+
EV_OC_FLOW,
|
|
10
|
+
EV_OC_TOOL_HIT,
|
|
11
|
+
EV_OC_FILES_TOUCHED,
|
|
12
|
+
EV_OC_TOKENS_DELTA,
|
|
13
|
+
] as const
|
|
14
|
+
|
|
15
|
+
export type OcEventName = (typeof OC_EVENT_NAMES)[number]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pware.oc.opencode.constants.filetouch
|
|
3
|
+
*
|
|
4
|
+
* OpenCode file-touch kinds: whether a tool call reads or writes a file.
|
|
5
|
+
* Classified in `pware.oc.opencode/pware.oc.opencode.files.ts`.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** File touch: the tool read/viewed the file. */
|
|
9
|
+
export const FILE_TOUCH_READ = "read"
|
|
10
|
+
|
|
11
|
+
/** File touch: the tool wrote/deleted the file. */
|
|
12
|
+
export const FILE_TOUCH_WRITE = "write"
|
|
13
|
+
|
|
14
|
+
/** Every file-touch kind. */
|
|
15
|
+
export const FILE_TOUCHES = [FILE_TOUCH_READ, FILE_TOUCH_WRITE] as const
|
|
16
|
+
|
|
17
|
+
/** A file-touch kind. */
|
|
18
|
+
export type FileTouch = (typeof FILE_TOUCHES)[number]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pware.oc.opencode.constants.questionkind
|
|
3
|
+
*
|
|
4
|
+
* OpenCode `question` tool-part kinds: what an open question means for the
|
|
5
|
+
* "My work" queue. Classified in
|
|
6
|
+
* `pware.oc.opencode/resolver/pware.oc.opencode.resolver.question.ts`:
|
|
7
|
+
* - `question` — the agent is live and waiting for an answer (running/pending).
|
|
8
|
+
* - `interrupted` — the tool was aborted (`metadata.interrupted`), the answer
|
|
9
|
+
* never came, but the session moved on or died — still your call.
|
|
10
|
+
* - `error` — the tool genuinely failed (no answer, no interrupt marker).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** Question kind: the agent is live and waiting for an answer. */
|
|
14
|
+
export const QUESTION_KIND_QUESTION = "question"
|
|
15
|
+
|
|
16
|
+
/** Question kind: the tool was aborted — the answer never came. */
|
|
17
|
+
export const QUESTION_KIND_INTERRUPTED = "interrupted"
|
|
18
|
+
|
|
19
|
+
/** Question kind: the tool genuinely failed. */
|
|
20
|
+
export const QUESTION_KIND_ERROR = "error"
|
|
21
|
+
|
|
22
|
+
/** Every open-question kind. */
|
|
23
|
+
export const QUESTION_KINDS = [
|
|
24
|
+
QUESTION_KIND_QUESTION,
|
|
25
|
+
QUESTION_KIND_INTERRUPTED,
|
|
26
|
+
QUESTION_KIND_ERROR,
|
|
27
|
+
] as const
|
|
28
|
+
|
|
29
|
+
/** An open-question kind. */
|
|
30
|
+
export type OpenQuestionKind = (typeof QUESTION_KINDS)[number]
|
|
31
|
+
|
|
32
|
+
/** True when the open question is live and awaiting an answer. */
|
|
33
|
+
export function isWaitingForAnswer(kind: OpenQuestionKind | null): boolean {
|
|
34
|
+
return kind === QUESTION_KIND_QUESTION
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** True when the question was aborted and the answer never came. */
|
|
38
|
+
export function isInterrupted(kind: OpenQuestionKind | null): boolean {
|
|
39
|
+
return kind === QUESTION_KIND_INTERRUPTED
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** True when the question tool genuinely failed. */
|
|
43
|
+
export function isQuestionError(kind: OpenQuestionKind | null): boolean {
|
|
44
|
+
return kind === QUESTION_KIND_ERROR
|
|
45
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pware.oc.opencode.constants.sessionstatus
|
|
3
|
+
*
|
|
4
|
+
* OpenCode session-domain string literals: the session status a `SessionView`
|
|
5
|
+
* carries, and the finer-grained activity state used for approval rows. Both
|
|
6
|
+
* derive from SQLite rows + host events in
|
|
7
|
+
* `pware.oc.opencode/resolver/pware.oc.opencode.resolver.session.ts`.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Session status: recently updated — the agent is likely still working. */
|
|
11
|
+
export const SESSION_STATUS_RUNNING = "running"
|
|
12
|
+
|
|
13
|
+
/** Session status: quiet — no recent updates. */
|
|
14
|
+
export const SESSION_STATUS_IDLE = "idle"
|
|
15
|
+
|
|
16
|
+
/** Session status: archived by the host. */
|
|
17
|
+
export const SESSION_STATUS_ARCHIVED = "archived"
|
|
18
|
+
|
|
19
|
+
/** Session status: no state could be determined. */
|
|
20
|
+
export const SESSION_STATUS_UNKNOWN = "unknown"
|
|
21
|
+
|
|
22
|
+
/** Every session status a `SessionView` can carry. */
|
|
23
|
+
export const SESSION_STATUSES = [
|
|
24
|
+
SESSION_STATUS_RUNNING,
|
|
25
|
+
SESSION_STATUS_IDLE,
|
|
26
|
+
SESSION_STATUS_ARCHIVED,
|
|
27
|
+
SESSION_STATUS_UNKNOWN,
|
|
28
|
+
] as const
|
|
29
|
+
|
|
30
|
+
/** A session status value. */
|
|
31
|
+
export type AgentStatus = (typeof SESSION_STATUSES)[number]
|
|
32
|
+
|
|
33
|
+
/** Session activity state: a fresh `time_updated` — the session is streaming. */
|
|
34
|
+
export const SESSION_STATE_STREAMING = "streaming"
|
|
35
|
+
|
|
36
|
+
/** Session activity state: stale in SQLite but a background task is running. */
|
|
37
|
+
export const SESSION_STATE_AWAITING_BACKGROUND = "awaiting-background"
|
|
38
|
+
|
|
39
|
+
/** Session activity state: quiet. */
|
|
40
|
+
export const SESSION_STATE_IDLE = SESSION_STATUS_IDLE
|
|
41
|
+
|
|
42
|
+
/** Session activity state: archived. */
|
|
43
|
+
export const SESSION_STATE_ARCHIVED = SESSION_STATUS_ARCHIVED
|
|
44
|
+
|
|
45
|
+
/** Session activity state: unknown. */
|
|
46
|
+
export const SESSION_STATE_UNKNOWN = SESSION_STATUS_UNKNOWN
|
|
47
|
+
|
|
48
|
+
/** Every session activity state. */
|
|
49
|
+
export const SESSION_STATES = [
|
|
50
|
+
SESSION_STATE_STREAMING,
|
|
51
|
+
SESSION_STATE_AWAITING_BACKGROUND,
|
|
52
|
+
SESSION_STATE_IDLE,
|
|
53
|
+
SESSION_STATE_ARCHIVED,
|
|
54
|
+
SESSION_STATE_UNKNOWN,
|
|
55
|
+
] as const
|
|
56
|
+
|
|
57
|
+
/** A session activity state. */
|
|
58
|
+
export type SessionState = (typeof SESSION_STATES)[number]
|