@anhlp/alp 0.0.0-beta.1 → 0.0.0-beta.2

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.
@@ -53,7 +53,7 @@ npm run build --workspace=@anhlp/alp-runtime
53
53
  npm run test --workspace=@anhlp/alp-runtime
54
54
  ```
55
55
 
56
- Root `npm run build` retains the neutral legacy bundle paths and builds the CLI without Paseo. `npm run build:paseo` builds the optional plugin. Workspace builds compile the selected package and its dependencies, preserving individual module imports and emitting declarations. The `alp-source` export condition is only for repository builds/typechecking; published consumers use ordinary JavaScript and declaration exports.
56
+ Root `npm run build` retains the neutral legacy bundle paths and builds the CLI without Paseo. `npm run build:paseo` builds the optional plugin. Workspace builds compile the selected package and its dependencies, preserving individual module imports and emitting declarations. The `alp-source` export condition is only for repository builds/typechecking; published consumers use ordinary JavaScript and declaration exports. The Paseo plugin `@anhlp/alp-paseo` is the exception: it ships no declarations, since Paseo installs an `npm:` plugin without its peers and resolves every type import ([#111](https://github.com/phucanh08/alp/issues/111)).
57
57
 
58
58
  ## Test layout
59
59
 
@@ -138,7 +138,7 @@ Restarting is non-destructive. `alp daemon restart` (or the plugin's supervision
138
138
 
139
139
  ## Independent versions and publishing
140
140
 
141
- > **For the beta `0.0.0-beta.1`, do not use the commands in this section.** The beta gives all
141
+ > **For the beta `0.0.0-beta.2`, do not use the commands in this section.** The beta gives all
142
142
  > eight published packages one version, packs them once and publishes those tarballs with
143
143
  > `--tag beta`, following the manual checklist in [beta-scope.md](alp/beta-scope.md) §7. The
144
144
  > `npm publish` below has no `--tag`, so it would set `latest`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anhlp/alp",
3
- "version": "0.0.0-beta.1",
3
+ "version": "0.0.0-beta.2",
4
4
  "description": "ALP command line and alpd, the per-user daemon that runs filesystem-defined Codex and Claude Code agent teams",
5
5
  "type": "module",
6
6
  "files": [
@@ -67,7 +67,7 @@
67
67
  "build:alp-web": "npm run build --workspace @anhlp/alp-web",
68
68
  "typecheck:alp-web": "npm run typecheck --workspace @anhlp/alp-web",
69
69
  "test:alp-web": "npm test --workspace @anhlp/alp-web",
70
- "test:alp-web:e2e": "node scripts/build-workspace.mjs cli && node scripts/build-workspace.mjs daemon && npm run build --workspace @anhlp/alp-web && npm run test:e2e --workspace @anhlp/alp-web",
70
+ "test:alp-web:e2e": "npm run test:e2e --workspace @anhlp/alp-web",
71
71
  "validate:web": "node scripts/validate.mjs web"
72
72
  },
73
73
  "engines": {
@@ -91,9 +91,9 @@
91
91
  "zod": "4.4.3"
92
92
  },
93
93
  "dependencies": {
94
- "@anhlp/alp-client": "0.0.0-beta.1",
95
- "@anhlp/alp-core": "0.0.0-beta.1",
96
- "@anhlp/alp-protocol": "0.0.0-beta.1",
94
+ "@anhlp/alp-client": "0.0.0-beta.2",
95
+ "@anhlp/alp-core": "0.0.0-beta.2",
96
+ "@anhlp/alp-protocol": "0.0.0-beta.2",
97
97
  "@anthropic-ai/claude-agent-sdk": "^0.3.292",
98
98
  "smol-toml": "1.9.0"
99
99
  },
@@ -22,8 +22,16 @@ export type AcpAgentOptions = {
22
22
  export declare function hostServers(servers?: acp.McpServer[]): Record<string, HostMcpServer>;
23
23
  /** A prompt's content as the text ALP gives the agent: files the editor attaches are named or inlined. */
24
24
  export declare function promptText(blocks: acp.ContentBlock[]): string;
25
- /** What kind of work a tool call is, from its name or its command's first word (Claude reports tools as `Read {…}`). */
26
- export declare function toolKind(item: Extract<TimelineItem, {
25
+ type ToolItem = Extract<TimelineItem, {
27
26
  kind: 'tool_call';
28
- }>): acp.ToolKind;
27
+ }>;
28
+ /** What kind of work a tool call is: from its typed detail, else its name or its command's first word (older Claude items are `Read {…}` commands). */
29
+ export declare function toolKind(item: ToolItem): acp.ToolKind;
30
+ export declare function toolTitle(item: ToolItem, cwd?: string): string;
31
+ export declare function toolOutput(item: ToolItem): acp.ToolCallContent[];
32
+ /** The call's input as the editor shows it raw: a command, a typed call's own fields without its long texts, or the tool's input. */
33
+ export declare function toolInput(item: ToolItem): unknown;
34
+ /** The file a typed call read or changed, for the editor to follow. */
35
+ export declare function toolLocations(item: ToolItem): acp.ToolCallLocation[] | undefined;
29
36
  export declare function createAcpAgent(options: AcpAgentOptions): acp.AgentApp;
37
+ export {};
@@ -2,6 +2,7 @@
2
2
  import * as acp from "@agentclientprotocol/sdk";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import path from "node:path";
5
+ var unlistedOf = ({ teams, team, teamLabel }) => teams.some((entry) => entry.id === team) ? void 0 : { id: team, label: teamLabel ?? (team === "custom" ? "Custom" : team) };
5
6
  var MODES = [
6
7
  { id: "read-only", name: "Read only", description: "Agents read and search; nothing is changed" },
7
8
  { id: "workspace-write", name: "Workspace write", description: "Agents change files and run commands inside the project" },
@@ -43,34 +44,120 @@ ${resource.text}
43
44
  }
44
45
  return text.trim();
45
46
  }
47
+ function shown(file, cwd) {
48
+ if (!cwd || !path.isAbsolute(file)) return file;
49
+ const relative = path.relative(cwd, file);
50
+ return relative && !relative.startsWith("..") && !path.isAbsolute(relative) ? relative : file;
51
+ }
46
52
  function toolKind(item) {
47
- const word = item.detail.type === "shell" ? String(item.detail.command ?? "").trim().split(/[\s{(]/)[0] : item.name;
53
+ const detail = item.detail;
54
+ switch (detail.type) {
55
+ case "read":
56
+ return "read";
57
+ case "edit":
58
+ case "write":
59
+ return "edit";
60
+ case "search":
61
+ return detail.toolName === "web_search" ? "fetch" : "search";
62
+ case "fetch":
63
+ return "fetch";
64
+ case "plan":
65
+ return "think";
66
+ case "sub_agent":
67
+ case "plain_text":
68
+ return "other";
69
+ }
70
+ const word = detail.type === "shell" ? String(detail.command ?? "").trim().split(/[\s{(]/)[0] : item.name;
48
71
  if (/^(Read|NotebookRead|LS)$/.test(word)) return "read";
49
72
  if (/^(Edit|MultiEdit|Write|NotebookEdit)$/.test(word)) return "edit";
50
73
  if (/^(Grep|Glob)$/.test(word)) return "search";
51
74
  if (/^(WebFetch|WebSearch)$/.test(word)) return "fetch";
52
75
  if (/^(Skill|Task|TodoWrite)$/.test(word) || item.name.startsWith("alp_")) return "other";
53
- return item.detail.type === "shell" ? "execute" : "other";
76
+ return detail.type === "shell" ? "execute" : "other";
54
77
  }
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 ?? {};
78
+ function toolTitle(item, cwd) {
79
+ const detail = item.detail;
80
+ switch (detail.type) {
81
+ case "shell":
82
+ return clip(String(detail.command ?? "").split("\n")[0], 160);
83
+ case "read":
84
+ return clip(`Read ${shown(detail.filePath, cwd)}`, 160);
85
+ case "edit":
86
+ return clip(`Edit ${shown(detail.filePath, cwd)}`, 160);
87
+ case "write":
88
+ return clip(`Write ${shown(detail.filePath, cwd)}`, 160);
89
+ case "search":
90
+ return clip(`${detail.toolName === "web_search" ? "Search the web" : "Search"} ${detail.query}`, 160);
91
+ case "fetch":
92
+ return clip(`Fetch ${detail.url}`, 160);
93
+ case "sub_agent":
94
+ return clip(`${detail.subAgentType ?? "Sub-agent"}${detail.description ? `: ${detail.description.split("\n")[0]}` : ""}`, 160);
95
+ case "plan":
96
+ return "Plan";
97
+ case "plain_text":
98
+ return clip(`${item.name}${detail.label ? `: ${detail.label}` : ""}`, 160);
99
+ }
100
+ const input = detail.input ?? {};
58
101
  if (item.name === "alp_delegate") return clip(`Delegate to ${input.agent ?? "an agent"}: ${String(input.task ?? "").split("\n")[0]}`, 160);
59
102
  return clip(`${item.name} ${JSON.stringify(input)}`, 160);
60
103
  }
104
+ var bounded = (text) => text.length > OUTPUT_CHARS ? `${text.slice(0, OUTPUT_CHARS)}
105
+ \u2026 (${text.length - OUTPUT_CHARS} more characters)` : text;
61
106
  function toolOutput(item) {
107
+ const detail = item.detail;
108
+ const parts = [];
62
109
  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);
110
+ switch (detail.type) {
111
+ case "shell":
112
+ text = detail.output ?? "";
113
+ break;
114
+ case "read":
115
+ text = detail.content ?? "";
116
+ break;
117
+ case "edit":
118
+ if (detail.newString !== void 0 || detail.oldString !== void 0) parts.push({ type: "diff", path: detail.filePath, oldText: detail.oldString ?? null, newText: detail.newString ?? "" });
119
+ else if (detail.unifiedDiff) text = "```diff\n" + detail.unifiedDiff + (detail.unifiedDiff.endsWith("\n") ? "" : "\n") + "```";
120
+ break;
121
+ case "write":
122
+ if (detail.content !== void 0) parts.push({ type: "diff", path: detail.filePath, oldText: null, newText: detail.content });
123
+ break;
124
+ case "search":
125
+ text = detail.content ?? [...detail.filePaths ?? [], ...(detail.webResults ?? []).map((hit) => `${hit.title} \u2014 ${hit.url}`)].join("\n");
126
+ break;
127
+ case "fetch":
128
+ text = detail.result ?? "";
129
+ break;
130
+ case "sub_agent":
131
+ text = detail.log || detail.description || "";
132
+ break;
133
+ case "plan":
134
+ text = detail.text;
135
+ break;
136
+ case "plain_text":
137
+ text = detail.text ?? "";
138
+ break;
139
+ default: {
140
+ const output = detail.output;
141
+ 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);
142
+ }
67
143
  }
68
144
  if (item.error) text = text ? `${text}
69
145
  ${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 } }];
146
+ if (text) parts.push({ type: "content", content: { type: "text", text: bounded(text) } });
147
+ return parts.map((part) => part.type === "diff" ? { ...part, newText: bounded(part.newText), ...part.oldText ? { oldText: bounded(part.oldText) } : {} } : part);
148
+ }
149
+ function toolInput(item) {
150
+ const detail = item.detail;
151
+ if (detail.type === "shell") return { command: detail.command, cwd: detail.cwd };
152
+ if (detail.type === "unknown" || !["read", "edit", "write", "search", "fetch", "sub_agent", "plan", "plain_text"].includes(detail.type)) return detail.input;
153
+ const { type: _type, content: _content, oldString: _old, newString: _new, unifiedDiff: _diff, result: _result, log: _log, text: _text, filePaths: _paths, webResults: _hits, ...rest } = detail;
154
+ return rest;
155
+ }
156
+ function toolLocations(item) {
157
+ const detail = item.detail;
158
+ if (detail.type !== "read" && detail.type !== "edit" && detail.type !== "write") return void 0;
159
+ const filePath = String(detail.filePath);
160
+ return [{ path: filePath, ...detail.type === "read" && typeof detail.offset === "number" ? { line: detail.offset } : {} }];
74
161
  }
75
162
  var clip = (text, chars) => text.length > chars ? `${text.slice(0, chars - 1)}\u2026` : text;
76
163
  var status = (state) => state === "running" ? "in_progress" : state;
@@ -109,7 +196,7 @@ function createAcpAgent(options) {
109
196
  }
110
197
  const say = (session, text) => send(session, { sessionUpdate: "agent_message_chunk", content: { type: "text", text } });
111
198
  function configOptions(session) {
112
- const teams = session.opened ? session.preview.teams.filter((team) => team.id === session.preview.team) : session.preview.teams;
199
+ const teams = session.opened ? choices(session).filter((team) => team.id === session.preview.team) : choices(session);
113
200
  return [
114
201
  {
115
202
  id: "team",
@@ -118,14 +205,21 @@ function createAcpAgent(options) {
118
205
  type: "select",
119
206
  currentValue: session.preview.team,
120
207
  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 } : {} }))
208
+ options: teams.map((team) => ({ value: team.id, name: team.label, ...team.description ? { description: team.description } : {} }))
122
209
  },
123
210
  { 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
211
  ];
125
212
  }
213
+ function choices(session) {
214
+ const { teams, team } = session.preview;
215
+ const own = unlistedOf(session.preview);
216
+ const first = session.unlisted && session.unlisted.id !== team && !teams.some((entry) => entry.id === session.unlisted.id) ? [session.unlisted] : [];
217
+ return [...first, ...own ? [own] : [], ...teams];
218
+ }
126
219
  const modeState = (session) => ({ currentModeId: session.preview.mode, availableModes: MODES });
127
220
  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() };
221
+ const unlisted = unlistedOf(preview2);
222
+ const session = { id, cwd, mcpServers, preview: preview2, opened, ...unlisted ? { unlisted } : {}, ours: /* @__PURE__ */ new Set(), texts: /* @__PURE__ */ new Map(), tools: /* @__PURE__ */ new Set(), questions: /* @__PURE__ */ new Map(), queue: Promise.resolve() };
129
223
  sessions.set(id, session);
130
224
  return session;
131
225
  }
@@ -223,7 +317,8 @@ ${item2.text}`;
223
317
  send(session, { sessionUpdate: "plan", entries: item2.items.map((entry) => ({ content: entry.text, priority: "medium", status: entry.status })) });
224
318
  return;
225
319
  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 };
320
+ const locations = toolLocations(item2);
321
+ const fields = { title: toolTitle(item2, session.cwd), kind: toolKind(item2), status: status(item2.status), content: toolOutput(item2), rawInput: toolInput(item2), ...locations ? { locations } : {} };
227
322
  if (session.tools.has(item2.id)) send(session, { sessionUpdate: "tool_call_update", toolCallId: item2.id, ...fields });
228
323
  else {
229
324
  session.tools.add(item2.id);
@@ -356,7 +451,7 @@ ${item2.text}`;
356
451
  if (params.configId === "mode") await setMode(session, value);
357
452
  else if (params.configId === "team") {
358
453
  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}`);
454
+ if (!choices(session).some((team) => team.id === value)) throw acp.RequestError.invalidParams(void 0, `Unknown team ${value}`);
360
455
  session.preview = { ...await preview(session.cwd, session.mcpServers, value), mode: session.preview.mode };
361
456
  } else throw acp.RequestError.invalidParams(void 0, `Unknown option ${params.configId}`);
362
457
  return { configOptions: configOptions(session) };
@@ -386,5 +481,9 @@ export {
386
481
  createAcpAgent,
387
482
  hostServers,
388
483
  promptText,
389
- toolKind
484
+ toolInput,
485
+ toolKind,
486
+ toolLocations,
487
+ toolOutput,
488
+ toolTitle
390
489
  };
@@ -6,7 +6,7 @@ import { startDaemon } from "@anhlp/alp-client/supervise";
6
6
  import { createLogger } from "@anhlp/alp-core/log";
7
7
  import { createAcpAgent } from "./agent.js";
8
8
  async function runAcp({ daemonEntry }) {
9
- const version = true ? "0.0.0-beta.1" : "0.0.0";
9
+ const version = true ? "0.0.0-beta.2" : "0.0.0";
10
10
  const logger = createLogger({ component: "acp" });
11
11
  const log = (message, fields) => logger.warn(message, fields);
12
12
  const app = createAcpAgent({
@@ -18552,8 +18552,10 @@ var AlpRpcError = class extends Error {
18552
18552
  };
18553
18553
  var ERROR = Object.freeze({ failed: 1e3, notFound: 1001, forbidden: 1003, conflict: 1004, unavailable: 1005, protocol: 1006 });
18554
18554
  var RPC_ERROR = Object.freeze({ parseError: -32700, invalidRequest: -32600, methodNotFound: -32601, invalidParams: -32602 });
18555
- var CAPABILITY = Object.freeze({ attachAfter: "attach.after", promptReceipts: "prompt.receipts", extensionMethods: "extension.methods", team: "team", task: "task", library: "library", settingsLanguage: "settings.language" });
18556
- var RPC_METHODS = Object.freeze(["assignment.recall", "board.list", "catalog.get", "daemon.hello", "daemon.pause", "daemon.pauses", "daemon.resume", "daemon.shutdown", "daemon.status", "daemon.usage", "extension.register", "extension.unregister", "library.delete", "library.duplicate", "library.get", "library.list", "library.models", "library.rename", "library.save", "library.skills.set", "question.answer", "question.list", "session.archive", "session.attach", "session.close", "session.configure", "session.create", "session.get", "session.interrupt", "session.list", "session.log", "session.message", "session.preview", "session.prompt", "session.release", "session.rename", "session.status", "settings.language.get", "settings.language.set", "task.create", "task.gate.resolve", "task.list", "task.update", "team.list"]);
18555
+ var CAPABILITY = Object.freeze({ attachAfter: "attach.after", promptReceipts: "prompt.receipts", extensionMethods: "extension.methods", team: "team", task: "task", library: "library", settingsLanguage: "settings.language", sessionListPage: "session.list.page", questionWatch: "question.watch", questionWatchTurns: "question.watch.turns", workspaceFiles: "workspace.files", workspaceFilesRead: "workspace.files.read", promptImages: "prompt.images" });
18556
+ var PROMPT_IMAGES = Object.freeze({ mimeTypes: Object.freeze(["image/png", "image/jpeg", "image/gif", "image/webp"]), maxCount: 8, maxBytes: 5 * 1024 * 1024, maxTotalBytes: 11 * 1024 * 1024 });
18557
+ var RPC_METHODS = Object.freeze(["assignment.recall", "board.list", "catalog.get", "daemon.hello", "daemon.pause", "daemon.pauses", "daemon.resume", "daemon.shutdown", "daemon.status", "daemon.usage", "extension.register", "extension.unregister", "library.delete", "library.duplicate", "library.get", "library.list", "library.models", "library.rename", "library.save", "library.skills.set", "question.answer", "question.list", "question.watch", "session.archive", "session.attach", "session.close", "session.configure", "session.create", "session.get", "session.interrupt", "session.list", "session.log", "session.message", "session.preview", "session.prompt", "session.release", "session.rename", "session.status", "settings.language.get", "settings.language.set", "task.create", "task.gate.resolve", "task.list", "task.update", "team.list", "workspace.files.list", "workspace.files.read", "workspace.files.search"]);
18558
+ var WORKSPACE_FILES = Object.freeze({ maxReadBytes: 1024 * 1024, maxListEntries: 1e3, maxPathLength: 4096 });
18557
18559
 
18558
18560
  // packages/client/src/rpc/errors.js
18559
18561
  var BRAND = Symbol.for("@anhlp/alp-client/AlpClientError");
@@ -19323,6 +19325,7 @@ async function startDaemon({ home, entry, env = process.env, timeoutMs = 15e3 })
19323
19325
  // packages/cli/src/acp/agent.ts
19324
19326
  import { randomUUID } from "node:crypto";
19325
19327
  import path4 from "node:path";
19328
+ var unlistedOf = ({ teams, team, teamLabel }) => teams.some((entry) => entry.id === team) ? void 0 : { id: team, label: teamLabel ?? (team === "custom" ? "Custom" : team) };
19326
19329
  var MODES = [
19327
19330
  { id: "read-only", name: "Read only", description: "Agents read and search; nothing is changed" },
19328
19331
  { id: "workspace-write", name: "Workspace write", description: "Agents change files and run commands inside the project" },
@@ -19364,34 +19367,120 @@ ${resource.text}
19364
19367
  }
19365
19368
  return text.trim();
19366
19369
  }
19370
+ function shown(file2, cwd) {
19371
+ if (!cwd || !path4.isAbsolute(file2)) return file2;
19372
+ const relative = path4.relative(cwd, file2);
19373
+ return relative && !relative.startsWith("..") && !path4.isAbsolute(relative) ? relative : file2;
19374
+ }
19367
19375
  function toolKind(item) {
19368
- const word = item.detail.type === "shell" ? String(item.detail.command ?? "").trim().split(/[\s{(]/)[0] : item.name;
19376
+ const detail = item.detail;
19377
+ switch (detail.type) {
19378
+ case "read":
19379
+ return "read";
19380
+ case "edit":
19381
+ case "write":
19382
+ return "edit";
19383
+ case "search":
19384
+ return detail.toolName === "web_search" ? "fetch" : "search";
19385
+ case "fetch":
19386
+ return "fetch";
19387
+ case "plan":
19388
+ return "think";
19389
+ case "sub_agent":
19390
+ case "plain_text":
19391
+ return "other";
19392
+ }
19393
+ const word = detail.type === "shell" ? String(detail.command ?? "").trim().split(/[\s{(]/)[0] : item.name;
19369
19394
  if (/^(Read|NotebookRead|LS)$/.test(word)) return "read";
19370
19395
  if (/^(Edit|MultiEdit|Write|NotebookEdit)$/.test(word)) return "edit";
19371
19396
  if (/^(Grep|Glob)$/.test(word)) return "search";
19372
19397
  if (/^(WebFetch|WebSearch)$/.test(word)) return "fetch";
19373
19398
  if (/^(Skill|Task|TodoWrite)$/.test(word) || item.name.startsWith("alp_")) return "other";
19374
- return item.detail.type === "shell" ? "execute" : "other";
19375
- }
19376
- function toolTitle(item) {
19377
- if (item.detail.type === "shell") return clip(String(item.detail.command ?? "").split("\n")[0], 160);
19378
- const input = item.detail.input ?? {};
19399
+ return detail.type === "shell" ? "execute" : "other";
19400
+ }
19401
+ function toolTitle(item, cwd) {
19402
+ const detail = item.detail;
19403
+ switch (detail.type) {
19404
+ case "shell":
19405
+ return clip(String(detail.command ?? "").split("\n")[0], 160);
19406
+ case "read":
19407
+ return clip(`Read ${shown(detail.filePath, cwd)}`, 160);
19408
+ case "edit":
19409
+ return clip(`Edit ${shown(detail.filePath, cwd)}`, 160);
19410
+ case "write":
19411
+ return clip(`Write ${shown(detail.filePath, cwd)}`, 160);
19412
+ case "search":
19413
+ return clip(`${detail.toolName === "web_search" ? "Search the web" : "Search"} ${detail.query}`, 160);
19414
+ case "fetch":
19415
+ return clip(`Fetch ${detail.url}`, 160);
19416
+ case "sub_agent":
19417
+ return clip(`${detail.subAgentType ?? "Sub-agent"}${detail.description ? `: ${detail.description.split("\n")[0]}` : ""}`, 160);
19418
+ case "plan":
19419
+ return "Plan";
19420
+ case "plain_text":
19421
+ return clip(`${item.name}${detail.label ? `: ${detail.label}` : ""}`, 160);
19422
+ }
19423
+ const input = detail.input ?? {};
19379
19424
  if (item.name === "alp_delegate") return clip(`Delegate to ${input.agent ?? "an agent"}: ${String(input.task ?? "").split("\n")[0]}`, 160);
19380
19425
  return clip(`${item.name} ${JSON.stringify(input)}`, 160);
19381
19426
  }
19427
+ var bounded = (text) => text.length > OUTPUT_CHARS ? `${text.slice(0, OUTPUT_CHARS)}
19428
+ \u2026 (${text.length - OUTPUT_CHARS} more characters)` : text;
19382
19429
  function toolOutput(item) {
19430
+ const detail = item.detail;
19431
+ const parts = [];
19383
19432
  let text = "";
19384
- if (item.detail.type === "shell") text = item.detail.output ?? "";
19385
- else {
19386
- const output = item.detail.output;
19387
- 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);
19433
+ switch (detail.type) {
19434
+ case "shell":
19435
+ text = detail.output ?? "";
19436
+ break;
19437
+ case "read":
19438
+ text = detail.content ?? "";
19439
+ break;
19440
+ case "edit":
19441
+ if (detail.newString !== void 0 || detail.oldString !== void 0) parts.push({ type: "diff", path: detail.filePath, oldText: detail.oldString ?? null, newText: detail.newString ?? "" });
19442
+ else if (detail.unifiedDiff) text = "```diff\n" + detail.unifiedDiff + (detail.unifiedDiff.endsWith("\n") ? "" : "\n") + "```";
19443
+ break;
19444
+ case "write":
19445
+ if (detail.content !== void 0) parts.push({ type: "diff", path: detail.filePath, oldText: null, newText: detail.content });
19446
+ break;
19447
+ case "search":
19448
+ text = detail.content ?? [...detail.filePaths ?? [], ...(detail.webResults ?? []).map((hit) => `${hit.title} \u2014 ${hit.url}`)].join("\n");
19449
+ break;
19450
+ case "fetch":
19451
+ text = detail.result ?? "";
19452
+ break;
19453
+ case "sub_agent":
19454
+ text = detail.log || detail.description || "";
19455
+ break;
19456
+ case "plan":
19457
+ text = detail.text;
19458
+ break;
19459
+ case "plain_text":
19460
+ text = detail.text ?? "";
19461
+ break;
19462
+ default: {
19463
+ const output = detail.output;
19464
+ 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);
19465
+ }
19388
19466
  }
19389
19467
  if (item.error) text = text ? `${text}
19390
19468
  ${item.error}` : item.error;
19391
- if (!text) return [];
19392
- if (text.length > OUTPUT_CHARS) text = `${text.slice(0, OUTPUT_CHARS)}
19393
- \u2026 (${text.length - OUTPUT_CHARS} more characters)`;
19394
- return [{ type: "content", content: { type: "text", text } }];
19469
+ if (text) parts.push({ type: "content", content: { type: "text", text: bounded(text) } });
19470
+ return parts.map((part) => part.type === "diff" ? { ...part, newText: bounded(part.newText), ...part.oldText ? { oldText: bounded(part.oldText) } : {} } : part);
19471
+ }
19472
+ function toolInput(item) {
19473
+ const detail = item.detail;
19474
+ if (detail.type === "shell") return { command: detail.command, cwd: detail.cwd };
19475
+ if (detail.type === "unknown" || !["read", "edit", "write", "search", "fetch", "sub_agent", "plan", "plain_text"].includes(detail.type)) return detail.input;
19476
+ const { type: _type, content: _content, oldString: _old, newString: _new, unifiedDiff: _diff, result: _result, log: _log, text: _text, filePaths: _paths, webResults: _hits, ...rest } = detail;
19477
+ return rest;
19478
+ }
19479
+ function toolLocations(item) {
19480
+ const detail = item.detail;
19481
+ if (detail.type !== "read" && detail.type !== "edit" && detail.type !== "write") return void 0;
19482
+ const filePath = String(detail.filePath);
19483
+ return [{ path: filePath, ...detail.type === "read" && typeof detail.offset === "number" ? { line: detail.offset } : {} }];
19395
19484
  }
19396
19485
  var clip = (text, chars) => text.length > chars ? `${text.slice(0, chars - 1)}\u2026` : text;
19397
19486
  var status = (state) => state === "running" ? "in_progress" : state;
@@ -19430,7 +19519,7 @@ function createAcpAgent(options) {
19430
19519
  }
19431
19520
  const say = (session, text) => send(session, { sessionUpdate: "agent_message_chunk", content: { type: "text", text } });
19432
19521
  function configOptions(session) {
19433
- const teams = session.opened ? session.preview.teams.filter((team) => team.id === session.preview.team) : session.preview.teams;
19522
+ const teams = session.opened ? choices(session).filter((team) => team.id === session.preview.team) : choices(session);
19434
19523
  return [
19435
19524
  {
19436
19525
  id: "team",
@@ -19439,14 +19528,21 @@ function createAcpAgent(options) {
19439
19528
  type: "select",
19440
19529
  currentValue: session.preview.team,
19441
19530
  description: session.opened ? "A session keeps its team; start a new one for another team" : "The team that works on this session",
19442
- 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 } : {} }))
19531
+ options: teams.map((team) => ({ value: team.id, name: team.label, ...team.description ? { description: team.description } : {} }))
19443
19532
  },
19444
19533
  { 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 })) }
19445
19534
  ];
19446
19535
  }
19536
+ function choices(session) {
19537
+ const { teams, team } = session.preview;
19538
+ const own2 = unlistedOf(session.preview);
19539
+ const first = session.unlisted && session.unlisted.id !== team && !teams.some((entry) => entry.id === session.unlisted.id) ? [session.unlisted] : [];
19540
+ return [...first, ...own2 ? [own2] : [], ...teams];
19541
+ }
19447
19542
  const modeState = (session) => ({ currentModeId: session.preview.mode, availableModes: MODES });
19448
19543
  function track(id, cwd, mcpServers, preview2, opened) {
19449
- 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() };
19544
+ const unlisted = unlistedOf(preview2);
19545
+ const session = { id, cwd, mcpServers, preview: preview2, opened, ...unlisted ? { unlisted } : {}, ours: /* @__PURE__ */ new Set(), texts: /* @__PURE__ */ new Map(), tools: /* @__PURE__ */ new Set(), questions: /* @__PURE__ */ new Map(), queue: Promise.resolve() };
19450
19546
  sessions.set(id, session);
19451
19547
  return session;
19452
19548
  }
@@ -19544,7 +19640,8 @@ ${item2.text}`;
19544
19640
  send(session, { sessionUpdate: "plan", entries: item2.items.map((entry) => ({ content: entry.text, priority: "medium", status: entry.status })) });
19545
19641
  return;
19546
19642
  case "tool_call": {
19547
- 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 };
19643
+ const locations = toolLocations(item2);
19644
+ const fields = { title: toolTitle(item2, session.cwd), kind: toolKind(item2), status: status(item2.status), content: toolOutput(item2), rawInput: toolInput(item2), ...locations ? { locations } : {} };
19548
19645
  if (session.tools.has(item2.id)) send(session, { sessionUpdate: "tool_call_update", toolCallId: item2.id, ...fields });
19549
19646
  else {
19550
19647
  session.tools.add(item2.id);
@@ -19677,7 +19774,7 @@ ${item2.text}`;
19677
19774
  if (params.configId === "mode") await setMode(session, value);
19678
19775
  else if (params.configId === "team") {
19679
19776
  if (session.opened && value !== session.preview.team) throw RequestError.invalidParams(void 0, "A session keeps its team; start a new one for another team");
19680
- if (!session.preview.teams.some((team) => team.id === value)) throw RequestError.invalidParams(void 0, `Unknown team ${value}`);
19777
+ if (!choices(session).some((team) => team.id === value)) throw RequestError.invalidParams(void 0, `Unknown team ${value}`);
19681
19778
  session.preview = { ...await preview(session.cwd, session.mcpServers, value), mode: session.preview.mode };
19682
19779
  } else throw RequestError.invalidParams(void 0, `Unknown option ${params.configId}`);
19683
19780
  return { configOptions: configOptions(session) };
@@ -19706,7 +19803,7 @@ ${item2.text}`;
19706
19803
 
19707
19804
  // packages/cli/src/acp/main.ts
19708
19805
  async function runAcp({ daemonEntry }) {
19709
- const version2 = true ? "0.0.0-beta.1" : "0.0.0";
19806
+ const version2 = true ? "0.0.0-beta.2" : "0.0.0";
19710
19807
  const logger = createLogger({ component: "acp" });
19711
19808
  const log = (message, fields) => logger.warn(message, fields);
19712
19809
  const app = createAcpAgent({