@shawnstack/quickforge 1.4.0 → 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-DG4TcBMc.js → monaco-CGq6uVF1.js} +1 -1
- package/dist/assets/{react-vendor-CiCXOLb5.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 +283 -45
- package/server/agent-profile-files.mjs +179 -0
- package/server/agent-profiles.mjs +59 -5
- package/server/approval-store.mjs +13 -1
- package/server/auto-compaction.mjs +111 -112
- 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/context-usage.mjs +108 -0
- package/server/custom-commands.mjs +157 -28
- package/server/frontmatter.mjs +167 -0
- package/server/index.mjs +52 -3
- package/server/mcp/registry.mjs +40 -0
- package/server/project-config.mjs +43 -6
- package/server/routes/agent-profiles.mjs +6 -2
- package/server/routes/agent.mjs +13 -2
- package/server/routes/channels.mjs +145 -0
- package/server/routes/mcp.mjs +7 -1
- package/server/routes/models.mjs +68 -0
- package/server/routes/project.mjs +34 -4
- package/server/routes/scheduled-tasks.mjs +6 -5
- package/server/routes/shared-conversation.mjs +1 -1
- 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 +30 -2
- package/server/subagents.mjs +8 -6
- package/server/system-prompt.mjs +3 -2
- package/server/tools/definitions.mjs +19 -1
- package/server/tools/index.mjs +83 -0
- package/server/utils/package-update.mjs +156 -0
- package/dist/assets/AgentProfilesPage-C79teCgh.js +0 -1
- package/dist/assets/ChatPanelHost-BjdIshtX.js +0 -195
- package/dist/assets/PluginsPage-Dt7Iiddo.js +0 -1
- package/dist/assets/ScheduledTasksPage-C047y3p3.js +0 -2
- package/dist/assets/SharedConversationPage-8X8kfztQ.js +0 -1
- package/dist/assets/TerminalDock-CEuJNf0m.js +0 -2
- package/dist/assets/WorkspaceInspector-BIa5gLVs.js +0 -3
- package/dist/assets/WorkspaceReaderDialog-bTeERaGd.js +0 -6
- package/dist/assets/icons-Dsc5yL3l.js +0 -1
- package/dist/assets/index-CPAWYhzz.css +0 -3
- package/dist/assets/index-YTL26wyJ.js +0 -814
|
@@ -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
|
@@ -94,6 +94,7 @@ export const configDir = path.join(dataDir, 'config')
|
|
|
94
94
|
export const storageDir = path.join(dataDir, 'storage')
|
|
95
95
|
export const cacheDir = path.join(dataDir, 'cache')
|
|
96
96
|
export const logsDir = path.join(dataDir, 'logs')
|
|
97
|
+
export const userCommandsDir = path.join(dataDir, 'commands')
|
|
97
98
|
|
|
98
99
|
const quickForgeConfigFile = path.join(configDir, 'config.json')
|
|
99
100
|
const configMigrationMarkerFile = path.join(configDir, '.layout-migrated')
|
|
@@ -467,6 +468,26 @@ async function rebuildBucketIndex() {
|
|
|
467
468
|
bucketIndexBuilt = true
|
|
468
469
|
}
|
|
469
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
|
+
|
|
470
491
|
export async function findSessionBucket(sessionId) {
|
|
471
492
|
if (!bucketIndexBuilt) {
|
|
472
493
|
await ensureStorage()
|
|
@@ -477,8 +498,12 @@ export async function findSessionBucket(sessionId) {
|
|
|
477
498
|
|
|
478
499
|
export async function readSessionValue(sessionId) {
|
|
479
500
|
const bucket = await findSessionBucket(sessionId)
|
|
480
|
-
if (
|
|
481
|
-
|
|
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)
|
|
482
507
|
}
|
|
483
508
|
|
|
484
509
|
export async function writeSessionValue(sessionId, value) {
|
|
@@ -724,6 +749,9 @@ export function ensureStorage() {
|
|
|
724
749
|
fs.mkdir(path.join(cacheDir, 'projects'), { recursive: true }),
|
|
725
750
|
fs.mkdir(path.join(storageDir, 'conversations', 'global', 'sessions'), { recursive: true }),
|
|
726
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 }),
|
|
727
755
|
cleanOldLogs(),
|
|
728
756
|
])
|
|
729
757
|
|
package/server/subagents.mjs
CHANGED
|
@@ -21,23 +21,23 @@ export const subagentDefinitions = [
|
|
|
21
21
|
name: 'general',
|
|
22
22
|
label: 'General',
|
|
23
23
|
mode: 'subagent',
|
|
24
|
-
description: 'A general-purpose agent for
|
|
24
|
+
description: 'A general-purpose agent for bounded complex multi-step implementation or broader independent work. It has full built-in workspace tool access, excluding MCP tools and Agent Skills, so it can modify files when needed. Prefer Explore for focused read-only repository discovery, source search, call-chain lookup, tests/docs discovery, and impact analysis.',
|
|
25
25
|
allowedTools: ['read_file', 'grep_files', 'write_file', 'edit_file', 'run_command'],
|
|
26
26
|
allowFileMutations: true,
|
|
27
27
|
maxRuntimeMs: 30 * 60 * 1000,
|
|
28
28
|
maxToolCalls: 300,
|
|
29
|
-
systemPrompt: `You are General, a general-purpose subagent for complex
|
|
29
|
+
systemPrompt: `You are General, a general-purpose subagent for bounded complex multi-step implementation tasks and broader independent work. You may inspect, edit, write files, and run commands using the built-in workspace tools when needed. You do not have MCP tools or Agent Skills. Prefer Explore for focused read-only repository discovery, source search, call-chain lookup, tests/docs discovery, and impact analysis. Make focused, minimal changes that satisfy the delegated task, and verify your changes when appropriate.`,
|
|
30
30
|
},
|
|
31
31
|
{
|
|
32
32
|
name: 'explore',
|
|
33
33
|
label: 'Explore',
|
|
34
34
|
mode: 'subagent',
|
|
35
|
-
description: '
|
|
36
|
-
allowedTools: ['read_file', 'grep_files'],
|
|
35
|
+
description: 'The preferred subagent for focused read-only repository exploration, file discovery, source search, call-chain lookup, related tests/docs/wiki discovery, safe inspection commands, pattern lookup, and impact analysis before non-trivial implementation. It cannot modify files.',
|
|
36
|
+
allowedTools: ['read_file', 'grep_files', 'run_command'],
|
|
37
37
|
allowFileMutations: false,
|
|
38
38
|
maxRuntimeMs: 30 * 60 * 1000,
|
|
39
39
|
maxToolCalls: 300,
|
|
40
|
-
systemPrompt: `You are Explore,
|
|
40
|
+
systemPrompt: `You are Explore, the preferred read-only repository exploration subagent. Use read_file, grep_files, and safe read-only run_command calls to locate files, inspect project structure, search source, trace call chains, find related tests/docs/wiki pages, run diagnostics, identify patterns, assess impact, and answer focused questions before non-trivial implementation. You cannot modify files.`,
|
|
41
41
|
},
|
|
42
42
|
]
|
|
43
43
|
|
|
@@ -80,7 +80,9 @@ export function composeSubagentSystemPrompt({ definition, parentSystemPrompt, pr
|
|
|
80
80
|
'- run_subagent is not available to subagents.',
|
|
81
81
|
definition.allowFileMutations
|
|
82
82
|
? '- File modification tools are available when needed, subject to the parent session approval/YOLO policy.'
|
|
83
|
-
:
|
|
83
|
+
: definition.allowedTools.includes('run_command')
|
|
84
|
+
? '- This subagent is read-only. Do not modify files. Use run_command only for safe inspection or diagnostic commands.'
|
|
85
|
+
: '- This subagent is read-only. Do not modify files or run commands.',
|
|
84
86
|
workspaceLines.length ? `\nWorkspace context:\n${workspaceLines.join('\n')}` : '',
|
|
85
87
|
'</subagent_instructions>',
|
|
86
88
|
].filter(Boolean).join('\n')
|
package/server/system-prompt.mjs
CHANGED
|
@@ -5,10 +5,11 @@ 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.
|
|
11
|
-
- Unless the change is trivial and localized to an already-known file, use Explore first for read-only repository research; prefer Explore for broad searches, pattern lookup, impact analysis, and locating related tests, docs, or build scripts.
|
|
12
|
+
- Unless the change is trivial and localized to an already-known file, use Explore first for read-only repository research before implementation decisions; prefer Explore for file discovery, source location, broad searches, call-chain lookup, pattern lookup, impact analysis, and locating related tests, docs, wiki pages, or build scripts.
|
|
12
13
|
- For complex multi-step work, use General only for bounded assistance; the parent assistant remains responsible for final decisions, minimal edits, and verification.
|
|
13
14
|
- Make minimal, focused changes.
|
|
14
15
|
- Prefer dedicated workspace tools for reading, editing, and searching files.
|
|
@@ -63,7 +64,7 @@ function appendSubagentCatalog(parts, subagents) {
|
|
|
63
64
|
const subagentParts = subagents.map(formatSubagentCatalogItem)
|
|
64
65
|
parts.push(`
|
|
65
66
|
<available_subagents>
|
|
66
|
-
The run_subagent tool can delegate work to an enabled temporary Agent Profile.
|
|
67
|
+
The run_subagent tool can delegate work to an enabled temporary Agent Profile. Prefer Explore for focused read-only repository discovery before implementation decisions, including locating files, searching source, tracing call chains, finding related tests/docs/wiki pages, and impact analysis. Use General for bounded complex multi-step implementation or broader independent work; custom profiles may also be available when enabled.
|
|
67
68
|
|
|
68
69
|
Choose the most appropriate subagent by name, keep delegation concrete, and include relevant context. Treat subagent output as advisory; you remain responsible for the final answer.
|
|
69
70
|
|
|
@@ -15,7 +15,7 @@ import { loadSelectedGlobalSkills, loadSelectedProjectSkills, mergeSkills } from
|
|
|
15
15
|
export const subagentTool = {
|
|
16
16
|
name: 'run_subagent',
|
|
17
17
|
label: 'Run subagent',
|
|
18
|
-
description: 'Delegate a bounded task to an enabled temporary Agent Profile.
|
|
18
|
+
description: 'Delegate a bounded task to an enabled temporary Agent Profile. Prefer explore for focused read-only repository discovery before implementation decisions, including locating files, searching source, tracing call chains, finding related tests/docs/wiki pages, and impact analysis. Use general for bounded complex multi-step implementation or broader independent work. Custom profiles can also be enabled as subagents. Subagents are short-lived and do not receive MCP or Agent Skill tools.',
|
|
19
19
|
parameters: Type.Object({
|
|
20
20
|
subagent: Type.String({ description: 'Agent Profile name to invoke.' }),
|
|
21
21
|
task: Type.String({ description: 'Concrete, bounded task for the subagent. Do not delegate vague or open-ended work.' }),
|
|
@@ -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{it as t,nt as n,s as r}from"./icons-Dsc5yL3l.js";import{n as i}from"./react-vendor-CiCXOLb5.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-YTL26wyJ.js";var p=e(t(),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,t]=(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,n]=await Promise.all([y(`/api/agent-profiles`),y(`/api/agent-profiles/available-tools`)]);t(e.agents),b(n.tools)}(0,p.useEffect)(()=>{let e=!1;async function n(){try{let[n,r]=await Promise.all([y(`/api/agent-profiles`),y(`/api/agent-profiles/available-tools`)]);if(e)return;t(n.agents),b(r.tools)}catch(t){e||R(t instanceof Error?t.message:o(`requestFailed`))}}return n(),()=>{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)(n,{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)(r,{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)(r,{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};
|