@phenx-inc/ctlsurf 0.15.2 → 0.16.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.
- package/out/headless/index.mjs +10 -3
- package/out/headless/index.mjs.map +2 -2
- package/out/main/index.js +54 -4
- package/out/preload/index.js +2 -0
- package/out/renderer/assets/{cssMode-CPfLk04b.js → cssMode-Xx1-SozN.js} +3 -3
- package/out/renderer/assets/{freemarker2-CDHpuFqv.js → freemarker2-DM49yPw1.js} +1 -1
- package/out/renderer/assets/{handlebars-CgX9Jz36.js → handlebars-DJJBblpV.js} +1 -1
- package/out/renderer/assets/{html-DdgFccDG.js → html-DnMnJOxw.js} +1 -1
- package/out/renderer/assets/{htmlMode-CRHIwtvP.js → htmlMode-BG5u2CSH.js} +3 -3
- package/out/renderer/assets/{index-DVdWgw7p.js → index-D65QIRev.js} +300 -84
- package/out/renderer/assets/{index-3sFJEz7i.css → index-DID1LlMU.css} +88 -0
- package/out/renderer/assets/{javascript-CHhxL0s7.js → javascript-Dci_phmN.js} +2 -2
- package/out/renderer/assets/{jsonMode-Cy7-eWai.js → jsonMode-Dmp3JJSm.js} +3 -3
- package/out/renderer/assets/{liquid-vMkF9pfk.js → liquid-C9KVysk1.js} +1 -1
- package/out/renderer/assets/{lspLanguageFeatures-nAygNzuG.js → lspLanguageFeatures-IDD0-KWa.js} +1 -1
- package/out/renderer/assets/{mdx-C4UVz5dW.js → mdx-ascmBFXX.js} +1 -1
- package/out/renderer/assets/{python-BHemzN9g.js → python-DcbM4Hy5.js} +1 -1
- package/out/renderer/assets/{razor-DH6NPzbo.js → razor-XJROxpYK.js} +1 -1
- package/out/renderer/assets/{tsMode-DxBdlGZB.js → tsMode-EXbzul3n.js} +1 -1
- package/out/renderer/assets/{typescript-BRCOaBQ5.js → typescript-DhTtFBLt.js} +1 -1
- package/out/renderer/assets/{xml-DVv9gDP1.js → xml-BLfEkVl1.js} +1 -1
- package/out/renderer/assets/{yaml-jB-C3CdS.js → yaml-DpzsqvWa.js} +1 -1
- package/out/renderer/index.html +2 -2
- package/package.json +1 -1
- package/src/main/index.ts +52 -1
- package/src/main/orchestrator.ts +18 -3
- package/src/preload/index.ts +4 -0
- package/src/renderer/App.tsx +179 -50
- package/src/renderer/components/AgentPicker.tsx +61 -10
- package/src/renderer/components/ProjectPicker.tsx +44 -13
- package/src/renderer/components/TerminalPanel.tsx +34 -7
- package/src/renderer/styles.css +88 -0
|
@@ -104,6 +104,7 @@ interface AgentConfig {
|
|
|
104
104
|
interface TerminalPanelProps {
|
|
105
105
|
tabId: string
|
|
106
106
|
agent: AgentConfig | null
|
|
107
|
+
sessionVersion: number
|
|
107
108
|
onSpawn: (tabId: string, agent: AgentConfig) => Promise<void>
|
|
108
109
|
onExit: (tabId: string) => void
|
|
109
110
|
isActive: boolean
|
|
@@ -118,7 +119,7 @@ type TermState = {
|
|
|
118
119
|
fitAddon: FitAddon
|
|
119
120
|
unsubData: (() => void) | null
|
|
120
121
|
unsubExit: (() => void) | null
|
|
121
|
-
|
|
122
|
+
spawnedSessionKey: string | null
|
|
122
123
|
// Follow mode: while true, new output keeps the view pinned to the bottom.
|
|
123
124
|
pinnedToBottom: boolean
|
|
124
125
|
// When NOT following, the absolute buffer line held at the top of the
|
|
@@ -280,7 +281,7 @@ function getOrCreateTerminal(tabId: string, onExit: (tabId: string) => void): {
|
|
|
280
281
|
fitAddon,
|
|
281
282
|
unsubData: null,
|
|
282
283
|
unsubExit: null,
|
|
283
|
-
|
|
284
|
+
spawnedSessionKey: null,
|
|
284
285
|
pinnedToBottom: true,
|
|
285
286
|
anchorLine: null,
|
|
286
287
|
userScrollPending: false,
|
|
@@ -343,6 +344,29 @@ export function focusTerminal(tabId: string): void {
|
|
|
343
344
|
_terminals.get(tabId)?.terminal.focus()
|
|
344
345
|
}
|
|
345
346
|
|
|
347
|
+
// Capture the rendered transcript before a live session is replaced. This is
|
|
348
|
+
// intentionally sourced from the terminal the user actually saw, so it also
|
|
349
|
+
// works for shells, custom agents, and remote SSH sessions whose native JSONL
|
|
350
|
+
// transcripts are unavailable on this machine.
|
|
351
|
+
const MAX_HANDOFF_CHARS = 1_000_000
|
|
352
|
+
export function captureTerminalSession(tabId: string): string {
|
|
353
|
+
const terminal = _terminals.get(tabId)?.terminal
|
|
354
|
+
if (!terminal) return ''
|
|
355
|
+
const buffer = terminal.buffer.active
|
|
356
|
+
const lines: string[] = []
|
|
357
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
358
|
+
const line = buffer.getLine(i)
|
|
359
|
+
if (!line) continue
|
|
360
|
+
const text = line.translateToString(true)
|
|
361
|
+
if (line.isWrapped && lines.length > 0) lines[lines.length - 1] += text
|
|
362
|
+
else lines.push(text)
|
|
363
|
+
}
|
|
364
|
+
while (lines.length && lines[lines.length - 1].trim() === '') lines.pop()
|
|
365
|
+
const text = lines.join('\n').trim()
|
|
366
|
+
if (text.length <= MAX_HANDOFF_CHARS) return text
|
|
367
|
+
return `[Earlier terminal output omitted; showing the last ${MAX_HANDOFF_CHARS.toLocaleString()} characters.]\n${text.slice(-MAX_HANDOFF_CHARS)}`
|
|
368
|
+
}
|
|
369
|
+
|
|
346
370
|
export function destroyTerminal(tabId: string): void {
|
|
347
371
|
const state = _terminals.get(tabId)
|
|
348
372
|
if (!state) return
|
|
@@ -354,7 +378,7 @@ export function destroyTerminal(tabId: string): void {
|
|
|
354
378
|
|
|
355
379
|
// ─── Component ───────────────────────────────────────
|
|
356
380
|
|
|
357
|
-
export function TerminalPanel({ tabId, agent, onSpawn, onExit, isActive }: TerminalPanelProps) {
|
|
381
|
+
export function TerminalPanel({ tabId, agent, sessionVersion, onSpawn, onExit, isActive }: TerminalPanelProps) {
|
|
358
382
|
const containerRef = useRef<HTMLDivElement>(null)
|
|
359
383
|
const [menu, setMenu] = useState<{ x: number; y: number } | null>(null)
|
|
360
384
|
// Snapshot panel: the ⋮ button freezes the visible screen text into a static,
|
|
@@ -513,14 +537,17 @@ export function TerminalPanel({ tabId, agent, onSpawn, onExit, isActive }: Termi
|
|
|
513
537
|
}
|
|
514
538
|
}, [tabId, onExit])
|
|
515
539
|
|
|
516
|
-
// Spawn agent
|
|
540
|
+
// Spawn whenever the selected agent or session version changes. The version
|
|
541
|
+
// makes choosing a new directory (or explicitly restarting the same agent)
|
|
542
|
+
// observable even though the agent id itself did not change.
|
|
517
543
|
useEffect(() => {
|
|
518
544
|
if (!agent) return
|
|
519
545
|
const state = _terminals.get(tabId)
|
|
520
546
|
if (!state) return
|
|
521
|
-
|
|
547
|
+
const sessionKey = `${agent.id}:${sessionVersion}`
|
|
548
|
+
if (state.spawnedSessionKey === sessionKey) return
|
|
522
549
|
|
|
523
|
-
state.
|
|
550
|
+
state.spawnedSessionKey = sessionKey
|
|
524
551
|
state.terminal.clear()
|
|
525
552
|
|
|
526
553
|
onSpawn(tabId, agent).then(() => {
|
|
@@ -534,7 +561,7 @@ export function TerminalPanel({ tabId, agent, onSpawn, onExit, isActive }: Termi
|
|
|
534
561
|
}
|
|
535
562
|
state.terminal.focus()
|
|
536
563
|
})
|
|
537
|
-
}, [tabId, agent, onSpawn])
|
|
564
|
+
}, [tabId, agent, sessionVersion, onSpawn])
|
|
538
565
|
|
|
539
566
|
// Focus terminal when tab becomes active — delay fit to let the container get its layout dimensions
|
|
540
567
|
useEffect(() => {
|
package/src/renderer/styles.css
CHANGED
|
@@ -1604,6 +1604,73 @@ html, body, #root {
|
|
|
1604
1604
|
letter-spacing: 1px;
|
|
1605
1605
|
}
|
|
1606
1606
|
|
|
1607
|
+
.agent-picker-warning {
|
|
1608
|
+
margin: -2px 0 14px;
|
|
1609
|
+
padding: 9px 11px;
|
|
1610
|
+
border: 1px solid color-mix(in srgb, var(--warning) 45%, transparent);
|
|
1611
|
+
border-radius: 7px;
|
|
1612
|
+
background: color-mix(in srgb, var(--warning) 9%, transparent);
|
|
1613
|
+
color: var(--text-muted);
|
|
1614
|
+
font-size: 11px;
|
|
1615
|
+
line-height: 1.45;
|
|
1616
|
+
text-align: left;
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
.agent-handoff-options {
|
|
1620
|
+
display: grid;
|
|
1621
|
+
gap: 7px;
|
|
1622
|
+
margin: 0 0 14px;
|
|
1623
|
+
padding: 0;
|
|
1624
|
+
border: 0;
|
|
1625
|
+
text-align: left;
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
.agent-handoff-options legend {
|
|
1629
|
+
margin-bottom: 7px;
|
|
1630
|
+
color: var(--text-muted);
|
|
1631
|
+
font-size: 10px;
|
|
1632
|
+
letter-spacing: 0.8px;
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
.agent-handoff-options label {
|
|
1636
|
+
display: flex;
|
|
1637
|
+
align-items: flex-start;
|
|
1638
|
+
gap: 9px;
|
|
1639
|
+
padding: 9px 10px;
|
|
1640
|
+
border: 1px solid var(--border-subtle);
|
|
1641
|
+
border-radius: 7px;
|
|
1642
|
+
background: var(--bg-chrome);
|
|
1643
|
+
cursor: pointer;
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
.agent-handoff-options label.selected {
|
|
1647
|
+
border-color: var(--accent);
|
|
1648
|
+
background: var(--bg-chrome-hi);
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
.agent-handoff-options input {
|
|
1652
|
+
margin-top: 2px;
|
|
1653
|
+
accent-color: var(--accent);
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
.agent-handoff-options span,
|
|
1657
|
+
.agent-handoff-options strong,
|
|
1658
|
+
.agent-handoff-options small {
|
|
1659
|
+
display: block;
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
.agent-handoff-options strong {
|
|
1663
|
+
color: var(--text-bright);
|
|
1664
|
+
font-size: 12px;
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
.agent-handoff-options small {
|
|
1668
|
+
margin-top: 2px;
|
|
1669
|
+
color: var(--text-muted);
|
|
1670
|
+
font-size: 10px;
|
|
1671
|
+
line-height: 1.35;
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1607
1674
|
.agent-picker-list {
|
|
1608
1675
|
display: flex;
|
|
1609
1676
|
flex-direction: column;
|
|
@@ -1664,6 +1731,27 @@ html, body, #root {
|
|
|
1664
1731
|
accent-color: var(--accent);
|
|
1665
1732
|
}
|
|
1666
1733
|
|
|
1734
|
+
.project-notify-option {
|
|
1735
|
+
align-items: flex-start;
|
|
1736
|
+
text-align: left;
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
.project-notify-option input {
|
|
1740
|
+
margin-top: 2px;
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
.project-notify-option span,
|
|
1744
|
+
.project-notify-option small {
|
|
1745
|
+
display: block;
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
.project-notify-option small {
|
|
1749
|
+
margin-top: 2px;
|
|
1750
|
+
color: var(--text-muted);
|
|
1751
|
+
font-size: 10px;
|
|
1752
|
+
line-height: 1.35;
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1667
1755
|
/* ─── Remote (SSH) connect ───────────────────────── */
|
|
1668
1756
|
|
|
1669
1757
|
.remote-section-label {
|