@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.
Files changed (164) hide show
  1. package/README.md +452 -452
  2. package/dist/assets/AgentProfilesPage-BtZpDV-3.js +1 -0
  3. package/dist/assets/ChatPanelHost-BF1V-xOV.js +244 -0
  4. package/dist/assets/{PluginsPage-3pZG7D7e.js → PluginsPage-CCIj8-Mn.js} +1 -1
  5. package/dist/assets/ScheduledTasksPage-DIMewQ3i.js +2 -0
  6. package/dist/assets/{SettingsWorkspacePage-DWkPyGP_.js → SettingsWorkspacePage-BPD495WT.js} +296 -290
  7. package/dist/assets/SharedConversationPage-B_oMTp7B.js +1 -0
  8. package/dist/assets/TerminalDock-wgW7kSC4.js +2 -0
  9. package/dist/assets/WorkspaceInspector-CdX45r3u.js +13 -0
  10. package/dist/assets/abnfDiagram-VRR7QNED-Dl_Fvlqg.js +1 -0
  11. package/dist/assets/arc-Bhg4c0ht.js +1 -0
  12. package/dist/assets/architecture-TIHT7OUA-BgP-IMeo.js +1 -0
  13. package/dist/assets/architectureDiagram-ZJ3FMSHR-BuojN27B.js +36 -0
  14. package/dist/assets/array-CYkMkqnU.js +1 -0
  15. package/dist/assets/blockDiagram-677ZJIJ3-alDLbrR1.js +132 -0
  16. package/dist/assets/c4Diagram-LMCZKHZV-D1lKa4VT.js +10 -0
  17. package/dist/assets/channel-Rz20dKTK.js +1 -0
  18. package/dist/assets/chunk-2Q5K7J3B-Cb14ZF_k.js +1 -0
  19. package/dist/assets/chunk-32BRIVSS-CwWLNjMZ.js +1 -0
  20. package/dist/assets/chunk-52WLFC77-Be569m7d.js +10 -0
  21. package/dist/assets/chunk-5FCAYU7R-BSvoiWV1.js +1 -0
  22. package/dist/assets/chunk-5HE753X5-CRgsy8WS.js +1 -0
  23. package/dist/assets/chunk-5JV3BV7I-CdW477UR.js +1 -0
  24. package/dist/assets/chunk-5TONJI2A-CdtKzSmp.js +2 -0
  25. package/dist/assets/chunk-5VM5RSS4-BKjjssLZ.js +15 -0
  26. package/dist/assets/chunk-7BUUIJ7U-ByAXiXdn.js +1 -0
  27. package/dist/assets/chunk-BIQX33UG-DeG8SCT1.js +1 -0
  28. package/dist/assets/chunk-C7G6YPKG-D_zHT68Q.js +1 -0
  29. package/dist/assets/chunk-CQNSW5MT-CHpHoUSu.js +1 -0
  30. package/dist/assets/chunk-CYSBUYHQ-CRP50Vov.js +1 -0
  31. package/dist/assets/chunk-EMLP6XTP-DX6uQQ2v.js +1 -0
  32. package/dist/assets/chunk-EX3LRPZG-DAJRNkHR.js +231 -0
  33. package/dist/assets/chunk-FWX5IMBZ-XbM5yomC.js +2 -0
  34. package/dist/assets/chunk-HOUHSVGY-Ci0ljnb1.js +1 -0
  35. package/dist/assets/chunk-ICXQ74PX-Dy2WJ71z.js +2 -0
  36. package/dist/assets/chunk-JG7HCLWE-2PrkzHkt.js +2 -0
  37. package/dist/assets/chunk-JWPE2WC7-D32uacSM.js +1 -0
  38. package/dist/assets/chunk-KEIR6QF5-77hsebd7.js +161 -0
  39. package/dist/assets/chunk-MOJQB5TN-DIAFLtiS.js +88 -0
  40. package/dist/assets/chunk-MOZMSUNE-BbscOaEz.js +1 -0
  41. package/dist/assets/chunk-OGEWGWER-ivBVxzKe.js +1 -0
  42. package/dist/assets/chunk-OSBZ3O6U-frD0hkdx.js +1 -0
  43. package/dist/assets/chunk-PUDLZKDR-B-37Rxae.js +156 -0
  44. package/dist/assets/chunk-Q4XR5HBZ-Y90m2RTe.js +11 -0
  45. package/dist/assets/chunk-QBLGF6JB-DixBqslB.js +1 -0
  46. package/dist/assets/chunk-R7FJI6CG-DXS1UXn_.js +1 -0
  47. package/dist/assets/chunk-RYQCIY6F-n4JxhNd0.js +1 -0
  48. package/dist/assets/chunk-U6XO7XAA-yHlgxMmn.js +2 -0
  49. package/dist/assets/chunk-V7JOEXUC-Z6kuZfbl.js +206 -0
  50. package/dist/assets/chunk-VAUOI2AC-BOZvd2Sn.js +1 -0
  51. package/dist/assets/chunk-VR4S4FIN-Cs_SIQma.js +1 -0
  52. package/dist/assets/chunk-WYO6CB5R-CGExYbV-.js +128 -0
  53. package/dist/assets/chunk-XXDRQBXY-CD_EGCtT.js +1 -0
  54. package/dist/assets/chunk-Y2CYZVJY-CQltPwYk.js +1 -0
  55. package/dist/assets/chunk-YOTPTUD7-DopPXWSl.js +1 -0
  56. package/dist/assets/chunk-ZGVPDNZ5-CJHOeqMb.js +62 -0
  57. package/dist/assets/chunk-ZIRB5QZD-BBVc5mf6.js +32 -0
  58. package/dist/assets/classDiagram-OUVF2IWQ-C_ozTKCt.js +1 -0
  59. package/dist/assets/classDiagram-v2-EOCWNBFH-FOfFbbZF.js +1 -0
  60. package/dist/assets/cose-bilkent-JH36ORCC-DLjV-dTm.js +1 -0
  61. package/dist/assets/cynefin-VYW2F7L2-BzPhLtd5.js +1 -0
  62. package/dist/assets/cynefinDiagram-TSTJHNR4-BfH3zVS-.js +62 -0
  63. package/dist/assets/cytoscape.esm-DwdZnGOm.js +321 -0
  64. package/dist/assets/dagre-CY3D84Cl.js +1 -0
  65. package/dist/assets/dagre-VKFMJZFB-BIhwZCIZ.js +4 -0
  66. package/dist/assets/defaultLocale-Beh6XjaL.js +1 -0
  67. package/dist/assets/diagram-FQU43EPY-BHnzNM2w.js +3 -0
  68. package/dist/assets/diagram-G47NLZAW-C7-ZlmFT.js +24 -0
  69. package/dist/assets/diagram-NH7WQ7WH-C-P9Y7IR.js +24 -0
  70. package/dist/assets/diagram-OA4YK3LP-Ci5lZ_T5.js +30 -0
  71. package/dist/assets/diagram-WEI45ONY-C1xuK2bs.js +41 -0
  72. package/dist/assets/dist-DelZBZpK.js +1 -0
  73. package/dist/assets/ebnfDiagram-CCIWWBDH-DKQ5NWMD.js +1 -0
  74. package/dist/assets/erDiagram-Q63AITRT-HAWySadP.js +85 -0
  75. package/dist/assets/eventmodeling-45OFAUF4-BcgGbVJ-.js +1 -0
  76. package/dist/assets/flowDiagram-23GEKE2U-dtkzcC8E.js +1 -0
  77. package/dist/assets/ganttDiagram-NO4QXBWP-C1hwIcOA.js +292 -0
  78. package/dist/assets/gitGraph-TEB2WS4Q-VByWboBU.js +1 -0
  79. package/dist/assets/gitGraphDiagram-IHSO6WYX-DYUYxs1R.js +106 -0
  80. package/dist/assets/graphlib-CAISHZh4.js +1 -0
  81. package/dist/assets/icons-C7j5jdKo.js +1 -0
  82. package/dist/assets/index-Bxg5fT2p.css +3 -0
  83. package/dist/assets/index-DKmdePUP.js +63 -0
  84. package/dist/assets/info-DKCQHKI2-CZeDLZxx.js +1 -0
  85. package/dist/assets/infoDiagram-FWYZ7A6U-CnIL6tv4.js +2 -0
  86. package/dist/assets/init-Rr1s_RiX.js +1 -0
  87. package/dist/assets/ishikawaDiagram-FXEZZL3T-B7WXNycQ.js +70 -0
  88. package/dist/assets/journeyDiagram-5HDEW3XC-D4HvioyY.js +139 -0
  89. package/dist/assets/kanban-definition-HUTT4EX6-CDs763eS.js +89 -0
  90. package/dist/assets/line-ikKkQiFp.js +1 -0
  91. package/dist/assets/linear-GqPyxQkN.js +1 -0
  92. package/dist/assets/map-CJwjCvwr.js +1 -0
  93. package/dist/assets/mcp-servers-dialog-jHlDR9jP.js +5 -0
  94. package/dist/assets/mermaid-parser.core-CoE8cccr.js +4 -0
  95. package/dist/assets/mermaid-renderer-BmbSAmpG.js +2 -0
  96. package/dist/assets/mermaid.core-DlFSL90j.js +11 -0
  97. package/dist/assets/mindmap-definition-LN4V7U3C-DLdq0K9C.js +96 -0
  98. package/dist/assets/{monaco-CzxeAjEw.js → monaco-CdOyGlWD.js} +1 -1
  99. package/dist/assets/ordinal-CibZU3PM.js +1 -0
  100. package/dist/assets/{otel-DQf3c6v2.js → otel-BNGM50C-.js} +1 -1
  101. package/dist/assets/packet-7NZHBO7P-decrrxyX.js +1 -0
  102. package/dist/assets/path-BAQ3hXlG.js +1 -0
  103. package/dist/assets/pegDiagram-2B236MQR-Cm1kPRC2.js +1 -0
  104. package/dist/assets/{pi-ai-DjJwSQBQ.js → pi-ai-Cr3rpCXT.js} +2 -2
  105. package/dist/assets/{pi-web-ui-DqxtBlZL.js → pi-web-ui-DoRGtREc.js} +371 -371
  106. package/dist/assets/pie-RZYD4A2V-J1CasQZl.js +1 -0
  107. package/dist/assets/pieDiagram-ENE6RG2P-Df05wjjo.js +39 -0
  108. package/dist/assets/quadrantDiagram-ABIIQ3AL-BjCwGxJC.js +7 -0
  109. package/dist/assets/radar-I7S5WNFK-B-sZytUA.js +1 -0
  110. package/dist/assets/railroad-3IZDKUUU-BSaqNPm8.js +1 -0
  111. package/dist/assets/railroad-abnf-AHOZXSZD-lzZR7PxT.js +1 -0
  112. package/dist/assets/railroad-ebnf-EBAXGLYW-DQtz1Fh9.js +1 -0
  113. package/dist/assets/railroad-peg-LSFZ7HO6-CpMsuGY3.js +1 -0
  114. package/dist/assets/railroadDiagram-RFXS5EU6-CTUvpKkv.js +1 -0
  115. package/dist/assets/{react-vendor-BR9MG2D-.js → react-vendor-VqdnQHHS.js} +1 -1
  116. package/dist/assets/requirementDiagram-TGXJPOKE-BU1kvEku.js +84 -0
  117. package/dist/assets/rough.esm-Djo4Abte.js +1 -0
  118. package/dist/assets/sankeyDiagram-HTMAVEWB-BHmvxVqH.js +40 -0
  119. package/dist/assets/sequenceDiagram-DBY2YBRQ-CTpwICgg.js +162 -0
  120. package/dist/assets/sizeCapture-X5ZJPWSS-BbLhGaa4.js +1 -0
  121. package/dist/assets/{skills-dialog-B0jNVGZW.js → skills-dialog-DvSnPPzu.js} +1 -1
  122. package/dist/assets/src-B4Tvg3gT.js +1 -0
  123. package/dist/assets/stateDiagram-2N3HPSRC-BxuDwIgT.js +1 -0
  124. package/dist/assets/stateDiagram-v2-6OUMAXLB-BGLshxcY.js +1 -0
  125. package/dist/assets/swimlanes-5IMT3BWC-EkZsiylV.js +2 -0
  126. package/dist/assets/swimlanesDiagram-G3AALYLV-CqdqxA-b.js +8 -0
  127. package/dist/assets/timeline-definition-FHXFAJF6-C9ewOCTa.js +120 -0
  128. package/dist/assets/treeView-QDETBFTQ-Byb71heW.js +1 -0
  129. package/dist/assets/treemap-6X3UGDF4-CMV1SJsq.js +1 -0
  130. package/dist/assets/vennDiagram-L72KCM5P-CR49Y4Lp.js +34 -0
  131. package/dist/assets/wardley-OPB4EBWU-DtCHtr2V.js +1 -0
  132. package/dist/assets/wardleyDiagram-EHGQE667-Cn0GgwWi.js +78 -0
  133. package/dist/assets/xychartDiagram-FW5EYKEG-DlmPNAvd.js +7 -0
  134. package/dist/index.html +8 -8
  135. package/package.json +2 -1
  136. package/server/agent-manager.mjs +174 -9
  137. package/server/agent-profile-files.mjs +86 -6
  138. package/server/agent-profile-schema.mjs +197 -0
  139. package/server/agent-profiles.mjs +172 -41
  140. package/server/auto-compaction.mjs +2 -2
  141. package/server/project-route-context.mjs +25 -0
  142. package/server/routes/agent-profiles.mjs +9 -3
  143. package/server/routes/backup.mjs +135 -48
  144. package/server/routes/project.mjs +6 -10
  145. package/server/routes/workspace.mjs +240 -11
  146. package/server/storage.mjs +34 -0
  147. package/server/subagents.mjs +64 -0
  148. package/server/tools/definitions.mjs +17 -1
  149. package/server/utils/platform.mjs +4 -4
  150. package/dist/assets/AgentProfilesPage-R7N7eDq5.js +0 -1
  151. package/dist/assets/ChatPanelHost-C4orgd5l.js +0 -242
  152. package/dist/assets/ScheduledTasksPage-BAqoK4xk.js +0 -2
  153. package/dist/assets/SharedConversationPage-C-1n_MmB.js +0 -1
  154. package/dist/assets/TerminalDock-B6KAXkIU.js +0 -2
  155. package/dist/assets/WorkspaceInspector-CkXyD_k4.js +0 -3
  156. package/dist/assets/WorkspaceReaderDialog-Dp2QAwBo.js +0 -1
  157. package/dist/assets/diff-line-counts-Dm5kKQOo.js +0 -10
  158. package/dist/assets/icons-DHoaB5uq.js +0 -1
  159. package/dist/assets/index-C77QCHE9.css +0 -3
  160. package/dist/assets/index-CZb_mGGF.js +0 -63
  161. package/dist/assets/mcp-servers-dialog-VbGvNekJ.js +0 -5
  162. package/dist/assets/useAppTheme-DsabFF9D.js +0 -1
  163. /package/dist/assets/{plugin-api-Ds5xGIL7.js → plugin-api-BbUkYm3Y.js} +0 -0
  164. /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(active?.path || '', target)
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
- if (active?.path) {
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 currentGitBranch(workspaceRoot) {
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: await currentGitBranch(context.workspaceRoot),
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 = (await git(['diff', '--stat'], context.workspaceRoot, { allowFailure: true })).stdout.toString('utf8')
419
- const worktreeDiff = (await git(['diff'], context.workspaceRoot, { allowFailure: true })).stdout.toString('utf8')
420
- const files = status.files.map((file) => `- ${file.status}${file.staged ? ' staged' : ''}${file.unstaged ? ' unstaged' : ''}: ${file.oldPath ? `${file.oldPath} -> ` : ''}${file.path}`).join('\n')
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 commitGitChanges(context, body?.message, Boolean(body?.includeUnstaged))
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
@@ -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()
@@ -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.String({ description: 'Agent Profile name to invoke.' }),
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(`Directory does not exist: ${resolved}`)
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(`Directory does not exist: ${resolved}`)
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};