@chengchenccc/oh-my-agent 0.1.1-rc.1
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/README.md +79 -0
- package/dist/cli/args.d.ts +24 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +120 -0
- package/dist/cli/initial-input.d.ts +32 -0
- package/dist/cli/initial-input.d.ts.map +1 -0
- package/dist/cli/initial-input.js +131 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +3 -0
- package/dist/core/__fixtures__/echo-model.d.ts +6 -0
- package/dist/core/__fixtures__/echo-model.d.ts.map +1 -0
- package/dist/core/__fixtures__/echo-model.js +10 -0
- package/dist/core/__fixtures__/mcp-echo-server.d.ts +2 -0
- package/dist/core/__fixtures__/mcp-echo-server.d.ts.map +1 -0
- package/dist/core/__fixtures__/mcp-echo-server.js +54 -0
- package/dist/core/agent-runtime.d.ts +21 -0
- package/dist/core/agent-runtime.d.ts.map +1 -0
- package/dist/core/agent-runtime.js +14 -0
- package/dist/core/memory/autonomous-memory.d.ts +21 -0
- package/dist/core/memory/autonomous-memory.d.ts.map +1 -0
- package/dist/core/memory/autonomous-memory.js +202 -0
- package/dist/core/persistence/in-memory-session-store.d.ts +3 -0
- package/dist/core/persistence/in-memory-session-store.d.ts.map +1 -0
- package/dist/core/persistence/in-memory-session-store.js +109 -0
- package/dist/core/persistence/session-store.contract.d.ts +5 -0
- package/dist/core/persistence/session-store.contract.d.ts.map +1 -0
- package/dist/core/persistence/session-store.contract.js +183 -0
- package/dist/core/persistence/session-store.d.ts +33 -0
- package/dist/core/persistence/session-store.d.ts.map +1 -0
- package/dist/core/persistence/session-store.js +22 -0
- package/dist/core/persistence/session-tree.d.ts +49 -0
- package/dist/core/persistence/session-tree.d.ts.map +1 -0
- package/dist/core/persistence/session-tree.js +1 -0
- package/dist/core/plugins/plugin-code.d.ts +13 -0
- package/dist/core/plugins/plugin-code.d.ts.map +1 -0
- package/dist/core/plugins/plugin-code.js +62 -0
- package/dist/core/plugins/plugin-marketplace.d.ts +72 -0
- package/dist/core/plugins/plugin-marketplace.d.ts.map +1 -0
- package/dist/core/plugins/plugin-marketplace.js +340 -0
- package/dist/core/plugins/plugin-resolve.d.ts +32 -0
- package/dist/core/plugins/plugin-resolve.d.ts.map +1 -0
- package/dist/core/plugins/plugin-resolve.js +101 -0
- package/dist/core/plugins/plugin-trust.d.ts +24 -0
- package/dist/core/plugins/plugin-trust.d.ts.map +1 -0
- package/dist/core/plugins/plugin-trust.js +89 -0
- package/dist/core/runtime/agent-event.d.ts +98 -0
- package/dist/core/runtime/agent-event.d.ts.map +1 -0
- package/dist/core/runtime/agent-event.js +1 -0
- package/dist/core/runtime/agent-loop.d.ts +142 -0
- package/dist/core/runtime/agent-loop.d.ts.map +1 -0
- package/dist/core/runtime/agent-loop.js +1008 -0
- package/dist/core/runtime/approval.d.ts +23 -0
- package/dist/core/runtime/approval.d.ts.map +1 -0
- package/dist/core/runtime/approval.js +25 -0
- package/dist/core/runtime/compaction.d.ts +22 -0
- package/dist/core/runtime/compaction.d.ts.map +1 -0
- package/dist/core/runtime/compaction.js +90 -0
- package/dist/core/runtime/context-estimate.d.ts +42 -0
- package/dist/core/runtime/context-estimate.d.ts.map +1 -0
- package/dist/core/runtime/context-estimate.js +34 -0
- package/dist/core/runtime/create-runtime.d.ts +81 -0
- package/dist/core/runtime/create-runtime.d.ts.map +1 -0
- package/dist/core/runtime/create-runtime.js +250 -0
- package/dist/core/runtime/fake-provider.d.ts +3 -0
- package/dist/core/runtime/fake-provider.d.ts.map +1 -0
- package/dist/core/runtime/fake-provider.js +87 -0
- package/dist/core/runtime/logger.d.ts +13 -0
- package/dist/core/runtime/logger.d.ts.map +1 -0
- package/dist/core/runtime/logger.js +38 -0
- package/dist/core/runtime/loop-input.d.ts +39 -0
- package/dist/core/runtime/loop-input.d.ts.map +1 -0
- package/dist/core/runtime/loop-input.js +38 -0
- package/dist/core/runtime/message-cache.d.ts +16 -0
- package/dist/core/runtime/message-cache.d.ts.map +1 -0
- package/dist/core/runtime/message-cache.js +23 -0
- package/dist/core/runtime/model-catalog.d.ts +10 -0
- package/dist/core/runtime/model-catalog.d.ts.map +1 -0
- package/dist/core/runtime/model-catalog.js +14 -0
- package/dist/core/runtime/plugin-runtime.d.ts +41 -0
- package/dist/core/runtime/plugin-runtime.d.ts.map +1 -0
- package/dist/core/runtime/plugin-runtime.js +1 -0
- package/dist/core/runtime/plugin.d.ts +86 -0
- package/dist/core/runtime/plugin.d.ts.map +1 -0
- package/dist/core/runtime/plugin.js +23 -0
- package/dist/core/runtime/process-tree.d.ts +12 -0
- package/dist/core/runtime/process-tree.d.ts.map +1 -0
- package/dist/core/runtime/process-tree.js +40 -0
- package/dist/core/runtime/prompt.d.ts +23 -0
- package/dist/core/runtime/prompt.d.ts.map +1 -0
- package/dist/core/runtime/prompt.js +29 -0
- package/dist/core/runtime/prompts.d.ts +16 -0
- package/dist/core/runtime/prompts.d.ts.map +1 -0
- package/dist/core/runtime/prompts.js +36 -0
- package/dist/core/runtime/retry.d.ts +20 -0
- package/dist/core/runtime/retry.d.ts.map +1 -0
- package/dist/core/runtime/retry.js +60 -0
- package/dist/core/runtime/run-runtime.d.ts +93 -0
- package/dist/core/runtime/run-runtime.d.ts.map +1 -0
- package/dist/core/runtime/run-runtime.js +664 -0
- package/dist/core/runtime/runtime-catalog.d.ts +12 -0
- package/dist/core/runtime/runtime-catalog.d.ts.map +1 -0
- package/dist/core/runtime/runtime-catalog.js +89 -0
- package/dist/core/runtime/stream-rules.d.ts +9 -0
- package/dist/core/runtime/stream-rules.d.ts.map +1 -0
- package/dist/core/runtime/stream-rules.js +44 -0
- package/dist/core/runtime/title.d.ts +7 -0
- package/dist/core/runtime/title.d.ts.map +1 -0
- package/dist/core/runtime/title.js +114 -0
- package/dist/core/runtime/todo.d.ts +16 -0
- package/dist/core/runtime/todo.d.ts.map +1 -0
- package/dist/core/runtime/todo.js +40 -0
- package/dist/core/runtime/tool-filter.d.ts +12 -0
- package/dist/core/runtime/tool-filter.d.ts.map +1 -0
- package/dist/core/runtime/tool-filter.js +23 -0
- package/dist/core/runtime/tool-pruning.d.ts +29 -0
- package/dist/core/runtime/tool-pruning.d.ts.map +1 -0
- package/dist/core/runtime/tool-pruning.js +105 -0
- package/dist/core/session/input-history.d.ts +8 -0
- package/dist/core/session/input-history.d.ts.map +1 -0
- package/dist/core/session/input-history.js +41 -0
- package/dist/core/session/session-file.d.ts +84 -0
- package/dist/core/session/session-file.d.ts.map +1 -0
- package/dist/core/session/session-file.js +392 -0
- package/dist/core/session/session-loop.d.ts +33 -0
- package/dist/core/session/session-loop.d.ts.map +1 -0
- package/dist/core/session/session-loop.js +30 -0
- package/dist/core/settings/project-settings.d.ts +46 -0
- package/dist/core/settings/project-settings.d.ts.map +1 -0
- package/dist/core/settings/project-settings.js +85 -0
- package/dist/core/settings/workspace-context.d.ts +10 -0
- package/dist/core/settings/workspace-context.d.ts.map +1 -0
- package/dist/core/settings/workspace-context.js +48 -0
- package/dist/core/tools/agent-fs-like.d.ts +15 -0
- package/dist/core/tools/agent-fs-like.d.ts.map +1 -0
- package/dist/core/tools/agent-fs-like.js +4 -0
- package/dist/core/tools/ask-question.d.ts +5 -0
- package/dist/core/tools/ask-question.d.ts.map +1 -0
- package/dist/core/tools/ask-question.js +51 -0
- package/dist/core/tools/bash.d.ts +5 -0
- package/dist/core/tools/bash.d.ts.map +1 -0
- package/dist/core/tools/bash.js +109 -0
- package/dist/core/tools/eval.d.ts +10 -0
- package/dist/core/tools/eval.d.ts.map +1 -0
- package/dist/core/tools/eval.js +83 -0
- package/dist/core/tools/file-tools.d.ts +18 -0
- package/dist/core/tools/file-tools.d.ts.map +1 -0
- package/dist/core/tools/file-tools.js +249 -0
- package/dist/core/tools/glob.d.ts +5 -0
- package/dist/core/tools/glob.d.ts.map +1 -0
- package/dist/core/tools/glob.js +54 -0
- package/dist/core/tools/grep.d.ts +5 -0
- package/dist/core/tools/grep.d.ts.map +1 -0
- package/dist/core/tools/grep.js +61 -0
- package/dist/core/tools/index.d.ts +14 -0
- package/dist/core/tools/index.d.ts.map +1 -0
- package/dist/core/tools/index.js +14 -0
- package/dist/core/tools/ls-tree.d.ts +10 -0
- package/dist/core/tools/ls-tree.d.ts.map +1 -0
- package/dist/core/tools/ls-tree.js +177 -0
- package/dist/core/tools/mcp-mount.d.ts +63 -0
- package/dist/core/tools/mcp-mount.d.ts.map +1 -0
- package/dist/core/tools/mcp-mount.js +287 -0
- package/dist/core/tools/read-image.d.ts +6 -0
- package/dist/core/tools/read-image.d.ts.map +1 -0
- package/dist/core/tools/read-image.js +98 -0
- package/dist/core/tools/skill.d.ts +11 -0
- package/dist/core/tools/skill.d.ts.map +1 -0
- package/dist/core/tools/skill.js +65 -0
- package/dist/core/tools/skills.d.ts +10 -0
- package/dist/core/tools/skills.d.ts.map +1 -0
- package/dist/core/tools/skills.js +116 -0
- package/dist/core/tools/todo-store.d.ts +8 -0
- package/dist/core/tools/todo-store.d.ts.map +1 -0
- package/dist/core/tools/todo-store.js +100 -0
- package/dist/core/tools/todo.d.ts +8 -0
- package/dist/core/tools/todo.d.ts.map +1 -0
- package/dist/core/tools/todo.js +91 -0
- package/dist/core/tools/url-guard.d.ts +12 -0
- package/dist/core/tools/url-guard.d.ts.map +1 -0
- package/dist/core/tools/url-guard.js +58 -0
- package/dist/core/tools/web-ports-std.d.ts +6 -0
- package/dist/core/tools/web-ports-std.d.ts.map +1 -0
- package/dist/core/tools/web-ports-std.js +145 -0
- package/dist/core/tools/web-ports.d.ts +56 -0
- package/dist/core/tools/web-ports.d.ts.map +1 -0
- package/dist/core/tools/web-ports.js +62 -0
- package/dist/core/tools/workspace-sandbox.d.ts +20 -0
- package/dist/core/tools/workspace-sandbox.d.ts.map +1 -0
- package/dist/core/tools/workspace-sandbox.js +84 -0
- package/dist/core/workflow/subagent-registry.d.ts +40 -0
- package/dist/core/workflow/subagent-registry.d.ts.map +1 -0
- package/dist/core/workflow/subagent-registry.js +118 -0
- package/dist/core/workflow/workflow-evaluator.d.ts +23 -0
- package/dist/core/workflow/workflow-evaluator.d.ts.map +1 -0
- package/dist/core/workflow/workflow-evaluator.js +21 -0
- package/dist/core/workflow/workflow-executor.d.ts +108 -0
- package/dist/core/workflow/workflow-executor.d.ts.map +1 -0
- package/dist/core/workflow/workflow-executor.js +549 -0
- package/dist/core/workflow/workflow-tools.d.ts +50 -0
- package/dist/core/workflow/workflow-tools.d.ts.map +1 -0
- package/dist/core/workflow/workflow-tools.js +217 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/main.d.ts +9 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +97 -0
- package/dist/modes/json-mode.d.ts +5 -0
- package/dist/modes/json-mode.d.ts.map +1 -0
- package/dist/modes/json-mode.js +46 -0
- package/dist/modes/print-mode.d.ts +20 -0
- package/dist/modes/print-mode.d.ts.map +1 -0
- package/dist/modes/print-mode.js +82 -0
- package/dist/modes/rpc/jsonl.d.ts +23 -0
- package/dist/modes/rpc/jsonl.d.ts.map +1 -0
- package/dist/modes/rpc/jsonl.js +88 -0
- package/dist/modes/rpc/rpc-mode.d.ts +18 -0
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -0
- package/dist/modes/rpc/rpc-mode.js +326 -0
- package/dist/modes/tui/settings-overlay.d.ts +23 -0
- package/dist/modes/tui/settings-overlay.d.ts.map +1 -0
- package/dist/modes/tui/settings-overlay.js +136 -0
- package/dist/modes/tui/tui-commands-session.d.ts +3 -0
- package/dist/modes/tui/tui-commands-session.d.ts.map +1 -0
- package/dist/modes/tui/tui-commands-session.js +191 -0
- package/dist/modes/tui/tui-commands.d.ts +52 -0
- package/dist/modes/tui/tui-commands.d.ts.map +1 -0
- package/dist/modes/tui/tui-commands.js +373 -0
- package/dist/modes/tui/tui-components.d.ts +81 -0
- package/dist/modes/tui/tui-components.d.ts.map +1 -0
- package/dist/modes/tui/tui-components.js +248 -0
- package/dist/modes/tui/tui-format.d.ts +65 -0
- package/dist/modes/tui/tui-format.d.ts.map +1 -0
- package/dist/modes/tui/tui-format.js +246 -0
- package/dist/modes/tui/tui-frame-provider.d.ts +16 -0
- package/dist/modes/tui/tui-frame-provider.d.ts.map +1 -0
- package/dist/modes/tui/tui-frame-provider.js +31 -0
- package/dist/modes/tui/tui-interactive.d.ts +16 -0
- package/dist/modes/tui/tui-interactive.d.ts.map +1 -0
- package/dist/modes/tui/tui-interactive.js +123 -0
- package/dist/modes/tui/tui-io.d.ts +12 -0
- package/dist/modes/tui/tui-io.d.ts.map +1 -0
- package/dist/modes/tui/tui-io.js +620 -0
- package/dist/modes/tui/tui-mode.d.ts +120 -0
- package/dist/modes/tui/tui-mode.d.ts.map +1 -0
- package/dist/modes/tui/tui-mode.js +391 -0
- package/dist/modes/tui/tui-render.d.ts +54 -0
- package/dist/modes/tui/tui-render.d.ts.map +1 -0
- package/dist/modes/tui/tui-render.js +399 -0
- package/dist/modes/tui/tui-slash.d.ts +12 -0
- package/dist/modes/tui/tui-slash.d.ts.map +1 -0
- package/dist/modes/tui/tui-slash.js +64 -0
- package/dist/modes/tui/tui-tool-render.d.ts +6 -0
- package/dist/modes/tui/tui-tool-render.d.ts.map +1 -0
- package/dist/modes/tui/tui-tool-render.js +79 -0
- package/dist/modes/tui/tui-transcript-reconciler.d.ts +25 -0
- package/dist/modes/tui/tui-transcript-reconciler.d.ts.map +1 -0
- package/dist/modes/tui/tui-transcript-reconciler.js +97 -0
- package/dist/modes/tui/view-state.d.ts +67 -0
- package/dist/modes/tui/view-state.d.ts.map +1 -0
- package/dist/modes/tui/view-state.js +353 -0
- package/dist/prompts/agents/subagent.md +26 -0
- package/dist/prompts/system/base.md +20 -0
- package/dist/prompts/system/memory.md +23 -0
- package/dist/prompts/system/safety.md +12 -0
- package/dist/protocol/index.d.ts +5 -0
- package/dist/protocol/index.d.ts.map +1 -0
- package/dist/protocol/index.js +2 -0
- package/dist/protocol/mapping.d.ts +25 -0
- package/dist/protocol/mapping.d.ts.map +1 -0
- package/dist/protocol/mapping.js +115 -0
- package/dist/protocol/transport.d.ts +1396 -0
- package/dist/protocol/transport.d.ts.map +1 -0
- package/dist/protocol/transport.js +170 -0
- package/package.json +49 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { AskQuestionInput, AskQuestionResult } from "@chengchenccc/agent-contract";
|
|
2
|
+
import type { ModelRuntime } from "@chengchenccc/ai";
|
|
3
|
+
import { type SlashCommand } from "@chengchenccc/tui";
|
|
4
|
+
import type { ToolFilter } from "../../core/runtime/tool-filter.js";
|
|
5
|
+
import { type SessionBranchNode } from "../../core/session/session-file.js";
|
|
6
|
+
import { type ProjectSettings } from "../../core/settings/project-settings.js";
|
|
7
|
+
import { type TuiViewState } from "./view-state.js";
|
|
8
|
+
/** TUI mode: oma's standalone interactive surface. One process = N
|
|
9
|
+
* consecutive Runs over ONE session file; each Run is its own Runtime
|
|
10
|
+
* (one Runtime = one Run invariant preserved). Enter submits; while a Run
|
|
11
|
+
* is live, Enter STEERS the message into the loop immediately (pi's
|
|
12
|
+
* streamingBehavior:"steer") and a steer rejected because the loop is
|
|
13
|
+
* settling falls back to a queue that auto-drains as the next Run's input
|
|
14
|
+
* (pi's AgentBusyError -> followUp) — no message is ever dropped. Esc
|
|
15
|
+
* aborts; ctrl+t toggles thinking; ctrl+o toggles tool detail; /exit
|
|
16
|
+
* quits.
|
|
17
|
+
*
|
|
18
|
+
* All terminal wiring lives behind the TerminalIo seam so tests can drive
|
|
19
|
+
* the whole loop headlessly. */
|
|
20
|
+
export interface TuiModeOptions {
|
|
21
|
+
modelRuntime: ModelRuntime;
|
|
22
|
+
workspaceRoot: string;
|
|
23
|
+
/** Canonical `<provider>/<model>` id; undefined = first available. */
|
|
24
|
+
model?: string;
|
|
25
|
+
/** Resume a specific session file instead of starting fresh. */
|
|
26
|
+
sessionId?: string;
|
|
27
|
+
/** Prefill the editor with this text on boot (`oma "prompt"`). The user
|
|
28
|
+
* hits Enter to send it like any other input. */
|
|
29
|
+
initialPrompt?: string;
|
|
30
|
+
/** --tools filter (CLI): applied to the final tool table. */
|
|
31
|
+
toolFilter?: ToolFilter;
|
|
32
|
+
}
|
|
33
|
+
/** View/abort commands from the terminal (Esc abort, ctrl+t, ctrl+o, ctrl+p). */
|
|
34
|
+
export type TuiCommand = "toggleThinking" | "toggleToolDetail" | "abort" | "pickModel" | "forkTree";
|
|
35
|
+
export interface TuiIo {
|
|
36
|
+
/** Render the current view state. */
|
|
37
|
+
render(state: TuiViewState): void;
|
|
38
|
+
/** Wait for the next user submit; resolves null on quit (Ctrl-D / /exit).
|
|
39
|
+
* Submits that arrive while a run is live (busy) are delivered to
|
|
40
|
+
* onLiveInput instead - waitForInput only resolves between runs. */
|
|
41
|
+
waitForInput(): Promise<string | null>;
|
|
42
|
+
/** Called once when a run goes live or settles, to toggle input mode. */
|
|
43
|
+
setBusy?(busy: boolean): void;
|
|
44
|
+
/** Subscriber for inputs submitted while a run is live (steer). */
|
|
45
|
+
onLiveInput?(handler: ((text: string) => void) | null): void;
|
|
46
|
+
/** Subscriber for slash commands submitted while a run is live; the
|
|
47
|
+
* session loop executes them instead of steering the text (pi's
|
|
48
|
+
* LiveCommandController). */
|
|
49
|
+
onLiveCommand?(handler: ((text: string) => void) | null): void;
|
|
50
|
+
/** Subscriber for view/abort commands (Esc, ctrl+t, ctrl+o). */
|
|
51
|
+
onCommand?(handler: ((cmd: TuiCommand) => void) | null): void;
|
|
52
|
+
/** Register the slash-command list for editor autocomplete. */
|
|
53
|
+
setSlashCommands?(commands: readonly SlashCommand[]): void;
|
|
54
|
+
/** Interactive session picker overlay; resolves the chosen session id,
|
|
55
|
+
* or null when cancelled. Absent = caller falls back to a text list. */
|
|
56
|
+
pickSession?(sessions: ReadonlyArray<{
|
|
57
|
+
id: string;
|
|
58
|
+
title?: string;
|
|
59
|
+
preview: string;
|
|
60
|
+
modifiedAt: number;
|
|
61
|
+
workspace?: string;
|
|
62
|
+
forkOf?: string;
|
|
63
|
+
}>): Promise<string | null>;
|
|
64
|
+
/** Interactive model picker overlay (ctrl+p); resolves the chosen
|
|
65
|
+
* canonical `<provider>/<model>` id, or null when cancelled. */
|
|
66
|
+
pickModel?(models: ReadonlyArray<{
|
|
67
|
+
id: string;
|
|
68
|
+
label: string;
|
|
69
|
+
description?: string;
|
|
70
|
+
}>): Promise<string | null>;
|
|
71
|
+
/** Interactive approval confirm (HITL); resolves "allow"/"deny", null on
|
|
72
|
+
* cancel (treated as deny — fail-closed). Absent = deny. */
|
|
73
|
+
confirmApproval?(req: {
|
|
74
|
+
toolName: string;
|
|
75
|
+
reason?: string;
|
|
76
|
+
}): Promise<"allow" | "deny" | null>;
|
|
77
|
+
/** Interactive ask_question form (HITL); resolves answers or null on
|
|
78
|
+
* cancel/unsupported question kind (fail-closed). */
|
|
79
|
+
askQuestions?(input: AskQuestionInput): Promise<AskQuestionResult | null>;
|
|
80
|
+
/** Interactive fork-point picker (pi's user-message selector): lists the
|
|
81
|
+
* session's user messages; resolves the chosen 1-based ordinal, or null
|
|
82
|
+
* when cancelled. Absent = caller falls back to /fork <n>. */
|
|
83
|
+
pickForkPoint?(points: ReadonlyArray<{
|
|
84
|
+
ordinal: number;
|
|
85
|
+
text: string;
|
|
86
|
+
}>): Promise<number | null>;
|
|
87
|
+
/** Interactive branch-tree fork picker: lists the session's parentId-
|
|
88
|
+
* chained message nodes; resolves the chosen node id, or null when
|
|
89
|
+
* cancelled. Absent = caller falls back to the /fork text path. */
|
|
90
|
+
pickBranchTree?(nodes: ReadonlyArray<SessionBranchNode>): Promise<string | null>;
|
|
91
|
+
/** Interactive settings editor; resolves the updated settings or null on
|
|
92
|
+
* cancel. Absent = caller falls back to text status. */
|
|
93
|
+
editSettings?(settings: ProjectSettings): Promise<ProjectSettings | null>;
|
|
94
|
+
/** Update the fixed header's model/session line. `context` is sticky:
|
|
95
|
+
* once set it stays until the next value arrives. */
|
|
96
|
+
setHeader?(info: {
|
|
97
|
+
model?: string;
|
|
98
|
+
sessionId?: string;
|
|
99
|
+
title?: string;
|
|
100
|
+
context?: string;
|
|
101
|
+
}): void;
|
|
102
|
+
/** Prefill the editor text (used for `oma "<prompt>"`). */
|
|
103
|
+
setInputText?(text: string): void;
|
|
104
|
+
/** True while the terminal window holds focus (CSI 1004 reporting).
|
|
105
|
+
* Absent = always considered focused. */
|
|
106
|
+
isFocused?(): boolean;
|
|
107
|
+
/** Subscriber for terminal focus transitions (CSI 1004 reporting). */
|
|
108
|
+
onFocus?(handler: ((focused: boolean) => void) | null): void;
|
|
109
|
+
/** Best-effort completion ping (BEL). Absent = silent. */
|
|
110
|
+
notify?(): void;
|
|
111
|
+
/** Stop the terminal (restore modes). */
|
|
112
|
+
close(): void;
|
|
113
|
+
}
|
|
114
|
+
/** The full interactive session loop, driver-agnostic. */
|
|
115
|
+
export declare function runTuiSession(opts: TuiModeOptions, io: TuiIo): Promise<number>;
|
|
116
|
+
/** CLI entry: run the interactive session over the real terminal. */
|
|
117
|
+
export declare function runTuiMode(opts: TuiModeOptions): Promise<number>;
|
|
118
|
+
export { formatModelMeta } from "./tui-format.js";
|
|
119
|
+
export { createTerminalIo } from "./tui-io.js";
|
|
120
|
+
//# sourceMappingURL=tui-mode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tui-mode.d.ts","sourceRoot":"","sources":["../../../src/modes/tui/tui-mode.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { ProcessTerminal } from "@chengchenccc/tui";
|
|
3
|
+
import { buildCliRunInput } from "../../cli/initial-input.js";
|
|
4
|
+
import { assemblePluginRuntime } from "../../core/plugins/plugin-resolve.js";
|
|
5
|
+
import { createOmaRuntime } from "../../core/runtime/create-runtime.js";
|
|
6
|
+
import { appendSessionMessages, listSessions, } from "../../core/session/session-file.js";
|
|
7
|
+
import { persistSessionTurn, resolveSession } from "../../core/session/session-loop.js";
|
|
8
|
+
import { loadProjectSettings } from "../../core/settings/project-settings.js";
|
|
9
|
+
import { buildCommands } from "./tui-commands.js";
|
|
10
|
+
import { formatTokens } from "./tui-format.js";
|
|
11
|
+
import { forkTreeInteractive, lastRunRecap, listModelRows, listModels, pickModelInteractive, registerIoHandlers, } from "./tui-interactive.js";
|
|
12
|
+
import { createTerminalIo } from "./tui-io.js";
|
|
13
|
+
import { buildSlashSystem } from "./tui-slash.js";
|
|
14
|
+
import { addUserInput, applyEvent, applyOutcome, hydrateTranscript, initialViewState, settleSteeredMessages, } from "./view-state.js";
|
|
15
|
+
/** A saved project model is used only when it still resolves in the catalog;
|
|
16
|
+
* a stale provider/model must not brick TUI startup. */
|
|
17
|
+
async function savedModelIsAvailable(modelRuntime, modelId) {
|
|
18
|
+
try {
|
|
19
|
+
const catalog = await modelRuntime.getCatalog();
|
|
20
|
+
return catalog.models.some((m) => `${m.providerId}/${m.modelId}` === modelId && m.available !== false);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** The full interactive session loop, driver-agnostic. */
|
|
27
|
+
export async function runTuiSession(opts, io) {
|
|
28
|
+
let session = resolveSession(opts.sessionId);
|
|
29
|
+
const state = initialViewState();
|
|
30
|
+
hydrateTranscript(state, session.messages);
|
|
31
|
+
let modelId;
|
|
32
|
+
if (opts.model) {
|
|
33
|
+
modelId = opts.model;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
const saved = loadProjectSettings(opts.workspaceRoot).model;
|
|
37
|
+
modelId = saved && (await savedModelIsAvailable(opts.modelRuntime, saved)) ? saved : undefined;
|
|
38
|
+
}
|
|
39
|
+
let liveRuntime = null;
|
|
40
|
+
let sessionTitle;
|
|
41
|
+
let quitting = false;
|
|
42
|
+
let exitArmed = false;
|
|
43
|
+
/** Estimated context tokens after the last run: drives the picker's
|
|
44
|
+
* over-context warning (pi grays out models smaller than the session). */
|
|
45
|
+
let lastContextTokens;
|
|
46
|
+
/** Recap shown when the terminal regains focus after an unfocused run. */
|
|
47
|
+
let pendingFocusRecap;
|
|
48
|
+
/** Prompt queued by a command (skill invocations): submitted as the next
|
|
49
|
+
* normal run. */
|
|
50
|
+
let pendingPrompt;
|
|
51
|
+
/** Workflow script queued by /workflow: injected into the next run input. */
|
|
52
|
+
let pendingWorkflowScript;
|
|
53
|
+
function pushStatus(lines, replacePrefix) {
|
|
54
|
+
const items = (typeof lines === "string" ? [lines] : lines).map((text) => ({
|
|
55
|
+
kind: "status",
|
|
56
|
+
text,
|
|
57
|
+
streaming: false,
|
|
58
|
+
}));
|
|
59
|
+
if (replacePrefix) {
|
|
60
|
+
for (let i = state.runs.length - 1; i >= 0; i--) {
|
|
61
|
+
const first = state.runs[i].items[0];
|
|
62
|
+
if (first?.kind === "status" && first.text.startsWith(replacePrefix)) {
|
|
63
|
+
state.runs[i] = { items, running: false };
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
state.runs.push({ items, running: false });
|
|
69
|
+
}
|
|
70
|
+
/** Compact recap text for the most recent run: last assistant message
|
|
71
|
+
* first line, falling back to the auto title. */
|
|
72
|
+
io.setHeader?.({ model: modelId, sessionId: session.sessionId, title: sessionTitle });
|
|
73
|
+
// `oma "<prompt>"` opens the TUI with the prompt prefilled in the editor.
|
|
74
|
+
if (opts.initialPrompt)
|
|
75
|
+
io.setInputText?.(opts.initialPrompt);
|
|
76
|
+
const ctx = {
|
|
77
|
+
state,
|
|
78
|
+
io,
|
|
79
|
+
opts,
|
|
80
|
+
get session() {
|
|
81
|
+
return session;
|
|
82
|
+
},
|
|
83
|
+
set session(value) {
|
|
84
|
+
session = value;
|
|
85
|
+
},
|
|
86
|
+
get sessionTitle() {
|
|
87
|
+
return sessionTitle;
|
|
88
|
+
},
|
|
89
|
+
set sessionTitle(value) {
|
|
90
|
+
sessionTitle = value;
|
|
91
|
+
},
|
|
92
|
+
get modelId() {
|
|
93
|
+
return modelId;
|
|
94
|
+
},
|
|
95
|
+
set modelId(value) {
|
|
96
|
+
modelId = value;
|
|
97
|
+
},
|
|
98
|
+
get quitting() {
|
|
99
|
+
return quitting;
|
|
100
|
+
},
|
|
101
|
+
set quitting(value) {
|
|
102
|
+
quitting = value;
|
|
103
|
+
},
|
|
104
|
+
get exitArmed() {
|
|
105
|
+
return exitArmed;
|
|
106
|
+
},
|
|
107
|
+
set exitArmed(value) {
|
|
108
|
+
exitArmed = value;
|
|
109
|
+
},
|
|
110
|
+
get pendingPrompt() {
|
|
111
|
+
return pendingPrompt;
|
|
112
|
+
},
|
|
113
|
+
set pendingPrompt(value) {
|
|
114
|
+
pendingPrompt = value;
|
|
115
|
+
},
|
|
116
|
+
get pendingWorkflowScript() {
|
|
117
|
+
return pendingWorkflowScript;
|
|
118
|
+
},
|
|
119
|
+
set pendingWorkflowScript(value) {
|
|
120
|
+
pendingWorkflowScript = value;
|
|
121
|
+
},
|
|
122
|
+
get liveRuntime() {
|
|
123
|
+
return liveRuntime;
|
|
124
|
+
},
|
|
125
|
+
set liveRuntime(value) {
|
|
126
|
+
liveRuntime = value;
|
|
127
|
+
},
|
|
128
|
+
get lastContextTokens() {
|
|
129
|
+
return lastContextTokens;
|
|
130
|
+
},
|
|
131
|
+
set lastContextTokens(value) {
|
|
132
|
+
lastContextTokens = value;
|
|
133
|
+
},
|
|
134
|
+
get pendingFocusRecap() {
|
|
135
|
+
return pendingFocusRecap;
|
|
136
|
+
},
|
|
137
|
+
set pendingFocusRecap(value) {
|
|
138
|
+
pendingFocusRecap = value;
|
|
139
|
+
},
|
|
140
|
+
pushStatus,
|
|
141
|
+
listModels: () => listModels(ctx),
|
|
142
|
+
listModelRows: () => listModelRows(ctx),
|
|
143
|
+
listSessions,
|
|
144
|
+
pickModelInteractive: () => pickModelInteractive(ctx),
|
|
145
|
+
forkTreeInteractive: () => forkTreeInteractive(ctx),
|
|
146
|
+
};
|
|
147
|
+
const commands = buildCommands(ctx);
|
|
148
|
+
const { slashCommands, runCommandText } = buildSlashSystem(ctx, commands);
|
|
149
|
+
ctx.runCommandText = runCommandText;
|
|
150
|
+
io.setSlashCommands?.(slashCommands);
|
|
151
|
+
registerIoHandlers(ctx);
|
|
152
|
+
io.onLiveCommand?.((text) => {
|
|
153
|
+
void runCommandText(text).then(() => io.render(state));
|
|
154
|
+
});
|
|
155
|
+
/** Steers rejected while the loop was settling; drained as the next
|
|
156
|
+
* Run's prompt when the current Run ends (pi's followUp fallback). */
|
|
157
|
+
const pendingFollowUps = [];
|
|
158
|
+
for (;;) {
|
|
159
|
+
io.render(state);
|
|
160
|
+
// Steers that arrived while the previous loop was settling are drained
|
|
161
|
+
// here as the next Run's prompt (already echoed as » items — no re-echo).
|
|
162
|
+
let text;
|
|
163
|
+
let fromFollowUp = false;
|
|
164
|
+
if (pendingFollowUps.length > 0) {
|
|
165
|
+
const drained = pendingFollowUps.splice(0);
|
|
166
|
+
text = drained.join("\n\n");
|
|
167
|
+
fromFollowUp = true;
|
|
168
|
+
// Settle the queued » echoes in place — no re-echo (pi moves queued
|
|
169
|
+
// messages into the chat when they are delivered).
|
|
170
|
+
settleSteeredMessages(state, drained);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
const input = await io.waitForInput();
|
|
174
|
+
if (input === null)
|
|
175
|
+
return 0;
|
|
176
|
+
text = input.trim();
|
|
177
|
+
if (!text)
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (text.startsWith("/")) {
|
|
181
|
+
await runCommandText(text);
|
|
182
|
+
if (quitting)
|
|
183
|
+
return 0;
|
|
184
|
+
// A command queued a prompt (skill invocation, workflow): submit it as
|
|
185
|
+
// the next run instead of returning to the editor.
|
|
186
|
+
if (pendingPrompt === undefined)
|
|
187
|
+
continue;
|
|
188
|
+
text = pendingPrompt;
|
|
189
|
+
pendingPrompt = undefined;
|
|
190
|
+
}
|
|
191
|
+
if (!fromFollowUp)
|
|
192
|
+
addUserInput(state, text);
|
|
193
|
+
const built = await buildCliRunInput({
|
|
194
|
+
prompt: text,
|
|
195
|
+
workspaceRoot: opts.workspaceRoot,
|
|
196
|
+
modelRuntime: opts.modelRuntime,
|
|
197
|
+
modelId,
|
|
198
|
+
});
|
|
199
|
+
modelId = built.run.model.modelId;
|
|
200
|
+
// /workflow queued a script: this run executes the vm workflow instead
|
|
201
|
+
// of a conversational loop (create-runtime branches on input.workflow).
|
|
202
|
+
let runInput = built;
|
|
203
|
+
if (pendingWorkflowScript !== undefined) {
|
|
204
|
+
runInput = { ...built, workflow: { script: pendingWorkflowScript } };
|
|
205
|
+
pendingWorkflowScript = undefined;
|
|
206
|
+
}
|
|
207
|
+
const pluginRt = await assemblePluginRuntime(opts.workspaceRoot, "tui");
|
|
208
|
+
for (const w of pluginRt.warnings)
|
|
209
|
+
pushStatus(`[plugin] ${w}`);
|
|
210
|
+
const runtime = await createOmaRuntime({
|
|
211
|
+
runId: `tui-${randomUUID()}`,
|
|
212
|
+
modelId: built.run.model.modelId,
|
|
213
|
+
workspaceRoot: opts.workspaceRoot,
|
|
214
|
+
workspaceAccess: "read_write",
|
|
215
|
+
modelRuntime: opts.modelRuntime,
|
|
216
|
+
skillRoots: built.run.skillRoots ?? [],
|
|
217
|
+
gateWorkspaceMcp: true,
|
|
218
|
+
// HITL: interactive approval overlay; absent picker or cancel = deny.
|
|
219
|
+
approvalHandler: async (req) => {
|
|
220
|
+
const verdict = await io.confirmApproval?.({
|
|
221
|
+
toolName: req.toolName,
|
|
222
|
+
...(req.reason ? { reason: req.reason } : {}),
|
|
223
|
+
});
|
|
224
|
+
return verdict === "allow"
|
|
225
|
+
? { decision: "allow" }
|
|
226
|
+
: { decision: "deny", reason: "user denied" };
|
|
227
|
+
},
|
|
228
|
+
// HITL ask_question: interactive overlay; absent/cancel = null (tool
|
|
229
|
+
// fails closed with "no answer").
|
|
230
|
+
...(io.askQuestions ? { askHandler: io.askQuestions } : {}),
|
|
231
|
+
...(pluginRt.plugins.length || pluginRt.mcpServers.length
|
|
232
|
+
? { pluginComponents: { plugins: pluginRt.plugins, mcpServers: pluginRt.mcpServers } }
|
|
233
|
+
: {}),
|
|
234
|
+
...(built.run.permissionMode ? { permissionMode: built.run.permissionMode } : {}),
|
|
235
|
+
...(opts.toolFilter ? { toolFilter: opts.toolFilter } : {}),
|
|
236
|
+
sessionTranscript: session.messages.length
|
|
237
|
+
? session.messages.map((m, i) => ({
|
|
238
|
+
productEntryId: `session:${i}`,
|
|
239
|
+
message: m,
|
|
240
|
+
}))
|
|
241
|
+
: undefined,
|
|
242
|
+
// Render on every event so model chunks (message_update) hit the
|
|
243
|
+
// screen incrementally; the TUI's requestRender throttles/coalesces,
|
|
244
|
+
// so high-frequency chunk events are safe here.
|
|
245
|
+
onEvent: (envelope) => {
|
|
246
|
+
applyEvent(state, envelope.data);
|
|
247
|
+
io.render(state);
|
|
248
|
+
},
|
|
249
|
+
// Real-time session persistence (pi appendMessage): every
|
|
250
|
+
// conversational persist (user prompt, steer, assistant, tool result)
|
|
251
|
+
// lands in the session file immediately, so even a killed/failed
|
|
252
|
+
// process leaves its context for the next turn.
|
|
253
|
+
onPersistMessages: (messages) => {
|
|
254
|
+
appendSessionMessages(session.sessionId, opts.workspaceRoot, messages, session.dir);
|
|
255
|
+
// The session file is wire-loose; the in-memory transcript keeps the
|
|
256
|
+
// same loose shape so it round-trips into sessionTranscript verbatim.
|
|
257
|
+
session.messages = [
|
|
258
|
+
...session.messages,
|
|
259
|
+
...messages.map((m) => ({ ...m })),
|
|
260
|
+
];
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
io.setBusy?.(true);
|
|
264
|
+
liveRuntime = runtime;
|
|
265
|
+
let outcome;
|
|
266
|
+
try {
|
|
267
|
+
// Steer: a submit while the run is live injects immediately (pi's
|
|
268
|
+
// streamingBehavior:"steer" — the loop buffers to a safe boundary).
|
|
269
|
+
// A steer rejected because the loop is settling falls back to the
|
|
270
|
+
// follow-up queue and is delivered as the next Run's input — the
|
|
271
|
+
// message is never dropped (pi's AgentBusyError -> followUp).
|
|
272
|
+
const steerHandler = (text) => {
|
|
273
|
+
addUserInput(state, text, true);
|
|
274
|
+
io.render(state);
|
|
275
|
+
runtime
|
|
276
|
+
.steer({
|
|
277
|
+
inputId: `steer-${randomUUID()}`,
|
|
278
|
+
message: { role: "user", text },
|
|
279
|
+
})
|
|
280
|
+
.catch(() => {
|
|
281
|
+
pendingFollowUps.push(text);
|
|
282
|
+
pushStatus("queued: run is settling — sends when it ends");
|
|
283
|
+
io.render(state);
|
|
284
|
+
});
|
|
285
|
+
};
|
|
286
|
+
io.onLiveInput?.(steerHandler);
|
|
287
|
+
const segment = await runtime.run(runInput);
|
|
288
|
+
outcome = await segment.outcome;
|
|
289
|
+
io.onLiveInput?.(null);
|
|
290
|
+
}
|
|
291
|
+
catch (err) {
|
|
292
|
+
outcome = {
|
|
293
|
+
status: "failed",
|
|
294
|
+
error: err instanceof Error ? err.message : String(err),
|
|
295
|
+
};
|
|
296
|
+
io.onLiveInput?.(null);
|
|
297
|
+
}
|
|
298
|
+
liveRuntime = null;
|
|
299
|
+
applyOutcome(state, outcome);
|
|
300
|
+
io.setBusy?.(false);
|
|
301
|
+
// Long runs often outlast the user's attention: ping when the terminal
|
|
302
|
+
// lost focus so switching back is prompted (pi's desktop-notify analog).
|
|
303
|
+
if (io.isFocused?.() === false) {
|
|
304
|
+
pendingFocusRecap = lastRunRecap(ctx);
|
|
305
|
+
pushStatus("run finished — switch back for recap");
|
|
306
|
+
io.notify?.();
|
|
307
|
+
}
|
|
308
|
+
// Messages were persisted in real time (onPersistMessages); only the
|
|
309
|
+
// end-of-run artifacts (compaction summaries, auto title) remain.
|
|
310
|
+
await persistSessionTurn({
|
|
311
|
+
sessionId: session.sessionId,
|
|
312
|
+
cwd: opts.workspaceRoot,
|
|
313
|
+
runtime,
|
|
314
|
+
dir: session.dir,
|
|
315
|
+
...(outcome.status === "completed" ? { title: outcome.title } : {}),
|
|
316
|
+
});
|
|
317
|
+
if (outcome.status === "completed") {
|
|
318
|
+
sessionTitle = outcome.title ?? sessionTitle;
|
|
319
|
+
io.setHeader?.({ model: modelId, sessionId: session.sessionId, title: sessionTitle });
|
|
320
|
+
}
|
|
321
|
+
// Compaction happened mid-run: surface what was folded away so the
|
|
322
|
+
// user knows the context was summarized.
|
|
323
|
+
for (const summary of await runtime.compactions()) {
|
|
324
|
+
pushStatus(`compacted: ${summary.slice(0, 160)}${summary.length > 160 ? "…" : ""}`);
|
|
325
|
+
}
|
|
326
|
+
// omp AutoLearn-style indicator: the run's background memory-learn pass
|
|
327
|
+
// shows on the transcript without blocking the editor (the promise
|
|
328
|
+
// resolves even after close()).
|
|
329
|
+
const learning = runtime.memoryLearning();
|
|
330
|
+
if (learning) {
|
|
331
|
+
pushStatus("memory: learning…");
|
|
332
|
+
io.render(state);
|
|
333
|
+
void learning.then((res) => {
|
|
334
|
+
pushStatus(res.freshFacts > 0
|
|
335
|
+
? `memory: learned ${res.freshFacts} fact${res.freshFacts === 1 ? "" : "s"}`
|
|
336
|
+
: res.ran
|
|
337
|
+
? "memory: nothing new to learn"
|
|
338
|
+
: "memory: skipped", "memory: ");
|
|
339
|
+
io.render(state);
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
// Context footprint of the settled branch under the run model's window
|
|
343
|
+
// (pi's context-usage display); read BEFORE close() like compactions().
|
|
344
|
+
const usage = await runtime.contextUsage().catch(() => undefined);
|
|
345
|
+
if (usage)
|
|
346
|
+
lastContextTokens = usage.estimatedTokens;
|
|
347
|
+
const usageParts = [];
|
|
348
|
+
if (outcome.usage) {
|
|
349
|
+
if (outcome.usage.inputTokens)
|
|
350
|
+
usageParts.push(`↑${outcome.usage.inputTokens}`);
|
|
351
|
+
if (outcome.usage.outputTokens)
|
|
352
|
+
usageParts.push(`↓${outcome.usage.outputTokens}`);
|
|
353
|
+
if (outcome.usage.cacheReadTokens)
|
|
354
|
+
usageParts.push(`cache ${outcome.usage.cacheReadTokens}`);
|
|
355
|
+
}
|
|
356
|
+
if ((usage && usage.limit > 0) || usageParts.length > 0) {
|
|
357
|
+
const ctx = usage && usage.limit > 0
|
|
358
|
+
? `ctx ${formatTokens(usage.estimatedTokens)}/${formatTokens(usage.limit)}`
|
|
359
|
+
: "";
|
|
360
|
+
const pct = usage && usage.limit > 0
|
|
361
|
+
? Math.min(100, Math.round((usage.estimatedTokens / usage.limit) * 100))
|
|
362
|
+
: 0;
|
|
363
|
+
const context = [ctx, ...(usageParts.length > 0 ? [usageParts.join(" ")] : [])]
|
|
364
|
+
.filter(Boolean)
|
|
365
|
+
.join(" · ");
|
|
366
|
+
io.setHeader?.({
|
|
367
|
+
model: modelId,
|
|
368
|
+
sessionId: session.sessionId,
|
|
369
|
+
title: sessionTitle,
|
|
370
|
+
context: pct > 0 ? `${context} · ${pct}%` : context,
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
await runtime.close().catch(() => { });
|
|
374
|
+
// /exit (or a second ctrl+c path) may have run via the live-command
|
|
375
|
+
// channel while the Run was live — honor it now that the Run settled.
|
|
376
|
+
if (quitting)
|
|
377
|
+
return 0;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/** CLI entry: run the interactive session over the real terminal. */
|
|
381
|
+
export async function runTuiMode(opts) {
|
|
382
|
+
const io = createTerminalIo(new ProcessTerminal(), opts.workspaceRoot);
|
|
383
|
+
try {
|
|
384
|
+
return await runTuiSession(opts, io);
|
|
385
|
+
}
|
|
386
|
+
finally {
|
|
387
|
+
io.close();
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
export { formatModelMeta } from "./tui-format.js";
|
|
391
|
+
export { createTerminalIo } from "./tui-io.js";
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type Container, type Loader, type TUI } from "@chengchenccc/tui";
|
|
2
|
+
import type { OmaTranscriptContainer } from "./tui-components.js";
|
|
3
|
+
import type { TranscriptItem, TuiViewState } from "./view-state.js";
|
|
4
|
+
/** Renders transcript items to display lines, memoizing markdown/tool blocks.
|
|
5
|
+
* Extracted from tui-io.ts to keep the io assembly file under 500 lines. */
|
|
6
|
+
export declare class TuiItemRenderer {
|
|
7
|
+
private readonly tui;
|
|
8
|
+
private readonly markdownCache;
|
|
9
|
+
private readonly itemLineCache;
|
|
10
|
+
private readonly toolBlockCache;
|
|
11
|
+
constructor(tui: TUI);
|
|
12
|
+
renderItem(item: TranscriptItem, state: TuiViewState): string[];
|
|
13
|
+
private computeRenderItem;
|
|
14
|
+
private markdownLines;
|
|
15
|
+
private renderThinking;
|
|
16
|
+
private renderTool;
|
|
17
|
+
}
|
|
18
|
+
/** Header/status/transcript render shell for createTerminalIo. Owns the
|
|
19
|
+
* display-only mutable state (header fields, busy, status line) so the io
|
|
20
|
+
* assembly file stays small. */
|
|
21
|
+
export declare class TuiRenderShell {
|
|
22
|
+
private readonly tui;
|
|
23
|
+
private readonly headerContainer;
|
|
24
|
+
private readonly transcript;
|
|
25
|
+
private readonly statusContainer;
|
|
26
|
+
private readonly workspaceRoot;
|
|
27
|
+
private readonly welcomeTip;
|
|
28
|
+
private headerInfo;
|
|
29
|
+
private headerModel;
|
|
30
|
+
private headerSession;
|
|
31
|
+
private headerTitle;
|
|
32
|
+
private headerContext;
|
|
33
|
+
private busy;
|
|
34
|
+
private loader;
|
|
35
|
+
private busySeconds;
|
|
36
|
+
private currentState;
|
|
37
|
+
statusLineText: string;
|
|
38
|
+
private readonly itemRenderer;
|
|
39
|
+
private readonly reconciler;
|
|
40
|
+
lastLiveStartRow: number;
|
|
41
|
+
lastTotalRows: number;
|
|
42
|
+
constructor(tui: TUI, headerContainer: Container, transcript: OmaTranscriptContainer, statusContainer: Container, workspaceRoot: string, welcomeTip: string);
|
|
43
|
+
setHeader(model: string, session: string, title: string, context?: string): void;
|
|
44
|
+
setBusy(busy: boolean, loader: Loader | null, busySeconds: number): void;
|
|
45
|
+
setBusySeconds(busySeconds: number): void;
|
|
46
|
+
setCurrentState(state: TuiViewState): void;
|
|
47
|
+
renderHeader(): void;
|
|
48
|
+
currentActivitySummary(): string | undefined;
|
|
49
|
+
updateWorkingMessage(): void;
|
|
50
|
+
addStatusBar(): void;
|
|
51
|
+
renderIdleFooter(): void;
|
|
52
|
+
render(state: TuiViewState): void;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=tui-render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tui-render.d.ts","sourceRoot":"","sources":["../../../src/modes/tui/tui-render.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,SAAS,EAEd,KAAK,MAAM,EAMX,KAAK,GAAG,EAGT,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAgBlE,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpE;6EAC6E;AAC7E,qBAAa,eAAe;IAQd,OAAO,CAAC,QAAQ,CAAC,GAAG;IAPhC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA2C;IACzE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAG1B;IACJ,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAoD;gBAEtD,GAAG,EAAE,GAAG;IAErC,UAAU,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,GAAG,MAAM,EAAE;IAuB/D,OAAO,CAAC,iBAAiB;IAyBzB,OAAO,CAAC,aAAa;IAgBrB,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,UAAU;CAyHnB;AAED;;iCAEiC;AACjC,qBAAa,cAAc;IAiBvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,UAAU;IArB7B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,WAAW,CAAM;IACzB,OAAO,CAAC,aAAa,CAAM;IAC3B,OAAO,CAAC,WAAW,CAAM;IACzB,OAAO,CAAC,aAAa,CAAM;IAC3B,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,YAAY,CAA6B;IACjD,cAAc,SAAM;IACpB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAkB;IAC/C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA0B;IACrD,gBAAgB,SAAK;IACrB,aAAa,SAAK;gBAGC,GAAG,EAAE,GAAG,EACR,eAAe,EAAE,SAAS,EAC1B,UAAU,EAAE,sBAAsB,EAClC,eAAe,EAAE,SAAS,EAC1B,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM;IAMrC,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI;IAahF,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI;IAMxE,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAIzC,eAAe,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI;IAI1C,YAAY,IAAI,IAAI;IAiDpB,sBAAsB,IAAI,MAAM,GAAG,SAAS;IAyC5C,oBAAoB,IAAI,IAAI;IAU5B,YAAY,IAAI,IAAI;IAqBpB,gBAAgB,IAAI,IAAI;IAKxB,MAAM,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI;CA6BlC"}
|