opencode-context-tree 0.1.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/CHANGELOG.md +84 -0
- package/LICENSE +21 -0
- package/README.md +122 -0
- package/dist/server.js +19792 -0
- package/dist/tui.js +22742 -0
- package/docs/USAGE.md +155 -0
- package/package.json +93 -0
- package/src/core/actions.ts +53 -0
- package/src/core/adopt.ts +93 -0
- package/src/core/consumers.ts +39 -0
- package/src/core/crop.ts +176 -0
- package/src/core/cropplan.ts +201 -0
- package/src/core/ctree-args.ts +89 -0
- package/src/core/decision.ts +97 -0
- package/src/core/journal.ts +343 -0
- package/src/core/lanes.ts +149 -0
- package/src/core/navigation.ts +95 -0
- package/src/core/tokens.ts +105 -0
- package/src/core/transcript.ts +124 -0
- package/src/core/tree.ts +738 -0
- package/src/core/undo.ts +52 -0
- package/src/server/index.ts +278 -0
- package/src/shared/adopt.ts +82 -0
- package/src/shared/debug.ts +9 -0
- package/src/shared/store.ts +286 -0
- package/src/tui/actions.ts +447 -0
- package/src/tui/editor.ts +45 -0
- package/src/tui/index.tsx +421 -0
- package/src/tui/route.tsx +1058 -0
- package/src/tui/transcripts.ts +55 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal structural transcript types the TUI/server fill from OpenCode's own
|
|
3
|
+
* `Message`/`Part` shapes (DESIGN.md §7), plus pure preview helpers used by
|
|
4
|
+
* `core/tree.ts`.
|
|
5
|
+
*
|
|
6
|
+
* Pure, no OpenCode/opentui/solid-js imports — see test/core-purity.test.ts.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export type StepPart = {
|
|
10
|
+
id: string
|
|
11
|
+
type: string
|
|
12
|
+
text?: string
|
|
13
|
+
tool?: string
|
|
14
|
+
callID?: string
|
|
15
|
+
state?: {
|
|
16
|
+
status?: string
|
|
17
|
+
input?: unknown
|
|
18
|
+
output?: string
|
|
19
|
+
title?: string
|
|
20
|
+
time?: { start?: number; end?: number }
|
|
21
|
+
}
|
|
22
|
+
time?: { start?: number; end?: number }
|
|
23
|
+
metadata?: Record<string, unknown>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type TranscriptMessage = {
|
|
27
|
+
id: string
|
|
28
|
+
role: "user" | "assistant"
|
|
29
|
+
time: { created: number; completed?: number }
|
|
30
|
+
tokens?: { input: number; output: number; reasoning: number; cache: { read: number; write: number } }
|
|
31
|
+
/** OpenCode-native compaction summary marker (not the ctree "jump summary", which is a
|
|
32
|
+
* regular user message tagged via `metadata.ctree.kind === "summary"` instead). */
|
|
33
|
+
summary?: boolean
|
|
34
|
+
parts: StepPart[]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type Transcript = {
|
|
38
|
+
sessionID: string
|
|
39
|
+
title: string
|
|
40
|
+
status: "available" | "deleted"
|
|
41
|
+
messages: TranscriptMessage[]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Part "kind" for filtering/glyph purposes. Anything not text/tool/reasoning (e.g.
|
|
45
|
+
* step-start, step-finish, snapshot, patch, retry) is "other". */
|
|
46
|
+
export function stepKind(part: StepPart): "text" | "tool" | "reasoning" | "other" {
|
|
47
|
+
if (part.type === "text") return "text"
|
|
48
|
+
if (part.type === "tool") return "tool"
|
|
49
|
+
if (part.type === "reasoning") return "reasoning"
|
|
50
|
+
return "other"
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function flatten(text: string): string {
|
|
54
|
+
return text.replace(/\s+/g, " ").trim()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function truncate(text: string, max: number): string {
|
|
58
|
+
if (text.length <= max) return text
|
|
59
|
+
if (max <= 1) return text.slice(0, max)
|
|
60
|
+
return `${text.slice(0, max - 1)}…`
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Best-effort "primary argument" of a tool call, mirroring core/crop.ts's `shortArg`. */
|
|
64
|
+
function primaryArgOf(input: unknown): string {
|
|
65
|
+
if (!input || typeof input !== "object") return ""
|
|
66
|
+
const record = input as Record<string, unknown>
|
|
67
|
+
const candidate = record["command"] ?? record["filePath"] ?? record["path"] ?? record["pattern"] ?? record["url"]
|
|
68
|
+
return typeof candidate === "string" ? candidate : ""
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The shell command of a bash/exec tool call, if any — drives the `[bash $ …]` form. */
|
|
72
|
+
function commandOf(input: unknown): string {
|
|
73
|
+
if (!input || typeof input !== "object") return ""
|
|
74
|
+
const candidate = (input as Record<string, unknown>)["command"]
|
|
75
|
+
return typeof candidate === "string" ? candidate : ""
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* One-line, content-forward preview of a single part — the Pi outline × DSH trajectory row
|
|
80
|
+
* (DESIGN.md §7.1). The command/argument is the DSH "payload"; the output snippet is its
|
|
81
|
+
* "result":
|
|
82
|
+
* - tool with a shell command: `[bash $ <cmd>]` (the command is the payload)
|
|
83
|
+
* - other tools: `[<tool>: <arg>] → <output>` (or `[<tool>]` when there is no argument)
|
|
84
|
+
* - text: first 60 chars, newlines flattened
|
|
85
|
+
* - reasoning: literal `(thinking)`
|
|
86
|
+
* - other (step-start/finish/snapshot/patch/retry, …): title or type, first 60 chars
|
|
87
|
+
*
|
|
88
|
+
* The `⚙`/`✗` glyph and error flag are added by the renderer, not here.
|
|
89
|
+
*/
|
|
90
|
+
export function partPreview(part: StepPart): string {
|
|
91
|
+
const kind = stepKind(part)
|
|
92
|
+
if (kind === "tool") {
|
|
93
|
+
const tool = part.tool ?? "tool"
|
|
94
|
+
const command = commandOf(part.state?.input)
|
|
95
|
+
if (command) return `[${tool} $ ${truncate(flatten(command), 52)}]`
|
|
96
|
+
const arg = truncate(flatten(primaryArgOf(part.state?.input)), 40)
|
|
97
|
+
const head = arg ? `[${tool}: ${arg}]` : `[${tool}]`
|
|
98
|
+
const output = truncate(flatten(part.state?.output ?? ""), 30)
|
|
99
|
+
return output ? `${head} → ${output}` : head
|
|
100
|
+
}
|
|
101
|
+
if (kind === "reasoning") return "(thinking)"
|
|
102
|
+
if (kind === "text") return truncate(flatten(part.text ?? ""), 60)
|
|
103
|
+
return truncate(flatten(part.state?.title ?? part.type), 60)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* One-line preview of a whole message (DESIGN.md §7.2's `T<n> ● user <preview>`):
|
|
108
|
+
* the first text part if there is one, else a `[tool a, b]` summary of the tool
|
|
109
|
+
* calls it made. Single line, ≤ 60 chars, newlines flattened.
|
|
110
|
+
*/
|
|
111
|
+
export function messagePreview(message: TranscriptMessage): string {
|
|
112
|
+
const textPart = message.parts.find((p) => p.type === "text" && p.text)
|
|
113
|
+
if (textPart) return truncate(flatten(textPart.text ?? ""), 60)
|
|
114
|
+
|
|
115
|
+
const tools: string[] = []
|
|
116
|
+
for (const part of message.parts) {
|
|
117
|
+
if (part.type !== "tool") continue
|
|
118
|
+
const name = part.tool ?? "tool"
|
|
119
|
+
if (!tools.includes(name)) tools.push(name)
|
|
120
|
+
}
|
|
121
|
+
if (tools.length > 0) return truncate(`[tool ${tools.join(", ")}]`, 60)
|
|
122
|
+
|
|
123
|
+
return ""
|
|
124
|
+
}
|