Skip to content

Commit d4f54ac

Browse files
committed
done
1 parent 8b5ffc1 commit d4f54ac

File tree

11 files changed

+847
-305
lines changed

11 files changed

+847
-305
lines changed

.vscode/launch.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,18 @@
1212
"skipFiles": ["<node_internals>/**"],
1313
"console": "integratedTerminal",
1414
"sourceMaps": true
15+
},
16+
{
17+
"name": "Attach to process",
18+
"type": "node",
19+
"request": "attach",
20+
"port": 9229,
21+
"skipFiles": [
22+
// Node.js internal core modules
23+
"<node_internals>/**",
24+
// Ignore all dependencies (optional)
25+
"${workspaceFolder}/node_modules/**"
26+
]
1527
}
1628
]
1729
}

src/commands/acp/README.md

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,16 @@ DEBUG=neovate:acp* neovate acp
6666
- ✅ Slash commands
6767
- ✅ Model management
6868
- ✅ Logging system
69+
- ✅ File system operations (read/write via ACP protocol, plugin-based)
70+
71+
### Implementation Highlights
72+
73+
**File System Support**:
74+
75+
- Uses plugin architecture (zero intrusion to core code)
76+
- Shares 95% logic with standard tools
77+
- Only customizes file I/O layer (5%)
78+
- See `FILE_SYSTEM_SUPPORT.md` for details
6979

7080
## 🏗️ Architecture
7181

@@ -172,11 +182,32 @@ Then watch logs:
172182
tail -f /tmp/neovate-acp.log
173183
```
174184

185+
### debug for vscode
186+
187+
zed config
188+
189+
```json
190+
{
191+
"agent_servers": {
192+
"Neovate Agent": {
193+
"favorite_models": [],
194+
"type": "custom",
195+
"env": {
196+
"DEBUG=neovate": "*"
197+
},
198+
"command": "tsx",
199+
"args": ["--inspect-brk", "xx/neovate-code/src/cli.ts", "acp"]
200+
}
201+
}
202+
}
203+
```
204+
205+
Go to VSCode's debug panel, select "Attach to process" in the drop down, and hit the play button (F5).
206+
175207
## 🤝 Contributing
176208

177209
### Not Implemented
178210

179-
- ❌ File system operations (fs.read, fs.write, fs.list)
180211
- ❌ Terminal operations (terminal.execute, terminal.read_output)
181212
- ❌ Session persistence (loadSession, listSessions)
182213
- ❌ Advanced features (forkSession, authenticate)

src/commands/acp/agent.ts

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
type AuthenticateRequest,
1010
type AuthenticateResponse,
1111
type CancelNotification,
12+
type FileSystemCapability,
1213
type ForkSessionRequest,
1314
type ForkSessionResponse,
1415
type InitializeRequest,
@@ -38,6 +39,7 @@ import { MessageBus, DirectTransport } from '../../messageBus';
3839
import { NodeBridge } from '../../nodeBridge';
3940
import type { ACPContextCreateOpts } from './types';
4041
import { ACPSession } from './session';
42+
import { createACPPlugin } from './plugin';
4143

4244
const debug = createDebug('neovate:acp:agent');
4345

@@ -63,6 +65,7 @@ export class NeovateACPAgent implements Agent {
6365
private context?: Context;
6466
private defaultCwd: string;
6567
private contextCreateOpts: ACPContextCreateOpts;
68+
private clientFsCapabilities?: FileSystemCapability; // FileSystemCapability from client
6669

6770
constructor(
6871
connection: AgentSideConnection,
@@ -80,33 +83,45 @@ export class NeovateACPAgent implements Agent {
8083
log('Initializing ACP agent');
8184
debug('Initialize params: %O', params);
8285

86+
// Check client capabilities for file system support
87+
if (params.clientCapabilities?.fs) {
88+
log(
89+
'Client supports file system capabilities:',
90+
params.clientCapabilities.fs,
91+
);
92+
this.clientFsCapabilities = params.clientCapabilities.fs;
93+
}
94+
// Create context with ACP plugin if file system is supported
95+
log('Creating Neovate context');
96+
const plugins = [...(this.contextCreateOpts.plugins || [])];
97+
98+
// Add ACP file system plugin if capabilities are available
99+
if (this.clientFsCapabilities) {
100+
log('Adding ACP file system plugin');
101+
const acpPlugin = createACPPlugin({
102+
connection: this.connection,
103+
});
104+
plugins.push(acpPlugin);
105+
}
106+
83107
// Create MessageBus for event-driven architecture
84108
log('Creating NodeBridge and MessageBus');
85109
const nodeBridge = new NodeBridge({
86-
contextCreateOpts: this.contextCreateOpts,
110+
contextCreateOpts: {
111+
...this.contextCreateOpts,
112+
cwd: this.defaultCwd,
113+
plugins,
114+
},
87115
});
88116

89117
const [clientTransport, nodeTransport] = DirectTransport.createPair();
90118
const messageBus = new MessageBus();
91119
messageBus.setTransport(clientTransport);
92120
nodeBridge.messageBus.setTransport(nodeTransport);
93121

94-
// Auto-approve all tool calls in ACP mode
95-
messageBus.registerHandler('toolApproval', async () => {
96-
return { approved: true };
97-
});
98-
99122
this.messageBus = messageBus;
100123
this.nodeBridge = nodeBridge;
101124

102-
// Create context
103-
log('Creating Neovate context');
104-
this.context = await Context.create({
105-
...this.contextCreateOpts,
106-
cwd: this.contextCreateOpts.cwd || this.defaultCwd,
107-
messageBus: this.messageBus,
108-
});
109-
110125
log('Agent initialized successfully');
111126
return {
112127
protocolVersion: PROTOCOL_VERSION, // ACP protocol version (will be proper type after SDK install)
@@ -184,6 +199,7 @@ export class NeovateACPAgent implements Agent {
184199
sessionId,
185200
this.messageBus,
186201
this.connection,
202+
this.clientFsCapabilities, // Pass fs capabilities
187203
);
188204

189205
this.sessions.set(sessionId, acpSession);

src/commands/acp/plugin.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/**
2+
* ACP Plugin - Override read/write tools to use ACP protocol
3+
*
4+
* This plugin replaces the default read/write tools with ACP-enabled versions
5+
* when running in ACP mode. It's a non-invasive way to add ACP file system support.
6+
*
7+
* Architecture:
8+
* - Uses plugin system's tool name override mechanism
9+
* - Returns tools with same names (TOOL_NAMES.READ, TOOL_NAMES.WRITE)
10+
* - enforce: 'post' ensures these tools execute after built-in tools
11+
* - Tools with same name get replaced in Tools constructor
12+
*
13+
* Graceful Degradation:
14+
* - ACP tools try ACP protocol first (connection.readTextFile/writeTextFile)
15+
* - If ACP fails or is unavailable, automatically falls back to fs module
16+
* - Logs warnings when fallback occurs for debugging
17+
* - Ensures file operations always succeed when possible
18+
*
19+
* @see src/tool.ts - Tools constructor uses tool.name as key, later tools override earlier ones
20+
* @see src/plugin.ts - PluginHookType.SeriesMerge concatenates all plugin results
21+
*/
22+
23+
import type { AgentSideConnection } from '@agentclientprotocol/sdk';
24+
import type { Plugin } from '../../plugin';
25+
import { createReadTool } from './tools/read';
26+
import { createWriteTool } from './tools/write';
27+
28+
/**
29+
* Create ACP file system plugin
30+
*
31+
* @param opts.connection - ACP connection instance for file operations
32+
* @returns Plugin that overrides read/write tools with ACP-enabled versions
33+
*
34+
* @example
35+
* ```typescript
36+
* // In ACP agent initialization
37+
* if (params.clientCapabilities?.fs) {
38+
* const plugin = createACPPlugin({ connection });
39+
* context.pluginManager.register(plugin);
40+
* }
41+
* ```
42+
*/
43+
export function createACPPlugin(opts: {
44+
connection: AgentSideConnection;
45+
}): Plugin {
46+
return {
47+
name: 'acp',
48+
49+
enforce: 'post',
50+
51+
/**
52+
* Tool hook: Returns ACP-enabled read/write tools
53+
* @param toolOpts.sessionId - Session ID for this tool invocation
54+
* @param toolOpts.isPlan - Whether in plan mode
55+
* @this {Context} - Context object with cwd, productName, etc.
56+
* @returns Array of ACP-enabled tools that will override built-in tools
57+
*/
58+
tool(toolOpts) {
59+
const { sessionId } = toolOpts;
60+
61+
return [
62+
createReadTool({
63+
cwd: this.cwd,
64+
productName: this.productName,
65+
connection: opts.connection,
66+
sessionId,
67+
}),
68+
69+
createWriteTool({
70+
cwd: this.cwd,
71+
connection: opts.connection,
72+
sessionId,
73+
}),
74+
];
75+
},
76+
};
77+
}

src/commands/acp/session.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
safeParseJson,
2525
toACPToolContent,
2626
} from './utils/messageAdapter';
27+
import { createACPPlugin } from './plugin';
2728

2829
/**
2930
* ACPSession wraps a Neovate session and handles ACP protocol events
@@ -36,6 +37,7 @@ export class ACPSession {
3637
private readonly id: string,
3738
private readonly messageBus: MessageBus,
3839
private readonly connection: AgentSideConnection,
40+
private readonly clientFsCapabilities?: any, // FileSystemCapability from client
3941
) {}
4042

4143
/**
@@ -108,7 +110,6 @@ export class ACPSession {
108110
toolCall: {
109111
toolCallId: toolUse.callId,
110112
kind: mapApprovalCategory(category),
111-
status: 'pending',
112113
},
113114
options: [
114115
{
@@ -400,12 +401,13 @@ export class ACPSession {
400401
}
401402

402403
/**
403-
* Abort the current prompt
404+
* Abort any pending prompt
404405
*/
405406
async abort() {
406407
await this.messageBus.request('session.cancel', {
407408
cwd: this.defaultCwd,
408409
sessionId: this.id,
409410
});
411+
this.pendingPrompt?.abort();
410412
}
411413
}

0 commit comments

Comments
 (0)