dsh-notch 0.3.1 → 0.3.4
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/README.en.md +58 -47
- package/README.md +60 -55
- package/cordis.patch.yml +3 -0
- package/desktop/README.md +29 -0
- package/desktop/notch-lifecycle.mjs +120 -0
- package/desktop/restart-probe.mjs +61 -0
- package/docs/desktop-017rc2.md +23 -0
- package/docs/elastic-edge-dock.md +127 -0
- package/docs/motion-audit.md +1 -1
- package/docs/releases/v0.3.2.md +38 -0
- package/docs/releases/v0.3.3.md +9 -0
- package/docs/subagent-compatibility.md +51 -0
- package/docs/verification/elastic-algorithm-timing.json +337 -0
- package/docs/verification/elastic-attention.json +44 -0
- package/docs/verification/elastic-frame-timing.json +240 -0
- package/docs/verification/elastic-tension.json +59 -0
- package/docs/verification/elastic-tip-taper.json +181 -0
- package/dshx.yml +2 -0
- package/lib/client.js +112 -0
- package/lib/client.js.map +1 -0
- package/lib/index.mjs +990 -0
- package/lib/index.mjs.map +1 -0
- package/package.json +58 -10
- package/src/board.ts +178 -35
- package/src/client/index.ts +104 -0
- package/src/dsh-notch.ts +19 -2
- package/src/http.ts +17 -2
- package/src/session-state.ts +15 -1
- package/src/store.ts +3 -3
- package/src/types.ts +3 -0
- package/BASELINE.md +0 -13
- package/DESIGN.md +0 -34
- package/cordis.yml +0 -5
- package/macos/Package.swift +0 -17
- package/macos/STATUS-MOTION.md +0 -49
- package/macos/Sources/Client.swift +0 -139
- package/macos/Sources/IdleRobot.swift +0 -423
- package/macos/Sources/NotchMarkdown.swift +0 -266
- package/macos/Sources/Panel.swift +0 -153
- package/macos/Sources/Resources/Idle/balance.json +0 -1
- package/macos/Sources/Resources/Idle/blink.json +0 -1
- package/macos/Sources/Resources/Idle/cube-in.json +0 -1
- package/macos/Sources/Resources/Idle/dance.json +0 -1
- package/macos/Sources/Resources/Idle/hop.json +0 -1
- package/macos/Sources/Resources/Idle/nod.json +0 -1
- package/macos/Sources/Resources/Idle/satellite-in.json +0 -1
- package/macos/Sources/Resources/Idle/satellite-out.json +0 -1
- package/macos/Sources/Resources/Idle/scan.json +0 -1
- package/macos/Sources/Resources/Idle/sleep.json +0 -1
- package/macos/Sources/Resources/Idle/sneeze.json +0 -1
- package/macos/Sources/Resources/Idle/stretch.json +0 -1
- package/macos/Sources/Resources/Idle/tilt.json +0 -1
- package/macos/Sources/ReviewStudio.swift +0 -337
- package/macos/Sources/RootView.swift +0 -1093
- package/macos/Sources/StatusOrbit.swift +0 -642
- package/macos/Sources/main.swift +0 -153
- package/macos/Tests/ExpandedHeightProbe.swift +0 -108
- package/macos/Tests/GeometryProbe.swift +0 -98
- package/macos/Tests/IdleProbe.swift +0 -294
- package/macos/Tests/MotionProbe.swift +0 -106
- package/macos/Tests/OutcomeProbe.swift +0 -133
- package/macos/Tests/ScrollbarProbe.swift +0 -91
- package/macos/Tests/expanded-height.sh +0 -13
- package/macos/Tests/geometry.sh +0 -14
- package/macos/Tests/idle.sh +0 -14
- package/macos/Tests/motion.sh +0 -14
- package/macos/Tests/outcome.sh +0 -14
- package/macos/Tests/scrollbar.sh +0 -13
- package/tests/answer-lifecycle.test.mjs +0 -23
- package/tests/board.test.mjs +0 -7
- package/tests/browse-sync.test.mjs +0 -20
- package/tests/mirror.test.mjs +0 -4
- package/tests/peer-range.test.mjs +0 -21
- package/tests/session-state.test.mjs +0 -54
- package/tests/unread-hold.test.mjs +0 -90
- package/tools/desktop-shell/README.md +0 -56
- package/tools/desktop-shell/child-log.mjs +0 -6
- package/tools/desktop-shell/child-log.test.mjs +0 -14
- package/tools/desktop-shell/package-recovery.py +0 -78
- package/tools/desktop-shell/recovery-probe.cjs +0 -50
- package/tools/desktop-shell/renderer-recovery.mjs +0 -95
- package/tools/desktop-shell/renderer-recovery.test.mjs +0 -24
- package/tools/diagnostics/completion-watch.py +0 -101
- package/tools/diagnostics/completion-watch.test.py +0 -17
- package/tools/diagnostics/renderer-diagnostics.mjs +0 -56
- package/tools/diagnostics/renderer-diagnostics.test.mjs +0 -32
- package/tools/idle/LICENSE.open-bot-motion +0 -21
- package/tools/idle/export.mjs +0 -40
- package/tools/idle/index.html +0 -4
- package/tools/idle/motions.js +0 -83
- package/tools/idle/original.js +0 -1673
- package/tools/idle/renderer.js +0 -1673
- package/tools/recording/Client.swift +0 -76
- package/tools/recording/DemoCatalog.swift +0 -174
- package/tools/recording/README.md +0 -23
- package/tools/recording/build.sh +0 -41
- package/tools/recording/main.swift +0 -100
- package/tools/review/ReviewStudio.swift +0 -236
- package/tools/review/audit.sh +0 -19
- package/tools/review/build.sh +0 -7
- package/tools/review/main.swift +0 -158
- package/tools/review/render-audit.swift +0 -32
|
@@ -0,0 +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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"}
|
package/package.json
CHANGED
|
@@ -1,29 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-notch",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public",
|
|
6
|
+
"registry": "https://registry.npmjs.org/"
|
|
7
|
+
},
|
|
4
8
|
"type": "module",
|
|
5
|
-
"main": "
|
|
9
|
+
"main": "lib/index.mjs",
|
|
6
10
|
"description": "Native macOS notch for live DSH sessions, approvals, and model questions.",
|
|
7
11
|
"scripts": {
|
|
8
|
-
"test": "node --import tsx --test tests/*.test.mjs",
|
|
12
|
+
"test": "node --import ./tests/isolate-home.mjs --import tsx --test tests/*.test.mjs",
|
|
9
13
|
"test:motion": "sh macos/Tests/motion.sh",
|
|
10
14
|
"test:geometry": "sh macos/Tests/geometry.sh",
|
|
11
15
|
"build:macos": "swift build --package-path macos -c release",
|
|
12
16
|
"test:outcome": "sh macos/Tests/outcome.sh",
|
|
17
|
+
"test:presentation": "sh macos/Tests/presentation.sh",
|
|
18
|
+
"test:edge-dock": "sh macos/Tests/edge-dock.sh",
|
|
19
|
+
"build:elastic-preview": "sh tools/elastic-preview/build.sh",
|
|
13
20
|
"test:idle": "sh macos/Tests/idle.sh",
|
|
14
21
|
"build:demo": "sh tools/recording/build.sh",
|
|
15
22
|
"test:scrollbar": "sh macos/Tests/scrollbar.sh",
|
|
16
|
-
"test:expanded-height": "sh macos/Tests/expanded-height.sh"
|
|
23
|
+
"test:expanded-height": "sh macos/Tests/expanded-height.sh",
|
|
24
|
+
"test:host-lifetime": "sh macos/Tests/host-lifetime.sh",
|
|
25
|
+
"build": "tsdown --config-loader native",
|
|
26
|
+
"test:desktop-launch": "sh macos/Tests/desktop-launch.sh"
|
|
17
27
|
},
|
|
18
28
|
"devDependencies": {
|
|
29
|
+
"@deepseek-ai/dsh-home-paths": "0.2.0-rc.2",
|
|
30
|
+
"semver": "7.8.5",
|
|
31
|
+
"tsdown": "^0.22.2",
|
|
19
32
|
"tsx": "4.22.4"
|
|
20
33
|
},
|
|
21
34
|
"peerDependencies": {
|
|
22
|
-
"@deepseek-ai/dsh": ">=0.
|
|
23
|
-
"@deepseek-ai/dsh-
|
|
24
|
-
"@deepseek-ai/dsh-
|
|
25
|
-
"@deepseek-ai/dsh-
|
|
26
|
-
"@deepseek-ai/dsh-user-
|
|
35
|
+
"@deepseek-ai/dsh": ">=0.2.0-rc.1 <0.2.1",
|
|
36
|
+
"@deepseek-ai/dsh-home-paths": ">=0.2.0-rc.1 <0.2.1",
|
|
37
|
+
"@deepseek-ai/dsh-host-webserver": ">=0.2.0-rc.1 <0.2.1",
|
|
38
|
+
"@deepseek-ai/dsh-session": ">=0.2.0-rc.1 <0.2.1",
|
|
39
|
+
"@deepseek-ai/dsh-user-approval": ">=0.2.0-rc.1 <0.2.1",
|
|
40
|
+
"@deepseek-ai/dsh-user-questions": ">=0.2.0-rc.1 <0.2.1",
|
|
41
|
+
"@deepseek-ai/schemastery": "^3.18.4",
|
|
42
|
+
"@deepseek-ai/dsh-api-session-controller": ">=0.2.0-rc.1 <0.2.1",
|
|
43
|
+
"@deepseek-ai/dsh-client-ui-workspace": ">=0.2.0-rc.1 <0.2.1",
|
|
44
|
+
"@deepseek-ai/dsh-client-ui-session": ">=0.2.0-rc.1 <0.2.1"
|
|
27
45
|
},
|
|
28
46
|
"peerDependenciesMeta": {
|
|
29
47
|
"@deepseek-ai/dsh": {
|
|
@@ -42,5 +60,35 @@
|
|
|
42
60
|
"optional": true
|
|
43
61
|
}
|
|
44
62
|
},
|
|
45
|
-
"license": "MIT"
|
|
63
|
+
"license": "MIT",
|
|
64
|
+
"dsh": {
|
|
65
|
+
"bundle": {
|
|
66
|
+
"patch": "./cordis.patch.yml"
|
|
67
|
+
},
|
|
68
|
+
"client": {
|
|
69
|
+
"platform": "web",
|
|
70
|
+
"inject": [
|
|
71
|
+
"@deepseek-ai/dsh-api-session-controller",
|
|
72
|
+
"@deepseek-ai/dsh-client-ui-workspace",
|
|
73
|
+
"@deepseek-ai/dsh-client-ui-session"
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"files": [
|
|
78
|
+
"src",
|
|
79
|
+
"cordis.patch.yml",
|
|
80
|
+
"package.json",
|
|
81
|
+
"README.md",
|
|
82
|
+
"LICENSE",
|
|
83
|
+
"lib",
|
|
84
|
+
"desktop",
|
|
85
|
+
"dshx.yml",
|
|
86
|
+
"README.en.md",
|
|
87
|
+
"docs"
|
|
88
|
+
],
|
|
89
|
+
"exports": {
|
|
90
|
+
".": "./lib/index.mjs",
|
|
91
|
+
"./client": "./lib/client.js",
|
|
92
|
+
"./package.json": "./package.json"
|
|
93
|
+
}
|
|
46
94
|
}
|
package/src/board.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto'
|
|
2
2
|
import type { Context } from '@deepseek-ai/cordis'
|
|
3
3
|
import type { Session } from '@deepseek-ai/dsh-session'
|
|
4
|
+
import type { JobRegistry } from '@deepseek-ai/dsh-jobs'
|
|
4
5
|
import type { ApprovalOutcome, ApprovalRequest } from '@deepseek-ai/dsh-user-approval'
|
|
5
6
|
import type {
|
|
6
7
|
AskUserQuestionAnswer,
|
|
7
8
|
AskUserQuestionRequest,
|
|
8
9
|
} from '@deepseek-ai/dsh-user-questions'
|
|
9
|
-
import { foldSession, isChildSession } from './session-state.ts'
|
|
10
|
+
import { conversationOwner, foldSession, isChildSession } from './session-state.ts'
|
|
10
11
|
import { loadSeen, saveSeen } from './store.ts'
|
|
11
12
|
import type {
|
|
12
13
|
ApprovalOutcomeWire,
|
|
@@ -38,22 +39,75 @@ interface HeldAsk {
|
|
|
38
39
|
|
|
39
40
|
type Held = HeldApproval | HeldAsk
|
|
40
41
|
|
|
41
|
-
interface SidebarRow { id: string; title: string; completed: boolean; running: boolean }
|
|
42
|
+
interface SidebarRow { id: string; title: string; completed: boolean; running: boolean; updatedAt?: number }
|
|
42
43
|
export class Board {
|
|
43
|
-
private sidebar: { clientId: string; at: number; rows: SidebarRow[] } | undefined
|
|
44
|
+
private sidebar: { clientId: string; at: number; focused: boolean; rows: SidebarRow[]; projectionVersion?: number } | undefined
|
|
45
|
+
private readonly instance = randomUUID()
|
|
46
|
+
private readonly traces: Record<string, unknown>[] = []
|
|
47
|
+
private lastMirrorTrace = ''
|
|
48
|
+
private lastRowTrace = ''
|
|
49
|
+
|
|
50
|
+
/** Bounded, content-free diagnostics for state-source races. */
|
|
51
|
+
diagnostics(): unknown {
|
|
52
|
+
return { stateRevision: 4, instance: this.instance,
|
|
53
|
+
sidebar: this.sidebar && { clientId: this.sidebar.clientId, at: this.sidebar.at, focused: this.sidebar.focused },
|
|
54
|
+
traces: this.traces }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private trace(event: string, value: Record<string, unknown>): void {
|
|
58
|
+
this.traces.push({ at: Date.now(), event, ...value })
|
|
59
|
+
if (this.traces.length > 160) this.traces.shift()
|
|
60
|
+
}
|
|
44
61
|
|
|
45
62
|
syncSidebar(input: unknown): boolean {
|
|
46
63
|
if (!input || typeof input !== 'object') return false
|
|
47
|
-
const data = input as { clientId?: unknown; focused?: unknown; rows?: unknown }
|
|
64
|
+
const data = input as { clientId?: unknown; focused?: unknown; rows?: unknown; projectionVersion?: unknown; viewed?: { id?: unknown; at?: unknown } }
|
|
48
65
|
if (typeof data.clientId !== 'string' || !Array.isArray(data.rows) || data.rows.length > 1000) return false
|
|
49
66
|
const rows: SidebarRow[] = []
|
|
50
67
|
for (const row of data.rows) {
|
|
51
|
-
|
|
52
|
-
|
|
68
|
+
// Session IDs are opaque; API-created conversations need no session- prefix.
|
|
69
|
+
if (!row || typeof row.id !== 'string' || row.id.length === 0 || typeof row.title !== 'string' || typeof row.completed !== 'boolean' || typeof row.running !== 'boolean') return false
|
|
70
|
+
rows.push({ id: row.id, title: row.title.slice(0, 512), completed: row.completed, running: row.running,
|
|
71
|
+
...(typeof row.updatedAt === 'number' && Number.isFinite(row.updatedAt) ? { updatedAt: row.updatedAt } : {}) })
|
|
72
|
+
}
|
|
73
|
+
const sessions = new Map(this.ctx.sessions.list().map(session => [session.id, session]))
|
|
74
|
+
// Completion is a fact, not a page-local negative flag. A page that was
|
|
75
|
+
// reloaded or selected a different conversation cannot retract another
|
|
76
|
+
// page's reminder. Explicit, timestamped reading acknowledgements can.
|
|
77
|
+
for (const row of rows) {
|
|
78
|
+
if (!row.completed || row.running) continue
|
|
79
|
+
const session = sessions.get(row.id)
|
|
80
|
+
if (session && (isChildSession(session) || this.isBusy(session))) continue
|
|
81
|
+
const turn = session ? foldSession(session).lastTurn : undefined
|
|
82
|
+
const seen = this.seen[row.id]
|
|
83
|
+
const completedAt = turn?.at ?? row.updatedAt
|
|
84
|
+
if (seen !== undefined && (completedAt === undefined || seen >= completedAt)) continue
|
|
85
|
+
this.browserCompletions.set(row.id, row)
|
|
86
|
+
}
|
|
87
|
+
const viewed = data.viewed
|
|
88
|
+
if (data.projectionVersion === 3 && data.focused === true && viewed
|
|
89
|
+
&& typeof viewed.id === 'string' && typeof viewed.at === 'number' && Number.isFinite(viewed.at)
|
|
90
|
+
&& rows.some(row => row.id === viewed.id && !row.running)) {
|
|
91
|
+
const session = sessions.get(viewed.id)
|
|
92
|
+
if (!session || (!isChildSession(session) && !this.conversationBusy(session, sessions))) this.readThrough(viewed.id, Math.min(viewed.at, Date.now()))
|
|
93
|
+
}
|
|
94
|
+
// Legacy clients can acknowledge only a reminder they themselves showed.
|
|
95
|
+
if (data.projectionVersion !== 3 && data.focused === true && this.sidebar?.clientId === data.clientId) {
|
|
96
|
+
for (const previous of this.sidebar.rows) {
|
|
97
|
+
if (!previous.completed || rows.some(row => row.id === previous.id && row.completed)) continue
|
|
98
|
+
const session = sessions.get(previous.id)
|
|
99
|
+
const at = session ? foldSession(session).lastTurn?.at : Date.now()
|
|
100
|
+
if (at !== undefined && (!session || !this.conversationBusy(session, sessions))) this.readThrough(previous.id, at)
|
|
101
|
+
}
|
|
53
102
|
}
|
|
54
103
|
// A background browser cannot overwrite the last foreground page's state.
|
|
55
|
-
if (this.sidebar && this.sidebar.clientId !== data.clientId && data.focused !== true && Date.now() - this.sidebar.at < 5000) return true
|
|
56
|
-
this.sidebar = { clientId: data.clientId, at: Date.now(), rows
|
|
104
|
+
if (this.sidebar && this.sidebar.clientId !== data.clientId && data.focused !== true && Date.now() - this.sidebar.at < 5000) { this.bump(); return true }
|
|
105
|
+
this.sidebar = { clientId: data.clientId, at: Date.now(), focused: data.focused === true, rows,
|
|
106
|
+
...(data.projectionVersion === 2 || data.projectionVersion === 3 ? { projectionVersion: data.projectionVersion } : {}) }
|
|
107
|
+
const mirrorTrace = { clientId: data.clientId, focused: data.focused === true,
|
|
108
|
+
rows: rows.map(({ id, running, completed }) => ({ id, running, completed })) }
|
|
109
|
+
const signature = JSON.stringify(mirrorTrace)
|
|
110
|
+
if (signature !== this.lastMirrorTrace) { this.lastMirrorTrace = signature; this.trace('sidebar', mirrorTrace) }
|
|
57
111
|
this.bump()
|
|
58
112
|
return true
|
|
59
113
|
}
|
|
@@ -64,8 +118,9 @@ export class Board {
|
|
|
64
118
|
|
|
65
119
|
private readonly pending = new Map<string, Held>()
|
|
66
120
|
private readonly seen = loadSeen()
|
|
67
|
-
private
|
|
121
|
+
private running = new Set<string>()
|
|
68
122
|
private readonly pendingUnread = new Set<string>()
|
|
123
|
+
private readonly browserCompletions = new Map<string, SidebarRow>()
|
|
69
124
|
private readonly listeners = new Set<() => void>()
|
|
70
125
|
private focus: NotchFocus | null = null
|
|
71
126
|
|
|
@@ -88,13 +143,46 @@ export class Board {
|
|
|
88
143
|
|
|
89
144
|
snapshot(origin: string): NotchSnapshot {
|
|
90
145
|
const rows: NotchRow[] = []
|
|
91
|
-
|
|
92
|
-
|
|
146
|
+
const sessions = this.ctx.sessions.list()
|
|
147
|
+
const byId = new Map<string, Session>(sessions.map(session => [session.id, session]))
|
|
148
|
+
const groups = new Map<Session, Session[]>()
|
|
149
|
+
for (const session of sessions) {
|
|
150
|
+
const owner = conversationOwner(session, byId)
|
|
151
|
+
if (!owner) {
|
|
152
|
+
// Broken/unloaded lineage must not invent a task or lose a user question.
|
|
153
|
+
const ids = new Set([session.id])
|
|
154
|
+
const approval = this.heldApproval(ids)
|
|
155
|
+
const ask = this.heldAsk(ids)
|
|
156
|
+
if (approval || ask) rows.push({
|
|
157
|
+
id: session.id, title: this.titleOf(session), child: true,
|
|
158
|
+
busy: false, unread: false,
|
|
159
|
+
...(approval ? { approval } : {}), ...(ask ? { ask } : {}),
|
|
160
|
+
})
|
|
161
|
+
continue
|
|
162
|
+
}
|
|
163
|
+
const members = groups.get(owner) ?? []
|
|
164
|
+
members.push(session)
|
|
165
|
+
groups.set(owner, members)
|
|
166
|
+
}
|
|
167
|
+
for (const [owner, members] of groups) {
|
|
168
|
+
const row = this.rowFor(owner, members)
|
|
93
169
|
if (row) rows.push(row)
|
|
94
170
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
171
|
+
const freshSidebar = this.sidebarRows()
|
|
172
|
+
const coldRows = new Map(this.browserCompletions)
|
|
173
|
+
for (const source of freshSidebar ?? []) {
|
|
174
|
+
if (source.running) coldRows.set(source.id, source)
|
|
175
|
+
}
|
|
176
|
+
for (const source of coldRows.values()) {
|
|
177
|
+
// The Host owns loaded session classification. A stale mirror must not
|
|
178
|
+
// resurrect a filtered child as child:false or override a settled root.
|
|
179
|
+
if (byId.has(source.id) || (!source.completed && !source.running)) continue
|
|
180
|
+
// Heartbeat freshness is a liveness lease, not an unread-state change.
|
|
181
|
+
// Retain completed cold rows until acknowledged; expire running-only
|
|
182
|
+
// claims when their source stops reporting.
|
|
183
|
+
if (source.running && !freshSidebar) continue
|
|
184
|
+
const seen = this.seen[source.id]
|
|
185
|
+
if (source.completed && seen !== undefined && (source.updatedAt === undefined || seen >= source.updatedAt)) continue
|
|
98
186
|
rows.push({ id: source.id, title: source.title, child: false, busy: source.running, unread: source.completed })
|
|
99
187
|
}
|
|
100
188
|
rows.sort((a, b) => Number(Boolean(b.approval || b.ask)) - Number(Boolean(a.approval || a.ask))
|
|
@@ -102,13 +190,24 @@ export class Board {
|
|
|
102
190
|
|| Number(b.unread) - Number(a.unread)
|
|
103
191
|
|| (b.lastTurn?.at ?? 0) - (a.lastTurn?.at ?? 0))
|
|
104
192
|
this.running = new Set(
|
|
105
|
-
|
|
193
|
+
sessions.filter((session) => this.isBusy(session)).map((session) => session.id),
|
|
106
194
|
)
|
|
107
|
-
|
|
195
|
+
const stateRows = rows.map(row => ({ id: row.id, busy: row.busy, unread: row.unread,
|
|
196
|
+
turnAt: row.lastTurn?.at, action: Boolean(row.ask || row.approval) }))
|
|
197
|
+
const rowSignature = JSON.stringify(stateRows)
|
|
198
|
+
if (rowSignature !== this.lastRowTrace) {
|
|
199
|
+
this.lastRowTrace = rowSignature
|
|
200
|
+
this.trace('snapshot', { rows: stateRows, clientId: this.sidebar?.clientId,
|
|
201
|
+
mirrorAt: this.sidebar?.at, running: [...this.running], pendingUnread: [...this.pendingUnread] })
|
|
202
|
+
}
|
|
203
|
+
return { ok: true, stateRevision: 4, generatedAt: Date.now(), origin, rows,
|
|
204
|
+
sidebarSyncedAt: freshSidebar ? this.sidebar?.at : undefined,
|
|
205
|
+
sidebarProjectionVersion: freshSidebar ? this.sidebar?.projectionVersion : undefined }
|
|
108
206
|
}
|
|
109
207
|
|
|
110
208
|
markSeen(sessionId: string): void {
|
|
111
209
|
this.pendingUnread.delete(sessionId)
|
|
210
|
+
this.browserCompletions.delete(sessionId)
|
|
112
211
|
this.seen[sessionId] = Date.now()
|
|
113
212
|
saveSeen(this.seen)
|
|
114
213
|
this.bump()
|
|
@@ -117,6 +216,8 @@ export class Board {
|
|
|
117
216
|
markAllSeen(): void {
|
|
118
217
|
this.pendingUnread.clear()
|
|
119
218
|
const now = Date.now()
|
|
219
|
+
for (const id of this.browserCompletions.keys()) this.seen[id] = now
|
|
220
|
+
this.browserCompletions.clear()
|
|
120
221
|
for (const session of this.ctx.sessions.list()) {
|
|
121
222
|
this.seen[session.id] = now
|
|
122
223
|
}
|
|
@@ -124,6 +225,29 @@ export class Board {
|
|
|
124
225
|
this.bump()
|
|
125
226
|
}
|
|
126
227
|
|
|
228
|
+
private readThrough(sessionId: string, at: number): void {
|
|
229
|
+
const seen = this.seen[sessionId]
|
|
230
|
+
if ((seen ?? -Infinity) >= at) return
|
|
231
|
+
const session = this.ctx.sessions.list().find(item => item.id === sessionId)
|
|
232
|
+
const turn = session && foldSession(session).lastTurn
|
|
233
|
+
const completedAt = turn?.at ?? this.browserCompletions.get(sessionId)?.updatedAt
|
|
234
|
+
// An idle focused page reports every 400 ms. Persist once per result,
|
|
235
|
+
// not on every heartbeat for as long as that conversation stays open.
|
|
236
|
+
if (seen !== undefined && (completedAt === undefined || seen >= completedAt)) return
|
|
237
|
+
this.seen[sessionId] = at
|
|
238
|
+
if (completedAt === undefined || completedAt <= at) {
|
|
239
|
+
this.pendingUnread.delete(sessionId)
|
|
240
|
+
this.browserCompletions.delete(sessionId)
|
|
241
|
+
}
|
|
242
|
+
saveSeen(this.seen)
|
|
243
|
+
this.trace('read', { sessionId, through: at })
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
noteTurnEnd(session: Session, at = foldSession(session).lastTurn?.at): void {
|
|
247
|
+
if (isChildSession(session)) return
|
|
248
|
+
if (at !== undefined && (this.seen[session.id] ?? -Infinity) < at) this.pendingUnread.add(session.id)
|
|
249
|
+
}
|
|
250
|
+
|
|
127
251
|
/**
|
|
128
252
|
* Record a "show this session in a DSH UI" wish from the native helper.
|
|
129
253
|
* Returns false for unknown sessions. The wish expires after 60s; every
|
|
@@ -131,7 +255,8 @@ export class Board {
|
|
|
131
255
|
*/
|
|
132
256
|
requestFocus(sessionId: string): boolean {
|
|
133
257
|
const known = this.ctx.sessions.list().some((session) => session.id === sessionId)
|
|
134
|
-
|
|
258
|
+
const mirrored = this.browserCompletions.has(sessionId) || this.sidebarRows()?.some(row => row.id === sessionId)
|
|
259
|
+
if (!known && !mirrored) return false
|
|
135
260
|
this.focus = { sessionId, at: Date.now() }
|
|
136
261
|
this.bump()
|
|
137
262
|
return true
|
|
@@ -232,28 +357,46 @@ export class Board {
|
|
|
232
357
|
return this.ctx.agents.get(session.id)?.status === 'running'
|
|
233
358
|
}
|
|
234
359
|
|
|
235
|
-
private
|
|
360
|
+
private conversationBusy(owner: Session, sessions: ReadonlyMap<string, Session>): boolean {
|
|
361
|
+
for (const member of sessions.values()) {
|
|
362
|
+
if (conversationOwner(member, sessions)?.id === owner.id
|
|
363
|
+
&& (this.isBusy(member) || this.hasSubagentJob(member))) return true
|
|
364
|
+
}
|
|
365
|
+
return false
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
private hasSubagentJob(session: Session): boolean {
|
|
369
|
+
const agent = this.ctx.agents.get(session.id)
|
|
370
|
+
if (!agent) return false
|
|
371
|
+
// Remote backends have no local Agent/Session. The official background
|
|
372
|
+
// job registry retains their owner through running and stopping, including
|
|
373
|
+
// jobs that were already active when Notch was hot-loaded.
|
|
374
|
+
const jobs = this.ctx.get('jobs') as Pick<JobRegistry, 'list'> | undefined
|
|
375
|
+
return jobs?.list(agent.id).some(job => job.owner === session.id
|
|
376
|
+
&& job.kind === 'subagent'
|
|
377
|
+
&& (job.status === 'running' || job.status === 'stopping')) ?? false
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
private rowFor(session: Session, members: Session[]): NotchRow | undefined {
|
|
236
381
|
const folded = foldSession(session)
|
|
237
382
|
const child = isChildSession(session)
|
|
238
383
|
const lastSeen = this.seen[session.id]
|
|
239
|
-
const busy = this.isBusy(
|
|
240
|
-
|
|
384
|
+
const busy = members.some(member => this.isBusy(member) || this.hasSubagentJob(member))
|
|
385
|
+
// Completion belongs to the owner's turn, never an individual worker.
|
|
386
|
+
if (folded.busy) {
|
|
241
387
|
this.pendingUnread.delete(session.id)
|
|
242
|
-
} else if (this.running.has(session.id) && folded.lastTurn
|
|
388
|
+
} else if (this.running.has(session.id) && folded.lastTurn) {
|
|
243
389
|
this.pendingUnread.add(session.id)
|
|
244
390
|
}
|
|
245
391
|
const dismissed = lastSeen !== undefined && (folded.lastTurn === undefined || lastSeen >= folded.lastTurn.at)
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
const approval = this.heldApproval(session.id)
|
|
254
|
-
const ask = this.heldAsk(session.id)
|
|
392
|
+
const unread = !dismissed && !busy
|
|
393
|
+
&& (this.pendingUnread.has(session.id) || this.browserCompletions.has(session.id))
|
|
394
|
+
const memberIds = new Set(members.map(member => member.id))
|
|
395
|
+
// Keep the original request id/resolver so answering the root's yellow
|
|
396
|
+
// lamp settles the correct child. Further child questions queue here.
|
|
397
|
+
const approval = this.heldApproval(memberIds)
|
|
398
|
+
const ask = this.heldAsk(memberIds)
|
|
255
399
|
if (!busy && !unread && !approval && !ask) return undefined
|
|
256
|
-
if (child && !approval && !ask && !busy) return undefined
|
|
257
400
|
const title = this.titleOf(session)
|
|
258
401
|
const row: NotchRow = {
|
|
259
402
|
id: session.id,
|
|
@@ -268,9 +411,9 @@ export class Board {
|
|
|
268
411
|
return row
|
|
269
412
|
}
|
|
270
413
|
|
|
271
|
-
private heldApproval(
|
|
414
|
+
private heldApproval(sessionIds: ReadonlySet<string>): NotchApproval | undefined {
|
|
272
415
|
for (const held of this.pending.values()) {
|
|
273
|
-
if (held.kind === 'approval' && held.sessionId
|
|
416
|
+
if (held.kind === 'approval' && sessionIds.has(held.sessionId)) {
|
|
274
417
|
return {
|
|
275
418
|
id: held.id,
|
|
276
419
|
toolName: held.toolName,
|
|
@@ -281,9 +424,9 @@ export class Board {
|
|
|
281
424
|
return undefined
|
|
282
425
|
}
|
|
283
426
|
|
|
284
|
-
private heldAsk(
|
|
427
|
+
private heldAsk(sessionIds: ReadonlySet<string>): NotchAsk | undefined {
|
|
285
428
|
for (const held of this.pending.values()) {
|
|
286
|
-
if (held.kind === 'ask' && held.sessionId
|
|
429
|
+
if (held.kind === 'ask' && sessionIds.has(held.sessionId)) {
|
|
287
430
|
return { id: held.id, questions: held.questions }
|
|
288
431
|
}
|
|
289
432
|
}
|