@vladimirven/openswe 0.1.0
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/AGENTS.md +203 -0
- package/CLAUDE.md +203 -0
- package/README.md +166 -0
- package/bun.lock +447 -0
- package/bunfig.toml +4 -0
- package/package.json +42 -0
- package/src/app.tsx +84 -0
- package/src/components/App.tsx +526 -0
- package/src/components/ConfirmDialog.tsx +88 -0
- package/src/components/Footer.tsx +50 -0
- package/src/components/HelpModal.tsx +136 -0
- package/src/components/IssueSelectorModal.tsx +701 -0
- package/src/components/ManualSessionModal.tsx +191 -0
- package/src/components/PhaseProgress.tsx +45 -0
- package/src/components/Preview.tsx +249 -0
- package/src/components/ProviderSwitcherModal.tsx +156 -0
- package/src/components/ScrollableText.tsx +120 -0
- package/src/components/SessionCard.tsx +60 -0
- package/src/components/SessionList.tsx +79 -0
- package/src/components/SessionTerminal.tsx +89 -0
- package/src/components/StatusBar.tsx +84 -0
- package/src/components/ThemeSwitcherModal.tsx +237 -0
- package/src/components/index.ts +58 -0
- package/src/components/session-utils.ts +337 -0
- package/src/components/theme.ts +206 -0
- package/src/components/types.ts +215 -0
- package/src/config/defaults.ts +44 -0
- package/src/config/env.ts +67 -0
- package/src/config/global.ts +252 -0
- package/src/config/index.ts +171 -0
- package/src/config/types.ts +131 -0
- package/src/core/.gitkeep +0 -0
- package/src/core/index.ts +5 -0
- package/src/core/parser.ts +62 -0
- package/src/core/process-manager.ts +52 -0
- package/src/core/session.ts +423 -0
- package/src/core/tmux.ts +206 -0
- package/src/git/.gitkeep +0 -0
- package/src/git/index.ts +8 -0
- package/src/git/repo.ts +443 -0
- package/src/git/worktree.ts +317 -0
- package/src/github/.gitkeep +0 -0
- package/src/github/client.ts +208 -0
- package/src/github/index.ts +8 -0
- package/src/github/issues.ts +351 -0
- package/src/index.ts +369 -0
- package/src/prompts/.gitkeep +0 -0
- package/src/prompts/index.ts +1 -0
- package/src/prompts/swe-system.ts +22 -0
- package/src/providers/claude.ts +103 -0
- package/src/providers/index.ts +21 -0
- package/src/providers/opencode.ts +98 -0
- package/src/providers/registry.ts +53 -0
- package/src/providers/types.ts +117 -0
- package/src/store/buffers.ts +234 -0
- package/src/store/db.test.ts +579 -0
- package/src/store/db.ts +249 -0
- package/src/store/index.ts +101 -0
- package/src/store/project.ts +119 -0
- package/src/store/schema.sql +71 -0
- package/src/store/sessions.ts +454 -0
- package/src/store/types.ts +194 -0
- package/src/theme/context.tsx +170 -0
- package/src/theme/custom.ts +134 -0
- package/src/theme/index.ts +58 -0
- package/src/theme/loader.ts +264 -0
- package/src/theme/themes/aura.json +69 -0
- package/src/theme/themes/ayu.json +80 -0
- package/src/theme/themes/carbonfox.json +248 -0
- package/src/theme/themes/catppuccin-frappe.json +233 -0
- package/src/theme/themes/catppuccin-macchiato.json +233 -0
- package/src/theme/themes/catppuccin.json +112 -0
- package/src/theme/themes/cobalt2.json +228 -0
- package/src/theme/themes/cursor.json +249 -0
- package/src/theme/themes/dracula.json +219 -0
- package/src/theme/themes/everforest.json +241 -0
- package/src/theme/themes/flexoki.json +237 -0
- package/src/theme/themes/github.json +233 -0
- package/src/theme/themes/gruvbox.json +242 -0
- package/src/theme/themes/kanagawa.json +77 -0
- package/src/theme/themes/lucent-orng.json +237 -0
- package/src/theme/themes/material.json +235 -0
- package/src/theme/themes/matrix.json +77 -0
- package/src/theme/themes/mercury.json +252 -0
- package/src/theme/themes/monokai.json +221 -0
- package/src/theme/themes/nightowl.json +221 -0
- package/src/theme/themes/nord.json +223 -0
- package/src/theme/themes/one-dark.json +84 -0
- package/src/theme/themes/opencode.json +245 -0
- package/src/theme/themes/orng.json +249 -0
- package/src/theme/themes/osaka-jade.json +93 -0
- package/src/theme/themes/palenight.json +222 -0
- package/src/theme/themes/rosepine.json +234 -0
- package/src/theme/themes/solarized.json +223 -0
- package/src/theme/themes/synthwave84.json +226 -0
- package/src/theme/themes/tokyonight.json +243 -0
- package/src/theme/themes/vercel.json +245 -0
- package/src/theme/themes/vesper.json +218 -0
- package/src/theme/themes/zenburn.json +223 -0
- package/src/theme/types.ts +225 -0
- package/src/types/sql.d.ts +4 -0
- package/src/utils/ansi-parser.ts +225 -0
- package/src/utils/format.ts +46 -0
- package/src/utils/id.ts +15 -0
- package/src/utils/logger.ts +112 -0
- package/src/utils/prerequisites.ts +118 -0
- package/src/utils/shell.ts +9 -0
- package/src/wizard/flows.ts +419 -0
- package/src/wizard/index.ts +37 -0
- package/src/wizard/prompts.ts +190 -0
- package/src/workspace/detect.test.ts +51 -0
- package/src/workspace/detect.ts +223 -0
- package/src/workspace/index.ts +71 -0
- package/src/workspace/init.ts +131 -0
- package/src/workspace/paths.ts +143 -0
- package/src/workspace/project.ts +164 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session database operations
|
|
3
|
+
*
|
|
4
|
+
* CRUD operations for managing sessions in the database.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { getDatabase, nowISO } from "./db"
|
|
8
|
+
import { generateId } from "../utils/id"
|
|
9
|
+
import type {
|
|
10
|
+
Session,
|
|
11
|
+
CreateSessionInput,
|
|
12
|
+
UpdateSessionInput,
|
|
13
|
+
Phase,
|
|
14
|
+
Status,
|
|
15
|
+
AISessionData,
|
|
16
|
+
} from "./types"
|
|
17
|
+
import { isValidAISessionData } from "./types"
|
|
18
|
+
import { logger } from "../utils/logger"
|
|
19
|
+
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// Database Row Type
|
|
22
|
+
// ============================================================================
|
|
23
|
+
|
|
24
|
+
interface SessionRow {
|
|
25
|
+
id: string
|
|
26
|
+
name: string
|
|
27
|
+
issue_number: number | null
|
|
28
|
+
issue_title: string | null
|
|
29
|
+
issue_body: string | null
|
|
30
|
+
issue_url: string | null
|
|
31
|
+
worktree_path: string
|
|
32
|
+
branch_name: string
|
|
33
|
+
phase: string
|
|
34
|
+
status: string
|
|
35
|
+
attention_reason: string | null
|
|
36
|
+
retry_count: number
|
|
37
|
+
tokens_used: number
|
|
38
|
+
pid: number | null
|
|
39
|
+
ai_session_data: string | null
|
|
40
|
+
created_at: string
|
|
41
|
+
updated_at: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ============================================================================
|
|
45
|
+
// Row Mapping
|
|
46
|
+
// ============================================================================
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Convert database row to Session
|
|
50
|
+
*/
|
|
51
|
+
function rowToSession(row: SessionRow): Session {
|
|
52
|
+
return {
|
|
53
|
+
id: row.id,
|
|
54
|
+
name: row.name,
|
|
55
|
+
issueNumber: row.issue_number,
|
|
56
|
+
issueTitle: row.issue_title,
|
|
57
|
+
issueBody: row.issue_body,
|
|
58
|
+
issueUrl: row.issue_url,
|
|
59
|
+
worktreePath: row.worktree_path,
|
|
60
|
+
branchName: row.branch_name,
|
|
61
|
+
phase: row.phase as Phase,
|
|
62
|
+
status: row.status as Status,
|
|
63
|
+
attentionReason: row.attention_reason,
|
|
64
|
+
retryCount: row.retry_count,
|
|
65
|
+
tokensUsed: row.tokens_used,
|
|
66
|
+
pid: row.pid,
|
|
67
|
+
aiSessionData: parseAISessionData(row.ai_session_data),
|
|
68
|
+
createdAt: row.created_at,
|
|
69
|
+
updatedAt: row.updated_at,
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Parse AI session data JSON
|
|
75
|
+
*/
|
|
76
|
+
function parseAISessionData(data: string | null): AISessionData | null {
|
|
77
|
+
if (!data) return null
|
|
78
|
+
try {
|
|
79
|
+
const parsed = JSON.parse(data)
|
|
80
|
+
if (!isValidAISessionData(parsed)) return null
|
|
81
|
+
return parsed
|
|
82
|
+
} catch {
|
|
83
|
+
return null
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Serialize AI session data to JSON
|
|
89
|
+
*/
|
|
90
|
+
function serializeAISessionData(data: AISessionData | null | undefined): string | null {
|
|
91
|
+
if (!data) return null
|
|
92
|
+
return JSON.stringify(data)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ============================================================================
|
|
96
|
+
// Query Operations
|
|
97
|
+
// ============================================================================
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get all sessions
|
|
101
|
+
*/
|
|
102
|
+
export function getAllSessions(): Session[] {
|
|
103
|
+
const db = getDatabase()
|
|
104
|
+
const rows = db.query<SessionRow, []>("SELECT * FROM sessions ORDER BY created_at DESC").all()
|
|
105
|
+
return rows.map(rowToSession)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get a session by ID
|
|
110
|
+
*
|
|
111
|
+
* @param id - Session UUID
|
|
112
|
+
* @returns Session or null if not found
|
|
113
|
+
*/
|
|
114
|
+
export function getSession(id: string): Session | null {
|
|
115
|
+
const db = getDatabase()
|
|
116
|
+
const row = db
|
|
117
|
+
.query<SessionRow, [string]>("SELECT * FROM sessions WHERE id = ?")
|
|
118
|
+
.get(id)
|
|
119
|
+
|
|
120
|
+
return row ? rowToSession(row) : null
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get sessions by status
|
|
125
|
+
*
|
|
126
|
+
* @param status - Status to filter by
|
|
127
|
+
*/
|
|
128
|
+
export function getSessionsByStatus(status: Status): Session[] {
|
|
129
|
+
const db = getDatabase()
|
|
130
|
+
const rows = db
|
|
131
|
+
.query<SessionRow, [string]>(
|
|
132
|
+
"SELECT * FROM sessions WHERE status = ? ORDER BY created_at DESC"
|
|
133
|
+
)
|
|
134
|
+
.all(status)
|
|
135
|
+
|
|
136
|
+
return rows.map(rowToSession)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get sessions by phase
|
|
141
|
+
*
|
|
142
|
+
* @param phase - Phase to filter by
|
|
143
|
+
*/
|
|
144
|
+
export function getSessionsByPhase(phase: Phase): Session[] {
|
|
145
|
+
const db = getDatabase()
|
|
146
|
+
const rows = db
|
|
147
|
+
.query<SessionRow, [string]>(
|
|
148
|
+
"SELECT * FROM sessions WHERE phase = ? ORDER BY created_at DESC"
|
|
149
|
+
)
|
|
150
|
+
.all(phase)
|
|
151
|
+
|
|
152
|
+
return rows.map(rowToSession)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Get count of active sessions
|
|
157
|
+
*/
|
|
158
|
+
export function getActiveSessionCount(): number {
|
|
159
|
+
const db = getDatabase()
|
|
160
|
+
const result = db
|
|
161
|
+
.query<{ count: number }, []>(
|
|
162
|
+
"SELECT COUNT(*) as count FROM sessions WHERE status = 'active'"
|
|
163
|
+
)
|
|
164
|
+
.get()
|
|
165
|
+
|
|
166
|
+
return result?.count ?? 0
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get count of all sessions
|
|
171
|
+
*/
|
|
172
|
+
export function getSessionCount(): number {
|
|
173
|
+
const db = getDatabase()
|
|
174
|
+
const result = db
|
|
175
|
+
.query<{ count: number }, []>("SELECT COUNT(*) as count FROM sessions")
|
|
176
|
+
.get()
|
|
177
|
+
|
|
178
|
+
return result?.count ?? 0
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ============================================================================
|
|
182
|
+
// Create Operations
|
|
183
|
+
// ============================================================================
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Create a new session
|
|
187
|
+
*
|
|
188
|
+
* @param data - Session creation data
|
|
189
|
+
* @returns The created Session
|
|
190
|
+
*/
|
|
191
|
+
export function createSession(data: CreateSessionInput): Session {
|
|
192
|
+
const db = getDatabase()
|
|
193
|
+
const id = generateId()
|
|
194
|
+
const now = nowISO()
|
|
195
|
+
const aiSessionData = serializeAISessionData(data.aiSessionData ?? null)
|
|
196
|
+
|
|
197
|
+
logger.debug("Creating session", {
|
|
198
|
+
id,
|
|
199
|
+
name: data.name,
|
|
200
|
+
issueNumber: data.issueNumber ?? null,
|
|
201
|
+
worktreePath: data.worktreePath,
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
db.query(
|
|
205
|
+
`INSERT INTO sessions (
|
|
206
|
+
id, name, issue_number, issue_title, issue_body, issue_url,
|
|
207
|
+
worktree_path, branch_name, phase, status,
|
|
208
|
+
attention_reason, retry_count, tokens_used, pid, ai_session_data,
|
|
209
|
+
created_at, updated_at
|
|
210
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending', 'queued', NULL, 0, 0, NULL, ?, ?, ?)`
|
|
211
|
+
).run(
|
|
212
|
+
id,
|
|
213
|
+
data.name,
|
|
214
|
+
data.issueNumber ?? null,
|
|
215
|
+
data.issueTitle ?? null,
|
|
216
|
+
data.issueBody ?? null,
|
|
217
|
+
data.issueUrl ?? null,
|
|
218
|
+
data.worktreePath,
|
|
219
|
+
data.branchName,
|
|
220
|
+
aiSessionData,
|
|
221
|
+
now,
|
|
222
|
+
now
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
logger.debug("Session created", { id })
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
id,
|
|
229
|
+
name: data.name,
|
|
230
|
+
issueNumber: data.issueNumber ?? null,
|
|
231
|
+
issueTitle: data.issueTitle ?? null,
|
|
232
|
+
issueBody: data.issueBody ?? null,
|
|
233
|
+
issueUrl: data.issueUrl ?? null,
|
|
234
|
+
worktreePath: data.worktreePath,
|
|
235
|
+
branchName: data.branchName,
|
|
236
|
+
phase: "pending",
|
|
237
|
+
status: "queued",
|
|
238
|
+
attentionReason: null,
|
|
239
|
+
retryCount: 0,
|
|
240
|
+
tokensUsed: 0,
|
|
241
|
+
pid: null,
|
|
242
|
+
aiSessionData: data.aiSessionData ?? null,
|
|
243
|
+
createdAt: now,
|
|
244
|
+
updatedAt: now,
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ============================================================================
|
|
249
|
+
// Update Operations
|
|
250
|
+
// ============================================================================
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Update a session
|
|
254
|
+
*
|
|
255
|
+
* @param id - Session ID
|
|
256
|
+
* @param data - Fields to update
|
|
257
|
+
* @returns Updated Session
|
|
258
|
+
* @throws Error if session not found
|
|
259
|
+
*/
|
|
260
|
+
export function updateSession(id: string, data: UpdateSessionInput): Session {
|
|
261
|
+
const db = getDatabase()
|
|
262
|
+
const now = nowISO()
|
|
263
|
+
|
|
264
|
+
// Build dynamic update query
|
|
265
|
+
const updates: string[] = ["updated_at = ?"]
|
|
266
|
+
const values: (string | number | null)[] = [now]
|
|
267
|
+
|
|
268
|
+
if (data.name !== undefined) {
|
|
269
|
+
updates.push("name = ?")
|
|
270
|
+
values.push(data.name)
|
|
271
|
+
}
|
|
272
|
+
if (data.phase !== undefined) {
|
|
273
|
+
updates.push("phase = ?")
|
|
274
|
+
values.push(data.phase)
|
|
275
|
+
}
|
|
276
|
+
if (data.status !== undefined) {
|
|
277
|
+
updates.push("status = ?")
|
|
278
|
+
values.push(data.status)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const shouldClearAttention =
|
|
282
|
+
data.status !== undefined &&
|
|
283
|
+
data.status !== "needs_attention" &&
|
|
284
|
+
data.attentionReason === undefined
|
|
285
|
+
|
|
286
|
+
if (data.attentionReason !== undefined) {
|
|
287
|
+
updates.push("attention_reason = ?")
|
|
288
|
+
values.push(data.attentionReason)
|
|
289
|
+
} else if (shouldClearAttention) {
|
|
290
|
+
updates.push("attention_reason = NULL")
|
|
291
|
+
}
|
|
292
|
+
if (data.retryCount !== undefined) {
|
|
293
|
+
updates.push("retry_count = ?")
|
|
294
|
+
values.push(data.retryCount)
|
|
295
|
+
}
|
|
296
|
+
if (data.tokensUsed !== undefined) {
|
|
297
|
+
updates.push("tokens_used = ?")
|
|
298
|
+
values.push(data.tokensUsed)
|
|
299
|
+
}
|
|
300
|
+
if (data.pid !== undefined) {
|
|
301
|
+
updates.push("pid = ?")
|
|
302
|
+
values.push(data.pid)
|
|
303
|
+
}
|
|
304
|
+
if (data.aiSessionData !== undefined) {
|
|
305
|
+
updates.push("ai_session_data = ?")
|
|
306
|
+
values.push(serializeAISessionData(data.aiSessionData))
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
values.push(id)
|
|
310
|
+
|
|
311
|
+
db.query(`UPDATE sessions SET ${updates.join(", ")} WHERE id = ?`).run(...values)
|
|
312
|
+
|
|
313
|
+
const session = getSession(id)
|
|
314
|
+
if (!session) {
|
|
315
|
+
throw new Error(`Session not found: ${id}`)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return session
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Update session phase
|
|
323
|
+
*
|
|
324
|
+
* @param id - Session ID
|
|
325
|
+
* @param phase - New phase
|
|
326
|
+
*/
|
|
327
|
+
export function updateSessionPhase(id: string, phase: Phase): void {
|
|
328
|
+
updateSession(id, { phase })
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Update session status
|
|
333
|
+
*
|
|
334
|
+
* @param id - Session ID
|
|
335
|
+
* @param status - New status
|
|
336
|
+
* @param reason - Optional attention reason (for needs_attention status)
|
|
337
|
+
*/
|
|
338
|
+
export function updateSessionStatus(
|
|
339
|
+
id: string,
|
|
340
|
+
status: Status,
|
|
341
|
+
reason?: string
|
|
342
|
+
): void {
|
|
343
|
+
updateSession(id, {
|
|
344
|
+
status,
|
|
345
|
+
attentionReason: status === "needs_attention" ? reason ?? null : null,
|
|
346
|
+
})
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Increment retry count and return new value
|
|
351
|
+
*
|
|
352
|
+
* @param id - Session ID
|
|
353
|
+
* @returns New retry count
|
|
354
|
+
*/
|
|
355
|
+
export function incrementRetryCount(id: string): number {
|
|
356
|
+
const db = getDatabase()
|
|
357
|
+
const now = nowISO()
|
|
358
|
+
|
|
359
|
+
db.query(
|
|
360
|
+
"UPDATE sessions SET retry_count = retry_count + 1, updated_at = ? WHERE id = ?"
|
|
361
|
+
).run(now, id)
|
|
362
|
+
|
|
363
|
+
const session = getSession(id)
|
|
364
|
+
return session?.retryCount ?? 0
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Update tokens used
|
|
369
|
+
*
|
|
370
|
+
* @param id - Session ID
|
|
371
|
+
* @param tokens - New total tokens used
|
|
372
|
+
*/
|
|
373
|
+
export function updateTokensUsed(id: string, tokens: number): void {
|
|
374
|
+
const db = getDatabase()
|
|
375
|
+
const now = nowISO()
|
|
376
|
+
|
|
377
|
+
db.query("UPDATE sessions SET tokens_used = ?, updated_at = ? WHERE id = ?").run(
|
|
378
|
+
tokens,
|
|
379
|
+
now,
|
|
380
|
+
id
|
|
381
|
+
)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Set the process ID for a session
|
|
386
|
+
*
|
|
387
|
+
* @param id - Session ID
|
|
388
|
+
* @param pid - Process ID or null to clear
|
|
389
|
+
*/
|
|
390
|
+
export function setPid(id: string, pid: number | null): void {
|
|
391
|
+
const db = getDatabase()
|
|
392
|
+
const now = nowISO()
|
|
393
|
+
|
|
394
|
+
db.query("UPDATE sessions SET pid = ?, updated_at = ? WHERE id = ?").run(
|
|
395
|
+
pid,
|
|
396
|
+
now,
|
|
397
|
+
id
|
|
398
|
+
)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Set the AI session data for a session
|
|
403
|
+
*
|
|
404
|
+
* @param id - Session ID
|
|
405
|
+
* @param data - AI session data or null to clear
|
|
406
|
+
*/
|
|
407
|
+
export function setAISessionData(id: string, data: AISessionData | null): void {
|
|
408
|
+
const db = getDatabase()
|
|
409
|
+
const now = nowISO()
|
|
410
|
+
const serialized = serializeAISessionData(data)
|
|
411
|
+
|
|
412
|
+
db.query("UPDATE sessions SET ai_session_data = ?, updated_at = ? WHERE id = ?").run(
|
|
413
|
+
serialized,
|
|
414
|
+
now,
|
|
415
|
+
id
|
|
416
|
+
)
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Get the AI session data for a session
|
|
421
|
+
*
|
|
422
|
+
* @param id - Session ID
|
|
423
|
+
* @returns AISessionData or null
|
|
424
|
+
*/
|
|
425
|
+
export function getAISessionData(id: string): AISessionData | null {
|
|
426
|
+
const session = getSession(id)
|
|
427
|
+
return session?.aiSessionData ?? null
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// ============================================================================
|
|
431
|
+
// Delete Operations
|
|
432
|
+
// ============================================================================
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Delete a session
|
|
436
|
+
*
|
|
437
|
+
* Note: This cascades to delete associated tasks and output buffers.
|
|
438
|
+
*
|
|
439
|
+
* @param id - Session ID
|
|
440
|
+
*/
|
|
441
|
+
export function deleteSession(id: string): void {
|
|
442
|
+
const db = getDatabase()
|
|
443
|
+
db.query("DELETE FROM sessions WHERE id = ?").run(id)
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Delete all sessions
|
|
448
|
+
*
|
|
449
|
+
* Warning: This cascades to delete all tasks and buffers.
|
|
450
|
+
*/
|
|
451
|
+
export function deleteAllSessions(): void {
|
|
452
|
+
const db = getDatabase()
|
|
453
|
+
db.query("DELETE FROM sessions").run()
|
|
454
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database type definitions for OpenSWE
|
|
3
|
+
*
|
|
4
|
+
* Defines types for sessions, human tasks, project state, and output buffers
|
|
5
|
+
* stored in the SQLite database.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Enums / Type Literals
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
/** Session phases representing the workflow stages */
|
|
13
|
+
export type Phase =
|
|
14
|
+
| "pending"
|
|
15
|
+
| "planning"
|
|
16
|
+
| "working"
|
|
17
|
+
| "completed"
|
|
18
|
+
| "failed"
|
|
19
|
+
|
|
20
|
+
/** Session status for tracking current state */
|
|
21
|
+
export type Status =
|
|
22
|
+
| "queued"
|
|
23
|
+
| "active"
|
|
24
|
+
| "paused"
|
|
25
|
+
| "needs_attention"
|
|
26
|
+
| "completed"
|
|
27
|
+
| "failed"
|
|
28
|
+
|
|
29
|
+
/** Backend session reference for AI integrations */
|
|
30
|
+
export interface AISessionData {
|
|
31
|
+
backend: "opencode" | "claude"
|
|
32
|
+
sessionId?: string
|
|
33
|
+
[key: string]: unknown
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ============================================================================
|
|
37
|
+
// Session Types
|
|
38
|
+
// ============================================================================
|
|
39
|
+
|
|
40
|
+
/** A session represents work on a specific issue or task */
|
|
41
|
+
export interface Session {
|
|
42
|
+
/** Unique identifier (UUID) */
|
|
43
|
+
id: string
|
|
44
|
+
/** Human-readable session name */
|
|
45
|
+
name: string
|
|
46
|
+
/** GitHub issue number (if linked to an issue) */
|
|
47
|
+
issueNumber: number | null
|
|
48
|
+
/** GitHub issue title (cached) */
|
|
49
|
+
issueTitle: string | null
|
|
50
|
+
/** GitHub issue body/description (cached) */
|
|
51
|
+
issueBody: string | null
|
|
52
|
+
/** GitHub issue URL (for quick access) */
|
|
53
|
+
issueUrl: string | null
|
|
54
|
+
/** Absolute path to the git worktree for this session */
|
|
55
|
+
worktreePath: string
|
|
56
|
+
/** Git branch name for this session */
|
|
57
|
+
branchName: string
|
|
58
|
+
/** Current workflow phase */
|
|
59
|
+
phase: Phase
|
|
60
|
+
/** Current session status */
|
|
61
|
+
status: Status
|
|
62
|
+
/** Reason for needs_attention status */
|
|
63
|
+
attentionReason: string | null
|
|
64
|
+
/** Number of retry attempts */
|
|
65
|
+
retryCount: number
|
|
66
|
+
/** Total tokens used by AI */
|
|
67
|
+
tokensUsed: number
|
|
68
|
+
/** Process ID if session is running */
|
|
69
|
+
pid: number | null
|
|
70
|
+
/** AI backend session reference data */
|
|
71
|
+
aiSessionData: AISessionData | null
|
|
72
|
+
/** ISO timestamp when session was created */
|
|
73
|
+
createdAt: string
|
|
74
|
+
/** ISO timestamp when session was last updated */
|
|
75
|
+
updatedAt: string
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Input for creating a new session */
|
|
79
|
+
export interface CreateSessionInput {
|
|
80
|
+
name: string
|
|
81
|
+
issueNumber?: number
|
|
82
|
+
issueTitle?: string
|
|
83
|
+
issueBody?: string
|
|
84
|
+
issueUrl?: string
|
|
85
|
+
worktreePath: string
|
|
86
|
+
branchName: string
|
|
87
|
+
aiSessionData?: AISessionData | null
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Input for updating an existing session */
|
|
91
|
+
export interface UpdateSessionInput {
|
|
92
|
+
name?: string
|
|
93
|
+
phase?: Phase
|
|
94
|
+
status?: Status
|
|
95
|
+
attentionReason?: string | null
|
|
96
|
+
retryCount?: number
|
|
97
|
+
tokensUsed?: number
|
|
98
|
+
pid?: number | null
|
|
99
|
+
aiSessionData?: AISessionData | null
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ============================================================================
|
|
103
|
+
// Human Task Types - REMOVED
|
|
104
|
+
// ============================================================================
|
|
105
|
+
|
|
106
|
+
// ============================================================================
|
|
107
|
+
// Project State Types
|
|
108
|
+
// ============================================================================
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Project state stored in the database
|
|
112
|
+
*
|
|
113
|
+
* Note: This is distinct from ProjectConfig in workspace/project.ts.
|
|
114
|
+
* ProjectConfig is stored in .openswe/project.json and is the source of truth.
|
|
115
|
+
* ProjectState in the database mirrors essential fields for querying.
|
|
116
|
+
*/
|
|
117
|
+
export interface ProjectState {
|
|
118
|
+
/** Always 1 (singleton) */
|
|
119
|
+
id: 1
|
|
120
|
+
/** Full repository name in owner/repo format */
|
|
121
|
+
repoFullName: string
|
|
122
|
+
/** Git remote URL */
|
|
123
|
+
repoUrl: string
|
|
124
|
+
/** ISO timestamp when project was created */
|
|
125
|
+
createdAt: string
|
|
126
|
+
/** ISO timestamp when project was last opened */
|
|
127
|
+
lastOpenedAt: string
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Input for creating project state */
|
|
131
|
+
export interface CreateProjectInput {
|
|
132
|
+
repoFullName: string
|
|
133
|
+
repoUrl: string
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ============================================================================
|
|
137
|
+
// Output Buffer Types
|
|
138
|
+
// ============================================================================
|
|
139
|
+
|
|
140
|
+
/** Circular buffer for session output */
|
|
141
|
+
export interface OutputBuffer {
|
|
142
|
+
/** Session ID this buffer belongs to */
|
|
143
|
+
sessionId: string
|
|
144
|
+
/** Array of output lines (stored as JSON) */
|
|
145
|
+
lines: string[]
|
|
146
|
+
/** ISO timestamp when buffer was last updated */
|
|
147
|
+
lastUpdated: string
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ============================================================================
|
|
151
|
+
// Type Guards
|
|
152
|
+
// ============================================================================
|
|
153
|
+
|
|
154
|
+
const VALID_PHASES: readonly Phase[] = [
|
|
155
|
+
"pending",
|
|
156
|
+
"planning",
|
|
157
|
+
"working",
|
|
158
|
+
"completed",
|
|
159
|
+
"failed",
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
const VALID_STATUSES: readonly Status[] = [
|
|
163
|
+
"queued",
|
|
164
|
+
"active",
|
|
165
|
+
"paused",
|
|
166
|
+
"needs_attention",
|
|
167
|
+
"completed",
|
|
168
|
+
"failed",
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
const VALID_AI_BACKENDS = ["opencode", "claude"] as const
|
|
172
|
+
|
|
173
|
+
/** Check if a value is a valid Phase */
|
|
174
|
+
export function isValidPhase(val: unknown): val is Phase {
|
|
175
|
+
return typeof val === "string" && VALID_PHASES.includes(val as Phase)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Check if a value is a valid Status */
|
|
179
|
+
export function isValidStatus(val: unknown): val is Status {
|
|
180
|
+
return typeof val === "string" && VALID_STATUSES.includes(val as Status)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Check if a value is valid AISessionData */
|
|
184
|
+
export function isValidAISessionData(val: unknown): val is AISessionData {
|
|
185
|
+
if (typeof val !== "object" || val === null) return false
|
|
186
|
+
|
|
187
|
+
const backend = (val as { backend?: unknown }).backend
|
|
188
|
+
if (backend !== "opencode" && backend !== "claude") return false
|
|
189
|
+
|
|
190
|
+
const sessionId = (val as { sessionId?: unknown }).sessionId
|
|
191
|
+
if (sessionId !== undefined && typeof sessionId !== "string") return false
|
|
192
|
+
|
|
193
|
+
return true
|
|
194
|
+
}
|