Skip to content

Releases

v1.20.0

Choose a tag to compare

@github-actions github-actions released this 06 Oct 10:30
bc64f48

Second of the two PRs that replace #91 (docs/DESIGN-attachments.md holds the model this code keeps).

Added

  • ask takes attachments: up to 8 image or PDF files (png, jpg, jpeg, webp, pdf; 10 MB each, 14 MB together —
    this server's cap, under every figure Google publishes; base64 adds a third), sent as inlineData parts before the
    question in the user turn, with or without a Context Cache (a cache hit without attachments still sends the bare
    prompt). They belong to one question, never to the workspace cache. A path must be inside the workspace (the same
    realpath jail as ask_agentic, its secret rules included), a symlink is refused, every file is checked (type, size,
    readability) by stat before any is read, read through an O_NOFOLLOW descriptor whose device and inode must be the
    ones recorded at inspection (a file swapped, replaced, deleted or grown in between is refused), and its first bytes
    must match the declared type. Their tokens are what Gemini counts: one free countTokens call on the parts, whose
    total enters the size preflight, the budget reservation and the TPM throttle (measured 2026-10-01: a 1×1 PNG is
    1090 tokens on gemini-3-flash and 259 on 2.5-flash-lite, so no local figure would do); a count that cannot be
    obtained refuses the call (ATTACHMENT_TOKENS_UNCOUNTED, retryable). Inspection, read and count happen before the
    preflight and every reservation, so a bad file costs nothing: a model without vision is ATTACHMENTS_UNSUPPORTED,
    a bad file ATTACHMENT_INVALID (retryable: false), a request whose conservative size (the attachments encoded, the
    workspace bodies with their markers, the prompt, the system instruction, the JSON framing) would cross Google's
    20 MB inline limit REQUEST_TOO_LARGE, the ask_agentic fallback cannot carry them
    and says so.
    structuredContent.attachments lists what was sent and attachmentTokens what Gemini counted. The latest-vision
    alias finally has an input to see.

Changed

  • The Flash-Lite models count as vision-capable (Google lists image and PDF input on them; countTokens on
    gemini-2.5-flash-lite counted an inline PNG and PDF on 2026-10-01), so attachments work on a Flash-Lite model
    named explicitly or through latest-lite (latest-vision keeps resolving among the reasoning and fast tiers).

v1.19.0

Choose a tag to compare

@github-actions github-actions released this 06 Oct 10:30
273d674

From the 2026-10-01 comparison with Google's own tools and the community (docs/COMPETITION-2026-10-01.md).

Added

  • serviceTier on ask and code (standard | flex), with the operator default GEMINI_CODE_CONTEXT_SERVICE_TIER
    (trimmed, case-insensitive; an invalid value warns on stderr and is standard): flex is Google's half-price tier
    (longer latency). The cost estimate, the usage ledger and the daily budget use the flex price. A request flex refuses
    under load (429/503) is NOT retried by this server — the network retry covers connection failures only; the result
    says RATE_LIMIT / OVERLOADED, retryable: true with Google's retry hint when it sent one (a 429/503 on the
    stale-cache retry is reported as itself), and the client retries; a flex request is sent exactly once — the server's
    own network retry is off for it, every send being billed. Reported as serviceTier on every result of a call that reached model resolution, errors and
    timeouts included (the requested tier until the model is resolved; the earliest validation errors — workspace,
    thinking budget — carry no tier); a failure before any response (a reset, the
    300 s header wait on a queued flex request) is NETWORK_ERROR, retryable: true; the ask_agentic fallback runs standard and says so (progress line, warning,
    serviceTierDowngraded). Not available on the Vertex AI backend (the SDK sends no tier there, js-genai#1468): a
    per-call flex is refused by name, a default of flex runs standard with a startup warning. Registered follow-ups:
    DEF-FLEX-01 (the fetch headers timeout on flex), DEF-FLEX-02 (the usage rows do not record the tier yet).

v1.18.0

Choose a tag to compare

@github-actions github-actions released this 06 Oct 10:30
a1e3ae7

Fixed

  • code reads the configured default model (GEMINI_CODE_CONTEXT_DEFAULT_MODEL, then the credentials profile), as ask and ask_agentic always did; before, a code call without model used latest-pro-thinking whatever the operator configured. A configured default that resolves but cannot reason or think (a flash or lite tier set for cheaper ask calls, or a model without thinking) is replaced by latest-pro-thinking for code, with a warning in the log, a progress line, and configuredModelReplaced in the response, so an audit can tell a fallback from a caller's choice; a configured default that does not resolve at all fails code as it fails ask. A per-call model still wins and is never replaced; an empty model is a schema error. The model descriptions of code and ask_agentic say so. Five tests pin it.

v1.17.1

Choose a tag to compare

@github-actions github-actions released this 01 Oct 07:24
fd02263

Changed

  • Every public claim brought to what the code does (audit of the npm page, 2026-10-01): the context window is 1M tokens, not 2M; the default caching mode is implicit (since v1.14.0) and the README, the caching page, the ask description and the MCP registry manifest say so, with the explicit Context Cache as the documented option (GEMINI_CODE_CONTEXT_CACHING_MODE, cachingMode); the default model is latest-pro-thinking everywhere (--help, the init prompt, README, server.json, getting-started); one measurement with its date and mode replaces the five repeat-query figures; the cost-model numbers are labelled as explicit-mode measurements and the estimator's 25 % fallback is named; the secret denylist is listed as the sandbox checks it (basenames, extensions and directories); code is said to take only a per-call model (it does not read GEMINI_CODE_CONTEXT_DEFAULT_MODEL — registered as a follow-up); onWorkspaceTooLarge is ask's alone; the no-progress threshold is 5; per-tool parameters are listed; the comparison with gemini-mcp-tool is re-measured on 2026-10-01 (its 1.1.8 release and answered issues) and the dead-dependency row is gone; the release-cadence and "production-deployed / daily" sentences are gone; the 48-hour SLA is an aim; one contact domain.
  • README: a claude mcp add one-liner, a supported-platforms note (better-sqlite3 prebuilt bindings), absolute links to the docs so the npm page renders them; docs/configuration.md documents GEMINI_CODE_CONTEXT_AGENTIC_STALL_MS and GEMINI_CODE_CONTEXT_AGENTIC_TOOL_CONCURRENCY; docs/getting-started.md has the one-liner, six tools and no stale timings; docs/cost-model.md, docs/security.md, docs/architecture.md and docs/migration-from-jamubc.md say which mode their figures and uploads belong to.
  • package.json description and keywords describe 1.17 (no 2m-context, no files-api); names the bin path as dist/index.js — the form npm publishes, so a publish prints no auto-correction.

v1.17.0

Choose a tag to compare

@github-actions github-actions released this 01 Oct 07:04
c2d0400

Changed

  • better-sqlite3 13 (was 12.11.1). Its native binding uses N-API and ships inside the package for macOS (arm64, x64),
    Linux glibc and musl (arm64, x64) and Windows (arm64, x64): installing no longer runs prebuild-install or downloads
    a binary, installs with --ignore-scripts work, and a Node.js upgrade no longer breaks the binding ("compiled against
    a different Node.js version"). On any other platform the package still installs, but no binding is compiled any more:
    the server stops when it opens its database, with an error that names the platform and how to build the binding
    (npm run build-release in the better-sqlite3 package directory). The unpacked dependency is 27.3 MB (12.11.1:
    10.4 MB plus the one binary it downloaded). Bundled SQLite 3.53.4.
  • Contributors add an entry to CHANGELOG.md for a user-visible change; the unused @changesets/cli dev
    dependency is gone (the repository never had a .changeset/ setup, and releases take their notes from this file).
  • Releases are staged on npm instead of published directly: the release workflow runs npm stage publish
    (Trusted Publishing, provenance), and a version becomes public only when a maintainer approves it on
    npmjs.com with two-factor authentication; the workflow pins npm 11.20.0 for it (staged publishing needs 11.15.0+).
  • The README opens with the Quantum Media Technologies wordmark and closes with a "Made by" line, both linking to www.qmediat.io/open-source; package.json homepage points there and author is the company line every qmediat package uses.
  • SECURITY.md and docs/security.md name the company security mailbox security@qmediat.io (one address across
    every published qmediat package) next to GitHub's private reporting form.
  • README.md gains a "Trademarks and affiliation" section: Gemini is Google's trademark; this integration is
    independent and not endorsed by Google; use of the Gemini API through it is subject to Google's terms.
  • Development: the project builds with TypeScript 7 (the native compiler). The published JavaScript is byte-identical
    to 1.16.4 except dist/manifest/db.js (the better-sqlite3 error above); the type declarations describe the same types (only the order of union members and properties and the
    quote style differ) and the source maps are regenerated. The TypeScript 7 npm package ships only tsc — no
    tsserver and no JavaScript API — so an editor set to use the workspace TypeScript version cannot load it from
    node_modules; use the editor's bundled one.
  • The release workflow asks npm once whether the version is already public and stops on any answer other than yes
    or no (a registry error used to read as "not public"); a new GitHub Release is a draft until the version is public,
    and a published one is never withdrawn on npm's answer (the run warns); the npm it runs is pinned by version and by
    the sha512 of its tarball; a failed stage is named in the run summary.

v1.16.4

Choose a tag to compare

@github-actions github-actions released this 25 Sep 18:55
3e158f1

Fixed

  • latest-flash resolved to an Interactions-API-only model and every call failed with HTTP 400. Google's
    gemini-omni-* family (gemini-omni-flash-preview, gemini-omni-1.1-flash) is served only by the Interactions
    API, yet ListModels advertises generateContent for it, and the registry's version-descending sort places
    omni ahead of every gemini-N.M-flash. The taxonomy now classifies omni as agent (not drop-in replaceable)
    with an anchored rule, and latest-flash resolves to gemini-flash-latest
    again (Google's rolling alias — gemini-3.8-flash on 2026-09-23). Regression tests cover both IDs. gemini-pro-latest and gemini-flash-latest (Google's rolling aliases) were
    verified against the live API and are unaffected.

Changed

  • server.json (MCP registry manifest) is bumped with the package, and the release workflow refuses a tag whose
    manifest versions differ from package.json.
  • CONTRIBUTING.md and the PR template asked for a changeset; the repository never had a .changeset/ setup and
    the CHANGELOG is the release record, so both now ask for a CHANGELOG entry.

Security

  • Dependency refresh to close every open Dependabot advisory (69 alerts, 25 high — all transitive):
    @modelcontextprotocol/sdk 1.30.0, @google/genai 1.52.0, hono 4.13.8, fast-uri 3.1.8, qs 6.16.0,
    body-parser 2.3.0, ip-address 10.7.2, protobufjs 7.6.6, ws 8.21.3. npm audit reports 0 vulnerabilities.
    The published floors are raised to the versions this release was tested with (@modelcontextprotocol/sdk ^1.30.0,
    @google/genai ^1.52.0). The advisories are in transitive dependencies under caret ranges, so a project that
    already has this package in its lockfile keeps its old tree until it runs npm update (or npm audit fix);
    a fresh install gets the refreshed tree.
  • CI and the release workflow refuse a build with a known high-severity advisory in the shipped dependency tree
    (npm audit --omit=dev --audit-level=high; the dev toolchain stays covered by Dependabot alerts);
    the CI token is read-only (permissions: contents: read).
  • Dependabot security updates are enabled on the repository.
  • SECURITY.md: the supported-versions table still described the pre-1.0 state; it now names the latest 1.x
    release as the supported one and points to GitHub's private vulnerability reporting form first.

v1.16.3 — ask_agentic streaming hotfix (collectStream parts accumulation)

Choose a tag to compare

@qmt qmt released this 01 May 14:25
8f64110

Fixed — ask_agentic streaming hotfix (combined: empty-text terminator + multi-chunk parts fragmentation, HOTFIX)

v1.16.3 ships TWO related fixes for the v1.16.2 streaming regression, folded into a single release because the second was discovered during end-to-end MCP smoke-test of the first.

Fix #1 — empty-text-terminator regression

Reproduction: v1.16.2 ask_agentic returned (model returned empty response) on every tool-using prompt against live Gemini Pro / Flash. Symptoms surfaced minutes after v1.16.2 published, on the first end-to-end smoke test from the global install.

Root cause: Gemini's actual stream protocol for tool-using turns emits TWO chunks:

  • Chunk 1: { candidates: [{ content: { parts: [{ functionCall: {...} }] } }] } (the actual tool call)
  • Chunk 2: { candidates: [{ content: { parts: [{ text: '' }] }, finishReason: 'STOP' }] } (terminator)

v1.16.2's collectStream gate required parts.length > 0 to preserve a chunk's lastCandidates. Chunk 2 has parts.length === 1 (one element with empty text + finishReason), so the gate ACCEPTED it and overwrote chunk 1's functionCall. The agentic loop then saw zero functionCallParts and routed to the final-text path, returning the empty string fallback.

Fix #2 — multi-chunk parts fragmentation (T35)

Reproduction: during end-to-end MCP smoke-test of Fix #1 against live Gemini Pro, single-FC prompts (Tests 1–4) all passed. Test 5 (multi-file prompt: "read package.json, server.json, CHANGELOG.md") failed with Gemini 400: Function call default_api:read_file ... missing thought_signature, position 2.

Root cause: Fix #1 used a content-bearing-parts gate over lastCandidates = chunk.candidates last-write-wins semantics. When Gemini emits PARALLEL functionCall Parts across separate chunks, each chunk's parts overwrite the prior chunk's. The Gemini-3 contract (https://ai.google.dev/gemini-api/docs/thought-signatures) mandates that the thought signature on the FIRST functionCall part be returned in the next-turn replay. Last-write-wins drops chunk-1's signature; iteration 2's generateContent sees a model turn whose first FC has no signature; Gemini 400s. Pre-existing fragility — masked by Fix #1's complete failure mode in v1.16.2 (no FCs ever made it through the loop, so multi-FC was never reached).

Fix: collectStream no longer gates or overwrites — it ACCUMULATES parts across every chunk verbatim. Empty-text terminator parts, parallel functionCall Parts, and standalone signature-bearing parts are all preserved exactly as Gemini emitted them. The candidate scaffold (finishReason, safetyRatings, groundingMetadata, citationMetadata) stays last-write-wins; on stream exit we synthesise the final candidates shape by overlaying the accumulated parts onto the last seen scaffold.

This also closes the documented code tool fragility (T35 secondary): executableCode in chunk 1 + codeExecutionResult in chunk 2 now both reach code.tool.ts's extractor.

Pre-existing in v1.16.2 only. v1.16.1 and earlier used non-streaming generateContent for ask_agentic — never affected by either fix.

Why v1.16.2's review cycle missed Fix #1

The v1.16.2 PR went through the standard review cycle: 3-way Round-1 (gemini + grok + codex), 3-way Round-2 verification, Copilot review, all CI checks green. Gemini Round-1 specifically called out this CLASS of bug (terminator-only chunks dropping functionCall) and a fix was folded — but the fix targeted the SHAPE parts: [] (empty array) and missed the empirical SHAPE parts: [{ text: '' }] (length-1 array with empty content). Unit tests pinned the [] shape but not the [{ text: '' }] shape because no one captured a raw stream from live Gemini before merging.

Why v1.16.3 hotfix-A's first MCP smoke pass missed Fix #2

A single-FC smoke (Test 1) was sufficient to confirm Fix #1 against live Gemini. Multi-FC parallel was filed as T35 (deferred-with-documentation) on the explicit reasoning "currently zero empirical reports". Tests 1–4 of the documented end-to-end smoke plan passed; Test 5 (multi-file) was the first empirical report — fold it in same release.

Process update: the new test/integration/real-gemini.smoke.test.ts (v1.15.3 P10) will be extended in a follow-up PR with env-gated streaming + tool-calling smokes for BOTH single-FC and parallel-multi-FC prompts. A pre-merge live smoke covering both would have caught Fix #2 in seconds.

Coverage

773 tests pass (766 → 773 across the full release, including the R1 + R2 fold-cycle pins documented below — net +7 over v1.16.2). The 5 T35-specific regression pins cover the multi-chunk parts fragmentation cases: parallel-FC across 3 chunks pinning thoughtSignature on FC1, executableCode + codeExecutionResult cross-chunk, standalone signature on empty-text terminator, content.role synthesis, candidates-undefined preservation for naked-text streams.

R1 fold-cycle (4-way: GPT + Gemini + Grok + Copilot)

  • Fold A (GPT F1 / Gemini F1 / Grok F2 — 3-way consensus, /6step PARTIAL Medium): synthesised candidates collapsed to single-element array, narrowing the pre-T35 lastCandidates = chunk.candidates contract. Per-index Map<number, Part[]> accumulation + Map<number, Candidate> scaffold tracking restore multi-candidate preservation for any future caller that sets candidateCount > 1 (today none does — code.tool.ts:727 already iterates ALL candidates so the type narrowing was a real regression-in-waiting).
  • Fold B (GPT F2 — TP Medium): added it('preserves multi-candidate streams (candidateCount>1) with per-index parts accumulation') regression pin.
  • Fold D (Grok F1 — PARTIAL Low): retitled the empty-text-terminator regression test to mention both the hotfix-A history and the T35 strengthening explicitly.
  • Fold G (Copilot — TP Medium): reconciled CHANGELOG T36 status with docs/FOLLOW-UP-PRS.md (T36 is RESOLVED-BY-T35, not "remains tracked").
  • Fold H (Copilot — TP Low): updated ask-agentic.test.ts:2469 comment narrating "content-bearing gate" to describe the actual T35 accumulation behaviour.
  • Accept-deferred C (Grok F3 — TP Medium): end-to-end multi-FC + thoughtSignature test through askAgenticTool.execute. Covered at the collector contract level by stream-collector.test.ts:189. Live-API integration smoke is the documented next-PR scope (see Fix #2 closing notes above).
  • Accept E (Grok F4 — Low): test count "766 → 771" verified empirically post-R1 fold becomes 766 → 772; the "5 regression pins" wording in the original Coverage section refers to T35-specific pins — accurate at that scope.
  • Accept F (Grok F5 — Low): Keep-a-Changelog Unreleased + per-version compare-link not added; project's prior versions (v1.0.0 → v1.16.2) follow the same convention of only emitting the [1.0.0] link.

R2 fold-cycle (3-way: GPT + Gemini + Grok)

  • Fold I (GPT R2 F1 / Gemini R2 F1 — 2-way HIGH consensus, /6step TP Medium): the R1-fold per-index Maps were keyed by the loop variable i (array position) rather than cand.index ?? i. Per @google/genai SDK Candidate.index doc: "The 0-based index of this candidate in the list of generated responses. Useful for distinguishing between multiple candidates when candidate_count > 1." Sparse stream emission like candidates: [{ index: 1, content: ... }] (only index 1, not 0) would have cross-wired that part into bucket 0. Fixed: keys now use cand.index ?? i, and the synth .sort() is now load-bearing because Map insertion order can diverge from ordinal index under sparse emission.
  • Fold J (GPT R2 F2 / Grok R2 F1 — TP Low): added it('keys multi-candidate buckets by Candidate.index, not array position (R2 fold — sparse emission)') regression pin that emits sparse + reverse-Map-insertion-order chunks and asserts ordinal output ordering with no cross-bucket bleed.
  • Accept K (Grok R2 F2 — Nit): the .sort() on Array.from(Map.entries()) was flagged as redundant under R1's keying (Map preserves insertion order from the ascending-i loop). After Fold I the sort BECOMES load-bearing for sparse emission, so it stays — comment added explaining why.

Notes

  • Anyone on v1.16.2 with ask_agentic calls in production should upgrade IMMEDIATELY. Tool-calling iterations were silently failing on every prompt.
  • T35 (true parts accumulation) — closed in this release. T36 (toolCall predicate completeness for future Gemini server-side tools) is also resolved by T35 in v1.16.3 — the accumulation contract preserves any Part shape verbatim (functionCall, toolCall, executableCode, codeExecutionResult, thoughtSignature) without per-shape gating. See docs/FOLLOW-UP-PRS.md "T36" for the full rationale.
  • v1.6/v1.7 streaming refactor track still considered shipped — both fixes are defects in the v1.16.2 agentic-loop refactor, fixed in place.

v1.16.2 — ask_agentic loop streaming + stallMs heartbeat watchdog (T33 TRIM)

Choose a tag to compare

@qmt qmt released this 30 Apr 21:57
371bd8f

Changed — ask_agentic loop streaming + heartbeat-aware stallMs watchdog (T33 TRIM)

ask_agentic's main loop now uses generateContentStream + collectStream instead of non-streaming generateContent. Closes T33 — the last open item in the v1.6/v1.7 streaming refactor track. The post-loop forced-finalization rescue keeps the v1.15.0 P3 apiCallSucceeded non-streaming contract unchanged.

Why now: ask_agentic was the last tool in the codebase still using non-streaming dispatch. Operator consequences:

  1. No heartbeat-aware liveness watchdog. Only wall-clock iterationTimeoutMs was available. A dead socket at second 5 of a 120s-budgeted iter wasted 115 seconds before the wall-clock fired. stallMs (the right knob per ask/code v1.12.0 conventions) closes that gap.
  2. No live thought-chunk progress visibility. During the 30-120s thinking window each iter, operators saw only iter-counter notifications between iters. ask + code already stream thought previews live; this brings parity.
  3. UX inconsistency — every other long-running tool in the codebase streams. ask_agentic was the outlier.

What's new

  • stallMs per-call schema field (1s–10min, optional) — heartbeat-aware stall watchdog that resets on every chunk yielded by generateContentStream (text or thought). Fires ONLY when the stream goes silent for this long. Does NOT fire while the model is actively thinking. Documented as the right knob for "kill dead sockets quickly without penalising deep reasoning."
  • GEMINI_CODE_CONTEXT_AGENTIC_STALL_MS env var — fallback path identical to ask/code (GEMINI_CODE_CONTEXT_ASK_STALL_MS, GEMINI_CODE_CONTEXT_CODE_STALL_MS). Recommended: 60_000 (60s).
  • structuredContent.timeoutKind on errorCode: 'TIMEOUT' — 'stall' | 'total' discriminates which watchdog fired. timeoutMs reports the configured TOTAL wall-clock cap (or null when disabled) — verbatim mirror of ask.tool.ts:1132; the new stallMs sibling field reports the configured stall watchdog (or null when disabled). Wrappers can apply different retry policies based on timeoutKind — 'stall' is usually safe to retry (dead socket); 'total' means raise iterationTimeoutMs or narrow the prompt.
  • Live thinking: … progress events — same UX as ask/code, throttled to ~1.5s by collectStream so the MCP host's progress channel doesn't get flooded on long thinking bursts.

What's preserved verbatim (TRIM scope)

The v1.6/v1.7 plan recommended converting BOTH the loop AND the rescue to streaming. After cross-reviewer consultation (gcc-ask: GO full scope; grok: TRIM), TRIM was adopted:

  • Rescue keeps generateContent (not streaming) — see the dispatch site at src/tools/ask-agentic.tool.ts (search for the rescue's // Each agentic iteration is its own comment). The v1.15.0 P3+P4+P5 apiCallSucceeded contract — Google billed → finalize budget; not billed → cancel — was stabilized through 4 review rounds + /6step cross-corroboration. Streaming the rescue would re-open that recently-stabilized surface for marginal liveness gain on a once-per-call path.
  • Mid-stream failures cannot be retried (Gemini's generateContentStream has no resume) — the v1.6/v1.7 plan §11 explicitly cites this. The rescue's transient-failure budget routing depends on knowing whether the API was billed; partial-stream-then-fail blurs that signal. The loop tolerates a discarded mid-stream failure (next iteration retries the question); the rescue is the last shot before failing the whole call.
  • Content-aware signatureCounts dedupe (v1.16.0) unchanged — collectStream reassembles into the same GenerateContentResponse shape; iterResult.signatures extraction is identical.
  • withNetworkRetry outer wrap unchanged — wraps stream OPENING only, mirroring ask/code.
  • iterDispatched flag (v1.14.x H2) unchanged — set immediately before generateContentStream (which IS the dispatch).
  • createTimeoutController rescue site explicitly keeps stallEnvVar: '' — proves rescue is non-streaming and stall is intentionally disabled there.

Backward compatibility

  • No breaking change. stallMs is an optional new schema field; existing callers that don't set it see identical behavior to v1.16.1.
  • structuredContent shape: timeoutMs continues to report the configured TOTAL wall-clock cap (or null when disabled) — verbatim mirror of ask.tool.ts:1132. New sibling field stallMs reports the configured stall watchdog (or null when disabled). The discriminator for which one fired is the new timeoutKind: 'stall' | 'total' field. Wrappers that branched on timeoutMs > X pre-v1.16.2 see a stable contract — only null (disabled) is a new case, and existing branches handle it correctly because null > X is always false.
  • Rescue path completely unchanged. Operators who hit the rescue see byte-identical behavior to v1.16.1 (convergenceForced, rescueErrorCode, etc.).

PR-Round-1 review folds (gemini Finding #1 + #2)

Round-1 cross-review (gemini-code-context + grok + codex) flagged two empirically verifiable issues. Both folded same PR per workflow:

  • Finding #1 (HIGH) — collectStream candidate-preservation defensive gap (src/tools/shared/stream-collector.ts:174-176). Pre-fix gate was chunk.candidates.length > 0 (outer-array length only). If Gemini's stream protocol ever emits a final terminator shape like { candidates: [{ content: { parts: [] }, finishReason: 'STOP' }] }, the previous chunk's functionCall Part would be silently overwritten — the agentic loop would treat the iteration as final-text and never dispatch the tool. Fixed by adding a parts.length > 0 gate on the inner array. Empirical current-protocol behaviour (functionCall + finishReason packed into one chunk) is preserved either way; this hardens against fragmentation patterns that future Gemini SDK / thinking-mode variants could legitimately introduce. Side-effect on ask / code: lastCandidates.finishReason may now come from a non-final chunk if the final chunk has no parts — observably equivalent for both, since they read accumulated response.text, not candidates[0].finishReason.
  • Finding #2 (MEDIUM) — timeoutMs payload contract divergence from ask/code (src/tools/ask-agentic.tool.ts). Initial commit collapsed timeoutMs to "the limit that fired" (stall value when stall fired). ask.tool.ts:1132 always reports the configured total wall-clock cap regardless of which watchdog fired — the discriminator is timeoutKind. Reverted to ask/code parity: timeoutMs = configured total (or null), stallMs = configured stall (or null), timeoutKind = which one fired.

Two new regression tests pin the fixes:

  • test/unit/stream-collector.test.ts — does not overwrite with terminator-only candidates that have empty parts (v1.16.2 fragmentation guard) — exercises the multi-chunk fragmentation pattern at the collector level.
  • test/unit/ask-agentic.test.ts — dispatches functionCall correctly when stream fragments fc into one chunk and terminator into the next (v1.16.2 PR-Round-1) — exercises the same pattern at the agentic-loop level, asserting that read_file actually dispatches and the loop reaches organic final-text.

Coverage

764 passed | 9 skipped (759 → 762 in initial commit; 762 → 764 after Round-1 folds: added 2 regression tests for the fragmentation guard + adjusted 2 stallMs tests for the corrected timeoutMs semantics). All v1.15.0 rescue tests + v1.16.0 content-aware NO_PROGRESS tests still green — proves the TRIM scope-exclusion held.

Notes

  • The v1.6/v1.7 streaming refactor track is now FULLY SHIPPED across v1.6.0 (T19 timeout + AbortController), v1.7.0 (T20 ask/code streaming + T18 budget + D#7 status UX), and v1.16.2 (T33 ask_agentic loop streaming).
  • T34+ items in docs/FOLLOW-UP-PRS.md are smaller / unrelated.

v1.16.1 — AGENTIC_NO_PROGRESS message enrichment (R2 close)

Choose a tag to compare

@qmt qmt released this 30 Apr 20:54
08403b9

Highlights

Closes the v1.14.4-cycle followup R2 (structuredContent dropped on MCP error channel).

/6step verdict: PARTIAL TP — consumer-side rendering, not our bug. Wire format correctly preserves both structuredContent and isError per MCP spec (verified empirically — CallToolResultSchema defines them as independent optional fields). The "loss" operators saw on 2026-04-29 + 2026-04-30 benchmarks is consumer-side: Claude Code displays message-only on isError results.

Workaround we control: enrich the AGENTIC_NO_PROGRESS message string with iterations + filesRead so they're visible regardless of consumer rendering. structuredContent payload stays intact for any consumer that reads it.

Format

Before (v1.16.0):

... was repeated N times without new file reads between repeats. Returning partial state.

After (v1.16.1):

... was repeated N times without new file reads between repeats (after K iter, X files read). Returning partial state.

Tests

759 passed | 9 skipped (no count change). Regex-based pin assertions added to existing test.

Compatibility

No API/schema/code change beyond the one message-format string + 2 test assertions. structuredContent fields completely unchanged.

Install

npm install -g @qmediat.io/gemini-code-context-mcp@1.16.1

v1.16.0 — content-aware NO_PROGRESS dedupe (P2 Phase B)

Choose a tag to compare

@qmt qmt released this 30 Apr 20:48
42109b9

Highlights

The v1.14.4 threshold bump (3 → 5) was a stop-gap. v1.16.0 ships the planned Phase B content-aware refactor:

Same call signature counts toward the no-progress threshold ONLY if no read_file against an unseen path happened between repeats. If filesReadSet grew between identical signatures, count resets to 1 — the model is exploring, not stuck.

Closes today's empirical regression where Gemini 3 Pro under HIGH thinking re-runs the same grep while ALSO reading new files between iterations. That IS progress; the simple counter (v1.14.4) tripped before maxIterations. Phase B distinguishes "exploring with repeated lookups" from "genuinely stuck".

Changes

  • signatureCounts Map type changed <string, number> → <string, { count: number; lastFilesReadSize: number }>.
  • Per-iter snapshot of filesReadSet.size taken once after tool execution; used to decide reset-vs-increment per signature.
  • AA1 fix folded (3-of-3 cross-corroborated TP HIGH, grok escalated): within-iter parallel duplicates dedupe via Set BEFORE counting. Pathological 5× same signature in one batch counts as +1, not +5.
  • Threshold (5) unchanged. Hard caps unchanged.
  • Error message updated: "... repeated N times without new file reads between repeats".

Tests

759 passed | 9 skipped (was 756 in v1.15.3 — +3 net new test cases). Lint + typecheck green on Node 22 + Node 24.

Compatibility

No API/schema change. Behaviour change observable only in dedupe-trip decision: agentic calls that previously bailed at NO_PROGRESS while reading new files now run to completion (or to maxIterations + rescue).

Future-proof note

read_file is the sole progress oracle because ask_agentic's tool surface is intentionally read-only. When a future PR adds workspace-mutating tools (write_file, terminal exec), the oracle MUST be expanded — guidance documented in code.

Install

npm install -g @qmediat.io/gemini-code-context-mcp@1.16.0