ompchamber 0.7.0 → 1.0.0

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 (165) hide show
  1. package/dist/client/index.html +1 -1
  2. package/dist/client/static/css/index.e880517fcd.css +2 -0
  3. package/dist/client/static/js/async/{3y.4c2596f64c.js → 3y.71f2ccf3b6.js} +2 -2
  4. package/dist/client/static/js/async/{xw.ae67ba06fd.js → 5b.0d6ae50fe8.js} +10 -10
  5. package/dist/client/static/js/async/5bh.8b2363424f.js +1 -0
  6. package/dist/client/static/js/async/{vy.bbe114c4e0.js → bi.09688ba75c.js} +1 -2
  7. package/dist/client/static/js/async/c.797d933882.js +1 -0
  8. package/dist/client/static/js/async/{dc.6dc4b0a04b.js → dc.856fda6560.js} +2 -2
  9. package/dist/client/static/js/async/g.a06ca3b3e9.js +2 -0
  10. package/dist/client/static/js/async/g.a06ca3b3e9.js.LICENSE.txt +11 -0
  11. package/dist/client/static/js/async/{m.4b29d34f93.js → m.c038c71b9f.js} +2 -2
  12. package/dist/client/static/js/async/ma.2c261b04dd.js +2 -0
  13. package/dist/client/static/js/async/ms.98960af4eb.js +67 -0
  14. package/dist/client/static/js/async/nam.b5663e919f.js +10 -0
  15. package/dist/client/static/js/async/nam.b5663e919f.js.LICENSE.txt +11 -0
  16. package/dist/client/static/js/async/ot.84c71b308f.js +2 -0
  17. package/dist/client/static/js/async/ot.84c71b308f.js.LICENSE.txt +11 -0
  18. package/dist/client/static/js/async/{q.542c184fec.js → q.23af2a358f.js} +3 -3
  19. package/dist/client/static/js/async/{s.413c2a4683.js → s.222ee974f6.js} +2 -2
  20. package/dist/client/static/js/async/t.4dca0e2ba8.js +1 -0
  21. package/dist/client/static/js/async/uf.46ad6209fa.js +2 -0
  22. package/dist/client/static/js/async/xn.6360379f71.js +2 -0
  23. package/dist/client/static/js/async/xnl.feb0021e50.js +2 -0
  24. package/dist/client/static/js/async/xnl.feb0021e50.js.LICENSE.txt +12 -0
  25. package/dist/client/static/js/index.0de15c5e17.js +33 -0
  26. package/dist/client/static/js/{uy.2e8929ed0c.js → jp.50817896f3.js} +6 -6
  27. package/package.json +2 -1
  28. package/src/client/components/common/MarkdownRenderer.tsx +69 -27
  29. package/src/client/components/common/code-editor/history.test.ts +118 -0
  30. package/src/client/components/common/code-editor/history.ts +101 -0
  31. package/src/client/components/common/code-editor/index.tsx +56 -66
  32. package/src/client/components/common/code-surface/index.tsx +84 -11
  33. package/src/client/components/common/code-surface/measure.ts +66 -17
  34. package/src/client/components/common/diagram-viewer/index.tsx +258 -0
  35. package/src/client/components/common/diagram-viewer/read-block.ts +53 -0
  36. package/src/client/components/layout/session-sidebar/CategoryItem.tsx +2 -1
  37. package/src/client/components/layout/session-sidebar/SessionItem.tsx +11 -4
  38. package/src/client/components/layout/session-sidebar/SessionList.tsx +1 -1
  39. package/src/client/components/mobile/mobile-session-sidebar/Item.tsx +1 -1
  40. package/src/client/components/mobile/mobile-session-sidebar/List.tsx +1 -1
  41. package/src/client/components/mobile/mobile-session-sidebar/SessionRow.tsx +2 -2
  42. package/src/client/components/settings/LazyModal.tsx +1 -1
  43. package/src/client/components/settings/Modal.tsx +2 -1
  44. package/src/client/components/workspace/chat-timeline/ComposerDock.tsx +11 -4
  45. package/src/client/components/workspace/chat-timeline/QueueList.tsx +18 -14
  46. package/src/client/components/workspace/chat-timeline/ToolCallCard.tsx +8 -1
  47. package/src/client/components/workspace/chat-timeline/chat-input/index.tsx +11 -52
  48. package/src/client/components/workspace/chat-timeline/chat-input/selection.ts +57 -0
  49. package/src/client/components/workspace/chat-timeline/index.tsx +124 -108
  50. package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/FrameControl.tsx +172 -0
  51. package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/QuestionBlock.tsx +155 -0
  52. package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/drafts.ts +113 -0
  53. package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/index.tsx +92 -0
  54. package/src/client/components/workspace/chat-timeline/tool-renderers/{ask-dialog → extension-dialog}/Footer.tsx +3 -3
  55. package/src/client/components/workspace/chat-timeline/tool-renderers/{ask-dialog → extension-dialog}/Header.tsx +3 -3
  56. package/src/client/components/workspace/chat-timeline/tool-renderers/{ask-dialog → extension-dialog}/Lazy.tsx +8 -8
  57. package/src/client/components/workspace/chat-timeline/tool-renderers/{ask-dialog → extension-dialog}/SelectBody.tsx +3 -3
  58. package/src/client/components/workspace/chat-timeline/tool-renderers/{ask-dialog → extension-dialog}/index.tsx +16 -10
  59. package/src/client/components/workspace/chat-timeline/tool-renderers/index.tsx +2 -2
  60. package/src/client/components/workspace/file-explorer/index.tsx +18 -5
  61. package/src/client/components/workspace/git-panel/BranchToolbar.tsx +2 -2
  62. package/src/client/components/workspace/git-panel/index.tsx +5 -1
  63. package/src/client/components/workspace/terminal-panel/Header.tsx +51 -19
  64. package/src/client/components/workspace/terminal-panel/RealtimeXtermView.tsx +86 -282
  65. package/src/client/components/workspace/terminal-panel/index.tsx +76 -50
  66. package/src/client/components/workspace/terminal-panel/mount.ts +275 -0
  67. package/src/client/data/theme/terminal.ts +27 -29
  68. package/src/client/hooks/chat/omp/session-list.tsx +23 -2
  69. package/src/client/hooks/chat/omp/session-sidebar-controller.ts +1 -1
  70. package/src/client/hooks/chat/timeline/actions.ts +23 -34
  71. package/src/client/hooks/chat/timeline/ask-frames.ts +102 -0
  72. package/src/client/hooks/chat/timeline/extension-dialog.ts +12 -9
  73. package/src/client/hooks/chat/timeline/index.ts +18 -28
  74. package/src/client/hooks/chat/timeline/prompt-text.ts +35 -0
  75. package/src/client/hooks/chat/timeline/queue.ts +110 -46
  76. package/src/client/hooks/chat/timeline/send.ts +13 -27
  77. package/src/client/hooks/chat/timeline/session-load.ts +48 -106
  78. package/src/client/hooks/chat/timeline/session-pagination.ts +144 -0
  79. package/src/client/hooks/editor/use-code-window.ts +118 -0
  80. package/src/client/hooks/ui/input-required-alert.ts +43 -0
  81. package/src/client/hooks/ui/notification-sound.ts +65 -0
  82. package/src/client/hooks/ui/theme.ts +22 -6
  83. package/src/client/hooks/workspace/terminal/index.ts +217 -0
  84. package/src/client/hooks/workspace/terminal/socket.ts +227 -0
  85. package/src/client/tailwind.css +38 -12
  86. package/src/server/db.server.ts +19 -0
  87. package/src/server/lib/fs/git-sync.ts +191 -0
  88. package/src/server/lib/omp/rpc/manager.ts +72 -6
  89. package/src/server/lib/omp/rpc/session-commands.ts +6 -0
  90. package/src/server/lib/omp/rpc/session-registry.ts +18 -0
  91. package/src/server/lib/omp/session/sidebar-data.server.ts +10 -4
  92. package/src/server/lib/queue/delivery.server.ts +114 -0
  93. package/src/server/lib/queue/schema-migration.server.ts +50 -0
  94. package/src/server/lib/queue/store.server.ts +185 -0
  95. package/src/server/lib/terminal/foreground.ts +73 -0
  96. package/src/server/lib/terminal/history.test.ts +118 -0
  97. package/src/server/lib/terminal/history.ts +205 -0
  98. package/src/server/lib/terminal/runtime.server.ts +338 -0
  99. package/src/server/lib/terminal/session.server.ts +215 -0
  100. package/src/server/lib/terminal/shell.test.ts +97 -0
  101. package/src/server/lib/terminal/shell.ts +103 -0
  102. package/src/server/routes/fs/git.ts +22 -12
  103. package/src/server/routes/index.ts +2 -1
  104. package/src/server/routes/sessions/index.ts +11 -3
  105. package/src/server/routes/sessions/queue.ts +121 -0
  106. package/src/server/routes/sessions/session.ts +1 -132
  107. package/src/server/routes/settings/commands.ts +1 -1
  108. package/src/server/routes/terminal/index.ts +2 -2
  109. package/src/server/routes/terminal/run.ts +8 -100
  110. package/src/server/routes/terminal/sessions.ts +24 -0
  111. package/src/server/routes/terminal/ws.ts +149 -0
  112. package/src/shared/lib/chat/ask-questions.test.ts +169 -0
  113. package/src/shared/lib/chat/ask-questions.ts +264 -0
  114. package/src/shared/lib/chat/omp/agent-events.test.ts +37 -1
  115. package/src/shared/lib/chat/omp/agent-events.ts +30 -0
  116. package/src/shared/lib/chat/timeline/omp-callbacks.test.ts +9 -1
  117. package/src/shared/lib/chat/timeline/omp-callbacks.ts +45 -8
  118. package/src/shared/lib/code/lazy-window.test.ts +161 -0
  119. package/src/shared/lib/code/lazy-window.ts +156 -0
  120. package/src/shared/lib/code/syntax-highlight.test.ts +50 -1
  121. package/src/shared/lib/code/syntax-highlight.ts +14 -1
  122. package/src/shared/lib/code/windowed-highlight.ts +63 -0
  123. package/src/shared/lib/markdown/mermaid.ts +8 -6
  124. package/src/shared/lib/omp/session/stream-state.server.ts +2 -3
  125. package/src/shared/lib/workspace/session-state/store.ts +1 -4
  126. package/src/shared/lib/workspace/terminal/protocol.ts +222 -0
  127. package/src/shared/types/omp/agent.ts +14 -2
  128. package/src/shared/types/terminal.ts +17 -15
  129. package/src/shared/types/workspace.ts +6 -2
  130. package/dist/client/static/css/index.51f68210a5.css +0 -2
  131. package/dist/client/static/js/async/5b.d88304bc13.js +0 -1
  132. package/dist/client/static/js/async/c.cd45b0ac45.js +0 -1
  133. package/dist/client/static/js/async/fw.6a51998a8e.js +0 -6
  134. package/dist/client/static/js/async/g.86e2256a70.js +0 -2
  135. package/dist/client/static/js/async/kc.bbdb35b771.js +0 -1
  136. package/dist/client/static/js/async/mw.ca1512563c.js +0 -10
  137. package/dist/client/static/js/async/rf.d626afc367.js +0 -2
  138. package/dist/client/static/js/async/t.cda4891526.js +0 -1
  139. package/dist/client/static/js/async/uf.24ca3d49c3.js +0 -2
  140. package/dist/client/static/js/async/uf.24ca3d49c3.js.LICENSE.txt +0 -6
  141. package/dist/client/static/js/async/v0.394ed37c6b.js +0 -2
  142. package/dist/client/static/js/async/v0.394ed37c6b.js.LICENSE.txt +0 -6
  143. package/dist/client/static/js/async/vy.bbe114c4e0.js.LICENSE.txt +0 -6
  144. package/dist/client/static/js/async/xn.afecebbbd9.js +0 -2
  145. package/dist/client/static/js/index.eccad0b70a.js +0 -33
  146. package/dist/client/static/js/index.eccad0b70a.js.LICENSE.txt +0 -4
  147. package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Ask.tsx +0 -205
  148. package/src/client/components/workspace/terminal-panel/InputBar.tsx +0 -82
  149. package/src/client/components/workspace/terminal-panel/LogEntry.tsx +0 -70
  150. package/src/client/hooks/workspace/terminal-output.ts +0 -66
  151. package/src/client/hooks/workspace/terminal.ts +0 -283
  152. package/src/server/lib/fs/terminal-cwd.ts +0 -49
  153. package/src/server/routes/terminal/stream.ts +0 -100
  154. /package/dist/client/static/js/async/{3y.4c2596f64c.js.LICENSE.txt → 3y.71f2ccf3b6.js.LICENSE.txt} +0 -0
  155. /package/dist/client/static/js/async/{g.86e2256a70.js.LICENSE.txt → 5b.0d6ae50fe8.js.LICENSE.txt} +0 -0
  156. /package/dist/client/static/js/async/{dc.6dc4b0a04b.js.LICENSE.txt → dc.856fda6560.js.LICENSE.txt} +0 -0
  157. /package/dist/client/static/js/async/{fw.6a51998a8e.js.LICENSE.txt → m.c038c71b9f.js.LICENSE.txt} +0 -0
  158. /package/dist/client/static/js/async/{m.4b29d34f93.js.LICENSE.txt → ma.2c261b04dd.js.LICENSE.txt} +0 -0
  159. /package/dist/client/static/js/async/{xn.afecebbbd9.js.LICENSE.txt → ms.98960af4eb.js.LICENSE.txt} +0 -0
  160. /package/dist/client/static/js/async/{mw.ca1512563c.js.LICENSE.txt → q.23af2a358f.js.LICENSE.txt} +0 -0
  161. /package/dist/client/static/js/async/{q.542c184fec.js.LICENSE.txt → s.222ee974f6.js.LICENSE.txt} +0 -0
  162. /package/dist/client/static/js/async/{rf.d626afc367.js.LICENSE.txt → uf.46ad6209fa.js.LICENSE.txt} +0 -0
  163. /package/dist/client/static/js/async/{s.413c2a4683.js.LICENSE.txt → xn.6360379f71.js.LICENSE.txt} +0 -0
  164. /package/dist/client/static/js/{async/xw.ae67ba06fd.js.LICENSE.txt → index.0de15c5e17.js.LICENSE.txt} +0 -0
  165. /package/dist/client/static/js/{uy.2e8929ed0c.js.LICENSE.txt → jp.50817896f3.js.LICENSE.txt} +0 -0
@@ -2,9 +2,9 @@
2
2
  import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
3
3
  import { AlertCircle, CheckCircle2, FileText, HelpCircle, ListFilter, Terminal } from 'lucide-preact';
4
4
  import type { ExtensionUiDialogRequest } from '@/client/hooks/chat/omp';
5
- import { AskDialogHeader } from '@/client/components/workspace/chat-timeline/tool-renderers/ask-dialog/Header';
6
- import { AskDialogSelectBody } from '@/client/components/workspace/chat-timeline/tool-renderers/ask-dialog/SelectBody';
7
- import { AskDialogFooter } from '@/client/components/workspace/chat-timeline/tool-renderers/ask-dialog/Footer';
5
+ import { ExtensionDialogHeader } from '@/client/components/workspace/chat-timeline/tool-renderers/extension-dialog/Header';
6
+ import { ExtensionDialogSelectBody } from '@/client/components/workspace/chat-timeline/tool-renderers/extension-dialog/SelectBody';
7
+ import { ExtensionDialogFooter } from '@/client/components/workspace/chat-timeline/tool-renderers/extension-dialog/Footer';
8
8
  import { MarkdownRenderer } from '@/client/components/common/MarkdownRenderer';
9
9
 
10
10
  export type ExtensionDialogResponse =
@@ -12,12 +12,18 @@ export type ExtensionDialogResponse =
12
12
  | { confirmed: boolean }
13
13
  | { cancelled: true };
14
14
 
15
- interface AskDialogProps {
15
+ interface ExtensionDialogProps {
16
16
  request: ExtensionUiDialogRequest;
17
17
  onRespond: (request: ExtensionUiDialogRequest, response: ExtensionDialogResponse) => void;
18
18
  }
19
19
 
20
- export function AskDialog({ request, onRespond }: AskDialogProps) {
20
+ /**
21
+ * Blocking extension dialog that has no card of its own to render in: an
22
+ * approval gate under `always-ask`, or a picker an extension raised. The `ask`
23
+ * tool's own dialogs render inline on its tool card instead (see ask-panel) —
24
+ * a modal would only ever show the question currently in flight.
25
+ */
26
+ export function ExtensionDialog({ request, onRespond }: ExtensionDialogProps) {
21
27
  const [value, setValue] = useState(request.method === 'editor' ? request.prefill ?? '' : '');
22
28
  const [selectedOption, setSelectedOption] = useState<string | null>(null);
23
29
  const [customValue, setCustomValue] = useState('');
@@ -202,7 +208,7 @@ export function AskDialog({ request, onRespond }: AskDialogProps) {
202
208
 
203
209
  return (
204
210
  <div
205
- className="ask-dialog-backdrop fixed inset-0 z-50 flex items-center justify-center p-4 sm:p-6"
211
+ className="extension-dialog-backdrop fixed inset-0 z-50 flex items-center justify-center p-4 sm:p-6"
206
212
  style={{
207
213
  background: 'color-mix(in srgb, var(--theme-ink) 55%, transparent)',
208
214
  backdropFilter: 'blur(8px)',
@@ -217,12 +223,12 @@ export function AskDialog({ request, onRespond }: AskDialogProps) {
217
223
  aria-modal="true"
218
224
  aria-label={request.title || methodMeta.label}
219
225
  tabIndex={-1}
220
- className="ask-dialog-panel w-full max-w-[560px] overflow-hidden rounded-2xl border border-ink/15 bg-paper shadow-2xl outline-none"
226
+ className="extension-dialog-panel w-full max-w-[560px] overflow-hidden rounded-2xl border border-ink/15 bg-paper shadow-2xl outline-none"
221
227
  style={{
222
228
  boxShadow: '0 24px 64px -8px color-mix(in srgb, var(--theme-ink) 28%, transparent)',
223
229
  }}
224
230
  >
225
- <AskDialogHeader
231
+ <ExtensionDialogHeader
226
232
  title={request.title}
227
233
  methodLabel={methodMeta.label}
228
234
  badge={methodMeta.badge}
@@ -240,7 +246,7 @@ export function AskDialog({ request, onRespond }: AskDialogProps) {
240
246
 
241
247
  {/* Select Options */}
242
248
  {request.method === 'select' && (
243
- <AskDialogSelectBody
249
+ <ExtensionDialogSelectBody
244
250
  options={options}
245
251
  optionDetails={optionDetails}
246
252
  selectedOption={selectedOption}
@@ -307,7 +313,7 @@ export function AskDialog({ request, onRespond }: AskDialogProps) {
307
313
  )}
308
314
  </div>
309
315
 
310
- <AskDialogFooter
316
+ <ExtensionDialogFooter
311
317
  method={request.method}
312
318
  optionsLength={options.length}
313
319
  selectedOption={selectedOption}
@@ -19,7 +19,7 @@ import { AstEdit } from '@/client/components/workspace/chat-timeline/tool-render
19
19
  import { Resolve } from '@/client/components/workspace/chat-timeline/tool-renderers/panels/Resolve';
20
20
  import { ManageSkill } from '@/client/components/workspace/chat-timeline/tool-renderers/panels/ManageSkill';
21
21
  import { SearchFs } from '@/client/components/workspace/chat-timeline/tool-renderers/panels/SearchFs';
22
- import { Ask } from '@/client/components/workspace/chat-timeline/tool-renderers/panels/Ask';
22
+ import { AskPanel } from '@/client/components/workspace/chat-timeline/tool-renderers/ask-panel';
23
23
  import { Think } from '@/client/components/workspace/chat-timeline/tool-renderers/panels/Think';
24
24
  import { Read } from '@/client/components/workspace/chat-timeline/tool-renderers/panels/Read';
25
25
  import { Edit } from '@/client/components/workspace/chat-timeline/tool-renderers/panels/Edit';
@@ -142,7 +142,7 @@ export function ToolDetailsPanel({ tool }: { tool: ToolCallData }): ReactNode {
142
142
  return <Memory tool={tool} />;
143
143
  }
144
144
  if (key === 'goal' || key === 'yield') return <Goal tool={tool} />;
145
- if (key === 'ask') return <Ask tool={tool} />;
145
+ if (key === 'ask') return <AskPanel tool={tool} />;
146
146
  if (key === 'think') return <Think tool={tool} />;
147
147
 
148
148
  // Dynamic MCP / custom tools — `mcp__<tool>` names and xd:// device writes
@@ -63,15 +63,28 @@ export function FileExplorer({ className = '', enabled = true, rootPath, onOpenF
63
63
  loadFiles();
64
64
  }, [refreshKey, rootPath, enabled, activeRepo, expandedPathsReady]);
65
65
 
66
- // Auto refresh: re-read the directory tree on a cadence so external changes
67
- // (terminal output, agent edits) surface without a manual refresh. Silent —
68
- // the loading spinner stays reserved for the user's own refresh button.
69
- usePanelRefresh(() => loadFiles({ silent: true }), enabled && expandedPathsReady);
66
+ // The listing and its git decorations describe the same working tree, so one
67
+ // refresh drives both. Re-reading only the tree left every folder dot stale:
68
+ // `useGitStatus` has no poll of its own in this panel, so a new/changed file
69
+ // appeared under a folder that still claimed a clean status until the user
70
+ // hit refresh or refocused the window.
71
+ const refreshListing = () => {
72
+ loadFiles({ silent: true });
73
+ refreshGitStatus();
74
+ };
75
+ // Auto refresh: re-read on a cadence so external changes (terminal output,
76
+ // agent edits) surface without a manual refresh. Silent — the loading
77
+ // spinner stays reserved for the user's own refresh button.
78
+ usePanelRefresh(refreshListing, enabled && expandedPathsReady);
70
79
  // AI edits land between poll ticks: re-read right after a file-mutating tool
71
80
  // (edit / write / ast_edit / bash) finishes, not up to 2s later.
72
- useFileMutationRefresh(() => loadFiles({ silent: true }), enabled && expandedPathsReady);
81
+ useFileMutationRefresh(refreshListing, enabled && expandedPathsReady);
73
82
 
74
83
  const loadChildren = (path: string) => {
84
+ // Expanding IS a directory load: fetch the children AND re-read git status,
85
+ // so a freshly listed row never renders bare next to a status map that
86
+ // still predates its change (the panel poll alone can lag a fresh edit).
87
+ refreshGitStatus();
75
88
  return fetch(listUrl(path))
76
89
  .then(r => r.json())
77
90
  .then(data => {
@@ -113,7 +113,7 @@ export function GitBranchToolbar({
113
113
  <button
114
114
  type="button"
115
115
  onClick={onSync}
116
- disabled={isBusy || (syncCount.ahead === 0 && syncCount.behind === 0)}
116
+ disabled={isBusy}
117
117
  title={
118
118
  syncCount.ahead > 0 && syncCount.behind > 0
119
119
  ? `Pull ${syncCount.behind} then push ${syncCount.ahead} commit(s)`
@@ -121,7 +121,7 @@ export function GitBranchToolbar({
121
121
  ? `Push ${syncCount.ahead} commit(s)`
122
122
  : syncCount.behind > 0
123
123
  ? `Pull ${syncCount.behind} commit(s)`
124
- : 'Up to date'
124
+ : 'Up to date — sync anyway'
125
125
  }
126
126
  className="flex items-center gap-1 p-1 rounded hover:text-ink hover:bg-ink/5 cursor-pointer transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
127
127
  >
@@ -34,6 +34,10 @@ export function GitPanel({ className = '', enabled = true, rootPath, refreshKey
34
34
  const params = new URLSearchParams();
35
35
  if (rootPath) params.set('root', rootPath);
36
36
  if (targetRepo && targetRepo !== '.') params.set('repo', targetRepo);
37
+ // The toolbar's ↑ahead ↓behind badge is what this asks for; it makes the
38
+ // loader refresh the branch's remote-tracking ref before counting, so the
39
+ // numbers describe origin rather than the last fetch.
40
+ params.set('sync', '1');
37
41
  params.set('t', String(Date.now()));
38
42
  fetcher.load(`/api/fs/git?${params.toString()}`);
39
43
  };
@@ -200,7 +204,7 @@ export function GitPanel({ className = '', enabled = true, rootPath, refreshKey
200
204
  if (!enabled || pollingRepos || rescanningRepos) return;
201
205
  setExtraRepos(repos);
202
206
  setRescanningRepos(true);
203
- const params = new URLSearchParams({ rescan: '1' });
207
+ const params = new URLSearchParams({ rescan: '1', sync: '1' });
204
208
  if (rootPath) params.set('root', rootPath);
205
209
  if (activeRepo !== '.') params.set('repo', activeRepo);
206
210
  params.set('t', String(Date.now()));
@@ -1,23 +1,46 @@
1
- import { Loader2, Terminal } from 'lucide-preact';
1
+ import { Loader2, RotateCw, Terminal } from 'lucide-preact';
2
2
  import { GitRepoDropdown } from '@/client/components/workspace/file-explorer/GitRepoDropdown';
3
+ import type { TerminalStatus } from '@/client/hooks/workspace/terminal';
3
4
 
4
5
  interface TerminalHeaderProps {
5
- isRunning: boolean;
6
+ status: TerminalStatus;
7
+ exitCode: number | null;
6
8
  rootPath?: string;
7
9
  activeRepo: string;
8
10
  onSelectRepo: (repo: string) => void;
9
- bunVersion?: string;
10
- nodeVersion?: string;
11
+ /** Working directory the shell actually runs in. */
12
+ cwd?: string;
13
+ shell?: string;
14
+ gitBranch?: string;
15
+ onRestart: () => void;
11
16
  }
12
17
 
18
+ const STATUS_LABEL: Record<TerminalStatus, string> = {
19
+ connecting: 'CONNECTING',
20
+ running: 'LIVE',
21
+ exited: 'EXITED',
22
+ error: 'ERROR',
23
+ };
24
+
25
+ const STATUS_CLASS: Record<TerminalStatus, string> = {
26
+ connecting: 'bg-warning/10 text-warning border-warning/20',
27
+ running: 'bg-success/10 text-success border-success/20',
28
+ exited: 'bg-ink/5 text-ink/60 border-ink/15',
29
+ error: 'bg-error/10 text-error border-error/20',
30
+ };
31
+
13
32
  export function TerminalHeader({
14
- isRunning,
33
+ status,
34
+ exitCode,
15
35
  rootPath,
16
36
  activeRepo,
17
37
  onSelectRepo,
18
- bunVersion,
19
- nodeVersion,
38
+ cwd,
39
+ shell,
40
+ gitBranch,
41
+ onRestart,
20
42
  }: TerminalHeaderProps) {
43
+ const shellName = shell ? shell.split('/').pop() : '';
21
44
  return (
22
45
  <div className="h-10 px-3 border-b border-ink/10 bg-paper flex items-center justify-between flex-shrink-0 select-none">
23
46
  {/* Left cluster: Title, Status, Repo */}
@@ -29,22 +52,31 @@ export function TerminalHeader({
29
52
  </span>
30
53
  </div>
31
54
 
32
- {isRunning && (
33
- <span className="inline-flex items-center space-x-1 px-1.5 py-0.5 rounded text-[10px] font-mono bg-warning/10 text-warning border border-warning/20 animate-pulse">
34
- <Loader2 size={10} className="animate-spin" />
35
- <span className="hidden xs:inline">RUNNING</span>
36
- </span>
37
- )}
55
+ <span
56
+ className={`inline-flex items-center space-x-1 px-1.5 py-0.5 rounded text-[10px] font-mono border ${STATUS_CLASS[status]}`}
57
+ title={status === 'exited' && exitCode !== null ? `Shell exited with code ${exitCode}` : undefined}
58
+ >
59
+ {status === 'connecting' && <Loader2 size={10} className="animate-spin" />}
60
+ <span>{status === 'exited' && exitCode !== null ? `EXIT ${exitCode}` : STATUS_LABEL[status]}</span>
61
+ </span>
38
62
 
39
63
  <GitRepoDropdown rootPath={rootPath} activeRepo={activeRepo} onSelectRepo={onSelectRepo} />
64
+
65
+ <button
66
+ type="button"
67
+ onClick={onRestart}
68
+ title="Kill this shell and start a new one"
69
+ className="p-1 rounded text-ink/50 hover:text-ink hover:bg-ink/5 transition-colors cursor-pointer flex-shrink-0"
70
+ >
71
+ <RotateCw size={12} />
72
+ </button>
40
73
  </div>
41
74
 
42
- {bunVersion && (
43
- <span className="hidden sm:flex items-center space-x-2 text-[10px] font-mono text-ink/40 flex-shrink-0">
44
- <span>Bun {bunVersion}</span>
45
- {nodeVersion && <span>Node {nodeVersion}</span>}
46
- </span>
47
- )}
75
+ <span className="hidden sm:flex items-center space-x-2 text-[10px] font-mono text-ink/40 flex-shrink-0 min-w-0">
76
+ {cwd && <span className="truncate max-w-[280px]" title={cwd}>{cwd}</span>}
77
+ {gitBranch && <span>{gitBranch}</span>}
78
+ {shellName && <span>{shellName}</span>}
79
+ </span>
48
80
  </div>
49
81
  );
50
82
  }