@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,620 @@
|
|
|
1
|
+
import { applyBackgroundToLine, CombinedAutocompleteProvider, Container, Editor, Loader, matchesKey, ProcessTerminal, SelectList, Text, TUI, truncateToWidth, } from "@chengchenccc/tui";
|
|
2
|
+
import { appendInputHistory, loadInputHistory, saveInputHistory, } from "../../core/session/input-history.js";
|
|
3
|
+
import { SettingsOverlay } from "./settings-overlay.js";
|
|
4
|
+
import { HistorySearchOverlay, OmaTranscriptContainer, PickerOverlay } from "./tui-components.js";
|
|
5
|
+
import { EDITOR_THEME, relativeTime, WELCOME_TIPS } from "./tui-format.js";
|
|
6
|
+
import { createOmaFrameProvider } from "./tui-frame-provider.js";
|
|
7
|
+
import { TuiRenderShell } from "./tui-render.js";
|
|
8
|
+
/** Pre-order branch-tree rows with git-graph prefixes (pi tree-selector
|
|
9
|
+
* semantics): indent grows only at branch points, single-child chains stay
|
|
10
|
+
* flat, ancestors leave "│" rails at their fork columns. */
|
|
11
|
+
export function layoutBranchTree(nodes) {
|
|
12
|
+
const byId = new Map(nodes.map((n) => [n.id, n]));
|
|
13
|
+
const childrenOf = new Map();
|
|
14
|
+
const roots = [];
|
|
15
|
+
for (const n of nodes) {
|
|
16
|
+
if (n.parentId && byId.has(n.parentId)) {
|
|
17
|
+
const arr = childrenOf.get(n.parentId) ?? [];
|
|
18
|
+
arr.push(n);
|
|
19
|
+
childrenOf.set(n.parentId, arr);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
roots.push(n);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const multipleRoots = roots.length > 1;
|
|
26
|
+
const rows = [];
|
|
27
|
+
const stack = [];
|
|
28
|
+
for (let i = roots.length - 1; i >= 0; i--) {
|
|
29
|
+
stack.push([
|
|
30
|
+
roots[i],
|
|
31
|
+
multipleRoots ? 1 : 0,
|
|
32
|
+
multipleRoots,
|
|
33
|
+
multipleRoots,
|
|
34
|
+
i === roots.length - 1,
|
|
35
|
+
[],
|
|
36
|
+
multipleRoots,
|
|
37
|
+
]);
|
|
38
|
+
}
|
|
39
|
+
while (stack.length > 0) {
|
|
40
|
+
const [node, lane, justBranched, showConnector, isLast, gutters, isVirtualRootChild] = stack.pop();
|
|
41
|
+
const displayIndent = multipleRoots ? Math.max(0, lane - 1) : lane;
|
|
42
|
+
const connectorLevel = showConnector && !isVirtualRootChild ? displayIndent - 1 : -1;
|
|
43
|
+
const cells = [];
|
|
44
|
+
for (let level = 0; level < displayIndent; level++) {
|
|
45
|
+
const gutter = gutters.find((g) => g.position === level);
|
|
46
|
+
if (level === connectorLevel)
|
|
47
|
+
cells.push(isLast ? "└─ " : "├─ ");
|
|
48
|
+
else
|
|
49
|
+
cells.push(gutter?.show ? "│ " : " ");
|
|
50
|
+
}
|
|
51
|
+
rows.push({ node, prefix: cells.join("") });
|
|
52
|
+
const children = childrenOf.get(node.id) ?? [];
|
|
53
|
+
const multipleChildren = children.length > 1;
|
|
54
|
+
const childLane = multipleChildren || (justBranched && lane > 0) ? lane + 1 : lane;
|
|
55
|
+
const connectorDisplayed = showConnector && !isVirtualRootChild;
|
|
56
|
+
const childGutters = connectorDisplayed
|
|
57
|
+
? [...gutters, { position: Math.max(0, displayIndent - 1), show: !isLast }]
|
|
58
|
+
: gutters;
|
|
59
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
60
|
+
stack.push([
|
|
61
|
+
children[i],
|
|
62
|
+
childLane,
|
|
63
|
+
multipleChildren,
|
|
64
|
+
multipleChildren,
|
|
65
|
+
i === children.length - 1,
|
|
66
|
+
childGutters,
|
|
67
|
+
false,
|
|
68
|
+
]);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return rows;
|
|
72
|
+
}
|
|
73
|
+
/** One-shot SelectList overlay; resolves the picked value or null on esc. */
|
|
74
|
+
function pickOne(tui, title, items) {
|
|
75
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
76
|
+
const list = new SelectList([...items], 10, EDITOR_THEME.selectList, {
|
|
77
|
+
minPrimaryColumnWidth: 6,
|
|
78
|
+
maxPrimaryColumnWidth: 42,
|
|
79
|
+
});
|
|
80
|
+
const overlayBox = new PickerOverlay(new Text(title, 0, 0), list);
|
|
81
|
+
const overlay = tui.showOverlay(overlayBox, { width: "70%", anchor: "center" });
|
|
82
|
+
list.onSelect = (item) => {
|
|
83
|
+
overlay.hide();
|
|
84
|
+
resolve(item.value);
|
|
85
|
+
};
|
|
86
|
+
list.onCancel = () => {
|
|
87
|
+
overlay.hide();
|
|
88
|
+
resolve(null);
|
|
89
|
+
};
|
|
90
|
+
return promise;
|
|
91
|
+
}
|
|
92
|
+
/** Notice-only overlay; resolves true when dismissed, null is not needed. */
|
|
93
|
+
function pickNotice(tui, title) {
|
|
94
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
95
|
+
const list = new SelectList([{ value: "ok", label: "ok" }], 1, EDITOR_THEME.selectList, {
|
|
96
|
+
minPrimaryColumnWidth: 2,
|
|
97
|
+
maxPrimaryColumnWidth: 4,
|
|
98
|
+
});
|
|
99
|
+
const overlayBox = new PickerOverlay(new Text(title, 0, 0), list);
|
|
100
|
+
const overlay = tui.showOverlay(overlayBox, { width: "60%", anchor: "center" });
|
|
101
|
+
list.onSelect = () => {
|
|
102
|
+
overlay.hide();
|
|
103
|
+
resolve(true);
|
|
104
|
+
};
|
|
105
|
+
list.onCancel = () => {
|
|
106
|
+
overlay.hide();
|
|
107
|
+
resolve(true);
|
|
108
|
+
};
|
|
109
|
+
return promise;
|
|
110
|
+
}
|
|
111
|
+
export function createTerminalIo(terminal = new ProcessTerminal(), workspaceRoot = process.cwd()) {
|
|
112
|
+
const tui = new TUI(terminal);
|
|
113
|
+
const headerContainer = new Container();
|
|
114
|
+
const transcript = new OmaTranscriptContainer();
|
|
115
|
+
const statusContainer = new Container();
|
|
116
|
+
const welcomeTip = WELCOME_TIPS[Math.floor(Math.random() * WELCOME_TIPS.length)] ?? "";
|
|
117
|
+
const shell = new TuiRenderShell(tui, headerContainer, transcript, statusContainer, workspaceRoot, welcomeTip);
|
|
118
|
+
const editorTheme = {
|
|
119
|
+
...EDITOR_THEME,
|
|
120
|
+
topBorder: (width) => {
|
|
121
|
+
if (!shell.statusLineText)
|
|
122
|
+
return EDITOR_THEME.borderColor("─".repeat(width));
|
|
123
|
+
// Re-fit the powerline status bar to the current width. The raw string
|
|
124
|
+
// is width-independent; addStatusBar must not pre-truncate it, or a
|
|
125
|
+
// terminal resize leaves a stale-width status line above the editor.
|
|
126
|
+
const line = truncateToWidth(shell.statusLineText, width, "");
|
|
127
|
+
return applyBackgroundToLine(line, width, (s) => `\u001b[48;5;234m${s}\u001b[0m`);
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
const editor = new Editor(tui, editorTheme);
|
|
131
|
+
shell.renderHeader();
|
|
132
|
+
let pending = null;
|
|
133
|
+
let busy = false;
|
|
134
|
+
let liveHandler = null;
|
|
135
|
+
let liveCommandHandler = null;
|
|
136
|
+
let commandHandler = null;
|
|
137
|
+
let focusHandler = null;
|
|
138
|
+
let loader = null;
|
|
139
|
+
let busySince = 0;
|
|
140
|
+
let busySeconds = 0;
|
|
141
|
+
let elapsedTimer;
|
|
142
|
+
let focused = true;
|
|
143
|
+
// Persistent prompt history (pi's HistoryStorage): loaded newest-first,
|
|
144
|
+
// fed to the editor (up/down recall, in-memory cap 100) and appended on
|
|
145
|
+
// every submit — idle prompts AND live steers both pass through
|
|
146
|
+
// editor.onSubmit, the single choke point. Drained follow-ups were already
|
|
147
|
+
// recorded at steer time.
|
|
148
|
+
let historyEntries = loadInputHistory();
|
|
149
|
+
for (const prompt of historyEntries.slice(0, 100).reverse()) {
|
|
150
|
+
editor.addToHistory(prompt);
|
|
151
|
+
}
|
|
152
|
+
// Idle Ctrl-C quits only on a SECOND press within 2s (a stray press must
|
|
153
|
+
// not kill the session); busy Ctrl-C stays single-press because aborting
|
|
154
|
+
// a run is not destructive. Ctrl-D remains an instant quit.
|
|
155
|
+
let quitArmed = false;
|
|
156
|
+
let quitHint = null;
|
|
157
|
+
let quitTimer;
|
|
158
|
+
let escArmed = false;
|
|
159
|
+
let escTimer;
|
|
160
|
+
let escHint = null;
|
|
161
|
+
function startElapsedTimer() {
|
|
162
|
+
clearInterval(elapsedTimer);
|
|
163
|
+
elapsedTimer = setInterval(() => {
|
|
164
|
+
if (!busy)
|
|
165
|
+
return;
|
|
166
|
+
busySeconds = Math.floor((Date.now() - busySince) / 1000);
|
|
167
|
+
shell.setBusySeconds(busySeconds);
|
|
168
|
+
shell.updateWorkingMessage();
|
|
169
|
+
}, 1000);
|
|
170
|
+
}
|
|
171
|
+
/** Derive a short human-readable summary for the currently streaming
|
|
172
|
+
* activity (omp's intent/working message). Prefer a live tool intent;
|
|
173
|
+
* when the model is composing a response, surface the first line of its
|
|
174
|
+
* thinking or output so the composer is never just "working…". */
|
|
175
|
+
function dismissQuitHint() {
|
|
176
|
+
quitArmed = false;
|
|
177
|
+
clearTimeout(quitTimer);
|
|
178
|
+
if (quitHint) {
|
|
179
|
+
statusContainer.removeChild(quitHint);
|
|
180
|
+
quitHint = null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function armQuit() {
|
|
184
|
+
quitArmed = true;
|
|
185
|
+
quitHint = new Text("\u001b[33m press ctrl+c again to quit\u001b[0m", 0, 0);
|
|
186
|
+
statusContainer.addChild(quitHint);
|
|
187
|
+
tui.requestRender();
|
|
188
|
+
quitTimer = setTimeout(() => {
|
|
189
|
+
dismissQuitHint();
|
|
190
|
+
tui.requestRender();
|
|
191
|
+
}, 2_000);
|
|
192
|
+
}
|
|
193
|
+
/** Idle esc-esc opens the branch tree. A single esc only arms so the esc
|
|
194
|
+
* that CLOSES an overlay can never immediately re-summon it. */
|
|
195
|
+
function dismissEscArm() {
|
|
196
|
+
escArmed = false;
|
|
197
|
+
clearTimeout(escTimer);
|
|
198
|
+
if (escHint) {
|
|
199
|
+
statusContainer.removeChild(escHint);
|
|
200
|
+
escHint = null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function armEsc() {
|
|
204
|
+
dismissEscArm();
|
|
205
|
+
escArmed = true;
|
|
206
|
+
escHint = new Text("\u001b[2m press esc again for branch tree\u001b[0m", 0, 0);
|
|
207
|
+
statusContainer.addChild(escHint);
|
|
208
|
+
tui.requestRender();
|
|
209
|
+
escTimer = setTimeout(() => {
|
|
210
|
+
dismissEscArm();
|
|
211
|
+
tui.requestRender();
|
|
212
|
+
}, 2_000);
|
|
213
|
+
}
|
|
214
|
+
function recordHistory(prompt) {
|
|
215
|
+
const next = appendInputHistory(historyEntries, prompt);
|
|
216
|
+
if (next === historyEntries)
|
|
217
|
+
return;
|
|
218
|
+
historyEntries = next;
|
|
219
|
+
saveInputHistory(historyEntries);
|
|
220
|
+
editor.addToHistory(prompt);
|
|
221
|
+
}
|
|
222
|
+
editor.onSubmit = (text) => {
|
|
223
|
+
if (busy) {
|
|
224
|
+
const trimmed = text.trim();
|
|
225
|
+
// Slash commands execute live (pi's LiveCommandController) instead of
|
|
226
|
+
// being steered into the model as literal text; anything else steers
|
|
227
|
+
// immediately — the session loop queues a rejected steer as the next
|
|
228
|
+
// Run's input, so nothing is lost either way.
|
|
229
|
+
if (!trimmed)
|
|
230
|
+
return;
|
|
231
|
+
if (trimmed.startsWith("/")) {
|
|
232
|
+
if (liveCommandHandler)
|
|
233
|
+
liveCommandHandler(trimmed);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
recordHistory(trimmed);
|
|
237
|
+
if (liveHandler)
|
|
238
|
+
liveHandler(trimmed);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
dismissEscArm();
|
|
242
|
+
if (!pending)
|
|
243
|
+
return;
|
|
244
|
+
const resolve = pending;
|
|
245
|
+
pending = null;
|
|
246
|
+
if (text === "/exit" || text === "/quit") {
|
|
247
|
+
resolve(null);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
recordHistory(text);
|
|
251
|
+
resolve(text);
|
|
252
|
+
};
|
|
253
|
+
// Esc/ctrl+t/ctrl+o are intercepted before the editor sees them. Esc
|
|
254
|
+
// aborts a live run (pi's app.interrupt); ctrl+t and ctrl+o toggle the
|
|
255
|
+
// thinking-block and tool-detail views globally.
|
|
256
|
+
// Last rendered state: resize/setHeader rebuild the transcript from it.
|
|
257
|
+
tui.addInputListener((data) => {
|
|
258
|
+
// Focus reporting (CSI 1004): ESC[I focused, ESC[O unfocused.
|
|
259
|
+
if (data === "\x1b[I") {
|
|
260
|
+
focused = true;
|
|
261
|
+
focusHandler?.(true);
|
|
262
|
+
return { consume: true };
|
|
263
|
+
}
|
|
264
|
+
if (data === "\x1b[O") {
|
|
265
|
+
focused = false;
|
|
266
|
+
focusHandler?.(false);
|
|
267
|
+
return { consume: true };
|
|
268
|
+
}
|
|
269
|
+
if (matchesKey(data, "escape")) {
|
|
270
|
+
// An overlay must own Esc: abort/forkTree must not steal the key while
|
|
271
|
+
// the user is trying to cancel the overlay, or it becomes impossible to
|
|
272
|
+
// close (e.g. branch-tree picker).
|
|
273
|
+
if (tui.hasOverlay())
|
|
274
|
+
return undefined;
|
|
275
|
+
if (busy) {
|
|
276
|
+
if (commandHandler)
|
|
277
|
+
commandHandler("abort");
|
|
278
|
+
return { consume: true };
|
|
279
|
+
}
|
|
280
|
+
if (!editor.isShowingAutocomplete() && commandHandler) {
|
|
281
|
+
// esc-esc summons; the first esc only arms (quit-arm pattern) so an
|
|
282
|
+
// extra esc while dismissing overlays never reopens the tree.
|
|
283
|
+
if (escArmed) {
|
|
284
|
+
dismissEscArm();
|
|
285
|
+
commandHandler("forkTree");
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
armEsc();
|
|
289
|
+
}
|
|
290
|
+
return { consume: true };
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (matchesKey(data, "ctrl+t")) {
|
|
294
|
+
if (commandHandler)
|
|
295
|
+
commandHandler("toggleThinking");
|
|
296
|
+
return { consume: true };
|
|
297
|
+
}
|
|
298
|
+
if (matchesKey(data, "ctrl+o")) {
|
|
299
|
+
if (commandHandler)
|
|
300
|
+
commandHandler("toggleToolDetail");
|
|
301
|
+
return { consume: true };
|
|
302
|
+
}
|
|
303
|
+
if (matchesKey(data, "ctrl+p")) {
|
|
304
|
+
if (commandHandler)
|
|
305
|
+
commandHandler("pickModel");
|
|
306
|
+
return { consume: true };
|
|
307
|
+
}
|
|
308
|
+
// ctrl+r: search the persistent prompt history (pi's history search).
|
|
309
|
+
if (matchesKey(data, "ctrl+r")) {
|
|
310
|
+
openHistorySearch();
|
|
311
|
+
return { consume: true };
|
|
312
|
+
}
|
|
313
|
+
// ctrl+c aborts the live run when busy and quits when idle.
|
|
314
|
+
if (matchesKey(data, "ctrl+c")) {
|
|
315
|
+
if (busy) {
|
|
316
|
+
if (commandHandler)
|
|
317
|
+
commandHandler("abort");
|
|
318
|
+
}
|
|
319
|
+
else if (pending) {
|
|
320
|
+
// Idle: the second press within 2s quits; the first just arms.
|
|
321
|
+
if (quitArmed) {
|
|
322
|
+
dismissQuitHint();
|
|
323
|
+
const resolve = pending;
|
|
324
|
+
pending = null;
|
|
325
|
+
resolve(null);
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
armQuit();
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
tui.requestRender();
|
|
332
|
+
return { consume: true };
|
|
333
|
+
}
|
|
334
|
+
return undefined;
|
|
335
|
+
});
|
|
336
|
+
function openHistorySearch() {
|
|
337
|
+
if (historyEntries.length === 0) {
|
|
338
|
+
const hint = new Text("\u001b[2m history is empty\u001b[0m", 0, 0);
|
|
339
|
+
statusContainer.addChild(hint);
|
|
340
|
+
tui.requestRender();
|
|
341
|
+
setTimeout(() => {
|
|
342
|
+
statusContainer.removeChild(hint);
|
|
343
|
+
tui.requestRender();
|
|
344
|
+
}, 1_500);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
348
|
+
const overlayBox = new HistorySearchOverlay(new Text(" history search — filter, enter, esc", 0, 0), historyEntries, EDITOR_THEME.selectList, (value) => {
|
|
349
|
+
overlay.hide();
|
|
350
|
+
resolve(value);
|
|
351
|
+
}, () => {
|
|
352
|
+
overlay.hide();
|
|
353
|
+
resolve(null);
|
|
354
|
+
});
|
|
355
|
+
const overlay = tui.showOverlay(overlayBox, { width: "70%", anchor: "center" });
|
|
356
|
+
void promise.then((value) => {
|
|
357
|
+
if (value !== null)
|
|
358
|
+
editor.setText(value);
|
|
359
|
+
tui.requestRender();
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
tui.setFrameProvider(createOmaFrameProvider({
|
|
363
|
+
headerContainer,
|
|
364
|
+
transcript,
|
|
365
|
+
statusContainer,
|
|
366
|
+
editor,
|
|
367
|
+
shell,
|
|
368
|
+
}));
|
|
369
|
+
tui.addChild(headerContainer);
|
|
370
|
+
tui.addChild(transcript);
|
|
371
|
+
tui.addChild(statusContainer);
|
|
372
|
+
tui.addChild(editor);
|
|
373
|
+
tui.setFocus(editor);
|
|
374
|
+
tui.start();
|
|
375
|
+
tui.terminal.write("\x1b[?1004h");
|
|
376
|
+
return {
|
|
377
|
+
render(state) {
|
|
378
|
+
shell.render(state);
|
|
379
|
+
},
|
|
380
|
+
waitForInput() {
|
|
381
|
+
return new Promise((resolve) => {
|
|
382
|
+
pending = resolve;
|
|
383
|
+
});
|
|
384
|
+
},
|
|
385
|
+
setBusy(next) {
|
|
386
|
+
busy = next;
|
|
387
|
+
// The animated status line: a braille spinner + "working (Ns)" while
|
|
388
|
+
// a run is live, removed when it settles. Loader drives its own timer
|
|
389
|
+
// and calls requestRender on every frame; the elapsed timer ticks the
|
|
390
|
+
// seconds.
|
|
391
|
+
statusContainer.clear();
|
|
392
|
+
if (next) {
|
|
393
|
+
shell.addStatusBar();
|
|
394
|
+
shell.setBusy(true, null, 0);
|
|
395
|
+
busySince = Date.now();
|
|
396
|
+
busySeconds = 0;
|
|
397
|
+
shell.setBusySeconds(0);
|
|
398
|
+
loader = new Loader(tui, (s) => `\u001b[36m${s}\u001b[0m`, (s) => `\u001b[2m${s}\u001b[0m`, "working… (esc to abort)");
|
|
399
|
+
loader.start();
|
|
400
|
+
statusContainer.addChild(loader);
|
|
401
|
+
shell.setBusy(true, loader, 0);
|
|
402
|
+
shell.updateWorkingMessage();
|
|
403
|
+
startElapsedTimer();
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
clearInterval(elapsedTimer);
|
|
407
|
+
busySeconds = 0;
|
|
408
|
+
if (loader) {
|
|
409
|
+
loader.stop();
|
|
410
|
+
loader = null;
|
|
411
|
+
}
|
|
412
|
+
shell.setBusy(false, null, 0);
|
|
413
|
+
shell.renderIdleFooter();
|
|
414
|
+
}
|
|
415
|
+
tui.requestRender();
|
|
416
|
+
},
|
|
417
|
+
onLiveInput(handler) {
|
|
418
|
+
liveHandler = handler;
|
|
419
|
+
},
|
|
420
|
+
onLiveCommand(handler) {
|
|
421
|
+
liveCommandHandler = handler;
|
|
422
|
+
},
|
|
423
|
+
onCommand(handler) {
|
|
424
|
+
commandHandler = handler;
|
|
425
|
+
},
|
|
426
|
+
onFocus(handler) {
|
|
427
|
+
focusHandler = handler;
|
|
428
|
+
},
|
|
429
|
+
setSlashCommands(commands) {
|
|
430
|
+
// Registers slash-command autocomplete; also enables @-file completion
|
|
431
|
+
// over the workspace as a side effect of the combined provider.
|
|
432
|
+
editor.setAutocompleteProvider(new CombinedAutocompleteProvider([...commands], workspaceRoot));
|
|
433
|
+
},
|
|
434
|
+
pickSession(sessions) {
|
|
435
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
436
|
+
const items = sessions.map((s) => {
|
|
437
|
+
const base = s.title ?? (s.preview || s.id.slice(0, 8));
|
|
438
|
+
const fork = s.forkOf ? ` \u2442 ${s.forkOf.slice(0, 8)}` : "";
|
|
439
|
+
const workspace = s.workspace ? ` [${s.workspace}]` : "";
|
|
440
|
+
return {
|
|
441
|
+
value: s.id,
|
|
442
|
+
label: relativeTime(s.modifiedAt),
|
|
443
|
+
description: `${base}${fork}${workspace}`,
|
|
444
|
+
};
|
|
445
|
+
});
|
|
446
|
+
const list = new SelectList(items, 10, EDITOR_THEME.selectList, {
|
|
447
|
+
minPrimaryColumnWidth: 6,
|
|
448
|
+
maxPrimaryColumnWidth: 8,
|
|
449
|
+
});
|
|
450
|
+
const overlayBox = new PickerOverlay(new Text(" resume session — select, enter, esc", 0, 0), list);
|
|
451
|
+
const overlay = tui.showOverlay(overlayBox, { width: "60%", anchor: "center" });
|
|
452
|
+
list.onSelect = (item) => {
|
|
453
|
+
overlay.hide();
|
|
454
|
+
resolve(item.value);
|
|
455
|
+
};
|
|
456
|
+
list.onCancel = () => {
|
|
457
|
+
overlay.hide();
|
|
458
|
+
resolve(null);
|
|
459
|
+
};
|
|
460
|
+
return promise;
|
|
461
|
+
},
|
|
462
|
+
pickModel(models) {
|
|
463
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
464
|
+
const items = models.map((m) => ({
|
|
465
|
+
value: m.id,
|
|
466
|
+
label: m.id,
|
|
467
|
+
description: m.description,
|
|
468
|
+
}));
|
|
469
|
+
const list = new SelectList(items, 10, EDITOR_THEME.selectList, {
|
|
470
|
+
minPrimaryColumnWidth: 12,
|
|
471
|
+
maxPrimaryColumnWidth: 32,
|
|
472
|
+
});
|
|
473
|
+
const overlayBox = new PickerOverlay(new Text(" pick model — select, enter, esc", 0, 0), list);
|
|
474
|
+
const overlay = tui.showOverlay(overlayBox, { width: "60%", anchor: "center" });
|
|
475
|
+
list.onSelect = (item) => {
|
|
476
|
+
overlay.hide();
|
|
477
|
+
resolve(item.value);
|
|
478
|
+
};
|
|
479
|
+
list.onCancel = () => {
|
|
480
|
+
overlay.hide();
|
|
481
|
+
resolve(null);
|
|
482
|
+
};
|
|
483
|
+
return promise;
|
|
484
|
+
},
|
|
485
|
+
confirmApproval(req) {
|
|
486
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
487
|
+
const list = new SelectList([
|
|
488
|
+
{ value: "allow", label: "allow", description: "run the tool" },
|
|
489
|
+
{ value: "deny", label: "deny", description: "block with an error result" },
|
|
490
|
+
], 2, EDITOR_THEME.selectList, { minPrimaryColumnWidth: 6, maxPrimaryColumnWidth: 8 });
|
|
491
|
+
const overlayBox = new PickerOverlay(new Text(` approve ${req.toolName}${req.reason ? ` — ${req.reason}` : ""} — select, enter, esc`, 0, 0), list);
|
|
492
|
+
const overlay = tui.showOverlay(overlayBox, { width: "60%", anchor: "center" });
|
|
493
|
+
list.onSelect = (item) => {
|
|
494
|
+
overlay.hide();
|
|
495
|
+
resolve(item.value === "allow" ? "allow" : "deny");
|
|
496
|
+
};
|
|
497
|
+
list.onCancel = () => {
|
|
498
|
+
overlay.hide();
|
|
499
|
+
resolve(null);
|
|
500
|
+
};
|
|
501
|
+
return promise;
|
|
502
|
+
},
|
|
503
|
+
askQuestions(input) {
|
|
504
|
+
// ponytail: TUI v1 fully supports single-select questions via the
|
|
505
|
+
// SelectList overlay; multi/text degrade to a notice overlay resolving
|
|
506
|
+
// null (tool fails closed). Upgrade path: an Input-based form overlay
|
|
507
|
+
// (packages/tui Input is Focusable) for text + checkbox rows for multi.
|
|
508
|
+
return (async () => {
|
|
509
|
+
const answers = [];
|
|
510
|
+
for (const q of input.questions) {
|
|
511
|
+
if (q.kind === "text" || q.multi) {
|
|
512
|
+
const unsupported = await pickNotice(tui, ` ${q.question} — ${q.kind === "text" ? "text" : "multi-select"} input not supported in TUI yet — esc`);
|
|
513
|
+
if (!unsupported)
|
|
514
|
+
return null;
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
const options = q.options ?? [];
|
|
518
|
+
const picked = await pickOne(tui, ` ${q.question} — select, enter, esc${q.header ? ` — ${q.header}` : ""}`, options.map((o) => ({
|
|
519
|
+
value: o.value,
|
|
520
|
+
label: o.label + (q.recommended === o.value ? " (Recommended)" : ""),
|
|
521
|
+
description: o.description,
|
|
522
|
+
})));
|
|
523
|
+
if (picked === null)
|
|
524
|
+
return null;
|
|
525
|
+
answers.push({ id: q.id, selectedValues: [picked] });
|
|
526
|
+
}
|
|
527
|
+
return { answers };
|
|
528
|
+
})();
|
|
529
|
+
},
|
|
530
|
+
pickForkPoint(points) {
|
|
531
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
532
|
+
const items = points.map((p) => ({
|
|
533
|
+
value: String(p.ordinal),
|
|
534
|
+
label: `#${p.ordinal}`,
|
|
535
|
+
description: p.text,
|
|
536
|
+
}));
|
|
537
|
+
const list = new SelectList(items, 10, EDITOR_THEME.selectList, {
|
|
538
|
+
minPrimaryColumnWidth: 4,
|
|
539
|
+
maxPrimaryColumnWidth: 6,
|
|
540
|
+
});
|
|
541
|
+
const overlayBox = new PickerOverlay(new Text(" fork from message — select, enter, esc", 0, 0), list);
|
|
542
|
+
const overlay = tui.showOverlay(overlayBox, { width: "70%", anchor: "center" });
|
|
543
|
+
list.onSelect = (item) => {
|
|
544
|
+
overlay.hide();
|
|
545
|
+
resolve(Number(item.value));
|
|
546
|
+
};
|
|
547
|
+
list.onCancel = () => {
|
|
548
|
+
overlay.hide();
|
|
549
|
+
resolve(null);
|
|
550
|
+
};
|
|
551
|
+
return promise;
|
|
552
|
+
},
|
|
553
|
+
pickBranchTree(nodes) {
|
|
554
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
555
|
+
// Git-graph layout (pi tree-selector): a node's indent only grows at
|
|
556
|
+
// branch points — single-child chains stay flat, so a linear session
|
|
557
|
+
// renders as one column instead of one-indent-per-message. Rails:
|
|
558
|
+
// "│" at ancestor fork columns, "├─/└─" for children of a fork.
|
|
559
|
+
const items = layoutBranchTree(nodes)
|
|
560
|
+
.slice(0, 200)
|
|
561
|
+
.map(({ node: n, prefix }) => {
|
|
562
|
+
const roleColor = n.role === "user" ? "\u001b[36m" : n.role === "assistant" ? "\u001b[32m" : "\u001b[2m";
|
|
563
|
+
const ordinal = n.ordinal !== undefined ? ` #${n.ordinal}` : "";
|
|
564
|
+
return {
|
|
565
|
+
value: n.id,
|
|
566
|
+
label: `${prefix}${roleColor}${n.role}${ordinal}\u001b[0m`,
|
|
567
|
+
description: n.text.replace(/\s+/g, " ").slice(0, 60),
|
|
568
|
+
};
|
|
569
|
+
});
|
|
570
|
+
const list = new SelectList(items, 12, EDITOR_THEME.selectList, {
|
|
571
|
+
minPrimaryColumnWidth: 14,
|
|
572
|
+
maxPrimaryColumnWidth: 32,
|
|
573
|
+
});
|
|
574
|
+
const overlayBox = new PickerOverlay(new Text(" fork from branch node — select, enter, esc-esc to open", 0, 0), list);
|
|
575
|
+
const overlay = tui.showOverlay(overlayBox, { width: "75%", anchor: "center" });
|
|
576
|
+
list.onSelect = (item) => {
|
|
577
|
+
overlay.hide();
|
|
578
|
+
resolve(item.value);
|
|
579
|
+
};
|
|
580
|
+
list.onCancel = () => {
|
|
581
|
+
overlay.hide();
|
|
582
|
+
resolve(null);
|
|
583
|
+
};
|
|
584
|
+
return promise;
|
|
585
|
+
},
|
|
586
|
+
editSettings(settings) {
|
|
587
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
588
|
+
const box = new SettingsOverlay(settings, () => {
|
|
589
|
+
handle.hide();
|
|
590
|
+
resolve(box.getSettings());
|
|
591
|
+
});
|
|
592
|
+
const handle = tui.showOverlay(box, { width: "70%", anchor: "center" });
|
|
593
|
+
return promise;
|
|
594
|
+
},
|
|
595
|
+
setHeader(info) {
|
|
596
|
+
shell.setHeader(info.model ?? "", info.sessionId ?? "", info.title ?? "", info.context);
|
|
597
|
+
},
|
|
598
|
+
setInputText(text) {
|
|
599
|
+
editor.setText(text);
|
|
600
|
+
tui.requestRender();
|
|
601
|
+
},
|
|
602
|
+
isFocused() {
|
|
603
|
+
return focused;
|
|
604
|
+
},
|
|
605
|
+
notify() {
|
|
606
|
+
tui.terminal.write("\x07");
|
|
607
|
+
},
|
|
608
|
+
close() {
|
|
609
|
+
clearInterval(elapsedTimer);
|
|
610
|
+
clearTimeout(quitTimer);
|
|
611
|
+
clearTimeout(escTimer);
|
|
612
|
+
dismissQuitHint();
|
|
613
|
+
dismissEscArm();
|
|
614
|
+
if (loader)
|
|
615
|
+
loader.stop();
|
|
616
|
+
tui.terminal.write("\x1b[?1004l");
|
|
617
|
+
tui.stop();
|
|
618
|
+
},
|
|
619
|
+
};
|
|
620
|
+
}
|