@shawnstack/quickforge 1.7.0 → 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-B8bHMwfv.js → PluginsPage-DEMqwhOA.js} +1 -1
- package/dist/assets/ScheduledTasksPage-DTP_gedp.js +2 -0
- package/dist/assets/{SettingsWorkspacePage-BOP9CtoI.js → SettingsWorkspacePage-Dr9zT5Bs.js} +112 -112
- package/dist/assets/SharedConversationPage-op3DRwcw.js +1 -0
- package/dist/assets/TerminalDock-BL_UovwU.js +2 -0
- 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-C13M09wD.js → monaco-BTsVCDWS.js} +1 -1
- package/dist/assets/{react-vendor-2RKYr-A4.js → react-vendor-Dr5xvL-e.js} +1 -1
- package/dist/assets/{skills-dialog-MpoxntiV.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/backup.mjs +79 -72
- package/server/routes/mcp.mjs +0 -5
- package/server/routes/scheduled-tasks.mjs +72 -32
- package/server/routes/workspace.mjs +171 -49
- package/server/utils/scheduled-tasks.mjs +23 -10
- package/server/utils/workspace.mjs +25 -3
- package/dist/assets/AgentProfilesPage-DQ8URegq.js +0 -1
- package/dist/assets/ChatPanelHost-vi1tDqlJ.js +0 -291
- package/dist/assets/ScheduledTasksPage-AaUz5el2.js +0 -2
- package/dist/assets/SharedConversationPage-9A4L9UcQ.js +0 -1
- package/dist/assets/TerminalDock-DQOXpaDe.js +0 -2
- package/dist/assets/WorkspaceInspector-DaM7TJHb.js +0 -13
- package/dist/assets/icons-ko_i0WpN.js +0 -1
- package/dist/assets/index-B2eauKpo.css +0 -3
- package/dist/assets/index-B8Awq5FV.js +0 -63
- package/dist/assets/mcp-servers-dialog-XE4K8PCO.js +0 -5
|
@@ -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,14 +299,76 @@ 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()
|
|
316
|
+
}
|
|
317
|
+
}
|
|
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
|
+
}
|
|
304
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))
|
|
305
372
|
}
|
|
306
373
|
|
|
307
374
|
export async function listGitStatus(context) {
|
|
@@ -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
|
|
|
@@ -662,53 +732,105 @@ async function handleWorkspaceFile(req, res, url) {
|
|
|
662
732
|
})
|
|
663
733
|
}
|
|
664
734
|
|
|
665
|
-
|
|
666
|
-
const
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
735
|
+
function createWorkspacePreviewError(message, statusCode, previewCode) {
|
|
736
|
+
const error = new Error(message)
|
|
737
|
+
error.statusCode = statusCode
|
|
738
|
+
error.previewCode = previewCode
|
|
739
|
+
return error
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
export function workspacePreviewIssueFromError(error, requestedPath = '') {
|
|
743
|
+
let status = error?.statusCode || 500
|
|
744
|
+
let code = error?.previewCode || 'PREVIEW_SERVICE_FAILED'
|
|
745
|
+
|
|
746
|
+
if (error?.code === 'ENOENT' || error?.code === 'ENOTDIR') {
|
|
747
|
+
status = 404
|
|
748
|
+
code = 'PREVIEW_FILE_NOT_FOUND'
|
|
749
|
+
} else if (error?.name === 'URIError') {
|
|
750
|
+
status = 400
|
|
751
|
+
code = 'PREVIEW_INVALID_PATH'
|
|
752
|
+
} else if (error?.code === 'EACCES' || error?.code === 'EPERM') {
|
|
753
|
+
status = 403
|
|
754
|
+
code = 'PREVIEW_PERMISSION_DENIED'
|
|
755
|
+
} else if (status === 403 && !error?.previewCode) {
|
|
756
|
+
code = 'PREVIEW_PERMISSION_DENIED'
|
|
757
|
+
} else if (status === 400 && !error?.previewCode) {
|
|
758
|
+
code = 'PREVIEW_INVALID_PATH'
|
|
673
759
|
}
|
|
674
760
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
761
|
+
return {
|
|
762
|
+
status,
|
|
763
|
+
payload: {
|
|
764
|
+
error: error?.message || 'Internal server error',
|
|
765
|
+
code,
|
|
766
|
+
path: requestedPath,
|
|
767
|
+
},
|
|
681
768
|
}
|
|
769
|
+
}
|
|
682
770
|
|
|
683
|
-
|
|
771
|
+
export async function inspectWorkspacePreviewFile(context, relativePath) {
|
|
684
772
|
const file = resolveWorkspacePath(relativePath, context)
|
|
685
773
|
await assertSafeWorkspacePath(file, context)
|
|
686
774
|
const extension = path.extname(file).toLowerCase()
|
|
687
775
|
if (!PREVIEW_ALLOWED_EXTENSIONS.has(extension)) {
|
|
688
|
-
|
|
689
|
-
error.statusCode = 415
|
|
690
|
-
throw error
|
|
776
|
+
throw createWorkspacePreviewError('Unsupported preview file type', 415, 'PREVIEW_UNSUPPORTED_TYPE')
|
|
691
777
|
}
|
|
778
|
+
|
|
692
779
|
const stat = await fs.stat(file)
|
|
693
780
|
if (!stat.isFile()) {
|
|
694
|
-
|
|
695
|
-
error.statusCode = 400
|
|
696
|
-
throw error
|
|
781
|
+
throw createWorkspacePreviewError('Path is not a file', 400, 'PREVIEW_INVALID_PATH')
|
|
697
782
|
}
|
|
698
783
|
if (stat.size > MAX_STATIC_PREVIEW_BYTES) {
|
|
699
|
-
|
|
700
|
-
error.statusCode = 413
|
|
701
|
-
throw error
|
|
784
|
+
throw createWorkspacePreviewError('File is too large to preview', 413, 'PREVIEW_FILE_TOO_LARGE')
|
|
702
785
|
}
|
|
703
786
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
787
|
+
return {
|
|
788
|
+
file,
|
|
789
|
+
stat,
|
|
790
|
+
contentType: previewContentType(file),
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
async function handleWorkspacePreview(req, res, url) {
|
|
795
|
+
let relativePath = ''
|
|
796
|
+
try {
|
|
797
|
+
const prefix = '/api/workspace/preview/'
|
|
798
|
+
const tail = url.pathname.startsWith(prefix) ? url.pathname.slice(prefix.length) : ''
|
|
799
|
+
const slashIndex = tail.indexOf('/')
|
|
800
|
+
if (slashIndex <= 0) {
|
|
801
|
+
throw createWorkspacePreviewError('projectId and path are required', 400, 'PREVIEW_INVALID_PATH')
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
const projectId = decodeURIComponent(tail.slice(0, slashIndex))
|
|
805
|
+
relativePath = decodeURIComponent(tail.slice(slashIndex + 1))
|
|
806
|
+
if (!projectId || !relativePath) {
|
|
807
|
+
throw createWorkspacePreviewError('projectId and path are required', 400, 'PREVIEW_INVALID_PATH')
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
const context = await projectContextFromId(projectId)
|
|
811
|
+
const preview = await inspectWorkspacePreviewFile(context, relativePath)
|
|
812
|
+
if (url.searchParams.get('__quickforge_check') === '1') {
|
|
813
|
+
sendJson(res, 200, {
|
|
814
|
+
ok: true,
|
|
815
|
+
path: relativePath,
|
|
816
|
+
size: preview.stat.size,
|
|
817
|
+
contentType: preview.contentType,
|
|
818
|
+
})
|
|
819
|
+
return
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
res.writeHead(200, {
|
|
823
|
+
'content-type': preview.contentType,
|
|
824
|
+
'cache-control': 'no-store',
|
|
825
|
+
'x-content-type-options': 'nosniff',
|
|
826
|
+
})
|
|
827
|
+
const buffer = await fs.readFile(preview.file)
|
|
828
|
+
res.end(buffer)
|
|
829
|
+
} catch (error) {
|
|
830
|
+
const issue = workspacePreviewIssueFromError(error, relativePath)
|
|
831
|
+
if (issue.status >= 500) logger.error('Workspace preview failed', { error: issue.payload.error, path: relativePath })
|
|
832
|
+
sendJson(res, issue.status, issue.payload)
|
|
833
|
+
}
|
|
712
834
|
}
|
|
713
835
|
|
|
714
836
|
async function handleWorkspaceResolvePath(req, res) {
|
|
@@ -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{Ot as t,Q as n,St as r,T as i,Tt as a,a as ee,c as o}from"./icons-ko_i0WpN.js";import{i as s,n as c}from"./react-vendor-2RKYr-A4.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-B8Awq5FV.js";var p=e(t(),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,t]=(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,n]=await Promise.all([w(`/api/agent-profiles`),w(`/api/agent-profiles/available-tools`)]);t(e.agents),c(n.tools)}(0,p.useEffect)(()=>{let e=!1;async function n(){try{let[n,r]=await Promise.all([w(`/api/agent-profiles`),w(`/api/agent-profiles/available-tools`)]);if(e)return;t(n.agents),c(r.tools)}catch(t){e||z(t instanceof Error?t.message:d(`requestFailed`))}}return n(),()=>{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 n=!e.enabledAsSubagent,r=e.enabledAsSubagent;t(t=>t.map(t=>t.id===e.id?{...t,enabledAsSubagent:n}:t)),V(null);try{await w(`/api/agent-profiles/${encodeURIComponent(e.id)}`,{method:`PATCH`,body:JSON.stringify({enabledAsSubagent:n})})}catch(n){t(t=>t.map(t=>t.id===e.id?{...t,enabledAsSubagent:r}:t)),z(n instanceof Error?n.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)(a,{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)(r,{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)(n,{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)(i,{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)(ee,{className:`size-3.5`}),d(`delete`)]})]}),document.body):null]})}export{T as AgentProfilesPage};
|