herm-tui 1.0.0-dev.1 → 1.0.0-dev.3

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.
Files changed (188) hide show
  1. package/db.worker.js +81 -0
  2. package/highlights-eq9cgrbb.scm +604 -0
  3. package/highlights-ghv9g403.scm +205 -0
  4. package/highlights-hk7bwhj4.scm +284 -0
  5. package/highlights-r812a2qc.scm +150 -0
  6. package/highlights-x6tmsnaa.scm +115 -0
  7. package/index.js +10374 -0
  8. package/injections-73j83es3.scm +27 -0
  9. package/package.json +14 -64
  10. package/parser.worker.js +8 -0
  11. package/tree-sitter-3jzf13jk.wasm +0 -0
  12. package/tree-sitter-javascript-nd0q4pe9.wasm +0 -0
  13. package/tree-sitter-markdown-411r6y9b.wasm +0 -0
  14. package/tree-sitter-markdown_inline-j5349f42.wasm +0 -0
  15. package/tree-sitter-typescript-zxjzwt75.wasm +0 -0
  16. package/tree-sitter-zig-e78zbjpm.wasm +0 -0
  17. package/scripts/postinstall.ts +0 -29
  18. package/src/app/gateway.tsx +0 -83
  19. package/src/app/gatewayEvents.ts +0 -203
  20. package/src/app/launch.ts +0 -41
  21. package/src/app/skin.tsx +0 -31
  22. package/src/app/spawnHistory.ts +0 -75
  23. package/src/app/tabs.ts +0 -23
  24. package/src/app/turnReducer.ts +0 -390
  25. package/src/app/useAppKeys.ts +0 -268
  26. package/src/app/useAtRefPopover.ts +0 -99
  27. package/src/app/useInputHistory.ts +0 -66
  28. package/src/app/useSession.ts +0 -102
  29. package/src/app/useSlashCommands.ts +0 -70
  30. package/src/app/useSlashPopover.ts +0 -48
  31. package/src/app.tsx +0 -917
  32. package/src/commands/slash.ts +0 -151
  33. package/src/components/avatar/AnimatedAvatar.tsx +0 -66
  34. package/src/components/avatar/eikon.ts +0 -144
  35. package/src/components/avatar/states/error.ts +0 -1155
  36. package/src/components/avatar/states/idle.ts +0 -1155
  37. package/src/components/avatar/states/index.ts +0 -30
  38. package/src/components/avatar/states/listening.ts +0 -1155
  39. package/src/components/avatar/states/speaking.ts +0 -1155
  40. package/src/components/avatar/states/thinking.ts +0 -1155
  41. package/src/components/avatar/states/working.ts +0 -1155
  42. package/src/components/chat/AtRefPopover.tsx +0 -54
  43. package/src/components/chat/CodeBlock.tsx +0 -67
  44. package/src/components/chat/Composer.tsx +0 -347
  45. package/src/components/chat/DiffBlock.tsx +0 -116
  46. package/src/components/chat/ErrorBlock.tsx +0 -70
  47. package/src/components/chat/MediaChip.tsx +0 -114
  48. package/src/components/chat/MessageItem.tsx +0 -282
  49. package/src/components/chat/MessageList.tsx +0 -114
  50. package/src/components/chat/PromptCard.tsx +0 -359
  51. package/src/components/chat/SlashPopover.tsx +0 -158
  52. package/src/components/chat/ThoughtCloud.tsx +0 -185
  53. package/src/components/chat/TypingIndicator.tsx +0 -25
  54. package/src/components/chat/tool/Subagent.tsx +0 -75
  55. package/src/components/chat/tool/frame.tsx +0 -69
  56. package/src/components/chat/tool/index.tsx +0 -65
  57. package/src/components/chat/tool/preview.ts +0 -57
  58. package/src/components/sidebar/ContextGauge.tsx +0 -102
  59. package/src/components/sidebar/Sidebar.tsx +0 -143
  60. package/src/components/tabs/TabBar.tsx +0 -50
  61. package/src/components/ui/FileLink.tsx +0 -52
  62. package/src/config/index.ts +0 -156
  63. package/src/config/lane.ts +0 -161
  64. package/src/config/models.ts +0 -95
  65. package/src/config/rules.ts +0 -80
  66. package/src/config/schema.ts +0 -308
  67. package/src/dialogs/alert.tsx +0 -52
  68. package/src/dialogs/chafa.tsx +0 -72
  69. package/src/dialogs/confirm.tsx +0 -58
  70. package/src/dialogs/curator.tsx +0 -153
  71. package/src/dialogs/eikon-picker.tsx +0 -95
  72. package/src/dialogs/help.tsx +0 -80
  73. package/src/dialogs/history.tsx +0 -92
  74. package/src/dialogs/info.tsx +0 -115
  75. package/src/dialogs/keys.tsx +0 -170
  76. package/src/dialogs/logs.tsx +0 -42
  77. package/src/dialogs/message.tsx +0 -38
  78. package/src/dialogs/model-picker.tsx +0 -123
  79. package/src/dialogs/new-profile.tsx +0 -69
  80. package/src/dialogs/new-task.tsx +0 -103
  81. package/src/dialogs/profile.tsx +0 -55
  82. package/src/dialogs/rollback.tsx +0 -190
  83. package/src/dialogs/spawn-history.tsx +0 -80
  84. package/src/dialogs/text-prompt.tsx +0 -68
  85. package/src/dialogs/theme-picker.tsx +0 -50
  86. package/src/home/index.ts +0 -23
  87. package/src/home/store.ts +0 -267
  88. package/src/index.tsx +0 -113
  89. package/src/keys/catalog.ts +0 -115
  90. package/src/keys/chord.ts +0 -125
  91. package/src/keys/conflicts.ts +0 -48
  92. package/src/keys/context.tsx +0 -112
  93. package/src/keys/index.ts +0 -5
  94. package/src/keys/list.ts +0 -94
  95. package/src/keys/oc-compat.ts +0 -87
  96. package/src/tabs/Agents.tsx +0 -607
  97. package/src/tabs/Analytics.tsx +0 -154
  98. package/src/tabs/Chat.tsx +0 -50
  99. package/src/tabs/Config.tsx +0 -605
  100. package/src/tabs/Context.tsx +0 -599
  101. package/src/tabs/Cron.tsx +0 -294
  102. package/src/tabs/Env.tsx +0 -227
  103. package/src/tabs/Kanban.tsx +0 -367
  104. package/src/tabs/Memory.tsx +0 -294
  105. package/src/tabs/Sessions.tsx +0 -786
  106. package/src/tabs/Skills.tsx +0 -507
  107. package/src/tabs/Toolsets.tsx +0 -266
  108. package/src/theme/builtin.ts +0 -78
  109. package/src/theme/context.tsx +0 -106
  110. package/src/theme/index.ts +0 -4
  111. package/src/theme/resolve.ts +0 -134
  112. package/src/theme/syntax.ts +0 -31
  113. package/src/theme/themes/aura.json +0 -69
  114. package/src/theme/themes/ayu.json +0 -80
  115. package/src/theme/themes/carbonfox.json +0 -248
  116. package/src/theme/themes/catppuccin-frappe.json +0 -233
  117. package/src/theme/themes/catppuccin-macchiato.json +0 -233
  118. package/src/theme/themes/catppuccin.json +0 -112
  119. package/src/theme/themes/cobalt2.json +0 -228
  120. package/src/theme/themes/cursor.json +0 -249
  121. package/src/theme/themes/dracula.json +0 -219
  122. package/src/theme/themes/everforest.json +0 -241
  123. package/src/theme/themes/flexoki.json +0 -237
  124. package/src/theme/themes/github.json +0 -233
  125. package/src/theme/themes/gruvbox.json +0 -242
  126. package/src/theme/themes/kanagawa.json +0 -77
  127. package/src/theme/themes/lucent-orng.json +0 -237
  128. package/src/theme/themes/material.json +0 -235
  129. package/src/theme/themes/matrix.json +0 -77
  130. package/src/theme/themes/mercury.json +0 -252
  131. package/src/theme/themes/monokai.json +0 -221
  132. package/src/theme/themes/nightowl.json +0 -221
  133. package/src/theme/themes/nord.json +0 -223
  134. package/src/theme/themes/one-dark.json +0 -84
  135. package/src/theme/themes/opencode.json +0 -245
  136. package/src/theme/themes/orng.json +0 -249
  137. package/src/theme/themes/osaka-jade.json +0 -93
  138. package/src/theme/themes/palenight.json +0 -222
  139. package/src/theme/themes/rosepine.json +0 -234
  140. package/src/theme/themes/solarized.json +0 -223
  141. package/src/theme/themes/synthwave84.json +0 -226
  142. package/src/theme/themes/tokyonight.json +0 -243
  143. package/src/theme/themes/vercel.json +0 -245
  144. package/src/theme/themes/vesper.json +0 -218
  145. package/src/theme/themes/zenburn.json +0 -223
  146. package/src/theme/types.ts +0 -119
  147. package/src/types/message.ts +0 -97
  148. package/src/ui/ChafaImage.tsx +0 -64
  149. package/src/ui/Splash.tsx +0 -118
  150. package/src/ui/borders.ts +0 -28
  151. package/src/ui/command.tsx +0 -104
  152. package/src/ui/dialog-select.tsx +0 -164
  153. package/src/ui/dialog.tsx +0 -102
  154. package/src/ui/fmt.ts +0 -82
  155. package/src/ui/kv.tsx +0 -28
  156. package/src/ui/shell.tsx +0 -45
  157. package/src/ui/spinner.tsx +0 -59
  158. package/src/ui/splash-art.ts +0 -123
  159. package/src/ui/table.tsx +0 -117
  160. package/src/ui/ticker.tsx +0 -90
  161. package/src/ui/toast.tsx +0 -130
  162. package/src/utils/categorical.ts +0 -77
  163. package/src/utils/chafa.ts +0 -173
  164. package/src/utils/clipboard.ts +0 -67
  165. package/src/utils/context-segments.ts +0 -317
  166. package/src/utils/control.ts +0 -495
  167. package/src/utils/drop.ts +0 -25
  168. package/src/utils/editor.ts +0 -33
  169. package/src/utils/fuzzy.ts +0 -45
  170. package/src/utils/gateway-client.ts +0 -253
  171. package/src/utils/gateway-types.ts +0 -282
  172. package/src/utils/git.ts +0 -57
  173. package/src/utils/hermes-analytics.ts +0 -134
  174. package/src/utils/hermes-home.ts +0 -821
  175. package/src/utils/hermes-kanban.ts +0 -154
  176. package/src/utils/hermes-profiles.ts +0 -217
  177. package/src/utils/interpolate.ts +0 -31
  178. package/src/utils/math-unicode.ts +0 -818
  179. package/src/utils/memory-activity.ts +0 -140
  180. package/src/utils/open-file.ts +0 -13
  181. package/src/utils/paths.ts +0 -52
  182. package/src/utils/perf.ts +0 -235
  183. package/src/utils/preferences.ts +0 -150
  184. package/src/utils/sessions-db.ts +0 -396
  185. package/src/utils/subagent-tree.ts +0 -146
  186. package/src/utils/terminal-reset.ts +0 -129
  187. package/src/utils/tips.ts +0 -67
  188. package/src/utils/tokens.ts +0 -87
@@ -1,253 +0,0 @@
1
- // Stdio JSON-RPC 2.0 client for tui_gateway. Spawns the gateway as a child
2
- // process and speaks newline-delimited JSON on stdin/stdout.
3
-
4
- import { EventEmitter } from "events"
5
- import { homedir } from "os"
6
- import { resolve, delimiter } from "path"
7
- import { existsSync } from "fs"
8
- import type { GatewayEvent } from "./gateway-types"
9
-
10
- const LOG_MAX = 200
11
- const LOG_PREVIEW = 240
12
- const STARTUP_MS = 15_000
13
- const REQUEST_MS = 120_000
14
-
15
- /** Locate the hermes-agent source tree (gateway + hermes_cli live here).
16
- * Default: ~/.hermes/hermes-agent (where `hermes update` installs it).
17
- * Override with HERMES_AGENT_ROOT for dev clones / exotic layouts. */
18
- export function hermesAgentRoot(): string {
19
- if (process.env.HERMES_AGENT_ROOT) return process.env.HERMES_AGENT_ROOT
20
- const home = process.env.HOME || homedir()
21
- return `${home}/.hermes/hermes-agent`
22
- }
23
-
24
- type Pending = {
25
- resolve: (v: unknown) => void
26
- reject: (e: Error) => void
27
- }
28
-
29
- function python(root: string): string {
30
- const env = process.env.HERMES_PYTHON?.trim()
31
- if (env) return env
32
-
33
- const venv = process.env.VIRTUAL_ENV?.trim()
34
- const paths = [
35
- venv && resolve(venv, "bin/python"),
36
- resolve(root, "venv/bin/python"),
37
- resolve(root, "venv/bin/python3"),
38
- resolve(root, ".venv/bin/python"),
39
- resolve(root, ".venv/bin/python3"),
40
- ]
41
- return paths.find(p => p && existsSync(p)) || "python3"
42
- }
43
-
44
- function asEvent(v: unknown): GatewayEvent | null {
45
- if (v && typeof v === "object" && !Array.isArray(v) && typeof (v as { type?: unknown }).type === "string")
46
- return v as GatewayEvent
47
- return null
48
- }
49
-
50
- // Read lines from a ReadableStream (Bun subprocess stdout/stderr)
51
- async function lines(stream: ReadableStream<Uint8Array>, cb: (line: string) => void) {
52
- const reader = stream.getReader()
53
- const decoder = new TextDecoder()
54
- let buf = ""
55
- try {
56
- while (true) {
57
- const { done, value } = await reader.read()
58
- if (done) break
59
- buf += decoder.decode(value, { stream: true })
60
- const parts = buf.split("\n")
61
- buf = parts.pop() || ""
62
- for (const line of parts) {
63
- if (line) cb(line)
64
- }
65
- }
66
- // Flush remaining
67
- if (buf.trim()) cb(buf)
68
- } catch {
69
- // Stream closed
70
- }
71
- }
72
-
73
- export class GatewayClient extends EventEmitter {
74
- private proc: ReturnType<typeof Bun.spawn> | null = null
75
- private id = 0
76
- private logs: string[] = []
77
- private pending = new Map<string, Pending>()
78
- private buf: GatewayEvent[] = []
79
- private exit: number | null | undefined
80
- private ok = false
81
- private timer: ReturnType<typeof setTimeout> | null = null
82
- private sub = false
83
-
84
- private root(): string { return hermesAgentRoot() }
85
-
86
- private push(ev: GatewayEvent) {
87
- if (ev.type === "gateway.ready") {
88
- this.ok = true
89
- if (this.timer) { clearTimeout(this.timer); this.timer = null }
90
- }
91
- if (this.sub) return void this.emit("event", ev)
92
- this.buf.push(ev)
93
- }
94
-
95
- private log(line: string) {
96
- if (this.logs.push(line) > LOG_MAX) this.logs.splice(0, this.logs.length - LOG_MAX)
97
- }
98
-
99
- private dispatch(msg: Record<string, unknown>) {
100
- const id = msg.id as string | undefined
101
- const p = id ? this.pending.get(id) : undefined
102
-
103
- if (p) {
104
- this.pending.delete(id!)
105
- if (msg.error) {
106
- const err = msg.error as { message?: unknown }
107
- p.reject(new Error(typeof err?.message === "string" ? err.message : "request failed"))
108
- } else {
109
- p.resolve(msg.result)
110
- }
111
- return
112
- }
113
-
114
- if (msg.method === "event") {
115
- const ev = asEvent(msg.params)
116
- if (ev) this.push(ev)
117
- }
118
- }
119
-
120
- private fail(err: Error) {
121
- for (const p of this.pending.values()) p.reject(err)
122
- this.pending.clear()
123
- }
124
-
125
- start() {
126
- const root = this.root()
127
- const bin = python(root)
128
- const cwd = process.env.HERMES_CWD || root
129
- const env = { ...process.env } as Record<string, string>
130
- const pp = env.PYTHONPATH?.trim()
131
- env.PYTHONPATH = pp ? `${root}${delimiter}${pp}` : root
132
-
133
- // Reset state
134
- this.ok = false
135
- this.buf = []
136
- this.exit = undefined
137
-
138
- if (this.proc) {
139
- try { this.proc.kill() } catch {}
140
- }
141
-
142
- if (this.timer) clearTimeout(this.timer)
143
- this.timer = setTimeout(() => {
144
- if (this.ok) return
145
- this.log(`[startup] timed out (python=${bin}, cwd=${cwd})`)
146
- this.push({ type: "gateway.start_timeout", payload: { cwd, python: bin } })
147
- }, STARTUP_MS)
148
-
149
- const proc = Bun.spawn(["sh", "-c", `exec ${bin} -m tui_gateway.entry`], {
150
- cwd,
151
- env,
152
- stdin: "pipe",
153
- stdout: "pipe",
154
- stderr: "pipe",
155
- })
156
- this.proc = proc
157
-
158
- // Read stdout lines — Bun returns ReadableStream
159
- if (this.proc.stdout) {
160
- lines(this.proc.stdout as ReadableStream<Uint8Array>, raw => {
161
- try {
162
- this.dispatch(JSON.parse(raw))
163
- } catch {
164
- const preview = raw.trim().slice(0, LOG_PREVIEW) || "(empty)"
165
- this.log(`[protocol] malformed: ${preview}`)
166
- this.push({ type: "gateway.protocol_error", payload: { preview } })
167
- }
168
- })
169
- }
170
-
171
- // Read stderr lines
172
- if (this.proc.stderr) {
173
- lines(this.proc.stderr as ReadableStream<Uint8Array>, raw => {
174
- const line = raw.trim()
175
- if (!line) return
176
- this.log(line)
177
- this.push({ type: "gateway.stderr", payload: { line } })
178
- })
179
- }
180
-
181
- // Handle exit — guard against a superseded proc (restart kills the
182
- // old one, whose exit handler must not touch the new proc's state).
183
- proc.exited.then(code => {
184
- if (this.proc !== proc) return
185
- if (this.timer) { clearTimeout(this.timer); this.timer = null }
186
- this.fail(new Error(`gateway exited${code === null ? "" : ` (${code})`}`))
187
- if (this.sub) this.emit("exit", code)
188
- else this.exit = code
189
- })
190
- }
191
-
192
- drain() {
193
- if (this.sub) return
194
- this.sub = true
195
- for (const ev of this.buf.splice(0)) this.emit("event", ev)
196
- if (this.exit !== undefined) {
197
- const code = this.exit
198
- this.exit = undefined
199
- this.emit("exit", code)
200
- }
201
- }
202
-
203
- tail(n = 20): string {
204
- return this.logs.slice(-Math.max(1, n)).join("\n")
205
- }
206
-
207
- private sid = ""
208
-
209
- /** Set the active session id; auto-injected into subsequent requests. */
210
- setSession(sid: string) {
211
- this.sid = sid
212
- }
213
-
214
- request<T = unknown>(method: string, params: Record<string, unknown> = {}): Promise<T> {
215
- if (!this.proc || this.proc.exitCode !== null) this.start()
216
-
217
- const stdin = this.proc?.stdin
218
- if (!stdin || typeof stdin === "number") return Promise.reject(new Error("gateway not running"))
219
-
220
- const rid = `r${++this.id}`
221
- const writer = stdin as { write(data: string | Uint8Array): number }
222
- const merged = this.sid && params.session_id === undefined
223
- ? { session_id: this.sid, ...params }
224
- : params
225
-
226
- return new Promise((resolve, reject) => {
227
- const timeout = setTimeout(() => {
228
- if (this.pending.delete(rid)) reject(new Error(`timeout: ${method}`))
229
- }, REQUEST_MS)
230
-
231
- this.pending.set(rid, {
232
- reject: e => { clearTimeout(timeout); reject(e) },
233
- resolve: v => { clearTimeout(timeout); resolve(v as T) },
234
- })
235
-
236
- try {
237
- writer.write(JSON.stringify({ jsonrpc: "2.0", id: rid, method, params: merged }) + "\n")
238
- } catch (e) {
239
- clearTimeout(timeout)
240
- this.pending.delete(rid)
241
- reject(e instanceof Error ? e : new Error(String(e)))
242
- }
243
- })
244
- }
245
-
246
- kill() {
247
- this.proc?.kill()
248
- }
249
-
250
- get ready(): boolean {
251
- return this.ok
252
- }
253
- }
@@ -1,282 +0,0 @@
1
- // Typed events and RPC responses for the tui_gateway JSON-RPC protocol.
2
-
3
- import type { Usage } from "../types/message"
4
-
5
- // ── Events (server → client) ────────────────────────────────────────
6
-
7
- export type GatewayEvent =
8
- | { type: "gateway.ready"; payload?: { skin?: GatewaySkin } }
9
- | { type: "gateway.stderr"; payload: { line: string } }
10
- | { type: "gateway.start_timeout"; payload?: { cwd?: string; python?: string } }
11
- | { type: "gateway.protocol_error"; payload?: { preview?: string } }
12
- | { type: "session.info"; payload: SessionInfo }
13
- | { type: "skin.changed"; payload?: GatewaySkin }
14
- | { type: "message.start"; payload?: undefined }
15
- | { type: "message.delta"; payload?: { text?: string; rendered?: string } }
16
- | { type: "message.complete"; payload?: { text?: string | null; rendered?: string; reasoning?: string; status?: "complete" | "error" | "interrupted"; usage?: Usage } }
17
- | { type: "thinking.delta"; payload?: { text?: string } }
18
- | { type: "reasoning.delta"; payload?: { text?: string } }
19
- | { type: "reasoning.available"; payload?: { text?: string } }
20
- | { type: "status.update"; payload?: { text?: string; kind?: string } }
21
- | { type: "tool.start"; payload: { tool_id: string; name?: string; context?: string } }
22
- | { type: "tool.progress"; payload: { name?: string; preview?: string } }
23
- | { type: "tool.generating"; payload: { name?: string } }
24
- | { type: "tool.complete"; payload: { tool_id: string; name?: string; summary?: string; error?: string; inline_diff?: string } }
25
- | { type: "clarify.request"; payload: { request_id: string; question: string; choices: string[] | null } }
26
- | { type: "approval.request"; payload: { command: string; description: string } }
27
- | { type: "sudo.request"; payload: { request_id: string } }
28
- | { type: "secret.request"; payload: { request_id: string; prompt: string; env_var: string } }
29
- | { type: "background.complete"; payload: { task_id: string; text: string } }
30
- | { type: "review.summary"; payload?: { text?: string } }
31
- | { type: "btw.complete"; payload: { text: string } }
32
- | { type: "browser.progress"; payload?: { message?: string; level?: "info" | "error" } }
33
- | { type: "subagent.start"; payload: SubagentPayload }
34
- | { type: "subagent.thinking"; payload: SubagentPayload }
35
- | { type: "subagent.tool"; payload: SubagentPayload }
36
- | { type: "subagent.progress"; payload: SubagentPayload }
37
- | { type: "subagent.complete"; payload: SubagentPayload }
38
- | { type: "error"; payload?: { message?: string } }
39
-
40
- export type SubagentPayload = {
41
- task_index: number
42
- goal: string
43
- task_count?: number
44
- status?: "running" | "completed" | "failed" | "interrupted"
45
- text?: string
46
- tool_name?: string
47
- tool_preview?: string
48
- summary?: string
49
- duration_seconds?: number
50
- // Spawn-tree identity (upstream delegate_tool threads these through
51
- // every subagent.* event). All optional — absence falls back to flat
52
- // task_index keying.
53
- subagent_id?: string
54
- parent_id?: string
55
- depth?: number
56
- model?: string
57
- tool_count?: number
58
- toolsets?: string[]
59
- // Rollups on subagent.complete
60
- input_tokens?: number
61
- output_tokens?: number
62
- reasoning_tokens?: number
63
- api_calls?: number
64
- cost_usd?: number
65
- files_read?: string[]
66
- files_written?: string[]
67
- output_tail?: Array<{ tool: string; preview: string; is_error?: boolean }>
68
- }
69
-
70
- // delegation.status response — list_active_subagents() snapshot plus
71
- // scheduler flags. Records are a copy of the live registry minus the
72
- // agent handle.
73
- export type DelegationRecord = {
74
- subagent_id: string
75
- parent_id?: string | null
76
- depth: number
77
- goal: string
78
- model?: string
79
- started_at?: number
80
- tool_count?: number
81
- status?: string
82
- }
83
-
84
- export type DelegationStatus = {
85
- active: DelegationRecord[]
86
- paused: boolean
87
- max_spawn_depth: number
88
- max_concurrent_children: number
89
- }
90
-
91
- // spawn_tree.list index entries + spawn_tree.load payload
92
- export type SpawnTreeEntry = {
93
- path: string
94
- session_id: string
95
- label: string
96
- count: number
97
- started_at?: number | null
98
- finished_at: number
99
- }
100
-
101
- export type SpawnTreeSnapshot = {
102
- session_id?: string
103
- label?: string
104
- started_at?: number | null
105
- finished_at?: number
106
- subagents: SpawnSubagent[]
107
- }
108
-
109
- // Persisted per-subagent record — the shape we save, and the shape
110
- // spawn_tree.load round-trips. A completed SubagentPayload superset.
111
- export type SpawnSubagent = {
112
- subagent_id: string
113
- parent_id?: string | null
114
- depth: number
115
- goal: string
116
- model?: string
117
- started_at: number
118
- finished_at?: number
119
- tool_count: number
120
- status: "running" | "completed" | "failed" | "interrupted"
121
- input_tokens?: number
122
- output_tokens?: number
123
- cost_usd?: number
124
- trail?: Array<{ name: string; preview?: string }>
125
- }
126
-
127
- export type GatewaySkin = {
128
- name?: string
129
- colors?: Record<string, string>
130
- branding?: Record<string, string>
131
- banner_hero?: string
132
- banner_logo?: string
133
- tool_prefix?: string
134
- help_header?: string
135
- }
136
-
137
- export type McpServer = {
138
- name: string
139
- transport: string
140
- tools: number
141
- connected: boolean
142
- error?: string
143
- }
144
-
145
- export type SessionInfo = {
146
- model?: string
147
- cwd?: string
148
- session_id?: string
149
- tools?: Record<string, string[]>
150
- skills?: Record<string, string[]>
151
- version?: string
152
- /**
153
- * Wire usage payload for the current session. Server builds this via
154
- * `_get_usage(agent)` (tui_gateway/server.py:826), which extends the
155
- * base Usage with ctx/compression fields when a ContextCompressor is
156
- * attached — so `compressions`/`context_used`/`context_max`/
157
- * `context_percent` may be present. Intersection type keeps both
158
- * shapes satisfied.
159
- */
160
- usage?: Usage & {
161
- context_used?: number
162
- context_max?: number
163
- context_percent?: number
164
- compressions?: number
165
- }
166
- context_max?: number
167
- context_used?: number
168
- credential_warning?: string
169
- mcp_servers?: McpServer[]
170
- /** hermes-agent version string (e.g. "1.14.2-dev+abc123") */
171
- release_date?: string
172
- /** commits behind origin/main; null = unknown, 0 = up to date */
173
- update_behind?: number | null
174
- /** platform-appropriate update invocation */
175
- update_command?: string
176
- }
177
-
178
- // ── RPC responses ───────────────────────────────────────────────────
179
-
180
- export type SessionCreateResponse = {
181
- session_id: string
182
- info?: SessionInfo & { credential_warning?: string }
183
- }
184
-
185
- export type SessionResumeResponse = {
186
- session_id: string
187
- resumed?: string
188
- messages: TranscriptMessage[]
189
- message_count?: number
190
- info?: SessionInfo
191
- }
192
-
193
- export type SessionListItem = {
194
- id: string
195
- title: string
196
- preview: string
197
- message_count: number
198
- started_at: number
199
- source?: string
200
- }
201
-
202
- export type SessionListResponse = {
203
- sessions?: SessionListItem[]
204
- }
205
-
206
- export type SessionUsageResponse = {
207
- model?: string
208
- calls?: number
209
- input?: number
210
- output?: number
211
- total?: number
212
- cache_read?: number
213
- cache_write?: number
214
- cost_usd?: number
215
- cost_status?: "estimated" | "exact"
216
- context_used?: number
217
- context_max?: number
218
- context_percent?: number
219
- compressions?: number
220
- }
221
-
222
- /** Content part inside a multimodal user turn — upstream stores the raw
223
- * OpenAI content list for native-mode image routing. We only care about
224
- * flattening the text fragments back into a string for render. */
225
- export type ContentPart =
226
- | { type: "text"; text: string }
227
- | { type: "image_url"; image_url: { url: string } }
228
- | { type: string }
229
-
230
- export type TranscriptMessage = {
231
- role: "user" | "assistant" | "system" | "tool"
232
- /** Either a plain string (text-mode, assistant, system) or a list of
233
- * OpenAI content parts (native-mode user turns with attached images). */
234
- text?: string | ContentPart[]
235
- name?: string
236
- context?: string
237
- }
238
-
239
- export type CommandsCatalogResponse = {
240
- categories?: Array<{ name: string; pairs?: [string, string][] }>
241
- pairs?: [string, string][]
242
- canon?: Record<string, string>
243
- sub?: Record<string, string[]>
244
- skill_count?: number
245
- warning?: string
246
- }
247
-
248
- export type ConfigSetResponse = {
249
- value?: string
250
- info?: SessionInfo
251
- warning?: string
252
- history_reset?: boolean
253
- }
254
-
255
- export type ModelOptionsResponse = {
256
- provider?: string
257
- model?: string
258
- providers?: {
259
- slug: string
260
- name: string
261
- models?: string[]
262
- total_models?: number
263
- is_current?: boolean
264
- warning?: string
265
- }[]
266
- }
267
-
268
- export type ImageAttachResponse = {
269
- attached: boolean
270
- path?: string
271
- count?: number
272
- name?: string
273
- width?: number
274
- height?: number
275
- token_estimate?: number
276
- message?: string
277
- }
278
-
279
- export type DropDetectResponse =
280
- | { matched: false }
281
- | ({ matched: true; is_image: true; text: string } & Omit<ImageAttachResponse, "attached" | "message">)
282
- | { matched: true; is_image: false; path: string; name: string; text: string }
package/src/utils/git.ts DELETED
@@ -1,57 +0,0 @@
1
- // Git branch for the sidebar. One-shot resolve + fs.watch on
2
- // `<gitdir>/HEAD` so checkout/switch is picked up without polling.
3
- // Ink's equivalent polls every 15s; the watcher is strictly cheaper
4
- // and fires exactly on the event that matters.
5
-
6
- import { useEffect, useState } from "react"
7
- import { watch, type FSWatcher } from "node:fs"
8
-
9
- const TIMEOUT = 500
10
-
11
- async function git(cwd: string, ...args: string[]): Promise<string | null> {
12
- const p = Bun.spawn(["git", "-C", cwd, ...args], { stdout: "pipe", stderr: "ignore" })
13
- const t = setTimeout(() => p.kill(), TIMEOUT)
14
- const out = await new Response(p.stdout).text()
15
- clearTimeout(t)
16
- return (await p.exited) === 0 ? out.trim() : null
17
- }
18
-
19
- /** Branch name for `cwd`, or null when not in a repo / detached HEAD. */
20
- export async function branch(cwd: string): Promise<string | null> {
21
- const b = await git(cwd, "rev-parse", "--abbrev-ref", "HEAD")
22
- return !b || b === "HEAD" ? null : b
23
- }
24
-
25
- /** Absolute .git dir for `cwd` (handles worktrees via git's own resolver). */
26
- export async function gitdir(cwd: string): Promise<string | null> {
27
- return git(cwd, "rev-parse", "--absolute-git-dir")
28
- }
29
-
30
- export function useGitBranch(cwd: string | undefined): string | null {
31
- const [val, set] = useState<string | null>(null)
32
-
33
- useEffect(() => {
34
- if (!cwd) { set(null); return }
35
- let dead = false
36
- let w: FSWatcher | undefined
37
- const read = () => branch(cwd).then(b => { if (!dead) set(b) })
38
- void read()
39
- // HEAD is rewritten (not edited in-place) on checkout — watch the
40
- // parent dir and filter on basename so rename-into-place fires.
41
- gitdir(cwd).then(dir => {
42
- if (dead || !dir) return
43
- try {
44
- w = watch(dir, { persistent: false }, (_ev, f) => {
45
- if (f === "HEAD") void read()
46
- })
47
- } catch { /* unwatchable fs */ }
48
- })
49
- return () => { dead = true; w?.close() }
50
- }, [cwd])
51
-
52
- return val
53
- }
54
-
55
- /** Right-ellipsise keeping the discriminating tail (feature/foo → …e/foo). */
56
- export const rtrunc = (s: string, max: number) =>
57
- s.length <= max ? s : "…" + s.slice(-(max - 1))