dsh-phone-bridge 0.2.6 → 0.2.8

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.
Files changed (2) hide show
  1. package/index.js +98 -18
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -244,6 +244,32 @@ const REQUIRED_CONTROLLER_METHODS = [
244
244
  "selectModel",
245
245
  ];
246
246
 
247
+ // The DSH version this build was last exercised against. Bumped by hand when the
248
+ // plugin is re-tested on a newer DSH. It is not a compatibility claim, only the
249
+ // value /health compares the running version to, so that a mismatch shows up as a
250
+ // sentence rather than as a mystery.
251
+ const TESTED_WITH_DSH = "0.2.0-rc.2";
252
+
253
+ // Best effort. The plugin runs inside the DSH process, and Electron patches fs so
254
+ // an app.asar reads like a directory, which is what makes this possible at all.
255
+ // Returns null when it cannot be read; /health says so rather than guessing.
256
+ function readDshVersion() {
257
+ const candidates = [];
258
+ if (process.resourcesPath) {
259
+ candidates.push(join(process.resourcesPath, "app.asar", "package.json"));
260
+ candidates.push(join(process.resourcesPath, "app", "package.json"));
261
+ }
262
+ for (const candidate of candidates) {
263
+ try {
264
+ const parsed = JSON.parse(readFileSync(candidate, "utf8"));
265
+ if (parsed?.version) return { name: parsed.name ?? null, version: parsed.version };
266
+ } catch {
267
+ // try the next candidate
268
+ }
269
+ }
270
+ return null;
271
+ }
272
+
247
273
  // A session lives at sessions/<slug-of-cwd>/<sessionId>/, where the slug is a
248
274
  // lossy encoding of the working directory. Locate it by scanning instead of
249
275
  // trying to re-derive that encoding.
@@ -943,29 +969,74 @@ export function apply(ctx, config = {}) {
943
969
  }
944
970
  const sessionController = ctx.get("sessionController");
945
971
  if (!sessionController) throw new Error("sessionController is unavailable");
946
- if (typeof sessionController.page !== "function") {
947
- throw new Error("this DSH build does not expose sessionController.page");
948
- }
949
972
 
950
973
  const signal = new AbortController().signal;
951
- // throughSeq: -1 means "the newest page"; maxMessages stays well above the
952
- // requested count because assistant and tool events sit between the user's.
953
- const page = await sessionController.page({
954
- address: { kind: "session", sessionId },
955
- throughSeq: -1,
956
- maxMessages: 200,
957
- }, signal);
974
+ const traces = {};
975
+
976
+ // Two ways in, tried in order.
977
+ //
978
+ // inspect() hands back the event list outright (SessionInspection.events),
979
+ // so it needs no cursor. page() does need one: throughSeq is an absolute
980
+ // sequence, and -1 is not "newest" but "nothing", since paginate starts at
981
+ // end = throughSeq + 1 = 0 and walks backwards from -1. That misreading is
982
+ // what made the first version answer ok with zero messages.
983
+ let events = [];
984
+ let used = null;
985
+
986
+ if (typeof sessionController.inspect === "function") {
987
+ try {
988
+ const inspection = await sessionController.inspect(sessionId, signal);
989
+ const list = Array.isArray(inspection?.events) ? inspection.events : [];
990
+ traces.inspect = {
991
+ eventCount: list.length,
992
+ firstSeq: list[0]?.seq ?? null,
993
+ lastSeq: list[list.length - 1]?.seq ?? null,
994
+ };
995
+ if (list.length) {
996
+ events = list;
997
+ used = "inspect";
998
+ }
999
+ } catch (error) {
1000
+ traces.inspect = { error: String(error?.message ?? error) };
1001
+ }
1002
+ }
1003
+
1004
+ if (!events.length && typeof sessionController.page === "function") {
1005
+ try {
1006
+ // Page from whatever cursor inspect gave us, else from the very start.
1007
+ const cursor = Number(traces.inspect?.lastSeq ?? 0);
1008
+ const page = await sessionController.page({
1009
+ address: { kind: "session", sessionId },
1010
+ throughSeq: Number.isSafeInteger(cursor) && cursor >= 0 ? cursor : 0,
1011
+ maxMessages: 400,
1012
+ }, signal);
1013
+ const list = (page?.records ?? []).map((record) => record?.event).filter(Boolean);
1014
+ traces.page = {
1015
+ cursor,
1016
+ recordCount: list.length,
1017
+ hasMore: page?.hasMore ?? null,
1018
+ firstSeq: list[0]?.seq ?? null,
1019
+ lastSeq: list[list.length - 1]?.seq ?? null,
1020
+ };
1021
+ if (list.length) {
1022
+ events = list;
1023
+ used = "page";
1024
+ }
1025
+ } catch (error) {
1026
+ traces.page = { error: String(error?.message ?? error) };
1027
+ }
1028
+ }
958
1029
 
959
1030
  const texts = [];
960
- const allRecords = page?.records ?? [];
961
1031
  const typeTally = {};
962
1032
  let firstUserEvent = null;
963
- for (const record of allRecords) {
964
- const event = record?.event;
1033
+ let userEventCount = 0;
1034
+ for (const event of events) {
965
1035
  if (!event) continue;
966
1036
  const key = event.type ?? "(none)";
967
1037
  typeTally[key] = (typeTally[key] ?? 0) + 1;
968
1038
  if (event.type !== "user/message") continue;
1039
+ userEventCount += 1;
969
1040
  if (!firstUserEvent) firstUserEvent = event;
970
1041
  if (event?.data?.source?.kind !== "user") continue;
971
1042
  const text = textFromContentBlocks(event.data.content);
@@ -976,6 +1047,7 @@ export function apply(ctx, config = {}) {
976
1047
  const payload = {
977
1048
  ok: true,
978
1049
  sessionId,
1050
+ source: used,
979
1051
  total: texts.length,
980
1052
  // Newest last, so the caller can slice from the end.
981
1053
  messages: texts.slice(-limit),
@@ -986,12 +1058,13 @@ export function apply(ctx, config = {}) {
986
1058
  // why is to look at what actually arrived. Loopback-only, and truncated.
987
1059
  if ((url.searchParams.get("debug") ?? "") === "1") {
988
1060
  payload.diagnostics = {
989
- recordCount: allRecords.length,
990
- hasMore: page?.hasMore ?? null,
991
- recordKeys: allRecords[0] ? Object.keys(allRecords[0]) : [],
1061
+ used,
1062
+ eventCount: events.length,
1063
+ userEventCount,
992
1064
  eventTypes: typeTally,
993
- firstRecordSample: allRecords[0] ? JSON.stringify(allRecords[0]).slice(0, 700) : null,
994
- firstUserEventSample: firstUserEvent ? JSON.stringify(firstUserEvent).slice(0, 1400) : null,
1065
+ traces,
1066
+ firstEventKeys: events[0] ? Object.keys(events[0]) : [],
1067
+ firstUserEventSample: firstUserEvent ? JSON.stringify(firstUserEvent).slice(0, 1500) : null,
995
1068
  };
996
1069
  }
997
1070
 
@@ -1011,11 +1084,18 @@ export function apply(ctx, config = {}) {
1011
1084
  const missing = sessionController
1012
1085
  ? REQUIRED_CONTROLLER_METHODS.filter((name) => typeof sessionController[name] !== "function")
1013
1086
  : REQUIRED_CONTROLLER_METHODS.slice();
1087
+ const dsh = readDshVersion();
1014
1088
  sendJson(res, 200, {
1015
1089
  ok: missing.length === 0,
1016
1090
  controllerAvailable: Boolean(sessionController),
1017
1091
  required: REQUIRED_CONTROLLER_METHODS,
1018
1092
  missing,
1093
+ // The method list above is the real compatibility signal. The version is
1094
+ // context: a newer DSH can keep every method and still change what one of
1095
+ // them means, and that is the one failure nothing here can detect.
1096
+ testedWith: TESTED_WITH_DSH,
1097
+ dshVersion: dsh?.version ?? null,
1098
+ versionMatchesTested: dsh?.version ? dsh.version === TESTED_WITH_DSH : null,
1019
1099
  });
1020
1100
  } catch (error) {
1021
1101
  sendJson(res, 500, { ok: false, error: String(error?.message ?? error) });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-phone-bridge",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "Drive an existing DSH desktop session from a chat app, plus a sidebar delete button and trash panel",
5
5
  "type": "module",
6
6
  "main": "index.js",