@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
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export const MAX_SELECTED_CAPABILITIES = 4
|
|
2
|
+
export const SELECTED_CAPABILITY_PLUGIN_NAME_MAX_LENGTH = 120
|
|
3
|
+
export const SELECTED_CAPABILITY_NAME_MAX_LENGTH = 120
|
|
4
|
+
export const SELECTED_CAPABILITY_LABEL_MAX_LENGTH = 160
|
|
5
|
+
export const SELECTED_CAPABILITY_DESCRIPTION_MAX_LENGTH = 400
|
|
6
|
+
export const SELECTED_CAPABILITIES_DETAILS_KEY = 'selectedCapabilities'
|
|
7
|
+
|
|
8
|
+
const SELECTED_CAPABILITY_TYPES = new Set(['plugin', 'skill', 'tool', 'command'])
|
|
9
|
+
|
|
10
|
+
function normalizedText(value, maxLength) {
|
|
11
|
+
if (typeof value !== 'string') return undefined
|
|
12
|
+
const normalized = value.trim().slice(0, maxLength)
|
|
13
|
+
return normalized || undefined
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function selectedCapabilityKey(capability) {
|
|
17
|
+
return `${capability.type}:${capability.pluginName}:${capability.name}`
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function normalizeSelectedCapabilities(value) {
|
|
21
|
+
if (!Array.isArray(value)) return []
|
|
22
|
+
const result = []
|
|
23
|
+
const seen = new Set()
|
|
24
|
+
for (const item of value) {
|
|
25
|
+
if (!item || typeof item !== 'object' || Array.isArray(item) || !SELECTED_CAPABILITY_TYPES.has(item.type)) continue
|
|
26
|
+
const pluginName = normalizedText(item.pluginName, SELECTED_CAPABILITY_PLUGIN_NAME_MAX_LENGTH)
|
|
27
|
+
const name = normalizedText(item.name, SELECTED_CAPABILITY_NAME_MAX_LENGTH)
|
|
28
|
+
const label = normalizedText(item.label, SELECTED_CAPABILITY_LABEL_MAX_LENGTH)
|
|
29
|
+
if (!pluginName || !name || !label) continue
|
|
30
|
+
const capability = { type: item.type, pluginName, name, label }
|
|
31
|
+
const description = normalizedText(item.description, SELECTED_CAPABILITY_DESCRIPTION_MAX_LENGTH)
|
|
32
|
+
if (description) capability.description = description
|
|
33
|
+
const key = selectedCapabilityKey(capability)
|
|
34
|
+
if (seen.has(key)) continue
|
|
35
|
+
seen.add(key)
|
|
36
|
+
result.push(capability)
|
|
37
|
+
if (result.length >= MAX_SELECTED_CAPABILITIES) break
|
|
38
|
+
}
|
|
39
|
+
return result
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function selectedCapabilitySnapshots(value) {
|
|
43
|
+
return normalizeSelectedCapabilities(value).map(({ type, pluginName, name, label }) => ({ type, pluginName, name, label }))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function selectedCapabilitiesFromMessage(message) {
|
|
47
|
+
const details = message?.details
|
|
48
|
+
if (!details || typeof details !== 'object' || Array.isArray(details)) return []
|
|
49
|
+
return selectedCapabilitySnapshots(details[SELECTED_CAPABILITIES_DETAILS_KEY])
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function withCanonicalSelectedCapabilities(message, capabilities) {
|
|
53
|
+
if (!message || typeof message !== 'object') return message
|
|
54
|
+
const snapshots = selectedCapabilitySnapshots(capabilities)
|
|
55
|
+
const details = message.details && typeof message.details === 'object' && !Array.isArray(message.details)
|
|
56
|
+
? message.details
|
|
57
|
+
: {}
|
|
58
|
+
const nextDetails = { ...details }
|
|
59
|
+
if (snapshots.length > 0) nextDetails[SELECTED_CAPABILITIES_DETAILS_KEY] = snapshots
|
|
60
|
+
else delete nextDetails[SELECTED_CAPABILITIES_DETAILS_KEY]
|
|
61
|
+
const next = { ...message }
|
|
62
|
+
if (Object.keys(nextDetails).length > 0) next.details = nextDetails
|
|
63
|
+
else delete next.details
|
|
64
|
+
return next
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function selectedCapabilityPrompt(capabilities) {
|
|
68
|
+
const normalized = normalizeSelectedCapabilities(capabilities)
|
|
69
|
+
if (normalized.length === 0) return null
|
|
70
|
+
const lines = normalized.map((capability) => {
|
|
71
|
+
const toolHint = capability.type === 'tool' ? ` Tool name: plugin__${capability.pluginName}__${capability.name}.` : ''
|
|
72
|
+
const description = capability.description ? ` Description: ${capability.description}` : ''
|
|
73
|
+
return `- ${capability.label} (${capability.type}, plugin: ${capability.pluginName}, name: ${capability.name}).${toolHint}${description}`
|
|
74
|
+
}).join('\n')
|
|
75
|
+
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}`
|
|
76
|
+
}
|
|
@@ -98,7 +98,12 @@ function normalizeSessionEntry(bucket, sessionId, rawState, rawMetadata, diagnos
|
|
|
98
98
|
// Resilience: a failing entry (unreadable file, validation mismatch,
|
|
99
99
|
// cross-bucket duplicate) never aborts the run — it is counted in `skipped`
|
|
100
100
|
// and described in `diagnostics` so the operator can inspect and fix the
|
|
101
|
-
// source tree, then simply re-run.
|
|
101
|
+
// source tree, then simply re-run. The same holds one level up: an unreadable
|
|
102
|
+
// or malformed sessions-metadata.json (the physical adapter only swallows
|
|
103
|
+
// ENOENT — corrupt JSON, EACCES/EBUSY rethrow) degrades that bucket to empty
|
|
104
|
+
// metadata instead of failing the import, and with it startup: body files
|
|
105
|
+
// still import through deriveMetadata, only metadata-only entries are lost
|
|
106
|
+
// (they carry no messages and are dropped by design anyway).
|
|
102
107
|
export async function importSessionStateFromJson({ storage = null, logger = defaultLogger } = {}) {
|
|
103
108
|
const storageModule = storage ?? await import('./storage.mjs')
|
|
104
109
|
const fsAdapter = storageModule.createPhysicalSessionStateFsAdapter()
|
|
@@ -109,8 +114,20 @@ export async function importSessionStateFromJson({ storage = null, logger = defa
|
|
|
109
114
|
|
|
110
115
|
for await (const rawBucket of fsAdapter.listBuckets()) {
|
|
111
116
|
const bucket = normalizeBucket(rawBucket)
|
|
112
|
-
|
|
113
|
-
|
|
117
|
+
let metadata = {}
|
|
118
|
+
try {
|
|
119
|
+
const rawMetadata = await fsAdapter.readMetadataBucket(rawBucket)
|
|
120
|
+
if (!isPlainObject(rawMetadata)) throw new TypeError('Session bucket stores must be objects')
|
|
121
|
+
metadata = rawMetadata
|
|
122
|
+
} catch (error) {
|
|
123
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
124
|
+
diagnostics.push({ kind: 'metadata-bucket-error', scope: bucket.scope, projectId: bucket.projectId, message })
|
|
125
|
+
logger.warn('Session metadata bucket unreadable; importing with body-derived metadata', {
|
|
126
|
+
scope: bucket.scope,
|
|
127
|
+
projectId: bucket.projectId,
|
|
128
|
+
message,
|
|
129
|
+
})
|
|
130
|
+
}
|
|
114
131
|
const fileIds = new Set()
|
|
115
132
|
for await (const sessionId of fsAdapter.listSessionFiles(rawBucket)) fileIds.add(sessionId)
|
|
116
133
|
const ids = [...new Set([...fileIds, ...Object.keys(metadata)])].sort((left, right) => left.localeCompare(right))
|
package/server/skills.mjs
CHANGED
|
@@ -392,7 +392,7 @@ function searchDirsForList(value) {
|
|
|
392
392
|
return value.length === 1 ? value[0] : value.slice()
|
|
393
393
|
}
|
|
394
394
|
|
|
395
|
-
function summarizeSkills(skills) {
|
|
395
|
+
export function summarizeSkills(skills) {
|
|
396
396
|
return skills.map(({ instructions: _instructions, rootDir: _rootDir, location: _location, ...summary }) => summary)
|
|
397
397
|
}
|
|
398
398
|
|
package/server/system-prompt.mjs
CHANGED
|
@@ -2,8 +2,7 @@ export const BASE_SYSTEM_PROMPT = `You are a pragmatic coding assistant.
|
|
|
2
2
|
|
|
3
3
|
For project tasks:
|
|
4
4
|
- Do not assume requirements. If ambiguous, state assumptions or ask.
|
|
5
|
-
-
|
|
6
|
-
- Make surgical changes only. Do not refactor unrelated code.
|
|
5
|
+
- Do not refactor unrelated code.
|
|
7
6
|
- Match existing style.
|
|
8
7
|
- When content has room for visual explanation, first consider whether an SVG diagram can improve understanding.
|
|
9
8
|
- For multi-step work, use a brief plan.
|
|
@@ -11,7 +10,6 @@ For project tasks:
|
|
|
11
10
|
- Before taking action, confirm with the user.
|
|
12
11
|
- Unless the change is trivial and localized to an already-known file, use Explore first for read-only repository research before implementation decisions; prefer Explore for file discovery, source location, broad searches, call-chain lookup, pattern lookup, impact analysis, and locating related tests, docs, wiki pages, or build scripts.
|
|
13
12
|
- For complex multi-step work, use General only for bounded assistance; the parent assistant remains responsible for final decisions, minimal edits, and verification.
|
|
14
|
-
- Make minimal, focused changes.
|
|
15
13
|
- Prefer dedicated workspace tools for reading, editing, and searching files.
|
|
16
14
|
- If dedicated tools are unavailable or insufficient, use the shell/command tool.
|
|
17
15
|
- Use Python through the shell for reliable scripting, data processing, or file transformations.
|
|
@@ -54,8 +54,26 @@ export const globalMemoryTool = {
|
|
|
54
54
|
executionMode: 'sequential',
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
export const askUserTool = {
|
|
58
|
+
name: 'ask_user',
|
|
59
|
+
label: 'Ask user',
|
|
60
|
+
description: 'Ask the user one or more questions (up to 4) and wait for their answers before continuing. Use this when you need the user to choose between approaches, confirm a direction, or supply information you cannot infer. Questions should be self-contained; each question offers 2-4 options and the user may answer with free-form text instead. All questions are answered together, then the run resumes.',
|
|
61
|
+
parameters: Type.Object({
|
|
62
|
+
questions: Type.Array(Type.Object({
|
|
63
|
+
question: Type.String({ description: 'Complete, self-contained question text.' }),
|
|
64
|
+
options: Type.Optional(Type.Array(Type.Object({
|
|
65
|
+
label: Type.String({ description: 'Concise option label.' }),
|
|
66
|
+
description: Type.Optional(Type.String({ description: 'Optional one-line explanation of the trade-off.' })),
|
|
67
|
+
}), { maxItems: 4, description: '2-4 mutually exclusive options.' })),
|
|
68
|
+
multiSelect: Type.Optional(Type.Boolean({ description: 'Allow selecting multiple options.', default: false })),
|
|
69
|
+
allowCustom: Type.Optional(Type.Boolean({ description: 'Allow a free-form answer instead of the options.', default: true })),
|
|
70
|
+
}), { minItems: 1, maxItems: 4, description: 'Questions asked together in one card; the user answers all of them in one pass.' }),
|
|
71
|
+
}),
|
|
72
|
+
}
|
|
73
|
+
|
|
57
74
|
export const workspaceTools = [
|
|
58
75
|
subagentTool,
|
|
76
|
+
askUserTool,
|
|
59
77
|
{
|
|
60
78
|
name: 'read_file',
|
|
61
79
|
label: 'Read file',
|
|
@@ -116,16 +134,6 @@ export const workspaceTools = [
|
|
|
116
134
|
}),
|
|
117
135
|
executionMode: 'sequential',
|
|
118
136
|
},
|
|
119
|
-
{
|
|
120
|
-
name: 'generate_image',
|
|
121
|
-
label: 'Generate image',
|
|
122
|
-
description: 'Generate images with the configured OpenRouter provider and save them as assets owned by the current conversation. Use this when the user explicitly asks for a generated bitmap image. The operation may incur provider charges.',
|
|
123
|
-
parameters: Type.Object({
|
|
124
|
-
prompt: Type.String({ description: 'Detailed image-generation prompt.' }),
|
|
125
|
-
model: Type.Optional(Type.String({ description: 'Optional OpenRouter image model ID. Defaults to google/gemini-2.5-flash-image.' })),
|
|
126
|
-
}),
|
|
127
|
-
executionMode: 'sequential',
|
|
128
|
-
},
|
|
129
137
|
{
|
|
130
138
|
name: 'present_files',
|
|
131
139
|
label: 'Present files',
|
package/server/tools/index.mjs
CHANGED
|
@@ -486,7 +486,7 @@ export async function toolGrepFiles(params, context, runtime = {}) {
|
|
|
486
486
|
}
|
|
487
487
|
|
|
488
488
|
// --- write_file ---
|
|
489
|
-
export async function toolWriteFile(params, context) {
|
|
489
|
+
export async function toolWriteFile(params, context, runtime = {}) {
|
|
490
490
|
const file = resolveWorkspacePath(params?.path, context)
|
|
491
491
|
await assertSafeWorkspacePath(file, context, { forWrite: true })
|
|
492
492
|
|
|
@@ -502,6 +502,12 @@ export async function toolWriteFile(params, context) {
|
|
|
502
502
|
}
|
|
503
503
|
const diff = createTextDiff(oldText, content, relativePath, { oldExists: existed })
|
|
504
504
|
|
|
505
|
+
// 写盘前先发一次 partial update,让前端在 running 阶段就能滚动显示 ±行数。
|
|
506
|
+
runtime.onUpdate?.({
|
|
507
|
+
content: [{ type: 'text', text: `${existed ? 'Writing' : 'Creating'} ${relativePath} (+${diff.addedLines} -${diff.removedLines})` }],
|
|
508
|
+
details: { running: true, path: relativePath, project: context?.project, diff: { addedLines: diff.addedLines, removedLines: diff.removedLines } },
|
|
509
|
+
})
|
|
510
|
+
|
|
505
511
|
await fs.mkdir(path.dirname(file), { recursive: true })
|
|
506
512
|
await fs.writeFile(file, content, 'utf8')
|
|
507
513
|
|
|
@@ -536,7 +542,7 @@ function convertToLineEnding(text, ending) {
|
|
|
536
542
|
return ending === '\r\n' ? normalized.replaceAll('\n', '\r\n') : normalized
|
|
537
543
|
}
|
|
538
544
|
|
|
539
|
-
export async function toolEditFile(params, context) {
|
|
545
|
+
export async function toolEditFile(params, context, runtime = {}) {
|
|
540
546
|
const file = resolveWorkspacePath(params?.path, context)
|
|
541
547
|
await assertSafeWorkspacePath(file, context)
|
|
542
548
|
|
|
@@ -560,6 +566,12 @@ export async function toolEditFile(params, context) {
|
|
|
560
566
|
const relativePath = toWorkspaceRelative(file, context)
|
|
561
567
|
const diff = createTextDiff(text, nextText, relativePath)
|
|
562
568
|
|
|
569
|
+
// 写盘前先发一次 partial update,让前端在 running 阶段就能滚动显示 ±行数。
|
|
570
|
+
runtime.onUpdate?.({
|
|
571
|
+
content: [{ type: 'text', text: `Editing ${relativePath} (+${diff.addedLines} -${diff.removedLines})` }],
|
|
572
|
+
details: { running: true, path: relativePath, project: context?.project, diff: { addedLines: diff.addedLines, removedLines: diff.removedLines } },
|
|
573
|
+
})
|
|
574
|
+
|
|
563
575
|
await fs.writeFile(file, nextText, 'utf8')
|
|
564
576
|
|
|
565
577
|
return {
|
|
@@ -41,7 +41,7 @@ export function toWorkspaceRelative(fullPath, context) {
|
|
|
41
41
|
|
|
42
42
|
export function isSensitiveWorkspacePath(fullPath, context) {
|
|
43
43
|
const relative = toWorkspaceRelative(fullPath, context)
|
|
44
|
-
const parts = relative.split('/')
|
|
44
|
+
const parts = relative.split('/').map((part) => part.toLocaleLowerCase())
|
|
45
45
|
const name = parts.at(-1) || ''
|
|
46
46
|
return (
|
|
47
47
|
parts.includes('.git') ||
|
|
@@ -61,6 +61,13 @@ export function isSensitiveWorkspacePath(fullPath, context) {
|
|
|
61
61
|
)
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
function sensitivePathError(fullPath, context) {
|
|
65
|
+
return Object.assign(new Error(`Access to sensitive path is blocked: ${toWorkspaceRelative(fullPath, context)}`), {
|
|
66
|
+
statusCode: 403,
|
|
67
|
+
errorCode: 'WORKSPACE_SENSITIVE_PATH',
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
64
71
|
async function realpathNearestExistingParent(inputPath) {
|
|
65
72
|
let current = path.resolve(inputPath)
|
|
66
73
|
while (true) {
|
|
@@ -77,9 +84,7 @@ async function realpathNearestExistingParent(inputPath) {
|
|
|
77
84
|
|
|
78
85
|
async function assertSafeWorkspacePathWithRoot(fullPath, context, workspaceReal, options = {}) {
|
|
79
86
|
if (!options.allowSensitive && isSensitiveWorkspacePath(fullPath, context)) {
|
|
80
|
-
|
|
81
|
-
error.statusCode = 403
|
|
82
|
-
throw error
|
|
87
|
+
throw sensitivePathError(fullPath, context)
|
|
83
88
|
}
|
|
84
89
|
|
|
85
90
|
let targetReal
|
|
@@ -98,8 +103,12 @@ async function assertSafeWorkspacePathWithRoot(fullPath, context, workspaceReal,
|
|
|
98
103
|
if (!isInside(workspaceReal, targetReal)) {
|
|
99
104
|
const error = new Error(`Path resolves outside the selected project: ${toWorkspaceRelative(fullPath, context)}`)
|
|
100
105
|
error.statusCode = 403
|
|
106
|
+
error.errorCode = 'WORKSPACE_PATH_ESCAPE'
|
|
101
107
|
throw error
|
|
102
108
|
}
|
|
109
|
+
if (!options.allowSensitive && isSensitiveWorkspacePath(targetReal, { workspaceRoot: workspaceReal })) {
|
|
110
|
+
throw sensitivePathError(fullPath, context)
|
|
111
|
+
}
|
|
103
112
|
}
|
|
104
113
|
|
|
105
114
|
export async function createWorkspacePathValidator(context) {
|