@shawnstack/quickforge 1.7.7 → 1.7.9

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 (85) hide show
  1. package/bin/quickforge.mjs +10 -1
  2. package/dist/assets/AgentProfilesPage-BSM5w3bn.js +1 -0
  3. package/dist/assets/ChatPanelHost-cd80S4EX.js +48 -0
  4. package/dist/assets/CloudAccountSettingsPage-BJMrfgy9.js +1 -0
  5. package/dist/assets/{PluginsPage-CPE5tbbf.js → PluginsPage-mJkTmVVV.js} +1 -1
  6. package/dist/assets/ScheduledTasksPage-DJj_NLm9.js +2 -0
  7. package/dist/assets/{SettingsWorkspacePage-BE2T4d3P.js → SettingsWorkspacePage-7lQ0Wl63.js} +389 -389
  8. package/dist/assets/ShareLinksSettingsPage-DjFq_Lnb.js +1 -0
  9. package/dist/assets/SharedConversationPage-BbuNpcpP.js +1 -0
  10. package/dist/assets/TerminalDock-CczcCJJE.js +2 -0
  11. package/dist/assets/WorkspaceInspector-0i6x-diB.js +36 -0
  12. package/dist/assets/{icons-2D18YbSI.js → icons-DM_qJ7VJ.js} +1 -1
  13. package/dist/assets/{index-BiCTgUlY.css → index-D-J_8Smf.css} +1 -1
  14. package/dist/assets/index-DFASc15l.js +66 -0
  15. package/dist/assets/local-tools-Dn1Y9aPe.js +270 -0
  16. package/dist/assets/{model-display-label-C6-0eDxF.js → logger-8REuLPGK.js} +1 -1
  17. package/dist/assets/{mcp-servers-dialog-GY7sdfIr.js → mcp-servers-dialog-BG4b67ea.js} +2 -2
  18. package/dist/assets/{model-reference-o41fJ6jQ.js → model-display-label-CURwH0xM.js} +1 -1
  19. package/dist/assets/{monaco-DvXErxp2.js → monaco-bDr860xx.js} +1 -1
  20. package/dist/assets/{react-vendor-3UaA-C9W.js → react-vendor-BpWW_Nvp.js} +1 -1
  21. package/dist/assets/{skills-dialog-C1fJ1Jl7.js → skills-dialog-DIzZvQqW.js} +3 -3
  22. package/dist/index.html +8 -8
  23. package/package.json +1 -1
  24. package/server/acp/server.mjs +17 -1
  25. package/server/agent-manager.mjs +251 -31
  26. package/server/ai-http-logger.mjs +162 -19
  27. package/server/ai-provider-options.mjs +4 -1
  28. package/server/auto-archive.mjs +42 -29
  29. package/server/cloud/auto-approval.mjs +155 -0
  30. package/server/cloud/client.mjs +5 -0
  31. package/server/cloud/qf-agent-process.mjs +163 -3
  32. package/server/cloud/service-config.mjs +1 -1
  33. package/server/index.mjs +69 -13
  34. package/server/lan-access-backup.mjs +256 -0
  35. package/server/lan-access-cutover.mjs +306 -0
  36. package/server/lan-access-json-file.mjs +75 -0
  37. package/server/lan-access-service.mjs +155 -0
  38. package/server/lan-access-store.mjs +110 -9
  39. package/server/maintenance/downgrade-lan-access-v1.mjs +101 -0
  40. package/server/maintenance/downgrade-session-state-v1.mjs +130 -0
  41. package/server/maintenance/downgrade-share-v1.mjs +101 -0
  42. package/server/maintenance/export-lan-access-v1.mjs +80 -0
  43. package/server/maintenance/export-scheduled-runs-v1.mjs +45 -0
  44. package/server/maintenance/export-session-state-v1.mjs +67 -0
  45. package/server/maintenance/export-share-v1.mjs +77 -0
  46. package/server/public-api.mjs +21 -2
  47. package/server/routes/agent.mjs +37 -4
  48. package/server/routes/backup.mjs +221 -39
  49. package/server/routes/cloud.mjs +28 -1
  50. package/server/routes/scheduled-tasks.mjs +347 -134
  51. package/server/routes/shares.mjs +4 -2
  52. package/server/routes/storage.mjs +138 -8
  53. package/server/routes/workspace.mjs +265 -4
  54. package/server/scheduled-runs-backup.mjs +235 -0
  55. package/server/scheduled-runs-cutover.mjs +497 -0
  56. package/server/scheduled-task-runs-service.mjs +279 -0
  57. package/server/session-index-service.mjs +495 -0
  58. package/server/session-state-backup.mjs +277 -0
  59. package/server/session-state-cutover.mjs +370 -0
  60. package/server/session-state-service.mjs +709 -0
  61. package/server/share-backup.mjs +255 -0
  62. package/server/share-cutover.mjs +296 -0
  63. package/server/share-json-file.mjs +60 -0
  64. package/server/share-service.mjs +163 -0
  65. package/server/share-store.mjs +196 -19
  66. package/server/skills.mjs +0 -1
  67. package/server/sqlite/database.mjs +233 -0
  68. package/server/sqlite/lan-access-repository.mjs +652 -0
  69. package/server/sqlite/migrations.mjs +577 -0
  70. package/server/sqlite/scheduled-task-runs-repository.mjs +464 -0
  71. package/server/sqlite/session-index-repository.mjs +297 -0
  72. package/server/sqlite/session-state-repository.mjs +786 -0
  73. package/server/sqlite/share-repository.mjs +854 -0
  74. package/server/storage.mjs +317 -1
  75. package/dist/assets/AgentProfilesPage-BfA_sQ3A.js +0 -1
  76. package/dist/assets/ChatPanelHost-Dul1ahKj.js +0 -307
  77. package/dist/assets/CloudAccountSettingsPage-qZh0lTUN.js +0 -1
  78. package/dist/assets/ScheduledTasksPage-PTxL_PPg.js +0 -2
  79. package/dist/assets/ShareLinksSettingsPage-Bl8fP62X.js +0 -1
  80. package/dist/assets/SharedConversationPage-BK9XJnEe.js +0 -1
  81. package/dist/assets/TerminalDock-CL7HU9DI.js +0 -2
  82. package/dist/assets/WorkspaceInspector-MNBLIKMn.js +0 -36
  83. package/dist/assets/index-BTPzyq7d.js +0 -65
  84. package/dist/assets/mermaid-renderer-jEbqFaTe.js +0 -2
  85. /package/dist/assets/{plugin-api-Uu2xAB4d.js → plugin-api-BrdQeM4B.js} +0 -0
@@ -0,0 +1,235 @@
1
+ import { randomUUID } from 'node:crypto'
2
+ import { promises as fs } from 'node:fs'
3
+ import path from 'node:path'
4
+ import { getSqliteStorage } from './sqlite/database.mjs'
5
+ import { createScheduledTaskRunsRepository } from './sqlite/scheduled-task-runs-repository.mjs'
6
+ import {
7
+ currentScheduledRunsMaintenanceContext,
8
+ getScheduledRunsPhase,
9
+ isScheduledRunsAuthoritative,
10
+ runScheduledRunsMaintenance,
11
+ splitScheduledTasksRuns,
12
+ verifyScheduledRunsRepository,
13
+ } from './scheduled-runs-cutover.mjs'
14
+ import { readStore, storageDir, writeStore } from './storage.mjs'
15
+
16
+ const RESTORE_PLAN_FILE = path.join(storageDir, 'scheduled-runs-restore-plan.json')
17
+ const ROLL_FORWARD_STATUSES = new Set(['prepared', 'applying', 'target_applied'])
18
+ const ROLL_BACK_STATUSES = new Set(['compensating', 'compensation_failed'])
19
+
20
+ function repositoryRequired() {
21
+ try {
22
+ return createScheduledTaskRunsRepository(getSqliteStorage())
23
+ } catch (error) {
24
+ throw new Error('SQLite storage is required for authoritative scheduled runs', { cause: error })
25
+ }
26
+ }
27
+
28
+ function groupRuns(repository, taskIds) {
29
+ const grouped = new Map(taskIds.map((taskId) => [taskId, []]))
30
+ if (taskIds.length === 0) return grouped
31
+ let page = 1
32
+ let total = null
33
+ let count = 0
34
+ while (true) {
35
+ const result = repository.list({ taskIds, page, pageSize: 100 })
36
+ if (!result || !Array.isArray(result.runs) || !Number.isInteger(result.total) || result.total < 0) {
37
+ throw new Error('Scheduled runs repository returned an invalid backup page')
38
+ }
39
+ if (total === null) total = result.total
40
+ else if (total !== result.total) throw new Error('Scheduled runs repository changed during backup export')
41
+ for (const run of result.runs) {
42
+ if (!taskIds.includes(run.taskId)) throw new Error('Scheduled runs repository returned an orphan backup row')
43
+ const { taskId, source: _source, ...value } = run
44
+ grouped.get(taskId).push(value)
45
+ count += 1
46
+ }
47
+ if (count >= total) break
48
+ if (result.runs.length === 0) throw new Error('Scheduled runs repository backup pagination was incomplete')
49
+ page += 1
50
+ }
51
+ if (count !== total) throw new Error('Scheduled runs repository backup count verification failed')
52
+ return grouped
53
+ }
54
+
55
+ export async function readScheduledTasksForBackup({
56
+ readTasks = () => readStore('scheduled-tasks'),
57
+ repository: repositoryOverride,
58
+ maintenance = true,
59
+ } = {}) {
60
+ const operation = async () => {
61
+ const tasks = await readTasks()
62
+ if (!isScheduledRunsAuthoritative()) return tasks
63
+ const repository = repositoryOverride || repositoryRequired()
64
+ const taskIds = Object.keys(tasks)
65
+ const grouped = groupRuns(repository, taskIds)
66
+ const logical = Object.fromEntries(Object.entries(tasks).map(([taskId, task]) => [taskId, {
67
+ ...task,
68
+ runs: grouped.get(taskId) || [],
69
+ }]))
70
+ const snapshot = splitScheduledTasksRuns(logical)
71
+ const repositoryCount = repository.count({ taskIds })
72
+ if (snapshot.count !== repositoryCount) {
73
+ throw new Error('Scheduled runs logical backup count/digest verification failed')
74
+ }
75
+ verifyScheduledRunsRepository(repository, snapshot)
76
+ return logical
77
+ }
78
+ if (!maintenance || !isScheduledRunsAuthoritative() || currentScheduledRunsMaintenanceContext()) return operation()
79
+ return runScheduledRunsMaintenance(operation, { operation: 'scheduled-runs-backup-export', pauseRuntime: false })
80
+ }
81
+
82
+ async function writePlan(plan, planFile = RESTORE_PLAN_FILE) {
83
+ await fs.mkdir(path.dirname(planFile), { recursive: true })
84
+ const temporary = `${planFile}.${process.pid}.${randomUUID()}.tmp`
85
+ try {
86
+ await fs.writeFile(temporary, `${JSON.stringify(plan, null, 2)}\n`, 'utf8')
87
+ await fs.rename(temporary, planFile)
88
+ } catch (error) {
89
+ await fs.rm(temporary, { force: true }).catch(() => {})
90
+ throw error
91
+ }
92
+ }
93
+
94
+ function planSnapshot(value) {
95
+ const snapshot = splitScheduledTasksRuns(value)
96
+ return { count: snapshot.count, digest: snapshot.digest }
97
+ }
98
+
99
+ function validatePlan(plan) {
100
+ if (!plan || plan.version !== 1 || plan.operation !== 'scheduled_tasks_restore' || !isPlainPlanValue(plan.before) || !isPlainPlanValue(plan.target)) {
101
+ throw new Error('Scheduled runs restore plan is invalid')
102
+ }
103
+ if (!ROLL_FORWARD_STATUSES.has(plan.status) && !ROLL_BACK_STATUSES.has(plan.status)) {
104
+ throw new Error('Scheduled runs restore plan status is invalid')
105
+ }
106
+ const before = planSnapshot(plan.before)
107
+ const target = planSnapshot(plan.target)
108
+ if (plan.beforeCount !== before.count || plan.beforeDigest !== before.digest || plan.targetCount !== target.count || plan.targetDigest !== target.digest) {
109
+ throw new Error('Scheduled runs restore plan digest is invalid')
110
+ }
111
+ return plan
112
+ }
113
+
114
+ function isPlainPlanValue(value) {
115
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
116
+ }
117
+
118
+ async function applyLogicalTasks(tasks, repository, writeTasks = (value) => writeStore('scheduled-tasks', value)) {
119
+ const snapshot = splitScheduledTasksRuns(tasks)
120
+ repository.replaceAll(snapshot.entries, { source: 'restore' })
121
+ verifyScheduledRunsRepository(repository, snapshot)
122
+ await writeTasks(snapshot.metadata)
123
+ const verifiedMetadata = splitScheduledTasksRuns(snapshot.metadata)
124
+ if (verifiedMetadata.count !== 0) throw new Error('Scheduled runs restore metadata verification failed')
125
+ return snapshot
126
+ }
127
+
128
+ function hasActiveRuns(tasks, repository) {
129
+ for (const task of Object.values(tasks || {})) {
130
+ if (task?.currentRunId || (Array.isArray(task?.currentRunIds) && task.currentRunIds.some(Boolean))) return true
131
+ if (Array.isArray(task?.runs) && task.runs.some((run) => run?.status === 'running')) return true
132
+ }
133
+ return repository.count({ status: 'running' }) > 0
134
+ }
135
+
136
+ function activeRestoreError() {
137
+ const error = new Error('Cannot restore scheduled tasks while a run is active')
138
+ error.statusCode = 409
139
+ error.errorCode = 'scheduled_runs_active'
140
+ return error
141
+ }
142
+
143
+ export async function restoreScheduledTasks(tasks, {
144
+ mode = 'replace',
145
+ repository: repositoryOverride,
146
+ readCurrent,
147
+ writeTasks,
148
+ planFile = RESTORE_PLAN_FILE,
149
+ storage,
150
+ } = {}) {
151
+ if (!isScheduledRunsAuthoritative()) {
152
+ const current = readCurrent ? await readCurrent() : await readStore('scheduled-tasks')
153
+ const target = mode === 'merge' ? { ...current, ...tasks } : tasks
154
+ if (hasActiveRuns(current, { count: () => 0 })) throw activeRestoreError()
155
+ await writeStore('scheduled-tasks', target)
156
+ return target
157
+ }
158
+
159
+ const repository = repositoryOverride || repositoryRequired()
160
+ return runScheduledRunsMaintenance(async () => {
161
+ const current = readCurrent
162
+ ? await readCurrent()
163
+ : await readScheduledTasksForBackup({ repository, maintenance: false })
164
+ if (hasActiveRuns(current, repository)) throw activeRestoreError()
165
+ const target = mode === 'merge' ? { ...current, ...tasks } : tasks
166
+ const beforeSnapshot = planSnapshot(current)
167
+ const targetSnapshot = planSnapshot(target)
168
+ let plan = {
169
+ version: 1,
170
+ operation: 'scheduled_tasks_restore',
171
+ status: 'prepared',
172
+ createdAt: new Date().toISOString(),
173
+ before: current,
174
+ target,
175
+ beforeCount: beforeSnapshot.count,
176
+ beforeDigest: beforeSnapshot.digest,
177
+ targetCount: targetSnapshot.count,
178
+ targetDigest: targetSnapshot.digest,
179
+ }
180
+ await writePlan(plan, planFile)
181
+ try {
182
+ plan = { ...plan, status: 'applying' }
183
+ await writePlan(plan, planFile)
184
+ await applyLogicalTasks(target, repository, writeTasks)
185
+ plan = { ...plan, status: 'target_applied' }
186
+ await writePlan(plan, planFile)
187
+ await fs.rm(planFile, { force: true })
188
+ return target
189
+ } catch (error) {
190
+ try {
191
+ plan = { ...plan, status: 'compensating', failedAt: new Date().toISOString() }
192
+ await writePlan(plan, planFile)
193
+ await applyLogicalTasks(current, repository, writeTasks)
194
+ await fs.rm(planFile, { force: true })
195
+ } catch (compensationError) {
196
+ await writePlan({
197
+ ...plan,
198
+ status: 'compensation_failed',
199
+ compensationFailedAt: new Date().toISOString(),
200
+ errorName: error?.name || 'Error',
201
+ compensationErrorName: compensationError?.name || 'Error',
202
+ }, planFile).catch(() => {})
203
+ const retained = new Error('Scheduled task restore failed and compensation failed', { cause: compensationError })
204
+ retained.retainScheduledRunsMaintenance = true
205
+ throw retained
206
+ }
207
+ throw error
208
+ }
209
+ }, { storage, operation: 'scheduled-runs-restore' })
210
+ }
211
+
212
+ export async function recoverScheduledRunsRestorePlan({
213
+ repository: repositoryOverride,
214
+ writeTasks,
215
+ planFile = RESTORE_PLAN_FILE,
216
+ storage,
217
+ } = {}) {
218
+ let plan
219
+ try {
220
+ plan = validatePlan(JSON.parse(await fs.readFile(planFile, 'utf8')))
221
+ } catch (error) {
222
+ if (error?.code === 'ENOENT') return false
223
+ throw new Error(`Scheduled runs restore plan is unreadable or invalid: ${error?.message || error}`, { cause: error })
224
+ }
225
+ if (!['sqlite_authoritative_json_pending', 'authoritative'].includes(getScheduledRunsPhase())) {
226
+ throw new Error('Scheduled runs restore plan exists outside authoritative mode')
227
+ }
228
+ const repository = repositoryOverride || repositoryRequired()
229
+ return runScheduledRunsMaintenance(async () => {
230
+ const value = ROLL_BACK_STATUSES.has(plan.status) ? plan.before : plan.target
231
+ await applyLogicalTasks(value, repository, writeTasks)
232
+ await fs.rm(planFile, { force: true })
233
+ return true
234
+ }, { storage, operation: 'scheduled-runs-restore-recovery', pauseRuntime: false })
235
+ }