@byok-sdk/client 0.8.1 → 0.9.0
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/README.md +7 -1
- package/dist/adapters/index.js +14 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/agent-home.d.ts +5 -0
- package/dist/bin/agent-memory-mcp-server.d.ts +38 -0
- package/dist/bin/agent-message-mcp-server.d.ts +24 -0
- package/dist/bin/byok-agent-memory-mcp.d.ts +2 -0
- package/dist/bin/byok-agent-memory-mcp.js +432 -0
- package/dist/bin/byok-agent-memory-mcp.js.map +1 -0
- package/dist/bin/byok-agent-message-mcp.d.ts +2 -0
- package/dist/bin/byok-agent-message-mcp.js +441 -0
- package/dist/bin/byok-agent-message-mcp.js.map +1 -0
- package/dist/bin/byok-agent.js +1792 -192
- package/dist/bin/byok-agent.js.map +1 -1
- package/dist/bin/byok-approval-mcp.js.map +1 -1
- package/dist/daemon/agent-memory-filesystem.d.ts +23 -0
- package/dist/daemon/agent-memory-fs-helper.d.ts +10 -0
- package/dist/daemon/agent-memory.d.ts +160 -0
- package/dist/daemon/agent-message-outbox.d.ts +58 -0
- package/dist/daemon/control-protocol.d.ts +25 -0
- package/dist/daemon/create-daemon.d.ts +10 -0
- package/dist/daemon/memory-guidance.d.ts +8 -0
- package/dist/daemon/resolve-agent-memory-mcp-bin.d.ts +6 -0
- package/dist/daemon/resolve-agent-message-mcp-bin.d.ts +6 -0
- package/dist/daemon/task-runner.d.ts +79 -3
- package/dist/daemon/toolset-registry.d.ts +2 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1801 -201
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/package.json +7 -5
package/dist/bin/byok-agent.js
CHANGED
|
@@ -3,7 +3,7 @@ import { randomUUID, createHash, randomBytes, timingSafeEqual, createHmac, creat
|
|
|
3
3
|
import { readFileSync, promises, linkSync, fstatSync, lstatSync, unlinkSync, constants, readSync, openSync, writeFileSync, fchmodSync, fsyncSync, closeSync, opendirSync, existsSync, realpathSync, mkdirSync, renameSync, chmodSync, statSync, readdirSync } from 'fs';
|
|
4
4
|
import * as path3 from 'path';
|
|
5
5
|
import path3__default, { isAbsolute, join } from 'path';
|
|
6
|
-
import { AGENT_CONTENT_ARTIFACT_READ_CAPABILITY, AGENT_CONTENT_TRANSCRIPT_READ_CAPABILITY, AGENT_CONTENT_WORKSPACE_READ_CAPABILITY, TASK_STATES, AgentEgressPolicySchema, AgentHomeProjectionPayloadSchema, CONFIGURED_TOOLSETS_MAX_ITEMS, ToolsetIdSchema, AgentRefSchema, AgentContentReceiptPayloadSchema, BYOK_PAIR_PATH, PairResponseSchema, BYOK_CHALLENGE_PATH, BYOK_TOKEN_PATH, partitionAgentEvents, TASK_TRANSITIONS, encodeEnvelope, PROTOCOL_VERSION, createEnvelope, AGENT_EGRESS_RELIABLE_ACK_CAPABILITY, byokBlobUrlPath, BYOK_BLOBS_PATH, byokBlobFinalizePath, STRICT_AGENT_ONLY_CAPABILITY, AGENT_HOME_PROJECTION_CAPABILITY, AGENT_EGRESS_POLICY_CAPABILITY, AGENT_EGRESS_FRESH_SESSION_CAPABILITY, AgentHomeProjectionCompletionRequestSchema, byokAgentHomeProjectionCompletionPath, AgentHomeProjectionReadbackSchema, parseMessage, checkResultDocument, MAX_MESSAGES_PER_BATCH, BYOK_CAPABILITIES_PATH, BYOK_PRESENCE_PATH, RuntimeIdSchema, TERMINAL_INFERENCE_USAGE_MAX_DURATION_MS, TERMINAL_INFERENCE_USAGE_MAX_TOKENS, RESULT_DOCUMENT_MAX_BYTES, decodeEnvelope, BYOK_EVENTS_PATH, BYOK_MESSAGES_PATH, MessagesSendResponseSchema, UnknownMessageTypeError, BYOK_WS_PATH } from '@byok-sdk/protocol';
|
|
6
|
+
import { AGENT_CONTENT_ARTIFACT_READ_CAPABILITY, AGENT_CONTENT_TRANSCRIPT_READ_CAPABILITY, AGENT_CONTENT_WORKSPACE_READ_CAPABILITY, TASK_STATES, AgentEgressPolicySchema, AgentHomeProjectionPayloadSchema, CONFIGURED_TOOLSETS_MAX_ITEMS, ToolsetIdSchema, AgentRefSchema, AgentContentReceiptPayloadSchema, BYOK_PAIR_PATH, PairResponseSchema, BYOK_CHALLENGE_PATH, BYOK_TOKEN_PATH, partitionAgentEvents, TASK_TRANSITIONS, encodeEnvelope, PROTOCOL_VERSION, createEnvelope, AGENT_EGRESS_RELIABLE_ACK_CAPABILITY, byokBlobUrlPath, BYOK_BLOBS_PATH, byokBlobFinalizePath, TERMINAL_PROJECTION_SELECTION_CAPABILITY, STRICT_AGENT_ONLY_CAPABILITY, AGENT_HOME_PROJECTION_CAPABILITY, AGENT_EGRESS_POLICY_CAPABILITY, AGENT_EGRESS_FRESH_SESSION_CAPABILITY, AGENT_MESSAGE_EGRESS_CAPABILITY, AgentHomeProjectionCompletionRequestSchema, byokAgentHomeProjectionCompletionPath, AgentHomeProjectionReadbackSchema, parseMessage, checkResultDocument, MAX_MESSAGES_PER_BATCH, BYOK_CAPABILITIES_PATH, BYOK_PRESENCE_PATH, AgentMessagePublishPayloadSchema, AgentMessageDispositionPayloadSchema, RuntimeIdSchema, AGENT_MEMORY_PROJECTION_CAPABILITY, TERMINAL_INFERENCE_USAGE_MAX_DURATION_MS, TERMINAL_INFERENCE_USAGE_MAX_TOKENS, RESULT_DOCUMENT_MAX_BYTES, decodeEnvelope, BYOK_EVENTS_PATH, BYOK_MESSAGES_PATH, MessagesSendResponseSchema, UnknownMessageTypeError, AGENT_MEMORY_PROJECTION_MAX_ORDERING_VALUE, AgentMemoryProjectionMutationSchema, AGENT_MEMORY_PROJECTION_MAX_REDACTED_BYTES, BYOK_WS_PATH } from '@byok-sdk/protocol';
|
|
7
7
|
import net, { createServer, createConnection } from 'net';
|
|
8
8
|
import * as os from 'os';
|
|
9
9
|
import os__default from 'os';
|
|
@@ -649,12 +649,16 @@ var AgentHomeLeaseManager = class _AgentHomeLeaseManager {
|
|
|
649
649
|
let handle;
|
|
650
650
|
let rootGate;
|
|
651
651
|
let ownsMarker = false;
|
|
652
|
+
let homeIdentity;
|
|
652
653
|
try {
|
|
653
654
|
rootGate = await acquirePathMutationGate({
|
|
654
655
|
scope: "agent-home-root",
|
|
655
656
|
targetPath: resolution.agentsRoot
|
|
656
657
|
}, { waitMs: 1e3 });
|
|
657
658
|
await ensureDirectoryNoSymlink(resolution.agentsRoot, canonicalHome);
|
|
659
|
+
const homeStat = await promises.stat(canonicalHome, { bigint: true });
|
|
660
|
+
if (!homeStat.isDirectory()) throw new AgentHomeResolutionError(`Agent home ${canonicalHome} is not a directory`);
|
|
661
|
+
homeIdentity = Object.freeze({ dev: homeStat.dev, ino: homeStat.ino });
|
|
658
662
|
const internalDir = await ensureDirectoryNoSymlink(
|
|
659
663
|
canonicalHome,
|
|
660
664
|
path3__default.join(canonicalHome, AGENT_HOME_INTERNAL_DIRECTORY)
|
|
@@ -693,6 +697,7 @@ var AgentHomeLeaseManager = class _AgentHomeLeaseManager {
|
|
|
693
697
|
}
|
|
694
698
|
const acquiredLockPath = lockPath;
|
|
695
699
|
if (acquiredLockPath === void 0) throw new AgentHomeError("Agent home lease marker path was not established");
|
|
700
|
+
if (homeIdentity === void 0) throw new AgentHomeError("Agent home lease identity was not established");
|
|
696
701
|
let released = false;
|
|
697
702
|
let releaseAttempt;
|
|
698
703
|
return {
|
|
@@ -700,6 +705,7 @@ var AgentHomeLeaseManager = class _AgentHomeLeaseManager {
|
|
|
700
705
|
agentRef,
|
|
701
706
|
canonicalHome,
|
|
702
707
|
cwd: canonicalHome,
|
|
708
|
+
homeIdentity,
|
|
703
709
|
release: () => {
|
|
704
710
|
if (released) return Promise.resolve();
|
|
705
711
|
if (releaseAttempt !== void 0) return releaseAttempt;
|
|
@@ -1048,14 +1054,14 @@ function sameTaskTerminalMatch(entry, expected) {
|
|
|
1048
1054
|
return sameRef(entry.agentRef, expected.agentRef) && entry.taskId === expected.taskId && entry.runtimeId === expected.runtimeId && entry.cwd === path3__default.resolve(expected.cwd);
|
|
1049
1055
|
}
|
|
1050
1056
|
function sessionFileName(runtimeId, sessionRef) {
|
|
1051
|
-
const
|
|
1057
|
+
const digest3 = createHash("sha256").update(sessionRef, "utf8").digest("hex");
|
|
1052
1058
|
const runtime = runtimeId.replace(/[^a-z0-9._-]/giu, "_").slice(0, 64) || "runtime";
|
|
1053
|
-
return `${runtime}-${
|
|
1059
|
+
return `${runtime}-${digest3}.jsonl`;
|
|
1054
1060
|
}
|
|
1055
1061
|
function taskTerminalFileName(runtimeId, taskId) {
|
|
1056
|
-
const
|
|
1062
|
+
const digest3 = createHash("sha256").update(taskId, "utf8").digest("hex");
|
|
1057
1063
|
const runtime = runtimeId.replace(/[^a-z0-9._-]/giu, "_").slice(0, 64) || "runtime";
|
|
1058
|
-
return `${runtime}-task-${
|
|
1064
|
+
return `${runtime}-task-${digest3}.jsonl`;
|
|
1059
1065
|
}
|
|
1060
1066
|
async function evidenceDirectory(cwdInput) {
|
|
1061
1067
|
if (!path3__default.isAbsolute(cwdInput)) {
|
|
@@ -4794,6 +4800,7 @@ var CodexAdapter = class {
|
|
|
4794
4800
|
steer: false,
|
|
4795
4801
|
resume: true,
|
|
4796
4802
|
approvalInteractive: false,
|
|
4803
|
+
mcpToolsets: true,
|
|
4797
4804
|
permissionModes: ["auto", "readonly"]
|
|
4798
4805
|
},
|
|
4799
4806
|
environmentRequirements: { credentialNames: [] }
|
|
@@ -4925,7 +4932,7 @@ ${withStreams.stderr ?? ""}`);
|
|
|
4925
4932
|
resumeRef: startInput.manifest.sessionRef,
|
|
4926
4933
|
instruction: startInput.instruction,
|
|
4927
4934
|
modelId: manifestModelId,
|
|
4928
|
-
policyArgs: [...policyArgs],
|
|
4935
|
+
policyArgs: [...policyArgs, ...codexMcpConfigArgs(startInput.mcpServers)],
|
|
4929
4936
|
cwd: manifestCwd,
|
|
4930
4937
|
env: runtimeEnv,
|
|
4931
4938
|
spawnFn: this.options.spawnFn,
|
|
@@ -4955,6 +4962,18 @@ ${withStreams.stderr ?? ""}`);
|
|
|
4955
4962
|
return (this.options.resolveBin ?? resolveCodexBin)();
|
|
4956
4963
|
}
|
|
4957
4964
|
};
|
|
4965
|
+
function codexMcpConfigArgs(servers) {
|
|
4966
|
+
if (servers === void 0 || Object.keys(servers).length === 0) return [];
|
|
4967
|
+
const args = ["--ignore-user-config"];
|
|
4968
|
+
for (const [name, server] of Object.entries(servers).sort(([left], [right]) => left.localeCompare(right))) {
|
|
4969
|
+
args.push("-c", `mcp_servers.${name}.command=${JSON.stringify(server.command)}`);
|
|
4970
|
+
if (server.args !== void 0) args.push("-c", `mcp_servers.${name}.args=${JSON.stringify([...server.args])}`);
|
|
4971
|
+
for (const [key, value] of Object.entries(server.env ?? {}).sort(([left], [right]) => left.localeCompare(right))) {
|
|
4972
|
+
args.push("-c", `mcp_servers.${name}.env.${key}=${JSON.stringify(value)}`);
|
|
4973
|
+
}
|
|
4974
|
+
}
|
|
4975
|
+
return args;
|
|
4976
|
+
}
|
|
4958
4977
|
async function resolveRealWorkspaceDir(workspaceDir) {
|
|
4959
4978
|
return promises.realpath(workspaceDir).catch(() => workspaceDir);
|
|
4960
4979
|
}
|
|
@@ -6666,6 +6685,27 @@ function parseApprovalsRequestParams(value) {
|
|
|
6666
6685
|
if (typeof value.summary !== "string") return void 0;
|
|
6667
6686
|
return { taskId: value.taskId, summary: value.summary };
|
|
6668
6687
|
}
|
|
6688
|
+
function parseAgentMessagePublishParams(value) {
|
|
6689
|
+
if (!isRecord3(value) || Object.keys(value).some((key) => key !== "contextToken" && key !== "contentType" && key !== "body")) return void 0;
|
|
6690
|
+
if (typeof value.contextToken !== "string" || value.contextToken.length < 32 || value.contextToken.length > 160) return void 0;
|
|
6691
|
+
if (value.contentType !== "text/plain" && value.contentType !== "text/markdown") return void 0;
|
|
6692
|
+
if (typeof value.body !== "string" || value.body.length === 0) return void 0;
|
|
6693
|
+
return { contextToken: value.contextToken, contentType: value.contentType, body: value.body };
|
|
6694
|
+
}
|
|
6695
|
+
function validAgentMemoryContextToken(value) {
|
|
6696
|
+
return typeof value === "string" && value.length >= 32 && value.length <= 160 && !/[\u0000\r\n]/u.test(value);
|
|
6697
|
+
}
|
|
6698
|
+
function parseAgentMemoryRecallParams(value) {
|
|
6699
|
+
if (!isRecord3(value) || Object.keys(value).some((key) => key !== "contextToken" && key !== "path" && key !== "ifRevision")) return void 0;
|
|
6700
|
+
if (!validAgentMemoryContextToken(value.contextToken) || typeof value.path !== "string" || value.ifRevision !== void 0 && typeof value.ifRevision !== "string") return void 0;
|
|
6701
|
+
return { contextToken: value.contextToken, path: value.path, ...value.ifRevision === void 0 ? {} : { ifRevision: value.ifRevision } };
|
|
6702
|
+
}
|
|
6703
|
+
function parseAgentMemorySaveParams(value) {
|
|
6704
|
+
if (!isRecord3(value) || Object.keys(value).some((key) => key !== "contextToken" && key !== "op" && key !== "path" && key !== "expectedRevision" && key !== "content")) return void 0;
|
|
6705
|
+
if (!validAgentMemoryContextToken(value.contextToken) || value.op !== "replace" && value.op !== "delete" || typeof value.path !== "string" || typeof value.expectedRevision !== "string" || value.op === "replace" && typeof value.content !== "string" || value.op === "delete" && value.content !== void 0) return void 0;
|
|
6706
|
+
const content = value.content;
|
|
6707
|
+
return { contextToken: value.contextToken, op: value.op, path: value.path, expectedRevision: value.expectedRevision, ...typeof content === "string" ? { content } : {} };
|
|
6708
|
+
}
|
|
6669
6709
|
var ASSERTION_AUDIENCE_MAX_BYTES = 256;
|
|
6670
6710
|
function parseAssertionIssueParams(value) {
|
|
6671
6711
|
if (!isRecord3(value)) return void 0;
|
|
@@ -6923,6 +6963,8 @@ async function startControlServer(opts) {
|
|
|
6923
6963
|
}
|
|
6924
6964
|
return { endpoint, stopServing, close };
|
|
6925
6965
|
}
|
|
6966
|
+
var AGENT_MESSAGE_MCP_SERVER_NAME = "byokagentmessage";
|
|
6967
|
+
var AGENT_MEMORY_MCP_SERVER_NAME = "byokagentmemory";
|
|
6926
6968
|
var MAX_LOCAL_MCP_SERVERS_PER_TOOLSET = 16;
|
|
6927
6969
|
var MAX_LOCAL_MCP_ARGS = 64;
|
|
6928
6970
|
var MAX_LOCAL_MCP_TOKEN_CHARS = 4096;
|
|
@@ -7012,7 +7054,7 @@ function buildState(configured) {
|
|
|
7012
7054
|
`DaemonConfig.mcpToolsets.${toolsetId}.mcpServers contains invalid server name ${JSON.stringify(serverName)}`
|
|
7013
7055
|
);
|
|
7014
7056
|
}
|
|
7015
|
-
if (serverName === APPROVAL_MCP_SERVER_NAME) {
|
|
7057
|
+
if (serverName === APPROVAL_MCP_SERVER_NAME || serverName === AGENT_MESSAGE_MCP_SERVER_NAME || serverName === AGENT_MEMORY_MCP_SERVER_NAME) {
|
|
7016
7058
|
throw new Error(
|
|
7017
7059
|
`DaemonConfig.mcpToolsets.${toolsetId}.mcpServers.${serverName} uses a server name reserved by the daemon`
|
|
7018
7060
|
);
|
|
@@ -7175,9 +7217,9 @@ function deterministicJitterMs(input) {
|
|
|
7175
7217
|
throw new Error("deterministic jitter ratio must be between 0 and 1");
|
|
7176
7218
|
}
|
|
7177
7219
|
if (input.baseMs === 0 || ratio === 0) return Math.round(input.baseMs);
|
|
7178
|
-
const
|
|
7179
|
-
const high =
|
|
7180
|
-
const low =
|
|
7220
|
+
const digest3 = createHash("sha256").update("byok-jitter-v1\0", "utf8").update(input.domain, "utf8").update("\0", "utf8").update(input.seed, "utf8").update("\0", "utf8").update(String(input.sequence), "utf8").digest();
|
|
7221
|
+
const high = digest3.readUInt32BE(0) & 2097151;
|
|
7222
|
+
const low = digest3.readUInt32BE(4);
|
|
7181
7223
|
const unit = (high * 4294967296 + low) / 9007199254740992;
|
|
7182
7224
|
const multiplier = 1 - ratio + unit * ratio * 2;
|
|
7183
7225
|
return Math.max(0, Math.round(input.baseMs * multiplier));
|
|
@@ -9187,9 +9229,9 @@ function isSqliteAvailable() {
|
|
|
9187
9229
|
}
|
|
9188
9230
|
}
|
|
9189
9231
|
var SECURE_FILE_MODE = 384;
|
|
9190
|
-
function openJournalDatabase(
|
|
9232
|
+
function openJournalDatabase(path41, busyTimeoutMs, faults) {
|
|
9191
9233
|
const { DatabaseSync } = loadSqliteModule();
|
|
9192
|
-
const db = new DatabaseSync(
|
|
9234
|
+
const db = new DatabaseSync(path41, { timeout: busyTimeoutMs });
|
|
9193
9235
|
try {
|
|
9194
9236
|
faults?.onStep?.("after-open");
|
|
9195
9237
|
db.exec("PRAGMA auto_vacuum = INCREMENTAL;");
|
|
@@ -9332,9 +9374,9 @@ var RECEIVED_STATE = "received";
|
|
|
9332
9374
|
function byteLength(value) {
|
|
9333
9375
|
return Buffer.byteLength(value, "utf8");
|
|
9334
9376
|
}
|
|
9335
|
-
function fileBytes(
|
|
9377
|
+
function fileBytes(path41) {
|
|
9336
9378
|
try {
|
|
9337
|
-
return statSync(
|
|
9379
|
+
return statSync(path41).size;
|
|
9338
9380
|
} catch {
|
|
9339
9381
|
return 0;
|
|
9340
9382
|
}
|
|
@@ -10561,130 +10603,1262 @@ var ProgressBatcher = class {
|
|
|
10561
10603
|
}
|
|
10562
10604
|
}
|
|
10563
10605
|
};
|
|
10564
|
-
|
|
10565
|
-
|
|
10566
|
-
var
|
|
10567
|
-
|
|
10568
|
-
|
|
10569
|
-
|
|
10570
|
-
var GIT_OBSERVATION_TIMEOUT_MS = 5e3;
|
|
10571
|
-
var MAX_PENDING_APPROVALS_PER_TASK = 16;
|
|
10572
|
-
var NoPendingApprovalError = class extends Error {
|
|
10573
|
-
constructor(taskId) {
|
|
10574
|
-
super(`no pending out-of-band approval to resolve for task ${taskId}`);
|
|
10575
|
-
this.taskId = taskId;
|
|
10576
|
-
this.name = "NoPendingApprovalError";
|
|
10606
|
+
var AGENT_MESSAGE_DIRECTORY = path3__default.join(".byok", "messages");
|
|
10607
|
+
var AGENT_MESSAGE_OUTBOX_FILENAME = "outbox-v1.jsonl";
|
|
10608
|
+
var AgentMessageOutboxError = class extends Error {
|
|
10609
|
+
constructor(message) {
|
|
10610
|
+
super(message);
|
|
10611
|
+
this.name = "AgentMessageOutboxError";
|
|
10577
10612
|
}
|
|
10578
|
-
taskId;
|
|
10579
10613
|
};
|
|
10580
|
-
|
|
10581
|
-
|
|
10582
|
-
|
|
10583
|
-
|
|
10584
|
-
|
|
10585
|
-
|
|
10586
|
-
|
|
10587
|
-
|
|
10588
|
-
|
|
10589
|
-
|
|
10590
|
-
|
|
10591
|
-
|
|
10592
|
-
|
|
10593
|
-
|
|
10594
|
-
|
|
10595
|
-
|
|
10596
|
-
|
|
10614
|
+
function stableJson(value) {
|
|
10615
|
+
const encoded = JSON.stringify(value);
|
|
10616
|
+
if (encoded === void 0) throw new AgentMessageOutboxError("message outbox value is not serializable");
|
|
10617
|
+
return encoded;
|
|
10618
|
+
}
|
|
10619
|
+
function hashBody(body) {
|
|
10620
|
+
return `sha256:${createHash("sha256").update(body, "utf8").digest("hex")}`;
|
|
10621
|
+
}
|
|
10622
|
+
function sameAgentRef(left, right) {
|
|
10623
|
+
return left.agentId === right.agentId && left.profileRevision === right.profileRevision;
|
|
10624
|
+
}
|
|
10625
|
+
function exactDisposition(record, value) {
|
|
10626
|
+
return value.messageId === record.messageId && value.cursor === record.cursor && value.contract === record.contract && value.contentHash === record.contentHash && value.sessionRef === record.sessionRef && sameAgentRef(value.agentRef, record.agentRef);
|
|
10627
|
+
}
|
|
10628
|
+
var AgentMessageOutbox = class _AgentMessageOutbox {
|
|
10629
|
+
constructor(homeDir, outboxPath) {
|
|
10630
|
+
this.homeDir = homeDir;
|
|
10631
|
+
this.outboxPath = outboxPath;
|
|
10632
|
+
}
|
|
10633
|
+
homeDir;
|
|
10634
|
+
outboxPath;
|
|
10635
|
+
pendingByTask = /* @__PURE__ */ new Map();
|
|
10636
|
+
dispositionByTask = /* @__PURE__ */ new Map();
|
|
10637
|
+
nextCursor = 1;
|
|
10638
|
+
logEntries = 0;
|
|
10639
|
+
writeTail = Promise.resolve();
|
|
10640
|
+
static async open(homeDir) {
|
|
10641
|
+
const directory = path3__default.join(homeDir, AGENT_MESSAGE_DIRECTORY);
|
|
10642
|
+
await ensureSecureDir(directory);
|
|
10643
|
+
const outbox = new _AgentMessageOutbox(homeDir, path3__default.join(directory, AGENT_MESSAGE_OUTBOX_FILENAME));
|
|
10644
|
+
await outbox.load();
|
|
10645
|
+
return outbox;
|
|
10646
|
+
}
|
|
10647
|
+
/** Re-open every existing Agent-local message outbox without following Agent-home symlinks. */
|
|
10648
|
+
static async recover(agentsRoot, tenantId) {
|
|
10649
|
+
if (!path3__default.isAbsolute(agentsRoot)) throw new AgentMessageOutboxError("message outbox recovery root must be absolute");
|
|
10650
|
+
let canonicalRoot;
|
|
10651
|
+
try {
|
|
10652
|
+
canonicalRoot = await promises.realpath(agentsRoot);
|
|
10653
|
+
} catch (error) {
|
|
10654
|
+
if (error.code === "ENOENT") return [];
|
|
10655
|
+
throw error;
|
|
10656
|
+
}
|
|
10657
|
+
const recovered = [];
|
|
10658
|
+
for (const entry of await promises.readdir(canonicalRoot, { withFileTypes: true })) {
|
|
10659
|
+
if (!entry.isDirectory() || entry.isSymbolicLink()) continue;
|
|
10660
|
+
const home = path3__default.join(canonicalRoot, entry.name);
|
|
10661
|
+
const canonicalHome = await promises.realpath(home);
|
|
10662
|
+
if (path3__default.relative(canonicalRoot, canonicalHome) !== entry.name) {
|
|
10663
|
+
throw new AgentMessageOutboxError(`message outbox recovery home escaped the canonical agents root: ${entry.name}`);
|
|
10664
|
+
}
|
|
10665
|
+
try {
|
|
10666
|
+
await promises.lstat(path3__default.join(canonicalHome, AGENT_MESSAGE_DIRECTORY, AGENT_MESSAGE_OUTBOX_FILENAME));
|
|
10667
|
+
} catch (error) {
|
|
10668
|
+
if (error.code === "ENOENT") continue;
|
|
10669
|
+
throw error;
|
|
10670
|
+
}
|
|
10671
|
+
const outbox = await _AgentMessageOutbox.open(canonicalHome);
|
|
10672
|
+
if (outbox.records().some((record) => record.agentRef.agentId !== entry.name)) {
|
|
10673
|
+
throw new AgentMessageOutboxError(`message outbox in ${entry.name} claims a different Agent`);
|
|
10674
|
+
}
|
|
10675
|
+
if (outbox.records().some((record) => record.tenantId !== tenantId)) {
|
|
10676
|
+
throw new AgentMessageOutboxError(`message outbox in ${entry.name} claims a different authenticated tenant`);
|
|
10677
|
+
}
|
|
10678
|
+
recovered.push(outbox);
|
|
10597
10679
|
}
|
|
10680
|
+
return Object.freeze(recovered);
|
|
10598
10681
|
}
|
|
10682
|
+
records() {
|
|
10683
|
+
return Object.freeze([...this.pendingByTask.values()].sort((a, b) => a.cursor - b.cursor));
|
|
10684
|
+
}
|
|
10685
|
+
/** Activated records with no exact disposition yet; only these may be transport-replayed. */
|
|
10686
|
+
retryableRecords() {
|
|
10687
|
+
return Object.freeze(this.records().filter((record) => !this.dispositionByTask.has(record.taskId)));
|
|
10688
|
+
}
|
|
10689
|
+
get(taskId) {
|
|
10690
|
+
return this.pendingByTask.get(taskId);
|
|
10691
|
+
}
|
|
10692
|
+
async appendDraft(input) {
|
|
10693
|
+
return this.exclusive(async () => {
|
|
10694
|
+
if (input.contentType !== input.requirement.contentType) throw new AgentMessageOutboxError("message contentType does not match the offer contract");
|
|
10695
|
+
const byteCount = Buffer.byteLength(input.body, "utf8");
|
|
10696
|
+
if (byteCount < 1 || byteCount > input.requirement.maxBytes) throw new AgentMessageOutboxError("message body exceeds the offer byte contract");
|
|
10697
|
+
const existing = this.pendingByTask.get(input.taskId);
|
|
10698
|
+
if (existing !== void 0) {
|
|
10699
|
+
if (existing.tenantId !== input.tenantId || existing.contentType !== input.contentType || existing.body !== input.body || existing.contract !== input.requirement.contract || !sameAgentRef(existing.agentRef, input.agentRef)) {
|
|
10700
|
+
throw new AgentMessageOutboxError("required-message task already has a different immutable draft");
|
|
10701
|
+
}
|
|
10702
|
+
return existing;
|
|
10703
|
+
}
|
|
10704
|
+
if (this.pendingByTask.size >= input.maxPendingEvents) throw new AgentMessageOutboxError("message outbox event quota is exhausted");
|
|
10705
|
+
const pendingBytes = this.records().reduce((sum, record2) => sum + record2.byteCount, 0);
|
|
10706
|
+
if (pendingBytes + byteCount > input.maxPendingBytes) throw new AgentMessageOutboxError("message outbox byte quota is exhausted");
|
|
10707
|
+
const record = Object.freeze({
|
|
10708
|
+
schema: 1,
|
|
10709
|
+
taskId: input.taskId,
|
|
10710
|
+
tenantId: input.tenantId,
|
|
10711
|
+
agentRef: Object.freeze({ ...input.agentRef }),
|
|
10712
|
+
contract: input.requirement.contract,
|
|
10713
|
+
messageId: randomUUID(),
|
|
10714
|
+
cursor: this.nextCursor++,
|
|
10715
|
+
contentType: input.contentType,
|
|
10716
|
+
body: input.body,
|
|
10717
|
+
contentHash: hashBody(input.body),
|
|
10718
|
+
byteCount,
|
|
10719
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10720
|
+
...input.sessionRef === void 0 ? {} : { sessionRef: input.sessionRef }
|
|
10721
|
+
});
|
|
10722
|
+
await this.appendEntry({ schema: 1, kind: "append", record });
|
|
10723
|
+
this.pendingByTask.set(record.taskId, record);
|
|
10724
|
+
return record;
|
|
10725
|
+
});
|
|
10726
|
+
}
|
|
10727
|
+
async activate(taskId, sessionRef) {
|
|
10728
|
+
return this.exclusive(async () => {
|
|
10729
|
+
const record = this.pendingByTask.get(taskId);
|
|
10730
|
+
if (record === void 0) return void 0;
|
|
10731
|
+
if (record.sessionRef !== void 0) {
|
|
10732
|
+
if (record.sessionRef !== sessionRef) throw new AgentMessageOutboxError("message draft is already bound to a different session");
|
|
10733
|
+
return record;
|
|
10734
|
+
}
|
|
10735
|
+
await this.appendEntry({ schema: 1, kind: "activate", taskId, messageId: record.messageId, sessionRef });
|
|
10736
|
+
const activated = Object.freeze({ ...record, sessionRef });
|
|
10737
|
+
this.pendingByTask.set(taskId, activated);
|
|
10738
|
+
return activated;
|
|
10739
|
+
});
|
|
10740
|
+
}
|
|
10741
|
+
publishPayload(record) {
|
|
10742
|
+
if (record.sessionRef === void 0) throw new AgentMessageOutboxError("message draft is not bound to a durable session");
|
|
10743
|
+
return AgentMessagePublishPayloadSchema.parse({
|
|
10744
|
+
agentRef: record.agentRef,
|
|
10745
|
+
sessionRef: record.sessionRef,
|
|
10746
|
+
contract: record.contract,
|
|
10747
|
+
messageId: record.messageId,
|
|
10748
|
+
cursor: record.cursor,
|
|
10749
|
+
contentType: record.contentType,
|
|
10750
|
+
body: record.body,
|
|
10751
|
+
contentHash: record.contentHash,
|
|
10752
|
+
byteCount: record.byteCount
|
|
10753
|
+
});
|
|
10754
|
+
}
|
|
10755
|
+
async applyDisposition(taskId, input) {
|
|
10756
|
+
return this.exclusive(async () => {
|
|
10757
|
+
const disposition = AgentMessageDispositionPayloadSchema.parse(input);
|
|
10758
|
+
const record = this.pendingByTask.get(taskId);
|
|
10759
|
+
if (record === void 0) return "unknown";
|
|
10760
|
+
if (!exactDisposition(record, disposition)) return "mismatch";
|
|
10761
|
+
await this.appendEntry({ schema: 1, kind: "disposition", taskId, disposition });
|
|
10762
|
+
if (disposition.outcome === "accepted") {
|
|
10763
|
+
this.pendingByTask.delete(taskId);
|
|
10764
|
+
this.dispositionByTask.delete(taskId);
|
|
10765
|
+
} else {
|
|
10766
|
+
this.dispositionByTask.set(taskId, disposition);
|
|
10767
|
+
}
|
|
10768
|
+
if (this.logEntries >= 512) await this.compact();
|
|
10769
|
+
return disposition.outcome;
|
|
10770
|
+
});
|
|
10771
|
+
}
|
|
10772
|
+
async load() {
|
|
10773
|
+
let raw;
|
|
10774
|
+
try {
|
|
10775
|
+
const stat = await promises.stat(this.outboxPath);
|
|
10776
|
+
if (stat.size > 64 * 1024 * 1024) throw new AgentMessageOutboxError("message outbox exceeds its bounded on-disk size");
|
|
10777
|
+
raw = await promises.readFile(this.outboxPath, "utf8");
|
|
10778
|
+
} catch (error) {
|
|
10779
|
+
if (error.code === "ENOENT") return;
|
|
10780
|
+
throw error;
|
|
10781
|
+
}
|
|
10782
|
+
for (const line of raw.split("\n")) {
|
|
10783
|
+
if (line.length === 0) continue;
|
|
10784
|
+
const entry = JSON.parse(line);
|
|
10785
|
+
this.logEntries += 1;
|
|
10786
|
+
if (entry.schema !== 1) throw new AgentMessageOutboxError("message outbox schema is unsupported");
|
|
10787
|
+
if (entry.kind === "append") {
|
|
10788
|
+
if (this.pendingByTask.has(entry.record.taskId)) throw new AgentMessageOutboxError("message outbox contains duplicate task draft");
|
|
10789
|
+
this.pendingByTask.set(entry.record.taskId, Object.freeze(entry.record));
|
|
10790
|
+
this.nextCursor = Math.max(this.nextCursor, entry.record.cursor + 1);
|
|
10791
|
+
} else if (entry.kind === "activate") {
|
|
10792
|
+
const record = this.pendingByTask.get(entry.taskId);
|
|
10793
|
+
if (record === void 0 || record.messageId !== entry.messageId) throw new AgentMessageOutboxError("message activation has no exact draft");
|
|
10794
|
+
if (record.sessionRef !== void 0 && record.sessionRef !== entry.sessionRef) throw new AgentMessageOutboxError("message activation session conflicts");
|
|
10795
|
+
this.pendingByTask.set(entry.taskId, Object.freeze({ ...record, sessionRef: entry.sessionRef }));
|
|
10796
|
+
} else if (entry.kind === "disposition") {
|
|
10797
|
+
const record = this.pendingByTask.get(entry.taskId);
|
|
10798
|
+
if (record === void 0 || !exactDisposition(record, entry.disposition)) throw new AgentMessageOutboxError("message outbox contains a mismatched disposition");
|
|
10799
|
+
if (entry.disposition.outcome === "accepted") {
|
|
10800
|
+
this.pendingByTask.delete(entry.taskId);
|
|
10801
|
+
this.dispositionByTask.delete(entry.taskId);
|
|
10802
|
+
} else {
|
|
10803
|
+
this.dispositionByTask.set(entry.taskId, entry.disposition);
|
|
10804
|
+
}
|
|
10805
|
+
} else {
|
|
10806
|
+
throw new AgentMessageOutboxError("message outbox entry kind is invalid");
|
|
10807
|
+
}
|
|
10808
|
+
}
|
|
10809
|
+
}
|
|
10810
|
+
async appendEntry(entry) {
|
|
10811
|
+
const handle = await promises.open(this.outboxPath, constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY, 384);
|
|
10812
|
+
try {
|
|
10813
|
+
await handle.write(`${stableJson(entry)}
|
|
10814
|
+
`, void 0, "utf8");
|
|
10815
|
+
await handle.sync();
|
|
10816
|
+
this.logEntries += 1;
|
|
10817
|
+
} finally {
|
|
10818
|
+
await handle.close();
|
|
10819
|
+
}
|
|
10820
|
+
}
|
|
10821
|
+
async compact() {
|
|
10822
|
+
const entries = this.records().flatMap((record) => {
|
|
10823
|
+
const append = { schema: 1, kind: "append", record };
|
|
10824
|
+
const disposition = this.dispositionByTask.get(record.taskId);
|
|
10825
|
+
return disposition === void 0 ? [append] : [append, { schema: 1, kind: "disposition", taskId: record.taskId, disposition }];
|
|
10826
|
+
});
|
|
10827
|
+
await atomicWriteFile(this.outboxPath, entries.map(stableJson).join(entries.length === 0 ? "" : "\n"), { mode: 384 });
|
|
10828
|
+
this.logEntries = entries.length;
|
|
10829
|
+
}
|
|
10830
|
+
async exclusive(fn) {
|
|
10831
|
+
const prior = this.writeTail;
|
|
10832
|
+
let release;
|
|
10833
|
+
this.writeTail = new Promise((resolve) => {
|
|
10834
|
+
release = resolve;
|
|
10835
|
+
});
|
|
10836
|
+
await prior;
|
|
10837
|
+
try {
|
|
10838
|
+
return await fn();
|
|
10839
|
+
} finally {
|
|
10840
|
+
release();
|
|
10841
|
+
}
|
|
10842
|
+
}
|
|
10843
|
+
};
|
|
10844
|
+
|
|
10845
|
+
// src/daemon/memory-guidance.ts
|
|
10846
|
+
var AGENT_MEMORY_GUIDANCE = [
|
|
10847
|
+
"At the start of this Agent task, first read `MEMORY.md` in the provided `cwd`.",
|
|
10848
|
+
"Treat `MEMORY.md` as a concise, self-contained recovery index; if it is empty, initialize a brief index from durable, non-secret task knowledge.",
|
|
10849
|
+
"Read files under `notes/` only as needed, following pointers from the index.",
|
|
10850
|
+
"When task permissions allow and a durable value is learned, update the relevant `notes/` entry and the `MEMORY.md` index.",
|
|
10851
|
+
"Never write credentials, secrets, tokens, API keys, private keys, or other authentication material to `MEMORY.md` or `notes/`."
|
|
10852
|
+
].join("\n");
|
|
10853
|
+
function prependAgentMemoryGuidance(instruction) {
|
|
10854
|
+
return `${AGENT_MEMORY_GUIDANCE}
|
|
10855
|
+
|
|
10856
|
+
${instruction}`;
|
|
10599
10857
|
}
|
|
10600
|
-
var
|
|
10601
|
-
|
|
10602
|
-
|
|
10603
|
-
|
|
10604
|
-
|
|
10605
|
-
|
|
10858
|
+
var AGENT_MEMORY_AUDIT_FILENAME = "agent-memory-audit-v1.jsonl";
|
|
10859
|
+
var AGENT_MEMORY_OUTBOX_FILENAME = "agent-memory-redacted-outbox-v2.json";
|
|
10860
|
+
var AGENT_MEMORY_MAX_FILE_BYTES = 256 * 1024;
|
|
10861
|
+
var AGENT_MEMORY_MAX_SNAPSHOT_BYTES = 1024 * 1024;
|
|
10862
|
+
var AGENT_MEMORY_MAX_SNAPSHOT_FILES = 128;
|
|
10863
|
+
var AGENT_MEMORY_MAX_SNAPSHOT_ENTRIES = 512;
|
|
10864
|
+
var AGENT_MEMORY_MAX_LOCAL_LOG_BYTES = AGENT_MEMORY_MAX_SNAPSHOT_BYTES;
|
|
10865
|
+
var REVISION = /^sha256:[a-f0-9]{64}$/u;
|
|
10866
|
+
var SAFE_SEGMENT = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
|
|
10867
|
+
var SECRET_LIKE = /(?:^|[-_.])(secret|token|credential|password|passwd|api[-_]?key|private[-_]?key|cookie)(?:$|[-_.])/iu;
|
|
10868
|
+
var encoder2 = new TextEncoder();
|
|
10869
|
+
var AGENT_MEMORY_PROJECTION_PUBLISH_TIMEOUT_MS = 1e4;
|
|
10870
|
+
var AgentMemoryError = class extends Error {
|
|
10871
|
+
constructor(message) {
|
|
10872
|
+
super(message);
|
|
10873
|
+
this.name = "AgentMemoryError";
|
|
10874
|
+
}
|
|
10875
|
+
};
|
|
10876
|
+
var AgentMemoryRevisionConflictError = class extends AgentMemoryError {
|
|
10877
|
+
constructor(expectedRevision, actualRevision) {
|
|
10878
|
+
super(`Agent memory revision conflict: expected ${expectedRevision}, current ${actualRevision}`);
|
|
10879
|
+
this.expectedRevision = expectedRevision;
|
|
10880
|
+
this.actualRevision = actualRevision;
|
|
10881
|
+
this.name = "AgentMemoryRevisionConflictError";
|
|
10882
|
+
}
|
|
10883
|
+
expectedRevision;
|
|
10884
|
+
actualRevision;
|
|
10885
|
+
};
|
|
10886
|
+
var DRAINED_AGENT_MEMORY_PROJECTION_REPLAY = Object.freeze({ status: "drained" });
|
|
10887
|
+
var AgentMemoryProjectionReplayPendingError = class extends AgentMemoryError {
|
|
10888
|
+
constructor(outcome) {
|
|
10889
|
+
super("Agent memory projection replay remains pending");
|
|
10890
|
+
this.outcome = outcome;
|
|
10891
|
+
this.name = "AgentMemoryProjectionReplayPendingError";
|
|
10892
|
+
}
|
|
10893
|
+
outcome;
|
|
10894
|
+
};
|
|
10895
|
+
function digestBytes(content) {
|
|
10896
|
+
return `sha256:${createHash("sha256").update(content).digest("hex")}`;
|
|
10606
10897
|
}
|
|
10607
|
-
|
|
10608
|
-
|
|
10609
|
-
const rank = new Map(preference.map((id, index) => [id, index]));
|
|
10610
|
-
return [...candidates].sort((a, b) => (rank.get(a.descriptor.id) ?? preference.length) - (rank.get(b.descriptor.id) ?? preference.length));
|
|
10898
|
+
function digest2(content) {
|
|
10899
|
+
return digestBytes(encoder2.encode(content));
|
|
10611
10900
|
}
|
|
10612
|
-
function
|
|
10613
|
-
return
|
|
10901
|
+
function nonEmpty(value) {
|
|
10902
|
+
return typeof value === "string" && value.length > 0 && !/[\u0000\r\n]/u.test(value);
|
|
10614
10903
|
}
|
|
10615
|
-
function
|
|
10616
|
-
return
|
|
10904
|
+
function revision(value) {
|
|
10905
|
+
return typeof value === "string" && REVISION.test(value);
|
|
10617
10906
|
}
|
|
10618
|
-
function
|
|
10619
|
-
|
|
10620
|
-
|
|
10907
|
+
function taskContext(value) {
|
|
10908
|
+
if (!value || !nonEmpty(value.taskId) || !nonEmpty(value.tenantId) || !nonEmpty(value.deviceId) || !nonEmpty(value.sessionRef) || !nonEmpty(value.runtimeId) || !nonEmpty(value.leaseId) || !value.agentRef || !nonEmpty(value.agentRef.agentId) || !nonEmpty(value.agentRef.profileRevision) || !path3__default.isAbsolute(value.canonicalHome) || typeof value.homeIdentity?.dev !== "bigint" || typeof value.homeIdentity.ino !== "bigint") {
|
|
10909
|
+
throw new AgentMemoryError("Agent memory requires an exact active Agent task context");
|
|
10910
|
+
}
|
|
10911
|
+
return Object.freeze({ ...value, agentRef: Object.freeze({ ...value.agentRef }), homeIdentity: Object.freeze({ ...value.homeIdentity }), canonicalHome: path3__default.resolve(value.canonicalHome) });
|
|
10621
10912
|
}
|
|
10622
|
-
function
|
|
10623
|
-
|
|
10913
|
+
function validateAgentMemoryPath(value) {
|
|
10914
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 1024 || /[\u0000\\]/u.test(value)) throw new AgentMemoryError("memory path is invalid");
|
|
10915
|
+
if (value === "MEMORY.md") return value;
|
|
10916
|
+
if (path3__default.posix.isAbsolute(value) || /[*?\[{]/u.test(value)) throw new AgentMemoryError("memory path must name exactly one file");
|
|
10917
|
+
const parts = value.split("/");
|
|
10918
|
+
if (parts.length < 2 || parts[0] !== "notes" || !value.endsWith(".md") || parts.some((part) => part === "" || part === "." || part === ".." || part === ".byok" || !SAFE_SEGMENT.test(part) || SECRET_LIKE.test(part))) {
|
|
10919
|
+
throw new AgentMemoryError("memory path must be MEMORY.md or notes/<safe-relative>.md");
|
|
10920
|
+
}
|
|
10921
|
+
return value;
|
|
10624
10922
|
}
|
|
10625
|
-
|
|
10626
|
-
|
|
10627
|
-
|
|
10923
|
+
var SECURE_DIRECTORY_DESCRIPTOR_ROOT = "/proc/self/fd";
|
|
10924
|
+
function isAgentMemorySecureFilesystemAvailable(externalHelperConfigured = false) {
|
|
10925
|
+
const nativeLinux = process.platform === "linux" && typeof constants.O_NOFOLLOW === "number" && typeof constants.O_DIRECTORY === "number" && typeof constants.O_NONBLOCK === "number" && existsSync(SECURE_DIRECTORY_DESCRIPTOR_ROOT);
|
|
10926
|
+
return nativeLinux || externalHelperConfigured && process.platform === "darwin";
|
|
10628
10927
|
}
|
|
10629
|
-
function
|
|
10630
|
-
if (!
|
|
10631
|
-
|
|
10632
|
-
|
|
10928
|
+
function requireSecureDirectoryDescriptors() {
|
|
10929
|
+
if (!isAgentMemorySecureFilesystemAvailable(false) || process.platform !== "linux") {
|
|
10930
|
+
throw new AgentMemoryError("Agent memory is unavailable because this Node platform lacks safe descriptor-relative filesystem operations");
|
|
10931
|
+
}
|
|
10932
|
+
return SECURE_DIRECTORY_DESCRIPTOR_ROOT;
|
|
10633
10933
|
}
|
|
10634
|
-
function
|
|
10635
|
-
|
|
10934
|
+
function noFollowFlags(base) {
|
|
10935
|
+
requireSecureDirectoryDescriptors();
|
|
10936
|
+
return base | constants.O_NOFOLLOW;
|
|
10636
10937
|
}
|
|
10637
|
-
function
|
|
10638
|
-
return
|
|
10639
|
-
let settled = false;
|
|
10640
|
-
const timer = setTimeout(() => {
|
|
10641
|
-
if (!settled) {
|
|
10642
|
-
settled = true;
|
|
10643
|
-
resolve(false);
|
|
10644
|
-
}
|
|
10645
|
-
}, timeoutMs);
|
|
10646
|
-
timer.unref?.();
|
|
10647
|
-
void (async () => {
|
|
10648
|
-
try {
|
|
10649
|
-
await fn();
|
|
10650
|
-
} catch {
|
|
10651
|
-
}
|
|
10652
|
-
if (!settled) {
|
|
10653
|
-
settled = true;
|
|
10654
|
-
clearTimeout(timer);
|
|
10655
|
-
resolve(true);
|
|
10656
|
-
}
|
|
10657
|
-
})();
|
|
10658
|
-
});
|
|
10938
|
+
function descriptorPath(handle) {
|
|
10939
|
+
return `${requireSecureDirectoryDescriptors()}/${handle.fd}`;
|
|
10659
10940
|
}
|
|
10660
|
-
function
|
|
10941
|
+
async function openPinnedDirectory(target, expectedIdentity) {
|
|
10942
|
+
requireSecureDirectoryDescriptors();
|
|
10943
|
+
let handle;
|
|
10661
10944
|
try {
|
|
10662
|
-
|
|
10663
|
-
|
|
10664
|
-
|
|
10945
|
+
handle = await promises.open(target, noFollowFlags(constants.O_RDONLY | constants.O_DIRECTORY));
|
|
10946
|
+
const stat = await handle.stat({ bigint: true });
|
|
10947
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || expectedIdentity !== void 0 && (stat.dev !== expectedIdentity.dev || stat.ino !== expectedIdentity.ino)) throw new AgentMemoryError("memory directory is not a real directory");
|
|
10948
|
+
return handle;
|
|
10949
|
+
} catch (error) {
|
|
10950
|
+
await handle?.close().catch(() => {
|
|
10951
|
+
});
|
|
10952
|
+
if (error instanceof AgentMemoryError) throw error;
|
|
10953
|
+
throw new AgentMemoryError("memory directory is unavailable or unsafe");
|
|
10665
10954
|
}
|
|
10666
10955
|
}
|
|
10667
|
-
async function
|
|
10668
|
-
const
|
|
10669
|
-
const candidate = path3__default.resolve(realWorkspaceDir, name);
|
|
10670
|
-
const prefix = realWorkspaceDir.endsWith(path3__default.sep) ? realWorkspaceDir : realWorkspaceDir + path3__default.sep;
|
|
10671
|
-
if (candidate !== realWorkspaceDir && !candidate.startsWith(prefix)) {
|
|
10672
|
-
return { ok: false, reason: `artifact name "${name}" resolves outside the task workspace \u2014 rejected` };
|
|
10673
|
-
}
|
|
10674
|
-
let realCandidate = candidate;
|
|
10956
|
+
async function withPinnedDirectory(home, parts, operation, expectedHomeIdentity) {
|
|
10957
|
+
const handles = [];
|
|
10675
10958
|
try {
|
|
10676
|
-
|
|
10677
|
-
|
|
10678
|
-
|
|
10679
|
-
|
|
10680
|
-
|
|
10959
|
+
let directory = await openPinnedDirectory(home, expectedHomeIdentity);
|
|
10960
|
+
handles.push(directory);
|
|
10961
|
+
for (const part of parts) {
|
|
10962
|
+
directory = await openPinnedDirectory(`${descriptorPath(directory)}/${part}`);
|
|
10963
|
+
handles.push(directory);
|
|
10964
|
+
}
|
|
10965
|
+
return await operation(directory);
|
|
10966
|
+
} finally {
|
|
10967
|
+
await Promise.all(handles.reverse().map((handle) => handle.close().catch(() => {
|
|
10968
|
+
})));
|
|
10681
10969
|
}
|
|
10682
|
-
|
|
10970
|
+
}
|
|
10971
|
+
async function withMemoryParent(context, relativePath, operation) {
|
|
10972
|
+
const parts = relativePath.split("/");
|
|
10973
|
+
const fileName = parts.pop();
|
|
10974
|
+
if (fileName === void 0 || parts.some((part) => !SAFE_SEGMENT.test(part) && part !== ".byok")) throw new AgentMemoryError("memory path is invalid");
|
|
10975
|
+
return withPinnedDirectory(context.canonicalHome, parts, (directory) => operation(directory, fileName), context.homeIdentity);
|
|
10976
|
+
}
|
|
10977
|
+
async function readPinnedFile(directory, fileName, maxBytes = AGENT_MEMORY_MAX_FILE_BYTES) {
|
|
10683
10978
|
let handle;
|
|
10684
10979
|
try {
|
|
10685
|
-
handle = await promises.open(
|
|
10686
|
-
|
|
10687
|
-
|
|
10980
|
+
handle = await promises.open(
|
|
10981
|
+
`${descriptorPath(directory)}/${fileName}`,
|
|
10982
|
+
noFollowFlags(constants.O_RDONLY | constants.O_NONBLOCK)
|
|
10983
|
+
);
|
|
10984
|
+
} catch (error) {
|
|
10985
|
+
if (error.code === "ENOENT") return Object.freeze({ exists: false, content: "", revision: digest2(""), byteCount: 0 });
|
|
10986
|
+
throw new AgentMemoryError("could not open memory file");
|
|
10987
|
+
}
|
|
10988
|
+
try {
|
|
10989
|
+
const before = await handle.stat({ bigint: true });
|
|
10990
|
+
if (!before.isFile() || before.isSymbolicLink() || before.size > BigInt(maxBytes)) throw new AgentMemoryError("memory file is not a bounded regular file");
|
|
10991
|
+
const bytes = Buffer.alloc(Number(before.size));
|
|
10992
|
+
let offset = 0;
|
|
10993
|
+
while (offset < bytes.length) {
|
|
10994
|
+
const read = await handle.read(bytes, offset, bytes.length - offset, offset);
|
|
10995
|
+
if (read.bytesRead === 0) break;
|
|
10996
|
+
offset += read.bytesRead;
|
|
10997
|
+
}
|
|
10998
|
+
const after = await handle.stat({ bigint: true });
|
|
10999
|
+
if (offset !== bytes.length || after.size !== before.size || after.mtimeNs !== before.mtimeNs || after.ino !== before.ino) throw new AgentMemoryError("memory file changed during read");
|
|
11000
|
+
const content = bytes.toString("utf8");
|
|
11001
|
+
if (!Buffer.from(content, "utf8").equals(bytes)) throw new AgentMemoryError("memory file is not valid UTF-8");
|
|
11002
|
+
return Object.freeze({ exists: true, content, revision: digestBytes(bytes), byteCount: bytes.length });
|
|
11003
|
+
} finally {
|
|
11004
|
+
await handle.close().catch(() => {
|
|
11005
|
+
});
|
|
11006
|
+
}
|
|
11007
|
+
}
|
|
11008
|
+
async function readFile(context, relativePath, maxBytes = AGENT_MEMORY_MAX_FILE_BYTES) {
|
|
11009
|
+
if (context.filesystem !== void 0) return context.filesystem.read(relativePath, maxBytes);
|
|
11010
|
+
return withMemoryParent(context, relativePath, (directory, fileName) => readPinnedFile(directory, fileName, maxBytes));
|
|
11011
|
+
}
|
|
11012
|
+
async function syncDirectory(directory) {
|
|
11013
|
+
try {
|
|
11014
|
+
await directory.sync();
|
|
11015
|
+
} catch (error) {
|
|
11016
|
+
if (!["EINVAL", "EPERM"].includes(error.code ?? "")) throw error;
|
|
11017
|
+
}
|
|
11018
|
+
}
|
|
11019
|
+
async function replaceNative(context, relativePath, expected, content) {
|
|
11020
|
+
const byteCount = encoder2.encode(content).byteLength;
|
|
11021
|
+
if (byteCount > AGENT_MEMORY_MAX_FILE_BYTES) throw new AgentMemoryError("memory content exceeds its bounded file size");
|
|
11022
|
+
return withMemoryParent(context, relativePath, async (directory, fileName) => {
|
|
11023
|
+
const before = await readPinnedFile(directory, fileName);
|
|
11024
|
+
if (before.revision !== expected) throw new AgentMemoryRevisionConflictError(expected, before.revision);
|
|
11025
|
+
const parent = descriptorPath(directory);
|
|
11026
|
+
const temporary = `${parent}/.byok-memory-${randomUUID()}.tmp`;
|
|
11027
|
+
let handle;
|
|
11028
|
+
try {
|
|
11029
|
+
const check = await readPinnedFile(directory, fileName);
|
|
11030
|
+
if (check.revision !== expected || check.exists !== before.exists) throw new AgentMemoryRevisionConflictError(expected, check.revision);
|
|
11031
|
+
handle = await promises.open(temporary, noFollowFlags(constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL), 384);
|
|
11032
|
+
await handle.writeFile(content, "utf8");
|
|
11033
|
+
await handle.sync();
|
|
11034
|
+
await handle.close();
|
|
11035
|
+
handle = void 0;
|
|
11036
|
+
const finalCheck = await readPinnedFile(directory, fileName);
|
|
11037
|
+
if (finalCheck.revision !== expected || finalCheck.exists !== before.exists) throw new AgentMemoryRevisionConflictError(expected, finalCheck.revision);
|
|
11038
|
+
await promises.rename(temporary, `${parent}/${fileName}`);
|
|
11039
|
+
await syncDirectory(directory);
|
|
11040
|
+
return Object.freeze({ exists: true, content, revision: digest2(content), byteCount });
|
|
11041
|
+
} catch (error) {
|
|
11042
|
+
await handle?.close().catch(() => {
|
|
11043
|
+
});
|
|
11044
|
+
await promises.rm(temporary, { force: true }).catch(() => {
|
|
11045
|
+
});
|
|
11046
|
+
if (error instanceof AgentMemoryError) throw error;
|
|
11047
|
+
throw new AgentMemoryError("could not atomically replace memory file");
|
|
11048
|
+
}
|
|
11049
|
+
});
|
|
11050
|
+
}
|
|
11051
|
+
async function replace(context, relativePath, expected, content) {
|
|
11052
|
+
if (context.filesystem !== void 0) return context.filesystem.replace(relativePath, expected, content, AGENT_MEMORY_MAX_FILE_BYTES);
|
|
11053
|
+
return replaceNative(context, relativePath, expected, content);
|
|
11054
|
+
}
|
|
11055
|
+
async function removeNative(context, relativePath, expected) {
|
|
11056
|
+
if (relativePath === "MEMORY.md") throw new AgentMemoryError("MEMORY.md may not be deleted");
|
|
11057
|
+
await withMemoryParent(context, relativePath, async (directory, fileName) => {
|
|
11058
|
+
const before = await readPinnedFile(directory, fileName);
|
|
11059
|
+
if (!before.exists || before.revision !== expected) throw new AgentMemoryRevisionConflictError(expected, before.revision);
|
|
11060
|
+
const parent = descriptorPath(directory);
|
|
11061
|
+
const tombstone = `${parent}/.byok-memory-delete-${randomUUID()}.tmp`;
|
|
11062
|
+
try {
|
|
11063
|
+
const check = await readPinnedFile(directory, fileName);
|
|
11064
|
+
if (!check.exists || check.revision !== expected) throw new AgentMemoryRevisionConflictError(expected, check.revision);
|
|
11065
|
+
await promises.rename(`${parent}/${fileName}`, tombstone);
|
|
11066
|
+
await syncDirectory(directory);
|
|
11067
|
+
await promises.rm(tombstone);
|
|
11068
|
+
await syncDirectory(directory);
|
|
11069
|
+
} catch (error) {
|
|
11070
|
+
if (error instanceof AgentMemoryError) throw error;
|
|
11071
|
+
throw new AgentMemoryError("could not atomically delete memory file");
|
|
11072
|
+
}
|
|
11073
|
+
});
|
|
11074
|
+
}
|
|
11075
|
+
async function remove(context, relativePath, expected) {
|
|
11076
|
+
if (relativePath === "MEMORY.md") throw new AgentMemoryError("MEMORY.md may not be deleted");
|
|
11077
|
+
if (context.filesystem !== void 0) return context.filesystem.delete(relativePath, expected);
|
|
11078
|
+
return removeNative(context, relativePath, expected);
|
|
11079
|
+
}
|
|
11080
|
+
async function readInternalFile(context, fileName) {
|
|
11081
|
+
const relativePath = `${AGENT_HOME_INTERNAL_DIRECTORY}/${fileName}`;
|
|
11082
|
+
if (context.filesystem !== void 0) return context.filesystem.read(relativePath, AGENT_MEMORY_MAX_LOCAL_LOG_BYTES);
|
|
11083
|
+
return withPinnedDirectory(
|
|
11084
|
+
context.canonicalHome,
|
|
11085
|
+
[AGENT_HOME_INTERNAL_DIRECTORY],
|
|
11086
|
+
(directory) => readPinnedFile(directory, fileName, AGENT_MEMORY_MAX_LOCAL_LOG_BYTES),
|
|
11087
|
+
context.homeIdentity
|
|
11088
|
+
);
|
|
11089
|
+
}
|
|
11090
|
+
async function replaceInternalFile(context, fileName, expectedRevision, content) {
|
|
11091
|
+
const byteCount = encoder2.encode(content).byteLength;
|
|
11092
|
+
if (byteCount > AGENT_MEMORY_MAX_LOCAL_LOG_BYTES) throw new AgentMemoryError("Agent memory internal state exceeds its bounded size");
|
|
11093
|
+
const relativePath = `${AGENT_HOME_INTERNAL_DIRECTORY}/${fileName}`;
|
|
11094
|
+
if (context.filesystem !== void 0) return context.filesystem.replace(relativePath, expectedRevision, content, AGENT_MEMORY_MAX_LOCAL_LOG_BYTES);
|
|
11095
|
+
return withPinnedDirectory(context.canonicalHome, [AGENT_HOME_INTERNAL_DIRECTORY], async (directory) => {
|
|
11096
|
+
const before = await readPinnedFile(directory, fileName, AGENT_MEMORY_MAX_LOCAL_LOG_BYTES);
|
|
11097
|
+
if (before.revision !== expectedRevision) throw new AgentMemoryRevisionConflictError(expectedRevision, before.revision);
|
|
11098
|
+
const parent = descriptorPath(directory);
|
|
11099
|
+
const temporary = `${parent}/.byok-agent-memory-${randomUUID()}.tmp`;
|
|
11100
|
+
let handle;
|
|
11101
|
+
try {
|
|
11102
|
+
handle = await promises.open(temporary, noFollowFlags(constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL), 384);
|
|
11103
|
+
await handle.writeFile(content, "utf8");
|
|
11104
|
+
await handle.sync();
|
|
11105
|
+
await handle.close();
|
|
11106
|
+
handle = void 0;
|
|
11107
|
+
const check = await readPinnedFile(directory, fileName, AGENT_MEMORY_MAX_LOCAL_LOG_BYTES);
|
|
11108
|
+
if (check.revision !== expectedRevision || check.exists !== before.exists) throw new AgentMemoryRevisionConflictError(expectedRevision, check.revision);
|
|
11109
|
+
await promises.rename(temporary, `${parent}/${fileName}`);
|
|
11110
|
+
await syncDirectory(directory);
|
|
11111
|
+
return Object.freeze({ exists: true, content, revision: digest2(content), byteCount });
|
|
11112
|
+
} catch (error) {
|
|
11113
|
+
await handle?.close().catch(() => {
|
|
11114
|
+
});
|
|
11115
|
+
await promises.rm(temporary, { force: true }).catch(() => {
|
|
11116
|
+
});
|
|
11117
|
+
if (error instanceof AgentMemoryError) throw error;
|
|
11118
|
+
throw new AgentMemoryError("could not atomically replace Agent memory internal state");
|
|
11119
|
+
}
|
|
11120
|
+
}, context.homeIdentity);
|
|
11121
|
+
}
|
|
11122
|
+
function boundedAuditTail(previous, entry) {
|
|
11123
|
+
if (encoder2.encode(entry).byteLength > AGENT_MEMORY_MAX_LOCAL_LOG_BYTES) throw new AgentMemoryError("Agent memory audit entry exceeds its bounded size");
|
|
11124
|
+
const lines = previous.split("\n").filter((line) => line.length > 0);
|
|
11125
|
+
const kept = [entry];
|
|
11126
|
+
let byteCount = encoder2.encode(entry).byteLength;
|
|
11127
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
11128
|
+
const candidate = `${lines[index]}
|
|
11129
|
+
`;
|
|
11130
|
+
const candidateBytes = encoder2.encode(candidate).byteLength;
|
|
11131
|
+
if (byteCount + candidateBytes > AGENT_MEMORY_MAX_LOCAL_LOG_BYTES) break;
|
|
11132
|
+
kept.unshift(candidate);
|
|
11133
|
+
byteCount += candidateBytes;
|
|
11134
|
+
}
|
|
11135
|
+
return kept.join("");
|
|
11136
|
+
}
|
|
11137
|
+
async function audit(context, kind, values) {
|
|
11138
|
+
const entry = `${JSON.stringify({ version: 1, kind, taskId: context.taskId, tenantId: context.tenantId, deviceId: context.deviceId, agentRef: context.agentRef, sessionRef: context.sessionRef, runtimeId: context.runtimeId, ...values, recordedAt: (/* @__PURE__ */ new Date()).toISOString() })}
|
|
11139
|
+
`;
|
|
11140
|
+
const current = await readInternalFile(context, AGENT_MEMORY_AUDIT_FILENAME);
|
|
11141
|
+
await replaceInternalFile(context, AGENT_MEMORY_AUDIT_FILENAME, current.revision, boundedAuditTail(current.content, entry));
|
|
11142
|
+
}
|
|
11143
|
+
async function recordAuditWarning(context, kind, values) {
|
|
11144
|
+
try {
|
|
11145
|
+
await audit(context, kind, values);
|
|
11146
|
+
return void 0;
|
|
11147
|
+
} catch {
|
|
11148
|
+
return Object.freeze({ code: "agent_memory_audit_unavailable" });
|
|
11149
|
+
}
|
|
11150
|
+
}
|
|
11151
|
+
var agentMemoryHomeQueues = /* @__PURE__ */ new Map();
|
|
11152
|
+
async function exclusiveAgentMemoryHome(home, fn) {
|
|
11153
|
+
const previous = agentMemoryHomeQueues.get(home) ?? Promise.resolve();
|
|
11154
|
+
let release;
|
|
11155
|
+
const next = new Promise((resolve) => {
|
|
11156
|
+
release = resolve;
|
|
11157
|
+
});
|
|
11158
|
+
agentMemoryHomeQueues.set(home, next);
|
|
11159
|
+
await previous;
|
|
11160
|
+
try {
|
|
11161
|
+
return await fn();
|
|
11162
|
+
} finally {
|
|
11163
|
+
release();
|
|
11164
|
+
if (agentMemoryHomeQueues.get(home) === next) agentMemoryHomeQueues.delete(home);
|
|
11165
|
+
}
|
|
11166
|
+
}
|
|
11167
|
+
var AgentMemoryService = class {
|
|
11168
|
+
constructor(input) {
|
|
11169
|
+
this.input = input;
|
|
11170
|
+
}
|
|
11171
|
+
input;
|
|
11172
|
+
async recall(input) {
|
|
11173
|
+
const context = taskContext(this.input);
|
|
11174
|
+
const relativePath = validateAgentMemoryPath(input.path);
|
|
11175
|
+
if (input.ifRevision !== void 0 && !revision(input.ifRevision)) throw new AgentMemoryError("ifRevision must be a sha256 content revision");
|
|
11176
|
+
const current = await readFile(context, relativePath);
|
|
11177
|
+
if (!current.exists) throw new AgentMemoryError("memory file does not exist");
|
|
11178
|
+
if (input.ifRevision !== void 0 && current.revision !== input.ifRevision) throw new AgentMemoryRevisionConflictError(input.ifRevision, current.revision);
|
|
11179
|
+
const auditWarning = await exclusiveAgentMemoryHome(context.canonicalHome, () => recordAuditWarning(context, "recall", {
|
|
11180
|
+
path: relativePath,
|
|
11181
|
+
revision: current.revision,
|
|
11182
|
+
byteCount: current.byteCount
|
|
11183
|
+
}));
|
|
11184
|
+
return Object.freeze({
|
|
11185
|
+
path: relativePath,
|
|
11186
|
+
revision: current.revision,
|
|
11187
|
+
content: current.content,
|
|
11188
|
+
...auditWarning === void 0 ? {} : { auditWarning }
|
|
11189
|
+
});
|
|
11190
|
+
}
|
|
11191
|
+
async save(input) {
|
|
11192
|
+
const context = taskContext(this.input);
|
|
11193
|
+
const relativePath = validateAgentMemoryPath(input.path);
|
|
11194
|
+
if (input.op !== "replace" && input.op !== "delete" || !revision(input.expectedRevision)) throw new AgentMemoryError("memory save requires op and sha256 expectedRevision");
|
|
11195
|
+
if (input.op === "replace" && typeof input.content !== "string") throw new AgentMemoryError("replace requires string content");
|
|
11196
|
+
if (input.op === "delete" && input.content !== void 0) throw new AgentMemoryError("delete does not accept content");
|
|
11197
|
+
const expectedRevision = input.expectedRevision;
|
|
11198
|
+
const content = input.content;
|
|
11199
|
+
return exclusiveAgentMemoryHome(context.canonicalHome, async () => {
|
|
11200
|
+
if (input.op === "delete") {
|
|
11201
|
+
await remove(context, relativePath, expectedRevision);
|
|
11202
|
+
const auditWarning2 = await recordAuditWarning(context, "save", { path: relativePath, operation: "delete" });
|
|
11203
|
+
return Object.freeze({ path: relativePath, deleted: true, ...auditWarning2 === void 0 ? {} : { auditWarning: auditWarning2 } });
|
|
11204
|
+
}
|
|
11205
|
+
if (typeof content !== "string") throw new AgentMemoryError("replace requires string content");
|
|
11206
|
+
const current = await replace(context, relativePath, expectedRevision, content);
|
|
11207
|
+
const auditWarning = await recordAuditWarning(context, "save", { path: relativePath, operation: "replace", revision: current.revision, byteCount: current.byteCount });
|
|
11208
|
+
return Object.freeze({ path: relativePath, revision: current.revision, deleted: false, ...auditWarning === void 0 ? {} : { auditWarning } });
|
|
11209
|
+
});
|
|
11210
|
+
}
|
|
11211
|
+
};
|
|
11212
|
+
async function memoryNotePaths(context) {
|
|
11213
|
+
if (context.filesystem !== void 0) {
|
|
11214
|
+
const paths = await context.filesystem.walk("notes", AGENT_MEMORY_MAX_SNAPSHOT_ENTRIES);
|
|
11215
|
+
const candidates2 = [];
|
|
11216
|
+
for (const candidate of paths) {
|
|
11217
|
+
try {
|
|
11218
|
+
candidates2.push(validateAgentMemoryPath(candidate));
|
|
11219
|
+
} catch {
|
|
11220
|
+
}
|
|
11221
|
+
}
|
|
11222
|
+
return Object.freeze(candidates2);
|
|
11223
|
+
}
|
|
11224
|
+
const candidates = [];
|
|
11225
|
+
let entriesSeen = 0;
|
|
11226
|
+
async function walk(directory, relativeDirectory) {
|
|
11227
|
+
const entries = await promises.readdir(descriptorPath(directory), { withFileTypes: true });
|
|
11228
|
+
for (const entry of entries) {
|
|
11229
|
+
entriesSeen += 1;
|
|
11230
|
+
if (entriesSeen > AGENT_MEMORY_MAX_SNAPSHOT_ENTRIES) throw new AgentMemoryError("memory snapshot exceeds bounded directory entries");
|
|
11231
|
+
const candidate = `${relativeDirectory}/${entry.name}`;
|
|
11232
|
+
if (entry.isSymbolicLink()) throw new AgentMemoryError("memory notes contains a symlink");
|
|
11233
|
+
if (entry.isDirectory()) {
|
|
11234
|
+
if (SAFE_SEGMENT.test(entry.name) && !SECRET_LIKE.test(entry.name) && entry.name !== ".byok") {
|
|
11235
|
+
const nested = await openPinnedDirectory(`${descriptorPath(directory)}/${entry.name}`);
|
|
11236
|
+
try {
|
|
11237
|
+
await walk(nested, candidate);
|
|
11238
|
+
} finally {
|
|
11239
|
+
await nested.close().catch(() => {
|
|
11240
|
+
});
|
|
11241
|
+
}
|
|
11242
|
+
}
|
|
11243
|
+
continue;
|
|
11244
|
+
}
|
|
11245
|
+
if (!entry.isFile()) continue;
|
|
11246
|
+
try {
|
|
11247
|
+
candidates.push(validateAgentMemoryPath(candidate));
|
|
11248
|
+
} catch {
|
|
11249
|
+
}
|
|
11250
|
+
}
|
|
11251
|
+
}
|
|
11252
|
+
await withPinnedDirectory(context.canonicalHome, ["notes"], (directory) => walk(directory, "notes"), context.homeIdentity);
|
|
11253
|
+
return Object.freeze(candidates);
|
|
11254
|
+
}
|
|
11255
|
+
async function captureAgentMemorySnapshot(input) {
|
|
11256
|
+
const context = taskContext(input);
|
|
11257
|
+
const candidates = ["MEMORY.md", ...await memoryNotePaths(context)];
|
|
11258
|
+
const paths = [...new Set(candidates)].sort((a, b) => a.localeCompare(b));
|
|
11259
|
+
if (paths.length > AGENT_MEMORY_MAX_SNAPSHOT_FILES) throw new AgentMemoryError("memory snapshot exceeds bounded file count");
|
|
11260
|
+
const files = [];
|
|
11261
|
+
let totalBytes = 0;
|
|
11262
|
+
for (const relativePath of paths) {
|
|
11263
|
+
const current = await readFile(context, relativePath);
|
|
11264
|
+
if (!current.exists) {
|
|
11265
|
+
if (relativePath === "MEMORY.md") throw new AgentMemoryError("MEMORY.md disappeared before snapshot");
|
|
11266
|
+
continue;
|
|
11267
|
+
}
|
|
11268
|
+
totalBytes += current.byteCount;
|
|
11269
|
+
if (totalBytes > AGENT_MEMORY_MAX_SNAPSHOT_BYTES) throw new AgentMemoryError("memory snapshot exceeds bounded total size");
|
|
11270
|
+
files.push(Object.freeze({ path: relativePath, revision: current.revision, byteCount: current.byteCount, content: current.content }));
|
|
11271
|
+
}
|
|
11272
|
+
const snapshot = Object.freeze({ files: Object.freeze(files), totalBytes });
|
|
11273
|
+
await exclusiveAgentMemoryHome(context.canonicalHome, () => audit(context, "snapshot", {
|
|
11274
|
+
files: files.map((file) => ({ path: file.path, revision: file.revision, byteCount: file.byteCount }))
|
|
11275
|
+
}));
|
|
11276
|
+
return snapshot;
|
|
11277
|
+
}
|
|
11278
|
+
function bytesEqual(left, right) {
|
|
11279
|
+
if (left.byteLength !== right.byteLength) return false;
|
|
11280
|
+
return left.every((value, index) => value === right[index]);
|
|
11281
|
+
}
|
|
11282
|
+
function rawSnapshotBytes(snapshot) {
|
|
11283
|
+
return encoder2.encode(JSON.stringify({
|
|
11284
|
+
files: snapshot.files.map((file) => ({ path: file.path, revision: file.revision, byteCount: file.byteCount, content: file.content }))
|
|
11285
|
+
}));
|
|
11286
|
+
}
|
|
11287
|
+
function redactedBytes(raw, candidate) {
|
|
11288
|
+
if (!(candidate instanceof Uint8Array)) throw new AgentMemoryError("redactor must return Uint8Array bytes");
|
|
11289
|
+
const bytes = candidate.slice();
|
|
11290
|
+
if (bytes.byteLength > AGENT_MEMORY_PROJECTION_MAX_REDACTED_BYTES) throw new AgentMemoryError("redacted snapshot exceeds bounded projection bytes");
|
|
11291
|
+
if (bytesEqual(bytes, rawSnapshotBytes(raw))) throw new AgentMemoryError("identity/pass-through redactor is forbidden");
|
|
11292
|
+
return bytes;
|
|
11293
|
+
}
|
|
11294
|
+
function isPlainRecord(value) {
|
|
11295
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11296
|
+
}
|
|
11297
|
+
function exactKeys(value, keys) {
|
|
11298
|
+
const actual = Object.keys(value).sort();
|
|
11299
|
+
const expected = [...keys].sort();
|
|
11300
|
+
return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
|
|
11301
|
+
}
|
|
11302
|
+
function validOrderingValue(value, allowZero = false) {
|
|
11303
|
+
return typeof value === "number" && Number.isInteger(value) && value >= (allowZero ? 0 : 1) && value <= AGENT_MEMORY_PROJECTION_MAX_ORDERING_VALUE;
|
|
11304
|
+
}
|
|
11305
|
+
function freezeOutboxRecord(value) {
|
|
11306
|
+
if (!isPlainRecord(value) || !exactKeys(value, ["version", "mutation", "createdAt"]) || value.version !== 2 || typeof value.createdAt !== "string" || value.createdAt.length === 0 || /[\u0000\r\n]/u.test(value.createdAt)) throw new AgentMemoryError("Agent memory outbox state is invalid");
|
|
11307
|
+
const mutation = AgentMemoryProjectionMutationSchema.safeParse(value.mutation);
|
|
11308
|
+
if (!mutation.success) throw new AgentMemoryError("Agent memory outbox state is invalid");
|
|
11309
|
+
return Object.freeze({ version: 2, mutation: Object.freeze(mutation.data), createdAt: value.createdAt });
|
|
11310
|
+
}
|
|
11311
|
+
function freezeOutboxState(value) {
|
|
11312
|
+
if (!isPlainRecord(value) || !exactKeys(value, ["version", "currentWriterEpoch", "highWater", "pending"]) || value.version !== 2 || !validOrderingValue(value.currentWriterEpoch) || !Array.isArray(value.highWater) || !Array.isArray(value.pending)) throw new AgentMemoryError("Agent memory outbox state is invalid");
|
|
11313
|
+
if (value.highWater.length > 1 || value.pending.length > 1) throw new AgentMemoryError("Agent memory outbox state is invalid");
|
|
11314
|
+
const highWater = value.highWater.map((entry) => {
|
|
11315
|
+
if (!isPlainRecord(entry) || !exactKeys(entry, ["writerEpoch", "sourceSeq"]) || !validOrderingValue(entry.writerEpoch) || !validOrderingValue(entry.sourceSeq, true)) throw new AgentMemoryError("Agent memory outbox state is invalid");
|
|
11316
|
+
if (entry.writerEpoch !== value.currentWriterEpoch) throw new AgentMemoryError("Agent memory outbox state is invalid");
|
|
11317
|
+
return Object.freeze({ writerEpoch: entry.writerEpoch, sourceSeq: entry.sourceSeq });
|
|
11318
|
+
}).sort((left, right) => left.writerEpoch - right.writerEpoch);
|
|
11319
|
+
const pending = value.pending.map(freezeOutboxRecord).sort((left, right) => left.mutation.sourceSeq - right.mutation.sourceSeq);
|
|
11320
|
+
const seenEpochs = /* @__PURE__ */ new Set();
|
|
11321
|
+
const highWaterByEpoch = /* @__PURE__ */ new Map();
|
|
11322
|
+
for (const entry of highWater) {
|
|
11323
|
+
if (seenEpochs.has(entry.writerEpoch)) throw new AgentMemoryError("Agent memory outbox state is invalid");
|
|
11324
|
+
seenEpochs.add(entry.writerEpoch);
|
|
11325
|
+
highWaterByEpoch.set(entry.writerEpoch, entry.sourceSeq);
|
|
11326
|
+
}
|
|
11327
|
+
const mutationIds = /* @__PURE__ */ new Set();
|
|
11328
|
+
const sourceSeqs = /* @__PURE__ */ new Set();
|
|
11329
|
+
for (const record of pending) {
|
|
11330
|
+
if (record.mutation.writerEpoch !== value.currentWriterEpoch || mutationIds.has(record.mutation.mutationId) || sourceSeqs.has(record.mutation.sourceSeq) || record.mutation.sourceSeq > (highWaterByEpoch.get(record.mutation.writerEpoch) ?? 0)) throw new AgentMemoryError("Agent memory outbox state is invalid");
|
|
11331
|
+
mutationIds.add(record.mutation.mutationId);
|
|
11332
|
+
sourceSeqs.add(record.mutation.sourceSeq);
|
|
11333
|
+
}
|
|
11334
|
+
return Object.freeze({ version: 2, currentWriterEpoch: value.currentWriterEpoch, highWater: Object.freeze(highWater), pending: Object.freeze(pending) });
|
|
11335
|
+
}
|
|
11336
|
+
function outboxStateJson(state) {
|
|
11337
|
+
return JSON.stringify({ version: state.version, currentWriterEpoch: state.currentWriterEpoch, highWater: state.highWater, pending: state.pending });
|
|
11338
|
+
}
|
|
11339
|
+
function initialOutboxState(writerEpoch) {
|
|
11340
|
+
return freezeOutboxState({ version: 2, currentWriterEpoch: writerEpoch, highWater: [], pending: [] });
|
|
11341
|
+
}
|
|
11342
|
+
function stateWithHighWater(state, writerEpoch, sourceSeq, pending) {
|
|
11343
|
+
if (writerEpoch !== state.currentWriterEpoch) throw new AgentMemoryError("Agent memory outbox state is invalid");
|
|
11344
|
+
return freezeOutboxState({ version: 2, currentWriterEpoch: state.currentWriterEpoch, highWater: [Object.freeze({ writerEpoch, sourceSeq })], pending });
|
|
11345
|
+
}
|
|
11346
|
+
async function publishAgentMemoryProjection(port, mutation) {
|
|
11347
|
+
let timer;
|
|
11348
|
+
try {
|
|
11349
|
+
return await new Promise((resolve, reject) => {
|
|
11350
|
+
timer = setTimeout(
|
|
11351
|
+
() => reject(new AgentMemoryError("Agent memory projection publish timed out")),
|
|
11352
|
+
AGENT_MEMORY_PROJECTION_PUBLISH_TIMEOUT_MS
|
|
11353
|
+
);
|
|
11354
|
+
try {
|
|
11355
|
+
Promise.resolve(port.publish(Object.freeze({ mutation }))).then(resolve, reject);
|
|
11356
|
+
} catch (error) {
|
|
11357
|
+
reject(error);
|
|
11358
|
+
}
|
|
11359
|
+
});
|
|
11360
|
+
} finally {
|
|
11361
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
11362
|
+
}
|
|
11363
|
+
}
|
|
11364
|
+
var AgentMemoryRedactedOutbox = class _AgentMemoryRedactedOutbox {
|
|
11365
|
+
constructor(context, grant, filePath) {
|
|
11366
|
+
this.context = context;
|
|
11367
|
+
this.grant = grant;
|
|
11368
|
+
this.filePath = filePath;
|
|
11369
|
+
}
|
|
11370
|
+
context;
|
|
11371
|
+
grant;
|
|
11372
|
+
filePath;
|
|
11373
|
+
state;
|
|
11374
|
+
fileRevision;
|
|
11375
|
+
writeTail = Promise.resolve();
|
|
11376
|
+
static async open(input, grant) {
|
|
11377
|
+
const context = taskContext(input);
|
|
11378
|
+
const outbox = new _AgentMemoryRedactedOutbox(context, grant, path3__default.join(context.canonicalHome, AGENT_HOME_INTERNAL_DIRECTORY, AGENT_MEMORY_OUTBOX_FILENAME));
|
|
11379
|
+
await outbox.load();
|
|
11380
|
+
await outbox.admitGrant();
|
|
11381
|
+
return outbox;
|
|
11382
|
+
}
|
|
11383
|
+
pending() {
|
|
11384
|
+
return this.state.pending;
|
|
11385
|
+
}
|
|
11386
|
+
async append(bytes) {
|
|
11387
|
+
return this.exclusive(async () => {
|
|
11388
|
+
if (this.state.currentWriterEpoch !== this.grant.writerEpoch) throw new AgentMemoryError("Agent memory outbox writer epoch is stale");
|
|
11389
|
+
if (this.state.pending.length > 0) throw new AgentMemoryError("Agent memory outbox has pending projection mutations");
|
|
11390
|
+
const currentHighWater = this.state.highWater[0]?.sourceSeq ?? 0;
|
|
11391
|
+
if (currentHighWater >= AGENT_MEMORY_PROJECTION_MAX_ORDERING_VALUE) throw new AgentMemoryError("Agent memory outbox source sequence is exhausted");
|
|
11392
|
+
const sourceSeq = currentHighWater + 1;
|
|
11393
|
+
const mutation = AgentMemoryProjectionMutationSchema.parse({
|
|
11394
|
+
taskId: this.context.taskId,
|
|
11395
|
+
agentRef: this.context.agentRef,
|
|
11396
|
+
sessionRef: this.context.sessionRef,
|
|
11397
|
+
runtimeId: this.context.runtimeId,
|
|
11398
|
+
grantRef: this.grant.grantRef,
|
|
11399
|
+
writerEpoch: this.grant.writerEpoch,
|
|
11400
|
+
sourceSeq,
|
|
11401
|
+
mutationId: randomUUID(),
|
|
11402
|
+
policyRevision: this.grant.policyRevision,
|
|
11403
|
+
snapshot: { redactedHash: digestBytes(bytes), redactedByteCount: bytes.byteLength, redactedBytes: Buffer.from(bytes).toString("base64url") }
|
|
11404
|
+
});
|
|
11405
|
+
const record = Object.freeze({ version: 2, mutation: Object.freeze(mutation), createdAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
11406
|
+
await this.persist(stateWithHighWater(this.state, mutation.writerEpoch, mutation.sourceSeq, [record]));
|
|
11407
|
+
return record;
|
|
11408
|
+
});
|
|
11409
|
+
}
|
|
11410
|
+
async replay(port) {
|
|
11411
|
+
return this.exclusive(async () => {
|
|
11412
|
+
for (const record of this.pending()) {
|
|
11413
|
+
if (!(await publishAgentMemoryProjection(port, record.mutation)).accepted) {
|
|
11414
|
+
return Object.freeze({
|
|
11415
|
+
status: "pending",
|
|
11416
|
+
writerEpoch: record.mutation.writerEpoch,
|
|
11417
|
+
sourceSeq: record.mutation.sourceSeq,
|
|
11418
|
+
mutationId: record.mutation.mutationId
|
|
11419
|
+
});
|
|
11420
|
+
}
|
|
11421
|
+
await this.persist(freezeOutboxState({
|
|
11422
|
+
version: 2,
|
|
11423
|
+
currentWriterEpoch: this.state.currentWriterEpoch,
|
|
11424
|
+
highWater: this.state.highWater,
|
|
11425
|
+
pending: this.state.pending.filter((candidate) => candidate.mutation.mutationId !== record.mutation.mutationId)
|
|
11426
|
+
}));
|
|
11427
|
+
}
|
|
11428
|
+
return DRAINED_AGENT_MEMORY_PROJECTION_REPLAY;
|
|
11429
|
+
});
|
|
11430
|
+
}
|
|
11431
|
+
async load() {
|
|
11432
|
+
const current = await readInternalFile(this.context, AGENT_MEMORY_OUTBOX_FILENAME);
|
|
11433
|
+
this.fileRevision = current.revision;
|
|
11434
|
+
this.state = current.exists ? this.loadBody(current.content) : initialOutboxState(this.grant.writerEpoch);
|
|
11435
|
+
}
|
|
11436
|
+
loadBody(body) {
|
|
11437
|
+
try {
|
|
11438
|
+
return freezeOutboxState(JSON.parse(body));
|
|
11439
|
+
} catch {
|
|
11440
|
+
throw new AgentMemoryError("Agent memory outbox state is invalid");
|
|
11441
|
+
}
|
|
11442
|
+
}
|
|
11443
|
+
async admitGrant() {
|
|
11444
|
+
if (this.grant.writerEpoch < this.state.currentWriterEpoch) throw new AgentMemoryError("Agent memory outbox writer epoch is stale");
|
|
11445
|
+
if (this.grant.writerEpoch === this.state.currentWriterEpoch) return;
|
|
11446
|
+
await this.persist(freezeOutboxState({ version: 2, currentWriterEpoch: this.grant.writerEpoch, highWater: [], pending: [] }));
|
|
11447
|
+
}
|
|
11448
|
+
async persist(next) {
|
|
11449
|
+
const replaced = await replaceInternalFile(this.context, AGENT_MEMORY_OUTBOX_FILENAME, this.fileRevision, outboxStateJson(next));
|
|
11450
|
+
this.fileRevision = replaced.revision;
|
|
11451
|
+
this.state = next;
|
|
11452
|
+
}
|
|
11453
|
+
async exclusive(fn) {
|
|
11454
|
+
const previous = this.writeTail;
|
|
11455
|
+
let release;
|
|
11456
|
+
this.writeTail = new Promise((resolve) => {
|
|
11457
|
+
release = resolve;
|
|
11458
|
+
});
|
|
11459
|
+
await previous;
|
|
11460
|
+
try {
|
|
11461
|
+
return await fn();
|
|
11462
|
+
} finally {
|
|
11463
|
+
release();
|
|
11464
|
+
}
|
|
11465
|
+
}
|
|
11466
|
+
};
|
|
11467
|
+
async function snapshotAndProjectAgentMemory(input, projection) {
|
|
11468
|
+
if (projection?.capability !== AGENT_MEMORY_PROJECTION_CAPABILITY || !projection.grant || !projection.redactor || !projection.port) return;
|
|
11469
|
+
const context = taskContext(input);
|
|
11470
|
+
const outbox = await AgentMemoryRedactedOutbox.open(context, projection.grant);
|
|
11471
|
+
const initialReplay = await outbox.replay(projection.port);
|
|
11472
|
+
if (initialReplay.status === "pending") throw new AgentMemoryProjectionReplayPendingError(initialReplay);
|
|
11473
|
+
const snapshot = await captureAgentMemorySnapshot(context);
|
|
11474
|
+
await outbox.append(redactedBytes(snapshot, await projection.redactor.redact(snapshot)));
|
|
11475
|
+
const trailingReplay = await outbox.replay(projection.port);
|
|
11476
|
+
if (trailingReplay.status === "pending") throw new AgentMemoryProjectionReplayPendingError(trailingReplay);
|
|
11477
|
+
}
|
|
11478
|
+
var AGENT_MEMORY_FILESYSTEM_HELPER_PROTOCOL = 2;
|
|
11479
|
+
var AGENT_MEMORY_FILESYSTEM_HELPER_VERSION = "2";
|
|
11480
|
+
var HELPER_REQUEST_TIMEOUT_MS = 1e4;
|
|
11481
|
+
var HELPER_MAX_JSON_LINE_BYTES = 2 * 1024 * 1024;
|
|
11482
|
+
var HELPER_MAX_CONTENT_BYTES = 1 * 1024 * 1024;
|
|
11483
|
+
var HELPER_MAX_STDERR_BYTES = 4 * 1024;
|
|
11484
|
+
var HelperRevisionConflict = class extends Error {
|
|
11485
|
+
constructor(actualRevision) {
|
|
11486
|
+
super("helper revision conflict");
|
|
11487
|
+
this.actualRevision = actualRevision;
|
|
11488
|
+
}
|
|
11489
|
+
actualRevision;
|
|
11490
|
+
};
|
|
11491
|
+
function safeHelperPath(value) {
|
|
11492
|
+
if (typeof value !== "string" || !path3__default.isAbsolute(value) || value.length === 0 || /[\u0000\r\n]/u.test(value)) {
|
|
11493
|
+
throw new AgentMemoryError("Agent memory filesystem helper must be an explicit absolute executable path");
|
|
11494
|
+
}
|
|
11495
|
+
return path3__default.resolve(value);
|
|
11496
|
+
}
|
|
11497
|
+
function helperPlatformSupported() {
|
|
11498
|
+
return process.platform === "darwin";
|
|
11499
|
+
}
|
|
11500
|
+
function isAgentMemoryFilesystemHelperSupported() {
|
|
11501
|
+
return helperPlatformSupported();
|
|
11502
|
+
}
|
|
11503
|
+
function unixIdentity(homeIdentity) {
|
|
11504
|
+
return Object.freeze({ kind: "unix", dev: homeIdentity.dev.toString(10), ino: homeIdentity.ino.toString(10) });
|
|
11505
|
+
}
|
|
11506
|
+
function responseRecord(value) {
|
|
11507
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
11508
|
+
const record = value;
|
|
11509
|
+
if (typeof record.id !== "string" || record.protocol !== AGENT_MEMORY_FILESYSTEM_HELPER_PROTOCOL || typeof record.ok !== "boolean") return void 0;
|
|
11510
|
+
if (record.ok) {
|
|
11511
|
+
if (record.result === null || typeof record.result !== "object" || Array.isArray(record.result)) return void 0;
|
|
11512
|
+
return record;
|
|
11513
|
+
}
|
|
11514
|
+
if (record.error === null || typeof record.error !== "object" || Array.isArray(record.error)) return void 0;
|
|
11515
|
+
const error = record.error;
|
|
11516
|
+
if (typeof error.code !== "string" || typeof error.message !== "string" || error.actualRevision !== void 0 && typeof error.actualRevision !== "string") return void 0;
|
|
11517
|
+
return record;
|
|
11518
|
+
}
|
|
11519
|
+
function boundedFileState(result, maxBytes) {
|
|
11520
|
+
let content;
|
|
11521
|
+
try {
|
|
11522
|
+
if (typeof result.contentBase64 !== "string" || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}|[A-Za-z0-9+/]{3})?$/u.test(result.contentBase64)) {
|
|
11523
|
+
throw new Error("invalid base64");
|
|
11524
|
+
}
|
|
11525
|
+
content = Buffer.from(result.contentBase64, "base64").toString("utf8");
|
|
11526
|
+
} catch {
|
|
11527
|
+
throw new AgentMemoryError("Agent memory filesystem helper returned an invalid file state");
|
|
11528
|
+
}
|
|
11529
|
+
if (typeof result.exists !== "boolean" || typeof result.revision !== "string" || typeof result.byteCount !== "number" || !Number.isSafeInteger(result.byteCount) || result.byteCount < 0 || result.byteCount > maxBytes || Buffer.byteLength(content, "utf8") !== result.byteCount || !/^sha256:[a-f0-9]{64}$/u.test(result.revision)) {
|
|
11530
|
+
throw new AgentMemoryError("Agent memory filesystem helper returned an invalid file state");
|
|
11531
|
+
}
|
|
11532
|
+
return Object.freeze({ exists: result.exists, content, revision: result.revision, byteCount: result.byteCount });
|
|
11533
|
+
}
|
|
11534
|
+
function encodeReplaceContent(content, maxBytes) {
|
|
11535
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 0 || maxBytes > HELPER_MAX_CONTENT_BYTES) {
|
|
11536
|
+
throw new AgentMemoryError("Agent memory filesystem helper requested an invalid byte limit");
|
|
11537
|
+
}
|
|
11538
|
+
const bytes = Buffer.from(content, "utf8");
|
|
11539
|
+
if (bytes.byteLength > maxBytes) {
|
|
11540
|
+
throw new AgentMemoryError("Agent memory filesystem helper replacement exceeds its requested byte limit");
|
|
11541
|
+
}
|
|
11542
|
+
return bytes.toString("base64").replace(/=+$/u, "");
|
|
11543
|
+
}
|
|
11544
|
+
var AgentMemoryFilesystemHelperClient = class _AgentMemoryFilesystemHelperClient {
|
|
11545
|
+
constructor(child) {
|
|
11546
|
+
this.child = child;
|
|
11547
|
+
child.stdout.on("data", (chunk) => this.handleStdoutChunk(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
|
|
11548
|
+
child.stderr.on("data", (chunk) => {
|
|
11549
|
+
this.stderrBytes += Buffer.byteLength(chunk);
|
|
11550
|
+
if (this.stderrBytes > HELPER_MAX_STDERR_BYTES) this.fail(new AgentMemoryError("Agent memory filesystem helper exceeded bounded stderr"));
|
|
11551
|
+
});
|
|
11552
|
+
child.stdin.on("error", () => this.fail(new AgentMemoryError("Agent memory filesystem helper request could not be written")));
|
|
11553
|
+
child.once("error", () => this.fail(new AgentMemoryError("Agent memory filesystem helper could not be started")));
|
|
11554
|
+
child.once("exit", (code, signal) => {
|
|
11555
|
+
if (!this.closed || code !== 0) this.fail(new AgentMemoryError(`Agent memory filesystem helper exited unexpectedly (${code ?? signal ?? "unknown"})`));
|
|
11556
|
+
});
|
|
11557
|
+
}
|
|
11558
|
+
child;
|
|
11559
|
+
pending = /* @__PURE__ */ new Map();
|
|
11560
|
+
stdoutBuffer = Buffer.alloc(0);
|
|
11561
|
+
sequence = 0;
|
|
11562
|
+
stderrBytes = 0;
|
|
11563
|
+
closed = false;
|
|
11564
|
+
fatalError;
|
|
11565
|
+
static async open(input) {
|
|
11566
|
+
if (!helperPlatformSupported()) throw new AgentMemoryError("Agent memory filesystem helper is not admitted on this platform");
|
|
11567
|
+
const helperBin = safeHelperPath(input.helperBin);
|
|
11568
|
+
const child = spawn(helperBin, ["serve"], {
|
|
11569
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
11570
|
+
shell: false,
|
|
11571
|
+
windowsHide: true,
|
|
11572
|
+
env: Object.freeze({})
|
|
11573
|
+
});
|
|
11574
|
+
const client = new _AgentMemoryFilesystemHelperClient(child);
|
|
11575
|
+
try {
|
|
11576
|
+
const result = await client.request("open", {
|
|
11577
|
+
root: path3__default.resolve(input.canonicalHome),
|
|
11578
|
+
expectedIdentity: unixIdentity(input.homeIdentity)
|
|
11579
|
+
});
|
|
11580
|
+
if (result.helperVersion !== AGENT_MEMORY_FILESYSTEM_HELPER_VERSION) throw new AgentMemoryError("Agent memory filesystem helper version mismatch");
|
|
11581
|
+
const identity = result.identity;
|
|
11582
|
+
if (identity === null || typeof identity !== "object" || Array.isArray(identity)) throw new AgentMemoryError("Agent memory filesystem helper omitted root identity");
|
|
11583
|
+
const actual = identity;
|
|
11584
|
+
const expected = unixIdentity(input.homeIdentity);
|
|
11585
|
+
if (actual.kind !== expected.kind || actual.dev !== expected.dev || actual.ino !== expected.ino) throw new AgentMemoryError("Agent memory filesystem helper root identity mismatch");
|
|
11586
|
+
return client;
|
|
11587
|
+
} catch (error) {
|
|
11588
|
+
await client.close().catch(() => {
|
|
11589
|
+
});
|
|
11590
|
+
throw error;
|
|
11591
|
+
}
|
|
11592
|
+
}
|
|
11593
|
+
async read(relativePath, maxBytes) {
|
|
11594
|
+
return boundedFileState(await this.request("read", { path: relativePath, maxBytes }), maxBytes);
|
|
11595
|
+
}
|
|
11596
|
+
async replace(relativePath, expectedRevision, content, maxBytes) {
|
|
11597
|
+
try {
|
|
11598
|
+
const contentBase64 = encodeReplaceContent(content, maxBytes);
|
|
11599
|
+
return boundedFileState(await this.request("replace", { path: relativePath, expectedRevision, contentBase64, maxBytes }), maxBytes);
|
|
11600
|
+
} catch (error) {
|
|
11601
|
+
if (error instanceof HelperRevisionConflict) throw new AgentMemoryRevisionConflictError(expectedRevision, error.actualRevision);
|
|
11602
|
+
throw error;
|
|
11603
|
+
}
|
|
11604
|
+
}
|
|
11605
|
+
async delete(relativePath, expectedRevision) {
|
|
11606
|
+
try {
|
|
11607
|
+
await this.request("delete", { path: relativePath, expectedRevision });
|
|
11608
|
+
} catch (error) {
|
|
11609
|
+
if (error instanceof HelperRevisionConflict) throw new AgentMemoryRevisionConflictError(expectedRevision, error.actualRevision);
|
|
11610
|
+
throw error;
|
|
11611
|
+
}
|
|
11612
|
+
}
|
|
11613
|
+
async append(relativePath, content, maxBytes) {
|
|
11614
|
+
await this.request("append", { path: relativePath, content, maxBytes });
|
|
11615
|
+
}
|
|
11616
|
+
async walk(relativePath, maxEntries) {
|
|
11617
|
+
const result = await this.request("walk", { path: relativePath, maxEntries });
|
|
11618
|
+
if (!Array.isArray(result.paths) || result.paths.length > maxEntries || result.paths.some((candidate) => typeof candidate !== "string")) {
|
|
11619
|
+
throw new AgentMemoryError("Agent memory filesystem helper returned an invalid walk result");
|
|
11620
|
+
}
|
|
11621
|
+
return Object.freeze([...result.paths]);
|
|
11622
|
+
}
|
|
11623
|
+
async close() {
|
|
11624
|
+
if (this.closed) return;
|
|
11625
|
+
this.closed = true;
|
|
11626
|
+
try {
|
|
11627
|
+
if (this.child.exitCode === null && this.child.signalCode === null && this.fatalError === void 0) await this.requestWhileClosing("close", {});
|
|
11628
|
+
} finally {
|
|
11629
|
+
this.child.stdin.end();
|
|
11630
|
+
if (this.child.exitCode === null && this.child.signalCode === null) this.child.kill();
|
|
11631
|
+
this.rejectPending(new AgentMemoryError("Agent memory filesystem helper is closed"));
|
|
11632
|
+
}
|
|
11633
|
+
}
|
|
11634
|
+
request(op, fields) {
|
|
11635
|
+
if (this.closed) return Promise.reject(new AgentMemoryError("Agent memory filesystem helper is closed"));
|
|
11636
|
+
return this.requestInternal(op, fields);
|
|
11637
|
+
}
|
|
11638
|
+
requestWhileClosing(op, fields) {
|
|
11639
|
+
return this.requestInternal(op, fields);
|
|
11640
|
+
}
|
|
11641
|
+
requestInternal(op, fields) {
|
|
11642
|
+
if (this.fatalError !== void 0) return Promise.reject(this.fatalError);
|
|
11643
|
+
const id = `m${++this.sequence}`;
|
|
11644
|
+
let line;
|
|
11645
|
+
try {
|
|
11646
|
+
line = `${JSON.stringify({ id, protocol: AGENT_MEMORY_FILESYSTEM_HELPER_PROTOCOL, op, ...fields })}
|
|
11647
|
+
`;
|
|
11648
|
+
} catch {
|
|
11649
|
+
return Promise.reject(new AgentMemoryError("Agent memory filesystem helper request could not be encoded"));
|
|
11650
|
+
}
|
|
11651
|
+
if (Buffer.byteLength(line, "utf8") > HELPER_MAX_JSON_LINE_BYTES) {
|
|
11652
|
+
return Promise.reject(new AgentMemoryError("Agent memory filesystem helper request exceeded bounded stdin"));
|
|
11653
|
+
}
|
|
11654
|
+
return new Promise((resolve, reject) => {
|
|
11655
|
+
const timer = setTimeout(() => {
|
|
11656
|
+
this.pending.delete(id);
|
|
11657
|
+
const error = new AgentMemoryError("Agent memory filesystem helper request timed out");
|
|
11658
|
+
reject(error);
|
|
11659
|
+
this.fail(error);
|
|
11660
|
+
}, HELPER_REQUEST_TIMEOUT_MS);
|
|
11661
|
+
timer.unref?.();
|
|
11662
|
+
this.pending.set(id, { resolve: (response) => resolve(response.result), reject, timer });
|
|
11663
|
+
this.child.stdin.write(line, (error) => {
|
|
11664
|
+
if (!error) return;
|
|
11665
|
+
const item = this.pending.get(id);
|
|
11666
|
+
if (item === void 0) return;
|
|
11667
|
+
clearTimeout(item.timer);
|
|
11668
|
+
this.pending.delete(id);
|
|
11669
|
+
item.reject(new AgentMemoryError("Agent memory filesystem helper request could not be written"));
|
|
11670
|
+
});
|
|
11671
|
+
});
|
|
11672
|
+
}
|
|
11673
|
+
handleStdoutChunk(chunk) {
|
|
11674
|
+
let offset = 0;
|
|
11675
|
+
while (offset < chunk.length && this.fatalError === void 0) {
|
|
11676
|
+
const newline = chunk.indexOf(10, offset);
|
|
11677
|
+
const end = newline === -1 ? chunk.length : newline;
|
|
11678
|
+
const segment = chunk.subarray(offset, end);
|
|
11679
|
+
if (this.stdoutBuffer.length + segment.length > HELPER_MAX_JSON_LINE_BYTES) {
|
|
11680
|
+
this.fail(new AgentMemoryError("Agent memory filesystem helper exceeded bounded stdout"));
|
|
11681
|
+
return;
|
|
11682
|
+
}
|
|
11683
|
+
this.stdoutBuffer = this.stdoutBuffer.length === 0 ? Buffer.from(segment) : Buffer.concat([this.stdoutBuffer, segment]);
|
|
11684
|
+
if (newline === -1) return;
|
|
11685
|
+
const line = this.stdoutBuffer;
|
|
11686
|
+
this.stdoutBuffer = Buffer.alloc(0);
|
|
11687
|
+
this.handleLine(line);
|
|
11688
|
+
offset = newline + 1;
|
|
11689
|
+
}
|
|
11690
|
+
}
|
|
11691
|
+
handleLine(line) {
|
|
11692
|
+
let parsed;
|
|
11693
|
+
try {
|
|
11694
|
+
parsed = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(line));
|
|
11695
|
+
} catch {
|
|
11696
|
+
this.fail(new AgentMemoryError("Agent memory filesystem helper returned malformed JSON"));
|
|
11697
|
+
return;
|
|
11698
|
+
}
|
|
11699
|
+
const response = responseRecord(parsed);
|
|
11700
|
+
if (response === void 0) {
|
|
11701
|
+
this.fail(new AgentMemoryError("Agent memory filesystem helper returned an invalid response"));
|
|
11702
|
+
return;
|
|
11703
|
+
}
|
|
11704
|
+
const item = this.pending.get(response.id);
|
|
11705
|
+
if (item === void 0) {
|
|
11706
|
+
this.fail(new AgentMemoryError("Agent memory filesystem helper returned an unsolicited response"));
|
|
11707
|
+
return;
|
|
11708
|
+
}
|
|
11709
|
+
clearTimeout(item.timer);
|
|
11710
|
+
this.pending.delete(response.id);
|
|
11711
|
+
if (response.ok) {
|
|
11712
|
+
item.resolve(response);
|
|
11713
|
+
return;
|
|
11714
|
+
}
|
|
11715
|
+
if (response.error.code === "revision_conflict" && typeof response.error.actualRevision === "string") {
|
|
11716
|
+
item.reject(new HelperRevisionConflict(response.error.actualRevision));
|
|
11717
|
+
return;
|
|
11718
|
+
}
|
|
11719
|
+
item.reject(new AgentMemoryError(`Agent memory filesystem helper rejected the operation: ${response.error.code}`));
|
|
11720
|
+
}
|
|
11721
|
+
fail(error) {
|
|
11722
|
+
if (this.fatalError !== void 0) return;
|
|
11723
|
+
this.fatalError = error;
|
|
11724
|
+
this.rejectPending(error);
|
|
11725
|
+
if (this.child.exitCode === null && this.child.signalCode === null) this.child.kill();
|
|
11726
|
+
}
|
|
11727
|
+
rejectPending(error) {
|
|
11728
|
+
for (const item of this.pending.values()) {
|
|
11729
|
+
clearTimeout(item.timer);
|
|
11730
|
+
item.reject(error);
|
|
11731
|
+
}
|
|
11732
|
+
this.pending.clear();
|
|
11733
|
+
}
|
|
11734
|
+
};
|
|
11735
|
+
async function openAgentMemoryFilesystemHelper(input) {
|
|
11736
|
+
return AgentMemoryFilesystemHelperClient.open(input);
|
|
11737
|
+
}
|
|
11738
|
+
|
|
11739
|
+
// src/daemon/task-runner.ts
|
|
11740
|
+
var DEFAULT_APPROVAL_TIMEOUT_MS = 10 * 6e4;
|
|
11741
|
+
var DEFAULT_SHUTDOWN_INTERRUPT_TIMEOUT_MS = 5e3;
|
|
11742
|
+
var AGENT_TERMINAL_EVIDENCE_MAX_ATTEMPTS = 3;
|
|
11743
|
+
var AGENT_TERMINAL_EVIDENCE_RETRY_DELAY_MS = 20;
|
|
11744
|
+
var GIT_OBSERVATION_TIMEOUT_MS = 5e3;
|
|
11745
|
+
var MAX_PENDING_APPROVALS_PER_TASK = 16;
|
|
11746
|
+
var NoPendingApprovalError = class extends Error {
|
|
11747
|
+
constructor(taskId) {
|
|
11748
|
+
super(`no pending out-of-band approval to resolve for task ${taskId}`);
|
|
11749
|
+
this.taskId = taskId;
|
|
11750
|
+
this.name = "NoPendingApprovalError";
|
|
11751
|
+
}
|
|
11752
|
+
taskId;
|
|
11753
|
+
};
|
|
11754
|
+
var MAX_INLINE_ARTIFACT_BYTES = 64 * 1024;
|
|
11755
|
+
var MAX_TRACKED_TASK_IDS = 2e3;
|
|
11756
|
+
var MAX_DURATION_EXCEEDED_REASON_PREFIX = "resource limit exceeded: maxDurationMs";
|
|
11757
|
+
var MAX_OUTPUT_BYTES_EXCEEDED_REASON_PREFIX = "resource limit exceeded: maxTaskOutputBytes";
|
|
11758
|
+
var MAX_PROGRESS_BATCH_BYTES_EXCEEDED_REASON_PREFIX = "resource limit exceeded: progressBatch.maxBatchBytes";
|
|
11759
|
+
var RESULT_DOCUMENT_UNDELIVERABLE_REASON_PREFIX = "result document undeliverable";
|
|
11760
|
+
function resultDocumentRejectionDetail(check) {
|
|
11761
|
+
switch (check.reason) {
|
|
11762
|
+
case "over-cap":
|
|
11763
|
+
return `${check.bytes} bytes as canonical JSON, over the ${RESULT_DOCUMENT_MAX_BYTES}-byte limit (it is never truncated \u2014 a truncated JSON document is not valid JSON; use artifactRefs for a result this size)`;
|
|
11764
|
+
case "not-serializable":
|
|
11765
|
+
return "not JSON-serializable (JSON.stringify threw, or produced no output at all)";
|
|
11766
|
+
case "not-plain-json":
|
|
11767
|
+
return "not plain JSON data: it does not equal its own JSON round trip, so serializing it would silently change it (an undefined-valued key, NaN, a function or symbol value, a Date, a toJSON that rewrites the value, or a getter that answers differently on a second read)";
|
|
11768
|
+
default: {
|
|
11769
|
+
const exhaustive = check;
|
|
11770
|
+
throw new Error(`unhandled result document rejection: ${JSON.stringify(exhaustive)}`);
|
|
11771
|
+
}
|
|
11772
|
+
}
|
|
11773
|
+
}
|
|
11774
|
+
var DEFAULT_MAX_TASK_OUTPUT_BYTES = 64 * 1024 * 1024;
|
|
11775
|
+
function isKnownRuntimeId(id) {
|
|
11776
|
+
return RuntimeIdSchema.safeParse(id).success;
|
|
11777
|
+
}
|
|
11778
|
+
function terminalUsageNumber(value, maximum) {
|
|
11779
|
+
return value !== void 0 && Number.isSafeInteger(value) && value >= 0 && value <= maximum ? value : void 0;
|
|
11780
|
+
}
|
|
11781
|
+
var DEFAULT_RUNTIME_PREFERENCE = ["claude", "codex", "pi"];
|
|
11782
|
+
function orderByPreference(candidates, preference) {
|
|
11783
|
+
const rank = new Map(preference.map((id, index) => [id, index]));
|
|
11784
|
+
return [...candidates].sort((a, b) => (rank.get(a.descriptor.id) ?? preference.length) - (rank.get(b.descriptor.id) ?? preference.length));
|
|
11785
|
+
}
|
|
11786
|
+
function adapterSupportsMode(descriptor, mode) {
|
|
11787
|
+
return descriptor.capabilities.permissionModes.includes(mode);
|
|
11788
|
+
}
|
|
11789
|
+
function adapterSupportsMcpToolsets(descriptor) {
|
|
11790
|
+
return descriptor.capabilities.mcpToolsets === true;
|
|
11791
|
+
}
|
|
11792
|
+
function withoutRequiredToolsets(payload) {
|
|
11793
|
+
const { requiredToolsets, egressPolicy, messageEgress, ...offer } = payload;
|
|
11794
|
+
return offer;
|
|
11795
|
+
}
|
|
11796
|
+
function sameEgressPolicy(left, right) {
|
|
11797
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
11798
|
+
}
|
|
11799
|
+
function offeredAgentRef(payload) {
|
|
11800
|
+
if (!Object.prototype.hasOwnProperty.call(payload, "agentRef")) return void 0;
|
|
11801
|
+
return validateAgentRef(payload.agentRef);
|
|
11802
|
+
}
|
|
11803
|
+
function offeredSessionRef(payload) {
|
|
11804
|
+
if (!Object.prototype.hasOwnProperty.call(payload, "sessionRef")) return void 0;
|
|
11805
|
+
const value = payload.sessionRef;
|
|
11806
|
+
return typeof value === "string" ? value : void 0;
|
|
11807
|
+
}
|
|
11808
|
+
function errorMessage4(err) {
|
|
11809
|
+
return err instanceof Error ? err.message : String(err);
|
|
11810
|
+
}
|
|
11811
|
+
function raceSettleFirst(fn, timeoutMs) {
|
|
11812
|
+
return new Promise((resolve) => {
|
|
11813
|
+
let settled = false;
|
|
11814
|
+
const timer = setTimeout(() => {
|
|
11815
|
+
if (!settled) {
|
|
11816
|
+
settled = true;
|
|
11817
|
+
resolve(false);
|
|
11818
|
+
}
|
|
11819
|
+
}, timeoutMs);
|
|
11820
|
+
timer.unref?.();
|
|
11821
|
+
void (async () => {
|
|
11822
|
+
try {
|
|
11823
|
+
await fn();
|
|
11824
|
+
} catch {
|
|
11825
|
+
}
|
|
11826
|
+
if (!settled) {
|
|
11827
|
+
settled = true;
|
|
11828
|
+
clearTimeout(timer);
|
|
11829
|
+
resolve(true);
|
|
11830
|
+
}
|
|
11831
|
+
})();
|
|
11832
|
+
});
|
|
11833
|
+
}
|
|
11834
|
+
function estimateEventBytes(event) {
|
|
11835
|
+
try {
|
|
11836
|
+
return Buffer.byteLength(JSON.stringify(event), "utf8");
|
|
11837
|
+
} catch {
|
|
11838
|
+
return 0;
|
|
11839
|
+
}
|
|
11840
|
+
}
|
|
11841
|
+
async function openArtifact(workspaceDir, name) {
|
|
11842
|
+
const realWorkspaceDir = await promises.realpath(workspaceDir).catch(() => workspaceDir);
|
|
11843
|
+
const candidate = path3__default.resolve(realWorkspaceDir, name);
|
|
11844
|
+
const prefix = realWorkspaceDir.endsWith(path3__default.sep) ? realWorkspaceDir : realWorkspaceDir + path3__default.sep;
|
|
11845
|
+
if (candidate !== realWorkspaceDir && !candidate.startsWith(prefix)) {
|
|
11846
|
+
return { ok: false, reason: `artifact name "${name}" resolves outside the task workspace \u2014 rejected` };
|
|
11847
|
+
}
|
|
11848
|
+
let realCandidate = candidate;
|
|
11849
|
+
try {
|
|
11850
|
+
realCandidate = await promises.realpath(candidate);
|
|
11851
|
+
} catch {
|
|
11852
|
+
}
|
|
11853
|
+
if (realCandidate !== realWorkspaceDir && !realCandidate.startsWith(prefix)) {
|
|
11854
|
+
return { ok: false, reason: `artifact name "${name}" resolves outside the task workspace \u2014 rejected` };
|
|
11855
|
+
}
|
|
11856
|
+
const O_NOFOLLOW = constants.O_NOFOLLOW ?? 0;
|
|
11857
|
+
let handle;
|
|
11858
|
+
try {
|
|
11859
|
+
handle = await promises.open(candidate, constants.O_RDONLY | O_NOFOLLOW);
|
|
11860
|
+
} catch (err) {
|
|
11861
|
+
return { ok: false, reason: `artifact "${name}" could not be opened: ${errorMessage4(err)}` };
|
|
10688
11862
|
}
|
|
10689
11863
|
try {
|
|
10690
11864
|
const st = await handle.stat();
|
|
@@ -10706,6 +11880,16 @@ var TaskRunner = class {
|
|
|
10706
11880
|
}
|
|
10707
11881
|
deps;
|
|
10708
11882
|
tasks = /* @__PURE__ */ new Map();
|
|
11883
|
+
pendingMessageTasks = /* @__PURE__ */ new Map();
|
|
11884
|
+
messageContextByToken = /* @__PURE__ */ new Map();
|
|
11885
|
+
messageContextByTask = /* @__PURE__ */ new Map();
|
|
11886
|
+
memoryContextByToken = /* @__PURE__ */ new Map();
|
|
11887
|
+
memoryContextByTask = /* @__PURE__ */ new Map();
|
|
11888
|
+
memoryInFlightByTask = /* @__PURE__ */ new Map();
|
|
11889
|
+
memoryClosingTasks = /* @__PURE__ */ new Set();
|
|
11890
|
+
memoryFilesystemByTask = /* @__PURE__ */ new Map();
|
|
11891
|
+
recoveredMessageOutboxes = /* @__PURE__ */ new Map();
|
|
11892
|
+
recoveredMessageRetryTimers = /* @__PURE__ */ new Map();
|
|
10709
11893
|
/**
|
|
10710
11894
|
* Finding F4 (cancel lost during the offer-processing window): a
|
|
10711
11895
|
* `task.cancel` for a taskId that hasn't finished `handleOffer` yet (still
|
|
@@ -10845,6 +12029,128 @@ var TaskRunner = class {
|
|
|
10845
12029
|
pendingApprovals: (active.pendingApprovalId !== void 0 ? 1 : 0) + active.approvalQueue.length
|
|
10846
12030
|
}));
|
|
10847
12031
|
}
|
|
12032
|
+
/** Authenticated control-socket entry used only by the SDK-owned task MCP helper. */
|
|
12033
|
+
async publishAgentMessage(input) {
|
|
12034
|
+
const taskId = this.messageContextByToken.get(input.contextToken);
|
|
12035
|
+
if (taskId === void 0 || this.messageContextByTask.get(taskId) !== input.contextToken) {
|
|
12036
|
+
throw new Error("invalid or expired Agent message task context");
|
|
12037
|
+
}
|
|
12038
|
+
const pending = this.pendingMessageTasks.get(taskId);
|
|
12039
|
+
const active = this.tasks.get(taskId);
|
|
12040
|
+
const context = pending ?? (active?.messageRequirement && active.agentRef && active.messageOutbox ? { agentRef: active.agentRef, requirement: active.messageRequirement, outbox: active.messageOutbox } : void 0);
|
|
12041
|
+
if (context === void 0) throw new Error("task has no active required Agent message contract");
|
|
12042
|
+
const record = await context.outbox.appendDraft({
|
|
12043
|
+
taskId,
|
|
12044
|
+
tenantId: this.deps.tenantId,
|
|
12045
|
+
agentRef: context.agentRef,
|
|
12046
|
+
requirement: context.requirement,
|
|
12047
|
+
contentType: input.contentType,
|
|
12048
|
+
body: input.body,
|
|
12049
|
+
maxPendingEvents: 64,
|
|
12050
|
+
maxPendingBytes: 4 * 1024 * 1024
|
|
12051
|
+
});
|
|
12052
|
+
if (active === void 0) return { messageId: record.messageId, state: "staged" };
|
|
12053
|
+
const activated = await context.outbox.activate(taskId, active.session.sessionRef);
|
|
12054
|
+
if (activated === void 0) throw new Error("Agent message draft disappeared before activation");
|
|
12055
|
+
this.sendAgentMessageRecord(context.outbox, activated);
|
|
12056
|
+
return { messageId: activated.messageId, state: "pending" };
|
|
12057
|
+
}
|
|
12058
|
+
/** Authenticated control-socket entry used only by the SDK-owned memory MCP helper. */
|
|
12059
|
+
async recallAgentMemory(input) {
|
|
12060
|
+
return this.runMemoryOperation(input.contextToken, (context) => new AgentMemoryService(context).recall(input));
|
|
12061
|
+
}
|
|
12062
|
+
/** Authenticated control-socket entry used only by the SDK-owned memory MCP helper. */
|
|
12063
|
+
async saveAgentMemory(input) {
|
|
12064
|
+
return this.runMemoryOperation(input.contextToken, (context) => new AgentMemoryService(context).save(input));
|
|
12065
|
+
}
|
|
12066
|
+
/** Restore activated, unaccepted message drafts before transport admission on daemon restart. */
|
|
12067
|
+
async recoverAgentMessageOutboxes(agentsRoot) {
|
|
12068
|
+
if (this.deps.tenantId === void 0) throw new Error("Agent message recovery requires authenticated tenant enrollment");
|
|
12069
|
+
for (const outbox of await AgentMessageOutbox.recover(agentsRoot, this.deps.tenantId)) {
|
|
12070
|
+
for (const record of outbox.retryableRecords()) {
|
|
12071
|
+
if (record.sessionRef === void 0) continue;
|
|
12072
|
+
const existing = this.recoveredMessageOutboxes.get(record.taskId);
|
|
12073
|
+
if (existing !== void 0 && existing !== outbox) throw new Error(`multiple Agent message outboxes claim task ${record.taskId}`);
|
|
12074
|
+
this.recoveredMessageOutboxes.set(record.taskId, outbox);
|
|
12075
|
+
}
|
|
12076
|
+
}
|
|
12077
|
+
}
|
|
12078
|
+
/** Retry stable recovered records after a transport handshake/re-handshake. */
|
|
12079
|
+
retryRecoveredAgentMessages() {
|
|
12080
|
+
for (const [taskId, outbox] of this.recoveredMessageOutboxes) {
|
|
12081
|
+
const record = outbox.get(taskId);
|
|
12082
|
+
if (record?.sessionRef !== void 0) this.sendAgentMessageRecord(outbox, record);
|
|
12083
|
+
}
|
|
12084
|
+
}
|
|
12085
|
+
sendAgentMessageRecord(outbox, record) {
|
|
12086
|
+
this.deps.send(createEnvelope("agent.message.publish", outbox.publishPayload(record), {
|
|
12087
|
+
taskId: record.taskId,
|
|
12088
|
+
sessionRef: record.sessionRef
|
|
12089
|
+
}));
|
|
12090
|
+
const active = this.tasks.get(record.taskId);
|
|
12091
|
+
if (active?.messageOutbox === outbox) {
|
|
12092
|
+
if (active.messageRetryTimer !== void 0) clearTimeout(active.messageRetryTimer);
|
|
12093
|
+
active.messageRetryTimer = setTimeout(() => {
|
|
12094
|
+
active.messageRetryTimer = void 0;
|
|
12095
|
+
if (this.tasks.get(record.taskId) !== active) return;
|
|
12096
|
+
const pending = outbox.get(record.taskId);
|
|
12097
|
+
if (pending !== void 0) this.sendAgentMessageRecord(outbox, pending);
|
|
12098
|
+
}, 1e3);
|
|
12099
|
+
active.messageRetryTimer.unref?.();
|
|
12100
|
+
return;
|
|
12101
|
+
}
|
|
12102
|
+
if (this.recoveredMessageOutboxes.get(record.taskId) === outbox) {
|
|
12103
|
+
const prior = this.recoveredMessageRetryTimers.get(record.taskId);
|
|
12104
|
+
if (prior !== void 0) clearTimeout(prior);
|
|
12105
|
+
const timer = setTimeout(() => {
|
|
12106
|
+
this.recoveredMessageRetryTimers.delete(record.taskId);
|
|
12107
|
+
const pending = outbox.get(record.taskId);
|
|
12108
|
+
if (pending !== void 0 && this.recoveredMessageOutboxes.get(record.taskId) === outbox) {
|
|
12109
|
+
this.sendAgentMessageRecord(outbox, pending);
|
|
12110
|
+
}
|
|
12111
|
+
}, 1e3);
|
|
12112
|
+
timer.unref?.();
|
|
12113
|
+
this.recoveredMessageRetryTimers.set(record.taskId, timer);
|
|
12114
|
+
}
|
|
12115
|
+
}
|
|
12116
|
+
async handleAgentMessageDisposition(taskId, disposition) {
|
|
12117
|
+
const active = this.tasks.get(taskId);
|
|
12118
|
+
const outbox = active?.messageOutbox ?? this.recoveredMessageOutboxes.get(taskId);
|
|
12119
|
+
if (outbox === void 0) return;
|
|
12120
|
+
const outcome = await outbox.applyDisposition(taskId, disposition);
|
|
12121
|
+
if (active === void 0) {
|
|
12122
|
+
if (outcome === "accepted" || outcome === "held" || outcome === "refused") {
|
|
12123
|
+
const timer = this.recoveredMessageRetryTimers.get(taskId);
|
|
12124
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
12125
|
+
this.recoveredMessageRetryTimers.delete(taskId);
|
|
12126
|
+
}
|
|
12127
|
+
if (outcome === "accepted") this.recoveredMessageOutboxes.delete(taskId);
|
|
12128
|
+
return;
|
|
12129
|
+
}
|
|
12130
|
+
if (outcome === "held") {
|
|
12131
|
+
if (active.messageRetryTimer !== void 0) clearTimeout(active.messageRetryTimer);
|
|
12132
|
+
active.messageRetryTimer = void 0;
|
|
12133
|
+
return;
|
|
12134
|
+
}
|
|
12135
|
+
if (outcome === "refused") {
|
|
12136
|
+
if (active.messageRetryTimer !== void 0) clearTimeout(active.messageRetryTimer);
|
|
12137
|
+
active.messageRetryTimer = void 0;
|
|
12138
|
+
this.revokeAgentMessageContext(taskId);
|
|
12139
|
+
return;
|
|
12140
|
+
}
|
|
12141
|
+
if (outcome !== "accepted") return;
|
|
12142
|
+
if (active.messageRetryTimer !== void 0) {
|
|
12143
|
+
clearTimeout(active.messageRetryTimer);
|
|
12144
|
+
active.messageRetryTimer = void 0;
|
|
12145
|
+
}
|
|
12146
|
+
active.messageAccepted = true;
|
|
12147
|
+
this.revokeAgentMessageContext(taskId);
|
|
12148
|
+
if (active.pendingMessageCompletion !== void 0) {
|
|
12149
|
+
const completion = active.pendingMessageCompletion;
|
|
12150
|
+
active.pendingMessageCompletion = void 0;
|
|
12151
|
+
await this.publishSuccessfulCompletion(active, completion.finalOutput, completion.document);
|
|
12152
|
+
}
|
|
12153
|
+
}
|
|
10848
12154
|
/** M4 Phase 2: stop claiming any FUTURE `task.offer` — see `stoppingOffers`'s own doc comment. Idempotent. */
|
|
10849
12155
|
stopAcceptingOffers() {
|
|
10850
12156
|
this.stoppingOffers = true;
|
|
@@ -11020,6 +12326,9 @@ var TaskRunner = class {
|
|
|
11020
12326
|
case "task.reject":
|
|
11021
12327
|
await this.handleReject(envelope.task_id, envelope.payload.reason, envelope.payload.approvalId);
|
|
11022
12328
|
return;
|
|
12329
|
+
case "agent.message.disposition":
|
|
12330
|
+
await this.handleAgentMessageDisposition(envelope.task_id, envelope.payload);
|
|
12331
|
+
return;
|
|
11023
12332
|
default:
|
|
11024
12333
|
return;
|
|
11025
12334
|
}
|
|
@@ -11050,12 +12359,22 @@ var TaskRunner = class {
|
|
|
11050
12359
|
this.decline(taskId, reason, retryable, agentRef);
|
|
11051
12360
|
};
|
|
11052
12361
|
const sessionRef = offeredSessionRef(payload);
|
|
12362
|
+
const messageRequirement = "messageEgress" in payload ? payload.messageEgress : void 0;
|
|
12363
|
+
const terminalProjection = "terminalProjection" in payload ? payload.terminalProjection : void 0;
|
|
11053
12364
|
if ("egressPolicy" in payload) {
|
|
11054
12365
|
if (this.deps.agentEgressPolicy === void 0 || !sameEgressPolicy(this.deps.agentEgressPolicy, payload.egressPolicy)) {
|
|
11055
12366
|
decline("Agent egress offer policy is not exactly enabled by this daemon", false);
|
|
11056
12367
|
return;
|
|
11057
12368
|
}
|
|
11058
12369
|
}
|
|
12370
|
+
if (messageRequirement !== void 0 && (agentRef === void 0 || this.deps.agentMessageMcpBin === void 0 || this.deps.tenantId === void 0)) {
|
|
12371
|
+
decline("required Agent message egress is unavailable on this daemon", false);
|
|
12372
|
+
return;
|
|
12373
|
+
}
|
|
12374
|
+
if (terminalProjection?.mode === "result-document" && this.deps.resultDocument === void 0) {
|
|
12375
|
+
decline("offer requires a result document but this daemon has no resultDocument extractor", false);
|
|
12376
|
+
return;
|
|
12377
|
+
}
|
|
11059
12378
|
this.inFlightOffers.add(taskId);
|
|
11060
12379
|
let agentBinding;
|
|
11061
12380
|
let agentLeaseTransferred = false;
|
|
@@ -11114,11 +12433,29 @@ var TaskRunner = class {
|
|
|
11114
12433
|
}
|
|
11115
12434
|
const offered = withoutRequiredToolsets(payload);
|
|
11116
12435
|
const requestedRuntime = payload.dispatchSelection?.runtimeId ?? payload.runtime;
|
|
11117
|
-
const
|
|
12436
|
+
const requiresAgentMemoryMcp = agentRef !== void 0 && this.deps.agentMemoryMcpBin !== void 0 && isAgentMemorySecureFilesystemAvailable(this.deps.agentMemoryFilesystemHelperBin !== void 0);
|
|
12437
|
+
const pick = await this.pickAdapter(
|
|
12438
|
+
requestedRuntime,
|
|
12439
|
+
payload.policy.mode,
|
|
12440
|
+
requiredToolsets !== void 0 || messageRequirement !== void 0 || requiresAgentMemoryMcp
|
|
12441
|
+
);
|
|
11118
12442
|
if (!pick.ok) {
|
|
11119
12443
|
decline(pick.reason, pick.retryable);
|
|
11120
12444
|
return;
|
|
11121
12445
|
}
|
|
12446
|
+
let taskMcpServers = this.withAgentMessageMcp(
|
|
12447
|
+
resolvedMcp?.ok ? resolvedMcp.servers : void 0,
|
|
12448
|
+
taskId,
|
|
12449
|
+
messageRequirement
|
|
12450
|
+
);
|
|
12451
|
+
if (requiresAgentMemoryMcp && agentRef !== void 0) {
|
|
12452
|
+
try {
|
|
12453
|
+
taskMcpServers = this.withAgentMemoryMcp(taskMcpServers, taskId, agentRef);
|
|
12454
|
+
} catch (error) {
|
|
12455
|
+
decline(`Agent memory MCP configuration failed: ${errorMessage4(error)}`, false);
|
|
12456
|
+
return;
|
|
12457
|
+
}
|
|
12458
|
+
}
|
|
11122
12459
|
let prepared;
|
|
11123
12460
|
try {
|
|
11124
12461
|
prepared = await pick.adapter.prepare({
|
|
@@ -11126,7 +12463,7 @@ var TaskRunner = class {
|
|
|
11126
12463
|
policy: decision.policy,
|
|
11127
12464
|
descriptor: pick.descriptor,
|
|
11128
12465
|
requiredToolsetIds: requiredToolsets ?? [],
|
|
11129
|
-
...
|
|
12466
|
+
...taskMcpServers === void 0 ? {} : { mcpServers: taskMcpServers }
|
|
11130
12467
|
});
|
|
11131
12468
|
} catch (error) {
|
|
11132
12469
|
decline(`runtime preparation failed: ${errorMessage4(error)}`, true);
|
|
@@ -11181,6 +12518,23 @@ var TaskRunner = class {
|
|
|
11181
12518
|
decline(`Agent home initialization failed: ${errorMessage4(error)}`, false);
|
|
11182
12519
|
return;
|
|
11183
12520
|
}
|
|
12521
|
+
if (messageRequirement !== void 0) {
|
|
12522
|
+
try {
|
|
12523
|
+
const outbox = await AgentMessageOutbox.open(agentBinding.resolution.canonicalHome);
|
|
12524
|
+
this.pendingMessageTasks.set(taskId, {
|
|
12525
|
+
taskId,
|
|
12526
|
+
agentRef: agentBinding.resolution.agentRef,
|
|
12527
|
+
requirement: messageRequirement,
|
|
12528
|
+
outbox
|
|
12529
|
+
});
|
|
12530
|
+
} catch (error) {
|
|
12531
|
+
await agentBinding.lease.release().catch(() => {
|
|
12532
|
+
});
|
|
12533
|
+
agentBinding = void 0;
|
|
12534
|
+
decline(`Agent message outbox initialization failed: ${errorMessage4(error)}`, true);
|
|
12535
|
+
return;
|
|
12536
|
+
}
|
|
12537
|
+
}
|
|
11184
12538
|
} else if (this.deps.gitWorkspaceManager && this.deps.gitWorkspaceStore) {
|
|
11185
12539
|
known = sessionRef ? await this.deps.sessionWorkspaces.get(sessionRef) : void 0;
|
|
11186
12540
|
const gitManager = this.deps.gitWorkspaceManager;
|
|
@@ -11364,9 +12718,9 @@ var TaskRunner = class {
|
|
|
11364
12718
|
}
|
|
11365
12719
|
const startInput = {
|
|
11366
12720
|
manifest,
|
|
11367
|
-
instruction: gitWorkspaceId ? prependGitWorkspaceGuidance(resolvedInstruction) : resolvedInstruction,
|
|
12721
|
+
instruction: agentBinding === void 0 ? gitWorkspaceId ? prependGitWorkspaceGuidance(resolvedInstruction) : resolvedInstruction : prependAgentMemoryGuidance(resolvedInstruction),
|
|
11368
12722
|
env,
|
|
11369
|
-
...
|
|
12723
|
+
...taskMcpServers === void 0 ? {} : { mcpServers: taskMcpServers },
|
|
11370
12724
|
approvalChannel: {
|
|
11371
12725
|
taskId,
|
|
11372
12726
|
storeDir: this.deps.storeDir,
|
|
@@ -11436,7 +12790,12 @@ var TaskRunner = class {
|
|
|
11436
12790
|
),
|
|
11437
12791
|
approvalQueue: [],
|
|
11438
12792
|
outputBytesSoFar: 0,
|
|
11439
|
-
startedAtMs: Date.now()
|
|
12793
|
+
startedAtMs: Date.now(),
|
|
12794
|
+
...messageRequirement === void 0 ? {} : {
|
|
12795
|
+
messageRequirement,
|
|
12796
|
+
messageOutbox: this.pendingMessageTasks.get(taskId).outbox
|
|
12797
|
+
},
|
|
12798
|
+
...terminalProjection === void 0 ? {} : { terminalProjection }
|
|
11440
12799
|
};
|
|
11441
12800
|
if (agentBinding !== void 0) {
|
|
11442
12801
|
try {
|
|
@@ -11464,6 +12823,7 @@ var TaskRunner = class {
|
|
|
11464
12823
|
return;
|
|
11465
12824
|
}
|
|
11466
12825
|
}
|
|
12826
|
+
const activatedMessageRecord = active.messageOutbox === void 0 ? void 0 : await active.messageOutbox.activate(taskId, session.sessionRef);
|
|
11467
12827
|
if (this.pendingCancelled.has(taskId)) {
|
|
11468
12828
|
const reason = this.pendingCancelled.get(taskId);
|
|
11469
12829
|
this.pendingCancelled.delete(taskId);
|
|
@@ -11489,6 +12849,10 @@ var TaskRunner = class {
|
|
|
11489
12849
|
this.deps.send(createEnvelope("task.started", {}, { taskId }));
|
|
11490
12850
|
agentLeaseTransferred = agentBinding !== void 0;
|
|
11491
12851
|
this.tasks.set(taskId, active);
|
|
12852
|
+
this.pendingMessageTasks.delete(taskId);
|
|
12853
|
+
if (active.messageOutbox !== void 0 && activatedMessageRecord !== void 0) {
|
|
12854
|
+
this.sendAgentMessageRecord(active.messageOutbox, activatedMessageRecord);
|
|
12855
|
+
}
|
|
11492
12856
|
if (payload.limits?.maxDurationMs !== void 0) {
|
|
11493
12857
|
this.armMaxDurationTimer(active, payload.limits.maxDurationMs);
|
|
11494
12858
|
}
|
|
@@ -11506,6 +12870,11 @@ var TaskRunner = class {
|
|
|
11506
12870
|
});
|
|
11507
12871
|
}
|
|
11508
12872
|
} finally {
|
|
12873
|
+
if (!agentLeaseTransferred) {
|
|
12874
|
+
this.pendingMessageTasks.delete(taskId);
|
|
12875
|
+
this.revokeAgentMessageContext(taskId);
|
|
12876
|
+
this.revokeAgentMemoryContext(taskId);
|
|
12877
|
+
}
|
|
11509
12878
|
if (agentBinding !== void 0 && !agentLeaseTransferred) {
|
|
11510
12879
|
await agentBinding.lease.release().catch(() => {
|
|
11511
12880
|
});
|
|
@@ -11513,6 +12882,153 @@ var TaskRunner = class {
|
|
|
11513
12882
|
this.inFlightOffers.delete(taskId);
|
|
11514
12883
|
}
|
|
11515
12884
|
}
|
|
12885
|
+
withAgentMessageMcp(existing, taskId, requirement) {
|
|
12886
|
+
if (requirement === void 0) return existing;
|
|
12887
|
+
const bin = this.deps.agentMessageMcpBin;
|
|
12888
|
+
const contextToken = `${randomUUID()}.${randomUUID()}`;
|
|
12889
|
+
this.revokeAgentMessageContext(taskId);
|
|
12890
|
+
this.messageContextByToken.set(contextToken, taskId);
|
|
12891
|
+
this.messageContextByTask.set(taskId, contextToken);
|
|
12892
|
+
return Object.freeze({
|
|
12893
|
+
...existing ?? {},
|
|
12894
|
+
[AGENT_MESSAGE_MCP_SERVER_NAME]: Object.freeze({
|
|
12895
|
+
command: bin.command,
|
|
12896
|
+
args: Object.freeze([...bin.args]),
|
|
12897
|
+
env: Object.freeze({
|
|
12898
|
+
BYOK_STORE_DIR: this.deps.storeDir,
|
|
12899
|
+
BYOK_PRODUCT_ID: this.deps.productId,
|
|
12900
|
+
BYOK_AGENT_MESSAGE_CONTEXT: contextToken
|
|
12901
|
+
})
|
|
12902
|
+
})
|
|
12903
|
+
});
|
|
12904
|
+
}
|
|
12905
|
+
revokeAgentMessageContext(taskId) {
|
|
12906
|
+
const token = this.messageContextByTask.get(taskId);
|
|
12907
|
+
if (token !== void 0) this.messageContextByToken.delete(token);
|
|
12908
|
+
this.messageContextByTask.delete(taskId);
|
|
12909
|
+
}
|
|
12910
|
+
/** Injected only after strict Agent admission; a host registry may never replace this reserved name. */
|
|
12911
|
+
withAgentMemoryMcp(existing, taskId, agentRef) {
|
|
12912
|
+
if (existing !== void 0 && Object.prototype.hasOwnProperty.call(existing, AGENT_MEMORY_MCP_SERVER_NAME)) {
|
|
12913
|
+
throw new Error(`MCP server name "${AGENT_MEMORY_MCP_SERVER_NAME}" is reserved by the daemon`);
|
|
12914
|
+
}
|
|
12915
|
+
const bin = this.deps.agentMemoryMcpBin;
|
|
12916
|
+
const contextToken = `${randomUUID()}.${randomUUID()}`;
|
|
12917
|
+
this.revokeAgentMemoryContext(taskId);
|
|
12918
|
+
this.memoryContextByToken.set(contextToken, Object.freeze({ taskId, agentRef: Object.freeze({ ...agentRef }) }));
|
|
12919
|
+
this.memoryContextByTask.set(taskId, contextToken);
|
|
12920
|
+
return Object.freeze({
|
|
12921
|
+
...existing ?? {},
|
|
12922
|
+
[AGENT_MEMORY_MCP_SERVER_NAME]: Object.freeze({
|
|
12923
|
+
command: bin.command,
|
|
12924
|
+
args: Object.freeze([...bin.args]),
|
|
12925
|
+
env: Object.freeze({
|
|
12926
|
+
BYOK_STORE_DIR: this.deps.storeDir,
|
|
12927
|
+
BYOK_PRODUCT_ID: this.deps.productId,
|
|
12928
|
+
BYOK_AGENT_MEMORY_CONTEXT: contextToken
|
|
12929
|
+
})
|
|
12930
|
+
})
|
|
12931
|
+
});
|
|
12932
|
+
}
|
|
12933
|
+
/** Reconstruct all sensitive context from the active sealed task, never from MCP/model arguments. */
|
|
12934
|
+
activeMemoryContext(contextToken) {
|
|
12935
|
+
const registered = this.memoryContextByToken.get(contextToken);
|
|
12936
|
+
if (registered === void 0 || this.memoryContextByTask.get(registered.taskId) !== contextToken || this.memoryClosingTasks.has(registered.taskId)) {
|
|
12937
|
+
throw new Error("invalid, expired, or quiescing Agent memory task context");
|
|
12938
|
+
}
|
|
12939
|
+
const active = this.tasks.get(registered.taskId);
|
|
12940
|
+
if (active === void 0 || active.finalizationStarted || active.agentBinding === void 0 || active.agentRef === void 0 || active.agentHandoff === void 0 || this.deps.tenantId === void 0 || active.agentRef.agentId !== registered.agentRef.agentId || active.agentRef.profileRevision !== registered.agentRef.profileRevision || active.agentBinding.resolution.agentRef.agentId !== registered.agentRef.agentId || active.agentBinding.resolution.agentRef.profileRevision !== registered.agentRef.profileRevision || active.agentHandoff.sessionRef !== active.session.sessionRef || active.agentHandoff.runtimeId !== active.adapter.descriptor.id || active.agentHandoff.cwd !== active.agentBinding.lease.cwd || active.agentBinding.lease.canonicalHome !== active.agentBinding.resolution.canonicalHome) {
|
|
12941
|
+
throw new Error("Agent memory context no longer matches its exact active task identity");
|
|
12942
|
+
}
|
|
12943
|
+
return Object.freeze({
|
|
12944
|
+
taskId: registered.taskId,
|
|
12945
|
+
tenantId: this.deps.tenantId,
|
|
12946
|
+
deviceId: this.deps.deviceId,
|
|
12947
|
+
agentRef: Object.freeze({ ...active.agentRef }),
|
|
12948
|
+
sessionRef: active.session.sessionRef,
|
|
12949
|
+
runtimeId: active.adapter.descriptor.id,
|
|
12950
|
+
canonicalHome: active.agentBinding.resolution.canonicalHome,
|
|
12951
|
+
leaseId: active.agentBinding.lease.leaseId,
|
|
12952
|
+
homeIdentity: active.agentBinding.lease.homeIdentity
|
|
12953
|
+
});
|
|
12954
|
+
}
|
|
12955
|
+
async runMemoryOperation(contextToken, operation) {
|
|
12956
|
+
let context = this.activeMemoryContext(contextToken);
|
|
12957
|
+
context = await this.bindAgentMemoryFilesystem(context);
|
|
12958
|
+
const current = this.activeMemoryContext(contextToken);
|
|
12959
|
+
if (current.taskId !== context.taskId || current.leaseId !== context.leaseId) throw new Error("Agent memory context changed during secure filesystem handshake");
|
|
12960
|
+
context = Object.freeze({ ...current, ...context.filesystem === void 0 ? {} : { filesystem: context.filesystem } });
|
|
12961
|
+
const pending = this.memoryInFlightByTask.get(context.taskId) ?? /* @__PURE__ */ new Set();
|
|
12962
|
+
this.memoryInFlightByTask.set(context.taskId, pending);
|
|
12963
|
+
const result = operation(context);
|
|
12964
|
+
pending.add(result);
|
|
12965
|
+
try {
|
|
12966
|
+
return await result;
|
|
12967
|
+
} finally {
|
|
12968
|
+
pending.delete(result);
|
|
12969
|
+
if (pending.size === 0) this.memoryInFlightByTask.delete(context.taskId);
|
|
12970
|
+
}
|
|
12971
|
+
}
|
|
12972
|
+
async quiesceAndSnapshotAgentMemory(active) {
|
|
12973
|
+
if (active.agentBinding === void 0 || active.agentRef === void 0 || active.agentHandoff === void 0) return;
|
|
12974
|
+
if (!isAgentMemorySecureFilesystemAvailable(this.deps.agentMemoryFilesystemHelperBin !== void 0)) return;
|
|
12975
|
+
if (this.deps.tenantId === void 0) {
|
|
12976
|
+
console.error(`[byok/client] Agent memory quiescent snapshot skipped for ${active.taskId}: exact tenant context is unavailable`);
|
|
12977
|
+
return;
|
|
12978
|
+
}
|
|
12979
|
+
this.memoryClosingTasks.add(active.taskId);
|
|
12980
|
+
try {
|
|
12981
|
+
await Promise.allSettled([...this.memoryInFlightByTask.get(active.taskId) ?? []]);
|
|
12982
|
+
const context = await this.bindAgentMemoryFilesystem({
|
|
12983
|
+
taskId: active.taskId,
|
|
12984
|
+
tenantId: this.deps.tenantId,
|
|
12985
|
+
deviceId: this.deps.deviceId,
|
|
12986
|
+
agentRef: active.agentRef,
|
|
12987
|
+
sessionRef: active.agentHandoff.sessionRef,
|
|
12988
|
+
runtimeId: active.agentHandoff.runtimeId,
|
|
12989
|
+
canonicalHome: active.agentBinding.resolution.canonicalHome,
|
|
12990
|
+
leaseId: active.agentBinding.lease.leaseId,
|
|
12991
|
+
homeIdentity: active.agentBinding.lease.homeIdentity
|
|
12992
|
+
});
|
|
12993
|
+
await snapshotAndProjectAgentMemory(context, this.deps.agentMemoryHostedProjection);
|
|
12994
|
+
} catch (error) {
|
|
12995
|
+
console.error(`[byok/client] Agent memory quiescent snapshot failed for ${active.taskId}: ${errorMessage4(error)}`);
|
|
12996
|
+
} finally {
|
|
12997
|
+
await this.closeAgentMemoryFilesystem(active.taskId);
|
|
12998
|
+
}
|
|
12999
|
+
}
|
|
13000
|
+
async bindAgentMemoryFilesystem(context) {
|
|
13001
|
+
const helperBin = this.deps.agentMemoryFilesystemHelperBin;
|
|
13002
|
+
if (helperBin === void 0) return context;
|
|
13003
|
+
let filesystem = this.memoryFilesystemByTask.get(context.taskId);
|
|
13004
|
+
if (filesystem === void 0) {
|
|
13005
|
+
filesystem = openAgentMemoryFilesystemHelper({
|
|
13006
|
+
helperBin,
|
|
13007
|
+
canonicalHome: context.canonicalHome,
|
|
13008
|
+
homeIdentity: context.homeIdentity
|
|
13009
|
+
});
|
|
13010
|
+
this.memoryFilesystemByTask.set(context.taskId, filesystem);
|
|
13011
|
+
void filesystem.catch(() => {
|
|
13012
|
+
if (this.memoryFilesystemByTask.get(context.taskId) === filesystem) this.memoryFilesystemByTask.delete(context.taskId);
|
|
13013
|
+
});
|
|
13014
|
+
}
|
|
13015
|
+
return Object.freeze({ ...context, filesystem: await filesystem });
|
|
13016
|
+
}
|
|
13017
|
+
async closeAgentMemoryFilesystem(taskId) {
|
|
13018
|
+
const filesystem = this.memoryFilesystemByTask.get(taskId);
|
|
13019
|
+
this.memoryFilesystemByTask.delete(taskId);
|
|
13020
|
+
if (filesystem === void 0) return;
|
|
13021
|
+
await filesystem.then((authority) => authority.close()).catch(() => {
|
|
13022
|
+
});
|
|
13023
|
+
}
|
|
13024
|
+
revokeAgentMemoryContext(taskId) {
|
|
13025
|
+
const token = this.memoryContextByTask.get(taskId);
|
|
13026
|
+
if (token !== void 0) this.memoryContextByToken.delete(token);
|
|
13027
|
+
this.memoryContextByTask.delete(taskId);
|
|
13028
|
+
this.memoryInFlightByTask.delete(taskId);
|
|
13029
|
+
this.memoryClosingTasks.delete(taskId);
|
|
13030
|
+
void this.closeAgentMemoryFilesystem(taskId);
|
|
13031
|
+
}
|
|
11516
13032
|
/** Protocol §7: an instruction too large to inline arrives as a `blobRef` — resolve it via the blob client rather than failing closed. */
|
|
11517
13033
|
async resolveInstruction(instruction) {
|
|
11518
13034
|
if (typeof instruction === "string") return instruction;
|
|
@@ -11590,42 +13106,13 @@ var TaskRunner = class {
|
|
|
11590
13106
|
if (!outcome.deliver) return;
|
|
11591
13107
|
await this.observeGit(active, "completed");
|
|
11592
13108
|
if (this.tasks.get(active.taskId) !== active || active.beingTornDown) return;
|
|
11593
|
-
if (
|
|
11594
|
-
|
|
11595
|
-
|
|
11596
|
-
|
|
11597
|
-
false
|
|
11598
|
-
);
|
|
13109
|
+
if (active.messageRequirement !== void 0 && active.messageAccepted !== true) {
|
|
13110
|
+
active.pendingMessageCompletion = { finalOutput, ...outcome.document === void 0 ? {} : { document: outcome.document } };
|
|
13111
|
+
const record = active.messageOutbox?.get(active.taskId);
|
|
13112
|
+
if (record !== void 0) this.sendAgentMessageRecord(active.messageOutbox, record);
|
|
11599
13113
|
return;
|
|
11600
13114
|
}
|
|
11601
|
-
|
|
11602
|
-
await this.persistAgentTerminalEvidence(active, "complete");
|
|
11603
|
-
this.deps.send(
|
|
11604
|
-
createEnvelope(
|
|
11605
|
-
"task.complete",
|
|
11606
|
-
{
|
|
11607
|
-
summary: finalOutput,
|
|
11608
|
-
sessionRef: active.session.sessionRef,
|
|
11609
|
-
// Spread rather than `document: outcome.document`, so a
|
|
11610
|
-
// completion with no document is the exact same payload it
|
|
11611
|
-
// was before this field existed — not one carrying an
|
|
11612
|
-
// explicit `document: undefined` key.
|
|
11613
|
-
//
|
|
11614
|
-
// `outcome.document` is the protocol's CANONICAL SNAPSHOT
|
|
11615
|
-
// (`checkResultDocument`), never the object the extractor
|
|
11616
|
-
// returned: pure data serializes identically at the root
|
|
11617
|
-
// (where it was measured) and nested inside this payload
|
|
11618
|
-
// (where the codec actually serializes it), so a contextual
|
|
11619
|
-
// `toJSON(key)` or an unstable getter cannot make the wire
|
|
11620
|
-
// bytes differ from what the cap gate approved.
|
|
11621
|
-
...outcome.document !== void 0 ? { document: outcome.document } : {},
|
|
11622
|
-
...this.terminalInferenceUsagePayload(active),
|
|
11623
|
-
...this.agentTerminalPayload(active)
|
|
11624
|
-
},
|
|
11625
|
-
{ taskId: active.taskId, sessionRef: active.session.sessionRef }
|
|
11626
|
-
)
|
|
11627
|
-
);
|
|
11628
|
-
await this.finish(active.taskId);
|
|
13115
|
+
await this.publishSuccessfulCompletion(active, finalOutput, outcome.document);
|
|
11629
13116
|
return;
|
|
11630
13117
|
}
|
|
11631
13118
|
if (event.type === "progress") {
|
|
@@ -11661,6 +13148,26 @@ var TaskRunner = class {
|
|
|
11661
13148
|
await this.fail(active.taskId, failure.reason, failure.retryable);
|
|
11662
13149
|
}
|
|
11663
13150
|
}
|
|
13151
|
+
async publishSuccessfulCompletion(active, finalOutput, document) {
|
|
13152
|
+
if (document !== void 0 && !this.hasResultDocumentCapability()) {
|
|
13153
|
+
await this.fail(
|
|
13154
|
+
active.taskId,
|
|
13155
|
+
`${RESULT_DOCUMENT_UNDELIVERABLE_REASON_PREFIX}: the connected server stopped advertising the result-document capability before this completion could be sent (a reconnect to an older server), so it would silently discard this document`,
|
|
13156
|
+
false
|
|
13157
|
+
);
|
|
13158
|
+
return;
|
|
13159
|
+
}
|
|
13160
|
+
if (!this.reserveSemanticTerminal(active)) return;
|
|
13161
|
+
await this.persistAgentTerminalEvidence(active, "complete");
|
|
13162
|
+
this.deps.send(createEnvelope("task.complete", {
|
|
13163
|
+
summary: finalOutput,
|
|
13164
|
+
sessionRef: active.session.sessionRef,
|
|
13165
|
+
...document !== void 0 ? { document } : {},
|
|
13166
|
+
...this.terminalInferenceUsagePayload(active),
|
|
13167
|
+
...this.agentTerminalPayload(active)
|
|
13168
|
+
}, { taskId: active.taskId, sessionRef: active.session.sessionRef }));
|
|
13169
|
+
await this.finish(active.taskId);
|
|
13170
|
+
}
|
|
11664
13171
|
/**
|
|
11665
13172
|
* Protocol §7: an `artifact` `AgentEvent` only names a file the runtime
|
|
11666
13173
|
* wrote into the task workspace (`name`/`contentType` — it carries no
|
|
@@ -12343,11 +13850,26 @@ var TaskRunner = class {
|
|
|
12343
13850
|
* docs/protocol.md §7.2.
|
|
12344
13851
|
*/
|
|
12345
13852
|
async resolveResultDocument(active, finalOutput) {
|
|
13853
|
+
if (active.terminalProjection?.mode === "none" || active.messageRequirement !== void 0 && active.terminalProjection === void 0) return { deliver: true };
|
|
12346
13854
|
const extract = this.deps.resultDocument?.extract;
|
|
12347
|
-
if (!extract)
|
|
13855
|
+
if (!extract) {
|
|
13856
|
+
if (active.terminalProjection?.mode === "result-document") {
|
|
13857
|
+
await this.fail(
|
|
13858
|
+
active.taskId,
|
|
13859
|
+
`${RESULT_DOCUMENT_UNDELIVERABLE_REASON_PREFIX}: the offer requires result contract ${active.terminalProjection.contract} but this daemon has no extractor`,
|
|
13860
|
+
false
|
|
13861
|
+
);
|
|
13862
|
+
return { deliver: false };
|
|
13863
|
+
}
|
|
13864
|
+
return { deliver: true };
|
|
13865
|
+
}
|
|
12348
13866
|
let document;
|
|
12349
13867
|
try {
|
|
12350
|
-
document = extract(finalOutput, {
|
|
13868
|
+
document = extract(finalOutput, {
|
|
13869
|
+
taskId: active.taskId,
|
|
13870
|
+
sessionRef: active.session.sessionRef,
|
|
13871
|
+
...active.terminalProjection === void 0 ? {} : { terminalProjection: active.terminalProjection }
|
|
13872
|
+
});
|
|
12351
13873
|
} catch (err) {
|
|
12352
13874
|
await this.fail(
|
|
12353
13875
|
active.taskId,
|
|
@@ -12364,7 +13886,17 @@ var TaskRunner = class {
|
|
|
12364
13886
|
);
|
|
12365
13887
|
return { deliver: false };
|
|
12366
13888
|
}
|
|
12367
|
-
if (document === void 0)
|
|
13889
|
+
if (document === void 0) {
|
|
13890
|
+
if (active.terminalProjection?.mode === "result-document") {
|
|
13891
|
+
await this.fail(
|
|
13892
|
+
active.taskId,
|
|
13893
|
+
`${RESULT_DOCUMENT_UNDELIVERABLE_REASON_PREFIX}: the required result contract ${active.terminalProjection.contract} produced no document`,
|
|
13894
|
+
false
|
|
13895
|
+
);
|
|
13896
|
+
return { deliver: false };
|
|
13897
|
+
}
|
|
13898
|
+
return { deliver: true };
|
|
13899
|
+
}
|
|
12368
13900
|
const check = checkResultDocument(document);
|
|
12369
13901
|
if (!check.ok) {
|
|
12370
13902
|
const detail = resultDocumentRejectionDetail(check);
|
|
@@ -12498,6 +14030,10 @@ var TaskRunner = class {
|
|
|
12498
14030
|
clearTimeout(active.maxDurationTimer);
|
|
12499
14031
|
active.maxDurationTimer = void 0;
|
|
12500
14032
|
}
|
|
14033
|
+
if (active.messageRetryTimer) {
|
|
14034
|
+
clearTimeout(active.messageRetryTimer);
|
|
14035
|
+
active.messageRetryTimer = void 0;
|
|
14036
|
+
}
|
|
12501
14037
|
active.batcher.stop();
|
|
12502
14038
|
this.addFinishedTaskId(taskId);
|
|
12503
14039
|
const queued = active.approvalQueue.splice(0);
|
|
@@ -12538,6 +14074,7 @@ var TaskRunner = class {
|
|
|
12538
14074
|
if (active.agentBinding !== void 0 && active.agentRef !== void 0 && active.agentHandoff !== void 0) {
|
|
12539
14075
|
const agentRef = active.agentRef;
|
|
12540
14076
|
const handoff = active.agentHandoff;
|
|
14077
|
+
await this.quiesceAndSnapshotAgentMemory(active);
|
|
12541
14078
|
if (!active.agentTerminalPersisted) {
|
|
12542
14079
|
const cause = active.terminalCause ?? "failed";
|
|
12543
14080
|
const result = await this.retryAgentTerminalEvidence(
|
|
@@ -12576,11 +14113,15 @@ var TaskRunner = class {
|
|
|
12576
14113
|
}
|
|
12577
14114
|
active.gitLease?.release();
|
|
12578
14115
|
this.tasks.delete(taskId);
|
|
14116
|
+
this.revokeAgentMessageContext(taskId);
|
|
14117
|
+
this.revokeAgentMemoryContext(taskId);
|
|
12579
14118
|
active.resolveSemanticTerminalSettled?.(leaseReleased);
|
|
12580
14119
|
return leaseReleased;
|
|
12581
14120
|
}
|
|
12582
14121
|
active.gitLease?.release();
|
|
12583
14122
|
this.tasks.delete(taskId);
|
|
14123
|
+
this.revokeAgentMessageContext(taskId);
|
|
14124
|
+
this.revokeAgentMemoryContext(taskId);
|
|
12584
14125
|
active.resolveSemanticTerminalSettled?.(true);
|
|
12585
14126
|
return true;
|
|
12586
14127
|
}
|
|
@@ -12744,9 +14285,9 @@ function metadataStatusEvent(event) {
|
|
|
12744
14285
|
return { ...event };
|
|
12745
14286
|
}
|
|
12746
14287
|
}
|
|
12747
|
-
var
|
|
14288
|
+
var encoder3 = new TextEncoder();
|
|
12748
14289
|
function eventBytes(event) {
|
|
12749
|
-
return
|
|
14290
|
+
return encoder3.encode(JSON.stringify(event)).length;
|
|
12750
14291
|
}
|
|
12751
14292
|
var AGENT_EGRESS_DIRECTORY = path3__default.join(".byok", "egress");
|
|
12752
14293
|
var AGENT_RELIABLE_SPOOL_FILENAME = "reliable-v1.jsonl";
|
|
@@ -12764,7 +14305,7 @@ var AgentReliableQuotaError = class extends AgentReliableSpoolError {
|
|
|
12764
14305
|
}
|
|
12765
14306
|
reason;
|
|
12766
14307
|
};
|
|
12767
|
-
function
|
|
14308
|
+
function sameAgentRef2(left, right) {
|
|
12768
14309
|
return left.agentId === right.agentId && left.profileRevision === right.profileRevision;
|
|
12769
14310
|
}
|
|
12770
14311
|
function stableRecordJson(value) {
|
|
@@ -12797,7 +14338,7 @@ function assertWireType(value) {
|
|
|
12797
14338
|
}
|
|
12798
14339
|
}
|
|
12799
14340
|
function sameRecord(record, candidate) {
|
|
12800
|
-
return record.wireType === candidate.wireType &&
|
|
14341
|
+
return record.wireType === candidate.wireType && sameAgentRef2(record.agentRef, candidate.agentRef) && record.tenantId === candidate.tenantId && record.policyRevision === candidate.policyRevision && record.eventId === candidate.eventId && record.cursor === candidate.cursor && record.payloadHash === candidate.payloadHash && record.byteCount === candidate.byteCount && record.sessionRef === candidate.sessionRef && record.taskId === candidate.taskId;
|
|
12801
14342
|
}
|
|
12802
14343
|
function parseEntry2(value) {
|
|
12803
14344
|
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new AgentReliableSpoolError("reliable spool entry is not an object");
|
|
@@ -12984,7 +14525,7 @@ var AgentReliableSpool = class _AgentReliableSpool {
|
|
|
12984
14525
|
return this.exclusive(async () => {
|
|
12985
14526
|
const record = this.pending.get(ack.eventId);
|
|
12986
14527
|
if (!record) return false;
|
|
12987
|
-
if (record.cursor !== ack.cursor || record.tenantId !== ack.tenantId || record.sessionRef !== ack.sessionRef || record.policyRevision !== ack.policyRevision || !
|
|
14528
|
+
if (record.cursor !== ack.cursor || record.tenantId !== ack.tenantId || record.sessionRef !== ack.sessionRef || record.policyRevision !== ack.policyRevision || !sameAgentRef2(record.agentRef, ack.agentRef)) return false;
|
|
12988
14529
|
await this.appendEntry({ schema: 1, kind: "ack", ack: Object.freeze({ ...ack, agentRef: { ...ack.agentRef } }) });
|
|
12989
14530
|
this.pending.delete(record.eventId);
|
|
12990
14531
|
if (this.logEntries >= 512) await this.compact();
|
|
@@ -13017,7 +14558,7 @@ var AgentReliableSpool = class _AgentReliableSpool {
|
|
|
13017
14558
|
} else {
|
|
13018
14559
|
const record = this.pending.get(entry.ack.eventId);
|
|
13019
14560
|
if (!record) throw new AgentReliableSpoolError("reliable spool acknowledges an unknown event");
|
|
13020
|
-
if (record.cursor !== entry.ack.cursor || record.tenantId !== entry.ack.tenantId || record.sessionRef !== entry.ack.sessionRef || record.policyRevision !== entry.ack.policyRevision || !
|
|
14561
|
+
if (record.cursor !== entry.ack.cursor || record.tenantId !== entry.ack.tenantId || record.sessionRef !== entry.ack.sessionRef || record.policyRevision !== entry.ack.policyRevision || !sameAgentRef2(record.agentRef, entry.ack.agentRef)) throw new AgentReliableSpoolError("reliable spool contains a mismatched ack");
|
|
13021
14562
|
this.pending.delete(record.eventId);
|
|
13022
14563
|
}
|
|
13023
14564
|
}
|
|
@@ -13679,7 +15220,7 @@ var AgentHomeProjectionCompletionError = class extends Error {
|
|
|
13679
15220
|
this.name = "AgentHomeProjectionCompletionError";
|
|
13680
15221
|
}
|
|
13681
15222
|
};
|
|
13682
|
-
function
|
|
15223
|
+
function sameAgentRef3(left, right) {
|
|
13683
15224
|
return left.agentId === right.agentId && left.profileRevision === right.profileRevision;
|
|
13684
15225
|
}
|
|
13685
15226
|
var AgentHomeProjectionCompletionClient = class {
|
|
@@ -13722,7 +15263,7 @@ var AgentHomeProjectionCompletionClient = class {
|
|
|
13722
15263
|
cause: error
|
|
13723
15264
|
});
|
|
13724
15265
|
}
|
|
13725
|
-
if (readback.tenantId !== this.options.tenantId || readback.deviceId !== this.options.deviceId || readback.requestId !== completion.requestId || !
|
|
15266
|
+
if (readback.tenantId !== this.options.tenantId || readback.deviceId !== this.options.deviceId || readback.requestId !== completion.requestId || !sameAgentRef3(readback.agentRef, completion.agentRef) || readback.projectionHash !== completion.projectionHash || readback.status !== completion.outcome || readback.completedAt === void 0) {
|
|
13726
15267
|
throw new AgentHomeProjectionCompletionError(
|
|
13727
15268
|
"Agent-home projection completion readback does not exactly match the authenticated request"
|
|
13728
15269
|
);
|
|
@@ -13730,6 +15271,15 @@ var AgentHomeProjectionCompletionClient = class {
|
|
|
13730
15271
|
return readback;
|
|
13731
15272
|
}
|
|
13732
15273
|
};
|
|
15274
|
+
function resolveAgentMessageMcpBin() {
|
|
15275
|
+
const script = path3__default.join(path3__default.dirname(fileURLToPath(import.meta.url)), "bin", "byok-agent-message-mcp.js");
|
|
15276
|
+
return Object.freeze({ command: process.execPath, args: Object.freeze([script]) });
|
|
15277
|
+
}
|
|
15278
|
+
function resolveAgentMemoryMcpBin(externalHelperConfigured = false) {
|
|
15279
|
+
if (!isAgentMemorySecureFilesystemAvailable(externalHelperConfigured)) return void 0;
|
|
15280
|
+
const script = path3__default.join(path3__default.dirname(fileURLToPath(import.meta.url)), "bin", "byok-agent-memory-mcp.js");
|
|
15281
|
+
return Object.freeze({ command: process.execPath, args: Object.freeze([script]) });
|
|
15282
|
+
}
|
|
13733
15283
|
var AGENT_CONTENT_READ_SURFACES = ["workspace", "transcript", "artifact"];
|
|
13734
15284
|
var AGENT_CONTENT_READ_CAPABILITIES = Object.freeze({
|
|
13735
15285
|
workspace: AGENT_CONTENT_WORKSPACE_READ_CAPABILITY,
|
|
@@ -13961,11 +15511,11 @@ var RootPolicyError = class extends Error {
|
|
|
13961
15511
|
this.reason = reason;
|
|
13962
15512
|
}
|
|
13963
15513
|
};
|
|
13964
|
-
function
|
|
15514
|
+
function sameAgentRef4(left, right) {
|
|
13965
15515
|
return left.agentId === right.agentId && left.profileRevision === right.profileRevision;
|
|
13966
15516
|
}
|
|
13967
15517
|
function sameSessionIdentity(left, right) {
|
|
13968
|
-
return
|
|
15518
|
+
return sameAgentRef4(left.agentRef, right.agentRef) && left.sessionRef === right.sessionRef && left.runtimeId === right.runtimeId && left.cwd === right.cwd;
|
|
13969
15519
|
}
|
|
13970
15520
|
function validateRequest(request) {
|
|
13971
15521
|
if (!isRecord5(request)) throw new AgentContentReadRequestError("content read request must be an object");
|
|
@@ -14237,7 +15787,7 @@ var AgentContentReadPolicyEngine = class {
|
|
|
14237
15787
|
}
|
|
14238
15788
|
async checkSessionIdentity(request, resolver, requiredCwd) {
|
|
14239
15789
|
const session = request.session;
|
|
14240
|
-
if (session === void 0 || !
|
|
15790
|
+
if (session === void 0 || !sameAgentRef4(session.agentRef, request.agentRef) || requiredCwd !== void 0 && session.cwd !== requiredCwd) {
|
|
14241
15791
|
return "identity-mismatch";
|
|
14242
15792
|
}
|
|
14243
15793
|
let expected;
|
|
@@ -14358,6 +15908,7 @@ function computeCapabilities(adapters, agentHomeConfigured = false, strictAgentO
|
|
|
14358
15908
|
if (adapters.some((adapter) => adapter.descriptor.capabilities.steer)) flags.push("steer");
|
|
14359
15909
|
flags.push("blob-upload");
|
|
14360
15910
|
flags.push("approval-targeting");
|
|
15911
|
+
flags.push(TERMINAL_PROJECTION_SELECTION_CAPABILITY);
|
|
14361
15912
|
const selectionAdapters = adapters.filter(
|
|
14362
15913
|
(adapter) => ALL_RUNTIME_IDS.includes(adapter.descriptor.id)
|
|
14363
15914
|
);
|
|
@@ -14376,6 +15927,9 @@ function computeCapabilities(adapters, agentHomeConfigured = false, strictAgentO
|
|
|
14376
15927
|
AGENT_EGRESS_RELIABLE_ACK_CAPABILITY,
|
|
14377
15928
|
AGENT_EGRESS_FRESH_SESSION_CAPABILITY
|
|
14378
15929
|
);
|
|
15930
|
+
if (agentHomeConfigured && adapters.some((adapter) => adapter.descriptor.capabilities.mcpToolsets === true)) {
|
|
15931
|
+
flags.push(AGENT_MESSAGE_EGRESS_CAPABILITY);
|
|
15932
|
+
}
|
|
14379
15933
|
}
|
|
14380
15934
|
if (contentReadPolicies !== void 0) {
|
|
14381
15935
|
for (const surface of Object.keys(AGENT_CONTENT_READ_CAPABILITIES)) {
|
|
@@ -14486,6 +16040,23 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
14486
16040
|
"DaemonConfig.agentHome and DaemonConfig.gitWorkspace are mutually exclusive; Agent home is the only workspace authority for Agent offers"
|
|
14487
16041
|
);
|
|
14488
16042
|
}
|
|
16043
|
+
if (config.agentMemory !== void 0 && config.agentHome === void 0) {
|
|
16044
|
+
throw new Error("DaemonConfig.agentMemory requires DaemonConfig.agentHome for the exact Agent memory authority");
|
|
16045
|
+
}
|
|
16046
|
+
if (config.agentMemoryFilesystem !== void 0 && config.agentHome === void 0) {
|
|
16047
|
+
throw new Error("DaemonConfig.agentMemoryFilesystem requires DaemonConfig.agentHome for the exact Agent memory authority");
|
|
16048
|
+
}
|
|
16049
|
+
if (config.agentMemoryFilesystem !== void 0 && (!path3__default.isAbsolute(config.agentMemoryFilesystem.helperBin) || /[\u0000\r\n]/u.test(config.agentMemoryFilesystem.helperBin))) {
|
|
16050
|
+
throw new Error("DaemonConfig.agentMemoryFilesystem.helperBin must be an explicit absolute executable path");
|
|
16051
|
+
}
|
|
16052
|
+
const externalAgentMemoryFilesystem = config.agentMemoryFilesystem !== void 0;
|
|
16053
|
+
if (externalAgentMemoryFilesystem && !isAgentMemoryFilesystemHelperSupported()) {
|
|
16054
|
+
throw new Error("DaemonConfig.agentMemoryFilesystem is not admitted on this platform without its native race proof");
|
|
16055
|
+
}
|
|
16056
|
+
if (config.agentMemory !== void 0 && !isAgentMemorySecureFilesystemAvailable(externalAgentMemoryFilesystem)) {
|
|
16057
|
+
throw new Error("DaemonConfig.agentMemory requires a platform with safe descriptor-relative filesystem operations");
|
|
16058
|
+
}
|
|
16059
|
+
const agentMemoryMcpBin = config.agentHome === void 0 ? void 0 : resolveAgentMemoryMcpBin(externalAgentMemoryFilesystem);
|
|
14489
16060
|
const localAgentRelease = resolveLocalAgentReleaseIdentity(config.localAgentRelease);
|
|
14490
16061
|
const toolsetRegistry = new McpToolsetRegistry(config.mcpToolsets);
|
|
14491
16062
|
validatePiByokLauncherConfig(config.piByokLauncher);
|
|
@@ -14917,6 +16488,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
14917
16488
|
approvalRegistry,
|
|
14918
16489
|
storeDir,
|
|
14919
16490
|
productId: config.productId,
|
|
16491
|
+
tenantId: record.tenantId,
|
|
14920
16492
|
// U2 terminal inference usage consumes the one U4a-resolved,
|
|
14921
16493
|
// process-immutable identity captured above. This is composition-only:
|
|
14922
16494
|
// TaskRunner receives no config, manifest, or second identity resolver.
|
|
@@ -14931,6 +16503,10 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
14931
16503
|
// doc comment. Spread rather than assigned so an unconfigured daemon
|
|
14932
16504
|
// builds the exact `deps` object it did before this seam existed.
|
|
14933
16505
|
...config.resultDocument ? { resultDocument: config.resultDocument } : {},
|
|
16506
|
+
...agentMemoryMcpBin === void 0 ? {} : { agentMemoryMcpBin },
|
|
16507
|
+
...config.agentMemoryFilesystem === void 0 ? {} : { agentMemoryFilesystemHelperBin: path3__default.resolve(config.agentMemoryFilesystem.helperBin) },
|
|
16508
|
+
...config.agentHome !== void 0 && config.agentEgress !== void 0 ? { agentMessageMcpBin: resolveAgentMessageMcpBin() } : {},
|
|
16509
|
+
...config.agentMemory === void 0 ? {} : { agentMemoryHostedProjection: config.agentMemory },
|
|
14934
16510
|
// M4 Phase 3 hardening: bridges TaskRunner's stale-approval-race
|
|
14935
16511
|
// finding out to the SAME local observability seam every other
|
|
14936
16512
|
// daemon-local event already uses (see observer.ts's own module doc
|
|
@@ -14961,6 +16537,9 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
14961
16537
|
...activePressureEngine ? { admissionGuard: () => activePressureEngine.admissionGuard() } : {}
|
|
14962
16538
|
};
|
|
14963
16539
|
runner = new TaskRunner(deps);
|
|
16540
|
+
if (config.agentHome !== void 0 && config.agentEgress !== void 0) {
|
|
16541
|
+
await runner.recoverAgentMessageOutboxes(path3__default.join(config.agentHome.hostStorageRoot, "agents"));
|
|
16542
|
+
}
|
|
14964
16543
|
const handleAgentHomeProjectionEnvelope = async (envelope) => {
|
|
14965
16544
|
if (envelope.type !== "agent.home.projection") return false;
|
|
14966
16545
|
if (agentHomeManager === void 0 || agentHomeProjectionCompletion === void 0) {
|
|
@@ -15143,6 +16722,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
15143
16722
|
observer.handleInboundEnvelope(envelope);
|
|
15144
16723
|
if (envelope.type === "agent.home.projection") return handleAgentHomeProjectionEnvelope(envelope).then(() => void 0);
|
|
15145
16724
|
if (envelope.type === "agent.egress.ack") return handleAgentEgressEnvelope(envelope).then(() => void 0);
|
|
16725
|
+
if (envelope.type === "agent.message.disposition") return runner?.handleEnvelope(envelope) ?? Promise.resolve();
|
|
15146
16726
|
if (envelope.type === "agent.content.read") return handleAgentContentReadEnvelope(envelope).then(() => void 0);
|
|
15147
16727
|
return runner?.handleEnvelope(envelope) ?? Promise.resolve();
|
|
15148
16728
|
},
|
|
@@ -15150,6 +16730,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
15150
16730
|
const wasSettled = connectionState === "open" || connectionState === "degraded";
|
|
15151
16731
|
connectionState = state;
|
|
15152
16732
|
observer.noteConnectionState(state);
|
|
16733
|
+
if (!wasSettled && (state === "open" || state === "degraded")) runner?.retryRecoveredAgentMessages();
|
|
15153
16734
|
if (!wasSettled && (state === "open" || state === "degraded")) runPresenceDiscovery();
|
|
15154
16735
|
},
|
|
15155
16736
|
backoff: overrides.backoff,
|
|
@@ -15168,6 +16749,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
15168
16749
|
});
|
|
15169
16750
|
await connection.start();
|
|
15170
16751
|
await connection.waitForAck();
|
|
16752
|
+
runner.retryRecoveredAgentMessages();
|
|
15171
16753
|
for (const record2 of agentEgress.retryableReliableRecords(connection.getServerCapabilities())) {
|
|
15172
16754
|
dispatchReliableRecord(record2);
|
|
15173
16755
|
}
|
|
@@ -15515,6 +17097,24 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
|
|
|
15515
17097
|
if (!runner) throw new ControlError("not_found", "daemon is not started");
|
|
15516
17098
|
return runner.requestApproval(parsed.taskId, parsed.summary);
|
|
15517
17099
|
},
|
|
17100
|
+
"agent_messages.publish": (params) => {
|
|
17101
|
+
const parsed = parseAgentMessagePublishParams(params);
|
|
17102
|
+
if (!parsed) throw new ControlError("bad_request", "agent_messages.publish requires exactly {contextToken,contentType,body}");
|
|
17103
|
+
if (!runner) throw new ControlError("not_found", "daemon is not started");
|
|
17104
|
+
return runner.publishAgentMessage(parsed);
|
|
17105
|
+
},
|
|
17106
|
+
"agent_memory.recall": (params) => {
|
|
17107
|
+
const parsed = parseAgentMemoryRecallParams(params);
|
|
17108
|
+
if (!parsed) throw new ControlError("bad_request", "agent_memory.recall requires exactly {contextToken,path,ifRevision?}");
|
|
17109
|
+
if (!runner) throw new ControlError("not_found", "daemon is not started");
|
|
17110
|
+
return runner.recallAgentMemory(parsed);
|
|
17111
|
+
},
|
|
17112
|
+
"agent_memory.save": (params) => {
|
|
17113
|
+
const parsed = parseAgentMemorySaveParams(params);
|
|
17114
|
+
if (!parsed) throw new ControlError("bad_request", "agent_memory.save requires exactly {contextToken,op,path,expectedRevision,content?}");
|
|
17115
|
+
if (!runner) throw new ControlError("not_found", "daemon is not started");
|
|
17116
|
+
return runner.saveAgentMemory(parsed);
|
|
17117
|
+
},
|
|
15518
17118
|
/**
|
|
15519
17119
|
* Plan `device-assertion-broker`: mint one short-lived, audience-scoped
|
|
15520
17120
|
* device assertion for a sibling local process.
|
|
@@ -16088,7 +17688,7 @@ ${args.map((a) => ` ${plistString(a)}`).join("\n")}
|
|
|
16088
17688
|
}
|
|
16089
17689
|
function createLaunchdLifecycle(def, deps = {}) {
|
|
16090
17690
|
const run = deps.run ?? defaultRunner;
|
|
16091
|
-
const
|
|
17691
|
+
const fs30 = deps.fs ?? promises;
|
|
16092
17692
|
const homedir = deps.homedir ?? (() => os__default.homedir());
|
|
16093
17693
|
const getuid = deps.getuid ?? (() => {
|
|
16094
17694
|
if (typeof process.getuid !== "function") {
|
|
@@ -16102,7 +17702,7 @@ function createLaunchdLifecycle(def, deps = {}) {
|
|
|
16102
17702
|
const serviceTarget = () => `${domainTarget()}/${label}`;
|
|
16103
17703
|
async function fileExists(p) {
|
|
16104
17704
|
try {
|
|
16105
|
-
await
|
|
17705
|
+
await fs30.stat(p);
|
|
16106
17706
|
return true;
|
|
16107
17707
|
} catch {
|
|
16108
17708
|
return false;
|
|
@@ -16110,9 +17710,9 @@ function createLaunchdLifecycle(def, deps = {}) {
|
|
|
16110
17710
|
}
|
|
16111
17711
|
async function writePlist(program) {
|
|
16112
17712
|
const xml = generateLaunchdPlist({ label, program, logDir: def.logDir });
|
|
16113
|
-
await
|
|
16114
|
-
await
|
|
16115
|
-
await
|
|
17713
|
+
await fs30.mkdir(path3__default.dirname(plistPath()), { recursive: true });
|
|
17714
|
+
await fs30.mkdir(def.logDir, { recursive: true });
|
|
17715
|
+
await fs30.writeFile(plistPath(), xml, "utf8");
|
|
16116
17716
|
}
|
|
16117
17717
|
async function install(opts = {}) {
|
|
16118
17718
|
await writePlist(opts.program ?? def.program);
|
|
@@ -16123,7 +17723,7 @@ function createLaunchdLifecycle(def, deps = {}) {
|
|
|
16123
17723
|
}
|
|
16124
17724
|
async function uninstall() {
|
|
16125
17725
|
await runIdempotent(run, "launchctl", ["bootout", serviceTarget()], "launchctl bootout", LAUNCHD_NOT_LOADED);
|
|
16126
|
-
await
|
|
17726
|
+
await fs30.rm(plistPath(), { force: true });
|
|
16127
17727
|
}
|
|
16128
17728
|
async function start() {
|
|
16129
17729
|
if (!await fileExists(plistPath())) {
|
|
@@ -16207,14 +17807,14 @@ WantedBy=default.target
|
|
|
16207
17807
|
}
|
|
16208
17808
|
function createSystemdLifecycle(def, deps = {}) {
|
|
16209
17809
|
const run = deps.run ?? defaultRunner;
|
|
16210
|
-
const
|
|
17810
|
+
const fs30 = deps.fs ?? promises;
|
|
16211
17811
|
const homedir = deps.homedir ?? (() => os__default.homedir());
|
|
16212
17812
|
const name = sanitizeServiceName(def.name);
|
|
16213
17813
|
const unitName = `${name}.service`;
|
|
16214
17814
|
const unitPath = () => path3__default.join(homedir(), ".config", "systemd", "user", unitName);
|
|
16215
17815
|
async function fileExists(p) {
|
|
16216
17816
|
try {
|
|
16217
|
-
await
|
|
17817
|
+
await fs30.stat(p);
|
|
16218
17818
|
return true;
|
|
16219
17819
|
} catch {
|
|
16220
17820
|
return false;
|
|
@@ -16222,9 +17822,9 @@ function createSystemdLifecycle(def, deps = {}) {
|
|
|
16222
17822
|
}
|
|
16223
17823
|
async function writeUnit(program) {
|
|
16224
17824
|
const unit = generateSystemdUnit({ name, displayName: def.displayName ?? def.name, program, logDir: def.logDir });
|
|
16225
|
-
await
|
|
16226
|
-
await
|
|
16227
|
-
await
|
|
17825
|
+
await fs30.mkdir(path3__default.dirname(unitPath()), { recursive: true });
|
|
17826
|
+
await fs30.mkdir(def.logDir, { recursive: true });
|
|
17827
|
+
await fs30.writeFile(unitPath(), unit, "utf8");
|
|
16228
17828
|
}
|
|
16229
17829
|
async function install(opts = {}) {
|
|
16230
17830
|
await writeUnit(opts.program ?? def.program);
|
|
@@ -16233,7 +17833,7 @@ function createSystemdLifecycle(def, deps = {}) {
|
|
|
16233
17833
|
}
|
|
16234
17834
|
async function uninstall() {
|
|
16235
17835
|
await runIdempotent(run, "systemctl", ["--user", "disable", "--now", unitName], "systemctl disable --now", SYSTEMD_NOT_LOADED);
|
|
16236
|
-
await
|
|
17836
|
+
await fs30.rm(unitPath(), { force: true });
|
|
16237
17837
|
await run("systemctl", ["--user", "daemon-reload"]);
|
|
16238
17838
|
}
|
|
16239
17839
|
async function start() {
|
|
@@ -16296,7 +17896,7 @@ ${argXml}${cwdXml}
|
|
|
16296
17896
|
}
|
|
16297
17897
|
function createWinswLifecycle(def, deps = {}) {
|
|
16298
17898
|
const run = deps.run ?? defaultRunner;
|
|
16299
|
-
const
|
|
17899
|
+
const fs30 = deps.fs ?? promises;
|
|
16300
17900
|
const windows = def.windows;
|
|
16301
17901
|
if (!windows) {
|
|
16302
17902
|
throw new Error("WinSW service lifecycle requires `ServiceDefinition.windows.winswBin` (the product-bundled WinSW executable path)");
|
|
@@ -16308,7 +17908,7 @@ function createWinswLifecycle(def, deps = {}) {
|
|
|
16308
17908
|
const xmlPath = path3__default.join(installDir, `${id}.xml`);
|
|
16309
17909
|
async function fileExists(p) {
|
|
16310
17910
|
try {
|
|
16311
|
-
await
|
|
17911
|
+
await fs30.stat(p);
|
|
16312
17912
|
return true;
|
|
16313
17913
|
} catch {
|
|
16314
17914
|
return false;
|
|
@@ -16316,10 +17916,10 @@ function createWinswLifecycle(def, deps = {}) {
|
|
|
16316
17916
|
}
|
|
16317
17917
|
async function writeFiles(program) {
|
|
16318
17918
|
const xml = generateWinswXml({ id, displayName: def.displayName ?? def.name, program, logDir: def.logDir });
|
|
16319
|
-
await
|
|
16320
|
-
await
|
|
16321
|
-
await
|
|
16322
|
-
await
|
|
17919
|
+
await fs30.mkdir(installDir, { recursive: true });
|
|
17920
|
+
await fs30.mkdir(def.logDir, { recursive: true });
|
|
17921
|
+
await fs30.copyFile(winswBin, exePath);
|
|
17922
|
+
await fs30.writeFile(xmlPath, xml, "utf8");
|
|
16323
17923
|
}
|
|
16324
17924
|
async function install(opts = {}) {
|
|
16325
17925
|
await writeFiles(opts.program ?? def.program);
|
|
@@ -16329,8 +17929,8 @@ function createWinswLifecycle(def, deps = {}) {
|
|
|
16329
17929
|
async function uninstall() {
|
|
16330
17930
|
await runIdempotent(run, exePath, ["stop"], "winsw stop", WINSW_NOT_INSTALLED);
|
|
16331
17931
|
await runIdempotent(run, exePath, ["uninstall"], "winsw uninstall", WINSW_NOT_INSTALLED);
|
|
16332
|
-
await
|
|
16333
|
-
await
|
|
17932
|
+
await fs30.rm(exePath, { force: true });
|
|
17933
|
+
await fs30.rm(xmlPath, { force: true });
|
|
16334
17934
|
}
|
|
16335
17935
|
async function start() {
|
|
16336
17936
|
if (!await fileExists(xmlPath)) {
|
|
@@ -16375,7 +17975,7 @@ function createServiceLifecycle(def, opts = {}) {
|
|
|
16375
17975
|
|
|
16376
17976
|
// src/bin/official-release.ts
|
|
16377
17977
|
var OFFICIAL_LOCAL_AGENT_RELEASE = resolveLocalAgentReleaseIdentity({
|
|
16378
|
-
version: "0.
|
|
17978
|
+
version: "0.9.0"
|
|
16379
17979
|
});
|
|
16380
17980
|
|
|
16381
17981
|
// src/bin/config.ts
|
|
@@ -18590,8 +20190,8 @@ async function runTasksFollowCommand(config, deps) {
|
|
|
18590
20190
|
});
|
|
18591
20191
|
return;
|
|
18592
20192
|
}
|
|
18593
|
-
const
|
|
18594
|
-
await followAuditLog(
|
|
20193
|
+
const path41 = auditLogPath(storeDir);
|
|
20194
|
+
await followAuditLog(path41, (event) => log(formatDaemonEventLine(event)), {
|
|
18595
20195
|
signal: deps.signal,
|
|
18596
20196
|
pollIntervalMs: deps.pollIntervalMs,
|
|
18597
20197
|
fromEnd: true
|