bingocode 1.1.200 → 1.1.201

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bingocode",
3
- "version": "1.1.200",
3
+ "version": "1.1.201",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "claude": "bin/claude-win.cjs",
@@ -1,229 +1,229 @@
1
- version: 2
2
-
3
- # Provider Preset Configuration
4
- # fields array declares fields to be filled when adding
5
- # key: 'name' | 'apiKey' | 'baseUrl' mapped directly to top-level fields, others saved in extra.<key>
6
- # secret: true uses mask display in frontend
7
- #
8
- # modelsUrl: model list path relative to baseUrl, empty string means dynamic pulling not supported
9
- # modelsAuthStyle: bearer → Authorization: Bearer <apiKey>
10
- # x-api-key → x-api-key: <apiKey> + anthropic-version header
11
- # modelsDataPath: record field name for models array in JSON response (almost always 'data')
12
-
13
- presets:
14
- - id: official
15
- name: Claude Official
16
- baseUrl: ''
17
- apiFormat: anthropic
18
- needsApiKey: false
19
- websiteUrl: https://www.anthropic.com/claude-code
20
- modelsUrl: /v1/models
21
- modelsAuthStyle: x-api-key
22
- modelsDataPath: data
23
- fields:
24
- - key: name
25
- label: Provider Nickname
26
- required: true
27
- secret: false
28
- placeholder: 'e.g. Claude Official'
29
-
30
- - id: openai
31
- name: OpenAI
32
- baseUrl: https://api.openai.com/v1
33
- apiFormat: openai_chat
34
- needsApiKey: true
35
- websiteUrl: https://platform.openai.com
36
- modelsUrl: /v1/models
37
- modelsAuthStyle: bearer
38
- modelsDataPath: data
39
- fields:
40
- - key: name
41
- label: Provider Nickname
42
- required: true
43
- secret: false
44
- placeholder: 'e.g. My OpenAI'
45
- - key: apiKey
46
- label: API Key
47
- required: true
48
- secret: true
49
- placeholder: 'sk-...'
50
- - key: baseUrl
51
- label: Base URL (Optional)
52
- required: false
53
- secret: false
54
- default: https://api.openai.com/v1
55
- placeholder: 'https://api.openai.com/v1'
56
-
57
- - id: gemini
58
- name: Google Gemini
59
- baseUrl: https://generativelanguage.googleapis.com/v1beta/openai
60
- apiFormat: openai_chat
61
- needsApiKey: true
62
- websiteUrl: https://aistudio.google.com
63
- modelsUrl: /v1/models
64
- modelsAuthStyle: bearer
65
- modelsDataPath: data
66
- fields:
67
- - key: name
68
- label: Provider Nickname
69
- required: true
70
- secret: false
71
- placeholder: 'e.g. My Gemini'
72
- - key: apiKey
73
- label: API Key
74
- required: true
75
- secret: true
76
- placeholder: 'Gemini API Key'
77
-
78
- - id: mistral
79
- name: Mistral AI
80
- baseUrl: https://api.mistral.ai/v1
81
- apiFormat: openai_chat
82
- needsApiKey: true
83
- websiteUrl: https://console.mistral.ai
84
- modelsUrl: /v1/models
85
- modelsAuthStyle: bearer
86
- modelsDataPath: data
87
- fields:
88
- - key: name
89
- label: Provider Nickname
90
- required: true
91
- secret: false
92
- placeholder: 'e.g. My Mistral'
93
- - key: apiKey
94
- label: API Key
95
- required: true
96
- secret: true
97
- placeholder: 'Mistral API Key'
98
-
99
- - id: deepseek
100
- name: DeepSeek
101
- baseUrl: https://api.deepseek.com/anthropic
102
- apiFormat: anthropic
103
- needsApiKey: true
104
- websiteUrl: https://platform.deepseek.com
105
- # Models list follows OpenAI endpoint (no /v1/models under /anthropic)
106
- modelsUrl: https://api.deepseek.com/v1/models
107
- modelsAuthStyle: bearer
108
- modelsDataPath: data
109
- fields:
110
- - key: name
111
- label: Provider Nickname
112
- required: true
113
- secret: false
114
- placeholder: 'e.g. My DeepSeek'
115
- - key: apiKey
116
- label: API Key
117
- required: true
118
- secret: true
119
- placeholder: 'sk-...'
120
-
121
- - id: nvidia
122
- name: NVIDIA API
123
- baseUrl: https://integrate.api.nvidia.com/v1
124
- apiFormat: openai_chat
125
- needsApiKey: true
126
- websiteUrl: https://build.nvidia.com/explore/discover
127
- modelsUrl: /models
128
- modelsAuthStyle: bearer
129
- modelsDataPath: data
130
- fields:
131
- - key: name
132
- label: Provider Nickname
133
- required: true
134
- secret: false
135
- placeholder: 'e.g. My NVIDIA'
136
- - key: apiKey
137
- label: API Key
138
- required: true
139
- secret: true
140
- placeholder: 'nvapi-...'
141
-
142
- - id: zhipuglm
143
- name: Zhipu GLM
144
- baseUrl: https://open.bigmodel.cn/api/paas/v4
145
- apiFormat: openai_chat
146
- needsApiKey: true
147
- websiteUrl: https://open.bigmodel.cn
148
- modelsUrl: /v4/models
149
- modelsAuthStyle: bearer
150
- modelsDataPath: data
151
- fields:
152
- - key: name
153
- label: Provider Nickname
154
- required: true
155
- secret: false
156
- placeholder: 'e.g. My GLM'
157
- - key: apiKey
158
- label: API Key
159
- required: true
160
- secret: true
161
- placeholder: 'Zhipu API Key (glm-5.1)'
162
-
163
- - id: kimi
164
- name: Kimi
165
- baseUrl: https://api.moonshot.ai/v1
166
- apiFormat: openai_chat
167
- needsApiKey: true
168
- websiteUrl: https://platform.moonshot.cn
169
- modelsUrl: /models
170
- modelsAuthStyle: bearer
171
- modelsDataPath: data
172
- fields:
173
- - key: name
174
- label: Provider Nickname
175
- required: true
176
- secret: false
177
- placeholder: 'e.g. My Kimi'
178
- - key: apiKey
179
- label: API Key
180
- required: true
181
- secret: true
182
- placeholder: 'Moonshot API Key (kimi-k2.6)'
183
-
184
- - id: minimax
185
- name: MiniMax
186
- baseUrl: https://api.minimax.io/v1
187
- apiFormat: openai_chat
188
- needsApiKey: true
189
- websiteUrl: https://platform.minimaxi.com
190
- modelsUrl: /models
191
- modelsAuthStyle: bearer
192
- modelsDataPath: data
193
- fields:
194
- - key: name
195
- label: Provider Nickname
196
- required: true
197
- secret: false
198
- placeholder: 'e.g. My MiniMax'
199
- - key: apiKey
200
- label: API Key
201
- required: true
202
- secret: true
203
- placeholder: 'MiniMax API Key (MiniMax-M2.7)'
204
-
205
- - id: custom
206
- name: Custom
207
- baseUrl: ''
208
- apiFormat: openai_chat
209
- needsApiKey: true
210
- websiteUrl: ''
211
- modelsUrl: /v1/models
212
- modelsAuthStyle: bearer
213
- modelsDataPath: data
214
- fields:
215
- - key: name
216
- label: Provider Nickname
217
- required: true
218
- secret: false
219
- placeholder: 'e.g. My Custom Provider'
220
- - key: baseUrl
221
- label: Base URL
222
- required: true
223
- secret: false
224
- placeholder: 'https://your-api-endpoint.com/v1'
225
- - key: apiKey
226
- label: API Key
227
- required: false
228
- secret: true
229
- placeholder: '(Optional) API Key'
1
+ version: 2
2
+
3
+ # Provider Preset Configuration
4
+ # fields array declares fields to be filled when adding
5
+ # key: 'name' | 'apiKey' | 'baseUrl' mapped directly to top-level fields, others saved in extra.<key>
6
+ # secret: true uses mask display in frontend
7
+ #
8
+ # modelsUrl: model list path relative to baseUrl, empty string means dynamic pulling not supported
9
+ # modelsAuthStyle: bearer → Authorization: Bearer <apiKey>
10
+ # x-api-key → x-api-key: <apiKey> + anthropic-version header
11
+ # modelsDataPath: record field name for models array in JSON response (almost always 'data')
12
+
13
+ presets:
14
+ - id: official
15
+ name: Claude Official
16
+ baseUrl: ''
17
+ apiFormat: anthropic
18
+ needsApiKey: false
19
+ websiteUrl: https://www.anthropic.com/claude-code
20
+ modelsUrl: /v1/models
21
+ modelsAuthStyle: x-api-key
22
+ modelsDataPath: data
23
+ fields:
24
+ - key: name
25
+ label: Provider Nickname
26
+ required: true
27
+ secret: false
28
+ placeholder: 'e.g. Claude Official'
29
+
30
+ - id: openai
31
+ name: OpenAI
32
+ baseUrl: https://api.openai.com/v1
33
+ apiFormat: openai_chat
34
+ needsApiKey: true
35
+ websiteUrl: https://platform.openai.com
36
+ modelsUrl: /v1/models
37
+ modelsAuthStyle: bearer
38
+ modelsDataPath: data
39
+ fields:
40
+ - key: name
41
+ label: Provider Nickname
42
+ required: true
43
+ secret: false
44
+ placeholder: 'e.g. My OpenAI'
45
+ - key: apiKey
46
+ label: API Key
47
+ required: true
48
+ secret: true
49
+ placeholder: 'sk-...'
50
+ - key: baseUrl
51
+ label: Base URL (Optional)
52
+ required: false
53
+ secret: false
54
+ default: https://api.openai.com/v1
55
+ placeholder: 'https://api.openai.com/v1'
56
+
57
+ - id: gemini
58
+ name: Google Gemini
59
+ baseUrl: https://generativelanguage.googleapis.com/v1beta/openai
60
+ apiFormat: openai_chat
61
+ needsApiKey: true
62
+ websiteUrl: https://aistudio.google.com
63
+ modelsUrl: /v1/models
64
+ modelsAuthStyle: bearer
65
+ modelsDataPath: data
66
+ fields:
67
+ - key: name
68
+ label: Provider Nickname
69
+ required: true
70
+ secret: false
71
+ placeholder: 'e.g. My Gemini'
72
+ - key: apiKey
73
+ label: API Key
74
+ required: true
75
+ secret: true
76
+ placeholder: 'Gemini API Key'
77
+
78
+ - id: mistral
79
+ name: Mistral AI
80
+ baseUrl: https://api.mistral.ai/v1
81
+ apiFormat: openai_chat
82
+ needsApiKey: true
83
+ websiteUrl: https://console.mistral.ai
84
+ modelsUrl: /v1/models
85
+ modelsAuthStyle: bearer
86
+ modelsDataPath: data
87
+ fields:
88
+ - key: name
89
+ label: Provider Nickname
90
+ required: true
91
+ secret: false
92
+ placeholder: 'e.g. My Mistral'
93
+ - key: apiKey
94
+ label: API Key
95
+ required: true
96
+ secret: true
97
+ placeholder: 'Mistral API Key'
98
+
99
+ - id: deepseek
100
+ name: DeepSeek
101
+ baseUrl: https://api.deepseek.com/anthropic
102
+ apiFormat: anthropic
103
+ needsApiKey: true
104
+ websiteUrl: https://platform.deepseek.com
105
+ # Models list follows OpenAI endpoint (no /v1/models under /anthropic)
106
+ modelsUrl: https://api.deepseek.com/v1/models
107
+ modelsAuthStyle: bearer
108
+ modelsDataPath: data
109
+ fields:
110
+ - key: name
111
+ label: Provider Nickname
112
+ required: true
113
+ secret: false
114
+ placeholder: 'e.g. My DeepSeek'
115
+ - key: apiKey
116
+ label: API Key
117
+ required: true
118
+ secret: true
119
+ placeholder: 'sk-...'
120
+
121
+ - id: nvidia
122
+ name: NVIDIA API
123
+ baseUrl: https://integrate.api.nvidia.com/v1
124
+ apiFormat: openai_chat
125
+ needsApiKey: true
126
+ websiteUrl: https://build.nvidia.com/explore/discover
127
+ modelsUrl: /models
128
+ modelsAuthStyle: bearer
129
+ modelsDataPath: data
130
+ fields:
131
+ - key: name
132
+ label: Provider Nickname
133
+ required: true
134
+ secret: false
135
+ placeholder: 'e.g. My NVIDIA'
136
+ - key: apiKey
137
+ label: API Key
138
+ required: true
139
+ secret: true
140
+ placeholder: 'nvapi-...'
141
+
142
+ - id: zhipuglm
143
+ name: Zhipu GLM
144
+ baseUrl: https://open.bigmodel.cn/api/paas/v4
145
+ apiFormat: openai_chat
146
+ needsApiKey: true
147
+ websiteUrl: https://open.bigmodel.cn
148
+ modelsUrl: /v4/models
149
+ modelsAuthStyle: bearer
150
+ modelsDataPath: data
151
+ fields:
152
+ - key: name
153
+ label: Provider Nickname
154
+ required: true
155
+ secret: false
156
+ placeholder: 'e.g. My GLM'
157
+ - key: apiKey
158
+ label: API Key
159
+ required: true
160
+ secret: true
161
+ placeholder: 'Zhipu API Key (glm-5.1)'
162
+
163
+ - id: kimi
164
+ name: Kimi
165
+ baseUrl: https://api.moonshot.ai/v1
166
+ apiFormat: openai_chat
167
+ needsApiKey: true
168
+ websiteUrl: https://platform.moonshot.cn
169
+ modelsUrl: /models
170
+ modelsAuthStyle: bearer
171
+ modelsDataPath: data
172
+ fields:
173
+ - key: name
174
+ label: Provider Nickname
175
+ required: true
176
+ secret: false
177
+ placeholder: 'e.g. My Kimi'
178
+ - key: apiKey
179
+ label: API Key
180
+ required: true
181
+ secret: true
182
+ placeholder: 'Moonshot API Key (kimi-k2.6)'
183
+
184
+ - id: minimax
185
+ name: MiniMax
186
+ baseUrl: https://api.minimax.io/v1
187
+ apiFormat: openai_chat
188
+ needsApiKey: true
189
+ websiteUrl: https://platform.minimaxi.com
190
+ modelsUrl: /models
191
+ modelsAuthStyle: bearer
192
+ modelsDataPath: data
193
+ fields:
194
+ - key: name
195
+ label: Provider Nickname
196
+ required: true
197
+ secret: false
198
+ placeholder: 'e.g. My MiniMax'
199
+ - key: apiKey
200
+ label: API Key
201
+ required: true
202
+ secret: true
203
+ placeholder: 'MiniMax API Key (MiniMax-M2.7)'
204
+
205
+ - id: custom
206
+ name: Custom
207
+ baseUrl: ''
208
+ apiFormat: openai_chat
209
+ needsApiKey: true
210
+ websiteUrl: ''
211
+ modelsUrl: /v1/models
212
+ modelsAuthStyle: bearer
213
+ modelsDataPath: data
214
+ fields:
215
+ - key: name
216
+ label: Provider Nickname
217
+ required: true
218
+ secret: false
219
+ placeholder: 'e.g. My Custom Provider'
220
+ - key: baseUrl
221
+ label: Base URL
222
+ required: true
223
+ secret: false
224
+ placeholder: 'https://your-api-endpoint.com/v1'
225
+ - key: apiKey
226
+ label: API Key
227
+ required: false
228
+ secret: true
229
+ placeholder: '(Optional) API Key'
@@ -9,6 +9,7 @@
9
9
  import * as fs from 'fs/promises'
10
10
  import * as path from 'path'
11
11
  import * as os from 'os'
12
+ import { parse as parseJsonc } from 'jsonc-parser/lib/esm/main.js'
12
13
  import { getDirectFetchOptions } from '../../utils/proxy.ts'
13
14
  import { ApiError } from '../middleware/errorHandler.js'
14
15
  import { anthropicToOpenaiChat } from '../proxy/transform/anthropicToOpenaiChat.js'
@@ -268,6 +269,18 @@ export class ProviderService {
268
269
  // --- Settings sync ---
269
270
 
270
271
  private async syncToSettings(provider: SavedProvider): Promise<void> {
272
+ // Prefer slot-based model IDs when slots are configured, so that the
273
+ // slot table (set via "Configure Model Slots" in the TUI) is the single
274
+ // source of truth. Falling back to provider.models only when no slots
275
+ // exist keeps things working for users who haven't configured slots yet.
276
+ const slots = await this.readSlots()
277
+ const hasSlots = Object.keys(slots).length > 0
278
+ if (hasSlots) {
279
+ await this.syncSettingsForSlots(slots)
280
+ return
281
+ }
282
+
283
+ // Legacy fallback: no slots configured — use provider.models directly
271
284
  const settings = await this.readSettings()
272
285
  const existingEnv = (settings.env as Record<string, string>) || {}
273
286
 
@@ -620,7 +633,9 @@ export class ProviderService {
620
633
  let originalContent: string | null = null
621
634
  try {
622
635
  originalContent = await fs.readFile(settingsPath, "utf-8")
623
- settings = JSON.parse(originalContent)
636
+ // VS Code settings.json uses JSONC (JSON with comments)
637
+ // parseJsonc handles comments/trailing commas, unlike JSON.parse
638
+ settings = parseJsonc(originalContent) as Record<string, unknown>
624
639
  } catch {
625
640
  // File does not exist yet — start fresh
626
641
  }
@@ -650,7 +665,9 @@ export class ProviderService {
650
665
 
651
666
  try {
652
667
  await fs.mkdir(path.dirname(settingsPath), { recursive: true })
653
- await fs.writeFile(settingsPath, JSON.stringify(settings, null, 4) + '\n')
668
+ const tmpPath = `${settingsPath}.tmp.${Date.now()}`
669
+ await fs.writeFile(tmpPath, JSON.stringify(settings, null, 4) + '\n')
670
+ await fs.rename(tmpPath, settingsPath)
654
671
  writtenPaths.push(settingsPath)
655
672
  } catch (err) {
656
673
  failedPaths.push(settingsPath)
@@ -661,7 +678,9 @@ export class ProviderService {
661
678
  // Fix #11: Attempt restore from backup on write failure
662
679
  if (originalContent !== null) {
663
680
  try {
664
- await fs.writeFile(settingsPath, originalContent, "utf-8")
681
+ const tmpPath2 = `${settingsPath}.tmp.${Date.now()}`
682
+ await fs.writeFile(tmpPath2, originalContent, "utf-8")
683
+ await fs.rename(tmpPath2, settingsPath)
665
684
  console.error(`[ProviderService] Restored ${settingsPath} from in-memory copy after write failure`)
666
685
  } catch {
667
686
  // In-memory restore failed, try .bak file
@@ -694,7 +713,9 @@ export class ProviderService {
694
713
  const original = originalContents.get(p)
695
714
  try {
696
715
  if (original !== null) {
697
- await fs.writeFile(p, original, "utf-8")
716
+ const tmpPath = `${p}.tmp.${Date.now()}`
717
+ await fs.writeFile(tmpPath, original, "utf-8")
718
+ await fs.rename(tmpPath, p)
698
719
  } else {
699
720
  await fs.unlink(p).catch(() => {})
700
721
  }
@@ -754,14 +775,16 @@ export class ProviderService {
754
775
  for (const settingsPath of linkedPaths) {
755
776
  try {
756
777
  const raw = await fs.readFile(settingsPath, "utf-8")
757
- const settings = JSON.parse(raw)
778
+ const settings = parseJsonc(raw) as Record<string, unknown>
758
779
  settings.claudeCode = preLinkClaudeCode[settingsPath] ?? {
759
780
  preferredLocation: "panel",
760
781
  environmentVariables: [
761
782
  { name: "ANTHROPIC_BASE_URL", value: "https://api.anthropic.com" },
762
783
  ],
763
784
  }
764
- await fs.writeFile(settingsPath, JSON.stringify(settings, null, 4) + '\n')
785
+ const tmpPath = `${settingsPath}.tmp.${Date.now()}`
786
+ await fs.writeFile(tmpPath, JSON.stringify(settings, null, 4) + '\n')
787
+ await fs.rename(tmpPath, settingsPath)
765
788
  } catch {
766
789
  // File may have been moved or deleted since linking — harmless
767
790
  }
@@ -5,7 +5,7 @@ import { ConfigurableShortcutHint } from 'src/components/ConfigurableShortcutHin
5
5
  import { CtrlOToExpand, SubAgentProvider } from 'src/components/CtrlOToExpand.js';
6
6
  import { Byline } from 'src/components/design-system/Byline.js';
7
7
  import { KeyboardShortcutHint } from 'src/components/design-system/KeyboardShortcutHint.js';
8
- import type { z } from 'zod/v4';
8
+ import { z } from 'zod/v4';
9
9
  import { AgentProgressLine } from '../../components/AgentProgressLine.js';
10
10
  import { FallbackToolUseErrorMessage } from '../../components/FallbackToolUseErrorMessage.js';
11
11
  import { FallbackToolUseRejectedMessage } from '../../components/FallbackToolUseRejectedMessage.js';
@@ -15,14 +15,14 @@ import { MessageResponse } from '../../components/MessageResponse.js';
15
15
  import { ToolUseLoader } from '../../components/ToolUseLoader.js';
16
16
  import { Box, Text } from '../../ink.js';
17
17
  import { getDumpPromptsPath } from '../../services/api/dumpPrompts.js';
18
- import { findToolByName, type Tools } from '../../Tool.js';
18
+ import { buildTool, findToolByName, toolMatchesName, type Tool, type Tools } from '../../Tool.js';
19
19
  import type { Message, ProgressMessage } from '../../types/message.js';
20
20
  import type { AgentToolProgress } from '../../types/tools.js';
21
21
  import { count } from '../../utils/array.js';
22
22
  import { getSearchOrReadFromContent, getSearchReadSummaryText } from '../../utils/collapseReadSearch.js';
23
23
  import { getDisplayPath } from '../../utils/file.js';
24
24
  import { formatDuration, formatNumber } from '../../utils/format.js';
25
- import { buildSubagentLookups, createAssistantMessage, EMPTY_LOOKUPS } from '../../utils/messages.js';
25
+ import { buildSubagentLookups, createAssistantMessage, EMPTY_LOOKUPS, type MessageLookups } from '../../utils/messages.js';
26
26
  import type { ModelAlias } from '../../utils/model/aliases.js';
27
27
  import { getMainLoopModel, parseUserSpecifiedModel, renderModelName } from '../../utils/model/model.js';
28
28
  import type { Theme, ThemeName } from '../../utils/theme.js';
@@ -32,6 +32,58 @@ import { getAgentColor } from './agentColorManager.js';
32
32
  import { GENERAL_PURPOSE_AGENT } from './built-in/generalPurposeAgent.js';
33
33
  const MAX_PROGRESS_MESSAGES_TO_SHOW = 3;
34
34
 
35
+ /**
36
+ * Create a minimal fallback Tool object for a sub-agent tool name not present
37
+ * in the coordinator's tool set. Used in brain mode where the coordinator only
38
+ * has [Agent, AskUserQuestion] but sub-agents use Read/Grep/Bash/Edit/etc.
39
+ * Without fallbacks, findToolByName returns null and renders blank lines.
40
+ */
41
+ function createFallbackTool(name: string): Tool {
42
+ return buildTool({
43
+ name,
44
+ inputSchema: z.object({}).passthrough(),
45
+ description: async () => `Sub-agent tool: ${name}`,
46
+ call: async () => ({ data: {} as unknown }) as any,
47
+ maxResultSizeChars: 100_000,
48
+ prompt: async () => '',
49
+ mapToolResultToToolResultBlockParam: (_content: unknown, toolUseID: string) => ({
50
+ tool_use_id: toolUseID,
51
+ content: [{ type: 'text' as const, text: '' }],
52
+ type: 'tool_result' as const,
53
+ }),
54
+ renderToolUseMessage: (input: Record<string, unknown>) => {
55
+ const keys = Object.keys(input);
56
+ if (keys.length === 0) return '';
57
+ return keys.map(k => {
58
+ const v = input[k];
59
+ let s = typeof v === 'string' ? v : JSON.stringify(v);
60
+ if (s.length > 80) s = s.slice(0, 80) + '\u2026';
61
+ return `${k}: ${s}`;
62
+ }).join(', ');
63
+ },
64
+ renderToolResultMessage: () => null,
65
+ })
66
+ }
67
+
68
+ /**
69
+ * Given the result of buildSubagentLookups, create fallback Tool entries
70
+ * for any tool name not already in `tools`. Returns the original `tools`
71
+ * array if no new names are found. Avoids re-scanning progress data.
72
+ */
73
+ function augmentToolsFromLookups(
74
+ tools: Tools,
75
+ lookups: MessageLookups,
76
+ ): Tools {
77
+ const toolNames = new Set<string>()
78
+ for (const block of lookups.toolUseByToolUseID.values()) {
79
+ toolNames.add(block.name)
80
+ }
81
+ if (toolNames.size === 0) return tools
82
+ const missing = [...toolNames].filter(n => !tools.some(t => toolMatchesName(t, n)))
83
+ if (missing.length === 0) return tools
84
+ return [...tools, ...missing.map(createFallbackTool)]
85
+ }
86
+
35
87
  /**
36
88
  * Guard: checks if progress data has a `message` field (agent_progress or
37
89
  * skill_progress). Other progress types (e.g. bash_progress forwarded from
@@ -262,12 +314,12 @@ function VerboseAgentTranscript(t0) {
262
314
  lookups: agentLookups,
263
315
  inProgressToolUseIDs
264
316
  } = t1;
265
- let t2;
317
+ const augmentedTools = augmentToolsFromLookups(tools, agentLookups); let t2;
266
318
  if ($[2] !== agentLookups || $[3] !== inProgressToolUseIDs || $[4] !== progressMessages || $[5] !== tools || $[6] !== verbose) {
267
319
  const filteredMessages = progressMessages.filter(_temp4);
268
320
  let t3;
269
321
  if ($[8] !== agentLookups || $[9] !== inProgressToolUseIDs || $[10] !== tools || $[11] !== verbose) {
270
- t3 = progressMessage => <MessageResponse key={progressMessage.uuid} height={1}><MessageComponent message={progressMessage.data.message} lookups={agentLookups} addMargin={false} tools={tools} commands={[]} verbose={verbose} inProgressToolUseIDs={inProgressToolUseIDs} progressMessagesForMessage={[]} shouldAnimate={false} shouldShowDot={false} isTranscriptMode={false} isStatic={true} /></MessageResponse>;
322
+ t3 = progressMessage => <MessageResponse key={progressMessage.uuid} height={1}><MessageComponent message={progressMessage.data.message} lookups={agentLookups} addMargin={false} tools={augmentedTools} commands={[]} verbose={verbose} inProgressToolUseIDs={inProgressToolUseIDs} progressMessagesForMessage={[]} shouldAnimate={false} shouldShowDot={false} isTranscriptMode={false} isStatic={true} /></MessageResponse>;
271
323
  $[8] = agentLookups;
272
324
  $[9] = inProgressToolUseIDs;
273
325
  $[10] = tools;
@@ -384,6 +436,12 @@ export function renderToolResultMessage(data: Output, progressMessagesForMessage
384
436
  speed: null
385
437
  }
386
438
  });
439
+ const agentProgressData = progressMessagesForMessage
440
+ .filter((pm): pm is ProgressMessage<AgentToolProgress> => hasProgressMessage(pm.data))
441
+ .map(pm => pm.data)
442
+ const { lookups: resultLookups } = buildSubagentLookups(agentProgressData)
443
+ const augmentedTools = augmentToolsFromLookups(tools, resultLookups)
444
+
387
445
  return <Box flexDirection="column">
388
446
  {"external" === 'ant' && <MessageResponse>
389
447
  <Text color="warning">
@@ -394,13 +452,13 @@ export function renderToolResultMessage(data: Output, progressMessagesForMessage
394
452
  <AgentPromptDisplay prompt={prompt} theme={theme} />
395
453
  </MessageResponse>}
396
454
  {isTranscriptMode ? <SubAgentProvider>
397
- <VerboseAgentTranscript progressMessages={progressMessagesForMessage} tools={tools} verbose={verbose} />
455
+ <VerboseAgentTranscript progressMessages={progressMessagesForMessage} tools={augmentedTools} verbose={verbose} />
398
456
  </SubAgentProvider> : null}
399
457
  {isTranscriptMode && content && content.length > 0 && <MessageResponse>
400
458
  <AgentResponseDisplay content={content} theme={theme} />
401
459
  </MessageResponse>}
402
460
  <MessageResponse height={1}>
403
- <MessageComponent message={finalAssistantMessage} lookups={EMPTY_LOOKUPS} addMargin={false} tools={tools} commands={[]} verbose={verbose} inProgressToolUseIDs={new Set()} progressMessagesForMessage={[]} shouldAnimate={false} shouldShowDot={false} isTranscriptMode={false} isStatic={true} />
461
+ <MessageComponent message={finalAssistantMessage} lookups={EMPTY_LOOKUPS} addMargin={false} tools={augmentedTools} commands={[]} verbose={verbose} inProgressToolUseIDs={new Set()} progressMessagesForMessage={[]} shouldAnimate={false} shouldShowDot={false} isTranscriptMode={false} isStatic={true} />
404
462
  </MessageResponse>
405
463
  {!isTranscriptMode && <Text dimColor>
406
464
  {' '}
@@ -540,6 +598,13 @@ export function renderToolUseProgressMessage(progressMessages: ProgressMessage<P
540
598
  lookups: subagentLookups,
541
599
  inProgressToolUseIDs: collapsedInProgressIDs
542
600
  } = buildSubagentLookups(progressMessages.filter((pm): pm is ProgressMessage<AgentToolProgress> => hasProgressMessage(pm.data)).map(pm => pm.data));
601
+
602
+ // Augment tools with fallback entries for sub-agent tools not in the
603
+ // coordinator's tool set. In brain mode the coordinator only has
604
+ // [Agent, AskUserQuestion], but sub-agents call Read/Grep/Bash/Edit/etc.
605
+ // Without fallbacks, findToolByName returns null and renders blank lines.
606
+ const augmentedTools = augmentToolsFromLookups(tools, subagentLookups);
607
+
543
608
  return <MessageResponse>
544
609
  <Box flexDirection="column">
545
610
  <SubAgentProvider>
@@ -558,7 +623,7 @@ export function renderToolUseProgressMessage(progressMessages: ProgressMessage<P
558
623
  // content (tool not found, renderToolUseMessage returns null)
559
624
  // doesn't leave a blank line. Tool call headers are single-line
560
625
  // anyway so truncation isn't needed.
561
- return <MessageComponent key={processed.message.uuid} message={processed.message.data.message} lookups={subagentLookups} addMargin={false} tools={tools} commands={[]} verbose={verbose} inProgressToolUseIDs={collapsedInProgressIDs} progressMessagesForMessage={[]} shouldAnimate={false} shouldShowDot={false} style="condensed" isTranscriptMode={false} isStatic={true} />;
626
+ return <MessageComponent key={processed.message.uuid} message={processed.message.data.message} lookups={subagentLookups} addMargin={false} tools={augmentedTools} commands={[]} verbose={verbose} inProgressToolUseIDs={collapsedInProgressIDs} progressMessagesForMessage={[]} shouldAnimate={false} shouldShowDot={false} style="condensed" isTranscriptMode={false} isStatic={true} />;
562
627
  })}
563
628
  </SubAgentProvider>
564
629
  {hiddenToolUseCount > 0 && <Text dimColor>
@@ -6,7 +6,7 @@ import { getCanonicalName } from './model/model.js'
6
6
  import { getModelCapability } from './model/modelCapabilities.js'
7
7
 
8
8
  // Model context window size (200k tokens for all models right now)
9
- export const MODEL_CONTEXT_WINDOW_DEFAULT = 200_000
9
+ export const MODEL_CONTEXT_WINDOW_DEFAULT = 1_000_000
10
10
 
11
11
  // Maximum output tokens for compact operations
12
12
  export const COMPACT_MAX_OUTPUT_TOKENS = 20_000