@shawnstack/quickforge 1.7.11 → 1.8.0
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 +1 -1
- package/dist/assets/AgentProfilesPage-lKGIE5Dj.js +1 -0
- package/dist/assets/ChatPanelHost-CWUYIkXG.js +11 -0
- package/dist/assets/CloudAccountSettingsPage-BhXH0ups.js +1 -0
- package/dist/assets/{PluginsPage-mLvwvTmL.js → PluginsPage-DHWF13mN.js} +1 -1
- package/dist/assets/ScheduledTasksPage-DLAg-i8c.js +2 -0
- package/dist/assets/{SettingsWorkspacePage-0OINfEqK.js → SettingsWorkspacePage-BhUzL5oi.js} +313 -313
- package/dist/assets/ShareLinksSettingsPage-DogGa3lH.js +1 -0
- package/dist/assets/SharedConversationPage-7QpeaG_P.js +1 -0
- package/dist/assets/TerminalDock-CWSKcuiU.js +2 -0
- package/dist/assets/WorkspaceInspector-CEfyYqWy.js +36 -0
- package/dist/assets/{abnfDiagram-VRR7QNED-CMhVoYUh.js → abnfDiagram-VRR7QNED-DGklo3PO.js} +1 -1
- package/dist/assets/{architectureDiagram-ZJ3FMSHR-C52qL6Hg.js → architectureDiagram-ZJ3FMSHR-CioWLVTp.js} +1 -1
- package/dist/assets/{blockDiagram-677ZJIJ3-CM3XbLol.js → blockDiagram-677ZJIJ3-BJWBq5e9.js} +1 -1
- package/dist/assets/{c4Diagram-LMCZKHZV-D7Bh1ui1.js → c4Diagram-LMCZKHZV-BacRvqFK.js} +1 -1
- package/dist/assets/channel-CeXY-wZo.js +1 -0
- package/dist/assets/{chunk-32BRIVSS-xgrtbT8e.js → chunk-32BRIVSS-B53BNE2C.js} +1 -1
- package/dist/assets/{chunk-52WLFC77-CDbaHw80.js → chunk-52WLFC77-CYq4hT4f.js} +1 -1
- package/dist/assets/{chunk-C7G6YPKG-ZMLUvfLy.js → chunk-C7G6YPKG-KFaGpP_Z.js} +1 -1
- package/dist/assets/{chunk-EX3LRPZG-BcQhaqt7.js → chunk-EX3LRPZG-DqVd_ihG.js} +1 -1
- package/dist/assets/{chunk-FWX5IMBZ-DrnrdrSu.js → chunk-FWX5IMBZ-BxRjZkOP.js} +2 -2
- package/dist/assets/{chunk-HOUHSVGY-tdc-Z9eW.js → chunk-HOUHSVGY-C9QO8omv.js} +1 -1
- package/dist/assets/{chunk-ICXQ74PX-Cd7igVGt.js → chunk-ICXQ74PX-ClbGPgLV.js} +1 -1
- package/dist/assets/{chunk-MOJQB5TN-CLTjV5ov.js → chunk-MOJQB5TN-Ba4wlF1J.js} +1 -1
- package/dist/assets/{chunk-OGEWGWER-TgvkXkFX.js → chunk-OGEWGWER-DczhQmcx.js} +1 -1
- package/dist/assets/{chunk-PUDLZKDR-C5GMVYup.js → chunk-PUDLZKDR-CPWpcKUF.js} +1 -1
- package/dist/assets/{chunk-Q4XR5HBZ-fo5-HsqY.js → chunk-Q4XR5HBZ-CiF6T--X.js} +1 -1
- package/dist/assets/{chunk-V7JOEXUC-CEDazV6w.js → chunk-V7JOEXUC-Dxv0zwmw.js} +1 -1
- package/dist/assets/{chunk-VAUOI2AC-CWYjg8zE.js → chunk-VAUOI2AC-D3J5bw1P.js} +1 -1
- package/dist/assets/{chunk-VR4S4FIN-C_-eEj-9.js → chunk-VR4S4FIN-BXQSwwII.js} +1 -1
- package/dist/assets/{chunk-WYO6CB5R-NF-MsJJR.js → chunk-WYO6CB5R-C0azl-fk.js} +2 -2
- package/dist/assets/{chunk-ZGVPDNZ5-CRrAYi4g.js → chunk-ZGVPDNZ5-D_8F4Akx.js} +1 -1
- package/dist/assets/classDiagram-OUVF2IWQ-B8M6sf49.js +1 -0
- package/dist/assets/classDiagram-v2-EOCWNBFH-BY7fzBDH.js +1 -0
- package/dist/assets/{cynefinDiagram-TSTJHNR4-D4sZ7LYI.js → cynefinDiagram-TSTJHNR4-BFlsWz0B.js} +1 -1
- package/dist/assets/{dagre-VKFMJZFB-BlsnSh-Q.js → dagre-VKFMJZFB-DXhpjlEs.js} +1 -1
- package/dist/assets/{diagram-FQU43EPY-DW1yQiRO.js → diagram-FQU43EPY-DRVVOD9k.js} +1 -1
- package/dist/assets/{diagram-G47NLZAW-BTUAsFjb.js → diagram-G47NLZAW-BV9Gw1tL.js} +1 -1
- package/dist/assets/{diagram-NH7WQ7WH-BcaomqSd.js → diagram-NH7WQ7WH-CrUkXfRY.js} +1 -1
- package/dist/assets/{diagram-OA4YK3LP-DDLX7Xwr.js → diagram-OA4YK3LP-CuILjn7v.js} +1 -1
- package/dist/assets/{diagram-WEI45ONY-CIave0An.js → diagram-WEI45ONY-nlNN3tWB.js} +1 -1
- package/dist/assets/{ebnfDiagram-CCIWWBDH-Cr1Y6lPu.js → ebnfDiagram-CCIWWBDH-BuDgZt9n.js} +1 -1
- package/dist/assets/{erDiagram-Q63AITRT-D3EgiVVH.js → erDiagram-Q63AITRT-MEwxjmtP.js} +1 -1
- package/dist/assets/flowDiagram-23GEKE2U-BiUP-Scq.js +1 -0
- package/dist/assets/{ganttDiagram-NO4QXBWP-B0oyDIqK.js → ganttDiagram-NO4QXBWP-DNw5qghE.js} +1 -1
- package/dist/assets/{gitGraphDiagram-IHSO6WYX-uQtr6IqK.js → gitGraphDiagram-IHSO6WYX-CaTcakAt.js} +1 -1
- package/dist/assets/index-CX36XjVA.css +3 -0
- package/dist/assets/index-CgVTwN15.js +66 -0
- package/dist/assets/{infoDiagram-FWYZ7A6U-B_GzlLIM.js → infoDiagram-FWYZ7A6U-DwfyP7dU.js} +1 -1
- package/dist/assets/{ishikawaDiagram-FXEZZL3T-CKQIsSdU.js → ishikawaDiagram-FXEZZL3T-CmrzbOCV.js} +1 -1
- package/dist/assets/{journeyDiagram-5HDEW3XC-DrrvzZL1.js → journeyDiagram-5HDEW3XC-CYqe3sgr.js} +1 -1
- package/dist/assets/{kanban-definition-HUTT4EX6-CJQRTdXz.js → kanban-definition-HUTT4EX6-BMDO_mv0.js} +1 -1
- package/dist/assets/{line-B4DxX7tC.js → line-SbxJR8ZT.js} +1 -1
- package/dist/assets/lit-vendor-CinchGmI.js +2 -0
- package/dist/assets/local-tools-CJVNO1aW.js +388 -0
- package/dist/assets/{mcp-servers-dialog-B2lALtTE.js → mcp-servers-dialog-xf3UR49J.js} +2 -2
- package/dist/assets/{mermaid.core-DWpShXCs.js → mermaid.core-CwXTzYjs.js} +3 -3
- package/dist/assets/{mindmap-definition-LN4V7U3C-CEVwZD78.js → mindmap-definition-LN4V7U3C-CLyJ78mO.js} +1 -1
- package/dist/assets/{pegDiagram-2B236MQR-CBLkBdnQ.js → pegDiagram-2B236MQR-4qFznrSw.js} +1 -1
- package/dist/assets/{pi-web-ui-DV5jKjSg.js → pi-web-ui-BNuvqKDT.js} +289 -289
- package/dist/assets/{pieDiagram-ENE6RG2P-8tCnwtWS.js → pieDiagram-ENE6RG2P-141sGxVe.js} +1 -1
- package/dist/assets/{quadrantDiagram-ABIIQ3AL-CWd28cHF.js → quadrantDiagram-ABIIQ3AL-SUI44pAX.js} +1 -1
- package/dist/assets/{railroadDiagram-RFXS5EU6-CI4ouOBs.js → railroadDiagram-RFXS5EU6-1oiZuYBB.js} +1 -1
- package/dist/assets/{requirementDiagram-TGXJPOKE-DtFz7sw4.js → requirementDiagram-TGXJPOKE-CfVishLi.js} +1 -1
- package/dist/assets/{sankeyDiagram-HTMAVEWB--VeOwwk9.js → sankeyDiagram-HTMAVEWB-DVu7J_Z9.js} +1 -1
- package/dist/assets/{sequenceDiagram-DBY2YBRQ-B8AHk5Oe.js → sequenceDiagram-DBY2YBRQ-D-Ip2yiS.js} +1 -1
- package/dist/assets/{skills-dialog-DYp35JM3.js → skills-dialog-Bbqtuy2h.js} +3 -3
- package/dist/assets/{stateDiagram-2N3HPSRC-etXnJ8X8.js → stateDiagram-2N3HPSRC-Cg0hWwrp.js} +1 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-DhmzNQd9.js +1 -0
- package/dist/assets/{swimlanes-5IMT3BWC-pabIKAmn.js → swimlanes-5IMT3BWC-Co5D-cGU.js} +1 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-BEvomQNa.js +8 -0
- package/dist/assets/{timeline-definition-FHXFAJF6-C55MKm3L.js → timeline-definition-FHXFAJF6-CBk4c_Ua.js} +1 -1
- package/dist/assets/{vennDiagram-L72KCM5P-CgNf1Q9W.js → vennDiagram-L72KCM5P-BII7wdRx.js} +1 -1
- package/dist/assets/{wardleyDiagram-EHGQE667-Drt5ytec.js → wardleyDiagram-EHGQE667-CkyJJIXP.js} +1 -1
- package/dist/assets/{xychartDiagram-FW5EYKEG-hhoEo_Ar.js → xychartDiagram-FW5EYKEG-B1A66Ks0.js} +1 -1
- package/dist/index.html +6 -6
- package/dist/licenses/material-icon-theme.txt +8 -8
- package/package.json +1 -1
- package/server/agent-manager.mjs +305 -49
- package/server/approval-store.mjs +1 -0
- package/server/ask-store.mjs +78 -0
- package/server/context-references.mjs +139 -0
- package/server/custom-commands.mjs +43 -0
- package/server/index.mjs +1 -1
- package/server/project-config.mjs +17 -5
- package/server/routes/agent-profiles.mjs +16 -1
- package/server/routes/agent.mjs +10 -1
- package/server/routes/shared-conversation.mjs +13 -1
- package/server/routes/skills.mjs +22 -0
- package/server/routes/workspace.mjs +166 -2
- package/server/selected-capabilities.mjs +76 -0
- package/server/session-state-import.mjs +20 -3
- package/server/skills.mjs +1 -1
- package/server/system-prompt.mjs +1 -3
- package/server/tools/definitions.mjs +18 -10
- package/server/tools/index.mjs +14 -2
- package/server/utils/workspace.mjs +13 -4
- package/skills/skill-creator/SKILL.md +485 -485
- package/skills/skill-creator/agents/analyzer.md +274 -274
- package/skills/skill-creator/agents/comparator.md +202 -202
- package/skills/skill-creator/agents/grader.md +223 -223
- package/skills/skill-creator/assets/eval_review.html +146 -146
- package/skills/skill-creator/eval-viewer/viewer.html +1325 -1325
- package/skills/skill-creator/references/schemas.md +430 -430
- package/dist/assets/AgentProfilesPage-Cvb1KTz5.js +0 -1
- package/dist/assets/ChatPanelHost-C7zV-xE_.js +0 -48
- package/dist/assets/CloudAccountSettingsPage-mAs_nbvw.js +0 -1
- package/dist/assets/ScheduledTasksPage-CuUW_9lq.js +0 -2
- package/dist/assets/ShareLinksSettingsPage-DqeyJYoD.js +0 -1
- package/dist/assets/SharedConversationPage-C7CDEoX9.js +0 -1
- package/dist/assets/TerminalDock-CZXcuMpq.js +0 -2
- package/dist/assets/WorkspaceInspector-CNSBOEvP.js +0 -36
- package/dist/assets/channel-Duu1CuH9.js +0 -1
- package/dist/assets/classDiagram-OUVF2IWQ-Cc63fLWP.js +0 -1
- package/dist/assets/classDiagram-v2-EOCWNBFH-CoXU0qT6.js +0 -1
- package/dist/assets/flowDiagram-23GEKE2U-BS7KXcqn.js +0 -1
- package/dist/assets/index-BgMvQW1b.js +0 -66
- package/dist/assets/index-CB1g2D0f.css +0 -3
- package/dist/assets/lit-vendor-DvaQX_UQ.js +0 -2
- package/dist/assets/local-tools-Dopjg9xq.js +0 -270
- package/dist/assets/stateDiagram-v2-6OUMAXLB-Dms3pICj.js +0 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-B14aWjv6.js +0 -8
package/server/agent-manager.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
composeSubagentSystemPrompt,
|
|
14
14
|
formatSubagentTask,
|
|
15
15
|
} from './subagents.mjs'
|
|
16
|
-
import { agentProfileSnapshot, getAgentProfile } from './agent-profiles.mjs'
|
|
16
|
+
import { agentProfileSnapshot, getAgentProfile, listSubagentProfiles } from './agent-profiles.mjs'
|
|
17
17
|
import { modelBindingFromModel, resolveImplicitModelPreference, resolveModelBinding } from './model-catalog.mjs'
|
|
18
18
|
import { agentProfileFromMarkdown } from './agent-profile-files.mjs'
|
|
19
19
|
import {
|
|
@@ -52,11 +52,27 @@ import {
|
|
|
52
52
|
readAutoCompactSettings,
|
|
53
53
|
} from './auto-compaction.mjs'
|
|
54
54
|
import {
|
|
55
|
+
formatAgentCommandPrompt,
|
|
56
|
+
formatSkillCommandPrompt,
|
|
55
57
|
handleInternalCommand,
|
|
56
58
|
parseInternalCommandInvocation,
|
|
57
59
|
resolveCustomCommandInvocation,
|
|
58
60
|
} from './custom-commands.mjs'
|
|
61
|
+
import { mergeSkills, normalizeSkillNames } from './skills.mjs'
|
|
62
|
+
import {
|
|
63
|
+
contextReferencesFromMessage,
|
|
64
|
+
contextReferencesPrompt,
|
|
65
|
+
validatePromptContextReferences,
|
|
66
|
+
validateContextReferences,
|
|
67
|
+
withCanonicalContextReferences,
|
|
68
|
+
} from './context-references.mjs'
|
|
59
69
|
import { serverConvertToLlm, messageText, lastAssistantText } from './message-converters.mjs'
|
|
70
|
+
import {
|
|
71
|
+
normalizeSelectedCapabilities,
|
|
72
|
+
selectedCapabilitiesFromMessage,
|
|
73
|
+
selectedCapabilityPrompt,
|
|
74
|
+
withCanonicalSelectedCapabilities,
|
|
75
|
+
} from './selected-capabilities.mjs'
|
|
60
76
|
import { mergeQuickForgeTiming, wrapToolDefinition, wrapMcpToolDefinition, wrapPluginToolDefinition, sessionSkillsContext } from './tool-wiring.mjs'
|
|
61
77
|
import {
|
|
62
78
|
APPROVAL_TIMEOUT_MS,
|
|
@@ -68,6 +84,15 @@ import {
|
|
|
68
84
|
commandToolPermissionError,
|
|
69
85
|
createCommandToolPermissions,
|
|
70
86
|
} from './approval-store.mjs'
|
|
87
|
+
import {
|
|
88
|
+
ASK_TIMEOUT_MS,
|
|
89
|
+
pendingAsks,
|
|
90
|
+
getPendingAskForSession,
|
|
91
|
+
normalizeAskQuestions,
|
|
92
|
+
formatAskResult,
|
|
93
|
+
} from './ask-store.mjs'
|
|
94
|
+
|
|
95
|
+
export { getPendingAskForSession, normalizeAskQuestions } from './ask-store.mjs'
|
|
71
96
|
|
|
72
97
|
// ---------------------------------------------------------------------------
|
|
73
98
|
// Tool definitions (server-side, no REST roundtrip)
|
|
@@ -88,8 +113,25 @@ function wrapSubagentToolDefinition(definition, parentSessionId) {
|
|
|
88
113
|
}
|
|
89
114
|
}
|
|
90
115
|
|
|
116
|
+
function wrapAskUserToolDefinition(definition, parentSessionId) {
|
|
117
|
+
return {
|
|
118
|
+
...definition,
|
|
119
|
+
execute: async (toolCallId, params) => {
|
|
120
|
+
const session = agentSessions.get(parentSessionId)
|
|
121
|
+
if (!session) {
|
|
122
|
+
return {
|
|
123
|
+
content: [{ type: 'text', text: 'No active session for ask_user.' }],
|
|
124
|
+
details: { askId: null, skipped: true },
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return createAskUserPromise(session, toolCallId, params || {})
|
|
128
|
+
},
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
91
132
|
function wrapWorkspaceToolDefinition(definition, context, toolPermissions, options = {}) {
|
|
92
133
|
if (definition.name === 'run_subagent') return wrapSubagentToolDefinition(definition, options.parentSessionId)
|
|
134
|
+
if (definition.name === 'ask_user') return wrapAskUserToolDefinition(definition, options.parentSessionId)
|
|
93
135
|
return wrapToolDefinition(definition, context, toolPermissions)
|
|
94
136
|
}
|
|
95
137
|
|
|
@@ -304,8 +346,83 @@ function createApprovalPromise(session, toolCallId, toolName, args, source) {
|
|
|
304
346
|
})
|
|
305
347
|
}
|
|
306
348
|
|
|
307
|
-
|
|
308
|
-
|
|
349
|
+
/**
|
|
350
|
+
* Create a Promise that resolves when the user answers (or skips) an ask_user
|
|
351
|
+
* tool call. The tool's execute blocks on this promise, pausing the agent loop
|
|
352
|
+
* until the user responds.
|
|
353
|
+
*/
|
|
354
|
+
function createAskUserPromise(session, toolCallId, params) {
|
|
355
|
+
const questions = normalizeAskQuestions(params)
|
|
356
|
+
if (!questions.length) {
|
|
357
|
+
return Promise.resolve({
|
|
358
|
+
content: [{ type: 'text', text: formatAskResult(questions, null, true, 'no-questions') }],
|
|
359
|
+
details: { askId: null, skipped: true },
|
|
360
|
+
})
|
|
361
|
+
}
|
|
362
|
+
const askId = randomUUID()
|
|
363
|
+
return new Promise((resolve) => {
|
|
364
|
+
let settled = false
|
|
365
|
+
const requestedAt = Date.now()
|
|
366
|
+
const expiresAt = requestedAt + ASK_TIMEOUT_MS
|
|
367
|
+
|
|
368
|
+
const timeout = setTimeout(() => {
|
|
369
|
+
if (settled) return
|
|
370
|
+
finish({ skipped: true, reason: 'timeout' })
|
|
371
|
+
}, ASK_TIMEOUT_MS)
|
|
372
|
+
|
|
373
|
+
let onAbort = null
|
|
374
|
+
|
|
375
|
+
const finish = (payload) => {
|
|
376
|
+
if (settled) return
|
|
377
|
+
settled = true
|
|
378
|
+
clearTimeout(timeout)
|
|
379
|
+
if (onAbort && session.agent.signal) session.agent.signal.removeEventListener('abort', onAbort)
|
|
380
|
+
pendingAsks.delete(askId)
|
|
381
|
+
const answers = payload.skipped ? null : payload.answers
|
|
382
|
+
resolve({
|
|
383
|
+
content: [{ type: 'text', text: formatAskResult(questions, answers, payload.skipped, payload.reason) }],
|
|
384
|
+
details: { askId, questions, answers, skipped: !!payload.skipped, ...(payload.reason ? { skipReason: payload.reason } : {}) },
|
|
385
|
+
})
|
|
386
|
+
emitSessionEvent(session, {
|
|
387
|
+
type: 'ask_user_answered',
|
|
388
|
+
sessionId: session.sessionId,
|
|
389
|
+
askId,
|
|
390
|
+
toolCallId,
|
|
391
|
+
skipped: !!payload.skipped,
|
|
392
|
+
answers: answers || [],
|
|
393
|
+
})
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const signal = session.agent.signal
|
|
397
|
+
if (signal) {
|
|
398
|
+
if (signal.aborted) {
|
|
399
|
+
finish({ skipped: true, reason: 'aborted' })
|
|
400
|
+
return
|
|
401
|
+
}
|
|
402
|
+
onAbort = () => finish({ skipped: true, reason: 'aborted' })
|
|
403
|
+
signal.addEventListener('abort', onAbort, { once: true })
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
pendingAsks.set(askId, {
|
|
407
|
+
finish: (payload) => finish(payload),
|
|
408
|
+
sessionId: session.sessionId,
|
|
409
|
+
toolCallId,
|
|
410
|
+
questions,
|
|
411
|
+
requestedAt,
|
|
412
|
+
expiresAt,
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
emitSessionEvent(session, {
|
|
416
|
+
type: 'ask_user_required',
|
|
417
|
+
sessionId: session.sessionId,
|
|
418
|
+
askId,
|
|
419
|
+
toolCallId,
|
|
420
|
+
questions,
|
|
421
|
+
})
|
|
422
|
+
})
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function createAcpApprovalPromise(session, request) { return new Promise((resolve, reject) => {
|
|
309
426
|
let settled = false
|
|
310
427
|
const requestedAt = Date.now()
|
|
311
428
|
const expiresAt = requestedAt + APPROVAL_TIMEOUT_MS
|
|
@@ -1035,10 +1152,103 @@ function planCommandState(userMessage, args) {
|
|
|
1035
1152
|
}
|
|
1036
1153
|
}
|
|
1037
1154
|
|
|
1155
|
+
function parseSlashNameAndTask(args) {
|
|
1156
|
+
const trimmed = String(args || '').trim()
|
|
1157
|
+
if (!trimmed) return { name: '', task: '' }
|
|
1158
|
+
const match = trimmed.match(/^(\S+)(?:\s+([\s\S]*))?$/)
|
|
1159
|
+
return { name: match?.[1] || '', task: (match?.[2] || '').trim() }
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
async function enabledSkillsForSession(session) {
|
|
1163
|
+
// Same sources as the activate_skill tool (loadSkillToolContext), resolved
|
|
1164
|
+
// from the session's captured skill selections and workspace.
|
|
1165
|
+
const skillContext = await loadSkillToolContext({
|
|
1166
|
+
...sessionSkillsContext(session),
|
|
1167
|
+
workspaceRoot: session.projectContext?.workspaceRoot,
|
|
1168
|
+
})
|
|
1169
|
+
return mergeSkills(skillContext.globalSkills, skillContext.projectSkills)
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
function enabledSkillsLine(enabledSkills) {
|
|
1173
|
+
if (enabledSkills.length === 0) return 'No skills are currently enabled.'
|
|
1174
|
+
return `Enabled skills: ${enabledSkills.map((skill) => skill.name).join(', ')}.`
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
function availableSubagentsLine(profiles) {
|
|
1178
|
+
return `Available subagents: ${profiles.map((profile) => profile.name).join(', ')}.`
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
async function skillCommandState(session, userMessage, args) {
|
|
1182
|
+
const { name, task } = parseSlashNameAndTask(args)
|
|
1183
|
+
const enabledSkills = await enabledSkillsForSession(session)
|
|
1184
|
+
|
|
1185
|
+
if (!name) {
|
|
1186
|
+
return { textResponse: ['Usage: /skill <name> [task]', '', enabledSkillsLine(enabledSkills)].join('\n') }
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
const skillName = normalizeSkillNames([name])[0]
|
|
1190
|
+
const skill = skillName ? enabledSkills.find((item) => item.name === skillName) : null
|
|
1191
|
+
if (!skill) {
|
|
1192
|
+
return {
|
|
1193
|
+
textResponse: [
|
|
1194
|
+
`Unknown or disabled skill: ${name}`,
|
|
1195
|
+
'',
|
|
1196
|
+
'Usage: /skill <name> [task]',
|
|
1197
|
+
'',
|
|
1198
|
+
enabledSkillsLine(enabledSkills),
|
|
1199
|
+
].join('\n'),
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
return {
|
|
1204
|
+
userMessage,
|
|
1205
|
+
commandPrompt: formatSkillCommandPrompt(skill.name, task),
|
|
1206
|
+
commandName: 'skill',
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
async function agentCommandState(session, userMessage, args) {
|
|
1211
|
+
const { name, task } = parseSlashNameAndTask(args)
|
|
1212
|
+
const profileOptions = { workspaceRoot: session.projectContext?.workspaceRoot }
|
|
1213
|
+
|
|
1214
|
+
if (!name) {
|
|
1215
|
+
const profiles = await listSubagentProfiles(profileOptions)
|
|
1216
|
+
return { textResponse: ['Usage: /agent <name> <task>', '', availableSubagentsLine(profiles)].join('\n') }
|
|
1217
|
+
}
|
|
1218
|
+
if (!task) {
|
|
1219
|
+
return { textResponse: 'Usage: /agent <name> <task>' }
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
const profile = await getAgentProfile(name, profileOptions)
|
|
1223
|
+
if (!profile || profile.enabledAsSubagent !== true) {
|
|
1224
|
+
const profiles = await listSubagentProfiles(profileOptions)
|
|
1225
|
+
return {
|
|
1226
|
+
textResponse: [
|
|
1227
|
+
`Unknown subagent: ${name}`,
|
|
1228
|
+
'',
|
|
1229
|
+
'Usage: /agent <name> <task>',
|
|
1230
|
+
'',
|
|
1231
|
+
availableSubagentsLine(profiles),
|
|
1232
|
+
].join('\n'),
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
return {
|
|
1237
|
+
userMessage,
|
|
1238
|
+
commandPrompt: formatAgentCommandPrompt(profile.name, task),
|
|
1239
|
+
commandName: 'agent',
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1038
1243
|
async function resolveCommandState(session, userMessage, promptCommand = null) {
|
|
1039
1244
|
const command = normalizedPromptCommand(promptCommand) || promptCommandFromMessage(userMessage)
|
|
1245
|
+
const internalInvocation = internalInvocationForPromptCommand(userMessage, command)
|
|
1246
|
+
// /skill and /agent need session context (enabled skills, workspace-rooted
|
|
1247
|
+
// agent profiles), so they are resolved here before handleInternalCommand.
|
|
1248
|
+
if (internalInvocation?.type === 'skill') return skillCommandState(session, userMessage, internalInvocation.args)
|
|
1249
|
+
if (internalInvocation?.type === 'agent') return agentCommandState(session, userMessage, internalInvocation.args)
|
|
1040
1250
|
const internalResponse = await handleInternalCommand(
|
|
1041
|
-
|
|
1251
|
+
internalInvocation,
|
|
1042
1252
|
session.projectContext?.workspaceRoot,
|
|
1043
1253
|
session.projectContext?.project?.commandDir,
|
|
1044
1254
|
)
|
|
@@ -1066,6 +1276,14 @@ async function resolveCommandState(session, userMessage, promptCommand = null) {
|
|
|
1066
1276
|
commandName: 'review',
|
|
1067
1277
|
}
|
|
1068
1278
|
}
|
|
1279
|
+
if (internalResponse?.commit) {
|
|
1280
|
+
return {
|
|
1281
|
+
userMessage,
|
|
1282
|
+
commandPrompt: formatCommitCommandPrompt(internalResponse.args),
|
|
1283
|
+
permissions: { allowEdit: false, allowCommands: true, allowSubagents: false },
|
|
1284
|
+
commandName: 'commit',
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1069
1287
|
|
|
1070
1288
|
if (!session.projectContext?.workspaceRoot) {
|
|
1071
1289
|
// Even without a project, user-level custom commands (~/.quickforge/commands/) are available
|
|
@@ -1175,6 +1393,24 @@ ${taskText}
|
|
|
1175
1393
|
</plan_command_invocation>`
|
|
1176
1394
|
}
|
|
1177
1395
|
|
|
1396
|
+
function formatCommitCommandPrompt(message) {
|
|
1397
|
+
const messageText = String(message || '').trim() || '(none; generate a message from the diff and repository style)'
|
|
1398
|
+
return `<commit_command_invocation name="commit">
|
|
1399
|
+
This /commit command applies only to the current user request. Create at most one local commit for the current task.
|
|
1400
|
+
|
|
1401
|
+
Rules for this turn:
|
|
1402
|
+
- Inspect the current task's Git changes and commit only files related to this task; do not mix in unrelated changes.
|
|
1403
|
+
- Never use \`git add .\`, \`git add -A\`, or \`git add --all\`; stage only explicit task-related paths.
|
|
1404
|
+
- Run relevant validation before committing and stop if it fails. Do not modify code, bypass hooks, or alter unrelated changes.
|
|
1405
|
+
- Create at most one local commit. Do not push, tag, release, publish, or otherwise affect a remote.
|
|
1406
|
+
- Use the requested message below, or generate one from the diff and repository conventions when none is provided.
|
|
1407
|
+
- Report the commit hash and message, validations run, and any remaining working tree changes.
|
|
1408
|
+
|
|
1409
|
+
Requested commit message:
|
|
1410
|
+
${messageText}
|
|
1411
|
+
</commit_command_invocation>`
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1178
1414
|
function formatReviewCommandPrompt(scope) {
|
|
1179
1415
|
const scopeText = String(scope || '').trim() || '(none; review the repository changes that appear relevant for a pre-commit check)'
|
|
1180
1416
|
return `<review_command_invocation name="review">
|
|
@@ -1541,30 +1777,6 @@ function textFromMessageContent(content) {
|
|
|
1541
1777
|
return ''
|
|
1542
1778
|
}
|
|
1543
1779
|
|
|
1544
|
-
function selectedCapabilityPrompt(capabilities) {
|
|
1545
|
-
if (!Array.isArray(capabilities) || capabilities.length === 0) return null
|
|
1546
|
-
const normalized = capabilities
|
|
1547
|
-
.filter((capability) => capability && typeof capability === 'object')
|
|
1548
|
-
.map((capability) => ({
|
|
1549
|
-
type: String(capability.type || '').slice(0, 32),
|
|
1550
|
-
pluginName: String(capability.pluginName || '').slice(0, 120),
|
|
1551
|
-
name: String(capability.name || '').slice(0, 120),
|
|
1552
|
-
label: String(capability.label || capability.name || '').slice(0, 160),
|
|
1553
|
-
description: String(capability.description || '').slice(0, 400),
|
|
1554
|
-
}))
|
|
1555
|
-
.filter((capability) => capability.type && capability.pluginName && capability.name)
|
|
1556
|
-
.slice(0, 4)
|
|
1557
|
-
if (normalized.length === 0) return null
|
|
1558
|
-
|
|
1559
|
-
const lines = normalized.map((capability) => {
|
|
1560
|
-
const toolHint = capability.type === 'tool' ? ` Tool name: plugin__${capability.pluginName}__${capability.name}.` : ''
|
|
1561
|
-
const description = capability.description ? ` Description: ${capability.description}` : ''
|
|
1562
|
-
return `- ${capability.label} (${capability.type}, plugin: ${capability.pluginName}, name: ${capability.name}).${toolHint}${description}`
|
|
1563
|
-
}).join('\n')
|
|
1564
|
-
|
|
1565
|
-
return `The user selected the following QuickForge plugin capability mentions for this turn. Treat them as an explicit preference for routing and context. Use the selected capability when relevant, but do not force it if it is unrelated to the actual request.\n\n${lines}`
|
|
1566
|
-
}
|
|
1567
|
-
|
|
1568
1780
|
function applyActiveCapabilityPrompt(messages, capabilityPrompt) {
|
|
1569
1781
|
if (!capabilityPrompt) return messages
|
|
1570
1782
|
|
|
@@ -1880,6 +2092,8 @@ export async function createAgent(sessionId, config = {}) {
|
|
|
1880
2092
|
if (commandPermissionError) return { block: true, reason: commandPermissionError }
|
|
1881
2093
|
const isSkillTool = toolName === 'activate_skill' || toolName === 'read_skill_resource'
|
|
1882
2094
|
if (isSkillTool) return undefined
|
|
2095
|
+
// ask_user only waits for the user's answer; it changes nothing and needs no approval.
|
|
2096
|
+
if (toolName === 'ask_user') return undefined
|
|
1883
2097
|
if (profileToolNames && !profileToolNames.includes(toolName)) return { block: true, reason: `Agent profile ${agentProfile.name} is not allowed to use ${toolName}.` }
|
|
1884
2098
|
if (toolName === 'manage_global_memory') return undefined
|
|
1885
2099
|
if (toolName === 'run_subagent') {
|
|
@@ -2592,14 +2806,11 @@ async function refreshSessionModelBinding(session) {
|
|
|
2592
2806
|
* Send a user message to the agent and start the agent loop.
|
|
2593
2807
|
* Returns immediately; events are streamed via the event bus.
|
|
2594
2808
|
*/
|
|
2595
|
-
export async function runPrompt(sessionId, message, selectedCapabilities = [], promptCommand = null, transientContextPrompt = null, modelAccessContext = null) {
|
|
2809
|
+
export async function runPrompt(sessionId, message, selectedCapabilities = [], promptCommand = null, transientContextPrompt = null, modelAccessContext = null, contextReferences = undefined) {
|
|
2596
2810
|
let session = await syncSessionFromStorage(sessionId)
|
|
2597
2811
|
if (!session) {
|
|
2598
2812
|
throw Object.assign(new Error('Session not found'), { statusCode: 404 })
|
|
2599
2813
|
}
|
|
2600
|
-
if (modelAccessContext) session.modelAccessContext = modelAccessContext
|
|
2601
|
-
if (session.harness === AGENT_HARNESS_QUICKFORGE) await refreshSessionModelBinding(session)
|
|
2602
|
-
|
|
2603
2814
|
if (session.agent.state.isStreaming || session.abortPending) {
|
|
2604
2815
|
throw Object.assign(new Error('Generation is still running. Stop it or wait until it finishes.'), {
|
|
2605
2816
|
statusCode: 409,
|
|
@@ -2607,17 +2818,27 @@ export async function runPrompt(sessionId, message, selectedCapabilities = [], p
|
|
|
2607
2818
|
})
|
|
2608
2819
|
}
|
|
2609
2820
|
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
// Build user message
|
|
2821
|
+
// Validate references before model refresh, memory refresh, idle timers, title,
|
|
2822
|
+
// message persistence, or agent_start side effects.
|
|
2614
2823
|
const initialUserMessage = typeof message === 'string'
|
|
2615
2824
|
? { role: 'user', content: message, timestamp: new Date().toISOString() }
|
|
2616
2825
|
: message
|
|
2826
|
+
const canonicalContextReferences = await validatePromptContextReferences(contextReferences, session)
|
|
2827
|
+
const canonicalSelectedCapabilities = normalizeSelectedCapabilities(selectedCapabilities)
|
|
2828
|
+
const canonicalInitialUserMessage = withCanonicalSelectedCapabilities(
|
|
2829
|
+
withCanonicalContextReferences(initialUserMessage, canonicalContextReferences),
|
|
2830
|
+
canonicalSelectedCapabilities,
|
|
2831
|
+
)
|
|
2832
|
+
|
|
2833
|
+
if (modelAccessContext) session.modelAccessContext = modelAccessContext
|
|
2834
|
+
if (session.harness === AGENT_HARNESS_QUICKFORGE) await refreshSessionModelBinding(session)
|
|
2835
|
+
if (session.harness === AGENT_HARNESS_QUICKFORGE) await refreshMemoryState(session)
|
|
2836
|
+
resetIdleTimer(session)
|
|
2837
|
+
|
|
2617
2838
|
const commandState = session.harness === AGENT_HARNESS_QUICKFORGE
|
|
2618
|
-
? await resolveCommandState(session,
|
|
2619
|
-
: { userMessage:
|
|
2620
|
-
const resolvedUserMessage = commandState.userMessage ??
|
|
2839
|
+
? await resolveCommandState(session, canonicalInitialUserMessage, promptCommand)
|
|
2840
|
+
: { userMessage: canonicalInitialUserMessage }
|
|
2841
|
+
const resolvedUserMessage = commandState.userMessage ?? canonicalInitialUserMessage
|
|
2621
2842
|
|
|
2622
2843
|
if (session.harness === AGENT_HARNESS_OPENCODE) {
|
|
2623
2844
|
session.agent.validatePrompt(resolvedUserMessage)
|
|
@@ -2626,7 +2847,7 @@ export async function runPrompt(sessionId, message, selectedCapabilities = [], p
|
|
|
2626
2847
|
if (commandState.textResponse) {
|
|
2627
2848
|
session.agent.state.messages = [
|
|
2628
2849
|
...session.agent.state.messages,
|
|
2629
|
-
|
|
2850
|
+
canonicalInitialUserMessage,
|
|
2630
2851
|
assistantTextMessage(commandState.textResponse, session.model),
|
|
2631
2852
|
]
|
|
2632
2853
|
await persistSession(session)
|
|
@@ -2641,11 +2862,11 @@ export async function runPrompt(sessionId, message, selectedCapabilities = [], p
|
|
|
2641
2862
|
}
|
|
2642
2863
|
|
|
2643
2864
|
if (commandState.summary) {
|
|
2644
|
-
return summarySession(session,
|
|
2865
|
+
return summarySession(session, canonicalInitialUserMessage, commandState.summary)
|
|
2645
2866
|
}
|
|
2646
2867
|
|
|
2647
2868
|
if (commandState.compact) {
|
|
2648
|
-
return compactSession(session,
|
|
2869
|
+
return compactSession(session, canonicalInitialUserMessage, commandState.compact)
|
|
2649
2870
|
}
|
|
2650
2871
|
|
|
2651
2872
|
const userMessage = session.harness === AGENT_HARNESS_QUICKFORGE
|
|
@@ -2665,10 +2886,12 @@ export async function runPrompt(sessionId, message, selectedCapabilities = [], p
|
|
|
2665
2886
|
session.activeCommandName = commandState.commandName ?? null
|
|
2666
2887
|
session.activeCommandPermissions = commandState.permissions ?? null
|
|
2667
2888
|
session.activeCommandPrompt = commandState.commandPrompt ?? null
|
|
2668
|
-
session.activeCapabilityPrompt = selectedCapabilityPrompt(
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
: null
|
|
2889
|
+
session.activeCapabilityPrompt = selectedCapabilityPrompt(canonicalSelectedCapabilities)
|
|
2890
|
+
const referencePrompt = contextReferencesPrompt(canonicalContextReferences)
|
|
2891
|
+
session.activeTransientContextPrompt = [
|
|
2892
|
+
typeof transientContextPrompt === 'string' && transientContextPrompt.trim() ? transientContextPrompt : null,
|
|
2893
|
+
referencePrompt,
|
|
2894
|
+
].filter(Boolean).join('\n\n') || null
|
|
2672
2895
|
|
|
2673
2896
|
// Fire and forget — events come through eventBus
|
|
2674
2897
|
session.agent.prompt(userMessage).catch((err) => {
|
|
@@ -2744,8 +2967,14 @@ export async function continueSession(sessionId, modelAccessContext = null) {
|
|
|
2744
2967
|
}
|
|
2745
2968
|
|
|
2746
2969
|
const lastUserMessage = messages[lastUserIndex]
|
|
2747
|
-
const
|
|
2748
|
-
const
|
|
2970
|
+
const canonicalContextReferences = await validateContextReferences(contextReferencesFromMessage(lastUserMessage), session)
|
|
2971
|
+
const canonicalSelectedCapabilities = normalizeSelectedCapabilities(selectedCapabilitiesFromMessage(lastUserMessage))
|
|
2972
|
+
const canonicalLastUserMessage = withCanonicalSelectedCapabilities(
|
|
2973
|
+
withCanonicalContextReferences(lastUserMessage, canonicalContextReferences),
|
|
2974
|
+
canonicalSelectedCapabilities,
|
|
2975
|
+
)
|
|
2976
|
+
const commandState = await resolveCommandState(session, canonicalLastUserMessage)
|
|
2977
|
+
const continuedUserMessage = prepareCloudUserMessage(session, commandState.userMessage ?? canonicalLastUserMessage)
|
|
2749
2978
|
const trimmedMessages = messages.slice(0, lastUserIndex).concat(continuedUserMessage)
|
|
2750
2979
|
updateSessionMessages(session, trimmedMessages)
|
|
2751
2980
|
const compactedUpToIndex = Number(session.contextCompaction?.compactedUpToIndex) || 0
|
|
@@ -2764,7 +2993,8 @@ export async function continueSession(sessionId, modelAccessContext = null) {
|
|
|
2764
2993
|
session.activeCommandName = commandState.commandName ?? null
|
|
2765
2994
|
session.activeCommandPermissions = commandState.permissions ?? null
|
|
2766
2995
|
session.activeCommandPrompt = commandState.commandPrompt ?? null
|
|
2767
|
-
session.activeCapabilityPrompt =
|
|
2996
|
+
session.activeCapabilityPrompt = selectedCapabilityPrompt(canonicalSelectedCapabilities)
|
|
2997
|
+
session.activeTransientContextPrompt = contextReferencesPrompt(canonicalContextReferences)
|
|
2768
2998
|
|
|
2769
2999
|
session.agent.continue().catch((err) => {
|
|
2770
3000
|
logger.error(`Agent continue error for session ${sessionId}:`, err, { sessionId })
|
|
@@ -2774,6 +3004,7 @@ export async function continueSession(sessionId, modelAccessContext = null) {
|
|
|
2774
3004
|
session.activeCommandPermissions = null
|
|
2775
3005
|
session.activeCommandPrompt = null
|
|
2776
3006
|
session.activeCapabilityPrompt = null
|
|
3007
|
+
session.activeTransientContextPrompt = null
|
|
2777
3008
|
})
|
|
2778
3009
|
|
|
2779
3010
|
return { sessionId, status: 'running' }
|
|
@@ -2972,6 +3203,7 @@ export function getSessionState(sessionId) {
|
|
|
2972
3203
|
contextUsage: getSessionContextUsage(session),
|
|
2973
3204
|
pendingToolApproval: getPendingApprovalForSession(session.sessionId),
|
|
2974
3205
|
pendingAutoCompactApproval: getPendingAutoCompactApprovalForSession(session.sessionId),
|
|
3206
|
+
pendingAsk: getPendingAskForSession(session.sessionId),
|
|
2975
3207
|
acpSession: session.harness === AGENT_HARNESS_OPENCODE ? session.agent.state.acpSession : undefined,
|
|
2976
3208
|
isStreaming: session.abortPending ? false : session.agent.state.isStreaming,
|
|
2977
3209
|
errorMessage: session.agent.state.errorMessage,
|
|
@@ -3228,6 +3460,30 @@ export function rejectAutoCompact(sessionId, approvalId) {
|
|
|
3228
3460
|
return { rejected: true, approvalId }
|
|
3229
3461
|
}
|
|
3230
3462
|
|
|
3463
|
+
/**
|
|
3464
|
+
* Resolve a pending ask_user call with the user's answers (or a skip).
|
|
3465
|
+
* `answers` is an array aligned with the ask's questions:
|
|
3466
|
+
* `[{ choices: string[], custom?: string }]`.
|
|
3467
|
+
*/
|
|
3468
|
+
export function answerAsk(sessionId, askId, { answers, skipped = false } = {}) {
|
|
3469
|
+
const ask = pendingAsks.get(askId)
|
|
3470
|
+
if (!ask || ask.sessionId !== sessionId) {
|
|
3471
|
+
throw Object.assign(new Error('No pending ask for this session'), { statusCode: 404 })
|
|
3472
|
+
}
|
|
3473
|
+
const normalizedAnswers = (Array.isArray(answers) ? answers : []).slice(0, ask.questions.length).map((answer) => ({
|
|
3474
|
+
choices: (Array.isArray(answer?.choices) ? answer.choices : [])
|
|
3475
|
+
.filter((choice) => typeof choice === 'string')
|
|
3476
|
+
.map((choice) => choice.slice(0, 500))
|
|
3477
|
+
.slice(0, 8),
|
|
3478
|
+
...(typeof answer?.custom === 'string' && answer.custom.trim()
|
|
3479
|
+
? { custom: answer.custom.slice(0, 4000) }
|
|
3480
|
+
: {}),
|
|
3481
|
+
}))
|
|
3482
|
+
if (skipped) ask.finish({ skipped: true })
|
|
3483
|
+
else ask.finish({ answers: normalizedAnswers })
|
|
3484
|
+
return { answered: true, askId, skipped: !!skipped }
|
|
3485
|
+
}
|
|
3486
|
+
|
|
3231
3487
|
export function abortToolCall(sessionId, toolCallId) {
|
|
3232
3488
|
const session = agentSessions.get(sessionId)
|
|
3233
3489
|
if (!session) {
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ask-user store — shared pending-question state.
|
|
3
|
+
*
|
|
4
|
+
* The Promise-based ask flow (createAskUserPromise) lives in agent-manager.mjs
|
|
5
|
+
* because it depends on the agent event buses; this module only holds the
|
|
6
|
+
* pending queue, mirroring approval-store.mjs.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Waiting for a user decision is not a security gate — allow generous time
|
|
10
|
+
// before resolving as skipped so the agent can continue on its own.
|
|
11
|
+
export const ASK_TIMEOUT_MS = 30 * 60 * 1000 // 30 minutes
|
|
12
|
+
|
|
13
|
+
/** askId → { finish, sessionId, toolCallId, questions, requestedAt, expiresAt } */
|
|
14
|
+
export const pendingAsks = new Map()
|
|
15
|
+
|
|
16
|
+
export function getPendingAskForSession(sessionId) {
|
|
17
|
+
for (const [askId, ask] of pendingAsks) {
|
|
18
|
+
if (ask.sessionId === sessionId) {
|
|
19
|
+
return {
|
|
20
|
+
askId,
|
|
21
|
+
toolCallId: ask.toolCallId,
|
|
22
|
+
questions: ask.questions,
|
|
23
|
+
requestedAt: ask.requestedAt,
|
|
24
|
+
expiresAt: ask.expiresAt,
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return null
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Pure helpers (no dependencies — unit-tested directly)
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
/** Normalize ask_user params into a bounded questions list (robust against model shorthand). */
|
|
36
|
+
export function normalizeAskQuestions(params) {
|
|
37
|
+
const rawQuestions = Array.isArray(params?.questions)
|
|
38
|
+
? params.questions
|
|
39
|
+
: (typeof params?.question === 'string' ? [{ ...params }] : [])
|
|
40
|
+
return rawQuestions
|
|
41
|
+
.filter((q) => q && typeof q.question === 'string' && q.question.trim())
|
|
42
|
+
.slice(0, 4)
|
|
43
|
+
.map((q) => ({
|
|
44
|
+
question: String(q.question),
|
|
45
|
+
multiSelect: q.multiSelect === true,
|
|
46
|
+
allowCustom: q.allowCustom === false ? false : true,
|
|
47
|
+
options: (Array.isArray(q.options) ? q.options : [])
|
|
48
|
+
.filter((option) => option && typeof option.label === 'string' && option.label.trim())
|
|
49
|
+
.slice(0, 4)
|
|
50
|
+
.map((option) => ({
|
|
51
|
+
label: String(option.label),
|
|
52
|
+
...(typeof option.description === 'string' && option.description ? { description: option.description } : {}),
|
|
53
|
+
})),
|
|
54
|
+
}))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Render the tool-result text returned to the model. */
|
|
58
|
+
export function formatAskResult(questions, answers, skipped, reason) {
|
|
59
|
+
if (skipped) {
|
|
60
|
+
const note = reason === 'timeout'
|
|
61
|
+
? '(等待超时)'
|
|
62
|
+
: reason === 'aborted'
|
|
63
|
+
? '(运行被停止)'
|
|
64
|
+
: reason === 'no-questions'
|
|
65
|
+
? '(问题列表无效)'
|
|
66
|
+
: '(用户跳过)'
|
|
67
|
+
return `用户没有回答这些问题${note}。请按你的默认方案继续,不要再重复追问。`
|
|
68
|
+
}
|
|
69
|
+
const lines = questions.map((q, index) => {
|
|
70
|
+
const answer = answers?.[index]
|
|
71
|
+
if (!answer) return `${index + 1}. ${q.question} → 用户未回答`
|
|
72
|
+
const choiceText = Array.isArray(answer.choices) && answer.choices.length ? answer.choices.join('、') : ''
|
|
73
|
+
const customText = typeof answer.custom === 'string' && answer.custom.trim() ? answer.custom.trim() : ''
|
|
74
|
+
const text = [choiceText, customText].filter(Boolean).join(' 补充:')
|
|
75
|
+
return `${index + 1}. ${q.question} → ${text || '用户未回答'}`
|
|
76
|
+
})
|
|
77
|
+
return `用户的回答:\n${lines.join('\n')}`
|
|
78
|
+
}
|