From 95ced76a3d193ed1264d23b7f00388c619101839 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Thu, 3 Sep 2026 02:29:59 +0800 Subject: [PATCH 1/7] fix: list and count extension-type integrations when a live IDE bridge serves them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CLI presented extension-type integrations as categorically unusable ("not available from the CLI"). That is stale: with VS Code and the Altimate extension open on the project, the workspace engine discovers the bridge, allowlists the extension tools into the session, and serves them — verified end to end (compile + warehouse materialization) on 2026-09-03. - `datamate_manager list-integrations`: with a live bridge for the project, list extension-type rows, mark them "(via VS Code)" and count them; without one, keep them omitted but say they serve while VS Code with the Altimate extension is open on this project — a state, not an impossibility. - Attach announcement: append "Plus N extension tools via the connected VS Code window" when the bridge is serving them. Absent extension tools stay unwarned — that is the normal no-IDE case, never `missing`. - New `liveBridge()` probe in engine-probes: a read-only mirror of the engine's sidecar discovery (cwd prefix match, else the sole live bridge); dead pids are skipped, never GC'd — stale-sidecar cleanup stays with the engine and the extension. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01MaT4GH2aCGEmTg6bBPZBeU --- .../opencode/src/altimate/tools/datamate.ts | 32 ++++--- .../src/altimate/workspace/engine-overlay.ts | 7 +- .../src/altimate/workspace/engine-probes.ts | 51 +++++++++- .../src/altimate/workspace/engine-seams.ts | 1 + .../src/altimate/workspace/engine-types.ts | 8 ++ .../tools/datamate-list-integrations.test.ts | 96 +++++++++++++++++++ .../altimate/workspace/engine-overlay.test.ts | 15 +++ .../altimate/workspace/engine-probes.test.ts | 63 +++++++++++- 8 files changed, 259 insertions(+), 14 deletions(-) create mode 100644 packages/opencode/test/altimate/tools/datamate-list-integrations.test.ts diff --git a/packages/opencode/src/altimate/tools/datamate.ts b/packages/opencode/src/altimate/tools/datamate.ts index 6a0d45a1a..04d17bd4a 100644 --- a/packages/opencode/src/altimate/tools/datamate.ts +++ b/packages/opencode/src/altimate/tools/datamate.ts @@ -15,6 +15,8 @@ import { Log } from "@/altimate/util/log" import { DATAMATE_KEY, readDatamateTransportFromIde } from "../datamate-transport" // altimate_change - workspace mode owns the datamate key import { managedWorkspaceLoaded } from "../workspace/engine-overlay" +// altimate_change - extension-type rows depend on a live IDE bridge +import { liveBridge } from "../workspace/engine-probes" const log = Log.create({ service: "datamate" }) @@ -142,33 +144,41 @@ async function handleListIntegrations() { try { const catalog = await AltimateApi.listIntegrations() // altimate_change start — extension-type integrations are RPC into a live VS - // Code host and cannot work from the CLI. Hide them from this surface (the - // workspace UI still offers them) and say how many were hidden. - const integrations = catalog.filter((i) => i.type !== "extension") + // Code host. With a bridge running for this project they serve from the CLI + // like any other integration, so list them; without one, keep them out of + // the table and say how they come back — not that they never can. E2E + // (2026-09-03) proved the full chain: compile_model/run_model over the + // bridge materialized a model on the warehouse from a headless run. + const extension = catalog.filter((i) => i.type === "extension") + const bridged = extension.length > 0 && liveBridge(projectRoot()) + const integrations = bridged ? catalog : catalog.filter((i) => i.type !== "extension") const hidden = catalog.length - integrations.length - const omitted = - hidden > 0 - ? `${hidden} extension-type integration${hidden === 1 ? " was" : "s were"} omitted — they require a live VS Code bridge and are not available from the CLI.` + const footer = bridged + ? `${extension.length} extension-type integration${extension.length === 1 ? " is" : "s are"} served via the connected VS Code window.` + : hidden > 0 + ? `${hidden} extension-type integration${hidden === 1 ? " was" : "s were"} omitted — they serve while VS Code with the Altimate extension is open on this project.` : "" if (integrations.length === 0) { return { title: hidden > 0 ? `Integrations: none available on the CLI (${hidden} hidden)` : "Integrations: none found", - metadata: { count: 0, hidden }, - output: omitted ? `No integrations available. ${omitted}` : "No integrations available.", + metadata: { count: 0, hidden, bridge: bridged }, + output: footer ? `No integrations available. ${footer}` : "No integrations available.", } } + const extensionIds = new Set(extension.map((i) => i.id)) // altimate_change end const lines = ["ID | Name | Tools", "---|------|------"] for (const i of integrations) { const tools = i.tools?.map((t) => t.key).join(", ") ?? "none" - lines.push(`${i.id} | ${i.name} | ${tools}`) + // altimate_change — mark the rows the IDE bridge serves + lines.push(`${i.id} | ${i.name}${extensionIds.has(i.id) ? " (via VS Code)" : ""} | ${tools}`) } // altimate_change start - if (omitted) lines.push("", `(${omitted})`) + if (footer) lines.push("", `(${footer})`) // altimate_change end return { title: `Integrations: ${integrations.length} available`, - metadata: { count: integrations.length, hidden }, + metadata: { count: integrations.length, hidden, bridge: bridged }, output: lines.join("\n"), } } catch (e) { diff --git a/packages/opencode/src/altimate/workspace/engine-overlay.ts b/packages/opencode/src/altimate/workspace/engine-overlay.ts index 424c0f140..f8cdbb54c 100644 --- a/packages/opencode/src/altimate/workspace/engine-overlay.ts +++ b/packages/opencode/src/altimate/workspace/engine-overlay.ts @@ -43,6 +43,7 @@ import { REPAIRABLE, TOOL_PREFIX, clearsFloor, + describeExtensionServed, describeMissing, describeRefusal, engineEntry, @@ -650,6 +651,10 @@ async function reconcile(sessionID: string, directory: string, state: DirectoryS // tools beyond the allowlist (knowledge, memory) when the workspace enables // them, so the "N of M declared" line counts only the declared ones present. const served = declared ? declared.keys.length - (missing?.length ?? 0) : present.size + // Extension-declared tools appear in `present` only while the engine holds a + // live IDE bridge; when they do they are real capability and the line names + // them, but their absence is the normal no-IDE case, never `missing`. + const extServed = declared ? declared.extensionKeys.filter((k) => present.has(k)).length : 0 const outcome: Outcome = { kind: "attached", available: present.size, @@ -671,7 +676,7 @@ async function reconcile(sessionID: string, directory: string, state: DirectoryS await notify({ title: `Workspace "${workspace.name}"`, message: declared - ? `${served} of ${declared.keys.length} declared integration tools available.${describeMissing(missing ?? [])}` + ? `${served} of ${declared.keys.length} declared integration tools available.${describeMissing(missing ?? [])}${describeExtensionServed(extServed)}` : `${outcome.available} integration tools available.`, variant: missing && missing.length > 0 ? "warning" : "info", }) diff --git a/packages/opencode/src/altimate/workspace/engine-probes.ts b/packages/opencode/src/altimate/workspace/engine-probes.ts index 80676f2c4..08fe9aaa6 100644 --- a/packages/opencode/src/altimate/workspace/engine-probes.ts +++ b/packages/opencode/src/altimate/workspace/engine-probes.ts @@ -2,7 +2,9 @@ // // Everything that asks the outside world a question: the binary, its // version, the workspace allowlist, and the user-facing surfaces. -import { statSync } from "fs" +import { readFileSync, readdirSync, statSync } from "fs" +import { homedir } from "os" +import { isAbsolute, join, relative, resolve } from "path" import launch from "cross-spawn" import { which as whichBinary } from "@opencode-ai/core/util/which" import { AltimateApi } from "@/altimate/api/client" @@ -164,6 +166,53 @@ export async function declaredBounded(workspaceId: string): Promise { + const rel = relative(resolve(folder), resolve(cwd)) + return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel)) + } + if (bridges.some((folders) => folders.some(within))) return true + return bridges.length === 1 +} + +function pidAlive(pid: number): boolean { + try { + process.kill(pid, 0) + return true + } catch (err) { + // EPERM is a live process owned by someone else. + return (err as NodeJS.ErrnoException).code === "EPERM" + } +} + export async function notify(toast: Toast): Promise { if (syncInternals.notify) return syncInternals.notify(toast) try { diff --git a/packages/opencode/src/altimate/workspace/engine-seams.ts b/packages/opencode/src/altimate/workspace/engine-seams.ts index 9d2f0ad99..093a02b97 100644 --- a/packages/opencode/src/altimate/workspace/engine-seams.ts +++ b/packages/opencode/src/altimate/workspace/engine-seams.ts @@ -27,6 +27,7 @@ export const syncInternals: { versionOf?: (bin: string) => Promise fingerprint?: (bin: string) => string | null declared?: (workspaceId: string) => Promise + liveBridge?: (cwd: string) => boolean notify?: (toast: Toast) => Promise printLine?: (line: string) => void /** Install-offer seams (see engine-offer.ts). */ diff --git a/packages/opencode/src/altimate/workspace/engine-types.ts b/packages/opencode/src/altimate/workspace/engine-types.ts index e88d8c3b5..070c26dc6 100644 --- a/packages/opencode/src/altimate/workspace/engine-types.ts +++ b/packages/opencode/src/altimate/workspace/engine-types.ts @@ -214,6 +214,14 @@ export function describeMissing(missing: string[]): string { return ` Declared but not available: ${shown}${more}.` } +/** Extension-declared tools a connected IDE bridge is actually serving. Zero + * is the normal no-IDE case and says nothing — absent extension tools are + * expected, not missing, so they never join `describeMissing`. */ +export function describeExtensionServed(count: number): string { + if (count === 0) return "" + return ` Plus ${count} extension tool${count === 1 ? "" : "s"} via the connected VS Code window.` +} + /** What each outcome MEANS, as tables over the whole union: a new variant * fails to compile until every table names it, and the safe answer is false. */ export const SERVING: Record = { diff --git a/packages/opencode/test/altimate/tools/datamate-list-integrations.test.ts b/packages/opencode/test/altimate/tools/datamate-list-integrations.test.ts new file mode 100644 index 000000000..a41df5409 --- /dev/null +++ b/packages/opencode/test/altimate/tools/datamate-list-integrations.test.ts @@ -0,0 +1,96 @@ +// altimate_change - new file +// +// `datamate_manager list-integrations` and extension-type rows. Extension +// integrations are RPC into a live VS Code host: with a bridge running for +// this project they serve from the CLI like any other integration; without +// one they are dormant, not impossible. The listing must say which of those +// is true rather than a blanket "not available from the CLI". +import { afterEach, describe, expect, test } from "bun:test" +import { initTool } from "../tool-fixture" +import { tmpdir } from "../../fixture/fixture" +import { Instance } from "../../../src/project/instance" +import { AltimateApi } from "../../../src/altimate/api/client" +import { DatamateManagerTool } from "../../../src/altimate/tools/datamate" +import { syncInternals } from "../../../src/altimate/workspace/engine-seams" +import { SessionID, MessageID } from "../../../src/session/schema" + +const ctx = { + sessionID: SessionID.make("ses_test"), + messageID: MessageID.make("msg_test"), + callID: "call_test", + agent: "build", + abort: AbortSignal.any([]), + messages: [], + metadata: () => {}, + ask: async () => {}, +} + +const CATALOG = [ + { id: "snowflake", name: "Snowflake", type: "connection", tools: [{ key: "execute_query" }] }, + { + id: "power-user-for-dbt", + name: "Power User for dbt", + type: "extension", + tools: [{ key: "compile_model" }, { key: "run_model" }], + }, +] + +const originalList = AltimateApi.listIntegrations +const originalIsConfigured = AltimateApi.isConfigured + +afterEach(() => { + ;(AltimateApi as unknown as { listIntegrations: typeof originalList }).listIntegrations = originalList + ;(AltimateApi as unknown as { isConfigured: typeof originalIsConfigured }).isConfigured = originalIsConfigured + for (const key of Object.keys(syncInternals)) delete (syncInternals as Record)[key] +}) + +function serveCatalog(catalog: unknown[] = CATALOG): void { + ;(AltimateApi as unknown as { isConfigured: () => Promise }).isConfigured = async () => true + ;(AltimateApi as unknown as { listIntegrations: () => Promise }).listIntegrations = async () => catalog +} + +async function list() { + await using tmp = await tmpdir() + return await Instance.provide({ + directory: tmp.path, + fn: async () => { + const tool = await initTool(DatamateManagerTool) + return tool.execute({ operation: "list-integrations" }, ctx as any) + }, + }) +} + +describe("datamate_manager list-integrations and extension-type integrations", () => { + test("without a bridge they are omitted with copy that says how they come back", async () => { + serveCatalog() + syncInternals.liveBridge = () => false + const result = await list() + expect(result.metadata).toMatchObject({ count: 1, hidden: 1, bridge: false }) + expect(result.output).toContain("snowflake | Snowflake") + expect(result.output).not.toContain("power-user-for-dbt") + expect(result.output).toContain( + "1 extension-type integration was omitted — they serve while VS Code with the Altimate extension is open on this project.", + ) + expect(result.output).not.toContain("not available from the CLI") + }) + + test("with a live bridge they are listed, marked, and counted", async () => { + serveCatalog() + syncInternals.liveBridge = () => true + const result = await list() + expect(result.metadata).toMatchObject({ count: 2, hidden: 0, bridge: true }) + expect(result.output).toContain("power-user-for-dbt | Power User for dbt (via VS Code) | compile_model, run_model") + expect(result.output).toContain("1 extension-type integration is served via the connected VS Code window.") + expect(result.title).toBe("Integrations: 2 available") + }) + + test("a catalog with no extension rows never probes for a bridge", async () => { + serveCatalog([CATALOG[0]]) + syncInternals.liveBridge = () => { + throw new Error("must not probe") + } + const result = await list() + expect(result.metadata).toMatchObject({ count: 1, hidden: 0, bridge: false }) + expect(result.output).not.toContain("extension-type") + }) +}) diff --git a/packages/opencode/test/altimate/workspace/engine-overlay.test.ts b/packages/opencode/test/altimate/workspace/engine-overlay.test.ts index af96b1f6a..3b4ee48d6 100644 --- a/packages/opencode/test/altimate/workspace/engine-overlay.test.ts +++ b/packages/opencode/test/altimate/workspace/engine-overlay.test.ts @@ -425,6 +425,21 @@ describe("beforeTurn — what a turn boundary does", () => { expect(h.toasts[0].message).toBe("2 integration tools available.") }) + test("extension tools a live bridge serves are announced; absent ones are expected, not missing", async () => { + const h = install({ + tools: { datamate_dbt_build_model: {}, datamate_dbt_compile_model: {}, datamate_get_projects: {} }, + declared: { keys: ["dbt_build_model", "dbt_compile_model"], extensionKeys: ["get_projects", "run_model"] }, + }) + await beforeTurn("s1") + // `run_model` is declared extension-type but no bridge serves it: that is + // the normal no-IDE case, so the outcome stays clean and unwarned. + expect(settledOutcome("s1")).toEqual({ kind: "attached", available: 3, declared: 2, missing: [] }) + expect(h.toasts[0].message).toBe( + "2 of 2 declared integration tools available. Plus 1 extension tool via the connected VS Code window.", + ) + expect(h.toasts[0].variant).toBe("info") + }) + test("the inventory is announced per session, not per process", async () => { const h = install({}) await beforeTurn("s1") diff --git a/packages/opencode/test/altimate/workspace/engine-probes.test.ts b/packages/opencode/test/altimate/workspace/engine-probes.test.ts index a5edccfd8..2bb14068f 100644 --- a/packages/opencode/test/altimate/workspace/engine-probes.test.ts +++ b/packages/opencode/test/altimate/workspace/engine-probes.test.ts @@ -6,7 +6,7 @@ import { describe, expect, test } from "bun:test" import { chmodSync, mkdtempSync, statSync, utimesSync, writeFileSync } from "node:fs" import os from "node:os" import path from "node:path" -import { fingerprint, versionOf } from "../../../src/altimate/workspace/engine-probes" +import { fingerprint, liveBridge, versionOf } from "../../../src/altimate/workspace/engine-probes" const posix = process.platform !== "win32" @@ -59,3 +59,64 @@ describe("versionOf", () => { expect(await versionOf(path.join(os.tmpdir(), "definitely-not-here-" + process.pid))).toBeNull() }) }) + +describe("liveBridge", () => { + // A pid above any realistic pid_max, so the liveness check reports it dead — + // the same trick the engine's own discovery tests use. + const DEAD_PID = 2 ** 31 - 1 + + function sidecars(entries: Record): string { + const dir = mkdtempSync(path.join(os.tmpdir(), "bridge-sidecar-")) + for (const [name, data] of Object.entries(entries)) { + writeFileSync(path.join(dir, name), JSON.stringify(data)) + } + return dir + } + + test("a live bridge recording this directory is found, from the folder or below it", () => { + const cwd = mkdtempSync(path.join(os.tmpdir(), "bridge-ws-")) + // A second live bridge keeps the single-bridge fallback out of play, so + // these assertions exercise the cwd match alone. + const dir = sidecars({ + "a.json": { socketPath: "/tmp/a.sock", workspaceFolders: [cwd], pid: process.pid }, + "b.json": { socketPath: "/tmp/b.sock", workspaceFolders: ["/somewhere/else"], pid: process.pid }, + }) + expect(liveBridge(cwd, dir)).toBe(true) + expect(liveBridge(path.join(cwd, "models", "staging"), dir)).toBe(true) + // A sibling directory that merely shares the prefix string is not within. + expect(liveBridge(cwd + "-other", dir)).toBe(false) + }) + + test("a dead bridge is skipped and its sidecar is left for the engine to GC", () => { + const cwd = mkdtempSync(path.join(os.tmpdir(), "bridge-ws-")) + const dir = sidecars({ + "a.json": { socketPath: "/tmp/a.sock", workspaceFolders: [cwd], pid: DEAD_PID }, + }) + expect(liveBridge(cwd, dir)).toBe(false) + expect(statSync(path.join(dir, "a.json")).isFile()).toBe(true) + }) + + test("the sole live bridge counts even for an unrelated directory; two decline to guess", () => { + const cwd = mkdtempSync(path.join(os.tmpdir(), "bridge-ws-")) + const one = sidecars({ + "a.json": { socketPath: "/tmp/a.sock", workspaceFolders: ["/somewhere/else"], pid: process.pid }, + }) + expect(liveBridge(cwd, one)).toBe(true) + const two = sidecars({ + "a.json": { socketPath: "/tmp/a.sock", workspaceFolders: ["/somewhere/else"], pid: process.pid }, + "b.json": { socketPath: "/tmp/b.sock", workspaceFolders: ["/somewhere/third"], pid: process.pid }, + }) + expect(liveBridge(cwd, two)).toBe(false) + }) + + test("garbage is not a bridge: no dir, no socketPath, unparseable JSON", () => { + const cwd = mkdtempSync(path.join(os.tmpdir(), "bridge-ws-")) + expect(liveBridge(cwd, path.join(os.tmpdir(), "no-such-dir-" + process.pid))).toBe(false) + const dir = sidecars({ + "no-sock.json": { workspaceFolders: [cwd], pid: process.pid }, + }) + writeFileSync(path.join(dir, "broken.json"), "{not json") + writeFileSync(path.join(dir, "not-a-sidecar.txt"), "ignored") + expect(liveBridge(cwd, dir)).toBe(false) + }) +}) From 1e44c32adc918633b7dbac110b0920c2c144e0b9 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Thu, 3 Sep 2026 12:22:51 +0800 Subject: [PATCH 2/7] fix: probe the bridge with the engine's actual spawn cwd (codex review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `connectLocal` spawns the engine with the instance directory as its cwd, so discovery matches against that — not the Git root `projectRoot()` returns. Launched from a subdirectory with two live bridges, the probe declined (no cwd match, fallback refused) while the engine connected. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01MaT4GH2aCGEmTg6bBPZBeU --- packages/opencode/src/altimate/tools/datamate.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/altimate/tools/datamate.ts b/packages/opencode/src/altimate/tools/datamate.ts index 04d17bd4a..41afc5145 100644 --- a/packages/opencode/src/altimate/tools/datamate.ts +++ b/packages/opencode/src/altimate/tools/datamate.ts @@ -150,7 +150,12 @@ async function handleListIntegrations() { // (2026-09-03) proved the full chain: compile_model/run_model over the // bridge materialized a model on the warehouse from a headless run. const extension = catalog.filter((i) => i.type === "extension") - const bridged = extension.length > 0 && liveBridge(projectRoot()) + // Probe with Instance.directory, not projectRoot(): the engine is spawned + // with the instance directory as its cwd (mcp/index.ts connectLocal), so + // this is the cwd its own discovery will match. Probing the Git root + // instead diverged when altimate-code was launched from a subdirectory + // with more than one live bridge. (codex review) + const bridged = extension.length > 0 && liveBridge(Instance.directory) const integrations = bridged ? catalog : catalog.filter((i) => i.type !== "extension") const hidden = catalog.length - integrations.length const footer = bridged From b56be7db1fff6ae679f7df807d05f42c8bf4d889 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Thu, 3 Sep 2026 12:38:39 +0800 Subject: [PATCH 3/7] fix: harden the bridge probe and honor the engine's effective spawn cwd (review round) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review-round fixes from codex r2, CodeRabbit, kilo and cubic: - Probe with the cwd the engine is actually spawned with: `MCP.entry` (new, exposing the connect path's own config precedence) resolves a local entry's `cwd` against the instance directory, mirroring `connectLocal` exactly; falls back to the instance directory. (codex) - Validate the sidecar's folders shape: a non-array degrades to a folderless live bridge instead of throwing out of the probe and breaking the whole listing. (coderabbit, cubic) - ".." only counts as a complete path component: a child literally named "..cache" is inside the workspace folder. (cubic) - A recorded pid must be a positive integer to be probed — kill(0) and kill(-1) signal process groups and would read garbage pids as alive. (cubic) - Include `extServed` in the attach-announcement dedup signature so an equal-count tool swap that changes only the extension share still re-announces. (coderabbit, kilo, cubic) Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01MaT4GH2aCGEmTg6bBPZBeU --- .../opencode/src/altimate/tools/datamate.ts | 30 ++++++++++++---- .../src/altimate/workspace/engine-overlay.ts | 5 ++- .../src/altimate/workspace/engine-probes.ts | 23 +++++++++--- packages/opencode/src/mcp/index.ts | 13 +++++++ .../altimate/workspace/engine-probes.test.ts | 35 +++++++++++++++++++ packages/opencode/test/session/prompt.test.ts | 1 + .../test/session/snapshot-tool-race.test.ts | 1 + 7 files changed, 96 insertions(+), 12 deletions(-) diff --git a/packages/opencode/src/altimate/tools/datamate.ts b/packages/opencode/src/altimate/tools/datamate.ts index 41afc5145..c4ba3ed4a 100644 --- a/packages/opencode/src/altimate/tools/datamate.ts +++ b/packages/opencode/src/altimate/tools/datamate.ts @@ -1,3 +1,4 @@ +import path from "path" import z from "zod" import { Tool } from "../../tool/tool" import { AltimateApi } from "../api/client" @@ -140,6 +141,23 @@ async function handleList() { } } +// altimate_change start — the cwd the shared datamate entry would be spawned +// with, mirroring connectLocal: a local entry's `cwd` resolved against the +// instance directory, else the instance directory. Runtime-added entries win +// over file config (MCP.entry uses the connect path's own precedence). Falls +// back to the instance directory when no MCP runtime is up — that is +// connectLocal's default too. +async function engineSpawnCwd(): Promise { + try { + const entry = await MCP.entry(DATAMATE_KEY) + if (entry && entry.type === "local" && entry.cwd) return path.resolve(Instance.directory, entry.cwd) + } catch (e) { + log.warn("could not read the datamate MCP entry for the bridge probe", { error: String(e) }) + } + return Instance.directory +} +// altimate_change end + async function handleListIntegrations() { try { const catalog = await AltimateApi.listIntegrations() @@ -150,12 +168,12 @@ async function handleListIntegrations() { // (2026-09-03) proved the full chain: compile_model/run_model over the // bridge materialized a model on the warehouse from a headless run. const extension = catalog.filter((i) => i.type === "extension") - // Probe with Instance.directory, not projectRoot(): the engine is spawned - // with the instance directory as its cwd (mcp/index.ts connectLocal), so - // this is the cwd its own discovery will match. Probing the Git root - // instead diverged when altimate-code was launched from a subdirectory - // with more than one live bridge. (codex review) - const bridged = extension.length > 0 && liveBridge(Instance.directory) + // Probe with the cwd the engine is actually spawned with: connectLocal + // resolves a local entry's `cwd` against the instance directory and falls + // back to the instance directory itself — not the Git root projectRoot() + // returns. Probing anything else diverged whenever the effective spawn cwd + // and the probe input straddled different sidecars. (codex review, r1+r2) + const bridged = extension.length > 0 && liveBridge(await engineSpawnCwd()) const integrations = bridged ? catalog : catalog.filter((i) => i.type !== "extension") const hidden = catalog.length - integrations.length const footer = bridged diff --git a/packages/opencode/src/altimate/workspace/engine-overlay.ts b/packages/opencode/src/altimate/workspace/engine-overlay.ts index f8cdbb54c..521ef8c2e 100644 --- a/packages/opencode/src/altimate/workspace/engine-overlay.ts +++ b/packages/opencode/src/altimate/workspace/engine-overlay.ts @@ -663,7 +663,10 @@ async function reconcile(sessionID: string, directory: string, state: DirectoryS const rec = record(sessionID, outcome) // Keyed on the workspace too: a re-link with an identical inventory is still // a new verdict the user should hear. - const signature = `attached:${workspace.key}:${outcome.available}:${outcome.declared ?? "?"}:${(missing ?? []).join(",")}` + // extServed is part of what the user hears, so it is part of the signature: + // an equal-count tool swap that changes only the extension share must still + // re-announce. (bot review) + const signature = `attached:${workspace.key}:${outcome.available}:${outcome.declared ?? "?"}:${(missing ?? []).join(",")}:${extServed}` if (rec.announced === signature) return rec.announced = signature log.info("workspace engine attached", { diff --git a/packages/opencode/src/altimate/workspace/engine-probes.ts b/packages/opencode/src/altimate/workspace/engine-probes.ts index 08fe9aaa6..255aadeb5 100644 --- a/packages/opencode/src/altimate/workspace/engine-probes.ts +++ b/packages/opencode/src/altimate/workspace/engine-probes.ts @@ -4,7 +4,7 @@ // version, the workspace allowlist, and the user-facing surfaces. import { readFileSync, readdirSync, statSync } from "fs" import { homedir } from "os" -import { isAbsolute, join, relative, resolve } from "path" +import { isAbsolute, join, relative, resolve, sep } from "path" import launch from "cross-spawn" import { which as whichBinary } from "@opencode-ai/core/util/which" import { AltimateApi } from "@/altimate/api/client" @@ -184,9 +184,20 @@ export function liveBridge(cwd: string, dir: string = join(homedir(), ".altimate workspaceFolders?: string[] pid?: number } - if (!data.socketPath) continue - if (typeof data.pid === "number" && !pidAlive(data.pid)) continue - bridges.push(data.workspaceFolders ?? []) + if (typeof data.socketPath !== "string" || !data.socketPath) continue + // A sidecar without a pid counts as live, matching the engine's own + // discovery; a recorded pid disqualifies unless it names a live real + // process. Non-positive pids never do — kill(0)/kill(-1) probe process + // groups, which would read any garbage pid as alive. (bot review) + if (typeof data.pid === "number" && !(Number.isInteger(data.pid) && data.pid > 0 && pidAlive(data.pid))) + continue + // Validate the folders shape: this is an unvalidated JSON file, and a + // non-array here must degrade to "live bridge, no recorded folders", + // not throw out of the probe. (bot review) + const folders = Array.isArray(data.workspaceFolders) + ? data.workspaceFolders.filter((f): f is string => typeof f === "string") + : [] + bridges.push(folders) } catch { // An unreadable sidecar is not a live bridge. } @@ -197,7 +208,9 @@ export function liveBridge(cwd: string, dir: string = join(homedir(), ".altimate if (bridges.length === 0) return false const within = (folder: string) => { const rel = relative(resolve(folder), resolve(cwd)) - return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel)) + // ".." must be a complete path component: a child literally named + // "..cache" yields rel "..cache", which is inside. (bot review) + return rel === "" || (rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel)) } if (bridges.some((folders) => folders.some(within))) return true return bridges.length === 1 diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 1d2dc2102..498569630 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -326,6 +326,12 @@ export interface Interface { readonly supportsOAuth: (mcpName: string) => Effect.Effect readonly hasStoredTokens: (mcpName: string) => Effect.Effect readonly getAuthStatus: (mcpName: string) => Effect.Effect + // altimate_change start — the effective config for one key: runtime-added + // entries win over file config, the same precedence the connect path uses. + // Lets callers mirror connectLocal's spawn environment (notably `cwd`) + // without re-deriving the merge. + readonly entry: (name: string) => Effect.Effect + // altimate_change end } export class Service extends Context.Service()("@opencode/MCP") {} @@ -1372,6 +1378,8 @@ export const layer = Layer.effect( supportsOAuth, hasStoredTokens, getAuthStatus, + // altimate_change — see Interface.entry + entry: getMcpConfig, }) }), ) @@ -1404,6 +1412,11 @@ export async function status() { export async function tools() { return runMcp((svc) => svc.tools()) } +// altimate_change start — see Interface.entry +export async function entry(name: string) { + return runMcp((svc) => svc.entry(name)) +} +// altimate_change end export async function add(name: string, mcp: ConfigMCPV1.Info) { return runMcp((svc) => svc.add(name, mcp)) } diff --git a/packages/opencode/test/altimate/workspace/engine-probes.test.ts b/packages/opencode/test/altimate/workspace/engine-probes.test.ts index 2bb14068f..9806ecbd1 100644 --- a/packages/opencode/test/altimate/workspace/engine-probes.test.ts +++ b/packages/opencode/test/altimate/workspace/engine-probes.test.ts @@ -83,6 +83,9 @@ describe("liveBridge", () => { }) expect(liveBridge(cwd, dir)).toBe(true) expect(liveBridge(path.join(cwd, "models", "staging"), dir)).toBe(true) + // A child literally named "..cache" is inside: ".." only counts as a + // complete path component. + expect(liveBridge(path.join(cwd, "..cache"), dir)).toBe(true) // A sibling directory that merely shares the prefix string is not within. expect(liveBridge(cwd + "-other", dir)).toBe(false) }) @@ -96,6 +99,16 @@ describe("liveBridge", () => { expect(statSync(path.join(dir, "a.json")).isFile()).toBe(true) }) + test("a non-positive or non-integer pid is never alive — kill(0)/kill(-1) probe process groups", () => { + const cwd = mkdtempSync(path.join(os.tmpdir(), "bridge-ws-")) + for (const pid of [0, -1, 1.5]) { + const dir = sidecars({ + "a.json": { socketPath: "/tmp/a.sock", workspaceFolders: [cwd], pid }, + }) + expect(liveBridge(cwd, dir)).toBe(false) + } + }) + test("the sole live bridge counts even for an unrelated directory; two decline to guess", () => { const cwd = mkdtempSync(path.join(os.tmpdir(), "bridge-ws-")) const one = sidecars({ @@ -119,4 +132,26 @@ describe("liveBridge", () => { writeFileSync(path.join(dir, "not-a-sidecar.txt"), "ignored") expect(liveBridge(cwd, dir)).toBe(false) }) + + test("a malformed folders shape degrades to a folderless live bridge, never a throw", () => { + const cwd = mkdtempSync(path.join(os.tmpdir(), "bridge-ws-")) + // Object-valued and mixed-type folders come from an unvalidated JSON file. + const dir = sidecars({ + "obj.json": { socketPath: "/tmp/a.sock", workspaceFolders: {}, pid: process.pid }, + }) + expect(liveBridge(cwd, dir)).toBe(true) // sole live bridge, no folders — fallback + const mixed = sidecars({ + "mixed.json": { socketPath: "/tmp/a.sock", workspaceFolders: [42, cwd], pid: process.pid }, + "other.json": { socketPath: "/tmp/b.sock", workspaceFolders: ["/somewhere/else"], pid: process.pid }, + }) + expect(liveBridge(cwd, mixed)).toBe(true) // the string folder still matches + }) + + test("a sidecar without a pid counts as live, matching the engine's discovery", () => { + const cwd = mkdtempSync(path.join(os.tmpdir(), "bridge-ws-")) + const dir = sidecars({ + "no-pid.json": { socketPath: "/tmp/a.sock", workspaceFolders: [cwd] }, + }) + expect(liveBridge(cwd, dir)).toBe(true) + }) }) diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index 92fe3f813..cf14e14c2 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -140,6 +140,7 @@ const mcp = Layer.succeed( supportsOAuth: () => Effect.succeed(false), hasStoredTokens: () => Effect.succeed(false), getAuthStatus: () => Effect.succeed("not_authenticated" as const), + entry: () => Effect.succeed(undefined), }), ) diff --git a/packages/opencode/test/session/snapshot-tool-race.test.ts b/packages/opencode/test/session/snapshot-tool-race.test.ts index f1990520a..c39aa1b83 100644 --- a/packages/opencode/test/session/snapshot-tool-race.test.ts +++ b/packages/opencode/test/session/snapshot-tool-race.test.ts @@ -53,6 +53,7 @@ const mcp = Layer.succeed( supportsOAuth: () => Effect.succeed(false), hasStoredTokens: () => Effect.succeed(false), getAuthStatus: () => Effect.succeed("not_authenticated" as const), + entry: () => Effect.succeed(undefined), }), ) From 179e433a247ccef9a6372c540dc7c1e6969e3479 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Thu, 3 Sep 2026 12:47:44 +0800 Subject: [PATCH 4/7] fix: reject corrupt sidecar records the probe cannot verify (codex r3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - A present pid must be a positive live integer: the bridge extension always writes one, so a string, null, or object pid is a corrupt record — and unlike the engine, the probe has no connection attempt behind it to catch a bad guess. Absent pid stays live (engine parity). - Folder entries must be nonempty absolute strings: resolve("") is the process cwd, so an empty or relative entry could spuriously match and bypass the two-bridge decline. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01MaT4GH2aCGEmTg6bBPZBeU --- .../src/altimate/workspace/engine-probes.ts | 19 +++++++++++------- .../altimate/workspace/engine-probes.test.ts | 20 ++++++++++++++++--- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/packages/opencode/src/altimate/workspace/engine-probes.ts b/packages/opencode/src/altimate/workspace/engine-probes.ts index 255aadeb5..e14382bcd 100644 --- a/packages/opencode/src/altimate/workspace/engine-probes.ts +++ b/packages/opencode/src/altimate/workspace/engine-probes.ts @@ -186,16 +186,21 @@ export function liveBridge(cwd: string, dir: string = join(homedir(), ".altimate } if (typeof data.socketPath !== "string" || !data.socketPath) continue // A sidecar without a pid counts as live, matching the engine's own - // discovery; a recorded pid disqualifies unless it names a live real - // process. Non-positive pids never do — kill(0)/kill(-1) probe process - // groups, which would read any garbage pid as alive. (bot review) - if (typeof data.pid === "number" && !(Number.isInteger(data.pid) && data.pid > 0 && pidAlive(data.pid))) + // discovery. A PRESENT pid must be a live real process: the bridge + // extension always writes a positive integer, so a string, null, or + // non-positive value is a corrupt record, not a legacy shape — and + // unlike the engine, this probe has no connection attempt behind it + // to catch a bad guess. kill(0)/kill(-1) probe process groups, which + // would read garbage pids as alive. (codex r3, cubic) + if ("pid" in data && !(typeof data.pid === "number" && Number.isInteger(data.pid) && data.pid > 0 && pidAlive(data.pid))) continue // Validate the folders shape: this is an unvalidated JSON file, and a - // non-array here must degrade to "live bridge, no recorded folders", - // not throw out of the probe. (bot review) + // non-array must degrade to "live bridge, no recorded folders", not + // throw out of the probe. Only nonempty absolute strings survive — + // resolve("") is the process cwd, so an empty or relative entry would + // spuriously match and bypass the two-bridge decline. (codex r3) const folders = Array.isArray(data.workspaceFolders) - ? data.workspaceFolders.filter((f): f is string => typeof f === "string") + ? data.workspaceFolders.filter((f): f is string => typeof f === "string" && isAbsolute(f)) : [] bridges.push(folders) } catch { diff --git a/packages/opencode/test/altimate/workspace/engine-probes.test.ts b/packages/opencode/test/altimate/workspace/engine-probes.test.ts index 9806ecbd1..ecae76454 100644 --- a/packages/opencode/test/altimate/workspace/engine-probes.test.ts +++ b/packages/opencode/test/altimate/workspace/engine-probes.test.ts @@ -99,16 +99,30 @@ describe("liveBridge", () => { expect(statSync(path.join(dir, "a.json")).isFile()).toBe(true) }) - test("a non-positive or non-integer pid is never alive — kill(0)/kill(-1) probe process groups", () => { + test("a present-but-invalid pid is a corrupt record, never a live pid-less sidecar", () => { const cwd = mkdtempSync(path.join(os.tmpdir(), "bridge-ws-")) - for (const pid of [0, -1, 1.5]) { + // Non-positive and non-integer: kill(0)/kill(-1) probe process groups. + // Wrong type entirely: the extension always writes a positive integer. + for (const pid of [0, -1, 1.5, "1234", null, {}] as unknown[]) { const dir = sidecars({ - "a.json": { socketPath: "/tmp/a.sock", workspaceFolders: [cwd], pid }, + "a.json": { socketPath: "/tmp/a.sock", workspaceFolders: [cwd], pid: pid as number }, }) expect(liveBridge(cwd, dir)).toBe(false) } }) + test("empty and relative folder strings are dropped — resolve('') is the process cwd", () => { + const cwd = mkdtempSync(path.join(os.tmpdir(), "bridge-ws-")) + // Bridge A is malformed, bridge B is live elsewhere: A must not match via + // resolve("") and must not defeat the two-bridge decline. + const dir = sidecars({ + "a.json": { socketPath: "/tmp/a.sock", workspaceFolders: ["", "relative/dir"], pid: process.pid }, + "b.json": { socketPath: "/tmp/b.sock", workspaceFolders: ["/somewhere/else"], pid: process.pid }, + }) + expect(liveBridge(process.cwd(), dir)).toBe(false) + expect(liveBridge(cwd, dir)).toBe(false) + }) + test("the sole live bridge counts even for an unrelated directory; two decline to guess", () => { const cwd = mkdtempSync(path.join(os.tmpdir(), "bridge-ws-")) const one = sidecars({ From a87dd0bbe058100e7963dd476b906ba23fe55c03 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Thu, 3 Sep 2026 12:54:17 +0800 Subject: [PATCH 5/7] fix: require fully qualified folder paths on Windows (codex r4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit isAbsolute("\repo") is true on win32, but resolve() completes a drive-relative path with the process's current drive — a corrupt sidecar entry could match any cwd on that drive and defeat the two-bridge decline. Folders now require a drive-qualified or UNC prefix on Windows; POSIX keeps plain isAbsolute. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01MaT4GH2aCGEmTg6bBPZBeU --- .../src/altimate/workspace/engine-probes.ts | 20 +++++++++++++++---- .../altimate/workspace/engine-probes.test.ts | 17 +++++++++++++++- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/altimate/workspace/engine-probes.ts b/packages/opencode/src/altimate/workspace/engine-probes.ts index e14382bcd..0d1700e25 100644 --- a/packages/opencode/src/altimate/workspace/engine-probes.ts +++ b/packages/opencode/src/altimate/workspace/engine-probes.ts @@ -196,11 +196,12 @@ export function liveBridge(cwd: string, dir: string = join(homedir(), ".altimate continue // Validate the folders shape: this is an unvalidated JSON file, and a // non-array must degrade to "live bridge, no recorded folders", not - // throw out of the probe. Only nonempty absolute strings survive — - // resolve("") is the process cwd, so an empty or relative entry would - // spuriously match and bypass the two-bridge decline. (codex r3) + // throw out of the probe. Only fully qualified strings survive — + // anything resolve() would complete from the process's own cwd or + // drive could spuriously match and bypass the two-bridge decline. + // (codex r3+r4) const folders = Array.isArray(data.workspaceFolders) - ? data.workspaceFolders.filter((f): f is string => typeof f === "string" && isAbsolute(f)) + ? data.workspaceFolders.filter((f): f is string => typeof f === "string" && qualifiedFolder(f)) : [] bridges.push(folders) } catch { @@ -221,6 +222,17 @@ export function liveBridge(cwd: string, dir: string = join(homedir(), ".altimate return bridges.length === 1 } +/** A recorded folder must be fully qualified. On Windows, drive-relative + * paths like "\repo" count as absolute to Node, but resolve() completes them + * with the process's CURRENT drive — so a corrupt entry could match any cwd + * on that drive and defeat the two-bridge decline. Drive-qualified (C:\ or + * C:/) or UNC (\\server\share) only; POSIX keeps plain isAbsolute. The + * platform parameter exists for tests. (codex r4) */ +export function qualifiedFolder(f: string, win: boolean = process.platform === "win32"): boolean { + if (!f) return false + return win ? /^([a-zA-Z]:[\\/]|\\\\)/.test(f) : isAbsolute(f) +} + function pidAlive(pid: number): boolean { try { process.kill(pid, 0) diff --git a/packages/opencode/test/altimate/workspace/engine-probes.test.ts b/packages/opencode/test/altimate/workspace/engine-probes.test.ts index ecae76454..40c56bcde 100644 --- a/packages/opencode/test/altimate/workspace/engine-probes.test.ts +++ b/packages/opencode/test/altimate/workspace/engine-probes.test.ts @@ -6,7 +6,7 @@ import { describe, expect, test } from "bun:test" import { chmodSync, mkdtempSync, statSync, utimesSync, writeFileSync } from "node:fs" import os from "node:os" import path from "node:path" -import { fingerprint, liveBridge, versionOf } from "../../../src/altimate/workspace/engine-probes" +import { fingerprint, liveBridge, qualifiedFolder, versionOf } from "../../../src/altimate/workspace/engine-probes" const posix = process.platform !== "win32" @@ -111,6 +111,21 @@ describe("liveBridge", () => { } }) + test("a recorded folder must be fully qualified on Windows — drive-relative resolves onto the current drive", () => { + // win branch + expect(qualifiedFolder("C:\\ws", true)).toBe(true) + expect(qualifiedFolder("c:/ws", true)).toBe(true) + expect(qualifiedFolder("\\\\server\\share\\ws", true)).toBe(true) + expect(qualifiedFolder("\\repo", true)).toBe(false) + expect(qualifiedFolder("\\", true)).toBe(false) + expect(qualifiedFolder("C:relative", true)).toBe(false) + expect(qualifiedFolder("", true)).toBe(false) + // posix branch + expect(qualifiedFolder("/home/ws", false)).toBe(true) + expect(qualifiedFolder("relative/dir", false)).toBe(false) + expect(qualifiedFolder("", false)).toBe(false) + }) + test("empty and relative folder strings are dropped — resolve('') is the process cwd", () => { const cwd = mkdtempSync(path.join(os.tmpdir(), "bridge-ws-")) // Bridge A is malformed, bridge B is live elsewhere: A must not match via From 40378b3d076cad5f19777f39f5c08ccf67ab7236 Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Thu, 3 Sep 2026 13:00:38 +0800 Subject: [PATCH 6/7] chore: wrap the MCP.entry service wiring in a start/end marker pair The single-line annotation did not cover the code line, failing the strict marker guard for upstream-shared files. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01MaT4GH2aCGEmTg6bBPZBeU --- packages/opencode/src/mcp/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 498569630..d5dbb65b9 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -1378,8 +1378,9 @@ export const layer = Layer.effect( supportsOAuth, hasStoredTokens, getAuthStatus, - // altimate_change — see Interface.entry + // altimate_change start — see Interface.entry entry: getMcpConfig, + // altimate_change end }) }), ) From 098bed80e609c09ee2c250068754412d0b1bac3a Mon Sep 17 00:00:00 2001 From: ralphstodomingo Date: Thu, 3 Sep 2026 13:23:15 +0800 Subject: [PATCH 7/7] fix: require complete UNC components in qualifiedFolder (codex r5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resolve("\\\\") is "C:\\" and resolve("\\\\server") is "C:\\server" — the current drive again, not a UNC device. The UNC alternative now requires nonempty server and share components. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01MaT4GH2aCGEmTg6bBPZBeU --- packages/opencode/src/altimate/workspace/engine-probes.ts | 8 +++++--- .../test/altimate/workspace/engine-probes.test.ts | 6 ++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/altimate/workspace/engine-probes.ts b/packages/opencode/src/altimate/workspace/engine-probes.ts index 0d1700e25..02b1603f6 100644 --- a/packages/opencode/src/altimate/workspace/engine-probes.ts +++ b/packages/opencode/src/altimate/workspace/engine-probes.ts @@ -226,11 +226,13 @@ export function liveBridge(cwd: string, dir: string = join(homedir(), ".altimate * paths like "\repo" count as absolute to Node, but resolve() completes them * with the process's CURRENT drive — so a corrupt entry could match any cwd * on that drive and defeat the two-bridge decline. Drive-qualified (C:\ or - * C:/) or UNC (\\server\share) only; POSIX keeps plain isAbsolute. The - * platform parameter exists for tests. (codex r4) */ + * C:/) or complete UNC only: a UNC value needs nonempty server AND share + * components — resolve("\\\\") is "C:\\" and resolve("\\\\server") is + * "C:\\server", both on the current drive again. POSIX keeps plain + * isAbsolute. The platform parameter exists for tests. (codex r4+r5) */ export function qualifiedFolder(f: string, win: boolean = process.platform === "win32"): boolean { if (!f) return false - return win ? /^([a-zA-Z]:[\\/]|\\\\)/.test(f) : isAbsolute(f) + return win ? /^([a-zA-Z]:[\\/]|[\\/]{2}[^\\/]+[\\/]+[^\\/]+)/.test(f) : isAbsolute(f) } function pidAlive(pid: number): boolean { diff --git a/packages/opencode/test/altimate/workspace/engine-probes.test.ts b/packages/opencode/test/altimate/workspace/engine-probes.test.ts index 40c56bcde..9354fe4da 100644 --- a/packages/opencode/test/altimate/workspace/engine-probes.test.ts +++ b/packages/opencode/test/altimate/workspace/engine-probes.test.ts @@ -116,8 +116,14 @@ describe("liveBridge", () => { expect(qualifiedFolder("C:\\ws", true)).toBe(true) expect(qualifiedFolder("c:/ws", true)).toBe(true) expect(qualifiedFolder("\\\\server\\share\\ws", true)).toBe(true) + expect(qualifiedFolder("\\\\server\\share", true)).toBe(true) expect(qualifiedFolder("\\repo", true)).toBe(false) expect(qualifiedFolder("\\", true)).toBe(false) + // Incomplete pseudo-UNC: resolve("\\\\") is "C:\\", resolve("\\\\server") + // is "C:\\server" — the current drive again, not a UNC device. + expect(qualifiedFolder("\\\\", true)).toBe(false) + expect(qualifiedFolder("\\\\server", true)).toBe(false) + expect(qualifiedFolder("\\\\server\\", true)).toBe(false) expect(qualifiedFolder("C:relative", true)).toBe(false) expect(qualifiedFolder("", true)).toBe(false) // posix branch