Skip to content

Commit c9ae8aa

Browse files
author
ralphstodomingo
committed
feat(workspace): attach the bound workspace's engine as a derived MCP overlay
With the pilot flag on in a bound project, the `datamate` MCP entry is derived at config load as the workspace's pinned local engine: never written to disk, overriding IDE, hosted and stale entries. MCP bootstrap starts it; each turn boundary re-reads the binding, replaces it on re-link, retries a failed handshake once, announces once per verdict. Writers refuse the key. Terminal only.
1 parent 23e5903 commit c9ae8aa

16 files changed

Lines changed: 1591 additions & 17 deletions

File tree

packages/opencode/src/altimate/api/client.ts

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ const DatamateSummary = z.object({
3737
const IntegrationSummary = z.object({
3838
id: z.coerce.string(),
3939
name: z.string().optional(),
40+
// altimate_change start — catalog `type` (tool | mcp | code | api | extension);
41+
// extension-type integrations have no meaning on the CLI surface.
42+
type: z.string().optional(),
43+
// altimate_change end
4044
description: z.string().nullable().optional(),
4145
tools: z
4246
.array(
@@ -227,19 +231,30 @@ export namespace AltimateApi {
227231

228232
async function request(creds: AltimateCredentials, method: string, endpoint: string, body?: unknown) {
229233
const url = `${creds.altimateUrl}${endpoint}`
230-
const res = await fetch(url, {
231-
method,
232-
headers: {
233-
"Content-Type": "application/json",
234-
Authorization: `Bearer ${creds.altimateApiKey}`,
235-
"x-tenant": creds.altimateInstanceName,
236-
},
237-
...(body ? { body: JSON.stringify(body) } : {}),
238-
})
239-
if (!res.ok) {
240-
throw new Error(`API ${method} ${endpoint} failed with status ${res.status}`)
234+
// altimate_change start — upstream_fix: bound every API request. Without a
235+
// signal a stalled server holds the caller indefinitely. The abort stays
236+
// armed until the BODY is read: `fetch` resolves on headers.
237+
const controller = new AbortController()
238+
const timeout = setTimeout(() => controller.abort(), 15_000)
239+
try {
240+
const res = await fetch(url, {
241+
signal: controller.signal,
242+
method,
243+
headers: {
244+
"Content-Type": "application/json",
245+
Authorization: `Bearer ${creds.altimateApiKey}`,
246+
"x-tenant": creds.altimateInstanceName,
247+
},
248+
...(body ? { body: JSON.stringify(body) } : {}),
249+
})
250+
if (!res.ok) {
251+
throw new Error(`API ${method} ${endpoint} failed with status ${res.status}`)
252+
}
253+
return await res.json()
254+
} finally {
255+
clearTimeout(timeout)
241256
}
242-
return res.json()
257+
// altimate_change end
243258
}
244259

245260
export async function listDatamates() {

packages/opencode/src/altimate/tools/datamate.ts

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import { Instance } from "../../project/instance"
1313
import { Global } from "../../global"
1414
import { Log } from "@/altimate/util/log"
1515
import { DATAMATE_KEY, readDatamateTransportFromIde } from "../datamate-transport"
16+
// altimate_change - workspace mode owns the datamate key
17+
import { managedWorkspace } from "../workspace/engine-overlay"
1618

1719
const log = Log.create({ service: "datamate" })
1820

@@ -138,22 +140,35 @@ async function handleList() {
138140

139141
async function handleListIntegrations() {
140142
try {
141-
const integrations = await AltimateApi.listIntegrations()
143+
const catalog = await AltimateApi.listIntegrations()
144+
// altimate_change start — extension-type integrations are RPC into a live VS
145+
// Code host and cannot work from the CLI. Hide them from this surface (the
146+
// workspace UI still offers them) and say how many were hidden.
147+
const integrations = catalog.filter((i) => i.type !== "extension")
148+
const hidden = catalog.length - integrations.length
149+
const omitted =
150+
hidden > 0
151+
? `${hidden} extension-type integration${hidden === 1 ? " was" : "s were"} omitted — they require a live VS Code bridge and are not available from the CLI.`
152+
: ""
142153
if (integrations.length === 0) {
143154
return {
144-
title: "Integrations: none found",
145-
metadata: { count: 0 },
146-
output: "No integrations available.",
155+
title: hidden > 0 ? `Integrations: none available on the CLI (${hidden} hidden)` : "Integrations: none found",
156+
metadata: { count: 0, hidden },
157+
output: omitted ? `No integrations available. ${omitted}` : "No integrations available.",
147158
}
148159
}
160+
// altimate_change end
149161
const lines = ["ID | Name | Tools", "---|------|------"]
150162
for (const i of integrations) {
151163
const tools = i.tools?.map((t) => t.key).join(", ") ?? "none"
152164
lines.push(`${i.id} | ${i.name} | ${tools}`)
153165
}
166+
// altimate_change start
167+
if (omitted) lines.push("", `(${omitted})`)
168+
// altimate_change end
154169
return {
155170
title: `Integrations: ${integrations.length} available`,
156-
metadata: { count: integrations.length },
171+
metadata: { count: integrations.length, hidden },
157172
output: lines.join("\n"),
158173
}
159174
} catch (e) {
@@ -201,6 +216,23 @@ async function handleAdd(args: { datamate_id?: string; name?: string; scope?: "p
201216
? DATAMATE_KEY
202217
: (args.name ?? `datamate-${slugify(datamate.name)}`)
203218

219+
// altimate_change start — in workspace mode the shared `datamate` key is the
220+
// bound workspace's own engine, derived at config load. Adding a datamate
221+
// under that key would replace it; refuse and say why. Standalone
222+
// `datamate-<name>` entries are a different key and stay the user's.
223+
const managed = serverName === DATAMATE_KEY ? managedWorkspace() : null
224+
if (managed) {
225+
return {
226+
title: `Datamate add: '${DATAMATE_KEY}' is managed by workspace "${managed.name}"`,
227+
metadata: { serverName: DATAMATE_KEY, managedBy: managed.id, datamateId: args.datamate_id },
228+
output:
229+
`This project is linked to workspace "${managed.name}", whose integrations are served by the ` +
230+
`workspace's own engine under the '${DATAMATE_KEY}' MCP server. Adding datamate '${datamate.name}' ` +
231+
`there is not applied. Unlink the project, or run without ALTIMATE_WORKSPACE, to manage that entry by hand.`,
232+
}
233+
}
234+
// altimate_change end
235+
204236
const creds = transport ? undefined : await AltimateApi.getCredentials()
205237
const mcpConfig =
206238
transport?.type === "remote"

0 commit comments

Comments
 (0)