@testany/hephos 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +79 -0
  3. package/out/LocalExecutionEnvironment.d.ts +23 -0
  4. package/out/LocalExecutionEnvironment.d.ts.map +1 -0
  5. package/out/LocalExecutionEnvironment.js +124 -0
  6. package/out/LocalExecutionEnvironment.js.map +1 -0
  7. package/out/adapters/AdapterFactory.d.ts +55 -0
  8. package/out/adapters/AdapterFactory.d.ts.map +1 -0
  9. package/out/adapters/AdapterFactory.js +117 -0
  10. package/out/adapters/AdapterFactory.js.map +1 -0
  11. package/out/adapters/ClaudeCodeAdapter.d.ts +28 -0
  12. package/out/adapters/ClaudeCodeAdapter.d.ts.map +1 -0
  13. package/out/adapters/ClaudeCodeAdapter.js +95 -0
  14. package/out/adapters/ClaudeCodeAdapter.js.map +1 -0
  15. package/out/adapters/GenericShellAdapter.d.ts +51 -0
  16. package/out/adapters/GenericShellAdapter.d.ts.map +1 -0
  17. package/out/adapters/GenericShellAdapter.js +162 -0
  18. package/out/adapters/GenericShellAdapter.js.map +1 -0
  19. package/out/adapters/OpenAICodexAdapter.d.ts +36 -0
  20. package/out/adapters/OpenAICodexAdapter.d.ts.map +1 -0
  21. package/out/adapters/OpenAICodexAdapter.js +155 -0
  22. package/out/adapters/OpenAICodexAdapter.js.map +1 -0
  23. package/out/adapters/index.d.ts +12 -0
  24. package/out/adapters/index.d.ts.map +1 -0
  25. package/out/adapters/index.js +12 -0
  26. package/out/adapters/index.js.map +1 -0
  27. package/out/cli-layer-index.d.ts +14 -0
  28. package/out/cli-layer-index.d.ts.map +1 -0
  29. package/out/cli-layer-index.js +15 -0
  30. package/out/cli-layer-index.js.map +1 -0
  31. package/out/cli.d.ts +8 -0
  32. package/out/cli.d.ts.map +1 -0
  33. package/out/cli.js +297 -0
  34. package/out/cli.js.map +1 -0
  35. package/out/commands/AgentsCommand.d.ts +47 -0
  36. package/out/commands/AgentsCommand.d.ts.map +1 -0
  37. package/out/commands/AgentsCommand.js +487 -0
  38. package/out/commands/AgentsCommand.js.map +1 -0
  39. package/out/config/ConfigAdapter.d.ts +58 -0
  40. package/out/config/ConfigAdapter.d.ts.map +1 -0
  41. package/out/config/ConfigAdapter.js +95 -0
  42. package/out/config/ConfigAdapter.js.map +1 -0
  43. package/out/config/UIPreferences.d.ts +24 -0
  44. package/out/config/UIPreferences.d.ts.map +1 -0
  45. package/out/config/UIPreferences.js +18 -0
  46. package/out/config/UIPreferences.js.map +1 -0
  47. package/out/config/index.d.ts +9 -0
  48. package/out/config/index.d.ts.map +1 -0
  49. package/out/config/index.js +9 -0
  50. package/out/config/index.js.map +1 -0
  51. package/out/outputs/ConsoleLogger.d.ts +30 -0
  52. package/out/outputs/ConsoleLogger.d.ts.map +1 -0
  53. package/out/outputs/ConsoleLogger.js +54 -0
  54. package/out/outputs/ConsoleLogger.js.map +1 -0
  55. package/out/outputs/ConsoleOutput.d.ts +30 -0
  56. package/out/outputs/ConsoleOutput.d.ts.map +1 -0
  57. package/out/outputs/ConsoleOutput.js +49 -0
  58. package/out/outputs/ConsoleOutput.js.map +1 -0
  59. package/out/outputs/IOutput.d.ts +38 -0
  60. package/out/outputs/IOutput.d.ts.map +1 -0
  61. package/out/outputs/IOutput.js +13 -0
  62. package/out/outputs/IOutput.js.map +1 -0
  63. package/out/outputs/InkOutput.d.ts +24 -0
  64. package/out/outputs/InkOutput.d.ts.map +1 -0
  65. package/out/outputs/InkOutput.js +38 -0
  66. package/out/outputs/InkOutput.js.map +1 -0
  67. package/out/repl/ReplModeInk.d.ts +8 -0
  68. package/out/repl/ReplModeInk.d.ts.map +1 -0
  69. package/out/repl/ReplModeInk.js +1486 -0
  70. package/out/repl/ReplModeInk.js.map +1 -0
  71. package/out/repl/components/AgentsMenu.d.ts +7 -0
  72. package/out/repl/components/AgentsMenu.d.ts.map +1 -0
  73. package/out/repl/components/AgentsMenu.js +565 -0
  74. package/out/repl/components/AgentsMenu.js.map +1 -0
  75. package/out/repl/components/LoadingIndicator.d.ts +5 -0
  76. package/out/repl/components/LoadingIndicator.d.ts.map +1 -0
  77. package/out/repl/components/LoadingIndicator.js +14 -0
  78. package/out/repl/components/LoadingIndicator.js.map +1 -0
  79. package/out/repl/components/QueueDisplay.d.ts +18 -0
  80. package/out/repl/components/QueueDisplay.d.ts.map +1 -0
  81. package/out/repl/components/QueueDisplay.js +16 -0
  82. package/out/repl/components/QueueDisplay.js.map +1 -0
  83. package/out/repl/components/RestorePrompt.d.ts +25 -0
  84. package/out/repl/components/RestorePrompt.d.ts.map +1 -0
  85. package/out/repl/components/RestorePrompt.js +32 -0
  86. package/out/repl/components/RestorePrompt.js.map +1 -0
  87. package/out/repl/components/StreamingDisplay.d.ts +5 -0
  88. package/out/repl/components/StreamingDisplay.d.ts.map +1 -0
  89. package/out/repl/components/StreamingDisplay.js +27 -0
  90. package/out/repl/components/StreamingDisplay.js.map +1 -0
  91. package/out/repl/components/ThinkingIndicator.d.ts +19 -0
  92. package/out/repl/components/ThinkingIndicator.d.ts.map +1 -0
  93. package/out/repl/components/ThinkingIndicator.js +35 -0
  94. package/out/repl/components/ThinkingIndicator.js.map +1 -0
  95. package/out/repl/wizard/wizardStep1Reducer.d.ts +30 -0
  96. package/out/repl/wizard/wizardStep1Reducer.d.ts.map +1 -0
  97. package/out/repl/wizard/wizardStep1Reducer.js +189 -0
  98. package/out/repl/wizard/wizardStep1Reducer.js.map +1 -0
  99. package/package.json +62 -0
@@ -0,0 +1,1486 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * ReplModeInk - 基于 Ink + React 的交互式 REPL
4
+ */
5
+ import { useState, useEffect, useRef, useMemo } from 'react';
6
+ import { render, Box, Text, useInput, useApp, Static, useStdout } from 'ink';
7
+ import TextInput from 'ink-text-input';
8
+ import * as fs from 'fs';
9
+ import { watch } from 'fs';
10
+ import * as path from 'path';
11
+ import { fileURLToPath } from 'url';
12
+ import { detectAllTools } from '@testany/agent-chatter-core';
13
+ import { initializeServices } from '@testany/agent-chatter-core';
14
+ import { LocalExecutionEnvironment } from '../LocalExecutionEnvironment.js';
15
+ import { AdapterFactory } from '../adapters/AdapterFactory.js';
16
+ import { splitConfig, DEFAULT_UI_PREFERENCES } from '../config/index.js';
17
+ import { processWizardStep1Input } from './wizard/wizardStep1Reducer.js';
18
+ import { AgentsMenu } from './components/AgentsMenu.js';
19
+ import { RegistryStorage } from '@testany/agent-chatter-core';
20
+ import { ThinkingIndicator } from './components/ThinkingIndicator.js';
21
+ import { getTeamConfigDir, ensureTeamConfigDir, resolveTeamConfigPath, formatMissingConfigError, discoverTeamConfigs } from '@testany/agent-chatter-core';
22
+ import { SessionStorageService } from '@testany/agent-chatter-core';
23
+ import { RestorePrompt } from './components/RestorePrompt.js';
24
+ import { QueueDisplay } from './components/QueueDisplay.js';
25
+ // Read version from package.json
26
+ const __filename = fileURLToPath(import.meta.url);
27
+ const __dirname = path.dirname(__filename);
28
+ const packageJsonPath = path.join(__dirname, '..', '..', 'package.json');
29
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
30
+ const VERSION = packageJson.version;
31
+ const commands = [
32
+ { name: '/help', desc: 'Show this help message' },
33
+ { name: '/status', desc: 'Check installed AI CLI tools' },
34
+ { name: '/agents', desc: 'Manage registered AI agents' },
35
+ { name: '/team', desc: 'Manage team configurations' },
36
+ { name: '/clear', desc: 'Clear the screen' },
37
+ { name: '/exit', desc: 'Exit the application' },
38
+ ];
39
+ const teamCommands = [
40
+ { name: '/team create', desc: 'Create a new team configuration' },
41
+ { name: '/team list', desc: 'List all team configurations' },
42
+ { name: '/team deploy', desc: 'Deploy and load a team configuration' },
43
+ { name: '/team edit', desc: 'Edit an existing team configuration' },
44
+ { name: '/team delete', desc: 'Delete a team configuration' },
45
+ ];
46
+ const agentsCommands = [
47
+ { name: '/agents register', desc: 'Scan and register AI CLI tools' },
48
+ { name: '/agents list', desc: 'List all registered agents' },
49
+ { name: '/agents verify', desc: 'Verify agent availability' },
50
+ { name: '/agents info', desc: 'Show agent detailed information' },
51
+ { name: '/agents edit', desc: 'Edit agent configuration' },
52
+ { name: '/agents delete', desc: 'Delete a registered agent' },
53
+ ];
54
+ // 欢迎界面组件
55
+ function WelcomeScreen() {
56
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { borderStyle: "single", borderColor: "cyan", paddingX: 2, paddingY: 1, flexDirection: "column", alignItems: "center", children: [_jsx(Text, { bold: true, color: "cyan", children: "AGENT CHATTER" }), _jsx(Text, { dimColor: true, children: "Multi-AI Conversation Orchestrator" })] }), _jsxs(Text, { dimColor: true, children: [" Version ", VERSION, " \u2022 TestAny.io"] }), _jsxs(Text, { dimColor: true, children: [" Type ", _jsx(Text, { color: "green", children: "/help" }), " for available commands"] }), _jsxs(Text, { dimColor: true, children: [" Type ", _jsx(Text, { color: "green", children: "/exit" }), " to quit"] })] }));
57
+ }
58
+ // 命令提示组件
59
+ // In alternateBuffer mode, we need a fixed height to prevent layout jumps
60
+ const MAX_VISIBLE_HINTS = 5; // Max visible command hints (not counting scroll indicators)
61
+ const HINT_BOX_HEIGHT = MAX_VISIBLE_HINTS + 1; // +1 for marginTop
62
+ function CommandHints({ input, selectedIndex }) {
63
+ if (!input.startsWith('/')) {
64
+ // Return empty placeholder with fixed height to prevent layout shift
65
+ return _jsx(Box, { height: HINT_BOX_HEIGHT });
66
+ }
67
+ const matches = commands.filter(cmd => cmd.name.startsWith(input));
68
+ if (matches.length === 0) {
69
+ // Return empty placeholder with fixed height to prevent layout shift
70
+ return _jsx(Box, { height: HINT_BOX_HEIGHT });
71
+ }
72
+ // If all matches fit, show them all
73
+ if (matches.length <= MAX_VISIBLE_HINTS) {
74
+ return (_jsx(Box, { flexDirection: "column", marginLeft: 2, marginTop: 1, height: HINT_BOX_HEIGHT, children: matches.map((cmd, idx) => (_jsxs(Box, { children: [_jsxs(Text, { color: idx === selectedIndex ? 'green' : 'gray', bold: idx === selectedIndex, children: [idx === selectedIndex ? '▶ ' : ' ', cmd.name] }), _jsxs(Text, { dimColor: true, children: [" - ", cmd.desc] })] }, cmd.name))) }));
75
+ }
76
+ // Need scrolling: reserve 1 line for scroll indicator, show MAX_VISIBLE_HINTS - 1 items
77
+ const visibleItems = MAX_VISIBLE_HINTS - 1;
78
+ // Calculate scroll window to keep selectedIndex visible
79
+ let startIdx = 0;
80
+ if (selectedIndex >= visibleItems) {
81
+ startIdx = selectedIndex - visibleItems + 1;
82
+ }
83
+ startIdx = Math.max(0, Math.min(startIdx, matches.length - visibleItems));
84
+ const displayMatches = matches.slice(startIdx, startIdx + visibleItems);
85
+ const hasMoreAbove = startIdx > 0;
86
+ const hasMoreBelow = startIdx + visibleItems < matches.length;
87
+ // Build scroll indicator text
88
+ let scrollIndicator = '';
89
+ if (hasMoreAbove && hasMoreBelow) {
90
+ scrollIndicator = `↑${startIdx} more above | ↓${matches.length - startIdx - visibleItems} more below`;
91
+ }
92
+ else if (hasMoreAbove) {
93
+ scrollIndicator = `↑ ${startIdx} more above`;
94
+ }
95
+ else if (hasMoreBelow) {
96
+ scrollIndicator = `↓ ${matches.length - startIdx - visibleItems} more below`;
97
+ }
98
+ return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: 1, height: HINT_BOX_HEIGHT, children: [displayMatches.map((cmd, idx) => {
99
+ const actualIdx = startIdx + idx;
100
+ return (_jsxs(Box, { children: [_jsxs(Text, { color: actualIdx === selectedIndex ? 'green' : 'gray', bold: actualIdx === selectedIndex, children: [actualIdx === selectedIndex ? '▶ ' : ' ', cmd.name] }), _jsxs(Text, { dimColor: true, children: [" - ", cmd.desc] })] }, cmd.name));
101
+ }), scrollIndicator && (_jsxs(Text, { dimColor: true, children: [" ", scrollIndicator] }))] }));
102
+ }
103
+ // 帮助信息
104
+ function HelpMessage() {
105
+ return (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Available Commands:" }), commands.map(cmd => (_jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { color: "green", children: cmd.name.padEnd(18) }), _jsx(Text, { dimColor: true, children: cmd.desc })] }, cmd.name))), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Message Markers:" }), _jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { color: "green", children: '[FROM:name]'.padEnd(18) }), _jsx(Text, { dimColor: true, children: "Specify human sender (required for multi-human teams)" })] }), _jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { color: "green", children: '[NEXT:name]'.padEnd(18) }), _jsx(Text, { dimColor: true, children: "Route message to specific member" })] }), _jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { color: "green", children: '[TEAM_TASK:desc]'.padEnd(18) }), _jsx(Text, { dimColor: true, children: "Set persistent team-wide task context" })] })] })] }));
106
+ }
107
+ /**
108
+ * Renders the active todo list with in-place updates.
109
+ * Shows member name in their theme color.
110
+ */
111
+ function TodoListView({ todoList }) {
112
+ const statusEmoji = (status) => {
113
+ switch (status) {
114
+ case 'completed': return '✅';
115
+ case 'cancelled': return '❌';
116
+ default: return '⭕'; // pending, in_progress
117
+ }
118
+ };
119
+ return (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsxs(Text, { bold: true, color: todoList.memberThemeColor, children: ["\uD83D\uDCCB Plan (", todoList.memberDisplayName, "):"] }), todoList.items.map((item, idx) => (_jsxs(Text, { color: item.status === 'completed' ? 'green' : 'yellow', children: [statusEmoji(item.status), " ", item.text] }, `${todoList.todoId}-${idx}`)))] }));
120
+ }
121
+ // ===== End Todo List Types and Component =====
122
+ // 工具状态显示
123
+ function StatusDisplay({ tools }) {
124
+ const installed = tools.filter(t => t.installed);
125
+ const notInstalled = tools.filter(t => !t.installed);
126
+ return (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [installed.length > 0 && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "green", children: "\u2713 Installed:" }), installed.map(tool => (_jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { color: "green", children: "\u25CF" }), _jsxs(Text, { children: [" ", tool.displayName] }), tool.version && _jsxs(Text, { dimColor: true, children: [" (v", tool.version, ")"] })] }, tool.name)))] })), notInstalled.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: "yellow", children: "\u2717 Not Installed:" }), notInstalled.map(tool => (_jsxs(Box, { marginLeft: 2, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "\u25CB" }), _jsxs(Text, { children: [" ", tool.displayName] })] }), tool.installHint && (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { dimColor: true, children: [" ", tool.installHint] }) }))] }, tool.name)))] }))] }));
127
+ }
128
+ // Team configuration list component (using discoverTeamConfigs)
129
+ function TeamList() {
130
+ const configs = discoverTeamConfigs();
131
+ if (configs.length === 0) {
132
+ return (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: "yellow", children: "No team configurations found" }), _jsx(Text, { dimColor: true, children: "Use /team create to create a team configuration" })] }));
133
+ }
134
+ return (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Available Teams:" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: configs.map((config, idx) => (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { children: [_jsxs(Text, { color: "cyan", bold: true, children: ["[", idx + 1, "]"] }), _jsx(Text, { children: " " }), _jsx(Text, { color: "green", children: config.displayName })] }), _jsx(Box, { marginLeft: 4, children: _jsxs(Text, { dimColor: true, children: ["File: ", config.filename] }) }), _jsx(Box, { marginLeft: 4, children: _jsxs(Text, { dimColor: true, children: ["Members: ", config.memberCount, " (", config.aiCount, " AI, ", config.humanCount, " Human)"] }) })] }, config.filename))) }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "Type " }), _jsx(Text, { color: "green", children: "/team deploy <filename>" }), _jsx(Text, { dimColor: true, children: " to deploy a team" })] })] }));
135
+ }
136
+ // Team menu help component
137
+ function TeamMenuHelp() {
138
+ return (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Team Management Commands:" }), teamCommands.map(cmd => (_jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { color: "green", children: cmd.name.padEnd(22) }), _jsx(Text, { dimColor: true, children: cmd.desc })] }, cmd.name)))] }));
139
+ }
140
+ // Legacy config list (kept for /list command backward compatibility)
141
+ // Now uses discoverTeamConfigs() for content-based discovery
142
+ function ConfigList({ currentConfigPath }) {
143
+ const configs = discoverTeamConfigs();
144
+ if (configs.length === 0) {
145
+ return (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: "yellow", children: "No configuration files found" }), _jsx(Text, { dimColor: true, children: "Use /team create to create a team configuration" })] }));
146
+ }
147
+ return (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: "yellow", dimColor: true, children: "Note: /list is deprecated. Use /team list instead." }), _jsx(Box, { marginTop: 1, children: configs.map((config) => {
148
+ const isActive = currentConfigPath && config.filename === path.basename(currentConfigPath);
149
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: isActive ? 'green' : 'gray', children: isActive ? '●' : '○' }), _jsx(Text, { children: " " }), _jsx(Text, { color: isActive ? 'green' : 'white', children: config.displayName })] }), _jsx(Box, { marginLeft: 4, children: _jsxs(Text, { dimColor: true, children: ["File: ", config.filename] }) })] }, config.filename));
150
+ }) })] }));
151
+ }
152
+ function WizardView({ wizardState }) {
153
+ const { step, totalSteps, data } = wizardState;
154
+ return (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Box, { borderStyle: "single", borderColor: "gray", padding: 1, children: _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, color: "cyan", children: "Team Creation Wizard" }), _jsxs(Text, { dimColor: true, children: ["Step ", step, "/", totalSteps] })] }) }), step === 1 && _jsx(WizardStep1TeamStructure, { data: data }), step === 2 && _jsx(WizardStep2DetectAgents, { data: data }), step === 3 && _jsx(WizardStep3ConfigureMembers, { data: data }), step === 4 && _jsx(WizardStep4TeamSettings, { data: data }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "Press " }), _jsx(Text, { color: "yellow", children: "Ctrl+C" }), _jsx(Text, { dimColor: true, children: " to cancel wizard" })] })] }));
155
+ }
156
+ function WizardStep1TeamStructure({ data }) {
157
+ const visibleRoles = (data.roleDefinitions || []).filter(role => role.name && role.name.trim().length > 0);
158
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, children: "Step 1/4: Team Structure" }), _jsx(Text, { dimColor: true, children: "Define your team's basic structure" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { children: ["Team Name: ", _jsx(Text, { color: "cyan", children: data.teamName || '(not set)' })] }), _jsxs(Text, { children: ["Description: ", _jsx(Text, { color: "cyan", children: data.teamDescription || '(not set)' })] }), _jsxs(Text, { children: ["Instruction File: ", _jsx(Text, { color: "cyan", children: data.teamInstructionFile || '(not set)' })] }), visibleRoles.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { children: "Roles Defined:" }), visibleRoles.map((role, idx) => (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { children: ["\u2022 ", role.name, role.description ? `: ${role.description}` : ''] }) }, `${role.name}-${idx}`)))] }))] })] }));
159
+ }
160
+ function WizardStep2DetectAgents({ data }) {
161
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, children: "Step 2/4: Detect Available AI Agents" }), _jsx(Text, { dimColor: true, children: "Scanning installed AI CLI tools..." }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: data.availableAgents && data.availableAgents.length > 0 ? (_jsxs(_Fragment, { children: [_jsxs(Text, { color: "green", children: ["\u2713 Found ", data.availableAgents.length, " AI agent(s)"] }), data.availableAgents.map((agent, idx) => (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { children: [data.selectedAgents?.includes(agent) ? '☑' : '☐', " ", agent] }) }, idx)))] })) : (_jsx(Text, { color: "yellow", children: "No AI agents detected" })) })] }));
162
+ }
163
+ function WizardStep3ConfigureMembers({ data }) {
164
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, children: "Step 3/4: Configure Team Members" }), _jsx(Text, { dimColor: true, children: "Configure each team member" }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: data.memberConfigs && data.memberConfigs.length > 0 ? (data.memberConfigs.map((member, idx) => (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { children: ["Member ", idx + 1, ": ", _jsx(Text, { color: "cyan", children: member.displayName || '(not configured)' })] }), _jsx(Box, { marginLeft: 2, children: _jsxs(Text, { dimColor: true, children: ["Type: ", member.type, ", Role: ", member.assignedRole] }) })] }, idx)))) : (_jsx(Text, { color: "yellow", children: "No members configured yet" })) })] }));
165
+ }
166
+ function WizardStep4TeamSettings({ data }) {
167
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, children: "Step 4/4: Team Settings" }), _jsx(Text, { dimColor: true, children: "Configure final team settings" }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsxs(Text, { children: ["Max Rounds: ", _jsx(Text, { color: "cyan", children: data.maxRounds ?? '(not set)' })] }) })] }));
168
+ }
169
+ function MenuView({ menuState, menuItems, selectedIndex }) {
170
+ return (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Box, { borderStyle: "single", borderColor: "gray", padding: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Team Configuration Editor" }) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { bold: true, children: ["Team: ", menuState.config.team?.name || 'Unknown'] }), _jsxs(Text, { dimColor: true, children: ["File: ", menuState.configPath] })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Main Menu" }), _jsx(Text, { dimColor: true, children: '─'.repeat(40) }), menuItems.map((item, idx) => (_jsx(Box, { children: _jsxs(Text, { color: idx === selectedIndex ? 'green' : 'gray', bold: idx === selectedIndex, children: [idx === selectedIndex ? '▶ ' : ' ', item.label] }) }, idx)))] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "Use " }), _jsx(Text, { color: "yellow", children: "\u2191\u2193" }), _jsx(Text, { dimColor: true, children: " to navigate, " }), _jsx(Text, { color: "yellow", children: "Enter" }), _jsx(Text, { dimColor: true, children: " to select, " }), _jsx(Text, { color: "yellow", children: "Ctrl+C" }), _jsx(Text, { dimColor: true, children: " to cancel" })] })] }));
171
+ }
172
+ function FormView({ formState }) {
173
+ const currentField = formState.fields[formState.currentFieldIndex];
174
+ const error = formState.errors[currentField?.name];
175
+ return (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Box, { borderStyle: "single", borderColor: "gray", padding: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Input Form" }) }), currentField && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: currentField.label }), currentField.required && _jsx(Text, { dimColor: true, children: "(required)" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { children: currentField.value }) }), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["\u2717 ", error] }) })), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "Press " }), _jsx(Text, { color: "yellow", children: "Enter" }), _jsx(Text, { dimColor: true, children: " to confirm, " }), _jsx(Text, { color: "yellow", children: "Ctrl+C" }), _jsx(Text, { dimColor: true, children: " to cancel" })] })] }))] }));
176
+ }
177
+ function SelectView({ title, options, selectedIndex, multiSelect, selectedItems }) {
178
+ return (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Box, { borderStyle: "single", borderColor: "gray", padding: 1, children: _jsx(Text, { bold: true, color: "cyan", children: title }) }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: options.map((option, idx) => {
179
+ const isSelected = idx === selectedIndex;
180
+ const isChecked = multiSelect && selectedItems?.has(option);
181
+ return (_jsx(Box, { children: _jsxs(Text, { color: isSelected ? 'green' : 'gray', bold: isSelected, children: [isSelected ? '▶ ' : ' ', multiSelect ? (isChecked ? '☑' : '☐') : '', ' ', option] }) }, idx));
182
+ }) }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "Use " }), _jsx(Text, { color: "yellow", children: "\u2191\u2193" }), _jsx(Text, { dimColor: true, children: " to navigate, " }), multiSelect && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "yellow", children: "Space" }), _jsx(Text, { dimColor: true, children: " to toggle, " })] })), _jsx(Text, { color: "yellow", children: "Enter" }), _jsx(Text, { dimColor: true, children: " to confirm" })] })] }));
183
+ }
184
+ function App({ registryPath, debug = false }) {
185
+ const { stdout } = useStdout();
186
+ const terminalWidth = stdout?.columns || 80;
187
+ const [input, setInput] = useState('');
188
+ // Initialize output with WelcomeScreen as first item - it will be rendered once via Static
189
+ const [output, setOutput] = useState([_jsx(WelcomeScreen, {}, "welcome")]);
190
+ const [currentConfig, setCurrentConfig] = useState(null);
191
+ const [currentConfigPath, setCurrentConfigPath] = useState(null);
192
+ const [uiPrefs, setUiPrefs] = useState(DEFAULT_UI_PREFERENCES);
193
+ const [keyCounter, setKeyCounter] = useState(0);
194
+ const [selectedIndex, setSelectedIndex] = useState(0);
195
+ const [mode, setMode] = useState('normal');
196
+ const [activeCoordinator, setActiveCoordinator] = useState(null);
197
+ const [activeTeam, setActiveTeam] = useState(null);
198
+ const [executingAgent, setExecutingAgent] = useState(null);
199
+ const [activeTodoList, setActiveTodoList] = useState(null);
200
+ const [isExiting, setIsExiting] = useState(false);
201
+ const [queueState, setQueueState] = useState(null);
202
+ // Streaming event handling
203
+ const pendingEventsRef = useRef([]);
204
+ const flushScheduledRef = useRef(false);
205
+ const eventListenerRef = useRef(null);
206
+ const eventEmitterRef = useRef(null);
207
+ // Registry path management
208
+ const [registry] = useState(() => {
209
+ const defaultPath = new RegistryStorage().getPath();
210
+ return registryPath || defaultPath;
211
+ });
212
+ // Wizard state
213
+ const [wizardState, setWizardState] = useState(null);
214
+ // Menu state
215
+ const [menuState, setMenuState] = useState(null);
216
+ const [menuItems, setMenuItems] = useState([]);
217
+ // Session restore state
218
+ const [sessionStorage] = useState(() => new SessionStorageService());
219
+ const [pendingRestore, setPendingRestore] = useState(null);
220
+ // Form state
221
+ const [formState, setFormState] = useState(null);
222
+ // Select state
223
+ const [selectState, setSelectState] = useState(null);
224
+ // Confirmation state
225
+ const [confirmState, setConfirmState] = useState(null);
226
+ const { exit } = useApp();
227
+ const debugLogStreamRef = useRef(null);
228
+ const getNextKey = () => {
229
+ const currentKey = keyCounter;
230
+ setKeyCounter(prev => prev + 1);
231
+ return currentKey;
232
+ };
233
+ useEffect(() => {
234
+ return () => {
235
+ attachEventEmitter(null);
236
+ if (debugLogStreamRef.current) {
237
+ debugLogStreamRef.current.end('\n');
238
+ }
239
+ };
240
+ }, []);
241
+ // 获取当前匹配的命令
242
+ const getMatches = () => {
243
+ if (!input.startsWith('/'))
244
+ return [];
245
+ return commands.filter(cmd => cmd.name.startsWith(input));
246
+ };
247
+ const truncate = (val, max = 100) => {
248
+ if (!val)
249
+ return '';
250
+ return val.length > max ? `${val.slice(0, max)}…` : val;
251
+ };
252
+ /**
253
+ * Append node(s) to output, with optional sliding window to keep render fast.
254
+ * Supports both single ReactNode and ReactNode[] - arrays are flattened into individual items.
255
+ */
256
+ const appendOutput = (nodeOrNodes, maxItems = 500) => {
257
+ setOutput(prev => {
258
+ // Flatten arrays into individual items for correct sliding window counting
259
+ const nodesToAdd = Array.isArray(nodeOrNodes) ? nodeOrNodes : [nodeOrNodes];
260
+ const next = [...prev, ...nodesToAdd];
261
+ if (next.length > maxItems) {
262
+ // drop oldest
263
+ return next.slice(next.length - maxItems);
264
+ }
265
+ return next;
266
+ });
267
+ };
268
+ // Logger for Core services - writes to debug.log (and stderr for warn/error) to keep REPL UI clean
269
+ useEffect(() => {
270
+ if (!debug)
271
+ return;
272
+ const logPath = path.join(process.cwd(), 'debug.log');
273
+ const stream = fs.createWriteStream(logPath, { flags: 'a' });
274
+ stream.write(`\n===== ${new Date().toISOString()} REPL session started =====\n`);
275
+ debugLogStreamRef.current = stream;
276
+ return () => {
277
+ stream.end('\n');
278
+ };
279
+ }, [debug]);
280
+ const uiLogger = useMemo(() => {
281
+ const writeLog = (level, message, context, echoToStderr) => {
282
+ const formatted = context ? `[Core] [${level}] ${message} ${JSON.stringify(context)}` : `[Core] [${level}] ${message}`;
283
+ if (debug && debugLogStreamRef.current) {
284
+ debugLogStreamRef.current.write(`${new Date().toISOString()} ${formatted}\n`);
285
+ }
286
+ if (echoToStderr) {
287
+ console.error(`\x1b[2m${formatted}\x1b[0m`);
288
+ }
289
+ };
290
+ return {
291
+ debug: (message, context) => {
292
+ if (!debug)
293
+ return;
294
+ writeLog('DEBUG', message, context, false);
295
+ },
296
+ info: (message, context) => {
297
+ if (!debug)
298
+ return;
299
+ writeLog('INFO', message, context, false);
300
+ },
301
+ warn: (message, context) => {
302
+ writeLog('WARN', message, context, true);
303
+ },
304
+ error: (message, context) => {
305
+ writeLog('ERROR', message, context, true);
306
+ },
307
+ };
308
+ }, [debug]);
309
+ // Helper to format verification results for UI display
310
+ const formatVerificationResults = (results) => {
311
+ for (const [agentType, result] of results) {
312
+ if (result.status === 'verified') {
313
+ appendOutput(_jsxs(Text, { color: "green", children: ["\u2713 Agent ", agentType, " verified"] }, `verify-${agentType}-${getNextKey()}`));
314
+ }
315
+ else if (result.status === 'verified_with_warnings') {
316
+ appendOutput(_jsxs(Text, { color: "yellow", children: ["\u26A0 Agent ", agentType, " verified with warnings"] }, `verify-${agentType}-${getNextKey()}`));
317
+ }
318
+ if (result.checks && result.checks.length > 0) {
319
+ appendOutput(_jsx(Text, { dimColor: true, children: " Verification checks:" }, `verify-checks-${agentType}-${getNextKey()}`));
320
+ for (const check of result.checks) {
321
+ const icon = check.passed ? '✓' : '✗';
322
+ appendOutput(_jsxs(Text, { dimColor: true, children: [" ", icon, " ", check.name, ": ", check.message] }, `check-${agentType}-${check.name}-${getNextKey()}`));
323
+ if (check.warning) {
324
+ appendOutput(_jsxs(Text, { color: "yellow", children: [" \u26A0 ", check.warning] }, `check-warn-${agentType}-${check.name}-${getNextKey()}`));
325
+ }
326
+ }
327
+ }
328
+ }
329
+ };
330
+ const logEventToDebug = (text) => {
331
+ if (!debug)
332
+ return;
333
+ const entry = `${new Date().toISOString()} [AgentEvent] ${text}`;
334
+ if (debugLogStreamRef.current) {
335
+ debugLogStreamRef.current.write(`${entry}\n`);
336
+ }
337
+ // Do not echo to UI/STDOUT to keep REPL clean
338
+ };
339
+ const renderEvent = (ev) => {
340
+ const key = `stream-${ev.eventId || `${ev.agentId}-${ev.timestamp}`}-${getNextKey()}`;
341
+ switch (ev.type) {
342
+ case 'session.started':
343
+ return null; // too verbose for UI
344
+ case 'text':
345
+ // Skip 'result' category for Claude - it duplicates the streaming 'assistant-message' content
346
+ // We display 'assistant-message' (streaming chunks) for real-time feedback
347
+ // but skip 'result' (final complete response) to avoid showing the same text twice
348
+ if (ev.category === 'result') {
349
+ return null;
350
+ }
351
+ // Reasoning/text 都显示在 UI(与 v0.2.8 行为一致),并在 debug 时写日志
352
+ if (ev.category === 'reasoning') {
353
+ logEventToDebug(`[text:reasoning] ${truncate(ev.text)}`);
354
+ }
355
+ return (_jsx(Box, { flexDirection: "column", marginTop: 0, children: _jsx(Text, { color: ev.category === 'reasoning' ? 'gray' : undefined, children: ev.text }) }, key));
356
+ case 'tool.started': {
357
+ const formatTodos = (todos) => {
358
+ const names = todos
359
+ .map(t => t?.content)
360
+ .filter(Boolean)
361
+ .join(' | ');
362
+ return names ? `todos: ${names}` : '';
363
+ };
364
+ const displayParam = ev.input?.command ||
365
+ ev.input?.pattern ||
366
+ ev.input?.file_path ||
367
+ ev.input?.path ||
368
+ (Array.isArray(ev.input?.todos) ? formatTodos(ev.input?.todos) : '') ||
369
+ ev.input?.notebook_path ||
370
+ ev.input?.prompt ||
371
+ '';
372
+ const label = displayParam
373
+ ? `⏺ ${ev.toolName ?? 'tool'} (${displayParam})`
374
+ : `⏺ ${ev.toolName ?? 'tool'}`;
375
+ logEventToDebug(`[tool.started] ${label}`);
376
+ return (_jsx(Text, { color: "yellow", children: label }, key));
377
+ }
378
+ case 'tool.completed':
379
+ logEventToDebug(`[tool.completed] ${ev.toolId || ev.toolName} output=${truncate(ev.output)}${ev.error ? ` error=${ev.error}` : ''}`);
380
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "green", children: ["\u23BF ", truncate(ev.output)] }), ev.error && _jsxs(Text, { color: "red", children: [" error: ", ev.error] })] }, key));
381
+ case 'turn.completed':
382
+ return null; // internal state, UI already shows ✓ agent 完成
383
+ case 'error':
384
+ return (_jsxs(Text, { color: "red", children: ["error: ", ev.error] }, key));
385
+ case 'todo_list':
386
+ // IMPORTANT: Do NOT render here - handled separately via activeTodoList state
387
+ return null;
388
+ default:
389
+ return null;
390
+ }
391
+ };
392
+ const flushPendingNow = () => {
393
+ if (pendingEventsRef.current.length === 0)
394
+ return;
395
+ const pending = [...pendingEventsRef.current];
396
+ pendingEventsRef.current = [];
397
+ const nodes = pending
398
+ .map(renderEvent)
399
+ .filter((n) => Boolean(n));
400
+ if (nodes.length > 0) {
401
+ appendOutput(nodes);
402
+ }
403
+ };
404
+ const scheduleStreamFlush = () => {
405
+ if (flushScheduledRef.current)
406
+ return;
407
+ flushScheduledRef.current = true;
408
+ setTimeout(() => {
409
+ flushScheduledRef.current = false;
410
+ if (pendingEventsRef.current.length === 0)
411
+ return;
412
+ const pending = [...pendingEventsRef.current];
413
+ pendingEventsRef.current = [];
414
+ const nodes = pending
415
+ .map(renderEvent)
416
+ .filter((n) => Boolean(n));
417
+ if (nodes.length > 0) {
418
+ appendOutput(nodes);
419
+ }
420
+ }, 16);
421
+ };
422
+ /**
423
+ * Handle todo_list events - update activeTodoList state for in-place rendering.
424
+ * Looks up member info from activeTeam to get display name and theme color.
425
+ */
426
+ const handleTodoListEvent = (ev) => {
427
+ // Find the member info for display
428
+ let memberDisplayName = ev.agentId;
429
+ let memberThemeColor = 'cyan';
430
+ if (activeTeam) {
431
+ const member = activeTeam.members.find(m => m.id === ev.agentId);
432
+ if (member) {
433
+ memberDisplayName = member.displayName;
434
+ memberThemeColor = member.themeColor || 'cyan';
435
+ }
436
+ }
437
+ setActiveTodoList({
438
+ todoId: ev.todoId,
439
+ agentId: ev.agentId,
440
+ memberDisplayName,
441
+ memberThemeColor,
442
+ items: ev.items
443
+ });
444
+ };
445
+ /**
446
+ * Clear todo list - call on agent switch, cancel, or error.
447
+ */
448
+ const clearTodoList = () => {
449
+ setActiveTodoList(null);
450
+ };
451
+ const attachEventEmitter = (emitter) => {
452
+ // Cleanup previous listener
453
+ if (eventListenerRef.current && eventEmitterRef.current) {
454
+ eventEmitterRef.current.off?.('agent-event', eventListenerRef.current);
455
+ eventEmitterRef.current.removeListener?.('agent-event', eventListenerRef.current);
456
+ }
457
+ eventEmitterRef.current = emitter;
458
+ if (!emitter)
459
+ return;
460
+ const listener = (ev) => {
461
+ // Handle todo_list events separately - update state, don't add to output
462
+ if (ev.type === 'todo_list') {
463
+ handleTodoListEvent(ev);
464
+ return;
465
+ }
466
+ pendingEventsRef.current.push(ev);
467
+ scheduleStreamFlush();
468
+ };
469
+ eventListenerRef.current = listener;
470
+ emitter.on('agent-event', listener);
471
+ };
472
+ // Handle input submission (Enter key)
473
+ const handleInputSubmit = (value) => {
474
+ if (mode === 'conversation') {
475
+ const success = handleConversationInput(value.trim());
476
+ // Only clear input if message was successfully processed
477
+ // If validation failed, keep the input so user can edit and retry
478
+ if (success) {
479
+ setInput('');
480
+ }
481
+ }
482
+ else if (mode === 'wizard') {
483
+ handleWizardInput(value.trim());
484
+ setInput('');
485
+ }
486
+ else if (mode === 'form') {
487
+ handleFormSubmit();
488
+ setInput('');
489
+ }
490
+ else {
491
+ // Normal mode - handle autocomplete
492
+ const matches = getMatches();
493
+ if (matches.length > 0 && value !== matches[selectedIndex].name) {
494
+ // Autocomplete
495
+ setInput(matches[selectedIndex].name + ' ');
496
+ setSelectedIndex(0);
497
+ }
498
+ else {
499
+ // Execute command
500
+ handleCommand(value.trim());
501
+ setInput('');
502
+ setSelectedIndex(0);
503
+ }
504
+ }
505
+ };
506
+ useInput((inputChar, key) => {
507
+ // CRITICAL: Prevent parent useInput from handling events in agentsMenu mode
508
+ // AgentsMenu component handles all input in that mode
509
+ if (mode === 'agentsMenu') {
510
+ return;
511
+ }
512
+ // Handle restore-prompt mode (R/N key selection)
513
+ if (mode === 'restore-prompt' && pendingRestore) {
514
+ const choice = inputChar.toLowerCase();
515
+ if (choice === 'r') {
516
+ // Resume session
517
+ handleRestoreChoice(true);
518
+ return;
519
+ }
520
+ if (choice === 'n') {
521
+ // Start new session
522
+ handleRestoreChoice(false);
523
+ return;
524
+ }
525
+ // ESC to cancel and return to normal mode
526
+ if (key.escape) {
527
+ setPendingRestore(null);
528
+ setMode('normal');
529
+ appendOutput(_jsx(Text, { color: "yellow", children: "Session restore cancelled" }, `restore-cancel-${getNextKey()}`));
530
+ return;
531
+ }
532
+ // Invalid input - show hint
533
+ if (inputChar && !key.ctrl) {
534
+ appendOutput(_jsx(Text, { dimColor: true, children: "Press R to resume or N to start new" }, `restore-hint-${getNextKey()}`));
535
+ }
536
+ return;
537
+ }
538
+ // ESC key - Cancel agent execution in conversation mode
539
+ if (key.escape) {
540
+ if (mode === 'conversation' && activeCoordinator && executingAgent) {
541
+ // Check if ESC cancellation is allowed (LLD-05: use uiPrefs)
542
+ if (uiPrefs.allowEscCancel) {
543
+ // handleUserCancellation is async, fire-and-forget for UI responsiveness
544
+ activeCoordinator.handleUserCancellation().catch(() => {
545
+ // Errors already logged in method
546
+ });
547
+ clearTodoList(); // Clear todo list on cancel
548
+ appendOutput(_jsx(Text, { color: "yellow", children: "Agent execution cancelled by user (ESC)" }, `agent-cancelled-${getNextKey()}`));
549
+ return;
550
+ }
551
+ }
552
+ }
553
+ // Ctrl+C 退出或取消
554
+ if (key.ctrl && inputChar === 'c') {
555
+ if (mode === 'conversation' && activeCoordinator) {
556
+ // 退出对话模式
557
+ // stop() is async, fire-and-forget for UI responsiveness
558
+ activeCoordinator.stop().catch(() => {
559
+ // Errors already logged in method
560
+ });
561
+ clearTodoList(); // Clear todo list on exit
562
+ setQueueState(null); // Clear queue display on exit
563
+ setMode('normal');
564
+ setActiveCoordinator(null);
565
+ setActiveTeam(null);
566
+ appendOutput(_jsx(Text, { color: "yellow", children: "Conversation stopped." }, `conv-stopped-${getNextKey()}`));
567
+ setInput('');
568
+ return;
569
+ }
570
+ else if (mode === 'wizard') {
571
+ // 取消向导
572
+ setMode('normal');
573
+ setWizardState(null);
574
+ appendOutput(_jsx(Text, { color: "yellow", children: "Wizard cancelled." }, `wizard-cancelled-${getNextKey()}`));
575
+ setInput('');
576
+ return;
577
+ }
578
+ else if (mode === 'menu') {
579
+ // 退出菜单
580
+ setMode('normal');
581
+ setMenuState(null);
582
+ setMenuItems([]);
583
+ appendOutput(_jsx(Text, { color: "yellow", children: "Menu editor closed." }, `menu-cancelled-${getNextKey()}`));
584
+ setInput('');
585
+ return;
586
+ }
587
+ else if (mode === 'form') {
588
+ // 取消表单
589
+ setMode('normal');
590
+ setFormState(null);
591
+ appendOutput(_jsx(Text, { color: "yellow", children: "Form cancelled." }, `form-cancelled-${getNextKey()}`));
592
+ setInput('');
593
+ return;
594
+ }
595
+ else if (mode === 'select') {
596
+ // 取消选择
597
+ setMode('normal');
598
+ setSelectState(null);
599
+ appendOutput(_jsx(Text, { color: "yellow", children: "Selection cancelled." }, `select-cancelled-${getNextKey()}`));
600
+ setInput('');
601
+ return;
602
+ }
603
+ else {
604
+ appendOutput(_jsx(Text, { color: "cyan", children: "Goodbye! \uD83D\uDC4B" }, "goodbye"));
605
+ setTimeout(() => exit(), 100);
606
+ return;
607
+ }
608
+ }
609
+ // 退格
610
+ if (key.backspace || key.delete) {
611
+ // CRITICAL: When in modes that use TextInput (normal, conversation, wizard, form),
612
+ // we MUST NOT handle backspace here. ink-text-input handles it internally based on cursor position.
613
+ // If we handle it here, we blindly slice the end of the string, which is wrong if the cursor is in the middle.
614
+ if (mode === 'normal' || mode === 'conversation' || mode === 'wizard' || mode === 'form') {
615
+ return;
616
+ }
617
+ setInput(prev => prev.slice(0, -1));
618
+ // if (mode === 'normal') setSelectedIndex(0); // This logic is likely not needed for menu/select modes
619
+ return;
620
+ }
621
+ // 回车 - 仅在 menu/select 模式处理(TextInput 模式由 onSubmit 处理)
622
+ if (key.return) {
623
+ if (mode === 'select') {
624
+ // 选择模式:确认选择
625
+ handleSelectConfirm();
626
+ }
627
+ else if (mode === 'menu') {
628
+ // 菜单模式:选择菜单项
629
+ handleMenuSelect();
630
+ }
631
+ // normal/conversation/wizard/form 由 TextInput 的 onSubmit 处理
632
+ return;
633
+ }
634
+ // 处理不同模式下的上下键导航
635
+ if (key.upArrow) {
636
+ if (mode === 'normal') {
637
+ const matches = getMatches();
638
+ if (matches.length > 0) {
639
+ setSelectedIndex(prev => (prev > 0 ? prev - 1 : matches.length - 1));
640
+ return;
641
+ }
642
+ }
643
+ else if (mode === 'menu' && menuItems.length > 0) {
644
+ setSelectedIndex(prev => (prev > 0 ? prev - 1 : menuItems.length - 1));
645
+ return;
646
+ }
647
+ else if (mode === 'select' && selectState) {
648
+ setSelectedIndex(prev => (prev > 0 ? prev - 1 : selectState.options.length - 1));
649
+ return;
650
+ }
651
+ }
652
+ if (key.downArrow) {
653
+ if (mode === 'normal') {
654
+ const matches = getMatches();
655
+ if (matches.length > 0) {
656
+ setSelectedIndex(prev => (prev < matches.length - 1 ? prev + 1 : 0));
657
+ return;
658
+ }
659
+ }
660
+ else if (mode === 'menu' && menuItems.length > 0) {
661
+ setSelectedIndex(prev => (prev < menuItems.length - 1 ? prev + 1 : 0));
662
+ return;
663
+ }
664
+ else if (mode === 'select' && selectState) {
665
+ setSelectedIndex(prev => (prev < selectState.options.length - 1 ? prev + 1 : 0));
666
+ return;
667
+ }
668
+ }
669
+ // 处理空格键 (用于多选模式)
670
+ if (inputChar === ' ' && mode === 'select' && selectState && selectState.multiSelect) {
671
+ const option = selectState.options[selectedIndex];
672
+ setSelectState(prev => {
673
+ if (!prev)
674
+ return prev;
675
+ const newSelected = new Set(prev.selectedItems);
676
+ if (newSelected.has(option)) {
677
+ newSelected.delete(option);
678
+ }
679
+ else {
680
+ newSelected.add(option);
681
+ }
682
+ return { ...prev, selectedItems: newSelected };
683
+ });
684
+ return;
685
+ }
686
+ // Tab键自动补全 (只在normal模式)
687
+ if (mode === 'normal' && key.tab) {
688
+ const matches = getMatches();
689
+ if (matches.length > 0) {
690
+ setInput(matches[selectedIndex].name + ' ');
691
+ setSelectedIndex(0);
692
+ return;
693
+ }
694
+ }
695
+ // 处理确认对话框
696
+ if (confirmState) {
697
+ if (inputChar === 'y' || inputChar === 'Y') {
698
+ confirmState.onConfirm();
699
+ setConfirmState(null);
700
+ setInput('');
701
+ return;
702
+ }
703
+ else if (inputChar === 'n' || inputChar === 'N' || key.return) {
704
+ confirmState.onCancel();
705
+ setConfirmState(null);
706
+ setInput('');
707
+ return;
708
+ }
709
+ }
710
+ // 普通字符输入 - 仅在非 TextInput 模式(menu/select)中处理
711
+ // TextInput 模式(normal/conversation/wizard/form)由 TextInput onChange 处理
712
+ if (inputChar && (mode === 'menu' || mode === 'select')) {
713
+ setInput(prev => prev + inputChar);
714
+ }
715
+ });
716
+ // File watching for team config directory changes
717
+ useEffect(() => {
718
+ // Ensure directory exists before setting up watcher
719
+ // This handles fresh installs where directory doesn't exist yet
720
+ ensureTeamConfigDir();
721
+ const configDir = getTeamConfigDir();
722
+ // Watch for file changes in team-config directory
723
+ const watcher = watch(configDir, { recursive: false }, (eventType, filename) => {
724
+ if (filename && filename.endsWith('.json')) {
725
+ // Show notification when config files change
726
+ appendOutput(_jsx(Text, { color: "cyan", dimColor: true, children: "Team config changed. Type /team list to refresh." }, `file-change-${Date.now()}`));
727
+ }
728
+ });
729
+ // Cleanup watcher on component unmount
730
+ return () => {
731
+ watcher.close();
732
+ };
733
+ }, []); // Empty dependency array: setup once on mount
734
+ /**
735
+ * Handle conversation input. Returns true if message was processed successfully,
736
+ * false if validation failed and the input should be preserved for user to edit.
737
+ */
738
+ const handleConversationInput = (message) => {
739
+ if (!message)
740
+ return true; // Empty message is "successful" (nothing to do)
741
+ // 检查是否是退出对话命令
742
+ if (message === '/end' || message === '/exit' || message === '/quit') {
743
+ if (activeCoordinator) {
744
+ // stop() is async, fire-and-forget for UI responsiveness
745
+ activeCoordinator.stop().catch(() => {
746
+ // Errors already logged in method
747
+ });
748
+ }
749
+ setMode('normal');
750
+ setActiveCoordinator(null);
751
+ setActiveTeam(null);
752
+ setQueueState(null); // Clear queue display on exit
753
+ appendOutput(_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: "green", dimColor: true, children: '─'.repeat(40) }), _jsx(Text, { bold: true, color: "green", children: "Conversation Ended" }), _jsx(Text, { dimColor: true, children: "You are back in normal mode. Type /help for commands." }), _jsx(Text, { color: "green", dimColor: true, children: '─'.repeat(40) })] }, `conv-end-${getNextKey()}`));
754
+ return true;
755
+ }
756
+ // 通过coordinator继续对话
757
+ // 检查是否有role在等待输入
758
+ if (activeCoordinator && activeTeam) {
759
+ // Validate [TEAM_TASK] format if present
760
+ // Correct format: [TEAM_TASK:description] - must have colon and closing bracket
761
+ // MECE: if message contains "TEAM_TASK", it must match the correct pattern exactly
762
+ const hasTeamTaskKeyword = /\bTEAM_TASK\b/i.test(message);
763
+ if (hasTeamTaskKeyword) {
764
+ const correctFormat = /\[TEAM_TASK:\s*[^\]]+\]/i;
765
+ if (!correctFormat.test(message)) {
766
+ appendOutput(_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: "red", children: "\u2717 Invalid [TEAM_TASK] format. Message not sent." }), _jsxs(Text, { dimColor: true, children: ["Correct format: ", _jsx(Text, { color: "green", children: "[TEAM_TASK:your task description]" })] }), _jsx(Text, { dimColor: true, children: "Example: [TEAM_TASK:Review the PRD document] [NEXT:max]" })] }, `task-hint-${getNextKey()}`));
767
+ return false; // Validation failed - preserve input for user to edit
768
+ }
769
+ }
770
+ const waitingRoleId = activeCoordinator.getWaitingForMemberId();
771
+ // Check if single human - allow sending without explicit waitingRoleId
772
+ const humans = activeTeam.members.filter(m => m.type === 'human');
773
+ const isSingleHuman = humans.length === 1;
774
+ if (waitingRoleId || isSingleHuman) {
775
+ const displayRole = waitingRoleId
776
+ ? activeTeam.members.find(r => r.id === waitingRoleId)
777
+ : humans[0];
778
+ appendOutput(_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { color: "green", children: ["[", displayRole?.displayName || 'You', "]:"] }), _jsx(Text, { children: message }), _jsx(Text, { dimColor: true, children: '─'.repeat(40) })] }, `user-msg-${getNextKey()}`));
779
+ // Use new sendMessage API
780
+ activeCoordinator.sendMessage(message).catch(err => {
781
+ appendOutput(_jsx(Text, { color: "red", children: String(err) }, `send-err-${getNextKey()}`));
782
+ });
783
+ return true;
784
+ }
785
+ else {
786
+ // Check if message has [FROM:xxx] to allow buzzing in
787
+ if (message.match(/\[FROM:/i)) {
788
+ // Allow buzzing in even if not waiting
789
+ activeCoordinator.sendMessage(message).catch(err => {
790
+ appendOutput(_jsx(Text, { color: "red", children: String(err) }, `send-err-${getNextKey()}`));
791
+ });
792
+ return true;
793
+ }
794
+ else {
795
+ appendOutput(_jsx(Text, { color: "yellow", children: "No team member is waiting for input right now. Wait for the coordinator to prompt you, or use [FROM:Name] to buzz in." }, `no-waiting-${getNextKey()}`));
796
+ return false; // Keep input so user can add [FROM:xxx]
797
+ }
798
+ }
799
+ }
800
+ return true;
801
+ };
802
+ const handleCommand = async (cmd) => {
803
+ if (!cmd)
804
+ return;
805
+ const parts = cmd.split(/\s+/);
806
+ const command = parts[0].toLowerCase();
807
+ const args = parts.slice(1);
808
+ // 添加用户输入到输出
809
+ appendOutput(_jsxs(Text, { color: "cyan", children: ["agent-chatter> ", cmd] }, `input-${getNextKey()}`));
810
+ switch (command) {
811
+ case '/help':
812
+ appendOutput(_jsx(HelpMessage, {}, `help-${getNextKey()}`));
813
+ break;
814
+ case '/status':
815
+ appendOutput(_jsx(Text, { dimColor: true, children: "Detecting AI CLI tools..." }, `status-msg-${getNextKey()}`));
816
+ const tools = await detectAllTools();
817
+ appendOutput(_jsx(StatusDisplay, { tools: tools }, `status-${getNextKey()}`));
818
+ break;
819
+ case '/list':
820
+ appendOutput(_jsx(Text, { dimColor: true, children: "Looking for configuration files..." }, `list-msg-${getNextKey()}`));
821
+ appendOutput(_jsx(ConfigList, { currentConfigPath: currentConfigPath }, `list-${getNextKey()}`));
822
+ break;
823
+ case '/team':
824
+ handleTeamCommand(args);
825
+ break;
826
+ case '/agents':
827
+ handleAgentsCommand(args);
828
+ break;
829
+ case '/clear':
830
+ setOutput([]);
831
+ break;
832
+ case '/exit':
833
+ case '/quit':
834
+ setIsExiting(true);
835
+ appendOutput(_jsx(Text, { color: "cyan", children: "Goodbye! \uD83D\uDC4B" }, "goodbye"));
836
+ setTimeout(() => exit(), 100);
837
+ break;
838
+ default:
839
+ // Check if this looks like a conversation message (not starting with /)
840
+ if (!command.startsWith('/')) {
841
+ appendOutput(_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: "yellow", children: "\u26A0 You are not in conversation mode." }), _jsx(Text, { dimColor: true, children: "To start a conversation:" }), _jsxs(Text, { dimColor: true, children: [" 1. Use ", _jsx(Text, { color: "green", children: "/team list" }), " to see available teams"] }), _jsxs(Text, { dimColor: true, children: [" 2. Use ", _jsx(Text, { color: "green", children: "/team deploy <filename>" }), " to deploy a team"] }), _jsx(Text, { dimColor: true, children: " 3. Then type your message to start talking" })] }, `not-deployed-${getNextKey()}`));
842
+ }
843
+ else {
844
+ appendOutput(_jsxs(Text, { color: "yellow", children: ["Unknown command: ", command] }, `unknown-${getNextKey()}`));
845
+ appendOutput(_jsx(Text, { dimColor: true, children: "Type /help for available commands." }, `help-hint-${getNextKey()}`));
846
+ }
847
+ }
848
+ };
849
+ const handleTeamCommand = (args) => {
850
+ if (args.length === 0) {
851
+ appendOutput(_jsx(TeamMenuHelp, {}, `team-help-${getNextKey()}`));
852
+ return;
853
+ }
854
+ const subcommand = args[0].toLowerCase();
855
+ const subargs = args.slice(1);
856
+ switch (subcommand) {
857
+ case 'create':
858
+ startTeamCreationWizard();
859
+ break;
860
+ case 'list':
861
+ appendOutput(_jsx(TeamList, {}, `team-list-${getNextKey()}`));
862
+ break;
863
+ case 'deploy':
864
+ if (subargs.length === 0) {
865
+ appendOutput(_jsx(Text, { color: "yellow", children: "Usage: /team deploy <filename>" }, `team-deploy-usage-${getNextKey()}`));
866
+ appendOutput(_jsx(Text, { dimColor: true, children: "Use /team list to see available configurations" }, `team-deploy-hint-${getNextKey()}`));
867
+ }
868
+ else {
869
+ const config = loadConfig(subargs[0]);
870
+ if (config) {
871
+ // Check for existing session and prompt for restore
872
+ checkAndPromptRestore(config);
873
+ }
874
+ }
875
+ break;
876
+ case 'edit':
877
+ if (subargs.length === 0) {
878
+ appendOutput(_jsx(Text, { color: "yellow", children: "Usage: /team edit <filename>" }, `team-edit-usage-${getNextKey()}`));
879
+ }
880
+ else {
881
+ startTeamEditMenu(subargs[0]);
882
+ }
883
+ break;
884
+ case 'delete':
885
+ if (subargs.length === 0) {
886
+ appendOutput(_jsx(Text, { color: "yellow", children: "Usage: /team delete <filename>" }, `team-delete-usage-${getNextKey()}`));
887
+ }
888
+ else {
889
+ deleteTeamConfiguration(subargs[0]);
890
+ }
891
+ break;
892
+ default:
893
+ appendOutput(_jsxs(Text, { color: "yellow", children: ["Unknown team subcommand: ", subcommand] }, `team-unknown-${getNextKey()}`));
894
+ appendOutput(_jsx(Text, { dimColor: true, children: "Type /team for available commands." }, `team-hint-${getNextKey()}`));
895
+ }
896
+ };
897
+ const handleAgentsCommand = (args) => {
898
+ // If no arguments, enter interactive agents menu
899
+ if (args.length === 0) {
900
+ setMode('agentsMenu');
901
+ setInput('');
902
+ setSelectedIndex(0);
903
+ return;
904
+ }
905
+ const subcommand = args[0].toLowerCase();
906
+ // If user provides a subcommand, suggest using CLI
907
+ appendOutput(_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: "yellow", children: "Tip: Type /agents to enter interactive menu" }), _jsxs(Text, { dimColor: true, children: ["Or use the CLI: agent-chatter agents ", subcommand, " ", args.slice(1).join(' ')] })] }, `agents-cli-hint-${getNextKey()}`));
908
+ };
909
+ // ============================================================================
910
+ // Wizard Input Handlers
911
+ // ============================================================================
912
+ const handleWizardInput = (value) => {
913
+ if (!wizardState)
914
+ return;
915
+ const { step, data } = wizardState;
916
+ if (step === 1) {
917
+ handleWizardStep1Input(value);
918
+ }
919
+ else if (step === 2) {
920
+ // Step 2: Detect Agents (在 Phase 2 实现)
921
+ appendOutput(_jsx(Text, { color: "yellow", children: "Step 2 interaction not yet implemented" }, `wizard-step2-${getNextKey()}`));
922
+ }
923
+ else if (step === 3) {
924
+ // Step 3: Configure Members (在 Phase 2 实现)
925
+ appendOutput(_jsx(Text, { color: "yellow", children: "Step 3 interaction not yet implemented" }, `wizard-step3-${getNextKey()}`));
926
+ }
927
+ else if (step === 4) {
928
+ // Step 4: Team Settings (在 Phase 2 实现)
929
+ appendOutput(_jsx(Text, { color: "yellow", children: "Step 4 interaction not yet implemented" }, `wizard-step4-${getNextKey()}`));
930
+ }
931
+ };
932
+ const renderWizardEvent = (event, key) => {
933
+ switch (event.type) {
934
+ case 'info':
935
+ return _jsx(Text, { color: "green", children: event.message }, `wizard-info-${key}`);
936
+ case 'prompt':
937
+ return _jsx(Text, { color: "cyan", children: event.message }, `wizard-prompt-${key}`);
938
+ case 'error':
939
+ return _jsx(Text, { color: "red", children: event.message }, `wizard-error-${key}`);
940
+ case 'divider':
941
+ return _jsx(Text, { color: "green", dimColor: true, children: (event.char || '─').repeat(40) }, `wizard-divider-${key}`);
942
+ default:
943
+ return null;
944
+ }
945
+ };
946
+ const handleWizardStep1Input = (value) => {
947
+ if (!wizardState) {
948
+ return;
949
+ }
950
+ const result = processWizardStep1Input(wizardState.data, value);
951
+ setWizardState(prev => prev ? {
952
+ ...prev,
953
+ data: result.data,
954
+ step: result.stepCompleted ? 2 : prev.step
955
+ } : null);
956
+ const nodes = result.events
957
+ .map(event => renderWizardEvent(event, getNextKey()))
958
+ .filter(Boolean);
959
+ if (nodes.length > 0) {
960
+ nodes.forEach(node => appendOutput(node));
961
+ }
962
+ };
963
+ const handleFormSubmit = () => {
964
+ if (!formState)
965
+ return;
966
+ const currentField = formState.fields[formState.currentFieldIndex];
967
+ if (!currentField)
968
+ return;
969
+ // 验证
970
+ if (currentField.validation) {
971
+ const error = currentField.validation(input);
972
+ if (error) {
973
+ setFormState(prev => prev ? {
974
+ ...prev,
975
+ errors: { ...prev.errors, [currentField.name]: error }
976
+ } : null);
977
+ return;
978
+ }
979
+ }
980
+ // 保存值并移动到下一个字段
981
+ const newValues = { ...formState.values, [currentField.name]: input };
982
+ const nextIndex = formState.currentFieldIndex + 1;
983
+ if (nextIndex < formState.fields.length) {
984
+ // 还有更多字段
985
+ setFormState(prev => prev ? {
986
+ ...prev,
987
+ currentFieldIndex: nextIndex,
988
+ values: newValues,
989
+ errors: { ...prev.errors, [currentField.name]: undefined }
990
+ } : null);
991
+ }
992
+ else {
993
+ // 表单完成
994
+ appendOutput(_jsx(Text, { color: "green", children: "Form completed!" }, `form-complete-${getNextKey()}`));
995
+ setMode('normal');
996
+ setFormState(null);
997
+ }
998
+ };
999
+ const handleSelectConfirm = () => {
1000
+ if (!selectState)
1001
+ return;
1002
+ if (selectState.multiSelect) {
1003
+ // 多选:返回所有选中项
1004
+ const selected = Array.from(selectState.selectedItems);
1005
+ selectState.onComplete(selected);
1006
+ }
1007
+ else {
1008
+ // 单选:返回当前选中项
1009
+ const selected = selectState.options[selectedIndex];
1010
+ selectState.onComplete(selected);
1011
+ }
1012
+ setSelectState(null);
1013
+ setMode('normal');
1014
+ };
1015
+ const handleMenuSelect = () => {
1016
+ if (!menuState || !menuItems[selectedIndex])
1017
+ return;
1018
+ const selected = menuItems[selectedIndex];
1019
+ switch (selected.value) {
1020
+ case 'save':
1021
+ // 保存并退出
1022
+ appendOutput(_jsx(Text, { color: "green", children: "Changes saved!" }, `menu-save-${getNextKey()}`));
1023
+ setMode('normal');
1024
+ setMenuState(null);
1025
+ setMenuItems([]);
1026
+ break;
1027
+ case 'cancel':
1028
+ // 取消并退出
1029
+ appendOutput(_jsx(Text, { color: "yellow", children: "Changes discarded" }, `menu-cancel-${getNextKey()}`));
1030
+ setMode('normal');
1031
+ setMenuState(null);
1032
+ setMenuItems([]);
1033
+ break;
1034
+ default:
1035
+ appendOutput(_jsxs(Text, { color: "yellow", children: ["Menu item \"", selected.label, "\" not yet implemented"] }, `menu-todo-${getNextKey()}`));
1036
+ }
1037
+ };
1038
+ const startTeamCreationWizard = () => {
1039
+ appendOutput(_jsx(Text, { color: "cyan", dimColor: true, children: '═'.repeat(40) }, `wizard-start-${getNextKey()}`));
1040
+ appendOutput(_jsx(Text, { bold: true, color: "cyan", children: "Team Creation Wizard" }, `wizard-title-${getNextKey()}`));
1041
+ appendOutput(_jsx(Text, { dimColor: true, children: "Step 1/4: Team Structure" }, `wizard-subtitle-${getNextKey()}`));
1042
+ appendOutput(_jsx(Text, { color: "cyan", dimColor: true, children: '─'.repeat(40) }, `wizard-divider-${getNextKey()}`));
1043
+ appendOutput(_jsx(Text, { color: "cyan", children: "Enter team name:" }, `wizard-prompt-name-${getNextKey()}`));
1044
+ // 初始化向导状态 - 从第一个字段开始
1045
+ setWizardState({
1046
+ step: 1,
1047
+ totalSteps: 4,
1048
+ data: {}
1049
+ });
1050
+ setMode('wizard');
1051
+ setInput('');
1052
+ setSelectedIndex(0);
1053
+ };
1054
+ const startTeamEditMenu = (filename) => {
1055
+ try {
1056
+ const resolution = resolveTeamConfigPath(filename);
1057
+ if (!resolution.exists) {
1058
+ appendOutput(_jsx(Text, { color: "red", children: formatMissingConfigError(filename, resolution) }, `edit-notfound-${getNextKey()}`));
1059
+ return;
1060
+ }
1061
+ if (resolution.warning) {
1062
+ appendOutput(_jsx(Text, { color: "yellow", children: resolution.warning }, `edit-warning-${getNextKey()}`));
1063
+ }
1064
+ const content = fs.readFileSync(resolution.path, 'utf-8');
1065
+ const config = JSON.parse(content);
1066
+ appendOutput(_jsxs(Text, { color: "cyan", children: ["Opening team editor for: ", filename] }, `edit-start-${getNextKey()}`));
1067
+ // 初始化菜单状态
1068
+ setMenuState({
1069
+ configPath: filename,
1070
+ config,
1071
+ selectedIndex: 0,
1072
+ editing: false,
1073
+ changes: {}
1074
+ });
1075
+ setMenuItems([
1076
+ { label: 'Edit team information', value: 'edit_info' },
1077
+ { label: 'Add new member', value: 'add_member' },
1078
+ ...(config.team?.members || []).map((member, idx) => ({
1079
+ label: `Edit member: ${member.displayName || member.name}`,
1080
+ value: `edit_member_${idx}`
1081
+ })),
1082
+ { label: 'Remove member', value: 'remove_member' },
1083
+ { label: 'Change member order', value: 'change_order' },
1084
+ { label: 'Save and exit', value: 'save' },
1085
+ { label: 'Exit without saving', value: 'cancel' }
1086
+ ]);
1087
+ setMode('menu');
1088
+ setInput('');
1089
+ setSelectedIndex(0);
1090
+ }
1091
+ catch (error) {
1092
+ appendOutput(_jsxs(Text, { color: "red", children: ["Error: Failed to load configuration: ", String(error)] }, `edit-err-${getNextKey()}`));
1093
+ }
1094
+ };
1095
+ const listTeamConfigurations = () => {
1096
+ const configDir = getTeamConfigDir();
1097
+ // Check if directory exists, if not create it
1098
+ if (!fs.existsSync(configDir)) {
1099
+ appendOutput(_jsx(Text, { color: "yellow", children: "No team configuration files found" }, `list-empty-${getNextKey()}`));
1100
+ return;
1101
+ }
1102
+ const files = fs.readdirSync(configDir).filter(f => f.endsWith('-config.json') || f === 'agent-chatter-config.json');
1103
+ if (files.length === 0) {
1104
+ appendOutput(_jsx(Text, { color: "yellow", children: "No team configuration files found" }, `list-empty-${getNextKey()}`));
1105
+ return;
1106
+ }
1107
+ appendOutput(_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Available Team Configurations:" }), files.map(file => {
1108
+ try {
1109
+ const fullPath = path.join(configDir, file);
1110
+ const content = fs.readFileSync(fullPath, 'utf-8');
1111
+ const config = JSON.parse(content);
1112
+ const isActive = currentConfigPath && file === path.basename(currentConfigPath);
1113
+ return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: isActive ? 'green' : 'gray', children: [isActive ? '●' : '○', " "] }), _jsx(Text, { bold: true, children: file })] }), _jsx(Box, { marginLeft: 2, children: _jsxs(Text, { dimColor: true, children: ["Team: ", config.team?.name || 'Unknown'] }) }), _jsx(Box, { marginLeft: 2, children: _jsxs(Text, { dimColor: true, children: ["Members: ", config.team?.members?.length || 0] }) })] }, file));
1114
+ }
1115
+ catch {
1116
+ return (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { dimColor: true, children: ["\u25CB ", file, " (invalid)"] }) }, file));
1117
+ }
1118
+ })] }, `list-${getNextKey()}`));
1119
+ };
1120
+ const showTeamConfiguration = (filename) => {
1121
+ try {
1122
+ const resolution = resolveTeamConfigPath(filename);
1123
+ if (!resolution.exists) {
1124
+ appendOutput(_jsx(Text, { color: "red", children: formatMissingConfigError(filename, resolution) }, `show-notfound-${getNextKey()}`));
1125
+ return;
1126
+ }
1127
+ if (resolution.warning) {
1128
+ appendOutput(_jsx(Text, { color: "yellow", children: resolution.warning }, `show-warning-${getNextKey()}`));
1129
+ }
1130
+ const content = fs.readFileSync(resolution.path, 'utf-8');
1131
+ const config = JSON.parse(content);
1132
+ appendOutput(_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsxs(Text, { bold: true, color: "cyan", children: ["Team: ", config.team?.displayName || config.team?.name || 'Unknown'] }), _jsxs(Text, { dimColor: true, children: ["File: ", filename] }), _jsx(Text, { dimColor: true, children: '─'.repeat(40) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { children: ["Description: ", config.team?.description || 'N/A'] }), config.team?.instructionFile && (_jsxs(Text, { children: ["Instruction File: ", config.team.instructionFile] })), _jsxs(Text, { children: ["Max Rounds: ", config.maxRounds || 'Unlimited'] })] }), config.team?.roleDefinitions && config.team.roleDefinitions.length > 0 && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Role Definitions:" }), config.team.roleDefinitions.map((role, idx) => (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { children: ["\u2022 ", role.name, ": ", role.description || 'N/A'] }) }, idx)))] })), config.team?.members && config.team.members.length > 0 && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { bold: true, children: ["Members (", config.team.members.length, "):"] }), config.team.members.map((member, idx) => (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: [_jsxs(Text, { children: [idx + 1, ". ", _jsx(Text, { bold: true, children: member.displayName }), " (", member.type, ") - Role: ", member.role] }), member.baseDir && (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { dimColor: true, children: ["Base Dir: ", member.baseDir] }) }))] }, idx)))] }))] }, `show-${getNextKey()}`));
1133
+ }
1134
+ catch (error) {
1135
+ appendOutput(_jsxs(Text, { color: "red", children: ["Error: Failed to read configuration: ", String(error)] }, `show-err-${getNextKey()}`));
1136
+ }
1137
+ };
1138
+ const deleteTeamConfiguration = (filename) => {
1139
+ // 安全检查
1140
+ if (currentConfigPath && filename === path.basename(currentConfigPath)) {
1141
+ appendOutput(_jsx(Text, { color: "red", children: "Error: Cannot delete currently loaded configuration" }, `delete-active-${getNextKey()}`));
1142
+ return;
1143
+ }
1144
+ if (mode === 'conversation') {
1145
+ appendOutput(_jsx(Text, { color: "red", children: "Error: Cannot delete configuration with active conversation" }, `delete-conv-${getNextKey()}`));
1146
+ return;
1147
+ }
1148
+ const resolution = resolveTeamConfigPath(filename);
1149
+ if (!resolution.exists) {
1150
+ appendOutput(_jsx(Text, { color: "red", children: formatMissingConfigError(filename, resolution) }, `delete-notfound-${getNextKey()}`));
1151
+ return;
1152
+ }
1153
+ if (resolution.warning) {
1154
+ appendOutput(_jsx(Text, { color: "yellow", children: resolution.warning }, `delete-warning-${getNextKey()}`));
1155
+ }
1156
+ // 显示确认对话框
1157
+ appendOutput(_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: "yellow", children: "\u26A0 Delete Team Configuration" }), _jsx(Text, { dimColor: true, children: '─'.repeat(40) }), _jsxs(Text, { children: ["File: ", filename] }), _jsx(Text, { color: "red", children: "This will permanently delete this configuration file." }), _jsx(Text, { color: "red", children: "This action cannot be undone." }), _jsx(Text, { dimColor: true, children: '─'.repeat(40) }), _jsx(Text, { children: "Confirm deletion? [y/N]" })] }, `delete-confirm-${getNextKey()}`));
1158
+ setConfirmState({
1159
+ message: `Delete ${filename}?`,
1160
+ onConfirm: () => {
1161
+ try {
1162
+ fs.unlinkSync(resolution.path);
1163
+ appendOutput(_jsxs(Text, { color: "green", children: ["\u2713 Team configuration deleted: ", filename] }, `delete-success-${getNextKey()}`));
1164
+ }
1165
+ catch (error) {
1166
+ appendOutput(_jsxs(Text, { color: "red", children: ["Error: Failed to delete configuration: ", String(error)] }, `delete-err-${getNextKey()}`));
1167
+ }
1168
+ },
1169
+ onCancel: () => {
1170
+ appendOutput(_jsx(Text, { color: "yellow", children: "Deletion cancelled" }, `delete-cancel-${getNextKey()}`));
1171
+ }
1172
+ });
1173
+ };
1174
+ const loadConfig = (filePath) => {
1175
+ try {
1176
+ const resolution = resolveTeamConfigPath(filePath);
1177
+ if (!resolution.exists) {
1178
+ appendOutput(_jsx(Text, { color: "red", children: formatMissingConfigError(filePath, resolution) }, `config-notfound-${getNextKey()}`));
1179
+ return null;
1180
+ }
1181
+ if (resolution.warning) {
1182
+ appendOutput(_jsx(Text, { color: "yellow", children: resolution.warning }, `config-warning-${getNextKey()}`));
1183
+ }
1184
+ const content = fs.readFileSync(resolution.path, 'utf-8');
1185
+ const config = JSON.parse(content);
1186
+ // Apply conversation config defaults
1187
+ if (!config.conversation) {
1188
+ config.conversation = {};
1189
+ }
1190
+ if (config.conversation.maxAgentResponseTime === undefined) {
1191
+ config.conversation.maxAgentResponseTime = 1800000; // 30 minutes
1192
+ }
1193
+ if (config.conversation.showThinkingTimer === undefined) {
1194
+ config.conversation.showThinkingTimer = true;
1195
+ }
1196
+ if (config.conversation.allowEscCancel === undefined) {
1197
+ config.conversation.allowEscCancel = true;
1198
+ }
1199
+ setCurrentConfig(config);
1200
+ setCurrentConfigPath(filePath);
1201
+ appendOutput(_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "green", children: ["\u2713 Configuration loaded: ", _jsx(Text, { bold: true, children: filePath })] }), _jsxs(Text, { dimColor: true, children: [" Team: ", config.team?.name || 'Unknown'] }), _jsxs(Text, { dimColor: true, children: [" Agents: ", config.agents?.length || 0] })] }, `config-loaded-${getNextKey()}`));
1202
+ return config;
1203
+ }
1204
+ catch (error) {
1205
+ appendOutput(_jsxs(Text, { color: "red", children: ["Error: Failed to load configuration: ", String(error)] }, `config-err-${getNextKey()}`));
1206
+ return null;
1207
+ }
1208
+ };
1209
+ const initializeAndDeployTeam = async (config) => {
1210
+ try {
1211
+ appendOutput(_jsx(Text, { dimColor: true, children: "Initializing services..." }, `init-${getNextKey()}`));
1212
+ // LLD-05: Split config into Core config and UI preferences
1213
+ const { coreConfig, uiPrefs: extractedUiPrefs } = splitConfig(config);
1214
+ setUiPrefs(extractedUiPrefs);
1215
+ // CLI layer provides concrete implementations
1216
+ const executionEnv = new LocalExecutionEnvironment();
1217
+ const adapterFactory = new AdapterFactory(executionEnv);
1218
+ const { coordinator, team, messageRouter, eventEmitter, verificationResults } = await initializeServices(coreConfig, {
1219
+ logger: uiLogger,
1220
+ executionEnv,
1221
+ adapterFactory,
1222
+ onMessage: (message) => {
1223
+ // AI 文本已经通过流式事件显示,这里不再重复
1224
+ // Human 消息已经在 handleConversationInput 中显示,这里也不再重复
1225
+ // 只保留 system 消息(如果有的话)
1226
+ if (message.speaker.type === 'ai' || message.speaker.type === 'human') {
1227
+ return;
1228
+ }
1229
+ const timestamp = new Date(message.timestamp).toLocaleTimeString();
1230
+ appendOutput(_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { color: "yellow", children: ["[", timestamp, "] ", message.speaker.displayName, ":"] }), _jsx(Text, { children: message.content }), _jsx(Text, { dimColor: true, children: '─'.repeat(60) })] }, `msg-${getNextKey()}`));
1231
+ },
1232
+ onStatusChange: (status) => {
1233
+ appendOutput(_jsxs(Text, { dimColor: true, children: ["[Status] ", status] }, `status-${getNextKey()}`));
1234
+ },
1235
+ onAgentStarted: (member) => {
1236
+ flushPendingNow();
1237
+ clearTodoList(); // Clear previous agent's todo list on agent switch
1238
+ const color = member.themeColor ?? 'white';
1239
+ const displayLabel = member.displayRole
1240
+ ? `${member.displayName} (${member.displayRole})`
1241
+ : member.displayName;
1242
+ appendOutput(_jsxs(Text, { backgroundColor: color, color: "black", children: ["\u2192 ", displayLabel, " started..."] }, `agent-start-${getNextKey()}`));
1243
+ setExecutingAgent(member);
1244
+ },
1245
+ onAgentCompleted: (member) => {
1246
+ flushPendingNow();
1247
+ // NOTE: Do NOT clearTodoList here - per design, todo list stays visible
1248
+ // after turn completes until next agent starts or new todo arrives
1249
+ const color = member.themeColor ?? 'white';
1250
+ const displayLabel = member.displayRole
1251
+ ? `${member.displayName} (${member.displayRole})`
1252
+ : member.displayName;
1253
+ appendOutput(_jsxs(Text, { backgroundColor: color, color: "black", children: ["\u2713 ", displayLabel, " completed"] }, `agent-done-${getNextKey()}`));
1254
+ appendOutput(_jsx(Text, { children: ' ' }, `agent-done-spacer-${getNextKey()}`));
1255
+ setExecutingAgent(null);
1256
+ },
1257
+ onQueueUpdate: (event) => {
1258
+ setQueueState(event);
1259
+ },
1260
+ onPartialResolveFailure: (skipped, available) => {
1261
+ appendOutput(_jsxs(Text, { color: "yellow", children: ["\u26A0\uFE0F Skipped unknown members: ", skipped.join(', '), '\n', " Available: ", available.join(', ')] }, `partial-fail-${getNextKey()}`));
1262
+ },
1263
+ onUnresolvedAddressees: (addressees, _message) => {
1264
+ const availableNames = team.members.map(m => m.name);
1265
+ appendOutput(_jsxs(Text, { color: "red", children: ["\u274C Cannot resolve: ", addressees.join(', '), '\n', " Available: ", availableNames.join(', ')] }, `unresolved-${getNextKey()}`));
1266
+ }
1267
+ });
1268
+ attachEventEmitter(eventEmitter);
1269
+ // Display verification results from Core
1270
+ formatVerificationResults(verificationResults);
1271
+ if (!team.members.length) {
1272
+ throw new Error('Team has no members configured. Please update the configuration file.');
1273
+ }
1274
+ setActiveCoordinator(coordinator);
1275
+ setActiveTeam(team);
1276
+ setMode('conversation');
1277
+ // Set team (async for potential session restore in the future)
1278
+ await coordinator.setTeam(team);
1279
+ // If only one human member, auto-set as waiting
1280
+ const humans = team.members.filter(m => m.type === 'human');
1281
+ if (humans.length === 1) {
1282
+ coordinator.setWaitingForMemberId(humans[0].id);
1283
+ }
1284
+ appendOutput(_jsxs(Text, { color: "green", children: ["\u2713 Team \"", team.name, "\" deployed successfully"] }, `deploy-success-${getNextKey()}`));
1285
+ appendOutput(_jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Text, { children: ["Team Name: ", team.displayName ?? team.name] }), team.description && _jsxs(Text, { children: ["Team Description: ", team.description] }), _jsx(Text, { children: "Members:" }), team.members.map((m, idx) => {
1286
+ const bgColor = m.themeColor ?? 'white';
1287
+ return (_jsxs(Text, { children: [" ", _jsxs(Text, { backgroundColor: bgColor, color: "black", children: [m.displayName, m.displayRole ? ` (${m.displayRole})` : ''] })] }, `member-${idx}`));
1288
+ })] }, `team-info-${getNextKey()}`));
1289
+ appendOutput(_jsx(Text, { dimColor: true, children: "Type your message below to continue. Use [NEXT:member_name] to assign the next speaker, use [TEAM_TASK: your task] to post your task to the team." }, `deploy-hint-${getNextKey()}`));
1290
+ // Force a re-render after Static updates in alternateBuffer mode
1291
+ // This ensures the input prompt is visible after deploy
1292
+ setTimeout(() => setInput(prev => prev), 50);
1293
+ return coordinator;
1294
+ }
1295
+ catch (error) {
1296
+ const errMsg = error instanceof Error ? error.message : String(error);
1297
+ appendOutput(_jsxs(Text, { color: "red", children: ["\u2717 ", errMsg] }, `deploy-err-${getNextKey()}`));
1298
+ return null;
1299
+ }
1300
+ };
1301
+ /**
1302
+ * Check for existing session and show restore prompt if found
1303
+ */
1304
+ const checkAndPromptRestore = async (config) => {
1305
+ try {
1306
+ appendOutput(_jsx(Text, { dimColor: true, children: "Initializing services..." }, `init-${getNextKey()}`));
1307
+ // LLD-05: Split config into Core config and UI preferences
1308
+ const { coreConfig, uiPrefs: extractedUiPrefs } = splitConfig(config);
1309
+ setUiPrefs(extractedUiPrefs);
1310
+ // CLI layer provides concrete implementations
1311
+ const executionEnv = new LocalExecutionEnvironment();
1312
+ const adapterFactory = new AdapterFactory(executionEnv);
1313
+ const { coordinator, team, messageRouter, eventEmitter, verificationResults } = await initializeServices(coreConfig, {
1314
+ logger: uiLogger,
1315
+ executionEnv,
1316
+ adapterFactory,
1317
+ onMessage: (message) => {
1318
+ if (message.speaker.type === 'ai' || message.speaker.type === 'human') {
1319
+ return;
1320
+ }
1321
+ const timestamp = new Date(message.timestamp).toLocaleTimeString();
1322
+ appendOutput(_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { color: "yellow", children: ["[", timestamp, "] ", message.speaker.displayName, ":"] }), _jsx(Text, { children: message.content }), _jsx(Text, { dimColor: true, children: '─'.repeat(60) })] }, `msg-${getNextKey()}`));
1323
+ },
1324
+ onStatusChange: (status) => {
1325
+ appendOutput(_jsxs(Text, { dimColor: true, children: ["[Status] ", status] }, `status-${getNextKey()}`));
1326
+ },
1327
+ onAgentStarted: (member) => {
1328
+ flushPendingNow();
1329
+ clearTodoList();
1330
+ const color = member.themeColor ?? 'white';
1331
+ const displayLabel = member.displayRole
1332
+ ? `${member.displayName} (${member.displayRole})`
1333
+ : member.displayName;
1334
+ appendOutput(_jsxs(Text, { backgroundColor: color, color: "black", children: ["\u2192 ", displayLabel, " started..."] }, `agent-start-${getNextKey()}`));
1335
+ setExecutingAgent(member);
1336
+ },
1337
+ onAgentCompleted: (member) => {
1338
+ flushPendingNow();
1339
+ const color = member.themeColor ?? 'white';
1340
+ const displayLabel = member.displayRole
1341
+ ? `${member.displayName} (${member.displayRole})`
1342
+ : member.displayName;
1343
+ appendOutput(_jsxs(Text, { backgroundColor: color, color: "black", children: ["\u2713 ", displayLabel, " completed"] }, `agent-done-${getNextKey()}`));
1344
+ appendOutput(_jsx(Text, { children: ' ' }, `agent-done-spacer-${getNextKey()}`));
1345
+ setExecutingAgent(null);
1346
+ },
1347
+ onQueueUpdate: (event) => {
1348
+ setQueueState(event);
1349
+ },
1350
+ onPartialResolveFailure: (skipped, available) => {
1351
+ appendOutput(_jsxs(Text, { color: "yellow", children: ["\u26A0\uFE0F Skipped unknown members: ", skipped.join(', '), '\n', " Available: ", available.join(', ')] }, `partial-fail-${getNextKey()}`));
1352
+ },
1353
+ onUnresolvedAddressees: (addressees, _message) => {
1354
+ const availableNames = team.members.map(m => m.name);
1355
+ appendOutput(_jsxs(Text, { color: "red", children: ["\u274C Cannot resolve: ", addressees.join(', '), '\n', " Available: ", availableNames.join(', ')] }, `unresolved-${getNextKey()}`));
1356
+ }
1357
+ });
1358
+ // Display verification results from Core
1359
+ formatVerificationResults(verificationResults);
1360
+ if (!team.members.length) {
1361
+ throw new Error('Team has no members configured. Please update the configuration file.');
1362
+ }
1363
+ // Check for existing sessions
1364
+ const latestSession = await sessionStorage.getLatestSession(team.id);
1365
+ if (latestSession) {
1366
+ // Show restore prompt
1367
+ const summary = {
1368
+ sessionId: latestSession.sessionId,
1369
+ createdAt: latestSession.createdAt,
1370
+ updatedAt: latestSession.updatedAt,
1371
+ messageCount: latestSession.metadata.messageCount,
1372
+ summary: latestSession.metadata.summary,
1373
+ };
1374
+ setPendingRestore({
1375
+ team,
1376
+ config,
1377
+ session: summary,
1378
+ coordinator,
1379
+ eventEmitter,
1380
+ });
1381
+ setMode('restore-prompt');
1382
+ setCurrentConfig(config);
1383
+ }
1384
+ else {
1385
+ // No existing session, start fresh
1386
+ attachEventEmitter(eventEmitter);
1387
+ setActiveCoordinator(coordinator);
1388
+ setActiveTeam(team);
1389
+ setCurrentConfig(config);
1390
+ setMode('conversation');
1391
+ await coordinator.setTeam(team);
1392
+ const humans = team.members.filter(m => m.type === 'human');
1393
+ if (humans.length === 1) {
1394
+ coordinator.setWaitingForMemberId(humans[0].id);
1395
+ }
1396
+ appendOutput(_jsxs(Text, { color: "green", children: ["\u2713 Team \"", team.name, "\" deployed successfully"] }, `deploy-success-${getNextKey()}`));
1397
+ appendOutput(_jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Text, { children: ["Team Name: ", team.displayName ?? team.name] }), team.description && _jsxs(Text, { children: ["Team Description: ", team.description] }), _jsx(Text, { children: "Members:" }), team.members.map((m, idx) => {
1398
+ const bgColor = m.themeColor ?? 'white';
1399
+ return (_jsxs(Text, { children: [" ", _jsxs(Text, { backgroundColor: bgColor, color: "black", children: [m.displayName, m.displayRole ? ` (${m.displayRole})` : ''] })] }, `member-${idx}`));
1400
+ })] }, `team-info-${getNextKey()}`));
1401
+ appendOutput(_jsx(Text, { dimColor: true, children: "Type your message below to continue. Use [NEXT:member_name] to assign the next speaker, use [TEAM_TASK: your task] to post your task to the team." }, `deploy-hint-${getNextKey()}`));
1402
+ setTimeout(() => setInput(prev => prev), 50);
1403
+ }
1404
+ }
1405
+ catch (error) {
1406
+ const errMsg = error instanceof Error ? error.message : String(error);
1407
+ appendOutput(_jsxs(Text, { color: "red", children: ["\u2717 ", errMsg] }, `deploy-err-${getNextKey()}`));
1408
+ }
1409
+ };
1410
+ /**
1411
+ * Handle user's restore choice (R or N)
1412
+ */
1413
+ const handleRestoreChoice = async (resume) => {
1414
+ if (!pendingRestore)
1415
+ return;
1416
+ const { team, config, session, coordinator, eventEmitter } = pendingRestore;
1417
+ try {
1418
+ attachEventEmitter(eventEmitter);
1419
+ setActiveCoordinator(coordinator);
1420
+ setActiveTeam(team);
1421
+ setMode('conversation');
1422
+ if (resume) {
1423
+ // Resume session
1424
+ await coordinator.setTeam(team, { resumeSessionId: session.sessionId });
1425
+ appendOutput(_jsxs(Text, { color: "green", children: ["\u2713 Restored session with ", session.messageCount, " messages"] }, `restore-success-${getNextKey()}`));
1426
+ }
1427
+ else {
1428
+ // Start new session
1429
+ await coordinator.setTeam(team);
1430
+ appendOutput(_jsxs(Text, { color: "green", children: ["\u2713 Team \"", team.name, "\" deployed successfully"] }, `deploy-success-${getNextKey()}`));
1431
+ }
1432
+ // Always show team info (for both resume and new session)
1433
+ appendOutput(_jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Text, { children: ["Team Name: ", team.displayName ?? team.name] }), team.description && _jsxs(Text, { children: ["Team Description: ", team.description] }), _jsx(Text, { children: "Members:" }), team.members.map((m, idx) => {
1434
+ const bgColor = m.themeColor ?? 'white';
1435
+ return (_jsxs(Text, { children: [" ", _jsxs(Text, { backgroundColor: bgColor, color: "black", children: [m.displayName, m.displayRole ? ` (${m.displayRole})` : ''] })] }, `member-${idx}`));
1436
+ })] }, `team-info-${getNextKey()}`));
1437
+ const humans = team.members.filter(m => m.type === 'human');
1438
+ if (humans.length === 1) {
1439
+ coordinator.setWaitingForMemberId(humans[0].id);
1440
+ }
1441
+ appendOutput(_jsx(Text, { dimColor: true, children: "Type your message below to continue. Use [NEXT:member_name] to assign the next speaker, use [TEAM_TASK: your task] to post your task to the team." }, `deploy-hint-${getNextKey()}`));
1442
+ setTimeout(() => setInput(prev => prev), 50);
1443
+ }
1444
+ catch (error) {
1445
+ const errMsg = error instanceof Error ? error.message : String(error);
1446
+ appendOutput(_jsxs(Text, { color: "red", children: ["\u2717 ", errMsg] }, `restore-err-${getNextKey()}`));
1447
+ setMode('normal');
1448
+ }
1449
+ finally {
1450
+ setPendingRestore(null);
1451
+ }
1452
+ };
1453
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items: output, children: (item, idx) => _jsx(Box, { children: item }, `output-${idx}`) }), mode === 'conversation' && queueState && !queueState.isEmpty && (_jsx(QueueDisplay, { items: queueState.items, executing: queueState.executing, visible: true })), mode === 'conversation' && activeTodoList && (_jsx(TodoListView, { todoList: activeTodoList })), mode === 'conversation' && executingAgent && uiPrefs.showThinkingTimer && (_jsx(ThinkingIndicator, { member: executingAgent, maxTimeoutMs: currentConfig?.conversation?.maxAgentResponseTime ?? 1800000, allowEscCancel: uiPrefs.allowEscCancel })), mode === 'wizard' && wizardState && (_jsx(WizardView, { wizardState: wizardState })), mode === 'menu' && menuState && (_jsx(MenuView, { menuState: menuState, menuItems: menuItems, selectedIndex: selectedIndex })), mode === 'form' && formState && (_jsx(FormView, { formState: formState })), mode === 'select' && selectState && (_jsx(SelectView, { title: selectState.title, options: selectState.options, selectedIndex: selectedIndex, multiSelect: selectState.multiSelect, selectedItems: selectState.selectedItems })), mode === 'restore-prompt' && pendingRestore && (_jsx(RestorePrompt, { session: pendingRestore.session, teamName: pendingRestore.team.name })), mode === 'agentsMenu' && (_jsx(AgentsMenu, { registryPath: registry, onClose: () => {
1454
+ setMode('normal');
1455
+ setInput('');
1456
+ }, onShowMessage: (message, color) => {
1457
+ appendOutput(_jsx(Text, { color: color || 'white', children: message }, `agents-msg-${keyCounter}`));
1458
+ setKeyCounter(prev => prev + 1);
1459
+ } })), !isExiting && (mode === 'normal' || mode === 'conversation' || mode === 'wizard' || mode === 'form') && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: mode === 'conversation' ? 'green' : 'cyan', dimColor: true, children: '─'.repeat(terminalWidth - 4) }), _jsxs(Box, { children: [mode === 'conversation' ? (_jsx(Text, { color: "green", bold: true, children: (() => {
1460
+ // Get waiting member's display name for the prompt
1461
+ if (activeCoordinator && activeTeam) {
1462
+ const waitingRoleId = activeCoordinator.getWaitingForMemberId();
1463
+ if (waitingRoleId) {
1464
+ const waitingMember = activeTeam.members.find(m => m.id === waitingRoleId);
1465
+ if (waitingMember) {
1466
+ return `${waitingMember.displayName}> `;
1467
+ }
1468
+ }
1469
+ // Fallback: if only one human, show their name
1470
+ const humans = activeTeam.members.filter(m => m.type === 'human');
1471
+ if (humans.length === 1) {
1472
+ return `${humans[0].displayName}> `;
1473
+ }
1474
+ }
1475
+ // Fallback to generic prompt if multiple humans or no team
1476
+ return 'you> ';
1477
+ })() })) : mode === 'wizard' ? (_jsx(Text, { color: "cyan", bold: true, children: "wizard> " })) : mode === 'form' ? (_jsx(Text, { color: "cyan", bold: true, children: "input> " })) : (_jsx(Text, { color: "cyan", children: "agent-chatter> " })), _jsx(TextInput, { value: input, onChange: setInput, onSubmit: handleInputSubmit, placeholder: " " })] }), _jsx(Text, { color: mode === 'conversation' ? 'green' : 'cyan', dimColor: true, children: '─'.repeat(terminalWidth - 4) })] })), !isExiting && mode === 'normal' && _jsx(CommandHints, { input: input, selectedIndex: selectedIndex })] }));
1478
+ }
1479
+ export function startReplInk(registryPath, options = {}) {
1480
+ render(_jsx(App, { registryPath: registryPath, debug: options.debug }), {
1481
+ // Don't use alternateBuffer - it prevents scrolling for long agent outputs
1482
+ // Trade-off: long input lines may cause duplicate prompts, but scrolling works
1483
+ incrementalRendering: true
1484
+ });
1485
+ }
1486
+ //# sourceMappingURL=ReplModeInk.js.map