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,211 +1,248 @@
1
- /**
2
- * pware.oc.core.opencode.resolver.session
3
- *
4
- * Session rows → SessionView plus the hierarchy queries (current, children,
5
- * siblings, recent mains, lookups by id). Read-only against opencode.db.
6
- */
7
- import { uniqueIds, type SqlDb } from "../../pware.oc.core/pware.oc.core.sqlite.js"
8
- import {
9
- SESSION_STATE_ARCHIVED,
10
- SESSION_STATE_AWAITING_BACKGROUND,
11
- SESSION_STATE_IDLE,
12
- SESSION_STATE_STREAMING,
13
- SESSION_STATE_UNKNOWN,
14
- SESSION_STATUS_ARCHIVED,
15
- SESSION_STATUS_IDLE,
16
- SESSION_STATUS_RUNNING,
17
- SESSION_STATUS_UNKNOWN,
18
- type AgentStatus,
19
- type SessionState,
20
- } from "../constants/pware.oc.opencode.constants.sessionStatus.js"
21
-
22
- export type { AgentStatus, SessionState }
23
-
24
- export type SessionRow = {
25
- id: string
26
- project_id: string
27
- parent_id: string | null
28
- directory: string
29
- title: string
30
- agent: string | null
31
- model: string | null
32
- cost: number
33
- tokens_input: number
34
- tokens_output: number
35
- tokens_reasoning: number
36
- time_created: number
37
- time_updated: number
38
- time_archived: number | null
39
- /** 1 when the session has at least one part, 0 for an empty shell (ghost). */
40
- has_content: number
41
- }
42
-
43
- /** Finer-grained "is this session still working" state for approval rows. */
44
- export type SessionActivityState = {
45
- running: boolean
46
- state: SessionState
47
- }
48
-
49
- export type SessionView = {
50
- id: string
51
- title: string
52
- agent: string
53
- status: AgentStatus
54
- isMain: boolean
55
- parentId: string | null
56
- directory: string
57
- tokensTotal: number
58
- cost: number
59
- timeUpdated: number
60
- ageMs: number
61
- hasContent: boolean
62
- }
63
-
64
- const RUNNING_MS = 2 * 60 * 1000
65
-
66
- const SESSION_SELECT = `
67
- id, project_id, parent_id, directory, title, agent, model,
68
- cost, tokens_input, tokens_output, tokens_reasoning,
69
- time_created, time_updated, time_archived,
70
- EXISTS (SELECT 1 FROM part WHERE part.session_id = session.id) AS has_content
71
- `
72
-
73
- export function inferStatus(row: SessionRow, now = Date.now()): AgentStatus {
74
- if (row.time_archived) return SESSION_STATUS_ARCHIVED
75
- const age = now - (row.time_updated || 0)
76
- if (age < 0) return SESSION_STATUS_UNKNOWN
77
- if (age <= RUNNING_MS) return SESSION_STATUS_RUNNING
78
- return SESSION_STATUS_IDLE
79
- }
80
-
81
- export function toSessionView(row: SessionRow, now = Date.now()): SessionView {
82
- const ti = row.tokens_input || 0
83
- const to = row.tokens_output || 0
84
- const tr = row.tokens_reasoning || 0
85
- return {
86
- id: row.id,
87
- title: row.title?.trim() || "untitled",
88
- agent: row.agent?.trim() || "unknown",
89
- status: inferStatus(row, now),
90
- isMain: !row.parent_id,
91
- parentId: row.parent_id,
92
- directory: row.directory,
93
- tokensTotal: ti + to + tr,
94
- cost: row.cost || 0,
95
- timeUpdated: row.time_updated,
96
- ageMs: Math.max(0, now - (row.time_updated || 0)),
97
- hasContent: row.has_content === 1,
98
- }
99
- }
100
-
101
- /** A session with real content (≥1 part). A ghost — created but never prompted — is false. */
102
- export function isRealSession(view: Pick<SessionView, "hasContent"> | null | undefined): boolean {
103
- return Boolean(view?.hasContent)
104
- }
105
-
106
- /**
107
- * Recompute a `SessionView` status against a fresh clock. The live snapshot
108
- * cache refreshes `ageMs` on every hit, so `status` (also age-derived) must be
109
- * refreshed with it — otherwise a session that went idle keeps reading running.
110
- */
111
- export function refreshSessionStatus(view: SessionView, now: number): AgentStatus {
112
- if (view.status === SESSION_STATUS_ARCHIVED) return SESSION_STATUS_ARCHIVED
113
- const age = now - view.timeUpdated
114
- if (age < 0) return SESSION_STATUS_UNKNOWN
115
- if (age <= RUNNING_MS) return SESSION_STATUS_RUNNING
116
- return SESSION_STATUS_IDLE
117
- }
118
-
119
- export function getSessionById(db: SqlDb, id: string): SessionRow | null {
120
- return db.get<SessionRow>(
121
- `SELECT ${SESSION_SELECT} FROM session WHERE id = ? LIMIT 1`,
122
- id,
123
- )
124
- }
125
-
126
- /**
127
- * Is this session still working, and how? Streaming = a fresh `time_updated`;
128
- * awaiting-background = stale in SQLite but the caller's already-resolved
129
- * `backgroundTaskActive` (`.omo/run-continuation` marker) is true;
130
- * idle/archived/unknown otherwise. The omo layer resolves the marker — this
131
- * opencode function never reads omo files.
132
- */
133
- export function sessionActivityState(
134
- db: SqlDb,
135
- sessionId: string,
136
- opts?: { backgroundTaskActive?: boolean | null; now?: number },
137
- ): SessionActivityState {
138
- const row = getSessionById(db, sessionId)
139
- if (!row) return { running: false, state: SESSION_STATE_UNKNOWN }
140
- if (row.time_archived) return { running: false, state: SESSION_STATE_ARCHIVED }
141
- const now = opts?.now ?? Date.now()
142
- if (now - row.time_updated <= RUNNING_MS) return { running: true, state: SESSION_STATE_STREAMING }
143
- if (opts?.backgroundTaskActive) return { running: true, state: SESSION_STATE_AWAITING_BACKGROUND }
144
- return { running: false, state: SESSION_STATE_IDLE }
145
- }
146
-
147
- export function listChildSessions(db: SqlDb, parentId: string): SessionRow[] {
148
- return db.all<SessionRow>(
149
- `SELECT ${SESSION_SELECT} FROM session WHERE parent_id = ? ORDER BY time_updated DESC LIMIT 40`,
150
- parentId,
151
- )
152
- }
153
-
154
- export function listSiblingSessions(db: SqlDb, parentId: string, excludeId: string): SessionRow[] {
155
- return db.all<SessionRow>(
156
- `SELECT ${SESSION_SELECT} FROM session
157
- WHERE parent_id = ? AND id != ?
158
- ORDER BY time_updated DESC LIMIT 40`,
159
- parentId,
160
- excludeId,
161
- )
162
- }
163
-
164
- const RECENT_LIMIT = 4
165
-
166
- export function listRecentMainSessions(
167
- db: SqlDb,
168
- opts: { projectId: string; limit?: number },
169
- ): SessionRow[] {
170
- const limit = Math.max(1, Math.min(opts.limit ?? RECENT_LIMIT, 20))
171
- try {
172
- return db.all<SessionRow>(
173
- `SELECT ${SESSION_SELECT} FROM session
174
- WHERE parent_id IS NULL
175
- AND (time_archived IS NULL OR time_archived = 0)
176
- AND project_id = ?
177
- AND EXISTS (SELECT 1 FROM part WHERE part.session_id = session.id)
178
- ORDER BY time_updated DESC
179
- LIMIT ${limit}`,
180
- opts.projectId,
181
- )
182
- } catch {
183
- return []
184
- }
185
- }
186
-
187
- export function getSessionsByIds(db: SqlDb, ids: string[]): SessionRow[] {
188
- const clean = uniqueIds(ids)
189
- if (clean.length === 0) return []
190
- const placeholders = clean.map(() => "?").join(",")
191
- return db.all<SessionRow>(
192
- `SELECT ${SESSION_SELECT} FROM session WHERE id IN (${placeholders})`,
193
- ...clean,
194
- )
195
- }
196
-
197
- export function sessionScanStamp(db: SqlDb, sessionId: string): string {
198
- try {
199
- const s = db.get<{ time_updated: number }>(
200
- `SELECT time_updated FROM session WHERE id = ? LIMIT 1`,
201
- sessionId,
202
- )
203
- const p = db.get<{ m: number | null }>(
204
- `SELECT MAX(time_updated) AS m FROM part WHERE session_id = ?`,
205
- sessionId,
206
- )
207
- return `${s?.time_updated ?? 0}|${p?.m ?? 0}`
208
- } catch {
209
- return "x"
210
- }
211
- }
1
+ /**
2
+ * pware.oc.core.opencode.resolver.session
3
+ *
4
+ * Session rows → SessionView plus the hierarchy queries (current, children,
5
+ * siblings, recent mains, lookups by id). Read-only against opencode.db.
6
+ */
7
+ import { uniqueIds, type SqlDb } from "../../pware.oc.core/pware.oc.core.sqlite.js"
8
+ import {
9
+ SESSION_STATE_ARCHIVED,
10
+ SESSION_STATE_AWAITING_BACKGROUND,
11
+ SESSION_STATE_IDLE,
12
+ SESSION_STATE_STREAMING,
13
+ SESSION_STATE_UNKNOWN,
14
+ SESSION_STATUS_ARCHIVED,
15
+ SESSION_STATUS_IDLE,
16
+ SESSION_STATUS_RUNNING,
17
+ SESSION_STATUS_UNKNOWN,
18
+ type AgentStatus,
19
+ type SessionState,
20
+ } from "../constants/pware.oc.opencode.constants.sessionStatus.js"
21
+
22
+ export type { AgentStatus, SessionState }
23
+
24
+ export type SessionRow = {
25
+ id: string
26
+ project_id: string
27
+ parent_id: string | null
28
+ directory: string
29
+ title: string
30
+ agent: string | null
31
+ model: string | null
32
+ cost: number
33
+ tokens_input: number
34
+ tokens_output: number
35
+ tokens_reasoning: number
36
+ time_created: number
37
+ time_updated: number
38
+ time_archived: number | null
39
+ /** 1 when the session has at least one part, 0 for an empty shell (ghost). */
40
+ has_content: number
41
+ }
42
+
43
+ /** Finer-grained "is this session still working" state for approval rows. */
44
+ export type SessionActivityState = {
45
+ running: boolean
46
+ state: SessionState
47
+ }
48
+
49
+ export type SessionView = {
50
+ id: string
51
+ title: string
52
+ agent: string
53
+ status: AgentStatus
54
+ isMain: boolean
55
+ parentId: string | null
56
+ directory: string
57
+ tokensTotal: number
58
+ cost: number
59
+ timeUpdated: number
60
+ ageMs: number
61
+ hasContent: boolean
62
+ }
63
+
64
+ const RUNNING_MS = 2 * 60 * 1000
65
+
66
+ export const HOUR_MS = 3_600_000
67
+
68
+ /**
69
+ * Age tier for a session row: how bright should it render?
70
+ *
71
+ * - `keep` (pinned/current) is always full.
72
+ * - `ageMs` null or NaN (unknown / uncomputed) is treated as full — defensive,
73
+ * never hide a row whose age we do not know.
74
+ * - age < dimMs → full
75
+ * - age < visibleMs dim
76
+ * - otherwise hidden
77
+ *
78
+ * Boundaries: age exactly `dimMs` is `dim`; age exactly `visibleMs` is `hidden`.
79
+ */
80
+ export function sessionAgeTier(
81
+ ageMs: number | null,
82
+ dimMs: number,
83
+ visibleMs: number,
84
+ keep: boolean,
85
+ ): "full" | "dim" | "hidden" {
86
+ if (keep) return "full"
87
+ if (ageMs == null || Number.isNaN(ageMs)) return "full"
88
+ if (ageMs < dimMs) return "full"
89
+ if (ageMs < visibleMs) return "dim"
90
+ return "hidden"
91
+ }
92
+
93
+ /**
94
+ * Row opacity is the single decision point: dim rows render at 0.5, everything
95
+ * else (full, hidden, kept) uses the renderer default. Never hardcode 0.5
96
+ * elsewhere callers import this.
97
+ */
98
+ export function sessionRowOpacity(
99
+ ageMs: number | null,
100
+ dimMs: number,
101
+ visibleMs: number,
102
+ keep: boolean,
103
+ ): number | undefined {
104
+ return sessionAgeTier(ageMs, dimMs, visibleMs, keep) === "dim" ? 0.5 : undefined
105
+ }
106
+
107
+ const SESSION_SELECT = `
108
+ id, project_id, parent_id, directory, title, agent, model,
109
+ cost, tokens_input, tokens_output, tokens_reasoning,
110
+ time_created, time_updated, time_archived,
111
+ EXISTS (SELECT 1 FROM part WHERE part.session_id = session.id) AS has_content
112
+ `
113
+
114
+ export function inferStatus(row: SessionRow, now = Date.now()): AgentStatus {
115
+ if (row.time_archived) return SESSION_STATUS_ARCHIVED
116
+ const age = now - (row.time_updated || 0)
117
+ if (age < 0) return SESSION_STATUS_UNKNOWN
118
+ if (age <= RUNNING_MS) return SESSION_STATUS_RUNNING
119
+ return SESSION_STATUS_IDLE
120
+ }
121
+
122
+ export function toSessionView(row: SessionRow, now = Date.now()): SessionView {
123
+ const ti = row.tokens_input || 0
124
+ const to = row.tokens_output || 0
125
+ const tr = row.tokens_reasoning || 0
126
+ return {
127
+ id: row.id,
128
+ title: row.title?.trim() || "untitled",
129
+ agent: row.agent?.trim() || "unknown",
130
+ status: inferStatus(row, now),
131
+ isMain: !row.parent_id,
132
+ parentId: row.parent_id,
133
+ directory: row.directory,
134
+ tokensTotal: ti + to + tr,
135
+ cost: row.cost || 0,
136
+ timeUpdated: row.time_updated,
137
+ ageMs: Math.max(0, now - (row.time_updated || 0)),
138
+ hasContent: row.has_content === 1,
139
+ }
140
+ }
141
+
142
+ /** A session with real content (≥1 part). A ghost created but never prompted — is false. */
143
+ export function isRealSession(view: Pick<SessionView, "hasContent"> | null | undefined): boolean {
144
+ return Boolean(view?.hasContent)
145
+ }
146
+
147
+ /**
148
+ * Recompute a `SessionView` status against a fresh clock. The live snapshot
149
+ * cache refreshes `ageMs` on every hit, so `status` (also age-derived) must be
150
+ * refreshed with it — otherwise a session that went idle keeps reading running.
151
+ */
152
+ export function refreshSessionStatus(view: SessionView, now: number): AgentStatus {
153
+ if (view.status === SESSION_STATUS_ARCHIVED) return SESSION_STATUS_ARCHIVED
154
+ const age = now - view.timeUpdated
155
+ if (age < 0) return SESSION_STATUS_UNKNOWN
156
+ if (age <= RUNNING_MS) return SESSION_STATUS_RUNNING
157
+ return SESSION_STATUS_IDLE
158
+ }
159
+
160
+ export function getSessionById(db: SqlDb, id: string): SessionRow | null {
161
+ return db.get<SessionRow>(
162
+ `SELECT ${SESSION_SELECT} FROM session WHERE id = ? LIMIT 1`,
163
+ id,
164
+ )
165
+ }
166
+
167
+ /**
168
+ * Is this session still working, and how? Streaming = a fresh `time_updated`;
169
+ * awaiting-background = stale in SQLite but the caller's already-resolved
170
+ * `backgroundTaskActive` (`.omo/run-continuation` marker) is true;
171
+ * idle/archived/unknown otherwise. The omo layer resolves the marker — this
172
+ * opencode function never reads omo files.
173
+ */
174
+ export function sessionActivityState(
175
+ db: SqlDb,
176
+ sessionId: string,
177
+ opts?: { backgroundTaskActive?: boolean | null; now?: number },
178
+ ): SessionActivityState {
179
+ const row = getSessionById(db, sessionId)
180
+ if (!row) return { running: false, state: SESSION_STATE_UNKNOWN }
181
+ if (row.time_archived) return { running: false, state: SESSION_STATE_ARCHIVED }
182
+ const now = opts?.now ?? Date.now()
183
+ if (now - row.time_updated <= RUNNING_MS) return { running: true, state: SESSION_STATE_STREAMING }
184
+ if (opts?.backgroundTaskActive) return { running: true, state: SESSION_STATE_AWAITING_BACKGROUND }
185
+ return { running: false, state: SESSION_STATE_IDLE }
186
+ }
187
+
188
+ export function listChildSessions(db: SqlDb, parentId: string): SessionRow[] {
189
+ return db.all<SessionRow>(
190
+ `SELECT ${SESSION_SELECT} FROM session WHERE parent_id = ? ORDER BY time_updated DESC LIMIT 40`,
191
+ parentId,
192
+ )
193
+ }
194
+
195
+ export function listSiblingSessions(db: SqlDb, parentId: string, excludeId: string): SessionRow[] {
196
+ return db.all<SessionRow>(
197
+ `SELECT ${SESSION_SELECT} FROM session
198
+ WHERE parent_id = ? AND id != ?
199
+ ORDER BY time_updated DESC LIMIT 40`,
200
+ parentId,
201
+ excludeId,
202
+ )
203
+ }
204
+
205
+ const RECENT_LIMIT = 4
206
+
207
+ export function listRecentMainSessions(
208
+ db: SqlDb,
209
+ opts: { projectId: string; limit?: number },
210
+ ): SessionRow[] {
211
+ const limit = Math.max(1, Math.min(opts.limit ?? RECENT_LIMIT, 20))
212
+ return db.all<SessionRow>(
213
+ `SELECT ${SESSION_SELECT} FROM session
214
+ WHERE parent_id IS NULL
215
+ AND (time_archived IS NULL OR time_archived = 0)
216
+ AND project_id = ?
217
+ AND EXISTS (SELECT 1 FROM part WHERE part.session_id = session.id)
218
+ ORDER BY time_updated DESC
219
+ LIMIT ${limit}`,
220
+ opts.projectId,
221
+ )
222
+ }
223
+
224
+ export function getSessionsByIds(db: SqlDb, ids: string[]): SessionRow[] {
225
+ const clean = uniqueIds(ids)
226
+ if (clean.length === 0) return []
227
+ const placeholders = clean.map(() => "?").join(",")
228
+ return db.all<SessionRow>(
229
+ `SELECT ${SESSION_SELECT} FROM session WHERE id IN (${placeholders})`,
230
+ ...clean,
231
+ )
232
+ }
233
+
234
+ export function sessionScanStamp(db: SqlDb, sessionId: string): string {
235
+ try {
236
+ const s = db.get<{ time_updated: number }>(
237
+ `SELECT time_updated FROM session WHERE id = ? LIMIT 1`,
238
+ sessionId,
239
+ )
240
+ const p = db.get<{ m: number | null }>(
241
+ `SELECT MAX(time_updated) AS m FROM part WHERE session_id = ?`,
242
+ sessionId,
243
+ )
244
+ return `${s?.time_updated ?? 0}|${p?.m ?? 0}`
245
+ } catch {
246
+ return "x"
247
+ }
248
+ }
@@ -1,26 +1,22 @@
1
- /**
2
- * pware.oc.core.opencode.resolver.todo
3
- *
4
- * Todo rows for one session — content, status, priority, position.
5
- */
6
- import type { SqlDb } from "../../pware.oc.core/pware.oc.core.sqlite.js"
7
-
8
- export type TodoRow = {
9
- content: string
10
- status: string
11
- priority: string
12
- position: number
13
- }
14
-
15
- export function listTodos(db: SqlDb, sessionId: string): TodoRow[] {
16
- try {
17
- return db.all<TodoRow>(
18
- `SELECT content, status, priority, position
19
- FROM todo WHERE session_id = ?
20
- ORDER BY position ASC LIMIT 40`,
21
- sessionId,
22
- )
23
- } catch {
24
- return []
25
- }
26
- }
1
+ /**
2
+ * pware.oc.core.opencode.resolver.todo
3
+ *
4
+ * Todo rows for one session — content, status, priority, position.
5
+ */
6
+ import type { SqlDb } from "../../pware.oc.core/pware.oc.core.sqlite.js"
7
+
8
+ export type TodoRow = {
9
+ content: string
10
+ status: string
11
+ priority: string
12
+ position: number
13
+ }
14
+
15
+ export function listTodos(db: SqlDb, sessionId: string): TodoRow[] {
16
+ return db.all<TodoRow>(
17
+ `SELECT content, status, priority, position
18
+ FROM todo WHERE session_id = ?
19
+ ORDER BY position ASC LIMIT 40`,
20
+ sessionId,
21
+ )
22
+ }