opencode-sesh 2.2.0 → 2.3.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/AGENTS.md +9 -5
- package/CHANGELOG.md +7 -0
- package/README.md +1 -1
- package/dist/tui.js +647 -542
- package/package.json +1 -1
- package/tui-plugins/sesh-panel/tui.tsx +95 -39
package/package.json
CHANGED
|
@@ -217,7 +217,11 @@ function truncate(text: string, width: number): string {
|
|
|
217
217
|
// so cap it a bit lower to guarantee separation.
|
|
218
218
|
const SIDEBAR_TITLE_WIDTH = 27
|
|
219
219
|
const SIDEBAR_GROUP_WIDTH = 30
|
|
220
|
-
const
|
|
220
|
+
const HOME_TITLE_MIN = 20
|
|
221
|
+
const HOME_DIR_MIN = 8
|
|
222
|
+
const HOME_HINT_MIN_WIDTH = 32
|
|
223
|
+
const HOME_DIR_MAX = 22
|
|
224
|
+
const HOME_AGE_WIDTH = 4
|
|
221
225
|
|
|
222
226
|
// The sidebar lists every session (newest first, pins ahead) and fills
|
|
223
227
|
// whatever vertical space it gets: overflow scrolls inside the stretched
|
|
@@ -2040,13 +2044,11 @@ const HOME_SESSION_LIMIT = 6
|
|
|
2040
2044
|
// https://github.com/anomalyco/opencode/blob/45898d0f8569225eb1ddb4ee13f8581b34dff311/packages/tui/src/component/prompt/index.tsx#L1676
|
|
2041
2045
|
// https://github.com/anomalyco/opencode/blob/45898d0f8569225eb1ddb4ee13f8581b34dff311/packages/tui/src/component/logo.tsx
|
|
2042
2046
|
// https://github.com/anomalyco/opencode/blob/45898d0f8569225eb1ddb4ee13f8581b34dff311/packages/tui/src/feature-plugins/home/footer.tsx
|
|
2043
|
-
// The
|
|
2044
|
-
//
|
|
2045
|
-
//
|
|
2046
|
-
//
|
|
2047
|
-
|
|
2048
|
-
const HOME_CHROME_ROWS = 14
|
|
2049
|
-
|
|
2047
|
+
// The list floats just under the composer instead of taking layout space, so
|
|
2048
|
+
// it budgets from its measured position: whatever lies between it and the
|
|
2049
|
+
// native status footer (three lines, hidden below 44 columns or 14 rows, plus
|
|
2050
|
+
// one line of slack for other footer plugins).
|
|
2051
|
+
const HOME_FOOTER_ROWS = 3
|
|
2050
2052
|
function HomeSessions(props: { api: TuiPluginApi } & PinProps) {
|
|
2051
2053
|
const theme = () => props.api.theme.current
|
|
2052
2054
|
const home = process.env.HOME ?? ""
|
|
@@ -2233,12 +2235,38 @@ function HomeSessions(props: { api: TuiPluginApi } & PinProps) {
|
|
|
2233
2235
|
: entries()
|
|
2234
2236
|
return pinnedFirst(list, props.pins())
|
|
2235
2237
|
})
|
|
2238
|
+
// Unmeasured (before the first frame) reads as no room.
|
|
2239
|
+
const [top, setTop] = createSignal(dimensions().height)
|
|
2240
|
+
const [listWidth, setListWidth] = createSignal(Math.min(75, dimensions().width))
|
|
2241
|
+
let anchor: { y: number; width: number } | undefined
|
|
2242
|
+
// Fixed columns: marker, title (flexes), directory (last segment), age.
|
|
2243
|
+
const columns = () => {
|
|
2244
|
+
const width = Math.max(1, listWidth())
|
|
2245
|
+
const age = HOME_AGE_WIDTH
|
|
2246
|
+
const wanted = Math.max(0, ...visible().map((entry) => rowDir(entry).length))
|
|
2247
|
+
const room = Math.min(HOME_DIR_MAX, wanted, width - 1 - age - 3 - HOME_TITLE_MIN)
|
|
2248
|
+
// A starved title is worse than no directory: drop the column instead.
|
|
2249
|
+
const dir = room >= HOME_DIR_MIN || room >= wanted ? Math.max(0, room) : 0
|
|
2250
|
+
// The armed-delete prompt may not push the age out of a narrow list.
|
|
2251
|
+
const pending = pendingDelete() ? Math.max(0, Math.min(Math.max(dir, 14), width - 1 - age - 3 - 1)) : dir
|
|
2252
|
+
return { age, dir: pending, title: Math.max(1, width - 1 - age - pending - (pending > 0 ? 3 : 2)) }
|
|
2253
|
+
}
|
|
2254
|
+
const rowDir = (entry: Entry) => `${props.pins().directories.includes(entry.dir) ? "★ " : ""}${home && entry.dir === home ? "~" : shortDir(entry.dir, home)}`
|
|
2255
|
+
// Running state comes from the host's live session store: no polling here.
|
|
2256
|
+
const isRunning = (id: string): boolean => {
|
|
2257
|
+
try {
|
|
2258
|
+
return sidebarMarker(false, props.api.state.session.status(id)) === "running"
|
|
2259
|
+
} catch {
|
|
2260
|
+
return false
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2236
2263
|
const layout = createMemo(() => {
|
|
2237
2264
|
const rows = dimensions().height
|
|
2238
|
-
const
|
|
2265
|
+
const footer = dimensions().width >= 44 && rows >= 14 ? HOME_FOOTER_ROWS : 0
|
|
2266
|
+
const budget = Math.max(0, rows - footer - 1 - top())
|
|
2239
2267
|
// Drop decorative padding/spacing before sessions. Keep the full list in
|
|
2240
2268
|
// the picker when even a heading and one session cannot fit.
|
|
2241
|
-
const padding = budget >=
|
|
2269
|
+
const padding = budget >= 4 ? 1 : 0
|
|
2242
2270
|
const limit = Math.min(HOME_SESSION_LIMIT, Math.max(0, budget - padding - 1))
|
|
2243
2271
|
const count = Math.max(1, Math.min(filtered().length, limit))
|
|
2244
2272
|
return { budget, limit, padding, gap: budget >= 1 + padding + count * 2 ? 1 : 0 }
|
|
@@ -2252,8 +2280,22 @@ function HomeSessions(props: { api: TuiPluginApi } & PinProps) {
|
|
|
2252
2280
|
})
|
|
2253
2281
|
|
|
2254
2282
|
return (
|
|
2255
|
-
<box
|
|
2256
|
-
|
|
2283
|
+
<box
|
|
2284
|
+
ref={(box: { y: number; width: number }) => { anchor = box }}
|
|
2285
|
+
renderAfter={() => {
|
|
2286
|
+
const y = anchor?.y
|
|
2287
|
+
if (y !== undefined && y !== top()) queueMicrotask(() => setTop(y))
|
|
2288
|
+
const width = anchor?.width
|
|
2289
|
+
if (width && width !== listWidth()) queueMicrotask(() => setListWidth(width))
|
|
2290
|
+
}}
|
|
2291
|
+
position="absolute"
|
|
2292
|
+
top={1}
|
|
2293
|
+
left={0}
|
|
2294
|
+
right={0}
|
|
2295
|
+
maxHeight={layout().budget}
|
|
2296
|
+
overflow="hidden"
|
|
2297
|
+
>
|
|
2298
|
+
<box visible={layout().limit > 0 && props.api.route.current.name === "home"} flexDirection="column" width="100%" paddingTop={layout().padding} gap={layout().gap}>
|
|
2257
2299
|
<Show
|
|
2258
2300
|
when={entries().length > 0}
|
|
2259
2301
|
fallback={
|
|
@@ -2266,33 +2308,31 @@ function HomeSessions(props: { api: TuiPluginApi } & PinProps) {
|
|
|
2266
2308
|
>
|
|
2267
2309
|
<box height={1} flexShrink={0} flexDirection="row" justifyContent="space-between" gap={2} overflow="hidden">
|
|
2268
2310
|
<box flexDirection="row" gap={1} flexShrink={0}>
|
|
2269
|
-
<text wrapMode="none" style={{ fg: theme().textMuted }}>Recent sessions</text>
|
|
2270
|
-
<Show when={dimensions().width >= 68}>
|
|
2271
|
-
<text style={{ fg: theme().textMuted }}>· option+o for all</text>
|
|
2272
|
-
</Show>
|
|
2311
|
+
<text wrapMode="none" attributes={TextAttributes.DIM} style={{ fg: theme().textMuted }}>Recent sessions</text>
|
|
2273
2312
|
</box>
|
|
2313
|
+
<Show when={listWidth() >= HOME_HINT_MIN_WIDTH || query() || searching()}>
|
|
2274
2314
|
<box
|
|
2275
2315
|
flexDirection="row"
|
|
2276
2316
|
flexShrink={1}
|
|
2277
2317
|
minWidth={0}
|
|
2278
2318
|
gap={1}
|
|
2279
2319
|
paddingLeft={1}
|
|
2280
|
-
paddingRight={1}
|
|
2281
2320
|
backgroundColor={searching() ? theme().backgroundElement : RGBA.fromInts(0, 0, 0, 0)}
|
|
2282
2321
|
onMouseDown={(event: { stopPropagation: () => void }) => {
|
|
2283
2322
|
event.stopPropagation()
|
|
2284
2323
|
setSearching(true)
|
|
2285
2324
|
}}
|
|
2286
2325
|
>
|
|
2287
|
-
<box width={24} flexShrink={1} minWidth={0}>
|
|
2326
|
+
<box width={query() || searching() ? 24 : 12} flexShrink={1} minWidth={0}>
|
|
2288
2327
|
<text
|
|
2289
2328
|
flexGrow={1}
|
|
2290
2329
|
flexShrink={1}
|
|
2291
2330
|
overflow="hidden"
|
|
2292
2331
|
wrapMode="none"
|
|
2332
|
+
attributes={query() ? undefined : TextAttributes.DIM}
|
|
2293
2333
|
style={{ fg: query() ? theme().text : theme().textMuted }}
|
|
2294
2334
|
>
|
|
2295
|
-
{query() || (searching() ? "" : "
|
|
2335
|
+
{query() || (searching() ? "" : "option+o all")}
|
|
2296
2336
|
{searching() ? "▏" : ""}
|
|
2297
2337
|
</text>
|
|
2298
2338
|
</box>
|
|
@@ -2302,6 +2342,7 @@ function HomeSessions(props: { api: TuiPluginApi } & PinProps) {
|
|
|
2302
2342
|
</text>
|
|
2303
2343
|
</Show>
|
|
2304
2344
|
</box>
|
|
2345
|
+
</Show>
|
|
2305
2346
|
</box>
|
|
2306
2347
|
<For each={visible()}>
|
|
2307
2348
|
{(entry) => (
|
|
@@ -2320,25 +2361,31 @@ function HomeSessions(props: { api: TuiPluginApi } & PinProps) {
|
|
|
2320
2361
|
: props.api.route.navigate("session", { sessionID: entry.id })
|
|
2321
2362
|
}
|
|
2322
2363
|
>
|
|
2323
|
-
<text flexShrink={0} style={{ fg: theme().textMuted }}>
|
|
2324
|
-
○
|
|
2364
|
+
<text width={1} flexShrink={0} style={{ fg: isRunning(entry.id) ? theme().success : theme().textMuted }}>
|
|
2365
|
+
{isRunning(entry.id) ? "●" : "○"}
|
|
2325
2366
|
</text>
|
|
2326
|
-
<
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
?
|
|
2341
|
-
:
|
|
2367
|
+
<box width={columns().title} flexShrink={0} overflow="hidden">
|
|
2368
|
+
<Highlighted
|
|
2369
|
+
text={truncate(`${props.pins().sessions.includes(entry.id) ? "★ " : ""}${entry.title}`, columns().title)}
|
|
2370
|
+
query={query()}
|
|
2371
|
+
color={theme().text}
|
|
2372
|
+
matchColor={theme().warning}
|
|
2373
|
+
/>
|
|
2374
|
+
</box>
|
|
2375
|
+
<Show when={columns().dir > 0}>
|
|
2376
|
+
<text
|
|
2377
|
+
width={columns().dir}
|
|
2378
|
+
flexShrink={0}
|
|
2379
|
+
wrapMode="none"
|
|
2380
|
+
overflow="hidden"
|
|
2381
|
+
attributes={pendingDelete() === entry.id ? undefined : TextAttributes.DIM}
|
|
2382
|
+
style={{ fg: pendingDelete() === entry.id ? theme().error : theme().textMuted }}
|
|
2383
|
+
>
|
|
2384
|
+
{truncate(pendingDelete() === entry.id ? "option+d again" : rowDir(entry), columns().dir)}
|
|
2385
|
+
</text>
|
|
2386
|
+
</Show>
|
|
2387
|
+
<text width={columns().age} flexShrink={0} wrapMode="none" attributes={TextAttributes.DIM} style={{ fg: theme().textMuted }}>
|
|
2388
|
+
{ago(entry.updated).padStart(columns().age)}
|
|
2342
2389
|
</text>
|
|
2343
2390
|
</box>
|
|
2344
2391
|
)}
|
|
@@ -2352,6 +2399,15 @@ function HomeSessions(props: { api: TuiPluginApi } & PinProps) {
|
|
|
2352
2399
|
)
|
|
2353
2400
|
}
|
|
2354
2401
|
|
|
2402
|
+
// prompt.footer also mounts in session composers: only home subscribes to polling.
|
|
2403
|
+
function HomeBottom(props: { api: TuiPluginApi } & PinProps) {
|
|
2404
|
+
return (
|
|
2405
|
+
<Show when={props.api.route.current.name === "home"}>
|
|
2406
|
+
<HomeSessions api={props.api} pins={props.pins} onTogglePin={props.onTogglePin} poll={props.poll} />
|
|
2407
|
+
</Show>
|
|
2408
|
+
)
|
|
2409
|
+
}
|
|
2410
|
+
|
|
2355
2411
|
const tui: TuiPlugin = async (api) => {
|
|
2356
2412
|
const [pins, setPins] = createSignal<Pins>(emptyPins())
|
|
2357
2413
|
// Keep exploratory context while the plugin remains loaded; a full OpenCode
|
|
@@ -2386,7 +2442,7 @@ const tui: TuiPlugin = async (api) => {
|
|
|
2386
2442
|
return <SidebarSessions api={api} pins={pins} onTogglePin={onTogglePin} poll={poll} />
|
|
2387
2443
|
},
|
|
2388
2444
|
home_bottom() {
|
|
2389
|
-
return <
|
|
2445
|
+
return <HomeBottom api={api} pins={pins} onTogglePin={onTogglePin} poll={poll} />
|
|
2390
2446
|
},
|
|
2391
2447
|
},
|
|
2392
2448
|
})
|
|
@@ -3599,7 +3655,7 @@ function createApi(ctx: Plugin.Context) {
|
|
|
3599
3655
|
}
|
|
3600
3656
|
if (cfg.slots.home_bottom && ctx.options?.showHome !== false) {
|
|
3601
3657
|
slotDisposers.push(ctx.ui.slot({
|
|
3602
|
-
|
|
3658
|
+
append: "prompt.footer",
|
|
3603
3659
|
render: cfg.slots.home_bottom as any,
|
|
3604
3660
|
}))
|
|
3605
3661
|
}
|