@shawnstack/quickforge 1.7.7 → 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.
Files changed (83) hide show
  1. package/dist/assets/AgentProfilesPage-BSM5w3bn.js +1 -0
  2. package/dist/assets/ChatPanelHost-cd80S4EX.js +48 -0
  3. package/dist/assets/CloudAccountSettingsPage-BJMrfgy9.js +1 -0
  4. package/dist/assets/{PluginsPage-CPE5tbbf.js → PluginsPage-mJkTmVVV.js} +1 -1
  5. package/dist/assets/ScheduledTasksPage-DJj_NLm9.js +2 -0
  6. package/dist/assets/{SettingsWorkspacePage-BE2T4d3P.js → SettingsWorkspacePage-7lQ0Wl63.js} +389 -389
  7. package/dist/assets/ShareLinksSettingsPage-DjFq_Lnb.js +1 -0
  8. package/dist/assets/SharedConversationPage-BbuNpcpP.js +1 -0
  9. package/dist/assets/TerminalDock-CczcCJJE.js +2 -0
  10. package/dist/assets/WorkspaceInspector-0i6x-diB.js +36 -0
  11. package/dist/assets/{icons-2D18YbSI.js → icons-DM_qJ7VJ.js} +1 -1
  12. package/dist/assets/{index-BiCTgUlY.css → index-D-J_8Smf.css} +1 -1
  13. package/dist/assets/index-DFASc15l.js +66 -0
  14. package/dist/assets/local-tools-Dn1Y9aPe.js +270 -0
  15. package/dist/assets/{model-display-label-C6-0eDxF.js → logger-8REuLPGK.js} +1 -1
  16. package/dist/assets/{mcp-servers-dialog-GY7sdfIr.js → mcp-servers-dialog-BG4b67ea.js} +2 -2
  17. package/dist/assets/{model-reference-o41fJ6jQ.js → model-display-label-CURwH0xM.js} +1 -1
  18. package/dist/assets/{monaco-DvXErxp2.js → monaco-bDr860xx.js} +1 -1
  19. package/dist/assets/{react-vendor-3UaA-C9W.js → react-vendor-BpWW_Nvp.js} +1 -1
  20. package/dist/assets/{skills-dialog-C1fJ1Jl7.js → skills-dialog-DIzZvQqW.js} +3 -3
  21. package/dist/index.html +8 -8
  22. package/package.json +1 -1
  23. package/server/acp/server.mjs +17 -1
  24. package/server/agent-manager.mjs +251 -31
  25. package/server/ai-http-logger.mjs +162 -19
  26. package/server/ai-provider-options.mjs +4 -1
  27. package/server/auto-archive.mjs +42 -29
  28. package/server/cloud/auto-approval.mjs +155 -0
  29. package/server/cloud/client.mjs +5 -0
  30. package/server/cloud/qf-agent-process.mjs +163 -3
  31. package/server/cloud/service-config.mjs +1 -1
  32. package/server/index.mjs +69 -13
  33. package/server/lan-access-backup.mjs +256 -0
  34. package/server/lan-access-cutover.mjs +306 -0
  35. package/server/lan-access-json-file.mjs +75 -0
  36. package/server/lan-access-service.mjs +155 -0
  37. package/server/lan-access-store.mjs +110 -9
  38. package/server/maintenance/downgrade-lan-access-v1.mjs +101 -0
  39. package/server/maintenance/downgrade-session-state-v1.mjs +130 -0
  40. package/server/maintenance/downgrade-share-v1.mjs +101 -0
  41. package/server/maintenance/export-lan-access-v1.mjs +80 -0
  42. package/server/maintenance/export-scheduled-runs-v1.mjs +45 -0
  43. package/server/maintenance/export-session-state-v1.mjs +67 -0
  44. package/server/maintenance/export-share-v1.mjs +77 -0
  45. package/server/routes/agent.mjs +37 -4
  46. package/server/routes/backup.mjs +221 -39
  47. package/server/routes/cloud.mjs +28 -1
  48. package/server/routes/scheduled-tasks.mjs +347 -134
  49. package/server/routes/shares.mjs +4 -2
  50. package/server/routes/storage.mjs +138 -8
  51. package/server/routes/workspace.mjs +265 -4
  52. package/server/scheduled-runs-backup.mjs +235 -0
  53. package/server/scheduled-runs-cutover.mjs +497 -0
  54. package/server/scheduled-task-runs-service.mjs +279 -0
  55. package/server/session-index-service.mjs +495 -0
  56. package/server/session-state-backup.mjs +277 -0
  57. package/server/session-state-cutover.mjs +370 -0
  58. package/server/session-state-service.mjs +709 -0
  59. package/server/share-backup.mjs +255 -0
  60. package/server/share-cutover.mjs +296 -0
  61. package/server/share-json-file.mjs +60 -0
  62. package/server/share-service.mjs +163 -0
  63. package/server/share-store.mjs +196 -19
  64. package/server/skills.mjs +0 -1
  65. package/server/sqlite/database.mjs +233 -0
  66. package/server/sqlite/lan-access-repository.mjs +652 -0
  67. package/server/sqlite/migrations.mjs +577 -0
  68. package/server/sqlite/scheduled-task-runs-repository.mjs +464 -0
  69. package/server/sqlite/session-index-repository.mjs +297 -0
  70. package/server/sqlite/session-state-repository.mjs +786 -0
  71. package/server/sqlite/share-repository.mjs +854 -0
  72. package/server/storage.mjs +317 -1
  73. package/dist/assets/AgentProfilesPage-BfA_sQ3A.js +0 -1
  74. package/dist/assets/ChatPanelHost-Dul1ahKj.js +0 -307
  75. package/dist/assets/CloudAccountSettingsPage-qZh0lTUN.js +0 -1
  76. package/dist/assets/ScheduledTasksPage-PTxL_PPg.js +0 -2
  77. package/dist/assets/ShareLinksSettingsPage-Bl8fP62X.js +0 -1
  78. package/dist/assets/SharedConversationPage-BK9XJnEe.js +0 -1
  79. package/dist/assets/TerminalDock-CL7HU9DI.js +0 -2
  80. package/dist/assets/WorkspaceInspector-MNBLIKMn.js +0 -36
  81. package/dist/assets/index-BTPzyq7d.js +0 -65
  82. package/dist/assets/mermaid-renderer-jEbqFaTe.js +0 -2
  83. /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
+ }