@agentconnect.md/daemon 1.0.0-rc.240 → 1.0.0-rc.242

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.
@@ -2,8 +2,8 @@ import { i as __require, o as __toESM, t as __commonJSMin } from "./rolldown-run
2
2
  import { t as require_src$1 } from "./src-DDtlVutj.js";
3
3
  import { T as NEVER, _ as record, a as array, b as unknown, c as discriminatedUnion, d as literal, l as int, m as object, n as _enum, o as boolean, p as number, u as intersection, v as string, x as url, y as union } from "./schemas-CQkO9hC5.js";
4
4
  import { c as registryPath, s as registryCachePath } from "./paths-DEcUO8lC.js";
5
- import { delimiter, dirname, join, normalize } from "node:path";
6
- import { accessSync, constants, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
5
+ import { delimiter, dirname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
6
+ import { accessSync, constants, existsSync, mkdirSync, promises, readFileSync, rmSync, writeFileSync } from "node:fs";
7
7
  import { spawn } from "child_process";
8
8
  import { EventEmitter } from "node:events";
9
9
  import { execFile, spawn as spawn$1 } from "node:child_process";
@@ -172,6 +172,205 @@ async function resolveRuntimes(cfg, root, opts = {}) {
172
172
  };
173
173
  }
174
174
  //#endregion
175
+ //#region src/mcp/tools.ts
176
+ const obj = (properties, required = []) => ({
177
+ type: "object",
178
+ properties,
179
+ required,
180
+ additionalProperties: false
181
+ });
182
+ /**
183
+ * Tools injected when an agent has a Slack integration. `sendSlackMessage` is
184
+ * the active-messaging tool from the design (token held by the daemon, invisible
185
+ * to the model); the rest are channel/user read helpers.
186
+ */
187
+ const SLACK_TOOLS = [
188
+ {
189
+ name: "sendSlackMessage",
190
+ description: "Send a message to a Slack channel. Omit `channel` to post in the channel this conversation is happening in. Omit `thread` to reply in the current thread; pass an empty string to post to the channel root instead.",
191
+ inputSchema: obj({
192
+ text: {
193
+ type: "string",
194
+ description: "Message body, in standard Markdown (CommonMark/GFM). Use `**bold**`, `~~strikethrough~~`, `# headings`, tables, etc. — Slack renders standard Markdown natively (do NOT use single-char Slack mrkdwn like `*bold*` or `~strike~`)."
195
+ },
196
+ channel: {
197
+ type: "string",
198
+ description: "Target channel ID (e.g. C0123ABC). Defaults to the current channel."
199
+ },
200
+ thread: {
201
+ type: "string",
202
+ description: "Thread timestamp to reply under. Defaults to the current thread; \"\" posts to the channel root."
203
+ }
204
+ }, ["text"])
205
+ },
206
+ {
207
+ name: "getCurrentChannel",
208
+ description: "Return the Slack channel (and thread) this conversation is bound to, including the channel name when available.",
209
+ inputSchema: obj({})
210
+ },
211
+ {
212
+ name: "listChannelMembers",
213
+ description: "List the users and bots in a channel (id, name, is_bot). Omit `channel` to list members of the current channel.",
214
+ inputSchema: obj({ channel: {
215
+ type: "string",
216
+ description: "Channel ID. Defaults to the current channel."
217
+ } })
218
+ },
219
+ {
220
+ name: "listChannels",
221
+ description: "List the public/private channels the bot is a member of and can post to.",
222
+ inputSchema: obj({})
223
+ },
224
+ {
225
+ name: "getUserProfile",
226
+ description: "Look up a Slack user or bot by id, returning their display name, real name, and bot flag.",
227
+ inputSchema: obj({ user: {
228
+ type: "string",
229
+ description: "User ID (e.g. U0123ABC)."
230
+ } }, ["user"])
231
+ },
232
+ {
233
+ name: "readSlackFile",
234
+ description: "Fetch the contents of a file shared in Slack, using the bot credentials. You do NOT have direct network access to Slack's private file URLs (they require the bot token) — use this tool instead of curl/fetch. Pass the file's `url` (the `url_private` / `uri` from a shared attachment or resource link). Images are returned as viewable image content; text files as text. Supply `mimeType` when known for correct handling.",
235
+ inputSchema: obj({
236
+ url: {
237
+ type: "string",
238
+ description: "The file's url_private (or url_private_download) / resource-link uri."
239
+ },
240
+ mimeType: {
241
+ type: "string",
242
+ description: "Optional MIME type hint, e.g. image/png or text/plain."
243
+ }
244
+ }, ["url"])
245
+ }
246
+ ];
247
+ /**
248
+ * Tools injected when an agent has a Telegram integration. Mirrors the Slack set
249
+ * but for the Telegram data model: `channel` is a chat id (e.g. -1001234567890),
250
+ * `thread` is a forum-topic `message_thread_id`, and there is no `listChannels`
251
+ * (a bot cannot enumerate the chats it belongs to).
252
+ */
253
+ const TELEGRAM_TOOLS = [
254
+ {
255
+ name: "sendTelegramMessage",
256
+ description: "Send a message to a Telegram chat. Omit `channel` to post in the chat this conversation is happening in. Omit `thread` to reply in the current forum topic; pass an empty string to post outside any topic.",
257
+ inputSchema: obj({
258
+ text: {
259
+ type: "string",
260
+ description: "Message body, in standard Markdown (CommonMark/GFM). Use `**bold**`, `*italic*`, `` `code` ``, `[links](url)`, lists, etc. — the daemon converts it to Telegram formatting and escapes special characters for you, so write plain Markdown (do NOT pre-escape with backslashes)."
261
+ },
262
+ channel: {
263
+ type: "string",
264
+ description: "Target chat id (e.g. -1001234567890, or a positive id for a private chat). Defaults to the current chat."
265
+ },
266
+ thread: {
267
+ type: "string",
268
+ description: "Forum-topic message_thread_id to reply under. Defaults to the current topic; \"\" posts outside any topic."
269
+ }
270
+ }, ["text"])
271
+ },
272
+ {
273
+ name: "getCurrentChannel",
274
+ description: "Return the Telegram chat (and forum topic) this conversation is bound to, including the chat title when available.",
275
+ inputSchema: obj({})
276
+ },
277
+ {
278
+ name: "listChannelMembers",
279
+ description: "List the users and bots in a chat (id, name, is_bot). Omit `channel` to list members of the current chat. Note: Telegram only exposes administrators for large groups.",
280
+ inputSchema: obj({ channel: {
281
+ type: "string",
282
+ description: "Chat id. Defaults to the current chat."
283
+ } })
284
+ },
285
+ {
286
+ name: "getUserProfile",
287
+ description: "Look up a Telegram user or bot by id, returning their display name, username, and bot flag.",
288
+ inputSchema: obj({ user: {
289
+ type: "string",
290
+ description: "Numeric user id (e.g. 12345678)."
291
+ } }, ["user"])
292
+ },
293
+ {
294
+ name: "readTelegramFile",
295
+ description: "Fetch the contents of a file shared in Telegram, using the bot credentials. You do NOT have direct network access to Telegram's file storage — use this tool instead of curl/fetch. Pass the file's `url` (the `file_id` from a shared attachment or the `uri` of a resource link). Images are returned as viewable image content; text files as text. Supply `mimeType` when known for correct handling.",
296
+ inputSchema: obj({
297
+ url: {
298
+ type: "string",
299
+ description: "The shared file's Telegram file_id (or resource-link uri)."
300
+ },
301
+ mimeType: {
302
+ type: "string",
303
+ description: "Optional MIME type hint, e.g. image/png or text/plain."
304
+ }
305
+ }, ["url"])
306
+ }
307
+ ];
308
+ /**
309
+ * The agent's long-term memory tools — available to EVERY agent regardless of
310
+ * integrations. Memory is a directory (`<agent-root>/memory/`) with a `MEMORY.md`
311
+ * index (injected into the prompt each session) plus `<topic>.md` files the agent
312
+ * reads on demand. Keep the index short; move detail into topic files.
313
+ */
314
+ const MEMORY_TOOLS = [
315
+ {
316
+ name: "listMemory",
317
+ description: "List your memory files — the MEMORY.md index plus any topic files you have written. Use this to discover what you have stored before reading or updating it.",
318
+ inputSchema: obj({})
319
+ },
320
+ {
321
+ name: "readMemory",
322
+ description: "Read one of your memory files. Omit `path` (or pass \"MEMORY.md\") to read the index; pass a topic file name (e.g. \"deploys.md\") to read that topic. The index is already shown to you at the start of each session — use this to pull the detail behind an index entry when it is relevant.",
323
+ inputSchema: obj({ path: {
324
+ type: "string",
325
+ description: "Memory file name (e.g. \"deploys.md\"). Defaults to the MEMORY.md index."
326
+ } })
327
+ },
328
+ {
329
+ name: "writeMemory",
330
+ description: "Save one of your memory files (whole-file overwrite). Omit `path` to write the MEMORY.md index; pass a topic file name (e.g. \"deploys.md\") for a topic. Keep the INDEX short — a scannable list that links to topic files (e.g. \"- [deploys](deploys.md) — how we ship\"); put the actual detail in the topic files. Use for durable facts across sessions: conventions, decisions, who to ask, things you had to re-learn. A flat directory — no subfolders in the path.",
331
+ inputSchema: obj({
332
+ path: {
333
+ type: "string",
334
+ description: "Memory file name (e.g. \"deploys.md\"). Defaults to the MEMORY.md index. No subdirectories."
335
+ },
336
+ content: {
337
+ type: "string",
338
+ description: "The full new file contents, in Markdown. Replaces the file."
339
+ }
340
+ }, ["content"])
341
+ }
342
+ ];
343
+ /** The memory tool names — used to strip them for a `native`-memory agent (which
344
+ * uses the runtime's own memory instead). */
345
+ const MEMORY_TOOL_NAMES = new Set(MEMORY_TOOLS.map((t) => t.name));
346
+ [...new Set([
347
+ ...SLACK_TOOLS,
348
+ ...TELEGRAM_TOOLS,
349
+ ...MEMORY_TOOLS
350
+ ].map((t) => t.name))];
351
+ /**
352
+ * The default MCP tool set for an agent. The memory tools are always present
353
+ * (every agent has memory); the platform tools are gated by the agent's
354
+ * integrations. Deduped by name so an agent bridging both platforms doesn't get
355
+ * the shared read helpers (getCurrentChannel/listChannelMembers/getUserProfile)
356
+ * twice — the first platform's variant wins, and both behave identically.
357
+ */
358
+ function toolsForIntegrations(integrations) {
359
+ const tools = [];
360
+ const seen = /* @__PURE__ */ new Set();
361
+ const add = (set) => {
362
+ for (const t of set) {
363
+ if (seen.has(t.name)) continue;
364
+ seen.add(t.name);
365
+ tools.push(t);
366
+ }
367
+ };
368
+ add(MEMORY_TOOLS);
369
+ if (integrations.some((i) => i.platform === "slack")) add(SLACK_TOOLS);
370
+ if (integrations.some((i) => i.platform === "telegram")) add(TELEGRAM_TOOLS);
371
+ return tools;
372
+ }
373
+ //#endregion
175
374
  //#region ../protocol/dist/envelope.js
176
375
  /**
177
376
  * The protocol envelope — every frame, both directions, is wrapped in this.
@@ -468,6 +667,11 @@ const AgentSpec = object({
468
667
  pause: boolean().optional(),
469
668
  workspace: AgentWorkspace.optional(),
470
669
  env: record(string(), string()).optional(),
670
+ memory: object({ provider: _enum([
671
+ "native",
672
+ "managed",
673
+ "external"
674
+ ]) }).optional(),
471
675
  mcpServers: array(string()).default([])
472
676
  });
473
677
  const AgentLaunch = object({
@@ -1722,6 +1926,492 @@ function isFrame(type) {
1722
1926
  return (frame) => frame.type === type;
1723
1927
  }
1724
1928
  //#endregion
1929
+ //#region src/agents/memory.ts
1930
+ /**
1931
+ * Agent memory — a DIRECTORY at the agent's ROOT (`<agent-root>/memory/`), OUTSIDE
1932
+ * the workspace so it survives a workspace reset / re-clone and is never committed
1933
+ * into a github-workspace repo.
1934
+ *
1935
+ * Index-plus-topics shape (the structure Claude Code's auto-memory uses): a lean
1936
+ * `MEMORY.md` index — injected into the prompt every session, so it must stay small
1937
+ * — plus any number of `<topic>.md` files the agent reads on demand. The agent
1938
+ * maintains it via the `readMemory` / `writeMemory` / `listMemory` MCP tools; the
1939
+ * daemon serves the same directory to the CP for the console.
1940
+ *
1941
+ * SECURITY: topic paths are agent/console-supplied, so every path is contained to
1942
+ * the memory dir — absolute paths and `..` escapes are rejected, and the resolved
1943
+ * path is re-checked so a symlink can't smuggle a target out. Flat by design (one
1944
+ * level): a topic is a file directly under `memory/`, no nested dirs.
1945
+ */
1946
+ const MEMORY_DIRNAME = "memory";
1947
+ /** Max bytes of the index injected into the prompt each session — keeps a large
1948
+ * memory from swelling the context (mirrors Claude Code's 25 KB entrypoint cap). */
1949
+ const MAX_INDEX_INJECT_BYTES = 25e3;
1950
+ /** Hard cap on a single memory file. Injected/read every session, so an unbounded
1951
+ * file would swell the prompt and the wire; writes over this are rejected. */
1952
+ const MAX_MEMORY_FILE_BYTES = 256e3;
1953
+ /** Raised when a memory path escapes the memory dir. Surfaces as `BAD_PAYLOAD`. */
1954
+ var MemoryPathError = class extends Error {
1955
+ constructor(message) {
1956
+ super(message);
1957
+ this.name = "MemoryPathError";
1958
+ }
1959
+ };
1960
+ /** Raised when a write exceeds {@link MAX_MEMORY_FILE_BYTES}. `BAD_PAYLOAD`. */
1961
+ var MemoryTooLargeError = class extends Error {
1962
+ constructor(message) {
1963
+ super(message);
1964
+ this.name = "MemoryTooLargeError";
1965
+ }
1966
+ };
1967
+ /** Raised when an `ifMatchMtime` precondition fails (the file changed under the
1968
+ * writer — e.g. a console edit racing a newer agent write). Surfaces as CONFLICT. */
1969
+ var MemoryConflictError = class extends Error {
1970
+ constructor(message) {
1971
+ super(message);
1972
+ this.name = "MemoryConflictError";
1973
+ }
1974
+ };
1975
+ /** The memory directory for an agent, given its root dir (where agent.json lives). */
1976
+ function memoryDir(agentDir) {
1977
+ return join(agentDir, MEMORY_DIRNAME);
1978
+ }
1979
+ /** Resolve a memory-dir-relative path to an absolute one, contained to the dir.
1980
+ * Rejects absolute paths, `..` escapes, and any nested directory (flat only). */
1981
+ function resolveInMemoryDir(agentDir, relPath) {
1982
+ if (isAbsolute(relPath)) throw new MemoryPathError("absolute paths are not allowed");
1983
+ const dir = memoryDir(agentDir);
1984
+ const abs = resolve(dir, relPath);
1985
+ if (abs !== dir && !abs.startsWith(dir + sep)) throw new MemoryPathError("path escapes the memory dir");
1986
+ const rel = abs.slice(dir.length + 1);
1987
+ if (rel.includes(sep)) throw new MemoryPathError("memory is a flat directory (no subdirectories)");
1988
+ if (rel.length === 0) throw new MemoryPathError("a file name is required");
1989
+ return abs;
1990
+ }
1991
+ /** Seed `<agent-root>/memory/MEMORY.md` with a header if the dir/index is absent
1992
+ * (idempotent). Called at session start so a brand-new agent always has an index. */
1993
+ function ensureMemory(agentDir, agentName) {
1994
+ const dir = memoryDir(agentDir);
1995
+ mkdirSync(dir, { recursive: true });
1996
+ const index = join(dir, MEMORY_INDEX);
1997
+ if (!existsSync(index)) writeFileSync(index, `# ${agentName} memory\n\nYour long-term memory index. Keep it short — link out to topic files (e.g. \`[deploys](deploys.md)\`) for detail and read them on demand with the readMemory tool.\n`);
1998
+ }
1999
+ /** Read the memory index (`MEMORY.md`) for prompt injection, trimmed to the inject
2000
+ * cap so a large index can't blow up the prompt; '' when absent. */
2001
+ async function readIndex(agentDir) {
2002
+ const text = await readMemoryFile(agentDir, MEMORY_INDEX);
2003
+ if (Buffer.byteLength(text) <= 25e3) return text;
2004
+ return Buffer.from(text, "utf8").subarray(0, MAX_INDEX_INJECT_BYTES).toString("utf8") + "\n\n[…memory index truncated — trim MEMORY.md]";
2005
+ }
2006
+ /** Read a memory file's text; '' when it does not exist (never throws on ENOENT). */
2007
+ async function readMemoryFile(agentDir, relPath) {
2008
+ const abs = resolveInMemoryDir(agentDir, relPath);
2009
+ try {
2010
+ return await promises.readFile(abs, "utf8");
2011
+ } catch (err) {
2012
+ if (err.code === "ENOENT") return "";
2013
+ throw err;
2014
+ }
2015
+ }
2016
+ /** Overwrite a memory file with `content` (creating the dir if needed). Atomic
2017
+ * (tmp + rename) so a concurrent read never sees a partial file.
2018
+ *
2019
+ * - Rejects content over {@link MAX_MEMORY_FILE_BYTES} ({@link MemoryTooLargeError}).
2020
+ * - `ifMatchMtime` (optimistic concurrency): when given, the current file's mtime
2021
+ * must equal it, else {@link MemoryConflictError} — so a console edit can't
2022
+ * clobber a newer write. A brand-new file (no mtime) matches `ifMatchMtime`
2023
+ * only when the caller passes none (or the empty string). */
2024
+ async function writeMemoryFile(agentDir, relPath, content, ifMatchMtime) {
2025
+ if (Buffer.byteLength(content) > 256e3) throw new MemoryTooLargeError(`memory file exceeds the ${MAX_MEMORY_FILE_BYTES}-byte limit`);
2026
+ const abs = resolveInMemoryDir(agentDir, relPath);
2027
+ if (ifMatchMtime) {
2028
+ let current = null;
2029
+ try {
2030
+ current = (await promises.stat(abs)).mtime.toISOString();
2031
+ } catch (err) {
2032
+ if (err.code !== "ENOENT") throw err;
2033
+ }
2034
+ if (current !== ifMatchMtime) throw new MemoryConflictError("the memory file changed since it was read; reload and retry");
2035
+ }
2036
+ await promises.mkdir(memoryDir(agentDir), { recursive: true });
2037
+ const tmp = `${abs}.tmp`;
2038
+ await promises.writeFile(tmp, content, "utf8");
2039
+ await promises.rename(tmp, abs);
2040
+ const st = await promises.stat(abs);
2041
+ return {
2042
+ size: st.size,
2043
+ mtime: st.mtime.toISOString()
2044
+ };
2045
+ }
2046
+ /** List the files in the memory dir (flat; index + topics), sorted with the index
2047
+ * first. Empty when the dir does not exist. `.tmp` write artifacts are skipped. */
2048
+ async function listMemory(agentDir) {
2049
+ const dir = memoryDir(agentDir);
2050
+ let dirents;
2051
+ try {
2052
+ dirents = await promises.readdir(dir, { withFileTypes: true });
2053
+ } catch (err) {
2054
+ if (err.code === "ENOENT") return [];
2055
+ throw err;
2056
+ }
2057
+ const files = [];
2058
+ for (const d of dirents) {
2059
+ if (!d.isFile() || d.name.endsWith(".tmp")) continue;
2060
+ try {
2061
+ const st = await promises.stat(join(dir, d.name));
2062
+ files.push({
2063
+ name: d.name,
2064
+ size: st.size,
2065
+ mtime: st.mtime.toISOString()
2066
+ });
2067
+ } catch {}
2068
+ }
2069
+ files.sort((a, b) => a.name === "MEMORY.md" ? -1 : b.name === "MEMORY.md" ? 1 : a.name.localeCompare(b.name));
2070
+ return files;
2071
+ }
2072
+ //#endregion
2073
+ //#region src/agents/native-memory.ts
2074
+ /**
2075
+ * Native-memory runtime levers + file surfacing.
2076
+ *
2077
+ * The `native` MemoryProvider (agents/memory-provider.ts) redirects the runtime's
2078
+ * OWN memory under the agent root — so each agent's native memory is isolated
2079
+ * instead of sharing the host's `~/.claude` / `~/.codex`. Two responsibilities:
2080
+ *
2081
+ * 1. `nativeRuntimeEnv` — the env delta that redirects (and, where needed, enables)
2082
+ * the runtime's memory. This is an EXPLICIT per-runtime registry: we only set
2083
+ * env for runtimes whose levers we've verified against their docs. A runtime not
2084
+ * in the registry is `isNativeRuntimeSupported === false`, and selecting native
2085
+ * for it fails loudly upstream — we never guess env for an unverified runtime.
2086
+ *
2087
+ * 2. `nativeMemoryList/Read/Write` — surface the runtime's memory files to the
2088
+ * console from wherever the redirect points. Unlike managed's flat
2089
+ * `<agent-root>/memory/`, the runtime dirs are nested (Claude keys memory by a
2090
+ * sanitized-cwd subpath), so this uses a nested-but-contained walk.
2091
+ *
2092
+ * Levers verified 2026-07-08 (claude docs + binary strings; codex docs; and the
2093
+ * daemon's own `runtimes/probe.ts` home-override table):
2094
+ * - claude → `CLAUDE_CONFIG_DIR=<root>/.claude`; auto-memory lands at
2095
+ * `<root>/.claude/projects/<sanitized-cwd>/memory/`. Do NOT set the disable flag.
2096
+ * - codex → `CODEX_HOME=<root>/.codex`; memories at `<root>/.codex/memories/`.
2097
+ */
2098
+ function cmdIncludes(runtime, needle) {
2099
+ return [runtime.command, ...runtime.args].join(" ").toLowerCase().includes(needle);
2100
+ }
2101
+ /**
2102
+ * The explicit registry. ADD a runtime here only after verifying its env levers
2103
+ * against the tool's own docs (and, ideally, the installed binary) — mirroring the
2104
+ * home-override table the daemon already keeps in `runtimes/probe.ts`. An absent
2105
+ * runtime is intentionally unsupported for native memory.
2106
+ */
2107
+ const NATIVE_RUNTIMES = [{
2108
+ matches: (rt) => cmdIncludes(rt, "claude"),
2109
+ env: (root) => ({ CLAUDE_CONFIG_DIR: join(root, ".claude") }),
2110
+ readRoot: (root) => join(root, ".claude", "projects")
2111
+ }, {
2112
+ matches: (rt) => cmdIncludes(rt, "codex"),
2113
+ env: (root) => ({ CODEX_HOME: join(root, ".codex") }),
2114
+ readRoot: (root) => join(root, ".codex", "memories")
2115
+ }];
2116
+ function specFor(runtime) {
2117
+ if (!runtime) return void 0;
2118
+ return NATIVE_RUNTIMES.find((s) => s.matches(runtime));
2119
+ }
2120
+ /** Whether native memory is supported (env levers registered) for this runtime. */
2121
+ function isNativeRuntimeSupported(runtime) {
2122
+ return specFor(runtime) !== void 0;
2123
+ }
2124
+ /** The env delta redirecting the runtime's own memory under `agentRoot`. Empty for
2125
+ * an unregistered runtime (callers should gate on `isNativeRuntimeSupported`). */
2126
+ function nativeRuntimeEnv(runtime, agentRoot) {
2127
+ return specFor(runtime)?.env(agentRoot) ?? {};
2128
+ }
2129
+ /** Resolve a console-supplied relative path under `root`, allowing nested dirs but
2130
+ * rejecting absolute paths, `..` escapes, and symlink-smuggled escapes. Unlike the
2131
+ * managed dir (flat), native runtime dirs are nested. */
2132
+ function resolveContained(root, relPath) {
2133
+ if (isAbsolute(relPath)) throw new MemoryPathError("absolute paths are not allowed");
2134
+ const abs = resolve(root, relPath);
2135
+ if (abs !== root && !abs.startsWith(root + sep)) throw new MemoryPathError("path escapes the memory dir");
2136
+ if (abs === root) throw new MemoryPathError("a file name is required");
2137
+ return abs;
2138
+ }
2139
+ /** Recursively list files under `dir` (relative names, posix-ish with `sep`), sorted.
2140
+ * Empty when the dir is absent. Skips `.tmp` artifacts. */
2141
+ async function listUnder(dir) {
2142
+ const out = [];
2143
+ async function walk(cur) {
2144
+ let dirents;
2145
+ try {
2146
+ dirents = await promises.readdir(cur, { withFileTypes: true });
2147
+ } catch (err) {
2148
+ if (err.code === "ENOENT") return;
2149
+ throw err;
2150
+ }
2151
+ for (const d of dirents) {
2152
+ const p = join(cur, d.name);
2153
+ if (d.isDirectory()) await walk(p);
2154
+ else if (d.isFile() && !d.name.endsWith(".tmp")) try {
2155
+ const st = await promises.stat(p);
2156
+ out.push({
2157
+ name: relative(dir, p),
2158
+ size: st.size,
2159
+ mtime: st.mtime.toISOString()
2160
+ });
2161
+ } catch {}
2162
+ }
2163
+ }
2164
+ await walk(dir);
2165
+ out.sort((a, b) => a.name.localeCompare(b.name));
2166
+ return out;
2167
+ }
2168
+ /** List the runtime's native memory files under the agent root. */
2169
+ async function nativeMemoryList(agentDir, runtime) {
2170
+ const spec = specFor(runtime);
2171
+ if (!spec) return [];
2172
+ return listUnder(spec.readRoot(agentDir));
2173
+ }
2174
+ /** Read one native memory file (relative to the runtime's read root); '' when absent. */
2175
+ async function nativeMemoryRead(agentDir, runtime, path) {
2176
+ const spec = specFor(runtime);
2177
+ if (!spec) return {
2178
+ path,
2179
+ content: ""
2180
+ };
2181
+ const abs = resolveContained(spec.readRoot(agentDir), path);
2182
+ try {
2183
+ return {
2184
+ path,
2185
+ content: await promises.readFile(abs, "utf8")
2186
+ };
2187
+ } catch (err) {
2188
+ if (err.code === "ENOENT") return {
2189
+ path,
2190
+ content: ""
2191
+ };
2192
+ throw err;
2193
+ }
2194
+ }
2195
+ /** Overwrite one native memory file (console edit). Atomic tmp+rename; size-capped. */
2196
+ async function nativeMemoryWrite(agentDir, runtime, path, content, _ifMatch) {
2197
+ const spec = specFor(runtime);
2198
+ if (!spec) throw new MemoryPathError("native memory is not supported for this runtime");
2199
+ if (Buffer.byteLength(content) > 256e3) throw new MemoryTooLargeError(`memory file exceeds the ${MAX_MEMORY_FILE_BYTES}-byte limit`);
2200
+ const abs = resolveContained(spec.readRoot(agentDir), path);
2201
+ const dir = abs.slice(0, abs.length - (abs.split(sep).pop()?.length ?? 0));
2202
+ await promises.mkdir(dir, { recursive: true });
2203
+ const tmp = `${abs}.tmp`;
2204
+ await promises.writeFile(tmp, content, "utf8");
2205
+ await promises.rename(tmp, abs);
2206
+ const st = await promises.stat(abs);
2207
+ return {
2208
+ ok: true,
2209
+ path,
2210
+ size: st.size,
2211
+ mtime: st.mtime.toISOString()
2212
+ };
2213
+ }
2214
+ //#endregion
2215
+ //#region src/agents/memory-provider.ts
2216
+ /** Thrown when a provider can't be built for an agent (e.g. `external` not yet
2217
+ * implemented, or `native` on a runtime whose memory env levers aren't registered). */
2218
+ var MemoryProviderUnavailableError = class extends Error {
2219
+ constructor(message) {
2220
+ super(message);
2221
+ this.name = "MemoryProviderUnavailableError";
2222
+ }
2223
+ };
2224
+ /**
2225
+ * `managed` memory: our `<agent-root>/memory/` directory. A thin facade over
2226
+ * `agents/memory.ts` — every method delegates to the existing primitive and lets
2227
+ * its error classes (`MemoryPathError` / `MemoryTooLargeError` /
2228
+ * `MemoryConflictError`) propagate raw, so the MCP + CP error mappings are unchanged.
2229
+ */
2230
+ var ManagedMemoryProvider = class {
2231
+ agentDirByAgent;
2232
+ kind = "managed";
2233
+ /** `agentDirByAgent` resolves an agent id → its root dir (which holds `memory/`),
2234
+ * or undefined for an unknown agent. */
2235
+ constructor(agentDirByAgent) {
2236
+ this.agentDirByAgent = agentDirByAgent;
2237
+ }
2238
+ dirFor(agentId) {
2239
+ const dir = this.agentDirByAgent(agentId);
2240
+ if (!dir) throw new Error(`unknown agent ${agentId}`);
2241
+ return dir;
2242
+ }
2243
+ runtimeEnv(runtime) {
2244
+ if (isClaudeRuntime(runtime)) return { CLAUDE_CODE_DISABLE_AUTO_MEMORY: "1" };
2245
+ return {};
2246
+ }
2247
+ ensure(scope, agentName) {
2248
+ ensureMemory(this.dirFor(scope.agentId), agentName);
2249
+ }
2250
+ async injectAtSessionStart(scope) {
2251
+ return readIndex(this.dirFor(scope.agentId));
2252
+ }
2253
+ async recordTurn(_scope, _turn) {}
2254
+ tools() {
2255
+ return MEMORY_TOOLS;
2256
+ }
2257
+ async list(scope) {
2258
+ return listMemory(this.dirFor(scope.agentId));
2259
+ }
2260
+ async read(scope, path) {
2261
+ return {
2262
+ path,
2263
+ content: await readMemoryFile(this.dirFor(scope.agentId), path)
2264
+ };
2265
+ }
2266
+ async write(scope, path, content, ifMatch) {
2267
+ const { size, mtime } = await writeMemoryFile(this.dirFor(scope.agentId), path, content, ifMatch);
2268
+ return {
2269
+ ok: true,
2270
+ path,
2271
+ size,
2272
+ mtime
2273
+ };
2274
+ }
2275
+ };
2276
+ /**
2277
+ * `native` memory: the runtime's OWN memory (Claude auto-memory / Codex memories),
2278
+ * redirected under the agent root via `runtimeEnv` for per-agent isolation. The
2279
+ * runtime writes and loads it itself, so we do NOT seed, inject, or expose tools —
2280
+ * doing so would create a second store the runtime never reads. `list`/`read`/`write`
2281
+ * surface the runtime's memory files for the console (see native-memory.ts).
2282
+ *
2283
+ * The per-runtime env levers are an explicit registry (native-memory.ts). An agent
2284
+ * selecting `native` on a runtime whose levers aren't registered fails loudly at
2285
+ * construction rather than silently mis-setting env — we don't guess env for a
2286
+ * runtime we haven't verified.
2287
+ */
2288
+ var NativeMemoryProvider = class {
2289
+ agentDirByAgent;
2290
+ runtimeFor;
2291
+ kind = "native";
2292
+ constructor(agentDirByAgent, runtimeFor) {
2293
+ this.agentDirByAgent = agentDirByAgent;
2294
+ this.runtimeFor = runtimeFor;
2295
+ }
2296
+ dirFor(agentId) {
2297
+ const dir = this.agentDirByAgent(agentId);
2298
+ if (!dir) throw new Error(`unknown agent ${agentId}`);
2299
+ return dir;
2300
+ }
2301
+ runtimeEnv(_runtime) {
2302
+ throw new Error("NativeMemoryProvider.runtimeEnv must not be called — use memoryProviderFor at spawn");
2303
+ }
2304
+ ensure() {}
2305
+ async injectAtSessionStart() {
2306
+ return "";
2307
+ }
2308
+ async recordTurn() {}
2309
+ tools() {
2310
+ return [];
2311
+ }
2312
+ async list(scope) {
2313
+ return nativeMemoryList(this.dirFor(scope.agentId), this.runtimeFor(scope.agentId));
2314
+ }
2315
+ async read(scope, path) {
2316
+ return nativeMemoryRead(this.dirFor(scope.agentId), this.runtimeFor(scope.agentId), path);
2317
+ }
2318
+ async write(scope, path, content, ifMatch) {
2319
+ return nativeMemoryWrite(this.dirFor(scope.agentId), this.runtimeFor(scope.agentId), path, content, ifMatch);
2320
+ }
2321
+ };
2322
+ /** A claude runtime — its command/args reference `claude` (mirrors AcpHost.isClaudeRuntime). */
2323
+ function isClaudeRuntime(runtime) {
2324
+ return [runtime.command, ...runtime.args].join(" ").toLowerCase().includes("claude");
2325
+ }
2326
+ /**
2327
+ * `DispatchingMemoryProvider` — the daemon's single `MemoryProvider`. Routes every
2328
+ * scope-bearing call to the agent's configured concrete provider (managed | native),
2329
+ * built lazily and cached (both are stateless facades over the resolvers). `external`
2330
+ * throws `MemoryProviderUnavailableError` when first needed.
2331
+ *
2332
+ * `runtimeEnv` is NOT the dispatch path (it has no scope); it throws here to make the
2333
+ * misuse obvious — the spawn path uses `memoryProviderFor` directly.
2334
+ */
2335
+ var DispatchingMemoryProvider = class {
2336
+ agentDirByAgent;
2337
+ runtimeFor;
2338
+ providerKindFor;
2339
+ kind = "managed";
2340
+ managed;
2341
+ native;
2342
+ constructor(agentDirByAgent, runtimeFor, providerKindFor) {
2343
+ this.agentDirByAgent = agentDirByAgent;
2344
+ this.runtimeFor = runtimeFor;
2345
+ this.providerKindFor = providerKindFor;
2346
+ this.managed = new ManagedMemoryProvider(agentDirByAgent);
2347
+ this.native = new NativeMemoryProvider(agentDirByAgent, runtimeFor);
2348
+ }
2349
+ forAgent(agentId) {
2350
+ const kind = this.providerKindFor(agentId);
2351
+ if (kind === "managed") return this.managed;
2352
+ if (kind === "native") return this.native;
2353
+ throw new MemoryProviderUnavailableError(`memory provider "${kind}" is not yet implemented`);
2354
+ }
2355
+ runtimeEnv(_runtime) {
2356
+ throw new Error("DispatchingMemoryProvider.runtimeEnv must not be called — use memoryProviderFor at spawn");
2357
+ }
2358
+ ensure(scope, agentName) {
2359
+ this.forAgent(scope.agentId).ensure(scope, agentName);
2360
+ }
2361
+ injectAtSessionStart(scope) {
2362
+ return this.forAgent(scope.agentId).injectAtSessionStart(scope);
2363
+ }
2364
+ recordTurn(scope, turn) {
2365
+ return this.forAgent(scope.agentId).recordTurn(scope, turn);
2366
+ }
2367
+ list(scope) {
2368
+ return this.forAgent(scope.agentId).list(scope);
2369
+ }
2370
+ read(scope, path) {
2371
+ return this.forAgent(scope.agentId).read(scope, path);
2372
+ }
2373
+ write(scope, path, content, ifMatch) {
2374
+ return this.forAgent(scope.agentId).write(scope, path, content, ifMatch);
2375
+ }
2376
+ /** The memory tools to inject for a specific agent (the daemon calls this in
2377
+ * `mcpServersFor` — native contributes none). Scope-carrying, unlike the
2378
+ * interface's scopeless `tools()`. */
2379
+ toolsForAgent(agentId) {
2380
+ return this.forAgent(agentId).tools();
2381
+ }
2382
+ /** Interface `tools()` has no scope; the daemon uses `toolsForAgent`. Return the
2383
+ * managed set as a safe default (never the live injection path). */
2384
+ tools() {
2385
+ return MEMORY_TOOLS;
2386
+ }
2387
+ };
2388
+ /** The daemon-side memory provider kind for an agent (absent ⇒ managed default). */
2389
+ function memoryKindOf(agent) {
2390
+ return agent.memory?.provider ?? "managed";
2391
+ }
2392
+ /**
2393
+ * Build the concrete provider for ONE agent — used at spawn (`daemon.ts` / `chat.ts`)
2394
+ * to source `runtimeEnv`, and validated eagerly so a misconfigured agent (external,
2395
+ * or native on an unregistered runtime) fails loudly with the agent's root bound.
2396
+ * Throws `MemoryProviderUnavailableError` on an unbuildable provider.
2397
+ */
2398
+ function memoryProviderFor(agent, runtime) {
2399
+ const kind = memoryKindOf(agent);
2400
+ if (kind === "managed") {
2401
+ const p = new ManagedMemoryProvider(() => agent.dir);
2402
+ return { runtimeEnv: () => p.runtimeEnv(runtime) };
2403
+ }
2404
+ if (kind === "native") {
2405
+ if (!isNativeRuntimeSupported(runtime)) throw new MemoryProviderUnavailableError(`native memory is not supported for this runtime (env levers unverified): ${runtime.command}`);
2406
+ return { runtimeEnv: () => nativeRuntimeEnv(runtime, agent.dir) };
2407
+ }
2408
+ throw new MemoryProviderUnavailableError(`memory provider "${kind}" is not yet implemented`);
2409
+ }
2410
+ /** Build the daemon's dispatching memory provider over the agent resolvers. */
2411
+ function createMemoryProvider(agentDirByAgent, runtimeFor, providerKindFor) {
2412
+ return new DispatchingMemoryProvider(agentDirByAgent, runtimeFor, providerKindFor);
2413
+ }
2414
+ //#endregion
1725
2415
  //#region ../../node_modules/.pnpm/@kwsites+file-exists@1.1.1/node_modules/@kwsites/file-exists/dist/src/index.js
1726
2416
  var require_src = /* @__PURE__ */ __commonJSMin(((exports) => {
1727
2417
  var __importDefault = exports && exports.__importDefault || function(mod) {
@@ -10409,13 +11099,15 @@ const ULTRACODE_EFFORT = "ultracode";
10409
11099
  * or with workflows unavailable the runtime reports `applied.ultracode=false` and
10410
11100
  * the session still runs on its default effort — nothing to fail on our side.
10411
11101
  * - `systemPrompt` (top-level, sibling of `claudeCode`): the agent's system-prompt
10412
- * seed. We send it as `{ append }` so it layers ON TOP of claude-acp's
10413
- * `claude_code` preset (keeping the coding scaffolding) rather than replacing it.
10414
- * Honored by claude-agent-acp #91; older runtimes ignore the unknown `_meta` key.
10415
- * This is the only channel for the system prompt — ACP session/new has no such
10416
- * field, and other runtimes have no equivalent yet (see cp-overlay.ts). */
10417
- function claudeSessionMeta(reasoningEffort, isClaudeRuntime, systemPrompt) {
11102
+ * seed PLUS, on a fresh session, the agent's memory index (standing context, not a
11103
+ * user turn — see SessionManager). We send it as `{ append }` so it layers ON TOP
11104
+ * of claude-acp's `claude_code` preset (keeping the coding scaffolding) rather than
11105
+ * replacing it. Honored by claude-agent-acp #91; older runtimes ignore the unknown
11106
+ * `_meta` key. This is the only channel for the system prompt — ACP session/new has
11107
+ * no such field, and other runtimes have no equivalent yet (see cp-overlay.ts). */
11108
+ function claudeSessionMeta(reasoningEffort, isClaudeRuntime, systemPrompt, memoryAppend) {
10418
11109
  if (!isClaudeRuntime) return void 0;
11110
+ const append = [systemPrompt, memoryAppend].filter(Boolean).join("\n\n");
10419
11111
  return {
10420
11112
  claudeCode: { options: {
10421
11113
  thinking: {
@@ -10427,7 +11119,7 @@ function claudeSessionMeta(reasoningEffort, isClaudeRuntime, systemPrompt) {
10427
11119
  enableWorkflows: true
10428
11120
  } } : {}
10429
11121
  } },
10430
- ...systemPrompt ? { systemPrompt: { append: systemPrompt } } : {}
11122
+ ...append ? { systemPrompt: { append } } : {}
10431
11123
  };
10432
11124
  }
10433
11125
  /**
@@ -10531,7 +11223,6 @@ var AcpHost = class {
10531
11223
  this.opts.log?.info(`acp: CLAUDE_CODE_EXECUTABLE not set — using claude on PATH (${claude})`);
10532
11224
  }
10533
11225
  }
10534
- if (this.isClaudeRuntime() && env.CLAUDE_CODE_DISABLE_AUTO_MEMORY === void 0) env.CLAUDE_CODE_DISABLE_AUTO_MEMORY = "1";
10535
11226
  const child = spawn$1(resolveCommandPath(this.runtime.command, env) ?? this.runtime.command, this.runtime.args, {
10536
11227
  stdio: [
10537
11228
  "pipe",
@@ -10587,12 +11278,21 @@ var AcpHost = class {
10587
11278
  if (!seed || this.isClaudeRuntime()) return void 0;
10588
11279
  return seed;
10589
11280
  }
11281
+ /** Whether this runtime carries the system prompt (and the fresh-session memory
11282
+ * index) via `_meta.systemPrompt` — true for Claude, which gets them there rather
11283
+ * than as a leading inline block. Lets SessionManager route memory correctly:
11284
+ * Claude → `_meta` (via newSession's memoryAppend); others → an inline block. */
11285
+ usesMetaSystemPrompt() {
11286
+ return this.isClaudeRuntime();
11287
+ }
10590
11288
  /** Create a fresh ACP session. `effortOverride` (the sticky per-session effort choice,
10591
11289
  * when set) takes the place of the agent default in the Claude `_meta` — the only
10592
11290
  * channel `ultracode` can ride (the `thought_level` select rejects it); select-based
10593
- * effort/model/fast overrides are layered on afterward by the daemon at turn start. */
10594
- async newSession(cwd, mcpServers = [], effortOverride) {
10595
- const _meta = claudeSessionMeta(effortOverride ?? this.opts.configPrefs?.reasoningEffort, this.isClaudeRuntime(), this.opts.configPrefs?.systemPrompt);
11291
+ * effort/model/fast overrides are layered on afterward by the daemon at turn start.
11292
+ * `memoryAppend` (the agent's memory index for a FRESH session) rides the same Claude
11293
+ * `_meta.systemPrompt` append — standing context, never a user turn (see #398). */
11294
+ async newSession(cwd, mcpServers = [], effortOverride, memoryAppend) {
11295
+ const _meta = claudeSessionMeta(effortOverride ?? this.opts.configPrefs?.reasoningEffort, this.isClaudeRuntime(), this.opts.configPrefs?.systemPrompt, memoryAppend);
10596
11296
  const res = await this.conn.agent.request(methods.agent.session.new, {
10597
11297
  cwd,
10598
11298
  mcpServers,
@@ -10804,6 +11504,6 @@ var AcpHost = class {
10804
11504
  }
10805
11505
  };
10806
11506
  //#endregion
10807
- export { resolveRuntimes as C, cachedRuntimeVersions as S, encode as _, initGitInjection as a, RESERVED_MCP_SERVER_NAME as b, simpleGit as c, RELAY_DAEMON_SUBPROTOCOL as d, RELAY_DAEMON_WS_PATH as f, decodeEnvelope as g, buildEnvelope as h, prepareWorkspace as i, isFrame as l, decodeRelayDaemonFrame as m, installedRuntimes as n, probeGitVersion as o, buildRelayDaemonFrame as p, prefetchWorkspace as r, sessionGitEnv as s, AcpHost as t, normalizeGitUrl as u, MAX_FRAME_BYTES as v, cachedRuntimeNames as x, MEMORY_INDEX as y };
11507
+ export { toolsForIntegrations as A, decodeRelayDaemonFrame as C, MAX_FRAME_BYTES as D, encode as E, cachedRuntimeVersions as M, resolveRuntimes as N, RESERVED_MCP_SERVER_NAME as O, buildRelayDaemonFrame as S, decodeEnvelope as T, writeMemoryFile as _, initGitInjection as a, RELAY_DAEMON_SUBPROTOCOL as b, simpleGit as c, memoryProviderFor as d, MemoryConflictError as f, resolveInMemoryDir as g, listMemory as h, prepareWorkspace as i, cachedRuntimeNames as j, MEMORY_TOOL_NAMES as k, createMemoryProvider as l, MemoryTooLargeError as m, installedRuntimes as n, probeGitVersion as o, MemoryPathError as p, prefetchWorkspace as r, sessionGitEnv as s, AcpHost as t, memoryKindOf as u, isFrame as v, buildEnvelope as w, RELAY_DAEMON_WS_PATH as x, normalizeGitUrl as y };
10808
11508
 
10809
- //# sourceMappingURL=acp-host-M9PGQrVJ.js.map
11509
+ //# sourceMappingURL=acp-host-iKdfasdN.js.map