@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
package/server/agent-manager.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events'
|
|
2
2
|
import { randomUUID } from 'node:crypto'
|
|
3
|
+
import { promises as fs } from 'node:fs'
|
|
4
|
+
import path from 'node:path'
|
|
3
5
|
import { Agent } from '@earendil-works/pi-agent-core'
|
|
4
6
|
import { streamSimpleWithAiHttpLogging } from './ai-http-logger.mjs'
|
|
5
7
|
import { loadSkillToolContext, abortRunningCommand } from './tools/index.mjs'
|
|
@@ -11,8 +13,20 @@ import {
|
|
|
11
13
|
formatSubagentTask,
|
|
12
14
|
} from './subagents.mjs'
|
|
13
15
|
import { agentProfileSnapshot, getAgentProfile } from './agent-profiles.mjs'
|
|
16
|
+
import { agentProfileFromMarkdown } from './agent-profile-files.mjs'
|
|
17
|
+
import {
|
|
18
|
+
applyCapabilityPolicy,
|
|
19
|
+
inferCapabilityPolicy,
|
|
20
|
+
modelReferenceSnapshot,
|
|
21
|
+
normalizeCapabilityPolicy,
|
|
22
|
+
normalizeModelReference,
|
|
23
|
+
resolveAgentProfileModel,
|
|
24
|
+
resolveAgentProfileThinkingLevel,
|
|
25
|
+
validateAgentProfileTools,
|
|
26
|
+
validateModelReference,
|
|
27
|
+
} from './agent-profile-schema.mjs'
|
|
14
28
|
import { projectContextFromId, defaultGlobalWorkspaceContext, readProjectConfig } from './project-config.mjs'
|
|
15
|
-
import { readStore, atomicUpdate, atomicSessionMetadataUpdate, readSessionValue, writeSessionValue, deleteSessionValue } from './storage.mjs'
|
|
29
|
+
import { ensureStorage, readStore, atomicUpdate, atomicSessionMetadataUpdate, readSessionValue, writeSessionValue, deleteSessionValue, tempAgentsDir } from './storage.mjs'
|
|
16
30
|
import { logger } from './utils/logger.mjs'
|
|
17
31
|
import { buildSystemPrompt, generateAiTitle, generateTitle } from './session-utils.mjs'
|
|
18
32
|
import { restoreReasoningContentInPayload } from './reasoning-cache.mjs'
|
|
@@ -385,6 +399,21 @@ function addToolTimingToEvent(session, event) {
|
|
|
385
399
|
return event
|
|
386
400
|
}
|
|
387
401
|
|
|
402
|
+
export function markLatestAssistantProcessFinished(messages, finishedAt = Date.now()) {
|
|
403
|
+
if (!Array.isArray(messages)) return false
|
|
404
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
405
|
+
const message = messages[index]
|
|
406
|
+
if (!message || message.role !== 'assistant') continue
|
|
407
|
+
const details = message.details && typeof message.details === 'object' && !Array.isArray(message.details)
|
|
408
|
+
? message.details
|
|
409
|
+
: {}
|
|
410
|
+
if (details.quickforgeProcessFinishedAt !== undefined) return false
|
|
411
|
+
messages[index] = { ...message, details: { ...details, quickforgeProcessFinishedAt: finishedAt } }
|
|
412
|
+
return true
|
|
413
|
+
}
|
|
414
|
+
return false
|
|
415
|
+
}
|
|
416
|
+
|
|
388
417
|
function updateSessionMessages(session, messages) {
|
|
389
418
|
session.agent.state.messages = messages
|
|
390
419
|
}
|
|
@@ -831,10 +860,109 @@ ${scopeText}
|
|
|
831
860
|
</review_command_invocation>`
|
|
832
861
|
}
|
|
833
862
|
|
|
863
|
+
const tempSubagentNameRegex = /^[a-z][a-z0-9_-]{1,39}$/
|
|
864
|
+
|
|
865
|
+
function frontmatterString(value) {
|
|
866
|
+
return String(value ?? '').replace(/\r?\n/g, ' ').replace(/"/g, '\\"')
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
function normalizeTemporarySubagentSpec(input) {
|
|
870
|
+
const spec = input && typeof input === 'object' && !Array.isArray(input) ? input : null
|
|
871
|
+
if (!spec || spec.type !== 'temporary') return null
|
|
872
|
+
const name = String(spec.name || '').trim().toLowerCase()
|
|
873
|
+
if (!tempSubagentNameRegex.test(name)) {
|
|
874
|
+
const error = new Error('Temporary subagent name must start with a letter and contain only lowercase letters, numbers, underscores, or hyphens')
|
|
875
|
+
error.statusCode = 400
|
|
876
|
+
throw error
|
|
877
|
+
}
|
|
878
|
+
const instructions = String(spec.instructions || '').trim()
|
|
879
|
+
if (!instructions) {
|
|
880
|
+
const error = new Error('Temporary subagent instructions are required')
|
|
881
|
+
error.statusCode = 400
|
|
882
|
+
throw error
|
|
883
|
+
}
|
|
884
|
+
const requestedTools = Array.isArray(spec.tools)
|
|
885
|
+
? [...new Set(spec.tools.map((toolName) => String(toolName || '').trim()).filter(Boolean))]
|
|
886
|
+
: ['read_file', 'grep_files', 'run_command']
|
|
887
|
+
const capabilityPolicy = normalizeCapabilityPolicy(spec.capabilityPolicy || 'readonly-research', requestedTools)
|
|
888
|
+
validateAgentProfileTools(requestedTools, capabilityPolicy)
|
|
889
|
+
const allowedTools = applyCapabilityPolicy(requestedTools, capabilityPolicy)
|
|
890
|
+
const model = validateModelReference(normalizeModelReference(spec.model))
|
|
891
|
+
return {
|
|
892
|
+
name,
|
|
893
|
+
label: String(spec.label || spec.description || name).trim().slice(0, 80) || name,
|
|
894
|
+
description: String(spec.description || '').trim().slice(0, 500),
|
|
895
|
+
systemPrompt: instructions,
|
|
896
|
+
allowedTools,
|
|
897
|
+
capabilityPolicy,
|
|
898
|
+
model,
|
|
899
|
+
maxRuntimeMs: spec.maxRuntimeMs,
|
|
900
|
+
maxToolCalls: spec.maxToolCalls,
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
function safeTempPathSegment(value, fallback) {
|
|
905
|
+
const text = String(value || '').replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 80)
|
|
906
|
+
return text || fallback
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
function temporarySubagentMarkdown(spec, parentSession, runId, expiresAt) {
|
|
910
|
+
return `---
|
|
911
|
+
name: ${spec.name}
|
|
912
|
+
label: "${frontmatterString(spec.label)}"
|
|
913
|
+
description: "${frontmatterString(spec.description)}"
|
|
914
|
+
source: temporary
|
|
915
|
+
lifecycle: temporary
|
|
916
|
+
createdBy: ai
|
|
917
|
+
createdAt: ${new Date().toISOString()}
|
|
918
|
+
parentSessionId: ${frontmatterString(parentSession.sessionId)}
|
|
919
|
+
runId: ${runId}
|
|
920
|
+
expiresAt: ${expiresAt}
|
|
921
|
+
enabled-as-subagent: true
|
|
922
|
+
capabilityPolicy: ${spec.capabilityPolicy}
|
|
923
|
+
tools: ${spec.allowedTools.join(', ')}
|
|
924
|
+
model:
|
|
925
|
+
mode: ${spec.model?.mode === 'fixed' ? 'fixed' : 'inherit'}${spec.model?.mode === 'fixed' ? `
|
|
926
|
+
provider: ${frontmatterString(spec.model.provider)}
|
|
927
|
+
modelId: ${frontmatterString(spec.model.modelId)}${spec.model.api ? `
|
|
928
|
+
api: ${frontmatterString(spec.model.api)}` : ''}${spec.model.baseUrl ? `
|
|
929
|
+
baseUrl: ${frontmatterString(spec.model.baseUrl)}` : ''}` : ''}
|
|
930
|
+
max-runtime-ms: ${Math.max(1000, Math.min(Number(spec.maxRuntimeMs || SUBAGENT_DEFAULT_TIMEOUT_MS), 30 * 60 * 1000))}
|
|
931
|
+
max-tool-calls: ${Math.max(1, Math.min(Number(spec.maxToolCalls || 300), 300))}
|
|
932
|
+
---
|
|
933
|
+
${spec.systemPrompt}
|
|
934
|
+
`
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
async function createTemporarySubagentProfile(parentSession, spec) {
|
|
938
|
+
await ensureStorage()
|
|
939
|
+
const runId = `run_${Date.now()}_${randomUUID().slice(0, 8)}`
|
|
940
|
+
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString()
|
|
941
|
+
const dir = path.join(tempAgentsDir, safeTempPathSegment(parentSession.sessionId, 'session'), runId)
|
|
942
|
+
await fs.mkdir(dir, { recursive: true })
|
|
943
|
+
const file = path.join(dir, `${spec.name}.md`)
|
|
944
|
+
const markdown = temporarySubagentMarkdown(spec, parentSession, runId, expiresAt)
|
|
945
|
+
await fs.writeFile(file, markdown, 'utf8')
|
|
946
|
+
const profile = agentProfileFromMarkdown(file, markdown, {
|
|
947
|
+
source: 'temporary',
|
|
948
|
+
idPrefix: `temporary:${safeTempPathSegment(parentSession.sessionId, 'session')}:${runId}`,
|
|
949
|
+
relativePath: path.relative(tempAgentsDir, file).replace(/\\/g, '/'),
|
|
950
|
+
})
|
|
951
|
+
if (!profile) throw new Error('Failed to create temporary subagent profile')
|
|
952
|
+
profile.readonly = true
|
|
953
|
+
return profile
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
async function resolveSubagentProfile(parentSession, requestedSubagent) {
|
|
957
|
+
const temporarySpec = normalizeTemporarySubagentSpec(requestedSubagent)
|
|
958
|
+
if (temporarySpec) return createTemporarySubagentProfile(parentSession, temporarySpec)
|
|
959
|
+
return getAgentProfile(requestedSubagent, { workspaceRoot: parentSession.projectContext?.workspaceRoot })
|
|
960
|
+
}
|
|
961
|
+
|
|
834
962
|
async function runSubagent(parentSession, params, parentSignal, onUpdate) {
|
|
835
|
-
const profile = await
|
|
963
|
+
const profile = await resolveSubagentProfile(parentSession, params?.subagent)
|
|
836
964
|
if (!profile || !profile.enabledAsSubagent) {
|
|
837
|
-
const error = new Error(`Unknown or disabled subagent: ${params?.subagent || ''}`)
|
|
965
|
+
const error = new Error(`Unknown or disabled subagent: ${typeof params?.subagent === 'string' ? params.subagent : params?.subagent?.name || ''}`)
|
|
838
966
|
error.statusCode = 400
|
|
839
967
|
throw error
|
|
840
968
|
}
|
|
@@ -854,6 +982,10 @@ async function runSubagent(parentSession, params, parentSignal, onUpdate) {
|
|
|
854
982
|
}
|
|
855
983
|
|
|
856
984
|
const timeoutMs = Math.max(1000, Math.min(Number(definition.maxRuntimeMs || SUBAGENT_DEFAULT_TIMEOUT_MS), 30 * 60 * 1000))
|
|
985
|
+
definition.capabilityPolicy ||= inferCapabilityPolicy(definition.allowedTools || [])
|
|
986
|
+
definition.allowedTools = applyCapabilityPolicy(definition.allowedTools || [], definition.capabilityPolicy)
|
|
987
|
+
const { model: subagentModel, info: subagentModelInfo } = await resolveAgentProfileModel(definition, parentSession.model, readStore)
|
|
988
|
+
const subagentThinkingLevel = resolveAgentProfileThinkingLevel(definition, parentSession.thinkingLevel, subagentModel)
|
|
857
989
|
const subagentSessionId = `${parentSession.sessionId}:subagent:${definition.name}:${randomUUID()}`
|
|
858
990
|
const startedAt = Date.now()
|
|
859
991
|
let toolCalls = 0
|
|
@@ -898,6 +1030,11 @@ async function runSubagent(parentSession, params, parentSignal, onUpdate) {
|
|
|
898
1030
|
toolCalls,
|
|
899
1031
|
allowedTools: definition.allowedTools,
|
|
900
1032
|
timeoutMs,
|
|
1033
|
+
source: definition.source,
|
|
1034
|
+
lifecycle: definition.lifecycle,
|
|
1035
|
+
profilePath: definition.filePath,
|
|
1036
|
+
capabilityPolicy: definition.capabilityPolicy,
|
|
1037
|
+
model: subagentModelInfo,
|
|
901
1038
|
durationMs: Date.now() - startedAt,
|
|
902
1039
|
messages: latestMessages,
|
|
903
1040
|
tools: toolsForClient,
|
|
@@ -932,8 +1069,8 @@ async function runSubagent(parentSession, params, parentSignal, onUpdate) {
|
|
|
932
1069
|
const subagent = new Agent({
|
|
933
1070
|
initialState: {
|
|
934
1071
|
systemPrompt,
|
|
935
|
-
model:
|
|
936
|
-
thinkingLevel:
|
|
1072
|
+
model: subagentModel,
|
|
1073
|
+
thinkingLevel: subagentThinkingLevel,
|
|
937
1074
|
messages: [],
|
|
938
1075
|
tools,
|
|
939
1076
|
},
|
|
@@ -1013,6 +1150,11 @@ async function runSubagent(parentSession, params, parentSignal, onUpdate) {
|
|
|
1013
1150
|
toolCalls,
|
|
1014
1151
|
allowedTools: definition.allowedTools,
|
|
1015
1152
|
timeoutMs,
|
|
1153
|
+
source: definition.source,
|
|
1154
|
+
lifecycle: definition.lifecycle,
|
|
1155
|
+
profilePath: definition.filePath,
|
|
1156
|
+
capabilityPolicy: definition.capabilityPolicy,
|
|
1157
|
+
model: subagentModelInfo,
|
|
1016
1158
|
durationMs: Date.now() - startedAt,
|
|
1017
1159
|
messages: latestMessages,
|
|
1018
1160
|
tools: toolsForClient,
|
|
@@ -1229,6 +1371,8 @@ export async function createAgent(sessionId, config = {}) {
|
|
|
1229
1371
|
const profileSystemPrompt = agentProfile?.systemPrompt ? `\n\n<agent_profile_instructions>\nAgent Profile: ${agentProfile.label || agentProfile.name}\n${agentProfile.systemPrompt}\n</agent_profile_instructions>` : ''
|
|
1230
1372
|
const resolvedSystemPrompt = systemPrompt ?? `${await buildSystemPrompt(projectId)}${profileSystemPrompt}`
|
|
1231
1373
|
|
|
1374
|
+
let resolvedAgentProfile = agentProfile
|
|
1375
|
+
|
|
1232
1376
|
// Resolve model
|
|
1233
1377
|
let resolvedModel = model
|
|
1234
1378
|
if (!resolvedModel) {
|
|
@@ -1241,6 +1385,17 @@ export async function createAgent(sessionId, config = {}) {
|
|
|
1241
1385
|
// ignore
|
|
1242
1386
|
}
|
|
1243
1387
|
}
|
|
1388
|
+
if (agentProfile?.model?.mode === 'fixed') {
|
|
1389
|
+
resolvedModel = (await resolveAgentProfileModel(agentProfile, resolvedModel, readStore)).model
|
|
1390
|
+
resolvedAgentProfile = {
|
|
1391
|
+
...agentProfile,
|
|
1392
|
+
model: modelReferenceSnapshot(agentProfile.model),
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
if (resolvedAgentProfile && !resolvedModel) throw new Error('No active model is configured for the agent session.')
|
|
1396
|
+
const resolvedThinkingLevel = agentProfile
|
|
1397
|
+
? resolveAgentProfileThinkingLevel(agentProfile, thinkingLevel, resolvedModel)
|
|
1398
|
+
: thinkingLevel
|
|
1244
1399
|
|
|
1245
1400
|
// Build skills tools for enabled skills, plus workspace tools when a project is available.
|
|
1246
1401
|
const profileToolNames = Array.isArray(agentProfile?.allowedTools) ? agentProfile.allowedTools : null
|
|
@@ -1279,7 +1434,7 @@ export async function createAgent(sessionId, config = {}) {
|
|
|
1279
1434
|
initialState: {
|
|
1280
1435
|
systemPrompt: resolvedSystemPrompt,
|
|
1281
1436
|
model: resolvedModel,
|
|
1282
|
-
thinkingLevel,
|
|
1437
|
+
thinkingLevel: resolvedThinkingLevel,
|
|
1283
1438
|
messages,
|
|
1284
1439
|
tools,
|
|
1285
1440
|
},
|
|
@@ -1300,7 +1455,14 @@ export async function createAgent(sessionId, config = {}) {
|
|
|
1300
1455
|
const isSkillTool = toolName === 'activate_skill' || toolName === 'read_skill_resource'
|
|
1301
1456
|
if (isSkillTool) return undefined
|
|
1302
1457
|
if (profileToolNames && !profileToolNames.includes(toolName)) return { block: true, reason: `Agent profile ${agentProfile.name} is not allowed to use ${toolName}.` }
|
|
1303
|
-
if (toolName === 'run_subagent')
|
|
1458
|
+
if (toolName === 'run_subagent') {
|
|
1459
|
+
const requested = context.args?.subagent
|
|
1460
|
+
const policy = requested && typeof requested === 'object' ? normalizeCapabilityPolicy(requested.capabilityPolicy || 'readonly-research', Array.isArray(requested.tools) ? requested.tools : []) : null
|
|
1461
|
+
if (policy && ['code-edit', 'docs-edit'].includes(policy) && !hasFullAccess(currentSession)) {
|
|
1462
|
+
return { block: true, reason: `Temporary subagent capability policy ${policy} requires full access or approval.` }
|
|
1463
|
+
}
|
|
1464
|
+
return undefined
|
|
1465
|
+
}
|
|
1304
1466
|
if (isMcpToolName(toolName) || isPluginToolName(toolName)) {
|
|
1305
1467
|
if (!hasFullAccess(currentSession)) return createApprovalPromise(currentSession, toolCallId, toolName, context.args)
|
|
1306
1468
|
return undefined
|
|
@@ -1328,7 +1490,7 @@ export async function createAgent(sessionId, config = {}) {
|
|
|
1328
1490
|
accessMode,
|
|
1329
1491
|
yoloMode: resolvedYoloMode,
|
|
1330
1492
|
model: resolvedModel,
|
|
1331
|
-
thinkingLevel,
|
|
1493
|
+
thinkingLevel: resolvedThinkingLevel,
|
|
1332
1494
|
scope,
|
|
1333
1495
|
title,
|
|
1334
1496
|
createdAt,
|
|
@@ -1348,7 +1510,7 @@ export async function createAgent(sessionId, config = {}) {
|
|
|
1348
1510
|
toolTimings: new Map(),
|
|
1349
1511
|
getApiKey,
|
|
1350
1512
|
contextCompaction,
|
|
1351
|
-
agentProfile:
|
|
1513
|
+
agentProfile: resolvedAgentProfile ? agentProfileSnapshot(resolvedAgentProfile) : null,
|
|
1352
1514
|
idleRetention,
|
|
1353
1515
|
lastTransformedContextMessages: null,
|
|
1354
1516
|
autoCompacting: false,
|
|
@@ -1374,6 +1536,9 @@ export async function createAgent(sessionId, config = {}) {
|
|
|
1374
1536
|
? 'error'
|
|
1375
1537
|
: 'idle'
|
|
1376
1538
|
: undefined
|
|
1539
|
+
if (timedEvent.type === 'agent_end') {
|
|
1540
|
+
markLatestAssistantProcessFinished(agent.state.messages)
|
|
1541
|
+
}
|
|
1377
1542
|
const forwardEvent = timedEvent.type === 'agent_end'
|
|
1378
1543
|
? {
|
|
1379
1544
|
...timedEvent,
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { existsSync, promises as fs } from 'node:fs'
|
|
2
2
|
import os from 'node:os'
|
|
3
3
|
import path from 'node:path'
|
|
4
|
-
import {
|
|
4
|
+
import { userAgentsDir } from './storage.mjs'
|
|
5
5
|
import { logger } from './utils/logger.mjs'
|
|
6
6
|
import { firstOptionalBoolean, firstString, parseFrontmatter, splitDelimitedList } from './frontmatter.mjs'
|
|
7
|
+
import { normalizeAgentProfileThinkingLevel, normalizeCapabilityPolicy, normalizeModelReference, validateAgentProfileTools, validateModelReference } from './agent-profile-schema.mjs'
|
|
7
8
|
|
|
8
9
|
const DEFAULT_MAX_RUNTIME_MS = 30 * 60 * 1000
|
|
9
10
|
const DEFAULT_MAX_TOOL_CALLS = 300
|
|
@@ -19,7 +20,6 @@ const toolAliases = new Map([
|
|
|
19
20
|
])
|
|
20
21
|
|
|
21
22
|
const claudeUserAgentsDir = path.join(os.homedir(), '.claude', 'agents')
|
|
22
|
-
const userAgentsDir = path.join(dataDir, 'agents')
|
|
23
23
|
|
|
24
24
|
function normalizeString(value) {
|
|
25
25
|
return typeof value === 'string' && value.trim() ? value.trim() : undefined
|
|
@@ -60,6 +60,64 @@ function hasMutationTool(allowedTools) {
|
|
|
60
60
|
return allowedTools.some((toolName) => toolName === 'write_file' || toolName === 'edit_file')
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
function quoteFrontmatter(value) {
|
|
64
|
+
return `"${String(value ?? '').replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\r?\n/g, ' ')}"`
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function formatModelFrontmatter(model) {
|
|
68
|
+
if (!model || model.mode !== 'fixed') return 'model:\n mode: inherit'
|
|
69
|
+
return [
|
|
70
|
+
'model:',
|
|
71
|
+
' mode: fixed',
|
|
72
|
+
` provider: ${quoteFrontmatter(model.provider)}`,
|
|
73
|
+
` modelId: ${quoteFrontmatter(model.modelId)}`,
|
|
74
|
+
model.api ? ` api: ${quoteFrontmatter(model.api)}` : '',
|
|
75
|
+
model.baseUrl ? ` baseUrl: ${quoteFrontmatter(model.baseUrl)}` : '',
|
|
76
|
+
].filter(Boolean).join('\n')
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function userAgentProfileFilePath(name) {
|
|
80
|
+
const normalized = normalizeName(name)
|
|
81
|
+
if (!normalized) throw new Error('Invalid agent profile name')
|
|
82
|
+
return path.join(userAgentsDir, `${normalized}.md`)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function agentProfileToMarkdown(profile) {
|
|
86
|
+
return `---
|
|
87
|
+
id: ${quoteFrontmatter(profile.id)}
|
|
88
|
+
name: ${profile.name}
|
|
89
|
+
label: ${quoteFrontmatter(profile.label || profile.name)}
|
|
90
|
+
description: ${quoteFrontmatter(profile.description || '')}
|
|
91
|
+
source: user
|
|
92
|
+
lifecycle: persistent
|
|
93
|
+
managed: true
|
|
94
|
+
managedBy: quickforge
|
|
95
|
+
readonly: false
|
|
96
|
+
enabled-as-subagent: ${profile.enabledAsSubagent === true ? 'true' : 'false'}
|
|
97
|
+
capabilityPolicy: ${profile.capabilityPolicy || 'review-only'}
|
|
98
|
+
tools: ${(profile.allowedTools || []).join(', ')}
|
|
99
|
+
${formatModelFrontmatter(profile.model)}
|
|
100
|
+
thinking-level: ${normalizeAgentProfileThinkingLevel(profile.thinkingLevel)}
|
|
101
|
+
max-runtime-ms: ${profile.maxRuntimeMs || DEFAULT_MAX_RUNTIME_MS}
|
|
102
|
+
max-tool-calls: ${profile.maxToolCalls || DEFAULT_MAX_TOOL_CALLS}
|
|
103
|
+
created-at: ${quoteFrontmatter(profile.createdAt || new Date().toISOString())}
|
|
104
|
+
updated-at: ${quoteFrontmatter(profile.updatedAt || new Date().toISOString())}
|
|
105
|
+
---
|
|
106
|
+
${String(profile.systemPrompt || '').trim()}
|
|
107
|
+
`
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function writeUserAgentProfileMarkdown(profile) {
|
|
111
|
+
await fs.mkdir(userAgentsDir, { recursive: true })
|
|
112
|
+
const file = userAgentProfileFilePath(profile.name)
|
|
113
|
+
await fs.writeFile(file, agentProfileToMarkdown(profile), 'utf8')
|
|
114
|
+
return file
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function deleteUserAgentProfileMarkdown(profile) {
|
|
118
|
+
await fs.rm(userAgentProfileFilePath(profile.name), { force: true })
|
|
119
|
+
}
|
|
120
|
+
|
|
63
121
|
export function agentProfileFromMarkdown(file, text, options = {}) {
|
|
64
122
|
const parsed = parseFrontmatter(text)
|
|
65
123
|
if (!parsed.body) return null
|
|
@@ -72,30 +130,52 @@ export function agentProfileFromMarkdown(file, text, options = {}) {
|
|
|
72
130
|
const allowedTools = normalizeTools(
|
|
73
131
|
metadata.tools ?? metadata['allowed-tools'] ?? metadata.allowedTools,
|
|
74
132
|
)
|
|
133
|
+
const capabilityPolicy = normalizeCapabilityPolicy(
|
|
134
|
+
metadata.capabilityPolicy ?? metadata['capability-policy'] ?? metadata.capability_policy,
|
|
135
|
+
allowedTools,
|
|
136
|
+
)
|
|
137
|
+
validateAgentProfileTools(allowedTools, capabilityPolicy)
|
|
138
|
+
const model = validateModelReference(normalizeModelReference(metadata.model, metadata))
|
|
75
139
|
const label = firstString(metadata.label, metadata.displayName, metadata.title) || name
|
|
76
140
|
const enabledAsSubagent = firstOptionalBoolean(
|
|
77
141
|
metadata['enabled-as-subagent'],
|
|
78
142
|
metadata.enabled_as_subagent,
|
|
79
143
|
metadata.enabledAsSubagent,
|
|
80
144
|
)
|
|
145
|
+
const source = firstString(metadata.source) || options.source || 'file'
|
|
146
|
+
const lifecycle = firstString(metadata.lifecycle) || (source === 'temporary' ? 'temporary' : 'persistent')
|
|
147
|
+
const explicitReadonly = firstOptionalBoolean(metadata.readonly, metadata['read-only'])
|
|
148
|
+
const managedBy = firstString(metadata.managedBy, metadata['managed-by'])
|
|
149
|
+
const profileId = firstString(metadata.id) || `${options.idPrefix || source || 'file'}:${name}`
|
|
81
150
|
|
|
82
151
|
return {
|
|
83
|
-
id:
|
|
152
|
+
id: profileId,
|
|
84
153
|
name,
|
|
85
154
|
label: label.slice(0, 80),
|
|
86
155
|
description: String(firstString(metadata.description) || '').slice(0, 500),
|
|
87
156
|
systemPrompt: parsed.body,
|
|
88
157
|
allowedTools,
|
|
158
|
+
capabilityPolicy,
|
|
159
|
+
model,
|
|
160
|
+
thinkingLevel: normalizeAgentProfileThinkingLevel(
|
|
161
|
+
metadata['thinking-level'] ?? metadata.thinking_level ?? metadata.thinkingLevel,
|
|
162
|
+
),
|
|
163
|
+
lifecycle,
|
|
164
|
+
managed: firstOptionalBoolean(metadata.managed) === true,
|
|
165
|
+
managedBy,
|
|
166
|
+
expiresAt: firstString(metadata.expiresAt, metadata['expires-at']),
|
|
167
|
+
parentSessionId: firstString(metadata.parentSessionId, metadata['parent-session-id']),
|
|
168
|
+
runId: firstString(metadata.runId, metadata['run-id']),
|
|
89
169
|
maxRuntimeMs: normalizeRuntime(metadata['max-runtime-ms'] ?? metadata.max_runtime_ms ?? metadata.maxRuntimeMs),
|
|
90
170
|
maxToolCalls: normalizeToolCalls(metadata['max-tool-calls'] ?? metadata.max_tool_calls ?? metadata.maxToolCalls),
|
|
91
171
|
enabledAsSubagent: enabledAsSubagent === undefined ? true : enabledAsSubagent,
|
|
92
172
|
builtin: false,
|
|
93
|
-
source
|
|
94
|
-
readonly: true,
|
|
173
|
+
source,
|
|
174
|
+
readonly: explicitReadonly === undefined ? true : explicitReadonly,
|
|
95
175
|
filePath: file,
|
|
96
176
|
relativePath: options.relativePath || path.basename(file),
|
|
97
177
|
allowFileMutations: hasMutationTool(allowedTools),
|
|
98
|
-
createdAt: 'file',
|
|
178
|
+
createdAt: firstString(metadata.createdAt, metadata['created-at']) || 'file',
|
|
99
179
|
updatedAt: 'file',
|
|
100
180
|
}
|
|
101
181
|
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
export const AGENT_PROFILE_TOOL_NAMES = ['read_file', 'grep_files', 'write_file', 'edit_file', 'run_command']
|
|
2
|
+
export const AGENT_PROFILE_MODEL_INHERIT = { mode: 'inherit' }
|
|
3
|
+
export const AGENT_PROFILE_THINKING_LEVELS = ['inherit', 'off', 'low', 'medium', 'high', 'xhigh']
|
|
4
|
+
|
|
5
|
+
const agentProfileThinkingLevels = new Set(AGENT_PROFILE_THINKING_LEVELS)
|
|
6
|
+
|
|
7
|
+
export const CAPABILITY_POLICIES = {
|
|
8
|
+
'readonly-research': {
|
|
9
|
+
readonly: true,
|
|
10
|
+
allowedTools: ['read_file', 'grep_files', 'run_command'],
|
|
11
|
+
},
|
|
12
|
+
'safe-validation': {
|
|
13
|
+
readonly: true,
|
|
14
|
+
allowedTools: ['read_file', 'grep_files', 'run_command'],
|
|
15
|
+
},
|
|
16
|
+
'review-only': {
|
|
17
|
+
readonly: true,
|
|
18
|
+
allowedTools: ['read_file', 'grep_files'],
|
|
19
|
+
},
|
|
20
|
+
'docs-edit': {
|
|
21
|
+
readonly: false,
|
|
22
|
+
allowedTools: ['read_file', 'grep_files', 'write_file', 'edit_file'],
|
|
23
|
+
pathScope: ['docs/**', 'README.md', 'CHANGELOG.md'],
|
|
24
|
+
},
|
|
25
|
+
'code-edit': {
|
|
26
|
+
readonly: false,
|
|
27
|
+
allowedTools: ['read_file', 'grep_files', 'write_file', 'edit_file', 'run_command'],
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const policyNames = new Set(Object.keys(CAPABILITY_POLICIES))
|
|
32
|
+
const allowedToolNames = new Set(AGENT_PROFILE_TOOL_NAMES)
|
|
33
|
+
|
|
34
|
+
function requestError(message, statusCode = 400) {
|
|
35
|
+
const error = new Error(message)
|
|
36
|
+
error.statusCode = statusCode
|
|
37
|
+
return error
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function normalizeString(value) {
|
|
41
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function sameBaseUrl(a, b) {
|
|
45
|
+
return String(a || '').trim().replace(/\/$/, '') === String(b || '').trim().replace(/\/$/, '')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function readModelField(value, ...keys) {
|
|
49
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined
|
|
50
|
+
for (const key of keys) {
|
|
51
|
+
const text = normalizeString(value[key])
|
|
52
|
+
if (text) return text
|
|
53
|
+
}
|
|
54
|
+
return undefined
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function inferCapabilityPolicy(allowedTools = []) {
|
|
58
|
+
if (allowedTools.some((toolName) => toolName === 'write_file' || toolName === 'edit_file')) return 'code-edit'
|
|
59
|
+
if (allowedTools.includes('run_command')) return 'readonly-research'
|
|
60
|
+
return 'review-only'
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function normalizeCapabilityPolicy(value, allowedTools = []) {
|
|
64
|
+
const policy = normalizeString(value)?.toLowerCase()
|
|
65
|
+
if (policy && policyNames.has(policy)) return policy
|
|
66
|
+
return inferCapabilityPolicy(allowedTools)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function validateAgentProfileTools(allowedTools = [], capabilityPolicy = inferCapabilityPolicy(allowedTools)) {
|
|
70
|
+
const policy = CAPABILITY_POLICIES[capabilityPolicy]
|
|
71
|
+
if (!policy) throw requestError(`Unsupported capability policy: ${capabilityPolicy}`)
|
|
72
|
+
const allowedByPolicy = new Set(policy.allowedTools)
|
|
73
|
+
for (const toolName of allowedTools) {
|
|
74
|
+
if (!allowedToolNames.has(toolName)) throw requestError(`Unsupported tool for custom agent: ${toolName}`)
|
|
75
|
+
if (!allowedByPolicy.has(toolName)) throw requestError(`Tool ${toolName} is not allowed by capability policy ${capabilityPolicy}`)
|
|
76
|
+
}
|
|
77
|
+
return true
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function applyCapabilityPolicy(allowedTools = [], capabilityPolicy = inferCapabilityPolicy(allowedTools)) {
|
|
81
|
+
const policy = CAPABILITY_POLICIES[capabilityPolicy] || CAPABILITY_POLICIES['review-only']
|
|
82
|
+
const allowedByPolicy = new Set(policy.allowedTools)
|
|
83
|
+
return allowedTools.filter((toolName) => allowedByPolicy.has(toolName))
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function normalizeModelReference(input, metadata = {}) {
|
|
87
|
+
const modelValue = input === undefined ? metadata.model : input
|
|
88
|
+
const flatMode = normalizeString(metadata['model-mode'] ?? metadata.modelMode)?.toLowerCase()
|
|
89
|
+
const flatProvider = normalizeString(metadata['model-provider'] ?? metadata.modelProvider ?? metadata.provider)
|
|
90
|
+
const flatModelId = normalizeString(metadata['model-id'] ?? metadata.model_id ?? metadata.modelId)
|
|
91
|
+
const flatApi = normalizeString(metadata['model-api'] ?? metadata.modelApi ?? metadata.api)
|
|
92
|
+
const flatBaseUrl = normalizeString(metadata['model-base-url'] ?? metadata.modelBaseUrl ?? metadata.baseUrl)
|
|
93
|
+
|
|
94
|
+
if (flatMode === 'fixed' || flatProvider || flatModelId) {
|
|
95
|
+
return {
|
|
96
|
+
mode: 'fixed',
|
|
97
|
+
provider: flatProvider,
|
|
98
|
+
modelId: flatModelId,
|
|
99
|
+
api: flatApi,
|
|
100
|
+
baseUrl: flatBaseUrl,
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (modelValue === undefined || modelValue === null || modelValue === '') return { ...AGENT_PROFILE_MODEL_INHERIT }
|
|
105
|
+
if (typeof modelValue === 'string') {
|
|
106
|
+
const normalized = modelValue.trim().toLowerCase()
|
|
107
|
+
if (!normalized || normalized === 'inherit' || normalized === 'parent' || normalized === 'default') return { ...AGENT_PROFILE_MODEL_INHERIT }
|
|
108
|
+
const colon = modelValue.indexOf(':')
|
|
109
|
+
if (colon > 0) {
|
|
110
|
+
return {
|
|
111
|
+
mode: 'fixed',
|
|
112
|
+
provider: modelValue.slice(0, colon).trim(),
|
|
113
|
+
modelId: modelValue.slice(colon + 1).trim(),
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return { ...AGENT_PROFILE_MODEL_INHERIT }
|
|
117
|
+
}
|
|
118
|
+
if (!modelValue || typeof modelValue !== 'object' || Array.isArray(modelValue)) return { ...AGENT_PROFILE_MODEL_INHERIT }
|
|
119
|
+
|
|
120
|
+
const mode = normalizeString(modelValue.mode)?.toLowerCase()
|
|
121
|
+
if (!mode || mode === 'inherit' || mode === 'parent' || mode === 'default') return { ...AGENT_PROFILE_MODEL_INHERIT }
|
|
122
|
+
if (mode !== 'fixed') return { ...AGENT_PROFILE_MODEL_INHERIT }
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
mode: 'fixed',
|
|
126
|
+
provider: readModelField(modelValue, 'provider'),
|
|
127
|
+
modelId: readModelField(modelValue, 'modelId', 'model-id', 'model_id', 'id', 'model'),
|
|
128
|
+
api: readModelField(modelValue, 'api'),
|
|
129
|
+
baseUrl: readModelField(modelValue, 'baseUrl', 'base-url', 'base_url'),
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function validateModelReference(modelRef) {
|
|
134
|
+
const ref = modelRef || AGENT_PROFILE_MODEL_INHERIT
|
|
135
|
+
if (ref.mode !== 'fixed') return { ...AGENT_PROFILE_MODEL_INHERIT }
|
|
136
|
+
if (!ref.provider || !ref.modelId) throw requestError('Fixed agent model requires provider and modelId')
|
|
137
|
+
return {
|
|
138
|
+
mode: 'fixed',
|
|
139
|
+
provider: ref.provider,
|
|
140
|
+
modelId: ref.modelId,
|
|
141
|
+
api: ref.api,
|
|
142
|
+
baseUrl: ref.baseUrl,
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function normalizeAgentProfileThinkingLevel(value, fallback = 'inherit') {
|
|
147
|
+
const normalized = normalizeString(value)?.toLowerCase() || fallback
|
|
148
|
+
if (!agentProfileThinkingLevels.has(normalized)) {
|
|
149
|
+
throw requestError(`Unsupported agent thinking level: ${normalized}`)
|
|
150
|
+
}
|
|
151
|
+
return normalized
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function resolveAgentProfileThinkingLevel(profile, inheritedThinkingLevel, model) {
|
|
155
|
+
const configured = normalizeAgentProfileThinkingLevel(profile?.thinkingLevel)
|
|
156
|
+
const requested = configured === 'inherit'
|
|
157
|
+
? normalizeAgentProfileThinkingLevel(inheritedThinkingLevel || 'off', 'off')
|
|
158
|
+
: configured
|
|
159
|
+
return model?.reasoning === true ? requested : 'off'
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function modelReferenceSnapshot(modelRef) {
|
|
163
|
+
const ref = modelRef || AGENT_PROFILE_MODEL_INHERIT
|
|
164
|
+
if (ref.mode !== 'fixed') return { ...AGENT_PROFILE_MODEL_INHERIT }
|
|
165
|
+
return {
|
|
166
|
+
mode: 'fixed',
|
|
167
|
+
provider: ref.provider,
|
|
168
|
+
modelId: ref.modelId,
|
|
169
|
+
...(ref.api ? { api: ref.api } : {}),
|
|
170
|
+
...(ref.baseUrl ? { baseUrl: ref.baseUrl } : {}),
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export async function resolveAgentProfileModel(profile, parentModel, readStore) {
|
|
175
|
+
const ref = profile?.model || AGENT_PROFILE_MODEL_INHERIT
|
|
176
|
+
if (!ref || ref.mode !== 'fixed') {
|
|
177
|
+
if (!parentModel) throw requestError('No active model is configured for the parent session.')
|
|
178
|
+
return { model: parentModel, info: { ...modelReferenceSnapshot(ref), inherited: true } }
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const validRef = validateModelReference(ref)
|
|
182
|
+
const store = await readStore('custom-providers').catch(() => ({}))
|
|
183
|
+
const providers = Array.isArray(store) ? store : Object.values(store || {})
|
|
184
|
+
const configuredModels = providers
|
|
185
|
+
.flatMap((provider) => Array.isArray(provider?.models) ? provider.models : [])
|
|
186
|
+
.filter((model) => model && typeof model === 'object')
|
|
187
|
+
const model = configuredModels.find((candidate) => (
|
|
188
|
+
candidate.provider === validRef.provider &&
|
|
189
|
+
candidate.id === validRef.modelId &&
|
|
190
|
+
(!validRef.api || candidate.api === validRef.api) &&
|
|
191
|
+
(!validRef.baseUrl || sameBaseUrl(candidate.baseUrl, validRef.baseUrl))
|
|
192
|
+
))
|
|
193
|
+
if (!model) {
|
|
194
|
+
throw requestError(`Agent profile ${profile?.name || ''} requested model ${validRef.provider}/${validRef.modelId}, but it is not configured.`)
|
|
195
|
+
}
|
|
196
|
+
return { model, info: { ...modelReferenceSnapshot(validRef), inherited: false } }
|
|
197
|
+
}
|