fix: sync tasks-index format and 3.3.0 protocol for ZCode App session visibility#11
Conversation
There was a problem hiding this comment.
Code Review
This pull request updates the client to support ZCode 3.3.0, including adding the 'auto' mode, making thoughtLevel optional, and updating the session fork handler to use forkedSessionId. It also fixes a critical bug in tasks-index.ts where node:sqlite's DatabaseSync was incorrectly called without preparing statements, and adds comprehensive regression tests. The reviewer feedback highlights that returning only forkedSessionId from fork breaks compatibility with standard ACP clients expecting sessionId, and suggests mapping it back. Additionally, the reviewer recommends optimizing the title extraction logic in session.ts using a regular expression instead of splitting potentially large prompt strings.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| // 3.3.0 returns `forkedSessionId` (not `sessionId`) for the new session id. | ||
| // Register it so subsequent ACP calls targeting the fork can resolve the sid. | ||
| const result = (resp.result ?? {}) as { forkedSessionId?: string }; | ||
| if (result.forkedSessionId) { | ||
| server.sessionMap.set(result.forkedSessionId, result.forkedSessionId); | ||
| } | ||
| log(`session/fork → ${result.forkedSessionId ?? "?"}`); | ||
| return result; |
There was a problem hiding this comment.
The ACP specification for session/fork expects the response to contain sessionId for the newly created session. Returning only forkedSessionId from ZCode 3.3.0 will break compatibility with standard ACP clients (such as the Zed editor) that look for sessionId. We should map forkedSessionId to sessionId in the returned object to ensure compatibility with both the ACP client and the ZCode backend.
const result = (resp.result ?? {}) as { forkedSessionId?: string; sessionId?: string };
const forkedSessionId = result.forkedSessionId ?? result.sessionId;
if (forkedSessionId) {
server.sessionMap.set(forkedSessionId, forkedSessionId);
}
log(`session/fork → ${forkedSessionId ?? "?"}`);
return {
...result,
sessionId: forkedSessionId,
};| const result = (await fork(server, { sessionId: acpSid })) as { | ||
| forkedSessionId?: string; | ||
| }; | ||
| return ok(`✓ forked new session: ${result.forkedSessionId ?? "?"}`); |
There was a problem hiding this comment.
Align the slash command handler to use the mapped sessionId from the fork response, falling back to forkedSessionId if necessary.
const result = (await fork(server, { sessionId: acpSid })) as {
forkedSessionId?: string;
sessionId?: string;
};
return ok(`✓ forked new session: ${result.sessionId ?? result.forkedSessionId ?? "?"}`);| const title = | ||
| text | ||
| .split(/\r\n|\r|\n/) | ||
| .map((l) => l.trim()) | ||
| .find((l) => l.length > 0) | ||
| ?.slice(0, 80) ?? text.slice(0, 80); |
There was a problem hiding this comment.
Splitting and mapping the entire prompt text by newlines to find the first non-empty line is highly inefficient and can cause performance bottlenecks or out-of-memory issues when handling very large prompts (e.g., pasted code or logs). Using a regular expression to match the first non-empty line is much more efficient as it avoids scanning or allocating arrays for the entire string.
const firstLine = text.match(/^[ \t]*(\S[^\r\n]*)/m)?.[1];
const title = (firstLine ?? text).trim().slice(0, 80);
Summary
Bridge-created sessions were invisible in the ZCode App sidebar, and several extension methods diverged from the ZCode 3.3.0 app-server protocol. This PR fixes the tasks-index sync layer and aligns the bridge with 3.3.0's schema.
Changes
tasks-index session sync (root cause of sidebar invisibility)
tasks-index.ts): the original code used Python-stylecon.run(sql, params)/con.get(sql, params), but Node'sDatabaseSynconly exposescon.prepare(sql).run(...)/.get(...). The wrong API threwTypeError, which the try/catch silently swallowed — every session sync was a silent no-op. Fixed to the prepared-statement pattern.model = "GLM-5.2"(bare id), but the App stores"builtin:bigmodel-coding-plan/GLM-5.2"(fullproviderKey/modelIdpath). The App's sidebar filters/grouping excluded rows with the bare format.resolveProviderModel()now reads the provider key fromconfig.jsonand returns the full path.searchable_textempty. The App uses this column for full-text search (built viabuildSearchableTextFromMessages, capped at 200k chars).updateSessionTitle()now accepts asearchableTextparam; the first user prompt is passed as the initial value.3.3.0 protocol alignment
session/forkresponse field: 3.3.0 returnsforkedSessionId, notsessionId. The bridge was reading the wrong field, so forked sessions were never registered insessionMap— any subsequent ACP call targeting the fork failed to resolve. Fixed in bothextensions.tsandslash.ts.automode: 3.3.0's mode enum is["plan","build","edit","yolo","auto"](5 values); the bridge advertised only 4.settings.modecarries onlycurrent(noavailablelist), so the full enum is hardcoded. Added"auto"tobuildModes()andCONFIG_META.session/setThoughtLevel: 3.3.0 marksthoughtLevelas optional (omitting resets to the model default). The bridge threw if it was absent. Now forwards it only when present.Session title
\r\n/\n/\r), truncated to 80 chars. Previouslytext.slice(0, 80)could include newlines from multi-line prompts.Testing
tests/tasks-index.test.tscovering: correct prepared-statement API usage, model format (builtin:bigmodel-coding-plan/GLM-5.2), INSERT OR IGNORE idempotency, searchable_text population + 200k cap, title_overridden respect, and the override-branch searchable_text refresh.vi.mock("node:sqlite")with an in-memory fake DB — no dependency on~/.zcode/or real sqlite, CI-stable.