Skip to content

Commit 0560497

Browse files
committed
docs: fix compaction cross-references and section focus
1 parent db7243c commit 0560497

File tree

17 files changed

+1440
-338
lines changed

17 files changed

+1440
-338
lines changed

packages/opencode/src/cli/cmd/tui/component/dialog-command.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,10 @@ function init() {
5656
})
5757

5858
const result = {
59-
trigger(name: string, source?: "prompt") {
59+
trigger(name: string, source?: "prompt", data?: any) {
6060
for (const option of options()) {
6161
if (option.value === name) {
62-
option.onSelect?.(dialog, source)
62+
option.onSelect?.(dialog, source, data)
6363
return
6464
}
6565
}

packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx

Lines changed: 49 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export function Autocomplete(props: {
7676
}) {
7777
const sdk = useSDK()
7878
const sync = useSync()
79-
const command = useCommandDialog()
79+
const dialog = useCommandDialog()
8080
const { theme } = useTheme()
8181
const dimensions = useTerminalDimensions()
8282
const frecency = useFrecency()
@@ -317,81 +317,88 @@ export function Autocomplete(props: {
317317
const results: AutocompleteOption[] = []
318318
const s = session()
319319
for (const command of sync.data.command) {
320-
results.push({
321-
display: "/" + command.name + (command.mcp ? " (MCP)" : ""),
322-
description: command.description,
323-
onSelect: () => {
324-
const newText = "/" + command.name + " "
325-
const cursor = props.input().logicalCursor
326-
props.input().deleteRange(0, 0, cursor.row, cursor.col)
327-
props.input().insertText(newText)
328-
props.input().cursorOffset = Bun.stringWidth(newText)
329-
},
330-
})
320+
if (command.compact && s) {
321+
results.push({
322+
display: "/" + command.name + (command.mcp ? " (MCP)" : ""),
323+
description: command.description ?? "compact the session",
324+
onSelect: () => {
325+
dialog.trigger("session.compact", "prompt", {
326+
commandName: command.name,
327+
template: command.template,
328+
})
329+
},
330+
})
331+
} else if (!command.compact) {
332+
results.push({
333+
display: "/" + command.name + (command.mcp ? " (MCP)" : ""),
334+
description: command.description,
335+
onSelect: () => {
336+
const newText = "/" + command.name + " "
337+
const cursor = props.input().logicalCursor
338+
props.input().deleteRange(0, 0, cursor.row, cursor.col)
339+
props.input().insertText(newText)
340+
props.input().cursorOffset = Bun.stringWidth(newText)
341+
},
342+
})
343+
}
331344
}
332345
if (s) {
333346
results.push(
334347
{
335348
display: "/undo",
336349
description: "undo the last message",
337350
onSelect: () => {
338-
command.trigger("session.undo")
351+
dialog.trigger("session.undo")
339352
},
340353
},
341354
{
342355
display: "/redo",
343356
description: "redo the last message",
344-
onSelect: () => command.trigger("session.redo"),
345-
},
346-
{
347-
display: "/compact",
348-
aliases: ["/summarize"],
349-
description: "compact the session",
350-
onSelect: () => command.trigger("session.compact"),
357+
onSelect: () => dialog.trigger("session.redo"),
351358
},
352359
{
353360
display: "/unshare",
354361
disabled: !s.share,
355362
description: "unshare a session",
356-
onSelect: () => command.trigger("session.unshare"),
363+
onSelect: () => dialog.trigger("session.unshare"),
357364
},
358365
{
359366
display: "/rename",
360367
description: "rename session",
361-
onSelect: () => command.trigger("session.rename"),
368+
onSelect: () => dialog.trigger("session.rename"),
362369
},
363370
{
364371
display: "/copy",
365372
description: "copy session transcript to clipboard",
366-
onSelect: () => command.trigger("session.copy"),
373+
onSelect: () => dialog.trigger("session.copy"),
367374
},
368375
{
369376
display: "/export",
370377
description: "export session transcript to file",
371-
onSelect: () => command.trigger("session.export"),
378+
onSelect: () => dialog.trigger("session.export"),
372379
},
373380
{
374381
display: "/timeline",
375382
description: "jump to message",
376-
onSelect: () => command.trigger("session.timeline"),
383+
onSelect: () => dialog.trigger("session.timeline"),
377384
},
378385
{
379386
display: "/fork",
380387
description: "fork from message",
381-
onSelect: () => command.trigger("session.fork"),
388+
onSelect: () => dialog.trigger("session.fork"),
382389
},
383390
{
384391
display: "/thinking",
385392
description: "toggle thinking visibility",
386-
onSelect: () => command.trigger("session.toggle.thinking"),
393+
onSelect: () => dialog.trigger("session.toggle.thinking"),
387394
},
388395
)
389396
if (sync.data.config.share !== "disabled") {
390397
results.push({
391398
display: "/share",
392399
disabled: !!s.share?.url,
393400
description: "share a session",
394-
onSelect: () => command.trigger("session.share"),
401+
onSelect: () => dialog.trigger("session.share"),
395402
})
396403
}
397404
}
@@ -401,64 +408,64 @@ export function Autocomplete(props: {
401408
display: "/new",
402409
aliases: ["/clear"],
403410
description: "create a new session",
404-
onSelect: () => command.trigger("session.new"),
411+
onSelect: () => dialog.trigger("session.new"),
405412
},
406413
{
407414
display: "/models",
408415
description: "list models",
409-
onSelect: () => command.trigger("model.list"),
416+
onSelect: () => dialog.trigger("model.list"),
410417
},
411418
{
412419
display: "/agents",
413420
description: "list agents",
414-
onSelect: () => command.trigger("agent.list"),
421+
onSelect: () => dialog.trigger("agent.list"),
415422
},
416423
{
417424
display: "/session",
418425
aliases: ["/resume", "/continue"],
419426
description: "list sessions",
420-
onSelect: () => command.trigger("session.list"),
427+
onSelect: () => dialog.trigger("session.list"),
421428
},
422429
{
423430
display: "/status",
424431
description: "show status",
425-
onSelect: () => command.trigger("opencode.status"),
432+
onSelect: () => dialog.trigger("opencode.status"),
426433
},
427434
{
428435
display: "/mcp",
429436
description: "toggle MCPs",
430-
onSelect: () => command.trigger("mcp.list"),
437+
onSelect: () => dialog.trigger("mcp.list"),
431438
},
432439
{
433440
display: "/theme",
434441
description: "toggle theme",
435-
onSelect: () => command.trigger("theme.switch"),
442+
onSelect: () => dialog.trigger("theme.switch"),
436443
},
437444
{
438445
display: "/editor",
439446
description: "open editor",
440-
onSelect: () => command.trigger("prompt.editor", "prompt"),
447+
onSelect: () => dialog.trigger("prompt.editor", "prompt"),
441448
},
442449
{
443450
display: "/connect",
444451
description: "connect to a provider",
445-
onSelect: () => command.trigger("provider.connect"),
452+
onSelect: () => dialog.trigger("provider.connect"),
446453
},
447454
{
448455
display: "/help",
449456
description: "show help",
450-
onSelect: () => command.trigger("help.show"),
457+
onSelect: () => dialog.trigger("help.show"),
451458
},
452459
{
453460
display: "/commands",
454461
description: "show all commands",
455-
onSelect: () => command.show(),
462+
onSelect: () => dialog.show(),
456463
},
457464
{
458465
display: "/exit",
459466
aliases: ["/quit", "/q"],
460467
description: "exit the app",
461-
onSelect: () => command.trigger("app.exit"),
468+
onSelect: () => dialog.trigger("app.exit"),
462469
},
463470
)
464471
const max = firstBy(results, [(x) => x.display.length, "desc"])?.display.length
@@ -564,7 +571,7 @@ export function Autocomplete(props: {
564571
}
565572

566573
function show(mode: "@" | "/") {
567-
command.keybinds(false)
574+
dialog.keybinds(false)
568575
setStore({
569576
visible: mode,
570577
index: props.input().cursorOffset,
@@ -581,7 +588,7 @@ export function Autocomplete(props: {
581588
draft.input = props.input().plainText
582589
})
583590
}
584-
command.keybinds(true)
591+
dialog.keybinds(true)
585592
setStore("visible", false)
586593
}
587594

packages/opencode/src/cli/cmd/tui/routes/session/index.tsx

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ export function Session() {
356356
value: "session.compact",
357357
keybind: "session_compact",
358358
category: "Session",
359-
onSelect: (dialog) => {
359+
onSelect: async (dialog, trigger, data) => {
360360
const selectedModel = local.model.current()
361361
if (!selectedModel) {
362362
toast.show({
@@ -366,11 +366,29 @@ export function Session() {
366366
})
367367
return
368368
}
369-
sdk.client.session.summarize({
369+
// If no template provided (e.g., via keybind), use the default /compact command
370+
const prompt = data?.template ?? sync.data.command.find((c) => c.name === "compact")?.template
371+
if (!prompt) {
372+
toast.show({
373+
variant: "warning",
374+
message: "No compact command configured",
375+
duration: 3000,
376+
})
377+
return
378+
}
379+
const result = await sdk.client.session.summarize({
370380
sessionID: route.sessionID,
371-
modelID: selectedModel.modelID,
372381
providerID: selectedModel.providerID,
382+
modelID: selectedModel.modelID,
383+
prompt,
373384
})
385+
if (result.error) {
386+
toast.show({
387+
variant: "error",
388+
message: "Summarize failed: " + JSON.stringify(result.error),
389+
duration: 5000,
390+
})
391+
}
374392
dialog.clear()
375393
},
376394
},
@@ -915,11 +933,11 @@ export function Session() {
915933
{(function () {
916934
const command = useCommandDialog()
917935
const [hover, setHover] = createSignal(false)
918-
const dialog = useDialog()
936+
const modal = useDialog()
919937

920938
const handleUnrevert = async () => {
921939
const confirmed = await DialogConfirm.show(
922-
dialog,
940+
modal,
923941
"Confirm Redo",
924942
"Are you sure you want to restore the reverted messages?",
925943
)

packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export interface DialogSelectOption<T = any> {
3838
disabled?: boolean
3939
bg?: RGBA
4040
gutter?: JSX.Element
41-
onSelect?: (ctx: DialogContext, trigger?: "prompt") => void
41+
onSelect?: (ctx: DialogContext, trigger?: "prompt", data?: any) => void
4242
}
4343

4444
export type DialogSelectRef<T> = {

packages/opencode/src/command/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Instance } from "../project/instance"
55
import { Identifier } from "../id/id"
66
import PROMPT_INITIALIZE from "./template/initialize.txt"
77
import PROMPT_REVIEW from "./template/review.txt"
8+
import PROMPT_COMPACT from "./template/compact.txt"
89
import { MCP } from "../mcp"
910

1011
export namespace Command {
@@ -31,6 +32,7 @@ export namespace Command {
3132
// https://zod.dev/v4/changelog?id=zfunction
3233
template: z.promise(z.string()).or(z.string()),
3334
subtask: z.boolean().optional(),
35+
compact: z.boolean().optional(),
3436
hints: z.array(z.string()),
3537
})
3638
.meta({
@@ -53,6 +55,7 @@ export namespace Command {
5355
export const Default = {
5456
INIT: "init",
5557
REVIEW: "review",
58+
COMPACT: "compact",
5659
} as const
5760

5861
const state = Instance.state(async () => {
@@ -76,6 +79,13 @@ export namespace Command {
7679
subtask: true,
7780
hints: hints(PROMPT_REVIEW),
7881
},
82+
[Default.COMPACT]: {
83+
name: Default.COMPACT,
84+
description: "summarize session for compaction",
85+
template: PROMPT_COMPACT,
86+
compact: true,
87+
hints: hints(PROMPT_COMPACT),
88+
},
7989
}
8090

8191
for (const [name, command] of Object.entries(cfg.command ?? {})) {
@@ -88,6 +98,7 @@ export namespace Command {
8898
return command.template
8999
},
90100
subtask: command.subtask,
101+
compact: command.compact,
91102
hints: hints(command.template),
92103
}
93104
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Provide a detailed prompt for continuing our conversation above. Focus on information that would be helpful for continuing the conversation, including what we did, what we're doing, which files we're working on, and what we're going to do next considering new session will not have access to our conversation.

packages/opencode/src/config/config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,7 @@ export namespace Config {
526526
agent: z.string().optional(),
527527
model: z.string().optional(),
528528
subtask: z.boolean().optional(),
529+
compact: z.boolean().optional(),
529530
})
530531
export type Command = z.infer<typeof Command>
531532

@@ -1002,6 +1003,10 @@ export namespace Config {
10021003
.object({
10031004
auto: z.boolean().optional().describe("Enable automatic compaction when context is full (default: true)"),
10041005
prune: z.boolean().optional().describe("Enable pruning of old tool outputs (default: true)"),
1006+
command: z
1007+
.string()
1008+
.optional()
1009+
.describe("Command to use for automatic compaction when context limit is reached. Defaults to 'compact'"),
10051010
})
10061011
.optional(),
10071012
experimental: z

packages/opencode/src/server/server.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1189,6 +1189,7 @@ export namespace Server {
11891189
z.object({
11901190
providerID: z.string(),
11911191
modelID: z.string(),
1192+
prompt: z.string().describe("Prompt to use for compaction"),
11921193
auto: z.boolean().optional().default(false),
11931194
}),
11941195
),
@@ -1214,6 +1215,7 @@ export namespace Server {
12141215
modelID: body.modelID,
12151216
},
12161217
auto: body.auto,
1218+
prompt: body.prompt,
12171219
})
12181220
await SessionPrompt.loop(sessionID)
12191221
return c.json(true)

0 commit comments

Comments
 (0)