@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,279 @@
1
+ import { createScheduledTaskRunsRepository } from './sqlite/scheduled-task-runs-repository.mjs'
2
+ import { isScheduledRunsAuthoritative } from './scheduled-runs-cutover.mjs'
3
+
4
+ const NULLABLE_UPDATE_FIELDS = Object.freeze([
5
+ 'inputContent', 'aiResult', 'result', 'errorMessage', 'warning', 'sessionId', 'finishedAt',
6
+ 'durationMs', 'agentId', 'agentLabel', 'agentSnapshot', 'legacy',
7
+ ])
8
+
9
+ function compositeKey(taskId, runId) {
10
+ return JSON.stringify([String(taskId ?? ''), String(runId ?? '')])
11
+ }
12
+
13
+ function normalizeTasks(value) {
14
+ if (Array.isArray(value)) return value
15
+ if (value && typeof value === 'object') return Object.values(value)
16
+ return []
17
+ }
18
+
19
+ function parsePositiveInteger(value, fallback, max) {
20
+ const parsed = Number(value)
21
+ if (!Number.isInteger(parsed) || parsed <= 0) return fallback
22
+ return Math.min(parsed, max)
23
+ }
24
+
25
+ function normalizedDateBoundary(value) {
26
+ if (value === undefined || value === null || value === '') return null
27
+ const timestamp = new Date(String(value)).getTime()
28
+ return Number.isNaN(timestamp) ? null : timestamp
29
+ }
30
+
31
+ function runMatchesKeyword(run, keyword) {
32
+ if (!keyword) return true
33
+ const text = [run.taskTitle, run.inputContent, run.aiResult, run.result, run.errorMessage]
34
+ .filter(Boolean)
35
+ .join('\n')
36
+ .toLowerCase()
37
+ return text.includes(keyword.toLowerCase())
38
+ }
39
+
40
+ function updateValue(run) {
41
+ const value = { status: run.status }
42
+ for (const field of NULLABLE_UPDATE_FIELDS) value[field] = run[field] ?? null
43
+ return value
44
+ }
45
+
46
+ function sortedDiagnosticsEntries(keys) {
47
+ return [...keys]
48
+ .map((key) => {
49
+ const [taskId, runId] = JSON.parse(key)
50
+ return { taskId, runId }
51
+ })
52
+ .sort((a, b) => a.taskId.localeCompare(b.taskId) || a.runId.localeCompare(b.runId))
53
+ }
54
+
55
+ function taskMetadata(task) {
56
+ return {
57
+ taskTitle: task.title,
58
+ scheduleRule: task.scheduleRule,
59
+ projectName: task.projectName,
60
+ }
61
+ }
62
+
63
+ export function createScheduledTaskRunsService({
64
+ readTasks,
65
+ getRepository = () => createScheduledTaskRunsRepository(),
66
+ isAuthoritative = isScheduledRunsAuthoritative,
67
+ logger = console,
68
+ } = {}) {
69
+ if (typeof readTasks !== 'function') throw new TypeError('Scheduled task runs service requires readTasks')
70
+ if (typeof getRepository !== 'function') throw new TypeError('Scheduled task runs service requires getRepository')
71
+ if (typeof isAuthoritative !== 'function') throw new TypeError('Scheduled task runs service requires isAuthoritative')
72
+
73
+ let repositoryInstance = null
74
+ const taskOperations = new Map()
75
+ const dirtyTaskIds = new Set()
76
+ const dirtyRunKeys = new Set()
77
+ const failureCounts = Object.create(null)
78
+ let readDegraded = false
79
+ let lastFailureAt = null
80
+
81
+ async function repository() {
82
+ if (!repositoryInstance) repositoryInstance = await getRepository()
83
+ return repositoryInstance
84
+ }
85
+
86
+ function markDirty(taskId, runId) {
87
+ dirtyTaskIds.add(taskId)
88
+ if (runId !== undefined && runId !== null) dirtyRunKeys.add(compositeKey(taskId, runId))
89
+ }
90
+
91
+ function clearDirty(taskId, runId) {
92
+ dirtyRunKeys.delete(compositeKey(taskId, runId))
93
+ if (![...dirtyRunKeys].some((key) => JSON.parse(key)[0] === taskId)) dirtyTaskIds.delete(taskId)
94
+ }
95
+
96
+ function recordFailure(operation, error, { taskId, runId, phase } = {}) {
97
+ failureCounts[operation] = (failureCounts[operation] || 0) + 1
98
+ lastFailureAt = new Date().toISOString()
99
+ if (taskId) markDirty(taskId, runId)
100
+ logger?.warn?.('Scheduled task run persistence failed', {
101
+ operation,
102
+ taskId,
103
+ runId,
104
+ phase,
105
+ errorName: error?.name || 'Error',
106
+ errorCode: error?.code,
107
+ })
108
+ }
109
+
110
+ async function performSyncRun(taskId, run, phase) {
111
+ try {
112
+ const repo = await repository()
113
+ if (isAuthoritative()) {
114
+ const result = repo.upsert(taskId, run, { source: 'runtime' })
115
+ clearDirty(taskId, run.id)
116
+ return result
117
+ }
118
+ const current = repo.get(taskId, run.id)
119
+ const result = current
120
+ ? repo.update(taskId, run.id, updateValue(run))
121
+ : repo.upsert(taskId, run, { source: 'hybrid_shadow' })
122
+ clearDirty(taskId, run.id)
123
+ return result
124
+ } catch (error) {
125
+ recordFailure('sync', error, { taskId, runId: run.id, phase })
126
+ if (isAuthoritative()) throw error
127
+ return null
128
+ }
129
+ }
130
+
131
+ async function syncRun(taskId, run, { phase = 'updated' } = {}) {
132
+ if (!taskId || !run?.id) return null
133
+ const previous = taskOperations.get(taskId) || Promise.resolve()
134
+ const operation = previous.catch(() => undefined).then(() => performSyncRun(taskId, run, phase))
135
+ taskOperations.set(taskId, operation)
136
+ try {
137
+ return await operation
138
+ } finally {
139
+ if (taskOperations.get(taskId) === operation) taskOperations.delete(taskId)
140
+ }
141
+ }
142
+
143
+ async function deleteRun(taskId, runId) {
144
+ try {
145
+ const deleted = (await repository()).delete(taskId, runId)
146
+ clearDirty(taskId, runId)
147
+ return deleted
148
+ } catch (error) {
149
+ recordFailure('delete', error, { taskId, runId })
150
+ if (isAuthoritative()) throw error
151
+ return false
152
+ }
153
+ }
154
+
155
+ async function performDeleteTaskRuns(taskId) {
156
+ try {
157
+ await (await repository()).deleteByTask(taskId)
158
+ dirtyTaskIds.delete(taskId)
159
+ for (const key of dirtyRunKeys) if (JSON.parse(key)[0] === taskId) dirtyRunKeys.delete(key)
160
+ return true
161
+ } catch (error) {
162
+ recordFailure('deleteByTask', error, { taskId })
163
+ if (isAuthoritative()) throw error
164
+ return false
165
+ }
166
+ }
167
+
168
+ async function deleteTaskRuns(taskId) {
169
+ if (!taskId) return false
170
+ const previous = taskOperations.get(taskId) || Promise.resolve()
171
+ const operation = previous.catch(() => undefined).then(() => performDeleteTaskRuns(taskId))
172
+ taskOperations.set(taskId, operation)
173
+ try {
174
+ return await operation
175
+ } finally {
176
+ if (taskOperations.get(taskId) === operation) taskOperations.delete(taskId)
177
+ }
178
+ }
179
+
180
+ async function listAuthoritative(filters, tasks) {
181
+ const repo = await repository()
182
+ const metadataById = new Map(tasks.filter((task) => task?.id).map((task) => [task.id, task]))
183
+ const currentTaskIds = [...metadataById.keys()]
184
+ const keyword = String(filters.keyword || '').trim()
185
+ const keywordTaskIds = keyword
186
+ ? tasks.filter((task) => String(task?.title || '').toLowerCase().includes(keyword.toLowerCase())).map((task) => task.id)
187
+ : undefined
188
+ const result = repo.list({
189
+ page: filters.page,
190
+ pageSize: filters.pageSize,
191
+ taskId: filters.taskId,
192
+ taskIds: currentTaskIds,
193
+ status: filters.status,
194
+ trigger: filters.trigger,
195
+ keyword,
196
+ keywordTaskIds,
197
+ startedFrom: filters.startedFrom,
198
+ startedTo: filters.startedTo,
199
+ })
200
+ return {
201
+ ...result,
202
+ runs: result.runs.map((run) => {
203
+ const { source: _source, ...publicRun } = run
204
+ return { ...publicRun, ...taskMetadata(metadataById.get(run.taskId)) }
205
+ }),
206
+ }
207
+ }
208
+
209
+ async function listHybrid(filters, tasks) {
210
+ const page = parsePositiveInteger(filters.page, 1, 100_000)
211
+ const pageSize = parsePositiveInteger(filters.pageSize, 10, 100)
212
+ const taskId = String(filters.taskId || '').trim()
213
+ const status = String(filters.status || '').trim()
214
+ const trigger = String(filters.trigger || '').trim()
215
+ const keyword = String(filters.keyword || '').trim()
216
+ const startedFrom = normalizedDateBoundary(filters.startedFrom)
217
+ const startedTo = normalizedDateBoundary(filters.startedTo)
218
+ let runs = []
219
+ for (const task of tasks) {
220
+ if (!task?.id) continue
221
+ const seen = new Set()
222
+ for (const run of Array.isArray(task.runs) ? task.runs : []) {
223
+ if (!run?.id || seen.has(run.id)) continue
224
+ seen.add(run.id)
225
+ runs.push({ ...run, taskId: task.id, ...taskMetadata(task) })
226
+ }
227
+ }
228
+ if (taskId) runs = runs.filter((run) => run.taskId === taskId)
229
+ if (status) runs = runs.filter((run) => run.status === status)
230
+ if (trigger) runs = runs.filter((run) => run.trigger === trigger)
231
+ if (startedFrom !== null) runs = runs.filter((run) => new Date(run.startedAt).getTime() >= startedFrom)
232
+ if (startedTo !== null) runs = runs.filter((run) => new Date(run.startedAt).getTime() <= startedTo)
233
+ if (keyword) runs = runs.filter((run) => runMatchesKeyword(run, keyword))
234
+ runs.sort((a, b) => String(b.startedAt).localeCompare(String(a.startedAt)) || String(b.id).localeCompare(String(a.id)) || String(b.taskId).localeCompare(String(a.taskId)))
235
+ const total = runs.length
236
+ const start = (page - 1) * pageSize
237
+ return { runs: runs.slice(start, start + pageSize), total, page, pageSize }
238
+ }
239
+
240
+ async function listRuns(filters = {}) {
241
+ await Promise.all([...taskOperations.values()].map((operation) => operation.catch(() => undefined)))
242
+ const tasks = normalizeTasks(await readTasks())
243
+ if (!isAuthoritative()) return listHybrid(filters, tasks)
244
+ try {
245
+ const result = await listAuthoritative(filters, tasks)
246
+ readDegraded = false
247
+ return result
248
+ } catch (error) {
249
+ readDegraded = true
250
+ recordFailure('list', error, { taskId: filters.taskId || undefined })
251
+ throw error
252
+ }
253
+ }
254
+
255
+ async function recentRuns(taskId, limit = 5) {
256
+ if (!isAuthoritative()) {
257
+ const task = normalizeTasks(await readTasks()).find((value) => value?.id === taskId)
258
+ return Array.isArray(task?.runs) ? task.runs.slice(0, limit) : []
259
+ }
260
+ const normalizedLimit = Math.max(1, Math.min(Number(limit) || 5, 200))
261
+ return (await repository()).listByTask(taskId, { limit: normalizedLimit }).map((run) => {
262
+ const { source: _source, ...publicRun } = run
263
+ return publicRun
264
+ })
265
+ }
266
+
267
+ function getDiagnostics() {
268
+ return {
269
+ dirtyTaskIds: [...dirtyTaskIds].sort(),
270
+ dirtyRunIds: sortedDiagnosticsEntries(dirtyRunKeys),
271
+ ownedRunIds: [],
272
+ readDegraded,
273
+ lastFailureAt,
274
+ failureCounts: { ...failureCounts },
275
+ }
276
+ }
277
+
278
+ return Object.freeze({ syncRun, deleteRun, deleteTaskRuns, listRuns, recentRuns, getDiagnostics })
279
+ }