ghosty-acp 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +50 -14
  2. package/bridge.mjs +35 -2
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -17,15 +17,14 @@ Nothing to install — `npx` fetches it:
17
17
  npx -y ghosty-acp wss://your-agent.example.com/acp
18
18
  ```
19
19
 
20
- ## Zed
20
+ ## VS Code
21
21
 
22
- `settings.json`:
22
+ Install the **ACP Client** extension, then in `settings.json`:
23
23
 
24
24
  ```jsonc
25
25
  {
26
- "agent_servers": {
26
+ "acp.agents": {
27
27
  "My agent": {
28
- "type": "custom",
29
28
  "command": "npx",
30
29
  "args": ["-y", "ghosty-acp", "wss://your-agent.example.com/acp"],
31
30
  "env": { "GHOSTY_ACP_TOKEN": "…" }
@@ -34,14 +33,15 @@ npx -y ghosty-acp wss://your-agent.example.com/acp
34
33
  }
35
34
  ```
36
35
 
37
- ## VS Code
36
+ ## Zed
38
37
 
39
- Install the **ACP Client** extension, then in `settings.json`:
38
+ `settings.json`:
40
39
 
41
40
  ```jsonc
42
41
  {
43
- "acp.agents": {
42
+ "agent_servers": {
44
43
  "My agent": {
44
+ "type": "custom",
45
45
  "command": "npx",
46
46
  "args": ["-y", "ghosty-acp", "wss://your-agent.example.com/acp"],
47
47
  "env": { "GHOSTY_ACP_TOKEN": "…" }
@@ -52,6 +52,23 @@ Install the **ACP Client** extension, then in `settings.json`:
52
52
 
53
53
  The token is optional — omit it if the other end doesn't require one.
54
54
 
55
+ ## The working directory (`cwd`)
56
+
57
+ Your editor runs on your machine; the agent lives in a remote box, and they share no disk.
58
+ Every ACP client sends the path of the project **open on your machine** in `session/new` —
59
+ a path that does not exist over there. The agent rejects it (`Invalid params: invalid
60
+ directory path`) and editors show it as *"Failed to connect"*, which sends you off to check
61
+ the URL and the token, the two things that were fine.
62
+
63
+ So the bridge rewrites that one field: `cwd` becomes `/root`, or whatever you set in
64
+ `GHOSTY_ACP_CWD`. Nothing else in the message is touched.
65
+
66
+ ```json
67
+ "env": { "GHOSTY_ACP_TOKEN": "…", "GHOSTY_ACP_CWD": "/workspace" }
68
+ ```
69
+
70
+ The agent edits **what is in the box**, not your working copy.
71
+
55
72
  ## ⚠️ Put the token in `env`, never in `args`
56
73
 
57
74
  Any process on your machine can read another process's arguments with `ps`. The token is
@@ -67,6 +84,7 @@ else there breaks the editor.
67
84
  | `rechazado (1006)` | Bad credential, or the server refused the connection |
68
85
  | `la máquina estaba en reposo…` | The agent was asleep; connecting wakes it |
69
86
  | `conexión cerrada (…)` | The other end closed; the bridge exits so the editor notices |
87
+ | `cwd … → …` | The project path was remapped to one the agent can see |
70
88
 
71
89
  Requires Node 22+. MIT.
72
90
 
@@ -92,15 +110,14 @@ Nada que instalar — `npx` lo descarga:
92
110
  npx -y ghosty-acp wss://tu-agente.example.com/acp
93
111
  ```
94
112
 
95
- ## Zed
113
+ ## VS Code
96
114
 
97
- En `settings.json`:
115
+ Instala la extensión **ACP Client** y en `settings.json`:
98
116
 
99
117
  ```jsonc
100
118
  {
101
- "agent_servers": {
119
+ "acp.agents": {
102
120
  "Mi agente": {
103
- "type": "custom",
104
121
  "command": "npx",
105
122
  "args": ["-y", "ghosty-acp", "wss://tu-agente.example.com/acp"],
106
123
  "env": { "GHOSTY_ACP_TOKEN": "…" }
@@ -109,14 +126,15 @@ En `settings.json`:
109
126
  }
110
127
  ```
111
128
 
112
- ## VS Code
129
+ ## Zed
113
130
 
114
- Instala la extensión **ACP Client** y en `settings.json`:
131
+ En `settings.json`:
115
132
 
116
133
  ```jsonc
117
134
  {
118
- "acp.agents": {
135
+ "agent_servers": {
119
136
  "Mi agente": {
137
+ "type": "custom",
120
138
  "command": "npx",
121
139
  "args": ["-y", "ghosty-acp", "wss://tu-agente.example.com/acp"],
122
140
  "env": { "GHOSTY_ACP_TOKEN": "…" }
@@ -127,6 +145,23 @@ Instala la extensión **ACP Client** y en `settings.json`:
127
145
 
128
146
  El token es opcional: si el otro extremo no pide credencial, omítelo.
129
147
 
148
+ ## El directorio de trabajo (`cwd`)
149
+
150
+ Tu editor corre en tu máquina y el agente vive en una caja remota: no comparten disco.
151
+ Todo cliente ACP manda en `session/new` la ruta del proyecto **abierto en tu máquina**, una
152
+ ruta que allá no existe. El agente la rechaza (`Invalid params: invalid directory path`) y
153
+ los editores lo pintan como *«Failed to connect»*, que manda a revisar la URL y el token
154
+ —justo lo que sí estaba bien—.
155
+
156
+ Por eso el puente reescribe ese único campo: `cwd` pasa a ser `/root`, o lo que pongas en
157
+ `GHOSTY_ACP_CWD`. Del resto del mensaje no toca nada.
158
+
159
+ ```json
160
+ "env": { "GHOSTY_ACP_TOKEN": "…", "GHOSTY_ACP_CWD": "/workspace" }
161
+ ```
162
+
163
+ El agente edita **lo que hay en la caja**, no tu copia de trabajo.
164
+
130
165
  ## ⚠️ El token va en `env`, nunca en `args`
131
166
 
132
167
  Los argumentos de un proceso los puede leer cualquier otro proceso de la máquina con un
@@ -142,5 +177,6 @@ flujo JSON-RPC, porque cualquier otra cosa ahí rompe al editor.
142
177
  | `rechazado (1006)` | La credencial no vale, o el servidor no aceptó la conexión |
143
178
  | `la máquina estaba en reposo…` | El agente estaba dormido; la conexión lo despierta |
144
179
  | `conexión cerrada (…)` | El otro extremo cerró; el puente sale para que el editor se entere |
180
+ | `cwd … → …` | Se remapeó la ruta del proyecto a una que el agente sí ve |
145
181
 
146
182
  Necesita Node 22 o superior. MIT.
package/bridge.mjs CHANGED
@@ -17,6 +17,7 @@
17
17
 
18
18
  const url = process.argv[2];
19
19
  const token = process.env.GHOSTY_ACP_TOKEN || "";
20
+ const cwdRemoto = process.env.GHOSTY_ACP_CWD || "/root";
20
21
 
21
22
  if (!url) {
22
23
  console.error("uso: node bridge.mjs <wss://…/acp> (token en GHOSTY_ACP_TOKEN)");
@@ -91,6 +92,35 @@ ws.onmessage = (e) => {
91
92
  }
92
93
  };
93
94
 
95
+ // ── `cwd`: LO ÚNICO que este puente traduce ──────────────────────────────────────
96
+ //
97
+ // Rompe a propósito el "no traduce nada" de arriba, y vale la pena decir por qué. El
98
+ // editor corre en tu máquina y el agente vive en una microVM: NO comparten disco. Todo
99
+ // cliente ACP manda en `session/new` el directorio del proyecto ABIERTO EN TU MÁQUINA
100
+ // (`/Users/tu/proyecto`), una ruta que dentro de la caja sencillamente no existe. goose
101
+ // la rechaza con `-32602 Invalid params: invalid directory path`, y los editores pintan
102
+ // eso como «Failed to connect» — un mensaje que manda a revisar la URL y el token, que
103
+ // son justamente lo que sí estaba bien. Se pierde la tarde.
104
+ //
105
+ // Se reescribe SÓLO el campo `cwd` y SÓLO en los dos métodos que lo llevan. El resto del
106
+ // mensaje pasa intacto; si no es JSON, o no es uno de esos métodos, no se toca nada.
107
+ const METODOS_CON_CWD = new Set(["session/new", "session/load"]);
108
+
109
+ const remapearCwd = (linea) => {
110
+ // Un mensaje que no parsea no es asunto nuestro: se mueve tal cual, como todo lo demás.
111
+ let msg;
112
+ try {
113
+ msg = JSON.parse(linea);
114
+ } catch {
115
+ return linea;
116
+ }
117
+ if (!METODOS_CON_CWD.has(msg?.method) || typeof msg?.params?.cwd !== "string") return linea;
118
+ if (msg.params.cwd === cwdRemoto) return linea;
119
+ diag(`cwd ${msg.params.cwd} → ${cwdRemoto} (el agente no ve tu disco)`);
120
+ msg.params.cwd = cwdRemoto;
121
+ return JSON.stringify(msg);
122
+ };
123
+
94
124
  // ── editor → caja ────────────────────────────────────────────────────────────────
95
125
  //
96
126
  // Aquí SÍ hace falta acumular: stdin es un flujo y un mensaje puede llegar partido en
@@ -101,9 +131,12 @@ process.stdin.on("data", (chunk) => {
101
131
  buffer += chunk;
102
132
  let nl;
103
133
  while ((nl = buffer.indexOf("\n")) !== -1) {
104
- const linea = buffer.slice(0, nl).trim();
134
+ const cruda = buffer.slice(0, nl).trim();
105
135
  buffer = buffer.slice(nl + 1);
106
- if (!linea) continue;
136
+ if (!cruda) continue;
137
+ // Antes de la cola y antes del envío: así la línea que se guarda dormida ya va
138
+ // corregida y no hay dos caminos que mantener.
139
+ const linea = remapearCwd(cruda);
107
140
  // Si el socket aún no está abierto la línea se descarta y se dice: mandarla al vacío
108
141
  // en silencio dejaría al editor esperando para siempre una respuesta que nadie oyó.
109
142
  if (ws.readyState === WebSocket.CONNECTING) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ghosty-acp",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Conecta tu editor a un agente ACP remoto. Puente entre entrada/salida estándar y WebSocket, sin dependencias.",
5
5
  "type": "module",
6
6
  "bin": { "ghosty-acp": "bridge.mjs" },