repoburg 1.3.196 → 1.3.197
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/agent-pane/backend/data.db-shm +0 -0
- package/agent-pane/backend/data.db-wal +0 -0
- package/agent-pane/backend/dist/index.js +13399 -10390
- package/agent-pane/backend/package-lock.json +162 -92
- package/agent-pane/backend/scripts/backfill-from-jsonl.ts +206 -0
- package/agent-pane/backend/scripts/import-session-from-jsonl.ts +264 -0
- package/agent-pane/backend/src/claude/claude-message-mapper.test.ts +116 -8
- package/agent-pane/backend/src/claude/claude-message-mapper.ts +114 -9
- package/agent-pane/backend/src/claude/claude-provider.ts +918 -84
- package/agent-pane/backend/src/claude/claude-types.ts +16 -4
- package/agent-pane/backend/src/codex/app-server-client.ts +22 -0
- package/agent-pane/backend/src/db/index.ts +102 -0
- package/agent-pane/backend/src/db/schema.ts +4 -0
- package/agent-pane/backend/src/index.ts +3 -1
- package/agent-pane/backend/src/integration/helpers/test-app.ts +2 -0
- package/agent-pane/backend/src/integration/session-operations.test.ts +18 -11
- package/agent-pane/backend/src/integration/threads.test.ts +309 -0
- package/agent-pane/backend/src/providers/runtime-provider.ts +19 -1
- package/agent-pane/backend/src/routes/providers.ts +11 -0
- package/agent-pane/backend/src/routes/sessions.test.ts +143 -9
- package/agent-pane/backend/src/routes/sessions.ts +133 -28
- package/agent-pane/backend/src/routes/threads.ts +308 -0
- package/agent-pane/backend/src/routes/turns.ts +32 -9
- package/agent-pane/frontend/dist/assets/index-BYo-lnxm.js +362 -0
- package/agent-pane/frontend/dist/assets/index-Dwwhx7YN.css +1 -0
- package/agent-pane/frontend/dist/index.html +2 -2
- package/agent-pane/frontend/package-lock.json +51 -9
- package/agent-pane/frontend/src/__tests__/ThreadsPanel.test.tsx +192 -0
- package/agent-pane/frontend/src/api/providers.ts +6 -1
- package/agent-pane/frontend/src/api/sessions.test.ts +4 -0
- package/agent-pane/frontend/src/api/sessions.ts +13 -5
- package/agent-pane/frontend/src/api/websocket.ts +65 -1
- package/agent-pane/frontend/src/components/CollapsibleToolCalls.test.tsx +4 -4
- package/agent-pane/frontend/src/components/CollapsibleToolCalls.tsx +13 -4
- package/agent-pane/frontend/src/components/CommandInputBar.test.tsx +2 -2
- package/agent-pane/frontend/src/components/CommandInputBar.tsx +292 -29
- package/agent-pane/frontend/src/components/CommandPalette.test.tsx +0 -4
- package/agent-pane/frontend/src/components/CommandPalette.tsx +1 -17
- package/agent-pane/frontend/src/components/CommentableText.tsx +291 -0
- package/agent-pane/frontend/src/components/RenderArea.tsx +9 -2
- package/agent-pane/frontend/src/components/RightPanel.test.tsx +5 -5
- package/agent-pane/frontend/src/components/RightPanel.tsx +20 -14
- package/agent-pane/frontend/src/components/SessionTabs.tsx +7 -2
- package/agent-pane/frontend/src/components/StatusBar.tsx +68 -1
- package/agent-pane/frontend/src/components/TurnContent.test.tsx +29 -18
- package/agent-pane/frontend/src/components/TurnContent.tsx +199 -28
- package/agent-pane/frontend/src/components/input/SlashPopup.tsx +24 -5
- package/agent-pane/frontend/src/components/panels/TasksPanel.test.tsx +97 -0
- package/agent-pane/frontend/src/components/panels/TasksPanel.tsx +208 -0
- package/agent-pane/frontend/src/components/panels/ThreadsPanel.tsx +350 -0
- package/agent-pane/frontend/src/components/panels/TurnsPanel.tsx +47 -25
- package/agent-pane/frontend/src/hooks/useSessionStatus.test.ts +2 -0
- package/agent-pane/frontend/src/store/sessionSlice.ts +29 -6
- package/agent-pane/frontend/src/store/store.test.ts +5 -5
- package/agent-pane/frontend/src/store/store.ts +4 -1
- package/agent-pane/frontend/src/store/threadSlice.ts +136 -0
- package/agent-pane/frontend/src/store/wsSlice.ts +72 -3
- package/agent-pane/frontend/src/styles/app.css +150 -19
- package/agent-pane/frontend/src/test/fixtures.ts +4 -0
- package/agent-pane/frontend/src/types/index.ts +5 -2
- package/agent-pane/frontend/src/utils/duration.test.ts +4 -0
- package/agent-pane/frontend/vite.config.ts +2 -2
- package/agent-pane/shared/provider-types.ts +14 -0
- package/agent-pane/shared/types.ts +60 -0
- package/package.json +1 -1
- package/agent-pane/frontend/dist/assets/index-Bg0PF2aV.css +0 -1
- package/agent-pane/frontend/dist/assets/index-BlybeX5e.js +0 -281
- package/agent-pane/frontend/src/components/panels/LogsPanel.test.tsx +0 -58
- package/agent-pane/frontend/src/components/panels/LogsPanel.tsx +0 -25
- package/agent-pane/frontend/src/components/panels/MetricsPanel.test.tsx +0 -71
- package/agent-pane/frontend/src/components/panels/MetricsPanel.tsx +0 -114
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import"https://fonts.googleapis.com/css2?family=Inter:wght@400;450;510;590;600&family=JetBrains+Mono:wght@400;500&display=swap";:root{--canvas: #fdfdfc;--surface-0: #f8f7f5;--surface-1: #f0efed;--surface-2: #e6e4e0;--surface-3: #d6d3cd;--border: #e2e0db;--border-muted: #edebe7;--border-hover: #ccc8c0;--text: #1c1b18;--text-2: #54504a;--text-3: #78736c;--text-muted: #a8a399;--accent: #d97706;--accent-hover: #b45309;--accent-soft: rgba(217, 119, 6, .08);--accent-glow: rgba(217, 119, 6, .2);--green: #16a34a;--amber: #ca8a04;--red: #dc2626;--green-soft: rgba(22, 163, 74, .08);--amber-soft: rgba(202, 138, 4, .08);--red-soft: rgba(220, 38, 38, .08);--ic-claude: #c2410c;--ic-codex: #6366f1;--ic-repoburg: #059669;--ic-cursor: #db2777;--font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--font-mono: "JetBrains Mono", "SF Mono", "Fira Code", monospace;--r-sm: 4px;--r-md: 6px;--r-lg: 8px;--r-xl: 12px;--r-pill: 9999px;--ease: cubic-bezier(.25, .46, .45, .94);--ease-out: cubic-bezier(.22, 1, .36, 1);--dur-fast: .1s;--dur-normal: .15s;--dur-slow: .25s;--inset-highlight: inset 0 1px 0 rgba(0, 0, 0, .03);--inset-strong: inset 0 1px 0 rgba(0, 0, 0, .05);--focus-ring: 0 0 0 3px var(--accent-glow)}*{margin:0;padding:0;box-sizing:border-box}html,body{height:100%;overflow:hidden}body{font-family:var(--font-sans);font-size:13px;font-weight:400;background:var(--canvas);color:var(--text-2);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-rendering:optimizeLegibility;font-feature-settings:"ss03","cv11";font-variant-numeric:tabular-nums;letter-spacing:-.01em}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--surface-3);border-radius:3px}::-webkit-scrollbar-thumb:hover{background:var(--border-hover)}:focus-visible{outline:none;box-shadow:var(--focus-ring);border-radius:var(--r-md)}.ide-root{display:flex;flex-direction:column;height:100vh}.ide-layout{display:grid;grid-template-columns:240px 1fr 300px;flex:1;overflow:hidden}.ide-sidebar{background:var(--surface-0);border-right:1px solid var(--border);overflow-y:auto;display:flex;flex-direction:column}.sidebar-header{padding:10px 12px 8px;display:flex;align-items:center;justify-content:space-between}.sidebar-header-title{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.08em;color:var(--text-muted)}.sidebar-add{width:20px;height:20px;border-radius:var(--r-sm);border:none;background:transparent;color:var(--text-muted);cursor:pointer;font-size:15px;display:flex;align-items:center;justify-content:center;transition:all var(--dur-normal) var(--ease)}.sidebar-add:hover{background:#0000000f;color:var(--text)}.tree-section{margin-bottom:2px}.tree-section-header{padding:5px 8px;display:flex;align-items:center;gap:6px;font-size:11px;font-weight:590;color:var(--text-2);cursor:pointer;-webkit-user-select:none;user-select:none;border-radius:var(--r-sm);transition:background var(--dur-fast) var(--ease)}.tree-section-header:hover{background:#0000000a}.tree-chevron{font-size:9px;color:var(--text-muted);width:10px;transition:transform var(--dur-normal) var(--ease)}.tree-section.collapsed .tree-chevron{transform:rotate(-90deg)}.tree-section.collapsed .tree-children{display:none}.tree-count{margin-left:auto;font-size:10px;background:var(--surface-2);padding:1px 6px;border-radius:var(--r-pill);color:var(--text-muted);font-variant-numeric:tabular-nums}.tree-children{padding-left:4px}.tree-item{padding:4px 8px 4px 26px;border-radius:var(--r-sm);font-size:12px;cursor:pointer;display:flex;align-items:center;gap:6px;color:var(--text-2);-webkit-user-select:none;user-select:none;transition:all var(--dur-fast) var(--ease);position:relative}.tree-item:hover{background:#0000000a;color:var(--text)}.tree-item.active{background:var(--accent-soft);color:var(--text)}.tree-item.active:before{content:"";position:absolute;left:0;top:4px;bottom:4px;width:2px;background:var(--accent);border-radius:var(--r-pill)}.tree-item .ri{width:16px;height:16px;border-radius:3px;display:flex;align-items:center;justify-content:center;font-size:8px;font-weight:700;color:#fff;flex-shrink:0}.tree-label{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sidebar-footer{margin-top:auto;padding:8px 12px;border-top:1px solid var(--border-muted);font-size:11px;color:var(--text-muted);display:flex;align-items:center;gap:6px;cursor:pointer}.sidebar-footer:hover{color:var(--text-2)}.ide-center{display:flex;flex-direction:column;background:var(--canvas);overflow:hidden;min-height:0}.ide-tabs{display:flex;background:var(--surface-0);border-bottom:1px solid var(--border);overflow-x:auto;flex-shrink:0;scrollbar-width:none}.ide-tabs::-webkit-scrollbar{display:none}.ide-tab{padding:8px 12px;font-size:12px;color:var(--text-3);border-right:1px solid var(--border-muted);cursor:pointer;display:flex;align-items:center;gap:6px;white-space:nowrap;-webkit-user-select:none;user-select:none;transition:all var(--dur-fast) var(--ease);position:relative}.ide-tab:hover{background:#00000008;color:var(--text-2)}.ide-tab.active{color:var(--text);background:var(--canvas)}.ide-tab.active:after{content:"";position:absolute;left:0;right:0;top:0;height:2px;background:var(--accent)}.ide-tab .ri{width:14px;height:14px;font-size:7px;border-radius:3px}.ide-tab-close{margin-left:4px;color:var(--text-muted);font-size:14px;opacity:0;transition:opacity var(--dur-fast) var(--ease);display:flex;align-items:center}.ide-tab:hover .ide-tab-close{opacity:1}.ide-tab-close:hover{color:var(--red)}.ide-tab-add{padding:8px 10px;color:var(--text-muted);cursor:pointer;font-size:14px;transition:color var(--dur-fast) var(--ease);display:flex;align-items:center}.ide-tab-add:hover{color:var(--text)}.ide-render{flex:1;overflow:hidden;display:flex;flex-direction:column;position:relative;min-height:0}.render-scroll{flex:1;padding:20px 28px;overflow-y:auto;font-size:14px;line-height:1.7;color:var(--text);min-height:0}.render-content{max-width:720px}.render-content h1,.render-content h2,.render-content h3{margin:20px 0 8px;font-weight:600;color:var(--text);line-height:1.3}.render-content h1{font-size:20px}.render-content h2{font-size:17px}.render-content h3{font-size:15px}.render-content p{margin-bottom:12px;color:var(--text-2)}.render-content ul,.render-content ol{margin-bottom:12px;padding-left:24px}.render-content li{margin-bottom:4px;color:var(--text-2)}.render-content code{font-family:var(--font-mono);font-size:12px;background:var(--surface-1);padding:2px 6px;border-radius:var(--r-sm);color:var(--accent)}.render-content pre{background:var(--surface-0);border:1px solid var(--border);border-radius:var(--r-md);padding:12px 16px;overflow-x:auto;margin-bottom:12px}.render-content pre code{background:none;padding:0;color:var(--text);font-size:12.5px;line-height:1.6}.render-content a{color:var(--accent);text-decoration:none}.render-content a:hover{text-decoration:underline}.render-content blockquote{border-left:3px solid var(--border);padding-left:14px;color:var(--text-3);margin-bottom:12px}.render-content table{width:100%;border-collapse:collapse;margin-bottom:12px}.render-content th,.render-content td{border:1px solid var(--border);padding:6px 10px;font-size:13px}.render-content th{background:var(--surface-1);font-weight:510}.turn-header{display:flex;align-items:center;gap:8px;margin-bottom:14px}.turn-header-icon{width:24px;height:24px;border-radius:var(--r-sm);display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:9px;font-weight:700;color:#fff}.turn-header-title{font-size:14px;font-weight:600;color:var(--text)}.turn-header-meta{font-size:11px;color:var(--text-muted)}.tool-calls{margin-bottom:16px}.tool-calls-header{display:flex;align-items:center;gap:8px;padding:6px 12px;background:var(--surface-1);border:1px solid var(--border);border-radius:var(--r-sm);cursor:pointer;transition:background .15s ease-out;-webkit-user-select:none;user-select:none}.tool-calls-header:hover{background:var(--surface-2)}.tool-calls-icon{display:flex;align-items:center;color:var(--text-3);flex-shrink:0}.tool-calls-summary{font-size:13px;font-weight:500;color:var(--text-2);flex:1}.tool-calls-chevron{color:var(--text-muted);transition:transform .15s ease-out;flex-shrink:0}.tool-calls-body{margin-top:8px;padding-left:16px;border-left:2px solid var(--border)}.intermediate-segment{margin-bottom:16px}.intermediate-segment:last-child{margin-bottom:0}.user-message{font-size:14px;color:var(--text);background:var(--accent-soft);padding:12px 16px;border-radius:var(--r-md);border:1px solid rgba(217,119,6,.15)}.cmd-bar{position:relative;display:flex;flex-direction:column;padding:0;background:var(--surface-0);border-top:1px solid var(--border);flex-shrink:0}.cmd-input-row{display:flex;align-items:flex-end;gap:8px;padding:8px 12px}.cmd-bar:focus-within{box-shadow:inset 0 0 0 1px #d9770666}.cmd-textarea{flex:1;border:none;background:transparent;color:var(--text);font-size:13px;font-family:var(--font-sans);outline:none;resize:none;overflow-y:auto;line-height:1.5;padding:6px 0}.cmd-textarea::placeholder{color:var(--text-muted)}.cmd-textarea:disabled{opacity:.5}.cmd-textarea:focus-visible{box-shadow:none}.cmd-send-btn{width:28px;height:28px;border:none;border-radius:var(--r-sm);background:var(--surface-2);color:var(--text-muted);cursor:default;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:all var(--dur-fast) var(--ease)}.cmd-send-btn:not(:disabled){background:var(--accent);color:#fff;cursor:pointer}.cmd-send-btn:not(:disabled):hover{background:var(--accent-hover)}.cmd-send-btn:disabled{opacity:.4}.cmd-stop-btn{width:28px;height:28px;border:none;border-radius:var(--r-sm);background:var(--red);color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:all var(--dur-fast) var(--ease)}.cmd-stop-btn:hover{background:#b91c1c}.ide-right{background:var(--surface-0);border-left:1px solid var(--border);display:flex;flex-direction:column;overflow:hidden}.right-tabs{display:flex;padding:0 8px;border-bottom:1px solid var(--border);flex-shrink:0;gap:2px}.right-tab{padding:7px 10px;font-size:11px;font-weight:510;color:var(--text-3);cursor:pointer;border-bottom:2px solid transparent;-webkit-user-select:none;user-select:none;transition:all var(--dur-fast) var(--ease);margin-bottom:-1px}.right-tab:hover{color:var(--text-2)}.right-tab.active{color:var(--text);border-bottom-color:var(--accent)}.right-content{flex:1;overflow-y:auto;padding:14px}.right-section{margin-bottom:20px}.right-section-title{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.08em;color:var(--text-muted);margin-bottom:8px}.turn-item{display:flex;align-items:center;gap:8px;padding:7px 10px;border-radius:var(--r-sm);cursor:pointer;font-size:12px;color:var(--text-3);transition:all var(--dur-fast) var(--ease);position:relative}.turn-item:hover{background:#0000000a;color:var(--text-2)}.turn-item.active{background:var(--accent-soft);color:var(--text)}.turn-item.active:before{content:"";position:absolute;left:0;top:4px;bottom:4px;width:2px;background:var(--accent);border-radius:var(--r-pill)}.turn-item-icon{width:18px;height:18px;border-radius:var(--r-sm);display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:8px;font-weight:700;color:#fff}.turn-item-label{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.turn-item-status{font-size:10px;color:var(--amber);flex-shrink:0}.turn-item-meta{font-size:10px;color:var(--text-muted);flex-shrink:0}.file-change{display:flex;align-items:center;gap:8px;padding:5px 0;font-size:12px;border-bottom:1px solid var(--border-muted)}.file-change:last-child{border:none}.file-change.clickable{cursor:pointer;transition:background var(--dur-fast) var(--ease)}.file-change.clickable:hover{background:#00000008}.fc-status{width:14px;text-align:center;font-weight:700;font-size:11px;flex-shrink:0}.fc-add{color:var(--green)}.fc-mod{color:var(--amber)}.fc-del{color:var(--red)}.fc-name{flex:1;color:var(--text-2);font-family:var(--font-mono);font-size:11px}.fc-delta{font-size:10px;color:var(--text-muted);flex-shrink:0;font-variant-numeric:tabular-nums}.diff-file-header{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--surface-0);border:1px solid var(--border);border-radius:var(--r-md) var(--r-md) 0 0;border-bottom:none}.diff-file-status{width:16px;height:16px;border-radius:3px;display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:700;flex-shrink:0}.diff-file-name{font-family:var(--font-mono);font-size:13px;font-weight:510;color:var(--text)}.diff-file-stats{display:flex;align-items:center;gap:6px;margin-left:auto;font-size:11px;font-variant-numeric:tabular-nums}.diff-file-badge{padding:1px 7px;border-radius:var(--r-pill);background:var(--surface-2);color:var(--text-3);font-size:10px}.diff-body{border:1px solid var(--border);border-radius:0 0 var(--r-md) var(--r-md);overflow:hidden;margin-bottom:20px}.diff-line{display:flex;align-items:flex-start;font-family:var(--font-mono);font-size:12px;line-height:1.6}.diff-line-num{width:42px;flex-shrink:0;text-align:right;padding:0 8px;color:var(--text-muted);background:var(--surface-0);-webkit-user-select:none;user-select:none;border-right:1px solid var(--border-muted)}.diff-sign{width:20px;flex-shrink:0;text-align:center;-webkit-user-select:none;user-select:none;font-weight:700}.diff-line-content{flex:1;padding:0 8px;white-space:pre-wrap;word-break:break-all;min-width:0}.diff-line-add{background:#16a34a0f}.diff-line-add .diff-sign{color:var(--green)}.diff-line-del{background:#dc26260f}.diff-line-del .diff-sign{color:var(--red)}.diff-line-ctx .diff-sign{color:var(--text-muted)}.diff-line-ctx .diff-line-content{color:var(--text-3)}.changes-scope-toggle{display:flex;gap:2px;margin-bottom:12px;padding:2px;background:var(--surface-1);border-radius:var(--r-md)}.scope-btn{flex:1;padding:5px 10px;border:none;background:transparent;color:var(--text-3);font-size:11px;font-weight:510;cursor:pointer;border-radius:var(--r-sm);font-family:var(--font-sans);transition:all var(--dur-fast) var(--ease)}.scope-btn:hover{color:var(--text-2)}.scope-btn.active{background:var(--surface-0);color:var(--text);box-shadow:var(--inset-highlight)}.ide-statusbar{display:flex;align-items:center;gap:12px;padding:0 12px;height:24px;background:var(--surface-0);border-top:1px solid var(--border);font-size:11px;color:var(--text-muted);flex-shrink:0;font-variant-numeric:tabular-nums}.sb-item{display:flex;align-items:center;gap:3px;cursor:pointer;transition:color var(--dur-fast) var(--ease)}.sb-item:hover{color:var(--text-2)}.sb-spacer{flex:1}.sb-divider{color:var(--border)}.sb-context{display:flex;align-items:center;gap:5px}.sb-context-text{font-size:10px;color:var(--text-muted);font-variant-numeric:tabular-nums}.model-selector{position:relative}.model-selector-btn{display:flex;align-items:center;gap:4px;background:none;border:none;cursor:pointer;color:var(--text-muted);font-size:11px;padding:2px 6px;border-radius:3px;transition:background var(--dur-fast) var(--ease),color var(--dur-fast) var(--ease)}.model-selector-btn:hover{background:var(--surface-1);color:var(--text-2)}.model-selector-provider{font-weight:500;color:var(--accent)}.model-selector-sep{color:var(--border);margin:0 1px}.model-selector-model{color:var(--text-2)}.model-selector-reasoning{color:var(--text-muted);font-style:italic}.model-selector-chevron{opacity:.5;flex-shrink:0}.model-selector-popover{position:absolute;bottom:calc(100% + 4px);left:0;display:flex;gap:1px;background:var(--surface-0);border:1px solid var(--border);border-radius:6px;box-shadow:0 4px 16px #0003;z-index:100;min-width:0;overflow:hidden}.model-selector-column{min-width:140px;max-width:220px;padding:4px 0;background:var(--surface-0)}.model-selector-col-header{padding:4px 10px;font-size:9px;font-weight:600;text-transform:uppercase;color:var(--text-muted);letter-spacing:.5px}.model-selector-item{display:flex;align-items:center;gap:6px;padding:6px 10px;cursor:pointer;font-size:11px;color:var(--text-2);transition:background var(--dur-fast) var(--ease)}.model-selector-item:hover{background:var(--surface-1)}.model-selector-item.selected{color:var(--accent)}.model-selector-item.disabled{cursor:default;opacity:.5}.model-selector-item.disabled:hover{background:none}.model-selector-item-main{display:flex;flex-direction:column;gap:1px;flex:1;min-width:0}.model-selector-item-name{font-weight:500;white-space:nowrap}.model-selector-item-desc{font-size:9px;color:var(--text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.model-selector-coming-soon{display:flex;align-items:center;gap:2px;font-size:9px;color:var(--text-muted);font-style:italic}.model-selector-empty{padding:8px 10px;font-size:10px;color:var(--text-muted);font-style:italic}.dialog-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 8px center;padding-right:26px}.badge{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:var(--r-pill);font-size:11px;font-weight:510;white-space:nowrap}.badge-running{background:var(--green-soft);color:var(--green)}.badge-queued{background:var(--amber-soft);color:var(--amber)}.badge-done{background:var(--accent-soft);color:var(--accent)}.badge-failed{background:var(--red-soft);color:var(--red)}.badge-paused{background:#a8a3991f;color:var(--text-muted)}.runtime-icon{width:20px;height:20px;border-radius:var(--r-sm);display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:700;color:#fff;flex-shrink:0;letter-spacing:0}.ic-claude{background:var(--ic-claude)}.ic-codex{background:var(--ic-codex)}.ic-repoburg{background:var(--ic-repoburg)}.ic-cursor{background:var(--ic-cursor)}.status-dot{width:6px;height:6px;border-radius:50%;display:inline-block;flex-shrink:0}.status-dot.running{background:var(--green);animation:dot-pulse 1.5s ease-in-out infinite}.status-dot.queued{background:var(--amber)}.status-dot.error{background:var(--red)}.status-dot.done{background:var(--accent)}.status-dot.paused{background:var(--text-muted)}[data-lucide],svg.lucide{width:16px;height:16px;stroke-width:1.5;flex-shrink:0}.icon-xs{width:12px!important;height:12px!important}.icon-sm{width:14px!important;height:14px!important}.icon-lg{width:20px!important;height:20px!important}.dialog-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:#0000004d;display:flex;align-items:center;justify-content:center;z-index:100}.dialog{background:var(--surface-0);border:1px solid var(--border);border-radius:var(--r-lg);width:400px;max-width:90vw;box-shadow:0 8px 32px #00000029}.dialog-header{display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-bottom:1px solid var(--border-muted)}.dialog-title{font-size:14px;font-weight:600;color:var(--text)}.dialog-close{border:none;background:transparent;color:var(--text-muted);cursor:pointer;padding:4px;border-radius:var(--r-sm);display:flex;align-items:center;transition:all var(--dur-fast) var(--ease)}.dialog-close:hover{background:#0000000f;color:var(--text)}.dialog-body{padding:16px;display:flex;flex-direction:column;gap:14px}.dialog-label{display:flex;flex-direction:column;gap:4px;font-size:11px;font-weight:510;color:var(--text-muted)}.dialog-input{border:1px solid var(--border);border-radius:var(--r-sm);padding:8px 10px;font-size:13px;font-family:var(--font-sans);color:var(--text);background:var(--canvas);outline:none;transition:border-color var(--dur-fast) var(--ease)}.dialog-input:focus{border-color:var(--accent);box-shadow:var(--focus-ring)}.dialog-footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid var(--border-muted)}.dialog-btn-cancel{padding:6px 14px;border-radius:var(--r-sm);border:1px solid var(--border);background:transparent;color:var(--text-2);font-size:12px;cursor:pointer;font-family:var(--font-sans);transition:all var(--dur-fast) var(--ease)}.dialog-btn-cancel:hover{background:var(--surface-2);color:var(--text)}.dialog-btn-create{padding:6px 14px;border-radius:var(--r-sm);border:none;background:var(--accent);color:#fff;font-size:12px;font-weight:510;cursor:pointer;font-family:var(--font-sans);transition:all var(--dur-fast) var(--ease)}.dialog-btn-create:hover{background:var(--accent-hover)}.turn-group{margin-bottom:24px}.turn-block{margin-bottom:32px;padding-bottom:8px;border-bottom:1px solid var(--border)}.turn-block:last-child{border-bottom:none}.cmd-block{background:var(--surface-0);border:1px solid var(--border);border-radius:var(--r-md);padding:10px 14px;overflow-x:auto;margin-bottom:12px}.cmd-block code{font-family:var(--font-mono);font-size:12px;color:var(--text)}.cmd-output{background:var(--surface-0);border:1px solid var(--border-muted);border-radius:var(--r-md);padding:10px 14px;overflow-x:auto;margin-bottom:12px;max-height:200px;overflow-y:auto}.cmd-output code{font-family:var(--font-mono);font-size:12px;color:var(--text-3)}.cmd-output-toggle{border:1px solid var(--border);border-radius:var(--r-sm);background:var(--surface-1);color:var(--text-muted);font-size:11px;font-family:var(--font-sans);padding:4px 10px;cursor:pointer;margin-bottom:12px;transition:all var(--dur-fast) var(--ease)}.cmd-output-toggle:hover{background:var(--surface-2);color:var(--text-2)}.mcp-args{margin-bottom:12px}.mcp-arg{margin-bottom:6px}.mcp-arg-key{font-size:11px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.04em;display:block;margin-bottom:3px}.mcp-arg-value{font-size:12px;color:var(--text-2);white-space:pre-wrap;word-break:break-word}.file-change-list{display:flex;flex-direction:column;gap:2px}.todo-list{list-style:none;padding-left:0}.todo-done{color:var(--text-3);text-decoration:line-through}.todo-pending{color:var(--text-2)}.empty-state{padding:40px 20px;text-align:center;color:var(--text-muted);font-size:13px}.palette-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:#00000040;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);display:flex;align-items:flex-start;justify-content:center;padding-top:120px;z-index:200}.palette{background:var(--surface-0);border:1px solid var(--border);border-radius:var(--r-lg);width:520px;max-width:90vw;box-shadow:0 12px 40px #0003;overflow:hidden}.palette-search{display:flex;align-items:center;gap:8px;padding:12px 16px;border-bottom:1px solid var(--border-muted)}.palette-search input{flex:1;border:none;background:transparent;color:var(--text);font-size:15px;font-family:var(--font-sans);outline:none}.palette-search input::placeholder{color:var(--text-muted)}.palette-esc{font-size:10px;font-weight:600;color:var(--text-muted);background:var(--surface-2);padding:2px 6px;border-radius:var(--r-sm)}.palette-list{max-height:360px;overflow-y:auto;padding:4px}.palette-empty{padding:24px;text-align:center;color:var(--text-muted);font-size:13px}.palette-item{display:flex;align-items:center;gap:10px;padding:8px 12px;border-radius:var(--r-sm);cursor:pointer;transition:background var(--dur-fast) var(--ease)}.palette-item.selected{background:var(--accent-soft)}.palette-item-icon{color:var(--text-muted);flex-shrink:0;display:flex;align-items:center}.palette-item.selected .palette-item-icon{color:var(--accent)}.palette-item-label{font-size:13px;color:var(--text);font-weight:510}.palette-item-hint{font-size:11px;color:var(--text-muted);margin-left:auto}.palette-item-enter{color:var(--text-muted);flex-shrink:0}.toast-container{position:fixed;bottom:36px;right:16px;display:flex;flex-direction:column-reverse;gap:8px;z-index:300;pointer-events:none}.toast{display:flex;align-items:center;gap:8px;padding:10px 14px;border-radius:var(--r-md);font-size:12px;font-weight:500;min-width:280px;max-width:400px;pointer-events:auto;animation:toast-in .2s ease-out;border:1px solid var(--border);background:var(--surface-0);box-shadow:0 4px 16px #0000001f}@keyframes toast-in{0%{opacity:0;transform:translate(20px)}to{opacity:1;transform:translate(0)}}.toast-success{border-left:3px solid var(--green)}.toast-success svg{color:var(--green)}.toast-error{border-left:3px solid var(--red)}.toast-error svg{color:var(--red)}.toast-info{border-left:3px solid var(--accent)}.toast-info svg{color:var(--accent)}.toast-warn{border-left:3px solid var(--amber)}.toast-warn svg{color:var(--amber)}.toast-message{flex:1;color:var(--text-2)}.toast-close{border:none;background:transparent;color:var(--text-muted);cursor:pointer;padding:2px;border-radius:var(--r-sm);display:flex;align-items:center;transition:color var(--dur-fast) var(--ease)}.toast-close:hover{color:var(--text)}.error-banner{display:flex;align-items:center;gap:10px;padding:10px 16px;background:#dc262614;border:1px solid rgba(220,38,38,.2);border-radius:var(--r-md);margin-bottom:16px}.banner-icon{width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;flex-shrink:0}.error-banner .banner-icon{background:var(--red);color:#fff}.error-banner .banner-text{color:var(--red);font-size:13px;font-weight:500}.aborted-banner{display:flex;align-items:center;gap:10px;padding:10px 16px;background:#d9770614;border:1px solid rgba(217,119,6,.2);border-radius:var(--r-md);margin-bottom:16px}.aborted-banner .banner-icon{background:var(--amber);color:#fff}.aborted-banner .banner-text{color:var(--amber);font-size:13px;font-weight:500}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}.skeleton-bars{width:80%;max-width:500px;margin:20px 0}.skeleton-bar{height:10px;border-radius:var(--r-sm);background:var(--surface-1);margin-bottom:8px;animation:shimmer 1.2s infinite}.skeleton-bar:nth-child(1){width:60%}.skeleton-bar:nth-child(2){width:85%}.skeleton-bar:nth-child(3){width:70%}.skeleton-bar:nth-child(4){width:90%}.skeleton-bar:nth-child(5){width:50%}@keyframes shimmer{0%{opacity:.3}50%{opacity:.6}to{opacity:.3}}@keyframes dot-pulse{0%{opacity:1}to{opacity:.4}}.diff-file-header{transition:background .3s ease-out}.diff-file-header.diff-highlight{background:var(--accent-soft)}.palette-footer{display:flex;gap:16px;padding:8px 16px;border-top:1px solid var(--border-muted);font-size:11px;color:var(--text-muted)}.palette-footer-hint{display:flex;align-items:center;gap:4px}.kbd{font-family:var(--font-mono);font-size:10px;padding:1px 5px;border-radius:var(--r-sm);background:var(--surface-2);border:1px solid var(--border)}.toast-undo{font-size:12px;font-weight:500;color:var(--accent);background:transparent;border:none;cursor:pointer;padding:2px 8px;border-radius:var(--r-sm);display:flex;align-items:center;gap:4px;transition:background var(--dur-fast) var(--ease)}.toast-undo:hover{background:var(--accent-soft)}.tree-section-label{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:11px}.tree-section-add{width:18px;height:18px;border:none;border-radius:var(--r-sm);background:transparent;color:var(--text-muted);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all var(--dur-fast) var(--ease);flex-shrink:0}.tree-section-add:hover{background:var(--accent-soft);color:var(--accent)}.tree-empty{padding:4px 8px 4px 26px;font-size:11px;color:var(--text-muted);font-style:italic}.tree-subgroup{margin-bottom:1px}.tree-subgroup-header{padding:3px 8px 3px 18px;display:flex;align-items:center;gap:5px;font-size:10px;font-weight:590;color:var(--text-muted);cursor:pointer;-webkit-user-select:none;user-select:none;border-radius:var(--r-sm);transition:background var(--dur-fast) var(--ease)}.tree-subgroup-header:hover{background:#0000000a;color:var(--text-2)}.tree-subgroup.collapsed .tree-chevron{transform:rotate(-90deg)}.tree-subgroup.collapsed .tree-subgroup-items{display:none}.tree-subgroup-label{text-transform:uppercase;letter-spacing:.05em}.tree-subgroup-count{margin-left:auto;font-size:9px;color:var(--text-muted);font-variant-numeric:tabular-nums}.tree-subgroup-items{padding-left:0}.tree-item-finished .tree-label{color:var(--text-muted);opacity:.6}.tree-item-finished:hover .tree-label{opacity:.85}.ctx-menu{background:var(--surface-0);border:1px solid var(--border);border-radius:var(--r-md);box-shadow:0 4px 16px #0000001f;padding:4px;min-width:160px}.ctx-menu-item{display:flex;align-items:center;gap:8px;padding:6px 10px;border-radius:var(--r-sm);font-size:12px;color:var(--text-2);cursor:pointer;transition:background var(--dur-fast) var(--ease)}.ctx-menu-item:hover{background:var(--accent-soft);color:var(--text)}.ctx-menu-item.ctx-danger:hover{background:#dc262614;color:var(--red)}.ctx-menu-item.ctx-disabled{opacity:.4;cursor:default}.ctx-menu-item.ctx-disabled:hover{background:transparent;color:var(--text-2)}.tree-rename-input{flex:1;border:1px solid var(--accent);border-radius:var(--r-sm);padding:1px 6px;font-size:12px;font-family:var(--font-sans);color:var(--text);background:var(--canvas);outline:none;box-shadow:var(--focus-ring);min-width:0}.slash-popup{position:absolute;bottom:100%;left:12px;right:12px;margin-bottom:4px;background:var(--surface-0);border:1px solid var(--border);border-radius:var(--r-md);box-shadow:0 4px 16px #0000001f;max-height:280px;overflow-y:auto;z-index:100}.slash-popup-header{padding:6px 10px;font-size:10px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.05em;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:4px}.slash-popup-back{background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:11px;padding:2px 4px;border-radius:var(--r-sm)}.slash-popup-back:hover{background:var(--surface-1);color:var(--text-2)}.slash-popup-empty{padding:12px 10px;font-size:12px;color:var(--text-muted);text-align:center}.slash-popup-item{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:6px 10px;cursor:pointer;border-radius:var(--r-sm);transition:background var(--dur-fast) var(--ease)}.slash-popup-item:hover,.slash-popup-item.selected{background:var(--accent-soft)}.slash-popup-label{font-size:12px;font-weight:500;color:var(--text-2);font-family:var(--font-mono)}.slash-popup-hint{font-size:11px;color:var(--text-muted)}.slash-popup-item-main{display:flex;flex-direction:column;gap:1px}.slash-popup-check{color:var(--accent);font-size:13px}.skill-badges{display:flex;flex-wrap:wrap;gap:4px;padding:6px 12px 0}.skill-badge{display:inline-flex;align-items:center;gap:4px;padding:2px 6px;font-size:11px;font-family:var(--font-mono);color:var(--accent);background:var(--accent-soft);border-radius:var(--r-sm)}.skill-badge-remove{background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:13px;line-height:1;padding:0}.skill-badge-remove:hover{color:var(--red)}.attachment-preview-area{display:flex;flex-wrap:wrap;gap:4px;padding:6px 12px 0}.attachment-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 6px 2px 2px;border:1px solid var(--border);border-radius:var(--r-pill);background:var(--surface-1);max-width:180px;position:relative;transition:border-color var(--dur-fast) var(--ease)}.attachment-chip:hover{border-color:var(--accent)}.attachment-thumb{width:18px;height:18px;object-fit:cover;border-radius:50%;flex-shrink:0}.attachment-file-icon{width:14px;height:14px;color:var(--text-muted);flex-shrink:0}.attachment-info{display:flex;flex-direction:column;gap:0;overflow:hidden;min-width:0}.attachment-name{font-size:10px;font-family:var(--font-mono);color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100px}.attachment-size{font-size:9px;color:var(--text-muted);font-variant-numeric:tabular-nums}.attachment-remove{background:none;border:none;color:var(--text-muted);cursor:pointer;padding:1px;display:flex;align-items:center;justify-content:center;border-radius:var(--r-xs);transition:all var(--dur-fast) var(--ease);flex-shrink:0}.attachment-remove:hover{color:var(--red);background:#ef44441a}.attachment-clip-btn{width:28px;height:28px;border:none;border-radius:var(--r-sm);background:transparent;color:var(--text-muted);cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:all var(--dur-fast) var(--ease)}.attachment-clip-btn:hover{color:var(--accent);background:var(--surface-2)}.attachment-clip-btn:disabled{opacity:.3;cursor:default}.cmd-bar.drag-over{box-shadow:inset 0 0 0 2px var(--accent);background:var(--accent-soft)}.image-preview-modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:10000;background:#000c;display:flex;align-items:center;justify-content:center;padding:32px;animation:modal-fade-in .15s ease-out}@keyframes modal-fade-in{0%{opacity:0}to{opacity:1}}.image-preview-content{position:relative;max-width:90vw;max-height:90vh;display:flex;flex-direction:column;align-items:center;gap:8px}.image-preview-content img{max-width:100%;max-height:80vh;object-fit:contain;border-radius:var(--r-sm);border:1px solid var(--border)}.image-preview-info{display:flex;gap:12px;font-size:12px;color:var(--text-muted);font-family:var(--font-mono)}.image-preview-close{position:absolute;top:-12px;right:-12px;width:28px;height:28px;border:none;border-radius:50%;background:var(--surface-2);color:var(--text);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all var(--dur-fast) var(--ease)}.image-preview-close:hover{background:var(--red);color:#fff}.attachment-pills{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px}.attachment-pill{display:inline-flex;align-items:center;gap:4px;padding:2px 8px 2px 2px;border:1px solid var(--border);border-radius:var(--r-pill);background:var(--surface-1);font-size:10px;font-family:var(--font-mono);color:var(--text-muted);transition:border-color var(--dur-fast) var(--ease)}.attachment-pill:hover{border-color:var(--accent)}.attachment-pill-thumb{width:16px;height:16px;object-fit:cover;border-radius:50%;flex-shrink:0}.attachment-pill-icon{color:var(--text-muted);flex-shrink:0}.attachment-pill-name{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100px}.right-tab-badge{display:inline-flex;align-items:center;justify-content:center;min-width:16px;height:16px;padding:0 4px;margin-left:4px;font-size:10px;font-weight:600;color:#fff;background:var(--accent);border-radius:8px}.task-count-badge{font-size:10px;font-weight:500}.task-card{border:1px solid var(--border);border-radius:var(--r-md);padding:10px 12px;margin-bottom:10px;transition:border-color var(--dur-fast) var(--ease)}.task-card.task-running{border-color:var(--accent)}.task-card.task-failed{border-color:var(--red)}.task-card-header{display:flex;align-items:center;gap:8px}.task-card-icon{display:flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:var(--r-sm);background:var(--surface-2);color:var(--text-3);flex-shrink:0}.task-card-type{font-size:13px;font-weight:600;color:var(--text)}.task-card-status{display:flex;align-items:center;gap:3px;font-size:11px;padding:2px 6px;border-radius:var(--r-sm);flex-shrink:0}.task-status-running{color:var(--accent);background:var(--accent-soft)}.task-status-done{color:var(--green);background:var(--green-soft)}.task-status-failed{color:var(--red);background:var(--red-soft)}.task-card-chevron{color:var(--text-muted);flex-shrink:0;transition:transform var(--dur-fast) var(--ease)}.task-card-desc{font-size:12px;color:var(--text-2);margin-top:6px;font-family:var(--font-mono)}.task-card-meta{font-size:11px;color:var(--text-muted);margin-top:4px}.task-card-body{margin-top:8px}.task-output{max-height:300px}.task-card-placeholder{font-size:12px;color:var(--text-muted);font-style:italic;padding:8px 0}.task-card-context{font-size:10px;color:var(--text-muted);margin-top:6px;padding-top:6px;border-top:1px solid var(--border-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}mark[data-thread-id]{background:#d977062e;border-radius:2px;cursor:pointer;padding:0 1px;transition:background var(--dur-fast) var(--ease)}mark[data-thread-id]:hover{background:#d977064d}mark[data-thread-id].thread-mark-resolved{background:transparent;border-bottom:2px dashed var(--text-muted)}.selection-actions{position:fixed;z-index:9999;display:flex;gap:4px}.discuss-button{display:flex;align-items:center;gap:4px;padding:4px 10px;border:1px solid var(--border);border-radius:var(--r-sm);background:var(--surface-0);color:var(--accent);font-size:11px;font-weight:500;cursor:pointer;box-shadow:0 2px 8px #0000001f;transition:all var(--dur-fast) var(--ease)}.discuss-button:hover{background:var(--accent-soft);border-color:var(--accent)}.thread-create-popover{position:fixed;z-index:9999;min-width:280px;max-width:360px;background:var(--surface-0);border:1px solid var(--border);border-radius:var(--r-md);box-shadow:0 4px 16px #00000026;padding:10px;display:flex;flex-direction:column;gap:8px;transform:translate(-100%)}.thread-create-header{display:flex;align-items:flex-start;justify-content:space-between;gap:8px}.thread-create-quote{font-size:11px;color:var(--text-muted);font-style:italic;flex:1;line-height:1.4}.thread-create-close{border:none;background:transparent;color:var(--text-muted);cursor:pointer;padding:2px;border-radius:var(--r-sm);flex-shrink:0}.thread-create-close:hover{background:var(--surface-1);color:var(--text)}.thread-create-input{border:1px solid var(--border);border-radius:var(--r-sm);padding:8px 10px;font-size:13px;font-family:var(--font-sans);color:var(--text);background:var(--canvas);outline:none;resize:none;line-height:1.5;transition:border-color var(--dur-fast) var(--ease)}.thread-create-input:focus{border-color:var(--accent);box-shadow:var(--focus-ring)}.thread-create-footer{display:flex;align-items:center;justify-content:space-between}.thread-create-hint{font-size:10px;color:var(--text-muted)}.thread-create-send{display:flex;align-items:center;gap:4px;padding:4px 10px;border:none;border-radius:var(--r-sm);background:var(--accent);color:#fff;font-size:11px;font-weight:500;cursor:pointer;transition:background var(--dur-fast) var(--ease)}.thread-create-send:disabled{opacity:.4;cursor:default}.thread-create-send:not(:disabled):hover{background:var(--accent-hover)}.thread-filter-tabs{display:flex;gap:2px;margin-bottom:12px;padding:2px;background:var(--surface-1);border-radius:var(--r-md)}.thread-filter-tab{flex:1;padding:4px 8px;border:none;background:transparent;color:var(--text-3);font-size:10px;font-weight:500;cursor:pointer;border-radius:var(--r-sm);font-family:var(--font-sans);transition:all var(--dur-fast) var(--ease)}.thread-filter-tab:hover{color:var(--text-2)}.thread-filter-tab.active{background:var(--surface-0);color:var(--text);box-shadow:var(--inset-highlight)}.thread-card{border:1px solid var(--border);border-radius:var(--r-md);padding:10px 12px;margin-bottom:10px;transition:border-color var(--dur-fast) var(--ease),background var(--dur-fast) var(--ease)}.thread-card.thread-resolved{opacity:.7}.thread-card-highlighted{border-color:var(--accent);background:var(--accent-soft)}.thread-card-header{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;cursor:pointer}.thread-card-quote{font-size:11px;color:var(--text-muted);font-style:italic;line-height:1.4;flex:1}.thread-card-header-right{display:flex;align-items:center;gap:4px;flex-shrink:0}.thread-status-badge{font-size:10px;font-weight:600;padding:2px 8px;border-radius:var(--r-pill)}.thread-status-open{background:var(--accent-soft);color:var(--accent)}.thread-status-resolved{background:var(--green-soft);color:var(--green)}.thread-card-conversation{margin-top:8px;max-height:300px;overflow-y:auto}.thread-card-turn{margin-bottom:8px;padding-bottom:8px;border-bottom:1px solid var(--border-muted)}.thread-card-turn:last-child{border-bottom:none;margin-bottom:0;padding-bottom:0}.thread-card-user-message{font-size:12px;color:var(--text);background:var(--accent-soft);padding:6px 10px;border-radius:var(--r-sm);border:1px solid rgba(217,119,6,.15);margin-bottom:4px}.thread-card-ai-response{font-size:12px;color:var(--text-2);padding:4px 0;line-height:1.5}.thread-card-ai-response p{margin-bottom:6px}.thread-card-ai-response code{font-family:var(--font-mono);font-size:11px;background:var(--surface-1);padding:1px 4px;border-radius:var(--r-sm);color:var(--accent)}.thread-card-ai-response pre{background:var(--surface-0);border:1px solid var(--border);border-radius:var(--r-sm);padding:8px;overflow-x:auto;margin-bottom:6px}.thread-card-ai-response pre code{background:none;padding:0}.thread-card-pending{font-size:11px;color:var(--text-muted);font-style:italic;padding:4px 0}.thread-card-placeholder{font-size:11px;color:var(--text-muted);font-style:italic;padding:8px 0}.thread-card-followup{display:flex;gap:4px;margin-top:8px}.thread-card-followup-input{flex:1;border:1px solid var(--border);border-radius:var(--r-sm);padding:6px 8px;font-size:12px;font-family:var(--font-sans);color:var(--text);background:var(--canvas);outline:none;resize:none;line-height:1.5;transition:border-color var(--dur-fast) var(--ease)}.thread-card-followup-input:focus{border-color:var(--accent)}.thread-card-followup-input:disabled{opacity:.5}.thread-card-followup-send{width:28px;height:28px;border:none;border-radius:var(--r-sm);background:var(--accent);color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:background var(--dur-fast) var(--ease)}.thread-card-followup-send:disabled{opacity:.4;cursor:default}.thread-card-followup-send:not(:disabled):hover{background:var(--accent-hover)}.thread-locate-btn{background:none;border:none;color:var(--text-muted);cursor:pointer;padding:2px;border-radius:3px;display:flex;align-items:center;transition:all var(--dur-fast) var(--ease);flex-shrink:0}.thread-locate-btn:hover{color:var(--accent);background:var(--accent-soft)}.thread-mark-flash{animation:mark-flash 1.5s ease-out}@keyframes mark-flash{0%{background:#d9770680}to{background:#d977062e}}.thread-card-actions{display:flex;gap:6px;margin-top:8px;padding-top:8px;border-top:1px solid var(--border-muted)}.thread-action-btn{display:flex;align-items:center;gap:4px;padding:4px 8px;border:1px solid var(--border);border-radius:var(--r-sm);background:transparent;color:var(--text-3);font-size:11px;cursor:pointer;font-family:var(--font-sans);transition:all var(--dur-fast) var(--ease)}.thread-action-btn:hover{background:var(--surface-1);color:var(--text)}.thread-action-btn.thread-action-danger:hover{background:#dc262614;color:var(--red);border-color:#dc262633}.agent-call-card{display:flex;align-items:center;gap:8px;padding:8px 14px;border-left:3px solid var(--accent);background:var(--surface);border-radius:0 var(--r-sm) var(--r-sm) 0}.agent-call-icon{color:var(--text-2);display:flex;align-items:center}.agent-call-desc{font-size:13px;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.agent-call-status{font-size:10px;font-family:var(--font-mono);padding:1px 6px;border-radius:var(--r-sm)}.agent-call-status.running{color:var(--accent);background:var(--accent-soft)}.agent-call-status.done{color:var(--green);background:#22c55e14}.agent-call-status.failed{color:var(--red);background:#ef444414}
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;450;510;590;600&family=JetBrains+Mono:wght@400;500&display=swap"
|
|
11
11
|
rel="stylesheet"
|
|
12
12
|
/>
|
|
13
|
-
<script type="module" crossorigin src="/assets/index-
|
|
14
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
13
|
+
<script type="module" crossorigin src="/assets/index-BYo-lnxm.js"></script>
|
|
14
|
+
<link rel="stylesheet" crossorigin href="/assets/index-Dwwhx7YN.css">
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
|
17
17
|
<div id="root"></div>
|
|
@@ -1016,6 +1016,9 @@
|
|
|
1016
1016
|
"x64"
|
|
1017
1017
|
],
|
|
1018
1018
|
"dev": true,
|
|
1019
|
+
"libc": [
|
|
1020
|
+
"glibc"
|
|
1021
|
+
],
|
|
1019
1022
|
"license": "MIT",
|
|
1020
1023
|
"optional": true,
|
|
1021
1024
|
"os": [
|
|
@@ -1135,6 +1138,9 @@
|
|
|
1135
1138
|
"arm"
|
|
1136
1139
|
],
|
|
1137
1140
|
"dev": true,
|
|
1141
|
+
"libc": [
|
|
1142
|
+
"glibc"
|
|
1143
|
+
],
|
|
1138
1144
|
"license": "MIT",
|
|
1139
1145
|
"optional": true,
|
|
1140
1146
|
"os": [
|
|
@@ -1149,6 +1155,9 @@
|
|
|
1149
1155
|
"arm"
|
|
1150
1156
|
],
|
|
1151
1157
|
"dev": true,
|
|
1158
|
+
"libc": [
|
|
1159
|
+
"musl"
|
|
1160
|
+
],
|
|
1152
1161
|
"license": "MIT",
|
|
1153
1162
|
"optional": true,
|
|
1154
1163
|
"os": [
|
|
@@ -1163,6 +1172,9 @@
|
|
|
1163
1172
|
"arm64"
|
|
1164
1173
|
],
|
|
1165
1174
|
"dev": true,
|
|
1175
|
+
"libc": [
|
|
1176
|
+
"glibc"
|
|
1177
|
+
],
|
|
1166
1178
|
"license": "MIT",
|
|
1167
1179
|
"optional": true,
|
|
1168
1180
|
"os": [
|
|
@@ -1177,6 +1189,9 @@
|
|
|
1177
1189
|
"arm64"
|
|
1178
1190
|
],
|
|
1179
1191
|
"dev": true,
|
|
1192
|
+
"libc": [
|
|
1193
|
+
"musl"
|
|
1194
|
+
],
|
|
1180
1195
|
"license": "MIT",
|
|
1181
1196
|
"optional": true,
|
|
1182
1197
|
"os": [
|
|
@@ -1191,6 +1206,9 @@
|
|
|
1191
1206
|
"loong64"
|
|
1192
1207
|
],
|
|
1193
1208
|
"dev": true,
|
|
1209
|
+
"libc": [
|
|
1210
|
+
"glibc"
|
|
1211
|
+
],
|
|
1194
1212
|
"license": "MIT",
|
|
1195
1213
|
"optional": true,
|
|
1196
1214
|
"os": [
|
|
@@ -1205,6 +1223,9 @@
|
|
|
1205
1223
|
"loong64"
|
|
1206
1224
|
],
|
|
1207
1225
|
"dev": true,
|
|
1226
|
+
"libc": [
|
|
1227
|
+
"musl"
|
|
1228
|
+
],
|
|
1208
1229
|
"license": "MIT",
|
|
1209
1230
|
"optional": true,
|
|
1210
1231
|
"os": [
|
|
@@ -1219,6 +1240,9 @@
|
|
|
1219
1240
|
"ppc64"
|
|
1220
1241
|
],
|
|
1221
1242
|
"dev": true,
|
|
1243
|
+
"libc": [
|
|
1244
|
+
"glibc"
|
|
1245
|
+
],
|
|
1222
1246
|
"license": "MIT",
|
|
1223
1247
|
"optional": true,
|
|
1224
1248
|
"os": [
|
|
@@ -1233,6 +1257,9 @@
|
|
|
1233
1257
|
"ppc64"
|
|
1234
1258
|
],
|
|
1235
1259
|
"dev": true,
|
|
1260
|
+
"libc": [
|
|
1261
|
+
"musl"
|
|
1262
|
+
],
|
|
1236
1263
|
"license": "MIT",
|
|
1237
1264
|
"optional": true,
|
|
1238
1265
|
"os": [
|
|
@@ -1247,6 +1274,9 @@
|
|
|
1247
1274
|
"riscv64"
|
|
1248
1275
|
],
|
|
1249
1276
|
"dev": true,
|
|
1277
|
+
"libc": [
|
|
1278
|
+
"glibc"
|
|
1279
|
+
],
|
|
1250
1280
|
"license": "MIT",
|
|
1251
1281
|
"optional": true,
|
|
1252
1282
|
"os": [
|
|
@@ -1261,6 +1291,9 @@
|
|
|
1261
1291
|
"riscv64"
|
|
1262
1292
|
],
|
|
1263
1293
|
"dev": true,
|
|
1294
|
+
"libc": [
|
|
1295
|
+
"musl"
|
|
1296
|
+
],
|
|
1264
1297
|
"license": "MIT",
|
|
1265
1298
|
"optional": true,
|
|
1266
1299
|
"os": [
|
|
@@ -1275,6 +1308,9 @@
|
|
|
1275
1308
|
"s390x"
|
|
1276
1309
|
],
|
|
1277
1310
|
"dev": true,
|
|
1311
|
+
"libc": [
|
|
1312
|
+
"glibc"
|
|
1313
|
+
],
|
|
1278
1314
|
"license": "MIT",
|
|
1279
1315
|
"optional": true,
|
|
1280
1316
|
"os": [
|
|
@@ -1289,6 +1325,9 @@
|
|
|
1289
1325
|
"x64"
|
|
1290
1326
|
],
|
|
1291
1327
|
"dev": true,
|
|
1328
|
+
"libc": [
|
|
1329
|
+
"glibc"
|
|
1330
|
+
],
|
|
1292
1331
|
"license": "MIT",
|
|
1293
1332
|
"optional": true,
|
|
1294
1333
|
"os": [
|
|
@@ -1303,6 +1342,9 @@
|
|
|
1303
1342
|
"x64"
|
|
1304
1343
|
],
|
|
1305
1344
|
"dev": true,
|
|
1345
|
+
"libc": [
|
|
1346
|
+
"musl"
|
|
1347
|
+
],
|
|
1306
1348
|
"license": "MIT",
|
|
1307
1349
|
"optional": true,
|
|
1308
1350
|
"os": [
|
|
@@ -1853,9 +1895,9 @@
|
|
|
1853
1895
|
}
|
|
1854
1896
|
},
|
|
1855
1897
|
"node_modules/baseline-browser-mapping": {
|
|
1856
|
-
"version": "2.11.
|
|
1857
|
-
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.
|
|
1858
|
-
"integrity": "sha512-
|
|
1898
|
+
"version": "2.11.15",
|
|
1899
|
+
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.15.tgz",
|
|
1900
|
+
"integrity": "sha512-FwMjJJ7HnyZpWe+oWxegG0fezZyBZUagI5LZEoO3GCbtbKNwRfMH9Ue5d5v01PNePBy1QSfPSDTTeVL0Hb9EzA==",
|
|
1859
1901
|
"dev": true,
|
|
1860
1902
|
"license": "Apache-2.0",
|
|
1861
1903
|
"bin": {
|
|
@@ -2183,9 +2225,9 @@
|
|
|
2183
2225
|
"license": "MIT"
|
|
2184
2226
|
},
|
|
2185
2227
|
"node_modules/electron-to-chromium": {
|
|
2186
|
-
"version": "1.5.
|
|
2187
|
-
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.
|
|
2188
|
-
"integrity": "sha512-
|
|
2228
|
+
"version": "1.5.409",
|
|
2229
|
+
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.409.tgz",
|
|
2230
|
+
"integrity": "sha512-ChI4N44d0B4A6C8prnNjMOaGgE59fUyEVYcRYm2XEXIjMbbvF5i9UL1cblDbpGqiU0uS8FE8UcKxqZqTXdmzbQ==",
|
|
2189
2231
|
"dev": true,
|
|
2190
2232
|
"license": "ISC"
|
|
2191
2233
|
},
|
|
@@ -4137,9 +4179,9 @@
|
|
|
4137
4179
|
"license": "ISC"
|
|
4138
4180
|
},
|
|
4139
4181
|
"node_modules/zustand": {
|
|
4140
|
-
"version": "5.0.
|
|
4141
|
-
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.
|
|
4142
|
-
"integrity": "sha512
|
|
4182
|
+
"version": "5.0.15",
|
|
4183
|
+
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.15.tgz",
|
|
4184
|
+
"integrity": "sha512-MpSEjRiBkA9crSYeOUH32rJC7SVqAbm0Fqcqge/bUi2PPoLcBWKOsG+C8mevmpr8TwXHBVkChbbJiyvkE+i/3A==",
|
|
4143
4185
|
"license": "MIT",
|
|
4144
4186
|
"engines": {
|
|
4145
4187
|
"node": ">=12.20.0"
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { render, screen, fireEvent, cleanup } from '@testing-library/react';
|
|
3
|
+
import ThreadsPanel from '../components/panels/ThreadsPanel.tsx';
|
|
4
|
+
import { useStore } from '../store/store.ts';
|
|
5
|
+
import { makeSession, makeDetail, makeTurn } from '../test/fixtures.ts';
|
|
6
|
+
import type { DiscussionThread } from '../types/index.ts';
|
|
7
|
+
|
|
8
|
+
const NOW = '2025-01-01T00:00:00Z';
|
|
9
|
+
|
|
10
|
+
function makeThread(id: string, sessionId: string, overrides: Partial<DiscussionThread> = {}): DiscussionThread {
|
|
11
|
+
return {
|
|
12
|
+
id,
|
|
13
|
+
session_id: sessionId,
|
|
14
|
+
item_id: 'i1',
|
|
15
|
+
turn_id: 't1',
|
|
16
|
+
start_offset: 0,
|
|
17
|
+
end_offset: 10,
|
|
18
|
+
selected_text: 'Some selected text',
|
|
19
|
+
status: 'open',
|
|
20
|
+
resolved_at: null,
|
|
21
|
+
created_at: NOW,
|
|
22
|
+
updated_at: NOW,
|
|
23
|
+
external_thread_id: null,
|
|
24
|
+
...overrides,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe('ThreadsPanel', () => {
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
// Reset store to a clean state with a session
|
|
31
|
+
useStore.setState({
|
|
32
|
+
sessions: [],
|
|
33
|
+
sessionDetails: {},
|
|
34
|
+
activeSessionId: null,
|
|
35
|
+
openTabs: [],
|
|
36
|
+
activeTurnIndex: 0,
|
|
37
|
+
submittingPrompt: null,
|
|
38
|
+
restoredPrompt: null,
|
|
39
|
+
activeRightTab: 'threads',
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
afterEach(() => {
|
|
44
|
+
cleanup();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('shows empty state when no session selected', () => {
|
|
48
|
+
render(<ThreadsPanel />);
|
|
49
|
+
expect(screen.getByText('No session selected')).toBeInTheDocument();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('shows empty state with instructions when no threads exist', () => {
|
|
53
|
+
const session = makeSession('s1');
|
|
54
|
+
const detail = makeDetail('s1', []);
|
|
55
|
+
useStore.setState({
|
|
56
|
+
sessions: [session],
|
|
57
|
+
sessionDetails: { s1: detail },
|
|
58
|
+
activeSessionId: 's1',
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
render(<ThreadsPanel />);
|
|
62
|
+
expect(screen.getByText('No threads yet.')).toBeInTheDocument();
|
|
63
|
+
expect(screen.getByText(/Select text in an agent response/)).toBeInTheDocument();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('renders an open thread card with quoted text', () => {
|
|
67
|
+
const thread = makeThread('th1', 's1');
|
|
68
|
+
const detail = makeDetail('s1', [], { threads: [thread] });
|
|
69
|
+
|
|
70
|
+
useStore.setState({
|
|
71
|
+
sessions: [makeSession('s1')],
|
|
72
|
+
sessionDetails: { s1: detail },
|
|
73
|
+
activeSessionId: 's1',
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
render(<ThreadsPanel />);
|
|
77
|
+
expect(screen.getByText(/Some selected text/)).toBeInTheDocument();
|
|
78
|
+
expect(screen.getByText('Open')).toBeInTheDocument();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('shows filter tabs with counts', () => {
|
|
82
|
+
const openThread = makeThread('th1', 's1', { status: 'open' });
|
|
83
|
+
const resolvedThread = makeThread('th2', 's1', { status: 'resolved' });
|
|
84
|
+
const detail = makeDetail('s1', [], { threads: [openThread, resolvedThread] });
|
|
85
|
+
|
|
86
|
+
useStore.setState({
|
|
87
|
+
sessions: [makeSession('s1')],
|
|
88
|
+
sessionDetails: { s1: detail },
|
|
89
|
+
activeSessionId: 's1',
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
render(<ThreadsPanel />);
|
|
93
|
+
expect(screen.getByText(/Open \(1\)/)).toBeInTheDocument();
|
|
94
|
+
expect(screen.getByText(/Resolved \(1\)/)).toBeInTheDocument();
|
|
95
|
+
expect(screen.getByText(/All \(2\)/)).toBeInTheDocument();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('filters threads by resolved status', () => {
|
|
99
|
+
const openThread = makeThread('th1', 's1', { status: 'open', selected_text: 'Open thread text' });
|
|
100
|
+
const resolvedThread = makeThread('th2', 's1', { status: 'resolved', selected_text: 'Resolved thread text' });
|
|
101
|
+
const detail = makeDetail('s1', [], { threads: [openThread, resolvedThread] });
|
|
102
|
+
|
|
103
|
+
useStore.setState({
|
|
104
|
+
sessions: [makeSession('s1')],
|
|
105
|
+
sessionDetails: { s1: detail },
|
|
106
|
+
activeSessionId: 's1',
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
render(<ThreadsPanel />);
|
|
110
|
+
// Click "Resolved" filter
|
|
111
|
+
fireEvent.click(screen.getByText(/Resolved/));
|
|
112
|
+
expect(screen.getByText(/Resolved thread text/)).toBeInTheDocument();
|
|
113
|
+
expect(screen.queryByText(/Open thread text/)).not.toBeInTheDocument();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('shows thread conversation history from turns', () => {
|
|
117
|
+
const thread = makeThread('th1', 's1');
|
|
118
|
+
const threadTurn = makeTurn('tt1', 's1', {
|
|
119
|
+
thread_id: 'th1',
|
|
120
|
+
user_prompt: '[Quoting from your previous response]:\n> Some text\n\nWhat is this?',
|
|
121
|
+
status: 'completed',
|
|
122
|
+
items: [{
|
|
123
|
+
id: 'ti1',
|
|
124
|
+
turn_id: 'tt1',
|
|
125
|
+
external_item_id: 'ext-ti1',
|
|
126
|
+
type: 'agent_message' as const,
|
|
127
|
+
data: { text: 'This is an explanation.' },
|
|
128
|
+
status: 'completed' as const,
|
|
129
|
+
created_at: NOW,
|
|
130
|
+
updated_at: NOW,
|
|
131
|
+
}],
|
|
132
|
+
});
|
|
133
|
+
const detail = makeDetail('s1', [threadTurn], { threads: [thread] });
|
|
134
|
+
|
|
135
|
+
useStore.setState({
|
|
136
|
+
sessions: [makeSession('s1')],
|
|
137
|
+
sessionDetails: { s1: detail },
|
|
138
|
+
activeSessionId: 's1',
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
render(<ThreadsPanel />);
|
|
142
|
+
// User message (with quote prefix stripped)
|
|
143
|
+
expect(screen.getByText('What is this?')).toBeInTheDocument();
|
|
144
|
+
// AI response
|
|
145
|
+
expect(screen.getByText('This is an explanation.')).toBeInTheDocument();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('shows follow-up input for open threads', () => {
|
|
149
|
+
const thread = makeThread('th1', 's1', { status: 'open' });
|
|
150
|
+
const detail = makeDetail('s1', [], { threads: [thread] });
|
|
151
|
+
|
|
152
|
+
useStore.setState({
|
|
153
|
+
sessions: [makeSession('s1')],
|
|
154
|
+
sessionDetails: { s1: detail },
|
|
155
|
+
activeSessionId: 's1',
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
render(<ThreadsPanel />);
|
|
159
|
+
expect(screen.getByPlaceholderText('Type a follow-up…')).toBeInTheDocument();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('shows resolve and delete actions for open threads', () => {
|
|
163
|
+
const thread = makeThread('th1', 's1', { status: 'open' });
|
|
164
|
+
const detail = makeDetail('s1', [], { threads: [thread] });
|
|
165
|
+
|
|
166
|
+
useStore.setState({
|
|
167
|
+
sessions: [makeSession('s1')],
|
|
168
|
+
sessionDetails: { s1: detail },
|
|
169
|
+
activeSessionId: 's1',
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
render(<ThreadsPanel />);
|
|
173
|
+
expect(screen.getByText('Resolve')).toBeInTheDocument();
|
|
174
|
+
expect(screen.getByText('Delete')).toBeInTheDocument();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('shows reopen action for resolved threads', () => {
|
|
178
|
+
const thread = makeThread('th1', 's1', { status: 'resolved' });
|
|
179
|
+
const detail = makeDetail('s1', [], { threads: [thread] });
|
|
180
|
+
|
|
181
|
+
useStore.setState({
|
|
182
|
+
sessions: [makeSession('s1')],
|
|
183
|
+
sessionDetails: { s1: detail },
|
|
184
|
+
activeSessionId: 's1',
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
render(<ThreadsPanel />);
|
|
188
|
+
// Switch to "All" filter to see resolved threads
|
|
189
|
+
fireEvent.click(screen.getByText(/All/));
|
|
190
|
+
expect(screen.getByText('Reopen')).toBeInTheDocument();
|
|
191
|
+
});
|
|
192
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { apiGet } from './client.ts';
|
|
2
|
-
import type { ProviderDefinition } from '../types/index.ts';
|
|
2
|
+
import type { ProviderDefinition, ProviderSlashCommand } from '../types/index.ts';
|
|
3
3
|
|
|
4
4
|
/** Fetch all provider definitions with capability flags. */
|
|
5
5
|
export function fetchProviders(): Promise<ProviderDefinition[]> {
|
|
@@ -9,4 +9,9 @@ export function fetchProviders(): Promise<ProviderDefinition[]> {
|
|
|
9
9
|
/** Fetch a single provider definition by ID. */
|
|
10
10
|
export function fetchProvider(id: string): Promise<ProviderDefinition> {
|
|
11
11
|
return apiGet<ProviderDefinition>(`/providers/${id}`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Fetch provider-native slash commands (e.g. Claude CLI /compact, /clear). */
|
|
15
|
+
export function fetchProviderCommands(id: string): Promise<ProviderSlashCommand[]> {
|
|
16
|
+
return apiGet<ProviderSlashCommand[]>(`/providers/${id}/commands`);
|
|
12
17
|
}
|
|
@@ -42,6 +42,8 @@ function makeSession(overrides: Partial<Session> = {}): Session {
|
|
|
42
42
|
lifecycle: 'working',
|
|
43
43
|
has_unreviewed: 0,
|
|
44
44
|
runtime_config: null,
|
|
45
|
+
rewound_at_turn_id: null,
|
|
46
|
+
pre_rewind_thread_id: null,
|
|
45
47
|
created_at: NOW,
|
|
46
48
|
updated_at: NOW,
|
|
47
49
|
...overrides,
|
|
@@ -59,6 +61,8 @@ function makeTurn(overrides: Partial<Turn> = {}): Turn {
|
|
|
59
61
|
external_turn_id: null,
|
|
60
62
|
status: 'completed',
|
|
61
63
|
error: null,
|
|
64
|
+
thread_id: null,
|
|
65
|
+
attachments: null,
|
|
62
66
|
created_at: NOW,
|
|
63
67
|
updated_at: NOW,
|
|
64
68
|
items: [],
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { apiGet, apiPost, apiDelete, apiPatch } from './client.ts';
|
|
2
|
-
import type { Session, SessionDetail, Turn } from '../types/index.ts';
|
|
2
|
+
import type { Session, SessionDetail, Turn, Attachment } from '../types/index.ts';
|
|
3
3
|
|
|
4
4
|
/** Fetch all sessions, ordered by creation date (newest first). */
|
|
5
5
|
export function fetchSessions(): Promise<Session[]> {
|
|
@@ -28,9 +28,12 @@ export function deleteSession(id: string): Promise<{ success: boolean }> {
|
|
|
28
28
|
return apiDelete<{ success: boolean }>(`/sessions/${id}`);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
/** Submit a user prompt to a session — starts a
|
|
32
|
-
export function submitPrompt(sessionId: string, userPrompt: string): Promise<Turn> {
|
|
33
|
-
return apiPost<Turn>(`/sessions/${sessionId}/turns`, {
|
|
31
|
+
/** Submit a user prompt to a session — starts a turn (fire-and-forget). */
|
|
32
|
+
export function submitPrompt(sessionId: string, userPrompt: string, attachments?: Attachment[]): Promise<Turn> {
|
|
33
|
+
return apiPost<Turn>(`/sessions/${sessionId}/turns`, {
|
|
34
|
+
user_prompt: userPrompt,
|
|
35
|
+
...(attachments?.length ? { attachments } : {}),
|
|
36
|
+
});
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
/** Cancel the currently running turn in a session. */
|
|
@@ -78,11 +81,16 @@ export function updateRuntimeConfig(
|
|
|
78
81
|
return apiPatch<Session>(`/sessions/${sessionId}`, { runtime_config: runtimeConfig });
|
|
79
82
|
}
|
|
80
83
|
|
|
81
|
-
/** Rewind a session to a specific turn —
|
|
84
|
+
/** Rewind a session to a specific turn — hides turns after the target (non-destructive), forks the provider session. */
|
|
82
85
|
export function rewindSession(sessionId: string, turnId: string): Promise<Session> {
|
|
83
86
|
return apiPost<Session>(`/sessions/${sessionId}/rewind`, { turn_id: turnId });
|
|
84
87
|
}
|
|
85
88
|
|
|
89
|
+
/** Restore a rewound session — unhides all turns, restores the original provider thread. */
|
|
90
|
+
export function restoreSession(sessionId: string): Promise<Session> {
|
|
91
|
+
return apiPost<Session>(`/sessions/${sessionId}/restore`, {});
|
|
92
|
+
}
|
|
93
|
+
|
|
86
94
|
/** Branch a session from a specific turn — creates a new session with turns up to and including the target. */
|
|
87
95
|
export function branchSession(sessionId: string, turnId: string, title?: string): Promise<Session> {
|
|
88
96
|
return apiPost<Session>(`/sessions/${sessionId}/branch`, { turn_id: turnId, title });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useStore } from '../store/store.ts';
|
|
2
|
-
import type { Item, TokenUsage } from '../types/index.ts';
|
|
2
|
+
import type { Item, TokenUsage, Turn, DiscussionThread, ContextUsage } from '../types/index.ts';
|
|
3
3
|
|
|
4
4
|
type WsEventHandler = (data: unknown) => void;
|
|
5
5
|
|
|
@@ -119,6 +119,13 @@ interface TokenUsageUpdatedPayload {
|
|
|
119
119
|
usage: TokenUsage;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
interface ContextUsagePayload {
|
|
123
|
+
sessionId: string;
|
|
124
|
+
totalTokens: number;
|
|
125
|
+
contextWindow: number;
|
|
126
|
+
percentage: number;
|
|
127
|
+
}
|
|
128
|
+
|
|
122
129
|
interface TurnFailedPayload {
|
|
123
130
|
sessionId: string;
|
|
124
131
|
turnId: string;
|
|
@@ -130,6 +137,34 @@ interface SessionTitlePayload {
|
|
|
130
137
|
title: string;
|
|
131
138
|
}
|
|
132
139
|
|
|
140
|
+
interface BackgroundTasksPayload {
|
|
141
|
+
sessionId: string;
|
|
142
|
+
tasks: Array<{ task_id: string; task_type: string; description: string }>;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
interface ThreadCreatedPayload {
|
|
146
|
+
sessionId: string;
|
|
147
|
+
thread: DiscussionThread;
|
|
148
|
+
turn: Turn;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
interface ThreadFollowupPayload {
|
|
152
|
+
sessionId: string;
|
|
153
|
+
threadId: string;
|
|
154
|
+
turn: Turn;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
interface ThreadResolvedPayload {
|
|
158
|
+
sessionId: string;
|
|
159
|
+
threadId: string;
|
|
160
|
+
status: 'open' | 'resolved';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
interface ThreadDeletedPayload {
|
|
164
|
+
sessionId: string;
|
|
165
|
+
threadId: string;
|
|
166
|
+
}
|
|
167
|
+
|
|
133
168
|
const HANDLERS: Record<string, WsEventHandler> = {
|
|
134
169
|
'turn:started': (data) => {
|
|
135
170
|
const p = data as TurnEventPayload;
|
|
@@ -159,6 +194,15 @@ const HANDLERS: Record<string, WsEventHandler> = {
|
|
|
159
194
|
const p = data as TokenUsageUpdatedPayload;
|
|
160
195
|
useStore.getState().onTokenUsageUpdated(p.sessionId, p.turnId, p.usage);
|
|
161
196
|
},
|
|
197
|
+
'turn:contextUsage': (data) => {
|
|
198
|
+
const p = data as ContextUsagePayload;
|
|
199
|
+
const ctx: ContextUsage = {
|
|
200
|
+
totalTokens: p.totalTokens,
|
|
201
|
+
contextWindow: p.contextWindow,
|
|
202
|
+
percentage: p.percentage,
|
|
203
|
+
};
|
|
204
|
+
useStore.getState().onContextUsageUpdated(p.sessionId, ctx);
|
|
205
|
+
},
|
|
162
206
|
'turn:failed': (data) => {
|
|
163
207
|
const p = data as TurnFailedPayload;
|
|
164
208
|
useStore.getState().onTurnFailed(p.sessionId, p.turnId, p.error);
|
|
@@ -171,4 +215,24 @@ const HANDLERS: Record<string, WsEventHandler> = {
|
|
|
171
215
|
const p = data as SessionTitlePayload;
|
|
172
216
|
useStore.getState().onSessionTitle(p.sessionId, p.title);
|
|
173
217
|
},
|
|
218
|
+
'background_tasks': (data) => {
|
|
219
|
+
const p = data as BackgroundTasksPayload;
|
|
220
|
+
useStore.getState().onBackgroundTasksChanged(p.sessionId, p.tasks);
|
|
221
|
+
},
|
|
222
|
+
'thread:created': (data) => {
|
|
223
|
+
const p = data as ThreadCreatedPayload;
|
|
224
|
+
useStore.getState().onThreadCreated(p.sessionId, p.thread, p.turn);
|
|
225
|
+
},
|
|
226
|
+
'thread:followup': (data) => {
|
|
227
|
+
const p = data as ThreadFollowupPayload;
|
|
228
|
+
useStore.getState().onThreadFollowup(p.sessionId, p.threadId, p.turn);
|
|
229
|
+
},
|
|
230
|
+
'thread:resolved': (data) => {
|
|
231
|
+
const p = data as ThreadResolvedPayload;
|
|
232
|
+
useStore.getState().onThreadResolved(p.sessionId, p.threadId, p.status);
|
|
233
|
+
},
|
|
234
|
+
'thread:deleted': (data) => {
|
|
235
|
+
const p = data as ThreadDeletedPayload;
|
|
236
|
+
useStore.getState().onThreadDeleted(p.sessionId, p.threadId);
|
|
237
|
+
},
|
|
174
238
|
};
|