@aexol/spectral 0.9.171 → 0.9.173
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/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +23 -6
- package/dist/relay/dispatcher.d.ts.map +1 -1
- package/dist/relay/dispatcher.js +2 -1
- package/dist/relay/history-projection.d.ts +41 -0
- package/dist/relay/history-projection.d.ts.map +1 -0
- package/dist/relay/history-projection.js +245 -0
- package/dist/server/handlers/sessions.d.ts.map +1 -1
- package/dist/server/handlers/sessions.js +5 -3
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/agent/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAIX,SAAS,EAET,MAAM,4BAA4B,CAAC;AAUpC,OAAO,EACN,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EACN,KAAK,WAAW,EAKhB,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAOrD,OAAO,KAAK,EAAE,gBAAgB,IAAI,oBAAoB,EAAE,MAAM,8CAA8C,CAAC;AAsG7G;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACxC,gBAAgB,EAAE,MAAM,GAAG,SAAS,EACpC,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,GAAG,cAAc,GAAG,OAAO,GAAG,QAAQ,CAAC,GACpF,MAAM,CAYR;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/agent/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAIX,SAAS,EAET,MAAM,4BAA4B,CAAC;AAUpC,OAAO,EACN,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EACN,KAAK,WAAW,EAKhB,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAOrD,OAAO,KAAK,EAAE,gBAAgB,IAAI,oBAAoB,EAAE,MAAM,8CAA8C,CAAC;AAsG7G;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACxC,gBAAgB,EAAE,MAAM,GAAG,SAAS,EACpC,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,GAAG,cAAc,GAAG,OAAO,GAAG,QAAQ,CAAC,GACpF,MAAM,CAYR;AAiKD;;;GAGG;AACH,wBAAgB,oCAAoC,CACnD,cAAc,EAAE,gBAAgB,CAAC,gBAAgB,CAAC,GAChD,IAAI,CAAC,oBAAoB,CAAC,gBAAgB,CAAC,EAAE,WAAW,CAAC,CAK3D;AAED,wBAAgB,oBAAoB,CACnC,SAAS,EAAE,MAAM,EAAE,GAAG,SAAS,EAC/B,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,YAAY,EACjB,GAAG,EAAE,gBAAgB,GACnB,SAAS,EAAE,CAwCb;AAicD,MAAM,CAAC,OAAO,UAAU,iBAAiB,CACxC,GAAG,EAAE,YAAY,EACjB,IAAI,CAAC,EAAE;IACN,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB,GACC,IAAI,CAgaN"}
|
package/dist/agent/index.js
CHANGED
|
@@ -145,6 +145,23 @@ export function buildSubagentSystemPrompt(baseSystemPrompt, agent) {
|
|
|
145
145
|
const remainder = stripped.replace(/^\n+|\n+$/g, "");
|
|
146
146
|
return remainder ? `${remainder}\n\n---\n\n${subagentBlock}` : subagentBlock;
|
|
147
147
|
}
|
|
148
|
+
/** Projects out each result's `messages` transcript. */
|
|
149
|
+
function stripResultMessages(results) {
|
|
150
|
+
return results.map(({ messages: _messages, ...lightResult }) => lightResult);
|
|
151
|
+
}
|
|
152
|
+
/** Converts live subagent details into their persisted form.
|
|
153
|
+
*
|
|
154
|
+
* The frontend reads only `details.mode` (and the dedicated `subagent_end`
|
|
155
|
+
* event fields), never `details.results[].messages`. That transcript is dead
|
|
156
|
+
* weight in `events_jsonl` (~891KB per heavy subagent tool result), so it is
|
|
157
|
+
* dropped here at the persistence source.
|
|
158
|
+
*/
|
|
159
|
+
function persistDetails(details) {
|
|
160
|
+
return {
|
|
161
|
+
...details,
|
|
162
|
+
results: stripResultMessages(details.results),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
148
165
|
// ---------------------------------------------------------------------------
|
|
149
166
|
// Helpers — message extraction
|
|
150
167
|
// ---------------------------------------------------------------------------
|
|
@@ -797,7 +814,7 @@ export default function subagentExtension(ext, opts) {
|
|
|
797
814
|
text: `Chain stopped at step ${i + 1} (${step.agent}): ${errorMsg}`,
|
|
798
815
|
},
|
|
799
816
|
],
|
|
800
|
-
details: makeDetails("chain")(results),
|
|
817
|
+
details: persistDetails(makeDetails("chain")(results)),
|
|
801
818
|
isError: true,
|
|
802
819
|
};
|
|
803
820
|
}
|
|
@@ -817,7 +834,7 @@ export default function subagentExtension(ext, opts) {
|
|
|
817
834
|
text: `Chain completed (${results.length} step${results.length > 1 ? "s" : ""})\n\n${chainSummaries.join("\n\n")}`,
|
|
818
835
|
},
|
|
819
836
|
],
|
|
820
|
-
details: makeDetails("chain")(results),
|
|
837
|
+
details: persistDetails(makeDetails("chain")(results)),
|
|
821
838
|
};
|
|
822
839
|
}
|
|
823
840
|
// ── Parallel mode ───────────────────────────────────────────
|
|
@@ -855,7 +872,7 @@ export default function subagentExtension(ext, opts) {
|
|
|
855
872
|
text: `Parallel: ${done}/${allResults.length} done, ${running} running...`,
|
|
856
873
|
},
|
|
857
874
|
],
|
|
858
|
-
details: makeDetails("parallel")([...allResults]),
|
|
875
|
+
details: persistDetails(makeDetails("parallel")([...allResults])),
|
|
859
876
|
});
|
|
860
877
|
}
|
|
861
878
|
};
|
|
@@ -882,7 +899,7 @@ export default function subagentExtension(ext, opts) {
|
|
|
882
899
|
text: `Parallel: ${successCount}/${results.length} succeeded\n\n${summaries.join("\n\n")}`,
|
|
883
900
|
},
|
|
884
901
|
],
|
|
885
|
-
details: makeDetails("parallel")(results),
|
|
902
|
+
details: persistDetails(makeDetails("parallel")(results)),
|
|
886
903
|
};
|
|
887
904
|
}
|
|
888
905
|
// ── Single mode ─────────────────────────────────────────────
|
|
@@ -903,7 +920,7 @@ export default function subagentExtension(ext, opts) {
|
|
|
903
920
|
text: `Agent ${result.stopReason || "failed"}: ${errorMsg}`,
|
|
904
921
|
},
|
|
905
922
|
],
|
|
906
|
-
details: makeDetails("single")([result]),
|
|
923
|
+
details: persistDetails(makeDetails("single")([result])),
|
|
907
924
|
isError: true,
|
|
908
925
|
};
|
|
909
926
|
}
|
|
@@ -914,7 +931,7 @@ export default function subagentExtension(ext, opts) {
|
|
|
914
931
|
text: getFinalOutput(result.messages) || "(no output)",
|
|
915
932
|
},
|
|
916
933
|
],
|
|
917
|
-
details: makeDetails("single")([result]),
|
|
934
|
+
details: persistDetails(makeDetails("single")([result])),
|
|
918
935
|
};
|
|
919
936
|
}
|
|
920
937
|
const available = agents.map((a) => `${a.name} (${a.source})`).join(", ") || "none";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/relay/dispatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAQH,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,SAAS,EACT,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACf,MAAM,uBAAuB,CAAC;AAK/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAkDzD,OAAO,KAAK,EAEV,oBAAoB,EACpB,UAAU,EACX,MAAM,6BAA6B,CAAC;AAErC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAGzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/relay/dispatcher.ts"],"names":[],"mappings":"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"}
|
package/dist/relay/dispatcher.js
CHANGED
|
@@ -62,6 +62,7 @@ import { humanizeProviderError } from "../server/error-humanizer.js";
|
|
|
62
62
|
import { handleAutoResearch } from "./auto-research.js";
|
|
63
63
|
import { handleAutoOptimize } from "./auto-optimizer.js";
|
|
64
64
|
import { chunkHistory } from "./history-chunker.js";
|
|
65
|
+
import { projectHistoryForReplay } from "./history-projection.js";
|
|
65
66
|
/**
|
|
66
67
|
* Inline path matcher. Returns `null` for any path/method combination we
|
|
67
68
|
* don't recognise; the caller turns that into a `404 Unknown route`.
|
|
@@ -1166,7 +1167,7 @@ function makeRelaySubscriber(sessionId, relay) {
|
|
|
1166
1167
|
* preserved verbatim on `session_ready_start`; none are dropped.
|
|
1167
1168
|
*/
|
|
1168
1169
|
async function emitChunkedSessionReady(relay, sessionId, history, attachResult) {
|
|
1169
|
-
const chunks = chunkHistory(history);
|
|
1170
|
+
const chunks = chunkHistory(projectHistoryForReplay(history));
|
|
1170
1171
|
relay.send({
|
|
1171
1172
|
kind: "ws_event",
|
|
1172
1173
|
sessionId,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire-side compaction/projection for replayed session history.
|
|
3
|
+
*
|
|
4
|
+
* Stored `events_jsonl` blobs can grow to several megabytes because they
|
|
5
|
+
* contain data the browser never renders:
|
|
6
|
+
*
|
|
7
|
+
* - `tool_result.result.details.results` — the subagent's full internal
|
|
8
|
+
* transcript (`messages`, `systemPrompt`, `task`, `stderr`, …).
|
|
9
|
+
* - Hundreds of repeated `subagent_tool_progress` snapshots for the same
|
|
10
|
+
* `stepId`, each re-carrying full tool args and partial results.
|
|
11
|
+
*
|
|
12
|
+
* This module performs a **projection at replay build time**. It never
|
|
13
|
+
* mutates SQLite or the live agent stream; it only shapes the history that is
|
|
14
|
+
* about to be serialized into `history_chunk` frames.
|
|
15
|
+
*
|
|
16
|
+
* The projection is intentionally best-effort:
|
|
17
|
+
* - Malformed JSONL lines are skipped.
|
|
18
|
+
* - Unknown events and every field the frontend renders are left untouched.
|
|
19
|
+
*/
|
|
20
|
+
import type { WireMessage } from "../server/wire.js";
|
|
21
|
+
/** Byte ceiling for individual preserved strings inside a projected event. */
|
|
22
|
+
export declare const MAX_PROJECTED_FIELD_BYTES: number;
|
|
23
|
+
/** Marker appended when an oversized string is truncated. */
|
|
24
|
+
export declare const TRUNCATION_MARKER = "\n...[spectral truncated for history replay]";
|
|
25
|
+
/**
|
|
26
|
+
* Parse and project a newline-delimited `events_jsonl` string.
|
|
27
|
+
*
|
|
28
|
+
* - Skips blank/malformed lines.
|
|
29
|
+
* - Dedupes `subagent_tool_progress` by `stepId` (last occurrence wins).
|
|
30
|
+
* - Re-joins the projected events into a JSONL string.
|
|
31
|
+
*/
|
|
32
|
+
export declare function projectEventsJsonl(eventsJsonl: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* Project an entire persisted history tail before it is chunked and emitted.
|
|
35
|
+
*
|
|
36
|
+
* Only assistant messages carry `events_jsonl`. Messages whose projection is
|
|
37
|
+
* byte-identical to the input are returned by reference, so this is a no-op
|
|
38
|
+
* for already-compact histories.
|
|
39
|
+
*/
|
|
40
|
+
export declare function projectHistoryForReplay(history: readonly WireMessage[]): WireMessage[];
|
|
41
|
+
//# sourceMappingURL=history-projection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history-projection.d.ts","sourceRoot":"","sources":["../../src/relay/history-projection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,8EAA8E;AAC9E,eAAO,MAAM,yBAAyB,QAAa,CAAC;AAEpD,6DAA6D;AAC7D,eAAO,MAAM,iBAAiB,iDAAiD,CAAC;AAoMhF;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAwC9D;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,GAAG,WAAW,EAAE,CAMtF"}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/** Byte ceiling for individual preserved strings inside a projected event. */
|
|
2
|
+
export const MAX_PROJECTED_FIELD_BYTES = 256 * 1024; // 256 KB
|
|
3
|
+
/** Marker appended when an oversized string is truncated. */
|
|
4
|
+
export const TRUNCATION_MARKER = "\n...[spectral truncated for history replay]";
|
|
5
|
+
const textEncoder = new TextEncoder();
|
|
6
|
+
function isJsonObject(value) {
|
|
7
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* UTF-8-safe truncation to `maxBytes`. The returned string ends on a valid
|
|
11
|
+
* code point boundary (a binary search finds the longest decodable prefix).
|
|
12
|
+
*/
|
|
13
|
+
function truncateUtf8(value, maxBytes) {
|
|
14
|
+
const encoded = textEncoder.encode(value);
|
|
15
|
+
if (encoded.length <= maxBytes)
|
|
16
|
+
return value;
|
|
17
|
+
const markerBytes = textEncoder.encode(TRUNCATION_MARKER).length;
|
|
18
|
+
const targetBytes = Math.max(0, maxBytes - markerBytes);
|
|
19
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
20
|
+
let low = 0;
|
|
21
|
+
let high = encoded.length;
|
|
22
|
+
while (low < high) {
|
|
23
|
+
const mid = Math.ceil((low + high) / 2);
|
|
24
|
+
try {
|
|
25
|
+
decoder.decode(encoded.subarray(0, mid));
|
|
26
|
+
low = mid;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
high = mid - 1;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const targetEnd = Math.min(low, targetBytes);
|
|
33
|
+
return decoder.decode(encoded.subarray(0, targetEnd)) + TRUNCATION_MARKER;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Recursively cap strings in a JSON value at `MAX_PROJECTED_FIELD_BYTES`.
|
|
37
|
+
* Preserves the shape of objects/arrays and all non-string primitives.
|
|
38
|
+
*/
|
|
39
|
+
function truncateStrings(value) {
|
|
40
|
+
if (typeof value === "string") {
|
|
41
|
+
return truncateUtf8(value, MAX_PROJECTED_FIELD_BYTES);
|
|
42
|
+
}
|
|
43
|
+
if (Array.isArray(value)) {
|
|
44
|
+
return value.map((item) => truncateStrings(item));
|
|
45
|
+
}
|
|
46
|
+
if (isJsonObject(value)) {
|
|
47
|
+
const out = {};
|
|
48
|
+
for (const [key, item] of Object.entries(value)) {
|
|
49
|
+
out[key] = truncateStrings(item);
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Project a single `tool_result` event.
|
|
57
|
+
*
|
|
58
|
+
* `result.details.results` can contain the subagent's full internal transcript
|
|
59
|
+
* (~1 MB of dead weight). The frontend only reads `result.content` plus
|
|
60
|
+
* `details.status`/`details.mode`, so we keep exactly those and drop the rest.
|
|
61
|
+
*/
|
|
62
|
+
function projectToolResult(event) {
|
|
63
|
+
const result = event.result;
|
|
64
|
+
if (!isJsonObject(result)) {
|
|
65
|
+
return { ...event, result: truncateStrings(result) };
|
|
66
|
+
}
|
|
67
|
+
const details = result.details;
|
|
68
|
+
let projectedResult = result;
|
|
69
|
+
if (isJsonObject(details)) {
|
|
70
|
+
const compactDetails = {};
|
|
71
|
+
if ("status" in details)
|
|
72
|
+
compactDetails.status = details.status;
|
|
73
|
+
if ("mode" in details)
|
|
74
|
+
compactDetails.mode = details.mode;
|
|
75
|
+
projectedResult = { ...result, details: compactDetails };
|
|
76
|
+
}
|
|
77
|
+
if (typeof projectedResult.content === "string") {
|
|
78
|
+
projectedResult = {
|
|
79
|
+
...projectedResult,
|
|
80
|
+
content: truncateUtf8(projectedResult.content, MAX_PROJECTED_FIELD_BYTES),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return { ...event, result: projectedResult };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Project a single `subagent_tool_progress` event. Deduplication is handled
|
|
87
|
+
* in `projectEventsJsonl`; here we only cap oversized string fields that are
|
|
88
|
+
* repeated across hundreds of snapshots.
|
|
89
|
+
*/
|
|
90
|
+
function projectSubagentToolProgress(event) {
|
|
91
|
+
const out = { ...event };
|
|
92
|
+
if ("args" in out)
|
|
93
|
+
out.args = truncateStrings(out.args);
|
|
94
|
+
if ("result" in out)
|
|
95
|
+
out.result = truncateStrings(out.result);
|
|
96
|
+
if ("partialResult" in out)
|
|
97
|
+
out.partialResult = truncateStrings(out.partialResult);
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Concatenate `delta` payloads from a run of text or thinking deltas.
|
|
102
|
+
*
|
|
103
|
+
* `delta` is a string in the wire frame. Non-string/null deltas are treated
|
|
104
|
+
* as empty rather than risking malformed output; the wire format never
|
|
105
|
+
* produces them for these event types.
|
|
106
|
+
*/
|
|
107
|
+
function concatDeltas(events) {
|
|
108
|
+
return events.map((event) => event.delta ?? "").join("");
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Merge a run of adjacent `text_delta` or `thinking_delta` events into one.
|
|
112
|
+
*
|
|
113
|
+
* The run is guaranteed to have a single event type. Only
|
|
114
|
+
* `type`/`messageId`/`delta` (plus merged `metadata` for thinking) survive;
|
|
115
|
+
* all other per-event fields are framing noise and are intentionally dropped.
|
|
116
|
+
*/
|
|
117
|
+
function coalesceDeltaRun(run) {
|
|
118
|
+
const first = run[0];
|
|
119
|
+
const delta = concatDeltas(run);
|
|
120
|
+
if (first.type === "text_delta") {
|
|
121
|
+
const merged = { type: "text_delta", delta };
|
|
122
|
+
if (first.messageId !== undefined)
|
|
123
|
+
merged.messageId = first.messageId;
|
|
124
|
+
return merged;
|
|
125
|
+
}
|
|
126
|
+
if (first.type === "thinking_delta") {
|
|
127
|
+
const metadata = {};
|
|
128
|
+
for (const event of run) {
|
|
129
|
+
if (isJsonObject(event.metadata))
|
|
130
|
+
Object.assign(metadata, event.metadata);
|
|
131
|
+
}
|
|
132
|
+
const merged = { type: "thinking_delta", delta };
|
|
133
|
+
if (first.messageId !== undefined)
|
|
134
|
+
merged.messageId = first.messageId;
|
|
135
|
+
if (Object.keys(metadata).length > 0)
|
|
136
|
+
merged.metadata = metadata;
|
|
137
|
+
return merged;
|
|
138
|
+
}
|
|
139
|
+
throw new Error(`Unexpected delta run type: ${String(first.type)}`);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Coalesce adjacent `text_delta` and `thinking_delta` events into single
|
|
143
|
+
* events, preserving overall ordering and every character of delta text.
|
|
144
|
+
*
|
|
145
|
+
* Runs stop at any event of a different type, so `thinking_end` /
|
|
146
|
+
* `thinking_start`, `tool_call` / `tool_result`, and all other segment
|
|
147
|
+
* boundaries remain intact.
|
|
148
|
+
*/
|
|
149
|
+
function coalesceDeltaEvents(events) {
|
|
150
|
+
const coalesced = [];
|
|
151
|
+
let run = [];
|
|
152
|
+
const flush = () => {
|
|
153
|
+
if (run.length > 0)
|
|
154
|
+
coalesced.push(coalesceDeltaRun(run));
|
|
155
|
+
run = [];
|
|
156
|
+
};
|
|
157
|
+
for (const event of events) {
|
|
158
|
+
const isDeltaEvent = event.type === "text_delta" || event.type === "thinking_delta";
|
|
159
|
+
if (!isDeltaEvent) {
|
|
160
|
+
flush();
|
|
161
|
+
coalesced.push(event);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (run.length > 0 && run[0].type !== event.type)
|
|
165
|
+
flush();
|
|
166
|
+
run.push(event);
|
|
167
|
+
}
|
|
168
|
+
flush();
|
|
169
|
+
return coalesced;
|
|
170
|
+
}
|
|
171
|
+
/** Apply the event-specific projection rules. */
|
|
172
|
+
function projectEvent(event) {
|
|
173
|
+
switch (event.type) {
|
|
174
|
+
case "tool_result":
|
|
175
|
+
return projectToolResult(event);
|
|
176
|
+
case "subagent_tool_progress":
|
|
177
|
+
return projectSubagentToolProgress(event);
|
|
178
|
+
default:
|
|
179
|
+
return event;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Parse and project a newline-delimited `events_jsonl` string.
|
|
184
|
+
*
|
|
185
|
+
* - Skips blank/malformed lines.
|
|
186
|
+
* - Dedupes `subagent_tool_progress` by `stepId` (last occurrence wins).
|
|
187
|
+
* - Re-joins the projected events into a JSONL string.
|
|
188
|
+
*/
|
|
189
|
+
export function projectEventsJsonl(eventsJsonl) {
|
|
190
|
+
if (!eventsJsonl)
|
|
191
|
+
return eventsJsonl;
|
|
192
|
+
const hadTrailingNewline = eventsJsonl.endsWith("\n");
|
|
193
|
+
const lines = eventsJsonl.split("\n");
|
|
194
|
+
const events = [];
|
|
195
|
+
for (const line of lines) {
|
|
196
|
+
if (line.length === 0)
|
|
197
|
+
continue;
|
|
198
|
+
try {
|
|
199
|
+
const parsed = JSON.parse(line);
|
|
200
|
+
if (isJsonObject(parsed))
|
|
201
|
+
events.push(parsed);
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// Best-effort: a malformed line cannot be recovered safely, so skip it.
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (events.length === 0)
|
|
208
|
+
return "";
|
|
209
|
+
// Keep only the last `subagent_tool_progress` snapshot per stepId. Iterate
|
|
210
|
+
// backwards so the retained event stays at the position of its last
|
|
211
|
+
// occurrence, then reverse back to chronological order.
|
|
212
|
+
const seenStepIds = new Set();
|
|
213
|
+
const kept = [];
|
|
214
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
215
|
+
const event = events[i];
|
|
216
|
+
if (event.type === "subagent_tool_progress") {
|
|
217
|
+
const stepId = event.stepId;
|
|
218
|
+
if (typeof stepId === "string" && stepId.length > 0) {
|
|
219
|
+
if (seenStepIds.has(stepId))
|
|
220
|
+
continue;
|
|
221
|
+
seenStepIds.add(stepId);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
kept.push(event);
|
|
225
|
+
}
|
|
226
|
+
kept.reverse();
|
|
227
|
+
const coalesced = coalesceDeltaEvents(kept);
|
|
228
|
+
const joined = coalesced.map((event) => JSON.stringify(projectEvent(event))).join("\n");
|
|
229
|
+
return hadTrailingNewline ? `${joined}\n` : joined;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Project an entire persisted history tail before it is chunked and emitted.
|
|
233
|
+
*
|
|
234
|
+
* Only assistant messages carry `events_jsonl`. Messages whose projection is
|
|
235
|
+
* byte-identical to the input are returned by reference, so this is a no-op
|
|
236
|
+
* for already-compact histories.
|
|
237
|
+
*/
|
|
238
|
+
export function projectHistoryForReplay(history) {
|
|
239
|
+
return history.map((message) => {
|
|
240
|
+
if (message.role !== "assistant" || !message.events)
|
|
241
|
+
return message;
|
|
242
|
+
const events = projectEventsJsonl(message.events);
|
|
243
|
+
return events === message.events ? message : { ...message, events };
|
|
244
|
+
});
|
|
245
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../../src/server/handlers/sessions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,KAAK,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../../src/server/handlers/sessions.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* bridge driving events at a row that no longer exists. This matches the
|
|
10
10
|
* ordering the old Hono route used.
|
|
11
11
|
*/
|
|
12
|
+
import { projectHistoryForReplay } from "../../relay/history-projection.js";
|
|
12
13
|
import { BadRequestError, NotFoundError } from "./errors.js";
|
|
13
14
|
export function parseCompactSessionMode(body) {
|
|
14
15
|
if (body === undefined || body === null)
|
|
@@ -68,14 +69,15 @@ export function handleGetSessionMessages(store, id, query) {
|
|
|
68
69
|
beforeId,
|
|
69
70
|
limit,
|
|
70
71
|
});
|
|
72
|
+
const projectedMessages = projectHistoryForReplay(messages);
|
|
71
73
|
const totalMessageCount = store.countMessagesBySession(id);
|
|
72
|
-
const loadedMessageCount =
|
|
74
|
+
const loadedMessageCount = projectedMessages.length;
|
|
73
75
|
return {
|
|
74
76
|
...meta,
|
|
75
|
-
messages,
|
|
77
|
+
messages: projectedMessages,
|
|
76
78
|
totalMessageCount,
|
|
77
79
|
loadedMessageCount,
|
|
78
|
-
hasEarlierMessages: totalMessageCount > loadedMessageCount &&
|
|
80
|
+
hasEarlierMessages: totalMessageCount > loadedMessageCount && projectedMessages.length > 0,
|
|
79
81
|
};
|
|
80
82
|
}
|
|
81
83
|
export function handleUpdateSession(store, id, body) {
|