whale-code 6.4.0 → 6.5.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.
Files changed (187) hide show
  1. package/bin/swagmanager-mcp.js +51 -0
  2. package/dist/cli/app.js +30 -2
  3. package/dist/cli/chat/ChatApp.d.ts +4 -4
  4. package/dist/cli/chat/ChatApp.js +114 -44
  5. package/dist/cli/chat/ChatInput.d.ts +13 -6
  6. package/dist/cli/chat/ChatInput.js +433 -89
  7. package/dist/cli/chat/MemoryManager.d.ts +15 -0
  8. package/dist/cli/chat/MemoryManager.js +61 -0
  9. package/dist/cli/chat/MessageList.d.ts +8 -0
  10. package/dist/cli/chat/MessageList.js +1 -1
  11. package/dist/cli/chat/NodeManager.d.ts +30 -0
  12. package/dist/cli/chat/NodeManager.js +89 -0
  13. package/dist/cli/chat/NodeSelector.d.ts +19 -0
  14. package/dist/cli/chat/NodeSelector.js +37 -0
  15. package/dist/cli/chat/PlanApproval.d.ts +17 -0
  16. package/dist/cli/chat/PlanApproval.js +82 -0
  17. package/dist/cli/chat/SessionManager.d.ts +16 -0
  18. package/dist/cli/chat/SessionManager.js +43 -0
  19. package/dist/cli/chat/SlashMenu.d.ts +38 -0
  20. package/dist/cli/chat/SlashMenu.js +208 -0
  21. package/dist/cli/chat/StatusBar.d.ts +16 -0
  22. package/dist/cli/chat/StatusBar.js +22 -0
  23. package/dist/cli/chat/ThemeSelector.d.ts +14 -0
  24. package/dist/cli/chat/ThemeSelector.js +29 -0
  25. package/dist/cli/chat/ToolIndicator.d.ts +8 -0
  26. package/dist/cli/chat/ToolIndicator.js +33 -9
  27. package/dist/cli/chat/hooks/useAgentLoop.d.ts +2 -1
  28. package/dist/cli/chat/hooks/useAgentLoop.js +22 -17
  29. package/dist/cli/chat/hooks/useSlashCommands.d.ts +19 -0
  30. package/dist/cli/chat/hooks/useSlashCommands.js +254 -15
  31. package/dist/cli/commands/config-cmd.js +4 -25
  32. package/dist/cli/commands/db.d.ts +13 -0
  33. package/dist/cli/commands/db.js +243 -0
  34. package/dist/cli/commands/doctor.js +6 -9
  35. package/dist/cli/commands/mcp.js +1 -20
  36. package/dist/cli/services/agent-events.d.ts +22 -1
  37. package/dist/cli/services/agent-events.js +9 -0
  38. package/dist/cli/services/agent-loop.js +65 -8
  39. package/dist/cli/services/agent-worker-base.js +21 -6
  40. package/dist/cli/services/api-retry.d.ts +25 -0
  41. package/dist/cli/services/api-retry.js +91 -0
  42. package/dist/cli/services/auth-service.d.ts +1 -1
  43. package/dist/cli/services/auth-service.js +40 -19
  44. package/dist/cli/services/background-processes.js +26 -2
  45. package/dist/cli/services/config-store.d.ts +13 -1
  46. package/dist/cli/services/config-store.js +116 -13
  47. package/dist/cli/services/format-server-response.js +12 -6
  48. package/dist/cli/services/ink-resize-fix.d.ts +18 -0
  49. package/dist/cli/services/ink-resize-fix.js +66 -0
  50. package/dist/cli/services/interactive-tools.d.ts +14 -0
  51. package/dist/cli/services/interactive-tools.js +47 -2
  52. package/dist/cli/services/keybinding-manager.js +1 -1
  53. package/dist/cli/services/local-tools.js +35 -2
  54. package/dist/cli/services/server-tools.js +175 -3
  55. package/dist/cli/services/subagent.js +7 -6
  56. package/dist/cli/services/system-prompt.js +5 -3
  57. package/dist/cli/services/task-decomposer.d.ts +35 -0
  58. package/dist/cli/services/task-decomposer.js +199 -0
  59. package/dist/cli/services/team-lead.d.ts +18 -0
  60. package/dist/cli/services/team-lead.js +80 -0
  61. package/dist/cli/services/teammate.js +5 -5
  62. package/dist/cli/services/telemetry.d.ts +8 -2
  63. package/dist/cli/services/telemetry.js +116 -92
  64. package/dist/cli/services/tools/agent-tools.d.ts +1 -0
  65. package/dist/cli/services/tools/agent-tools.js +50 -4
  66. package/dist/cli/services/tools/file-ops.d.ts +2 -0
  67. package/dist/cli/services/tools/file-ops.js +85 -19
  68. package/dist/cli/services/tools/shell-exec.js +22 -12
  69. package/dist/cli/shared/Theme.d.ts +1 -2
  70. package/dist/cli/shared/Theme.js +1 -1
  71. package/dist/cli/shared/WhaleBanner.d.ts +4 -1
  72. package/dist/cli/shared/WhaleBanner.js +12 -8
  73. package/dist/cli/shared/markdown.d.ts +5 -4
  74. package/dist/cli/shared/markdown.js +376 -334
  75. package/dist/cli/shared/theme-manager.d.ts +27 -0
  76. package/dist/cli/shared/theme-manager.js +178 -0
  77. package/dist/cli/shared/theme-presets.d.ts +16 -0
  78. package/dist/cli/shared/theme-presets.js +265 -0
  79. package/dist/index.js +0 -51
  80. package/dist/node/adapters/imessage.d.ts +10 -0
  81. package/dist/node/adapters/imessage.js +45 -6
  82. package/dist/node/cli.js +459 -8
  83. package/dist/node/config.d.ts +17 -0
  84. package/dist/node/gateway-client.d.ts +55 -0
  85. package/dist/node/gateway-client.js +201 -0
  86. package/dist/node/portal/clipboard.d.ts +28 -0
  87. package/dist/node/portal/clipboard.js +183 -0
  88. package/dist/node/portal/discovery.d.ts +29 -0
  89. package/dist/node/portal/discovery.js +61 -0
  90. package/dist/node/portal/forward.d.ts +30 -0
  91. package/dist/node/portal/forward.js +90 -0
  92. package/dist/node/portal/index.d.ts +47 -0
  93. package/dist/node/portal/index.js +250 -0
  94. package/dist/node/portal/multiplexer.d.ts +48 -0
  95. package/dist/node/portal/multiplexer.js +207 -0
  96. package/dist/node/portal/permissions.d.ts +36 -0
  97. package/dist/node/portal/permissions.js +131 -0
  98. package/dist/node/portal/protocol.d.ts +140 -0
  99. package/dist/node/portal/protocol.js +193 -0
  100. package/dist/node/portal/screen.d.ts +18 -0
  101. package/dist/node/portal/screen.js +93 -0
  102. package/dist/node/portal/session.d.ts +68 -0
  103. package/dist/node/portal/session.js +127 -0
  104. package/dist/node/portal/shell.d.ts +26 -0
  105. package/dist/node/portal/shell.js +142 -0
  106. package/dist/node/portal/stream.d.ts +43 -0
  107. package/dist/node/portal/stream.js +90 -0
  108. package/dist/node/portal/transfer.d.ts +33 -0
  109. package/dist/node/portal/transfer.js +231 -0
  110. package/dist/node/portal/ui.d.ts +16 -0
  111. package/dist/node/portal/ui.js +148 -0
  112. package/dist/node/remote-desktop/compile-helper.d.ts +13 -0
  113. package/dist/node/remote-desktop/compile-helper.js +73 -0
  114. package/dist/node/remote-desktop/index.d.ts +67 -0
  115. package/dist/node/remote-desktop/index.js +220 -0
  116. package/dist/node/remote-desktop/protocol.d.ts +96 -0
  117. package/dist/node/remote-desktop/protocol.js +67 -0
  118. package/dist/node/runtime.d.ts +8 -1
  119. package/dist/node/runtime.js +117 -9
  120. package/dist/server/handlers/__test-utils__/test-db.d.ts +25 -0
  121. package/dist/server/handlers/__test-utils__/test-db.js +128 -0
  122. package/dist/server/handlers/api-keys.js +26 -2
  123. package/dist/server/handlers/browser.d.ts +0 -4
  124. package/dist/server/handlers/browser.js +0 -46
  125. package/dist/server/handlers/catalog.js +37 -14
  126. package/dist/server/handlers/clickhouse.d.ts +10 -0
  127. package/dist/server/handlers/clickhouse.js +215 -0
  128. package/dist/server/handlers/comms.d.ts +308 -4
  129. package/dist/server/handlers/comms.js +444 -11
  130. package/dist/server/handlers/creations.js +1 -1
  131. package/dist/server/handlers/crm.d.ts +54 -8
  132. package/dist/server/handlers/crm.js +353 -68
  133. package/dist/server/handlers/embeddings.js +3 -3
  134. package/dist/server/handlers/enrichment.js +39 -55
  135. package/dist/server/handlers/inventory.js +1 -1
  136. package/dist/server/handlers/kali.d.ts +9 -1
  137. package/dist/server/handlers/kali.js +50 -1
  138. package/dist/server/handlers/media.d.ts +8 -0
  139. package/dist/server/handlers/media.js +902 -0
  140. package/dist/server/handlers/meta-ads.js +6 -3
  141. package/dist/server/handlers/nodes.d.ts +2 -0
  142. package/dist/server/handlers/nodes.js +331 -40
  143. package/dist/server/handlers/operations.d.ts +4 -6
  144. package/dist/server/handlers/operations.js +99 -38
  145. package/dist/server/handlers/platform.js +224 -107
  146. package/dist/server/handlers/remove-bg.d.ts +6 -0
  147. package/dist/server/handlers/remove-bg.js +96 -0
  148. package/dist/server/handlers/storefront.d.ts +6 -0
  149. package/dist/server/handlers/storefront.js +477 -0
  150. package/dist/server/handlers/supply-chain.js +21 -3
  151. package/dist/server/handlers/workflow-steps.js +87 -31
  152. package/dist/server/handlers/workflows.js +4 -1
  153. package/dist/server/index.js +334 -88
  154. package/dist/server/lib/clickhouse-buffer.d.ts +48 -0
  155. package/dist/server/lib/clickhouse-buffer.js +175 -0
  156. package/dist/server/lib/clickhouse-client.d.ts +112 -0
  157. package/dist/server/lib/clickhouse-client.js +141 -0
  158. package/dist/server/lib/coa-renderer.d.ts +91 -0
  159. package/dist/server/lib/coa-renderer.js +411 -0
  160. package/dist/server/lib/compaction-service.js +46 -1
  161. package/dist/server/lib/pdf-renderer.d.ts +143 -0
  162. package/dist/server/lib/pdf-renderer.js +867 -0
  163. package/dist/server/lib/react-pdf-layout.d.ts +40 -0
  164. package/dist/server/lib/react-pdf-layout.js +437 -0
  165. package/dist/server/lib/server-agent-loop.d.ts +2 -0
  166. package/dist/server/lib/server-agent-loop.js +36 -17
  167. package/dist/server/lib/server-subagent.d.ts +3 -0
  168. package/dist/server/lib/server-subagent.js +9 -6
  169. package/dist/server/lib/supabase-client.js +51 -3
  170. package/dist/server/lib/template-resolver.js +14 -4
  171. package/dist/server/lib/utils.js +15 -0
  172. package/dist/server/local-agent-gateway.d.ts +44 -0
  173. package/dist/server/local-agent-gateway.js +389 -49
  174. package/dist/server/providers/anthropic.js +12 -2
  175. package/dist/server/providers/gemini.js +17 -2
  176. package/dist/server/proxy-handlers.js +151 -0
  177. package/dist/server/tool-router.d.ts +2 -2
  178. package/dist/server/tool-router.js +25 -35
  179. package/dist/shared/agent-core.d.ts +25 -2
  180. package/dist/shared/agent-core.js +66 -5
  181. package/dist/shared/api-client.js +54 -3
  182. package/dist/shared/sse-parser.d.ts +1 -1
  183. package/dist/shared/sse-parser.js +5 -2
  184. package/dist/shared/tool-dispatch.js +15 -1
  185. package/package.json +16 -10
  186. package/dist/server/handlers/__test-utils__/mock-supabase.d.ts +0 -11
  187. package/dist/server/handlers/__test-utils__/mock-supabase.js +0 -393
@@ -0,0 +1,208 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * SlashMenu — Two-level categorized slash command menu
4
+ *
5
+ * Pure rendering component: zero internal state, zero keyboard handling.
6
+ * ChatInput owns all state and passes props.
7
+ *
8
+ * Level 1: Category list (6 rows)
9
+ * Level 2: Commands within a category (with breadcrumb header)
10
+ * Filter: Flat filtered list (existing behavior)
11
+ */
12
+ import { Box, Text } from "ink";
13
+ import { colors } from "../shared/Theme.js";
14
+ // ── Command Definitions (6 categories) ──
15
+ export const CATEGORIES = [
16
+ {
17
+ label: "Session",
18
+ color: colors.brand,
19
+ commands: [
20
+ { name: "/clear", description: "Clear conversation" },
21
+ { name: "/save", description: "Save session to disk" },
22
+ { name: "/sessions", description: "List saved sessions" },
23
+ { name: "/resume", description: "Resume a saved session" },
24
+ { name: "/compact", description: "Compress conversation context" },
25
+ { name: "/rewind", description: "Rewind conversation to earlier point" },
26
+ ],
27
+ },
28
+ {
29
+ label: "Config",
30
+ color: colors.warning,
31
+ commands: [
32
+ { name: "/model", description: "Switch model (Anthropic/Bedrock/Gemini)" },
33
+ { name: "/mode", description: "Permission mode (default/plan/yolo)" },
34
+ { name: "/thinking", description: "Toggle extended thinking" },
35
+ { name: "/store", description: "Switch active store" },
36
+ { name: "/theme", description: "Switch color theme" },
37
+ { name: "/init", description: "Generate project config (.whale/CLAUDE.md)" },
38
+ { name: "/update", description: "Check for updates & install" },
39
+ ],
40
+ },
41
+ {
42
+ label: "Info",
43
+ color: colors.info,
44
+ commands: [
45
+ { name: "/help", description: "Show available commands" },
46
+ { name: "/status", description: "Show session info" },
47
+ { name: "/tools", description: "List all tools" },
48
+ { name: "/mcp", description: "Server connection status" },
49
+ { name: "/agents", description: "List available agent types" },
50
+ ],
51
+ },
52
+ {
53
+ label: "Memory",
54
+ color: colors.purple,
55
+ commands: [
56
+ { name: "/remember", description: "Remember a fact across sessions" },
57
+ { name: "/forget", description: "Forget a remembered fact" },
58
+ { name: "/memory", description: "List all remembered facts" },
59
+ ],
60
+ },
61
+ {
62
+ label: "Nodes",
63
+ color: colors.success,
64
+ commands: [
65
+ { name: "/nodes", description: "Connected whale-nodes" },
66
+ ],
67
+ },
68
+ {
69
+ label: "Account",
70
+ color: colors.tertiary,
71
+ commands: [
72
+ { name: "/logout", description: "Log out and exit" },
73
+ { name: "/exit", description: "Exit" },
74
+ ],
75
+ },
76
+ ];
77
+ /** Flat array of all commands (for external consumers like /help) */
78
+ export const SLASH_COMMANDS = CATEGORIES.flatMap(c => c.commands);
79
+ // ── Category helpers ──
80
+ export function getCategoryCount() {
81
+ return CATEGORIES.length;
82
+ }
83
+ export function getCategoryCommandCount(catIndex) {
84
+ return CATEGORIES[catIndex]?.commands.length ?? 0;
85
+ }
86
+ export function getCategoryLabel(catIndex) {
87
+ return CATEGORIES[catIndex]?.label ?? "";
88
+ }
89
+ export function getCategoryColor(catIndex) {
90
+ return CATEGORIES[catIndex]?.color ?? colors.tertiary;
91
+ }
92
+ export function getCategoryCommand(catIndex, cmdIndex) {
93
+ return CATEGORIES[catIndex]?.commands[cmdIndex] ?? null;
94
+ }
95
+ // ── Windowed rendering constants ──
96
+ const MAX_VISIBLE = 12;
97
+ const SCROLL_PADDING = 2;
98
+ // ── Flat list builder (memoized on filter) ──
99
+ function buildFlatList(filter) {
100
+ const q = filter.toLowerCase();
101
+ const items = [];
102
+ for (const cat of CATEGORIES) {
103
+ const matched = q
104
+ ? cat.commands.filter(c => c.name.includes(q) || c.description.toLowerCase().includes(q))
105
+ : cat.commands;
106
+ if (matched.length === 0)
107
+ continue;
108
+ items.push({ kind: "header", label: cat.label, color: cat.color });
109
+ for (const cmd of matched) {
110
+ items.push({ kind: "command", command: cmd, color: cat.color });
111
+ }
112
+ }
113
+ return items;
114
+ }
115
+ /** Count of selectable (command) items in a filtered list */
116
+ export function getFilteredSelectableCount(filter) {
117
+ const q = filter.toLowerCase();
118
+ let count = 0;
119
+ for (const cat of CATEGORIES) {
120
+ const matched = q
121
+ ? cat.commands.filter(c => c.name.includes(q) || c.description.toLowerCase().includes(q))
122
+ : cat.commands;
123
+ count += matched.length;
124
+ }
125
+ return count;
126
+ }
127
+ /** Get the command at a given selectable index (skipping headers) */
128
+ export function getCommandAtIndex(filter, selectableIndex) {
129
+ const flat = buildFlatList(filter);
130
+ let si = 0;
131
+ for (const item of flat) {
132
+ if (item.kind === "command") {
133
+ if (si === selectableIndex)
134
+ return item.command;
135
+ si++;
136
+ }
137
+ }
138
+ return null;
139
+ }
140
+ export function SlashMenu({ filter, selectedIndex, selectedCategory }) {
141
+ // ── Level 1: Category list ──
142
+ if (selectedCategory === null && filter === "") {
143
+ return (_jsx(Box, { flexDirection: "column", children: CATEGORIES.map((cat, i) => {
144
+ const isSelected = i === selectedIndex;
145
+ const count = cat.commands.length;
146
+ const countLabel = count === 1 ? "1 command" : `${count} commands`;
147
+ return (_jsxs(Box, { children: [_jsx(Text, { color: isSelected ? cat.color : colors.quaternary, children: isSelected ? "\u203A " : " " }), _jsx(Text, { color: isSelected ? cat.color : colors.secondary, bold: isSelected, children: cat.label }), _jsxs(Text, { color: colors.tertiary, children: [" ", countLabel] })] }, cat.label));
148
+ }) }));
149
+ }
150
+ // ── Level 2: Commands within a category ──
151
+ if (selectedCategory !== null && filter === "") {
152
+ const cat = CATEGORIES[selectedCategory];
153
+ if (!cat)
154
+ return null;
155
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { children: _jsx(Text, { color: cat.color, children: ` \u2039 ${cat.label}` }) }), cat.commands.map((cmd, i) => {
156
+ const isSelected = i === selectedIndex;
157
+ return (_jsxs(Box, { children: [_jsx(Text, { color: isSelected ? cat.color : colors.quaternary, children: isSelected ? "\u203A " : " " }), _jsx(Text, { color: isSelected ? cat.color : colors.secondary, bold: isSelected, children: cmd.name }), _jsxs(Text, { color: colors.tertiary, children: [" ", cmd.description] })] }, cmd.name));
158
+ })] }));
159
+ }
160
+ // ── Filter mode: flat filtered list (existing behavior) ──
161
+ const flatList = buildFlatList(filter);
162
+ // Map selectedIndex (selectable-only) → flat-list index
163
+ let selectedFlatIndex = -1;
164
+ {
165
+ let si = 0;
166
+ for (let i = 0; i < flatList.length; i++) {
167
+ if (flatList[i].kind === "command") {
168
+ if (si === selectedIndex) {
169
+ selectedFlatIndex = i;
170
+ break;
171
+ }
172
+ si++;
173
+ }
174
+ }
175
+ }
176
+ // Calculate visible window
177
+ let windowStart = 0;
178
+ let windowEnd = flatList.length;
179
+ if (flatList.length > MAX_VISIBLE) {
180
+ windowStart = Math.max(0, selectedFlatIndex - SCROLL_PADDING);
181
+ windowEnd = windowStart + MAX_VISIBLE;
182
+ if (windowEnd > flatList.length) {
183
+ windowEnd = flatList.length;
184
+ windowStart = Math.max(0, windowEnd - MAX_VISIBLE);
185
+ }
186
+ }
187
+ const visibleItems = flatList.slice(windowStart, windowEnd);
188
+ const hasOverflowAbove = windowStart > 0;
189
+ const hasOverflowBelow = windowEnd < flatList.length;
190
+ // No matching commands
191
+ if (flatList.length === 0) {
192
+ return _jsx(Text, { color: colors.tertiary, children: " no matching commands" });
193
+ }
194
+ // Track selectable index as we render
195
+ let selectableCounter = 0;
196
+ for (let i = 0; i < windowStart; i++) {
197
+ if (flatList[i].kind === "command")
198
+ selectableCounter++;
199
+ }
200
+ return (_jsxs(Box, { flexDirection: "column", children: [hasOverflowAbove && (_jsxs(Text, { color: colors.quaternary, children: [" ", " \u2191 more"] })), visibleItems.map((item) => {
201
+ if (item.kind === "header") {
202
+ return (_jsx(Box, { children: _jsx(Text, { color: item.color, dimColor: true, children: ` ${item.label}` }) }, `h-${item.label}`));
203
+ }
204
+ const thisSelectableIdx = selectableCounter++;
205
+ const isSelected = thisSelectableIdx === selectedIndex;
206
+ return (_jsxs(Box, { children: [_jsx(Text, { color: isSelected ? item.color : colors.quaternary, children: isSelected ? "\u203A " : " " }), _jsx(Text, { color: isSelected ? item.color : colors.secondary, bold: isSelected, children: item.command.name }), _jsxs(Text, { color: colors.tertiary, children: [" ", item.command.description] })] }, item.command.name));
207
+ }), hasOverflowBelow && (_jsxs(Text, { color: colors.quaternary, children: [" ", " \u2193 more"] }))] }));
208
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * StatusBar — persistent status line below the chat input
3
+ *
4
+ * Shows: permission mode · model · thinking on/off · tool count · streaming elapsed
5
+ * Single line, dimmed colors, dot-separated items.
6
+ */
7
+ interface StatusBarProps {
8
+ permissionMode: string;
9
+ model: string;
10
+ thinkingEnabled: boolean;
11
+ serverTools: number;
12
+ isStreaming: boolean;
13
+ toolsExpanded?: boolean;
14
+ }
15
+ export declare function StatusBar({ permissionMode, model, thinkingEnabled, serverTools, isStreaming, toolsExpanded, }: StatusBarProps): import("react/jsx-runtime").JSX.Element;
16
+ export {};
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * StatusBar — persistent status line below the chat input
4
+ *
5
+ * Shows: permission mode · model · thinking on/off · tool count · streaming elapsed
6
+ * Single line, dimmed colors, dot-separated items.
7
+ */
8
+ import { Box, Text } from "ink";
9
+ import { colors, symbols } from "../shared/Theme.js";
10
+ // ── Helpers ──
11
+ function modeColor(mode) {
12
+ switch (mode) {
13
+ case "yolo": return colors.error;
14
+ case "plan": return colors.info;
15
+ default: return colors.quaternary;
16
+ }
17
+ }
18
+ // ── Component ──
19
+ export function StatusBar({ permissionMode, model, thinkingEnabled, serverTools, isStreaming, toolsExpanded, }) {
20
+ const sep = ` ${symbols.dot} `;
21
+ return (_jsxs(Box, { marginLeft: 2, overflow: "hidden", children: [_jsx(Text, { color: modeColor(permissionMode), wrap: "truncate", children: permissionMode }), _jsxs(Text, { color: colors.quaternary, wrap: "truncate", children: [sep, model, sep, "thinking ", thinkingEnabled ? "on" : "off", serverTools > 0 ? `${sep}${serverTools} tools` : ""] }), toolsExpanded && !isStreaming && (_jsxs(Text, { color: colors.info, wrap: "truncate", children: [sep, "expanded ^O close"] }))] }));
22
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * ThemeSelector — inline theme picker for chat
3
+ *
4
+ * Renders a SelectInput with available color themes.
5
+ * Shows colored preview dots (brand/success/error/purple) for each theme.
6
+ * Current theme marked with dot. Esc to cancel.
7
+ */
8
+ import { type ThemePreset } from "../shared/theme-manager.js";
9
+ interface ThemeSelectorProps {
10
+ onSelect: (preset: ThemePreset) => void;
11
+ onCancel: () => void;
12
+ }
13
+ export declare function ThemeSelector({ onSelect, onCancel }: ThemeSelectorProps): import("react/jsx-runtime").JSX.Element;
14
+ export {};
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text, useInput } from "ink";
3
+ import SelectInput from "ink-select-input";
4
+ import { colors, symbols } from "../shared/Theme.js";
5
+ import { getThemePresets, getCurrentThemeId } from "../shared/theme-manager.js";
6
+ export function ThemeSelector({ onSelect, onCancel }) {
7
+ useInput((_input, key) => {
8
+ if (key.escape)
9
+ onCancel();
10
+ });
11
+ const presets = getThemePresets();
12
+ const currentId = getCurrentThemeId();
13
+ const items = presets.map((p) => ({
14
+ label: p.label,
15
+ value: p.id,
16
+ }));
17
+ const handleSelect = (item) => {
18
+ const preset = presets.find((p) => p.id === item.value);
19
+ if (preset)
20
+ onSelect(preset);
21
+ };
22
+ const initialIdx = Math.max(0, items.findIndex((i) => i.value === currentId));
23
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: colors.secondary, children: " Select theme:" }), _jsx(Box, { height: 1 }), _jsx(SelectInput, { items: items, initialIndex: initialIdx, onSelect: handleSelect, indicatorComponent: ({ isSelected }) => (_jsxs(Text, { color: isSelected ? colors.brand : colors.quaternary, children: [isSelected ? symbols.arrowRight : " ", " "] })), itemComponent: ({ isSelected, label, value }) => {
24
+ const preset = presets.find((p) => p.id === value);
25
+ const isCurrent = value === currentId;
26
+ const chrome = preset?.chrome;
27
+ return (_jsxs(Box, { children: [_jsx(Text, { color: isSelected ? colors.brand : colors.text, bold: isSelected, children: label }), chrome && (_jsxs(Text, { children: [" ", _jsx(Text, { color: chrome.brand, children: "\u25CF" }), _jsx(Text, { color: chrome.success, children: "\u25CF" }), _jsx(Text, { color: chrome.error, children: "\u25CF" }), _jsx(Text, { color: chrome.purple, children: "\u25CF" })] })), isCurrent && _jsxs(Text, { color: colors.success, children: [" ", symbols.dot, " current"] })] }));
28
+ } }), _jsx(Text, { color: colors.quaternary, children: " esc to cancel" })] }));
29
+ }
@@ -17,6 +17,14 @@ interface ToolIndicatorProps {
17
17
  expanded?: boolean;
18
18
  /** When > 1, shows a "× N" badge and hides result (collapsed duplicate group) */
19
19
  count?: number;
20
+ progress?: {
21
+ phase?: string;
22
+ elapsed_s: number;
23
+ stdout_lines: number;
24
+ stderr_lines: number;
25
+ stdout_bytes: number;
26
+ last_line?: string;
27
+ };
20
28
  }
21
29
  /** Shorten an absolute path for display: strip cwd, collapse home, truncate */
22
30
  export declare function shortenPath(fullPath: string, maxLen?: number): string;
@@ -19,7 +19,10 @@ import os from "os";
19
19
  // CONSTANTS
20
20
  // ============================================================================
21
21
  const AUTO_EXPAND_THRESHOLD = 12;
22
+ const SERVER_AUTO_EXPAND_THRESHOLD = 30; // Server tools (tables) get more room before collapsing
22
23
  const PREVIEW_LINES = 6;
24
+ const SERVER_PREVIEW_LINES = 20; // Tables need ~header + separator + 17 data rows
25
+ const MINI_PREVIEW_LINES = 3;
23
26
  // ============================================================================
24
27
  // HELPERS
25
28
  // ============================================================================
@@ -239,6 +242,16 @@ function formatSearchResult(result) {
239
242
  return line;
240
243
  }).join("\n");
241
244
  }
245
+ function formatElapsed(s) {
246
+ const m = Math.floor(s / 60);
247
+ const sec = Math.floor(s % 60);
248
+ return m > 0 ? `${m}m${sec}s` : `${sec}s`;
249
+ }
250
+ function progressBar(elapsed, maxSeconds = 300) {
251
+ const pct = Math.min(elapsed / maxSeconds, 1);
252
+ const filled = Math.round(pct * 20);
253
+ return "\u2588".repeat(filled) + "\u2591".repeat(20 - filled);
254
+ }
242
255
  const PLAIN_TEXT_TOOLS = new Set(["enter_plan_mode", "exit_plan_mode", "ask_user_question", "skill"]);
243
256
  function detectLang(toolName, input) {
244
257
  if (toolName === "edit_file" || toolName === "multi_edit")
@@ -281,7 +294,7 @@ function wrapInFence(content, lang, subtitle) {
281
294
  // ============================================================================
282
295
  // COMPONENT
283
296
  // ============================================================================
284
- export const ToolIndicator = React.memo(function ToolIndicator({ id: _id, name, status, result, input, durationMs, expanded = false, count }) {
297
+ export const ToolIndicator = React.memo(function ToolIndicator({ id: _id, name, status, result, input, durationMs, expanded = false, count, progress }) {
285
298
  const context = useMemo(() => formatContext(name, input), [name, input]);
286
299
  const lineCount = useMemo(() => result ? result.split("\n").length : 0, [result]);
287
300
  // Elapsed time counter for running tools
@@ -354,8 +367,7 @@ export const ToolIndicator = React.memo(function ToolIndicator({ id: _id, name,
354
367
  if (category === "search") {
355
368
  const lines = result.split("\n").filter(l => l.trim());
356
369
  const files = new Set(lines.map(l => l.split(":")[0]).filter(f => f.includes("/") || f.includes(".")));
357
- if (lines.length > 0)
358
- return { type: "search", matches: lines.length, files: files.size };
370
+ return { type: "search", matches: lines.length, files: files.size };
359
371
  }
360
372
  if (category === "command") {
361
373
  const lines = result.split("\n").filter(l => l.trim());
@@ -389,6 +401,10 @@ export const ToolIndicator = React.memo(function ToolIndicator({ id: _id, name,
389
401
  }, [status, result]);
390
402
  // ── RUNNING ──
391
403
  if (status === "running") {
404
+ // Kali with structured progress — richer display
405
+ if (name === "kali" && progress) {
406
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: catStyle.color, children: _jsx(Spinner, { type: "dots" }) }), _jsxs(Text, { color: catStyle.color, children: [" ", catStyle.icon] }), _jsxs(Text, { color: catStyle.color, bold: true, children: [" ", getDisplayName(name)] }), context ? _jsxs(Text, { color: colors.dim, children: [" ", context] }) : null, progress.phase && _jsxs(Text, { color: colors.info, children: [" ", progress.phase] }), _jsxs(Text, { color: progress.elapsed_s >= 60 ? colors.warning : colors.dim, children: [" ", formatElapsed(progress.elapsed_s)] })] }), _jsxs(Box, { marginLeft: 4, children: [_jsx(Text, { color: colors.tertiary, children: progressBar(progress.elapsed_s) }), _jsxs(Text, { color: colors.dim, children: [" ", progress.stdout_lines, " lines"] }), progress.stderr_lines > 0 && _jsxs(Text, { color: colors.warning, children: [" ", progress.stderr_lines, " err"] })] }), progress.last_line && (_jsx(Box, { marginLeft: 4, children: _jsx(Text, { color: colors.tertiary, wrap: "truncate", children: progress.last_line }) }))] }));
407
+ }
392
408
  return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: catStyle.color, children: _jsx(Spinner, { type: "dots" }) }), _jsxs(Text, { color: catStyle.color, children: [" ", catStyle.icon] }), _jsxs(Text, { color: catStyle.color, bold: true, children: [" ", getDisplayName(name)] }), context ? _jsxs(Text, { color: colors.dim, children: [" ", context] }) : null, elapsed >= 2 && _jsxs(Text, { color: elapsed >= 30 ? colors.warning : colors.dim, children: [" ", formatDuration(elapsed * 1000)] })] }), liveLines.length > 0 && (_jsx(Box, { flexDirection: "column", marginLeft: 4, children: liveLines.map((line, i) => (_jsx(Text, { color: colors.tertiary, wrap: "truncate", children: line }, i))) }))] }));
393
409
  }
394
410
  // ── ERROR ──
@@ -397,10 +413,13 @@ export const ToolIndicator = React.memo(function ToolIndicator({ id: _id, name,
397
413
  }
398
414
  // ── SUCCESS ──
399
415
  const hasResult = !!(result && result.trim());
400
- const isShort = lineCount <= AUTO_EXPAND_THRESHOLD;
416
+ const isServer = category === "server" || category === "agent";
417
+ const autoExpandLimit = isServer ? SERVER_AUTO_EXPAND_THRESHOLD : AUTO_EXPAND_THRESHOLD;
418
+ const isShort = lineCount <= autoExpandLimit;
401
419
  // Read/write results stay collapsed (just header line) — Claude Code parity.
402
420
  // Edit/search/command results auto-expand if short enough.
403
421
  // Writes with diffs auto-expand like edits.
422
+ // Server/agent results get a higher threshold — tables need more room.
404
423
  const hasDiff = lang === "diff";
405
424
  const collapseByDefault = category === "read" || (category === "write" && !hasDiff) || category === "directory";
406
425
  // Interactive tools (plan mode) always expand to show their content
@@ -419,11 +438,15 @@ export const ToolIndicator = React.memo(function ToolIndicator({ id: _id, name,
419
438
  }, [isGrouped, result]);
420
439
  return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: catStyle.color, children: [" ", catStyle.icon] }), _jsxs(Text, { color: catStyle.color, bold: true, children: [" ", getDisplayName(name)] }), context ? _jsxs(Text, { color: colors.dim, children: [" ", context] }) : null, durationMs !== undefined && (durationMs > 3000
421
440
  ? _jsxs(Text, { color: colors.warning, children: [" ", formatDuration(durationMs)] })
422
- : _jsxs(Text, { color: colors.dim, children: [" ", formatDuration(durationMs)] })), summary?.type === "edit" && category === "edit" && (_jsxs(_Fragment, { children: [_jsxs(Text, { color: colors.success, children: [" +", summary.added] }), _jsxs(Text, { color: colors.error, children: [" -", summary.removed] })] })), summary?.type === "search" && (_jsxs(Text, { color: colors.dim, children: [" ", summary.matches, " match", summary.matches !== 1 ? "es" : "", summary.files > 0 ? ` in ${summary.files} file${summary.files !== 1 ? "s" : ""}` : ""] })), summary && "label" in summary && summary.type !== "server" && (_jsxs(Text, { color: colors.dim, children: [" ", summary.label] })), summary?.type === "server" && (_jsxs(Text, { color: colors.info, children: [" ", summary.label] })), !summary && hasResult && !showFull && _jsxs(Text, { color: colors.dim, children: [" ", lineCount, " lines"] }), (count ?? 0) > 1 && _jsxs(Text, { color: colors.dim, dimColor: true, children: [" \u00D7 ", count] })] }), isGrouped && groupSummaryLine && (_jsx(Box, { marginLeft: 4, children: _jsx(Text, { color: colors.tertiary, wrap: "truncate", children: groupSummaryLine }) })), category === "write" && hasDiff && summary?.type === "edit" && (_jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { color: colors.tertiary, children: "\u2514 Added " }), _jsx(Text, { color: colors.success, children: summary.added }), _jsx(Text, { color: colors.tertiary, children: " lines, removed " }), _jsx(Text, { color: colors.error, children: summary.removed }), _jsx(Text, { color: colors.tertiary, children: " lines" })] })), showFull && (_jsx(Box, { marginLeft: 2, flexDirection: "column", children: _jsx(MarkdownText, { text: category === "agent"
441
+ : _jsxs(Text, { color: colors.dim, children: [" ", formatDuration(durationMs)] })), summary?.type === "edit" && category === "edit" && (_jsxs(_Fragment, { children: [_jsxs(Text, { color: colors.success, children: [" +", summary.added] }), _jsxs(Text, { color: colors.error, children: [" -", summary.removed] })] })), summary?.type === "search" && (_jsxs(Text, { color: colors.dim, children: [" ", summary.matches, " match", summary.matches !== 1 ? "es" : "", summary.files > 0 ? ` in ${summary.files} file${summary.files !== 1 ? "s" : ""}` : ""] })), summary && "label" in summary && summary.type !== "server" && (_jsxs(Text, { color: colors.dim, children: [" ", summary.label] })), summary?.type === "server" && (_jsxs(Text, { color: colors.info, children: [" ", summary.label] })), !summary && hasResult && !showFull && _jsxs(Text, { color: colors.dim, children: [" ", lineCount, " lines"] }), !hasResult && status === "success" && _jsx(Text, { color: colors.quaternary, children: " (no output)" }), hasResult && !showFull && collapseByDefault && !isGrouped && _jsx(Text, { color: colors.quaternary, children: " ^O" }), (count ?? 0) > 1 && _jsxs(Text, { color: colors.dim, dimColor: true, children: [" \u00D7 ", count] })] }), isGrouped && groupSummaryLine && (_jsx(Box, { marginLeft: 4, children: _jsx(Text, { color: colors.tertiary, wrap: "truncate", children: groupSummaryLine }) })), category === "write" && hasDiff && summary?.type === "edit" && (_jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { color: colors.tertiary, children: "\u2514 Added " }), _jsx(Text, { color: colors.success, children: summary.added }), _jsx(Text, { color: colors.tertiary, children: " lines, removed " }), _jsx(Text, { color: colors.error, children: summary.removed }), _jsx(Text, { color: colors.tertiary, children: " lines" })] })), showFull && (_jsx(Box, { marginLeft: 2, flexDirection: "column", children: _jsx(MarkdownText, { text: category === "agent"
423
442
  ? result
424
- : wrapInFence(category === "search" ? formatSearchResult(result) : result, lang, filePath) }) })), hasResult && !showFull && !collapseByDefault && !isGrouped && (_jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsx(MarkdownText, { text: category === "agent"
425
- ? result.split("\n").slice(0, PREVIEW_LINES).join("\n")
426
- : wrapInFence((category === "search" ? formatSearchResult(result) : result).split("\n").slice(0, PREVIEW_LINES).join("\n"), lang, filePath) }), _jsxs(Text, { color: colors.quaternary, children: [" \u2514 +", lineCount - PREVIEW_LINES, " lines "] }), _jsx(Text, { color: colors.tertiary, dimColor: true, children: "^E" })] }))] }));
443
+ : wrapInFence(category === "search" ? formatSearchResult(result) : result, lang, filePath) }) })), hasResult && !showFull && collapseByDefault && !isGrouped && (_jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsx(MarkdownText, { text: wrapInFence(result.split("\n").slice(0, MINI_PREVIEW_LINES).join("\n"), lang, filePath) }), lineCount > MINI_PREVIEW_LINES && _jsxs(Text, { color: colors.quaternary, children: [" \u2514 +", lineCount - MINI_PREVIEW_LINES, " lines ", _jsx(Text, { color: colors.tertiary, children: "^O expand" })] })] })), hasResult && !showFull && !collapseByDefault && !isGrouped && (() => {
444
+ const previewN = isServer ? SERVER_PREVIEW_LINES : PREVIEW_LINES;
445
+ const remaining = lineCount - previewN;
446
+ return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsx(MarkdownText, { text: category === "agent"
447
+ ? result.split("\n").slice(0, previewN).join("\n")
448
+ : wrapInFence((category === "search" ? formatSearchResult(result) : result).split("\n").slice(0, previewN).join("\n"), lang, filePath) }), remaining > 0 && _jsxs(Text, { color: colors.quaternary, children: [" \u2514 +", remaining, " lines ", _jsx(Text, { color: colors.tertiary, children: "^O expand" })] })] }));
449
+ })()] }));
427
450
  }, (prev, next) => {
428
451
  // Custom comparator: skip deep-comparing input object reference
429
452
  return prev.id === next.id
@@ -432,5 +455,6 @@ export const ToolIndicator = React.memo(function ToolIndicator({ id: _id, name,
432
455
  && prev.result === next.result
433
456
  && prev.durationMs === next.durationMs
434
457
  && prev.name === next.name
435
- && prev.count === next.count;
458
+ && prev.count === next.count
459
+ && prev.progress === next.progress;
436
460
  });
@@ -15,7 +15,8 @@ export interface AgentLoopDeps {
15
15
  abortRef: React.MutableRefObject<AbortController | null>;
16
16
  accTextRef: React.MutableRefObject<string>;
17
17
  lastContentUpdateRef: React.MutableRefObject<number>;
18
- thinkingVerbRef: React.MutableRefObject<string>;
18
+ thinkingChunksRef: React.MutableRefObject<number>;
19
+ thinkingDoneRef: React.MutableRefObject<boolean>;
19
20
  rewindManagerRef: React.MutableRefObject<RewindManager>;
20
21
  turnIndexRef: React.MutableRefObject<number>;
21
22
  setMessages: React.Dispatch<React.SetStateAction<ChatMessage[]>>;
@@ -7,15 +7,8 @@ import { useCallback, useRef } from "react";
7
7
  import { runAgentLoop } from "../../services/agent-loop.js";
8
8
  import { AgentEventEmitter } from "../../services/agent-events.js";
9
9
  import { listBackups } from "../../services/file-history.js";
10
- function randomVerb() {
11
- const THINKING_VERBS = [
12
- "thinking\u2026", "reasoning\u2026", "considering\u2026", "analyzing\u2026", "evaluating\u2026",
13
- "pondering\u2026", "processing\u2026", "reflecting\u2026", "examining\u2026", "working\u2026",
14
- ];
15
- return THINKING_VERBS[Math.floor(Math.random() * THINKING_VERBS.length)];
16
- }
17
10
  export function useAgentLoop(deps) {
18
- const { isStreaming, thinkingEnabled, conversationRef, abortRef, accTextRef, lastContentUpdateRef, thinkingVerbRef, rewindManagerRef, turnIndexRef, setMessages, setStreamingText, setIsStreaming, setActiveTools, setSubagentActivity, setCompletedSubagents, setTeamState, } = deps;
11
+ const { isStreaming, thinkingEnabled, conversationRef, abortRef, accTextRef, lastContentUpdateRef, thinkingChunksRef, thinkingDoneRef, rewindManagerRef, turnIndexRef, setMessages, setStreamingText, setIsStreaming, setActiveTools, setSubagentActivity, setCompletedSubagents, setTeamState, } = deps;
19
12
  // Single ref for the unified flush timer — survives across handleSend calls for cleanup
20
13
  const flushTimerRef = useRef(null);
21
14
  const handleSend = useCallback(async (userMessage, images) => {
@@ -32,7 +25,8 @@ export function useAgentLoop(deps) {
32
25
  setCompletedSubagents([]);
33
26
  setTeamState(null);
34
27
  setIsStreaming(true);
35
- thinkingVerbRef.current = randomVerb();
28
+ thinkingChunksRef.current = 0;
29
+ thinkingDoneRef.current = false;
36
30
  const abort = new AbortController();
37
31
  abortRef.current = abort;
38
32
  accTextRef.current = "";
@@ -101,7 +95,15 @@ export function useAgentLoop(deps) {
101
95
  const teammateStatus = new Map();
102
96
  const unsub = emitter.onEvent((event) => {
103
97
  switch (event.type) {
98
+ case "thinking":
99
+ thinkingChunksRef.current = event.chunkCount;
100
+ markDirty("text");
101
+ break;
104
102
  case "text":
103
+ if (thinkingChunksRef.current > 0 && !thinkingDoneRef.current) {
104
+ thinkingDoneRef.current = true;
105
+ markDirty("text");
106
+ }
105
107
  accTextRef.current += event.text;
106
108
  markDirty("text");
107
109
  break;
@@ -115,6 +117,14 @@ export function useAgentLoop(deps) {
115
117
  }
116
118
  break;
117
119
  }
120
+ case "tool_progress": {
121
+ const idx = toolCalls.findIndex(t => t.name === event.toolName && t.status === "running");
122
+ if (idx >= 0) {
123
+ toolCalls[idx].progress = event.progress;
124
+ markDirty("tools");
125
+ }
126
+ break;
127
+ }
118
128
  case "team_start":
119
129
  teamName = event.name;
120
130
  teamTotal = event.taskCount;
@@ -293,14 +303,9 @@ export function useAgentLoop(deps) {
293
303
  }
294
304
  }
295
305
  }
296
- setMessages(prev => {
297
- const last = prev[prev.length - 1];
298
- if (last?.role === "assistant" && !last.text && !last.usage && last.toolCalls?.length) {
299
- const updated = { ...last, toolCalls: [...last.toolCalls, completedTool] };
300
- return [...prev.slice(0, -1), updated];
301
- }
302
- return [...prev, { role: "assistant", text: "", toolCalls: [completedTool] }];
303
- });
306
+ // Each tool result is its own message — goes to Static immediately.
307
+ // (Don't batch into the last message Static can't re-render updated items.)
308
+ setMessages(prev => [...prev, { role: "assistant", text: "", toolCalls: [completedTool] }]);
304
309
  setActiveTools([...toolCalls]);
305
310
  accTextRef.current = "";
306
311
  setStreamingText("");
@@ -6,6 +6,9 @@
6
6
  import type Anthropic from "@anthropic-ai/sdk";
7
7
  import { type StoreInfo } from "../../services/auth-service.js";
8
8
  import type { ChatMessage } from "../MessageList.js";
9
+ import type { NodeInfo } from "../NodeSelector.js";
10
+ import type { NodeStatus, NodeActionResult } from "../NodeManager.js";
11
+ import type { SessionMeta } from "../../services/session-persistence.js";
9
12
  export interface SlashCommandDeps {
10
13
  exit: () => void;
11
14
  toolsExpanded: boolean;
@@ -27,6 +30,15 @@ export interface SlashCommandDeps {
27
30
  setServerToolsAvailable: React.Dispatch<React.SetStateAction<number>>;
28
31
  setShowRewind: React.Dispatch<React.SetStateAction<boolean>>;
29
32
  rewindCheckpointCount: number;
33
+ setNodeSelectMode: React.Dispatch<React.SetStateAction<boolean>>;
34
+ setNodeList: React.Dispatch<React.SetStateAction<NodeInfo[]>>;
35
+ setSessionManagerMode: React.Dispatch<React.SetStateAction<boolean>>;
36
+ setSessionList: React.Dispatch<React.SetStateAction<SessionMeta[]>>;
37
+ setMemoryManagerMode: React.Dispatch<React.SetStateAction<boolean>>;
38
+ setMemoryList: React.Dispatch<React.SetStateAction<string[]>>;
39
+ setThemeSelectMode: React.Dispatch<React.SetStateAction<boolean>>;
40
+ setNodeManagerMode: React.Dispatch<React.SetStateAction<boolean>>;
41
+ setNodeManagerStatus: React.Dispatch<React.SetStateAction<NodeStatus>>;
30
42
  PKG_NAME: string;
31
43
  PKG_VERSION: string;
32
44
  }
@@ -34,4 +46,11 @@ export declare function useSlashCommands(deps: SlashCommandDeps): {
34
46
  handleCommand: (command: string) => Promise<void>;
35
47
  handleStoreSelect: (store: StoreInfo) => void;
36
48
  handleStoreCancel: () => void;
49
+ handleNodeSelect: (node: NodeInfo) => void;
50
+ handleNodeCancel: () => void;
51
+ handleSessionSelect: (session: SessionMeta) => void;
52
+ handleMemoryDelete: (index: number) => void;
53
+ handleNodeToggle: () => Promise<NodeActionResult>;
54
+ handleNodeRegister: () => Promise<NodeActionResult>;
55
+ handleNodeClose: (resultMessage?: string) => void;
37
56
  };