@shawnstack/quickforge 1.6.8 → 1.6.10
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-BtZpDV-3.js +1 -0
- package/dist/assets/ChatPanelHost-BF1V-xOV.js +244 -0
- package/dist/assets/{PluginsPage-3pZG7D7e.js → PluginsPage-CCIj8-Mn.js} +1 -1
- package/dist/assets/ScheduledTasksPage-DIMewQ3i.js +2 -0
- package/dist/assets/{SettingsWorkspacePage-DWkPyGP_.js → SettingsWorkspacePage-BPD495WT.js} +296 -290
- package/dist/assets/SharedConversationPage-B_oMTp7B.js +1 -0
- package/dist/assets/TerminalDock-wgW7kSC4.js +2 -0
- package/dist/assets/WorkspaceInspector-CdX45r3u.js +13 -0
- package/dist/assets/abnfDiagram-VRR7QNED-Dl_Fvlqg.js +1 -0
- package/dist/assets/arc-Bhg4c0ht.js +1 -0
- package/dist/assets/architecture-TIHT7OUA-BgP-IMeo.js +1 -0
- package/dist/assets/architectureDiagram-ZJ3FMSHR-BuojN27B.js +36 -0
- package/dist/assets/array-CYkMkqnU.js +1 -0
- package/dist/assets/blockDiagram-677ZJIJ3-alDLbrR1.js +132 -0
- package/dist/assets/c4Diagram-LMCZKHZV-D1lKa4VT.js +10 -0
- package/dist/assets/channel-Rz20dKTK.js +1 -0
- package/dist/assets/chunk-2Q5K7J3B-Cb14ZF_k.js +1 -0
- package/dist/assets/chunk-32BRIVSS-CwWLNjMZ.js +1 -0
- package/dist/assets/chunk-52WLFC77-Be569m7d.js +10 -0
- package/dist/assets/chunk-5FCAYU7R-BSvoiWV1.js +1 -0
- package/dist/assets/chunk-5HE753X5-CRgsy8WS.js +1 -0
- package/dist/assets/chunk-5JV3BV7I-CdW477UR.js +1 -0
- package/dist/assets/chunk-5TONJI2A-CdtKzSmp.js +2 -0
- package/dist/assets/chunk-5VM5RSS4-BKjjssLZ.js +15 -0
- package/dist/assets/chunk-7BUUIJ7U-ByAXiXdn.js +1 -0
- package/dist/assets/chunk-BIQX33UG-DeG8SCT1.js +1 -0
- package/dist/assets/chunk-C7G6YPKG-D_zHT68Q.js +1 -0
- package/dist/assets/chunk-CQNSW5MT-CHpHoUSu.js +1 -0
- package/dist/assets/chunk-CYSBUYHQ-CRP50Vov.js +1 -0
- package/dist/assets/chunk-EMLP6XTP-DX6uQQ2v.js +1 -0
- package/dist/assets/chunk-EX3LRPZG-DAJRNkHR.js +231 -0
- package/dist/assets/chunk-FWX5IMBZ-XbM5yomC.js +2 -0
- package/dist/assets/chunk-HOUHSVGY-Ci0ljnb1.js +1 -0
- package/dist/assets/chunk-ICXQ74PX-Dy2WJ71z.js +2 -0
- package/dist/assets/chunk-JG7HCLWE-2PrkzHkt.js +2 -0
- package/dist/assets/chunk-JWPE2WC7-D32uacSM.js +1 -0
- package/dist/assets/chunk-KEIR6QF5-77hsebd7.js +161 -0
- package/dist/assets/chunk-MOJQB5TN-DIAFLtiS.js +88 -0
- package/dist/assets/chunk-MOZMSUNE-BbscOaEz.js +1 -0
- package/dist/assets/chunk-OGEWGWER-ivBVxzKe.js +1 -0
- package/dist/assets/chunk-OSBZ3O6U-frD0hkdx.js +1 -0
- package/dist/assets/chunk-PUDLZKDR-B-37Rxae.js +156 -0
- package/dist/assets/chunk-Q4XR5HBZ-Y90m2RTe.js +11 -0
- package/dist/assets/chunk-QBLGF6JB-DixBqslB.js +1 -0
- package/dist/assets/chunk-R7FJI6CG-DXS1UXn_.js +1 -0
- package/dist/assets/chunk-RYQCIY6F-n4JxhNd0.js +1 -0
- package/dist/assets/chunk-U6XO7XAA-yHlgxMmn.js +2 -0
- package/dist/assets/chunk-V7JOEXUC-Z6kuZfbl.js +206 -0
- package/dist/assets/chunk-VAUOI2AC-BOZvd2Sn.js +1 -0
- package/dist/assets/chunk-VR4S4FIN-Cs_SIQma.js +1 -0
- package/dist/assets/chunk-WYO6CB5R-CGExYbV-.js +128 -0
- package/dist/assets/chunk-XXDRQBXY-CD_EGCtT.js +1 -0
- package/dist/assets/chunk-Y2CYZVJY-CQltPwYk.js +1 -0
- package/dist/assets/chunk-YOTPTUD7-DopPXWSl.js +1 -0
- package/dist/assets/chunk-ZGVPDNZ5-CJHOeqMb.js +62 -0
- package/dist/assets/chunk-ZIRB5QZD-BBVc5mf6.js +32 -0
- package/dist/assets/classDiagram-OUVF2IWQ-C_ozTKCt.js +1 -0
- package/dist/assets/classDiagram-v2-EOCWNBFH-FOfFbbZF.js +1 -0
- package/dist/assets/cose-bilkent-JH36ORCC-DLjV-dTm.js +1 -0
- package/dist/assets/cynefin-VYW2F7L2-BzPhLtd5.js +1 -0
- package/dist/assets/cynefinDiagram-TSTJHNR4-BfH3zVS-.js +62 -0
- package/dist/assets/cytoscape.esm-DwdZnGOm.js +321 -0
- package/dist/assets/dagre-CY3D84Cl.js +1 -0
- package/dist/assets/dagre-VKFMJZFB-BIhwZCIZ.js +4 -0
- package/dist/assets/defaultLocale-Beh6XjaL.js +1 -0
- package/dist/assets/diagram-FQU43EPY-BHnzNM2w.js +3 -0
- package/dist/assets/diagram-G47NLZAW-C7-ZlmFT.js +24 -0
- package/dist/assets/diagram-NH7WQ7WH-C-P9Y7IR.js +24 -0
- package/dist/assets/diagram-OA4YK3LP-Ci5lZ_T5.js +30 -0
- package/dist/assets/diagram-WEI45ONY-C1xuK2bs.js +41 -0
- package/dist/assets/dist-DelZBZpK.js +1 -0
- package/dist/assets/ebnfDiagram-CCIWWBDH-DKQ5NWMD.js +1 -0
- package/dist/assets/erDiagram-Q63AITRT-HAWySadP.js +85 -0
- package/dist/assets/eventmodeling-45OFAUF4-BcgGbVJ-.js +1 -0
- package/dist/assets/flowDiagram-23GEKE2U-dtkzcC8E.js +1 -0
- package/dist/assets/ganttDiagram-NO4QXBWP-C1hwIcOA.js +292 -0
- package/dist/assets/gitGraph-TEB2WS4Q-VByWboBU.js +1 -0
- package/dist/assets/gitGraphDiagram-IHSO6WYX-DYUYxs1R.js +106 -0
- package/dist/assets/graphlib-CAISHZh4.js +1 -0
- package/dist/assets/icons-C7j5jdKo.js +1 -0
- package/dist/assets/index-Bxg5fT2p.css +3 -0
- package/dist/assets/index-DKmdePUP.js +63 -0
- package/dist/assets/info-DKCQHKI2-CZeDLZxx.js +1 -0
- package/dist/assets/infoDiagram-FWYZ7A6U-CnIL6tv4.js +2 -0
- package/dist/assets/init-Rr1s_RiX.js +1 -0
- package/dist/assets/ishikawaDiagram-FXEZZL3T-B7WXNycQ.js +70 -0
- package/dist/assets/journeyDiagram-5HDEW3XC-D4HvioyY.js +139 -0
- package/dist/assets/kanban-definition-HUTT4EX6-CDs763eS.js +89 -0
- package/dist/assets/line-ikKkQiFp.js +1 -0
- package/dist/assets/linear-GqPyxQkN.js +1 -0
- package/dist/assets/map-CJwjCvwr.js +1 -0
- package/dist/assets/mcp-servers-dialog-jHlDR9jP.js +5 -0
- package/dist/assets/mermaid-parser.core-CoE8cccr.js +4 -0
- package/dist/assets/mermaid-renderer-BmbSAmpG.js +2 -0
- package/dist/assets/mermaid.core-DlFSL90j.js +11 -0
- package/dist/assets/mindmap-definition-LN4V7U3C-DLdq0K9C.js +96 -0
- package/dist/assets/{monaco-CzxeAjEw.js → monaco-CdOyGlWD.js} +1 -1
- package/dist/assets/ordinal-CibZU3PM.js +1 -0
- package/dist/assets/{otel-DQf3c6v2.js → otel-BNGM50C-.js} +1 -1
- package/dist/assets/packet-7NZHBO7P-decrrxyX.js +1 -0
- package/dist/assets/path-BAQ3hXlG.js +1 -0
- package/dist/assets/pegDiagram-2B236MQR-Cm1kPRC2.js +1 -0
- package/dist/assets/{pi-ai-DjJwSQBQ.js → pi-ai-Cr3rpCXT.js} +2 -2
- package/dist/assets/{pi-web-ui-DqxtBlZL.js → pi-web-ui-DoRGtREc.js} +371 -371
- package/dist/assets/pie-RZYD4A2V-J1CasQZl.js +1 -0
- package/dist/assets/pieDiagram-ENE6RG2P-Df05wjjo.js +39 -0
- package/dist/assets/quadrantDiagram-ABIIQ3AL-BjCwGxJC.js +7 -0
- package/dist/assets/radar-I7S5WNFK-B-sZytUA.js +1 -0
- package/dist/assets/railroad-3IZDKUUU-BSaqNPm8.js +1 -0
- package/dist/assets/railroad-abnf-AHOZXSZD-lzZR7PxT.js +1 -0
- package/dist/assets/railroad-ebnf-EBAXGLYW-DQtz1Fh9.js +1 -0
- package/dist/assets/railroad-peg-LSFZ7HO6-CpMsuGY3.js +1 -0
- package/dist/assets/railroadDiagram-RFXS5EU6-CTUvpKkv.js +1 -0
- package/dist/assets/{react-vendor-BR9MG2D-.js → react-vendor-VqdnQHHS.js} +1 -1
- package/dist/assets/requirementDiagram-TGXJPOKE-BU1kvEku.js +84 -0
- package/dist/assets/rough.esm-Djo4Abte.js +1 -0
- package/dist/assets/sankeyDiagram-HTMAVEWB-BHmvxVqH.js +40 -0
- package/dist/assets/sequenceDiagram-DBY2YBRQ-CTpwICgg.js +162 -0
- package/dist/assets/sizeCapture-X5ZJPWSS-BbLhGaa4.js +1 -0
- package/dist/assets/{skills-dialog-B0jNVGZW.js → skills-dialog-DvSnPPzu.js} +1 -1
- package/dist/assets/src-B4Tvg3gT.js +1 -0
- package/dist/assets/stateDiagram-2N3HPSRC-BxuDwIgT.js +1 -0
- package/dist/assets/stateDiagram-v2-6OUMAXLB-BGLshxcY.js +1 -0
- package/dist/assets/swimlanes-5IMT3BWC-EkZsiylV.js +2 -0
- package/dist/assets/swimlanesDiagram-G3AALYLV-CqdqxA-b.js +8 -0
- package/dist/assets/timeline-definition-FHXFAJF6-C9ewOCTa.js +120 -0
- package/dist/assets/treeView-QDETBFTQ-Byb71heW.js +1 -0
- package/dist/assets/treemap-6X3UGDF4-CMV1SJsq.js +1 -0
- package/dist/assets/vennDiagram-L72KCM5P-CR49Y4Lp.js +34 -0
- package/dist/assets/wardley-OPB4EBWU-DtCHtr2V.js +1 -0
- package/dist/assets/wardleyDiagram-EHGQE667-Cn0GgwWi.js +78 -0
- package/dist/assets/xychartDiagram-FW5EYKEG-DlmPNAvd.js +7 -0
- package/dist/index.html +8 -8
- package/package.json +2 -1
- package/server/agent-manager.mjs +174 -9
- package/server/agent-profile-files.mjs +86 -6
- package/server/agent-profile-schema.mjs +197 -0
- package/server/agent-profiles.mjs +172 -41
- package/server/auto-compaction.mjs +2 -2
- package/server/project-route-context.mjs +25 -0
- package/server/routes/agent-profiles.mjs +9 -3
- package/server/routes/backup.mjs +135 -48
- package/server/routes/project.mjs +6 -10
- package/server/routes/workspace.mjs +240 -11
- package/server/storage.mjs +34 -0
- package/server/subagents.mjs +64 -0
- package/server/tools/definitions.mjs +17 -1
- package/server/utils/platform.mjs +4 -4
- package/dist/assets/AgentProfilesPage-R7N7eDq5.js +0 -1
- package/dist/assets/ChatPanelHost-C4orgd5l.js +0 -242
- package/dist/assets/ScheduledTasksPage-BAqoK4xk.js +0 -2
- package/dist/assets/SharedConversationPage-C-1n_MmB.js +0 -1
- package/dist/assets/TerminalDock-B6KAXkIU.js +0 -2
- package/dist/assets/WorkspaceInspector-CkXyD_k4.js +0 -3
- package/dist/assets/WorkspaceReaderDialog-Dp2QAwBo.js +0 -1
- package/dist/assets/diff-line-counts-Dm5kKQOo.js +0 -10
- package/dist/assets/icons-DHoaB5uq.js +0 -1
- package/dist/assets/index-C77QCHE9.css +0 -3
- package/dist/assets/index-CZb_mGGF.js +0 -63
- package/dist/assets/mcp-servers-dialog-VbGvNekJ.js +0 -5
- package/dist/assets/useAppTheme-DsabFF9D.js +0 -1
- /package/dist/assets/{plugin-api-Ds5xGIL7.js → plugin-api-BbUkYm3Y.js} +0 -0
- /package/dist/assets/{xterm-aPHkK8RR.js → xterm-CGiQzDiR.js} +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { sendJson, readJsonBody, decodeSegment } from '../utils/response.mjs'
|
|
2
2
|
import { logger } from '../utils/logger.mjs'
|
|
3
3
|
import { getActiveProject, setActiveProjectPath, readProjectConfig, getDefaultWorkspaceRoot } from '../project-config.mjs'
|
|
4
|
+
import { resolveRequestedProject, workspaceRootAfterProjectDeletion } from '../project-route-context.mjs'
|
|
4
5
|
import { listProjectCommands, createCommandFile } from '../custom-commands.mjs'
|
|
5
6
|
import { atomicProjectConfigUpdate } from '../storage.mjs'
|
|
6
7
|
import { getWorkspaceRoot, setWorkspaceRoot } from '../utils/workspace.mjs'
|
|
@@ -137,13 +138,12 @@ export async function handleProjectApi(req, res, url) {
|
|
|
137
138
|
|
|
138
139
|
if (req.method === 'POST' && url.pathname === '/api/project/open-path') {
|
|
139
140
|
const body = await readJsonBody(req)
|
|
140
|
-
const active = body?.projectId
|
|
141
|
-
? config.projects.find((project) => project.id === body.projectId) ?? getActiveProject(config)
|
|
142
|
-
: getActiveProject(config)
|
|
141
|
+
const active = resolveRequestedProject(config, body?.projectId, getDefaultWorkspaceRoot())
|
|
143
142
|
const target = String(body?.path || '')
|
|
143
|
+
const root = active?.path || getWorkspaceRoot() || getDefaultWorkspaceRoot()
|
|
144
144
|
const resolved = target && path.isAbsolute(target)
|
|
145
145
|
? path.resolve(target)
|
|
146
|
-
: path.resolve(
|
|
146
|
+
: path.resolve(root, target)
|
|
147
147
|
await openPathInFileManager(resolved)
|
|
148
148
|
sendJson(res, 200, { ok: true })
|
|
149
149
|
return
|
|
@@ -151,9 +151,7 @@ export async function handleProjectApi(req, res, url) {
|
|
|
151
151
|
|
|
152
152
|
if (req.method === 'POST' && url.pathname === '/api/project/command') {
|
|
153
153
|
const body = await readJsonBody(req)
|
|
154
|
-
const active = body?.projectId
|
|
155
|
-
? config.projects.find((project) => project.id === body.projectId) ?? getActiveProject(config)
|
|
156
|
-
: getActiveProject(config)
|
|
154
|
+
const active = resolveRequestedProject(config, body?.projectId, getDefaultWorkspaceRoot())
|
|
157
155
|
if (!active?.path) {
|
|
158
156
|
const error = new Error('No active project')
|
|
159
157
|
error.statusCode = 400
|
|
@@ -178,9 +176,7 @@ export async function handleProjectApi(req, res, url) {
|
|
|
178
176
|
return cfg
|
|
179
177
|
})
|
|
180
178
|
const active = getActiveProject(updated)
|
|
181
|
-
|
|
182
|
-
setWorkspaceRoot(path.resolve(active.path))
|
|
183
|
-
}
|
|
179
|
+
setWorkspaceRoot(workspaceRootAfterProjectDeletion(active, getDefaultWorkspaceRoot()))
|
|
184
180
|
sendJson(res, 200, { project: active ?? null, projects: updated.projects, workspaceRoot: getWorkspaceRoot() })
|
|
185
181
|
return
|
|
186
182
|
}
|
|
@@ -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,
|
|
@@ -139,13 +140,17 @@ function parseGitStatus(buffer) {
|
|
|
139
140
|
return files.sort((left, right) => left.path.localeCompare(right.path, undefined, { sensitivity: 'base' }))
|
|
140
141
|
}
|
|
141
142
|
|
|
142
|
-
async function
|
|
143
|
+
async function currentGitHead(workspaceRoot) {
|
|
143
144
|
const result = await git(['branch', '--show-current'], workspaceRoot, { allowFailure: true })
|
|
144
145
|
const branch = result.stdout.toString('utf8').trim()
|
|
145
|
-
if (branch) return branch
|
|
146
|
+
if (branch) return { branch, detached: false }
|
|
146
147
|
const head = await git(['rev-parse', '--short', 'HEAD'], workspaceRoot, { allowFailure: true })
|
|
147
148
|
const commit = head.stdout.toString('utf8').trim()
|
|
148
|
-
return commit ? `HEAD ${commit}` : undefined
|
|
149
|
+
return { branch: commit ? `HEAD ${commit}` : undefined, detached: Boolean(commit) }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function currentGitBranch(workspaceRoot) {
|
|
153
|
+
return (await currentGitHead(workspaceRoot)).branch
|
|
149
154
|
}
|
|
150
155
|
|
|
151
156
|
async function assertValidBranchName(workspaceRoot, branch) {
|
|
@@ -317,9 +322,11 @@ async function listGitStatus(context) {
|
|
|
317
322
|
}
|
|
318
323
|
}
|
|
319
324
|
}
|
|
325
|
+
const head = await currentGitHead(context.workspaceRoot)
|
|
320
326
|
return {
|
|
321
327
|
isGitRepository: true,
|
|
322
|
-
branch:
|
|
328
|
+
branch: head.branch,
|
|
329
|
+
detached: head.detached,
|
|
323
330
|
counts: countGitStatus(files),
|
|
324
331
|
files,
|
|
325
332
|
}
|
|
@@ -347,8 +354,17 @@ async function hasStagedChanges(context) {
|
|
|
347
354
|
return result.code === 1
|
|
348
355
|
}
|
|
349
356
|
|
|
357
|
+
async function assertAttachedGitHead(context) {
|
|
358
|
+
const head = await currentGitHead(context.workspaceRoot)
|
|
359
|
+
if (!head.detached) return head
|
|
360
|
+
const error = new Error('Cannot commit or push while HEAD is detached')
|
|
361
|
+
error.statusCode = 400
|
|
362
|
+
throw error
|
|
363
|
+
}
|
|
364
|
+
|
|
350
365
|
async function commitGitChanges(context, message, includeUnstaged) {
|
|
351
366
|
await assertGitRepository(context)
|
|
367
|
+
await assertAttachedGitHead(context)
|
|
352
368
|
const commitMessage = normalizeCommitMessage(message)
|
|
353
369
|
if (includeUnstaged) await git(['add', '-A'], context.workspaceRoot)
|
|
354
370
|
if (!(await hasStagedChanges(context))) {
|
|
@@ -360,8 +376,88 @@ async function commitGitChanges(context, message, includeUnstaged) {
|
|
|
360
376
|
return listGitStatus(context)
|
|
361
377
|
}
|
|
362
378
|
|
|
379
|
+
async function normalizeGitOperationPath(context, value) {
|
|
380
|
+
const relativePath = typeof value === 'string' ? value.trim().replace(/\\/g, '/') : ''
|
|
381
|
+
if (!relativePath || relativePath === '.') {
|
|
382
|
+
const error = new Error('path is required')
|
|
383
|
+
error.statusCode = 400
|
|
384
|
+
throw error
|
|
385
|
+
}
|
|
386
|
+
const file = resolveWorkspacePath(relativePath, context)
|
|
387
|
+
await assertSafeWorkspacePath(file, context, { allowSensitive: true, ignoreMissing: true })
|
|
388
|
+
return toWorkspaceRelative(file, context)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
async function stageGitPath(context, value) {
|
|
392
|
+
await assertGitRepository(context)
|
|
393
|
+
const relativePath = await normalizeGitOperationPath(context, value)
|
|
394
|
+
await git(['add', '-A', '--', relativePath], context.workspaceRoot)
|
|
395
|
+
return listGitStatus(context)
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
async function stageAllGitChanges(context) {
|
|
399
|
+
await assertGitRepository(context)
|
|
400
|
+
await git(['add', '-A'], context.workspaceRoot)
|
|
401
|
+
return listGitStatus(context)
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async function unstageGitPath(context, value) {
|
|
405
|
+
await assertGitRepository(context)
|
|
406
|
+
const relativePath = await normalizeGitOperationPath(context, value)
|
|
407
|
+
await git(['restore', '--staged', '--', relativePath], context.workspaceRoot)
|
|
408
|
+
return listGitStatus(context)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
async function unstageAllGitChanges(context) {
|
|
412
|
+
await assertGitRepository(context)
|
|
413
|
+
await git(['restore', '--staged', '--', '.'], context.workspaceRoot)
|
|
414
|
+
return listGitStatus(context)
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
async function restoreGitPath(context, value) {
|
|
418
|
+
await assertGitRepository(context)
|
|
419
|
+
const relativePath = await normalizeGitOperationPath(context, value)
|
|
420
|
+
const status = await listGitStatus(context)
|
|
421
|
+
const changedFile = status.files.find((file) => file.path === relativePath || file.oldPath === relativePath)
|
|
422
|
+
if (!changedFile) return status
|
|
423
|
+
|
|
424
|
+
if (changedFile.status === 'untracked') {
|
|
425
|
+
const file = resolveWorkspacePath(changedFile.path, context)
|
|
426
|
+
await assertSafeWorkspacePath(file, context, { allowSensitive: true, ignoreMissing: true })
|
|
427
|
+
await fs.rm(file, { recursive: true, force: true })
|
|
428
|
+
return listGitStatus(context)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const targets = [...new Set([changedFile.path, changedFile.oldPath].filter(Boolean))]
|
|
432
|
+
if (changedFile.staged) {
|
|
433
|
+
await git(['restore', '--staged', '--', ...targets], context.workspaceRoot, { allowFailure: true })
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (changedFile.status === 'added') {
|
|
437
|
+
await git(['clean', '-fd', '--', changedFile.path], context.workspaceRoot)
|
|
438
|
+
return listGitStatus(context)
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (changedFile.oldPath) {
|
|
442
|
+
await git(['restore', '--worktree', '--', changedFile.oldPath], context.workspaceRoot, { allowFailure: true })
|
|
443
|
+
await git(['clean', '-fd', '--', changedFile.path], context.workspaceRoot, { allowFailure: true })
|
|
444
|
+
} else {
|
|
445
|
+
await git(['restore', '--worktree', '--', changedFile.path], context.workspaceRoot)
|
|
446
|
+
}
|
|
447
|
+
return listGitStatus(context)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
async function restoreAllGitChanges(context) {
|
|
451
|
+
await assertGitRepository(context)
|
|
452
|
+
await git(['restore', '--staged', '--', '.'], context.workspaceRoot, { allowFailure: true })
|
|
453
|
+
await git(['restore', '--worktree', '--', '.'], context.workspaceRoot, { allowFailure: true })
|
|
454
|
+
await git(['clean', '-fd'], context.workspaceRoot)
|
|
455
|
+
return listGitStatus(context)
|
|
456
|
+
}
|
|
457
|
+
|
|
363
458
|
async function pushGitBranch(context) {
|
|
364
459
|
await assertGitRepository(context)
|
|
460
|
+
await assertAttachedGitHead(context)
|
|
365
461
|
await git(['push'], context.workspaceRoot)
|
|
366
462
|
return listGitStatus(context)
|
|
367
463
|
}
|
|
@@ -398,7 +494,7 @@ function normalizeAiCommitMessage(text) {
|
|
|
398
494
|
return message
|
|
399
495
|
}
|
|
400
496
|
|
|
401
|
-
async function generateGitCommitMessage(context, model, thinkingLevel = 'off') {
|
|
497
|
+
async function generateGitCommitMessage(context, model, thinkingLevel = 'off', includeUnstaged = false) {
|
|
402
498
|
await assertGitRepository(context)
|
|
403
499
|
if (!model) {
|
|
404
500
|
const error = new Error('Please configure a default model first')
|
|
@@ -415,9 +511,19 @@ async function generateGitCommitMessage(context, model, thinkingLevel = 'off') {
|
|
|
415
511
|
|
|
416
512
|
const cachedStat = (await git(['diff', '--cached', '--stat'], context.workspaceRoot, { allowFailure: true })).stdout.toString('utf8')
|
|
417
513
|
const cachedDiff = (await git(['diff', '--cached'], context.workspaceRoot, { allowFailure: true })).stdout.toString('utf8')
|
|
418
|
-
const worktreeStat =
|
|
419
|
-
|
|
420
|
-
|
|
514
|
+
const worktreeStat = includeUnstaged
|
|
515
|
+
? (await git(['diff', '--stat'], context.workspaceRoot, { allowFailure: true })).stdout.toString('utf8')
|
|
516
|
+
: ''
|
|
517
|
+
const worktreeDiff = includeUnstaged
|
|
518
|
+
? (await git(['diff'], context.workspaceRoot, { allowFailure: true })).stdout.toString('utf8')
|
|
519
|
+
: ''
|
|
520
|
+
const selectedFiles = includeUnstaged ? status.files : status.files.filter((file) => file.staged)
|
|
521
|
+
if (!selectedFiles.length) {
|
|
522
|
+
const error = new Error('No staged changes to summarize')
|
|
523
|
+
error.statusCode = 400
|
|
524
|
+
throw error
|
|
525
|
+
}
|
|
526
|
+
const files = selectedFiles.map((file) => `- ${file.status}${file.staged ? ' staged' : ''}${file.unstaged ? ' unstaged' : ''}: ${file.oldPath ? `${file.oldPath} -> ` : ''}${file.path}`).join('\n')
|
|
421
527
|
const systemPrompt = `You generate Git commit messages.
|
|
422
528
|
Return only the commit message, no Markdown, no explanation.
|
|
423
529
|
Use Conventional Commit style when possible, for example: feat: add git tools summary.
|
|
@@ -640,6 +746,57 @@ async function handleWorkspaceResolvePath(req, res) {
|
|
|
640
746
|
})
|
|
641
747
|
}
|
|
642
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
|
+
|
|
643
800
|
async function handleGitStatus(req, res, url) {
|
|
644
801
|
const context = await projectContextFromUrl(url)
|
|
645
802
|
sendJson(res, 200, await listGitStatus(context))
|
|
@@ -752,10 +909,40 @@ async function contextFromGitBody(req) {
|
|
|
752
909
|
|
|
753
910
|
async function handleGitGenerateCommitMessage(req, res) {
|
|
754
911
|
const { context, body } = await contextFromGitBody(req)
|
|
755
|
-
const message = await generateGitCommitMessage(context, body?.model, body?.thinkingLevel)
|
|
912
|
+
const message = await generateGitCommitMessage(context, body?.model, body?.thinkingLevel, Boolean(body?.includeUnstaged))
|
|
756
913
|
sendJson(res, 200, { message })
|
|
757
914
|
}
|
|
758
915
|
|
|
916
|
+
async function handleGitStage(req, res) {
|
|
917
|
+
const { context, body } = await contextFromGitBody(req)
|
|
918
|
+
sendJson(res, 200, await stageGitPath(context, body?.path))
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
async function handleGitStageAll(req, res) {
|
|
922
|
+
const { context } = await contextFromGitBody(req)
|
|
923
|
+
sendJson(res, 200, await stageAllGitChanges(context))
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
async function handleGitUnstage(req, res) {
|
|
927
|
+
const { context, body } = await contextFromGitBody(req)
|
|
928
|
+
sendJson(res, 200, await unstageGitPath(context, body?.path))
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
async function handleGitUnstageAll(req, res) {
|
|
932
|
+
const { context } = await contextFromGitBody(req)
|
|
933
|
+
sendJson(res, 200, await unstageAllGitChanges(context))
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
async function handleGitRestore(req, res) {
|
|
937
|
+
const { context, body } = await contextFromGitBody(req)
|
|
938
|
+
sendJson(res, 200, await restoreGitPath(context, body?.path))
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
async function handleGitRestoreAll(req, res) {
|
|
942
|
+
const { context } = await contextFromGitBody(req)
|
|
943
|
+
sendJson(res, 200, await restoreAllGitChanges(context))
|
|
944
|
+
}
|
|
945
|
+
|
|
759
946
|
async function handleGitCommit(req, res) {
|
|
760
947
|
const { context, body } = await contextFromGitBody(req)
|
|
761
948
|
sendJson(res, 200, await commitGitChanges(context, body?.message, Boolean(body?.includeUnstaged)))
|
|
@@ -766,10 +953,24 @@ async function handleGitPush(req, res) {
|
|
|
766
953
|
sendJson(res, 200, await pushGitBranch(context))
|
|
767
954
|
}
|
|
768
955
|
|
|
956
|
+
export async function commitAndPushGitChanges(context, message, includeUnstaged) {
|
|
957
|
+
const committedStatus = await commitGitChanges(context, message, includeUnstaged)
|
|
958
|
+
try {
|
|
959
|
+
const pushedStatus = await pushGitBranch(context)
|
|
960
|
+
return { ...pushedStatus, committed: true, pushed: true }
|
|
961
|
+
} catch (error) {
|
|
962
|
+
return {
|
|
963
|
+
...committedStatus,
|
|
964
|
+
committed: true,
|
|
965
|
+
pushed: false,
|
|
966
|
+
pushError: error?.message || 'Push failed',
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
|
|
769
971
|
async function handleGitCommitAndPush(req, res) {
|
|
770
972
|
const { context, body } = await contextFromGitBody(req)
|
|
771
|
-
await
|
|
772
|
-
sendJson(res, 200, await pushGitBranch(context))
|
|
973
|
+
sendJson(res, 200, await commitAndPushGitChanges(context, body?.message, Boolean(body?.includeUnstaged)))
|
|
773
974
|
}
|
|
774
975
|
|
|
775
976
|
export async function handleWorkspaceApi(req, res, url) {
|
|
@@ -789,6 +990,10 @@ export async function handleWorkspaceApi(req, res, url) {
|
|
|
789
990
|
await handleWorkspaceResolvePath(req, res)
|
|
790
991
|
return
|
|
791
992
|
}
|
|
993
|
+
if (req.method === 'POST' && url.pathname === '/api/workspace/open-external') {
|
|
994
|
+
await handleWorkspaceOpenExternal(req, res)
|
|
995
|
+
return
|
|
996
|
+
}
|
|
792
997
|
|
|
793
998
|
const error = new Error('Not found')
|
|
794
999
|
error.statusCode = 404
|
|
@@ -824,6 +1029,30 @@ export async function handleGitApi(req, res, url) {
|
|
|
824
1029
|
await handleGitGenerateCommitMessage(req, res)
|
|
825
1030
|
return
|
|
826
1031
|
}
|
|
1032
|
+
if (req.method === 'POST' && url.pathname === '/api/git/stage') {
|
|
1033
|
+
await handleGitStage(req, res)
|
|
1034
|
+
return
|
|
1035
|
+
}
|
|
1036
|
+
if (req.method === 'POST' && url.pathname === '/api/git/stage-all') {
|
|
1037
|
+
await handleGitStageAll(req, res)
|
|
1038
|
+
return
|
|
1039
|
+
}
|
|
1040
|
+
if (req.method === 'POST' && url.pathname === '/api/git/unstage') {
|
|
1041
|
+
await handleGitUnstage(req, res)
|
|
1042
|
+
return
|
|
1043
|
+
}
|
|
1044
|
+
if (req.method === 'POST' && url.pathname === '/api/git/unstage-all') {
|
|
1045
|
+
await handleGitUnstageAll(req, res)
|
|
1046
|
+
return
|
|
1047
|
+
}
|
|
1048
|
+
if (req.method === 'POST' && url.pathname === '/api/git/restore') {
|
|
1049
|
+
await handleGitRestore(req, res)
|
|
1050
|
+
return
|
|
1051
|
+
}
|
|
1052
|
+
if (req.method === 'POST' && url.pathname === '/api/git/restore-all') {
|
|
1053
|
+
await handleGitRestoreAll(req, res)
|
|
1054
|
+
return
|
|
1055
|
+
}
|
|
827
1056
|
if (req.method === 'POST' && url.pathname === '/api/git/commit') {
|
|
828
1057
|
await handleGitCommit(req, res)
|
|
829
1058
|
return
|
package/server/storage.mjs
CHANGED
|
@@ -54,6 +54,7 @@ export const stores = new Set([
|
|
|
54
54
|
'sessions-metadata',
|
|
55
55
|
'scheduled-tasks',
|
|
56
56
|
'custom-agents',
|
|
57
|
+
'agent-profile-overrides',
|
|
57
58
|
])
|
|
58
59
|
|
|
59
60
|
// --- In-memory session bucket index ---
|
|
@@ -84,6 +85,7 @@ const soloConfigStores = {
|
|
|
84
85
|
settings: 'settings.json',
|
|
85
86
|
mcp: 'mcp-servers.json',
|
|
86
87
|
plugins: 'plugins.json',
|
|
88
|
+
'agent-profile-overrides': 'agent-profile-overrides.json',
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
const sharedConfigGroups = {
|
|
@@ -184,6 +186,9 @@ export const storageDir = path.join(dataDir, 'storage')
|
|
|
184
186
|
export const cacheDir = path.join(dataDir, 'cache')
|
|
185
187
|
export const logsDir = path.join(dataDir, 'logs')
|
|
186
188
|
export const userCommandsDir = path.join(dataDir, 'commands')
|
|
189
|
+
export const userAgentsDir = path.join(dataDir, 'agents')
|
|
190
|
+
export const builtinAgentsDir = path.join(userAgentsDir, 'builtin')
|
|
191
|
+
export const tempAgentsDir = path.join(cacheDir, 'global', 'tmp', 'agents')
|
|
187
192
|
|
|
188
193
|
const quickForgeConfigFile = path.join(configDir, 'config.json')
|
|
189
194
|
const configMigrationMarkerFile = path.join(configDir, '.layout-migrated')
|
|
@@ -878,6 +883,31 @@ export async function atomicProjectConfigUpdate(updateFn) {
|
|
|
878
883
|
})
|
|
879
884
|
}
|
|
880
885
|
|
|
886
|
+
export async function cleanOldTempAgents(retentionMs = 7 * 24 * 60 * 60 * 1000) {
|
|
887
|
+
try {
|
|
888
|
+
await fs.mkdir(tempAgentsDir, { recursive: true })
|
|
889
|
+
const cutoff = Date.now() - retentionMs
|
|
890
|
+
const sessionDirs = await fs.readdir(tempAgentsDir, { withFileTypes: true }).catch(() => [])
|
|
891
|
+
for (const sessionDir of sessionDirs) {
|
|
892
|
+
if (!sessionDir.isDirectory()) continue
|
|
893
|
+
const sessionPath = path.join(tempAgentsDir, sessionDir.name)
|
|
894
|
+
const runDirs = await fs.readdir(sessionPath, { withFileTypes: true }).catch(() => [])
|
|
895
|
+
await Promise.all(runDirs.map(async (runDir) => {
|
|
896
|
+
if (!runDir.isDirectory()) return
|
|
897
|
+
const runPath = path.join(sessionPath, runDir.name)
|
|
898
|
+
try {
|
|
899
|
+
const stat = await fs.stat(runPath)
|
|
900
|
+
if (stat.mtimeMs < cutoff) await fs.rm(runPath, { recursive: true, force: true })
|
|
901
|
+
} catch { /* ignore cleanup errors */ }
|
|
902
|
+
}))
|
|
903
|
+
const remaining = await fs.readdir(sessionPath).catch(() => [])
|
|
904
|
+
if (remaining.length === 0) await fs.rm(sessionPath, { recursive: true, force: true }).catch(() => {})
|
|
905
|
+
}
|
|
906
|
+
} catch {
|
|
907
|
+
// ignore cleanup errors
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
881
911
|
// Cached storage-initialization promise. ensureStorage() is idempotent (mkdir
|
|
882
912
|
// recursive, one-shot migration gated by a marker file, ensureJsonFile), so once
|
|
883
913
|
// it succeeds we can skip the redundant syscalls (~20 per call) every later call
|
|
@@ -894,6 +924,9 @@ export function ensureStorage() {
|
|
|
894
924
|
await Promise.all([
|
|
895
925
|
fs.mkdir(path.join(cacheDir, 'global', 'llm'), { recursive: true }),
|
|
896
926
|
fs.mkdir(path.join(cacheDir, 'global', 'tmp'), { recursive: true }),
|
|
927
|
+
fs.mkdir(tempAgentsDir, { recursive: true }),
|
|
928
|
+
fs.mkdir(userAgentsDir, { recursive: true }),
|
|
929
|
+
fs.mkdir(builtinAgentsDir, { recursive: true }),
|
|
897
930
|
fs.mkdir(path.join(cacheDir, 'projects'), { recursive: true }),
|
|
898
931
|
fs.mkdir(path.join(storageDir, 'conversations', 'global', 'sessions'), { recursive: true }),
|
|
899
932
|
fs.mkdir(path.join(storageDir, 'conversations', 'projects'), { recursive: true }),
|
|
@@ -901,6 +934,7 @@ export function ensureStorage() {
|
|
|
901
934
|
// they share the same file-tool capabilities as project conversations.
|
|
902
935
|
fs.mkdir(path.join(dataDir, 'workspace'), { recursive: true }),
|
|
903
936
|
cleanOldLogs(),
|
|
937
|
+
cleanOldTempAgents(),
|
|
904
938
|
])
|
|
905
939
|
|
|
906
940
|
await migrateUnifiedConfig()
|
package/server/subagents.mjs
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import { existsSync, promises as fs } from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { builtinAgentsDir, ensureStorage } from './storage.mjs'
|
|
5
|
+
|
|
1
6
|
const commonSubagentRules = `
|
|
2
7
|
You are a focused QuickForge subagent invoked by a parent coding assistant.
|
|
3
8
|
|
|
@@ -23,6 +28,9 @@ export const subagentDefinitions = [
|
|
|
23
28
|
mode: 'subagent',
|
|
24
29
|
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
30
|
allowedTools: ['read_file', 'grep_files', 'write_file', 'edit_file', 'run_command'],
|
|
31
|
+
capabilityPolicy: 'code-edit',
|
|
32
|
+
model: { mode: 'inherit' },
|
|
33
|
+
lifecycle: 'builtin',
|
|
26
34
|
allowFileMutations: true,
|
|
27
35
|
maxRuntimeMs: 30 * 60 * 1000,
|
|
28
36
|
maxToolCalls: 300,
|
|
@@ -34,6 +42,9 @@ export const subagentDefinitions = [
|
|
|
34
42
|
mode: 'subagent',
|
|
35
43
|
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
44
|
allowedTools: ['read_file', 'grep_files', 'run_command'],
|
|
45
|
+
capabilityPolicy: 'readonly-research',
|
|
46
|
+
model: { mode: 'inherit' },
|
|
47
|
+
lifecycle: 'builtin',
|
|
37
48
|
allowFileMutations: false,
|
|
38
49
|
maxRuntimeMs: 30 * 60 * 1000,
|
|
39
50
|
maxToolCalls: 300,
|
|
@@ -43,6 +54,59 @@ export const subagentDefinitions = [
|
|
|
43
54
|
|
|
44
55
|
const subagentByName = new Map(subagentDefinitions.map((definition) => [definition.name, definition]))
|
|
45
56
|
|
|
57
|
+
function markdownValue(value) {
|
|
58
|
+
return String(value ?? '').replace(/\r?\n/g, ' ').replace(/"/g, '\\"')
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function builtinSubagentMarkdown(definition) {
|
|
62
|
+
const body = definition.systemPrompt || ''
|
|
63
|
+
const hash = createHash('sha256').update(JSON.stringify({
|
|
64
|
+
name: definition.name,
|
|
65
|
+
label: definition.label,
|
|
66
|
+
description: definition.description,
|
|
67
|
+
allowedTools: definition.allowedTools,
|
|
68
|
+
capabilityPolicy: definition.capabilityPolicy,
|
|
69
|
+
body,
|
|
70
|
+
})).digest('hex')
|
|
71
|
+
return `---
|
|
72
|
+
name: ${definition.name}
|
|
73
|
+
label: "${markdownValue(definition.label || definition.name)}"
|
|
74
|
+
description: "${markdownValue(definition.description || '')}"
|
|
75
|
+
source: builtin
|
|
76
|
+
lifecycle: builtin
|
|
77
|
+
managed: true
|
|
78
|
+
managedBy: quickforge
|
|
79
|
+
managedHash: ${hash}
|
|
80
|
+
enabled-as-subagent: true
|
|
81
|
+
capabilityPolicy: ${definition.capabilityPolicy || 'review-only'}
|
|
82
|
+
tools: ${definition.allowedTools.join(', ')}
|
|
83
|
+
model:
|
|
84
|
+
mode: inherit
|
|
85
|
+
max-runtime-ms: ${definition.maxRuntimeMs || 30 * 60 * 1000}
|
|
86
|
+
max-tool-calls: ${definition.maxToolCalls || 300}
|
|
87
|
+
---
|
|
88
|
+
${body}
|
|
89
|
+
`
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function ensureBuiltinSubagentMarkdownFiles() {
|
|
93
|
+
await ensureStorage()
|
|
94
|
+
await fs.mkdir(builtinAgentsDir, { recursive: true })
|
|
95
|
+
await Promise.all(subagentDefinitions.map(async (definition) => {
|
|
96
|
+
const file = path.join(builtinAgentsDir, `${definition.name}.md`)
|
|
97
|
+
const content = builtinSubagentMarkdown(definition)
|
|
98
|
+
if (existsSync(file)) {
|
|
99
|
+
const current = await fs.readFile(file, 'utf8').catch(() => '')
|
|
100
|
+
if (current === content) return
|
|
101
|
+
}
|
|
102
|
+
await fs.writeFile(file, content, 'utf8')
|
|
103
|
+
}))
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function builtinSubagentProfilePath(name) {
|
|
107
|
+
return path.join(builtinAgentsDir, `${String(name || '').trim().toLowerCase()}.md`)
|
|
108
|
+
}
|
|
109
|
+
|
|
46
110
|
export function listSubagentSummaries() {
|
|
47
111
|
return subagentDefinitions.map(({ name, label, mode, description, allowedTools }) => ({
|
|
48
112
|
name,
|
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { Type } from 'typebox'
|
|
2
2
|
import { loadSelectedGlobalSkills, loadSelectedProjectSkills, mergeSkills } from '../skills.mjs'
|
|
3
3
|
|
|
4
|
+
const temporarySubagentSchema = Type.Object({
|
|
5
|
+
type: Type.Literal('temporary'),
|
|
6
|
+
name: Type.String({ description: 'Temporary subagent name, lowercase ASCII identifier.' }),
|
|
7
|
+
label: Type.Optional(Type.String({ description: 'Optional display name.' })),
|
|
8
|
+
description: Type.Optional(Type.String({ description: 'Short description of the temporary subagent.' })),
|
|
9
|
+
instructions: Type.String({ description: 'System instructions for the temporary subagent.' }),
|
|
10
|
+
capabilityPolicy: Type.Optional(Type.String({ description: 'Capability policy, defaults to readonly-research.' })),
|
|
11
|
+
tools: Type.Optional(Type.Array(Type.String({ description: 'Requested built-in tool names, constrained by capabilityPolicy.' }))),
|
|
12
|
+
model: Type.Optional(Type.Any({ description: 'Model reference. Omit or use { mode: "inherit" } to inherit the parent model.' })),
|
|
13
|
+
maxRuntimeMs: Type.Optional(Type.Number({ description: 'Optional max runtime in milliseconds.' })),
|
|
14
|
+
maxToolCalls: Type.Optional(Type.Number({ description: 'Optional max tool-call budget.' })),
|
|
15
|
+
})
|
|
16
|
+
|
|
4
17
|
// ---------------------------------------------------------------------------
|
|
5
18
|
// Canonical workspace tool definitions.
|
|
6
19
|
// These are the single source of truth for tool metadata (name, label,
|
|
@@ -17,7 +30,10 @@ export const subagentTool = {
|
|
|
17
30
|
label: 'Run subagent',
|
|
18
31
|
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
32
|
parameters: Type.Object({
|
|
20
|
-
subagent: Type.
|
|
33
|
+
subagent: Type.Union([
|
|
34
|
+
Type.String({ description: 'Agent Profile name to invoke.' }),
|
|
35
|
+
temporarySubagentSchema,
|
|
36
|
+
], { description: 'Agent Profile name or an inline temporary subagent spec to create and run once.' }),
|
|
21
37
|
task: Type.String({ description: 'Concrete, bounded task for the subagent. Do not delegate vague or open-ended work.' }),
|
|
22
38
|
context: Type.Optional(Type.String({ description: 'Relevant context from the parent conversation or current plan. Keep this focused.' })),
|
|
23
39
|
expectedOutput: Type.Optional(Type.String({ description: 'Optional output requirements for the subagent result.' })),
|
|
@@ -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{E as t,Ft as n,Nt as r,Rt as i,a,l as o,lt as s}from"./icons-DHoaB5uq.js";import{n as c}from"./react-vendor-BR9MG2D-.js";import{F as l,G as u,J as d,K as f,Y as p,nt as m,q as h,rt as g,st as _,tt as v}from"./index-CZb_mGGF.js";var y=e(i(),1),b=c();function x(){return{name:``,label:``,description:``,systemPrompt:``,allowedTools:[`read_file`,`grep_files`],maxRuntimeMs:`1800000`,maxToolCalls:`300`,enabledAsSubagent:!0}}function S(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 C(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 w(e){return!!(e.name.trim()&&e.label.trim()&&e.allowedTools.length>0)}async function T(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 E(){let[e,i]=(0,y.useState)([]),[c,E]=(0,y.useState)([]),[D,O]=(0,y.useState)(!1),[k,A]=(0,y.useState)(null),[j,M]=(0,y.useState)(()=>x()),[N,P]=(0,y.useState)(!1),[F,I]=(0,y.useState)(``),[L,R]=(0,y.useState)(!1),[z,B]=(0,y.useState)(),[V,H]=(0,y.useState)(`off`),[U,W]=(0,y.useState)(``),[G,K]=(0,y.useState)(null);async function q(){let[e,t]=await Promise.all([T(`/api/agent-profiles`),T(`/api/agent-profiles/available-tools`)]);i(e.agents),E(t.tools)}(0,y.useEffect)(()=>{let e=!1;async function t(){try{let[t,n]=await Promise.all([T(`/api/agent-profiles`),T(`/api/agent-profiles/available-tools`)]);if(e)return;i(t.agents),E(n.tools)}catch(t){e||W(t instanceof Error?t.message:_(`requestFailed`))}}return t(),()=>{e=!0}},[]),(0,y.useEffect)(()=>{let e=!1;async function t(){try{let t=await h(),n=await f(t),r=await d(t),i=r.model??await p(t)??n[0];if(e)return;B(i),H(r.thinkingLevel??u(i))}catch{}}return t(),()=>{e=!0}},[]),(0,y.useEffect)(()=>{if(!G)return;let e=()=>K(null);return window.addEventListener(`click`,e),window.addEventListener(`blur`,e),()=>{window.removeEventListener(`click`,e),window.removeEventListener(`blur`,e)}},[G]);let J=(0,y.useMemo)(()=>e.find(e=>e.id===k)??null,[e,k]);function Y(e,t){M(n=>({...n,[e]:t}))}function X(e){M(t=>({...t,allowedTools:t.allowedTools.includes(e)?t.allowedTools.filter(t=>t!==e):[...t.allowedTools,e]}))}function Z(){A(null),M(x()),I(``),W(``),O(!0)}function Q(e){A(e.id),M(S(e)),I(``),W(``),O(!0)}function $(){N||L||(O(!1),A(null),M(x()),I(``))}async function ee(){let e=F.trim();if(!e){W(_(`aiFillAgentInputRequired`));return}if(!z){W(_(`aiFillAgentNoModel`));return}R(!0),W(``);try{let t=await T(`/api/agent-profiles/ai-fill`,{method:`POST`,body:JSON.stringify({instruction:e,model:z,thinkingLevel:V})});M(e=>({...e,name:t.agent.name,label:t.agent.label,description:t.agent.description,systemPrompt:t.agent.systemPrompt}))}catch(e){W(e instanceof Error?e.message:_(`aiFillAgentFailed`))}finally{R(!1)}}async function te(){if(w(j)){P(!0),W(``);try{let e=C(j);k?await T(`/api/agent-profiles/${encodeURIComponent(k)}`,{method:`PATCH`,body:JSON.stringify(e)}):await T(`/api/agent-profiles`,{method:`POST`,body:JSON.stringify(e)}),$(),await q()}catch(e){W(e instanceof Error?e.message:_(`requestFailed`))}finally{P(!1)}}}async function ne(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)),K(null);try{await T(`/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)),W(t instanceof Error?t.message:_(`requestFailed`))}}async function re(e){if(!(e.builtin||e.readonly)&&await l({description:_(`confirmDeleteAgent`),confirmLabel:_(`confirmDelete`),cancelLabel:_(`cancel`),variant:`destructive`})){W(``);try{await T(`/api/agent-profiles/${encodeURIComponent(e.id)}`,{method:`DELETE`}),await q()}catch(e){W(e instanceof Error?e.message:_(`requestFailed`))}}}return D?(0,b.jsxs)(`div`,{className:`quickforge-settings-stack`,children:[(0,b.jsx)(`div`,{className:`quickforge-settings-heading`,children:(0,b.jsxs)(`h3`,{className:`quickforge-settings-title`,children:[_(J?`editAgent`:`createAgent`),(0,b.jsx)(v,{label:J?.readonly?_(`builtinAgentReadonly`):_(`agentsDescription`)})]})}),(0,b.jsxs)(`section`,{className:`quickforge-settings-section`,"aria-label":_(J?`editAgent`:`createAgent`),children:[(0,b.jsxs)(`div`,{className:`quickforge-settings-toolbar`,children:[(0,b.jsxs)(`button`,{className:`quickforge-settings-button quickforge-settings-button-secondary`,type:`button`,onClick:$,disabled:N||L,children:[(0,b.jsx)(n,{className:`mr-2 size-4`}),_(`back`)]}),(0,b.jsxs)(`div`,{className:`quickforge-settings-row-main`,children:[(0,b.jsx)(`div`,{className:`quickforge-settings-row-title`,children:_(J?`editAgent`:`createAgent`)}),J?.readonly?(0,b.jsx)(`div`,{className:`quickforge-settings-row-description`,children:_(`builtinAgentReadonly`)}):null]})]}),(0,b.jsx)(`div`,{className:`px-5 py-4`,children:(0,b.jsxs)(`div`,{className:`space-y-4`,children:[(0,b.jsxs)(`div`,{className:`rounded-2xl border border-border bg-muted/20 p-3`,children:[(0,b.jsxs)(`div`,{className:`mb-2 flex items-center gap-2 text-sm font-medium text-foreground`,children:[(0,b.jsx)(o,{className:`size-4 text-primary`}),_(`aiFillAgent`),(0,b.jsx)(v,{label:_(`aiFillAgentDescription`)})]}),(0,b.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:F,disabled:!!J?.readonly||L,onChange:e=>I(e.target.value),placeholder:_(`aiFillAgentPlaceholder`)}),(0,b.jsx)(`div`,{className:`mt-2 flex justify-end`,children:(0,b.jsxs)(m,{variant:`outline`,size:`sm`,onClick:()=>void ee(),disabled:!!J?.readonly||L||!F.trim(),children:[(0,b.jsx)(o,{className:`mr-1 size-3.5`}),_(L?`aiFillAgentLoading`:`aiFillAgent`)]})})]}),(0,b.jsxs)(`div`,{className:`grid gap-3 sm:grid-cols-2`,children:[(0,b.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[_(`agentName`),(0,b.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:j.name,disabled:!!J?.readonly,onChange:e=>Y(`name`,e.target.value),placeholder:`reviewer`})]}),(0,b.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[_(`agentLabel`),(0,b.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:j.label,disabled:!!J?.readonly,onChange:e=>Y(`label`,e.target.value),placeholder:_(`agentLabelPlaceholder`)})]})]}),(0,b.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[_(`agentDescription`),(0,b.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:j.description,disabled:!!J?.readonly,onChange:e=>Y(`description`,e.target.value)})]}),(0,b.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[_(`agentSystemPrompt`),(0,b.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:j.systemPrompt,disabled:!!J?.readonly,onChange:e=>Y(`systemPrompt`,e.target.value)})]}),(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`div`,{className:`mb-2 text-sm font-medium text-foreground`,children:_(`allowedTools`)}),(0,b.jsx)(`div`,{className:`grid gap-2 sm:grid-cols-2`,children:c.map(e=>(0,b.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,b.jsx)(`input`,{type:`checkbox`,className:`mt-1`,disabled:!!J?.readonly,checked:j.allowedTools.includes(e.name),onChange:()=>X(e.name)}),(0,b.jsxs)(`span`,{children:[(0,b.jsx)(`span`,{className:`font-medium text-foreground`,children:e.label}),(0,b.jsx)(`span`,{className:`ml-2 font-mono text-xs text-muted-foreground`,children:e.name}),e.riskLevel===`dangerous`?(0,b.jsx)(`span`,{className:`ml-2 rounded-full bg-amber-500/10 px-2 py-0.5 text-xs text-amber-700`,children:_(`highRiskTool`)}):null,(0,b.jsx)(`span`,{className:`mt-1 block text-xs text-muted-foreground`,children:e.description})]})]},e.name))})]}),(0,b.jsxs)(`div`,{className:`grid gap-3 sm:grid-cols-2`,children:[(0,b.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[_(`maxRuntimeMs`),(0,b.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:j.maxRuntimeMs,disabled:!!J?.readonly,onChange:e=>Y(`maxRuntimeMs`,e.target.value)})]}),(0,b.jsxs)(`label`,{className:`block text-sm font-medium text-foreground`,children:[_(`maxToolCalls`),(0,b.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:j.maxToolCalls,disabled:!!J?.readonly,onChange:e=>Y(`maxToolCalls`,e.target.value)})]})]}),(0,b.jsxs)(`label`,{className:`flex items-center gap-2 text-sm text-foreground`,children:[(0,b.jsx)(`input`,{type:`checkbox`,checked:j.enabledAsSubagent,disabled:!!J?.readonly,onChange:e=>Y(`enabledAsSubagent`,e.target.checked)}),_(`enabledAsSubagent`)]}),U?(0,b.jsx)(`div`,{className:`rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive`,children:U}):null]})}),(0,b.jsxs)(`div`,{className:`quickforge-settings-divider flex justify-end gap-2 px-5 py-4`,children:[(0,b.jsx)(m,{variant:`outline`,onClick:$,disabled:N||L,children:_(`cancel`)}),(0,b.jsx)(m,{onClick:te,disabled:N||L||!!J?.readonly||!w(j),children:_(`save`)})]})]})]}):(0,b.jsx)(`div`,{className:`quickforge-settings-stack`,children:(0,b.jsxs)(`section`,{className:`quickforge-settings-section`,"aria-label":_(`agentsTab`),children:[(0,b.jsxs)(`div`,{className:`quickforge-settings-toolbar`,children:[(0,b.jsxs)(`div`,{className:`quickforge-settings-row-main`,children:[(0,b.jsxs)(`div`,{className:`quickforge-settings-row-title`,children:[(0,b.jsx)(r,{className:`size-4 text-primary`}),_(`agentsTab`)]}),(0,b.jsx)(`div`,{className:`quickforge-settings-row-description`,children:_(`agentsDescription`)})]}),(0,b.jsx)(`button`,{className:`quickforge-settings-button quickforge-settings-button-primary`,type:`button`,onClick:Z,children:_(`createAgent`)})]}),U?(0,b.jsx)(`div`,{className:`quickforge-settings-alert quickforge-settings-warning-attached`,children:U}):null,e.length===0?(0,b.jsx)(`div`,{className:`quickforge-settings-empty-row`,children:_(`loading`)}):e.map(e=>(0,b.jsxs)(`div`,{className:`quickforge-settings-list-item`,children:[(0,b.jsxs)(`div`,{className:`quickforge-settings-list-item-main`,children:[(0,b.jsxs)(`div`,{className:`quickforge-settings-row-title`,children:[e.label,e.builtin?(0,b.jsx)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-info`,children:_(`builtinAgent`)}):null,(0,b.jsx)(`span`,{className:g(`quickforge-settings-badge`,e.enabledAsSubagent?`quickforge-settings-badge-success`:`quickforge-settings-badge-muted`),children:e.enabledAsSubagent?_(`enabled`):_(`disabled`)})]}),(0,b.jsx)(`div`,{className:`quickforge-settings-row-description quickforge-settings-mono`,children:e.name}),e.source&&!e.builtin?(0,b.jsxs)(`div`,{className:`quickforge-settings-row-description`,children:[e.source,e.relativePath?` · ${e.relativePath}`:``]}):null,(0,b.jsx)(`div`,{className:`quickforge-settings-row-description`,children:e.description||_(`noDescription`)}),(0,b.jsx)(`div`,{className:`quickforge-settings-meta`,children:e.allowedTools.map(e=>(0,b.jsx)(`code`,{className:`quickforge-settings-command-name`,children:e},e))}),(0,b.jsxs)(`div`,{className:`quickforge-settings-meta`,children:[(0,b.jsxs)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:[_(`maxRuntimeMs`),e.maxRuntimeMs??`-`]}),(0,b.jsxs)(`span`,{className:`quickforge-settings-badge quickforge-settings-badge-muted`,children:[_(`maxToolCalls`),e.maxToolCalls??`-`]})]})]}),(0,b.jsxs)(`div`,{className:`quickforge-settings-list-item-actions`,onClick:e=>e.stopPropagation(),children:[(0,b.jsxs)(`label`,{className:`quickforge-settings-switch`,"aria-disabled":e.builtin||e.readonly?`true`:`false`,title:e.enabledAsSubagent?_(`disableAsSubagent`):_(`enableAsSubagent`),children:[(0,b.jsx)(`input`,{type:`checkbox`,checked:e.enabledAsSubagent,disabled:e.builtin||e.readonly,onChange:()=>void ne(e)}),(0,b.jsx)(`span`,{"aria-hidden":`true`})]}),(0,b.jsxs)(`div`,{className:`relative`,children:[(0,b.jsx)(`button`,{className:`quickforge-settings-icon-action`,type:`button`,onClick:()=>K(G===e.id?null:e.id),title:_(`moreActions`),children:(0,b.jsx)(s,{className:`size-4`})}),G===e.id?(0,b.jsxs)(`div`,{className:`absolute right-0 z-20 mt-1 w-36 overflow-hidden rounded-xl border border-border bg-popover py-1 text-sm shadow-quickforge`,children:[(0,b.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`,disabled:e.builtin||e.readonly,onClick:()=>{K(null),Q(e)},children:[(0,b.jsx)(t,{className:`size-3.5`}),_(`editTask`)]}),(0,b.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`,disabled:e.builtin||e.readonly,onClick:()=>{K(null),re(e)},children:[(0,b.jsx)(a,{className:`size-3.5`}),_(`delete`)]})]}):null]})]})]},e.id))]})})}export{E as AgentProfilesPage};
|