@wrongstack/acp 0.296.4 → 0.298.0
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.
- package/dist/agent/protocol-handler.d.ts +39 -46
- package/dist/agent/session-store.d.ts +55 -0
- package/dist/agent/stdio-transport.d.ts +74 -1
- package/dist/agent.js +472 -210
- package/dist/client/terminal-server.d.ts +5 -1
- package/dist/client/websocket-transport.d.ts +2 -0
- package/dist/client.js +122 -56
- package/dist/index.js +484 -264
- package/dist/win32-cmd.d.ts +9 -6
- package/dist/wrongstack-acp-agent.js +312 -157
- package/package.json +4 -3
- package/dist/agent/index.d.ts.map +0 -1
- package/dist/agent/protocol-contract.d.ts.map +0 -1
- package/dist/agent/protocol-handler.d.ts.map +0 -1
- package/dist/agent/server-agent-turn.d.ts.map +0 -1
- package/dist/agent/session-store.d.ts.map +0 -1
- package/dist/agent/stdio-transport.d.ts.map +0 -1
- package/dist/agent/tools-registry.d.ts.map +0 -1
- package/dist/agent/wrongstack-acp-agent.d.ts.map +0 -1
- package/dist/agent/ws-bridge-transport.d.ts.map +0 -1
- package/dist/agent.js.map +0 -7
- package/dist/client/acp-message-routing.d.ts.map +0 -1
- package/dist/client/acp-session-callbacks.d.ts.map +0 -1
- package/dist/client/acp-session-content.d.ts.map +0 -1
- package/dist/client/acp-session-errors.d.ts.map +0 -1
- package/dist/client/acp-session-types.d.ts.map +0 -1
- package/dist/client/acp-session-updates.d.ts.map +0 -1
- package/dist/client/acp-session.d.ts.map +0 -1
- package/dist/client/file-server.d.ts.map +0 -1
- package/dist/client/index.d.ts.map +0 -1
- package/dist/client/permission.d.ts.map +0 -1
- package/dist/client/terminal-server.d.ts.map +0 -1
- package/dist/client/tool-translator.d.ts.map +0 -1
- package/dist/client/trust-boundary-permission.d.ts.map +0 -1
- package/dist/client/websocket-transport.d.ts.map +0 -1
- package/dist/client.js.map +0 -7
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -7
- package/dist/integration/acp-bench.d.ts.map +0 -1
- package/dist/integration/acp-subagent-runner.d.ts.map +0 -1
- package/dist/integration/ensemble-runner.d.ts.map +0 -1
- package/dist/integration/run-one-acp-task.d.ts.map +0 -1
- package/dist/legacy.d.ts.map +0 -1
- package/dist/legacy.js.map +0 -7
- package/dist/registry/acp-registry-fetch.d.ts.map +0 -1
- package/dist/registry/agents.catalog.d.ts.map +0 -1
- package/dist/registry/ensemble-registry.d.ts.map +0 -1
- package/dist/sdk.d.ts.map +0 -1
- package/dist/sdk.js.map +0 -7
- package/dist/types/acp-messages.d.ts.map +0 -1
- package/dist/types/acp-v1.d.ts.map +0 -1
- package/dist/v1.d.ts.map +0 -1
- package/dist/v1.js.map +0 -7
- package/dist/version.d.ts.map +0 -1
- package/dist/win32-cmd.d.ts.map +0 -1
- package/dist/wrongstack-acp-agent.js.map +0 -7
|
@@ -1,50 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
* ACP v1 server-side protocol handler.
|
|
3
|
-
*
|
|
4
|
-
* Receives JSON-RPC requests from an external ACP client (Zed, JetBrains
|
|
5
|
-
* Junie, VS Code ACP extension, etc.) over stdio and answers them per the
|
|
6
|
-
* v1 spec. See https://agentclientprotocol.com/protocol/v1/overview.
|
|
7
|
-
*
|
|
8
|
-
* Supported methods
|
|
9
|
-
* ─────────────────
|
|
10
|
-
* - initialize — handshake
|
|
11
|
-
* - authenticate — optional, no-op when auth isn't required
|
|
12
|
-
* - session/new — create a session
|
|
13
|
-
* - session/load — restore a session by id
|
|
14
|
-
* - session/prompt — run one turn, stream session/update
|
|
15
|
-
* notifications, return stopReason
|
|
16
|
-
* - session/cancel — notification (no response); cancels the
|
|
17
|
-
* in-flight turn on the target session
|
|
18
|
-
* - session/set_mode — change the active mode for a session
|
|
19
|
-
* - session/set_config_option — change a config option value
|
|
20
|
-
* - session/list — list known sessions
|
|
21
|
-
*
|
|
22
|
-
* Method execution
|
|
23
|
-
* ────────────────
|
|
24
|
-
* The handler is transport-agnostic; it sends responses via the
|
|
25
|
-
* `AgentServerTransport` injected at construction. The actual
|
|
26
|
-
* agent-loop work for a `session/prompt` turn is delegated to the
|
|
27
|
-
* caller-provided `runTurn` callback, which receives the prompt
|
|
28
|
-
* blocks and the per-turn AbortSignal and resolves with the final
|
|
29
|
-
* stopReason. Updates are streamed via the `emit` callback passed
|
|
30
|
-
* to `runTurn`; the handler wraps each as a `session/update`
|
|
31
|
-
* notification.
|
|
32
|
-
*
|
|
33
|
-
* This separation keeps the handler unit-testable: tests can supply
|
|
34
|
-
* a fake `runTurn` that yields a canned sequence of updates, and
|
|
35
|
-
* assert on the JSON-RPC traffic the handler produces. A real
|
|
36
|
-
* production caller wires `runTurn` to a core `Agent` instance.
|
|
37
|
-
*
|
|
38
|
-
* Concurrency
|
|
39
|
-
* ───────────
|
|
40
|
-
* Each session is single-threaded (one active turn at a time). The
|
|
41
|
-
* handler keeps a per-session AbortController so a `session/cancel`
|
|
42
|
-
* notification can stop the running turn mid-stream without tearing
|
|
43
|
-
* down the session. Multiple sessions can be active concurrently.
|
|
44
|
-
*/
|
|
45
|
-
import { WRONGSTACK_VERSION, type ProtocolHandlerOptions } from './protocol-contract.js';
|
|
46
|
-
export { WRONGSTACK_VERSION };
|
|
1
|
+
import { type ProtocolHandlerOptions, WRONGSTACK_VERSION } from './protocol-contract.js';
|
|
47
2
|
export type { AgentCapabilities, ClientCapabilities, PromptCapabilities, ProtocolHandlerOptions, RunTurn, RunTurnApi, RunTurnInput, RunTurnPermissionRequest, RunTurnResult, SessionConfigOption, SessionMode, SessionPersistence, SessionState, } from './protocol-contract.js';
|
|
3
|
+
export { WRONGSTACK_VERSION };
|
|
48
4
|
export declare class ACPProtocolHandler {
|
|
49
5
|
private readonly transport;
|
|
50
6
|
private readonly defaultCwd;
|
|
@@ -104,6 +60,43 @@ export declare class ACPProtocolHandler {
|
|
|
104
60
|
/** Best-effort durable persistence of a session + its recorded history. */
|
|
105
61
|
private persist;
|
|
106
62
|
private sendError;
|
|
63
|
+
/**
|
|
64
|
+
* Allocate a session id (WS-015).
|
|
65
|
+
*
|
|
66
|
+
* This was `this.nextId++`, so ids were `sess_1`, `sess_2`, … — and the
|
|
67
|
+
* handler has no per-connection ownership: any caller that names a session
|
|
68
|
+
* id can `session/load`, `session/prompt`, `session/cancel` or
|
|
69
|
+
* `session/delete` it. Over stdio that is academic (one client per process),
|
|
70
|
+
* but the agent also serves over HTTP, where a guessable id is the whole
|
|
71
|
+
* authorization story for any local process or page that reaches the port.
|
|
72
|
+
*
|
|
73
|
+
* Random ids do not create ownership — they remove the trivial enumeration
|
|
74
|
+
* that made its absence exploitable. Real per-connection ownership is the
|
|
75
|
+
* larger fix and is noted in the WS-015 test file.
|
|
76
|
+
*
|
|
77
|
+
* The counter is retained: it keeps ids ordered for debugging and guarantees
|
|
78
|
+
* uniqueness within a process even in the (impossible) event of a UUID
|
|
79
|
+
* collision. The random half is what makes the id unguessable.
|
|
80
|
+
*/
|
|
81
|
+
/**
|
|
82
|
+
* Resolve a client-supplied `cwd` for a session, or `null` when it is not
|
|
83
|
+
* usable (WS-015).
|
|
84
|
+
*
|
|
85
|
+
* `session/new`, `session/load` and `session/fork` all took `params.cwd`
|
|
86
|
+
* with a single `typeof === 'string'` check and nothing else. That value is
|
|
87
|
+
* the working directory the agent then reads, writes and executes in.
|
|
88
|
+
*
|
|
89
|
+
* SCOPE, deliberately stated: this does NOT confine the session to a root.
|
|
90
|
+
* In ACP the client IS the editor and legitimately names its own workspace —
|
|
91
|
+
* Zed and JetBrains pass the project root — so a fixed boundary here would
|
|
92
|
+
* break the integration this package exists for. What it enforces is that
|
|
93
|
+
* the directory is absolute and actually exists as a directory: a relative
|
|
94
|
+
* or missing `cwd` is a bug or an attack under either reading, and silently
|
|
95
|
+
* running the agent somewhere other than where the client asked is worse
|
|
96
|
+
* than refusing. Confinement, if wanted, belongs in an operator-set option
|
|
97
|
+
* on top of this, not in place of it.
|
|
98
|
+
*/
|
|
99
|
+
private resolveSessionCwd;
|
|
107
100
|
private allocId;
|
|
108
101
|
}
|
|
109
102
|
declare function errorToJsonRpc(err: unknown): {
|
|
@@ -21,7 +21,43 @@ export declare class ACPSessionStore {
|
|
|
21
21
|
* Cleared automatically if the directory disappears between calls.
|
|
22
22
|
*/
|
|
23
23
|
private initialized;
|
|
24
|
+
/**
|
|
25
|
+
* Tail of in-flight sidecar-index mutations. `readIndex`/`writeIndex`/
|
|
26
|
+
* `updateIndex`/delete's index-touching branch all run through the
|
|
27
|
+
* `withIndexLock` gate so a concurrent save/delete/save cannot read the
|
|
28
|
+
* same baseline twice and clobber the other writer's edit (Chimera
|
|
29
|
+
* HIGH — race that could permanently omit or resurrect sessions in
|
|
30
|
+
* `list()`). Mirrors the `writeChains` pattern in
|
|
31
|
+
* `packages/core/src/storage/tool-audit-log.ts`.
|
|
32
|
+
*/
|
|
33
|
+
private indexChain;
|
|
34
|
+
/**
|
|
35
|
+
* Monotonic per-store counter. Used to make tmp filenames unique so two
|
|
36
|
+
* concurrent saves started in the same millisecond cannot collide on
|
|
37
|
+
* `<target>.<pid>.<ts>.tmp` and lose one's tmp mid-write (latent bug
|
|
38
|
+
* discovered by the concurrent same-id save/delete stress test).
|
|
39
|
+
*/
|
|
40
|
+
private writeSeq;
|
|
24
41
|
constructor(opts?: SessionStoreOptions);
|
|
42
|
+
/**
|
|
43
|
+
* Path of `<sessionId>.json` inside the store, or `null` when `sessionId`
|
|
44
|
+
* is not usable as a single path segment (WS-015).
|
|
45
|
+
*
|
|
46
|
+
* The session id arrives from the ACP client — `session/load`,
|
|
47
|
+
* `session/close` and `session/delete` all take it verbatim from the wire.
|
|
48
|
+
* It was joined straight onto the store directory, so `../../..` escaped it,
|
|
49
|
+
* and the `.json` suffix was the only thing narrowing the blast radius:
|
|
50
|
+
*
|
|
51
|
+
* - `load` — read any `.json` on the machine, e.g. the provider config
|
|
52
|
+
* holding API keys
|
|
53
|
+
* - `delete` — unlink any `.json` on the machine
|
|
54
|
+
* - `save` — the worst one: a loaded session keeps the traversing id as
|
|
55
|
+
* its `state.id`, so the next persist WRITES to that path
|
|
56
|
+
*
|
|
57
|
+
* All three now route through here, so the escape is closed once rather than
|
|
58
|
+
* three times.
|
|
59
|
+
*/
|
|
60
|
+
private sessionFile;
|
|
25
61
|
/** Ensure the store directory exists. Memoized — only mkdirs once. */
|
|
26
62
|
init(): Promise<void>;
|
|
27
63
|
/**
|
|
@@ -37,8 +73,27 @@ export declare class ACPSessionStore {
|
|
|
37
73
|
id: string;
|
|
38
74
|
updatedAt: string;
|
|
39
75
|
}>>;
|
|
76
|
+
/**
|
|
77
|
+
* Walk `<dir>` and parse every session file into `{id, updatedAt}`
|
|
78
|
+
* metadata. Used as the slow-path fallback by `list()` and by
|
|
79
|
+
* `updateIndex()` when the sidecar index is missing. Pure read —
|
|
80
|
+
* does NOT touch `index.json`.
|
|
81
|
+
*/
|
|
82
|
+
private scanForSessions;
|
|
40
83
|
/** Sidecar path that stores `{id, updatedAt}` for every saved session. */
|
|
41
84
|
private indexPath;
|
|
85
|
+
/**
|
|
86
|
+
* Serialize sidecar-index mutations. Each call appends `fn` to the tail
|
|
87
|
+
* of `indexChain` so reads and writes see a linearized order — a second
|
|
88
|
+
* caller cannot start until the first one's index update has finished.
|
|
89
|
+
*
|
|
90
|
+
* The chain swallows errors (`prev.then(fn, fn)`) so one failing writer
|
|
91
|
+
* does not poison every subsequent caller, and the catch keeps an
|
|
92
|
+
* unhandled rejection from leaking out of the stored tail. The cleanup
|
|
93
|
+
* removes the tail entry once settled, so a quiet store does not retain
|
|
94
|
+
* stale promises forever.
|
|
95
|
+
*/
|
|
96
|
+
private withIndexLock;
|
|
42
97
|
/** Read the sidecar index. Returns `null` when missing or unreadable. */
|
|
43
98
|
private readIndex;
|
|
44
99
|
/** Atomically replace the sidecar index with the supplied entries. */
|
|
@@ -4,8 +4,33 @@ export interface AgentServerTransport {
|
|
|
4
4
|
send(msg: ACPMessage): Promise<void>;
|
|
5
5
|
sendRaw(chunk: string): void;
|
|
6
6
|
read(): Promise<ACPMessage | null>;
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Register an observer that fires for every inbound message — including
|
|
9
|
+
* messages that resolve a parked `read()` call.
|
|
10
|
+
*
|
|
11
|
+
* Dual-fire contract: a message is delivered to BOTH the `read()`
|
|
12
|
+
* promise (if one is parked) AND every handler registered here. The
|
|
13
|
+
* legacy behavior was "no fire while read() is parked"; the new
|
|
14
|
+
* behavior was adopted to fix a deadlock when a JSON-RPC reply
|
|
15
|
+
* arrived before the next read() call (handlers that correlate
|
|
16
|
+
* responses to outbound requests would never see it). Handlers MUST
|
|
17
|
+
* tolerate being called for every inbound message and MUST NOT
|
|
18
|
+
* assume the message has not also been delivered to read().
|
|
19
|
+
*
|
|
20
|
+
* To consume a message exclusively (prevent it from being enqueued for
|
|
21
|
+
* read()), use `onMessageClaim()` instead. Most observers want
|
|
22
|
+
* `onMessage`; correlation handlers and the protocol layer want
|
|
23
|
+
* `onMessageClaim`.
|
|
24
|
+
*/
|
|
8
25
|
onMessage(handler: (msg: ACPMessage) => void): () => void;
|
|
26
|
+
/**
|
|
27
|
+
* Register a handler that MAY claim a message by returning `true`.
|
|
28
|
+
* Claimed messages are NOT enqueued for the read() loop. Required
|
|
29
|
+
* for the server transport so the correlation handler (which always
|
|
30
|
+
* fires) doesn't starve the read loop of pipelined requests and
|
|
31
|
+
* notifications. See `onMessageClaim` on the implementations.
|
|
32
|
+
*/
|
|
33
|
+
onMessageClaim?(handler: (msg: ACPMessage) => boolean): () => void;
|
|
9
34
|
}
|
|
10
35
|
/**
|
|
11
36
|
* Minimal client-side transport contract `ACPSession` drives. `ClientTransport`
|
|
@@ -15,7 +40,21 @@ export interface AgentServerTransport {
|
|
|
15
40
|
export interface ACPClientTransport {
|
|
16
41
|
start(): Promise<void>;
|
|
17
42
|
send(msg: ACPMessage): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* See {@link AgentServerTransport.onMessage}. The client contract has the
|
|
45
|
+
* same dual-fire guarantee: handlers fire for every inbound message,
|
|
46
|
+
* regardless of any concurrent reader. Client-side consumers always
|
|
47
|
+
* observe the full message stream via onMessage, so claim semantics
|
|
48
|
+
* are not needed — see {@link onMessageClaim} for the optional
|
|
49
|
+
* claim variant.
|
|
50
|
+
*/
|
|
18
51
|
onMessage(handler: (msg: ACPMessage) => void): () => void;
|
|
52
|
+
/**
|
|
53
|
+
* See {@link AgentServerTransport.onMessageClaim}. Optional on the
|
|
54
|
+
* client contract: client-side consumers always observe the full
|
|
55
|
+
* message stream via onMessage, so claim semantics are not needed.
|
|
56
|
+
*/
|
|
57
|
+
onMessageClaim?(handler: (msg: ACPMessage) => boolean): () => void;
|
|
19
58
|
stop(): void;
|
|
20
59
|
}
|
|
21
60
|
export declare class StdioTransport implements AgentServerTransport {
|
|
@@ -24,20 +63,38 @@ export declare class StdioTransport implements AgentServerTransport {
|
|
|
24
63
|
private readonly stderr;
|
|
25
64
|
private buffer;
|
|
26
65
|
private readonly handlers;
|
|
66
|
+
private readonly claimHandlers;
|
|
27
67
|
private closed;
|
|
28
68
|
private resolveRead;
|
|
29
69
|
private messageQueue;
|
|
70
|
+
private queuedChars;
|
|
30
71
|
private readonly maxFrameChars;
|
|
31
72
|
private readonly maxQueuedMessages;
|
|
73
|
+
private readonly maxQueuedChars;
|
|
74
|
+
private readonly onStdinData;
|
|
75
|
+
private readonly onStdinEnd;
|
|
76
|
+
private readonly onStdinError;
|
|
32
77
|
constructor(opts?: {
|
|
33
78
|
maxFrameChars?: number;
|
|
34
79
|
maxQueuedMessages?: number;
|
|
80
|
+
maxQueuedChars?: number;
|
|
35
81
|
});
|
|
36
82
|
sendStartupMarker(): void;
|
|
37
83
|
send(msg: ACPMessage): Promise<void>;
|
|
38
84
|
sendRaw(chunk: string): void;
|
|
39
85
|
read(): Promise<ACPMessage | null>;
|
|
40
86
|
onMessage(handler: (msg: ACPMessage) => void): () => void;
|
|
87
|
+
/**
|
|
88
|
+
* Register a handler that MAY claim a message by returning `true`.
|
|
89
|
+
* Claimed messages are NOT enqueued for the read() loop — they are
|
|
90
|
+
* considered fully consumed by the handler. This is how the ACP
|
|
91
|
+
* server transport prevents the correlation handler (which always
|
|
92
|
+
* fires on every message but only actually correlates responses)
|
|
93
|
+
* from starving the read() loop of pipelined requests, notifications,
|
|
94
|
+
* and any message the handler chose to ignore. See dispatch() for
|
|
95
|
+
* the gating logic.
|
|
96
|
+
*/
|
|
97
|
+
onMessageClaim(handler: (msg: ACPMessage) => boolean): () => void;
|
|
41
98
|
close(): void;
|
|
42
99
|
private onData;
|
|
43
100
|
private dispatch;
|
|
@@ -62,6 +119,8 @@ export interface ClientTransportOptions {
|
|
|
62
119
|
maxFrameChars?: number | undefined;
|
|
63
120
|
/** Maximum messages retained for the optional read() API. Default 1000. */
|
|
64
121
|
maxQueuedMessages?: number | undefined;
|
|
122
|
+
/** Maximum serialized characters retained for the optional read() API. Default 32 MiB. */
|
|
123
|
+
maxQueuedChars?: number | undefined;
|
|
65
124
|
}
|
|
66
125
|
export interface ACPChildProcess extends EventEmitter {
|
|
67
126
|
stdout: NodeJS.ReadableStream;
|
|
@@ -74,17 +133,31 @@ export declare class ClientTransport implements ACPClientTransport {
|
|
|
74
133
|
private child;
|
|
75
134
|
private buffer;
|
|
76
135
|
private readonly handlers;
|
|
136
|
+
private readonly claimHandlers;
|
|
77
137
|
private closed;
|
|
78
138
|
private resolveRead;
|
|
79
139
|
private messageQueue;
|
|
140
|
+
private queuedChars;
|
|
80
141
|
private readonly opts;
|
|
81
142
|
private readonly maxFrameChars;
|
|
82
143
|
private readonly maxQueuedMessages;
|
|
144
|
+
private readonly maxQueuedChars;
|
|
83
145
|
constructor(options: ClientTransportOptions);
|
|
84
146
|
start(): Promise<void>;
|
|
85
147
|
send(msg: ACPMessage): Promise<void>;
|
|
86
148
|
read(): Promise<ACPMessage | null>;
|
|
87
149
|
onMessage(handler: (msg: ACPMessage) => void): () => void;
|
|
150
|
+
/**
|
|
151
|
+
* Register a handler that MAY claim a message by returning `true`.
|
|
152
|
+
* Claimed messages are NOT enqueued for the read() loop — they are
|
|
153
|
+
* considered fully consumed by the handler. This is how the ACP
|
|
154
|
+
* server transport prevents the correlation handler (which always
|
|
155
|
+
* fires on every message but only actually correlates responses)
|
|
156
|
+
* from starving the read() loop of pipelined requests, notifications,
|
|
157
|
+
* and any message the handler chose to ignore. See dispatch() for
|
|
158
|
+
* the gating logic.
|
|
159
|
+
*/
|
|
160
|
+
onMessageClaim(handler: (msg: ACPMessage) => boolean): () => void;
|
|
88
161
|
stop(): void;
|
|
89
162
|
private onChildData;
|
|
90
163
|
private onChildError;
|