@anhlp/alp 0.0.0-beta.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 (175) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +7 -0
  3. package/README.md +416 -0
  4. package/dist/THIRD-PARTY-NOTICES.txt +626 -0
  5. package/dist/acp.js +19723 -0
  6. package/dist/alpd.js +41053 -0
  7. package/docs/modular-monorepo.md +210 -0
  8. package/package.json +126 -0
  9. package/packages/cli/README.md +7 -0
  10. package/packages/cli/dist/THIRD-PARTY-NOTICES.txt +626 -0
  11. package/packages/cli/dist/acp/agent.d.ts +29 -0
  12. package/packages/cli/dist/acp/agent.js +390 -0
  13. package/packages/cli/dist/acp/main.d.ts +4 -0
  14. package/packages/cli/dist/acp/main.js +23 -0
  15. package/packages/cli/dist/acp.js +19723 -0
  16. package/packages/cli/dist/alpd.js +41053 -0
  17. package/packages/cli/dist/backup.d.ts +4 -0
  18. package/packages/cli/dist/backup.js +146 -0
  19. package/packages/cli/dist/build-manifest.json +139 -0
  20. package/packages/cli/dist/cli.d.ts +2 -0
  21. package/packages/cli/dist/cli.js +24 -0
  22. package/packages/cli/dist/daemon.d.ts +35 -0
  23. package/packages/cli/dist/daemon.js +122 -0
  24. package/packages/cli/dist/index.d.ts +1 -0
  25. package/packages/cli/dist/index.js +5 -0
  26. package/packages/cli/dist/interfaces.d.ts +16 -0
  27. package/packages/cli/dist/interfaces.js +96 -0
  28. package/packages/cli/dist/library.d.ts +140 -0
  29. package/packages/cli/dist/library.js +319 -0
  30. package/packages/cli/dist/paths.d.ts +3 -0
  31. package/packages/cli/dist/paths.js +18 -0
  32. package/packages/cli/dist/projects.d.ts +9 -0
  33. package/packages/cli/dist/projects.js +159 -0
  34. package/packages/cli/dist/sessions.d.ts +39 -0
  35. package/packages/cli/dist/sessions.js +425 -0
  36. package/packages/cli/dist/tasks.d.ts +29 -0
  37. package/packages/cli/dist/tasks.js +345 -0
  38. package/packages/cli/dist/usage.d.ts +3 -0
  39. package/packages/cli/dist/usage.js +72 -0
  40. package/packages/client/README.md +24 -0
  41. package/packages/client/dist/acp-probe.d.ts +44 -0
  42. package/packages/client/dist/acp-probe.js +37 -0
  43. package/packages/client/dist/antigravity-probe.d.ts +34 -0
  44. package/packages/client/dist/antigravity-probe.js +95 -0
  45. package/packages/client/dist/command.d.ts +5 -0
  46. package/packages/client/dist/command.js +33 -0
  47. package/packages/client/dist/doctor.d.ts +13 -0
  48. package/packages/client/dist/doctor.js +587 -0
  49. package/packages/client/dist/index.d.ts +101 -0
  50. package/packages/client/dist/index.js +422 -0
  51. package/packages/client/dist/ipc.d.ts +42 -0
  52. package/packages/client/dist/ipc.js +69 -0
  53. package/packages/client/dist/library-test.d.ts +94 -0
  54. package/packages/client/dist/library-test.js +37 -0
  55. package/packages/client/dist/mcp-probe.d.ts +24 -0
  56. package/packages/client/dist/mcp-probe.js +147 -0
  57. package/packages/client/dist/portable.d.ts +18 -0
  58. package/packages/client/dist/portable.js +27 -0
  59. package/packages/client/dist/prompt.d.ts +32 -0
  60. package/packages/client/dist/prompt.js +59 -0
  61. package/packages/client/dist/render.d.ts +6 -0
  62. package/packages/client/dist/render.js +120 -0
  63. package/packages/client/dist/rpc/client.d.ts +82 -0
  64. package/packages/client/dist/rpc/client.js +292 -0
  65. package/packages/client/dist/rpc/errors.d.ts +42 -0
  66. package/packages/client/dist/rpc/errors.js +34 -0
  67. package/packages/client/dist/rpc/framing.d.ts +21 -0
  68. package/packages/client/dist/rpc/framing.js +57 -0
  69. package/packages/client/dist/rpc/handshake.d.ts +19 -0
  70. package/packages/client/dist/rpc/handshake.js +8 -0
  71. package/packages/client/dist/rpc/reconnect.d.ts +97 -0
  72. package/packages/client/dist/rpc/reconnect.js +227 -0
  73. package/packages/client/dist/rpc/timers.d.ts +11 -0
  74. package/packages/client/dist/rpc/timers.js +11 -0
  75. package/packages/client/dist/service.d.ts +50 -0
  76. package/packages/client/dist/service.js +143 -0
  77. package/packages/client/dist/setup.d.ts +2 -0
  78. package/packages/client/dist/setup.js +146 -0
  79. package/packages/client/dist/supervise.d.ts +12 -0
  80. package/packages/client/dist/supervise.js +81 -0
  81. package/packages/client/dist/websocket.d.ts +51 -0
  82. package/packages/client/dist/websocket.js +190 -0
  83. package/packages/core/README.md +7 -0
  84. package/packages/core/dist/adapter.d.ts +20 -0
  85. package/packages/core/dist/adapter.js +30 -0
  86. package/packages/core/dist/atomic.d.ts +2 -0
  87. package/packages/core/dist/atomic.js +28 -0
  88. package/packages/core/dist/backup.d.ts +27 -0
  89. package/packages/core/dist/backup.js +582 -0
  90. package/packages/core/dist/beads.d.ts +65 -0
  91. package/packages/core/dist/beads.js +213 -0
  92. package/packages/core/dist/delegation.d.ts +2 -0
  93. package/packages/core/dist/delegation.js +32 -0
  94. package/packages/core/dist/errors.d.ts +4 -0
  95. package/packages/core/dist/errors.js +11 -0
  96. package/packages/core/dist/formulas.d.ts +25 -0
  97. package/packages/core/dist/formulas.js +201 -0
  98. package/packages/core/dist/hook-run.d.ts +44 -0
  99. package/packages/core/dist/hook-run.js +60 -0
  100. package/packages/core/dist/hooks.d.ts +35 -0
  101. package/packages/core/dist/hooks.js +30 -0
  102. package/packages/core/dist/index.d.ts +30 -0
  103. package/packages/core/dist/index.js +63 -0
  104. package/packages/core/dist/init.d.ts +30 -0
  105. package/packages/core/dist/init.js +88 -0
  106. package/packages/core/dist/library-edit.d.ts +90 -0
  107. package/packages/core/dist/library-edit.js +315 -0
  108. package/packages/core/dist/library.d.ts +28 -0
  109. package/packages/core/dist/library.js +108 -0
  110. package/packages/core/dist/log.d.ts +37 -0
  111. package/packages/core/dist/log.js +156 -0
  112. package/packages/core/dist/migrations.d.ts +9 -0
  113. package/packages/core/dist/migrations.js +95 -0
  114. package/packages/core/dist/models.d.ts +11 -0
  115. package/packages/core/dist/models.js +44 -0
  116. package/packages/core/dist/permissions.d.ts +14 -0
  117. package/packages/core/dist/permissions.js +229 -0
  118. package/packages/core/dist/profiles.d.ts +27 -0
  119. package/packages/core/dist/profiles.js +122 -0
  120. package/packages/core/dist/promptsafe.d.ts +1 -0
  121. package/packages/core/dist/promptsafe.js +13 -0
  122. package/packages/core/dist/providers.d.ts +90 -0
  123. package/packages/core/dist/providers.js +101 -0
  124. package/packages/core/dist/resolver.d.ts +41 -0
  125. package/packages/core/dist/resolver.js +240 -0
  126. package/packages/core/dist/safe-path.d.ts +2 -0
  127. package/packages/core/dist/safe-path.js +24 -0
  128. package/packages/core/dist/shell.d.ts +16 -0
  129. package/packages/core/dist/shell.js +22 -0
  130. package/packages/core/dist/state-schema.d.ts +9 -0
  131. package/packages/core/dist/state-schema.js +54 -0
  132. package/packages/core/dist/task-rows.d.ts +47 -0
  133. package/packages/core/dist/task-rows.js +45 -0
  134. package/packages/core/dist/tasks.d.ts +128 -0
  135. package/packages/core/dist/tasks.js +835 -0
  136. package/packages/core/dist/teams.d.ts +69 -0
  137. package/packages/core/dist/teams.js +159 -0
  138. package/packages/core/dist/template-path.d.ts +2 -0
  139. package/packages/core/dist/template-path.js +10 -0
  140. package/packages/core/dist/trust.d.ts +8 -0
  141. package/packages/core/dist/trust.js +47 -0
  142. package/packages/core/dist/types.d.ts +51 -0
  143. package/packages/core/dist/upgrade.d.ts +10 -0
  144. package/packages/core/dist/upgrade.js +200 -0
  145. package/packages/core/dist/user-settings.d.ts +6 -0
  146. package/packages/core/dist/user-settings.js +72 -0
  147. package/packages/core/dist/validation.d.ts +17 -0
  148. package/packages/core/dist/validation.js +295 -0
  149. package/packages/core/dist/verify.d.ts +15 -0
  150. package/packages/core/dist/verify.js +123 -0
  151. package/packages/core/dist/workflow.d.ts +14 -0
  152. package/packages/core/dist/workflow.js +24 -0
  153. package/templates/ALP.md +52 -0
  154. package/templates/agents/lead/AGENT.md +90 -0
  155. package/templates/agents/main/AGENT.md +150 -0
  156. package/templates/agents/oracle/AGENT.md +44 -0
  157. package/templates/agents/peer/AGENT.md +55 -0
  158. package/templates/agents/reviewer/AGENT.md +80 -0
  159. package/templates/agents/supervisor/AGENT.md +72 -0
  160. package/templates/providers/antigravity.json +6 -0
  161. package/templates/providers/opencode.json +7 -0
  162. package/templates/role-skills.json +8 -0
  163. package/templates/skills/bug-loop/SKILL.md +44 -0
  164. package/templates/skills/bug-loop/references/test-proof.md +25 -0
  165. package/templates/skills/goal-griller/SKILL.md +49 -0
  166. package/templates/skills/prompt-leverage/SKILL.md +37 -0
  167. package/templates/skills/prompt-leverage/references/brief-template.md +27 -0
  168. package/templates/skills/sequence-execution-plan/SKILL.md +42 -0
  169. package/templates/skills/sequence-execution-plan/references/plan-template.md +22 -0
  170. package/templates/skills/smart-commits/SKILL.md +40 -0
  171. package/templates/skills/xia/SKILL.md +42 -0
  172. package/templates/teams/cafe/HOUSE_RULES.md +1 -0
  173. package/templates/teams/cafe/team.json +15 -0
  174. package/templates/teams/pho/HOUSE_RULES.md +1 -0
  175. package/templates/teams/pho/team.json +14 -0
@@ -0,0 +1,390 @@
1
+ // packages/cli/src/acp/agent.ts
2
+ import * as acp from "@agentclientprotocol/sdk";
3
+ import { randomUUID } from "node:crypto";
4
+ import path from "node:path";
5
+ var MODES = [
6
+ { id: "read-only", name: "Read only", description: "Agents read and search; nothing is changed" },
7
+ { id: "workspace-write", name: "Workspace write", description: "Agents change files and run commands inside the project" },
8
+ { id: "full-access", name: "Full access", description: "Agents may do anything, without a sandbox" }
9
+ ];
10
+ var OUTPUT_CHARS = 2e4;
11
+ var COMMANDS = [
12
+ { name: "answer", description: "Answer the question an agent asked you", input: { hint: "your answer" } },
13
+ { name: "dismiss", description: "Dismiss the question an agent asked you", input: { hint: "why (optional)" } }
14
+ ];
15
+ function hostServers(servers = []) {
16
+ const host = {};
17
+ for (const server of servers) {
18
+ if ("command" in server) host[server.name] = { type: "stdio", command: server.command, args: server.args, env: Object.fromEntries(server.env.map((variable) => [variable.name, variable.value])) };
19
+ else if (server.type === "http" || server.type === "sse") host[server.name] = { type: server.type, url: server.url, headers: Object.fromEntries(server.headers.map((header) => [header.name, header.value])) };
20
+ }
21
+ return host;
22
+ }
23
+ function promptText(blocks) {
24
+ let text = "";
25
+ for (const block of blocks) {
26
+ if (block.type === "text") {
27
+ text += block.text;
28
+ continue;
29
+ }
30
+ let part = "";
31
+ if (block.type === "resource_link") part = `@${block.uri.replace(/^file:\/\//, "")}`;
32
+ else if (block.type === "resource") {
33
+ const { resource } = block;
34
+ const where = resource.uri.replace(/^file:\/\//, "");
35
+ part = "text" in resource ? `
36
+ <file path="${where}">
37
+ ${resource.text}
38
+ </file>
39
+ ` : `@${where}`;
40
+ }
41
+ if (part && text && !/\s$/.test(text) && !part.startsWith("\n")) text += " ";
42
+ text += part;
43
+ }
44
+ return text.trim();
45
+ }
46
+ function toolKind(item) {
47
+ const word = item.detail.type === "shell" ? String(item.detail.command ?? "").trim().split(/[\s{(]/)[0] : item.name;
48
+ if (/^(Read|NotebookRead|LS)$/.test(word)) return "read";
49
+ if (/^(Edit|MultiEdit|Write|NotebookEdit)$/.test(word)) return "edit";
50
+ if (/^(Grep|Glob)$/.test(word)) return "search";
51
+ if (/^(WebFetch|WebSearch)$/.test(word)) return "fetch";
52
+ if (/^(Skill|Task|TodoWrite)$/.test(word) || item.name.startsWith("alp_")) return "other";
53
+ return item.detail.type === "shell" ? "execute" : "other";
54
+ }
55
+ function toolTitle(item) {
56
+ if (item.detail.type === "shell") return clip(String(item.detail.command ?? "").split("\n")[0], 160);
57
+ const input = item.detail.input ?? {};
58
+ if (item.name === "alp_delegate") return clip(`Delegate to ${input.agent ?? "an agent"}: ${String(input.task ?? "").split("\n")[0]}`, 160);
59
+ return clip(`${item.name} ${JSON.stringify(input)}`, 160);
60
+ }
61
+ function toolOutput(item) {
62
+ let text = "";
63
+ if (item.detail.type === "shell") text = item.detail.output ?? "";
64
+ else {
65
+ const output = item.detail.output;
66
+ text = Array.isArray(output) ? output.map((part) => typeof part?.text === "string" ? part.text : JSON.stringify(part)).join("\n") : output == null ? "" : typeof output === "string" ? output : JSON.stringify(output, null, 2);
67
+ }
68
+ if (item.error) text = text ? `${text}
69
+ ${item.error}` : item.error;
70
+ if (!text) return [];
71
+ if (text.length > OUTPUT_CHARS) text = `${text.slice(0, OUTPUT_CHARS)}
72
+ \u2026 (${text.length - OUTPUT_CHARS} more characters)`;
73
+ return [{ type: "content", content: { type: "text", text } }];
74
+ }
75
+ var clip = (text, chars) => text.length > chars ? `${text.slice(0, chars - 1)}\u2026` : text;
76
+ var status = (state) => state === "running" ? "in_progress" : state;
77
+ function createAcpAgent(options) {
78
+ const log = options.log ?? (() => {
79
+ });
80
+ const sessions = /* @__PURE__ */ new Map();
81
+ let alpd;
82
+ let editor;
83
+ let leaving = false;
84
+ async function daemon() {
85
+ alpd ??= options.connect().then((connection) => {
86
+ connection.onEvent(project);
87
+ connection.onClose((error) => {
88
+ if (leaving) return;
89
+ log("lost the connection to alpd", { code: "ALP_IPC_LOST", error });
90
+ alpd = void 0;
91
+ for (const session of sessions.values()) {
92
+ session.opened = false;
93
+ session.waiting?.done({ state: "failed", error: `ALP lost its connection to alpd: ${error.message}` });
94
+ }
95
+ });
96
+ return connection;
97
+ }, (error) => {
98
+ alpd = void 0;
99
+ log("could not reach alpd", { code: "ALP_IPC_CONNECT_FAILED", error });
100
+ throw error;
101
+ });
102
+ return alpd;
103
+ }
104
+ const request = async (method, params) => (await daemon()).request(method, params);
105
+ function send(session, update) {
106
+ const client = editor;
107
+ if (!client) return;
108
+ session.queue = session.queue.then(() => client.notify("session/update", { sessionId: session.id, update })).catch((error) => log("could not send an update to the editor", { code: "ALP_ACP_UPDATE_FAILED", error }));
109
+ }
110
+ const say = (session, text) => send(session, { sessionUpdate: "agent_message_chunk", content: { type: "text", text } });
111
+ function configOptions(session) {
112
+ const teams = session.opened ? session.preview.teams.filter((team) => team.id === session.preview.team) : session.preview.teams;
113
+ return [
114
+ {
115
+ id: "team",
116
+ name: "Team",
117
+ category: "model",
118
+ type: "select",
119
+ currentValue: session.preview.team,
120
+ description: session.opened ? "A session keeps its team; start a new one for another team" : "The team that works on this session",
121
+ options: (teams.length ? teams : [{ id: session.preview.team, label: session.preview.teamLabel ?? session.preview.team }]).map((team) => ({ value: team.id, name: team.label, ...team.description ? { description: team.description } : {} }))
122
+ },
123
+ { id: "mode", name: "Permissions", category: "mode", type: "select", currentValue: session.preview.mode, options: MODES.map((mode) => ({ value: mode.id, name: mode.name, description: mode.description })) }
124
+ ];
125
+ }
126
+ const modeState = (session) => ({ currentModeId: session.preview.mode, availableModes: MODES });
127
+ function track(id, cwd, mcpServers, preview2, opened) {
128
+ const session = { id, cwd, mcpServers, preview: preview2, opened, ours: /* @__PURE__ */ new Set(), texts: /* @__PURE__ */ new Map(), tools: /* @__PURE__ */ new Set(), questions: /* @__PURE__ */ new Map(), queue: Promise.resolve() };
129
+ sessions.set(id, session);
130
+ return session;
131
+ }
132
+ function known(sessionId) {
133
+ const session = sessions.get(sessionId);
134
+ if (!session) throw acp.RequestError.resourceNotFound(sessionId);
135
+ return session;
136
+ }
137
+ function project(envelope) {
138
+ const session = sessions.get(envelope.sessionId);
139
+ const { event } = envelope;
140
+ if (event.type === "question") {
141
+ const root = sessions.get(event.question.rootId);
142
+ if (!root || root.questions.has(event.question.id)) return;
143
+ root.questions.set(event.question.id, event.question);
144
+ const options2 = event.question.options?.length ? `
145
+ Options: ${event.question.options.join(" \xB7 ")}` : "";
146
+ say(root, `
147
+
148
+ **${event.question.agent} asks you:** ${event.question.body}${options2}
149
+
150
+ Reply with /answer <your answer>, or /dismiss.
151
+
152
+ `);
153
+ return;
154
+ }
155
+ if (event.type === "question.resolved") {
156
+ for (const root of sessions.values()) root.questions.delete(event.questionId);
157
+ return;
158
+ }
159
+ if (!session) return;
160
+ switch (event.type) {
161
+ case "session.updated":
162
+ if (event.session.mode !== session.preview.mode) {
163
+ session.preview = { ...session.preview, mode: event.session.mode };
164
+ send(session, { sessionUpdate: "current_mode_update", currentModeId: event.session.mode });
165
+ send(session, { sessionUpdate: "config_option_update", configOptions: configOptions(session) });
166
+ }
167
+ return;
168
+ case "prompt.accepted":
169
+ if (session.waiting?.clientMessageId === event.clientMessageId) session.waiting.turnId = event.turnId;
170
+ return;
171
+ case "prompt.failed":
172
+ if (session.waiting?.clientMessageId === event.clientMessageId) session.waiting.done({ state: "failed", error: event.error.message });
173
+ return;
174
+ case "turn.ended":
175
+ if (session.waiting?.turnId === event.turnId) session.waiting.done({ state: event.state, ...event.error ? { error: event.error.message } : {} });
176
+ return;
177
+ case "session.failed":
178
+ session.opened = false;
179
+ session.waiting?.done({ state: "failed", error: event.error.message });
180
+ return;
181
+ case "session.closed":
182
+ session.opened = false;
183
+ return;
184
+ case "item":
185
+ item(session, event.item);
186
+ return;
187
+ }
188
+ }
189
+ function item(session, item2) {
190
+ switch (item2.kind) {
191
+ case "user_message": {
192
+ if (item2.clientMessageId && session.ours.has(item2.clientMessageId)) return;
193
+ if (item2.clientMessageId?.startsWith("alp-") || item2.id.startsWith("user:alp-")) {
194
+ send(session, { sessionUpdate: "tool_call", toolCallId: item2.id, title: clip(`ALP: ${item2.text.split("\n").find((line) => line.trim()) ?? "note"}`, 160), kind: "other", status: "completed", content: [{ type: "content", content: { type: "text", text: item2.text } }] });
195
+ return;
196
+ }
197
+ send(session, { sessionUpdate: "user_message_chunk", content: { type: "text", text: item2.text } });
198
+ return;
199
+ }
200
+ case "assistant_message": {
201
+ const before = session.texts.get(item2.id) ?? "";
202
+ session.texts.set(item2.id, item2.text);
203
+ const fresh = item2.text.startsWith(before) ? item2.text.slice(before.length) : `
204
+ ${item2.text}`;
205
+ if (fresh) say(session, fresh);
206
+ return;
207
+ }
208
+ case "notice":
209
+ say(session, `
210
+
211
+ > ALP: ${item2.text}
212
+
213
+ `);
214
+ return;
215
+ case "compaction":
216
+ if (item2.status !== "running") say(session, `
217
+
218
+ > ALP: ${item2.status === "completed" ? `the context was compacted${item2.preTokens ? ` from ${Math.round(item2.preTokens / 1e3)}k tokens` : ""}` : "compacting the context failed"}
219
+
220
+ `);
221
+ return;
222
+ case "todo":
223
+ send(session, { sessionUpdate: "plan", entries: item2.items.map((entry) => ({ content: entry.text, priority: "medium", status: entry.status })) });
224
+ return;
225
+ case "tool_call": {
226
+ const fields = { title: toolTitle(item2), kind: toolKind(item2), status: status(item2.status), content: toolOutput(item2), rawInput: item2.detail.type === "shell" ? { command: item2.detail.command, cwd: item2.detail.cwd } : item2.detail.input };
227
+ if (session.tools.has(item2.id)) send(session, { sessionUpdate: "tool_call_update", toolCallId: item2.id, ...fields });
228
+ else {
229
+ session.tools.add(item2.id);
230
+ send(session, { sessionUpdate: "tool_call", toolCallId: item2.id, ...fields });
231
+ }
232
+ return;
233
+ }
234
+ }
235
+ }
236
+ async function open(session, resume) {
237
+ await request("session.create", {
238
+ sessionId: session.id,
239
+ spec: { cwd: session.cwd, persist: true, workflow: session.preview.team, mode: session.preview.mode, mcpServers: session.mcpServers },
240
+ history: "skip",
241
+ ...resume ? { resume: true } : {}
242
+ });
243
+ session.opened = true;
244
+ }
245
+ async function prompt(session, blocks) {
246
+ const text = promptText(blocks);
247
+ const command = /^\/(answer|dismiss)\b\s*([\s\S]*)$/.exec(text);
248
+ if (command) {
249
+ const [question] = session.questions.values();
250
+ if (!question) say(session, "No agent is waiting for an answer from you.");
251
+ else {
252
+ await request("question.answer", command[1] === "answer" ? { questionId: question.id, text: command[2].trim() || "yes" } : { questionId: question.id, dismiss: true, ...command[2].trim() ? { reason: command[2].trim() } : {} });
253
+ session.questions.delete(question.id);
254
+ say(session, command[1] === "answer" ? `Sent your answer to ${question.agent}.` : `Dismissed ${question.agent}'s question.`);
255
+ }
256
+ await session.queue;
257
+ return { stopReason: "end_turn" };
258
+ }
259
+ if (!text) throw acp.RequestError.invalidParams(void 0, "The prompt is empty");
260
+ if (!session.opened) {
261
+ const { session: snapshot } = await request("session.get", { sessionId: session.id }).catch(() => ({ session: void 0 }));
262
+ await open(session, !!snapshot);
263
+ send(session, { sessionUpdate: "config_option_update", configOptions: configOptions(session) });
264
+ }
265
+ if (session.waiting) throw acp.RequestError.invalidRequest(void 0, "A prompt is already running in this session");
266
+ const clientMessageId = randomUUID();
267
+ session.ours.add(clientMessageId);
268
+ let done;
269
+ const ended = new Promise((resolve) => {
270
+ done = (result2) => {
271
+ if (session.waiting?.clientMessageId === clientMessageId) session.waiting = void 0;
272
+ resolve(result2);
273
+ };
274
+ });
275
+ session.waiting = { clientMessageId, done };
276
+ try {
277
+ await request("session.prompt", { sessionId: session.id, clientMessageId, delivery: "auto", content: [{ type: "text", text }] });
278
+ } catch (error) {
279
+ done({ state: "failed", error: error instanceof Error ? error.message : String(error) });
280
+ }
281
+ const result = await ended;
282
+ await session.queue;
283
+ if (result.state === "canceled") return { stopReason: "cancelled" };
284
+ if (result.state === "failed") throw acp.RequestError.internalError(void 0, result.error ?? "The turn failed");
285
+ return { stopReason: "end_turn" };
286
+ }
287
+ async function preview(cwd, mcpServers, team, mode) {
288
+ const { preview: preview2 } = await request("session.preview", { spec: { cwd, mcpServers, ...team ? { workflow: team } : {}, ...mode ? { mode } : {} } });
289
+ return preview2;
290
+ }
291
+ const absolute = (cwd) => {
292
+ if (!path.isAbsolute(cwd)) throw acp.RequestError.invalidParams(void 0, "cwd must be an absolute path");
293
+ return path.resolve(cwd);
294
+ };
295
+ return acp.agent({ name: "alp" }).onConnect((connection) => {
296
+ editor = connection.client;
297
+ void connection.closed.then(() => release());
298
+ }).onRequest("initialize", () => ({
299
+ protocolVersion: acp.PROTOCOL_VERSION,
300
+ agentCapabilities: {
301
+ loadSession: true,
302
+ promptCapabilities: { embeddedContext: true },
303
+ mcpCapabilities: { http: true, sse: true },
304
+ sessionCapabilities: { list: {}, close: {} }
305
+ },
306
+ agentInfo: { name: "alp", title: "ALP", version: options.version },
307
+ authMethods: []
308
+ })).onRequest("authenticate", () => ({})).onRequest("session/new", async ({ params }) => {
309
+ const cwd = absolute(params.cwd);
310
+ const mcpServers = hostServers(params.mcpServers);
311
+ const session = track(`acp-${randomUUID()}`, cwd, mcpServers, await preview(cwd, mcpServers), false);
312
+ queueMicrotask(() => send(session, { sessionUpdate: "available_commands_update", availableCommands: COMMANDS }));
313
+ return { sessionId: session.id, configOptions: configOptions(session), modes: modeState(session) };
314
+ }).onRequest("session/load", async ({ params }) => {
315
+ const cwd = absolute(params.cwd);
316
+ const { session: record } = await request("session.get", { sessionId: params.sessionId }).catch(() => {
317
+ throw acp.RequestError.resourceNotFound(params.sessionId);
318
+ });
319
+ if (record.parentId || path.resolve(record.projectRoot) !== cwd) throw acp.RequestError.invalidParams(void 0, "That ALP session belongs to another project");
320
+ const session = track(params.sessionId, cwd, hostServers(params.mcpServers), {
321
+ teams: [{ id: record.workflow?.mode ?? "pho", label: record.teamLabel ?? record.workflow?.mode ?? "pho" }],
322
+ team: record.workflow?.mode ?? "pho",
323
+ ...record.teamLabel ? { teamLabel: record.teamLabel } : {},
324
+ agent: record.agent,
325
+ runtime: record.runtime,
326
+ model: record.model,
327
+ mode: record.mode,
328
+ thinking: record.thinking
329
+ }, true);
330
+ const live = record.status === "idle" || record.status === "running";
331
+ await request("session.attach", { sessionId: session.id, replay: true });
332
+ session.opened = live;
333
+ await session.queue;
334
+ send(session, { sessionUpdate: "available_commands_update", availableCommands: COMMANDS });
335
+ return { configOptions: configOptions(session), modes: modeState(session) };
336
+ }).onRequest("session/list", async ({ params }) => {
337
+ const { sessions: rows } = await request("session.list", { ...params.cwd ? { projectRoot: path.resolve(params.cwd) } : {}, rootsOnly: true, includeClosed: true });
338
+ return {
339
+ sessions: rows.filter((row) => row.persistent).slice(0, 100).map((row) => ({
340
+ sessionId: row.id,
341
+ cwd: row.projectRoot,
342
+ title: row.title ?? `ALP ${row.agent}`,
343
+ ...row.updatedAt ? { updatedAt: row.updatedAt } : {}
344
+ }))
345
+ };
346
+ }).onRequest("session/prompt", ({ params }) => prompt(known(params.sessionId), params.prompt)).onNotification("session/cancel", async ({ params }) => {
347
+ const session = sessions.get(params.sessionId);
348
+ if (!session?.opened) return;
349
+ await request("session.interrupt", { sessionId: session.id }).catch((error) => log("session.interrupt failed", { code: "ALP_ACP_INTERRUPT_FAILED", error }));
350
+ }).onRequest("session/set_mode", async ({ params }) => {
351
+ await setMode(known(params.sessionId), params.modeId);
352
+ return {};
353
+ }).onRequest("session/set_config_option", async ({ params }) => {
354
+ const session = known(params.sessionId);
355
+ const value = String(params.value);
356
+ if (params.configId === "mode") await setMode(session, value);
357
+ else if (params.configId === "team") {
358
+ if (session.opened && value !== session.preview.team) throw acp.RequestError.invalidParams(void 0, "A session keeps its team; start a new one for another team");
359
+ if (!session.preview.teams.some((team) => team.id === value)) throw acp.RequestError.invalidParams(void 0, `Unknown team ${value}`);
360
+ session.preview = { ...await preview(session.cwd, session.mcpServers, value), mode: session.preview.mode };
361
+ } else throw acp.RequestError.invalidParams(void 0, `Unknown option ${params.configId}`);
362
+ return { configOptions: configOptions(session) };
363
+ }).onRequest("session/close", async ({ params }) => {
364
+ const session = known(params.sessionId);
365
+ sessions.delete(session.id);
366
+ if (session.opened) await request("session.release", { sessionId: session.id }).catch(() => {
367
+ });
368
+ return {};
369
+ });
370
+ async function setMode(session, mode) {
371
+ if (!MODES.some((entry) => entry.id === mode)) throw acp.RequestError.invalidParams(void 0, `Unknown mode ${mode}`);
372
+ if (session.opened) await request("session.configure", { sessionId: session.id, mode });
373
+ session.preview = { ...session.preview, mode };
374
+ }
375
+ async function release() {
376
+ leaving = true;
377
+ const connection = await alpd?.catch(() => void 0);
378
+ if (!connection) return;
379
+ await Promise.all([...sessions.values()].filter((session) => session.opened).map((session) => connection.request("session.release", { sessionId: session.id }).catch(() => {
380
+ })));
381
+ sessions.clear();
382
+ connection.close();
383
+ }
384
+ }
385
+ export {
386
+ createAcpAgent,
387
+ hostServers,
388
+ promptText,
389
+ toolKind
390
+ };
@@ -0,0 +1,4 @@
1
+ /** `alp acp`: serves the Agent Client Protocol on stdio until the editor closes it (ALPD §60). */
2
+ export declare function runAcp({ daemonEntry }: {
3
+ daemonEntry: string;
4
+ }): Promise<void>;
@@ -0,0 +1,23 @@
1
+ // packages/cli/src/acp/main.ts
2
+ import * as acp from "@agentclientprotocol/sdk";
3
+ import { Readable, Writable } from "node:stream";
4
+ import { alpHome, connect } from "@anhlp/alp-client/index";
5
+ import { startDaemon } from "@anhlp/alp-client/supervise";
6
+ import { createLogger } from "@anhlp/alp-core/log";
7
+ import { createAcpAgent } from "./agent.js";
8
+ async function runAcp({ daemonEntry }) {
9
+ const version = true ? "0.0.0-beta.1" : "0.0.0";
10
+ const logger = createLogger({ component: "acp" });
11
+ const log = (message, fields) => logger.warn(message, fields);
12
+ const app = createAcpAgent({
13
+ version,
14
+ log,
15
+ connect: async () => connect(await startDaemon({ home: alpHome(), entry: daemonEntry }), { name: "alp-acp", version })
16
+ });
17
+ const stream = acp.ndJsonStream(Writable.toWeb(process.stdout), Readable.toWeb(process.stdin));
18
+ const connection = app.connect(stream);
19
+ await connection.closed;
20
+ }
21
+ export {
22
+ runAcp
23
+ };