Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cli/release/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codebuff",
"version": "1.0.666",
"version": "1.0.667",
"description": "AI coding agent",
"license": "MIT",
"bin": {
Expand Down
6 changes: 4 additions & 2 deletions cli/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,9 @@ async function main(): Promise<void> {
const wasmBinary = (
globalThis as { __CODEBUFF_TREE_SITTER_WASM_BINARY__?: Uint8Array }
).__CODEBUFF_TREE_SITTER_WASM_BINARY__
const wasmPath = process.env.CODEBUFF_TREE_SITTER_WASM_PATH
const wasmPath = (
globalThis as { __CODEBUFF_TREE_SITTER_WASM_PATH__?: string }
).__CODEBUFF_TREE_SITTER_WASM_PATH__

// Diagnostic dump so CI logs (and bug reports) show exactly what
// the runtime saw when smoke fails. process.execPath, the
Expand All @@ -216,7 +218,7 @@ async function main(): Promise<void> {
`[smoke diag] siblingPath=${siblingPath}\n` +
`[smoke diag] siblingExists=${fs.existsSync(siblingPath)}\n` +
`[smoke diag] dir contents (${dirListing.length}): ${dirListing.slice(0, 30).join(', ')}\n` +
`[smoke diag] env.CODEBUFF_TREE_SITTER_WASM_PATH=${wasmPath ?? '<unset>'}\n` +
`[smoke diag] globalThis wasmPath=${wasmPath ?? '<unset>'}\n` +
`[smoke diag] globalThis wasmBinary bytes=${wasmBinary?.byteLength ?? 0}\n`,
)

Expand Down
7 changes: 7 additions & 0 deletions cli/src/pre-init/tree-sitter-wasm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ if (siblingPath) {
// emscripten, which fs.readFile's it.
process.env.CODEBUFF_TREE_SITTER_WASM_PATH = siblingPath

// Also publish on globalThis so the smoke handler in index.tsx can
// read it without touching process.env (which is gated by the env
// architecture check outside the allowlisted pre-init files).
;(
globalThis as { __CODEBUFF_TREE_SITTER_WASM_PATH__?: string }
).__CODEBUFF_TREE_SITTER_WASM_PATH__ = siblingPath

// Also try the synchronous-bytes path: hand the bytes straight to
// Parser.init({ wasmBinary }) so the SDK doesn't need to round-trip
// through emscripten's path resolution. Both channels feed the same
Expand Down
2 changes: 1 addition & 1 deletion freebuff/cli/release/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "freebuff",
"version": "0.0.74",
"version": "0.0.75",
"description": "The world's strongest free coding agent",
"license": "MIT",
"bin": {
Expand Down
81 changes: 77 additions & 4 deletions packages/code-map/src/init-node.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { execFileSync } from 'child_process'
import * as fs from 'fs'
import * as path from 'path'

Expand All @@ -6,6 +7,22 @@ import { Parser } from 'web-tree-sitter'
const TREE_SITTER_WASM_ENV_VAR = 'CODEBUFF_TREE_SITTER_WASM_PATH'
const WASM_BINARY_GLOBAL_KEY = '__CODEBUFF_TREE_SITTER_WASM_BINARY__'

// Pinned to the version in sdk/package.json. If we bump web-tree-sitter,
// update this too — fetching a wasm built for a different version of the
// runtime would crash with a more confusing error than "missing wasm".
const WEB_TREE_SITTER_VERSION = '0.25.10'

// Self-heal endpoints for users on an old npm wrapper. The wrapper
// auto-updates the binary but not itself, so users on pre-0.0.74
// (freebuff) / pre-1.0.666 (codebuff) wrappers download the new binary
// but their wrapper drops the sibling tree-sitter.wasm we tarball
// alongside it. On missing wasm, the binary fetches it from one of
// these CDNs and caches it next to itself for subsequent runs.
const WASM_DOWNLOAD_URLS = [
`https://unpkg.com/web-tree-sitter@${WEB_TREE_SITTER_VERSION}/tree-sitter.wasm`,
`https://cdn.jsdelivr.net/npm/web-tree-sitter@${WEB_TREE_SITTER_VERSION}/tree-sitter.wasm`,
]

/**
* Override the path to `tree-sitter.wasm` used during {@link initTreeSitterForNode}.
*
Expand All @@ -30,6 +47,56 @@ function getEmbeddedWasmBinary(): Uint8Array | undefined {
)[WASM_BINARY_GLOBAL_KEY]
}

/**
* Synchronously download tree-sitter.wasm from a public CDN and write it
* to `targetPath`. Returns the path on success, null on any failure.
*
* Sync rather than async because this is called from emscripten's
* locateFile callback, which must return a path immediately. We shell
* out to `curl` (built-in on macOS / Linux / Windows 10+); if that
* isn't available or the network's down, the caller falls through to
* the next resolution strategy and ultimately throws a clear error.
*
* Logs a one-line status to stderr so users see what's happening on
* the first run after an old-wrapper auto-update.
*/
function downloadWasmTo(targetPath: string): string | null {
// Print to stderr so it doesn't pollute machine-readable stdout.
// Visible to humans during the (briefly noticeable) first launch.
process.stderr.write(
`[tree-sitter] tree-sitter.wasm missing; downloading to ${targetPath}\n`,
)
for (const url of WASM_DOWNLOAD_URLS) {
try {
execFileSync(
'curl',
[
'-fsSL',
'--connect-timeout',
'10',
'--max-time',
'60',
'-o',
targetPath,
url,
],
{ stdio: 'pipe' },
)
if (fs.existsSync(targetPath) && fs.statSync(targetPath).size > 0) {
process.stderr.write(`[tree-sitter] downloaded ${url}\n`)
return targetPath
}
} catch (err) {
process.stderr.write(
`[tree-sitter] download from ${url} failed: ${
err instanceof Error ? err.message : String(err)
}\n`,
)
}
}
return null
}

function resolveTreeSitterWasm(scriptDir: string): string {
// Only return paths that fs.existsSync confirms — emscripten will
// fs.readFile whatever we hand it, and bunfs internal paths (the
Expand All @@ -56,13 +123,19 @@ function resolveTreeSitterWasm(scriptDir: string): string {
// path later. emscripten calls this locateFile callback during
// Parser.init's async work, by which time execPath has stabilized.
try {
const sibling = path.join(
path.dirname(process.execPath),
'tree-sitter.wasm',
)
const siblingDir = path.dirname(process.execPath)
const sibling = path.join(siblingDir, 'tree-sitter.wasm')
if (fs.existsSync(sibling)) {
return sibling
}

// Self-heal: download from a CDN and cache next to the binary. This
// is the path users on old npm wrappers take — their wrapper
// auto-updated the binary but didn't extract the tarballed wasm
// sibling, so the file isn't there on first run. Once we cache it,
// subsequent runs short-circuit at the existsSync above.
const downloaded = downloadWasmTo(sibling)
if (downloaded) return downloaded
} catch {
// process.execPath may be unavailable in exotic runtimes; fall through.
}
Expand Down
Loading