@xenosystem/acp-core 0.1.1

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 (60) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +9 -0
  3. package/README.md +26 -0
  4. package/dist/agent-process.d.ts +94 -0
  5. package/dist/agent-process.d.ts.map +1 -0
  6. package/dist/agent-process.js +391 -0
  7. package/dist/agent-process.js.map +1 -0
  8. package/dist/agent-version-policy.d.ts +25 -0
  9. package/dist/agent-version-policy.d.ts.map +1 -0
  10. package/dist/agent-version-policy.js +87 -0
  11. package/dist/agent-version-policy.js.map +1 -0
  12. package/dist/capabilities.d.ts +41 -0
  13. package/dist/capabilities.d.ts.map +1 -0
  14. package/dist/capabilities.js +202 -0
  15. package/dist/capabilities.js.map +1 -0
  16. package/dist/certification.d.ts +3 -0
  17. package/dist/certification.d.ts.map +1 -0
  18. package/dist/certification.js +3 -0
  19. package/dist/certification.js.map +1 -0
  20. package/dist/config.d.ts +23 -0
  21. package/dist/config.d.ts.map +1 -0
  22. package/dist/config.js +136 -0
  23. package/dist/config.js.map +1 -0
  24. package/dist/flatten.d.ts +31 -0
  25. package/dist/flatten.d.ts.map +1 -0
  26. package/dist/flatten.js +82 -0
  27. package/dist/flatten.js.map +1 -0
  28. package/dist/index.d.ts +33 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +33 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/logger.d.ts +17 -0
  33. package/dist/logger.d.ts.map +1 -0
  34. package/dist/logger.js +38 -0
  35. package/dist/logger.js.map +1 -0
  36. package/dist/manager.d.ts +54 -0
  37. package/dist/manager.d.ts.map +1 -0
  38. package/dist/manager.js +194 -0
  39. package/dist/manager.js.map +1 -0
  40. package/dist/process-resolver.d.ts +50 -0
  41. package/dist/process-resolver.d.ts.map +1 -0
  42. package/dist/process-resolver.js +78 -0
  43. package/dist/process-resolver.js.map +1 -0
  44. package/dist/registry.d.ts +21 -0
  45. package/dist/registry.d.ts.map +1 -0
  46. package/dist/registry.js +74 -0
  47. package/dist/registry.js.map +1 -0
  48. package/dist/session.d.ts +34 -0
  49. package/dist/session.d.ts.map +1 -0
  50. package/dist/session.js +146 -0
  51. package/dist/session.js.map +1 -0
  52. package/dist/types.d.ts +161 -0
  53. package/dist/types.d.ts.map +1 -0
  54. package/dist/types.js +9 -0
  55. package/dist/types.js.map +1 -0
  56. package/dist/workspace.d.ts +27 -0
  57. package/dist/workspace.d.ts.map +1 -0
  58. package/dist/workspace.js +47 -0
  59. package/dist/workspace.js.map +1 -0
  60. package/package.json +59 -0
package/dist/logger.js ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Tiny leveled logger. Writes to **stderr only** — stdout is reserved for the NDJSON
3
+ * channel shared with agent subprocesses, so writing logs there would corrupt the
4
+ * protocol stream. (See CLAUDE.md rule 2.)
5
+ */
6
+ const ORDER = {
7
+ debug: 10,
8
+ info: 20,
9
+ warn: 30,
10
+ error: 40,
11
+ silent: 99,
12
+ };
13
+ function envLevel() {
14
+ const v = (process.env.XENO_ACP_LOG ?? "info").toLowerCase();
15
+ return ["debug", "info", "warn", "error", "silent"].includes(v)
16
+ ? v
17
+ : "info";
18
+ }
19
+ function emit(level, threshold, scope, args) {
20
+ if (ORDER[level] < ORDER[threshold])
21
+ return;
22
+ const tag = `[xeno-acp${scope ? ":" + scope : ""}] ${level.toUpperCase()}`;
23
+ // eslint-disable-next-line no-console
24
+ console.error(tag, ...args);
25
+ }
26
+ export function createLogger(scope = "", level = envLevel()) {
27
+ const self = {
28
+ level,
29
+ debug: (...a) => emit("debug", self.level, scope, a),
30
+ info: (...a) => emit("info", self.level, scope, a),
31
+ warn: (...a) => emit("warn", self.level, scope, a),
32
+ error: (...a) => emit("error", self.level, scope, a),
33
+ child: (s) => createLogger(scope ? `${scope}.${s}` : s, self.level),
34
+ };
35
+ return self;
36
+ }
37
+ export const log = createLogger();
38
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,MAAM,KAAK,GAA6B;IACtC,KAAK,EAAE,EAAE;IACT,IAAI,EAAE,EAAE;IACR,IAAI,EAAE,EAAE;IACR,KAAK,EAAE,EAAE;IACT,MAAM,EAAE,EAAE;CACX,CAAC;AAEF,SAAS,QAAQ;IACf,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;IAC7D,OAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAW,CAAC,QAAQ,CAAC,CAAa,CAAC;QACpF,CAAC,CAAE,CAAc;QACjB,CAAC,CAAC,MAAM,CAAC;AACb,CAAC;AAWD,SAAS,IAAI,CAAC,KAAe,EAAE,SAAmB,EAAE,KAAa,EAAE,IAAe;IAChF,IAAI,KAAK,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC;QAAE,OAAO;IAC5C,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;IAC3E,sCAAsC;IACtC,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAK,GAAG,EAAE,EAAE,QAAkB,QAAQ,EAAE;IACnE,MAAM,IAAI,GAAW;QACnB,KAAK;QACL,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;QACpD,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;QAClD,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;QAClD,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;QACpD,KAAK,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC;KAC5E,CAAC;IACF,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,MAAM,GAAG,GAAG,YAAY,EAAE,CAAC"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * `AcpManager` — the public engine entry point.
3
+ *
4
+ * Owns a lazy pool of `AgentProcess` (one per agent id), a per-agent concurrency
5
+ * semaphore, and the `prompt()` async-iterator that in-process consumers and the HTTP
6
+ * server both build on. Renders chat messages into an ACP prompt (SPEC §5) and yields the
7
+ * structured `AcpEvent` stream.
8
+ */
9
+ import type { ContentBlock } from "@agentclientprotocol/sdk";
10
+ import type { AcpEvent, AgentDef, AgentStatus, ChatMessage, PromptOptions, ResolvedConfig } from "./types.js";
11
+ import { type Logger } from "./logger.js";
12
+ export interface ResolvedModel {
13
+ id: string;
14
+ submodel?: string;
15
+ }
16
+ /** Parse an OpenAI `model` value of the form `agentId` or `agentId:submodel`. */
17
+ export declare function resolveModel(model: string): ResolvedModel;
18
+ export declare class AcpManager {
19
+ private readonly config;
20
+ private readonly logger;
21
+ private readonly defs;
22
+ private readonly processes;
23
+ private readonly semaphores;
24
+ constructor(config: ResolvedConfig, logger?: Logger);
25
+ /** Enabled agent definitions. */
26
+ listAgents(): AgentDef[];
27
+ getAgentDef(id: string): AgentDef | undefined;
28
+ hasAgent(id: string): boolean;
29
+ /** Status snapshot for every agent that has been touched (plus idle for the rest). */
30
+ statuses(): AgentStatus[];
31
+ private process;
32
+ private semaphore;
33
+ /**
34
+ * Drive one prompt turn against `agentId` (or `agentId:submodel`). Returns an async
35
+ * iterator of structured events ending in `done` (or `error`).
36
+ */
37
+ prompt(agentId: string, opts: PromptOptions): AsyncGenerator<AcpEvent>;
38
+ /** Convenience: run a turn and collect the full assistant text + stop reason. */
39
+ complete(agentId: string, opts: PromptOptions): Promise<{
40
+ text: string;
41
+ stopReason: string;
42
+ usage?: AcpEvent & {
43
+ type: "usage";
44
+ };
45
+ }>;
46
+ dispose(): Promise<void>;
47
+ }
48
+ /**
49
+ * Render chat messages into ACP prompt content (SPEC §5). Version 0.1.0 uses ephemeral sessions,
50
+ * so the conversation is flattened into the turn: a single text block carrying any system
51
+ * instructions plus the transcript, with the trailing user message as the active ask.
52
+ */
53
+ export declare function renderMessages(messages: ChatMessage[]): ContentBlock[];
54
+ //# sourceMappingURL=manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../src/manager.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,KAAK,EACV,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,WAAW,EACX,aAAa,EACb,cAAc,EACf,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AA2BxD,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,iFAAiF;AACjF,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,CAIzD;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IACxC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA+B;IACpD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAmC;IAC7D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAgC;gBAE/C,MAAM,EAAE,cAAc,EAAE,MAAM,GAAE,MAAuB;IAMnE,iCAAiC;IACjC,UAAU,IAAI,QAAQ,EAAE;IAIxB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS;IAI7C,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAI7B,sFAAsF;IACtF,QAAQ,IAAI,WAAW,EAAE;IAczB,OAAO,CAAC,OAAO;IAgBf,OAAO,CAAC,SAAS;IAUjB;;;OAGG;IACI,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,cAAc,CAAC,QAAQ,CAAC;IAyB7E,iFAAiF;IAC3E,QAAQ,CACZ,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,QAAQ,GAAG;YAAE,IAAI,EAAE,OAAO,CAAA;SAAE,CAAA;KAAE,CAAC;IAehF,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAI/B;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,YAAY,EAAE,CA6BtE"}
@@ -0,0 +1,194 @@
1
+ /**
2
+ * `AcpManager` — the public engine entry point.
3
+ *
4
+ * Owns a lazy pool of `AgentProcess` (one per agent id), a per-agent concurrency
5
+ * semaphore, and the `prompt()` async-iterator that in-process consumers and the HTTP
6
+ * server both build on. Renders chat messages into an ACP prompt (SPEC §5) and yields the
7
+ * structured `AcpEvent` stream.
8
+ */
9
+ import { AgentProcess } from "./agent-process.js";
10
+ import { createLogger } from "./logger.js";
11
+ /** Counting semaphore for bounding concurrent turns per agent. */
12
+ class Semaphore {
13
+ limit;
14
+ active = 0;
15
+ queue = [];
16
+ constructor(limit) {
17
+ this.limit = limit;
18
+ }
19
+ acquire() {
20
+ return new Promise((resolve) => {
21
+ const attempt = () => {
22
+ if (this.active < this.limit) {
23
+ this.active++;
24
+ resolve(() => this.release());
25
+ }
26
+ else {
27
+ this.queue.push(attempt);
28
+ }
29
+ };
30
+ attempt();
31
+ });
32
+ }
33
+ release() {
34
+ this.active--;
35
+ const next = this.queue.shift();
36
+ if (next)
37
+ next();
38
+ }
39
+ }
40
+ /** Parse an OpenAI `model` value of the form `agentId` or `agentId:submodel`. */
41
+ export function resolveModel(model) {
42
+ const idx = model.indexOf(":");
43
+ if (idx === -1)
44
+ return { id: model };
45
+ return { id: model.slice(0, idx), submodel: model.slice(idx + 1) };
46
+ }
47
+ export class AcpManager {
48
+ config;
49
+ logger;
50
+ defs = new Map();
51
+ processes = new Map();
52
+ semaphores = new Map();
53
+ constructor(config, logger = createLogger()) {
54
+ this.config = config;
55
+ this.logger = logger;
56
+ for (const def of config.agents)
57
+ this.defs.set(def.id, def);
58
+ }
59
+ /** Enabled agent definitions. */
60
+ listAgents() {
61
+ return [...this.defs.values()];
62
+ }
63
+ getAgentDef(id) {
64
+ return this.defs.get(id);
65
+ }
66
+ hasAgent(id) {
67
+ return this.defs.has(id);
68
+ }
69
+ /** Status snapshot for every agent that has been touched (plus idle for the rest). */
70
+ statuses() {
71
+ return this.listAgents().map((def) => {
72
+ const proc = this.processes.get(def.id);
73
+ return (proc?.status() ?? {
74
+ id: def.id,
75
+ name: def.name,
76
+ state: "idle",
77
+ activeSessions: 0,
78
+ });
79
+ });
80
+ }
81
+ process(id) {
82
+ let proc = this.processes.get(id);
83
+ if (!proc) {
84
+ const def = this.defs.get(id);
85
+ if (!def)
86
+ throw new Error(`unknown agent: ${id}`);
87
+ proc = new AgentProcess({
88
+ def,
89
+ workspaceRoot: this.config.workspaceRoot,
90
+ keepWorkspaces: this.config.keepWorkspaces,
91
+ logger: this.logger,
92
+ });
93
+ this.processes.set(id, proc);
94
+ }
95
+ return proc;
96
+ }
97
+ semaphore(id) {
98
+ let sem = this.semaphores.get(id);
99
+ if (!sem) {
100
+ const def = this.defs.get(id);
101
+ sem = new Semaphore(def?.maxConcurrentSessions ?? this.config.defaults.maxConcurrentSessions);
102
+ this.semaphores.set(id, sem);
103
+ }
104
+ return sem;
105
+ }
106
+ /**
107
+ * Drive one prompt turn against `agentId` (or `agentId:submodel`). Returns an async
108
+ * iterator of structured events ending in `done` (or `error`).
109
+ */
110
+ async *prompt(agentId, opts) {
111
+ const { id } = resolveModel(agentId);
112
+ if (!this.defs.has(id)) {
113
+ yield { type: "error", message: `unknown agent: ${id}` };
114
+ return;
115
+ }
116
+ const proc = this.process(id);
117
+ const release = await this.semaphore(id).acquire();
118
+ try {
119
+ const session = await proc.createSession(opts.workspace);
120
+ const prompt = renderMessages(opts.messages);
121
+ // Drive the turn concurrently; it ends the session, closing the event stream.
122
+ const turn = proc.runPrompt(session, prompt, opts.signal);
123
+ for await (const ev of session.events()) {
124
+ yield ev;
125
+ }
126
+ await turn;
127
+ }
128
+ catch (err) {
129
+ yield { type: "error", message: err.message ?? String(err) };
130
+ }
131
+ finally {
132
+ release();
133
+ }
134
+ }
135
+ /** Convenience: run a turn and collect the full assistant text + stop reason. */
136
+ async complete(agentId, opts) {
137
+ let text = "";
138
+ let stopReason = "end_turn";
139
+ let usage;
140
+ let errored;
141
+ for await (const ev of this.prompt(agentId, opts)) {
142
+ if (ev.type === "text")
143
+ text += ev.text;
144
+ else if (ev.type === "usage")
145
+ usage = ev;
146
+ else if (ev.type === "done")
147
+ stopReason = ev.stopReason;
148
+ else if (ev.type === "error")
149
+ errored = ev.message;
150
+ }
151
+ if (errored && !text)
152
+ throw new Error(errored);
153
+ return { text, stopReason, ...(usage ? { usage } : {}) };
154
+ }
155
+ async dispose() {
156
+ await Promise.all([...this.processes.values()].map((p) => p.dispose()));
157
+ this.processes.clear();
158
+ }
159
+ }
160
+ /**
161
+ * Render chat messages into ACP prompt content (SPEC §5). Version 0.1.0 uses ephemeral sessions,
162
+ * so the conversation is flattened into the turn: a single text block carrying any system
163
+ * instructions plus the transcript, with the trailing user message as the active ask.
164
+ */
165
+ export function renderMessages(messages) {
166
+ const system = messages
167
+ .filter((m) => m.role === "system")
168
+ .map((m) => m.content)
169
+ .join("\n\n")
170
+ .trim();
171
+ const convo = messages.filter((m) => m.role !== "system");
172
+ if (convo.length === 1 && convo[0]?.role === "user") {
173
+ const only = convo[0].content;
174
+ const text = system ? `${system}\n\n${only}` : only;
175
+ return [{ type: "text", text }];
176
+ }
177
+ const lines = [];
178
+ if (system)
179
+ lines.push(`# Instructions\n${system}`);
180
+ if (convo.length > 1)
181
+ lines.push(`# Conversation`);
182
+ for (const m of convo) {
183
+ const who = m.role === "user"
184
+ ? "User"
185
+ : m.role === "assistant"
186
+ ? "Assistant"
187
+ : m.role === "tool"
188
+ ? `Tool${m.name ? `(${m.name})` : ""}`
189
+ : "Message";
190
+ lines.push(`\n## ${who}\n${m.content}`);
191
+ }
192
+ return [{ type: "text", text: lines.join("\n").trim() }];
193
+ }
194
+ //# sourceMappingURL=manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manager.js","sourceRoot":"","sources":["../src/manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Cross-platform command resolution WITHOUT `shell: true`.
3
+ *
4
+ * Why: `spawn(cmd, args, { shell: true })` concatenates args into a shell string (Node
5
+ * DEP0190) and is an injection surface. We never want a shell. But on Windows, Node refuses
6
+ * to `spawn` a `.cmd`/`.bat` file with `shell:false` (post-CVE-2024-27980), and most native
7
+ * CLIs installed via npm ARE `.cmd` shims. So:
8
+ *
9
+ * - POSIX → spawn(command, args) unchanged.
10
+ * - Windows .exe/bin → spawn(resolvedPath, args).
11
+ * - Windows .cmd/.bat→ spawn(comspec, ["/c", resolvedPath, ...args]) (explicit cmd.exe,
12
+ * still shell:false; our args are passed as argv, never a shell line).
13
+ *
14
+ * The `command` is always treated as a SINGLE executable token — it is never split, and a
15
+ * raw command line containing shell metacharacters is rejected. Callers must keep `args`
16
+ * separate; `args` are passed literally (no shell), so metacharacters in args are inert.
17
+ *
18
+ * `resolveCommand` takes injectable `platform`/`lookup` so it is deterministically testable
19
+ * on any OS.
20
+ */
21
+ export interface ResolveOptions {
22
+ platform?: NodeJS.Platform;
23
+ /** Returns an absolute resolved path for a bare command, or null if not found. */
24
+ lookup?: (command: string) => string | null;
25
+ /** Override for the Windows command interpreter (defaults to %ComSpec% or cmd.exe). */
26
+ comspec?: string;
27
+ /** PATH string (defaults to process.env.PATH). */
28
+ pathEnv?: string;
29
+ /** PATHEXT string (defaults to process.env.PATHEXT or a sane Windows default). */
30
+ pathExt?: string;
31
+ }
32
+ export interface ResolvedSpawn {
33
+ /** Executable to hand to `spawn` (with shell:false). */
34
+ file: string;
35
+ /** Arguments to hand to `spawn`. */
36
+ args: string[];
37
+ /** Always false — this module exists specifically to avoid a shell. */
38
+ shell: false;
39
+ /** True when the target was routed through cmd.exe /c (a Windows .cmd/.bat shim). */
40
+ viaCmd: boolean;
41
+ }
42
+ /** Throw if `command` looks like a shell command line rather than a single executable. */
43
+ export declare function assertExecutableToken(command: string): void;
44
+ /** Default Windows PATH+PATHEXT search for a bare command name. */
45
+ export declare function windowsLookup(command: string, opts?: ResolveOptions): string | null;
46
+ /**
47
+ * Resolve a command + args into a `spawn(file, args, { shell:false })` invocation.
48
+ */
49
+ export declare function resolveCommand(command: string, args: string[], opts?: ResolveOptions): ResolvedSpawn;
50
+ //# sourceMappingURL=process-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process-resolver.d.ts","sourceRoot":"","sources":["../src/process-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAQH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,kFAAkF;IAClF,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IAC5C,uFAAuF;IACvF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,oCAAoC;IACpC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,uEAAuE;IACvE,KAAK,EAAE,KAAK,CAAC;IACb,qFAAqF;IACrF,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,0FAA0F;AAC1F,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAS3D;AAED,mEAAmE;AACnE,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,cAAmB,GAAG,MAAM,GAAG,IAAI,CAmBvF;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,GAAE,cAAmB,GACxB,aAAa,CAqBf"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Cross-platform command resolution WITHOUT `shell: true`.
3
+ *
4
+ * Why: `spawn(cmd, args, { shell: true })` concatenates args into a shell string (Node
5
+ * DEP0190) and is an injection surface. We never want a shell. But on Windows, Node refuses
6
+ * to `spawn` a `.cmd`/`.bat` file with `shell:false` (post-CVE-2024-27980), and most native
7
+ * CLIs installed via npm ARE `.cmd` shims. So:
8
+ *
9
+ * - POSIX → spawn(command, args) unchanged.
10
+ * - Windows .exe/bin → spawn(resolvedPath, args).
11
+ * - Windows .cmd/.bat→ spawn(comspec, ["/c", resolvedPath, ...args]) (explicit cmd.exe,
12
+ * still shell:false; our args are passed as argv, never a shell line).
13
+ *
14
+ * The `command` is always treated as a SINGLE executable token — it is never split, and a
15
+ * raw command line containing shell metacharacters is rejected. Callers must keep `args`
16
+ * separate; `args` are passed literally (no shell), so metacharacters in args are inert.
17
+ *
18
+ * `resolveCommand` takes injectable `platform`/`lookup` so it is deterministically testable
19
+ * on any OS.
20
+ */
21
+ import { existsSync } from "node:fs";
22
+ import { join, isAbsolute } from "node:path";
23
+ /** Shell metacharacters that must never appear in an executable token. */
24
+ const SHELL_METACHARS = /[&|;<>$`\n\r]/;
25
+ /** Throw if `command` looks like a shell command line rather than a single executable. */
26
+ export function assertExecutableToken(command) {
27
+ if (!command || !command.trim()) {
28
+ throw new Error("empty command");
29
+ }
30
+ if (SHELL_METACHARS.test(command)) {
31
+ throw new Error(`refusing to spawn: command contains shell metacharacters (treated as a single executable token): ${JSON.stringify(command)}`);
32
+ }
33
+ }
34
+ /** Default Windows PATH+PATHEXT search for a bare command name. */
35
+ export function windowsLookup(command, opts = {}) {
36
+ const pathEnv = opts.pathEnv ?? process.env.PATH ?? "";
37
+ const pathExt = (opts.pathExt ?? process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";");
38
+ const dirs = pathEnv.split(";").filter(Boolean);
39
+ // If it already has an extension or a path separator, only check it directly.
40
+ const hasExt = /\.[a-z0-9]+$/i.test(command);
41
+ const candidates = [];
42
+ for (const dir of dirs) {
43
+ if (hasExt) {
44
+ candidates.push(join(dir, command));
45
+ }
46
+ else {
47
+ for (const ext of pathExt)
48
+ candidates.push(join(dir, command + ext));
49
+ }
50
+ }
51
+ for (const c of candidates) {
52
+ if (existsSync(c))
53
+ return c;
54
+ }
55
+ return null;
56
+ }
57
+ /**
58
+ * Resolve a command + args into a `spawn(file, args, { shell:false })` invocation.
59
+ */
60
+ export function resolveCommand(command, args, opts = {}) {
61
+ assertExecutableToken(command);
62
+ const platform = opts.platform ?? process.platform;
63
+ if (platform !== "win32") {
64
+ // POSIX: spawn handles PATH lookup and direct paths fine. Command is unchanged.
65
+ return { file: command, args: [...args], shell: false, viaCmd: false };
66
+ }
67
+ // Windows.
68
+ const hasSep = command.includes("\\") || command.includes("/");
69
+ const resolved = hasSep || isAbsolute(command)
70
+ ? command
71
+ : (opts.lookup ? opts.lookup(command) : windowsLookup(command, opts)) ?? command;
72
+ if (/\.(cmd|bat)$/i.test(resolved)) {
73
+ const comspec = opts.comspec ?? process.env.ComSpec ?? "cmd.exe";
74
+ return { file: comspec, args: ["/c", resolved, ...args], shell: false, viaCmd: true };
75
+ }
76
+ return { file: resolved, args: [...args], shell: false, viaCmd: false };
77
+ }
78
+ //# sourceMappingURL=process-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process-resolver.js","sourceRoot":"","sources":["../src/process-resolver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Built-in ACP agent definitions.
3
+ *
4
+ * These are merged with the user's config: a config `agents` entry with the same `id`
5
+ * overrides the matching built-in field-by-field; an entry with a new id adds an agent.
6
+ * Adding/changing an agent is therefore data, not code (CLAUDE.md rule 6).
7
+ *
8
+ * Launch commands reflect the current ecosystem:
9
+ * - claude-code / codex: no native ACP — bridged by the official Zed ACP adapters.
10
+ * - opencode / gemini / kimi: native ACP (`opencode acp`, `gemini --acp`, `kimi acp`).
11
+ * - glm: ACP Registry package (`npx -y glm-acp-agent@latest`).
12
+ *
13
+ * Each agent authenticates with the operator's OWN provider credentials (SPEC §9).
14
+ */
15
+ import type { AgentDef } from "./types.js";
16
+ export declare const BUILTIN_AGENTS: Record<string, AgentDef>;
17
+ /** Merge a partial config entry onto a built-in (if one exists for the id). */
18
+ export declare function mergeAgentDef(entry: Partial<AgentDef> & {
19
+ id: string;
20
+ }): AgentDef;
21
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CA6CnD,CAAC;AAEF,+EAA+E;AAC/E,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG,QAAQ,CAWjF"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Built-in ACP agent definitions.
3
+ *
4
+ * These are merged with the user's config: a config `agents` entry with the same `id`
5
+ * overrides the matching built-in field-by-field; an entry with a new id adds an agent.
6
+ * Adding/changing an agent is therefore data, not code (CLAUDE.md rule 6).
7
+ *
8
+ * Launch commands reflect the current ecosystem:
9
+ * - claude-code / codex: no native ACP — bridged by the official Zed ACP adapters.
10
+ * - opencode / gemini / kimi: native ACP (`opencode acp`, `gemini --acp`, `kimi acp`).
11
+ * - glm: ACP Registry package (`npx -y glm-acp-agent@latest`).
12
+ *
13
+ * Each agent authenticates with the operator's OWN provider credentials (SPEC §9).
14
+ */
15
+ export const BUILTIN_AGENTS = {
16
+ "claude-code": {
17
+ id: "claude-code",
18
+ name: "Claude Code",
19
+ cmd: "npx",
20
+ args: ["-y", "@zed-industries/claude-code-acp"],
21
+ defaultModel: null,
22
+ // Don't inherit a parent Claude Code session's markers (nested-session guard).
23
+ unsetEnv: ["CLAUDECODE", "CLAUDE_CODE_ENTRYPOINT", "CLAUDE_CODE_SSE_PORT"],
24
+ },
25
+ codex: {
26
+ id: "codex",
27
+ name: "Codex CLI",
28
+ cmd: "npx",
29
+ args: ["-y", "@zed-industries/codex-acp"],
30
+ defaultModel: null,
31
+ },
32
+ opencode: {
33
+ id: "opencode",
34
+ name: "OpenCode",
35
+ cmd: "opencode",
36
+ args: ["acp"],
37
+ defaultModel: null,
38
+ },
39
+ gemini: {
40
+ id: "gemini",
41
+ name: "Gemini CLI",
42
+ cmd: "gemini",
43
+ args: ["--acp"],
44
+ defaultModel: null,
45
+ },
46
+ kimi: {
47
+ id: "kimi",
48
+ name: "Kimi CLI",
49
+ cmd: "kimi",
50
+ args: ["acp"],
51
+ defaultModel: null,
52
+ },
53
+ glm: {
54
+ id: "glm",
55
+ name: "GLM Agent",
56
+ cmd: "npx",
57
+ args: ["-y", "glm-acp-agent@latest"],
58
+ defaultModel: null,
59
+ },
60
+ };
61
+ /** Merge a partial config entry onto a built-in (if one exists for the id). */
62
+ export function mergeAgentDef(entry) {
63
+ const base = BUILTIN_AGENTS[entry.id];
64
+ const merged = { ...(base ?? {}), ...entry };
65
+ if (!merged.cmd) {
66
+ throw new Error(`Agent "${entry.id}" has no built-in default and no "cmd" in config — cannot launch it.`);
67
+ }
68
+ if (!merged.name)
69
+ merged.name = entry.id;
70
+ if (!merged.args)
71
+ merged.args = [];
72
+ return merged;
73
+ }
74
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,MAAM,CAAC,MAAM,cAAc,GAA6B;IACtD,aAAa,EAAE;QACb,EAAE,EAAE,aAAa;QACjB,IAAI,EAAE,aAAa;QACnB,GAAG,EAAE,KAAK;QACV,IAAI,EAAE,CAAC,IAAI,EAAE,iCAAiC,CAAC;QAC/C,YAAY,EAAE,IAAI;QAClB,+EAA+E;QAC/E,QAAQ,EAAE,CAAC,YAAY,EAAE,wBAAwB,EAAE,sBAAsB,CAAC;KAC3E;IACD,KAAK,EAAE;QACL,EAAE,EAAE,OAAO;QACX,IAAI,EAAE,WAAW;QACjB,GAAG,EAAE,KAAK;QACV,IAAI,EAAE,CAAC,IAAI,EAAE,2BAA2B,CAAC;QACzC,YAAY,EAAE,IAAI;KACnB;IACD,QAAQ,EAAE;QACR,EAAE,EAAE,UAAU;QACd,IAAI,EAAE,UAAU;QAChB,GAAG,EAAE,UAAU;QACf,IAAI,EAAE,CAAC,KAAK,CAAC;QACb,YAAY,EAAE,IAAI;KACnB;IACD,MAAM,EAAE;QACN,EAAE,EAAE,QAAQ;QACZ,IAAI,EAAE,YAAY;QAClB,GAAG,EAAE,QAAQ;QACb,IAAI,EAAE,CAAC,OAAO,CAAC;QACf,YAAY,EAAE,IAAI;KACnB;IACD,IAAI,EAAE;QACJ,EAAE,EAAE,MAAM;QACV,IAAI,EAAE,UAAU;QAChB,GAAG,EAAE,MAAM;QACX,IAAI,EAAE,CAAC,KAAK,CAAC;QACb,YAAY,EAAE,IAAI;KACnB;IACD,GAAG,EAAE;QACH,EAAE,EAAE,KAAK;QACT,IAAI,EAAE,WAAW;QACjB,GAAG,EAAE,KAAK;QACV,IAAI,EAAE,CAAC,IAAI,EAAE,sBAAsB,CAAC;QACpC,YAAY,EAAE,IAAI;KACnB;CACF,CAAC;AAEF,+EAA+E;AAC/E,MAAM,UAAU,aAAa,CAAC,KAAyC;IACrE,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,GAAG,KAAK,EAAc,CAAC;IACzD,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CACb,UAAU,KAAK,CAAC,EAAE,sEAAsE,CACzF,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,IAAI;QAAE,MAAM,CAAC,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC;IACzC,IAAI,CAAC,MAAM,CAAC,IAAI;QAAE,MAAM,CAAC,IAAI,GAAG,EAAE,CAAC;IACnC,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * A `Session` wraps one ACP session id and an async event queue.
3
+ *
4
+ * The per-connection client handler dispatches `session/update` notifications here via
5
+ * `onUpdate`; the consumer (AcpManager / an embedder) drains `events()` as an async
6
+ * iterable. The queue closes when the prompt turn ends, so the iterator terminates
7
+ * naturally after the trailing `done` event.
8
+ */
9
+ import type { SessionUpdate, StopReason } from "@agentclientprotocol/sdk";
10
+ import type { AcpEvent } from "./types.js";
11
+ import type { Workspace } from "./workspace.js";
12
+ /** Convert a raw ACP session update into zero or more structured `AcpEvent`s. */
13
+ export declare function updateToEvents(update: SessionUpdate): AcpEvent[];
14
+ export declare class Session {
15
+ readonly id: string;
16
+ readonly workspace: Workspace;
17
+ private queue;
18
+ private ended;
19
+ private rawUpdateHandler?;
20
+ constructor(id: string, workspace: Workspace, rawUpdateHandler?: (update: SessionUpdate) => void);
21
+ /** Feed a raw ACP update (called by the connection's client handler). */
22
+ onUpdate(update: SessionUpdate): void;
23
+ /** Temporarily observe raw, SDK-validated ACP updates for an embedding host. */
24
+ setRawUpdateHandler(handler?: (update: SessionUpdate) => void): void;
25
+ /** Push a synthesized event (usage, error, etc.). */
26
+ push(event: AcpEvent): void;
27
+ /** Emit the terminal `done` event and close the stream. */
28
+ end(stopReason: StopReason): void;
29
+ /** Emit an error and close the stream. */
30
+ fail(message: string): void;
31
+ /** Async-iterate the event stream until the turn ends. */
32
+ events(): AsyncIterableIterator<AcpEvent>;
33
+ }
34
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC1E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAyChD,iFAAiF;AACjF,wBAAgB,cAAc,CAAC,MAAM,EAAE,aAAa,GAAG,QAAQ,EAAE,CA6ChE;AAED,qBAAa,OAAO;IAClB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,OAAO,CAAC,KAAK,CAA8B;IAC3C,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,gBAAgB,CAAC,CAAkC;gBAE/C,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI;IAMhG,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI;IAKrC,gFAAgF;IAChF,mBAAmB,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,GAAG,IAAI;IAIpE,qDAAqD;IACrD,IAAI,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI;IAI3B,2DAA2D;IAC3D,GAAG,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI;IAOjC,0CAA0C;IAC1C,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAO3B,0DAA0D;IAC1D,MAAM,IAAI,qBAAqB,CAAC,QAAQ,CAAC;CAG1C"}