dsh-context-fold 0.1.1 → 0.1.3

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/lib/client.js CHANGED
@@ -12,16 +12,31 @@ const zh = { "tab.contextFold": "上下文折叠" };
12
12
  /** Dictionary: keep keys aligned with `locale/*.json`. */
13
13
  const en = { "tab.contextFold": "Context fold" };
14
14
  const react = require("react");
15
- /** Fetch the report for one session. */
15
+ /**
16
+ * Fetch the report for one session.
17
+ *
18
+ * Failures are reported rather than swallowed: before the routes existed the
19
+ * tab rendered "no report available", which reads like "this session folded
20
+ * nothing" instead of "the request never landed".
21
+ *
22
+ * @param sessionId - session to report on.
23
+ * @returns the payload, or a message describing why there is none.
24
+ */
16
25
  async function fetchReport(sessionId) {
17
- const response = await fetch("/api/context-fold/report", {
18
- method: "POST",
19
- headers: { "content-type": "application/json" },
20
- body: JSON.stringify({ sessionId })
21
- });
22
- if (!response.ok) return void 0;
23
- const body = await response.json();
24
- return body.ok === true ? body.value : void 0;
26
+ let response;
27
+ try {
28
+ response = await fetch("/api/context-fold/report", {
29
+ method: "POST",
30
+ headers: { "content-type": "application/json" },
31
+ body: JSON.stringify({ sessionId })
32
+ });
33
+ } catch (error) {
34
+ return { error: `request failed: ${error instanceof Error ? error.message : String(error)}` };
35
+ }
36
+ const body = await response.json().catch(() => void 0);
37
+ if (!response.ok) return { error: body?.error ?? `HTTP ${String(response.status)}` };
38
+ if (body?.ok !== true || body.value === void 0) return { error: body?.error ?? "the server returned no value" };
39
+ return { report: body.value };
25
40
  }
26
41
  /** Fetch the full text behind one fold. */
27
42
  async function fetchOriginal(sessionId, locator) {
@@ -83,6 +98,10 @@ const styles = {
83
98
  marginTop: "6px",
84
99
  color: "#facc15"
85
100
  },
101
+ error: {
102
+ marginTop: "6px",
103
+ color: "#f87171"
104
+ },
86
105
  reread: {
87
106
  marginTop: "6px",
88
107
  color: "#f87171"
@@ -231,11 +250,14 @@ function ContextFoldTab(props) {
231
250
  }
232
251
  let cancelled = false;
233
252
  setState({ status: "loading" });
234
- fetchReport(sessionId).then((report) => {
253
+ fetchReport(sessionId).then((result) => {
235
254
  if (cancelled) return;
236
- setState(report === void 0 ? { status: "empty" } : {
255
+ setState("report" in result ? {
237
256
  status: "ready",
238
- report
257
+ report: result.report
258
+ } : {
259
+ status: "error",
260
+ error: result.error
239
261
  });
240
262
  });
241
263
  return () => {
@@ -254,10 +276,14 @@ function ContextFoldTab(props) {
254
276
  style: styles.muted,
255
277
  key: "m"
256
278
  }, "Loading…"));
279
+ else if (state.status === "error") children.push(react.createElement("div", {
280
+ style: styles.error,
281
+ key: "e"
282
+ }, `Could not load the report: ${state.error ?? "unknown"}`));
257
283
  else if (state.status !== "ready" || state.report === void 0) children.push(react.createElement("div", {
258
284
  style: styles.muted,
259
285
  key: "m"
260
- }, "No report available for this session."));
286
+ }, "This session has no folds to report."));
261
287
  else {
262
288
  children.push(react.createElement(Summary, {
263
289
  key: "summary",
package/lib/index.js CHANGED
@@ -1407,7 +1407,7 @@ function buildReport(session, deps) {
1407
1407
  const event = session.eventAt(fold.seq);
1408
1408
  const message = event === void 0 ? null : session.deriveEventMessage(event);
1409
1409
  const content = message !== null && message.role === "tool" ? message.content : void 0;
1410
- const tokensAfter = content === void 0 ? 0 : deps.estimateMessage(content);
1410
+ const tokensAfter = message === null ? 0 : deps.estimateMessage(message);
1411
1411
  const inline = inlineTextOf(content) ?? "";
1412
1412
  const locator = locatorOf(inline);
1413
1413
  folds.push({
@@ -1444,10 +1444,7 @@ function buildReport(session, deps) {
1444
1444
  charsBefore: target.chars?.[position] ?? 0,
1445
1445
  charsAfter: Array.from(block.text).length,
1446
1446
  tokensBefore: estimateFromChars(target.chars?.[position] ?? 0),
1447
- tokensAfter: deps.estimateMessage([{
1448
- type: "text",
1449
- text: block.text
1450
- }]),
1447
+ tokensAfter: estimateFromChars(Array.from(block.text).length),
1451
1448
  inline: block.text,
1452
1449
  recoverable: locator !== void 0,
1453
1450
  ...locator === void 0 ? {} : { locator }
@@ -1525,11 +1522,12 @@ function json(value, status = 200) {
1525
1522
  /**
1526
1523
  * Register the two read-only routes the tab uses.
1527
1524
  *
1528
- * @param register - the host route registrar.
1525
+ * @param connection - the host `connection` service (its `fetch.register` is the route registrar).
1529
1526
  * @param deps - session and spill access.
1530
1527
  * @returns a disposer for both routes.
1531
1528
  */
1532
- function registerRoutes(register, deps) {
1529
+ function registerRoutes(connection, deps) {
1530
+ const register = connection.fetch.register.bind(connection.fetch);
1533
1531
  const report = register({
1534
1532
  path: "/api/context-fold/report",
1535
1533
  methods: ["POST"],
@@ -1720,18 +1718,18 @@ function apply(ctx, rawConfig = {}) {
1720
1718
  });
1721
1719
  ctx.inject(["connection"], (scope) => {
1722
1720
  const connection = scope.connection;
1723
- if (connection === void 0) {
1724
- ctx.logger.warn("context-fold: no ctx.connection service; the conversation tab has no data source");
1721
+ if (connection === void 0 || typeof connection.fetch?.register !== "function") {
1722
+ ctx.logger.warn("context-fold: no ctx.connection.fetch.register; the conversation tab has no data source");
1725
1723
  return;
1726
1724
  }
1727
1725
  const tokenMeter = ctx.get("tokenMeter");
1728
- registerRoutes(connection, {
1726
+ ctx.effect(() => registerRoutes(connection, {
1729
1727
  sessionOf: (id) => {
1730
1728
  return ctx.get("sessions")?.get(id);
1731
1729
  },
1732
1730
  estimateMessage: (message) => tokenMeter === void 0 ? 0 : tokenMeter.estimateMessage(message),
1733
1731
  ...ctx.get("spillStore") === void 0 ? {} : { spillStore: ctx.get("spillStore") }
1734
- });
1732
+ }), "context-fold: conversation tab routes");
1735
1733
  });
1736
1734
  ctx.on("session/disposed", (session) => {
1737
1735
  states.delete(session.header.id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-context-fold",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "dsh-context-fold: stage-boundary folding of completed tool results into a conclusion plus a spill path, deterministic and replay-safe.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",