@keith9681/dsh-sidebar 0.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +0 -0
  3. package/README_EN.md +0 -0
  4. package/cordis.patch.yml +49 -0
  5. package/lib/client-editor.js +34651 -0
  6. package/lib/client-mermaid.js +200629 -0
  7. package/lib/client-registry.js +13388 -0
  8. package/lib/client-terminal.js +8904 -0
  9. package/lib/client.js +13388 -0
  10. package/lib/index.js +3959 -0
  11. package/lib/invariant.js +22 -0
  12. package/lib/types/agent-pty.d.ts +226 -0
  13. package/lib/types/browser-probe.d.ts +15 -0
  14. package/lib/types/bundle-route.d.ts +12 -0
  15. package/lib/types/client/BrowserView.d.ts +22 -0
  16. package/lib/types/client/DiffTab.d.ts +6 -0
  17. package/lib/types/client/DiffView.d.ts +51 -0
  18. package/lib/types/client/EditorHost.d.ts +12 -0
  19. package/lib/types/client/FileTree.d.ts +36 -0
  20. package/lib/types/client/GitView.d.ts +8 -0
  21. package/lib/types/client/OrphanedTab.d.ts +11 -0
  22. package/lib/types/client/PdfView.d.ts +6 -0
  23. package/lib/types/client/RenderBoundary.d.ts +32 -0
  24. package/lib/types/client/SandboxStatusBar.d.ts +10 -0
  25. package/lib/types/client/SideCardSection.d.ts +74 -0
  26. package/lib/types/client/SideChatView.d.ts +16 -0
  27. package/lib/types/client/Sidebar.d.ts +6 -0
  28. package/lib/types/client/SubagentView.d.ts +16 -0
  29. package/lib/types/client/TabBar.d.ts +43 -0
  30. package/lib/types/client/TerminalView.d.ts +24 -0
  31. package/lib/types/client/TextEditor.d.ts +10 -0
  32. package/lib/types/client/TreePanel.d.ts +23 -0
  33. package/lib/types/client/UploadOverlay.d.ts +20 -0
  34. package/lib/types/client/add-plugin-modal.d.ts +15 -0
  35. package/lib/types/client/api.d.ts +268 -0
  36. package/lib/types/client/binary-download.d.ts +12 -0
  37. package/lib/types/client/breakpoints.d.ts +12 -0
  38. package/lib/types/client/browser.d.ts +50 -0
  39. package/lib/types/client/builtins/index.d.ts +18 -0
  40. package/lib/types/client/builtins/tabs.d.ts +11 -0
  41. package/lib/types/client/builtins/viewers.d.ts +3 -0
  42. package/lib/types/client/chunk-loader.d.ts +124 -0
  43. package/lib/types/client/chunks/editor.d.ts +9 -0
  44. package/lib/types/client/chunks/mermaid.d.ts +10 -0
  45. package/lib/types/client/chunks/terminal.d.ts +9 -0
  46. package/lib/types/client/cm-themes.d.ts +24 -0
  47. package/lib/types/client/conversation-draft.d.ts +14 -0
  48. package/lib/types/client/desktop-env.d.ts +35 -0
  49. package/lib/types/client/editable-state.d.ts +27 -0
  50. package/lib/types/client/editor-load.d.ts +66 -0
  51. package/lib/types/client/frame-batcher.d.ts +24 -0
  52. package/lib/types/client/icons.d.ts +57 -0
  53. package/lib/types/client/image-types.d.ts +3 -0
  54. package/lib/types/client/ime-guard.d.ts +36 -0
  55. package/lib/types/client/index.d.ts +19 -0
  56. package/lib/types/client/intercept.d.ts +33 -0
  57. package/lib/types/client/lang.d.ts +12 -0
  58. package/lib/types/client/lazy-chunk.d.ts +25 -0
  59. package/lib/types/client/link-intercept.d.ts +42 -0
  60. package/lib/types/client/locales.d.ts +366 -0
  61. package/lib/types/client/mermaid-blocks.d.ts +41 -0
  62. package/lib/types/client/mermaid-sanitize.d.ts +2 -0
  63. package/lib/types/client/mermaid.d.ts +12 -0
  64. package/lib/types/client/open-when-sized.d.ts +20 -0
  65. package/lib/types/client/open-with-settings.d.ts +4 -0
  66. package/lib/types/client/open-with.d.ts +87 -0
  67. package/lib/types/client/openpath-intercept.d.ts +41 -0
  68. package/lib/types/client/paths.d.ts +35 -0
  69. package/lib/types/client/pdf-types.d.ts +2 -0
  70. package/lib/types/client/plugin-settings.d.ts +8 -0
  71. package/lib/types/client/plugins-shared.d.ts +27 -0
  72. package/lib/types/client/plugins-tabs.d.ts +3 -0
  73. package/lib/types/client/plugins-viewers.d.ts +3 -0
  74. package/lib/types/client/prefs.d.ts +41 -0
  75. package/lib/types/client/produced-files.d.ts +23 -0
  76. package/lib/types/client/selection-payload.d.ts +27 -0
  77. package/lib/types/client/service.d.ts +440 -0
  78. package/lib/types/client/settings-nav-icon.d.ts +19 -0
  79. package/lib/types/client/shell-presets.d.ts +48 -0
  80. package/lib/types/client/sidechat-transcript.d.ts +103 -0
  81. package/lib/types/client/split-pane.d.ts +30 -0
  82. package/lib/types/client/state.d.ts +359 -0
  83. package/lib/types/client/subagent-detect.d.ts +54 -0
  84. package/lib/types/client/subagent-jobs.d.ts +63 -0
  85. package/lib/types/client/tab-content-memo.d.ts +33 -0
  86. package/lib/types/client/terminal-font.d.ts +20 -0
  87. package/lib/types/client/theme.d.ts +45 -0
  88. package/lib/types/client/titlebar-strip.d.ts +23 -0
  89. package/lib/types/client/upload.d.ts +69 -0
  90. package/lib/types/client/wco.d.ts +47 -0
  91. package/lib/types/config.d.ts +64 -0
  92. package/lib/types/context-types.d.ts +603 -0
  93. package/lib/types/fs-operations.d.ts +28 -0
  94. package/lib/types/fs-search.d.ts +24 -0
  95. package/lib/types/fs-tree.d.ts +51 -0
  96. package/lib/types/git.d.ts +82 -0
  97. package/lib/types/html-route.d.ts +59 -0
  98. package/lib/types/index.d.ts +47 -0
  99. package/lib/types/invariant.d.ts +15 -0
  100. package/lib/types/jobs-routes.d.ts +45 -0
  101. package/lib/types/open-external.d.ts +24 -0
  102. package/lib/types/prefs-shared.d.ts +225 -0
  103. package/lib/types/pty-deps.d.ts +78 -0
  104. package/lib/types/pty-manager.d.ts +168 -0
  105. package/lib/types/sidechat-core.d.ts +172 -0
  106. package/lib/types/sidechat-routes.d.ts +30 -0
  107. package/lib/types/subagent-activity.d.ts +44 -0
  108. package/lib/types/subagent-live-route.d.ts +44 -0
  109. package/lib/types/tools.d.ts +31 -0
  110. package/lib/types/trust-fence.d.ts +24 -0
  111. package/lib/types/wire.d.ts +38 -0
  112. package/package.json +173 -0
  113. package/scripts/install.ps1 +289 -0
  114. package/scripts/install.sh +287 -0
  115. package/src/agent-pty.ts +527 -0
  116. package/src/browser-probe.ts +26 -0
  117. package/src/bundle-route.ts +129 -0
  118. package/src/client/BrowserView.tsx +253 -0
  119. package/src/client/DiffTab.tsx +110 -0
  120. package/src/client/DiffView.tsx +305 -0
  121. package/src/client/EditorHost.tsx +525 -0
  122. package/src/client/FileTree.tsx +653 -0
  123. package/src/client/GitView.tsx +558 -0
  124. package/src/client/OrphanedTab.tsx +27 -0
  125. package/src/client/PdfView.tsx +110 -0
  126. package/src/client/RenderBoundary.tsx +49 -0
  127. package/src/client/SandboxStatusBar.tsx +60 -0
  128. package/src/client/SideCardSection.module.css +960 -0
  129. package/src/client/SideCardSection.tsx +1152 -0
  130. package/src/client/SideChatView.module.css +437 -0
  131. package/src/client/SideChatView.tsx +644 -0
  132. package/src/client/Sidebar.tsx +1148 -0
  133. package/src/client/SubagentView.module.css +547 -0
  134. package/src/client/SubagentView.tsx +883 -0
  135. package/src/client/TabBar.tsx +305 -0
  136. package/src/client/TerminalView.tsx +383 -0
  137. package/src/client/TextEditor.tsx +500 -0
  138. package/src/client/TreePanel.tsx +273 -0
  139. package/src/client/UploadOverlay.tsx +62 -0
  140. package/src/client/add-plugin-modal.tsx +199 -0
  141. package/src/client/api.ts +335 -0
  142. package/src/client/binary-download.tsx +23 -0
  143. package/src/client/breakpoints.ts +51 -0
  144. package/src/client/browser.ts +119 -0
  145. package/src/client/builtins/index.ts +37 -0
  146. package/src/client/builtins/tabs.tsx +331 -0
  147. package/src/client/builtins/viewers.tsx +125 -0
  148. package/src/client/chunk-loader.ts +351 -0
  149. package/src/client/chunks/editor.tsx +9 -0
  150. package/src/client/chunks/mermaid.tsx +10 -0
  151. package/src/client/chunks/terminal.tsx +9 -0
  152. package/src/client/cm-themes.ts +129 -0
  153. package/src/client/conversation-draft.ts +29 -0
  154. package/src/client/css-modules.d.ts +5 -0
  155. package/src/client/desktop-env.ts +72 -0
  156. package/src/client/editable-state.ts +70 -0
  157. package/src/client/editor-load.ts +92 -0
  158. package/src/client/frame-batcher.ts +56 -0
  159. package/src/client/icons.tsx +161 -0
  160. package/src/client/image-types.ts +8 -0
  161. package/src/client/ime-guard.ts +47 -0
  162. package/src/client/index.tsx +369 -0
  163. package/src/client/intercept.tsx +110 -0
  164. package/src/client/lang.ts +103 -0
  165. package/src/client/layout.css +94 -0
  166. package/src/client/lazy-chunk.tsx +89 -0
  167. package/src/client/link-intercept.ts +73 -0
  168. package/src/client/locales.ts +734 -0
  169. package/src/client/mermaid-blocks.ts +110 -0
  170. package/src/client/mermaid-sanitize.ts +93 -0
  171. package/src/client/mermaid.tsx +401 -0
  172. package/src/client/open-when-sized.ts +43 -0
  173. package/src/client/open-with-settings.tsx +135 -0
  174. package/src/client/open-with.ts +221 -0
  175. package/src/client/openpath-intercept.ts +62 -0
  176. package/src/client/paths.ts +47 -0
  177. package/src/client/pdf-types.ts +4 -0
  178. package/src/client/plugin-settings.ts +43 -0
  179. package/src/client/plugins-shared.ts +29 -0
  180. package/src/client/plugins-tabs.ts +87 -0
  181. package/src/client/plugins-viewers.ts +30 -0
  182. package/src/client/prefs.ts +207 -0
  183. package/src/client/produced-files.ts +91 -0
  184. package/src/client/selection-payload.ts +86 -0
  185. package/src/client/service.ts +838 -0
  186. package/src/client/settings-nav-icon.ts +45 -0
  187. package/src/client/shell-presets.ts +83 -0
  188. package/src/client/sidebar.module.css +2577 -0
  189. package/src/client/sidechat-transcript.ts +321 -0
  190. package/src/client/split-pane.tsx +322 -0
  191. package/src/client/state.ts +1175 -0
  192. package/src/client/subagent-detect.ts +137 -0
  193. package/src/client/subagent-jobs.ts +161 -0
  194. package/src/client/tab-content-memo.ts +46 -0
  195. package/src/client/terminal-font.ts +28 -0
  196. package/src/client/theme.ts +110 -0
  197. package/src/client/titlebar-strip.ts +37 -0
  198. package/src/client/upload.ts +187 -0
  199. package/src/client/wco.ts +120 -0
  200. package/src/config.ts +155 -0
  201. package/src/context-types.ts +610 -0
  202. package/src/fs-operations.ts +99 -0
  203. package/src/fs-search.ts +86 -0
  204. package/src/fs-tree.ts +158 -0
  205. package/src/git.ts +244 -0
  206. package/src/html-route.ts +106 -0
  207. package/src/index.ts +1132 -0
  208. package/src/invariant.ts +32 -0
  209. package/src/jobs-routes.ts +257 -0
  210. package/src/open-external.ts +90 -0
  211. package/src/prefs-shared.ts +268 -0
  212. package/src/pty-deps.ts +240 -0
  213. package/src/pty-manager.ts +376 -0
  214. package/src/sidechat-core.ts +468 -0
  215. package/src/sidechat-routes.ts +344 -0
  216. package/src/subagent-activity.ts +91 -0
  217. package/src/subagent-live-route.ts +93 -0
  218. package/src/tools.ts +479 -0
  219. package/src/trust-fence.ts +77 -0
  220. package/src/wire.ts +100 -0
@@ -0,0 +1,168 @@
1
+ import type { IPty } from 'node-pty';
2
+ import { type NodePtyModule } from './pty-deps.ts';
3
+ /**
4
+ * Restore the executable bit pnpm strips from node-pty's prebuilt
5
+ * spawn-helper (the macOS helper that forks and sets up the pty). Without it
6
+ * every spawn fails with `posix_spawnp failed`. Idempotent; mirrors
7
+ * @deepseek-ai/dsh-terminal-bash's ensure-spawn-helper postinstall, run at
8
+ * plugin activation so link-installed deployments get the fix too.
9
+ */
10
+ export declare function ensureSpawnHelper(): void;
11
+ /** One live terminal. */
12
+ export interface SidebarPty {
13
+ /** `${sessionId}:${tabId}` registry key. */
14
+ key: string;
15
+ sessionId: string;
16
+ tabId: string;
17
+ /** The working directory the process was SPAWNED with (a reconnect that
18
+ * resolves a different authoritative cwd respawns instead of reusing —
19
+ * the page-load hydrate race can attach the real cwd after the first
20
+ * connect, and a shell in the wrong directory must not linger). */
21
+ cwd: string;
22
+ pty: IPty;
23
+ /** Output accumulated since spawn (bounded; head dropped when over the limit). */
24
+ transcript: string;
25
+ /** Whether the top-level process exited (transcript stays replayable). */
26
+ exited: boolean;
27
+ exitCode?: number | null;
28
+ }
29
+ /**
30
+ * The terminal registry. `maxPerSession` bounds concurrent processes per
31
+ * conversation (the client caps tabs at the same number).
32
+ *
33
+ * Lifecycle of a UI-tab pty when its WebSocket drops:
34
+ * - **Close frame** (`{type:'close'}`): the user closed the tab → schedule a
35
+ * 0-ms close (quota released immediately).
36
+ * - **Park frame** (`{type:'park'}`): the user switched to another
37
+ * conversation; the tab is still open in its session's persisted state but
38
+ * its view unmounted → mark the pty as parked (no auto-close countdown).
39
+ * The pty stays alive until the user switches back (a reconnecting view
40
+ * calls `open()` which clears the parked state) or the tab is later closed
41
+ * (a `{type:'close'}` frame from a fresh connection). Without `park`, a
42
+ * bare socket drop would start the reconnect-grace countdown and kill the
43
+ * shell after `reconnectGraceMs` — wrong for a session switch, where the
44
+ * user is still actively using the app, just in another conversation.
45
+ * - **Bare socket drop** (no frame): page refresh, crash, plugin teardown →
46
+ * schedule a close after `reconnectGraceMs` so a quick reconnect reattaches
47
+ * the same shell.
48
+ */
49
+ export declare class PtyManager {
50
+ private readonly shell;
51
+ private readonly maxPerSession;
52
+ private readonly shellArgs;
53
+ /** The loaded node-pty module (injected so a broken install degrades instead of crashing the plugin). */
54
+ private readonly nodePty;
55
+ private readonly sessions;
56
+ private readonly pendingCloses;
57
+ /** Tabs whose view unmounted because the user switched conversations — the
58
+ * tab is still open in its session's state, so the pty must NOT enter the
59
+ * reconnect-grace countdown. Cleared by `cancelClose` (a reconnecting
60
+ * view's `open()` cancels it) or by `scheduleClose` (an explicit close
61
+ * frame still kills a parked pty). */
62
+ private readonly parked;
63
+ constructor(shell: string, maxPerSession: number, shellArgs?: string[],
64
+ /** The loaded node-pty module (injected so a broken install degrades instead of crashing the plugin). */
65
+ nodePty?: NodePtyModule);
66
+ /** All live terminal keys of one session. */
67
+ keysOf(sessionId: string): string[];
68
+ /**
69
+ * Open (or reuse) the terminal for a session/tab key. A handle whose
70
+ * process already exited is replaced with a fresh spawn (reconnecting a
71
+ * dead terminal must yield a live shell, not an input sink), and so is a
72
+ * live handle whose spawn cwd differs from the now-authoritative one (the
73
+ * first connect of a page load can arrive before the session hydrates, so
74
+ * it fell back to the process cwd — reconnecting with the real cwd must
75
+ * restart the shell in the right directory). Reopening also cancels any
76
+ * pending scheduled close (a reconnect within the grace window keeps the
77
+ * process alive).
78
+ * @param sessionId - conversation id.
79
+ * @param tabId - client tab id.
80
+ * @param cwd - initial working directory (the session's cwd).
81
+ * @param cols - initial terminal width.
82
+ * @param rows - initial terminal height.
83
+ * @returns the live handle.
84
+ * @throws {SidebarError} pty-error when the per-session cap is reached.
85
+ */
86
+ open(sessionId: string, tabId: string, cwd: string, cols: number, rows: number, shell?: string, shellArgs?: string[]): SidebarPty;
87
+ /**
88
+ * Schedule the terminal's destruction after `delayMs`. A tab close sends
89
+ * delay 0 (release the quota immediately); a bare socket drop (refresh,
90
+ * crash) uses the grace period so a quick reconnect keeps the process.
91
+ * `open()` cancels any pending close. Clears the parked state — an explicit
92
+ * close frame on a parked pty (the user switched back and closed the tab)
93
+ * still kills it.
94
+ */
95
+ scheduleClose(key: string, delayMs: number): void;
96
+ /**
97
+ * Park a terminal: the owning tab's view unmounted because the user
98
+ * switched to another conversation, but the tab is still open in its
99
+ * session's persisted state. Cancels any pending grace close and marks
100
+ * the pty so the host's `ws.on('close')` handler does NOT start the
101
+ * reconnect-grace countdown — the pty stays alive until the user switches
102
+ * back (a reconnecting view's `open()` clears this) or explicitly closes
103
+ * the tab (a `{type:'close'}` frame's `scheduleClose` clears this).
104
+ */
105
+ park(key: string): void;
106
+ /** Whether this pty was parked (its view unmounted for a session switch). */
107
+ isParked(key: string): boolean;
108
+ /** Cancel a pending scheduled close (the terminal is being reopened).
109
+ * Also clears the parked state — a reconnecting view reattaches a parked
110
+ * pty and resumes normal lifecycle. */
111
+ cancelClose(key: string): void;
112
+ /** Resolve a live handle by key, or undefined. */
113
+ get(key: string): SidebarPty | undefined;
114
+ /** Close a terminal and drop its state (the owning tab was closed). */
115
+ close(key: string): void;
116
+ /** Close every terminal (plugin teardown). */
117
+ disposeAll(): void;
118
+ }
119
+ /**
120
+ * Inputs for {@link defaultShell} resolution. Every field is optional and
121
+ * defaults to the live process, which keeps the no-argument call sites
122
+ * working while tests (and exotic embedders) can pin the platform, the
123
+ * environment, and the existence probe independently — the Windows chain
124
+ * never executes on the ubuntu CI runners, so it is only testable through
125
+ * these injection points.
126
+ */
127
+ export interface ShellResolutionOptions {
128
+ /** Platform override (defaults to `process.platform`). */
129
+ platform?: NodeJS.Platform;
130
+ /** Environment override; the resolver only reads SHELL, DSH_SIDEBAR_SHELL, PATH, ProgramW6432, ProgramFiles, LOCALAPPDATA. */
131
+ env?: NodeJS.ProcessEnv;
132
+ /** Explicitly configured shell (the `shell` config field); wins over every automatic source. Empty means unset. */
133
+ explicit?: string;
134
+ /** File-existence probe override (defaults to `existsSync`). */
135
+ exists?: (path: string) => boolean;
136
+ }
137
+ /**
138
+ * The interactive shell for this platform, resolved like a terminal
139
+ * emulator: an explicitly configured shell (the `shell` config field) wins,
140
+ * then `$SHELL` on POSIX (deployment override), then the account's login
141
+ * shell from passwd, then `/bin/bash`. The passwd step matters because
142
+ * service managers and container inits often start dsh without `SHELL`, and
143
+ * the tab should still open the user's login shell (e.g. zsh) instead of
144
+ * silently degrading to bash.
145
+ *
146
+ * Windows previously short-circuited to `powershell.exe` (the inbox 5.1)
147
+ * before any resolution, so PowerShell 7 users always got a legacy shell
148
+ * without `??`/`?.`/ternary and with poor ANSI/UTF-8 defaults. The Windows
149
+ * chain is now: explicit shell → `DSH_SIDEBAR_SHELL` env override → first
150
+ * `pwsh.exe` found on PATH or in a known install directory → the 5.1
151
+ * fallback (machines without PowerShell 7 keep working).
152
+ */
153
+ export declare function defaultShell(options?: ShellResolutionOptions): string;
154
+ /**
155
+ * A short display name for a shell executable, used as the terminal tab
156
+ * title. `/bin/zsh` → `zsh`, `C:\...\powershell.exe` → `powershell`.
157
+ * Falls back to the raw value when no basename can be derived.
158
+ */
159
+ export declare function shellDisplayName(shell: string): string;
160
+ /**
161
+ * Spawn arguments that make the shell behave like a terminal-emulator tab:
162
+ * POSIX shells start as login shells (`-l`) so they read the profile files
163
+ * (`~/.profile`, `~/.zprofile`); Windows PowerShell takes no login flag.
164
+ *
165
+ * When explicit `configured` args are supplied they REPLACE the platform
166
+ * defaults entirely, giving deployments full control over shell startup.
167
+ */
168
+ export declare function shellSpawnArgs(configured?: string[]): string[];
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Pure side-conversation ("Side Chat") logic shared by the host routes and
3
+ * the client tab. Framework-free (no React, no Node) so both halves and the
4
+ * node test environment can import it.
5
+ *
6
+ * A side thread is a child session the plugin creates ITSELF with a custom
7
+ * seed — the parent session's FULL event log up to the click moment
8
+ * (completed turns, the unanswered user message, and — when the parent is
9
+ * mid-turn — the in-progress assistant output and tool activity). The log
10
+ * model forbids open-turn seeds, so an in-flight parent turn is copied
11
+ * verbatim and CLOSED with synthetic `step/end` + `turn/end{reason:
12
+ * 'interrupted'}` events: the child sees the partial turn as honestly
13
+ * frozen ("cut off"), never as a completed answer. The one case that cannot
14
+ * be closed honestly — a tool call still executing (no `tool/result` yet;
15
+ * providers reject dangling assistant calls) — falls back to cutting before
16
+ * the open turn and carrying the partial content as a structured text
17
+ * snapshot inside the boundary prompt.
18
+ */
19
+ import type { SidebarHistoryEntry, SidebarSessionSummary } from './context-types.ts';
20
+ /** The durable thread-label prefix (also the row filter in the client list). */
21
+ export declare const SIDE_LABEL_PREFIX = "Side: ";
22
+ /** The pinned label of a freshly created thread that no prompt has reached
23
+ * yet (Codex-style immediate create: the tab opens an EMPTY thread, the
24
+ * first composer message carries the boundary and earns the real label).
25
+ * The client renders it localized; the prefix keeps the row filter honest. */
26
+ export declare const SIDE_NEW_THREAD_TITLE = "Side: New thread";
27
+ /** Maximum code points kept in a durable thread label (matches subagent labels). */
28
+ export declare const LABEL_MAX_CHARS = 48;
29
+ /** The boundary message's opening line — the transcript mapping drops user
30
+ * rows starting with it (same first line as dsh-sidechain's boundary, so
31
+ * the two plugins' threads render consistently in either UI). */
32
+ export declare const SIDE_BOUNDARY_PREFIX = "Side conversation boundary";
33
+ /** The plugin identity stamped on the source of context-injection messages
34
+ * (boundary prompt + parked snapshot), so the transcript recognizes them
35
+ * structurally — not by text prefix. */
36
+ export declare const SIDE_INJECTION_PLUGIN = "dsh-better-sidebar";
37
+ /**
38
+ * The boundary prompt delivered as the thread's first user message: the
39
+ * inherited seed is reference context only, never active instruction.
40
+ * Model-facing contract — change only with intent, tests pin the sentences.
41
+ */
42
+ export declare const SIDE_BOUNDARY_PROMPT = "Side conversation boundary.\n\nEverything before this boundary is inherited history from the parent session: its completed turns, its pending question, and \u2014 if the parent was mid-turn \u2014 its in-progress output frozen at the moment this side conversation started. It is reference context only. It is not your current task.\n\nDo not continue, execute, or complete any instructions, plans, tool calls, approvals, edits, or requests from before this boundary. Only messages submitted after this boundary are active user instructions for this side conversation.\n\nMode: this is a continuable side conversation. Your answers stay in this side thread and are viewed in the side panel; they are never delivered into the parent session.";
43
+ /** One seed event (structural mirror of the durable SessionEvent). The
44
+ * envelope fields are preserved verbatim: surface-eligible events
45
+ * (user/message, assistant/message, tool/result) REQUIRE the `surfaceOp`
46
+ * marker (and may carry `sourceEventSeqs`) — the seed validator rejects
47
+ * them without it. */
48
+ export interface SeedEvent {
49
+ type: string;
50
+ seq: number;
51
+ time: number;
52
+ data: Record<string, unknown>;
53
+ /** Surface marker of message-producing events ('append' | replace op). */
54
+ surfaceOp?: unknown;
55
+ /** Seq numbers of earlier events this event cites as sources. */
56
+ sourceEventSeqs?: unknown;
57
+ /** Reader-skip marker of purely informational events. */
58
+ ignorable?: true;
59
+ }
60
+ /** The minimal structural face of a session-log event this module reads
61
+ * (loose enough to accept both the host's real SessionEvent and the
62
+ * client's SidebarSessionEvent mirror). */
63
+ export interface SidechatLogEvent {
64
+ type: string;
65
+ seq: number;
66
+ time: number;
67
+ data: unknown;
68
+ }
69
+ /** The result of cutting a parent log into a side-thread inheritance. */
70
+ export interface SidechatInheritance {
71
+ /** The child seed: contiguous from seq 0, ends outside any open turn. */
72
+ seed: SeedEvent[];
73
+ /**
74
+ * Structured snapshot of the parent's in-progress turn when it could NOT
75
+ * be included as events (a tool call was still executing); null when the
76
+ * seed already carries the whole picture.
77
+ */
78
+ snapshot: string | null;
79
+ }
80
+ /**
81
+ * Whether the open turn ending the log has a `tool/call` without its paired
82
+ * `tool/result` in the CURRENT open step. Providers reject dangling
83
+ * assistant calls, so such a turn cannot be honestly closed and the
84
+ * inheritance must fall back to the snapshot.
85
+ */
86
+ export declare function hasDanglingToolCall(events: readonly SidechatLogEvent[], turnStart: number): boolean;
87
+ /**
88
+ * Build the side-thread inheritance for one parent log: the full event log
89
+ * up to the click moment, honestly closed when it ends inside an open turn.
90
+ */
91
+ export declare function buildSidechatInheritance(events: readonly SidechatLogEvent[]): SidechatInheritance;
92
+ /** The seed half of {@link buildSidechatInheritance} (test convenience). */
93
+ export declare function sidechatSeed(events: readonly SidechatLogEvent[]): SeedEvent[];
94
+ /**
95
+ * Structured text snapshot of the parent's OPEN turn (from its `turn/start`
96
+ * to the log tail): the accumulated assistant/reasoning output verbatim
97
+ * (code blocks ride the raw deltas) and the tool activity — executed tools
98
+ * with their result text, the still-executing one marked. Returns null when
99
+ * there is no open turn or nothing to show.
100
+ */
101
+ export declare function buildOpenTurnSnapshot(events: readonly SidechatLogEvent[]): string | null;
102
+ /** One side-thread row in the client's thread list. */
103
+ export interface SideThreadRow {
104
+ id: string;
105
+ /** The durable thread title ('Side: …'). */
106
+ title: string;
107
+ /** Whether the thread's agent is currently running. */
108
+ running: boolean;
109
+ }
110
+ /**
111
+ * Derive the side threads of one parent session from the client session list:
112
+ * durable `origin: 'subagent'` children of the parent whose pinned title
113
+ * carries the thread label prefix (our creation path pins it via
114
+ * sessionTitle.rename; dsh-sidechain threads share the convention, so they
115
+ * are visible here too).
116
+ */
117
+ export declare function sideThreadRows(byId: Readonly<Record<string, SidebarSessionSummary>>, sessionId: string): SideThreadRow[];
118
+ /** Truncate + prefix a question into a durable thread label. */
119
+ export declare function sideLabel(question: string): string;
120
+ /**
121
+ * Whether the thread log already carries the side boundary message — i.e.
122
+ * the first prompt was delivered. Tolerant to the content shape (block
123
+ * array or bare string) and to inherited seed messages (only an OWN
124
+ * boundary message starts with the prefix; seed messages came from the
125
+ * parent's log, which never contains one).
126
+ */
127
+ export declare function boundaryDelivered(events: readonly SidechatLogEvent[]): boolean;
128
+ /**
129
+ * Whether a logged user/message is a CONTEXT INJECTION (the boundary prompt
130
+ * plus the parked in-progress snapshot) rather than a real user message.
131
+ * New threads deliver the injection via `agent.inject` stamped with a
132
+ * non-'user' source kind; threads created before that split carry
133
+ * boundary+question in ONE 'user' message, recognized by the boundary
134
+ * prefix. Both render as one collapsible injection row — never as a user
135
+ * bubble.
136
+ */
137
+ export declare function isContextInjectionMessage(data: Record<string, unknown>): boolean;
138
+ /** The info the thread header shows (live runtime state + agent identity). */
139
+ export interface SidechatThreadInfo {
140
+ /** A live agent drives the thread right now (false = cold/persisted). */
141
+ live: boolean;
142
+ /** Live lifecycle state; absent on cold threads. */
143
+ status?: 'idle' | 'running';
144
+ /** Provider route of the live agent. */
145
+ provider?: string;
146
+ /** Model id of the live agent. */
147
+ model?: string;
148
+ /** The recorded agent preset (live header, or persisted on cold reads). */
149
+ preset?: string;
150
+ }
151
+ /** The events a thread produced itself: everything after the LAST
152
+ * `session/end-seed` marker (the fork-seed boundary). */
153
+ export declare function threadOwnEvents(entries: readonly SidebarHistoryEntry[]): SidechatLogEvent[];
154
+ /**
155
+ * Whether the thread has at least one completed turn — the save-as-new-
156
+ * session precondition (`session.fork` refuses to fork before the first
157
+ * `turn/end`).
158
+ */
159
+ export declare function threadHasCompletedTurn(entries: readonly SidebarHistoryEntry[]): boolean;
160
+ /** Whether the thread ends with a user message that no completed turn
161
+ * answered yet — such a pending follow-up is NOT carried into the saved
162
+ * session (the fork cut is the last `turn/end`). */
163
+ export declare function threadTrailingPending(entries: readonly SidebarHistoryEntry[]): boolean;
164
+ /**
165
+ * The agent preset a session actually runs: newest `agent-preset/selected`
166
+ * event wins, else the creation header (mirror of the dsh-agent-presets
167
+ * resolveSessionPreset helper — replicated here to avoid a host dependency
168
+ * on that package).
169
+ */
170
+ export declare function resolvePresetId(header: {
171
+ agentPreset?: string;
172
+ }, events: readonly SidechatLogEvent[]): string | undefined;
@@ -0,0 +1,30 @@
1
+ import type { Context } from './context-types.ts';
2
+ import { type SidechatThreadInfo } from './sidechat-core.ts';
3
+ /** The five Side Chat routes of the sidebar API (wire method names). */
4
+ export interface SidechatRoutes {
5
+ /** Create a side thread child seeded with the parent's log up to now.
6
+ * `question` is optional: empty creates an EMPTY thread (Codex-style
7
+ * immediate create); the first `sidechat.prompt` then carries the
8
+ * boundary + snapshot and earns the thread its real label. */
9
+ 'sidechat.start'(payload: unknown): Promise<{
10
+ childId: string;
11
+ }>;
12
+ /** Deliver one follow-up message to a thread (live, or cold-resumed). */
13
+ 'sidechat.prompt'(payload: unknown): Promise<{
14
+ accepted: true;
15
+ }>;
16
+ /** Abort the thread's running turn (queued work is preserved). */
17
+ 'sidechat.cancel'(payload: unknown): Promise<{
18
+ accepted: true;
19
+ }>;
20
+ /** Release the thread's live agent (session and history stay persisted). */
21
+ 'sidechat.dispose'(payload: unknown): Promise<{
22
+ accepted: true;
23
+ }>;
24
+ /** Live state + agent identity for the thread header. */
25
+ 'sidechat.info'(payload: unknown): Promise<SidechatThreadInfo>;
26
+ }
27
+ /** Build the Side Chat routes (all optional services degrade to a wire
28
+ * error the tab surfaces inline). The record keys are the FULL wire method
29
+ * names the /sidebar/api dispatcher looks up (`api[method]`). */
30
+ export declare function buildSidechatApi(ctx: Context): SidechatRoutes;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Pure derivation of the compact LIVE line shown on a running subagent card:
3
+ * the last text output and the last tool call of the child's session events.
4
+ * The host batch route feeds raw session events into this parser; the client
5
+ * only receives the already-folded `LastActivity` map. Renders nothing
6
+ * itself — the SubagentView component turns this into the card's status
7
+ * lines. Kept framework-free so the parser is unit-testable in the node
8
+ * environment.
9
+ */
10
+ import type { SidebarSessionEvent } from './context-types.ts';
11
+ /**
12
+ * Extract the concatenated plain text of a content-block list (the durable
13
+ * `ContentBlock[]` shape, structurally: blocks with `type: 'text'` carry
14
+ * `text`; anything else — tool_use, image, … — contributes nothing).
15
+ * @param content - the raw `content` field of a message event.
16
+ * @returns the joined text, or undefined when the message carries no text.
17
+ */
18
+ export declare function contentText(content: unknown): string | undefined;
19
+ /** The live status of one subagent card (both fields optional). */
20
+ export interface LastActivity {
21
+ /** The latest assembled assistant text output in the tail. */
22
+ text?: string;
23
+ /** The latest tool call in the tail. */
24
+ tool?: {
25
+ name: string;
26
+ args: string;
27
+ };
28
+ }
29
+ /**
30
+ * Fold a session event log into the last text output + last tool call (each
31
+ * is the LAST occurrence in event order). Lifecycle events and raw
32
+ * `assistant/chunk` rows are ignored — the card shows what the subagent is
33
+ * doing right now, not its plumbing. The scan runs BACKWARD from the newest
34
+ * event and stops once both fields are found, so a long history costs only
35
+ * the recent tail in the common case.
36
+ * @param events - the session's append-only event log (oldest → newest).
37
+ * @param maxMessages - optional message-boundary window: only the tail's
38
+ * last `maxMessages` surface messages (`user/message`, `assistant/message`)
39
+ * and the events between them are considered, mirroring the old
40
+ * `subagents.history({ maxMessages })` window. Stale activity older than
41
+ * the window is never surfaced, and a long log is never scanned in full.
42
+ * @returns the last text and/or tool call; an empty object when the log has neither.
43
+ */
44
+ export declare function lastActivity(events: readonly SidebarSessionEvent[], maxMessages?: number): LastActivity;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * The live-preview route of the Subagent page ('subagents.live'): one
3
+ * request per refresh instead of N per-child `subagents.history` calls.
4
+ *
5
+ * The route takes the already-resolved topology root (`rootSessionId`),
6
+ * enumerates the whole descendant tree ONCE through the host subagent
7
+ * runtime (`ctx.get('subagents')` / `listDescendants`), keeps only rows the
8
+ * catalog reports running (`activity: 'running'` — the same gate the client
9
+ * renders cards on), and folds the newest text/tool activity from each
10
+ * child's attached session event log. It never touches DSH source and never
11
+ * reads the model's `job_output` cursor.
12
+ *
13
+ * Degradation contract:
14
+ * - `ctx.get('subagents')` missing or `listDescendants` failure → 503 (the
15
+ * Subagent page has no topology to show in such deployments anyway).
16
+ * - One child's events missing/corrupt → that child is skipped, the rest of
17
+ * the batch still returns.
18
+ */
19
+ import type { Context } from './context-types.ts';
20
+ import { type LastActivity } from './subagent-activity.ts';
21
+ /** The live-preview routes of the /sidebar JSON API. */
22
+ export interface SidebarSubagentLiveRoutes {
23
+ /**
24
+ * Fold one tree's running subagent histories into a compact live map.
25
+ * @param payload - `{ rootSessionId }`.
26
+ * @returns `{ live: Record<childSessionId, LastActivity> }`; children with
27
+ * no text/tool yet are omitted.
28
+ */
29
+ live(payload: unknown): Promise<{
30
+ live: Record<string, LastActivity>;
31
+ }>;
32
+ }
33
+ /**
34
+ * The recent-message window of the live preview: only the last 12 surface
35
+ * messages of a child's log are folded, matching the old per-card
36
+ * `subagents.history({ maxMessages: 12 })` window. Keeps stale tool calls
37
+ * out of the preview and bounds the backward scan per child.
38
+ */
39
+ export declare const LIVE_WINDOW_MESSAGES = 12;
40
+ /**
41
+ * Build the live-preview routes bound to the plugin context.
42
+ * @param ctx - host plugin context.
43
+ */
44
+ export declare function buildSubagentLiveApi(ctx: Context): SidebarSubagentLiveRoutes;
@@ -0,0 +1,31 @@
1
+ import type { Context } from './context-types.ts';
2
+ import { AgentPtyRegistry } from './agent-pty.ts';
3
+ /**
4
+ * Bound a string to a byte limit, marking truncation. Truncation never
5
+ * splits a multi-byte UTF-8 sequence: when the byte cap lands inside one,
6
+ * the walk-back retreats to the sequence's leading byte so the retained
7
+ * prefix decodes cleanly (a split would decode to U+FFFD).
8
+ * @internal exported for the unit tests, like {@link snapshotOf}.
9
+ */
10
+ export declare function boundBytes(text: string, maxBytes: number): {
11
+ text: string;
12
+ truncated: boolean;
13
+ };
14
+ /**
15
+ * Register the eight terminal tools against the host tool registry. The
16
+ * `resolveCwd` callback threads the live session cwd (authoritative from the
17
+ * session store, falling back to the process cwd) so a freshly-created
18
+ * terminal lands in the right directory without the model passing it.
19
+ * Every uuid-keyed tool first asserts the terminal belongs to the calling
20
+ * session (`registry.assertOwned`), so one agent can never reach another
21
+ * session's terminals.
22
+ * @param ctx - host plugin context (carries the tools service).
23
+ * @param registry - the agent-owned terminal registry.
24
+ * @param resolveCwd - live cwd resolver for one session id.
25
+ * @returns a disposer that unregisters all eight tools (the caller gates
26
+ * registration on the side-card setting and calls this to turn them off).
27
+ */
28
+ export declare function registerTools(ctx: Context, registry: AgentPtyRegistry, resolveCwd: (sessionId: string) => string, readShellOverrides: () => {
29
+ shell?: string;
30
+ shellArgs?: string[];
31
+ }): () => void;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Browser-trust fence for the sidebar routes, behaviorally identical to the
3
+ * /api gateway's fence in @deepseek-ai/dsh-client-connection
4
+ * (src/api-request-trust.ts + src/loopback-hostname.ts, BSD-3-Clause,
5
+ * copied here because the package does not export these helpers and the
6
+ * plugin must not depend on its internals). Host-header loopback or a
7
+ * configured trusted authority passes; cross-site browser markers refuse.
8
+ * This is a DNS-rebinding / cross-site defense, not authentication.
9
+ */
10
+ import type { IncomingHttpHeaders } from 'node:http';
11
+ /** The request facts the fence reads (structural subset of IncomingMessage). */
12
+ interface ApiTrustRequest {
13
+ headers: IncomingHttpHeaders;
14
+ }
15
+ /** Whether a normalized URL hostname names the local loopback authority. */
16
+ export declare function isLoopbackHostname(hostname: string): boolean;
17
+ /**
18
+ * Decide whether one sidebar request may reach the plugin routes.
19
+ * @param request - node HTTP request facts (headers).
20
+ * @param trustedHosts - non-loopback authorities this deployment serves.
21
+ * @returns true when the Host is ours (loopback or trusted) and browser markers are same-origin.
22
+ */
23
+ export declare function isTrustedApiRequest(request: ApiTrustRequest, trustedHosts: readonly string[]): boolean;
24
+ export {};
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Wire helpers for the /sidebar JSON API: bounded body reading, response
3
+ * writing, and the shared error envelope. Every API method returns
4
+ * `{ok: true, value}` on success and `{ok: false, error: {code, message}}`
5
+ * (HTTP 4xx/5xx matching the code) on failure.
6
+ */
7
+ import type { SidebarHttpRequest, SidebarHttpResponse } from './context-types.ts';
8
+ /** Machine-readable error codes of the sidebar API. */
9
+ export type SidebarErrorCode = 'bad-request' | 'not-found' | 'forbidden' | 'method-error' | 'too-large' | 'fs-error' | 'git-error' | 'pty-error' | 'pty-deps-missing' | 'job-error' | 'sidechat-error' | 'subagents-unavailable' | 'settings-rejected' | 'settings-conflict' | 'internal';
10
+ /** One API failure with its wire code and HTTP status. */
11
+ export declare class SidebarError extends Error {
12
+ readonly code: SidebarErrorCode;
13
+ readonly status: number;
14
+ constructor(code: SidebarErrorCode, message: string, status?: number);
15
+ }
16
+ /** Success envelope of one API method. */
17
+ export interface SidebarOk<T> {
18
+ ok: true;
19
+ value: T;
20
+ }
21
+ /** Failure envelope of one API method. */
22
+ export interface SidebarErr {
23
+ ok: false;
24
+ error: {
25
+ code: SidebarErrorCode;
26
+ message: string;
27
+ };
28
+ }
29
+ /** Read and parse the JSON request body (bounded; malformed → bad-request). */
30
+ export declare function readJsonBody(req: SidebarHttpRequest): Promise<unknown>;
31
+ /** Write a JSON response with the given status. */
32
+ export declare function writeJson(res: SidebarHttpResponse, status: number, body: unknown): void;
33
+ /** Write the success envelope. */
34
+ export declare function writeOk(res: SidebarHttpResponse, value: unknown): void;
35
+ /** Write the failure envelope for any thrown value (unknown → internal 500). */
36
+ export declare function writeError(res: SidebarHttpResponse, error: unknown): void;
37
+ /** Narrow an unknown payload value to a string, else throw bad-request. */
38
+ export declare function requireString(payload: unknown, key: string): string;