@shawnstack/quickforge 1.7.6 → 1.7.8
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/dist/assets/AgentProfilesPage-BSM5w3bn.js +1 -0
- package/dist/assets/ChatPanelHost-cd80S4EX.js +48 -0
- package/dist/assets/CloudAccountSettingsPage-BJMrfgy9.js +1 -0
- package/dist/assets/{PluginsPage-BDKYfLna.js → PluginsPage-mJkTmVVV.js} +1 -1
- package/dist/assets/ScheduledTasksPage-DJj_NLm9.js +2 -0
- package/dist/assets/{SettingsWorkspacePage-DSy66Tuw.js → SettingsWorkspacePage-7lQ0Wl63.js} +391 -391
- package/dist/assets/ShareLinksSettingsPage-DjFq_Lnb.js +1 -0
- package/dist/assets/SharedConversationPage-BbuNpcpP.js +1 -0
- package/dist/assets/TerminalDock-CczcCJJE.js +2 -0
- package/dist/assets/WorkspaceInspector-0i6x-diB.js +36 -0
- package/dist/assets/{icons-2D18YbSI.js → icons-DM_qJ7VJ.js} +1 -1
- package/dist/assets/index-D-J_8Smf.css +3 -0
- package/dist/assets/index-DFASc15l.js +66 -0
- package/dist/assets/local-tools-Dn1Y9aPe.js +270 -0
- package/dist/assets/{model-display-label-C6-0eDxF.js → logger-8REuLPGK.js} +1 -1
- package/dist/assets/{mcp-servers-dialog-BEV4_TXC.js → mcp-servers-dialog-BG4b67ea.js} +2 -2
- package/dist/assets/{model-reference-o41fJ6jQ.js → model-display-label-CURwH0xM.js} +1 -1
- package/dist/assets/{monaco-DvXErxp2.js → monaco-bDr860xx.js} +1 -1
- package/dist/assets/{react-vendor-3UaA-C9W.js → react-vendor-BpWW_Nvp.js} +1 -1
- package/dist/assets/{skills-dialog-BTJ7mzk_.js → skills-dialog-DIzZvQqW.js} +3 -3
- package/dist/index.html +8 -8
- package/package.json +1 -1
- package/server/acp/server.mjs +17 -1
- package/server/agent-manager.mjs +251 -31
- package/server/ai-http-logger.mjs +162 -19
- package/server/ai-provider-options.mjs +4 -1
- package/server/auto-archive.mjs +42 -29
- package/server/auto-compaction.mjs +43 -1
- package/server/cloud/auto-approval.mjs +155 -0
- package/server/cloud/client.mjs +5 -0
- package/server/cloud/config.mjs +8 -16
- package/server/cloud/qf-agent-process.mjs +163 -3
- package/server/cloud/service-config.mjs +1 -1
- package/server/context-usage.mjs +11 -4
- package/server/index.mjs +69 -13
- package/server/lan-access-backup.mjs +256 -0
- package/server/lan-access-cutover.mjs +306 -0
- package/server/lan-access-json-file.mjs +75 -0
- package/server/lan-access-service.mjs +155 -0
- package/server/lan-access-store.mjs +110 -9
- package/server/maintenance/downgrade-lan-access-v1.mjs +101 -0
- package/server/maintenance/downgrade-session-state-v1.mjs +130 -0
- package/server/maintenance/downgrade-share-v1.mjs +101 -0
- package/server/maintenance/export-lan-access-v1.mjs +80 -0
- package/server/maintenance/export-scheduled-runs-v1.mjs +45 -0
- package/server/maintenance/export-session-state-v1.mjs +67 -0
- package/server/maintenance/export-share-v1.mjs +77 -0
- package/server/routes/agent.mjs +37 -4
- package/server/routes/backup.mjs +221 -39
- package/server/routes/cloud.mjs +28 -1
- package/server/routes/scheduled-tasks.mjs +347 -134
- package/server/routes/shares.mjs +4 -2
- package/server/routes/storage.mjs +138 -8
- package/server/routes/workspace.mjs +265 -4
- package/server/scheduled-runs-backup.mjs +235 -0
- package/server/scheduled-runs-cutover.mjs +497 -0
- package/server/scheduled-task-runs-service.mjs +279 -0
- package/server/session-index-service.mjs +495 -0
- package/server/session-state-backup.mjs +277 -0
- package/server/session-state-cutover.mjs +370 -0
- package/server/session-state-service.mjs +709 -0
- package/server/share-backup.mjs +255 -0
- package/server/share-cutover.mjs +296 -0
- package/server/share-json-file.mjs +60 -0
- package/server/share-service.mjs +163 -0
- package/server/share-store.mjs +196 -19
- package/server/skills.mjs +0 -1
- package/server/sqlite/database.mjs +233 -0
- package/server/sqlite/lan-access-repository.mjs +652 -0
- package/server/sqlite/migrations.mjs +577 -0
- package/server/sqlite/scheduled-task-runs-repository.mjs +464 -0
- package/server/sqlite/session-index-repository.mjs +297 -0
- package/server/sqlite/session-state-repository.mjs +786 -0
- package/server/sqlite/share-repository.mjs +854 -0
- package/server/storage.mjs +317 -1
- package/dist/assets/AgentProfilesPage-Dl5Peqov.js +0 -1
- package/dist/assets/ChatPanelHost-D3987TUJ.js +0 -307
- package/dist/assets/CloudAccountSettingsPage-BG5Nn3bq.js +0 -1
- package/dist/assets/ScheduledTasksPage--GAki3lB.js +0 -2
- package/dist/assets/ShareLinksSettingsPage-BQQMsvJo.js +0 -1
- package/dist/assets/SharedConversationPage-B9BUVCri.js +0 -1
- package/dist/assets/TerminalDock-CT1nYo7P.js +0 -2
- package/dist/assets/WorkspaceInspector-BnfDLf9a.js +0 -36
- package/dist/assets/index-1ei6nhs-.css +0 -3
- package/dist/assets/index-BivLuZvX.js +0 -65
- package/dist/assets/mermaid-renderer-jEbqFaTe.js +0 -2
- /package/dist/assets/{plugin-api-Uu2xAB4d.js → plugin-api-BrdQeM4B.js} +0 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { mkdir } from 'node:fs/promises'
|
|
2
|
+
import os from 'node:os'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { DatabaseSync } from 'node:sqlite'
|
|
5
|
+
import { logger } from '../utils/logger.mjs'
|
|
6
|
+
import { applySqliteMigrations, inspectSqliteMigrationState, SQLITE_MIGRATIONS } from './migrations.mjs'
|
|
7
|
+
|
|
8
|
+
export const SQLITE_BUSY_TIMEOUT_MS = 5_000
|
|
9
|
+
export const SQLITE_JOURNAL_MODE = 'wal'
|
|
10
|
+
export const SQLITE_SYNCHRONOUS = 1
|
|
11
|
+
|
|
12
|
+
const sqliteLogger = logger.child({ component: 'sqlite' })
|
|
13
|
+
const TRANSACTION_MODES = new Set(['deferred', 'immediate', 'exclusive'])
|
|
14
|
+
let initializationPromise = null
|
|
15
|
+
let openState = null
|
|
16
|
+
let savepointCounter = 0
|
|
17
|
+
|
|
18
|
+
function defaultDataDir() {
|
|
19
|
+
return process.env.QUICKFORGE_DATA_DIR
|
|
20
|
+
? path.resolve(process.env.QUICKFORGE_DATA_DIR)
|
|
21
|
+
: path.join(os.homedir(), '.quickforge')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function resolveSqliteDatabasePath({ dataDir, databasePath } = {}) {
|
|
25
|
+
if (databasePath) return path.resolve(databasePath)
|
|
26
|
+
return path.join(path.resolve(dataDir || defaultDataDir()), 'storage', 'quickforge.sqlite3')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizeMode(mode = 'immediate') {
|
|
30
|
+
const normalized = String(mode).toLowerCase()
|
|
31
|
+
if (!TRANSACTION_MODES.has(normalized)) {
|
|
32
|
+
throw new Error(`Unsupported SQLite transaction mode: ${mode}`)
|
|
33
|
+
}
|
|
34
|
+
return normalized
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function assertSynchronousResult(result) {
|
|
38
|
+
if (result && (typeof result === 'object' || typeof result === 'function') && typeof result.then === 'function') {
|
|
39
|
+
void Promise.resolve(result).catch(() => {})
|
|
40
|
+
throw new Error('SQLite transaction callback must be synchronous; async/thenable results are not supported')
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function pragmaValue(database, name, field = name) {
|
|
45
|
+
return database.prepare(`PRAGMA ${name}`).get()[field]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function readPragmas(database) {
|
|
49
|
+
return {
|
|
50
|
+
busyTimeout: Number(pragmaValue(database, 'busy_timeout', 'timeout')),
|
|
51
|
+
foreignKeys: Number(pragmaValue(database, 'foreign_keys', 'foreign_keys')) === 1,
|
|
52
|
+
journalMode: String(pragmaValue(database, 'journal_mode', 'journal_mode')).toLowerCase(),
|
|
53
|
+
synchronous: Number(pragmaValue(database, 'synchronous', 'synchronous')),
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function verifyPragmas(database) {
|
|
58
|
+
const summary = readPragmas(database)
|
|
59
|
+
if (summary.busyTimeout !== SQLITE_BUSY_TIMEOUT_MS) throw new Error(`SQLite busy_timeout verification failed: ${summary.busyTimeout}`)
|
|
60
|
+
if (!summary.foreignKeys) throw new Error('SQLite foreign_keys verification failed')
|
|
61
|
+
if (summary.journalMode !== SQLITE_JOURNAL_MODE) throw new Error(`SQLite journal_mode verification failed: ${summary.journalMode}`)
|
|
62
|
+
if (summary.synchronous !== SQLITE_SYNCHRONOUS) throw new Error(`SQLite synchronous verification failed: ${summary.synchronous}`)
|
|
63
|
+
return summary
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function configurePragmas(database) {
|
|
67
|
+
database.exec(`PRAGMA busy_timeout = ${SQLITE_BUSY_TIMEOUT_MS}`)
|
|
68
|
+
database.exec('PRAGMA foreign_keys = ON')
|
|
69
|
+
database.exec('PRAGMA journal_mode = WAL')
|
|
70
|
+
database.exec('PRAGMA synchronous = NORMAL')
|
|
71
|
+
return verifyPragmas(database)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function publicHealth(state, { quickCheck = false } = {}) {
|
|
75
|
+
const alive = Number(state.database.prepare('SELECT 1 AS ok').get().ok) === 1
|
|
76
|
+
const pragmas = verifyPragmas(state.database)
|
|
77
|
+
const migration = inspectSqliteMigrationState(state.database, { migrations: state.migrations })
|
|
78
|
+
let quickCheckResult = null
|
|
79
|
+
if (quickCheck) {
|
|
80
|
+
quickCheckResult = state.database.prepare('PRAGMA quick_check').all().map((row) => row.quick_check)
|
|
81
|
+
if (quickCheckResult.length !== 1 || quickCheckResult[0] !== 'ok') {
|
|
82
|
+
throw new Error(`SQLite quick_check failed: ${quickCheckResult.join(', ')}`)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
ok: alive && migration.consistent,
|
|
87
|
+
sqliteVersion: String(state.database.prepare('SELECT sqlite_version() AS version').get().version),
|
|
88
|
+
schemaVersion: migration.userVersion,
|
|
89
|
+
latestSchemaVersion: migration.latestVersion,
|
|
90
|
+
migrationCount: migration.applied.length,
|
|
91
|
+
journalMode: pragmas.journalMode,
|
|
92
|
+
busyTimeout: pragmas.busyTimeout,
|
|
93
|
+
foreignKeys: pragmas.foreignKeys,
|
|
94
|
+
synchronous: 'normal',
|
|
95
|
+
...(quickCheck ? { quickCheck: quickCheckResult[0] } : {}),
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function createHandle(state) {
|
|
100
|
+
return Object.freeze({
|
|
101
|
+
exec(sql) {
|
|
102
|
+
return state.database.exec(sql)
|
|
103
|
+
},
|
|
104
|
+
prepare(sql) {
|
|
105
|
+
return state.database.prepare(sql)
|
|
106
|
+
},
|
|
107
|
+
transaction(callback, { mode = 'immediate' } = {}) {
|
|
108
|
+
if (typeof callback !== 'function') throw new TypeError('SQLite transaction callback must be a function')
|
|
109
|
+
const normalizedMode = normalizeMode(mode)
|
|
110
|
+
const nested = state.transactionDepth > 0
|
|
111
|
+
const savepoint = nested ? `quickforge_sp_${++savepointCounter}` : null
|
|
112
|
+
if (nested) state.database.exec(`SAVEPOINT ${savepoint}`)
|
|
113
|
+
else state.database.exec(`BEGIN ${normalizedMode.toUpperCase()}`)
|
|
114
|
+
state.transactionDepth += 1
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const result = callback(state.handle)
|
|
118
|
+
assertSynchronousResult(result)
|
|
119
|
+
if (nested) state.database.exec(`RELEASE SAVEPOINT ${savepoint}`)
|
|
120
|
+
else state.database.exec('COMMIT')
|
|
121
|
+
return result
|
|
122
|
+
} catch (error) {
|
|
123
|
+
try {
|
|
124
|
+
if (nested) {
|
|
125
|
+
state.database.exec(`ROLLBACK TO SAVEPOINT ${savepoint}`)
|
|
126
|
+
state.database.exec(`RELEASE SAVEPOINT ${savepoint}`)
|
|
127
|
+
} else {
|
|
128
|
+
state.database.exec('ROLLBACK')
|
|
129
|
+
}
|
|
130
|
+
} catch (rollbackError) {
|
|
131
|
+
throw new Error(`${error instanceof Error ? error.message : String(error)}; SQLite rollback failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`, { cause: rollbackError })
|
|
132
|
+
}
|
|
133
|
+
throw error
|
|
134
|
+
} finally {
|
|
135
|
+
state.transactionDepth -= 1
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
health(options) {
|
|
139
|
+
return publicHealth(state, options)
|
|
140
|
+
},
|
|
141
|
+
get summary() {
|
|
142
|
+
return publicHealth(state)
|
|
143
|
+
},
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function initializeSqliteStorage({ dataDir, databasePath, migrations = SQLITE_MIGRATIONS } = {}) {
|
|
148
|
+
const resolvedPath = resolveSqliteDatabasePath({ dataDir, databasePath })
|
|
149
|
+
if (openState) {
|
|
150
|
+
if (openState.databasePath !== resolvedPath) {
|
|
151
|
+
return Promise.reject(new Error('SQLite storage is already initialized with a different database path'))
|
|
152
|
+
}
|
|
153
|
+
return Promise.resolve(openState.handle)
|
|
154
|
+
}
|
|
155
|
+
if (initializationPromise) {
|
|
156
|
+
if (initializationPromise.databasePath !== resolvedPath) {
|
|
157
|
+
return Promise.reject(new Error('SQLite storage initialization is already in progress for a different database path'))
|
|
158
|
+
}
|
|
159
|
+
return initializationPromise.promise
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const pending = (async () => {
|
|
163
|
+
let database = null
|
|
164
|
+
try {
|
|
165
|
+
await mkdir(path.dirname(resolvedPath), { recursive: true })
|
|
166
|
+
database = new DatabaseSync(resolvedPath)
|
|
167
|
+
const pragmas = configurePragmas(database)
|
|
168
|
+
const migration = applySqliteMigrations(database, { migrations })
|
|
169
|
+
const sqliteVersion = String(database.prepare('SELECT sqlite_version() AS version').get().version)
|
|
170
|
+
const state = {
|
|
171
|
+
database,
|
|
172
|
+
databasePath: resolvedPath,
|
|
173
|
+
migrations,
|
|
174
|
+
transactionDepth: 0,
|
|
175
|
+
handle: null,
|
|
176
|
+
}
|
|
177
|
+
state.handle = createHandle(state)
|
|
178
|
+
openState = state
|
|
179
|
+
sqliteLogger.info('SQLite storage initialized', {
|
|
180
|
+
sqliteVersion,
|
|
181
|
+
schemaVersion: migration.userVersion,
|
|
182
|
+
migrationCount: migration.applied.length,
|
|
183
|
+
journalMode: pragmas.journalMode,
|
|
184
|
+
busyTimeout: pragmas.busyTimeout,
|
|
185
|
+
})
|
|
186
|
+
return state.handle
|
|
187
|
+
} catch (error) {
|
|
188
|
+
if (database) {
|
|
189
|
+
try { database.close() } catch { /* Preserve initialization failure. */ }
|
|
190
|
+
}
|
|
191
|
+
sqliteLogger.error('SQLite storage initialization failed', {
|
|
192
|
+
error: error instanceof Error ? error.message : String(error),
|
|
193
|
+
})
|
|
194
|
+
throw error
|
|
195
|
+
}
|
|
196
|
+
})()
|
|
197
|
+
|
|
198
|
+
const promise = pending.finally(() => {
|
|
199
|
+
if (initializationPromise?.promise === promise) initializationPromise = null
|
|
200
|
+
})
|
|
201
|
+
initializationPromise = { databasePath: resolvedPath, promise }
|
|
202
|
+
return promise
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function getSqliteStorage() {
|
|
206
|
+
if (!openState) throw new Error('SQLite storage is not initialized')
|
|
207
|
+
return openState.handle
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function getSqliteStorageSummary(options) {
|
|
211
|
+
return getSqliteStorage().health(options)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export async function closeSqliteStorage() {
|
|
215
|
+
if (initializationPromise) {
|
|
216
|
+
try { await initializationPromise.promise } catch { /* Failed initialization has no open handle. */ }
|
|
217
|
+
}
|
|
218
|
+
if (!openState) return
|
|
219
|
+
const state = openState
|
|
220
|
+
openState = null
|
|
221
|
+
try {
|
|
222
|
+
state.database.close()
|
|
223
|
+
sqliteLogger.info('SQLite storage closed', {
|
|
224
|
+
sqliteVersion: process.versions.sqlite,
|
|
225
|
+
schemaVersion: state.migrations.length,
|
|
226
|
+
})
|
|
227
|
+
} catch (error) {
|
|
228
|
+
sqliteLogger.error('SQLite storage close failed', {
|
|
229
|
+
error: error instanceof Error ? error.message : String(error),
|
|
230
|
+
})
|
|
231
|
+
throw error
|
|
232
|
+
}
|
|
233
|
+
}
|