@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.
- package/dist/{acp-host-M9PGQrVJ.js → acp-host-iKdfasdN.js} +715 -15
- package/dist/acp-host-iKdfasdN.js.map +1 -0
- package/dist/{agent-list-LnizWro3.js → agent-list-aVWm-4ru.js} +2 -2
- package/dist/{agent-list-LnizWro3.js.map → agent-list-aVWm-4ru.js.map} +1 -1
- package/dist/{chat-CeThir-Y.js → chat-hWjORIJV.js} +7 -4
- package/dist/chat-hWjORIJV.js.map +1 -0
- package/dist/index.js +4 -4
- package/dist/{load-agents-C3pJSVeT.js → load-agents-Dt8ZYMsH.js} +6 -1
- package/dist/load-agents-Dt8ZYMsH.js.map +1 -0
- package/dist/{login-CoeR7Gr0.js → login-BKn49aay.js} +4 -4
- package/dist/{login-CoeR7Gr0.js.map → login-BKn49aay.js.map} +1 -1
- package/dist/{run-foreground-Cdxkfe7g.js → run-foreground-CLPgcMuB.js} +26 -419
- package/dist/{run-foreground-Cdxkfe7g.js.map → run-foreground-CLPgcMuB.js.map} +1 -1
- package/dist/run-foreground-V9zo7E1r.js +2 -0
- package/package.json +1 -1
- package/dist/acp-host-M9PGQrVJ.js.map +0 -1
- package/dist/chat-CeThir-Y.js.map +0 -1
- package/dist/load-agents-C3pJSVeT.js.map +0 -1
- package/dist/run-foreground-B8NoERhV.js +0 -2
|
@@ -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
|
|
10413
|
-
*
|
|
10414
|
-
*
|
|
10415
|
-
*
|
|
10416
|
-
*
|
|
10417
|
-
|
|
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
|
-
...
|
|
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
|
-
|
|
10595
|
-
|
|
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 {
|
|
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-
|
|
11509
|
+
//# sourceMappingURL=acp-host-iKdfasdN.js.map
|