@shawnstack/quickforge 1.4.1 → 1.5.0
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/README.md +12 -12
- package/bin/quickforge.mjs +9 -0
- package/dist/assets/AgentProfilesPage-DUmXUxjA.js +1 -0
- package/dist/assets/ChatPanelHost-Syx0SSLe.js +242 -0
- package/dist/assets/PluginsPage-kiBq0gOT.js +1 -0
- package/dist/assets/ScheduledTasksPage-Dw4-tgp9.js +2 -0
- package/dist/assets/SharedConversationPage-CaE9bNb9.js +1 -0
- package/dist/assets/TerminalDock-BYJcp8Ts.js +2 -0
- package/dist/assets/WorkspaceInspector-Bzmv8Cvi.js +3 -0
- package/dist/assets/WorkspaceReaderDialog-BJo_KEWi.js +1 -0
- package/dist/assets/diff-line-counts-BZoYp5ai.js +10 -0
- package/dist/assets/icons-47L5YLKz.js +1 -0
- package/dist/assets/index-CqfScETb.js +1200 -0
- package/dist/assets/index-DzkBgHZf.css +3 -0
- package/dist/assets/{monaco-evITXh-m.js → monaco-CGq6uVF1.js} +1 -1
- package/dist/assets/{react-vendor-Mthyt1p4.js → react-vendor-DunfCFfp.js} +1 -1
- package/dist/favicon.svg +16 -1
- package/dist/index.html +5 -5
- package/dist/manifest.webmanifest +30 -30
- package/package.json +3 -2
- package/server/acp/server.mjs +921 -0
- package/server/agent-manager.mjs +198 -32
- package/server/agent-profile-files.mjs +179 -0
- package/server/agent-profiles.mjs +59 -5
- package/server/auto-compaction.mjs +82 -39
- package/server/channels/process-channel.mjs +278 -0
- package/server/channels/providers/wechat.mjs +271 -0
- package/server/channels/registry.mjs +58 -0
- package/server/custom-commands.mjs +13 -1
- package/server/frontmatter.mjs +167 -0
- package/server/index.mjs +52 -3
- package/server/project-config.mjs +43 -6
- package/server/routes/agent-profiles.mjs +6 -2
- package/server/routes/agent.mjs +12 -1
- package/server/routes/channels.mjs +145 -0
- package/server/routes/models.mjs +68 -0
- package/server/routes/project.mjs +2 -2
- package/server/routes/scheduled-tasks.mjs +6 -5
- package/server/routes/storage.mjs +4 -2
- package/server/routes/system.mjs +27 -0
- package/server/routes/tools.mjs +17 -6
- package/server/routes/workspace.mjs +138 -0
- package/server/session-utils.mjs +10 -2
- package/server/storage.mjs +29 -2
- package/server/system-prompt.mjs +1 -0
- package/server/tools/definitions.mjs +18 -0
- package/server/tools/index.mjs +83 -0
- package/server/utils/package-update.mjs +156 -0
- package/dist/assets/AgentProfilesPage-CNK5PxA3.js +0 -1
- package/dist/assets/ChatPanelHost-FqPQwwMO.js +0 -217
- package/dist/assets/PluginsPage-BCu1Ept0.js +0 -1
- package/dist/assets/ScheduledTasksPage-Bx04rjui.js +0 -2
- package/dist/assets/SharedConversationPage-55vX9sqe.js +0 -1
- package/dist/assets/TerminalDock-DLN_pLkJ.js +0 -2
- package/dist/assets/WorkspaceInspector-DoemHHnY.js +0 -3
- package/dist/assets/WorkspaceReaderDialog-C6xUHBCw.js +0 -6
- package/dist/assets/icons-BWtivFsx.js +0 -1
- package/dist/assets/index-CxOHP41X.css +0 -3
- package/dist/assets/index-Dcf73EL8.js +0 -895
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
} from '../utils/workspace.mjs'
|
|
13
13
|
|
|
14
14
|
const MAX_PREVIEW_BYTES = 1024 * 1024
|
|
15
|
+
const MAX_STATIC_PREVIEW_BYTES = 10 * 1024 * 1024
|
|
16
|
+
const PREVIEW_ALLOWED_EXTENSIONS = new Set(['.html', '.htm', '.css', '.js', '.mjs', '.json', '.svg', '.png', '.jpg', '.jpeg', '.webp', '.gif', '.ico', '.txt', '.md'])
|
|
15
17
|
const MAX_TREE_NODES = 5000
|
|
16
18
|
const SKIP_DIRS = new Set(['.git', 'node_modules', 'dist', 'dist-ssr', 'package-dist', 'package-offline', '.vite', 'coverage'])
|
|
17
19
|
|
|
@@ -34,6 +36,28 @@ function languageFromPath(filePath) {
|
|
|
34
36
|
return extensionLanguageMap.get(extension) || 'plaintext'
|
|
35
37
|
}
|
|
36
38
|
|
|
39
|
+
function previewContentType(filePath) {
|
|
40
|
+
const ext = path.extname(filePath).toLowerCase()
|
|
41
|
+
const map = {
|
|
42
|
+
'.html': 'text/html; charset=utf-8',
|
|
43
|
+
'.htm': 'text/html; charset=utf-8',
|
|
44
|
+
'.js': 'application/javascript; charset=utf-8',
|
|
45
|
+
'.mjs': 'application/javascript; charset=utf-8',
|
|
46
|
+
'.css': 'text/css; charset=utf-8',
|
|
47
|
+
'.json': 'application/json; charset=utf-8',
|
|
48
|
+
'.svg': 'image/svg+xml',
|
|
49
|
+
'.png': 'image/png',
|
|
50
|
+
'.jpg': 'image/jpeg',
|
|
51
|
+
'.jpeg': 'image/jpeg',
|
|
52
|
+
'.webp': 'image/webp',
|
|
53
|
+
'.gif': 'image/gif',
|
|
54
|
+
'.ico': 'image/x-icon',
|
|
55
|
+
'.txt': 'text/plain; charset=utf-8',
|
|
56
|
+
'.md': 'text/markdown; charset=utf-8',
|
|
57
|
+
}
|
|
58
|
+
return map[ext] || 'application/octet-stream'
|
|
59
|
+
}
|
|
60
|
+
|
|
37
61
|
function isBinaryBuffer(buffer) {
|
|
38
62
|
const length = Math.min(buffer.length, 8000)
|
|
39
63
|
for (let index = 0; index < length; index += 1) {
|
|
@@ -144,10 +168,71 @@ function countGitStatus(files) {
|
|
|
144
168
|
}, { staged: 0, unstaged: 0, untracked: 0, conflicts: 0, total: 0 })
|
|
145
169
|
}
|
|
146
170
|
|
|
171
|
+
function countTextLines(text) {
|
|
172
|
+
if (text.length === 0) return 0
|
|
173
|
+
return text.split('\n').length - (text.endsWith('\n') ? 1 : 0)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// numstat 的路径列对 rename 用 "prefix/{old => new}" 或 "old => new" 形式,取新路径
|
|
177
|
+
function numstatNewPath(rawPath) {
|
|
178
|
+
const arrow = rawPath.indexOf(' => ')
|
|
179
|
+
if (arrow < 0) return rawPath
|
|
180
|
+
const head = rawPath.slice(0, arrow)
|
|
181
|
+
const tail = rawPath.slice(arrow + 4)
|
|
182
|
+
const brace = head.lastIndexOf('{')
|
|
183
|
+
if (brace < 0) return tail
|
|
184
|
+
return `${head.slice(0, brace)}${tail.replace(/\}$/, '')}`
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 工作区 vs HEAD 的每个文件增删行数(口径与 git diff --numstat 一致)
|
|
188
|
+
async function collectNumstat(context) {
|
|
189
|
+
const map = new Map()
|
|
190
|
+
const result = await git(['diff', 'HEAD', '--numstat', '-z'], context.workspaceRoot, { allowFailure: true })
|
|
191
|
+
if (result.code !== 0) return map
|
|
192
|
+
const records = result.stdout.toString('utf8').split('\0').filter(Boolean)
|
|
193
|
+
for (const record of records) {
|
|
194
|
+
const fields = record.split('\t')
|
|
195
|
+
if (fields.length < 3) continue
|
|
196
|
+
const added = fields[0]
|
|
197
|
+
const removed = fields[1]
|
|
198
|
+
const rawPath = fields.slice(2).join('\t')
|
|
199
|
+
if (added === '-' || removed === '-') continue // 二进制文件
|
|
200
|
+
const additions = Number(added)
|
|
201
|
+
const deletions = Number(removed)
|
|
202
|
+
if (!Number.isFinite(additions) || !Number.isFinite(deletions)) continue
|
|
203
|
+
map.set(numstatNewPath(rawPath), { additions, deletions })
|
|
204
|
+
}
|
|
205
|
+
return map
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// 未跟踪文件不在 numstat 中,按工作区文件行数估算新增行
|
|
209
|
+
async function countWorkspaceLines(context, relativePath) {
|
|
210
|
+
try {
|
|
211
|
+
const { content } = await readWorkspaceTextFile(context, relativePath)
|
|
212
|
+
return countTextLines(content)
|
|
213
|
+
} catch {
|
|
214
|
+
return undefined
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
147
218
|
async function listGitStatus(context) {
|
|
148
219
|
if (!(await isGitRepository(context.workspaceRoot))) return { isGitRepository: false, files: [] }
|
|
149
220
|
const result = await git(['status', '--porcelain=v1', '-z'], context.workspaceRoot)
|
|
150
221
|
const files = parseGitStatus(result.stdout)
|
|
222
|
+
const numstat = await collectNumstat(context)
|
|
223
|
+
for (const file of files) {
|
|
224
|
+
const entry = numstat.get(file.path)
|
|
225
|
+
if (entry) {
|
|
226
|
+
file.additions = entry.additions
|
|
227
|
+
file.deletions = entry.deletions
|
|
228
|
+
} else if (file.status === 'untracked' || file.status === 'added') {
|
|
229
|
+
const count = await countWorkspaceLines(context, file.path)
|
|
230
|
+
if (typeof count === 'number') {
|
|
231
|
+
file.additions = count
|
|
232
|
+
file.deletions = 0
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
151
236
|
return {
|
|
152
237
|
isGitRepository: true,
|
|
153
238
|
branch: await currentGitBranch(context.workspaceRoot),
|
|
@@ -246,6 +331,55 @@ async function handleWorkspaceFile(req, res, url) {
|
|
|
246
331
|
})
|
|
247
332
|
}
|
|
248
333
|
|
|
334
|
+
async function handleWorkspacePreview(req, res, url) {
|
|
335
|
+
const prefix = '/api/workspace/preview/'
|
|
336
|
+
const tail = url.pathname.startsWith(prefix) ? url.pathname.slice(prefix.length) : ''
|
|
337
|
+
const slashIndex = tail.indexOf('/')
|
|
338
|
+
if (slashIndex <= 0) {
|
|
339
|
+
const error = new Error('projectId and path are required')
|
|
340
|
+
error.statusCode = 400
|
|
341
|
+
throw error
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const projectId = decodeURIComponent(tail.slice(0, slashIndex))
|
|
345
|
+
const relativePath = decodeURIComponent(tail.slice(slashIndex + 1))
|
|
346
|
+
if (!projectId || !relativePath) {
|
|
347
|
+
const error = new Error('projectId and path are required')
|
|
348
|
+
error.statusCode = 400
|
|
349
|
+
throw error
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const context = await projectContextFromId(projectId)
|
|
353
|
+
const file = resolveWorkspacePath(relativePath, context)
|
|
354
|
+
await assertSafeWorkspacePath(file, context)
|
|
355
|
+
const extension = path.extname(file).toLowerCase()
|
|
356
|
+
if (!PREVIEW_ALLOWED_EXTENSIONS.has(extension)) {
|
|
357
|
+
const error = new Error('Unsupported preview file type')
|
|
358
|
+
error.statusCode = 415
|
|
359
|
+
throw error
|
|
360
|
+
}
|
|
361
|
+
const stat = await fs.stat(file)
|
|
362
|
+
if (!stat.isFile()) {
|
|
363
|
+
const error = new Error('Path is not a file')
|
|
364
|
+
error.statusCode = 400
|
|
365
|
+
throw error
|
|
366
|
+
}
|
|
367
|
+
if (stat.size > MAX_STATIC_PREVIEW_BYTES) {
|
|
368
|
+
const error = new Error('File is too large to preview')
|
|
369
|
+
error.statusCode = 413
|
|
370
|
+
throw error
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const contentType = previewContentType(file)
|
|
374
|
+
res.writeHead(200, {
|
|
375
|
+
'content-type': contentType,
|
|
376
|
+
'cache-control': 'no-store',
|
|
377
|
+
'x-content-type-options': 'nosniff',
|
|
378
|
+
})
|
|
379
|
+
const buffer = await fs.readFile(file)
|
|
380
|
+
res.end(buffer)
|
|
381
|
+
}
|
|
382
|
+
|
|
249
383
|
async function handleWorkspaceResolvePath(req, res) {
|
|
250
384
|
const body = await readJsonBody(req, 16 * 1024)
|
|
251
385
|
const projectId = typeof body?.projectId === 'string' ? body.projectId : ''
|
|
@@ -344,6 +478,10 @@ export async function handleWorkspaceApi(req, res, url) {
|
|
|
344
478
|
await handleWorkspaceFile(req, res, url)
|
|
345
479
|
return
|
|
346
480
|
}
|
|
481
|
+
if (req.method === 'GET' && url.pathname.startsWith('/api/workspace/preview/')) {
|
|
482
|
+
await handleWorkspacePreview(req, res, url)
|
|
483
|
+
return
|
|
484
|
+
}
|
|
347
485
|
if (req.method === 'POST' && url.pathname === '/api/workspace/resolve-path') {
|
|
348
486
|
await handleWorkspaceResolvePath(req, res)
|
|
349
487
|
return
|
package/server/session-utils.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { streamSimple } from '@earendil-works/pi-ai'
|
|
2
|
-
import { buildInstructionsPayload } from './project-config.mjs'
|
|
2
|
+
import { buildInstructionsPayload, projectContextFromId } from './project-config.mjs'
|
|
3
3
|
import { composeSystemPrompt } from './system-prompt.mjs'
|
|
4
4
|
import { listSubagentProfiles } from './agent-profiles.mjs'
|
|
5
5
|
|
|
@@ -9,9 +9,17 @@ import { listSubagentProfiles } from './agent-profiles.mjs'
|
|
|
9
9
|
|
|
10
10
|
export async function buildSystemPrompt(projectId) {
|
|
11
11
|
const instructions = await buildInstructionsPayload(projectId)
|
|
12
|
+
let workspaceRoot = instructions.workspace?.root || null
|
|
13
|
+
if (projectId && !workspaceRoot) {
|
|
14
|
+
try {
|
|
15
|
+
workspaceRoot = (await projectContextFromId(projectId))?.workspaceRoot || null
|
|
16
|
+
} catch {
|
|
17
|
+
// project may have been removed; fall back to global agent profile discovery
|
|
18
|
+
}
|
|
19
|
+
}
|
|
12
20
|
return composeSystemPrompt({
|
|
13
21
|
...instructions,
|
|
14
|
-
subagents: await listSubagentProfiles(),
|
|
22
|
+
subagents: await listSubagentProfiles({ projectId, workspaceRoot }),
|
|
15
23
|
})
|
|
16
24
|
}
|
|
17
25
|
|
package/server/storage.mjs
CHANGED
|
@@ -468,6 +468,26 @@ async function rebuildBucketIndex() {
|
|
|
468
468
|
bucketIndexBuilt = true
|
|
469
469
|
}
|
|
470
470
|
|
|
471
|
+
async function findSessionBucketByDataFile(sessionId) {
|
|
472
|
+
assertSafePathSegment(sessionId)
|
|
473
|
+
const buckets = [
|
|
474
|
+
{ scope: 'global' },
|
|
475
|
+
...(await listProjectIds()).map((projectId) => ({ scope: 'project', projectId })),
|
|
476
|
+
]
|
|
477
|
+
|
|
478
|
+
for (const bucket of buckets) {
|
|
479
|
+
const file = sessionDataFile(sessionId, bucket)
|
|
480
|
+
try {
|
|
481
|
+
const stat = await fs.stat(file)
|
|
482
|
+
if (stat.isFile()) return bucket
|
|
483
|
+
} catch (error) {
|
|
484
|
+
if (error?.code !== 'ENOENT') throw error
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return null
|
|
489
|
+
}
|
|
490
|
+
|
|
471
491
|
export async function findSessionBucket(sessionId) {
|
|
472
492
|
if (!bucketIndexBuilt) {
|
|
473
493
|
await ensureStorage()
|
|
@@ -478,8 +498,12 @@ export async function findSessionBucket(sessionId) {
|
|
|
478
498
|
|
|
479
499
|
export async function readSessionValue(sessionId) {
|
|
480
500
|
const bucket = await findSessionBucket(sessionId)
|
|
481
|
-
if (
|
|
482
|
-
|
|
501
|
+
if (bucket) return readJsonFile(sessionDataFile(sessionId, bucket), null)
|
|
502
|
+
|
|
503
|
+
const recoveredBucket = await findSessionBucketByDataFile(sessionId)
|
|
504
|
+
if (!recoveredBucket) return null
|
|
505
|
+
sessionBucketIndex.set(sessionId, recoveredBucket)
|
|
506
|
+
return readJsonFile(sessionDataFile(sessionId, recoveredBucket), null)
|
|
483
507
|
}
|
|
484
508
|
|
|
485
509
|
export async function writeSessionValue(sessionId, value) {
|
|
@@ -725,6 +749,9 @@ export function ensureStorage() {
|
|
|
725
749
|
fs.mkdir(path.join(cacheDir, 'projects'), { recursive: true }),
|
|
726
750
|
fs.mkdir(path.join(storageDir, 'conversations', 'global', 'sessions'), { recursive: true }),
|
|
727
751
|
fs.mkdir(path.join(storageDir, 'conversations', 'projects'), { recursive: true }),
|
|
752
|
+
// Default workspace directory for global (non-project) conversations, so
|
|
753
|
+
// they share the same file-tool capabilities as project conversations.
|
|
754
|
+
fs.mkdir(path.join(dataDir, 'workspace'), { recursive: true }),
|
|
728
755
|
cleanOldLogs(),
|
|
729
756
|
])
|
|
730
757
|
|
package/server/system-prompt.mjs
CHANGED
|
@@ -5,6 +5,7 @@ For project tasks:
|
|
|
5
5
|
- Prefer the simplest solution that satisfies the request.
|
|
6
6
|
- Make surgical changes only. Do not refactor unrelated code.
|
|
7
7
|
- Match existing style.
|
|
8
|
+
- When content has room for visual explanation, first consider whether an SVG diagram can improve understanding.
|
|
8
9
|
- For multi-step work, use a brief plan.
|
|
9
10
|
- Before changing files, gather sufficient context: relevant files, entry points or call chains, existing patterns, tests or validation commands, and docs/wiki impact.
|
|
10
11
|
- Before taking action, confirm with the user.
|
|
@@ -86,6 +86,24 @@ export const workspaceTools = [
|
|
|
86
86
|
}),
|
|
87
87
|
executionMode: 'sequential',
|
|
88
88
|
},
|
|
89
|
+
{
|
|
90
|
+
name: 'present_files',
|
|
91
|
+
label: 'Present files',
|
|
92
|
+
description: 'Show one or more AI-produced artifact files to the user. Use this after creating or editing user-facing files such as HTML pages, SVG/images, Markdown documents, or other deliverables. HTML files will be previewed directly in the artifact preview panel.',
|
|
93
|
+
parameters: Type.Object({
|
|
94
|
+
files: Type.Array(Type.Union([
|
|
95
|
+
Type.String({ description: 'File path relative to the workspace root.' }),
|
|
96
|
+
Type.Object({
|
|
97
|
+
path: Type.String({ description: 'File path relative to the workspace root.' }),
|
|
98
|
+
title: Type.Optional(Type.String({ description: 'Optional display title.' })),
|
|
99
|
+
description: Type.Optional(Type.String({ description: 'Optional short description shown in artifact lists.' })),
|
|
100
|
+
kind: Type.Optional(Type.String({ description: 'Optional file kind hint, such as html, image, markdown, or code.' })),
|
|
101
|
+
preview: Type.Optional(Type.Boolean({ description: 'Whether this file should be opened as the default preview.' })),
|
|
102
|
+
}),
|
|
103
|
+
]), { description: 'Artifact files to present.' }),
|
|
104
|
+
defaultPreview: Type.Optional(Type.String({ description: 'File path to open as the default preview when multiple files are presented.' })),
|
|
105
|
+
}),
|
|
106
|
+
},
|
|
89
107
|
]
|
|
90
108
|
|
|
91
109
|
function activeSkillSchema(skills) {
|
package/server/tools/index.mjs
CHANGED
|
@@ -619,6 +619,88 @@ export async function toolReadSkillResource(params, context) {
|
|
|
619
619
|
}
|
|
620
620
|
}
|
|
621
621
|
|
|
622
|
+
// --- present_files ---
|
|
623
|
+
function inferPresentedFileKind(relativePath) {
|
|
624
|
+
const lower = String(relativePath || '').toLowerCase()
|
|
625
|
+
if (lower.endsWith('.html') || lower.endsWith('.htm')) return 'html'
|
|
626
|
+
if (/\.(svg|png|jpe?g|webp|gif|bmp|ico)$/.test(lower)) return 'image'
|
|
627
|
+
if (lower.endsWith('.md') || lower.endsWith('.mdx')) return 'markdown'
|
|
628
|
+
if (/\.(ts|tsx|js|jsx|mjs|cjs|css|scss|less|json|jsonc|txt|xml|yml|yaml|toml|ini|py|rb|go|rs|java|c|h|cpp|hpp|cs|php|sh|bash|zsh|ps1)$/.test(lower)) return 'code'
|
|
629
|
+
return 'unknown'
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function normalizePresentFileEntry(entry) {
|
|
633
|
+
if (typeof entry === 'string') return { path: entry }
|
|
634
|
+
if (entry && typeof entry === 'object' && typeof entry.path === 'string') {
|
|
635
|
+
return {
|
|
636
|
+
path: entry.path,
|
|
637
|
+
title: typeof entry.title === 'string' ? entry.title : undefined,
|
|
638
|
+
description: typeof entry.description === 'string' ? entry.description : undefined,
|
|
639
|
+
kind: typeof entry.kind === 'string' ? entry.kind : undefined,
|
|
640
|
+
preview: typeof entry.preview === 'boolean' ? entry.preview : undefined,
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
return undefined
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
export async function toolPresentFiles(params, context) {
|
|
647
|
+
const rawFiles = Array.isArray(params?.files) ? params.files : []
|
|
648
|
+
const defaultPreviewInput = typeof params?.defaultPreview === 'string' ? params.defaultPreview : undefined
|
|
649
|
+
if (rawFiles.length === 0) {
|
|
650
|
+
const error = new Error('files must contain at least one path')
|
|
651
|
+
error.statusCode = 400
|
|
652
|
+
throw error
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const files = []
|
|
656
|
+
const seen = new Set()
|
|
657
|
+
for (const raw of rawFiles) {
|
|
658
|
+
const entry = normalizePresentFileEntry(raw)
|
|
659
|
+
if (!entry?.path?.trim()) continue
|
|
660
|
+
const file = resolveWorkspacePath(entry.path, context)
|
|
661
|
+
await assertSafeWorkspacePath(file, context)
|
|
662
|
+
const stat = await fs.stat(file)
|
|
663
|
+
if (!stat.isFile()) {
|
|
664
|
+
const error = new Error(`Path is not a file: ${entry.path}`)
|
|
665
|
+
error.statusCode = 400
|
|
666
|
+
throw error
|
|
667
|
+
}
|
|
668
|
+
const relativePath = toWorkspaceRelative(file, context)
|
|
669
|
+
const key = relativePath.toLowerCase()
|
|
670
|
+
if (seen.has(key)) continue
|
|
671
|
+
seen.add(key)
|
|
672
|
+
const kind = entry.kind || inferPresentedFileKind(relativePath)
|
|
673
|
+
const isDefaultPreview = defaultPreviewInput ? relativePath === defaultPreviewInput.replace(/\\/g, '/') : false
|
|
674
|
+
files.push({
|
|
675
|
+
path: relativePath,
|
|
676
|
+
title: entry.title,
|
|
677
|
+
description: entry.description,
|
|
678
|
+
kind,
|
|
679
|
+
preview: entry.preview ?? (isDefaultPreview || kind === 'html'),
|
|
680
|
+
defaultPreview: isDefaultPreview,
|
|
681
|
+
bytes: stat.size,
|
|
682
|
+
})
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
if (files.length === 0) {
|
|
686
|
+
const error = new Error('No valid files to present')
|
|
687
|
+
error.statusCode = 400
|
|
688
|
+
throw error
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
const previewed = files.filter((file) => file.preview || file.kind === 'html').map((file) => file.path)
|
|
692
|
+
return {
|
|
693
|
+
content: `Presented ${files.length} file(s)${previewed.length ? ` and opened ${previewed.length} preview(s)` : ''}: ${files.map((file) => file.path).join(', ')}`,
|
|
694
|
+
details: {
|
|
695
|
+
type: 'present_files_result',
|
|
696
|
+
files,
|
|
697
|
+
defaultPreview: files.find((file) => file.defaultPreview)?.path,
|
|
698
|
+
previewed,
|
|
699
|
+
project: context?.project,
|
|
700
|
+
},
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
622
704
|
// --- run_command ---
|
|
623
705
|
const DEFAULT_RUN_COMMAND_TIMEOUT_MS = 30 * 60 * 1000
|
|
624
706
|
const MIN_RUN_COMMAND_TIMEOUT_MS = 1000
|
|
@@ -1009,6 +1091,7 @@ export const toolHandlers = {
|
|
|
1009
1091
|
write_file: toolWriteFile,
|
|
1010
1092
|
edit_file: toolEditFile,
|
|
1011
1093
|
run_command: toolRunCommand,
|
|
1094
|
+
present_files: toolPresentFiles,
|
|
1012
1095
|
activate_skill: toolActivateSkill,
|
|
1013
1096
|
read_skill_resource: toolReadSkillResource,
|
|
1014
1097
|
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process'
|
|
2
|
+
import { promises as fs } from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
|
|
5
|
+
function normalizeRepositoryUrl(value) {
|
|
6
|
+
if (!value || typeof value !== 'string') return ''
|
|
7
|
+
return value
|
|
8
|
+
.replace(/^git\+/i, '')
|
|
9
|
+
.replace(/^git@github\.com:/i, 'https://github.com/')
|
|
10
|
+
.replace(/\.git$/i, '')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function getPackageInfo(projectRoot) {
|
|
14
|
+
const packageJsonPath = path.join(projectRoot, 'package.json')
|
|
15
|
+
try {
|
|
16
|
+
const text = await fs.readFile(packageJsonPath, 'utf8')
|
|
17
|
+
const pkg = JSON.parse(text)
|
|
18
|
+
const repositoryUrl = normalizeRepositoryUrl(
|
|
19
|
+
typeof pkg.repository === 'string' ? pkg.repository : pkg.repository?.url,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
name: pkg.name || 'quickforge',
|
|
24
|
+
version: pkg.version || '0.0.0',
|
|
25
|
+
repositoryUrl,
|
|
26
|
+
homepage: pkg.homepage || repositoryUrl,
|
|
27
|
+
bugsUrl: typeof pkg.bugs === 'string' ? pkg.bugs : pkg.bugs?.url || '',
|
|
28
|
+
}
|
|
29
|
+
} catch (error) {
|
|
30
|
+
throw new Error(`Unable to read package metadata: ${error.message}`)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function normalizeVersion(version) {
|
|
35
|
+
return String(version || '').trim().replace(/^v/i, '')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function parseVersion(version) {
|
|
39
|
+
const [main, prerelease = ''] = normalizeVersion(version).split('-', 2)
|
|
40
|
+
const numbers = main.split('.').slice(0, 3).map((part) => {
|
|
41
|
+
const value = Number(part)
|
|
42
|
+
return Number.isFinite(value) ? value : 0
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
while (numbers.length < 3) numbers.push(0)
|
|
46
|
+
return { numbers, prerelease }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function comparePrerelease(left, right) {
|
|
50
|
+
if (left === right) return 0
|
|
51
|
+
if (!left) return 1
|
|
52
|
+
if (!right) return -1
|
|
53
|
+
|
|
54
|
+
const leftParts = left.split('.')
|
|
55
|
+
const rightParts = right.split('.')
|
|
56
|
+
const maxLength = Math.max(leftParts.length, rightParts.length)
|
|
57
|
+
|
|
58
|
+
for (let i = 0; i < maxLength; i += 1) {
|
|
59
|
+
const leftPart = leftParts[i]
|
|
60
|
+
const rightPart = rightParts[i]
|
|
61
|
+
if (leftPart === rightPart) continue
|
|
62
|
+
if (leftPart === undefined) return -1
|
|
63
|
+
if (rightPart === undefined) return 1
|
|
64
|
+
|
|
65
|
+
const leftNumber = /^\d+$/.test(leftPart) ? Number(leftPart) : null
|
|
66
|
+
const rightNumber = /^\d+$/.test(rightPart) ? Number(rightPart) : null
|
|
67
|
+
|
|
68
|
+
if (leftNumber !== null && rightNumber !== null) return leftNumber > rightNumber ? 1 : -1
|
|
69
|
+
if (leftNumber !== null) return -1
|
|
70
|
+
if (rightNumber !== null) return 1
|
|
71
|
+
|
|
72
|
+
return leftPart > rightPart ? 1 : -1
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return 0
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function compareVersions(left, right) {
|
|
79
|
+
const parsedLeft = parseVersion(left)
|
|
80
|
+
const parsedRight = parseVersion(right)
|
|
81
|
+
|
|
82
|
+
for (let i = 0; i < 3; i += 1) {
|
|
83
|
+
if (parsedLeft.numbers[i] > parsedRight.numbers[i]) return 1
|
|
84
|
+
if (parsedLeft.numbers[i] < parsedRight.numbers[i]) return -1
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return comparePrerelease(parsedLeft.prerelease, parsedRight.prerelease)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function getRegistryPackageUrl(packageName) {
|
|
91
|
+
const registry = (process.env.npm_config_registry || 'https://registry.npmjs.org/').replace(/\/+$/, '')
|
|
92
|
+
return `${registry}/${encodeURIComponent(packageName)}`
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function fetchLatestVersion(packageName) {
|
|
96
|
+
const controller = new AbortController()
|
|
97
|
+
const timeout = setTimeout(() => controller.abort(), 5000)
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const response = await fetch(getRegistryPackageUrl(packageName), {
|
|
101
|
+
headers: { accept: 'application/json' },
|
|
102
|
+
signal: controller.signal,
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
if (!response.ok) throw new Error(`registry returned HTTP ${response.status}`)
|
|
106
|
+
|
|
107
|
+
const metadata = await response.json()
|
|
108
|
+
const latest = metadata?.['dist-tags']?.latest
|
|
109
|
+
if (!latest || typeof latest !== 'string') throw new Error('latest version not found in registry response')
|
|
110
|
+
return latest
|
|
111
|
+
} catch (error) {
|
|
112
|
+
if (error.name === 'AbortError') throw new Error('request timeout')
|
|
113
|
+
throw error
|
|
114
|
+
} finally {
|
|
115
|
+
clearTimeout(timeout)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export async function checkForUpdates(projectRoot) {
|
|
120
|
+
const pkg = await getPackageInfo(projectRoot)
|
|
121
|
+
const latestVersion = await fetchLatestVersion(pkg.name)
|
|
122
|
+
const comparison = compareVersions(pkg.version, latestVersion)
|
|
123
|
+
return {
|
|
124
|
+
...pkg,
|
|
125
|
+
currentVersion: pkg.version,
|
|
126
|
+
latestVersion,
|
|
127
|
+
updateAvailable: comparison < 0,
|
|
128
|
+
localVersionIsNewer: comparison > 0,
|
|
129
|
+
installCommand: `npm install -g ${pkg.name}@latest`,
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function getNpmCommand() {
|
|
134
|
+
return process.platform === 'win32' ? 'npm.cmd' : 'npm'
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function installLatestVersion(packageName, options = {}) {
|
|
138
|
+
const target = `${packageName}@latest`
|
|
139
|
+
const child = spawn(getNpmCommand(), ['install', '-g', target], {
|
|
140
|
+
cwd: options.cwd,
|
|
141
|
+
stdio: 'ignore',
|
|
142
|
+
shell: process.platform === 'win32',
|
|
143
|
+
windowsHide: true,
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
return new Promise((resolve, reject) => {
|
|
147
|
+
child.on('error', reject)
|
|
148
|
+
child.on('exit', (code) => {
|
|
149
|
+
if (code === 0) {
|
|
150
|
+
resolve()
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
reject(new Error(`npm install exited with code ${code}`))
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{i as e}from"./rolldown-runtime-DWdDZTNf.js";import{ct as t,s as n,ut as r}from"./icons-BWtivFsx.js";import{n as i}from"./react-vendor-Mthyt1p4.js";import{E as a,O as o,S as s,T as c,b as l,v as u,x as d,y as f}from"./index-Dcf73EL8.js";var p=e(r(),1),m=i();function h(){return{name:``,label:``,description:``,systemPrompt:``,allowedTools:[`read_file`,`grep_files`],maxRuntimeMs:`1800000`,maxToolCalls:`300`,enabledAsSubagent:!0}}function g(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}}function _(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}}function v(e){return!!(e.name.trim()&&e.label.trim()&&e.allowedTools.length>0)}async function y(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 b(){let[e,r]=(0,p.useState)([]),[i,b]=(0,p.useState)([]),[x,S]=(0,p.useState)(!1),[C,w]=(0,p.useState)(null),[T,E]=(0,p.useState)(()=>h()),[D,O]=(0,p.useState)(!1),[k,A]=(0,p.useState)(``),[j,M]=(0,p.useState)(!1),[N,P]=(0,p.useState)(),[F,I]=(0,p.useState)(`off`),[L,R]=(0,p.useState)(``);async function z(){let[e,t]=await Promise.all([y(`/api/agent-profiles`),y(`/api/agent-profiles/available-tools`)]);r(e.agents),b(t.tools)}(0,p.useEffect)(()=>{let e=!1;async function t(){try{let[t,n]=await Promise.all([y(`/api/agent-profiles`),y(`/api/agent-profiles/available-tools`)]);if(e)return;r(t.agents),b(n.tools)}catch(t){e||R(t instanceof Error?t.message:o(`requestFailed`))}}return t(),()=>{e=!0}},[]),(0,p.useEffect)(()=>{let e=!1;async function t(){try{let t=await l(),n=await f(t),r=await d(t),i=r.model??await s(t)??n[0];if(e)return;P(i),I(r.thinkingLevel??u(i))}catch{}}return t(),()=>{e=!0}},[]);let B=(0,p.useMemo)(()=>e.find(e=>e.id===C)??null,[e,C]);function V(e,t){E(n=>({...n,[e]:t}))}function H(e){E(t=>({...t,allowedTools:t.allowedTools.includes(e)?t.allowedTools.filter(t=>t!==e):[...t.allowedTools,e]}))}function U(){w(null),E(h()),A(``),R(``),S(!0)}function W(e){w(e.id),E(g(e)),A(``),R(``),S(!0)}function G(){D||j||(S(!1),w(null),E(h()),A(``))}async function K(){let e=k.trim();if(!e){R(o(`aiFillAgentInputRequired`));return}if(!N){R(o(`aiFillAgentNoModel`));return}M(!0),R(``);try{let t=await y(`/api/agent-profiles/ai-fill`,{method:`POST`,body:JSON.stringify({instruction:e,model:N,thinkingLevel:F})});E(e=>({...e,name:t.agent.name,label:t.agent.label,description:t.agent.description,systemPrompt:t.agent.systemPrompt}))}catch(e){R(e instanceof Error?e.message:o(`aiFillAgentFailed`))}finally{M(!1)}}async function q(){if(v(T)){O(!0),R(``);try{let e=_(T);C?await y(`/api/agent-profiles/${encodeURIComponent(C)}`,{method:`PATCH`,body:JSON.stringify(e)}):await y(`/api/agent-profiles`,{method:`POST`,body:JSON.stringify(e)}),G(),await z()}catch(e){R(e instanceof Error?e.message:o(`requestFailed`))}finally{O(!1)}}}async function J(e){if(!e.builtin&&await c({description:o(`confirmDeleteAgent`),confirmLabel:o(`confirmDelete`),cancelLabel:o(`cancel`),variant:`destructive`})){R(``);try{await y(`/api/agent-profiles/${encodeURIComponent(e.id)}`,{method:`DELETE`}),await z()}catch(e){R(e instanceof Error?e.message:o(`requestFailed`))}}}return(0,m.jsxs)(`div`,{className:`flex min-h-0 flex-1 flex-col overflow-hidden bg-background`,children:[(0,m.jsx)(`div`,{className:`border-b border-border px-6 py-5`,children:(0,m.jsxs)(`div`,{className:`flex flex-wrap items-center justify-between gap-3`,children:[(0,m.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,m.jsx)(`div`,{className:`flex size-10 items-center justify-center rounded-2xl bg-primary/10 text-primary`,children:(0,m.jsx)(t,{className:`size-5`})}),(0,m.jsxs)(`div`,{children:[(0,m.jsx)(`h1`,{className:`text-lg font-semibold text-foreground`,children:o(`agentsTab`)}),(0,m.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:o(`agentsDescription`)})]})]}),(0,m.jsx)(a,{onClick:U,children:o(`createAgent`)})]})}),(0,m.jsx)(`div`,{className:`min-h-0 flex-1 overflow-y-auto p-6`,children:(0,m.jsxs)(`div`,{className:`mx-auto max-w-5xl space-y-5`,children:[L&&!x?(0,m.jsx)(`div`,{className:`rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive`,children:L}):null,(0,m.jsx)(`div`,{className:`grid gap-4 md:grid-cols-2`,children:e.map(e=>(0,m.jsxs)(`div`,{className:`rounded-2xl border border-border bg-card p-4 shadow-sm`,children:[(0,m.jsxs)(`div`,{className:`flex items-start justify-between gap-3`,children:[(0,m.jsxs)(`div`,{className:`min-w-0`,children:[(0,m.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[(0,m.jsx)(`h3`,{className:`truncate text-base font-semibold text-foreground`,children:e.label}),e.builtin?(0,m.jsx)(`span`,{className:`rounded-full bg-primary/10 px-2 py-0.5 text-xs text-primary`,children:o(`builtinAgent`)}):null,e.enabledAsSubagent?(0,m.jsx)(`span`,{className:`rounded-full bg-emerald-500/10 px-2 py-0.5 text-xs text-emerald-700`,children:o(`enabledAsSubagent`)}):null]}),(0,m.jsx)(`p`,{className:`mt-1 font-mono text-xs text-muted-foreground`,children:e.name}),(0,m.jsx)(`p`,{className:`mt-2 text-sm text-muted-foreground`,children:e.description||o(`noDescription`)})]}),(0,m.jsxs)(`div`,{className:`flex shrink-0 gap-1`,children:[(0,m.jsx)(a,{variant:`outline`,size:`sm`,disabled:e.builtin,onClick:()=>W(e),children:o(`editTask`)}),(0,m.jsx)(a,{variant:`destructive`,size:`sm`,disabled:e.builtin,onClick:()=>void J(e),children:o(`delete`)})]})]}),(0,m.jsx)(`div`,{className:`mt-3 flex flex-wrap gap-1`,children:e.allowedTools.map(e=>(0,m.jsx)(`span`,{className:`rounded-full bg-muted px-2 py-0.5 font-mono text-xs text-muted-foreground`,children:e},e))}),(0,m.jsxs)(`div`,{className:`mt-3 grid gap-2 border-t border-border pt-3 text-xs text-muted-foreground sm:grid-cols-2`,children:[(0,m.jsxs)(`span`,{children:[o(`maxRuntimeMs`),e.maxRuntimeMs??`-`]}),(0,m.jsxs)(`span`,{children:[o(`maxToolCalls`),e.maxToolCalls??`-`]})]})]},e.id))})]})}),x?(0,m.jsx)(`div`,{className:`fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4`,onMouseDown:e=>{e.target===e.currentTarget&&G()},children:(0,m.jsxs)(`div`,{className:`flex max-h-[90vh] w-full max-w-3xl flex-col overflow-hidden rounded-2xl border border-border bg-background shadow-2xl`,onMouseDown:e=>e.stopPropagation(),children:[(0,m.jsxs)(`div`,{className:`shrink-0 border-b border-border px-5 py-4`,children:[(0,m.jsx)(`h2`,{className:`text-base font-semibold text-foreground`,children:o(B?`editAgent`:`createAgent`)}),B?.builtin?(0,m.jsx)(`p`,{className:`mt-1 text-sm text-muted-foreground`,children:o(`builtinAgentReadonly`)}):null]}),(0,m.jsx)(`div`,{className:`min-h-0 flex-1 overflow-y-auto 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)(n,{className:`size-4 text-primary`}),o(`aiFillAgent`)]}),(0,m.jsx)(`p`,{className:`mb-2 text-xs text-muted-foreground`,children:o(`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:k,disabled:!!B?.builtin||j,onChange:e=>A(e.target.value),placeholder:o(`aiFillAgentPlaceholder`)}),(0,m.jsx)(`div`,{className:`mt-2 flex justify-end`,children:(0,m.jsxs)(a,{variant:`outline`,size:`sm`,onClick:()=>void K(),disabled:!!B?.builtin||j||!k.trim(),children:[(0,m.jsx)(n,{className:`mr-1 size-3.5`}),o(j?`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:[o(`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:T.name,disabled:!!B?.builtin,onChange:e=>V(`name`,e.target.value),placeholder:`reviewer`})]}),(0,m.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[o(`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:T.label,disabled:!!B?.builtin,onChange:e=>V(`label`,e.target.value),placeholder:o(`agentLabelPlaceholder`)})]})]}),(0,m.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[o(`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:T.description,disabled:!!B?.builtin,onChange:e=>V(`description`,e.target.value)})]}),(0,m.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[o(`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:T.systemPrompt,disabled:!!B?.builtin,onChange:e=>V(`systemPrompt`,e.target.value)})]}),(0,m.jsxs)(`div`,{children:[(0,m.jsx)(`div`,{className:`mb-2 text-sm font-medium text-foreground`,children:o(`allowedTools`)}),(0,m.jsx)(`div`,{className:`grid gap-2 sm:grid-cols-2`,children:i.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:!!B?.builtin,checked:T.allowedTools.includes(e.name),onChange:()=>H(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:o(`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:[o(`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:T.maxRuntimeMs,disabled:!!B?.builtin,onChange:e=>V(`maxRuntimeMs`,e.target.value)})]}),(0,m.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[o(`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:T.maxToolCalls,disabled:!!B?.builtin,onChange:e=>V(`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:T.enabledAsSubagent,disabled:!!B?.builtin,onChange:e=>V(`enabledAsSubagent`,e.target.checked)}),o(`enabledAsSubagent`)]}),L?(0,m.jsx)(`div`,{className:`rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive`,children:L}):null]})}),(0,m.jsx)(`div`,{className:`shrink-0 border-t border-border px-5 py-4`,children:(0,m.jsxs)(`div`,{className:`flex justify-end gap-2`,children:[(0,m.jsx)(a,{variant:`outline`,onClick:G,disabled:D||j,children:o(`cancel`)}),(0,m.jsx)(a,{onClick:q,disabled:D||j||!!B?.builtin||!v(T),children:o(`save`)})]})})]})}):null]})}export{b as AgentProfilesPage};
|