@shawnstack/quickforge 1.3.18 → 1.3.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/README.md +10 -10
  2. package/bin/quickforge.mjs +258 -49
  3. package/dist/assets/anthropic-Bj3HAZgj.js +39 -0
  4. package/dist/assets/azure-openai-responses-IdZZrSrI.js +1 -0
  5. package/dist/assets/github-copilot-headers-CMb2BbzT.js +1 -0
  6. package/dist/assets/google-Brt_lS1J.js +1 -0
  7. package/dist/assets/{google-shared-XhYUKiGZ.js → google-shared-CLc4ziON.js} +3 -3
  8. package/dist/assets/google-vertex-B6HsoZ34.js +1 -0
  9. package/dist/assets/{index-Dm7aEWvT.js → index-D0CVLdX_.js} +525 -489
  10. package/dist/assets/index-D0W9hAl_.css +3 -0
  11. package/dist/assets/{mistral-DxhS4Wkn.js → mistral-CenXqwPz.js} +3 -3
  12. package/dist/assets/openai-codex-responses-D9ffGwbj.js +7 -0
  13. package/dist/assets/openai-completions-eWdeSGBG.js +5 -0
  14. package/dist/assets/openai-responses-Cavpmjeu.js +1 -0
  15. package/dist/assets/{openai-responses-shared-f_P3e1nz.js → openai-responses-shared-DF3ZGaUx.js} +5 -3
  16. package/dist/assets/transform-messages-CmnxG9RB.js +1 -0
  17. package/dist/index.html +2 -2
  18. package/node_modules/@anthropic-ai/sdk/CHANGELOG.md +34 -0
  19. package/node_modules/@anthropic-ai/sdk/bin/migration-config.json +185 -0
  20. package/node_modules/@anthropic-ai/sdk/package.json +1 -1
  21. package/node_modules/@anthropic-ai/sdk/resources/beta/beta.js +4 -0
  22. package/node_modules/@anthropic-ai/sdk/resources/beta/beta.mjs +4 -0
  23. package/node_modules/@anthropic-ai/sdk/resources/beta/files.js +5 -5
  24. package/node_modules/@anthropic-ai/sdk/resources/beta/files.mjs +5 -5
  25. package/node_modules/@anthropic-ai/sdk/resources/beta/index.js +11 -9
  26. package/node_modules/@anthropic-ai/sdk/resources/beta/index.mjs +1 -0
  27. package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/index.js +11 -0
  28. package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/index.mjs +5 -0
  29. package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memories.js +130 -0
  30. package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memories.mjs +126 -0
  31. package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memory-stores.js +145 -0
  32. package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memory-stores.mjs +140 -0
  33. package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memory-versions.js +81 -0
  34. package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores/memory-versions.mjs +77 -0
  35. package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores.js +6 -0
  36. package/node_modules/@anthropic-ai/sdk/resources/beta/memory-stores.mjs +3 -0
  37. package/node_modules/@anthropic-ai/sdk/tools/memory/node.js +12 -5
  38. package/node_modules/@anthropic-ai/sdk/tools/memory/node.mjs +12 -5
  39. package/node_modules/@anthropic-ai/sdk/version.js +1 -1
  40. package/node_modules/@anthropic-ai/sdk/version.mjs +1 -1
  41. package/node_modules/@aws-sdk/client-bedrock-runtime/package.json +5 -5
  42. package/node_modules/@aws-sdk/core/package.json +2 -2
  43. package/node_modules/@aws-sdk/credential-provider-env/package.json +2 -2
  44. package/node_modules/@aws-sdk/credential-provider-http/dist-cjs/fromHttp/fromHttp.js +12 -6
  45. package/node_modules/@aws-sdk/credential-provider-http/dist-es/fromHttp/fromHttp.js +12 -6
  46. package/node_modules/@aws-sdk/credential-provider-http/package.json +3 -2
  47. package/node_modules/@aws-sdk/credential-provider-ini/package.json +9 -9
  48. package/node_modules/@aws-sdk/credential-provider-login/package.json +3 -3
  49. package/node_modules/@aws-sdk/credential-provider-node/package.json +7 -7
  50. package/node_modules/@aws-sdk/credential-provider-process/package.json +2 -2
  51. package/node_modules/@aws-sdk/credential-provider-sso/package.json +4 -4
  52. package/node_modules/@aws-sdk/credential-provider-web-identity/package.json +3 -3
  53. package/node_modules/@aws-sdk/middleware-websocket/package.json +2 -2
  54. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/cognito-identity/index.js +1 -1
  55. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/signin/index.js +1 -1
  56. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sso/index.js +1 -1
  57. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sso-oidc/index.js +1 -1
  58. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sts/index.js +1 -1
  59. package/node_modules/@aws-sdk/nested-clients/package.json +3 -3
  60. package/node_modules/@aws-sdk/signature-v4-multi-region/package.json +1 -2
  61. package/node_modules/@aws-sdk/token-providers/package.json +3 -3
  62. package/node_modules/@aws-sdk/xml-builder/package.json +2 -2
  63. package/node_modules/@mariozechner/pi-agent-core/README.md +14 -0
  64. package/node_modules/@mariozechner/pi-agent-core/dist/agent-loop.js +9 -0
  65. package/node_modules/@mariozechner/pi-agent-core/dist/agent.js +1 -1
  66. package/node_modules/@mariozechner/pi-agent-core/package.json +2 -2
  67. package/node_modules/@mariozechner/pi-ai/README.md +20 -31
  68. package/node_modules/@mariozechner/pi-ai/dist/env-api-keys.js +7 -0
  69. package/node_modules/@mariozechner/pi-ai/dist/index.js +2 -0
  70. package/node_modules/@mariozechner/pi-ai/dist/models.generated.js +2420 -1213
  71. package/node_modules/@mariozechner/pi-ai/dist/models.js +28 -20
  72. package/node_modules/@mariozechner/pi-ai/dist/providers/amazon-bedrock.js +11 -11
  73. package/node_modules/@mariozechner/pi-ai/dist/providers/anthropic.js +43 -26
  74. package/node_modules/@mariozechner/pi-ai/dist/providers/azure-openai-responses.js +12 -6
  75. package/node_modules/@mariozechner/pi-ai/dist/providers/cloudflare.js +10 -3
  76. package/node_modules/@mariozechner/pi-ai/dist/providers/google-shared.js +4 -13
  77. package/node_modules/@mariozechner/pi-ai/dist/providers/google-vertex.js +4 -3
  78. package/node_modules/@mariozechner/pi-ai/dist/providers/google.js +4 -3
  79. package/node_modules/@mariozechner/pi-ai/dist/providers/mistral.js +8 -7
  80. package/node_modules/@mariozechner/pi-ai/dist/providers/openai-codex-responses.js +296 -41
  81. package/node_modules/@mariozechner/pi-ai/dist/providers/openai-completions.js +169 -153
  82. package/node_modules/@mariozechner/pi-ai/dist/providers/openai-responses-shared.js +14 -1
  83. package/node_modules/@mariozechner/pi-ai/dist/providers/openai-responses.js +22 -8
  84. package/node_modules/@mariozechner/pi-ai/dist/providers/register-builtins.js +0 -18
  85. package/node_modules/@mariozechner/pi-ai/dist/providers/simple-options.js +1 -0
  86. package/node_modules/@mariozechner/pi-ai/dist/session-resources.js +22 -0
  87. package/node_modules/@mariozechner/pi-ai/dist/utils/diagnostics.js +25 -0
  88. package/node_modules/@mariozechner/pi-ai/dist/utils/oauth/index.js +0 -10
  89. package/node_modules/@mariozechner/pi-ai/dist/utils/oauth/openai-codex.js +25 -14
  90. package/node_modules/@mariozechner/pi-ai/dist/utils/overflow.js +14 -0
  91. package/node_modules/@mariozechner/pi-ai/package.json +2 -6
  92. package/package.json +3 -3
  93. package/server/agent-manager.mjs +279 -12
  94. package/server/auto-compaction.mjs +1 -2
  95. package/server/conversation-compaction.mjs +0 -5
  96. package/server/index.mjs +1 -0
  97. package/server/routes/static.mjs +1 -0
  98. package/server/routes/tools.mjs +3 -1
  99. package/server/session-utils.mjs +6 -1
  100. package/server/share-store.mjs +27 -4
  101. package/server/subagents.mjs +101 -0
  102. package/server/system-prompt.mjs +30 -1
  103. package/server/tools/definitions.mjs +18 -0
  104. package/server/tools/index.mjs +1013 -911
  105. package/dist/assets/anthropic-Ck2DxOfr.js +0 -39
  106. package/dist/assets/azure-openai-responses-DIoz5q4Z.js +0 -1
  107. package/dist/assets/github-copilot-headers-CrI0CIJ7.js +0 -1
  108. package/dist/assets/google-Dau-4ve_.js +0 -1
  109. package/dist/assets/google-gemini-cli-DttMmbGb.js +0 -2
  110. package/dist/assets/google-vertex-BeukMl44.js +0 -1
  111. package/dist/assets/index-DgJVElbv.css +0 -3
  112. package/dist/assets/openai-codex-responses-X3sTzNAa.js +0 -7
  113. package/dist/assets/openai-completions-CRB9Vm0w.js +0 -5
  114. package/dist/assets/openai-responses-DXluu3oi.js +0 -1
  115. package/dist/assets/transform-messages-CV4kCtBB.js +0 -1
  116. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/LICENSE +0 -201
  117. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/README.md +0 -62
  118. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-cjs/index.js +0 -156
  119. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/constants.js +0 -2
  120. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/fromEnvSigningName.js +0 -16
  121. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/fromSso.js +0 -80
  122. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/fromStatic.js +0 -8
  123. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/getNewSsoOidcToken.js +0 -11
  124. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/getSsoOidcClient.js +0 -10
  125. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/index.js +0 -4
  126. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/nodeProvider.js +0 -5
  127. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/validateTokenExpiry.js +0 -7
  128. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/validateTokenKey.js +0 -7
  129. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/dist-es/writeSSOTokenToFile.js +0 -8
  130. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/package.json +0 -69
  131. package/node_modules/@mariozechner/pi-ai/dist/providers/google-gemini-cli.js +0 -779
  132. package/node_modules/@mariozechner/pi-ai/dist/utils/oauth/google-antigravity.js +0 -377
  133. package/node_modules/@mariozechner/pi-ai/dist/utils/oauth/google-gemini-cli.js +0 -482
@@ -70,7 +70,6 @@ function estimateMessageTokens(message) {
70
70
  const parts = [message.role || '', contentToText(message.content)]
71
71
  if (message.toolName) parts.push(message.toolName)
72
72
  if (message.toolCallId) parts.push(message.toolCallId)
73
- if (message.details !== undefined) parts.push(safeJson(message.details))
74
73
  if (message.attachments !== undefined) parts.push(safeJson(message.attachments))
75
74
  return estimateTextTokens(parts.join('\n'))
76
75
  }
@@ -82,7 +81,7 @@ function estimateMessagesTokens(messages) {
82
81
  function estimateMessagesChars(messages) {
83
82
  return (Array.isArray(messages) ? messages : []).reduce((total, message) => {
84
83
  if (!message || typeof message !== 'object') return total
85
- return total + [message.role || '', contentToText(message.content), safeJson(message.details), safeJson(message.attachments)].join('\n').length
84
+ return total + [message.role || '', contentToText(message.content), safeJson(message.attachments)].join('\n').length
86
85
  }, 0)
87
86
  }
88
87
 
@@ -134,10 +134,6 @@ function formatMessageForTranscript(message, index) {
134
134
  const attachments = attachmentsSummary(message)
135
135
  if (attachments) bodyParts.push(attachments)
136
136
 
137
- if (role === 'toolResult' && message?.details !== undefined) {
138
- bodyParts.push(`[tool result details]\n${safeJson(message.details, 4000)}`)
139
- }
140
-
141
137
  const body = bodyParts.join('\n\n').trim() || '[empty]'
142
138
  return redactSensitive(`### Message ${index + 1}: ${role}\n${body}`)
143
139
  }
@@ -158,7 +154,6 @@ function approximateMessageChars(message) {
158
154
  if (message?.attachments) total += safeJson(message.attachments, 1000).length
159
155
  if (message?.toolName) total += String(message.toolName).length
160
156
  if (message?.toolCallId) total += String(message.toolCallId).length
161
- if (message?.details) total += safeJson(message.details, 1000).length
162
157
  return total
163
158
  }
164
159
 
package/server/index.mjs CHANGED
@@ -376,6 +376,7 @@ function isStaticAssetPath(pathname) {
376
376
  return pathname.startsWith('/assets/')
377
377
  || pathname === '/favicon.svg'
378
378
  || pathname === '/vite.svg'
379
+ || pathname === '/manifest.webmanifest'
379
380
  }
380
381
 
381
382
  function isSharePath(pathname) {
@@ -13,6 +13,7 @@ function getContentType(filePath) {
13
13
  '.mjs': 'text/javascript; charset=utf-8',
14
14
  '.css': 'text/css; charset=utf-8',
15
15
  '.json': 'application/json; charset=utf-8',
16
+ '.webmanifest': 'application/manifest+json; charset=utf-8',
16
17
  '.svg': 'image/svg+xml',
17
18
  '.png': 'image/png',
18
19
  '.jpg': 'image/jpeg',
@@ -4,6 +4,8 @@ import { toolHandlers, loadSkillToolContext } from '../tools/index.mjs'
4
4
  import { createSkillTools, workspaceTools } from '../tools/definitions.mjs'
5
5
  import { projectContextFromId, readProjectConfig } from '../project-config.mjs'
6
6
 
7
+ const directRouteDisabledTools = new Set(['run_subagent'])
8
+
7
9
  /**
8
10
  * GET /api/tools — returns canonical tool definitions (no project context needed).
9
11
  */
@@ -68,7 +70,7 @@ export async function handleToolApi(req, res, url) {
68
70
  }
69
71
 
70
72
  const handler = toolHandlers[name]
71
- if (!handler) {
73
+ if (!handler || directRouteDisabledTools.has(name)) {
72
74
  const error = new Error(`Unknown tool: ${name}`)
73
75
  error.statusCode = 404
74
76
  throw error
@@ -1,13 +1,18 @@
1
1
  import { streamSimple } from '@mariozechner/pi-ai'
2
2
  import { buildInstructionsPayload } from './project-config.mjs'
3
3
  import { composeSystemPrompt } from './system-prompt.mjs'
4
+ import { listSubagentSummaries } from './subagents.mjs'
4
5
 
5
6
  // ---------------------------------------------------------------------------
6
7
  // System prompt
7
8
  // ---------------------------------------------------------------------------
8
9
 
9
10
  export async function buildSystemPrompt(projectId) {
10
- return composeSystemPrompt(await buildInstructionsPayload(projectId))
11
+ const instructions = await buildInstructionsPayload(projectId)
12
+ return composeSystemPrompt({
13
+ ...instructions,
14
+ subagents: listSubagentSummaries(),
15
+ })
11
16
  }
12
17
 
13
18
  // ---------------------------------------------------------------------------
@@ -370,6 +370,29 @@ export async function issueConversationShareToken(shareId) {
370
370
  })
371
371
  }
372
372
 
373
+ function messageTimestampMs(message) {
374
+ const timestamp = message?.timestamp
375
+ if (typeof timestamp === 'number' && Number.isFinite(timestamp)) return timestamp
376
+ if (typeof timestamp === 'string') {
377
+ const trimmed = timestamp.trim()
378
+ if (!trimmed) return undefined
379
+ const numeric = Number(trimmed)
380
+ if (Number.isFinite(numeric)) return numeric
381
+ const parsed = Date.parse(trimmed)
382
+ return Number.isNaN(parsed) ? undefined : parsed
383
+ }
384
+ return undefined
385
+ }
386
+
387
+ function lastModifiedFromMessages(messages, fallback) {
388
+ for (let index = messages.length - 1; index >= 0; index--) {
389
+ const timestamp = messageTimestampMs(messages[index])
390
+ if (timestamp !== undefined) return new Date(timestamp).toISOString()
391
+ }
392
+ const fallbackMs = Date.parse(fallback)
393
+ return Number.isNaN(fallbackMs) ? new Date().toISOString() : new Date(fallbackMs).toISOString()
394
+ }
395
+
373
396
  export async function rollbackSharedSessionMessages(record, rollbackMessageIndex) {
374
397
  const { readSessionValue, writeSessionValue, atomicUpdate } = await import('./storage.mjs')
375
398
  const { rollbackSessionMessages, rollbackStartIndexFromMessage } = await import('./agent-manager.mjs')
@@ -394,11 +417,11 @@ export async function rollbackSharedSessionMessages(record, rollbackMessageIndex
394
417
  }
395
418
 
396
419
  const nextMessages = messages.slice(0, rollbackIndex)
397
- const now = new Date().toISOString()
420
+ const lastModified = lastModifiedFromMessages(nextMessages, session.createdAt || session.lastModified)
398
421
  await writeSessionValue(record.sessionId, {
399
422
  ...session,
400
423
  messages: nextMessages,
401
- lastModified: now,
424
+ lastModified,
402
425
  })
403
426
  await atomicUpdate('sessions-metadata', (metadata) => {
404
427
  const existing = metadata[record.sessionId]
@@ -406,13 +429,13 @@ export async function rollbackSharedSessionMessages(record, rollbackMessageIndex
406
429
  metadata[record.sessionId] = {
407
430
  ...existing,
408
431
  messageCount: nextMessages.length,
409
- lastModified: now,
432
+ lastModified,
410
433
  preview: previewFromMessages(nextMessages),
411
434
  }
412
435
  }
413
436
  return metadata
414
437
  })
415
- return { session: { ...session, messages: nextMessages, lastModified: now }, rollbackIndex }
438
+ return { session: { ...session, messages: nextMessages, lastModified }, rollbackIndex }
416
439
  }
417
440
 
418
441
  function textFromContent(content) {
@@ -0,0 +1,101 @@
1
+ const commonSubagentRules = `
2
+ You are a focused QuickForge subagent invoked by a parent coding assistant.
3
+
4
+ Rules:
5
+ - Work only on the delegated task. Do not broaden scope.
6
+ - Do not ask the user questions directly. If required information is missing, report it under "Needs clarification".
7
+ - Prefer evidence from read_file and grep_files before making claims.
8
+ - Treat your findings as advisory; the parent assistant makes final decisions.
9
+ - Do not attempt to call or simulate other subagents.
10
+ - Keep the response concise and structured.
11
+
12
+ Return this structure when practical:
13
+ 1. Summary
14
+ 2. Work performed or findings with evidence, including file paths when relevant
15
+ 3. Risks or unknowns
16
+ 4. Suggested next steps
17
+ `.trim()
18
+
19
+ export const subagentDefinitions = [
20
+ {
21
+ name: 'general',
22
+ label: 'General',
23
+ mode: 'subagent',
24
+ description: 'A general-purpose agent for researching complex problems and executing multi-step tasks. It has full built-in workspace tool access, excluding MCP tools and Agent Skills, so it can modify files when needed. Use it for substantial independent work units, including parallelizable work.',
25
+ allowedTools: ['read_file', 'grep_files', 'write_file', 'edit_file', 'run_command'],
26
+ allowFileMutations: true,
27
+ maxRuntimeMs: 30 * 60 * 1000,
28
+ maxToolCalls: 30,
29
+ systemPrompt: `You are General, a general-purpose subagent for complex research and multi-step implementation tasks. You may inspect, edit, write files, and run commands using the built-in workspace tools when needed. You do not have MCP tools or Agent Skills. Make focused, minimal changes that satisfy the delegated task, and verify your changes when appropriate.`,
30
+ },
31
+ {
32
+ name: 'explore',
33
+ label: 'Explore',
34
+ mode: 'subagent',
35
+ description: 'A fast read-only agent for targeted exploration and focused questions. It cannot modify files. Use it to quickly find relevant files, search keywords, identify patterns, or summarize findings.',
36
+ allowedTools: ['read_file', 'grep_files'],
37
+ allowFileMutations: false,
38
+ maxRuntimeMs: 30 * 60 * 1000,
39
+ maxToolCalls: 16,
40
+ systemPrompt: `You are Explore, a fast read-only exploration subagent. Use read_file and grep_files to locate relevant files, search keywords, identify patterns, and answer focused questions. You cannot modify files or run commands.`,
41
+ },
42
+ ]
43
+
44
+ const subagentByName = new Map(subagentDefinitions.map((definition) => [definition.name, definition]))
45
+
46
+ export function listSubagentSummaries() {
47
+ return subagentDefinitions.map(({ name, label, mode, description, allowedTools }) => ({
48
+ name,
49
+ label,
50
+ mode,
51
+ description,
52
+ allowedTools: [...allowedTools],
53
+ }))
54
+ }
55
+
56
+ export function getSubagentDefinition(name) {
57
+ return subagentByName.get(String(name || '').trim().toLowerCase()) || null
58
+ }
59
+
60
+ export function composeSubagentSystemPrompt({ definition, parentSystemPrompt, projectContext }) {
61
+ const workspaceLines = []
62
+ if (projectContext?.project?.name) workspaceLines.push(`- Project name: ${projectContext.project.name}`)
63
+ if (projectContext?.workspaceRoot) workspaceLines.push(`- Workspace root: ${projectContext.workspaceRoot}`)
64
+ if (projectContext?.project?.id) workspaceLines.push(`- Project ID: ${projectContext.project.id}`)
65
+
66
+ return [
67
+ parentSystemPrompt || '',
68
+ '<subagent_instructions>',
69
+ `Subagent: ${definition.label || definition.name}`,
70
+ `Mode: ${definition.mode || 'subagent'}`,
71
+ `Description: ${definition.description}`,
72
+ '',
73
+ definition.systemPrompt,
74
+ '',
75
+ commonSubagentRules,
76
+ '',
77
+ 'Tool constraints:',
78
+ `- Allowed tools: ${definition.allowedTools.join(', ')}`,
79
+ '- MCP tools and Agent Skill tools are not available to subagents.',
80
+ '- run_subagent is not available to subagents.',
81
+ definition.allowFileMutations
82
+ ? '- File modification tools are available when needed, subject to the parent session approval/YOLO policy.'
83
+ : '- This subagent is read-only. Do not modify files or run commands.',
84
+ workspaceLines.length ? `\nWorkspace context:\n${workspaceLines.join('\n')}` : '',
85
+ '</subagent_instructions>',
86
+ ].filter(Boolean).join('\n')
87
+ }
88
+
89
+ export function formatSubagentTask(params) {
90
+ const task = String(params?.task || '').trim()
91
+ const context = String(params?.context || '').trim()
92
+ const expectedOutput = String(params?.expectedOutput || '').trim()
93
+
94
+ return [
95
+ '<delegated_task>',
96
+ task,
97
+ '</delegated_task>',
98
+ context ? `\n<context>\n${context}\n</context>` : '',
99
+ expectedOutput ? `\n<expected_output>\n${expectedOutput}\n</expected_output>` : '',
100
+ ].filter(Boolean).join('\n')
101
+ }
@@ -6,7 +6,10 @@ For project tasks:
6
6
  - Make surgical changes only. Do not refactor unrelated code.
7
7
  - Match existing style.
8
8
  - For multi-step work, use a brief plan.
9
- - Inspect the workspace before changing files.
9
+ - Before changing files, gather sufficient context: relevant files, entry points or call chains, existing patterns, tests or validation commands, and docs/wiki impact.
10
+ - Before taking action, confirm with the user.
11
+ - Unless the change is trivial and localized to an already-known file, use Explore first for read-only repository research; prefer Explore for broad searches, pattern lookup, impact analysis, and locating related tests, docs, or build scripts.
12
+ - For complex multi-step work, use General only for bounded assistance; the parent assistant remains responsible for final decisions, minimal edits, and verification.
10
13
  - Make minimal, focused changes.
11
14
  - Prefer dedicated workspace tools for reading, editing, and searching files.
12
15
  - If dedicated tools are unavailable or insufficient, use the shell/command tool.
@@ -36,6 +39,31 @@ function formatSkillCatalogItem(skill) {
36
39
  return ` <skill>\n${details.join('\n')}\n </skill>`
37
40
  }
38
41
 
42
+ function formatSubagentCatalogItem(subagent) {
43
+ const details = [
44
+ ` <name>${escapeXml(subagent.name)}</name>`,
45
+ ` <description>${escapeXml(subagent.description)}</description>`,
46
+ ]
47
+ if (Array.isArray(subagent.allowedTools)) details.push(` <allowed_tools>${escapeXml(subagent.allowedTools.join(', '))}</allowed_tools>`)
48
+ return ` <subagent>\n${details.join('\n')}\n </subagent>`
49
+ }
50
+
51
+ function appendSubagentCatalog(parts, subagents) {
52
+ if (!Array.isArray(subagents) || subagents.length === 0) return
53
+
54
+ const subagentParts = subagents.map(formatSubagentCatalogItem)
55
+ parts.push(`
56
+ <available_subagents>
57
+ The run_subagent tool can delegate work to one of QuickForge's built-in temporary subagents.
58
+
59
+ Use Explore for fast read-only codebase search, pattern lookup, and repository questions. Use General for complex research or multi-step implementation work that may need built-in workspace tools, including file edits. Keep delegation concrete and include relevant context. Treat subagent output as advisory; you remain responsible for the final answer.
60
+
61
+ Subagents are short-lived, cannot call other subagents, and do not receive MCP tools or Agent Skill tools. General may modify files subject to the parent session approval/YOLO policy; Explore is read-only.
62
+
63
+ ${subagentParts.join('\n')}
64
+ </available_subagents>`)
65
+ }
66
+
39
67
  function appendSkillsCatalog(parts, skills) {
40
68
  if (!Array.isArray(skills) || skills.length === 0) return
41
69
 
@@ -86,6 +114,7 @@ ${lines.join('\n')}
86
114
  ]
87
115
 
88
116
  appendSkillsCatalog(parts, skills)
117
+ appendSubagentCatalog(parts, instructions.subagents)
89
118
 
90
119
  return parts.join('\n')
91
120
  }
@@ -1,5 +1,6 @@
1
1
  import { Type } from 'typebox'
2
2
  import { loadSelectedGlobalSkills, loadSelectedProjectSkills, mergeSkills } from '../skills.mjs'
3
+ import { listSubagentSummaries } from '../subagents.mjs'
3
4
 
4
5
  // ---------------------------------------------------------------------------
5
6
  // Canonical workspace tool definitions.
@@ -12,7 +13,24 @@ import { loadSelectedGlobalSkills, loadSelectedProjectSkills, mergeSkills } from
12
13
  // it to a handler, and the frontend can fetch definitions from /api/tools.
13
14
  // ---------------------------------------------------------------------------
14
15
 
16
+ const subagentNames = listSubagentSummaries().map((subagent) => subagent.name)
17
+
18
+ export const subagentTool = {
19
+ name: 'run_subagent',
20
+ label: 'Run subagent',
21
+ description: 'Delegate a bounded task to a built-in temporary subagent. Use general for complex multi-step work with full built-in workspace tools, or explore for fast read-only lookup and focused analysis. Subagents are short-lived and do not receive MCP or Agent Skill tools.',
22
+ parameters: Type.Object({
23
+ subagent: subagentNames.length
24
+ ? Type.String({ enum: subagentNames, description: 'Specialized subagent to invoke.' })
25
+ : Type.String({ description: 'Specialized subagent to invoke.' }),
26
+ task: Type.String({ description: 'Concrete, bounded task for the subagent. Do not delegate vague or open-ended work.' }),
27
+ context: Type.Optional(Type.String({ description: 'Relevant context from the parent conversation or current plan. Keep this focused.' })),
28
+ expectedOutput: Type.Optional(Type.String({ description: 'Optional output requirements for the subagent result.' })),
29
+ }),
30
+ }
31
+
15
32
  export const workspaceTools = [
33
+ subagentTool,
16
34
  {
17
35
  name: 'read_file',
18
36
  label: 'Read file',