ompchamber 0.8.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 (79) hide show
  1. package/dist/client/index.html +1 -1
  2. package/dist/client/static/css/{index.5c0447cefe.css → index.e880517fcd.css} +1 -1
  3. package/dist/client/static/js/async/g.a06ca3b3e9.js +2 -0
  4. package/dist/client/static/js/async/{m.0896dab823.js → m.c038c71b9f.js} +2 -2
  5. package/dist/client/static/js/async/ms.98960af4eb.js +67 -0
  6. package/dist/client/static/js/async/ms.98960af4eb.js.LICENSE.txt +12 -0
  7. package/dist/client/static/js/async/ot.84c71b308f.js +2 -0
  8. package/dist/client/static/js/async/{x4.69ec69c61b.js.LICENSE.txt → ot.84c71b308f.js.LICENSE.txt} +5 -0
  9. package/dist/client/static/js/{index.dc5f5fabe8.js → index.0de15c5e17.js} +10 -10
  10. package/package.json +2 -1
  11. package/src/client/components/layout/session-sidebar/CategoryItem.tsx +1 -1
  12. package/src/client/components/layout/session-sidebar/SessionItem.tsx +1 -1
  13. package/src/client/components/layout/session-sidebar/SessionList.tsx +1 -1
  14. package/src/client/components/mobile/mobile-session-sidebar/Item.tsx +1 -1
  15. package/src/client/components/mobile/mobile-session-sidebar/List.tsx +1 -1
  16. package/src/client/components/mobile/mobile-session-sidebar/SessionRow.tsx +2 -2
  17. package/src/client/components/workspace/chat-timeline/ComposerDock.tsx +11 -4
  18. package/src/client/components/workspace/chat-timeline/QueueList.tsx +18 -14
  19. package/src/client/components/workspace/chat-timeline/index.tsx +4 -2
  20. package/src/client/components/workspace/git-panel/BranchToolbar.tsx +2 -2
  21. package/src/client/components/workspace/git-panel/index.tsx +5 -1
  22. package/src/client/components/workspace/terminal-panel/Header.tsx +51 -19
  23. package/src/client/components/workspace/terminal-panel/RealtimeXtermView.tsx +86 -282
  24. package/src/client/components/workspace/terminal-panel/index.tsx +76 -50
  25. package/src/client/components/workspace/terminal-panel/mount.ts +275 -0
  26. package/src/client/data/theme/terminal.ts +27 -29
  27. package/src/client/hooks/chat/omp/session-list.tsx +19 -2
  28. package/src/client/hooks/chat/omp/session-sidebar-controller.ts +1 -1
  29. package/src/client/hooks/chat/timeline/actions.ts +23 -25
  30. package/src/client/hooks/chat/timeline/index.ts +12 -20
  31. package/src/client/hooks/chat/timeline/queue.ts +110 -46
  32. package/src/client/hooks/workspace/terminal/index.ts +217 -0
  33. package/src/client/hooks/workspace/terminal/socket.ts +227 -0
  34. package/src/server/db.server.ts +19 -0
  35. package/src/server/lib/fs/git-sync.ts +191 -0
  36. package/src/server/lib/omp/rpc/manager.ts +58 -3
  37. package/src/server/lib/omp/rpc/session-commands.ts +6 -0
  38. package/src/server/lib/omp/session/sidebar-data.server.ts +3 -3
  39. package/src/server/lib/queue/delivery.server.ts +114 -0
  40. package/src/server/lib/queue/schema-migration.server.ts +50 -0
  41. package/src/server/lib/queue/store.server.ts +185 -0
  42. package/src/server/lib/terminal/foreground.ts +73 -0
  43. package/src/server/lib/terminal/history.test.ts +118 -0
  44. package/src/server/lib/terminal/history.ts +205 -0
  45. package/src/server/lib/terminal/runtime.server.ts +338 -0
  46. package/src/server/lib/terminal/session.server.ts +215 -0
  47. package/src/server/lib/terminal/shell.test.ts +97 -0
  48. package/src/server/lib/terminal/shell.ts +103 -0
  49. package/src/server/routes/fs/git.ts +22 -12
  50. package/src/server/routes/index.ts +2 -1
  51. package/src/server/routes/sessions/index.ts +11 -3
  52. package/src/server/routes/sessions/queue.ts +121 -0
  53. package/src/server/routes/sessions/session.ts +1 -132
  54. package/src/server/routes/settings/commands.ts +1 -1
  55. package/src/server/routes/terminal/index.ts +2 -2
  56. package/src/server/routes/terminal/run.ts +8 -100
  57. package/src/server/routes/terminal/sessions.ts +24 -0
  58. package/src/server/routes/terminal/ws.ts +149 -0
  59. package/src/shared/lib/chat/omp/agent-events.test.ts +37 -1
  60. package/src/shared/lib/chat/omp/agent-events.ts +30 -0
  61. package/src/shared/lib/chat/timeline/omp-callbacks.test.ts +9 -1
  62. package/src/shared/lib/chat/timeline/omp-callbacks.ts +45 -8
  63. package/src/shared/lib/omp/session/stream-state.server.ts +2 -3
  64. package/src/shared/lib/workspace/session-state/store.ts +1 -4
  65. package/src/shared/lib/workspace/terminal/protocol.ts +222 -0
  66. package/src/shared/types/omp/agent.ts +12 -0
  67. package/src/shared/types/terminal.ts +17 -15
  68. package/src/shared/types/workspace.ts +2 -2
  69. package/dist/client/static/js/async/g.d1c2fe09aa.js +0 -2
  70. package/dist/client/static/js/async/x4.69ec69c61b.js +0 -6
  71. package/src/client/components/workspace/terminal-panel/InputBar.tsx +0 -82
  72. package/src/client/components/workspace/terminal-panel/LogEntry.tsx +0 -70
  73. package/src/client/hooks/workspace/terminal-output.ts +0 -66
  74. package/src/client/hooks/workspace/terminal.ts +0 -283
  75. package/src/server/lib/fs/terminal-cwd.ts +0 -49
  76. package/src/server/routes/terminal/stream.ts +0 -100
  77. /package/dist/client/static/js/async/{g.d1c2fe09aa.js.LICENSE.txt → g.a06ca3b3e9.js.LICENSE.txt} +0 -0
  78. /package/dist/client/static/js/async/{m.0896dab823.js.LICENSE.txt → m.c038c71b9f.js.LICENSE.txt} +0 -0
  79. /package/dist/client/static/js/{index.dc5f5fabe8.js.LICENSE.txt → index.0de15c5e17.js.LICENSE.txt} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ompchamber",
3
- "version": "0.8.0",
3
+ "version": "1.0.0",
4
4
  "description": "Developer web console and diagnostic chamber for AI Oh-My-Pi — Elysia + Preact on Bun.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "Wijanarko Putra Rajeb <wijanarko.rajeb@gmail.com>",
@@ -51,6 +51,7 @@
51
51
  "@shikijs/themes": "^4.4.3",
52
52
  "@tailwindcss/typography": "^0.5.20",
53
53
  "@xterm/addon-fit": "^0.11.0",
54
+ "@xterm/addon-webgl": "^0.19.0",
54
55
  "@xterm/xterm": "^6.0.0",
55
56
  "dompurify": "^3.4.15",
56
57
  "elysia": "^1.4.30",
@@ -29,7 +29,7 @@ export function Category({
29
29
  onNewSessionForFolder: (id: number) => void;
30
30
  forceExpanded?: boolean;
31
31
  showArchived?: boolean;
32
- sessionStatus?: Record<string, 'stream' | 'finish' | 'abort' | 'error'>;
32
+ sessionStatus?: Record<string, 'stream' | 'finish' | 'abort'>;
33
33
  }) {
34
34
  const [isOpen, setIsOpen] = useState(folder.isExpanded || false);
35
35
  const [showMenu, setShowMenu] = useState(false);
@@ -6,7 +6,7 @@ export interface SessionItemProps {
6
6
  title: string;
7
7
  isActive?: boolean;
8
8
  isArchived?: boolean;
9
- status?: 'stream' | 'finish' | 'abort' | 'error';
9
+ status?: 'stream' | 'finish' | 'abort';
10
10
  /** The session's agent is blocked on a question until the user answers it. */
11
11
  awaitingInput?: boolean;
12
12
  onClick?: () => void;
@@ -6,7 +6,7 @@ interface SessionSidebarSessionListProps {
6
6
  activeSessionId: number | string | null;
7
7
  searchQuery: string;
8
8
  showArchived: boolean;
9
- sessionStatus: Record<string, 'stream' | 'finish' | 'abort' | 'error'>;
9
+ sessionStatus: Record<string, 'stream' | 'finish' | 'abort'>;
10
10
  isScrolling: boolean;
11
11
  onScroll: () => void;
12
12
  onSelectSession: (id: number | string) => void;
@@ -17,7 +17,7 @@ interface MobileSessionCategoryProps {
17
17
  isExpanded: boolean;
18
18
  onToggleExpand: () => void;
19
19
  showArchived?: boolean;
20
- sessionStatus?: Record<string, 'stream' | 'finish' | 'abort' | 'error'>;
20
+ sessionStatus?: Record<string, 'stream' | 'finish' | 'abort'>;
21
21
  }
22
22
 
23
23
  export function MobileSessionCategory({
@@ -7,7 +7,7 @@ interface MobileSessionListProps {
7
7
  activeSessionId: number | string | null;
8
8
  expandedFolders: Record<number, boolean>;
9
9
  showArchived: boolean;
10
- sessionStatus: Record<string, 'stream' | 'finish' | 'abort' | 'error'>;
10
+ sessionStatus: Record<string, 'stream' | 'finish' | 'abort'>;
11
11
  isScrolling: boolean;
12
12
  onScroll: () => void;
13
13
  onSelectSession: (id: number | string) => void;
@@ -5,7 +5,7 @@ import { useInlineRename } from '@/client/hooks/ui/inline-rename';
5
5
  export interface MobileSessionRowProps {
6
6
  session: SessionItemData;
7
7
  isActive: boolean;
8
- status?: 'stream' | 'finish' | 'abort' | 'error';
8
+ status?: 'stream' | 'finish' | 'abort';
9
9
  /** Relative age of the session, or null when no usable timestamp exists. */
10
10
  timeAgo: string | null;
11
11
  showTreeGlyph?: boolean;
@@ -64,7 +64,7 @@ export function MobileSessionRow({
64
64
  <div className="flex items-center space-x-1.5 min-w-0 pr-2">
65
65
  <span className="w-4 flex-shrink-0 flex items-center justify-center">
66
66
  {status === 'stream' && <Loader2 size={13} className="text-ink/50 animate-spin" />}
67
- {(status === 'finish' || status === 'abort' || status === 'error') && <Check size={13} className="text-ink/50" />}
67
+ {(status === 'finish' || status === 'abort') && <Check size={13} className="text-ink/50" />}
68
68
  </span>
69
69
  {showTreeGlyph && <span className="text-ink/40 text-xs flex-shrink-0 font-mono">&gt;</span>}
70
70
  <span className="text-xs truncate leading-snug">
@@ -13,7 +13,8 @@ interface ComposerDockProps {
13
13
  provider?: string;
14
14
  providerNames?: Record<string, string>;
15
15
  messageQueue: QueuedMessage[];
16
- setMessageQueue: Dispatch<SetStateAction<QueuedMessage[]>>;
16
+ onRemoveQueueItem: (id: string) => void;
17
+ onReorderQueue: (orderedIds: string[]) => void;
17
18
  onEditQueueItem: (item: QueuedMessage) => void;
18
19
  onSendNowQueueItem: (item: QueuedMessage) => void;
19
20
  steeringQueue: QueuedMessage[];
@@ -49,7 +50,8 @@ export function ComposerDock({
49
50
  provider,
50
51
  providerNames,
51
52
  messageQueue,
52
- setMessageQueue,
53
+ onRemoveQueueItem,
54
+ onReorderQueue,
53
55
  onEditQueueItem,
54
56
  onSendNowQueueItem,
55
57
  steeringQueue,
@@ -87,13 +89,18 @@ export function ComposerDock({
87
89
  )}
88
90
  <QueueList
89
91
  queue={messageQueue}
90
- setQueue={setMessageQueue}
92
+ onRemove={onRemoveQueueItem}
93
+ onReorder={onReorderQueue}
91
94
  onEdit={onEditQueueItem}
92
95
  onSendNow={onSendNowQueueItem}
93
96
  />
94
97
  <QueueList
95
98
  queue={steeringQueue}
96
- setQueue={setSteeringQueue}
99
+ onRemove={(id) => setSteeringQueue((q) => q.filter((i) => i.id !== id))}
100
+ onReorder={(orderedIds) => setSteeringQueue((prev) => {
101
+ const byId = new Map(prev.map((i) => [i.id, i]));
102
+ return orderedIds.map((id) => byId.get(id)).filter((i): i is QueuedMessage => Boolean(i));
103
+ })}
97
104
  isSteering
98
105
  />
99
106
  <ChatInput
@@ -1,4 +1,3 @@
1
- import type { Dispatch, SetStateAction } from 'preact/compat';
2
1
  import { useRef } from 'preact/hooks';
3
2
  import { GripVertical, Pencil, Send, X } from 'lucide-preact';
4
3
  import type { QueuedMessage } from '@/shared/types';
@@ -7,7 +6,10 @@ export type { QueuedMessage };
7
6
 
8
7
  interface QueueListProps {
9
8
  queue: QueuedMessage[];
10
- setQueue: Dispatch<SetStateAction<QueuedMessage[]>>;
9
+ /** Reorder handler (drag-and-drop): receives the new id order. */
10
+ onReorder?: (orderedIds: string[]) => void;
11
+ /** Remove one row by id. */
12
+ onRemove?: (id: string) => void;
11
13
  /** True when every row in this panel is a steering delivery. */
12
14
  isSteering?: boolean;
13
15
  onEdit?: (item: QueuedMessage) => void;
@@ -20,18 +22,18 @@ const KIND_LABEL: Record<string, string> = {
20
22
  followup: 'follow-up',
21
23
  };
22
24
 
23
- export function QueueList({ queue, setQueue, isSteering = false, onEdit, onSendNow }: QueueListProps) {
25
+ export function QueueList({ queue, onReorder, onRemove, isSteering = false, onEdit, onSendNow }: QueueListProps) {
24
26
  const dragItem = useRef<number | null>(null);
25
27
  const dragOverItem = useRef<number | null>(null);
26
28
 
27
29
  const handleSort = () => {
28
30
  if (dragItem.current === null || dragOverItem.current === null) return;
29
- const _queue = [...queue];
30
- const draggedItemContent = _queue.splice(dragItem.current, 1)[0];
31
- _queue.splice(dragOverItem.current, 0, draggedItemContent);
31
+ const ids = queue.map((i) => i.id);
32
+ const draggedId = ids.splice(dragItem.current, 1)[0];
33
+ ids.splice(dragOverItem.current, 0, draggedId);
32
34
  dragItem.current = null;
33
35
  dragOverItem.current = null;
34
- setQueue(_queue);
36
+ onReorder?.(ids);
35
37
  };
36
38
 
37
39
  if (queue.length === 0) return null;
@@ -80,13 +82,15 @@ export function QueueList({ queue, setQueue, isSteering = false, onEdit, onSendN
80
82
  <Pencil size={12} />
81
83
  </button>
82
84
  )}
83
- <button
84
- onClick={() => setQueue(q => q.filter(i => i.id !== item.id))}
85
- className="text-ink/40 hover:text-error opacity-0 group-hover:opacity-100 transition-opacity"
86
- title="Remove"
87
- >
88
- <X size={14} />
89
- </button>
85
+ {onRemove && (
86
+ <button
87
+ onClick={() => onRemove(item.id)}
88
+ className="text-ink/40 hover:text-error opacity-0 group-hover:opacity-100 transition-opacity"
89
+ title="Remove"
90
+ >
91
+ <X size={14} />
92
+ </button>
93
+ )}
90
94
  </div>
91
95
  ))}
92
96
  </div>
@@ -49,7 +49,8 @@ export function ChatTimeline({ className = '', appSettings = {}, onSessionTitle,
49
49
  loadOlder,
50
50
  generatingVerb,
51
51
  messageQueue,
52
- setMessageQueue,
52
+ removeMessage,
53
+ reorderMessages,
53
54
  steeringQueue,
54
55
  setSteeringQueue,
55
56
  inputValue,
@@ -243,7 +244,8 @@ export function ChatTimeline({ className = '', appSettings = {}, onSessionTitle,
243
244
  provider={sessionProvider}
244
245
  providerNames={providerNames}
245
246
  messageQueue={messageQueue}
246
- setMessageQueue={setMessageQueue}
247
+ onRemoveQueueItem={removeMessage}
248
+ onReorderQueue={reorderMessages}
247
249
  onEditQueueItem={handleEditQueueItem}
248
250
  onSendNowQueueItem={handleSendNowQueueItem}
249
251
  steeringQueue={steeringQueue}
@@ -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
  }