@shawnstack/quickforge 1.7.1 → 1.7.2
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-BFgyFa5e.js +1 -0
- package/dist/assets/ChatPanelHost-Cld9HcVz.js +288 -0
- package/dist/assets/{PluginsPage-CKyWhlCo.js → PluginsPage-DEMqwhOA.js} +1 -1
- package/dist/assets/ScheduledTasksPage-DTP_gedp.js +2 -0
- package/dist/assets/{SettingsWorkspacePage-DwqEnUmX.js → SettingsWorkspacePage-Dr9zT5Bs.js} +112 -112
- package/dist/assets/SharedConversationPage-op3DRwcw.js +1 -0
- package/dist/assets/{TerminalDock-B9xKnimU.js → TerminalDock-BL_UovwU.js} +2 -2
- package/dist/assets/WorkspaceInspector-73dhAime.js +13 -0
- package/dist/assets/icons-BP8YOS-Z.js +1 -0
- package/dist/assets/index-Cu2bBHLv.css +3 -0
- package/dist/assets/index-yV1wtqTr.js +66 -0
- package/dist/assets/mcp-servers-dialog-Bp6kbIup.js +5 -0
- package/dist/assets/{monaco-CPwJMUsl.js → monaco-BTsVCDWS.js} +1 -1
- package/dist/assets/{react-vendor-CLbWF1Oy.js → react-vendor-Dr5xvL-e.js} +1 -1
- package/dist/assets/{skills-dialog-D18mxNyW.js → skills-dialog-DiJAUfvW.js} +1 -1
- package/dist/index.html +6 -6
- package/package.json +1 -1
- package/server/acp/server.mjs +19 -7
- package/server/mcp/registry.mjs +54 -14
- package/server/plugins/loader.mjs +9 -1
- package/server/plugins/registry.mjs +32 -10
- package/server/routes/mcp.mjs +0 -5
- package/server/routes/scheduled-tasks.mjs +72 -32
- package/server/routes/workspace.mjs +87 -17
- package/server/utils/scheduled-tasks.mjs +23 -10
- package/server/utils/workspace.mjs +25 -3
- package/dist/assets/AgentProfilesPage-__uY0AvK.js +0 -1
- package/dist/assets/ChatPanelHost-DIs_vWFX.js +0 -291
- package/dist/assets/ScheduledTasksPage-KtJoeJYt.js +0 -2
- package/dist/assets/SharedConversationPage-yujCRsbe.js +0 -1
- package/dist/assets/WorkspaceInspector-CE6RD6Ys.js +0 -13
- package/dist/assets/icons-pPRMD2tE.js +0 -1
- package/dist/assets/index-BSXpUDCq.js +0 -63
- package/dist/assets/index-DpO7jEGP.css +0 -3
- package/dist/assets/mcp-servers-dialog-CaQTvGiW.js +0 -5
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { streamSimple } from '@earendil-works/pi-ai/compat'
|
|
2
2
|
import { readJsonBody, sendJson, decodeSegment } from '../utils/response.mjs'
|
|
3
3
|
import { readStore, atomicUpdate } from '../storage.mjs'
|
|
4
|
-
import { createAgent, getSessionEventBus, agentEvents, persistSessionState } from '../agent-manager.mjs'
|
|
4
|
+
import { createAgent, getSessionEventBus, agentEvents, persistSessionState, abortRun } from '../agent-manager.mjs'
|
|
5
5
|
import { agentProfileSnapshot, getAgentProfile } from '../agent-profiles.mjs'
|
|
6
6
|
import { projectContextFromId, readProjectConfig } from '../project-config.mjs'
|
|
7
7
|
import { logger } from '../utils/logger.mjs'
|
|
@@ -436,21 +436,30 @@ async function updateTask(taskId, updater) {
|
|
|
436
436
|
return updated
|
|
437
437
|
}
|
|
438
438
|
|
|
439
|
+
function recurringTaskStatusRepair(task, now) {
|
|
440
|
+
if (task?.status !== 'completed' || !isRecurringTask(task)) return null
|
|
441
|
+
const nextRunAt = task.nextRunAt && new Date(task.nextRunAt).getTime() > now.getTime()
|
|
442
|
+
? task.nextRunAt
|
|
443
|
+
: calculateNextRun(task, now)
|
|
444
|
+
if (!nextRunAt) return null
|
|
445
|
+
return {
|
|
446
|
+
...task,
|
|
447
|
+
status: 'enabled',
|
|
448
|
+
nextRunAt,
|
|
449
|
+
scheduleRule: scheduleRuleFor({ ...task, nextRunAt }),
|
|
450
|
+
updatedAt: now.toISOString(),
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
439
454
|
async function repairRecurringTaskStatuses() {
|
|
455
|
+
const now = new Date()
|
|
456
|
+
const snapshot = await readStore(STORE)
|
|
457
|
+
if (!Object.values(snapshot).some((task) => recurringTaskStatusRepair(task, now))) return
|
|
458
|
+
|
|
440
459
|
await atomicUpdate(STORE, (data) => {
|
|
441
460
|
for (const [taskId, task] of Object.entries(data)) {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
? task.nextRunAt
|
|
445
|
-
: calculateNextRun(task)
|
|
446
|
-
if (!nextRunAt) continue
|
|
447
|
-
data[taskId] = {
|
|
448
|
-
...task,
|
|
449
|
-
status: 'enabled',
|
|
450
|
-
nextRunAt,
|
|
451
|
-
scheduleRule: scheduleRuleFor({ ...task, nextRunAt }),
|
|
452
|
-
updatedAt: new Date().toISOString(),
|
|
453
|
-
}
|
|
461
|
+
const repaired = recurringTaskStatusRepair(task, now)
|
|
462
|
+
if (repaired) data[taskId] = repaired
|
|
454
463
|
}
|
|
455
464
|
return data
|
|
456
465
|
})
|
|
@@ -566,14 +575,15 @@ async function executeTask(task, trigger = 'schedule', onStarted) {
|
|
|
566
575
|
onStarted?.({ taskId: task.id, runId, sessionId })
|
|
567
576
|
|
|
568
577
|
const eventBus = getSessionEventBus(sessionId)
|
|
578
|
+
const runtimeLimitMs = Math.max(1000, Math.min(Number(executionAgent?.maxRuntimeMs || 30 * 60 * 1000), 30 * 60 * 1000))
|
|
579
|
+
let timeout = null
|
|
580
|
+
let handler = null
|
|
581
|
+
let timedOut = false
|
|
582
|
+
let resolveFinished
|
|
569
583
|
const finished = new Promise((resolve) => {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
eventBus?.removeListener('agent_event', handler)
|
|
573
|
-
}
|
|
574
|
-
const handler = (event) => {
|
|
584
|
+
resolveFinished = resolve
|
|
585
|
+
handler = (event) => {
|
|
575
586
|
if (event.type !== 'agent_end') return
|
|
576
|
-
cleanup(handler, timeout)
|
|
577
587
|
const errorMessage = event.errorMessage || session.agent.state.errorMessage
|
|
578
588
|
const aborted = session.status === 'aborted' || session.agent.state.messages.some((message) => message?.role === 'assistant' && message?.stopReason === 'aborted')
|
|
579
589
|
resolve({
|
|
@@ -583,24 +593,54 @@ async function executeTask(task, trigger = 'schedule', onStarted) {
|
|
|
583
593
|
messages: event.messages ?? session.agent.state.messages,
|
|
584
594
|
})
|
|
585
595
|
}
|
|
586
|
-
const timeout = setTimeout(() => {
|
|
587
|
-
cleanup(handler, timeout)
|
|
588
|
-
resolve({ ok: false, aborted: false, error: '执行超时', messages: session.agent.state.messages })
|
|
589
|
-
}, Math.max(1000, Math.min(Number(executionAgent?.maxRuntimeMs || 30 * 60 * 1000), 30 * 60 * 1000)))
|
|
590
596
|
eventBus?.on('agent_event', handler)
|
|
591
597
|
})
|
|
592
598
|
|
|
599
|
+
const runPromise = (async () => {
|
|
600
|
+
try {
|
|
601
|
+
await session.agent.continue()
|
|
602
|
+
} catch (continueError) {
|
|
603
|
+
if (continueError?.message !== 'Request was aborted' && continueError?.message !== 'Scheduled task aborted') {
|
|
604
|
+
throw continueError
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
return finished
|
|
608
|
+
})()
|
|
609
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
610
|
+
timeout = setTimeout(() => resolve({ timedOut: true }), runtimeLimitMs)
|
|
611
|
+
})
|
|
612
|
+
|
|
613
|
+
let result
|
|
593
614
|
try {
|
|
594
|
-
await
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
615
|
+
const outcome = await Promise.race([
|
|
616
|
+
runPromise.then((value) => ({ result: value })),
|
|
617
|
+
timeoutPromise,
|
|
618
|
+
])
|
|
619
|
+
if (outcome.timedOut) {
|
|
620
|
+
timedOut = true
|
|
621
|
+
const abortPromise = Promise.resolve()
|
|
622
|
+
.then(() => abortRun(sessionId))
|
|
623
|
+
.catch((error) => {
|
|
624
|
+
logger.warn(`Failed to abort timed out scheduled task ${task.id}:`, error)
|
|
625
|
+
})
|
|
626
|
+
await Promise.race([
|
|
627
|
+
abortPromise,
|
|
628
|
+
new Promise((resolve) => setTimeout(resolve, 1000)),
|
|
629
|
+
])
|
|
630
|
+
await Promise.race([
|
|
631
|
+
runPromise.catch(() => undefined),
|
|
632
|
+
new Promise((resolve) => setTimeout(resolve, 1000)),
|
|
633
|
+
])
|
|
634
|
+
result = { ok: false, aborted: true, error: '执行超时', messages: session.agent.state.messages }
|
|
598
635
|
} else {
|
|
599
|
-
|
|
636
|
+
result = outcome.result
|
|
600
637
|
}
|
|
638
|
+
} finally {
|
|
639
|
+
clearTimeout(timeout)
|
|
640
|
+
eventBus?.removeListener('agent_event', handler)
|
|
641
|
+
if (timedOut) resolveFinished?.({ ok: false, aborted: true, error: '执行超时', messages: session.agent.state.messages })
|
|
601
642
|
}
|
|
602
|
-
|
|
603
|
-
if (result.aborted) settled = true
|
|
643
|
+
settled = true
|
|
604
644
|
const finishedAt = new Date().toISOString()
|
|
605
645
|
const durationMs = new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
606
646
|
const aiResult = result.ok ? latestAssistantText(result.messages) : ''
|
|
@@ -634,7 +674,7 @@ async function executeTask(task, trigger = 'schedule', onStarted) {
|
|
|
634
674
|
inputContent: run.inputContent ?? latestTask.instruction,
|
|
635
675
|
aiResult: result.ok ? aiResult : undefined,
|
|
636
676
|
result: result.ok ? (aiResult || `已完成,结果保存在会话 ${sessionId}`) : undefined,
|
|
637
|
-
errorMessage: result.
|
|
677
|
+
errorMessage: result.error,
|
|
638
678
|
sessionId,
|
|
639
679
|
agentId: executionAgent?.id || latestTask.agentId || null,
|
|
640
680
|
agentLabel: executionAgent?.label || null,
|
|
@@ -651,7 +691,7 @@ async function executeTask(task, trigger = 'schedule', onStarted) {
|
|
|
651
691
|
sessionId,
|
|
652
692
|
status: result.aborted ? 'failed' : (result.ok ? 'success' : 'failed'),
|
|
653
693
|
result: aiResult,
|
|
654
|
-
errorMessage: result.
|
|
694
|
+
errorMessage: result.error,
|
|
655
695
|
})
|
|
656
696
|
} catch (error) {
|
|
657
697
|
const finishedAt = new Date().toISOString()
|
|
@@ -9,12 +9,17 @@ import { logger } from '../utils/logger.mjs'
|
|
|
9
9
|
import { openPathInFileManager, openPathInIDEA, openPathInVSCode } from '../utils/platform.mjs'
|
|
10
10
|
import {
|
|
11
11
|
assertSafeWorkspacePath,
|
|
12
|
+
createWorkspacePathValidator,
|
|
12
13
|
resolveWorkspacePath,
|
|
13
14
|
toWorkspaceRelative,
|
|
14
15
|
} from '../utils/workspace.mjs'
|
|
15
16
|
|
|
16
17
|
const MAX_PREVIEW_BYTES = 50 * 1024 * 1024
|
|
17
18
|
const MAX_STATIC_PREVIEW_BYTES = 50 * 1024 * 1024
|
|
19
|
+
const MAX_GIT_LINE_COUNT_FILES = 100
|
|
20
|
+
const MAX_GIT_LINE_COUNT_FILE_BYTES = 1024 * 1024
|
|
21
|
+
const MAX_GIT_LINE_COUNT_TOTAL_BYTES = 10 * 1024 * 1024
|
|
22
|
+
const GIT_LINE_COUNT_CONCURRENCY = 6
|
|
18
23
|
const PREVIEW_ALLOWED_EXTENSIONS = new Set(['.html', '.htm', '.css', '.js', '.mjs', '.json', '.svg', '.png', '.jpg', '.jpeg', '.webp', '.gif', '.ico', '.txt', '.md'])
|
|
19
24
|
const MAX_TREE_NODES = 50000
|
|
20
25
|
const SKIP_DIRS = new Set(['.git', 'node_modules'])
|
|
@@ -294,16 +299,78 @@ async function collectNumstat(context) {
|
|
|
294
299
|
return map
|
|
295
300
|
}
|
|
296
301
|
|
|
297
|
-
|
|
298
|
-
|
|
302
|
+
async function readUtf8FileAtMost(fullPath, maxBytes) {
|
|
303
|
+
if (maxBytes === 0) return ''
|
|
304
|
+
const handle = await fs.open(fullPath, 'r')
|
|
299
305
|
try {
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
306
|
+
const buffer = Buffer.allocUnsafe(maxBytes)
|
|
307
|
+
let offset = 0
|
|
308
|
+
while (offset < maxBytes) {
|
|
309
|
+
const { bytesRead } = await handle.read(buffer, offset, maxBytes - offset, offset)
|
|
310
|
+
if (bytesRead === 0) break
|
|
311
|
+
offset += bytesRead
|
|
312
|
+
}
|
|
313
|
+
return buffer.subarray(0, offset).toString('utf8')
|
|
314
|
+
} finally {
|
|
315
|
+
await handle.close()
|
|
304
316
|
}
|
|
305
317
|
}
|
|
306
318
|
|
|
319
|
+
async function poolMap(items, fn, concurrency) {
|
|
320
|
+
const results = new Array(items.length)
|
|
321
|
+
let cursor = 0
|
|
322
|
+
async function worker() {
|
|
323
|
+
while (cursor < items.length) {
|
|
324
|
+
const index = cursor
|
|
325
|
+
cursor += 1
|
|
326
|
+
results[index] = await fn(items[index], index)
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker()))
|
|
330
|
+
return results
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// 未跟踪文件不在 numstat 中,在有界预算内按工作区文件行数估算新增行
|
|
334
|
+
async function collectWorkspaceLineCounts(context, files) {
|
|
335
|
+
const candidates = files
|
|
336
|
+
.filter((file) => file.status === 'untracked' || file.status === 'added')
|
|
337
|
+
.slice(0, MAX_GIT_LINE_COUNT_FILES)
|
|
338
|
+
if (candidates.length === 0) return new Map()
|
|
339
|
+
|
|
340
|
+
const validateWorkspacePath = await createWorkspacePathValidator(context)
|
|
341
|
+
const inspected = await poolMap(candidates, async (file) => {
|
|
342
|
+
try {
|
|
343
|
+
const fullPath = resolveWorkspacePath(file.path, context)
|
|
344
|
+
await validateWorkspacePath(fullPath, { allowSensitive: true })
|
|
345
|
+
const stat = await fs.stat(fullPath)
|
|
346
|
+
if (!stat.isFile() || stat.size > MAX_GIT_LINE_COUNT_FILE_BYTES) return null
|
|
347
|
+
return { file, fullPath, size: stat.size }
|
|
348
|
+
} catch {
|
|
349
|
+
return null
|
|
350
|
+
}
|
|
351
|
+
}, GIT_LINE_COUNT_CONCURRENCY)
|
|
352
|
+
|
|
353
|
+
let totalBytes = 0
|
|
354
|
+
const selected = []
|
|
355
|
+
for (const entry of inspected) {
|
|
356
|
+
if (!entry || totalBytes + entry.size > MAX_GIT_LINE_COUNT_TOTAL_BYTES) continue
|
|
357
|
+
totalBytes += entry.size
|
|
358
|
+
selected.push(entry)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const counts = await poolMap(selected, async ({ file, fullPath, size }) => {
|
|
362
|
+
try {
|
|
363
|
+
const stat = await fs.stat(fullPath)
|
|
364
|
+
if (!stat.isFile() || stat.size > size) return null
|
|
365
|
+
const content = await readUtf8FileAtMost(fullPath, size)
|
|
366
|
+
return [file.path, countTextLines(content)]
|
|
367
|
+
} catch {
|
|
368
|
+
return null
|
|
369
|
+
}
|
|
370
|
+
}, GIT_LINE_COUNT_CONCURRENCY)
|
|
371
|
+
return new Map(counts.filter(Boolean))
|
|
372
|
+
}
|
|
373
|
+
|
|
307
374
|
export async function listGitStatus(context) {
|
|
308
375
|
if (!(await isGitRepository(context.workspaceRoot))) return { isGitRepository: false, files: [] }
|
|
309
376
|
const result = await git(
|
|
@@ -312,17 +379,19 @@ export async function listGitStatus(context) {
|
|
|
312
379
|
)
|
|
313
380
|
const files = parseGitStatus(result.stdout)
|
|
314
381
|
const numstat = await collectNumstat(context)
|
|
382
|
+
const fallbackFiles = files.filter((file) => !numstat.has(file.path))
|
|
383
|
+
const workspaceLineCounts = await collectWorkspaceLineCounts(context, fallbackFiles)
|
|
315
384
|
for (const file of files) {
|
|
316
385
|
const entry = numstat.get(file.path)
|
|
317
386
|
if (entry) {
|
|
318
387
|
file.additions = entry.additions
|
|
319
388
|
file.deletions = entry.deletions
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
389
|
+
continue
|
|
390
|
+
}
|
|
391
|
+
const count = workspaceLineCounts.get(file.path)
|
|
392
|
+
if (typeof count === 'number') {
|
|
393
|
+
file.additions = count
|
|
394
|
+
file.deletions = 0
|
|
326
395
|
}
|
|
327
396
|
}
|
|
328
397
|
const head = await currentGitHead(context.workspaceRoot)
|
|
@@ -601,7 +670,7 @@ async function readWorkspaceTextFile(context, relativePath) {
|
|
|
601
670
|
return { content: buffer.toString('utf8'), size: stat.size, path: toWorkspaceRelative(file, context) }
|
|
602
671
|
}
|
|
603
672
|
|
|
604
|
-
async function buildTreeForDirectory(dir, context, counter) {
|
|
673
|
+
async function buildTreeForDirectory(dir, context, counter, validateWorkspacePath) {
|
|
605
674
|
const entries = await fs.readdir(dir, { withFileTypes: true }).catch(() => [])
|
|
606
675
|
const nodes = []
|
|
607
676
|
const sortedEntries = entries.sort((left, right) => {
|
|
@@ -616,20 +685,20 @@ async function buildTreeForDirectory(dir, context, counter) {
|
|
|
616
685
|
if (entry.isDirectory()) {
|
|
617
686
|
if (SKIP_DIRS.has(entry.name)) continue
|
|
618
687
|
try {
|
|
619
|
-
await
|
|
688
|
+
await validateWorkspacePath(fullPath, { allowSensitive: true })
|
|
620
689
|
counter.count += 1
|
|
621
690
|
nodes.push({
|
|
622
691
|
name: entry.name,
|
|
623
692
|
path: relativePath,
|
|
624
693
|
type: 'directory',
|
|
625
|
-
children: await buildTreeForDirectory(fullPath, context, counter),
|
|
694
|
+
children: await buildTreeForDirectory(fullPath, context, counter, validateWorkspacePath),
|
|
626
695
|
})
|
|
627
696
|
} catch {
|
|
628
697
|
// Skip directories that cannot be safely resolved.
|
|
629
698
|
}
|
|
630
699
|
} else if (entry.isFile()) {
|
|
631
700
|
try {
|
|
632
|
-
await
|
|
701
|
+
await validateWorkspacePath(fullPath, { allowSensitive: true })
|
|
633
702
|
counter.count += 1
|
|
634
703
|
nodes.push({ name: entry.name, path: relativePath, type: 'file' })
|
|
635
704
|
} catch {
|
|
@@ -642,7 +711,8 @@ async function buildTreeForDirectory(dir, context, counter) {
|
|
|
642
711
|
|
|
643
712
|
async function handleWorkspaceTree(req, res, url) {
|
|
644
713
|
const context = await projectContextFromUrl(url)
|
|
645
|
-
const
|
|
714
|
+
const validateWorkspacePath = await createWorkspacePathValidator(context)
|
|
715
|
+
const tree = await buildTreeForDirectory(context.workspaceRoot, context, { count: 0 }, validateWorkspacePath)
|
|
646
716
|
sendJson(res, 200, { root: context.project.name, tree })
|
|
647
717
|
}
|
|
648
718
|
|
|
@@ -90,6 +90,7 @@ function parseCronField(field, min, max) {
|
|
|
90
90
|
for (const part of field.split(',')) {
|
|
91
91
|
if (/^\*\/\d+$/.test(part)) {
|
|
92
92
|
const step = Number(part.slice(2))
|
|
93
|
+
if (!Number.isInteger(step) || step <= 0) return null
|
|
93
94
|
for (let value = min; value <= max; value += step) values.add(value)
|
|
94
95
|
} else if (/^\d+-\d+$/.test(part)) {
|
|
95
96
|
const [start, end] = part.split('-').map(Number)
|
|
@@ -102,25 +103,37 @@ function parseCronField(field, min, max) {
|
|
|
102
103
|
return { any: false, values: [...values] }
|
|
103
104
|
}
|
|
104
105
|
|
|
105
|
-
|
|
106
|
+
function parseCronExpression(cronExpression) {
|
|
106
107
|
const fields = String(cronExpression || '').trim().split(/\s+/)
|
|
107
|
-
if (fields.length !== 5) return
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
108
|
+
if (fields.length !== 5) return null
|
|
109
|
+
const rules = [
|
|
110
|
+
parseCronField(fields[0], 0, 59),
|
|
111
|
+
parseCronField(fields[1], 0, 23),
|
|
112
|
+
parseCronField(fields[2], 1, 31),
|
|
113
|
+
parseCronField(fields[3], 1, 12),
|
|
114
|
+
parseCronField(fields[4], 0, 6),
|
|
114
115
|
]
|
|
115
|
-
return
|
|
116
|
+
return rules.every(Boolean) ? rules : null
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function cronRulesMatch(date, rules) {
|
|
120
|
+
const values = [date.getMinutes(), date.getHours(), date.getDate(), date.getMonth() + 1, date.getDay()]
|
|
121
|
+
return rules.every((rule, index) => rule.any || rule.values.includes(values[index]))
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function cronMatches(date, cronExpression) {
|
|
125
|
+
const rules = parseCronExpression(cronExpression)
|
|
126
|
+
return rules ? cronRulesMatch(date, rules) : false
|
|
116
127
|
}
|
|
117
128
|
|
|
118
129
|
export function nextCronRun(cronExpression, base = new Date()) {
|
|
130
|
+
const rules = parseCronExpression(cronExpression)
|
|
131
|
+
if (!rules) return null
|
|
119
132
|
const cursor = new Date(base.getTime() + minuteMs)
|
|
120
133
|
cursor.setSeconds(0, 0)
|
|
121
134
|
const maxChecks = 366 * 24 * 60
|
|
122
135
|
for (let index = 0; index < maxChecks; index += 1) {
|
|
123
|
-
if (
|
|
136
|
+
if (cronRulesMatch(cursor, rules)) return cursor
|
|
124
137
|
cursor.setMinutes(cursor.getMinutes() + 1)
|
|
125
138
|
}
|
|
126
139
|
return null
|
|
@@ -75,15 +75,13 @@ async function realpathNearestExistingParent(inputPath) {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
async function assertSafeWorkspacePathWithRoot(fullPath, context, workspaceReal, options = {}) {
|
|
79
79
|
if (!options.allowSensitive && isSensitiveWorkspacePath(fullPath, context)) {
|
|
80
80
|
const error = new Error(`Access to sensitive path is blocked: ${toWorkspaceRelative(fullPath, context)}`)
|
|
81
81
|
error.statusCode = 403
|
|
82
82
|
throw error
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
const workspaceRoot = getToolWorkspaceRoot(context)
|
|
86
|
-
const workspaceReal = await fs.realpath(workspaceRoot)
|
|
87
85
|
let targetReal
|
|
88
86
|
try {
|
|
89
87
|
targetReal = await fs.realpath(fullPath)
|
|
@@ -104,6 +102,16 @@ export async function assertSafeWorkspacePath(fullPath, context, options = {}) {
|
|
|
104
102
|
}
|
|
105
103
|
}
|
|
106
104
|
|
|
105
|
+
export async function createWorkspacePathValidator(context) {
|
|
106
|
+
const workspaceReal = await fs.realpath(getToolWorkspaceRoot(context))
|
|
107
|
+
return (fullPath, options = {}) => assertSafeWorkspacePathWithRoot(fullPath, context, workspaceReal, options)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function assertSafeWorkspacePath(fullPath, context, options = {}) {
|
|
111
|
+
const validateWorkspacePath = await createWorkspacePathValidator(context)
|
|
112
|
+
return validateWorkspacePath(fullPath, options)
|
|
113
|
+
}
|
|
114
|
+
|
|
107
115
|
export function truncateText(text, maxChars = 50000) {
|
|
108
116
|
if (text.length <= maxChars) return text
|
|
109
117
|
return `${text.slice(0, maxChars)}\n\n[truncated ${text.length - maxChars} characters]`
|
|
@@ -134,12 +142,25 @@ export async function assertDirectory(dir) {
|
|
|
134
142
|
const SIZE_SKIP_DIRS = new Set(['.git', 'node_modules', 'dist', 'dist-ssr', '.vite', '.cache', '.next', '.nuxt', '__pycache__', '.venv', 'venv'])
|
|
135
143
|
const directorySizeCache = new Map()
|
|
136
144
|
const DIRECTORY_SIZE_CACHE_TTL_MS = 10_000
|
|
145
|
+
const DIRECTORY_SIZE_CACHE_MAX_ENTRIES = 10_000
|
|
146
|
+
|
|
147
|
+
function pruneDirectorySizeCache(now = Date.now()) {
|
|
148
|
+
for (const [key, cached] of directorySizeCache) {
|
|
149
|
+
if (now - cached.ts >= DIRECTORY_SIZE_CACHE_TTL_MS) directorySizeCache.delete(key)
|
|
150
|
+
}
|
|
151
|
+
while (directorySizeCache.size > DIRECTORY_SIZE_CACHE_MAX_ENTRIES) {
|
|
152
|
+
const oldestKey = directorySizeCache.keys().next().value
|
|
153
|
+
if (oldestKey === undefined) break
|
|
154
|
+
directorySizeCache.delete(oldestKey)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
137
157
|
|
|
138
158
|
export async function directorySize(dir) {
|
|
139
159
|
try {
|
|
140
160
|
const now = Date.now()
|
|
141
161
|
const cached = directorySizeCache.get(dir)
|
|
142
162
|
if (cached && now - cached.ts < DIRECTORY_SIZE_CACHE_TTL_MS) return cached.size
|
|
163
|
+
if (cached) directorySizeCache.delete(dir)
|
|
143
164
|
|
|
144
165
|
const entries = await fs.readdir(dir, { withFileTypes: true })
|
|
145
166
|
const sizes = await Promise.all(entries.map(async (entry) => {
|
|
@@ -151,6 +172,7 @@ export async function directorySize(dir) {
|
|
|
151
172
|
}))
|
|
152
173
|
const size = sizes.reduce((sum, value) => sum + value, 0)
|
|
153
174
|
directorySizeCache.set(dir, { size, ts: now })
|
|
175
|
+
if (directorySizeCache.size > DIRECTORY_SIZE_CACHE_MAX_ENTRIES) pruneDirectorySizeCache(now)
|
|
154
176
|
return size
|
|
155
177
|
} catch {
|
|
156
178
|
return 0
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{i as e}from"./rolldown-runtime-DWdDZTNf.js";import{E as t,Ft as n,Mt as r,Ot as i,a,c as o,it as ee}from"./icons-pPRMD2tE.js";import{i as s,n as c}from"./react-vendor-CLbWF1Oy.js";import{$ as te,Q as l,V as ne,Z as re,et as ie,lt as u,mt as d,st as f,tt as ae}from"./index-BSXpUDCq.js";var p=e(n(),1),oe=s(),m=c(),h=144,g=82,_=4,v=8;function y(e){return JSON.stringify({provider:e.provider,modelId:e.id,api:e.api,baseUrl:e.baseUrl})}function b(e){if(!e)return{mode:`inherit`};try{let t=JSON.parse(e);return{mode:`fixed`,provider:String(t.provider||``),modelId:String(t.modelId||``),api:t.api?String(t.api):void 0,baseUrl:t.baseUrl?String(t.baseUrl):void 0}}catch{return{mode:`inherit`}}}function x(e){return!e||e.mode!==`fixed`?``:JSON.stringify({provider:e.provider,modelId:e.modelId,api:e.api,baseUrl:e.baseUrl})}function se(e){return e.name||`${e.provider}/${e.id}`}function S(){return{name:``,label:``,description:``,systemPrompt:``,allowedTools:[`read_file`,`grep_files`],maxRuntimeMs:`1800000`,maxToolCalls:`300`,enabledAsSubagent:!0,modelMode:`inherit`,fixedModelValue:``,thinkingLevel:`inherit`}}function ce(e){return{name:e.name,label:e.label,description:e.description??``,systemPrompt:e.systemPrompt??``,allowedTools:e.allowedTools??[],maxRuntimeMs:String(e.maxRuntimeMs??18e5),maxToolCalls:String(e.maxToolCalls??300),enabledAsSubagent:e.enabledAsSubagent,modelMode:e.model?.mode===`fixed`?`fixed`:`inherit`,fixedModelValue:x(e.model),thinkingLevel:e.thinkingLevel??`inherit`}}function le(e){return{name:e.name.trim().toLowerCase(),label:e.label.trim(),description:e.description.trim(),systemPrompt:e.systemPrompt.trim(),allowedTools:e.allowedTools,maxRuntimeMs:Number(e.maxRuntimeMs||18e5),maxToolCalls:Number(e.maxToolCalls||300),enabledAsSubagent:e.enabledAsSubagent,model:e.modelMode===`fixed`?b(e.fixedModelValue):{mode:`inherit`},thinkingLevel:e.thinkingLevel}}function C(e){return!!(e.name.trim()&&e.label.trim()&&e.allowedTools.length>0)}async function w(e,t){let n=await fetch(e,{...t,headers:{"content-type":`application/json`,...t?.headers}}),r=await n.json().catch(()=>null);if(!n.ok)throw Error(r?.error||`请求失败`);return r}function T(){let[e,n]=(0,p.useState)([]),[s,c]=(0,p.useState)([]),[x,T]=(0,p.useState)(!1),[E,D]=(0,p.useState)(null),[O,k]=(0,p.useState)(()=>S()),[A,j]=(0,p.useState)(!1),[M,N]=(0,p.useState)(``),[P,F]=(0,p.useState)(!1),[I,ue]=(0,p.useState)(),[L,de]=(0,p.useState)([]),[fe,pe]=(0,p.useState)(`off`),[R,z]=(0,p.useState)(``),[B,V]=(0,p.useState)(null),[H,U]=(0,p.useState)(null);async function W(){let[e,t]=await Promise.all([w(`/api/agent-profiles`),w(`/api/agent-profiles/available-tools`)]);n(e.agents),c(t.tools)}(0,p.useEffect)(()=>{let e=!1;async function t(){try{let[t,r]=await Promise.all([w(`/api/agent-profiles`),w(`/api/agent-profiles/available-tools`)]);if(e)return;n(t.agents),c(r.tools)}catch(t){e||z(t instanceof Error?t.message:d(`requestFailed`))}}return t(),()=>{e=!0}},[]),(0,p.useEffect)(()=>{let e=!1;async function t(){try{let t=await te(),n=await l(t);de(n);let r=await ie(t),i=r.model??await ae(t)??n[0];if(e)return;ue(i),pe(r.thinkingLevel??re(i))}catch{}}return t(),()=>{e=!0}},[]),(0,p.useEffect)(()=>{if(!B)return;let e=()=>{V(null),U(null)},t=t=>{t.key===`Escape`&&e()};return window.addEventListener(`click`,e),window.addEventListener(`blur`,e),window.addEventListener(`resize`,e),window.addEventListener(`scroll`,e,!0),document.addEventListener(`keydown`,t),()=>{window.removeEventListener(`click`,e),window.removeEventListener(`blur`,e),window.removeEventListener(`resize`,e),window.removeEventListener(`scroll`,e,!0),document.removeEventListener(`keydown`,t)}},[B]);let G=(0,p.useMemo)(()=>e.find(e=>e.id===E)??null,[e,E]),K=(0,p.useMemo)(()=>e.find(e=>e.id===B)??null,[e,B]),q=!!G?.readonly,J=!!(G?.readonly&&!G?.builtin),Y=(0,p.useMemo)(()=>L.find(e=>y(e)===O.fixedModelValue),[O.fixedModelValue,L]),X=O.modelMode===`fixed`&&!!Y&&Y?.reasoning!==!0;function me(e,t){if(e.stopPropagation(),B===t){V(null),U(null);return}let n=e.currentTarget.getBoundingClientRect(),r=Math.max(v,Math.min(n.right-h,window.innerWidth-h-v)),i=n.bottom+_,a=n.top-_-g;U({left:r,top:i+g<=window.innerHeight-v?i:Math.max(v,a)}),V(t)}function Z(e,t){k(n=>({...n,[e]:t}))}function he(e){k(t=>({...t,allowedTools:t.allowedTools.includes(e)?t.allowedTools.filter(t=>t!==e):[...t.allowedTools,e]}))}function ge(){D(null),k(S()),N(``),z(``),T(!0)}function Q(e){D(e.id),k(ce(e)),N(``),z(``),T(!0)}function $(){A||P||(T(!1),D(null),k(S()),N(``))}async function _e(){let e=M.trim();if(!e){z(d(`aiFillAgentInputRequired`));return}if(!I){z(d(`aiFillAgentNoModel`));return}F(!0),z(``);try{let t=await w(`/api/agent-profiles/ai-fill`,{method:`POST`,body:JSON.stringify({instruction:e,model:I,thinkingLevel:fe})});k(e=>({...e,name:t.agent.name,label:t.agent.label,description:t.agent.description,systemPrompt:t.agent.systemPrompt}))}catch(e){z(e instanceof Error?e.message:d(`aiFillAgentFailed`))}finally{F(!1)}}async function ve(){if(C(O)){j(!0),z(``);try{let e=G?.builtin?{model:O.modelMode===`fixed`?b(O.fixedModelValue):{mode:`inherit`}}:le({...O,thinkingLevel:X?`off`:O.thinkingLevel});E?await w(`/api/agent-profiles/${encodeURIComponent(E)}`,{method:`PATCH`,body:JSON.stringify(e)}):await w(`/api/agent-profiles`,{method:`POST`,body:JSON.stringify(e)}),$(),await W()}catch(e){z(e instanceof Error?e.message:d(`requestFailed`))}finally{j(!1)}}}async function ye(e){if(e.builtin||e.readonly)return;let t=!e.enabledAsSubagent,r=e.enabledAsSubagent;n(n=>n.map(n=>n.id===e.id?{...n,enabledAsSubagent:t}:n)),V(null);try{await w(`/api/agent-profiles/${encodeURIComponent(e.id)}`,{method:`PATCH`,body:JSON.stringify({enabledAsSubagent:t})})}catch(t){n(t=>t.map(t=>t.id===e.id?{...t,enabledAsSubagent:r}:t)),z(t instanceof Error?t.message:d(`requestFailed`))}}async function be(e){if(!(e.builtin||e.readonly)&&await ne({description:d(`confirmDeleteAgent`),confirmLabel:d(`confirmDelete`),cancelLabel:d(`cancel`),variant:`destructive`})){z(``);try{await w(`/api/agent-profiles/${encodeURIComponent(e.id)}`,{method:`DELETE`}),await W()}catch(e){z(e instanceof Error?e.message:d(`requestFailed`))}}}return x?(0,m.jsxs)(`div`,{className:`quickforge-settings-stack`,children:[(0,m.jsx)(`div`,{className:`quickforge-settings-heading`,children:(0,m.jsxs)(`h3`,{className:`quickforge-settings-title`,children:[G?.builtin?d(`builtinAgentModelSettings`):d(G?`editAgent`:`createAgent`),(0,m.jsx)(f,{label:G?.builtin?d(`builtinAgentModelOnly`):G?.readonly?d(`readonlyAgentDescription`):d(`agentsDescription`)})]})}),(0,m.jsxs)(`section`,{className:`quickforge-settings-section`,"aria-label":d(G?`editAgent`:`createAgent`),children:[(0,m.jsxs)(`div`,{className:`quickforge-settings-toolbar`,children:[(0,m.jsxs)(`button`,{className:`quickforge-settings-button quickforge-settings-button-secondary`,type:`button`,onClick:$,disabled:A||P,children:[(0,m.jsx)(r,{className:`mr-2 size-4`}),d(`back`)]}),(0,m.jsxs)(`div`,{className:`quickforge-settings-row-main`,children:[(0,m.jsx)(`div`,{className:`quickforge-settings-row-title`,children:G?.builtin?d(`builtinAgentModelSettings`):d(G?`editAgent`:`createAgent`)}),G?.builtin?(0,m.jsx)(`div`,{className:`quickforge-settings-row-description`,children:d(`builtinAgentModelOnly`)}):G?.readonly?(0,m.jsx)(`div`,{className:`quickforge-settings-row-description`,children:d(`readonlyAgentDescription`)}):null]})]}),(0,m.jsx)(`div`,{className:`px-5 py-4`,children:(0,m.jsxs)(`div`,{className:`space-y-4`,children:[(0,m.jsxs)(`div`,{className:`rounded-2xl border border-border bg-muted/20 p-3`,children:[(0,m.jsxs)(`div`,{className:`mb-2 flex items-center gap-2 text-sm font-medium text-foreground`,children:[(0,m.jsx)(o,{className:`size-4 text-primary`}),d(`aiFillAgent`),(0,m.jsx)(f,{label:d(`aiFillAgentDescription`)})]}),(0,m.jsx)(`textarea`,{className:`min-h-20 w-full resize-y rounded-xl border border-input bg-background px-3 py-2 text-sm outline-none transition-colors placeholder:text-muted-foreground/65 focus:border-ring disabled:opacity-60`,value:M,disabled:q||P,onChange:e=>N(e.target.value),placeholder:d(`aiFillAgentPlaceholder`)}),(0,m.jsx)(`div`,{className:`mt-2 flex justify-end`,children:(0,m.jsxs)(u,{variant:`outline`,size:`sm`,onClick:()=>void _e(),disabled:q||P||!M.trim(),children:[(0,m.jsx)(o,{className:`mr-1 size-3.5`}),d(P?`aiFillAgentLoading`:`aiFillAgent`)]})})]}),(0,m.jsxs)(`div`,{className:`grid gap-3 sm:grid-cols-2`,children:[(0,m.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[d(`agentName`),(0,m.jsx)(`input`,{className:`mt-1 h-10 w-full rounded-md border border-input bg-background px-3 text-sm outline-none focus:border-ring disabled:opacity-60`,value:O.name,disabled:q,onChange:e=>Z(`name`,e.target.value),placeholder:`reviewer`})]}),(0,m.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[d(`agentLabel`),(0,m.jsx)(`input`,{className:`mt-1 h-10 w-full rounded-md border border-input bg-background px-3 text-sm outline-none focus:border-ring disabled:opacity-60`,value:O.label,disabled:q,onChange:e=>Z(`label`,e.target.value),placeholder:d(`agentLabelPlaceholder`)})]})]}),G?(0,m.jsxs)(`div`,{className:`rounded-xl border border-border bg-muted/20 px-3 py-2 text-sm`,children:[(0,m.jsx)(`div`,{className:`text-xs font-medium text-muted-foreground`,children:d(`agentSourcePath`)}),(0,m.jsx)(`div`,{className:`mt-1 truncate font-mono text-xs text-foreground`,title:G.source?`${G.source}${G.relativePath?` · ${G.relativePath}`:``}`:void 0,children:G.source?`${G.source}${G.relativePath?` · ${G.relativePath}`:``}`:G.builtin?d(`builtinAgent`):`-`})]}):null,(0,m.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[d(`agentDescription`),(0,m.jsx)(`input`,{className:`mt-1 h-10 w-full rounded-md border border-input bg-background px-3 text-sm outline-none focus:border-ring disabled:opacity-60`,value:O.description,disabled:q,onChange:e=>Z(`description`,e.target.value)})]}),(0,m.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[d(`agentSystemPrompt`),(0,m.jsx)(`textarea`,{className:`mt-1 min-h-36 w-full resize-y rounded-xl border border-input bg-background px-3 py-2 text-sm outline-none focus:border-ring disabled:opacity-60`,value:O.systemPrompt,disabled:q,onChange:e=>Z(`systemPrompt`,e.target.value)})]}),(0,m.jsxs)(`div`,{className:`grid gap-3 sm:grid-cols-2`,children:[(0,m.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[d(`agentModelMode`),(0,m.jsxs)(`select`,{className:`mt-1 h-10 w-full rounded-md border border-input bg-background px-3 text-sm outline-none focus:border-ring disabled:opacity-60`,value:O.modelMode,disabled:J,onChange:e=>Z(`modelMode`,e.target.value),children:[(0,m.jsx)(`option`,{value:`inherit`,children:d(`agentModelInherit`)}),(0,m.jsx)(`option`,{value:`fixed`,children:d(`agentModelFixed`)})]})]}),(0,m.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[d(`agentFixedModel`),(0,m.jsxs)(`select`,{className:`mt-1 h-10 w-full rounded-md border border-input bg-background px-3 text-sm outline-none focus:border-ring disabled:opacity-60`,value:O.fixedModelValue,disabled:J||O.modelMode!==`fixed`,onChange:e=>Z(`fixedModelValue`,e.target.value),children:[(0,m.jsx)(`option`,{value:``,children:d(`agentModelInherit`)}),L.map(e=>(0,m.jsx)(`option`,{value:y(e),children:se(e)},y(e)))]})]})]}),(0,m.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[d(`agentThinkingLevel`),(0,m.jsxs)(`select`,{className:`mt-1 h-10 w-full rounded-md border border-input bg-background px-3 text-sm outline-none focus:border-ring disabled:opacity-60`,value:X&&O.thinkingLevel!==`inherit`?`off`:O.thinkingLevel,disabled:q||X,onChange:e=>Z(`thinkingLevel`,e.target.value),children:[(0,m.jsx)(`option`,{value:`inherit`,children:d(`agentThinkingInherit`)}),(0,m.jsx)(`option`,{value:`off`,children:d(`thinkingOff`)}),(0,m.jsx)(`option`,{value:`low`,children:d(`thinkingLow`)}),(0,m.jsx)(`option`,{value:`medium`,children:d(`thinkingMedium`)}),(0,m.jsx)(`option`,{value:`high`,children:d(`thinkingHigh`)}),(0,m.jsx)(`option`,{value:`xhigh`,children:d(`thinkingXHigh`)})]}),(0,m.jsx)(`span`,{className:`mt-1 block text-xs text-muted-foreground`,children:d(X?`agentThinkingUnsupported`:`agentThinkingDescription`)})]}),(0,m.jsxs)(`div`,{children:[(0,m.jsx)(`div`,{className:`mb-2 text-sm font-medium text-foreground`,children:d(`allowedTools`)}),(0,m.jsx)(`div`,{className:`grid gap-2 sm:grid-cols-2`,children:s.map(e=>(0,m.jsxs)(`label`,{className:`flex items-start gap-2 rounded-xl border border-border bg-muted/20 p-3 text-sm disabled:opacity-60`,children:[(0,m.jsx)(`input`,{type:`checkbox`,className:`mt-1`,disabled:q,checked:O.allowedTools.includes(e.name),onChange:()=>he(e.name)}),(0,m.jsxs)(`span`,{children:[(0,m.jsx)(`span`,{className:`font-medium text-foreground`,children:e.label}),(0,m.jsx)(`span`,{className:`ml-2 font-mono text-xs text-muted-foreground`,children:e.name}),e.riskLevel===`dangerous`?(0,m.jsx)(`span`,{className:`ml-2 rounded-full bg-amber-500/10 px-2 py-0.5 text-xs text-amber-700`,children:d(`highRiskTool`)}):null,(0,m.jsx)(`span`,{className:`mt-1 block text-xs text-muted-foreground`,children:e.description})]})]},e.name))})]}),(0,m.jsxs)(`div`,{className:`grid gap-3 sm:grid-cols-2`,children:[(0,m.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[d(`maxRuntimeMs`),(0,m.jsx)(`input`,{type:`number`,className:`mt-1 h-10 w-full rounded-md border border-input bg-background px-3 text-sm outline-none focus:border-ring disabled:opacity-60`,value:O.maxRuntimeMs,disabled:q,onChange:e=>Z(`maxRuntimeMs`,e.target.value)})]}),(0,m.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[d(`maxToolCalls`),(0,m.jsx)(`input`,{type:`number`,className:`mt-1 h-10 w-full rounded-md border border-input bg-background px-3 text-sm outline-none focus:border-ring disabled:opacity-60`,value:O.maxToolCalls,disabled:q,onChange:e=>Z(`maxToolCalls`,e.target.value)})]})]}),(0,m.jsxs)(`label`,{className:`flex items-center gap-2 text-sm text-foreground`,children:[(0,m.jsx)(`input`,{type:`checkbox`,checked:O.enabledAsSubagent,disabled:q,onChange:e=>Z(`enabledAsSubagent`,e.target.checked)}),d(`enabledAsSubagent`)]}),R?(0,m.jsx)(`div`,{className:`rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive`,children:R}):null]})}),(0,m.jsxs)(`div`,{className:`quickforge-settings-divider flex justify-end gap-2 px-5 py-4`,children:[(0,m.jsx)(u,{variant:`outline`,onClick:$,disabled:A||P,children:d(`cancel`)}),(0,m.jsx)(u,{onClick:ve,disabled:A||P||J||!G?.builtin&&!C(O)||O.modelMode===`fixed`&&!O.fixedModelValue,children:d(`save`)})]})]})]}):(0,m.jsxs)(`div`,{className:`quickforge-settings-stack`,children:[(0,m.jsxs)(`section`,{className:`quickforge-settings-section`,"aria-label":d(`agentsTab`),children:[(0,m.jsxs)(`div`,{className:`quickforge-settings-toolbar`,children:[(0,m.jsxs)(`div`,{className:`quickforge-settings-row-main`,children:[(0,m.jsxs)(`div`,{className:`quickforge-settings-row-title`,children:[(0,m.jsx)(i,{className:`size-4 text-primary`}),d(`agentsTab`)]}),(0,m.jsx)(`div`,{className:`quickforge-settings-row-description`,children:d(`agentsDescription`)})]}),(0,m.jsx)(`button`,{className:`quickforge-settings-button quickforge-settings-button-primary`,type:`button`,onClick:ge,children:d(`createAgent`)})]}),R?(0,m.jsx)(`div`,{className:`quickforge-settings-alert quickforge-settings-warning-attached`,children:R}):null,e.length===0?(0,m.jsx)(`div`,{className:`quickforge-settings-empty-row`,children:d(`loading`)}):e.map(e=>(0,m.jsxs)(`div`,{className:`quickforge-settings-list-item quickforge-agent-profile-row`,role:`button`,tabIndex:0,onClick:()=>Q(e),onKeyDown:t=>{t.target===t.currentTarget&&(t.key===`Enter`||t.key===` `)&&(t.preventDefault(),Q(e))},children:[(0,m.jsx)(`div`,{className:`quickforge-settings-list-item-main quickforge-agent-profile-row-main`,children:(0,m.jsxs)(`div`,{className:`quickforge-agent-profile-summary`,children:[(0,m.jsx)(`span`,{className:`quickforge-agent-profile-label`,title:e.label,children:e.label}),e.description?(0,m.jsx)(`span`,{className:`quickforge-agent-profile-description`,title:e.description,children:e.description}):null]})}),(0,m.jsxs)(`div`,{className:`quickforge-settings-list-item-actions`,onClick:e=>e.stopPropagation(),children:[(0,m.jsxs)(`label`,{className:`quickforge-settings-switch`,"aria-disabled":e.builtin||e.readonly?`true`:`false`,title:e.enabledAsSubagent?d(`disableAsSubagent`):d(`enableAsSubagent`),children:[(0,m.jsx)(`input`,{type:`checkbox`,checked:e.enabledAsSubagent,disabled:e.builtin||e.readonly,onChange:()=>void ye(e)}),(0,m.jsx)(`span`,{"aria-hidden":`true`})]}),(0,m.jsx)(`button`,{className:`quickforge-settings-icon-action`,type:`button`,onClick:t=>me(t,e.id),title:d(`moreActions`),"aria-label":d(`moreActions`),"aria-haspopup":`menu`,"aria-expanded":B===e.id,children:(0,m.jsx)(ee,{className:`size-4`})})]})]},e.id))]}),K&&H?(0,oe.createPortal)((0,m.jsxs)(`div`,{className:`fixed z-50 w-36 overflow-hidden rounded-xl border border-border bg-popover py-1 text-sm shadow-quickforge`,style:{left:H.left,top:H.top},role:`menu`,"aria-label":d(`moreActions`),onClick:e=>e.stopPropagation(),children:[(0,m.jsxs)(`button`,{className:`flex w-full items-center gap-2 px-3 py-2 text-left hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50`,type:`button`,role:`menuitem`,disabled:K.readonly&&!K.builtin,onClick:()=>{V(null),U(null),Q(K)},children:[(0,m.jsx)(t,{className:`size-3.5`}),K.builtin?d(`builtinAgentModelSettings`):d(`editTask`)]}),(0,m.jsxs)(`button`,{className:`flex w-full items-center gap-2 px-3 py-2 text-left text-destructive hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50`,type:`button`,role:`menuitem`,disabled:K.builtin||K.readonly,onClick:()=>{V(null),U(null),be(K)},children:[(0,m.jsx)(a,{className:`size-3.5`}),d(`delete`)]})]}),document.body):null]})}export{T as AgentProfilesPage};
|