Skip to content

Commit e46ffce

Browse files
guIDE Devcursoragent
andcommitted
v0.4.97: restore Cloud compact tool examples; parse Qwen function= XML.
Keep JSON path; accept and strip function=/parameter= dialect. CI this tag: Windows CUDA only. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent f6fd90d commit e46ffce

7 files changed

Lines changed: 234 additions & 29 deletions

File tree

‎.github/workflows/build.yml‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ jobs:
6363
echo "release_name=guIDE v$VERSION" >> "$GITHUB_OUTPUT"
6464
6565
# ─── Windows CPU ────────────────────────────────────────────
66+
# TEMP 0.4.97: operator asked Windows CUDA only (faster release). Re-enable full matrix later.
6667
build-windows-cpu:
68+
if: false
6769
needs: prepare
6870
runs-on: windows-2022
6971
steps:
@@ -258,6 +260,7 @@ jobs:
258260

259261
# ─── Windows Lite CUDA (stripped extras, isolated SKU) ─
260262
build-windows-lite:
263+
if: false
261264
needs: prepare
262265
runs-on: windows-2022
263266
steps:
@@ -347,6 +350,7 @@ jobs:
347350

348351
# ─── Linux CPU ──────────────────────────────────────────────
349352
build-linux-cpu:
353+
if: false
350354
needs: prepare
351355
runs-on: ubuntu-latest
352356
steps:
@@ -408,6 +412,7 @@ jobs:
408412

409413
# ─── Linux CUDA ─────────────────────────────────────────────
410414
build-linux-cuda:
415+
if: false
411416
needs: prepare
412417
runs-on: ubuntu-latest
413418
steps:
@@ -476,6 +481,7 @@ jobs:
476481

477482
# ─── macOS ──────────────────────────────────────────────────
478483
build-mac:
484+
if: false
479485
needs: prepare
480486
runs-on: macos-latest
481487
steps:
@@ -558,18 +564,13 @@ jobs:
558564

559565
# ─── GitHub Release ─────────────────────────────────────────
560566
release:
561-
needs: [prepare, build-windows-cpu, build-windows-cuda, build-windows-lite, build-linux-cpu, build-linux-cuda, build-mac]
567+
needs: [prepare, build-windows-cuda]
562568
runs-on: ubuntu-latest
563569
if: >-
564570
always() &&
565571
!cancelled() &&
566572
needs.prepare.result == 'success' &&
567-
needs.build-windows-cpu.result == 'success' &&
568573
needs.build-windows-cuda.result == 'success' &&
569-
needs.build-windows-lite.result == 'success' &&
570-
needs.build-linux-cpu.result == 'success' &&
571-
needs.build-linux-cuda.result == 'success' &&
572-
needs.build-mac.result == 'success' &&
573574
(github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/v'))
574575
steps:
575576
- name: Download all artifacts
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# v0.4.97 — Cloud tool dialect: keep JSON + accept Qwen `<function=`
2+
3+
Status: **SHIPPED v0.4.97** — operator go 2026-09-26; CI Windows CUDA only for this tag.
4+
5+
## Problem (user words)
6+
7+
Cipher sometimes emits JSON tool calls, sometimes Qwen-native `<function=…>` / `<parameter=…>`. Last fail leaked XML and stopped with `toolsParsed=0`. Shipping takes hours — this deploy must cover both dialects and the prompt hole from the last ship.
8+
9+
## Log evidence (v0.4.96)
10+
11+
```
12+
20:48:55 round end iter=0 stopReason=stop toolsParsed=0 proseChars=142
13+
20:48:55 ai-chat DONE: toolCallCount=0
14+
```
15+
16+
UI showed mangled `<function=list_directory>` / `<parameter=…>`. Not Ship A (no tools had run). Not length-stop.
17+
18+
## Correction (operator): we did have tool examples
19+
20+
Correct. Through **v0.4.89** Secrypt Cloud used `mcpToolServer.getCompactToolHint(...)`, which starts with `getAgentToolPromptHeader({ compact: true })` — JSON fence format + concrete `read_file` / `write_file` / `edit_file` / `run_command` examples.
21+
22+
**v0.4.94** (`c9ffc6f`) replaced that with bare `buildCloudToolListing` — name list only, **no** format header/examples. **v0.4.95** Ship E only truncated descriptions to 100 chars on that already-stripped listing.
23+
24+
| Piece | v0.4.89 Cloud (working pattern) | Live 0.4.96 |
25+
|--------|----------------------------------|-------------|
26+
| Tool catalog | `getCompactToolHint` + compactDescriptions | `buildCloudToolListing` name list |
27+
| JSON format + examples | **Yes** (compact header) | **Removed in 0.4.94** |
28+
| Desc length | compactDescriptions | slice 100 |
29+
30+
So the examples were stripped in **0.4.94**, not invented-missing forever. Model fell back to Qwen `<function=` — host still does not parse that dialect.
31+
32+
## Proposed change (both required in one ship)
33+
34+
### A — Prompt: restore Cloud compact tool catalog (prefer JSON)
35+
36+
Replace `buildCloudToolListing(...)` with the **0.4.89 Secrypt path**:
37+
38+
```js
39+
toolPrompt = mcpToolServer.getCompactToolHint('default', {
40+
toolDefs: filteredDefs,
41+
planning: mode.planning,
42+
compactDescriptions: true,
43+
}).join('');
44+
```
45+
46+
That restores the JSON fence header + examples. Settings tool filter (`filteredDefs`) stays. Do **not** document `<function=` as preferred.
47+
48+
Optional: drop the separate thin `buildCloudToolListing` or keep it unused — prefer delete if nothing else imports it.
49+
50+
### B — Parser: additive dialects (never break JSON)
51+
52+
In `tools/toolParser.js` **add** Method for:
53+
54+
```
55+
<function=TOOL_NAME>
56+
<parameter=KEY>VALUE</parameter>
57+
…
58+
</function>
59+
```
60+
61+
- Map tool name via existing aliases / VALID_TOOLS.
62+
- Params object from parameter tags (trim values).
63+
- Run **alongside** existing JSON / fence / tool_code paths (order: existing first or XML first — either OK if dedupe by signature).
64+
- `looksLikeToolAttempt`: also true on `/<function\s*=/i`.
65+
- `stripToolCallText` + streaming hold: remove `<function=…</function>` (and orphan open) so UI never shows markup.
66+
67+
Malformed mashups (screenshot: command fragment between tags): parse what is well-formed; if zero valid tools but `<function=` present → existing repair retry (`looksLikeToolAttempt` true).
68+
69+
### C — Tests (must pass before tag)
70+
71+
1. Pure JSON fence → still parses (regression).
72+
2. Pure `<function=list_directory><parameter=dirPath>.</parameter></function>` → one call.
73+
3. Screenshot-shaped mangled string → `looksLikeToolAttempt` true; strip leaves prose; best-effort parse ≥0 tools.
74+
4. Mixed prose + JSON unchanged.
75+
5. Cloud tool prompt (via getCompactToolHint) includes ```json and example `{"tool":`.
76+
6. Regression: Settings-filtered toolDefs still only list enabled tools.
77+
78+
### D — Ship
79+
80+
Bump **0.4.97**, commit, tag, CI green.
81+
82+
## What this ship does NOT do
83+
84+
- Force tools after clean prose.
85+
- Change 27B/2B weights or sampler.
86+
- Teach the model to prefer `<function=` (JSON remains the instructed format).
87+
- Touch FileShot / other platforms’ 2B chatbot prompts.
88+
89+
## Validation
90+
91+
- Unit suite above.
92+
- Manual after install: SaaS-style agent prompt → log `toolsParsed≥1`, tool cards, **no** raw `<function=` in chat; a second turn that already used JSON still works.
93+
94+
## Execute order
95+
96+
1. A prompt header restore
97+
2. B parser + strip + looksLike
98+
3. C tests
99+
4. D release

‎cloudAgenticChat.js‎

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,6 @@ const { resolveCloudOutputTokens, secryptQualitySampling } = require('./cloudLLM
2929
/** After tool results (or a system repair) are already in history — do not re-ask the original user text. */
3030
const NEXT_FROM_HISTORY = '';
3131

32-
function buildCloudToolListing(toolDefs) {
33-
let prompt = '## Tools\n';
34-
for (const tool of toolDefs || []) {
35-
if (!tool || tool.name === 'generate_image') continue;
36-
const params = tool.parameters
37-
? Object.entries(tool.parameters)
38-
.map(([n, i]) => `${n}:${i.type}${i.required ? '*' : ''}`)
39-
.join(', ')
40-
: '';
41-
const desc = String(tool.description || '').replace(/\s+/g, ' ').trim().slice(0, 100);
42-
prompt += `**${tool.name}**(${params})${desc ? ` — ${desc}` : ''}\n`;
43-
}
44-
return prompt;
45-
}
46-
4732
function createThinkTagSplitter({ onThinking, onContent }) {
4833
const OPEN = '<think>';
4934
const CLOSE = '</think>';
@@ -162,7 +147,14 @@ async function runCloudAgenticChat({
162147

163148
let toolPrompt = '';
164149
if (mode.toolsActive) {
165-
toolPrompt = buildCloudToolListing(filteredDefs);
150+
// Restore compact catalog with JSON fence examples (pre-0.4.94). Settings filter stays via toolDefs.
151+
toolPrompt = mcpToolServer
152+
.getCompactToolHint('default', {
153+
toolDefs: filteredDefs,
154+
planning: mode.planning,
155+
compactDescriptions: true,
156+
})
157+
.join('');
166158
if (enableSubAgents && !isSecryptCloud && toolPrompt) {
167159
toolPrompt +=
168160
'\n- **spawn_subagent** — Delegate a focused sub-task to an isolated sub-agent (local model only; unavailable in cloud mode).';
@@ -561,4 +553,4 @@ async function runCloudAgenticChat({
561553
};
562554
}
563555

564-
module.exports = { runCloudAgenticChat, createThinkTagSplitter, buildCloudToolListing };
556+
module.exports = { runCloudAgenticChat, createThinkTagSplitter };

‎package.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "guide-ide",
3-
"version": "0.4.96",
3+
"version": "0.4.97",
44
"description": "guIDE Local First IDE",
55
"main": "electron-main.js",
66
"scripts": {
@@ -17,7 +17,7 @@
1717
"release:minor": "npm version minor && git push && git push --tags",
1818
"release:major": "npm version major && git push && git push --tags",
1919
"postinstall": "cd frontend && npm install",
20-
"test": "node tools/apiReturnSmoke.test.js && node tools/writeTodos.test.js && node tools/modelProfiles.test.js && node tools/samplingProfile.test.js && node tools/agentGenOptionsAudit.test.js && node tools/todoDigestHelpers.test.js && node tools/modelDetection.test.js && node tools/kvEstimate.test.js && node tools/gemma4ChannelRouting.test.js && node tools/planModePrompt.test.js && node tools/toolPromptBudget.test.js && node tools/promptDedupe.test.js && node tools/fenceFlush.test.js && node tools/streamRouting.test.js && node tools/agentFenceRouting.test.js && node tools/catchUpProse.test.js && node tools/fileTokenBatch.test.js && node tools/repairToolCalls.test.js && node tools/markdownFenceSplit.test.js && node tools/jsonStringChunkDecode.test.js && node tools/partialWriteFileExtract.test.js && node tools/streamingBlockPreserve.test.js && node tools/streamingToolFilter.test.js && node tools/secryptCloudModel.test.js && node tools/cloudContextFit.test.js && node tools/cloudContextSummarize.test.js && node tools/cloudTightPrompt.test.js && node tools/mediaArchMatrix.test.js && node tools/mediaAux.test.js && node tools/mediaAssets.test.js && node tools/gguf5dCompat.test.js && node tools/mediaEngine.test.js && node tools/mediaSmoke.test.js && node tools/optionalComponents.test.js && node tools/updateVariant.test.js && node tools/torOnDemand.test.js",
20+
"test": "node tools/apiReturnSmoke.test.js && node tools/writeTodos.test.js && node tools/modelProfiles.test.js && node tools/samplingProfile.test.js && node tools/agentGenOptionsAudit.test.js && node tools/todoDigestHelpers.test.js && node tools/modelDetection.test.js && node tools/kvEstimate.test.js && node tools/gemma4ChannelRouting.test.js && node tools/planModePrompt.test.js && node tools/toolPromptBudget.test.js && node tools/promptDedupe.test.js && node tools/fenceFlush.test.js && node tools/streamRouting.test.js && node tools/agentFenceRouting.test.js && node tools/catchUpProse.test.js && node tools/fileTokenBatch.test.js && node tools/repairToolCalls.test.js && node tools/markdownFenceSplit.test.js && node tools/jsonStringChunkDecode.test.js && node tools/partialWriteFileExtract.test.js && node tools/streamingBlockPreserve.test.js && node tools/streamingToolFilter.test.js && node tools/secryptCloudModel.test.js && node tools/cloudContextFit.test.js && node tools/cloudContextSummarize.test.js && node tools/qwenFunctionXml.test.js && node tools/cloudTightPrompt.test.js && node tools/mediaArchMatrix.test.js && node tools/mediaAux.test.js && node tools/mediaAssets.test.js && node tools/gguf5dCompat.test.js && node tools/mediaEngine.test.js && node tools/mediaSmoke.test.js && node tools/optionalComponents.test.js && node tools/updateVariant.test.js && node tools/torOnDemand.test.js",
2121
"test:media-e2e": "node tools/mediaE2E.test.js"
2222
},
2323
"keywords": [

‎tools/cloudTightPrompt.test.js‎

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ const assert = require('assert');
44
const { buildCloudSystemPrompt } = require('../chatEngine');
55
const { MCPToolServer } = require('../mcpToolServer');
66
const { CloudLLMService, resolveCloudOutputTokens, secryptQualitySampling } = require('../cloudLLMService');
7-
const { buildCloudToolListing } = require('../cloudAgenticChat');
87
const { getCloudAgentSystemPrompt } = require('../agentModeResolver');
98
const { resolveSlashSkill, listSkills, formatSkillsHelp } = require('../skills/registry');
109

@@ -22,9 +21,17 @@ assert.ok(cloudIdentity.includes('general-purpose'));
2221
assert.ok(cloudIdentity.includes('call that tool in this response'));
2322

2423
const twoTools = defs.filter((d) => d.name === 'read_file' || d.name === 'web_search');
25-
const listing = buildCloudToolListing(twoTools);
24+
const listing = server
25+
.getCompactToolHint('default', {
26+
toolDefs: twoTools,
27+
planning: false,
28+
compactDescriptions: true,
29+
})
30+
.join('');
2631
assert.ok(listing.includes('read_file'));
2732
assert.ok(listing.includes('web_search'));
33+
assert.ok(listing.includes('```json'), 'compact catalog must include JSON fence examples');
34+
assert.ok(listing.includes('"tool"'), 'compact catalog must show tool JSON shape');
2835
assert.ok(!listing.includes('Pattern —'));
2936
assert.ok(!listing.includes('Common patterns'));
3037

@@ -36,6 +43,7 @@ const prompt = buildCloudSystemPrompt({
3643
assert.ok(prompt.includes('read_file'));
3744
assert.ok(prompt.includes('web_search'));
3845
assert.ok(prompt.includes('general-purpose'));
46+
assert.ok(prompt.includes('```json'));
3947

4048
const llm = new CloudLLMService();
4149
assert.strictEqual(llm._getModelContextLimit('secrypt', 'cipher-quality'), 24576);
@@ -74,9 +82,14 @@ assert.ok(goal.text.toLowerCase().includes('goal'));
7482
const help = resolveSlashSkill('/skills');
7583
assert.strictEqual(help.sendToModel, false);
7684

77-
const noImage = buildCloudToolListing(defs.filter((d) => d.name === 'generate_image' || d.name === 'read_file'));
85+
const noImage = server
86+
.getCompactToolHint('default', {
87+
toolDefs: defs.filter((d) => d.name === 'generate_image' || d.name === 'read_file'),
88+
planning: false,
89+
compactDescriptions: true,
90+
})
91+
.join('');
7892
assert.ok(noImage.includes('read_file'));
79-
assert.ok(!noImage.includes('generate_image'));
8093

8194
console.log('cloudTightPrompt+skills OK', {
8295
listingChars: listing.length,

‎tools/qwenFunctionXml.test.js‎

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
'use strict';
2+
3+
const assert = require('assert');
4+
const {
5+
parseToolCalls,
6+
looksLikeToolAttempt,
7+
stripToolCallText,
8+
} = require('./toolParser');
9+
10+
const jsonFence = '```json\n{"tool":"read_file","params":{"filePath":"src/index.js"}}\n```';
11+
const jsonCalls = parseToolCalls(jsonFence);
12+
assert.strictEqual(jsonCalls.length, 1);
13+
assert.strictEqual(jsonCalls[0].tool, 'read_file');
14+
assert.strictEqual(jsonCalls[0].params.filePath, 'src/index.js');
15+
16+
const cleanQwen =
17+
'Love this challenge.\n'
18+
+ '<function=list_directory>\n'
19+
+ '<parameter=dirPath>\n.\n</parameter>\n'
20+
+ '</function>';
21+
assert.ok(looksLikeToolAttempt(cleanQwen));
22+
const qwenCalls = parseToolCalls(cleanQwen);
23+
assert.strictEqual(qwenCalls.length, 1, JSON.stringify(qwenCalls));
24+
assert.strictEqual(qwenCalls[0].tool, 'list_directory');
25+
assert.strictEqual(qwenCalls[0].params.dirPath, '.');
26+
const stripped = stripToolCallText(cleanQwen);
27+
assert.ok(!stripped.includes('<function='), stripped);
28+
assert.ok(stripped.includes('Love this challenge'));
29+
30+
const mangled =
31+
'Love this challenge. Let me first look.\n'
32+
+ '<function=list_directory>\n'
33+
+ '<parameter=dirPath>\n'
34+
+ '.\n'
35+
+ '</parameter>\n'
36+
+ '</function>-" && (git --version 2>nul || echo no-git) </parameter>\n'
37+
+ '<parameter=reason>\n'
38+
+ 'Check Node, npm, and git versions to pick a compatible stack.\n'
39+
+ '</parameter>\n'
40+
+ '</function>';
41+
assert.ok(looksLikeToolAttempt(mangled));
42+
const mangledCalls = parseToolCalls(mangled);
43+
assert.ok(mangledCalls.some((c) => c.tool === 'list_directory'), JSON.stringify(mangledCalls));
44+
const mangledStrip = stripToolCallText(mangled);
45+
assert.ok(!mangledStrip.includes('<function=list_directory>'), mangledStrip);
46+
47+
const runCmd =
48+
'<function=run_command>\n'
49+
+ '<parameter=command>\nnode --version\n</parameter>\n'
50+
+ '</function>';
51+
const runCalls = parseToolCalls(runCmd);
52+
assert.strictEqual(runCalls.length, 1);
53+
assert.strictEqual(runCalls[0].tool, 'run_command');
54+
assert.ok(String(runCalls[0].params.command).includes('node --version'));
55+
56+
console.log('qwenFunctionXml.test.js OK', {
57+
json: jsonCalls[0].tool,
58+
qwen: qwenCalls[0].tool,
59+
mangled: mangledCalls.map((c) => c.tool),
60+
});

0 commit comments

Comments
 (0)