@agentconnect.md/daemon 1.0.0-rc.79 → 1.0.0-rc.80
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 +313 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -13257,6 +13257,19 @@ const SessionListItem = object({
|
|
|
13257
13257
|
const SessionListReq = object({ agentId: string().uuid().optional() });
|
|
13258
13258
|
/** D→C REP (corr = req id): the daemon's current sessions. */
|
|
13259
13259
|
const SessionListPage = object({ sessions: array(SessionListItem) });
|
|
13260
|
+
object({
|
|
13261
|
+
toolCallId: string(),
|
|
13262
|
+
kind: string().optional(),
|
|
13263
|
+
status: string().optional(),
|
|
13264
|
+
rawInput: unknown().optional(),
|
|
13265
|
+
rawOutput: unknown().optional(),
|
|
13266
|
+
content: array(unknown()).optional(),
|
|
13267
|
+
locations: array(object({
|
|
13268
|
+
path: string(),
|
|
13269
|
+
line: number().int().optional()
|
|
13270
|
+
})).optional(),
|
|
13271
|
+
truncated: boolean().optional()
|
|
13272
|
+
});
|
|
13260
13273
|
/** One message in a session transcript page (a body — returned only for display). */
|
|
13261
13274
|
const SessionMessage = object({
|
|
13262
13275
|
seq: number().int(),
|
|
@@ -13264,7 +13277,13 @@ const SessionMessage = object({
|
|
|
13264
13277
|
senderName: string().optional(),
|
|
13265
13278
|
ts: string(),
|
|
13266
13279
|
kind: string(),
|
|
13267
|
-
text: string()
|
|
13280
|
+
text: string(),
|
|
13281
|
+
toolCallId: string().optional(),
|
|
13282
|
+
toolStatus: string().optional(),
|
|
13283
|
+
toolKind: string().optional(),
|
|
13284
|
+
body: string().optional(),
|
|
13285
|
+
bodyTruncated: boolean().optional(),
|
|
13286
|
+
bodyBytes: number().int().optional()
|
|
13268
13287
|
});
|
|
13269
13288
|
/** C→D REQ: fetch one page of a session's history from the owning daemon. */
|
|
13270
13289
|
const SessionHistoryReq = object({
|
|
@@ -13278,6 +13297,24 @@ const SessionHistoryPage = object({
|
|
|
13278
13297
|
messages: array(SessionMessage),
|
|
13279
13298
|
nextCursor: string().optional()
|
|
13280
13299
|
});
|
|
13300
|
+
/**
|
|
13301
|
+
* C→D REQ: fetch the FULL (untruncated) ToolBody JSON for one tool call, chunked.
|
|
13302
|
+
* The inline `SessionMessage.body` preview is capped at 32 KiB; when a body is
|
|
13303
|
+
* bigger the console pages the whole thing back through this frame by offset.
|
|
13304
|
+
*/
|
|
13305
|
+
const SessionToolBodyReq = object({
|
|
13306
|
+
sessionId: string(),
|
|
13307
|
+
toolCallId: string(),
|
|
13308
|
+
offset: number().int().nonnegative().default(0)
|
|
13309
|
+
});
|
|
13310
|
+
/** D→C REP (corr = the req id): one frame-budgeted byte slice of the full ToolBody JSON. */
|
|
13311
|
+
const SessionToolBodyChunk = object({
|
|
13312
|
+
sessionId: string(),
|
|
13313
|
+
toolCallId: string(),
|
|
13314
|
+
data: string(),
|
|
13315
|
+
totalBytes: number().int(),
|
|
13316
|
+
nextOffset: number().int().optional()
|
|
13317
|
+
});
|
|
13281
13318
|
//#endregion
|
|
13282
13319
|
//#region ../protocol/dist/frames/workspace.js
|
|
13283
13320
|
/**
|
|
@@ -13518,6 +13555,8 @@ const FRAME_SCHEMAS = {
|
|
|
13518
13555
|
"session/list/page": SessionListPage,
|
|
13519
13556
|
"session/history": SessionHistoryReq,
|
|
13520
13557
|
"session/history/page": SessionHistoryPage,
|
|
13558
|
+
"session/tool-body": SessionToolBodyReq,
|
|
13559
|
+
"session/tool-body/chunk": SessionToolBodyChunk,
|
|
13521
13560
|
"workspace/list": WorkspaceListReq,
|
|
13522
13561
|
"workspace/list/page": WorkspaceListPage,
|
|
13523
13562
|
"workspace/read": WorkspaceReadReq,
|
|
@@ -13621,6 +13660,8 @@ discriminatedUnion("type", [
|
|
|
13621
13660
|
frame("session/list/page", FRAME_SCHEMAS["session/list/page"]),
|
|
13622
13661
|
frame("session/history", FRAME_SCHEMAS["session/history"]),
|
|
13623
13662
|
frame("session/history/page", FRAME_SCHEMAS["session/history/page"]),
|
|
13663
|
+
frame("session/tool-body", FRAME_SCHEMAS["session/tool-body"]),
|
|
13664
|
+
frame("session/tool-body/chunk", FRAME_SCHEMAS["session/tool-body/chunk"]),
|
|
13624
13665
|
frame("workspace/list", FRAME_SCHEMAS["workspace/list"]),
|
|
13625
13666
|
frame("workspace/list/page", FRAME_SCHEMAS["workspace/list/page"]),
|
|
13626
13667
|
frame("workspace/read", FRAME_SCHEMAS["workspace/read"]),
|
|
@@ -23865,13 +23906,17 @@ var LocalStore = class {
|
|
|
23865
23906
|
CREATE TABLE IF NOT EXISTS transcript (
|
|
23866
23907
|
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
23867
23908
|
channel TEXT NOT NULL, thread TEXT NOT NULL, ts TEXT,
|
|
23868
|
-
sender TEXT NOT NULL, kind TEXT NOT NULL, text TEXT NOT NULL
|
|
23909
|
+
sender TEXT NOT NULL, kind TEXT NOT NULL, text TEXT NOT NULL,
|
|
23910
|
+
tool_call_id TEXT, body TEXT
|
|
23869
23911
|
);
|
|
23870
23912
|
CREATE INDEX IF NOT EXISTS transcript_thread_seq ON transcript (channel, thread, seq);
|
|
23871
23913
|
-- Dedup conversational rows by platform ts (double-fired inbound / redelivery);
|
|
23872
23914
|
-- internal events have no platform ts and are intentionally never deduped here.
|
|
23873
23915
|
CREATE UNIQUE INDEX IF NOT EXISTS transcript_text_ts
|
|
23874
23916
|
ON transcript (channel, thread, ts) WHERE kind = 'text';
|
|
23917
|
+
-- transcript_tool_call (partial unique on tool_call_id) is created in
|
|
23918
|
+
-- migrateTranscriptToolBody, after the column is guaranteed to exist — a legacy
|
|
23919
|
+
-- rebuild in migrateTranscript recreates the table without the new columns.
|
|
23875
23920
|
CREATE TABLE IF NOT EXISTS cp_routing (
|
|
23876
23921
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
23877
23922
|
routingEpoch INTEGER, assignments TEXT, globalRules TEXT
|
|
@@ -23883,6 +23928,18 @@ var LocalStore = class {
|
|
|
23883
23928
|
);
|
|
23884
23929
|
`);
|
|
23885
23930
|
this.migrateSessionUsage();
|
|
23931
|
+
this.migrateTranscriptToolBody();
|
|
23932
|
+
}
|
|
23933
|
+
/** Add the `transcript.tool_call_id` + `transcript.body` columns (and the partial
|
|
23934
|
+
* unique index) to a pre-existing DB. CREATE TABLE IF NOT EXISTS above adds them for
|
|
23935
|
+
* fresh DBs but never alters an existing table, so upgrade in place. No-op once the
|
|
23936
|
+
* columns are present. */
|
|
23937
|
+
migrateTranscriptToolBody() {
|
|
23938
|
+
const cols = this.db.prepare("PRAGMA table_info(transcript)").all();
|
|
23939
|
+
if (!cols.some((c) => c.name === "tool_call_id")) this.db.exec("ALTER TABLE transcript ADD COLUMN tool_call_id TEXT");
|
|
23940
|
+
if (!cols.some((c) => c.name === "body")) this.db.exec("ALTER TABLE transcript ADD COLUMN body TEXT");
|
|
23941
|
+
this.db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS transcript_tool_call
|
|
23942
|
+
ON transcript (channel, thread, tool_call_id) WHERE tool_call_id IS NOT NULL`);
|
|
23886
23943
|
}
|
|
23887
23944
|
/** Add the `sessions.usage` (JSON token accounting), `sessions.muted` (`!stop`
|
|
23888
23945
|
* thread mute) and `sessions.triggeredBy` columns to a pre-existing DB. CREATE
|
|
@@ -24032,6 +24089,32 @@ var LocalStore = class {
|
|
|
24032
24089
|
appendTranscript(e) {
|
|
24033
24090
|
this.db.prepare("INSERT OR IGNORE INTO transcript (channel, thread, ts, sender, kind, text) VALUES (@channel, @thread, @ts, @sender, @kind, @text)").run(e);
|
|
24034
24091
|
}
|
|
24092
|
+
/** First sight of a tool call: insert its kind='tool' row (title in `text`, the
|
|
24093
|
+
* serialized ToolBody in `body`). INSERT OR IGNORE so a re-fired first update is a
|
|
24094
|
+
* no-op — the partial unique index on (channel, thread, tool_call_id) dedups. `seq`
|
|
24095
|
+
* is assigned here and stays stable across later `updateToolCall`s. */
|
|
24096
|
+
insertToolCall(e) {
|
|
24097
|
+
this.db.prepare(`INSERT OR IGNORE INTO transcript (channel, thread, ts, sender, kind, text, tool_call_id, body)
|
|
24098
|
+
VALUES (@channel, @thread, @ts, @sender, 'tool', @text, @toolCallId, @body)`).run({
|
|
24099
|
+
channel: e.channel,
|
|
24100
|
+
thread: e.thread,
|
|
24101
|
+
ts: e.ts,
|
|
24102
|
+
sender: e.sender,
|
|
24103
|
+
text: e.title,
|
|
24104
|
+
toolCallId: e.toolCallId,
|
|
24105
|
+
body: e.body
|
|
24106
|
+
});
|
|
24107
|
+
}
|
|
24108
|
+
/** Later update for a tool call: overwrite `text` (title) + `body` on the existing
|
|
24109
|
+
* row, keyed by (channel, thread, tool_call_id). `seq`/`ts` are untouched so the
|
|
24110
|
+
* row keeps its first-seen position. No-op if the row is absent. */
|
|
24111
|
+
updateToolCall(channel, thread, toolCallId, patch) {
|
|
24112
|
+
this.db.prepare("UPDATE transcript SET text = ?, body = ? WHERE channel = ? AND thread = ? AND tool_call_id = ?").run(patch.title, patch.body, channel, thread, toolCallId);
|
|
24113
|
+
}
|
|
24114
|
+
/** The full stored ToolBody JSON for one tool call, or undefined if unknown. */
|
|
24115
|
+
getToolBody(channel, thread, toolCallId) {
|
|
24116
|
+
return this.db.prepare("SELECT body FROM transcript WHERE channel = ? AND thread = ? AND tool_call_id = ?").get(channel, thread, toolCallId)?.body ?? void 0;
|
|
24117
|
+
}
|
|
24035
24118
|
/**
|
|
24036
24119
|
* §8.5 cross-agent catch-up: conversational (`text`) rows only — tool/reasoning rows
|
|
24037
24120
|
* are audit/UI data and must never be replayed back into an agent's prompt. Ordered by
|
|
@@ -24294,6 +24377,9 @@ function transcriptCoords(msg) {
|
|
|
24294
24377
|
}
|
|
24295
24378
|
//#endregion
|
|
24296
24379
|
//#region src/session/transcript-recorder.ts
|
|
24380
|
+
/** Single-body write ceiling (protects the daemon DB from a huge rawOutput/content);
|
|
24381
|
+
* separate from the read-side 32 KiB inline preview cap. */
|
|
24382
|
+
const MAX_TOOL_BODY_BYTES = 1024 * 1024;
|
|
24297
24383
|
/**
|
|
24298
24384
|
* Turns the raw ACP `session/update` stream into the *full* activity log — tool calls
|
|
24299
24385
|
* and reasoning — independent of the agent's Slack `output.mode`. (Output mode only
|
|
@@ -24302,12 +24388,14 @@ function transcriptCoords(msg) {
|
|
|
24302
24388
|
* It deliberately does NOT emit `text`: conversational rows are recorded at the
|
|
24303
24389
|
* platform-send boundary instead, where the real message `ts` is known (so they sort
|
|
24304
24390
|
* against the §8.5 replay marker). Reasoning chunks are coalesced into one block per
|
|
24305
|
-
* run; each tool invocation is
|
|
24306
|
-
* `tool_call_update`
|
|
24391
|
+
* run; each tool invocation is merged per `toolCallId` across its `tool_call` +
|
|
24392
|
+
* `tool_call_update` burst — emitting an `insert` on first sight and an `update` on
|
|
24393
|
+
* each later change, so the row's full body fills in as the call streams to completion.
|
|
24307
24394
|
*/
|
|
24308
24395
|
var TranscriptRecorder = class {
|
|
24309
24396
|
thought = "";
|
|
24310
|
-
|
|
24397
|
+
tools = /* @__PURE__ */ new Map();
|
|
24398
|
+
titles = /* @__PURE__ */ new Map();
|
|
24311
24399
|
onUpdate(update) {
|
|
24312
24400
|
switch (update.sessionUpdate) {
|
|
24313
24401
|
case "agent_thought_chunk": {
|
|
@@ -24320,11 +24408,17 @@ var TranscriptRecorder = class {
|
|
|
24320
24408
|
const flushed = this.flushThought();
|
|
24321
24409
|
const u = update;
|
|
24322
24410
|
const id = u.toolCallId ?? "";
|
|
24323
|
-
if (id
|
|
24324
|
-
|
|
24411
|
+
if (!id) return flushed;
|
|
24412
|
+
const first = !this.tools.has(id);
|
|
24413
|
+
const body = this.merge(id, u);
|
|
24414
|
+
const title = u.title ?? this.titles.get(id) ?? id;
|
|
24415
|
+
if (u.title !== void 0 && u.title !== null) this.titles.set(id, u.title);
|
|
24325
24416
|
return [...flushed, {
|
|
24326
24417
|
kind: "tool",
|
|
24327
|
-
|
|
24418
|
+
op: first ? "insert" : "update",
|
|
24419
|
+
toolCallId: id,
|
|
24420
|
+
text: title,
|
|
24421
|
+
body: serializeBody(body)
|
|
24328
24422
|
}];
|
|
24329
24423
|
}
|
|
24330
24424
|
case "agent_message_chunk": return this.flushThought();
|
|
@@ -24335,6 +24429,23 @@ var TranscriptRecorder = class {
|
|
|
24335
24429
|
onFinal() {
|
|
24336
24430
|
return this.flushThought();
|
|
24337
24431
|
}
|
|
24432
|
+
/**
|
|
24433
|
+
* Merge one `tool_call`/`tool_call_update` into the accumulated ToolBody per ACP
|
|
24434
|
+
* semantics: present-and-non-null scalar fields (kind/status/rawInput/rawOutput)
|
|
24435
|
+
* overlay the prior value; `content`/`locations` are wholesale replacements when
|
|
24436
|
+
* present. Returns the updated body (also cached in `tools`).
|
|
24437
|
+
*/
|
|
24438
|
+
merge(id, u) {
|
|
24439
|
+
const body = this.tools.get(id) ?? { toolCallId: id };
|
|
24440
|
+
if (u.kind !== void 0 && u.kind !== null) body.kind = u.kind;
|
|
24441
|
+
if (u.status !== void 0 && u.status !== null) body.status = u.status;
|
|
24442
|
+
if (u.rawInput !== void 0 && u.rawInput !== null) body.rawInput = u.rawInput;
|
|
24443
|
+
if (u.rawOutput !== void 0 && u.rawOutput !== null) body.rawOutput = u.rawOutput;
|
|
24444
|
+
if (u.content !== void 0 && u.content !== null) body.content = u.content;
|
|
24445
|
+
if (u.locations !== void 0 && u.locations !== null) body.locations = u.locations;
|
|
24446
|
+
this.tools.set(id, body);
|
|
24447
|
+
return body;
|
|
24448
|
+
}
|
|
24338
24449
|
flushThought() {
|
|
24339
24450
|
const text = this.thought.trim();
|
|
24340
24451
|
this.thought = "";
|
|
@@ -24344,6 +24455,25 @@ var TranscriptRecorder = class {
|
|
|
24344
24455
|
}] : [];
|
|
24345
24456
|
}
|
|
24346
24457
|
};
|
|
24458
|
+
/** Serialize a ToolBody, capping the encoded size at MAX_TOOL_BODY_BYTES. When over
|
|
24459
|
+
* cap, drop the biggest free-form tails (rawOutput first, then content) and flag
|
|
24460
|
+
* `truncated` so the read side / UI can show it was capped at write time. */
|
|
24461
|
+
function serializeBody(body) {
|
|
24462
|
+
let json = JSON.stringify(body);
|
|
24463
|
+
if (Buffer.byteLength(json) <= MAX_TOOL_BODY_BYTES) return json;
|
|
24464
|
+
const capped = {
|
|
24465
|
+
...body,
|
|
24466
|
+
truncated: true
|
|
24467
|
+
};
|
|
24468
|
+
capped.rawOutput = void 0;
|
|
24469
|
+
json = JSON.stringify(capped);
|
|
24470
|
+
if (Buffer.byteLength(json) <= MAX_TOOL_BODY_BYTES) return json;
|
|
24471
|
+
capped.content = void 0;
|
|
24472
|
+
json = JSON.stringify(capped);
|
|
24473
|
+
if (Buffer.byteLength(json) <= MAX_TOOL_BODY_BYTES) return json;
|
|
24474
|
+
capped.rawInput = void 0;
|
|
24475
|
+
return JSON.stringify(capped);
|
|
24476
|
+
}
|
|
24347
24477
|
//#endregion
|
|
24348
24478
|
//#region src/mcp/ipc.ts
|
|
24349
24479
|
/** Frame a message for the wire: compact JSON + a single trailing newline. */
|
|
@@ -82323,7 +82453,7 @@ function makeLogger(level = "info") {
|
|
|
82323
82453
|
const SNIFF_BYTES = 8192;
|
|
82324
82454
|
/** Encoded-payload ceiling, leaving headroom under MAX_FRAME_BYTES for the
|
|
82325
82455
|
* envelope (id/ts/type/corr + fencing ext, well under 4 KiB). */
|
|
82326
|
-
const REPLY_BUDGET = MAX_FRAME_BYTES - 4096;
|
|
82456
|
+
const REPLY_BUDGET$1 = MAX_FRAME_BYTES - 4096;
|
|
82327
82457
|
/** Path-containment / bad-request violation → `BAD_PAYLOAD` on the wire. */
|
|
82328
82458
|
var WorkspaceViolationError = class extends Error {
|
|
82329
82459
|
constructor(message) {
|
|
@@ -82340,7 +82470,7 @@ function under(root, p) {
|
|
|
82340
82470
|
/** Largest index ≤ `len` that lands on a UTF-8 character boundary — i.e. never
|
|
82341
82471
|
* cuts a multi-byte sequence. If the slice's trailing char is incomplete, the
|
|
82342
82472
|
* boundary is placed before that char (its bytes belong to the next slice). */
|
|
82343
|
-
function utf8Boundary(buf, len) {
|
|
82473
|
+
function utf8Boundary$1(buf, len) {
|
|
82344
82474
|
if (len >= buf.length) len = buf.length;
|
|
82345
82475
|
if (len <= 0) return 0;
|
|
82346
82476
|
let start = len - 1;
|
|
@@ -82351,7 +82481,7 @@ function utf8Boundary(buf, len) {
|
|
|
82351
82481
|
}
|
|
82352
82482
|
/** The encoded size of the payload the wire will carry (JSON.stringify matches
|
|
82353
82483
|
* the codec's `encode`), measured in bytes. */
|
|
82354
|
-
function encodedBytes(payload) {
|
|
82484
|
+
function encodedBytes$1(payload) {
|
|
82355
82485
|
return Buffer.byteLength(JSON.stringify(payload));
|
|
82356
82486
|
}
|
|
82357
82487
|
function createWorkspaceReader(workspaceRootByAgent) {
|
|
@@ -82435,8 +82565,8 @@ function createWorkspaceReader(workspaceRootByAgent) {
|
|
|
82435
82565
|
let acc = 0;
|
|
82436
82566
|
for (let i = start; i < entries.length && page.length < req.limit; i++) {
|
|
82437
82567
|
const e = entries[i];
|
|
82438
|
-
const enc = encodedBytes(e) + 1;
|
|
82439
|
-
if (page.length > 0 && acc + enc > REPLY_BUDGET) break;
|
|
82568
|
+
const enc = encodedBytes$1(e) + 1;
|
|
82569
|
+
if (page.length > 0 && acc + enc > REPLY_BUDGET$1) break;
|
|
82440
82570
|
page.push(e);
|
|
82441
82571
|
acc += enc;
|
|
82442
82572
|
}
|
|
@@ -82490,12 +82620,12 @@ function createWorkspaceReader(workspaceRootByAgent) {
|
|
|
82490
82620
|
const { bytesRead } = await fh.read(buf, 0, want, req.offset);
|
|
82491
82621
|
slice = buf.subarray(0, bytesRead);
|
|
82492
82622
|
}
|
|
82493
|
-
let end = utf8Boundary(slice, slice.length);
|
|
82623
|
+
let end = utf8Boundary$1(slice, slice.length);
|
|
82494
82624
|
let content = slice.toString("utf8", 0, end);
|
|
82495
|
-
while (end > 0 && encodedBytes(content) > REPLY_BUDGET) {
|
|
82496
|
-
const factor = REPLY_BUDGET / encodedBytes(content);
|
|
82625
|
+
while (end > 0 && encodedBytes$1(content) > REPLY_BUDGET$1) {
|
|
82626
|
+
const factor = REPLY_BUDGET$1 / encodedBytes$1(content);
|
|
82497
82627
|
const shrunk = Math.max(1, Math.floor(end * factor * .9));
|
|
82498
|
-
end = utf8Boundary(slice, Math.min(shrunk, end - 1));
|
|
82628
|
+
end = utf8Boundary$1(slice, Math.min(shrunk, end - 1));
|
|
82499
82629
|
content = slice.toString("utf8", 0, end);
|
|
82500
82630
|
}
|
|
82501
82631
|
const nextOffset = req.offset + end;
|
|
@@ -82893,6 +83023,13 @@ var CpClient = class {
|
|
|
82893
83023
|
this.sendError(frame.id, "INTERNAL", `session/history failed: ${err.message}`, false);
|
|
82894
83024
|
}
|
|
82895
83025
|
return;
|
|
83026
|
+
case "session/tool-body":
|
|
83027
|
+
try {
|
|
83028
|
+
this.reply(frame, "session/tool-body/chunk", this.deps.sessionRead.toolBody(frame.payload));
|
|
83029
|
+
} catch (err) {
|
|
83030
|
+
this.sendError(frame.id, "INTERNAL", `session/tool-body failed: ${err.message}`, false);
|
|
83031
|
+
}
|
|
83032
|
+
return;
|
|
82896
83033
|
case "workspace/list":
|
|
82897
83034
|
this.deps.workspaceRead.list(frame.payload).then((page) => this.reply(frame, "workspace/list/page", page)).catch((err) => this.workspaceError(frame.id, "workspace/list", err));
|
|
82898
83035
|
return;
|
|
@@ -82935,6 +83072,40 @@ var CpClient = class {
|
|
|
82935
83072
|
};
|
|
82936
83073
|
//#endregion
|
|
82937
83074
|
//#region src/cp/session-reader.ts
|
|
83075
|
+
/**
|
|
83076
|
+
* `SessionReader` — the read-only seam answering the CP's `session/list`,
|
|
83077
|
+
* `session/history` and `session/tool-body` REQs from the daemon's local store.
|
|
83078
|
+
* Bodies live only on the daemon (§1/§12); this projects store rows onto the
|
|
83079
|
+
* protocol shapes. The protocol `sessionId` IS the store's `acpSessionId`
|
|
83080
|
+
* (SessionManager returns it).
|
|
83081
|
+
*
|
|
83082
|
+
* Frame-size safety: every REP must serialise under the 256 KiB wire cap. A single
|
|
83083
|
+
* tool body can be large, so `history` caps each inline `body` at a 32 KiB VALID-JSON
|
|
83084
|
+
* preview and assembles a page under the per-frame budget (newest rows that fit);
|
|
83085
|
+
* the FULL body is pulled on demand via `toolBody`, chunked by offset.
|
|
83086
|
+
*/
|
|
83087
|
+
/** Encoded-payload ceiling, leaving headroom under MAX_FRAME_BYTES for the
|
|
83088
|
+
* envelope (id/ts/type/corr + fencing ext, well under 4 KiB). */
|
|
83089
|
+
const REPLY_BUDGET = MAX_FRAME_BYTES - 4096;
|
|
83090
|
+
/** Inline `SessionMessage.body` preview cap. Bodies over this are shrunk to a
|
|
83091
|
+
* valid-JSON preview + `bodyTruncated`; the full body is fetched via `toolBody`. */
|
|
83092
|
+
const PREVIEW_CAP = 32 * 1024;
|
|
83093
|
+
/** The encoded size of the payload the wire will carry (JSON.stringify matches the
|
|
83094
|
+
* codec's `encode`), measured in bytes. */
|
|
83095
|
+
function encodedBytes(payload) {
|
|
83096
|
+
return Buffer.byteLength(JSON.stringify(payload));
|
|
83097
|
+
}
|
|
83098
|
+
/** Largest index ≤ `len` that lands on a UTF-8 character boundary — never cuts a
|
|
83099
|
+
* multi-byte sequence (mirrors workspace-reader's slicer). */
|
|
83100
|
+
function utf8Boundary(buf, len) {
|
|
83101
|
+
if (len >= buf.length) len = buf.length;
|
|
83102
|
+
if (len <= 0) return 0;
|
|
83103
|
+
let start = len - 1;
|
|
83104
|
+
while (start > 0 && ((buf[start] ?? 0) & 192) === 128) start--;
|
|
83105
|
+
const lead = buf[start] ?? 0;
|
|
83106
|
+
const seqLen = lead < 128 ? 1 : (lead & 224) === 192 ? 2 : (lead & 240) === 224 ? 3 : (lead & 248) === 240 ? 4 : 1;
|
|
83107
|
+
return start + seqLen <= len ? len : start;
|
|
83108
|
+
}
|
|
82938
83109
|
/** Parse a session row's `usage` JSON into the wire shape. Returns undefined when
|
|
82939
83110
|
* absent, empty, or unparseable so the optional field is simply omitted. */
|
|
82940
83111
|
function parseUsage(raw) {
|
|
@@ -82946,6 +83117,46 @@ function parseUsage(raw) {
|
|
|
82946
83117
|
return;
|
|
82947
83118
|
}
|
|
82948
83119
|
}
|
|
83120
|
+
/** Rough character budget to trim a free-form value to when shrinking a preview. A
|
|
83121
|
+
* string is truncated to this many chars; a non-string is stringified then truncated
|
|
83122
|
+
* (kept as a string — the preview is still valid JSON, just lossy on that field). */
|
|
83123
|
+
function shrinkField(value, chars) {
|
|
83124
|
+
if (value === void 0) return void 0;
|
|
83125
|
+
const s = typeof value === "string" ? value : JSON.stringify(value);
|
|
83126
|
+
if (s.length <= chars) return value;
|
|
83127
|
+
return s.slice(0, chars) + "…";
|
|
83128
|
+
}
|
|
83129
|
+
/**
|
|
83130
|
+
* Build a VALID-JSON preview of a tool body that encodes within PREVIEW_CAP. The
|
|
83131
|
+
* heavy free-form fields (rawOutput, rawInput, content) are trimmed progressively
|
|
83132
|
+
* until it fits; `truncated`/status/kind/locations are preserved. Always sets
|
|
83133
|
+
* `truncated:true` on the returned preview.
|
|
83134
|
+
*/
|
|
83135
|
+
function previewBody(full) {
|
|
83136
|
+
const preview = {
|
|
83137
|
+
...full,
|
|
83138
|
+
truncated: true
|
|
83139
|
+
};
|
|
83140
|
+
for (const chars of [
|
|
83141
|
+
8192,
|
|
83142
|
+
2048,
|
|
83143
|
+
512,
|
|
83144
|
+
128,
|
|
83145
|
+
16
|
|
83146
|
+
]) {
|
|
83147
|
+
preview.rawOutput = shrinkField(full.rawOutput, chars);
|
|
83148
|
+
preview.rawInput = shrinkField(full.rawInput, chars);
|
|
83149
|
+
if (full.content !== void 0) {
|
|
83150
|
+
const asStr = JSON.stringify(full.content);
|
|
83151
|
+
preview.content = asStr.length <= chars ? full.content : [shrinkField(asStr, chars)];
|
|
83152
|
+
}
|
|
83153
|
+
if (encodedBytes(preview) <= PREVIEW_CAP) return preview;
|
|
83154
|
+
}
|
|
83155
|
+
preview.rawOutput = void 0;
|
|
83156
|
+
preview.rawInput = void 0;
|
|
83157
|
+
preview.content = void 0;
|
|
83158
|
+
return preview;
|
|
83159
|
+
}
|
|
82949
83160
|
function createSessionReader(store) {
|
|
82950
83161
|
return {
|
|
82951
83162
|
list(req) {
|
|
@@ -82982,9 +83193,9 @@ function createSessionReader(store) {
|
|
|
82982
83193
|
const { rows, hasMore } = store.transcriptPage(rec.channel, rec.thread, before, req.limit);
|
|
82983
83194
|
const ordered = rows.slice().reverse();
|
|
82984
83195
|
const names = store.getDisplayNames(ordered.map((r) => r.sender));
|
|
82985
|
-
const
|
|
83196
|
+
const built = ordered.map((r) => {
|
|
82986
83197
|
const senderName = names.get(r.sender);
|
|
82987
|
-
|
|
83198
|
+
const base = {
|
|
82988
83199
|
seq: r.seq,
|
|
82989
83200
|
sender: r.sender,
|
|
82990
83201
|
...senderName ? { senderName } : {},
|
|
@@ -82992,11 +83203,76 @@ function createSessionReader(store) {
|
|
|
82992
83203
|
kind: r.kind,
|
|
82993
83204
|
text: r.text
|
|
82994
83205
|
};
|
|
83206
|
+
if (r.kind !== "tool" || !r.body) return base;
|
|
83207
|
+
let full;
|
|
83208
|
+
try {
|
|
83209
|
+
full = JSON.parse(r.body);
|
|
83210
|
+
} catch {
|
|
83211
|
+
return base;
|
|
83212
|
+
}
|
|
83213
|
+
const bytes = Buffer.byteLength(r.body);
|
|
83214
|
+
base.toolCallId = full.toolCallId ?? r.toolCallId ?? void 0;
|
|
83215
|
+
if (full.status !== void 0) base.toolStatus = full.status;
|
|
83216
|
+
if (full.kind !== void 0) base.toolKind = full.kind;
|
|
83217
|
+
if (bytes <= PREVIEW_CAP) base.body = r.body;
|
|
83218
|
+
else {
|
|
83219
|
+
base.body = JSON.stringify(previewBody(full));
|
|
83220
|
+
base.bodyTruncated = true;
|
|
83221
|
+
base.bodyBytes = bytes;
|
|
83222
|
+
}
|
|
83223
|
+
return base;
|
|
82995
83224
|
});
|
|
83225
|
+
const kept = [];
|
|
83226
|
+
let acc = 0;
|
|
83227
|
+
let droppedToBudget = false;
|
|
83228
|
+
for (let i = built.length - 1; i >= 0; i--) {
|
|
83229
|
+
const m = built[i];
|
|
83230
|
+
const enc = encodedBytes(m) + 1;
|
|
83231
|
+
if (kept.length > 0 && acc + enc > REPLY_BUDGET) {
|
|
83232
|
+
droppedToBudget = true;
|
|
83233
|
+
break;
|
|
83234
|
+
}
|
|
83235
|
+
kept.push(m);
|
|
83236
|
+
acc += enc;
|
|
83237
|
+
}
|
|
83238
|
+
kept.reverse();
|
|
83239
|
+
const hasOlder = hasMore || droppedToBudget;
|
|
82996
83240
|
return {
|
|
82997
83241
|
sessionId: req.sessionId,
|
|
82998
|
-
messages,
|
|
82999
|
-
...
|
|
83242
|
+
messages: kept,
|
|
83243
|
+
...hasOlder && kept.length > 0 ? { nextCursor: String(kept[0].seq) } : {}
|
|
83244
|
+
};
|
|
83245
|
+
},
|
|
83246
|
+
toolBody(req) {
|
|
83247
|
+
const rec = store.getSessionByAcpId(req.sessionId);
|
|
83248
|
+
const empty = {
|
|
83249
|
+
sessionId: req.sessionId,
|
|
83250
|
+
toolCallId: req.toolCallId,
|
|
83251
|
+
data: "",
|
|
83252
|
+
totalBytes: 0
|
|
83253
|
+
};
|
|
83254
|
+
if (!rec) return empty;
|
|
83255
|
+
const body = store.getToolBody(rec.channel, rec.thread, req.toolCallId);
|
|
83256
|
+
if (body === void 0) return empty;
|
|
83257
|
+
const buf = Buffer.from(body, "utf8");
|
|
83258
|
+
const totalBytes = buf.length;
|
|
83259
|
+
const offset = Math.min(req.offset, totalBytes);
|
|
83260
|
+
const remaining = buf.subarray(offset);
|
|
83261
|
+
let end = utf8Boundary(remaining, remaining.length);
|
|
83262
|
+
let data = remaining.toString("utf8", 0, end);
|
|
83263
|
+
while (end > 0 && encodedBytes(data) > REPLY_BUDGET) {
|
|
83264
|
+
const factor = REPLY_BUDGET / encodedBytes(data);
|
|
83265
|
+
const shrunk = Math.max(1, Math.floor(end * factor * .9));
|
|
83266
|
+
end = utf8Boundary(remaining, Math.min(shrunk, end - 1));
|
|
83267
|
+
data = remaining.toString("utf8", 0, end);
|
|
83268
|
+
}
|
|
83269
|
+
const nextOffset = offset + end;
|
|
83270
|
+
return {
|
|
83271
|
+
sessionId: req.sessionId,
|
|
83272
|
+
toolCallId: req.toolCallId,
|
|
83273
|
+
data,
|
|
83274
|
+
totalBytes,
|
|
83275
|
+
...nextOffset < totalBytes ? { nextOffset } : {}
|
|
83000
83276
|
};
|
|
83001
83277
|
}
|
|
83002
83278
|
};
|
|
@@ -84409,6 +84685,22 @@ var Daemon = class Daemon {
|
|
|
84409
84685
|
/** Persist one internal activity event (tool/reasoning). Ordered by row `seq`, so its
|
|
84410
84686
|
* `ts` is just a wall-clock stamp for display — never used for replay/sorting. */
|
|
84411
84687
|
recordEvent(agentId, channel, thread, ev) {
|
|
84688
|
+
if (ev.kind === "tool") {
|
|
84689
|
+
if (ev.op === "insert") this.store.insertToolCall({
|
|
84690
|
+
channel,
|
|
84691
|
+
thread,
|
|
84692
|
+
ts: String(Date.now()),
|
|
84693
|
+
sender: agentId,
|
|
84694
|
+
toolCallId: ev.toolCallId,
|
|
84695
|
+
title: ev.text,
|
|
84696
|
+
body: ev.body
|
|
84697
|
+
});
|
|
84698
|
+
else this.store.updateToolCall(channel, thread, ev.toolCallId, {
|
|
84699
|
+
title: ev.text,
|
|
84700
|
+
body: ev.body
|
|
84701
|
+
});
|
|
84702
|
+
return;
|
|
84703
|
+
}
|
|
84412
84704
|
this.store.appendTranscript({
|
|
84413
84705
|
channel,
|
|
84414
84706
|
thread,
|