Skip to content

Commit f618a0f

Browse files
v0.4.8
# v0.4.8 — LLM Tool & Plugin Fix --- ## Features - **`call_llm` tool** — Invoke a secondary LLM for focused subtasks like summarization, classification, and structured extraction. Supported across all three backends: Claude, Codex, and Copilot. Works with both API key (full features) and OAuth (basic features). Supports parallel calls, file attachments, and structured JSON output (eb0a3476, fa91af4c, b6e5c85f) ## Bug Fixes - **Plugin name resolution** — Skills failed to resolve when the workspace directory name didn't match the SDK plugin name. Now reads the actual plugin name from `.claude-plugin/plugin.json` (b7904cb7) - **Skill live reload** — Adding a workspace skill caused global and project skills to disappear until restart. All reload paths now use `loadAllSkills` to return the full three-tier list (4172fd82) - **Codex event queue race condition** — Tool results and assistant text could be lost when async `item/completed` handlers were still running at `turn/completed`. Fixed by deferring queue completion until all handlers finish (fa91af4c) ## Internal - Copilot `runMiniCompletion` now functional — enables title generation on the Copilot backend (fa91af4c) - Copilot event adapter suppresses reasoning/intent events (fa91af4c) - `call_llm` model badge shown in TurnCard activity rows (8bb4bcd2)
1 parent 42a9f80 commit f618a0f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+2190
-333
lines changed

apps/electron/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@craft-agent/electron",
3-
"version": "0.4.7",
3+
"version": "0.4.8",
44
"description": "Electron desktop app for Craft Agents",
55
"main": "dist/main.cjs",
66
"private": true,
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
# LLM Tool (`call_llm`)
2+
3+
Invoke a secondary LLM for focused subtasks. The tool loads file content automatically from paths you provide.
4+
5+
## When to Use
6+
7+
| Use Case | Model | Features |
8+
|----------|-------|----------|
9+
| Summarize large file | haiku | `attachments` |
10+
| Classify content | haiku | `outputFormat: "classification"` |
11+
| Extract structured data | haiku | `outputSchema` |
12+
| Deep analysis | sonnet/opus | `thinking: true` (API key only) |
13+
| Parallel processing | any | Multiple calls in one message |
14+
15+
## Authentication Paths
16+
17+
Features depend on how you authenticate:
18+
19+
| Feature | API Key | OAuth |
20+
|---------|---------|-------|
21+
| Text attachments | Yes | Yes |
22+
| Image attachments | Yes | No |
23+
| Structured output | Guaranteed (tool_choice) | Prompt-based |
24+
| Extended thinking | Yes | No |
25+
| All models | Yes | Yes |
26+
27+
- **API key**: Full features via direct Anthropic SDK
28+
- **OAuth**: Basic features via agent-native callback
29+
30+
## Parameters
31+
32+
| Parameter | Type | Description |
33+
|-----------|------|-------------|
34+
| `prompt` | string | Instructions for the LLM (required) |
35+
| `attachments` | array | File/image paths to include |
36+
| `model` | string | Any model from the model registry. Defaults to Haiku (fastest) |
37+
| `systemPrompt` | string | Optional system prompt |
38+
| `maxTokens` | number | Max output tokens (1-64000, default 4096) |
39+
| `temperature` | number | Sampling temperature 0-1 (ignored if thinking=true) |
40+
| `thinking` | boolean | Enable extended thinking (API key only) |
41+
| `thinkingBudget` | number | Token budget for thinking (1024-100000, default 10000) |
42+
| `outputFormat` | enum | Predefined output format |
43+
| `outputSchema` | object | Custom JSON Schema |
44+
45+
## Attachments
46+
47+
```typescript
48+
// Simple file
49+
attachments: ["/src/auth.ts"]
50+
51+
// Large file - use line range (required for files >2000 lines)
52+
attachments: [{ path: "/logs/app.log", startLine: 1000, endLine: 1500 }]
53+
54+
// Mix of files and images (API key only for images)
55+
attachments: ["/src/component.tsx", "/designs/mockup.png"]
56+
```
57+
58+
### Line Ranges
59+
60+
For files larger than 2000 lines or 500KB, you must specify a line range:
61+
62+
```typescript
63+
{ path: "/path/to/large-file.log", startLine: 100, endLine: 600 }
64+
```
65+
66+
The tool will tell you the file structure if you try to load a file that's too large.
67+
68+
### Supported Formats
69+
70+
- **Text files**: Any UTF-8 encoded file (`.ts`, `.js`, `.py`, `.md`, `.json`, etc.)
71+
- **Images**: `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp` (max 5MB each, API key only)
72+
73+
## Output Formats
74+
75+
| Format | Returns |
76+
|--------|---------|
77+
| `summary` | `{ summary, key_points[], word_count }` |
78+
| `classification` | `{ category, confidence, reasoning }` |
79+
| `extraction` | `{ items[], count }` |
80+
| `analysis` | `{ findings[], issues[], recommendations[] }` |
81+
| `comparison` | `{ similarities[], differences[], verdict }` |
82+
| `validation` | `{ valid, errors[], warnings[] }` |
83+
84+
## Parallel Processing
85+
86+
Call multiple times in a single message for parallel execution:
87+
88+
```
89+
call_llm(prompt: "Summarize", attachments: ["/file1.ts"])
90+
call_llm(prompt: "Summarize", attachments: ["/file2.ts"])
91+
call_llm(prompt: "Summarize", attachments: ["/file3.ts"])
92+
// All run simultaneously - ~3x faster than sequential!
93+
```
94+
95+
Use cases for parallel calls:
96+
- Analyze multiple files at once
97+
- Get multiple perspectives on the same code
98+
- Process batch data
99+
- Generate variations
100+
101+
## Examples
102+
103+
### Summarize a File (Cheap)
104+
105+
```typescript
106+
call_llm({
107+
prompt: "Summarize the main functionality",
108+
attachments: ["/src/auth/handler.ts"],
109+
model: "claude-haiku-4-5-20251001"
110+
})
111+
```
112+
113+
### Extract Structured Data
114+
115+
```typescript
116+
call_llm({
117+
prompt: "Extract all API endpoints from this file",
118+
attachments: ["/src/routes.ts"],
119+
outputSchema: {
120+
type: "object",
121+
properties: {
122+
endpoints: {
123+
type: "array",
124+
items: {
125+
type: "object",
126+
properties: {
127+
method: { type: "string" },
128+
path: { type: "string" },
129+
handler: { type: "string" }
130+
}
131+
}
132+
}
133+
},
134+
required: ["endpoints"]
135+
}
136+
})
137+
```
138+
139+
### Classify Content
140+
141+
```typescript
142+
call_llm({
143+
prompt: "Classify this support ticket by urgency and category",
144+
attachments: ["/tickets/latest.txt"],
145+
outputFormat: "classification"
146+
})
147+
// Returns: { category: "billing", confidence: 0.92, reasoning: "..." }
148+
```
149+
150+
### Deep Analysis with Thinking (API Key Only)
151+
152+
```typescript
153+
call_llm({
154+
prompt: "Analyze this algorithm for edge cases and potential bugs",
155+
attachments: ["/src/sorting.ts"],
156+
model: "claude-sonnet-4-5-20250929",
157+
thinking: true,
158+
thinkingBudget: 15000
159+
})
160+
```
161+
162+
### Analyze Screenshot (API Key Only)
163+
164+
```typescript
165+
call_llm({
166+
prompt: "Describe the UI issues in this screenshot",
167+
attachments: ["/screenshots/bug-report.png"],
168+
model: "claude-sonnet-4-5-20250929"
169+
})
170+
```
171+
172+
## Constraints
173+
174+
| Constraint | Limit |
175+
|------------|-------|
176+
| Max attachments | 20 per call |
177+
| Max text file size | 2000 lines or 500KB |
178+
| Max image size | 5MB |
179+
| Max total content | 2MB across all attachments |
180+
| thinking + structured output | Mutually exclusive |
181+
| thinking + haiku | Not supported (use Sonnet/Opus) |
182+
| thinking + OAuth | Not supported (use API key) |
183+
| images + OAuth | Not supported (use API key) |
184+
185+
## Error Handling
186+
187+
The tool provides detailed error messages with recovery suggestions. Common errors:
188+
189+
### Attachment Errors
190+
191+
| Error | Cause | Solution |
192+
|-------|-------|----------|
193+
| File not found | Path doesn't exist | Check path spelling, use absolute paths |
194+
| File too large | >2000 lines or >500KB | Use line range: `{ path, startLine, endLine }` |
195+
| Line range too large | Range exceeds 2000 lines | Reduce range or split into multiple calls |
196+
| Binary file detected | Non-text file without image extension | Use only text files or supported images |
197+
| Permission denied | Cannot read file | Check file permissions |
198+
| Empty file | File has no content | Skip empty files |
199+
| Broken symlink | Symlink target missing | Fix or remove symlink |
200+
201+
### Parameter Errors
202+
203+
| Error | Cause | Solution |
204+
|-------|-------|----------|
205+
| thinking + outputFormat | Incompatible modes | Remove one option |
206+
| thinking + haiku | Haiku doesn't support thinking | Use Sonnet or Opus |
207+
| thinking + OAuth | OAuth doesn't support thinking | Use API key or remove thinking |
208+
| images + OAuth | OAuth doesn't support images | Use API key or remove images |
209+
| thinkingBudget without thinking | Missing thinking=true | Add `thinking: true` |
210+
| Invalid line range | startLine > endLine | Fix range values |
211+
| Unknown model | Model not in registry | Check available models in settings |
212+
213+
### API Errors (API Key Path)
214+
215+
| Status | Meaning | Recovery |
216+
|--------|---------|----------|
217+
| 401 | Invalid API key | Check/refresh credentials |
218+
| 403 | Access denied | Verify model access on your plan |
219+
| 429 | Rate limited | Reduce parallel calls, wait before retry |
220+
| 500/502/503 | API unavailable | Retry in a few seconds |
221+
222+
## When NOT to Use
223+
224+
- You can reason through it yourself without needing a cheaper model
225+
- The task requires your conversation context
226+
- You need tools (Read, Bash, Glob) - use Task tool with subagents instead
227+
- Simple one-liner responses that don't need isolation
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# v0.4.8 — LLM Tool & Plugin Fix
2+
3+
---
4+
5+
## Features
6+
7+
- **`call_llm` tool** — Invoke a secondary LLM for focused subtasks like summarization, classification, and structured extraction. Supported across all three backends: Claude, Codex, and Copilot. Works with both API key (full features) and OAuth (basic features). Supports parallel calls, file attachments, and structured JSON output (eb0a3476, fa91af4c, b6e5c85f)
8+
9+
## Bug Fixes
10+
11+
- **Plugin name resolution** — Skills failed to resolve when the workspace directory name didn't match the SDK plugin name. Now reads the actual plugin name from `.claude-plugin/plugin.json` (b7904cb7)
12+
- **Skill live reload** — Adding a workspace skill caused global and project skills to disappear until restart. All reload paths now use `loadAllSkills` to return the full three-tier list (4172fd82)
13+
- **Codex event queue race condition** — Tool results and assistant text could be lost when async `item/completed` handlers were still running at `turn/completed`. Fixed by deferring queue completion until all handlers finish (fa91af4c)
14+
15+
## Internal
16+
17+
- Copilot `runMiniCompletion` now functional — enables title generation on the Copilot backend (fa91af4c)
18+
- Copilot event adapter suppresses reasoning/intent events (fa91af4c)
19+
- `call_llm` model badge shown in TurnCard activity rows (8bb4bcd2)

apps/electron/src/main/lib/config-watcher.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import {
3939
import { permissionsConfigCache, getAppPermissionsDir } from '@craft-agent/shared/agent';
4040
import { getWorkspacePath, getWorkspaceSourcesPath, getWorkspaceSkillsPath } from '@craft-agent/shared/workspaces';
4141
import type { LoadedSkill } from '@craft-agent/shared/skills';
42-
import { loadSkill, loadWorkspaceSkills, skillNeedsIconDownload, downloadSkillIcon } from '@craft-agent/shared/skills';
42+
import { loadSkill, loadAllSkills, skillNeedsIconDownload, downloadSkillIcon } from '@craft-agent/shared/skills';
4343
import {
4444
loadStatusConfig,
4545
statusNeedsIconDownload,
@@ -323,7 +323,6 @@ export class ConfigWatcher {
323323
debug('[ConfigWatcher] Setting up workspace watcher for:', this.workspaceDir);
324324
try {
325325
const watcher = watch(this.workspaceDir, { recursive: true }, (eventType, filename) => {
326-
debug('[ConfigWatcher] RAW FILE EVENT:', eventType, filename);
327326
if (!filename) return;
328327

329328
// Normalize path separators
@@ -714,7 +713,7 @@ export class ConfigWatcher {
714713
}
715714

716715
// Notify list change
717-
const allSkills = loadWorkspaceSkills(this.workspaceDir);
716+
const allSkills = loadAllSkills(this.workspaceDir);
718717
this.callbacks.onSkillsListChange?.(allSkills);
719718
} catch (error) {
720719
debug('[ConfigWatcher] Error handling skills dir change:', error);

apps/electron/src/main/sessions.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
migrateLegacyCredentials,
3636
migrateLegacyLlmConnectionsConfig,
3737
migrateOrphanedDefaultConnections,
38+
MODEL_REGISTRY,
3839
type Workspace,
3940
} from '@craft-agent/shared/config'
4041
import { loadWorkspaceConfig } from '@craft-agent/shared/workspaces'
@@ -76,7 +77,7 @@ import { getCredentialManager } from '@craft-agent/shared/credentials'
7677
import { CraftMcpClient } from '@craft-agent/shared/mcp'
7778
import { type Session, type Message, type SessionEvent, type FileAttachment, type StoredAttachment, type SendMessageOptions, IPC_CHANNELS, generateMessageId } from '../shared/types'
7879
import { formatPathsToRelative, formatToolInputPaths, perf, encodeIconToDataUrl, getEmojiIcon, resetSummarizationClient, resolveToolIcon } from '@craft-agent/shared/utils'
79-
import { loadWorkspaceSkills, loadAllSkills, loadSkillBySlug, type LoadedSkill } from '@craft-agent/shared/skills'
80+
import { loadAllSkills, loadSkillBySlug, type LoadedSkill } from '@craft-agent/shared/skills'
8081
import type { ToolDisplayMeta } from '@craft-agent/core/types'
8182
import { getToolIconsDir, isCodexModel, getMiniModel, isAnthropicProvider, DEFAULT_MODEL, DEFAULT_CODEX_MODEL } from '@craft-agent/shared/config'
8283
import type { SummarizeCallback } from '@craft-agent/shared/sources'
@@ -518,6 +519,7 @@ function resolveToolDisplayMeta(
518519
const internalMcpServers: Record<string, Record<string, string>> = {
519520
'session': {
520521
'SubmitPlan': 'Submit Plan',
522+
'call_llm': 'LLM Query',
521523
'config_validate': 'Validate Config',
522524
'skill_validate': 'Validate Skill',
523525
'mermaid_validate': 'Validate Mermaid',
@@ -585,7 +587,7 @@ function resolveToolDisplayMeta(
585587
if (skillSlug) {
586588
// Load skills and find the one being invoked
587589
try {
588-
const skills = loadWorkspaceSkills(workspaceRootPath)
590+
const skills = loadAllSkills(workspaceRootPath)
589591
const skill = skills.find(s => s.slug === skillSlug)
590592
if (skill) {
591593
// Try file-based icon first, fall back to emoji icon from metadata
@@ -1077,8 +1079,8 @@ export class SessionManager {
10771079
onSkillChange: async (slug, skill) => {
10781080
sessionLog.info(`Skill '${slug}' changed:`, skill ? 'updated' : 'deleted')
10791081
// Broadcast updated list to UI
1080-
const { loadWorkspaceSkills } = await import('@craft-agent/shared/skills')
1081-
const skills = loadWorkspaceSkills(workspaceRootPath)
1082+
const { loadAllSkills } = await import('@craft-agent/shared/skills')
1083+
const skills = loadAllSkills(workspaceRootPath)
10821084
this.broadcastSkillsChanged(skills)
10831085
},
10841086

@@ -4889,6 +4891,18 @@ To view this task's output:
48894891
// Format tool input paths to relative for better readability
48904892
const formattedToolInput = formatToolInputPaths(event.input)
48914893

4894+
// Resolve call_llm model short name (e.g., "haiku") to full ID (e.g., "claude-haiku-4-5-20251001")
4895+
// for TurnCard badge display. The LLM sends short names but we want the resolved model shown.
4896+
if (event.toolName === 'mcp__session__call_llm' && formattedToolInput?.model) {
4897+
const shortName = String(formattedToolInput.model)
4898+
const modelDef = MODEL_REGISTRY.find(m => m.id === shortName)
4899+
|| MODEL_REGISTRY.find(m => m.shortName.toLowerCase() === shortName.toLowerCase())
4900+
|| MODEL_REGISTRY.find(m => m.name.toLowerCase() === shortName.toLowerCase())
4901+
if (modelDef) {
4902+
formattedToolInput.model = modelDef.id
4903+
}
4904+
}
4905+
48924906
// Resolve tool display metadata (icon, displayName) for skills/sources
48934907
// Only resolve when we have input (second event for SDK dual-event pattern)
48944908
const workspaceRootPath = managed.workspace.rootPath
@@ -5516,7 +5530,7 @@ To view this task's output:
55165530
*/
55175531
private resolveHookMentions(workspaceRootPath: string, mentions: string[]): { sourceSlugs: string[]; skillSlugs: string[] } | undefined {
55185532
const sources = loadWorkspaceSources(workspaceRootPath)
5519-
const skills = loadWorkspaceSkills(workspaceRootPath)
5533+
const skills = loadAllSkills(workspaceRootPath)
55205534
const sourceSlugs: string[] = []
55215535
const skillSlugs: string[] = []
55225536

apps/electron/src/renderer/App.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { useUpdateChecker } from '@/hooks/useUpdateChecker'
2424
import { NavigationProvider } from '@/contexts/NavigationContext'
2525
import { navigate, routes } from './lib/navigate'
2626
import { stripMarkdown } from './utils/text'
27-
import { extractWorkspaceSlug } from '@craft-agent/shared/utils/workspace'
27+
import { extractWorkspaceSlugFromPath } from '@craft-agent/shared/utils/workspace-slug'
2828
import { initRendererPerf } from './lib/perf'
2929
import {
3030
initializeSessionsAtom,
@@ -186,7 +186,7 @@ export default function App() {
186186
if (!windowWorkspaceId) return null
187187
const workspace = workspaces.find(w => w.id === windowWorkspaceId)
188188
if (!workspace?.rootPath) return windowWorkspaceId // Fallback to ID
189-
return extractWorkspaceSlug(workspace.rootPath, windowWorkspaceId)
189+
return extractWorkspaceSlugFromPath(workspace.rootPath, windowWorkspaceId)
190190
}, [windowWorkspaceId, workspaces])
191191

192192
// LLM connections with authentication status (for provider selection)

apps/electron/src/renderer/components/app-shell/input/FreeFormInput.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
import { Icon_Home, Icon_Folder } from '@craft-agent/ui'
1616

1717
import * as storage from '@/lib/local-storage'
18-
import { extractWorkspaceSlug } from '@craft-agent/shared/utils/workspace'
18+
import { extractWorkspaceSlugFromPath } from '@craft-agent/shared/utils/workspace-slug'
1919

2020
import { Button } from '@/components/ui/button'
2121
import {
@@ -363,7 +363,7 @@ export function FreeFormInput({
363363
// SDK expects "workspaceSlug:skillSlug" format, NOT UUID
364364
const workspaceSlug = React.useMemo(() => {
365365
if (!workspaceRootPath) return workspaceId // Fallback to ID if no path
366-
return extractWorkspaceSlug(workspaceRootPath, workspaceId ?? '')
366+
return extractWorkspaceSlugFromPath(workspaceRootPath, workspaceId ?? '')
367367
}, [workspaceRootPath, workspaceId])
368368

369369
// Shuffle placeholder order once per mount so each session feels fresh

0 commit comments

Comments
 (0)