dsh-notch 0.3.6 → 0.3.7

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
@@ -4,7 +4,7 @@ window.__ModuleLoader__.load({
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
- //#region src/client/index.ts
7
+ // src/client/index.ts
8
8
  const name = "dsh-notch-client";
9
9
  const inject = [
10
10
  "sessions",
@@ -101,7 +101,6 @@ window.__ModuleLoader__.load({
101
101
  controller.abort();
102
102
  }, "dsh-notch: client sync");
103
103
  }
104
- //#endregion
105
104
  exports.apply = apply;
106
105
  exports.inject = inject;
107
106
  exports.name = name;
package/lib/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":[],"sources":["../src/client/index.ts"],"sourcesContent":["import type { ISessions } from '@deepseek-ai/dsh-api-session-controller/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-session/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-workspace/client'\nimport type { SessionId } from '@deepseek-ai/dsh-session/types'\n/**\n * Headless follower: polls the Host for notch focus wishes and selects the\n * session in every open DSH page (app webview or browser tab).\n *\n * First poll only arms (ignores a wish already sitting there) so a reload\n * does not yank the current session. A later click bumps `at` and opens.\n */\nimport type { Context as ClientContext } from '@deepseek-ai/cordis'\n\nexport const name = 'dsh-notch-client'\nexport const inject = ['sessions', 'uiWorkspace', 'uiSession']\n\ninterface PendingFocusWire {\n ok?: boolean\n focus?: { sessionId?: string; at?: number } | null\n}\n\nconst POLL_MS = 400\n\nexport function apply(ctx: ClientContext): void {\n const sessions = ctx.get('sessions') as unknown as ISessions\n const clientId = crypto.randomUUID()\n let lastAt = 0\n let armed = false\n let stopped = false\n const controller = new AbortController()\n\n const open = async (sessionId: string): Promise<void> => {\n try {\n ctx.uiWorkspace.openSession(sessionId as SessionId)\n return\n } catch {\n /* list may be stale; refresh once and retry */\n }\n try {\n await sessions.refresh()\n if (stopped) return\n ctx.uiWorkspace.openSession(sessionId as SessionId)\n } catch {\n /* session not in this page's list */\n }\n }\n\n const poll = async (): Promise<void> => {\n try {\n const snapshot = sessions.list.getSnapshot()\n if (snapshot.phase === 'ready') {\n // parentId also records independent user forks; only origin classifies\n // a delegated child, matching the official workspace sidebar.\n const rows = snapshot.ids.map(id => snapshot.byId[id]).filter(row => row && row.origin !== 'subagent').map(row => ({ id: row.id, title: row.displayTitle, completed: ctx.uiSession.sessionStatus.getSnapshot().get(row.id)?.completionUnread === true, running: row.running, updatedAt: row.updatedAt }))\n const focused = document.hasFocus()\n const current = rows.find(row => row.id === Object.values(snapshot.byId).find(item => (item.retainedBy?.mainView ?? 0) > 0)?.id)\n // updatedAt is the user's prompt timestamp, not the completion time.\n // Capture this observation before sending it, so a delayed request\n // cannot acknowledge a turn that finishes after the user looked.\n const viewed = focused && current && !current.running\n ? { id: current.id, at: Date.now() } : undefined\n await fetch('/dsh-notch/sidebar', { signal: controller.signal, method: 'POST', headers: { 'content-type': 'application/json', 'x-dsh-notch-client': '1' }, body: JSON.stringify({ clientId, projectionVersion: 3, focused, viewed, rows }) })\n }\n if (stopped) return\n const response = await fetch('/dsh-notch/pending-focus', { cache: 'no-store', signal: controller.signal })\n if (!response.ok) return\n const data = (await response.json()) as PendingFocusWire\n if (stopped) return\n const focus = data.focus\n if (!armed) {\n armed = true\n if (focus && typeof focus.at === 'number') lastAt = focus.at\n return\n }\n if (!focus || typeof focus.at !== 'number' || focus.at <= lastAt) return\n if (typeof focus.sessionId !== 'string' || focus.sessionId === '') return\n lastAt = focus.at\n await open(focus.sessionId)\n try {\n if (stopped) return\n window.focus()\n } catch {\n /* native helper already fronts DSH.app */\n }\n } catch {\n /* Host unreachable mid-poll; the next tick retries. */\n }\n }\n\n const tick = (): void => {\n if (stopped) return\n void poll().finally(() => {\n if (!stopped) timer = window.setTimeout(tick, POLL_MS)\n })\n }\n let timer: ReturnType<typeof window.setTimeout> | undefined\n tick()\n\n ctx.effect(() => () => {\n stopped = true\n if (timer !== undefined) window.clearTimeout(timer)\n controller.abort()\n }, 'dsh-notch: client sync')\n}\n"],"mappings":";;;;;;;EAaA,MAAa,OAAO;EACpB,MAAa,SAAS;GAAC;GAAY;GAAe;EAAW;EAO7D,MAAM,UAAU;EAEhB,SAAgB,MAAM,KAA0B;GAC9C,MAAM,WAAW,IAAI,IAAI,UAAU;GACnC,MAAM,WAAW,OAAO,WAAW;GACnC,IAAI,SAAS;GACb,IAAI,QAAQ;GACZ,IAAI,UAAU;GACd,MAAM,aAAa,IAAI,gBAAgB;GAEvC,MAAM,OAAO,OAAO,cAAqC;IACvD,IAAI;KACF,IAAI,YAAY,YAAY,SAAsB;KAClD;IACF,QAAQ,CAER;IACA,IAAI;KACF,MAAM,SAAS,QAAQ;KACvB,IAAI,SAAS;KACb,IAAI,YAAY,YAAY,SAAsB;IACpD,QAAQ,CAER;GACF;GAEA,MAAM,OAAO,YAA2B;IACtC,IAAI;KACF,MAAM,WAAW,SAAS,KAAK,YAAY;KAC3C,IAAI,SAAS,UAAU,SAAS;MAG9B,MAAM,OAAO,SAAS,IAAI,KAAI,OAAM,SAAS,KAAK,GAAG,CAAC,CAAC,QAAO,QAAO,OAAO,IAAI,WAAW,UAAU,CAAC,CAAC,KAAI,SAAQ;OAAE,IAAI,IAAI;OAAI,OAAO,IAAI;OAAc,WAAW,IAAI,UAAU,cAAc,YAAY,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,qBAAqB;OAAM,SAAS,IAAI;OAAS,WAAW,IAAI;MAAU,EAAE;MACxS,MAAM,UAAU,SAAS,SAAS;MAClC,MAAM,UAAU,KAAK,MAAK,QAAO,IAAI,OAAO,OAAO,OAAO,SAAS,IAAI,CAAC,CAAC,MAAK,UAAS,KAAK,YAAY,YAAY,KAAK,CAAC,CAAC,EAAE,EAAE;MAI/H,MAAM,SAAS,WAAW,WAAW,CAAC,QAAQ,UAC1C;OAAE,IAAI,QAAQ;OAAI,IAAI,KAAK,IAAI;MAAE,IAAI,KAAA;MACzC,MAAM,MAAM,sBAAsB;OAAE,QAAQ,WAAW;OAAQ,QAAQ;OAAQ,SAAS;QAAE,gBAAgB;QAAoB,sBAAsB;OAAI;OAAG,MAAM,KAAK,UAAU;QAAE;QAAU,mBAAmB;QAAG;QAAS;QAAQ;OAAK,CAAC;MAAE,CAAC;KAC9O;KACA,IAAI,SAAS;KACb,MAAM,WAAW,MAAM,MAAM,4BAA4B;MAAE,OAAO;MAAY,QAAQ,WAAW;KAAO,CAAC;KACzG,IAAI,CAAC,SAAS,IAAI;KAClB,MAAM,OAAQ,MAAM,SAAS,KAAK;KAClC,IAAI,SAAS;KACb,MAAM,QAAQ,KAAK;KACnB,IAAI,CAAC,OAAO;MACV,QAAQ;MACR,IAAI,SAAS,OAAO,MAAM,OAAO,UAAU,SAAS,MAAM;MAC1D;KACF;KACA,IAAI,CAAC,SAAS,OAAO,MAAM,OAAO,YAAY,MAAM,MAAM,QAAQ;KAClE,IAAI,OAAO,MAAM,cAAc,YAAY,MAAM,cAAc,IAAI;KACnE,SAAS,MAAM;KACf,MAAM,KAAK,MAAM,SAAS;KAC1B,IAAI;MACF,IAAI,SAAS;MACb,OAAO,MAAM;KACf,QAAQ,CAER;IACF,QAAQ,CAER;GACF;GAEA,MAAM,aAAmB;IACvB,IAAI,SAAS;IACb,KAAU,CAAC,CAAC,cAAc;KACxB,IAAI,CAAC,SAAS,QAAQ,OAAO,WAAW,MAAM,OAAO;IACvD,CAAC;GACH;GACA,IAAI;GACJ,KAAK;GAEL,IAAI,mBAAmB;IACrB,UAAU;IACV,IAAI,UAAU,KAAA,GAAW,OAAO,aAAa,KAAK;IAClD,WAAW,MAAM;GACnB,GAAG,wBAAwB;EAC7B"}
1
+ {"version":3,"file":"client.js","names":[],"sources":["../src/client/index.ts"],"sourcesContent":["import type { ISessions } from '@deepseek-ai/dsh-api-session-controller/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-session/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-workspace/client'\nimport type { SessionId } from '@deepseek-ai/dsh-session/types'\n/**\n * Headless follower: polls the Host for notch focus wishes and selects the\n * session in every open DSH page (app webview or browser tab).\n *\n * First poll only arms (ignores a wish already sitting there) so a reload\n * does not yank the current session. A later click bumps `at` and opens.\n */\nimport type { Context as ClientContext } from '@deepseek-ai/cordis'\n\nexport const name = 'dsh-notch-client'\nexport const inject = ['sessions', 'uiWorkspace', 'uiSession']\n\ninterface PendingFocusWire {\n ok?: boolean\n focus?: { sessionId?: string; at?: number } | null\n}\n\nconst POLL_MS = 400\n\nexport function apply(ctx: ClientContext): void {\n const sessions = ctx.get('sessions') as unknown as ISessions\n const clientId = crypto.randomUUID()\n let lastAt = 0\n let armed = false\n let stopped = false\n const controller = new AbortController()\n\n const open = async (sessionId: string): Promise<void> => {\n try {\n ctx.uiWorkspace.openSession(sessionId as SessionId)\n return\n } catch {\n /* list may be stale; refresh once and retry */\n }\n try {\n await sessions.refresh()\n if (stopped) return\n ctx.uiWorkspace.openSession(sessionId as SessionId)\n } catch {\n /* session not in this page's list */\n }\n }\n\n const poll = async (): Promise<void> => {\n try {\n const snapshot = sessions.list.getSnapshot()\n if (snapshot.phase === 'ready') {\n // parentId also records independent user forks; only origin classifies\n // a delegated child, matching the official workspace sidebar.\n const rows = snapshot.ids.map(id => snapshot.byId[id]).filter(row => row && row.origin !== 'subagent').map(row => ({ id: row.id, title: row.displayTitle, completed: ctx.uiSession.sessionStatus.getSnapshot().get(row.id)?.completionUnread === true, running: row.running, updatedAt: row.updatedAt }))\n const focused = document.hasFocus()\n const current = rows.find(row => row.id === Object.values(snapshot.byId).find(item => (item.retainedBy?.mainView ?? 0) > 0)?.id)\n // updatedAt is the user's prompt timestamp, not the completion time.\n // Capture this observation before sending it, so a delayed request\n // cannot acknowledge a turn that finishes after the user looked.\n const viewed = focused && current && !current.running\n ? { id: current.id, at: Date.now() } : undefined\n await fetch('/dsh-notch/sidebar', { signal: controller.signal, method: 'POST', headers: { 'content-type': 'application/json', 'x-dsh-notch-client': '1' }, body: JSON.stringify({ clientId, projectionVersion: 3, focused, viewed, rows }) })\n }\n if (stopped) return\n const response = await fetch('/dsh-notch/pending-focus', { cache: 'no-store', signal: controller.signal })\n if (!response.ok) return\n const data = (await response.json()) as PendingFocusWire\n if (stopped) return\n const focus = data.focus\n if (!armed) {\n armed = true\n if (focus && typeof focus.at === 'number') lastAt = focus.at\n return\n }\n if (!focus || typeof focus.at !== 'number' || focus.at <= lastAt) return\n if (typeof focus.sessionId !== 'string' || focus.sessionId === '') return\n lastAt = focus.at\n await open(focus.sessionId)\n try {\n if (stopped) return\n window.focus()\n } catch {\n /* native helper already fronts DSH.app */\n }\n } catch {\n /* Host unreachable mid-poll; the next tick retries. */\n }\n }\n\n const tick = (): void => {\n if (stopped) return\n void poll().finally(() => {\n if (!stopped) timer = window.setTimeout(tick, POLL_MS)\n })\n }\n let timer: number | ReturnType<typeof setTimeout> | undefined\n tick()\n\n ctx.effect(() => () => {\n stopped = true\n if (timer !== undefined) window.clearTimeout(timer)\n controller.abort()\n }, 'dsh-notch: client sync')\n}\n"],"mappings":";;;;;;;EAaA,MAAa,OAAO;EACpB,MAAa,SAAS;GAAC;GAAY;GAAe;EAAW;EAO7D,MAAM,UAAU;EAEhB,SAAgB,MAAM,KAA0B;GAC9C,MAAM,WAAW,IAAI,IAAI,UAAU;GACnC,MAAM,WAAW,OAAO,WAAW;GACnC,IAAI,SAAS;GACb,IAAI,QAAQ;GACZ,IAAI,UAAU;GACd,MAAM,aAAa,IAAI,gBAAgB;GAEvC,MAAM,OAAO,OAAO,cAAqC;IACvD,IAAI;KACF,IAAI,YAAY,YAAY,SAAsB;KAClD;IACF,QAAQ,CAER;IACA,IAAI;KACF,MAAM,SAAS,QAAQ;KACvB,IAAI,SAAS;KACb,IAAI,YAAY,YAAY,SAAsB;IACpD,QAAQ,CAER;GACF;GAEA,MAAM,OAAO,YAA2B;IACtC,IAAI;KACF,MAAM,WAAW,SAAS,KAAK,YAAY;KAC3C,IAAI,SAAS,UAAU,SAAS;MAG9B,MAAM,OAAO,SAAS,IAAI,KAAI,OAAM,SAAS,KAAK,GAAG,CAAC,CAAC,QAAO,QAAO,OAAO,IAAI,WAAW,UAAU,CAAC,CAAC,KAAI,SAAQ;OAAE,IAAI,IAAI;OAAI,OAAO,IAAI;OAAc,WAAW,IAAI,UAAU,cAAc,YAAY,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,qBAAqB;OAAM,SAAS,IAAI;OAAS,WAAW,IAAI;MAAU,EAAE;MACxS,MAAM,UAAU,SAAS,SAAS;MAClC,MAAM,UAAU,KAAK,MAAK,QAAO,IAAI,OAAO,OAAO,OAAO,SAAS,IAAI,CAAC,CAAC,MAAK,UAAS,KAAK,YAAY,YAAY,KAAK,CAAC,CAAC,EAAE,EAAE;MAI/H,MAAM,SAAS,WAAW,WAAW,CAAC,QAAQ,UAC1C;OAAE,IAAI,QAAQ;OAAI,IAAI,KAAK,IAAI;MAAE,IAAI,KAAA;MACzC,MAAM,MAAM,sBAAsB;OAAE,QAAQ,WAAW;OAAQ,QAAQ;OAAQ,SAAS;QAAE,gBAAgB;QAAoB,sBAAsB;OAAI;OAAG,MAAM,KAAK,UAAU;QAAE;QAAU,mBAAmB;QAAG;QAAS;QAAQ;OAAK,CAAC;MAAE,CAAC;KAC9O;KACA,IAAI,SAAS;KACb,MAAM,WAAW,MAAM,MAAM,4BAA4B;MAAE,OAAO;MAAY,QAAQ,WAAW;KAAO,CAAC;KACzG,IAAI,CAAC,SAAS,IAAI;KAClB,MAAM,OAAQ,MAAM,SAAS,KAAK;KAClC,IAAI,SAAS;KACb,MAAM,QAAQ,KAAK;KACnB,IAAI,CAAC,OAAO;MACV,QAAQ;MACR,IAAI,SAAS,OAAO,MAAM,OAAO,UAAU,SAAS,MAAM;MAC1D;KACF;KACA,IAAI,CAAC,SAAS,OAAO,MAAM,OAAO,YAAY,MAAM,MAAM,QAAQ;KAClE,IAAI,OAAO,MAAM,cAAc,YAAY,MAAM,cAAc,IAAI;KACnE,SAAS,MAAM;KACf,MAAM,KAAK,MAAM,SAAS;KAC1B,IAAI;MACF,IAAI,SAAS;MACb,OAAO,MAAM;KACf,QAAQ,CAER;IACF,QAAQ,CAER;GACF;GAEA,MAAM,aAAmB;IACvB,IAAI,SAAS;IACb,KAAU,CAAC,CAAC,cAAc;KACxB,IAAI,CAAC,SAAS,QAAQ,OAAO,WAAW,MAAM,OAAO;IACvD,CAAC;GACH;GACA,IAAI;GACJ,KAAK;GAEL,IAAI,mBAAmB;IACrB,UAAU;IACV,IAAI,UAAU,KAAA,GAAW,OAAO,aAAa,KAAK;IAClD,WAAW,MAAM;GACnB,GAAG,wBAAwB;EAC7B"}
package/lib/index.mjs CHANGED
@@ -4,7 +4,7 @@ import { dirname, join, resolve } from "node:path";
4
4
  import { execFileSync, spawn } from "node:child_process";
5
5
  import { randomBytes, randomUUID } from "node:crypto";
6
6
  import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
7
- //#region desktop/notch-lifecycle.mjs
7
+ // desktop/notch-lifecycle.mjs
8
8
  /** PID alone is not ownership: require both executable and process start time. */
9
9
  function processIdentity(pid) {
10
10
  if (!Number.isSafeInteger(pid) || pid <= 1) return { state: "dead" };
@@ -194,8 +194,7 @@ function superviseNotch({ bin, pidPath, log, interval = 250, env = process.env,
194
194
  kill: stop
195
195
  };
196
196
  }
197
- //#endregion
198
- //#region src/session-state.ts
197
+ // src/session-state.ts
199
198
  const FAILED = /* @__PURE__ */ new Set([
200
199
  "error",
201
200
  "blocked",
@@ -239,8 +238,7 @@ function conversationOwner(session, sessions) {
239
238
  }
240
239
  return current;
241
240
  }
242
- //#endregion
243
- //#region src/store.ts
241
+ // src/store.ts
244
242
  const DIR = join(resolveDshHome(), "dsh-notch");
245
243
  const RUNTIME = join(DIR, "runtime.json");
246
244
  const SEEN = join(DIR, "seen.json");
@@ -281,8 +279,7 @@ function saveSeen(map) {
281
279
  mode: 384
282
280
  });
283
281
  }
284
- //#endregion
285
- //#region src/board.ts
282
+ // src/board.ts
286
283
  var Board = class {
287
284
  ctx;
288
285
  sidebar;
@@ -678,8 +675,7 @@ var Board = class {
678
675
  return session.id.slice(0, 8);
679
676
  }
680
677
  };
681
- //#endregion
682
- //#region src/http.ts
678
+ // src/http.ts
683
679
  const PREFIX = "/dsh-notch";
684
680
  function isLoopback(req) {
685
681
  const ip = req.socket.remoteAddress ?? "";
@@ -725,7 +721,7 @@ function send(res, status, body) {
725
721
  });
726
722
  res.end(json);
727
723
  }
728
- function readBody(req, limit = 64 * 1024) {
724
+ function readBody(req, limit = 65536) {
729
725
  return new Promise((resolve, reject) => {
730
726
  const chunks = [];
731
727
  let size = 0;
@@ -778,7 +774,7 @@ function attachHttp(ctx, board, token, origin) {
778
774
  });
779
775
  return;
780
776
  }
781
- const ok = board.syncSidebar(JSON.parse(await readBody(req, 512 * 1024)));
777
+ const ok = board.syncSidebar(JSON.parse(await readBody(req, 524288)));
782
778
  send(res, ok ? 200 : 400, { ok });
783
779
  return;
784
780
  }
@@ -912,8 +908,7 @@ function attachHttp(ctx, board, token, origin) {
912
908
  disposeRoute();
913
909
  };
914
910
  }
915
- //#endregion
916
- //#region src/dsh-notch.ts
911
+ // src/dsh-notch.ts
917
912
  const name = "dsh-notch";
918
913
  const inject = [
919
914
  "sessions",
@@ -984,7 +979,6 @@ function debounce(fn, ms) {
984
979
  timer = void 0;
985
980
  } });
986
981
  }
987
- //#endregion
988
982
  export { Config, apply, inject, name };
989
983
 
990
984
  //# sourceMappingURL=index.mjs.map
package/lib/index.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../desktop/notch-lifecycle.mjs","../src/session-state.ts","../src/store.ts","../src/board.ts","../src/http.ts","../src/dsh-notch.ts"],"sourcesContent":["import { spawn, execFileSync } from 'node:child_process'\nimport { readFileSync, mkdirSync, writeFileSync, renameSync, unlinkSync, statSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\n\n/** PID alone is not ownership: require both executable and process start time. */\nexport function processIdentity(pid) {\n if (!Number.isSafeInteger(pid) || pid <= 1) return { state: 'dead' }\n try {\n const text = execFileSync('/bin/ps', ['-p', String(pid), '-o', 'lstart=', '-o', 'comm='],\n { encoding: 'utf8', env: { ...process.env, LC_ALL: 'C' }, timeout: 1000 }).trim()\n const match = /^(.{24})\\s+(.+)$/.exec(text)\n if (match) return { state: 'alive', stamp: match[1], command: match[2] }\n } catch {}\n try { process.kill(pid, 0) } catch (error) {\n if (error.code === 'ESRCH') return { state: 'dead' }\n }\n return { state: 'unknown' }\n}\n\nexport function processLiveness(pid) {\n if (!Number.isSafeInteger(pid) || pid <= 1) return 'dead'\n try { process.kill(pid, 0); return 'alive' } catch (error) {\n return error.code === 'ESRCH' ? 'dead' : 'unknown'\n }\n}\n\n/** The desktop owns an adopted helper too. If it exits during adoption, retry. */\nexport function superviseNotch({ bin, pidPath, log, interval = 250, env = process.env,\n probe = processIdentity, alive = processLiveness, launch = spawn, signal = process.kill.bind(process) }) {\n let current, pending, timer, stopped = false\n const starts = []\n const note = text => log.write(`[notch] ${text}\\n`)\n const storedPID = () => {\n try { return Number(readFileSync(pidPath, 'utf8').trim()) } catch { return 0 }\n }\n const owned = pid => {\n const identity = probe(pid)\n return identity.state === 'alive' && resolve(identity.command) === resolve(bin)\n ? { pid, ...identity, checkedAt: Date.now() } : undefined\n }\n const schedule = () => {\n clearTimeout(timer)\n if (!stopped) { timer = setTimeout(reconcile, interval); timer.unref?.() }\n }\n function reconcile() {\n if (stopped) return\n if (pending) { schedule(); return }\n // External installers hold this short lease while replacing executable + PID.\n // A crashed installer cannot disable recovery indefinitely.\n try {\n if (Date.now() - statSync(`${pidPath}.updating`).mtimeMs < 60000) { schedule(); return }\n } catch {}\n if (current) {\n const state = alive(current.pid)\n if (state === 'unknown' || (state === 'alive' && Date.now() - current.checkedAt < 5000)) {\n schedule(); return\n }\n const live = probe(current.pid)\n if (live.state === 'unknown' || (live.state === 'alive' && live.stamp === current.stamp && live.command === current.command)) {\n current.checkedAt = Date.now()\n schedule(); return\n }\n current = undefined\n }\n const pid = storedPID()\n const identity = probe(pid)\n if (identity.state === 'unknown') { schedule(); return }\n current = owned(pid)\n if (current) { note(`adopt ${current.pid}`); schedule(); return }\n while (starts.length && Date.now() - starts[0] > 10000) starts.shift()\n if (starts.length >= 3) { note('helper repeatedly exited; retry paused'); stopped = true; return }\n starts.push(Date.now())\n const child = launch(bin, [], { stdio: ['ignore', 'pipe', 'pipe'], detached: false, env })\n pending = child\n child.stdout?.pipe(log, { end: false })\n child.stderr?.pipe(log, { end: false })\n child.once('spawn', () => {\n if (pending !== child) return\n pending = undefined\n if (stopped) { child.kill('SIGTERM'); return }\n current = owned(child.pid)\n // A successful spawn still must be attributable before touching its PID file.\n if (!current) { child.kill('SIGTERM'); schedule(); return }\n mkdirSync(dirname(pidPath), { recursive: true })\n const stage = `${pidPath}.${process.pid}.tmp`\n writeFileSync(stage, `${child.pid}\\n`)\n renameSync(stage, pidPath)\n note(`spawn ${child.pid}`)\n schedule()\n })\n child.once('error', error => { if (pending === child) pending = undefined; note(`spawn error: ${error.message}`); schedule() })\n child.once('exit', () => {\n if (pending === child) pending = undefined\n if (current?.pid === child.pid) current = undefined\n schedule()\n })\n schedule()\n }\n const stop = () => {\n stopped = true\n clearTimeout(timer)\n pending?.kill('SIGTERM')\n // A native hot replacement may have updated the file since the last check.\n const candidates = [current, owned(storedPID())].filter(Boolean)\n const seen = new Set()\n for (const item of candidates) {\n if (seen.has(item.pid)) continue\n seen.add(item.pid)\n const live = owned(item.pid)\n if (!live || live.stamp !== item.stamp) continue\n try { signal(item.pid, 'SIGTERM'); note(`stop ${item.pid}`) } catch (error) {\n if (error.code !== 'ESRCH') note(`stop failed: ${error.message}`)\n }\n if (storedPID() === item.pid) { try { unlinkSync(pidPath) } catch {} }\n }\n current = undefined\n }\n reconcile()\n return { get pid() { return current?.pid ?? pending?.pid }, stop, kill: stop }\n}\n","import type { Session } from '@deepseek-ai/dsh-session'\nimport type { NotchLastTurn } from './types.ts'\n\nexport interface FoldedSession {\n busy: boolean\n lastTurn?: NotchLastTurn\n}\n\nconst FAILED = new Set(['error', 'blocked', 'max-tokens'])\n\nexport function foldSession(session: Session): FoldedSession {\n let busy = false\n let lastTurn: NotchLastTurn | undefined\n for (const event of session.snapshotEvents()) {\n if (event.type === 'turn/start') {\n busy = true\n lastTurn = undefined\n continue\n }\n if (event.type === 'turn/end') {\n busy = false\n const kind = event.data.reason.kind\n lastTurn = { at: event.time, kind, failed: FAILED.has(kind) }\n }\n }\n return { busy, lastTurn }\n}\n\nexport function isChildSession(session: Session): boolean {\n // parentSession is also seed lineage for an independent user fork.\n // Use the same presentation discriminator as Harness's workspace sidebar.\n return session.header.origin === 'subagent'\n}\n\n/** Follow only delegation edges; user forks remain separate conversations. */\nexport function conversationOwner(session: Session, sessions: ReadonlyMap<string, Session>): Session | undefined {\n let current: Session | undefined = session\n const visited = new Set<string>()\n while (current && isChildSession(current)) {\n if (visited.has(current.id)) return undefined\n visited.add(current.id)\n current = current.header.parentSession === undefined ? undefined : sessions.get(current.header.parentSession)\n }\n return current\n}\n","import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { resolveDshHome } from '@deepseek-ai/dsh-home-paths'\nimport { join } from 'node:path'\nimport { randomBytes } from 'node:crypto'\n\nexport const DIR = join(resolveDshHome(), 'dsh-notch')\nexport const RUNTIME = join(DIR, 'runtime.json')\nconst SEEN = join(DIR, 'seen.json')\n\nexport interface RuntimeFile {\n origin: string\n token: string\n pid: number\n writtenAt: number\n}\n\nfunction ensureDir(): void {\n mkdirSync(DIR, { recursive: true, mode: 0o700 })\n}\n\nfunction readJson<T>(path: string): T | undefined {\n try {\n return JSON.parse(readFileSync(path, 'utf8')) as T\n } catch {\n return undefined\n }\n}\n\nexport function loadOrCreateToken(): string {\n const existing = readJson<RuntimeFile>(RUNTIME)\n if (existing?.token && existing.token.length >= 16) return existing.token\n return randomBytes(24).toString('hex')\n}\n\nexport function writeRuntime(file: RuntimeFile): void {\n ensureDir()\n writeFileSync(RUNTIME, `${JSON.stringify(file, null, 2)}\\n`, { encoding: 'utf8', mode: 0o600 })\n}\n\nexport function loadSeen(): Record<string, number> {\n const value = readJson<Record<string, number>>(SEEN)\n if (!value || typeof value !== 'object') return {}\n return value\n}\n\nexport function saveSeen(map: Record<string, number>): void {\n ensureDir()\n writeFileSync(SEEN, `${JSON.stringify(map)}\\n`, { encoding: 'utf8', mode: 0o600 })\n}\n","import { randomUUID } from 'node:crypto'\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { Session } from '@deepseek-ai/dsh-session'\nimport type { JobRegistry } from '@deepseek-ai/dsh-jobs'\nimport type { ApprovalOutcome, ApprovalRequest } from '@deepseek-ai/dsh-user-approval'\nimport type {\n AskUserQuestionAnswer,\n AskUserQuestionRequest,\n} from '@deepseek-ai/dsh-user-questions'\nimport { conversationOwner, foldSession, isChildSession } from './session-state.ts'\nimport { loadSeen, saveSeen } from './store.ts'\nimport type {\n ApprovalOutcomeWire,\n NotchAnswerItem,\n NotchApproval,\n NotchAsk,\n NotchFocus,\n NotchQuestion,\n NotchRow,\n NotchSnapshot,\n} from './types.ts'\n\ninterface HeldApproval {\n kind: 'approval'\n id: string\n sessionId: string\n toolName: string\n reason?: string\n resolve: (outcome: ApprovalOutcome) => void\n}\n\ninterface HeldAsk {\n kind: 'ask'\n id: string\n sessionId: string\n questions: NotchQuestion[]\n resolve: (answer: AskUserQuestionAnswer) => void\n}\n\ntype Held = HeldApproval | HeldAsk\n\ninterface SidebarRow { id: string; title: string; completed: boolean; running: boolean; updatedAt?: number }\nexport class Board {\n private sidebar: { clientId: string; at: number; focused: boolean; rows: SidebarRow[]; projectionVersion?: number } | undefined\n private readonly instance = randomUUID()\n private readonly traces: Record<string, unknown>[] = []\n private lastMirrorTrace = ''\n private lastRowTrace = ''\n\n /** Bounded, content-free diagnostics for state-source races. */\n diagnostics(): unknown {\n return { stateRevision: 4, instance: this.instance,\n sidebar: this.sidebar && { clientId: this.sidebar.clientId, at: this.sidebar.at, focused: this.sidebar.focused },\n traces: this.traces }\n }\n\n private trace(event: string, value: Record<string, unknown>): void {\n this.traces.push({ at: Date.now(), event, ...value })\n if (this.traces.length > 160) this.traces.shift()\n }\n\n syncSidebar(input: unknown): boolean {\n if (!input || typeof input !== 'object') return false\n const data = input as { clientId?: unknown; focused?: unknown; rows?: unknown; projectionVersion?: unknown; viewed?: { id?: unknown; at?: unknown } }\n if (typeof data.clientId !== 'string' || !Array.isArray(data.rows) || data.rows.length > 1000) return false\n const rows: SidebarRow[] = []\n for (const row of data.rows) {\n // Session IDs are opaque; API-created conversations need no session- prefix.\n if (!row || typeof row.id !== 'string' || row.id.length === 0 || typeof row.title !== 'string' || typeof row.completed !== 'boolean' || typeof row.running !== 'boolean') return false\n rows.push({ id: row.id, title: row.title.slice(0, 512), completed: row.completed, running: row.running,\n ...(typeof row.updatedAt === 'number' && Number.isFinite(row.updatedAt) ? { updatedAt: row.updatedAt } : {}) })\n }\n const sessions = new Map(this.ctx.sessions.list().map(session => [session.id, session]))\n // Completion is a fact, not a page-local negative flag. A page that was\n // reloaded or selected a different conversation cannot retract another\n // page's reminder. Explicit, timestamped reading acknowledgements can.\n for (const row of rows) {\n if (!row.completed || row.running) continue\n const session = sessions.get(row.id)\n if (session && (isChildSession(session) || this.isBusy(session))) continue\n const turn = session ? foldSession(session).lastTurn : undefined\n const seen = this.seen[row.id]\n const completedAt = turn?.at ?? row.updatedAt\n if (seen !== undefined && (completedAt === undefined || seen >= completedAt)) continue\n this.browserCompletions.set(row.id, row)\n }\n const viewed = data.viewed\n if (data.projectionVersion === 3 && data.focused === true && viewed\n && typeof viewed.id === 'string' && typeof viewed.at === 'number' && Number.isFinite(viewed.at)\n && rows.some(row => row.id === viewed.id && !row.running)) {\n const session = sessions.get(viewed.id)\n if (!session || (!isChildSession(session) && !this.conversationBusy(session, sessions))) this.readThrough(viewed.id, Math.min(viewed.at, Date.now()))\n }\n // Legacy clients can acknowledge only a reminder they themselves showed.\n if (data.projectionVersion !== 3 && data.focused === true && this.sidebar?.clientId === data.clientId) {\n for (const previous of this.sidebar.rows) {\n if (!previous.completed || rows.some(row => row.id === previous.id && row.completed)) continue\n const session = sessions.get(previous.id)\n const at = session ? foldSession(session).lastTurn?.at : Date.now()\n if (at !== undefined && (!session || !this.conversationBusy(session, sessions))) this.readThrough(previous.id, at)\n }\n }\n // A background browser cannot overwrite the last foreground page's state.\n if (this.sidebar && this.sidebar.clientId !== data.clientId && data.focused !== true && Date.now() - this.sidebar.at < 5000) { this.bump(); return true }\n this.sidebar = { clientId: data.clientId, at: Date.now(), focused: data.focused === true, rows,\n ...(data.projectionVersion === 2 || data.projectionVersion === 3 ? { projectionVersion: data.projectionVersion } : {}) }\n const mirrorTrace = { clientId: data.clientId, focused: data.focused === true,\n rows: rows.map(({ id, running, completed }) => ({ id, running, completed })) }\n const signature = JSON.stringify(mirrorTrace)\n if (signature !== this.lastMirrorTrace) { this.lastMirrorTrace = signature; this.trace('sidebar', mirrorTrace) }\n this.bump()\n return true\n }\n\n private sidebarRows(): SidebarRow[] | undefined {\n return this.sidebar && Date.now() - this.sidebar.at < 5000 ? this.sidebar.rows : undefined\n }\n\n private readonly pending = new Map<string, Held>()\n private readonly seen = loadSeen()\n private running = new Set<string>()\n private readonly pendingUnread = new Set<string>()\n private readonly browserCompletions = new Map<string, SidebarRow>()\n private readonly listeners = new Set<() => void>()\n private focus: NotchFocus | null = null\n\n constructor(private readonly ctx: Context) {}\n\n onChange(fn: () => void): () => void {\n this.listeners.add(fn)\n return () => { this.listeners.delete(fn) }\n }\n\n notify(): void {\n this.bump()\n }\n\n private bump(): void {\n for (const fn of this.listeners) {\n try { fn() } catch (error) { this.ctx.logger.warn('dsh-notch: subscriber failed: %s', String(error)) }\n }\n }\n\n snapshot(origin: string): NotchSnapshot {\n const rows: NotchRow[] = []\n const sessions = this.ctx.sessions.list()\n const byId = new Map<string, Session>(sessions.map(session => [session.id, session]))\n const groups = new Map<Session, Session[]>()\n for (const session of sessions) {\n const owner = conversationOwner(session, byId)\n if (!owner) {\n // Broken/unloaded lineage must not invent a task or lose a user question.\n const ids = new Set([session.id])\n const approval = this.heldApproval(ids)\n const ask = this.heldAsk(ids)\n if (approval || ask) rows.push({\n id: session.id, title: this.titleOf(session), child: true,\n busy: false, unread: false,\n ...(approval ? { approval } : {}), ...(ask ? { ask } : {}),\n })\n continue\n }\n const members = groups.get(owner) ?? []\n members.push(session)\n groups.set(owner, members)\n }\n for (const [owner, members] of groups) {\n const row = this.rowFor(owner, members)\n if (row) rows.push(row)\n }\n const freshSidebar = this.sidebarRows()\n const coldRows = new Map(this.browserCompletions)\n for (const source of freshSidebar ?? []) {\n if (source.running) coldRows.set(source.id, source)\n }\n for (const source of coldRows.values()) {\n // The Host owns loaded session classification. A stale mirror must not\n // resurrect a filtered child as child:false or override a settled root.\n if (byId.has(source.id) || (!source.completed && !source.running)) continue\n // Heartbeat freshness is a liveness lease, not an unread-state change.\n // Retain completed cold rows until acknowledged; expire running-only\n // claims when their source stops reporting.\n if (source.running && !freshSidebar) continue\n const seen = this.seen[source.id]\n if (source.completed && seen !== undefined && (source.updatedAt === undefined || seen >= source.updatedAt)) continue\n rows.push({ id: source.id, title: source.title, child: false, busy: source.running, unread: source.completed })\n }\n rows.sort((a, b) => Number(Boolean(b.approval || b.ask)) - Number(Boolean(a.approval || a.ask))\n || Number(b.busy) - Number(a.busy)\n || Number(b.unread) - Number(a.unread)\n || (b.lastTurn?.at ?? 0) - (a.lastTurn?.at ?? 0))\n this.running = new Set(\n sessions.filter((session) => this.isBusy(session)).map((session) => session.id),\n )\n const stateRows = rows.map(row => ({ id: row.id, busy: row.busy, unread: row.unread,\n turnAt: row.lastTurn?.at, action: Boolean(row.ask || row.approval) }))\n const rowSignature = JSON.stringify(stateRows)\n if (rowSignature !== this.lastRowTrace) {\n this.lastRowTrace = rowSignature\n this.trace('snapshot', { rows: stateRows, clientId: this.sidebar?.clientId,\n mirrorAt: this.sidebar?.at, running: [...this.running], pendingUnread: [...this.pendingUnread] })\n }\n return { ok: true, stateRevision: 4, generatedAt: Date.now(), origin, rows,\n sidebarSyncedAt: freshSidebar ? this.sidebar?.at : undefined,\n sidebarProjectionVersion: freshSidebar ? this.sidebar?.projectionVersion : undefined }\n }\n\n markSeen(sessionId: string): void {\n this.pendingUnread.delete(sessionId)\n this.browserCompletions.delete(sessionId)\n this.seen[sessionId] = Date.now()\n saveSeen(this.seen)\n this.bump()\n }\n\n markAllSeen(): void {\n this.pendingUnread.clear()\n const now = Date.now()\n for (const id of this.browserCompletions.keys()) this.seen[id] = now\n this.browserCompletions.clear()\n for (const session of this.ctx.sessions.list()) {\n this.seen[session.id] = now\n }\n saveSeen(this.seen)\n this.bump()\n }\n\n private readThrough(sessionId: string, at: number): void {\n const seen = this.seen[sessionId]\n if ((seen ?? -Infinity) >= at) return\n const session = this.ctx.sessions.list().find(item => item.id === sessionId)\n const turn = session && foldSession(session).lastTurn\n const completedAt = turn?.at ?? this.browserCompletions.get(sessionId)?.updatedAt\n // An idle focused page reports every 400 ms. Persist once per result,\n // not on every heartbeat for as long as that conversation stays open.\n if (seen !== undefined && (completedAt === undefined || seen >= completedAt)) return\n this.seen[sessionId] = at\n if (completedAt === undefined || completedAt <= at) {\n this.pendingUnread.delete(sessionId)\n this.browserCompletions.delete(sessionId)\n }\n saveSeen(this.seen)\n this.trace('read', { sessionId, through: at })\n }\n\n noteTurnEnd(session: Session, at = foldSession(session).lastTurn?.at): void {\n if (isChildSession(session)) return\n if (at !== undefined && (this.seen[session.id] ?? -Infinity) < at) this.pendingUnread.add(session.id)\n }\n\n /**\n * Record a \"show this session in a DSH UI\" wish from the native helper.\n * Returns false for unknown sessions. The wish expires after 60s; every\n * open DSH page consumes it idempotently, so no per-client cursor is kept.\n */\n requestFocus(sessionId: string): boolean {\n const known = this.ctx.sessions.list().some((session) => session.id === sessionId)\n const mirrored = this.browserCompletions.has(sessionId) || this.sidebarRows()?.some(row => row.id === sessionId)\n if (!known && !mirrored) return false\n this.focus = { sessionId, at: Date.now() }\n this.bump()\n return true\n }\n\n peekFocus(): NotchFocus | null {\n if (this.focus === null) return null\n if (Date.now() - this.focus.at > 60_000) {\n this.focus = null\n return null\n }\n return this.focus\n }\n\n decideApproval(id: string, outcome: ApprovalOutcomeWire): boolean {\n const held = this.pending.get(id)\n if (!held || held.kind !== 'approval') return false\n this.pending.delete(id)\n this.seen[held.sessionId] = Date.now()\n saveSeen(this.seen)\n held.resolve(outcome)\n this.bump()\n return true\n }\n\n answerAsk(id: string, answers: NotchAnswerItem[]): boolean {\n const held = this.pending.get(id)\n if (!held || held.kind !== 'ask') return false\n this.pending.delete(id)\n this.seen[held.sessionId] = Date.now()\n saveSeen(this.seen)\n held.resolve({ answers })\n this.bump()\n return true\n }\n\n holdApproval(request: ApprovalRequest, next: () => Promise<ApprovalOutcome>): Promise<ApprovalOutcome> {\n const sessionId = String(request.agent.id)\n const id = randomUUID()\n const fromNotch = new Promise<ApprovalOutcome>((resolve) => {\n const held: HeldApproval = {\n kind: 'approval',\n id,\n sessionId,\n toolName: request.toolName,\n resolve,\n }\n if (request.reason) held.reason = request.reason\n this.pending.set(id, held)\n this.bump()\n request.signal?.addEventListener('abort', () => {\n if (!this.pending.delete(id)) return\n this.bump()\n }, { once: true })\n })\n return Promise.race([fromNotch, next()]).finally(() => {\n if (this.pending.delete(id)) this.bump()\n })\n }\n\n holdAsk(request: AskUserQuestionRequest, next: () => Promise<AskUserQuestionAnswer>): Promise<AskUserQuestionAnswer> {\n const sessionId = request.agent ? String(request.agent.id) : ''\n if (!sessionId) return next()\n const originalSignal = request.signal\n const downstream = new AbortController()\n request.signal = originalSignal\n ? AbortSignal.any([originalSignal, downstream.signal])\n : downstream.signal\n const id = randomUUID()\n const questions: NotchQuestion[] = request.questions.map((item) => ({\n id: item.id,\n question: item.question,\n ...(item.detail === undefined ? {} : { detail: item.detail }),\n ...(item.header === undefined ? {} : { header: item.header }),\n ...(item.options === undefined ? {} : { options: item.options }),\n ...(item.multiSelect === undefined ? {} : { multiSelect: item.multiSelect }),\n ...(item.intent === undefined ? {} : { intent: item.intent }),\n }))\n const fromNotch = new Promise<AskUserQuestionAnswer>((resolve) => {\n this.pending.set(id, { kind: 'ask', id, sessionId, questions, resolve })\n this.bump()\n request.signal?.addEventListener('abort', () => {\n if (!this.pending.delete(id)) return\n this.bump()\n }, { once: true })\n })\n return Promise.race([fromNotch, Promise.resolve().then(next)]).finally(() => {\n // The losing Web answerer must end too, so its pending interaction unmounts.\n // Cancel only this delegated wait, never the owning agent's signal.\n downstream.abort(new Error('Question settled through another answerer'))\n if (originalSignal === undefined) delete request.signal\n else request.signal = originalSignal\n if (this.pending.delete(id)) this.bump()\n })\n }\n\n private isBusy(session: Session): boolean {\n return this.ctx.agents.get(session.id)?.status === 'running'\n }\n\n private conversationBusy(owner: Session, sessions: ReadonlyMap<string, Session>): boolean {\n for (const member of sessions.values()) {\n if (conversationOwner(member, sessions)?.id === owner.id\n && (this.isBusy(member) || this.hasSubagentJob(member))) return true\n }\n return false\n }\n\n private hasSubagentJob(session: Session): boolean {\n const agent = this.ctx.agents.get(session.id)\n if (!agent) return false\n // Remote backends have no local Agent/Session. The official background\n // job registry retains their owner through running and stopping, including\n // jobs that were already active when Notch was hot-loaded.\n const jobs = this.ctx.get('jobs') as Pick<JobRegistry, 'list'> | undefined\n return jobs?.list(agent.id).some(job => job.owner === session.id\n && job.kind === 'subagent'\n && (job.status === 'running' || job.status === 'stopping')) ?? false\n }\n\n private rowFor(session: Session, members: Session[]): NotchRow | undefined {\n const folded = foldSession(session)\n const child = isChildSession(session)\n const lastSeen = this.seen[session.id]\n const busy = members.some(member => this.isBusy(member) || this.hasSubagentJob(member))\n // Completion belongs to the owner's turn, never an individual worker.\n if (folded.busy) {\n this.pendingUnread.delete(session.id)\n } else if (this.running.has(session.id) && folded.lastTurn) {\n this.pendingUnread.add(session.id)\n }\n const dismissed = lastSeen !== undefined && (folded.lastTurn === undefined || lastSeen >= folded.lastTurn.at)\n const unread = !dismissed && !busy\n && (this.pendingUnread.has(session.id) || this.browserCompletions.has(session.id))\n const memberIds = new Set(members.map(member => member.id))\n // Keep the original request id/resolver so answering the root's yellow\n // lamp settles the correct child. Further child questions queue here.\n const approval = this.heldApproval(memberIds)\n const ask = this.heldAsk(memberIds)\n if (!busy && !unread && !approval && !ask) return undefined\n const title = this.titleOf(session)\n const row: NotchRow = {\n id: session.id,\n title,\n child,\n busy,\n unread,\n }\n if (folded.lastTurn && !busy) row.lastTurn = folded.lastTurn\n if (approval) row.approval = approval\n if (ask) row.ask = ask\n return row\n }\n\n private heldApproval(sessionIds: ReadonlySet<string>): NotchApproval | undefined {\n for (const held of this.pending.values()) {\n if (held.kind === 'approval' && sessionIds.has(held.sessionId)) {\n return {\n id: held.id,\n toolName: held.toolName,\n ...(held.reason === undefined ? {} : { reason: held.reason }),\n }\n }\n }\n return undefined\n }\n\n private heldAsk(sessionIds: ReadonlySet<string>): NotchAsk | undefined {\n for (const held of this.pending.values()) {\n if (held.kind === 'ask' && sessionIds.has(held.sessionId)) {\n return { id: held.id, questions: held.questions }\n }\n }\n return undefined\n }\n\n private titleOf(session: Session): string {\n const service = this.ctx.get('sessionTitle') as { get(target: Session): { title: string } | undefined } | undefined\n const title = service?.get(session)?.title?.trim()\n if (title) return title\n return session.id.slice(0, 8)\n }\n}\n","import { randomUUID } from 'node:crypto'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { Board } from './board.ts'\nimport type { ApprovalOutcomeWire, NotchAnswerItem } from './types.ts'\n\nconst PREFIX = '/dsh-notch'\n\nexport function isLoopback(req: IncomingMessage): boolean {\n const ip = req.socket.remoteAddress ?? ''\n return ip === '127.0.0.1' || ip === '::1' || ip === ':ffff:127.0.0.1' || ip === '::ffff:127.0.0.1'\n}\n\nexport function authorized(req: IncomingMessage, token: string): boolean {\n const header = req.headers.authorization\n if (header === `Bearer ${token}`) return true\n try {\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\n return url.searchParams.get('token') === token\n } catch {\n return false\n }\n}\n\n/**\n * Browser-side trust for pages served by this same Host: loopback remote,\n * never cross-site, and a matching Origin when one is present. Same rule the\n * shipped workspace file panel uses — no secret ever lands in page JS.\n */\nexport function browserTrusted(req: IncomingMessage): boolean {\n if (!isLoopback(req)) return false\n if (req.headers['sec-fetch-site'] === 'cross-site') return false\n const host = req.headers.host\n if (host === undefined) return false\n const origin = req.headers.origin\n if (origin === undefined) return true\n try {\n return new URL(origin).host === new URL(`http://${host}`).host\n } catch {\n return false\n }\n}\n\n/** Accept same-origin Web writes and requests forwarded by the Desktop shell. */\nexport function sidebarTrusted(req: IncomingMessage, origin: string): boolean {\n if (!browserTrusted(req) || !req.headers['content-type']?.startsWith('application/json')) return false\n if (req.headers.origin !== undefined) return true\n // Desktop validates dsh-app://app, then removes Origin and fetch metadata.\n // A script-only header still prevents a foreign page from submitting a form.\n return req.headers['x-dsh-notch-client'] === '1'\n && req.headers['sec-fetch-site'] === undefined\n && req.headers.host === new URL(origin).host\n}\n\nfunction send(res: ServerResponse, status: number, body: unknown): void {\n const json = JSON.stringify(body)\n res.writeHead(status, {\n 'content-type': 'application/json; charset=utf-8',\n 'cache-control': 'no-store',\n })\n res.end(json)\n}\n\nfunction readBody(req: IncomingMessage, limit = 64 * 1024): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = []\n let size = 0\n req.on('data', (chunk: Buffer) => {\n size += chunk.length\n if (size > limit) {\n reject(new Error('body too large'))\n req.destroy()\n return\n }\n chunks.push(chunk)\n })\n req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')))\n req.on('error', reject)\n })\n}\n\nexport function attachHttp(ctx: Context, board: Board, token: string, origin: string): () => void {\n const streams = new Map<string, ServerResponse>()\n const push = () => {\n const payload = `data: ${JSON.stringify(board.snapshot(origin))}\\n\\n`\n for (const [id, res] of streams) {\n try {\n res.write(payload)\n } catch {\n streams.delete(id)\n }\n }\n }\n const stopListen = board.onChange(push)\n\n const handler = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\n const path = url.pathname\n const method = req.method ?? 'GET'\n\n // Browser pages served by this same Host consume focus wishes through\n // same-origin trust (no secret in page JS); everything else keeps the\n // notch Bearer token.\n if (method === 'GET' && path === `${PREFIX}/pending-focus`) {\n if (!browserTrusted(req)) {\n send(res, 403, { ok: false, error: 'forbidden' })\n return\n }\n send(res, 200, { ok: true, focus: board.peekFocus() })\n return\n }\n\n if (method === 'POST' && path === `${PREFIX}/sidebar`) {\n if (!sidebarTrusted(req, origin)) {\n send(res, 403, { ok: false, error: 'forbidden' })\n return\n }\n const ok = board.syncSidebar(JSON.parse(await readBody(req, 512 * 1024)))\n send(res, ok ? 200 : 400, { ok })\n return\n }\n\n if (!isLoopback(req) || !authorized(req, token)) {\n send(res, 403, { ok: false, error: 'forbidden' })\n return\n }\n\n if (method === 'GET' && path === `${PREFIX}/status`) {\n send(res, 200, board.snapshot(origin))\n return\n }\n\n if (method === 'GET' && path === `${PREFIX}/diagnostics`) {\n send(res, 200, board.diagnostics())\n return\n }\n\n if (method === 'GET' && path === `${PREFIX}/events`) {\n const id = randomUUID()\n res.writeHead(200, {\n 'content-type': 'text/event-stream',\n 'cache-control': 'no-cache',\n connection: 'keep-alive',\n })\n streams.set(id, res)\n res.write(`data: ${JSON.stringify(board.snapshot(origin))}\\n\\n`)\n req.on('close', () => { streams.delete(id) })\n return\n }\n\n if (method === 'POST' && path === `${PREFIX}/seen`) {\n const body = JSON.parse(await readBody(req)) as { sessionId?: string; all?: boolean }\n if (body.all) {\n board.markAllSeen()\n send(res, 200, { ok: true })\n return\n }\n if (!body.sessionId) {\n send(res, 400, { ok: false, error: 'sessionId required' })\n return\n }\n board.markSeen(body.sessionId)\n send(res, 200, { ok: true })\n return\n }\n\n if (method === 'POST' && path === `${PREFIX}/approve`) {\n const body = JSON.parse(await readBody(req)) as { id?: string; outcome?: ApprovalOutcomeWire }\n if (!body.id || (body.outcome !== 'allowed-once' && body.outcome !== 'rejected')) {\n send(res, 400, { ok: false, error: 'id and outcome required' })\n return\n }\n if (!board.decideApproval(body.id, body.outcome)) {\n send(res, 404, { ok: false, error: 'not pending' })\n return\n }\n send(res, 200, { ok: true })\n return\n }\n\n if (method === 'POST' && path === `${PREFIX}/answer`) {\n const body = JSON.parse(await readBody(req)) as { id?: string; answers?: NotchAnswerItem[] }\n if (!body.id || !Array.isArray(body.answers)) {\n send(res, 400, { ok: false, error: 'id and answers required' })\n return\n }\n if (!board.answerAsk(body.id, body.answers)) {\n send(res, 404, { ok: false, error: 'not pending' })\n return\n }\n send(res, 200, { ok: true })\n return\n }\n\n if (method === 'POST' && path === `${PREFIX}/focus`) {\n const body = JSON.parse(await readBody(req)) as { sessionId?: string }\n if (!body.sessionId) {\n send(res, 400, { ok: false, error: 'sessionId required' })\n return\n }\n if (!board.requestFocus(body.sessionId)) {\n send(res, 404, { ok: false, error: 'unknown session' })\n return\n }\n send(res, 200, { ok: true })\n return\n }\n\n send(res, 404, { ok: false, error: 'not found' })\n }\n\n const disposeRoute = ctx.webServer.register({\n kind: 'prefix',\n path: PREFIX,\n handler: (req, res) => {\n void handler(req, res).catch((error: unknown) => {\n if (!res.headersSent) send(res, 400, { ok: false, error: String(error) })\n })\n },\n })\n\n return () => {\n stopListen()\n for (const res of streams.values()) {\n try { res.end() } catch { /* already closed */ }\n }\n streams.clear()\n disposeRoute()\n }\n}\n","import type { Context } from '@deepseek-ai/cordis'\nimport Schema from '@deepseek-ai/schemastery'\nimport { createWriteStream, existsSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { superviseNotch } from '../desktop/notch-lifecycle.mjs'\nimport type { ApprovalOutcome, ApprovalRequest } from '@deepseek-ai/dsh-user-approval'\nimport type { AskUserQuestionAnswer, AskUserQuestionRequest } from '@deepseek-ai/dsh-user-questions'\nimport type {} from '@deepseek-ai/dsh-host-webserver'\nimport type {} from '@deepseek-ai/dsh-session'\nimport { Board } from './board.ts'\nimport { attachHttp } from './http.ts'\nimport { DIR, RUNTIME, loadOrCreateToken, writeRuntime } from './store.ts'\n\nexport const name = 'dsh-notch'\nexport const inject = ['sessions', 'webServer', 'approval', 'userQuestions', 'agents']\nexport const Config = Schema.object({\n helperPath: Schema.string().default('').description('Optional installed native Notch executable; managed for this Host lifetime.'),\n})\n\nexport function apply(ctx: Context, config: { helperPath?: string } = {}) {\n console.log('[my-plugins/dsh-notch] loaded')\n const board = new Board(ctx)\n const token = loadOrCreateToken()\n const origin = `http://${ctx.webServer.host}:${String(ctx.webServer.port)}`\n writeRuntime({\n origin,\n token,\n pid: process.pid,\n writtenAt: Date.now(),\n })\n if (config.helperPath) {\n if (!existsSync(config.helperPath)) throw new Error('Configured Notch helper does not exist')\n ctx.effect(() => {\n const log = createWriteStream(join(DIR, 'helper.log'), { flags: 'a', mode: 0o600 })\n const helper = superviseNotch({ bin: config.helperPath, pidPath: join(DIR, 'helper.pid'), log,\n env: { ...process.env, DSH_NOTCH_RUNTIME_FILE: RUNTIME } })\n return () => { helper.stop(); log.end() }\n }, 'dsh-notch: native helper')\n }\n\n ctx.effect(() => attachHttp(ctx, board, token, origin), 'dsh-notch: http')\n\n const notify = debounce(() => board.notify(), 80)\n ctx.effect(() => notify.dispose, 'dsh-notch: notification timer')\n ctx.effect(() => ctx.on('session/created', notify), 'dsh-notch: created')\n ctx.effect(() => ctx.on('session/disposed', notify), 'dsh-notch: disposed')\n ctx.effect(() => ctx.on('session/event', (session, event) => {\n const type = String(event.type)\n if (type === 'turn/end') board.noteTurnEnd(session, event.time)\n if (type === 'turn/start' || type === 'turn/end' || type === 'session/title') {\n notify()\n }\n // When a turn starts or user sends a message in DSH, mark session as read\n if (type === 'turn/start' || (type === 'user/message' && (event.data as Record<string, unknown>)?.source === 'user')) {\n board.markSeen(session.id)\n }\n }), 'dsh-notch: events')\n ctx.effect(() => ctx.on('agent/status', notify), 'dsh-notch: agent-status')\n\n\n // Only intercept user-questions/request (AskUserQuestion).\n // Do NOT intercept internal tool approval/request which causes false alarms\n // when background agent tools run sandbox checks.\n ctx.on('user-questions/request', (\n request: AskUserQuestionRequest,\n next: () => Promise<AskUserQuestionAnswer>,\n ) => board.holdAsk(request, next), { prepend: true })\n}\n\nfunction debounce(fn: () => void, ms: number): (() => void) & { dispose: () => void } {\n let timer: ReturnType<typeof setTimeout> | undefined\n const notify = () => {\n if (timer) clearTimeout(timer)\n timer = setTimeout(() => { timer = undefined; fn() }, ms)\n }\n return Object.assign(notify, { dispose: () => { if (timer) clearTimeout(timer); timer = undefined } })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../desktop/notch-lifecycle.mjs","../src/session-state.ts","../src/store.ts","../src/board.ts","../src/http.ts","../src/dsh-notch.ts"],"sourcesContent":["import { spawn, execFileSync } from 'node:child_process'\nimport { readFileSync, mkdirSync, writeFileSync, renameSync, unlinkSync, statSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\n\n/** PID alone is not ownership: require both executable and process start time. */\nexport function processIdentity(pid) {\n if (!Number.isSafeInteger(pid) || pid <= 1) return { state: 'dead' }\n try {\n const text = execFileSync('/bin/ps', ['-p', String(pid), '-o', 'lstart=', '-o', 'comm='],\n { encoding: 'utf8', env: { ...process.env, LC_ALL: 'C' }, timeout: 1000 }).trim()\n const match = /^(.{24})\\s+(.+)$/.exec(text)\n if (match) return { state: 'alive', stamp: match[1], command: match[2] }\n } catch {}\n try { process.kill(pid, 0) } catch (error) {\n if (error.code === 'ESRCH') return { state: 'dead' }\n }\n return { state: 'unknown' }\n}\n\nexport function processLiveness(pid) {\n if (!Number.isSafeInteger(pid) || pid <= 1) return 'dead'\n try { process.kill(pid, 0); return 'alive' } catch (error) {\n return error.code === 'ESRCH' ? 'dead' : 'unknown'\n }\n}\n\n/** The desktop owns an adopted helper too. If it exits during adoption, retry. */\nexport function superviseNotch({ bin, pidPath, log, interval = 250, env = process.env,\n probe = processIdentity, alive = processLiveness, launch = spawn, signal = process.kill.bind(process) }) {\n let current, pending, timer, stopped = false\n const starts = []\n const note = text => log.write(`[notch] ${text}\\n`)\n const storedPID = () => {\n try { return Number(readFileSync(pidPath, 'utf8').trim()) } catch { return 0 }\n }\n const owned = pid => {\n const identity = probe(pid)\n return identity.state === 'alive' && resolve(identity.command) === resolve(bin)\n ? { pid, ...identity, checkedAt: Date.now() } : undefined\n }\n const schedule = () => {\n clearTimeout(timer)\n if (!stopped) { timer = setTimeout(reconcile, interval); timer.unref?.() }\n }\n function reconcile() {\n if (stopped) return\n if (pending) { schedule(); return }\n // External installers hold this short lease while replacing executable + PID.\n // A crashed installer cannot disable recovery indefinitely.\n try {\n if (Date.now() - statSync(`${pidPath}.updating`).mtimeMs < 60000) { schedule(); return }\n } catch {}\n if (current) {\n const state = alive(current.pid)\n if (state === 'unknown' || (state === 'alive' && Date.now() - current.checkedAt < 5000)) {\n schedule(); return\n }\n const live = probe(current.pid)\n if (live.state === 'unknown' || (live.state === 'alive' && live.stamp === current.stamp && live.command === current.command)) {\n current.checkedAt = Date.now()\n schedule(); return\n }\n current = undefined\n }\n const pid = storedPID()\n const identity = probe(pid)\n if (identity.state === 'unknown') { schedule(); return }\n current = owned(pid)\n if (current) { note(`adopt ${current.pid}`); schedule(); return }\n while (starts.length && Date.now() - starts[0] > 10000) starts.shift()\n if (starts.length >= 3) { note('helper repeatedly exited; retry paused'); stopped = true; return }\n starts.push(Date.now())\n const child = launch(bin, [], { stdio: ['ignore', 'pipe', 'pipe'], detached: false, env })\n pending = child\n child.stdout?.pipe(log, { end: false })\n child.stderr?.pipe(log, { end: false })\n child.once('spawn', () => {\n if (pending !== child) return\n pending = undefined\n if (stopped) { child.kill('SIGTERM'); return }\n current = owned(child.pid)\n // A successful spawn still must be attributable before touching its PID file.\n if (!current) { child.kill('SIGTERM'); schedule(); return }\n mkdirSync(dirname(pidPath), { recursive: true })\n const stage = `${pidPath}.${process.pid}.tmp`\n writeFileSync(stage, `${child.pid}\\n`)\n renameSync(stage, pidPath)\n note(`spawn ${child.pid}`)\n schedule()\n })\n child.once('error', error => { if (pending === child) pending = undefined; note(`spawn error: ${error.message}`); schedule() })\n child.once('exit', () => {\n if (pending === child) pending = undefined\n if (current?.pid === child.pid) current = undefined\n schedule()\n })\n schedule()\n }\n const stop = () => {\n stopped = true\n clearTimeout(timer)\n pending?.kill('SIGTERM')\n // A native hot replacement may have updated the file since the last check.\n const candidates = [current, owned(storedPID())].filter(Boolean)\n const seen = new Set()\n for (const item of candidates) {\n if (seen.has(item.pid)) continue\n seen.add(item.pid)\n const live = owned(item.pid)\n if (!live || live.stamp !== item.stamp) continue\n try { signal(item.pid, 'SIGTERM'); note(`stop ${item.pid}`) } catch (error) {\n if (error.code !== 'ESRCH') note(`stop failed: ${error.message}`)\n }\n if (storedPID() === item.pid) { try { unlinkSync(pidPath) } catch {} }\n }\n current = undefined\n }\n reconcile()\n return { get pid() { return current?.pid ?? pending?.pid }, stop, kill: stop }\n}\n","import type { Session } from '@deepseek-ai/dsh-session'\nimport type { NotchLastTurn } from './types.ts'\n\nexport interface FoldedSession {\n busy: boolean\n lastTurn?: NotchLastTurn\n}\n\nconst FAILED = new Set(['error', 'blocked', 'max-tokens'])\n\nexport function foldSession(session: Session): FoldedSession {\n let busy = false\n let lastTurn: NotchLastTurn | undefined\n for (const event of session.snapshotEvents()) {\n if (event.type === 'turn/start') {\n busy = true\n lastTurn = undefined\n continue\n }\n if (event.type === 'turn/end') {\n busy = false\n const kind = event.data.reason.kind\n lastTurn = { at: event.time, kind, failed: FAILED.has(kind) }\n }\n }\n return { busy, lastTurn }\n}\n\nexport function isChildSession(session: Session): boolean {\n // parentSession is also seed lineage for an independent user fork.\n // Use the same presentation discriminator as Harness's workspace sidebar.\n return session.header.origin === 'subagent'\n}\n\n/** Follow only delegation edges; user forks remain separate conversations. */\nexport function conversationOwner(session: Session, sessions: ReadonlyMap<string, Session>): Session | undefined {\n let current: Session | undefined = session\n const visited = new Set<string>()\n while (current && isChildSession(current)) {\n if (visited.has(current.id)) return undefined\n visited.add(current.id)\n current = current.header.parentSession === undefined ? undefined : sessions.get(current.header.parentSession)\n }\n return current\n}\n","import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { resolveDshHome } from '@deepseek-ai/dsh-home-paths'\nimport { join } from 'node:path'\nimport { randomBytes } from 'node:crypto'\n\nexport const DIR = join(resolveDshHome(), 'dsh-notch')\nexport const RUNTIME = join(DIR, 'runtime.json')\nconst SEEN = join(DIR, 'seen.json')\n\nexport interface RuntimeFile {\n origin: string\n token: string\n pid: number\n writtenAt: number\n}\n\nfunction ensureDir(): void {\n mkdirSync(DIR, { recursive: true, mode: 0o700 })\n}\n\nfunction readJson<T>(path: string): T | undefined {\n try {\n return JSON.parse(readFileSync(path, 'utf8')) as T\n } catch {\n return undefined\n }\n}\n\nexport function loadOrCreateToken(): string {\n const existing = readJson<RuntimeFile>(RUNTIME)\n if (existing?.token && existing.token.length >= 16) return existing.token\n return randomBytes(24).toString('hex')\n}\n\nexport function writeRuntime(file: RuntimeFile): void {\n ensureDir()\n writeFileSync(RUNTIME, `${JSON.stringify(file, null, 2)}\\n`, { encoding: 'utf8', mode: 0o600 })\n}\n\nexport function loadSeen(): Record<string, number> {\n const value = readJson<Record<string, number>>(SEEN)\n if (!value || typeof value !== 'object') return {}\n return value\n}\n\nexport function saveSeen(map: Record<string, number>): void {\n ensureDir()\n writeFileSync(SEEN, `${JSON.stringify(map)}\\n`, { encoding: 'utf8', mode: 0o600 })\n}\n","import { randomUUID } from 'node:crypto'\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { Session } from '@deepseek-ai/dsh-session'\nimport type { JobRegistry } from '@deepseek-ai/dsh-jobs'\nimport type { ApprovalOutcome, ApprovalRequest } from '@deepseek-ai/dsh-user-approval'\nimport type {\n AskUserQuestionAnswer,\n AskUserQuestionRequest,\n} from '@deepseek-ai/dsh-user-questions'\nimport { conversationOwner, foldSession, isChildSession } from './session-state.ts'\nimport { loadSeen, saveSeen } from './store.ts'\nimport type {\n ApprovalOutcomeWire,\n NotchAnswerItem,\n NotchApproval,\n NotchAsk,\n NotchFocus,\n NotchQuestion,\n NotchRow,\n NotchSnapshot,\n} from './types.ts'\n\ninterface HeldApproval {\n kind: 'approval'\n id: string\n sessionId: string\n toolName: string\n reason?: string\n resolve: (outcome: ApprovalOutcome) => void\n}\n\ninterface HeldAsk {\n kind: 'ask'\n id: string\n sessionId: string\n questions: NotchQuestion[]\n resolve: (answer: AskUserQuestionAnswer) => void\n}\n\ntype Held = HeldApproval | HeldAsk\n\ninterface SidebarRow { id: string; title: string; completed: boolean; running: boolean; updatedAt?: number }\nexport class Board {\n private sidebar: { clientId: string; at: number; focused: boolean; rows: SidebarRow[]; projectionVersion?: number } | undefined\n private readonly instance = randomUUID()\n private readonly traces: Record<string, unknown>[] = []\n private lastMirrorTrace = ''\n private lastRowTrace = ''\n\n /** Bounded, content-free diagnostics for state-source races. */\n diagnostics(): unknown {\n return { stateRevision: 4, instance: this.instance,\n sidebar: this.sidebar && { clientId: this.sidebar.clientId, at: this.sidebar.at, focused: this.sidebar.focused },\n traces: this.traces }\n }\n\n private trace(event: string, value: Record<string, unknown>): void {\n this.traces.push({ at: Date.now(), event, ...value })\n if (this.traces.length > 160) this.traces.shift()\n }\n\n syncSidebar(input: unknown): boolean {\n if (!input || typeof input !== 'object') return false\n const data = input as { clientId?: unknown; focused?: unknown; rows?: unknown; projectionVersion?: unknown; viewed?: { id?: unknown; at?: unknown } }\n if (typeof data.clientId !== 'string' || !Array.isArray(data.rows) || data.rows.length > 1000) return false\n const rows: SidebarRow[] = []\n for (const row of data.rows) {\n // Session IDs are opaque; API-created conversations need no session- prefix.\n if (!row || typeof row.id !== 'string' || row.id.length === 0 || typeof row.title !== 'string' || typeof row.completed !== 'boolean' || typeof row.running !== 'boolean') return false\n rows.push({ id: row.id, title: row.title.slice(0, 512), completed: row.completed, running: row.running,\n ...(typeof row.updatedAt === 'number' && Number.isFinite(row.updatedAt) ? { updatedAt: row.updatedAt } : {}) })\n }\n const sessions = new Map<string, Session>(this.ctx.sessions.list().map(session => [session.id, session]))\n // Completion is a fact, not a page-local negative flag. A page that was\n // reloaded or selected a different conversation cannot retract another\n // page's reminder. Explicit, timestamped reading acknowledgements can.\n for (const row of rows) {\n if (!row.completed || row.running) continue\n const session = sessions.get(row.id)\n if (session && (isChildSession(session) || this.isBusy(session))) continue\n const turn = session ? foldSession(session).lastTurn : undefined\n const seen = this.seen[row.id]\n const completedAt = turn?.at ?? row.updatedAt\n if (seen !== undefined && (completedAt === undefined || seen >= completedAt)) continue\n this.browserCompletions.set(row.id, row)\n }\n const viewed = data.viewed\n if (data.projectionVersion === 3 && data.focused === true && viewed\n && typeof viewed.id === 'string' && typeof viewed.at === 'number' && Number.isFinite(viewed.at)\n && rows.some(row => row.id === viewed.id && !row.running)) {\n const session = sessions.get(viewed.id)\n if (!session || (!isChildSession(session) && !this.conversationBusy(session, sessions))) this.readThrough(viewed.id, Math.min(viewed.at, Date.now()))\n }\n // Legacy clients can acknowledge only a reminder they themselves showed.\n if (data.projectionVersion !== 3 && data.focused === true && this.sidebar?.clientId === data.clientId) {\n for (const previous of this.sidebar.rows) {\n if (!previous.completed || rows.some(row => row.id === previous.id && row.completed)) continue\n const session = sessions.get(previous.id)\n const at = session ? foldSession(session).lastTurn?.at : Date.now()\n if (at !== undefined && (!session || !this.conversationBusy(session, sessions))) this.readThrough(previous.id, at)\n }\n }\n // A background browser cannot overwrite the last foreground page's state.\n if (this.sidebar && this.sidebar.clientId !== data.clientId && data.focused !== true && Date.now() - this.sidebar.at < 5000) { this.bump(); return true }\n this.sidebar = { clientId: data.clientId, at: Date.now(), focused: data.focused === true, rows,\n ...(data.projectionVersion === 2 || data.projectionVersion === 3 ? { projectionVersion: data.projectionVersion } : {}) }\n const mirrorTrace = { clientId: data.clientId, focused: data.focused === true,\n rows: rows.map(({ id, running, completed }) => ({ id, running, completed })) }\n const signature = JSON.stringify(mirrorTrace)\n if (signature !== this.lastMirrorTrace) { this.lastMirrorTrace = signature; this.trace('sidebar', mirrorTrace) }\n this.bump()\n return true\n }\n\n private sidebarRows(): SidebarRow[] | undefined {\n return this.sidebar && Date.now() - this.sidebar.at < 5000 ? this.sidebar.rows : undefined\n }\n\n private readonly pending = new Map<string, Held>()\n private readonly seen = loadSeen()\n private running = new Set<string>()\n private readonly pendingUnread = new Set<string>()\n private readonly browserCompletions = new Map<string, SidebarRow>()\n private readonly listeners = new Set<() => void>()\n private focus: NotchFocus | null = null\n\n constructor(private readonly ctx: Context) {}\n\n onChange(fn: () => void): () => void {\n this.listeners.add(fn)\n return () => { this.listeners.delete(fn) }\n }\n\n notify(): void {\n this.bump()\n }\n\n private bump(): void {\n for (const fn of this.listeners) {\n try { fn() } catch (error) { this.ctx.logger.warn('dsh-notch: subscriber failed: %s', String(error)) }\n }\n }\n\n snapshot(origin: string): NotchSnapshot {\n const rows: NotchRow[] = []\n const sessions = this.ctx.sessions.list()\n const byId = new Map<string, Session>(sessions.map(session => [session.id, session]))\n const groups = new Map<Session, Session[]>()\n for (const session of sessions) {\n const owner = conversationOwner(session, byId)\n if (!owner) {\n // Broken/unloaded lineage must not invent a task or lose a user question.\n const ids = new Set([session.id])\n const approval = this.heldApproval(ids)\n const ask = this.heldAsk(ids)\n if (approval || ask) rows.push({\n id: session.id, title: this.titleOf(session), child: true,\n busy: false, unread: false,\n ...(approval ? { approval } : {}), ...(ask ? { ask } : {}),\n })\n continue\n }\n const members = groups.get(owner) ?? []\n members.push(session)\n groups.set(owner, members)\n }\n for (const [owner, members] of groups) {\n const row = this.rowFor(owner, members)\n if (row) rows.push(row)\n }\n const freshSidebar = this.sidebarRows()\n const coldRows = new Map(this.browserCompletions)\n for (const source of freshSidebar ?? []) {\n if (source.running) coldRows.set(source.id, source)\n }\n for (const source of coldRows.values()) {\n // The Host owns loaded session classification. A stale mirror must not\n // resurrect a filtered child as child:false or override a settled root.\n if (byId.has(source.id) || (!source.completed && !source.running)) continue\n // Heartbeat freshness is a liveness lease, not an unread-state change.\n // Retain completed cold rows until acknowledged; expire running-only\n // claims when their source stops reporting.\n if (source.running && !freshSidebar) continue\n const seen = this.seen[source.id]\n if (source.completed && seen !== undefined && (source.updatedAt === undefined || seen >= source.updatedAt)) continue\n rows.push({ id: source.id, title: source.title, child: false, busy: source.running, unread: source.completed })\n }\n rows.sort((a, b) => Number(Boolean(b.approval || b.ask)) - Number(Boolean(a.approval || a.ask))\n || Number(b.busy) - Number(a.busy)\n || Number(b.unread) - Number(a.unread)\n || (b.lastTurn?.at ?? 0) - (a.lastTurn?.at ?? 0))\n this.running = new Set(\n sessions.filter((session) => this.isBusy(session)).map((session) => session.id),\n )\n const stateRows = rows.map(row => ({ id: row.id, busy: row.busy, unread: row.unread,\n turnAt: row.lastTurn?.at, action: Boolean(row.ask || row.approval) }))\n const rowSignature = JSON.stringify(stateRows)\n if (rowSignature !== this.lastRowTrace) {\n this.lastRowTrace = rowSignature\n this.trace('snapshot', { rows: stateRows, clientId: this.sidebar?.clientId,\n mirrorAt: this.sidebar?.at, running: [...this.running], pendingUnread: [...this.pendingUnread] })\n }\n return { ok: true, stateRevision: 4, generatedAt: Date.now(), origin, rows,\n sidebarSyncedAt: freshSidebar ? this.sidebar?.at : undefined,\n sidebarProjectionVersion: freshSidebar ? this.sidebar?.projectionVersion : undefined }\n }\n\n markSeen(sessionId: string): void {\n this.pendingUnread.delete(sessionId)\n this.browserCompletions.delete(sessionId)\n this.seen[sessionId] = Date.now()\n saveSeen(this.seen)\n this.bump()\n }\n\n markAllSeen(): void {\n this.pendingUnread.clear()\n const now = Date.now()\n for (const id of this.browserCompletions.keys()) this.seen[id] = now\n this.browserCompletions.clear()\n for (const session of this.ctx.sessions.list()) {\n this.seen[session.id] = now\n }\n saveSeen(this.seen)\n this.bump()\n }\n\n private readThrough(sessionId: string, at: number): void {\n const seen = this.seen[sessionId]\n if ((seen ?? -Infinity) >= at) return\n const session = this.ctx.sessions.list().find(item => item.id === sessionId)\n const turn = session && foldSession(session).lastTurn\n const completedAt = turn?.at ?? this.browserCompletions.get(sessionId)?.updatedAt\n // An idle focused page reports every 400 ms. Persist once per result,\n // not on every heartbeat for as long as that conversation stays open.\n if (seen !== undefined && (completedAt === undefined || seen >= completedAt)) return\n this.seen[sessionId] = at\n if (completedAt === undefined || completedAt <= at) {\n this.pendingUnread.delete(sessionId)\n this.browserCompletions.delete(sessionId)\n }\n saveSeen(this.seen)\n this.trace('read', { sessionId, through: at })\n }\n\n noteTurnEnd(session: Session, at = foldSession(session).lastTurn?.at): void {\n if (isChildSession(session)) return\n if (at !== undefined && (this.seen[session.id] ?? -Infinity) < at) this.pendingUnread.add(session.id)\n }\n\n /**\n * Record a \"show this session in a DSH UI\" wish from the native helper.\n * Returns false for unknown sessions. The wish expires after 60s; every\n * open DSH page consumes it idempotently, so no per-client cursor is kept.\n */\n requestFocus(sessionId: string): boolean {\n const known = this.ctx.sessions.list().some((session) => session.id === sessionId)\n const mirrored = this.browserCompletions.has(sessionId) || this.sidebarRows()?.some(row => row.id === sessionId)\n if (!known && !mirrored) return false\n this.focus = { sessionId, at: Date.now() }\n this.bump()\n return true\n }\n\n peekFocus(): NotchFocus | null {\n if (this.focus === null) return null\n if (Date.now() - this.focus.at > 60_000) {\n this.focus = null\n return null\n }\n return this.focus\n }\n\n decideApproval(id: string, outcome: ApprovalOutcomeWire): boolean {\n const held = this.pending.get(id)\n if (!held || held.kind !== 'approval') return false\n this.pending.delete(id)\n this.seen[held.sessionId] = Date.now()\n saveSeen(this.seen)\n held.resolve(outcome)\n this.bump()\n return true\n }\n\n answerAsk(id: string, answers: NotchAnswerItem[]): boolean {\n const held = this.pending.get(id)\n if (!held || held.kind !== 'ask') return false\n this.pending.delete(id)\n this.seen[held.sessionId] = Date.now()\n saveSeen(this.seen)\n held.resolve({ answers })\n this.bump()\n return true\n }\n\n holdApproval(request: ApprovalRequest, next: () => Promise<ApprovalOutcome>): Promise<ApprovalOutcome> {\n const sessionId = String(request.agent.id)\n const id = randomUUID()\n const fromNotch = new Promise<ApprovalOutcome>((resolve) => {\n const held: HeldApproval = {\n kind: 'approval',\n id,\n sessionId,\n toolName: request.toolName,\n resolve,\n }\n if (request.reason) held.reason = request.reason\n this.pending.set(id, held)\n this.bump()\n request.signal?.addEventListener('abort', () => {\n if (!this.pending.delete(id)) return\n this.bump()\n }, { once: true })\n })\n return Promise.race([fromNotch, next()]).finally(() => {\n if (this.pending.delete(id)) this.bump()\n })\n }\n\n holdAsk(request: AskUserQuestionRequest, next: () => Promise<AskUserQuestionAnswer>): Promise<AskUserQuestionAnswer> {\n const sessionId = request.agent ? String(request.agent.id) : ''\n if (!sessionId) return next()\n const originalSignal = request.signal\n const downstream = new AbortController()\n request.signal = originalSignal\n ? AbortSignal.any([originalSignal, downstream.signal])\n : downstream.signal\n const id = randomUUID()\n const questions: NotchQuestion[] = request.questions.map((item) => ({\n id: item.id,\n question: item.question,\n ...(item.detail === undefined ? {} : { detail: item.detail }),\n ...(item.header === undefined ? {} : { header: item.header }),\n ...(item.options === undefined ? {} : { options: item.options }),\n ...(item.multiSelect === undefined ? {} : { multiSelect: item.multiSelect }),\n ...(item.intent === undefined ? {} : { intent: item.intent }),\n }))\n const fromNotch = new Promise<AskUserQuestionAnswer>((resolve) => {\n this.pending.set(id, { kind: 'ask', id, sessionId, questions, resolve })\n this.bump()\n request.signal?.addEventListener('abort', () => {\n if (!this.pending.delete(id)) return\n this.bump()\n }, { once: true })\n })\n return Promise.race([fromNotch, Promise.resolve().then(next)]).finally(() => {\n // The losing Web answerer must end too, so its pending interaction unmounts.\n // Cancel only this delegated wait, never the owning agent's signal.\n downstream.abort(new Error('Question settled through another answerer'))\n if (originalSignal === undefined) delete request.signal\n else request.signal = originalSignal\n if (this.pending.delete(id)) this.bump()\n })\n }\n\n private isBusy(session: Session): boolean {\n return this.ctx.agents.get(session.id)?.status === 'running'\n }\n\n private conversationBusy(owner: Session, sessions: ReadonlyMap<string, Session>): boolean {\n for (const member of sessions.values()) {\n if (conversationOwner(member, sessions)?.id === owner.id\n && (this.isBusy(member) || this.hasSubagentJob(member))) return true\n }\n return false\n }\n\n private hasSubagentJob(session: Session): boolean {\n const agent = this.ctx.agents.get(session.id)\n if (!agent) return false\n // Remote backends have no local Agent/Session. The official background\n // job registry retains their owner through running and stopping, including\n // jobs that were already active when Notch was hot-loaded.\n const jobs = this.ctx.get('jobs') as Pick<JobRegistry, 'list'> | undefined\n return jobs?.list(agent.id).some(job => job.owner === session.id\n && job.kind === 'subagent'\n && (job.status === 'running' || job.status === 'stopping')) ?? false\n }\n\n private rowFor(session: Session, members: Session[]): NotchRow | undefined {\n const folded = foldSession(session)\n const child = isChildSession(session)\n const lastSeen = this.seen[session.id]\n const busy = members.some(member => this.isBusy(member) || this.hasSubagentJob(member))\n // Completion belongs to the owner's turn, never an individual worker.\n if (folded.busy) {\n this.pendingUnread.delete(session.id)\n } else if (this.running.has(session.id) && folded.lastTurn) {\n this.pendingUnread.add(session.id)\n }\n const dismissed = lastSeen !== undefined && (folded.lastTurn === undefined || lastSeen >= folded.lastTurn.at)\n const unread = !dismissed && !busy\n && (this.pendingUnread.has(session.id) || this.browserCompletions.has(session.id))\n const memberIds = new Set(members.map(member => member.id))\n // Keep the original request id/resolver so answering the root's yellow\n // lamp settles the correct child. Further child questions queue here.\n const approval = this.heldApproval(memberIds)\n const ask = this.heldAsk(memberIds)\n if (!busy && !unread && !approval && !ask) return undefined\n const title = this.titleOf(session)\n const row: NotchRow = {\n id: session.id,\n title,\n child,\n busy,\n unread,\n }\n if (folded.lastTurn && !busy) row.lastTurn = folded.lastTurn\n if (approval) row.approval = approval\n if (ask) row.ask = ask\n return row\n }\n\n private heldApproval(sessionIds: ReadonlySet<string>): NotchApproval | undefined {\n for (const held of this.pending.values()) {\n if (held.kind === 'approval' && sessionIds.has(held.sessionId)) {\n return {\n id: held.id,\n toolName: held.toolName,\n ...(held.reason === undefined ? {} : { reason: held.reason }),\n }\n }\n }\n return undefined\n }\n\n private heldAsk(sessionIds: ReadonlySet<string>): NotchAsk | undefined {\n for (const held of this.pending.values()) {\n if (held.kind === 'ask' && sessionIds.has(held.sessionId)) {\n return { id: held.id, questions: held.questions }\n }\n }\n return undefined\n }\n\n private titleOf(session: Session): string {\n const service = this.ctx.get('sessionTitle') as { get(target: Session): { title: string } | undefined } | undefined\n const title = service?.get(session)?.title?.trim()\n if (title) return title\n return session.id.slice(0, 8)\n }\n}\n","import { randomUUID } from 'node:crypto'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { Board } from './board.ts'\nimport type { ApprovalOutcomeWire, NotchAnswerItem } from './types.ts'\n\nconst PREFIX = '/dsh-notch'\n\nexport function isLoopback(req: IncomingMessage): boolean {\n const ip = req.socket.remoteAddress ?? ''\n return ip === '127.0.0.1' || ip === '::1' || ip === ':ffff:127.0.0.1' || ip === '::ffff:127.0.0.1'\n}\n\nexport function authorized(req: IncomingMessage, token: string): boolean {\n const header = req.headers.authorization\n if (header === `Bearer ${token}`) return true\n try {\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\n return url.searchParams.get('token') === token\n } catch {\n return false\n }\n}\n\n/**\n * Browser-side trust for pages served by this same Host: loopback remote,\n * never cross-site, and a matching Origin when one is present. Same rule the\n * shipped workspace file panel uses — no secret ever lands in page JS.\n */\nexport function browserTrusted(req: IncomingMessage): boolean {\n if (!isLoopback(req)) return false\n if (req.headers['sec-fetch-site'] === 'cross-site') return false\n const host = req.headers.host\n if (host === undefined) return false\n const origin = req.headers.origin\n if (origin === undefined) return true\n try {\n return new URL(origin).host === new URL(`http://${host}`).host\n } catch {\n return false\n }\n}\n\n/** Accept same-origin Web writes and requests forwarded by the Desktop shell. */\nexport function sidebarTrusted(req: IncomingMessage, origin: string): boolean {\n if (!browserTrusted(req) || !req.headers['content-type']?.startsWith('application/json')) return false\n if (req.headers.origin !== undefined) return true\n // Desktop validates dsh-app://app, then removes Origin and fetch metadata.\n // A script-only header still prevents a foreign page from submitting a form.\n return req.headers['x-dsh-notch-client'] === '1'\n && req.headers['sec-fetch-site'] === undefined\n && req.headers.host === new URL(origin).host\n}\n\nfunction send(res: ServerResponse, status: number, body: unknown): void {\n const json = JSON.stringify(body)\n res.writeHead(status, {\n 'content-type': 'application/json; charset=utf-8',\n 'cache-control': 'no-store',\n })\n res.end(json)\n}\n\nfunction readBody(req: IncomingMessage, limit = 64 * 1024): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = []\n let size = 0\n req.on('data', (chunk: Buffer) => {\n size += chunk.length\n if (size > limit) {\n reject(new Error('body too large'))\n req.destroy()\n return\n }\n chunks.push(chunk)\n })\n req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')))\n req.on('error', reject)\n })\n}\n\nexport function attachHttp(ctx: Context, board: Board, token: string, origin: string): () => void {\n const streams = new Map<string, ServerResponse>()\n const push = () => {\n const payload = `data: ${JSON.stringify(board.snapshot(origin))}\\n\\n`\n for (const [id, res] of streams) {\n try {\n res.write(payload)\n } catch {\n streams.delete(id)\n }\n }\n }\n const stopListen = board.onChange(push)\n\n const handler = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\n const path = url.pathname\n const method = req.method ?? 'GET'\n\n // Browser pages served by this same Host consume focus wishes through\n // same-origin trust (no secret in page JS); everything else keeps the\n // notch Bearer token.\n if (method === 'GET' && path === `${PREFIX}/pending-focus`) {\n if (!browserTrusted(req)) {\n send(res, 403, { ok: false, error: 'forbidden' })\n return\n }\n send(res, 200, { ok: true, focus: board.peekFocus() })\n return\n }\n\n if (method === 'POST' && path === `${PREFIX}/sidebar`) {\n if (!sidebarTrusted(req, origin)) {\n send(res, 403, { ok: false, error: 'forbidden' })\n return\n }\n const ok = board.syncSidebar(JSON.parse(await readBody(req, 512 * 1024)))\n send(res, ok ? 200 : 400, { ok })\n return\n }\n\n if (!isLoopback(req) || !authorized(req, token)) {\n send(res, 403, { ok: false, error: 'forbidden' })\n return\n }\n\n if (method === 'GET' && path === `${PREFIX}/status`) {\n send(res, 200, board.snapshot(origin))\n return\n }\n\n if (method === 'GET' && path === `${PREFIX}/diagnostics`) {\n send(res, 200, board.diagnostics())\n return\n }\n\n if (method === 'GET' && path === `${PREFIX}/events`) {\n const id = randomUUID()\n res.writeHead(200, {\n 'content-type': 'text/event-stream',\n 'cache-control': 'no-cache',\n connection: 'keep-alive',\n })\n streams.set(id, res)\n res.write(`data: ${JSON.stringify(board.snapshot(origin))}\\n\\n`)\n req.on('close', () => { streams.delete(id) })\n return\n }\n\n if (method === 'POST' && path === `${PREFIX}/seen`) {\n const body = JSON.parse(await readBody(req)) as { sessionId?: string; all?: boolean }\n if (body.all) {\n board.markAllSeen()\n send(res, 200, { ok: true })\n return\n }\n if (!body.sessionId) {\n send(res, 400, { ok: false, error: 'sessionId required' })\n return\n }\n board.markSeen(body.sessionId)\n send(res, 200, { ok: true })\n return\n }\n\n if (method === 'POST' && path === `${PREFIX}/approve`) {\n const body = JSON.parse(await readBody(req)) as { id?: string; outcome?: ApprovalOutcomeWire }\n if (!body.id || (body.outcome !== 'allowed-once' && body.outcome !== 'rejected')) {\n send(res, 400, { ok: false, error: 'id and outcome required' })\n return\n }\n if (!board.decideApproval(body.id, body.outcome)) {\n send(res, 404, { ok: false, error: 'not pending' })\n return\n }\n send(res, 200, { ok: true })\n return\n }\n\n if (method === 'POST' && path === `${PREFIX}/answer`) {\n const body = JSON.parse(await readBody(req)) as { id?: string; answers?: NotchAnswerItem[] }\n if (!body.id || !Array.isArray(body.answers)) {\n send(res, 400, { ok: false, error: 'id and answers required' })\n return\n }\n if (!board.answerAsk(body.id, body.answers)) {\n send(res, 404, { ok: false, error: 'not pending' })\n return\n }\n send(res, 200, { ok: true })\n return\n }\n\n if (method === 'POST' && path === `${PREFIX}/focus`) {\n const body = JSON.parse(await readBody(req)) as { sessionId?: string }\n if (!body.sessionId) {\n send(res, 400, { ok: false, error: 'sessionId required' })\n return\n }\n if (!board.requestFocus(body.sessionId)) {\n send(res, 404, { ok: false, error: 'unknown session' })\n return\n }\n send(res, 200, { ok: true })\n return\n }\n\n send(res, 404, { ok: false, error: 'not found' })\n }\n\n const disposeRoute = ctx.webServer.register({\n kind: 'prefix',\n path: PREFIX,\n handler: (req, res) => {\n void handler(req, res).catch((error: unknown) => {\n if (!res.headersSent) send(res, 400, { ok: false, error: String(error) })\n })\n },\n })\n\n return () => {\n stopListen()\n for (const res of streams.values()) {\n try { res.end() } catch { /* already closed */ }\n }\n streams.clear()\n disposeRoute()\n }\n}\n","import type { Context } from '@deepseek-ai/cordis'\nimport Schema from '@deepseek-ai/schemastery'\nimport { createWriteStream, existsSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { superviseNotch } from '../desktop/notch-lifecycle.mjs'\nimport type { ApprovalOutcome, ApprovalRequest } from '@deepseek-ai/dsh-user-approval'\nimport type { AskUserQuestionAnswer, AskUserQuestionRequest } from '@deepseek-ai/dsh-user-questions'\nimport type {} from '@deepseek-ai/dsh-host-webserver'\nimport type {} from '@deepseek-ai/dsh-session'\nimport { Board } from './board.ts'\nimport { attachHttp } from './http.ts'\nimport { DIR, RUNTIME, loadOrCreateToken, writeRuntime } from './store.ts'\n\nexport const name = 'dsh-notch'\nexport const inject = ['sessions', 'webServer', 'approval', 'userQuestions', 'agents']\nexport const Config = Schema.object({\n helperPath: Schema.string().default('').description('Optional installed native Notch executable; managed for this Host lifetime.'),\n})\n\nexport function apply(ctx: Context, config: { helperPath?: string } = {}) {\n console.log('[my-plugins/dsh-notch] loaded')\n const board = new Board(ctx)\n const token = loadOrCreateToken()\n const origin = `http://${ctx.webServer.host}:${String(ctx.webServer.port)}`\n writeRuntime({\n origin,\n token,\n pid: process.pid,\n writtenAt: Date.now(),\n })\n if (config.helperPath) {\n if (!existsSync(config.helperPath)) throw new Error('Configured Notch helper does not exist')\n ctx.effect(() => {\n const log = createWriteStream(join(DIR, 'helper.log'), { flags: 'a', mode: 0o600 })\n const helper = superviseNotch({ bin: config.helperPath, pidPath: join(DIR, 'helper.pid'), log,\n env: { ...process.env, DSH_NOTCH_RUNTIME_FILE: RUNTIME } })\n return () => { helper.stop(); log.end() }\n }, 'dsh-notch: native helper')\n }\n\n ctx.effect(() => attachHttp(ctx, board, token, origin), 'dsh-notch: http')\n\n const notify = debounce(() => board.notify(), 80)\n ctx.effect(() => notify.dispose, 'dsh-notch: notification timer')\n ctx.effect(() => ctx.on('session/created', notify), 'dsh-notch: created')\n ctx.effect(() => ctx.on('session/disposed', notify), 'dsh-notch: disposed')\n ctx.effect(() => ctx.on('session/event', (session, event) => {\n const type = String(event.type)\n if (type === 'turn/end') board.noteTurnEnd(session, event.time)\n if (type === 'turn/start' || type === 'turn/end' || type === 'session/title') {\n notify()\n }\n // When a turn starts or user sends a message in DSH, mark session as read\n if (type === 'turn/start' || (type === 'user/message' && (event.data as Record<string, unknown>)?.source === 'user')) {\n board.markSeen(session.id)\n }\n }), 'dsh-notch: events')\n ctx.effect(() => ctx.on('agent/status', notify), 'dsh-notch: agent-status')\n\n\n // Only intercept user-questions/request (AskUserQuestion).\n // Do NOT intercept internal tool approval/request which causes false alarms\n // when background agent tools run sandbox checks.\n ctx.on('user-questions/request', (\n request: AskUserQuestionRequest,\n next: () => Promise<AskUserQuestionAnswer>,\n ) => board.holdAsk(request, next), { prepend: true })\n}\n\nfunction debounce(fn: () => void, ms: number): (() => void) & { dispose: () => void } {\n let timer: ReturnType<typeof setTimeout> | undefined\n const notify = () => {\n if (timer) clearTimeout(timer)\n timer = setTimeout(() => { timer = undefined; fn() }, ms)\n }\n return Object.assign(notify, { dispose: () => { if (timer) clearTimeout(timer); timer = undefined } })\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-notch",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "registry": "https://registry.npmjs.org/"
@@ -31,25 +31,28 @@
31
31
  "test:expanded-height": "sh macos/Tests/expanded-height.sh",
32
32
  "test:host-lifetime": "sh macos/Tests/host-lifetime.sh",
33
33
  "build": "tsdown --config-loader native",
34
- "test:desktop-launch": "sh macos/Tests/desktop-launch.sh"
34
+ "test:desktop-launch": "sh macos/Tests/desktop-launch.sh",
35
+ "typecheck": "tsc -p tsconfig.json --noEmit"
35
36
  },
36
37
  "devDependencies": {
37
- "@deepseek-ai/dsh-home-paths": "0.2.0-rc.2",
38
+ "@deepseek-ai/dsh-home-paths": ">=0.2.1-rc.1 <0.2.2",
38
39
  "semver": "7.8.5",
39
40
  "tsdown": "^0.22.2",
40
- "tsx": "4.22.4"
41
+ "tsx": "4.22.4",
42
+ "@types/node": "^22.19.0",
43
+ "typescript": "^5.9.3"
41
44
  },
42
45
  "peerDependencies": {
43
- "@deepseek-ai/dsh": ">=0.2.0-rc.1 <0.2.1",
44
- "@deepseek-ai/dsh-home-paths": ">=0.2.0-rc.1 <0.2.1",
45
- "@deepseek-ai/dsh-host-webserver": ">=0.2.0-rc.1 <0.2.1",
46
- "@deepseek-ai/dsh-session": ">=0.2.0-rc.1 <0.2.1",
47
- "@deepseek-ai/dsh-user-approval": ">=0.2.0-rc.1 <0.2.1",
48
- "@deepseek-ai/dsh-user-questions": ">=0.2.0-rc.1 <0.2.1",
46
+ "@deepseek-ai/dsh": ">=0.2.1-rc.1 <0.2.2",
47
+ "@deepseek-ai/dsh-home-paths": ">=0.2.1-rc.1 <0.2.2",
48
+ "@deepseek-ai/dsh-host-webserver": ">=0.2.1-rc.1 <0.2.2",
49
+ "@deepseek-ai/dsh-session": ">=0.2.1-rc.1 <0.2.2",
50
+ "@deepseek-ai/dsh-user-approval": ">=0.2.1-rc.1 <0.2.2",
51
+ "@deepseek-ai/dsh-user-questions": ">=0.2.1-rc.1 <0.2.2",
49
52
  "@deepseek-ai/schemastery": "^3.18.4",
50
- "@deepseek-ai/dsh-api-session-controller": ">=0.2.0-rc.1 <0.2.1",
51
- "@deepseek-ai/dsh-client-ui-workspace": ">=0.2.0-rc.1 <0.2.1",
52
- "@deepseek-ai/dsh-client-ui-session": ">=0.2.0-rc.1 <0.2.1"
53
+ "@deepseek-ai/dsh-api-session-controller": ">=0.2.1-rc.1 <0.2.2",
54
+ "@deepseek-ai/dsh-client-ui-workspace": ">=0.2.1-rc.1 <0.2.2",
55
+ "@deepseek-ai/dsh-client-ui-session": ">=0.2.1-rc.1 <0.2.2"
53
56
  },
54
57
  "peerDependenciesMeta": {
55
58
  "@deepseek-ai/dsh": {
@@ -98,5 +101,8 @@
98
101
  ".": "./lib/index.mjs",
99
102
  "./client": "./lib/client.js",
100
103
  "./package.json": "./package.json"
104
+ },
105
+ "engines": {
106
+ "dsh": ">=0.2.1-rc.1 <0.2.2"
101
107
  }
102
108
  }
package/src/board.ts CHANGED
@@ -70,7 +70,7 @@ export class Board {
70
70
  rows.push({ id: row.id, title: row.title.slice(0, 512), completed: row.completed, running: row.running,
71
71
  ...(typeof row.updatedAt === 'number' && Number.isFinite(row.updatedAt) ? { updatedAt: row.updatedAt } : {}) })
72
72
  }
73
- const sessions = new Map(this.ctx.sessions.list().map(session => [session.id, session]))
73
+ const sessions = new Map<string, Session>(this.ctx.sessions.list().map(session => [session.id, session]))
74
74
  // Completion is a fact, not a page-local negative flag. A page that was
75
75
  // reloaded or selected a different conversation cannot retract another
76
76
  // page's reminder. Explicit, timestamped reading acknowledgements can.
@@ -93,7 +93,7 @@ export function apply(ctx: ClientContext): void {
93
93
  if (!stopped) timer = window.setTimeout(tick, POLL_MS)
94
94
  })
95
95
  }
96
- let timer: ReturnType<typeof window.setTimeout> | undefined
96
+ let timer: number | ReturnType<typeof setTimeout> | undefined
97
97
  tick()
98
98
 
99
99
  ctx.effect(() => () => {