@shawnstack/quickforge 1.6.7 → 1.6.9
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 +10 -10
- package/dist/assets/AgentProfilesPage-5eqU0Cjk.js +1 -0
- package/dist/assets/ChatPanelHost-BNFeNRy-.js +244 -0
- package/dist/assets/{PluginsPage-DDxjlhiK.js → PluginsPage-C5OClNdT.js} +1 -1
- package/dist/assets/ScheduledTasksPage-yT10oaaM.js +2 -0
- package/dist/assets/{SettingsWorkspacePage-B_7o3noo.js → SettingsWorkspacePage-DuXwIeLd.js} +346 -335
- package/dist/assets/SharedConversationPage-ClpUSa0F.js +1 -0
- package/dist/assets/TerminalDock-BmL-FeKv.js +2 -0
- package/dist/assets/WorkspaceInspector-yH23pXpj.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-Qof73j0o.css +3 -0
- package/dist/assets/index-_ut7awrd.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-CJ4HKWVZ.js → mcp-servers-dialog-DC2Zfojn.js} +2 -2
- 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-FCb94ino.js → monaco-CdOyGlWD.js} +1 -1
- package/dist/assets/ordinal-CibZU3PM.js +1 -0
- package/dist/assets/otel-BNGM50C-.js +3 -0
- 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-Cr3rpCXT.js +131 -0
- package/dist/assets/pi-web-ui-DoRGtREc.js +2803 -0
- 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-CmCLl7K_.js → skills-dialog-Mnj8fZwq.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 +4 -3
- 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/ai-http-logger.mjs +1 -1
- package/server/auto-compaction.mjs +2 -2
- package/server/project-route-context.mjs +25 -0
- package/server/routes/agent-profiles.mjs +10 -4
- package/server/routes/models.mjs +1 -1
- package/server/routes/project.mjs +6 -10
- package/server/routes/scheduled-tasks.mjs +1 -1
- package/server/routes/workspace.mjs +185 -12
- package/server/session-utils.mjs +1 -1
- package/server/storage.mjs +34 -0
- package/server/subagents.mjs +64 -0
- package/server/tools/definitions.mjs +17 -1
- package/server/utils/platform.mjs +25 -2
- package/dist/assets/AgentProfilesPage-XJdWmkQb.js +0 -1
- package/dist/assets/ChatPanelHost-BrUHIzBw.js +0 -242
- package/dist/assets/ScheduledTasksPage-Bw-pSpbv.js +0 -2
- package/dist/assets/SharedConversationPage-B5DPQla3.js +0 -1
- package/dist/assets/TerminalDock-CVObjSM6.js +0 -2
- package/dist/assets/WorkspaceInspector-Dk5887WR.js +0 -3
- package/dist/assets/WorkspaceReaderDialog-D-_BBvud.js +0 -1
- package/dist/assets/diff-line-counts-DagL0jQr.js +0 -10
- package/dist/assets/icons-DHoaB5uq.js +0 -1
- package/dist/assets/index-C77QCHE9.css +0 -3
- package/dist/assets/index-DeUvsbzW.js +0 -63
- package/dist/assets/pi-ai-Cx633yhb.js +0 -134
- package/dist/assets/pi-web-ui-DFNE2m5b.js +0 -2770
- package/dist/assets/useAppTheme-BwZ5OEf3.js +0 -1
- /package/dist/assets/{plugin-api-UKg_cgSG.js → plugin-api-BbUkYm3Y.js} +0 -0
- /package/dist/assets/{xterm-BtSXYfUR.js → xterm-CGiQzDiR.js} +0 -0
|
@@ -1,17 +1,36 @@
|
|
|
1
|
+
import { existsSync, promises as fs } from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
1
3
|
import { randomUUID } from 'node:crypto'
|
|
2
|
-
import { readStore,
|
|
3
|
-
import { subagentDefinitions } from './subagents.mjs'
|
|
4
|
+
import { readStore, userAgentsDir, writeStore } from './storage.mjs'
|
|
5
|
+
import { builtinSubagentProfilePath, ensureBuiltinSubagentMarkdownFiles, subagentDefinitions } from './subagents.mjs'
|
|
4
6
|
import { workspaceTools } from './tools/definitions.mjs'
|
|
5
7
|
import { defaultGlobalWorkspaceContext, projectContextFromId } from './project-config.mjs'
|
|
6
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
deleteUserAgentProfileMarkdown,
|
|
10
|
+
loadFileAgentProfiles,
|
|
11
|
+
userAgentProfileFilePath,
|
|
12
|
+
writeUserAgentProfileMarkdown,
|
|
13
|
+
} from './agent-profile-files.mjs'
|
|
14
|
+
import {
|
|
15
|
+
AGENT_PROFILE_TOOL_NAMES,
|
|
16
|
+
inferCapabilityPolicy,
|
|
17
|
+
modelReferenceSnapshot,
|
|
18
|
+
normalizeAgentProfileThinkingLevel,
|
|
19
|
+
normalizeCapabilityPolicy,
|
|
20
|
+
normalizeModelReference,
|
|
21
|
+
validateAgentProfileTools,
|
|
22
|
+
validateModelReference,
|
|
23
|
+
} from './agent-profile-schema.mjs'
|
|
7
24
|
|
|
8
25
|
const STORE = 'custom-agents'
|
|
26
|
+
const BUILTIN_OVERRIDES_STORE = 'agent-profile-overrides'
|
|
9
27
|
const RESERVED_NAMES = new Set(subagentDefinitions.map((definition) => definition.name))
|
|
10
|
-
export const AGENT_PROFILE_TOOL_NAMES = ['read_file', 'grep_files', 'write_file', 'edit_file', 'run_command']
|
|
11
28
|
const allowedToolNames = new Set(AGENT_PROFILE_TOOL_NAMES)
|
|
12
29
|
const nameRegex = /^[a-z][a-z0-9_-]{1,39}$/
|
|
13
30
|
const DEFAULT_MAX_RUNTIME_MS = 30 * 60 * 1000
|
|
14
31
|
const DEFAULT_MAX_TOOL_CALLS = 300
|
|
32
|
+
const MIGRATION_MARKER_FILE = path.join(userAgentsDir, '.custom-agents-migrated.json')
|
|
33
|
+
let customAgentMigrationPromise = null
|
|
15
34
|
|
|
16
35
|
function requestError(message, statusCode = 400) {
|
|
17
36
|
const error = new Error(message)
|
|
@@ -46,7 +65,7 @@ function normalizeOptionalRuntime(value, fallback) {
|
|
|
46
65
|
return Math.min(Math.max(Math.round(parsed), 1000), DEFAULT_MAX_RUNTIME_MS)
|
|
47
66
|
}
|
|
48
67
|
|
|
49
|
-
function builtinProfileFromSubagent(definition) {
|
|
68
|
+
function builtinProfileFromSubagent(definition, override = null) {
|
|
50
69
|
return {
|
|
51
70
|
id: definition.name,
|
|
52
71
|
name: definition.name,
|
|
@@ -54,20 +73,28 @@ function builtinProfileFromSubagent(definition) {
|
|
|
54
73
|
description: definition.description || '',
|
|
55
74
|
systemPrompt: definition.systemPrompt || '',
|
|
56
75
|
allowedTools: [...definition.allowedTools],
|
|
76
|
+
capabilityPolicy: definition.capabilityPolicy || inferCapabilityPolicy(definition.allowedTools),
|
|
77
|
+
model: modelReferenceSnapshot(override?.model || definition.model),
|
|
78
|
+
thinkingLevel: 'inherit',
|
|
79
|
+
lifecycle: 'builtin',
|
|
80
|
+
managed: true,
|
|
57
81
|
maxRuntimeMs: definition.maxRuntimeMs || DEFAULT_MAX_RUNTIME_MS,
|
|
58
82
|
maxToolCalls: definition.maxToolCalls || DEFAULT_MAX_TOOL_CALLS,
|
|
59
83
|
enabledAsSubagent: true,
|
|
60
84
|
builtin: true,
|
|
61
85
|
source: 'builtin',
|
|
62
86
|
readonly: true,
|
|
87
|
+
filePath: builtinSubagentProfilePath(definition.name),
|
|
88
|
+
relativePath: `~/.quickforge/agents/builtin/${definition.name}.md`,
|
|
63
89
|
allowFileMutations: definition.allowFileMutations === true,
|
|
64
90
|
createdAt: 'builtin',
|
|
65
91
|
updatedAt: 'builtin',
|
|
66
92
|
}
|
|
67
93
|
}
|
|
68
94
|
|
|
69
|
-
export function listBuiltinAgentProfiles() {
|
|
70
|
-
|
|
95
|
+
export async function listBuiltinAgentProfiles() {
|
|
96
|
+
const overrides = await readStore(BUILTIN_OVERRIDES_STORE).catch(() => ({}))
|
|
97
|
+
return subagentDefinitions.map((definition) => builtinProfileFromSubagent(definition, overrides?.[definition.name]))
|
|
71
98
|
}
|
|
72
99
|
|
|
73
100
|
function normalizeProfileInput(input, existing = null, { creating = false } = {}) {
|
|
@@ -85,6 +112,13 @@ function normalizeProfileInput(input, existing = null, { creating = false } = {}
|
|
|
85
112
|
for (const toolName of allowedTools) {
|
|
86
113
|
if (!allowedToolNames.has(toolName)) throw requestError(`Unsupported tool for custom agent: ${toolName}`)
|
|
87
114
|
}
|
|
115
|
+
const capabilityPolicy = normalizeCapabilityPolicy(
|
|
116
|
+
input?.capabilityPolicy ?? existing?.capabilityPolicy,
|
|
117
|
+
allowedTools,
|
|
118
|
+
)
|
|
119
|
+
validateAgentProfileTools(allowedTools, capabilityPolicy)
|
|
120
|
+
const model = validateModelReference(normalizeModelReference(input?.model ?? existing?.model))
|
|
121
|
+
const thinkingLevel = normalizeAgentProfileThinkingLevel(input?.thinkingLevel ?? existing?.thinkingLevel)
|
|
88
122
|
|
|
89
123
|
return {
|
|
90
124
|
id: existing?.id || `agent-${randomUUID()}`,
|
|
@@ -93,12 +127,18 @@ function normalizeProfileInput(input, existing = null, { creating = false } = {}
|
|
|
93
127
|
description: String(input?.description ?? existing?.description ?? '').trim().slice(0, 500),
|
|
94
128
|
systemPrompt: String(input?.systemPrompt ?? existing?.systemPrompt ?? '').trim(),
|
|
95
129
|
allowedTools,
|
|
130
|
+
capabilityPolicy,
|
|
131
|
+
model,
|
|
132
|
+
thinkingLevel,
|
|
133
|
+
lifecycle: existing?.lifecycle || 'persistent',
|
|
96
134
|
maxRuntimeMs: normalizeOptionalRuntime(input?.maxRuntimeMs ?? existing?.maxRuntimeMs, DEFAULT_MAX_RUNTIME_MS),
|
|
97
135
|
maxToolCalls: normalizeOptionalPositiveInteger(input?.maxToolCalls ?? existing?.maxToolCalls, DEFAULT_MAX_TOOL_CALLS, 300),
|
|
98
136
|
enabledAsSubagent: input?.enabledAsSubagent === undefined ? Boolean(existing?.enabledAsSubagent ?? true) : input.enabledAsSubagent === true,
|
|
99
137
|
builtin: false,
|
|
100
|
-
source: '
|
|
138
|
+
source: 'user',
|
|
101
139
|
readonly: false,
|
|
140
|
+
managed: true,
|
|
141
|
+
managedBy: 'quickforge',
|
|
102
142
|
allowFileMutations: allowedTools.some((toolName) => toolName === 'write_file' || toolName === 'edit_file'),
|
|
103
143
|
createdAt: existing?.createdAt || now,
|
|
104
144
|
updatedAt: now,
|
|
@@ -110,6 +150,74 @@ async function readCustomAgentMap() {
|
|
|
110
150
|
return data && typeof data === 'object' ? data : {}
|
|
111
151
|
}
|
|
112
152
|
|
|
153
|
+
function migrationReportEntry(profile, file, extra = {}) {
|
|
154
|
+
return {
|
|
155
|
+
id: profile.id,
|
|
156
|
+
fromName: profile.name,
|
|
157
|
+
toName: path.basename(file, '.md'),
|
|
158
|
+
file,
|
|
159
|
+
...extra,
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function uniqueMigratedName(name, usedNames) {
|
|
164
|
+
const base = String(name || '').trim().toLowerCase()
|
|
165
|
+
const candidates = [base, `${base}-store`, `${base}-custom`]
|
|
166
|
+
for (const candidate of candidates) {
|
|
167
|
+
if (nameRegex.test(candidate) && !usedNames.has(candidate) && !RESERVED_NAMES.has(candidate) && !existsSync(userAgentProfileFilePath(candidate))) return candidate
|
|
168
|
+
}
|
|
169
|
+
for (let index = 2; index < 100; index += 1) {
|
|
170
|
+
const candidate = `${base.slice(0, 34)}-${index}`
|
|
171
|
+
if (nameRegex.test(candidate) && !usedNames.has(candidate) && !RESERVED_NAMES.has(candidate) && !existsSync(userAgentProfileFilePath(candidate))) return candidate
|
|
172
|
+
}
|
|
173
|
+
return null
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function migrateCustomAgentStoreToFiles() {
|
|
177
|
+
if (existsSync(MIGRATION_MARKER_FILE)) return
|
|
178
|
+
await fs.mkdir(userAgentsDir, { recursive: true })
|
|
179
|
+
const custom = Object.values(await readCustomAgentMap())
|
|
180
|
+
const report = { migratedAt: new Date().toISOString(), sourceStore: STORE, migrated: [], conflicts: [], skipped: [] }
|
|
181
|
+
if (custom.length === 0) {
|
|
182
|
+
await fs.writeFile(MIGRATION_MARKER_FILE, JSON.stringify(report, null, 2), 'utf8')
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const usedNames = new Set()
|
|
187
|
+
for (const item of custom) {
|
|
188
|
+
try {
|
|
189
|
+
const profile = normalizeProfileInput({ ...item, id: item.id }, item)
|
|
190
|
+
let targetName = profile.name
|
|
191
|
+
let conflictReason = ''
|
|
192
|
+
if (RESERVED_NAMES.has(targetName) || existsSync(userAgentProfileFilePath(targetName)) || usedNames.has(targetName)) {
|
|
193
|
+
conflictReason = RESERVED_NAMES.has(targetName) ? 'reserved builtin name or duplicate' : 'target file already exists or duplicate'
|
|
194
|
+
targetName = uniqueMigratedName(targetName, usedNames)
|
|
195
|
+
}
|
|
196
|
+
if (!targetName) {
|
|
197
|
+
report.skipped.push({ id: item?.id, fromName: item?.name, reason: 'unable to allocate unique markdown file name' })
|
|
198
|
+
continue
|
|
199
|
+
}
|
|
200
|
+
const migrated = { ...profile, name: targetName, source: 'user', readonly: false, managed: true, managedBy: 'quickforge' }
|
|
201
|
+
const file = await writeUserAgentProfileMarkdown(migrated)
|
|
202
|
+
usedNames.add(targetName)
|
|
203
|
+
const entry = migrationReportEntry(item, file, conflictReason ? { reason: conflictReason } : {})
|
|
204
|
+
if (conflictReason) report.conflicts.push(entry)
|
|
205
|
+
else report.migrated.push(entry)
|
|
206
|
+
} catch (error) {
|
|
207
|
+
report.skipped.push({ id: item?.id, fromName: item?.name, reason: error?.message || String(error) })
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
await fs.writeFile(MIGRATION_MARKER_FILE, JSON.stringify(report, null, 2), 'utf8')
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function ensureCustomAgentStoreMigrated() {
|
|
214
|
+
if (existsSync(MIGRATION_MARKER_FILE)) return
|
|
215
|
+
customAgentMigrationPromise ||= migrateCustomAgentStoreToFiles().finally(() => {
|
|
216
|
+
customAgentMigrationPromise = null
|
|
217
|
+
})
|
|
218
|
+
await customAgentMigrationPromise
|
|
219
|
+
}
|
|
220
|
+
|
|
113
221
|
async function resolveWorkspaceRoot(options = {}) {
|
|
114
222
|
if (options.workspaceRoot) return options.workspaceRoot
|
|
115
223
|
if (options.projectId) {
|
|
@@ -146,10 +254,11 @@ function mergeProfiles({ builtin = [], file = [], custom = [] }) {
|
|
|
146
254
|
}
|
|
147
255
|
|
|
148
256
|
export async function listAgentProfiles(options = {}) {
|
|
149
|
-
|
|
257
|
+
await ensureBuiltinSubagentMarkdownFiles()
|
|
258
|
+
await ensureCustomAgentStoreMigrated()
|
|
150
259
|
const workspaceRoot = await resolveWorkspaceRoot(options)
|
|
151
260
|
const file = await loadFileAgentProfiles(workspaceRoot, { reservedNames: RESERVED_NAMES })
|
|
152
|
-
const profiles = mergeProfiles({ builtin: listBuiltinAgentProfiles(), file, custom })
|
|
261
|
+
const profiles = mergeProfiles({ builtin: await listBuiltinAgentProfiles(), file, custom: [] })
|
|
153
262
|
return options.includeDisabled ? profiles : profiles.filter((profile) => profile.enabledAsSubagent || profile.builtin || profile.enabledAsSubagent === false)
|
|
154
263
|
}
|
|
155
264
|
|
|
@@ -163,46 +272,60 @@ export async function getAgentProfile(idOrName, options = {}) {
|
|
|
163
272
|
const profiles = await listAgentProfiles({ ...options, includeDisabled: true })
|
|
164
273
|
const byName = profiles.find((profile) => profile.name === key)
|
|
165
274
|
if (byName) return byName
|
|
166
|
-
|
|
167
|
-
|
|
275
|
+
return profiles.find((profile) => String(profile.id || '').toLowerCase() === key) || null
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function assertEditableUserProfile(profile) {
|
|
279
|
+
if (!profile) throw requestError('Agent not found', 404)
|
|
280
|
+
if (profile.builtin) throw requestError('Built-in agents cannot be modified', 403)
|
|
281
|
+
if (profile.readonly) throw requestError('Read-only agents cannot be modified from the API', 403)
|
|
282
|
+
if (profile.source !== 'user' || profile.managedBy !== 'quickforge') throw requestError('Only QuickForge-managed user agents can be modified from the API', 403)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function assertNameAvailable(name, currentId = null) {
|
|
286
|
+
if (RESERVED_NAMES.has(name)) throw requestError(`Agent name is reserved: ${name}`, 409)
|
|
287
|
+
const existing = await listAgentProfiles({ includeDisabled: true })
|
|
288
|
+
const conflict = existing.find((profile) => profile.name === name && profile.id !== currentId)
|
|
289
|
+
if (conflict) throw requestError(`Agent name already exists: ${name}`, 409)
|
|
168
290
|
}
|
|
169
291
|
|
|
170
292
|
export async function createCustomAgentProfile(input) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
created = profile
|
|
177
|
-
map[profile.id] = profile
|
|
178
|
-
return map
|
|
179
|
-
})
|
|
180
|
-
return created
|
|
293
|
+
await ensureCustomAgentStoreMigrated()
|
|
294
|
+
const profile = normalizeProfileInput(input, null, { creating: true })
|
|
295
|
+
await assertNameAvailable(profile.name)
|
|
296
|
+
const file = await writeUserAgentProfileMarkdown(profile)
|
|
297
|
+
return { ...profile, filePath: file, relativePath: `~/.quickforge/agents/${profile.name}.md` }
|
|
181
298
|
}
|
|
182
299
|
|
|
183
300
|
export async function updateCustomAgentProfile(id, patch) {
|
|
184
|
-
|
|
185
|
-
await
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
301
|
+
await ensureCustomAgentStoreMigrated()
|
|
302
|
+
const current = await getAgentProfile(id)
|
|
303
|
+
assertEditableUserProfile(current)
|
|
304
|
+
const next = normalizeProfileInput(patch, current)
|
|
305
|
+
await assertNameAvailable(next.name, current.id)
|
|
306
|
+
const file = await writeUserAgentProfileMarkdown(next)
|
|
307
|
+
if (next.name !== current.name) await deleteUserAgentProfileMarkdown(current)
|
|
308
|
+
return { ...next, filePath: file, relativePath: `~/.quickforge/agents/${next.name}.md` }
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export async function updateBuiltinAgentModelOverride(id, modelInput) {
|
|
312
|
+
const current = await getAgentProfile(id)
|
|
313
|
+
if (!current) throw requestError('Agent not found', 404)
|
|
314
|
+
if (!current.builtin) throw requestError('Only built-in agents support model overrides', 400)
|
|
315
|
+
const model = validateModelReference(normalizeModelReference(modelInput))
|
|
316
|
+
const overrides = await readStore(BUILTIN_OVERRIDES_STORE).catch(() => ({}))
|
|
317
|
+
const next = { ...(overrides && typeof overrides === 'object' ? overrides : {}) }
|
|
318
|
+
if (model.mode === 'fixed') next[current.name] = { model }
|
|
319
|
+
else delete next[current.name]
|
|
320
|
+
await writeStore(BUILTIN_OVERRIDES_STORE, next)
|
|
321
|
+
return { ...current, model: modelReferenceSnapshot(model) }
|
|
197
322
|
}
|
|
198
323
|
|
|
199
324
|
export async function deleteCustomAgentProfile(id) {
|
|
200
|
-
await
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
return map
|
|
205
|
-
})
|
|
325
|
+
await ensureCustomAgentStoreMigrated()
|
|
326
|
+
const current = await getAgentProfile(id)
|
|
327
|
+
assertEditableUserProfile(current)
|
|
328
|
+
await deleteUserAgentProfileMarkdown(current)
|
|
206
329
|
}
|
|
207
330
|
|
|
208
331
|
export function agentProfileSnapshot(profile) {
|
|
@@ -214,6 +337,11 @@ export function agentProfileSnapshot(profile) {
|
|
|
214
337
|
description: profile.description,
|
|
215
338
|
systemPrompt: profile.systemPrompt,
|
|
216
339
|
allowedTools: [...profile.allowedTools],
|
|
340
|
+
capabilityPolicy: profile.capabilityPolicy || inferCapabilityPolicy(profile.allowedTools || []),
|
|
341
|
+
model: modelReferenceSnapshot(profile.model),
|
|
342
|
+
thinkingLevel: normalizeAgentProfileThinkingLevel(profile.thinkingLevel),
|
|
343
|
+
lifecycle: profile.lifecycle || (profile.builtin ? 'builtin' : 'persistent'),
|
|
344
|
+
managed: profile.managed === true,
|
|
217
345
|
maxRuntimeMs: profile.maxRuntimeMs,
|
|
218
346
|
maxToolCalls: profile.maxToolCalls,
|
|
219
347
|
enabledAsSubagent: profile.enabledAsSubagent === true,
|
|
@@ -222,9 +350,12 @@ export function agentProfileSnapshot(profile) {
|
|
|
222
350
|
readonly: profile.readonly === true || profile.builtin === true,
|
|
223
351
|
filePath: profile.filePath,
|
|
224
352
|
relativePath: profile.relativePath,
|
|
353
|
+
warnings: Array.isArray(profile.warnings) ? [...profile.warnings] : undefined,
|
|
225
354
|
}
|
|
226
355
|
}
|
|
227
356
|
|
|
357
|
+
export { AGENT_PROFILE_TOOL_NAMES } from './agent-profile-schema.mjs'
|
|
358
|
+
|
|
228
359
|
export function listAvailableAgentTools() {
|
|
229
360
|
const labels = {
|
|
230
361
|
read_file: 'Read file',
|
|
@@ -2,7 +2,7 @@ import fs from 'node:fs'
|
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
4
4
|
import { randomUUID } from 'node:crypto'
|
|
5
|
-
import { streamSimple } from '@earendil-works/pi-ai'
|
|
5
|
+
import { streamSimple } from '@earendil-works/pi-ai/compat'
|
|
6
6
|
import { logsDir } from './storage.mjs'
|
|
7
7
|
|
|
8
8
|
const PATCH_MARKER = Symbol.for('quickforge.aiHttpLogger.fetchPatched')
|
|
@@ -5,7 +5,7 @@ import { estimateContextUsage, shouldCompactContextByPercent } from './context-u
|
|
|
5
5
|
export const AUTO_COMPACT_SETTINGS_KEY = 'auto-compact-settings'
|
|
6
6
|
|
|
7
7
|
export const DEFAULT_AUTO_COMPACT_SETTINGS = {
|
|
8
|
-
enabled:
|
|
8
|
+
enabled: true,
|
|
9
9
|
thresholdPercent: 80,
|
|
10
10
|
keepRecentTurns: 2,
|
|
11
11
|
minSourceChars: 1600,
|
|
@@ -24,7 +24,7 @@ function clampNumber(value, fallback, min, max) {
|
|
|
24
24
|
export function normalizeAutoCompactSettings(value) {
|
|
25
25
|
if (!value || typeof value !== 'object') return { ...DEFAULT_AUTO_COMPACT_SETTINGS }
|
|
26
26
|
return {
|
|
27
|
-
enabled: value.enabled
|
|
27
|
+
enabled: value.enabled !== false,
|
|
28
28
|
thresholdPercent: clampNumber(value.thresholdPercent, DEFAULT_AUTO_COMPACT_SETTINGS.thresholdPercent, 50, 95),
|
|
29
29
|
keepRecentTurns: clampNumber(value.keepRecentTurns, DEFAULT_AUTO_COMPACT_SETTINGS.keepRecentTurns, 1, 20),
|
|
30
30
|
minSourceChars: clampNumber(value.minSourceChars, DEFAULT_AUTO_COMPACT_SETTINGS.minSourceChars, 0, 200000),
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
|
|
3
|
+
export function resolveRequestedProject(config, projectId, defaultWorkspaceRoot) {
|
|
4
|
+
if (!projectId) return config.projects.find((project) => project.id === config.activeProjectId) ?? config.projects[0]
|
|
5
|
+
if (projectId === 'default') {
|
|
6
|
+
return {
|
|
7
|
+
id: 'default',
|
|
8
|
+
name: 'workspace',
|
|
9
|
+
path: defaultWorkspaceRoot,
|
|
10
|
+
lastOpenedAt: '',
|
|
11
|
+
sortOrder: 0,
|
|
12
|
+
skills: [],
|
|
13
|
+
commandDir: '',
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const project = config.projects.find((item) => item.id === projectId)
|
|
17
|
+
if (project) return project
|
|
18
|
+
const error = new Error('Unknown project')
|
|
19
|
+
error.statusCode = 404
|
|
20
|
+
throw error
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function workspaceRootAfterProjectDeletion(activeProject, defaultWorkspaceRoot) {
|
|
24
|
+
return path.resolve(activeProject?.path || defaultWorkspaceRoot)
|
|
25
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { streamSimple } from '@earendil-works/pi-ai'
|
|
1
|
+
import { streamSimple } from '@earendil-works/pi-ai/compat'
|
|
2
2
|
import { sendJson, readJsonBody, decodeSegment } from '../utils/response.mjs'
|
|
3
3
|
import { readStore } from '../storage.mjs'
|
|
4
4
|
import { logger } from '../utils/logger.mjs'
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
getAgentProfile,
|
|
10
10
|
listAgentProfiles,
|
|
11
11
|
listAvailableAgentTools,
|
|
12
|
+
updateBuiltinAgentModelOverride,
|
|
12
13
|
updateCustomAgentProfile,
|
|
13
14
|
} from '../agent-profiles.mjs'
|
|
14
15
|
|
|
@@ -155,9 +156,14 @@ export async function handleAgentProfilesApi(req, res, url) {
|
|
|
155
156
|
|
|
156
157
|
if (req.method === 'PATCH' || req.method === 'PUT') {
|
|
157
158
|
const current = await getAgentProfile(id)
|
|
158
|
-
if (current?.builtin) throw requestError('Built-in agents cannot be modified', 403)
|
|
159
|
-
if (current?.readonly) throw requestError('File-based agents cannot be modified from the API', 403)
|
|
160
159
|
const body = await readJsonBody(req)
|
|
160
|
+
if (current?.builtin) {
|
|
161
|
+
const keys = Object.keys(body || {})
|
|
162
|
+
if (keys.length !== 1 || keys[0] !== 'model') throw requestError('Built-in agents only allow model updates', 403)
|
|
163
|
+
sendJson(res, 200, { agent: agentProfileSnapshot(await updateBuiltinAgentModelOverride(id, body.model)) })
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
if (current?.readonly) throw requestError('Read-only agents cannot be modified from the API', 403)
|
|
161
167
|
sendJson(res, 200, { agent: await updateCustomAgentProfile(id, body || {}) })
|
|
162
168
|
return
|
|
163
169
|
}
|
|
@@ -165,7 +171,7 @@ export async function handleAgentProfilesApi(req, res, url) {
|
|
|
165
171
|
if (req.method === 'DELETE') {
|
|
166
172
|
const current = await getAgentProfile(id)
|
|
167
173
|
if (current?.builtin) throw requestError('Built-in agents cannot be deleted', 403)
|
|
168
|
-
if (current?.readonly) throw requestError('
|
|
174
|
+
if (current?.readonly) throw requestError('Read-only agents cannot be deleted from the API', 403)
|
|
169
175
|
await deleteCustomAgentProfile(id)
|
|
170
176
|
sendJson(res, 200, { ok: true })
|
|
171
177
|
return
|
package/server/routes/models.mjs
CHANGED
|
@@ -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
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { streamSimple } from '@earendil-works/pi-ai'
|
|
1
|
+
import { streamSimple } from '@earendil-works/pi-ai/compat'
|
|
2
2
|
import { readJsonBody, sendJson, decodeSegment } from '../utils/response.mjs'
|
|
3
3
|
import { readStore, atomicUpdate } from '../storage.mjs'
|
|
4
4
|
import { createAgent, getSessionEventBus, agentEvents, persistSessionState } from '../agent-manager.mjs'
|