@agentconnect.md/daemon 1.0.0-rc.62 → 1.0.0-rc.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +325 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { EventEmitter } from "node:events";
|
|
|
4
4
|
import childProcess, { execFile, spawn } from "node:child_process";
|
|
5
5
|
import * as sp from "node:path";
|
|
6
6
|
import path, { delimiter, dirname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
|
|
7
|
-
import fs, { accessSync, chmodSync, constants, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, stat, unlinkSync, unwatchFile, watch, watchFile, writeFileSync } from "node:fs";
|
|
7
|
+
import fs, { accessSync, chmodSync, constants, existsSync, mkdirSync, mkdtempSync, promises, readFileSync, readdirSync, rmSync, stat, unlinkSync, unwatchFile, watch, watchFile, writeFileSync } from "node:fs";
|
|
8
8
|
import process$1 from "node:process";
|
|
9
9
|
import { stripVTControlCharacters } from "node:util";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
@@ -13254,6 +13254,72 @@ const SessionHistoryPage = object({
|
|
|
13254
13254
|
nextCursor: string().optional()
|
|
13255
13255
|
});
|
|
13256
13256
|
//#endregion
|
|
13257
|
+
//#region ../protocol/dist/frames/workspace.js
|
|
13258
|
+
/**
|
|
13259
|
+
* Agent workspace file browsing (C→D REQ → REP) — the console's on-demand pulls.
|
|
13260
|
+
*
|
|
13261
|
+
* The CP stores NO workspace data — directory listings and file bytes are pulled
|
|
13262
|
+
* live from the owning daemon and proxied to the console, never persisted
|
|
13263
|
+
* (body-locality). Missing paths are DATA (`exists:false`), not errors; only
|
|
13264
|
+
* path-containment violations (`BAD_PAYLOAD`) and unexpected fs failures
|
|
13265
|
+
* (`INTERNAL`) come back as `error` frames.
|
|
13266
|
+
*
|
|
13267
|
+
* - `workspace/list`: one cursor-paginated page of a directory's entries.
|
|
13268
|
+
* - `workspace/read`: one byte slice of a file. `limit` is only a ceiling — the
|
|
13269
|
+
* daemon returns FEWER bytes when needed to keep the JSON-escaped REP under the
|
|
13270
|
+
* 256 KiB frame cap (control bytes escape to `\uXXXX`, a 6× blowup), and always
|
|
13271
|
+
* ends the slice on a UTF-8 character boundary. `nextOffset` is the authoritative
|
|
13272
|
+
* byte offset to request next — callers must NOT recompute it from the decoded
|
|
13273
|
+
* `content` (a split multi-byte char would make the byte count drift).
|
|
13274
|
+
*/
|
|
13275
|
+
/** One directory entry in a workspace listing (name-only; not a full path). */
|
|
13276
|
+
const WorkspaceEntry = object({
|
|
13277
|
+
name: string(),
|
|
13278
|
+
type: _enum([
|
|
13279
|
+
"dir",
|
|
13280
|
+
"file",
|
|
13281
|
+
"symlink",
|
|
13282
|
+
"other"
|
|
13283
|
+
]),
|
|
13284
|
+
size: number().int().nonnegative().optional(),
|
|
13285
|
+
mtime: string().optional()
|
|
13286
|
+
});
|
|
13287
|
+
/** C→D REQ: list one page of a directory in the agent's workspace. */
|
|
13288
|
+
const WorkspaceListReq = object({
|
|
13289
|
+
agentId: string().min(1),
|
|
13290
|
+
path: string().default(""),
|
|
13291
|
+
cursor: string().optional(),
|
|
13292
|
+
limit: number().int().positive().max(500).default(200)
|
|
13293
|
+
});
|
|
13294
|
+
/** D→C REP (corr = the req id): a page of entries + the cursor for the next page. */
|
|
13295
|
+
const WorkspaceListPage = object({
|
|
13296
|
+
agentId: string(),
|
|
13297
|
+
path: string(),
|
|
13298
|
+
exists: boolean(),
|
|
13299
|
+
entries: array(WorkspaceEntry),
|
|
13300
|
+
nextCursor: string().optional()
|
|
13301
|
+
});
|
|
13302
|
+
/** C→D REQ: read one byte slice of a file in the agent's workspace. */
|
|
13303
|
+
const WorkspaceReadReq = object({
|
|
13304
|
+
agentId: string().min(1),
|
|
13305
|
+
path: string().min(1),
|
|
13306
|
+
offset: number().int().nonnegative().default(0),
|
|
13307
|
+
limit: number().int().positive().max(65536).default(65536)
|
|
13308
|
+
});
|
|
13309
|
+
/** D→C REP (corr = the req id): the file slice (or `exists:false` / binary-detected). */
|
|
13310
|
+
const WorkspaceReadContent = object({
|
|
13311
|
+
agentId: string(),
|
|
13312
|
+
path: string(),
|
|
13313
|
+
exists: boolean(),
|
|
13314
|
+
size: number().int().nonnegative().optional(),
|
|
13315
|
+
mtime: string().optional(),
|
|
13316
|
+
encoding: _enum(["utf8", "none"]).optional(),
|
|
13317
|
+
content: string().optional(),
|
|
13318
|
+
offset: number().int().nonnegative().optional(),
|
|
13319
|
+
nextOffset: number().int().nonnegative().optional(),
|
|
13320
|
+
truncated: boolean().optional()
|
|
13321
|
+
});
|
|
13322
|
+
//#endregion
|
|
13257
13323
|
//#region ../protocol/dist/frames/telemetry.js
|
|
13258
13324
|
/**
|
|
13259
13325
|
* Telemetry & facts (D→C) — protocol §7.
|
|
@@ -13373,6 +13439,10 @@ const FRAME_SCHEMAS = {
|
|
|
13373
13439
|
"session/list/page": SessionListPage,
|
|
13374
13440
|
"session/history": SessionHistoryReq,
|
|
13375
13441
|
"session/history/page": SessionHistoryPage,
|
|
13442
|
+
"workspace/list": WorkspaceListReq,
|
|
13443
|
+
"workspace/list/page": WorkspaceListPage,
|
|
13444
|
+
"workspace/read": WorkspaceReadReq,
|
|
13445
|
+
"workspace/read/content": WorkspaceReadContent,
|
|
13376
13446
|
"config/push": object({ keys: record(string(), unknown()) }),
|
|
13377
13447
|
"daemon/restart": object({
|
|
13378
13448
|
reason: string(),
|
|
@@ -13468,6 +13538,10 @@ discriminatedUnion("type", [
|
|
|
13468
13538
|
frame("session/list/page", FRAME_SCHEMAS["session/list/page"]),
|
|
13469
13539
|
frame("session/history", FRAME_SCHEMAS["session/history"]),
|
|
13470
13540
|
frame("session/history/page", FRAME_SCHEMAS["session/history/page"]),
|
|
13541
|
+
frame("workspace/list", FRAME_SCHEMAS["workspace/list"]),
|
|
13542
|
+
frame("workspace/list/page", FRAME_SCHEMAS["workspace/list/page"]),
|
|
13543
|
+
frame("workspace/read", FRAME_SCHEMAS["workspace/read"]),
|
|
13544
|
+
frame("workspace/read/content", FRAME_SCHEMAS["workspace/read/content"]),
|
|
13471
13545
|
frame("config/push", FRAME_SCHEMAS["config/push"]),
|
|
13472
13546
|
frame("daemon/restart", FRAME_SCHEMAS["daemon/restart"]),
|
|
13473
13547
|
frame("daemon/upgrade", FRAME_SCHEMAS["daemon/upgrade"]),
|
|
@@ -81991,6 +82065,237 @@ function makeLogger(level = "info") {
|
|
|
81991
82065
|
};
|
|
81992
82066
|
}
|
|
81993
82067
|
//#endregion
|
|
82068
|
+
//#region src/cp/workspace-reader.ts
|
|
82069
|
+
/**
|
|
82070
|
+
* `WorkspaceReader` — the read-only seam answering the CP's `workspace/list` and
|
|
82071
|
+
* `workspace/read` REQs from the agent's local workspace directory. File bytes
|
|
82072
|
+
* live only on the daemon (§1/§12); this streams single pages/slices to the CP,
|
|
82073
|
+
* never whole trees or files.
|
|
82074
|
+
*
|
|
82075
|
+
* Containment is to `workspace.path` EXACTLY — the parent directory holds
|
|
82076
|
+
* `agent.json` and `.env` secrets, so escaping even one level is a secret leak.
|
|
82077
|
+
* Every request path is (1) rejected if absolute, (2) lexically resolved against
|
|
82078
|
+
* the root and prefix-checked, then (3) canonicalised with `realpath` and
|
|
82079
|
+
* re-verified so symlinks (including an intermediate directory component swapped
|
|
82080
|
+
* to a symlink after the check) cannot smuggle a target outside the root; all
|
|
82081
|
+
* I/O runs on the canonical path. Violations throw `WorkspaceViolationError`,
|
|
82082
|
+
* which the dispatcher maps to a `BAD_PAYLOAD` error frame; missing paths are
|
|
82083
|
+
* data (`exists:false`), not errors. Never log file contents.
|
|
82084
|
+
*
|
|
82085
|
+
* Frame-size safety: every REP must serialise under the 256 KiB wire cap. JSON
|
|
82086
|
+
* escaping of control bytes is a 6× blowup, so `read` bounds the slice by the
|
|
82087
|
+
* *encoded* reply size (not the raw byte `limit`) and `list` stops a page early
|
|
82088
|
+
* when the encoded entries would overflow — both leave headroom for the envelope.
|
|
82089
|
+
*/
|
|
82090
|
+
/** Bytes sniffed from the head of a file for binary (NUL byte) detection. */
|
|
82091
|
+
const SNIFF_BYTES = 8192;
|
|
82092
|
+
/** Encoded-payload ceiling, leaving headroom under MAX_FRAME_BYTES for the
|
|
82093
|
+
* envelope (id/ts/type/corr + fencing ext, well under 4 KiB). */
|
|
82094
|
+
const REPLY_BUDGET = MAX_FRAME_BYTES - 4096;
|
|
82095
|
+
/** Path-containment / bad-request violation → `BAD_PAYLOAD` on the wire. */
|
|
82096
|
+
var WorkspaceViolationError = class extends Error {
|
|
82097
|
+
constructor(message) {
|
|
82098
|
+
super(message);
|
|
82099
|
+
this.name = "WorkspaceViolationError";
|
|
82100
|
+
}
|
|
82101
|
+
};
|
|
82102
|
+
function isErrno(err, code) {
|
|
82103
|
+
return err?.code === code;
|
|
82104
|
+
}
|
|
82105
|
+
function under(root, p) {
|
|
82106
|
+
return p === root || p.startsWith(root + sp.sep);
|
|
82107
|
+
}
|
|
82108
|
+
/** Largest index ≤ `len` that lands on a UTF-8 character boundary — i.e. never
|
|
82109
|
+
* cuts a multi-byte sequence. If the slice's trailing char is incomplete, the
|
|
82110
|
+
* boundary is placed before that char (its bytes belong to the next slice). */
|
|
82111
|
+
function utf8Boundary(buf, len) {
|
|
82112
|
+
if (len >= buf.length) len = buf.length;
|
|
82113
|
+
if (len <= 0) return 0;
|
|
82114
|
+
let start = len - 1;
|
|
82115
|
+
while (start > 0 && ((buf[start] ?? 0) & 192) === 128) start--;
|
|
82116
|
+
const lead = buf[start] ?? 0;
|
|
82117
|
+
const seqLen = lead < 128 ? 1 : (lead & 224) === 192 ? 2 : (lead & 240) === 224 ? 3 : (lead & 248) === 240 ? 4 : 1;
|
|
82118
|
+
return start + seqLen <= len ? len : start;
|
|
82119
|
+
}
|
|
82120
|
+
/** The encoded size of the payload the wire will carry (JSON.stringify matches
|
|
82121
|
+
* the codec's `encode`), measured in bytes. */
|
|
82122
|
+
function encodedBytes(payload) {
|
|
82123
|
+
return Buffer.byteLength(JSON.stringify(payload));
|
|
82124
|
+
}
|
|
82125
|
+
function createWorkspaceReader(workspaceRootByAgent) {
|
|
82126
|
+
function rootFor(agentId) {
|
|
82127
|
+
const root = workspaceRootByAgent(agentId);
|
|
82128
|
+
if (!root) throw new WorkspaceViolationError(`unknown agent "${agentId}"`);
|
|
82129
|
+
return root;
|
|
82130
|
+
}
|
|
82131
|
+
/**
|
|
82132
|
+
* Lexical containment. Rejects absolute paths and `..` escapes; returns the
|
|
82133
|
+
* lexically-resolved path plus the canonical (realpath'd) root, or `null` for
|
|
82134
|
+
* `realRoot` when the root does not exist yet (callers report `exists:false`).
|
|
82135
|
+
*/
|
|
82136
|
+
async function resolveContained(root, relPath) {
|
|
82137
|
+
if (sp.isAbsolute(relPath)) throw new WorkspaceViolationError("absolute paths are not allowed");
|
|
82138
|
+
const resolved = sp.resolve(root, relPath);
|
|
82139
|
+
if (!under(root, resolved)) throw new WorkspaceViolationError("path escapes the workspace root");
|
|
82140
|
+
try {
|
|
82141
|
+
return {
|
|
82142
|
+
resolved,
|
|
82143
|
+
realRoot: await promises.realpath(root)
|
|
82144
|
+
};
|
|
82145
|
+
} catch (err) {
|
|
82146
|
+
if (isErrno(err, "ENOENT")) return {
|
|
82147
|
+
resolved,
|
|
82148
|
+
realRoot: null
|
|
82149
|
+
};
|
|
82150
|
+
throw err;
|
|
82151
|
+
}
|
|
82152
|
+
}
|
|
82153
|
+
/** Canonicalise an existing target and re-verify containment on the REAL path,
|
|
82154
|
+
* closing the check-vs-use gap (a symlinked intermediate component resolves
|
|
82155
|
+
* out and is rejected here). Returns the canonical path used for I/O. */
|
|
82156
|
+
async function canonicalUnder(realRoot, abs) {
|
|
82157
|
+
const canon = await promises.realpath(abs);
|
|
82158
|
+
if (!under(realRoot, canon)) throw new WorkspaceViolationError("path resolves outside the workspace root");
|
|
82159
|
+
return canon;
|
|
82160
|
+
}
|
|
82161
|
+
return {
|
|
82162
|
+
async list(req) {
|
|
82163
|
+
const { resolved, realRoot } = await resolveContained(rootFor(req.agentId), req.path);
|
|
82164
|
+
const notFound = {
|
|
82165
|
+
agentId: req.agentId,
|
|
82166
|
+
path: req.path,
|
|
82167
|
+
exists: false,
|
|
82168
|
+
entries: []
|
|
82169
|
+
};
|
|
82170
|
+
if (realRoot === null) return notFound;
|
|
82171
|
+
let target;
|
|
82172
|
+
try {
|
|
82173
|
+
target = await canonicalUnder(realRoot, resolved);
|
|
82174
|
+
} catch (err) {
|
|
82175
|
+
if (isErrno(err, "ENOENT")) return notFound;
|
|
82176
|
+
throw err;
|
|
82177
|
+
}
|
|
82178
|
+
let dirents;
|
|
82179
|
+
try {
|
|
82180
|
+
dirents = await promises.readdir(target, { withFileTypes: true });
|
|
82181
|
+
} catch (err) {
|
|
82182
|
+
if (isErrno(err, "ENOENT") || isErrno(err, "ENOTDIR")) return notFound;
|
|
82183
|
+
throw err;
|
|
82184
|
+
}
|
|
82185
|
+
const entries = [];
|
|
82186
|
+
for (const d of dirents) {
|
|
82187
|
+
if (d.name === ".git") continue;
|
|
82188
|
+
const type = d.isDirectory() ? "dir" : d.isFile() ? "file" : d.isSymbolicLink() ? "symlink" : "other";
|
|
82189
|
+
const entry = {
|
|
82190
|
+
name: d.name,
|
|
82191
|
+
type
|
|
82192
|
+
};
|
|
82193
|
+
if (type === "file") try {
|
|
82194
|
+
const st = await promises.lstat(sp.join(target, d.name));
|
|
82195
|
+
entry.size = st.size;
|
|
82196
|
+
entry.mtime = st.mtime.toISOString();
|
|
82197
|
+
} catch {}
|
|
82198
|
+
entries.push(entry);
|
|
82199
|
+
}
|
|
82200
|
+
entries.sort(compareEntries);
|
|
82201
|
+
const start = req.cursor !== void 0 && /^\d+$/.test(req.cursor) ? Number(req.cursor) : 0;
|
|
82202
|
+
const page = [];
|
|
82203
|
+
let acc = 0;
|
|
82204
|
+
for (let i = start; i < entries.length && page.length < req.limit; i++) {
|
|
82205
|
+
const e = entries[i];
|
|
82206
|
+
const enc = encodedBytes(e) + 1;
|
|
82207
|
+
if (page.length > 0 && acc + enc > REPLY_BUDGET) break;
|
|
82208
|
+
page.push(e);
|
|
82209
|
+
acc += enc;
|
|
82210
|
+
}
|
|
82211
|
+
const consumed = start + page.length;
|
|
82212
|
+
return {
|
|
82213
|
+
agentId: req.agentId,
|
|
82214
|
+
path: req.path,
|
|
82215
|
+
exists: true,
|
|
82216
|
+
entries: page,
|
|
82217
|
+
...consumed < entries.length ? { nextCursor: String(consumed) } : {}
|
|
82218
|
+
};
|
|
82219
|
+
},
|
|
82220
|
+
async read(req) {
|
|
82221
|
+
const { resolved, realRoot } = await resolveContained(rootFor(req.agentId), req.path);
|
|
82222
|
+
const notFound = {
|
|
82223
|
+
agentId: req.agentId,
|
|
82224
|
+
path: req.path,
|
|
82225
|
+
exists: false
|
|
82226
|
+
};
|
|
82227
|
+
if (realRoot === null) return notFound;
|
|
82228
|
+
let st;
|
|
82229
|
+
try {
|
|
82230
|
+
st = await promises.lstat(resolved);
|
|
82231
|
+
} catch (err) {
|
|
82232
|
+
if (isErrno(err, "ENOENT")) return notFound;
|
|
82233
|
+
throw err;
|
|
82234
|
+
}
|
|
82235
|
+
if (!st.isFile()) throw new WorkspaceViolationError("not a regular file");
|
|
82236
|
+
const target = await canonicalUnder(realRoot, resolved);
|
|
82237
|
+
const size = st.size;
|
|
82238
|
+
const mtime = st.mtime.toISOString();
|
|
82239
|
+
const fh = await promises.open(target, "r");
|
|
82240
|
+
try {
|
|
82241
|
+
const sniffLen = Math.min(SNIFF_BYTES, size);
|
|
82242
|
+
if (sniffLen > 0) {
|
|
82243
|
+
const sniff = Buffer.alloc(sniffLen);
|
|
82244
|
+
const { bytesRead } = await fh.read(sniff, 0, sniffLen, 0);
|
|
82245
|
+
if (sniff.subarray(0, bytesRead).includes(0)) return {
|
|
82246
|
+
agentId: req.agentId,
|
|
82247
|
+
path: req.path,
|
|
82248
|
+
exists: true,
|
|
82249
|
+
size,
|
|
82250
|
+
mtime,
|
|
82251
|
+
encoding: "none"
|
|
82252
|
+
};
|
|
82253
|
+
}
|
|
82254
|
+
const want = Math.min(req.limit, Math.max(0, size - req.offset));
|
|
82255
|
+
let slice = Buffer.alloc(0);
|
|
82256
|
+
if (want > 0) {
|
|
82257
|
+
const buf = Buffer.alloc(want);
|
|
82258
|
+
const { bytesRead } = await fh.read(buf, 0, want, req.offset);
|
|
82259
|
+
slice = buf.subarray(0, bytesRead);
|
|
82260
|
+
}
|
|
82261
|
+
let end = utf8Boundary(slice, slice.length);
|
|
82262
|
+
let content = slice.toString("utf8", 0, end);
|
|
82263
|
+
while (end > 0 && encodedBytes(content) > REPLY_BUDGET) {
|
|
82264
|
+
const factor = REPLY_BUDGET / encodedBytes(content);
|
|
82265
|
+
const shrunk = Math.max(1, Math.floor(end * factor * .9));
|
|
82266
|
+
end = utf8Boundary(slice, Math.min(shrunk, end - 1));
|
|
82267
|
+
content = slice.toString("utf8", 0, end);
|
|
82268
|
+
}
|
|
82269
|
+
const nextOffset = req.offset + end;
|
|
82270
|
+
return {
|
|
82271
|
+
agentId: req.agentId,
|
|
82272
|
+
path: req.path,
|
|
82273
|
+
exists: true,
|
|
82274
|
+
size,
|
|
82275
|
+
mtime,
|
|
82276
|
+
encoding: "utf8",
|
|
82277
|
+
content,
|
|
82278
|
+
offset: req.offset,
|
|
82279
|
+
nextOffset,
|
|
82280
|
+
truncated: nextOffset < size
|
|
82281
|
+
};
|
|
82282
|
+
} finally {
|
|
82283
|
+
await fh.close();
|
|
82284
|
+
}
|
|
82285
|
+
}
|
|
82286
|
+
};
|
|
82287
|
+
}
|
|
82288
|
+
/** dirs first, then case-insensitive alphabetical (stable within the page). */
|
|
82289
|
+
function compareEntries(a, b) {
|
|
82290
|
+
const aDir = a.type === "dir" ? 0 : 1;
|
|
82291
|
+
const bDir = b.type === "dir" ? 0 : 1;
|
|
82292
|
+
if (aDir !== bDir) return aDir - bDir;
|
|
82293
|
+
const al = a.name.toLowerCase();
|
|
82294
|
+
const bl = b.name.toLowerCase();
|
|
82295
|
+
if (al !== bl) return al < bl ? -1 : 1;
|
|
82296
|
+
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
|
|
82297
|
+
}
|
|
82298
|
+
//#endregion
|
|
81994
82299
|
//#region src/cp/correlator.ts
|
|
81995
82300
|
/** Transport-free representation of a typed wire `error` REP. */
|
|
81996
82301
|
var WireError = class extends Error {
|
|
@@ -82356,6 +82661,12 @@ var CpClient = class {
|
|
|
82356
82661
|
this.sendError(frame.id, "INTERNAL", `session/history failed: ${err.message}`, false);
|
|
82357
82662
|
}
|
|
82358
82663
|
return;
|
|
82664
|
+
case "workspace/list":
|
|
82665
|
+
this.deps.workspaceRead.list(frame.payload).then((page) => this.reply(frame, "workspace/list/page", page)).catch((err) => this.workspaceError(frame.id, "workspace/list", err));
|
|
82666
|
+
return;
|
|
82667
|
+
case "workspace/read":
|
|
82668
|
+
this.deps.workspaceRead.read(frame.payload).then((content) => this.reply(frame, "workspace/read/content", content)).catch((err) => this.workspaceError(frame.id, "workspace/read", err));
|
|
82669
|
+
return;
|
|
82359
82670
|
case "agent/prompt":
|
|
82360
82671
|
this.sendError(frame.id, "INTERNAL", "agent/prompt not implemented", false);
|
|
82361
82672
|
return;
|
|
@@ -82364,6 +82675,18 @@ var CpClient = class {
|
|
|
82364
82675
|
return;
|
|
82365
82676
|
}
|
|
82366
82677
|
}
|
|
82678
|
+
/** Map a workspace read failure onto the wire: containment/bad-request
|
|
82679
|
+
* violations → BAD_PAYLOAD (their messages are hand-written and path-free);
|
|
82680
|
+
* anything else → INTERNAL with a GENERIC message — raw fs errors (ELOOP,
|
|
82681
|
+
* EACCES, …) embed absolute host paths that must not leak to the CP/UI. */
|
|
82682
|
+
workspaceError(corr, op, err) {
|
|
82683
|
+
if (err instanceof WorkspaceViolationError) {
|
|
82684
|
+
this.sendError(corr, "BAD_PAYLOAD", `${op} failed: ${err.message}`, false);
|
|
82685
|
+
return;
|
|
82686
|
+
}
|
|
82687
|
+
this.deps.log.warn(`cp: ${op} failed: ${err?.message}`);
|
|
82688
|
+
this.sendError(corr, "INTERNAL", `${op} failed`, false);
|
|
82689
|
+
}
|
|
82367
82690
|
reply(req, type, payload) {
|
|
82368
82691
|
this.transport?.send(encode(buildEnvelope(type, payload, { corr: req.id })));
|
|
82369
82692
|
}
|
|
@@ -83926,6 +84249,7 @@ var Daemon = class Daemon {
|
|
|
83926
84249
|
degradedScopes: () => this.cpDegradedScopes(),
|
|
83927
84250
|
configApply: this.cpConfigApply(),
|
|
83928
84251
|
sessionRead: createSessionReader(this.store),
|
|
84252
|
+
workspaceRead: createWorkspaceReader((id) => this.agents.get(id)?.workspace.path),
|
|
83929
84253
|
clock: systemClock,
|
|
83930
84254
|
connect: () => ClientTransport.dial(url),
|
|
83931
84255
|
log: this.log
|