@shawnstack/quickforge 1.6.9 → 1.6.11
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 +452 -452
- package/dist/assets/AgentProfilesPage-CvoB0OId.js +1 -0
- package/dist/assets/ChatPanelHost-Cyi1yi_P.js +244 -0
- package/dist/assets/{PluginsPage-C5OClNdT.js → PluginsPage-B_4nlPca.js} +1 -1
- package/dist/assets/{ScheduledTasksPage-yT10oaaM.js → ScheduledTasksPage-Zbsuo8o6.js} +2 -2
- package/dist/assets/{SettingsWorkspacePage-DuXwIeLd.js → SettingsWorkspacePage-BtgkwIKN.js} +358 -293
- package/dist/assets/{SharedConversationPage-ClpUSa0F.js → SharedConversationPage-B5FS3eSl.js} +1 -1
- package/dist/assets/TerminalDock-CiJCmyqg.js +2 -0
- package/dist/assets/WorkspaceInspector-C9HyQIvF.js +13 -0
- package/dist/assets/index-BFKildY3.js +63 -0
- package/dist/assets/index-CsKSKUn-.css +3 -0
- package/dist/assets/{mcp-servers-dialog-DC2Zfojn.js → mcp-servers-dialog-BPk4a4nW.js} +2 -2
- package/dist/assets/{skills-dialog-Mnj8fZwq.js → skills-dialog-DcxeFWSt.js} +1 -1
- package/dist/index.html +4 -4
- package/package.json +1 -1
- package/server/agent-manager.mjs +45 -3
- package/server/approval-store.mjs +4 -0
- package/server/global-memory.mjs +168 -0
- package/server/index.mjs +7 -0
- package/server/project-config.mjs +3 -0
- package/server/routes/backup.mjs +135 -48
- package/server/routes/memory.mjs +26 -0
- package/server/routes/tools.mjs +5 -3
- package/server/routes/workspace.mjs +56 -0
- package/server/system-prompt.mjs +22 -0
- package/server/tools/definitions.mjs +14 -0
- package/server/tools/index.mjs +6 -0
- package/server/utils/platform.mjs +4 -4
- package/dist/assets/AgentProfilesPage-5eqU0Cjk.js +0 -1
- package/dist/assets/ChatPanelHost-BNFeNRy-.js +0 -244
- package/dist/assets/TerminalDock-BmL-FeKv.js +0 -2
- package/dist/assets/WorkspaceInspector-yH23pXpj.js +0 -13
- package/dist/assets/index-Qof73j0o.css +0 -3
- package/dist/assets/index-_ut7awrd.js +0 -63
package/server/routes/backup.mjs
CHANGED
|
@@ -18,12 +18,17 @@ const BACKUP_VERSION = 1
|
|
|
18
18
|
const BACKUP_APP = 'quickforge'
|
|
19
19
|
const IMPORT_UPLOAD_MAX_BYTES = Number(process.env.QUICKFORGE_IMPORT_UPLOAD_MAX_BYTES || 1024 * 1024 * 1024)
|
|
20
20
|
const backupScopes = new Set(['all', 'config', 'sessions'])
|
|
21
|
-
const
|
|
21
|
+
const settingsSectionIds = ['settings', 'mcp', 'providerKeys', 'customProviders', 'projects', 'scheduledTasks']
|
|
22
|
+
const exportSectionIds = new Set(settingsSectionIds)
|
|
23
|
+
const restoreSectionIds = new Set([...settingsSectionIds, 'conversations'])
|
|
22
24
|
const restoreModes = new Set(['replace', 'merge'])
|
|
23
25
|
|
|
24
26
|
function normalizeMode(value) {
|
|
25
27
|
const mode = String(value || 'replace')
|
|
26
|
-
|
|
28
|
+
if (restoreModes.has(mode)) return mode
|
|
29
|
+
const error = new Error(`Invalid restore mode: ${mode}`)
|
|
30
|
+
error.statusCode = 400
|
|
31
|
+
throw error
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
function normalizeScope(value) {
|
|
@@ -36,25 +41,27 @@ function parseBoolean(value) {
|
|
|
36
41
|
return text === '1' || text === 'true' || text === 'yes'
|
|
37
42
|
}
|
|
38
43
|
|
|
39
|
-
function
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (backup && typeof backup === 'object' && !Array.isArray(backup)) {
|
|
46
|
-
if (backup.data && typeof backup.data === 'object' && !Array.isArray(backup.data)) {
|
|
47
|
-
backup.scope = 'config'
|
|
48
|
-
return backup
|
|
49
|
-
}
|
|
50
|
-
return {
|
|
51
|
-
app: backup.app ?? BACKUP_APP,
|
|
52
|
-
version: backup.version ?? BACKUP_VERSION,
|
|
53
|
-
exportedAt: backup.exportedAt ?? null,
|
|
54
|
-
scope: 'config',
|
|
55
|
-
includeSecrets: backup.includeSecrets === true,
|
|
56
|
-
data: backup,
|
|
44
|
+
function extractSettingsBackupFromText(text) {
|
|
45
|
+
let ignoredConversations = false
|
|
46
|
+
const backup = JSON.parse(text, (key, value) => {
|
|
47
|
+
if (key === 'sessions' || key === 'sessionsMetadata' || key === 'sessions-metadata') {
|
|
48
|
+
ignoredConversations = true
|
|
49
|
+
return undefined
|
|
57
50
|
}
|
|
51
|
+
return value
|
|
52
|
+
})
|
|
53
|
+
if (backup && typeof backup === 'object' && !Array.isArray(backup)) {
|
|
54
|
+
const normalized = backup.data && typeof backup.data === 'object' && !Array.isArray(backup.data)
|
|
55
|
+
? { ...backup, scope: 'config' }
|
|
56
|
+
: {
|
|
57
|
+
app: backup.app ?? BACKUP_APP,
|
|
58
|
+
version: backup.version ?? BACKUP_VERSION,
|
|
59
|
+
exportedAt: backup.exportedAt ?? null,
|
|
60
|
+
scope: 'config',
|
|
61
|
+
includeSecrets: backup.includeSecrets === true,
|
|
62
|
+
data: backup,
|
|
63
|
+
}
|
|
64
|
+
return { backup: normalized, ignoredConversations }
|
|
58
65
|
}
|
|
59
66
|
const error = new Error('Invalid backup file')
|
|
60
67
|
error.statusCode = 400
|
|
@@ -92,9 +99,22 @@ async function readPendingImportBackup(token) {
|
|
|
92
99
|
throw error
|
|
93
100
|
}
|
|
94
101
|
const file = path.join(storageDir, 'pending-imports', `${token}.json`)
|
|
95
|
-
|
|
102
|
+
try {
|
|
103
|
+
return JSON.parse(await fs.readFile(file, 'utf8'))
|
|
104
|
+
} catch (cause) {
|
|
105
|
+
if (cause?.code === 'ENOENT') {
|
|
106
|
+
const error = new Error('Import preview has expired. Please select the backup file again.')
|
|
107
|
+
error.statusCode = 400
|
|
108
|
+
throw error
|
|
109
|
+
}
|
|
110
|
+
throw cause
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function deletePendingImportBackup(token) {
|
|
115
|
+
if (!/^[0-9a-f-]{36}$/i.test(String(token || ''))) return
|
|
116
|
+
const file = path.join(storageDir, 'pending-imports', `${token}.json`)
|
|
96
117
|
await fs.rm(file, { force: true })
|
|
97
|
-
return backup
|
|
98
118
|
}
|
|
99
119
|
|
|
100
120
|
function backupTimestamp(date = new Date()) {
|
|
@@ -170,30 +190,48 @@ function normalizeSessionMetadata(sessions, metadata) {
|
|
|
170
190
|
return nextMetadata
|
|
171
191
|
}
|
|
172
192
|
|
|
193
|
+
function normalizeExportSections(value) {
|
|
194
|
+
if (value === null || value === undefined || value === '') return null
|
|
195
|
+
const items = Array.isArray(value) ? value : String(value).split(',')
|
|
196
|
+
const selected = new Set()
|
|
197
|
+
for (const item of items) {
|
|
198
|
+
const id = String(item).trim()
|
|
199
|
+
if (!exportSectionIds.has(id)) {
|
|
200
|
+
const error = new Error(`Invalid export section: ${id}`)
|
|
201
|
+
error.statusCode = 400
|
|
202
|
+
throw error
|
|
203
|
+
}
|
|
204
|
+
selected.add(id)
|
|
205
|
+
}
|
|
206
|
+
if (selected.size === 0) {
|
|
207
|
+
const error = new Error('No export sections selected')
|
|
208
|
+
error.statusCode = 400
|
|
209
|
+
throw error
|
|
210
|
+
}
|
|
211
|
+
return selected
|
|
212
|
+
}
|
|
213
|
+
|
|
173
214
|
async function buildBackup(scope = 'all', options = {}) {
|
|
174
215
|
const normalizedScope = normalizeScope(scope)
|
|
175
|
-
const
|
|
176
|
-
const
|
|
177
|
-
const
|
|
216
|
+
const selected = normalizeExportSections(options.sections)
|
|
217
|
+
const includeConfig = selected ? true : normalizedScope === 'all' || normalizedScope === 'config'
|
|
218
|
+
const includeSessions = selected ? false : normalizedScope === 'all' || normalizedScope === 'sessions'
|
|
219
|
+
const includeSecrets = selected ? selected.has('providerKeys') : Boolean(options.includeSecrets && includeConfig)
|
|
178
220
|
const data = {}
|
|
179
221
|
|
|
180
222
|
if (includeConfig) {
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
readStore('
|
|
184
|
-
|
|
185
|
-
readStore('
|
|
186
|
-
|
|
187
|
-
|
|
223
|
+
const shouldInclude = (id) => !selected || selected.has(id)
|
|
224
|
+
const entries = await Promise.all([
|
|
225
|
+
shouldInclude('settings') ? readStore('settings').then((value) => ['settings', value]) : null,
|
|
226
|
+
shouldInclude('mcp') ? readStore('mcp').then((value) => ['mcp', value]) : null,
|
|
227
|
+
shouldInclude('providerKeys') ? readStore('provider-keys').then((value) => ['providerKeys', value]) : null,
|
|
228
|
+
shouldInclude('customProviders') ? readStore('custom-providers').then((value) => ['customProviders', value]) : null,
|
|
229
|
+
shouldInclude('projects') ? readProjectConfigData().then((value) => ['projects', value]) : null,
|
|
230
|
+
shouldInclude('scheduledTasks') ? readStore('scheduled-tasks').then((value) => ['scheduledTasks', value]) : null,
|
|
188
231
|
])
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
customProviders,
|
|
193
|
-
projects,
|
|
194
|
-
scheduledTasks,
|
|
195
|
-
})
|
|
196
|
-
if (includeSecrets) data.providerKeys = providerKeys
|
|
232
|
+
for (const entry of entries) {
|
|
233
|
+
if (entry) data[entry[0]] = entry[1]
|
|
234
|
+
}
|
|
197
235
|
}
|
|
198
236
|
|
|
199
237
|
if (includeSessions) {
|
|
@@ -211,7 +249,8 @@ async function buildBackup(scope = 'all', options = {}) {
|
|
|
211
249
|
app: BACKUP_APP,
|
|
212
250
|
version: BACKUP_VERSION,
|
|
213
251
|
exportedAt: new Date().toISOString(),
|
|
214
|
-
scope: normalizedScope,
|
|
252
|
+
scope: selected ? 'config' : normalizedScope,
|
|
253
|
+
exportedSections: selected ? settingsSectionIds.filter((id) => selected.has(id)) : undefined,
|
|
215
254
|
includeSecrets,
|
|
216
255
|
data,
|
|
217
256
|
}
|
|
@@ -267,6 +306,42 @@ function normalizeBackupPayload(payload) {
|
|
|
267
306
|
}
|
|
268
307
|
}
|
|
269
308
|
|
|
309
|
+
function validateSettingsImportBackup(payload) {
|
|
310
|
+
const normalized = normalizeBackupPayload(payload)
|
|
311
|
+
const validSections = {}
|
|
312
|
+
const invalidSections = {}
|
|
313
|
+
|
|
314
|
+
for (const id of settingsSectionIds) {
|
|
315
|
+
const value = normalized.sections[id]
|
|
316
|
+
if (value === undefined) continue
|
|
317
|
+
try {
|
|
318
|
+
validSections[id] = id === 'projects'
|
|
319
|
+
? assertProjectConfig(value)
|
|
320
|
+
: assertObjectSection(value, id)
|
|
321
|
+
} catch (error) {
|
|
322
|
+
invalidSections[id] = error instanceof Error ? error.message : `Invalid backup section: ${id}`
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (Object.keys(validSections).length === 0) {
|
|
327
|
+
const error = new Error('Backup does not contain any valid settings sections')
|
|
328
|
+
error.statusCode = 400
|
|
329
|
+
throw error
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
backup: {
|
|
334
|
+
app: normalized.app,
|
|
335
|
+
version: normalized.version,
|
|
336
|
+
exportedAt: normalized.exportedAt,
|
|
337
|
+
scope: 'config',
|
|
338
|
+
includeSecrets: normalized.includeSecrets,
|
|
339
|
+
data: validSections,
|
|
340
|
+
},
|
|
341
|
+
invalidSections,
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
270
345
|
function validateBackupPayload(payload) {
|
|
271
346
|
const backup = normalizeBackupPayload(payload)
|
|
272
347
|
const { sections } = backup
|
|
@@ -349,11 +424,20 @@ function backupWithSelectedSections(backup, selected) {
|
|
|
349
424
|
|
|
350
425
|
function parseImportPayload(body) {
|
|
351
426
|
const payload = body?.backup && typeof body.backup === 'object' ? body.backup : body
|
|
352
|
-
const
|
|
427
|
+
const normalized = normalizeBackupPayload(payload)
|
|
353
428
|
const requestedSections = body?.backup && typeof body === 'object' ? body.sections : undefined
|
|
354
|
-
const selected = normalizeRestoreSections(requestedSections,
|
|
429
|
+
const selected = normalizeRestoreSections(requestedSections, normalized.sections)
|
|
430
|
+
const filtered = selected ? backupWithSelectedSections(normalized, selected) : normalized
|
|
431
|
+
const backup = validateBackupPayload({
|
|
432
|
+
app: filtered.app,
|
|
433
|
+
version: filtered.version,
|
|
434
|
+
exportedAt: filtered.exportedAt,
|
|
435
|
+
scope: filtered.scope,
|
|
436
|
+
includeSecrets: filtered.includeSecrets,
|
|
437
|
+
data: filtered.sections,
|
|
438
|
+
})
|
|
355
439
|
const mode = normalizeMode(body?.mode)
|
|
356
|
-
return { backup
|
|
440
|
+
return { backup, mode }
|
|
357
441
|
}
|
|
358
442
|
|
|
359
443
|
function countKeys(value) {
|
|
@@ -494,6 +578,7 @@ export async function handleBackupApi(req, res, url) {
|
|
|
494
578
|
if (req.method === 'GET' && url.pathname === '/api/backup/export') {
|
|
495
579
|
await ensureStorage()
|
|
496
580
|
sendJson(res, 200, await buildBackup(url.searchParams.get('scope'), {
|
|
581
|
+
sections: url.searchParams.get('sections'),
|
|
497
582
|
includeSecrets: parseBoolean(url.searchParams.get('includeSecrets')),
|
|
498
583
|
}))
|
|
499
584
|
return
|
|
@@ -509,10 +594,11 @@ export async function handleBackupApi(req, res, url) {
|
|
|
509
594
|
if (req.method === 'POST' && url.pathname === '/api/backup/inspect-file') {
|
|
510
595
|
await ensureStorage()
|
|
511
596
|
const text = await readTextBody(req)
|
|
512
|
-
const backup =
|
|
513
|
-
const
|
|
514
|
-
const
|
|
515
|
-
|
|
597
|
+
const { backup, ignoredConversations } = extractSettingsBackupFromText(text)
|
|
598
|
+
const { backup: validBackup, invalidSections } = validateSettingsImportBackup(backup)
|
|
599
|
+
const inspect = inspectBackup(validBackup)
|
|
600
|
+
const token = await writePendingImportBackup(validBackup)
|
|
601
|
+
sendJson(res, 200, { ...inspect, invalidSections, ignoredConversations, importToken: token })
|
|
516
602
|
return
|
|
517
603
|
}
|
|
518
604
|
|
|
@@ -523,6 +609,7 @@ export async function handleBackupApi(req, res, url) {
|
|
|
523
609
|
const { backup, mode } = parseImportPayload(importBody)
|
|
524
610
|
const safetyBackupPath = await writeSafetyBackup(backup.sections.sessions !== undefined || backup.sections.sessionsMetadata !== undefined ? 'all' : 'config')
|
|
525
611
|
const summary = await restoreValidatedBackup(backup, mode)
|
|
612
|
+
if (body?.importToken) await deletePendingImportBackup(body.importToken)
|
|
526
613
|
sendJson(res, 200, { ok: true, safetyBackupPath, summary })
|
|
527
614
|
return
|
|
528
615
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { readJsonBody, sendJson } from '../utils/response.mjs'
|
|
2
|
+
import { readGlobalMemoryDocument, saveGlobalMemoryDocument } from '../global-memory.mjs'
|
|
3
|
+
|
|
4
|
+
const MAX_MEMORY_REQUEST_BYTES = 20 * 1024
|
|
5
|
+
|
|
6
|
+
export async function handleMemoryApi(req, res, options = {}) {
|
|
7
|
+
if (req.method === 'GET') {
|
|
8
|
+
sendJson(res, 200, await readGlobalMemoryDocument(options))
|
|
9
|
+
return
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (req.method === 'PUT') {
|
|
13
|
+
const body = await readJsonBody(req, MAX_MEMORY_REQUEST_BYTES)
|
|
14
|
+
if (!body || typeof body.markdown !== 'string') {
|
|
15
|
+
const error = new Error('Memory Markdown is required.')
|
|
16
|
+
error.statusCode = 400
|
|
17
|
+
throw error
|
|
18
|
+
}
|
|
19
|
+
sendJson(res, 200, await saveGlobalMemoryDocument(body.markdown, options))
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const error = new Error('Method not allowed')
|
|
24
|
+
error.statusCode = 405
|
|
25
|
+
throw error
|
|
26
|
+
}
|
package/server/routes/tools.mjs
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { sendJson, readJsonBody, decodeSegment } from '../utils/response.mjs'
|
|
2
2
|
import { readStore } from '../storage.mjs'
|
|
3
3
|
import { toolHandlers, loadSkillToolContext } from '../tools/index.mjs'
|
|
4
|
-
import { createSkillTools, workspaceTools } from '../tools/definitions.mjs'
|
|
4
|
+
import { createSkillTools, globalMemoryTool, workspaceTools } from '../tools/definitions.mjs'
|
|
5
|
+
import { isGlobalMemoryEnabled } from '../global-memory.mjs'
|
|
5
6
|
import { createMcpToolDefinitions } from '../mcp/registry.mjs'
|
|
6
7
|
import { callPluginTool, createPluginToolDefinitions, isPluginToolName } from '../plugins/registry.mjs'
|
|
7
8
|
import { safeReadTools } from '../approval-store.mjs'
|
|
8
9
|
import { projectContextFromId, readProjectConfig } from '../project-config.mjs'
|
|
9
10
|
|
|
10
|
-
const directRouteDisabledTools = new Set(['run_subagent'])
|
|
11
|
+
const directRouteDisabledTools = new Set(['run_subagent', 'manage_global_memory'])
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* GET /api/tools — returns canonical tool definitions (no project context needed).
|
|
@@ -22,7 +23,8 @@ export async function handleGetTools(_req, res) {
|
|
|
22
23
|
})
|
|
23
24
|
const pluginTools = await createPluginToolDefinitions(activeProject ? { workspaceRoot: activeProject.path, project: activeProject } : null)
|
|
24
25
|
const mcpTools = await createMcpToolDefinitions()
|
|
25
|
-
|
|
26
|
+
const memoryTools = await isGlobalMemoryEnabled() ? [globalMemoryTool] : []
|
|
27
|
+
sendJson(res, 200, { tools: [...skillTools, ...memoryTools, ...workspaceTools, ...mcpTools, ...pluginTools] })
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
const workspaceToolNames = new Set(workspaceTools.map((tool) => tool.name))
|
|
@@ -6,6 +6,7 @@ import { sendJson, readJsonBody } from '../utils/response.mjs'
|
|
|
6
6
|
import { projectContextFromId } from '../project-config.mjs'
|
|
7
7
|
import { readStore } from '../storage.mjs'
|
|
8
8
|
import { logger } from '../utils/logger.mjs'
|
|
9
|
+
import { openPathInFileManager, openPathInIDEA, openPathInVSCode } from '../utils/platform.mjs'
|
|
9
10
|
import {
|
|
10
11
|
assertSafeWorkspacePath,
|
|
11
12
|
resolveWorkspacePath,
|
|
@@ -745,6 +746,57 @@ async function handleWorkspaceResolvePath(req, res) {
|
|
|
745
746
|
})
|
|
746
747
|
}
|
|
747
748
|
|
|
749
|
+
export async function openWorkspaceExternalPath(context, inputPath, target, openers = {}) {
|
|
750
|
+
const relativePath = typeof inputPath === 'string' ? inputPath.trim() : ''
|
|
751
|
+
if (!relativePath) {
|
|
752
|
+
const error = new Error('path is required')
|
|
753
|
+
error.statusCode = 400
|
|
754
|
+
throw error
|
|
755
|
+
}
|
|
756
|
+
if (target !== 'explorer' && target !== 'vscode' && target !== 'idea') {
|
|
757
|
+
const error = new Error('target must be explorer, vscode, or idea')
|
|
758
|
+
error.statusCode = 400
|
|
759
|
+
throw error
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const fullPath = resolveWorkspacePath(relativePath, context)
|
|
763
|
+
await assertSafeWorkspacePath(fullPath, context, {
|
|
764
|
+
allowSensitive: true,
|
|
765
|
+
ignoreMissing: true,
|
|
766
|
+
})
|
|
767
|
+
const stat = await fs.stat(fullPath).catch(() => null)
|
|
768
|
+
|
|
769
|
+
if (target === 'explorer') {
|
|
770
|
+
const directory = stat?.isDirectory() ? fullPath : path.dirname(fullPath)
|
|
771
|
+
await assertSafeWorkspacePath(directory, context, { allowSensitive: true })
|
|
772
|
+
await (openers.explorer ?? openPathInFileManager)(directory)
|
|
773
|
+
return { ok: true, opened: 'directory', target }
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
if (!stat?.isFile()) {
|
|
777
|
+
const error = new Error(`File does not exist: ${toWorkspaceRelative(fullPath, context)}`)
|
|
778
|
+
error.statusCode = 400
|
|
779
|
+
throw error
|
|
780
|
+
}
|
|
781
|
+
const opener = target === 'vscode'
|
|
782
|
+
? (openers.vscode ?? openPathInVSCode)
|
|
783
|
+
: (openers.idea ?? openPathInIDEA)
|
|
784
|
+
await opener(fullPath)
|
|
785
|
+
return { ok: true, opened: 'file', target }
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
async function handleWorkspaceOpenExternal(req, res) {
|
|
789
|
+
const body = await readJsonBody(req, 16 * 1024)
|
|
790
|
+
const projectId = typeof body?.projectId === 'string' ? body.projectId : ''
|
|
791
|
+
if (!projectId) {
|
|
792
|
+
const error = new Error('projectId is required')
|
|
793
|
+
error.statusCode = 400
|
|
794
|
+
throw error
|
|
795
|
+
}
|
|
796
|
+
const context = await projectContextFromId(projectId)
|
|
797
|
+
sendJson(res, 200, await openWorkspaceExternalPath(context, body?.path, body?.target))
|
|
798
|
+
}
|
|
799
|
+
|
|
748
800
|
async function handleGitStatus(req, res, url) {
|
|
749
801
|
const context = await projectContextFromUrl(url)
|
|
750
802
|
sendJson(res, 200, await listGitStatus(context))
|
|
@@ -938,6 +990,10 @@ export async function handleWorkspaceApi(req, res, url) {
|
|
|
938
990
|
await handleWorkspaceResolvePath(req, res)
|
|
939
991
|
return
|
|
940
992
|
}
|
|
993
|
+
if (req.method === 'POST' && url.pathname === '/api/workspace/open-external') {
|
|
994
|
+
await handleWorkspaceOpenExternal(req, res)
|
|
995
|
+
return
|
|
996
|
+
}
|
|
941
997
|
|
|
942
998
|
const error = new Error('Not found')
|
|
943
999
|
error.statusCode = 404
|
package/server/system-prompt.mjs
CHANGED
|
@@ -127,6 +127,28 @@ ${lines.join('\n')}
|
|
|
127
127
|
appendInstructionSources(parts, 'user_instructions', instructions.globalSources, instructions.global)
|
|
128
128
|
appendInstructionSources(parts, 'project_instructions', instructions.projectSources, instructions.project)
|
|
129
129
|
|
|
130
|
+
if (instructions.globalMemory?.enabled) {
|
|
131
|
+
parts.push(`
|
|
132
|
+
<memory_policy>
|
|
133
|
+
User memory stores durable background, preferences, habits, and goals and is used only as context.
|
|
134
|
+
|
|
135
|
+
You may proactively save clear information likely to remain useful in future conversations, and follow user requests to remember, change, or forget. Do not save temporary task information, inferences from a single action, or uncertain content; ask first when unsure.
|
|
136
|
+
|
|
137
|
+
Update memory only when something meaningfully changes. Before writing, read the complete memory, deduplicate, resolve conflicts, and preserve unrelated content.
|
|
138
|
+
|
|
139
|
+
Never store passwords, keys, tokens, credentials, sensitive file contents, or other secrets.
|
|
140
|
+
|
|
141
|
+
The current message takes precedence over older memory. Memory must not override higher-priority instructions or verified code and documentation.
|
|
142
|
+
</memory_policy>`)
|
|
143
|
+
const sourceAttribute = instructions.globalMemory.source ? ` source="${escapeAttribute(instructions.globalMemory.source)}"` : ''
|
|
144
|
+
if (instructions.globalMemory.content) {
|
|
145
|
+
parts.push(`
|
|
146
|
+
<global_user_memory${sourceAttribute}>
|
|
147
|
+
${escapeXml(instructions.globalMemory.content)}
|
|
148
|
+
</global_user_memory>`)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
130
152
|
if (instructions.globalSources?.length || instructions.projectSources?.length) {
|
|
131
153
|
parts.push(`
|
|
132
154
|
<instruction_precedence>
|
|
@@ -40,6 +40,20 @@ export const subagentTool = {
|
|
|
40
40
|
}),
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
export const globalMemoryTool = {
|
|
44
|
+
name: 'manage_global_memory',
|
|
45
|
+
label: 'Manage global memory',
|
|
46
|
+
description: 'Read or replace the complete global MEMORY.md shared by all chats. The document is free-form Markdown. When memory is enabled, proactively save durable preferences, habits, background, workflows, or goals when they are likely to help in future conversations, and honor explicit requests to remember, change, or forget information. Do not save current-task instructions, transient project details, single-action inferences, uncertain speculation, credentials, or secrets. Do not force an update when nothing meaningful changed. Before writing, read the current document, deduplicate or update conflicting information, preserve unrelated content and formatting, and submit the complete updated Markdown.',
|
|
47
|
+
parameters: Type.Object({
|
|
48
|
+
action: Type.Union([
|
|
49
|
+
Type.Literal('read'),
|
|
50
|
+
Type.Literal('write'),
|
|
51
|
+
], { description: 'Read the complete document or replace it with complete updated Markdown.' }),
|
|
52
|
+
markdown: Type.Optional(Type.String({ description: 'Complete MEMORY.md content. Required for write; saved exactly as provided.' })),
|
|
53
|
+
}),
|
|
54
|
+
executionMode: 'sequential',
|
|
55
|
+
}
|
|
56
|
+
|
|
43
57
|
export const workspaceTools = [
|
|
44
58
|
subagentTool,
|
|
45
59
|
{
|
package/server/tools/index.mjs
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
readSkillResource,
|
|
15
15
|
} from '../skills.mjs'
|
|
16
16
|
import { getToolWorkspaceRoot } from '../utils/workspace.mjs'
|
|
17
|
+
import { manageGlobalMemory } from '../global-memory.mjs'
|
|
17
18
|
|
|
18
19
|
const require = createRequire(import.meta.url)
|
|
19
20
|
|
|
@@ -1088,7 +1089,12 @@ export async function toolRunCommand(params, context, runtime = {}) {
|
|
|
1088
1089
|
})
|
|
1089
1090
|
}
|
|
1090
1091
|
|
|
1092
|
+
export async function toolManageGlobalMemory(params) {
|
|
1093
|
+
return manageGlobalMemory(params)
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1091
1096
|
export const toolHandlers = {
|
|
1097
|
+
manage_global_memory: toolManageGlobalMemory,
|
|
1092
1098
|
read_file: toolReadFile,
|
|
1093
1099
|
grep_files: toolGrepFiles,
|
|
1094
1100
|
write_file: toolWriteFile,
|
|
@@ -186,8 +186,8 @@ export async function openPathInFileManager(targetPath) {
|
|
|
186
186
|
export async function openPathInVSCode(targetPath) {
|
|
187
187
|
const resolved = path.resolve(String(targetPath || ''))
|
|
188
188
|
const stat = await fs.stat(resolved).catch(() => null)
|
|
189
|
-
if (!stat || !stat.isDirectory()) {
|
|
190
|
-
const error = new Error(`
|
|
189
|
+
if (!stat || (!stat.isDirectory() && !stat.isFile())) {
|
|
190
|
+
const error = new Error(`Path does not exist: ${resolved}`)
|
|
191
191
|
error.statusCode = 400
|
|
192
192
|
throw error
|
|
193
193
|
}
|
|
@@ -234,8 +234,8 @@ export async function openPathInVSCode(targetPath) {
|
|
|
234
234
|
export async function openPathInIDEA(targetPath) {
|
|
235
235
|
const resolved = path.resolve(String(targetPath || ''))
|
|
236
236
|
const stat = await fs.stat(resolved).catch(() => null)
|
|
237
|
-
if (!stat || !stat.isDirectory()) {
|
|
238
|
-
const error = new Error(`
|
|
237
|
+
if (!stat || (!stat.isDirectory() && !stat.isFile())) {
|
|
238
|
+
const error = new Error(`Path does not exist: ${resolved}`)
|
|
239
239
|
error.statusCode = 400
|
|
240
240
|
throw error
|
|
241
241
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{i as e}from"./rolldown-runtime-DWdDZTNf.js";import{D as t,Ft as n,Rt as r,Vt as i,a,dt as ee,l as o}from"./icons-C7j5jdKo.js";import{i as s,n as c}from"./react-vendor-VqdnQHHS.js";import{$ as te,Q as ne,V as l,Z as re,ct as u,et as ie,lt as ae,pt as d,st as f,tt as oe}from"./index-_ut7awrd.js";var p=e(i(),1),se=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 ce(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 le(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 ue(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,i]=(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,de]=(0,p.useState)(),[L,fe]=(0,p.useState)([]),[pe,me]=(0,p.useState)(`off`),[R,z]=(0,p.useState)(``),[B,V]=(0,p.useState)(null),[H,U]=(0,p.useState)(null);async function W(){let[e,t]=await Promise.all([w(`/api/agent-profiles`),w(`/api/agent-profiles/available-tools`)]);i(e.agents),c(t.tools)}(0,p.useEffect)(()=>{let e=!1;async function t(){try{let[t,n]=await Promise.all([w(`/api/agent-profiles`),w(`/api/agent-profiles/available-tools`)]);if(e)return;i(t.agents),c(n.tools)}catch(t){e||z(t instanceof Error?t.message:d(`requestFailed`))}}return t(),()=>{e=!0}},[]),(0,p.useEffect)(()=>{let e=!1;async function t(){try{let t=await te(),n=await ne(t);fe(n);let r=await ie(t),i=r.model??await oe(t)??n[0];if(e)return;de(i),me(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 he(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 ge(e){k(t=>({...t,allowedTools:t.allowedTools.includes(e)?t.allowedTools.filter(t=>t!==e):[...t.allowedTools,e]}))}function _e(){D(null),k(S()),N(``),z(``),T(!0)}function Q(e){D(e.id),k(le(e)),N(``),z(``),T(!0)}function $(){A||P||(T(!1),D(null),k(S()),N(``))}async function ve(){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:pe})});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 ye(){if(C(O)){j(!0),z(``);try{let e=G?.builtin?{model:O.modelMode===`fixed`?b(O.fixedModelValue):{mode:`inherit`}}:ue({...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 be(e){if(e.builtin||e.readonly)return;let t=!e.enabledAsSubagent,n=e.enabledAsSubagent;i(n=>n.map(n=>n.id===e.id?{...n,enabledAsSubagent:t}:n)),V(null);try{await w(`/api/agent-profiles/${encodeURIComponent(e.id)}`,{method:`PATCH`,body:JSON.stringify({enabledAsSubagent:t})})}catch(t){i(t=>t.map(t=>t.id===e.id?{...t,enabledAsSubagent:n}:t)),z(t instanceof Error?t.message:d(`requestFailed`))}}async function xe(e){if(!(e.builtin||e.readonly)&&await l({description:d(`confirmDeleteAgent`),confirmLabel:d(`confirmDelete`),cancelLabel:d(`cancel`),variant:`destructive`})){z(``);try{await w(`/api/agent-profiles/${encodeURIComponent(e.id)}`,{method:`DELETE`}),await W()}catch(e){z(e instanceof Error?e.message:d(`requestFailed`))}}}return x?(0,m.jsxs)(`div`,{className:`quickforge-settings-stack`,children:[(0,m.jsx)(`div`,{className:`quickforge-settings-heading`,children:(0,m.jsxs)(`h3`,{className:`quickforge-settings-title`,children:[G?.builtin?d(`builtinAgentModelSettings`):d(G?`editAgent`:`createAgent`),(0,m.jsx)(f,{label:G?.builtin?d(`builtinAgentModelOnly`):G?.readonly?d(`readonlyAgentDescription`):d(`agentsDescription`)})]})}),(0,m.jsxs)(`section`,{className:`quickforge-settings-section`,"aria-label":d(G?`editAgent`:`createAgent`),children:[(0,m.jsxs)(`div`,{className:`quickforge-settings-toolbar`,children:[(0,m.jsxs)(`button`,{className:`quickforge-settings-button quickforge-settings-button-secondary`,type:`button`,onClick:$,disabled:A||P,children:[(0,m.jsx)(r,{className:`mr-2 size-4`}),d(`back`)]}),(0,m.jsxs)(`div`,{className:`quickforge-settings-row-main`,children:[(0,m.jsx)(`div`,{className:`quickforge-settings-row-title`,children:G?.builtin?d(`builtinAgentModelSettings`):d(G?`editAgent`:`createAgent`)}),G?.builtin?(0,m.jsx)(`div`,{className:`quickforge-settings-row-description`,children:d(`builtinAgentModelOnly`)}):G?.readonly?(0,m.jsx)(`div`,{className:`quickforge-settings-row-description`,children:d(`readonlyAgentDescription`)}):null]})]}),(0,m.jsx)(`div`,{className:`px-5 py-4`,children:(0,m.jsxs)(`div`,{className:`space-y-4`,children:[(0,m.jsxs)(`div`,{className:`rounded-2xl border border-border bg-muted/20 p-3`,children:[(0,m.jsxs)(`div`,{className:`mb-2 flex items-center gap-2 text-sm font-medium text-foreground`,children:[(0,m.jsx)(o,{className:`size-4 text-primary`}),d(`aiFillAgent`),(0,m.jsx)(f,{label:d(`aiFillAgentDescription`)})]}),(0,m.jsx)(`textarea`,{className:`min-h-20 w-full resize-y rounded-xl border border-input bg-background px-3 py-2 text-sm outline-none transition-colors placeholder:text-muted-foreground/65 focus:border-ring disabled:opacity-60`,value:M,disabled:q||P,onChange:e=>N(e.target.value),placeholder:d(`aiFillAgentPlaceholder`)}),(0,m.jsx)(`div`,{className:`mt-2 flex justify-end`,children:(0,m.jsxs)(u,{variant:`outline`,size:`sm`,onClick:()=>void ve(),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:ce(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:()=>ge(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:ye,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)(n,{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:_e,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.jsxs)(`div`,{className:`quickforge-settings-list-item-main quickforge-agent-profile-row-main`,children:[(0,m.jsx)(`span`,{className:ae(`quickforge-agent-profile-status-dot`,e.enabledAsSubagent?`quickforge-agent-profile-status-dot--enabled`:null),"aria-label":e.enabledAsSubagent?d(`enabled`):d(`disabled`),title:e.enabledAsSubagent?d(`enabled`):d(`disabled`)}),(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}),(0,m.jsx)(`span`,{className:`quickforge-agent-profile-name quickforge-settings-mono`,title:e.name,children:e.name}),(0,m.jsxs)(`span`,{className:`quickforge-agent-profile-description`,title:e.description||d(`noDescription`),children:[`· `,e.description||d(`noDescription`)]}),(0,m.jsxs)(`span`,{className:`quickforge-agent-profile-name quickforge-settings-mono`,title:e.model?.mode===`fixed`?`${e.model.provider}/${e.model.modelId}`:d(`agentModelInherit`),children:[`· `,e.model?.mode===`fixed`?`${e.model.provider}/${e.model.modelId}`:d(`agentModelInherit`)]})]})]}),(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 be(e)}),(0,m.jsx)(`span`,{"aria-hidden":`true`})]}),(0,m.jsx)(`button`,{className:`quickforge-settings-icon-action`,type:`button`,onClick:t=>he(t,e.id),title:d(`moreActions`),"aria-label":d(`moreActions`),"aria-haspopup":`menu`,"aria-expanded":B===e.id,children:(0,m.jsx)(ee,{className:`size-4`})})]})]},e.id))]}),K&&H?(0,se.createPortal)((0,m.jsxs)(`div`,{className:`fixed z-50 w-36 overflow-hidden rounded-xl border border-border bg-popover py-1 text-sm shadow-quickforge`,style:{left:H.left,top:H.top},role:`menu`,"aria-label":d(`moreActions`),onClick:e=>e.stopPropagation(),children:[(0,m.jsxs)(`button`,{className:`flex w-full items-center gap-2 px-3 py-2 text-left hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50`,type:`button`,role:`menuitem`,disabled:K.readonly&&!K.builtin,onClick:()=>{V(null),U(null),Q(K)},children:[(0,m.jsx)(t,{className:`size-3.5`}),K.builtin?d(`builtinAgentModelSettings`):d(`editTask`)]}),(0,m.jsxs)(`button`,{className:`flex w-full items-center gap-2 px-3 py-2 text-left text-destructive hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50`,type:`button`,role:`menuitem`,disabled:K.builtin||K.readonly,onClick:()=>{V(null),U(null),xe(K)},children:[(0,m.jsx)(a,{className:`size-3.5`}),d(`delete`)]})]}),document.body):null]})}export{T as AgentProfilesPage};
|