@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.
- 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-CPE5tbbf.js → PluginsPage-mJkTmVVV.js} +1 -1
- package/dist/assets/ScheduledTasksPage-DJj_NLm9.js +2 -0
- package/dist/assets/{SettingsWorkspacePage-BE2T4d3P.js → SettingsWorkspacePage-7lQ0Wl63.js} +389 -389
- 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-BiCTgUlY.css → index-D-J_8Smf.css} +1 -1
- 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-GY7sdfIr.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-C1fJ1Jl7.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/cloud/auto-approval.mjs +155 -0
- package/server/cloud/client.mjs +5 -0
- package/server/cloud/qf-agent-process.mjs +163 -3
- package/server/cloud/service-config.mjs +1 -1
- 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-BfA_sQ3A.js +0 -1
- package/dist/assets/ChatPanelHost-Dul1ahKj.js +0 -307
- package/dist/assets/CloudAccountSettingsPage-qZh0lTUN.js +0 -1
- package/dist/assets/ScheduledTasksPage-PTxL_PPg.js +0 -2
- package/dist/assets/ShareLinksSettingsPage-Bl8fP62X.js +0 -1
- package/dist/assets/SharedConversationPage-BK9XJnEe.js +0 -1
- package/dist/assets/TerminalDock-CL7HU9DI.js +0 -2
- package/dist/assets/WorkspaceInspector-MNBLIKMn.js +0 -36
- package/dist/assets/index-BTPzyq7d.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,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
|
+
}
|