@agentconnect.md/daemon 1.0.0-rc.220 → 1.0.0-rc.221
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-CeJJZbtg.js → acp-host-zyZ_j0yY.js} +129 -7
- package/dist/acp-host-zyZ_j0yY.js.map +1 -0
- package/dist/{agent-list-CoYaBcDx.js → agent-list-Vx-p1X3q.js} +2 -2
- package/dist/{agent-list-CoYaBcDx.js.map → agent-list-Vx-p1X3q.js.map} +1 -1
- package/dist/{chat-Bcpnzw3h.js → chat-CqfkPkqK.js} +4 -3
- package/dist/chat-CqfkPkqK.js.map +1 -0
- package/dist/index.js +4 -4
- package/dist/{load-agents-nWFFmN5-.js → load-agents-C1m5Y9n_.js} +2 -1
- package/dist/load-agents-C1m5Y9n_.js.map +1 -0
- package/dist/{login-B99oQNXR.js → login-DUhkVhAr.js} +4 -4
- package/dist/{login-B99oQNXR.js.map → login-DUhkVhAr.js.map} +1 -1
- package/dist/run-foreground-PSOzCSP8.js +2 -0
- package/dist/{run-foreground-DLxz75b4.js → run-foreground-mvTzCScX.js} +524 -51
- package/dist/run-foreground-mvTzCScX.js.map +1 -0
- package/package.json +1 -1
- package/dist/acp-host-CeJJZbtg.js.map +0 -1
- package/dist/chat-Bcpnzw3h.js.map +0 -1
- package/dist/load-agents-nWFFmN5-.js.map +0 -1
- package/dist/run-foreground-DLxz75b4.js.map +0 -1
- package/dist/run-foreground-PidBMaVD.js +0 -2
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { i as __require, o as __toESM, t as __commonJSMin } from "./rolldown-runtime-CE-6LUnI.js";
|
|
2
2
|
import { n as require_ms, t as require_src$2 } from "./src-DDtlVutj.js";
|
|
3
3
|
import { t as DAEMON_VERSION } from "./version-DGaRXjBK.js";
|
|
4
|
-
import { a as agentChildEnv, i as selectAgent, n as findAgentFiles, o as loadConfig, r as loadAgents, s as persistDaemonId } from "./load-agents-
|
|
4
|
+
import { a as agentChildEnv, i as selectAgent, n as findAgentFiles, o as loadConfig, r as loadAgents, s as persistDaemonId } from "./load-agents-C1m5Y9n_.js";
|
|
5
5
|
import { l as resolveRoot, o as mcpSocketPath, u as statePath } from "./paths-DEcUO8lC.js";
|
|
6
|
-
import { _ as
|
|
6
|
+
import { _ as cachedRuntimeVersions, a as initGitInjection, c as simpleGit, d as MAX_FRAME_BYTES, f as buildEnvelope, g as cachedRuntimeNames, h as MEMORY_INDEX, i as prepareWorkspace, m as encode, n as installedRuntimes, o as probeGitVersion, p as decodeEnvelope, r as prefetchWorkspace, s as sessionGitEnv, t as AcpHost, u as normalizeGitUrl, v as resolveRuntimes } from "./acp-host-zyZ_j0yY.js";
|
|
7
7
|
import { i as GitCredentialCache, n as gitcredSocketPath, r as writeGitcredShim, t as GitCredServer } from "./gitcred-server-Dcsq6QJe.js";
|
|
8
8
|
import { n as encodeFrame, t as decodeFrames } from "./ipc-2Xbxe7uu.js";
|
|
9
9
|
import { URL as URL$1 } from "url";
|
|
10
10
|
import * as tls from "tls";
|
|
11
11
|
import * as net$2 from "net";
|
|
12
12
|
import * as sp from "node:path";
|
|
13
|
-
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
13
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
14
14
|
import * as http$6 from "http";
|
|
15
15
|
import { Agent } from "https";
|
|
16
16
|
import { chmodSync, existsSync, mkdirSync, mkdtempSync, promises, readFileSync, rmSync, stat, unwatchFile, watch, watchFile, writeFileSync } from "node:fs";
|
|
@@ -5333,6 +5333,7 @@ function hostSpawnSig(a) {
|
|
|
5333
5333
|
reasoningEffort: a.reasoningEffort,
|
|
5334
5334
|
executionMode: a.executionMode,
|
|
5335
5335
|
fastMode: a.fastMode,
|
|
5336
|
+
permissionMode: a.permissionMode,
|
|
5336
5337
|
env: a.runtimeOverrides?.env
|
|
5337
5338
|
});
|
|
5338
5339
|
}
|
|
@@ -5395,7 +5396,8 @@ var LocalStore = class {
|
|
|
5395
5396
|
key TEXT PRIMARY KEY, agentId TEXT, platform TEXT, channel TEXT, thread TEXT,
|
|
5396
5397
|
acpSessionId TEXT, state TEXT, lastDeliveredTs TEXT, updatedAt INTEGER,
|
|
5397
5398
|
usage TEXT, muted INTEGER, triggeredBy TEXT, title TEXT, modelOverride TEXT,
|
|
5398
|
-
effortOverride TEXT, fastModeOverride INTEGER,
|
|
5399
|
+
effortOverride TEXT, permissionModeOverride TEXT, fastModeOverride INTEGER,
|
|
5400
|
+
outputModeOverride TEXT
|
|
5399
5401
|
);
|
|
5400
5402
|
-- Platform id → human display name (Slack channel/user names, daemon-resolved
|
|
5401
5403
|
-- and cached here so session read-back can label ids without a Slack call).
|
|
@@ -5442,10 +5444,11 @@ var LocalStore = class {
|
|
|
5442
5444
|
}
|
|
5443
5445
|
/** Add the `sessions.usage` (JSON token accounting), `sessions.muted` (`!stop`
|
|
5444
5446
|
* thread mute), `sessions.triggeredBy`, `sessions.title`, `sessions.modelOverride`,
|
|
5445
|
-
* `sessions.effortOverride
|
|
5446
|
-
* model / reasoning-effort /
|
|
5447
|
-
*
|
|
5448
|
-
*
|
|
5447
|
+
* `sessions.effortOverride`, `sessions.permissionModeOverride` and
|
|
5448
|
+
* `sessions.fastModeOverride` (the sticky per-session model / reasoning-effort /
|
|
5449
|
+
* permission-mode / fast-mode choices) columns to a pre-existing DB. CREATE TABLE IF
|
|
5450
|
+
* NOT EXISTS above adds them for fresh DBs but never alters an existing table, so
|
|
5451
|
+
* upgrade in place. No-op once the columns are present. */
|
|
5449
5452
|
migrateSessionUsage() {
|
|
5450
5453
|
const cols = this.db.prepare("PRAGMA table_info(sessions)").all();
|
|
5451
5454
|
if (!cols.some((c) => c.name === "usage")) this.db.exec("ALTER TABLE sessions ADD COLUMN usage TEXT");
|
|
@@ -5454,6 +5457,7 @@ var LocalStore = class {
|
|
|
5454
5457
|
if (!cols.some((c) => c.name === "title")) this.db.exec("ALTER TABLE sessions ADD COLUMN title TEXT");
|
|
5455
5458
|
if (!cols.some((c) => c.name === "modelOverride")) this.db.exec("ALTER TABLE sessions ADD COLUMN modelOverride TEXT");
|
|
5456
5459
|
if (!cols.some((c) => c.name === "effortOverride")) this.db.exec("ALTER TABLE sessions ADD COLUMN effortOverride TEXT");
|
|
5460
|
+
if (!cols.some((c) => c.name === "permissionModeOverride")) this.db.exec("ALTER TABLE sessions ADD COLUMN permissionModeOverride TEXT");
|
|
5457
5461
|
if (!cols.some((c) => c.name === "fastModeOverride")) this.db.exec("ALTER TABLE sessions ADD COLUMN fastModeOverride INTEGER");
|
|
5458
5462
|
if (!cols.some((c) => c.name === "outputModeOverride")) this.db.exec("ALTER TABLE sessions ADD COLUMN outputModeOverride TEXT");
|
|
5459
5463
|
}
|
|
@@ -5563,6 +5567,15 @@ var LocalStore = class {
|
|
|
5563
5567
|
setEffortOverride(key, effort) {
|
|
5564
5568
|
this.db.prepare("UPDATE sessions SET effortOverride = ? WHERE key = ?").run(effort, key);
|
|
5565
5569
|
}
|
|
5570
|
+
/** The session-scoped permission-mode override (set via status bars), or undefined if
|
|
5571
|
+
* the session runs on the agent's default. Sticky across turns and restarts. */
|
|
5572
|
+
getPermissionModeOverride(key) {
|
|
5573
|
+
return this.db.prepare("SELECT permissionModeOverride FROM sessions WHERE key = ?").get(key)?.permissionModeOverride ?? void 0;
|
|
5574
|
+
}
|
|
5575
|
+
/** Persist the session-scoped permission-mode override. No-op on an unknown key. */
|
|
5576
|
+
setPermissionModeOverride(key, mode) {
|
|
5577
|
+
this.db.prepare("UPDATE sessions SET permissionModeOverride = ? WHERE key = ?").run(mode, key);
|
|
5578
|
+
}
|
|
5566
5579
|
/** The session-scoped fast-mode override (set via the status-bar fast toggle), or
|
|
5567
5580
|
* undefined if the session runs on the agent's default. Stored as 0/1; sticky across
|
|
5568
5581
|
* turns and restarts, re-applied to the ACP session on each dispatch. */
|
|
@@ -5757,6 +5770,150 @@ function monotonicTs() {
|
|
|
5757
5770
|
return String(last);
|
|
5758
5771
|
}
|
|
5759
5772
|
//#endregion
|
|
5773
|
+
//#region src/agents/memory.ts
|
|
5774
|
+
/**
|
|
5775
|
+
* Agent memory — a DIRECTORY at the agent's ROOT (`<agent-root>/memory/`), OUTSIDE
|
|
5776
|
+
* the workspace so it survives a workspace reset / re-clone and is never committed
|
|
5777
|
+
* into a github-workspace repo.
|
|
5778
|
+
*
|
|
5779
|
+
* Index-plus-topics shape (the structure Claude Code's auto-memory uses): a lean
|
|
5780
|
+
* `MEMORY.md` index — injected into the prompt every session, so it must stay small
|
|
5781
|
+
* — plus any number of `<topic>.md` files the agent reads on demand. The agent
|
|
5782
|
+
* maintains it via the `readMemory` / `writeMemory` / `listMemory` MCP tools; the
|
|
5783
|
+
* daemon serves the same directory to the CP for the console.
|
|
5784
|
+
*
|
|
5785
|
+
* SECURITY: topic paths are agent/console-supplied, so every path is contained to
|
|
5786
|
+
* the memory dir — absolute paths and `..` escapes are rejected, and the resolved
|
|
5787
|
+
* path is re-checked so a symlink can't smuggle a target out. Flat by design (one
|
|
5788
|
+
* level): a topic is a file directly under `memory/`, no nested dirs.
|
|
5789
|
+
*/
|
|
5790
|
+
const MEMORY_DIRNAME = "memory";
|
|
5791
|
+
/** Max bytes of the index injected into the prompt each session — keeps a large
|
|
5792
|
+
* memory from swelling the context (mirrors Claude Code's 25 KB entrypoint cap). */
|
|
5793
|
+
const MAX_INDEX_INJECT_BYTES = 25e3;
|
|
5794
|
+
/** Hard cap on a single memory file. Injected/read every session, so an unbounded
|
|
5795
|
+
* file would swell the prompt and the wire; writes over this are rejected. */
|
|
5796
|
+
const MAX_MEMORY_FILE_BYTES = 256e3;
|
|
5797
|
+
/** Raised when a memory path escapes the memory dir. Surfaces as `BAD_PAYLOAD`. */
|
|
5798
|
+
var MemoryPathError = class extends Error {
|
|
5799
|
+
constructor(message) {
|
|
5800
|
+
super(message);
|
|
5801
|
+
this.name = "MemoryPathError";
|
|
5802
|
+
}
|
|
5803
|
+
};
|
|
5804
|
+
/** Raised when a write exceeds {@link MAX_MEMORY_FILE_BYTES}. `BAD_PAYLOAD`. */
|
|
5805
|
+
var MemoryTooLargeError = class extends Error {
|
|
5806
|
+
constructor(message) {
|
|
5807
|
+
super(message);
|
|
5808
|
+
this.name = "MemoryTooLargeError";
|
|
5809
|
+
}
|
|
5810
|
+
};
|
|
5811
|
+
/** Raised when an `ifMatchMtime` precondition fails (the file changed under the
|
|
5812
|
+
* writer — e.g. a console edit racing a newer agent write). Surfaces as CONFLICT. */
|
|
5813
|
+
var MemoryConflictError = class extends Error {
|
|
5814
|
+
constructor(message) {
|
|
5815
|
+
super(message);
|
|
5816
|
+
this.name = "MemoryConflictError";
|
|
5817
|
+
}
|
|
5818
|
+
};
|
|
5819
|
+
/** The memory directory for an agent, given its root dir (where agent.json lives). */
|
|
5820
|
+
function memoryDir(agentDir) {
|
|
5821
|
+
return join(agentDir, MEMORY_DIRNAME);
|
|
5822
|
+
}
|
|
5823
|
+
/** Resolve a memory-dir-relative path to an absolute one, contained to the dir.
|
|
5824
|
+
* Rejects absolute paths, `..` escapes, and any nested directory (flat only). */
|
|
5825
|
+
function resolveInMemoryDir(agentDir, relPath) {
|
|
5826
|
+
if (isAbsolute(relPath)) throw new MemoryPathError("absolute paths are not allowed");
|
|
5827
|
+
const dir = memoryDir(agentDir);
|
|
5828
|
+
const abs = resolve(dir, relPath);
|
|
5829
|
+
if (abs !== dir && !abs.startsWith(dir + sep)) throw new MemoryPathError("path escapes the memory dir");
|
|
5830
|
+
const rel = abs.slice(dir.length + 1);
|
|
5831
|
+
if (rel.includes(sep)) throw new MemoryPathError("memory is a flat directory (no subdirectories)");
|
|
5832
|
+
if (rel.length === 0) throw new MemoryPathError("a file name is required");
|
|
5833
|
+
return abs;
|
|
5834
|
+
}
|
|
5835
|
+
/** Seed `<agent-root>/memory/MEMORY.md` with a header if the dir/index is absent
|
|
5836
|
+
* (idempotent). Called at session start so a brand-new agent always has an index. */
|
|
5837
|
+
function ensureMemory(agentDir, agentName) {
|
|
5838
|
+
const dir = memoryDir(agentDir);
|
|
5839
|
+
mkdirSync(dir, { recursive: true });
|
|
5840
|
+
const index = join(dir, MEMORY_INDEX);
|
|
5841
|
+
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`);
|
|
5842
|
+
}
|
|
5843
|
+
/** Read the memory index (`MEMORY.md`) for prompt injection, trimmed to the inject
|
|
5844
|
+
* cap so a large index can't blow up the prompt; '' when absent. */
|
|
5845
|
+
async function readIndex(agentDir) {
|
|
5846
|
+
const text = await readMemoryFile(agentDir, MEMORY_INDEX);
|
|
5847
|
+
if (Buffer.byteLength(text) <= 25e3) return text;
|
|
5848
|
+
return Buffer.from(text, "utf8").subarray(0, MAX_INDEX_INJECT_BYTES).toString("utf8") + "\n\n[…memory index truncated — trim MEMORY.md]";
|
|
5849
|
+
}
|
|
5850
|
+
/** Read a memory file's text; '' when it does not exist (never throws on ENOENT). */
|
|
5851
|
+
async function readMemoryFile(agentDir, relPath) {
|
|
5852
|
+
const abs = resolveInMemoryDir(agentDir, relPath);
|
|
5853
|
+
try {
|
|
5854
|
+
return await promises.readFile(abs, "utf8");
|
|
5855
|
+
} catch (err) {
|
|
5856
|
+
if (err.code === "ENOENT") return "";
|
|
5857
|
+
throw err;
|
|
5858
|
+
}
|
|
5859
|
+
}
|
|
5860
|
+
/** Overwrite a memory file with `content` (creating the dir if needed). Atomic
|
|
5861
|
+
* (tmp + rename) so a concurrent read never sees a partial file.
|
|
5862
|
+
*
|
|
5863
|
+
* - Rejects content over {@link MAX_MEMORY_FILE_BYTES} ({@link MemoryTooLargeError}).
|
|
5864
|
+
* - `ifMatchMtime` (optimistic concurrency): when given, the current file's mtime
|
|
5865
|
+
* must equal it, else {@link MemoryConflictError} — so a console edit can't
|
|
5866
|
+
* clobber a newer write. A brand-new file (no mtime) matches `ifMatchMtime`
|
|
5867
|
+
* only when the caller passes none (or the empty string). */
|
|
5868
|
+
async function writeMemoryFile(agentDir, relPath, content, ifMatchMtime) {
|
|
5869
|
+
if (Buffer.byteLength(content) > 256e3) throw new MemoryTooLargeError(`memory file exceeds the ${MAX_MEMORY_FILE_BYTES}-byte limit`);
|
|
5870
|
+
const abs = resolveInMemoryDir(agentDir, relPath);
|
|
5871
|
+
if (ifMatchMtime) {
|
|
5872
|
+
let current = null;
|
|
5873
|
+
try {
|
|
5874
|
+
current = (await promises.stat(abs)).mtime.toISOString();
|
|
5875
|
+
} catch (err) {
|
|
5876
|
+
if (err.code !== "ENOENT") throw err;
|
|
5877
|
+
}
|
|
5878
|
+
if (current !== ifMatchMtime) throw new MemoryConflictError("the memory file changed since it was read; reload and retry");
|
|
5879
|
+
}
|
|
5880
|
+
await promises.mkdir(memoryDir(agentDir), { recursive: true });
|
|
5881
|
+
const tmp = `${abs}.tmp`;
|
|
5882
|
+
await promises.writeFile(tmp, content, "utf8");
|
|
5883
|
+
await promises.rename(tmp, abs);
|
|
5884
|
+
const st = await promises.stat(abs);
|
|
5885
|
+
return {
|
|
5886
|
+
size: st.size,
|
|
5887
|
+
mtime: st.mtime.toISOString()
|
|
5888
|
+
};
|
|
5889
|
+
}
|
|
5890
|
+
/** List the files in the memory dir (flat; index + topics), sorted with the index
|
|
5891
|
+
* first. Empty when the dir does not exist. `.tmp` write artifacts are skipped. */
|
|
5892
|
+
async function listMemory(agentDir) {
|
|
5893
|
+
const dir = memoryDir(agentDir);
|
|
5894
|
+
let dirents;
|
|
5895
|
+
try {
|
|
5896
|
+
dirents = await promises.readdir(dir, { withFileTypes: true });
|
|
5897
|
+
} catch (err) {
|
|
5898
|
+
if (err.code === "ENOENT") return [];
|
|
5899
|
+
throw err;
|
|
5900
|
+
}
|
|
5901
|
+
const files = [];
|
|
5902
|
+
for (const d of dirents) {
|
|
5903
|
+
if (!d.isFile() || d.name.endsWith(".tmp")) continue;
|
|
5904
|
+
try {
|
|
5905
|
+
const st = await promises.stat(join(dir, d.name));
|
|
5906
|
+
files.push({
|
|
5907
|
+
name: d.name,
|
|
5908
|
+
size: st.size,
|
|
5909
|
+
mtime: st.mtime.toISOString()
|
|
5910
|
+
});
|
|
5911
|
+
} catch {}
|
|
5912
|
+
}
|
|
5913
|
+
files.sort((a, b) => a.name === "MEMORY.md" ? -1 : b.name === "MEMORY.md" ? 1 : a.name.localeCompare(b.name));
|
|
5914
|
+
return files;
|
|
5915
|
+
}
|
|
5916
|
+
//#endregion
|
|
5760
5917
|
//#region src/session/attachment-block.ts
|
|
5761
5918
|
/**
|
|
5762
5919
|
* Turn one Slack attachment into the richest ACP content block the agent can
|
|
@@ -5833,6 +5990,7 @@ var SessionManager = class {
|
|
|
5833
5990
|
async handle(agentId, msg) {
|
|
5834
5991
|
const agent = this.deps.agentById(agentId);
|
|
5835
5992
|
if (!agent) throw new Error(`unknown agent ${agentId}`);
|
|
5993
|
+
ensureMemory(agent.dir, agent.name);
|
|
5836
5994
|
const { thread, ts: coordTs } = transcriptCoords(msg);
|
|
5837
5995
|
const ts = msg.platform === "webchat" ? monotonicTs() : coordTs;
|
|
5838
5996
|
const key = sessionKey(msg.platform, msg.channel, thread, agentId);
|
|
@@ -5925,6 +6083,13 @@ var SessionManager = class {
|
|
|
5925
6083
|
text: `${head}\n${ctxText}`
|
|
5926
6084
|
});
|
|
5927
6085
|
}
|
|
6086
|
+
if (created) {
|
|
6087
|
+
const index = (await readIndex(agent.dir)).trim();
|
|
6088
|
+
if (index) blocks.push({
|
|
6089
|
+
type: "text",
|
|
6090
|
+
text: `# Your memory\nThis is your memory INDEX from earlier sessions. Read a linked topic file with the \`readMemory\` tool when relevant; revise memory with \`writeMemory\`.\n\n${index}`
|
|
6091
|
+
});
|
|
6092
|
+
}
|
|
5928
6093
|
blocks.push({
|
|
5929
6094
|
type: "text",
|
|
5930
6095
|
text: msg.text
|
|
@@ -6093,8 +6258,34 @@ function guessMimeFromUrl(url) {
|
|
|
6093
6258
|
* missing connection — the caller turns that into an MCP `isError` result.
|
|
6094
6259
|
*/
|
|
6095
6260
|
async function executeTool(ctx, name, args, deps) {
|
|
6096
|
-
|
|
6097
|
-
|
|
6261
|
+
if (name === "listMemory" || name === "readMemory" || name === "writeMemory") {
|
|
6262
|
+
const dir = deps.memoryDirFor(ctx.agentId);
|
|
6263
|
+
if (!dir) throw new Error(`unknown agent ${ctx.agentId}`);
|
|
6264
|
+
try {
|
|
6265
|
+
if (name === "listMemory") return { files: await listMemory(dir) };
|
|
6266
|
+
if (name === "readMemory") {
|
|
6267
|
+
const path = optionalString(args, "path") ?? "MEMORY.md";
|
|
6268
|
+
return {
|
|
6269
|
+
path,
|
|
6270
|
+
content: await readMemoryFile(dir, path)
|
|
6271
|
+
};
|
|
6272
|
+
}
|
|
6273
|
+
const path = optionalString(args, "path") ?? "MEMORY.md";
|
|
6274
|
+
const { size, mtime } = await writeMemoryFile(dir, path, requireStringAllowEmpty(args, "content"));
|
|
6275
|
+
return {
|
|
6276
|
+
ok: true,
|
|
6277
|
+
path,
|
|
6278
|
+
size,
|
|
6279
|
+
mtime
|
|
6280
|
+
};
|
|
6281
|
+
} catch (err) {
|
|
6282
|
+
if (err instanceof MemoryPathError) throw new Error(`invalid memory path: ${err.message}`);
|
|
6283
|
+
if (err instanceof MemoryTooLargeError) throw new Error(err.message);
|
|
6284
|
+
throw err;
|
|
6285
|
+
}
|
|
6286
|
+
}
|
|
6287
|
+
const gw = ctx.integrationId ? deps.gatewayFor(ctx.integrationId) : void 0;
|
|
6288
|
+
if (!gw) throw new Error(`no live Slack connection for integration ${ctx.integrationId ?? "(none)"}`);
|
|
6098
6289
|
switch (name) {
|
|
6099
6290
|
case "sendSlackMessage": {
|
|
6100
6291
|
const text = requireString(args, "text");
|
|
@@ -6158,6 +6349,13 @@ function requireString(args, key) {
|
|
|
6158
6349
|
if (typeof v !== "string" || v.length === 0) throw new Error(`missing required string argument: ${key}`);
|
|
6159
6350
|
return v;
|
|
6160
6351
|
}
|
|
6352
|
+
/** Like `requireString` but accepts '' — for `updateMemory`, where an empty string
|
|
6353
|
+
* is a valid value (clear the memory). */
|
|
6354
|
+
function requireStringAllowEmpty(args, key) {
|
|
6355
|
+
const v = args[key];
|
|
6356
|
+
if (typeof v !== "string") throw new Error(`missing required string argument: ${key}`);
|
|
6357
|
+
return v;
|
|
6358
|
+
}
|
|
6161
6359
|
function optionalString(args, key) {
|
|
6162
6360
|
const v = args[key];
|
|
6163
6361
|
if (v === void 0 || v === null) return void 0;
|
|
@@ -6377,13 +6575,48 @@ const SLACK_TOOLS = [
|
|
|
6377
6575
|
}, ["url"])
|
|
6378
6576
|
}
|
|
6379
6577
|
];
|
|
6380
|
-
SLACK_TOOLS.map((t) => t.name);
|
|
6381
6578
|
/**
|
|
6382
|
-
* The
|
|
6383
|
-
*
|
|
6579
|
+
* The agent's long-term memory tools — available to EVERY agent regardless of
|
|
6580
|
+
* integrations. Memory is a directory (`<agent-root>/memory/`) with a `MEMORY.md`
|
|
6581
|
+
* index (injected into the prompt each session) plus `<topic>.md` files the agent
|
|
6582
|
+
* reads on demand. Keep the index short; move detail into topic files.
|
|
6583
|
+
*/
|
|
6584
|
+
const MEMORY_TOOLS = [
|
|
6585
|
+
{
|
|
6586
|
+
name: "listMemory",
|
|
6587
|
+
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.",
|
|
6588
|
+
inputSchema: obj({})
|
|
6589
|
+
},
|
|
6590
|
+
{
|
|
6591
|
+
name: "readMemory",
|
|
6592
|
+
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.",
|
|
6593
|
+
inputSchema: obj({ path: {
|
|
6594
|
+
type: "string",
|
|
6595
|
+
description: "Memory file name (e.g. \"deploys.md\"). Defaults to the MEMORY.md index."
|
|
6596
|
+
} })
|
|
6597
|
+
},
|
|
6598
|
+
{
|
|
6599
|
+
name: "writeMemory",
|
|
6600
|
+
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.",
|
|
6601
|
+
inputSchema: obj({
|
|
6602
|
+
path: {
|
|
6603
|
+
type: "string",
|
|
6604
|
+
description: "Memory file name (e.g. \"deploys.md\"). Defaults to the MEMORY.md index. No subdirectories."
|
|
6605
|
+
},
|
|
6606
|
+
content: {
|
|
6607
|
+
type: "string",
|
|
6608
|
+
description: "The full new file contents, in Markdown. Replaces the file."
|
|
6609
|
+
}
|
|
6610
|
+
}, ["content"])
|
|
6611
|
+
}
|
|
6612
|
+
];
|
|
6613
|
+
[...SLACK_TOOLS, ...MEMORY_TOOLS].map((t) => t.name);
|
|
6614
|
+
/**
|
|
6615
|
+
* The default MCP tool set for an agent. The memory tools are always present
|
|
6616
|
+
* (every agent has memory); Slack tools are gated by a Slack integration.
|
|
6384
6617
|
*/
|
|
6385
6618
|
function toolsForIntegrations(integrations) {
|
|
6386
|
-
const tools = [];
|
|
6619
|
+
const tools = [...MEMORY_TOOLS];
|
|
6387
6620
|
if (integrations.some((i) => i.platform === "slack")) tools.push(...SLACK_TOOLS);
|
|
6388
6621
|
return tools;
|
|
6389
6622
|
}
|
|
@@ -63012,6 +63245,7 @@ const STATUS_ACTION = {
|
|
|
63012
63245
|
manage: "ac_manage",
|
|
63013
63246
|
setModel: "ac_set_model",
|
|
63014
63247
|
setEffort: "ac_set_effort",
|
|
63248
|
+
setPermissionMode: "ac_set_permission_mode",
|
|
63015
63249
|
setFast: "ac_set_fast",
|
|
63016
63250
|
setOutput: "ac_set_output",
|
|
63017
63251
|
cancel: "ac_cancel",
|
|
@@ -63095,6 +63329,34 @@ function buildStatusModal(info, sessionKey, link) {
|
|
|
63095
63329
|
text: `*Model:* ${info.model}`
|
|
63096
63330
|
}
|
|
63097
63331
|
});
|
|
63332
|
+
const permissionModes = info.permissionModes ?? [];
|
|
63333
|
+
if (permissionModes.length > 0) {
|
|
63334
|
+
const opts = info.permissionMode && !permissionModes.includes(info.permissionMode) ? [info.permissionMode, ...permissionModes] : permissionModes;
|
|
63335
|
+
const option = (m) => ({
|
|
63336
|
+
text: {
|
|
63337
|
+
type: "plain_text",
|
|
63338
|
+
text: clampTo(m, 75)
|
|
63339
|
+
},
|
|
63340
|
+
value: m
|
|
63341
|
+
});
|
|
63342
|
+
blocks.push({
|
|
63343
|
+
type: "section",
|
|
63344
|
+
text: {
|
|
63345
|
+
type: "mrkdwn",
|
|
63346
|
+
text: "*Permission mode*"
|
|
63347
|
+
},
|
|
63348
|
+
accessory: {
|
|
63349
|
+
type: "static_select",
|
|
63350
|
+
action_id: STATUS_ACTION.setPermissionMode,
|
|
63351
|
+
placeholder: {
|
|
63352
|
+
type: "plain_text",
|
|
63353
|
+
text: "Permission mode"
|
|
63354
|
+
},
|
|
63355
|
+
...info.permissionMode ? { initial_option: option(info.permissionMode) } : {},
|
|
63356
|
+
options: opts.map(option)
|
|
63357
|
+
}
|
|
63358
|
+
});
|
|
63359
|
+
}
|
|
63098
63360
|
const efforts = info.efforts ?? [];
|
|
63099
63361
|
if (efforts.length > 0) {
|
|
63100
63362
|
const opts = info.effort && !efforts.includes(info.effort) ? [info.effort, ...efforts] : efforts;
|
|
@@ -63459,6 +63721,7 @@ var SlackConnection = class {
|
|
|
63459
63721
|
deps;
|
|
63460
63722
|
app;
|
|
63461
63723
|
queue;
|
|
63724
|
+
assistantDmThreads = /* @__PURE__ */ new Map();
|
|
63462
63725
|
botUserId = "";
|
|
63463
63726
|
/** The appToken this socket is keyed by (one socket per unique appToken). */
|
|
63464
63727
|
appToken;
|
|
@@ -63499,8 +63762,8 @@ var SlackConnection = class {
|
|
|
63499
63762
|
this.workspaceUrl = auth.url ?? "";
|
|
63500
63763
|
log?.debug(`slack: auth.test ok → bot user ${this.botUserId} (bot_id ${this.botId || "n/a"})`);
|
|
63501
63764
|
const deliver = (ev, kind) => {
|
|
63502
|
-
const msg = normalizeSlackEvent(ev, { traceId: this.deps.newTraceId() });
|
|
63503
|
-
log?.debug(`slack: inbound ${kind} ch=${msg.channel} user=${msg.sender.id} isBot=${msg.sender.isBot} isDm=${msg.isDm} mentions=[${msg.mentionedBots.join(",")}] text=${JSON.stringify(msg.text.slice(0, 80))}`);
|
|
63765
|
+
const msg = normalizeSlackEvent(this.withAssistantThread(ev), { traceId: this.deps.newTraceId() });
|
|
63766
|
+
log?.debug(`slack: inbound ${kind} ch=${msg.channel} thread=${msg.thread ?? "none"} user=${msg.sender.id} isBot=${msg.sender.isBot} isDm=${msg.isDm} mentions=[${msg.mentionedBots.join(",")}] text=${JSON.stringify(msg.text.slice(0, 80))}`);
|
|
63504
63767
|
this.deps.onMessage(msg);
|
|
63505
63768
|
};
|
|
63506
63769
|
this.app.message(async ({ message }) => {
|
|
@@ -63516,6 +63779,10 @@ var SlackConnection = class {
|
|
|
63516
63779
|
if (!ev.channel) return;
|
|
63517
63780
|
deliver(ev, "app_mention");
|
|
63518
63781
|
});
|
|
63782
|
+
this.app.event("assistant_thread_started", async ({ event }) => {
|
|
63783
|
+
const thread = this.rememberAssistantThread(event);
|
|
63784
|
+
if (thread) log?.debug(`slack: assistant thread started ch=${thread.channel} thread=${thread.threadTs}`);
|
|
63785
|
+
});
|
|
63519
63786
|
this.app.event("member_joined_channel", async ({ event }) => {
|
|
63520
63787
|
const ev = event;
|
|
63521
63788
|
if (ev.user !== this.botUserId) return;
|
|
@@ -63562,6 +63829,16 @@ var SlackConnection = class {
|
|
|
63562
63829
|
effort
|
|
63563
63830
|
});
|
|
63564
63831
|
});
|
|
63832
|
+
this.app.action(STATUS_ACTION.setPermissionMode, async ({ ack, action, body }) => {
|
|
63833
|
+
await ack();
|
|
63834
|
+
const sessionKey = body?.view?.private_metadata ?? action.block_id;
|
|
63835
|
+
const permissionMode = action.selected_option?.value;
|
|
63836
|
+
if (sessionKey && permissionMode) this.deps.onStatusAction?.({
|
|
63837
|
+
kind: "set-permission-mode",
|
|
63838
|
+
sessionKey,
|
|
63839
|
+
permissionMode
|
|
63840
|
+
});
|
|
63841
|
+
});
|
|
63565
63842
|
this.app.action(STATUS_ACTION.setFast, async ({ ack, action, body }) => {
|
|
63566
63843
|
await ack();
|
|
63567
63844
|
const sessionKey = body?.view?.private_metadata ?? action.block_id;
|
|
@@ -63597,6 +63874,25 @@ var SlackConnection = class {
|
|
|
63597
63874
|
await this.app.start();
|
|
63598
63875
|
log?.debug("slack: app.start resolved → socket established");
|
|
63599
63876
|
}
|
|
63877
|
+
rememberAssistantThread(ev) {
|
|
63878
|
+
const channel = ev.assistant_thread?.channel_id;
|
|
63879
|
+
const threadTs = ev.assistant_thread?.thread_ts;
|
|
63880
|
+
if (!channel || !threadTs) return null;
|
|
63881
|
+
this.assistantDmThreads.set(channel, threadTs);
|
|
63882
|
+
return {
|
|
63883
|
+
channel,
|
|
63884
|
+
threadTs
|
|
63885
|
+
};
|
|
63886
|
+
}
|
|
63887
|
+
withAssistantThread(ev) {
|
|
63888
|
+
if (ev.thread_ts || !ev.channel) return ev;
|
|
63889
|
+
if (ev.channel_type !== "im" && !ev.channel.startsWith("D")) return ev;
|
|
63890
|
+
const threadTs = this.assistantDmThreads.get(ev.channel);
|
|
63891
|
+
return threadTs ? {
|
|
63892
|
+
...ev,
|
|
63893
|
+
thread_ts: threadTs
|
|
63894
|
+
} : ev;
|
|
63895
|
+
}
|
|
63600
63896
|
async postMessage(channel, text, threadTs) {
|
|
63601
63897
|
return this.queue.enqueue(async () => {
|
|
63602
63898
|
return (await this.app.client.chat.postMessage({
|
|
@@ -64852,6 +65148,50 @@ function makeLogger(level = "info") {
|
|
|
64852
65148
|
};
|
|
64853
65149
|
}
|
|
64854
65150
|
//#endregion
|
|
65151
|
+
//#region src/cp/wire-slice.ts
|
|
65152
|
+
/**
|
|
65153
|
+
* Pure helpers for slicing file bytes into a single WS frame — shared by the
|
|
65154
|
+
* workspace and memory readers so both honour the 256 KiB wire cap and never
|
|
65155
|
+
* split a UTF-8 character across slices.
|
|
65156
|
+
*/
|
|
65157
|
+
/** Encoded-payload ceiling, leaving headroom under MAX_FRAME_BYTES for the
|
|
65158
|
+
* envelope (id/ts/type/corr + fencing ext, well under 4 KiB). */
|
|
65159
|
+
const REPLY_BUDGET$1 = MAX_FRAME_BYTES - 4096;
|
|
65160
|
+
/** The encoded size of the payload the wire will carry (JSON.stringify matches
|
|
65161
|
+
* the codec's `encode`), measured in bytes. */
|
|
65162
|
+
function encodedBytes$1(payload) {
|
|
65163
|
+
return Buffer.byteLength(JSON.stringify(payload));
|
|
65164
|
+
}
|
|
65165
|
+
/** Largest index ≤ `len` that lands on a UTF-8 character boundary — i.e. never
|
|
65166
|
+
* cuts a multi-byte sequence. If the slice's trailing char is incomplete, the
|
|
65167
|
+
* boundary is placed before that char (its bytes belong to the next slice). */
|
|
65168
|
+
function utf8Boundary$1(buf, len) {
|
|
65169
|
+
if (len >= buf.length) len = buf.length;
|
|
65170
|
+
if (len <= 0) return 0;
|
|
65171
|
+
let start = len - 1;
|
|
65172
|
+
while (start > 0 && ((buf[start] ?? 0) & 192) === 128) start--;
|
|
65173
|
+
const lead = buf[start] ?? 0;
|
|
65174
|
+
const seqLen = lead < 128 ? 1 : (lead & 224) === 192 ? 2 : (lead & 240) === 224 ? 3 : (lead & 248) === 240 ? 4 : 1;
|
|
65175
|
+
return start + seqLen <= len ? len : start;
|
|
65176
|
+
}
|
|
65177
|
+
/** Shrink `content` (a utf8 view of `slice[0..end]`) until its JSON-escaped size
|
|
65178
|
+
* fits `REPLY_BUDGET`, keeping the cut on a UTF-8 boundary. Returns the final
|
|
65179
|
+
* `{ end, content }`. Control-byte-heavy text escapes ~6×, so the raw byte limit
|
|
65180
|
+
* is not enough on its own. */
|
|
65181
|
+
function fitToBudget(slice, end) {
|
|
65182
|
+
let content = slice.toString("utf8", 0, end);
|
|
65183
|
+
while (end > 0 && encodedBytes$1(content) > REPLY_BUDGET$1) {
|
|
65184
|
+
const factor = REPLY_BUDGET$1 / encodedBytes$1(content);
|
|
65185
|
+
const shrunk = Math.max(1, Math.floor(end * factor * .9));
|
|
65186
|
+
end = utf8Boundary$1(slice, Math.min(shrunk, end - 1));
|
|
65187
|
+
content = slice.toString("utf8", 0, end);
|
|
65188
|
+
}
|
|
65189
|
+
return {
|
|
65190
|
+
end,
|
|
65191
|
+
content
|
|
65192
|
+
};
|
|
65193
|
+
}
|
|
65194
|
+
//#endregion
|
|
64855
65195
|
//#region src/cp/workspace-reader.ts
|
|
64856
65196
|
/**
|
|
64857
65197
|
* `WorkspaceReader` — the read-only seam answering the CP's `workspace/list` and
|
|
@@ -64876,9 +65216,6 @@ function makeLogger(level = "info") {
|
|
|
64876
65216
|
*/
|
|
64877
65217
|
/** Bytes sniffed from the head of a file for binary (NUL byte) detection. */
|
|
64878
65218
|
const SNIFF_BYTES = 8192;
|
|
64879
|
-
/** Encoded-payload ceiling, leaving headroom under MAX_FRAME_BYTES for the
|
|
64880
|
-
* envelope (id/ts/type/corr + fencing ext, well under 4 KiB). */
|
|
64881
|
-
const REPLY_BUDGET$1 = MAX_FRAME_BYTES - 4096;
|
|
64882
65219
|
/** Path-containment / bad-request violation → `BAD_PAYLOAD` on the wire. */
|
|
64883
65220
|
var WorkspaceViolationError = class extends Error {
|
|
64884
65221
|
constructor(message) {
|
|
@@ -64886,29 +65223,12 @@ var WorkspaceViolationError = class extends Error {
|
|
|
64886
65223
|
this.name = "WorkspaceViolationError";
|
|
64887
65224
|
}
|
|
64888
65225
|
};
|
|
64889
|
-
function isErrno(err, code) {
|
|
65226
|
+
function isErrno$1(err, code) {
|
|
64890
65227
|
return err?.code === code;
|
|
64891
65228
|
}
|
|
64892
65229
|
function under(root, p) {
|
|
64893
65230
|
return p === root || p.startsWith(root + sp.sep);
|
|
64894
65231
|
}
|
|
64895
|
-
/** Largest index ≤ `len` that lands on a UTF-8 character boundary — i.e. never
|
|
64896
|
-
* cuts a multi-byte sequence. If the slice's trailing char is incomplete, the
|
|
64897
|
-
* boundary is placed before that char (its bytes belong to the next slice). */
|
|
64898
|
-
function utf8Boundary$1(buf, len) {
|
|
64899
|
-
if (len >= buf.length) len = buf.length;
|
|
64900
|
-
if (len <= 0) return 0;
|
|
64901
|
-
let start = len - 1;
|
|
64902
|
-
while (start > 0 && ((buf[start] ?? 0) & 192) === 128) start--;
|
|
64903
|
-
const lead = buf[start] ?? 0;
|
|
64904
|
-
const seqLen = lead < 128 ? 1 : (lead & 224) === 192 ? 2 : (lead & 240) === 224 ? 3 : (lead & 248) === 240 ? 4 : 1;
|
|
64905
|
-
return start + seqLen <= len ? len : start;
|
|
64906
|
-
}
|
|
64907
|
-
/** The encoded size of the payload the wire will carry (JSON.stringify matches
|
|
64908
|
-
* the codec's `encode`), measured in bytes. */
|
|
64909
|
-
function encodedBytes$1(payload) {
|
|
64910
|
-
return Buffer.byteLength(JSON.stringify(payload));
|
|
64911
|
-
}
|
|
64912
65232
|
function createWorkspaceReader(workspaceRootByAgent) {
|
|
64913
65233
|
function rootFor(agentId) {
|
|
64914
65234
|
const root = workspaceRootByAgent(agentId);
|
|
@@ -64930,7 +65250,7 @@ function createWorkspaceReader(workspaceRootByAgent) {
|
|
|
64930
65250
|
realRoot: await promises.realpath(root)
|
|
64931
65251
|
};
|
|
64932
65252
|
} catch (err) {
|
|
64933
|
-
if (isErrno(err, "ENOENT")) return {
|
|
65253
|
+
if (isErrno$1(err, "ENOENT")) return {
|
|
64934
65254
|
resolved,
|
|
64935
65255
|
realRoot: null
|
|
64936
65256
|
};
|
|
@@ -64959,14 +65279,14 @@ function createWorkspaceReader(workspaceRootByAgent) {
|
|
|
64959
65279
|
try {
|
|
64960
65280
|
target = await canonicalUnder(realRoot, resolved);
|
|
64961
65281
|
} catch (err) {
|
|
64962
|
-
if (isErrno(err, "ENOENT")) return notFound;
|
|
65282
|
+
if (isErrno$1(err, "ENOENT")) return notFound;
|
|
64963
65283
|
throw err;
|
|
64964
65284
|
}
|
|
64965
65285
|
let dirents;
|
|
64966
65286
|
try {
|
|
64967
65287
|
dirents = await promises.readdir(target, { withFileTypes: true });
|
|
64968
65288
|
} catch (err) {
|
|
64969
|
-
if (isErrno(err, "ENOENT") || isErrno(err, "ENOTDIR")) return notFound;
|
|
65289
|
+
if (isErrno$1(err, "ENOENT") || isErrno$1(err, "ENOTDIR")) return notFound;
|
|
64970
65290
|
throw err;
|
|
64971
65291
|
}
|
|
64972
65292
|
const entries = [];
|
|
@@ -65016,7 +65336,7 @@ function createWorkspaceReader(workspaceRootByAgent) {
|
|
|
65016
65336
|
try {
|
|
65017
65337
|
st = await promises.lstat(resolved);
|
|
65018
65338
|
} catch (err) {
|
|
65019
|
-
if (isErrno(err, "ENOENT")) return notFound;
|
|
65339
|
+
if (isErrno$1(err, "ENOENT")) return notFound;
|
|
65020
65340
|
throw err;
|
|
65021
65341
|
}
|
|
65022
65342
|
if (!st.isFile()) throw new WorkspaceViolationError("not a regular file");
|
|
@@ -65046,13 +65366,9 @@ function createWorkspaceReader(workspaceRootByAgent) {
|
|
|
65046
65366
|
slice = buf.subarray(0, bytesRead);
|
|
65047
65367
|
}
|
|
65048
65368
|
let end = utf8Boundary$1(slice, slice.length);
|
|
65049
|
-
|
|
65050
|
-
|
|
65051
|
-
|
|
65052
|
-
const shrunk = Math.max(1, Math.floor(end * factor * .9));
|
|
65053
|
-
end = utf8Boundary$1(slice, Math.min(shrunk, end - 1));
|
|
65054
|
-
content = slice.toString("utf8", 0, end);
|
|
65055
|
-
}
|
|
65369
|
+
const fitted = fitToBudget(slice, end);
|
|
65370
|
+
end = fitted.end;
|
|
65371
|
+
const content = fitted.content;
|
|
65056
65372
|
const nextOffset = req.offset + end;
|
|
65057
65373
|
return {
|
|
65058
65374
|
agentId: req.agentId,
|
|
@@ -65083,6 +65399,103 @@ function compareEntries(a, b) {
|
|
|
65083
65399
|
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
|
|
65084
65400
|
}
|
|
65085
65401
|
//#endregion
|
|
65402
|
+
//#region src/cp/memory-reader.ts
|
|
65403
|
+
/**
|
|
65404
|
+
* `MemoryReader` — the seam answering the CP's `memory/list`, `memory/read` and
|
|
65405
|
+
* `memory/write` REQs for an agent's memory directory (`<agent-root>/memory/`).
|
|
65406
|
+
* The files live only on the daemon (§1/§12); reads stream single byte slices,
|
|
65407
|
+
* writes replace one whole file.
|
|
65408
|
+
*
|
|
65409
|
+
* Path containment lives in `agents/memory.ts` (`resolveInMemoryDir`): a topic
|
|
65410
|
+
* path that escapes the memory dir throws `MemoryPathError` → `BAD_PAYLOAD`. An
|
|
65411
|
+
* unknown agent is also `BAD_PAYLOAD`; a not-yet-created file/dir is data
|
|
65412
|
+
* (`exists:false`), not an error. Frame-size safety mirrors the workspace reader:
|
|
65413
|
+
* the read slice is bounded by the *encoded* reply size and ends on a UTF-8
|
|
65414
|
+
* boundary. Never log memory contents.
|
|
65415
|
+
*/
|
|
65416
|
+
/** Unknown-agent violation → `BAD_PAYLOAD` on the wire (path escapes surface as
|
|
65417
|
+
* MemoryPathError from agents/memory.ts, mapped the same way by the dispatcher). */
|
|
65418
|
+
var MemoryViolationError = class extends Error {
|
|
65419
|
+
constructor(message) {
|
|
65420
|
+
super(message);
|
|
65421
|
+
this.name = "MemoryViolationError";
|
|
65422
|
+
}
|
|
65423
|
+
};
|
|
65424
|
+
function isErrno(err, code) {
|
|
65425
|
+
return err?.code === code;
|
|
65426
|
+
}
|
|
65427
|
+
/** `agentDirByAgent` resolves an agent id → its root dir (which holds `memory/`),
|
|
65428
|
+
* or undefined for an unknown agent. */
|
|
65429
|
+
function createMemoryReader(agentDirByAgent) {
|
|
65430
|
+
function dirFor(agentId) {
|
|
65431
|
+
const dir = agentDirByAgent(agentId);
|
|
65432
|
+
if (!dir) throw new MemoryViolationError(`unknown agent "${agentId}"`);
|
|
65433
|
+
return dir;
|
|
65434
|
+
}
|
|
65435
|
+
return {
|
|
65436
|
+
async list(req) {
|
|
65437
|
+
const files = await listMemory(dirFor(req.agentId));
|
|
65438
|
+
return {
|
|
65439
|
+
agentId: req.agentId,
|
|
65440
|
+
exists: files.length > 0,
|
|
65441
|
+
entries: files
|
|
65442
|
+
};
|
|
65443
|
+
},
|
|
65444
|
+
async read(req) {
|
|
65445
|
+
const file = resolveInMemoryDir(dirFor(req.agentId), req.path);
|
|
65446
|
+
const notFound = {
|
|
65447
|
+
agentId: req.agentId,
|
|
65448
|
+
path: req.path,
|
|
65449
|
+
exists: false
|
|
65450
|
+
};
|
|
65451
|
+
let st;
|
|
65452
|
+
try {
|
|
65453
|
+
st = await promises.stat(file);
|
|
65454
|
+
} catch (err) {
|
|
65455
|
+
if (isErrno(err, "ENOENT")) return notFound;
|
|
65456
|
+
throw err;
|
|
65457
|
+
}
|
|
65458
|
+
if (!st.isFile()) return notFound;
|
|
65459
|
+
const size = st.size;
|
|
65460
|
+
const mtime = st.mtime.toISOString();
|
|
65461
|
+
const fh = await promises.open(file, "r");
|
|
65462
|
+
try {
|
|
65463
|
+
const want = Math.min(req.limit, Math.max(0, size - req.offset));
|
|
65464
|
+
let slice = Buffer.alloc(0);
|
|
65465
|
+
if (want > 0) {
|
|
65466
|
+
const buf = Buffer.alloc(want);
|
|
65467
|
+
const { bytesRead } = await fh.read(buf, 0, want, req.offset);
|
|
65468
|
+
slice = buf.subarray(0, bytesRead);
|
|
65469
|
+
}
|
|
65470
|
+
const { end, content } = fitToBudget(slice, utf8Boundary$1(slice, slice.length));
|
|
65471
|
+
const nextOffset = req.offset + end;
|
|
65472
|
+
return {
|
|
65473
|
+
agentId: req.agentId,
|
|
65474
|
+
path: req.path,
|
|
65475
|
+
exists: true,
|
|
65476
|
+
size,
|
|
65477
|
+
mtime,
|
|
65478
|
+
content,
|
|
65479
|
+
offset: req.offset,
|
|
65480
|
+
nextOffset,
|
|
65481
|
+
truncated: nextOffset < size
|
|
65482
|
+
};
|
|
65483
|
+
} finally {
|
|
65484
|
+
await fh.close();
|
|
65485
|
+
}
|
|
65486
|
+
},
|
|
65487
|
+
async write(req) {
|
|
65488
|
+
const { size, mtime } = await writeMemoryFile(dirFor(req.agentId), req.path, req.content, req.ifMatchMtime);
|
|
65489
|
+
return {
|
|
65490
|
+
agentId: req.agentId,
|
|
65491
|
+
path: req.path,
|
|
65492
|
+
size,
|
|
65493
|
+
mtime
|
|
65494
|
+
};
|
|
65495
|
+
}
|
|
65496
|
+
};
|
|
65497
|
+
}
|
|
65498
|
+
//#endregion
|
|
65086
65499
|
//#region src/cp/correlator.ts
|
|
65087
65500
|
/** Transport-free representation of a typed wire `error` REP. */
|
|
65088
65501
|
var WireError = class extends Error {
|
|
@@ -65547,6 +65960,15 @@ var CpClient = class {
|
|
|
65547
65960
|
case "workspace/gitpull":
|
|
65548
65961
|
this.deps.workspaceGit.pull(frame.payload.agentId).then((result) => this.reply(frame, "workspace/gitpull/result", result)).catch((err) => this.workspaceError(frame.id, "workspace/gitpull", err));
|
|
65549
65962
|
return;
|
|
65963
|
+
case "memory/list":
|
|
65964
|
+
this.deps.memoryReader.list(frame.payload).then((page) => this.reply(frame, "memory/list/page", page)).catch((err) => this.memoryError(frame.id, "memory/list", err));
|
|
65965
|
+
return;
|
|
65966
|
+
case "memory/read":
|
|
65967
|
+
this.deps.memoryReader.read(frame.payload).then((content) => this.reply(frame, "memory/read/content", content)).catch((err) => this.memoryError(frame.id, "memory/read", err));
|
|
65968
|
+
return;
|
|
65969
|
+
case "memory/write":
|
|
65970
|
+
this.deps.memoryReader.write(frame.payload).then((ok) => this.reply(frame, "memory/write/ok", ok)).catch((err) => this.memoryError(frame.id, "memory/write", err));
|
|
65971
|
+
return;
|
|
65550
65972
|
case "webchat/message":
|
|
65551
65973
|
if (!this.deps.onWebchatMessage) {
|
|
65552
65974
|
this.sendError(frame.id, "INTERNAL", "webchat not supported", false);
|
|
@@ -65567,6 +65989,9 @@ var CpClient = class {
|
|
|
65567
65989
|
case "webchat/set-effort":
|
|
65568
65990
|
this.deps.onWebchatSetEffort?.(frame.payload);
|
|
65569
65991
|
return;
|
|
65992
|
+
case "webchat/set-permission-mode":
|
|
65993
|
+
this.deps.onWebchatSetPermissionMode?.(frame.payload);
|
|
65994
|
+
return;
|
|
65570
65995
|
case "webchat/set-fast":
|
|
65571
65996
|
this.deps.onWebchatSetFast?.(frame.payload);
|
|
65572
65997
|
return;
|
|
@@ -65590,6 +66015,18 @@ var CpClient = class {
|
|
|
65590
66015
|
this.deps.log.warn(`cp: ${op} failed: ${err?.message}`);
|
|
65591
66016
|
this.sendError(corr, "INTERNAL", `${op} failed`, false);
|
|
65592
66017
|
}
|
|
66018
|
+
memoryError(corr, op, err) {
|
|
66019
|
+
if (err instanceof MemoryConflictError) {
|
|
66020
|
+
this.sendError(corr, "CONFLICT", `${op} failed: ${err.message}`, false);
|
|
66021
|
+
return;
|
|
66022
|
+
}
|
|
66023
|
+
if (err instanceof MemoryViolationError || err instanceof MemoryPathError || err instanceof MemoryTooLargeError) {
|
|
66024
|
+
this.sendError(corr, "BAD_PAYLOAD", `${op} failed: ${err.message}`, false);
|
|
66025
|
+
return;
|
|
66026
|
+
}
|
|
66027
|
+
this.deps.log.warn(`cp: ${op} failed: ${err?.message}`);
|
|
66028
|
+
this.sendError(corr, "INTERNAL", `${op} failed`, false);
|
|
66029
|
+
}
|
|
65593
66030
|
reply(req, type, payload) {
|
|
65594
66031
|
this.transport?.send(encode(buildEnvelope(type, payload, { corr: req.id })));
|
|
65595
66032
|
}
|
|
@@ -66040,6 +66477,7 @@ function applySpecFields(raw, spec, opts) {
|
|
|
66040
66477
|
if (spec.reasoningEffort !== void 0) raw.reasoningEffort = spec.reasoningEffort;
|
|
66041
66478
|
if (spec.executionMode !== void 0) raw.executionMode = spec.executionMode;
|
|
66042
66479
|
if (spec.fastMode !== void 0) raw.fastMode = spec.fastMode;
|
|
66480
|
+
if (spec.permissionMode !== void 0) raw.permissionMode = spec.permissionMode;
|
|
66043
66481
|
if (spec.outputMode !== void 0) {
|
|
66044
66482
|
const out = typeof raw.output === "object" && raw.output !== null ? raw.output : {};
|
|
66045
66483
|
out.mode = spec.outputMode;
|
|
@@ -82267,6 +82705,7 @@ var Daemon = class Daemon {
|
|
|
82267
82705
|
log: this.log,
|
|
82268
82706
|
now: () => Date.now(),
|
|
82269
82707
|
gatewayFor: (integrationId) => this.connByIntegration.get(integrationId),
|
|
82708
|
+
memoryDirFor: (agentId) => this.agents.get(agentId)?.dir,
|
|
82270
82709
|
recordOutbound: (ctx, channel, thread, text, ts) => this.store.appendTranscript({
|
|
82271
82710
|
channel,
|
|
82272
82711
|
thread: thread ?? ctx.thread,
|
|
@@ -82285,11 +82724,11 @@ var Daemon = class Daemon {
|
|
|
82285
82724
|
agentById: (id) => this.agents.get(id),
|
|
82286
82725
|
mcpServersFor: ({ agent, channel, thread }) => {
|
|
82287
82726
|
const tools = toolsForIntegrations(agent.integrations);
|
|
82727
|
+
if (tools.length === 0) return [];
|
|
82288
82728
|
const integrationId = agent.integrations.find((i) => i.platform === "slack")?.id;
|
|
82289
|
-
if (tools.length === 0 || !integrationId) return [];
|
|
82290
82729
|
const token = this.mcp.register({
|
|
82291
82730
|
agentId: agent.id,
|
|
82292
|
-
integrationId,
|
|
82731
|
+
...integrationId ? { integrationId } : {},
|
|
82293
82732
|
channel,
|
|
82294
82733
|
thread,
|
|
82295
82734
|
tools
|
|
@@ -82631,6 +83070,7 @@ var Daemon = class Daemon {
|
|
|
82631
83070
|
},
|
|
82632
83071
|
configPrefs: {
|
|
82633
83072
|
model: agent.runtimeOverrides?.model,
|
|
83073
|
+
permissionMode: agent.permissionMode,
|
|
82634
83074
|
reasoningEffort: agent.reasoningEffort,
|
|
82635
83075
|
fastMode: agent.fastMode,
|
|
82636
83076
|
systemPrompt: agent.description
|
|
@@ -82653,6 +83093,10 @@ var Daemon = class Daemon {
|
|
|
82653
83093
|
}
|
|
82654
83094
|
this.seenMsgIds.add(msg.msgId);
|
|
82655
83095
|
if (this.seenMsgIds.size > 2e3) this.seenMsgIds.clear();
|
|
83096
|
+
if (this.ownBotIdentities().has(msg.sender.id)) {
|
|
83097
|
+
this.log.debug(`routing: dropping own bot echo ${msg.msgId}`);
|
|
83098
|
+
return;
|
|
83099
|
+
}
|
|
82656
83100
|
const command = parseCommand(msg.text);
|
|
82657
83101
|
if (command) {
|
|
82658
83102
|
this.handleCommand(command, msg);
|
|
@@ -82783,6 +83227,21 @@ var Daemon = class Daemon {
|
|
|
82783
83227
|
}
|
|
82784
83228
|
host.setSessionEffort(acpSessionId, effort).then(() => this.refreshStatusBarForKey(key)).catch((err) => this.log.warn(`set-effort failed: ${err.message}`));
|
|
82785
83229
|
}
|
|
83230
|
+
/** Switch a session's permission/approval mode by its local key. Records the sticky
|
|
83231
|
+
* override and applies it live via ACP `mode` when the runtime advertises one. */
|
|
83232
|
+
setPermissionModeByKey(key, permissionMode) {
|
|
83233
|
+
const rec = this.store.getSession(key);
|
|
83234
|
+
if (!rec) return;
|
|
83235
|
+
this.store.setPermissionModeOverride(key, permissionMode);
|
|
83236
|
+
this.log.info(`session ${key} permission-mode override → "${permissionMode}"`);
|
|
83237
|
+
const acpSessionId = rec.acpSessionId;
|
|
83238
|
+
const host = this.hosts.get(rec.agentId);
|
|
83239
|
+
if (!acpSessionId || !host?.hasSession(acpSessionId)) {
|
|
83240
|
+
this.refreshStatusBarForKey(key);
|
|
83241
|
+
return;
|
|
83242
|
+
}
|
|
83243
|
+
host.setSessionPermissionMode(acpSessionId, permissionMode).then(() => this.refreshStatusBarForKey(key)).catch((err) => this.log.warn(`set-permission-mode failed: ${err.message}`));
|
|
83244
|
+
}
|
|
82786
83245
|
/** Toggle a session's fast mode by its local key — the fast-mode counterpart of
|
|
82787
83246
|
* {@link setModelByKey}. Records the sticky override and applies it live when the
|
|
82788
83247
|
* current model offers a fast toggle. */
|
|
@@ -82823,6 +83282,10 @@ var Daemon = class Daemon {
|
|
|
82823
83282
|
handleWebchatSetEffort(m) {
|
|
82824
83283
|
this.setEffortByKey(this.webchatSessionKey(m.conversationId, m.agentId), m.effort);
|
|
82825
83284
|
}
|
|
83285
|
+
/** Handle a webchat in-session permission-mode switch (C→D `webchat/set-permission-mode`). */
|
|
83286
|
+
handleWebchatSetPermissionMode(m) {
|
|
83287
|
+
this.setPermissionModeByKey(this.webchatSessionKey(m.conversationId, m.agentId), m.permissionMode);
|
|
83288
|
+
}
|
|
82826
83289
|
/** Handle a webchat in-session fast-mode toggle (C→D `webchat/set-fast`). */
|
|
82827
83290
|
handleWebchatSetFast(m) {
|
|
82828
83291
|
this.setFastByKey(this.webchatSessionKey(m.conversationId, m.agentId), m.fastMode);
|
|
@@ -82846,6 +83309,8 @@ var Daemon = class Daemon {
|
|
|
82846
83309
|
if (a.model) this.setModelByKey(a.sessionKey, a.model);
|
|
82847
83310
|
} else if (a.kind === "set-effort") {
|
|
82848
83311
|
if (a.effort) this.setEffortByKey(a.sessionKey, a.effort);
|
|
83312
|
+
} else if (a.kind === "set-permission-mode") {
|
|
83313
|
+
if (a.permissionMode) this.setPermissionModeByKey(a.sessionKey, a.permissionMode);
|
|
82849
83314
|
} else if (a.kind === "set-fast") {
|
|
82850
83315
|
if (a.fastMode !== void 0) this.setFastByKey(a.sessionKey, a.fastMode);
|
|
82851
83316
|
} else if (a.kind === "set-output") {
|
|
@@ -83084,6 +83549,8 @@ var Daemon = class Daemon {
|
|
|
83084
83549
|
const host = await this.ensureHostAsync(agentId);
|
|
83085
83550
|
const override = this.store.getModelOverride(key);
|
|
83086
83551
|
if (override) await host.setSessionModel(sessionId, override).catch((err) => this.log.debug(`model override "${override}" not applied: ${err.message}`));
|
|
83552
|
+
const permissionModeOverride = this.store.getPermissionModeOverride(key);
|
|
83553
|
+
if (permissionModeOverride) await host.setSessionPermissionMode(sessionId, permissionModeOverride).catch((err) => this.log.debug(`permission-mode override "${permissionModeOverride}" not applied: ${err.message}`));
|
|
83087
83554
|
const effortOverride = this.store.getEffortOverride(key);
|
|
83088
83555
|
if (effortOverride) await host.setSessionEffort(sessionId, effortOverride).catch((err) => this.log.debug(`effort override "${effortOverride}" not applied: ${err.message}`));
|
|
83089
83556
|
const fastOverride = this.store.getFastModeOverride(key);
|
|
@@ -83347,12 +83814,15 @@ var Daemon = class Daemon {
|
|
|
83347
83814
|
const host = this.hosts.get(agentId);
|
|
83348
83815
|
const model = host?.modelOptions?.();
|
|
83349
83816
|
const effort = host?.effortOptions?.();
|
|
83817
|
+
const permissionMode = host?.permissionModeOptions?.();
|
|
83350
83818
|
const fast = host?.fastModeOption?.();
|
|
83351
83819
|
const effortOverride = this.store.getEffortOverride(sessionKey);
|
|
83820
|
+
const permissionModeOverride = this.store.getPermissionModeOverride(sessionKey);
|
|
83352
83821
|
const fastOverride = this.store.getFastModeOverride(sessionKey);
|
|
83353
83822
|
return {
|
|
83354
83823
|
model: model?.current ?? agent?.runtimeOverrides?.model,
|
|
83355
83824
|
effort: effortOverride ?? effort?.current ?? agent?.reasoningEffort,
|
|
83825
|
+
permissionMode: permissionModeOverride ?? permissionMode?.current ?? agent?.permissionMode,
|
|
83356
83826
|
fastMode: fastOverride ?? fast?.current ?? agent?.fastMode,
|
|
83357
83827
|
contextUsed: usage.contextUsed,
|
|
83358
83828
|
contextSize: usage.contextSize,
|
|
@@ -83369,6 +83839,7 @@ var Daemon = class Daemon {
|
|
|
83369
83839
|
} : {},
|
|
83370
83840
|
...model?.models?.length ? { models: model.models } : {},
|
|
83371
83841
|
...effort?.efforts?.length ? { efforts: effort.efforts } : {},
|
|
83842
|
+
...permissionMode?.modes?.length ? { permissionModes: permissionMode.modes } : {},
|
|
83372
83843
|
...fast ? { fastModeAvailable: true } : {},
|
|
83373
83844
|
...acpSessionId ? { sessionId: acpSessionId } : {}
|
|
83374
83845
|
};
|
|
@@ -83995,10 +84466,12 @@ var Daemon = class Daemon {
|
|
|
83995
84466
|
sessionRead: createSessionReader(this.store, (agentId) => this.replyConnFor(agentId)?.workspaceUrl),
|
|
83996
84467
|
workspaceRead: createWorkspaceReader((id) => this.agents.get(id)?.workspace.path),
|
|
83997
84468
|
workspaceGit: createWorkspaceGit((id) => this.agents.get(id)?.workspace.path),
|
|
84469
|
+
memoryReader: createMemoryReader((id) => this.agents.get(id)?.dir),
|
|
83998
84470
|
onWebchatMessage: (m) => this.handleWebchatMessage(m),
|
|
83999
84471
|
onWebchatClose: (m) => this.handleWebchatClose(m),
|
|
84000
84472
|
onWebchatSetModel: (m) => this.handleWebchatSetModel(m),
|
|
84001
84473
|
onWebchatSetEffort: (m) => this.handleWebchatSetEffort(m),
|
|
84474
|
+
onWebchatSetPermissionMode: (m) => this.handleWebchatSetPermissionMode(m),
|
|
84002
84475
|
onWebchatSetFast: (m) => this.handleWebchatSetFast(m),
|
|
84003
84476
|
onWebchatCancel: (m) => this.handleWebchatCancel(m),
|
|
84004
84477
|
clock: systemClock,
|
|
@@ -84115,4 +84588,4 @@ async function runForeground(opts, deps = {}) {
|
|
|
84115
84588
|
//#endregion
|
|
84116
84589
|
export { ClientTransport as n, runForeground as t };
|
|
84117
84590
|
|
|
84118
|
-
//# sourceMappingURL=run-foreground-
|
|
84591
|
+
//# sourceMappingURL=run-foreground-mvTzCScX.js.map
|