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,253 +1,282 @@
1
- /**
2
- * pware.oc.core.mywork.resolver
3
- *
4
- * The "My work" queue — one list of things awaiting the user's action, from
5
- * two sources: an open OpenCode `question` (core, always) and an OMO plan
6
- * awaiting approval (optional, only when `.omo/` is present).
7
- */
8
- import type { OpenQuestion } from "../pware.oc.opencode/resolver/pware.oc.opencode.resolver.question.js"
9
- import type {
10
- AgentStatus,
11
- SessionActivityState,
12
- } from "../pware.oc.opencode/resolver/pware.oc.opencode.resolver.session.js"
13
- import type { ReviewState } from "../pware.oc.omo/resolver/pware.oc.omo.resolver.plan.js"
14
- import type { EnrichedApproval } from "./pware.oc.runtime.mywork-enrich.js"
15
- import {
16
- MY_WORK_GROUP_DISMISSED,
17
- MY_WORK_GROUP_DRAFT_DOCS,
18
- MY_WORK_GROUP_DRAFTING,
19
- MY_WORK_GROUP_FINISHED,
20
- MY_WORK_GROUP_PINNED,
21
- MY_WORK_GROUP_PLANS,
22
- MY_WORK_GROUP_READY_REVIEW,
23
- MY_WORK_GROUP_READY_START,
24
- MY_WORK_GROUP_SESSIONS,
25
- type ApprovalGroupKind,
26
- } from "../pware.oc.core/constants/pware.oc.core.constants.myWork.js"
27
- import { isDraftOf, resolveApprovalGroup } from "../pware.oc.omo/resolver/pware.oc.omo.resolver.approvalGroup.js"
28
- import {
29
- QUESTION_KIND_ERROR,
30
- QUESTION_KIND_INTERRUPTED,
31
- QUESTION_KIND_QUESTION,
32
- type OpenQuestionKind,
33
- } from "../pware.oc.opencode/constants/pware.oc.opencode.constants.questionKind.js"
34
- import {
35
- START_WORK_MAKE_PR,
36
- START_WORK_PLAIN,
37
- START_WORK_SHIP,
38
- type StartWorkMode,
39
- } from "../pware.oc.omo/constants/pware.oc.omo.constants.startWork.js"
40
-
41
- export type { ApprovalGroupKind, OpenQuestionKind, StartWorkMode }
42
-
43
- export type QuestionGroupKind = OpenQuestionKind | typeof MY_WORK_GROUP_DISMISSED
44
-
45
- export type MyWorkItem =
46
- | {
47
- kind: QuestionGroupKind
48
- partId: string
49
- sessionId: string
50
- title: string
51
- startedAt: number | null
52
- /** `state.error` text for interrupted/error rows; null for an open question. */
53
- reason: string | null
54
- }
55
- | {
56
- kind: ApprovalGroupKind
57
- name: string
58
- rel: string
59
- pendingAction: string | null
60
- updatedAt: number | null
61
- sessionState: SessionActivityState | null
62
- review: ReviewState | null
63
- }
64
- | {
65
- kind: typeof MY_WORK_GROUP_SESSIONS
66
- sessionId: string
67
- title: string
68
- status: AgentStatus
69
- timeUpdated: number | null
70
- }
71
- | {
72
- kind: typeof MY_WORK_GROUP_PINNED
73
- sessionId: string
74
- title: string
75
- status: AgentStatus
76
- timeUpdated: number | null
77
- }
78
- | {
79
- kind: typeof MY_WORK_GROUP_DRAFT_DOCS
80
- name: string
81
- rel: string
82
- updatedAt: number | null
83
- }
84
- | {
85
- kind: typeof MY_WORK_GROUP_PLANS
86
- name: string
87
- rel: string
88
- updatedAt: number | null
89
- }
90
-
91
- export type MyWorkKind = MyWorkItem["kind"]
92
-
93
- export const MY_WORK_ORDER: readonly MyWorkKind[] = [
94
- MY_WORK_GROUP_PINNED,
95
- QUESTION_KIND_QUESTION,
96
- QUESTION_KIND_INTERRUPTED,
97
- QUESTION_KIND_ERROR,
98
- MY_WORK_GROUP_SESSIONS,
99
- MY_WORK_GROUP_READY_REVIEW,
100
- MY_WORK_GROUP_READY_START,
101
- MY_WORK_GROUP_FINISHED,
102
- MY_WORK_GROUP_DRAFTING,
103
- MY_WORK_GROUP_DRAFT_DOCS,
104
- MY_WORK_GROUP_PLANS,
105
- MY_WORK_GROUP_DISMISSED,
106
- ]
107
-
108
- const MY_WORK_LABELS: Record<MyWorkKind, string> = {
109
- [MY_WORK_GROUP_PINNED]: "Pinned",
110
- [QUESTION_KIND_QUESTION]: "Awaiting answer",
111
- [QUESTION_KIND_INTERRUPTED]: "Interrupted",
112
- [QUESTION_KIND_ERROR]: "Errors",
113
- [MY_WORK_GROUP_SESSIONS]: "Sessions",
114
- [MY_WORK_GROUP_READY_REVIEW]: "Ready to review",
115
- [MY_WORK_GROUP_READY_START]: "Ready to start",
116
- [MY_WORK_GROUP_FINISHED]: "Finished",
117
- [MY_WORK_GROUP_DISMISSED]: "Dismissed questions",
118
- [MY_WORK_GROUP_DRAFTING]: "Drafting",
119
- [MY_WORK_GROUP_DRAFT_DOCS]: "Draft docs",
120
- [MY_WORK_GROUP_PLANS]: "Plans",
121
- }
122
-
123
- export function myWorkLabel(kind: MyWorkKind): string {
124
- return MY_WORK_LABELS[kind]
125
- }
126
-
127
- /**
128
- * Why "Navigate to session" is unavailable for an approval: no writer session
129
- * was found in the database, or the database itself could not be opened. Null
130
- * when navigation is actionable.
131
- */
132
- export function approvalContinueHint(
133
- sessionId: string | null | undefined,
134
- dbAvailable: boolean,
135
- ): string | null {
136
- if (sessionId) return null
137
- return dbAvailable ? "No session wrote this plan" : "Database unavailable"
138
- }
139
-
140
- /** The exact "start work" command text for a delivery mode and optional plan name. */
141
- export function startWorkCommand(mode: StartWorkMode, planName?: string | null): string {
142
- const name = typeof planName === "string" ? planName.trim() : ""
143
- const base = name ? `start work ${name}` : "start work"
144
- if (mode === START_WORK_MAKE_PR) return `${base} --make-pr`
145
- if (mode === START_WORK_SHIP) return `${base} --ship`
146
- return base
147
- }
148
-
149
- export function groupMyWork(
150
- items: readonly MyWorkItem[],
151
- ): { kind: MyWorkKind; items: MyWorkItem[] }[] {
152
- const out: { kind: MyWorkKind; items: MyWorkItem[] }[] = []
153
- for (const kind of MY_WORK_ORDER) {
154
- const bucket = items.filter((i) => i.kind === kind)
155
- if (bucket.length > 0) out.push({ kind, items: bucket })
156
- }
157
- return out
158
- }
159
-
160
- /** Build the question items from an open-question read (title comes from the row). */
161
- export function toQuestionItems(questions: readonly OpenQuestion[]): MyWorkItem[] {
162
- return questions.map((q) => ({
163
- kind: q.kind,
164
- partId: q.partId,
165
- sessionId: q.sessionId,
166
- title: q.title,
167
- startedAt: q.startedAt,
168
- reason: q.reason,
169
- }))
170
- }
171
-
172
- /** Build the approval items from a pending-approval read, dropping superseded plans. */
173
- export function toApprovalItems(approvals: readonly EnrichedApproval[]): MyWorkItem[] {
174
- const out: MyWorkItem[] = []
175
- for (const a of approvals) {
176
- const kind = resolveApprovalGroup(a.status, isDraftOf(a.rel), a.workState, a.todosDone)
177
- if (!kind) continue
178
- out.push({
179
- kind,
180
- name: a.name,
181
- rel: a.rel,
182
- pendingAction: a.pendingAction,
183
- updatedAt: a.updatedAt,
184
- sessionState: a.sessionState,
185
- review: a.review,
186
- })
187
- }
188
- return out
189
- }
190
-
191
- /** Build the Draft docs items — drafts no action group covers, browsed as files. */
192
- export function toDraftDocItems(
193
- docs: readonly { name: string; rel: string; updatedAt: number | null }[],
194
- ): MyWorkItem[] {
195
- return docs.map((d) => ({ kind: MY_WORK_GROUP_DRAFT_DOCS, name: d.name, rel: d.rel, updatedAt: d.updatedAt }))
196
- }
197
-
198
- /** Build the Plans items plan files no action group covers, browsed as files. */
199
- export function toPlanItems(
200
- docs: readonly { name: string; rel: string; updatedAt: number | null }[],
201
- ): MyWorkItem[] {
202
- return docs.map((d) => ({ kind: MY_WORK_GROUP_PLANS, name: d.name, rel: d.rel, updatedAt: d.updatedAt }))
203
- }
204
-
205
- /** Build the sessions group from recent main sessions — every session, live or idle. */
206
- export function toSessionItems(
207
- sessions: readonly {
208
- id: string
209
- title: string
210
- status: AgentStatus
211
- timeUpdated: number | null
212
- }[],
213
- ): MyWorkItem[] {
214
- return sessions.map((s) => ({
215
- kind: MY_WORK_GROUP_SESSIONS,
216
- sessionId: s.id,
217
- title: s.title,
218
- status: s.status,
219
- timeUpdated: s.timeUpdated,
220
- }))
221
- }
222
-
223
- /** Parse the persisted dismissed-question set (a JSON array of part ids) from the kv store. */
224
- export function parseDismissed(raw: string | null | undefined): ReadonlySet<string> {
225
- if (!raw) return new Set()
226
- try {
227
- const value = JSON.parse(raw)
228
- if (!Array.isArray(value)) return new Set()
229
- return new Set(value.filter((x): x is string => typeof x === "string" && x.length > 0))
230
- } catch {
231
- return new Set()
232
- }
233
- }
234
-
235
- /** Serialize a dismissed-question id set back to the JSON string the kv store holds. */
236
- export function formatDismissed(ids: ReadonlySet<string>): string {
237
- return JSON.stringify([...ids])
238
- }
239
-
240
- export function dropDismissed(
241
- items: readonly MyWorkItem[],
242
- dismissed: ReadonlySet<string>,
243
- ): MyWorkItem[] {
244
- return items.map((i) => {
245
- if (!("partId" in i)) return i
246
- const byId = dismissed.has(i.partId)
247
- const byReason = i.kind === QUESTION_KIND_ERROR
248
- && typeof i.reason === "string"
249
- && i.reason.toLowerCase().includes("dismissed this question")
250
- if (!byId && !byReason) return i
251
- return { ...i, kind: MY_WORK_GROUP_DISMISSED }
252
- })
253
- }
1
+ /**
2
+ * pware.oc.core.mywork.resolver
3
+ *
4
+ * The "My work" queue — one list of things awaiting the user's action, from
5
+ * two sources: an open OpenCode `question` (core, always) and an OMO plan
6
+ * awaiting approval (optional, only when `.omo/` is present).
7
+ */
8
+ import type { OpenQuestion } from "../pware.oc.opencode/resolver/pware.oc.opencode.resolver.question.js"
9
+ import type {
10
+ AgentStatus,
11
+ SessionActivityState,
12
+ } from "../pware.oc.opencode/resolver/pware.oc.opencode.resolver.session.js"
13
+ import type { ReviewState } from "../pware.oc.omo/resolver/pware.oc.omo.resolver.plan.js"
14
+ import type { EnrichedApproval } from "./pware.oc.runtime.mywork-enrich.js"
15
+ import {
16
+ MY_WORK_GROUP_DISMISSED,
17
+ MY_WORK_GROUP_DRAFT_DOCS,
18
+ MY_WORK_GROUP_DRAFTING,
19
+ MY_WORK_GROUP_FINISHED,
20
+ MY_WORK_GROUP_PINNED,
21
+ MY_WORK_GROUP_PLANS,
22
+ MY_WORK_GROUP_READY_REVIEW,
23
+ MY_WORK_GROUP_READY_START,
24
+ MY_WORK_GROUP_SESSIONS,
25
+ type ApprovalGroupKind,
26
+ } from "../pware.oc.core/constants/pware.oc.core.constants.myWork.js"
27
+ import { isDraftOf, resolveApprovalGroup } from "../pware.oc.omo/resolver/pware.oc.omo.resolver.approvalGroup.js"
28
+ import {
29
+ QUESTION_KIND_ERROR,
30
+ QUESTION_KIND_INTERRUPTED,
31
+ QUESTION_KIND_QUESTION,
32
+ type OpenQuestionKind,
33
+ } from "../pware.oc.opencode/constants/pware.oc.opencode.constants.questionKind.js"
34
+ import {
35
+ START_WORK_MAKE_PR,
36
+ START_WORK_PLAIN,
37
+ START_WORK_SHIP,
38
+ type StartWorkMode,
39
+ } from "../pware.oc.omo/constants/pware.oc.omo.constants.startWork.js"
40
+
41
+ export type { ApprovalGroupKind, OpenQuestionKind, StartWorkMode }
42
+
43
+ export type QuestionGroupKind = OpenQuestionKind | typeof MY_WORK_GROUP_DISMISSED
44
+
45
+ export type MyWorkItem =
46
+ | {
47
+ kind: QuestionGroupKind
48
+ partId: string
49
+ sessionId: string
50
+ title: string
51
+ startedAt: number | null
52
+ /** `state.error` text for interrupted/error rows; null for an open question. */
53
+ reason: string | null
54
+ }
55
+ | {
56
+ kind: ApprovalGroupKind
57
+ name: string
58
+ rel: string
59
+ pendingAction: string | null
60
+ updatedAt: number | null
61
+ sessionState: SessionActivityState | null
62
+ review: ReviewState | null
63
+ }
64
+ | {
65
+ kind: typeof MY_WORK_GROUP_SESSIONS
66
+ sessionId: string
67
+ title: string
68
+ status: AgentStatus
69
+ timeUpdated: number | null
70
+ }
71
+ | {
72
+ kind: typeof MY_WORK_GROUP_PINNED
73
+ sessionId: string
74
+ title: string
75
+ status: AgentStatus
76
+ timeUpdated: number | null
77
+ }
78
+ | {
79
+ kind: typeof MY_WORK_GROUP_DRAFT_DOCS
80
+ name: string
81
+ rel: string
82
+ updatedAt: number | null
83
+ }
84
+ | {
85
+ kind: typeof MY_WORK_GROUP_PLANS
86
+ name: string
87
+ rel: string
88
+ updatedAt: number | null
89
+ }
90
+
91
+ export type MyWorkKind = MyWorkItem["kind"]
92
+
93
+ export const MY_WORK_ORDER: readonly MyWorkKind[] = [
94
+ MY_WORK_GROUP_PINNED,
95
+ QUESTION_KIND_QUESTION,
96
+ QUESTION_KIND_INTERRUPTED,
97
+ QUESTION_KIND_ERROR,
98
+ MY_WORK_GROUP_SESSIONS,
99
+ MY_WORK_GROUP_READY_REVIEW,
100
+ MY_WORK_GROUP_READY_START,
101
+ MY_WORK_GROUP_FINISHED,
102
+ MY_WORK_GROUP_DRAFTING,
103
+ MY_WORK_GROUP_DRAFT_DOCS,
104
+ MY_WORK_GROUP_PLANS,
105
+ MY_WORK_GROUP_DISMISSED,
106
+ ]
107
+
108
+ const MY_WORK_LABELS: Record<MyWorkKind, string> = {
109
+ [MY_WORK_GROUP_PINNED]: "Pinned",
110
+ [QUESTION_KIND_QUESTION]: "Awaiting answer",
111
+ [QUESTION_KIND_INTERRUPTED]: "Interrupted",
112
+ [QUESTION_KIND_ERROR]: "Errors",
113
+ [MY_WORK_GROUP_SESSIONS]: "Sessions",
114
+ [MY_WORK_GROUP_READY_REVIEW]: "Ready to review",
115
+ [MY_WORK_GROUP_READY_START]: "Ready to start",
116
+ [MY_WORK_GROUP_FINISHED]: "Finished",
117
+ [MY_WORK_GROUP_DISMISSED]: "Dismissed questions",
118
+ [MY_WORK_GROUP_DRAFTING]: "Drafting",
119
+ [MY_WORK_GROUP_DRAFT_DOCS]: "Draft docs",
120
+ [MY_WORK_GROUP_PLANS]: "Plans",
121
+ }
122
+
123
+ export function myWorkLabel(kind: MyWorkKind): string {
124
+ return MY_WORK_LABELS[kind]
125
+ }
126
+
127
+ /**
128
+ * Why "Navigate to session" is unavailable for an approval: no writer session
129
+ * was found in the database, or the database itself could not be opened. Null
130
+ * when navigation is actionable.
131
+ */
132
+ export function approvalContinueHint(
133
+ sessionId: string | null | undefined,
134
+ dbAvailable: boolean,
135
+ ): string | null {
136
+ if (sessionId) return null
137
+ return dbAvailable ? "No session wrote this plan" : "Database unavailable"
138
+ }
139
+
140
+ /** The exact "start work" command text for a delivery mode and optional plan name. */
141
+ export function startWorkCommand(mode: StartWorkMode, planName?: string | null): string {
142
+ const name = typeof planName === "string" ? planName.trim() : ""
143
+ const base = name ? `start work ${name}` : "start work"
144
+ if (mode === START_WORK_MAKE_PR) return `${base} --make-pr`
145
+ if (mode === START_WORK_SHIP) return `${base} --ship`
146
+ return base
147
+ }
148
+
149
+ /** Group the My work queue. Pinned always leads — even when empty — as a placeholder. */
150
+ export function groupMyWork(
151
+ items: readonly MyWorkItem[],
152
+ ): { kind: MyWorkKind; items: MyWorkItem[] }[] {
153
+ const out: { kind: MyWorkKind; items: MyWorkItem[] }[] = []
154
+ out.push({ kind: MY_WORK_GROUP_PINNED, items: items.filter((i) => i.kind === MY_WORK_GROUP_PINNED) })
155
+ for (const kind of MY_WORK_ORDER) {
156
+ if (kind === MY_WORK_GROUP_PINNED) continue
157
+ const bucket = items.filter((i) => i.kind === kind)
158
+ if (bucket.length > 0) out.push({ kind, items: bucket })
159
+ }
160
+ return out
161
+ }
162
+
163
+ /** Build the question items from an open-question read (title comes from the row). */
164
+ export function toQuestionItems(questions: readonly OpenQuestion[]): MyWorkItem[] {
165
+ return questions.map((q) => ({
166
+ kind: q.kind,
167
+ partId: q.partId,
168
+ sessionId: q.sessionId,
169
+ title: q.title,
170
+ startedAt: q.startedAt,
171
+ reason: q.reason,
172
+ }))
173
+ }
174
+
175
+ /** Build the approval items from a pending-approval read, dropping superseded plans. */
176
+ export function toApprovalItems(approvals: readonly EnrichedApproval[]): MyWorkItem[] {
177
+ const out: MyWorkItem[] = []
178
+ for (const a of approvals) {
179
+ const kind = resolveApprovalGroup(a.status, isDraftOf(a.rel), a.workState, a.todosDone)
180
+ if (!kind) continue
181
+ out.push({
182
+ kind,
183
+ name: a.name,
184
+ rel: a.rel,
185
+ pendingAction: a.pendingAction,
186
+ updatedAt: a.updatedAt,
187
+ sessionState: a.sessionState,
188
+ review: a.review,
189
+ })
190
+ }
191
+ return out
192
+ }
193
+
194
+ /** Build the Draft docs items — drafts no action group covers, browsed as files. */
195
+ export function toDraftDocItems(
196
+ docs: readonly { name: string; rel: string; updatedAt: number | null }[],
197
+ ): MyWorkItem[] {
198
+ return docs.map((d) => ({ kind: MY_WORK_GROUP_DRAFT_DOCS, name: d.name, rel: d.rel, updatedAt: d.updatedAt }))
199
+ }
200
+
201
+ /** Build the Plans items — plan files no action group covers, browsed as files. */
202
+ export function toPlanItems(
203
+ docs: readonly { name: string; rel: string; updatedAt: number | null }[],
204
+ ): MyWorkItem[] {
205
+ return docs.map((d) => ({ kind: MY_WORK_GROUP_PLANS, name: d.name, rel: d.rel, updatedAt: d.updatedAt }))
206
+ }
207
+
208
+ /** Build the sessions group from recent main sessions — every session, live or idle. */
209
+ export function toSessionItems(
210
+ sessions: readonly {
211
+ id: string
212
+ title: string
213
+ status: AgentStatus
214
+ timeUpdated: number | null
215
+ }[],
216
+ ): MyWorkItem[] {
217
+ return sessions.map((s) => ({
218
+ kind: MY_WORK_GROUP_SESSIONS,
219
+ sessionId: s.id,
220
+ title: s.title,
221
+ status: s.status,
222
+ timeUpdated: s.timeUpdated,
223
+ }))
224
+ }
225
+
226
+ /** Build pinned items from pinned session ids joined to their recent-session rows, in pin order; ids missing from `recent` are skipped. */
227
+ export function toPinnedItems(
228
+ pinnedIds: readonly string[],
229
+ recent: readonly {
230
+ id: string
231
+ title: string
232
+ status: AgentStatus
233
+ timeUpdated: number | null
234
+ }[],
235
+ ): MyWorkItem[] {
236
+ const byId = new Map(recent.map((s) => [s.id, s] as const))
237
+ const out: MyWorkItem[] = []
238
+ for (const id of pinnedIds) {
239
+ const s = byId.get(id)
240
+ if (!s) continue
241
+ out.push({
242
+ kind: MY_WORK_GROUP_PINNED,
243
+ sessionId: s.id,
244
+ title: s.title,
245
+ status: s.status,
246
+ timeUpdated: s.timeUpdated,
247
+ })
248
+ }
249
+ return out
250
+ }
251
+
252
+ /** Parse the persisted dismissed-question set (a JSON array of part ids) from the kv store. */
253
+ export function parseDismissed(raw: string | null | undefined): ReadonlySet<string> {
254
+ if (!raw) return new Set()
255
+ try {
256
+ const value = JSON.parse(raw)
257
+ if (!Array.isArray(value)) return new Set()
258
+ return new Set(value.filter((x): x is string => typeof x === "string" && x.length > 0))
259
+ } catch {
260
+ return new Set()
261
+ }
262
+ }
263
+
264
+ /** Serialize a dismissed-question id set back to the JSON string the kv store holds. */
265
+ export function formatDismissed(ids: ReadonlySet<string>): string {
266
+ return JSON.stringify([...ids])
267
+ }
268
+
269
+ export function dropDismissed(
270
+ items: readonly MyWorkItem[],
271
+ dismissed: ReadonlySet<string>,
272
+ ): MyWorkItem[] {
273
+ return items.map((i) => {
274
+ if (!("partId" in i)) return i
275
+ const byId = dismissed.has(i.partId)
276
+ const byReason = i.kind === QUESTION_KIND_ERROR
277
+ && typeof i.reason === "string"
278
+ && i.reason.toLowerCase().includes("dismissed this question")
279
+ if (!byId && !byReason) return i
280
+ return { ...i, kind: MY_WORK_GROUP_DISMISSED }
281
+ })
282
+ }
@@ -0,0 +1,37 @@
1
+ import type { DocView } from "../pware.oc.omo/resolver/pware.oc.omo.resolver.doc.js"
2
+ import {
3
+ DraftFile,
4
+ sessionForPlanFile,
5
+ } from "../pware.oc.omo/resolver/index.js"
6
+ import { readonlyReadGateway } from "../pware.oc.core/pware.oc.core.sqliteGateway.js"
7
+
8
+ export type PlanSessionRead = {
9
+ readonly dbAvailable: boolean
10
+ readonly sessionId: string | null
11
+ }
12
+
13
+ export function readSessionDrafts(opts: {
14
+ readonly dbPath: string
15
+ readonly projectRoot: string | null
16
+ readonly sessionId: string
17
+ }): Promise<DocView[]> {
18
+ if (!opts.projectRoot || !opts.sessionId) return Promise.resolve([])
19
+ return readonlyReadGateway.run<DocView[]>({
20
+ dbPath: opts.dbPath,
21
+ initial: () => [] as DocView[],
22
+ stages: [(db) => DraftFile.list(opts.projectRoot, opts.sessionId, { db })],
23
+ fallback: () => [],
24
+ })
25
+ }
26
+
27
+ export function readPlanSession(opts: {
28
+ readonly dbPath: string
29
+ readonly rel: string
30
+ }): Promise<PlanSessionRead> {
31
+ return readonlyReadGateway.run<PlanSessionRead>({
32
+ dbPath: opts.dbPath,
33
+ initial: () => ({ dbAvailable: true, sessionId: null }),
34
+ stages: [(db) => ({ dbAvailable: true, sessionId: sessionForPlanFile(db, opts.rel) })],
35
+ fallback: () => ({ dbAvailable: false, sessionId: null }),
36
+ })
37
+ }