Skip to content

Commit c88718e

Browse files
author
ralphstodomingo
committed
fix(workspace): don't offer a local install for a remote workspace, and drop the filter
`attach <url>` runs the plugin on the client while the binding, PATH and MCP session live on the server, so probing PATH here describes the wrong machine and Install now would install on the client behind a success toast. The offer now refuses when the server's directory does not exist locally and says where the fix belongs. Separately, the persistent select kept a typed filter across phase changes, stranding the recovery rows; the dialog has three fixed options and no use for filtering.
1 parent a1795ec commit c88718e

1 file changed

Lines changed: 27 additions & 0 deletions

File tree

packages/opencode/src/plugin/tui/altimate/workspace.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import type { TuiPlugin, TuiPluginApi } from "@opencode-ai/plugin/tui"
2525
import type { BuiltinTuiPlugin } from "@opencode-ai/tui/builtins"
2626
import { createHash } from "node:crypto"
27+
import { existsSync } from "node:fs"
2728
import open from "open"
2829
import { createSignal, onCleanup, onMount } from "solid-js"
2930
import {
@@ -50,6 +51,7 @@ import {
5051
import { readLocalBinding, recordApprovedBinding } from "@/altimate/workspace/state"
5152
import {
5253
describeOffer,
54+
installCommand,
5355
installEngine,
5456
nodeMajor as detectNodeMajor,
5557
MIN_NODE_MAJOR,
@@ -1372,6 +1374,12 @@ function EngineInstallOfferDialog(props: EngineOfferProps) {
13721374
<props.api.ui.DialogSelect
13731375
title={title()}
13741376
options={options()}
1377+
// No filter. The row set changes with the phase, and a query typed to
1378+
// reach "Install now" still applies afterwards — the installing sentinel
1379+
// and the failure rows do not match it, so `filtered()` empties and the
1380+
// recovery actions become unreachable. A fixed three-option dialog gains
1381+
// nothing from filtering anyway.
1382+
skipFilter
13751383
current={canInstall() ? "install" : "copy"}
13761384
onSelect={(option) => {
13771385
if (option.value === "busy") return
@@ -1434,6 +1442,25 @@ async function showEngineInstallOffer(api: TuiPluginApi): Promise<void> {
14341442
// that arrive close together both pass — which is worse than the bug it
14351443
// fixes, because the second dialog can replace an installing one and start a
14361444
// concurrent global npm install.
1445+
// `attach <url>` runs this plugin on the CLIENT while the binding, the PATH
1446+
// that matters and the MCP session all live on the SERVER. Probing PATH here
1447+
// would describe the wrong machine, and "Install now" would install npm on
1448+
// the client, leaving the server exactly as it was behind a success toast.
1449+
// attach.ts recognises that case the same way — the server's directory does
1450+
// not exist locally — so use it and refuse to act, saying where the fix goes.
1451+
//
1452+
// Not a complete answer: a client that happens to have the same path, with a
1453+
// binding, is still misread. Closing that needs server-side discovery and
1454+
// install behind an API, which this PR does not add.
1455+
if (!existsSync(api.state.path.directory)) {
1456+
log.info("engine install offer suppressed: not the host that owns this workspace")
1457+
api.ui.toast({
1458+
variant: "warning",
1459+
message: `This workspace's engine is missing on the server, not on this machine. Run there: ${installCommand()}`,
1460+
duration: 30_000,
1461+
})
1462+
return
1463+
}
14371464
if (engineOfferVisible) return
14381465
if (engineInstallInFlight) {
14391466
// An install started from an earlier dialog is still running; offering

0 commit comments

Comments
 (0)