centaurus-cli 3.1.2 → 3.1.4

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 (142) hide show
  1. package/dist/cli-adapter.js +689 -155
  2. package/dist/cli-adapter.js.map +1 -1
  3. package/dist/config/defaultConfig.js +1 -4
  4. package/dist/config/defaultConfig.js.map +1 -1
  5. package/dist/config/models.js +6 -0
  6. package/dist/config/models.js.map +1 -1
  7. package/dist/config/slash-commands.js +66 -2
  8. package/dist/config/slash-commands.js.map +1 -1
  9. package/dist/config/types.js +4 -4
  10. package/dist/config/types.js.map +1 -1
  11. package/dist/index.js +36 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/services/ai-context-injector.js +109 -0
  14. package/dist/services/ai-context-injector.js.map +1 -1
  15. package/dist/services/ai-service-client.js +3 -2
  16. package/dist/services/ai-service-client.js.map +1 -1
  17. package/dist/services/api-client.js.map +1 -1
  18. package/dist/services/background-task-manager.js +59 -0
  19. package/dist/services/background-task-manager.js.map +1 -1
  20. package/dist/services/local-chat-storage.js +2 -0
  21. package/dist/services/local-chat-storage.js.map +1 -1
  22. package/dist/services/skill-storage.js +141 -0
  23. package/dist/services/skill-storage.js.map +1 -0
  24. package/dist/services/sub-agent-manager.js +49 -8
  25. package/dist/services/sub-agent-manager.js.map +1 -1
  26. package/dist/services/warpify-detector.js +17 -5
  27. package/dist/services/warpify-detector.js.map +1 -1
  28. package/dist/tools/background-command.js +5 -2
  29. package/dist/tools/background-command.js.map +1 -1
  30. package/dist/tools/command.js +367 -109
  31. package/dist/tools/command.js.map +1 -1
  32. package/dist/tools/file-ops.js +23 -6
  33. package/dist/tools/file-ops.js.map +1 -1
  34. package/dist/tools/plan-mode.js +184 -336
  35. package/dist/tools/plan-mode.js.map +1 -1
  36. package/dist/tools/sub-agent.js +24 -5
  37. package/dist/tools/sub-agent.js.map +1 -1
  38. package/dist/tools/todo-list.js +157 -0
  39. package/dist/tools/todo-list.js.map +1 -0
  40. package/dist/types/skill.js +30 -0
  41. package/dist/types/skill.js.map +1 -0
  42. package/dist/ui/components/App.js +956 -162
  43. package/dist/ui/components/App.js.map +1 -1
  44. package/dist/ui/components/AuthScreen.js +3 -1
  45. package/dist/ui/components/AuthScreen.js.map +1 -1
  46. package/dist/ui/components/AuthWelcomeScreen.js +3 -1
  47. package/dist/ui/components/AuthWelcomeScreen.js.map +1 -1
  48. package/dist/ui/components/CodeBlock.js +3 -1
  49. package/dist/ui/components/CodeBlock.js.map +1 -1
  50. package/dist/ui/components/CompactShellPreview.js +44 -0
  51. package/dist/ui/components/CompactShellPreview.js.map +1 -0
  52. package/dist/ui/components/ConfigViewer.js +3 -1
  53. package/dist/ui/components/ConfigViewer.js.map +1 -1
  54. package/dist/ui/components/ConfirmPrompt.js +3 -1
  55. package/dist/ui/components/ConfirmPrompt.js.map +1 -1
  56. package/dist/ui/components/ConnectionStatusMessage.js +3 -1
  57. package/dist/ui/components/ConnectionStatusMessage.js.map +1 -1
  58. package/dist/ui/components/DetailedPlanReviewScreen.js +84 -74
  59. package/dist/ui/components/DetailedPlanReviewScreen.js.map +1 -1
  60. package/dist/ui/components/DiffViewer.js +6 -3
  61. package/dist/ui/components/DiffViewer.js.map +1 -1
  62. package/dist/ui/components/FileCreationPreview.js.map +1 -1
  63. package/dist/ui/components/FileTagAutocomplete.js +4 -2
  64. package/dist/ui/components/FileTagAutocomplete.js.map +1 -1
  65. package/dist/ui/components/InputBox.js +243 -40
  66. package/dist/ui/components/InputBox.js.map +1 -1
  67. package/dist/ui/components/InteractiveShell.js +5 -3
  68. package/dist/ui/components/InteractiveShell.js.map +1 -1
  69. package/dist/ui/components/KeyboardHelp.js +4 -1
  70. package/dist/ui/components/KeyboardHelp.js.map +1 -1
  71. package/dist/ui/components/LoadingIndicator.js +3 -1
  72. package/dist/ui/components/LoadingIndicator.js.map +1 -1
  73. package/dist/ui/components/MCPAddScreen.js +63 -13
  74. package/dist/ui/components/MCPAddScreen.js.map +1 -1
  75. package/dist/ui/components/MarkdownRenderer.js +3 -1
  76. package/dist/ui/components/MarkdownRenderer.js.map +1 -1
  77. package/dist/ui/components/MessageDisplay.js +9 -7
  78. package/dist/ui/components/MessageDisplay.js.map +1 -1
  79. package/dist/ui/components/ModelPicker.js +170 -0
  80. package/dist/ui/components/ModelPicker.js.map +1 -0
  81. package/dist/ui/components/MonitorModeAIPanel.js +3 -1
  82. package/dist/ui/components/MonitorModeAIPanel.js.map +1 -1
  83. package/dist/ui/components/PlanAcceptedMessage.js +12 -6
  84. package/dist/ui/components/PlanAcceptedMessage.js.map +1 -1
  85. package/dist/ui/components/PlanQuestionMessage.js +37 -0
  86. package/dist/ui/components/PlanQuestionMessage.js.map +1 -0
  87. package/dist/ui/components/PlanQuestionScreen.js +138 -0
  88. package/dist/ui/components/PlanQuestionScreen.js.map +1 -0
  89. package/dist/ui/components/PlanReviewScreen.js +7 -9
  90. package/dist/ui/components/PlanReviewScreen.js.map +1 -1
  91. package/dist/ui/components/RulesEditorScreen.js +65 -28
  92. package/dist/ui/components/RulesEditorScreen.js.map +1 -1
  93. package/dist/ui/components/SelectPrompt.js +3 -1
  94. package/dist/ui/components/SelectPrompt.js.map +1 -1
  95. package/dist/ui/components/SkillCreatorScreen.js +217 -0
  96. package/dist/ui/components/SkillCreatorScreen.js.map +1 -0
  97. package/dist/ui/components/SlashCommandAutocomplete.js +4 -2
  98. package/dist/ui/components/SlashCommandAutocomplete.js.map +1 -1
  99. package/dist/ui/components/StatusBar.js +4 -2
  100. package/dist/ui/components/StatusBar.js.map +1 -1
  101. package/dist/ui/components/StreamingMessageDisplay.js +5 -3
  102. package/dist/ui/components/StreamingMessageDisplay.js.map +1 -1
  103. package/dist/ui/components/SubAgentListScreen.js +65 -0
  104. package/dist/ui/components/SubAgentListScreen.js.map +1 -0
  105. package/dist/ui/components/SubAgentViewScreen.js +123 -0
  106. package/dist/ui/components/SubAgentViewScreen.js.map +1 -0
  107. package/dist/ui/components/TaskCompletedMessage.js +40 -8
  108. package/dist/ui/components/TaskCompletedMessage.js.map +1 -1
  109. package/dist/ui/components/TaskProgressIndicator.js +6 -4
  110. package/dist/ui/components/TaskProgressIndicator.js.map +1 -1
  111. package/dist/ui/components/TextEditor.js +297 -0
  112. package/dist/ui/components/TextEditor.js.map +1 -0
  113. package/dist/ui/components/TodoListMessage.js +59 -0
  114. package/dist/ui/components/TodoListMessage.js.map +1 -0
  115. package/dist/ui/components/ToolExecutionMessage.js +134 -84
  116. package/dist/ui/components/ToolExecutionMessage.js.map +1 -1
  117. package/dist/ui/components/ToolExecutionStatus.js +3 -1
  118. package/dist/ui/components/ToolExecutionStatus.js.map +1 -1
  119. package/dist/ui/components/WelcomeBanner.js +33 -33
  120. package/dist/ui/components/WelcomeBanner.js.map +1 -1
  121. package/dist/ui/components/WorkflowCreatorScreen.js +5 -3
  122. package/dist/ui/components/WorkflowCreatorScreen.js.map +1 -1
  123. package/dist/ui/theme.js +97 -0
  124. package/dist/ui/theme.js.map +1 -0
  125. package/dist/ui/utils/chat-history-limit.js +247 -0
  126. package/dist/ui/utils/chat-history-limit.js.map +1 -0
  127. package/dist/utils/chat-formatter.js +22 -9
  128. package/dist/utils/chat-formatter.js.map +1 -1
  129. package/dist/utils/git-stats.js +7 -5
  130. package/dist/utils/git-stats.js.map +1 -1
  131. package/dist/utils/input-classifier.js +11 -1
  132. package/dist/utils/input-classifier.js.map +1 -1
  133. package/dist/utils/output-truncation.js +175 -0
  134. package/dist/utils/output-truncation.js.map +1 -0
  135. package/dist/utils/rule-reference-resolver.js +3 -3
  136. package/dist/utils/rule-reference-resolver.js.map +1 -1
  137. package/dist/utils/tunnel-commands-manager.js +134 -0
  138. package/dist/utils/tunnel-commands-manager.js.map +1 -0
  139. package/package.json +91 -90
  140. package/postinstall.js +4 -11
  141. package/dist/ui/components/MultiLineInput.js +0 -255
  142. package/dist/ui/components/MultiLineInput.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/ui/components/SlashCommandAutocomplete.tsx"],"sourcesContent":["import React from 'react';\r\nimport { Box, Text } from 'ink';\r\n\r\ninterface SlashCommandAutocompleteProps {\r\n commands: Array<{ name: string; description: string }>;\r\n selectedIndex: number;\r\n maxVisibleItems: number;\r\n scrollOffset: number;\r\n}\r\n\r\nexport const SlashCommandAutocomplete: React.FC<SlashCommandAutocompleteProps> = ({\r\n commands,\r\n selectedIndex,\r\n maxVisibleItems,\r\n scrollOffset\r\n}) => {\r\n if (commands.length === 0 || maxVisibleItems === 0) return null;\r\n\r\n // Calculate the visible window of commands\r\n const visibleCommands = commands.slice(scrollOffset, scrollOffset + maxVisibleItems);\r\n\r\n // Determine if we need scroll indicators\r\n const hasMoreAbove = scrollOffset > 0;\r\n const hasMoreBelow = scrollOffset + maxVisibleItems < commands.length;\r\n\r\n return (\r\n <Box\r\n flexDirection=\"column\"\r\n borderStyle=\"round\"\r\n borderColor=\"white\"\r\n paddingX={1}\r\n marginLeft={1}\r\n marginTop={0}\r\n >\r\n {/* Scroll up indicator */}\r\n {hasMoreAbove && (\r\n <Text color=\"#666666\" dimColor>↑ more</Text>\r\n )}\r\n\r\n {visibleCommands.map((cmd, index) => {\r\n const actualIndex = scrollOffset + index;\r\n const isSelected = actualIndex === selectedIndex;\r\n\r\n return (\r\n <Box\r\n key={cmd.name}\r\n paddingX={1}\r\n >\r\n <Text\r\n color={isSelected ? '#00ccff' : '#666666'}\r\n bold={isSelected}\r\n inverse={isSelected}\r\n >\r\n {cmd.name}\r\n </Text>\r\n <Text color=\"#666666\"> {cmd.description}</Text>\r\n </Box>\r\n );\r\n })}\r\n\r\n {/* Scroll down indicator */}\r\n {hasMoreBelow && (\r\n <Text color=\"#666666\" dimColor>↓ more</Text>\r\n )}\r\n </Box>\r\n );\r\n};\r\n"],"mappings":"AAAA,OAAO,WAAW;AAClB,SAAS,KAAK,YAAY;AASnB,MAAM,2BAAoE,CAAC;AAAA,EAC9E;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ,MAAM;AACF,MAAI,SAAS,WAAW,KAAK,oBAAoB,EAAG,QAAO;AAG3D,QAAM,kBAAkB,SAAS,MAAM,cAAc,eAAe,eAAe;AAGnF,QAAM,eAAe,eAAe;AACpC,QAAM,eAAe,eAAe,kBAAkB,SAAS;AAE/D,SACI;AAAA,IAAC;AAAA;AAAA,MACG,eAAc;AAAA,MACd,aAAY;AAAA,MACZ,aAAY;AAAA,MACZ,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,WAAW;AAAA;AAAA,IAGV,gBACG,oCAAC,QAAK,OAAM,WAAU,UAAQ,QAAC,aAAM;AAAA,IAGxC,gBAAgB,IAAI,CAAC,KAAK,UAAU;AACjC,YAAM,cAAc,eAAe;AACnC,YAAM,aAAa,gBAAgB;AAEnC,aACI;AAAA,QAAC;AAAA;AAAA,UACG,KAAK,IAAI;AAAA,UACT,UAAU;AAAA;AAAA,QAEV;AAAA,UAAC;AAAA;AAAA,YACG,OAAO,aAAa,YAAY;AAAA,YAChC,MAAM;AAAA,YACN,SAAS;AAAA;AAAA,UAER,IAAI;AAAA,QACT;AAAA,QACA,oCAAC,QAAK,OAAM,aAAU,MAAG,IAAI,WAAY;AAAA,MAC7C;AAAA,IAER,CAAC;AAAA,IAGA,gBACG,oCAAC,QAAK,OAAM,WAAU,UAAQ,QAAC,aAAM;AAAA,EAE7C;AAER;","names":[]}
1
+ {"version":3,"sources":["../../../src/ui/components/SlashCommandAutocomplete.tsx"],"sourcesContent":["import React from 'react';\r\nimport { Box, Text } from 'ink';\r\nimport { useTheme } from '../theme.js';\r\n\r\ninterface SlashCommandAutocompleteProps {\r\n commands: Array<{ name: string; description: string; swatchColor?: string }>;\r\n selectedIndex: number;\r\n maxVisibleItems: number;\r\n scrollOffset: number;\r\n}\r\n\r\nexport const SlashCommandAutocomplete: React.FC<SlashCommandAutocompleteProps> = ({\r\n commands,\r\n selectedIndex,\r\n maxVisibleItems,\r\n scrollOffset\r\n}) => {\r\n const theme = useTheme();\r\n\r\n if (commands.length === 0 || maxVisibleItems === 0) return null;\r\n\r\n // Calculate the visible window of commands\r\n const visibleCommands = commands.slice(scrollOffset, scrollOffset + maxVisibleItems);\r\n\r\n // Determine if we need scroll indicators\r\n const hasMoreAbove = scrollOffset > 0;\r\n const hasMoreBelow = scrollOffset + maxVisibleItems < commands.length;\r\n\r\n return (\r\n <Box\r\n flexDirection=\"column\"\r\n borderStyle=\"round\"\r\n borderColor=\"white\"\r\n paddingX={1}\r\n marginLeft={1}\r\n marginTop={0}\r\n >\r\n {/* Scroll up indicator */}\r\n {hasMoreAbove && (\r\n <Text color=\"#666666\" dimColor>↑ more</Text>\r\n )}\r\n\r\n {visibleCommands.map((cmd, index) => {\r\n const actualIndex = scrollOffset + index;\r\n const isSelected = actualIndex === selectedIndex;\r\n\r\n return (\r\n <Box\r\n key={cmd.name}\r\n paddingX={1}\r\n >\r\n <Text\r\n color={isSelected ? theme.accent : '#666666'}\r\n bold={isSelected}\r\n inverse={isSelected}\r\n >\r\n {cmd.name}\r\n </Text>\r\n {cmd.swatchColor ? (\r\n <>\r\n <Text color={cmd.swatchColor}> ████</Text>\r\n <Text color=\"#666666\"> {cmd.description}</Text>\r\n </>\r\n ) : (\r\n <Text color=\"#666666\"> {cmd.description}</Text>\r\n )}\r\n </Box>\r\n );\r\n })}\r\n\r\n {/* Scroll down indicator */}\r\n {hasMoreBelow && (\r\n <Text color=\"#666666\" dimColor>↓ more</Text>\r\n )}\r\n </Box>\r\n );\r\n};\r\n"],"mappings":"AAAA,OAAO,WAAW;AAClB,SAAS,KAAK,YAAY;AAC1B,SAAS,gBAAgB;AASlB,MAAM,2BAAoE,CAAC;AAAA,EAC9E;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ,MAAM;AACF,QAAM,QAAQ,SAAS;AAEvB,MAAI,SAAS,WAAW,KAAK,oBAAoB,EAAG,QAAO;AAG3D,QAAM,kBAAkB,SAAS,MAAM,cAAc,eAAe,eAAe;AAGnF,QAAM,eAAe,eAAe;AACpC,QAAM,eAAe,eAAe,kBAAkB,SAAS;AAE/D,SACI;AAAA,IAAC;AAAA;AAAA,MACG,eAAc;AAAA,MACd,aAAY;AAAA,MACZ,aAAY;AAAA,MACZ,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,WAAW;AAAA;AAAA,IAGV,gBACG,oCAAC,QAAK,OAAM,WAAU,UAAQ,QAAC,aAAM;AAAA,IAGxC,gBAAgB,IAAI,CAAC,KAAK,UAAU;AACjC,YAAM,cAAc,eAAe;AACnC,YAAM,aAAa,gBAAgB;AAEnC,aACI;AAAA,QAAC;AAAA;AAAA,UACG,KAAK,IAAI;AAAA,UACT,UAAU;AAAA;AAAA,QAEV;AAAA,UAAC;AAAA;AAAA,YACG,OAAO,aAAa,MAAM,SAAS;AAAA,YACnC,MAAM;AAAA,YACN,SAAS;AAAA;AAAA,UAER,IAAI;AAAA,QACT;AAAA,QACC,IAAI,cACH,0DACE,oCAAC,QAAK,OAAO,IAAI,eAAa,4BAAM,GACpC,oCAAC,QAAK,OAAM,aAAU,MAAG,IAAI,WAAY,CAC3C,IAEA,oCAAC,QAAK,OAAM,aAAU,MAAG,IAAI,WAAY;AAAA,MAE/C;AAAA,IAER,CAAC;AAAA,IAGA,gBACG,oCAAC,QAAK,OAAM,WAAU,UAAQ,QAAC,aAAM;AAAA,EAE7C;AAER;","names":[]}
@@ -2,6 +2,7 @@ import React from "react";
2
2
  import { Box, Text } from "ink";
3
3
  import Spinner from "ink-spinner";
4
4
  import { useConnectivity } from "../../hooks/useConnectivity.js";
5
+ import { useTheme } from "../theme.js";
5
6
  const StatusBar = ({
6
7
  autoAcceptMode,
7
8
  isLoading,
@@ -12,11 +13,12 @@ const StatusBar = ({
12
13
  maxTokens
13
14
  }) => {
14
15
  const isConnected = useConnectivity();
16
+ const theme = useTheme();
15
17
  const tokenUsagePercent = currentTokens && maxTokens && maxTokens > 0 ? Math.round(currentTokens / maxTokens * 100) : 0;
16
18
  const getTokenColor = () => {
17
19
  if (tokenUsagePercent >= 90) return "red";
18
20
  if (tokenUsagePercent >= 75) return "yellow";
19
- return "#00ccff";
21
+ return theme.accent;
20
22
  };
21
23
  const formatTokenCount = (count) => {
22
24
  if (count >= 1e6) {
@@ -51,7 +53,7 @@ const StatusBar = ({
51
53
  return null;
52
54
  };
53
55
  const workingDir = getWorkingDirectory();
54
- return /* @__PURE__ */ React.createElement(Box, { borderStyle: "single", borderColor: "#003b59", paddingX: 1 }, /* @__PURE__ */ React.createElement(Box, { flexGrow: 1 }, getSubshellPrefix(), workingDir && /* @__PURE__ */ React.createElement(Box, { marginRight: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "#666666" }, "CWD: "), /* @__PURE__ */ React.createElement(Text, { color: "#00ccff" }, workingDir)), isLoading && /* @__PURE__ */ React.createElement(Box, { marginRight: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "#00ccff" }, /* @__PURE__ */ React.createElement(Spinner, { type: "dots" })), /* @__PURE__ */ React.createElement(Text, { color: "#00ccff" }, " ", loadingMessage)), !isLoading && model && /* @__PURE__ */ React.createElement(Box, { marginRight: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "#666666" }, "Model: "), /* @__PURE__ */ React.createElement(Text, { color: "#00ccff" }, model)), currentTokens !== void 0 && maxTokens !== void 0 && maxTokens > 0 && /* @__PURE__ */ React.createElement(Box, { marginRight: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "#666666" }, "Context: "), /* @__PURE__ */ React.createElement(Text, { color: getTokenColor() }, formatTokenCount(currentTokens), "/", formatTokenCount(maxTokens), " (", tokenUsagePercent, "%)"))), /* @__PURE__ */ React.createElement(Box, null, !isConnected && /* @__PURE__ */ React.createElement(Box, { marginRight: 2 }, /* @__PURE__ */ React.createElement(Text, { color: "red", bold: true }, "No internet connection")), autoAcceptMode && /* @__PURE__ */ React.createElement(Text, { color: "#00cc66", bold: true }, "[AUTO-ACCEPT: ON]"), !autoAcceptMode && /* @__PURE__ */ React.createElement(Text, { color: "#666666", dimColor: true }, "[AUTO-ACCEPT: OFF]")));
56
+ return /* @__PURE__ */ React.createElement(Box, { borderStyle: "single", borderColor: theme.bgAccent, paddingX: 1 }, /* @__PURE__ */ React.createElement(Box, { flexGrow: 1 }, getSubshellPrefix(), workingDir && /* @__PURE__ */ React.createElement(Box, { marginRight: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "#666666" }, "CWD: "), /* @__PURE__ */ React.createElement(Text, { color: theme.accent }, workingDir)), isLoading && /* @__PURE__ */ React.createElement(Box, { marginRight: 1 }, /* @__PURE__ */ React.createElement(Text, { color: theme.accent }, /* @__PURE__ */ React.createElement(Spinner, { type: "dots" })), /* @__PURE__ */ React.createElement(Text, { color: theme.accent }, " ", loadingMessage)), !isLoading && model && /* @__PURE__ */ React.createElement(Box, { marginRight: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "#666666" }, "Model: "), /* @__PURE__ */ React.createElement(Text, { color: theme.accent }, model)), currentTokens !== void 0 && maxTokens !== void 0 && maxTokens > 0 && /* @__PURE__ */ React.createElement(Box, { marginRight: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "#666666" }, "Context: "), /* @__PURE__ */ React.createElement(Text, { color: getTokenColor() }, formatTokenCount(currentTokens), "/", formatTokenCount(maxTokens), " (", tokenUsagePercent, "%)"))), /* @__PURE__ */ React.createElement(Box, null, !isConnected && /* @__PURE__ */ React.createElement(Box, { marginRight: 2 }, /* @__PURE__ */ React.createElement(Text, { color: "red", bold: true }, "No internet connection")), autoAcceptMode && /* @__PURE__ */ React.createElement(Text, { color: "#00cc66", bold: true }, "[AUTO-ACCEPT: ON]"), !autoAcceptMode && /* @__PURE__ */ React.createElement(Text, { color: "#666666", dimColor: true }, "[AUTO-ACCEPT: OFF]")));
55
57
  };
56
58
  export {
57
59
  StatusBar
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/ui/components/StatusBar.tsx"],"sourcesContent":["import React from 'react';\r\nimport { Box, Text } from 'ink';\r\nimport Spinner from 'ink-spinner';\r\nimport { SubshellContext } from '../../context/types.js';\r\n\r\ninterface StatusBarProps {\r\n autoAcceptMode: boolean;\r\n isLoading: boolean;\r\n loadingMessage?: string;\r\n model?: string;\r\n subshellContext?: SubshellContext;\r\n currentTokens?: number;\r\n maxTokens?: number;\r\n}\r\n\r\nimport { useConnectivity } from '../../hooks/useConnectivity.js';\r\n\r\nexport const StatusBar: React.FC<StatusBarProps> = ({\r\n autoAcceptMode,\r\n isLoading,\r\n loadingMessage = 'Processing...',\r\n model,\r\n subshellContext,\r\n currentTokens,\r\n maxTokens\r\n}) => {\r\n const isConnected = useConnectivity();\r\n\r\n // Calculate token usage percentage\r\n const tokenUsagePercent = currentTokens && maxTokens && maxTokens > 0\r\n ? Math.round((currentTokens / maxTokens) * 100)\r\n : 0;\r\n\r\n // Determine color based on usage\r\n const getTokenColor = () => {\r\n if (tokenUsagePercent >= 90) return 'red';\r\n if (tokenUsagePercent >= 75) return 'yellow';\r\n return '#00ccff';\r\n };\r\n\r\n // Format token count with K/M suffix\r\n const formatTokenCount = (count: number): string => {\r\n if (count >= 1000000) {\r\n return `${(count / 1000000).toFixed(1)}M`;\r\n }\r\n if (count >= 1000) {\r\n return `${(count / 1000).toFixed(1)}K`;\r\n }\r\n return count.toString();\r\n };\r\n\r\n // Get subshell type prefix with appropriate color\r\n const getSubshellPrefix = () => {\r\n if (!subshellContext || subshellContext.type === 'local') {\r\n return null;\r\n }\r\n\r\n const typeColors: Record<string, string> = {\r\n ssh: 'cyan',\r\n wsl: 'yellow',\r\n docker: 'blue',\r\n };\r\n\r\n const color = typeColors[subshellContext.type] || 'cyan';\r\n const prefix = subshellContext.type.toUpperCase();\r\n\r\n return (\r\n <Box marginRight={1}>\r\n <Text color={color} bold>[{prefix}]</Text>\r\n </Box>\r\n );\r\n };\r\n\r\n // Get working directory from context if available\r\n const getWorkingDirectory = () => {\r\n if (subshellContext && subshellContext.type !== 'local') {\r\n const cwd = subshellContext.metadata.workingDirectory;\r\n // Truncate long paths\r\n if (cwd.length > 40) {\r\n return '...' + cwd.slice(-37);\r\n }\r\n return cwd;\r\n }\r\n return null;\r\n };\r\n\r\n const workingDir = getWorkingDirectory();\r\n\r\n return (\r\n <Box borderStyle=\"single\" borderColor=\"#003b59\" paddingX={1}>\r\n <Box flexGrow={1}>\r\n {/* Show subshell prefix and working directory */}\r\n {getSubshellPrefix()}\r\n\r\n {workingDir && (\r\n <Box marginRight={1}>\r\n <Text color=\"#666666\">CWD: </Text>\r\n <Text color=\"#00ccff\">{workingDir}</Text>\r\n </Box>\r\n )}\r\n\r\n {isLoading && (\r\n <Box marginRight={1}>\r\n <Text color=\"#00ccff\">\r\n <Spinner type=\"dots\" />\r\n </Text>\r\n <Text color=\"#00ccff\"> {loadingMessage}</Text>\r\n </Box>\r\n )}\r\n\r\n {!isLoading && model && (\r\n <Box marginRight={1}>\r\n <Text color=\"#666666\">Model: </Text>\r\n <Text color=\"#00ccff\">{model}</Text>\r\n </Box>\r\n )}\r\n\r\n {/* Token usage display */}\r\n {currentTokens !== undefined && maxTokens !== undefined && maxTokens > 0 && (\r\n <Box marginRight={1}>\r\n <Text color=\"#666666\">Context: </Text>\r\n <Text color={getTokenColor()}>\r\n {formatTokenCount(currentTokens)}/{formatTokenCount(maxTokens)} ({tokenUsagePercent}%)\r\n </Text>\r\n </Box>\r\n )}\r\n </Box>\r\n\r\n <Box>\r\n {!isConnected && (\r\n <Box marginRight={2}>\r\n <Text color=\"red\" bold>No internet connection</Text>\r\n </Box>\r\n )}\r\n\r\n {autoAcceptMode && (\r\n <Text color=\"#00cc66\" bold>[AUTO-ACCEPT: ON]</Text>\r\n )}\r\n {!autoAcceptMode && (\r\n <Text color=\"#666666\" dimColor>[AUTO-ACCEPT: OFF]</Text>\r\n )}\r\n </Box>\r\n </Box>\r\n );\r\n};\r\n"],"mappings":"AAAA,OAAO,WAAW;AAClB,SAAS,KAAK,YAAY;AAC1B,OAAO,aAAa;AAapB,SAAS,uBAAuB;AAEzB,MAAM,YAAsC,CAAC;AAAA,EAClD;AAAA,EACA;AAAA,EACA,iBAAiB;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,cAAc,gBAAgB;AAGpC,QAAM,oBAAoB,iBAAiB,aAAa,YAAY,IAChE,KAAK,MAAO,gBAAgB,YAAa,GAAG,IAC5C;AAGJ,QAAM,gBAAgB,MAAM;AAC1B,QAAI,qBAAqB,GAAI,QAAO;AACpC,QAAI,qBAAqB,GAAI,QAAO;AACpC,WAAO;AAAA,EACT;AAGA,QAAM,mBAAmB,CAAC,UAA0B;AAClD,QAAI,SAAS,KAAS;AACpB,aAAO,IAAI,QAAQ,KAAS,QAAQ,CAAC,CAAC;AAAA,IACxC;AACA,QAAI,SAAS,KAAM;AACjB,aAAO,IAAI,QAAQ,KAAM,QAAQ,CAAC,CAAC;AAAA,IACrC;AACA,WAAO,MAAM,SAAS;AAAA,EACxB;AAGA,QAAM,oBAAoB,MAAM;AAC9B,QAAI,CAAC,mBAAmB,gBAAgB,SAAS,SAAS;AACxD,aAAO;AAAA,IACT;AAEA,UAAM,aAAqC;AAAA,MACzC,KAAK;AAAA,MACL,KAAK;AAAA,MACL,QAAQ;AAAA,IACV;AAEA,UAAM,QAAQ,WAAW,gBAAgB,IAAI,KAAK;AAClD,UAAM,SAAS,gBAAgB,KAAK,YAAY;AAEhD,WACE,oCAAC,OAAI,aAAa,KAChB,oCAAC,QAAK,OAAc,MAAI,QAAC,KAAE,QAAO,GAAC,CACrC;AAAA,EAEJ;AAGA,QAAM,sBAAsB,MAAM;AAChC,QAAI,mBAAmB,gBAAgB,SAAS,SAAS;AACvD,YAAM,MAAM,gBAAgB,SAAS;AAErC,UAAI,IAAI,SAAS,IAAI;AACnB,eAAO,QAAQ,IAAI,MAAM,GAAG;AAAA,MAC9B;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,oBAAoB;AAEvC,SACE,oCAAC,OAAI,aAAY,UAAS,aAAY,WAAU,UAAU,KACxD,oCAAC,OAAI,UAAU,KAEZ,kBAAkB,GAElB,cACC,oCAAC,OAAI,aAAa,KAChB,oCAAC,QAAK,OAAM,aAAU,OAAK,GAC3B,oCAAC,QAAK,OAAM,aAAW,UAAW,CACpC,GAGD,aACC,oCAAC,OAAI,aAAa,KAChB,oCAAC,QAAK,OAAM,aACV,oCAAC,WAAQ,MAAK,QAAO,CACvB,GACA,oCAAC,QAAK,OAAM,aAAU,KAAE,cAAe,CACzC,GAGD,CAAC,aAAa,SACb,oCAAC,OAAI,aAAa,KAChB,oCAAC,QAAK,OAAM,aAAU,SAAO,GAC7B,oCAAC,QAAK,OAAM,aAAW,KAAM,CAC/B,GAID,kBAAkB,UAAa,cAAc,UAAa,YAAY,KACrE,oCAAC,OAAI,aAAa,KAChB,oCAAC,QAAK,OAAM,aAAU,WAAS,GAC/B,oCAAC,QAAK,OAAO,cAAc,KACxB,iBAAiB,aAAa,GAAE,KAAE,iBAAiB,SAAS,GAAE,MAAG,mBAAkB,IACtF,CACF,CAEJ,GAEA,oCAAC,WACE,CAAC,eACA,oCAAC,OAAI,aAAa,KAChB,oCAAC,QAAK,OAAM,OAAM,MAAI,QAAC,wBAAsB,CAC/C,GAGD,kBACC,oCAAC,QAAK,OAAM,WAAU,MAAI,QAAC,mBAAiB,GAE7C,CAAC,kBACA,oCAAC,QAAK,OAAM,WAAU,UAAQ,QAAC,oBAAkB,CAErD,CACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/ui/components/StatusBar.tsx"],"sourcesContent":["import React from 'react';\r\nimport { Box, Text } from 'ink';\r\nimport Spinner from 'ink-spinner';\r\nimport { SubshellContext } from '../../context/types.js';\r\n\r\ninterface StatusBarProps {\r\n autoAcceptMode: boolean;\r\n isLoading: boolean;\r\n loadingMessage?: string;\r\n model?: string;\r\n subshellContext?: SubshellContext;\r\n currentTokens?: number;\r\n maxTokens?: number;\r\n}\r\n\r\nimport { useConnectivity } from '../../hooks/useConnectivity.js';\r\nimport { useTheme } from '../theme.js';\r\n\r\nexport const StatusBar: React.FC<StatusBarProps> = ({\r\n autoAcceptMode,\r\n isLoading,\r\n loadingMessage = 'Processing...',\r\n model,\r\n subshellContext,\r\n currentTokens,\r\n maxTokens\r\n}) => {\r\n const isConnected = useConnectivity();\r\n const theme = useTheme();\r\n\r\n // Calculate token usage percentage\r\n const tokenUsagePercent = currentTokens && maxTokens && maxTokens > 0\r\n ? Math.round((currentTokens / maxTokens) * 100)\r\n : 0;\r\n\r\n // Determine color based on usage\r\n const getTokenColor = () => {\r\n if (tokenUsagePercent >= 90) return 'red';\r\n if (tokenUsagePercent >= 75) return 'yellow';\r\n return theme.accent;\r\n };\r\n\r\n // Format token count with K/M suffix\r\n const formatTokenCount = (count: number): string => {\r\n if (count >= 1000000) {\r\n return `${(count / 1000000).toFixed(1)}M`;\r\n }\r\n if (count >= 1000) {\r\n return `${(count / 1000).toFixed(1)}K`;\r\n }\r\n return count.toString();\r\n };\r\n\r\n // Get subshell type prefix with appropriate color\r\n const getSubshellPrefix = () => {\r\n if (!subshellContext || subshellContext.type === 'local') {\r\n return null;\r\n }\r\n\r\n const typeColors: Record<string, string> = {\r\n ssh: 'cyan',\r\n wsl: 'yellow',\r\n docker: 'blue',\r\n };\r\n\r\n const color = typeColors[subshellContext.type] || 'cyan';\r\n const prefix = subshellContext.type.toUpperCase();\r\n\r\n return (\r\n <Box marginRight={1}>\r\n <Text color={color} bold>[{prefix}]</Text>\r\n </Box>\r\n );\r\n };\r\n\r\n // Get working directory from context if available\r\n const getWorkingDirectory = () => {\r\n if (subshellContext && subshellContext.type !== 'local') {\r\n const cwd = subshellContext.metadata.workingDirectory;\r\n // Truncate long paths\r\n if (cwd.length > 40) {\r\n return '...' + cwd.slice(-37);\r\n }\r\n return cwd;\r\n }\r\n return null;\r\n };\r\n\r\n const workingDir = getWorkingDirectory();\r\n\r\n return (\r\n <Box borderStyle=\"single\" borderColor={theme.bgAccent} paddingX={1}>\r\n <Box flexGrow={1}>\r\n {/* Show subshell prefix and working directory */}\r\n {getSubshellPrefix()}\r\n\r\n {workingDir && (\r\n <Box marginRight={1}>\r\n <Text color=\"#666666\">CWD: </Text>\r\n <Text color={theme.accent}>{workingDir}</Text>\r\n </Box>\r\n )}\r\n\r\n {isLoading && (\r\n <Box marginRight={1}>\r\n <Text color={theme.accent}>\r\n <Spinner type=\"dots\" />\r\n </Text>\r\n <Text color={theme.accent}> {loadingMessage}</Text>\r\n </Box>\r\n )}\r\n\r\n {!isLoading && model && (\r\n <Box marginRight={1}>\r\n <Text color=\"#666666\">Model: </Text>\r\n <Text color={theme.accent}>{model}</Text>\r\n </Box>\r\n )}\r\n\r\n {/* Token usage display */}\r\n {currentTokens !== undefined && maxTokens !== undefined && maxTokens > 0 && (\r\n <Box marginRight={1}>\r\n <Text color=\"#666666\">Context: </Text>\r\n <Text color={getTokenColor()}>\r\n {formatTokenCount(currentTokens)}/{formatTokenCount(maxTokens)} ({tokenUsagePercent}%)\r\n </Text>\r\n </Box>\r\n )}\r\n </Box>\r\n\r\n <Box>\r\n {!isConnected && (\r\n <Box marginRight={2}>\r\n <Text color=\"red\" bold>No internet connection</Text>\r\n </Box>\r\n )}\r\n\r\n {autoAcceptMode && (\r\n <Text color=\"#00cc66\" bold>[AUTO-ACCEPT: ON]</Text>\r\n )}\r\n {!autoAcceptMode && (\r\n <Text color=\"#666666\" dimColor>[AUTO-ACCEPT: OFF]</Text>\r\n )}\r\n </Box>\r\n </Box>\r\n );\r\n};\r\n"],"mappings":"AAAA,OAAO,WAAW;AAClB,SAAS,KAAK,YAAY;AAC1B,OAAO,aAAa;AAapB,SAAS,uBAAuB;AAChC,SAAS,gBAAgB;AAElB,MAAM,YAAsC,CAAC;AAAA,EAClD;AAAA,EACA;AAAA,EACA,iBAAiB;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,cAAc,gBAAgB;AACpC,QAAM,QAAQ,SAAS;AAGvB,QAAM,oBAAoB,iBAAiB,aAAa,YAAY,IAChE,KAAK,MAAO,gBAAgB,YAAa,GAAG,IAC5C;AAGJ,QAAM,gBAAgB,MAAM;AAC1B,QAAI,qBAAqB,GAAI,QAAO;AACpC,QAAI,qBAAqB,GAAI,QAAO;AACpC,WAAO,MAAM;AAAA,EACf;AAGA,QAAM,mBAAmB,CAAC,UAA0B;AAClD,QAAI,SAAS,KAAS;AACpB,aAAO,IAAI,QAAQ,KAAS,QAAQ,CAAC,CAAC;AAAA,IACxC;AACA,QAAI,SAAS,KAAM;AACjB,aAAO,IAAI,QAAQ,KAAM,QAAQ,CAAC,CAAC;AAAA,IACrC;AACA,WAAO,MAAM,SAAS;AAAA,EACxB;AAGA,QAAM,oBAAoB,MAAM;AAC9B,QAAI,CAAC,mBAAmB,gBAAgB,SAAS,SAAS;AACxD,aAAO;AAAA,IACT;AAEA,UAAM,aAAqC;AAAA,MACzC,KAAK;AAAA,MACL,KAAK;AAAA,MACL,QAAQ;AAAA,IACV;AAEA,UAAM,QAAQ,WAAW,gBAAgB,IAAI,KAAK;AAClD,UAAM,SAAS,gBAAgB,KAAK,YAAY;AAEhD,WACE,oCAAC,OAAI,aAAa,KAChB,oCAAC,QAAK,OAAc,MAAI,QAAC,KAAE,QAAO,GAAC,CACrC;AAAA,EAEJ;AAGA,QAAM,sBAAsB,MAAM;AAChC,QAAI,mBAAmB,gBAAgB,SAAS,SAAS;AACvD,YAAM,MAAM,gBAAgB,SAAS;AAErC,UAAI,IAAI,SAAS,IAAI;AACnB,eAAO,QAAQ,IAAI,MAAM,GAAG;AAAA,MAC9B;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,oBAAoB;AAEvC,SACE,oCAAC,OAAI,aAAY,UAAS,aAAa,MAAM,UAAU,UAAU,KAC/D,oCAAC,OAAI,UAAU,KAEZ,kBAAkB,GAElB,cACC,oCAAC,OAAI,aAAa,KAChB,oCAAC,QAAK,OAAM,aAAU,OAAK,GAC3B,oCAAC,QAAK,OAAO,MAAM,UAAS,UAAW,CACzC,GAGD,aACC,oCAAC,OAAI,aAAa,KAChB,oCAAC,QAAK,OAAO,MAAM,UACjB,oCAAC,WAAQ,MAAK,QAAO,CACvB,GACA,oCAAC,QAAK,OAAO,MAAM,UAAQ,KAAE,cAAe,CAC9C,GAGD,CAAC,aAAa,SACb,oCAAC,OAAI,aAAa,KAChB,oCAAC,QAAK,OAAM,aAAU,SAAO,GAC7B,oCAAC,QAAK,OAAO,MAAM,UAAS,KAAM,CACpC,GAID,kBAAkB,UAAa,cAAc,UAAa,YAAY,KACrE,oCAAC,OAAI,aAAa,KAChB,oCAAC,QAAK,OAAM,aAAU,WAAS,GAC/B,oCAAC,QAAK,OAAO,cAAc,KACxB,iBAAiB,aAAa,GAAE,KAAE,iBAAiB,SAAS,GAAE,MAAG,mBAAkB,IACtF,CACF,CAEJ,GAEA,oCAAC,WACE,CAAC,eACA,oCAAC,OAAI,aAAa,KAChB,oCAAC,QAAK,OAAM,OAAM,MAAI,QAAC,wBAAsB,CAC/C,GAGD,kBACC,oCAAC,QAAK,OAAM,WAAU,MAAI,QAAC,mBAAiB,GAE7C,CAAC,kBACA,oCAAC,QAAK,OAAM,WAAU,UAAQ,QAAC,oBAAkB,CAErD,CACF;AAEJ;","names":[]}
@@ -4,6 +4,7 @@ import wrapAnsi from "wrap-ansi";
4
4
  import { MarkdownRenderer } from "./MarkdownRenderer.js";
5
5
  import { getTerminalDimensions } from "../../hooks/useTerminalDimensions.js";
6
6
  import { ShimmerText } from "./ShimmerText.js";
7
+ import { useTheme } from "../theme.js";
7
8
  const STREAMING_TAIL_MAX_LINES = 5;
8
9
  const STREAMING_UPDATE_THROTTLE_MS = 50;
9
10
  function getLatestVisualTail(content, maxLines, width) {
@@ -31,6 +32,7 @@ const StreamingMessageDisplay = React.memo(({
31
32
  maxLines
32
33
  }) => {
33
34
  const [displayContent, setDisplayContent] = useState("");
35
+ const theme = useTheme();
34
36
  const contentBufferRef = useRef("");
35
37
  const updateTimerRef = useRef(null);
36
38
  const messageIdRef = useRef(message.id);
@@ -66,13 +68,13 @@ const StreamingMessageDisplay = React.memo(({
66
68
  const getRoleColor = (role) => {
67
69
  switch (role) {
68
70
  case "user":
69
- return "#00ccff";
71
+ return theme.accent;
70
72
  case "assistant":
71
73
  return "#00cc66";
72
74
  case "system":
73
75
  return "#ffaa00";
74
76
  case "tool":
75
- return "#00ccff";
77
+ return theme.accent;
76
78
  default:
77
79
  return "#ffffff";
78
80
  }
@@ -102,7 +104,7 @@ const StreamingMessageDisplay = React.memo(({
102
104
  Box,
103
105
  {
104
106
  borderStyle: "single",
105
- borderColor: "#00ccff",
107
+ borderColor: theme.accent,
106
108
  borderLeft: true,
107
109
  borderRight: false,
108
110
  borderTop: false,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/ui/components/StreamingMessageDisplay.tsx"],"sourcesContent":["import React, { useState, useEffect, useRef } from 'react';\r\nimport { Box, Text } from 'ink';\r\nimport wrapAnsi from 'wrap-ansi';\r\nimport { Message } from '../../types/index.js';\r\nimport { MarkdownRenderer } from './MarkdownRenderer.js';\r\nimport { getTerminalDimensions } from '../../hooks/useTerminalDimensions.js';\r\nimport { ShimmerText } from './ShimmerText.js';\r\n\r\ninterface StreamingMessageDisplayProps {\r\n message: Message;\r\n /** Optional override for max lines to display during streaming */\r\n maxLines?: number;\r\n}\r\n\r\nconst STREAMING_TAIL_MAX_LINES = 5;\r\nconst STREAMING_UPDATE_THROTTLE_MS = 50;\r\n\r\n/**\r\n * Keep only the latest visual lines (after wrapping), not just newline-delimited lines.\r\n * This prevents long wrapped paragraphs from flooding the streaming area.\r\n */\r\nfunction getLatestVisualTail(content: string, maxLines: number, width: number): string {\r\n if (!content) return '';\r\n\r\n const normalized = content.replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n');\r\n const logicalLines = normalized.split('\\n');\r\n const safeWidth = Math.max(1, width);\r\n let remaining = Math.max(1, maxLines);\r\n const tail: string[] = [];\r\n\r\n for (let i = logicalLines.length - 1; i >= 0 && remaining > 0; i--) {\r\n const line = logicalLines[i];\r\n const wrapped = wrapAnsi(line.length > 0 ? line : ' ', safeWidth, { hard: true, trim: false }).split('\\n');\r\n\r\n if (wrapped.length <= remaining) {\r\n tail.unshift(line);\r\n remaining -= wrapped.length;\r\n continue;\r\n }\r\n\r\n // Include only the visible tail of this line when it exceeds the remaining budget.\r\n tail.unshift(wrapped.slice(-remaining).join('\\n'));\r\n remaining = 0;\r\n }\r\n\r\n return tail.join('\\n');\r\n}\r\n\r\n/**\r\n * StreamingMessageDisplay - Shows streaming content as it arrives\r\n * Truncates from top if content exceeds max lines to prevent flickering\r\n * Dynamically adjusts based on terminal height\r\n */\r\nexport const StreamingMessageDisplay: React.FC<StreamingMessageDisplayProps> = React.memo(({\r\n message,\r\n maxLines\r\n}) => {\r\n const [displayContent, setDisplayContent] = useState('');\r\n const contentBufferRef = useRef('');\r\n const updateTimerRef = useRef<NodeJS.Timeout | null>(null);\r\n const messageIdRef = useRef(message.id);\r\n\r\n useEffect(() => {\r\n // If message ID changed, reset everything\r\n if (messageIdRef.current !== message.id) {\r\n messageIdRef.current = message.id;\r\n setDisplayContent('');\r\n contentBufferRef.current = '';\r\n if (updateTimerRef.current) {\r\n clearTimeout(updateTimerRef.current);\r\n }\r\n }\r\n\r\n // Buffer the new content\r\n contentBufferRef.current = message.content;\r\n\r\n // Clear any existing timer\r\n if (updateTimerRef.current) {\r\n clearTimeout(updateTimerRef.current);\r\n }\r\n\r\n // Throttle updates to every 50ms for more responsive streaming while preventing excessive re-renders\r\n updateTimerRef.current = setTimeout(() => {\r\n const fullContent = contentBufferRef.current;\r\n const renderWidth = Math.max(20, (process.stdout.columns || 120) - 6);\r\n\r\n // Keep a tight tail window to avoid Ink duplication/flicker from large streamed payloads.\r\n const { maxStreamingLines } = getTerminalDimensions();\r\n const effectiveMaxLines = Math.max(\r\n 1,\r\n Math.min(maxLines ?? maxStreamingLines, STREAMING_TAIL_MAX_LINES)\r\n );\r\n\r\n setDisplayContent(getLatestVisualTail(fullContent, effectiveMaxLines, renderWidth));\r\n }, STREAMING_UPDATE_THROTTLE_MS);\r\n\r\n return () => {\r\n if (updateTimerRef.current) {\r\n clearTimeout(updateTimerRef.current);\r\n }\r\n };\r\n }, [maxLines, message.content, message.id]);\r\n\r\n const getRoleColor = (role: string) => {\r\n switch (role) {\r\n case 'user': return '#00ccff';\r\n case 'assistant': return '#00cc66';\r\n case 'system': return '#ffaa00';\r\n case 'tool': return '#00ccff';\r\n default: return '#ffffff';\r\n }\r\n };\r\n\r\n const getRoleLabel = (role: string) => {\r\n switch (role) {\r\n case 'user': return 'You';\r\n case 'assistant': return 'Centaurus';\r\n case 'system': return 'System';\r\n case 'tool': return 'Tool';\r\n default: return role;\r\n }\r\n };\r\n\r\n const timestamp = message.timestamp\r\n ? new Date(message.timestamp).toLocaleTimeString()\r\n : '';\r\n\r\n // Skip rendering completely empty streaming messages\r\n // This prevents blank space from appearing when tool goes to executing state\r\n const hasContent = displayContent.trim();\r\n const hasThoughts = message.thoughts && message.thoughts.length > 0;\r\n const hasThinkingDuration = message.thinkingDuration !== undefined;\r\n\r\n if (!hasContent && !hasThoughts && !hasThinkingDuration) {\r\n return null;\r\n }\r\n\r\n\r\n return (\r\n <Box flexDirection=\"column\" marginBottom={1}>\r\n <Box marginBottom={1}>\r\n <Text color={getRoleColor(message.role)} bold>\r\n {getRoleLabel(message.role)}:\r\n </Text>\r\n {timestamp && (\r\n <Text color=\"#666666\" dimColor> {timestamp}</Text>\r\n )}\r\n </Box>\r\n\r\n {/* Display thinking duration at the TOP after thinking completes */}\r\n {message.thinkingDuration !== undefined && message.thinkingDuration >= 0 && (\r\n <Box marginBottom={1}>\r\n <Text dimColor>Thought for {message.thinkingDuration} second{message.thinkingDuration !== 1 ? 's' : ''} ›</Text>\r\n </Box>\r\n )}\r\n\r\n {/* Display the current streaming content with markdown formatting and blue border */}\r\n {/* Also includes thinking lines inside the same border to keep the blue line continuous */}\r\n {/* Only render if there's actual content or thinking lines to display */}\r\n {(displayContent.trim() || (message.thoughts && message.thoughts.length > 0 && message.thinkingDuration === undefined)) && (\r\n <Box\r\n borderStyle=\"single\"\r\n borderColor=\"#00ccff\"\r\n borderLeft={true}\r\n borderRight={false}\r\n borderTop={false}\r\n borderBottom={false}\r\n paddingLeft={1}\r\n flexDirection=\"column\"\r\n >\r\n {displayContent.trim() && (\r\n <MarkdownRenderer content={displayContent} maxWidth={(process.stdout.columns || 120) - 6} />\r\n )}\r\n\r\n {/* Display thinking lines (greyed out, last 3 lines) - shown WHILE thinking */}\r\n {message.thoughts && message.thoughts.length > 0 && message.thinkingDuration === undefined && (\r\n <Box flexDirection=\"column\" marginTop={displayContent.trim() ? 1 : 0}>\r\n {/* \"Thinking...\" label just above the thought preview lines */}\r\n <ShimmerText text=\"Thinking...\" baseColor=\"#4a4a4a\" dimShimmer />\r\n {message.thoughts.map((line, idx) => (\r\n <Text key={idx} dimColor>\r\n {line}\r\n </Text>\r\n ))}\r\n </Box>\r\n )}\r\n </Box>\r\n )}\r\n </Box>\r\n );\r\n});\r\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/ui/components/StreamingMessageDisplay.tsx"],"sourcesContent":["import React, { useState, useEffect, useRef } from 'react';\r\nimport { Box, Text } from 'ink';\r\nimport wrapAnsi from 'wrap-ansi';\r\nimport { Message } from '../../types/index.js';\r\nimport { MarkdownRenderer } from './MarkdownRenderer.js';\r\nimport { getTerminalDimensions } from '../../hooks/useTerminalDimensions.js';\r\nimport { ShimmerText } from './ShimmerText.js';\r\nimport { useTheme } from '../theme.js';\r\n\r\ninterface StreamingMessageDisplayProps {\r\n message: Message;\r\n /** Optional override for max lines to display during streaming */\r\n maxLines?: number;\r\n}\r\n\r\nconst STREAMING_TAIL_MAX_LINES = 5;\r\nconst STREAMING_UPDATE_THROTTLE_MS = 50;\r\n\r\n/**\r\n * Keep only the latest visual lines (after wrapping), not just newline-delimited lines.\r\n * This prevents long wrapped paragraphs from flooding the streaming area.\r\n */\r\nfunction getLatestVisualTail(content: string, maxLines: number, width: number): string {\r\n if (!content) return '';\r\n\r\n const normalized = content.replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n');\r\n const logicalLines = normalized.split('\\n');\r\n const safeWidth = Math.max(1, width);\r\n let remaining = Math.max(1, maxLines);\r\n const tail: string[] = [];\r\n\r\n for (let i = logicalLines.length - 1; i >= 0 && remaining > 0; i--) {\r\n const line = logicalLines[i];\r\n const wrapped = wrapAnsi(line.length > 0 ? line : ' ', safeWidth, { hard: true, trim: false }).split('\\n');\r\n\r\n if (wrapped.length <= remaining) {\r\n tail.unshift(line);\r\n remaining -= wrapped.length;\r\n continue;\r\n }\r\n\r\n // Include only the visible tail of this line when it exceeds the remaining budget.\r\n tail.unshift(wrapped.slice(-remaining).join('\\n'));\r\n remaining = 0;\r\n }\r\n\r\n return tail.join('\\n');\r\n}\r\n\r\n/**\r\n * StreamingMessageDisplay - Shows streaming content as it arrives\r\n * Truncates from top if content exceeds max lines to prevent flickering\r\n * Dynamically adjusts based on terminal height\r\n */\r\nexport const StreamingMessageDisplay: React.FC<StreamingMessageDisplayProps> = React.memo(({\r\n message,\r\n maxLines\r\n}) => {\r\n const [displayContent, setDisplayContent] = useState('');\r\n const theme = useTheme();\r\n const contentBufferRef = useRef('');\r\n const updateTimerRef = useRef<NodeJS.Timeout | null>(null);\r\n const messageIdRef = useRef(message.id);\r\n\r\n useEffect(() => {\r\n // If message ID changed, reset everything\r\n if (messageIdRef.current !== message.id) {\r\n messageIdRef.current = message.id;\r\n setDisplayContent('');\r\n contentBufferRef.current = '';\r\n if (updateTimerRef.current) {\r\n clearTimeout(updateTimerRef.current);\r\n }\r\n }\r\n\r\n // Buffer the new content\r\n contentBufferRef.current = message.content;\r\n\r\n // Clear any existing timer\r\n if (updateTimerRef.current) {\r\n clearTimeout(updateTimerRef.current);\r\n }\r\n\r\n // Throttle updates to every 50ms for more responsive streaming while preventing excessive re-renders\r\n updateTimerRef.current = setTimeout(() => {\r\n const fullContent = contentBufferRef.current;\r\n const renderWidth = Math.max(20, (process.stdout.columns || 120) - 6);\r\n\r\n // Keep a tight tail window to avoid Ink duplication/flicker from large streamed payloads.\r\n const { maxStreamingLines } = getTerminalDimensions();\r\n const effectiveMaxLines = Math.max(\r\n 1,\r\n Math.min(maxLines ?? maxStreamingLines, STREAMING_TAIL_MAX_LINES)\r\n );\r\n\r\n setDisplayContent(getLatestVisualTail(fullContent, effectiveMaxLines, renderWidth));\r\n }, STREAMING_UPDATE_THROTTLE_MS);\r\n\r\n return () => {\r\n if (updateTimerRef.current) {\r\n clearTimeout(updateTimerRef.current);\r\n }\r\n };\r\n }, [maxLines, message.content, message.id]);\r\n\r\n const getRoleColor = (role: string) => {\r\n switch (role) {\r\n case 'user': return theme.accent;\r\n case 'assistant': return '#00cc66';\r\n case 'system': return '#ffaa00';\r\n case 'tool': return theme.accent;\r\n default: return '#ffffff';\r\n }\r\n };\r\n\r\n const getRoleLabel = (role: string) => {\r\n switch (role) {\r\n case 'user': return 'You';\r\n case 'assistant': return 'Centaurus';\r\n case 'system': return 'System';\r\n case 'tool': return 'Tool';\r\n default: return role;\r\n }\r\n };\r\n\r\n const timestamp = message.timestamp\r\n ? new Date(message.timestamp).toLocaleTimeString()\r\n : '';\r\n\r\n // Skip rendering completely empty streaming messages\r\n // This prevents blank space from appearing when tool goes to executing state\r\n const hasContent = displayContent.trim();\r\n const hasThoughts = message.thoughts && message.thoughts.length > 0;\r\n const hasThinkingDuration = message.thinkingDuration !== undefined;\r\n\r\n if (!hasContent && !hasThoughts && !hasThinkingDuration) {\r\n return null;\r\n }\r\n\r\n\r\n return (\r\n <Box flexDirection=\"column\" marginBottom={1}>\r\n <Box marginBottom={1}>\r\n <Text color={getRoleColor(message.role)} bold>\r\n {getRoleLabel(message.role)}:\r\n </Text>\r\n {timestamp && (\r\n <Text color=\"#666666\" dimColor> {timestamp}</Text>\r\n )}\r\n </Box>\r\n\r\n {/* Display thinking duration at the TOP after thinking completes */}\r\n {message.thinkingDuration !== undefined && message.thinkingDuration >= 0 && (\r\n <Box marginBottom={1}>\r\n <Text dimColor>Thought for {message.thinkingDuration} second{message.thinkingDuration !== 1 ? 's' : ''} ›</Text>\r\n </Box>\r\n )}\r\n\r\n {/* Display the current streaming content with markdown formatting and blue border */}\r\n {/* Also includes thinking lines inside the same border to keep the blue line continuous */}\r\n {/* Only render if there's actual content or thinking lines to display */}\r\n {(displayContent.trim() || (message.thoughts && message.thoughts.length > 0 && message.thinkingDuration === undefined)) && (\r\n <Box\r\n borderStyle=\"single\"\r\n borderColor={theme.accent}\r\n borderLeft={true}\r\n borderRight={false}\r\n borderTop={false}\r\n borderBottom={false}\r\n paddingLeft={1}\r\n flexDirection=\"column\"\r\n >\r\n {displayContent.trim() && (\r\n <MarkdownRenderer content={displayContent} maxWidth={(process.stdout.columns || 120) - 6} />\r\n )}\r\n\r\n {/* Display thinking lines (greyed out, last 3 lines) - shown WHILE thinking */}\r\n {message.thoughts && message.thoughts.length > 0 && message.thinkingDuration === undefined && (\r\n <Box flexDirection=\"column\" marginTop={displayContent.trim() ? 1 : 0}>\r\n {/* \"Thinking...\" label just above the thought preview lines */}\r\n <ShimmerText text=\"Thinking...\" baseColor=\"#4a4a4a\" dimShimmer />\r\n {message.thoughts.map((line, idx) => (\r\n <Text key={idx} dimColor>\r\n {line}\r\n </Text>\r\n ))}\r\n </Box>\r\n )}\r\n </Box>\r\n )}\r\n </Box>\r\n );\r\n});\r\n"],"mappings":"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","names":[]}
@@ -0,0 +1,65 @@
1
+ import React from "react";
2
+ import { Box, Text, useInput } from "ink";
3
+ import { useTheme } from "../theme.js";
4
+ const STATUS_ICONS = {
5
+ running: { icon: "\u{1F504}", color: "#00cc66" },
6
+ pending: { icon: "\u23F3", color: "#ffaa00" },
7
+ completed: { icon: "\u2705", color: "#00cc66" },
8
+ failed: { icon: "\u274C", color: "#ff3366" },
9
+ terminated: { icon: "\u{1F6D1}", color: "#ff6666" }
10
+ };
11
+ const SubAgentListScreen = ({
12
+ agents,
13
+ onSelect,
14
+ onCancel
15
+ }) => {
16
+ const theme = useTheme();
17
+ const [selectedIndex, setSelectedIndex] = React.useState(0);
18
+ useInput((input, key) => {
19
+ if (key.escape) {
20
+ onCancel();
21
+ return;
22
+ }
23
+ if (key.upArrow) {
24
+ setSelectedIndex((prev) => Math.max(0, prev - 1));
25
+ }
26
+ if (key.downArrow) {
27
+ setSelectedIndex((prev) => Math.min(agents.length - 1, prev + 1));
28
+ }
29
+ if (key.return) {
30
+ if (agents.length > 0) {
31
+ onSelect(agents[selectedIndex].id);
32
+ }
33
+ }
34
+ });
35
+ const formatDuration = (start) => {
36
+ const secs = Math.round((Date.now() - new Date(start).getTime()) / 1e3);
37
+ if (secs < 60) return `${secs}s`;
38
+ const mins = Math.floor(secs / 60);
39
+ const remSecs = secs % 60;
40
+ return `${mins}m ${remSecs}s`;
41
+ };
42
+ const truncate = (s, n) => s.length > n ? s.slice(0, n - 1) + "\u2026" : s;
43
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React.createElement(Box, { borderStyle: "round", borderColor: theme.accent, paddingX: 2, paddingY: 1, marginBottom: 1 }, /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { color: theme.accent, bold: true }, "\u{1F916} Sub-Agents (", agents.length, ")"), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Select a sub-agent to view its activity. Press ESC to return to chat.")))), agents.length === 0 ? /* @__PURE__ */ React.createElement(Box, { paddingX: 2 }, /* @__PURE__ */ React.createElement(Text, { color: "#666666" }, "No sub-agents found.")) : agents.map((agent, index) => {
44
+ const isSelected = index === selectedIndex;
45
+ const statusInfo = STATUS_ICONS[agent.status] || { icon: "\u2753", color: "#666666" };
46
+ return /* @__PURE__ */ React.createElement(
47
+ Box,
48
+ {
49
+ key: agent.id,
50
+ flexDirection: "column",
51
+ borderStyle: isSelected ? "round" : "single",
52
+ borderColor: isSelected ? theme.accent : "#333333",
53
+ paddingX: 1,
54
+ marginBottom: 0
55
+ },
56
+ /* @__PURE__ */ React.createElement(Box, { justifyContent: "space-between" }, /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: isSelected ? theme.accent : "#888888", bold: isSelected }, isSelected ? "\u25B6 " : " ", statusInfo.icon, " ", agent.id), /* @__PURE__ */ React.createElement(Text, { color: statusInfo.color, bold: true }, " [", agent.status, "]")), /* @__PURE__ */ React.createElement(Box, { gap: 2 }, /* @__PURE__ */ React.createElement(Text, { color: "#666666" }, formatDuration(agent.startTime)), /* @__PURE__ */ React.createElement(Text, { color: "#666666" }, "turns: ", agent.turnCount), /* @__PURE__ */ React.createElement(Text, { color: "#666666" }, "files: ", agent.fileOpsCount))),
57
+ /* @__PURE__ */ React.createElement(Box, { marginLeft: 4 }, /* @__PURE__ */ React.createElement(Text, { color: "#888888", dimColor: true }, truncate(agent.prompt, 80))),
58
+ /* @__PURE__ */ React.createElement(Box, { marginLeft: 4 }, /* @__PURE__ */ React.createElement(Text, { color: "#555555", dimColor: true }, "model: ", agent.model))
59
+ );
60
+ }), /* @__PURE__ */ React.createElement(Box, { marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "#666666", dimColor: true }, "\u2191\u2193 Navigate \u2022 Enter to view \u2022 ESC to return")));
61
+ };
62
+ export {
63
+ SubAgentListScreen
64
+ };
65
+ //# sourceMappingURL=SubAgentListScreen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/ui/components/SubAgentListScreen.tsx"],"sourcesContent":["import React from 'react';\r\nimport { Box, Text, useInput } from 'ink';\r\nimport { useTheme } from '../theme.js';\r\n\r\nexport interface SubAgentListItem {\r\n id: string;\r\n status: string;\r\n prompt: string;\r\n model: string;\r\n startTime: Date;\r\n turnCount: number;\r\n fileOpsCount: number;\r\n}\r\n\r\ninterface SubAgentListScreenProps {\r\n agents: SubAgentListItem[];\r\n onSelect: (agentId: string) => void;\r\n onCancel: () => void;\r\n}\r\n\r\nconst STATUS_ICONS: Record<string, { icon: string; color: string }> = {\r\n running: { icon: '🔄', color: '#00cc66' },\r\n pending: { icon: '⏳', color: '#ffaa00' },\r\n completed: { icon: '✅', color: '#00cc66' },\r\n failed: { icon: '❌', color: '#ff3366' },\r\n terminated: { icon: '🛑', color: '#ff6666' },\r\n};\r\n\r\nexport const SubAgentListScreen: React.FC<SubAgentListScreenProps> = ({\r\n agents,\r\n onSelect,\r\n onCancel,\r\n}) => {\r\n const theme = useTheme();\r\n const [selectedIndex, setSelectedIndex] = React.useState(0);\r\n\r\n useInput((input, key) => {\r\n if (key.escape) {\r\n onCancel();\r\n return;\r\n }\r\n if (key.upArrow) {\r\n setSelectedIndex(prev => Math.max(0, prev - 1));\r\n }\r\n if (key.downArrow) {\r\n setSelectedIndex(prev => Math.min(agents.length - 1, prev + 1));\r\n }\r\n if (key.return) {\r\n if (agents.length > 0) {\r\n onSelect(agents[selectedIndex].id);\r\n }\r\n }\r\n });\r\n\r\n const formatDuration = (start: Date) => {\r\n const secs = Math.round((Date.now() - new Date(start).getTime()) / 1000);\r\n if (secs < 60) return `${secs}s`;\r\n const mins = Math.floor(secs / 60);\r\n const remSecs = secs % 60;\r\n return `${mins}m ${remSecs}s`;\r\n };\r\n\r\n const truncate = (s: string, n: number) =>\r\n s.length > n ? s.slice(0, n - 1) + '\\u2026' : s;\r\n\r\n return (\r\n <Box flexDirection=\"column\" paddingX={1}>\r\n <Box borderStyle=\"round\" borderColor={theme.accent} paddingX={2} paddingY={1} marginBottom={1}>\r\n <Box flexDirection=\"column\">\r\n <Text color={theme.accent} bold>🤖 Sub-Agents ({agents.length})</Text>\r\n <Box marginTop={1}>\r\n <Text dimColor>Select a sub-agent to view its activity. Press ESC to return to chat.</Text>\r\n </Box>\r\n </Box>\r\n </Box>\r\n\r\n {agents.length === 0 ? (\r\n <Box paddingX={2}>\r\n <Text color=\"#666666\">No sub-agents found.</Text>\r\n </Box>\r\n ) : (\r\n agents.map((agent, index) => {\r\n const isSelected = index === selectedIndex;\r\n const statusInfo = STATUS_ICONS[agent.status] || { icon: '❓', color: '#666666' };\r\n\r\n return (\r\n <Box\r\n key={agent.id}\r\n flexDirection=\"column\"\r\n borderStyle={isSelected ? 'round' : 'single'}\r\n borderColor={isSelected ? theme.accent : '#333333'}\r\n paddingX={1}\r\n marginBottom={0}\r\n >\r\n <Box justifyContent=\"space-between\">\r\n <Box>\r\n <Text color={isSelected ? theme.accent : '#888888'} bold={isSelected}>\r\n {isSelected ? '▶ ' : ' '}{statusInfo.icon} {agent.id}\r\n </Text>\r\n <Text color={statusInfo.color} bold> [{agent.status}]</Text>\r\n </Box>\r\n <Box gap={2}>\r\n <Text color=\"#666666\">{formatDuration(agent.startTime)}</Text>\r\n <Text color=\"#666666\">turns: {agent.turnCount}</Text>\r\n <Text color=\"#666666\">files: {agent.fileOpsCount}</Text>\r\n </Box>\r\n </Box>\r\n <Box marginLeft={4}>\r\n <Text color=\"#888888\" dimColor>{truncate(agent.prompt, 80)}</Text>\r\n </Box>\r\n <Box marginLeft={4}>\r\n <Text color=\"#555555\" dimColor>model: {agent.model}</Text>\r\n </Box>\r\n </Box>\r\n );\r\n })\r\n )}\r\n\r\n <Box marginTop={1} paddingX={1}>\r\n <Text color=\"#666666\" dimColor>↑↓ Navigate • Enter to view • ESC to return</Text>\r\n </Box>\r\n </Box>\r\n );\r\n};\r\n"],"mappings":"AAAA,OAAO,WAAW;AAClB,SAAS,KAAK,MAAM,gBAAgB;AACpC,SAAS,gBAAgB;AAkBzB,MAAM,eAAgE;AAAA,EACpE,SAAY,EAAE,MAAM,aAAM,OAAO,UAAU;AAAA,EAC3C,SAAY,EAAE,MAAM,UAAK,OAAO,UAAU;AAAA,EAC1C,WAAY,EAAE,MAAM,UAAK,OAAO,UAAU;AAAA,EAC1C,QAAY,EAAE,MAAM,UAAK,OAAO,UAAU;AAAA,EAC1C,YAAY,EAAE,MAAM,aAAM,OAAO,UAAU;AAC7C;AAEO,MAAM,qBAAwD,CAAC;AAAA,EACpE;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,QAAQ,SAAS;AACvB,QAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAS,CAAC;AAE1D,WAAS,CAAC,OAAO,QAAQ;AACvB,QAAI,IAAI,QAAQ;AACd,eAAS;AACT;AAAA,IACF;AACA,QAAI,IAAI,SAAS;AACf,uBAAiB,UAAQ,KAAK,IAAI,GAAG,OAAO,CAAC,CAAC;AAAA,IAChD;AACA,QAAI,IAAI,WAAW;AACjB,uBAAiB,UAAQ,KAAK,IAAI,OAAO,SAAS,GAAG,OAAO,CAAC,CAAC;AAAA,IAChE;AACA,QAAI,IAAI,QAAQ;AACd,UAAI,OAAO,SAAS,GAAG;AACrB,iBAAS,OAAO,aAAa,EAAE,EAAE;AAAA,MACnC;AAAA,IACF;AAAA,EACF,CAAC;AAED,QAAM,iBAAiB,CAAC,UAAgB;AACtC,UAAM,OAAO,KAAK,OAAO,KAAK,IAAI,IAAI,IAAI,KAAK,KAAK,EAAE,QAAQ,KAAK,GAAI;AACvE,QAAI,OAAO,GAAI,QAAO,GAAG,IAAI;AAC7B,UAAM,OAAO,KAAK,MAAM,OAAO,EAAE;AACjC,UAAM,UAAU,OAAO;AACvB,WAAO,GAAG,IAAI,KAAK,OAAO;AAAA,EAC5B;AAEA,QAAM,WAAW,CAAC,GAAW,MAC3B,EAAE,SAAS,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC,IAAI,WAAW;AAEhD,SACE,oCAAC,OAAI,eAAc,UAAS,UAAU,KACpC,oCAAC,OAAI,aAAY,SAAQ,aAAa,MAAM,QAAQ,UAAU,GAAG,UAAU,GAAG,cAAc,KAC1F,oCAAC,OAAI,eAAc,YACjB,oCAAC,QAAK,OAAO,MAAM,QAAQ,MAAI,QAAC,0BAAgB,OAAO,QAAO,GAAC,GAC/D,oCAAC,OAAI,WAAW,KACd,oCAAC,QAAK,UAAQ,QAAC,uEAAqE,CACtF,CACF,CACF,GAEC,OAAO,WAAW,IACjB,oCAAC,OAAI,UAAU,KACb,oCAAC,QAAK,OAAM,aAAU,sBAAoB,CAC5C,IAEA,OAAO,IAAI,CAAC,OAAO,UAAU;AAC3B,UAAM,aAAa,UAAU;AAC7B,UAAM,aAAa,aAAa,MAAM,MAAM,KAAK,EAAE,MAAM,UAAK,OAAO,UAAU;AAE/E,WACE;AAAA,MAAC;AAAA;AAAA,QACC,KAAK,MAAM;AAAA,QACX,eAAc;AAAA,QACd,aAAa,aAAa,UAAU;AAAA,QACpC,aAAa,aAAa,MAAM,SAAS;AAAA,QACzC,UAAU;AAAA,QACV,cAAc;AAAA;AAAA,MAEd,oCAAC,OAAI,gBAAe,mBAClB,oCAAC,WACC,oCAAC,QAAK,OAAO,aAAa,MAAM,SAAS,WAAW,MAAM,cACvD,aAAa,YAAO,MAAM,WAAW,MAAK,KAAE,MAAM,EACrD,GACA,oCAAC,QAAK,OAAO,WAAW,OAAO,MAAI,QAAC,MAAG,MAAM,QAAO,GAAC,CACvD,GACA,oCAAC,OAAI,KAAK,KACR,oCAAC,QAAK,OAAM,aAAW,eAAe,MAAM,SAAS,CAAE,GACvD,oCAAC,QAAK,OAAM,aAAU,WAAQ,MAAM,SAAU,GAC9C,oCAAC,QAAK,OAAM,aAAU,WAAQ,MAAM,YAAa,CACnD,CACF;AAAA,MACA,oCAAC,OAAI,YAAY,KACf,oCAAC,QAAK,OAAM,WAAU,UAAQ,QAAE,SAAS,MAAM,QAAQ,EAAE,CAAE,CAC7D;AAAA,MACA,oCAAC,OAAI,YAAY,KACf,oCAAC,QAAK,OAAM,WAAU,UAAQ,QAAC,WAAQ,MAAM,KAAM,CACrD;AAAA,IACF;AAAA,EAEJ,CAAC,GAGH,oCAAC,OAAI,WAAW,GAAG,UAAU,KAC3B,oCAAC,QAAK,OAAM,WAAU,UAAQ,QAAC,qEAA+C,CAChF,CACF;AAEJ;","names":[]}
@@ -0,0 +1,123 @@
1
+ import React, { useState, useEffect, useRef, useCallback } from "react";
2
+ import { Box, Text, Static, useInput, useStdout } from "ink";
3
+ import { useTheme } from "../theme.js";
4
+ import { SubAgentManager } from "../../services/sub-agent-manager.js";
5
+ import { MessageDisplay } from "./MessageDisplay.js";
6
+ import { ToolExecutionMessage } from "./ToolExecutionMessage.js";
7
+ const SubAgentViewScreen = ({
8
+ agentId,
9
+ onBack,
10
+ onTerminate,
11
+ onAutoReturn
12
+ }) => {
13
+ const theme = useTheme();
14
+ const { stdout } = useStdout();
15
+ useEffect(() => {
16
+ process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
17
+ }, []);
18
+ const [messages, setMessages] = useState([]);
19
+ const [currentMessage, setCurrentMessage] = useState(null);
20
+ const [agentStatus, setAgentStatus] = useState("pending");
21
+ const [agentModel, setAgentModel] = useState("");
22
+ const [agentPrompt, setAgentPrompt] = useState("");
23
+ const prevCountRef = useRef(0);
24
+ const hasAutoReturnedRef = useRef(false);
25
+ const buildMessages = useCallback(() => {
26
+ const agent = SubAgentManager.getSubAgent(agentId);
27
+ if (!agent) return;
28
+ setAgentStatus(agent.status);
29
+ setAgentModel(agent.model);
30
+ setAgentPrompt(agent.prompt);
31
+ const newMessages = [];
32
+ let msgIdx = 0;
33
+ if (prevCountRef.current === 0 || newMessages.length === 0) {
34
+ newMessages.push({
35
+ id: `sa-header-${agentId}`,
36
+ role: "system",
37
+ content: `\u{1F916} **Sub-Agent ${agentId}** | Model: ${agent.model} | Task: ${agent.prompt}`,
38
+ timestamp: agent.startTime
39
+ });
40
+ }
41
+ for (const tool of agent.toolHistory) {
42
+ msgIdx++;
43
+ const toolExec = {
44
+ toolName: tool.toolName,
45
+ status: tool.success ? "completed" : "error",
46
+ result: tool.result,
47
+ error: tool.success ? void 0 : tool.result,
48
+ arguments: tool.arguments,
49
+ timestamp: tool.timestamp
50
+ };
51
+ newMessages.push({
52
+ id: `sa-tool-${agentId}-${msgIdx}`,
53
+ role: "tool",
54
+ content: "",
55
+ timestamp: tool.timestamp,
56
+ toolExecution: toolExec,
57
+ shouldStream: false
58
+ });
59
+ }
60
+ for (const msg of agent.conversationHistory) {
61
+ if (msg.role === "assistant" && msg.content && msg.content.trim() && (!msg.tool_calls || msg.tool_calls.length === 0)) {
62
+ msgIdx++;
63
+ newMessages.push({
64
+ id: `sa-text-${agentId}-${msgIdx}`,
65
+ role: "assistant",
66
+ content: msg.content,
67
+ timestamp: /* @__PURE__ */ new Date(),
68
+ shouldStream: false
69
+ });
70
+ }
71
+ }
72
+ if (agent.status === "completed" || agent.status === "failed" || agent.status === "terminated") {
73
+ const statusEmoji = agent.status === "completed" ? "\u2705" : agent.status === "failed" ? "\u274C" : "\u{1F6D1}";
74
+ const durationSec = Math.round(((agent.endTime || /* @__PURE__ */ new Date()).getTime() - agent.startTime.getTime()) / 1e3);
75
+ newMessages.push({
76
+ id: `sa-done-${agentId}`,
77
+ role: "system",
78
+ content: `${statusEmoji} **Sub-agent ${agent.status}** after ${durationSec}s \u2014 ${agent.fileOperations.length} file operations, ${agent.toolHistory.length} tool calls.${agent.result ? "\n\n" + agent.result : ""}${agent.error ? "\n\n\u26A0\uFE0F " + agent.error : ""}`,
79
+ timestamp: agent.endTime || /* @__PURE__ */ new Date()
80
+ });
81
+ }
82
+ const total = newMessages.length;
83
+ if (total > 0 && (agent.status === "running" || agent.status === "pending")) {
84
+ setMessages(newMessages.slice(0, total - 1));
85
+ setCurrentMessage(newMessages[total - 1]);
86
+ } else {
87
+ setMessages(newMessages);
88
+ setCurrentMessage(null);
89
+ }
90
+ prevCountRef.current = agent.toolHistory.length;
91
+ if ((agent.status === "completed" || agent.status === "failed" || agent.status === "terminated") && !hasAutoReturnedRef.current) {
92
+ hasAutoReturnedRef.current = true;
93
+ setTimeout(() => onAutoReturn(), 2e3);
94
+ }
95
+ }, [agentId, onAutoReturn]);
96
+ useEffect(() => {
97
+ buildMessages();
98
+ const interval = setInterval(buildMessages, 500);
99
+ return () => clearInterval(interval);
100
+ }, [buildMessages]);
101
+ useInput((input, key) => {
102
+ if (key.escape) {
103
+ onBack();
104
+ return;
105
+ }
106
+ if (input === "t" || input === "T") {
107
+ if (agentStatus === "running" || agentStatus === "pending") {
108
+ onTerminate(agentId);
109
+ }
110
+ }
111
+ });
112
+ const isActive = agentStatus === "running" || agentStatus === "pending";
113
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Static, { items: messages }, (item) => {
114
+ if (item.toolExecution) {
115
+ return /* @__PURE__ */ React.createElement(ToolExecutionMessage, { key: item.id, message: item });
116
+ }
117
+ return /* @__PURE__ */ React.createElement(MessageDisplay, { key: item.id, message: item, fileCountBefore: 0 });
118
+ }), currentMessage && (currentMessage.toolExecution ? /* @__PURE__ */ React.createElement(ToolExecutionMessage, { key: currentMessage.id, message: currentMessage }) : /* @__PURE__ */ React.createElement(MessageDisplay, { key: currentMessage.id, message: currentMessage, fileCountBefore: 0 })), /* @__PURE__ */ React.createElement(Box, { marginTop: 1, justifyContent: "space-between", paddingX: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "#666666", dimColor: true }, "ESC to go back", isActive ? " \u2022 T to terminate" : ""), /* @__PURE__ */ React.createElement(Box, { gap: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "#666666" }, "\u{1F916} ", agentId), isActive && /* @__PURE__ */ React.createElement(Text, { color: "#00cc66" }, "\u25CF Live"), !isActive && /* @__PURE__ */ React.createElement(Text, { color: "#666666" }, "\u25CF ", agentStatus))));
119
+ };
120
+ export {
121
+ SubAgentViewScreen
122
+ };
123
+ //# sourceMappingURL=SubAgentViewScreen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/ui/components/SubAgentViewScreen.tsx"],"sourcesContent":["/**\r\n * SubAgentViewScreen — read-only chat view that mirrors the main chat UI.\r\n *\r\n * Converts the sub-agent's toolHistory and conversationHistory into the\r\n * same Message objects used by MessageDisplay / ToolExecutionMessage,\r\n * then renders them via Ink's <Static> so they look identical to the\r\n * normal chat. No input bar is shown.\r\n *\r\n * Auto-returns to the main chat when the sub-agent completes.\r\n */\r\n\r\nimport React, { useState, useEffect, useRef, useCallback } from 'react';\r\nimport { Box, Text, Static, useInput, useStdout } from 'ink';\r\nimport { useTheme } from '../theme.js';\r\nimport { SubAgentManager } from '../../services/sub-agent-manager.js';\r\nimport { MessageDisplay } from './MessageDisplay.js';\r\nimport { ToolExecutionMessage } from './ToolExecutionMessage.js';\r\nimport { Message, ToolExecution as UIToolExecution } from '../../types/index.js';\r\nimport { ToolExecution as SubAgentToolExec } from '../../services/sub-agent-manager.js';\r\n\r\ninterface SubAgentViewScreenProps {\r\n agentId: string;\r\n onBack: () => void;\r\n onTerminate: (agentId: string) => void;\r\n onAutoReturn: () => void; // Called when sub-agent completes\r\n}\r\n\r\nexport const SubAgentViewScreen: React.FC<SubAgentViewScreenProps> = ({\r\n agentId,\r\n onBack,\r\n onTerminate,\r\n onAutoReturn,\r\n}) => {\r\n const theme = useTheme();\r\n const { stdout } = useStdout();\r\n\r\n // Clear terminal on mount (single immediate clear — no delayed clears\r\n // which would erase the component's own rendered content)\r\n useEffect(() => {\r\n process.stdout.write('\\x1b[2J\\x1b[3J\\x1b[H');\r\n }, []);\r\n\r\n const [messages, setMessages] = useState<Message[]>([]);\r\n const [currentMessage, setCurrentMessage] = useState<Message | null>(null);\r\n const [agentStatus, setAgentStatus] = useState('pending');\r\n const [agentModel, setAgentModel] = useState('');\r\n const [agentPrompt, setAgentPrompt] = useState('');\r\n const prevCountRef = useRef(0);\r\n const hasAutoReturnedRef = useRef(false);\r\n\r\n // Convert sub-agent tool executions into UI Message objects\r\n const buildMessages = useCallback(() => {\r\n const agent = SubAgentManager.getSubAgent(agentId);\r\n if (!agent) return;\r\n\r\n setAgentStatus(agent.status);\r\n setAgentModel(agent.model);\r\n setAgentPrompt(agent.prompt);\r\n\r\n const newMessages: Message[] = [];\r\n let msgIdx = 0;\r\n\r\n // Header message showing what the sub-agent is doing\r\n if (prevCountRef.current === 0 || newMessages.length === 0) {\r\n newMessages.push({\r\n id: `sa-header-${agentId}`,\r\n role: 'system',\r\n content: `🤖 **Sub-Agent ${agentId}** | Model: ${agent.model} | Task: ${agent.prompt}`,\r\n timestamp: agent.startTime,\r\n });\r\n }\r\n\r\n // Build messages from tool history — each tool becomes a tool execution message\r\n for (const tool of agent.toolHistory) {\r\n msgIdx++;\r\n const toolExec: UIToolExecution = {\r\n toolName: tool.toolName,\r\n status: tool.success ? 'completed' : 'error',\r\n result: tool.result,\r\n error: tool.success ? undefined : tool.result,\r\n arguments: tool.arguments,\r\n timestamp: tool.timestamp,\r\n };\r\n\r\n newMessages.push({\r\n id: `sa-tool-${agentId}-${msgIdx}`,\r\n role: 'tool',\r\n content: '',\r\n timestamp: tool.timestamp,\r\n toolExecution: toolExec,\r\n shouldStream: false,\r\n });\r\n }\r\n\r\n // Add assistant text messages from conversation history (non-tool-call ones)\r\n for (const msg of agent.conversationHistory) {\r\n if (msg.role === 'assistant' && msg.content && msg.content.trim() && (!msg.tool_calls || msg.tool_calls.length === 0)) {\r\n msgIdx++;\r\n newMessages.push({\r\n id: `sa-text-${agentId}-${msgIdx}`,\r\n role: 'assistant',\r\n content: msg.content,\r\n timestamp: new Date(),\r\n shouldStream: false,\r\n });\r\n }\r\n }\r\n\r\n // Completion / failure / termination message\r\n if (agent.status === 'completed' || agent.status === 'failed' || agent.status === 'terminated') {\r\n const statusEmoji = agent.status === 'completed' ? '✅' : agent.status === 'failed' ? '❌' : '🛑';\r\n const durationSec = Math.round(((agent.endTime || new Date()).getTime() - agent.startTime.getTime()) / 1000);\r\n newMessages.push({\r\n id: `sa-done-${agentId}`,\r\n role: 'system',\r\n content: `${statusEmoji} **Sub-agent ${agent.status}** after ${durationSec}s — ${agent.fileOperations.length} file operations, ${agent.toolHistory.length} tool calls.${agent.result ? '\\n\\n' + agent.result : ''}${agent.error ? '\\n\\n⚠️ ' + agent.error : ''}`,\r\n timestamp: agent.endTime || new Date(),\r\n });\r\n }\r\n\r\n // Split: committed history vs current dynamic message\r\n const total = newMessages.length;\r\n if (total > 0 && (agent.status === 'running' || agent.status === 'pending')) {\r\n setMessages(newMessages.slice(0, total - 1));\r\n setCurrentMessage(newMessages[total - 1]);\r\n } else {\r\n setMessages(newMessages);\r\n setCurrentMessage(null);\r\n }\r\n\r\n prevCountRef.current = agent.toolHistory.length;\r\n\r\n // Auto-return when sub-agent finishes\r\n if ((agent.status === 'completed' || agent.status === 'failed' || agent.status === 'terminated') && !hasAutoReturnedRef.current) {\r\n hasAutoReturnedRef.current = true;\r\n // Small delay so user can see the completion message\r\n setTimeout(() => onAutoReturn(), 2000);\r\n }\r\n }, [agentId, onAutoReturn]);\r\n\r\n // Poll for live updates\r\n useEffect(() => {\r\n buildMessages();\r\n const interval = setInterval(buildMessages, 500);\r\n return () => clearInterval(interval);\r\n }, [buildMessages]);\r\n\r\n useInput((input, key) => {\r\n if (key.escape) {\r\n onBack();\r\n return;\r\n }\r\n if (input === 't' || input === 'T') {\r\n if (agentStatus === 'running' || agentStatus === 'pending') {\r\n onTerminate(agentId);\r\n }\r\n }\r\n });\r\n\r\n const isActive = agentStatus === 'running' || agentStatus === 'pending';\r\n\r\n return (\r\n <Box flexDirection=\"column\">\r\n {/* Static messages — renders like the main chat */}\r\n <Static items={messages}>\r\n {(item) => {\r\n if (item.toolExecution) {\r\n return <ToolExecutionMessage key={item.id} message={item} />;\r\n }\r\n return <MessageDisplay key={item.id} message={item} fileCountBefore={0} />;\r\n }}\r\n </Static>\r\n\r\n {/* Current dynamic message (latest, still updating) */}\r\n {currentMessage && (\r\n currentMessage.toolExecution\r\n ? <ToolExecutionMessage key={currentMessage.id} message={currentMessage} />\r\n : <MessageDisplay key={currentMessage.id} message={currentMessage} fileCountBefore={0} />\r\n )}\r\n\r\n {/* Footer bar — no input box, just status info */}\r\n <Box marginTop={1} justifyContent=\"space-between\" paddingX={1}>\r\n <Text color=\"#666666\" dimColor>\r\n ESC to go back{isActive ? ' • T to terminate' : ''}\r\n </Text>\r\n <Box gap={1}>\r\n <Text color=\"#666666\">🤖 {agentId}</Text>\r\n {isActive && <Text color=\"#00cc66\">● Live</Text>}\r\n {!isActive && <Text color=\"#666666\">● {agentStatus}</Text>}\r\n </Box>\r\n </Box>\r\n </Box>\r\n );\r\n};\r\n"],"mappings":"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","names":[]}
@@ -1,25 +1,57 @@
1
1
  import React from "react";
2
2
  import { Box, Text } from "ink";
3
+ import { useTheme } from "../theme.js";
3
4
  const TaskCompletedMessage = ({
4
- taskNumber,
5
- totalTasks,
6
- taskDescription,
7
- completionNote
5
+ completedStepNumber,
6
+ completedStepDescription,
7
+ completedCount,
8
+ totalCount,
9
+ completionNote,
10
+ allSteps
8
11
  }) => {
12
+ const theme = useTheme();
13
+ const nextPendingId = allSteps?.find((s) => s.status === "pending")?.id ?? null;
9
14
  return /* @__PURE__ */ React.createElement(
10
15
  Box,
11
16
  {
12
17
  flexDirection: "column",
13
18
  borderStyle: "round",
14
- borderColor: "#f59e0b",
19
+ borderColor: theme.accent,
15
20
  paddingX: 1,
16
21
  paddingY: 0,
17
22
  marginY: 0,
18
23
  alignSelf: "flex-start"
19
24
  },
20
- /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: "#f59e0b", bold: true }, "[x] "), /* @__PURE__ */ React.createElement(Text, { color: "#f59e0b", bold: true }, "Task ", taskNumber, " of ", totalTasks, " Completed")),
21
- /* @__PURE__ */ React.createElement(Box, { marginLeft: 2 }, /* @__PURE__ */ React.createElement(Text, { color: "#888888", wrap: "wrap" }, taskDescription)),
22
- completionNote && /* @__PURE__ */ React.createElement(Box, { marginLeft: 2, marginTop: 0 }, /* @__PURE__ */ React.createElement(Text, { color: "#666666", dimColor: true, wrap: "wrap" }, "Note: ", completionNote))
25
+ allSteps && allSteps.length > 0 && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, allSteps.map((step) => {
26
+ const isCompleted = step.status === "completed";
27
+ const isNextPending = step.id === nextPendingId;
28
+ let marker;
29
+ if (isCompleted) {
30
+ marker = /* @__PURE__ */ React.createElement(Text, { color: "#00cc66" }, "\u2713", " ");
31
+ } else if (isNextPending) {
32
+ marker = /* @__PURE__ */ React.createElement(Text, { color: "#f59e0b" }, "\u25D4", " ");
33
+ } else {
34
+ marker = /* @__PURE__ */ React.createElement(Text, { color: "#555555" }, " ");
35
+ }
36
+ let textColor;
37
+ if (isCompleted) {
38
+ textColor = "#888888";
39
+ } else if (isNextPending) {
40
+ textColor = "#f59e0b";
41
+ } else {
42
+ textColor = "#555555";
43
+ }
44
+ return /* @__PURE__ */ React.createElement(Box, { key: step.id }, marker, /* @__PURE__ */ React.createElement(
45
+ Text,
46
+ {
47
+ color: textColor,
48
+ wrap: "truncate-end"
49
+ },
50
+ step.id,
51
+ ". ",
52
+ step.description
53
+ ));
54
+ }))
23
55
  );
24
56
  };
25
57
  var TaskCompletedMessage_default = TaskCompletedMessage;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/ui/components/TaskCompletedMessage.tsx"],"sourcesContent":["/**\r\n * TaskCompletedMessage Component\r\n * Displays a task completion message in the chat with checkbox styling\r\n */\r\n\r\nimport React from 'react';\r\nimport { Box, Text } from 'ink';\r\n\r\ninterface TaskCompletedMessageProps {\r\n taskNumber: number;\r\n totalTasks: number;\r\n taskDescription: string;\r\n completionNote?: string;\r\n}\r\n\r\nexport const TaskCompletedMessage: React.FC<TaskCompletedMessageProps> = ({\r\n taskNumber,\r\n totalTasks,\r\n taskDescription,\r\n completionNote\r\n}) => {\r\n return (\r\n <Box\r\n flexDirection=\"column\"\r\n borderStyle=\"round\"\r\n borderColor=\"#f59e0b\"\r\n paddingX={1}\r\n paddingY={0}\r\n marginY={0}\r\n alignSelf=\"flex-start\"\r\n >\r\n <Box>\r\n <Text color=\"#f59e0b\" bold>[x] </Text>\r\n <Text color=\"#f59e0b\" bold>Task {taskNumber} of {totalTasks} Completed</Text>\r\n </Box>\r\n <Box marginLeft={2}>\r\n <Text color=\"#888888\" wrap=\"wrap\">{taskDescription}</Text>\r\n </Box>\r\n {completionNote && (\r\n <Box marginLeft={2} marginTop={0}>\r\n <Text color=\"#666666\" dimColor wrap=\"wrap\">Note: {completionNote}</Text>\r\n </Box>\r\n )}\r\n </Box>\r\n );\r\n};\r\n\r\nexport default TaskCompletedMessage;\r\n"],"mappings":"AAKA,OAAO,WAAW;AAClB,SAAS,KAAK,YAAY;AASnB,MAAM,uBAA4D,CAAC;AAAA,EACtE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ,MAAM;AACF,SACI;AAAA,IAAC;AAAA;AAAA,MACG,eAAc;AAAA,MACd,aAAY;AAAA,MACZ,aAAY;AAAA,MACZ,UAAU;AAAA,MACV,UAAU;AAAA,MACV,SAAS;AAAA,MACT,WAAU;AAAA;AAAA,IAEV,oCAAC,WACG,oCAAC,QAAK,OAAM,WAAU,MAAI,QAAC,MAAI,GAC/B,oCAAC,QAAK,OAAM,WAAU,MAAI,QAAC,SAAM,YAAW,QAAK,YAAW,YAAU,CAC1E;AAAA,IACA,oCAAC,OAAI,YAAY,KACb,oCAAC,QAAK,OAAM,WAAU,MAAK,UAAQ,eAAgB,CACvD;AAAA,IACC,kBACG,oCAAC,OAAI,YAAY,GAAG,WAAW,KAC3B,oCAAC,QAAK,OAAM,WAAU,UAAQ,MAAC,MAAK,UAAO,UAAO,cAAe,CACrE;AAAA,EAER;AAER;AAEA,IAAO,+BAAQ;","names":[]}
1
+ {"version":3,"sources":["../../../src/ui/components/TaskCompletedMessage.tsx"],"sourcesContent":["/**\r\n * TaskCompletedMessage Component\r\n * Shows the full implementation steps list with tick marks for completed steps.\r\n * - Completed steps get a green tick mark\r\n * - The next pending step (being worked on) gets a yellow spinner marker\r\n * - Future pending steps have no marker\r\n * Wrapped in a bordered box for visual consistency in the chat.\r\n */\r\n\r\nimport React from 'react';\r\nimport { Box, Text } from 'ink';\r\nimport { useTheme } from '../theme.js';\r\n\r\ninterface StepStatus {\r\n id: number;\r\n description: string;\r\n status: 'pending' | 'completed';\r\n}\r\n\r\ninterface TaskCompletedMessageProps {\r\n completedStepNumber: number;\r\n completedStepDescription: string;\r\n completedCount: number;\r\n totalCount: number;\r\n completionNote?: string;\r\n allSteps?: StepStatus[];\r\n}\r\n\r\nexport const TaskCompletedMessage: React.FC<TaskCompletedMessageProps> = ({\r\n completedStepNumber,\r\n completedStepDescription,\r\n completedCount,\r\n totalCount,\r\n completionNote,\r\n allSteps\r\n}) => {\r\n const theme = useTheme();\r\n const nextPendingId = allSteps?.find(s => s.status === 'pending')?.id ?? null;\r\n\r\n return (\r\n <Box\r\n flexDirection=\"column\"\r\n borderStyle=\"round\"\r\n borderColor={theme.accent}\r\n paddingX={1}\r\n paddingY={0}\r\n marginY={0}\r\n alignSelf=\"flex-start\"\r\n >\r\n {allSteps && allSteps.length > 0 && (\r\n <Box flexDirection=\"column\">\r\n {allSteps.map((step) => {\r\n const isCompleted = step.status === 'completed';\r\n const isNextPending = step.id === nextPendingId;\r\n\r\n let marker: React.ReactNode;\r\n if (isCompleted) {\r\n marker = <Text color=\"#00cc66\">{'\\u2713'} </Text>;\r\n } else if (isNextPending) {\r\n marker = <Text color=\"#f59e0b\">{'\\u25D4'} </Text>;\r\n } else {\r\n marker = <Text color=\"#555555\"> </Text>;\r\n }\r\n\r\n let textColor: string;\r\n if (isCompleted) {\r\n textColor = '#888888';\r\n } else if (isNextPending) {\r\n textColor = '#f59e0b';\r\n } else {\r\n textColor = '#555555';\r\n }\r\n\r\n return (\r\n <Box key={step.id}>\r\n {marker}\r\n <Text\r\n color={textColor}\r\n wrap=\"truncate-end\"\r\n >\r\n {step.id}. {step.description}\r\n </Text>\r\n </Box>\r\n );\r\n })}\r\n </Box>\r\n )}\r\n </Box>\r\n );\r\n};\r\n\r\nexport default TaskCompletedMessage;\r\n"],"mappings":"AASA,OAAO,WAAW;AAClB,SAAS,KAAK,YAAY;AAC1B,SAAS,gBAAgB;AAiBlB,MAAM,uBAA4D,CAAC;AAAA,EACtE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ,MAAM;AACF,QAAM,QAAQ,SAAS;AACvB,QAAM,gBAAgB,UAAU,KAAK,OAAK,EAAE,WAAW,SAAS,GAAG,MAAM;AAEzE,SACI;AAAA,IAAC;AAAA;AAAA,MACG,eAAc;AAAA,MACd,aAAY;AAAA,MACZ,aAAa,MAAM;AAAA,MACnB,UAAU;AAAA,MACV,UAAU;AAAA,MACV,SAAS;AAAA,MACT,WAAU;AAAA;AAAA,IAET,YAAY,SAAS,SAAS,KAC3B,oCAAC,OAAI,eAAc,YACd,SAAS,IAAI,CAAC,SAAS;AACpB,YAAM,cAAc,KAAK,WAAW;AACpC,YAAM,gBAAgB,KAAK,OAAO;AAElC,UAAI;AACJ,UAAI,aAAa;AACb,iBAAS,oCAAC,QAAK,OAAM,aAAW,UAAS,GAAC;AAAA,MAC9C,WAAW,eAAe;AACtB,iBAAS,oCAAC,QAAK,OAAM,aAAW,UAAS,GAAC;AAAA,MAC9C,OAAO;AACH,iBAAS,oCAAC,QAAK,OAAM,aAAU,IAAE;AAAA,MACrC;AAEA,UAAI;AACJ,UAAI,aAAa;AACb,oBAAY;AAAA,MAChB,WAAW,eAAe;AACtB,oBAAY;AAAA,MAChB,OAAO;AACH,oBAAY;AAAA,MAChB;AAEA,aACI,oCAAC,OAAI,KAAK,KAAK,MACV,QACD;AAAA,QAAC;AAAA;AAAA,UACG,OAAO;AAAA,UACP,MAAK;AAAA;AAAA,QAEJ,KAAK;AAAA,QAAG;AAAA,QAAG,KAAK;AAAA,MACrB,CACJ;AAAA,IAER,CAAC,CACL;AAAA,EAER;AAER;AAEA,IAAO,+BAAQ;","names":[]}
@@ -1,5 +1,6 @@
1
1
  import React from "react";
2
2
  import { Box, Text } from "ink";
3
+ import { useTheme } from "../theme.js";
3
4
  const TaskProgressIndicator = ({
4
5
  currentTask,
5
6
  totalTasks,
@@ -8,6 +9,7 @@ const TaskProgressIndicator = ({
8
9
  taskDescription,
9
10
  compact = false
10
11
  }) => {
12
+ const theme = useTheme();
11
13
  const tasksComplete = currentTask - 1;
12
14
  const subtasksComplete = currentSubtask ? currentSubtask - 1 : 0;
13
15
  let overallProgress = tasksComplete / totalTasks * 100;
@@ -20,21 +22,21 @@ const TaskProgressIndicator = ({
20
22
  const filledWidth = Math.round(overallProgress / 100 * barWidth);
21
23
  const progressBar = "\u2588".repeat(filledWidth) + "\u2591".repeat(barWidth - filledWidth);
22
24
  if (compact) {
23
- return /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: "#00ccff" }, "\u{1F4CA} "), /* @__PURE__ */ React.createElement(Text, { color: "#888888" }, "Task ", currentTask, "/", totalTasks), currentSubtask !== void 0 && totalSubtasks !== void 0 && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Text, { color: "#666666" }, " | "), /* @__PURE__ */ React.createElement(Text, { color: "#888888" }, "Step ", currentSubtask, "/", totalSubtasks)), /* @__PURE__ */ React.createElement(Text, { color: "#666666" }, " | "), /* @__PURE__ */ React.createElement(Text, { color: "#00cc66" }, overallProgress, "%"));
25
+ return /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: theme.accent }, "\u{1F4CA} "), /* @__PURE__ */ React.createElement(Text, { color: "#888888" }, "Task ", currentTask, "/", totalTasks), currentSubtask !== void 0 && totalSubtasks !== void 0 && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Text, { color: "#666666" }, " | "), /* @__PURE__ */ React.createElement(Text, { color: "#888888" }, "Step ", currentSubtask, "/", totalSubtasks)), /* @__PURE__ */ React.createElement(Text, { color: "#666666" }, " | "), /* @__PURE__ */ React.createElement(Text, { color: "#00cc66" }, overallProgress, "%"));
24
26
  }
25
27
  return /* @__PURE__ */ React.createElement(
26
28
  Box,
27
29
  {
28
30
  flexDirection: "column",
29
31
  borderStyle: "round",
30
- borderColor: "#00ccff",
32
+ borderColor: theme.accent,
31
33
  paddingX: 1,
32
34
  paddingY: 0,
33
35
  marginY: 0
34
36
  },
35
- /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: "#00ccff", bold: true }, "\u{1F4CA} Progress")),
37
+ /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: theme.accent, bold: true }, "\u{1F4CA} Progress")),
36
38
  /* @__PURE__ */ React.createElement(Box, { marginTop: 0 }, /* @__PURE__ */ React.createElement(Text, { color: "#888888" }, "["), /* @__PURE__ */ React.createElement(Text, { color: "#00cc66" }, progressBar), /* @__PURE__ */ React.createElement(Text, { color: "#888888" }, "] "), /* @__PURE__ */ React.createElement(Text, { color: "#00cc66", bold: true }, overallProgress, "%")),
37
- /* @__PURE__ */ React.createElement(Box, { marginTop: 0 }, /* @__PURE__ */ React.createElement(Text, { color: "#ffffff" }, "Task "), /* @__PURE__ */ React.createElement(Text, { color: "#00ccff", bold: true }, currentTask), /* @__PURE__ */ React.createElement(Text, { color: "#666666" }, " / "), /* @__PURE__ */ React.createElement(Text, { color: "#888888" }, totalTasks), currentSubtask !== void 0 && totalSubtasks !== void 0 && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Text, { color: "#666666" }, " \u2022 "), /* @__PURE__ */ React.createElement(Text, { color: "#ffffff" }, "Subtask "), /* @__PURE__ */ React.createElement(Text, { color: "#ffaa00", bold: true }, currentSubtask), /* @__PURE__ */ React.createElement(Text, { color: "#666666" }, " / "), /* @__PURE__ */ React.createElement(Text, { color: "#888888" }, totalSubtasks))),
39
+ /* @__PURE__ */ React.createElement(Box, { marginTop: 0 }, /* @__PURE__ */ React.createElement(Text, { color: "#ffffff" }, "Task "), /* @__PURE__ */ React.createElement(Text, { color: theme.accent, bold: true }, currentTask), /* @__PURE__ */ React.createElement(Text, { color: "#666666" }, " / "), /* @__PURE__ */ React.createElement(Text, { color: "#888888" }, totalTasks), currentSubtask !== void 0 && totalSubtasks !== void 0 && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Text, { color: "#666666" }, " \u2022 "), /* @__PURE__ */ React.createElement(Text, { color: "#ffffff" }, "Subtask "), /* @__PURE__ */ React.createElement(Text, { color: "#ffaa00", bold: true }, currentSubtask), /* @__PURE__ */ React.createElement(Text, { color: "#666666" }, " / "), /* @__PURE__ */ React.createElement(Text, { color: "#888888" }, totalSubtasks))),
38
40
  taskDescription && /* @__PURE__ */ React.createElement(Box, { marginTop: 0 }, /* @__PURE__ */ React.createElement(Text, { color: "#666666", wrap: "truncate-end" }, taskDescription.length > 60 ? taskDescription.substring(0, 60) + "..." : taskDescription))
39
41
  );
40
42
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/ui/components/TaskProgressIndicator.tsx"],"sourcesContent":["/**\r\n * TaskProgressIndicator Component\r\n * \r\n * Displays the current execution progress during phased plan execution\r\n * Shows current task number, subtask progress, and overall completion\r\n */\r\n\r\nimport React from 'react';\r\nimport { Box, Text } from 'ink';\r\n\r\ninterface TaskProgressIndicatorProps {\r\n currentTask: number;\r\n totalTasks: number;\r\n currentSubtask?: number;\r\n totalSubtasks?: number;\r\n taskDescription?: string;\r\n compact?: boolean;\r\n}\r\n\r\nexport const TaskProgressIndicator: React.FC<TaskProgressIndicatorProps> = ({\r\n currentTask,\r\n totalTasks,\r\n currentSubtask,\r\n totalSubtasks,\r\n taskDescription,\r\n compact = false\r\n}) => {\r\n // Calculate overall progress percentage\r\n const tasksComplete = currentTask - 1;\r\n const subtasksComplete = currentSubtask ? currentSubtask - 1 : 0;\r\n\r\n // For overall progress, each task is weighted equally\r\n // If we have subtask info, factor that into current task progress\r\n let overallProgress = (tasksComplete / totalTasks) * 100;\r\n if (totalSubtasks && totalSubtasks > 0) {\r\n const currentTaskProgress = (subtasksComplete / totalSubtasks) / totalTasks * 100;\r\n overallProgress += currentTaskProgress;\r\n }\r\n overallProgress = Math.round(overallProgress);\r\n\r\n // Generate progress bar\r\n const barWidth = 20;\r\n const filledWidth = Math.round((overallProgress / 100) * barWidth);\r\n const progressBar = '█'.repeat(filledWidth) + '░'.repeat(barWidth - filledWidth);\r\n\r\n if (compact) {\r\n return (\r\n <Box>\r\n <Text color=\"#00ccff\">📊 </Text>\r\n <Text color=\"#888888\">Task {currentTask}/{totalTasks}</Text>\r\n {currentSubtask !== undefined && totalSubtasks !== undefined && (\r\n <>\r\n <Text color=\"#666666\"> | </Text>\r\n <Text color=\"#888888\">Step {currentSubtask}/{totalSubtasks}</Text>\r\n </>\r\n )}\r\n <Text color=\"#666666\"> | </Text>\r\n <Text color=\"#00cc66\">{overallProgress}%</Text>\r\n </Box>\r\n );\r\n }\r\n\r\n return (\r\n <Box\r\n flexDirection=\"column\"\r\n borderStyle=\"round\"\r\n borderColor=\"#00ccff\"\r\n paddingX={1}\r\n paddingY={0}\r\n marginY={0}\r\n >\r\n <Box>\r\n <Text color=\"#00ccff\" bold>📊 Progress</Text>\r\n </Box>\r\n\r\n <Box marginTop={0}>\r\n <Text color=\"#888888\">[</Text>\r\n <Text color=\"#00cc66\">{progressBar}</Text>\r\n <Text color=\"#888888\">] </Text>\r\n <Text color=\"#00cc66\" bold>{overallProgress}%</Text>\r\n </Box>\r\n\r\n <Box marginTop={0}>\r\n <Text color=\"#ffffff\">Task </Text>\r\n <Text color=\"#00ccff\" bold>{currentTask}</Text>\r\n <Text color=\"#666666\"> / </Text>\r\n <Text color=\"#888888\">{totalTasks}</Text>\r\n\r\n {currentSubtask !== undefined && totalSubtasks !== undefined && (\r\n <>\r\n <Text color=\"#666666\"> • </Text>\r\n <Text color=\"#ffffff\">Subtask </Text>\r\n <Text color=\"#ffaa00\" bold>{currentSubtask}</Text>\r\n <Text color=\"#666666\"> / </Text>\r\n <Text color=\"#888888\">{totalSubtasks}</Text>\r\n </>\r\n )}\r\n </Box>\r\n\r\n {taskDescription && (\r\n <Box marginTop={0}>\r\n <Text color=\"#666666\" wrap=\"truncate-end\">\r\n {taskDescription.length > 60\r\n ? taskDescription.substring(0, 60) + '...'\r\n : taskDescription}\r\n </Text>\r\n </Box>\r\n )}\r\n </Box>\r\n );\r\n};\r\n\r\nexport default TaskProgressIndicator;\r\n"],"mappings":"AAOA,OAAO,WAAW;AAClB,SAAS,KAAK,YAAY;AAWnB,MAAM,wBAA8D,CAAC;AAAA,EACxE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AACd,MAAM;AAEF,QAAM,gBAAgB,cAAc;AACpC,QAAM,mBAAmB,iBAAiB,iBAAiB,IAAI;AAI/D,MAAI,kBAAmB,gBAAgB,aAAc;AACrD,MAAI,iBAAiB,gBAAgB,GAAG;AACpC,UAAM,sBAAuB,mBAAmB,gBAAiB,aAAa;AAC9E,uBAAmB;AAAA,EACvB;AACA,oBAAkB,KAAK,MAAM,eAAe;AAG5C,QAAM,WAAW;AACjB,QAAM,cAAc,KAAK,MAAO,kBAAkB,MAAO,QAAQ;AACjE,QAAM,cAAc,SAAI,OAAO,WAAW,IAAI,SAAI,OAAO,WAAW,WAAW;AAE/E,MAAI,SAAS;AACT,WACI,oCAAC,WACG,oCAAC,QAAK,OAAM,aAAU,YAAG,GACzB,oCAAC,QAAK,OAAM,aAAU,SAAM,aAAY,KAAE,UAAW,GACpD,mBAAmB,UAAa,kBAAkB,UAC/C,0DACI,oCAAC,QAAK,OAAM,aAAU,KAAG,GACzB,oCAAC,QAAK,OAAM,aAAU,SAAM,gBAAe,KAAE,aAAc,CAC/D,GAEJ,oCAAC,QAAK,OAAM,aAAU,KAAG,GACzB,oCAAC,QAAK,OAAM,aAAW,iBAAgB,GAAC,CAC5C;AAAA,EAER;AAEA,SACI;AAAA,IAAC;AAAA;AAAA,MACG,eAAc;AAAA,MACd,aAAY;AAAA,MACZ,aAAY;AAAA,MACZ,UAAU;AAAA,MACV,UAAU;AAAA,MACV,SAAS;AAAA;AAAA,IAET,oCAAC,WACG,oCAAC,QAAK,OAAM,WAAU,MAAI,QAAC,oBAAW,CAC1C;AAAA,IAEA,oCAAC,OAAI,WAAW,KACZ,oCAAC,QAAK,OAAM,aAAU,GAAC,GACvB,oCAAC,QAAK,OAAM,aAAW,WAAY,GACnC,oCAAC,QAAK,OAAM,aAAU,IAAE,GACxB,oCAAC,QAAK,OAAM,WAAU,MAAI,QAAE,iBAAgB,GAAC,CACjD;AAAA,IAEA,oCAAC,OAAI,WAAW,KACZ,oCAAC,QAAK,OAAM,aAAU,OAAK,GAC3B,oCAAC,QAAK,OAAM,WAAU,MAAI,QAAE,WAAY,GACxC,oCAAC,QAAK,OAAM,aAAU,KAAG,GACzB,oCAAC,QAAK,OAAM,aAAW,UAAW,GAEjC,mBAAmB,UAAa,kBAAkB,UAC/C,0DACI,oCAAC,QAAK,OAAM,aAAU,YAAK,GAC3B,oCAAC,QAAK,OAAM,aAAU,UAAQ,GAC9B,oCAAC,QAAK,OAAM,WAAU,MAAI,QAAE,cAAe,GAC3C,oCAAC,QAAK,OAAM,aAAU,KAAG,GACzB,oCAAC,QAAK,OAAM,aAAW,aAAc,CACzC,CAER;AAAA,IAEC,mBACG,oCAAC,OAAI,WAAW,KACZ,oCAAC,QAAK,OAAM,WAAU,MAAK,kBACtB,gBAAgB,SAAS,KACpB,gBAAgB,UAAU,GAAG,EAAE,IAAI,QACnC,eACV,CACJ;AAAA,EAER;AAER;AAEA,IAAO,gCAAQ;","names":[]}
1
+ {"version":3,"sources":["../../../src/ui/components/TaskProgressIndicator.tsx"],"sourcesContent":["/**\r\n * TaskProgressIndicator Component\r\n * \r\n * Displays the current execution progress during phased plan execution\r\n * Shows current task number, subtask progress, and overall completion\r\n */\r\n\r\nimport React from 'react';\r\nimport { Box, Text } from 'ink';\r\nimport { useTheme } from '../theme.js';\r\n\r\ninterface TaskProgressIndicatorProps {\r\n currentTask: number;\r\n totalTasks: number;\r\n currentSubtask?: number;\r\n totalSubtasks?: number;\r\n taskDescription?: string;\r\n compact?: boolean;\r\n}\r\n\r\nexport const TaskProgressIndicator: React.FC<TaskProgressIndicatorProps> = ({\r\n currentTask,\r\n totalTasks,\r\n currentSubtask,\r\n totalSubtasks,\r\n taskDescription,\r\n compact = false\r\n}) => {\r\n const theme = useTheme();\r\n // Calculate overall progress percentage\r\n const tasksComplete = currentTask - 1;\r\n const subtasksComplete = currentSubtask ? currentSubtask - 1 : 0;\r\n\r\n // For overall progress, each task is weighted equally\r\n // If we have subtask info, factor that into current task progress\r\n let overallProgress = (tasksComplete / totalTasks) * 100;\r\n if (totalSubtasks && totalSubtasks > 0) {\r\n const currentTaskProgress = (subtasksComplete / totalSubtasks) / totalTasks * 100;\r\n overallProgress += currentTaskProgress;\r\n }\r\n overallProgress = Math.round(overallProgress);\r\n\r\n // Generate progress bar\r\n const barWidth = 20;\r\n const filledWidth = Math.round((overallProgress / 100) * barWidth);\r\n const progressBar = '█'.repeat(filledWidth) + '░'.repeat(barWidth - filledWidth);\r\n\r\n if (compact) {\r\n return (\r\n <Box>\r\n <Text color={theme.accent}>📊 </Text>\r\n <Text color=\"#888888\">Task {currentTask}/{totalTasks}</Text>\r\n {currentSubtask !== undefined && totalSubtasks !== undefined && (\r\n <>\r\n <Text color=\"#666666\"> | </Text>\r\n <Text color=\"#888888\">Step {currentSubtask}/{totalSubtasks}</Text>\r\n </>\r\n )}\r\n <Text color=\"#666666\"> | </Text>\r\n <Text color=\"#00cc66\">{overallProgress}%</Text>\r\n </Box>\r\n );\r\n }\r\n\r\n return (\r\n <Box\r\n flexDirection=\"column\"\r\n borderStyle=\"round\"\r\n borderColor={theme.accent}\r\n paddingX={1}\r\n paddingY={0}\r\n marginY={0}\r\n >\r\n <Box>\r\n <Text color={theme.accent} bold>📊 Progress</Text>\r\n </Box>\r\n\r\n <Box marginTop={0}>\r\n <Text color=\"#888888\">[</Text>\r\n <Text color=\"#00cc66\">{progressBar}</Text>\r\n <Text color=\"#888888\">] </Text>\r\n <Text color=\"#00cc66\" bold>{overallProgress}%</Text>\r\n </Box>\r\n\r\n <Box marginTop={0}>\r\n <Text color=\"#ffffff\">Task </Text>\r\n <Text color={theme.accent} bold>{currentTask}</Text>\r\n <Text color=\"#666666\"> / </Text>\r\n <Text color=\"#888888\">{totalTasks}</Text>\r\n\r\n {currentSubtask !== undefined && totalSubtasks !== undefined && (\r\n <>\r\n <Text color=\"#666666\"> • </Text>\r\n <Text color=\"#ffffff\">Subtask </Text>\r\n <Text color=\"#ffaa00\" bold>{currentSubtask}</Text>\r\n <Text color=\"#666666\"> / </Text>\r\n <Text color=\"#888888\">{totalSubtasks}</Text>\r\n </>\r\n )}\r\n </Box>\r\n\r\n {taskDescription && (\r\n <Box marginTop={0}>\r\n <Text color=\"#666666\" wrap=\"truncate-end\">\r\n {taskDescription.length > 60\r\n ? taskDescription.substring(0, 60) + '...'\r\n : taskDescription}\r\n </Text>\r\n </Box>\r\n )}\r\n </Box>\r\n );\r\n};\r\n\r\nexport default TaskProgressIndicator;\r\n"],"mappings":"AAOA,OAAO,WAAW;AAClB,SAAS,KAAK,YAAY;AAC1B,SAAS,gBAAgB;AAWlB,MAAM,wBAA8D,CAAC;AAAA,EACxE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AACd,MAAM;AACF,QAAM,QAAQ,SAAS;AAEvB,QAAM,gBAAgB,cAAc;AACpC,QAAM,mBAAmB,iBAAiB,iBAAiB,IAAI;AAI/D,MAAI,kBAAmB,gBAAgB,aAAc;AACrD,MAAI,iBAAiB,gBAAgB,GAAG;AACpC,UAAM,sBAAuB,mBAAmB,gBAAiB,aAAa;AAC9E,uBAAmB;AAAA,EACvB;AACA,oBAAkB,KAAK,MAAM,eAAe;AAG5C,QAAM,WAAW;AACjB,QAAM,cAAc,KAAK,MAAO,kBAAkB,MAAO,QAAQ;AACjE,QAAM,cAAc,SAAI,OAAO,WAAW,IAAI,SAAI,OAAO,WAAW,WAAW;AAE/E,MAAI,SAAS;AACT,WACI,oCAAC,WACG,oCAAC,QAAK,OAAO,MAAM,UAAQ,YAAG,GAC9B,oCAAC,QAAK,OAAM,aAAU,SAAM,aAAY,KAAE,UAAW,GACpD,mBAAmB,UAAa,kBAAkB,UAC/C,0DACI,oCAAC,QAAK,OAAM,aAAU,KAAG,GACzB,oCAAC,QAAK,OAAM,aAAU,SAAM,gBAAe,KAAE,aAAc,CAC/D,GAEJ,oCAAC,QAAK,OAAM,aAAU,KAAG,GACzB,oCAAC,QAAK,OAAM,aAAW,iBAAgB,GAAC,CAC5C;AAAA,EAER;AAEA,SACI;AAAA,IAAC;AAAA;AAAA,MACG,eAAc;AAAA,MACd,aAAY;AAAA,MACZ,aAAa,MAAM;AAAA,MACnB,UAAU;AAAA,MACV,UAAU;AAAA,MACV,SAAS;AAAA;AAAA,IAET,oCAAC,WACG,oCAAC,QAAK,OAAO,MAAM,QAAQ,MAAI,QAAC,oBAAW,CAC/C;AAAA,IAEA,oCAAC,OAAI,WAAW,KACZ,oCAAC,QAAK,OAAM,aAAU,GAAC,GACvB,oCAAC,QAAK,OAAM,aAAW,WAAY,GACnC,oCAAC,QAAK,OAAM,aAAU,IAAE,GACxB,oCAAC,QAAK,OAAM,WAAU,MAAI,QAAE,iBAAgB,GAAC,CACjD;AAAA,IAEA,oCAAC,OAAI,WAAW,KACZ,oCAAC,QAAK,OAAM,aAAU,OAAK,GAC3B,oCAAC,QAAK,OAAO,MAAM,QAAQ,MAAI,QAAE,WAAY,GAC7C,oCAAC,QAAK,OAAM,aAAU,KAAG,GACzB,oCAAC,QAAK,OAAM,aAAW,UAAW,GAEjC,mBAAmB,UAAa,kBAAkB,UAC/C,0DACI,oCAAC,QAAK,OAAM,aAAU,YAAK,GAC3B,oCAAC,QAAK,OAAM,aAAU,UAAQ,GAC9B,oCAAC,QAAK,OAAM,WAAU,MAAI,QAAE,cAAe,GAC3C,oCAAC,QAAK,OAAM,aAAU,KAAG,GACzB,oCAAC,QAAK,OAAM,aAAW,aAAc,CACzC,CAER;AAAA,IAEC,mBACG,oCAAC,OAAI,WAAW,KACZ,oCAAC,QAAK,OAAM,WAAU,MAAK,kBACtB,gBAAgB,SAAS,KACpB,gBAAgB,UAAU,GAAG,EAAE,IAAI,QACnC,eACV,CACJ;AAAA,EAER;AAER;AAEA,IAAO,gCAAQ;","names":[]}